diff --git a/.github/workflows/build-and-test-alpha.yml b/.github/workflows/build-and-test-alpha.yml new file mode 100644 index 000000000..10b253f0b --- /dev/null +++ b/.github/workflows/build-and-test-alpha.yml @@ -0,0 +1,140 @@ +name: Build + Test v3 alpha + +on: + push: + branches: [v3-alpha] + paths-ignore: + - 'mkdocs-website/**/*' + workflow_dispatch: + +jobs: + test_go: + name: Run Go Tests + if: github.repository == 'wailsapp/wails' + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [windows-latest, macos-latest, ubuntu-latest] + go-version: [1.21] + + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Install linux dependencies + if: matrix.os == 'ubuntu-latest' + run: sudo apt-get update -y && sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.0-dev libayatana-appindicator3-dev build-essential pkg-config + + - name: Setup Go + uses: actions/setup-go@v3 + with: + go-version: ${{ matrix.go-version }} + + - name: Install Task + uses: arduino/setup-task@v1 + with: + version: 3.x + repo-token: ${{ secrets.GITHUB_TOKEN }} + + - name: Build Examples + working-directory: ./v3 + run: task test:examples + + - name: Run tests (mac) + if: matrix.os == 'macos-latest' + env: + CGO_LDFLAGS: -framework UniformTypeIdentifiers -mmacosx-version-min=10.13 + working-directory: ./v3 + run: go test -v ./... + + - name: Run tests (!mac) + if: matrix.os != 'macos-latest' + working-directory: ./v3 + run: go test -v ./... + + test_js: + name: Run JS Tests + if: github.repository == 'wailsapp/wails' + runs-on: ubuntu-latest + strategy: + matrix: + node-version: [18.x] + + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v3 + with: + node-version: ${{ matrix.node-version }} + + - name: Install dependencies + run: npm install + working-directory: v2/internal/frontend/runtime + + - name: Run tests + run: npm test + working-directory: v2/internal/frontend/runtime + + test_templates: + name: Test Templates + needs: test_go + runs-on: ${{ matrix.os }} + strategy: + fail-fast: true + 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.21] + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Setup Go + uses: actions/setup-go@v3 + with: + go-version: ${{ matrix.go-version }} + + - name: Setup Golang caches + uses: actions/cache@v3 + with: + path: | + ~/.cache/go-build + ~/go/pkg/mod + key: ${{ runner.os }}-golang-${{ hashFiles('**/go.sum') }} + restore-keys: | + ${{ runner.os }}-golang- + + - name: Build Wails3 CLI + run: | + cd ./v3/cmd/wails3 + go install + wails3 -help + + - name: Install linux dependencies + if: matrix.os == 'ubuntu-latest' + run: sudo apt-get update -y && sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.0-dev libayatana-appindicator3-dev build-essential pkg-config + + - 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 diff --git a/.github/workflows/v3-docs.yml b/.github/workflows/v3-docs.yml new file mode 100644 index 000000000..434a4d89b --- /dev/null +++ b/.github/workflows/v3-docs.yml @@ -0,0 +1,35 @@ +name: v3 docs +on: + push: + branches: + - v3-alpha + paths: + - 'mkdocs-website/**/*' +permissions: + contents: write +jobs: + deploy: + runs-on: ubuntu-latest + if: github.event.repository.fork == false + defaults: + run: + working-directory: ./mkdocs-website + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-python@v4 + with: + python-version: 3.x + - run: echo "cache_id=$(date --utc '+%V')" >> $GITHUB_ENV + - uses: actions/cache@v3 + with: + key: mkdocs-material-${{ env.cache_id }} + path: .cache + restore-keys: | + mkdocs-material- + - run: sudo apt-get install pngquant + - run: pip install pillow cairosvg + - run: pip install git+https://${GH_TOKEN}@github.com/squidfunk/mkdocs-material-insiders.git + - run: mkdocs build --config-file mkdocs.insiders.yml + - run: mkdocs gh-deploy --force +env: + GH_TOKEN: ${{ secrets.GH_TOKEN }} diff --git a/CNAME b/CNAME new file mode 100644 index 000000000..23caa94af --- /dev/null +++ b/CNAME @@ -0,0 +1 @@ +v3alpha.wails.io \ No newline at end of file diff --git a/Taskfile.yaml b/Taskfile.yaml index 7cc165825..03e6e2208 100644 --- a/Taskfile.yaml +++ b/Taskfile.yaml @@ -16,6 +16,11 @@ includes: dir: v3 optional: true + docs: + taskfile: mkdocs-website + dir: mkdocs-website + optional: true + tasks: contributors:check: cmds: diff --git a/mkdocs-website/CNAME b/mkdocs-website/CNAME new file mode 100644 index 000000000..23caa94af --- /dev/null +++ b/mkdocs-website/CNAME @@ -0,0 +1 @@ +v3alpha.wails.io \ No newline at end of file diff --git a/mkdocs-website/README.md b/mkdocs-website/README.md new file mode 100644 index 000000000..f4901146a --- /dev/null +++ b/mkdocs-website/README.md @@ -0,0 +1,45 @@ +# v3 Docs + +This is the documentation for Wails v3. It is currently a work in progress. + +If you do not wish to build it locally, it is available online at +[https://wailsapp.github.io/wails/](https://wailsapp.github.io/wails/). + +## Recommended Setup Steps + +Install the wails3 CLI if you haven't already: + +```shell +go install github.com/wailsapp/wails/v3/cmd/wails3@latest +``` + +The documentation uses mkdocs, so you will need to install +[Python](https://www.python.org/). Once installed, you can setup the +documentation by running the following command: + +```bash +wails3 task docs:setup +``` + +This will install the required dependencies for you. + +If you have installed the wails3 CLI, you can run the following command to build +the documentation and serve it locally: + +```bash +wails3 task docs:serve +``` + +### Manual Setup + +To install manually, you will need to do the following: + +- Install [Python](https://www.python.org/) +- Run `pip install -r requirements.txt` to install the required dependencies +- Run `mkdocs serve` to serve the documentation locally +- Run `mkdocs build` to build the documentation + +## Contributing + +If you would like to contribute to the documentation, please feel free to open a +PR! diff --git a/mkdocs-website/Taskfile.yml b/mkdocs-website/Taskfile.yml new file mode 100644 index 000000000..605e51c68 --- /dev/null +++ b/mkdocs-website/Taskfile.yml @@ -0,0 +1,59 @@ +# https://taskfile.dev + +version: '3' + +tasks: + + setup: + summary: Setup the project + preconditions: + - sh: python{{exeExt}} --version + msg: "Looks like Python isn't installed. Python is required to build the documentation: https://www.python.org/downloads/" + cmds: + - python -m pip install -r requirements.txt --user + + setup:insiders: + summary: Setup the project (insiders) + preconditions: + - sh: python{{exeExt}} --version + msg: "Looks like Python isn't installed. Python is required to build the documentation: https://www.python.org/downloads/" + cmds: + - python -m pip install -r requirements.insiders.txt --user + + upgrade:insiders: + summary: Upgrade the project (insiders) + preconditions: + - sh: python{{exeExt}} --version + msg: "Looks like Python isn't installed. Python is required to build the documentation: https://www.python.org/downloads/" + cmds: + - python -m pip install -r requirements.insiders.txt --upgrade --user + + build: + summary: Builds the documentation + preconditions: + - sh: mkdocs --version + msg: "Looks like mkdocs isn't installed. Run `wails3 task setup` or `task setup` in the documentation directory to install it." + cmds: + - mkdocs build + + serve: + summary: Builds the documentation and serves it locally + preconditions: + - sh: mkdocs --version + msg: "Looks like mkdocs isn't installed. Run `wails3 task setup` or `task setup` in the documentation directory to install it." + cmds: + - mkdocs serve + + serve:insiders: + summary: Builds the documentation and serves it locally + preconditions: + - sh: mkdocs --version + msg: "Looks like mkdocs isn't installed. Run `wails3 task setup` or `task setup` in the documentation directory to install it." + cmds: + - mkdocs serve --config-file mkdocs.insiders.yml + + update:api: + summary: Updates the API documentation + dir: generate + cmds: + - go run . diff --git a/mkdocs-website/docs/CNAME b/mkdocs-website/docs/CNAME new file mode 100644 index 000000000..23caa94af --- /dev/null +++ b/mkdocs-website/docs/CNAME @@ -0,0 +1 @@ +v3alpha.wails.io \ No newline at end of file diff --git a/mkdocs-website/docs/en/API/application.md b/mkdocs-website/docs/en/API/application.md new file mode 100644 index 000000000..577aa2106 --- /dev/null +++ b/mkdocs-website/docs/en/API/application.md @@ -0,0 +1,358 @@ +# Application + +The application API assists in creating an application using the Wails framework. + +### New + +API: `New(appOptions Options) *App` + +`New(appOptions Options)` creates a new application using the given application +options . It applies default values for unspecified options, merges them with +the provided ones, initializes and returns an instance of the application. + +In case of an error during initialization, the application is stopped with the +error message provided. + +It should be noted that if a global application instance already exists, that +instance will be returned instead of creating a new one. + +```go title="main.go" hl_lines="6-9" +package main + +import "github.com/wailsapp/wails/v3/pkg/application" + +func main() { + app := application.New(application.Options{ + Name: "WebviewWindow Demo", + // Other options + }) + + // Rest of application +} +``` + +### Get + +`Get()` returns the global application instance. It's useful when you need to +access the application from different parts of your code. + +```go + // Get the application instance + app := application.Get() +``` + +### Capabilities + +API: `Capabilities() capabilities.Capabilities` + +`Capabilities()` retrieves a map of capabilities that the application currently +has. Capabilities can be about different features the operating system provides, +like webview features. + +```go + // Get the application capabilities + capabilities := app.Capabilities() + if capabilities.HasNativeDrag { + // Do something + } +``` + +### GetPID + +API: `GetPID() int` + +`GetPID()` returns the Process ID of the application. + +```go + pid := app.GetPID() +``` + +### Run + +API: `Run() error` + +`Run()` starts the execution of the application and its components. + +```go + app := application.New(application.Options{ + //options + }) + // Run the application + err := application.Run() + if err != nil { + // Handle error + } +``` + +### Quit + +API: `Quit()` + +`Quit()` quits the application by destroying windows and potentially other +components. + +```go + // Quit the application + app.Quit() +``` + +### IsDarkMode + +API: `IsDarkMode() bool` + +`IsDarkMode()` checks if the application is running in dark mode. It returns a +boolean indicating whether dark mode is enabled. + +```go + // Check if dark mode is enabled + if app.IsDarkMode() { + // Do something + } +``` + +### Hide + +API: `Hide()` + +`Hide()` hides the application window. + +```go + // Hide the application window + app.Hide() +``` + +### Show + +API: `Show()` + +`Show()` shows the application window. + +```go + // Show the application window + app.Show() +``` + +### NewWebviewWindow + +API: `NewWebviewWindow() *WebviewWindow` + +`NewWebviewWindow()` creates a new Webview window with default options, and +returns it. + +```go + // Create a new webview window + window := app.NewWebviewWindow() +``` + +### NewWebviewWindowWithOptions + +API: +`NewWebviewWindowWithOptions(windowOptions WebviewWindowOptions) *WebviewWindow` + +`NewWebviewWindowWithOptions()` creates a new webview window with custom +options. The newly created window is added to a map of windows managed by the +application. + +```go + // Create a new webview window with custom options + window := app.NewWebviewWindowWithOptions(WebviewWindowOptions{ + Name: "Main", + Title: "My Window", + Width: 800, + Height: 600, + }) +``` + +### OnWindowCreation + +API: `OnWindowCreation(callback func(window *WebviewWindow))` + +`OnWindowCreation()` registers a callback function to be called when a window is +created. + +```go + // Register a callback to be called when a window is created + app.OnWindowCreation(func(window *WebviewWindow) { + // Do something + }) +``` + +### GetWindowByName + +API: `GetWindowByName(name string) *WebviewWindow` + +`GetWindowByName()` fetches and returns a window with a specific name. + +```go + // Get a window by name + window := app.GetWindowByName("Main") +``` + +### CurrentWindow + +API: `CurrentWindow() *WebviewWindow` + +`CurrentWindow()` fetches and returns a pointer to the currently active window +in the application. If there is no window, it returns nil. + +```go + // Get the current window + window := app.CurrentWindow() +``` + +### RegisterContextMenu + +API: `RegisterContextMenu(name string, menu *Menu)` + +`RegisterContextMenu()` registers a context menu with a given name. This menu +can be used later in the application. + +```go + + // Create a new menu + ctxmenu := app.NewMenu() + + // Register the menu as a context menu + app.RegisterContextMenu("MyContextMenu", ctxmenu) +``` + +### SetMenu + +API: `SetMenu(menu *Menu)` + +`SetMenu()` sets the menu for the application. On Mac, this will be the global +menu. For Windows and Linux, this will be the default menu for any new window +created. + +```go + // Create a new menu + menu := app.NewMenu() + + // Set the menu for the application + app.SetMenu(menu) +``` + +### ShowAboutDialog + +API: `ShowAboutDialog()` + +`ShowAboutDialog()` shows an "About" dialog box. It can show the application's +name, description and icon. + +```go + // Show the about dialog + app.ShowAboutDialog() +``` + +### Info + +API: `InfoDialog()` + +`InfoDialog()` creates and returns a new instance of `MessageDialog` with an +`InfoDialogType`. This dialog is typically used to display informational +messages to the user. + +### Question + +API: `QuestionDialog()` + +`QuestionDialog()` creates and returns a new instance of `MessageDialog` with a +`QuestionDialogType`. This dialog is often used to ask a question to the user +and expect a response. + +### Warning + +API: `WarningDialog()` + +`WarningDialog()` creates and returns a new instance of `MessageDialog` with a +`WarningDialogType`. As the name suggests, this dialog is primarily used to +display warning messages to the user. + +### Error + +API: `ErrorDialog()` + +`ErrorDialog()` creates and returns a new instance of `MessageDialog` with an +`ErrorDialogType`. This dialog is designed to be used when you need to display +an error message to the user. + +### OpenFile + +API: `OpenFileDialog()` + +`OpenFileDialog()` creates and returns a new `OpenFileDialogStruct`. This dialog +prompts the user to select one or more files from their file system. + +### SaveFile + +API: `SaveFileDialog()` + +`SaveFileDialog()` creates and returns a new `SaveFileDialogStruct`. This dialog +prompts the user to choose a location on their file system where a file should +be saved. + +### OpenDirectory + +API: `OpenDirectoryDialog()` + +`OpenDirectoryDialog()` creates and returns a new instance of `MessageDialog` +with an `OpenDirectoryDialogType`. This dialog enables the user to choose a +directory from their file system. + +### On + +API: +`On(eventType events.ApplicationEventType, callback func(event *Event)) func()` + +`On()` registers an event listener for specific application events. The callback +function provided will be triggered when the corresponding event occurs. The +function returns a function that can be called to remove the listener. + +### RegisterHook + +API: +`RegisterHook(eventType events.ApplicationEventType, callback func(event *Event)) func()` + +`RegisterHook()` registers a callback to be run as a hook during specific +events. These hooks are run before listeners attached with `On()`. The function +returns a function that can be called to remove the hook. + +### GetPrimaryScreen + +API: `GetPrimaryScreen() (*Screen, error)` + +`GetPrimaryScreen()` returns the primary screen of the system. + +### GetScreens + +API: `GetScreens() ([]*Screen, error)` + +`GetScreens()` returns information about all screens attached to the system. + +This is a brief summary of the exported methods in the provided `App` struct. Do +note that for more detailed functionality or considerations, refer to the actual +Go code or further internal documentation. + +## Options + +```go title="application_options.go" +--8<-- +../v3/pkg/application/options_application.go +--8<-- +``` + +### Windows Options + +```go title="application_options_windows.go" +--8<-- +../v3/pkg/application/options_application_win.go +--8<-- +``` + +### Mac Options + +```go title="options_application_mac.go" +--8<-- +../v3/pkg/application/options_application_mac.go +--8<-- +``` diff --git a/mkdocs-website/docs/en/API/fullapi.md b/mkdocs-website/docs/en/API/fullapi.md new file mode 100644 index 000000000..4193f1ab5 --- /dev/null +++ b/mkdocs-website/docs/en/API/fullapi.md @@ -0,0 +1,4336 @@ +# application + +```go +import "github.com/wailsapp/wails/v3/pkg/application" +``` + +## Index + +- [Constants](<#constants>) +- [Variables](<#variables>) +- [func DefaultLogger\(level slog.Level\) \*slog.Logger](<#DefaultLogger>) +- [func Fatal\(message string, args ...interface\{\}\)](<#Fatal>) +- [func InvokeAsync\(fn func\(\)\)](<#InvokeAsync>) +- [func InvokeSync\(fn func\(\)\)](<#InvokeSync>) +- [func InvokeSyncWithError\(fn func\(\) error\) \(err error\)](<#InvokeSyncWithError>) +- [func InvokeSyncWithResult\[T any\]\(fn func\(\) T\) \(res T\)](<#InvokeSyncWithResult>) +- [func InvokeSyncWithResultAndError\[T any\]\(fn func\(\) \(T, error\)\) \(res T, err error\)](<#InvokeSyncWithResultAndError>) +- [func NewIconFromResource\(instance w32.HINSTANCE, resId uint16\) \(w32.HICON, error\)](<#NewIconFromResource>) +- [func ScaleToDefaultDPI\(pixels int, dpi uint\) int](<#ScaleToDefaultDPI>) +- [func ScaleWithDPI\(pixels int, dpi uint\) int](<#ScaleWithDPI>) +- [type ActivationPolicy](<#ActivationPolicy>) +- [type App](<#App>) + - [func Get\(\) \*App](<#Get>) + - [func New\(appOptions Options\) \*App](<#New>) + - [func \(a \*App\) Capabilities\(\) capabilities.Capabilities](<#App.Capabilities>) + - [func \(a \*App\) Clipboard\(\) \*Clipboard](<#App.Clipboard>) + - [func \(a \*App\) CurrentWindow\(\) \*WebviewWindow](<#App.CurrentWindow>) + - [func \(a \*App\) GetPID\(\) int](<#App.GetPID>) + - [func \(a \*App\) GetPrimaryScreen\(\) \(\*Screen, error\)](<#App.GetPrimaryScreen>) + - [func \(a \*App\) GetScreens\(\) \(\[\]\*Screen, error\)](<#App.GetScreens>) + - [func \(a \*App\) GetWindowByName\(name string\) \*WebviewWindow](<#App.GetWindowByName>) + - [func \(a \*App\) Hide\(\)](<#App.Hide>) + - [func \(a \*App\) IsDarkMode\(\) bool](<#App.IsDarkMode>) + - [func \(a \*App\) NewMenu\(\) \*Menu](<#App.NewMenu>) + - [func \(a \*App\) NewSystemTray\(\) \*SystemTray](<#App.NewSystemTray>) + - [func \(a \*App\) NewWebviewWindow\(\) \*WebviewWindow](<#App.NewWebviewWindow>) + - [func \(a \*App\) NewWebviewWindowWithOptions\(windowOptions WebviewWindowOptions\) \*WebviewWindow](<#App.NewWebviewWindowWithOptions>) + - [func \(a \*App\) On\(eventType events.ApplicationEventType, callback func\(event \*Event\)\) func\(\)](<#App.On>) + - [func \(a \*App\) OnWindowCreation\(callback func\(window \*WebviewWindow\)\)](<#App.OnWindowCreation>) + - [func \(a \*App\) Quit\(\)](<#App.Quit>) + - [func \(a \*App\) RegisterContextMenu\(name string, menu \*Menu\)](<#App.RegisterContextMenu>) + - [func \(a \*App\) RegisterHook\(eventType events.ApplicationEventType, callback func\(event \*Event\)\) func\(\)](<#App.RegisterHook>) + - [func \(a \*App\) Run\(\) error](<#App.Run>) + - [func \(a \*App\) SetMenu\(menu \*Menu\)](<#App.SetMenu>) + - [func \(a \*App\) Show\(\)](<#App.Show>) + - [func \(a \*App\) ShowAboutDialog\(\)](<#App.ShowAboutDialog>) +- [type ApplicationEventContext](<#ApplicationEventContext>) + - [func \(c ApplicationEventContext\) IsDarkMode\(\) bool](<#ApplicationEventContext.IsDarkMode>) + - [func \(c ApplicationEventContext\) OpenedFiles\(\) \[\]string](<#ApplicationEventContext.OpenedFiles>) +- [type Args](<#Args>) + - [func \(a \*Args\) Bool\(s string\) \*bool](<#Args.Bool>) + - [func \(a \*Args\) Float64\(s string\) \*float64](<#Args.Float64>) + - [func \(a \*Args\) Int\(s string\) \*int](<#Args.Int>) + - [func \(a \*Args\) String\(key string\) \*string](<#Args.String>) + - [func \(a \*Args\) UInt\(s string\) \*uint](<#Args.UInt>) + - [func \(a \*Args\) UInt8\(s string\) \*uint8](<#Args.UInt8>) +- [type AssetOptions](<#AssetOptions>) +- [type BackdropType](<#BackdropType>) +- [type BackgroundType](<#BackgroundType>) +- [type Bindings](<#Bindings>) + - [func NewBindings\(structs \[\]any, aliases map\[uint32\]uint32\) \(\*Bindings, error\)](<#NewBindings>) + - [func \(b \*Bindings\) Add\(structPtr interface\{\}\) error](<#Bindings.Add>) + - [func \(b \*Bindings\) AddPlugins\(plugins map\[string\]Plugin\) error](<#Bindings.AddPlugins>) + - [func \(b \*Bindings\) GenerateID\(name string\) \(uint32, error\)](<#Bindings.GenerateID>) + - [func \(b \*Bindings\) Get\(options \*CallOptions\) \*BoundMethod](<#Bindings.Get>) + - [func \(b \*Bindings\) GetByID\(id uint32\) \*BoundMethod](<#Bindings.GetByID>) +- [type BoundMethod](<#BoundMethod>) + - [func \(b \*BoundMethod\) Call\(args \[\]interface\{\}\) \(returnValue interface\{\}, err error\)](<#BoundMethod.Call>) + - [func \(b \*BoundMethod\) String\(\) string](<#BoundMethod.String>) +- [type Button](<#Button>) + - [func \(b \*Button\) OnClick\(callback func\(\)\) \*Button](<#Button.OnClick>) + - [func \(b \*Button\) SetAsCancel\(\) \*Button](<#Button.SetAsCancel>) + - [func \(b \*Button\) SetAsDefault\(\) \*Button](<#Button.SetAsDefault>) +- [type CallOptions](<#CallOptions>) + - [func \(c CallOptions\) Name\(\) string](<#CallOptions.Name>) +- [type Clipboard](<#Clipboard>) + - [func \(c \*Clipboard\) SetText\(text string\) bool](<#Clipboard.SetText>) + - [func \(c \*Clipboard\) Text\(\) \(string, bool\)](<#Clipboard.Text>) +- [type Context](<#Context>) + - [func \(c \*Context\) ClickedMenuItem\(\) \*MenuItem](<#Context.ClickedMenuItem>) + - [func \(c \*Context\) ContextMenuData\(\) any](<#Context.ContextMenuData>) + - [func \(c \*Context\) IsChecked\(\) bool](<#Context.IsChecked>) +- [type ContextMenuData](<#ContextMenuData>) +- [type DialogType](<#DialogType>) +- [type Event](<#Event>) + - [func \(w \*Event\) Cancel\(\)](<#Event.Cancel>) + - [func \(w \*Event\) Context\(\) \*ApplicationEventContext](<#Event.Context>) +- [type EventListener](<#EventListener>) +- [type EventProcessor](<#EventProcessor>) + - [func NewWailsEventProcessor\(dispatchEventToWindows func\(\*WailsEvent\)\) \*EventProcessor](<#NewWailsEventProcessor>) + - [func \(e \*EventProcessor\) Emit\(thisEvent \*WailsEvent\)](<#EventProcessor.Emit>) + - [func \(e \*EventProcessor\) Off\(eventName string\)](<#EventProcessor.Off>) + - [func \(e \*EventProcessor\) OffAll\(\)](<#EventProcessor.OffAll>) + - [func \(e \*EventProcessor\) On\(eventName string, callback func\(event \*WailsEvent\)\) func\(\)](<#EventProcessor.On>) + - [func \(e \*EventProcessor\) OnMultiple\(eventName string, callback func\(event \*WailsEvent\), counter int\) func\(\)](<#EventProcessor.OnMultiple>) + - [func \(e \*EventProcessor\) Once\(eventName string, callback func\(event \*WailsEvent\)\) func\(\)](<#EventProcessor.Once>) + - [func \(e \*EventProcessor\) RegisterHook\(eventName string, callback func\(\*WailsEvent\)\) func\(\)](<#EventProcessor.RegisterHook>) +- [type FileFilter](<#FileFilter>) +- [type IconPosition](<#IconPosition>) +- [type MacAppearanceType](<#MacAppearanceType>) +- [type MacBackdrop](<#MacBackdrop>) +- [type MacOptions](<#MacOptions>) +- [type MacTitleBar](<#MacTitleBar>) +- [type MacToolbarStyle](<#MacToolbarStyle>) +- [type MacWindow](<#MacWindow>) +- [type Menu](<#Menu>) + - [func NewMenu\(\) \*Menu](<#NewMenu>) + - [func \(m \*Menu\) Add\(label string\) \*MenuItem](<#Menu.Add>) + - [func \(m \*Menu\) AddCheckbox\(label string, enabled bool\) \*MenuItem](<#Menu.AddCheckbox>) + - [func \(m \*Menu\) AddRadio\(label string, enabled bool\) \*MenuItem](<#Menu.AddRadio>) + - [func \(m \*Menu\) AddRole\(role Role\) \*Menu](<#Menu.AddRole>) + - [func \(m \*Menu\) AddSeparator\(\)](<#Menu.AddSeparator>) + - [func \(m \*Menu\) AddSubmenu\(s string\) \*Menu](<#Menu.AddSubmenu>) + - [func \(m \*Menu\) SetLabel\(label string\)](<#Menu.SetLabel>) + - [func \(m \*Menu\) Update\(\)](<#Menu.Update>) +- [type MenuItem](<#MenuItem>) + - [func \(m \*MenuItem\) Checked\(\) bool](<#MenuItem.Checked>) + - [func \(m \*MenuItem\) Enabled\(\) bool](<#MenuItem.Enabled>) + - [func \(m \*MenuItem\) Hidden\(\) bool](<#MenuItem.Hidden>) + - [func \(m \*MenuItem\) IsCheckbox\(\) bool](<#MenuItem.IsCheckbox>) + - [func \(m \*MenuItem\) IsRadio\(\) bool](<#MenuItem.IsRadio>) + - [func \(m \*MenuItem\) IsSeparator\(\) bool](<#MenuItem.IsSeparator>) + - [func \(m \*MenuItem\) IsSubmenu\(\) bool](<#MenuItem.IsSubmenu>) + - [func \(m \*MenuItem\) Label\(\) string](<#MenuItem.Label>) + - [func \(m \*MenuItem\) OnClick\(f func\(\*Context\)\) \*MenuItem](<#MenuItem.OnClick>) + - [func \(m \*MenuItem\) SetAccelerator\(shortcut string\) \*MenuItem](<#MenuItem.SetAccelerator>) + - [func \(m \*MenuItem\) SetChecked\(checked bool\) \*MenuItem](<#MenuItem.SetChecked>) + - [func \(m \*MenuItem\) SetEnabled\(enabled bool\) \*MenuItem](<#MenuItem.SetEnabled>) + - [func \(m \*MenuItem\) SetHidden\(hidden bool\) \*MenuItem](<#MenuItem.SetHidden>) + - [func \(m \*MenuItem\) SetLabel\(s string\) \*MenuItem](<#MenuItem.SetLabel>) + - [func \(m \*MenuItem\) SetTooltip\(s string\) \*MenuItem](<#MenuItem.SetTooltip>) + - [func \(m \*MenuItem\) Tooltip\(\) string](<#MenuItem.Tooltip>) +- [type MessageDialog](<#MessageDialog>) + - [func ErrorDialog\(\) \*MessageDialog](<#ErrorDialog>) + - [func InfoDialog\(\) \*MessageDialog](<#InfoDialog>) + - [func OpenDirectoryDialog\(\) \*MessageDialog](<#OpenDirectoryDialog>) + - [func QuestionDialog\(\) \*MessageDialog](<#QuestionDialog>) + - [func WarningDialog\(\) \*MessageDialog](<#WarningDialog>) + - [func \(d \*MessageDialog\) AddButton\(s string\) \*Button](<#MessageDialog.AddButton>) + - [func \(d \*MessageDialog\) AddButtons\(buttons \[\]\*Button\) \*MessageDialog](<#MessageDialog.AddButtons>) + - [func \(d \*MessageDialog\) AttachToWindow\(window \*WebviewWindow\) \*MessageDialog](<#MessageDialog.AttachToWindow>) + - [func \(d \*MessageDialog\) SetCancelButton\(button \*Button\) \*MessageDialog](<#MessageDialog.SetCancelButton>) + - [func \(d \*MessageDialog\) SetDefaultButton\(button \*Button\) \*MessageDialog](<#MessageDialog.SetDefaultButton>) + - [func \(d \*MessageDialog\) SetIcon\(icon \[\]byte\) \*MessageDialog](<#MessageDialog.SetIcon>) + - [func \(d \*MessageDialog\) SetMessage\(message string\) \*MessageDialog](<#MessageDialog.SetMessage>) + - [func \(d \*MessageDialog\) SetTitle\(title string\) \*MessageDialog](<#MessageDialog.SetTitle>) + - [func \(d \*MessageDialog\) Show\(\)](<#MessageDialog.Show>) +- [type MessageDialogOptions](<#MessageDialogOptions>) +- [type MessageProcessor](<#MessageProcessor>) + - [func NewMessageProcessor\(logger \*slog.Logger\) \*MessageProcessor](<#NewMessageProcessor>) + - [func \(m \*MessageProcessor\) Error\(message string, args ...any\)](<#MessageProcessor.Error>) + - [func \(m \*MessageProcessor\) HandleRuntimeCall\(rw http.ResponseWriter, r \*http.Request\)](<#MessageProcessor.HandleRuntimeCall>) + - [func \(m \*MessageProcessor\) HandleRuntimeCallWithIDs\(rw http.ResponseWriter, r \*http.Request\)](<#MessageProcessor.HandleRuntimeCallWithIDs>) + - [func \(m \*MessageProcessor\) Info\(message string, args ...any\)](<#MessageProcessor.Info>) +- [type Middleware](<#Middleware>) + - [func ChainMiddleware\(middleware ...Middleware\) Middleware](<#ChainMiddleware>) +- [type OpenFileDialogOptions](<#OpenFileDialogOptions>) +- [type OpenFileDialogStruct](<#OpenFileDialogStruct>) + - [func OpenFileDialog\(\) \*OpenFileDialogStruct](<#OpenFileDialog>) + - [func OpenFileDialogWithOptions\(options \*OpenFileDialogOptions\) \*OpenFileDialogStruct](<#OpenFileDialogWithOptions>) + - [func \(d \*OpenFileDialogStruct\) AddFilter\(displayName, pattern string\) \*OpenFileDialogStruct](<#OpenFileDialogStruct.AddFilter>) + - [func \(d \*OpenFileDialogStruct\) AllowsOtherFileTypes\(allowsOtherFileTypes bool\) \*OpenFileDialogStruct](<#OpenFileDialogStruct.AllowsOtherFileTypes>) + - [func \(d \*OpenFileDialogStruct\) AttachToWindow\(window \*WebviewWindow\) \*OpenFileDialogStruct](<#OpenFileDialogStruct.AttachToWindow>) + - [func \(d \*OpenFileDialogStruct\) CanChooseDirectories\(canChooseDirectories bool\) \*OpenFileDialogStruct](<#OpenFileDialogStruct.CanChooseDirectories>) + - [func \(d \*OpenFileDialogStruct\) CanChooseFiles\(canChooseFiles bool\) \*OpenFileDialogStruct](<#OpenFileDialogStruct.CanChooseFiles>) + - [func \(d \*OpenFileDialogStruct\) CanCreateDirectories\(canCreateDirectories bool\) \*OpenFileDialogStruct](<#OpenFileDialogStruct.CanCreateDirectories>) + - [func \(d \*OpenFileDialogStruct\) CanSelectHiddenExtension\(canSelectHiddenExtension bool\) \*OpenFileDialogStruct](<#OpenFileDialogStruct.CanSelectHiddenExtension>) + - [func \(d \*OpenFileDialogStruct\) HideExtension\(hideExtension bool\) \*OpenFileDialogStruct](<#OpenFileDialogStruct.HideExtension>) + - [func \(d \*OpenFileDialogStruct\) PromptForMultipleSelection\(\) \(\[\]string, error\)](<#OpenFileDialogStruct.PromptForMultipleSelection>) + - [func \(d \*OpenFileDialogStruct\) PromptForSingleSelection\(\) \(string, error\)](<#OpenFileDialogStruct.PromptForSingleSelection>) + - [func \(d \*OpenFileDialogStruct\) ResolvesAliases\(resolvesAliases bool\) \*OpenFileDialogStruct](<#OpenFileDialogStruct.ResolvesAliases>) + - [func \(d \*OpenFileDialogStruct\) SetButtonText\(text string\) \*OpenFileDialogStruct](<#OpenFileDialogStruct.SetButtonText>) + - [func \(d \*OpenFileDialogStruct\) SetDirectory\(directory string\) \*OpenFileDialogStruct](<#OpenFileDialogStruct.SetDirectory>) + - [func \(d \*OpenFileDialogStruct\) SetMessage\(message string\) \*OpenFileDialogStruct](<#OpenFileDialogStruct.SetMessage>) + - [func \(d \*OpenFileDialogStruct\) SetOptions\(options \*OpenFileDialogOptions\)](<#OpenFileDialogStruct.SetOptions>) + - [func \(d \*OpenFileDialogStruct\) SetTitle\(title string\) \*OpenFileDialogStruct](<#OpenFileDialogStruct.SetTitle>) + - [func \(d \*OpenFileDialogStruct\) ShowHiddenFiles\(showHiddenFiles bool\) \*OpenFileDialogStruct](<#OpenFileDialogStruct.ShowHiddenFiles>) + - [func \(d \*OpenFileDialogStruct\) TreatsFilePackagesAsDirectories\(treatsFilePackagesAsDirectories bool\) \*OpenFileDialogStruct](<#OpenFileDialogStruct.TreatsFilePackagesAsDirectories>) +- [type Options](<#Options>) +- [type Parameter](<#Parameter>) + - [func \(p \*Parameter\) IsError\(\) bool](<#Parameter.IsError>) + - [func \(p \*Parameter\) IsType\(typename string\) bool](<#Parameter.IsType>) +- [type Plugin](<#Plugin>) +- [type PluginCallOptions](<#PluginCallOptions>) +- [type PluginManager](<#PluginManager>) + - [func NewPluginManager\(plugins map\[string\]Plugin, assetServer \*assetserver.AssetServer\) \*PluginManager](<#NewPluginManager>) + - [func \(p \*PluginManager\) Init\(\) error](<#PluginManager.Init>) + - [func \(p \*PluginManager\) Shutdown\(\)](<#PluginManager.Shutdown>) +- [type PositionOptions](<#PositionOptions>) +- [type QueryParams](<#QueryParams>) + - [func \(qp QueryParams\) Args\(\) \(\*Args, error\)](<#QueryParams.Args>) + - [func \(qp QueryParams\) Bool\(key string\) \*bool](<#QueryParams.Bool>) + - [func \(qp QueryParams\) Float64\(key string\) \*float64](<#QueryParams.Float64>) + - [func \(qp QueryParams\) Int\(key string\) \*int](<#QueryParams.Int>) + - [func \(qp QueryParams\) String\(key string\) \*string](<#QueryParams.String>) + - [func \(qp QueryParams\) ToStruct\(str any\) error](<#QueryParams.ToStruct>) + - [func \(qp QueryParams\) UInt\(key string\) \*uint](<#QueryParams.UInt>) + - [func \(qp QueryParams\) UInt8\(key string\) \*uint8](<#QueryParams.UInt8>) +- [type RGBA](<#RGBA>) +- [type RadioGroup](<#RadioGroup>) + - [func \(r \*RadioGroup\) Add\(id int, item \*MenuItem\)](<#RadioGroup.Add>) + - [func \(r \*RadioGroup\) Bounds\(\) \(int, int\)](<#RadioGroup.Bounds>) + - [func \(r \*RadioGroup\) MenuID\(item \*MenuItem\) int](<#RadioGroup.MenuID>) +- [type RadioGroupMember](<#RadioGroupMember>) +- [type Rect](<#Rect>) +- [type Role](<#Role>) +- [type SaveFileDialogOptions](<#SaveFileDialogOptions>) +- [type SaveFileDialogStruct](<#SaveFileDialogStruct>) + - [func SaveFileDialog\(\) \*SaveFileDialogStruct](<#SaveFileDialog>) + - [func SaveFileDialogWithOptions\(s \*SaveFileDialogOptions\) \*SaveFileDialogStruct](<#SaveFileDialogWithOptions>) + - [func \(d \*SaveFileDialogStruct\) AddFilter\(displayName, pattern string\) \*SaveFileDialogStruct](<#SaveFileDialogStruct.AddFilter>) + - [func \(d \*SaveFileDialogStruct\) AllowsOtherFileTypes\(allowOtherFileTypes bool\) \*SaveFileDialogStruct](<#SaveFileDialogStruct.AllowsOtherFileTypes>) + - [func \(d \*SaveFileDialogStruct\) AttachToWindow\(window \*WebviewWindow\) \*SaveFileDialogStruct](<#SaveFileDialogStruct.AttachToWindow>) + - [func \(d \*SaveFileDialogStruct\) CanCreateDirectories\(canCreateDirectories bool\) \*SaveFileDialogStruct](<#SaveFileDialogStruct.CanCreateDirectories>) + - [func \(d \*SaveFileDialogStruct\) CanSelectHiddenExtension\(canSelectHiddenExtension bool\) \*SaveFileDialogStruct](<#SaveFileDialogStruct.CanSelectHiddenExtension>) + - [func \(d \*SaveFileDialogStruct\) HideExtension\(hideExtension bool\) \*SaveFileDialogStruct](<#SaveFileDialogStruct.HideExtension>) + - [func \(d \*SaveFileDialogStruct\) PromptForSingleSelection\(\) \(string, error\)](<#SaveFileDialogStruct.PromptForSingleSelection>) + - [func \(d \*SaveFileDialogStruct\) SetButtonText\(text string\) \*SaveFileDialogStruct](<#SaveFileDialogStruct.SetButtonText>) + - [func \(d \*SaveFileDialogStruct\) SetDirectory\(directory string\) \*SaveFileDialogStruct](<#SaveFileDialogStruct.SetDirectory>) + - [func \(d \*SaveFileDialogStruct\) SetFilename\(filename string\) \*SaveFileDialogStruct](<#SaveFileDialogStruct.SetFilename>) + - [func \(d \*SaveFileDialogStruct\) SetMessage\(message string\) \*SaveFileDialogStruct](<#SaveFileDialogStruct.SetMessage>) + - [func \(d \*SaveFileDialogStruct\) SetOptions\(options \*SaveFileDialogOptions\)](<#SaveFileDialogStruct.SetOptions>) + - [func \(d \*SaveFileDialogStruct\) ShowHiddenFiles\(showHiddenFiles bool\) \*SaveFileDialogStruct](<#SaveFileDialogStruct.ShowHiddenFiles>) + - [func \(d \*SaveFileDialogStruct\) TreatsFilePackagesAsDirectories\(treatsFilePackagesAsDirectories bool\) \*SaveFileDialogStruct](<#SaveFileDialogStruct.TreatsFilePackagesAsDirectories>) +- [type Screen](<#Screen>) +- [type Size](<#Size>) +- [type SystemTray](<#SystemTray>) + - [func \(s \*SystemTray\) AttachWindow\(window \*WebviewWindow\) \*SystemTray](<#SystemTray.AttachWindow>) + - [func \(s \*SystemTray\) Destroy\(\)](<#SystemTray.Destroy>) + - [func \(s \*SystemTray\) Label\(\) string](<#SystemTray.Label>) + - [func \(s \*SystemTray\) OnClick\(handler func\(\)\) \*SystemTray](<#SystemTray.OnClick>) + - [func \(s \*SystemTray\) OnDoubleClick\(handler func\(\)\) \*SystemTray](<#SystemTray.OnDoubleClick>) + - [func \(s \*SystemTray\) OnMouseEnter\(handler func\(\)\) \*SystemTray](<#SystemTray.OnMouseEnter>) + - [func \(s \*SystemTray\) OnMouseLeave\(handler func\(\)\) \*SystemTray](<#SystemTray.OnMouseLeave>) + - [func \(s \*SystemTray\) OnRightClick\(handler func\(\)\) \*SystemTray](<#SystemTray.OnRightClick>) + - [func \(s \*SystemTray\) OnRightDoubleClick\(handler func\(\)\) \*SystemTray](<#SystemTray.OnRightDoubleClick>) + - [func \(s \*SystemTray\) OpenMenu\(\)](<#SystemTray.OpenMenu>) + - [func \(s \*SystemTray\) PositionWindow\(window \*WebviewWindow, offset int\) error](<#SystemTray.PositionWindow>) + - [func \(s \*SystemTray\) SetDarkModeIcon\(icon \[\]byte\) \*SystemTray](<#SystemTray.SetDarkModeIcon>) + - [func \(s \*SystemTray\) SetIcon\(icon \[\]byte\) \*SystemTray](<#SystemTray.SetIcon>) + - [func \(s \*SystemTray\) SetIconPosition\(iconPosition int\) \*SystemTray](<#SystemTray.SetIconPosition>) + - [func \(s \*SystemTray\) SetLabel\(label string\)](<#SystemTray.SetLabel>) + - [func \(s \*SystemTray\) SetMenu\(menu \*Menu\) \*SystemTray](<#SystemTray.SetMenu>) + - [func \(s \*SystemTray\) SetTemplateIcon\(icon \[\]byte\) \*SystemTray](<#SystemTray.SetTemplateIcon>) + - [func \(s \*SystemTray\) WindowDebounce\(debounce time.Duration\) \*SystemTray](<#SystemTray.WindowDebounce>) + - [func \(s \*SystemTray\) WindowOffset\(offset int\) \*SystemTray](<#SystemTray.WindowOffset>) +- [type Theme](<#Theme>) +- [type ThemeSettings](<#ThemeSettings>) +- [type WailsEvent](<#WailsEvent>) + - [func \(e \*WailsEvent\) Cancel\(\)](<#WailsEvent.Cancel>) +- [type WebviewWindow](<#WebviewWindow>) + - [func \(w \*WebviewWindow\) AbsolutePosition\(\) \(int, int\)](<#WebviewWindow.AbsolutePosition>) + - [func \(w \*WebviewWindow\) Center\(\)](<#WebviewWindow.Center>) + - [func \(w \*WebviewWindow\) Close\(\)](<#WebviewWindow.Close>) + - [func \(w \*WebviewWindow\) Destroy\(\)](<#WebviewWindow.Destroy>) + - [func \(w \*WebviewWindow\) ExecJS\(js string\)](<#WebviewWindow.ExecJS>) + - [func \(w \*WebviewWindow\) Flash\(enabled bool\)](<#WebviewWindow.Flash>) + - [func \(w \*WebviewWindow\) Focus\(\)](<#WebviewWindow.Focus>) + - [func \(w \*WebviewWindow\) ForceReload\(\)](<#WebviewWindow.ForceReload>) + - [func \(w \*WebviewWindow\) Fullscreen\(\) \*WebviewWindow](<#WebviewWindow.Fullscreen>) + - [func \(w \*WebviewWindow\) GetScreen\(\) \(\*Screen, error\)](<#WebviewWindow.GetScreen>) + - [func \(w \*WebviewWindow\) GetZoom\(\) float64](<#WebviewWindow.GetZoom>) + - [func \(w \*WebviewWindow\) Height\(\) int](<#WebviewWindow.Height>) + - [func \(w \*WebviewWindow\) Hide\(\) \*WebviewWindow](<#WebviewWindow.Hide>) + - [func \(w \*WebviewWindow\) IsFocused\(\) bool](<#WebviewWindow.IsFocused>) + - [func \(w \*WebviewWindow\) IsFullscreen\(\) bool](<#WebviewWindow.IsFullscreen>) + - [func \(w \*WebviewWindow\) IsMaximised\(\) bool](<#WebviewWindow.IsMaximised>) + - [func \(w \*WebviewWindow\) IsMinimised\(\) bool](<#WebviewWindow.IsMinimised>) + - [func \(w \*WebviewWindow\) IsVisible\(\) bool](<#WebviewWindow.IsVisible>) + - [func \(w \*WebviewWindow\) Maximise\(\) \*WebviewWindow](<#WebviewWindow.Maximise>) + - [func \(w \*WebviewWindow\) Minimise\(\) \*WebviewWindow](<#WebviewWindow.Minimise>) + - [func \(w \*WebviewWindow\) Name\(\) string](<#WebviewWindow.Name>) + - [func \(w \*WebviewWindow\) NativeWindowHandle\(\) \(uintptr, error\)](<#WebviewWindow.NativeWindowHandle>) + - [func \(w \*WebviewWindow\) On\(eventType events.WindowEventType, callback func\(event \*WindowEvent\)\) func\(\)](<#WebviewWindow.On>) + - [func \(w \*WebviewWindow\) Print\(\) error](<#WebviewWindow.Print>) + - [func \(w \*WebviewWindow\) RegisterContextMenu\(name string, menu \*Menu\)](<#WebviewWindow.RegisterContextMenu>) + - [func \(w \*WebviewWindow\) RegisterHook\(eventType events.WindowEventType, callback func\(event \*WindowEvent\)\) func\(\)](<#WebviewWindow.RegisterHook>) + - [func \(w \*WebviewWindow\) RelativePosition\(\) \(int, int\)](<#WebviewWindow.RelativePosition>) + - [func \(w \*WebviewWindow\) Reload\(\)](<#WebviewWindow.Reload>) + - [func \(w \*WebviewWindow\) Resizable\(\) bool](<#WebviewWindow.Resizable>) + - [func \(w \*WebviewWindow\) Restore\(\)](<#WebviewWindow.Restore>) + - [func \(w \*WebviewWindow\) SetAbsolutePosition\(x int, y int\)](<#WebviewWindow.SetAbsolutePosition>) + - [func \(w \*WebviewWindow\) SetAlwaysOnTop\(b bool\) \*WebviewWindow](<#WebviewWindow.SetAlwaysOnTop>) + - [func \(w \*WebviewWindow\) SetBackgroundColour\(colour RGBA\) \*WebviewWindow](<#WebviewWindow.SetBackgroundColour>) + - [func \(w \*WebviewWindow\) SetEnabled\(enabled bool\)](<#WebviewWindow.SetEnabled>) + - [func \(w \*WebviewWindow\) SetFrameless\(frameless bool\) \*WebviewWindow](<#WebviewWindow.SetFrameless>) + - [func \(w \*WebviewWindow\) SetFullscreenButtonEnabled\(enabled bool\) \*WebviewWindow](<#WebviewWindow.SetFullscreenButtonEnabled>) + - [func \(w \*WebviewWindow\) SetHTML\(html string\) \*WebviewWindow](<#WebviewWindow.SetHTML>) + - [func \(w \*WebviewWindow\) SetMaxSize\(maxWidth, maxHeight int\) \*WebviewWindow](<#WebviewWindow.SetMaxSize>) + - [func \(w \*WebviewWindow\) SetMinSize\(minWidth, minHeight int\) \*WebviewWindow](<#WebviewWindow.SetMinSize>) + - [func \(w \*WebviewWindow\) SetRelativePosition\(x, y int\) \*WebviewWindow](<#WebviewWindow.SetRelativePosition>) + - [func \(w \*WebviewWindow\) SetResizable\(b bool\) \*WebviewWindow](<#WebviewWindow.SetResizable>) + - [func \(w \*WebviewWindow\) SetSize\(width, height int\) \*WebviewWindow](<#WebviewWindow.SetSize>) + - [func \(w \*WebviewWindow\) SetTitle\(title string\) \*WebviewWindow](<#WebviewWindow.SetTitle>) + - [func \(w \*WebviewWindow\) SetURL\(s string\) \*WebviewWindow](<#WebviewWindow.SetURL>) + - [func \(w \*WebviewWindow\) SetZoom\(magnification float64\) \*WebviewWindow](<#WebviewWindow.SetZoom>) + - [func \(w \*WebviewWindow\) Show\(\) \*WebviewWindow](<#WebviewWindow.Show>) + - [func \(w \*WebviewWindow\) Size\(\) \(int, int\)](<#WebviewWindow.Size>) + - [func \(w \*WebviewWindow\) ToggleDevTools\(\)](<#WebviewWindow.ToggleDevTools>) + - [func \(w \*WebviewWindow\) ToggleFullscreen\(\)](<#WebviewWindow.ToggleFullscreen>) + - [func \(w \*WebviewWindow\) UnFullscreen\(\)](<#WebviewWindow.UnFullscreen>) + - [func \(w \*WebviewWindow\) UnMaximise\(\)](<#WebviewWindow.UnMaximise>) + - [func \(w \*WebviewWindow\) UnMinimise\(\)](<#WebviewWindow.UnMinimise>) + - [func \(w \*WebviewWindow\) Width\(\) int](<#WebviewWindow.Width>) + - [func \(w \*WebviewWindow\) Zoom\(\)](<#WebviewWindow.Zoom>) + - [func \(w \*WebviewWindow\) ZoomIn\(\)](<#WebviewWindow.ZoomIn>) + - [func \(w \*WebviewWindow\) ZoomOut\(\)](<#WebviewWindow.ZoomOut>) + - [func \(w \*WebviewWindow\) ZoomReset\(\) \*WebviewWindow](<#WebviewWindow.ZoomReset>) +- [type WebviewWindowOptions](<#WebviewWindowOptions>) +- [type Win32Menu](<#Win32Menu>) + - [func NewApplicationMenu\(parent w32.HWND, inputMenu \*Menu\) \*Win32Menu](<#NewApplicationMenu>) + - [func NewPopupMenu\(parent w32.HWND, inputMenu \*Menu\) \*Win32Menu](<#NewPopupMenu>) + - [func \(p \*Win32Menu\) Destroy\(\)](<#Win32Menu.Destroy>) + - [func \(p \*Win32Menu\) OnMenuClose\(fn func\(\)\)](<#Win32Menu.OnMenuClose>) + - [func \(p \*Win32Menu\) OnMenuOpen\(fn func\(\)\)](<#Win32Menu.OnMenuOpen>) + - [func \(p \*Win32Menu\) ProcessCommand\(cmdMsgID int\) bool](<#Win32Menu.ProcessCommand>) + - [func \(p \*Win32Menu\) ShowAt\(x int, y int\)](<#Win32Menu.ShowAt>) + - [func \(p \*Win32Menu\) ShowAtCursor\(\)](<#Win32Menu.ShowAtCursor>) + - [func \(p \*Win32Menu\) Update\(\)](<#Win32Menu.Update>) + - [func \(p \*Win32Menu\) UpdateMenuItem\(item \*MenuItem\)](<#Win32Menu.UpdateMenuItem>) +- [type WindowAttachConfig](<#WindowAttachConfig>) +- [type WindowEvent](<#WindowEvent>) + - [func NewWindowEvent\(\) \*WindowEvent](<#NewWindowEvent>) + - [func \(w \*WindowEvent\) Cancel\(\)](<#WindowEvent.Cancel>) + - [func \(w \*WindowEvent\) Context\(\) \*WindowEventContext](<#WindowEvent.Context>) +- [type WindowEventContext](<#WindowEventContext>) + - [func \(c WindowEventContext\) DroppedFiles\(\) \[\]string](<#WindowEventContext.DroppedFiles>) +- [type WindowEventListener](<#WindowEventListener>) +- [type WindowState](<#WindowState>) +- [type WindowsOptions](<#WindowsOptions>) +- [type WindowsWindow](<#WindowsWindow>) + + +## Constants + + + +```go +const ( + ApplicationHide = 0 + ApplicationShow = 1 + ApplicationQuit = 2 +) +``` + + + +```go +const ( + ClipboardSetText = 0 + ClipboardText = 1 +) +``` + + + +```go +const ( + DialogInfo = 0 + DialogWarning = 1 + DialogError = 2 + DialogQuestion = 3 + DialogOpenFile = 4 + DialogSaveFile = 5 +) +``` + + + +```go +const ( + ScreensGetAll = 0 + ScreensGetPrimary = 1 + ScreensGetCurrent = 2 +) +``` + + + +```go +const ( + WindowCenter = 0 + WindowSetTitle = 1 + WindowFullscreen = 2 + WindowUnFullscreen = 3 + WindowSetSize = 4 + WindowSize = 5 + WindowSetMaxSize = 6 + WindowSetMinSize = 7 + WindowSetAlwaysOnTop = 8 + WindowSetRelativePosition = 9 + WindowRelativePosition = 10 + WindowScreen = 11 + WindowHide = 12 + WindowMaximise = 13 + WindowUnMaximise = 14 + WindowToggleMaximise = 15 + WindowMinimise = 16 + WindowUnMinimise = 17 + WindowRestore = 18 + WindowShow = 19 + WindowClose = 20 + WindowSetBackgroundColour = 21 + WindowSetResizable = 22 + WindowWidth = 23 + WindowHeight = 24 + WindowZoomIn = 25 + WindowZoomOut = 26 + WindowZoomReset = 27 + WindowGetZoomLevel = 28 + WindowSetZoomLevel = 29 +) +``` + + + +```go +const ( + NSImageNone = iota + NSImageOnly + NSImageLeft + NSImageRight + NSImageBelow + NSImageAbove + NSImageOverlaps + NSImageLeading + NSImageTrailing +) +``` + + + +```go +const ( + CallBinding = 0 +) +``` + + + +```go +const ( + ContextMenuOpen = 0 +) +``` + + + +```go +const ( + EventsEmit = 0 +) +``` + + + +```go +const ( + MenuItemMsgID = w32.WM_APP + 1024 +) +``` + + + +```go +const ( + SystemIsDarkMode = 0 +) +``` + + + +```go +const ( + WM_USER_SYSTRAY = w32.WM_USER + 1 +) +``` + +## Variables + +BuildInfo contains the build info for the application + +```go +var BuildInfo *debug.BuildInfo +``` + +BuildSettings contains the build settings for the application + +```go +var BuildSettings map[string]string +``` + +MacTitleBarDefault results in the default Mac MacTitleBar + +```go +var MacTitleBarDefault = MacTitleBar{ + AppearsTransparent: false, + Hide: false, + HideTitle: false, + FullSizeContent: false, + UseToolbar: false, + HideToolbarSeparator: false, +} +``` + +MacTitleBarHidden results in a hidden title bar and a full size content window, yet the title bar still has the standard window controls \(“traffic lights”\) in the top left. + +```go +var MacTitleBarHidden = MacTitleBar{ + AppearsTransparent: true, + Hide: false, + HideTitle: true, + FullSizeContent: true, + UseToolbar: false, + HideToolbarSeparator: false, +} +``` + +MacTitleBarHiddenInset results in a hidden title bar with an alternative look where the traffic light buttons are slightly more inset from the window edge. + +```go +var MacTitleBarHiddenInset = MacTitleBar{ + AppearsTransparent: true, + Hide: false, + HideTitle: true, + FullSizeContent: true, + UseToolbar: true, + HideToolbarSeparator: true, +} +``` + +MacTitleBarHiddenInsetUnified results in a hidden title bar with an alternative look where the traffic light buttons are even more inset from the window edge. + +```go +var MacTitleBarHiddenInsetUnified = MacTitleBar{ + AppearsTransparent: true, + Hide: false, + HideTitle: true, + FullSizeContent: true, + UseToolbar: true, + HideToolbarSeparator: true, + ToolbarStyle: MacToolbarStyleUnified, +} +``` + + + +```go +var VirtualKeyCodes = map[uint]string{ + 0x01: "lbutton", + 0x02: "rbutton", + 0x03: "cancel", + 0x04: "mbutton", + 0x05: "xbutton1", + 0x06: "xbutton2", + 0x08: "back", + 0x09: "tab", + 0x0C: "clear", + 0x0D: "return", + 0x10: "shift", + 0x11: "control", + 0x12: "menu", + 0x13: "pause", + 0x14: "capital", + 0x15: "kana", + 0x17: "junja", + 0x18: "final", + 0x19: "hanja", + 0x1B: "escape", + 0x1C: "convert", + 0x1D: "nonconvert", + 0x1E: "accept", + 0x1F: "modechange", + 0x20: "space", + 0x21: "prior", + 0x22: "next", + 0x23: "end", + 0x24: "home", + 0x25: "left", + 0x26: "up", + 0x27: "right", + 0x28: "down", + 0x29: "select", + 0x2A: "print", + 0x2B: "execute", + 0x2C: "snapshot", + 0x2D: "insert", + 0x2E: "delete", + 0x2F: "help", + 0x30: "0", + 0x31: "1", + 0x32: "2", + 0x33: "3", + 0x34: "4", + 0x35: "5", + 0x36: "6", + 0x37: "7", + 0x38: "8", + 0x39: "9", + 0x41: "a", + 0x42: "b", + 0x43: "c", + 0x44: "d", + 0x45: "e", + 0x46: "f", + 0x47: "g", + 0x48: "h", + 0x49: "i", + 0x4A: "j", + 0x4B: "k", + 0x4C: "l", + 0x4D: "m", + 0x4E: "n", + 0x4F: "o", + 0x50: "p", + 0x51: "q", + 0x52: "r", + 0x53: "s", + 0x54: "t", + 0x55: "u", + 0x56: "v", + 0x57: "w", + 0x58: "x", + 0x59: "y", + 0x5A: "z", + 0x5B: "lwin", + 0x5C: "rwin", + 0x5D: "apps", + 0x5F: "sleep", + 0x60: "numpad0", + 0x61: "numpad1", + 0x62: "numpad2", + 0x63: "numpad3", + 0x64: "numpad4", + 0x65: "numpad5", + 0x66: "numpad6", + 0x67: "numpad7", + 0x68: "numpad8", + 0x69: "numpad9", + 0x6A: "multiply", + 0x6B: "add", + 0x6C: "separator", + 0x6D: "subtract", + 0x6E: "decimal", + 0x6F: "divide", + 0x70: "f1", + 0x71: "f2", + 0x72: "f3", + 0x73: "f4", + 0x74: "f5", + 0x75: "f6", + 0x76: "f7", + 0x77: "f8", + 0x78: "f9", + 0x79: "f10", + 0x7A: "f11", + 0x7B: "f12", + 0x7C: "f13", + 0x7D: "f14", + 0x7E: "f15", + 0x7F: "f16", + 0x80: "f17", + 0x81: "f18", + 0x82: "f19", + 0x83: "f20", + 0x84: "f21", + 0x85: "f22", + 0x86: "f23", + 0x87: "f24", + 0x88: "navigation_view", + 0x89: "navigation_menu", + 0x8A: "navigation_up", + 0x8B: "navigation_down", + 0x8C: "navigation_left", + 0x8D: "navigation_right", + 0x8E: "navigation_accept", + 0x8F: "navigation_cancel", + 0x90: "numlock", + 0x91: "scroll", + 0x92: "oem_nec_equal", + 0x93: "oem_fj_masshou", + 0x94: "oem_fj_touroku", + 0x95: "oem_fj_loya", + 0x96: "oem_fj_roya", + 0xA0: "lshift", + 0xA1: "rshift", + 0xA2: "lcontrol", + 0xA3: "rcontrol", + 0xA4: "lmenu", + 0xA5: "rmenu", + 0xA6: "browser_back", + 0xA7: "browser_forward", + 0xA8: "browser_refresh", + 0xA9: "browser_stop", + 0xAA: "browser_search", + 0xAB: "browser_favorites", + 0xAC: "browser_home", + 0xAD: "volume_mute", + 0xAE: "volume_down", + 0xAF: "volume_up", + 0xB0: "media_next_track", + 0xB1: "media_prev_track", + 0xB2: "media_stop", + 0xB3: "media_play_pause", + 0xB4: "launch_mail", + 0xB5: "launch_media_select", + 0xB6: "launch_app1", + 0xB7: "launch_app2", + 0xBA: "oem_1", + 0xBB: "oem_plus", + 0xBC: "oem_comma", + 0xBD: "oem_minus", + 0xBE: "oem_period", + 0xBF: "oem_2", + 0xC0: "oem_3", + 0xC3: "gamepad_a", + 0xC4: "gamepad_b", + 0xC5: "gamepad_x", + 0xC6: "gamepad_y", + 0xC7: "gamepad_right_shoulder", + 0xC8: "gamepad_left_shoulder", + 0xC9: "gamepad_left_trigger", + 0xCA: "gamepad_right_trigger", + 0xCB: "gamepad_dpad_up", + 0xCC: "gamepad_dpad_down", + 0xCD: "gamepad_dpad_left", + 0xCE: "gamepad_dpad_right", + 0xCF: "gamepad_menu", + 0xD0: "gamepad_view", + 0xD1: "gamepad_left_thumbstick_button", + 0xD2: "gamepad_right_thumbstick_button", + 0xD3: "gamepad_left_thumbstick_up", + 0xD4: "gamepad_left_thumbstick_down", + 0xD5: "gamepad_left_thumbstick_right", + 0xD6: "gamepad_left_thumbstick_left", + 0xD7: "gamepad_right_thumbstick_up", + 0xD8: "gamepad_right_thumbstick_down", + 0xD9: "gamepad_right_thumbstick_right", + 0xDA: "gamepad_right_thumbstick_left", + 0xDB: "oem_4", + 0xDC: "oem_5", + 0xDD: "oem_6", + 0xDE: "oem_7", + 0xDF: "oem_8", + 0xE1: "oem_ax", + 0xE2: "oem_102", + 0xE3: "ico_help", + 0xE4: "ico_00", + 0xE5: "processkey", + 0xE6: "ico_clear", + 0xE7: "packet", + 0xE9: "oem_reset", + 0xEA: "oem_jump", + 0xEB: "oem_pa1", + 0xEC: "oem_pa2", + 0xED: "oem_pa3", + 0xEE: "oem_wsctrl", + 0xEF: "oem_cusel", + 0xF0: "oem_attn", + 0xF1: "oem_finish", + 0xF2: "oem_copy", + 0xF3: "oem_auto", + 0xF4: "oem_enlw", + 0xF5: "oem_backtab", + 0xF6: "attn", + 0xF7: "crsel", + 0xF8: "exsel", + 0xF9: "ereof", + 0xFA: "play", + 0xFB: "zoom", + 0xFC: "noname", + 0xFD: "pa1", + 0xFE: "oem_clear", +} +``` + + + +```go +var WebviewWindowDefaults = &WebviewWindowOptions{ + Title: "", + Width: 800, + Height: 600, + URL: "", + BackgroundColour: RGBA{ + Red: 255, + Green: 255, + Blue: 255, + Alpha: 255, + }, +} +``` + + +## func [DefaultLogger]() + +```go +func DefaultLogger(level slog.Level) *slog.Logger +``` + + + + +## func [Fatal]() + +```go +func Fatal(message string, args ...interface{}) +``` + + + + +## func [InvokeAsync]() + +```go +func InvokeAsync(fn func()) +``` + + + + +## func [InvokeSync]() + +```go +func InvokeSync(fn func()) +``` + + + + +## func [InvokeSyncWithError]() + +```go +func InvokeSyncWithError(fn func() error) (err error) +``` + + + + +## func [InvokeSyncWithResult]() + +```go +func InvokeSyncWithResult[T any](fn func() T) (res T) +``` + + + + +## func [InvokeSyncWithResultAndError]() + +```go +func InvokeSyncWithResultAndError[T any](fn func() (T, error)) (res T, err error) +``` + + + + +## func [NewIconFromResource]() + +```go +func NewIconFromResource(instance w32.HINSTANCE, resId uint16) (w32.HICON, error) +``` + + + + +## func [ScaleToDefaultDPI]() + +```go +func ScaleToDefaultDPI(pixels int, dpi uint) int +``` + + + + +## func [ScaleWithDPI]() + +```go +func ScaleWithDPI(pixels int, dpi uint) int +``` + + + + +## type [ActivationPolicy]() + +ActivationPolicy is the activation policy for the application. + +```go +type ActivationPolicy int +``` + + + +```go +const ( + // ActivationPolicyRegular is used for applications that have a user interface, + ActivationPolicyRegular ActivationPolicy = iota + // ActivationPolicyAccessory is used for applications that do not have a main window, + // such as system tray applications or background applications. + ActivationPolicyAccessory + ActivationPolicyProhibited +) +``` + + +## type [App]() + + + +```go +type App struct { + + // The main application menu + ApplicationMenu *Menu + + Events *EventProcessor + Logger *slog.Logger + // contains filtered or unexported fields +} +``` + + +### func [Get]() + +```go +func Get() *App +``` + + + + +### func [New]() + +```go +func New(appOptions Options) *App +``` + + + + +### func \(\*App\) [Capabilities]() + +```go +func (a *App) Capabilities() capabilities.Capabilities +``` + + + + +### func \(\*App\) [Clipboard]() + +```go +func (a *App) Clipboard() *Clipboard +``` + + + + +### func \(\*App\) [CurrentWindow]() + +```go +func (a *App) CurrentWindow() *WebviewWindow +``` + + + + +### func \(\*App\) [GetPID]() + +```go +func (a *App) GetPID() int +``` + + + + +### func \(\*App\) [GetPrimaryScreen]() + +```go +func (a *App) GetPrimaryScreen() (*Screen, error) +``` + + + + +### func \(\*App\) [GetScreens]() + +```go +func (a *App) GetScreens() ([]*Screen, error) +``` + + + + +### func \(\*App\) [GetWindowByName]() + +```go +func (a *App) GetWindowByName(name string) *WebviewWindow +``` + + + + +### func \(\*App\) [Hide]() + +```go +func (a *App) Hide() +``` + + + + +### func \(\*App\) [IsDarkMode]() + +```go +func (a *App) IsDarkMode() bool +``` + + + + +### func \(\*App\) [NewMenu]() + +```go +func (a *App) NewMenu() *Menu +``` + + + + +### func \(\*App\) [NewSystemTray]() + +```go +func (a *App) NewSystemTray() *SystemTray +``` + + + + +### func \(\*App\) [NewWebviewWindow]() + +```go +func (a *App) NewWebviewWindow() *WebviewWindow +``` + + + + +### func \(\*App\) [NewWebviewWindowWithOptions]() + +```go +func (a *App) NewWebviewWindowWithOptions(windowOptions WebviewWindowOptions) *WebviewWindow +``` + + + + +### func \(\*App\) [On]() + +```go +func (a *App) On(eventType events.ApplicationEventType, callback func(event *Event)) func() +``` + + + + +### func \(\*App\) [OnWindowCreation]() + +```go +func (a *App) OnWindowCreation(callback func(window *WebviewWindow)) +``` + + + + +### func \(\*App\) [Quit]() + +```go +func (a *App) Quit() +``` + + + + +### func \(\*App\) [RegisterContextMenu]() + +```go +func (a *App) RegisterContextMenu(name string, menu *Menu) +``` + + + + +### func \(\*App\) [RegisterHook]() + +```go +func (a *App) RegisterHook(eventType events.ApplicationEventType, callback func(event *Event)) func() +``` + +RegisterHook registers a hook for the given event type. Hooks are called before the event listeners and can cancel the event. The returned function can be called to remove the hook. + + +### func \(\*App\) [Run]() + +```go +func (a *App) Run() error +``` + + + + +### func \(\*App\) [SetMenu]() + +```go +func (a *App) SetMenu(menu *Menu) +``` + + + + +### func \(\*App\) [Show]() + +```go +func (a *App) Show() +``` + + + + +### func \(\*App\) [ShowAboutDialog]() + +```go +func (a *App) ShowAboutDialog() +``` + + + + +## type [ApplicationEventContext]() + + + +```go +type ApplicationEventContext struct { + // contains filtered or unexported fields +} +``` + + +### func \(ApplicationEventContext\) [IsDarkMode]() + +```go +func (c ApplicationEventContext) IsDarkMode() bool +``` + + + + +### func \(ApplicationEventContext\) [OpenedFiles]() + +```go +func (c ApplicationEventContext) OpenedFiles() []string +``` + + + + +## type [Args]() + + + +```go +type Args struct { + // contains filtered or unexported fields +} +``` + + +### func \(\*Args\) [Bool]() + +```go +func (a *Args) Bool(s string) *bool +``` + + + + +### func \(\*Args\) [Float64]() + +```go +func (a *Args) Float64(s string) *float64 +``` + + + + +### func \(\*Args\) [Int]() + +```go +func (a *Args) Int(s string) *int +``` + + + + +### func \(\*Args\) [String]() + +```go +func (a *Args) String(key string) *string +``` + + + + +### func \(\*Args\) [UInt]() + +```go +func (a *Args) UInt(s string) *uint +``` + + + + +### func \(\*Args\) [UInt8]() + +```go +func (a *Args) UInt8(s string) *uint8 +``` + + + + +## type [AssetOptions]() + +AssetOptions defines the configuration of the AssetServer. + +```go +type AssetOptions struct { + // FS defines the static assets to be used. A GET request is first tried to be served from this FS. If the FS returns + // `os.ErrNotExist` for that file, the request handling will fallback to the Handler and tries to serve the GET + // request from it. + // + // If set to nil, all GET requests will be forwarded to Handler. + FS fs.FS + + // Handler will be called for every GET request that can't be served from FS, due to `os.ErrNotExist`. Furthermore all + // non GET requests will always be served from this Handler. + // + // If not defined, the result is the following in cases where the Handler would have been called: + // GET request: `http.StatusNotFound` + // Other request: `http.StatusMethodNotAllowed` + Handler http.Handler + + // Middleware is HTTP Middleware which allows to hook into the AssetServer request chain. It allows to skip the default + // request handler dynamically, e.g. implement specialized Routing etc. + // The Middleware is called to build a new `http.Handler` used by the AssetSever and it also receives the default + // handler used by the AssetServer as an argument. + // + // If not defined, the default AssetServer request chain is executed. + // + // Multiple Middlewares can be chained together with: + // ChainMiddleware(middleware ...Middleware) Middleware + Middleware Middleware + + // External URL can be set to a development server URL so that all requests are forwarded to it. This is useful + // when using a development server like `vite` or `snowpack` which serves the assets on a different port. + ExternalURL string +} +``` + + +## type [BackdropType]() + + + +```go +type BackdropType int32 +``` + + + +```go +const ( + Auto BackdropType = 0 + None BackdropType = 1 + Mica BackdropType = 2 + Acrylic BackdropType = 3 + Tabbed BackdropType = 4 +) +``` + + +## type [BackgroundType]() + + + +```go +type BackgroundType int +``` + + + +```go +const ( + BackgroundTypeSolid BackgroundType = iota + BackgroundTypeTransparent + BackgroundTypeTranslucent +) +``` + + +## type [Bindings]() + + + +```go +type Bindings struct { + // contains filtered or unexported fields +} +``` + + +### func [NewBindings]() + +```go +func NewBindings(structs []any, aliases map[uint32]uint32) (*Bindings, error) +``` + + + + +### func \(\*Bindings\) [Add]() + +```go +func (b *Bindings) Add(structPtr interface{}) error +``` + +Add the given struct methods to the Bindings + + +### func \(\*Bindings\) [AddPlugins]() + +```go +func (b *Bindings) AddPlugins(plugins map[string]Plugin) error +``` + + + + +### func \(\*Bindings\) [GenerateID]() + +```go +func (b *Bindings) GenerateID(name string) (uint32, error) +``` + +GenerateID generates a unique ID for a binding + + +### func \(\*Bindings\) [Get]() + +```go +func (b *Bindings) Get(options *CallOptions) *BoundMethod +``` + +Get returns the bound method with the given name + + +### func \(\*Bindings\) [GetByID]() + +```go +func (b *Bindings) GetByID(id uint32) *BoundMethod +``` + +GetByID returns the bound method with the given ID + + +## type [BoundMethod]() + +BoundMethod defines all the data related to a Go method that is bound to the Wails application + +```go +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:"-"` + PackageName string + StructName string + PackagePath string +} +``` + + +### func \(\*BoundMethod\) [Call]() + +```go +func (b *BoundMethod) Call(args []interface{}) (returnValue interface{}, err error) +``` + +Call will attempt to call this bound method with the given args + + +### func \(\*BoundMethod\) [String]() + +```go +func (b *BoundMethod) String() string +``` + + + + +## type [Button]() + + + +```go +type Button struct { + Label string + IsCancel bool + IsDefault bool + Callback func() +} +``` + + +### func \(\*Button\) [OnClick]() + +```go +func (b *Button) OnClick(callback func()) *Button +``` + + + + +### func \(\*Button\) [SetAsCancel]() + +```go +func (b *Button) SetAsCancel() *Button +``` + + + + +### func \(\*Button\) [SetAsDefault]() + +```go +func (b *Button) SetAsDefault() *Button +``` + + + + +## type [CallOptions]() + + + +```go +type CallOptions struct { + MethodID uint32 `json:"methodID"` + PackageName string `json:"packageName"` + StructName string `json:"structName"` + MethodName string `json:"methodName"` + Args []any `json:"args"` +} +``` + + +### func \(CallOptions\) [Name]() + +```go +func (c CallOptions) Name() string +``` + + + + +## type [Clipboard]() + + + +```go +type Clipboard struct { + // contains filtered or unexported fields +} +``` + + +### func \(\*Clipboard\) [SetText]() + +```go +func (c *Clipboard) SetText(text string) bool +``` + + + + +### func \(\*Clipboard\) [Text]() + +```go +func (c *Clipboard) Text() (string, bool) +``` + + + + +## type [Context]() + + + +```go +type Context struct { + // contains filtered or unexported fields +} +``` + + +### func \(\*Context\) [ClickedMenuItem]() + +```go +func (c *Context) ClickedMenuItem() *MenuItem +``` + + + + +### func \(\*Context\) [ContextMenuData]() + +```go +func (c *Context) ContextMenuData() any +``` + + + + +### func \(\*Context\) [IsChecked]() + +```go +func (c *Context) IsChecked() bool +``` + + + + +## type [ContextMenuData]() + + + +```go +type ContextMenuData struct { + Id string `json:"id"` + X int `json:"x"` + Y int `json:"y"` + Data any `json:"data"` +} +``` + + +## type [DialogType]() + + + +```go +type DialogType int +``` + + + +```go +const ( + InfoDialogType DialogType = iota + QuestionDialogType + WarningDialogType + ErrorDialogType + OpenDirectoryDialogType +) +``` + + +## type [Event]() + + + +```go +type Event struct { + Id uint + + Cancelled bool + // contains filtered or unexported fields +} +``` + + +### func \(\*Event\) [Cancel]() + +```go +func (w *Event) Cancel() +``` + + + + +### func \(\*Event\) [Context]() + +```go +func (w *Event) Context() *ApplicationEventContext +``` + + + + +## type [EventListener]() + + + +```go +type EventListener struct { + // contains filtered or unexported fields +} +``` + + +## type [EventProcessor]() + +EventProcessor handles custom events + +```go +type EventProcessor struct { + // contains filtered or unexported fields +} +``` + + +### func [NewWailsEventProcessor]() + +```go +func NewWailsEventProcessor(dispatchEventToWindows func(*WailsEvent)) *EventProcessor +``` + + + + +### func \(\*EventProcessor\) [Emit]() + +```go +func (e *EventProcessor) Emit(thisEvent *WailsEvent) +``` + +Emit sends an event to all listeners + + +### func \(\*EventProcessor\) [Off]() + +```go +func (e *EventProcessor) Off(eventName string) +``` + + + + +### func \(\*EventProcessor\) [OffAll]() + +```go +func (e *EventProcessor) OffAll() +``` + + + + +### func \(\*EventProcessor\) [On]() + +```go +func (e *EventProcessor) On(eventName string, callback func(event *WailsEvent)) func() +``` + +On is the equivalent of Javascript's \`addEventListener\` + + +### func \(\*EventProcessor\) [OnMultiple]() + +```go +func (e *EventProcessor) OnMultiple(eventName string, callback func(event *WailsEvent), counter int) func() +``` + +OnMultiple is the same as \`On\` but will unregister after \`count\` events + + +### func \(\*EventProcessor\) [Once]() + +```go +func (e *EventProcessor) Once(eventName string, callback func(event *WailsEvent)) func() +``` + +Once is the same as \`On\` but will unregister after the first event + + +### func \(\*EventProcessor\) [RegisterHook]() + +```go +func (e *EventProcessor) RegisterHook(eventName string, callback func(*WailsEvent)) func() +``` + +RegisterHook provides a means of registering methods to be called before emitting the event + + +## type [FileFilter]() + + + +```go +type FileFilter struct { + DisplayName string // Filter information EG: "Image Files (*.jpg, *.png)" + Pattern string // semicolon separated list of extensions, EG: "*.jpg;*.png" +} +``` + + +## type [IconPosition]() + + + +```go +type IconPosition int +``` + + +## type [MacAppearanceType]() + +MacAppearanceType is a type of Appearance for Cocoa windows + +```go +type MacAppearanceType string +``` + + + +```go +const ( + // DefaultAppearance uses the default system value + DefaultAppearance MacAppearanceType = "" + // NSAppearanceNameAqua - The standard light system appearance. + NSAppearanceNameAqua MacAppearanceType = "NSAppearanceNameAqua" + // NSAppearanceNameDarkAqua - The standard dark system appearance. + NSAppearanceNameDarkAqua MacAppearanceType = "NSAppearanceNameDarkAqua" + // NSAppearanceNameVibrantLight - The light vibrant appearance + NSAppearanceNameVibrantLight MacAppearanceType = "NSAppearanceNameVibrantLight" + // NSAppearanceNameAccessibilityHighContrastAqua - A high-contrast version of the standard light system appearance. + NSAppearanceNameAccessibilityHighContrastAqua MacAppearanceType = "NSAppearanceNameAccessibilityHighContrastAqua" + // NSAppearanceNameAccessibilityHighContrastDarkAqua - A high-contrast version of the standard dark system appearance. + NSAppearanceNameAccessibilityHighContrastDarkAqua MacAppearanceType = "NSAppearanceNameAccessibilityHighContrastDarkAqua" + // NSAppearanceNameAccessibilityHighContrastVibrantLight - A high-contrast version of the light vibrant appearance. + NSAppearanceNameAccessibilityHighContrastVibrantLight MacAppearanceType = "NSAppearanceNameAccessibilityHighContrastVibrantLight" + // NSAppearanceNameAccessibilityHighContrastVibrantDark - A high-contrast version of the dark vibrant appearance. + NSAppearanceNameAccessibilityHighContrastVibrantDark MacAppearanceType = "NSAppearanceNameAccessibilityHighContrastVibrantDark" +) +``` + + +## type [MacBackdrop]() + +MacBackdrop is the backdrop type for macOS + +```go +type MacBackdrop int +``` + + + +```go +const ( + // MacBackdropNormal - The default value. The window will have a normal opaque background. + MacBackdropNormal MacBackdrop = iota + // MacBackdropTransparent - The window will have a transparent background, with the content underneath it being visible + MacBackdropTransparent + // MacBackdropTranslucent - The window will have a translucent background, with the content underneath it being "fuzzy" or "frosted" + MacBackdropTranslucent +) +``` + + +## type [MacOptions]() + +MacOptions contains options for macOS applications. + +```go +type MacOptions struct { + // ActivationPolicy is the activation policy for the application. Defaults to + // applicationActivationPolicyRegular. + ActivationPolicy ActivationPolicy + // If set to true, the application will terminate when the last window is closed. + ApplicationShouldTerminateAfterLastWindowClosed bool +} +``` + + +## type [MacTitleBar]() + +MacTitleBar contains options for the Mac titlebar + +```go +type MacTitleBar struct { + // AppearsTransparent will make the titlebar transparent + AppearsTransparent bool + // Hide will hide the titlebar + Hide bool + // HideTitle will hide the title + HideTitle bool + // FullSizeContent will extend the window content to the full size of the window + FullSizeContent bool + // UseToolbar will use a toolbar instead of a titlebar + UseToolbar bool + // HideToolbarSeparator will hide the toolbar separator + HideToolbarSeparator bool + // ToolbarStyle is the style of toolbar to use + ToolbarStyle MacToolbarStyle +} +``` + + +## type [MacToolbarStyle]() + +MacToolbarStyle is the style of toolbar for macOS + +```go +type MacToolbarStyle int +``` + + + +```go +const ( + // MacToolbarStyleAutomatic - The default value. The style will be determined by the window's given configuration + MacToolbarStyleAutomatic MacToolbarStyle = iota + // MacToolbarStyleExpanded - The toolbar will appear below the window title + MacToolbarStyleExpanded + // MacToolbarStylePreference - The toolbar will appear below the window title and the items in the toolbar will attempt to have equal widths when possible + MacToolbarStylePreference + // MacToolbarStyleUnified - The window title will appear inline with the toolbar when visible + MacToolbarStyleUnified + // MacToolbarStyleUnifiedCompact - Same as MacToolbarStyleUnified, but with reduced margins in the toolbar allowing more focus to be on the contents of the window + MacToolbarStyleUnifiedCompact +) +``` + + +## type [MacWindow]() + +MacWindow contains macOS specific options for Webview Windows + +```go +type MacWindow struct { + // Backdrop is the backdrop type for the window + Backdrop MacBackdrop + // DisableShadow will disable the window shadow + DisableShadow bool + // TitleBar contains options for the Mac titlebar + TitleBar MacTitleBar + // Appearance is the appearance type for the window + Appearance MacAppearanceType + // InvisibleTitleBarHeight defines the height of an invisible titlebar which responds to dragging + InvisibleTitleBarHeight int + // Maps events from platform specific to common event types + EventMapping map[events.WindowEventType]events.WindowEventType + + // EnableFraudulentWebsiteWarnings will enable warnings for fraudulent websites. + // Default: false + EnableFraudulentWebsiteWarnings bool +} +``` + + +## type [Menu]() + + + +```go +type Menu struct { + // contains filtered or unexported fields +} +``` + + +### func [NewMenu]() + +```go +func NewMenu() *Menu +``` + + + + +### func \(\*Menu\) [Add]() + +```go +func (m *Menu) Add(label string) *MenuItem +``` + + + + +### func \(\*Menu\) [AddCheckbox]() + +```go +func (m *Menu) AddCheckbox(label string, enabled bool) *MenuItem +``` + + + + +### func \(\*Menu\) [AddRadio]() + +```go +func (m *Menu) AddRadio(label string, enabled bool) *MenuItem +``` + + + + +### func \(\*Menu\) [AddRole]() + +```go +func (m *Menu) AddRole(role Role) *Menu +``` + + + + +### func \(\*Menu\) [AddSeparator]() + +```go +func (m *Menu) AddSeparator() +``` + + + + +### func \(\*Menu\) [AddSubmenu]() + +```go +func (m *Menu) AddSubmenu(s string) *Menu +``` + + + + +### func \(\*Menu\) [SetLabel]() + +```go +func (m *Menu) SetLabel(label string) +``` + + + + +### func \(\*Menu\) [Update]() + +```go +func (m *Menu) Update() +``` + + + + +## type [MenuItem]() + + + +```go +type MenuItem struct { + // contains filtered or unexported fields +} +``` + + +### func \(\*MenuItem\) [Checked]() + +```go +func (m *MenuItem) Checked() bool +``` + + + + +### func \(\*MenuItem\) [Enabled]() + +```go +func (m *MenuItem) Enabled() bool +``` + + + + +### func \(\*MenuItem\) [Hidden]() + +```go +func (m *MenuItem) Hidden() bool +``` + + + + +### func \(\*MenuItem\) [IsCheckbox]() + +```go +func (m *MenuItem) IsCheckbox() bool +``` + + + + +### func \(\*MenuItem\) [IsRadio]() + +```go +func (m *MenuItem) IsRadio() bool +``` + + + + +### func \(\*MenuItem\) [IsSeparator]() + +```go +func (m *MenuItem) IsSeparator() bool +``` + + + + +### func \(\*MenuItem\) [IsSubmenu]() + +```go +func (m *MenuItem) IsSubmenu() bool +``` + + + + +### func \(\*MenuItem\) [Label]() + +```go +func (m *MenuItem) Label() string +``` + + + + +### func \(\*MenuItem\) [OnClick]() + +```go +func (m *MenuItem) OnClick(f func(*Context)) *MenuItem +``` + + + + +### func \(\*MenuItem\) [SetAccelerator]() + +```go +func (m *MenuItem) SetAccelerator(shortcut string) *MenuItem +``` + + + + +### func \(\*MenuItem\) [SetChecked]() + +```go +func (m *MenuItem) SetChecked(checked bool) *MenuItem +``` + + + + +### func \(\*MenuItem\) [SetEnabled]() + +```go +func (m *MenuItem) SetEnabled(enabled bool) *MenuItem +``` + + + + +### func \(\*MenuItem\) [SetHidden]() + +```go +func (m *MenuItem) SetHidden(hidden bool) *MenuItem +``` + + + + +### func \(\*MenuItem\) [SetLabel]() + +```go +func (m *MenuItem) SetLabel(s string) *MenuItem +``` + + + + +### func \(\*MenuItem\) [SetTooltip]() + +```go +func (m *MenuItem) SetTooltip(s string) *MenuItem +``` + + + + +### func \(\*MenuItem\) [Tooltip]() + +```go +func (m *MenuItem) Tooltip() string +``` + + + + +## type [MessageDialog]() + + + +```go +type MessageDialog struct { + MessageDialogOptions + // contains filtered or unexported fields +} +``` + + +### func [ErrorDialog]() + +```go +func ErrorDialog() *MessageDialog +``` + + + + +### func [InfoDialog]() + +```go +func InfoDialog() *MessageDialog +``` + + + + +### func [OpenDirectoryDialog]() + +```go +func OpenDirectoryDialog() *MessageDialog +``` + + + + +### func [QuestionDialog]() + +```go +func QuestionDialog() *MessageDialog +``` + + + + +### func [WarningDialog]() + +```go +func WarningDialog() *MessageDialog +``` + + + + +### func \(\*MessageDialog\) [AddButton]() + +```go +func (d *MessageDialog) AddButton(s string) *Button +``` + + + + +### func \(\*MessageDialog\) [AddButtons]() + +```go +func (d *MessageDialog) AddButtons(buttons []*Button) *MessageDialog +``` + + + + +### func \(\*MessageDialog\) [AttachToWindow]() + +```go +func (d *MessageDialog) AttachToWindow(window *WebviewWindow) *MessageDialog +``` + + + + +### func \(\*MessageDialog\) [SetCancelButton]() + +```go +func (d *MessageDialog) SetCancelButton(button *Button) *MessageDialog +``` + + + + +### func \(\*MessageDialog\) [SetDefaultButton]() + +```go +func (d *MessageDialog) SetDefaultButton(button *Button) *MessageDialog +``` + + + + +### func \(\*MessageDialog\) [SetIcon]() + +```go +func (d *MessageDialog) SetIcon(icon []byte) *MessageDialog +``` + + + + +### func \(\*MessageDialog\) [SetMessage]() + +```go +func (d *MessageDialog) SetMessage(message string) *MessageDialog +``` + + + + +### func \(\*MessageDialog\) [SetTitle]() + +```go +func (d *MessageDialog) SetTitle(title string) *MessageDialog +``` + + + + +### func \(\*MessageDialog\) [Show]() + +```go +func (d *MessageDialog) Show() +``` + + + + +## type [MessageDialogOptions]() + + + +```go +type MessageDialogOptions struct { + DialogType DialogType + Title string + Message string + Buttons []*Button + Icon []byte + // contains filtered or unexported fields +} +``` + + +## type [MessageProcessor]() + + + +```go +type MessageProcessor struct { + // contains filtered or unexported fields +} +``` + + +### func [NewMessageProcessor]() + +```go +func NewMessageProcessor(logger *slog.Logger) *MessageProcessor +``` + + + + +### func \(\*MessageProcessor\) [Error]() + +```go +func (m *MessageProcessor) Error(message string, args ...any) +``` + + + + +### func \(\*MessageProcessor\) [HandleRuntimeCall]() + +```go +func (m *MessageProcessor) HandleRuntimeCall(rw http.ResponseWriter, r *http.Request) +``` + + + + +### func \(\*MessageProcessor\) [HandleRuntimeCallWithIDs]() + +```go +func (m *MessageProcessor) HandleRuntimeCallWithIDs(rw http.ResponseWriter, r *http.Request) +``` + + + + +### func \(\*MessageProcessor\) [Info]() + +```go +func (m *MessageProcessor) Info(message string, args ...any) +``` + + + + +## type [Middleware]() + +Middleware defines HTTP middleware that can be applied to the AssetServer. The handler passed as next is the next handler in the chain. One can decide to call the next handler or implement a specialized handling. + +```go +type Middleware func(next http.Handler) http.Handler +``` + + +### func [ChainMiddleware]() + +```go +func ChainMiddleware(middleware ...Middleware) Middleware +``` + +ChainMiddleware allows chaining multiple middlewares to one middleware. + + +## type [OpenFileDialogOptions]() + + + +```go +type OpenFileDialogOptions struct { + CanChooseDirectories bool + CanChooseFiles bool + CanCreateDirectories bool + ShowHiddenFiles bool + ResolvesAliases bool + AllowsMultipleSelection bool + HideExtension bool + CanSelectHiddenExtension bool + TreatsFilePackagesAsDirectories bool + AllowsOtherFileTypes bool + Filters []FileFilter + Window *WebviewWindow + + Title string + Message string + ButtonText string + Directory string +} +``` + + +## type [OpenFileDialogStruct]() + + + +```go +type OpenFileDialogStruct struct { + // contains filtered or unexported fields +} +``` + + +### func [OpenFileDialog]() + +```go +func OpenFileDialog() *OpenFileDialogStruct +``` + + + + +### func [OpenFileDialogWithOptions]() + +```go +func OpenFileDialogWithOptions(options *OpenFileDialogOptions) *OpenFileDialogStruct +``` + + + + +### func \(\*OpenFileDialogStruct\) [AddFilter]() + +```go +func (d *OpenFileDialogStruct) AddFilter(displayName, pattern string) *OpenFileDialogStruct +``` + +AddFilter adds a filter to the dialog. The filter is a display name and a semicolon separated list of extensions. EG: AddFilter\("Image Files", "\*.jpg;\*.png"\) + + +### func \(\*OpenFileDialogStruct\) [AllowsOtherFileTypes]() + +```go +func (d *OpenFileDialogStruct) AllowsOtherFileTypes(allowsOtherFileTypes bool) *OpenFileDialogStruct +``` + + + + +### func \(\*OpenFileDialogStruct\) [AttachToWindow]() + +```go +func (d *OpenFileDialogStruct) AttachToWindow(window *WebviewWindow) *OpenFileDialogStruct +``` + + + + +### func \(\*OpenFileDialogStruct\) [CanChooseDirectories]() + +```go +func (d *OpenFileDialogStruct) CanChooseDirectories(canChooseDirectories bool) *OpenFileDialogStruct +``` + + + + +### func \(\*OpenFileDialogStruct\) [CanChooseFiles]() + +```go +func (d *OpenFileDialogStruct) CanChooseFiles(canChooseFiles bool) *OpenFileDialogStruct +``` + + + + +### func \(\*OpenFileDialogStruct\) [CanCreateDirectories]() + +```go +func (d *OpenFileDialogStruct) CanCreateDirectories(canCreateDirectories bool) *OpenFileDialogStruct +``` + + + + +### func \(\*OpenFileDialogStruct\) [CanSelectHiddenExtension]() + +```go +func (d *OpenFileDialogStruct) CanSelectHiddenExtension(canSelectHiddenExtension bool) *OpenFileDialogStruct +``` + + + + +### func \(\*OpenFileDialogStruct\) [HideExtension]() + +```go +func (d *OpenFileDialogStruct) HideExtension(hideExtension bool) *OpenFileDialogStruct +``` + + + + +### func \(\*OpenFileDialogStruct\) [PromptForMultipleSelection]() + +```go +func (d *OpenFileDialogStruct) PromptForMultipleSelection() ([]string, error) +``` + + + + +### func \(\*OpenFileDialogStruct\) [PromptForSingleSelection]() + +```go +func (d *OpenFileDialogStruct) PromptForSingleSelection() (string, error) +``` + + + + +### func \(\*OpenFileDialogStruct\) [ResolvesAliases]() + +```go +func (d *OpenFileDialogStruct) ResolvesAliases(resolvesAliases bool) *OpenFileDialogStruct +``` + + + + +### func \(\*OpenFileDialogStruct\) [SetButtonText]() + +```go +func (d *OpenFileDialogStruct) SetButtonText(text string) *OpenFileDialogStruct +``` + + + + +### func \(\*OpenFileDialogStruct\) [SetDirectory]() + +```go +func (d *OpenFileDialogStruct) SetDirectory(directory string) *OpenFileDialogStruct +``` + + + + +### func \(\*OpenFileDialogStruct\) [SetMessage]() + +```go +func (d *OpenFileDialogStruct) SetMessage(message string) *OpenFileDialogStruct +``` + + + + +### func \(\*OpenFileDialogStruct\) [SetOptions]() + +```go +func (d *OpenFileDialogStruct) SetOptions(options *OpenFileDialogOptions) +``` + + + + +### func \(\*OpenFileDialogStruct\) [SetTitle]() + +```go +func (d *OpenFileDialogStruct) SetTitle(title string) *OpenFileDialogStruct +``` + + + + +### func \(\*OpenFileDialogStruct\) [ShowHiddenFiles]() + +```go +func (d *OpenFileDialogStruct) ShowHiddenFiles(showHiddenFiles bool) *OpenFileDialogStruct +``` + + + + +### func \(\*OpenFileDialogStruct\) [TreatsFilePackagesAsDirectories]() + +```go +func (d *OpenFileDialogStruct) TreatsFilePackagesAsDirectories(treatsFilePackagesAsDirectories bool) *OpenFileDialogStruct +``` + + + + +## type [Options]() + + + +```go +type Options struct { + // Name is the name of the application + Name string + + // Description is the description of the application (used in the default about box) + Description string + + // Icon is the icon of the application (used in the default about box) + Icon []byte + + // Mac is the Mac specific configuration for Mac builds + Mac MacOptions + + // Windows is the Windows specific configuration for Windows builds + Windows WindowsOptions + + // Bind allows you to bind Go methods to the frontend. + Bind []any + + // 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). + // If not defined, a default logger is used. + Logger *slog.Logger + + // LogLevel defines the log level of the Wails system logger. + LogLevel slog.Level + + // Assets are the application assets to be used. + Assets AssetOptions + + // Plugins is a map of plugins used by the application + Plugins map[string]Plugin + + // Flags are key value pairs that are available to the frontend. + // This is also used by Wails to provide information to the frontend. + Flags map[string]any + + // PanicHandler is a way to register a custom panic handler + PanicHandler func(any) + + // KeyBindings is a map of key bindings to functions + KeyBindings map[string]func(window *WebviewWindow) +} +``` + + +## type [Parameter]() + +Parameter defines a Go method parameter + +```go +type Parameter struct { + Name string `json:"name,omitempty"` + TypeName string `json:"type"` + ReflectType reflect.Type +} +``` + + +### func \(\*Parameter\) [IsError]() + +```go +func (p *Parameter) IsError() bool +``` + +IsError returns true if the parameter type is an error + + +### func \(\*Parameter\) [IsType]() + +```go +func (p *Parameter) IsType(typename string) bool +``` + +IsType returns true if the given + + +## type [Plugin]() + + + +```go +type Plugin interface { + Name() string + Init() error + Shutdown() + CallableByJS() []string + InjectJS() string +} +``` + + +## type [PluginCallOptions]() + + + +```go +type PluginCallOptions struct { + Name string `json:"name"` + Args []any `json:"args"` +} +``` + + +## type [PluginManager]() + + + +```go +type PluginManager struct { + // contains filtered or unexported fields +} +``` + + +### func [NewPluginManager]() + +```go +func NewPluginManager(plugins map[string]Plugin, assetServer *assetserver.AssetServer) *PluginManager +``` + + + + +### func \(\*PluginManager\) [Init]() + +```go +func (p *PluginManager) Init() error +``` + + + + +### func \(\*PluginManager\) [Shutdown]() + +```go +func (p *PluginManager) Shutdown() +``` + + + + +## type [PositionOptions]() + + + +```go +type PositionOptions struct { + Buffer int +} +``` + + +## type [QueryParams]() + + + +```go +type QueryParams map[string][]string +``` + + +### func \(QueryParams\) [Args]() + +```go +func (qp QueryParams) Args() (*Args, error) +``` + + + + +### func \(QueryParams\) [Bool]() + +```go +func (qp QueryParams) Bool(key string) *bool +``` + + + + +### func \(QueryParams\) [Float64]() + +```go +func (qp QueryParams) Float64(key string) *float64 +``` + + + + +### func \(QueryParams\) [Int]() + +```go +func (qp QueryParams) Int(key string) *int +``` + + + + +### func \(QueryParams\) [String]() + +```go +func (qp QueryParams) String(key string) *string +``` + + + + +### func \(QueryParams\) [ToStruct]() + +```go +func (qp QueryParams) ToStruct(str any) error +``` + + + + +### func \(QueryParams\) [UInt]() + +```go +func (qp QueryParams) UInt(key string) *uint +``` + + + + +### func \(QueryParams\) [UInt8]() + +```go +func (qp QueryParams) UInt8(key string) *uint8 +``` + + + + +## type [RGBA]() + + + +```go +type RGBA struct { + Red, Green, Blue, Alpha uint8 +} +``` + + +## type [RadioGroup]() + + + +```go +type RadioGroup []*RadioGroupMember +``` + + +### func \(\*RadioGroup\) [Add]() + +```go +func (r *RadioGroup) Add(id int, item *MenuItem) +``` + + + + +### func \(\*RadioGroup\) [Bounds]() + +```go +func (r *RadioGroup) Bounds() (int, int) +``` + + + + +### func \(\*RadioGroup\) [MenuID]() + +```go +func (r *RadioGroup) MenuID(item *MenuItem) int +``` + + + + +## type [RadioGroupMember]() + + + +```go +type RadioGroupMember struct { + ID int + MenuItem *MenuItem +} +``` + + +## type [Rect]() + + + +```go +type Rect struct { + X int + Y int + Width int + Height int +} +``` + + +## type [Role]() + +Role is a type to identify menu roles + +```go +type Role uint +``` + +These constants need to be kept in sync with \`v2/internal/frontend/desktop/darwin/Role.h\` + +```go +const ( + NoRole Role = iota + AppMenu Role = iota + EditMenu Role = iota + ViewMenu Role = iota + WindowMenu Role = iota + ServicesMenu Role = iota + HelpMenu Role = iota + + Hide Role = iota + HideOthers Role = iota + UnHide Role = iota + About Role = iota + Undo Role = iota + Redo Role = iota + Cut Role = iota + Copy Role = iota + Paste Role = iota + PasteAndMatchStyle Role = iota + SelectAll Role = iota + Delete Role = iota + SpeechMenu Role = iota + Quit Role = iota + FileMenu Role = iota + Close Role = iota + Reload Role = iota + ForceReload Role = iota + ToggleDevTools Role = iota + ResetZoom Role = iota + ZoomIn Role = iota + ZoomOut Role = iota + ToggleFullscreen Role = iota + + Minimize Role = iota + Zoom Role = iota + FullScreen Role = iota +) +``` + + +## type [SaveFileDialogOptions]() + + + +```go +type SaveFileDialogOptions struct { + CanCreateDirectories bool + ShowHiddenFiles bool + CanSelectHiddenExtension bool + AllowOtherFileTypes bool + HideExtension bool + TreatsFilePackagesAsDirectories bool + Title string + Message string + Directory string + Filename string + ButtonText string + Filters []FileFilter + Window *WebviewWindow +} +``` + + +## type [SaveFileDialogStruct]() + + + +```go +type SaveFileDialogStruct struct { + // contains filtered or unexported fields +} +``` + + +### func [SaveFileDialog]() + +```go +func SaveFileDialog() *SaveFileDialogStruct +``` + + + + +### func [SaveFileDialogWithOptions]() + +```go +func SaveFileDialogWithOptions(s *SaveFileDialogOptions) *SaveFileDialogStruct +``` + + + + +### func \(\*SaveFileDialogStruct\) [AddFilter]() + +```go +func (d *SaveFileDialogStruct) AddFilter(displayName, pattern string) *SaveFileDialogStruct +``` + +AddFilter adds a filter to the dialog. The filter is a display name and a semicolon separated list of extensions. EG: AddFilter\("Image Files", "\*.jpg;\*.png"\) + + +### func \(\*SaveFileDialogStruct\) [AllowsOtherFileTypes]() + +```go +func (d *SaveFileDialogStruct) AllowsOtherFileTypes(allowOtherFileTypes bool) *SaveFileDialogStruct +``` + + + + +### func \(\*SaveFileDialogStruct\) [AttachToWindow]() + +```go +func (d *SaveFileDialogStruct) AttachToWindow(window *WebviewWindow) *SaveFileDialogStruct +``` + + + + +### func \(\*SaveFileDialogStruct\) [CanCreateDirectories]() + +```go +func (d *SaveFileDialogStruct) CanCreateDirectories(canCreateDirectories bool) *SaveFileDialogStruct +``` + + + + +### func \(\*SaveFileDialogStruct\) [CanSelectHiddenExtension]() + +```go +func (d *SaveFileDialogStruct) CanSelectHiddenExtension(canSelectHiddenExtension bool) *SaveFileDialogStruct +``` + + + + +### func \(\*SaveFileDialogStruct\) [HideExtension]() + +```go +func (d *SaveFileDialogStruct) HideExtension(hideExtension bool) *SaveFileDialogStruct +``` + + + + +### func \(\*SaveFileDialogStruct\) [PromptForSingleSelection]() + +```go +func (d *SaveFileDialogStruct) PromptForSingleSelection() (string, error) +``` + + + + +### func \(\*SaveFileDialogStruct\) [SetButtonText]() + +```go +func (d *SaveFileDialogStruct) SetButtonText(text string) *SaveFileDialogStruct +``` + + + + +### func \(\*SaveFileDialogStruct\) [SetDirectory]() + +```go +func (d *SaveFileDialogStruct) SetDirectory(directory string) *SaveFileDialogStruct +``` + + + + +### func \(\*SaveFileDialogStruct\) [SetFilename]() + +```go +func (d *SaveFileDialogStruct) SetFilename(filename string) *SaveFileDialogStruct +``` + + + + +### func \(\*SaveFileDialogStruct\) [SetMessage]() + +```go +func (d *SaveFileDialogStruct) SetMessage(message string) *SaveFileDialogStruct +``` + + + + +### func \(\*SaveFileDialogStruct\) [SetOptions]() + +```go +func (d *SaveFileDialogStruct) SetOptions(options *SaveFileDialogOptions) +``` + + + + +### func \(\*SaveFileDialogStruct\) [ShowHiddenFiles]() + +```go +func (d *SaveFileDialogStruct) ShowHiddenFiles(showHiddenFiles bool) *SaveFileDialogStruct +``` + + + + +### func \(\*SaveFileDialogStruct\) [TreatsFilePackagesAsDirectories]() + +```go +func (d *SaveFileDialogStruct) TreatsFilePackagesAsDirectories(treatsFilePackagesAsDirectories bool) *SaveFileDialogStruct +``` + + + + +## type [Screen]() + + + +```go +type Screen struct { + ID string // A unique identifier for the display + Name string // The name of the display + Scale float32 // The scale factor of the display + X int // The x-coordinate of the top-left corner of the rectangle + Y int // The y-coordinate of the top-left corner of the rectangle + Size Size // The size of the display + Bounds Rect // The bounds of the display + WorkArea Rect // The work area of the display + IsPrimary bool // Whether this is the primary display + Rotation float32 // The rotation of the display +} +``` + + +## type [Size]() + + + +```go +type Size struct { + Width int + Height int +} +``` + + +## type [SystemTray]() + + + +```go +type SystemTray struct { + // contains filtered or unexported fields +} +``` + + +### func \(\*SystemTray\) [AttachWindow]() + +```go +func (s *SystemTray) AttachWindow(window *WebviewWindow) *SystemTray +``` + +AttachWindow attaches a window to the system tray. The window will be shown when the system tray icon is clicked. The window will be hidden when the system tray icon is clicked again, or when the window loses focus. + + +### func \(\*SystemTray\) [Destroy]() + +```go +func (s *SystemTray) Destroy() +``` + + + + +### func \(\*SystemTray\) [Label]() + +```go +func (s *SystemTray) Label() string +``` + + + + +### func \(\*SystemTray\) [OnClick]() + +```go +func (s *SystemTray) OnClick(handler func()) *SystemTray +``` + + + + +### func \(\*SystemTray\) [OnDoubleClick]() + +```go +func (s *SystemTray) OnDoubleClick(handler func()) *SystemTray +``` + + + + +### func \(\*SystemTray\) [OnMouseEnter]() + +```go +func (s *SystemTray) OnMouseEnter(handler func()) *SystemTray +``` + + + + +### func \(\*SystemTray\) [OnMouseLeave]() + +```go +func (s *SystemTray) OnMouseLeave(handler func()) *SystemTray +``` + + + + +### func \(\*SystemTray\) [OnRightClick]() + +```go +func (s *SystemTray) OnRightClick(handler func()) *SystemTray +``` + + + + +### func \(\*SystemTray\) [OnRightDoubleClick]() + +```go +func (s *SystemTray) OnRightDoubleClick(handler func()) *SystemTray +``` + + + + +### func \(\*SystemTray\) [OpenMenu]() + +```go +func (s *SystemTray) OpenMenu() +``` + + + + +### func \(\*SystemTray\) [PositionWindow]() + +```go +func (s *SystemTray) PositionWindow(window *WebviewWindow, offset int) error +``` + + + + +### func \(\*SystemTray\) [SetDarkModeIcon]() + +```go +func (s *SystemTray) SetDarkModeIcon(icon []byte) *SystemTray +``` + + + + +### func \(\*SystemTray\) [SetIcon]() + +```go +func (s *SystemTray) SetIcon(icon []byte) *SystemTray +``` + + + + +### func \(\*SystemTray\) [SetIconPosition]() + +```go +func (s *SystemTray) SetIconPosition(iconPosition int) *SystemTray +``` + + + + +### func \(\*SystemTray\) [SetLabel]() + +```go +func (s *SystemTray) SetLabel(label string) +``` + + + + +### func \(\*SystemTray\) [SetMenu]() + +```go +func (s *SystemTray) SetMenu(menu *Menu) *SystemTray +``` + + + + +### func \(\*SystemTray\) [SetTemplateIcon]() + +```go +func (s *SystemTray) SetTemplateIcon(icon []byte) *SystemTray +``` + + + + +### func \(\*SystemTray\) [WindowDebounce]() + +```go +func (s *SystemTray) WindowDebounce(debounce time.Duration) *SystemTray +``` + +WindowDebounce is used by Windows to indicate how long to wait before responding to a mouse up event on the notification icon. This prevents the window from being hidden and then immediately shown when the user clicks on the system tray icon. See https://stackoverflow.com/questions/4585283/alternate-showing-hiding-window-when-notify-icon-is-clicked + + +### func \(\*SystemTray\) [WindowOffset]() + +```go +func (s *SystemTray) WindowOffset(offset int) *SystemTray +``` + +WindowOffset sets the gap in pixels between the system tray and the window + + +## type [Theme]() + + + +```go +type Theme int +``` + + + +```go +const ( + // SystemDefault will use whatever the system theme is. The application will follow system theme changes. + SystemDefault Theme = 0 + // Dark Mode + Dark Theme = 1 + // Light Mode + Light Theme = 2 +) +``` + + +## type [ThemeSettings]() + +ThemeSettings defines custom colours to use in dark or light mode. They may be set using the hex values: 0x00BBGGRR + +```go +type ThemeSettings struct { + DarkModeTitleBar int32 + DarkModeTitleBarInactive int32 + DarkModeTitleText int32 + DarkModeTitleTextInactive int32 + DarkModeBorder int32 + DarkModeBorderInactive int32 + LightModeTitleBar int32 + LightModeTitleBarInactive int32 + LightModeTitleText int32 + LightModeTitleTextInactive int32 + LightModeBorder int32 + LightModeBorderInactive int32 +} +``` + + +## type [WailsEvent]() + + + +```go +type WailsEvent struct { + Name string `json:"name"` + Data any `json:"data"` + Sender string `json:"sender"` + Cancelled bool +} +``` + + +### func \(\*WailsEvent\) [Cancel]() + +```go +func (e *WailsEvent) Cancel() +``` + + + + +## type [WebviewWindow]() + + + +```go +type WebviewWindow struct { + // contains filtered or unexported fields +} +``` + + +### func \(\*WebviewWindow\) [AbsolutePosition]() + +```go +func (w *WebviewWindow) AbsolutePosition() (int, int) +``` + +AbsolutePosition returns the absolute position of the window to the screen + + +### func \(\*WebviewWindow\) [Center]() + +```go +func (w *WebviewWindow) Center() +``` + +Center centers the window on the screen + + +### func \(\*WebviewWindow\) [Close]() + +```go +func (w *WebviewWindow) Close() +``` + +Close closes the window + + +### func \(\*WebviewWindow\) [Destroy]() + +```go +func (w *WebviewWindow) Destroy() +``` + + + + +### func \(\*WebviewWindow\) [ExecJS]() + +```go +func (w *WebviewWindow) ExecJS(js string) +``` + +ExecJS executes the given javascript in the context of the window. + + +### func \(\*WebviewWindow\) [Flash]() + +```go +func (w *WebviewWindow) Flash(enabled bool) +``` + +Flash flashes the window's taskbar button/icon. Windows only. + + +### func \(\*WebviewWindow\) [Focus]() + +```go +func (w *WebviewWindow) Focus() +``` + + + + +### func \(\*WebviewWindow\) [ForceReload]() + +```go +func (w *WebviewWindow) ForceReload() +``` + +ForceReload forces the window to reload the page assets + + +### func \(\*WebviewWindow\) [Fullscreen]() + +```go +func (w *WebviewWindow) Fullscreen() *WebviewWindow +``` + +Fullscreen sets the window to fullscreen mode. Min/Max size constraints are disabled. + + +### func \(\*WebviewWindow\) [GetScreen]() + +```go +func (w *WebviewWindow) GetScreen() (*Screen, error) +``` + +GetScreen returns the screen that the window is on + + +### func \(\*WebviewWindow\) [GetZoom]() + +```go +func (w *WebviewWindow) GetZoom() float64 +``` + +GetZoom returns the current zoom level of the window. + + +### func \(\*WebviewWindow\) [Height]() + +```go +func (w *WebviewWindow) Height() int +``` + +Height returns the height of the window + + +### func \(\*WebviewWindow\) [Hide]() + +```go +func (w *WebviewWindow) Hide() *WebviewWindow +``` + +Hide hides the window. + + +### func \(\*WebviewWindow\) [IsFocused]() + +```go +func (w *WebviewWindow) IsFocused() bool +``` + +IsFocused returns true if the window is currently focused + + +### func \(\*WebviewWindow\) [IsFullscreen]() + +```go +func (w *WebviewWindow) IsFullscreen() bool +``` + +IsFullscreen returns true if the window is fullscreen + + +### func \(\*WebviewWindow\) [IsMaximised]() + +```go +func (w *WebviewWindow) IsMaximised() bool +``` + +IsMaximised returns true if the window is maximised + + +### func \(\*WebviewWindow\) [IsMinimised]() + +```go +func (w *WebviewWindow) IsMinimised() bool +``` + +IsMinimised returns true if the window is minimised + + +### func \(\*WebviewWindow\) [IsVisible]() + +```go +func (w *WebviewWindow) IsVisible() bool +``` + +IsVisible returns true if the window is visible + + +### func \(\*WebviewWindow\) [Maximise]() + +```go +func (w *WebviewWindow) Maximise() *WebviewWindow +``` + +Maximise maximises the window. Min/Max size constraints are disabled. + + +### func \(\*WebviewWindow\) [Minimise]() + +```go +func (w *WebviewWindow) Minimise() *WebviewWindow +``` + +Minimise minimises the window. + + +### func \(\*WebviewWindow\) [Name]() + +```go +func (w *WebviewWindow) Name() string +``` + +Name returns the name of the window + + +### func \(\*WebviewWindow\) [NativeWindowHandle]() + +```go +func (w *WebviewWindow) NativeWindowHandle() (uintptr, error) +``` + +NativeWindowHandle returns the platform native window handle for the window. + + +### func \(\*WebviewWindow\) [On]() + +```go +func (w *WebviewWindow) On(eventType events.WindowEventType, callback func(event *WindowEvent)) func() +``` + +On registers a callback for the given window event + + +### func \(\*WebviewWindow\) [Print]() + +```go +func (w *WebviewWindow) Print() error +``` + + + + +### func \(\*WebviewWindow\) [RegisterContextMenu]() + +```go +func (w *WebviewWindow) RegisterContextMenu(name string, menu *Menu) +``` + +RegisterContextMenu registers a context menu and assigns it the given name. + + +### func \(\*WebviewWindow\) [RegisterHook]() + +```go +func (w *WebviewWindow) RegisterHook(eventType events.WindowEventType, callback func(event *WindowEvent)) func() +``` + +RegisterHook registers a hook for the given window event + + +### func \(\*WebviewWindow\) [RelativePosition]() + +```go +func (w *WebviewWindow) RelativePosition() (int, int) +``` + +RelativePosition returns the relative position of the window to the screen + + +### func \(\*WebviewWindow\) [Reload]() + +```go +func (w *WebviewWindow) Reload() +``` + +Reload reloads the page assets + + +### func \(\*WebviewWindow\) [Resizable]() + +```go +func (w *WebviewWindow) Resizable() bool +``` + +Resizable returns true if the window is resizable. + + +### func \(\*WebviewWindow\) [Restore]() + +```go +func (w *WebviewWindow) Restore() +``` + +Restore restores the window to its previous state if it was previously minimised, maximised or fullscreen. + + +### func \(\*WebviewWindow\) [SetAbsolutePosition]() + +```go +func (w *WebviewWindow) SetAbsolutePosition(x int, y int) +``` + + + + +### func \(\*WebviewWindow\) [SetAlwaysOnTop]() + +```go +func (w *WebviewWindow) SetAlwaysOnTop(b bool) *WebviewWindow +``` + +SetAlwaysOnTop sets the window to be always on top. + + +### func \(\*WebviewWindow\) [SetBackgroundColour]() + +```go +func (w *WebviewWindow) SetBackgroundColour(colour RGBA) *WebviewWindow +``` + +SetBackgroundColour sets the background colour of the window + + +### func \(\*WebviewWindow\) [SetEnabled]() + +```go +func (w *WebviewWindow) SetEnabled(enabled bool) +``` + + + + +### func \(\*WebviewWindow\) [SetFrameless]() + +```go +func (w *WebviewWindow) SetFrameless(frameless bool) *WebviewWindow +``` + +SetFrameless removes the window frame and title bar + + +### func \(\*WebviewWindow\) [SetFullscreenButtonEnabled]() + +```go +func (w *WebviewWindow) SetFullscreenButtonEnabled(enabled bool) *WebviewWindow +``` + + + + +### func \(\*WebviewWindow\) [SetHTML]() + +```go +func (w *WebviewWindow) SetHTML(html string) *WebviewWindow +``` + +SetHTML sets the HTML of the window to the given html string. + + +### func \(\*WebviewWindow\) [SetMaxSize]() + +```go +func (w *WebviewWindow) SetMaxSize(maxWidth, maxHeight int) *WebviewWindow +``` + +SetMaxSize sets the maximum size of the window. + + +### func \(\*WebviewWindow\) [SetMinSize]() + +```go +func (w *WebviewWindow) SetMinSize(minWidth, minHeight int) *WebviewWindow +``` + +SetMinSize sets the minimum size of the window. + + +### func \(\*WebviewWindow\) [SetRelativePosition]() + +```go +func (w *WebviewWindow) SetRelativePosition(x, y int) *WebviewWindow +``` + +SetRelativePosition sets the position of the window. + + +### func \(\*WebviewWindow\) [SetResizable]() + +```go +func (w *WebviewWindow) SetResizable(b bool) *WebviewWindow +``` + +SetResizable sets whether the window is resizable. + + +### func \(\*WebviewWindow\) [SetSize]() + +```go +func (w *WebviewWindow) SetSize(width, height int) *WebviewWindow +``` + +SetSize sets the size of the window + + +### func \(\*WebviewWindow\) [SetTitle]() + +```go +func (w *WebviewWindow) SetTitle(title string) *WebviewWindow +``` + +SetTitle sets the title of the window + + +### func \(\*WebviewWindow\) [SetURL]() + +```go +func (w *WebviewWindow) SetURL(s string) *WebviewWindow +``` + + + + +### func \(\*WebviewWindow\) [SetZoom]() + +```go +func (w *WebviewWindow) SetZoom(magnification float64) *WebviewWindow +``` + +SetZoom sets the zoom level of the window. + + +### func \(\*WebviewWindow\) [Show]() + +```go +func (w *WebviewWindow) Show() *WebviewWindow +``` + +Show shows the window. + + +### func \(\*WebviewWindow\) [Size]() + +```go +func (w *WebviewWindow) Size() (int, int) +``` + +Size returns the size of the window + + +### func \(\*WebviewWindow\) [ToggleDevTools]() + +```go +func (w *WebviewWindow) ToggleDevTools() +``` + + + + +### func \(\*WebviewWindow\) [ToggleFullscreen]() + +```go +func (w *WebviewWindow) ToggleFullscreen() +``` + +ToggleFullscreen toggles the window between fullscreen and normal + + +### func \(\*WebviewWindow\) [UnFullscreen]() + +```go +func (w *WebviewWindow) UnFullscreen() +``` + +UnFullscreen un\-fullscreens the window. + + +### func \(\*WebviewWindow\) [UnMaximise]() + +```go +func (w *WebviewWindow) UnMaximise() +``` + +UnMaximise un\-maximises the window. + + +### func \(\*WebviewWindow\) [UnMinimise]() + +```go +func (w *WebviewWindow) UnMinimise() +``` + +UnMinimise un\-minimises the window. Min/Max size constraints are re\-enabled. + + +### func \(\*WebviewWindow\) [Width]() + +```go +func (w *WebviewWindow) Width() int +``` + +Width returns the width of the window + + +### func \(\*WebviewWindow\) [Zoom]() + +```go +func (w *WebviewWindow) Zoom() +``` + + + + +### func \(\*WebviewWindow\) [ZoomIn]() + +```go +func (w *WebviewWindow) ZoomIn() +``` + +ZoomIn increases the zoom level of the webview content + + +### func \(\*WebviewWindow\) [ZoomOut]() + +```go +func (w *WebviewWindow) ZoomOut() +``` + +ZoomOut decreases the zoom level of the webview content + + +### func \(\*WebviewWindow\) [ZoomReset]() + +```go +func (w *WebviewWindow) ZoomReset() *WebviewWindow +``` + +ZoomReset resets the zoom level of the webview content to 100% + + +## type [WebviewWindowOptions]() + + + +```go +type WebviewWindowOptions struct { + // Name is a unique identifier that can be given to a window. + Name string + + // Title is the title of the window. + Title string + + // Width is the starting width of the window. + Width int + + // Height is the starting height of the window. + Height int + + // AlwaysOnTop will make the window float above other windows. + AlwaysOnTop bool + + // URL is the URL to load in the window. + URL string + + // DisableResize will disable the ability to resize the window. + DisableResize bool + + // Frameless will remove the window frame. + Frameless bool + + // MinWidth is the minimum width of the window. + MinWidth int + + // MinHeight is the minimum height of the window. + MinHeight int + + // MaxWidth is the maximum width of the window. + MaxWidth int + + // MaxHeight is the maximum height of the window. + MaxHeight int + + // StartState indicates the state of the window when it is first shown. + // Default: WindowStateNormal + StartState WindowState + + // Centered will center the window on the screen. + Centered bool + + // BackgroundType is the type of background to use for the window. + // Default: BackgroundTypeSolid + BackgroundType BackgroundType + + // BackgroundColour is the colour to use for the window background. + BackgroundColour RGBA + + // HTML is the HTML to load in the window. + HTML string + + // JS is the JavaScript to load in the window. + JS string + + // CSS is the CSS to load in the window. + CSS string + + // X is the starting X position of the window. + X int + + // Y is the starting Y position of the window. + Y int + + // TransparentTitlebar will make the titlebar transparent. + // TODO: Move to mac window options + FullscreenButtonEnabled bool + + // Hidden will hide the window when it is first created. + Hidden bool + + // Zoom is the zoom level of the window. + Zoom float64 + + // ZoomControlEnabled will enable the zoom control. + ZoomControlEnabled bool + + // EnableDragAndDrop will enable drag and drop. + EnableDragAndDrop bool + + // OpenInspectorOnStartup will open the inspector when the window is first shown. + OpenInspectorOnStartup bool + + // Mac options + Mac MacWindow + + // Windows options + Windows WindowsWindow + + // Focused indicates the window should be focused when initially shown + Focused bool + + // ShouldClose is called when the window is about to close. + // Return true to allow the window to close, or false to prevent it from closing. + ShouldClose func(window *WebviewWindow) bool + + // If true, the window's devtools will be available (default true in builds without the `production` build tag) + DevToolsEnabled bool + + // If true, the window's default context menu will be disabled (default false) + DefaultContextMenuDisabled bool + + // KeyBindings is a map of key bindings to functions + KeyBindings map[string]func(window *WebviewWindow) +} +``` + + +## type [Win32Menu]() + + + +```go +type Win32Menu struct { + // contains filtered or unexported fields +} +``` + + +### func [NewApplicationMenu]() + +```go +func NewApplicationMenu(parent w32.HWND, inputMenu *Menu) *Win32Menu +``` + + + + +### func [NewPopupMenu]() + +```go +func NewPopupMenu(parent w32.HWND, inputMenu *Menu) *Win32Menu +``` + + + + +### func \(\*Win32Menu\) [Destroy]() + +```go +func (p *Win32Menu) Destroy() +``` + + + + +### func \(\*Win32Menu\) [OnMenuClose]() + +```go +func (p *Win32Menu) OnMenuClose(fn func()) +``` + + + + +### func \(\*Win32Menu\) [OnMenuOpen]() + +```go +func (p *Win32Menu) OnMenuOpen(fn func()) +``` + + + + +### func \(\*Win32Menu\) [ProcessCommand]() + +```go +func (p *Win32Menu) ProcessCommand(cmdMsgID int) bool +``` + + + + +### func \(\*Win32Menu\) [ShowAt]() + +```go +func (p *Win32Menu) ShowAt(x int, y int) +``` + + + + +### func \(\*Win32Menu\) [ShowAtCursor]() + +```go +func (p *Win32Menu) ShowAtCursor() +``` + + + + +### func \(\*Win32Menu\) [Update]() + +```go +func (p *Win32Menu) Update() +``` + + + + +### func \(\*Win32Menu\) [UpdateMenuItem]() + +```go +func (p *Win32Menu) UpdateMenuItem(item *MenuItem) +``` + + + + +## type [WindowAttachConfig]() + + + +```go +type WindowAttachConfig struct { + // Window is the window to attach to the system tray. If it's null, the request to attach will be ignored. + Window *WebviewWindow + + // Offset indicates the gap in pixels between the system tray and the window + Offset int + + // Debounce is used by Windows to indicate how long to wait before responding to a mouse + // up event on the notification icon. See https://stackoverflow.com/questions/4585283/alternate-showing-hiding-window-when-notify-icon-is-clicked + Debounce time.Duration + // contains filtered or unexported fields +} +``` + + +## type [WindowEvent]() + + + +```go +type WindowEvent struct { + Cancelled bool + // contains filtered or unexported fields +} +``` + + +### func [NewWindowEvent]() + +```go +func NewWindowEvent() *WindowEvent +``` + + + + +### func \(\*WindowEvent\) [Cancel]() + +```go +func (w *WindowEvent) Cancel() +``` + + + + +### func \(\*WindowEvent\) [Context]() + +```go +func (w *WindowEvent) Context() *WindowEventContext +``` + + + + +## type [WindowEventContext]() + + + +```go +type WindowEventContext struct { + // contains filtered or unexported fields +} +``` + + +### func \(WindowEventContext\) [DroppedFiles]() + +```go +func (c WindowEventContext) DroppedFiles() []string +``` + + + + +## type [WindowEventListener]() + + + +```go +type WindowEventListener struct { + // contains filtered or unexported fields +} +``` + + +## type [WindowState]() + + + +```go +type WindowState int +``` + + + +```go +const ( + WindowStateNormal WindowState = iota + WindowStateMinimised + WindowStateMaximised + WindowStateFullscreen +) +``` + + +## type [WindowsOptions]() + +WindowsOptions contains options for Windows applications. + +```go +type WindowsOptions struct { + + // WndProcInterceptor is a function that will be called for every message sent in the application. + // Use this to hook into the main message loop. This is useful for handling custom window messages. + // If `shouldReturn` is `true` then `returnCode` will be returned by the main message loop. + // If `shouldReturn` is `false` then returnCode will be ignored and the message will be processed by the main message loop. + WndProcInterceptor func(hwnd uintptr, msg uint32, wParam, lParam uintptr) (returnCode uintptr, shouldReturn bool) + + // DisableQuitOnLastWindowClosed disables the auto quit of the application if the last window has been closed. + DisableQuitOnLastWindowClosed bool + + // Path where the WebView2 stores the user data. If empty %APPDATA%\[BinaryName.exe] will be used. + // If the path is not valid, a messagebox will be displayed with the error and the app will exit with error code. + WebviewUserDataPath string + + // Path to the directory with WebView2 executables. If empty WebView2 installed in the system will be used. + WebviewBrowserPath string +} +``` + + +## type [WindowsWindow]() + + + +```go +type WindowsWindow struct { + // Select the type of translucent backdrop. Requires Windows 11 22621 or later. + // Only used when window's `BackgroundType` is set to `BackgroundTypeTranslucent`. + // Default: Auto + BackdropType BackdropType + + // Disable the icon in the titlebar + // Default: false + DisableIcon bool + + // Theme (Dark / Light / SystemDefault) + // Default: SystemDefault - The application will follow system theme changes. + Theme Theme + + // Specify custom colours to use for dark/light mode + // Default: nil + CustomTheme *ThemeSettings + + // Disable all window decorations in Frameless mode, which means no "Aero Shadow" and no "Rounded Corner" will be shown. + // "Rounded Corners" are only available on Windows 11. + // Default: false + DisableFramelessWindowDecorations bool + + // WindowMask is used to set the window shape. Use a PNG with an alpha channel to create a custom shape. + // Default: nil + WindowMask []byte + + // WindowMaskDraggable is used to make the window draggable by clicking on the window mask. + // Default: false + WindowMaskDraggable bool + + // WebviewGpuIsDisabled is used to enable / disable GPU acceleration for the webview + // Default: false + WebviewGpuIsDisabled bool + + // ResizeDebounceMS is the amount of time to debounce redraws of webview2 + // when resizing the window + // Default: 0 + ResizeDebounceMS uint16 + + // Disable the menu bar for this window + // Default: false + DisableMenu bool + + // Event mapping for the window. This allows you to define a translation from one event to another. + // Default: nil + EventMapping map[events.WindowEventType]events.WindowEventType + + // HiddenOnTaskbar hides the window from the taskbar + // Default: false + HiddenOnTaskbar bool + + // EnableSwipeGestures enables swipe gestures for the window + // Default: false + EnableSwipeGestures bool + + // EnableFraudulentWebsiteWarnings will enable warnings for fraudulent websites. + // Default: false + EnableFraudulentWebsiteWarnings bool + + // Menu is the menu to use for the window. + Menu *Menu +} +``` \ No newline at end of file diff --git a/mkdocs-website/docs/en/API/mainthread.md b/mkdocs-website/docs/en/API/mainthread.md new file mode 100644 index 000000000..0a22da5bb --- /dev/null +++ b/mkdocs-website/docs/en/API/mainthread.md @@ -0,0 +1,50 @@ +# Main Thread Functions + +These methods are utility functions to run code on the main thread. This is +required when you want to run custom code on the UI thread. + +### InvokeSync + +API: `InvokeSync(fn func())` + +This function runs the passed function (`fn`) synchronously. It uses a WaitGroup +(`wg`) to ensure that the main thread waits for the `fn` function to finish +before it continues. If a panic occurs inside `fn`, it will be passed to the +handler function `PanicHandler`, defined in the application options. + +### InvokeSyncWithResult + +API: `InvokeSyncWithResult[T any](fn func() T) (res T)` + +This function works similarly to `InvokeSync(fn func())`, however, it yields a +result. Use this for calling any function with a single return. + +### InvokeSyncWithError + +API: `InvokeSyncWithError(fn func() error) (err error)` + +This function runs `fn` synchronously and returns any error that `fn` produces. +Note that this function will recover from a panic if one occurs during `fn`'s +execution. + +### InvokeSyncWithResultAndError + +API: +`InvokeSyncWithResultAndError[T any](fn func() (T, error)) (res T, err error)` + +This function runs `fn` synchronously and returns both a result of type `T` and +an error. + +### InvokeAsync + +API: `InvokeAsync(fn func())` + +This function runs `fn` asynchronously. It runs the given function on the main +thread. If a panic occurs inside `fn`, it will be passed to the handler function +`PanicHandler`, defined in the application options. + +--- + +_Note_: These functions will block execution until `fn` has finished. It's +critical to ensure that `fn` doesn't block. If you need to run a function that +blocks, use `InvokeAsync` instead. diff --git a/mkdocs-website/docs/en/API/menu.md b/mkdocs-website/docs/en/API/menu.md new file mode 100644 index 000000000..fe28116bd --- /dev/null +++ b/mkdocs-website/docs/en/API/menu.md @@ -0,0 +1,69 @@ +# Menu + +Menus can be created and added to the application. They can be used to create +context menus, system tray menus and application menus. + +To create a new menu, call: + +```go + // Create a new menu + menu := app.NewMenu() +``` + +The following operations are then available on the `Menu` type: + +### Add + +API: `Add(label string) *MenuItem` + +This method takes a `label` of type `string` as an input and adds a new +`MenuItem` with the given label to the menu. It returns the `MenuItem` added. + +### AddSeparator + +API: `AddSeparator()` + +This method adds a new separator `MenuItem` to the menu. + +### AddCheckbox + +API: `AddCheckbox(label string, enabled bool) *MenuItem` + +This method takes a `label` of type `string` and `enabled` of type `bool` as +inputs and adds a new checkbox `MenuItem` with the given label and enabled state +to the menu. It returns the `MenuItem` added. + +### AddRadio + +API: `AddRadio(label string, enabled bool) *MenuItem` + +This method takes a `label` of type `string` and `enabled` of type `bool` as +inputs and adds a new radio `MenuItem` with the given label and enabled state to +the menu. It returns the `MenuItem` added. + +### Update + +API: `Update()` + +This method processes any radio groups and updates the menu if a menu +implementation is not initialized. + +### AddSubmenu + +API: `AddSubmenu(s string) *Menu` + +This method takes a `s` of type `string` as input and adds a new submenu +`MenuItem` with the given label to the menu. It returns the submenu added. + +### AddRole + +API: `AddRole(role Role) *Menu` + +This method takes `role` of type `Role` as input, adds it to the menu if it is +not `nil` and returns the `Menu`. + +### SetLabel + +API: `SetLabel(label string)` + +This method sets the `label` of the `Menu`. diff --git a/mkdocs-website/docs/en/API/systray.md b/mkdocs-website/docs/en/API/systray.md new file mode 100644 index 000000000..30f15dc47 --- /dev/null +++ b/mkdocs-website/docs/en/API/systray.md @@ -0,0 +1,112 @@ +# System Tray + +The system tray houses notification area on a desktop environment, which can +contain both icons of currently-running applications and specific system +notifications. + +You create a system tray by calling `app.NewSystemTray()`: + +```go + // Create a new system tray +tray := app.NewSystemTray() +``` + +The following methods are available on the `SystemTray` type: + +### SetLabel + +API: `SetLabel(label string)` + +The `SetLabel` method sets the tray's label. + +### Label + +API: `Label() string` + +The `Label` method retrieves the tray's label. + +### PositionWindow + +API: `PositionWindow(*WebviewWindow, offset int) error` + +The `PositionWindow` method calls both `AttachWindow` and `WindowOffset` +methods. + +### SetIcon + +API: `SetIcon(icon []byte) *SystemTray` + +The `SetIcon` method sets the system tray's icon. + +### SetDarkModeIcon + +API: `SetDarkModeIcon(icon []byte) *SystemTray` + +The `SetDarkModeIcon` method sets the system tray's icon when in dark mode. + +### SetMenu + +API: `SetMenu(menu *Menu) *SystemTray` + +The `SetMenu` method sets the system tray's menu. + +### Destroy + +API: `Destroy()` + +The `Destroy` method destroys the system tray instance. + +### OnClick + +API: `OnClick(handler func()) *SystemTray` + +The `OnClick` method sets the function to execute when the tray icon is clicked. + +### OnRightClick + +API: `OnRightClick(handler func()) *SystemTray` + +The `OnRightClick` method sets the function to execute when right-clicking the +tray icon. + +### OnDoubleClick + +API: `OnDoubleClick(handler func()) *SystemTray` + +The `OnDoubleClick` method sets the function to execute when double-clicking the +tray icon. + +### OnRightDoubleClick + +API: `OnRightDoubleClick(handler func()) *SystemTray` + +The `OnRightDoubleClick` method sets the function to execute when right +double-clicking the tray icon. + +### AttachWindow + +API: `AttachWindow(window *WebviewWindow) *SystemTray` + +The `AttachWindow` method attaches a window to the system tray. The window will +be shown when the system tray icon is clicked. + +### WindowOffset + +API: `WindowOffset(offset int) *SystemTray` + +The `WindowOffset` method sets the gap in pixels between the system tray and the +window. + +### WindowDebounce + +API: `WindowDebounce(debounce time.Duration) *SystemTray` + +The `WindowDebounce` method sets a debounce time. In the context of Windows, +this is used to specify how long to wait before responding to a mouse up event +on the notification icon. + +### OpenMenu + +API: `OpenMenu()` + +The `OpenMenu` method opens the menu associated with the system tray. diff --git a/mkdocs-website/docs/en/API/window.md b/mkdocs-website/docs/en/API/window.md new file mode 100644 index 000000000..aa73ec91a --- /dev/null +++ b/mkdocs-website/docs/en/API/window.md @@ -0,0 +1,114 @@ +# Window + +To create a window, use +[Application.NewWebviewWindow](application.md#newwebviewwindow) or +[Application.NewWebviewWindowWithOptions](application.md#newwebviewwindowwithoptions). +The former creates a window with default options, while the latter allows you to +specify custom options. + +These methods are callable on the returned WebviewWindow object: + +### SetTitle + +API: `SetTitle(title string) *WebviewWindow` + +This method updates the window title to the provided string. It returns the +WebviewWindow object, allowing for method chaining. + +### Name + +API: `Name() string` + +This function returns the name of the WebviewWindow. + +### SetSize + +API: `SetSize(width, height int) *WebviewWindow` + +This method sets the size of the WebviewWindow to the provided width and height +parameters. If the dimensions provided exceed the constraints, they are adjusted +appropriately. + +### SetAlwaysOnTop + +API: `SetAlwaysOnTop(b bool) *WebviewWindow` + +This function sets the window to stay on top based on the boolean flag provided. + +### Show + +API: `Show() *WebviewWindow` + +`Show` method is used to make the window visible. If the window is not running, +it first invokes the `run` method to start the window and then makes it visible. + +### Hide + +API: `Hide() *WebviewWindow` + +`Hide` method is used to hide the window. It sets the hidden status of the +window to true and emits the window hide event. + +### SetURL + +API: `SetURL(s string) *WebviewWindow` + +`SetURL` method is used to set the URL of the window to the given URL string. + +### SetZoom + +API: `SetZoom(magnification float64) *WebviewWindow` + +`SetZoom` method sets the zoom level of the window content to the provided +magnification level. + +### GetZoom + +API: `GetZoom() float64` + +`GetZoom` function returns the current zoom level of the window content. + +### GetScreen + +API: `GetScreen() (*Screen, error)` + +`GetScreen` method returns the screen on which the window is displayed. + +#### SetFrameless + +API: `SetFrameless(frameless bool) *WebviewWindow` + +This function is used to remove the window frame and title bar. It toggles the +framelessness of the window according to the boolean value provided (true for +frameless, false for framed). + +#### RegisterContextMenu + +API: `RegisterContextMenu(name string, menu *Menu)` + +This function is used to register a context menu and assigns it the given name. + +#### NativeWindowHandle + +API: `NativeWindowHandle() (uintptr, error)` + +This function is used to fetch the platform native window handle for the window. + +#### Focus + +API: `Focus()` + +This function is used to focus the window. + +#### SetEnabled + +API: `SetEnabled(enabled bool)` + +This function is used to enable/disable the window based on the provided boolean +value. + +#### SetAbsolutePosition + +API: `SetAbsolutePosition(x int, y int)` + +This function sets the absolute position of the window in the screen. diff --git a/mkdocs-website/docs/en/changelog.md b/mkdocs-website/docs/en/changelog.md new file mode 100644 index 000000000..4f6cb8bc6 --- /dev/null +++ b/mkdocs-website/docs/en/changelog.md @@ -0,0 +1,39 @@ +# Changelog + + + +## [Unreleased] + +### Added + +- [darwin] add Event ApplicationShouldHandleReopen to able handle dock icon click by @5aaee9 in [#2991](https://github.com/wailsapp/wails/pull/2991) +- [darwin] add getPrimaryScreen/getScreens to impl by @tmclane in [#2618](https://github.com/wailsapp/wails/pull/2618) + +### Fixed + +- Fixed Doctor apt package verify by [Atterpac](https://github.com/Atterpac) in [#2972](https://github.com/wailsapp/wails/pull/2972). +- Fixed application frozen when quit (Darwin) by @5aaee9 in [#2982](https://github.com/wailsapp/wails/pull/2982) +- Fixed background colours of examples on Windows by [mmgvh](https://github.com/mmghv) in [#2750](https://github.com/wailsapp/wails/pull/2750). +- Fixed default context menus by [mmgvh](https://github.com/mmghv) in [#2753](https://github.com/wailsapp/wails/pull/2753). + + +### Changed + +### Removed + +### Deprecated + +### Security diff --git a/mkdocs-website/docs/en/development/changes.md b/mkdocs-website/docs/en/development/changes.md new file mode 100644 index 000000000..9b37d2576 --- /dev/null +++ b/mkdocs-website/docs/en/development/changes.md @@ -0,0 +1,454 @@ +# Changes for v3 + +!!! note + This is currently an unsorted brain dump of changes. It will be organised into a more readable format soon. + +## Options + +The application options have been revised since v2. + +## Events + +In v3, there are 3 types of events: + +- Application Events +- Window Events +- Custom Events + +### Application Events + +Application events are events that are emitted by the application. These events +include native events such as `ApplicationDidFinishLaunching` on macOS. + +### Window Events + +Window events are events that are emitted by a window. These events include +native events such as `WindowDidBecomeMain` on macOS. Common events are also +defined, so they work cross-platform, e.g. `WindowClosing`. + +### Custom Events + +Events that the user defines are called `WailsEvents`. This is to differentiate +them from the `Event` object that is used to communicate with the browser. +WailsEvents are now objects that encapsulate all the details of an event. This +includes the event name, the data, and the source of the event. + +The data associated with a WailsEvent is now a single value. If multiple values +are required, then a struct can be used. + +### Event callbacks and `Emit` function signature + +The signatures events callbacks (as used by `On`, `Once` & `OnMultiple`) have +changed. In v2, the callback function received optional data. In v3, the +callback function receives a `WailsEvent` object that contains all data related +to the event. + +Similarly, the `Emit` function has changed. Instead of taking a name and +optional data, it now takes a single `WailsEvent` object that it will emit. + +### `Off` and `OffAll` + +In v2, `Off` and `OffAll` calls would remove events in both JS and Go. Due to +the multi-window nature of v3, this has been changed so that these methods only +apply to the context they are called in. For example, if you call `Off` in a +window, it will only remove events for that window. If you use `Off` in Go, it +will only remove events for Go. + +### Hooks + +Event Hooks are a new feature in v3. They allow you to hook into the event +system and perform actions when certain events are emitted. For example, you can +hook into the `WindowClosing` event and perform some cleanup before the window +closes. Hooks can be registered at the application level or at the window level +using `RegisterHook`. Application level are for application events. Window level +hooks will only be called for the window they are registered with. + +### Logging + +Logging in v2 was confusing as both application logs and system (internal) logs +were using the same logger. We have simplified this as follows: + +- Internal logs are now handled using the standard Go `slog` logger. This is + configured using the `logger` option in the application options. By default, + this uses the [tint](https://github.com/lmittmann/tint) logger. +- Application logs can now be achieved through the new `log` plugin which + utilises `slog` under the hood. This plugin provides a simple API for logging + to the console. It is available in both Go and JS. + +### Developer notes + +When emitting an event in Go, it will dispatch the event to local Go listeners +and also each window in the application. When emitting an event in JS, it now +sends the event to the application. This will be processed as if it was emitted +in Go, however the sender ID will be that of the window. + +## Window + +The Window API has largely remained the same, however the methods are now on an +instance of a window rather than the runtime. Some notable differences are: + +- Windows now have a Name that identifies them. This is used to identify the + window when emitting events. +- Windows have even more methods on the that were previously unavailable, such + as `AbsolutePosition` and `ToggleDevTools`. +- Windows can now accept files via native drag and drop. See the Drag and Drop + section for more details. + +## ClipBoard + +The clipboard API has been simplified. There is now a single `Clipboard` object +that can be used to read and write to the clipboard. The `Clipboard` object is +available in both Go and JS. `SetText()` to set the text and `Text()` to get the +text. + +## Bindings + +Bindings work in a similar way to v2, by providing a means to bind struct +methods to the frontend. These can be called in the frontend using the binding +wrappers generated by the `wails3 generate bindings` command: + +```javascript +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import { main } from "./models"; + +window.go = window.go || {}; +window.go.main = { + GreetService: { + /** + * GreetService.Greet + * Greet greets a person + * @param name {string} + * @returns {Promise} + **/ + Greet: function (name) { + wails.CallByID(1411160069, ...Array.prototype.slice.call(arguments, 0)); + }, + + /** + * GreetService.GreetPerson + * GreetPerson greets a person + * @param person {main.Person} + * @returns {Promise} + **/ + GreetPerson: function (person) { + wails.CallByID(4021313248, ...Array.prototype.slice.call(arguments, 0)); + }, + }, +}; +``` + +Bound methods are obfuscated by default, and are identified using uint32 IDs, +calculated using the +[FNV hashing algorithm](https://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function). +This is to prevent the method name from being exposed in production builds. In +debug mode, the method IDs are logged along with the calculated ID of the method +to aid in debugging. If you wish to add an extra layer of obfuscation, you can +use the `BindAliases` option. This allows you to specify a map of alias IDs to +method IDs. When the frontend calls a method using an ID, the method ID will be +looked up in the alias map first for a match. If it does not find it, it assumes +it's a standard method ID and tries to find the method in the usual way. + +Example: + +```go + app := application.New(application.Options{ + Bind: []any{ + &GreetService{}, + }, + BindAliases: map[uint32]uint32{ + 1: 1411160069, + 2: 4021313248, + }, + Assets: application.AssetOptions{ + FS: assets, + }, + Mac: application.MacOptions{ + ApplicationShouldTerminateAfterLastWindowClosed: true, + }, + }) +``` + +We can now call using this alias in the frontend: `wails.Call(1, "world!")`. + +### Insecure calls + +If you don't mind your calls being available in plain text in your binary and +have no intention of using [garble](https://github.com/burrowers/garble), then +you can use the insecure `wails.CallByName()` method. This method takes the +fully qualified name of the method to call and the arguments to pass to it. +Example: + + ```go + wails.CallByName("main.GreetService.Greet", "world!") + ``` + +!!! danger + + This is only provided as a convenience method for development. It is not recommended to use this in production. + +## Dialogs + +Dialogs are now available in JavaScript! + +### Windows + +Dialog buttons in Windows are not configurable and are constant depending on the +type of dialog. To trigger a callback when a button is pressed, create a button +with the same name as the button you wish to have the callback attached to. +Example: Create a button with the label `Ok` and use `OnClick()` to set the +callback method: + +```go + dialog := app.QuestionDialog(). + SetTitle("Update"). + SetMessage("The cancel button is selected when pressing escape") + ok := dialog.AddButton("Ok") + ok.OnClick(func() { + // Do something + }) + no := dialog.AddButton("Cancel") + dialog.SetDefaultButton(ok) + dialog.SetCancelButton(no) + dialog.Show() +``` + +## Drag and Drop + +Native drag and drop can be enabled per-window. Simply set the +`EnableDragAndDrop` window config option to `true` and the window will allow +files to be dragged onto it. When this happens, the `events.FilesDropped` event +will be emitted. The filenames can then be retrieved from the +`WindowEvent.Context()` using the `DroppedFiles()` method. This returns a slice +of strings containing the filenames. + +## Context Menus + +Context menus are contextual menus that are shown when the user right-clicks on +an element. Creating a context menu is the same as creating a standard menu , by +using `app.NewMenu()`. To make the context menu available to a window, call +`window.RegisterContextMenu(name, menu)`. The name will be the id of the context +menu and used by the frontend. + +To indicate that an element has a context menu, add the `data-contextmenu` +attribute to the element. The value of this attribute should be the name of a +context menu previously registered with the window. + +It is possible to register a context menu at the application level, making it +available to all windows. This can be done using +`app.RegisterContextMenu(name, menu)`. If a context menu cannot be found at the +window level, the application context menus will be checked. A demo of this can +be found in `v3/examples/contextmenus`. + +## Wails Markup Language (WML) + +The Wails Markup Language is a simple markup language that allows you to add +functionality to standard HTML elements without the use of Javascript. + +The following tags are currently supported: + +### `data-wml-event` + +This specifies that a Wails event will be emitted when the element is clicked. +The value of the attribute should be the name of the event to emit. + +Example: + +```html + +``` + +Sometimes you need the user to confirm an action. This can be done by adding the +`data-wml-confirm` attribute to the element. The value of this attribute will be +the message to display to the user. + +Example: + +```html + +``` + +### `data-wml-window` + +Any `wails.window` method can be called by adding the `data-wml-window` +attribute to an element. The value of the attribute should be the name of the +method to call. The method name should be in the same case as the method. + +```html + +``` + +### `data-wml-trigger` + +This attribute specifies which javascript event should trigger the action. The +default is `click`. + +```html + +``` + +## Systray + +Wails 3 comes with a built-in systray. This is a fully featured systray that has +been designed to be as simple as possible to use. It is possible to set the +icon, tooltip and menu of the systray. It is possible to also "attach" a window +to the systray. Doing this will provide the following functionality: + +- Clicking the systray icon with toggle the window visibility +- Right-clicking the systray will open the menu, if there is one + +On macOS, if there is no attached window, the systray will use the default +method of displaying the menu (any button). If there is an attached window but +no menu, the systray will toggle the window regardless of the button pressed. + +## Plugins + +Plugins are a way to extend the functionality of your Wails application. + +### Creating a plugin + +Plugins are standard Go structure that adhere to the following interface: + +```go +type Plugin interface { + Name() string + Init(*application.App) error + Shutdown() + CallableByJS() []string + InjectJS() string +} +``` + +The `Name()` method returns the name of the plugin. This is used for logging +purposes. + +The `Init(*application.App) error` method is called when the plugin is loaded. +The `*application.App` parameter is the application that the plugin is being +loaded into. Any errors will prevent the application from starting. + +The `Shutdown()` method is called when the application is shutting down. + +The `CallableByJS()` method returns a list of exported functions that can be +called from the frontend. These method names must exactly match the names of the +methods exported by the plugin. + +The `InjectJS()` method returns JavaScript that should be injected into all +windows as they are created. This is useful for adding custom JavaScript +functions that complement the plugin. + +### Tips + +#### Enums + +In Go, enums are often defined as a type and a set of constants. For example: + +```go +type MyEnum int + +const ( + MyEnumOne MyEnum = iota + MyEnumTwo + MyEnumThree +) +``` + +Due to incompatibility between Go and JavaScript, custom types cannot be used in +this way. The best strategy is to use a type alias for float64: + +```go +type MyEnum = float64 + +const ( + MyEnumOne MyEnum = iota + MyEnumTwo + MyEnumThree +) +``` + +In Javascript, you can then use the following: + +```js +const MyEnum = { + MyEnumOne: 0, + MyEnumTwo: 1, + MyEnumThree: 2, +}; +``` + +- Why use `float64`? Can't we use `int`? + - Because JavaScript doesn't have a concept of `int`. Everything is a + `number`, which translates to `float64` in Go. There are also restrictions + on casting types in Go's reflection package, which means using `int` doesn't + work. + +### BackgroundColour + +In v2, this was a pointer to an `RGBA` struct. In v3, this is an `RGBA` struct +value. + +### WindowIsTranslucent + +This flag has been removed. Now there is a `BackgroundType` flag that can be +used to set the type of background the window should have. This flag can be set +to any of the following values: + +- `BackgroundTypeSolid` - The window will have a solid background +- `BackgroundTypeTransparent` - The window will have a transparent background +- `BackgroundTypeTranslucent` - The window will have a translucent background + +On Windows, if the `BackgroundType` is set to `BackgroundTypeTranslucent`, the +type of translucency can be set using the `BackdropType` flag in the +`WindowsWindow` options. This can be set to any of the following values: + +- `Auto` - The window will use an effect determined by the system +- `None` - The window will have no background +- `Mica` - The window will use the Mica effect +- `Acrylic` - The window will use the acrylic effect +- `Tabbed` - The window will use the tabbed effect + +## Windows Application Options + +### WndProcInterceptor + +If this is set, the WndProc will be intercepted and the function will be called. +This allows you to handle Windows messages directly. The function should have +the following signature: + +```go +func(hwnd uintptr, msg uint32, wParam, lParam uintptr) (returnValue uintptr, shouldReturn) +``` + +The `shouldReturn` value should be set to `true` if the returnValue should be +returned by the main wndProc method. If it is set to `false`, the return value +will be ignored and the message will continue to be processed by the main +wndProc method. + +## Hide Window on Close + OnBeforeClose + +In v2, there was the `HideWindowOnClose` flag to hide the window when it closed. +There was a logical overlap between this flag and the `OnBeforeClose` callback. +In v3, the `HideWindowOnClose` flag has been removed and the `OnBeforeClose` +callback has been renamed to `ShouldClose`. The `ShouldClose` callback is called +when the user attempts to close a window. If the callback returns `true`, the +window will close. If it returns `false`, the window will not close. This can be +used to hide the window instead of closing it. + +## Window Drag + +In v2, the `--wails-drag` attribute was used to indicate that an element could +be used to drag the window. In v3, this has been replaced with +`--webkit-app-region` to be more in line with the way other frameworks handle +this. The `--webkit-app-region` attribute can be set to any of the following +values: + +- `drag` - The element can be used to drag the window +- `no-drag` - The element cannot be used to drag the window + +We would have ideally liked to use `app-region`, however this is not supported +by the `getComputedStyle` call on webkit on macOS. diff --git a/mkdocs-website/docs/en/development/introduction.md b/mkdocs-website/docs/en/development/introduction.md new file mode 100644 index 000000000..72cd12943 --- /dev/null +++ b/mkdocs-website/docs/en/development/introduction.md @@ -0,0 +1,209 @@ +# Introduction + +!!! note This guide is a work in progress. + +Thanks for wanting to help out with development of Wails! This guide will help +you get started. + +## Getting Started + +- Git clone this repository. Checkout the `v3-alpha` branch. +- Install the CLI: `cd v3/cmd/wails3 && go install` + +- Optional: If you are wanting to use the build system to build frontend code, + you will need to install [npm](https://nodejs.org/en/download). + +## Building + +For simple programs, you can use the standard `go build` command. It's also +possible to use `go run`. + +Wails also comes with a build system that can be used to build more complex +projects. It utilises the awesome [Task](https://taskfile.dev) build system. For +more information, check out the task homepage or run `wails task --help`. + +## Project layout + +The project has the following structure: + + ``` + v3 + ├── cmd/wails3 // CLI + ├── examples // Examples of Wails apps + ├── internal // Internal packages + | ├── runtime // The Wails JS runtime + | └── templates // The supported project templates + ├── pkg + | ├── application // The core Wails library + | └── events // The event definitions + | └── mac // macOS specific code used by plugins + | └── w32 // Windows specific code + ├── plugins // Supported plugins + ├── tasks // General tasks + └── Taskfile.yaml // Development tasks configuration + ``` + +## Development + +### Adding window functionality + +The preferred way to add window functionality is to add a new function to the +`pkg/application/webview_window.go` file. This should implement all the +functionality required for all platforms. Any platform specific code should be +called via a `webviewWindowImpl` interface method. This interface is implemented +by each of the target platforms to provide the platform specific functionality. +In some cases, this may do nothing. Once you've added the interface method, +ensure each platform implements it. A good example of this is the `SetMinSize` +method. + +- Mac: `webview_window_darwin.go` +- Windows: `webview_window_windows.go` +- Linux: `webview_window_linux.go` + +Most, if not all, of the platform specific code should be run on the main +thread. To simplify this, there are a number of `invokeSync` methods defined in +`application.go`. + +### Updating the runtime + +The runtime is located in `v3/internal/runtime`. When the runtime is updated, +the following steps need to be taken: + +```shell +wails3 task runtime:build +``` + +### Events + +Events are defined in `v3/pkg/events`. When adding a new event, the following +steps need to be taken: + +- Add the event to the `events.txt` file +- Run `wails3 task events:generate` + +There are a number of types of events: platform specific application and window +events + common events. The common events are useful for cross-platform event +handling, but you aren't limited to the "lowest common denominator". You can use +the platform specific events if you need to. + +When adding a common event, ensure that the platform specific events are mapped. +An example of this is in `window_webview_darwin.go`: + +```go + // Translate ShouldClose to common WindowClosing event + w.parent.On(events.Mac.WindowShouldClose, func(_ *WindowEventContext) { + w.parent.emit(events.Common.WindowClosing) + }) +``` + +NOTE: We may try to automate this in the future by adding the mapping to the +event definition. + +### Plugins + +Plugins are a way to extend the functionality of your Wails application. + +#### Creating a plugin + +Plugins are standard Go structure that adhere to the following interface: + +```go +type Plugin interface { + Name() string + Init(*application.App) error + Shutdown() + CallableByJS() []string + InjectJS() string +} +``` + +The `Name()` method returns the name of the plugin. This is used for logging +purposes. + +The `Init(*application.App) error` method is called when the plugin is loaded. +The `*application.App` parameter is the application that the plugin is being +loaded into. Any errors will prevent the application from starting. + +The `Shutdown()` method is called when the application is shutting down. + +The `CallableByJS()` method returns a list of exported functions that can be +called from the frontend. These method names must exactly match the names of the +methods exported by the plugin. + +The `InjectJS()` method returns JavaScript that should be injected into all +windows as they are created. This is useful for adding custom JavaScript +functions that complement the plugin. + +The built-in plugins can be found in the `v3/plugins` directory. +Check them out for inspiration. + +## Tasks + +The Wails CLI uses the [Task](https://taskfile.dev) build system. It is imported +as a library and used to run the tasks defined in `Taskfile.yaml`. The main +interfacing with Task happens in `v3/internal/commands/task.go`. + +### Upgrading Taskfile + +To check if there's an upgrade for Taskfile, run `wails3 task -version` and +check against the Task website. + +To upgrade the version of Taskfile used, run: + +```shell +wails3 task taskfile:upgrade +``` + +If there are incompatibilities then they should appear in the +`v3/internal/commands/task.go` file. + +Usually the best way to fix incompatibilities is to clone the task repo at +`https://github.com/go-task/task` and look at the git history to determine what +has changed and why. + +To check all changes have worked correctly, re-install the CLI and check the +version again: + +```shell +wails3 task cli:install +wails3 task -version +``` + +## Opening a PR + +Make sure that all PRs have a ticket associated with them providing context to +the change. If there is no ticket, please create one first. Ensure that all PRs +have updated the CHANGELOG.md file with the changes made. The CHANGELOG.md file +is located in the `mkdocs-website/docs` directory. + +## Misc Tasks + +### Upgrading Taskfile + +The Wails CLI uses the [Task](https://taskfile.dev) build system. It is imported +as a library and used to run the tasks defined in `Taskfile.yaml`. The main +interfacing with Task happens in `v3/internal/commands/task.go`. + +To check if there's an upgrade for Taskfile, run `wails3 task -version` and +check against the Task website. + +To upgrade the version of Taskfile used, run: + +```shell +wails3 task taskfile:upgrade +``` + +If there are incompatibilities then they should appear in the +`v3/internal/commands/task.go` file. + +Usually the best way to fix incompatibilities is to clone the task repo at +`https://github.com/go-task/task` and look at the git history to determine what +has changed and why. + +To check all changes have worked correctly, re-install the CLI and check the +version again: + +```shell +wails3 task cli:install +wails3 task -version +``` diff --git a/mkdocs-website/docs/en/development/status.md b/mkdocs-website/docs/en/development/status.md new file mode 100644 index 000000000..3d6a9e440 --- /dev/null +++ b/mkdocs-website/docs/en/development/status.md @@ -0,0 +1,388 @@ +# Status + +Status of features in v3. + +!!! note + + This list is a mixture of public and internal API support.
+ It is not complete and probably not up to date. + +## Known Issues + +- Linux is not yet up to feature parity with Windows/Mac + +## Application + +Application interface methods + +| Method | Windows | Linux | Mac | Notes | +|---------------------------------------------------------------|---------|-------|-----|-------| +| run() error | Y | Y | Y | | +| destroy() | | Y | Y | | +| setApplicationMenu(menu \*Menu) | Y | Y | Y | | +| name() string | | Y | Y | | +| getCurrentWindowID() uint | Y | Y | Y | | +| showAboutDialog(name string, description string, icon []byte) | | Y | Y | | +| setIcon(icon []byte) | - | Y | Y | | +| on(id uint) | | | Y | | +| dispatchOnMainThread(fn func()) | Y | Y | Y | | +| hide() | Y | Y | Y | | +| show() | Y | Y | Y | | +| getPrimaryScreen() (\*Screen, error) | | Y | Y | | +| getScreens() ([]\*Screen, error) | | Y | Y | | + +## Webview Window + +Webview Window Interface Methods + +| Method | Windows | Linux | Mac | Notes | +|----------------------------------------------------|---------|-------|-----|------------------------------------------| +| center() | Y | Y | Y | | +| close() | y | Y | Y | | +| destroy() | | Y | Y | | +| execJS(js string) | y | Y | Y | | +| focus() | Y | Y | | | +| forceReload() | | Y | Y | | +| fullscreen() | Y | Y | Y | | +| getScreen() (\*Screen, error) | y | Y | Y | | +| getZoom() float64 | | Y | Y | | +| height() int | Y | Y | Y | | +| hide() | Y | Y | Y | | +| isFullscreen() bool | Y | Y | Y | | +| isMaximised() bool | Y | Y | Y | | +| isMinimised() bool | Y | Y | Y | | +| maximise() | Y | Y | Y | | +| minimise() | Y | Y | Y | | +| nativeWindowHandle() (uintptr, error) | Y | Y | | | +| on(eventID uint) | y | | Y | | +| openContextMenu(menu *Menu, data *ContextMenuData) | y | | Y | | +| relativePosition() (int, int) | Y | Y | Y | | +| reload() | y | Y | Y | | +| run() | Y | Y | Y | | +| setAlwaysOnTop(alwaysOnTop bool) | Y | Y | Y | | +| setBackgroundColour(color RGBA) | Y | Y | Y | | +| setEnabled(bool) | | Y | Y | | +| setFrameless(bool) | | Y | Y | | +| setFullscreenButtonEnabled(enabled bool) | - | Y | Y | There is no fullscreen button in Windows | +| setHTML(html string) | Y | Y | Y | | +| setMaxSize(width, height int) | Y | Y | Y | | +| setMinSize(width, height int) | Y | Y | Y | | +| setRelativePosition(x int, y int) | Y | Y | Y | | +| setResizable(resizable bool) | Y | Y | Y | | +| setSize(width, height int) | Y | Y | Y | | +| setTitle(title string) | Y | Y | Y | | +| setURL(url string) | Y | Y | Y | | +| setZoom(zoom float64) | Y | Y | Y | | +| show() | Y | Y | Y | | +| size() (int, int) | Y | Y | Y | | +| toggleDevTools() | Y | Y | Y | | +| unfullscreen() | Y | Y | Y | | +| unmaximise() | Y | Y | Y | | +| unminimise() | Y | Y | Y | | +| width() int | Y | Y | Y | | +| zoom() | | Y | Y | | +| zoomIn() | Y | Y | Y | | +| zoomOut() | Y | Y | Y | | +| zoomReset() | Y | Y | Y | | + +## Runtime + +### Application + +| Feature | Windows | Linux | Mac | Notes | +|---------|---------|-------|-----|-------| +| Quit | Y | Y | Y | | +| Hide | Y | | Y | | +| Show | Y | | Y | | + +### Dialogs + +| Feature | Windows | Linux | Mac | Notes | +|----------|---------|-------|-----|-------| +| Info | Y | Y | Y | | +| Warning | Y | Y | Y | | +| Error | Y | Y | Y | | +| Question | Y | Y | Y | | +| OpenFile | Y | | Y | | +| SaveFile | Y | | Y | | + +### Clipboard + +| Feature | Windows | Linux | Mac | Notes | +|---------|---------|-------|-----|-------| +| SetText | Y | | Y | | +| Text | Y | | Y | | + +### ContextMenu + +| Feature | Windows | Linux | Mac | Notes | +|------------------|---------|-------|-----|-------| +| OpenContextMenu | Y | | Y | | +| On By Default | | | | | +| Control via HTML | Y | | | | + +The default context menu is enabled by default for all elements that are +`contentEditable: true`, `` or ` +

content editable

+ + +
+

Default Context Menu hidden here

+

Context Menu hidden here even if text is selected

+ +
+
+

Nested section reverted to auto (smart) default Context Menu

+

Context menu shown here only if you select text

+
+
+
+ + \ No newline at end of file diff --git a/v3/examples/contextmenus/main.go b/v3/examples/contextmenus/main.go new file mode 100644 index 000000000..4cb7977d0 --- /dev/null +++ b/v3/examples/contextmenus/main.go @@ -0,0 +1,59 @@ +package main + +import ( + "embed" + _ "embed" + "log" + + "github.com/wailsapp/wails/v3/pkg/application" +) + +//go:embed assets +var assets embed.FS + +func main() { + + app := application.New(application.Options{ + Name: "Context Menu Demo", + Description: "A demo of the Context Menu API", + Assets: application.AssetOptions{ + FS: assets, + }, + Mac: application.MacOptions{ + ApplicationShouldTerminateAfterLastWindowClosed: true, + }, + }) + + mainWindow := app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{ + Title: "Context Menu Demo", + Width: 1024, + Height: 1024, + Mac: application.MacWindow{ + Backdrop: application.MacBackdropTranslucent, + TitleBar: application.MacTitleBarHiddenInsetUnified, + InvisibleTitleBarHeight: 50, + }, + }) + + contextMenu := app.NewMenu() + contextMenu.Add("Click Me").OnClick(func(data *application.Context) { + app.Logger.Info("Context menu", "context data", data.ContextMenuData()) + }) + + globalContextMenu := app.NewMenu() + globalContextMenu.Add("Default context menu item").OnClick(func(data *application.Context) { + app.Logger.Info("Context menu", "context data", data.ContextMenuData()) + }) + + // Registering the menu with a window will make it available to that window only + mainWindow.RegisterContextMenu("test", contextMenu) + + // Registering the menu with the app will make it available to all windows + app.RegisterContextMenu("test", globalContextMenu) + + err := app.Run() + + if err != nil { + log.Fatal(err.Error()) + } +} diff --git a/v3/examples/dev/.gitignore b/v3/examples/dev/.gitignore new file mode 100644 index 000000000..c2a88322f --- /dev/null +++ b/v3/examples/dev/.gitignore @@ -0,0 +1 @@ +.task \ No newline at end of file diff --git a/v3/examples/dev/README.md b/v3/examples/dev/README.md new file mode 100644 index 000000000..5dcc421f7 --- /dev/null +++ b/v3/examples/dev/README.md @@ -0,0 +1,4 @@ +# Dev Example + +**NOTE**: This example is currently a work in progress. It is not yet ready for use. + diff --git a/v3/examples/dev/Taskfile.yml b/v3/examples/dev/Taskfile.yml new file mode 100644 index 000000000..9886a67cc --- /dev/null +++ b/v3/examples/dev/Taskfile.yml @@ -0,0 +1,183 @@ +version: '3' + +vars: + APP_NAME: "dev{{exeExt}}" + +tasks: + + pre-build: + summary: Pre-build hooks + + post-build: + summary: Post-build hooks + + install-frontend-deps: + summary: Install frontend dependencies + dir: frontend + sources: + - package.json + - package-lock.json + generates: + - node_modules/* + preconditions: + - sh: npm version + msg: "Looks like npm isn't installed. Npm is part of the Node installer: https://nodejs.org/en/download/" + cmds: + - npm install + + build-frontend: + summary: Build the frontend project + dir: frontend + sources: + - src/* + generates: + - dist/* + deps: + - install-frontend-deps + cmds: + - npm run build + + build:darwin: + summary: Builds the application + platforms: + - darwin + cmds: + - task: pre-build + - task: build-frontend + - go build -gcflags=all="-N -l" -o bin/{{.APP_NAME}} + - task: post-build + env: + CGO_CFLAGS: "-mmacosx-version-min=10.13" + CGO_LDFLAGS: "-mmacosx-version-min=10.13" + MACOSX_DEPLOYMENT_TARGET: "10.13" + + build:backend:darwin: + summary: Builds the application + platforms: + - darwin + cmds: + - task: pre-build + - go build -gcflags=all="-N -l" -o bin/{{.APP_NAME}} + - task: post-build + env: + CGO_CFLAGS: "-mmacosx-version-min=10.13" + CGO_LDFLAGS: "-mmacosx-version-min=10.13" + MACOSX_DEPLOYMENT_TARGET: "10.13" + + build:windows: + summary: Builds the application for Windows + platforms: + - windows + cmds: + - task: pre-build + - task: build-frontend + - go build -gcflags=all="-N -l" -o bin/{{.APP_NAME}} + - task: post-build + + + build:backend:windows: + summary: Builds the backend application for Windows + platforms: + - windows + cmds: + - task: pre-build + - go build -gcflags=all="-N -l" -o bin/{{.APP_NAME}} + - task: post-build + + build: + summary: Builds the application + watch: true + sources: + - main.go + cmds: + - task: build:darwin + - task: build:windows + - task: run + + build:backend: + summary: Builds the backend application + cmds: + - task: build:backend:darwin + - task: build:backend:windows + + generate-icons: + summary: Generates Windows `.ico` and Mac `.icns` files from an image + dir: build + cmds: + # Generates both .ico and .icns files + - wails generate icons -input appicon.png + + build-app-prod-darwin: + summary: Creates a production build of the application + cmds: + - task: pre-build + - task: build-frontend + - GOOS=darwin GOARCH={{.ARCH}} go build -tags production -ldflags="-w -s" -o build/bin/{{.APP_NAME}} + - task: post-build + env: + CGO_CFLAGS: "-mmacosx-version-min=10.13" + CGO_LDFLAGS: "-mmacosx-version-min=10.13" + MACOSX_DEPLOYMENT_TARGET: "10.13" + vars: + ARCH: $GOARCH + + frontend:dev: + summary: Runs the frontend in development mode + deps: + - task: install-frontend-deps + dir: frontend + cmds: + - npm run dev + + run: + summary: Runs the application + cmds: + - ./bin/{{.APP_NAME}} + + dev: + summary: Runs the application in development mode + watch: true + preconditions: + - sh: 'wails3 tool checkport -p 5173' + msg: "Vite does not appear to be running. Please run `wails3 task frontend:dev` in another terminal." + cmds: + - task: build:backend + + create-app-bundle: + summary: Builds a `.app` bundle + cmds: + - mkdir -p {{.APP_NAME}}.app/Contents/{MacOS,Resources} + - cp build/icons.icns {{.APP_NAME}}.app/Contents/Resources + - cp build/bin/{{.APP_NAME}} {{.APP_NAME}}.app/Contents/MacOS + - cp build/Info.plist {{.APP_NAME}}.app/Contents + + package-darwin-arm64: + summary: Packages a production build of the application into a `.app` bundle + platform: darwin + deps: + - task: build-app-prod-darwin + vars: + ARCH: arm64 + - generate-icons + cmds: + - task: create-app-bundle + + generate:syso: + dir: build + platform: windows + cmds: + - wails generate syso -arch {{.ARCH}} -icon icon.ico -manifest wails.exe.manifest -info info.json -out ../wails.syso + vars: + ARCH: $GOARCH + + package:windows: + summary: Packages a production build of the application into a `.exe` bundle + platform: windows + deps: + - generate-icons + cmds: + - task: generate:syso + vars: + ARCH: amd64 + - go build -tags production -ldflags="-w -s -H windowsgui" -o bin/{{.APP_NAME}}.exe + - powershell Remove-item wails.syso diff --git a/v3/examples/dev/build/Info.dev.plist b/v3/examples/dev/build/Info.dev.plist new file mode 100644 index 000000000..0c0ed6032 --- /dev/null +++ b/v3/examples/dev/build/Info.dev.plist @@ -0,0 +1,32 @@ + + + + CFBundlePackageType + APPL + CFBundleName + My Product Name + CFBundleExecutable + dev + CFBundleIdentifier + com.wails.dev + CFBundleVersion + v1.0.0 + CFBundleGetInfoString + This is a comment + CFBundleShortVersionString + v1.0.0 + CFBundleIconFile + icons + LSMinimumSystemVersion + 10.13.0 + NSHighResolutionCapable + true + NSHumanReadableCopyright + (c) 2023 My Company Name + NSAppTransportSecurity + + NSAllowsLocalNetworking + + + + \ No newline at end of file diff --git a/v3/examples/dev/build/Info.plist b/v3/examples/dev/build/Info.plist new file mode 100644 index 000000000..f9ea24900 --- /dev/null +++ b/v3/examples/dev/build/Info.plist @@ -0,0 +1,27 @@ + + + + CFBundlePackageType + APPL + CFBundleName + My Product Name + CFBundleExecutable + dev + CFBundleIdentifier + com.wails.dev + CFBundleVersion + v1.0.0 + CFBundleGetInfoString + This is a comment + CFBundleShortVersionString + v1.0.0 + CFBundleIconFile + icons + LSMinimumSystemVersion + 10.13.0 + NSHighResolutionCapable + true + NSHumanReadableCopyright + (c) 2023 My Company Name + + \ No newline at end of file diff --git a/v3/examples/dev/build/appicon.png b/v3/examples/dev/build/appicon.png new file mode 100644 index 000000000..63617fe4f Binary files /dev/null and b/v3/examples/dev/build/appicon.png differ diff --git a/v3/examples/dev/build/icons.icns b/v3/examples/dev/build/icons.icns new file mode 100644 index 000000000..1b5bd4c86 Binary files /dev/null and b/v3/examples/dev/build/icons.icns differ diff --git a/v3/examples/dev/frontend/dist/assets/index-3635012e.css b/v3/examples/dev/frontend/dist/assets/index-3635012e.css new file mode 100644 index 000000000..14cfb027f --- /dev/null +++ b/v3/examples/dev/frontend/dist/assets/index-3635012e.css @@ -0,0 +1 @@ +:root{font-family:Inter,Avenir,Helvetica,Arial,sans-serif;font-size:16px;line-height:24px;font-weight:400;color-scheme:light dark;color:#ffffffde;background-color:#242424;font-synthesis:none;text-rendering:optimizeLegibility;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;-webkit-text-size-adjust:100%}a{font-weight:500;color:#646cff;text-decoration:inherit}a:hover{color:#535bf2}body{margin:0;display:flex;place-items:center;min-width:320px;min-height:100vh}h1{font-size:3.2em;line-height:1.1}.card{padding:2em}#app{max-width:1280px;margin:0 auto;padding:2rem;text-align:center}button{border-radius:8px;border:1px solid transparent;padding:.6em 1.2em;font-size:1em;font-weight:500;font-family:inherit;background-color:#1a1a1a;cursor:pointer;transition:border-color .25s}button:hover{border-color:#646cff}button:focus,button:focus-visible{outline:4px auto -webkit-focus-ring-color}@media (prefers-color-scheme: light){:root{color:#213547;background-color:#fff}a:hover{color:#747bff}button{background-color:#f9f9f9}}.logo.svelte-c9fbf7{height:6em;padding:1.5em;will-change:filter}.logo.svelte-c9fbf7:hover{filter:drop-shadow(0 0 2em #646cffaa)}.logo.svelte.svelte-c9fbf7:hover{filter:drop-shadow(0 0 2em #ff3e00aa)}.read-the-docs.svelte-c9fbf7{color:#888} diff --git a/v3/examples/dev/frontend/dist/assets/index-9076c63b.js b/v3/examples/dev/frontend/dist/assets/index-9076c63b.js new file mode 100644 index 000000000..e2b665b28 --- /dev/null +++ b/v3/examples/dev/frontend/dist/assets/index-9076c63b.js @@ -0,0 +1 @@ +(function(){const t=document.createElement("link").relList;if(t&&t.supports&&t.supports("modulepreload"))return;for(const o of document.querySelectorAll('link[rel="modulepreload"]'))r(o);new MutationObserver(o=>{for(const s of o)if(s.type==="childList")for(const i of s.addedNodes)i.tagName==="LINK"&&i.rel==="modulepreload"&&r(i)}).observe(document,{childList:!0,subtree:!0});function n(o){const s={};return o.integrity&&(s.integrity=o.integrity),o.referrerPolicy&&(s.referrerPolicy=o.referrerPolicy),o.crossOrigin==="use-credentials"?s.credentials="include":o.crossOrigin==="anonymous"?s.credentials="omit":s.credentials="same-origin",s}function r(o){if(o.ep)return;o.ep=!0;const s=n(o);fetch(o.href,s)}})();function v(){}function I(e){return e()}function W(){return Object.create(null)}function A(e){e.forEach(I)}function K(e){return typeof e=="function"}function F(e,t){return e!=e?t==t:e!==t||e&&typeof e=="object"||typeof e=="function"}let S;function X(e,t){return S||(S=document.createElement("a")),S.href=t,e===S.href}function Y(e){return Object.keys(e).length===0}function c(e,t){e.appendChild(t)}function V(e,t,n){e.insertBefore(t,n||null)}function M(e){e.parentNode&&e.parentNode.removeChild(e)}function a(e){return document.createElement(e)}function k(e){return document.createTextNode(e)}function w(){return k(" ")}function Z(e,t,n,r){return e.addEventListener(t,n,r),()=>e.removeEventListener(t,n,r)}function u(e,t,n){n==null?e.removeAttribute(t):e.getAttribute(t)!==n&&e.setAttribute(t,n)}function ee(e){return Array.from(e.childNodes)}function te(e,t){t=""+t,e.data!==t&&(e.data=t)}let q;function E(e){q=e}const $=[],B=[];let y=[];const H=[],ne=Promise.resolve();let j=!1;function re(){j||(j=!0,ne.then(z))}function P(e){y.push(e)}const N=new Set;let g=0;function z(){if(g!==0)return;const e=q;do{try{for(;g<$.length;){const t=$[g];g++,E(t),oe(t.$$)}}catch(t){throw $.length=0,g=0,t}for(E(null),$.length=0,g=0;B.length;)B.pop()();for(let t=0;te.indexOf(r)===-1?t.push(r):n.push(r)),n.forEach(r=>r()),y=t}const C=new Set;let ie;function D(e,t){e&&e.i&&(C.delete(e),e.i(t))}function le(e,t,n,r){if(e&&e.o){if(C.has(e))return;C.add(e),ie.c.push(()=>{C.delete(e),r&&(n&&e.d(1),r())}),e.o(t)}else r&&r()}function ce(e){e&&e.c()}function G(e,t,n,r){const{fragment:o,after_update:s}=e.$$;o&&o.m(t,n),r||P(()=>{const i=e.$$.on_mount.map(I).filter(K);e.$$.on_destroy?e.$$.on_destroy.push(...i):A(i),e.$$.on_mount=[]}),s.forEach(P)}function J(e,t){const n=e.$$;n.fragment!==null&&(se(n.after_update),A(n.on_destroy),n.fragment&&n.fragment.d(t),n.on_destroy=n.fragment=null,n.ctx=[])}function fe(e,t){e.$$.dirty[0]===-1&&($.push(e),re(),e.$$.dirty.fill(0)),e.$$.dirty[t/31|0]|=1<{const _=x.length?x[0]:d;return l.ctx&&o(l.ctx[f],l.ctx[f]=_)&&(!l.skip_bound&&l.bound[f]&&l.bound[f](_),b&&fe(e,f)),d}):[],l.update(),b=!0,A(l.before_update),l.fragment=r?r(l.ctx):!1,t.target){if(t.hydrate){const f=ee(t.target);l.fragment&&l.fragment.l(f),f.forEach(M)}else l.fragment&&l.fragment.c();t.intro&&D(e.$$.fragment),G(e,t.target,t.anchor,t.customElement),z()}E(p)}class R{$destroy(){J(this,1),this.$destroy=v}$on(t,n){if(!K(n))return v;const r=this.$$.callbacks[t]||(this.$$.callbacks[t]=[]);return r.push(n),()=>{const o=r.indexOf(n);o!==-1&&r.splice(o,1)}}$set(t){this.$$set&&!Y(t)&&(this.$$.skip_bound=!0,this.$$set(t),this.$$.skip_bound=!1)}}const ue="/assets/svelte-a39f39b7.svg";function ae(e){let t,n,r,o,s;return{c(){t=a("button"),n=k("count is "),r=k(e[0])},m(i,h){V(i,t,h),c(t,n),c(t,r),o||(s=Z(t,"click",e[1]),o=!0)},p(i,[h]){h&1&&te(r,i[0])},i:v,o:v,d(i){i&&M(t),o=!1,s()}}}function de(e,t,n){let r=0;return[r,()=>{n(0,r+=1)}]}class he extends R{constructor(t){super(),Q(this,t,de,ae,F,{})}}function pe(e){let t,n,r,o,s,i,h,p,l,b,f,d,x,_,T,L,O;return d=new he({}),{c(){t=a("main"),n=a("div"),r=a("a"),r.innerHTML='',o=w(),s=a("a"),i=a("img"),p=w(),l=a("h1"),l.textContent="Wails + Svelte",b=w(),f=a("div"),ce(d.$$.fragment),x=w(),_=a("p"),_.innerHTML='Check out SvelteKit, the official Svelte app framework powered by Vite!',T=w(),L=a("p"),L.textContent="Click on the Wails and Svelte logos to learn more",u(r,"href","https://wails.io"),u(r,"target","_blank"),u(r,"rel","noreferrer"),X(i.src,h=ue)||u(i,"src",h),u(i,"class","logo svelte svelte-c9fbf7"),u(i,"alt","Svelte Logo"),u(s,"href","https://svelte.dev"),u(s,"target","_blank"),u(s,"rel","noreferrer"),u(f,"class","card"),u(L,"class","read-the-docs svelte-c9fbf7")},m(m,U){V(m,t,U),c(t,n),c(n,r),c(n,o),c(n,s),c(s,i),c(t,p),c(t,l),c(t,b),c(t,f),G(d,f,null),c(t,x),c(t,_),c(t,T),c(t,L),O=!0},p:v,i(m){O||(D(d.$$.fragment,m),O=!0)},o(m){le(d.$$.fragment,m),O=!1},d(m){m&&M(t),J(d)}}}class me extends R{constructor(t){super(),Q(this,t,null,pe,F,{})}}new me({target:document.getElementById("app")}); diff --git a/v3/examples/dev/frontend/dist/assets/svelte-a39f39b7.svg b/v3/examples/dev/frontend/dist/assets/svelte-a39f39b7.svg new file mode 100644 index 000000000..c5e08481f --- /dev/null +++ b/v3/examples/dev/frontend/dist/assets/svelte-a39f39b7.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/v3/examples/dev/frontend/dist/index.html b/v3/examples/dev/frontend/dist/index.html new file mode 100644 index 000000000..c4380af7b --- /dev/null +++ b/v3/examples/dev/frontend/dist/index.html @@ -0,0 +1,15 @@ + + + + + + + Wails + Svelte + + + + +
+ + + diff --git a/v3/examples/dev/frontend/dist/wails.png b/v3/examples/dev/frontend/dist/wails.png new file mode 100644 index 000000000..8bdf42483 Binary files /dev/null and b/v3/examples/dev/frontend/dist/wails.png differ diff --git a/v3/examples/dev/frontend/index.html b/v3/examples/dev/frontend/index.html new file mode 100644 index 000000000..1ea50f904 --- /dev/null +++ b/v3/examples/dev/frontend/index.html @@ -0,0 +1,13 @@ + + + + + + + Wails + Svelte + + +
+ + + diff --git a/v3/examples/dev/frontend/jsconfig.json b/v3/examples/dev/frontend/jsconfig.json new file mode 100644 index 000000000..e596c5823 --- /dev/null +++ b/v3/examples/dev/frontend/jsconfig.json @@ -0,0 +1,33 @@ +{ + "compilerOptions": { + "moduleResolution": "Node", + "target": "ESNext", + "module": "ESNext", + /** + * svelte-preprocess cannot figure out whether you have + * a value or a type, so tell TypeScript to enforce using + * `import type` instead of `import` for Types. + */ + "importsNotUsedAsValues": "error", + "isolatedModules": true, + "resolveJsonModule": true, + /** + * To have warnings / errors of the Svelte compiler at the + * correct position, enable source maps by default. + */ + "sourceMap": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + /** + * Typecheck JS in `.svelte` and `.js` files by default. + * Disable this if you'd like to use dynamic types. + */ + "checkJs": true + }, + /** + * Use global.d.ts instead of compilerOptions.types + * to avoid limiting type declarations. + */ + "include": ["src/**/*.d.ts", "src/**/*.js", "src/**/*.svelte"] +} diff --git a/v3/examples/dev/frontend/package-lock.json b/v3/examples/dev/frontend/package-lock.json new file mode 100644 index 000000000..c4a1366a0 --- /dev/null +++ b/v3/examples/dev/frontend/package-lock.json @@ -0,0 +1,685 @@ +{ + "name": "frontend", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "frontend", + "version": "0.0.0", + "devDependencies": { + "@sveltejs/vite-plugin-svelte": "^2.0.0", + "svelte": "^3.54.0", + "vite": "^4.0.0" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.20.tgz", + "integrity": "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz", + "integrity": "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.18.20.tgz", + "integrity": "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz", + "integrity": "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz", + "integrity": "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz", + "integrity": "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz", + "integrity": "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz", + "integrity": "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz", + "integrity": "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz", + "integrity": "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz", + "integrity": "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz", + "integrity": "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz", + "integrity": "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz", + "integrity": "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz", + "integrity": "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz", + "integrity": "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz", + "integrity": "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz", + "integrity": "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz", + "integrity": "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz", + "integrity": "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz", + "integrity": "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz", + "integrity": "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", + "dev": true + }, + "node_modules/@sveltejs/vite-plugin-svelte": { + "version": "2.4.5", + "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-2.4.5.tgz", + "integrity": "sha512-UJKsFNwhzCVuiZd06jM/psscyNJNDwjQC+qIeb7GBJK9iWeQCcIyfcPWDvbCudfcJggY9jtxJeeaZH7uny93FQ==", + "dev": true, + "dependencies": { + "@sveltejs/vite-plugin-svelte-inspector": "^1.0.3", + "debug": "^4.3.4", + "deepmerge": "^4.3.1", + "kleur": "^4.1.5", + "magic-string": "^0.30.2", + "svelte-hmr": "^0.15.3", + "vitefu": "^0.2.4" + }, + "engines": { + "node": "^14.18.0 || >= 16" + }, + "peerDependencies": { + "svelte": "^3.54.0 || ^4.0.0", + "vite": "^4.0.0" + } + }, + "node_modules/@sveltejs/vite-plugin-svelte-inspector": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte-inspector/-/vite-plugin-svelte-inspector-1.0.3.tgz", + "integrity": "sha512-Khdl5jmmPN6SUsVuqSXatKpQTMIifoQPDanaxC84m9JxIibWvSABJyHpyys0Z+1yYrxY5TTEQm+6elh0XCMaOA==", + "dev": true, + "dependencies": { + "debug": "^4.3.4" + }, + "engines": { + "node": "^14.18.0 || >= 16" + }, + "peerDependencies": { + "@sveltejs/vite-plugin-svelte": "^2.2.0", + "svelte": "^3.54.0 || ^4.0.0", + "vite": "^4.0.0" + } + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/esbuild": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.20.tgz", + "integrity": "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/android-arm": "0.18.20", + "@esbuild/android-arm64": "0.18.20", + "@esbuild/android-x64": "0.18.20", + "@esbuild/darwin-arm64": "0.18.20", + "@esbuild/darwin-x64": "0.18.20", + "@esbuild/freebsd-arm64": "0.18.20", + "@esbuild/freebsd-x64": "0.18.20", + "@esbuild/linux-arm": "0.18.20", + "@esbuild/linux-arm64": "0.18.20", + "@esbuild/linux-ia32": "0.18.20", + "@esbuild/linux-loong64": "0.18.20", + "@esbuild/linux-mips64el": "0.18.20", + "@esbuild/linux-ppc64": "0.18.20", + "@esbuild/linux-riscv64": "0.18.20", + "@esbuild/linux-s390x": "0.18.20", + "@esbuild/linux-x64": "0.18.20", + "@esbuild/netbsd-x64": "0.18.20", + "@esbuild/openbsd-x64": "0.18.20", + "@esbuild/sunos-x64": "0.18.20", + "@esbuild/win32-arm64": "0.18.20", + "@esbuild/win32-ia32": "0.18.20", + "@esbuild/win32-x64": "0.18.20" + } + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/kleur": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", + "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/magic-string": { + "version": "0.30.2", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.2.tgz", + "integrity": "sha512-lNZdu7pewtq/ZvWUp9Wpf/x7WzMTsR26TWV03BRZrXFsv+BI6dy8RAiKgm1uM/kyR0rCfUcqvOlXKG66KhIGug==", + "dev": true, + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.4.15" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/nanoid": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz", + "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true + }, + "node_modules/postcss": { + "version": "8.4.28", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.28.tgz", + "integrity": "sha512-Z7V5j0cq8oEKyejIKfpD8b4eBy9cwW2JWPk0+fB1HOAMsfHbnAXLLS+PfVWlzMSLQaWttKDt607I0XHmpE67Vw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "nanoid": "^3.3.6", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/rollup": { + "version": "3.28.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.28.0.tgz", + "integrity": "sha512-d7zhvo1OUY2SXSM6pfNjgD5+d0Nz87CUp4mt8l/GgVP3oBsPwzNvSzyu1me6BSG9JIgWNTVcafIXBIyM8yQ3yw==", + "dev": true, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=14.18.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/source-map-js": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", + "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/svelte": { + "version": "3.59.2", + "resolved": "https://registry.npmjs.org/svelte/-/svelte-3.59.2.tgz", + "integrity": "sha512-vzSyuGr3eEoAtT/A6bmajosJZIUWySzY2CzB3w2pgPvnkUjGqlDnsNnA0PMO+mMAhuyMul6C2uuZzY6ELSkzyA==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/svelte-hmr": { + "version": "0.15.3", + "resolved": "https://registry.npmjs.org/svelte-hmr/-/svelte-hmr-0.15.3.tgz", + "integrity": "sha512-41snaPswvSf8TJUhlkoJBekRrABDXDMdpNpT2tfHIv4JuhgvHqLMhEPGtaQn0BmbNSTkuz2Ed20DF2eHw0SmBQ==", + "dev": true, + "engines": { + "node": "^12.20 || ^14.13.1 || >= 16" + }, + "peerDependencies": { + "svelte": "^3.19.0 || ^4.0.0" + } + }, + "node_modules/vite": { + "version": "4.4.9", + "resolved": "https://registry.npmjs.org/vite/-/vite-4.4.9.tgz", + "integrity": "sha512-2mbUn2LlUmNASWwSCNSJ/EG2HuSRTnVNaydp6vMCm5VIqJsjMfbIWtbH2kDuwUVW5mMUKKZvGPX/rqeqVvv1XA==", + "dev": true, + "dependencies": { + "esbuild": "^0.18.10", + "postcss": "^8.4.27", + "rollup": "^3.27.1" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + }, + "peerDependencies": { + "@types/node": ">= 14", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/vitefu": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/vitefu/-/vitefu-0.2.4.tgz", + "integrity": "sha512-fanAXjSaf9xXtOOeno8wZXIhgia+CZury481LsDaV++lSvcU2R9Ch2bPh3PYFyoHW+w9LqAeYRISVQjUIew14g==", + "dev": true, + "peerDependencies": { + "vite": "^3.0.0 || ^4.0.0" + }, + "peerDependenciesMeta": { + "vite": { + "optional": true + } + } + } + } +} diff --git a/v3/examples/dev/frontend/package.json b/v3/examples/dev/frontend/package.json new file mode 100644 index 000000000..2e166feea --- /dev/null +++ b/v3/examples/dev/frontend/package.json @@ -0,0 +1,16 @@ +{ + "name": "frontend", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview" + }, + "devDependencies": { + "@sveltejs/vite-plugin-svelte": "^2.0.0", + "svelte": "^3.54.0", + "vite": "^4.0.0" + } +} \ No newline at end of file diff --git a/v3/examples/dev/frontend/public/wails.png b/v3/examples/dev/frontend/public/wails.png new file mode 100644 index 000000000..8bdf42483 Binary files /dev/null and b/v3/examples/dev/frontend/public/wails.png differ diff --git a/v3/examples/dev/frontend/src/App.svelte b/v3/examples/dev/frontend/src/App.svelte new file mode 100644 index 000000000..539c395dd --- /dev/null +++ b/v3/examples/dev/frontend/src/App.svelte @@ -0,0 +1,45 @@ + + +
+ +

Wails + Svelte

+ +
+ +
+ +

+ Check out SvelteKit, the official Svelte app framework powered by Vite! +

+ +

+ Click on the Wails and Svelte logos to learn more +

+
+ + diff --git a/v3/examples/dev/frontend/src/app.css b/v3/examples/dev/frontend/src/app.css new file mode 100644 index 000000000..bcc7233dd --- /dev/null +++ b/v3/examples/dev/frontend/src/app.css @@ -0,0 +1,81 @@ +:root { + font-family: Inter, Avenir, Helvetica, Arial, sans-serif; + font-size: 16px; + line-height: 24px; + font-weight: 400; + + color-scheme: light dark; + color: rgba(255, 255, 255, 0.87); + background-color: #242424; + + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + -webkit-text-size-adjust: 100%; +} + +a { + font-weight: 500; + color: #646cff; + text-decoration: inherit; +} +a:hover { + color: #535bf2; +} + +body { + margin: 0; + display: flex; + place-items: center; + min-width: 320px; + min-height: 100vh; +} + +h1 { + font-size: 3.2em; + line-height: 1.1; +} + +.card { + padding: 2em; +} + +#app { + max-width: 1280px; + margin: 0 auto; + padding: 2rem; + text-align: center; +} + +button { + border-radius: 8px; + border: 1px solid transparent; + padding: 0.6em 1.2em; + font-size: 1em; + font-weight: 500; + font-family: inherit; + background-color: #1a1a1a; + cursor: pointer; + transition: border-color 0.25s; +} +button:hover { + border-color: #646cff; +} +button:focus, +button:focus-visible { + outline: 4px auto -webkit-focus-ring-color; +} + +@media (prefers-color-scheme: light) { + :root { + color: #213547; + background-color: #ffffff; + } + a:hover { + color: #747bff; + } + button { + background-color: #f9f9f9; + } +} diff --git a/v3/examples/dev/frontend/src/assets/svelte.svg b/v3/examples/dev/frontend/src/assets/svelte.svg new file mode 100644 index 000000000..c5e08481f --- /dev/null +++ b/v3/examples/dev/frontend/src/assets/svelte.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/v3/examples/dev/frontend/src/lib/Counter.svelte b/v3/examples/dev/frontend/src/lib/Counter.svelte new file mode 100644 index 000000000..e45f90310 --- /dev/null +++ b/v3/examples/dev/frontend/src/lib/Counter.svelte @@ -0,0 +1,10 @@ + + + diff --git a/v3/examples/dev/frontend/src/main.js b/v3/examples/dev/frontend/src/main.js new file mode 100644 index 000000000..8a909a15a --- /dev/null +++ b/v3/examples/dev/frontend/src/main.js @@ -0,0 +1,8 @@ +import './app.css' +import App from './App.svelte' + +const app = new App({ + target: document.getElementById('app'), +}) + +export default app diff --git a/v3/examples/dev/frontend/src/vite-env.d.ts b/v3/examples/dev/frontend/src/vite-env.d.ts new file mode 100644 index 000000000..4078e7476 --- /dev/null +++ b/v3/examples/dev/frontend/src/vite-env.d.ts @@ -0,0 +1,2 @@ +/// +/// diff --git a/v3/examples/dev/frontend/vite.config.js b/v3/examples/dev/frontend/vite.config.js new file mode 100644 index 000000000..d70196943 --- /dev/null +++ b/v3/examples/dev/frontend/vite.config.js @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite' +import { svelte } from '@sveltejs/vite-plugin-svelte' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [svelte()], +}) diff --git a/v3/examples/dev/go.mod b/v3/examples/dev/go.mod new file mode 100644 index 000000000..a4b028b7a --- /dev/null +++ b/v3/examples/dev/go.mod @@ -0,0 +1,46 @@ +module changeme + +go 1.21 + +require github.com/wailsapp/wails/v3 v3.0.0-alpha.0 + +require ( + github.com/Microsoft/go-winio v0.4.16 // indirect + github.com/bep/debounce v1.2.1 // indirect + github.com/ebitengine/purego v0.4.0-alpha.4 // indirect + github.com/emirpasic/gods v1.12.0 // indirect + github.com/go-git/gcfg v1.5.0 // indirect + github.com/go-git/go-billy/v5 v5.2.0 // indirect + github.com/go-git/go-git/v5 v5.3.0 // indirect + github.com/go-ole/go-ole v1.2.6 // indirect + github.com/google/uuid v1.3.0 // indirect + github.com/imdario/mergo v0.3.12 // 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/json-iterator/go v1.1.12 // indirect + github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351 // 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.0 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.19 // 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/rivo/uniseg v0.4.4 // indirect + github.com/rogpeppe/go-internal v1.10.1-0.20230524175051-ec119421bb97 // indirect + github.com/samber/lo v1.38.1 // indirect + github.com/sergi/go-diff v1.2.0 // indirect + github.com/stretchr/testify v1.8.4 // indirect + github.com/wailsapp/go-webview2 v1.0.9 // indirect + github.com/wailsapp/mimetype v1.4.1 // indirect + github.com/xanzy/ssh-agent v0.3.0 // indirect + golang.org/x/crypto v0.9.0 // indirect + golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df // indirect + golang.org/x/net v0.10.0 // indirect + golang.org/x/sys v0.13.0 // indirect + gopkg.in/warnings.v0 v0.1.2 // indirect +) + +replace github.com/wailsapp/wails/v3 => ../.. diff --git a/v3/examples/dev/go.sum b/v3/examples/dev/go.sum new file mode 100644 index 000000000..ab2b768d2 --- /dev/null +++ b/v3/examples/dev/go.sum @@ -0,0 +1,159 @@ +github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= +github.com/Microsoft/go-winio v0.4.16 h1:FtSW/jqD+l4ba5iPBj9CODVtgfYAD8w2wS923g/cFDk= +github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0= +github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7 h1:uSoVVbwJiQipAclBbw+8quDsfcvFjOpI5iCf4p/cqCs= +github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/ghQa61ZWa/C2Aw3RkjiTBOix7dkqa1VLIs= +github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo4JG6LR5AXSUEsOjtdm0kw0FtQtMJA= +github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= +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/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +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/emirpasic/gods v1.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg= +github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o= +github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= +github.com/gliderlabs/ssh v0.2.2 h1:6zsha5zo/TWhRhwqCD3+EarCAgZ2yN28ipRnGPnwkI0= +github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= +github.com/go-git/gcfg v1.5.0 h1:Q5ViNfGF8zFgyJWPqYwA7qGFoMTEiBmdlkcfRmpIMa4= +github.com/go-git/gcfg v1.5.0/go.mod h1:5m20vg6GwYabIxaOonVkTdrILxQMpEShl1xiMF4ua+E= +github.com/go-git/go-billy/v5 v5.0.0/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0= +github.com/go-git/go-billy/v5 v5.1.0/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0= +github.com/go-git/go-billy/v5 v5.2.0 h1:GcoouCP9J+5slw2uXAocL70z8ml4A8B/H8nEPt6CLPk= +github.com/go-git/go-billy/v5 v5.2.0/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0= +github.com/go-git/go-git-fixtures/v4 v4.0.2-0.20200613231340-f56387b50c12 h1:PbKy9zOy4aAKrJ5pibIRpVO2BXnK1Tlcg+caKI7Ox5M= +github.com/go-git/go-git-fixtures/v4 v4.0.2-0.20200613231340-f56387b50c12/go.mod h1:m+ICp2rF3jDhFgEZ/8yziagdT1C+ZpZcrJjappBCDSw= +github.com/go-git/go-git/v5 v5.3.0 h1:8WKMtJR2j8RntEXR/uvTKagfEt4GYlwQ7mntE4+0GWc= +github.com/go-git/go-git/v5 v5.3.0/go.mod h1:xdX4bWJ48aOrdhnl2XqHYstHbbp6+LFS4r4X+lNVprw= +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/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU= +github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= +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/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4= +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/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351 h1:DowS9hvgyYSX4TO5NpyC606/Z4SxnNYbT+WX27or6Ck= +github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +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.0 h1:2n0d2BwPVXSUq5yhe8lJPHdxevE2qK5G99PMStMZMaI= +github.com/leaanthony/u v1.1.0/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/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-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= +github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/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/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +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= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis= +github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= +github.com/rogpeppe/go-internal v1.10.1-0.20230524175051-ec119421bb97 h1:3RPlVWzZ/PDqmVuf/FKHARG5EMid/tl7cv54Sw/QRVY= +github.com/rogpeppe/go-internal v1.10.1-0.20230524175051-ec119421bb97/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/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= +github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/wailsapp/go-webview2 v1.0.7 h1:s95+7irJsAsTy1RsjJ6N0cYX7tZ4gP7Uzawds0L2urs= +github.com/wailsapp/go-webview2 v1.0.7/go.mod h1:Uk2BePfCRzttBBjFrBmqKGJd41P6QIHeV9kTgIeOZNo= +github.com/wailsapp/go-webview2 v1.0.8-0.20231010092311-42cbb98eb53b/go.mod h1:Uk2BePfCRzttBBjFrBmqKGJd41P6QIHeV9kTgIeOZNo= +github.com/wailsapp/go-webview2 v1.0.8/go.mod h1:Uk2BePfCRzttBBjFrBmqKGJd41P6QIHeV9kTgIeOZNo= +github.com/wailsapp/go-webview2 v1.0.9/go.mod h1:Uk2BePfCRzttBBjFrBmqKGJd41P6QIHeV9kTgIeOZNo= +github.com/wailsapp/mimetype v1.4.1 h1:pQN9ycO7uo4vsUUuPeHEYoUkLVkaRntMnHJxVwYhwHs= +github.com/wailsapp/mimetype v1.4.1/go.mod h1:9aV5k31bBOv5z6u+QP8TltzvNGJPmNJD4XlAL3U+j3o= +github.com/xanzy/ssh-agent v0.3.0 h1:wUMzuKtKilRgBAD1sUb8gOwwRr2FGoBVumcjoOACClI= +github.com/xanzy/ssh-agent v0.3.0/go.mod h1:3s9xbODqPuuhK9JV1R321M/FlMZSBvE5aY6eAcqrDh0= +golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g= +golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0= +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/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= +golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +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= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/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-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= +golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +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= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +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-20200227125254-8fa46927fb4f/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/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.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/v3/examples/dev/main.go b/v3/examples/dev/main.go new file mode 100644 index 000000000..acfcc8ba6 --- /dev/null +++ b/v3/examples/dev/main.go @@ -0,0 +1,51 @@ +package main + +import ( + "embed" + _ "embed" + "log" + + "github.com/wailsapp/wails/v3/pkg/application" +) + +//go:embed frontend/dist +var assets embed.FS + +func main() { + app := application.New(application.Options{ + Name: "dev", + Description: "A demo of using raw HTML & CSS", + Assets: application.AssetOptions{ + FS: assets, + }, + Mac: application.MacOptions{ + ApplicationShouldTerminateAfterLastWindowClosed: true, + }, + }) + // Create window + app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{ + Title: "Plain Bundle", + CSS: `body { background-color: rgba(255, 255, 255, 0); } .main { color: white; margin: 20%; }`, + Mac: application.MacWindow{ + InvisibleTitleBarHeight: 50, + Backdrop: application.MacBackdropTranslucent, + TitleBar: application.MacTitleBarHiddenInset, + }, + + URL: "/", + }) + + //app.On(events.Common.ThemeChanged, func(e *application.Event) { + // if app.IsDarkMode() { + // log.Println("Dark mode!") + // } else { + // log.Println("Light mode!") + // } + //}) + + err := app.Run() + + if err != nil { + log.Fatal(err) + } +} diff --git a/v3/examples/dialogs/README.md b/v3/examples/dialogs/README.md new file mode 100644 index 000000000..d80afc91a --- /dev/null +++ b/v3/examples/dialogs/README.md @@ -0,0 +1,33 @@ +# Dialogs Example + +This example is a comprehensive example of using dialogs in Wails. + +## Running the example + +To run the example, simply run the following command: + +```bash +go run main.go +``` + +## Building the example + +To build the example in debug mode, simply run the following command: + +```bash +wails3 task build +``` + +To build the example to use application icons, simply run the following command: + +```bash +wails3 task package +``` + +# Status + +| Platform | Status | +|----------|----------------| +| Mac | Mostly Working | +| Windows | Working | +| Linux | | diff --git a/v3/examples/dialogs/Taskfile.yml b/v3/examples/dialogs/Taskfile.yml new file mode 100644 index 000000000..476646cdc --- /dev/null +++ b/v3/examples/dialogs/Taskfile.yml @@ -0,0 +1,105 @@ +version: '3' + +vars: + APP_NAME: "buildtest{{exeExt}}" +tasks: + + build: + summary: Builds the application + cmds: + - go build -gcflags=all="-N -l" -o bin/{{.APP_NAME}} + env: + CGO_CFLAGS: "-mmacosx-version-min=10.13" + CGO_LDFLAGS: "-mmacosx-version-min=10.13" + MACOSX_DEPLOYMENT_TARGET: "10.13" + + package: + summary: Packages a production build of the application into a `.app` or `.exe` bundle + cmds: + - task: package:darwin + - task: package:windows + +# ------------------------------------------------------------------------------ + + generate:icons: + summary: Generates Windows `.ico` and Mac `.icns` files from an image + dir: build + cmds: + # Generates both .ico and .icns files + - wails3 generate icons -input appicon.png + + build:production:darwin: + summary: Creates a production build of the application + cmds: + - GOOS=darwin GOARCH={{.GOARCH}} go build -tags production -ldflags="-w -s" -o build/bin/{{.APP_NAME}} + env: + CGO_CFLAGS: "-mmacosx-version-min=10.13" + CGO_LDFLAGS: "-mmacosx-version-min=10.13" + MACOSX_DEPLOYMENT_TARGET: "10.13" + GOARCH: '{{.GOARCH}}' + + generate:app_bundle: + summary: Builds a `.app` bundle + cmds: + - mkdir -p {{.APP_NAME}}.app/Contents/{MacOS,Resources} + - cp build/icons.icns {{.APP_NAME}}.app/Contents/Resources + - cp build/bin/{{.APP_NAME}} {{.APP_NAME}}.app/Contents/MacOS + - cp build/Info.plist {{.APP_NAME}}.app/Contents + + package:darwin:arm64: + summary: Packages a production build of the application into a `.app` bundle + platforms: + - darwin + deps: + - task: build:production:darwin + vars: + ARCH: arm64 + - generate:icons + cmds: + - task: generate:app_bundle + + package:darwin: + summary: Packages a production build of the application into a `.app` bundle + platforms: + - darwin + deps: + - task: build:production:darwin + - generate:icons + cmds: + - task: generate:app_bundle + + generate:syso: + dir: build + platforms: + - windows + cmds: + - wails3 generate syso -arch {{.GOARCH}} -icon icon.ico -manifest wails.exe.manifest -info info.json -out ../wails.syso + vars: + GOARCH: '{{.GOARCH}}' + + package:windows: + summary: Packages a production build of the application into a `.exe` bundle + platforms: + - windows/amd64 + deps: + - generate:icons + cmds: + - task: generate:syso + vars: + GOARCH: amd64 + - go build -tags production -gcflags=all="-N -l" -o bin/{{.APP_NAME}} + - powershell Remove-item wails.syso + + + package:windows:arm64: + summary: Packages a production build of the application into a `.exe` bundle (ARM64) + platforms: + - windows + cmds: + - task: generate:syso + vars: + GOARCH: arm64 + - go build -tags production -ldflags="-w -s -H windowsgui" -o bin/buildtest.arm64.exe + - powershell Remove-item wails.syso + env: + GOARCH: arm64 diff --git a/v3/examples/dialogs/build/Info.dev.plist b/v3/examples/dialogs/build/Info.dev.plist new file mode 100644 index 000000000..d6d28b179 --- /dev/null +++ b/v3/examples/dialogs/build/Info.dev.plist @@ -0,0 +1,35 @@ + + + + CFBundlePackageType + APPL + CFBundleName + My App + CFBundleExecutable + app + CFBundleIdentifier + com.wails.app + CFBundleVersion + v1.0.0 + CFBundleGetInfoString + The ultimate thing + CFBundleShortVersionString + v1 + CFBundleIconFile + icons + LSMinimumSystemVersion + 10.13.0 + NSHighResolutionCapable + true + NSHumanReadableCopyright + (c) Me + NSAppTransportSecurity + + NSAllowsLocalNetworking + + + + + + + \ No newline at end of file diff --git a/v3/examples/dialogs/build/Info.plist b/v3/examples/dialogs/build/Info.plist new file mode 100644 index 000000000..7f03f54e9 --- /dev/null +++ b/v3/examples/dialogs/build/Info.plist @@ -0,0 +1,27 @@ + + + + CFBundlePackageType + APPL + CFBundleName + My App + CFBundleExecutable + app + CFBundleIdentifier + com.wails.app + CFBundleVersion + v1.0.0 + CFBundleGetInfoString + The ultimate thing + CFBundleShortVersionString + v1 + CFBundleIconFile + icons + LSMinimumSystemVersion + 10.13.0 + NSHighResolutionCapable + true + NSHumanReadableCopyright + (c) Me + + \ No newline at end of file diff --git a/v3/examples/dialogs/build/appicon.png b/v3/examples/dialogs/build/appicon.png new file mode 100644 index 000000000..63617fe4f Binary files /dev/null and b/v3/examples/dialogs/build/appicon.png differ diff --git a/v3/examples/dialogs/build/icon.ico b/v3/examples/dialogs/build/icon.ico new file mode 100644 index 000000000..bfa0690b7 Binary files /dev/null and b/v3/examples/dialogs/build/icon.ico differ diff --git a/v3/examples/dialogs/build/icons.icns b/v3/examples/dialogs/build/icons.icns new file mode 100644 index 000000000..1b5bd4c86 Binary files /dev/null and b/v3/examples/dialogs/build/icons.icns differ diff --git a/v3/examples/dialogs/build/info.json b/v3/examples/dialogs/build/info.json new file mode 100644 index 000000000..1005eb5cb --- /dev/null +++ b/v3/examples/dialogs/build/info.json @@ -0,0 +1,15 @@ +{ + "fixed": { + "file_version": "v1.0.0" + }, + "info": { + "0000": { + "ProductVersion": "v1.0.0", + "CompanyName": "My Company Name", + "FileDescription": "A thing that does a thing", + "LegalCopyright": "(c) 2023 My Company Name", + "ProductName": "My Product Name", + "Comments": "This is a comment" + } + } +} \ No newline at end of file diff --git a/v3/examples/dialogs/build/wails.exe.manifest b/v3/examples/dialogs/build/wails.exe.manifest new file mode 100644 index 000000000..fb1ce5bde --- /dev/null +++ b/v3/examples/dialogs/build/wails.exe.manifest @@ -0,0 +1,15 @@ + + + + + + + + + + + true/pm + permonitorv2,permonitor + + + \ No newline at end of file diff --git a/v3/examples/dialogs/main.go b/v3/examples/dialogs/main.go new file mode 100644 index 000000000..b06ba3a6c --- /dev/null +++ b/v3/examples/dialogs/main.go @@ -0,0 +1,349 @@ +package main + +import ( + _ "embed" + "log" + "log/slog" + "os" + "runtime" + "strings" + + "github.com/wailsapp/wails/v3/pkg/icons" + + "github.com/wailsapp/wails/v3/pkg/application" +) + +func main() { + + app := application.New(application.Options{ + Name: "Dialogs Demo", + Description: "A demo of the dialogs API", + Assets: application.AlphaAssets, + Logger: application.DefaultLogger(slog.LevelDebug), + Mac: application.MacOptions{ + ApplicationShouldTerminateAfterLastWindowClosed: true, + }, + }) + + // Create a custom menu + menu := app.NewMenu() + menu.AddRole(application.AppMenu) + + // Let's make a "Demo" menu + infoMenu := menu.AddSubmenu("Info") + infoMenu.Add("Info").OnClick(func(ctx *application.Context) { + dialog := application.InfoDialog() + dialog.SetTitle("Custom Title") + dialog.SetMessage("This is a custom message") + dialog.Show() + }) + infoMenu.Add("Info (Title only)").OnClick(func(ctx *application.Context) { + dialog := application.InfoDialog() + dialog.SetTitle("Custom Title") + dialog.Show() + }) + infoMenu.Add("Info (Message only)").OnClick(func(ctx *application.Context) { + dialog := application.InfoDialog() + dialog.SetMessage("This is a custom message") + dialog.Show() + }) + infoMenu.Add("Info (Custom Icon)").OnClick(func(ctx *application.Context) { + dialog := application.InfoDialog() + dialog.SetTitle("Custom Icon Example") + dialog.SetMessage("Using a custom icon") + dialog.SetIcon(icons.ApplicationDarkMode256) + dialog.Show() + }) + infoMenu.Add("About").OnClick(func(ctx *application.Context) { + app.ShowAboutDialog() + }) + + questionMenu := menu.AddSubmenu("Question") + questionMenu.Add("Question (No default)").OnClick(func(ctx *application.Context) { + dialog := application.QuestionDialog() + dialog.SetMessage("No default button") + dialog.AddButton("Yes") + dialog.AddButton("No") + dialog.Show() + }) + questionMenu.Add("Question (Attached to Window)").OnClick(func(ctx *application.Context) { + dialog := application.QuestionDialog() + dialog.AttachToWindow(app.CurrentWindow()) + dialog.SetMessage("No default button") + dialog.AddButton("Yes") + dialog.AddButton("No") + dialog.Show() + }) + questionMenu.Add("Question (With Default)").OnClick(func(ctx *application.Context) { + dialog := application.QuestionDialog() + dialog.SetTitle("Quit") + dialog.SetMessage("You have unsaved work. Are you sure you want to quit?") + dialog.AddButton("Yes").OnClick(func() { + app.Quit() + }) + no := dialog.AddButton("No") + dialog.SetDefaultButton(no) + dialog.Show() + }) + questionMenu.Add("Question (With Cancel)").OnClick(func(ctx *application.Context) { + dialog := application.QuestionDialog(). + SetTitle("Update"). + SetMessage("The cancel button is selected when pressing escape") + download := dialog.AddButton("📥 Download") + download.OnClick(func() { + application.InfoDialog().SetMessage("Downloading...").Show() + }) + no := dialog.AddButton("Cancel") + dialog.SetDefaultButton(download) + dialog.SetCancelButton(no) + dialog.Show() + }) + questionMenu.Add("Question (Custom Icon)").OnClick(func(ctx *application.Context) { + dialog := application.QuestionDialog() + dialog.SetTitle("Custom Icon Example") + dialog.SetMessage("Using a custom icon") + dialog.SetIcon(icons.WailsLogoWhiteTransparent) + dialog.SetDefaultButton(dialog.AddButton("I like it!")) + dialog.AddButton("Not so keen...") + dialog.Show() + }) + + warningMenu := menu.AddSubmenu("Warning") + warningMenu.Add("Warning").OnClick(func(ctx *application.Context) { + dialog := application.WarningDialog() + dialog.SetTitle("Custom Title") + dialog.SetMessage("This is a custom message") + dialog.Show() + }) + warningMenu.Add("Warning (Title only)").OnClick(func(ctx *application.Context) { + dialog := application.WarningDialog() + dialog.SetTitle("Custom Title") + dialog.Show() + }) + warningMenu.Add("Warning (Message only)").OnClick(func(ctx *application.Context) { + dialog := application.WarningDialog() + dialog.SetMessage("This is a custom message") + dialog.Show() + }) + warningMenu.Add("Warning (Custom Icon)").OnClick(func(ctx *application.Context) { + dialog := application.WarningDialog() + dialog.SetTitle("Custom Icon Example") + dialog.SetMessage("Using a custom icon") + dialog.SetIcon(icons.ApplicationLightMode256) + dialog.Show() + }) + + errorMenu := menu.AddSubmenu("Error") + errorMenu.Add("Error").OnClick(func(ctx *application.Context) { + dialog := application.ErrorDialog() + dialog.SetTitle("Ooops") + dialog.SetMessage("I accidentally the whole of Twitter") + dialog.Show() + }) + errorMenu.Add("Error (Title Only)").OnClick(func(ctx *application.Context) { + dialog := application.ErrorDialog() + dialog.SetTitle("Custom Title") + dialog.Show() + }) + errorMenu.Add("Error (Custom Message)").OnClick(func(ctx *application.Context) { + dialog := application.ErrorDialog() + dialog.SetMessage("This is a custom message") + dialog.Show() + }) + errorMenu.Add("Error (Custom Icon)").OnClick(func(ctx *application.Context) { + dialog := application.ErrorDialog() + dialog.SetTitle("Custom Icon Example") + dialog.SetMessage("Using a custom icon") + dialog.SetIcon(icons.WailsLogoWhite) + dialog.Show() + }) + + openMenu := menu.AddSubmenu("Open") + openMenu.Add("Open File").OnClick(func(ctx *application.Context) { + result, _ := application.OpenFileDialog(). + CanChooseFiles(true). + PromptForSingleSelection() + if result != "" { + application.InfoDialog().SetMessage(result).Show() + } else { + application.InfoDialog().SetMessage("No file selected").Show() + } + }) + openMenu.Add("Open File (Show Hidden Files)").OnClick(func(ctx *application.Context) { + result, _ := application.OpenFileDialog(). + CanChooseFiles(true). + CanCreateDirectories(true). + ShowHiddenFiles(true). + PromptForSingleSelection() + if result != "" { + application.InfoDialog().SetMessage(result).Show() + } else { + application.InfoDialog().SetMessage("No file selected").Show() + } + }) + openMenu.Add("Open File (Attach to window)").OnClick(func(ctx *application.Context) { + result, _ := application.OpenFileDialog(). + CanChooseFiles(true). + CanCreateDirectories(true). + ShowHiddenFiles(true). + AttachToWindow(app.CurrentWindow()). + PromptForSingleSelection() + if result != "" { + application.InfoDialog().SetMessage(result).Show() + } else { + application.InfoDialog().SetMessage("No file selected").Show() + } + }) + openMenu.Add("Open Multiple Files (Show Hidden Files)").OnClick(func(ctx *application.Context) { + result, _ := application.OpenFileDialog(). + CanChooseFiles(true). + CanCreateDirectories(true). + ShowHiddenFiles(true). + PromptForMultipleSelection() + if len(result) > 0 { + application.InfoDialog().SetMessage(strings.Join(result, ",")).Show() + } else { + application.InfoDialog().SetMessage("No file selected").Show() + } + }) + openMenu.Add("Open Directory").OnClick(func(ctx *application.Context) { + result, _ := application.OpenFileDialog(). + CanChooseDirectories(true). + PromptForSingleSelection() + if result != "" { + application.InfoDialog().SetMessage(result).Show() + } else { + application.InfoDialog().SetMessage("No directory selected").Show() + } + }) + openMenu.Add("Open Directory (Create Directories)").OnClick(func(ctx *application.Context) { + result, _ := application.OpenFileDialog(). + CanChooseDirectories(true). + CanCreateDirectories(true). + PromptForSingleSelection() + if result != "" { + application.InfoDialog().SetMessage(result).Show() + } else { + application.InfoDialog().SetMessage("No directory selected").Show() + } + }) + openMenu.Add("Open Directory (Resolves Aliases)").OnClick(func(ctx *application.Context) { + result, _ := application.OpenFileDialog(). + CanChooseDirectories(true). + CanCreateDirectories(true). + ResolvesAliases(true). + PromptForSingleSelection() + if result != "" { + application.InfoDialog().SetMessage(result).Show() + } else { + application.InfoDialog().SetMessage("No directory selected").Show() + } + }) + openMenu.Add("Open File/Directory (Set Title)").OnClick(func(ctx *application.Context) { + dialog := application.OpenFileDialog(). + CanChooseDirectories(true). + CanCreateDirectories(true). + ResolvesAliases(true) + if runtime.GOOS == "darwin" { + dialog.SetMessage("Select a file/directory") + } else { + dialog.SetTitle("Select a file/directory") + } + + result, _ := dialog.PromptForSingleSelection() + if result != "" { + application.InfoDialog().SetMessage(result).Show() + } else { + application.InfoDialog().SetMessage("No file/directory selected").Show() + } + }) + openMenu.Add("Open (Full Example)").OnClick(func(ctx *application.Context) { + cwd, _ := os.Getwd() + dialog := application.OpenFileDialog(). + SetTitle("Select a file"). + SetMessage("Select a file to open"). + SetButtonText("Let's do this!"). + SetDirectory(cwd). + CanCreateDirectories(true). + ResolvesAliases(true). + AllowsOtherFileTypes(true). + TreatsFilePackagesAsDirectories(true). + ShowHiddenFiles(true). + CanSelectHiddenExtension(true). + AddFilter("Text Files", "*.txt; *.md"). + AddFilter("Video Files", "*.mov; *.mp4; *.avi") + + if runtime.GOOS == "darwin" { + dialog.SetMessage("Select a file") + } else { + dialog.SetTitle("Select a file") + } + + result, _ := dialog.PromptForSingleSelection() + if result != "" { + application.InfoDialog().SetMessage(result).Show() + } else { + application.InfoDialog().SetMessage("No file selected").Show() + } + }) + + saveMenu := menu.AddSubmenu("Save") + saveMenu.Add("Select File (Defaults)").OnClick(func(ctx *application.Context) { + result, _ := application.SaveFileDialog(). + PromptForSingleSelection() + if result != "" { + application.InfoDialog().SetMessage(result).Show() + } + }) + saveMenu.Add("Select File (Attach To WebviewWindow)").OnClick(func(ctx *application.Context) { + result, _ := application.SaveFileDialog(). + AttachToWindow(app.CurrentWindow()). + PromptForSingleSelection() + if result != "" { + application.InfoDialog().SetMessage(result).Show() + } + }) + saveMenu.Add("Select File (Show Hidden Files)").OnClick(func(ctx *application.Context) { + result, _ := application.SaveFileDialog(). + ShowHiddenFiles(true). + PromptForSingleSelection() + if result != "" { + application.InfoDialog().SetMessage(result).Show() + } + }) + saveMenu.Add("Select File (Cannot Create Directories)").OnClick(func(ctx *application.Context) { + result, _ := application.SaveFileDialog(). + CanCreateDirectories(false). + PromptForSingleSelection() + if result != "" { + application.InfoDialog().SetMessage(result).Show() + } + }) + saveMenu.Add("Select File (Full Example)").OnClick(func(ctx *application.Context) { + result, _ := application.SaveFileDialog(). + CanCreateDirectories(false). + ShowHiddenFiles(true). + SetMessage("Select a file"). + SetDirectory("/Applications"). + SetButtonText("Let's do this!"). + SetFilename("README.md"). + HideExtension(true). + AllowsOtherFileTypes(true). + TreatsFilePackagesAsDirectories(true). + ShowHiddenFiles(true). + PromptForSingleSelection() + if result != "" { + application.InfoDialog().SetMessage(result).Show() + } + }) + + app.SetMenu(menu) + + app.NewWebviewWindow() + + err := app.Run() + + if err != nil { + log.Fatal(err.Error()) + } +} diff --git a/v3/examples/drag-n-drop/README.md b/v3/examples/drag-n-drop/README.md new file mode 100644 index 000000000..cb3c56fd0 --- /dev/null +++ b/v3/examples/drag-n-drop/README.md @@ -0,0 +1,27 @@ +# Drag-n-drop Example + +This example demonstrates how to handle files being dragged into the application. + +## Running the example + +To run the example, simply run the following command: + +```bash +go run main.go +``` + +## Building the example + +To build the example in debug mode, simply run the following command: + +```bash +wails3 task build +``` + +# Status + +| Platform | Status | +|----------|-------------| +| Mac | Working | +| Windows | Not Working | +| Linux | | diff --git a/v3/examples/drag-n-drop/assets/index.html b/v3/examples/drag-n-drop/assets/index.html new file mode 100644 index 000000000..6e7e2dd5a --- /dev/null +++ b/v3/examples/drag-n-drop/assets/index.html @@ -0,0 +1,36 @@ + + + + + Title + + + +

Drag-n-drop Demo

+
+Drop Files onto this window... +
+ + + + + \ No newline at end of file diff --git a/v3/examples/drag-n-drop/main.go b/v3/examples/drag-n-drop/main.go new file mode 100644 index 000000000..a0feae586 --- /dev/null +++ b/v3/examples/drag-n-drop/main.go @@ -0,0 +1,52 @@ +package main + +import ( + "embed" + _ "embed" + "log" + + "github.com/wailsapp/wails/v3/pkg/application" + "github.com/wailsapp/wails/v3/pkg/events" +) + +//go:embed assets +var assets embed.FS + +func main() { + + app := application.New(application.Options{ + Name: "Drag-n-drop Demo", + Description: "A demo of the Drag-n-drop API", + Assets: application.AssetOptions{ + FS: assets, + }, + Mac: application.MacOptions{ + ApplicationShouldTerminateAfterLastWindowClosed: true, + }, + }) + + window := app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{ + Title: "Drag-n-drop Demo", + Mac: application.MacWindow{ + Backdrop: application.MacBackdropTranslucent, + TitleBar: application.MacTitleBarHiddenInsetUnified, + InvisibleTitleBarHeight: 50, + }, + EnableDragAndDrop: true, + }) + + window.On(events.Common.WindowFilesDropped, func(event *application.WindowEvent) { + files := event.Context().DroppedFiles() + app.Events.Emit(&application.WailsEvent{ + Name: "files", + Data: files, + }) + app.Logger.Info("Files Dropped!", "files", files) + }) + + err := app.Run() + + if err != nil { + log.Fatal(err.Error()) + } +} diff --git a/v3/examples/events/README.md b/v3/examples/events/README.md new file mode 100644 index 000000000..596540f5a --- /dev/null +++ b/v3/examples/events/README.md @@ -0,0 +1,25 @@ +# Events Example + +This example is a demonstration of using the new events API. +It has 2 windows that can emit events from the frontend and the backend emits an event every 10 seconds. +All events emitted are logged either to the console or the window. + +It also demonstrates the use of `RegisterHook` to register a function to be called when an event is emitted. +For one window, it captures the `WindowClosing` event and prevents the window from closing twice. +The other window uses both hooks and events to show the window is gaining focus. + +## Running the example + +To run the example, simply run the following command: + +```bash +go run main.go +``` + +# Status + +| Platform | Status | +|----------|---------| +| Mac | | +| Windows | Working | +| Linux | | \ No newline at end of file diff --git a/v3/examples/events/assets/index.html b/v3/examples/events/assets/index.html new file mode 100644 index 000000000..d8e0906ee --- /dev/null +++ b/v3/examples/events/assets/index.html @@ -0,0 +1,23 @@ + + + + + Title + + + +

Events Demo

+
+The main program emits an event every 10s which will be displayed in the section below. +To send an event from this window, click here: +
+ + + + + \ No newline at end of file diff --git a/v3/examples/events/main.go b/v3/examples/events/main.go new file mode 100644 index 000000000..8ec5a2bc8 --- /dev/null +++ b/v3/examples/events/main.go @@ -0,0 +1,110 @@ +package main + +import ( + "embed" + _ "embed" + "log" + "time" + + "github.com/wailsapp/wails/v3/pkg/application" + "github.com/wailsapp/wails/v3/pkg/events" +) + +//go:embed assets +var assets embed.FS + +func main() { + + app := application.New(application.Options{ + Name: "Events Demo", + Description: "A demo of the Events API", + Assets: application.AssetOptions{ + FS: assets, + }, + Mac: application.MacOptions{ + ApplicationShouldTerminateAfterLastWindowClosed: true, + }, + }) + + // Custom event handling + app.Events.On("myevent", func(e *application.WailsEvent) { + app.Logger.Info("[Go] WailsEvent received", "name", e.Name, "data", e.Data, "sender", e.Sender, "cancelled", e.Cancelled) + }) + + // OS specific application events + app.On(events.Common.ApplicationStarted, func(event *application.Event) { + for { + app.Events.Emit(&application.WailsEvent{ + Name: "myevent", + Data: "hello", + }) + time.Sleep(10 * time.Second) + } + }) + + app.On(events.Common.ThemeChanged, func(event *application.Event) { + app.Logger.Info("System theme changed!") + if event.Context().IsDarkMode() { + app.Logger.Info("System is now using dark mode!") + } else { + app.Logger.Info("System is now using light mode!") + } + }) + + // Platform agnostic events + app.On(events.Common.ApplicationStarted, func(event *application.Event) { + app.Logger.Info("events.Common.ApplicationStarted fired!") + }) + + win1 := app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{ + Title: "Events Demo", + Name: "Window 1", + Mac: application.MacWindow{ + Backdrop: application.MacBackdropTranslucent, + TitleBar: application.MacTitleBarHiddenInsetUnified, + InvisibleTitleBarHeight: 50, + }, + }) + + var countdown = 3 + + win1.RegisterHook(events.Common.WindowClosing, func(e *application.WindowEvent) { + countdown-- + if countdown == 0 { + app.Logger.Info("Window 1 Closing!") + return + } + app.Logger.Info("Window 1 Closing? Nope! Not closing!") + e.Cancel() + }) + + win2 := app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{ + Title: "Events Demo", + Name: "Window 2", + Mac: application.MacWindow{ + Backdrop: application.MacBackdropTranslucent, + TitleBar: application.MacTitleBarHiddenInsetUnified, + InvisibleTitleBarHeight: 50, + }, + }) + + var cancel bool + + win2.RegisterHook(events.Common.WindowFocus, func(e *application.WindowEvent) { + app.Logger.Info("[Hook] Window focus!") + cancel = !cancel + if cancel { + e.Cancel() + } + }) + + win2.On(events.Common.WindowFocus, func(e *application.WindowEvent) { + app.Logger.Info("[Event] Window focus!") + }) + + err := app.Run() + + if err != nil { + log.Fatal(err.Error()) + } +} diff --git a/v3/examples/frameless/README.md b/v3/examples/frameless/README.md new file mode 100644 index 000000000..f17f7a5d3 --- /dev/null +++ b/v3/examples/frameless/README.md @@ -0,0 +1,19 @@ +# Frameless Example + +This example is a demonstration of using frameless windows in Wails. + +## Running the example + +To run the example, simply run the following command: + +```bash +go run . +``` + +# Status + +| Platform | Status | +|----------|---------| +| Mac | Working | +| Windows | Working | +| Linux | | diff --git a/v3/examples/frameless/assets/index.html b/v3/examples/frameless/assets/index.html new file mode 100644 index 000000000..3889e74e0 --- /dev/null +++ b/v3/examples/frameless/assets/index.html @@ -0,0 +1,32 @@ + + + + + + +
+
Draggable
+
Not Draggable
+
Not Draggable
+
Draggable
+
+ + diff --git a/v3/examples/frameless/go.mod b/v3/examples/frameless/go.mod new file mode 100644 index 000000000..edfa98f91 --- /dev/null +++ b/v3/examples/frameless/go.mod @@ -0,0 +1,47 @@ +module frameless + +go 1.21 + +toolchain go1.21.0 + +require github.com/wailsapp/wails/v3 v3.0.0-alpha.0 + +require ( + github.com/Microsoft/go-winio v0.4.16 // indirect + github.com/bep/debounce v1.2.1 // indirect + github.com/ebitengine/purego v0.4.0-alpha.4 // indirect + github.com/emirpasic/gods v1.12.0 // indirect + github.com/go-git/gcfg v1.5.0 // indirect + github.com/go-git/go-billy/v5 v5.2.0 // indirect + github.com/go-git/go-git/v5 v5.3.0 // indirect + github.com/go-ole/go-ole v1.2.6 // indirect + github.com/godbus/dbus/v5 v5.1.0 // indirect + github.com/google/uuid v1.3.0 // indirect + github.com/imdario/mergo v0.3.12 // 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/json-iterator/go v1.1.12 // indirect + github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351 // 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.0 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.19 // 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/rivo/uniseg v0.4.4 // indirect + github.com/samber/lo v1.38.1 // indirect + github.com/sergi/go-diff v1.2.0 // indirect + github.com/wailsapp/go-webview2 v1.0.9 // indirect + github.com/wailsapp/mimetype v1.4.1 // indirect + github.com/xanzy/ssh-agent v0.3.0 // indirect + golang.org/x/crypto v0.9.0 // indirect + golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df // indirect + golang.org/x/net v0.10.0 // indirect + golang.org/x/sys v0.13.0 // indirect + gopkg.in/warnings.v0 v0.1.2 // indirect +) + +replace github.com/wailsapp/wails/v3 => ../.. diff --git a/v3/examples/frameless/go.sum b/v3/examples/frameless/go.sum new file mode 100644 index 000000000..169c421b9 --- /dev/null +++ b/v3/examples/frameless/go.sum @@ -0,0 +1,154 @@ +github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= +github.com/Microsoft/go-winio v0.4.16 h1:FtSW/jqD+l4ba5iPBj9CODVtgfYAD8w2wS923g/cFDk= +github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0= +github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7 h1:uSoVVbwJiQipAclBbw+8quDsfcvFjOpI5iCf4p/cqCs= +github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/ghQa61ZWa/C2Aw3RkjiTBOix7dkqa1VLIs= +github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo4JG6LR5AXSUEsOjtdm0kw0FtQtMJA= +github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= +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/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +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/emirpasic/gods v1.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg= +github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o= +github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= +github.com/gliderlabs/ssh v0.2.2 h1:6zsha5zo/TWhRhwqCD3+EarCAgZ2yN28ipRnGPnwkI0= +github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= +github.com/go-git/gcfg v1.5.0 h1:Q5ViNfGF8zFgyJWPqYwA7qGFoMTEiBmdlkcfRmpIMa4= +github.com/go-git/gcfg v1.5.0/go.mod h1:5m20vg6GwYabIxaOonVkTdrILxQMpEShl1xiMF4ua+E= +github.com/go-git/go-billy/v5 v5.0.0/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0= +github.com/go-git/go-billy/v5 v5.1.0/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0= +github.com/go-git/go-billy/v5 v5.2.0 h1:GcoouCP9J+5slw2uXAocL70z8ml4A8B/H8nEPt6CLPk= +github.com/go-git/go-billy/v5 v5.2.0/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0= +github.com/go-git/go-git-fixtures/v4 v4.0.2-0.20200613231340-f56387b50c12 h1:PbKy9zOy4aAKrJ5pibIRpVO2BXnK1Tlcg+caKI7Ox5M= +github.com/go-git/go-git-fixtures/v4 v4.0.2-0.20200613231340-f56387b50c12/go.mod h1:m+ICp2rF3jDhFgEZ/8yziagdT1C+ZpZcrJjappBCDSw= +github.com/go-git/go-git/v5 v5.3.0 h1:8WKMtJR2j8RntEXR/uvTKagfEt4GYlwQ7mntE4+0GWc= +github.com/go-git/go-git/v5 v5.3.0/go.mod h1:xdX4bWJ48aOrdhnl2XqHYstHbbp6+LFS4r4X+lNVprw= +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/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= +github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU= +github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= +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/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4= +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/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351 h1:DowS9hvgyYSX4TO5NpyC606/Z4SxnNYbT+WX27or6Ck= +github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +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.0 h1:2n0d2BwPVXSUq5yhe8lJPHdxevE2qK5G99PMStMZMaI= +github.com/leaanthony/u v1.1.0/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/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-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= +github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/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/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/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis= +github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/samber/lo v1.38.1 h1:j2XEAqXKb09Am4ebOg31SpvzUTTs6EN3VfgeLUhPdXM= +github.com/samber/lo v1.38.1/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA= +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= +github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/wailsapp/go-webview2 v1.0.9 h1:lrU+q0cf1wgLdR69rN+ZnRtMJNaJRrcQ4ELxoO7/xjs= +github.com/wailsapp/go-webview2 v1.0.9/go.mod h1:Uk2BePfCRzttBBjFrBmqKGJd41P6QIHeV9kTgIeOZNo= +github.com/wailsapp/mimetype v1.4.1 h1:pQN9ycO7uo4vsUUuPeHEYoUkLVkaRntMnHJxVwYhwHs= +github.com/wailsapp/mimetype v1.4.1/go.mod h1:9aV5k31bBOv5z6u+QP8TltzvNGJPmNJD4XlAL3U+j3o= +github.com/xanzy/ssh-agent v0.3.0 h1:wUMzuKtKilRgBAD1sUb8gOwwRr2FGoBVumcjoOACClI= +github.com/xanzy/ssh-agent v0.3.0/go.mod h1:3s9xbODqPuuhK9JV1R321M/FlMZSBvE5aY6eAcqrDh0= +golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g= +golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0= +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/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= +golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +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= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/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-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= +golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +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= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +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-20200227125254-8fa46927fb4f/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/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.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= diff --git a/v3/examples/frameless/main.go b/v3/examples/frameless/main.go new file mode 100644 index 000000000..684c7eaf3 --- /dev/null +++ b/v3/examples/frameless/main.go @@ -0,0 +1,36 @@ +package main + +import ( + "embed" + _ "embed" + "log" + + "github.com/wailsapp/wails/v3/pkg/application" +) + +//go:embed assets +var assets embed.FS + +func main() { + + app := application.New(application.Options{ + Name: "Frameless Demo", + Description: "A demo of frameless windows", + Assets: application.AssetOptions{ + FS: assets, + }, + Mac: application.MacOptions{ + ApplicationShouldTerminateAfterLastWindowClosed: true, + }, + }) + + app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{ + Frameless: true, + }) + + err := app.Run() + + if err != nil { + log.Fatal(err.Error()) + } +} diff --git a/v3/examples/hide-window/main.go b/v3/examples/hide-window/main.go new file mode 100644 index 000000000..0f6f259b2 --- /dev/null +++ b/v3/examples/hide-window/main.go @@ -0,0 +1,72 @@ +package main + +import ( + _ "embed" + "log" + "runtime" + + "github.com/wailsapp/wails/v3/pkg/application" + "github.com/wailsapp/wails/v3/pkg/icons" + "github.com/wailsapp/wails/v3/pkg/events" +) + +func main() { + app := application.New(application.Options{ + Name: "Hide Window Demo", + Description: "A test of Hidden window and display it", + Assets: application.AlphaAssets, + Mac: application.MacOptions{ + // ActivationPolicy: application.ActivationPolicyAccessory, + ApplicationShouldTerminateAfterLastWindowClosed: false, + }, + }) + + systemTray := app.NewSystemTray() + + window := app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{ + Width: 500, + Height: 800, + Frameless: false, + AlwaysOnTop: false, + Hidden: false, + DisableResize: false, + ShouldClose: func(window *application.WebviewWindow) bool { + println("close") + window.Hide() + return false + }, + Windows: application.WindowsWindow{ + HiddenOnTaskbar: true, + }, + }) + + if runtime.GOOS == "darwin" { + systemTray.SetTemplateIcon(icons.SystrayMacTemplate) + } + + + // Click Dock icon tigger application show + app.On(events.Mac.ApplicationShouldHandleReopen, func(event *application.Event) { + println("reopen") + window.Show() + }) + + myMenu := app.NewMenu() + myMenu.Add("Show").OnClick(func(ctx *application.Context) { + window.Show() + }) + + myMenu.Add("Quit").OnClick(func(ctx *application.Context) { + app.Quit() + }) + + systemTray.SetMenu(myMenu) + systemTray.OnClick(func() { + app.CurrentWindow().Show() + }) + + err := app.Run() + if err != nil { + log.Fatal(err) + } +} diff --git a/v3/examples/http-cors-workaround/README.md b/v3/examples/http-cors-workaround/README.md new file mode 100644 index 000000000..bb2b57a12 --- /dev/null +++ b/v3/examples/http-cors-workaround/README.md @@ -0,0 +1,77 @@ +# HTTP CORS Workaround Example + +This example demonstrates how to use the Wails v3 HTTP runtime API to bypass CORS issues when making network requests from the frontend. + +## The Problem + +When using Wails with the `wails://wails` protocol on Linux/macOS, CORS restrictions prevent direct HTTP requests from the frontend to external APIs. This is because: + +1. Wails uses a custom protocol (`wails://wails`) which is not recognized by external servers +2. The browser sends an empty Origin header for custom protocols +3. External servers reject requests without a valid Origin header + +## The Solution + +The Wails HTTP runtime API allows you to make HTTP requests through the Go backend, completely bypassing CORS restrictions. + +## Usage + +Instead of using `fetch` or `axios` directly: + +```javascript +// This will fail with CORS error +const response = await fetch('https://api.example.com/data'); +``` + +Use the Wails HTTP API: + +```javascript +// This works - request is made from Go backend +const response = await wails.HTTP.Get('https://api.example.com/data'); +``` + +## API Methods + +- `wails.HTTP.Get(url, options)` - GET request +- `wails.HTTP.Post(url, body, options)` - POST request +- `wails.HTTP.Put(url, body, options)` - PUT request +- `wails.HTTP.Delete(url, options)` - DELETE request +- `wails.HTTP.Patch(url, body, options)` - PATCH request +- `wails.HTTP.Head(url, options)` - HEAD request +- `wails.HTTP.Fetch(options)` - Generic request with full options + +## Example with Headers and Timeout + +```javascript +const response = await wails.HTTP.Post('https://api.example.com/users', { + name: 'John Doe', + email: 'john@example.com' +}, { + headers: { + 'Authorization': 'Bearer token123', + 'X-Custom-Header': 'value' + }, + timeout: 10 // 10 seconds timeout +}); + +if (response.error) { + console.error('Request failed:', response.error); +} else { + console.log('Status:', response.statusCode); + console.log('Data:', JSON.parse(response.body)); +} +``` + +## Running the Example + +1. Navigate to this directory +2. Run `wails3 dev` +3. Click the buttons to test various HTTP methods +4. Check the console for responses + +## Notes + +- All requests are made from the Go backend, so there are no CORS restrictions +- The response includes `statusCode`, `headers`, `body`, and `error` (if any) +- Request bodies are automatically JSON stringified for objects +- Default timeout is 30 seconds, but can be customized \ No newline at end of file diff --git a/v3/examples/http-cors-workaround/assets/index.html b/v3/examples/http-cors-workaround/assets/index.html new file mode 100644 index 000000000..1c293a896 --- /dev/null +++ b/v3/examples/http-cors-workaround/assets/index.html @@ -0,0 +1,204 @@ + + + + + + HTTP CORS Workaround Example + + + +

HTTP CORS Workaround Example

+ +
+

Test HTTP Requests

+

Click the buttons below to test various HTTP methods using the Wails HTTP API:

+ + + + + + + +
Loading...
+
+ +
+

Response:

+
Click a button to make a request
+
+ + + + \ No newline at end of file diff --git a/v3/examples/http-cors-workaround/main.go b/v3/examples/http-cors-workaround/main.go new file mode 100644 index 000000000..57a02db34 --- /dev/null +++ b/v3/examples/http-cors-workaround/main.go @@ -0,0 +1,33 @@ +package main + +import ( + "embed" + "log" + + "github.com/wailsapp/wails/v3/pkg/application" +) + +//go:embed assets +var assets embed.FS + +func main() { + app := application.New(application.Options{ + Name: "HTTP CORS Workaround Example", + Description: "Demonstrates using Wails HTTP API to bypass CORS", + Assets: application.AssetOptions{ + FS: assets, + }, + }) + + app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{ + Title: "HTTP CORS Workaround", + Width: 800, + Height: 600, + URL: "/", + }) + + err := app.Run() + if err != nil { + log.Fatal(err) + } +} \ No newline at end of file diff --git a/v3/examples/keybindings/README.md b/v3/examples/keybindings/README.md new file mode 100644 index 000000000..2180cc9d0 --- /dev/null +++ b/v3/examples/keybindings/README.md @@ -0,0 +1,21 @@ +# Keybindings Example + +This simple example demonstrates how to use keybindings in your application. +Run the example and press `Ctrl/CMD+Shift+C` to center the focused window. + +## Running the example + +To run the example, simply run the following command: + +```bash +go run . +``` + +# Status + +| Platform | Status | +|----------|---------| +| Mac | Working | +| Windows | Working | +| Linux | | + diff --git a/v3/examples/keybindings/main.go b/v3/examples/keybindings/main.go new file mode 100644 index 000000000..a78068ac2 --- /dev/null +++ b/v3/examples/keybindings/main.go @@ -0,0 +1,50 @@ +package main + +import ( + _ "embed" + "github.com/wailsapp/wails/v3/pkg/application" + "log" +) + +func main() { + app := application.New(application.Options{ + Name: "Key Bindings Demo", + Description: "A demo of the Key Bindings Options", + Mac: application.MacOptions{ + ApplicationShouldTerminateAfterLastWindowClosed: true, + }, + KeyBindings: map[string]func(window *application.WebviewWindow){ + "shift+ctrl+c": func(window *application.WebviewWindow) { + window.Center() + }, + }, + }) + + app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{ + Name: "Window 1", + Title: "Window 1", + URL: "https://wails.io", + KeyBindings: map[string]func(window *application.WebviewWindow){ + "F12": func(window *application.WebviewWindow) { + window.ToggleDevTools() + }, + }, + }) + + app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{ + Name: "Window 2", + Title: "Window 2", + URL: "https://google.com", + KeyBindings: map[string]func(window *application.WebviewWindow){ + "F12": func(window *application.WebviewWindow) { + println("Window 2: Toggle Dev Tools") + }, + }, + }) + + err := app.Run() + if err != nil { + log.Fatal(err) + } + +} diff --git a/v3/examples/menu/README.md b/v3/examples/menu/README.md new file mode 100644 index 000000000..cc926df73 --- /dev/null +++ b/v3/examples/menu/README.md @@ -0,0 +1,27 @@ +# Menu Example + +This example is a demonstration of different ways to create applications without using npm. + +## Running the example + +To run the example, simply run the following command: + +```bash +go run . +``` + +# Status + +| Platform | Status | +|----------|---------| +| Mac | Working | +| Windows | Working | +| Linux | | + +# Known Issues + +- [Resize cursor still visible when not resizable](https://github.com/orgs/wailsapp/projects/6/views/1?pane=issue&itemId=40962163) + +--- + +Icon attribution: [Click icons created by kusumapotter - Flaticon](https://www.flaticon.com/free-icons/click) \ No newline at end of file diff --git a/v3/examples/menu/icon.png b/v3/examples/menu/icon.png new file mode 100644 index 000000000..e934687ca Binary files /dev/null and b/v3/examples/menu/icon.png differ diff --git a/v3/examples/menu/main.go b/v3/examples/menu/main.go new file mode 100644 index 000000000..815b3fe62 --- /dev/null +++ b/v3/examples/menu/main.go @@ -0,0 +1,115 @@ +package main + +import ( + _ "embed" + "log" + "runtime" + + "github.com/wailsapp/wails/v3/pkg/application" +) + +//go:embed icon.png +var clickBitmap []byte + +func main() { + + app := application.New(application.Options{ + Name: "Menu Demo", + Description: "A demo of the menu system", + //Assets: application.AlphaAssets, + Mac: application.MacOptions{ + ApplicationShouldTerminateAfterLastWindowClosed: true, + }, + }) + + // Create a custom menu + menu := app.NewMenu() + if runtime.GOOS == "darwin" { + menu.AddRole(application.AppMenu) + } + + // Let's make a "Demo" menu + myMenu := menu.AddSubmenu("Demo") + + // Disabled menu item + myMenu.Add("Not Enabled").SetEnabled(false) + + // Click callbacks + myMenu.Add("Click Me!").SetBitmap(clickBitmap).OnClick(func(ctx *application.Context) { + switch ctx.ClickedMenuItem().Label() { + case "Click Me!": + ctx.ClickedMenuItem().SetLabel("Thanks mate!") + case "Thanks mate!": + ctx.ClickedMenuItem().SetLabel("Click Me!") + } + }) + + // You can control the current window from the menu + myMenu.Add("Lock WebviewWindow Resize").OnClick(func(ctx *application.Context) { + if app.CurrentWindow().Resizable() { + app.CurrentWindow().SetResizable(false) + ctx.ClickedMenuItem().SetLabel("Unlock WebviewWindow Resize") + } else { + app.CurrentWindow().SetResizable(true) + ctx.ClickedMenuItem().SetLabel("Lock WebviewWindow Resize") + } + }) + + myMenu.AddSeparator() + + // Checkboxes will tell you their new state so you don't need to track it + myMenu.AddCheckbox("My checkbox", true).OnClick(func(context *application.Context) { + println("Clicked checkbox. Checked:", context.ClickedMenuItem().Checked()) + }) + myMenu.AddSeparator() + + // Callbacks can be shared. This is useful for radio groups + radioCallback := func(ctx *application.Context) { + menuItem := ctx.ClickedMenuItem() + menuItem.SetLabel(menuItem.Label() + "!") + } + + // Radio groups are created implicitly by placing radio items next to each other in a menu + myMenu.AddRadio("Radio 1", true).OnClick(radioCallback) + myMenu.AddRadio("Radio 2", false).OnClick(radioCallback) + myMenu.AddRadio("Radio 3", false).OnClick(radioCallback) + + // Submenus are also supported + submenu := myMenu.AddSubmenu("Submenu") + submenu.Add("Submenu item 1") + submenu.Add("Submenu item 2") + submenu.Add("Submenu item 3") + + myMenu.AddSeparator() + + beatles := myMenu.Add("Hello").OnClick(func(*application.Context) { + println("The beatles would be proud") + }) + myMenu.Add("Toggle the menuitem above").OnClick(func(*application.Context) { + if beatles.Enabled() { + beatles.SetEnabled(false) + beatles.SetLabel("Goodbye") + } else { + beatles.SetEnabled(true) + beatles.SetLabel("Hello") + } + }) + myMenu.Add("Hide the beatles").OnClick(func(ctx *application.Context) { + if beatles.Hidden() { + ctx.ClickedMenuItem().SetLabel("Hide the beatles!") + beatles.SetHidden(false) + } else { + beatles.SetHidden(true) + ctx.ClickedMenuItem().SetLabel("Unhide the beatles!") + } + }) + app.SetMenu(menu) + + app.NewWebviewWindow().SetBackgroundColour(application.NewRGB(33, 37, 41)) + + err := app.Run() + + if err != nil { + log.Fatal(err.Error()) + } +} diff --git a/v3/examples/oauth/README.md b/v3/examples/oauth/README.md new file mode 100644 index 000000000..ad19fb612 --- /dev/null +++ b/v3/examples/oauth/README.md @@ -0,0 +1,3 @@ +# OAuth Example + +This example is not ready for testing yet. diff --git a/v3/examples/oauth/assets/index.html b/v3/examples/oauth/assets/index.html new file mode 100644 index 000000000..3e698791f --- /dev/null +++ b/v3/examples/oauth/assets/index.html @@ -0,0 +1,39 @@ + + + + + Google SignIn + + + + +
+
+

Social Authentication

+

Login or Register with:

+ SignIn with Github +
+
+ + + + \ No newline at end of file diff --git a/v3/examples/oauth/main.go b/v3/examples/oauth/main.go new file mode 100644 index 000000000..449dbab96 --- /dev/null +++ b/v3/examples/oauth/main.go @@ -0,0 +1,67 @@ +package main + +import ( + "embed" + _ "embed" + "github.com/markbates/goth" + "github.com/markbates/goth/providers/github" + "github.com/wailsapp/wails/v3/pkg/application" + "github.com/wailsapp/wails/v3/plugins/oauth" + "log" + "os" +) + +//go:embed assets +var assets embed.FS + +func main() { + + oAuthPlugin := oauth.NewPlugin(oauth.Config{ + Providers: []goth.Provider{ + github.New( + os.Getenv("clientkey"), + os.Getenv("secret"), + "http://localhost:9876/auth/github/callback", + "email", + "profile"), + }, + }) + + app := application.New(application.Options{ + Name: "OAuth Demo", + Description: "A demo of the oauth Plugin", + Mac: application.MacOptions{ + ApplicationShouldTerminateAfterLastWindowClosed: true, + }, + Assets: application.AssetOptions{ + FS: assets, + }, + Plugins: map[string]application.Plugin{ + "github.com/wailsapp/wails/v3/plugins/oauth": oAuthPlugin, + }, + }) + + app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{ + Title: "OAuth Demo", + DevToolsEnabled: true, + OpenInspectorOnStartup: true, + Mac: application.MacWindow{ + Backdrop: application.MacBackdropTranslucent, + TitleBar: application.MacTitleBarHiddenInsetUnified, + InvisibleTitleBarHeight: 50, + }, + }) + + app.Events.On("github-login", func(e *application.WailsEvent) { + oAuthPlugin.Github() + }) + app.Events.On("github-logout", func(e *application.WailsEvent) { + oAuthPlugin.LogoutGithub() + }) + + err := app.Run() + + if err != nil { + log.Fatal(err.Error()) + } +} diff --git a/v3/examples/plain/README.md b/v3/examples/plain/README.md new file mode 100644 index 000000000..9d44f5119 --- /dev/null +++ b/v3/examples/plain/README.md @@ -0,0 +1,19 @@ +# Plain Example + +This example is a demonstration of different ways to create applications without using npm. + +## Running the example + +To run the example, simply run the following command: + +```bash +go run . +``` + +# Status + +| Platform | Status | +|----------|---------| +| Mac | Working | +| Windows | Working | +| Linux | | diff --git a/v3/examples/plain/main.go b/v3/examples/plain/main.go new file mode 100644 index 000000000..f1dac7aed --- /dev/null +++ b/v3/examples/plain/main.go @@ -0,0 +1,69 @@ +package main + +import ( + _ "embed" + "log" + "net/http" + "time" + + "github.com/wailsapp/wails/v3/pkg/application" +) + +func main() { + app := application.New(application.Options{ + Name: "Plain", + Description: "A demo of using raw HTML & CSS", + Mac: application.MacOptions{ + ApplicationShouldTerminateAfterLastWindowClosed: true, + }, + Assets: application.AssetOptions{ + Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + w.Write([]byte(`Plain Bundle

Plain Bundle

This is a plain bundle. It has no frontend code but this was Served by the AssetServer's Handler.



Clicking this paragraph emits an event...

`)) + }), + }, + }) + // Create window + app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{ + Title: "Plain Bundle", + CSS: `body { background-color: rgb(255, 255, 255); font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif; user-select: none; -ms-user-select: none; -webkit-user-select: none; } .main { color: white; margin: 20%; }`, + Mac: application.MacWindow{ + InvisibleTitleBarHeight: 50, + Backdrop: application.MacBackdropTranslucent, + TitleBar: application.MacTitleBarHiddenInsetUnified, + }, + URL: "/", + }) + + app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{ + Title: "HTML TEST", + HTML: "

AWESOME!

", + CSS: `body { background-color: rgb(255, 0, 0); font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif; user-select: none; -ms-user-select: none; -webkit-user-select: none; } .main { color: white; margin: 20%; }`, + JS: `window.iamhere = function() { console.log("Hello World!"); }`, + }) + + app.Events.On("clicked", func(_ *application.WailsEvent) { + println("clicked") + }) + + go func() { + time.Sleep(5 * time.Second) + + app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{ + Title: "Plain Bundle new Window from GoRoutine", + Width: 500, + Height: 500, + Mac: application.MacWindow{ + Backdrop: application.MacBackdropTranslucent, + TitleBar: application.MacTitleBarHiddenInsetUnified, + InvisibleTitleBarHeight: 50, + }, + }) + }() + + err := app.Run() + + if err != nil { + log.Fatal(err) + } +} diff --git a/v3/examples/plugins/README.md b/v3/examples/plugins/README.md new file mode 100644 index 000000000..8b51a0568 --- /dev/null +++ b/v3/examples/plugins/README.md @@ -0,0 +1,3 @@ +# Plugins Example + +This example is not ready for testing yet. \ No newline at end of file diff --git a/v3/examples/plugins/Taskfile.yml b/v3/examples/plugins/Taskfile.yml new file mode 100644 index 000000000..01d8f2efc --- /dev/null +++ b/v3/examples/plugins/Taskfile.yml @@ -0,0 +1,42 @@ +version: '3' + +tasks: + + pre-build: + summary: Pre-build hooks + + post-build: + summary: Post-build hooks + + build: + summary: Builds the application + cmds: + - task: pre-build + - go build -gcflags=all="-N -l" -o bin/testapp main.go + - task: post-build + env: + CGO_CFLAGS: "-mmacosx-version-min=10.13" + CGO_LDFLAGS: "-mmacosx-version-min=10.13" + MACOSX_DEPLOYMENT_TARGET: "10.13" + + generate-icons: + summary: Generates Windows `.ico` and Mac `.icns` files from an image + cmds: + # Generates both .ico and .icns files + - wails generate icons -input build/appicon.png + + build-prod: + summary: Creates a production build of the application + cmds: + - go build -tags production -ldflags="-w -s" -o bin/testapp + + package-darwin: + summary: Packages a production build of the application into a `.app` bundle + deps: + - build-prod + - generate-icons + cmds: + - mkdir -p buildtest.app/Contents/{MacOS,Resources} + - cp build/icons.icns buildtest.app/Contents/Resources + - cp bin/testapp buildtest.app/Contents/MacOS + - cp build/Info.plist buildtest.app/Contents \ No newline at end of file diff --git a/v3/examples/plugins/assets/index.html b/v3/examples/plugins/assets/index.html new file mode 100644 index 000000000..c12b595c1 --- /dev/null +++ b/v3/examples/plugins/assets/index.html @@ -0,0 +1,81 @@ + + + + + Wails Alpha + + + + +
Alpha
+
+

Open the inspector and start playing around with the plugins.

+
+ + \ No newline at end of file diff --git a/v3/examples/plugins/build/Info.dev.plist b/v3/examples/plugins/build/Info.dev.plist new file mode 100644 index 000000000..d6d28b179 --- /dev/null +++ b/v3/examples/plugins/build/Info.dev.plist @@ -0,0 +1,35 @@ + + + + CFBundlePackageType + APPL + CFBundleName + My App + CFBundleExecutable + app + CFBundleIdentifier + com.wails.app + CFBundleVersion + v1.0.0 + CFBundleGetInfoString + The ultimate thing + CFBundleShortVersionString + v1 + CFBundleIconFile + icons + LSMinimumSystemVersion + 10.13.0 + NSHighResolutionCapable + true + NSHumanReadableCopyright + (c) Me + NSAppTransportSecurity + + NSAllowsLocalNetworking + + + + + + + \ No newline at end of file diff --git a/v3/examples/plugins/build/Info.plist b/v3/examples/plugins/build/Info.plist new file mode 100644 index 000000000..ab571ad4f --- /dev/null +++ b/v3/examples/plugins/build/Info.plist @@ -0,0 +1,27 @@ + + + + CFBundlePackageType + APPL + CFBundleName + My App + CFBundleExecutable + testapp + CFBundleIdentifier + com.wails.app + CFBundleVersion + v1.0.0 + CFBundleGetInfoString + The ultimate thing + CFBundleShortVersionString + v1 + CFBundleIconFile + icons + LSMinimumSystemVersion + 10.13.0 + NSHighResolutionCapable + true + NSHumanReadableCopyright + (c) Me + + \ No newline at end of file diff --git a/v3/examples/plugins/build/appicon.png b/v3/examples/plugins/build/appicon.png new file mode 100644 index 000000000..63617fe4f Binary files /dev/null and b/v3/examples/plugins/build/appicon.png differ diff --git a/v3/examples/plugins/build/icons.icns b/v3/examples/plugins/build/icons.icns new file mode 100644 index 000000000..e69de29bb diff --git a/v3/examples/plugins/build/icons.ico b/v3/examples/plugins/build/icons.ico new file mode 100644 index 000000000..e69de29bb diff --git a/v3/examples/plugins/build/info.json b/v3/examples/plugins/build/info.json new file mode 100644 index 000000000..1005eb5cb --- /dev/null +++ b/v3/examples/plugins/build/info.json @@ -0,0 +1,15 @@ +{ + "fixed": { + "file_version": "v1.0.0" + }, + "info": { + "0000": { + "ProductVersion": "v1.0.0", + "CompanyName": "My Company Name", + "FileDescription": "A thing that does a thing", + "LegalCopyright": "(c) 2023 My Company Name", + "ProductName": "My Product Name", + "Comments": "This is a comment" + } + } +} \ No newline at end of file diff --git a/v3/examples/plugins/build/wails.exe.manifest b/v3/examples/plugins/build/wails.exe.manifest new file mode 100644 index 000000000..fb1ce5bde --- /dev/null +++ b/v3/examples/plugins/build/wails.exe.manifest @@ -0,0 +1,15 @@ + + + + + + + + + + + true/pm + permonitorv2,permonitor + + + \ No newline at end of file diff --git a/v3/examples/plugins/experimental/README.md b/v3/examples/plugins/experimental/README.md new file mode 100644 index 000000000..aee9b2e6d --- /dev/null +++ b/v3/examples/plugins/experimental/README.md @@ -0,0 +1,4 @@ +# Experimental Plugins + +This directory contains experimental plugins. These plugins are not supported by the Wails team and are provided as-is. +They may be removed at any time. If you have any thoughts about these plugins, they can be discussed on the [Wails Discord](https://discord.gg/u8JkcWsG). \ No newline at end of file diff --git a/v3/examples/plugins/go.mod b/v3/examples/plugins/go.mod new file mode 100644 index 000000000..f96af7572 --- /dev/null +++ b/v3/examples/plugins/go.mod @@ -0,0 +1,65 @@ +module plugin_demo + +go 1.21 + +toolchain go1.21.0 + +require github.com/wailsapp/wails/v3 v3.0.0-alpha.0 + +require ( + github.com/Microsoft/go-winio v0.4.16 // indirect + github.com/bep/debounce v1.2.1 // indirect + github.com/dustin/go-humanize v1.0.0 // indirect + github.com/ebitengine/purego v0.4.0-alpha.4 // indirect + github.com/emirpasic/gods v1.12.0 // indirect + github.com/go-git/gcfg v1.5.0 // indirect + github.com/go-git/go-billy/v5 v5.2.0 // indirect + github.com/go-git/go-git/v5 v5.3.0 // indirect + github.com/go-ole/go-ole v1.2.6 // indirect + github.com/google/uuid v1.3.0 // indirect + github.com/imdario/mergo v0.3.12 // 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/json-iterator/go v1.1.12 // indirect + github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect + github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351 // 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.0 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.19 // 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/errors v0.9.1 // indirect + github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect + github.com/rivo/uniseg v0.4.4 // indirect + github.com/rogpeppe/go-internal v1.10.1-0.20230524175051-ec119421bb97 // indirect + github.com/samber/lo v1.38.1 // indirect + github.com/sergi/go-diff v1.2.0 // indirect + github.com/stretchr/testify v1.8.4 // indirect + github.com/wailsapp/go-webview2 v1.0.9 // indirect + github.com/wailsapp/mimetype v1.4.1 // indirect + github.com/xanzy/ssh-agent v0.3.0 // indirect + golang.org/x/crypto v0.9.0 // indirect + golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df // indirect + golang.org/x/mod v0.12.0 // indirect + golang.org/x/net v0.10.0 // indirect + golang.org/x/sys v0.13.0 // indirect + golang.org/x/tools v0.6.0 // indirect + gopkg.in/warnings.v0 v0.1.2 // indirect + lukechampine.com/uint128 v1.2.0 // indirect + modernc.org/cc/v3 v3.40.0 // indirect + modernc.org/ccgo/v3 v3.16.13 // indirect + modernc.org/libc v1.22.3 // indirect + modernc.org/mathutil v1.5.0 // indirect + modernc.org/memory v1.5.0 // indirect + modernc.org/opt v0.1.3 // indirect + modernc.org/sqlite v1.21.0 // indirect + modernc.org/strutil v1.1.3 // indirect + modernc.org/token v1.0.1 // indirect +) + +replace github.com/wailsapp/wails/v3 => ../.. diff --git a/v3/examples/plugins/go.sum b/v3/examples/plugins/go.sum new file mode 100644 index 000000000..0671c68c8 --- /dev/null +++ b/v3/examples/plugins/go.sum @@ -0,0 +1,205 @@ +github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= +github.com/Microsoft/go-winio v0.4.16 h1:FtSW/jqD+l4ba5iPBj9CODVtgfYAD8w2wS923g/cFDk= +github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0= +github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7 h1:uSoVVbwJiQipAclBbw+8quDsfcvFjOpI5iCf4p/cqCs= +github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/ghQa61ZWa/C2Aw3RkjiTBOix7dkqa1VLIs= +github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo4JG6LR5AXSUEsOjtdm0kw0FtQtMJA= +github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= +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/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +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/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= +github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +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/emirpasic/gods v1.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg= +github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o= +github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= +github.com/gliderlabs/ssh v0.2.2 h1:6zsha5zo/TWhRhwqCD3+EarCAgZ2yN28ipRnGPnwkI0= +github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= +github.com/go-git/gcfg v1.5.0 h1:Q5ViNfGF8zFgyJWPqYwA7qGFoMTEiBmdlkcfRmpIMa4= +github.com/go-git/gcfg v1.5.0/go.mod h1:5m20vg6GwYabIxaOonVkTdrILxQMpEShl1xiMF4ua+E= +github.com/go-git/go-billy/v5 v5.0.0/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0= +github.com/go-git/go-billy/v5 v5.1.0/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0= +github.com/go-git/go-billy/v5 v5.2.0 h1:GcoouCP9J+5slw2uXAocL70z8ml4A8B/H8nEPt6CLPk= +github.com/go-git/go-billy/v5 v5.2.0/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0= +github.com/go-git/go-git-fixtures/v4 v4.0.2-0.20200613231340-f56387b50c12 h1:PbKy9zOy4aAKrJ5pibIRpVO2BXnK1Tlcg+caKI7Ox5M= +github.com/go-git/go-git-fixtures/v4 v4.0.2-0.20200613231340-f56387b50c12/go.mod h1:m+ICp2rF3jDhFgEZ/8yziagdT1C+ZpZcrJjappBCDSw= +github.com/go-git/go-git/v5 v5.3.0 h1:8WKMtJR2j8RntEXR/uvTKagfEt4GYlwQ7mntE4+0GWc= +github.com/go-git/go-git/v5 v5.3.0/go.mod h1:xdX4bWJ48aOrdhnl2XqHYstHbbp6+LFS4r4X+lNVprw= +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/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ= +github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo= +github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU= +github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= +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/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4= +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/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= +github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= +github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351 h1:DowS9hvgyYSX4TO5NpyC606/Z4SxnNYbT+WX27or6Ck= +github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +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.0 h1:2n0d2BwPVXSUq5yhe8lJPHdxevE2qK5G99PMStMZMaI= +github.com/leaanthony/u v1.1.0/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/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-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= +github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y= +github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= +github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/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/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/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= +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/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis= +github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= +github.com/rogpeppe/go-internal v1.10.1-0.20230524175051-ec119421bb97 h1:3RPlVWzZ/PDqmVuf/FKHARG5EMid/tl7cv54Sw/QRVY= +github.com/rogpeppe/go-internal v1.10.1-0.20230524175051-ec119421bb97/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/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= +github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/wailsapp/go-webview2 v1.0.8 h1:hyoFPlMSfb/NM64wuVbgBaq1MASJjqsSUYhN+Rbcr9Y= +github.com/wailsapp/go-webview2 v1.0.8/go.mod h1:Uk2BePfCRzttBBjFrBmqKGJd41P6QIHeV9kTgIeOZNo= +github.com/wailsapp/go-webview2 v1.0.9/go.mod h1:Uk2BePfCRzttBBjFrBmqKGJd41P6QIHeV9kTgIeOZNo= +github.com/wailsapp/mimetype v1.4.1 h1:pQN9ycO7uo4vsUUuPeHEYoUkLVkaRntMnHJxVwYhwHs= +github.com/wailsapp/mimetype v1.4.1/go.mod h1:9aV5k31bBOv5z6u+QP8TltzvNGJPmNJD4XlAL3U+j3o= +github.com/xanzy/ssh-agent v0.3.0 h1:wUMzuKtKilRgBAD1sUb8gOwwRr2FGoBVumcjoOACClI= +github.com/xanzy/ssh-agent v0.3.0/go.mod h1:3s9xbODqPuuhK9JV1R321M/FlMZSBvE5aY6eAcqrDh0= +golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g= +golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0= +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/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= +golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +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= +golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/sync v0.4.0 h1:zxkM55ReGkDlKSM+Fu41A+zmbZuaPVbGMzvvdUPznYQ= +golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +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= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/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-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= +golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +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= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +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-20200227125254-8fa46927fb4f/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/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.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +lukechampine.com/uint128 v1.2.0 h1:mBi/5l91vocEN8otkC5bDLhi2KdCticRiwbdB0O+rjI= +lukechampine.com/uint128 v1.2.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= +modernc.org/cc/v3 v3.40.0 h1:P3g79IUS/93SYhtoeaHW+kRCIrYaxJ27MFPv+7kaTOw= +modernc.org/cc/v3 v3.40.0/go.mod h1:/bTg4dnWkSXowUO6ssQKnOV0yMVxDYNIsIrzqTFDGH0= +modernc.org/ccgo/v3 v3.16.13 h1:Mkgdzl46i5F/CNR/Kj80Ri59hC8TKAhZrYSaqvkwzUw= +modernc.org/ccgo/v3 v3.16.13/go.mod h1:2Quk+5YgpImhPjv2Qsob1DnZ/4som1lJTodubIcoUkY= +modernc.org/ccorpus v1.11.6 h1:J16RXiiqiCgua6+ZvQot4yUuUy8zxgqbqEEUuGPlISk= +modernc.org/ccorpus v1.11.6/go.mod h1:2gEUTrWqdpH2pXsmTM1ZkjeSrUWDpjMu2T6m29L/ErQ= +modernc.org/httpfs v1.0.6 h1:AAgIpFZRXuYnkjftxTAZwMIiwEqAfk8aVB2/oA6nAeM= +modernc.org/httpfs v1.0.6/go.mod h1:7dosgurJGp0sPaRanU53W4xZYKh14wfzX420oZADeHM= +modernc.org/libc v1.22.3 h1:D/g6O5ftAfavceqlLOFwaZuA5KYafKwmr30A6iSqoyY= +modernc.org/libc v1.22.3/go.mod h1:MQrloYP209xa2zHome2a8HLiLm6k0UT8CoHpV74tOFw= +modernc.org/mathutil v1.5.0 h1:rV0Ko/6SfM+8G+yKiyI830l3Wuz1zRutdslNoQ0kfiQ= +modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= +modernc.org/memory v1.5.0 h1:N+/8c5rE6EqugZwHii4IFsaJ7MUhoWX07J5tC/iI5Ds= +modernc.org/memory v1.5.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU= +modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4= +modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= +modernc.org/sqlite v1.21.0 h1:4aP4MdUf15i3R3M2mx6Q90WHKz3nZLoz96zlB6tNdow= +modernc.org/sqlite v1.21.0/go.mod h1:XwQ0wZPIh1iKb5mkvCJ3szzbhk+tykC8ZWqTRTgYRwI= +modernc.org/strutil v1.1.3 h1:fNMm+oJklMGYfU9Ylcywl0CO5O6nTfaowNsh2wpPjzY= +modernc.org/strutil v1.1.3/go.mod h1:MEHNA7PdEnEwLvspRMtWTNnp2nnyvMfkimT1NKNAGbw= +modernc.org/tcl v1.15.1 h1:mOQwiEK4p7HruMZcwKTZPw/aqtGM4aY00uzWhlKKYws= +modernc.org/tcl v1.15.1/go.mod h1:aEjeGJX2gz1oWKOLDVZ2tnEWLUrIn8H+GFu+akoDhqs= +modernc.org/token v1.0.1 h1:A3qvTqOwexpfZZeyI0FeGPDlSWX5pjZu9hF4lU+EKWg= +modernc.org/token v1.0.1/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= +modernc.org/z v1.7.0 h1:xkDw/KepgEjeizO2sNco+hqYkU12taxQFqPEmgm1GWE= +modernc.org/z v1.7.0/go.mod h1:hVdgNMh8ggTuRG1rGU8x+xGRFfiQUIAw0ZqlPy8+HyQ= diff --git a/v3/examples/plugins/icon.ico b/v3/examples/plugins/icon.ico new file mode 100644 index 000000000..bfa0690b7 Binary files /dev/null and b/v3/examples/plugins/icon.ico differ diff --git a/v3/examples/plugins/icons.icns b/v3/examples/plugins/icons.icns new file mode 100644 index 000000000..1b5bd4c86 Binary files /dev/null and b/v3/examples/plugins/icons.icns differ diff --git a/v3/examples/plugins/main.go b/v3/examples/plugins/main.go new file mode 100644 index 000000000..fe02b81f7 --- /dev/null +++ b/v3/examples/plugins/main.go @@ -0,0 +1,63 @@ +package main + +import ( + "embed" + "os" + "plugin_demo/plugins/hashes" + + "github.com/wailsapp/wails/v3/pkg/application" + "github.com/wailsapp/wails/v3/plugins/experimental/server" + "github.com/wailsapp/wails/v3/plugins/kvstore" + "github.com/wailsapp/wails/v3/plugins/log" + "github.com/wailsapp/wails/v3/plugins/single_instance" + "github.com/wailsapp/wails/v3/plugins/sqlite" + "github.com/wailsapp/wails/v3/plugins/start_at_login" +) + +//go:embed assets/* +var assets embed.FS + +func main() { + + app := application.New(application.Options{ + Name: "Plugin Demo", + Description: "A demo of the plugins API", + Mac: application.MacOptions{ + ApplicationShouldTerminateAfterLastWindowClosed: true, + }, + Plugins: map[string]application.Plugin{ + "hashes": hashes.NewPlugin(), + "log": log.NewPlugin(), + "sqlite": sqlite.NewPlugin(&sqlite.Config{ + DBFile: "test.db", + }), + "kvstore": kvstore.NewPlugin(&kvstore.Config{ + Filename: "store.json", + AutoSave: true, + }), + "server": server.NewPlugin(&server.Config{ + Enabled: true, + Port: 34115, + }), + "single_instance": single_instance.NewPlugin(&single_instance.Config{ + // When true, the original app will be activated when a second instance is launched + ActivateAppOnSubsequentLaunch: true, + }), + "start_at_login": start_at_login.NewPlugin(start_at_login.Config{}), + }, + Assets: application.AssetOptions{ + FS: assets, + }, + }) + + app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{ + DevToolsEnabled: true, + }) + + err := app.Run() + + if err != nil { + println(err.Error()) + os.Exit(1) + } +} diff --git a/v3/examples/plugins/plugins/hashes/README.md b/v3/examples/plugins/plugins/hashes/README.md new file mode 100644 index 000000000..054f5f451 --- /dev/null +++ b/v3/examples/plugins/plugins/hashes/README.md @@ -0,0 +1,31 @@ +# Hashes Plugin + +This example plugin provides a way to generate hashes of strings. + +## Usage + +Add the plugin to the `Plugins` option in the Applications options: + +```go + Plugins: map[string]application.Plugin{ + "hashes": hashes.NewPlugin(), + }, +``` + +You can then call the Generate method from the frontend: + +```js + wails.Plugin("hashes","Generate","hello world").then((result) => console.log(result)) +``` + +This method returns a struct with the following fields: + +```typescript + interface Hashes { + md5: string; + sha1: string; + sha256: string; + } +``` + +A TypeScript definition file is provided for this interface. diff --git a/v3/examples/plugins/plugins/hashes/hashes.d.ts b/v3/examples/plugins/plugins/hashes/hashes.d.ts new file mode 100644 index 000000000..72b88e0f4 --- /dev/null +++ b/v3/examples/plugins/plugins/hashes/hashes.d.ts @@ -0,0 +1,8 @@ + +export namespace HashesPlugin { + export interface Hashes { + md5: string; + sha1: string; + sha256: string; + } +} \ No newline at end of file diff --git a/v3/examples/plugins/plugins/hashes/hashes.js b/v3/examples/plugins/plugins/hashes/hashes.js new file mode 100644 index 000000000..f9f8cf3b0 --- /dev/null +++ b/v3/examples/plugins/plugins/hashes/hashes.js @@ -0,0 +1,4 @@ +// Generate takes a string and returns a number of hashes for it +export function Generate(input) { + return wails.Plugin("hashes","Generate",input); +} \ No newline at end of file diff --git a/v3/examples/plugins/plugins/hashes/plugin.go b/v3/examples/plugins/plugins/hashes/plugin.go new file mode 100644 index 000000000..b3b842690 --- /dev/null +++ b/v3/examples/plugins/plugins/hashes/plugin.go @@ -0,0 +1,56 @@ +package hashes + +import ( + "crypto/md5" + "crypto/sha1" + "crypto/sha256" + "encoding/hex" +) + +// ---------------- Plugin Setup ---------------- + +type Plugin struct{} + +func NewPlugin() *Plugin { + return &Plugin{} +} + +func (r *Plugin) Shutdown() {} + +func (r *Plugin) Name() string { + return "Hashes Plugin" +} + +func (r *Plugin) Init() error { + return nil +} + +func (r *Plugin) CallableByJS() []string { + return []string{ + "Generate", + } +} + +func (r *Plugin) InjectJS() string { + return "" +} + +// ---------------- Plugin Methods ---------------- + +type Hashes struct { + MD5 string `json:"md5"` + SHA1 string `json:"sha1"` + SHA256 string `json:"sha256"` +} + +func (r *Plugin) Generate(s string) Hashes { + md5Hash := md5.Sum([]byte(s)) + sha1Hash := sha1.Sum([]byte(s)) + sha256Hash := sha256.Sum256([]byte(s)) + + return Hashes{ + MD5: hex.EncodeToString(md5Hash[:]), + SHA1: hex.EncodeToString(sha1Hash[:]), + SHA256: hex.EncodeToString(sha256Hash[:]), + } +} diff --git a/v3/examples/plugins/plugins/hashes/plugin.toml b/v3/examples/plugins/plugins/hashes/plugin.toml new file mode 100644 index 000000000..7835721be --- /dev/null +++ b/v3/examples/plugins/plugins/hashes/plugin.toml @@ -0,0 +1,10 @@ +# This is the plugin definition file for the "Hashes" plugin. + +Name = "Hashes" +Description = "Provides a method to generate a number of hashes." +Author = "Lea Anthony" +Version = "v1.0.0" +Website = "https://wails.io" +License = "MIT" + + diff --git a/v3/examples/plugins/store.json b/v3/examples/plugins/store.json new file mode 100644 index 000000000..948bb52b0 --- /dev/null +++ b/v3/examples/plugins/store.json @@ -0,0 +1 @@ +{"url2":"https://reddit.com"} \ No newline at end of file diff --git a/v3/examples/plugins/test.db b/v3/examples/plugins/test.db new file mode 100644 index 000000000..156136694 Binary files /dev/null and b/v3/examples/plugins/test.db differ diff --git a/v3/examples/screen/README.md b/v3/examples/screen/README.md new file mode 100644 index 000000000..1f922f341 --- /dev/null +++ b/v3/examples/screen/README.md @@ -0,0 +1,19 @@ +# Screen Example + +This example will detect all attached screens and display their details. + +## Running the example + +To run the example, simply run the following command: + +```bash +go run . +``` + +# Status + +| Platform | Status | +|----------|---------| +| Mac | Working | +| Windows | Working | +| Linux | | diff --git a/v3/examples/screen/assets/index.html b/v3/examples/screen/assets/index.html new file mode 100644 index 000000000..4b385ada1 --- /dev/null +++ b/v3/examples/screen/assets/index.html @@ -0,0 +1,66 @@ + + + + + Screens Demo + + + + + + + \ No newline at end of file diff --git a/v3/examples/screen/main.go b/v3/examples/screen/main.go new file mode 100644 index 000000000..3f80e2106 --- /dev/null +++ b/v3/examples/screen/main.go @@ -0,0 +1,49 @@ +package main + +import ( + "embed" + _ "embed" + "log" + + "github.com/wailsapp/wails/v3/pkg/application" +) + +//go:embed assets/* +var assets embed.FS + +func main() { + + app := application.New(application.Options{ + Name: "Screen Demo", + Description: "A demo of the Screen API", + Mac: application.MacOptions{ + ApplicationShouldTerminateAfterLastWindowClosed: true, + }, + Windows: application.WindowsOptions{ + WndProcInterceptor: nil, + DisableQuitOnLastWindowClosed: false, + WebviewUserDataPath: "", + WebviewBrowserPath: "", + }, + Assets: application.AssetOptions{ + FS: assets, + }, + }) + + app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{ + Title: "Screen Demo", + Width: 800, + Height: 600, + Mac: application.MacWindow{ + Backdrop: application.MacBackdropTranslucent, + TitleBar: application.MacTitleBarHiddenInsetUnified, + InvisibleTitleBarHeight: 50, + }, + }) + + err := app.Run() + + if err != nil { + log.Fatal(err.Error()) + } +} diff --git a/v3/examples/server/README.md b/v3/examples/server/README.md new file mode 100644 index 000000000..301188688 --- /dev/null +++ b/v3/examples/server/README.md @@ -0,0 +1,3 @@ +# Server Example + +This example is not ready for testing yet. \ No newline at end of file diff --git a/v3/examples/server/Taskfile.yml b/v3/examples/server/Taskfile.yml new file mode 100644 index 000000000..de33014d5 --- /dev/null +++ b/v3/examples/server/Taskfile.yml @@ -0,0 +1,41 @@ +version: '3' + +tasks: + + pre-build: + summary: Pre-build hooks + + post-build: + summary: Post-build hooks + + build: + summary: Builds the application + cmds: + - task: pre-build + - go build -gcflags=all="-N -l" -o bin/testapp main.go + - task: post-build + env: + GO_CFLAGS: "-mmacosx-version-min=10.13" + GO_LDFLAGS: "-mmacosx-version-min=10.13" + + generate-icons: + summary: Generates Windows `.ico` and Mac `.icns` files from an image + cmds: + # Generates both .ico and .icns files + - wails generate icons -input build/appicon.png + + build-prod: + summary: Creates a production build of the application + cmds: + - go build -tags production -ldflags="-w -s" -o bin/testapp + + package-darwin: + summary: Packages a production build of the application into a `.app` bundle + deps: + - build-prod + - generate-icons + cmds: + - mkdir -p buildtest.app/Contents/{MacOS,Resources} + - cp build/icons.icns buildtest.app/Contents/Resources + - cp bin/testapp buildtest.app/Contents/MacOS + - cp build/Info.plist buildtest.app/Contents diff --git a/v3/examples/server/assets/index.html b/v3/examples/server/assets/index.html new file mode 100644 index 000000000..f77c18294 --- /dev/null +++ b/v3/examples/server/assets/index.html @@ -0,0 +1,10 @@ + + + + + Title + + +HELLO! + + \ No newline at end of file diff --git a/v3/examples/server/bin/testapp b/v3/examples/server/bin/testapp new file mode 100755 index 000000000..b995799c9 Binary files /dev/null and b/v3/examples/server/bin/testapp differ diff --git a/v3/examples/server/build/Info.dev.plist b/v3/examples/server/build/Info.dev.plist new file mode 100644 index 000000000..d6d28b179 --- /dev/null +++ b/v3/examples/server/build/Info.dev.plist @@ -0,0 +1,35 @@ + + + + CFBundlePackageType + APPL + CFBundleName + My App + CFBundleExecutable + app + CFBundleIdentifier + com.wails.app + CFBundleVersion + v1.0.0 + CFBundleGetInfoString + The ultimate thing + CFBundleShortVersionString + v1 + CFBundleIconFile + icons + LSMinimumSystemVersion + 10.13.0 + NSHighResolutionCapable + true + NSHumanReadableCopyright + (c) Me + NSAppTransportSecurity + + NSAllowsLocalNetworking + + + + + + + \ No newline at end of file diff --git a/v3/examples/server/build/Info.plist b/v3/examples/server/build/Info.plist new file mode 100644 index 000000000..ab571ad4f --- /dev/null +++ b/v3/examples/server/build/Info.plist @@ -0,0 +1,27 @@ + + + + CFBundlePackageType + APPL + CFBundleName + My App + CFBundleExecutable + testapp + CFBundleIdentifier + com.wails.app + CFBundleVersion + v1.0.0 + CFBundleGetInfoString + The ultimate thing + CFBundleShortVersionString + v1 + CFBundleIconFile + icons + LSMinimumSystemVersion + 10.13.0 + NSHighResolutionCapable + true + NSHumanReadableCopyright + (c) Me + + \ No newline at end of file diff --git a/v3/examples/server/build/appicon.png b/v3/examples/server/build/appicon.png new file mode 100644 index 000000000..e69de29bb diff --git a/v3/examples/server/build/icons.icns b/v3/examples/server/build/icons.icns new file mode 100644 index 000000000..e69de29bb diff --git a/v3/examples/server/build/icons.ico b/v3/examples/server/build/icons.ico new file mode 100644 index 000000000..e69de29bb diff --git a/v3/examples/server/build/info.json b/v3/examples/server/build/info.json new file mode 100644 index 000000000..1005eb5cb --- /dev/null +++ b/v3/examples/server/build/info.json @@ -0,0 +1,15 @@ +{ + "fixed": { + "file_version": "v1.0.0" + }, + "info": { + "0000": { + "ProductVersion": "v1.0.0", + "CompanyName": "My Company Name", + "FileDescription": "A thing that does a thing", + "LegalCopyright": "(c) 2023 My Company Name", + "ProductName": "My Product Name", + "Comments": "This is a comment" + } + } +} \ No newline at end of file diff --git a/v3/examples/server/build/wails.exe.manifest b/v3/examples/server/build/wails.exe.manifest new file mode 100644 index 000000000..fb1ce5bde --- /dev/null +++ b/v3/examples/server/build/wails.exe.manifest @@ -0,0 +1,15 @@ + + + + + + + + + + + true/pm + permonitorv2,permonitor + + + \ No newline at end of file diff --git a/v3/examples/server/go.mod b/v3/examples/server/go.mod new file mode 100644 index 000000000..0f7a3a9b7 --- /dev/null +++ b/v3/examples/server/go.mod @@ -0,0 +1,50 @@ +module server_demo + +go 1.21 + +toolchain go1.21.0 + +require github.com/wailsapp/wails/v3 v3.0.0-alpha.0 + +require ( + github.com/Microsoft/go-winio v0.4.16 // indirect + github.com/bep/debounce v1.2.1 // indirect + github.com/ebitengine/purego v0.4.0-alpha.4 // indirect + github.com/emirpasic/gods v1.12.0 // indirect + github.com/go-git/gcfg v1.5.0 // indirect + github.com/go-git/go-billy/v5 v5.2.0 // indirect + github.com/go-git/go-git/v5 v5.3.0 // indirect + github.com/go-ole/go-ole v1.2.6 // indirect + github.com/google/uuid v1.3.0 // indirect + github.com/imdario/mergo v0.3.12 // 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/json-iterator/go v1.1.12 // indirect + github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351 // 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.0 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.19 // 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/rivo/uniseg v0.4.4 // indirect + github.com/rogpeppe/go-internal v1.10.1-0.20230524175051-ec119421bb97 // indirect + github.com/samber/lo v1.38.1 // indirect + github.com/sergi/go-diff v1.2.0 // indirect + github.com/stretchr/testify v1.8.4 // indirect + github.com/wailsapp/go-webview2 v1.0.9 // indirect + github.com/wailsapp/mimetype v1.4.1 // indirect + github.com/xanzy/ssh-agent v0.3.0 // indirect + golang.org/x/crypto v0.9.0 // indirect + golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df // indirect + golang.org/x/net v0.10.0 // indirect + golang.org/x/sys v0.13.0 // indirect + gopkg.in/warnings.v0 v0.1.2 // indirect +) + +replace github.com/wailsapp/wails/v3 => ../.. + +replace github.com/wailsapp/wails/v2 => ../../../v2 diff --git a/v3/examples/server/go.sum b/v3/examples/server/go.sum new file mode 100644 index 000000000..ab2b768d2 --- /dev/null +++ b/v3/examples/server/go.sum @@ -0,0 +1,159 @@ +github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= +github.com/Microsoft/go-winio v0.4.16 h1:FtSW/jqD+l4ba5iPBj9CODVtgfYAD8w2wS923g/cFDk= +github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0= +github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7 h1:uSoVVbwJiQipAclBbw+8quDsfcvFjOpI5iCf4p/cqCs= +github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/ghQa61ZWa/C2Aw3RkjiTBOix7dkqa1VLIs= +github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo4JG6LR5AXSUEsOjtdm0kw0FtQtMJA= +github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= +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/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +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/emirpasic/gods v1.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg= +github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o= +github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= +github.com/gliderlabs/ssh v0.2.2 h1:6zsha5zo/TWhRhwqCD3+EarCAgZ2yN28ipRnGPnwkI0= +github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= +github.com/go-git/gcfg v1.5.0 h1:Q5ViNfGF8zFgyJWPqYwA7qGFoMTEiBmdlkcfRmpIMa4= +github.com/go-git/gcfg v1.5.0/go.mod h1:5m20vg6GwYabIxaOonVkTdrILxQMpEShl1xiMF4ua+E= +github.com/go-git/go-billy/v5 v5.0.0/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0= +github.com/go-git/go-billy/v5 v5.1.0/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0= +github.com/go-git/go-billy/v5 v5.2.0 h1:GcoouCP9J+5slw2uXAocL70z8ml4A8B/H8nEPt6CLPk= +github.com/go-git/go-billy/v5 v5.2.0/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0= +github.com/go-git/go-git-fixtures/v4 v4.0.2-0.20200613231340-f56387b50c12 h1:PbKy9zOy4aAKrJ5pibIRpVO2BXnK1Tlcg+caKI7Ox5M= +github.com/go-git/go-git-fixtures/v4 v4.0.2-0.20200613231340-f56387b50c12/go.mod h1:m+ICp2rF3jDhFgEZ/8yziagdT1C+ZpZcrJjappBCDSw= +github.com/go-git/go-git/v5 v5.3.0 h1:8WKMtJR2j8RntEXR/uvTKagfEt4GYlwQ7mntE4+0GWc= +github.com/go-git/go-git/v5 v5.3.0/go.mod h1:xdX4bWJ48aOrdhnl2XqHYstHbbp6+LFS4r4X+lNVprw= +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/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU= +github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= +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/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4= +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/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351 h1:DowS9hvgyYSX4TO5NpyC606/Z4SxnNYbT+WX27or6Ck= +github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +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.0 h1:2n0d2BwPVXSUq5yhe8lJPHdxevE2qK5G99PMStMZMaI= +github.com/leaanthony/u v1.1.0/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/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-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= +github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/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/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +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= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis= +github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= +github.com/rogpeppe/go-internal v1.10.1-0.20230524175051-ec119421bb97 h1:3RPlVWzZ/PDqmVuf/FKHARG5EMid/tl7cv54Sw/QRVY= +github.com/rogpeppe/go-internal v1.10.1-0.20230524175051-ec119421bb97/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/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= +github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/wailsapp/go-webview2 v1.0.7 h1:s95+7irJsAsTy1RsjJ6N0cYX7tZ4gP7Uzawds0L2urs= +github.com/wailsapp/go-webview2 v1.0.7/go.mod h1:Uk2BePfCRzttBBjFrBmqKGJd41P6QIHeV9kTgIeOZNo= +github.com/wailsapp/go-webview2 v1.0.8-0.20231010092311-42cbb98eb53b/go.mod h1:Uk2BePfCRzttBBjFrBmqKGJd41P6QIHeV9kTgIeOZNo= +github.com/wailsapp/go-webview2 v1.0.8/go.mod h1:Uk2BePfCRzttBBjFrBmqKGJd41P6QIHeV9kTgIeOZNo= +github.com/wailsapp/go-webview2 v1.0.9/go.mod h1:Uk2BePfCRzttBBjFrBmqKGJd41P6QIHeV9kTgIeOZNo= +github.com/wailsapp/mimetype v1.4.1 h1:pQN9ycO7uo4vsUUuPeHEYoUkLVkaRntMnHJxVwYhwHs= +github.com/wailsapp/mimetype v1.4.1/go.mod h1:9aV5k31bBOv5z6u+QP8TltzvNGJPmNJD4XlAL3U+j3o= +github.com/xanzy/ssh-agent v0.3.0 h1:wUMzuKtKilRgBAD1sUb8gOwwRr2FGoBVumcjoOACClI= +github.com/xanzy/ssh-agent v0.3.0/go.mod h1:3s9xbODqPuuhK9JV1R321M/FlMZSBvE5aY6eAcqrDh0= +golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g= +golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0= +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/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= +golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +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= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/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-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= +golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +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= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +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-20200227125254-8fa46927fb4f/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/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.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/v3/examples/server/main.go b/v3/examples/server/main.go new file mode 100644 index 000000000..f9d8633ed --- /dev/null +++ b/v3/examples/server/main.go @@ -0,0 +1,55 @@ +package main + +import ( + "embed" + "os" + "time" + + "github.com/wailsapp/wails/v3/pkg/application" + "github.com/wailsapp/wails/v3/plugins/experimental/server" + "github.com/wailsapp/wails/v3/plugins/log" +) + +//go:embed assets/* +var assets embed.FS + +func main() { + + app := application.New(application.Options{ + Name: "Server Demo", + Description: "server only demo of the plugins API", + Mac: application.MacOptions{ + ApplicationShouldTerminateAfterLastWindowClosed: true, + }, + Plugins: map[string]application.Plugin{ + "log": log.NewPlugin(), + "server": server.NewPlugin(&server.Config{ + Host: "0.0.0.0", + Port: 34115, + Enabled: true, + }), + }, + Assets: application.AssetOptions{ + FS: assets, + }, + }) + go func() { + for { + app.Events.Emit(&application.WailsEvent{ + Name: "ping", + Data: "are you alive?", + }) + time.Sleep(10 * time.Second) + } + }() + + // window := app.NewWebviewWindow() + // window.ToggleDevTools() + + err := app.Run() + + if err != nil { + println(err.Error()) + os.Exit(1) + } +} diff --git a/v3/examples/systray/README.md b/v3/examples/systray/README.md new file mode 100644 index 000000000..3ff940f78 --- /dev/null +++ b/v3/examples/systray/README.md @@ -0,0 +1,17 @@ +# Systray Example + +This example creates a system tray with an attached window and a menu. +The window is hidden by default and toggled by left-clicking on the systray icon. +The window will hide automatically when it loses focus. +Right-clicking on the systray icon will show the menu. + +On Windows, if the icon is in the notification flyout, +then the window will be shown in the bottom right corner. + +# Status + +| Platform | Status | +|----------|---------| +| Mac | | +| Windows | Working | +| Linux | | diff --git a/v3/examples/systray/main.go b/v3/examples/systray/main.go new file mode 100644 index 000000000..592ce58c1 --- /dev/null +++ b/v3/examples/systray/main.go @@ -0,0 +1,102 @@ +package main + +import ( + _ "embed" + "fmt" + "log" + "runtime" + + "github.com/wailsapp/wails/v3/pkg/application" + "github.com/wailsapp/wails/v3/pkg/icons" +) + +func main() { + app := application.New(application.Options{ + Name: "Systray Demo", + Description: "A demo of the Systray API", + Assets: application.AlphaAssets, + Mac: application.MacOptions{ + ActivationPolicy: application.ActivationPolicyAccessory, + }, + }) + + systemTray := app.NewSystemTray() + + window := app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{ + Width: 500, + Height: 800, + Frameless: true, + AlwaysOnTop: true, + Hidden: true, + DisableResize: true, + ShouldClose: func(window *application.WebviewWindow) bool { + window.Hide() + return false + }, + Windows: application.WindowsWindow{ + HiddenOnTaskbar: true, + }, + KeyBindings: map[string]func(window *application.WebviewWindow){ + "F12": func(window *application.WebviewWindow) { + systemTray.OpenMenu() + }, + }, + }) + + if runtime.GOOS == "darwin" { + systemTray.SetTemplateIcon(icons.SystrayMacTemplate) + } + + myMenu := app.NewMenu() + myMenu.Add("Wails").SetBitmap(icons.WailsLogoBlackTransparent).SetEnabled(false) + + myMenu.Add("Hello World!").OnClick(func(ctx *application.Context) { + println("Hello World!") + q := application.QuestionDialog().SetTitle("Ready?").SetMessage("Are you feeling ready?") + q.AddButton("Yes").OnClick(func() { + println("Awesome!") + }) + q.AddButton("No").SetAsDefault().OnClick(func() { + println("Boo!") + }) + q.Show() + }) + subMenu := myMenu.AddSubmenu("Submenu") + subMenu.Add("Click me!").OnClick(func(ctx *application.Context) { + ctx.ClickedMenuItem().SetLabel("Clicked!") + }) + myMenu.AddSeparator() + myMenu.AddCheckbox("Checked", true).OnClick(func(ctx *application.Context) { + println("Checked: ", ctx.ClickedMenuItem().Checked()) + application.InfoDialog().SetTitle("Hello World!").SetMessage("Hello World!").Show() + }) + myMenu.Add("Enabled").OnClick(func(ctx *application.Context) { + println("Click me!") + ctx.ClickedMenuItem().SetLabel("Disabled!").SetEnabled(false) + }) + myMenu.AddSeparator() + // Callbacks can be shared. This is useful for radio groups + radioCallback := func(ctx *application.Context) { + menuItem := ctx.ClickedMenuItem() + menuItem.SetLabel(menuItem.Label() + "!") + fmt.Println("radioCallback: ") + } + + // Radio groups are created implicitly by placing radio items next to each other in a menu + myMenu.AddRadio("Radio 1", true).OnClick(radioCallback) + myMenu.AddRadio("Radio 2", false).OnClick(radioCallback) + myMenu.AddRadio("Radio 3", false).OnClick(radioCallback) + + myMenu.AddSeparator() + myMenu.Add("Quit").OnClick(func(ctx *application.Context) { + app.Quit() + }) + + systemTray.SetMenu(myMenu) + systemTray.AttachWindow(window).WindowOffset(5) + + err := app.Run() + if err != nil { + log.Fatal(err) + } +} diff --git a/v3/examples/video/README.md b/v3/examples/video/README.md new file mode 100644 index 000000000..012469afb --- /dev/null +++ b/v3/examples/video/README.md @@ -0,0 +1,19 @@ +# Video Example + +This example shows support for HTML5 video. + +## Running the example + +To run the example, simply run the following command: + +```bash +go run . +``` + +# Status + +| Platform | Status | +|----------|---------| +| Mac | | +| Windows | Working | +| Linux | | diff --git a/v3/examples/video/go.mod b/v3/examples/video/go.mod new file mode 100644 index 000000000..edfa98f91 --- /dev/null +++ b/v3/examples/video/go.mod @@ -0,0 +1,47 @@ +module frameless + +go 1.21 + +toolchain go1.21.0 + +require github.com/wailsapp/wails/v3 v3.0.0-alpha.0 + +require ( + github.com/Microsoft/go-winio v0.4.16 // indirect + github.com/bep/debounce v1.2.1 // indirect + github.com/ebitengine/purego v0.4.0-alpha.4 // indirect + github.com/emirpasic/gods v1.12.0 // indirect + github.com/go-git/gcfg v1.5.0 // indirect + github.com/go-git/go-billy/v5 v5.2.0 // indirect + github.com/go-git/go-git/v5 v5.3.0 // indirect + github.com/go-ole/go-ole v1.2.6 // indirect + github.com/godbus/dbus/v5 v5.1.0 // indirect + github.com/google/uuid v1.3.0 // indirect + github.com/imdario/mergo v0.3.12 // 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/json-iterator/go v1.1.12 // indirect + github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351 // 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.0 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.19 // 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/rivo/uniseg v0.4.4 // indirect + github.com/samber/lo v1.38.1 // indirect + github.com/sergi/go-diff v1.2.0 // indirect + github.com/wailsapp/go-webview2 v1.0.9 // indirect + github.com/wailsapp/mimetype v1.4.1 // indirect + github.com/xanzy/ssh-agent v0.3.0 // indirect + golang.org/x/crypto v0.9.0 // indirect + golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df // indirect + golang.org/x/net v0.10.0 // indirect + golang.org/x/sys v0.13.0 // indirect + gopkg.in/warnings.v0 v0.1.2 // indirect +) + +replace github.com/wailsapp/wails/v3 => ../.. diff --git a/v3/examples/video/go.sum b/v3/examples/video/go.sum new file mode 100644 index 000000000..169c421b9 --- /dev/null +++ b/v3/examples/video/go.sum @@ -0,0 +1,154 @@ +github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= +github.com/Microsoft/go-winio v0.4.16 h1:FtSW/jqD+l4ba5iPBj9CODVtgfYAD8w2wS923g/cFDk= +github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0= +github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7 h1:uSoVVbwJiQipAclBbw+8quDsfcvFjOpI5iCf4p/cqCs= +github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/ghQa61ZWa/C2Aw3RkjiTBOix7dkqa1VLIs= +github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo4JG6LR5AXSUEsOjtdm0kw0FtQtMJA= +github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= +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/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +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/emirpasic/gods v1.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg= +github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o= +github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= +github.com/gliderlabs/ssh v0.2.2 h1:6zsha5zo/TWhRhwqCD3+EarCAgZ2yN28ipRnGPnwkI0= +github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= +github.com/go-git/gcfg v1.5.0 h1:Q5ViNfGF8zFgyJWPqYwA7qGFoMTEiBmdlkcfRmpIMa4= +github.com/go-git/gcfg v1.5.0/go.mod h1:5m20vg6GwYabIxaOonVkTdrILxQMpEShl1xiMF4ua+E= +github.com/go-git/go-billy/v5 v5.0.0/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0= +github.com/go-git/go-billy/v5 v5.1.0/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0= +github.com/go-git/go-billy/v5 v5.2.0 h1:GcoouCP9J+5slw2uXAocL70z8ml4A8B/H8nEPt6CLPk= +github.com/go-git/go-billy/v5 v5.2.0/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0= +github.com/go-git/go-git-fixtures/v4 v4.0.2-0.20200613231340-f56387b50c12 h1:PbKy9zOy4aAKrJ5pibIRpVO2BXnK1Tlcg+caKI7Ox5M= +github.com/go-git/go-git-fixtures/v4 v4.0.2-0.20200613231340-f56387b50c12/go.mod h1:m+ICp2rF3jDhFgEZ/8yziagdT1C+ZpZcrJjappBCDSw= +github.com/go-git/go-git/v5 v5.3.0 h1:8WKMtJR2j8RntEXR/uvTKagfEt4GYlwQ7mntE4+0GWc= +github.com/go-git/go-git/v5 v5.3.0/go.mod h1:xdX4bWJ48aOrdhnl2XqHYstHbbp6+LFS4r4X+lNVprw= +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/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= +github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU= +github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= +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/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4= +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/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351 h1:DowS9hvgyYSX4TO5NpyC606/Z4SxnNYbT+WX27or6Ck= +github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +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.0 h1:2n0d2BwPVXSUq5yhe8lJPHdxevE2qK5G99PMStMZMaI= +github.com/leaanthony/u v1.1.0/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/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-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= +github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/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/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/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis= +github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/samber/lo v1.38.1 h1:j2XEAqXKb09Am4ebOg31SpvzUTTs6EN3VfgeLUhPdXM= +github.com/samber/lo v1.38.1/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA= +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= +github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/wailsapp/go-webview2 v1.0.9 h1:lrU+q0cf1wgLdR69rN+ZnRtMJNaJRrcQ4ELxoO7/xjs= +github.com/wailsapp/go-webview2 v1.0.9/go.mod h1:Uk2BePfCRzttBBjFrBmqKGJd41P6QIHeV9kTgIeOZNo= +github.com/wailsapp/mimetype v1.4.1 h1:pQN9ycO7uo4vsUUuPeHEYoUkLVkaRntMnHJxVwYhwHs= +github.com/wailsapp/mimetype v1.4.1/go.mod h1:9aV5k31bBOv5z6u+QP8TltzvNGJPmNJD4XlAL3U+j3o= +github.com/xanzy/ssh-agent v0.3.0 h1:wUMzuKtKilRgBAD1sUb8gOwwRr2FGoBVumcjoOACClI= +github.com/xanzy/ssh-agent v0.3.0/go.mod h1:3s9xbODqPuuhK9JV1R321M/FlMZSBvE5aY6eAcqrDh0= +golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g= +golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0= +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/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= +golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +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= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/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-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= +golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +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= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +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-20200227125254-8fa46927fb4f/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/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.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= diff --git a/v3/examples/video/main.go b/v3/examples/video/main.go new file mode 100644 index 000000000..10990a24a --- /dev/null +++ b/v3/examples/video/main.go @@ -0,0 +1,47 @@ +package main + +import ( + _ "embed" + "github.com/wailsapp/wails/v3/pkg/events" + "log" + + "github.com/wailsapp/wails/v3/pkg/application" +) + +func main() { + app := application.New(application.Options{ + Name: "Video Demo", + Description: "A demo of HTML5 Video API", + Assets: application.AlphaAssets, + Mac: application.MacOptions{ + ApplicationShouldTerminateAfterLastWindowClosed: true, + }, + Windows: application.WindowsOptions{ + WndProcInterceptor: nil, + DisableQuitOnLastWindowClosed: false, + WebviewUserDataPath: "", + WebviewBrowserPath: "", + }, + }) + app.On(events.Mac.ApplicationDidFinishLaunching, func(event *application.Event) { + log.Println("ApplicationDidFinishLaunching") + }) + + app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{ + BackgroundColour: application.NewRGB(33, 37, 41), + Mac: application.MacWindow{ + DisableShadow: true, + WebviewPreferences: application.MacWebviewPreferences{ + FullscreenEnabled: application.Enabled, + }, + }, + HTML: "", + }) + + err := app.Run() + + if err != nil { + log.Fatal(err) + } + +} diff --git a/v3/examples/window/README.md b/v3/examples/window/README.md new file mode 100644 index 000000000..2f1c7e810 --- /dev/null +++ b/v3/examples/window/README.md @@ -0,0 +1,19 @@ +# Window Example + +This example is a demonstration of the Windows API. + +## Running the example + +To run the example, simply run the following command: + +```bash +go run . +``` + +# Status + +| Platform | Status | +|----------|---------| +| Mac | | +| Windows | Working | +| Linux | | diff --git a/v3/examples/window/main.go b/v3/examples/window/main.go new file mode 100644 index 000000000..ccf9e05c7 --- /dev/null +++ b/v3/examples/window/main.go @@ -0,0 +1,417 @@ +package main + +import ( + _ "embed" + "fmt" + "log" + "math/rand" + "runtime" + "strconv" + "time" + + "github.com/samber/lo" + "github.com/wailsapp/wails/v3/pkg/events" + + "github.com/wailsapp/wails/v3/pkg/application" +) + +func main() { + app := application.New(application.Options{ + Name: "WebviewWindow Demo", + Description: "A demo of the WebviewWindow API", + Assets: application.AlphaAssets, + Mac: application.MacOptions{ + ApplicationShouldTerminateAfterLastWindowClosed: true, + }, + }) + app.On(events.Mac.ApplicationDidFinishLaunching, func(event *application.Event) { + log.Println("ApplicationDidFinishLaunching") + }) + + var hiddenWindows []*application.WebviewWindow + + currentWindow := func(fn func(window *application.WebviewWindow)) { + if app.CurrentWindow() != nil { + fn(app.CurrentWindow()) + } else { + println("Current WebviewWindow is nil") + } + } + + // Create a custom menu + menu := app.NewMenu() + menu.AddRole(application.AppMenu) + + windowCounter := 1 + + // Let's make a "Demo" menu + myMenu := menu.AddSubmenu("New") + + myMenu.Add("New WebviewWindow"). + SetAccelerator("CmdOrCtrl+N"). + OnClick(func(ctx *application.Context) { + app.NewWebviewWindow(). + SetTitle("WebviewWindow "+strconv.Itoa(windowCounter)). + SetRelativePosition(rand.Intn(1000), rand.Intn(800)). + SetURL("https://wails.io"). + Show() + windowCounter++ + }) + myMenu.Add("New WebviewWindow (Hides on Close one time)"). + SetAccelerator("CmdOrCtrl+H"). + OnClick(func(ctx *application.Context) { + app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{ + // This will be called when the user clicks the close button + // on the window. It will hide the window for 5 seconds. + // If the user clicks the close button again, the window will + // close. + ShouldClose: func(window *application.WebviewWindow) bool { + if !lo.Contains(hiddenWindows, window) { + hiddenWindows = append(hiddenWindows, window) + go func() { + time.Sleep(5 * time.Second) + window.Show() + }() + window.Hide() + return false + } + // Remove the window from the hiddenWindows list + hiddenWindows = lo.Without(hiddenWindows, window) + return true + }, + }). + SetTitle("WebviewWindow "+strconv.Itoa(windowCounter)). + SetRelativePosition(rand.Intn(1000), rand.Intn(800)). + SetURL("https://wails.io"). + Show() + windowCounter++ + }) + myMenu.Add("New Frameless WebviewWindow"). + SetAccelerator("CmdOrCtrl+F"). + OnClick(func(ctx *application.Context) { + app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{ + X: rand.Intn(1000), + Y: rand.Intn(800), + BackgroundColour: application.NewRGB(33, 37, 41), + Frameless: true, + Mac: application.MacWindow{ + InvisibleTitleBarHeight: 50, + }, + }).Show() + windowCounter++ + }) + myMenu.Add("New WebviewWindow (ignores mouse events"). + SetAccelerator("CmdOrCtrl+F"). + OnClick(func(ctx *application.Context) { + app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{ + HTML: "
", + X: rand.Intn(1000), + Y: rand.Intn(800), + IgnoreMouseEvents: true, + BackgroundType: application.BackgroundTypeTransparent, + Mac: application.MacWindow{ + InvisibleTitleBarHeight: 50, + }, + }).Show() + windowCounter++ + }) + if runtime.GOOS == "darwin" { + myMenu.Add("New WebviewWindow (MacTitleBarHiddenInset)"). + OnClick(func(ctx *application.Context) { + app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{ + Mac: application.MacWindow{ + TitleBar: application.MacTitleBarHiddenInset, + InvisibleTitleBarHeight: 25, + }, + }). + SetBackgroundColour(application.NewRGB(33, 37, 41)). + SetTitle("WebviewWindow "+strconv.Itoa(windowCounter)). + SetRelativePosition(rand.Intn(1000), rand.Intn(800)). + SetHTML("

A MacTitleBarHiddenInset WebviewWindow example

"). + Show() + windowCounter++ + }) + myMenu.Add("New WebviewWindow (MacTitleBarHiddenInsetUnified)"). + OnClick(func(ctx *application.Context) { + app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{ + Mac: application.MacWindow{ + TitleBar: application.MacTitleBarHiddenInsetUnified, + InvisibleTitleBarHeight: 50, + }, + }). + SetTitle("WebviewWindow "+strconv.Itoa(windowCounter)). + SetRelativePosition(rand.Intn(1000), rand.Intn(800)). + SetHTML("

A MacTitleBarHiddenInsetUnified WebviewWindow example

"). + Show() + windowCounter++ + }) + myMenu.Add("New WebviewWindow (MacTitleBarHidden)"). + OnClick(func(ctx *application.Context) { + app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{ + Mac: application.MacWindow{ + TitleBar: application.MacTitleBarHidden, + InvisibleTitleBarHeight: 25, + }, + }). + SetTitle("WebviewWindow "+strconv.Itoa(windowCounter)). + SetRelativePosition(rand.Intn(1000), rand.Intn(800)). + SetHTML("

A MacTitleBarHidden WebviewWindow example

"). + Show() + windowCounter++ + }) + } + if runtime.GOOS == "windows" { + myMenu.Add("New WebviewWindow (Mica)"). + OnClick(func(ctx *application.Context) { + app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{ + Title: "WebviewWindow " + strconv.Itoa(windowCounter), + X: rand.Intn(1000), + Y: rand.Intn(800), + BackgroundType: application.BackgroundTypeTranslucent, + HTML: "", + Windows: application.WindowsWindow{ + BackdropType: application.Mica, + }, + }).Show() + windowCounter++ + }) + myMenu.Add("New WebviewWindow (Acrylic)"). + OnClick(func(ctx *application.Context) { + app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{ + Title: "WebviewWindow " + strconv.Itoa(windowCounter), + X: rand.Intn(1000), + Y: rand.Intn(800), + BackgroundType: application.BackgroundTypeTranslucent, + HTML: "", + Windows: application.WindowsWindow{ + BackdropType: application.Acrylic, + }, + }).Show() + windowCounter++ + }) + myMenu.Add("New WebviewWindow (Tabbed)"). + OnClick(func(ctx *application.Context) { + app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{ + Title: "WebviewWindow " + strconv.Itoa(windowCounter), + X: rand.Intn(1000), + Y: rand.Intn(800), + BackgroundType: application.BackgroundTypeTranslucent, + HTML: "", + Windows: application.WindowsWindow{ + BackdropType: application.Tabbed, + }, + }).Show() + windowCounter++ + }) + } + + sizeMenu := menu.AddSubmenu("Size") + sizeMenu.Add("Set Size (800,600)").OnClick(func(ctx *application.Context) { + currentWindow(func(w *application.WebviewWindow) { + w.SetSize(800, 600) + }) + }) + + sizeMenu.Add("Set Size (Random)").OnClick(func(ctx *application.Context) { + currentWindow(func(w *application.WebviewWindow) { + w.SetSize(rand.Intn(800)+200, rand.Intn(600)+200) + }) + }) + sizeMenu.Add("Set Min Size (200,200)").OnClick(func(ctx *application.Context) { + currentWindow(func(w *application.WebviewWindow) { + w.SetMinSize(200, 200) + }) + }) + sizeMenu.Add("Set Max Size (600,600)").OnClick(func(ctx *application.Context) { + currentWindow(func(w *application.WebviewWindow) { + w.SetFullscreenButtonEnabled(false) + w.SetMaxSize(600, 600) + }) + }) + sizeMenu.Add("Get Current WebviewWindow Size").OnClick(func(ctx *application.Context) { + currentWindow(func(w *application.WebviewWindow) { + width, height := w.Size() + application.InfoDialog().SetTitle("Current WebviewWindow Size").SetMessage("Width: " + strconv.Itoa(width) + " Height: " + strconv.Itoa(height)).Show() + }) + }) + + sizeMenu.Add("Reset Min Size").OnClick(func(ctx *application.Context) { + currentWindow(func(w *application.WebviewWindow) { + w.SetMinSize(0, 0) + }) + }) + + sizeMenu.Add("Reset Max Size").OnClick(func(ctx *application.Context) { + currentWindow(func(w *application.WebviewWindow) { + w.SetMaxSize(0, 0) + w.SetFullscreenButtonEnabled(true) + }) + }) + positionMenu := menu.AddSubmenu("Position") + positionMenu.Add("Set Relative Position (0,0)").OnClick(func(ctx *application.Context) { + currentWindow(func(w *application.WebviewWindow) { + w.SetRelativePosition(0, 0) + }) + }) + positionMenu.Add("Set Relative Position (Random)").OnClick(func(ctx *application.Context) { + currentWindow(func(w *application.WebviewWindow) { + w.SetRelativePosition(rand.Intn(1000), rand.Intn(800)) + }) + }) + + positionMenu.Add("Get Relative Position").OnClick(func(ctx *application.Context) { + currentWindow(func(w *application.WebviewWindow) { + x, y := w.RelativePosition() + application.InfoDialog().SetTitle("Current WebviewWindow Position").SetMessage("X: " + strconv.Itoa(x) + " Y: " + strconv.Itoa(y)).Show() + }) + }) + + positionMenu.Add("Set Absolute Position (0,0)").OnClick(func(ctx *application.Context) { + currentWindow(func(w *application.WebviewWindow) { + w.SetAbsolutePosition(0, 0) + }) + }) + + positionMenu.Add("Set Absolute Position (Random)").OnClick(func(ctx *application.Context) { + currentWindow(func(w *application.WebviewWindow) { + w.SetAbsolutePosition(rand.Intn(1000), rand.Intn(800)) + }) + }) + + positionMenu.Add("Get Absolute Position").OnClick(func(ctx *application.Context) { + currentWindow(func(w *application.WebviewWindow) { + x, y := w.AbsolutePosition() + application.InfoDialog().SetTitle("Current WebviewWindow Position").SetMessage("X: " + strconv.Itoa(x) + " Y: " + strconv.Itoa(y)).Show() + }) + }) + + positionMenu.Add("Center").OnClick(func(ctx *application.Context) { + currentWindow(func(w *application.WebviewWindow) { + w.Center() + }) + }) + stateMenu := menu.AddSubmenu("State") + stateMenu.Add("Minimise (for 2 secs)").OnClick(func(ctx *application.Context) { + currentWindow(func(w *application.WebviewWindow) { + w.Minimise() + time.Sleep(2 * time.Second) + w.Restore() + }) + }) + stateMenu.Add("Maximise").OnClick(func(ctx *application.Context) { + currentWindow(func(w *application.WebviewWindow) { + w.Maximise() + }) + }) + stateMenu.Add("Fullscreen").OnClick(func(ctx *application.Context) { + currentWindow(func(w *application.WebviewWindow) { + w.Fullscreen() + }) + }) + stateMenu.Add("UnFullscreen").OnClick(func(ctx *application.Context) { + currentWindow(func(w *application.WebviewWindow) { + w.UnFullscreen() + }) + }) + stateMenu.Add("Restore").OnClick(func(ctx *application.Context) { + currentWindow(func(w *application.WebviewWindow) { + w.Restore() + }) + }) + stateMenu.Add("Hide (for 2 seconds)").OnClick(func(ctx *application.Context) { + currentWindow(func(w *application.WebviewWindow) { + w.Hide() + time.Sleep(2 * time.Second) + w.Show() + }) + }) + stateMenu.Add("Always on Top").OnClick(func(ctx *application.Context) { + currentWindow(func(w *application.WebviewWindow) { + w.SetAlwaysOnTop(true) + }) + }) + stateMenu.Add("Not always on Top").OnClick(func(ctx *application.Context) { + currentWindow(func(w *application.WebviewWindow) { + w.SetAlwaysOnTop(false) + }) + }) + stateMenu.Add("Google.com").OnClick(func(ctx *application.Context) { + currentWindow(func(w *application.WebviewWindow) { + w.SetURL("https://google.com") + }) + }) + stateMenu.Add("wails.io").OnClick(func(ctx *application.Context) { + currentWindow(func(w *application.WebviewWindow) { + w.SetURL("https://wails.io") + }) + }) + stateMenu.Add("Get Primary Screen").OnClick(func(ctx *application.Context) { + screen, err := app.GetPrimaryScreen() + if err != nil { + application.ErrorDialog().SetTitle("Error").SetMessage(err.Error()).Show() + return + } + msg := fmt.Sprintf("Screen: %+v", screen) + application.InfoDialog().SetTitle("Primary Screen").SetMessage(msg).Show() + }) + stateMenu.Add("Get Screens").OnClick(func(ctx *application.Context) { + screens, err := app.GetScreens() + if err != nil { + application.ErrorDialog().SetTitle("Error").SetMessage(err.Error()).Show() + return + } + for _, screen := range screens { + msg := fmt.Sprintf("Screen: %+v", screen) + application.InfoDialog().SetTitle(fmt.Sprintf("Screen %s", screen.ID)).SetMessage(msg).Show() + } + }) + stateMenu.Add("Get Screen for WebviewWindow").OnClick(func(ctx *application.Context) { + currentWindow(func(w *application.WebviewWindow) { + screen, err := w.GetScreen() + if err != nil { + application.ErrorDialog().SetTitle("Error").SetMessage(err.Error()).Show() + return + } + msg := fmt.Sprintf("Screen: %+v", screen) + application.InfoDialog().SetTitle(fmt.Sprintf("Screen %s", screen.ID)).SetMessage(msg).Show() + }) + }) + stateMenu.Add("Disable for 5s").OnClick(func(ctx *application.Context) { + currentWindow(func(w *application.WebviewWindow) { + w.SetEnabled(false) + time.Sleep(5 * time.Second) + w.SetEnabled(true) + }) + }) + + if runtime.GOOS == "windows" { + stateMenu.Add("Flash Start").OnClick(func(ctx *application.Context) { + currentWindow(func(w *application.WebviewWindow) { + time.Sleep(2 * time.Second) + w.Flash(true) + }) + }) + } + + printMenu := menu.AddSubmenu("Print") + printMenu.Add("Print").OnClick(func(ctx *application.Context) { + currentWindow(func(w *application.WebviewWindow) { + _ = w.Print() + }) + }) + + app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{ + BackgroundColour: application.NewRGB(33, 37, 41), + Mac: application.MacWindow{ + DisableShadow: true, + }, + }) + + app.SetMenu(menu) + err := app.Run() + + if err != nil { + log.Fatal(err) + } + +} diff --git a/v3/examples/wml/README.md b/v3/examples/wml/README.md new file mode 100644 index 000000000..a3e869e51 --- /dev/null +++ b/v3/examples/wml/README.md @@ -0,0 +1,19 @@ +# WML Example + +This is an example of how to use the experimental WML, which provides HTMX style calling of the runtime API. + +## Running the example + +To run the example, simply run the following command: + +```bash +go run . +``` + +# Status + +| Platform | Status | +|----------|---------| +| Mac | Working | +| Windows | Working | +| Linux | | diff --git a/v3/examples/wml/assets/index.html b/v3/examples/wml/assets/index.html new file mode 100644 index 000000000..8c2e28a05 --- /dev/null +++ b/v3/examples/wml/assets/index.html @@ -0,0 +1,159 @@ + + + + + Wails Alpha + + + + +
Alpha
+
+

Documentation

+

Feedback

+
+

This application contains no Javascript!

+

Emit event

+

Delete all the things!

+

Close the Window?

+

Center

+

Minimise

+

Maximise

+

UnMaximise

+

Fullscreen

+

UnFullscreen

+

Restore

+

Open Browser?

+

Hover over me

+
+ + + + + diff --git a/v3/examples/wml/main.go b/v3/examples/wml/main.go new file mode 100644 index 000000000..84f48a7c9 --- /dev/null +++ b/v3/examples/wml/main.go @@ -0,0 +1,50 @@ +package main + +import ( + "embed" + _ "embed" + "log" + + "github.com/wailsapp/wails/v3/pkg/application" +) + +//go:embed assets/* +var assets embed.FS + +func main() { + + app := application.New(application.Options{ + Name: "Wails ML Demo", + Description: "A demo of the Wails ML API", + Mac: application.MacOptions{ + ApplicationShouldTerminateAfterLastWindowClosed: true, + }, + Assets: application.AssetOptions{ + FS: assets, + }, + }) + + app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{ + Title: "Wails ML Demo", + Width: 1280, + Height: 1024, + Mac: application.MacWindow{ + Backdrop: application.MacBackdropTranslucent, + TitleBar: application.MacTitleBarHiddenInsetUnified, + InvisibleTitleBarHeight: 50, + }, + }) + + app.Events.On("button-pressed", func(_ *application.WailsEvent) { + println("Button Pressed!") + }) + app.Events.On("hover", func(_ *application.WailsEvent) { + println("Hover time!") + }) + + err := app.Run() + + if err != nil { + log.Fatal(err.Error()) + } +} diff --git a/v3/go.mod b/v3/go.mod new file mode 100644 index 000000000..a94caafd5 --- /dev/null +++ b/v3/go.mod @@ -0,0 +1,111 @@ +module github.com/wailsapp/wails/v3 + +go 1.21 + +require ( + github.com/bep/debounce v1.2.1 + github.com/ebitengine/purego v0.4.0-alpha.4 + github.com/go-git/go-git/v5 v5.3.0 + github.com/go-ole/go-ole v1.2.6 + github.com/go-task/task/v3 v3.31.0 + github.com/godbus/dbus/v5 v5.1.0 + github.com/google/go-cmp v0.5.9 + github.com/google/uuid v1.3.0 + github.com/gorilla/pat v1.0.1 + github.com/gorilla/sessions v1.2.1 + github.com/jackmordaunt/icns/v2 v2.2.1 + github.com/jaypipes/ghw v0.12.0 + github.com/json-iterator/go v1.1.12 + github.com/leaanthony/clir v1.6.0 + github.com/leaanthony/go-ansi-parser v1.6.1 + github.com/leaanthony/gosod v1.0.3 + github.com/leaanthony/u v1.1.0 + github.com/leaanthony/winicon v1.0.0 + github.com/lmittmann/tint v1.0.0 + github.com/markbates/goth v1.77.0 + github.com/matryer/is v1.4.0 + github.com/mattn/go-colorable v0.1.13 + github.com/mattn/go-isatty v0.0.19 + github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 + github.com/pkg/errors v0.9.1 + github.com/pterm/pterm v0.12.51 + github.com/samber/lo v1.38.1 + github.com/tc-hib/winres v0.1.6 + github.com/wailsapp/go-webview2 v1.0.9 + github.com/wailsapp/mimetype v1.4.1 + golang.org/x/net v0.10.0 + golang.org/x/sys v0.13.0 + modernc.org/sqlite v1.21.0 +) + +require ( + atomicgo.dev/cursor v0.1.1 // indirect + atomicgo.dev/keyboard v0.2.8 // indirect + github.com/Masterminds/semver/v3 v3.2.1 // indirect + github.com/Microsoft/go-winio v0.4.16 // indirect + github.com/StackExchange/wmi v1.2.1 // indirect + github.com/containerd/console v1.0.3 // indirect + github.com/dustin/go-humanize v1.0.0 // indirect + github.com/emirpasic/gods v1.12.0 // indirect + github.com/fatih/color v1.15.0 // indirect + github.com/ghodss/yaml v1.0.0 // indirect + github.com/go-git/gcfg v1.5.0 // indirect + github.com/go-git/go-billy/v5 v5.2.0 // indirect + github.com/go-task/slim-sprig/v3 v3.0.0 // indirect + github.com/golang/protobuf v1.4.2 // indirect + github.com/gookit/color v1.5.2 // indirect + github.com/gorilla/context v1.1.1 // indirect + github.com/gorilla/mux v1.6.2 // indirect + github.com/gorilla/securecookie v1.1.1 // indirect + github.com/imdario/mergo v0.3.12 // indirect + github.com/jaypipes/pcidb v1.0.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/joho/godotenv v1.5.1 // indirect + github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect + github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351 // indirect + github.com/klauspost/cpuid/v2 v2.2.3 // indirect + github.com/lithammer/fuzzysearch v1.1.5 // indirect + github.com/mattn/go-runewidth v0.0.14 // indirect + github.com/mattn/go-zglob v0.0.4 // indirect + github.com/mitchellh/go-homedir v1.1.0 // indirect + github.com/mitchellh/hashstructure/v2 v2.0.2 // indirect + github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 // indirect + github.com/radovskyb/watcher v1.0.7 // indirect + github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect + github.com/rivo/uniseg v0.4.4 // indirect + github.com/sajari/fuzzy v1.0.0 // indirect + github.com/sergi/go-diff v1.2.0 // indirect + github.com/xanzy/ssh-agent v0.3.0 // indirect + github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 // indirect + github.com/zeebo/xxh3 v1.0.2 // indirect + golang.org/x/crypto v0.9.0 // indirect + golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df // indirect + golang.org/x/image v0.5.0 // indirect + golang.org/x/mod v0.12.0 // indirect + golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43 // indirect + golang.org/x/sync v0.4.0 // indirect + golang.org/x/term v0.13.0 // indirect + golang.org/x/text v0.9.0 // indirect + golang.org/x/tools v0.6.0 // indirect + google.golang.org/appengine v1.6.6 // indirect + google.golang.org/protobuf v1.25.0 // indirect + gopkg.in/warnings.v0 v0.1.2 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect + howett.net/plist v1.0.0 // indirect + lukechampine.com/uint128 v1.2.0 // indirect + modernc.org/cc/v3 v3.40.0 // indirect + modernc.org/ccgo/v3 v3.16.13 // indirect + modernc.org/libc v1.22.3 // indirect + modernc.org/mathutil v1.5.0 // indirect + modernc.org/memory v1.5.0 // indirect + modernc.org/opt v0.1.3 // indirect + modernc.org/strutil v1.1.3 // indirect + modernc.org/token v1.0.1 // indirect + mvdan.cc/sh/v3 v3.7.0 // indirect +) + +replace github.com/ebitengine/purego v0.4.0-alpha.4 => github.com/tmclane/purego v0.0.0-20230818202843-0b72c8c9140f diff --git a/v3/go.sum b/v3/go.sum new file mode 100644 index 000000000..452179ac8 --- /dev/null +++ b/v3/go.sum @@ -0,0 +1,736 @@ +atomicgo.dev/assert v0.0.2 h1:FiKeMiZSgRrZsPo9qn/7vmr7mCsh5SZyXY4YGYiYwrg= +atomicgo.dev/assert v0.0.2/go.mod h1:ut4NcI3QDdJtlmAxQULOmA13Gz6e2DWbSAS8RUOmNYQ= +atomicgo.dev/cursor v0.1.1 h1:0t9sxQomCTRh5ug+hAMCs59x/UmC9QL6Ci5uosINKD4= +atomicgo.dev/cursor v0.1.1/go.mod h1:Lr4ZJB3U7DfPPOkbH7/6TOtJ4vFGHlgj1nc+n900IpU= +atomicgo.dev/keyboard v0.2.8 h1:Di09BitwZgdTV1hPyX/b9Cqxi8HVuJQwWivnZUEqlj4= +atomicgo.dev/keyboard v0.2.8/go.mod h1:BC4w9g00XkxH/f1HXhW2sXmJFOCWbKn9xrOunSFtExQ= +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= +cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= +cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= +cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= +cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= +cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= +cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= +cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= +cloud.google.com/go v0.67.0/go.mod h1:YNan/mUhNZFrYUor0vqrsQ0Ffl7Xtm/ACOy/vsTS858= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= +cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= +cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= +cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= +cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= +cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= +cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= +cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= +cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= +cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/MarvinJWendt/testza v0.1.0/go.mod h1:7AxNvlfeHP7Z/hDQ5JtE3OKYT3XFUeLCDE2DQninSqs= +github.com/MarvinJWendt/testza v0.2.1/go.mod h1:God7bhG8n6uQxwdScay+gjm9/LnO4D3kkcZX4hv9Rp8= +github.com/MarvinJWendt/testza v0.2.8/go.mod h1:nwIcjmr0Zz+Rcwfh3/4UhBp7ePKVhuBExvZqnKYWlII= +github.com/MarvinJWendt/testza v0.2.10/go.mod h1:pd+VWsoGUiFtq+hRKSU1Bktnn+DMCSrDrXDpX2bG66k= +github.com/MarvinJWendt/testza v0.2.12/go.mod h1:JOIegYyV7rX+7VZ9r77L/eH6CfJHHzXjB69adAhzZkI= +github.com/MarvinJWendt/testza v0.3.0/go.mod h1:eFcL4I0idjtIx8P9C6KkAuLgATNKpX4/2oUqKc6bF2c= +github.com/MarvinJWendt/testza v0.4.2/go.mod h1:mSdhXiKH8sg/gQehJ63bINcCKp7RtYewEjXsvsVUPbE= +github.com/MarvinJWendt/testza v0.5.1 h1:a9Fqx6vQrHQ4CyiaLhktfTTelwGotmFWy8MNhyaohw8= +github.com/MarvinJWendt/testza v0.5.1/go.mod h1:L7csM8IBqCc0HH4TRYZSPCIRg6zJeqzM1pm3FSYZBso= +github.com/Masterminds/semver/v3 v3.2.1 h1:RN9w6+7QoMeJVGyfmbcgs28Br8cvmnucEXnY0rYXWg0= +github.com/Masterminds/semver/v3 v3.2.1/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= +github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= +github.com/Microsoft/go-winio v0.4.16 h1:FtSW/jqD+l4ba5iPBj9CODVtgfYAD8w2wS923g/cFDk= +github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0= +github.com/StackExchange/wmi v1.2.1 h1:VIkavFPXSjcnS+O8yTq7NI32k0R5Aj+v39y29VYDOSA= +github.com/StackExchange/wmi v1.2.1/go.mod h1:rcmrprowKIVzvc+NUiLncP2uuArMWLCbu9SBzvHz7e8= +github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7 h1:uSoVVbwJiQipAclBbw+8quDsfcvFjOpI5iCf4p/cqCs= +github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/ghQa61ZWa/C2Aw3RkjiTBOix7dkqa1VLIs= +github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo4JG6LR5AXSUEsOjtdm0kw0FtQtMJA= +github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= +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/atomicgo/cursor v0.0.1/go.mod h1:cBON2QmmrysudxNBFthvMtN32r3jxVRIvzkUiF/RuIk= +github.com/bep/debounce v1.2.1 h1:v67fRdBA9UQu2NhLFXrSg0Brw7CexQekrBwDMM8bzeY= +github.com/bep/debounce v1.2.1/go.mod h1:H8yggRPQKLUhUoqrJC1bO2xNya7vanpDl7xR3ISbCJ0= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/containerd/console v1.0.3 h1:lIr7SlA5PxZyMV30bDW0MGbiOPXwc63yRuCP0ARubLw= +github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= +github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= +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/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.0-20210816181553-5444fa50b93d/go.mod h1:tmAIfUFEirG/Y8jhZ9M+h36obRZAk/1fcSpXwAVlfqE= +github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= +github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +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/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs= +github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw= +github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= +github.com/frankban/quicktest v1.14.5 h1:dfYrrRyLtiqT9GyKXgdh+k4inNeTvmGbuSgZ3lx3GhA= +github.com/frankban/quicktest v1.14.5/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= +github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/gliderlabs/ssh v0.2.2 h1:6zsha5zo/TWhRhwqCD3+EarCAgZ2yN28ipRnGPnwkI0= +github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= +github.com/go-git/gcfg v1.5.0 h1:Q5ViNfGF8zFgyJWPqYwA7qGFoMTEiBmdlkcfRmpIMa4= +github.com/go-git/gcfg v1.5.0/go.mod h1:5m20vg6GwYabIxaOonVkTdrILxQMpEShl1xiMF4ua+E= +github.com/go-git/go-billy/v5 v5.0.0/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0= +github.com/go-git/go-billy/v5 v5.1.0/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0= +github.com/go-git/go-billy/v5 v5.2.0 h1:GcoouCP9J+5slw2uXAocL70z8ml4A8B/H8nEPt6CLPk= +github.com/go-git/go-billy/v5 v5.2.0/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0= +github.com/go-git/go-git-fixtures/v4 v4.0.2-0.20200613231340-f56387b50c12 h1:PbKy9zOy4aAKrJ5pibIRpVO2BXnK1Tlcg+caKI7Ox5M= +github.com/go-git/go-git-fixtures/v4 v4.0.2-0.20200613231340-f56387b50c12/go.mod h1:m+ICp2rF3jDhFgEZ/8yziagdT1C+ZpZcrJjappBCDSw= +github.com/go-git/go-git/v5 v5.3.0 h1:8WKMtJR2j8RntEXR/uvTKagfEt4GYlwQ7mntE4+0GWc= +github.com/go-git/go-git/v5 v5.3.0/go.mod h1:xdX4bWJ48aOrdhnl2XqHYstHbbp6+LFS4r4X+lNVprw= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-ole/go-ole v1.2.5/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= +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-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= +github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= +github.com/go-task/task/v3 v3.31.0 h1:o6iyj9gPJXxvxPi/u/l8e025PmM2BqKgtLNPS2i7hV4= +github.com/go-task/task/v3 v3.31.0/go.mod h1:/CPDAu9nS3+soqY/e1tTrSo/zxk76lnljEV9aBTeKrg= +github.com/goccy/go-json v0.9.6/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +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-jwt/jwt/v4 v4.2.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200905233945-acf8798be1f7/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ= +github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/gookit/color v1.4.2/go.mod h1:fqRyamkC1W8uxl+lxCQxOT09l/vYfZ+QeiX3rKQHCoQ= +github.com/gookit/color v1.5.0/go.mod h1:43aQb+Zerm/BWh2GnrgOQm7ffz7tvQXEKV6BFMl7wAo= +github.com/gookit/color v1.5.2 h1:uLnfXcaFjlrDnQDT+NCBcfhrXqYTx/rcCa6xn01Y8yI= +github.com/gookit/color v1.5.2/go.mod h1:w8h4bGiHeeBpvQVePTutdbERIUf3oJE5lZ8HM0UgXyg= +github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8= +github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= +github.com/gorilla/mux v1.6.2 h1:Pgr17XVTNXAk3q/r4CpKzC5xBM/qW1uVLV+IhRZpIIk= +github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/pat v0.0.0-20180118222023-199c85a7f6d1/go.mod h1:YeAe0gNeiNT5hoiZRI4yiOky6jVdNvfO2N6Kav/HmxY= +github.com/gorilla/pat v1.0.1 h1:OeSoj6sffw4/majibAY2BAUsXjNP7fEE+w30KickaL4= +github.com/gorilla/pat v1.0.1/go.mod h1:YeAe0gNeiNT5hoiZRI4yiOky6jVdNvfO2N6Kav/HmxY= +github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ= +github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= +github.com/gorilla/sessions v1.1.1/go.mod h1:8KCfur6+4Mqcc6S0FEfKuN15Vl5MgXW92AE8ovaJD0w= +github.com/gorilla/sessions v1.2.1 h1:DHd3rPN5lE3Ts3D8rKkQ8x/0kqfeNmBAaiSi+o7FsgI= +github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU= +github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= +github.com/jackmordaunt/icns/v2 v2.2.1 h1:MGklwYP2yohKn2Bw7XxlF69LZe98S1vUfl5OvAulPwg= +github.com/jackmordaunt/icns/v2 v2.2.1/go.mod h1:6aYIB9eSzyfHHMKqDf17Xrs1zetQPReAkiUSHzdw4cI= +github.com/jarcoal/httpmock v0.0.0-20180424175123-9c70cfe4a1da/go.mod h1:ks+b9deReOc7jgqp+e7LuFiCBH6Rm5hL32cLcEAArb4= +github.com/jaypipes/ghw v0.12.0 h1:xU2/MDJfWmBhJnujHY9qwXQLs3DBsf0/Xa9vECY0Tho= +github.com/jaypipes/ghw v0.12.0/go.mod h1:jeJGbkRB2lL3/gxYzNYzEDETV1ZJ56OKr+CSeSEym+g= +github.com/jaypipes/pcidb v1.0.0 h1:vtZIfkiCUE42oYbJS0TAq9XSfSmcsgo9IdxSm9qzYU8= +github.com/jaypipes/pcidb v1.0.0/go.mod h1:TnYUvqhPBzCKnH34KrIX22kAeEbDCSRJ9cqLRCuNDfk= +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/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4= +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +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/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= +github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= +github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351 h1:DowS9hvgyYSX4TO5NpyC606/Z4SxnNYbT+WX27or6Ck= +github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/klauspost/cpuid/v2 v2.0.10/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c= +github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c= +github.com/klauspost/cpuid/v2 v2.2.3 h1:sxCkb+qR91z4vsqw4vGGZlDgPz3G7gjaLyK3V8y70BU= +github.com/klauspost/cpuid/v2 v2.2.3/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +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/clir v1.0.4/go.mod h1:k/RBkdkFl18xkkACMCLt09bhiZnrGORoxmomeMvDpE0= +github.com/leaanthony/clir v1.6.0 h1:mLV9thGkmqFqJU7ozmqlER8sBtGdZlz6H3gKsfIiB3o= +github.com/leaanthony/clir v1.6.0/go.mod h1:k/RBkdkFl18xkkACMCLt09bhiZnrGORoxmomeMvDpE0= +github.com/leaanthony/debme v1.2.1 h1:9Tgwf+kjcrbMQ4WnPcEIUcQuIZYqdWftzZkBr+i/oOc= +github.com/leaanthony/debme v1.2.1/go.mod h1:3V+sCm5tYAgQymvSOfYQ5Xx2JCr+OXiD9Jkw3otUjiA= +github.com/leaanthony/go-ansi-parser v1.6.1 h1:xd8bzARK3dErqkPFtoF9F3/HgN8UQk0ed1YDKpEz01A= +github.com/leaanthony/go-ansi-parser v1.6.1/go.mod h1:+vva/2y4alzVmmIEpk9QDhA7vLC5zKDTRwfZGOp3IWU= +github.com/leaanthony/gosod v1.0.3 h1:Fnt+/B6NjQOVuCWOKYRREZnjGyvg+mEhd1nkkA04aTQ= +github.com/leaanthony/gosod v1.0.3/go.mod h1:BJ2J+oHsQIyIQpnLPjnqFGTMnOZXDbvWtRCSG7jGxs4= +github.com/leaanthony/slicer v1.5.0/go.mod h1:FwrApmf8gOrpzEWM2J/9Lh79tyq8KTX5AzRtwV7m4AY= +github.com/leaanthony/u v1.1.0 h1:2n0d2BwPVXSUq5yhe8lJPHdxevE2qK5G99PMStMZMaI= +github.com/leaanthony/u v1.1.0/go.mod h1:9+o6hejoRljvZ3BzdYlVL0JYCwtnAsVuN9pVTQcaRfI= +github.com/leaanthony/winicon v1.0.0 h1:ZNt5U5dY71oEoKZ97UVwJRT4e+5xo5o/ieKuHuk8NqQ= +github.com/leaanthony/winicon v1.0.0/go.mod h1:en5xhijl92aphrJdmRPlh4NI1L6wq3gEm0LpXAPghjU= +github.com/lestrrat-go/backoff/v2 v2.0.8/go.mod h1:rHP/q/r9aT27n24JQLa7JhSQZCKBBOiM/uP402WwN8Y= +github.com/lestrrat-go/blackmagic v1.0.0/go.mod h1:TNgH//0vYSs8VXDCfkZLgIrVTTXQELZffUV0tz3MtdQ= +github.com/lestrrat-go/httpcc v1.0.0/go.mod h1:tGS/u00Vh5N6FHNkExqGGNId8e0Big+++0Gf8MBnAvE= +github.com/lestrrat-go/iter v1.0.1/go.mod h1:zIdgO1mRKhn8l9vrZJZz9TUMMFbQbLeTsbqPDrJ/OJc= +github.com/lestrrat-go/jwx v1.2.21/go.mod h1:9cfxnOH7G1gN75CaJP2hKGcxFEx5sPh1abRIA/ZJVh4= +github.com/lestrrat-go/option v1.0.0/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= +github.com/lithammer/fuzzysearch v1.1.5 h1:Ag7aKU08wp0R9QCfF4GoGST9HbmAIeLP7xwMrOBEp1c= +github.com/lithammer/fuzzysearch v1.1.5/go.mod h1:1R1LRNk7yKid1BaQkmuLQaHruxcC4HmAH30Dh61Ih1Q= +github.com/lmittmann/tint v1.0.0 h1:fzEj70K1L58uyoePQxKe+ezDZJ5pybiWGdA0JeFvvyw= +github.com/lmittmann/tint v1.0.0/go.mod h1:HIS3gSy7qNwGCj+5oRjAutErFBl4BzdQP6cJZ0NfMwE= +github.com/markbates/going v1.0.0/go.mod h1:I6mnB4BPnEeqo85ynXIx1ZFLLbtiLHNXVgWeFO9OGOA= +github.com/markbates/goth v1.77.0 h1:s3scqnWv/Zq/a5M766V0FKsLfOdFNdh/HEkuWCKbvT8= +github.com/markbates/goth v1.77.0/go.mod h1:X6xdNgpapSENS0O35iTBBcMHoJDQDfI9bJl+APCkYMc= +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-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= +github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU= +github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y= +github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= +github.com/mattn/go-zglob v0.0.4 h1:LQi2iOm0/fGgu80AioIJ/1j9w9Oh+9DZ39J4VAGzHQM= +github.com/mattn/go-zglob v0.0.4/go.mod h1:MxxjyoXXnMxfIpxTK2GAkw1w8glPsQILx3N5wrKakiY= +github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/hashstructure/v2 v2.0.2 h1:vGKWl0YJqUNxE8d+h8f6NJLcCJrgbhC4NcD46KavDd4= +github.com/mitchellh/hashstructure/v2 v2.0.2/go.mod h1:MG3aRVU/N29oo/V/IhBX8GR/zz4kQkprJgF2EVszyDE= +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/mrjones/oauth v0.0.0-20180629183705-f4e24b6d100c/go.mod h1:skjdDftzkFALcuGzYSklqYd8gvat6F1gZJ4YPVbkZpM= +github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ= +github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8= +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/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= +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/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/pterm/pterm v0.12.27/go.mod h1:PhQ89w4i95rhgE+xedAoqous6K9X+r6aSOI2eFF7DZI= +github.com/pterm/pterm v0.12.29/go.mod h1:WI3qxgvoQFFGKGjGnJR849gU0TsEOvKn5Q8LlY1U7lg= +github.com/pterm/pterm v0.12.30/go.mod h1:MOqLIyMOgmTDz9yorcYbcw+HsgoZo3BQfg2wtl3HEFE= +github.com/pterm/pterm v0.12.31/go.mod h1:32ZAWZVXD7ZfG0s8qqHXePte42kdz8ECtRyEejaWgXU= +github.com/pterm/pterm v0.12.33/go.mod h1:x+h2uL+n7CP/rel9+bImHD5lF3nM9vJj80k9ybiiTTE= +github.com/pterm/pterm v0.12.36/go.mod h1:NjiL09hFhT/vWjQHSj1athJpx6H8cjpHXNAK5bUw8T8= +github.com/pterm/pterm v0.12.40/go.mod h1:ffwPLwlbXxP+rxT0GsgDTzS3y3rmpAO1NMjUkGTYf8s= +github.com/pterm/pterm v0.12.51 h1:iwhNG1FhQMgks+5kVyr/ClRk3WJCuL907nJN7RqmEpw= +github.com/pterm/pterm v0.12.51/go.mod h1:79BLm4vos2z+eOoHnDG7ZWuYtLaSStyaspKjGmSoxc4= +github.com/radovskyb/watcher v1.0.7 h1:AYePLih6dpmS32vlHfhCeli8127LzkIgwJGcwwe8tUE= +github.com/radovskyb/watcher v1.0.7/go.mod h1:78okwvY5wPdzcb1UYnip1pvrZNIVEIh/Cm+ZuvsUYIg= +github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis= +github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/rodrigocfd/windigo v0.0.0-20230809154420-8faa606d9f5f h1:jIXpgDE/hOglaKyTP5ZRqe6rhcJx2dtCan2ncR66qhU= +github.com/rodrigocfd/windigo v0.0.0-20230809154420-8faa606d9f5f/go.mod h1:Vw0QLpSedVsbAFWYIVE/Zb1pa6XPIgwIMTUQQoUG8Wo= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.10.1-0.20230524175051-ec119421bb97 h1:3RPlVWzZ/PDqmVuf/FKHARG5EMid/tl7cv54Sw/QRVY= +github.com/rogpeppe/go-internal v1.10.1-0.20230524175051-ec119421bb97/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= +github.com/sajari/fuzzy v1.0.0 h1:+FmwVvJErsd0d0hAPlj4CxqxUtQY/fOoY0DwX4ykpRY= +github.com/sajari/fuzzy v1.0.0/go.mod h1:OjYR6KxoWOe9+dOlXeiCJd4dIbED4Oo8wpS89o0pwOo= +github.com/samber/lo v1.38.1 h1:j2XEAqXKb09Am4ebOg31SpvzUTTs6EN3VfgeLUhPdXM= +github.com/samber/lo v1.38.1/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA= +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= +github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= +github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/tc-hib/winres v0.1.6 h1:qgsYHze+BxQPEYilxIz/KCQGaClvI2+yLBAZs+3+0B8= +github.com/tc-hib/winres v0.1.6/go.mod h1:pe6dOR40VOrGz8PkzreVKNvEKnlE8t4yR8A8naL+t7A= +github.com/tmclane/purego v0.0.0-20230818202843-0b72c8c9140f h1:/HXk9aFXP97CJRzOIphm4pzySmJLqIMhKu1kD5usz1E= +github.com/tmclane/purego v0.0.0-20230818202843-0b72c8c9140f/go.mod h1:ah1In8AOtksoNK6yk5z1HTJeUkC1Ez4Wk2idgGslMwQ= +github.com/wailsapp/go-webview2 v1.0.8 h1:hyoFPlMSfb/NM64wuVbgBaq1MASJjqsSUYhN+Rbcr9Y= +github.com/wailsapp/go-webview2 v1.0.8/go.mod h1:Uk2BePfCRzttBBjFrBmqKGJd41P6QIHeV9kTgIeOZNo= +github.com/wailsapp/go-webview2 v1.0.9-0.20231016103125-072d9d56c9b2 h1:FDoMl9tSWP2bjfhARy8zaH2gRxq98TIleMmCHq6YuNo= +github.com/wailsapp/go-webview2 v1.0.9-0.20231016103125-072d9d56c9b2/go.mod h1:Uk2BePfCRzttBBjFrBmqKGJd41P6QIHeV9kTgIeOZNo= +github.com/wailsapp/go-webview2 v1.0.9 h1:lrU+q0cf1wgLdR69rN+ZnRtMJNaJRrcQ4ELxoO7/xjs= +github.com/wailsapp/go-webview2 v1.0.9/go.mod h1:Uk2BePfCRzttBBjFrBmqKGJd41P6QIHeV9kTgIeOZNo= +github.com/wailsapp/mimetype v1.4.1 h1:pQN9ycO7uo4vsUUuPeHEYoUkLVkaRntMnHJxVwYhwHs= +github.com/wailsapp/mimetype v1.4.1/go.mod h1:9aV5k31bBOv5z6u+QP8TltzvNGJPmNJD4XlAL3U+j3o= +github.com/xanzy/ssh-agent v0.3.0 h1:wUMzuKtKilRgBAD1sUb8gOwwRr2FGoBVumcjoOACClI= +github.com/xanzy/ssh-agent v0.3.0/go.mod h1:3s9xbODqPuuhK9JV1R321M/FlMZSBvE5aY6eAcqrDh0= +github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 h1:QldyIu/L63oPpyvQmHgvgickp1Yw510KJOqX7H24mg8= +github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778/go.mod h1:2MuV+tbUrU1zIOPMxZ5EncGwgmMJsa+9ucAQZXxsObs= +github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ= +github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0= +github.com/zeebo/xxh3 v1.0.2 h1:xZmwmqxHZA8AI603jOQ0tMqmBr9lPeFwGg6d+xy9DC0= +github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g= +golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= +golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +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/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.0.0-20200430140353-33d19683fad8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.0.0-20201208152932-35266b937fa6/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.5.0 h1:5JMiNunQeQw++mMOz48/ISeNu3Iweh/JaZU8ZLqHRrI= +golang.org/x/image v0.5.0/go.mod h1:FVC7BI/5Ym8R25iw5OLsgshdUBbT1h5jZTpA+mvAdZ4= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= +golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= +golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200927032502-5d4f70055728/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +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= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43 h1:ld7aEMNHoBnnDAX15v1T6z31v8HwR2A9FYOuAhWqkwc= +golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/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.4.0 h1:zxkM55ReGkDlKSM+Fu41A+zmbZuaPVbGMzvvdUPznYQ= +golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200810151505-1b9f1253b3ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/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-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/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-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211013075003-97ac67df715c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= +golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek= +golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= +golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= +golang.org/x/tools v0.0.0-20200929161345-d7fc70abf50f/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= +google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= +google.golang.org/api v0.32.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.6 h1:lMO5rYAqUxkmaj76jAkRUvt5JZgFymx/+Q5Mzfivuhc= +google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= +google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= +google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200929141702-51c3e5b607fe/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= +google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= +google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.32.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= +google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/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-20200227125254-8fa46927fb4f/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/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= +gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= +gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0/go.mod h1:WDnlLJ4WF5VGsH/HVa3CI79GS0ol3YnhVnKP89i0kNg= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +howett.net/plist v1.0.0 h1:7CrbWYbPPO/PyNy38b2EB/+gYbjCe2DXBxgtOOZbSQM= +howett.net/plist v1.0.0/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g= +lukechampine.com/uint128 v1.2.0 h1:mBi/5l91vocEN8otkC5bDLhi2KdCticRiwbdB0O+rjI= +lukechampine.com/uint128 v1.2.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= +modernc.org/cc/v3 v3.40.0 h1:P3g79IUS/93SYhtoeaHW+kRCIrYaxJ27MFPv+7kaTOw= +modernc.org/cc/v3 v3.40.0/go.mod h1:/bTg4dnWkSXowUO6ssQKnOV0yMVxDYNIsIrzqTFDGH0= +modernc.org/ccgo/v3 v3.16.13 h1:Mkgdzl46i5F/CNR/Kj80Ri59hC8TKAhZrYSaqvkwzUw= +modernc.org/ccgo/v3 v3.16.13/go.mod h1:2Quk+5YgpImhPjv2Qsob1DnZ/4som1lJTodubIcoUkY= +modernc.org/ccorpus v1.11.6 h1:J16RXiiqiCgua6+ZvQot4yUuUy8zxgqbqEEUuGPlISk= +modernc.org/ccorpus v1.11.6/go.mod h1:2gEUTrWqdpH2pXsmTM1ZkjeSrUWDpjMu2T6m29L/ErQ= +modernc.org/httpfs v1.0.6 h1:AAgIpFZRXuYnkjftxTAZwMIiwEqAfk8aVB2/oA6nAeM= +modernc.org/httpfs v1.0.6/go.mod h1:7dosgurJGp0sPaRanU53W4xZYKh14wfzX420oZADeHM= +modernc.org/libc v1.22.3 h1:D/g6O5ftAfavceqlLOFwaZuA5KYafKwmr30A6iSqoyY= +modernc.org/libc v1.22.3/go.mod h1:MQrloYP209xa2zHome2a8HLiLm6k0UT8CoHpV74tOFw= +modernc.org/mathutil v1.5.0 h1:rV0Ko/6SfM+8G+yKiyI830l3Wuz1zRutdslNoQ0kfiQ= +modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= +modernc.org/memory v1.5.0 h1:N+/8c5rE6EqugZwHii4IFsaJ7MUhoWX07J5tC/iI5Ds= +modernc.org/memory v1.5.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU= +modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4= +modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= +modernc.org/sqlite v1.21.0 h1:4aP4MdUf15i3R3M2mx6Q90WHKz3nZLoz96zlB6tNdow= +modernc.org/sqlite v1.21.0/go.mod h1:XwQ0wZPIh1iKb5mkvCJ3szzbhk+tykC8ZWqTRTgYRwI= +modernc.org/strutil v1.1.3 h1:fNMm+oJklMGYfU9Ylcywl0CO5O6nTfaowNsh2wpPjzY= +modernc.org/strutil v1.1.3/go.mod h1:MEHNA7PdEnEwLvspRMtWTNnp2nnyvMfkimT1NKNAGbw= +modernc.org/tcl v1.15.1 h1:mOQwiEK4p7HruMZcwKTZPw/aqtGM4aY00uzWhlKKYws= +modernc.org/tcl v1.15.1/go.mod h1:aEjeGJX2gz1oWKOLDVZ2tnEWLUrIn8H+GFu+akoDhqs= +modernc.org/token v1.0.1 h1:A3qvTqOwexpfZZeyI0FeGPDlSWX5pjZu9hF4lU+EKWg= +modernc.org/token v1.0.1/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= +modernc.org/z v1.7.0 h1:xkDw/KepgEjeizO2sNco+hqYkU12taxQFqPEmgm1GWE= +modernc.org/z v1.7.0/go.mod h1:hVdgNMh8ggTuRG1rGU8x+xGRFfiQUIAw0ZqlPy8+HyQ= +mvdan.cc/sh/v3 v3.7.0 h1:lSTjdP/1xsddtaKfGg7Myu7DnlHItd3/M2tomOcNNBg= +mvdan.cc/sh/v3 v3.7.0/go.mod h1:K2gwkaesF/D7av7Kxl0HbF5kGOd2ArupNTX3X44+8l8= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= +rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/v3/internal/assetserver/assethandler.go b/v3/internal/assetserver/assethandler.go new file mode 100644 index 000000000..9b6d11355 --- /dev/null +++ b/v3/internal/assetserver/assethandler.go @@ -0,0 +1,194 @@ +package assetserver + +import ( + "bytes" + "embed" + "errors" + "fmt" + "io" + iofs "io/fs" + "log/slog" + "net/http" + "os" + "path" + "strings" +) + +//go:embed defaultindex.html +var defaultHTML []byte + +const ( + indexHTML = "index.html" +) + +type assetHandler struct { + fs iofs.FS + handler http.Handler + + logger *slog.Logger + + retryMissingFiles bool +} + +func NewAssetHandler(options *Options, log *slog.Logger) (http.Handler, error) { + + vfs := options.Assets + if vfs != nil { + if _, err := vfs.Open("."); err != nil { + return nil, err + } + + subDir, err := FindPathToFile(vfs, indexHTML) + if err != nil { + if errors.Is(err, os.ErrNotExist) { + msg := "no `index.html` could be found in your Assets fs.FS" + if embedFs, isEmbedFs := vfs.(embed.FS); isEmbedFs { + rootFolder, _ := FindEmbedRootPath(embedFs) + msg += fmt.Sprintf(", please make sure the embedded directory '%s' is correct and contains your assets", rootFolder) + } + + return nil, fmt.Errorf(msg) + } + + return nil, err + } + + vfs, err = iofs.Sub(vfs, path.Clean(subDir)) + if err != nil { + return nil, err + } + } + + var result http.Handler = &assetHandler{ + fs: vfs, + handler: options.Handler, + logger: log, + } + + if middleware := options.Middleware; middleware != nil { + result = middleware(result) + } + + return result, nil +} + +func (d *assetHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request) { + url := req.URL.Path + handler := d.handler + if strings.EqualFold(req.Method, http.MethodGet) { + filename := path.Clean(strings.TrimPrefix(url, "/")) + + d.logInfo("Handling request", "url", url, "file", filename) + if err := d.serveFSFile(rw, req, filename); err != nil { + if os.IsNotExist(err) { + if handler != nil { + d.logInfo("File not found. Deferring to AssetHandler", "filename", filename, "url", url) + handler.ServeHTTP(rw, req) + err = nil + } else { + rw.WriteHeader(http.StatusNotFound) + err = nil + } + } + + if err != nil { + d.logError("Unable to handle request '%s': %s", url, err) + http.Error(rw, err.Error(), http.StatusInternalServerError) + } + } + } else if handler != nil { + d.logInfo("Non-GET request. Deferring to AssetHandler", "url", url) + handler.ServeHTTP(rw, req) + } else { + rw.WriteHeader(http.StatusMethodNotAllowed) + } +} + +// serveFile will try to load the file from the fs.FS and write it to the response +func (d *assetHandler) serveFSFile(rw http.ResponseWriter, req *http.Request, filename string) error { + if d.fs == nil { + return os.ErrNotExist + } + + file, err := d.fs.Open(filename) + if err != nil { + return err + } + defer file.Close() + + statInfo, err := file.Stat() + if err != nil { + return err + } + + url := req.URL.Path + isDirectoryPath := url == "" || url[len(url)-1] == '/' + if statInfo.IsDir() { + if !isDirectoryPath { + // If the URL doesn't end in a slash normally a http.redirect should be done, but that currently doesn't work on + // WebKit WebViews (macOS/Linux). + // So we handle this as a specific error + return fmt.Errorf("a directory has been requested without a trailing slash, please add a trailing slash to your request") + } + + filename = path.Join(filename, indexHTML) + + file, err = d.fs.Open(filename) + if err != nil { + return err + } + defer file.Close() + + statInfo, err = file.Stat() + if err != nil { + return err + } + } else if isDirectoryPath { + return fmt.Errorf("a file has been requested with a trailing slash, please remove the trailing slash from your request") + } + + var buf [512]byte + var n int + if _, haveType := rw.Header()[HeaderContentType]; !haveType { + // Detect MimeType by sniffing the first 512 bytes + n, err = file.Read(buf[:]) + if err != nil && err != io.EOF { + return err + } + + // Do the custom MimeType sniffing even though http.ServeContent would do it in case + // of an io.ReadSeeker. We would like to have a consistent behaviour in both cases. + if contentType := GetMimetype(filename, buf[:n]); contentType != "" { + rw.Header().Set(HeaderContentType, contentType) + } + } + + if fileSeeker, _ := file.(io.ReadSeeker); fileSeeker != nil { + if _, err := fileSeeker.Seek(0, io.SeekStart); err != nil { + return fmt.Errorf("seeker can't seek") + } + + http.ServeContent(rw, req, statInfo.Name(), statInfo.ModTime(), fileSeeker) + return nil + } + + rw.Header().Set(HeaderContentLength, fmt.Sprintf("%d", statInfo.Size())) + + // Write the first 512 bytes used for MimeType sniffing + _, err = io.Copy(rw, bytes.NewReader(buf[:n])) + if err != nil { + return err + } + + // Copy the remaining content of the file + _, err = io.Copy(rw, file) + return err +} + +func (d *assetHandler) logInfo(message string, args ...interface{}) { + d.logger.Debug("[AssetHandler] "+message, args...) +} + +func (d *assetHandler) logError(message string, args ...interface{}) { + d.logger.Error("[AssetHandler] "+message, args...) +} diff --git a/v3/internal/assetserver/assethandler_external.go b/v3/internal/assetserver/assethandler_external.go new file mode 100644 index 000000000..44bc79e52 --- /dev/null +++ b/v3/internal/assetserver/assethandler_external.go @@ -0,0 +1,79 @@ +//go:build dev +// +build dev + +package assetserver + +import ( + "errors" + "fmt" + "log/slog" + "net/http" + "net/http/httputil" + "net/url" +) + +func NewExternalAssetsHandler(logger *slog.Logger, options Options, url *url.URL) http.Handler { + baseHandler := options.Handler + + errSkipProxy := fmt.Errorf("skip proxying") + + proxy := httputil.NewSingleHostReverseProxy(url) + baseDirector := proxy.Director + proxy.Director = func(r *http.Request) { + baseDirector(r) + if logger != nil { + logger.Debug("[ExternalAssetHandler] Loading '%s'", r.URL) + } + } + + proxy.ModifyResponse = func(res *http.Response) error { + if baseHandler == nil { + return nil + } + + if res.StatusCode == http.StatusSwitchingProtocols { + return nil + } + + if res.StatusCode == http.StatusNotFound || res.StatusCode == http.StatusMethodNotAllowed { + return errSkipProxy + } + + return nil + } + + proxy.ErrorHandler = func(rw http.ResponseWriter, r *http.Request, err error) { + if baseHandler != nil && errors.Is(err, errSkipProxy) { + if logger != nil { + logger.Debug("[ExternalAssetHandler] Loading '%s' failed, using original AssetHandler", r.URL) + } + baseHandler.ServeHTTP(rw, r) + } else { + if logger != nil { + logger.Error("[ExternalAssetHandler] Proxy error: %v", err) + } + rw.WriteHeader(http.StatusBadGateway) + } + } + + var result http.Handler = http.HandlerFunc( + func(rw http.ResponseWriter, req *http.Request) { + if req.Method == http.MethodGet { + proxy.ServeHTTP(rw, req) + return + } + + if baseHandler != nil { + baseHandler.ServeHTTP(rw, req) + return + } + + rw.WriteHeader(http.StatusMethodNotAllowed) + }) + + if middleware := options.Middleware; middleware != nil { + result = middleware(result) + } + + return result +} diff --git a/v3/internal/assetserver/assetserver.go b/v3/internal/assetserver/assetserver.go new file mode 100644 index 000000000..5603c97cb --- /dev/null +++ b/v3/internal/assetserver/assetserver.go @@ -0,0 +1,298 @@ +package assetserver + +import ( + "bytes" + "fmt" + "log/slog" + "math/rand" + "net/http" + "net/http/httptest" + "net/http/httputil" + "strings" + "time" + + "golang.org/x/net/html" +) + +const ( + runtimeJSPath = "/wails/runtime.js" + ipcJSPath = "/wails/ipc.js" + runtimePath = "/wails/runtime" + capabilitiesPath = "/wails/capabilities" + flagsPath = "/wails/flags" +) + +const webViewRequestHeaderWindowId = "x-wails-window-id" +const webViewRequestHeaderWindowName = "x-wails-window-name" + +type RuntimeAssets interface { + DesktopIPC() []byte + WebsocketIPC() []byte + RuntimeDesktopJS() []byte +} + +type RuntimeHandler interface { + HandleRuntimeCall(w http.ResponseWriter, r *http.Request) +} + +type AssetServer struct { + handler http.Handler + runtimeJS []byte + debug bool + ipcJS func(*http.Request) []byte + + logger *slog.Logger + runtime RuntimeAssets + options *Options + + servingFromDisk bool + + // Use http based runtime + runtimeHandler RuntimeHandler + + // plugin scripts + pluginScripts map[string]string + + // GetCapabilities returns the capabilities of the runtime + GetCapabilities func() []byte + + // GetFlags returns the application flags + GetFlags func() []byte + + // External dev server proxy + wsHandler *httputil.ReverseProxy + + assetServerWebView +} + +func NewAssetServer(options *Options, servingFromDisk bool, logger *slog.Logger, runtime RuntimeAssets, debug bool, runtimeHandler RuntimeHandler) (*AssetServer, error) { + handler, err := NewAssetHandler(options, logger) + if err != nil { + return nil, err + } + + var buffer bytes.Buffer + buffer.Write(runtime.RuntimeDesktopJS()) + + result := &AssetServer{ + handler: handler, + runtimeJS: buffer.Bytes(), + runtimeHandler: runtimeHandler, + options: options, + + // Check if we have been given a directory to serve assets from. + // If so, this means we are in dev mode and are serving assets off disk. + // We indicate this through the `servingFromDisk` flag to ensure requests + // aren't cached in dev mode. + servingFromDisk: servingFromDisk, + logger: logger, + runtime: runtime, + debug: debug, + } + + // Check if proxy required + externalURL, err := options.getExternalURL() + if err != nil { + return nil, err + } + if externalURL != nil { + result.wsHandler = httputil.NewSingleHostReverseProxy(externalURL) + err := result.checkExternalURL() + if err != nil { + return nil, err + } + } + return result, nil +} + +func (d *AssetServer) checkExternalURL() error { + req, err := http.NewRequest("OPTIONS", "/", nil) + if err != nil { + return err + } + w := httptest.NewRecorder() + d.wsHandler.ServeHTTP(w, req) + if w.Code != http.StatusNoContent { + return fmt.Errorf("unable to connect to external server: %s. Please check it's running.", d.options.ExternalURL) + } + return nil +} + +func (d *AssetServer) LogDetails() { + if d.debug { + d.logger.Info("AssetServer Info:", + "assetsFS", d.options.Assets != nil, + "middleware", d.options.Middleware != nil, + "handler", d.options.Handler != nil, + "externalURL", d.options.ExternalURL, + ) + } +} + +func (d *AssetServer) AddPluginScript(pluginName string, script string) { + if d.pluginScripts == nil { + d.pluginScripts = make(map[string]string) + } + pluginName = strings.ReplaceAll(pluginName, "/", "_") + pluginName = html.EscapeString(pluginName) + pluginScriptName := fmt.Sprintf("/plugin_%s_%d.js", pluginName, rand.Intn(100000)) + d.pluginScripts[pluginScriptName] = script +} + +func (d *AssetServer) ServeHTTP(rw http.ResponseWriter, req *http.Request) { + start := time.Now() + wrapped := &contentTypeSniffer{rw: rw} + d.serveHTTP(wrapped, req) + d.logger.Info( + "Asset Request:", + "windowName", req.Header.Get(webViewRequestHeaderWindowName), + "windowID", req.Header.Get(webViewRequestHeaderWindowId), + "code", wrapped.status, + "method", req.Method, + "path", req.URL.EscapedPath(), + "duration", time.Since(start), + ) +} + +func (d *AssetServer) serveHTTP(rw http.ResponseWriter, req *http.Request) { + + if d.wsHandler != nil { + d.wsHandler.ServeHTTP(rw, req) + return + } else { + if isWebSocket(req) { + // WebSockets are not supported by the AssetServer + rw.WriteHeader(http.StatusNotImplemented) + return + } + } + + header := rw.Header() + if d.servingFromDisk { + header.Add(HeaderCacheControl, "no-cache") + } + + path := req.URL.Path + switch path { + case "", "/", "/index.html": + recorder := httptest.NewRecorder() + d.handler.ServeHTTP(recorder, req) + for k, v := range recorder.Result().Header { + header[k] = v + } + + switch recorder.Code { + case http.StatusOK: + content, err := d.processIndexHTML(recorder.Body.Bytes()) + if err != nil { + d.serveError(rw, err, "Unable to processIndexHTML") + return + } + d.writeBlob(rw, indexHTML, content) + + case http.StatusNotFound: + d.writeBlob(rw, indexHTML, defaultHTML) + + default: + rw.WriteHeader(recorder.Code) + + } + return + + case runtimeJSPath: + d.writeBlob(rw, path, d.runtimeJS) + + case capabilitiesPath: + var data = []byte("{}") + if d.GetCapabilities != nil { + data = d.GetCapabilities() + } + d.writeBlob(rw, path, data) + + case flagsPath: + var data = []byte("{}") + if d.GetFlags != nil { + data = d.GetFlags() + } + d.writeBlob(rw, path, data) + + case runtimePath: + d.runtimeHandler.HandleRuntimeCall(rw, req) + return + + case ipcJSPath: + content := d.runtime.DesktopIPC() + if d.ipcJS != nil { + content = d.ipcJS(req) + } + d.writeBlob(rw, path, content) + + default: + // Check if this is a plugin script + if script, ok := d.pluginScripts[path]; ok { + d.writeBlob(rw, path, []byte(script)) + } else { + d.handler.ServeHTTP(rw, req) + return + } + } +} + +func (d *AssetServer) processIndexHTML(indexHTML []byte) ([]byte, error) { + htmlNode, err := getHTMLNode(indexHTML) + if err != nil { + return nil, err + } + + if d.debug { + err = appendSpinnerToBody(htmlNode) + if err != nil { + return nil, err + } + } + + if err := insertScriptInHead(htmlNode, runtimeJSPath); err != nil { + return nil, err + } + + if d.debug { + if err := insertScriptInHead(htmlNode, ipcJSPath); err != nil { + return nil, err + } + } + + // Inject plugins + for scriptName := range d.pluginScripts { + if err := insertScriptInHead(htmlNode, scriptName); err != nil { + return nil, err + } + } + + var buffer bytes.Buffer + err = html.Render(&buffer, htmlNode) + if err != nil { + return nil, err + } + return buffer.Bytes(), nil +} + +func (d *AssetServer) writeBlob(rw http.ResponseWriter, filename string, blob []byte) { + err := serveFile(rw, filename, blob) + if err != nil { + d.serveError(rw, err, "Unable to write content %s", filename) + } +} + +func (d *AssetServer) serveError(rw http.ResponseWriter, err error, msg string, args ...interface{}) { + args = append(args, err) + d.logError(msg+": %s", args...) + rw.WriteHeader(http.StatusInternalServerError) +} + +func (d *AssetServer) logInfo(message string, args ...interface{}) { + d.logger.Info("Asset Request: "+message, args...) +} + +func (d *AssetServer) logError(message string, args ...interface{}) { + d.logger.Error("Asset Request: "+message, args...) +} diff --git a/v3/internal/assetserver/assetserver_dev.go b/v3/internal/assetserver/assetserver_dev.go new file mode 100644 index 000000000..ae80a74e7 --- /dev/null +++ b/v3/internal/assetserver/assetserver_dev.go @@ -0,0 +1,24 @@ +//go:build !production + +package assetserver + +/* +The assetserver for the dev mode. +Depending on the UserAgent it injects a websocket based IPC script into `index.html` or the default desktop IPC. The +default desktop IPC is injected when the webview accesses the devserver. +*/ +//func NewDevAssetServer(handler http.Handler, servingFromDisk bool, logger *slog.Logger, runtime RuntimeAssets, runtimeHandler RuntimeHandler) (*AssetServer, error) { +// result, err := NewAssetServerWithHandler(handler, servingFromDisk, logger, runtime, true, runtimeHandler) +// if err != nil { +// return nil, err +// } +// +// result.ipcJS = func(req *http.Request) []byte { +// if strings.Contains(req.UserAgent(), WailsUserAgentValue) { +// return runtime.DesktopIPC() +// } +// return runtime.WebsocketIPC() +// } +// +// return result, nil +//} diff --git a/v3/internal/assetserver/assetserver_webview.go b/v3/internal/assetserver/assetserver_webview.go new file mode 100644 index 000000000..66bc9597a --- /dev/null +++ b/v3/internal/assetserver/assetserver_webview.go @@ -0,0 +1,188 @@ +package assetserver + +import ( + "fmt" + "net/http" + "net/url" + "strconv" + "strings" + "sync" + + "github.com/wailsapp/wails/v3/internal/assetserver/webview" +) + +type assetServerWebView struct { + // ExpectedWebViewHost is checked against the Request Host of every WebViewRequest, other hosts won't be processed. + ExpectedWebViewHost string + + dispatchInit sync.Once + dispatchReqC chan<- webview.Request + dispatchWorkers int +} + +// ServeWebViewRequest processes the HTTP Request asynchronously by faking a golang HTTP Server. +// The request will be finished with a StatusNotImplemented code if no handler has written to the response. +// The AssetServer takes ownership of the request and the caller mustn't close it or access it in any other way. +func (d *AssetServer) ServeWebViewRequest(req webview.Request) { + d.dispatchInit.Do(func() { + workers := d.dispatchWorkers + if workers <= 0 { + return + } + + workerC := make(chan webview.Request, workers*2) + for i := 0; i < workers; i++ { + go func() { + for req := range workerC { + d.processWebViewRequest(req) + } + }() + } + + dispatchC := make(chan webview.Request) + go queueingDispatcher(50, dispatchC, workerC) + + d.dispatchReqC = dispatchC + }) + + if d.dispatchReqC == nil { + go d.processWebViewRequest(req) + } else { + d.dispatchReqC <- req + } +} + +func (d *AssetServer) processWebViewRequest(r webview.Request) { + uri, _ := r.URL() + d.processWebViewRequestInternal(r) + if err := r.Close(); err != nil { + d.logError("Unable to call close for request for uri '%s'", uri) + } +} + +// processHTTPRequest processes the HTTP Request by faking a golang HTTP Server. +// The request will be finished with a StatusNotImplemented code if no handler has written to the response. +func (d *AssetServer) processWebViewRequestInternal(r webview.Request) { + uri := "unknown" + var err error + + wrw := r.Response() + defer func() { + if err := wrw.Finish(); err != nil { + d.logError("Error finishing request '%s': %s", uri, err) + } + }() + + var rw http.ResponseWriter = &contentTypeSniffer{rw: wrw} // Make sure we have a Content-Type sniffer + defer rw.WriteHeader(http.StatusNotImplemented) // This is a NOP when a handler has already written and set the status + + uri, err = r.URL() + if err != nil { + d.logError("Error processing request, unable to get URL: %s (HttpResponse=500)", err) + http.Error(rw, err.Error(), http.StatusInternalServerError) + return + } + + method, err := r.Method() + if err != nil { + d.webviewRequestErrorHandler(uri, rw, fmt.Errorf("HTTP-Method: %w", err)) + return + } + + header, err := r.Header() + if err != nil { + d.webviewRequestErrorHandler(uri, rw, fmt.Errorf("HTTP-Header: %w", err)) + return + } + + body, err := r.Body() + if err != nil { + d.webviewRequestErrorHandler(uri, rw, fmt.Errorf("HTTP-Body: %w", err)) + return + } + + if body == nil { + body = http.NoBody + } + defer body.Close() + + req, err := http.NewRequest(method, uri, body) + if err != nil { + d.webviewRequestErrorHandler(uri, rw, fmt.Errorf("HTTP-Request: %w", err)) + return + } + + // For server requests, the URL is parsed from the URI supplied on the Request-Line as stored in RequestURI. For + // most requests, fields other than Path and RawQuery will be empty. (See RFC 7230, Section 5.3) + req.URL.Scheme = "" + req.URL.Host = "" + req.URL.Fragment = "" + req.URL.RawFragment = "" + + if url := req.URL; req.RequestURI == "" && url != nil { + req.RequestURI = url.String() + } + + req.Header = header + + if req.RemoteAddr == "" { + // 192.0.2.0/24 is "TEST-NET" in RFC 5737 + req.RemoteAddr = "192.0.2.1:1234" + } + + if req.RequestURI == "" && req.URL != nil { + req.RequestURI = req.URL.String() + } + + if req.ContentLength == 0 { + req.ContentLength, _ = strconv.ParseInt(req.Header.Get(HeaderContentLength), 10, 64) + } else { + req.Header.Set(HeaderContentLength, fmt.Sprintf("%d", req.ContentLength)) + } + + if host := req.Header.Get(HeaderHost); host != "" { + req.Host = host + } + + if expectedHost := d.ExpectedWebViewHost; expectedHost != "" && expectedHost != req.Host { + d.webviewRequestErrorHandler(uri, rw, fmt.Errorf("expected host '%s' in request, but was '%s'", expectedHost, req.Host)) + return + } + + d.ServeHTTP(rw, req) +} + +func (d *AssetServer) webviewRequestErrorHandler(uri string, rw http.ResponseWriter, err error) { + logInfo := uri + if uri, err := url.ParseRequestURI(uri); err == nil { + logInfo = strings.Replace(logInfo, fmt.Sprintf("%s://%s", uri.Scheme, uri.Host), "", 1) + } + + d.logError("Error processing request (HttpResponse=500)", "details", logInfo, "error", err.Error()) + http.Error(rw, err.Error(), http.StatusInternalServerError) +} + +func queueingDispatcher[T any](minQueueSize uint, inC <-chan T, outC chan<- T) { + q := newRingqueue[T](minQueueSize) + for { + in, ok := <-inC + if !ok { + return + } + + q.Add(in) + for q.Len() != 0 { + out, _ := q.Peek() + select { + case outC <- out: + q.Remove() + case in, ok := <-inC: + if !ok { + return + } + + q.Add(in) + } + } + } +} diff --git a/v3/internal/assetserver/common.go b/v3/internal/assetserver/common.go new file mode 100644 index 000000000..b787cc6bb --- /dev/null +++ b/v3/internal/assetserver/common.go @@ -0,0 +1,115 @@ +package assetserver + +import ( + "bytes" + "errors" + "fmt" + "io" + "net/http" + "strings" + + "golang.org/x/net/html" +) + +const ( + HeaderHost = "Host" + HeaderContentType = "Content-Type" + HeaderContentLength = "Content-Length" + HeaderUserAgent = "User-Agent" + HeaderCacheControl = "Cache-Control" + HeaderUpgrade = "Upgrade" + + WailsUserAgentValue = "wails.io" +) + +func serveFile(rw http.ResponseWriter, filename string, blob []byte) error { + header := rw.Header() + header.Set(HeaderContentLength, fmt.Sprintf("%d", len(blob))) + if mimeType := header.Get(HeaderContentType); mimeType == "" { + mimeType = GetMimetype(filename, blob) + header.Set(HeaderContentType, mimeType) + } + + rw.WriteHeader(http.StatusOK) + _, err := io.Copy(rw, bytes.NewReader(blob)) + return err +} + +func createScriptNode(scriptName string) *html.Node { + return &html.Node{ + Type: html.ElementNode, + Data: "script", + Attr: []html.Attribute{ + { + Key: "src", + Val: scriptName, + }, + }, + } +} + +func createDivNode(id string) *html.Node { + return &html.Node{ + Type: html.ElementNode, + Data: "div", + Attr: []html.Attribute{ + { + Namespace: "", + Key: "id", + Val: id, + }, + }, + } +} + +func insertScriptInHead(htmlNode *html.Node, scriptName string) error { + headNode := findFirstTag(htmlNode, "head") + if headNode == nil { + return errors.New("cannot find head in HTML") + } + scriptNode := createScriptNode(scriptName) + if headNode.FirstChild != nil { + headNode.InsertBefore(scriptNode, headNode.FirstChild) + } else { + headNode.AppendChild(scriptNode) + } + return nil +} + +func appendSpinnerToBody(htmlNode *html.Node) error { + bodyNode := findFirstTag(htmlNode, "body") + if bodyNode == nil { + return errors.New("cannot find body in HTML") + } + scriptNode := createDivNode("wails-spinner") + bodyNode.AppendChild(scriptNode) + return nil +} + +func getHTMLNode(htmldata []byte) (*html.Node, error) { + return html.Parse(bytes.NewReader(htmldata)) +} + +func findFirstTag(htmlnode *html.Node, tagName string) *html.Node { + var extractor func(*html.Node) *html.Node + var result *html.Node + extractor = func(node *html.Node) *html.Node { + if node.Type == html.ElementNode && node.Data == tagName { + return node + } + for child := node.FirstChild; child != nil; child = child.NextSibling { + result := extractor(child) + if result != nil { + return result + } + } + return nil + } + result = extractor(htmlnode) + return result +} + +func isWebSocket(req *http.Request) bool { + upgrade := req.Header.Get(HeaderUpgrade) + return strings.EqualFold(upgrade, "websocket") +} diff --git a/v3/internal/assetserver/content_type_sniffer.go b/v3/internal/assetserver/content_type_sniffer.go new file mode 100644 index 000000000..85e2191dc --- /dev/null +++ b/v3/internal/assetserver/content_type_sniffer.go @@ -0,0 +1,42 @@ +package assetserver + +import ( + "net/http" +) + +type contentTypeSniffer struct { + rw http.ResponseWriter + status int + wroteHeader bool +} + +func (rw contentTypeSniffer) Header() http.Header { + return rw.rw.Header() +} + +func (rw *contentTypeSniffer) Write(buf []byte) (int, error) { + rw.writeHeader(buf) + return rw.rw.Write(buf) +} + +func (rw *contentTypeSniffer) WriteHeader(code int) { + if rw.wroteHeader { + return + } + rw.status = code + rw.rw.WriteHeader(code) + rw.wroteHeader = true +} + +func (rw *contentTypeSniffer) writeHeader(b []byte) { + if rw.wroteHeader { + return + } + + m := rw.rw.Header() + if _, hasType := m[HeaderContentType]; !hasType { + m.Set(HeaderContentType, http.DetectContentType(b)) + } + + rw.WriteHeader(http.StatusOK) +} diff --git a/v3/internal/assetserver/defaultindex.html b/v3/internal/assetserver/defaultindex.html new file mode 100644 index 000000000..1ea97c405 --- /dev/null +++ b/v3/internal/assetserver/defaultindex.html @@ -0,0 +1,39 @@ + + + + + index.html not found + + + + +
index.html not found
+

Please try reloading the page

+ + \ No newline at end of file diff --git a/v3/internal/assetserver/fs.go b/v3/internal/assetserver/fs.go new file mode 100644 index 000000000..7ecc9cec8 --- /dev/null +++ b/v3/internal/assetserver/fs.go @@ -0,0 +1,75 @@ +package assetserver + +import ( + "embed" + "fmt" + "io/fs" + "os" + "path/filepath" + "strings" +) + +// FindEmbedRootPath finds the root path in the embed FS. It's the directory which contains all the files. +func FindEmbedRootPath(fsys embed.FS) (string, error) { + stopErr := fmt.Errorf("files or multiple dirs found") + + fPath := "" + err := fs.WalkDir(fsys, ".", func(path string, d fs.DirEntry, err error) error { + if err != nil { + return err + } + + if d.IsDir() { + fPath = path + if entries, dErr := fs.ReadDir(fsys, path); dErr != nil { + return dErr + } else if len(entries) <= 1 { + return nil + } + } + + return stopErr + }) + + if err != nil && err != stopErr { + return "", err + } + + return fPath, nil +} + +func FindPathToFile(fsys fs.FS, file string) (string, error) { + stat, _ := fs.Stat(fsys, file) + if stat != nil { + return ".", nil + } + var indexFiles []string + err := fs.WalkDir(fsys, ".", func(path string, d fs.DirEntry, err error) error { + if err != nil { + return err + } + if strings.HasSuffix(path, file) { + indexFiles = append(indexFiles, path) + } + return nil + }) + if err != nil { + return "", err + } + + if len(indexFiles) > 1 { + selected := indexFiles[0] + for _, f := range indexFiles { + if len(f) < len(selected) { + selected = f + } + } + path, _ := filepath.Split(selected) + return path, nil + } + if len(indexFiles) > 0 { + path, _ := filepath.Split(indexFiles[0]) + return path, nil + } + return "", fmt.Errorf("%s: %w", file, os.ErrNotExist) +} diff --git a/v3/internal/assetserver/middleware.go b/v3/internal/assetserver/middleware.go new file mode 100644 index 000000000..b3826ab7d --- /dev/null +++ b/v3/internal/assetserver/middleware.go @@ -0,0 +1,20 @@ +package assetserver + +import ( + "net/http" +) + +// Middleware defines a HTTP middleware that can be applied to the AssetServer. +// The handler passed as next is the next handler in the chain. One can decide to call the next handler +// or implement a specialized handling. +type Middleware func(next http.Handler) http.Handler + +// ChainMiddleware allows chaining multiple middlewares to one middleware. +func ChainMiddleware(middleware ...Middleware) Middleware { + return func(h http.Handler) http.Handler { + for i := len(middleware) - 1; i >= 0; i-- { + h = middleware[i](h) + } + return h + } +} diff --git a/v3/internal/assetserver/mimecache.go b/v3/internal/assetserver/mimecache.go new file mode 100644 index 000000000..9d97e8f5a --- /dev/null +++ b/v3/internal/assetserver/mimecache.go @@ -0,0 +1,67 @@ +package assetserver + +import ( + "net/http" + "path/filepath" + "sync" + + "github.com/wailsapp/mimetype" +) + +var ( + mimeCache = map[string]string{} + mimeMutex sync.Mutex + + // The list of builtin mime-types by extension as defined by + // the golang standard lib package "mime" + // The standard lib also takes into account mime type definitions from + // etc files like '/etc/apache2/mime.types' but we want to have the + // same behavivour on all platforms and not depend on some external file. + mimeTypesByExt = map[string]string{ + ".avif": "image/avif", + ".css": "text/css; charset=utf-8", + ".gif": "image/gif", + ".htm": "text/html; charset=utf-8", + ".html": "text/html; charset=utf-8", + ".jpeg": "image/jpeg", + ".jpg": "image/jpeg", + ".js": "text/javascript; charset=utf-8", + ".json": "application/json", + ".mjs": "text/javascript; charset=utf-8", + ".pdf": "application/pdf", + ".png": "image/png", + ".svg": "image/svg+xml", + ".wasm": "application/wasm", + ".webp": "image/webp", + ".xml": "text/xml; charset=utf-8", + } +) + +func GetMimetype(filename string, data []byte) string { + mimeMutex.Lock() + defer mimeMutex.Unlock() + + result := mimeTypesByExt[filepath.Ext(filename)] + if result != "" { + return result + } + + result = mimeCache[filename] + if result != "" { + return result + } + + detect := mimetype.Detect(data) + if detect == nil { + result = http.DetectContentType(data) + } else { + result = detect.String() + } + + if result == "" { + result = "application/octet-stream" + } + + mimeCache[filename] = result + return result +} diff --git a/v3/internal/assetserver/mimecache_test.go b/v3/internal/assetserver/mimecache_test.go new file mode 100644 index 000000000..48e5943fa --- /dev/null +++ b/v3/internal/assetserver/mimecache_test.go @@ -0,0 +1,46 @@ +package assetserver + +import ( + "testing" +) + +func TestGetMimetype(t *testing.T) { + type args struct { + filename string + data []byte + } + bomUTF8 := []byte{0xef, 0xbb, 0xbf} + var emptyMsg []byte + css := []byte("body{margin:0;padding:0;background-color:#d579b2}#app{font-family:Avenir,Helvetica,Arial,sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;text-align:center;color:#2c3e50;background-color:#ededed}#nav{padding:30px}#nav a{font-weight:700;color:#2c\n3e50}#nav a.router-link-exact-active{color:#42b983}.hello[data-v-4e26ad49]{margin:10px 0}") + html := []byte("title") + bomHtml := append(bomUTF8, html...) + svg := []byte("") + svgWithComment := append([]byte(""), svg...) + svgWithCommentAndControlChars := append([]byte(" \r\n "), svgWithComment...) + svgWithBomCommentAndControlChars := append(bomUTF8, append([]byte(" \r\n "), svgWithComment...)...) + + tests := []struct { + name string + args args + want string + }{ + {"nil data", args{"nil.svg", nil}, "image/svg+xml"}, + {"empty data", args{"empty.html", emptyMsg}, "text/html; charset=utf-8"}, + {"css", args{"test.css", css}, "text/css; charset=utf-8"}, + {"js", args{"test.js", []byte("let foo = 'bar'; console.log(foo);")}, "text/javascript; charset=utf-8"}, + {"mjs", args{"test.mjs", []byte("let foo = 'bar'; console.log(foo);")}, "text/javascript; charset=utf-8"}, + {"html-utf8", args{"test_utf8.html", html}, "text/html; charset=utf-8"}, + {"html-bom-utf8", args{"test_bom_utf8.html", bomHtml}, "text/html; charset=utf-8"}, + {"svg", args{"test.svg", svg}, "image/svg+xml"}, + {"svg-w-comment", args{"test_comment.svg", svgWithComment}, "image/svg+xml"}, + {"svg-w-control-comment", args{"test_control_comment.svg", svgWithCommentAndControlChars}, "image/svg+xml"}, + {"svg-w-bom-control-comment", args{"test_bom_control_comment.svg", svgWithBomCommentAndControlChars}, "image/svg+xml"}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := GetMimetype(tt.args.filename, tt.args.data); got != tt.want { + t.Errorf("GetMimetype() = '%v', want '%v'", got, tt.want) + } + }) + } +} diff --git a/v3/internal/assetserver/options.go b/v3/internal/assetserver/options.go new file mode 100644 index 000000000..1ad67cd8c --- /dev/null +++ b/v3/internal/assetserver/options.go @@ -0,0 +1,57 @@ +package assetserver + +import ( + "fmt" + "io/fs" + "net/http" + "net/url" +) + +// Options defines the configuration of the AssetServer. +type Options struct { + // Assets defines the static assets to be used. A GET request is first tried to be served from this Assets. If the Assets returns + // `os.ErrNotExist` for that file, the request handling will fallback to the Handler and tries to serve the GET + // request from it. + // + // If set to nil, all GET requests will be forwarded to Handler. + Assets fs.FS + + // Handler will be called for every GET request that can't be served from Assets, due to `os.ErrNotExist`. Furthermore all + // non GET requests will always be served from this Handler. + // + // If not defined, the result is the following in cases where the Handler would have been called: + // GET request: `http.StatusNotFound` + // Other request: `http.StatusMethodNotAllowed` + Handler http.Handler + + // Middleware is a HTTP Middleware which allows to hook into the AssetServer request chain. It allows to skip the default + // request handler dynamically, e.g. implement specialized Routing etc. + // The Middleware is called to build a new `http.Handler` used by the AssetSever and it also receives the default + // handler used by the AssetServer as an argument. + // + // If not defined, the default AssetServer request chain is executed. + // + // Multiple Middlewares can be chained together with: + // ChainMiddleware(middleware ...Middleware) Middleware + Middleware Middleware + + // ExternalURL is the URL that the assets are served from + // This is useful when using a development server like `vite` or `snowpack` which serves the assets on a different port. + ExternalURL string +} + +// Validate the options +func (o Options) Validate() error { + if o.Assets == nil && o.Handler == nil && o.Middleware == nil { + return fmt.Errorf("AssetServer options invalid: either Assets, Handler or Middleware must be set") + } + + return nil +} + +func (o Options) getExternalURL() (*url.URL, error) { + if o.ExternalURL == "" { + return nil, nil + } + return url.Parse(o.ExternalURL) +} diff --git a/v3/internal/assetserver/ringqueue.go b/v3/internal/assetserver/ringqueue.go new file mode 100644 index 000000000..b94e7cd5c --- /dev/null +++ b/v3/internal/assetserver/ringqueue.go @@ -0,0 +1,101 @@ +// Code from https://github.com/erikdubbelboer/ringqueue +/* +The MIT License (MIT) + +Copyright (c) 2015 Erik Dubbelboer + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ +package assetserver + +type ringqueue[T any] struct { + nodes []T + head int + tail int + cnt int + + minSize int +} + +func newRingqueue[T any](minSize uint) *ringqueue[T] { + if minSize < 2 { + minSize = 2 + } + return &ringqueue[T]{ + nodes: make([]T, minSize), + minSize: int(minSize), + } +} + +func (q *ringqueue[T]) resize(n int) { + nodes := make([]T, n) + if q.head < q.tail { + copy(nodes, q.nodes[q.head:q.tail]) + } else { + copy(nodes, q.nodes[q.head:]) + copy(nodes[len(q.nodes)-q.head:], q.nodes[:q.tail]) + } + + q.tail = q.cnt % n + q.head = 0 + q.nodes = nodes +} + +func (q *ringqueue[T]) Add(i T) { + if q.cnt == len(q.nodes) { + // Also tested a grow rate of 1.5, see: http://stackoverflow.com/questions/2269063/buffer-growth-strategy + // In Go this resulted in a higher memory usage. + q.resize(q.cnt * 2) + } + q.nodes[q.tail] = i + q.tail = (q.tail + 1) % len(q.nodes) + q.cnt++ +} + +func (q *ringqueue[T]) Peek() (T, bool) { + if q.cnt == 0 { + var none T + return none, false + } + return q.nodes[q.head], true +} + +func (q *ringqueue[T]) Remove() (T, bool) { + if q.cnt == 0 { + var none T + return none, false + } + i := q.nodes[q.head] + q.head = (q.head + 1) % len(q.nodes) + q.cnt-- + + if n := len(q.nodes) / 2; n > q.minSize && q.cnt <= n { + q.resize(n) + } + + return i, true +} + +func (q *ringqueue[T]) Cap() int { + return cap(q.nodes) +} + +func (q *ringqueue[T]) Len() int { + return q.cnt +} diff --git a/v3/internal/assetserver/testdata/index.html b/v3/internal/assetserver/testdata/index.html new file mode 100644 index 000000000..76da518f4 --- /dev/null +++ b/v3/internal/assetserver/testdata/index.html @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/v3/internal/assetserver/testdata/main.css b/v3/internal/assetserver/testdata/main.css new file mode 100644 index 000000000..57b00e6c6 --- /dev/null +++ b/v3/internal/assetserver/testdata/main.css @@ -0,0 +1,39 @@ + +html { + text-align: center; + color: white; + background-color: rgba(1, 1, 1, 0.1); +} + +body { + color: white; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif; + margin: 0; +} + +#result { + margin-top: 1rem; +} + +button { + -webkit-appearance: default-button; + padding: 6px; +} + +#name { + border-radius: 3px; + outline: none; + height: 20px; + -webkit-font-smoothing: antialiased; +} + +#logo { + width: 40%; + height: 40%; + padding-top: 20%; + margin: auto; + display: block; + background-position: center; + background-repeat: no-repeat; + background-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB2aWV3Qm94PSIwIDAgNTUxIDQzNiIgZmlsbC1ydWxlPSJldmVub2RkIiBzdHJva2UtbGluZWpvaW49InJvdW5kIiBzdHJva2UtbWl0ZXJsaW1pdD0iMiIgeG1sbnM6dj0iaHR0cHM6Ly92ZWN0YS5pby9uYW5vIj48ZyBmaWxsLXJ1bGU9Im5vbnplcm8iPjxwYXRoIGQ9Ik0xMDQuMDEgMzQ0LjM4OGgxOC40MDFsLTEuNzY4IDM5LjE2MSAxMi4xNDctMzkuMTYxaDE0Ljg2N2wtLjE4MSAzOS4xNjEgMTAuNTYxLTM5LjE2MWgxOC40MDFsLTIzLjI5NyA2Ni4xNzVoLTE2Ljk5N2wuMTgxLTQxLjM4My0xMi45MTcgNDEuMzgzaC0xNi45NTFsLTIuNDQ3LTY2LjE3NXptMTIwLjk3NSA0My4wNTloNy4zODhsLjIyNy0yNC41MjEtNy42MTUgMjQuNTIxem0tMjUuNzQ0IDIzLjExNmwyNC44ODMtNjYuMTc1aDIxLjgwMWw0LjY2NyA2Ni4xNzVoLTE4LjY3NGwuMDkyLTkuNzQ2aC0xMC45MjRsLTIuOTAxIDkuNzQ2aC0xOC45NDR6bTg4LjE4MyAwbDEwLjQ3LTY2LjE3NmgxOC40OTNsLTEwLjUxNiA2Ni4xNzUtMTguNDQ2LjAwMXptNjUuNzkzIDBsMTAuNTE2LTY2LjE3NWgxOC41MzZsLTcuODg2IDQ5Ljc2NmgxMy41NTJsLTIuNTgyIDE2LjQwOWgtMzIuMTM2em03NC43MjItMjAuMzUyYzIuMDU0IDEuNzIzIDQuMjE1IDMuMDUzIDYuNDgyIDMuOTlzNC40NCAxLjQwNCA2LjUyNiAxLjQwNGMxLjg0MyAwIDMuMzA4LS41MDYgNC4zOTYtMS41MThzMS42MzItMi4zOTUgMS42MzItNC4xNDhjMC0xLjUwOS0uNDU0LTMuMDEzLTEuMzU5LTQuNTA5cy0yLjY2LTMuNDgxLTUuMjU4LTUuOTU5Yy0zLjE0NC0zLjA1Mi01LjMwMy01Ljc0MS02LjQ4Mi04LjA2OXMtMS43NjYtNC44OTQtMS43NjYtNy43MDRjMC02LjMxNSAyLjAwMS0xMS4zMzIgNi4wMDUtMTUuMDQ4czkuNDM0LTUuNTc1IDE2LjI5NC01LjU3NWMyLjc4IDAgNS40MjIuMzEgNy45MzEuOTNzNS4wNiAxLjU3OSA3LjY2MSAyLjg3OGwtMi42MyAxNi4xMzZjLTEuOTk1LTEuMzktMy45MzUtMi40NDctNS44MjMtMy4xNzNzLTMuNjk0LTEuMDg5LTUuNDE3LTEuMDg5Yy0xLjU0MSAwLTIuNzU4LjQtMy42NDkgMS4ycy0xLjMzOCAxLjg5OC0xLjMzOCAzLjI4OGMwIDEuODc1IDEuNzA4IDQuNTAzIDUuMTIzIDcuODg2bC45OTcuOTk2YzMuNDQ1IDMuMzg2IDUuNzExIDYuMjg2IDYuNzk4IDguNzA1czEuNjMxIDUuMjA5IDEuNjMxIDguMzg0YzAgNy4wNzEtMi4xODMgMTIuNjQ2LTYuNTUgMTYuNzI0cy0xMC4zNDEgNi4xMi0xNy45MjUgNi4xMmMtMy4yMzQgMC02LjI5Mi0uMzg1LTkuMTc4LTEuMTU1cy01LjMxLTEuODM4LTcuMjc0LTMuMTk3bDMuMTczLTE3LjQ5NXoiIGZpbGw9IiNmZmYiLz48cGF0aCBkPSJNLjg4My0uMDgxTC4xMjEuMDgxLjI1Ni0uMDYzLjg4My0uMDgxeiIgZmlsbD0idXJsKCNBKSIgdHJhbnNmb3JtPSJtYXRyaXgoLTE2Ni41OTkgNC41NzEzMiA0LjU3MTMyIDE2Ni41OTkgMTQ3LjQwMyAxNjcuNjQ4KSIvPjxwYXRoIGQ9Ik0uODc4LS4yODVMLS4wNzMuNzEtMS4xODYuNTQyLjAxNS4yMDctLjg0Ni4wNzcuMzU1LS4yNThsLS44Ni0uMTNMLjY0OS0uNzFsLjIyOS40MjV6IiBmaWxsPSJ1cmwoI0IpIiB0cmFuc2Zvcm09Im1hdHJpeCgtMTA2LjQ0MyAtMTYuMDY2OSAtMTYuMDY2OSAxMDYuNDQzIDQyOC4xOSAxODguMDMzKSIvPjxwYXRoIGQ9Ik0uNDQtLjA0aDAgMEwuMjY1LS4wNTYuMTc3LjQzNy0uMzExLS4yNTUuMjYyLS40MzdoLjMwNkwuNDQtLjA0eiIgZmlsbD0idXJsKCNDKSIgdHJhbnNmb3JtPSJtYXRyaXgoLTExNC40ODQgLTE2Mi40MDggLTE2Mi40MDggMTE0LjQ4NCAzMzMuMjkxIDI4NS44MDQpIi8+PHBhdGggZD0iTS41IDBoMCAwIDB6IiBmaWxsPSJ1cmwoI0QpIiB0cmFuc2Zvcm09Im1hdHJpeCg2MS42OTE5IDU4LjgwOTEgNTguODA5MSAtNjEuNjkxOSAyNTguNjMxIDE4MC40MTMpIi8+PHBhdGggZD0iTS42MjItLjExNWguMTM5bC4wNDUuMTAyLjAyLjE5NS0uMjA0LS4yOTd6IiBmaWxsPSJ1cmwoI0UpIiB0cmFuc2Zvcm09Im1hdHJpeCgyMzguMTI2IDI5OC44OTMgMjk4Ljg5MyAtMjM4LjEyNiAxMTMuNTE2IC0xNTAuNTM2KSIvPjxwYXRoIGQ9Ik0uNDY3LjAwNUwuNDkuMDYyLjI3MS0uMDYyLjQ2Ny4wMDV6IiBmaWxsPSJ1cmwoI0YpIiB0cmFuc2Zvcm09Im1hdHJpeCgtMzY5LjUyOSAtOTcuNDExOCAtOTcuNDExOCAzNjkuNTI5IDU4Mi4zOCA5NC4wMjcpIi8+PGcgZmlsbD0idXJsKCNCKSI+PHBhdGggZD0iTS4yLjAwMWwuMDE5LS4wMTkuMzk1LjAzLS4wOTUuMDc3TC4yODIuMDY4LjIuMTM1LjQ2My4xOTQuMzc0LjI2Ni4xMzguMTg2aDAgMEwuMDQ3LjAzMy0uMTMxLS4yNjYuMi4wMDF6IiB0cmFuc2Zvcm09Im1hdHJpeCgtNDk2LjE1NiAtNTMuOTc1MSAtNTMuOTc1MSA0OTYuMTU2IDM2Ny44ODggMTI1LjA4NSkiLz48cGF0aCBkPSJNLjczNSAwaDAgMCAweiIgdHJhbnNmb3JtPSJtYXRyaXgoMTg1LjA3NiAxNzYuNDI3IDE3Ni40MjcgLTE4NS4wNzYgMTUzLjQ0NiA4MC4xNDg4KSIvPjwvZz48L2c+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJBIiB4MT0iMCIgeTE9IjAiIHgyPSIxIiB5Mj0iMCIgZ3JhZGllbnRUcmFuc2Zvcm09Im1hdHJpeCgxLC0zLjQ2OTQ1ZS0xOCwtMy40Njk0NWUtMTgsLTEsMCwtMy4wNTc2MWUtMDYpIiB4bGluazpocmVmPSIjRyI+PHN0b3Agb2Zmc2V0PSIwIiBzdG9wLWNvbG9yPSIjZTMzMjMyIi8+PHN0b3Agb2Zmc2V0PSIxIiBzdG9wLWNvbG9yPSIjNmIwMDBkIi8+PC9saW5lYXJHcmFkaWVudD48bGluZWFyR3JhZGllbnQgaWQ9IkIiIHgxPSIwIiB5MT0iMCIgeDI9IjEiIHkyPSIwIiB4bGluazpocmVmPSIjRyI+PHN0b3Agb2Zmc2V0PSIwIiBzdG9wLWNvbG9yPSIjZTMzMjMyIi8+PHN0b3Agb2Zmc2V0PSIxIiBzdG9wLWNvbG9yPSIjNmIwMDBkIi8+PC9saW5lYXJHcmFkaWVudD48bGluZWFyR3JhZGllbnQgaWQ9IkMiIHgxPSIwIiB5MT0iMCIgeDI9IjEiIHkyPSIwIiBncmFkaWVudFRyYW5zZm9ybT0ibWF0cml4KDEsLTEuMTEwMjJlLTE2LC0xLjExMDIyZS0xNiwtMSwwLC0yLjYxODYxZS0wNikiIHhsaW5rOmhyZWY9IiNHIj48c3RvcCBvZmZzZXQ9IjAiIHN0b3AtY29sb3I9IiNlMzMyMzIiLz48c3RvcCBvZmZzZXQ9IjEiIHN0b3AtY29sb3I9IiM2YjAwMGQiLz48L2xpbmVhckdyYWRpZW50PjxsaW5lYXJHcmFkaWVudCBpZD0iRCIgeDE9IjAiIHkxPSIwIiB4Mj0iMSIgeTI9IjAiIGdyYWRpZW50VHJhbnNmb3JtPSJtYXRyaXgoMSwtNS41NTExMmUtMTcsLTUuNTUxMTJlLTE3LC0xLDAsLTEuNTc1NjJlLTA2KSIgeGxpbms6aHJlZj0iI0ciPjxzdG9wIG9mZnNldD0iMCIgc3RvcC1jb2xvcj0iI2UzMzIzMiIvPjxzdG9wIG9mZnNldD0iMSIgc3RvcC1jb2xvcj0iIzZiMDAwZCIvPjwvbGluZWFyR3JhZGllbnQ+PGxpbmVhckdyYWRpZW50IGlkPSJFIiB4MT0iMCIgeTE9IjAiIHgyPSIxIiB5Mj0iMCIgZ3JhZGllbnRUcmFuc2Zvcm09Im1hdHJpeCgtMC44MDE4OTksLTAuNTk3NDYsLTAuNTk3NDYsMC44MDE4OTksMS4zNDk1LDAuNDQ3NDU3KSIgeGxpbms6aHJlZj0iI0ciPjxzdG9wIG9mZnNldD0iMCIgc3RvcC1jb2xvcj0iI2UzMzIzMiIvPjxzdG9wIG9mZnNldD0iMSIgc3RvcC1jb2xvcj0iIzZiMDAwZCIvPjwvbGluZWFyR3JhZGllbnQ+PGxpbmVhckdyYWRpZW50IGlkPSJGIiB4MT0iMCIgeTE9IjAiIHgyPSIxIiB5Mj0iMCIgZ3JhZGllbnRUcmFuc2Zvcm09Im1hdHJpeCgxLC0yLjc3NTU2ZS0xNywtMi43NzU1NmUtMTcsLTEsMCwtMS45MjgyNmUtMDYpIiB4bGluazpocmVmPSIjRyI+PHN0b3Agb2Zmc2V0PSIwIiBzdG9wLWNvbG9yPSIjZTMzMjMyIi8+PHN0b3Agb2Zmc2V0PSIxIiBzdG9wLWNvbG9yPSIjNmIwMDBkIi8+PC9saW5lYXJHcmFkaWVudD48bGluZWFyR3JhZGllbnQgaWQ9IkciIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIi8+PC9kZWZzPjwvc3ZnPg=="); +} diff --git a/v3/internal/assetserver/testdata/main.js b/v3/internal/assetserver/testdata/main.js new file mode 100644 index 000000000..274b4667c --- /dev/null +++ b/v3/internal/assetserver/testdata/main.js @@ -0,0 +1,20 @@ +import {ready} from '@wails/runtime'; + +ready(() => { + // Get input + focus + let nameElement = document.getElementById("name"); + nameElement.focus(); + + // Setup the greet function + window.greet = function () { + + // Get name + let name = nameElement.value; + + // Call App.Greet(name) + window.backend.main.App.Greet(name).then((result) => { + // Update result with data back from App.Greet() + document.getElementById("result").innerText = result; + }); + }; +}); \ No newline at end of file diff --git a/v3/internal/assetserver/testdata/subdir/index.html b/v3/internal/assetserver/testdata/subdir/index.html new file mode 100644 index 000000000..76da518f4 --- /dev/null +++ b/v3/internal/assetserver/testdata/subdir/index.html @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/v3/internal/assetserver/testdata/subdir/main.css b/v3/internal/assetserver/testdata/subdir/main.css new file mode 100644 index 000000000..57b00e6c6 --- /dev/null +++ b/v3/internal/assetserver/testdata/subdir/main.css @@ -0,0 +1,39 @@ + +html { + text-align: center; + color: white; + background-color: rgba(1, 1, 1, 0.1); +} + +body { + color: white; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif; + margin: 0; +} + +#result { + margin-top: 1rem; +} + +button { + -webkit-appearance: default-button; + padding: 6px; +} + +#name { + border-radius: 3px; + outline: none; + height: 20px; + -webkit-font-smoothing: antialiased; +} + +#logo { + width: 40%; + height: 40%; + padding-top: 20%; + margin: auto; + display: block; + background-position: center; + background-repeat: no-repeat; + background-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB2aWV3Qm94PSIwIDAgNTUxIDQzNiIgZmlsbC1ydWxlPSJldmVub2RkIiBzdHJva2UtbGluZWpvaW49InJvdW5kIiBzdHJva2UtbWl0ZXJsaW1pdD0iMiIgeG1sbnM6dj0iaHR0cHM6Ly92ZWN0YS5pby9uYW5vIj48ZyBmaWxsLXJ1bGU9Im5vbnplcm8iPjxwYXRoIGQ9Ik0xMDQuMDEgMzQ0LjM4OGgxOC40MDFsLTEuNzY4IDM5LjE2MSAxMi4xNDctMzkuMTYxaDE0Ljg2N2wtLjE4MSAzOS4xNjEgMTAuNTYxLTM5LjE2MWgxOC40MDFsLTIzLjI5NyA2Ni4xNzVoLTE2Ljk5N2wuMTgxLTQxLjM4My0xMi45MTcgNDEuMzgzaC0xNi45NTFsLTIuNDQ3LTY2LjE3NXptMTIwLjk3NSA0My4wNTloNy4zODhsLjIyNy0yNC41MjEtNy42MTUgMjQuNTIxem0tMjUuNzQ0IDIzLjExNmwyNC44ODMtNjYuMTc1aDIxLjgwMWw0LjY2NyA2Ni4xNzVoLTE4LjY3NGwuMDkyLTkuNzQ2aC0xMC45MjRsLTIuOTAxIDkuNzQ2aC0xOC45NDR6bTg4LjE4MyAwbDEwLjQ3LTY2LjE3NmgxOC40OTNsLTEwLjUxNiA2Ni4xNzUtMTguNDQ2LjAwMXptNjUuNzkzIDBsMTAuNTE2LTY2LjE3NWgxOC41MzZsLTcuODg2IDQ5Ljc2NmgxMy41NTJsLTIuNTgyIDE2LjQwOWgtMzIuMTM2em03NC43MjItMjAuMzUyYzIuMDU0IDEuNzIzIDQuMjE1IDMuMDUzIDYuNDgyIDMuOTlzNC40NCAxLjQwNCA2LjUyNiAxLjQwNGMxLjg0MyAwIDMuMzA4LS41MDYgNC4zOTYtMS41MThzMS42MzItMi4zOTUgMS42MzItNC4xNDhjMC0xLjUwOS0uNDU0LTMuMDEzLTEuMzU5LTQuNTA5cy0yLjY2LTMuNDgxLTUuMjU4LTUuOTU5Yy0zLjE0NC0zLjA1Mi01LjMwMy01Ljc0MS02LjQ4Mi04LjA2OXMtMS43NjYtNC44OTQtMS43NjYtNy43MDRjMC02LjMxNSAyLjAwMS0xMS4zMzIgNi4wMDUtMTUuMDQ4czkuNDM0LTUuNTc1IDE2LjI5NC01LjU3NWMyLjc4IDAgNS40MjIuMzEgNy45MzEuOTNzNS4wNiAxLjU3OSA3LjY2MSAyLjg3OGwtMi42MyAxNi4xMzZjLTEuOTk1LTEuMzktMy45MzUtMi40NDctNS44MjMtMy4xNzNzLTMuNjk0LTEuMDg5LTUuNDE3LTEuMDg5Yy0xLjU0MSAwLTIuNzU4LjQtMy42NDkgMS4ycy0xLjMzOCAxLjg5OC0xLjMzOCAzLjI4OGMwIDEuODc1IDEuNzA4IDQuNTAzIDUuMTIzIDcuODg2bC45OTcuOTk2YzMuNDQ1IDMuMzg2IDUuNzExIDYuMjg2IDYuNzk4IDguNzA1czEuNjMxIDUuMjA5IDEuNjMxIDguMzg0YzAgNy4wNzEtMi4xODMgMTIuNjQ2LTYuNTUgMTYuNzI0cy0xMC4zNDEgNi4xMi0xNy45MjUgNi4xMmMtMy4yMzQgMC02LjI5Mi0uMzg1LTkuMTc4LTEuMTU1cy01LjMxLTEuODM4LTcuMjc0LTMuMTk3bDMuMTczLTE3LjQ5NXoiIGZpbGw9IiNmZmYiLz48cGF0aCBkPSJNLjg4My0uMDgxTC4xMjEuMDgxLjI1Ni0uMDYzLjg4My0uMDgxeiIgZmlsbD0idXJsKCNBKSIgdHJhbnNmb3JtPSJtYXRyaXgoLTE2Ni41OTkgNC41NzEzMiA0LjU3MTMyIDE2Ni41OTkgMTQ3LjQwMyAxNjcuNjQ4KSIvPjxwYXRoIGQ9Ik0uODc4LS4yODVMLS4wNzMuNzEtMS4xODYuNTQyLjAxNS4yMDctLjg0Ni4wNzcuMzU1LS4yNThsLS44Ni0uMTNMLjY0OS0uNzFsLjIyOS40MjV6IiBmaWxsPSJ1cmwoI0IpIiB0cmFuc2Zvcm09Im1hdHJpeCgtMTA2LjQ0MyAtMTYuMDY2OSAtMTYuMDY2OSAxMDYuNDQzIDQyOC4xOSAxODguMDMzKSIvPjxwYXRoIGQ9Ik0uNDQtLjA0aDAgMEwuMjY1LS4wNTYuMTc3LjQzNy0uMzExLS4yNTUuMjYyLS40MzdoLjMwNkwuNDQtLjA0eiIgZmlsbD0idXJsKCNDKSIgdHJhbnNmb3JtPSJtYXRyaXgoLTExNC40ODQgLTE2Mi40MDggLTE2Mi40MDggMTE0LjQ4NCAzMzMuMjkxIDI4NS44MDQpIi8+PHBhdGggZD0iTS41IDBoMCAwIDB6IiBmaWxsPSJ1cmwoI0QpIiB0cmFuc2Zvcm09Im1hdHJpeCg2MS42OTE5IDU4LjgwOTEgNTguODA5MSAtNjEuNjkxOSAyNTguNjMxIDE4MC40MTMpIi8+PHBhdGggZD0iTS42MjItLjExNWguMTM5bC4wNDUuMTAyLjAyLjE5NS0uMjA0LS4yOTd6IiBmaWxsPSJ1cmwoI0UpIiB0cmFuc2Zvcm09Im1hdHJpeCgyMzguMTI2IDI5OC44OTMgMjk4Ljg5MyAtMjM4LjEyNiAxMTMuNTE2IC0xNTAuNTM2KSIvPjxwYXRoIGQ9Ik0uNDY3LjAwNUwuNDkuMDYyLjI3MS0uMDYyLjQ2Ny4wMDV6IiBmaWxsPSJ1cmwoI0YpIiB0cmFuc2Zvcm09Im1hdHJpeCgtMzY5LjUyOSAtOTcuNDExOCAtOTcuNDExOCAzNjkuNTI5IDU4Mi4zOCA5NC4wMjcpIi8+PGcgZmlsbD0idXJsKCNCKSI+PHBhdGggZD0iTS4yLjAwMWwuMDE5LS4wMTkuMzk1LjAzLS4wOTUuMDc3TC4yODIuMDY4LjIuMTM1LjQ2My4xOTQuMzc0LjI2Ni4xMzguMTg2aDAgMEwuMDQ3LjAzMy0uMTMxLS4yNjYuMi4wMDF6IiB0cmFuc2Zvcm09Im1hdHJpeCgtNDk2LjE1NiAtNTMuOTc1MSAtNTMuOTc1MSA0OTYuMTU2IDM2Ny44ODggMTI1LjA4NSkiLz48cGF0aCBkPSJNLjczNSAwaDAgMCAweiIgdHJhbnNmb3JtPSJtYXRyaXgoMTg1LjA3NiAxNzYuNDI3IDE3Ni40MjcgLTE4NS4wNzYgMTUzLjQ0NiA4MC4xNDg4KSIvPjwvZz48L2c+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJBIiB4MT0iMCIgeTE9IjAiIHgyPSIxIiB5Mj0iMCIgZ3JhZGllbnRUcmFuc2Zvcm09Im1hdHJpeCgxLC0zLjQ2OTQ1ZS0xOCwtMy40Njk0NWUtMTgsLTEsMCwtMy4wNTc2MWUtMDYpIiB4bGluazpocmVmPSIjRyI+PHN0b3Agb2Zmc2V0PSIwIiBzdG9wLWNvbG9yPSIjZTMzMjMyIi8+PHN0b3Agb2Zmc2V0PSIxIiBzdG9wLWNvbG9yPSIjNmIwMDBkIi8+PC9saW5lYXJHcmFkaWVudD48bGluZWFyR3JhZGllbnQgaWQ9IkIiIHgxPSIwIiB5MT0iMCIgeDI9IjEiIHkyPSIwIiB4bGluazpocmVmPSIjRyI+PHN0b3Agb2Zmc2V0PSIwIiBzdG9wLWNvbG9yPSIjZTMzMjMyIi8+PHN0b3Agb2Zmc2V0PSIxIiBzdG9wLWNvbG9yPSIjNmIwMDBkIi8+PC9saW5lYXJHcmFkaWVudD48bGluZWFyR3JhZGllbnQgaWQ9IkMiIHgxPSIwIiB5MT0iMCIgeDI9IjEiIHkyPSIwIiBncmFkaWVudFRyYW5zZm9ybT0ibWF0cml4KDEsLTEuMTEwMjJlLTE2LC0xLjExMDIyZS0xNiwtMSwwLC0yLjYxODYxZS0wNikiIHhsaW5rOmhyZWY9IiNHIj48c3RvcCBvZmZzZXQ9IjAiIHN0b3AtY29sb3I9IiNlMzMyMzIiLz48c3RvcCBvZmZzZXQ9IjEiIHN0b3AtY29sb3I9IiM2YjAwMGQiLz48L2xpbmVhckdyYWRpZW50PjxsaW5lYXJHcmFkaWVudCBpZD0iRCIgeDE9IjAiIHkxPSIwIiB4Mj0iMSIgeTI9IjAiIGdyYWRpZW50VHJhbnNmb3JtPSJtYXRyaXgoMSwtNS41NTExMmUtMTcsLTUuNTUxMTJlLTE3LC0xLDAsLTEuNTc1NjJlLTA2KSIgeGxpbms6aHJlZj0iI0ciPjxzdG9wIG9mZnNldD0iMCIgc3RvcC1jb2xvcj0iI2UzMzIzMiIvPjxzdG9wIG9mZnNldD0iMSIgc3RvcC1jb2xvcj0iIzZiMDAwZCIvPjwvbGluZWFyR3JhZGllbnQ+PGxpbmVhckdyYWRpZW50IGlkPSJFIiB4MT0iMCIgeTE9IjAiIHgyPSIxIiB5Mj0iMCIgZ3JhZGllbnRUcmFuc2Zvcm09Im1hdHJpeCgtMC44MDE4OTksLTAuNTk3NDYsLTAuNTk3NDYsMC44MDE4OTksMS4zNDk1LDAuNDQ3NDU3KSIgeGxpbms6aHJlZj0iI0ciPjxzdG9wIG9mZnNldD0iMCIgc3RvcC1jb2xvcj0iI2UzMzIzMiIvPjxzdG9wIG9mZnNldD0iMSIgc3RvcC1jb2xvcj0iIzZiMDAwZCIvPjwvbGluZWFyR3JhZGllbnQ+PGxpbmVhckdyYWRpZW50IGlkPSJGIiB4MT0iMCIgeTE9IjAiIHgyPSIxIiB5Mj0iMCIgZ3JhZGllbnRUcmFuc2Zvcm09Im1hdHJpeCgxLC0yLjc3NTU2ZS0xNywtMi43NzU1NmUtMTcsLTEsMCwtMS45MjgyNmUtMDYpIiB4bGluazpocmVmPSIjRyI+PHN0b3Agb2Zmc2V0PSIwIiBzdG9wLWNvbG9yPSIjZTMzMjMyIi8+PHN0b3Agb2Zmc2V0PSIxIiBzdG9wLWNvbG9yPSIjNmIwMDBkIi8+PC9saW5lYXJHcmFkaWVudD48bGluZWFyR3JhZGllbnQgaWQ9IkciIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIi8+PC9kZWZzPjwvc3ZnPg=="); +} diff --git a/v3/internal/assetserver/testdata/subdir/main.js b/v3/internal/assetserver/testdata/subdir/main.js new file mode 100644 index 000000000..274b4667c --- /dev/null +++ b/v3/internal/assetserver/testdata/subdir/main.js @@ -0,0 +1,20 @@ +import {ready} from '@wails/runtime'; + +ready(() => { + // Get input + focus + let nameElement = document.getElementById("name"); + nameElement.focus(); + + // Setup the greet function + window.greet = function () { + + // Get name + let name = nameElement.value; + + // Call App.Greet(name) + window.backend.main.App.Greet(name).then((result) => { + // Update result with data back from App.Greet() + document.getElementById("result").innerText = result; + }); + }; +}); \ No newline at end of file diff --git a/v3/internal/assetserver/testdata/testdata.go b/v3/internal/assetserver/testdata/testdata.go new file mode 100644 index 000000000..5387070ec --- /dev/null +++ b/v3/internal/assetserver/testdata/testdata.go @@ -0,0 +1,6 @@ +package testdata + +import "embed" + +//go:embed index.html main.css main.js +var TopLevelFS embed.FS diff --git a/v3/internal/assetserver/webview/request.go b/v3/internal/assetserver/webview/request.go new file mode 100644 index 000000000..18ff29890 --- /dev/null +++ b/v3/internal/assetserver/webview/request.go @@ -0,0 +1,17 @@ +package webview + +import ( + "io" + "net/http" +) + +type Request interface { + URL() (string, error) + Method() (string, error) + Header() (http.Header, error) + Body() (io.ReadCloser, error) + + Response() ResponseWriter + + Close() error +} diff --git a/v3/internal/assetserver/webview/request_darwin.go b/v3/internal/assetserver/webview/request_darwin.go new file mode 100644 index 000000000..f0e85780b --- /dev/null +++ b/v3/internal/assetserver/webview/request_darwin.go @@ -0,0 +1,248 @@ +//go:build darwin + +package webview + +/* +#cgo CFLAGS: -x objective-c +#cgo LDFLAGS: -framework Foundation -framework WebKit + +#import +#import +#include + +static void URLSchemeTaskRetain(void *wkUrlSchemeTask) { + id urlSchemeTask = (id) wkUrlSchemeTask; + [urlSchemeTask retain]; +} + +static void URLSchemeTaskRelease(void *wkUrlSchemeTask) { + id urlSchemeTask = (id) wkUrlSchemeTask; + [urlSchemeTask release]; +} + +static const char * URLSchemeTaskRequestURL(void *wkUrlSchemeTask) { + id urlSchemeTask = (id) wkUrlSchemeTask; + @autoreleasepool { + return [urlSchemeTask.request.URL.absoluteString UTF8String]; + } +} + +static const char * URLSchemeTaskRequestMethod(void *wkUrlSchemeTask) { + id urlSchemeTask = (id) wkUrlSchemeTask; + @autoreleasepool { + return [urlSchemeTask.request.HTTPMethod UTF8String]; + } +} + +static const char * URLSchemeTaskRequestHeadersJSON(void *wkUrlSchemeTask) { + id urlSchemeTask = (id) wkUrlSchemeTask; + @autoreleasepool { + NSData *headerData = [NSJSONSerialization dataWithJSONObject: urlSchemeTask.request.allHTTPHeaderFields options:0 error: nil]; + if (!headerData) { + return nil; + } + + NSString* headerString = [[[NSString alloc] initWithData:headerData encoding:NSUTF8StringEncoding] autorelease]; + const char * headerJSON = [headerString UTF8String]; + + return strdup(headerJSON); + } +} + +static bool URLSchemeTaskRequestBodyBytes(void *wkUrlSchemeTask, const void **body, int *bodyLen) { + id urlSchemeTask = (id) wkUrlSchemeTask; + @autoreleasepool { + if (!urlSchemeTask.request.HTTPBody) { + return false; + } + + *body = urlSchemeTask.request.HTTPBody.bytes; + *bodyLen = urlSchemeTask.request.HTTPBody.length; + return true; + } +} + +static bool URLSchemeTaskRequestBodyStreamOpen(void *wkUrlSchemeTask) { + id urlSchemeTask = (id) wkUrlSchemeTask; + @autoreleasepool { + if (!urlSchemeTask.request.HTTPBodyStream) { + return false; + } + + [urlSchemeTask.request.HTTPBodyStream open]; + return true; + } +} + +static void URLSchemeTaskRequestBodyStreamClose(void *wkUrlSchemeTask) { + id urlSchemeTask = (id) wkUrlSchemeTask; + @autoreleasepool { + if (!urlSchemeTask.request.HTTPBodyStream) { + return; + } + + [urlSchemeTask.request.HTTPBodyStream close]; + } +} + +static int URLSchemeTaskRequestBodyStreamRead(void *wkUrlSchemeTask, void *buf, int bufLen) { + id urlSchemeTask = (id) wkUrlSchemeTask; + + @autoreleasepool { + NSInputStream *stream = urlSchemeTask.request.HTTPBodyStream; + if (!stream) { + return -2; + } + + NSStreamStatus status = stream.streamStatus; + if (status == NSStreamStatusAtEnd || !stream.hasBytesAvailable) { + return 0; + } else if (status != NSStreamStatusOpen) { + return -3; + } + + return [stream read:buf maxLength:bufLen]; + } +} +*/ +import "C" + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "net/http" + "unsafe" +) + +// NewRequest creates as new WebViewRequest based on a pointer to an `id` +func NewRequest(wkURLSchemeTask unsafe.Pointer) Request { + C.URLSchemeTaskRetain(wkURLSchemeTask) + return newRequestFinalizer(&request{task: wkURLSchemeTask}) +} + +var _ Request = &request{} + +type request struct { + task unsafe.Pointer + + header http.Header + body io.ReadCloser + rw *responseWriter +} + +func (r *request) URL() (string, error) { + return C.GoString(C.URLSchemeTaskRequestURL(r.task)), nil +} + +func (r *request) Method() (string, error) { + return C.GoString(C.URLSchemeTaskRequestMethod(r.task)), nil +} + +func (r *request) Header() (http.Header, error) { + if r.header != nil { + return r.header, nil + } + + header := http.Header{} + if cHeaders := C.URLSchemeTaskRequestHeadersJSON(r.task); cHeaders != nil { + if headers := C.GoString(cHeaders); headers != "" { + var h map[string]string + if err := json.Unmarshal([]byte(headers), &h); err != nil { + return nil, fmt.Errorf("unable to unmarshal request headers: %s", err) + } + + for k, v := range h { + header.Add(k, v) + } + } + C.free(unsafe.Pointer(cHeaders)) + } + r.header = header + return header, nil +} + +func (r *request) Body() (io.ReadCloser, error) { + if r.body != nil { + return r.body, nil + } + + var body unsafe.Pointer + var bodyLen C.int + if C.URLSchemeTaskRequestBodyBytes(r.task, &body, &bodyLen) { + if body != nil && bodyLen > 0 { + r.body = io.NopCloser(bytes.NewReader(C.GoBytes(body, bodyLen))) + } else { + r.body = http.NoBody + } + } else if C.URLSchemeTaskRequestBodyStreamOpen(r.task) { + r.body = &requestBodyStreamReader{task: r.task} + } + + return r.body, nil +} + +func (r *request) Response() ResponseWriter { + if r.rw != nil { + return r.rw + } + + r.rw = &responseWriter{r: r} + return r.rw +} + +func (r *request) Close() error { + var err error + if r.body != nil { + err = r.body.Close() + } + r.Response().Finish() + C.URLSchemeTaskRelease(r.task) + return err +} + +var _ io.ReadCloser = &requestBodyStreamReader{} + +type requestBodyStreamReader struct { + task unsafe.Pointer + closed bool +} + +// Read implements io.Reader +func (r *requestBodyStreamReader) Read(p []byte) (n int, err error) { + var content unsafe.Pointer + var contentLen int + if p != nil { + content = unsafe.Pointer(&p[0]) + contentLen = len(p) + } + + res := C.URLSchemeTaskRequestBodyStreamRead(r.task, content, C.int(contentLen)) + if res > 0 { + return int(res), nil + } + + switch res { + case 0: + return 0, io.EOF + case -1: + return 0, fmt.Errorf("body: stream error") + case -2: + return 0, fmt.Errorf("body: no stream defined") + case -3: + return 0, io.ErrClosedPipe + default: + return 0, fmt.Errorf("body: unknown error %d", res) + } +} + +func (r *requestBodyStreamReader) Close() error { + if r.closed { + return nil + } + r.closed = true + + C.URLSchemeTaskRequestBodyStreamClose(r.task) + return nil +} diff --git a/v3/internal/assetserver/webview/request_finalizer.go b/v3/internal/assetserver/webview/request_finalizer.go new file mode 100644 index 000000000..6a8c6a928 --- /dev/null +++ b/v3/internal/assetserver/webview/request_finalizer.go @@ -0,0 +1,40 @@ +package webview + +import ( + "runtime" + "sync/atomic" +) + +var _ Request = &requestFinalizer{} + +type requestFinalizer struct { + Request + closed int32 +} + +// newRequestFinalizer returns a request with a runtime finalizer to make sure it will be closed from the finalizer +// if it has not been already closed. +// It also makes sure Close() of the wrapping request is only called once. +func newRequestFinalizer(r Request) Request { + rf := &requestFinalizer{Request: r} + // Make sure to async release since it might block the finalizer goroutine for a longer period + runtime.SetFinalizer(rf, func(obj *requestFinalizer) { rf.close(true) }) + return rf +} + +func (r *requestFinalizer) Close() error { + return r.close(false) +} + +func (r *requestFinalizer) close(asyncRelease bool) error { + if atomic.CompareAndSwapInt32(&r.closed, 0, 1) { + runtime.SetFinalizer(r, nil) + if asyncRelease { + go r.Request.Close() + return nil + } else { + return r.Request.Close() + } + } + return nil +} diff --git a/v3/internal/assetserver/webview/request_linux.go b/v3/internal/assetserver/webview/request_linux.go new file mode 100644 index 000000000..101ee12fb --- /dev/null +++ b/v3/internal/assetserver/webview/request_linux.go @@ -0,0 +1,83 @@ +//go:build linux +// +build linux + +package webview + +/* +#cgo linux pkg-config: gtk+-3.0 webkit2gtk-4.0 gio-unix-2.0 + +#include "gtk/gtk.h" +#include "webkit2/webkit2.h" +*/ +import "C" + +import ( + "io" + "net/http" + "unsafe" +) + +// NewRequest creates as new WebViewRequest based on a pointer to an `WebKitURISchemeRequest` +func NewRequest(webKitURISchemeRequest unsafe.Pointer) Request { + webkitReq := (*C.WebKitURISchemeRequest)(webKitURISchemeRequest) + C.g_object_ref(C.gpointer(webkitReq)) + + req := &request{req: webkitReq} + return newRequestFinalizer(req) +} + +var _ Request = &request{} + +type request struct { + req *C.WebKitURISchemeRequest + + header http.Header + body io.ReadCloser + rw *responseWriter +} + +func (r *request) URL() (string, error) { + return C.GoString(C.webkit_uri_scheme_request_get_uri(r.req)), nil +} + +func (r *request) Method() (string, error) { + return webkit_uri_scheme_request_get_http_method(r.req), nil +} + +func (r *request) Header() (http.Header, error) { + if r.header != nil { + return r.header, nil + } + + r.header = webkit_uri_scheme_request_get_http_headers(r.req) + return r.header, nil +} + +func (r *request) Body() (io.ReadCloser, error) { + if r.body != nil { + return r.body, nil + } + + r.body = webkit_uri_scheme_request_get_http_body(r.req) + + return r.body, nil +} + +func (r *request) Response() ResponseWriter { + if r.rw != nil { + return r.rw + } + + r.rw = &responseWriter{req: r.req} + return r.rw +} + +func (r *request) Close() error { + var err error + if r.body != nil { + err = r.body.Close() + } + r.Response().Finish() + C.g_object_unref(C.gpointer(r.req)) + return err +} diff --git a/v3/internal/assetserver/webview/request_linux_purego.go b/v3/internal/assetserver/webview/request_linux_purego.go new file mode 100644 index 000000000..bf724a55b --- /dev/null +++ b/v3/internal/assetserver/webview/request_linux_purego.go @@ -0,0 +1,94 @@ +//go:build linux && purego +// +build linux,purego + +package webview + +import ( + "io" + "net/http" + + "github.com/ebitengine/purego" +) + +// NewRequest creates as new WebViewRequest based on a pointer to an `WebKitURISchemeRequest` +// +// Please make sure to call Release() when finished using the request. +func NewRequest(webKitURISchemeRequest uintptr) Request { + webkitReq := webKitURISchemeRequest + req := &request{req: webkitReq} + req.AddRef() + return req +} + +var _ Request = &request{} + +type request struct { + req uintptr + + header http.Header + body io.ReadCloser + rw *responseWriter +} + +func (r *request) AddRef() error { + var objectRef func(uintptr) + purego.RegisterLibFunc(&objectRef, gtk, "g_object_ref") + objectRef(r.req) + return nil +} + +func (r *request) Release() error { + var objectUnref func(uintptr) + purego.RegisterLibFunc(&objectUnref, gtk, "g_object_unref") + objectUnref(r.req) + return nil +} + +func (r *request) URL() (string, error) { + var getUri func(uintptr) string + purego.RegisterLibFunc(&getUri, webkit, "webkit_uri_scheme_request_get_uri") + return getUri(r.req), nil +} + +func (r *request) Method() (string, error) { + return webkit_uri_scheme_request_get_http_method(r.req), nil +} + +func (r *request) Header() (http.Header, error) { + if r.header != nil { + return r.header, nil + } + + r.header = webkit_uri_scheme_request_get_http_headers(r.req) + return r.header, nil +} + +func (r *request) Body() (io.ReadCloser, error) { + if r.body != nil { + return r.body, nil + } + + // WebKit2GTK has currently no support for request bodies. + r.body = http.NoBody + + return r.body, nil +} + +func (r *request) Response() ResponseWriter { + if r.rw != nil { + return r.rw + } + + r.rw = &responseWriter{req: r.req} + return r.rw +} + +func (r *request) Close() error { + var err error + if r.body != nil { + err = r.body.Close() + } + r.Response().Finish() + r.Release() + return err +} diff --git a/v3/internal/assetserver/webview/request_windows.go b/v3/internal/assetserver/webview/request_windows.go new file mode 100644 index 000000000..77c51727a --- /dev/null +++ b/v3/internal/assetserver/webview/request_windows.go @@ -0,0 +1,216 @@ +//go:build windows + +package webview + +import ( + "fmt" + "io" + "net/http" + "strings" + + "github.com/wailsapp/go-webview2/pkg/edge" +) + +// NewRequest creates as new WebViewRequest for chromium. This Method must be called from the Main-Thread! +func NewRequest(env *edge.ICoreWebView2Environment, args *edge.ICoreWebView2WebResourceRequestedEventArgs, invokeSync func(fn func())) (Request, error) { + req, err := args.GetRequest() + if err != nil { + return nil, fmt.Errorf("GetRequest failed: %s", err) + } + defer req.Release() + + r := &request{ + invokeSync: invokeSync, + } + + code := http.StatusInternalServerError + r.response, err = env.CreateWebResourceResponse(nil, code, http.StatusText(code), "") + if err != nil { + return nil, fmt.Errorf("CreateWebResourceResponse failed: %s", err) + } + + if err := args.PutResponse(r.response); err != nil { + r.finishResponse() + return nil, fmt.Errorf("PutResponse failed: %s", err) + } + + r.deferral, err = args.GetDeferral() + if err != nil { + r.finishResponse() + return nil, fmt.Errorf("GetDeferral failed: %s", err) + } + + r.url, r.urlErr = req.GetUri() + r.method, r.methodErr = req.GetMethod() + r.header, r.headerErr = getHeaders(req) + + if content, err := req.GetContent(); err != nil { + r.bodyErr = err + } else if content != nil { + // It is safe to access Content from another Thread: https://learn.microsoft.com/en-us/microsoft-edge/webview2/concepts/threading-model#thread-safety + r.body = &iStreamReleaseCloser{stream: content} + } + + return r, nil +} + +var _ Request = &request{} + +type request struct { + response *edge.ICoreWebView2WebResourceResponse + deferral *edge.ICoreWebView2Deferral + + url string + urlErr error + + method string + methodErr error + + header http.Header + headerErr error + + body io.ReadCloser + bodyErr error + rw *responseWriter + + invokeSync func(fn func()) +} + +func (r *request) URL() (string, error) { + return r.url, r.urlErr +} + +func (r *request) Method() (string, error) { + return r.method, r.methodErr +} + +func (r *request) Header() (http.Header, error) { + return r.header, r.headerErr +} + +func (r *request) Body() (io.ReadCloser, error) { + return r.body, r.bodyErr +} + +func (r *request) Response() ResponseWriter { + if r.rw != nil { + return r.rw + } + + r.rw = &responseWriter{req: r} + return r.rw +} + +func (r *request) Close() error { + var errs []error + if r.body != nil { + if err := r.body.Close(); err != nil { + errs = append(errs, err) + } + r.body = nil + } + + if err := r.Response().Finish(); err != nil { + errs = append(errs, err) + } + + return combineErrs(errs) +} + +// finishResponse must be called on the main-thread +func (r *request) finishResponse() error { + var errs []error + if r.response != nil { + if err := r.response.Release(); err != nil { + errs = append(errs, err) + } + r.response = nil + } + if r.deferral != nil { + if err := r.deferral.Complete(); err != nil { + errs = append(errs, err) + } + + if err := r.deferral.Release(); err != nil { + errs = append(errs, err) + } + r.deferral = nil + } + return combineErrs(errs) +} + +type iStreamReleaseCloser struct { + stream *edge.IStream + closed bool +} + +func (i *iStreamReleaseCloser) Read(p []byte) (int, error) { + if i.closed { + return 0, io.ErrClosedPipe + } + return i.stream.Read(p) +} + +func (i *iStreamReleaseCloser) Close() error { + if i.closed { + return nil + } + i.closed = true + return i.stream.Release() +} + +func getHeaders(req *edge.ICoreWebView2WebResourceRequest) (http.Header, error) { + header := http.Header{} + headers, err := req.GetHeaders() + if err != nil { + return nil, fmt.Errorf("GetHeaders Error: %s", err) + } + defer headers.Release() + + headersIt, err := headers.GetIterator() + if err != nil { + return nil, fmt.Errorf("GetIterator Error: %s", err) + } + defer headersIt.Release() + + for { + has, err := headersIt.HasCurrentHeader() + if err != nil { + return nil, fmt.Errorf("HasCurrentHeader Error: %s", err) + } + if !has { + break + } + + name, value, err := headersIt.GetCurrentHeader() + if err != nil { + return nil, fmt.Errorf("GetCurrentHeader Error: %s", err) + } + + header.Set(name, value) + if _, err := headersIt.MoveNext(); err != nil { + return nil, fmt.Errorf("MoveNext Error: %s", err) + } + } + + // WebView2 has problems when a request returns a 304 status code and the WebView2 is going to hang for other + // requests including IPC calls. + // So prevent 304 status codes by removing the headers that are used in combinationwith caching. + header.Del("If-Modified-Since") + header.Del("If-None-Match") + return header, nil +} + +func combineErrs(errs []error) error { + // TODO use Go1.20 errors.Join + if len(errs) == 0 { + return nil + } + + errStrings := make([]string, len(errs)) + for i, err := range errs { + errStrings[i] = err.Error() + } + + return fmt.Errorf(strings.Join(errStrings, "\n")) +} diff --git a/v3/internal/assetserver/webview/responsewriter.go b/v3/internal/assetserver/webview/responsewriter.go new file mode 100644 index 000000000..2fc7ede51 --- /dev/null +++ b/v3/internal/assetserver/webview/responsewriter.go @@ -0,0 +1,28 @@ +package webview + +import ( + "errors" + "net/http" +) + +const ( + HeaderContentLength = "Content-Length" + HeaderContentType = "Content-Type" +) + +var ( + errRequestStopped = errors.New("request has been stopped") + errResponseFinished = errors.New("response has been finished") +) + +// A ResponseWriter interface is used by an HTTP handler to +// construct an HTTP response for the WebView. +type ResponseWriter interface { + http.ResponseWriter + + // Finish the response and flush all data. A Finish after the request has already been finished has no effect. + Finish() error + + // Code returns the HTTP status code of the response + Code() int +} diff --git a/v3/internal/assetserver/webview/responsewriter_darwin.go b/v3/internal/assetserver/webview/responsewriter_darwin.go new file mode 100644 index 000000000..499ae6f9f --- /dev/null +++ b/v3/internal/assetserver/webview/responsewriter_darwin.go @@ -0,0 +1,155 @@ +//go:build darwin + +package webview + +/* +#cgo CFLAGS: -x objective-c +#cgo LDFLAGS: -framework Foundation -framework WebKit + +#import +#import + +typedef void (^schemeTaskCaller)(id); + +static bool urlSchemeTaskCall(void *wkUrlSchemeTask, schemeTaskCaller fn) { + id urlSchemeTask = (id) wkUrlSchemeTask; + if (urlSchemeTask == nil) { + return false; + } + + @autoreleasepool { + @try { + fn(urlSchemeTask); + } @catch (NSException *exception) { + // This is very bad to detect a stopped schemeTask this should be implemented in a better way + // But it seems to be very tricky to not deadlock when keeping a lock curing executing fn() + // It seems like those call switch the thread back to the main thread and then deadlocks when they reentrant want + // to get the lock again to start another request or stop it. + if ([exception.reason isEqualToString: @"This task has already been stopped"]) { + return false; + } + + @throw exception; + } + + return true; + } +} + +static bool URLSchemeTaskDidReceiveData(void *wkUrlSchemeTask, void* data, int datalength) { + return urlSchemeTaskCall( + wkUrlSchemeTask, + ^(id urlSchemeTask) { + NSData *nsdata = [NSData dataWithBytes:data length:datalength]; + [urlSchemeTask didReceiveData:nsdata]; + }); +} + +static bool URLSchemeTaskDidFinish(void *wkUrlSchemeTask) { + return urlSchemeTaskCall( + wkUrlSchemeTask, + ^(id urlSchemeTask) { + [urlSchemeTask didFinish]; + }); +} + +static bool URLSchemeTaskDidReceiveResponse(void *wkUrlSchemeTask, int statusCode, void *headersString, int headersStringLength) { + return urlSchemeTaskCall( + wkUrlSchemeTask, + ^(id urlSchemeTask) { + NSData *nsHeadersJSON = [NSData dataWithBytes:headersString length:headersStringLength]; + NSDictionary *headerFields = [NSJSONSerialization JSONObjectWithData:nsHeadersJSON options: NSJSONReadingMutableContainers error: nil]; + NSHTTPURLResponse *response = [[[NSHTTPURLResponse alloc] initWithURL:urlSchemeTask.request.URL statusCode:statusCode HTTPVersion:@"HTTP/1.1" headerFields:headerFields] autorelease]; + + [urlSchemeTask didReceiveResponse:response]; + }); +} +*/ +import "C" + +import ( + "encoding/json" + "net/http" + "unsafe" +) + +var _ ResponseWriter = &responseWriter{} + +type responseWriter struct { + r *request + + header http.Header + wroteHeader bool + code int + + finished bool +} + +func (rw *responseWriter) Header() http.Header { + if rw.header == nil { + rw.header = http.Header{} + } + return rw.header +} + +func (rw *responseWriter) Write(buf []byte) (int, error) { + if rw.finished { + return 0, errResponseFinished + } + + rw.WriteHeader(http.StatusOK) + + var content unsafe.Pointer + var contentLen int + if buf != nil { + content = unsafe.Pointer(&buf[0]) + contentLen = len(buf) + } + + if !C.URLSchemeTaskDidReceiveData(rw.r.task, content, C.int(contentLen)) { + return 0, errRequestStopped + } + return contentLen, nil +} + +func (rw *responseWriter) WriteHeader(code int) { + rw.code = code + if rw.wroteHeader || rw.finished { + return + } + rw.wroteHeader = true + + header := map[string]string{} + for k := range rw.Header() { + header[k] = rw.Header().Get(k) + } + headerData, _ := json.Marshal(header) + + var headers unsafe.Pointer + var headersLen int + if len(headerData) != 0 { + headers = unsafe.Pointer(&headerData[0]) + headersLen = len(headerData) + } + + C.URLSchemeTaskDidReceiveResponse(rw.r.task, C.int(code), headers, C.int(headersLen)) +} + +func (rw *responseWriter) Finish() error { + if !rw.wroteHeader { + rw.WriteHeader(http.StatusNotImplemented) + } + + if rw.finished { + return nil + } + rw.finished = true + + C.URLSchemeTaskDidFinish(rw.r.task) + + return nil +} + +func (rw *responseWriter) Code() int { + return rw.code +} diff --git a/v3/internal/assetserver/webview/responsewriter_linux.go b/v3/internal/assetserver/webview/responsewriter_linux.go new file mode 100644 index 000000000..8d93b6388 --- /dev/null +++ b/v3/internal/assetserver/webview/responsewriter_linux.go @@ -0,0 +1,135 @@ +//go:build linux + +package webview + +/* +#cgo linux pkg-config: gtk+-3.0 webkit2gtk-4.0 gio-unix-2.0 + +#include "gtk/gtk.h" +#include "webkit2/webkit2.h" +#include "gio/gunixinputstream.h" + +*/ +import "C" +import ( + "fmt" + "io" + "net/http" + "os" + "strconv" + "syscall" + "unsafe" +) + +type responseWriter struct { + req *C.WebKitURISchemeRequest + + header http.Header + wroteHeader bool + finished bool + code int + + w io.WriteCloser + wErr error +} + +func (rw *responseWriter) Code() int { + return rw.code +} + +func (rw *responseWriter) Header() http.Header { + if rw.header == nil { + rw.header = http.Header{} + } + return rw.header +} + +func (rw *responseWriter) Write(buf []byte) (int, error) { + if rw.finished { + return 0, errResponseFinished + } + + rw.WriteHeader(http.StatusOK) + if rw.wErr != nil { + return 0, rw.wErr + } + return rw.w.Write(buf) +} + +func (rw *responseWriter) WriteHeader(code int) { + rw.code = code + if rw.wroteHeader || rw.finished { + return + } + rw.wroteHeader = true + + contentLength := int64(-1) + if sLen := rw.Header().Get(HeaderContentLength); sLen != "" { + if pLen, _ := strconv.ParseInt(sLen, 10, 64); pLen > 0 { + contentLength = pLen + } + } + + // We can't use os.Pipe here, because that returns files with a finalizer for closing the FD. But the control over the + // read FD is given to the InputStream and will be closed there. + // Furthermore we especially don't want to have the FD_CLOEXEC + rFD, w, err := pipe() + if err != nil { + rw.finishWithError(http.StatusInternalServerError, fmt.Errorf("unable to open pipe: %s", err)) + return + } + rw.w = w + + stream := C.g_unix_input_stream_new(C.int(rFD), C.gboolean(1)) + defer C.g_object_unref(C.gpointer(stream)) + + if err := webkit_uri_scheme_request_finish(rw.req, code, rw.Header(), stream, contentLength); err != nil { + rw.finishWithError(http.StatusInternalServerError, fmt.Errorf("unable to finish request: %s", err)) + return + } +} + +func (rw *responseWriter) Finish() error { + if !rw.wroteHeader { + rw.WriteHeader(http.StatusNotImplemented) + } + + if rw.finished { + return nil + } + rw.finished = true + if rw.w != nil { + rw.w.Close() + } + return nil +} + +func (rw *responseWriter) finishWithError(code int, err error) { + if rw.w != nil { + rw.w.Close() + rw.w = &nopCloser{io.Discard} + } + rw.wErr = err + + msg := C.CString(err.Error()) + gerr := C.g_error_new_literal(C.g_quark_from_string(msg), C.int(code), msg) + C.webkit_uri_scheme_request_finish_error(rw.req, gerr) + C.g_error_free(gerr) + C.free(unsafe.Pointer(msg)) +} + +type nopCloser struct { + io.Writer +} + +func (nopCloser) Close() error { return nil } + +func pipe() (r int, w *os.File, err error) { + var p [2]int + e := syscall.Pipe2(p[0:], 0) + if e != nil { + return 0, nil, fmt.Errorf("pipe2: %s", e) + } + + return p[0], os.NewFile(uintptr(p[1]), "|1"), nil +} diff --git a/v3/internal/assetserver/webview/responsewriter_linux_purego.go b/v3/internal/assetserver/webview/responsewriter_linux_purego.go new file mode 100644 index 000000000..6742d1bda --- /dev/null +++ b/v3/internal/assetserver/webview/responsewriter_linux_purego.go @@ -0,0 +1,180 @@ +//go:build linux && purego +// +build linux,purego + +package webview + +import ( + "fmt" + "io" + "net/http" + "os" + "strconv" + "syscall" + + "github.com/ebitengine/purego" +) + +const ( + gtk3 = "libgtk-3.so" + gtk4 = "libgtk-4.so" +) + +var ( + gtk uintptr + webkit uintptr + version int +) + +func init() { + var err error + // gtk, err = purego.Dlopen(gtk4, purego.RTLD_NOW|purego.RTLD_GLOBAL) + // if err == nil { + // version = 4 + // return + // } + // log.Println("Failed to open GTK4: Falling back to GTK3") + gtk, err = purego.Dlopen(gtk3, purego.RTLD_NOW|purego.RTLD_GLOBAL) + if err != nil { + panic(err) + } + version = 3 + + var webkit4 string = "libwebkit2gtk-4.1.so" + webkit, err = purego.Dlopen(webkit4, purego.RTLD_NOW|purego.RTLD_GLOBAL) + if err != nil { + panic(err) + } +} + +type responseWriter struct { + req uintptr + + header http.Header + wroteHeader bool + finished bool + code int + w io.WriteCloser + wErr error +} + +func (rw *responseWriter) Code() int { + return rw.code +} + +func (rw *responseWriter) Header() http.Header { + if rw.header == nil { + rw.header = http.Header{} + } + return rw.header +} + +func (rw *responseWriter) Write(buf []byte) (int, error) { + if rw.finished { + return 0, errResponseFinished + } + + rw.WriteHeader(http.StatusOK) + if rw.wErr != nil { + return 0, rw.wErr + } + return rw.w.Write(buf) +} + +func (rw *responseWriter) WriteHeader(code int) { + rw.code = code + + // TODO? Is this ever called? I don't think so! + if rw.wroteHeader || rw.finished { + return + } + rw.wroteHeader = true + + contentLength := int64(-1) + if sLen := rw.Header().Get(HeaderContentLength); sLen != "" { + if pLen, _ := strconv.ParseInt(sLen, 10, 64); pLen > 0 { + contentLength = pLen + } + } + // We can't use os.Pipe here, because that returns files with a finalizer for closing the FD. But the control over the + // read FD is given to the InputStream and will be closed there. + // Furthermore we especially don't want to have the FD_CLOEXEC + rFD, w, err := pipe() + if err != nil { + rw.finishWithError(http.StatusInternalServerError, fmt.Errorf("unable to open pipe: %s", err)) + return + } + rw.w = w + + var newStream func(int, bool) uintptr + purego.RegisterLibFunc(&newStream, gtk, "g_unix_input_stream_new") + var unRef func(uintptr) + purego.RegisterLibFunc(&unRef, gtk, "g_object_unref") + stream := newStream(rFD, true) + + /* var reqFinish func(uintptr, uintptr, uintptr, uintptr, int64) int + purego.RegisterLibFunc(&reqFinish, webkit, "webkit_uri_scheme_request_finish") + + header := rw.Header() + defer unRef(stream) + if err := reqFinish(rw.req, code, header, stream, contentLength); err != nil { + rw.finishWithError(http.StatusInternalServerError, fmt.Errorf("unable to finish request: %s", err)) + } + */ + if err := webkit_uri_scheme_request_finish(rw.req, code, rw.Header(), stream, contentLength); err != nil { + rw.finishWithError(http.StatusInternalServerError, fmt.Errorf("unable to finish request: %s", err)) + return + } +} + +func (rw *responseWriter) Finish() { + if !rw.wroteHeader { + rw.WriteHeader(http.StatusNotImplemented) + } + + if rw.finished { + return + } + rw.finished = true + if rw.w != nil { + rw.w.Close() + } +} + +func (rw *responseWriter) finishWithError(code int, err error) { + if rw.w != nil { + rw.w.Close() + rw.w = &nopCloser{io.Discard} + } + rw.wErr = err + + var newLiteral func(uint32, string, int, string) uintptr // is this correct? + purego.RegisterLibFunc(&newLiteral, gtk, "g_error_new_literal") + var newQuark func(string) uintptr + purego.RegisterLibFunc(&newQuark, gtk, "g_quark_from_string") + var freeError func(uintptr) + purego.RegisterLibFunc(&freeError, gtk, "g_error_free") + var finishError func(uintptr, uintptr) + purego.RegisterLibFunc(&finishError, webkit, "webkit_uri_scheme_request_finish_error") + + msg := string(err.Error()) + //gquark := newQuark(msg) + gerr := newLiteral(1, msg, code, msg) + finishError(rw.req, gerr) + freeError(gerr) +} + +type nopCloser struct { + io.Writer +} + +func (nopCloser) Close() error { return nil } + +func pipe() (r int, w *os.File, err error) { + var p [2]int + e := syscall.Pipe2(p[0:], 0) + if e != nil { + return 0, nil, fmt.Errorf("pipe2: %s", e) + } + + return p[0], os.NewFile(uintptr(p[1]), "|1"), nil +} diff --git a/v3/internal/assetserver/webview/responsewriter_windows.go b/v3/internal/assetserver/webview/responsewriter_windows.go new file mode 100644 index 000000000..09bec236f --- /dev/null +++ b/v3/internal/assetserver/webview/responsewriter_windows.go @@ -0,0 +1,108 @@ +//go:build windows + +package webview + +import ( + "bytes" + "fmt" + "net/http" + "strings" +) + +var _ http.ResponseWriter = &responseWriter{} + +type responseWriter struct { + req *request + + header http.Header + wroteHeader bool + code int + body *bytes.Buffer + + finished bool +} + +func (rw *responseWriter) Header() http.Header { + if rw.header == nil { + rw.header = http.Header{} + } + return rw.header +} + +func (rw *responseWriter) Write(buf []byte) (int, error) { + if rw.finished { + return 0, errResponseFinished + } + + rw.WriteHeader(http.StatusOK) + + return rw.body.Write(buf) +} + +func (rw *responseWriter) WriteHeader(code int) { + if rw.wroteHeader || rw.finished { + return + } + rw.wroteHeader = true + + if rw.body == nil { + rw.body = &bytes.Buffer{} + } + + rw.code = code +} + +func (rw *responseWriter) Finish() error { + if !rw.wroteHeader { + rw.WriteHeader(http.StatusNotImplemented) + } + + if rw.finished { + return nil + } + rw.finished = true + + var errs []error + + code := rw.code + if code == http.StatusNotModified { + // WebView2 has problems when a request returns a 304 status code and the WebView2 is going to hang for other + // requests including IPC calls. + errs = append(errs, fmt.Errorf("AssetServer returned 304 - StatusNotModified which are going to hang WebView2, changed code to 505 - StatusInternalServerError")) + code = http.StatusInternalServerError + } + + rw.req.invokeSync(func() { + resp := rw.req.response + + hdrs, err := resp.GetHeaders() + if err != nil { + errs = append(errs, fmt.Errorf("Resp.GetHeaders failed: %s", err)) + } else { + for k, v := range rw.header { + if err := hdrs.AppendHeader(k, strings.Join(v, ",")); err != nil { + errs = append(errs, fmt.Errorf("Resp.AppendHeader failed: %s", err)) + } + } + hdrs.Release() + } + + if err := resp.PutStatusCode(code); err != nil { + errs = append(errs, fmt.Errorf("Resp.PutStatusCode failed: %s", err)) + } + + if err := resp.PutByteContent(rw.body.Bytes()); err != nil { + errs = append(errs, fmt.Errorf("Resp.PutByteContent failed: %s", err)) + } + + if err := rw.req.finishResponse(); err != nil { + errs = append(errs, fmt.Errorf("Resp.finishResponse failed: %s", err)) + } + }) + + return combineErrs(errs) +} + +func (rw *responseWriter) Code() int { + return rw.code +} diff --git a/v3/internal/assetserver/webview/webkit2_36+.go b/v3/internal/assetserver/webview/webkit2_36+.go new file mode 100644 index 000000000..2c1a79c43 --- /dev/null +++ b/v3/internal/assetserver/webview/webkit2_36+.go @@ -0,0 +1,69 @@ +//go:build linux && (webkit2_36 || webkit2_40) + +package webview + +/* +#cgo linux pkg-config: gtk+-3.0 webkit2gtk-4.0 libsoup-2.4 + +#include "gtk/gtk.h" +#include "webkit2/webkit2.h" +#include "libsoup/soup.h" +*/ +import "C" + +import ( + "net/http" + "strings" + "unsafe" +) + +func webkit_uri_scheme_request_get_http_method(req *C.WebKitURISchemeRequest) string { + method := C.GoString(C.webkit_uri_scheme_request_get_http_method(req)) + return strings.ToUpper(method) +} + +func webkit_uri_scheme_request_get_http_headers(req *C.WebKitURISchemeRequest) http.Header { + hdrs := C.webkit_uri_scheme_request_get_http_headers(req) + + var iter C.SoupMessageHeadersIter + C.soup_message_headers_iter_init(&iter, hdrs) + + var name *C.char + var value *C.char + + h := http.Header{} + for C.soup_message_headers_iter_next(&iter, &name, &value) != 0 { + h.Add(C.GoString(name), C.GoString(value)) + } + + return h +} + +func webkit_uri_scheme_request_finish(req *C.WebKitURISchemeRequest, code int, header http.Header, stream *C.GInputStream, streamLength int64) error { + resp := C.webkit_uri_scheme_response_new(stream, C.gint64(streamLength)) + defer C.g_object_unref(C.gpointer(resp)) + + cReason := C.CString(http.StatusText(code)) + C.webkit_uri_scheme_response_set_status(resp, C.guint(code), cReason) + C.free(unsafe.Pointer(cReason)) + + cMimeType := C.CString(header.Get(HeaderContentType)) + C.webkit_uri_scheme_response_set_content_type(resp, cMimeType) + C.free(unsafe.Pointer(cMimeType)) + + hdrs := C.soup_message_headers_new(C.SOUP_MESSAGE_HEADERS_RESPONSE) + for name, values := range header { + cName := C.CString(name) + for _, value := range values { + cValue := C.CString(value) + C.soup_message_headers_append(hdrs, cName, cValue) + C.free(unsafe.Pointer(cValue)) + } + C.free(unsafe.Pointer(cName)) + } + + C.webkit_uri_scheme_response_set_http_headers(resp, hdrs) + + C.webkit_uri_scheme_request_finish_with_response(req, resp) + return nil +} diff --git a/v3/internal/assetserver/webview/webkit2_36+_purego.go b/v3/internal/assetserver/webview/webkit2_36+_purego.go new file mode 100644 index 000000000..2386868c3 --- /dev/null +++ b/v3/internal/assetserver/webview/webkit2_36+_purego.go @@ -0,0 +1,94 @@ +//go:build linux && (webkit2_36 || webkit2_40) && purego + +package webview + +import ( + "net/http" + "strings" + + "github.com/ebitengine/purego" +) + +func webkit_uri_scheme_request_get_http_method(req uintptr) string { + var getMethod func(uintptr) string + purego.RegisterLibFunc(&getMethod, gtk, "webkit_uri_scheme_request_get_http_method") + return strings.ToUpper(getMethod(req)) +} + +func webkit_uri_scheme_request_get_http_headers(req uintptr) http.Header { + var getHeaders func(uintptr) uintptr + purego.RegisterLibFunc(&getUri, webkit, "webkit_uri_scheme_request_get_http_headers") + + hdrs := getHeaders(req) + + var headersIterInit func(uintptr, uintptr) uintptr + purego.RegisterLibFunc(&headersIterInit, gtk, "soup_message_headers_iter_init") + + // TODO: How do we get a struct? + /* + typedef struct { + SoupMessageHeaders *hdrs; + int index_common; + int index_uncommon; + } SoupMessageHeadersIterReal; + */ + iter := make([]byte, 12) + headersIterInit(&iter, hdrs) + + var iterNext func(uintptr, *string, *string) int + purego.RegisterLibFunc(&iterNext, gtk, "soup_message_headers_iter_next") + + var name string + var value string + h := http.Header{} + + for iterNext(&iter, &name, &value) != 0 { + h.Add(name, value) + } + + return h +} + +func webkit_uri_scheme_request_finish(req uintptr, code int, header http.Header, stream uintptr, streamLength int64) error { + + var newResponse func(uintptr, int64) string + purego.RegisterLibFunc(&newResponse, webkit, "webkit_uri_scheme_response_new") + var unRef func(uintptr) + purego.RegisterLibFunc(&unRef, gtk, "g_object_unref") + + resp := newResponse(stream, streamLength) + defer unRef(resp) + + var setStatus func(uintptr, int, string) + purego.RegisterLibFunc(&unRef, webkit, "webkit_uri_scheme_response_set_status") + + setStatus(resp, code, cReason) + + var setContentType func(uintptr, string) + purego.RegisterLibFunc(&unRef, webkit, "webkit_uri_scheme_response_set_content_type") + + setContentType(resp, header.Get(HeaderContentType)) + + soup := gtk + var soupHeadersNew func(int) uintptr + purego.RegisterLibFunc(&unRef, soup, "soup_message_headers_new") + var soupHeadersAppend func(uintptr, string, string) + purego.RegisterLibFunc(&unRef, soup, "soup_message_headers_append") + + hdrs := soupHeadersNew(SOUP_MESSAGE_HEADERS_RESPONSE) + for name, values := range header { + for _, value := range values { + soupHeadersAppend(hdrs, name, value) + } + } + + var setHttpHeaders func(uintptr, uintptr) + purego.RegisterLibFunc(&unRef, webkit, "webkit_uri_scheme_response_set_http_headers") + + setHttpHeaders(resp, hdrs) + var finishWithResponse func(uintptr, uintptr) + purego.RegisterLibFunc(&unRef, webkit, "webkit_uri_scheme_request_finish_with_response") + finishWithResponse(req, resp) + + return nil +} diff --git a/v3/internal/assetserver/webview/webkit2_36.go b/v3/internal/assetserver/webview/webkit2_36.go new file mode 100644 index 000000000..cd200af8e --- /dev/null +++ b/v3/internal/assetserver/webview/webkit2_36.go @@ -0,0 +1,21 @@ +//go:build linux && webkit2_36 + +package webview + +/* +#cgo linux pkg-config: webkit2gtk-4.0 + +#include "webkit2/webkit2.h" +*/ +import "C" + +import ( + "io" + "net/http" +) + +const Webkit2MinMinorVersion = 36 + +func webkit_uri_scheme_request_get_http_body(_ *C.WebKitURISchemeRequest) io.ReadCloser { + return http.NoBody +} diff --git a/v3/internal/assetserver/webview/webkit2_40+.go b/v3/internal/assetserver/webview/webkit2_40+.go new file mode 100644 index 000000000..dceb0803d --- /dev/null +++ b/v3/internal/assetserver/webview/webkit2_40+.go @@ -0,0 +1,83 @@ +//go:build linux && webkit2_40 + +package webview + +/* +#cgo linux pkg-config: gtk+-3.0 webkit2gtk-4.0 gio-unix-2.0 + +#include "gtk/gtk.h" +#include "webkit2/webkit2.h" +#include "gio/gunixinputstream.h" +*/ +import "C" + +import ( + "fmt" + "io" + "net/http" + "unsafe" +) + +func webkit_uri_scheme_request_get_http_body(req *C.WebKitURISchemeRequest) io.ReadCloser { + stream := C.webkit_uri_scheme_request_get_http_body(req) + if stream == nil { + return http.NoBody + } + return &webkitRequestBody{stream: stream} +} + +type webkitRequestBody struct { + stream *C.GInputStream + closed bool +} + +// Read implements io.Reader +func (r *webkitRequestBody) Read(p []byte) (int, error) { + if r.closed { + return 0, io.ErrClosedPipe + } + + var content unsafe.Pointer + var contentLen int + if p != nil { + content = unsafe.Pointer(&p[0]) + contentLen = len(p) + } + + var n C.gsize + var gErr *C.GError + res := C.g_input_stream_read_all(r.stream, content, C.gsize(contentLen), &n, nil, &gErr) + if res == 0 { + return 0, formatGError("stream read failed", gErr) + } else if n == 0 { + return 0, io.EOF + } + return int(n), nil +} + +func (r *webkitRequestBody) Close() error { + if r.closed { + return nil + } + r.closed = true + + // https://docs.gtk.org/gio/method.InputStream.close.html + // Streams will be automatically closed when the last reference is dropped, but you might want to call this function + // to make sure resources are released as early as possible. + var err error + var gErr *C.GError + if C.g_input_stream_close(r.stream, nil, &gErr) == 0 { + err = formatGError("stream close failed", gErr) + } + C.g_object_unref(C.gpointer(r.stream)) + r.stream = nil + return err +} + +func formatGError(msg string, gErr *C.GError, args ...any) error { + if gErr != nil && gErr.message != nil { + msg += ": " + C.GoString(gErr.message) + C.g_error_free(gErr) + } + return fmt.Errorf(msg, args...) +} diff --git a/v3/internal/assetserver/webview/webkit2_40+_purego.go b/v3/internal/assetserver/webview/webkit2_40+_purego.go new file mode 100644 index 000000000..1088be25e --- /dev/null +++ b/v3/internal/assetserver/webview/webkit2_40+_purego.go @@ -0,0 +1,74 @@ +//go:build linux && webkit2_40 && purego + +package webview + +import ( + "fmt" + "io" + "net/http" + "unsafe" +) + +func webkit_uri_scheme_request_get_http_body(req *C.WebKitURISchemeRequest) io.ReadCloser { + stream := C.webkit_uri_scheme_request_get_http_body(req) + if stream == nil { + return http.NoBody + } + return &webkitRequestBody{stream: stream} +} + +type webkitRequestBody struct { + stream *C.GInputStream + closed bool +} + +// Read implements io.Reader +func (r *webkitRequestBody) Read(p []byte) (int, error) { + if r.closed { + return 0, io.ErrClosedPipe + } + + var content unsafe.Pointer + var contentLen int + if p != nil { + content = unsafe.Pointer(&p[0]) + contentLen = len(p) + } + + var n C.gsize + var gErr *C.GError + res := C.g_input_stream_read_all(r.stream, content, C.gsize(contentLen), &n, nil, &gErr) + if res == 0 { + return 0, formatGError("stream read failed", gErr) + } else if n == 0 { + return 0, io.EOF + } + return int(n), nil +} + +func (r *webkitRequestBody) Close() error { + if r.closed { + return nil + } + r.closed = true + + // https://docs.gtk.org/gio/method.InputStream.close.html + // Streams will be automatically closed when the last reference is dropped, but you might want to call this function + // to make sure resources are released as early as possible. + var err error + var gErr *C.GError + if C.g_input_stream_close(r.stream, nil, &gErr) == 0 { + err = formatGError("stream close failed", gErr) + } + C.g_object_unref(C.gpointer(r.stream)) + r.stream = nil + return err +} + +func formatGError(msg string, gErr *C.GError, args ...any) error { + if gErr != nil && gErr.message != nil { + msg += ": " + C.GoString(gErr.message) + C.g_error_free(gErr) + } + return fmt.Errorf(msg, args...) +} diff --git a/v3/internal/assetserver/webview/webkit2_40.go b/v3/internal/assetserver/webview/webkit2_40.go new file mode 100644 index 000000000..47b504383 --- /dev/null +++ b/v3/internal/assetserver/webview/webkit2_40.go @@ -0,0 +1,5 @@ +//go:build linux && webkit2_40 + +package webview + +const Webkit2MinMinorVersion = 40 diff --git a/v3/internal/assetserver/webview/webkit2_legacy.go b/v3/internal/assetserver/webview/webkit2_legacy.go new file mode 100644 index 000000000..1a87fe96a --- /dev/null +++ b/v3/internal/assetserver/webview/webkit2_legacy.go @@ -0,0 +1,48 @@ +//go:build linux && !(webkit2_36 || webkit2_40) + +package webview + +/* +#cgo linux pkg-config: gtk+-3.0 webkit2gtk-4.0 + +#include "gtk/gtk.h" +#include "webkit2/webkit2.h" +*/ +import "C" + +import ( + "fmt" + "io" + "net/http" + "unsafe" +) + +const Webkit2MinMinorVersion = 0 + +func webkit_uri_scheme_request_get_http_method(_ *C.WebKitURISchemeRequest) string { + return http.MethodGet +} + +func webkit_uri_scheme_request_get_http_headers(_ *C.WebKitURISchemeRequest) http.Header { + // Fake some basic default headers that are needed if e.g. request are being proxied to the an external sever, like + // we do in the devserver. + h := http.Header{} + h.Add("Accept", "*/*") + h.Add("User-Agent", "wails.io/605.1.15") + return h +} + +func webkit_uri_scheme_request_get_http_body(_ *C.WebKitURISchemeRequest) io.ReadCloser { + return http.NoBody +} + +func webkit_uri_scheme_request_finish(req *C.WebKitURISchemeRequest, code int, header http.Header, stream *C.GInputStream, streamLength int64) error { + if code != http.StatusOK { + return fmt.Errorf("StatusCodes not supported: %d - %s", code, http.StatusText(code)) + } + + cMimeType := C.CString(header.Get(HeaderContentType)) + C.webkit_uri_scheme_request_finish(req, stream, C.gint64(streamLength), cMimeType) + C.free(unsafe.Pointer(cMimeType)) + return nil +} diff --git a/v3/internal/assetserver/webview/webkit2_legacy_purego.go b/v3/internal/assetserver/webview/webkit2_legacy_purego.go new file mode 100644 index 000000000..2e88864c8 --- /dev/null +++ b/v3/internal/assetserver/webview/webkit2_legacy_purego.go @@ -0,0 +1,36 @@ +//go:build linux && !(webkit2_36 || webkit2_40) && purego + +package webview + +import ( + "fmt" + "io" + "net/http" + + "github.com/ebitengine/purego" +) + +const Webkit2MinMinorVersion = 0 + +func webkit_uri_scheme_request_get_http_method(_ uintptr) string { + return http.MethodGet +} + +func webkit_uri_scheme_request_get_http_headers(_ uintptr) http.Header { + return http.Header{} +} + +func webkit_uri_scheme_request_get_http_body(_ uintptr) io.ReadCloser { + return http.NoBody +} + +func webkit_uri_scheme_request_finish(req uintptr, code int, header http.Header, stream uintptr, streamLength int64) error { + if code != http.StatusOK { + return fmt.Errorf("StatusCodes not supported: %d - %s", code, http.StatusText(code)) + } + + var requestFinish func(uintptr, uintptr, int64, string) + purego.RegisterLibFunc(&requestFinish, webkit, "webkit_uri_scheme_request_finish") + requestFinish(req, stream, streamLength, header.Get(HeaderContentType)) + return nil +} diff --git a/v3/internal/capabilities/capabilities.go b/v3/internal/capabilities/capabilities.go new file mode 100644 index 000000000..5cf10735e --- /dev/null +++ b/v3/internal/capabilities/capabilities.go @@ -0,0 +1,20 @@ +package capabilities + +import "encoding/json" + +type Capabilities struct { + HasNativeDrag bool +} + +func NewCapabilities(version string) Capabilities { + return newCapabilities(version) +} + +func (c Capabilities) AsBytes() []byte { + // JSON encode + result, err := json.Marshal(c) + if err != nil { + return []byte("{}") + } + return result +} diff --git a/v3/internal/capabilities/capabilities_darwin.go b/v3/internal/capabilities/capabilities_darwin.go new file mode 100644 index 000000000..5cd0b600c --- /dev/null +++ b/v3/internal/capabilities/capabilities_darwin.go @@ -0,0 +1,9 @@ +//go:build darwin + +package capabilities + +func newCapabilities(_ string) Capabilities { + c := Capabilities{} + c.HasNativeDrag = false + return c +} diff --git a/v3/internal/capabilities/capabilities_linux.go b/v3/internal/capabilities/capabilities_linux.go new file mode 100644 index 000000000..e34b292fa --- /dev/null +++ b/v3/internal/capabilities/capabilities_linux.go @@ -0,0 +1,9 @@ +//go:build linux + +package capabilities + +func newCapabilities(_ string) Capabilities { + c := Capabilities{} + c.HasNativeDrag = false + return c +} diff --git a/v3/internal/capabilities/capabilities_windows.go b/v3/internal/capabilities/capabilities_windows.go new file mode 100644 index 000000000..ac5aabc8c --- /dev/null +++ b/v3/internal/capabilities/capabilities_windows.go @@ -0,0 +1,22 @@ +//go:build windows + +package capabilities + +import "github.com/wailsapp/go-webview2/webviewloader" + +type version string + +func (v version) IsAtLeast(input string) bool { + result, err := webviewloader.CompareBrowserVersions(string(v), input) + if err != nil { + return false + } + return result >= 0 +} + +func newCapabilities(webview2version string) Capabilities { + webview2 := version(webview2version) + c := Capabilities{} + c.HasNativeDrag = webview2.IsAtLeast("113.0.0.0") + return c +} diff --git a/v3/internal/commands/bindings.go b/v3/internal/commands/bindings.go new file mode 100644 index 000000000..05d031eff --- /dev/null +++ b/v3/internal/commands/bindings.go @@ -0,0 +1,15 @@ +package commands + +import "github.com/wailsapp/wails/v3/internal/parser" + +type GenerateBindingsOptions struct { + Silent bool `name:"silent" description:"Silent mode"` + ModelsFilename string `name:"m" description:"The filename for the models file" default:"models.ts"` + BindingsFilename string `name:"b" description:"The filename for the bindings file" default:"bindings_.js"` + ProjectDirectory string `name:"p" description:"The project directory" default:"."` + OutputDirectory string `name:"d" description:"The output directory" default:"."` +} + +func GenerateBindings(options *GenerateBindingsOptions) error { + return parser.GenerateBindingsAndModels(options.ProjectDirectory, options.OutputDirectory) +} diff --git a/v3/internal/commands/build-assets.go b/v3/internal/commands/build-assets.go new file mode 100644 index 000000000..8954f152e --- /dev/null +++ b/v3/internal/commands/build-assets.go @@ -0,0 +1,69 @@ +package commands + +import ( + "embed" + _ "embed" + "fmt" + "github.com/leaanthony/gosod" + "io/fs" + "os" + "path/filepath" + "strings" + "time" +) + +//go:embed build_assets +var buildAssets embed.FS + +type BuildAssetsOptions struct { + Dir string `description:"The directory to generate the files into" default:"build"` + Name string `description:"The name of the project"` + ProductName string `description:"The name of the product" default:"My Product"` + ProductDescription string `description:"The description of the product" default:"My Product Description"` + ProductVersion string `description:"The version of the product" default:"0.1.0"` + ProductCompany string `description:"The company of the product" default:"My Company"` + ProductCopyright string `description:"The copyright notice"` + ProductComments string `description:"Comments to add to the generated files" default:"This is a comment"` + ProductIdentifier string `description:"The product identifier, e.g com.mycompany.myproduct"` + Silent bool `description:"Suppress output to console"` +} + +func GenerateBuildAssets(options *BuildAssetsOptions) error { + + var err error + options.Dir, err = filepath.Abs(options.Dir) + if err != nil { + return err + } + + // If directory doesn't exist, create it + if _, err := os.Stat(options.Dir); os.IsNotExist(err) { + err = os.MkdirAll(options.Dir, 0755) + if err != nil { + return err + } + } + + if options.ProductComments == "" { + options.ProductComments = fmt.Sprintf("(c) %d %s", time.Now().Year(), options.ProductCompany) + } + + if options.ProductIdentifier == "" { + options.ProductIdentifier = "com.wails." + normaliseName(options.Name) + } + + tfs, err := fs.Sub(buildAssets, "build_assets") + if err != nil { + return err + } + + if !options.Silent { + println("Generating build assets in " + options.Dir) + } + return gosod.New(tfs).Extract(options.Dir, options) + +} + +func normaliseName(name string) string { + return strings.ToLower(strings.ReplaceAll(name, " ", "-")) +} diff --git a/v3/internal/commands/build.go b/v3/internal/commands/build.go new file mode 100644 index 000000000..b6c0677d1 --- /dev/null +++ b/v3/internal/commands/build.go @@ -0,0 +1,15 @@ +package commands + +import ( + "os" + + "github.com/pterm/pterm" + + "github.com/wailsapp/wails/v3/internal/flags" +) + +func Build(_ *flags.Build) error { + pterm.Info.Println("`wails build` is an alias for `wails task build`. Use `wails task` for much better control over your builds.") + os.Args = []string{"wails", "task", "build"} + return RunTask(&RunTaskOptions{}, []string{}) +} diff --git a/v3/internal/commands/build_assets/Info.dev.plist.tmpl b/v3/internal/commands/build_assets/Info.dev.plist.tmpl new file mode 100644 index 000000000..eead3308a --- /dev/null +++ b/v3/internal/commands/build_assets/Info.dev.plist.tmpl @@ -0,0 +1,32 @@ + + + + CFBundlePackageType + APPL + CFBundleName + {{.ProductName}} + CFBundleExecutable + {{.Name}} + CFBundleIdentifier + {{.ProductIdentifier}} + CFBundleVersion + {{.ProductVersion}} + CFBundleGetInfoString + {{.ProductComments}} + CFBundleShortVersionString + {{.ProductVersion}} + CFBundleIconFile + icons + LSMinimumSystemVersion + 10.13.0 + NSHighResolutionCapable + true + NSHumanReadableCopyright + {{.ProductCopyright}} + NSAppTransportSecurity + + NSAllowsLocalNetworking + + + + \ No newline at end of file diff --git a/v3/internal/commands/build_assets/Info.plist.tmpl b/v3/internal/commands/build_assets/Info.plist.tmpl new file mode 100644 index 000000000..61a60d9dd --- /dev/null +++ b/v3/internal/commands/build_assets/Info.plist.tmpl @@ -0,0 +1,27 @@ + + + + CFBundlePackageType + APPL + CFBundleName + My Productname + CFBundleExecutable + {{.Name}} + CFBundleIdentifier + {{.ProductIdentifier}} + CFBundleVersion + {{.ProductVersion}} + CFBundleGetInfoString + {{.ProductComments}} + CFBundleShortVersionString + {{.ProductVersion}} + CFBundleIconFile + icons + LSMinimumSystemVersion + 10.13.0 + NSHighResolutionCapable + true + NSHumanReadableCopyright + {{.ProductCopyright}} + + \ No newline at end of file diff --git a/v3/internal/commands/build_assets/appicon.png b/v3/internal/commands/build_assets/appicon.png new file mode 100644 index 000000000..63617fe4f Binary files /dev/null and b/v3/internal/commands/build_assets/appicon.png differ diff --git a/v3/internal/commands/build_assets/icon.ico b/v3/internal/commands/build_assets/icon.ico new file mode 100644 index 000000000..bfa0690b7 Binary files /dev/null and b/v3/internal/commands/build_assets/icon.ico differ diff --git a/v3/internal/commands/build_assets/icons.icns b/v3/internal/commands/build_assets/icons.icns new file mode 100644 index 000000000..1b5bd4c86 Binary files /dev/null and b/v3/internal/commands/build_assets/icons.icns differ diff --git a/v3/internal/commands/build_assets/info.json.tmpl b/v3/internal/commands/build_assets/info.json.tmpl new file mode 100644 index 000000000..c3423b2dd --- /dev/null +++ b/v3/internal/commands/build_assets/info.json.tmpl @@ -0,0 +1,15 @@ +{ + "fixed": { + "file_version": "{{.ProductVersion}}" + }, + "info": { + "0000": { + "ProductVersion": "{{.ProductVersion}}", + "CompanyName": "{{.ProductCompany}}", + "FileDescription": "{{.ProductDescription}}", + "LegalCopyright": "{{.ProductCopyright}}", + "ProductName": "{{.ProductName}}", + "Comments": "{{.ProductComments}}" + } + } +} \ No newline at end of file diff --git a/v3/internal/commands/build_assets/wails.exe.manifest.tmpl b/v3/internal/commands/build_assets/wails.exe.manifest.tmpl new file mode 100644 index 000000000..97cf66e73 --- /dev/null +++ b/v3/internal/commands/build_assets/wails.exe.manifest.tmpl @@ -0,0 +1,15 @@ + + + + + + + + + + + true/pm + permonitorv2,permonitor + + + \ No newline at end of file diff --git a/v3/internal/commands/constants.go b/v3/internal/commands/constants.go new file mode 100644 index 000000000..d44e5258b --- /dev/null +++ b/v3/internal/commands/constants.go @@ -0,0 +1,25 @@ +package commands + +import ( + "github.com/wailsapp/wails/v3/internal/parser" + "os" +) + +type GenerateConstantsOptions struct { + ConstantsFilename string `name:"f" description:"The filename for the models file" default:"constants.go"` + OutputFilename string `name:"o" description:"The output file" default:"constants.js"` +} + +func GenerateConstants(options *GenerateConstantsOptions) error { + goData, err := os.ReadFile(options.ConstantsFilename) + if err != nil { + return err + } + + result, err := parser.GenerateConstants(goData) + if err != nil { + return err + } + + return os.WriteFile(options.OutputFilename, []byte(result), 0644) +} diff --git a/v3/internal/commands/doctor.go b/v3/internal/commands/doctor.go new file mode 100644 index 000000000..08a3da2a2 --- /dev/null +++ b/v3/internal/commands/doctor.go @@ -0,0 +1,11 @@ +package commands + +import ( + "github.com/wailsapp/wails/v3/internal/doctor" +) + +type DoctorOptions struct{} + +func Doctor(_ *DoctorOptions) error { + return doctor.Run() +} diff --git a/v3/internal/commands/icons.go b/v3/internal/commands/icons.go new file mode 100644 index 000000000..ef6ce1971 --- /dev/null +++ b/v3/internal/commands/icons.go @@ -0,0 +1,168 @@ +package commands + +import ( + "bytes" + "fmt" + "image" + "image/color" + "image/png" + "os" + "strconv" + "strings" + + "github.com/jackmordaunt/icns/v2" + "github.com/leaanthony/winicon" +) + +type IconsOptions struct { + Example bool `description:"Generate example icon file (appicon.png) in the current directory"` + Input string `description:"The input image file"` + Sizes string `description:"The sizes to generate in .ico file (comma separated)" default:"256,128,64,48,32,16"` + WindowsFilename string `description:"The output filename for the Windows icon" default:"icon.ico"` + MacFilename string `description:"The output filename for the Mac icon bundle" default:"icons.icns"` +} + +func GenerateIcons(options *IconsOptions) error { + + if options.Example { + return generateExampleIcon() + } + + if options.Input == "" { + return fmt.Errorf("input is required") + } + + if options.WindowsFilename == "" && options.MacFilename == "" { + return fmt.Errorf("at least one output filename is required") + } + + // Parse sizes + var sizes = []int{256, 128, 64, 48, 32, 16} + var err error + if options.Sizes != "" { + sizes, err = parseSizes(options.Sizes) + if err != nil { + return err + } + } + iconData, err := os.ReadFile(options.Input) + if err != nil { + return err + } + + if options.WindowsFilename != "" { + err := generateWindowsIcon(iconData, sizes, options) + if err != nil { + return err + } + } + + if options.MacFilename != "" { + err := generateMacIcon(iconData, options) + if err != nil { + return err + } + } + + return nil +} + +func generateExampleIcon() error { + appIcon, err := buildAssets.ReadFile("build_assets/appicon.png") + if err != nil { + return err + } + return os.WriteFile("appicon.png", appIcon, 0644) +} + +func parseSizes(sizes string) ([]int, error) { + // split the input string by comma and confirm that each one is an integer + parsedSizes := strings.Split(sizes, ",") + var result []int + for _, size := range parsedSizes { + s, err := strconv.Atoi(size) + if err != nil { + return nil, err + } + if s == 0 { + continue + } + result = append(result, s) + } + + // put all integers in a slice and return + return result, nil +} + +func generateMacIcon(iconData []byte, options *IconsOptions) error { + + srcImg, _, err := image.Decode(bytes.NewBuffer(iconData)) + if err != nil { + return err + } + + dest, err := os.Create(options.MacFilename) + if err != nil { + return err + + } + defer func() { + err = dest.Close() + if err == nil { + return + } + }() + return icns.Encode(dest, srcImg) +} + +func generateWindowsIcon(iconData []byte, sizes []int, options *IconsOptions) error { + + var output bytes.Buffer + + err := winicon.GenerateIcon(bytes.NewBuffer(iconData), &output, sizes) + if err != nil { + return err + } + + err = os.WriteFile(options.WindowsFilename, output.Bytes(), 0644) + if err != nil { + return err + } + return nil +} + +func GenerateTemplateIcon(data []byte, outputFilename string) error { + // Decode the input file as a PNG + buffer := bytes.NewBuffer(data) + img, err := png.Decode(buffer) + if err != nil { + return fmt.Errorf("failed to decode input file as PNG: %w", err) + } + + // Create a new image with the same dimensions and RGBA color model + bounds := img.Bounds() + iconImg := image.NewRGBA(bounds) + + // Iterate over each pixel of the input image + for y := bounds.Min.Y; y < bounds.Max.Y; y++ { + for x := bounds.Min.X; x < bounds.Max.X; x++ { + // Get the alpha of the pixel + _, _, _, a := img.At(x, y).RGBA() + iconImg.SetRGBA(x, y, color.RGBA{R: 0, G: 0, B: 0, A: uint8(a)}) + } + } + + // Create the output file + outFile, err := os.Create(outputFilename) + if err != nil { + return fmt.Errorf("failed to create output file: %w", err) + } + defer outFile.Close() + + // Encode the template icon image as a PNG and write it to the output file + if err := png.Encode(outFile, iconImg); err != nil { + return fmt.Errorf("failed to encode output image as PNG: %w", err) + } + + return nil +} diff --git a/v3/internal/commands/icons_test.go b/v3/internal/commands/icons_test.go new file mode 100644 index 000000000..f56febaec --- /dev/null +++ b/v3/internal/commands/icons_test.go @@ -0,0 +1,285 @@ +package commands + +import ( + "fmt" + "os" + "path/filepath" + "runtime" + "testing" +) + +func TestGenerateIcon(t *testing.T) { + tests := []struct { + name string + setup func() *IconsOptions + wantErr bool + test func() error + }{ + { + name: "should generate an icon when using the `example` flag", + setup: func() *IconsOptions { + return &IconsOptions{ + Example: true, + } + }, + wantErr: false, + test: func() error { + // the file `appicon.png` should be created in the current directory + // check for the existence of the file + f, err := os.Stat("appicon.png") + if err != nil { + return err + } + defer func() { + err := os.Remove("appicon.png") + if err != nil { + panic(err) + } + }() + if f.IsDir() { + return fmt.Errorf("appicon.png is a directory") + } + if f.Size() == 0 { + return fmt.Errorf("appicon.png is empty") + } + return nil + }, + }, + { + name: "should generate a .ico file when using the `input` flag and `windowsfilena me` flag", + setup: func() *IconsOptions { + // Get the directory of this file + _, thisFile, _, _ := runtime.Caller(1) + localDir := filepath.Dir(thisFile) + // Get the path to the example icon + exampleIcon := filepath.Join(localDir, "build_assets", "appicon.png") + return &IconsOptions{ + Input: exampleIcon, + WindowsFilename: "appicon.ico", + } + }, + wantErr: false, + test: func() error { + // the file `appicon.ico` should be created in the current directory + // check for the existence of the file + f, err := os.Stat("appicon.ico") + if err != nil { + return err + } + defer func() { + // Remove the file + err = os.Remove("appicon.ico") + if err != nil { + return + } + }() + if f.IsDir() { + return fmt.Errorf("appicon.ico is a directory") + } + if f.Size() == 0 { + return fmt.Errorf("appicon.ico is empty") + } + // Remove the file + err = os.Remove("appicon.ico") + if err != nil { + return err + } + return nil + }, + }, + { + name: "should generate a .icns file when using the `input` flag and `macfilename` flag", + setup: func() *IconsOptions { + // Get the directory of this file + _, thisFile, _, _ := runtime.Caller(1) + localDir := filepath.Dir(thisFile) + // Get the path to the example icon + exampleIcon := filepath.Join(localDir, "build_assets", "appicon.png") + return &IconsOptions{ + Input: exampleIcon, + MacFilename: "appicon.icns", + } + }, + wantErr: false, + test: func() error { + // the file `appicon.icns` should be created in the current directory + // check for the existence of the file + f, err := os.Stat("appicon.icns") + if err != nil { + return err + } + defer func() { + // Remove the file + err = os.Remove("appicon.icns") + if err != nil { + panic(err) + } + }() + if f.IsDir() { + return fmt.Errorf("appicon.icns is a directory") + } + if f.Size() == 0 { + return fmt.Errorf("appicon.icns is empty") + } + // Remove the file + + return nil + }, + }, + { + name: "should generate a small .ico file when using the `input` flag and `sizes` flag", + setup: func() *IconsOptions { + // Get the directory of this file + _, thisFile, _, _ := runtime.Caller(1) + localDir := filepath.Dir(thisFile) + // Get the path to the example icon + exampleIcon := filepath.Join(localDir, "build_assets", "appicon.png") + return &IconsOptions{ + Input: exampleIcon, + Sizes: "16", + WindowsFilename: "appicon.ico", + } + }, + wantErr: false, + test: func() error { + // the file `appicon.ico` should be created in the current directory + // check for the existence of the file + f, err := os.Stat("appicon.ico") + if err != nil { + return err + } + defer func() { + err := os.Remove("appicon.ico") + if err != nil { + panic(err) + } + }() + // The size of the file should be 571 bytes + if f.Size() != 571 { + return fmt.Errorf("appicon.ico is not the correct size. Got %d", f.Size()) + } + if f.IsDir() { + return fmt.Errorf("appicon.ico is a directory") + } + if f.Size() == 0 { + return fmt.Errorf("appicon.ico is empty") + } + return nil + }, + }, + { + name: "should error if no input file is provided", + setup: func() *IconsOptions { + return &IconsOptions{} + }, + wantErr: true, + }, + { + name: "should error if neither mac or windows filename is provided", + setup: func() *IconsOptions { + // Get the directory of this file + _, thisFile, _, _ := runtime.Caller(1) + localDir := filepath.Dir(thisFile) + // Get the path to the example icon + exampleIcon := filepath.Join(localDir, "build_assets", "appicon.png") + return &IconsOptions{ + Input: exampleIcon, + } + }, + wantErr: true, + }, + { + name: "should error if bad sizes provided", + setup: func() *IconsOptions { + // Get the directory of this file + _, thisFile, _, _ := runtime.Caller(1) + localDir := filepath.Dir(thisFile) + // Get the path to the example icon + exampleIcon := filepath.Join(localDir, "build_assets", "appicon.png") + return &IconsOptions{ + Input: exampleIcon, + WindowsFilename: "appicon.ico", + Sizes: "bad", + } + }, + wantErr: true, + }, + { + name: "should ignore 0 size", + setup: func() *IconsOptions { + // Get the directory of this file + _, thisFile, _, _ := runtime.Caller(1) + localDir := filepath.Dir(thisFile) + // Get the path to the example icon + exampleIcon := filepath.Join(localDir, "build_assets", "appicon.png") + return &IconsOptions{ + Input: exampleIcon, + WindowsFilename: "appicon.ico", + Sizes: "0,16", + } + }, + wantErr: false, + test: func() error { + // Test the file exists and has 571 bytes + f, err := os.Stat("appicon.ico") + if err != nil { + return err + } + defer func() { + err := os.Remove("appicon.ico") + if err != nil { + panic(err) + } + }() + if f.Size() != 571 { + return fmt.Errorf("appicon.ico is not the correct size. Got %d", f.Size()) + } + if f.IsDir() { + return fmt.Errorf("appicon.ico is a directory") + } + if f.Size() == 0 { + return fmt.Errorf("appicon.ico is empty") + } + return nil + }, + }, + { + name: "should error if the input file does not exist", + setup: func() *IconsOptions { + return &IconsOptions{ + Input: "doesnotexist.png", + WindowsFilename: "appicon.ico", + } + }, + wantErr: true, + }, + { + name: "should error if the input file is not a png", + setup: func() *IconsOptions { + // Get the directory of this file + _, thisFile, _, _ := runtime.Caller(1) + return &IconsOptions{ + Input: thisFile, + WindowsFilename: "appicon.ico", + } + }, + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + options := tt.setup() + err := GenerateIcons(options) + if (err != nil) != tt.wantErr { + t.Errorf("GenerateIcon() error = %v, wantErr %v", err, tt.wantErr) + return + } + if tt.test != nil { + if err := tt.test(); err != nil { + t.Errorf("GenerateIcon() test error = %v", err) + } + } + }) + } +} diff --git a/v3/internal/commands/init.go b/v3/internal/commands/init.go new file mode 100644 index 000000000..71ca311ac --- /dev/null +++ b/v3/internal/commands/init.go @@ -0,0 +1,55 @@ +package commands + +import ( + "fmt" + "github.com/wailsapp/wails/v3/internal/flags" + "github.com/wailsapp/wails/v3/internal/templates" + "path/filepath" + + "github.com/pterm/pterm" +) + +func Init(options *flags.Init) error { + if options.List { + return printTemplates() + } + + if options.Quiet { + pterm.DisableOutput() + } + + if options.ProjectName == "" { + return fmt.Errorf("please use the -n flag to specify a project name") + } + + if !templates.ValidTemplateName(options.TemplateName) { + return fmt.Errorf("invalid template name: %s. Use -l flag to list valid templates", options.TemplateName) + } + + err := templates.Install(options) + if err != nil { + return err + } + + // Generate build assets + buildAssetsOptions := &BuildAssetsOptions{ + Name: options.ProjectName, + Dir: filepath.Join(options.ProjectDir, "build"), + Silent: true, + } + return GenerateBuildAssets(buildAssetsOptions) +} + +func printTemplates() error { + defaultTemplates := templates.GetDefaultTemplates() + + pterm.DefaultSection.Println("Available templates") + + table := pterm.TableData{{"Name", "Description"}} + for _, template := range defaultTemplates { + table = append(table, []string{template.Name, template.Description}) + } + err := pterm.DefaultTable.WithHasHeader(true).WithBoxed(true).WithData(table).Render() + pterm.Println() + return err +} diff --git a/v3/internal/commands/plugins.go b/v3/internal/commands/plugins.go new file mode 100644 index 000000000..a42790ab3 --- /dev/null +++ b/v3/internal/commands/plugins.go @@ -0,0 +1,45 @@ +package commands + +import ( + "github.com/wailsapp/wails/v3/internal/flags" + "github.com/wailsapp/wails/v3/internal/plugins" + "strings" + + "github.com/pterm/pterm" +) + +func toCamelCasePlugin(s string) string { + var camelCase string + var capitalize = true + + for _, c := range s { + if c >= 'a' && c <= 'z' || c >= '0' && c <= '9' { + if capitalize { + camelCase += strings.ToUpper(string(c)) + capitalize = false + } else { + camelCase += string(c) + } + } else if c >= 'A' && c <= 'Z' { + camelCase += string(c) + capitalize = false + } else { + capitalize = true + } + } + + return camelCase + "Plugin" +} + +func PluginInit(options *flags.PluginInit) error { + + if options.Quiet { + pterm.DisableOutput() + } + + if options.PackageName == "" { + options.PackageName = toCamelCasePlugin(options.Name) + } + + return plugins.Install(options) +} diff --git a/v3/internal/commands/syso.go b/v3/internal/commands/syso.go new file mode 100644 index 000000000..63a9b6186 --- /dev/null +++ b/v3/internal/commands/syso.go @@ -0,0 +1,103 @@ +package commands + +import ( + "fmt" + "os" + "runtime" + + "github.com/tc-hib/winres" + "github.com/tc-hib/winres/version" +) + +type SysoOptions struct { + Manifest string `description:"The manifest file"` + Info string `description:"The info.json file"` + Icon string `description:"The icon file"` + Out string `description:"The output filename for the syso file"` + Arch string `description:"The target architecture"` +} + +func (i *SysoOptions) Default() *SysoOptions { + return &SysoOptions{ + Arch: runtime.GOARCH, + } +} + +func GenerateSyso(options *SysoOptions) error { + + if options.Manifest == "" { + return fmt.Errorf("manifest is required") + } + if options.Icon == "" { + return fmt.Errorf("icon is required") + } + + rs := winres.ResourceSet{} + + // Process Icon + iconFile, err := os.Open(options.Icon) + if err != nil { + return err + } + defer iconFile.Close() + ico, err := winres.LoadICO(iconFile) + if err != nil { + return fmt.Errorf("couldn't load icon '%s': %v", options.Icon, err) + } + err = rs.SetIcon(winres.RT_ICON, ico) + if err != nil { + return err + } + + // Process Manifest + manifestData, err := os.ReadFile(options.Manifest) + if err != nil { + return err + } + + xmlData, err := winres.AppManifestFromXML(manifestData) + if err != nil { + return err + } + rs.SetManifest(xmlData) + + if options.Info != "" { + infoData, err := os.ReadFile(options.Info) + if err != nil { + return err + } + if len(infoData) != 0 { + var v version.Info + if err := v.UnmarshalJSON(infoData); err != nil { + return err + } + rs.SetVersionInfo(v) + } + } + + targetFile := options.Out + if targetFile == "" { + targetFile = "rsrc_windows_" + options.Arch + ".syso" + } + fout, err := os.Create(targetFile) + if err != nil { + return err + } + defer fout.Close() + + archs := map[string]winres.Arch{ + "amd64": winres.ArchAMD64, + "arm64": winres.ArchARM64, + "386": winres.ArchI386, + } + targetArch, supported := archs[options.Arch] + if !supported { + return fmt.Errorf("arch '%s' not supported", options.Arch) + } + + err = rs.WriteObject(fout, targetArch) + if err != nil { + return err + } + return nil +} diff --git a/v3/internal/commands/syso_test.go b/v3/internal/commands/syso_test.go new file mode 100644 index 000000000..6726b548e --- /dev/null +++ b/v3/internal/commands/syso_test.go @@ -0,0 +1,143 @@ +package commands + +import ( + "path/filepath" + "runtime" + "testing" +) + +func TestGenerateSyso(t *testing.T) { + tests := []struct { + name string + setup func() *SysoOptions + wantErr bool + test func() error + }{ + { + name: "should error if manifest filename is not provided", + setup: func() *SysoOptions { + return &SysoOptions{ + Manifest: "", + } + }, + wantErr: true, + }, + { + name: "should error if icon filename is not provided", + setup: func() *SysoOptions { + return &SysoOptions{ + Manifest: "test.manifest", + Icon: "", + } + }, + wantErr: true, + }, + { + name: "should error if icon filename does not exist", + setup: func() *SysoOptions { + return &SysoOptions{ + Manifest: "test.manifest", + Icon: "icon.ico", + } + }, + wantErr: true, + }, + { + name: "should error if icon is wrong format", + setup: func() *SysoOptions { + _, thisFile, _, _ := runtime.Caller(1) + return &SysoOptions{ + Manifest: "test.manifest", + Icon: thisFile, + } + }, + wantErr: true, + }, + { + name: "should error if manifest filename does not exist", + setup: func() *SysoOptions { + // Get the directory of this file + _, thisFile, _, _ := runtime.Caller(1) + localDir := filepath.Dir(thisFile) + // Get the path to the example icon + exampleIcon := filepath.Join(localDir, "examples", "icon.ico") + return &SysoOptions{ + Manifest: "test.manifest", + Icon: exampleIcon, + } + }, + wantErr: true, + }, + { + name: "should error if manifest is wrong format", + setup: func() *SysoOptions { + // Get the directory of this file + _, thisFile, _, _ := runtime.Caller(1) + localDir := filepath.Dir(thisFile) + // Get the path to the example icon + exampleIcon := filepath.Join(localDir, "examples", "icon.ico") + return &SysoOptions{ + Manifest: exampleIcon, + Icon: exampleIcon, + } + }, + wantErr: true, + }, + { + name: "should error if info file does not exist", + setup: func() *SysoOptions { + // Get the directory of this file + _, thisFile, _, _ := runtime.Caller(1) + localDir := filepath.Dir(thisFile) + // Get the path to the example icon + exampleIcon := filepath.Join(localDir, "examples", "icon.ico") + // Get the path to the example manifest + exampleManifest := filepath.Join(localDir, "examples", "wails.exe.manifest") + return &SysoOptions{ + Manifest: exampleManifest, + Icon: exampleIcon, + Info: "doesnotexist.json", + } + }, + wantErr: true, + }, + { + name: "should error if info file is wrong format", + setup: func() *SysoOptions { + // Get the directory of this file + _, thisFile, _, _ := runtime.Caller(1) + localDir := filepath.Dir(thisFile) + // Get the path to the example icon + exampleIcon := filepath.Join(localDir, "examples", "icon.ico") + // Get the path to the example manifest + exampleManifest := filepath.Join(localDir, "examples", "wails.exe.manifest") + return &SysoOptions{ + Manifest: exampleManifest, + Icon: exampleIcon, + Info: thisFile, + } + }, + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + options := tt.setup() + err := GenerateSyso(options) + if (err != nil) != tt.wantErr { + t.Errorf("GenerateSyso() error = %v, wantErr %v", err, tt.wantErr) + return + } + if (err != nil) && tt.wantErr { + println(err.Error()) + return + } + if tt.test != nil { + if err := tt.test(); err != nil { + t.Errorf("GenerateSyso() test error = %v", err) + } + } + }) + } +} diff --git a/v3/internal/commands/task.go b/v3/internal/commands/task.go new file mode 100644 index 000000000..5725d4db8 --- /dev/null +++ b/v3/internal/commands/task.go @@ -0,0 +1,182 @@ +package commands + +import ( + "context" + "fmt" + "github.com/go-task/task/v3/errors" + "log" + "os" + "path/filepath" + "strings" + "time" + + "github.com/pterm/pterm" + + "github.com/go-task/task/v3/args" + + "github.com/go-task/task/v3" + "github.com/go-task/task/v3/taskfile" +) + +// BuildSettings contains the CLI build settings +var BuildSettings = map[string]string{} + +type RunTaskOptions struct { + Name string `pos:"1"` + Help bool `name:"h" description:"shows Task usage"` + Init bool `name:"i" description:"creates a new Taskfile.yml"` + List bool `name:"list" description:"tasks with description of current Taskfile"` + ListAll bool `name:"list-all" description:"lists tasks with or without a description"` + ListJSON bool `name:"json" description:"formats task list as json"` + Status bool `name:"status" description:"exits with non-zero exit code if any of the given tasks is not up-to-date"` + Force bool `name:"f" description:"forces execution even when the task is up-to-date"` + Watch bool `name:"w" description:"enables watch of the given task"` + Verbose bool `name:"v" description:"enables verbose mode"` + Version bool `name:"version" description:"prints version"` + Silent bool `name:"s" description:"disables echoing"` + Parallel bool `name:"p" description:"executes tasks provided on command line in parallel"` + Dry bool `name:"dry" description:"compiles and prints tasks in the order that they would be run, without executing them"` + Summary bool `name:"summary" description:"show summary about a task"` + ExitCode bool `name:"x" description:"pass-through the exit code of the task command"` + Dir string `name:"dir" description:"sets directory of execution"` + EntryPoint string `name:"taskfile" description:"choose which Taskfile to run."` + OutputName string `name:"output" description:"sets output style: [interleaved|group|prefixed]"` + OutputGroupBegin string `name:"output-group-begin" description:"message template to print before a task's grouped output"` + OutputGroupEnd string `name:"output-group-end" description:"message template to print after a task's grouped output"` + Color bool `name:"c" description:"colored output. Enabled by default. Set flag to false or use NO_COLOR=1 to disable" default:"true"` + Concurrency int `name:"C" description:"limit number tasks to run concurrently"` + Interval int64 `name:"interval" description:"interval to watch for changes"` +} + +func RunTask(options *RunTaskOptions, otherArgs []string) error { + + if options.Version { + ver := BuildSettings["mod.github.com/go-task/task/v3"] + fmt.Println("Task Version:", ver) + return nil + } + + if options.Init { + wd, err := os.Getwd() + if err != nil { + return err + } + return task.InitTaskfile(os.Stdout, wd) + } + + if options.Dir != "" && options.EntryPoint != "" { + return fmt.Errorf("task: You can't set both --dir and --taskfile") + } + + if options.EntryPoint != "" { + options.Dir = filepath.Dir(options.EntryPoint) + options.EntryPoint = filepath.Base(options.EntryPoint) + } + + if options.OutputName != "group" { + if options.OutputGroupBegin != "" { + return fmt.Errorf("task: You can't set --output-group-begin without --output=group") + } + if options.OutputGroupBegin != "" { + return fmt.Errorf("task: You can't set --output-group-end without --output=group") + } + } + + e := task.Executor{ + Force: options.Force, + Watch: options.Watch, + Verbose: options.Verbose, + Silent: options.Silent, + Dir: options.Dir, + Dry: options.Dry, + Entrypoint: options.EntryPoint, + Summary: options.Summary, + Parallel: options.Parallel, + Color: options.Color, + Concurrency: options.Concurrency, + Interval: time.Duration(options.Interval) * time.Second, + + Stdin: os.Stdin, + Stdout: os.Stdout, + Stderr: os.Stderr, + + OutputStyle: taskfile.Output{ + Name: options.OutputName, + Group: taskfile.OutputGroup{ + Begin: options.OutputGroupBegin, + End: options.OutputGroupEnd, + }, + }, + } + + var listOptions = task.NewListOptions(options.List, options.ListAll, options.ListJSON) + if err := listOptions.Validate(); err != nil { + log.Fatal(err) + } + + if (listOptions.ShouldListTasks()) && options.Silent { + e.ListTaskNames(options.ListAll) + return nil + } + + if err := e.Setup(); err != nil { + log.Fatal(err) + } + + if listOptions.ShouldListTasks() { + if foundTasks, err := e.ListTasks(listOptions); !foundTasks || err != nil { + os.Exit(1) + } + return nil + } + + var ( + calls []taskfile.Call + globals *taskfile.Vars + ) + + var tasksAndVars []string + for _, taskAndVar := range os.Args[2:] { + if taskAndVar == "--" { + break + } + tasksAndVars = append(tasksAndVars, taskAndVar) + } + + if len(tasksAndVars) > 0 && len(otherArgs) > 0 { + if tasksAndVars[0] == otherArgs[0] { + otherArgs = otherArgs[1:] + } + } + + if e.Taskfile.Version.Compare(taskfile.V3) >= 0 { + calls, globals = args.ParseV3(tasksAndVars...) + } else { + calls, globals = args.ParseV2(tasksAndVars...) + } + + globals.Set("CLI_ARGS", taskfile.Var{Static: strings.Join(otherArgs, " ")}) + e.Taskfile.Vars.Merge(globals) + + if !options.Watch { + e.InterceptInterruptSignals() + } + + ctx := context.Background() + + if options.Status { + return e.Status(ctx, calls...) + } + + if err := e.Run(ctx, calls...); err != nil { + pterm.Error.Println(err.Error()) + + if options.ExitCode { + if err, ok := err.(*errors.TaskRunError); ok { + os.Exit(err.Code()) + } + } + os.Exit(1) + } + return nil +} diff --git a/v3/internal/commands/tool_checkport.go b/v3/internal/commands/tool_checkport.go new file mode 100644 index 000000000..1c209008c --- /dev/null +++ b/v3/internal/commands/tool_checkport.go @@ -0,0 +1,36 @@ +package commands + +import ( + "fmt" + "net" + "time" +) + +type ToolCheckPortOptions struct { + Host string `name:"h" description:"Host to check" default:"localhost"` + Port int `name:"p" description:"Port to check"` +} + +func isPortOpen(ip string, port int) bool { + timeout := time.Second + conn, err := net.DialTimeout("tcp", net.JoinHostPort(ip, fmt.Sprintf("%d", port)), timeout) + if err != nil { + return false + } + + // If there's no error, close the connection and return true. + if conn != nil { + _ = conn.Close() + } + return true +} + +func ToolCheckPort(options *ToolCheckPortOptions) error { + if options.Port == 0 { + return fmt.Errorf("please use the -p flag to specify a port") + } + if !isPortOpen(options.Host, options.Port) { + return fmt.Errorf("port %d is not open on %s", options.Port, options.Host) + } + return nil +} diff --git a/v3/internal/commands/version.go b/v3/internal/commands/version.go new file mode 100644 index 000000000..e4f9cb028 --- /dev/null +++ b/v3/internal/commands/version.go @@ -0,0 +1,13 @@ +package commands + +import ( + _ "embed" + "github.com/wailsapp/wails/v3/internal/version" +) + +type VersionOptions struct{} + +func Version(_ *VersionOptions) error { + println(version.VersionString) + return nil +} diff --git a/v3/internal/dbus/DbusMenu.xml b/v3/internal/dbus/DbusMenu.xml new file mode 100644 index 000000000..db6959845 --- /dev/null +++ b/v3/internal/dbus/DbusMenu.xml @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/v3/internal/dbus/README.md b/v3/internal/dbus/README.md new file mode 100644 index 000000000..d2cfce105 --- /dev/null +++ b/v3/internal/dbus/README.md @@ -0,0 +1,5 @@ +//Note that you need to have github.com/knightpp/dbus-codegen-go installed from "custom" branch + +//go:generate dbus-codegen-go -prefix org.kde -package notifier -output internal/generated/notifier/status_notifier_item.go internal/StatusNotifierItem.xml +//go:generate dbus-codegen-go -prefix com.canonical -package menu -output internal/generated/menu/dbus_menu.go internal/DbusMenu.xml + diff --git a/v3/internal/dbus/StatusNotifierItem.xml b/v3/internal/dbus/StatusNotifierItem.xml new file mode 100644 index 000000000..1093d3d18 --- /dev/null +++ b/v3/internal/dbus/StatusNotifierItem.xml @@ -0,0 +1,85 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/v3/internal/dbus/dbus.go b/v3/internal/dbus/dbus.go new file mode 100644 index 000000000..4e4a3adbd --- /dev/null +++ b/v3/internal/dbus/dbus.go @@ -0,0 +1,4 @@ +package dbus + +//go:generate dbus-codegen-go -prefix org.kde -package notifier -output notifier/status_notifier_item.go StatusNotifierItem.xml +//go:generate dbus-codegen-go -prefix com.canonical -package menu -output menu/dbus_menu.go DbusMenu.xml diff --git a/v3/internal/dbus/generate.sh b/v3/internal/dbus/generate.sh new file mode 100644 index 000000000..03716e522 --- /dev/null +++ b/v3/internal/dbus/generate.sh @@ -0,0 +1,2 @@ +dbus-codegen-go -prefix com.canonical -package menu -output dbus_menu.go DbusMenu.xml +dbus-codegen-go -prefix org.kde -package notifier -output status_notifier_item.go StatusNotifierItem.xml diff --git a/v3/internal/dbus/menu/.keep b/v3/internal/dbus/menu/.keep new file mode 100644 index 000000000..e69de29bb diff --git a/v3/internal/dbus/menu/dbus_menu.go b/v3/internal/dbus/menu/dbus_menu.go new file mode 100644 index 000000000..cbf4fd320 --- /dev/null +++ b/v3/internal/dbus/menu/dbus_menu.go @@ -0,0 +1,483 @@ +// Code generated by dbus-codegen-go DO NOT EDIT. +package menu + +import ( + "context" + "errors" + "fmt" + "github.com/godbus/dbus/v5" + "github.com/godbus/dbus/v5/introspect" +) + +var ( + // Introspection for com.canonical.dbusmenu + IntrospectDataDbusmenu = introspect.Interface{ + Name: "com.canonical.dbusmenu", + Methods: []introspect.Method{{Name: "GetLayout", Args: []introspect.Arg{ + {Name: "parentId", Type: "i", Direction: "in"}, + {Name: "recursionDepth", Type: "i", Direction: "in"}, + {Name: "propertyNames", Type: "as", Direction: "in"}, + {Name: "revision", Type: "u", Direction: "out"}, + {Name: "layout", Type: "(ia{sv}av)", Direction: "out"}, + }}, + {Name: "GetGroupProperties", Args: []introspect.Arg{ + {Name: "ids", Type: "ai", Direction: "in"}, + {Name: "propertyNames", Type: "as", Direction: "in"}, + {Name: "properties", Type: "a(ia{sv})", Direction: "out"}, + }}, + {Name: "GetProperty", Args: []introspect.Arg{ + {Name: "id", Type: "i", Direction: "in"}, + {Name: "name", Type: "s", Direction: "in"}, + {Name: "value", Type: "v", Direction: "out"}, + }}, + {Name: "Event", Args: []introspect.Arg{ + {Name: "id", Type: "i", Direction: "in"}, + {Name: "eventId", Type: "s", Direction: "in"}, + {Name: "data", Type: "v", Direction: "in"}, + {Name: "timestamp", Type: "u", Direction: "in"}, + }}, + {Name: "EventGroup", Args: []introspect.Arg{ + {Name: "events", Type: "a(isvu)", Direction: "in"}, + {Name: "idErrors", Type: "ai", Direction: "out"}, + }}, + {Name: "AboutToShow", Args: []introspect.Arg{ + {Name: "id", Type: "i", Direction: "in"}, + {Name: "needUpdate", Type: "b", Direction: "out"}, + }}, + {Name: "AboutToShowGroup", Args: []introspect.Arg{ + {Name: "ids", Type: "ai", Direction: "in"}, + {Name: "updatesNeeded", Type: "ai", Direction: "out"}, + {Name: "idErrors", Type: "ai", Direction: "out"}, + }}, + }, + Signals: []introspect.Signal{{Name: "ItemsPropertiesUpdated", Args: []introspect.Arg{ + {Name: "updatedProps", Type: "a(ia{sv})", Direction: "out"}, + {Name: "removedProps", Type: "a(ias)", Direction: "out"}, + }}, + {Name: "LayoutUpdated", Args: []introspect.Arg{ + {Name: "revision", Type: "u", Direction: "out"}, + {Name: "parent", Type: "i", Direction: "out"}, + }}, + {Name: "ItemActivationRequested", Args: []introspect.Arg{ + {Name: "id", Type: "i", Direction: "out"}, + {Name: "timestamp", Type: "u", Direction: "out"}, + }}, + }, + Properties: []introspect.Property{{Name: "Version", Type: "u", Access: "read"}, + {Name: "TextDirection", Type: "s", Access: "read"}, + {Name: "Status", Type: "s", Access: "read"}, + {Name: "IconThemePath", Type: "as", Access: "read"}, + }, + Annotations: []introspect.Annotation{}, + } +) + +// Signal is a common interface for all signals. +type Signal interface { + Name() string + Interface() string + Sender() string + + path() dbus.ObjectPath + values() []interface{} +} + +// Emit sends the given signal to the bus. +func Emit(conn *dbus.Conn, s Signal) error { + return conn.Emit(s.path(), s.Interface()+"."+s.Name(), s.values()...) +} + +// ErrUnknownSignal is returned by LookupSignal when a signal cannot be resolved. +var ErrUnknownSignal = errors.New("unknown signal") + +// LookupSignal converts the given raw D-Bus signal with variable body +// into one with typed structured body or returns ErrUnknownSignal error. +func LookupSignal(signal *dbus.Signal) (Signal, error) { + switch signal.Name { + case InterfaceDbusmenu + "." + "ItemsPropertiesUpdated": + v0, ok := signal.Body[0].([]struct { + V0 int32 + V1 map[string]dbus.Variant + }) + if !ok { + return nil, fmt.Errorf("prop .UpdatedProps is %T, not []struct {V0 int32;V1 map[string]dbus.Variant}", signal.Body[0]) + } + v1, ok := signal.Body[1].([]struct { + V0 int32 + V1 []string + }) + if !ok { + return nil, fmt.Errorf("prop .RemovedProps is %T, not []struct {V0 int32;V1 []string}", signal.Body[1]) + } + return &Dbusmenu_ItemsPropertiesUpdatedSignal{ + sender: signal.Sender, + Path: signal.Path, + Body: &Dbusmenu_ItemsPropertiesUpdatedSignalBody{ + UpdatedProps: v0, + RemovedProps: v1, + }, + }, nil + case InterfaceDbusmenu + "." + "LayoutUpdated": + v0, ok := signal.Body[0].(uint32) + if !ok { + return nil, fmt.Errorf("prop .Revision is %T, not uint32", signal.Body[0]) + } + v1, ok := signal.Body[1].(int32) + if !ok { + return nil, fmt.Errorf("prop .Parent is %T, not int32", signal.Body[1]) + } + return &Dbusmenu_LayoutUpdatedSignal{ + sender: signal.Sender, + Path: signal.Path, + Body: &Dbusmenu_LayoutUpdatedSignalBody{ + Revision: v0, + Parent: v1, + }, + }, nil + case InterfaceDbusmenu + "." + "ItemActivationRequested": + v0, ok := signal.Body[0].(int32) + if !ok { + return nil, fmt.Errorf("prop .Id is %T, not int32", signal.Body[0]) + } + v1, ok := signal.Body[1].(uint32) + if !ok { + return nil, fmt.Errorf("prop .Timestamp is %T, not uint32", signal.Body[1]) + } + return &Dbusmenu_ItemActivationRequestedSignal{ + sender: signal.Sender, + Path: signal.Path, + Body: &Dbusmenu_ItemActivationRequestedSignalBody{ + Id: v0, + Timestamp: v1, + }, + }, nil + default: + return nil, ErrUnknownSignal + } +} + +// AddMatchSignal registers a match rule for the given signal, +// opts are appended to the automatically generated signal's rules. +func AddMatchSignal(conn *dbus.Conn, s Signal, opts ...dbus.MatchOption) error { + return conn.AddMatchSignal(append([]dbus.MatchOption{ + dbus.WithMatchInterface(s.Interface()), + dbus.WithMatchMember(s.Name()), + }, opts...)...) +} + +// RemoveMatchSignal unregisters the previously registered subscription. +func RemoveMatchSignal(conn *dbus.Conn, s Signal, opts ...dbus.MatchOption) error { + return conn.RemoveMatchSignal(append([]dbus.MatchOption{ + dbus.WithMatchInterface(s.Interface()), + dbus.WithMatchMember(s.Name()), + }, opts...)...) +} + +// Interface name constants. +const ( + InterfaceDbusmenu = "com.canonical.dbusmenu" +) + +// Dbusmenuer is com.canonical.dbusmenu interface. +type Dbusmenuer interface { + // GetLayout is com.canonical.dbusmenu.GetLayout method. + GetLayout(parentId int32, recursionDepth int32, propertyNames []string) (revision uint32, layout struct { + V0 int32 + V1 map[string]dbus.Variant + V2 []dbus.Variant + }, err *dbus.Error) + // GetGroupProperties is com.canonical.dbusmenu.GetGroupProperties method. + GetGroupProperties(ids []int32, propertyNames []string) (properties []struct { + V0 int32 + V1 map[string]dbus.Variant + }, err *dbus.Error) + // GetProperty is com.canonical.dbusmenu.GetProperty method. + GetProperty(id int32, name string) (value dbus.Variant, err *dbus.Error) + // Event is com.canonical.dbusmenu.Event method. + Event(id int32, eventId string, data dbus.Variant, timestamp uint32) (err *dbus.Error) + // EventGroup is com.canonical.dbusmenu.EventGroup method. + EventGroup(events []struct { + V0 int32 + V1 string + V2 dbus.Variant + V3 uint32 + }) (idErrors []int32, err *dbus.Error) + // AboutToShow is com.canonical.dbusmenu.AboutToShow method. + AboutToShow(id int32) (needUpdate bool, err *dbus.Error) + // AboutToShowGroup is com.canonical.dbusmenu.AboutToShowGroup method. + AboutToShowGroup(ids []int32) (updatesNeeded []int32, idErrors []int32, err *dbus.Error) +} + +// ExportDbusmenu exports the given object that implements com.canonical.dbusmenu on the bus. +func ExportDbusmenu(conn *dbus.Conn, path dbus.ObjectPath, v Dbusmenuer) error { + return conn.ExportSubtreeMethodTable(map[string]interface{}{ + "GetLayout": v.GetLayout, + "GetGroupProperties": v.GetGroupProperties, + "GetProperty": v.GetProperty, + "Event": v.Event, + "EventGroup": v.EventGroup, + "AboutToShow": v.AboutToShow, + "AboutToShowGroup": v.AboutToShowGroup, + }, path, InterfaceDbusmenu) +} + +// UnexportDbusmenu unexports com.canonical.dbusmenu interface on the named path. +func UnexportDbusmenu(conn *dbus.Conn, path dbus.ObjectPath) error { + return conn.Export(nil, path, InterfaceDbusmenu) +} + +// UnimplementedDbusmenu can be embedded to have forward compatible server implementations. +type UnimplementedDbusmenu struct{} + +func (*UnimplementedDbusmenu) iface() string { + return InterfaceDbusmenu +} + +func (*UnimplementedDbusmenu) GetLayout(parentId int32, recursionDepth int32, propertyNames []string) (revision uint32, layout struct { + V0 int32 + V1 map[string]dbus.Variant + V2 []dbus.Variant +}, err *dbus.Error) { + err = &dbus.ErrMsgUnknownMethod + return +} + +func (*UnimplementedDbusmenu) GetGroupProperties(ids []int32, propertyNames []string) (properties []struct { + V0 int32 + V1 map[string]dbus.Variant +}, err *dbus.Error) { + err = &dbus.ErrMsgUnknownMethod + return +} + +func (*UnimplementedDbusmenu) GetProperty(id int32, name string) (value dbus.Variant, err *dbus.Error) { + err = &dbus.ErrMsgUnknownMethod + return +} + +func (*UnimplementedDbusmenu) Event(id int32, eventId string, data dbus.Variant, timestamp uint32) (err *dbus.Error) { + err = &dbus.ErrMsgUnknownMethod + return +} + +func (*UnimplementedDbusmenu) EventGroup(events []struct { + V0 int32 + V1 string + V2 dbus.Variant + V3 uint32 +}) (idErrors []int32, err *dbus.Error) { + err = &dbus.ErrMsgUnknownMethod + return +} + +func (*UnimplementedDbusmenu) AboutToShow(id int32) (needUpdate bool, err *dbus.Error) { + err = &dbus.ErrMsgUnknownMethod + return +} + +func (*UnimplementedDbusmenu) AboutToShowGroup(ids []int32) (updatesNeeded []int32, idErrors []int32, err *dbus.Error) { + err = &dbus.ErrMsgUnknownMethod + return +} + +// NewDbusmenu creates and allocates com.canonical.dbusmenu. +func NewDbusmenu(object dbus.BusObject) *Dbusmenu { + return &Dbusmenu{object} +} + +// Dbusmenu implements com.canonical.dbusmenu D-Bus interface. +type Dbusmenu struct { + object dbus.BusObject +} + +// GetLayout calls com.canonical.dbusmenu.GetLayout method. +func (o *Dbusmenu) GetLayout(ctx context.Context, parentId int32, recursionDepth int32, propertyNames []string) (revision uint32, layout struct { + V0 int32 + V1 map[string]dbus.Variant + V2 []dbus.Variant +}, err error) { + err = o.object.CallWithContext(ctx, InterfaceDbusmenu+".GetLayout", 0, parentId, recursionDepth, propertyNames).Store(&revision, &layout) + return +} + +// GetGroupProperties calls com.canonical.dbusmenu.GetGroupProperties method. +func (o *Dbusmenu) GetGroupProperties(ctx context.Context, ids []int32, propertyNames []string) (properties []struct { + V0 int32 + V1 map[string]dbus.Variant +}, err error) { + err = o.object.CallWithContext(ctx, InterfaceDbusmenu+".GetGroupProperties", 0, ids, propertyNames).Store(&properties) + return +} + +// GetProperty calls com.canonical.dbusmenu.GetProperty method. +func (o *Dbusmenu) GetProperty(ctx context.Context, id int32, name string) (value dbus.Variant, err error) { + err = o.object.CallWithContext(ctx, InterfaceDbusmenu+".GetProperty", 0, id, name).Store(&value) + return +} + +// Event calls com.canonical.dbusmenu.Event method. +func (o *Dbusmenu) Event(ctx context.Context, id int32, eventId string, data dbus.Variant, timestamp uint32) (err error) { + err = o.object.CallWithContext(ctx, InterfaceDbusmenu+".Event", 0, id, eventId, data, timestamp).Store() + return +} + +// EventGroup calls com.canonical.dbusmenu.EventGroup method. +func (o *Dbusmenu) EventGroup(ctx context.Context, events []struct { + V0 int32 + V1 string + V2 dbus.Variant + V3 uint32 +}) (idErrors []int32, err error) { + err = o.object.CallWithContext(ctx, InterfaceDbusmenu+".EventGroup", 0, events).Store(&idErrors) + return +} + +// AboutToShow calls com.canonical.dbusmenu.AboutToShow method. +func (o *Dbusmenu) AboutToShow(ctx context.Context, id int32) (needUpdate bool, err error) { + err = o.object.CallWithContext(ctx, InterfaceDbusmenu+".AboutToShow", 0, id).Store(&needUpdate) + return +} + +// AboutToShowGroup calls com.canonical.dbusmenu.AboutToShowGroup method. +func (o *Dbusmenu) AboutToShowGroup(ctx context.Context, ids []int32) (updatesNeeded []int32, idErrors []int32, err error) { + err = o.object.CallWithContext(ctx, InterfaceDbusmenu+".AboutToShowGroup", 0, ids).Store(&updatesNeeded, &idErrors) + return +} + +// GetVersion gets com.canonical.dbusmenu.Version property. +func (o *Dbusmenu) GetVersion(ctx context.Context) (version uint32, err error) { + err = o.object.CallWithContext(ctx, "org.freedesktop.DBus.Properties.Get", 0, InterfaceDbusmenu, "Version").Store(&version) + return +} + +// GetTextDirection gets com.canonical.dbusmenu.TextDirection property. +func (o *Dbusmenu) GetTextDirection(ctx context.Context) (textDirection string, err error) { + err = o.object.CallWithContext(ctx, "org.freedesktop.DBus.Properties.Get", 0, InterfaceDbusmenu, "TextDirection").Store(&textDirection) + return +} + +// GetStatus gets com.canonical.dbusmenu.Status property. +func (o *Dbusmenu) GetStatus(ctx context.Context) (status string, err error) { + err = o.object.CallWithContext(ctx, "org.freedesktop.DBus.Properties.Get", 0, InterfaceDbusmenu, "Status").Store(&status) + return +} + +// GetIconThemePath gets com.canonical.dbusmenu.IconThemePath property. +func (o *Dbusmenu) GetIconThemePath(ctx context.Context) (iconThemePath []string, err error) { + err = o.object.CallWithContext(ctx, "org.freedesktop.DBus.Properties.Get", 0, InterfaceDbusmenu, "IconThemePath").Store(&iconThemePath) + return +} + +// Dbusmenu_ItemsPropertiesUpdatedSignal represents com.canonical.dbusmenu.ItemsPropertiesUpdated signal. +type Dbusmenu_ItemsPropertiesUpdatedSignal struct { + sender string + Path dbus.ObjectPath + Body *Dbusmenu_ItemsPropertiesUpdatedSignalBody +} + +// Name returns the signal's name. +func (s *Dbusmenu_ItemsPropertiesUpdatedSignal) Name() string { + return "ItemsPropertiesUpdated" +} + +// Interface returns the signal's interface. +func (s *Dbusmenu_ItemsPropertiesUpdatedSignal) Interface() string { + return InterfaceDbusmenu +} + +// Sender returns the signal's sender unique name. +func (s *Dbusmenu_ItemsPropertiesUpdatedSignal) Sender() string { + return s.sender +} + +func (s *Dbusmenu_ItemsPropertiesUpdatedSignal) path() dbus.ObjectPath { + return s.Path +} + +func (s *Dbusmenu_ItemsPropertiesUpdatedSignal) values() []interface{} { + return []interface{}{s.Body.UpdatedProps, s.Body.RemovedProps} +} + +// Dbusmenu_ItemsPropertiesUpdatedSignalBody is body container. +type Dbusmenu_ItemsPropertiesUpdatedSignalBody struct { + UpdatedProps []struct { + V0 int32 + V1 map[string]dbus.Variant + } + RemovedProps []struct { + V0 int32 + V1 []string + } +} + +// Dbusmenu_LayoutUpdatedSignal represents com.canonical.dbusmenu.LayoutUpdated signal. +type Dbusmenu_LayoutUpdatedSignal struct { + sender string + Path dbus.ObjectPath + Body *Dbusmenu_LayoutUpdatedSignalBody +} + +// Name returns the signal's name. +func (s *Dbusmenu_LayoutUpdatedSignal) Name() string { + return "LayoutUpdated" +} + +// Interface returns the signal's interface. +func (s *Dbusmenu_LayoutUpdatedSignal) Interface() string { + return InterfaceDbusmenu +} + +// Sender returns the signal's sender unique name. +func (s *Dbusmenu_LayoutUpdatedSignal) Sender() string { + return s.sender +} + +func (s *Dbusmenu_LayoutUpdatedSignal) path() dbus.ObjectPath { + return s.Path +} + +func (s *Dbusmenu_LayoutUpdatedSignal) values() []interface{} { + return []interface{}{s.Body.Revision, s.Body.Parent} +} + +// Dbusmenu_LayoutUpdatedSignalBody is body container. +type Dbusmenu_LayoutUpdatedSignalBody struct { + Revision uint32 + Parent int32 +} + +// Dbusmenu_ItemActivationRequestedSignal represents com.canonical.dbusmenu.ItemActivationRequested signal. +type Dbusmenu_ItemActivationRequestedSignal struct { + sender string + Path dbus.ObjectPath + Body *Dbusmenu_ItemActivationRequestedSignalBody +} + +// Name returns the signal's name. +func (s *Dbusmenu_ItemActivationRequestedSignal) Name() string { + return "ItemActivationRequested" +} + +// Interface returns the signal's interface. +func (s *Dbusmenu_ItemActivationRequestedSignal) Interface() string { + return InterfaceDbusmenu +} + +// Sender returns the signal's sender unique name. +func (s *Dbusmenu_ItemActivationRequestedSignal) Sender() string { + return s.sender +} + +func (s *Dbusmenu_ItemActivationRequestedSignal) path() dbus.ObjectPath { + return s.Path +} + +func (s *Dbusmenu_ItemActivationRequestedSignal) values() []interface{} { + return []interface{}{s.Body.Id, s.Body.Timestamp} +} + +// Dbusmenu_ItemActivationRequestedSignalBody is body container. +type Dbusmenu_ItemActivationRequestedSignalBody struct { + Id int32 + Timestamp uint32 +} diff --git a/v3/internal/dbus/notifier/.keep b/v3/internal/dbus/notifier/.keep new file mode 100644 index 000000000..e69de29bb diff --git a/v3/internal/dbus/notifier/status_notifier_item.go b/v3/internal/dbus/notifier/status_notifier_item.go new file mode 100644 index 000000000..998916440 --- /dev/null +++ b/v3/internal/dbus/notifier/status_notifier_item.go @@ -0,0 +1,636 @@ +// Code generated by dbus-codegen-go DO NOT EDIT. +package notifier + +import ( + "context" + "errors" + "fmt" + "github.com/godbus/dbus/v5" + "github.com/godbus/dbus/v5/introspect" +) + +var ( + // Introspection for org.kde.StatusNotifierItem + IntrospectDataStatusNotifierItem = introspect.Interface{ + Name: "org.kde.StatusNotifierItem", + Methods: []introspect.Method{{Name: "ContextMenu", Args: []introspect.Arg{ + {Name: "x", Type: "i", Direction: "in"}, + {Name: "y", Type: "i", Direction: "in"}, + }}, + {Name: "Activate", Args: []introspect.Arg{ + {Name: "x", Type: "i", Direction: "in"}, + {Name: "y", Type: "i", Direction: "in"}, + }}, + {Name: "SecondaryActivate", Args: []introspect.Arg{ + {Name: "x", Type: "i", Direction: "in"}, + {Name: "y", Type: "i", Direction: "in"}, + }}, + {Name: "Scroll", Args: []introspect.Arg{ + {Name: "delta", Type: "i", Direction: "in"}, + {Name: "orientation", Type: "s", Direction: "in"}, + }}, + }, + Signals: []introspect.Signal{{Name: "NewTitle"}, + {Name: "NewIcon"}, + {Name: "NewAttentionIcon"}, + {Name: "NewOverlayIcon"}, + {Name: "NewStatus", Args: []introspect.Arg{ + {Name: "status", Type: "s", Direction: ""}, + }}, + {Name: "NewIconThemePath", Args: []introspect.Arg{ + {Name: "icon_theme_path", Type: "s", Direction: "out"}, + }}, + {Name: "NewMenu"}, + }, + Properties: []introspect.Property{{Name: "Category", Type: "s", Access: "read"}, + {Name: "Id", Type: "s", Access: "read"}, + {Name: "Title", Type: "s", Access: "read"}, + {Name: "Status", Type: "s", Access: "read"}, + {Name: "WindowId", Type: "i", Access: "read"}, + {Name: "IconThemePath", Type: "s", Access: "read"}, + {Name: "Menu", Type: "o", Access: "read"}, + {Name: "ItemIsMenu", Type: "b", Access: "read"}, + {Name: "IconName", Type: "s", Access: "read"}, + {Name: "IconPixmap", Type: "a(iiay)", Access: "read", Annotations: []introspect.Annotation{ + {Name: "org.qtproject.QtDBus.QtTypeName", Value: "KDbusImageVector"}, + }}, + {Name: "OverlayIconName", Type: "s", Access: "read"}, + {Name: "OverlayIconPixmap", Type: "a(iiay)", Access: "read", Annotations: []introspect.Annotation{ + {Name: "org.qtproject.QtDBus.QtTypeName", Value: "KDbusImageVector"}, + }}, + {Name: "AttentionIconName", Type: "s", Access: "read"}, + {Name: "AttentionIconPixmap", Type: "a(iiay)", Access: "read", Annotations: []introspect.Annotation{ + {Name: "org.qtproject.QtDBus.QtTypeName", Value: "KDbusImageVector"}, + }}, + {Name: "AttentionMovieName", Type: "s", Access: "read"}, + {Name: "ToolTip", Type: "(sa(iiay)ss)", Access: "read", Annotations: []introspect.Annotation{ + {Name: "org.qtproject.QtDBus.QtTypeName", Value: "KDbusToolTipStruct"}, + }}, + }, + Annotations: []introspect.Annotation{}, + } +) + +// Signal is a common interface for all signals. +type Signal interface { + Name() string + Interface() string + Sender() string + + path() dbus.ObjectPath + values() []interface{} +} + +// Emit sends the given signal to the bus. +func Emit(conn *dbus.Conn, s Signal) error { + return conn.Emit(s.path(), s.Interface()+"."+s.Name(), s.values()...) +} + +// ErrUnknownSignal is returned by LookupSignal when a signal cannot be resolved. +var ErrUnknownSignal = errors.New("unknown signal") + +// LookupSignal converts the given raw D-Bus signal with variable body +// into one with typed structured body or returns ErrUnknownSignal error. +func LookupSignal(signal *dbus.Signal) (Signal, error) { + switch signal.Name { + case InterfaceStatusNotifierItem + "." + "NewTitle": + return &StatusNotifierItem_NewTitleSignal{ + sender: signal.Sender, + Path: signal.Path, + Body: &StatusNotifierItem_NewTitleSignalBody{}, + }, nil + case InterfaceStatusNotifierItem + "." + "NewIcon": + return &StatusNotifierItem_NewIconSignal{ + sender: signal.Sender, + Path: signal.Path, + Body: &StatusNotifierItem_NewIconSignalBody{}, + }, nil + case InterfaceStatusNotifierItem + "." + "NewAttentionIcon": + return &StatusNotifierItem_NewAttentionIconSignal{ + sender: signal.Sender, + Path: signal.Path, + Body: &StatusNotifierItem_NewAttentionIconSignalBody{}, + }, nil + case InterfaceStatusNotifierItem + "." + "NewOverlayIcon": + return &StatusNotifierItem_NewOverlayIconSignal{ + sender: signal.Sender, + Path: signal.Path, + Body: &StatusNotifierItem_NewOverlayIconSignalBody{}, + }, nil + case InterfaceStatusNotifierItem + "." + "NewStatus": + v0, ok := signal.Body[0].(string) + if !ok { + return nil, fmt.Errorf("prop .Status is %T, not string", signal.Body[0]) + } + return &StatusNotifierItem_NewStatusSignal{ + sender: signal.Sender, + Path: signal.Path, + Body: &StatusNotifierItem_NewStatusSignalBody{ + Status: v0, + }, + }, nil + case InterfaceStatusNotifierItem + "." + "NewIconThemePath": + v0, ok := signal.Body[0].(string) + if !ok { + return nil, fmt.Errorf("prop .IconThemePath is %T, not string", signal.Body[0]) + } + return &StatusNotifierItem_NewIconThemePathSignal{ + sender: signal.Sender, + Path: signal.Path, + Body: &StatusNotifierItem_NewIconThemePathSignalBody{ + IconThemePath: v0, + }, + }, nil + case InterfaceStatusNotifierItem + "." + "NewMenu": + return &StatusNotifierItem_NewMenuSignal{ + sender: signal.Sender, + Path: signal.Path, + Body: &StatusNotifierItem_NewMenuSignalBody{}, + }, nil + default: + return nil, ErrUnknownSignal + } +} + +// AddMatchSignal registers a match rule for the given signal, +// opts are appended to the automatically generated signal's rules. +func AddMatchSignal(conn *dbus.Conn, s Signal, opts ...dbus.MatchOption) error { + return conn.AddMatchSignal(append([]dbus.MatchOption{ + dbus.WithMatchInterface(s.Interface()), + dbus.WithMatchMember(s.Name()), + }, opts...)...) +} + +// RemoveMatchSignal unregisters the previously registered subscription. +func RemoveMatchSignal(conn *dbus.Conn, s Signal, opts ...dbus.MatchOption) error { + return conn.RemoveMatchSignal(append([]dbus.MatchOption{ + dbus.WithMatchInterface(s.Interface()), + dbus.WithMatchMember(s.Name()), + }, opts...)...) +} + +// Interface name constants. +const ( + InterfaceStatusNotifierItem = "org.kde.StatusNotifierItem" +) + +// StatusNotifierItemer is org.kde.StatusNotifierItem interface. +type StatusNotifierItemer interface { + // ContextMenu is org.kde.StatusNotifierItem.ContextMenu method. + ContextMenu(x int32, y int32) (err *dbus.Error) + // Activate is org.kde.StatusNotifierItem.Activate method. + Activate(x int32, y int32) (err *dbus.Error) + // SecondaryActivate is org.kde.StatusNotifierItem.SecondaryActivate method. + SecondaryActivate(x int32, y int32) (err *dbus.Error) + // Scroll is org.kde.StatusNotifierItem.Scroll method. + Scroll(delta int32, orientation string) (err *dbus.Error) +} + +// ExportStatusNotifierItem exports the given object that implements org.kde.StatusNotifierItem on the bus. +func ExportStatusNotifierItem(conn *dbus.Conn, path dbus.ObjectPath, v StatusNotifierItemer) error { + return conn.ExportSubtreeMethodTable(map[string]interface{}{ + "ContextMenu": v.ContextMenu, + "Activate": v.Activate, + "SecondaryActivate": v.SecondaryActivate, + "Scroll": v.Scroll, + }, path, InterfaceStatusNotifierItem) +} + +// UnexportStatusNotifierItem unexports org.kde.StatusNotifierItem interface on the named path. +func UnexportStatusNotifierItem(conn *dbus.Conn, path dbus.ObjectPath) error { + return conn.Export(nil, path, InterfaceStatusNotifierItem) +} + +// UnimplementedStatusNotifierItem can be embedded to have forward compatible server implementations. +type UnimplementedStatusNotifierItem struct{} + +func (*UnimplementedStatusNotifierItem) iface() string { + return InterfaceStatusNotifierItem +} + +func (*UnimplementedStatusNotifierItem) ContextMenu(x int32, y int32) (err *dbus.Error) { + err = &dbus.ErrMsgUnknownMethod + return +} + +func (*UnimplementedStatusNotifierItem) Activate(x int32, y int32) (err *dbus.Error) { + err = &dbus.ErrMsgUnknownMethod + return +} + +func (*UnimplementedStatusNotifierItem) SecondaryActivate(x int32, y int32) (err *dbus.Error) { + err = &dbus.ErrMsgUnknownMethod + return +} + +func (*UnimplementedStatusNotifierItem) Scroll(delta int32, orientation string) (err *dbus.Error) { + err = &dbus.ErrMsgUnknownMethod + return +} + +// NewStatusNotifierItem creates and allocates org.kde.StatusNotifierItem. +func NewStatusNotifierItem(object dbus.BusObject) *StatusNotifierItem { + return &StatusNotifierItem{object} +} + +// StatusNotifierItem implements org.kde.StatusNotifierItem D-Bus interface. +type StatusNotifierItem struct { + object dbus.BusObject +} + +// ContextMenu calls org.kde.StatusNotifierItem.ContextMenu method. +func (o *StatusNotifierItem) ContextMenu(ctx context.Context, x int32, y int32) (err error) { + err = o.object.CallWithContext(ctx, InterfaceStatusNotifierItem+".ContextMenu", 0, x, y).Store() + return +} + +// Activate calls org.kde.StatusNotifierItem.Activate method. +func (o *StatusNotifierItem) Activate(ctx context.Context, x int32, y int32) (err error) { + err = o.object.CallWithContext(ctx, InterfaceStatusNotifierItem+".Activate", 0, x, y).Store() + return +} + +// SecondaryActivate calls org.kde.StatusNotifierItem.SecondaryActivate method. +func (o *StatusNotifierItem) SecondaryActivate(ctx context.Context, x int32, y int32) (err error) { + err = o.object.CallWithContext(ctx, InterfaceStatusNotifierItem+".SecondaryActivate", 0, x, y).Store() + return +} + +// Scroll calls org.kde.StatusNotifierItem.Scroll method. +func (o *StatusNotifierItem) Scroll(ctx context.Context, delta int32, orientation string) (err error) { + err = o.object.CallWithContext(ctx, InterfaceStatusNotifierItem+".Scroll", 0, delta, orientation).Store() + return +} + +// GetCategory gets org.kde.StatusNotifierItem.Category property. +func (o *StatusNotifierItem) GetCategory(ctx context.Context) (category string, err error) { + err = o.object.CallWithContext(ctx, "org.freedesktop.DBus.Properties.Get", 0, InterfaceStatusNotifierItem, "Category").Store(&category) + return +} + +// GetId gets org.kde.StatusNotifierItem.Id property. +func (o *StatusNotifierItem) GetId(ctx context.Context) (id string, err error) { + err = o.object.CallWithContext(ctx, "org.freedesktop.DBus.Properties.Get", 0, InterfaceStatusNotifierItem, "Id").Store(&id) + return +} + +// GetTitle gets org.kde.StatusNotifierItem.Title property. +func (o *StatusNotifierItem) GetTitle(ctx context.Context) (title string, err error) { + err = o.object.CallWithContext(ctx, "org.freedesktop.DBus.Properties.Get", 0, InterfaceStatusNotifierItem, "Title").Store(&title) + return +} + +// GetStatus gets org.kde.StatusNotifierItem.Status property. +func (o *StatusNotifierItem) GetStatus(ctx context.Context) (status string, err error) { + err = o.object.CallWithContext(ctx, "org.freedesktop.DBus.Properties.Get", 0, InterfaceStatusNotifierItem, "Status").Store(&status) + return +} + +// GetWindowId gets org.kde.StatusNotifierItem.WindowId property. +func (o *StatusNotifierItem) GetWindowId(ctx context.Context) (windowId int32, err error) { + err = o.object.CallWithContext(ctx, "org.freedesktop.DBus.Properties.Get", 0, InterfaceStatusNotifierItem, "WindowId").Store(&windowId) + return +} + +// GetIconThemePath gets org.kde.StatusNotifierItem.IconThemePath property. +func (o *StatusNotifierItem) GetIconThemePath(ctx context.Context) (iconThemePath string, err error) { + err = o.object.CallWithContext(ctx, "org.freedesktop.DBus.Properties.Get", 0, InterfaceStatusNotifierItem, "IconThemePath").Store(&iconThemePath) + return +} + +// GetMenu gets org.kde.StatusNotifierItem.Menu property. +func (o *StatusNotifierItem) GetMenu(ctx context.Context) (menu dbus.ObjectPath, err error) { + err = o.object.CallWithContext(ctx, "org.freedesktop.DBus.Properties.Get", 0, InterfaceStatusNotifierItem, "Menu").Store(&menu) + return +} + +// GetItemIsMenu gets org.kde.StatusNotifierItem.ItemIsMenu property. +func (o *StatusNotifierItem) GetItemIsMenu(ctx context.Context) (itemIsMenu bool, err error) { + err = o.object.CallWithContext(ctx, "org.freedesktop.DBus.Properties.Get", 0, InterfaceStatusNotifierItem, "ItemIsMenu").Store(&itemIsMenu) + return +} + +// GetIconName gets org.kde.StatusNotifierItem.IconName property. +func (o *StatusNotifierItem) GetIconName(ctx context.Context) (iconName string, err error) { + err = o.object.CallWithContext(ctx, "org.freedesktop.DBus.Properties.Get", 0, InterfaceStatusNotifierItem, "IconName").Store(&iconName) + return +} + +// GetIconPixmap gets org.kde.StatusNotifierItem.IconPixmap property. +// +// Annotations: +// +// @org.qtproject.QtDBus.QtTypeName = KDbusImageVector +func (o *StatusNotifierItem) GetIconPixmap(ctx context.Context) (iconPixmap []struct { + V0 int32 + V1 int32 + V2 []byte +}, err error) { + err = o.object.CallWithContext(ctx, "org.freedesktop.DBus.Properties.Get", 0, InterfaceStatusNotifierItem, "IconPixmap").Store(&iconPixmap) + return +} + +// GetOverlayIconName gets org.kde.StatusNotifierItem.OverlayIconName property. +func (o *StatusNotifierItem) GetOverlayIconName(ctx context.Context) (overlayIconName string, err error) { + err = o.object.CallWithContext(ctx, "org.freedesktop.DBus.Properties.Get", 0, InterfaceStatusNotifierItem, "OverlayIconName").Store(&overlayIconName) + return +} + +// GetOverlayIconPixmap gets org.kde.StatusNotifierItem.OverlayIconPixmap property. +// +// Annotations: +// +// @org.qtproject.QtDBus.QtTypeName = KDbusImageVector +func (o *StatusNotifierItem) GetOverlayIconPixmap(ctx context.Context) (overlayIconPixmap []struct { + V0 int32 + V1 int32 + V2 []byte +}, err error) { + err = o.object.CallWithContext(ctx, "org.freedesktop.DBus.Properties.Get", 0, InterfaceStatusNotifierItem, "OverlayIconPixmap").Store(&overlayIconPixmap) + return +} + +// GetAttentionIconName gets org.kde.StatusNotifierItem.AttentionIconName property. +func (o *StatusNotifierItem) GetAttentionIconName(ctx context.Context) (attentionIconName string, err error) { + err = o.object.CallWithContext(ctx, "org.freedesktop.DBus.Properties.Get", 0, InterfaceStatusNotifierItem, "AttentionIconName").Store(&attentionIconName) + return +} + +// GetAttentionIconPixmap gets org.kde.StatusNotifierItem.AttentionIconPixmap property. +// +// Annotations: +// +// @org.qtproject.QtDBus.QtTypeName = KDbusImageVector +func (o *StatusNotifierItem) GetAttentionIconPixmap(ctx context.Context) (attentionIconPixmap []struct { + V0 int32 + V1 int32 + V2 []byte +}, err error) { + err = o.object.CallWithContext(ctx, "org.freedesktop.DBus.Properties.Get", 0, InterfaceStatusNotifierItem, "AttentionIconPixmap").Store(&attentionIconPixmap) + return +} + +// GetAttentionMovieName gets org.kde.StatusNotifierItem.AttentionMovieName property. +func (o *StatusNotifierItem) GetAttentionMovieName(ctx context.Context) (attentionMovieName string, err error) { + err = o.object.CallWithContext(ctx, "org.freedesktop.DBus.Properties.Get", 0, InterfaceStatusNotifierItem, "AttentionMovieName").Store(&attentionMovieName) + return +} + +// GetToolTip gets org.kde.StatusNotifierItem.ToolTip property. +// +// Annotations: +// +// @org.qtproject.QtDBus.QtTypeName = KDbusToolTipStruct +func (o *StatusNotifierItem) GetToolTip(ctx context.Context) (toolTip struct { + V0 string + V1 []struct { + V0 int32 + V1 int32 + V2 []byte + } + V2 string + V3 string +}, err error) { + err = o.object.CallWithContext(ctx, "org.freedesktop.DBus.Properties.Get", 0, InterfaceStatusNotifierItem, "ToolTip").Store(&toolTip) + return +} + +// StatusNotifierItem_NewTitleSignal represents org.kde.StatusNotifierItem.NewTitle signal. +type StatusNotifierItem_NewTitleSignal struct { + sender string + Path dbus.ObjectPath + Body *StatusNotifierItem_NewTitleSignalBody +} + +// Name returns the signal's name. +func (s *StatusNotifierItem_NewTitleSignal) Name() string { + return "NewTitle" +} + +// Interface returns the signal's interface. +func (s *StatusNotifierItem_NewTitleSignal) Interface() string { + return InterfaceStatusNotifierItem +} + +// Sender returns the signal's sender unique name. +func (s *StatusNotifierItem_NewTitleSignal) Sender() string { + return s.sender +} + +func (s *StatusNotifierItem_NewTitleSignal) path() dbus.ObjectPath { + return s.Path +} + +func (s *StatusNotifierItem_NewTitleSignal) values() []interface{} { + return []interface{}{} +} + +// StatusNotifierItem_NewTitleSignalBody is body container. +type StatusNotifierItem_NewTitleSignalBody struct { +} + +// StatusNotifierItem_NewIconSignal represents org.kde.StatusNotifierItem.NewIcon signal. +type StatusNotifierItem_NewIconSignal struct { + sender string + Path dbus.ObjectPath + Body *StatusNotifierItem_NewIconSignalBody +} + +// Name returns the signal's name. +func (s *StatusNotifierItem_NewIconSignal) Name() string { + return "NewIcon" +} + +// Interface returns the signal's interface. +func (s *StatusNotifierItem_NewIconSignal) Interface() string { + return InterfaceStatusNotifierItem +} + +// Sender returns the signal's sender unique name. +func (s *StatusNotifierItem_NewIconSignal) Sender() string { + return s.sender +} + +func (s *StatusNotifierItem_NewIconSignal) path() dbus.ObjectPath { + return s.Path +} + +func (s *StatusNotifierItem_NewIconSignal) values() []interface{} { + return []interface{}{} +} + +// StatusNotifierItem_NewIconSignalBody is body container. +type StatusNotifierItem_NewIconSignalBody struct { +} + +// StatusNotifierItem_NewAttentionIconSignal represents org.kde.StatusNotifierItem.NewAttentionIcon signal. +type StatusNotifierItem_NewAttentionIconSignal struct { + sender string + Path dbus.ObjectPath + Body *StatusNotifierItem_NewAttentionIconSignalBody +} + +// Name returns the signal's name. +func (s *StatusNotifierItem_NewAttentionIconSignal) Name() string { + return "NewAttentionIcon" +} + +// Interface returns the signal's interface. +func (s *StatusNotifierItem_NewAttentionIconSignal) Interface() string { + return InterfaceStatusNotifierItem +} + +// Sender returns the signal's sender unique name. +func (s *StatusNotifierItem_NewAttentionIconSignal) Sender() string { + return s.sender +} + +func (s *StatusNotifierItem_NewAttentionIconSignal) path() dbus.ObjectPath { + return s.Path +} + +func (s *StatusNotifierItem_NewAttentionIconSignal) values() []interface{} { + return []interface{}{} +} + +// StatusNotifierItem_NewAttentionIconSignalBody is body container. +type StatusNotifierItem_NewAttentionIconSignalBody struct { +} + +// StatusNotifierItem_NewOverlayIconSignal represents org.kde.StatusNotifierItem.NewOverlayIcon signal. +type StatusNotifierItem_NewOverlayIconSignal struct { + sender string + Path dbus.ObjectPath + Body *StatusNotifierItem_NewOverlayIconSignalBody +} + +// Name returns the signal's name. +func (s *StatusNotifierItem_NewOverlayIconSignal) Name() string { + return "NewOverlayIcon" +} + +// Interface returns the signal's interface. +func (s *StatusNotifierItem_NewOverlayIconSignal) Interface() string { + return InterfaceStatusNotifierItem +} + +// Sender returns the signal's sender unique name. +func (s *StatusNotifierItem_NewOverlayIconSignal) Sender() string { + return s.sender +} + +func (s *StatusNotifierItem_NewOverlayIconSignal) path() dbus.ObjectPath { + return s.Path +} + +func (s *StatusNotifierItem_NewOverlayIconSignal) values() []interface{} { + return []interface{}{} +} + +// StatusNotifierItem_NewOverlayIconSignalBody is body container. +type StatusNotifierItem_NewOverlayIconSignalBody struct { +} + +// StatusNotifierItem_NewStatusSignal represents org.kde.StatusNotifierItem.NewStatus signal. +type StatusNotifierItem_NewStatusSignal struct { + sender string + Path dbus.ObjectPath + Body *StatusNotifierItem_NewStatusSignalBody +} + +// Name returns the signal's name. +func (s *StatusNotifierItem_NewStatusSignal) Name() string { + return "NewStatus" +} + +// Interface returns the signal's interface. +func (s *StatusNotifierItem_NewStatusSignal) Interface() string { + return InterfaceStatusNotifierItem +} + +// Sender returns the signal's sender unique name. +func (s *StatusNotifierItem_NewStatusSignal) Sender() string { + return s.sender +} + +func (s *StatusNotifierItem_NewStatusSignal) path() dbus.ObjectPath { + return s.Path +} + +func (s *StatusNotifierItem_NewStatusSignal) values() []interface{} { + return []interface{}{s.Body.Status} +} + +// StatusNotifierItem_NewStatusSignalBody is body container. +type StatusNotifierItem_NewStatusSignalBody struct { + Status string +} + +// StatusNotifierItem_NewIconThemePathSignal represents org.kde.StatusNotifierItem.NewIconThemePath signal. +type StatusNotifierItem_NewIconThemePathSignal struct { + sender string + Path dbus.ObjectPath + Body *StatusNotifierItem_NewIconThemePathSignalBody +} + +// Name returns the signal's name. +func (s *StatusNotifierItem_NewIconThemePathSignal) Name() string { + return "NewIconThemePath" +} + +// Interface returns the signal's interface. +func (s *StatusNotifierItem_NewIconThemePathSignal) Interface() string { + return InterfaceStatusNotifierItem +} + +// Sender returns the signal's sender unique name. +func (s *StatusNotifierItem_NewIconThemePathSignal) Sender() string { + return s.sender +} + +func (s *StatusNotifierItem_NewIconThemePathSignal) path() dbus.ObjectPath { + return s.Path +} + +func (s *StatusNotifierItem_NewIconThemePathSignal) values() []interface{} { + return []interface{}{s.Body.IconThemePath} +} + +// StatusNotifierItem_NewIconThemePathSignalBody is body container. +type StatusNotifierItem_NewIconThemePathSignalBody struct { + IconThemePath string +} + +// StatusNotifierItem_NewMenuSignal represents org.kde.StatusNotifierItem.NewMenu signal. +type StatusNotifierItem_NewMenuSignal struct { + sender string + Path dbus.ObjectPath + Body *StatusNotifierItem_NewMenuSignalBody +} + +// Name returns the signal's name. +func (s *StatusNotifierItem_NewMenuSignal) Name() string { + return "NewMenu" +} + +// Interface returns the signal's interface. +func (s *StatusNotifierItem_NewMenuSignal) Interface() string { + return InterfaceStatusNotifierItem +} + +// Sender returns the signal's sender unique name. +func (s *StatusNotifierItem_NewMenuSignal) Sender() string { + return s.sender +} + +func (s *StatusNotifierItem_NewMenuSignal) path() dbus.ObjectPath { + return s.Path +} + +func (s *StatusNotifierItem_NewMenuSignal) values() []interface{} { + return []interface{}{} +} + +// StatusNotifierItem_NewMenuSignalBody is body container. +type StatusNotifierItem_NewMenuSignalBody struct { +} diff --git a/v3/internal/debug/debug.go b/v3/internal/debug/debug.go new file mode 100644 index 000000000..394688ce7 --- /dev/null +++ b/v3/internal/debug/debug.go @@ -0,0 +1,42 @@ +package debug + +import ( + "os" + "path/filepath" + "runtime" +) + +var LocalModulePath = "" + +func init() { + // Check if .git exists in the relative directory from here: ../../.. + // If it does, we are in a local build + gitDir := RelativePath("..", "..", "..", ".git") + if _, err := os.Stat(gitDir); err == nil { + modulePath := RelativePath("..", "..", "..") + LocalModulePath, _ = filepath.Abs(modulePath) + } +} + +// RelativePath returns a qualified path created by joining the +// directory of the calling file and the given relative path. +func RelativePath(relativepath string, optionalpaths ...string) string { + _, thisFile, _, _ := runtime.Caller(1) + localDir := filepath.Dir(thisFile) + + // If we have optional paths, join them to the relativepath + if len(optionalpaths) > 0 { + paths := []string{relativepath} + paths = append(paths, optionalpaths...) + relativepath = filepath.Join(paths...) + } + result, err := filepath.Abs(filepath.Join(localDir, relativepath)) + if err != nil { + // I'm allowing this for 1 reason only: It's fatal if the path + // supplied is wrong as it's only used internally in Wails. If we get + // that path wrong, we should know about it immediately. The other reason is + // that it cuts down a ton of unnecassary error handling. + panic(err) + } + return result +} diff --git a/v3/internal/doctor/doctor.go b/v3/internal/doctor/doctor.go new file mode 100644 index 000000000..0d66f33f6 --- /dev/null +++ b/v3/internal/doctor/doctor.go @@ -0,0 +1,192 @@ +package doctor + +import ( + "fmt" + "github.com/go-git/go-git/v5" + "github.com/jaypipes/ghw" + "github.com/pterm/pterm" + "github.com/samber/lo" + "github.com/wailsapp/wails/v3/internal/operatingsystem" + "github.com/wailsapp/wails/v3/internal/version" + "path/filepath" + "runtime" + "runtime/debug" + "slices" + "strconv" +) + +func Run() (err error) { + + pterm.DefaultSection = *pterm.DefaultSection. + WithBottomPadding(0). + WithStyle(pterm.NewStyle(pterm.FgBlue, pterm.Bold)) + + pterm.Println() // Spacer + pterm.DefaultHeader.WithBackgroundStyle(pterm.NewStyle(pterm.BgLightBlue)).WithMargin(10).Println("Wails Doctor") + pterm.Println() // Spacer + + spinner, _ := pterm.DefaultSpinner.WithRemoveWhenDone().Start("Scanning system - Please wait (this may take a long time)...") + + defer func() { + if err != nil { + spinner.Fail() + } + }() + + /** Build **/ + + // BuildSettings contains the build settings for the application + var BuildSettings map[string]string + + // BuildInfo contains the build info for the application + var BuildInfo *debug.BuildInfo + + var ok bool + BuildInfo, ok = debug.ReadBuildInfo() + if !ok { + return fmt.Errorf("could not read build info from binary") + } + BuildSettings = lo.Associate(BuildInfo.Settings, func(setting debug.BuildSetting) (string, string) { + return setting.Key, setting.Value + }) + + /** Operating System **/ + + // Get system info + info, err := operatingsystem.Info() + if err != nil { + pterm.Error.Println("Failed to get system information") + return err + } + + /** Wails **/ + wailsPackage, _ := lo.Find(BuildInfo.Deps, func(dep *debug.Module) bool { + return dep.Path == "github.com/wailsapp/wails/v3" + }) + + wailsVersion := version.VersionString + if wailsPackage != nil && wailsPackage.Replace != nil { + wailsVersion = "(local) => " + filepath.ToSlash(wailsPackage.Replace.Path) + // Get the latest commit hash + repo, err := git.PlainOpen(filepath.Join(wailsPackage.Replace.Path, "..")) + if err == nil { + head, err := repo.Head() + if err == nil { + wailsVersion += " (" + head.Hash().String()[:8] + ")" + } + } + } + + platformExtras, ok := getInfo() + + spinner.Success() + + /** Output **/ + + pterm.DefaultSection.Println("Build Environment") + + tableData := pterm.TableData{ + {"Wails CLI", wailsVersion}, + {"Go Version", runtime.Version()}, + } + + if buildInfo, _ := debug.ReadBuildInfo(); buildInfo != nil { + buildSettingToName := map[string]string{ + "vcs.revision": "Revision", + "vcs.modified": "Modified", + } + for _, buildSetting := range buildInfo.Settings { + name := buildSettingToName[buildSetting.Key] + if name == "" { + continue + } + tableData = append(tableData, []string{name, buildSetting.Value}) + } + } + + mapKeys := lo.Keys(BuildSettings) + slices.Sort(mapKeys) + for _, key := range mapKeys { + tableData = append(tableData, []string{key, BuildSettings[key]}) + } + + //// Exit early if PM not found + //if info.PM != nil { + // wailsTableData = append(wailsTableData, []string{"Package Manager", info.PM.Name()}) + //} + + err = pterm.DefaultTable.WithData(tableData).Render() + if err != nil { + return err + } + + pterm.DefaultSection.Println("System") + + systemTabledata := pterm.TableData{ + {pterm.Sprint("Name"), info.Name}, + {pterm.Sprint("Version"), info.Version}, + {pterm.Sprint("ID"), info.ID}, + {pterm.Sprint("Branding"), info.Branding}, + + {pterm.Sprint("Platform"), runtime.GOOS}, + {pterm.Sprint("Architecture"), runtime.GOARCH}, + } + + mapKeys = lo.Keys(platformExtras) + slices.Sort(mapKeys) + for _, key := range mapKeys { + systemTabledata = append(systemTabledata, []string{key, platformExtras[key]}) + } + + // Probe CPU + cpus, _ := ghw.CPU() + if cpus != nil { + prefix := "CPU" + for idx, cpu := range cpus.Processors { + if len(cpus.Processors) > 1 { + prefix = "CPU " + strconv.Itoa(idx+1) + } + systemTabledata = append(systemTabledata, []string{prefix, cpu.Model}) + } + } else { + systemTabledata = append(systemTabledata, []string{"CPU", "Unknown"}) + } + + // Probe GPU + gpu, _ := ghw.GPU(ghw.WithDisableWarnings()) + if gpu != nil { + prefix := "GPU" + for idx, card := range gpu.GraphicsCards { + if len(gpu.GraphicsCards) > 1 { + prefix = "GPU " + strconv.Itoa(idx+1) + " " + } + details := fmt.Sprintf("%s (%s) - Driver: %s", card.DeviceInfo.Product.Name, card.DeviceInfo.Vendor.Name, card.DeviceInfo.Driver) + systemTabledata = append(systemTabledata, []string{prefix, details}) + } + } else { + systemTabledata = append(systemTabledata, []string{"GPU", "Unknown"}) + } + + memory, _ := ghw.Memory() + if memory != nil { + systemTabledata = append(systemTabledata, []string{"Memory", strconv.Itoa(int(memory.TotalPhysicalBytes/1024/1024/1024)) + "GB"}) + } else { + systemTabledata = append(systemTabledata, []string{"Memory", "Unknown"}) + } + + //systemTabledata = append(systemTabledata, []string{"CPU", cpu.Processors[0].Model}) + + err = pterm.DefaultTable.WithData(systemTabledata).Render() + if err != nil { + return err + } + + pterm.DefaultSection.Println("Diagnosis") + if !ok { + pterm.Warning.Println("There are some items above that need addressing!") + } else { + pterm.Success.Println("Your system is ready for Wails development!") + } + + return nil +} diff --git a/v3/internal/doctor/doctor_darwin.go b/v3/internal/doctor/doctor_darwin.go new file mode 100644 index 000000000..d92d8953e --- /dev/null +++ b/v3/internal/doctor/doctor_darwin.go @@ -0,0 +1,47 @@ +//go:build darwin + +package doctor + +import ( + "github.com/samber/lo" + "os/exec" + "strings" + "syscall" +) + +func getSysctl(name string) string { + value, err := syscall.Sysctl(name) + if err != nil { + return "unknown" + } + return value +} + +func getInfo() (map[string]string, bool) { + result := make(map[string]string) + ok := true + + // Determine if the app is running on Apple Silicon + // Credit: https://www.yellowduck.be/posts/detecting-apple-silicon-via-go/ + appleSilicon := "unknown" + r, err := syscall.Sysctl("sysctl.proc_translated") + if err == nil { + appleSilicon = lo.Ternary(r == "\x00\x00\x00" || r == "\x01\x00\x00", "true", "false") + } + result["Apple Silicon"] = appleSilicon + result["CPU"] = getSysctl("machdep.cpu.brand_string") + + // Check for xcode command line tools + output, err := exec.Command("xcode-select", "-v").Output() + cliToolsVersion := "N/A. Install by running: `xcode-select --install`" + if err != nil { + ok = false + } else { + cliToolsVersion = strings.TrimPrefix(string(output), "xcode-select version ") + cliToolsVersion = strings.TrimSpace(cliToolsVersion) + cliToolsVersion = strings.TrimSuffix(cliToolsVersion, ".") + } + result["Xcode cli tools"] = cliToolsVersion + + return result, ok +} diff --git a/v3/internal/doctor/doctor_linux.go b/v3/internal/doctor/doctor_linux.go new file mode 100644 index 000000000..7c64da1a6 --- /dev/null +++ b/v3/internal/doctor/doctor_linux.go @@ -0,0 +1,40 @@ +//go:build linux + +package doctor + +import ( + "github.com/wailsapp/wails/v3/internal/doctor/packagemanager" + "github.com/wailsapp/wails/v3/internal/operatingsystem" +) + +func getInfo() (map[string]string, bool) { + result := make(map[string]string) + ok := true + + info, _ := operatingsystem.Info() + + pm := packagemanager.Find(info.ID) + deps, _ := packagemanager.Dependencies(pm) + for _, dep := range deps { + var status string + + switch true { + case !dep.Installed: + if dep.Optional { + status = "[Optional] " + } else { + ok = false + } + status += "not installed." + if dep.InstallCommand != "" { + status += " Install with: " + dep.InstallCommand + } + case dep.Version != "": + status = dep.Version + } + + result[dep.Name] = status + } + + return result, ok +} diff --git a/v3/internal/doctor/doctor_windows.go b/v3/internal/doctor/doctor_windows.go new file mode 100644 index 000000000..90f5fd4db --- /dev/null +++ b/v3/internal/doctor/doctor_windows.go @@ -0,0 +1,21 @@ +//go:build windows + +package doctor + +import ( + "github.com/samber/lo" + "github.com/wailsapp/go-webview2/webviewloader" +) + +func getInfo() (map[string]string, bool) { + ok := true + result := make(map[string]string) + result["Go WebView2Loader"] = lo.Ternary(webviewloader.UsingGoWebview2Loader, "true", "false") + webviewVersion, err := webviewloader.GetAvailableCoreWebView2BrowserVersionString("") + if err != nil { + ok = false + webviewVersion = "Error:" + err.Error() + } + result["WebView2 Version"] = webviewVersion + return result, ok +} diff --git a/v3/internal/doctor/packagemanager/apt.go b/v3/internal/doctor/packagemanager/apt.go new file mode 100644 index 000000000..31926bda2 --- /dev/null +++ b/v3/internal/doctor/packagemanager/apt.go @@ -0,0 +1,92 @@ +//go:build linux + +package packagemanager + +import ( + "regexp" + "strings" +) + +// Apt represents the Apt manager +type Apt struct { + name string + osid string +} + +// NewApt creates a new Apt instance +func NewApt(osid string) *Apt { + return &Apt{ + name: "apt", + osid: osid, + } +} + +// Packages returns the libraries that we need for Wails to compile +// They will potentially differ on different distributions or versions +func (a *Apt) Packages() Packagemap { + return Packagemap{ + "libgtk-3": []*Package{ + {Name: "libgtk-3-dev", SystemPackage: true, Library: true}, + }, + "libwebkit": []*Package{ + {Name: "libwebkit2gtk-4.0-dev", SystemPackage: true, Library: true}, + }, + "gcc": []*Package{ + {Name: "build-essential", SystemPackage: true}, + }, + "pkg-config": []*Package{ + {Name: "pkg-config", SystemPackage: true}, + }, + "npm": []*Package{ + {Name: "npm", SystemPackage: true}, + }, + } +} + +// Name returns the name of the package manager +func (a *Apt) Name() string { + return a.name +} + +func (a *Apt) listPackage(name string) (string, error) { + return execCmd("apt", "list", "-qq", name) +} + +// PackageInstalled tests if the given package name is installed +func (a *Apt) PackageInstalled(pkg *Package) (bool, error) { + if pkg.SystemPackage == false { + return false, nil + } + output, err := a.listPackage(pkg.Name) + // apt list -qq returns "all" if you have packages installed globally and locally + return strings.Contains(output, "installed") || strings.Contains(output, " all"), err +} + +// PackageAvailable tests if the given package is available for installation +func (a *Apt) PackageAvailable(pkg *Package) (bool, error) { + if pkg.SystemPackage == false { + return false, nil + } + output, err := a.listPackage(pkg.Name) + // We add a space to ensure we get a full match, not partial match + escapechars, _ := regexp.Compile(`\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])`) + escapechars.ReplaceAllString(output, "") + installed := strings.HasPrefix(output, pkg.Name) + a.getPackageVersion(pkg, output) + return installed, err +} + +// InstallCommand returns the package manager specific command to install a package +func (a *Apt) InstallCommand(pkg *Package) string { + if pkg.SystemPackage == false { + return pkg.InstallCommand[a.osid] + } + return "sudo apt install " + pkg.Name +} + +func (a *Apt) getPackageVersion(pkg *Package, output string) { + splitOutput := strings.Split(output, " ") + if len(splitOutput) > 1 { + pkg.Version = splitOutput[1] + } +} diff --git a/v3/internal/doctor/packagemanager/dnf.go b/v3/internal/doctor/packagemanager/dnf.go new file mode 100644 index 000000000..f9ad17b1e --- /dev/null +++ b/v3/internal/doctor/packagemanager/dnf.go @@ -0,0 +1,116 @@ +//go:build linux + +package packagemanager + +import ( + "os/exec" + "strings" +) + +// Dnf represents the Dnf manager +type Dnf struct { + name string + osid string +} + +// NewDnf creates a new Dnf instance +func NewDnf(osid string) *Dnf { + return &Dnf{ + name: "dnf", + osid: osid, + } +} + +// Packages returns the libraries that we need for Wails to compile +// They will potentially differ on different distributions or versions +func (y *Dnf) Packages() Packagemap { + return Packagemap{ + "libgtk-3": []*Package{ + {Name: "gtk3-devel", SystemPackage: true, Library: true}, + }, + "libwebkit": []*Package{ + {Name: "webkit2gtk4.0-devel", SystemPackage: true, Library: true}, + {Name: "webkit2gtk3-devel", SystemPackage: true, Library: true}, + // {Name: "webkitgtk3-devel", SystemPackage: true, Library: true}, + }, + "gcc": []*Package{ + {Name: "gcc-c++", SystemPackage: true}, + }, + "pkg-config": []*Package{ + {Name: "pkgconf-pkg-config", SystemPackage: true}, + }, + "npm": []*Package{ + {Name: "npm", SystemPackage: true}, + {Name: "nodejs-npm", SystemPackage: true}, + }, + } +} + +// Name returns the name of the package manager +func (y *Dnf) Name() string { + return y.name +} + +// PackageInstalled tests if the given package name is installed +func (y *Dnf) PackageInstalled(pkg *Package) (bool, error) { + if pkg.SystemPackage == false { + return false, nil + } + stdout, err := execCmd("dnf", "info", "installed", pkg.Name) + if err != nil { + _, ok := err.(*exec.ExitError) + if ok { + return false, nil + } + return false, err + } + + splitoutput := strings.Split(stdout, "\n") + for _, line := range splitoutput { + if strings.HasPrefix(line, "Version") { + splitline := strings.Split(line, ":") + pkg.Version = strings.TrimSpace(splitline[1]) + } + } + + return true, err +} + +// PackageAvailable tests if the given package is available for installation +func (y *Dnf) PackageAvailable(pkg *Package) (bool, error) { + if pkg.SystemPackage == false { + return false, nil + } + stdout, err := execCmd("dnf", "info", pkg.Name) + // We add a space to ensure we get a full match, not partial match + if err != nil { + _, ok := err.(*exec.ExitError) + if ok { + return false, nil + } + return false, err + } + splitoutput := strings.Split(stdout, "\n") + for _, line := range splitoutput { + if strings.HasPrefix(line, "Version") { + splitline := strings.Split(line, ":") + pkg.Version = strings.TrimSpace(splitline[1]) + } + } + return true, nil +} + +// InstallCommand returns the package manager specific command to install a package +func (y *Dnf) InstallCommand(pkg *Package) string { + if pkg.SystemPackage == false { + return pkg.InstallCommand[y.osid] + } + return "sudo dnf install " + pkg.Name +} + +func (y *Dnf) getPackageVersion(pkg *Package, output string) { + splitOutput := strings.Split(output, " ") + if len(splitOutput) > 0 { + pkg.Version = splitOutput[1] + } +} diff --git a/v3/internal/doctor/packagemanager/emerge.go b/v3/internal/doctor/packagemanager/emerge.go new file mode 100644 index 000000000..0fc3e2dff --- /dev/null +++ b/v3/internal/doctor/packagemanager/emerge.go @@ -0,0 +1,112 @@ +//go:build linux + +package packagemanager + +import ( + "os/exec" + "regexp" + "strings" +) + +// Emerge represents the Emerge package manager +type Emerge struct { + name string + osid string +} + +// NewEmerge creates a new Emerge instance +func NewEmerge(osid string) *Emerge { + return &Emerge{ + name: "emerge", + osid: osid, + } +} + +// Packages returns the libraries that we need for Wails to compile +// They will potentially differ on different distributions or versions +func (e *Emerge) Packages() Packagemap { + return Packagemap{ + "libgtk-3": []*Package{ + {Name: "x11-libs/gtk+", SystemPackage: true, Library: true}, + }, + "libwebkit": []*Package{ + {Name: "net-libs/webkit-gtk", SystemPackage: true, Library: true}, + }, + "gcc": []*Package{ + {Name: "sys-devel/gcc", SystemPackage: true}, + }, + "pkg-config": []*Package{ + {Name: "dev-util/pkgconf", SystemPackage: true}, + }, + "npm": []*Package{ + {Name: "net-libs/nodejs", SystemPackage: true}, + }, + } +} + +// Name returns the name of the package manager +func (e *Emerge) Name() string { + return e.name +} + +// PackageInstalled tests if the given package name is installed +func (e *Emerge) PackageInstalled(pkg *Package) (bool, error) { + if pkg.SystemPackage == false { + return false, nil + } + stdout, err := execCmd("emerge", "-s", pkg.Name+"$") + if err != nil { + _, ok := err.(*exec.ExitError) + if ok { + return false, nil + } + return false, err + } + + regex := `.*\*\s+` + regexp.QuoteMeta(pkg.Name) + `\n(?:\S|\s)+?Latest version installed: (.*)` + installedRegex := regexp.MustCompile(regex) + matches := installedRegex.FindStringSubmatch(stdout) + pkg.Version = "" + noOfMatches := len(matches) + installed := false + if noOfMatches > 1 && matches[1] != "[ Not Installed ]" { + installed = true + pkg.Version = strings.TrimSpace(matches[1]) + } + return installed, err +} + +// PackageAvailable tests if the given package is available for installation +func (e *Emerge) PackageAvailable(pkg *Package) (bool, error) { + if pkg.SystemPackage == false { + return false, nil + } + stdout, err := execCmd("emerge", "-s", pkg.Name+"$") + // We add a space to ensure we get a full match, not partial match + if err != nil { + _, ok := err.(*exec.ExitError) + if ok { + return false, nil + } + return false, err + } + + installedRegex := regexp.MustCompile(`.*\*\s+` + regexp.QuoteMeta(pkg.Name) + `\n(?:\S|\s)+?Latest version available: (.*)`) + matches := installedRegex.FindStringSubmatch(stdout) + pkg.Version = "" + noOfMatches := len(matches) + available := false + if noOfMatches > 1 { + available = true + pkg.Version = strings.TrimSpace(matches[1]) + } + return available, nil +} + +// InstallCommand returns the package manager specific command to install a package +func (e *Emerge) InstallCommand(pkg *Package) string { + if pkg.SystemPackage == false { + return pkg.InstallCommand[e.osid] + } + return "sudo emerge " + pkg.Name +} diff --git a/v3/internal/doctor/packagemanager/eopkg.go b/v3/internal/doctor/packagemanager/eopkg.go new file mode 100644 index 000000000..36f02b3a6 --- /dev/null +++ b/v3/internal/doctor/packagemanager/eopkg.go @@ -0,0 +1,109 @@ +//go:build linux + +package packagemanager + +import ( + "regexp" + "strings" +) + +// Eopkg represents the Eopkg manager +type Eopkg struct { + name string + osid string +} + +// NewEopkg creates a new Eopkg instance +func NewEopkg(osid string) *Eopkg { + result := &Eopkg{ + name: "eopkg", + osid: osid, + } + result.intialiseName() + return result +} + +// Packages returns the packages that we need for Wails to compile +// They will potentially differ on different distributions or versions +func (e *Eopkg) Packages() Packagemap { + return Packagemap{ + "libgtk-3": []*Package{ + {Name: "libgtk-3-devel", SystemPackage: true, Library: true}, + }, + "libwebkit": []*Package{ + {Name: "libwebkit-gtk-devel", SystemPackage: true, Library: true}, + }, + "gcc": []*Package{ + {Name: "gcc", SystemPackage: true}, + }, + "pkg-config": []*Package{ + {Name: "pkg-config", SystemPackage: true}, + }, + "npm": []*Package{ + {Name: "nodejs", SystemPackage: true}, + }, + } +} + +// Name returns the name of the package manager +func (e *Eopkg) Name() string { + return e.name +} + +// PackageInstalled tests if the given package is installed +func (e *Eopkg) PackageInstalled(pkg *Package) (bool, error) { + if pkg.SystemPackage == false { + return false, nil + } + stdout, err := execCmd("eopkg", "info", pkg.Name) + return strings.HasPrefix(stdout, "Installed"), err +} + +// PackageAvailable tests if the given package is available for installation +func (e *Eopkg) PackageAvailable(pkg *Package) (bool, error) { + if pkg.SystemPackage == false { + return false, nil + } + stdout, err := execCmd("eopkg", "info", pkg.Name) + // We add a space to ensure we get a full match, not partial match + output := e.removeEscapeSequences(stdout) + installed := strings.Contains(output, "Package found in Solus repository") + e.getPackageVersion(pkg, output) + return installed, err +} + +// InstallCommand returns the package manager specific command to install a package +func (e *Eopkg) InstallCommand(pkg *Package) string { + if pkg.SystemPackage == false { + return pkg.InstallCommand[e.osid] + } + return "sudo eopkg it " + pkg.Name +} + +func (e *Eopkg) removeEscapeSequences(in string) string { + escapechars, _ := regexp.Compile(`\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])`) + return escapechars.ReplaceAllString(in, "") +} + +func (e *Eopkg) intialiseName() { + result := "eopkg" + stdout, err := execCmd("eopkg", "--version") + if err == nil { + result = strings.TrimSpace(stdout) + } + e.name = result +} + +func (e *Eopkg) getPackageVersion(pkg *Package, output string) { + + versionRegex := regexp.MustCompile(`.*Name.*version:\s+(.*)+, release: (.*)`) + matches := versionRegex.FindStringSubmatch(output) + pkg.Version = "" + noOfMatches := len(matches) + if noOfMatches > 1 { + pkg.Version = matches[1] + if noOfMatches > 2 { + pkg.Version += " (r" + matches[2] + ")" + } + } +} diff --git a/v3/internal/doctor/packagemanager/nixpkgs.go b/v3/internal/doctor/packagemanager/nixpkgs.go new file mode 100644 index 000000000..ae0f66db5 --- /dev/null +++ b/v3/internal/doctor/packagemanager/nixpkgs.go @@ -0,0 +1,148 @@ +//go:build linux + +package packagemanager + +import ( + "encoding/json" +) + +// Nixpkgs represents the Nixpkgs manager +type Nixpkgs struct { + name string + osid string +} + +type NixPackageDetail struct { + Name string + Pname string + Version string +} + +var available map[string]NixPackageDetail + +// NewNixpkgs creates a new Nixpkgs instance +func NewNixpkgs(osid string) *Nixpkgs { + available = map[string]NixPackageDetail{} + + return &Nixpkgs{ + name: "nixpkgs", + osid: osid, + } +} + +// Packages returns the libraries that we need for Wails to compile +// They will potentially differ on different distributions or versions +func (n *Nixpkgs) Packages() Packagemap { + // Currently, only support checking the default channel. + channel := "nixpkgs" + if n.osid == "nixos" { + channel = "nixos" + } + + return Packagemap{ + "libgtk-3": []*Package{ + {Name: channel + ".gtk3", SystemPackage: true, Library: true}, + }, + "libwebkit": []*Package{ + {Name: channel + ".webkitgtk", SystemPackage: true, Library: true}, + }, + "gcc": []*Package{ + {Name: channel + ".gcc", SystemPackage: true}, + }, + "pkg-config": []*Package{ + {Name: channel + ".pkg-config", SystemPackage: true}, + }, + "npm": []*Package{ + {Name: channel + ".nodejs", SystemPackage: true}, + }, + } +} + +// Name returns the name of the package manager +func (n *Nixpkgs) Name() string { + return n.name +} + +// PackageInstalled tests if the given package name is installed +func (n *Nixpkgs) PackageInstalled(pkg *Package) (bool, error) { + if pkg.SystemPackage == false { + return false, nil + } + + stdout, err := execCmd("nix-env", "--json", "-qA", pkg.Name) + if err != nil { + return false, nil + } + + var attributes map[string]NixPackageDetail + err = json.Unmarshal([]byte(stdout), &attributes) + if err != nil { + return false, err + } + + // Did we get one? + installed := false + for attribute, detail := range attributes { + if attribute == pkg.Name { + installed = true + pkg.Version = detail.Version + } + break + } + + // If on NixOS, package may be installed via system config, so check the nix store. + detail, ok := available[pkg.Name] + if !installed && n.osid == "nixos" && ok { + cmd := "nix-store --query --requisites /run/current-system | cut -d- -f2- | sort | uniq | grep '^" + detail.Pname + "'" + + if pkg.Library { + cmd += " | grep 'dev$'" + } + + stdout, err = execCmd("sh", "-c", cmd) + if err != nil { + return false, nil + } + + if len(stdout) > 0 { + installed = true + } + } + + return installed, nil +} + +// PackageAvailable tests if the given package is available for installation +func (n *Nixpkgs) PackageAvailable(pkg *Package) (bool, error) { + if pkg.SystemPackage == false { + return false, nil + } + + stdout, err := execCmd("nix-env", "--json", "-qaA", pkg.Name) + if err != nil { + return false, nil + } + + var attributes map[string]NixPackageDetail + err = json.Unmarshal([]byte(stdout), &attributes) + if err != nil { + return false, err + } + + // Grab first version. + for attribute, detail := range attributes { + pkg.Version = detail.Version + available[attribute] = detail + break + } + + return len(pkg.Version) > 0, nil +} + +// InstallCommand returns the package manager specific command to install a package +func (n *Nixpkgs) InstallCommand(pkg *Package) string { + if pkg.SystemPackage == false { + return pkg.InstallCommand[n.osid] + } + return "nix-env -iA " + pkg.Name +} diff --git a/v3/internal/doctor/packagemanager/packagemanager.go b/v3/internal/doctor/packagemanager/packagemanager.go new file mode 100644 index 000000000..800b05f7a --- /dev/null +++ b/v3/internal/doctor/packagemanager/packagemanager.go @@ -0,0 +1,169 @@ +//go:build linux + +package packagemanager + +import ( + "bytes" + "os" + "os/exec" + "sort" + "strings" +) + +func execCmd(command string, args ...string) (string, error) { + cmd := exec.Command(command, args...) + var stdo, stde bytes.Buffer + cmd.Stdout = &stdo + cmd.Stderr = &stde + cmd.Env = append(os.Environ(), "LANGUAGE=en_US.utf-8") + err := cmd.Run() + return stdo.String(), err +} + +// A list of package manager commands +var pmcommands = []string{ + "eopkg", + "apt", + "dnf", + "pacman", + "emerge", + "zypper", + "nix-env", +} + +// commandExists returns true if the given command can be found on the shell +func commandExists(name string) bool { + _, err := exec.LookPath(name) + if err != nil { + return false + } + return true +} + +// Find will attempt to find the system package manager +func Find(osid string) PackageManager { + + // Loop over pmcommands + for _, pmname := range pmcommands { + if commandExists(pmname) { + return newPackageManager(pmname, osid) + } + } + return nil +} + +func newPackageManager(pmname string, osid string) PackageManager { + switch pmname { + case "eopkg": + return NewEopkg(osid) + case "apt": + return NewApt(osid) + case "dnf": + return NewDnf(osid) + case "pacman": + return NewPacman(osid) + case "emerge": + return NewEmerge(osid) + case "zypper": + return NewZypper(osid) + case "nix-env": + return NewNixpkgs(osid) + } + return nil +} + +// Dependencies scans the system for required dependencies +// Returns a list of dependencies search for, whether they were found +// and whether they were installed +func Dependencies(p PackageManager) (DependencyList, error) { + + var dependencies DependencyList + + for name, packages := range p.Packages() { + dependency := &Dependency{Name: name} + for _, pkg := range packages { + dependency.Optional = pkg.Optional + dependency.External = !pkg.SystemPackage + dependency.InstallCommand = p.InstallCommand(pkg) + packageavailable, err := p.PackageAvailable(pkg) + if err != nil { + return nil, err + } + if packageavailable { + dependency.Version = pkg.Version + dependency.PackageName = pkg.Name + installed, err := p.PackageInstalled(pkg) + if err != nil { + return nil, err + } + if installed { + dependency.Installed = true + dependency.Version = pkg.Version + if !pkg.SystemPackage { + dependency.Version = AppVersion(name) + } + } else { + dependency.InstallCommand = p.InstallCommand(pkg) + } + break + } + } + dependencies = append(dependencies, dependency) + } + + // Sort dependencies + sort.Slice(dependencies, func(i, j int) bool { + return dependencies[i].Name < dependencies[j].Name + }) + + return dependencies, nil +} + +// AppVersion returns the version for application related to the given package +func AppVersion(name string) string { + + if name == "gcc" { + return gccVersion() + } + + if name == "pkg-config" { + return pkgConfigVersion() + } + + if name == "npm" { + return npmVersion() + } + + return "" + +} + +func gccVersion() string { + + var version string + var err error + + // Try "-dumpfullversion" + version, err = execCmd("gcc", "-dumpfullversion") + if err != nil { + + // Try -dumpversion + // We ignore the error as this function is not for testing whether the + // application exists, only that we can get the version number + dumpversion, err := execCmd("gcc", "-dumpversion") + if err == nil { + version = dumpversion + } + } + return strings.TrimSpace(version) +} + +func pkgConfigVersion() string { + version, _ := execCmd("pkg-config", "--version") + return strings.TrimSpace(version) +} + +func npmVersion() string { + version, _ := execCmd("npm", "--version") + return strings.TrimSpace(version) +} diff --git a/v3/internal/doctor/packagemanager/pacman.go b/v3/internal/doctor/packagemanager/pacman.go new file mode 100644 index 000000000..d7eb1607c --- /dev/null +++ b/v3/internal/doctor/packagemanager/pacman.go @@ -0,0 +1,109 @@ +//go:build linux + +package packagemanager + +import ( + "os/exec" + "regexp" + "strings" +) + +// Pacman represents the Pacman package manager +type Pacman struct { + name string + osid string +} + +// NewPacman creates a new Pacman instance +func NewPacman(osid string) *Pacman { + return &Pacman{ + name: "pacman", + osid: osid, + } +} + +// Packages returns the libraries that we need for Wails to compile +// They will potentially differ on different distributions or versions +func (p *Pacman) Packages() Packagemap { + return Packagemap{ + "libgtk-3": []*Package{ + {Name: "gtk3", SystemPackage: true, Library: true}, + }, + "libwebkit": []*Package{ + {Name: "webkit2gtk", SystemPackage: true, Library: true}, + }, + "gcc": []*Package{ + {Name: "gcc", SystemPackage: true}, + }, + "pkg-config": []*Package{ + {Name: "pkgconf", SystemPackage: true}, + }, + "npm": []*Package{ + {Name: "npm", SystemPackage: true}, + }, + } +} + +// Name returns the name of the package manager +func (p *Pacman) Name() string { + return p.name +} + +// PackageInstalled tests if the given package name is installed +func (p *Pacman) PackageInstalled(pkg *Package) (bool, error) { + if pkg.SystemPackage == false { + return false, nil + } + stdout, err := execCmd("pacman", "-Q", pkg.Name) + if err != nil { + _, ok := err.(*exec.ExitError) + if ok { + return false, nil + } + return false, err + } + + splitoutput := strings.Split(stdout, "\n") + for _, line := range splitoutput { + if strings.HasPrefix(line, pkg.Name) { + splitline := strings.Split(line, " ") + pkg.Version = strings.TrimSpace(splitline[1]) + } + } + + return true, err +} + +// PackageAvailable tests if the given package is available for installation +func (p *Pacman) PackageAvailable(pkg *Package) (bool, error) { + if pkg.SystemPackage == false { + return false, nil + } + output, err := execCmd("pacman", "-Si", pkg.Name) + // We add a space to ensure we get a full match, not partial match + if err != nil { + _, ok := err.(*exec.ExitError) + if ok { + return false, nil + } + return false, err + } + + reg := regexp.MustCompile(`.*Version.*?:\s+(.*)`) + matches := reg.FindStringSubmatch(output) + pkg.Version = "" + noOfMatches := len(matches) + if noOfMatches > 1 { + pkg.Version = strings.TrimSpace(matches[1]) + } + + return true, nil +} + +// InstallCommand returns the package manager specific command to install a package +func (p *Pacman) InstallCommand(pkg *Package) string { + if pkg.SystemPackage == false { + return pkg.InstallCommand[p.osid] + } + return "sudo pacman -S " + pkg.Name +} diff --git a/v3/internal/doctor/packagemanager/pm.go b/v3/internal/doctor/packagemanager/pm.go new file mode 100644 index 000000000..0602e1e03 --- /dev/null +++ b/v3/internal/doctor/packagemanager/pm.go @@ -0,0 +1,64 @@ +//go:build linux + +package packagemanager + +// Package contains information about a system package +type Package struct { + Name string + Version string + InstallCommand map[string]string + SystemPackage bool + Library bool + Optional bool +} + +type Packagemap = map[string][]*Package + +// PackageManager is a common interface across all package managers +type PackageManager interface { + Name() string + Packages() Packagemap + PackageInstalled(*Package) (bool, error) + PackageAvailable(*Package) (bool, error) + InstallCommand(*Package) string +} + +// Dependency represents a system package that we require +type Dependency struct { + Name string + PackageName string + Installed bool + InstallCommand string + Version string + Optional bool + External bool +} + +// DependencyList is a list of Dependency instances +type DependencyList []*Dependency + +// InstallAllRequiredCommand returns the command you need to use to install all required dependencies +func (d DependencyList) InstallAllRequiredCommand() string { + + result := "" + for _, dependency := range d { + if !dependency.Installed && !dependency.Optional { + result += " - " + dependency.Name + ": " + dependency.InstallCommand + "\n" + } + } + + return result +} + +// InstallAllOptionalCommand returns the command you need to use to install all optional dependencies +func (d DependencyList) InstallAllOptionalCommand() string { + + result := "" + for _, dependency := range d { + if !dependency.Installed && dependency.Optional { + result += " - " + dependency.Name + ": " + dependency.InstallCommand + "\n" + } + } + + return result +} diff --git a/v3/internal/doctor/packagemanager/zypper.go b/v3/internal/doctor/packagemanager/zypper.go new file mode 100644 index 000000000..afd5fd26c --- /dev/null +++ b/v3/internal/doctor/packagemanager/zypper.go @@ -0,0 +1,118 @@ +//go:build linux +// +build linux + +package packagemanager + +import ( + "os/exec" + "regexp" + "strings" +) + +// Zypper represents the Zypper package manager +type Zypper struct { + name string + osid string +} + +// NewZypper creates a new Zypper instance +func NewZypper(osid string) *Zypper { + return &Zypper{ + name: "zypper", + osid: osid, + } +} + +// Packages returns the libraries that we need for Wails to compile +// They will potentially differ on different distributions or versions +func (z *Zypper) Packages() Packagemap { + return Packagemap{ + "libgtk-3": []*Package{ + {Name: "gtk3-devel", SystemPackage: true, Library: true}, + }, + "libwebkit": []*Package{ + {Name: "webkit2gtk3-soup2-devel", SystemPackage: true, Library: true}, + {Name: "webkit2gtk3-devel", SystemPackage: true, Library: true}, + }, + "gcc": []*Package{ + {Name: "gcc-c++", SystemPackage: true}, + }, + "pkg-config": []*Package{ + {Name: "pkg-config", SystemPackage: true}, + {Name: "pkgconf-pkg-config", SystemPackage: true}, + }, + "npm": []*Package{ + {Name: "npm10", SystemPackage: true}, + }, + } +} + +// Name returns the name of the package manager +func (z *Zypper) Name() string { + return z.name +} + +// PackageInstalled tests if the given package name is installed +func (z *Zypper) PackageInstalled(pkg *Package) (bool, error) { + if pkg.SystemPackage == false { + return false, nil + } + stdout, err := execCmd("zypper", "info", pkg.Name) + if err != nil { + _, ok := err.(*exec.ExitError) + if ok { + return false, nil + } + return false, err + } + reg := regexp.MustCompile(`.*Installed\s*:\s*(Yes)\s*`) + matches := reg.FindStringSubmatch(stdout) + pkg.Version = "" + noOfMatches := len(matches) + if noOfMatches > 1 { + z.getPackageVersion(pkg, stdout) + } + return noOfMatches > 1, err +} + +// PackageAvailable tests if the given package is available for installation +func (z *Zypper) PackageAvailable(pkg *Package) (bool, error) { + if pkg.SystemPackage == false { + return false, nil + } + stdout, err := execCmd("zypper", "info", pkg.Name) + // We add a space to ensure we get a full match, not partial match + if err != nil { + _, ok := err.(*exec.ExitError) + if ok { + return false, nil + } + return false, err + } + + available := strings.Contains(stdout, "Information for package") + if available { + z.getPackageVersion(pkg, stdout) + } + + return available, nil +} + +// InstallCommand returns the package manager specific command to install a package +func (z *Zypper) InstallCommand(pkg *Package) string { + if pkg.SystemPackage == false { + return pkg.InstallCommand[z.osid] + } + return "sudo zypper in " + pkg.Name +} + +func (z *Zypper) getPackageVersion(pkg *Package, output string) { + + reg := regexp.MustCompile(`.*Version.*:(.*)`) + matches := reg.FindStringSubmatch(output) + pkg.Version = "" + noOfMatches := len(matches) + if noOfMatches > 1 { + pkg.Version = strings.TrimSpace(matches[1]) + } +} diff --git a/v3/internal/flags/build.go b/v3/internal/flags/build.go new file mode 100644 index 000000000..260c6067d --- /dev/null +++ b/v3/internal/flags/build.go @@ -0,0 +1,5 @@ +package flags + +type Build struct { + Common +} diff --git a/v3/internal/flags/common.go b/v3/internal/flags/common.go new file mode 100644 index 000000000..e58eff411 --- /dev/null +++ b/v3/internal/flags/common.go @@ -0,0 +1,5 @@ +package flags + +type Common struct { + NoColour bool `description:"Disable colour in output"` +} diff --git a/v3/internal/flags/init.go b/v3/internal/flags/init.go new file mode 100644 index 000000000..182d98184 --- /dev/null +++ b/v3/internal/flags/init.go @@ -0,0 +1,12 @@ +package flags + +type Init struct { + Common + + PackageName string `name:"p" description:"Package name" default:"main"` + TemplateName string `name:"t" description:"Name of built-in template to use, path to template or template url" default:"vanilla"` + ProjectName string `name:"n" description:"Name of project" default:""` + ProjectDir string `name:"d" description:"Project directory" default:"."` + Quiet bool `name:"q" description:"Suppress output to console"` + List bool `name:"l" description:"List templates"` +} diff --git a/v3/internal/flags/plugin.go b/v3/internal/flags/plugin.go new file mode 100644 index 000000000..62c0dd968 --- /dev/null +++ b/v3/internal/flags/plugin.go @@ -0,0 +1,9 @@ +package flags + +type PluginInit struct { + Name string `name:"n" description:"Name of plugin" default:"example_plugin"` + Description string `name:"d" description:"Description of plugin" default:"Example plugin"` + PackageName string `name:"p" description:"Package name for plugin" default:""` + OutputDir string `name:"o" description:"Output directory" default:"."` + Quiet bool `name:"q" description:"Suppress output to console"` +} diff --git a/v3/internal/go-common-file-dialog/LICENSE b/v3/internal/go-common-file-dialog/LICENSE new file mode 100644 index 000000000..508b6978e --- /dev/null +++ b/v3/internal/go-common-file-dialog/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019 Harry Phillips + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/v3/internal/go-common-file-dialog/README.md b/v3/internal/go-common-file-dialog/README.md new file mode 100644 index 000000000..1cb5902d1 --- /dev/null +++ b/v3/internal/go-common-file-dialog/README.md @@ -0,0 +1,31 @@ +# Common File Dialog bindings for Golang + +[Project Home](https://github.com/harry1453/go-common-file-dialog) + +This library contains bindings for Windows Vista and +newer's [Common File Dialogs](https://docs.microsoft.com/en-us/windows/win32/shell/common-file-dialog), which is the +standard system dialog for selecting files or folders to open or save. + +The Common File Dialogs have to be accessed via +the [COM Interface](https://en.wikipedia.org/wiki/Component_Object_Model), normally via C++ or via bindings (like in C#) +. + +This library contains bindings for Golang. **It does not require CGO**, and contains empty stubs for non-windows +platforms (so is safe to compile and run on platforms other than windows, but will just return errors at runtime). + +This can be very useful if you want to quickly get a file selector in your Golang application. The `cfdutil` package +contains utility functions with a single call to open and configure a dialog, and then get the result from it. Examples +for this are in [`_examples/usingutil`](_examples/usingutil). Or, if you want finer control over the dialog's operation, +you can use the base package. Examples for this are in [`_examples/notusingutil`](_examples/notusingutil). + +This library is available under the MIT license. + +Currently supported features: + +* Open File Dialog (to open a single file) +* Open Multiple Files Dialog (to open multiple files) +* Open Folder Dialog +* Save File Dialog +* Dialog "roles" to allow Windows to remember different "last locations" for different types of dialog +* Set dialog Title, Default Folder and Initial Folder +* Set dialog File Filters diff --git a/v3/internal/go-common-file-dialog/cfd/CommonFileDialog.go b/v3/internal/go-common-file-dialog/cfd/CommonFileDialog.go new file mode 100644 index 000000000..58e97aa4e --- /dev/null +++ b/v3/internal/go-common-file-dialog/cfd/CommonFileDialog.go @@ -0,0 +1,72 @@ +// Cross-platform. + +// Common File Dialogs +package cfd + +type Dialog interface { + // Show the dialog to the user. + // Blocks until the user has closed the dialog. + Show() error + // Sets the dialog's parent window. Use 0 to set the dialog to have no parent window. + SetParentWindowHandle(hwnd uintptr) + // Show the dialog to the user. + // Blocks until the user has closed the dialog and returns their selection. + // Returns an error if the user cancelled the dialog. + // Do not use for the Open Multiple Files dialog. Use ShowAndGetResults instead. + ShowAndGetResult() (string, error) + // Sets the title of the dialog window. + SetTitle(title string) error + // Sets the "role" of the dialog. This is used to derive the dialog's GUID, which the + // OS will use to differentiate it from dialogs that are intended for other purposes. + // This means that, for example, a dialog with role "Import" will have a different + // previous location that it will open to than a dialog with role "Open". Can be any string. + SetRole(role string) error + // Sets the folder used as a default if there is not a recently used folder value available + SetDefaultFolder(defaultFolder string) error + // Sets the folder that the dialog always opens to. + // If this is set, it will override the "default folder" behaviour and the dialog will always open to this folder. + SetFolder(folder string) error + // Gets the selected file or folder path, as an absolute path eg. "C:\Folder\file.txt" + // Do not use for the Open Multiple Files dialog. Use GetResults instead. + GetResult() (string, error) + // Sets the file name, I.E. the contents of the file name text box. + // For Select Folder Dialog, sets folder name. + SetFileName(fileName string) error + // Release the resources allocated to this Dialog. + // Should be called when the dialog is finished with. + Release() error +} + +type FileDialog interface { + Dialog + // Set the list of file filters that the user can select. + SetFileFilters(fileFilter []FileFilter) error + // Set the selected item from the list of file filters (set using SetFileFilters) by its index. Defaults to 0 (the first item in the list) if not called. + SetSelectedFileFilterIndex(index uint) error + // Sets the default extension applied when a user does not provide one as part of the file name. + // If the user selects a different file filter, the default extension will be automatically updated to match the new file filter. + // For Open / Open Multiple File Dialog, this only has an effect when the user specifies a file name with no extension and a file with the default extension exists. + // For Save File Dialog, this extension will be used whenever a user does not specify an extension. + SetDefaultExtension(defaultExtension string) error +} + +type OpenFileDialog interface { + FileDialog +} + +type OpenMultipleFilesDialog interface { + FileDialog + // Show the dialog to the user. + // Blocks until the user has closed the dialog and returns the selected files. + ShowAndGetResults() ([]string, error) + // Gets the selected file paths, as absolute paths eg. "C:\Folder\file.txt" + GetResults() ([]string, error) +} + +type SelectFolderDialog interface { + Dialog +} + +type SaveFileDialog interface { // TODO Properties + FileDialog +} diff --git a/v3/internal/go-common-file-dialog/cfd/CommonFileDialog_nonWindows.go b/v3/internal/go-common-file-dialog/cfd/CommonFileDialog_nonWindows.go new file mode 100644 index 000000000..3ab969850 --- /dev/null +++ b/v3/internal/go-common-file-dialog/cfd/CommonFileDialog_nonWindows.go @@ -0,0 +1,28 @@ +//go:build !windows +// +build !windows + +package cfd + +import "fmt" + +var unsupportedError = fmt.Errorf("common file dialogs are only available on windows") + +// TODO doc +func NewOpenFileDialog(config DialogConfig) (OpenFileDialog, error) { + return nil, unsupportedError +} + +// TODO doc +func NewOpenMultipleFilesDialog(config DialogConfig) (OpenMultipleFilesDialog, error) { + return nil, unsupportedError +} + +// TODO doc +func NewSelectFolderDialog(config DialogConfig) (SelectFolderDialog, error) { + return nil, unsupportedError +} + +// TODO doc +func NewSaveFileDialog(config DialogConfig) (SaveFileDialog, error) { + return nil, unsupportedError +} diff --git a/v3/internal/go-common-file-dialog/cfd/CommonFileDialog_windows.go b/v3/internal/go-common-file-dialog/cfd/CommonFileDialog_windows.go new file mode 100644 index 000000000..69f46118e --- /dev/null +++ b/v3/internal/go-common-file-dialog/cfd/CommonFileDialog_windows.go @@ -0,0 +1,79 @@ +//go:build windows +// +build windows + +package cfd + +import "github.com/go-ole/go-ole" + +func initialize() { + // Swallow error + _ = ole.CoInitializeEx(0, ole.COINIT_APARTMENTTHREADED|ole.COINIT_DISABLE_OLE1DDE) +} + +// TODO doc +func NewOpenFileDialog(config DialogConfig) (OpenFileDialog, error) { + initialize() + + openDialog, err := newIFileOpenDialog() + if err != nil { + return nil, err + } + err = config.apply(openDialog) + if err != nil { + return nil, err + } + return openDialog, nil +} + +// TODO doc +func NewOpenMultipleFilesDialog(config DialogConfig) (OpenMultipleFilesDialog, error) { + initialize() + + openDialog, err := newIFileOpenDialog() + if err != nil { + return nil, err + } + err = config.apply(openDialog) + if err != nil { + return nil, err + } + err = openDialog.setIsMultiselect(true) + if err != nil { + return nil, err + } + return openDialog, nil +} + +// TODO doc +func NewSelectFolderDialog(config DialogConfig) (SelectFolderDialog, error) { + initialize() + + openDialog, err := newIFileOpenDialog() + if err != nil { + return nil, err + } + err = config.apply(openDialog) + if err != nil { + return nil, err + } + err = openDialog.setPickFolders(true) + if err != nil { + return nil, err + } + return openDialog, nil +} + +// TODO doc +func NewSaveFileDialog(config DialogConfig) (SaveFileDialog, error) { + initialize() + + saveDialog, err := newIFileSaveDialog() + if err != nil { + return nil, err + } + err = config.apply(saveDialog) + if err != nil { + return nil, err + } + return saveDialog, nil +} diff --git a/v3/internal/go-common-file-dialog/cfd/DialogConfig.go b/v3/internal/go-common-file-dialog/cfd/DialogConfig.go new file mode 100644 index 000000000..221dbef27 --- /dev/null +++ b/v3/internal/go-common-file-dialog/cfd/DialogConfig.go @@ -0,0 +1,120 @@ +// Cross-platform. + +package cfd + +type FileFilter struct { + // The display name of the filter (That is shown to the user) + DisplayName string + // The filter pattern. Eg. "*.txt;*.png" to select all txt and png files, "*.*" to select any files, etc. + Pattern string +} + +type DialogConfig struct { + // The title of the dialog + Title string + // The role of the dialog. This is used to derive the dialog's GUID, which the + // OS will use to differentiate it from dialogs that are intended for other purposes. + // This means that, for example, a dialog with role "Import" will have a different + // previous location that it will open to than a dialog with role "Open". Can be any string. + Role string + // The default folder - the folder that is used the first time the user opens it + // (after the first time their last used location is used). + DefaultFolder string + // The initial folder - the folder that the dialog always opens to if not empty. + // If this is not empty, it will override the "default folder" behaviour and + // the dialog will always open to this folder. + Folder string + // The file filters that restrict which types of files the dialog is able to choose. + // Ignored by Select Folder Dialog. + FileFilters []FileFilter + // Sets the initially selected file filter. This is an index of FileFilters. + // Ignored by Select Folder Dialog. + SelectedFileFilterIndex uint + // The initial name of the file (I.E. the text in the file name text box) when the user opens the dialog. + // For the Select Folder Dialog, this sets the initial folder name. + FileName string + // The default extension applied when a user does not provide one as part of the file name. + // If the user selects a different file filter, the default extension will be automatically updated to match the new file filter. + // For Open / Open Multiple File Dialog, this only has an effect when the user specifies a file name with no extension and a file with the default extension exists. + // For Save File Dialog, this extension will be used whenever a user does not specify an extension. + // Ignored by Select Folder Dialog. + DefaultExtension string + // ParentWindowHandle is the handle (HWND) to the parent window of the dialog. + // If left as 0 / nil, the dialog will have no parent window. + ParentWindowHandle uintptr +} + +var defaultFilters = []FileFilter{ + { + DisplayName: "All Files (*.*)", + Pattern: "*.*", + }, +} + +func (config *DialogConfig) apply(dialog Dialog) (err error) { + if config.Title != "" { + err = dialog.SetTitle(config.Title) + if err != nil { + return + } + } + + if config.Role != "" { + err = dialog.SetRole(config.Role) + if err != nil { + return + } + } + + if config.Folder != "" { + err = dialog.SetFolder(config.Folder) + if err != nil { + return + } + } + + if config.DefaultFolder != "" { + err = dialog.SetDefaultFolder(config.DefaultFolder) + if err != nil { + return + } + } + + if config.FileName != "" { + err = dialog.SetFileName(config.FileName) + if err != nil { + return + } + } + + dialog.SetParentWindowHandle(config.ParentWindowHandle) + + if dialog, ok := dialog.(FileDialog); ok { + var fileFilters []FileFilter + if config.FileFilters != nil && len(config.FileFilters) > 0 { + fileFilters = config.FileFilters + } else { + fileFilters = defaultFilters + } + err = dialog.SetFileFilters(fileFilters) + if err != nil { + return + } + + if config.SelectedFileFilterIndex != 0 { + err = dialog.SetSelectedFileFilterIndex(config.SelectedFileFilterIndex) + if err != nil { + return + } + } + + if config.DefaultExtension != "" { + err = dialog.SetDefaultExtension(config.DefaultExtension) + if err != nil { + return + } + } + } + + return +} diff --git a/v3/internal/go-common-file-dialog/cfd/errors.go b/v3/internal/go-common-file-dialog/cfd/errors.go new file mode 100644 index 000000000..c097c8eb2 --- /dev/null +++ b/v3/internal/go-common-file-dialog/cfd/errors.go @@ -0,0 +1,7 @@ +package cfd + +import "errors" + +var ( + ErrorCancelled = errors.New("cancelled by user") +) diff --git a/v3/internal/go-common-file-dialog/cfd/iFileOpenDialog.go b/v3/internal/go-common-file-dialog/cfd/iFileOpenDialog.go new file mode 100644 index 000000000..42f83814a --- /dev/null +++ b/v3/internal/go-common-file-dialog/cfd/iFileOpenDialog.go @@ -0,0 +1,201 @@ +//go:build windows +// +build windows + +package cfd + +import ( + "github.com/go-ole/go-ole" + "github.com/google/uuid" + "syscall" + "unsafe" +) + +var ( + fileOpenDialogCLSID = ole.NewGUID("{DC1C5A9C-E88A-4dde-A5A1-60F82A20AEF7}") + fileOpenDialogIID = ole.NewGUID("{d57c7288-d4ad-4768-be02-9d969532d960}") +) + +type iFileOpenDialog struct { + vtbl *iFileOpenDialogVtbl + parentWindowHandle uintptr +} + +type iFileOpenDialogVtbl struct { + iFileDialogVtbl + + GetResults uintptr // func (ppenum **IShellItemArray) HRESULT + GetSelectedItems uintptr +} + +func newIFileOpenDialog() (*iFileOpenDialog, error) { + if unknown, err := ole.CreateInstance(fileOpenDialogCLSID, fileOpenDialogIID); err == nil { + return (*iFileOpenDialog)(unsafe.Pointer(unknown)), nil + } else { + return nil, err + } +} + +func (fileOpenDialog *iFileOpenDialog) Show() error { + return fileOpenDialog.vtbl.show(unsafe.Pointer(fileOpenDialog), fileOpenDialog.parentWindowHandle) +} + +func (fileOpenDialog *iFileOpenDialog) SetParentWindowHandle(hwnd uintptr) { + fileOpenDialog.parentWindowHandle = hwnd +} + +func (fileOpenDialog *iFileOpenDialog) ShowAndGetResult() (string, error) { + isMultiselect, err := fileOpenDialog.isMultiselect() + if err != nil { + return "", err + } + if isMultiselect { + // We should panic as this error is caused by the developer using the library + panic("use ShowAndGetResults for open multiple files dialog") + } + if err := fileOpenDialog.Show(); err != nil { + return "", err + } + return fileOpenDialog.GetResult() +} + +func (fileOpenDialog *iFileOpenDialog) ShowAndGetResults() ([]string, error) { + isMultiselect, err := fileOpenDialog.isMultiselect() + if err != nil { + return nil, err + } + if !isMultiselect { + // We should panic as this error is caused by the developer using the library + panic("use ShowAndGetResult for open single file dialog") + } + if err := fileOpenDialog.Show(); err != nil { + return nil, err + } + return fileOpenDialog.GetResults() +} + +func (fileOpenDialog *iFileOpenDialog) SetTitle(title string) error { + return fileOpenDialog.vtbl.setTitle(unsafe.Pointer(fileOpenDialog), title) +} + +func (fileOpenDialog *iFileOpenDialog) GetResult() (string, error) { + isMultiselect, err := fileOpenDialog.isMultiselect() + if err != nil { + return "", err + } + if isMultiselect { + // We should panic as this error is caused by the developer using the library + panic("use GetResults for open multiple files dialog") + } + return fileOpenDialog.vtbl.getResultString(unsafe.Pointer(fileOpenDialog)) +} + +func (fileOpenDialog *iFileOpenDialog) Release() error { + return fileOpenDialog.vtbl.release(unsafe.Pointer(fileOpenDialog)) +} + +func (fileOpenDialog *iFileOpenDialog) SetDefaultFolder(defaultFolderPath string) error { + return fileOpenDialog.vtbl.setDefaultFolder(unsafe.Pointer(fileOpenDialog), defaultFolderPath) +} + +func (fileOpenDialog *iFileOpenDialog) SetFolder(defaultFolderPath string) error { + return fileOpenDialog.vtbl.setFolder(unsafe.Pointer(fileOpenDialog), defaultFolderPath) +} + +func (fileOpenDialog *iFileOpenDialog) SetFileFilters(filter []FileFilter) error { + return fileOpenDialog.vtbl.setFileTypes(unsafe.Pointer(fileOpenDialog), filter) +} + +func (fileOpenDialog *iFileOpenDialog) SetRole(role string) error { + return fileOpenDialog.vtbl.setClientGuid(unsafe.Pointer(fileOpenDialog), StringToUUID(role)) +} + +// This should only be callable when the user asks for a multi select because +// otherwise they will be given the Dialog interface which does not expose this function. +func (fileOpenDialog *iFileOpenDialog) GetResults() ([]string, error) { + isMultiselect, err := fileOpenDialog.isMultiselect() + if err != nil { + return nil, err + } + if !isMultiselect { + // We should panic as this error is caused by the developer using the library + panic("use GetResult for open single file dialog") + } + return fileOpenDialog.vtbl.getResultsStrings(unsafe.Pointer(fileOpenDialog)) +} + +func (fileOpenDialog *iFileOpenDialog) SetDefaultExtension(defaultExtension string) error { + return fileOpenDialog.vtbl.setDefaultExtension(unsafe.Pointer(fileOpenDialog), defaultExtension) +} + +func (fileOpenDialog *iFileOpenDialog) SetFileName(initialFileName string) error { + return fileOpenDialog.vtbl.setFileName(unsafe.Pointer(fileOpenDialog), initialFileName) +} + +func (fileOpenDialog *iFileOpenDialog) SetSelectedFileFilterIndex(index uint) error { + return fileOpenDialog.vtbl.setSelectedFileFilterIndex(unsafe.Pointer(fileOpenDialog), index) +} + +func (fileOpenDialog *iFileOpenDialog) setPickFolders(pickFolders bool) error { + const FosPickfolders = 0x20 + if pickFolders { + return fileOpenDialog.vtbl.addOption(unsafe.Pointer(fileOpenDialog), FosPickfolders) + } else { + return fileOpenDialog.vtbl.removeOption(unsafe.Pointer(fileOpenDialog), FosPickfolders) + } +} + +const FosAllowMultiselect = 0x200 + +func (fileOpenDialog *iFileOpenDialog) isMultiselect() (bool, error) { + options, err := fileOpenDialog.vtbl.getOptions(unsafe.Pointer(fileOpenDialog)) + if err != nil { + return false, err + } + return options&FosAllowMultiselect != 0, nil +} + +func (fileOpenDialog *iFileOpenDialog) setIsMultiselect(isMultiselect bool) error { + if isMultiselect { + return fileOpenDialog.vtbl.addOption(unsafe.Pointer(fileOpenDialog), FosAllowMultiselect) + } else { + return fileOpenDialog.vtbl.removeOption(unsafe.Pointer(fileOpenDialog), FosAllowMultiselect) + } +} + +func (vtbl *iFileOpenDialogVtbl) getResults(objPtr unsafe.Pointer) (*iShellItemArray, error) { + var shellItemArray *iShellItemArray + ret, _, _ := syscall.Syscall(vtbl.GetResults, + 1, + uintptr(objPtr), + uintptr(unsafe.Pointer(&shellItemArray)), + 0) + return shellItemArray, hresultToError(ret) +} + +func (vtbl *iFileOpenDialogVtbl) getResultsStrings(objPtr unsafe.Pointer) ([]string, error) { + shellItemArray, err := vtbl.getResults(objPtr) + if err != nil { + return nil, err + } + if shellItemArray == nil { + return nil, ErrorCancelled + } + defer shellItemArray.vtbl.release(unsafe.Pointer(shellItemArray)) + count, err := shellItemArray.vtbl.getCount(unsafe.Pointer(shellItemArray)) + if err != nil { + return nil, err + } + var results []string + for i := uintptr(0); i < count; i++ { + newItem, err := shellItemArray.vtbl.getItemAt(unsafe.Pointer(shellItemArray), i) + if err != nil { + return nil, err + } + results = append(results, newItem) + } + return results, nil +} + +func StringToUUID(str string) *ole.GUID { + return ole.NewGUID(uuid.NewSHA1(uuid.Nil, []byte(str)).String()) +} diff --git a/v3/internal/go-common-file-dialog/cfd/iFileSaveDialog.go b/v3/internal/go-common-file-dialog/cfd/iFileSaveDialog.go new file mode 100644 index 000000000..ddee7b246 --- /dev/null +++ b/v3/internal/go-common-file-dialog/cfd/iFileSaveDialog.go @@ -0,0 +1,92 @@ +//go:build windows +// +build windows + +package cfd + +import ( + "github.com/go-ole/go-ole" + "unsafe" +) + +var ( + saveFileDialogCLSID = ole.NewGUID("{C0B4E2F3-BA21-4773-8DBA-335EC946EB8B}") + saveFileDialogIID = ole.NewGUID("{84bccd23-5fde-4cdb-aea4-af64b83d78ab}") +) + +type iFileSaveDialog struct { + vtbl *iFileSaveDialogVtbl + parentWindowHandle uintptr +} + +type iFileSaveDialogVtbl struct { + iFileDialogVtbl + + SetSaveAsItem uintptr + SetProperties uintptr + SetCollectedProperties uintptr + GetProperties uintptr + ApplyProperties uintptr +} + +func newIFileSaveDialog() (*iFileSaveDialog, error) { + if unknown, err := ole.CreateInstance(saveFileDialogCLSID, saveFileDialogIID); err == nil { + return (*iFileSaveDialog)(unsafe.Pointer(unknown)), nil + } else { + return nil, err + } +} + +func (fileSaveDialog *iFileSaveDialog) Show() error { + return fileSaveDialog.vtbl.show(unsafe.Pointer(fileSaveDialog), fileSaveDialog.parentWindowHandle) +} + +func (fileSaveDialog *iFileSaveDialog) SetParentWindowHandle(hwnd uintptr) { + fileSaveDialog.parentWindowHandle = hwnd +} + +func (fileSaveDialog *iFileSaveDialog) ShowAndGetResult() (string, error) { + if err := fileSaveDialog.Show(); err != nil { + return "", err + } + return fileSaveDialog.GetResult() +} + +func (fileSaveDialog *iFileSaveDialog) SetTitle(title string) error { + return fileSaveDialog.vtbl.setTitle(unsafe.Pointer(fileSaveDialog), title) +} + +func (fileSaveDialog *iFileSaveDialog) GetResult() (string, error) { + return fileSaveDialog.vtbl.getResultString(unsafe.Pointer(fileSaveDialog)) +} + +func (fileSaveDialog *iFileSaveDialog) Release() error { + return fileSaveDialog.vtbl.release(unsafe.Pointer(fileSaveDialog)) +} + +func (fileSaveDialog *iFileSaveDialog) SetDefaultFolder(defaultFolderPath string) error { + return fileSaveDialog.vtbl.setDefaultFolder(unsafe.Pointer(fileSaveDialog), defaultFolderPath) +} + +func (fileSaveDialog *iFileSaveDialog) SetFolder(defaultFolderPath string) error { + return fileSaveDialog.vtbl.setFolder(unsafe.Pointer(fileSaveDialog), defaultFolderPath) +} + +func (fileSaveDialog *iFileSaveDialog) SetFileFilters(filter []FileFilter) error { + return fileSaveDialog.vtbl.setFileTypes(unsafe.Pointer(fileSaveDialog), filter) +} + +func (fileSaveDialog *iFileSaveDialog) SetRole(role string) error { + return fileSaveDialog.vtbl.setClientGuid(unsafe.Pointer(fileSaveDialog), StringToUUID(role)) +} + +func (fileSaveDialog *iFileSaveDialog) SetDefaultExtension(defaultExtension string) error { + return fileSaveDialog.vtbl.setDefaultExtension(unsafe.Pointer(fileSaveDialog), defaultExtension) +} + +func (fileSaveDialog *iFileSaveDialog) SetFileName(initialFileName string) error { + return fileSaveDialog.vtbl.setFileName(unsafe.Pointer(fileSaveDialog), initialFileName) +} + +func (fileSaveDialog *iFileSaveDialog) SetSelectedFileFilterIndex(index uint) error { + return fileSaveDialog.vtbl.setSelectedFileFilterIndex(unsafe.Pointer(fileSaveDialog), index) +} diff --git a/v3/internal/go-common-file-dialog/cfd/iShellItem.go b/v3/internal/go-common-file-dialog/cfd/iShellItem.go new file mode 100644 index 000000000..6a747f4d9 --- /dev/null +++ b/v3/internal/go-common-file-dialog/cfd/iShellItem.go @@ -0,0 +1,53 @@ +//go:build windows +// +build windows + +package cfd + +import ( + "github.com/go-ole/go-ole" + "syscall" + "unsafe" +) + +var ( + procSHCreateItemFromParsingName = syscall.NewLazyDLL("Shell32.dll").NewProc("SHCreateItemFromParsingName") + iidShellItem = ole.NewGUID("43826d1e-e718-42ee-bc55-a1e261c37bfe") +) + +type iShellItem struct { + vtbl *iShellItemVtbl +} + +type iShellItemVtbl struct { + iUnknownVtbl + BindToHandler uintptr + GetParent uintptr + GetDisplayName uintptr // func (sigdnName SIGDN, ppszName *LPWSTR) HRESULT + GetAttributes uintptr + Compare uintptr +} + +func newIShellItem(path string) (*iShellItem, error) { + var shellItem *iShellItem + pathPtr := ole.SysAllocString(path) + ret, _, _ := procSHCreateItemFromParsingName.Call( + uintptr(unsafe.Pointer(pathPtr)), + 0, + uintptr(unsafe.Pointer(iidShellItem)), + uintptr(unsafe.Pointer(&shellItem))) + return shellItem, hresultToError(ret) +} + +func (vtbl *iShellItemVtbl) getDisplayName(objPtr unsafe.Pointer) (string, error) { + var ptr *uint16 + ret, _, _ := syscall.Syscall(vtbl.GetDisplayName, + 2, + uintptr(objPtr), + 0x80058000, // SIGDN_FILESYSPATH + uintptr(unsafe.Pointer(&ptr))) + if err := hresultToError(ret); err != nil { + return "", err + } + defer ole.CoTaskMemFree(uintptr(unsafe.Pointer(ptr))) + return ole.LpOleStrToString(ptr), nil +} diff --git a/v3/internal/go-common-file-dialog/cfd/iShellItemArray.go b/v3/internal/go-common-file-dialog/cfd/iShellItemArray.go new file mode 100644 index 000000000..84f26fa20 --- /dev/null +++ b/v3/internal/go-common-file-dialog/cfd/iShellItemArray.go @@ -0,0 +1,67 @@ +//go:build windows +// +build windows + +package cfd + +import ( + "github.com/go-ole/go-ole" + "syscall" + "unsafe" +) + +const ( + iidShellItemArrayGUID = "{b63ea76d-1f85-456f-a19c-48159efa858b}" +) + +var ( + iidShellItemArray *ole.GUID +) + +func init() { + iidShellItemArray, _ = ole.IIDFromString(iidShellItemArrayGUID) +} + +type iShellItemArray struct { + vtbl *iShellItemArrayVtbl +} + +type iShellItemArrayVtbl struct { + iUnknownVtbl + BindToHandler uintptr + GetPropertyStore uintptr + GetPropertyDescriptionList uintptr + GetAttributes uintptr + GetCount uintptr // func (pdwNumItems *DWORD) HRESULT + GetItemAt uintptr // func (dwIndex DWORD, ppsi **IShellItem) HRESULT + EnumItems uintptr +} + +func (vtbl *iShellItemArrayVtbl) getCount(objPtr unsafe.Pointer) (uintptr, error) { + var count uintptr + ret, _, _ := syscall.Syscall(vtbl.GetCount, + 1, + uintptr(objPtr), + uintptr(unsafe.Pointer(&count)), + 0) + if err := hresultToError(ret); err != nil { + return 0, err + } + return count, nil +} + +func (vtbl *iShellItemArrayVtbl) getItemAt(objPtr unsafe.Pointer, index uintptr) (string, error) { + var shellItem *iShellItem + ret, _, _ := syscall.Syscall(vtbl.GetItemAt, + 2, + uintptr(objPtr), + index, + uintptr(unsafe.Pointer(&shellItem))) + if err := hresultToError(ret); err != nil { + return "", err + } + if shellItem == nil { + return "", ErrorCancelled + } + defer shellItem.vtbl.release(unsafe.Pointer(shellItem)) + return shellItem.vtbl.getDisplayName(unsafe.Pointer(shellItem)) +} diff --git a/v3/internal/go-common-file-dialog/cfd/vtblCommon.go b/v3/internal/go-common-file-dialog/cfd/vtblCommon.go new file mode 100644 index 000000000..21015c27c --- /dev/null +++ b/v3/internal/go-common-file-dialog/cfd/vtblCommon.go @@ -0,0 +1,48 @@ +//go:build windows +// +build windows + +package cfd + +type comDlgFilterSpec struct { + pszName *int16 + pszSpec *int16 +} + +type iUnknownVtbl struct { + QueryInterface uintptr + AddRef uintptr + Release uintptr +} + +type iModalWindowVtbl struct { + iUnknownVtbl + Show uintptr // func (hwndOwner HWND) HRESULT +} + +type iFileDialogVtbl struct { + iModalWindowVtbl + SetFileTypes uintptr // func (cFileTypes UINT, rgFilterSpec *COMDLG_FILTERSPEC) HRESULT + SetFileTypeIndex uintptr // func(iFileType UINT) HRESULT + GetFileTypeIndex uintptr + Advise uintptr + Unadvise uintptr + SetOptions uintptr // func (fos FILEOPENDIALOGOPTIONS) HRESULT + GetOptions uintptr // func (pfos *FILEOPENDIALOGOPTIONS) HRESULT + SetDefaultFolder uintptr // func (psi *IShellItem) HRESULT + SetFolder uintptr // func (psi *IShellItem) HRESULT + GetFolder uintptr + GetCurrentSelection uintptr + SetFileName uintptr // func (pszName LPCWSTR) HRESULT + GetFileName uintptr + SetTitle uintptr // func(pszTitle LPCWSTR) HRESULT + SetOkButtonLabel uintptr + SetFileNameLabel uintptr + GetResult uintptr // func (ppsi **IShellItem) HRESULT + AddPlace uintptr + SetDefaultExtension uintptr // func (pszDefaultExtension LPCWSTR) HRESULT + // This can only be used from a callback. + Close uintptr + SetClientGuid uintptr // func (guid REFGUID) HRESULT + ClearClientData uintptr + SetFilter uintptr +} diff --git a/v3/internal/go-common-file-dialog/cfd/vtblCommonFunc.go b/v3/internal/go-common-file-dialog/cfd/vtblCommonFunc.go new file mode 100644 index 000000000..a92100010 --- /dev/null +++ b/v3/internal/go-common-file-dialog/cfd/vtblCommonFunc.go @@ -0,0 +1,227 @@ +//go:build windows +// +build windows + +package cfd + +import ( + "fmt" + "github.com/go-ole/go-ole" + "strings" + "syscall" + "unsafe" +) + +func hresultToError(hr uintptr) error { + if hr < 0 { + return ole.NewError(hr) + } + return nil +} + +func (vtbl *iUnknownVtbl) release(objPtr unsafe.Pointer) error { + ret, _, _ := syscall.Syscall(vtbl.Release, + 0, + uintptr(objPtr), + 0, + 0) + return hresultToError(ret) +} + +func (vtbl *iModalWindowVtbl) show(objPtr unsafe.Pointer, hwnd uintptr) error { + ret, _, _ := syscall.Syscall(vtbl.Show, + 1, + uintptr(objPtr), + hwnd, + 0) + return hresultToError(ret) +} + +func (vtbl *iFileDialogVtbl) setFileTypes(objPtr unsafe.Pointer, filters []FileFilter) error { + cFileTypes := len(filters) + if cFileTypes < 0 { + return fmt.Errorf("must specify at least one filter") + } + comDlgFilterSpecs := make([]comDlgFilterSpec, cFileTypes) + for i := 0; i < cFileTypes; i++ { + filter := &filters[i] + comDlgFilterSpecs[i] = comDlgFilterSpec{ + pszName: ole.SysAllocString(filter.DisplayName), + pszSpec: ole.SysAllocString(filter.Pattern), + } + } + ret, _, _ := syscall.Syscall(vtbl.SetFileTypes, + 2, + uintptr(objPtr), + uintptr(cFileTypes), + uintptr(unsafe.Pointer(&comDlgFilterSpecs[0]))) + return hresultToError(ret) +} + +// Options are: +// FOS_OVERWRITEPROMPT = 0x2, +// FOS_STRICTFILETYPES = 0x4, +// FOS_NOCHANGEDIR = 0x8, +// FOS_PICKFOLDERS = 0x20, +// FOS_FORCEFILESYSTEM = 0x40, +// FOS_ALLNONSTORAGEITEMS = 0x80, +// FOS_NOVALIDATE = 0x100, +// FOS_ALLOWMULTISELECT = 0x200, +// FOS_PATHMUSTEXIST = 0x800, +// FOS_FILEMUSTEXIST = 0x1000, +// FOS_CREATEPROMPT = 0x2000, +// FOS_SHAREAWARE = 0x4000, +// FOS_NOREADONLYRETURN = 0x8000, +// FOS_NOTESTFILECREATE = 0x10000, +// FOS_HIDEMRUPLACES = 0x20000, +// FOS_HIDEPINNEDPLACES = 0x40000, +// FOS_NODEREFERENCELINKS = 0x100000, +// FOS_OKBUTTONNEEDSINTERACTION = 0x200000, +// FOS_DONTADDTORECENT = 0x2000000, +// FOS_FORCESHOWHIDDEN = 0x10000000, +// FOS_DEFAULTNOMINIMODE = 0x20000000, +// FOS_FORCEPREVIEWPANEON = 0x40000000, +// FOS_SUPPORTSTREAMABLEITEMS = 0x80000000 +func (vtbl *iFileDialogVtbl) setOptions(objPtr unsafe.Pointer, options uint32) error { + ret, _, _ := syscall.Syscall(vtbl.SetOptions, + 1, + uintptr(objPtr), + uintptr(options), + 0) + return hresultToError(ret) +} + +func (vtbl *iFileDialogVtbl) getOptions(objPtr unsafe.Pointer) (uint32, error) { + var options uint32 + ret, _, _ := syscall.Syscall(vtbl.GetOptions, + 1, + uintptr(objPtr), + uintptr(unsafe.Pointer(&options)), + 0) + return options, hresultToError(ret) +} + +func (vtbl *iFileDialogVtbl) addOption(objPtr unsafe.Pointer, option uint32) error { + if options, err := vtbl.getOptions(objPtr); err == nil { + return vtbl.setOptions(objPtr, options|option) + } else { + return err + } +} + +func (vtbl *iFileDialogVtbl) removeOption(objPtr unsafe.Pointer, option uint32) error { + if options, err := vtbl.getOptions(objPtr); err == nil { + return vtbl.setOptions(objPtr, options&^option) + } else { + return err + } +} + +func (vtbl *iFileDialogVtbl) setDefaultFolder(objPtr unsafe.Pointer, path string) error { + shellItem, err := newIShellItem(path) + if err != nil { + return err + } + defer shellItem.vtbl.release(unsafe.Pointer(shellItem)) + ret, _, _ := syscall.Syscall(vtbl.SetDefaultFolder, + 1, + uintptr(objPtr), + uintptr(unsafe.Pointer(shellItem)), + 0) + return hresultToError(ret) +} + +func (vtbl *iFileDialogVtbl) setFolder(objPtr unsafe.Pointer, path string) error { + shellItem, err := newIShellItem(path) + if err != nil { + return err + } + defer shellItem.vtbl.release(unsafe.Pointer(shellItem)) + ret, _, _ := syscall.Syscall(vtbl.SetFolder, + 1, + uintptr(objPtr), + uintptr(unsafe.Pointer(shellItem)), + 0) + return hresultToError(ret) +} + +func (vtbl *iFileDialogVtbl) setTitle(objPtr unsafe.Pointer, title string) error { + titlePtr := ole.SysAllocString(title) + ret, _, _ := syscall.Syscall(vtbl.SetTitle, + 1, + uintptr(objPtr), + uintptr(unsafe.Pointer(titlePtr)), + 0) + return hresultToError(ret) +} + +func (vtbl *iFileDialogVtbl) close(objPtr unsafe.Pointer) error { + ret, _, _ := syscall.Syscall(vtbl.Close, + 1, + uintptr(objPtr), + 0, + 0) + return hresultToError(ret) +} + +func (vtbl *iFileDialogVtbl) getResult(objPtr unsafe.Pointer) (*iShellItem, error) { + var shellItem *iShellItem + ret, _, _ := syscall.Syscall(vtbl.GetResult, + 1, + uintptr(objPtr), + uintptr(unsafe.Pointer(&shellItem)), + 0) + return shellItem, hresultToError(ret) +} + +func (vtbl *iFileDialogVtbl) getResultString(objPtr unsafe.Pointer) (string, error) { + shellItem, err := vtbl.getResult(objPtr) + if err != nil { + return "", err + } + if shellItem == nil { + return "", ErrorCancelled + } + defer shellItem.vtbl.release(unsafe.Pointer(shellItem)) + return shellItem.vtbl.getDisplayName(unsafe.Pointer(shellItem)) +} + +func (vtbl *iFileDialogVtbl) setClientGuid(objPtr unsafe.Pointer, guid *ole.GUID) error { + ret, _, _ := syscall.Syscall(vtbl.SetClientGuid, + 1, + uintptr(objPtr), + uintptr(unsafe.Pointer(guid)), + 0) + return hresultToError(ret) +} + +func (vtbl *iFileDialogVtbl) setDefaultExtension(objPtr unsafe.Pointer, defaultExtension string) error { + if defaultExtension[0] == '.' { + defaultExtension = strings.TrimPrefix(defaultExtension, ".") + } + defaultExtensionPtr := ole.SysAllocString(defaultExtension) + ret, _, _ := syscall.Syscall(vtbl.SetDefaultExtension, + 1, + uintptr(objPtr), + uintptr(unsafe.Pointer(defaultExtensionPtr)), + 0) + return hresultToError(ret) +} + +func (vtbl *iFileDialogVtbl) setFileName(objPtr unsafe.Pointer, fileName string) error { + fileNamePtr := ole.SysAllocString(fileName) + ret, _, _ := syscall.Syscall(vtbl.SetFileName, + 1, + uintptr(objPtr), + uintptr(unsafe.Pointer(fileNamePtr)), + 0) + return hresultToError(ret) +} + +func (vtbl *iFileDialogVtbl) setSelectedFileFilterIndex(objPtr unsafe.Pointer, index uint) error { + ret, _, _ := syscall.Syscall(vtbl.SetFileTypeIndex, + 1, + uintptr(objPtr), + uintptr(index+1), // SetFileTypeIndex counts from 1 + 0) + return hresultToError(ret) +} diff --git a/v3/internal/go-common-file-dialog/cfdutil/CFDUtil.go b/v3/internal/go-common-file-dialog/cfdutil/CFDUtil.go new file mode 100644 index 000000000..aa3a783b2 --- /dev/null +++ b/v3/internal/go-common-file-dialog/cfdutil/CFDUtil.go @@ -0,0 +1,45 @@ +package cfdutil + +import ( + "github.com/wailsapp/wails/v3/internal/go-common-file-dialog/cfd" +) + +// TODO doc +func ShowOpenFileDialog(config cfd.DialogConfig) (string, error) { + dialog, err := cfd.NewOpenFileDialog(config) + if err != nil { + return "", err + } + defer dialog.Release() + return dialog.ShowAndGetResult() +} + +// TODO doc +func ShowOpenMultipleFilesDialog(config cfd.DialogConfig) ([]string, error) { + dialog, err := cfd.NewOpenMultipleFilesDialog(config) + if err != nil { + return nil, err + } + defer dialog.Release() + return dialog.ShowAndGetResults() +} + +// TODO doc +func ShowPickFolderDialog(config cfd.DialogConfig) (string, error) { + dialog, err := cfd.NewSelectFolderDialog(config) + if err != nil { + return "", err + } + defer dialog.Release() + return dialog.ShowAndGetResult() +} + +// TODO doc +func ShowSaveFileDialog(config cfd.DialogConfig) (string, error) { + dialog, err := cfd.NewSaveFileDialog(config) + if err != nil { + return "", err + } + defer dialog.Release() + return dialog.ShowAndGetResult() +} diff --git a/v3/internal/go-common-file-dialog/util/util.go b/v3/internal/go-common-file-dialog/util/util.go new file mode 100644 index 000000000..723fbedc0 --- /dev/null +++ b/v3/internal/go-common-file-dialog/util/util.go @@ -0,0 +1,10 @@ +package util + +import ( + "github.com/go-ole/go-ole" + "github.com/google/uuid" +) + +func StringToUUID(str string) *ole.GUID { + return ole.NewGUID(uuid.NewSHA1(uuid.Nil, []byte(str)).String()) +} diff --git a/v3/internal/go-common-file-dialog/util/util_test.go b/v3/internal/go-common-file-dialog/util/util_test.go new file mode 100644 index 000000000..2e8ffeb05 --- /dev/null +++ b/v3/internal/go-common-file-dialog/util/util_test.go @@ -0,0 +1,14 @@ +package util + +import ( + "github.com/go-ole/go-ole" + "testing" +) + +func TestStringToUUID(t *testing.T) { + generated := *StringToUUID("TestTestTest") + expected := *ole.NewGUID("7933985F-2C87-5A5B-A26E-5D0326829AC2") + if generated != expected { + t.Errorf("not equal. expected %s, found %s", expected.String(), generated.String()) + } +} diff --git a/v3/internal/hash/fnv.go b/v3/internal/hash/fnv.go new file mode 100644 index 000000000..71e792ff2 --- /dev/null +++ b/v3/internal/hash/fnv.go @@ -0,0 +1,12 @@ +package hash + +import "hash/fnv" + +func Fnv(s string) (uint32, error) { + h := fnv.New32a() + _, err := h.Write([]byte(s)) + if err != nil { + return 0, err + } + return h.Sum32(), nil +} diff --git a/v3/internal/operatingsystem/os.go b/v3/internal/operatingsystem/os.go new file mode 100644 index 000000000..2d5656281 --- /dev/null +++ b/v3/internal/operatingsystem/os.go @@ -0,0 +1,23 @@ +package operatingsystem + +// OS contains information about the operating system +type OS struct { + ID string + Name string + Version string + Branding string +} + +func (o *OS) AsLogSlice() []any { + return []any{ + "ID", o.ID, + "Name", o.Name, + "Version", o.Version, + "Branding", o.Branding, + } +} + +// Info retrieves information about the current platform +func Info() (*OS, error) { + return platformInfo() +} diff --git a/v3/internal/operatingsystem/os_darwin.go b/v3/internal/operatingsystem/os_darwin.go new file mode 100644 index 000000000..137cf71af --- /dev/null +++ b/v3/internal/operatingsystem/os_darwin.go @@ -0,0 +1,71 @@ +//go:build darwin + +package operatingsystem + +import ( + "os/exec" + "strings" +) + +var macOSNames = map[string]string{ + "10.10": "Yosemite", + "10.11": "El Capitan", + "10.12": "Sierra", + "10.13": "High Sierra", + "10.14": "Mojave", + "10.15": "Catalina", + "11": "Big Sur", + "12": "Monterey", + "13": "Ventura", + "14": "Sonoma", + // Add newer versions as they are released... +} + +func getOSName(version string) string { + trimmedVersion := version + if !strings.HasPrefix(version, "10.") { + trimmedVersion = strings.SplitN(version, ".", 2)[0] + } + name, ok := macOSNames[trimmedVersion] + if ok { + return name + } + return "MacOS " + version +} + +func getSysctlValue(key string) (string, error) { + // Run "sysctl" command + command := exec.Command("sysctl", key) + // Capture stdout + var stdout strings.Builder + command.Stdout = &stdout + // Run command + err := command.Run() + if err != nil { + return "", err + } + version := strings.TrimPrefix(stdout.String(), key+": ") + return strings.TrimSpace(version), nil +} + +func platformInfo() (*OS, error) { + // Default value + var result OS + result.ID = "Unknown" + result.Name = "MacOS" + result.Version = "Unknown" + + version, err := getSysctlValue("kern.osproductversion") + if err != nil { + return nil, err + } + result.Version = version + ID, err := getSysctlValue("kern.osversion") + if err != nil { + return nil, err + } + result.ID = ID + result.Branding = getOSName(result.Version) + + return &result, nil +} diff --git a/v3/internal/operatingsystem/os_linux.go b/v3/internal/operatingsystem/os_linux.go new file mode 100644 index 000000000..715207dc5 --- /dev/null +++ b/v3/internal/operatingsystem/os_linux.go @@ -0,0 +1,53 @@ +//go:build linux +// +build linux + +package operatingsystem + +import ( + "fmt" + "os" + "strings" +) + +// platformInfo is the platform specific method to get system information +func platformInfo() (*OS, error) { + _, err := os.Stat("/etc/os-release") + if os.IsNotExist(err) { + return nil, fmt.Errorf("unable to read system information") + } + + osRelease, _ := os.ReadFile("/etc/os-release") + return parseOsRelease(string(osRelease)), nil +} + +func parseOsRelease(osRelease string) *OS { + + // Default value + var result OS + result.ID = "Unknown" + result.Name = "Unknown" + result.Version = "Unknown" + + // Split into lines + lines := strings.Split(osRelease, "\n") + // Iterate lines + for _, line := range lines { + // Split each line by the equals char + splitLine := strings.SplitN(line, "=", 2) + // Check we have + if len(splitLine) != 2 { + continue + } + switch splitLine[0] { + case "ID": + result.ID = strings.ToLower(strings.Trim(splitLine[1], `"`)) + case "NAME": + result.Name = strings.Trim(splitLine[1], `"`) + case "VERSION_ID": + result.Version = strings.Trim(splitLine[1], `"`) + case "VERSION": + result.Branding = strings.Trim(splitLine[1], `"`) + } + } + return &result +} diff --git a/v3/internal/operatingsystem/os_windows.go b/v3/internal/operatingsystem/os_windows.go new file mode 100644 index 000000000..2e8c0775b --- /dev/null +++ b/v3/internal/operatingsystem/os_windows.go @@ -0,0 +1,34 @@ +//go:build windows + +package operatingsystem + +import ( + "fmt" + "github.com/wailsapp/wails/v3/pkg/w32" + + "golang.org/x/sys/windows/registry" +) + +func platformInfo() (*OS, error) { + // Default value + var result OS + result.ID = "Unknown" + result.Name = "Windows" + result.Version = "Unknown" + + // Credit: https://stackoverflow.com/a/33288328 + // Ignore errors as it isn't a showstopper + key, _ := registry.OpenKey(registry.LOCAL_MACHINE, `SOFTWARE\Microsoft\Windows NT\CurrentVersion`, registry.QUERY_VALUE) + + productName, _, _ := key.GetStringValue("ProductName") + currentBuild, _, _ := key.GetStringValue("CurrentBuildNumber") + displayVersion, _, _ := key.GetStringValue("DisplayVersion") + releaseId, _, _ := key.GetStringValue("ReleaseId") + + result.Name = productName + result.Version = fmt.Sprintf("%s (Build: %s)", releaseId, currentBuild) + result.ID = displayVersion + result.Branding = w32.GetBranding() + + return &result, key.Close() +} diff --git a/v3/internal/operatingsystem/version_windows.go b/v3/internal/operatingsystem/version_windows.go new file mode 100644 index 000000000..a8f53d134 --- /dev/null +++ b/v3/internal/operatingsystem/version_windows.go @@ -0,0 +1,62 @@ +//go:build windows + +package operatingsystem + +import ( + "strconv" + + "golang.org/x/sys/windows/registry" +) + +type WindowsVersionInfo struct { + Major int + Minor int + Build int + DisplayVersion string +} + +func (w *WindowsVersionInfo) IsWindowsVersionAtLeast(major, minor, buildNumber int) bool { + return w.Major >= major && w.Minor >= minor && w.Build >= buildNumber +} + +func GetWindowsVersionInfo() (*WindowsVersionInfo, error) { + key, err := registry.OpenKey(registry.LOCAL_MACHINE, `SOFTWARE\Microsoft\Windows NT\CurrentVersion`, registry.QUERY_VALUE) + if err != nil { + return nil, err + } + + return &WindowsVersionInfo{ + Major: regDWORDKeyAsInt(key, "CurrentMajorVersionNumber"), + Minor: regDWORDKeyAsInt(key, "CurrentMinorVersionNumber"), + Build: regStringKeyAsInt(key, "CurrentBuildNumber"), + DisplayVersion: regKeyAsString(key, "DisplayVersion"), + }, nil +} + +func regDWORDKeyAsInt(key registry.Key, name string) int { + result, _, err := key.GetIntegerValue(name) + if err != nil { + return -1 + } + return int(result) +} + +func regStringKeyAsInt(key registry.Key, name string) int { + resultStr, _, err := key.GetStringValue(name) + if err != nil { + return -1 + } + result, err := strconv.Atoi(resultStr) + if err != nil { + return -1 + } + return result +} + +func regKeyAsString(key registry.Key, name string) string { + resultStr, _, err := key.GetStringValue(name) + if err != nil { + return "" + } + return resultStr +} diff --git a/v3/internal/parser/README.md b/v3/internal/parser/README.md new file mode 100644 index 000000000..fc6a1c700 --- /dev/null +++ b/v3/internal/parser/README.md @@ -0,0 +1,68 @@ +# Parser + +This package contains the static analyser used for parsing Wails projects so that we may: + +- Generate the bindings for the frontend +- Generate Typescript definitions for the structs used by the bindings + +## Implemented + +- [ ] Bound types + - [x] Struct Literal Pointer + - [ ] Variable + - [ ] Assignment + - [x] Struct Literal Pointer + - [ ] Function + - [ ] Same package + - [ ] Different package + - [ ] Function + +- [x] Parsing of bound methods + - [x] Method names + - [x] Method parameters + - [x] Zero parameters + - [x] Single input parameter + - [x] Single output parameter + - [x] Multiple input parameters + - [x] Multiple output parameters + - [x] Named output parameters + - [x] int/8/16/32/64 + - [x] Pointer + - [x] uint/8/16/32/64 + - [x] Pointer + - [x] float + - [x] Pointer + - [x] string + - [x] Pointer + - [x] bool + - [x] Pointer + - [x] Struct + - [x] Pointer + - [x] Slices + - [x] Pointer + - [x] Maps + - [x] Pointer +- [x] Model Parsing + - [x] In same package + - [x] In different package + - [x] Multiple named fields, e.g. one,two,three string + - [x] Scalars + - [x] Arrays + - [x] Maps + - [x] Structs + - [x] Recursive + - [x] Anonymous +- [ ] Generation of models + - [x] Scalars + - [ ] Arrays + - [ ] Maps + - [x] Structs +- [ ] Generation of bindings + +## Limitations + +There are many ways to write a Go program so there are many program structures that we would need to support. This is a work in progress and will be improved over time. The current limitations are: + +- The call to `application.New()` must be in the `main` package +- Bound structs must be declared as struct literals + diff --git a/v3/internal/parser/bindings.go b/v3/internal/parser/bindings.go new file mode 100644 index 000000000..7aa59512b --- /dev/null +++ b/v3/internal/parser/bindings.go @@ -0,0 +1,268 @@ +package parser + +import ( + "fmt" + "sort" + "strconv" + "strings" + + "github.com/samber/lo" +) + +const header = `// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +` + +const bindingTemplate = ` + /** + * {{structName}}.{{methodName}} + *Comments + * @param name {string} + * @returns {Promise} + **/ + {{methodName}}: function({{inputs}}) { return wails.CallByID({{ID}}, ...Array.prototype.slice.call(arguments, 0)); }, +` + +var reservedWords = []string{ + "abstract", + "arguments", + "await", + "boolean", + "break", + "byte", + "case", + "catch", + "char", + "class", + "const", + "continue", + "debugger", + "default", + "delete", + "do", + "double", + "else", + "enum", + "eval", + "export", + "extends", + "false", + "final", + "finally", + "float", + "for", + "function", + "goto", + "if", + "implements", + "import", + "in", + "instanceof", + "int", + "interface", + "let", + "long", + "native", + "new", + "null", + "package", + "private", + "protected", + "public", + "return", + "short", + "static", + "super", + "switch", + "synchronized", + "this", + "throw", + "throws", + "transient", + "true", + "try", + "typeof", + "var", + "void", + "volatile", + "while", + "with", + "yield", + "object", +} + +func sanitiseJSVarName(name string) string { + // if the name is a reserved word, prefix with an + // underscore + if lo.Contains(reservedWords, name) { + return "_" + name + } + return name +} + +func GenerateBinding(structName string, method *BoundMethod) (string, []string) { + var models []string + result := strings.ReplaceAll(bindingTemplate, "{{structName}}", structName) + result = strings.ReplaceAll(result, "{{methodName}}", method.Name) + result = strings.ReplaceAll(result, "{{ID}}", fmt.Sprintf("%v", method.ID)) + comments := strings.TrimSpace(method.DocComment) + if comments != "" { + comments = " " + comments + } + result = strings.ReplaceAll(result, "Comments", comments) + var params string + for _, input := range method.Inputs { + pkgName := getPackageName(input) + if pkgName != "" { + models = append(models, pkgName) + } + params += " * @param " + sanitiseJSVarName(input.Name) + " {" + input.JSType() + "}\n" + } + params = strings.TrimSuffix(params, "\n") + if len(params) == 0 { + params = " *" + } + result = strings.ReplaceAll(result, " * @param name {string}", params) + var inputs string + for _, input := range method.Inputs { + pkgName := getPackageName(input) + if pkgName != "" { + models = append(models, pkgName) + } + inputs += sanitiseJSVarName(input.Name) + ", " + } + inputs = strings.TrimSuffix(inputs, ", ") + args := inputs + if len(args) > 0 { + args = ", " + args + } + result = strings.ReplaceAll(result, "{{inputs}}", inputs) + result = strings.ReplaceAll(result, "{{args}}", args) + + // outputs + var returns string + if len(method.Outputs) == 0 { + returns = " * @returns {Promise}" + } else { + returns = " * @returns {Promise<" + for _, output := range method.Outputs { + pkgName := getPackageName(output) + if pkgName != "" { + models = append(models, pkgName) + } + jsType := output.JSType() + if jsType == "error" { + jsType = "void" + } + returns += jsType + ", " + } + returns = strings.TrimSuffix(returns, ", ") + returns += ">}" + } + result = strings.ReplaceAll(result, " * @returns {Promise}", returns) + + return result, lo.Uniq(models) +} + +func getPackageName(input *Parameter) string { + if !input.Type.IsStruct { + return "" + } + result := input.Type.Package + if result == "" { + result = "main" + } + return result +} + +func normalisePackageNames(packageNames []string) map[string]string { + // We iterate over the package names and determine if any of them + // have a forward slash. If this is the case, we assume that the + // package name is the last element of the path. If this has already + // been found, then we need to add a digit to the end of the package + // name to make it unique. We return a map of the original package + // name to the new package name. + var result = make(map[string]string) + var packagesConverted = make(map[string]struct{}) + var count = 1 + for _, packageName := range packageNames { + var originalPackageName = packageName + if strings.Contains(packageName, "/") { + parts := strings.Split(packageName, "/") + packageName = parts[len(parts)-1] + } + if _, ok := packagesConverted[packageName]; ok { + // We've already seen this package name. Add a digit + // to the end of the package name to make it unique + count += 1 + packageName += strconv.Itoa(count) + + } + packagesConverted[packageName] = struct{}{} + result[originalPackageName] = packageName + } + + return result +} + +func GenerateBindings(bindings map[string]map[string][]*BoundMethod) map[string]string { + + var result = make(map[string]string) + + var normalisedPackageNames = normalisePackageNames(lo.Keys(bindings)) + // sort the bindings keys + packageNames := lo.Keys(bindings) + sort.Strings(packageNames) + for _, packageName := range packageNames { + var allModels []string + + packageBindings := bindings[packageName] + structNames := lo.Keys(packageBindings) + sort.Strings(structNames) + result[normalisedPackageNames[packageName]] += ` +window.go = window.go || {}; +` + // Iterate over the sorted struct keys + result[normalisedPackageNames[packageName]] += "window.go." + normalisedPackageNames[packageName] + " = {\n" + for _, structName := range structNames { + /** + * The GreetService provides methods to greet a person. + */ + //result[normalisedPackageNames[packageName]] += " /**\n" + //result[normalisedPackageNames[packageName]] += " {{structcomments}}\n" + //result[normalisedPackageNames[packageName]] += " */\n" + result[normalisedPackageNames[packageName]] += " " + structName + ": {\n" + methods := packageBindings[structName] + sort.Slice(methods, func(i, j int) bool { + return methods[i].Name < methods[j].Name + }) + for _, method := range methods { + thisBinding, models := GenerateBinding(structName, method) + allModels = append(allModels, models...) + result[normalisedPackageNames[packageName]] += thisBinding + } + result[normalisedPackageNames[packageName]] += " },\n" + } + result[normalisedPackageNames[packageName]] += "};\n" + + // add imports + /* if len(allModels) > 0 { + allModels := lo.Uniq(allModels) + var models []string + for _, model := range allModels { + models = append(models, normalisedPackageNames[model]) + } + sort.Strings(models) + result[normalisedPackageNames[packageName]] += "\n" + imports := "import {" + strings.Join(models, ", ") + "} from './models';\n" + result[normalisedPackageNames[packageName]] = imports + result[normalisedPackageNames[packageName]] + } + */ + + result[normalisedPackageNames[packageName]] = header + result[normalisedPackageNames[packageName]] + } + + return result +} diff --git a/v3/internal/parser/bindings_test.go b/v3/internal/parser/bindings_test.go new file mode 100644 index 000000000..1fb2a2c0b --- /dev/null +++ b/v3/internal/parser/bindings_test.go @@ -0,0 +1,129 @@ +package parser + +import ( + "embed" + "io/fs" + "os" + "testing" + + "github.com/google/go-cmp/cmp" +) + +//go:embed testdata +var testdata embed.FS + +func getFile(filename string) string { + // get the file from the testdata FS + file, err := fs.ReadFile(testdata, filename) + if err != nil { + panic(err) + } + return string(file) +} + +func TestGenerateBindings(t *testing.T) { + + tests := []struct { + dir string + want map[string]string + }{ + { + "testdata/function_single", + map[string]string{ + "main": getFile("testdata/function_single/bindings_main.js"), + }, + }, + { + "testdata/function_from_imported_package", + map[string]string{ + "main": getFile("testdata/function_from_imported_package/bindings_main.js"), + "services": getFile("testdata/function_from_imported_package/bindings_services.js"), + }, + }, + { + "testdata/variable_single", + map[string]string{ + "main": getFile("testdata/variable_single/bindings_main.js"), + }, + }, + { + "testdata/variable_single_from_function", + map[string]string{ + "main": getFile("testdata/variable_single_from_function/bindings_main.js"), + }, + }, + { + "testdata/variable_single_from_other_function", + map[string]string{ + "main": getFile("testdata/variable_single_from_other_function/bindings_main.js"), + "services": getFile("testdata/variable_single_from_other_function/bindings_services.js"), + }, + }, + { + "testdata/struct_literal_single", + map[string]string{ + "main": getFile("testdata/struct_literal_single/bindings_main.js"), + }, + }, + { + "testdata/struct_literal_multiple", + map[string]string{ + "main": getFile("testdata/struct_literal_multiple/bindings_main.js"), + }, + }, + { + "testdata/struct_literal_multiple_other", + map[string]string{ + "main": getFile("testdata/struct_literal_multiple_other/bindings_main.js"), + "services": getFile("testdata/struct_literal_multiple_other/bindings_services.js"), + }, + }, + { + "testdata/struct_literal_multiple_files", + map[string]string{ + "main": getFile("testdata/struct_literal_multiple_files/bindings_main.js"), + }, + }, + } + for _, tt := range tests { + t.Run(tt.dir, func(t *testing.T) { + // Run parser on directory + project, err := ParseProject(tt.dir) + if err != nil { + t.Errorf("ParseProject() error = %v", err) + return + } + + // Generate Bindings + got := GenerateBindings(project.BoundMethods) + + for name, binding := range got { + // check if the binding is in the expected bindings + expected, ok := tt.want[name] + if !ok { + err = os.WriteFile(tt.dir+"/bindings_"+name+".got.js", []byte(binding), 0644) + if err != nil { + t.Errorf("os.WriteFile() error = %v", err) + return + } + t.Errorf("GenerateBindings() unexpected binding = %v", name) + return + } + // compare the binding + + // convert all line endings to \n + binding = convertLineEndings(binding) + expected = convertLineEndings(expected) + + if diff := cmp.Diff(expected, binding); diff != "" { + err = os.WriteFile(tt.dir+"/bindings_"+name+".got.js", []byte(binding), 0644) + if err != nil { + t.Errorf("os.WriteFile() error = %v", err) + return + } + t.Fatalf("GenerateBindings() mismatch (-want +got):\n%s", diff) + } + } + }) + } +} diff --git a/v3/internal/parser/constants.go b/v3/internal/parser/constants.go new file mode 100644 index 000000000..cfd5c4ab8 --- /dev/null +++ b/v3/internal/parser/constants.go @@ -0,0 +1,54 @@ +package parser + +import ( + "fmt" + "go/ast" + "go/parser" + "go/token" + "strings" +) + +func GenerateConstants(goData []byte) (string, error) { + + // Create a new token file set and parser + fs := token.NewFileSet() + f, err := parser.ParseFile(fs, "", goData, parser.AllErrors) + if err != nil { + return "", err + } + + // Extract constant declarations and generate JavaScript constants + var jsConstants []string + for _, decl := range f.Decls { + if gd, ok := decl.(*ast.GenDecl); ok && gd.Tok == token.CONST { + for _, spec := range gd.Specs { + if vs, ok := spec.(*ast.ValueSpec); ok { + for i, name := range vs.Names { + value := vs.Values[i] + if value != nil { + jsConstants = append(jsConstants, fmt.Sprintf("export const %s = %s;", name.Name, jsValue(value))) + } + } + } + } + } + } + + // Join the JavaScript constants into a single string + jsCode := strings.Join(jsConstants, "\n") + + return jsCode, nil +} + +func jsValue(expr ast.Expr) string { + // Implement conversion from Go constant value to JavaScript value here. + // You can add more cases for different types if needed. + switch e := expr.(type) { + case *ast.BasicLit: + return e.Value + case *ast.Ident: + return e.Name + default: + return "" + } +} diff --git a/v3/internal/parser/constants_test.go b/v3/internal/parser/constants_test.go new file mode 100644 index 000000000..870ca4ecf --- /dev/null +++ b/v3/internal/parser/constants_test.go @@ -0,0 +1,55 @@ +package parser + +import "testing" + +func TestGenerateConstants(t *testing.T) { + tests := []struct { + name string + goData []byte + want string + wantErr bool + }{ + { + name: "int", + goData: []byte(`package test +const one = 1`), + want: "export const one = 1;", + wantErr: false, + }, + { + name: "float", + goData: []byte(`package test +const one_point_five = 1.5`), + want: "export const one_point_five = 1.5;", + wantErr: false, + }, + { + name: "string", + goData: []byte(`package test +const one_as_a_string = "1"`), + want: `export const one_as_a_string = "1";`, + wantErr: false, + }, + { + name: "nested", + goData: []byte(`package test +const ( + one_as_a_string = "1" +)`), + want: `export const one_as_a_string = "1";`, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := GenerateConstants(tt.goData) + if (err != nil) != tt.wantErr { + t.Errorf("GenerateConstants() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("GenerateConstants() got = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/v3/internal/parser/models.go b/v3/internal/parser/models.go new file mode 100644 index 000000000..07ecb3052 --- /dev/null +++ b/v3/internal/parser/models.go @@ -0,0 +1,184 @@ +package parser + +import ( + "bytes" + "embed" + "io" + "sort" + "strings" + "text/template" +) + +//go:embed templates +var templates embed.FS + +type ModelDefinitions struct { + Package string + Models map[string]*StructDef +} + +func GenerateModel(wr io.Writer, def *ModelDefinitions) error { + tmpl, err := template.New("model.ts.tmpl").ParseFS(templates, "templates/model.ts.tmpl") + if err != nil { + println("Unable to create class template: " + err.Error()) + return err + } + + err = tmpl.ExecuteTemplate(wr, "model.ts.tmpl", def) + if err != nil { + println("Problem executing template: " + err.Error()) + return err + } + return nil +} + +const modelsHeader = `// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT +` + +func pkgAlias(fullPkg string) string { + pkgParts := strings.Split(fullPkg, "/") + return pkgParts[len(pkgParts)-1] +} + +func GenerateModels(models map[packagePath]map[structName]*StructDef) (string, error) { + if models == nil { + return "", nil + } + + var buffer bytes.Buffer + buffer.WriteString(modelsHeader) + + // sort pkgs by alias (e.g. services) instead of full pkg name (e.g. github.com/wailsapp/wails/somedir/services) + // and then sort resulting list by the alias + var keys []string + for pkg, _ := range models { + keys = append(keys, pkg) + } + + sort.Slice(keys, func(i, j int) bool { + return pkgAlias(keys[i]) < pkgAlias(keys[j]) + }) + + for _, pkg := range keys { + err := GenerateModel(&buffer, &ModelDefinitions{ + Package: pkgAlias(pkg), + Models: models[pkg], + }) + if err != nil { + return "", err + } + } + return buffer.String(), nil +} + +//func GenerateClass(wr io.Writer, def *StructDef) error { +// tmpl, err := template.New("class.ts.tmpl").ParseFiles("templates/class.ts.tmpl") +// if err != nil { +// println("Unable to create class template: " + err.Error()) +// return err +// } +// +// err = tmpl.ExecuteTemplate(wr, "class.ts.tmpl", def) +// if err != nil { +// println("Problem executing template: " + err.Error()) +// return err +// } +// return nil +//} + +// +//import ( +// "bytes" +// "fmt" +// "go/ast" +// "go/types" +// "sort" +// "strings" +// "unicode" +//) +// +//func GenerateModels(context *Context) ([]byte, error) { +// var buf bytes.Buffer +// var pkgs []Package +// specs := context.GetBoundStructs() +// for pkg, pkgSpecs := range specs { +// pkgs = append(pkgs, Package{Name: pkg, Specs: pkgSpecs}) +// } +// knownStructs := newAllModels(specs) +// sort.Slice(pkgs, func(i, j int) bool { return pkgs[i].Name < pkgs[j].Name }) +// for _, pkg := range pkgs { +// if _, err := fmt.Fprintf(&buf, "namespace %s {\n", pkg.Name); err != nil { +// return nil, err +// } +// sort.Slice(pkg.Specs, func(i, j int) bool { return pkg.Specs[i].Name.Name < pkg.Specs[j].Name.Name }) +// for _, spec := range pkg.Specs { +// if structType, ok := spec.Type.(*ast.StructType); ok { +// if _, err := fmt.Fprintf(&buf, " class %s {\n", spec.Name.Name); err != nil { +// return nil, err +// } +// +// for _, field := range structType.Fields.List { +// +// // Ignore field names that have a lower case first letter +// if !unicode.IsUpper(rune(field.Names[0].Name[0])) { +// continue +// } +// +// // Get the Go type of the field +// goType := types.ExprString(field.Type) +// // Check if the type is an array +// if arrayType, ok := field.Type.(*ast.ArrayType); ok { +// // Get the element type of the array +// elementType := types.ExprString(arrayType.Elt) +// // Look up the corresponding TypeScript type +// tsType, ok := goToTS[elementType] +// if !ok { +// // strip off the * prefix if it is there +// if strings.HasPrefix(elementType, "*") { +// elementType = elementType[1:] +// } +// if knownStructs.exists(elementType) { +// tsType = elementType +// } else { +// tsType = "any" +// } +// } +// // Output the field as an array of the corresponding TypeScript type +// if _, err := fmt.Fprintf(&buf, " %s: %s[];\n", field.Names[0].Name, tsType); err != nil { +// return nil, err +// } +// } else { +// // strip off the * prefix if it is there +// if strings.HasPrefix(goType, "*") { +// goType = goType[1:] +// } +// // Look up the corresponding TypeScript type +// tsType, ok := goToTS[goType] +// if !ok { +// if knownStructs.exists(goType) { +// tsType = goType +// } else { +// tsType = "any" +// } +// } +// // Output the field as the corresponding TypeScript type +// if _, err := fmt.Fprintf(&buf, " %s: %s;\n", field.Names[0].Name, tsType); err != nil { +// return nil, err +// } +// } +// } +// +// if _, err := fmt.Fprintf(&buf, " }\n"); err != nil { +// return nil, err +// } +// } +// } +// +// if _, err := fmt.Fprintf(&buf, "}\n\n"); err != nil { +// return nil, err +// } +// } +// return buf.Bytes(), nil +//} diff --git a/v3/internal/parser/models_test.go b/v3/internal/parser/models_test.go new file mode 100644 index 000000000..f91f0b291 --- /dev/null +++ b/v3/internal/parser/models_test.go @@ -0,0 +1,85 @@ +package parser + +import ( + "github.com/google/go-cmp/cmp" + "os" + "path/filepath" + "strings" + "testing" +) + +func TestGenerateModels(t *testing.T) { + + tests := []struct { + dir string + want string + }{ + { + "testdata/function_single", + "", + }, + { + "testdata/function_from_imported_package", + getFile("testdata/function_from_imported_package/models.ts"), + }, + { + "testdata/variable_single", + "", + }, + { + "testdata/variable_single_from_function", + "", + }, + { + "testdata/variable_single_from_other_function", + getFile("testdata/variable_single_from_other_function/models.ts"), + }, + { + "testdata/struct_literal_single", + getFile("testdata/struct_literal_single/models.ts"), + }, + { + "testdata/struct_literal_multiple", + "", + }, + { + "testdata/struct_literal_multiple_other", + getFile("testdata/struct_literal_multiple_other/models.ts"), + }, + { + "testdata/struct_literal_multiple_files", + "", + }, + } + for _, tt := range tests { + t.Run(tt.dir, func(t *testing.T) { + // Run parser on directory + project, err := ParseProject(tt.dir) + if err != nil { + t.Fatalf("ParseProject() error = %v", err) + } + + // Generate Models + got, err := GenerateModels(project.Models) + if err != nil { + t.Fatalf("GenerateModels() error = %v", err) + } + // convert all line endings to \n + got = convertLineEndings(got) + want := convertLineEndings(tt.want) + if diff := cmp.Diff(want, got); diff != "" { + err = os.WriteFile(filepath.Join(tt.dir, "models.got.ts"), []byte(got), 0644) + if err != nil { + t.Errorf("os.WriteFile() error = %v", err) + return + } + t.Fatalf("GenerateModels() mismatch (-want +got):\n%s", diff) + } + }) + } +} + +func convertLineEndings(str string) string { + // replace all \r\n with \n + return strings.ReplaceAll(str, "\r\n", "\n") +} diff --git a/v3/internal/parser/parser.go b/v3/internal/parser/parser.go new file mode 100644 index 000000000..bb10ef264 --- /dev/null +++ b/v3/internal/parser/parser.go @@ -0,0 +1,857 @@ +package parser + +import ( + "fmt" + "go/ast" + "go/build" + "go/parser" + "go/token" + "log" + "os" + "path/filepath" + "reflect" + "strconv" + "strings" + + "github.com/samber/lo" + "github.com/wailsapp/wails/v3/internal/hash" +) + +type packagePath = string +type structName = string + +type StructDef struct { + Name string + DocComment string + Fields []*Field +} + +type ParameterType struct { + Name string + IsStruct bool + IsSlice bool + IsPointer bool + MapKey *ParameterType + MapValue *ParameterType + Package string +} + +type Parameter struct { + Name string + Type *ParameterType +} + +func (p *Parameter) JSType() string { + // Convert type to javascript equivalent type + var typeName string + switch p.Type.Name { + case "int", "int8", "int16", "int32", "int64", "uint", "uint8", "uint16", "uint32", "uint64", "uintptr", "float32", "float64": + typeName = "number" + case "string": + typeName = "string" + case "bool": + typeName = "boolean" + default: + typeName = p.Type.Name + } + + // if the type is a struct, we need to add the package name + if p.Type.IsStruct { + if p.Type.Package != "" { + parts := strings.Split(p.Type.Package, "/") + typeName = parts[len(parts)-1] + "." + typeName + // TODO: Check if this is a duplicate package name + } + } + + // Add slice suffix + if p.Type.IsSlice { + typeName += "[]" + } + + // Add pointer suffix + if p.Type.IsPointer { + typeName += " | null" + } + + return typeName +} + +type BoundMethod struct { + Name string + DocComment string + Inputs []*Parameter + Outputs []*Parameter + ID uint32 + Alias *uint32 +} + +func (m BoundMethod) IDAsString() string { + return strconv.Itoa(int(m.ID)) +} + +type Field struct { + Name string + Type *ParameterType +} + +func (f *Field) JSName() string { + return strings.ToLower(f.Name[0:1]) + f.Name[1:] +} + +// TSBuild contains the typescript to build a field for a JS object +// via assignment for simple types or constructors for structs +func (f *Field) TSBuild(pkg string) string { + if !f.Type.IsStruct { + return fmt.Sprintf("source['%s']", f.JSName()) + } + + if f.Type.Package == "" || f.Type.Package == pkg { + return fmt.Sprintf("%s.createFrom(source['%s'])", f.Type.Name, f.JSName()) + } + + return fmt.Sprintf("%s.%s.createFrom(source['%s'])", pkgAlias(f.Type.Package), f.Type.Name, f.JSName()) +} + +func (f *Field) JSDef(pkg string) string { + var jsType string + switch f.Type.Name { + case "int", "int8", "int16", "int32", "int64", "uint", "uint8", "uint16", "uint32", "uint64", "uintptr", "float32", "float64": + jsType = "number" + case "string": + jsType = "string" + case "bool": + jsType = "boolean" + default: + jsType = f.Type.Name + } + + var result string + if f.Type.Package == "" || f.Type.Package == pkg { + result += fmt.Sprintf("%s: %s;", f.JSName(), jsType) + } else { + parts := strings.Split(f.Type.Package, "/") + result += fmt.Sprintf("%s: %s.%s;", f.JSName(), parts[len(parts)-1], jsType) + } + + if !ast.IsExported(f.Name) { + result += " // Warning: this is unexported in the Go struct." + } + + return result +} + +type ParsedPackage struct { + Pkg *ast.Package + Name string + Path string + Dir string + StructCache map[structName]*StructDef +} + +type Project struct { + packageCache map[string]*ParsedPackage + Path string + BoundMethods map[packagePath]map[structName][]*BoundMethod + Models map[packagePath]map[structName]*StructDef + anonymousStructIDCounter int +} + +func ParseProject(projectPath string) (*Project, error) { + result := &Project{ + BoundMethods: make(map[packagePath]map[structName][]*BoundMethod), + packageCache: make(map[string]*ParsedPackage), + } + pkgs, err := result.parseDirectory(projectPath) + if err != nil { + return nil, err + } + err = result.findApplicationNewCalls(pkgs) + if err != nil { + return nil, err + } + for _, pkg := range result.packageCache { + if len(pkg.StructCache) > 0 { + if result.Models == nil { + result.Models = make(map[packagePath]map[structName]*StructDef) + } + result.Models[pkg.Path] = pkg.StructCache + } + } + return result, nil +} + +func GenerateBindingsAndModels(projectDir string, outputDir string) error { + p, err := ParseProject(projectDir) + if err != nil { + return err + } + + if p.BoundMethods == nil { + return nil + } + err = os.MkdirAll(outputDir, 0755) + if err != nil { + return err + } + generatedMethods := GenerateBindings(p.BoundMethods) + for pkg, text := range generatedMethods { + // Write the file + err = os.WriteFile(filepath.Join(outputDir, "bindings_"+pkg+".js"), []byte(text), 0644) + if err != nil { + return err + } + } + + // Generate Models + if len(p.Models) > 0 { + generatedModels, err := GenerateModels(p.Models) + if err != nil { + return err + } + err = os.WriteFile(filepath.Join(outputDir, "models.ts"), []byte(generatedModels), 0644) + if err != nil { + return err + } + } + + absPath, err := filepath.Abs(projectDir) + if err != nil { + return err + } + println("Generated bindings and models for project: " + absPath) + absPath, err = filepath.Abs(outputDir) + if err != nil { + return err + } + println("Output directory: " + absPath) + + return nil +} + +func (p *Project) parseDirectory(dir string) (map[string]*ParsedPackage, error) { + if p.packageCache[dir] != nil { + return map[string]*ParsedPackage{dir: p.packageCache[dir]}, nil + } + // Parse the directory + fset := token.NewFileSet() + if dir == "." || dir == "" { + cwd, err := os.Getwd() + if err != nil { + return nil, err + } + dir = cwd + } + pkgs, err := parser.ParseDir(fset, dir, nil, parser.ParseComments) + if err != nil { + return nil, err + } + var result = make(map[string]*ParsedPackage) + for packageName, pkg := range pkgs { + parsedPackage := &ParsedPackage{ + Pkg: pkg, + Name: packageName, + Path: packageName, + Dir: getDirectoryForPackage(pkg), + StructCache: make(map[structName]*StructDef), + } + p.packageCache[packageName] = parsedPackage + result[packageName] = parsedPackage + } + return result, nil +} + +func (p *Project) findApplicationNewCalls(pkgs map[string]*ParsedPackage) (err error) { + + var callFound bool + + for _, pkg := range pkgs { + thisPackage := pkg.Pkg + // Iterate through the package's files + for _, file := range thisPackage.Files { + // Use an ast.Inspector to find the calls to application.New + ast.Inspect(file, func(n ast.Node) bool { + // Check if the node is a call expression + callExpr, ok := n.(*ast.CallExpr) + if !ok { + return true + } + + // Check if the function being called is "application.New" + selExpr, ok := callExpr.Fun.(*ast.SelectorExpr) + if !ok { + return true + } + if selExpr.Sel.Name != "New" { + return true + } + if id, ok := selExpr.X.(*ast.Ident); !ok || id.Name != "application" { + return true + } + + // Check there is only 1 argument + if len(callExpr.Args) != 1 { + return true + } + + // Check argument 1 is a struct literal + structLit, ok := callExpr.Args[0].(*ast.CompositeLit) + if !ok { + return true + } + + // Check struct literal is of type "application.Options" + selectorExpr, ok := structLit.Type.(*ast.SelectorExpr) + if !ok { + return true + } + if selectorExpr.Sel.Name != "Options" { + return true + } + if id, ok := selectorExpr.X.(*ast.Ident); !ok || id.Name != "application" { + return true + } + + for _, elt := range structLit.Elts { + // Find the "Bind" field + kvExpr, ok := elt.(*ast.KeyValueExpr) + if !ok { + continue + } + if id, ok := kvExpr.Key.(*ast.Ident); !ok || id.Name != "Bind" { + continue + } + // Check the value is a slice of interfaces + sliceExpr, ok := kvExpr.Value.(*ast.CompositeLit) + if !ok { + continue + } + var arrayType *ast.ArrayType + if arrayType, ok = sliceExpr.Type.(*ast.ArrayType); !ok { + continue + } + + // Check array type is of type "interface{}" + _, isInterfaceType := arrayType.Elt.(*ast.InterfaceType) + if !isInterfaceType { + // Check it's an "any" type + ident, isAnyType := arrayType.Elt.(*ast.Ident) + if !isAnyType { + continue + } + if ident.Name != "any" { + continue + } + } + callFound = true + // Iterate through the slice elements + for _, elt := range sliceExpr.Elts { + result, shouldContinue := p.parseBoundExpression(elt, pkg) + if shouldContinue { + continue + } + return result + + } + } + + return true + }) + } + if !callFound { + return fmt.Errorf("no Bound structs found") + } + } + return nil +} + +func (p *Project) parseBoundUnaryExpression(unaryExpr *ast.UnaryExpr, pkg *ParsedPackage) (bool, bool) { + // Check the unary expression is a composite lit + + switch t := unaryExpr.X.(type) { + case *ast.CompositeLit: + return p.parseBoundCompositeLit(t, pkg) + } + return false, true + +} + +func (p *Project) addBoundMethods(packagePath string, name string, boundMethods []*BoundMethod) { + _, ok := p.BoundMethods[packagePath] + if !ok { + p.BoundMethods[packagePath] = make(map[structName][]*BoundMethod) + } + p.BoundMethods[packagePath][name] = boundMethods +} + +func (p *Project) parseBoundStructMethods(name string, pkg *ParsedPackage) error { + var methods []*BoundMethod + // Iterate over all files in the package + for _, file := range pkg.Pkg.Files { + // Iterate over all declarations in the file + for _, decl := range file.Decls { + // Check if the declaration is a type declaration + if funcDecl, ok := decl.(*ast.FuncDecl); ok && funcDecl.Recv != nil && funcDecl.Name.IsExported() { + var ident *ast.Ident + var ok bool + + switch funcDecl.Recv.List[0].Type.(type) { + case *ast.StarExpr: + recv := funcDecl.Recv.List[0].Type.(*ast.StarExpr) + ident, ok = recv.X.(*ast.Ident) + case *ast.Ident: + ident, ok = funcDecl.Recv.List[0].Type.(*ast.Ident) + } + if ok && ident.Name == name { + fqn := fmt.Sprintf("%s.%s.%s", pkg.Path, name, funcDecl.Name.Name) + + var alias *uint32 + var err error + // Check for the text `wails:methodID ` + if funcDecl.Doc != nil { + for _, docstring := range funcDecl.Doc.List { + if strings.Contains(docstring.Text, "//wails:methodID") { + idString := strings.TrimSpace(strings.TrimPrefix(docstring.Text, "//wails:methodID")) + parsedID, err := strconv.ParseUint(idString, 10, 32) + if err != nil { + return fmt.Errorf("invalid value in `wails:methodID` directive: '%s'. Expected a valid uint32 value", idString) + } + alias = lo.ToPtr(uint32(parsedID)) + break + } + } + } + id, err := hash.Fnv(fqn) + if err != nil { + return err + } + // Add the method to the list of methods + method := &BoundMethod{ + ID: id, + Name: funcDecl.Name.Name, + DocComment: funcDecl.Doc.Text(), + Alias: alias, + } + + if funcDecl.Type.Params != nil { + method.Inputs = p.parseParameters(funcDecl.Type.Params, pkg) + } + if funcDecl.Type.Results != nil { + method.Outputs = p.parseParameters(funcDecl.Type.Results, pkg) + } + + methods = append(methods, method) + } + + } + } + } + p.addBoundMethods(pkg.Path, name, methods) + return nil +} + +func (p *Project) parseParameters(params *ast.FieldList, pkg *ParsedPackage) []*Parameter { + var result []*Parameter + for _, field := range params.List { + var theseFields []*Parameter + if len(field.Names) > 0 { + for _, name := range field.Names { + theseFields = append(theseFields, &Parameter{ + Name: name.Name, + }) + } + } else { + theseFields = append(theseFields, &Parameter{ + Name: "", + }) + } + // loop over fields + for _, thisField := range theseFields { + thisField.Type = p.parseParameterType(field, pkg) + result = append(result, thisField) + } + } + return result +} + +func (p *Project) parseParameterType(field *ast.Field, pkg *ParsedPackage) *ParameterType { + result := &ParameterType{} + result.Name = getTypeString(field.Type) + switch t := field.Type.(type) { + case *ast.Ident: + result.IsStruct = isStructType(t) + case *ast.StarExpr: + result = p.parseParameterType(&ast.Field{Type: t.X}, pkg) + result.IsPointer = true + case *ast.StructType: + result.IsStruct = true + if result.Name == "" { + // Anonymous struct + result.Name = p.anonymousStructID() + // Create a new struct definition + result := &StructDef{ + Name: result.Name, + } + pkg.StructCache[result.Name] = result + // Parse the fields + result.Fields = p.parseStructFields(&ast.StructType{ + Fields: t.Fields, + }, pkg) + _ = result + } + case *ast.SelectorExpr: + extPackage, err := p.getParsedPackageFromName(t.X.(*ast.Ident).Name, pkg) + if err != nil { + log.Fatal(err) + } + result.IsStruct = p.getStructDef(t.Sel.Name, extPackage) + result.Package = extPackage.Path + case *ast.ArrayType: + result.IsSlice = true + result.IsStruct = isStructType(t.Elt) + case *ast.MapType: + tempfield := &ast.Field{Type: t.Key} + result.MapKey = p.parseParameterType(tempfield, pkg) + tempfield.Type = t.Value + result.MapValue = p.parseParameterType(tempfield, pkg) + default: + } + if result.IsStruct { + p.getStructDef(result.Name, pkg) + if result.Package == "" { + result.Package = pkg.Path + } + } + return result +} + +func (p *Project) getStructDef(name string, pkg *ParsedPackage) bool { + _, ok := pkg.StructCache[name] + if ok { + return true + } + // Iterate over all files in the package + for _, file := range pkg.Pkg.Files { + // Iterate over all declarations in the file + for _, decl := range file.Decls { + // Check if the declaration is a type declaration + if typeDecl, ok := decl.(*ast.GenDecl); ok { + // Check if the type declaration is a struct type + if typeDecl.Tok == token.TYPE { + for _, spec := range typeDecl.Specs { + if typeSpec, ok := spec.(*ast.TypeSpec); ok { + if structType, ok := typeSpec.Type.(*ast.StructType); ok { + if typeSpec.Name.Name == name { + result := &StructDef{ + Name: name, + DocComment: typeDecl.Doc.Text(), + } + pkg.StructCache[name] = result + result.Fields = p.parseStructFields(structType, pkg) + return true + } + } + } + } + } + } + } + } + return false +} + +func (p *Project) parseStructFields(structType *ast.StructType, pkg *ParsedPackage) []*Field { + var result []*Field + for _, field := range structType.Fields.List { + var theseFields []*Field + if len(field.Names) > 0 { + for _, name := range field.Names { + theseFields = append(theseFields, &Field{ + Name: name.Name, + }) + } + } else { + theseFields = append(theseFields, &Field{ + Name: "", + }) + } + // loop over fields + for _, thisField := range theseFields { + paramType := p.parseParameterType(field, pkg) + if paramType.IsStruct { + _, ok := pkg.StructCache[paramType.Name] + if !ok { + p.getStructDef(paramType.Name, pkg) + } + if paramType.Package == "" { + paramType.Package = pkg.Path + } + } + thisField.Type = paramType + result = append(result, thisField) + } + } + return result +} + +func (p *Project) getParsedPackageFromName(packageName string, currentPackage *ParsedPackage) (*ParsedPackage, error) { + for _, file := range currentPackage.Pkg.Files { + for _, imp := range file.Imports { + path, err := strconv.Unquote(imp.Path.Value) + if err != nil { + return nil, err + } + _, lastPathElement := filepath.Split(path) + if imp.Name != nil && imp.Name.Name == packageName || lastPathElement == packageName { + // Get the directory for the package + dir, err := getPackageDir(path) + if err != nil { + return nil, err + } + pkg, err := p.getPackageFromPath(dir, path) + if err != nil { + return nil, err + } + result := &ParsedPackage{ + Pkg: pkg, + Name: packageName, + Path: path, + Dir: dir, + StructCache: make(map[string]*StructDef), + } + p.packageCache[path] = result + return result, nil + } + } + } + return nil, fmt.Errorf("package %s not found in %s", packageName, currentPackage.Name) +} + +func getPackageDir(importPath string) (string, error) { + pkg, err := build.Import(importPath, "", build.FindOnly) + if err != nil { + return "", err + } + return pkg.Dir, nil +} + +func (p *Project) getPackageFromPath(packagedir string, packagepath string) (*ast.Package, error) { + impPkg, err := parser.ParseDir(token.NewFileSet(), packagedir, nil, parser.AllErrors) + if err != nil { + return nil, err + } + for impName, impPkg := range impPkg { + if impName == "main" { + continue + } + return impPkg, nil + } + return nil, fmt.Errorf("package not found in imported package %s", packagepath) +} + +func (p *Project) anonymousStructID() string { + p.anonymousStructIDCounter++ + return fmt.Sprintf("anon%d", p.anonymousStructIDCounter) +} + +func (p *Project) parseBoundExpression(elt ast.Expr, pkg *ParsedPackage) (bool, bool) { + + switch t := elt.(type) { + case *ast.UnaryExpr: + return p.parseBoundUnaryExpression(t, pkg) + case *ast.Ident: + return p.parseBoundIdent(t, pkg) + case *ast.CallExpr: + return p.parseBoundCallExpression(t, pkg) + default: + println("unhandled expression type", reflect.TypeOf(t).String()) + } + + return false, false +} + +func (p *Project) parseBoundIdent(ident *ast.Ident, pkg *ParsedPackage) (bool, bool) { + if ident.Obj == nil { + return false, true + } + switch t := ident.Obj.Decl.(type) { + //case *ast.StructType: + // return p.parseBoundStruct(t, pkg) + case *ast.TypeSpec: + return p.parseBoundTypeSpec(t, pkg) + case *ast.AssignStmt: + return p.parseBoundAssignment(t, pkg) + default: + println("unhandled ident type", reflect.TypeOf(t).String()) + } + return false, false +} + +func (p *Project) parseBoundAssignment(assign *ast.AssignStmt, pkg *ParsedPackage) (bool, bool) { + return p.parseBoundExpression(assign.Rhs[0], pkg) +} + +func (p *Project) parseBoundCompositeLit(lit *ast.CompositeLit, pkg *ParsedPackage) (bool, bool) { + + switch t := lit.Type.(type) { + case *ast.StructType: + //return p.parseBoundStructType(t, pkg) + return false, true + case *ast.Ident: + err := p.parseBoundStructMethods(t.Name, pkg) + if err != nil { + return true, false + } + return false, true + case *ast.SelectorExpr: + return p.parseBoundSelectorExpression(t, pkg) + } + + return false, true +} + +func (p *Project) parseBoundSelectorExpression(selector *ast.SelectorExpr, pkg *ParsedPackage) (bool, bool) { + + switch t := selector.X.(type) { + case *ast.Ident: + // Look up the package + var parsedPackage *ParsedPackage + parsedPackage, err := p.getParsedPackageFromName(t.Name, pkg) + if err != nil { + return true, false + } + err = p.parseBoundStructMethods(selector.Sel.Name, parsedPackage) + if err != nil { + return true, false + } + return false, true + default: + println("unhandled selector type", reflect.TypeOf(t).String()) + } + return false, true +} + +func (p *Project) parseBoundCallExpression(callExpr *ast.CallExpr, pkg *ParsedPackage) (bool, bool) { + + // Check if this call returns a struct pointer + switch t := callExpr.Fun.(type) { + case *ast.Ident: + if t.Obj == nil { + return false, true + } + switch t := t.Obj.Decl.(type) { + case *ast.FuncDecl: + return p.parseBoundFuncDecl(t, pkg) + } + + case *ast.SelectorExpr: + // Get package for selector + var parsedPackage *ParsedPackage + parsedPackage, err := p.getParsedPackageFromName(t.X.(*ast.Ident).Name, pkg) + if err != nil { + return true, false + } + // Get function from package + var extFundDecl *ast.FuncDecl + extFundDecl, err = p.getFunctionFromName(t.Sel.Name, parsedPackage) + if err != nil { + return true, false + } + return p.parseBoundFuncDecl(extFundDecl, parsedPackage) + default: + println("unhandled call type", reflect.TypeOf(t).String()) + } + + return false, true +} + +func (p *Project) parseBoundFuncDecl(t *ast.FuncDecl, pkg *ParsedPackage) (bool, bool) { + if t.Type.Results == nil { + return false, true + } + if len(t.Type.Results.List) != 1 { + return false, true + } + switch t := t.Type.Results.List[0].Type.(type) { + case *ast.StarExpr: + return p.parseBoundExpression(t.X, pkg) + default: + println("Unhandled funcdecl type", reflect.TypeOf(t).String()) + } + return false, false +} + +func (p *Project) parseBoundTypeSpec(typeSpec *ast.TypeSpec, pkg *ParsedPackage) (bool, bool) { + switch t := typeSpec.Type.(type) { + case *ast.StructType: + err := p.parseBoundStructMethods(typeSpec.Name.Name, pkg) + if err != nil { + return true, false + } + default: + println("unhandled type spec type", reflect.TypeOf(t).String()) + } + return false, true +} + +func (p *Project) getFunctionFromName(name string, parsedPackage *ParsedPackage) (*ast.FuncDecl, error) { + for _, f := range parsedPackage.Pkg.Files { + for _, decl := range f.Decls { + switch t := decl.(type) { + case *ast.FuncDecl: + if t.Name.Name == name { + return t, nil + } + } + } + } + return nil, fmt.Errorf("function not found") +} + +func getTypeString(expr ast.Expr) string { + switch t := expr.(type) { + case *ast.Ident: + return t.Name + case *ast.StarExpr: + return getTypeString(t.X) + case *ast.ArrayType: + return getTypeString(t.Elt) + case *ast.MapType: + return "map" + case *ast.SelectorExpr: + return getTypeString(t.Sel) + default: + return "" + } +} + +func isStructType(expr ast.Expr) bool { + switch e := expr.(type) { + case *ast.StructType: + return true + case *ast.StarExpr: + return isStructType(e.X) + case *ast.SelectorExpr: + return isStructType(e.Sel) + case *ast.ArrayType: + return isStructType(e.Elt) + case *ast.SliceExpr: + return isStructType(e.X) + case *ast.Ident: + return e.Obj != nil && e.Obj.Kind == ast.Typ + default: + return false + } +} + +func getDirectoryForPackage(pkg *ast.Package) string { + for filename := range pkg.Files { + path := filepath.Dir(filename) + abs, err := filepath.Abs(path) + if err != nil { + panic(err) + } + return abs + } + return "" +} diff --git a/v3/internal/parser/parser_test.go b/v3/internal/parser/parser_test.go new file mode 100644 index 000000000..8722836d4 --- /dev/null +++ b/v3/internal/parser/parser_test.go @@ -0,0 +1,2577 @@ +package parser + +import ( + "testing" + + "github.com/google/go-cmp/cmp" +) + +func TestParseDirectory(t *testing.T) { + tests := []struct { + name string + dir string + wantBoundMethods map[string]map[string][]*BoundMethod + wantModels map[string]map[string]*StructDef + wantErr bool + }{ + { + name: "should find single bound service", + dir: "testdata/struct_literal_single", + //wantModels: []string{"main.GreetService"}, + wantBoundMethods: map[string]map[string][]*BoundMethod{ + "main": { + "GreetService": { + { + Name: "Greet", + DocComment: "Greet someone\n", + Inputs: []*Parameter{ + { + Name: "name", + Type: &ParameterType{ + Name: "string", + }, + }, + }, + Outputs: []*Parameter{ + { + Name: "", + Type: &ParameterType{ + Name: "string", + }, + }, + }, + ID: 1411160069, + }, + { + Name: "NoInputsStringOut", + DocComment: "", + Inputs: nil, + Outputs: []*Parameter{ + { + Name: "", + Type: &ParameterType{ + Name: "string", + }, + }, + }, + ID: 1075577233, + }, + { + Name: "StringArrayInputStringOut", + Inputs: []*Parameter{ + { + Name: "in", + Type: &ParameterType{ + Name: "string", + IsSlice: true, + }, + }, + }, + Outputs: []*Parameter{ + { + Type: &ParameterType{ + Name: "string", + }, + }, + }, + ID: 1091960237, + }, + { + Name: "StringArrayInputStringArrayOut", + Inputs: []*Parameter{ + { + Name: "in", + Type: &ParameterType{ + Name: "string", + IsSlice: true, + }, + }, + }, + Outputs: []*Parameter{ + { + Type: &ParameterType{ + Name: "string", + IsSlice: true, + }, + }, + }, + ID: 383995060, + }, + { + Name: "StringArrayInputNamedOutput", + Inputs: []*Parameter{ + { + Name: "in", + Type: &ParameterType{ + Name: "string", + IsSlice: true, + }, + }, + }, + Outputs: []*Parameter{ + { + Name: "output", + Type: &ParameterType{ + Name: "string", + IsSlice: true, + }, + }, + }, + ID: 3678582682, + }, + { + Name: "StringArrayInputNamedOutputs", + Inputs: []*Parameter{ + { + Name: "in", + Type: &ParameterType{ + Name: "string", + IsSlice: true, + }, + }, + }, + Outputs: []*Parameter{ + { + Name: "output", + Type: &ParameterType{ + Name: "string", + IsSlice: true, + }, + }, + { + Name: "err", + Type: &ParameterType{ + Name: "error", + }, + }, + }, + ID: 319259595, + }, + { + Name: "IntPointerInputNamedOutputs", + Inputs: []*Parameter{ + { + Name: "in", + Type: &ParameterType{ + Name: "int", + IsPointer: true, + }, + }, + }, + Outputs: []*Parameter{ + { + Name: "output", + Type: &ParameterType{ + Name: "int", + IsPointer: true, + }, + }, + { + Name: "err", + Type: &ParameterType{ + Name: "error", + }}, + }, + ID: 2718999663, + }, + { + Name: "UIntPointerInAndOutput", + Inputs: []*Parameter{ + { + Name: "in", + Type: &ParameterType{ + Name: "uint", + IsPointer: true, + }, + }, + }, + Outputs: []*Parameter{ + { + Type: &ParameterType{ + Name: "uint", + IsPointer: true, + }, + }, + }, + ID: 1367187362, + }, + { + Name: "UInt8PointerInAndOutput", + Inputs: []*Parameter{ + { + Name: "in", + Type: &ParameterType{ + Name: "uint8", + IsPointer: true, + }, + }, + }, + Outputs: []*Parameter{ + { + Type: &ParameterType{ + Name: "uint8", + IsPointer: true, + }, + }, + }, + ID: 518250834, + }, + { + Name: "UInt16PointerInAndOutput", + Inputs: []*Parameter{ + { + Name: "in", + Type: &ParameterType{ + Name: "uint16", + IsPointer: true, + }, + }, + }, + Outputs: []*Parameter{ + { + Type: &ParameterType{ + Name: "uint16", + IsPointer: true, + }, + }, + }, + ID: 1236957573, + }, + { + Name: "UInt32PointerInAndOutput", + Inputs: []*Parameter{ + { + Name: "in", + Type: &ParameterType{ + Name: "uint32", + IsPointer: true, + }, + }, + }, + Outputs: []*Parameter{ + { + Type: &ParameterType{ + Name: "uint32", + IsPointer: true, + }, + }, + }, + ID: 1739300671, + }, + { + Name: "UInt64PointerInAndOutput", + Inputs: []*Parameter{ + { + Name: "in", + Type: &ParameterType{ + Name: "uint64", + IsPointer: true, + }, + }, + }, + Outputs: []*Parameter{ + { + Type: &ParameterType{ + Name: "uint64", + IsPointer: true, + }, + }, + }, + ID: 1403757716, + }, + { + Name: "IntPointerInAndOutput", + Inputs: []*Parameter{ + { + Name: "in", + Type: &ParameterType{ + Name: "int", + IsPointer: true, + }, + }, + }, + Outputs: []*Parameter{ + { + Type: &ParameterType{ + Name: "int", + IsPointer: true, + }, + }, + }, + ID: 1066151743, + }, + { + Name: "Int8PointerInAndOutput", + Inputs: []*Parameter{ + { + Name: "in", + Type: &ParameterType{ + Name: "int8", + IsPointer: true, + }, + }, + }, + Outputs: []*Parameter{ + { + Type: &ParameterType{ + Name: "int8", + IsPointer: true, + }, + }, + }, + ID: 2189402897, + }, + { + Name: "Int16PointerInAndOutput", + Inputs: []*Parameter{ + { + Name: "in", + Type: &ParameterType{ + Name: "int16", + IsPointer: true, + }, + }, + }, + Outputs: []*Parameter{ + { + Type: &ParameterType{ + Name: "int16", + IsPointer: true, + }, + }, + }, + ID: 1754277916, + }, + { + Name: "Int32PointerInAndOutput", + Inputs: []*Parameter{ + { + Name: "in", + Type: &ParameterType{ + Name: "int32", + IsPointer: true, + }, + }, + }, + Outputs: []*Parameter{ + { + Type: &ParameterType{ + Name: "int32", + IsPointer: true, + }, + }, + }, + ID: 4251088558, + }, + { + Name: "Int64PointerInAndOutput", + Inputs: []*Parameter{ + { + Name: "in", + Type: &ParameterType{ + Name: "int64", + IsPointer: true, + }, + }, + }, + Outputs: []*Parameter{ + { + Type: &ParameterType{ + Name: "int64", + IsPointer: true, + }, + }, + }, + ID: 2205561041, + }, + { + Name: "IntInIntOut", + Inputs: []*Parameter{ + { + Name: "in", + Type: &ParameterType{ + Name: "int", + }, + }, + }, + Outputs: []*Parameter{ + { + Type: &ParameterType{ + Name: "int", + }, + }, + }, + ID: 642881729, + }, + { + Name: "Int8InIntOut", + Inputs: []*Parameter{ + { + Name: "in", + Type: &ParameterType{ + Name: "int8", + }, + }, + }, + Outputs: []*Parameter{ + { + Type: &ParameterType{ + Name: "int8", + }, + }, + }, + ID: 572240879, + }, + { + Name: "Int16InIntOut", + Inputs: []*Parameter{ + { + Name: "in", + Type: &ParameterType{ + Name: "int16", + }, + }, + }, + Outputs: []*Parameter{ + { + Type: &ParameterType{ + Name: "int16", + }, + }, + }, + ID: 3306292566, + }, + { + Name: "Int32InIntOut", + Inputs: []*Parameter{ + { + Name: "in", + Type: &ParameterType{ + Name: "int32", + }, + }, + }, + Outputs: []*Parameter{ + { + Type: &ParameterType{ + Name: "int32", + }, + }, + }, + ID: 1909469092, + }, + { + Name: "Int64InIntOut", + Inputs: []*Parameter{ + { + Name: "in", + Type: &ParameterType{ + Name: "int64", + }, + }, + }, + Outputs: []*Parameter{ + { + Type: &ParameterType{ + Name: "int64", + }, + }, + }, + ID: 1343888303, + }, + { + Name: "UIntInUIntOut", + Inputs: []*Parameter{ + { + Name: "in", + Type: &ParameterType{ + Name: "uint", + }, + }, + }, + Outputs: []*Parameter{ + { + Type: &ParameterType{ + Name: "uint", + }, + }, + }, + ID: 2836661285, + }, + { + Name: "UInt8InUIntOut", + Inputs: []*Parameter{ + { + Name: "in", + Type: &ParameterType{ + Name: "uint8", + }, + }, + }, + Outputs: []*Parameter{ + { + Type: &ParameterType{ + Name: "uint8", + }, + }, + }, + ID: 2988345717, + }, + { + Name: "UInt16InUIntOut", + Inputs: []*Parameter{ + { + Name: "in", + Type: &ParameterType{ + Name: "uint16", + }, + }, + }, + Outputs: []*Parameter{ + { + Type: &ParameterType{ + Name: "uint16", + }, + }, + }, + ID: 3401034892, + }, + { + Name: "UInt32InUIntOut", + Inputs: []*Parameter{ + { + Name: "in", + Type: &ParameterType{ + Name: "uint32", + }, + }, + }, + Outputs: []*Parameter{ + { + Type: &ParameterType{ + Name: "uint32", + }, + }, + }, + ID: 1160383782, + }, + { + Name: "UInt64InUIntOut", + Inputs: []*Parameter{ + { + Name: "in", + Type: &ParameterType{ + Name: "uint64", + }, + }, + }, + Outputs: []*Parameter{ + { + Type: &ParameterType{ + Name: "uint64", + }, + }, + }, + ID: 793803239, + }, + { + Name: "Float32InFloat32Out", + Inputs: []*Parameter{ + { + Name: "in", + Type: &ParameterType{ + Name: "float32", + }, + }, + }, + Outputs: []*Parameter{ + { + Type: &ParameterType{ + Name: "float32", + }, + }, + }, + ID: 3132595881, + }, + { + Name: "Float64InFloat64Out", + Inputs: []*Parameter{ + { + Name: "in", + Type: &ParameterType{ + Name: "float64", + }, + }, + }, + Outputs: []*Parameter{ + { + Type: &ParameterType{ + Name: "float64", + }, + }, + }, + ID: 2182412247, + }, + { + Name: "PointerFloat32InFloat32Out", + Inputs: []*Parameter{ + { + Name: "in", + Type: &ParameterType{ + Name: "float32", + IsPointer: true, + }, + }, + }, + Outputs: []*Parameter{ + { + Type: &ParameterType{ + Name: "float32", + IsPointer: true, + }, + }, + }, + ID: 224675106, + }, + { + Name: "PointerFloat64InFloat64Out", + Inputs: []*Parameter{ + { + Name: "in", + Type: &ParameterType{ + Name: "float64", + IsPointer: true, + }, + }, + }, + Outputs: []*Parameter{ + { + Type: &ParameterType{ + Name: "float64", + IsPointer: true, + }, + }, + }, + ID: 2124953624, + }, + { + Name: "BoolInBoolOut", + Inputs: []*Parameter{ + { + Name: "in", + Type: &ParameterType{ + Name: "bool", + }, + }, + }, + Outputs: []*Parameter{ + { + Type: &ParameterType{ + Name: "bool", + }, + }, + }, + ID: 2424639793, + }, + { + Name: "PointerBoolInBoolOut", + Inputs: []*Parameter{ + { + Name: "in", + Type: &ParameterType{ + Name: "bool", + IsPointer: true, + }, + }, + }, + Outputs: []*Parameter{ + { + Type: &ParameterType{ + Name: "bool", + IsPointer: true, + }, + }, + }, + ID: 3589606958, + }, + { + Name: "PointerStringInStringOut", + Inputs: []*Parameter{ + { + Name: "in", + Type: &ParameterType{ + Name: "string", + IsPointer: true, + }, + }, + }, + Outputs: []*Parameter{ + { + Type: &ParameterType{ + Name: "string", + IsPointer: true, + }, + }, + }, + ID: 229603958, + }, + { + Name: "StructPointerInputErrorOutput", + Inputs: []*Parameter{ + { + Name: "in", + Type: &ParameterType{ + Name: "Person", + IsPointer: true, + IsStruct: true, + Package: "main", + }, + }, + }, + Outputs: []*Parameter{ + { + Type: &ParameterType{ + Name: "error", + }, + }, + }, + ID: 2447692557, + }, + { + Name: "StructInputStructOutput", + Inputs: []*Parameter{ + { + Name: "in", + Type: &ParameterType{ + Name: "Person", + IsStruct: true, + Package: "main", + }, + }, + }, + Outputs: []*Parameter{ + { + Type: &ParameterType{ + Name: "Person", + IsStruct: true, + Package: "main", + }, + }, + }, + ID: 3835643147, + }, + { + Name: "StructPointerInputStructPointerOutput", + Inputs: []*Parameter{ + { + Name: "in", + Type: &ParameterType{ + Name: "Person", + IsPointer: true, + IsStruct: true, + Package: "main", + }, + }, + }, + Outputs: []*Parameter{ + { + Type: &ParameterType{ + Name: "Person", + IsPointer: true, + IsStruct: true, + Package: "main", + }, + }, + }, + ID: 2943477349, + }, + { + Name: "MapIntInt", + Inputs: []*Parameter{ + { + Name: "in", + Type: &ParameterType{ + Name: "map", + MapKey: &ParameterType{ + Name: "int", + }, + MapValue: &ParameterType{ + Name: "int", + }, + }, + }, + }, + ID: 2386486356, + }, + { + Name: "PointerMapIntInt", + Inputs: []*Parameter{ + { + Name: "in", + Type: &ParameterType{ + Name: "map", + IsPointer: true, + MapKey: &ParameterType{ + Name: "int", + }, + MapValue: &ParameterType{ + Name: "int", + }, + }, + }, + }, + ID: 3516977899, + }, + { + Name: "MapIntPointerInt", + Inputs: []*Parameter{ + { + Name: "in", + Type: &ParameterType{ + Name: "map", + MapKey: &ParameterType{ + Name: "int", + IsPointer: true, + }, + MapValue: &ParameterType{ + Name: "int", + }, + }, + }, + }, + ID: 550413585, + }, + { + Name: "MapIntSliceInt", + Inputs: []*Parameter{ + { + Name: "in", + Type: &ParameterType{ + Name: "map", + MapKey: &ParameterType{ + Name: "int", + }, + MapValue: &ParameterType{ + Name: "int", + IsSlice: true, + }, + }, + }, + }, + ID: 2900172572, + }, + { + Name: "MapIntSliceIntInMapIntSliceIntOut", + Inputs: []*Parameter{ + { + Name: "in", + Type: &ParameterType{ + Name: "map", + MapKey: &ParameterType{ + Name: "int", + }, + MapValue: &ParameterType{ + Name: "int", + IsSlice: true, + }, + }, + }, + }, + Outputs: []*Parameter{ + { + Name: "out", + Type: &ParameterType{ + Name: "map", + MapKey: &ParameterType{ + Name: "int", + }, + MapValue: &ParameterType{ + Name: "int", + IsSlice: true, + }, + }, + }, + }, + ID: 881980169, + }, + { + Name: "ArrayInt", + Inputs: []*Parameter{ + { + Name: "in", + Type: &ParameterType{ + Name: "int", + IsSlice: true, + }, + }, + }, + ID: 3862002418, + }, + }, + }, + }, + wantModels: map[string]map[string]*StructDef{ + "main": { + "Person": { + Name: "Person", + Fields: []*Field{ + { + Name: "Name", + Type: &ParameterType{ + Name: "string", + }, + }, + { + Name: "Parent", + Type: &ParameterType{ + Name: "Person", + IsStruct: true, + IsPointer: true, + Package: "main", + }, + }, + { + Name: "Details", + Type: &ParameterType{ + Name: "anon1", + IsStruct: true, + Package: "main", + }, + }, + }, + }, + "anon1": { + Name: "anon1", + Fields: []*Field{ + { + Name: "Age", + Type: &ParameterType{ + Name: "int", + }, + }, + { + Name: "Address", + Type: &ParameterType{ + Name: "anon2", + IsStruct: true, + Package: "main", + }, + }, + }, + }, + "anon2": { + Name: "anon2", + Fields: []*Field{ + { + Name: "Street", + Type: &ParameterType{ + Name: "string", + }, + }, + }, + }, + }, + }, + wantErr: false, + }, + { + name: "should find single bound service (non-pointer receiver)", + dir: "testdata/struct_literal_non_pointer_single", + wantBoundMethods: map[string]map[string][]*BoundMethod{ + "main": { + "GreetService": { + { + Name: "Greet", + DocComment: "Greet someone\n", + Inputs: []*Parameter{ + { + Name: "name", + Type: &ParameterType{ + Name: "string", + }, + }, + }, + Outputs: []*Parameter{ + { + Name: "", + Type: &ParameterType{ + Name: "string", + }, + }, + }, + ID: 1411160069, + }, + { + Name: "NoInputsStringOut", + DocComment: "", + Inputs: nil, + Outputs: []*Parameter{ + { + Name: "", + Type: &ParameterType{ + Name: "string", + }, + }, + }, + ID: 1075577233, + }, + { + Name: "StringArrayInputStringOut", + Inputs: []*Parameter{ + { + Name: "in", + Type: &ParameterType{ + Name: "string", + IsSlice: true, + }, + }, + }, + Outputs: []*Parameter{ + { + Type: &ParameterType{ + Name: "string", + }, + }, + }, + ID: 1091960237, + }, + { + Name: "StringArrayInputStringArrayOut", + Inputs: []*Parameter{ + { + Name: "in", + Type: &ParameterType{ + Name: "string", + IsSlice: true, + }, + }, + }, + Outputs: []*Parameter{ + { + Type: &ParameterType{ + Name: "string", + IsSlice: true, + }, + }, + }, + ID: 383995060, + }, + { + Name: "StringArrayInputNamedOutput", + Inputs: []*Parameter{ + { + Name: "in", + Type: &ParameterType{ + Name: "string", + IsSlice: true, + }, + }, + }, + Outputs: []*Parameter{ + { + Name: "output", + Type: &ParameterType{ + Name: "string", + IsSlice: true, + }, + }, + }, + ID: 3678582682, + }, + { + Name: "StringArrayInputNamedOutputs", + Inputs: []*Parameter{ + { + Name: "in", + Type: &ParameterType{ + Name: "string", + IsSlice: true, + }, + }, + }, + Outputs: []*Parameter{ + { + Name: "output", + Type: &ParameterType{ + Name: "string", + IsSlice: true, + }, + }, + { + Name: "err", + Type: &ParameterType{ + Name: "error", + }, + }, + }, + ID: 319259595, + }, + { + Name: "IntPointerInputNamedOutputs", + Inputs: []*Parameter{ + { + Name: "in", + Type: &ParameterType{ + Name: "int", + IsPointer: true, + }, + }, + }, + Outputs: []*Parameter{ + { + Name: "output", + Type: &ParameterType{ + Name: "int", + IsPointer: true, + }, + }, + { + Name: "err", + Type: &ParameterType{ + Name: "error", + }}, + }, + ID: 2718999663, + }, + { + Name: "UIntPointerInAndOutput", + Inputs: []*Parameter{ + { + Name: "in", + Type: &ParameterType{ + Name: "uint", + IsPointer: true, + }, + }, + }, + Outputs: []*Parameter{ + { + Type: &ParameterType{ + Name: "uint", + IsPointer: true, + }, + }, + }, + ID: 1367187362, + }, + { + Name: "UInt8PointerInAndOutput", + Inputs: []*Parameter{ + { + Name: "in", + Type: &ParameterType{ + Name: "uint8", + IsPointer: true, + }, + }, + }, + Outputs: []*Parameter{ + { + Type: &ParameterType{ + Name: "uint8", + IsPointer: true, + }, + }, + }, + ID: 518250834, + }, + { + Name: "UInt16PointerInAndOutput", + Inputs: []*Parameter{ + { + Name: "in", + Type: &ParameterType{ + Name: "uint16", + IsPointer: true, + }, + }, + }, + Outputs: []*Parameter{ + { + Type: &ParameterType{ + Name: "uint16", + IsPointer: true, + }, + }, + }, + ID: 1236957573, + }, + { + Name: "UInt32PointerInAndOutput", + Inputs: []*Parameter{ + { + Name: "in", + Type: &ParameterType{ + Name: "uint32", + IsPointer: true, + }, + }, + }, + Outputs: []*Parameter{ + { + Type: &ParameterType{ + Name: "uint32", + IsPointer: true, + }, + }, + }, + ID: 1739300671, + }, + { + Name: "UInt64PointerInAndOutput", + Inputs: []*Parameter{ + { + Name: "in", + Type: &ParameterType{ + Name: "uint64", + IsPointer: true, + }, + }, + }, + Outputs: []*Parameter{ + { + Type: &ParameterType{ + Name: "uint64", + IsPointer: true, + }, + }, + }, + ID: 1403757716, + }, + { + Name: "IntPointerInAndOutput", + Inputs: []*Parameter{ + { + Name: "in", + Type: &ParameterType{ + Name: "int", + IsPointer: true, + }, + }, + }, + Outputs: []*Parameter{ + { + Type: &ParameterType{ + Name: "int", + IsPointer: true, + }, + }, + }, + ID: 1066151743, + }, + { + Name: "Int8PointerInAndOutput", + Inputs: []*Parameter{ + { + Name: "in", + Type: &ParameterType{ + Name: "int8", + IsPointer: true, + }, + }, + }, + Outputs: []*Parameter{ + { + Type: &ParameterType{ + Name: "int8", + IsPointer: true, + }, + }, + }, + ID: 2189402897, + }, + { + Name: "Int16PointerInAndOutput", + Inputs: []*Parameter{ + { + Name: "in", + Type: &ParameterType{ + Name: "int16", + IsPointer: true, + }, + }, + }, + Outputs: []*Parameter{ + { + Type: &ParameterType{ + Name: "int16", + IsPointer: true, + }, + }, + }, + ID: 1754277916, + }, + { + Name: "Int32PointerInAndOutput", + Inputs: []*Parameter{ + { + Name: "in", + Type: &ParameterType{ + Name: "int32", + IsPointer: true, + }, + }, + }, + Outputs: []*Parameter{ + { + Type: &ParameterType{ + Name: "int32", + IsPointer: true, + }, + }, + }, + ID: 4251088558, + }, + { + Name: "Int64PointerInAndOutput", + Inputs: []*Parameter{ + { + Name: "in", + Type: &ParameterType{ + Name: "int64", + IsPointer: true, + }, + }, + }, + Outputs: []*Parameter{ + { + Type: &ParameterType{ + Name: "int64", + IsPointer: true, + }, + }, + }, + ID: 2205561041, + }, + { + Name: "IntInIntOut", + Inputs: []*Parameter{ + { + Name: "in", + Type: &ParameterType{ + Name: "int", + }, + }, + }, + Outputs: []*Parameter{ + { + Type: &ParameterType{ + Name: "int", + }, + }, + }, + ID: 642881729, + }, + { + Name: "Int8InIntOut", + Inputs: []*Parameter{ + { + Name: "in", + Type: &ParameterType{ + Name: "int8", + }, + }, + }, + Outputs: []*Parameter{ + { + Type: &ParameterType{ + Name: "int8", + }, + }, + }, + ID: 572240879, + }, + { + Name: "Int16InIntOut", + Inputs: []*Parameter{ + { + Name: "in", + Type: &ParameterType{ + Name: "int16", + }, + }, + }, + Outputs: []*Parameter{ + { + Type: &ParameterType{ + Name: "int16", + }, + }, + }, + ID: 3306292566, + }, + { + Name: "Int32InIntOut", + Inputs: []*Parameter{ + { + Name: "in", + Type: &ParameterType{ + Name: "int32", + }, + }, + }, + Outputs: []*Parameter{ + { + Type: &ParameterType{ + Name: "int32", + }, + }, + }, + ID: 1909469092, + }, + { + Name: "Int64InIntOut", + Inputs: []*Parameter{ + { + Name: "in", + Type: &ParameterType{ + Name: "int64", + }, + }, + }, + Outputs: []*Parameter{ + { + Type: &ParameterType{ + Name: "int64", + }, + }, + }, + ID: 1343888303, + }, + { + Name: "UIntInUIntOut", + Inputs: []*Parameter{ + { + Name: "in", + Type: &ParameterType{ + Name: "uint", + }, + }, + }, + Outputs: []*Parameter{ + { + Type: &ParameterType{ + Name: "uint", + }, + }, + }, + ID: 2836661285, + }, + { + Name: "UInt8InUIntOut", + Inputs: []*Parameter{ + { + Name: "in", + Type: &ParameterType{ + Name: "uint8", + }, + }, + }, + Outputs: []*Parameter{ + { + Type: &ParameterType{ + Name: "uint8", + }, + }, + }, + ID: 2988345717, + }, + { + Name: "UInt16InUIntOut", + Inputs: []*Parameter{ + { + Name: "in", + Type: &ParameterType{ + Name: "uint16", + }, + }, + }, + Outputs: []*Parameter{ + { + Type: &ParameterType{ + Name: "uint16", + }, + }, + }, + ID: 3401034892, + }, + { + Name: "UInt32InUIntOut", + Inputs: []*Parameter{ + { + Name: "in", + Type: &ParameterType{ + Name: "uint32", + }, + }, + }, + Outputs: []*Parameter{ + { + Type: &ParameterType{ + Name: "uint32", + }, + }, + }, + ID: 1160383782, + }, + { + Name: "UInt64InUIntOut", + Inputs: []*Parameter{ + { + Name: "in", + Type: &ParameterType{ + Name: "uint64", + }, + }, + }, + Outputs: []*Parameter{ + { + Type: &ParameterType{ + Name: "uint64", + }, + }, + }, + ID: 793803239, + }, + { + Name: "Float32InFloat32Out", + Inputs: []*Parameter{ + { + Name: "in", + Type: &ParameterType{ + Name: "float32", + }, + }, + }, + Outputs: []*Parameter{ + { + Type: &ParameterType{ + Name: "float32", + }, + }, + }, + ID: 3132595881, + }, + { + Name: "Float64InFloat64Out", + Inputs: []*Parameter{ + { + Name: "in", + Type: &ParameterType{ + Name: "float64", + }, + }, + }, + Outputs: []*Parameter{ + { + Type: &ParameterType{ + Name: "float64", + }, + }, + }, + ID: 2182412247, + }, + { + Name: "PointerFloat32InFloat32Out", + Inputs: []*Parameter{ + { + Name: "in", + Type: &ParameterType{ + Name: "float32", + IsPointer: true, + }, + }, + }, + Outputs: []*Parameter{ + { + Type: &ParameterType{ + Name: "float32", + IsPointer: true, + }, + }, + }, + ID: 224675106, + }, + { + Name: "PointerFloat64InFloat64Out", + Inputs: []*Parameter{ + { + Name: "in", + Type: &ParameterType{ + Name: "float64", + IsPointer: true, + }, + }, + }, + Outputs: []*Parameter{ + { + Type: &ParameterType{ + Name: "float64", + IsPointer: true, + }, + }, + }, + ID: 2124953624, + }, + { + Name: "BoolInBoolOut", + Inputs: []*Parameter{ + { + Name: "in", + Type: &ParameterType{ + Name: "bool", + }, + }, + }, + Outputs: []*Parameter{ + { + Type: &ParameterType{ + Name: "bool", + }, + }, + }, + ID: 2424639793, + }, + { + Name: "PointerBoolInBoolOut", + Inputs: []*Parameter{ + { + Name: "in", + Type: &ParameterType{ + Name: "bool", + IsPointer: true, + }, + }, + }, + Outputs: []*Parameter{ + { + Type: &ParameterType{ + Name: "bool", + IsPointer: true, + }, + }, + }, + ID: 3589606958, + }, + { + Name: "PointerStringInStringOut", + Inputs: []*Parameter{ + { + Name: "in", + Type: &ParameterType{ + Name: "string", + IsPointer: true, + }, + }, + }, + Outputs: []*Parameter{ + { + Type: &ParameterType{ + Name: "string", + IsPointer: true, + }, + }, + }, + ID: 229603958, + }, + { + Name: "StructPointerInputErrorOutput", + Inputs: []*Parameter{ + { + Name: "in", + Type: &ParameterType{ + Name: "Person", + IsPointer: true, + IsStruct: true, + Package: "main", + }, + }, + }, + Outputs: []*Parameter{ + { + Type: &ParameterType{ + Name: "error", + }, + }, + }, + ID: 2447692557, + }, + { + Name: "StructInputStructOutput", + Inputs: []*Parameter{ + { + Name: "in", + Type: &ParameterType{ + Name: "Person", + IsStruct: true, + Package: "main", + }, + }, + }, + Outputs: []*Parameter{ + { + Type: &ParameterType{ + Name: "Person", + IsStruct: true, + Package: "main", + }, + }, + }, + ID: 3835643147, + }, + { + Name: "StructPointerInputStructPointerOutput", + Inputs: []*Parameter{ + { + Name: "in", + Type: &ParameterType{ + Name: "Person", + IsPointer: true, + IsStruct: true, + Package: "main", + }, + }, + }, + Outputs: []*Parameter{ + { + Type: &ParameterType{ + Name: "Person", + IsPointer: true, + IsStruct: true, + Package: "main", + }, + }, + }, + ID: 2943477349, + }, + { + Name: "MapIntInt", + Inputs: []*Parameter{ + { + Name: "in", + Type: &ParameterType{ + Name: "map", + MapKey: &ParameterType{ + Name: "int", + }, + MapValue: &ParameterType{ + Name: "int", + }, + }, + }, + }, + ID: 2386486356, + }, + { + Name: "PointerMapIntInt", + Inputs: []*Parameter{ + { + Name: "in", + Type: &ParameterType{ + Name: "map", + IsPointer: true, + MapKey: &ParameterType{ + Name: "int", + }, + MapValue: &ParameterType{ + Name: "int", + }, + }, + }, + }, + ID: 3516977899, + }, + { + Name: "MapIntPointerInt", + Inputs: []*Parameter{ + { + Name: "in", + Type: &ParameterType{ + Name: "map", + MapKey: &ParameterType{ + Name: "int", + IsPointer: true, + }, + MapValue: &ParameterType{ + Name: "int", + }, + }, + }, + }, + ID: 550413585, + }, + { + Name: "MapIntSliceInt", + Inputs: []*Parameter{ + { + Name: "in", + Type: &ParameterType{ + Name: "map", + MapKey: &ParameterType{ + Name: "int", + }, + MapValue: &ParameterType{ + Name: "int", + IsSlice: true, + }, + }, + }, + }, + ID: 2900172572, + }, + { + Name: "MapIntSliceIntInMapIntSliceIntOut", + Inputs: []*Parameter{ + { + Name: "in", + Type: &ParameterType{ + Name: "map", + MapKey: &ParameterType{ + Name: "int", + }, + MapValue: &ParameterType{ + Name: "int", + IsSlice: true, + }, + }, + }, + }, + Outputs: []*Parameter{ + { + Name: "out", + Type: &ParameterType{ + Name: "map", + MapKey: &ParameterType{ + Name: "int", + }, + MapValue: &ParameterType{ + Name: "int", + IsSlice: true, + }, + }, + }, + }, + ID: 881980169, + }, + { + Name: "ArrayInt", + Inputs: []*Parameter{ + { + Name: "in", + Type: &ParameterType{ + Name: "int", + IsSlice: true, + }, + }, + }, + ID: 3862002418, + }, + }, + }, + }, + wantModels: map[string]map[string]*StructDef{ + "main": { + "Person": { + Name: "Person", + Fields: []*Field{ + { + Name: "Name", + Type: &ParameterType{ + Name: "string", + }, + }, + { + Name: "Parent", + Type: &ParameterType{ + Name: "Person", + IsStruct: true, + IsPointer: true, + Package: "main", + }, + }, + { + Name: "Details", + Type: &ParameterType{ + Name: "anon1", + IsStruct: true, + Package: "main", + }, + }, + }, + }, + "anon1": { + Name: "anon1", + Fields: []*Field{ + { + Name: "Age", + Type: &ParameterType{ + Name: "int", + }, + }, + { + Name: "Address", + Type: &ParameterType{ + Name: "anon2", + IsStruct: true, + Package: "main", + }, + }, + }, + }, + "anon2": { + Name: "anon2", + Fields: []*Field{ + { + Name: "Street", + Type: &ParameterType{ + Name: "string", + }, + }, + }, + }, + }, + }, + wantErr: false, + }, + { + name: "should find multiple bound services", + dir: "testdata/struct_literal_multiple", + wantBoundMethods: map[string]map[string][]*BoundMethod{ + "main": { + "GreetService": { + { + Name: "Greet", + DocComment: "", + Inputs: []*Parameter{ + { + Name: "name", + Type: &ParameterType{ + Name: "string", + }, + }, + }, + Outputs: []*Parameter{ + { + Name: "", + Type: &ParameterType{ + Name: "string", + }, + }, + }, + ID: 1411160069, + }, + }, + "OtherService": { + { + Name: "Hello", + ID: 4249972365, + }, + }, + }, + }, + wantErr: false, + }, + { + name: "should find multiple bound services over multiple files", + dir: "testdata/struct_literal_multiple_files", + wantBoundMethods: map[string]map[string][]*BoundMethod{ + "main": { + "GreetService": { + { + Name: "Greet", + DocComment: "", + Inputs: []*Parameter{ + { + Name: "name", + Type: &ParameterType{ + Name: "string", + }, + }, + }, + Outputs: []*Parameter{ + { + Name: "", + Type: &ParameterType{ + Name: "string", + }, + }, + }, + ID: 1411160069, + }, + }, + "OtherService": { + { + Name: "Hello", + ID: 4249972365, + }, + }, + }, + }, + wantErr: false, + }, + { + name: "should find multiple bound services over multiple packages", + dir: "testdata/struct_literal_multiple_other", + wantErr: false, + wantBoundMethods: map[string]map[string][]*BoundMethod{ + "main": { + "GreetService": { + { + Name: "Greet", + DocComment: "Greet does XYZ\n", + Inputs: []*Parameter{ + { + Name: "name", + Type: &ParameterType{ + Name: "string", + }, + }, + }, + Outputs: []*Parameter{ + { + Name: "", + Type: &ParameterType{ + Name: "string", + }, + }, + }, + ID: 1411160069, + }, + { + Name: "NewPerson", + DocComment: "NewPerson creates a new person\n", + Inputs: []*Parameter{ + { + Name: "name", + Type: &ParameterType{ + Name: "string", + }, + }, + }, + Outputs: []*Parameter{ + { + Name: "", + Type: &ParameterType{ + Name: "Person", + IsPointer: true, + IsStruct: true, + Package: "main", + }, + }, + }, + ID: 1661412647, + }, + }, + }, + "github.com/wailsapp/wails/v3/internal/parser/testdata/struct_literal_multiple_other/services": { + "OtherService": { + { + Name: "Yay", + Outputs: []*Parameter{ + { + Type: &ParameterType{ + Name: "Address", + IsStruct: true, + IsPointer: true, + Package: "github.com/wailsapp/wails/v3/internal/parser/testdata/struct_literal_multiple_other/services", + }, + }, + }, + ID: 469445984, + }, + }, + }, + }, + wantModels: map[string]map[string]*StructDef{ + "main": { + "Person": { + Name: "Person", + Fields: []*Field{ + { + Name: "Name", + Type: &ParameterType{ + Name: "string", + }, + }, + { + Name: "Address", + Type: &ParameterType{ + Name: "Address", + IsStruct: true, + IsPointer: true, + Package: "github.com/wailsapp/wails/v3/internal/parser/testdata/struct_literal_multiple_other/services", + }, + }, + }, + }, + }, + "github.com/wailsapp/wails/v3/internal/parser/testdata/struct_literal_multiple_other/services": { + "Address": { + Name: "Address", + Fields: []*Field{ + { + Name: "Street", + Type: &ParameterType{ + Name: "string", + }, + }, + { + Name: "State", + Type: &ParameterType{ + Name: "string", + }, + }, + { + Name: "Country", + Type: &ParameterType{ + Name: "string", + }, + }, + }, + }, + }, + }, + }, + { + name: "should find a bound services using a variable", + dir: "testdata/variable_single", + wantErr: false, + wantBoundMethods: map[string]map[string][]*BoundMethod{ + "main": { + "GreetService": { + { + Name: "Greet", + DocComment: "Greet someone\n", + Inputs: []*Parameter{ + { + Name: "name", + Type: &ParameterType{ + Name: "string", + }, + }, + }, + Outputs: []*Parameter{ + { + Name: "", + Type: &ParameterType{ + Name: "string", + }, + }, + }, + ID: 1411160069, + }, + }, + }, + }, + }, + { + name: "should find a bound services using a variable from function call", + dir: "testdata/variable_single_from_function", + wantErr: false, + wantBoundMethods: map[string]map[string][]*BoundMethod{ + "main": { + "GreetService": { + { + Name: "Greet", + DocComment: "Greet someone\n", + Inputs: []*Parameter{ + { + Name: "name", + Type: &ParameterType{ + Name: "string", + }, + }, + }, + Outputs: []*Parameter{ + { + Name: "", + Type: &ParameterType{ + Name: "string", + }, + }, + }, + ID: 1411160069, + }, + }, + }, + }, + }, + { + name: "should find a bound services using a variable from function call in another package", + dir: "testdata/variable_single_from_other_function", + wantErr: false, + wantBoundMethods: map[string]map[string][]*BoundMethod{ + "main": { + "GreetService": { + { + Name: "Greet", + DocComment: "Greet does XYZ\n", + Inputs: []*Parameter{ + { + Name: "name", + Type: &ParameterType{ + Name: "string", + }, + }, + }, + Outputs: []*Parameter{ + { + Name: "", + Type: &ParameterType{ + Name: "string", + }, + }, + }, + ID: 1411160069, + }, + { + Name: "NewPerson", + DocComment: "NewPerson creates a new person\n", + Inputs: []*Parameter{ + { + Name: "name", + Type: &ParameterType{ + Name: "string", + }, + }, + }, + Outputs: []*Parameter{ + { + Name: "", + Type: &ParameterType{ + Name: "Person", + IsPointer: true, + IsStruct: true, + Package: "main", + }, + }, + }, + ID: 1661412647, + }, + }, + }, + "github.com/wailsapp/wails/v3/internal/parser/testdata/variable_single_from_other_function/services": { + "OtherService": { + { + Name: "Yay", + Outputs: []*Parameter{ + { + Type: &ParameterType{ + Name: "Address", + IsStruct: true, + IsPointer: true, + Package: "github.com/wailsapp/wails/v3/internal/parser/testdata/variable_single_from_other_function/services", + }, + }, + }, + ID: 302702907, + }, + }, + }, + }, + wantModels: map[string]map[string]*StructDef{ + "main": { + "Person": { + Name: "Person", + Fields: []*Field{ + { + Name: "Name", + Type: &ParameterType{ + Name: "string", + }, + }, + { + Name: "Address", + Type: &ParameterType{ + Name: "Address", + IsStruct: true, + IsPointer: true, + Package: "github.com/wailsapp/wails/v3/internal/parser/testdata/variable_single_from_other_function/services", + }, + }, + }, + }, + }, + "github.com/wailsapp/wails/v3/internal/parser/testdata/variable_single_from_other_function/services": { + "Address": { + Name: "Address", + Fields: []*Field{ + { + Name: "Street", + Type: &ParameterType{ + Name: "string", + }, + }, + { + Name: "State", + Type: &ParameterType{ + Name: "string", + }, + }, + { + Name: "Country", + Type: &ParameterType{ + Name: "string", + }, + }, + }, + }, + }, + }, + }, + { + name: "should find a bound service returned from a function call", + dir: "testdata/function_single", + wantErr: false, + wantBoundMethods: map[string]map[string][]*BoundMethod{ + "main": { + "GreetService": { + { + Name: "Greet", + DocComment: "Greet someone\n", + Inputs: []*Parameter{ + { + Name: "name", + Type: &ParameterType{ + Name: "string", + }, + }, + }, + Outputs: []*Parameter{ + { + Name: "", + Type: &ParameterType{ + Name: "string", + }, + }, + }, + ID: 1411160069, + }, + }, + }, + }, + }, + { + name: "should find a bound service returned from a function call in another package", + dir: "testdata/function_from_imported_package", + wantErr: false, + wantBoundMethods: map[string]map[string][]*BoundMethod{ + "main": { + "GreetService": { + { + Name: "Greet", + DocComment: "Greet does XYZ\n", + Inputs: []*Parameter{ + { + Name: "name", + Type: &ParameterType{ + Name: "string", + }, + }, + }, + Outputs: []*Parameter{ + { + Name: "", + Type: &ParameterType{ + Name: "string", + }, + }, + }, + ID: 1411160069, + }, + { + Name: "NewPerson", + DocComment: "NewPerson creates a new person\n", + Inputs: []*Parameter{ + { + Name: "name", + Type: &ParameterType{ + Name: "string", + }, + }, + }, + Outputs: []*Parameter{ + { + Name: "", + Type: &ParameterType{ + Name: "Person", + IsPointer: true, + IsStruct: true, + Package: "main", + }, + }, + }, + ID: 1661412647, + }, + }, + }, + "github.com/wailsapp/wails/v3/internal/parser/testdata/variable_single_from_other_function/services": { + "OtherService": { + { + Name: "Yay", + Outputs: []*Parameter{ + { + Type: &ParameterType{ + Name: "Address", + IsStruct: true, + IsPointer: true, + Package: "github.com/wailsapp/wails/v3/internal/parser/testdata/variable_single_from_other_function/services", + }, + }, + }, + ID: 302702907, + }, + }, + }, + }, + wantModels: map[string]map[string]*StructDef{ + "main": { + "Person": { + Name: "Person", + Fields: []*Field{ + { + Name: "Name", + Type: &ParameterType{ + Name: "string", + }, + }, + { + Name: "Address", + Type: &ParameterType{ + Name: "Address", + IsStruct: true, + IsPointer: true, + Package: "github.com/wailsapp/wails/v3/internal/parser/testdata/variable_single_from_other_function/services", + }, + }, + }, + }, + }, + "github.com/wailsapp/wails/v3/internal/parser/testdata/variable_single_from_other_function/services": { + "Address": { + Name: "Address", + Fields: []*Field{ + { + Name: "Street", + Type: &ParameterType{ + Name: "string", + }, + }, + { + Name: "State", + Type: &ParameterType{ + Name: "string", + }, + }, + { + Name: "Country", + Type: &ParameterType{ + Name: "string", + }, + }, + }, + }, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := ParseProject(tt.dir) + if (err != nil) != tt.wantErr { + t.Errorf("ParseDirectory() error = %v, wantErr %v", err, tt.wantErr) + return + } + if diff := cmp.Diff(tt.wantBoundMethods, got.BoundMethods); diff != "" { + t.Errorf("ParseDirectory() failed:\n" + diff) + } + if diff := cmp.Diff(tt.wantModels, got.Models); diff != "" { + t.Errorf("ParseDirectory() failed:\n" + diff) + } + }) + } + +} + +//func TestGenerateTypeScript(t *testing.T) { +// tests := []struct { +// name string +// dir string +// wantModels string +// wantErr bool +// }{ +// { +// name: "should find single bound service", +// dir: "testdata/struct_literal_single", +// wantModels: `namespace main { +// class GreetService { +// SomeVariable: number; +// } +//} +//`, +// wantErr: false, +// }, +// { +// name: "should find multiple bound services", +// dir: "testdata/struct_literal_multiple", +// wantModels: `namespace main { +// class GreetService { +// SomeVariable: number; +// } +// class OtherService { +// } +//} +//`, +// wantErr: false, +// }, +// { +// name: "should find multiple bound services over multiple files", +// dir: "testdata/struct_literal_multiple_files", +// wantModels: `namespace main { +// class GreetService { +// SomeVariable: number; +// } +// class OtherService { +// } +//} +//`, +// wantErr: false, +// }, +// { +// name: "should find bound services from other packages", +// dir: "../../examples/binding", +// wantModels: `namespace main { +// class localStruct { +// } +//} +//namespace models { +// class Person { +// Name: string; +// } +//} +//namespace services { +// class GreetService { +// SomeVariable: number; +// Parent: models.Person; +// } +//} +//`, +// wantErr: false, +// }, +// } +// for _, tt := range tests { +// t.Run(tt.name, func(t *testing.T) { +// Debug = true +// context, err := ParseDirectory(tt.dir) +// if (err != nil) != tt.wantErr { +// t.Errorf("ParseDirectory() error = %v, wantErr %v", err, tt.wantErr) +// return +// } +// +// ts, err := GenerateModels(context) +// require.NoError(t, err) +// require.Equal(t, tt.wantModels, string(ts)) +// +// }) +// } +//} diff --git a/v3/internal/parser/templates/class.ts.tmpl b/v3/internal/parser/templates/class.ts.tmpl new file mode 100644 index 000000000..886f6b663 --- /dev/null +++ b/v3/internal/parser/templates/class.ts.tmpl @@ -0,0 +1,16 @@ + export class {{.Name}} { + {{range .Fields}}{{.}} + {{end}} + static createFrom(source: any = {}) { + return new {{.Name}}(source); + } + + constructor(source: any = {}) { + if ('string' === typeof source) { + source = JSON.parse(source); + } + + {{range .Fields}}this.{{jsName .}} = source["{{jsName .}}"] + {{end}} + } + } diff --git a/v3/internal/parser/templates/model.ts.tmpl b/v3/internal/parser/templates/model.ts.tmpl new file mode 100644 index 000000000..232db7479 --- /dev/null +++ b/v3/internal/parser/templates/model.ts.tmpl @@ -0,0 +1,21 @@ +{{$pkg := .Package}} +export namespace {{.Package}} { + {{range $name, $def := .Models}} + export class {{$def.Name}} { + {{range $def.Fields}}{{.JSDef $pkg}} + {{end}} + static createFrom(source: any = {}) { + return new {{$def.Name}}(source); + } + + constructor(source: any = {}) { + if ('string' === typeof source) { + source = JSON.parse(source); + } + + {{range $def.Fields}}this.{{.JSName}} = {{.TSBuild $pkg}}; + {{end}} + } + } + {{end}} +} diff --git a/v3/internal/parser/testdata/function_from_imported_package/bindings_main.js b/v3/internal/parser/testdata/function_from_imported_package/bindings_main.js new file mode 100644 index 000000000..2c02c7608 --- /dev/null +++ b/v3/internal/parser/testdata/function_from_imported_package/bindings_main.js @@ -0,0 +1,28 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import {main} from './models'; + +window.go = window.go || {}; +window.go.main = { + GreetService: { + + /** + * GreetService.Greet + * Greet does XYZ + * @param name {string} + * @returns {Promise} + **/ + Greet: function(name) { wails.CallByID(1411160069, ...Array.prototype.slice.call(arguments, 0)); }, + + /** + * GreetService.NewPerson + * NewPerson creates a new person + * @param name {string} + * @returns {Promise} + **/ + NewPerson: function(name) { wails.CallByID(1661412647, ...Array.prototype.slice.call(arguments, 0)); }, + }, +}; + diff --git a/v3/internal/parser/testdata/function_from_imported_package/bindings_services.js b/v3/internal/parser/testdata/function_from_imported_package/bindings_services.js new file mode 100644 index 000000000..4e2578071 --- /dev/null +++ b/v3/internal/parser/testdata/function_from_imported_package/bindings_services.js @@ -0,0 +1,20 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import {services} from './models'; + +window.go = window.go || {}; +window.go.services = { + OtherService: { + + /** + * OtherService.Yay + * + * + * @returns {Promise} + **/ + Yay: function() { wails.CallByID(302702907, ...Array.prototype.slice.call(arguments, 0)); }, + }, +}; + diff --git a/v3/internal/parser/testdata/function_from_imported_package/main.go b/v3/internal/parser/testdata/function_from_imported_package/main.go new file mode 100644 index 000000000..3c061aa9b --- /dev/null +++ b/v3/internal/parser/testdata/function_from_imported_package/main.go @@ -0,0 +1,49 @@ +package main + +import ( + _ "embed" + "github.com/wailsapp/wails/v3/internal/parser/testdata/variable_single_from_other_function/services" + "log" + + "github.com/wailsapp/wails/v3/pkg/application" +) + +// GreetService is great +type GreetService struct { + SomeVariable int + lowerCase string + target *Person +} + +type Person struct { + Name string + Address *services.Address +} + +// Greet does XYZ +func (*GreetService) Greet(name string) string { + return "Hello " + name +} + +// NewPerson creates a new person +func (*GreetService) NewPerson(name string) *Person { + return &Person{Name: name} +} + +func main() { + app := application.New(application.Options{ + Bind: []interface{}{ + &GreetService{}, + services.NewOtherService(), + }, + }) + + app.NewWebviewWindow() + + err := app.Run() + + if err != nil { + log.Fatal(err) + } + +} diff --git a/v3/internal/parser/testdata/function_from_imported_package/models.ts b/v3/internal/parser/testdata/function_from_imported_package/models.ts new file mode 100644 index 000000000..a6639f8eb --- /dev/null +++ b/v3/internal/parser/testdata/function_from_imported_package/models.ts @@ -0,0 +1,51 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +export namespace main { + + export class Person { + name: string; + address: services.Address; + + static createFrom(source: any = {}) { + return new Person(source); + } + + constructor(source: any = {}) { + if ('string' === typeof source) { + source = JSON.parse(source); + } + + this.name = source['name']; + this.address = services.Address.createFrom(source['address']); + + } + } + +} + +export namespace services { + + export class Address { + street: string; + state: string; + country: string; + + static createFrom(source: any = {}) { + return new Address(source); + } + + constructor(source: any = {}) { + if ('string' === typeof source) { + source = JSON.parse(source); + } + + this.street = source['street']; + this.state = source['state']; + this.country = source['country']; + + } + } + +} diff --git a/v3/internal/parser/testdata/function_from_imported_package/services/other.go b/v3/internal/parser/testdata/function_from_imported_package/services/other.go new file mode 100644 index 000000000..2daa9df17 --- /dev/null +++ b/v3/internal/parser/testdata/function_from_imported_package/services/other.go @@ -0,0 +1,26 @@ +package services + +// OtherService is a struct +// that does things +type OtherService struct { + t int +} + +type Address struct { + Street string + State string + Country string +} + +// Yay does this and that +func (o *OtherService) Yay() *Address { + return &Address{ + Street: "123 Pitt Street", + State: "New South Wales", + Country: "Australia", + } +} + +func NewOtherService() *OtherService { + return &OtherService{} +} diff --git a/v3/internal/parser/testdata/function_single/bindings_main.js b/v3/internal/parser/testdata/function_single/bindings_main.js new file mode 100644 index 000000000..c9a4675b5 --- /dev/null +++ b/v3/internal/parser/testdata/function_single/bindings_main.js @@ -0,0 +1,18 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + + +window.go = window.go || {}; +window.go.main = { + GreetService: { + + /** + * GreetService.Greet + * Greet someone + * @param name {string} + * @returns {Promise} + **/ + Greet: function(name) { wails.CallByID(1411160069, ...Array.prototype.slice.call(arguments, 0)); }, + }, +}; diff --git a/v3/internal/parser/testdata/function_single/main.go b/v3/internal/parser/testdata/function_single/main.go new file mode 100644 index 000000000..88de7bf89 --- /dev/null +++ b/v3/internal/parser/testdata/function_single/main.go @@ -0,0 +1,39 @@ +package main + +import ( + _ "embed" + "github.com/wailsapp/wails/v3/pkg/application" + "log" +) + +// GreetService is great +type GreetService struct { + SomeVariable int + lowerCase string +} + +// Greet someone +func (*GreetService) Greet(name string) string { + return "Hello " + name +} + +func NewGreetService() *GreetService { + return &GreetService{} +} + +func main() { + app := application.New(application.Options{ + Bind: []interface{}{ + NewGreetService(), + }, + }) + + app.NewWebviewWindow() + + err := app.Run() + + if err != nil { + log.Fatal(err) + } + +} diff --git a/v3/internal/parser/testdata/function_single/models.ts b/v3/internal/parser/testdata/function_single/models.ts new file mode 100644 index 000000000..0817f259b --- /dev/null +++ b/v3/internal/parser/testdata/function_single/models.ts @@ -0,0 +1,5 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// TODO : nothing generated yet \ No newline at end of file diff --git a/v3/internal/parser/testdata/struct_literal_multiple/bindings_main.js b/v3/internal/parser/testdata/struct_literal_multiple/bindings_main.js new file mode 100644 index 000000000..edfdda68a --- /dev/null +++ b/v3/internal/parser/testdata/struct_literal_multiple/bindings_main.js @@ -0,0 +1,28 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + + +window.go = window.go || {}; +window.go.main = { + GreetService: { + + /** + * GreetService.Greet + * + * @param name {string} + * @returns {Promise} + **/ + Greet: function(name) { wails.CallByID(1411160069, ...Array.prototype.slice.call(arguments, 0)); }, + }, + OtherService: { + + /** + * OtherService.Hello + * + * + * @returns {Promise} + **/ + Hello: function() { wails.CallByID(4249972365, ...Array.prototype.slice.call(arguments, 0)); }, + }, +}; diff --git a/v3/internal/parser/testdata/struct_literal_multiple/main.go b/v3/internal/parser/testdata/struct_literal_multiple/main.go new file mode 100644 index 000000000..fb555d79f --- /dev/null +++ b/v3/internal/parser/testdata/struct_literal_multiple/main.go @@ -0,0 +1,41 @@ +package main + +import ( + _ "embed" + "log" + + "github.com/wailsapp/wails/v3/pkg/application" +) + +type GreetService struct { + SomeVariable int + lowerCase string +} + +func (*GreetService) Greet(name string) string { + return "Hello " + name +} + +type OtherService struct { + t int +} + +func (o *OtherService) Hello() {} + +func main() { + app := application.New(application.Options{ + Bind: []interface{}{ + &GreetService{}, + &OtherService{}, + }, + }) + + app.NewWebviewWindow() + + err := app.Run() + + if err != nil { + log.Fatal(err) + } + +} diff --git a/v3/internal/parser/testdata/struct_literal_multiple/models.ts b/v3/internal/parser/testdata/struct_literal_multiple/models.ts new file mode 100644 index 000000000..0817f259b --- /dev/null +++ b/v3/internal/parser/testdata/struct_literal_multiple/models.ts @@ -0,0 +1,5 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// TODO : nothing generated yet \ No newline at end of file diff --git a/v3/internal/parser/testdata/struct_literal_multiple_files/bindings_main.js b/v3/internal/parser/testdata/struct_literal_multiple_files/bindings_main.js new file mode 100644 index 000000000..edfdda68a --- /dev/null +++ b/v3/internal/parser/testdata/struct_literal_multiple_files/bindings_main.js @@ -0,0 +1,28 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + + +window.go = window.go || {}; +window.go.main = { + GreetService: { + + /** + * GreetService.Greet + * + * @param name {string} + * @returns {Promise} + **/ + Greet: function(name) { wails.CallByID(1411160069, ...Array.prototype.slice.call(arguments, 0)); }, + }, + OtherService: { + + /** + * OtherService.Hello + * + * + * @returns {Promise} + **/ + Hello: function() { wails.CallByID(4249972365, ...Array.prototype.slice.call(arguments, 0)); }, + }, +}; diff --git a/v3/internal/parser/testdata/struct_literal_multiple_files/greet.go b/v3/internal/parser/testdata/struct_literal_multiple_files/greet.go new file mode 100644 index 000000000..2a45396a7 --- /dev/null +++ b/v3/internal/parser/testdata/struct_literal_multiple_files/greet.go @@ -0,0 +1,14 @@ +package main + +import ( + _ "embed" +) + +type GreetService struct { + SomeVariable int + lowerCase string +} + +func (*GreetService) Greet(name string) string { + return "Hello " + name +} diff --git a/v3/internal/parser/testdata/struct_literal_multiple_files/main.go b/v3/internal/parser/testdata/struct_literal_multiple_files/main.go new file mode 100644 index 000000000..a75d4d7bd --- /dev/null +++ b/v3/internal/parser/testdata/struct_literal_multiple_files/main.go @@ -0,0 +1,26 @@ +package main + +import ( + _ "embed" + "log" + + "github.com/wailsapp/wails/v3/pkg/application" +) + +func main() { + app := application.New(application.Options{ + Bind: []interface{}{ + &GreetService{}, + &OtherService{}, + }, + }) + + app.NewWebviewWindow() + + err := app.Run() + + if err != nil { + log.Fatal(err) + } + +} diff --git a/v3/internal/parser/testdata/struct_literal_multiple_files/models.ts b/v3/internal/parser/testdata/struct_literal_multiple_files/models.ts new file mode 100644 index 000000000..0817f259b --- /dev/null +++ b/v3/internal/parser/testdata/struct_literal_multiple_files/models.ts @@ -0,0 +1,5 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// TODO : nothing generated yet \ No newline at end of file diff --git a/v3/internal/parser/testdata/struct_literal_multiple_files/other.go b/v3/internal/parser/testdata/struct_literal_multiple_files/other.go new file mode 100644 index 000000000..ad5e661ef --- /dev/null +++ b/v3/internal/parser/testdata/struct_literal_multiple_files/other.go @@ -0,0 +1,7 @@ +package main + +type OtherService struct { + t int +} + +func (o *OtherService) Hello() {} diff --git a/v3/internal/parser/testdata/struct_literal_multiple_other/bindings_main.js b/v3/internal/parser/testdata/struct_literal_multiple_other/bindings_main.js new file mode 100644 index 000000000..2c02c7608 --- /dev/null +++ b/v3/internal/parser/testdata/struct_literal_multiple_other/bindings_main.js @@ -0,0 +1,28 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import {main} from './models'; + +window.go = window.go || {}; +window.go.main = { + GreetService: { + + /** + * GreetService.Greet + * Greet does XYZ + * @param name {string} + * @returns {Promise} + **/ + Greet: function(name) { wails.CallByID(1411160069, ...Array.prototype.slice.call(arguments, 0)); }, + + /** + * GreetService.NewPerson + * NewPerson creates a new person + * @param name {string} + * @returns {Promise} + **/ + NewPerson: function(name) { wails.CallByID(1661412647, ...Array.prototype.slice.call(arguments, 0)); }, + }, +}; + diff --git a/v3/internal/parser/testdata/struct_literal_multiple_other/bindings_services.js b/v3/internal/parser/testdata/struct_literal_multiple_other/bindings_services.js new file mode 100644 index 000000000..ec6fb2aeb --- /dev/null +++ b/v3/internal/parser/testdata/struct_literal_multiple_other/bindings_services.js @@ -0,0 +1,20 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import {services} from './models'; + +window.go = window.go || {}; +window.go.services = { + OtherService: { + + /** + * OtherService.Yay + * + * + * @returns {Promise} + **/ + Yay: function() { wails.CallByID(469445984, ...Array.prototype.slice.call(arguments, 0)); }, + }, +}; + diff --git a/v3/internal/parser/testdata/struct_literal_multiple_other/main.go b/v3/internal/parser/testdata/struct_literal_multiple_other/main.go new file mode 100644 index 000000000..8c6953dbb --- /dev/null +++ b/v3/internal/parser/testdata/struct_literal_multiple_other/main.go @@ -0,0 +1,49 @@ +package main + +import ( + _ "embed" + "log" + + "github.com/wailsapp/wails/v3/internal/parser/testdata/struct_literal_multiple_other/services" + "github.com/wailsapp/wails/v3/pkg/application" +) + +// GreetService is great +type GreetService struct { + SomeVariable int + lowerCase string + target *Person +} + +type Person struct { + Name string + Address *services.Address +} + +// Greet does XYZ +func (*GreetService) Greet(name string) string { + return "Hello " + name +} + +// NewPerson creates a new person +func (*GreetService) NewPerson(name string) *Person { + return &Person{Name: name} +} + +func main() { + app := application.New(application.Options{ + Bind: []interface{}{ + &GreetService{}, + &services.OtherService{}, + }, + }) + + app.NewWebviewWindow() + + err := app.Run() + + if err != nil { + log.Fatal(err) + } + +} diff --git a/v3/internal/parser/testdata/struct_literal_multiple_other/models.ts b/v3/internal/parser/testdata/struct_literal_multiple_other/models.ts new file mode 100644 index 000000000..a6639f8eb --- /dev/null +++ b/v3/internal/parser/testdata/struct_literal_multiple_other/models.ts @@ -0,0 +1,51 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +export namespace main { + + export class Person { + name: string; + address: services.Address; + + static createFrom(source: any = {}) { + return new Person(source); + } + + constructor(source: any = {}) { + if ('string' === typeof source) { + source = JSON.parse(source); + } + + this.name = source['name']; + this.address = services.Address.createFrom(source['address']); + + } + } + +} + +export namespace services { + + export class Address { + street: string; + state: string; + country: string; + + static createFrom(source: any = {}) { + return new Address(source); + } + + constructor(source: any = {}) { + if ('string' === typeof source) { + source = JSON.parse(source); + } + + this.street = source['street']; + this.state = source['state']; + this.country = source['country']; + + } + } + +} diff --git a/v3/internal/parser/testdata/struct_literal_multiple_other/services/other.go b/v3/internal/parser/testdata/struct_literal_multiple_other/services/other.go new file mode 100644 index 000000000..55472595b --- /dev/null +++ b/v3/internal/parser/testdata/struct_literal_multiple_other/services/other.go @@ -0,0 +1,22 @@ +package services + +// OtherService is a struct +// that does things +type OtherService struct { + t int +} + +type Address struct { + Street string + State string + Country string +} + +// Yay does this and that +func (o *OtherService) Yay() *Address { + return &Address{ + Street: "123 Pitt Street", + State: "New South Wales", + Country: "Australia", + } +} diff --git a/v3/internal/parser/testdata/struct_literal_non_pointer_single/bindings_main.js b/v3/internal/parser/testdata/struct_literal_non_pointer_single/bindings_main.js new file mode 100644 index 000000000..893b066bc --- /dev/null +++ b/v3/internal/parser/testdata/struct_literal_non_pointer_single/bindings_main.js @@ -0,0 +1,356 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import {main} from './models'; + +window.go = window.go || {}; +window.go.main = { + GreetService: { + + /** + * GreetService.ArrayInt + * + * @param _in {number[]} + * @returns {Promise} + **/ + ArrayInt: function(_in) { wails.CallByID(3862002418, ...Array.prototype.slice.call(arguments, 0)); }, + + /** + * GreetService.BoolInBoolOut + * + * @param _in {boolean} + * @returns {Promise} + **/ + BoolInBoolOut: function(_in) { wails.CallByID(2424639793, ...Array.prototype.slice.call(arguments, 0)); }, + + /** + * GreetService.Float32InFloat32Out + * + * @param _in {number} + * @returns {Promise} + **/ + Float32InFloat32Out: function(_in) { wails.CallByID(3132595881, ...Array.prototype.slice.call(arguments, 0)); }, + + /** + * GreetService.Float64InFloat64Out + * + * @param _in {number} + * @returns {Promise} + **/ + Float64InFloat64Out: function(_in) { wails.CallByID(2182412247, ...Array.prototype.slice.call(arguments, 0)); }, + + /** + * GreetService.Greet + * Greet someone + * @param name {string} + * @returns {Promise} + **/ + Greet: function(name) { wails.CallByID(1411160069, ...Array.prototype.slice.call(arguments, 0)); }, + + /** + * GreetService.Int16InIntOut + * + * @param _in {number} + * @returns {Promise} + **/ + Int16InIntOut: function(_in) { wails.CallByID(3306292566, ...Array.prototype.slice.call(arguments, 0)); }, + + /** + * GreetService.Int16PointerInAndOutput + * + * @param _in {number | null} + * @returns {Promise} + **/ + Int16PointerInAndOutput: function(_in) { wails.CallByID(1754277916, ...Array.prototype.slice.call(arguments, 0)); }, + + /** + * GreetService.Int32InIntOut + * + * @param _in {number} + * @returns {Promise} + **/ + Int32InIntOut: function(_in) { wails.CallByID(1909469092, ...Array.prototype.slice.call(arguments, 0)); }, + + /** + * GreetService.Int32PointerInAndOutput + * + * @param _in {number | null} + * @returns {Promise} + **/ + Int32PointerInAndOutput: function(_in) { wails.CallByID(4251088558, ...Array.prototype.slice.call(arguments, 0)); }, + + /** + * GreetService.Int64InIntOut + * + * @param _in {number} + * @returns {Promise} + **/ + Int64InIntOut: function(_in) { wails.CallByID(1343888303, ...Array.prototype.slice.call(arguments, 0)); }, + + /** + * GreetService.Int64PointerInAndOutput + * + * @param _in {number | null} + * @returns {Promise} + **/ + Int64PointerInAndOutput: function(_in) { wails.CallByID(2205561041, ...Array.prototype.slice.call(arguments, 0)); }, + + /** + * GreetService.Int8InIntOut + * + * @param _in {number} + * @returns {Promise} + **/ + Int8InIntOut: function(_in) { wails.CallByID(572240879, ...Array.prototype.slice.call(arguments, 0)); }, + + /** + * GreetService.Int8PointerInAndOutput + * + * @param _in {number | null} + * @returns {Promise} + **/ + Int8PointerInAndOutput: function(_in) { wails.CallByID(2189402897, ...Array.prototype.slice.call(arguments, 0)); }, + + /** + * GreetService.IntInIntOut + * + * @param _in {number} + * @returns {Promise} + **/ + IntInIntOut: function(_in) { wails.CallByID(642881729, ...Array.prototype.slice.call(arguments, 0)); }, + + /** + * GreetService.IntPointerInAndOutput + * + * @param _in {number | null} + * @returns {Promise} + **/ + IntPointerInAndOutput: function(_in) { wails.CallByID(1066151743, ...Array.prototype.slice.call(arguments, 0)); }, + + /** + * GreetService.IntPointerInputNamedOutputs + * + * @param _in {number | null} + * @returns {Promise} + **/ + IntPointerInputNamedOutputs: function(_in) { wails.CallByID(2718999663, ...Array.prototype.slice.call(arguments, 0)); }, + + /** + * GreetService.MapIntInt + * + * @param _in {map} + * @returns {Promise} + **/ + MapIntInt: function(_in) { wails.CallByID(2386486356, ...Array.prototype.slice.call(arguments, 0)); }, + + /** + * GreetService.MapIntPointerInt + * + * @param _in {map} + * @returns {Promise} + **/ + MapIntPointerInt: function(_in) { wails.CallByID(550413585, ...Array.prototype.slice.call(arguments, 0)); }, + + /** + * GreetService.MapIntSliceInt + * + * @param _in {map} + * @returns {Promise} + **/ + MapIntSliceInt: function(_in) { wails.CallByID(2900172572, ...Array.prototype.slice.call(arguments, 0)); }, + + /** + * GreetService.MapIntSliceIntInMapIntSliceIntOut + * + * @param _in {map} + * @returns {Promise} + **/ + MapIntSliceIntInMapIntSliceIntOut: function(_in) { wails.CallByID(881980169, ...Array.prototype.slice.call(arguments, 0)); }, + + /** + * GreetService.NoInputsStringOut + * + * + * @returns {Promise} + **/ + NoInputsStringOut: function() { wails.CallByID(1075577233, ...Array.prototype.slice.call(arguments, 0)); }, + + /** + * GreetService.PointerBoolInBoolOut + * + * @param _in {boolean | null} + * @returns {Promise} + **/ + PointerBoolInBoolOut: function(_in) { wails.CallByID(3589606958, ...Array.prototype.slice.call(arguments, 0)); }, + + /** + * GreetService.PointerFloat32InFloat32Out + * + * @param _in {number | null} + * @returns {Promise} + **/ + PointerFloat32InFloat32Out: function(_in) { wails.CallByID(224675106, ...Array.prototype.slice.call(arguments, 0)); }, + + /** + * GreetService.PointerFloat64InFloat64Out + * + * @param _in {number | null} + * @returns {Promise} + **/ + PointerFloat64InFloat64Out: function(_in) { wails.CallByID(2124953624, ...Array.prototype.slice.call(arguments, 0)); }, + + /** + * GreetService.PointerMapIntInt + * + * @param _in {map | null} + * @returns {Promise} + **/ + PointerMapIntInt: function(_in) { wails.CallByID(3516977899, ...Array.prototype.slice.call(arguments, 0)); }, + + /** + * GreetService.PointerStringInStringOut + * + * @param _in {string | null} + * @returns {Promise} + **/ + PointerStringInStringOut: function(_in) { wails.CallByID(229603958, ...Array.prototype.slice.call(arguments, 0)); }, + + /** + * GreetService.StringArrayInputNamedOutput + * + * @param _in {string[]} + * @returns {Promise} + **/ + StringArrayInputNamedOutput: function(_in) { wails.CallByID(3678582682, ...Array.prototype.slice.call(arguments, 0)); }, + + /** + * GreetService.StringArrayInputNamedOutputs + * + * @param _in {string[]} + * @returns {Promise} + **/ + StringArrayInputNamedOutputs: function(_in) { wails.CallByID(319259595, ...Array.prototype.slice.call(arguments, 0)); }, + + /** + * GreetService.StringArrayInputStringArrayOut + * + * @param _in {string[]} + * @returns {Promise} + **/ + StringArrayInputStringArrayOut: function(_in) { wails.CallByID(383995060, ...Array.prototype.slice.call(arguments, 0)); }, + + /** + * GreetService.StringArrayInputStringOut + * + * @param _in {string[]} + * @returns {Promise} + **/ + StringArrayInputStringOut: function(_in) { wails.CallByID(1091960237, ...Array.prototype.slice.call(arguments, 0)); }, + + /** + * GreetService.StructInputStructOutput + * + * @param _in {main.Person} + * @returns {Promise} + **/ + StructInputStructOutput: function(_in) { wails.CallByID(3835643147, ...Array.prototype.slice.call(arguments, 0)); }, + + /** + * GreetService.StructPointerInputErrorOutput + * + * @param _in {main.Person | null} + * @returns {Promise} + **/ + StructPointerInputErrorOutput: function(_in) { wails.CallByID(2447692557, ...Array.prototype.slice.call(arguments, 0)); }, + + /** + * GreetService.StructPointerInputStructPointerOutput + * + * @param _in {main.Person | null} + * @returns {Promise} + **/ + StructPointerInputStructPointerOutput: function(_in) { wails.CallByID(2943477349, ...Array.prototype.slice.call(arguments, 0)); }, + + /** + * GreetService.UInt16InUIntOut + * + * @param _in {number} + * @returns {Promise} + **/ + UInt16InUIntOut: function(_in) { wails.CallByID(3401034892, ...Array.prototype.slice.call(arguments, 0)); }, + + /** + * GreetService.UInt16PointerInAndOutput + * + * @param _in {number | null} + * @returns {Promise} + **/ + UInt16PointerInAndOutput: function(_in) { wails.CallByID(1236957573, ...Array.prototype.slice.call(arguments, 0)); }, + + /** + * GreetService.UInt32InUIntOut + * + * @param _in {number} + * @returns {Promise} + **/ + UInt32InUIntOut: function(_in) { wails.CallByID(1160383782, ...Array.prototype.slice.call(arguments, 0)); }, + + /** + * GreetService.UInt32PointerInAndOutput + * + * @param _in {number | null} + * @returns {Promise} + **/ + UInt32PointerInAndOutput: function(_in) { wails.CallByID(1739300671, ...Array.prototype.slice.call(arguments, 0)); }, + + /** + * GreetService.UInt64InUIntOut + * + * @param _in {number} + * @returns {Promise} + **/ + UInt64InUIntOut: function(_in) { wails.CallByID(793803239, ...Array.prototype.slice.call(arguments, 0)); }, + + /** + * GreetService.UInt64PointerInAndOutput + * + * @param _in {number | null} + * @returns {Promise} + **/ + UInt64PointerInAndOutput: function(_in) { wails.CallByID(1403757716, ...Array.prototype.slice.call(arguments, 0)); }, + + /** + * GreetService.UInt8InUIntOut + * + * @param _in {number} + * @returns {Promise} + **/ + UInt8InUIntOut: function(_in) { wails.CallByID(2988345717, ...Array.prototype.slice.call(arguments, 0)); }, + + /** + * GreetService.UInt8PointerInAndOutput + * + * @param _in {number | null} + * @returns {Promise} + **/ + UInt8PointerInAndOutput: function(_in) { wails.CallByID(518250834, ...Array.prototype.slice.call(arguments, 0)); }, + + /** + * GreetService.UIntInUIntOut + * + * @param _in {number} + * @returns {Promise} + **/ + UIntInUIntOut: function(_in) { wails.CallByID(2836661285, ...Array.prototype.slice.call(arguments, 0)); }, + + /** + * GreetService.UIntPointerInAndOutput + * + * @param _in {number | null} + * @returns {Promise} + **/ + UIntPointerInAndOutput: function(_in) { wails.CallByID(1367187362, ...Array.prototype.slice.call(arguments, 0)); }, + }, +}; + diff --git a/v3/internal/parser/testdata/struct_literal_non_pointer_single/main.go b/v3/internal/parser/testdata/struct_literal_non_pointer_single/main.go new file mode 100644 index 000000000..e417a7b6f --- /dev/null +++ b/v3/internal/parser/testdata/struct_literal_non_pointer_single/main.go @@ -0,0 +1,205 @@ +package main + +import ( + _ "embed" + "log" + "strings" + + "github.com/wailsapp/wails/v3/pkg/application" +) + +type Person struct { + Name string + Parent *Person + Details struct { + Age int + Address struct { + Street string + } + } +} + +// GreetService is great +type GreetService struct { + SomeVariable int + lowerCase string +} + +// Greet someone +func (GreetService) Greet(name string) string { + return "Hello " + name +} + +func (GreetService) NoInputsStringOut() string { + return "Hello" +} + +func (GreetService) StringArrayInputStringOut(in []string) string { + return strings.Join(in, ",") +} + +func (GreetService) StringArrayInputStringArrayOut(in []string) []string { + return in +} + +func (GreetService) StringArrayInputNamedOutput(in []string) (output []string) { + return in +} + +func (GreetService) StringArrayInputNamedOutputs(in []string) (output []string, err error) { + return in, nil +} + +func (GreetService) IntPointerInputNamedOutputs(in *int) (output *int, err error) { + return in, nil +} + +func (GreetService) UIntPointerInAndOutput(in *uint) *uint { + return in +} + +func (GreetService) UInt8PointerInAndOutput(in *uint8) *uint8 { + return in +} + +func (GreetService) UInt16PointerInAndOutput(in *uint16) *uint16 { + return in +} + +func (GreetService) UInt32PointerInAndOutput(in *uint32) *uint32 { + return in +} + +func (GreetService) UInt64PointerInAndOutput(in *uint64) *uint64 { + return in +} + +func (GreetService) IntPointerInAndOutput(in *int) *int { + return in +} + +func (GreetService) Int8PointerInAndOutput(in *int8) *int8 { + return in +} + +func (GreetService) Int16PointerInAndOutput(in *int16) *int16 { + return in +} + +func (GreetService) Int32PointerInAndOutput(in *int32) *int32 { + return in +} + +func (GreetService) Int64PointerInAndOutput(in *int64) *int64 { + return in +} + +func (GreetService) IntInIntOut(in int) int { + return in +} + +func (GreetService) Int8InIntOut(in int8) int8 { + return in +} +func (GreetService) Int16InIntOut(in int16) int16 { + return in +} +func (GreetService) Int32InIntOut(in int32) int32 { + return in +} +func (GreetService) Int64InIntOut(in int64) int64 { + return in +} + +func (GreetService) UIntInUIntOut(in uint) uint { + return in +} + +func (GreetService) UInt8InUIntOut(in uint8) uint8 { + return in +} +func (GreetService) UInt16InUIntOut(in uint16) uint16 { + return in +} +func (GreetService) UInt32InUIntOut(in uint32) uint32 { + return in +} +func (GreetService) UInt64InUIntOut(in uint64) uint64 { + return in +} + +func (GreetService) Float32InFloat32Out(in float32) float32 { + return in +} + +func (GreetService) Float64InFloat64Out(in float64) float64 { + return in +} + +func (GreetService) PointerFloat32InFloat32Out(in *float32) *float32 { + return in +} + +func (GreetService) PointerFloat64InFloat64Out(in *float64) *float64 { + return in +} + +func (GreetService) BoolInBoolOut(in bool) bool { + return in +} + +func (GreetService) PointerBoolInBoolOut(in *bool) *bool { + return in +} + +func (GreetService) PointerStringInStringOut(in *string) *string { + return in +} + +func (GreetService) StructPointerInputErrorOutput(in *Person) error { + return nil +} + +func (GreetService) StructInputStructOutput(in Person) Person { + return in +} + +func (GreetService) StructPointerInputStructPointerOutput(in *Person) *Person { + return in +} + +func (GreetService) MapIntInt(in map[int]int) { +} + +func (GreetService) PointerMapIntInt(in *map[int]int) { +} + +func (GreetService) MapIntPointerInt(in map[*int]int) { +} + +func (GreetService) MapIntSliceInt(in map[int][]int) { +} + +func (GreetService) MapIntSliceIntInMapIntSliceIntOut(in map[int][]int) (out map[int][]int) { + return nil +} + +func (GreetService) ArrayInt(in [4]int) { +} + +func main() { + app := application.New(application.Options{ + Bind: []interface{}{ + &GreetService{}, + }, + }) + + app.NewWebviewWindow() + + err := app.Run() + + if err != nil { + log.Fatal(err) + } + +} diff --git a/v3/internal/parser/testdata/struct_literal_non_pointer_single/models.ts b/v3/internal/parser/testdata/struct_literal_non_pointer_single/models.ts new file mode 100644 index 000000000..91bab56ac --- /dev/null +++ b/v3/internal/parser/testdata/struct_literal_non_pointer_single/models.ts @@ -0,0 +1,64 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +export namespace main { + + export class Person { + name: string; + parent: Person; + details: anon1; + + static createFrom(source: any = {}) { + return new Person(source); + } + + constructor(source: any = {}) { + if ('string' === typeof source) { + source = JSON.parse(source); + } + + this.name = source['name']; + this.parent = Person.createFrom(source['parent']); + this.details = anon1.createFrom(source['details']); + + } + } + + export class anon1 { + age: number; + address: anon2; + + static createFrom(source: any = {}) { + return new anon1(source); + } + + constructor(source: any = {}) { + if ('string' === typeof source) { + source = JSON.parse(source); + } + + this.age = source['age']; + this.address = anon2.createFrom(source['address']); + + } + } + + export class anon2 { + street: string; + + static createFrom(source: any = {}) { + return new anon2(source); + } + + constructor(source: any = {}) { + if ('string' === typeof source) { + source = JSON.parse(source); + } + + this.street = source['street']; + + } + } + +} diff --git a/v3/internal/parser/testdata/struct_literal_single/bindings_main.js b/v3/internal/parser/testdata/struct_literal_single/bindings_main.js new file mode 100644 index 000000000..893b066bc --- /dev/null +++ b/v3/internal/parser/testdata/struct_literal_single/bindings_main.js @@ -0,0 +1,356 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import {main} from './models'; + +window.go = window.go || {}; +window.go.main = { + GreetService: { + + /** + * GreetService.ArrayInt + * + * @param _in {number[]} + * @returns {Promise} + **/ + ArrayInt: function(_in) { wails.CallByID(3862002418, ...Array.prototype.slice.call(arguments, 0)); }, + + /** + * GreetService.BoolInBoolOut + * + * @param _in {boolean} + * @returns {Promise} + **/ + BoolInBoolOut: function(_in) { wails.CallByID(2424639793, ...Array.prototype.slice.call(arguments, 0)); }, + + /** + * GreetService.Float32InFloat32Out + * + * @param _in {number} + * @returns {Promise} + **/ + Float32InFloat32Out: function(_in) { wails.CallByID(3132595881, ...Array.prototype.slice.call(arguments, 0)); }, + + /** + * GreetService.Float64InFloat64Out + * + * @param _in {number} + * @returns {Promise} + **/ + Float64InFloat64Out: function(_in) { wails.CallByID(2182412247, ...Array.prototype.slice.call(arguments, 0)); }, + + /** + * GreetService.Greet + * Greet someone + * @param name {string} + * @returns {Promise} + **/ + Greet: function(name) { wails.CallByID(1411160069, ...Array.prototype.slice.call(arguments, 0)); }, + + /** + * GreetService.Int16InIntOut + * + * @param _in {number} + * @returns {Promise} + **/ + Int16InIntOut: function(_in) { wails.CallByID(3306292566, ...Array.prototype.slice.call(arguments, 0)); }, + + /** + * GreetService.Int16PointerInAndOutput + * + * @param _in {number | null} + * @returns {Promise} + **/ + Int16PointerInAndOutput: function(_in) { wails.CallByID(1754277916, ...Array.prototype.slice.call(arguments, 0)); }, + + /** + * GreetService.Int32InIntOut + * + * @param _in {number} + * @returns {Promise} + **/ + Int32InIntOut: function(_in) { wails.CallByID(1909469092, ...Array.prototype.slice.call(arguments, 0)); }, + + /** + * GreetService.Int32PointerInAndOutput + * + * @param _in {number | null} + * @returns {Promise} + **/ + Int32PointerInAndOutput: function(_in) { wails.CallByID(4251088558, ...Array.prototype.slice.call(arguments, 0)); }, + + /** + * GreetService.Int64InIntOut + * + * @param _in {number} + * @returns {Promise} + **/ + Int64InIntOut: function(_in) { wails.CallByID(1343888303, ...Array.prototype.slice.call(arguments, 0)); }, + + /** + * GreetService.Int64PointerInAndOutput + * + * @param _in {number | null} + * @returns {Promise} + **/ + Int64PointerInAndOutput: function(_in) { wails.CallByID(2205561041, ...Array.prototype.slice.call(arguments, 0)); }, + + /** + * GreetService.Int8InIntOut + * + * @param _in {number} + * @returns {Promise} + **/ + Int8InIntOut: function(_in) { wails.CallByID(572240879, ...Array.prototype.slice.call(arguments, 0)); }, + + /** + * GreetService.Int8PointerInAndOutput + * + * @param _in {number | null} + * @returns {Promise} + **/ + Int8PointerInAndOutput: function(_in) { wails.CallByID(2189402897, ...Array.prototype.slice.call(arguments, 0)); }, + + /** + * GreetService.IntInIntOut + * + * @param _in {number} + * @returns {Promise} + **/ + IntInIntOut: function(_in) { wails.CallByID(642881729, ...Array.prototype.slice.call(arguments, 0)); }, + + /** + * GreetService.IntPointerInAndOutput + * + * @param _in {number | null} + * @returns {Promise} + **/ + IntPointerInAndOutput: function(_in) { wails.CallByID(1066151743, ...Array.prototype.slice.call(arguments, 0)); }, + + /** + * GreetService.IntPointerInputNamedOutputs + * + * @param _in {number | null} + * @returns {Promise} + **/ + IntPointerInputNamedOutputs: function(_in) { wails.CallByID(2718999663, ...Array.prototype.slice.call(arguments, 0)); }, + + /** + * GreetService.MapIntInt + * + * @param _in {map} + * @returns {Promise} + **/ + MapIntInt: function(_in) { wails.CallByID(2386486356, ...Array.prototype.slice.call(arguments, 0)); }, + + /** + * GreetService.MapIntPointerInt + * + * @param _in {map} + * @returns {Promise} + **/ + MapIntPointerInt: function(_in) { wails.CallByID(550413585, ...Array.prototype.slice.call(arguments, 0)); }, + + /** + * GreetService.MapIntSliceInt + * + * @param _in {map} + * @returns {Promise} + **/ + MapIntSliceInt: function(_in) { wails.CallByID(2900172572, ...Array.prototype.slice.call(arguments, 0)); }, + + /** + * GreetService.MapIntSliceIntInMapIntSliceIntOut + * + * @param _in {map} + * @returns {Promise} + **/ + MapIntSliceIntInMapIntSliceIntOut: function(_in) { wails.CallByID(881980169, ...Array.prototype.slice.call(arguments, 0)); }, + + /** + * GreetService.NoInputsStringOut + * + * + * @returns {Promise} + **/ + NoInputsStringOut: function() { wails.CallByID(1075577233, ...Array.prototype.slice.call(arguments, 0)); }, + + /** + * GreetService.PointerBoolInBoolOut + * + * @param _in {boolean | null} + * @returns {Promise} + **/ + PointerBoolInBoolOut: function(_in) { wails.CallByID(3589606958, ...Array.prototype.slice.call(arguments, 0)); }, + + /** + * GreetService.PointerFloat32InFloat32Out + * + * @param _in {number | null} + * @returns {Promise} + **/ + PointerFloat32InFloat32Out: function(_in) { wails.CallByID(224675106, ...Array.prototype.slice.call(arguments, 0)); }, + + /** + * GreetService.PointerFloat64InFloat64Out + * + * @param _in {number | null} + * @returns {Promise} + **/ + PointerFloat64InFloat64Out: function(_in) { wails.CallByID(2124953624, ...Array.prototype.slice.call(arguments, 0)); }, + + /** + * GreetService.PointerMapIntInt + * + * @param _in {map | null} + * @returns {Promise} + **/ + PointerMapIntInt: function(_in) { wails.CallByID(3516977899, ...Array.prototype.slice.call(arguments, 0)); }, + + /** + * GreetService.PointerStringInStringOut + * + * @param _in {string | null} + * @returns {Promise} + **/ + PointerStringInStringOut: function(_in) { wails.CallByID(229603958, ...Array.prototype.slice.call(arguments, 0)); }, + + /** + * GreetService.StringArrayInputNamedOutput + * + * @param _in {string[]} + * @returns {Promise} + **/ + StringArrayInputNamedOutput: function(_in) { wails.CallByID(3678582682, ...Array.prototype.slice.call(arguments, 0)); }, + + /** + * GreetService.StringArrayInputNamedOutputs + * + * @param _in {string[]} + * @returns {Promise} + **/ + StringArrayInputNamedOutputs: function(_in) { wails.CallByID(319259595, ...Array.prototype.slice.call(arguments, 0)); }, + + /** + * GreetService.StringArrayInputStringArrayOut + * + * @param _in {string[]} + * @returns {Promise} + **/ + StringArrayInputStringArrayOut: function(_in) { wails.CallByID(383995060, ...Array.prototype.slice.call(arguments, 0)); }, + + /** + * GreetService.StringArrayInputStringOut + * + * @param _in {string[]} + * @returns {Promise} + **/ + StringArrayInputStringOut: function(_in) { wails.CallByID(1091960237, ...Array.prototype.slice.call(arguments, 0)); }, + + /** + * GreetService.StructInputStructOutput + * + * @param _in {main.Person} + * @returns {Promise} + **/ + StructInputStructOutput: function(_in) { wails.CallByID(3835643147, ...Array.prototype.slice.call(arguments, 0)); }, + + /** + * GreetService.StructPointerInputErrorOutput + * + * @param _in {main.Person | null} + * @returns {Promise} + **/ + StructPointerInputErrorOutput: function(_in) { wails.CallByID(2447692557, ...Array.prototype.slice.call(arguments, 0)); }, + + /** + * GreetService.StructPointerInputStructPointerOutput + * + * @param _in {main.Person | null} + * @returns {Promise} + **/ + StructPointerInputStructPointerOutput: function(_in) { wails.CallByID(2943477349, ...Array.prototype.slice.call(arguments, 0)); }, + + /** + * GreetService.UInt16InUIntOut + * + * @param _in {number} + * @returns {Promise} + **/ + UInt16InUIntOut: function(_in) { wails.CallByID(3401034892, ...Array.prototype.slice.call(arguments, 0)); }, + + /** + * GreetService.UInt16PointerInAndOutput + * + * @param _in {number | null} + * @returns {Promise} + **/ + UInt16PointerInAndOutput: function(_in) { wails.CallByID(1236957573, ...Array.prototype.slice.call(arguments, 0)); }, + + /** + * GreetService.UInt32InUIntOut + * + * @param _in {number} + * @returns {Promise} + **/ + UInt32InUIntOut: function(_in) { wails.CallByID(1160383782, ...Array.prototype.slice.call(arguments, 0)); }, + + /** + * GreetService.UInt32PointerInAndOutput + * + * @param _in {number | null} + * @returns {Promise} + **/ + UInt32PointerInAndOutput: function(_in) { wails.CallByID(1739300671, ...Array.prototype.slice.call(arguments, 0)); }, + + /** + * GreetService.UInt64InUIntOut + * + * @param _in {number} + * @returns {Promise} + **/ + UInt64InUIntOut: function(_in) { wails.CallByID(793803239, ...Array.prototype.slice.call(arguments, 0)); }, + + /** + * GreetService.UInt64PointerInAndOutput + * + * @param _in {number | null} + * @returns {Promise} + **/ + UInt64PointerInAndOutput: function(_in) { wails.CallByID(1403757716, ...Array.prototype.slice.call(arguments, 0)); }, + + /** + * GreetService.UInt8InUIntOut + * + * @param _in {number} + * @returns {Promise} + **/ + UInt8InUIntOut: function(_in) { wails.CallByID(2988345717, ...Array.prototype.slice.call(arguments, 0)); }, + + /** + * GreetService.UInt8PointerInAndOutput + * + * @param _in {number | null} + * @returns {Promise} + **/ + UInt8PointerInAndOutput: function(_in) { wails.CallByID(518250834, ...Array.prototype.slice.call(arguments, 0)); }, + + /** + * GreetService.UIntInUIntOut + * + * @param _in {number} + * @returns {Promise} + **/ + UIntInUIntOut: function(_in) { wails.CallByID(2836661285, ...Array.prototype.slice.call(arguments, 0)); }, + + /** + * GreetService.UIntPointerInAndOutput + * + * @param _in {number | null} + * @returns {Promise} + **/ + UIntPointerInAndOutput: function(_in) { wails.CallByID(1367187362, ...Array.prototype.slice.call(arguments, 0)); }, + }, +}; + diff --git a/v3/internal/parser/testdata/struct_literal_single/main.go b/v3/internal/parser/testdata/struct_literal_single/main.go new file mode 100644 index 000000000..2438a69cb --- /dev/null +++ b/v3/internal/parser/testdata/struct_literal_single/main.go @@ -0,0 +1,205 @@ +package main + +import ( + _ "embed" + "log" + "strings" + + "github.com/wailsapp/wails/v3/pkg/application" +) + +type Person struct { + Name string + Parent *Person + Details struct { + Age int + Address struct { + Street string + } + } +} + +// GreetService is great +type GreetService struct { + SomeVariable int + lowerCase string +} + +// Greet someone +func (*GreetService) Greet(name string) string { + return "Hello " + name +} + +func (*GreetService) NoInputsStringOut() string { + return "Hello" +} + +func (*GreetService) StringArrayInputStringOut(in []string) string { + return strings.Join(in, ",") +} + +func (*GreetService) StringArrayInputStringArrayOut(in []string) []string { + return in +} + +func (*GreetService) StringArrayInputNamedOutput(in []string) (output []string) { + return in +} + +func (*GreetService) StringArrayInputNamedOutputs(in []string) (output []string, err error) { + return in, nil +} + +func (*GreetService) IntPointerInputNamedOutputs(in *int) (output *int, err error) { + return in, nil +} + +func (*GreetService) UIntPointerInAndOutput(in *uint) *uint { + return in +} + +func (*GreetService) UInt8PointerInAndOutput(in *uint8) *uint8 { + return in +} + +func (*GreetService) UInt16PointerInAndOutput(in *uint16) *uint16 { + return in +} + +func (*GreetService) UInt32PointerInAndOutput(in *uint32) *uint32 { + return in +} + +func (*GreetService) UInt64PointerInAndOutput(in *uint64) *uint64 { + return in +} + +func (*GreetService) IntPointerInAndOutput(in *int) *int { + return in +} + +func (*GreetService) Int8PointerInAndOutput(in *int8) *int8 { + return in +} + +func (*GreetService) Int16PointerInAndOutput(in *int16) *int16 { + return in +} + +func (*GreetService) Int32PointerInAndOutput(in *int32) *int32 { + return in +} + +func (*GreetService) Int64PointerInAndOutput(in *int64) *int64 { + return in +} + +func (*GreetService) IntInIntOut(in int) int { + return in +} + +func (*GreetService) Int8InIntOut(in int8) int8 { + return in +} +func (*GreetService) Int16InIntOut(in int16) int16 { + return in +} +func (*GreetService) Int32InIntOut(in int32) int32 { + return in +} +func (*GreetService) Int64InIntOut(in int64) int64 { + return in +} + +func (*GreetService) UIntInUIntOut(in uint) uint { + return in +} + +func (*GreetService) UInt8InUIntOut(in uint8) uint8 { + return in +} +func (*GreetService) UInt16InUIntOut(in uint16) uint16 { + return in +} +func (*GreetService) UInt32InUIntOut(in uint32) uint32 { + return in +} +func (*GreetService) UInt64InUIntOut(in uint64) uint64 { + return in +} + +func (*GreetService) Float32InFloat32Out(in float32) float32 { + return in +} + +func (*GreetService) Float64InFloat64Out(in float64) float64 { + return in +} + +func (*GreetService) PointerFloat32InFloat32Out(in *float32) *float32 { + return in +} + +func (*GreetService) PointerFloat64InFloat64Out(in *float64) *float64 { + return in +} + +func (*GreetService) BoolInBoolOut(in bool) bool { + return in +} + +func (*GreetService) PointerBoolInBoolOut(in *bool) *bool { + return in +} + +func (*GreetService) PointerStringInStringOut(in *string) *string { + return in +} + +func (*GreetService) StructPointerInputErrorOutput(in *Person) error { + return nil +} + +func (*GreetService) StructInputStructOutput(in Person) Person { + return in +} + +func (*GreetService) StructPointerInputStructPointerOutput(in *Person) *Person { + return in +} + +func (*GreetService) MapIntInt(in map[int]int) { +} + +func (*GreetService) PointerMapIntInt(in *map[int]int) { +} + +func (*GreetService) MapIntPointerInt(in map[*int]int) { +} + +func (*GreetService) MapIntSliceInt(in map[int][]int) { +} + +func (*GreetService) MapIntSliceIntInMapIntSliceIntOut(in map[int][]int) (out map[int][]int) { + return nil +} + +func (*GreetService) ArrayInt(in [4]int) { +} + +func main() { + app := application.New(application.Options{ + Bind: []interface{}{ + &GreetService{}, + }, + }) + + app.NewWebviewWindow() + + err := app.Run() + + if err != nil { + log.Fatal(err) + } + +} diff --git a/v3/internal/parser/testdata/struct_literal_single/models.ts b/v3/internal/parser/testdata/struct_literal_single/models.ts new file mode 100644 index 000000000..91bab56ac --- /dev/null +++ b/v3/internal/parser/testdata/struct_literal_single/models.ts @@ -0,0 +1,64 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +export namespace main { + + export class Person { + name: string; + parent: Person; + details: anon1; + + static createFrom(source: any = {}) { + return new Person(source); + } + + constructor(source: any = {}) { + if ('string' === typeof source) { + source = JSON.parse(source); + } + + this.name = source['name']; + this.parent = Person.createFrom(source['parent']); + this.details = anon1.createFrom(source['details']); + + } + } + + export class anon1 { + age: number; + address: anon2; + + static createFrom(source: any = {}) { + return new anon1(source); + } + + constructor(source: any = {}) { + if ('string' === typeof source) { + source = JSON.parse(source); + } + + this.age = source['age']; + this.address = anon2.createFrom(source['address']); + + } + } + + export class anon2 { + street: string; + + static createFrom(source: any = {}) { + return new anon2(source); + } + + constructor(source: any = {}) { + if ('string' === typeof source) { + source = JSON.parse(source); + } + + this.street = source['street']; + + } + } + +} diff --git a/v3/internal/parser/testdata/variable_single/bindings_main.js b/v3/internal/parser/testdata/variable_single/bindings_main.js new file mode 100644 index 000000000..c9a4675b5 --- /dev/null +++ b/v3/internal/parser/testdata/variable_single/bindings_main.js @@ -0,0 +1,18 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + + +window.go = window.go || {}; +window.go.main = { + GreetService: { + + /** + * GreetService.Greet + * Greet someone + * @param name {string} + * @returns {Promise} + **/ + Greet: function(name) { wails.CallByID(1411160069, ...Array.prototype.slice.call(arguments, 0)); }, + }, +}; diff --git a/v3/internal/parser/testdata/variable_single/main.go b/v3/internal/parser/testdata/variable_single/main.go new file mode 100644 index 000000000..baffd783c --- /dev/null +++ b/v3/internal/parser/testdata/variable_single/main.go @@ -0,0 +1,36 @@ +package main + +import ( + _ "embed" + "github.com/wailsapp/wails/v3/pkg/application" + "log" +) + +// GreetService is great +type GreetService struct { + SomeVariable int + lowerCase string +} + +// Greet someone +func (*GreetService) Greet(name string) string { + return "Hello " + name +} + +func main() { + greetService := &GreetService{} + app := application.New(application.Options{ + Bind: []interface{}{ + greetService, + }, + }) + + app.NewWebviewWindow() + + err := app.Run() + + if err != nil { + log.Fatal(err) + } + +} diff --git a/v3/internal/parser/testdata/variable_single/models.ts b/v3/internal/parser/testdata/variable_single/models.ts new file mode 100644 index 000000000..0817f259b --- /dev/null +++ b/v3/internal/parser/testdata/variable_single/models.ts @@ -0,0 +1,5 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// TODO : nothing generated yet \ No newline at end of file diff --git a/v3/internal/parser/testdata/variable_single_from_function/bindings_main.js b/v3/internal/parser/testdata/variable_single_from_function/bindings_main.js new file mode 100644 index 000000000..c9a4675b5 --- /dev/null +++ b/v3/internal/parser/testdata/variable_single_from_function/bindings_main.js @@ -0,0 +1,18 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + + +window.go = window.go || {}; +window.go.main = { + GreetService: { + + /** + * GreetService.Greet + * Greet someone + * @param name {string} + * @returns {Promise} + **/ + Greet: function(name) { wails.CallByID(1411160069, ...Array.prototype.slice.call(arguments, 0)); }, + }, +}; diff --git a/v3/internal/parser/testdata/variable_single_from_function/main.go b/v3/internal/parser/testdata/variable_single_from_function/main.go new file mode 100644 index 000000000..0d7121ca9 --- /dev/null +++ b/v3/internal/parser/testdata/variable_single_from_function/main.go @@ -0,0 +1,40 @@ +package main + +import ( + _ "embed" + "github.com/wailsapp/wails/v3/pkg/application" + "log" +) + +// GreetService is great +type GreetService struct { + SomeVariable int + lowerCase string +} + +// Greet someone +func (*GreetService) Greet(name string) string { + return "Hello " + name +} + +func NewGreetService() *GreetService { + return &GreetService{} +} + +func main() { + greetService := NewGreetService() + app := application.New(application.Options{ + Bind: []interface{}{ + greetService, + }, + }) + + app.NewWebviewWindow() + + err := app.Run() + + if err != nil { + log.Fatal(err) + } + +} diff --git a/v3/internal/parser/testdata/variable_single_from_function/models.ts b/v3/internal/parser/testdata/variable_single_from_function/models.ts new file mode 100644 index 000000000..0817f259b --- /dev/null +++ b/v3/internal/parser/testdata/variable_single_from_function/models.ts @@ -0,0 +1,5 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// TODO : nothing generated yet \ No newline at end of file diff --git a/v3/internal/parser/testdata/variable_single_from_other_function/bindings_main.js b/v3/internal/parser/testdata/variable_single_from_other_function/bindings_main.js new file mode 100644 index 000000000..2c02c7608 --- /dev/null +++ b/v3/internal/parser/testdata/variable_single_from_other_function/bindings_main.js @@ -0,0 +1,28 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import {main} from './models'; + +window.go = window.go || {}; +window.go.main = { + GreetService: { + + /** + * GreetService.Greet + * Greet does XYZ + * @param name {string} + * @returns {Promise} + **/ + Greet: function(name) { wails.CallByID(1411160069, ...Array.prototype.slice.call(arguments, 0)); }, + + /** + * GreetService.NewPerson + * NewPerson creates a new person + * @param name {string} + * @returns {Promise} + **/ + NewPerson: function(name) { wails.CallByID(1661412647, ...Array.prototype.slice.call(arguments, 0)); }, + }, +}; + diff --git a/v3/internal/parser/testdata/variable_single_from_other_function/bindings_services.js b/v3/internal/parser/testdata/variable_single_from_other_function/bindings_services.js new file mode 100644 index 000000000..4e2578071 --- /dev/null +++ b/v3/internal/parser/testdata/variable_single_from_other_function/bindings_services.js @@ -0,0 +1,20 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import {services} from './models'; + +window.go = window.go || {}; +window.go.services = { + OtherService: { + + /** + * OtherService.Yay + * + * + * @returns {Promise} + **/ + Yay: function() { wails.CallByID(302702907, ...Array.prototype.slice.call(arguments, 0)); }, + }, +}; + diff --git a/v3/internal/parser/testdata/variable_single_from_other_function/main.go b/v3/internal/parser/testdata/variable_single_from_other_function/main.go new file mode 100644 index 000000000..9d10a301e --- /dev/null +++ b/v3/internal/parser/testdata/variable_single_from_other_function/main.go @@ -0,0 +1,50 @@ +package main + +import ( + _ "embed" + "github.com/wailsapp/wails/v3/internal/parser/testdata/variable_single_from_other_function/services" + "log" + + "github.com/wailsapp/wails/v3/pkg/application" +) + +// GreetService is great +type GreetService struct { + SomeVariable int + lowerCase string + target *Person +} + +type Person struct { + Name string + Address *services.Address +} + +// Greet does XYZ +func (*GreetService) Greet(name string) string { + return "Hello " + name +} + +// NewPerson creates a new person +func (*GreetService) NewPerson(name string) *Person { + return &Person{Name: name} +} + +func main() { + otherService := services.NewOtherService() + app := application.New(application.Options{ + Bind: []interface{}{ + &GreetService{}, + otherService, + }, + }) + + app.NewWebviewWindow() + + err := app.Run() + + if err != nil { + log.Fatal(err) + } + +} diff --git a/v3/internal/parser/testdata/variable_single_from_other_function/models.ts b/v3/internal/parser/testdata/variable_single_from_other_function/models.ts new file mode 100644 index 000000000..a6639f8eb --- /dev/null +++ b/v3/internal/parser/testdata/variable_single_from_other_function/models.ts @@ -0,0 +1,51 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +export namespace main { + + export class Person { + name: string; + address: services.Address; + + static createFrom(source: any = {}) { + return new Person(source); + } + + constructor(source: any = {}) { + if ('string' === typeof source) { + source = JSON.parse(source); + } + + this.name = source['name']; + this.address = services.Address.createFrom(source['address']); + + } + } + +} + +export namespace services { + + export class Address { + street: string; + state: string; + country: string; + + static createFrom(source: any = {}) { + return new Address(source); + } + + constructor(source: any = {}) { + if ('string' === typeof source) { + source = JSON.parse(source); + } + + this.street = source['street']; + this.state = source['state']; + this.country = source['country']; + + } + } + +} diff --git a/v3/internal/parser/testdata/variable_single_from_other_function/services/other.go b/v3/internal/parser/testdata/variable_single_from_other_function/services/other.go new file mode 100644 index 000000000..2daa9df17 --- /dev/null +++ b/v3/internal/parser/testdata/variable_single_from_other_function/services/other.go @@ -0,0 +1,26 @@ +package services + +// OtherService is a struct +// that does things +type OtherService struct { + t int +} + +type Address struct { + Street string + State string + Country string +} + +// Yay does this and that +func (o *OtherService) Yay() *Address { + return &Address{ + Street: "123 Pitt Street", + State: "New South Wales", + Country: "Australia", + } +} + +func NewOtherService() *OtherService { + return &OtherService{} +} diff --git a/v3/internal/plugins/plugins.go b/v3/internal/plugins/plugins.go new file mode 100644 index 000000000..05de5f908 --- /dev/null +++ b/v3/internal/plugins/plugins.go @@ -0,0 +1,36 @@ +package plugins + +import ( + "embed" + "fmt" + "io/fs" + "os" + "path/filepath" + + "github.com/wailsapp/wails/v3/internal/flags" + + "github.com/leaanthony/gosod" + + "github.com/samber/lo" +) + +//go:embed template +var pluginTemplate embed.FS + +type TemplateOptions struct { + *flags.PluginInit +} + +func Install(options *flags.PluginInit) error { + + if options.OutputDir == "." || options.OutputDir == "" { + options.OutputDir = filepath.Join(lo.Must(os.Getwd()), options.Name) + } + fmt.Printf("Creating plugin '%s' into '%s'\n", options.Name, options.OutputDir) + tfs, err := fs.Sub(pluginTemplate, "template") + if err != nil { + return err + } + + return gosod.New(tfs).Extract(options.OutputDir, options) +} diff --git a/v3/internal/plugins/template/NEXT STEPS.md b/v3/internal/plugins/template/NEXT STEPS.md new file mode 100644 index 000000000..e58c6b119 --- /dev/null +++ b/v3/internal/plugins/template/NEXT STEPS.md @@ -0,0 +1,82 @@ +# Next Steps + +Congratulations on generating a plugin. This guide will help you author your plugin +and provide some tips on how to get started. + +## Plugin Structure + +The plugin is a standard Go module that adheres to the following interface: + +```go +type Plugin interface { + Name() string + Init(app *App) error + Shutdown() +} +``` + +The `Name()` method returns the name of the plugin. It should follow the Go module naming convention +and have a prefix of `wails-plugin-`, e.g. `github.com/myuser/wails-plugin-example`. + +The `Init()` method is called when the plugin is loaded. The `App` parameter is a pointer to the +main application struct. This may be used for showing dialogs, listening for events or even opening +new windows. The `Init()` method should return an error if it fails to initialise. This method is +called synchronously so the application will not start until it returns. + +The `Shutdown()` method is called when the application is shutting down. This is a good place to +perform any cleanup. This method is called synchronously so the application will not exit completely until +it returns. + +## Plugin Directory Structure + +The plugin directory structure is as follows: + +``` +plugin-name +├── models.d.ts +├── plugin.js +├── plugin.go +├── README.md +├── go.mod +├── go.sum +└── plugin.toml +``` + +### `plugin.go` + +This file contains the plugin code. It should contain a struct that implements the `Plugin` interface +and a `NewPlugin()` method that returns a pointer to the struct. Methods are exported by capitalising +the first letter of the method name. These methods may be called from the frontend. If methods +accept or return structs, these structs must be exported. + +### `plugin.js` + +This file should contain any JavaScript code that may help developers use the plugin. +In the example plugin, this file contains function wrappers for the plugin methods. +It's good to include JSDocs as that will help developers using your plugin. + +### `models.d.ts` + +This file should contain TypeScript definitions for any structs that are passed +or returned from the plugin. +` +### `plugin.toml` + +This file contains the plugin metadata. It is important to fill this out correctly +as it will be used by the Wails CLI. + +### `README.md` + +This file should contain a description of the plugin and how to use it. It should +also contain a link to the plugin repository and how to report bugs. + +### `go.mod` and `go.sum` + +These are standard Go module files. The package name in `go.mod` should match the +name of the plugin, e.g. `github.com/myuser/wails-plugin-example`. + +## Promoting your Plugin + +Once you have created your plugin, you should promote it on the Wails Discord server +in the `#plugins` channel. You should also open a PR to promote your plugin on the Wails +website. Update the `website/content/plugins.md` file and add your plugin to the list. \ No newline at end of file diff --git a/v3/internal/plugins/template/README.tmpl.md b/v3/internal/plugins/template/README.tmpl.md new file mode 100644 index 000000000..2f4944a3d --- /dev/null +++ b/v3/internal/plugins/template/README.tmpl.md @@ -0,0 +1,38 @@ +# {{.Name}} Plugin + +This example plugin provides a way to generate hashes of strings. + +## Installation + +Add the plugin to the `Plugins` option in the Applications options: + +```go + Plugins: map[string]application.Plugin{ + "{{.Name}}": {{.Name}}.NewPlugin(), + }, +``` + +## Usage + +You can then call the methods from the frontend: + +```js + wails.Plugin("{{.Name}}","All","hello world").then((result) => console.log(result)) +``` + +This method returns a struct with the following fields: + +```typescript + interface Hashes { + MD5: string; + SHA1: string; + SHA256: string; + } +``` + +A TypeScript definition file is provided for this interface. + +## Support + +If you find a bug in this plugin, please raise a ticket [here](https://github.com/plugin/repository). +Please do not contact the Wails team for support. \ No newline at end of file diff --git a/v3/internal/plugins/template/go.mod.tmpl b/v3/internal/plugins/template/go.mod.tmpl new file mode 100644 index 000000000..84ff2e07b --- /dev/null +++ b/v3/internal/plugins/template/go.mod.tmpl @@ -0,0 +1,12 @@ +module {{.Name}} + +go 1.20 + +require github.com/wailsapp/wails/v3 v3.0.0-alpha.0 + +require ( + github.com/imdario/mergo v0.3.12 // indirect + github.com/leaanthony/slicer v1.5.0 // indirect + github.com/wailsapp/mimetype v1.4.1 // indirect + golang.org/x/net v0.0.0-20220722155237-a158d28d115b // indirect +) diff --git a/v3/internal/plugins/template/go.sum b/v3/internal/plugins/template/go.sum new file mode 100644 index 000000000..2e2337936 --- /dev/null +++ b/v3/internal/plugins/template/go.sum @@ -0,0 +1,20 @@ +github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU= +github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= +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/wailsapp/mimetype v1.4.1 h1:pQN9ycO7uo4vsUUuPeHEYoUkLVkaRntMnHJxVwYhwHs= +github.com/wailsapp/mimetype v1.4.1/go.mod h1:9aV5k31bBOv5z6u+QP8TltzvNGJPmNJD4XlAL3U+j3o= +github.com/wailsapp/wails/v3 v3.0.0-alpha.0 h1:T5gqG98Xr8LBf69oxlPkhpsFD59w2SnqUZk6XHj8Zoc= +github.com/wailsapp/wails/v3 v3.0.0-alpha.0/go.mod h1:OAfO5bP0TSUvCIHZYc6Dqfow/9RqxzHvYtmhWPpo1c0= +golang.org/x/net v0.0.0-20210505024714-0287a6fb4125/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b h1:PxfKdU9lEEDYjdIzOtC4qFWgkU2rGHdKlKowJSMN9h0= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/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/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/v3/internal/plugins/template/models.d.ts.tmpl b/v3/internal/plugins/template/models.d.ts.tmpl new file mode 100644 index 000000000..567c91d70 --- /dev/null +++ b/v3/internal/plugins/template/models.d.ts.tmpl @@ -0,0 +1,10 @@ +// models.d.ts +// This file should contain any models that are used by the plugin. + +export namespace {{.Name}}Plugin { + export interface Hashes { + MD5: string; + SHA1: string; + SHA256: string; + } +} \ No newline at end of file diff --git a/v3/internal/plugins/template/plugin.go.tmpl b/v3/internal/plugins/template/plugin.go.tmpl new file mode 100644 index 000000000..e8104f2bd --- /dev/null +++ b/v3/internal/plugins/template/plugin.go.tmpl @@ -0,0 +1,67 @@ +package {{.Name}} + +import ( + "github.com/wailsapp/wails/v3/pkg/application" +) + +// ---------------- Plugin Setup ---------------- +// This is the main plugin struct. It can be named anything you like. +// It must implement the application.Plugin interface. +// Both the Init() and Shutdown() methods are called synchronously when the app starts and stops. + +type Config struct { + // Add any configuration options here +} + +type Plugin struct{ + config *Config + app *application.App +} + +func NewPlugin(config *Config) *Plugin { + return &Plugin{ + config: config, + } +} + +// Shutdown is called when the app is shutting down +// You can use this to clean up any resources you have allocated +func (p *Plugin) Shutdown() {} + +// Name returns the name of the plugin. +// You should use the go module format e.g. github.com/myuser/myplugin +func (p *Plugin) Name() string { + return "github.com/myuser/{{.Name}}" +} + +// Init is called when the app is starting up. You can use this to +// initialise any resources you need. You can also access the application +// instance via the app property. +func (p *Plugin) Init(app *application.App) error { + p.app = app + return nil +} + +// Exported returns a list of exported methods that can be called from the frontend +func (p *Plugin) CallableByJS() []string { + return []string{ + "Greet", + } +} + +// InjectJS returns any JS that should be injected into the frontend +func (p *Plugin) InjectJS() string { + return "" +} + +// ---------------- Plugin Methods ---------------- +// Plugin methods are just normal Go methods. You can add as many as you like. +// The only requirement is that they are exported (start with a capital letter). +// You can also return any type that is JSON serializable. +// Any methods that you want to be callable from the frontend must be returned by the +// CallableByJS() method above. +// See https://golang.org/pkg/encoding/json/#Marshal for more information. + +func (p *Plugin) Greet(name string) string { + return "Hello " + name +} diff --git a/v3/internal/plugins/template/plugin.tmpl.js b/v3/internal/plugins/template/plugin.tmpl.js new file mode 100644 index 000000000..c62bbb6e7 --- /dev/null +++ b/v3/internal/plugins/template/plugin.tmpl.js @@ -0,0 +1,46 @@ +// plugin.js +// This file should contain helper functions for the that can be used by the frontend. +// Below are examples of how to use JSDoc to define the Hashes struct and the exported functions. + +/** + * @typedef {Object} Hashes - A collection of hashes. + * @property {string} md5 - The MD5 hash of a string, represented as a hexadecimal string. + * @property {string} sha1 - The SHA-1 hash of a string, represented as a hexadecimal string. + * @property {string} sha256 - The SHA-256 hash of a string, represented as a hexadecimal string. + */ + +/** + * Generate all hashes for a string. + * @param input {string} - The string to generate hashes for. + * @returns {Promise} + */ +export function All(input) { + return wails.Plugin("{{.Name}}", "All", input); +} + +/** + * Generate the MD5 hash for a string. + * @param input {string} - The string to generate the hash for. + * @returns {Promise} + */ +export function MD5(input) { + return wails.Plugin("{{.Name}}", "MD5", input); +} + +/** + * Generate the SHA-1 hash for a string. + * @param input {string} - The string to generate the hash for. + * @returns {Promise} + */ +export function SHA1(input) { + return wails.Plugin("{{.Name}}", "SHA1", input); +} + +/** + * Generate the SHA-256 hash for a string. + * @param input {string} - The string to generate the hash for. + * @returns {Promise} + */ +export function SHA256(input) { + return wails.Plugin("{{.Name}}", "SHA256", input); +} \ No newline at end of file diff --git a/v3/internal/plugins/template/plugin.tmpl.toml b/v3/internal/plugins/template/plugin.tmpl.toml new file mode 100644 index 000000000..76e7aa384 --- /dev/null +++ b/v3/internal/plugins/template/plugin.tmpl.toml @@ -0,0 +1,11 @@ +# This is the plugin definition file for the "{{.Name}}" plugin. + +Name = "{{.Name}}" +Description = "{{.Description}}" +Author = "" +Version = "" +Website = "" +Repository = "" +License = "" + + diff --git a/v3/internal/runtime/.gitignore b/v3/internal/runtime/.gitignore new file mode 100644 index 000000000..cdd93f315 --- /dev/null +++ b/v3/internal/runtime/.gitignore @@ -0,0 +1,2 @@ +node_modules +.task \ No newline at end of file diff --git a/v3/internal/runtime/README.md b/v3/internal/runtime/README.md new file mode 100644 index 000000000..f6c2f249b --- /dev/null +++ b/v3/internal/runtime/README.md @@ -0,0 +1,3 @@ +# Runtime + +To rebuild the runtime run `task build-runtime` or if you have Wails v3 CLI, you can use `wails task build-runtime`. diff --git a/v3/internal/runtime/Taskfile.yaml b/v3/internal/runtime/Taskfile.yaml new file mode 100644 index 000000000..0e0f7c2b6 --- /dev/null +++ b/v3/internal/runtime/Taskfile.yaml @@ -0,0 +1,108 @@ +# https://taskfile.dev + +version: "3" + +tasks: + install-deps: + internal: true + sources: + - package.json + cmds: + - npm install + + test: + cmds: + - npx vitest run + + update: + cmds: + - npx npm-check-updates -u + + build:debug: + internal: true + cmds: + - npx esbuild desktop/main.js --bundle --tree-shaking=true --sourcemap=inline --outfile=runtime_debug_desktop_{{.PLATFORM}}.js --define:DEBUG=true --define:WINDOWS={{.WINDOWS}} --define:DARWIN={{.DARWIN}} --define:LINUX={{.LINUX}} --define:PLATFORM={{.PLATFORM}} --define:INVOKE={{.INVOKE}} + + build:debug:windows: + cmds: + - task: build:debug + vars: + WINDOWS: true + DARWIN: false + LINUX: false + PLATFORM: windows + INVOKE: "chrome.webview.postMessage" + + build:debug:linux: + cmds: + - task: build:debug + vars: + WINDOWS: false + DARWIN: false + LINUX: true + PLATFORM: linux + INVOKE: "webkit.messageHandlers.external.postMessage" + + build:debug:darwin: + cmds: + - task: build:debug + vars: + WINDOWS: false + DARWIN: true + LINUX: false + PLATFORM: darwin + INVOKE: "webkit.messageHandlers.external.postMessage" + + build:production: + internal: true + cmds: + - npx esbuild desktop/main.js --bundle --tree-shaking=true --minify --outfile=runtime_production_desktop_{{.PLATFORM}}.js --define:DEBUG=false --define:WINDOWS={{.WINDOWS}} --define:DARWIN={{.DARWIN}} --define:LINUX={{.LINUX}} --define:PLATFORM={{.PLATFORM}} --define:INVOKE={{.INVOKE}} + + build:production:windows: + cmds: + - task: build:production + vars: + WINDOWS: true + DARWIN: false + LINUX: false + PLATFORM: windows + INVOKE: "chrome.webview.postMessage" + + build:production:linux: + cmds: + - task: build:production + vars: + WINDOWS: false + DARWIN: false + LINUX: true + PLATFORM: linux + INVOKE: "webkit.messageHandlers.external.postMessage" + + build:production:darwin: + cmds: + - task: build:production + vars: + WINDOWS: false + DARWIN: true + LINUX: false + PLATFORM: darwin + INVOKE: "webkit.messageHandlers.external.postMessage" + + build:all: + internal: true + deps: + - build:debug:windows + - build:debug:linux + - build:debug:darwin + - build:production:windows + - build:production:linux + - build:production:darwin + + cmds: + - cmd: echo "Build Complete." + + build: + deps: + - install-deps + cmds: + - task: build:all diff --git a/v3/internal/runtime/assets.go b/v3/internal/runtime/assets.go new file mode 100644 index 000000000..fb3f09ebe --- /dev/null +++ b/v3/internal/runtime/assets.go @@ -0,0 +1,23 @@ +//go:build production + +package runtime + +var RuntimeAssetsBundle = &RuntimeAssets{ + runtimeDesktopJS: DesktopRuntime, +} + +type RuntimeAssets struct { + runtimeDesktopJS []byte +} + +func (r *RuntimeAssets) DesktopIPC() []byte { + return []byte("") +} + +func (r *RuntimeAssets) WebsocketIPC() []byte { + return []byte("") +} + +func (r *RuntimeAssets) RuntimeDesktopJS() []byte { + return r.runtimeDesktopJS +} diff --git a/v3/internal/runtime/assets_dev.go b/v3/internal/runtime/assets_dev.go new file mode 100644 index 000000000..c5e5ffe7d --- /dev/null +++ b/v3/internal/runtime/assets_dev.go @@ -0,0 +1,23 @@ +//go:build !production + +package runtime + +var RuntimeAssetsBundle = &RuntimeAssets{ + runtimeDesktopJS: DesktopRuntime, +} + +type RuntimeAssets struct { + runtimeDesktopJS []byte +} + +func (r *RuntimeAssets) DesktopIPC() []byte { + return []byte("") +} + +func (r *RuntimeAssets) WebsocketIPC() []byte { + return []byte("") +} + +func (r *RuntimeAssets) RuntimeDesktopJS() []byte { + return r.runtimeDesktopJS +} diff --git a/v3/internal/runtime/desktop/README.md b/v3/internal/runtime/desktop/README.md new file mode 100644 index 000000000..78ef387e4 --- /dev/null +++ b/v3/internal/runtime/desktop/README.md @@ -0,0 +1,3 @@ +# README + +After updating any files in this directory, you must run `wails task build:runtime` to regenerate the compiled JS. \ No newline at end of file diff --git a/v3/internal/runtime/desktop/api/README.md b/v3/internal/runtime/desktop/api/README.md new file mode 100644 index 000000000..0cb344e83 --- /dev/null +++ b/v3/internal/runtime/desktop/api/README.md @@ -0,0 +1,363 @@ +# Wails API + +This package provides a typed Javascript API for Wails applications. + +It provides methods for the following components: + +- [Dialog](#dialog) +- [Events](#events) +- [Window](#window) +- [Plugin](#plugin) +- [Screens](#screens) +- [Application](#application) + +## Installation + +In your Wails application, run the following command in the frontend project directory: + +```bash +npm install -D @wailsapp/api +``` + +## Usage + +Import the API into your application: + +```javascript +import * as Wails from "@wailsapp/api"; +``` + +Then use the API components: + +```javascript +function showDialog() { + Wails.Dialog.Info({ + Title: "Hello", + }).then((result) => { + console.log("Result: " + result); + }); +} +``` + +Individual components of the API can also be imported directly. + + + +## API + +### Dialog + +The Dialog API provides access to the native system dialogs. + +```javascript +import { Dialog } from "@wailsapp/api"; + +function example() { + Dialog.Info({ + Title: "Hello", + }).then((result) => { + console.log("Result: " + result); + }); +} +``` + +#### Message Dialogs + +Message dialogs are used to display a message to the user. +They can be used to display information, errors, warnings and questions. +Each method returns the button that was pressed by the user. + +- `Info(options: MessageDialogOptions): Promise` +- `Error(options: MessageDialogOptions): Promise` +- `Warning(options: MessageDialogOptions): Promise` +- `Question(options: MessageDialogOptions): Promise` + +#### Open Dialog + +The Open Dialog is used to open a file or directory. It returns the path of the selected file or directory. +If the `AllowsMultipleFiles` option is set, multiple files or directories can be selected and are returned +as an array of file paths. + +- `Open(options: OpenDialogOptions): Promise` + +#### Save Dialog + +The Save Dialog is used to save a file. It returns the path of the selected file. + +- `Save(options: SaveDialogOptions): Promise` + +### Events + +The Events API provides access to the Wails event system. This is a global event system +that can be used to send events between the Go and Javascript. +Events are available to every window in a multi-window application. +These API methods are specific to the window in which they are called in. + +```javascript +import { Events } from "@wailsapp/api"; + +function example() { + // Emit an event + Events.Emit("myevent", { message: "Hello" }); + + // Subscribe to an event + let unsub = Events.On("otherEvent", (data) => { + console.log("Received event: " + data); + }); + + // Unsubscribe from the event + unsub(); +} +``` + +#### Emit + +Emit an event with optional data. + +- `Emit(eventName: string, data?: any): void` + +#### Subscribe + +Three methods are provided to subscribe to an event: + - `On(eventName: string, callback: (data: any) => void): () => void` - Subscribe to all events of the given name + - `Once(eventName: string, callback: (data: any) => void): () => void` - Subscribe to one event of the given name + - `OnMultiple(eventName: string, callback: (data: any) => void, count: number): () => void` - Subscribe to multiple events of the given name + +The callback will be called when the event is emitted. +The returned function can be called to unsubscribe from the event. + +#### Unsubscribe + +As well as unsubscribing from a single event, you can unsubscribe from events of a given name or all events. + - `Off(eventName: string, additionalEventNames: ...string): void` - Unsubscribe from all events of the given name(s) + - `OffAll(): void` - Unsubscribe all events + +### Window + +The Window API provides a number of methods that interact with the window in which the API is called. + +- `Center: (void) => void` - Center the window +- `SetTitle: (title) => void` - Set the window title +- `Fullscreen: () => void` - Set the window to fullscreen +- `UnFullscreen: () => void` - Restore a fullscreen window +- `SetSize: (width: number, height: number) => void` - Set the window size +- `Size: () => Size` - Get the window size +- `SetMaxSize: (width, height) => void` - Set the window maximum size +- `SetMinSize: (width, height) => void` - Set the window minimum size +- `SetAlwaysOnTop: (onTop) => void` - Set window to be always on top +- `SetPosition: (x, y) => void` - Set the window position +- `Position: () => Position` - Get the window position +- `SetResizable: (resizable) => void` - Set whether the window is resizable +- `Screen: () => Screen` - Get information about the screen the window is on +- `Hide: () => void` - Hide the window +- `Show: () => void` - Show the window +- `Maximise: () => void` - Maximise the window +- `Close: () => void` - Close the window +- `ToggleMaximise: () => void` - Toggle the window maximise state +- `UnMaximise: () => void` - UnMaximise the window +- `Minimise: () => void` - Minimise the window +- `UnMinimise: () => void` - UnMinimise the window +- `SetBackgroundColour: (r, g, b, a) => void` - Set the background colour of the window + +### Plugin + +The Plugin API provides access to the Wails plugin system. +This method provides the ability to call a plugin method from the frontend. + +```javascript + +import { Plugin } from "@wailsapp/api"; + +function example() { + // Call a plugin method + Plugin.Call("myplugin", "MyMethod", { message: "Hello" }).then((result) => { + console.log("Result: " + result); + }); +} +``` + +### Screens + +The Screens API provides access to the Wails screen system. + +```javascript +import { Screens } from "@wailsapp/api"; + +function example() { + // Get all attatched screens + Screens.GetAll().then((screens) => { + console.log("Screens: " + screens); + }); + + // Get the primary screen + Screens.GetPrimary().then((screen) => { + console.log("Primary screen: " + screen); + }); + + // Get the screen the window is on + Screens.GetCurrent().then((screen) => { + console.log("Window screen: " + screen); + }); +} +``` + +- `GetAll: () => Promise` - Get all screens +- `GetPrimary: () => Promise` - Get the primary screen +- `GetCurrent: () => Promise` - Get the screen the window is on + +### Application + +The Application API provides access to the Wails application system. + +```javascript +import { Application } from "@wailsapp/api"; + +function example() { + + // Hide the application + Application.Hide(); + + // Shopw the application + Application.Show(); + + // Quit the application + Application.Quit(); + +} +``` + +- `Hide: () => void` - Hide the application +- `Show: () => void` - Show the application +- `Quit: () => void` - Quit the application + +## Types + +This is a comprehensive list of types used by the Wails API. + +```typescript + +export interface Button { + // The label of the button + Label?: string; + // True if this button is the cancel button (selected when pressing escape) + IsCancel?: boolean; + // True if this button is the default button (selected when pressing enter) + IsDefault?: boolean; +} + +interface MessageDialogOptions { + // The title for the dialog + Title?: string; + // The message to display + Message?: string; + // The buttons to use on the dialog + Buttons?: Button[]; +} + +export interface OpenFileDialogOptions { + // Allows the user to be able to select directories + CanChooseDirectories?: boolean; + // Allows the user to be able to select files + CanChooseFiles?: boolean; + // Provide an option to create directories in the dialog + CanCreateDirectories?: boolean; + // Makes the dialog show hidden files + ShowHiddenFiles?: boolean; + // Whether the dialog should follow filesystem aliases + ResolvesAliases?: boolean; + // Allow the user to select multiple files or directories + AllowsMultipleSelection?: boolean; + // Hide the extension when showing the filename + HideExtension?: boolean; + // Allow the user to select files where the system hides their extensions + CanSelectHiddenExtension?: boolean; + // Treats file packages as directories, e.g. .app on macOS + TreatsFilePackagesAsDirectories?: boolean; + // Allows selection of filetypes not specified in the filters + AllowsOtherFiletypes?: boolean; + // The file filters to use in the dialog + Filters?: FileFilter[]; + // The title of the dialog + Title?: string; + // The message to display + Message?: string; + // The label for the select button + ButtonText?: string; + // The default directory to open the dialog in + Directory?: string; +} +export interface FileFilter { + // The display name for the filter, e.g. "Text Files" + DisplayName?: string; + // The pattern to use for the filter, e.g. "*.txt;*.md" + Pattern?: string; +} +export interface SaveFileDialogOptions { + // Provide an option to create directories in the dialog + CanCreateDirectories?: boolean; + // Makes the dialog show hidden files + ShowHiddenFiles?: boolean; + // Allow the user to select files where the system hides their extensions + CanSelectHiddenExtension?: boolean; + // Allows selection of filetypes not specified in the filters + AllowOtherFiletypes?: boolean; + // Hide the extension when showing the filename + HideExtension?: boolean; + // Treats file packages as directories, e.g. .app on macOS + TreatsFilePackagesAsDirectories?: boolean; + // The message to show in the dialog + Message?: string; + // The default directory to open the dialog in + Directory?: string; + // The default filename to use in the dialog + Filename?: string; + // The label for the select button + ButtonText?: string; +} + +export interface Screen { + // The screen ID + Id: string; + // The screen name + Name: string; + // The screen scale. 1 = standard resolution, 2: 2x retina, etc. + Scale: number; + // The X position of the screen + X: number; + // The Y position of the screen + Y: number; + // The width and height of the screen + Size: Size; + // The bounds of the screen + Bounds: Rect; + // The work area of the screen + WorkArea: Rect; + // True if this is the primary screen + IsPrimary: boolean; + // The rotation of the screen + Rotation: number; +} +export interface Rect { + X: number; + Y: number; + Width: number; + Height: number; +} + +export interface WailsEvent { + // The name of the event + Name: string; + // The data associated with the event + Data?: any; +} + +export interface Size { + Width: number; + Height: number; +} +export interface Position { + X: number; + Y: number; +} + +``` \ No newline at end of file diff --git a/v3/internal/runtime/desktop/api/application.js b/v3/internal/runtime/desktop/api/application.js new file mode 100644 index 000000000..9ae7ba915 --- /dev/null +++ b/v3/internal/runtime/desktop/api/application.js @@ -0,0 +1,35 @@ +/* + _ __ _ __ +| | / /___ _(_) /____ +| | /| / / __ `/ / / ___/ +| |/ |/ / /_/ / / (__ ) +|__/|__/\__,_/_/_/____/ +The electron alternative for Go +(c) Lea Anthony 2019-present +*/ + +/* jshint esversion: 9 */ + +/** + * The Application API provides methods to interact with the application. + */ +export const Application = { + /** + * Hides the application + */ + Hide: () => { + return wails.Application.Hide(); + }, + /** + * Shows the application + */ + Show: () => { + return wails.Application.Show(); + }, + /** + * Quits the application + */ + Quit: () => { + return wails.Application.Quit(); + }, +}; diff --git a/v3/internal/runtime/desktop/api/browser.js b/v3/internal/runtime/desktop/api/browser.js new file mode 100644 index 000000000..3807ad640 --- /dev/null +++ b/v3/internal/runtime/desktop/api/browser.js @@ -0,0 +1,24 @@ +/* + _ __ _ __ +| | / /___ _(_) /____ +| | /| / / __ `/ / / ___/ +| |/ |/ / /_/ / / (__ ) +|__/|__/\__,_/_/_/____/ +The electron alternative for Go +(c) Lea Anthony 2019-present +*/ + +/* jshint esversion: 9 */ + +/** + * The Browser API provides methods to interact with the system browser. + */ +export const Browser = { + /** + * Opens a browser window to the given URL + * @returns {Promise} + */ + OpenURL: (url) => { + return wails.Browser.OpenURL(url); + }, +}; diff --git a/v3/internal/runtime/desktop/api/clipboard.js b/v3/internal/runtime/desktop/api/clipboard.js new file mode 100644 index 000000000..c18dcbe18 --- /dev/null +++ b/v3/internal/runtime/desktop/api/clipboard.js @@ -0,0 +1,31 @@ +/* + _ __ _ __ +| | / /___ _(_) /____ +| | /| / / __ `/ / / ___/ +| |/ |/ / /_/ / / (__ ) +|__/|__/\__,_/_/_/____/ +The electron alternative for Go +(c) Lea Anthony 2019-present +*/ + +/* jshint esversion: 9 */ + +/** + * The Clipboard API provides methods to interact with the system clipboard. + */ +export const Clipboard = { + /** + * Gets the text from the clipboard + * @returns {Promise} + */ + Text: () => { + return wails.Clipboard.Text(); + }, + /** + * Sets the text on the clipboard + * @param {string} text - text to set in the clipboard + */ + SetText: (text) => { + return wails.Clipboard.SetText(text); + }, +}; diff --git a/v3/internal/runtime/desktop/api/dialogs.js b/v3/internal/runtime/desktop/api/dialogs.js new file mode 100644 index 000000000..e129c31da --- /dev/null +++ b/v3/internal/runtime/desktop/api/dialogs.js @@ -0,0 +1,75 @@ +/* + _ __ _ __ +| | / /___ _(_) /____ +| | /| / / __ `/ / / ___/ +| |/ |/ / /_/ / / (__ ) +|__/|__/\__,_/_/_/____/ +The electron alternative for Go +(c) Lea Anthony 2019-present +*/ + +/* jshint esversion: 9 */ + +/** + * @typedef {import("./types").MessageDialogOptions} MessageDialogOptions + * @typedef {import("./types").OpenDialogOptions} OpenDialogOptions + * @typedef {import("./types").SaveDialogOptions} SaveDialogOptions + */ +/** + * The Dialog API provides methods to interact with system dialogs. + */ +export const Dialog = { + /** + * Shows an info dialog + * @param {MessageDialogOptions} options - options for the dialog + * @returns {Promise} + */ + Info: (options) => { + return wails.Dialog.Info(options); + }, + /** + * Shows a warning dialog + * @param {MessageDialogOptions} options - options for the dialog + * @returns {Promise} + */ + Warning: (options) => { + return wails.Dialog.Warning(options); + }, + /** + * Shows an error dialog + * @param {MessageDialogOptions} options - options for the dialog + * @returns {Promise} + */ + Error: (options) => { + return wails.Dialog.Error(options); + }, + + /** + * Shows a question dialog + * @param {MessageDialogOptions} options - options for the dialog + * @returns {Promise} + */ + Question: (options) => { + return wails.Dialog.Question(options); + }, + + /** + * Shows a file open dialog and returns the files selected by the user. + * A blank string indicates that the dialog was cancelled. + * @param {OpenDialogOptions} options - options for the dialog + * @returns {Promise|Promise} + */ + OpenFile: (options) => { + return wails.Dialog.OpenFile(options); + }, + + /** + * Shows a file save dialog and returns the filename given by the user. + * A blank string indicates that the dialog was cancelled. + * @param {SaveDialogOptions} options - options for the dialog + * @returns {Promise} + */ + SaveFile: (options) => { + return wails.Dialog.SaveFile(options); + }, +}; \ No newline at end of file diff --git a/v3/internal/runtime/desktop/api/event_types.js b/v3/internal/runtime/desktop/api/event_types.js new file mode 100644 index 000000000..6ba09269b --- /dev/null +++ b/v3/internal/runtime/desktop/api/event_types.js @@ -0,0 +1,178 @@ + +export const EventTypes = { + Windows: { + SystemThemeChanged: "windows:SystemThemeChanged", + APMPowerStatusChange: "windows:APMPowerStatusChange", + APMSuspend: "windows:APMSuspend", + APMResumeAutomatic: "windows:APMResumeAutomatic", + APMResumeSuspend: "windows:APMResumeSuspend", + APMPowerSettingChange: "windows:APMPowerSettingChange", + ApplicationStarted: "windows:ApplicationStarted", + WebViewNavigationCompleted: "windows:WebViewNavigationCompleted", + WindowInactive: "windows:WindowInactive", + WindowActive: "windows:WindowActive", + WindowClickActive: "windows:WindowClickActive", + WindowMaximise: "windows:WindowMaximise", + WindowUnMaximise: "windows:WindowUnMaximise", + WindowFullscreen: "windows:WindowFullscreen", + WindowUnFullscreen: "windows:WindowUnFullscreen", + WindowRestore: "windows:WindowRestore", + WindowMinimise: "windows:WindowMinimise", + WindowUnMinimise: "windows:WindowUnMinimise", + WindowClose: "windows:WindowClose", + WindowSetFocus: "windows:WindowSetFocus", + WindowKillFocus: "windows:WindowKillFocus", + WindowDragDrop: "windows:WindowDragDrop", + WindowDragEnter: "windows:WindowDragEnter", + WindowDragLeave: "windows:WindowDragLeave", + WindowDragOver: "windows:WindowDragOver", + }, + Mac: { + ApplicationDidBecomeActive: "mac:ApplicationDidBecomeActive", + ApplicationDidChangeBackingProperties: "mac:ApplicationDidChangeBackingProperties", + ApplicationDidChangeEffectiveAppearance: "mac:ApplicationDidChangeEffectiveAppearance", + ApplicationDidChangeIcon: "mac:ApplicationDidChangeIcon", + ApplicationDidChangeOcclusionState: "mac:ApplicationDidChangeOcclusionState", + ApplicationDidChangeScreenParameters: "mac:ApplicationDidChangeScreenParameters", + ApplicationDidChangeStatusBarFrame: "mac:ApplicationDidChangeStatusBarFrame", + ApplicationDidChangeStatusBarOrientation: "mac:ApplicationDidChangeStatusBarOrientation", + ApplicationDidFinishLaunching: "mac:ApplicationDidFinishLaunching", + ApplicationDidHide: "mac:ApplicationDidHide", + ApplicationDidResignActiveNotification: "mac:ApplicationDidResignActiveNotification", + ApplicationDidUnhide: "mac:ApplicationDidUnhide", + ApplicationDidUpdate: "mac:ApplicationDidUpdate", + ApplicationWillBecomeActive: "mac:ApplicationWillBecomeActive", + ApplicationWillFinishLaunching: "mac:ApplicationWillFinishLaunching", + ApplicationWillHide: "mac:ApplicationWillHide", + ApplicationWillResignActive: "mac:ApplicationWillResignActive", + ApplicationWillTerminate: "mac:ApplicationWillTerminate", + ApplicationWillUnhide: "mac:ApplicationWillUnhide", + ApplicationWillUpdate: "mac:ApplicationWillUpdate", + ApplicationDidChangeTheme: "mac:ApplicationDidChangeTheme!", + ApplicationShouldHandleReopen: "mac:ApplicationShouldHandleReopen!", + WindowDidBecomeKey: "mac:WindowDidBecomeKey", + WindowDidBecomeMain: "mac:WindowDidBecomeMain", + WindowDidBeginSheet: "mac:WindowDidBeginSheet", + WindowDidChangeAlpha: "mac:WindowDidChangeAlpha", + WindowDidChangeBackingLocation: "mac:WindowDidChangeBackingLocation", + WindowDidChangeBackingProperties: "mac:WindowDidChangeBackingProperties", + WindowDidChangeCollectionBehavior: "mac:WindowDidChangeCollectionBehavior", + WindowDidChangeEffectiveAppearance: "mac:WindowDidChangeEffectiveAppearance", + WindowDidChangeOcclusionState: "mac:WindowDidChangeOcclusionState", + WindowDidChangeOrderingMode: "mac:WindowDidChangeOrderingMode", + WindowDidChangeScreen: "mac:WindowDidChangeScreen", + WindowDidChangeScreenParameters: "mac:WindowDidChangeScreenParameters", + WindowDidChangeScreenProfile: "mac:WindowDidChangeScreenProfile", + WindowDidChangeScreenSpace: "mac:WindowDidChangeScreenSpace", + WindowDidChangeScreenSpaceProperties: "mac:WindowDidChangeScreenSpaceProperties", + WindowDidChangeSharingType: "mac:WindowDidChangeSharingType", + WindowDidChangeSpace: "mac:WindowDidChangeSpace", + WindowDidChangeSpaceOrderingMode: "mac:WindowDidChangeSpaceOrderingMode", + WindowDidChangeTitle: "mac:WindowDidChangeTitle", + WindowDidChangeToolbar: "mac:WindowDidChangeToolbar", + WindowDidChangeVisibility: "mac:WindowDidChangeVisibility", + WindowDidDeminiaturize: "mac:WindowDidDeminiaturize", + WindowDidEndSheet: "mac:WindowDidEndSheet", + WindowDidEnterFullScreen: "mac:WindowDidEnterFullScreen", + WindowDidEnterVersionBrowser: "mac:WindowDidEnterVersionBrowser", + WindowDidExitFullScreen: "mac:WindowDidExitFullScreen", + WindowDidExitVersionBrowser: "mac:WindowDidExitVersionBrowser", + WindowDidExpose: "mac:WindowDidExpose", + WindowDidFocus: "mac:WindowDidFocus", + WindowDidMiniaturize: "mac:WindowDidMiniaturize", + WindowDidMove: "mac:WindowDidMove", + WindowDidOrderOffScreen: "mac:WindowDidOrderOffScreen", + WindowDidOrderOnScreen: "mac:WindowDidOrderOnScreen", + WindowDidResignKey: "mac:WindowDidResignKey", + WindowDidResignMain: "mac:WindowDidResignMain", + WindowDidResize: "mac:WindowDidResize", + WindowDidUpdate: "mac:WindowDidUpdate", + WindowDidUpdateAlpha: "mac:WindowDidUpdateAlpha", + WindowDidUpdateCollectionBehavior: "mac:WindowDidUpdateCollectionBehavior", + WindowDidUpdateCollectionProperties: "mac:WindowDidUpdateCollectionProperties", + WindowDidUpdateShadow: "mac:WindowDidUpdateShadow", + WindowDidUpdateTitle: "mac:WindowDidUpdateTitle", + WindowDidUpdateToolbar: "mac:WindowDidUpdateToolbar", + WindowDidUpdateVisibility: "mac:WindowDidUpdateVisibility", + WindowShouldClose: "mac:WindowShouldClose!", + WindowWillBecomeKey: "mac:WindowWillBecomeKey", + WindowWillBecomeMain: "mac:WindowWillBecomeMain", + WindowWillBeginSheet: "mac:WindowWillBeginSheet", + WindowWillChangeOrderingMode: "mac:WindowWillChangeOrderingMode", + WindowWillClose: "mac:WindowWillClose", + WindowWillDeminiaturize: "mac:WindowWillDeminiaturize", + WindowWillEnterFullScreen: "mac:WindowWillEnterFullScreen", + WindowWillEnterVersionBrowser: "mac:WindowWillEnterVersionBrowser", + WindowWillExitFullScreen: "mac:WindowWillExitFullScreen", + WindowWillExitVersionBrowser: "mac:WindowWillExitVersionBrowser", + WindowWillFocus: "mac:WindowWillFocus", + WindowWillMiniaturize: "mac:WindowWillMiniaturize", + WindowWillMove: "mac:WindowWillMove", + WindowWillOrderOffScreen: "mac:WindowWillOrderOffScreen", + WindowWillOrderOnScreen: "mac:WindowWillOrderOnScreen", + WindowWillResignMain: "mac:WindowWillResignMain", + WindowWillResize: "mac:WindowWillResize", + WindowWillUnfocus: "mac:WindowWillUnfocus", + WindowWillUpdate: "mac:WindowWillUpdate", + WindowWillUpdateAlpha: "mac:WindowWillUpdateAlpha", + WindowWillUpdateCollectionBehavior: "mac:WindowWillUpdateCollectionBehavior", + WindowWillUpdateCollectionProperties: "mac:WindowWillUpdateCollectionProperties", + WindowWillUpdateShadow: "mac:WindowWillUpdateShadow", + WindowWillUpdateTitle: "mac:WindowWillUpdateTitle", + WindowWillUpdateToolbar: "mac:WindowWillUpdateToolbar", + WindowWillUpdateVisibility: "mac:WindowWillUpdateVisibility", + WindowWillUseStandardFrame: "mac:WindowWillUseStandardFrame", + MenuWillOpen: "mac:MenuWillOpen", + MenuDidOpen: "mac:MenuDidOpen", + MenuDidClose: "mac:MenuDidClose", + MenuWillSendAction: "mac:MenuWillSendAction", + MenuDidSendAction: "mac:MenuDidSendAction", + MenuWillHighlightItem: "mac:MenuWillHighlightItem", + MenuDidHighlightItem: "mac:MenuDidHighlightItem", + MenuWillDisplayItem: "mac:MenuWillDisplayItem", + MenuDidDisplayItem: "mac:MenuDidDisplayItem", + MenuWillAddItem: "mac:MenuWillAddItem", + MenuDidAddItem: "mac:MenuDidAddItem", + MenuWillRemoveItem: "mac:MenuWillRemoveItem", + MenuDidRemoveItem: "mac:MenuDidRemoveItem", + MenuWillBeginTracking: "mac:MenuWillBeginTracking", + MenuDidBeginTracking: "mac:MenuDidBeginTracking", + MenuWillEndTracking: "mac:MenuWillEndTracking", + MenuDidEndTracking: "mac:MenuDidEndTracking", + MenuWillUpdate: "mac:MenuWillUpdate", + MenuDidUpdate: "mac:MenuDidUpdate", + MenuWillPopUp: "mac:MenuWillPopUp", + MenuDidPopUp: "mac:MenuDidPopUp", + MenuWillSendActionToItem: "mac:MenuWillSendActionToItem", + MenuDidSendActionToItem: "mac:MenuDidSendActionToItem", + WebViewDidStartProvisionalNavigation: "mac:WebViewDidStartProvisionalNavigation", + WebViewDidReceiveServerRedirectForProvisionalNavigation: "mac:WebViewDidReceiveServerRedirectForProvisionalNavigation", + WebViewDidFinishNavigation: "mac:WebViewDidFinishNavigation", + WebViewDidCommitNavigation: "mac:WebViewDidCommitNavigation", + WindowFileDraggingEntered: "mac:WindowFileDraggingEntered", + WindowFileDraggingPerformed: "mac:WindowFileDraggingPerformed", + WindowFileDraggingExited: "mac:WindowFileDraggingExited", + }, + Common: { + ApplicationStarted: "common:ApplicationStarted", + WindowMaximise: "common:WindowMaximise", + WindowUnMaximise: "common:WindowUnMaximise", + WindowFullscreen: "common:WindowFullscreen", + WindowUnFullscreen: "common:WindowUnFullscreen", + WindowRestore: "common:WindowRestore", + WindowMinimise: "common:WindowMinimise", + WindowUnMinimise: "common:WindowUnMinimise", + WindowClosing: "common:WindowClosing", + WindowZoom: "common:WindowZoom", + WindowZoomIn: "common:WindowZoomIn", + WindowZoomOut: "common:WindowZoomOut", + WindowZoomReset: "common:WindowZoomReset", + WindowFocus: "common:WindowFocus", + WindowLostFocus: "common:WindowLostFocus", + WindowShow: "common:WindowShow", + WindowHide: "common:WindowHide", + WindowDPIChanged: "common:WindowDPIChanged", + WindowFilesDropped: "common:WindowFilesDropped", + ThemeChanged: "common:ThemeChanged", + }, +}; diff --git a/v3/internal/runtime/desktop/api/events.js b/v3/internal/runtime/desktop/api/events.js new file mode 100644 index 000000000..208177978 --- /dev/null +++ b/v3/internal/runtime/desktop/api/events.js @@ -0,0 +1,74 @@ +/* + _ __ _ __ +| | / /___ _(_) /____ +| | /| / / __ `/ / / ___/ +| |/ |/ / /_/ / / (__ ) +|__/|__/\__,_/_/_/____/ +The electron alternative for Go +(c) Lea Anthony 2019-present +*/ + +/* jshint esversion: 9 */ + +import {EventTypes} from './event_types'; + +/** + * The Events API provides methods to interact with the event system. + */ +export const Events = { + /** + * Emit an event + * @param {string} name + * @param {any=} data + */ + Emit: (name, data) => { + return wails.Events.Emit(name, data); + }, + /** + * Subscribe to an event + * @param {string} name - name of the event + * @param {(any) => void} callback - callback to call when the event is emitted + @returns {function()} unsubscribeMethod - method to unsubscribe from the event + */ + On: (name, callback) => { + return wails.Events.On(name, callback); + }, + /** + * Subscribe to an event once + * @param {string} name - name of the event + * @param {(any) => void} callback - callback to call when the event is emitted + * @returns {function()} unsubscribeMethod - method to unsubscribe from the event + */ + Once: (name, callback) => { + return wails.Events.Once(name, callback); + }, + /** + * Subscribe to an event multiple times + * @param {string} name - name of the event + * @param {(any) => void} callback - callback to call when the event is emitted + * @param {number} count - number of times to call the callback + * @returns {Promise} unsubscribeMethod - method to unsubscribe from the event + */ + OnMultiple: (name, callback, count) => { + return wails.Events.OnMultiple(name, callback, count); + }, + /** + * Unsubscribe from an event + * @param {string} name - name of the event to unsubscribe from + * @param {...string} additionalNames - additional names of events to unsubscribe from + */ + Off: (name, ...additionalNames) => { + wails.Events.Off(name, additionalNames); + }, + /** + * Unsubscribe all listeners from all events + */ + OffAll: () => { + wails.Events.OffAll(); + }, + + Windows: EventTypes.Windows, + Mac: EventTypes.Mac, + Common: EventTypes.Common, + +}; diff --git a/v3/internal/runtime/desktop/api/http.js b/v3/internal/runtime/desktop/api/http.js new file mode 100644 index 000000000..0fc684a42 --- /dev/null +++ b/v3/internal/runtime/desktop/api/http.js @@ -0,0 +1 @@ +export * from '../http'; \ No newline at end of file diff --git a/v3/internal/runtime/desktop/api/index.js b/v3/internal/runtime/desktop/api/index.js new file mode 100644 index 000000000..3b1eec881 --- /dev/null +++ b/v3/internal/runtime/desktop/api/index.js @@ -0,0 +1,33 @@ +/* + _ __ _ __ +| | / /___ _(_) /____ +| | /| / / __ `/ / / ___/ +| |/ |/ / /_/ / / (__ ) +|__/|__/\__,_/_/_/____/ +The electron alternative for Go +(c) Lea Anthony 2019-present +*/ +/* jshint esversion: 9 */ + +import * as Clipboard from "./clipboard"; +import * as Application from "./application"; +import * as Screens from "./screens"; +import * as Dialogs from "./dialogs"; +import * as Events from "./events"; +import * as Window from "./window"; +import * as HTTP from "./http"; + +export { Clipboard, Application, Screens, Dialogs, Events, Window, HTTP }; + +/** + * Call a plugin method + * @param {string} pluginName - name of the plugin + * @param {string} methodName - name of the method + * @param {...any} args - arguments to pass to the method + * @returns {Promise} - promise that resolves with the result + */ +export const Plugin = (pluginName, methodName, ...args) => { + return wails.Plugin(pluginName, methodName, ...args); +}; + + diff --git a/v3/internal/runtime/desktop/api/package.json b/v3/internal/runtime/desktop/api/package.json new file mode 100644 index 000000000..7335f6b13 --- /dev/null +++ b/v3/internal/runtime/desktop/api/package.json @@ -0,0 +1,16 @@ +{ + "name": "@wailsapp/api", + "version": "3.0.0-alpha.4", + "description": "Wails Runtime API", + "main": "index.js", + "repository": { + "type": "git", + "url": "https://github.com/wailsapp/wails.git" + }, + "author": "The Wails Team", + "license": "MIT", + "bugs": { + "url": "https://github.com/wailsapp/wails/issues" + }, + "homepage": "https://wails.io" +} diff --git a/v3/internal/runtime/desktop/api/screens.js b/v3/internal/runtime/desktop/api/screens.js new file mode 100644 index 000000000..f1e518d15 --- /dev/null +++ b/v3/internal/runtime/desktop/api/screens.js @@ -0,0 +1,38 @@ +/* + _ __ _ __ +| | / /___ _(_) /____ +| | /| / / __ `/ / / ___/ +| |/ |/ / /_/ / / (__ ) +|__/|__/\__,_/_/_/____/ +The electron alternative for Go +(c) Lea Anthony 2019-present +*/ + +/* jshint esversion: 9 */ + +/** + * The Screens API provides methods to interact with the system screens/monitors. + */ +export const Screens = { + /** + * Get the primary screen + * @returns {Promise} + */ + GetPrimary: () => { + return wails.Screens.GetPrimary(); + }, + /** + * Get all screens + * @returns {Promise} + */ + GetAll: () => { + return wails.Screens.GetAll(); + }, + /** + * Get the current screen + * @returns {Promise} + */ + GetCurrent: () => { + return wails.Screens.GetCurrent(); + }, +}; \ No newline at end of file diff --git a/v3/internal/runtime/desktop/api/types.d.ts b/v3/internal/runtime/desktop/api/types.d.ts new file mode 100644 index 000000000..fa5661f53 --- /dev/null +++ b/v3/internal/runtime/desktop/api/types.d.ts @@ -0,0 +1,130 @@ + +export interface Button { + // The label of the button + Label?: string; + // True if this button is the cancel button (selected when pressing escape) + IsCancel?: boolean; + // True if this button is the default button (selected when pressing enter) + IsDefault?: boolean; +} + +interface MessageDialogOptions { + // The title for the dialog + Title?: string; + // The message to display + Message?: string; + // The buttons to use on the dialog + Buttons?: Button[]; + // Whether the dialog is detached from the current window + Detached?: boolean; +} + +export interface OpenFileDialogOptions { + // Allows the user to be able to select directories + CanChooseDirectories?: boolean; + // Allows the user to be able to select files + CanChooseFiles?: boolean; + // Provide an option to create directories in the dialog + CanCreateDirectories?: boolean; + // Makes the dialog show hidden files + ShowHiddenFiles?: boolean; + // Whether the dialog should follow filesystem aliases + ResolvesAliases?: boolean; + // Allow the user to select multiple files or directories + AllowsMultipleSelection?: boolean; + // Hide the extension when showing the filename + HideExtension?: boolean; + // Allow the user to select files where the system hides their extensions + CanSelectHiddenExtension?: boolean; + // Treats file packages as directories, e.g. .app on macOS + TreatsFilePackagesAsDirectories?: boolean; + // Allows selection of filetypes not specified in the filters + AllowsOtherFiletypes?: boolean; + // The file filters to use in the dialog + Filters?: FileFilter[]; + // The title of the dialog + Title?: string; + // The message to display + Message?: string; + // The label for the select button + ButtonText?: string; + // The default directory to open the dialog in + Directory?: string; + // Whether the dialog is detached from the current window + Detached?: boolean; +} +export interface FileFilter { + // The display name for the filter, e.g. "Text Files" + DisplayName?: string; + // The pattern to use for the filter, e.g. "*.txt;*.md" + Pattern?: string; +} +export interface SaveFileDialogOptions { + // Provide an option to create directories in the dialog + CanCreateDirectories?: boolean; + // Makes the dialog show hidden files + ShowHiddenFiles?: boolean; + // Allow the user to select files where the system hides their extensions + CanSelectHiddenExtension?: boolean; + // Allows selection of filetypes not specified in the filters + AllowOtherFiletypes?: boolean; + // Hide the extension when showing the filename + HideExtension?: boolean; + // Treats file packages as directories, e.g. .app on macOS + TreatsFilePackagesAsDirectories?: boolean; + // The message to show in the dialog + Message?: string; + // The default directory to open the dialog in + Directory?: string; + // The default filename to use in the dialog + Filename?: string; + // The label for the select button + ButtonText?: string; + // Whether the dialog is detached from the current window + Detached?: boolean; +} + +export interface Screen { + // The screen ID + Id: string; + // The screen name + Name: string; + // The screen scale. 1 = standard resolution, 2: 2x retina, etc. + Scale: number; + // The X position of the screen + X: number; + // The Y position of the screen + Y: number; + // The width and height of the screen + Size: Size; + // The bounds of the screen + Bounds: Rect; + // The work area of the screen + WorkArea: Rect; + // True if this is the primary screen + IsPrimary: boolean; + // The rotation of the screen + Rotation: number; +} +export interface Rect { + X: number; + Y: number; + Width: number; + Height: number; +} + +export interface WailsEvent { + // The name of the event + Name: string; + // The data associated with the event + Data?: any; +} + +export interface Size { + Width: number; + Height: number; +} +export interface Position { + X: number; + Y: number; +} diff --git a/v3/internal/runtime/desktop/api/window.js b/v3/internal/runtime/desktop/api/window.js new file mode 100644 index 000000000..f6f97aee7 --- /dev/null +++ b/v3/internal/runtime/desktop/api/window.js @@ -0,0 +1,159 @@ +/* + _ __ _ __ +| | / /___ _(_) /____ +| | /| / / __ `/ / / ___/ +| |/ |/ / /_/ / / (__ ) +|__/|__/\__,_/_/_/____/ +The electron alternative for Go +(c) Lea Anthony 2019-present +*/ + +/* jshint esversion: 9 */ + + +/** + * The Window API provides methods to interact with the window. + */ +export const Window = { + /** + * Center the window. + */ + Center: () => void wails.Window.Center(), + /** + * Set the window title. + * @param title + */ + SetTitle: (title) => void wails.Window.SetTitle(title), + + /** + * Makes the window fullscreen. + */ + Fullscreen: () => void wails.Window.Fullscreen(), + + /** + * Unfullscreen the window. + */ + UnFullscreen: () => void wails.Window.UnFullscreen(), + + /** + * Set the window size. + * @param {number} width The window width + * @param {number} height The window height + */ + SetSize: (width, height) => void wails.Window.SetSize(width, height), + + /** + * Get the window size. + * @returns {Promise} The window size + */ + Size: () => { + return wails.Window.Size(); + }, + + /** + * Set the window maximum size. + * @param {number} width + * @param {number} height + */ + SetMaxSize: (width, height) => void wails.Window.SetMaxSize(width, height), + + /** + * Set the window minimum size. + * @param {number} width + * @param {number} height + */ + SetMinSize: (width, height) => void wails.Window.SetMinSize(width, height), + + /** + * Set window to be always on top. + * @param {boolean} onTop Whether the window should be always on top + */ + SetAlwaysOnTop: (onTop) => void wails.Window.SetAlwaysOnTop(onTop), + + /** + * Set the window position relative to the current monitor. + * @param {number} x + * @param {number} y + */ + SetRelativePosition: (x, y) => void wails.Window.SetRelativePosition(x, y), + + /** + * Get the window position relative to the current monitor. + * @returns {Promise} The window position + */ + RelativePosition: () => { + return wails.Window.RelativePosition(); + }, + + /** + * Set the absolute window position. + * @param {number} x + * @param {number} y + */ + SetAbsolutePosition: (x, y) => void wails.Window.SetAbsolutePosition(x, y), + + /** + * Get the absolute window position. + * @returns {Promise} The window position + */ + AbsolutePosition: () => { + return wails.Window.AbsolutePosition(); + }, + + /** + * Get the screen the window is on. + * @returns {Promise} + */ + Screen: () => { + return wails.Window.Screen(); + }, + + /** + * Hide the window + */ + Hide: () => void wails.Window.Hide(), + + /** + * Maximise the window + */ + Maximise: () => void wails.Window.Maximise(), + + /** + * Show the window + */ + Show: () => void wails.Window.Show(), + + /** + * Close the window + */ + Close: () => void wails.Window.Close(), + + /** + * Toggle the window maximise state + */ + ToggleMaximise: () => void wails.Window.ToggleMaximise(), + + /** + * Unmaximise the window + */ + UnMaximise: () => void wails.Window.UnMaximise(), + + /** + * Minimise the window + */ + Minimise: () => void wails.Window.Minimise(), + + /** + * Unminimise the window + */ + UnMinimise: () => void wails.Window.UnMinimise(), + + /** + * Set the background colour of the window. + * @param {number} r - The red value between 0 and 255 + * @param {number} g - The green value between 0 and 255 + * @param {number} b - The blue value between 0 and 255 + * @param {number} a - The alpha value between 0 and 255 + */ + SetBackgroundColour: (r, g, b, a) => void wails.Window.SetBackgroundColour(r, g, b, a), +}; diff --git a/v3/internal/runtime/desktop/application.js b/v3/internal/runtime/desktop/application.js new file mode 100644 index 000000000..097f322e9 --- /dev/null +++ b/v3/internal/runtime/desktop/application.js @@ -0,0 +1,43 @@ +/* + _ __ _ __ +| | / /___ _(_) /____ +| | /| / / __ `/ / / ___/ +| |/ |/ / /_/ / / (__ ) +|__/|__/\__,_/_/_/____/ +The electron alternative for Go +(c) Lea Anthony 2019-present +*/ + +/* jshint esversion: 9 */ + +import {newRuntimeCallerWithID, objectNames} from "./runtime"; + +let call = newRuntimeCallerWithID(objectNames.Application); + +let methods = { + Hide: 0, + Show: 1, + Quit: 2, +} + +/** + * Hide the application + */ +export function Hide() { + void call(methods.Hide); +} + +/** + * Show the application + */ +export function Show() { + void call(methods.Show); +} + + +/** + * Quit the application + */ +export function Quit() { + void call(methods.Quit); +} \ No newline at end of file diff --git a/v3/internal/runtime/desktop/browser.js b/v3/internal/runtime/desktop/browser.js new file mode 100644 index 000000000..4fa8b66a8 --- /dev/null +++ b/v3/internal/runtime/desktop/browser.js @@ -0,0 +1,25 @@ +/* + _ __ _ __ +| | / /___ _(_) /____ +| | /| / / __ `/ / / ___/ +| |/ |/ / /_/ / / (__ ) +|__/|__/\__,_/_/_/____/ +The electron alternative for Go +(c) Lea Anthony 2019-present +*/ + +/* jshint esversion: 9 */ + +import {newRuntimeCallerWithID, objectNames} from "./runtime"; + +let call = newRuntimeCallerWithID(objectNames.Browser); + +let BrowserOpenURL = 0; + +/** + * Open a browser window to the given URL + * @param {string} url - The URL to open + */ +export function OpenURL(url) { + void call(BrowserOpenURL, {url}); +} diff --git a/v3/internal/runtime/desktop/calls.js b/v3/internal/runtime/desktop/calls.js new file mode 100644 index 000000000..a9e60b716 --- /dev/null +++ b/v3/internal/runtime/desktop/calls.js @@ -0,0 +1,107 @@ +/* + _ __ _ __ +| | / /___ _(_) /____ +| | /| / / __ `/ / / ___/ +| |/ |/ / /_/ / / (__ ) +|__/|__/\__,_/_/_/____/ +The electron alternative for Go +(c) Lea Anthony 2019-present +*/ + +/* jshint esversion: 9 */ + +import {newRuntimeCallerWithID, objectNames} from "./runtime"; + +import { nanoid } from 'nanoid/non-secure'; + +let call = newRuntimeCallerWithID(objectNames.Call); + +let CallBinding = 0; + +let callResponses = new Map(); + +function generateID() { + let result; + do { + result = nanoid(); + } while (callResponses.has(result)); + return result; +} + +export function callCallback(id, data, isJSON) { + let p = callResponses.get(id); + if (p) { + if (isJSON) { + p.resolve(JSON.parse(data)); + } else { + p.resolve(data); + } + callResponses.delete(id); + } +} + +export function callErrorCallback(id, message) { + let p = callResponses.get(id); + if (p) { + p.reject(message); + callResponses.delete(id); + } +} + +function callBinding(type, options) { + return new Promise((resolve, reject) => { + let id = generateID(); + options = options || {}; + options["call-id"] = id; + + callResponses.set(id, {resolve, reject}); + call(type, options).catch((error) => { + reject(error); + callResponses.delete(id); + }); + }); +} + +export function Call(options) { + return callBinding(CallBinding, options); +} + +export function CallByName(name, ...args) { + + // Ensure first argument is a string and has 2 dots + if (typeof name !== "string" || name.split(".").length !== 3) { + throw new Error("CallByName requires a string in the format 'package.struct.method'"); + } + // Split inputs + let parts = name.split("."); + + return callBinding(CallBinding, { + packageName: parts[0], + structName: parts[1], + methodName: parts[2], + args: args, + }); +} + +export function CallByID(methodID, ...args) { + return callBinding(CallBinding, { + methodID: methodID, + args: args, + }); +} + +/** + * Call a plugin method + * @param {string} pluginName - name of the plugin + * @param {string} methodName - name of the method + * @param {...any} args - arguments to pass to the method + * @returns {Promise} - promise that resolves with the result + */ +export function Plugin(pluginName, methodName, ...args) { + return callBinding(CallBinding, { + packageName: "wails-plugins", + structName: pluginName, + methodName: methodName, + args: args, + }); +} \ No newline at end of file diff --git a/v3/internal/runtime/desktop/clipboard.js b/v3/internal/runtime/desktop/clipboard.js new file mode 100644 index 000000000..cc9eb0373 --- /dev/null +++ b/v3/internal/runtime/desktop/clipboard.js @@ -0,0 +1,33 @@ +/* + _ __ _ __ +| | / /___ _(_) /____ +| | /| / / __ `/ / / ___/ +| |/ |/ / /_/ / / (__ ) +|__/|__/\__,_/_/_/____/ +The electron alternative for Go +(c) Lea Anthony 2019-present +*/ + +/* jshint esversion: 9 */ + +import {newRuntimeCallerWithID, objectNames} from "./runtime"; + +let call = newRuntimeCallerWithID(objectNames.Clipboard); + +let ClipboardSetText = 0; +let ClipboardText = 1; + +/** + * Set the Clipboard text + */ +export function SetText(text) { + void call(ClipboardSetText, {text}); +} + +/** + * Get the Clipboard text + * @returns {Promise} + */ +export function Text() { + return call(ClipboardText); +} \ No newline at end of file diff --git a/v3/internal/runtime/desktop/contextmenu.js b/v3/internal/runtime/desktop/contextmenu.js new file mode 100644 index 000000000..9788a2118 --- /dev/null +++ b/v3/internal/runtime/desktop/contextmenu.js @@ -0,0 +1,85 @@ +import {newRuntimeCallerWithID, objectNames} from "./runtime"; + +let call = newRuntimeCallerWithID(objectNames.ContextMenu); + +let ContextMenuOpen = 0; + +function openContextMenu(id, x, y, data) { + void call(ContextMenuOpen, {id, x, y, data}); +} + +export function setupContextMenus() { + window.addEventListener('contextmenu', contextMenuHandler); +} + +function contextMenuHandler(event) { + // Check for custom context menu + let element = event.target; + let customContextMenu = window.getComputedStyle(element).getPropertyValue("--custom-contextmenu"); + customContextMenu = customContextMenu ? customContextMenu.trim() : ""; + if (customContextMenu) { + event.preventDefault(); + let customContextMenuData = window.getComputedStyle(element).getPropertyValue("--custom-contextmenu-data"); + openContextMenu(customContextMenu, event.clientX, event.clientY, customContextMenuData); + return + } + + processDefaultContextMenu(event); +} + + +/* +--default-contextmenu: auto; (default) will show the default context menu if contentEditable is true OR text has been selected OR element is input or textarea +--default-contextmenu: show; will always show the default context menu +--default-contextmenu: hide; will always hide the default context menu + +This rule is inherited like normal CSS rules, so nesting works as expected +*/ +function processDefaultContextMenu(event) { + // Debug builds always show the menu + if (DEBUG) { + return; + } + + // Process default context menu + const element = event.target; + const computedStyle = window.getComputedStyle(element); + const defaultContextMenuAction = computedStyle.getPropertyValue("--default-contextmenu").trim(); + switch (defaultContextMenuAction) { + case "show": + return; + case "hide": + event.preventDefault(); + return; + default: + // Check if contentEditable is true + if (element.isContentEditable) { + return; + } + + // Check if text has been selected + const selection = window.getSelection(); + const hasSelection = (selection.toString().length > 0) + if (hasSelection) { + for (let i = 0; i < selection.rangeCount; i++) { + const range = selection.getRangeAt(i); + const rects = range.getClientRects(); + for (let j = 0; j < rects.length; j++) { + const rect = rects[j]; + if (document.elementFromPoint(rect.left, rect.top) === element) { + return; + } + } + } + } + // Check if tagname is input or textarea + if (element.tagName === "INPUT" || element.tagName === "TEXTAREA") { + if (hasSelection || (!element.readOnly && !element.disabled)) { + return; + } + } + + // hide default context menu + event.preventDefault(); + } +} diff --git a/v3/internal/runtime/desktop/dialogs.js b/v3/internal/runtime/desktop/dialogs.js new file mode 100644 index 000000000..2865b7d84 --- /dev/null +++ b/v3/internal/runtime/desktop/dialogs.js @@ -0,0 +1,129 @@ +/* + _ __ _ __ +| | / /___ _(_) /____ +| | /| / / __ `/ / / ___/ +| |/ |/ / /_/ / / (__ ) +|__/|__/\__,_/_/_/____/ +The electron alternative for Go +(c) Lea Anthony 2019-present +*/ + +/* jshint esversion: 9 */ + +/** + * @typedef {import("./api/types").MessageDialogOptions} MessageDialogOptions + * @typedef {import("./api/types").OpenDialogOptions} OpenDialogOptions + * @typedef {import("./api/types").SaveDialogOptions} SaveDialogOptions + */ + +import {newRuntimeCallerWithID, objectNames} from "./runtime"; + +import { nanoid } from 'nanoid/non-secure'; + +let call = newRuntimeCallerWithID(objectNames.Dialog); + +let DialogInfo = 0; +let DialogWarning = 1; +let DialogError = 2; +let DialogQuestion = 3; +let DialogOpenFile = 4; +let DialogSaveFile = 5; + + +let dialogResponses = new Map(); + +function generateID() { + let result; + do { + result = nanoid(); + } while (dialogResponses.has(result)); + return result; +} + +export function dialogCallback(id, data, isJSON) { + let p = dialogResponses.get(id); + if (p) { + if (isJSON) { + p.resolve(JSON.parse(data)); + } else { + p.resolve(data); + } + dialogResponses.delete(id); + } +} +export function dialogErrorCallback(id, message) { + let p = dialogResponses.get(id); + if (p) { + p.reject(message); + dialogResponses.delete(id); + } +} + +function dialog(type, options) { + return new Promise((resolve, reject) => { + let id = generateID(); + options = options || {}; + options["dialog-id"] = id; + dialogResponses.set(id, {resolve, reject}); + call(type, options).catch((error) => { + reject(error); + dialogResponses.delete(id); + }); + }); +} + + +/** + * Shows an Info dialog with the given options. + * @param {MessageDialogOptions} options + * @returns {Promise} The label of the button pressed + */ +export function Info(options) { + return dialog(DialogInfo, options); +} + +/** + * Shows a Warning dialog with the given options. + * @param {MessageDialogOptions} options + * @returns {Promise} The label of the button pressed + */ +export function Warning(options) { + return dialog(DialogWarning, options); +} + +/** + * Shows an Error dialog with the given options. + * @param {MessageDialogOptions} options + * @returns {Promise} The label of the button pressed + */ +export function Error(options) { + return dialog(DialogError, options); +} + +/** + * Shows a Question dialog with the given options. + * @param {MessageDialogOptions} options + * @returns {Promise} The label of the button pressed + */ +export function Question(options) { + return dialog(DialogQuestion, options); +} + +/** + * Shows an Open dialog with the given options. + * @param {OpenDialogOptions} options + * @returns {Promise} Returns the selected file or an array of selected files if AllowsMultipleSelection is true. A blank string is returned if no file was selected. + */ +export function OpenFile(options) { + return dialog(DialogOpenFile, options); +} + +/** + * Shows a Save dialog with the given options. + * @param {SaveDialogOptions} options + * @returns {Promise} Returns the selected file. A blank string is returned if no file was selected. + */ +export function SaveFile(options) { + return dialog(DialogSaveFile, options); +} + diff --git a/v3/internal/runtime/desktop/drag.js b/v3/internal/runtime/desktop/drag.js new file mode 100644 index 000000000..b5dff0666 --- /dev/null +++ b/v3/internal/runtime/desktop/drag.js @@ -0,0 +1,143 @@ +/* + _ __ _ __ +| | / /___ _(_) /____ +| | /| / / __ `/ / / ___/ +| |/ |/ / /_/ / / (__ ) +|__/|__/\__,_/_/_/____/ +The electron alternative for Go +(c) Lea Anthony 2019-present +*/ + +/* jshint esversion: 9 */ + +import {invoke} from "./invoke"; +import {GetFlag} from "./flags"; + +let shouldDrag = false; + +export function dragTest(e) { + let val = window.getComputedStyle(e.target).getPropertyValue("--webkit-app-region"); + if (val) { + val = val.trim(); + } + + if (val !== "drag") { + return false; + } + + // Only process the primary button + if (e.buttons !== 1) { + return false; + } + + return e.detail === 1; +} + +export function setupDrag() { + window.addEventListener('mousedown', onMouseDown); + window.addEventListener('mousemove', onMouseMove); + window.addEventListener('mouseup', onMouseUp); +} + +let resizeEdge = null; +let resizable = false; + +export function setResizable(value) { + resizable = value; +} + +function testResize(e) { + if( resizeEdge ) { + invoke("resize:" + resizeEdge); + return true + } + return false; +} + +function onMouseDown(e) { + + // Check for resizing on Windows + if( WINDOWS ) { + if (testResize()) { + return; + } + } + if (dragTest(e)) { + // Ignore drag on scrollbars + if (e.offsetX > e.target.clientWidth || e.offsetY > e.target.clientHeight) { + return; + } + shouldDrag = true; + } else { + shouldDrag = false; + } +} + +function onMouseUp(e) { + let mousePressed = e.buttons !== undefined ? e.buttons : e.which; + if (mousePressed > 0) { + endDrag(); + } +} + +export function endDrag() { + document.body.style.cursor = 'default'; + shouldDrag = false; +} + +function setResize(cursor) { + document.documentElement.style.cursor = cursor || defaultCursor; + resizeEdge = cursor; +} + +function onMouseMove(e) { + if (shouldDrag) { + shouldDrag = false; + let mousePressed = e.buttons !== undefined ? e.buttons : e.which; + if (mousePressed > 0) { + invoke("drag"); + } + return; + } + + if (WINDOWS) { + if (resizable) { + handleResize(e); + } + } +} + +let defaultCursor = "auto"; + +function handleResize(e) { + let resizeHandleHeight = GetFlag("system.resizeHandleHeight") || 5; + let resizeHandleWidth = GetFlag("system.resizeHandleWidth") || 5; + + // Extra pixels for the corner areas + let cornerExtra = GetFlag("resizeCornerExtra") || 10; + + let rightBorder = window.outerWidth - e.clientX < resizeHandleWidth; + let leftBorder = e.clientX < resizeHandleWidth; + let topBorder = e.clientY < resizeHandleHeight; + let bottomBorder = window.outerHeight - e.clientY < resizeHandleHeight; + + // Adjust for corners + let rightCorner = window.outerWidth - e.clientX < (resizeHandleWidth + cornerExtra); + let leftCorner = e.clientX < (resizeHandleWidth + cornerExtra); + let topCorner = e.clientY < (resizeHandleHeight + cornerExtra); + let bottomCorner = window.outerHeight - e.clientY < (resizeHandleHeight + cornerExtra); + + // If we aren't on an edge, but were, reset the cursor to default + if (!leftBorder && !rightBorder && !topBorder && !bottomBorder && resizeEdge !== undefined) { + setResize(); + } + // Adjusted for corner areas + else if (rightCorner && bottomCorner) setResize("se-resize"); + else if (leftCorner && bottomCorner) setResize("sw-resize"); + else if (leftCorner && topCorner) setResize("nw-resize"); + else if (topCorner && rightCorner) setResize("ne-resize"); + else if (leftBorder) setResize("w-resize"); + else if (topBorder) setResize("n-resize"); + else if (bottomBorder) setResize("s-resize"); + else if (rightBorder) setResize("e-resize"); +} diff --git a/v3/internal/runtime/desktop/events.js b/v3/internal/runtime/desktop/events.js new file mode 100644 index 000000000..40e40ebd7 --- /dev/null +++ b/v3/internal/runtime/desktop/events.js @@ -0,0 +1,194 @@ +/* + _ __ _ __ +| | / /___ _(_) /____ +| | /| / / __ `/ / / ___/ +| |/ |/ / /_/ / / (__ ) +|__/|__/\__,_/_/_/____/ +The electron alternative for Go +(c) Lea Anthony 2019-present +*/ + +/* jshint esversion: 9 */ + +/** + * @typedef {import("./api/types").WailsEvent} WailsEvent + */ + +import {newRuntimeCallerWithID, objectNames} from "./runtime"; + +let call = newRuntimeCallerWithID(objectNames.Events); +let EventEmit = 0; + +/** + * The Listener class defines a listener! :-) + * + * @class Listener + */ +class Listener { + /** + * Creates an instance of Listener. + * @param {string} eventName + * @param {function} callback + * @param {number} maxCallbacks + * @memberof Listener + */ + constructor(eventName, callback, maxCallbacks) { + this.eventName = eventName; + // Default of -1 means infinite + this.maxCallbacks = maxCallbacks || -1; + // Callback invokes the callback with the given data + // Returns true if this listener should be destroyed + this.Callback = (data) => { + callback(data); + // If maxCallbacks is infinite, return false (do not destroy) + if (this.maxCallbacks === -1) { + return false; + } + // Decrement maxCallbacks. Return true if now 0, otherwise false + this.maxCallbacks -= 1; + return this.maxCallbacks === 0; + }; + } +} + + +/** + * WailsEvent defines a custom event. It is passed to event listeners. + * + * @class WailsEvent + * @property {string} name - Name of the event + * @property {any} data - Data associated with the event + */ +export class WailsEvent { + /** + * Creates an instance of WailsEvent. + * @param {string} name - Name of the event + * @param {any=null} data - Data associated with the event + * @memberof WailsEvent + */ + constructor(name, data = null) { + this.name = name; + this.data = data; + } +} + +export const eventListeners = new Map(); + +/** + * Registers an event listener that will be invoked `maxCallbacks` times before being destroyed + * + * @export + * @param {string} eventName + * @param {function(WailsEvent): void} callback + * @param {number} maxCallbacks + * @returns {function} A function to cancel the listener + */ +export function OnMultiple(eventName, callback, maxCallbacks) { + let listeners = eventListeners.get(eventName) || []; + const thisListener = new Listener(eventName, callback, maxCallbacks); + listeners.push(thisListener); + eventListeners.set(eventName, listeners); + return () => listenerOff(thisListener); +} + +/** + * Registers an event listener that will be invoked every time the event is emitted + * + * @export + * @param {string} eventName + * @param {function(WailsEvent): void} callback + * @returns {function} A function to cancel the listener + */ +export function On(eventName, callback) { + return OnMultiple(eventName, callback, -1); +} + +/** + * Registers an event listener that will be invoked once then destroyed + * + * @export + * @param {string} eventName + * @param {function(WailsEvent): void} callback + @returns {function} A function to cancel the listener + */ +export function Once(eventName, callback) { + return OnMultiple(eventName, callback, 1); +} + +/** + * listenerOff unregisters a listener previously registered with On + * + * @param {Listener} listener + */ +function listenerOff(listener) { + const eventName = listener.eventName; + // Remove local listener + let listeners = eventListeners.get(eventName).filter(l => l !== listener); + if (listeners.length === 0) { + eventListeners.delete(eventName); + } else { + eventListeners.set(eventName, listeners); + } +} + +/** + * dispatches an event to all listeners + * + * @export + * @param {WailsEvent} event + */ +export function dispatchWailsEvent(event) { + let listeners = eventListeners.get(event.name); + if (listeners) { + // iterate listeners and call callback. If callback returns true, remove listener + let toRemove = []; + listeners.forEach(listener => { + let remove = listener.Callback(event); + if (remove) { + toRemove.push(listener); + } + }); + // remove listeners + if (toRemove.length > 0) { + listeners = listeners.filter(l => !toRemove.includes(l)); + if (listeners.length === 0) { + eventListeners.delete(event.name); + } else { + eventListeners.set(event.name, listeners); + } + } + } +} + +/** + * Off unregisters a listener previously registered with On, + * optionally multiple listeners can be unregistered via `additionalEventNames` + * + [v3 CHANGE] Off only unregisters listeners within the current window + * + * @param {string} eventName + * @param {...string} additionalEventNames + */ +export function Off(eventName, ...additionalEventNames) { + let eventsToRemove = [eventName, ...additionalEventNames]; + eventsToRemove.forEach(eventName => { + eventListeners.delete(eventName); + }); +} + +/** + * OffAll unregisters all listeners + * [v3 CHANGE] OffAll only unregisters listeners within the current window + * + */ +export function OffAll() { + eventListeners.clear(); +} + +/** + * Emit an event + * @param {WailsEvent} event The event to emit + */ +export function Emit(event) { + void call(EventEmit, event); +} \ No newline at end of file diff --git a/v3/internal/runtime/desktop/events.test.js b/v3/internal/runtime/desktop/events.test.js new file mode 100644 index 000000000..a9b679657 --- /dev/null +++ b/v3/internal/runtime/desktop/events.test.js @@ -0,0 +1,115 @@ +import { On, Off, OffAll, OnMultiple, WailsEvent, dispatchWailsEvent, eventListeners, Once } from './events'; +import { expect, describe, it, vi, afterEach, beforeEach } from 'vitest'; + +afterEach(() => { + OffAll(); + vi.resetAllMocks(); +}); + +describe('OnMultiple', () => { + let testEvent = new WailsEvent('a', {}); + + it('should stop after a specified number of times', () => { + const cb = vi.fn(); + OnMultiple('a', cb, 5); + dispatchWailsEvent(testEvent); + dispatchWailsEvent(testEvent); + dispatchWailsEvent(testEvent); + dispatchWailsEvent(testEvent); + dispatchWailsEvent(testEvent); + dispatchWailsEvent(testEvent); + expect(cb).toBeCalledTimes(5); + }); + + it('should return a cancel fn', () => { + const cb = vi.fn() + const cancel = OnMultiple('a', cb, 5) + dispatchWailsEvent(testEvent) + dispatchWailsEvent(testEvent) + cancel() + dispatchWailsEvent(testEvent) + dispatchWailsEvent(testEvent) + expect(cb).toBeCalledTimes(2) + }) +}) + +describe('On', () => { + it('should create a listener with a count of -1', () => { + On('a', () => {}) + expect(eventListeners.get("a")[0].maxCallbacks).toBe(-1) + }) + + it('should return a cancel fn', () => { + const cancel = On('a', () => {}) + cancel(); + }) +}) + +describe('Once', () => { + it('should create a listener with a count of 1', () => { + Once('a', () => {}) + expect(eventListeners.get("a")[0].maxCallbacks).toBe(1) + }) + + it('should return a cancel fn', () => { + const cancel = EventsOn('a', () => {}) + cancel(); + }) +}) +// +// describe('EventsNotify', () => { +// it('should inform a listener', () => { +// const cb = vi.fn() +// EventsOn('a', cb) +// EventsNotify(JSON.stringify({name: 'a', data: ["one", "two", "three"]})) +// expect(cb).toBeCalledTimes(1); +// expect(cb).toHaveBeenLastCalledWith("one", "two", "three"); +// expect(window.WailsInvoke).toBeCalledTimes(0); +// }) +// }) +// +// describe('EventsEmit', () => { +// it('should emit an event', () => { +// EventsEmit('a', 'one', 'two', 'three') +// expect(window.WailsInvoke).toBeCalledTimes(1); +// const calledWith = window.WailsInvoke.calls[0][0]; +// expect(calledWith.slice(0, 2)).toBe('EE') +// expect(JSON.parse(calledWith.slice(2))).toStrictEqual({data: ["one", "two", "three"], name: "a"}) +// }) +// }) +// +describe('Off', () => { + beforeEach(() => { + On('a', () => {}) + On('a', () => {}) + On('a', () => {}) + On('b', () => {}) + On('c', () => {}) + }) + + it('should cancel all event listeners for a single type', () => { + Off('a') + expect(eventListeners.get('a')).toBeUndefined() + expect(eventListeners.get('b')).not.toBeUndefined() + expect(eventListeners.get('c')).not.toBeUndefined() + }) + + it('should cancel all event listeners for multiple types', () => { + Off('a', 'b') + expect(eventListeners.get('a')).toBeUndefined() + expect(eventListeners.get('b')).toBeUndefined() + expect(eventListeners.get('c')).not.toBeUndefined() + }) +}) + +describe('OffAll', () => { + it('should cancel all event listeners', () => { + On('a', () => {}) + On('a', () => {}) + On('a', () => {}) + On('b', () => {}) + On('c', () => {}) + OffAll() + expect(eventListeners.size).toBe(0) + }) +}) diff --git a/v3/internal/runtime/desktop/flags.js b/v3/internal/runtime/desktop/flags.js new file mode 100644 index 000000000..4157115d3 --- /dev/null +++ b/v3/internal/runtime/desktop/flags.js @@ -0,0 +1,57 @@ +/* + _ __ _ __ +| | / /___ _(_) /____ +| | /| / / __ `/ / / ___/ +| |/ |/ / /_/ / / (__ ) +|__/|__/\__,_/_/_/____/ +The electron alternative for Go +(c) Lea Anthony 2019-present +*/ + +/* jshint esversion: 9 */ + +let flags = new Map(); + +function convertToMap(obj) { + const map = new Map(); + + for (const [key, value] of Object.entries(obj)) { + if (typeof value === 'object' && value !== null) { + map.set(key, convertToMap(value)); // Recursively convert nested object + } else { + map.set(key, value); + } + } + + return map; +} + +fetch("/wails/flags").then((response) => { + response.json().then((data) => { + flags = convertToMap(data); + }); +}); + + +function getValueFromMap(keyString) { + const keys = keyString.split('.'); + let value = flags; + + for (const key of keys) { + if (value instanceof Map) { + value = value.get(key); + } else { + value = value[key]; + } + + if (value === undefined) { + break; + } + } + + return value; +} + +export function GetFlag(keyString) { + return getValueFromMap(keyString); +} diff --git a/v3/internal/runtime/desktop/http.js b/v3/internal/runtime/desktop/http.js new file mode 100644 index 000000000..720f7db02 --- /dev/null +++ b/v3/internal/runtime/desktop/http.js @@ -0,0 +1,134 @@ +/* + _ __ _ __ +| | / /___ _(_) /____ +| | /| / / __ `/ / / ___/ +| |/ |/ / /_/ / / (__ ) +|__/|__/\__,_/_/_/____/ +The electron alternative for Go +(c) Lea Anthony 2019-present +*/ + +/* jshint esversion: 9 */ + +import {newRuntimeCallerWithID, objectNames} from "./runtime"; + +let call = newRuntimeCallerWithID(objectNames.HTTP); + +let HTTPFetch = 0; + +/** + * Perform an HTTP request + * @param {Object} options - The request options + * @param {string} options.url - The URL to request + * @param {string} [options.method='GET'] - The HTTP method + * @param {Object} [options.headers] - Request headers + * @param {string} [options.body] - Request body + * @param {number} [options.timeout] - Request timeout in seconds + * @returns {Promise} The response object + */ +export function Fetch(options) { + // Ensure we have required fields + if (!options || !options.url) { + return Promise.reject(new Error("URL is required")); + } + + // Set defaults + const request = { + url: options.url, + method: options.method || 'GET', + headers: options.headers || {}, + body: options.body || '', + timeout: options.timeout || 30 + }; + + // For POST requests, we need to send the body differently + if (request.body) { + return call(HTTPFetch, null, JSON.stringify(request)); + } + + return call(HTTPFetch, null, JSON.stringify(request)); +} + +/** + * Convenience method for GET requests + * @param {string} url - The URL to request + * @param {Object} [options] - Additional options + * @returns {Promise} The response object + */ +export function Get(url, options = {}) { + return Fetch({ ...options, url, method: 'GET' }); +} + +/** + * Convenience method for POST requests + * @param {string} url - The URL to request + * @param {string|Object} body - The request body + * @param {Object} [options] - Additional options + * @returns {Promise} The response object + */ +export function Post(url, body, options = {}) { + if (typeof body === 'object' && !(body instanceof String)) { + body = JSON.stringify(body); + options.headers = { + 'Content-Type': 'application/json', + ...(options.headers || {}) + }; + } + return Fetch({ ...options, url, method: 'POST', body }); +} + +/** + * Convenience method for PUT requests + * @param {string} url - The URL to request + * @param {string|Object} body - The request body + * @param {Object} [options] - Additional options + * @returns {Promise} The response object + */ +export function Put(url, body, options = {}) { + if (typeof body === 'object' && !(body instanceof String)) { + body = JSON.stringify(body); + options.headers = { + 'Content-Type': 'application/json', + ...(options.headers || {}) + }; + } + return Fetch({ ...options, url, method: 'PUT', body }); +} + +/** + * Convenience method for DELETE requests + * @param {string} url - The URL to request + * @param {Object} [options] - Additional options + * @returns {Promise} The response object + */ +export function Delete(url, options = {}) { + return Fetch({ ...options, url, method: 'DELETE' }); +} + +/** + * Convenience method for PATCH requests + * @param {string} url - The URL to request + * @param {string|Object} body - The request body + * @param {Object} [options] - Additional options + * @returns {Promise} The response object + */ +export function Patch(url, body, options = {}) { + if (typeof body === 'object' && !(body instanceof String)) { + body = JSON.stringify(body); + options.headers = { + 'Content-Type': 'application/json', + ...(options.headers || {}) + }; + } + return Fetch({ ...options, url, method: 'PATCH', body }); +} + +/** + * Convenience method for HEAD requests + * @param {string} url - The URL to request + * @param {Object} [options] - Additional options + * @returns {Promise} The response object + */ +export function Head(url, options = {}) { + return Fetch({ ...options, url, method: 'HEAD' }); +} \ No newline at end of file diff --git a/v3/internal/runtime/desktop/invoke.js b/v3/internal/runtime/desktop/invoke.js new file mode 100644 index 000000000..b8a2daa1b --- /dev/null +++ b/v3/internal/runtime/desktop/invoke.js @@ -0,0 +1,20 @@ +/* + _ __ _ __ +| | / /___ _(_) /____ +| | /| / / __ `/ / / ___/ +| |/ |/ / /_/ / / (__ ) +|__/|__/\__,_/_/_/____/ +The electron alternative for Go +(c) Lea Anthony 2019-present +*/ + +/* jshint esversion: 9 */ + +// defined in the Taskfile +export let invoke = function(input) { + if(WINDOWS) { + chrome.webview.postMessage(input); + } else { + webkit.messageHandlers.external.postMessage(input); + } +} diff --git a/v3/internal/runtime/desktop/main.js b/v3/internal/runtime/desktop/main.js new file mode 100644 index 000000000..aa511e9b3 --- /dev/null +++ b/v3/internal/runtime/desktop/main.js @@ -0,0 +1,102 @@ +/* + _ __ _ __ +| | / /___ _(_) /____ +| | /| / / __ `/ / / ___/ +| |/ |/ / /_/ / / (__ ) +|__/|__/\__,_/_/_/____/ +The electron alternative for Go +(c) Lea Anthony 2019-present +*/ +/* jshint esversion: 9 */ + + +import * as Clipboard from './clipboard'; +import * as Application from './application'; +import * as Screens from './screens'; +import * as System from './system'; +import * as Browser from './browser'; +import * as HTTP from './http'; +import {Plugin, Call, callErrorCallback, callCallback, CallByID, CallByName} from "./calls"; +import {clientId} from './runtime'; +import {newWindow} from "./window"; +import {dispatchWailsEvent, Emit, Off, OffAll, On, Once, OnMultiple} from "./events"; +import {dialogCallback, dialogErrorCallback, Error, Info, OpenFile, Question, SaveFile, Warning,} from "./dialogs"; +import {setupContextMenus} from "./contextmenu"; +import {reloadWML} from "./wml"; +import {setupDrag, endDrag, setResizable} from "./drag"; + +window.wails = { + ...newRuntime(null), + Capabilities: {}, + clientId: clientId, +}; + +fetch("/wails/capabilities").then((response) => { + response.json().then((data) => { + window.wails.Capabilities = data; + }); +}); + +// Internal wails endpoints +window._wails = { + dialogCallback, + dialogErrorCallback, + dispatchWailsEvent, + callCallback, + callErrorCallback, + endDrag, + setResizable, +}; + +export function newRuntime(windowName) { + return { + Clipboard: { + ...Clipboard + }, + Application: { + ...Application, + GetWindowByName(windowName) { + return newRuntime(windowName); + } + }, + System, + Screens, + Browser, + HTTP, + Call, + CallByID, + CallByName, + Plugin, + WML: { + Reload: reloadWML, + }, + Dialog: { + Info, + Warning, + Error, + Question, + OpenFile, + SaveFile, + }, + Events: { + Emit, + On, + Once, + OnMultiple, + Off, + OffAll, + }, + Window: newWindow(windowName), + }; +} + +if (DEBUG) { + console.log("Wails v3.0.0 Debug Mode Enabled"); +} + +setupContextMenus(); +setupDrag(); + +document.addEventListener("DOMContentLoaded", function() { + reloadWML(); +}); diff --git a/v3/internal/runtime/desktop/runtime.js b/v3/internal/runtime/desktop/runtime.js new file mode 100644 index 000000000..9814919b4 --- /dev/null +++ b/v3/internal/runtime/desktop/runtime.js @@ -0,0 +1,112 @@ +/* + _ __ _ __ +| | / /___ _(_) /____ +| | /| / / __ `/ / / ___/ +| |/ |/ / /_/ / / (__ ) +|__/|__/\__,_/_/_/____/ +The electron alternative for Go +(c) Lea Anthony 2019-present +*/ + +/* jshint esversion: 9 */ +import { nanoid } from 'nanoid/non-secure'; + +const runtimeURL = window.location.origin + "/wails/runtime"; +// Object Names +export const objectNames = { + Call: 0, + Clipboard: 1, + Application: 2, + Events: 3, + ContextMenu: 4, + Dialog: 5, + Window: 6, + Screens: 7, + System: 8, + Browser: 9, + HTTP: 10, +} +export let clientId = nanoid(); + +function runtimeCall(method, windowName, args) { + let url = new URL(runtimeURL); + if( method ) { + url.searchParams.append("method", method); + } + let fetchOptions = { + headers: {}, + }; + if (windowName) { + fetchOptions.headers["x-wails-window-name"] = windowName; + } + 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)); + }); +} + +export function newRuntimeCaller(object, windowName) { + return function (method, args=null) { + return runtimeCall(object + "." + method, windowName, args); + }; +} + +function runtimeCallWithID(objectID, method, windowName, args, body) { + 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 (args) { + url.searchParams.append("args", JSON.stringify(args)); + } + if (body) { + fetchOptions.method = "POST"; + fetchOptions.body = body; + fetchOptions.headers["Content-Type"] = "application/json"; + } + 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)); + }); +} + +export function newRuntimeCallerWithID(object, windowName) { + return function (method, args=null, body=null) { + return runtimeCallWithID(object, method, windowName, args, body); + }; +} diff --git a/v3/internal/runtime/desktop/screens.js b/v3/internal/runtime/desktop/screens.js new file mode 100644 index 000000000..07b28a2ef --- /dev/null +++ b/v3/internal/runtime/desktop/screens.js @@ -0,0 +1,48 @@ +/* + _ __ _ __ +| | / /___ _(_) /____ +| | /| / / __ `/ / / ___/ +| |/ |/ / /_/ / / (__ ) +|__/|__/\__,_/_/_/____/ +The electron alternative for Go +(c) Lea Anthony 2019-present +*/ + +/* jshint esversion: 9 */ + +/** + * @typedef {import("./api/types").Screen} Screen + */ + +import {newRuntimeCallerWithID, objectNames} from "./runtime"; + +let call = newRuntimeCallerWithID(objectNames.Screens); + +let ScreensGetAll = 0; +let ScreensGetPrimary = 1; +let ScreensGetCurrent = 2; + +/** + * Gets all screens. + * @returns {Promise} + */ +export function GetAll() { + return call(ScreensGetAll); +} + +/** + * Gets the primary screen. + * @returns {Promise} + */ +export function GetPrimary() { + return call(ScreensGetPrimary); +} + +/** + * Gets the current active screen. + * @returns {Promise} + * @constructor + */ +export function GetCurrent() { + return call(ScreensGetCurrent); +} \ No newline at end of file diff --git a/v3/internal/runtime/desktop/system.js b/v3/internal/runtime/desktop/system.js new file mode 100644 index 000000000..d1f29e403 --- /dev/null +++ b/v3/internal/runtime/desktop/system.js @@ -0,0 +1,25 @@ +/* + _ __ _ __ +| | / /___ _(_) /____ +| | /| / / __ `/ / / ___/ +| |/ |/ / /_/ / / (__ ) +|__/|__/\__,_/_/_/____/ +The electron alternative for Go +(c) Lea Anthony 2019-present +*/ + +/* jshint esversion: 9 */ + +import {newRuntimeCallerWithID, objectNames} from "./runtime"; + +let call = newRuntimeCallerWithID(objectNames.System); + +let SystemIsDarkMode = 0; + +/** + * Determines if the system is currently using dark mode + * @returns {Promise} + */ +export function IsDarkMode() { + return call(SystemIsDarkMode); +} \ No newline at end of file diff --git a/v3/internal/runtime/desktop/window.js b/v3/internal/runtime/desktop/window.js new file mode 100644 index 000000000..f255ae42e --- /dev/null +++ b/v3/internal/runtime/desktop/window.js @@ -0,0 +1,228 @@ +/* + _ __ _ __ +| | / /___ _(_) /____ +| | /| / / __ `/ / / ___/ +| |/ |/ / /_/ / / (__ ) +|__/|__/\__,_/_/_/____/ +The electron alternative for Go +(c) Lea Anthony 2019-present +*/ + +/* jshint esversion: 9 */ + +/** + * @typedef {import("../api/types").Size} Size + * @typedef {import("../api/types").Position} Position + * @typedef {import("../api/types").Screen} Screen + */ + +import {newRuntimeCallerWithID, objectNames} from "./runtime"; + +let WindowCenter = 0; +let WindowSetTitle = 1; +let WindowFullscreen = 2; +let WindowUnFullscreen = 3; +let WindowSetSize = 4; +let WindowSize = 5; +let WindowSetMaxSize = 6; +let WindowSetMinSize = 7; +let WindowSetAlwaysOnTop = 8; +let WindowSetRelativePosition = 9; +let WindowRelativePosition = 10; +let WindowScreen = 11; +let WindowHide = 12; +let WindowMaximise = 13; +let WindowUnMaximise = 14; +let WindowToggleMaximise = 15; +let WindowMinimise = 16; +let WindowUnMinimise = 17; +let WindowRestore = 18; +let WindowShow = 19; +let WindowClose = 20; +let WindowSetBackgroundColour = 21; +let WindowSetResizable = 22; +let WindowWidth = 23; +let WindowHeight = 24; +let WindowZoomIn = 25; +let WindowZoomOut = 26; +let WindowZoomReset = 27; +let WindowGetZoomLevel = 28; +let WindowSetZoomLevel = 29; + +export function newWindow(windowName) { + let call = newRuntimeCallerWithID(objectNames.Window, windowName); + return { + + /** + * Centers the window. + */ + Center: () => void call(WindowCenter), + + /** + * Set the window title. + * @param title + */ + SetTitle: (title) => void call(WindowSetTitle, {title}), + + /** + * Makes the window fullscreen. + */ + Fullscreen: () => void call(WindowFullscreen), + + /** + * Unfullscreen the window. + */ + UnFullscreen: () => void call(WindowUnFullscreen), + + /** + * Set the window size. + * @param {number} width The window width + * @param {number} height The window height + */ + SetSize: (width, height) => call(WindowSetSize, {width,height}), + + /** + * Get the window size. + * @returns {Promise} The window size + */ + Size: () => { return call(WindowSize); }, + + /** + * Set the window maximum size. + * @param {number} width + * @param {number} height + */ + SetMaxSize: (width, height) => void call(WindowSetMaxSize, {width,height}), + + /** + * Set the window minimum size. + * @param {number} width + * @param {number} height + */ + SetMinSize: (width, height) => void call(WindowSetMinSize, {width,height}), + + /** + * Set window to be always on top. + * @param {boolean} onTop Whether the window should be always on top + */ + SetAlwaysOnTop: (onTop) => void call(WindowSetAlwaysOnTop, {alwaysOnTop:onTop}), + + /** + * Set the window relative position. + * @param {number} x + * @param {number} y + */ + SetRelativePosition: (x, y) => call(WindowSetRelativePosition, {x,y}), + + /** + * Get the window position. + * @returns {Promise} The window position + */ + RelativePosition: () => { return call(WindowRelativePosition); }, + + /** + * Get the screen the window is on. + * @returns {Promise} + */ + Screen: () => { return call(WindowScreen); }, + + /** + * Hide the window + */ + Hide: () => void call(WindowHide), + + /** + * Maximise the window + */ + Maximise: () => void call(WindowMaximise), + + /** + * Show the window + */ + Show: () => void call(WindowShow), + + /** + * Close the window + */ + Close: () => void call(WindowClose), + + /** + * Toggle the window maximise state + */ + ToggleMaximise: () => void call(WindowToggleMaximise), + + /** + * Unmaximise the window + */ + UnMaximise: () => void call(WindowUnMaximise), + + /** + * Minimise the window + */ + Minimise: () => void call(WindowMinimise), + + /** + * Unminimise the window + */ + UnMinimise: () => void call(WindowUnMinimise), + + /** + * Restore the window + */ + Restore: () => void call(WindowRestore), + + /** + * Set the background colour of the window. + * @param {number} r - A value between 0 and 255 + * @param {number} g - A value between 0 and 255 + * @param {number} b - A value between 0 and 255 + * @param {number} a - A value between 0 and 255 + */ + SetBackgroundColour: (r, g, b, a) => void call(WindowSetBackgroundColour, {r, g, b, a}), + + /** + * Set whether the window can be resized or not + * @param {boolean} resizable + */ + SetResizable: (resizable) => void call(WindowSetResizable, {resizable}), + + /** + * Get the window width + * @returns {Promise} + */ + Width: () => { return call(WindowWidth); }, + + /** + * Get the window height + * @returns {Promise} + */ + Height: () => { return call(WindowHeight); }, + + /** + * Zoom in the window + */ + ZoomIn: () => void call(WindowZoomIn), + + /** + * Zoom out the window + */ + ZoomOut: () => void call(WindowZoomOut), + + /** + * Reset the window zoom + */ + ZoomReset: () => void call(WindowZoomReset), + + /** + * Get the window zoom + * @returns {Promise} + */ + GetZoomLevel: () => { return call(WindowGetZoomLevel); }, + + /** + * Set the window zoom level + * @param {number} zoomLevel + */ + SetZoomLevel: (zoomLevel) => void call(WindowSetZoomLevel, {zoomLevel}), + }; +} diff --git a/v3/internal/runtime/desktop/wml.js b/v3/internal/runtime/desktop/wml.js new file mode 100644 index 000000000..32b7bc36f --- /dev/null +++ b/v3/internal/runtime/desktop/wml.js @@ -0,0 +1,102 @@ + +import {Emit, WailsEvent} from "./events"; +import {Question} from "./dialogs"; + +function sendEvent(eventName, data=null) { + let event = new WailsEvent(eventName, data); + Emit(event); +} + +function addWMLEventListeners() { + const elements = document.querySelectorAll('[wml-event]'); + elements.forEach(function (element) { + const eventType = element.getAttribute('wml-event'); + const confirm = element.getAttribute('wml-confirm'); + const trigger = element.getAttribute('wml-trigger') || "click"; + + let callback = function () { + if (confirm) { + Question({Title: "Confirm", Message:confirm, Detached: false, Buttons:[{Label:"Yes"},{Label:"No", IsDefault:true}]}).then(function (result) { + if (result !== "No") { + sendEvent(eventType); + } + }); + return; + } + sendEvent(eventType); + }; + // Remove existing listeners + + element.removeEventListener(trigger, callback); + + // Add new listener + element.addEventListener(trigger, callback); + }); +} + +function callWindowMethod(method) { + if (wails.Window[method] === undefined) { + console.log("Window method " + method + " not found"); + } + wails.Window[method](); +} + +function addWMLWindowListeners() { + const elements = document.querySelectorAll('[wml-window]'); + elements.forEach(function (element) { + const windowMethod = element.getAttribute('wml-window'); + const confirm = element.getAttribute('wml-confirm'); + const trigger = element.getAttribute('wml-trigger') || "click"; + + let callback = function () { + if (confirm) { + Question({Title: "Confirm", Message:confirm, Buttons:[{Label:"Yes"},{Label:"No", IsDefault:true}]}).then(function (result) { + if (result !== "No") { + callWindowMethod(windowMethod); + } + }); + return; + } + callWindowMethod(windowMethod); + }; + + // Remove existing listeners + element.removeEventListener(trigger, callback); + + // Add new listener + element.addEventListener(trigger, callback); + }); +} + +function addWMLOpenBrowserListener() { + const elements = document.querySelectorAll('[wml-openurl]'); + elements.forEach(function (element) { + const url = element.getAttribute('wml-openurl'); + const confirm = element.getAttribute('wml-confirm'); + const trigger = element.getAttribute('wml-trigger') || "click"; + + let callback = function () { + if (confirm) { + Question({Title: "Confirm", Message:confirm, Buttons:[{Label:"Yes"},{Label:"No", IsDefault:true}]}).then(function (result) { + if (result !== "No") { + void wails.Browser.OpenURL(url); + } + }); + return; + } + void wails.Browser.OpenURL(url); + }; + + // Remove existing listeners + element.removeEventListener(trigger, callback); + + // Add new listener + element.addEventListener(trigger, callback); + }); +} + +export function reloadWML() { + addWMLEventListeners(); + addWMLWindowListeners(); + addWMLOpenBrowserListener(); +} diff --git a/v3/internal/runtime/package-lock.json b/v3/internal/runtime/package-lock.json new file mode 100644 index 000000000..c822ec225 --- /dev/null +++ b/v3/internal/runtime/package-lock.json @@ -0,0 +1,8685 @@ +{ + "name": "runtime", + "version": "3.0.0", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "runtime", + "version": "3.0.0", + "license": "ISC", + "devDependencies": { + "esbuild": "^0.17.19", + "happy-dom": "^8.1.5", + "nanoid": "^4.0.0", + "npm-check-updates": "^16.6.3", + "svelte": "^3.55.1", + "vitest": "^0.28.3" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.17.19.tgz", + "integrity": "sha512-rIKddzqhmav7MSmoFCmDIb6e2W57geRsM94gV2l38fzhXMwq7hZoClug9USI2pFRGL06f4IOPHHpFNOkWieR8A==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.17.19.tgz", + "integrity": "sha512-KBMWvEZooR7+kzY0BtbTQn0OAYY7CsiydT63pVEaPtVYF0hXbUaOyZog37DKxK7NF3XacBJOpYT4adIJh+avxA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.17.19.tgz", + "integrity": "sha512-uUTTc4xGNDT7YSArp/zbtmbhO0uEEK9/ETW29Wk1thYUJBz3IVnvgEiEwEa9IeLyvnpKrWK64Utw2bgUmDveww==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.17.19.tgz", + "integrity": "sha512-80wEoCfF/hFKM6WE1FyBHc9SfUblloAWx6FJkFWTWiCoht9Mc0ARGEM47e67W9rI09YoUxJL68WHfDRYEAvOhg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.17.19.tgz", + "integrity": "sha512-IJM4JJsLhRYr9xdtLytPLSH9k/oxR3boaUIYiHkAawtwNOXKE8KoU8tMvryogdcT8AU+Bflmh81Xn6Q0vTZbQw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.17.19.tgz", + "integrity": "sha512-pBwbc7DufluUeGdjSU5Si+P3SoMF5DQ/F/UmTSb8HXO80ZEAJmrykPyzo1IfNbAoaqw48YRpv8shwd1NoI0jcQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.17.19.tgz", + "integrity": "sha512-4lu+n8Wk0XlajEhbEffdy2xy53dpR06SlzvhGByyg36qJw6Kpfk7cp45DR/62aPH9mtJRmIyrXAS5UWBrJT6TQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.17.19.tgz", + "integrity": "sha512-cdmT3KxjlOQ/gZ2cjfrQOtmhG4HJs6hhvm3mWSRDPtZ/lP5oe8FWceS10JaSJC13GBd4eH/haHnqf7hhGNLerA==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.17.19.tgz", + "integrity": "sha512-ct1Tg3WGwd3P+oZYqic+YZF4snNl2bsnMKRkb3ozHmnM0dGWuxcPTTntAF6bOP0Sp4x0PjSF+4uHQ1xvxfRKqg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.17.19.tgz", + "integrity": "sha512-w4IRhSy1VbsNxHRQpeGCHEmibqdTUx61Vc38APcsRbuVgK0OPEnQ0YD39Brymn96mOx48Y2laBQGqgZ0j9w6SQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.17.19.tgz", + "integrity": "sha512-2iAngUbBPMq439a+z//gE+9WBldoMp1s5GWsUSgqHLzLJ9WoZLZhpwWuym0u0u/4XmZ3gpHmzV84PonE+9IIdQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.17.19.tgz", + "integrity": "sha512-LKJltc4LVdMKHsrFe4MGNPp0hqDFA1Wpt3jE1gEyM3nKUvOiO//9PheZZHfYRfYl6AwdTH4aTcXSqBerX0ml4A==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.17.19.tgz", + "integrity": "sha512-/c/DGybs95WXNS8y3Ti/ytqETiW7EU44MEKuCAcpPto3YjQbyK3IQVKfF6nbghD7EcLUGl0NbiL5Rt5DMhn5tg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.17.19.tgz", + "integrity": "sha512-FC3nUAWhvFoutlhAkgHf8f5HwFWUL6bYdvLc/TTuxKlvLi3+pPzdZiFKSWz/PF30TB1K19SuCxDTI5KcqASJqA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.17.19.tgz", + "integrity": "sha512-IbFsFbxMWLuKEbH+7sTkKzL6NJmG2vRyy6K7JJo55w+8xDk7RElYn6xvXtDW8HCfoKBFK69f3pgBJSUSQPr+4Q==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.17.19.tgz", + "integrity": "sha512-68ngA9lg2H6zkZcyp22tsVt38mlhWde8l3eJLWkyLrp4HwMUr3c1s/M2t7+kHIhvMjglIBrFpncX1SzMckomGw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.17.19.tgz", + "integrity": "sha512-CwFq42rXCR8TYIjIfpXCbRX0rp1jo6cPIUPSaWwzbVI4aOfX96OXY8M6KNmtPcg7QjYeDmN+DD0Wp3LaBOLf4Q==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.17.19.tgz", + "integrity": "sha512-cnq5brJYrSZ2CF6c35eCmviIN3k3RczmHz8eYaVlNasVqsNY+JKohZU5MKmaOI+KkllCdzOKKdPs762VCPC20g==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.17.19.tgz", + "integrity": "sha512-vCRT7yP3zX+bKWFeP/zdS6SqdWB8OIpaRq/mbXQxTGHnIxspRtigpkUcDMlSCOejlHowLqII7K2JKevwyRP2rg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.17.19.tgz", + "integrity": "sha512-yYx+8jwowUstVdorcMdNlzklLYhPxjniHWFKgRqH7IFlUEa0Umu3KuYplf1HUZZ422e3NU9F4LGb+4O0Kdcaag==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.17.19.tgz", + "integrity": "sha512-eggDKanJszUtCdlVs0RB+h35wNlb5v4TWEkq4vZcmVt5u/HiDZrTXe2bWFQUez3RgNHwx/x4sk5++4NSSicKkw==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.17.19.tgz", + "integrity": "sha512-lAhycmKnVOuRYNtRtatQR1LPQf2oYCkRGkSFnseDAKPl8lu5SOsK/e1sXe5a0Pc5kHIHe6P2I/ilntNv2xf3cA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@gar/promisify": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz", + "integrity": "sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==", + "dev": true + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@npmcli/fs": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-3.1.0.tgz", + "integrity": "sha512-7kZUAaLscfgbwBQRbvdMYaZOWyMEcPTH/tJjnyAWJ/dvvs9Ef+CERx/qJb9GExJpl1qipaDGn7KqHnFGGixd0w==", + "dev": true, + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/git": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@npmcli/git/-/git-4.0.3.tgz", + "integrity": "sha512-8cXNkDIbnXPVbhXMmQ7/bklCAjtmPaXfI9aEM4iH+xSuEHINLMHhlfESvVwdqmHJRJkR48vNJTSUvoF6GRPSFA==", + "dev": true, + "dependencies": { + "@npmcli/promise-spawn": "^6.0.0", + "lru-cache": "^7.4.4", + "mkdirp": "^1.0.4", + "npm-pick-manifest": "^8.0.0", + "proc-log": "^3.0.0", + "promise-inflight": "^1.0.1", + "promise-retry": "^2.0.1", + "semver": "^7.3.5", + "which": "^3.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/installed-package-contents": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@npmcli/installed-package-contents/-/installed-package-contents-2.0.1.tgz", + "integrity": "sha512-GIykAFdOVK31Q1/zAtT5MbxqQL2vyl9mvFJv+OGu01zxbhL3p0xc8gJjdNGX1mWmUT43aEKVO2L6V/2j4TOsAA==", + "dev": true, + "dependencies": { + "npm-bundled": "^3.0.0", + "npm-normalize-package-bin": "^3.0.0" + }, + "bin": { + "installed-package-contents": "lib/index.js" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/move-file": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@npmcli/move-file/-/move-file-2.0.1.tgz", + "integrity": "sha512-mJd2Z5TjYWq/ttPLLGqArdtnC74J6bOzg4rMDnN+p1xTacZ2yPRCk2y0oSWQtygLR9YVQXgOcONrwtnk3JupxQ==", + "deprecated": "This functionality has been moved to @npmcli/fs", + "dev": true, + "dependencies": { + "mkdirp": "^1.0.4", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/@npmcli/node-gyp": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/node-gyp/-/node-gyp-3.0.0.tgz", + "integrity": "sha512-gp8pRXC2oOxu0DUE1/M3bYtb1b3/DbJ5aM113+XJBgfXdussRAsX0YOrOhdd8WvnAR6auDBvJomGAkLKA5ydxA==", + "dev": true, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/promise-spawn": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@npmcli/promise-spawn/-/promise-spawn-6.0.2.tgz", + "integrity": "sha512-gGq0NJkIGSwdbUt4yhdF8ZrmkGKVz9vAdVzpOfnom+V8PLSmSOVhZwbNvZZS1EYcJN5hzzKBxmmVVAInM6HQLg==", + "dev": true, + "dependencies": { + "which": "^3.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/run-script": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/run-script/-/run-script-6.0.0.tgz", + "integrity": "sha512-ql+AbRur1TeOdl1FY+RAwGW9fcr4ZwiVKabdvm93mujGREVuVLbdkXRJDrkTXSdCjaxYydr1wlA2v67jxWG5BQ==", + "dev": true, + "dependencies": { + "@npmcli/node-gyp": "^3.0.0", + "@npmcli/promise-spawn": "^6.0.0", + "node-gyp": "^9.0.0", + "read-package-json-fast": "^3.0.0", + "which": "^3.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@pnpm/network.ca-file": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@pnpm/network.ca-file/-/network.ca-file-1.0.2.tgz", + "integrity": "sha512-YcPQ8a0jwYU9bTdJDpXjMi7Brhkr1mXsXrUJvjqM2mQDgkRiz8jFaQGOdaLxgjtUfQgZhKy/O3cG/YwmgKaxLA==", + "dev": true, + "dependencies": { + "graceful-fs": "4.2.10" + }, + "engines": { + "node": ">=12.22.0" + } + }, + "node_modules/@pnpm/npm-conf": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@pnpm/npm-conf/-/npm-conf-1.0.5.tgz", + "integrity": "sha512-hD8ml183638O3R6/Txrh0L8VzGOrFXgRtRDG4qQC4tONdZ5Z1M+tlUUDUvrjYdmK6G+JTBTeaCLMna11cXzi8A==", + "dev": true, + "dependencies": { + "@pnpm/network.ca-file": "^1.0.1", + "config-chain": "^1.1.11" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@sindresorhus/is": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-5.3.0.tgz", + "integrity": "sha512-CX6t4SYQ37lzxicAqsBtxA3OseeoVrh9cSJ5PFYam0GksYlupRfy1A+Q4aYD3zvcfECLc0zO2u+ZnR2UYKvCrw==", + "dev": true, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sindresorhus/is?sponsor=1" + } + }, + "node_modules/@szmarczak/http-timer": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-5.0.1.tgz", + "integrity": "sha512-+PmQX0PiAYPMeVYe237LJAYvOMYW1j2rH5YROyS3b4CTVJum34HfRvKvAzozHAQG0TnHNdUfY9nCeUyRAs//cw==", + "dev": true, + "dependencies": { + "defer-to-connect": "^2.0.1" + }, + "engines": { + "node": ">=14.16" + } + }, + "node_modules/@tootallnate/once": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", + "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", + "dev": true, + "engines": { + "node": ">= 10" + } + }, + "node_modules/@types/chai": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.4.tgz", + "integrity": "sha512-KnRanxnpfpjUTqTCXslZSEdLfXExwgNxYPdiO2WGUj8+HDjFi8R3k5RVKPeSCzLjCcshCAtVO2QBbVuAV4kTnw==", + "dev": true + }, + "node_modules/@types/chai-subset": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@types/chai-subset/-/chai-subset-1.3.3.tgz", + "integrity": "sha512-frBecisrNGz+F4T6bcc+NLeolfiojh5FxW2klu669+8BARtyQv2C/GkNW6FUodVe4BroGMP/wER/YDGc7rEllw==", + "dev": true, + "dependencies": { + "@types/chai": "*" + } + }, + "node_modules/@types/http-cache-semantics": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.1.tgz", + "integrity": "sha512-SZs7ekbP8CN0txVG2xVRH6EgKmEm31BOxA07vkFaETzZz1xh+cbt8BcI0slpymvwhx5dlFnQG2rTlPVQn+iRPQ==", + "dev": true + }, + "node_modules/@types/node": { + "version": "18.11.18", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.18.tgz", + "integrity": "sha512-DHQpWGjyQKSHj3ebjFI/wRKcqQcdR+MoFBygntYOZytCqNfkd2ZC4ARDJ2DQqhjH5p85Nnd3jhUJIXrszFX/JA==", + "dev": true + }, + "node_modules/@vitest/expect": { + "version": "0.28.3", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-0.28.3.tgz", + "integrity": "sha512-dnxllhfln88DOvpAK1fuI7/xHwRgTgR4wdxHldPaoTaBu6Rh9zK5b//v/cjTkhOfNP/AJ8evbNO8H7c3biwd1g==", + "dev": true, + "dependencies": { + "@vitest/spy": "0.28.3", + "@vitest/utils": "0.28.3", + "chai": "^4.3.7" + } + }, + "node_modules/@vitest/runner": { + "version": "0.28.3", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-0.28.3.tgz", + "integrity": "sha512-P0qYbATaemy1midOLkw7qf8jraJszCoEvjQOSlseiXZyEDaZTZ50J+lolz2hWiWv6RwDu1iNseL9XLsG0Jm2KQ==", + "dev": true, + "dependencies": { + "@vitest/utils": "0.28.3", + "p-limit": "^4.0.0", + "pathe": "^1.1.0" + } + }, + "node_modules/@vitest/runner/node_modules/p-limit": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-4.0.0.tgz", + "integrity": "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^1.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@vitest/runner/node_modules/yocto-queue": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.0.0.tgz", + "integrity": "sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==", + "dev": true, + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@vitest/spy": { + "version": "0.28.3", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-0.28.3.tgz", + "integrity": "sha512-jULA6suS6CCr9VZfr7/9x97pZ0hC55prnUNHNrg5/q16ARBY38RsjsfhuUXt6QOwvIN3BhSS0QqPzyh5Di8g6w==", + "dev": true, + "dependencies": { + "tinyspy": "^1.0.2" + } + }, + "node_modules/@vitest/utils": { + "version": "0.28.3", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-0.28.3.tgz", + "integrity": "sha512-YHiQEHQqXyIbhDqETOJUKx9/psybF7SFFVCNfOvap0FvyUqbzTSDCa3S5lL4C0CLXkwVZttz9xknDoyHMguFRQ==", + "dev": true, + "dependencies": { + "cli-truncate": "^3.1.0", + "diff": "^5.1.0", + "loupe": "^2.3.6", + "picocolors": "^1.0.0", + "pretty-format": "^27.5.1" + } + }, + "node_modules/abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "dev": true + }, + "node_modules/acorn": { + "version": "8.8.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", + "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", + "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dev": true, + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/agentkeepalive": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.2.1.tgz", + "integrity": "sha512-Zn4cw2NEqd+9fiSVWMscnjyQ1a8Yfoc5oBajLeo5w+YBHgDUcEBY2hS4YpTz6iN5f/2zQiktcuM6tS8x1p9dpA==", + "dev": true, + "dependencies": { + "debug": "^4.1.0", + "depd": "^1.1.2", + "humanize-ms": "^1.2.1" + }, + "engines": { + "node": ">= 8.0.0" + } + }, + "node_modules/aggregate-error": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", + "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "dev": true, + "dependencies": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-align": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz", + "integrity": "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==", + "dev": true, + "dependencies": { + "string-width": "^4.1.0" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/aproba": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", + "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==", + "dev": true + }, + "node_modules/are-we-there-yet": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-3.0.1.tgz", + "integrity": "sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg==", + "dev": true, + "dependencies": { + "delegates": "^1.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/assertion-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/boxen": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/boxen/-/boxen-7.0.1.tgz", + "integrity": "sha512-8k2eH6SRAK00NDl1iX5q17RJ8rfl53TajdYxE3ssMLehbg487dEVgsad4pIsZb/QqBgYWIl6JOauMTLGX2Kpkw==", + "dev": true, + "dependencies": { + "ansi-align": "^3.0.1", + "camelcase": "^7.0.0", + "chalk": "^5.0.1", + "cli-boxes": "^3.0.0", + "string-width": "^5.1.2", + "type-fest": "^2.13.0", + "widest-line": "^4.0.1", + "wrap-ansi": "^8.0.1" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/boxen/node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/boxen/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true + }, + "node_modules/boxen/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/boxen/node_modules/strip-ansi": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.0.1.tgz", + "integrity": "sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw==", + "dev": true, + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true + }, + "node_modules/builtins": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/builtins/-/builtins-5.0.1.tgz", + "integrity": "sha512-qwVpFEHNfhYJIzNRBvd2C1kyo6jz3ZSMPyyuR47OPdiKWlbYnZNyDWuyR175qDnAJLiCo5fBBqPb3RiXgWlkOQ==", + "dev": true, + "dependencies": { + "semver": "^7.0.0" + } + }, + "node_modules/cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/cacache": { + "version": "17.0.4", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-17.0.4.tgz", + "integrity": "sha512-Z/nL3gU+zTUjz5pCA5vVjYM8pmaw2kxM7JEiE0fv3w77Wj+sFbi70CrBruUWH0uNcEdvLDixFpgA2JM4F4DBjA==", + "dev": true, + "dependencies": { + "@npmcli/fs": "^3.1.0", + "fs-minipass": "^3.0.0", + "glob": "^8.0.1", + "lru-cache": "^7.7.1", + "minipass": "^4.0.0", + "minipass-collect": "^1.0.2", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "p-map": "^4.0.0", + "promise-inflight": "^1.0.1", + "ssri": "^10.0.0", + "tar": "^6.1.11", + "unique-filename": "^3.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/cacheable-lookup": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-7.0.0.tgz", + "integrity": "sha512-+qJyx4xiKra8mZrcwhjMRMUhD5NR1R8esPkzIYxX96JiecFoxAXFuz/GpR3+ev4PE1WamHip78wV0vcmPQtp8w==", + "dev": true, + "engines": { + "node": ">=14.16" + } + }, + "node_modules/cacheable-request": { + "version": "10.2.7", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-10.2.7.tgz", + "integrity": "sha512-I4SA6mKgDxcxVbSt/UmIkb9Ny8qSkg6ReBHtAAXnZHk7KOSx5g3DTiAOaYzcHCs6oOdHn+bip9T48E6tMvK9hw==", + "dev": true, + "dependencies": { + "@types/http-cache-semantics": "^4.0.1", + "get-stream": "^6.0.1", + "http-cache-semantics": "^4.1.1", + "keyv": "^4.5.2", + "mimic-response": "^4.0.0", + "normalize-url": "^8.0.0", + "responselike": "^3.0.0" + }, + "engines": { + "node": ">=14.16" + } + }, + "node_modules/camelcase": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-7.0.1.tgz", + "integrity": "sha512-xlx1yCK2Oc1APsPXDL2LdlNP6+uu8OCDdhOBSVT279M/S+y75O30C2VuD8T2ogdePBBl7PfPF4504tnLgX3zfw==", + "dev": true, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/chai": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.7.tgz", + "integrity": "sha512-HLnAzZ2iupm25PlN0xFreAlBA5zaBSv3og0DdeGA4Ar6h6rJ3A0rolRUKJhSF2V10GZKDgWF/VmAEsNWjCRB+A==", + "dev": true, + "dependencies": { + "assertion-error": "^1.1.0", + "check-error": "^1.0.2", + "deep-eql": "^4.1.2", + "get-func-name": "^2.0.0", + "loupe": "^2.3.1", + "pathval": "^1.1.1", + "type-detect": "^4.0.5" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/chalk": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.2.0.tgz", + "integrity": "sha512-ree3Gqw/nazQAPuJJEy+avdl7QfZMcUvmHIKgEZkGL+xOBzRvup5Hxo6LHuMceSxOabuJLJm5Yp/92R9eMmMvA==", + "dev": true, + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/check-error": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", + "integrity": "sha512-BrgHpW9NURQgzoNyjfq0Wu6VFO6D7IZEmJNdtgNqpzGG8RuNFHt2jQxWlAs4HMe119chBnv+34syEZtc6IhLtA==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/ci-info": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.7.1.tgz", + "integrity": "sha512-4jYS4MOAaCIStSRwiuxc4B8MYhIe676yO1sYGzARnjXkWpmzZMMYxY6zu8WYWDhSuth5zhrQ1rhNSibyyvv4/w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "engines": { + "node": ">=8" + } + }, + "node_modules/clean-stack": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/cli-boxes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-3.0.0.tgz", + "integrity": "sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-table": { + "version": "0.3.11", + "resolved": "https://registry.npmjs.org/cli-table/-/cli-table-0.3.11.tgz", + "integrity": "sha512-IqLQi4lO0nIB4tcdTpN4LCB9FI3uqrJZK7RC515EnhZ6qBaglkIgICb1wjeAqpdoOabm1+SuQtkXIPdYC93jhQ==", + "dev": true, + "dependencies": { + "colors": "1.0.3" + }, + "engines": { + "node": ">= 0.2.0" + } + }, + "node_modules/cli-truncate": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-3.1.0.tgz", + "integrity": "sha512-wfOBkjXteqSnI59oPcJkcPl/ZmwvMMOj340qUIY1SKZCv0B9Cf4D4fAucRkIKQmsIuYK3x1rrgU7MeGRruiuiA==", + "dev": true, + "dependencies": { + "slice-ansi": "^5.0.0", + "string-width": "^5.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-truncate/node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/cli-truncate/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true + }, + "node_modules/cli-truncate/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-truncate/node_modules/strip-ansi": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.0.1.tgz", + "integrity": "sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw==", + "dev": true, + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/color-support": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", + "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", + "dev": true, + "bin": { + "color-support": "bin.js" + } + }, + "node_modules/colors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.0.3.tgz", + "integrity": "sha512-pFGrxThWcWQ2MsAz6RtgeWe4NK2kUE1WfsrvvlctdII745EW9I0yflqhe7++M5LEc7bV2c/9/5zc8sFcpL0Drw==", + "dev": true, + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/commander": { + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", + "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", + "dev": true, + "engines": { + "node": "^12.20.0 || >=14" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "node_modules/config-chain": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz", + "integrity": "sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==", + "dev": true, + "dependencies": { + "ini": "^1.3.4", + "proto-list": "~1.2.1" + } + }, + "node_modules/config-chain/node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true + }, + "node_modules/configstore": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/configstore/-/configstore-6.0.0.tgz", + "integrity": "sha512-cD31W1v3GqUlQvbBCGcXmd2Nj9SvLDOP1oQ0YFuLETufzSPaKp11rYBsSOm7rCsW3OnIRAFM3OxRhceaXNYHkA==", + "dev": true, + "dependencies": { + "dot-prop": "^6.0.1", + "graceful-fs": "^4.2.6", + "unique-string": "^3.0.0", + "write-file-atomic": "^3.0.3", + "xdg-basedir": "^5.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/yeoman/configstore?sponsor=1" + } + }, + "node_modules/console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==", + "dev": true + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/cross-spawn/node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/crypto-random-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-4.0.0.tgz", + "integrity": "sha512-x8dy3RnvYdlUcPOjkEHqozhiwzKNSq7GcPuXFbnyMOCHxX8V3OgIg/pYuabl2sbUPfIJaeAQB7PMOK8DFIdoRA==", + "dev": true, + "dependencies": { + "type-fest": "^1.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/crypto-random-string/node_modules/type-fest": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-1.4.0.tgz", + "integrity": "sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/css.escape": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz", + "integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==", + "dev": true + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "dev": true, + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/decompress-response/node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/deep-eql": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.3.tgz", + "integrity": "sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==", + "dev": true, + "dependencies": { + "type-detect": "^4.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "dev": true, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/defer-to-connect": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", + "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==", + "dev": true + }, + "node_modules/depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/diff": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.1.0.tgz", + "integrity": "sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/dot-prop": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-6.0.1.tgz", + "integrity": "sha512-tE7ztYzXHIeyvc7N+hR3oi7FIbf/NIjVP9hmAt3yMXzrQ072/fpjGLx2GxNxGxUl5V73MEqYzioOMoVhGMJ5cA==", + "dev": true, + "dependencies": { + "is-obj": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/encoding": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", + "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", + "dev": true, + "optional": true, + "dependencies": { + "iconv-lite": "^0.6.2" + } + }, + "node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/err-code": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", + "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==", + "dev": true + }, + "node_modules/esbuild": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.17.19.tgz", + "integrity": "sha512-XQ0jAPFkK/u3LcVRcvVHQcTIqD6E2H1fvZMA5dQPSOWb3suUbWbfbRf94pjc0bNzRYLfIrDRQXr7X+LHIm5oHw==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/android-arm": "0.17.19", + "@esbuild/android-arm64": "0.17.19", + "@esbuild/android-x64": "0.17.19", + "@esbuild/darwin-arm64": "0.17.19", + "@esbuild/darwin-x64": "0.17.19", + "@esbuild/freebsd-arm64": "0.17.19", + "@esbuild/freebsd-x64": "0.17.19", + "@esbuild/linux-arm": "0.17.19", + "@esbuild/linux-arm64": "0.17.19", + "@esbuild/linux-ia32": "0.17.19", + "@esbuild/linux-loong64": "0.17.19", + "@esbuild/linux-mips64el": "0.17.19", + "@esbuild/linux-ppc64": "0.17.19", + "@esbuild/linux-riscv64": "0.17.19", + "@esbuild/linux-s390x": "0.17.19", + "@esbuild/linux-x64": "0.17.19", + "@esbuild/netbsd-x64": "0.17.19", + "@esbuild/openbsd-x64": "0.17.19", + "@esbuild/sunos-x64": "0.17.19", + "@esbuild/win32-arm64": "0.17.19", + "@esbuild/win32-ia32": "0.17.19", + "@esbuild/win32-x64": "0.17.19" + } + }, + "node_modules/esbuild-android-64": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-android-64/-/esbuild-android-64-0.15.18.tgz", + "integrity": "sha512-wnpt3OXRhcjfIDSZu9bnzT4/TNTDsOUvip0foZOUBG7QbSt//w3QV4FInVJxNhKc/ErhUxc5z4QjHtMi7/TbgA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-android-arm64": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.15.18.tgz", + "integrity": "sha512-G4xu89B8FCzav9XU8EjsXacCKSG2FT7wW9J6hOc18soEHJdtWu03L3TQDGf0geNxfLTtxENKBzMSq9LlbjS8OQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-darwin-64": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.15.18.tgz", + "integrity": "sha512-2WAvs95uPnVJPuYKP0Eqx+Dl/jaYseZEUUT1sjg97TJa4oBtbAKnPnl3b5M9l51/nbx7+QAEtuummJZW0sBEmg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-darwin-arm64": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.15.18.tgz", + "integrity": "sha512-tKPSxcTJ5OmNb1btVikATJ8NftlyNlc8BVNtyT/UAr62JFOhwHlnoPrhYWz09akBLHI9nElFVfWSTSRsrZiDUA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-freebsd-64": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.15.18.tgz", + "integrity": "sha512-TT3uBUxkteAjR1QbsmvSsjpKjOX6UkCstr8nMr+q7zi3NuZ1oIpa8U41Y8I8dJH2fJgdC3Dj3CXO5biLQpfdZA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-freebsd-arm64": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.15.18.tgz", + "integrity": "sha512-R/oVr+X3Tkh+S0+tL41wRMbdWtpWB8hEAMsOXDumSSa6qJR89U0S/PpLXrGF7Wk/JykfpWNokERUpCeHDl47wA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-32": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.15.18.tgz", + "integrity": "sha512-lphF3HiCSYtaa9p1DtXndiQEeQDKPl9eN/XNoBf2amEghugNuqXNZA/ZovthNE2aa4EN43WroO0B85xVSjYkbg==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-64": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.15.18.tgz", + "integrity": "sha512-hNSeP97IviD7oxLKFuii5sDPJ+QHeiFTFLoLm7NZQligur8poNOWGIgpQ7Qf8Balb69hptMZzyOBIPtY09GZYw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-arm": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.15.18.tgz", + "integrity": "sha512-UH779gstRblS4aoS2qpMl3wjg7U0j+ygu3GjIeTonCcN79ZvpPee12Qun3vcdxX+37O5LFxz39XeW2I9bybMVA==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-arm64": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.15.18.tgz", + "integrity": "sha512-54qr8kg/6ilcxd+0V3h9rjT4qmjc0CccMVWrjOEM/pEcUzt8X62HfBSeZfT2ECpM7104mk4yfQXkosY8Quptug==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-mips64le": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.15.18.tgz", + "integrity": "sha512-Mk6Ppwzzz3YbMl/ZZL2P0q1tnYqh/trYZ1VfNP47C31yT0K8t9s7Z077QrDA/guU60tGNp2GOwCQnp+DYv7bxQ==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-ppc64le": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.15.18.tgz", + "integrity": "sha512-b0XkN4pL9WUulPTa/VKHx2wLCgvIAbgwABGnKMY19WhKZPT+8BxhZdqz6EgkqCLld7X5qiCY2F/bfpUUlnFZ9w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-riscv64": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.15.18.tgz", + "integrity": "sha512-ba2COaoF5wL6VLZWn04k+ACZjZ6NYniMSQStodFKH/Pu6RxzQqzsmjR1t9QC89VYJxBeyVPTaHuBMCejl3O/xg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-s390x": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.15.18.tgz", + "integrity": "sha512-VbpGuXEl5FCs1wDVp93O8UIzl3ZrglgnSQ+Hu79g7hZu6te6/YHgVJxCM2SqfIila0J3k0csfnf8VD2W7u2kzQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-netbsd-64": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.15.18.tgz", + "integrity": "sha512-98ukeCdvdX7wr1vUYQzKo4kQ0N2p27H7I11maINv73fVEXt2kyh4K4m9f35U1K43Xc2QGXlzAw0K9yoU7JUjOg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-openbsd-64": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.15.18.tgz", + "integrity": "sha512-yK5NCcH31Uae076AyQAXeJzt/vxIo9+omZRKj1pauhk3ITuADzuOx5N2fdHrAKPxN+zH3w96uFKlY7yIn490xQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-sunos-64": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.15.18.tgz", + "integrity": "sha512-On22LLFlBeLNj/YF3FT+cXcyKPEI263nflYlAhz5crxtp3yRG1Ugfr7ITyxmCmjm4vbN/dGrb/B7w7U8yJR9yw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-windows-32": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.15.18.tgz", + "integrity": "sha512-o+eyLu2MjVny/nt+E0uPnBxYuJHBvho8vWsC2lV61A7wwTWC3jkN2w36jtA+yv1UgYkHRihPuQsL23hsCYGcOQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-windows-64": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.15.18.tgz", + "integrity": "sha512-qinug1iTTaIIrCorAUjR0fcBk24fjzEedFYhhispP8Oc7SFvs+XeW3YpAKiKp8dRpizl4YYAhxMjlftAMJiaUw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-windows-arm64": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.15.18.tgz", + "integrity": "sha512-q9bsYzegpZcLziq0zgUi5KqGVtfhjxGbnksaBFYmWLxeV/S1fK4OLdq2DFYnXcLMjlZw2L0jLsk1eGoB522WXQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/escape-goat": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-goat/-/escape-goat-4.0.0.tgz", + "integrity": "sha512-2Sd4ShcWxbx6OY1IHyla/CVNwvg7XwZVoXZHcSu9w9SReNP1EzzD5T8NWKIR38fIqEns9kDWKUQTXXAmlDrdPg==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/fast-glob": { + "version": "3.2.12", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz", + "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-memoize": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/fast-memoize/-/fast-memoize-2.5.2.tgz", + "integrity": "sha512-Ue0LwpDYErFbmNnZSF0UH6eImUwDmogUO1jyE+JbN2gsQz/jICm1Ve7t9QT0rNSsfJt+Hs4/S3GnsDVjL4HVrw==", + "dev": true + }, + "node_modules/fastq": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", + "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", + "dev": true, + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/form-data-encoder": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-2.1.4.tgz", + "integrity": "sha512-yDYSgNMraqvnxiEXO4hi88+YZxaHC6QKzb5N84iRCTDeRO7ZALpir/lVmf/uXUhnwUr2O4HU8s/n6x+yNjQkHw==", + "dev": true, + "engines": { + "node": ">= 14.17" + } + }, + "node_modules/fp-and-or": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/fp-and-or/-/fp-and-or-0.1.3.tgz", + "integrity": "sha512-wJaE62fLaB3jCYvY2ZHjZvmKK2iiLiiehX38rz5QZxtdN8fVPJDeZUiVvJrHStdTc+23LHlyZuSEKgFc0pxi2g==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/fs-minipass": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-3.0.0.tgz", + "integrity": "sha512-EUojgQaSPy6sxcqcZgQv6TVF6jiKvurji3AxhAivs/Ep4O1UpS8TusaxpybfFHZ2skRhLqzk6WR8nqNYIMMDeA==", + "dev": true, + "dependencies": { + "minipass": "^4.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "node_modules/gauge": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-4.0.4.tgz", + "integrity": "sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg==", + "dev": true, + "dependencies": { + "aproba": "^1.0.3 || ^2.0.0", + "color-support": "^1.1.3", + "console-control-strings": "^1.1.0", + "has-unicode": "^2.0.1", + "signal-exit": "^3.0.7", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wide-align": "^1.1.5" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/get-func-name": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", + "integrity": "sha512-Hm0ixYtaSZ/V7C8FJrtZIuBBI+iSgL+1Aq82zSu8VQNB4S3Gk8e7Qs3VwBDJAhmRZcFqkl3tQu36g/Foh5I5ig==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/get-stdin": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-8.0.0.tgz", + "integrity": "sha512-sY22aA6xchAzprjyqmSEQv4UbAAzRN0L2dQB0NlN5acTTK9Don6nhoc3eAbUnpZiCANAMfd/+40kVdKfFygohg==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/glob": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/global-dirs": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-3.0.1.tgz", + "integrity": "sha512-NBcGGFbBA9s1VzD41QXDG+3++t9Mn5t1FpLdhESY6oKY4gYTFpX4wO3sqGUa0Srjtbfj3szX0RnemmrVRUdULA==", + "dev": true, + "dependencies": { + "ini": "2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/global-dirs/node_modules/ini": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ini/-/ini-2.0.0.tgz", + "integrity": "sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/got": { + "version": "12.5.3", + "resolved": "https://registry.npmjs.org/got/-/got-12.5.3.tgz", + "integrity": "sha512-8wKnb9MGU8IPGRIo+/ukTy9XLJBwDiCpIf5TVzQ9Cpol50eMTpBq2GAuDsuDIz7hTYmZgMgC1e9ydr6kSDWs3w==", + "dev": true, + "dependencies": { + "@sindresorhus/is": "^5.2.0", + "@szmarczak/http-timer": "^5.0.1", + "cacheable-lookup": "^7.0.0", + "cacheable-request": "^10.2.1", + "decompress-response": "^6.0.0", + "form-data-encoder": "^2.1.2", + "get-stream": "^6.0.1", + "http2-wrapper": "^2.1.10", + "lowercase-keys": "^3.0.0", + "p-cancelable": "^3.0.0", + "responselike": "^3.0.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sindresorhus/got?sponsor=1" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", + "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", + "dev": true + }, + "node_modules/happy-dom": { + "version": "8.1.5", + "resolved": "https://registry.npmjs.org/happy-dom/-/happy-dom-8.1.5.tgz", + "integrity": "sha512-/UXAJ2fHTs4H3vy7TS7c9PKFvPyaNialk2Er9NdXfpBKNaCITMOH03rkjHXp5jnJnSmRBa+av8E08PUAaIB1jQ==", + "dev": true, + "dependencies": { + "css.escape": "^1.5.1", + "he": "^1.2.0", + "node-fetch": "^2.x.x", + "webidl-conversions": "^7.0.0", + "whatwg-encoding": "^2.0.0", + "whatwg-mimetype": "^3.0.0" + } + }, + "node_modules/has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==", + "dev": true + }, + "node_modules/has-yarn": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-yarn/-/has-yarn-3.0.0.tgz", + "integrity": "sha512-IrsVwUHhEULx3R8f/aA8AHuEzAorplsab/v8HBzEiIukwq5i/EC+xmOW+HfP1OaDP+2JkgT1yILHN2O3UFIbcA==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true, + "bin": { + "he": "bin/he" + } + }, + "node_modules/hosted-git-info": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-5.2.1.tgz", + "integrity": "sha512-xIcQYMnhcx2Nr4JTjsFmwwnr9vldugPy9uVm0o87bjqqWMv9GaqsTeT+i99wTl0mk1uLxJtHxLb8kymqTENQsw==", + "dev": true, + "dependencies": { + "lru-cache": "^7.5.1" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/http-cache-semantics": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz", + "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==", + "dev": true + }, + "node_modules/http-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", + "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "dev": true, + "dependencies": { + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/http2-wrapper": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-2.2.0.tgz", + "integrity": "sha512-kZB0wxMo0sh1PehyjJUWRFEd99KC5TLjZ2cULC4f9iqJBAmKQQXEICjxl5iPJRwP40dpeHFqqhm7tYCvODpqpQ==", + "dev": true, + "dependencies": { + "quick-lru": "^5.1.1", + "resolve-alpn": "^1.2.0" + }, + "engines": { + "node": ">=10.19.0" + } + }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "dev": true, + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/humanize-ms": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", + "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", + "dev": true, + "dependencies": { + "ms": "^2.0.0" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ignore": { + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", + "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/ignore-walk": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-6.0.0.tgz", + "integrity": "sha512-bTf9UWe/UP1yxG3QUrj/KOvEhTAUWPcv+WvbFZ28LcqznXabp7Xu6o9y1JEC18+oqODuS7VhTpekV5XvFwsxJg==", + "dev": true, + "dependencies": { + "minimatch": "^5.0.1" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/import-lazy": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-4.0.0.tgz", + "integrity": "sha512-rKtvo6a868b5Hu3heneU+L4yEQ4jYKLtjpnPeUdK7h0yzXGmyBTypknlkCvHFBqfX9YlorEiMM6Dnq/5atfHkw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/infer-owner": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz", + "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==", + "dev": true + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "node_modules/ini": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ini/-/ini-3.0.1.tgz", + "integrity": "sha512-it4HyVAUTKBc6m8e1iXWvXSTdndF7HbdN713+kvLrymxTaU4AUBWrJ4vEooP+V7fexnVD3LKcBshjGGPefSMUQ==", + "dev": true, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/ip": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ip/-/ip-2.0.0.tgz", + "integrity": "sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ==", + "dev": true + }, + "node_modules/is-ci": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-3.0.1.tgz", + "integrity": "sha512-ZYvCgrefwqoQ6yTyYUbQu64HsITZ3NfKX1lzaEYdkTDcfKzzCI/wthRRYKkdjHKFVgNiXKAKm65Zo1pk2as/QQ==", + "dev": true, + "dependencies": { + "ci-info": "^3.2.0" + }, + "bin": { + "is-ci": "bin.js" + } + }, + "node_modules/is-core-module": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz", + "integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==", + "dev": true, + "dependencies": { + "has": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-installed-globally": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.4.0.tgz", + "integrity": "sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ==", + "dev": true, + "dependencies": { + "global-dirs": "^3.0.0", + "is-path-inside": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-lambda": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-lambda/-/is-lambda-1.0.1.tgz", + "integrity": "sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==", + "dev": true + }, + "node_modules/is-npm": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-6.0.0.tgz", + "integrity": "sha512-JEjxbSmtPSt1c8XTkVrlujcXdKV1/tvuQ7GwKcAlyiVLeYFQ2VHat8xfrDJsIkhCdF/tZ7CiIR3sy141c6+gPQ==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-obj": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", + "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", + "dev": true + }, + "node_modules/is-yarn-global": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/is-yarn-global/-/is-yarn-global-0.4.1.tgz", + "integrity": "sha512-/kppl+R+LO5VmhYSEWARUFjodS25D68gvj8W7z0I7OWhUla5xWu8KL6CtB2V0R6yqhnRgbcaREMr4EEM6htLPQ==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/jju": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/jju/-/jju-1.4.0.tgz", + "integrity": "sha512-8wb9Yw966OSxApiCt0K3yNJL8pnNeIv+OEq2YMidz4FKP6nonSRoOXc80iXY4JaN2FC11B9qsNmDsm+ZOfMROA==", + "dev": true + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true + }, + "node_modules/json-parse-even-better-errors": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-3.0.0.tgz", + "integrity": "sha512-iZbGHafX/59r39gPwVPRBGw0QQKnA7tte5pSMrhWOW7swGsVvVTjmfyAV9pNqk8YGT7tRCdxRu8uzcgZwoDooA==", + "dev": true, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/json-parse-helpfulerror": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/json-parse-helpfulerror/-/json-parse-helpfulerror-1.0.3.tgz", + "integrity": "sha512-XgP0FGR77+QhUxjXkwOMkC94k3WtqEBfcnjWqhRd82qTat4SWKRE+9kUnynz/shm3I4ea2+qISvTIeGTNU7kJg==", + "dev": true, + "dependencies": { + "jju": "^1.1.0" + } + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonc-parser": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz", + "integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==", + "dev": true + }, + "node_modules/jsonlines": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsonlines/-/jsonlines-0.1.1.tgz", + "integrity": "sha512-ekDrAGso79Cvf+dtm+mL8OBI2bmAOt3gssYs833De/C9NmIpWDWyUO4zPgB5x2/OhY366dkhgfPMYfwZF7yOZA==", + "dev": true + }, + "node_modules/jsonparse": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", + "integrity": "sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==", + "dev": true, + "engines": [ + "node >= 0.2.0" + ] + }, + "node_modules/keyv": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.2.tgz", + "integrity": "sha512-5MHbFaKn8cNSmVW7BYnijeAVlE4cYA/SVkifVgrh7yotnfhKmjuXpDKjrABLnT0SfHWV21P8ow07OGfRrNDg8g==", + "dev": true, + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/kleur": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", + "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/latest-version": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-7.0.0.tgz", + "integrity": "sha512-KvNT4XqAMzdcL6ka6Tl3i2lYeFDgXNCuIX+xNx6ZMVR1dFq+idXd9FLKNMOIx0t9mJ9/HudyX4oZWXZQ0UJHeg==", + "dev": true, + "dependencies": { + "package-json": "^8.1.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/local-pkg": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-0.4.2.tgz", + "integrity": "sha512-mlERgSPrbxU3BP4qBqAvvwlgW4MTg78iwJdGGnv7kibKjWcJksrG3t6LB5lXI93wXRDvG4NpUgJFmTG4T6rdrg==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true + }, + "node_modules/loupe": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.6.tgz", + "integrity": "sha512-RaPMZKiMy8/JruncMU5Bt6na1eftNoo++R4Y+N2FrxkDVTrGvcyzFTsaGif4QTeKESheMGegbhw6iUAq+5A8zA==", + "dev": true, + "dependencies": { + "get-func-name": "^2.0.0" + } + }, + "node_modules/lowercase-keys": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-3.0.0.tgz", + "integrity": "sha512-ozCC6gdQ+glXOQsveKD0YsDy8DSQFjDTz4zyzEHNV5+JP5D62LmfDZ6o1cycFx9ouG940M5dE8C8CTewdj2YWQ==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lru-cache": { + "version": "7.14.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.14.1.tgz", + "integrity": "sha512-ysxwsnTKdAx96aTRdhDOCQfDgbHnt8SK0KY8SEjO0wHinhWOFTESbjVCMPbU1uGXg/ch4lifqx0wfjOawU2+WA==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/make-fetch-happen": { + "version": "10.2.1", + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-10.2.1.tgz", + "integrity": "sha512-NgOPbRiaQM10DYXvN3/hhGVI2M5MtITFryzBGxHM5p4wnFxsVCbxkrBrDsk+EZ5OB4jEOT7AjDxtdF+KVEFT7w==", + "dev": true, + "dependencies": { + "agentkeepalive": "^4.2.1", + "cacache": "^16.1.0", + "http-cache-semantics": "^4.1.0", + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.0", + "is-lambda": "^1.0.1", + "lru-cache": "^7.7.1", + "minipass": "^3.1.6", + "minipass-collect": "^1.0.2", + "minipass-fetch": "^2.0.3", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "negotiator": "^0.6.3", + "promise-retry": "^2.0.1", + "socks-proxy-agent": "^7.0.0", + "ssri": "^9.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/make-fetch-happen/node_modules/@npmcli/fs": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-2.1.2.tgz", + "integrity": "sha512-yOJKRvohFOaLqipNtwYB9WugyZKhC/DZC4VYPmpaCzDBrA8YpK3qHZ8/HGscMnE4GqbkLNuVcCnxkeQEdGt6LQ==", + "dev": true, + "dependencies": { + "@gar/promisify": "^1.1.3", + "semver": "^7.3.5" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/make-fetch-happen/node_modules/cacache": { + "version": "16.1.3", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-16.1.3.tgz", + "integrity": "sha512-/+Emcj9DAXxX4cwlLmRI9c166RuL3w30zp4R7Joiv2cQTtTtA+jeuCAjH3ZlGnYS3tKENSrKhAzVVP9GVyzeYQ==", + "dev": true, + "dependencies": { + "@npmcli/fs": "^2.1.0", + "@npmcli/move-file": "^2.0.0", + "chownr": "^2.0.0", + "fs-minipass": "^2.1.0", + "glob": "^8.0.1", + "infer-owner": "^1.0.4", + "lru-cache": "^7.7.1", + "minipass": "^3.1.6", + "minipass-collect": "^1.0.2", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "mkdirp": "^1.0.4", + "p-map": "^4.0.0", + "promise-inflight": "^1.0.1", + "rimraf": "^3.0.2", + "ssri": "^9.0.0", + "tar": "^6.1.11", + "unique-filename": "^2.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/make-fetch-happen/node_modules/fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "dev": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/make-fetch-happen/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/make-fetch-happen/node_modules/ssri": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-9.0.1.tgz", + "integrity": "sha512-o57Wcn66jMQvfHG1FlYbWeZWW/dHZhJXjpIcTfXldXEk5nz5lStPo3mK0OJQfGR3RbZUlbISexbljkJzuEj/8Q==", + "dev": true, + "dependencies": { + "minipass": "^3.1.1" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/make-fetch-happen/node_modules/unique-filename": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-2.0.1.tgz", + "integrity": "sha512-ODWHtkkdx3IAR+veKxFV+VBkUMcN+FaqzUUd7IZzt+0zhDZFPFxhlqwPF3YQvMHx1TD0tdgYl+kuPnJ8E6ql7A==", + "dev": true, + "dependencies": { + "unique-slug": "^3.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/make-fetch-happen/node_modules/unique-slug": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-3.0.0.tgz", + "integrity": "sha512-8EyMynh679x/0gqE9fT9oilG+qEt+ibFyqjuVTsZn1+CMxH+XLlpvr2UZx4nVcCwTpx81nICr2JQFkM+HPLq4w==", + "dev": true, + "dependencies": { + "imurmurhash": "^0.1.4" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "dependencies": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mimic-response": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-4.0.0.tgz", + "integrity": "sha512-e5ISH9xMYU0DzrT+jl8q2ze9D6eWBto+I8CNpe+VI+K2J/F/k3PdkdTdz4wvGVH4NTpo+NRYTVIuMQEMMcsLqg==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/minimist": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.7.tgz", + "integrity": "sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minipass": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-4.0.0.tgz", + "integrity": "sha512-g2Uuh2jEKoht+zvO6vJqXmYpflPqzRBT+Th2h01DKh5z7wbY/AZ2gCQ78cP70YoHPyFdY30YBV5WxgLOEwOykw==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-collect": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-1.0.2.tgz", + "integrity": "sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==", + "dev": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minipass-collect/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-fetch": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-2.1.2.tgz", + "integrity": "sha512-LT49Zi2/WMROHYoqGgdlQIZh8mLPZmOrN2NdJjMXxYe4nkN6FUyuPuOAOedNJDrx0IRGg9+4guZewtp8hE6TxA==", + "dev": true, + "dependencies": { + "minipass": "^3.1.6", + "minipass-sized": "^1.0.3", + "minizlib": "^2.1.2" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + }, + "optionalDependencies": { + "encoding": "^0.1.13" + } + }, + "node_modules/minipass-fetch/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-flush": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz", + "integrity": "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==", + "dev": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minipass-flush/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-json-stream": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minipass-json-stream/-/minipass-json-stream-1.0.1.tgz", + "integrity": "sha512-ODqY18UZt/I8k+b7rl2AENgbWE8IDYam+undIJONvigAz8KR5GWblsFTEfQs0WODsjbSXWlm+JHEv8Gr6Tfdbg==", + "dev": true, + "dependencies": { + "jsonparse": "^1.3.1", + "minipass": "^3.0.0" + } + }, + "node_modules/minipass-json-stream/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-pipeline": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz", + "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==", + "dev": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-pipeline/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-sized": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/minipass-sized/-/minipass-sized-1.0.3.tgz", + "integrity": "sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==", + "dev": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-sized/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "dev": true, + "dependencies": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minizlib/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true, + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/mlly": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.1.0.tgz", + "integrity": "sha512-cwzBrBfwGC1gYJyfcy8TcZU1f+dbH/T+TuOhtYP2wLv/Fb51/uV7HJQfBPtEupZ2ORLRU1EKFS/QfS3eo9+kBQ==", + "dev": true, + "dependencies": { + "acorn": "^8.8.1", + "pathe": "^1.0.0", + "pkg-types": "^1.0.1", + "ufo": "^1.0.1" + } + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/nanoid": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-4.0.0.tgz", + "integrity": "sha512-IgBP8piMxe/gf73RTQx7hmnhwz0aaEXYakvqZyE302IXW3HyVNhdNGC+O2MwMAVhLEnvXlvKtGbtJf6wvHihCg==", + "dev": true, + "bin": { + "nanoid": "bin/nanoid.js" + }, + "engines": { + "node": "^14 || ^16 || >=18" + } + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/node-fetch": { + "version": "2.6.7", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", + "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", + "dev": true, + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/node-gyp": { + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-9.3.1.tgz", + "integrity": "sha512-4Q16ZCqq3g8awk6UplT7AuxQ35XN4R/yf/+wSAwcBUAjg7l58RTactWaP8fIDTi0FzI7YcVLujwExakZlfWkXg==", + "dev": true, + "dependencies": { + "env-paths": "^2.2.0", + "glob": "^7.1.4", + "graceful-fs": "^4.2.6", + "make-fetch-happen": "^10.0.3", + "nopt": "^6.0.0", + "npmlog": "^6.0.0", + "rimraf": "^3.0.2", + "semver": "^7.3.5", + "tar": "^6.1.2", + "which": "^2.0.2" + }, + "bin": { + "node-gyp": "bin/node-gyp.js" + }, + "engines": { + "node": "^12.13 || ^14.13 || >=16" + } + }, + "node_modules/node-gyp/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/node-gyp/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/node-gyp/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/node-gyp/node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/nopt": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-6.0.0.tgz", + "integrity": "sha512-ZwLpbTgdhuZUnZzjd7nb1ZV+4DoiC6/sfiVKok72ym/4Tlf+DFdlHYmT2JPmcNNWV6Pi3SDf1kT+A4r9RTuT9g==", + "dev": true, + "dependencies": { + "abbrev": "^1.0.0" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/normalize-package-data": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-5.0.0.tgz", + "integrity": "sha512-h9iPVIfrVZ9wVYQnxFgtw1ugSvGEMOlyPWWtm8BMJhnwyEL/FLbYbTY3V3PpjI/BUK67n9PEWDu6eHzu1fB15Q==", + "dev": true, + "dependencies": { + "hosted-git-info": "^6.0.0", + "is-core-module": "^2.8.1", + "semver": "^7.3.5", + "validate-npm-package-license": "^3.0.4" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/normalize-package-data/node_modules/hosted-git-info": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-6.1.1.tgz", + "integrity": "sha512-r0EI+HBMcXadMrugk0GCQ+6BQV39PiWAZVfq7oIckeGiN7sjRGyQxPdft3nQekFTCQbYxLBH+/axZMeH8UX6+w==", + "dev": true, + "dependencies": { + "lru-cache": "^7.5.1" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/normalize-url": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-8.0.0.tgz", + "integrity": "sha512-uVFpKhj5MheNBJRTiMZ9pE/7hD1QTeEvugSJW/OmLzAp78PB5O6adfMNTvmfKhXBkvCzC+rqifWcVYpGFwTjnw==", + "dev": true, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm-bundled": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-3.0.0.tgz", + "integrity": "sha512-Vq0eyEQy+elFpzsKjMss9kxqb9tG3YHg4dsyWuUENuzvSUWe1TCnW/vV9FkhvBk/brEDoDiVd+M1Btosa6ImdQ==", + "dev": true, + "dependencies": { + "npm-normalize-package-bin": "^3.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm-check-updates": { + "version": "16.6.3", + "resolved": "https://registry.npmjs.org/npm-check-updates/-/npm-check-updates-16.6.3.tgz", + "integrity": "sha512-EKhsCbBcVrPlYKzaYQtRhGv9fxpexwROcvl5HebCUNpiCSlOWrzaJvrMlwi9i9GCyJCnH+YAeBPYdqnArA390A==", + "dev": true, + "dependencies": { + "chalk": "^5.2.0", + "cli-table": "^0.3.11", + "commander": "^9.4.1", + "fast-memoize": "^2.5.2", + "find-up": "5.0.0", + "fp-and-or": "^0.1.3", + "get-stdin": "^8.0.0", + "globby": "^11.0.4", + "hosted-git-info": "^5.1.0", + "ini": "^3.0.1", + "json-parse-helpfulerror": "^1.0.3", + "jsonlines": "^0.1.1", + "lodash": "^4.17.21", + "minimatch": "^5.1.2", + "p-map": "^4.0.0", + "pacote": "15.0.8", + "parse-github-url": "^1.0.2", + "progress": "^2.0.3", + "prompts-ncu": "^2.5.1", + "rc-config-loader": "^4.1.1", + "remote-git-tags": "^3.0.0", + "rimraf": "^3.0.2", + "semver": "^7.3.8", + "semver-utils": "^1.1.4", + "source-map-support": "^0.5.21", + "spawn-please": "^2.0.1", + "untildify": "^4.0.0", + "update-notifier": "^6.0.2", + "yaml": "^2.2.0" + }, + "bin": { + "ncu": "build/src/bin/cli.js", + "npm-check-updates": "build/src/bin/cli.js" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/npm-install-checks": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/npm-install-checks/-/npm-install-checks-6.0.0.tgz", + "integrity": "sha512-SBU9oFglRVZnfElwAtF14NivyulDqF1VKqqwNsFW9HDcbHMAPHpRSsVFgKuwFGq/hVvWZExz62Th0kvxn/XE7Q==", + "dev": true, + "dependencies": { + "semver": "^7.1.1" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm-normalize-package-bin": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-3.0.0.tgz", + "integrity": "sha512-g+DPQSkusnk7HYXr75NtzkIP4+N81i3RPsGFidF3DzHd9MT9wWngmqoeg/fnHFz5MNdtG4w03s+QnhewSLTT2Q==", + "dev": true, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm-package-arg": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-10.1.0.tgz", + "integrity": "sha512-uFyyCEmgBfZTtrKk/5xDfHp6+MdrqGotX/VoOyEEl3mBwiEE5FlBaePanazJSVMPT7vKepcjYBY2ztg9A3yPIA==", + "dev": true, + "dependencies": { + "hosted-git-info": "^6.0.0", + "proc-log": "^3.0.0", + "semver": "^7.3.5", + "validate-npm-package-name": "^5.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm-package-arg/node_modules/hosted-git-info": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-6.1.1.tgz", + "integrity": "sha512-r0EI+HBMcXadMrugk0GCQ+6BQV39PiWAZVfq7oIckeGiN7sjRGyQxPdft3nQekFTCQbYxLBH+/axZMeH8UX6+w==", + "dev": true, + "dependencies": { + "lru-cache": "^7.5.1" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm-packlist": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-7.0.4.tgz", + "integrity": "sha512-d6RGEuRrNS5/N84iglPivjaJPxhDbZmlbTwTDX2IbcRHG5bZCdtysYMhwiPvcF4GisXHGn7xsxv+GQ7T/02M5Q==", + "dev": true, + "dependencies": { + "ignore-walk": "^6.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm-pick-manifest": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/npm-pick-manifest/-/npm-pick-manifest-8.0.1.tgz", + "integrity": "sha512-mRtvlBjTsJvfCCdmPtiu2bdlx8d/KXtF7yNXNWe7G0Z36qWA9Ny5zXsI2PfBZEv7SXgoxTmNaTzGSbbzDZChoA==", + "dev": true, + "dependencies": { + "npm-install-checks": "^6.0.0", + "npm-normalize-package-bin": "^3.0.0", + "npm-package-arg": "^10.0.0", + "semver": "^7.3.5" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm-registry-fetch": { + "version": "14.0.3", + "resolved": "https://registry.npmjs.org/npm-registry-fetch/-/npm-registry-fetch-14.0.3.tgz", + "integrity": "sha512-YaeRbVNpnWvsGOjX2wk5s85XJ7l1qQBGAp724h8e2CZFFhMSuw9enom7K1mWVUtvXO1uUSFIAPofQK0pPN0ZcA==", + "dev": true, + "dependencies": { + "make-fetch-happen": "^11.0.0", + "minipass": "^4.0.0", + "minipass-fetch": "^3.0.0", + "minipass-json-stream": "^1.0.1", + "minizlib": "^2.1.2", + "npm-package-arg": "^10.0.0", + "proc-log": "^3.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm-registry-fetch/node_modules/make-fetch-happen": { + "version": "11.0.2", + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-11.0.2.tgz", + "integrity": "sha512-5n/Pq41w/uZghpdlXAY5kIM85RgJThtTH/NYBRAZ9VUOBWV90USaQjwGrw76fZP3Lj5hl/VZjpVvOaRBMoL/2w==", + "dev": true, + "dependencies": { + "agentkeepalive": "^4.2.1", + "cacache": "^17.0.0", + "http-cache-semantics": "^4.1.0", + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.0", + "is-lambda": "^1.0.1", + "lru-cache": "^7.7.1", + "minipass": "^4.0.0", + "minipass-collect": "^1.0.2", + "minipass-fetch": "^3.0.0", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "negotiator": "^0.6.3", + "promise-retry": "^2.0.1", + "socks-proxy-agent": "^7.0.0", + "ssri": "^10.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm-registry-fetch/node_modules/minipass-fetch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-3.0.1.tgz", + "integrity": "sha512-t9/wowtf7DYkwz8cfMSt0rMwiyNIBXf5CKZ3S5ZMqRqMYT0oLTp0x1WorMI9WTwvaPg21r1JbFxJMum8JrLGfw==", + "dev": true, + "dependencies": { + "minipass": "^4.0.0", + "minipass-sized": "^1.0.3", + "minizlib": "^2.1.2" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + }, + "optionalDependencies": { + "encoding": "^0.1.13" + } + }, + "node_modules/npmlog": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-6.0.2.tgz", + "integrity": "sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg==", + "dev": true, + "dependencies": { + "are-we-there-yet": "^3.0.0", + "console-control-strings": "^1.1.0", + "gauge": "^4.0.3", + "set-blocking": "^2.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/p-cancelable": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-3.0.0.tgz", + "integrity": "sha512-mlVgR3PGuzlo0MmTdk4cXqXWlwQDLnONTAg6sm62XkMJEiRxN3GL3SffkYvqwonbkJBcrI7Uvv5Zh9yjvn2iUw==", + "dev": true, + "engines": { + "node": ">=12.20" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-map": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", + "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", + "dev": true, + "dependencies": { + "aggregate-error": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/package-json": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/package-json/-/package-json-8.1.0.tgz", + "integrity": "sha512-hySwcV8RAWeAfPsXb9/HGSPn8lwDnv6fabH+obUZKX169QknRkRhPxd1yMubpKDskLFATkl3jHpNtVtDPFA0Wg==", + "dev": true, + "dependencies": { + "got": "^12.1.0", + "registry-auth-token": "^5.0.1", + "registry-url": "^6.0.0", + "semver": "^7.3.7" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pacote": { + "version": "15.0.8", + "resolved": "https://registry.npmjs.org/pacote/-/pacote-15.0.8.tgz", + "integrity": "sha512-UlcumB/XS6xyyIMwg/WwMAyUmga+RivB5KgkRwA1hZNtrx+0Bt41KxHCvg1kr0pZ/ZeD8qjhW4fph6VaYRCbLw==", + "dev": true, + "dependencies": { + "@npmcli/git": "^4.0.0", + "@npmcli/installed-package-contents": "^2.0.1", + "@npmcli/promise-spawn": "^6.0.1", + "@npmcli/run-script": "^6.0.0", + "cacache": "^17.0.0", + "fs-minipass": "^3.0.0", + "minipass": "^4.0.0", + "npm-package-arg": "^10.0.0", + "npm-packlist": "^7.0.0", + "npm-pick-manifest": "^8.0.0", + "npm-registry-fetch": "^14.0.0", + "proc-log": "^3.0.0", + "promise-retry": "^2.0.1", + "read-package-json": "^6.0.0", + "read-package-json-fast": "^3.0.0", + "ssri": "^10.0.0", + "tar": "^6.1.11" + }, + "bin": { + "pacote": "lib/bin.js" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/parse-github-url": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/parse-github-url/-/parse-github-url-1.0.2.tgz", + "integrity": "sha512-kgBf6avCbO3Cn6+RnzRGLkUsv4ZVqv/VfAYkRsyBcgkshNvVBkRn1FEZcW0Jb+npXQWm2vHPnnOqFteZxRRGNw==", + "dev": true, + "bin": { + "parse-github-url": "cli.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/pathe": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.0.tgz", + "integrity": "sha512-ODbEPR0KKHqECXW1GoxdDb+AZvULmXjVPy4rt+pGo2+TnjJTIPJQSVS6N63n8T2Ip+syHhbn52OewKicV0373w==", + "dev": true + }, + "node_modules/pathval": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", + "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pkg-types": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.0.1.tgz", + "integrity": "sha512-jHv9HB+Ho7dj6ItwppRDDl0iZRYBD0jsakHXtFgoLr+cHSF6xC+QL54sJmWxyGxOLYSHm0afhXhXcQDQqH9z8g==", + "dev": true, + "dependencies": { + "jsonc-parser": "^3.2.0", + "mlly": "^1.0.0", + "pathe": "^1.0.0" + } + }, + "node_modules/postcss": { + "version": "8.4.21", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.21.tgz", + "integrity": "sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + } + ], + "dependencies": { + "nanoid": "^3.3.4", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss/node_modules/nanoid": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz", + "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==", + "dev": true, + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/pretty-format": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", + "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^17.0.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/proc-log": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-3.0.0.tgz", + "integrity": "sha512-++Vn7NS4Xf9NacaU9Xq3URUuqZETPsf8L4j5/ckhaRYsfPeRyzGw+iDjFhV/Jr3uNmTvvddEJFWh5R1gRgUH8A==", + "dev": true, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/promise-inflight": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", + "integrity": "sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==", + "dev": true + }, + "node_modules/promise-retry": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz", + "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==", + "dev": true, + "dependencies": { + "err-code": "^2.0.2", + "retry": "^0.12.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/prompts-ncu": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/prompts-ncu/-/prompts-ncu-2.5.1.tgz", + "integrity": "sha512-Hdd7GgV7b76Yh9FP9HL1D9xqtJCJdVPpiM2vDtuoc8W1KfweJe15gutFYmxkq83ViFaagFM8K0UcPCQ/tZq8bA==", + "dev": true, + "dependencies": { + "kleur": "^4.0.1", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/proto-list": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", + "integrity": "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==", + "dev": true + }, + "node_modules/pupa": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/pupa/-/pupa-3.1.0.tgz", + "integrity": "sha512-FLpr4flz5xZTSJxSeaheeMKN/EDzMdK7b8PTOC6a5PYFKTucWbdqjgqaEyH0shFiSJrVB1+Qqi4Tk19ccU6Aug==", + "dev": true, + "dependencies": { + "escape-goat": "^4.0.0" + }, + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/quick-lru": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", + "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "dev": true, + "dependencies": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "bin": { + "rc": "cli.js" + } + }, + "node_modules/rc-config-loader": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/rc-config-loader/-/rc-config-loader-4.1.2.tgz", + "integrity": "sha512-qKTnVWFl9OQYKATPzdfaZIbTxcHziQl92zYSxYC6umhOqyAsoj8H8Gq/+aFjAso68sBdjTz3A7omqeAkkF1MWg==", + "dev": true, + "dependencies": { + "debug": "^4.3.4", + "js-yaml": "^4.1.0", + "json5": "^2.2.2", + "require-from-string": "^2.0.2" + } + }, + "node_modules/rc/node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true + }, + "node_modules/react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", + "dev": true + }, + "node_modules/read-package-json": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/read-package-json/-/read-package-json-6.0.0.tgz", + "integrity": "sha512-b/9jxWJ8EwogJPpv99ma+QwtqB7FSl3+V6UXS7Aaay8/5VwMY50oIFooY1UKXMWpfNCM6T/PoGqa5GD1g9xf9w==", + "dev": true, + "dependencies": { + "glob": "^8.0.1", + "json-parse-even-better-errors": "^3.0.0", + "normalize-package-data": "^5.0.0", + "npm-normalize-package-bin": "^3.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/read-package-json-fast": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/read-package-json-fast/-/read-package-json-fast-3.0.2.tgz", + "integrity": "sha512-0J+Msgym3vrLOUB3hzQCuZHII0xkNGCtz/HJH9xZshwv9DbDwkw1KaE3gx/e2J5rpEY5rtOy6cyhKOPrkP7FZw==", + "dev": true, + "dependencies": { + "json-parse-even-better-errors": "^3.0.0", + "npm-normalize-package-bin": "^3.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/registry-auth-token": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-5.0.1.tgz", + "integrity": "sha512-UfxVOj8seK1yaIOiieV4FIP01vfBDLsY0H9sQzi9EbbUdJiuuBjJgLa1DpImXMNPnVkBD4eVxTEXcrZA6kfpJA==", + "dev": true, + "dependencies": { + "@pnpm/npm-conf": "^1.0.4" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/registry-url": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-6.0.1.tgz", + "integrity": "sha512-+crtS5QjFRqFCoQmvGduwYWEBng99ZvmFvF+cUJkGYF1L1BfU8C6Zp9T7f5vPAwyLkUExpvK+ANVZmGU49qi4Q==", + "dev": true, + "dependencies": { + "rc": "1.2.8" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/remote-git-tags": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/remote-git-tags/-/remote-git-tags-3.0.0.tgz", + "integrity": "sha512-C9hAO4eoEsX+OXA4rla66pXZQ+TLQ8T9dttgQj18yuKlPMTVkIkdYXvlMC55IuUsIkV6DpmQYi10JKFLaU+l7w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve": { + "version": "1.22.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", + "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", + "dev": true, + "dependencies": { + "is-core-module": "^2.9.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-alpn": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz", + "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==", + "dev": true + }, + "node_modules/responselike": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-3.0.0.tgz", + "integrity": "sha512-40yHxbNcl2+rzXvZuVkrYohathsSJlMTXKryG5y8uciHv1+xDLHQpgjG64JUO9nrEq2jGLH6IZ8BcZyw3wrweg==", + "dev": true, + "dependencies": { + "lowercase-keys": "^3.0.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/rimraf/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/rollup": { + "version": "2.79.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.79.1.tgz", + "integrity": "sha512-uKxbd0IhMZOhjAiD5oAFp7BqvkA4Dv47qpOCtaNvng4HBwdbWtdOh8f5nZNuk2rp51PMGk3bzfWu5oayNEuYnw==", + "dev": true, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=10.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true + }, + "node_modules/semver": { + "version": "7.3.8", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", + "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/semver-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-4.0.0.tgz", + "integrity": "sha512-0Ju4+6A8iOnpL/Thra7dZsSlOHYAHIeMxfhWQRI1/VLcT3WDBZKKtQt/QkBOsiIN9ZpuvHE6cGZ0x4glCMmfiA==", + "dev": true, + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/semver-utils": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/semver-utils/-/semver-utils-1.1.4.tgz", + "integrity": "sha512-EjnoLE5OGmDAVV/8YDoN5KiajNadjzIp9BAHOhYeQHt7j0UWxjmgsx4YD48wp4Ue1Qogq38F1GNUJNqF1kKKxA==", + "dev": true + }, + "node_modules/semver/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", + "dev": true + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true + }, + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "dev": true + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/slice-ansi": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-5.0.0.tgz", + "integrity": "sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^6.0.0", + "is-fullwidth-code-point": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/slice-ansi/node_modules/is-fullwidth-code-point": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz", + "integrity": "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "dev": true, + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.7.1.tgz", + "integrity": "sha512-7maUZy1N7uo6+WVEX6psASxtNlKaNVMlGQKkG/63nEDdLOWNbiUMoLK7X4uYoLhQstau72mLgfEWcXcwsaHbYQ==", + "dev": true, + "dependencies": { + "ip": "^2.0.0", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.13.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks-proxy-agent": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-7.0.0.tgz", + "integrity": "sha512-Fgl0YPZ902wEsAyiQ+idGd1A7rSFx/ayC1CQVMw5P+EQx2V0SgpGtf6OKFhVjPflPUl9YMmEOnmfjCdMUsygww==", + "dev": true, + "dependencies": { + "agent-base": "^6.0.2", + "debug": "^4.3.3", + "socks": "^2.6.2" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-js": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", + "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/spawn-please": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/spawn-please/-/spawn-please-2.0.1.tgz", + "integrity": "sha512-W+cFbZR2q2mMTfjz5ZGvhBAiX+e/zczFCNlbS9mxiSdYswBXwUuBUT+a0urH+xZZa8f/bs0mXHyZsZHR9hKogA==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.3" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/spdx-correct": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", + "integrity": "sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==", + "dev": true, + "dependencies": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-exceptions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", + "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==", + "dev": true + }, + "node_modules/spdx-expression-parse": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "dev": true, + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-license-ids": { + "version": "3.0.12", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.12.tgz", + "integrity": "sha512-rr+VVSXtRhO4OHbXUiAF7xW3Bo9DuuF6C5jH+q/x15j2jniycgKbxU09Hr0WqlSLUs4i4ltHGXqTe7VHclYWyA==", + "dev": true + }, + "node_modules/ssri": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-10.0.1.tgz", + "integrity": "sha512-WVy6di9DlPOeBWEjMScpNipeSX2jIZBGEn5Uuo8Q7aIuFEuDX0pw8RxcOjlD1TWP4obi24ki7m/13+nFpcbXrw==", + "dev": true, + "dependencies": { + "minipass": "^4.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true + }, + "node_modules/std-env": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.3.1.tgz", + "integrity": "sha512-3H20QlwQsSm2OvAxWIYhs+j01MzzqwMwGiiO1NQaJYZgJZFPuAbf95/DiKRBSTYIJ2FeGUc+B/6mPGcWP9dO3Q==", + "dev": true + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/strip-literal": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-1.0.0.tgz", + "integrity": "sha512-5o4LsH1lzBzO9UFH63AJ2ad2/S2AVx6NtjOcaz+VTT2h1RiRvbipW72z8M/lxEhcPHDBQwpDrnTF7sXy/7OwCQ==", + "dev": true, + "dependencies": { + "acorn": "^8.8.1" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/svelte": { + "version": "3.55.1", + "resolved": "https://registry.npmjs.org/svelte/-/svelte-3.55.1.tgz", + "integrity": "sha512-S+87/P0Ve67HxKkEV23iCdAh/SX1xiSfjF1HOglno/YTbSTW7RniICMCofWGdJJbdjw3S+0PfFb1JtGfTXE0oQ==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/tar": { + "version": "6.1.13", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.13.tgz", + "integrity": "sha512-jdIBIN6LTIe2jqzay/2vtYLlBHa3JF42ot3h1dW8Q0PaAG4v8rm0cvpVePtau5C6OKXGGcgO9q2AMNSWxiLqKw==", + "dev": true, + "dependencies": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^4.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/tar/node_modules/fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "dev": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/tar/node_modules/fs-minipass/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tinybench": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.3.1.tgz", + "integrity": "sha512-hGYWYBMPr7p4g5IarQE7XhlyWveh1EKhy4wUBS1LrHXCKYgvz+4/jCqgmJqZxxldesn05vccrtME2RLLZNW7iA==", + "dev": true + }, + "node_modules/tinypool": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-0.3.1.tgz", + "integrity": "sha512-zLA1ZXlstbU2rlpA4CIeVaqvWq41MTWqLY3FfsAXgC8+f7Pk7zroaJQxDgxn1xNudKW6Kmj4808rPFShUlIRmQ==", + "dev": true, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tinyspy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-1.0.2.tgz", + "integrity": "sha512-bSGlgwLBYf7PnUsQ6WOc6SJ3pGOcd+d8AA6EUnLDDM0kWEstC1JIlSZA3UNliDXhd9ABoS7hiRBDCu+XP/sf1Q==", + "dev": true, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "dev": true + }, + "node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/type-fest": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", + "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", + "dev": true, + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typedarray-to-buffer": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", + "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", + "dev": true, + "dependencies": { + "is-typedarray": "^1.0.0" + } + }, + "node_modules/ufo": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.0.1.tgz", + "integrity": "sha512-boAm74ubXHY7KJQZLlXrtMz52qFvpsbOxDcZOnw/Wf+LS4Mmyu7JxmzD4tDLtUQtmZECypJ0FrCz4QIe6dvKRA==", + "dev": true + }, + "node_modules/unique-filename": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-3.0.0.tgz", + "integrity": "sha512-afXhuC55wkAmZ0P18QsVE6kp8JaxrEokN2HGIoIVv2ijHQd419H0+6EigAFcIzXeMIkcIkNBpB3L/DXB3cTS/g==", + "dev": true, + "dependencies": { + "unique-slug": "^4.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/unique-slug": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-4.0.0.tgz", + "integrity": "sha512-WrcA6AyEfqDX5bWige/4NQfPZMtASNVxdmWR76WESYQVAACSgWcR6e9i0mofqqBxYFtL4oAxPIptY73/0YE1DQ==", + "dev": true, + "dependencies": { + "imurmurhash": "^0.1.4" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/unique-string": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-3.0.0.tgz", + "integrity": "sha512-VGXBUVwxKMBUznyffQweQABPRRW1vHZAbadFZud4pLFAqRGvv/96vafgjWFqzourzr8YonlQiPgH0YCJfawoGQ==", + "dev": true, + "dependencies": { + "crypto-random-string": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/untildify": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/untildify/-/untildify-4.0.0.tgz", + "integrity": "sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/update-notifier": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-6.0.2.tgz", + "integrity": "sha512-EDxhTEVPZZRLWYcJ4ZXjGFN0oP7qYvbXWzEgRm/Yql4dHX5wDbvh89YHP6PK1lzZJYrMtXUuZZz8XGK+U6U1og==", + "dev": true, + "dependencies": { + "boxen": "^7.0.0", + "chalk": "^5.0.1", + "configstore": "^6.0.0", + "has-yarn": "^3.0.0", + "import-lazy": "^4.0.0", + "is-ci": "^3.0.1", + "is-installed-globally": "^0.4.0", + "is-npm": "^6.0.0", + "is-yarn-global": "^0.4.0", + "latest-version": "^7.0.0", + "pupa": "^3.1.0", + "semver": "^7.3.7", + "semver-diff": "^4.0.0", + "xdg-basedir": "^5.1.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/yeoman/update-notifier?sponsor=1" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true + }, + "node_modules/validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "dev": true, + "dependencies": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "node_modules/validate-npm-package-name": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-5.0.0.tgz", + "integrity": "sha512-YuKoXDAhBYxY7SfOKxHBDoSyENFeW5VvIIQp2TGQuit8gpK6MnWaQelBKxso72DoxTZfZdcP3W90LqpSkgPzLQ==", + "dev": true, + "dependencies": { + "builtins": "^5.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/vite": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/vite/-/vite-3.2.5.tgz", + "integrity": "sha512-4mVEpXpSOgrssFZAOmGIr85wPHKvaDAcXqxVxVRZhljkJOMZi1ibLibzjLHzJvcok8BMguLc7g1W6W/GqZbLdQ==", + "dev": true, + "dependencies": { + "esbuild": "^0.15.9", + "postcss": "^8.4.18", + "resolve": "^1.22.1", + "rollup": "^2.79.1" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + }, + "peerDependencies": { + "@types/node": ">= 14", + "less": "*", + "sass": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "sass": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/vite-node": { + "version": "0.28.3", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-0.28.3.tgz", + "integrity": "sha512-uJJAOkgVwdfCX8PUQhqLyDOpkBS5+j+FdbsXoPVPDlvVjRkb/W/mLYQPSL6J+t8R0UV8tJSe8c9VyxVQNsDSyg==", + "dev": true, + "dependencies": { + "cac": "^6.7.14", + "debug": "^4.3.4", + "mlly": "^1.1.0", + "pathe": "^1.1.0", + "picocolors": "^1.0.0", + "source-map": "^0.6.1", + "source-map-support": "^0.5.21", + "vite": "^3.0.0 || ^4.0.0" + }, + "bin": { + "vite-node": "vite-node.mjs" + }, + "engines": { + "node": ">=v14.16.0" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/vite/node_modules/@esbuild/android-arm": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.15.18.tgz", + "integrity": "sha512-5GT+kcs2WVGjVs7+boataCkO5Fg0y4kCjzkB5bAip7H4jfnOS3dA6KPiww9W1OEKTKeAcUVhdZGvgI65OXmUnw==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-loong64": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.15.18.tgz", + "integrity": "sha512-L4jVKS82XVhw2nvzLg/19ClLWg0y27ulRwuP7lcyL6AbUWB5aPglXY3M21mauDQMDfRLs8cQmeT03r/+X3cZYQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/esbuild": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.15.18.tgz", + "integrity": "sha512-x/R72SmW3sSFRm5zrrIjAhCeQSAWoni3CmHEqfQrZIQTM3lVCdehdwuIqaOtfC2slvpdlLa62GYoN8SxT23m6Q==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/android-arm": "0.15.18", + "@esbuild/linux-loong64": "0.15.18", + "esbuild-android-64": "0.15.18", + "esbuild-android-arm64": "0.15.18", + "esbuild-darwin-64": "0.15.18", + "esbuild-darwin-arm64": "0.15.18", + "esbuild-freebsd-64": "0.15.18", + "esbuild-freebsd-arm64": "0.15.18", + "esbuild-linux-32": "0.15.18", + "esbuild-linux-64": "0.15.18", + "esbuild-linux-arm": "0.15.18", + "esbuild-linux-arm64": "0.15.18", + "esbuild-linux-mips64le": "0.15.18", + "esbuild-linux-ppc64le": "0.15.18", + "esbuild-linux-riscv64": "0.15.18", + "esbuild-linux-s390x": "0.15.18", + "esbuild-netbsd-64": "0.15.18", + "esbuild-openbsd-64": "0.15.18", + "esbuild-sunos-64": "0.15.18", + "esbuild-windows-32": "0.15.18", + "esbuild-windows-64": "0.15.18", + "esbuild-windows-arm64": "0.15.18" + } + }, + "node_modules/vitest": { + "version": "0.28.3", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-0.28.3.tgz", + "integrity": "sha512-N41VPNf3VGJlWQizGvl1P5MGyv3ZZA2Zvh+2V8L6tYBAAuqqDK4zExunT1Cdb6dGfZ4gr+IMrnG8d4Z6j9ctPw==", + "dev": true, + "dependencies": { + "@types/chai": "^4.3.4", + "@types/chai-subset": "^1.3.3", + "@types/node": "*", + "@vitest/expect": "0.28.3", + "@vitest/runner": "0.28.3", + "@vitest/spy": "0.28.3", + "@vitest/utils": "0.28.3", + "acorn": "^8.8.1", + "acorn-walk": "^8.2.0", + "cac": "^6.7.14", + "chai": "^4.3.7", + "debug": "^4.3.4", + "local-pkg": "^0.4.2", + "pathe": "^1.1.0", + "picocolors": "^1.0.0", + "source-map": "^0.6.1", + "std-env": "^3.3.1", + "strip-literal": "^1.0.0", + "tinybench": "^2.3.1", + "tinypool": "^0.3.1", + "tinyspy": "^1.0.2", + "vite": "^3.0.0 || ^4.0.0", + "vite-node": "0.28.3", + "why-is-node-running": "^2.2.2" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": ">=v14.16.0" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@vitest/browser": "*", + "@vitest/ui": "*", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@vitest/browser": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } + } + }, + "node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/whatwg-encoding": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz", + "integrity": "sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==", + "dev": true, + "dependencies": { + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/whatwg-mimetype": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz", + "integrity": "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dev": true, + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/whatwg-url/node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "dev": true + }, + "node_modules/which": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-3.0.0.tgz", + "integrity": "sha512-nla//68K9NU6yRiwDY/Q8aU6siKlSs64aEC7+IV56QoAuyQT2ovsJcgGYGyqMOmI/CGN1BOR6mM5EN0FBO+zyQ==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/why-is-node-running": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.2.2.tgz", + "integrity": "sha512-6tSwToZxTOcotxHeA+qGCq1mVzKR3CwcJGmVcY+QE8SHy6TnpFnh8PAvPNHYr7EcuVeG0QSMxtYCuO1ta/G/oA==", + "dev": true, + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wide-align": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", + "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", + "dev": true, + "dependencies": { + "string-width": "^1.0.2 || 2 || 3 || 4" + } + }, + "node_modules/widest-line": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-4.0.1.tgz", + "integrity": "sha512-o0cyEG0e8GPzT4iGHphIOh0cJOV8fivsXxddQasHPHfoZf1ZexrfeA21w2NaEN1RHE+fXlfISmOE8R9N3u3Qig==", + "dev": true, + "dependencies": { + "string-width": "^5.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/widest-line/node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/widest-line/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true + }, + "node_modules/widest-line/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/widest-line/node_modules/strip-ansi": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.0.1.tgz", + "integrity": "sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw==", + "dev": true, + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true + }, + "node_modules/wrap-ansi/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/wrap-ansi/node_modules/strip-ansi": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.0.1.tgz", + "integrity": "sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw==", + "dev": true, + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true + }, + "node_modules/write-file-atomic": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", + "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", + "dev": true, + "dependencies": { + "imurmurhash": "^0.1.4", + "is-typedarray": "^1.0.0", + "signal-exit": "^3.0.2", + "typedarray-to-buffer": "^3.1.5" + } + }, + "node_modules/xdg-basedir": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-5.1.0.tgz", + "integrity": "sha512-GCPAHLvrIH13+c0SuacwvRYj2SxJXQ4kaVTT5xgL3kPrz56XxkF21IGhjSE1+W0aw7gpBWRGXLCPnPby6lSpmQ==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/yaml": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.2.2.tgz", + "integrity": "sha512-CBKFWExMn46Foo4cldiChEzn7S7SRV+wqiluAb6xmueD/fGyRHIhX8m14vVGgeFWjN540nKCNVj6P21eQjgTuA==", + "dev": true, + "engines": { + "node": ">= 14" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + }, + "dependencies": { + "@esbuild/android-arm": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.17.19.tgz", + "integrity": "sha512-rIKddzqhmav7MSmoFCmDIb6e2W57geRsM94gV2l38fzhXMwq7hZoClug9USI2pFRGL06f4IOPHHpFNOkWieR8A==", + "dev": true, + "optional": true + }, + "@esbuild/android-arm64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.17.19.tgz", + "integrity": "sha512-KBMWvEZooR7+kzY0BtbTQn0OAYY7CsiydT63pVEaPtVYF0hXbUaOyZog37DKxK7NF3XacBJOpYT4adIJh+avxA==", + "dev": true, + "optional": true + }, + "@esbuild/android-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.17.19.tgz", + "integrity": "sha512-uUTTc4xGNDT7YSArp/zbtmbhO0uEEK9/ETW29Wk1thYUJBz3IVnvgEiEwEa9IeLyvnpKrWK64Utw2bgUmDveww==", + "dev": true, + "optional": true + }, + "@esbuild/darwin-arm64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.17.19.tgz", + "integrity": "sha512-80wEoCfF/hFKM6WE1FyBHc9SfUblloAWx6FJkFWTWiCoht9Mc0ARGEM47e67W9rI09YoUxJL68WHfDRYEAvOhg==", + "dev": true, + "optional": true + }, + "@esbuild/darwin-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.17.19.tgz", + "integrity": "sha512-IJM4JJsLhRYr9xdtLytPLSH9k/oxR3boaUIYiHkAawtwNOXKE8KoU8tMvryogdcT8AU+Bflmh81Xn6Q0vTZbQw==", + "dev": true, + "optional": true + }, + "@esbuild/freebsd-arm64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.17.19.tgz", + "integrity": "sha512-pBwbc7DufluUeGdjSU5Si+P3SoMF5DQ/F/UmTSb8HXO80ZEAJmrykPyzo1IfNbAoaqw48YRpv8shwd1NoI0jcQ==", + "dev": true, + "optional": true + }, + "@esbuild/freebsd-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.17.19.tgz", + "integrity": "sha512-4lu+n8Wk0XlajEhbEffdy2xy53dpR06SlzvhGByyg36qJw6Kpfk7cp45DR/62aPH9mtJRmIyrXAS5UWBrJT6TQ==", + "dev": true, + "optional": true + }, + "@esbuild/linux-arm": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.17.19.tgz", + "integrity": "sha512-cdmT3KxjlOQ/gZ2cjfrQOtmhG4HJs6hhvm3mWSRDPtZ/lP5oe8FWceS10JaSJC13GBd4eH/haHnqf7hhGNLerA==", + "dev": true, + "optional": true + }, + "@esbuild/linux-arm64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.17.19.tgz", + "integrity": "sha512-ct1Tg3WGwd3P+oZYqic+YZF4snNl2bsnMKRkb3ozHmnM0dGWuxcPTTntAF6bOP0Sp4x0PjSF+4uHQ1xvxfRKqg==", + "dev": true, + "optional": true + }, + "@esbuild/linux-ia32": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.17.19.tgz", + "integrity": "sha512-w4IRhSy1VbsNxHRQpeGCHEmibqdTUx61Vc38APcsRbuVgK0OPEnQ0YD39Brymn96mOx48Y2laBQGqgZ0j9w6SQ==", + "dev": true, + "optional": true + }, + "@esbuild/linux-loong64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.17.19.tgz", + "integrity": "sha512-2iAngUbBPMq439a+z//gE+9WBldoMp1s5GWsUSgqHLzLJ9WoZLZhpwWuym0u0u/4XmZ3gpHmzV84PonE+9IIdQ==", + "dev": true, + "optional": true + }, + "@esbuild/linux-mips64el": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.17.19.tgz", + "integrity": "sha512-LKJltc4LVdMKHsrFe4MGNPp0hqDFA1Wpt3jE1gEyM3nKUvOiO//9PheZZHfYRfYl6AwdTH4aTcXSqBerX0ml4A==", + "dev": true, + "optional": true + }, + "@esbuild/linux-ppc64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.17.19.tgz", + "integrity": "sha512-/c/DGybs95WXNS8y3Ti/ytqETiW7EU44MEKuCAcpPto3YjQbyK3IQVKfF6nbghD7EcLUGl0NbiL5Rt5DMhn5tg==", + "dev": true, + "optional": true + }, + "@esbuild/linux-riscv64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.17.19.tgz", + "integrity": "sha512-FC3nUAWhvFoutlhAkgHf8f5HwFWUL6bYdvLc/TTuxKlvLi3+pPzdZiFKSWz/PF30TB1K19SuCxDTI5KcqASJqA==", + "dev": true, + "optional": true + }, + "@esbuild/linux-s390x": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.17.19.tgz", + "integrity": "sha512-IbFsFbxMWLuKEbH+7sTkKzL6NJmG2vRyy6K7JJo55w+8xDk7RElYn6xvXtDW8HCfoKBFK69f3pgBJSUSQPr+4Q==", + "dev": true, + "optional": true + }, + "@esbuild/linux-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.17.19.tgz", + "integrity": "sha512-68ngA9lg2H6zkZcyp22tsVt38mlhWde8l3eJLWkyLrp4HwMUr3c1s/M2t7+kHIhvMjglIBrFpncX1SzMckomGw==", + "dev": true, + "optional": true + }, + "@esbuild/netbsd-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.17.19.tgz", + "integrity": "sha512-CwFq42rXCR8TYIjIfpXCbRX0rp1jo6cPIUPSaWwzbVI4aOfX96OXY8M6KNmtPcg7QjYeDmN+DD0Wp3LaBOLf4Q==", + "dev": true, + "optional": true + }, + "@esbuild/openbsd-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.17.19.tgz", + "integrity": "sha512-cnq5brJYrSZ2CF6c35eCmviIN3k3RczmHz8eYaVlNasVqsNY+JKohZU5MKmaOI+KkllCdzOKKdPs762VCPC20g==", + "dev": true, + "optional": true + }, + "@esbuild/sunos-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.17.19.tgz", + "integrity": "sha512-vCRT7yP3zX+bKWFeP/zdS6SqdWB8OIpaRq/mbXQxTGHnIxspRtigpkUcDMlSCOejlHowLqII7K2JKevwyRP2rg==", + "dev": true, + "optional": true + }, + "@esbuild/win32-arm64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.17.19.tgz", + "integrity": "sha512-yYx+8jwowUstVdorcMdNlzklLYhPxjniHWFKgRqH7IFlUEa0Umu3KuYplf1HUZZ422e3NU9F4LGb+4O0Kdcaag==", + "dev": true, + "optional": true + }, + "@esbuild/win32-ia32": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.17.19.tgz", + "integrity": "sha512-eggDKanJszUtCdlVs0RB+h35wNlb5v4TWEkq4vZcmVt5u/HiDZrTXe2bWFQUez3RgNHwx/x4sk5++4NSSicKkw==", + "dev": true, + "optional": true + }, + "@esbuild/win32-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.17.19.tgz", + "integrity": "sha512-lAhycmKnVOuRYNtRtatQR1LPQf2oYCkRGkSFnseDAKPl8lu5SOsK/e1sXe5a0Pc5kHIHe6P2I/ilntNv2xf3cA==", + "dev": true, + "optional": true + }, + "@gar/promisify": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz", + "integrity": "sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==", + "dev": true + }, + "@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "requires": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + } + }, + "@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true + }, + "@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "requires": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + } + }, + "@npmcli/fs": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-3.1.0.tgz", + "integrity": "sha512-7kZUAaLscfgbwBQRbvdMYaZOWyMEcPTH/tJjnyAWJ/dvvs9Ef+CERx/qJb9GExJpl1qipaDGn7KqHnFGGixd0w==", + "dev": true, + "requires": { + "semver": "^7.3.5" + } + }, + "@npmcli/git": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@npmcli/git/-/git-4.0.3.tgz", + "integrity": "sha512-8cXNkDIbnXPVbhXMmQ7/bklCAjtmPaXfI9aEM4iH+xSuEHINLMHhlfESvVwdqmHJRJkR48vNJTSUvoF6GRPSFA==", + "dev": true, + "requires": { + "@npmcli/promise-spawn": "^6.0.0", + "lru-cache": "^7.4.4", + "mkdirp": "^1.0.4", + "npm-pick-manifest": "^8.0.0", + "proc-log": "^3.0.0", + "promise-inflight": "^1.0.1", + "promise-retry": "^2.0.1", + "semver": "^7.3.5", + "which": "^3.0.0" + } + }, + "@npmcli/installed-package-contents": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@npmcli/installed-package-contents/-/installed-package-contents-2.0.1.tgz", + "integrity": "sha512-GIykAFdOVK31Q1/zAtT5MbxqQL2vyl9mvFJv+OGu01zxbhL3p0xc8gJjdNGX1mWmUT43aEKVO2L6V/2j4TOsAA==", + "dev": true, + "requires": { + "npm-bundled": "^3.0.0", + "npm-normalize-package-bin": "^3.0.0" + } + }, + "@npmcli/move-file": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@npmcli/move-file/-/move-file-2.0.1.tgz", + "integrity": "sha512-mJd2Z5TjYWq/ttPLLGqArdtnC74J6bOzg4rMDnN+p1xTacZ2yPRCk2y0oSWQtygLR9YVQXgOcONrwtnk3JupxQ==", + "dev": true, + "requires": { + "mkdirp": "^1.0.4", + "rimraf": "^3.0.2" + } + }, + "@npmcli/node-gyp": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/node-gyp/-/node-gyp-3.0.0.tgz", + "integrity": "sha512-gp8pRXC2oOxu0DUE1/M3bYtb1b3/DbJ5aM113+XJBgfXdussRAsX0YOrOhdd8WvnAR6auDBvJomGAkLKA5ydxA==", + "dev": true + }, + "@npmcli/promise-spawn": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@npmcli/promise-spawn/-/promise-spawn-6.0.2.tgz", + "integrity": "sha512-gGq0NJkIGSwdbUt4yhdF8ZrmkGKVz9vAdVzpOfnom+V8PLSmSOVhZwbNvZZS1EYcJN5hzzKBxmmVVAInM6HQLg==", + "dev": true, + "requires": { + "which": "^3.0.0" + } + }, + "@npmcli/run-script": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/run-script/-/run-script-6.0.0.tgz", + "integrity": "sha512-ql+AbRur1TeOdl1FY+RAwGW9fcr4ZwiVKabdvm93mujGREVuVLbdkXRJDrkTXSdCjaxYydr1wlA2v67jxWG5BQ==", + "dev": true, + "requires": { + "@npmcli/node-gyp": "^3.0.0", + "@npmcli/promise-spawn": "^6.0.0", + "node-gyp": "^9.0.0", + "read-package-json-fast": "^3.0.0", + "which": "^3.0.0" + } + }, + "@pnpm/network.ca-file": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@pnpm/network.ca-file/-/network.ca-file-1.0.2.tgz", + "integrity": "sha512-YcPQ8a0jwYU9bTdJDpXjMi7Brhkr1mXsXrUJvjqM2mQDgkRiz8jFaQGOdaLxgjtUfQgZhKy/O3cG/YwmgKaxLA==", + "dev": true, + "requires": { + "graceful-fs": "4.2.10" + } + }, + "@pnpm/npm-conf": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@pnpm/npm-conf/-/npm-conf-1.0.5.tgz", + "integrity": "sha512-hD8ml183638O3R6/Txrh0L8VzGOrFXgRtRDG4qQC4tONdZ5Z1M+tlUUDUvrjYdmK6G+JTBTeaCLMna11cXzi8A==", + "dev": true, + "requires": { + "@pnpm/network.ca-file": "^1.0.1", + "config-chain": "^1.1.11" + } + }, + "@sindresorhus/is": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-5.3.0.tgz", + "integrity": "sha512-CX6t4SYQ37lzxicAqsBtxA3OseeoVrh9cSJ5PFYam0GksYlupRfy1A+Q4aYD3zvcfECLc0zO2u+ZnR2UYKvCrw==", + "dev": true + }, + "@szmarczak/http-timer": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-5.0.1.tgz", + "integrity": "sha512-+PmQX0PiAYPMeVYe237LJAYvOMYW1j2rH5YROyS3b4CTVJum34HfRvKvAzozHAQG0TnHNdUfY9nCeUyRAs//cw==", + "dev": true, + "requires": { + "defer-to-connect": "^2.0.1" + } + }, + "@tootallnate/once": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", + "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", + "dev": true + }, + "@types/chai": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.4.tgz", + "integrity": "sha512-KnRanxnpfpjUTqTCXslZSEdLfXExwgNxYPdiO2WGUj8+HDjFi8R3k5RVKPeSCzLjCcshCAtVO2QBbVuAV4kTnw==", + "dev": true + }, + "@types/chai-subset": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@types/chai-subset/-/chai-subset-1.3.3.tgz", + "integrity": "sha512-frBecisrNGz+F4T6bcc+NLeolfiojh5FxW2klu669+8BARtyQv2C/GkNW6FUodVe4BroGMP/wER/YDGc7rEllw==", + "dev": true, + "requires": { + "@types/chai": "*" + } + }, + "@types/http-cache-semantics": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.1.tgz", + "integrity": "sha512-SZs7ekbP8CN0txVG2xVRH6EgKmEm31BOxA07vkFaETzZz1xh+cbt8BcI0slpymvwhx5dlFnQG2rTlPVQn+iRPQ==", + "dev": true + }, + "@types/node": { + "version": "18.11.18", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.18.tgz", + "integrity": "sha512-DHQpWGjyQKSHj3ebjFI/wRKcqQcdR+MoFBygntYOZytCqNfkd2ZC4ARDJ2DQqhjH5p85Nnd3jhUJIXrszFX/JA==", + "dev": true + }, + "@vitest/expect": { + "version": "0.28.3", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-0.28.3.tgz", + "integrity": "sha512-dnxllhfln88DOvpAK1fuI7/xHwRgTgR4wdxHldPaoTaBu6Rh9zK5b//v/cjTkhOfNP/AJ8evbNO8H7c3biwd1g==", + "dev": true, + "requires": { + "@vitest/spy": "0.28.3", + "@vitest/utils": "0.28.3", + "chai": "^4.3.7" + } + }, + "@vitest/runner": { + "version": "0.28.3", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-0.28.3.tgz", + "integrity": "sha512-P0qYbATaemy1midOLkw7qf8jraJszCoEvjQOSlseiXZyEDaZTZ50J+lolz2hWiWv6RwDu1iNseL9XLsG0Jm2KQ==", + "dev": true, + "requires": { + "@vitest/utils": "0.28.3", + "p-limit": "^4.0.0", + "pathe": "^1.1.0" + }, + "dependencies": { + "p-limit": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-4.0.0.tgz", + "integrity": "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==", + "dev": true, + "requires": { + "yocto-queue": "^1.0.0" + } + }, + "yocto-queue": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.0.0.tgz", + "integrity": "sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==", + "dev": true + } + } + }, + "@vitest/spy": { + "version": "0.28.3", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-0.28.3.tgz", + "integrity": "sha512-jULA6suS6CCr9VZfr7/9x97pZ0hC55prnUNHNrg5/q16ARBY38RsjsfhuUXt6QOwvIN3BhSS0QqPzyh5Di8g6w==", + "dev": true, + "requires": { + "tinyspy": "^1.0.2" + } + }, + "@vitest/utils": { + "version": "0.28.3", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-0.28.3.tgz", + "integrity": "sha512-YHiQEHQqXyIbhDqETOJUKx9/psybF7SFFVCNfOvap0FvyUqbzTSDCa3S5lL4C0CLXkwVZttz9xknDoyHMguFRQ==", + "dev": true, + "requires": { + "cli-truncate": "^3.1.0", + "diff": "^5.1.0", + "loupe": "^2.3.6", + "picocolors": "^1.0.0", + "pretty-format": "^27.5.1" + } + }, + "abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "dev": true + }, + "acorn": { + "version": "8.8.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", + "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==", + "dev": true + }, + "acorn-walk": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", + "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", + "dev": true + }, + "agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dev": true, + "requires": { + "debug": "4" + } + }, + "agentkeepalive": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.2.1.tgz", + "integrity": "sha512-Zn4cw2NEqd+9fiSVWMscnjyQ1a8Yfoc5oBajLeo5w+YBHgDUcEBY2hS4YpTz6iN5f/2zQiktcuM6tS8x1p9dpA==", + "dev": true, + "requires": { + "debug": "^4.1.0", + "depd": "^1.1.2", + "humanize-ms": "^1.2.1" + } + }, + "aggregate-error": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", + "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "dev": true, + "requires": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + } + }, + "ansi-align": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz", + "integrity": "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==", + "dev": true, + "requires": { + "string-width": "^4.1.0" + } + }, + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true + }, + "ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true + }, + "aproba": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", + "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==", + "dev": true + }, + "are-we-there-yet": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-3.0.1.tgz", + "integrity": "sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg==", + "dev": true, + "requires": { + "delegates": "^1.0.0", + "readable-stream": "^3.6.0" + } + }, + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true + }, + "assertion-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "dev": true + }, + "balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "boxen": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/boxen/-/boxen-7.0.1.tgz", + "integrity": "sha512-8k2eH6SRAK00NDl1iX5q17RJ8rfl53TajdYxE3ssMLehbg487dEVgsad4pIsZb/QqBgYWIl6JOauMTLGX2Kpkw==", + "dev": true, + "requires": { + "ansi-align": "^3.0.1", + "camelcase": "^7.0.0", + "chalk": "^5.0.1", + "cli-boxes": "^3.0.0", + "string-width": "^5.1.2", + "type-fest": "^2.13.0", + "widest-line": "^4.0.1", + "wrap-ansi": "^8.0.1" + }, + "dependencies": { + "ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "dev": true + }, + "emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true + }, + "string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "requires": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + } + }, + "strip-ansi": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.0.1.tgz", + "integrity": "sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw==", + "dev": true, + "requires": { + "ansi-regex": "^6.0.1" + } + } + } + }, + "brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0" + } + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true + }, + "builtins": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/builtins/-/builtins-5.0.1.tgz", + "integrity": "sha512-qwVpFEHNfhYJIzNRBvd2C1kyo6jz3ZSMPyyuR47OPdiKWlbYnZNyDWuyR175qDnAJLiCo5fBBqPb3RiXgWlkOQ==", + "dev": true, + "requires": { + "semver": "^7.0.0" + } + }, + "cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "dev": true + }, + "cacache": { + "version": "17.0.4", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-17.0.4.tgz", + "integrity": "sha512-Z/nL3gU+zTUjz5pCA5vVjYM8pmaw2kxM7JEiE0fv3w77Wj+sFbi70CrBruUWH0uNcEdvLDixFpgA2JM4F4DBjA==", + "dev": true, + "requires": { + "@npmcli/fs": "^3.1.0", + "fs-minipass": "^3.0.0", + "glob": "^8.0.1", + "lru-cache": "^7.7.1", + "minipass": "^4.0.0", + "minipass-collect": "^1.0.2", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "p-map": "^4.0.0", + "promise-inflight": "^1.0.1", + "ssri": "^10.0.0", + "tar": "^6.1.11", + "unique-filename": "^3.0.0" + } + }, + "cacheable-lookup": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-7.0.0.tgz", + "integrity": "sha512-+qJyx4xiKra8mZrcwhjMRMUhD5NR1R8esPkzIYxX96JiecFoxAXFuz/GpR3+ev4PE1WamHip78wV0vcmPQtp8w==", + "dev": true + }, + "cacheable-request": { + "version": "10.2.7", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-10.2.7.tgz", + "integrity": "sha512-I4SA6mKgDxcxVbSt/UmIkb9Ny8qSkg6ReBHtAAXnZHk7KOSx5g3DTiAOaYzcHCs6oOdHn+bip9T48E6tMvK9hw==", + "dev": true, + "requires": { + "@types/http-cache-semantics": "^4.0.1", + "get-stream": "^6.0.1", + "http-cache-semantics": "^4.1.1", + "keyv": "^4.5.2", + "mimic-response": "^4.0.0", + "normalize-url": "^8.0.0", + "responselike": "^3.0.0" + } + }, + "camelcase": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-7.0.1.tgz", + "integrity": "sha512-xlx1yCK2Oc1APsPXDL2LdlNP6+uu8OCDdhOBSVT279M/S+y75O30C2VuD8T2ogdePBBl7PfPF4504tnLgX3zfw==", + "dev": true + }, + "chai": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.7.tgz", + "integrity": "sha512-HLnAzZ2iupm25PlN0xFreAlBA5zaBSv3og0DdeGA4Ar6h6rJ3A0rolRUKJhSF2V10GZKDgWF/VmAEsNWjCRB+A==", + "dev": true, + "requires": { + "assertion-error": "^1.1.0", + "check-error": "^1.0.2", + "deep-eql": "^4.1.2", + "get-func-name": "^2.0.0", + "loupe": "^2.3.1", + "pathval": "^1.1.1", + "type-detect": "^4.0.5" + } + }, + "chalk": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.2.0.tgz", + "integrity": "sha512-ree3Gqw/nazQAPuJJEy+avdl7QfZMcUvmHIKgEZkGL+xOBzRvup5Hxo6LHuMceSxOabuJLJm5Yp/92R9eMmMvA==", + "dev": true + }, + "check-error": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", + "integrity": "sha512-BrgHpW9NURQgzoNyjfq0Wu6VFO6D7IZEmJNdtgNqpzGG8RuNFHt2jQxWlAs4HMe119chBnv+34syEZtc6IhLtA==", + "dev": true + }, + "chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "dev": true + }, + "ci-info": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.7.1.tgz", + "integrity": "sha512-4jYS4MOAaCIStSRwiuxc4B8MYhIe676yO1sYGzARnjXkWpmzZMMYxY6zu8WYWDhSuth5zhrQ1rhNSibyyvv4/w==", + "dev": true + }, + "clean-stack": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "dev": true + }, + "cli-boxes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-3.0.0.tgz", + "integrity": "sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==", + "dev": true + }, + "cli-table": { + "version": "0.3.11", + "resolved": "https://registry.npmjs.org/cli-table/-/cli-table-0.3.11.tgz", + "integrity": "sha512-IqLQi4lO0nIB4tcdTpN4LCB9FI3uqrJZK7RC515EnhZ6qBaglkIgICb1wjeAqpdoOabm1+SuQtkXIPdYC93jhQ==", + "dev": true, + "requires": { + "colors": "1.0.3" + } + }, + "cli-truncate": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-3.1.0.tgz", + "integrity": "sha512-wfOBkjXteqSnI59oPcJkcPl/ZmwvMMOj340qUIY1SKZCv0B9Cf4D4fAucRkIKQmsIuYK3x1rrgU7MeGRruiuiA==", + "dev": true, + "requires": { + "slice-ansi": "^5.0.0", + "string-width": "^5.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "dev": true + }, + "emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true + }, + "string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "requires": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + } + }, + "strip-ansi": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.0.1.tgz", + "integrity": "sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw==", + "dev": true, + "requires": { + "ansi-regex": "^6.0.1" + } + } + } + }, + "color-support": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", + "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", + "dev": true + }, + "colors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.0.3.tgz", + "integrity": "sha512-pFGrxThWcWQ2MsAz6RtgeWe4NK2kUE1WfsrvvlctdII745EW9I0yflqhe7++M5LEc7bV2c/9/5zc8sFcpL0Drw==", + "dev": true + }, + "commander": { + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", + "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "config-chain": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz", + "integrity": "sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==", + "dev": true, + "requires": { + "ini": "^1.3.4", + "proto-list": "~1.2.1" + }, + "dependencies": { + "ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true + } + } + }, + "configstore": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/configstore/-/configstore-6.0.0.tgz", + "integrity": "sha512-cD31W1v3GqUlQvbBCGcXmd2Nj9SvLDOP1oQ0YFuLETufzSPaKp11rYBsSOm7rCsW3OnIRAFM3OxRhceaXNYHkA==", + "dev": true, + "requires": { + "dot-prop": "^6.0.1", + "graceful-fs": "^4.2.6", + "unique-string": "^3.0.0", + "write-file-atomic": "^3.0.3", + "xdg-basedir": "^5.0.1" + } + }, + "console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==", + "dev": true + }, + "cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "dependencies": { + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + } + } + }, + "crypto-random-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-4.0.0.tgz", + "integrity": "sha512-x8dy3RnvYdlUcPOjkEHqozhiwzKNSq7GcPuXFbnyMOCHxX8V3OgIg/pYuabl2sbUPfIJaeAQB7PMOK8DFIdoRA==", + "dev": true, + "requires": { + "type-fest": "^1.0.1" + }, + "dependencies": { + "type-fest": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-1.4.0.tgz", + "integrity": "sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==", + "dev": true + } + } + }, + "css.escape": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz", + "integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==", + "dev": true + }, + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "dev": true, + "requires": { + "mimic-response": "^3.1.0" + }, + "dependencies": { + "mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "dev": true + } + } + }, + "deep-eql": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.3.tgz", + "integrity": "sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==", + "dev": true, + "requires": { + "type-detect": "^4.0.0" + } + }, + "deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "dev": true + }, + "defer-to-connect": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", + "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==", + "dev": true + }, + "delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==", + "dev": true + }, + "depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", + "dev": true + }, + "diff": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.1.0.tgz", + "integrity": "sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw==", + "dev": true + }, + "dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "requires": { + "path-type": "^4.0.0" + } + }, + "dot-prop": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-6.0.1.tgz", + "integrity": "sha512-tE7ztYzXHIeyvc7N+hR3oi7FIbf/NIjVP9hmAt3yMXzrQ072/fpjGLx2GxNxGxUl5V73MEqYzioOMoVhGMJ5cA==", + "dev": true, + "requires": { + "is-obj": "^2.0.0" + } + }, + "eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "encoding": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", + "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", + "dev": true, + "optional": true, + "requires": { + "iconv-lite": "^0.6.2" + } + }, + "env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "dev": true + }, + "err-code": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", + "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==", + "dev": true + }, + "esbuild": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.17.19.tgz", + "integrity": "sha512-XQ0jAPFkK/u3LcVRcvVHQcTIqD6E2H1fvZMA5dQPSOWb3suUbWbfbRf94pjc0bNzRYLfIrDRQXr7X+LHIm5oHw==", + "dev": true, + "requires": { + "@esbuild/android-arm": "0.17.19", + "@esbuild/android-arm64": "0.17.19", + "@esbuild/android-x64": "0.17.19", + "@esbuild/darwin-arm64": "0.17.19", + "@esbuild/darwin-x64": "0.17.19", + "@esbuild/freebsd-arm64": "0.17.19", + "@esbuild/freebsd-x64": "0.17.19", + "@esbuild/linux-arm": "0.17.19", + "@esbuild/linux-arm64": "0.17.19", + "@esbuild/linux-ia32": "0.17.19", + "@esbuild/linux-loong64": "0.17.19", + "@esbuild/linux-mips64el": "0.17.19", + "@esbuild/linux-ppc64": "0.17.19", + "@esbuild/linux-riscv64": "0.17.19", + "@esbuild/linux-s390x": "0.17.19", + "@esbuild/linux-x64": "0.17.19", + "@esbuild/netbsd-x64": "0.17.19", + "@esbuild/openbsd-x64": "0.17.19", + "@esbuild/sunos-x64": "0.17.19", + "@esbuild/win32-arm64": "0.17.19", + "@esbuild/win32-ia32": "0.17.19", + "@esbuild/win32-x64": "0.17.19" + } + }, + "esbuild-android-64": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-android-64/-/esbuild-android-64-0.15.18.tgz", + "integrity": "sha512-wnpt3OXRhcjfIDSZu9bnzT4/TNTDsOUvip0foZOUBG7QbSt//w3QV4FInVJxNhKc/ErhUxc5z4QjHtMi7/TbgA==", + "dev": true, + "optional": true + }, + "esbuild-android-arm64": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.15.18.tgz", + "integrity": "sha512-G4xu89B8FCzav9XU8EjsXacCKSG2FT7wW9J6hOc18soEHJdtWu03L3TQDGf0geNxfLTtxENKBzMSq9LlbjS8OQ==", + "dev": true, + "optional": true + }, + "esbuild-darwin-64": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.15.18.tgz", + "integrity": "sha512-2WAvs95uPnVJPuYKP0Eqx+Dl/jaYseZEUUT1sjg97TJa4oBtbAKnPnl3b5M9l51/nbx7+QAEtuummJZW0sBEmg==", + "dev": true, + "optional": true + }, + "esbuild-darwin-arm64": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.15.18.tgz", + "integrity": "sha512-tKPSxcTJ5OmNb1btVikATJ8NftlyNlc8BVNtyT/UAr62JFOhwHlnoPrhYWz09akBLHI9nElFVfWSTSRsrZiDUA==", + "dev": true, + "optional": true + }, + "esbuild-freebsd-64": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.15.18.tgz", + "integrity": "sha512-TT3uBUxkteAjR1QbsmvSsjpKjOX6UkCstr8nMr+q7zi3NuZ1oIpa8U41Y8I8dJH2fJgdC3Dj3CXO5biLQpfdZA==", + "dev": true, + "optional": true + }, + "esbuild-freebsd-arm64": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.15.18.tgz", + "integrity": "sha512-R/oVr+X3Tkh+S0+tL41wRMbdWtpWB8hEAMsOXDumSSa6qJR89U0S/PpLXrGF7Wk/JykfpWNokERUpCeHDl47wA==", + "dev": true, + "optional": true + }, + "esbuild-linux-32": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.15.18.tgz", + "integrity": "sha512-lphF3HiCSYtaa9p1DtXndiQEeQDKPl9eN/XNoBf2amEghugNuqXNZA/ZovthNE2aa4EN43WroO0B85xVSjYkbg==", + "dev": true, + "optional": true + }, + "esbuild-linux-64": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.15.18.tgz", + "integrity": "sha512-hNSeP97IviD7oxLKFuii5sDPJ+QHeiFTFLoLm7NZQligur8poNOWGIgpQ7Qf8Balb69hptMZzyOBIPtY09GZYw==", + "dev": true, + "optional": true + }, + "esbuild-linux-arm": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.15.18.tgz", + "integrity": "sha512-UH779gstRblS4aoS2qpMl3wjg7U0j+ygu3GjIeTonCcN79ZvpPee12Qun3vcdxX+37O5LFxz39XeW2I9bybMVA==", + "dev": true, + "optional": true + }, + "esbuild-linux-arm64": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.15.18.tgz", + "integrity": "sha512-54qr8kg/6ilcxd+0V3h9rjT4qmjc0CccMVWrjOEM/pEcUzt8X62HfBSeZfT2ECpM7104mk4yfQXkosY8Quptug==", + "dev": true, + "optional": true + }, + "esbuild-linux-mips64le": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.15.18.tgz", + "integrity": "sha512-Mk6Ppwzzz3YbMl/ZZL2P0q1tnYqh/trYZ1VfNP47C31yT0K8t9s7Z077QrDA/guU60tGNp2GOwCQnp+DYv7bxQ==", + "dev": true, + "optional": true + }, + "esbuild-linux-ppc64le": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.15.18.tgz", + "integrity": "sha512-b0XkN4pL9WUulPTa/VKHx2wLCgvIAbgwABGnKMY19WhKZPT+8BxhZdqz6EgkqCLld7X5qiCY2F/bfpUUlnFZ9w==", + "dev": true, + "optional": true + }, + "esbuild-linux-riscv64": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.15.18.tgz", + "integrity": "sha512-ba2COaoF5wL6VLZWn04k+ACZjZ6NYniMSQStodFKH/Pu6RxzQqzsmjR1t9QC89VYJxBeyVPTaHuBMCejl3O/xg==", + "dev": true, + "optional": true + }, + "esbuild-linux-s390x": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.15.18.tgz", + "integrity": "sha512-VbpGuXEl5FCs1wDVp93O8UIzl3ZrglgnSQ+Hu79g7hZu6te6/YHgVJxCM2SqfIila0J3k0csfnf8VD2W7u2kzQ==", + "dev": true, + "optional": true + }, + "esbuild-netbsd-64": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.15.18.tgz", + "integrity": "sha512-98ukeCdvdX7wr1vUYQzKo4kQ0N2p27H7I11maINv73fVEXt2kyh4K4m9f35U1K43Xc2QGXlzAw0K9yoU7JUjOg==", + "dev": true, + "optional": true + }, + "esbuild-openbsd-64": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.15.18.tgz", + "integrity": "sha512-yK5NCcH31Uae076AyQAXeJzt/vxIo9+omZRKj1pauhk3ITuADzuOx5N2fdHrAKPxN+zH3w96uFKlY7yIn490xQ==", + "dev": true, + "optional": true + }, + "esbuild-sunos-64": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.15.18.tgz", + "integrity": "sha512-On22LLFlBeLNj/YF3FT+cXcyKPEI263nflYlAhz5crxtp3yRG1Ugfr7ITyxmCmjm4vbN/dGrb/B7w7U8yJR9yw==", + "dev": true, + "optional": true + }, + "esbuild-windows-32": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.15.18.tgz", + "integrity": "sha512-o+eyLu2MjVny/nt+E0uPnBxYuJHBvho8vWsC2lV61A7wwTWC3jkN2w36jtA+yv1UgYkHRihPuQsL23hsCYGcOQ==", + "dev": true, + "optional": true + }, + "esbuild-windows-64": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.15.18.tgz", + "integrity": "sha512-qinug1iTTaIIrCorAUjR0fcBk24fjzEedFYhhispP8Oc7SFvs+XeW3YpAKiKp8dRpizl4YYAhxMjlftAMJiaUw==", + "dev": true, + "optional": true + }, + "esbuild-windows-arm64": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.15.18.tgz", + "integrity": "sha512-q9bsYzegpZcLziq0zgUi5KqGVtfhjxGbnksaBFYmWLxeV/S1fK4OLdq2DFYnXcLMjlZw2L0jLsk1eGoB522WXQ==", + "dev": true, + "optional": true + }, + "escape-goat": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-goat/-/escape-goat-4.0.0.tgz", + "integrity": "sha512-2Sd4ShcWxbx6OY1IHyla/CVNwvg7XwZVoXZHcSu9w9SReNP1EzzD5T8NWKIR38fIqEns9kDWKUQTXXAmlDrdPg==", + "dev": true + }, + "fast-glob": { + "version": "3.2.12", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz", + "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==", + "dev": true, + "requires": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + } + }, + "fast-memoize": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/fast-memoize/-/fast-memoize-2.5.2.tgz", + "integrity": "sha512-Ue0LwpDYErFbmNnZSF0UH6eImUwDmogUO1jyE+JbN2gsQz/jICm1Ve7t9QT0rNSsfJt+Hs4/S3GnsDVjL4HVrw==", + "dev": true + }, + "fastq": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", + "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", + "dev": true, + "requires": { + "reusify": "^1.0.4" + } + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "requires": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + } + }, + "form-data-encoder": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-2.1.4.tgz", + "integrity": "sha512-yDYSgNMraqvnxiEXO4hi88+YZxaHC6QKzb5N84iRCTDeRO7ZALpir/lVmf/uXUhnwUr2O4HU8s/n6x+yNjQkHw==", + "dev": true + }, + "fp-and-or": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/fp-and-or/-/fp-and-or-0.1.3.tgz", + "integrity": "sha512-wJaE62fLaB3jCYvY2ZHjZvmKK2iiLiiehX38rz5QZxtdN8fVPJDeZUiVvJrHStdTc+23LHlyZuSEKgFc0pxi2g==", + "dev": true + }, + "fs-minipass": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-3.0.0.tgz", + "integrity": "sha512-EUojgQaSPy6sxcqcZgQv6TVF6jiKvurji3AxhAivs/Ep4O1UpS8TusaxpybfFHZ2skRhLqzk6WR8nqNYIMMDeA==", + "dev": true, + "requires": { + "minipass": "^4.0.0" + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true + }, + "fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "optional": true + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "gauge": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-4.0.4.tgz", + "integrity": "sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg==", + "dev": true, + "requires": { + "aproba": "^1.0.3 || ^2.0.0", + "color-support": "^1.1.3", + "console-control-strings": "^1.1.0", + "has-unicode": "^2.0.1", + "signal-exit": "^3.0.7", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wide-align": "^1.1.5" + } + }, + "get-func-name": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", + "integrity": "sha512-Hm0ixYtaSZ/V7C8FJrtZIuBBI+iSgL+1Aq82zSu8VQNB4S3Gk8e7Qs3VwBDJAhmRZcFqkl3tQu36g/Foh5I5ig==", + "dev": true + }, + "get-stdin": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-8.0.0.tgz", + "integrity": "sha512-sY22aA6xchAzprjyqmSEQv4UbAAzRN0L2dQB0NlN5acTTK9Don6nhoc3eAbUnpZiCANAMfd/+40kVdKfFygohg==", + "dev": true + }, + "get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true + }, + "glob": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + } + }, + "glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + }, + "global-dirs": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-3.0.1.tgz", + "integrity": "sha512-NBcGGFbBA9s1VzD41QXDG+3++t9Mn5t1FpLdhESY6oKY4gYTFpX4wO3sqGUa0Srjtbfj3szX0RnemmrVRUdULA==", + "dev": true, + "requires": { + "ini": "2.0.0" + }, + "dependencies": { + "ini": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ini/-/ini-2.0.0.tgz", + "integrity": "sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==", + "dev": true + } + } + }, + "globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "requires": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + } + }, + "got": { + "version": "12.5.3", + "resolved": "https://registry.npmjs.org/got/-/got-12.5.3.tgz", + "integrity": "sha512-8wKnb9MGU8IPGRIo+/ukTy9XLJBwDiCpIf5TVzQ9Cpol50eMTpBq2GAuDsuDIz7hTYmZgMgC1e9ydr6kSDWs3w==", + "dev": true, + "requires": { + "@sindresorhus/is": "^5.2.0", + "@szmarczak/http-timer": "^5.0.1", + "cacheable-lookup": "^7.0.0", + "cacheable-request": "^10.2.1", + "decompress-response": "^6.0.0", + "form-data-encoder": "^2.1.2", + "get-stream": "^6.0.1", + "http2-wrapper": "^2.1.10", + "lowercase-keys": "^3.0.0", + "p-cancelable": "^3.0.0", + "responselike": "^3.0.0" + } + }, + "graceful-fs": { + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", + "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", + "dev": true + }, + "happy-dom": { + "version": "8.1.5", + "resolved": "https://registry.npmjs.org/happy-dom/-/happy-dom-8.1.5.tgz", + "integrity": "sha512-/UXAJ2fHTs4H3vy7TS7c9PKFvPyaNialk2Er9NdXfpBKNaCITMOH03rkjHXp5jnJnSmRBa+av8E08PUAaIB1jQ==", + "dev": true, + "requires": { + "css.escape": "^1.5.1", + "he": "^1.2.0", + "node-fetch": "^2.x.x", + "webidl-conversions": "^7.0.0", + "whatwg-encoding": "^2.0.0", + "whatwg-mimetype": "^3.0.0" + } + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==", + "dev": true + }, + "has-yarn": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-yarn/-/has-yarn-3.0.0.tgz", + "integrity": "sha512-IrsVwUHhEULx3R8f/aA8AHuEzAorplsab/v8HBzEiIukwq5i/EC+xmOW+HfP1OaDP+2JkgT1yILHN2O3UFIbcA==", + "dev": true + }, + "he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true + }, + "hosted-git-info": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-5.2.1.tgz", + "integrity": "sha512-xIcQYMnhcx2Nr4JTjsFmwwnr9vldugPy9uVm0o87bjqqWMv9GaqsTeT+i99wTl0mk1uLxJtHxLb8kymqTENQsw==", + "dev": true, + "requires": { + "lru-cache": "^7.5.1" + } + }, + "http-cache-semantics": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz", + "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==", + "dev": true + }, + "http-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", + "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "dev": true, + "requires": { + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" + } + }, + "http2-wrapper": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-2.2.0.tgz", + "integrity": "sha512-kZB0wxMo0sh1PehyjJUWRFEd99KC5TLjZ2cULC4f9iqJBAmKQQXEICjxl5iPJRwP40dpeHFqqhm7tYCvODpqpQ==", + "dev": true, + "requires": { + "quick-lru": "^5.1.1", + "resolve-alpn": "^1.2.0" + } + }, + "https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "dev": true, + "requires": { + "agent-base": "6", + "debug": "4" + } + }, + "humanize-ms": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", + "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", + "dev": true, + "requires": { + "ms": "^2.0.0" + } + }, + "iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + } + }, + "ignore": { + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", + "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", + "dev": true + }, + "ignore-walk": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-6.0.0.tgz", + "integrity": "sha512-bTf9UWe/UP1yxG3QUrj/KOvEhTAUWPcv+WvbFZ28LcqznXabp7Xu6o9y1JEC18+oqODuS7VhTpekV5XvFwsxJg==", + "dev": true, + "requires": { + "minimatch": "^5.0.1" + } + }, + "import-lazy": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-4.0.0.tgz", + "integrity": "sha512-rKtvo6a868b5Hu3heneU+L4yEQ4jYKLtjpnPeUdK7h0yzXGmyBTypknlkCvHFBqfX9YlorEiMM6Dnq/5atfHkw==", + "dev": true + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true + }, + "indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true + }, + "infer-owner": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz", + "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "ini": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ini/-/ini-3.0.1.tgz", + "integrity": "sha512-it4HyVAUTKBc6m8e1iXWvXSTdndF7HbdN713+kvLrymxTaU4AUBWrJ4vEooP+V7fexnVD3LKcBshjGGPefSMUQ==", + "dev": true + }, + "ip": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ip/-/ip-2.0.0.tgz", + "integrity": "sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ==", + "dev": true + }, + "is-ci": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-3.0.1.tgz", + "integrity": "sha512-ZYvCgrefwqoQ6yTyYUbQu64HsITZ3NfKX1lzaEYdkTDcfKzzCI/wthRRYKkdjHKFVgNiXKAKm65Zo1pk2as/QQ==", + "dev": true, + "requires": { + "ci-info": "^3.2.0" + } + }, + "is-core-module": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz", + "integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==", + "dev": true, + "requires": { + "has": "^1.0.3" + } + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, + "is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-installed-globally": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.4.0.tgz", + "integrity": "sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ==", + "dev": true, + "requires": { + "global-dirs": "^3.0.0", + "is-path-inside": "^3.0.2" + } + }, + "is-lambda": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-lambda/-/is-lambda-1.0.1.tgz", + "integrity": "sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==", + "dev": true + }, + "is-npm": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-6.0.0.tgz", + "integrity": "sha512-JEjxbSmtPSt1c8XTkVrlujcXdKV1/tvuQ7GwKcAlyiVLeYFQ2VHat8xfrDJsIkhCdF/tZ7CiIR3sy141c6+gPQ==", + "dev": true + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "is-obj": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", + "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", + "dev": true + }, + "is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true + }, + "is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", + "dev": true + }, + "is-yarn-global": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/is-yarn-global/-/is-yarn-global-0.4.1.tgz", + "integrity": "sha512-/kppl+R+LO5VmhYSEWARUFjodS25D68gvj8W7z0I7OWhUla5xWu8KL6CtB2V0R6yqhnRgbcaREMr4EEM6htLPQ==", + "dev": true + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "jju": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/jju/-/jju-1.4.0.tgz", + "integrity": "sha512-8wb9Yw966OSxApiCt0K3yNJL8pnNeIv+OEq2YMidz4FKP6nonSRoOXc80iXY4JaN2FC11B9qsNmDsm+ZOfMROA==", + "dev": true + }, + "js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "requires": { + "argparse": "^2.0.1" + } + }, + "json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true + }, + "json-parse-even-better-errors": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-3.0.0.tgz", + "integrity": "sha512-iZbGHafX/59r39gPwVPRBGw0QQKnA7tte5pSMrhWOW7swGsVvVTjmfyAV9pNqk8YGT7tRCdxRu8uzcgZwoDooA==", + "dev": true + }, + "json-parse-helpfulerror": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/json-parse-helpfulerror/-/json-parse-helpfulerror-1.0.3.tgz", + "integrity": "sha512-XgP0FGR77+QhUxjXkwOMkC94k3WtqEBfcnjWqhRd82qTat4SWKRE+9kUnynz/shm3I4ea2+qISvTIeGTNU7kJg==", + "dev": true, + "requires": { + "jju": "^1.1.0" + } + }, + "json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true + }, + "jsonc-parser": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz", + "integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==", + "dev": true + }, + "jsonlines": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsonlines/-/jsonlines-0.1.1.tgz", + "integrity": "sha512-ekDrAGso79Cvf+dtm+mL8OBI2bmAOt3gssYs833De/C9NmIpWDWyUO4zPgB5x2/OhY366dkhgfPMYfwZF7yOZA==", + "dev": true + }, + "jsonparse": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", + "integrity": "sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==", + "dev": true + }, + "keyv": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.2.tgz", + "integrity": "sha512-5MHbFaKn8cNSmVW7BYnijeAVlE4cYA/SVkifVgrh7yotnfhKmjuXpDKjrABLnT0SfHWV21P8ow07OGfRrNDg8g==", + "dev": true, + "requires": { + "json-buffer": "3.0.1" + } + }, + "kleur": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", + "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", + "dev": true + }, + "latest-version": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-7.0.0.tgz", + "integrity": "sha512-KvNT4XqAMzdcL6ka6Tl3i2lYeFDgXNCuIX+xNx6ZMVR1dFq+idXd9FLKNMOIx0t9mJ9/HudyX4oZWXZQ0UJHeg==", + "dev": true, + "requires": { + "package-json": "^8.1.0" + } + }, + "local-pkg": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-0.4.2.tgz", + "integrity": "sha512-mlERgSPrbxU3BP4qBqAvvwlgW4MTg78iwJdGGnv7kibKjWcJksrG3t6LB5lXI93wXRDvG4NpUgJFmTG4T6rdrg==", + "dev": true + }, + "locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "requires": { + "p-locate": "^5.0.0" + } + }, + "lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true + }, + "loupe": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.6.tgz", + "integrity": "sha512-RaPMZKiMy8/JruncMU5Bt6na1eftNoo++R4Y+N2FrxkDVTrGvcyzFTsaGif4QTeKESheMGegbhw6iUAq+5A8zA==", + "dev": true, + "requires": { + "get-func-name": "^2.0.0" + } + }, + "lowercase-keys": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-3.0.0.tgz", + "integrity": "sha512-ozCC6gdQ+glXOQsveKD0YsDy8DSQFjDTz4zyzEHNV5+JP5D62LmfDZ6o1cycFx9ouG940M5dE8C8CTewdj2YWQ==", + "dev": true + }, + "lru-cache": { + "version": "7.14.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.14.1.tgz", + "integrity": "sha512-ysxwsnTKdAx96aTRdhDOCQfDgbHnt8SK0KY8SEjO0wHinhWOFTESbjVCMPbU1uGXg/ch4lifqx0wfjOawU2+WA==", + "dev": true + }, + "make-fetch-happen": { + "version": "10.2.1", + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-10.2.1.tgz", + "integrity": "sha512-NgOPbRiaQM10DYXvN3/hhGVI2M5MtITFryzBGxHM5p4wnFxsVCbxkrBrDsk+EZ5OB4jEOT7AjDxtdF+KVEFT7w==", + "dev": true, + "requires": { + "agentkeepalive": "^4.2.1", + "cacache": "^16.1.0", + "http-cache-semantics": "^4.1.0", + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.0", + "is-lambda": "^1.0.1", + "lru-cache": "^7.7.1", + "minipass": "^3.1.6", + "minipass-collect": "^1.0.2", + "minipass-fetch": "^2.0.3", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "negotiator": "^0.6.3", + "promise-retry": "^2.0.1", + "socks-proxy-agent": "^7.0.0", + "ssri": "^9.0.0" + }, + "dependencies": { + "@npmcli/fs": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-2.1.2.tgz", + "integrity": "sha512-yOJKRvohFOaLqipNtwYB9WugyZKhC/DZC4VYPmpaCzDBrA8YpK3qHZ8/HGscMnE4GqbkLNuVcCnxkeQEdGt6LQ==", + "dev": true, + "requires": { + "@gar/promisify": "^1.1.3", + "semver": "^7.3.5" + } + }, + "cacache": { + "version": "16.1.3", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-16.1.3.tgz", + "integrity": "sha512-/+Emcj9DAXxX4cwlLmRI9c166RuL3w30zp4R7Joiv2cQTtTtA+jeuCAjH3ZlGnYS3tKENSrKhAzVVP9GVyzeYQ==", + "dev": true, + "requires": { + "@npmcli/fs": "^2.1.0", + "@npmcli/move-file": "^2.0.0", + "chownr": "^2.0.0", + "fs-minipass": "^2.1.0", + "glob": "^8.0.1", + "infer-owner": "^1.0.4", + "lru-cache": "^7.7.1", + "minipass": "^3.1.6", + "minipass-collect": "^1.0.2", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "mkdirp": "^1.0.4", + "p-map": "^4.0.0", + "promise-inflight": "^1.0.1", + "rimraf": "^3.0.2", + "ssri": "^9.0.0", + "tar": "^6.1.11", + "unique-filename": "^2.0.0" + } + }, + "fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "dev": true, + "requires": { + "minipass": "^3.0.0" + } + }, + "minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "ssri": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-9.0.1.tgz", + "integrity": "sha512-o57Wcn66jMQvfHG1FlYbWeZWW/dHZhJXjpIcTfXldXEk5nz5lStPo3mK0OJQfGR3RbZUlbISexbljkJzuEj/8Q==", + "dev": true, + "requires": { + "minipass": "^3.1.1" + } + }, + "unique-filename": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-2.0.1.tgz", + "integrity": "sha512-ODWHtkkdx3IAR+veKxFV+VBkUMcN+FaqzUUd7IZzt+0zhDZFPFxhlqwPF3YQvMHx1TD0tdgYl+kuPnJ8E6ql7A==", + "dev": true, + "requires": { + "unique-slug": "^3.0.0" + } + }, + "unique-slug": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-3.0.0.tgz", + "integrity": "sha512-8EyMynh679x/0gqE9fT9oilG+qEt+ibFyqjuVTsZn1+CMxH+XLlpvr2UZx4nVcCwTpx81nICr2JQFkM+HPLq4w==", + "dev": true, + "requires": { + "imurmurhash": "^0.1.4" + } + } + } + }, + "merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true + }, + "micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "requires": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + } + }, + "mimic-response": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-4.0.0.tgz", + "integrity": "sha512-e5ISH9xMYU0DzrT+jl8q2ze9D6eWBto+I8CNpe+VI+K2J/F/k3PdkdTdz4wvGVH4NTpo+NRYTVIuMQEMMcsLqg==", + "dev": true + }, + "minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, + "requires": { + "brace-expansion": "^2.0.1" + } + }, + "minimist": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.7.tgz", + "integrity": "sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==", + "dev": true + }, + "minipass": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-4.0.0.tgz", + "integrity": "sha512-g2Uuh2jEKoht+zvO6vJqXmYpflPqzRBT+Th2h01DKh5z7wbY/AZ2gCQ78cP70YoHPyFdY30YBV5WxgLOEwOykw==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "minipass-collect": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-1.0.2.tgz", + "integrity": "sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==", + "dev": true, + "requires": { + "minipass": "^3.0.0" + }, + "dependencies": { + "minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + } + } + }, + "minipass-fetch": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-2.1.2.tgz", + "integrity": "sha512-LT49Zi2/WMROHYoqGgdlQIZh8mLPZmOrN2NdJjMXxYe4nkN6FUyuPuOAOedNJDrx0IRGg9+4guZewtp8hE6TxA==", + "dev": true, + "requires": { + "encoding": "^0.1.13", + "minipass": "^3.1.6", + "minipass-sized": "^1.0.3", + "minizlib": "^2.1.2" + }, + "dependencies": { + "minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + } + } + }, + "minipass-flush": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz", + "integrity": "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==", + "dev": true, + "requires": { + "minipass": "^3.0.0" + }, + "dependencies": { + "minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + } + } + }, + "minipass-json-stream": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minipass-json-stream/-/minipass-json-stream-1.0.1.tgz", + "integrity": "sha512-ODqY18UZt/I8k+b7rl2AENgbWE8IDYam+undIJONvigAz8KR5GWblsFTEfQs0WODsjbSXWlm+JHEv8Gr6Tfdbg==", + "dev": true, + "requires": { + "jsonparse": "^1.3.1", + "minipass": "^3.0.0" + }, + "dependencies": { + "minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + } + } + }, + "minipass-pipeline": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz", + "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==", + "dev": true, + "requires": { + "minipass": "^3.0.0" + }, + "dependencies": { + "minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + } + } + }, + "minipass-sized": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/minipass-sized/-/minipass-sized-1.0.3.tgz", + "integrity": "sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==", + "dev": true, + "requires": { + "minipass": "^3.0.0" + }, + "dependencies": { + "minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + } + } + }, + "minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "dev": true, + "requires": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "dependencies": { + "minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + } + } + }, + "mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true + }, + "mlly": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.1.0.tgz", + "integrity": "sha512-cwzBrBfwGC1gYJyfcy8TcZU1f+dbH/T+TuOhtYP2wLv/Fb51/uV7HJQfBPtEupZ2ORLRU1EKFS/QfS3eo9+kBQ==", + "dev": true, + "requires": { + "acorn": "^8.8.1", + "pathe": "^1.0.0", + "pkg-types": "^1.0.1", + "ufo": "^1.0.1" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "nanoid": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-4.0.0.tgz", + "integrity": "sha512-IgBP8piMxe/gf73RTQx7hmnhwz0aaEXYakvqZyE302IXW3HyVNhdNGC+O2MwMAVhLEnvXlvKtGbtJf6wvHihCg==", + "dev": true + }, + "negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "dev": true + }, + "node-fetch": { + "version": "2.6.7", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", + "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", + "dev": true, + "requires": { + "whatwg-url": "^5.0.0" + } + }, + "node-gyp": { + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-9.3.1.tgz", + "integrity": "sha512-4Q16ZCqq3g8awk6UplT7AuxQ35XN4R/yf/+wSAwcBUAjg7l58RTactWaP8fIDTi0FzI7YcVLujwExakZlfWkXg==", + "dev": true, + "requires": { + "env-paths": "^2.2.0", + "glob": "^7.1.4", + "graceful-fs": "^4.2.6", + "make-fetch-happen": "^10.0.3", + "nopt": "^6.0.0", + "npmlog": "^6.0.0", + "rimraf": "^3.0.2", + "semver": "^7.3.5", + "tar": "^6.1.2", + "which": "^2.0.2" + }, + "dependencies": { + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + } + } + }, + "nopt": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-6.0.0.tgz", + "integrity": "sha512-ZwLpbTgdhuZUnZzjd7nb1ZV+4DoiC6/sfiVKok72ym/4Tlf+DFdlHYmT2JPmcNNWV6Pi3SDf1kT+A4r9RTuT9g==", + "dev": true, + "requires": { + "abbrev": "^1.0.0" + } + }, + "normalize-package-data": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-5.0.0.tgz", + "integrity": "sha512-h9iPVIfrVZ9wVYQnxFgtw1ugSvGEMOlyPWWtm8BMJhnwyEL/FLbYbTY3V3PpjI/BUK67n9PEWDu6eHzu1fB15Q==", + "dev": true, + "requires": { + "hosted-git-info": "^6.0.0", + "is-core-module": "^2.8.1", + "semver": "^7.3.5", + "validate-npm-package-license": "^3.0.4" + }, + "dependencies": { + "hosted-git-info": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-6.1.1.tgz", + "integrity": "sha512-r0EI+HBMcXadMrugk0GCQ+6BQV39PiWAZVfq7oIckeGiN7sjRGyQxPdft3nQekFTCQbYxLBH+/axZMeH8UX6+w==", + "dev": true, + "requires": { + "lru-cache": "^7.5.1" + } + } + } + }, + "normalize-url": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-8.0.0.tgz", + "integrity": "sha512-uVFpKhj5MheNBJRTiMZ9pE/7hD1QTeEvugSJW/OmLzAp78PB5O6adfMNTvmfKhXBkvCzC+rqifWcVYpGFwTjnw==", + "dev": true + }, + "npm-bundled": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-3.0.0.tgz", + "integrity": "sha512-Vq0eyEQy+elFpzsKjMss9kxqb9tG3YHg4dsyWuUENuzvSUWe1TCnW/vV9FkhvBk/brEDoDiVd+M1Btosa6ImdQ==", + "dev": true, + "requires": { + "npm-normalize-package-bin": "^3.0.0" + } + }, + "npm-check-updates": { + "version": "16.6.3", + "resolved": "https://registry.npmjs.org/npm-check-updates/-/npm-check-updates-16.6.3.tgz", + "integrity": "sha512-EKhsCbBcVrPlYKzaYQtRhGv9fxpexwROcvl5HebCUNpiCSlOWrzaJvrMlwi9i9GCyJCnH+YAeBPYdqnArA390A==", + "dev": true, + "requires": { + "chalk": "^5.2.0", + "cli-table": "^0.3.11", + "commander": "^9.4.1", + "fast-memoize": "^2.5.2", + "find-up": "5.0.0", + "fp-and-or": "^0.1.3", + "get-stdin": "^8.0.0", + "globby": "^11.0.4", + "hosted-git-info": "^5.1.0", + "ini": "^3.0.1", + "json-parse-helpfulerror": "^1.0.3", + "jsonlines": "^0.1.1", + "lodash": "^4.17.21", + "minimatch": "^5.1.2", + "p-map": "^4.0.0", + "pacote": "15.0.8", + "parse-github-url": "^1.0.2", + "progress": "^2.0.3", + "prompts-ncu": "^2.5.1", + "rc-config-loader": "^4.1.1", + "remote-git-tags": "^3.0.0", + "rimraf": "^3.0.2", + "semver": "^7.3.8", + "semver-utils": "^1.1.4", + "source-map-support": "^0.5.21", + "spawn-please": "^2.0.1", + "untildify": "^4.0.0", + "update-notifier": "^6.0.2", + "yaml": "^2.2.0" + } + }, + "npm-install-checks": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/npm-install-checks/-/npm-install-checks-6.0.0.tgz", + "integrity": "sha512-SBU9oFglRVZnfElwAtF14NivyulDqF1VKqqwNsFW9HDcbHMAPHpRSsVFgKuwFGq/hVvWZExz62Th0kvxn/XE7Q==", + "dev": true, + "requires": { + "semver": "^7.1.1" + } + }, + "npm-normalize-package-bin": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-3.0.0.tgz", + "integrity": "sha512-g+DPQSkusnk7HYXr75NtzkIP4+N81i3RPsGFidF3DzHd9MT9wWngmqoeg/fnHFz5MNdtG4w03s+QnhewSLTT2Q==", + "dev": true + }, + "npm-package-arg": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-10.1.0.tgz", + "integrity": "sha512-uFyyCEmgBfZTtrKk/5xDfHp6+MdrqGotX/VoOyEEl3mBwiEE5FlBaePanazJSVMPT7vKepcjYBY2ztg9A3yPIA==", + "dev": true, + "requires": { + "hosted-git-info": "^6.0.0", + "proc-log": "^3.0.0", + "semver": "^7.3.5", + "validate-npm-package-name": "^5.0.0" + }, + "dependencies": { + "hosted-git-info": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-6.1.1.tgz", + "integrity": "sha512-r0EI+HBMcXadMrugk0GCQ+6BQV39PiWAZVfq7oIckeGiN7sjRGyQxPdft3nQekFTCQbYxLBH+/axZMeH8UX6+w==", + "dev": true, + "requires": { + "lru-cache": "^7.5.1" + } + } + } + }, + "npm-packlist": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-7.0.4.tgz", + "integrity": "sha512-d6RGEuRrNS5/N84iglPivjaJPxhDbZmlbTwTDX2IbcRHG5bZCdtysYMhwiPvcF4GisXHGn7xsxv+GQ7T/02M5Q==", + "dev": true, + "requires": { + "ignore-walk": "^6.0.0" + } + }, + "npm-pick-manifest": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/npm-pick-manifest/-/npm-pick-manifest-8.0.1.tgz", + "integrity": "sha512-mRtvlBjTsJvfCCdmPtiu2bdlx8d/KXtF7yNXNWe7G0Z36qWA9Ny5zXsI2PfBZEv7SXgoxTmNaTzGSbbzDZChoA==", + "dev": true, + "requires": { + "npm-install-checks": "^6.0.0", + "npm-normalize-package-bin": "^3.0.0", + "npm-package-arg": "^10.0.0", + "semver": "^7.3.5" + } + }, + "npm-registry-fetch": { + "version": "14.0.3", + "resolved": "https://registry.npmjs.org/npm-registry-fetch/-/npm-registry-fetch-14.0.3.tgz", + "integrity": "sha512-YaeRbVNpnWvsGOjX2wk5s85XJ7l1qQBGAp724h8e2CZFFhMSuw9enom7K1mWVUtvXO1uUSFIAPofQK0pPN0ZcA==", + "dev": true, + "requires": { + "make-fetch-happen": "^11.0.0", + "minipass": "^4.0.0", + "minipass-fetch": "^3.0.0", + "minipass-json-stream": "^1.0.1", + "minizlib": "^2.1.2", + "npm-package-arg": "^10.0.0", + "proc-log": "^3.0.0" + }, + "dependencies": { + "make-fetch-happen": { + "version": "11.0.2", + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-11.0.2.tgz", + "integrity": "sha512-5n/Pq41w/uZghpdlXAY5kIM85RgJThtTH/NYBRAZ9VUOBWV90USaQjwGrw76fZP3Lj5hl/VZjpVvOaRBMoL/2w==", + "dev": true, + "requires": { + "agentkeepalive": "^4.2.1", + "cacache": "^17.0.0", + "http-cache-semantics": "^4.1.0", + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.0", + "is-lambda": "^1.0.1", + "lru-cache": "^7.7.1", + "minipass": "^4.0.0", + "minipass-collect": "^1.0.2", + "minipass-fetch": "^3.0.0", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "negotiator": "^0.6.3", + "promise-retry": "^2.0.1", + "socks-proxy-agent": "^7.0.0", + "ssri": "^10.0.0" + } + }, + "minipass-fetch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-3.0.1.tgz", + "integrity": "sha512-t9/wowtf7DYkwz8cfMSt0rMwiyNIBXf5CKZ3S5ZMqRqMYT0oLTp0x1WorMI9WTwvaPg21r1JbFxJMum8JrLGfw==", + "dev": true, + "requires": { + "encoding": "^0.1.13", + "minipass": "^4.0.0", + "minipass-sized": "^1.0.3", + "minizlib": "^2.1.2" + } + } + } + }, + "npmlog": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-6.0.2.tgz", + "integrity": "sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg==", + "dev": true, + "requires": { + "are-we-there-yet": "^3.0.0", + "console-control-strings": "^1.1.0", + "gauge": "^4.0.3", + "set-blocking": "^2.0.0" + } + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "p-cancelable": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-3.0.0.tgz", + "integrity": "sha512-mlVgR3PGuzlo0MmTdk4cXqXWlwQDLnONTAg6sm62XkMJEiRxN3GL3SffkYvqwonbkJBcrI7Uvv5Zh9yjvn2iUw==", + "dev": true + }, + "p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "requires": { + "yocto-queue": "^0.1.0" + } + }, + "p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "requires": { + "p-limit": "^3.0.2" + } + }, + "p-map": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", + "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", + "dev": true, + "requires": { + "aggregate-error": "^3.0.0" + } + }, + "package-json": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/package-json/-/package-json-8.1.0.tgz", + "integrity": "sha512-hySwcV8RAWeAfPsXb9/HGSPn8lwDnv6fabH+obUZKX169QknRkRhPxd1yMubpKDskLFATkl3jHpNtVtDPFA0Wg==", + "dev": true, + "requires": { + "got": "^12.1.0", + "registry-auth-token": "^5.0.1", + "registry-url": "^6.0.0", + "semver": "^7.3.7" + } + }, + "pacote": { + "version": "15.0.8", + "resolved": "https://registry.npmjs.org/pacote/-/pacote-15.0.8.tgz", + "integrity": "sha512-UlcumB/XS6xyyIMwg/WwMAyUmga+RivB5KgkRwA1hZNtrx+0Bt41KxHCvg1kr0pZ/ZeD8qjhW4fph6VaYRCbLw==", + "dev": true, + "requires": { + "@npmcli/git": "^4.0.0", + "@npmcli/installed-package-contents": "^2.0.1", + "@npmcli/promise-spawn": "^6.0.1", + "@npmcli/run-script": "^6.0.0", + "cacache": "^17.0.0", + "fs-minipass": "^3.0.0", + "minipass": "^4.0.0", + "npm-package-arg": "^10.0.0", + "npm-packlist": "^7.0.0", + "npm-pick-manifest": "^8.0.0", + "npm-registry-fetch": "^14.0.0", + "proc-log": "^3.0.0", + "promise-retry": "^2.0.1", + "read-package-json": "^6.0.0", + "read-package-json-fast": "^3.0.0", + "ssri": "^10.0.0", + "tar": "^6.1.11" + } + }, + "parse-github-url": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/parse-github-url/-/parse-github-url-1.0.2.tgz", + "integrity": "sha512-kgBf6avCbO3Cn6+RnzRGLkUsv4ZVqv/VfAYkRsyBcgkshNvVBkRn1FEZcW0Jb+npXQWm2vHPnnOqFteZxRRGNw==", + "dev": true + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true + }, + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true + }, + "path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true + }, + "pathe": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.0.tgz", + "integrity": "sha512-ODbEPR0KKHqECXW1GoxdDb+AZvULmXjVPy4rt+pGo2+TnjJTIPJQSVS6N63n8T2Ip+syHhbn52OewKicV0373w==", + "dev": true + }, + "pathval": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", + "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", + "dev": true + }, + "picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true + }, + "picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true + }, + "pkg-types": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.0.1.tgz", + "integrity": "sha512-jHv9HB+Ho7dj6ItwppRDDl0iZRYBD0jsakHXtFgoLr+cHSF6xC+QL54sJmWxyGxOLYSHm0afhXhXcQDQqH9z8g==", + "dev": true, + "requires": { + "jsonc-parser": "^3.2.0", + "mlly": "^1.0.0", + "pathe": "^1.0.0" + } + }, + "postcss": { + "version": "8.4.21", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.21.tgz", + "integrity": "sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg==", + "dev": true, + "requires": { + "nanoid": "^3.3.4", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + }, + "dependencies": { + "nanoid": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz", + "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==", + "dev": true + } + } + }, + "pretty-format": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", + "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^17.0.1" + }, + "dependencies": { + "ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true + } + } + }, + "proc-log": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-3.0.0.tgz", + "integrity": "sha512-++Vn7NS4Xf9NacaU9Xq3URUuqZETPsf8L4j5/ckhaRYsfPeRyzGw+iDjFhV/Jr3uNmTvvddEJFWh5R1gRgUH8A==", + "dev": true + }, + "progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "dev": true + }, + "promise-inflight": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", + "integrity": "sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==", + "dev": true + }, + "promise-retry": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz", + "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==", + "dev": true, + "requires": { + "err-code": "^2.0.2", + "retry": "^0.12.0" + } + }, + "prompts-ncu": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/prompts-ncu/-/prompts-ncu-2.5.1.tgz", + "integrity": "sha512-Hdd7GgV7b76Yh9FP9HL1D9xqtJCJdVPpiM2vDtuoc8W1KfweJe15gutFYmxkq83ViFaagFM8K0UcPCQ/tZq8bA==", + "dev": true, + "requires": { + "kleur": "^4.0.1", + "sisteransi": "^1.0.5" + } + }, + "proto-list": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", + "integrity": "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==", + "dev": true + }, + "pupa": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/pupa/-/pupa-3.1.0.tgz", + "integrity": "sha512-FLpr4flz5xZTSJxSeaheeMKN/EDzMdK7b8PTOC6a5PYFKTucWbdqjgqaEyH0shFiSJrVB1+Qqi4Tk19ccU6Aug==", + "dev": true, + "requires": { + "escape-goat": "^4.0.0" + } + }, + "queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true + }, + "quick-lru": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", + "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", + "dev": true + }, + "rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "dev": true, + "requires": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "dependencies": { + "ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true + } + } + }, + "rc-config-loader": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/rc-config-loader/-/rc-config-loader-4.1.2.tgz", + "integrity": "sha512-qKTnVWFl9OQYKATPzdfaZIbTxcHziQl92zYSxYC6umhOqyAsoj8H8Gq/+aFjAso68sBdjTz3A7omqeAkkF1MWg==", + "dev": true, + "requires": { + "debug": "^4.3.4", + "js-yaml": "^4.1.0", + "json5": "^2.2.2", + "require-from-string": "^2.0.2" + } + }, + "react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", + "dev": true + }, + "read-package-json": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/read-package-json/-/read-package-json-6.0.0.tgz", + "integrity": "sha512-b/9jxWJ8EwogJPpv99ma+QwtqB7FSl3+V6UXS7Aaay8/5VwMY50oIFooY1UKXMWpfNCM6T/PoGqa5GD1g9xf9w==", + "dev": true, + "requires": { + "glob": "^8.0.1", + "json-parse-even-better-errors": "^3.0.0", + "normalize-package-data": "^5.0.0", + "npm-normalize-package-bin": "^3.0.0" + } + }, + "read-package-json-fast": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/read-package-json-fast/-/read-package-json-fast-3.0.2.tgz", + "integrity": "sha512-0J+Msgym3vrLOUB3hzQCuZHII0xkNGCtz/HJH9xZshwv9DbDwkw1KaE3gx/e2J5rpEY5rtOy6cyhKOPrkP7FZw==", + "dev": true, + "requires": { + "json-parse-even-better-errors": "^3.0.0", + "npm-normalize-package-bin": "^3.0.0" + } + }, + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "registry-auth-token": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-5.0.1.tgz", + "integrity": "sha512-UfxVOj8seK1yaIOiieV4FIP01vfBDLsY0H9sQzi9EbbUdJiuuBjJgLa1DpImXMNPnVkBD4eVxTEXcrZA6kfpJA==", + "dev": true, + "requires": { + "@pnpm/npm-conf": "^1.0.4" + } + }, + "registry-url": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-6.0.1.tgz", + "integrity": "sha512-+crtS5QjFRqFCoQmvGduwYWEBng99ZvmFvF+cUJkGYF1L1BfU8C6Zp9T7f5vPAwyLkUExpvK+ANVZmGU49qi4Q==", + "dev": true, + "requires": { + "rc": "1.2.8" + } + }, + "remote-git-tags": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/remote-git-tags/-/remote-git-tags-3.0.0.tgz", + "integrity": "sha512-C9hAO4eoEsX+OXA4rla66pXZQ+TLQ8T9dttgQj18yuKlPMTVkIkdYXvlMC55IuUsIkV6DpmQYi10JKFLaU+l7w==", + "dev": true + }, + "require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true + }, + "resolve": { + "version": "1.22.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", + "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", + "dev": true, + "requires": { + "is-core-module": "^2.9.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + } + }, + "resolve-alpn": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz", + "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==", + "dev": true + }, + "responselike": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-3.0.0.tgz", + "integrity": "sha512-40yHxbNcl2+rzXvZuVkrYohathsSJlMTXKryG5y8uciHv1+xDLHQpgjG64JUO9nrEq2jGLH6IZ8BcZyw3wrweg==", + "dev": true, + "requires": { + "lowercase-keys": "^3.0.0" + } + }, + "retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", + "dev": true + }, + "reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true + }, + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + }, + "dependencies": { + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + } + } + }, + "rollup": { + "version": "2.79.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.79.1.tgz", + "integrity": "sha512-uKxbd0IhMZOhjAiD5oAFp7BqvkA4Dv47qpOCtaNvng4HBwdbWtdOh8f5nZNuk2rp51PMGk3bzfWu5oayNEuYnw==", + "dev": true, + "requires": { + "fsevents": "~2.3.2" + } + }, + "run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "requires": { + "queue-microtask": "^1.2.2" + } + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true + }, + "semver": { + "version": "7.3.8", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", + "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + }, + "dependencies": { + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + } + } + }, + "semver-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-4.0.0.tgz", + "integrity": "sha512-0Ju4+6A8iOnpL/Thra7dZsSlOHYAHIeMxfhWQRI1/VLcT3WDBZKKtQt/QkBOsiIN9ZpuvHE6cGZ0x4glCMmfiA==", + "dev": true, + "requires": { + "semver": "^7.3.5" + } + }, + "semver-utils": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/semver-utils/-/semver-utils-1.1.4.tgz", + "integrity": "sha512-EjnoLE5OGmDAVV/8YDoN5KiajNadjzIp9BAHOhYeQHt7j0UWxjmgsx4YD48wp4Ue1Qogq38F1GNUJNqF1kKKxA==", + "dev": true + }, + "set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", + "dev": true + }, + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true + }, + "siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true + }, + "signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true + }, + "sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "dev": true + }, + "slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true + }, + "slice-ansi": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-5.0.0.tgz", + "integrity": "sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==", + "dev": true, + "requires": { + "ansi-styles": "^6.0.0", + "is-fullwidth-code-point": "^4.0.0" + }, + "dependencies": { + "is-fullwidth-code-point": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz", + "integrity": "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==", + "dev": true + } + } + }, + "smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "dev": true + }, + "socks": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.7.1.tgz", + "integrity": "sha512-7maUZy1N7uo6+WVEX6psASxtNlKaNVMlGQKkG/63nEDdLOWNbiUMoLK7X4uYoLhQstau72mLgfEWcXcwsaHbYQ==", + "dev": true, + "requires": { + "ip": "^2.0.0", + "smart-buffer": "^4.2.0" + } + }, + "socks-proxy-agent": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-7.0.0.tgz", + "integrity": "sha512-Fgl0YPZ902wEsAyiQ+idGd1A7rSFx/ayC1CQVMw5P+EQx2V0SgpGtf6OKFhVjPflPUl9YMmEOnmfjCdMUsygww==", + "dev": true, + "requires": { + "agent-base": "^6.0.2", + "debug": "^4.3.3", + "socks": "^2.6.2" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "source-map-js": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", + "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "dev": true + }, + "source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "spawn-please": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/spawn-please/-/spawn-please-2.0.1.tgz", + "integrity": "sha512-W+cFbZR2q2mMTfjz5ZGvhBAiX+e/zczFCNlbS9mxiSdYswBXwUuBUT+a0urH+xZZa8f/bs0mXHyZsZHR9hKogA==", + "dev": true, + "requires": { + "cross-spawn": "^7.0.3" + } + }, + "spdx-correct": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", + "integrity": "sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==", + "dev": true, + "requires": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-exceptions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", + "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==", + "dev": true + }, + "spdx-expression-parse": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "dev": true, + "requires": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-license-ids": { + "version": "3.0.12", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.12.tgz", + "integrity": "sha512-rr+VVSXtRhO4OHbXUiAF7xW3Bo9DuuF6C5jH+q/x15j2jniycgKbxU09Hr0WqlSLUs4i4ltHGXqTe7VHclYWyA==", + "dev": true + }, + "ssri": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-10.0.1.tgz", + "integrity": "sha512-WVy6di9DlPOeBWEjMScpNipeSX2jIZBGEn5Uuo8Q7aIuFEuDX0pw8RxcOjlD1TWP4obi24ki7m/13+nFpcbXrw==", + "dev": true, + "requires": { + "minipass": "^4.0.0" + } + }, + "stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true + }, + "std-env": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.3.1.tgz", + "integrity": "sha512-3H20QlwQsSm2OvAxWIYhs+j01MzzqwMwGiiO1NQaJYZgJZFPuAbf95/DiKRBSTYIJ2FeGUc+B/6mPGcWP9dO3Q==", + "dev": true + }, + "string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "requires": { + "safe-buffer": "~5.2.0" + } + }, + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1" + } + }, + "strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "dev": true + }, + "strip-literal": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-1.0.0.tgz", + "integrity": "sha512-5o4LsH1lzBzO9UFH63AJ2ad2/S2AVx6NtjOcaz+VTT2h1RiRvbipW72z8M/lxEhcPHDBQwpDrnTF7sXy/7OwCQ==", + "dev": true, + "requires": { + "acorn": "^8.8.1" + } + }, + "supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true + }, + "svelte": { + "version": "3.55.1", + "resolved": "https://registry.npmjs.org/svelte/-/svelte-3.55.1.tgz", + "integrity": "sha512-S+87/P0Ve67HxKkEV23iCdAh/SX1xiSfjF1HOglno/YTbSTW7RniICMCofWGdJJbdjw3S+0PfFb1JtGfTXE0oQ==", + "dev": true + }, + "tar": { + "version": "6.1.13", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.13.tgz", + "integrity": "sha512-jdIBIN6LTIe2jqzay/2vtYLlBHa3JF42ot3h1dW8Q0PaAG4v8rm0cvpVePtau5C6OKXGGcgO9q2AMNSWxiLqKw==", + "dev": true, + "requires": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^4.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "dependencies": { + "fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "dev": true, + "requires": { + "minipass": "^3.0.0" + }, + "dependencies": { + "minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + } + } + } + } + }, + "tinybench": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.3.1.tgz", + "integrity": "sha512-hGYWYBMPr7p4g5IarQE7XhlyWveh1EKhy4wUBS1LrHXCKYgvz+4/jCqgmJqZxxldesn05vccrtME2RLLZNW7iA==", + "dev": true + }, + "tinypool": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-0.3.1.tgz", + "integrity": "sha512-zLA1ZXlstbU2rlpA4CIeVaqvWq41MTWqLY3FfsAXgC8+f7Pk7zroaJQxDgxn1xNudKW6Kmj4808rPFShUlIRmQ==", + "dev": true + }, + "tinyspy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-1.0.2.tgz", + "integrity": "sha512-bSGlgwLBYf7PnUsQ6WOc6SJ3pGOcd+d8AA6EUnLDDM0kWEstC1JIlSZA3UNliDXhd9ABoS7hiRBDCu+XP/sf1Q==", + "dev": true + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + }, + "tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "dev": true + }, + "type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true + }, + "type-fest": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", + "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", + "dev": true + }, + "typedarray-to-buffer": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", + "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", + "dev": true, + "requires": { + "is-typedarray": "^1.0.0" + } + }, + "ufo": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.0.1.tgz", + "integrity": "sha512-boAm74ubXHY7KJQZLlXrtMz52qFvpsbOxDcZOnw/Wf+LS4Mmyu7JxmzD4tDLtUQtmZECypJ0FrCz4QIe6dvKRA==", + "dev": true + }, + "unique-filename": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-3.0.0.tgz", + "integrity": "sha512-afXhuC55wkAmZ0P18QsVE6kp8JaxrEokN2HGIoIVv2ijHQd419H0+6EigAFcIzXeMIkcIkNBpB3L/DXB3cTS/g==", + "dev": true, + "requires": { + "unique-slug": "^4.0.0" + } + }, + "unique-slug": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-4.0.0.tgz", + "integrity": "sha512-WrcA6AyEfqDX5bWige/4NQfPZMtASNVxdmWR76WESYQVAACSgWcR6e9i0mofqqBxYFtL4oAxPIptY73/0YE1DQ==", + "dev": true, + "requires": { + "imurmurhash": "^0.1.4" + } + }, + "unique-string": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-3.0.0.tgz", + "integrity": "sha512-VGXBUVwxKMBUznyffQweQABPRRW1vHZAbadFZud4pLFAqRGvv/96vafgjWFqzourzr8YonlQiPgH0YCJfawoGQ==", + "dev": true, + "requires": { + "crypto-random-string": "^4.0.0" + } + }, + "untildify": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/untildify/-/untildify-4.0.0.tgz", + "integrity": "sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==", + "dev": true + }, + "update-notifier": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-6.0.2.tgz", + "integrity": "sha512-EDxhTEVPZZRLWYcJ4ZXjGFN0oP7qYvbXWzEgRm/Yql4dHX5wDbvh89YHP6PK1lzZJYrMtXUuZZz8XGK+U6U1og==", + "dev": true, + "requires": { + "boxen": "^7.0.0", + "chalk": "^5.0.1", + "configstore": "^6.0.0", + "has-yarn": "^3.0.0", + "import-lazy": "^4.0.0", + "is-ci": "^3.0.1", + "is-installed-globally": "^0.4.0", + "is-npm": "^6.0.0", + "is-yarn-global": "^0.4.0", + "latest-version": "^7.0.0", + "pupa": "^3.1.0", + "semver": "^7.3.7", + "semver-diff": "^4.0.0", + "xdg-basedir": "^5.1.0" + } + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true + }, + "validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "dev": true, + "requires": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "validate-npm-package-name": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-5.0.0.tgz", + "integrity": "sha512-YuKoXDAhBYxY7SfOKxHBDoSyENFeW5VvIIQp2TGQuit8gpK6MnWaQelBKxso72DoxTZfZdcP3W90LqpSkgPzLQ==", + "dev": true, + "requires": { + "builtins": "^5.0.0" + } + }, + "vite": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/vite/-/vite-3.2.5.tgz", + "integrity": "sha512-4mVEpXpSOgrssFZAOmGIr85wPHKvaDAcXqxVxVRZhljkJOMZi1ibLibzjLHzJvcok8BMguLc7g1W6W/GqZbLdQ==", + "dev": true, + "requires": { + "esbuild": "^0.15.9", + "fsevents": "~2.3.2", + "postcss": "^8.4.18", + "resolve": "^1.22.1", + "rollup": "^2.79.1" + }, + "dependencies": { + "@esbuild/android-arm": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.15.18.tgz", + "integrity": "sha512-5GT+kcs2WVGjVs7+boataCkO5Fg0y4kCjzkB5bAip7H4jfnOS3dA6KPiww9W1OEKTKeAcUVhdZGvgI65OXmUnw==", + "dev": true, + "optional": true + }, + "@esbuild/linux-loong64": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.15.18.tgz", + "integrity": "sha512-L4jVKS82XVhw2nvzLg/19ClLWg0y27ulRwuP7lcyL6AbUWB5aPglXY3M21mauDQMDfRLs8cQmeT03r/+X3cZYQ==", + "dev": true, + "optional": true + }, + "esbuild": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.15.18.tgz", + "integrity": "sha512-x/R72SmW3sSFRm5zrrIjAhCeQSAWoni3CmHEqfQrZIQTM3lVCdehdwuIqaOtfC2slvpdlLa62GYoN8SxT23m6Q==", + "dev": true, + "requires": { + "@esbuild/android-arm": "0.15.18", + "@esbuild/linux-loong64": "0.15.18", + "esbuild-android-64": "0.15.18", + "esbuild-android-arm64": "0.15.18", + "esbuild-darwin-64": "0.15.18", + "esbuild-darwin-arm64": "0.15.18", + "esbuild-freebsd-64": "0.15.18", + "esbuild-freebsd-arm64": "0.15.18", + "esbuild-linux-32": "0.15.18", + "esbuild-linux-64": "0.15.18", + "esbuild-linux-arm": "0.15.18", + "esbuild-linux-arm64": "0.15.18", + "esbuild-linux-mips64le": "0.15.18", + "esbuild-linux-ppc64le": "0.15.18", + "esbuild-linux-riscv64": "0.15.18", + "esbuild-linux-s390x": "0.15.18", + "esbuild-netbsd-64": "0.15.18", + "esbuild-openbsd-64": "0.15.18", + "esbuild-sunos-64": "0.15.18", + "esbuild-windows-32": "0.15.18", + "esbuild-windows-64": "0.15.18", + "esbuild-windows-arm64": "0.15.18" + } + } + } + }, + "vite-node": { + "version": "0.28.3", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-0.28.3.tgz", + "integrity": "sha512-uJJAOkgVwdfCX8PUQhqLyDOpkBS5+j+FdbsXoPVPDlvVjRkb/W/mLYQPSL6J+t8R0UV8tJSe8c9VyxVQNsDSyg==", + "dev": true, + "requires": { + "cac": "^6.7.14", + "debug": "^4.3.4", + "mlly": "^1.1.0", + "pathe": "^1.1.0", + "picocolors": "^1.0.0", + "source-map": "^0.6.1", + "source-map-support": "^0.5.21", + "vite": "^3.0.0 || ^4.0.0" + } + }, + "vitest": { + "version": "0.28.3", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-0.28.3.tgz", + "integrity": "sha512-N41VPNf3VGJlWQizGvl1P5MGyv3ZZA2Zvh+2V8L6tYBAAuqqDK4zExunT1Cdb6dGfZ4gr+IMrnG8d4Z6j9ctPw==", + "dev": true, + "requires": { + "@types/chai": "^4.3.4", + "@types/chai-subset": "^1.3.3", + "@types/node": "*", + "@vitest/expect": "0.28.3", + "@vitest/runner": "0.28.3", + "@vitest/spy": "0.28.3", + "@vitest/utils": "0.28.3", + "acorn": "^8.8.1", + "acorn-walk": "^8.2.0", + "cac": "^6.7.14", + "chai": "^4.3.7", + "debug": "^4.3.4", + "local-pkg": "^0.4.2", + "pathe": "^1.1.0", + "picocolors": "^1.0.0", + "source-map": "^0.6.1", + "std-env": "^3.3.1", + "strip-literal": "^1.0.0", + "tinybench": "^2.3.1", + "tinypool": "^0.3.1", + "tinyspy": "^1.0.2", + "vite": "^3.0.0 || ^4.0.0", + "vite-node": "0.28.3", + "why-is-node-running": "^2.2.2" + } + }, + "webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "dev": true + }, + "whatwg-encoding": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz", + "integrity": "sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==", + "dev": true, + "requires": { + "iconv-lite": "0.6.3" + } + }, + "whatwg-mimetype": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz", + "integrity": "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==", + "dev": true + }, + "whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dev": true, + "requires": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + }, + "dependencies": { + "webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "dev": true + } + } + }, + "which": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-3.0.0.tgz", + "integrity": "sha512-nla//68K9NU6yRiwDY/Q8aU6siKlSs64aEC7+IV56QoAuyQT2ovsJcgGYGyqMOmI/CGN1BOR6mM5EN0FBO+zyQ==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "why-is-node-running": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.2.2.tgz", + "integrity": "sha512-6tSwToZxTOcotxHeA+qGCq1mVzKR3CwcJGmVcY+QE8SHy6TnpFnh8PAvPNHYr7EcuVeG0QSMxtYCuO1ta/G/oA==", + "dev": true, + "requires": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + } + }, + "wide-align": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", + "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", + "dev": true, + "requires": { + "string-width": "^1.0.2 || 2 || 3 || 4" + } + }, + "widest-line": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-4.0.1.tgz", + "integrity": "sha512-o0cyEG0e8GPzT4iGHphIOh0cJOV8fivsXxddQasHPHfoZf1ZexrfeA21w2NaEN1RHE+fXlfISmOE8R9N3u3Qig==", + "dev": true, + "requires": { + "string-width": "^5.0.1" + }, + "dependencies": { + "ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "dev": true + }, + "emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true + }, + "string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "requires": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + } + }, + "strip-ansi": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.0.1.tgz", + "integrity": "sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw==", + "dev": true, + "requires": { + "ansi-regex": "^6.0.1" + } + } + } + }, + "wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "requires": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "dependencies": { + "ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "dev": true + }, + "emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true + }, + "string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "requires": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + } + }, + "strip-ansi": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.0.1.tgz", + "integrity": "sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw==", + "dev": true, + "requires": { + "ansi-regex": "^6.0.1" + } + } + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true + }, + "write-file-atomic": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", + "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", + "dev": true, + "requires": { + "imurmurhash": "^0.1.4", + "is-typedarray": "^1.0.0", + "signal-exit": "^3.0.2", + "typedarray-to-buffer": "^3.1.5" + } + }, + "xdg-basedir": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-5.1.0.tgz", + "integrity": "sha512-GCPAHLvrIH13+c0SuacwvRYj2SxJXQ4kaVTT5xgL3kPrz56XxkF21IGhjSE1+W0aw7gpBWRGXLCPnPby6lSpmQ==", + "dev": true + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "yaml": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.2.2.tgz", + "integrity": "sha512-CBKFWExMn46Foo4cldiChEzn7S7SRV+wqiluAb6xmueD/fGyRHIhX8m14vVGgeFWjN540nKCNVj6P21eQjgTuA==", + "dev": true + }, + "yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true + } + } +} diff --git a/v3/internal/runtime/package.json b/v3/internal/runtime/package.json new file mode 100644 index 000000000..f8c9c6023 --- /dev/null +++ b/v3/internal/runtime/package.json @@ -0,0 +1,17 @@ +{ + "name": "runtime", + "version": "3.0.0", + "description": "Wails JS Runtime", + "main": "index.js", + "scripts": {}, + "author": "Lea Anthony ", + "license": "ISC", + "devDependencies": { + "esbuild": "^0.17.19", + "happy-dom": "^8.1.5", + "nanoid": "^4.0.0", + "npm-check-updates": "^16.6.3", + "svelte": "^3.55.1", + "vitest": "^0.28.3" + } +} diff --git a/v3/internal/runtime/runtime_debug_darwin.go b/v3/internal/runtime/runtime_debug_darwin.go new file mode 100644 index 000000000..14656126f --- /dev/null +++ b/v3/internal/runtime/runtime_debug_darwin.go @@ -0,0 +1,8 @@ +//go:build darwin && !production + +package runtime + +import _ "embed" + +//go:embed runtime_debug_desktop_darwin.js +var DesktopRuntime []byte diff --git a/v3/internal/runtime/runtime_debug_desktop_darwin.js b/v3/internal/runtime/runtime_debug_desktop_darwin.js new file mode 100644 index 000000000..3e0861b89 --- /dev/null +++ b/v3/internal/runtime/runtime_debug_desktop_darwin.js @@ -0,0 +1,873 @@ +(() => { + var __defProp = Object.defineProperty; + var __export = (target, all) => { + for (var name in all) + __defProp(target, name, { get: all[name], enumerable: true }); + }; + + // desktop/clipboard.js + var clipboard_exports = {}; + __export(clipboard_exports, { + SetText: () => SetText, + Text: () => Text + }); + + // node_modules/nanoid/non-secure/index.js + var urlAlphabet = "useandom-26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict"; + var nanoid = (size = 21) => { + let id = ""; + let i = size; + while (i--) { + id += urlAlphabet[Math.random() * 64 | 0]; + } + return id; + }; + + // desktop/runtime.js + var runtimeURL = window.location.origin + "/wails/runtime"; + var objectNames = { + Call: 0, + Clipboard: 1, + Application: 2, + Events: 3, + ContextMenu: 4, + Dialog: 5, + Window: 6, + Screens: 7, + System: 8, + Browser: 9 + }; + var clientId = nanoid(); + 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 (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) { + 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)); + }); + } + function newRuntimeCallerWithID(object, windowName) { + return function(method, args = null) { + return runtimeCallWithID(object, method, windowName, args); + }; + } + + // desktop/clipboard.js + var call = newRuntimeCallerWithID(objectNames.Clipboard); + var ClipboardSetText = 0; + var ClipboardText = 1; + function SetText(text) { + void call(ClipboardSetText, { text }); + } + function Text() { + return call(ClipboardText); + } + + // desktop/application.js + var application_exports = {}; + __export(application_exports, { + Hide: () => Hide, + Quit: () => Quit, + Show: () => Show + }); + var call2 = newRuntimeCallerWithID(objectNames.Application); + var methods = { + Hide: 0, + Show: 1, + Quit: 2 + }; + function Hide() { + void call2(methods.Hide); + } + function Show() { + void call2(methods.Show); + } + function Quit() { + void call2(methods.Quit); + } + + // desktop/screens.js + var screens_exports = {}; + __export(screens_exports, { + GetAll: () => GetAll, + GetCurrent: () => GetCurrent, + GetPrimary: () => GetPrimary + }); + var call3 = newRuntimeCallerWithID(objectNames.Screens); + var ScreensGetAll = 0; + var ScreensGetPrimary = 1; + var ScreensGetCurrent = 2; + function GetAll() { + return call3(ScreensGetAll); + } + function GetPrimary() { + return call3(ScreensGetPrimary); + } + function GetCurrent() { + return call3(ScreensGetCurrent); + } + + // desktop/system.js + var system_exports = {}; + __export(system_exports, { + IsDarkMode: () => IsDarkMode + }); + var call4 = newRuntimeCallerWithID(objectNames.System); + var SystemIsDarkMode = 0; + function IsDarkMode() { + return call4(SystemIsDarkMode); + } + + // desktop/browser.js + var browser_exports = {}; + __export(browser_exports, { + OpenURL: () => OpenURL + }); + var call5 = newRuntimeCallerWithID(objectNames.Browser); + var BrowserOpenURL = 0; + function OpenURL(url) { + void call5(BrowserOpenURL, { url }); + } + + // desktop/calls.js + var call6 = newRuntimeCallerWithID(objectNames.Call); + var CallBinding = 0; + var callResponses = /* @__PURE__ */ new Map(); + function generateID() { + let result; + do { + result = nanoid(); + } while (callResponses.has(result)); + return result; + } + function callCallback(id, data, isJSON) { + let p = callResponses.get(id); + if (p) { + if (isJSON) { + p.resolve(JSON.parse(data)); + } else { + p.resolve(data); + } + callResponses.delete(id); + } + } + function callErrorCallback(id, message) { + let p = callResponses.get(id); + if (p) { + p.reject(message); + callResponses.delete(id); + } + } + function callBinding(type, options) { + return new Promise((resolve, reject) => { + let id = generateID(); + options = options || {}; + options["call-id"] = id; + callResponses.set(id, { resolve, reject }); + call6(type, options).catch((error) => { + reject(error); + callResponses.delete(id); + }); + }); + } + function Call(options) { + return callBinding(CallBinding, options); + } + function CallByName(name, ...args) { + if (typeof name !== "string" || name.split(".").length !== 3) { + throw new Error("CallByName requires a string in the format 'package.struct.method'"); + } + let parts = name.split("."); + return callBinding(CallBinding, { + packageName: parts[0], + structName: parts[1], + methodName: parts[2], + args + }); + } + function CallByID(methodID, ...args) { + return callBinding(CallBinding, { + methodID, + args + }); + } + function Plugin(pluginName, methodName, ...args) { + return callBinding(CallBinding, { + packageName: "wails-plugins", + structName: pluginName, + methodName, + args + }); + } + + // desktop/window.js + var WindowCenter = 0; + var WindowSetTitle = 1; + var WindowFullscreen = 2; + var WindowUnFullscreen = 3; + var WindowSetSize = 4; + var WindowSize = 5; + var WindowSetMaxSize = 6; + var WindowSetMinSize = 7; + var WindowSetAlwaysOnTop = 8; + var WindowSetRelativePosition = 9; + var WindowRelativePosition = 10; + var WindowScreen = 11; + var WindowHide = 12; + var WindowMaximise = 13; + var WindowUnMaximise = 14; + var WindowToggleMaximise = 15; + var WindowMinimise = 16; + var WindowUnMinimise = 17; + var WindowRestore = 18; + var WindowShow = 19; + var WindowClose = 20; + var WindowSetBackgroundColour = 21; + var WindowSetResizable = 22; + var WindowWidth = 23; + var WindowHeight = 24; + var WindowZoomIn = 25; + var WindowZoomOut = 26; + var WindowZoomReset = 27; + var WindowGetZoomLevel = 28; + var WindowSetZoomLevel = 29; + function newWindow(windowName) { + let call10 = newRuntimeCallerWithID(objectNames.Window, windowName); + return { + /** + * Centers the window. + */ + Center: () => void call10(WindowCenter), + /** + * Set the window title. + * @param title + */ + SetTitle: (title) => void call10(WindowSetTitle, { title }), + /** + * Makes the window fullscreen. + */ + Fullscreen: () => void call10(WindowFullscreen), + /** + * Unfullscreen the window. + */ + UnFullscreen: () => void call10(WindowUnFullscreen), + /** + * Set the window size. + * @param {number} width The window width + * @param {number} height The window height + */ + SetSize: (width, height) => call10(WindowSetSize, { width, height }), + /** + * Get the window size. + * @returns {Promise} The window size + */ + Size: () => { + return call10(WindowSize); + }, + /** + * Set the window maximum size. + * @param {number} width + * @param {number} height + */ + SetMaxSize: (width, height) => void call10(WindowSetMaxSize, { width, height }), + /** + * Set the window minimum size. + * @param {number} width + * @param {number} height + */ + SetMinSize: (width, height) => void call10(WindowSetMinSize, { width, height }), + /** + * Set window to be always on top. + * @param {boolean} onTop Whether the window should be always on top + */ + SetAlwaysOnTop: (onTop) => void call10(WindowSetAlwaysOnTop, { alwaysOnTop: onTop }), + /** + * Set the window relative position. + * @param {number} x + * @param {number} y + */ + SetRelativePosition: (x, y) => call10(WindowSetRelativePosition, { x, y }), + /** + * Get the window position. + * @returns {Promise} The window position + */ + RelativePosition: () => { + return call10(WindowRelativePosition); + }, + /** + * Get the screen the window is on. + * @returns {Promise} + */ + Screen: () => { + return call10(WindowScreen); + }, + /** + * Hide the window + */ + Hide: () => void call10(WindowHide), + /** + * Maximise the window + */ + Maximise: () => void call10(WindowMaximise), + /** + * Show the window + */ + Show: () => void call10(WindowShow), + /** + * Close the window + */ + Close: () => void call10(WindowClose), + /** + * Toggle the window maximise state + */ + ToggleMaximise: () => void call10(WindowToggleMaximise), + /** + * Unmaximise the window + */ + UnMaximise: () => void call10(WindowUnMaximise), + /** + * Minimise the window + */ + Minimise: () => void call10(WindowMinimise), + /** + * Unminimise the window + */ + UnMinimise: () => void call10(WindowUnMinimise), + /** + * Restore the window + */ + Restore: () => void call10(WindowRestore), + /** + * Set the background colour of the window. + * @param {number} r - A value between 0 and 255 + * @param {number} g - A value between 0 and 255 + * @param {number} b - A value between 0 and 255 + * @param {number} a - A value between 0 and 255 + */ + SetBackgroundColour: (r, g, b, a) => void call10(WindowSetBackgroundColour, { r, g, b, a }), + /** + * Set whether the window can be resized or not + * @param {boolean} resizable + */ + SetResizable: (resizable2) => void call10(WindowSetResizable, { resizable: resizable2 }), + /** + * Get the window width + * @returns {Promise} + */ + Width: () => { + return call10(WindowWidth); + }, + /** + * Get the window height + * @returns {Promise} + */ + Height: () => { + return call10(WindowHeight); + }, + /** + * Zoom in the window + */ + ZoomIn: () => void call10(WindowZoomIn), + /** + * Zoom out the window + */ + ZoomOut: () => void call10(WindowZoomOut), + /** + * Reset the window zoom + */ + ZoomReset: () => void call10(WindowZoomReset), + /** + * Get the window zoom + * @returns {Promise} + */ + GetZoomLevel: () => { + return call10(WindowGetZoomLevel); + }, + /** + * Set the window zoom level + * @param {number} zoomLevel + */ + SetZoomLevel: (zoomLevel) => void call10(WindowSetZoomLevel, { zoomLevel }) + }; + } + + // desktop/events.js + var call7 = newRuntimeCallerWithID(objectNames.Events); + var EventEmit = 0; + var Listener = class { + /** + * Creates an instance of Listener. + * @param {string} eventName + * @param {function} callback + * @param {number} maxCallbacks + * @memberof Listener + */ + constructor(eventName, callback, maxCallbacks) { + this.eventName = eventName; + this.maxCallbacks = maxCallbacks || -1; + this.Callback = (data) => { + callback(data); + if (this.maxCallbacks === -1) { + return false; + } + this.maxCallbacks -= 1; + return this.maxCallbacks === 0; + }; + } + }; + var WailsEvent = class { + /** + * Creates an instance of WailsEvent. + * @param {string} name - Name of the event + * @param {any=null} data - Data associated with the event + * @memberof WailsEvent + */ + constructor(name, data = null) { + this.name = name; + this.data = data; + } + }; + var eventListeners = /* @__PURE__ */ new Map(); + function OnMultiple(eventName, callback, maxCallbacks) { + let listeners = eventListeners.get(eventName) || []; + const thisListener = new Listener(eventName, callback, maxCallbacks); + listeners.push(thisListener); + eventListeners.set(eventName, listeners); + return () => listenerOff(thisListener); + } + function On(eventName, callback) { + return OnMultiple(eventName, callback, -1); + } + function Once(eventName, callback) { + return OnMultiple(eventName, callback, 1); + } + function listenerOff(listener) { + const eventName = listener.eventName; + let listeners = eventListeners.get(eventName).filter((l) => l !== listener); + if (listeners.length === 0) { + eventListeners.delete(eventName); + } else { + eventListeners.set(eventName, listeners); + } + } + function dispatchWailsEvent(event) { + let listeners = eventListeners.get(event.name); + if (listeners) { + let toRemove = []; + listeners.forEach((listener) => { + let remove = listener.Callback(event); + if (remove) { + toRemove.push(listener); + } + }); + if (toRemove.length > 0) { + listeners = listeners.filter((l) => !toRemove.includes(l)); + if (listeners.length === 0) { + eventListeners.delete(event.name); + } else { + eventListeners.set(event.name, listeners); + } + } + } + } + function Off(eventName, ...additionalEventNames) { + let eventsToRemove = [eventName, ...additionalEventNames]; + eventsToRemove.forEach((eventName2) => { + eventListeners.delete(eventName2); + }); + } + function OffAll() { + eventListeners.clear(); + } + function Emit(event) { + void call7(EventEmit, event); + } + + // desktop/dialogs.js + var call8 = newRuntimeCallerWithID(objectNames.Dialog); + var DialogInfo = 0; + var DialogWarning = 1; + var DialogError = 2; + var DialogQuestion = 3; + var DialogOpenFile = 4; + var DialogSaveFile = 5; + var dialogResponses = /* @__PURE__ */ new Map(); + function generateID2() { + let result; + do { + result = nanoid(); + } while (dialogResponses.has(result)); + return result; + } + function dialogCallback(id, data, isJSON) { + let p = dialogResponses.get(id); + if (p) { + if (isJSON) { + p.resolve(JSON.parse(data)); + } else { + p.resolve(data); + } + dialogResponses.delete(id); + } + } + function dialogErrorCallback(id, message) { + let p = dialogResponses.get(id); + if (p) { + p.reject(message); + dialogResponses.delete(id); + } + } + function dialog(type, options) { + return new Promise((resolve, reject) => { + let id = generateID2(); + options = options || {}; + options["dialog-id"] = id; + dialogResponses.set(id, { resolve, reject }); + call8(type, options).catch((error) => { + reject(error); + dialogResponses.delete(id); + }); + }); + } + function Info(options) { + return dialog(DialogInfo, options); + } + function Warning(options) { + return dialog(DialogWarning, options); + } + function Error2(options) { + return dialog(DialogError, options); + } + function Question(options) { + return dialog(DialogQuestion, options); + } + function OpenFile(options) { + return dialog(DialogOpenFile, options); + } + function SaveFile(options) { + return dialog(DialogSaveFile, options); + } + + // desktop/contextmenu.js + var call9 = newRuntimeCallerWithID(objectNames.ContextMenu); + var ContextMenuOpen = 0; + function openContextMenu(id, x, y, data) { + void call9(ContextMenuOpen, { id, x, y, data }); + } + function setupContextMenus() { + window.addEventListener("contextmenu", contextMenuHandler); + } + function contextMenuHandler(event) { + let element = event.target; + let customContextMenu = window.getComputedStyle(element).getPropertyValue("--custom-contextmenu"); + customContextMenu = customContextMenu ? customContextMenu.trim() : ""; + if (customContextMenu) { + event.preventDefault(); + let customContextMenuData = window.getComputedStyle(element).getPropertyValue("--custom-contextmenu-data"); + openContextMenu(customContextMenu, event.clientX, event.clientY, customContextMenuData); + return; + } + processDefaultContextMenu(event); + } + function processDefaultContextMenu(event) { + if (true) { + return; + } + const element = event.target; + const computedStyle = window.getComputedStyle(element); + const defaultContextMenuAction = computedStyle.getPropertyValue("--default-contextmenu").trim(); + switch (defaultContextMenuAction) { + case "show": + return; + case "hide": + event.preventDefault(); + return; + default: + if (element.isContentEditable) { + return; + } + const selection = window.getSelection(); + const hasSelection = selection.toString().length > 0; + if (hasSelection) { + for (let i = 0; i < selection.rangeCount; i++) { + const range = selection.getRangeAt(i); + const rects = range.getClientRects(); + for (let j = 0; j < rects.length; j++) { + const rect = rects[j]; + if (document.elementFromPoint(rect.left, rect.top) === element) { + return; + } + } + } + } + if (element.tagName === "INPUT" || element.tagName === "TEXTAREA") { + if (hasSelection || !element.readOnly && !element.disabled) { + return; + } + } + event.preventDefault(); + } + } + + // desktop/wml.js + function sendEvent(eventName, data = null) { + let event = new WailsEvent(eventName, data); + Emit(event); + } + function addWMLEventListeners() { + const elements = document.querySelectorAll("[wml-event]"); + elements.forEach(function(element) { + const eventType = element.getAttribute("wml-event"); + const confirm = element.getAttribute("wml-confirm"); + const trigger = element.getAttribute("wml-trigger") || "click"; + let callback = function() { + if (confirm) { + Question({ Title: "Confirm", Message: confirm, Detached: false, Buttons: [{ Label: "Yes" }, { Label: "No", IsDefault: true }] }).then(function(result) { + if (result !== "No") { + sendEvent(eventType); + } + }); + return; + } + sendEvent(eventType); + }; + element.removeEventListener(trigger, callback); + element.addEventListener(trigger, callback); + }); + } + function callWindowMethod(method) { + if (wails.Window[method] === void 0) { + console.log("Window method " + method + " not found"); + } + wails.Window[method](); + } + function addWMLWindowListeners() { + const elements = document.querySelectorAll("[wml-window]"); + elements.forEach(function(element) { + const windowMethod = element.getAttribute("wml-window"); + const confirm = element.getAttribute("wml-confirm"); + const trigger = element.getAttribute("wml-trigger") || "click"; + let callback = function() { + if (confirm) { + Question({ Title: "Confirm", Message: confirm, Buttons: [{ Label: "Yes" }, { Label: "No", IsDefault: true }] }).then(function(result) { + if (result !== "No") { + callWindowMethod(windowMethod); + } + }); + return; + } + callWindowMethod(windowMethod); + }; + element.removeEventListener(trigger, callback); + element.addEventListener(trigger, callback); + }); + } + function addWMLOpenBrowserListener() { + const elements = document.querySelectorAll("[wml-openurl]"); + elements.forEach(function(element) { + const url = element.getAttribute("wml-openurl"); + const confirm = element.getAttribute("wml-confirm"); + const trigger = element.getAttribute("wml-trigger") || "click"; + let callback = function() { + if (confirm) { + Question({ Title: "Confirm", Message: confirm, Buttons: [{ Label: "Yes" }, { Label: "No", IsDefault: true }] }).then(function(result) { + if (result !== "No") { + void wails.Browser.OpenURL(url); + } + }); + return; + } + void wails.Browser.OpenURL(url); + }; + element.removeEventListener(trigger, callback); + element.addEventListener(trigger, callback); + }); + } + function reloadWML() { + addWMLEventListeners(); + addWMLWindowListeners(); + addWMLOpenBrowserListener(); + } + + // desktop/invoke.js + var invoke = function(input) { + if (false) { + chrome.webview.postMessage(input); + } else { + webkit.messageHandlers.external.postMessage(input); + } + }; + + // desktop/flags.js + var flags = /* @__PURE__ */ new Map(); + function convertToMap(obj) { + const map = /* @__PURE__ */ new Map(); + for (const [key, value] of Object.entries(obj)) { + if (typeof value === "object" && value !== null) { + map.set(key, convertToMap(value)); + } else { + map.set(key, value); + } + } + return map; + } + fetch("/wails/flags").then((response) => { + response.json().then((data) => { + flags = convertToMap(data); + }); + }); + + // desktop/drag.js + var shouldDrag = false; + function dragTest(e) { + let val = window.getComputedStyle(e.target).getPropertyValue("--webkit-app-region"); + if (val) { + val = val.trim(); + } + if (val !== "drag") { + return false; + } + if (e.buttons !== 1) { + return false; + } + return e.detail === 1; + } + function setupDrag() { + window.addEventListener("mousedown", onMouseDown); + window.addEventListener("mousemove", onMouseMove); + window.addEventListener("mouseup", onMouseUp); + } + var resizable = false; + function setResizable(value) { + resizable = value; + } + function onMouseDown(e) { + if (false) { + if (testResize()) { + return; + } + } + if (dragTest(e)) { + if (e.offsetX > e.target.clientWidth || e.offsetY > e.target.clientHeight) { + return; + } + shouldDrag = true; + } else { + shouldDrag = false; + } + } + function onMouseUp(e) { + let mousePressed = e.buttons !== void 0 ? e.buttons : e.which; + if (mousePressed > 0) { + endDrag(); + } + } + function endDrag() { + document.body.style.cursor = "default"; + shouldDrag = false; + } + function onMouseMove(e) { + if (shouldDrag) { + shouldDrag = false; + let mousePressed = e.buttons !== void 0 ? e.buttons : e.which; + if (mousePressed > 0) { + invoke("drag"); + } + return; + } + if (false) { + if (resizable) { + handleResize(e); + } + } + } + + // desktop/main.js + window.wails = { + ...newRuntime(null), + Capabilities: {}, + clientId + }; + fetch("/wails/capabilities").then((response) => { + response.json().then((data) => { + window.wails.Capabilities = data; + }); + }); + window._wails = { + dialogCallback, + dialogErrorCallback, + dispatchWailsEvent, + callCallback, + callErrorCallback, + endDrag, + setResizable + }; + function newRuntime(windowName) { + return { + Clipboard: { + ...clipboard_exports + }, + Application: { + ...application_exports, + GetWindowByName(windowName2) { + return newRuntime(windowName2); + } + }, + System: system_exports, + Screens: screens_exports, + Browser: browser_exports, + Call, + CallByID, + CallByName, + Plugin, + WML: { + Reload: reloadWML + }, + Dialog: { + Info, + Warning, + Error: Error2, + Question, + OpenFile, + SaveFile + }, + Events: { + Emit, + On, + Once, + OnMultiple, + Off, + OffAll + }, + Window: newWindow(windowName) + }; + } + if (true) { + console.log("Wails v3.0.0 Debug Mode Enabled"); + } + setupContextMenus(); + setupDrag(); + document.addEventListener("DOMContentLoaded", function() { + reloadWML(); + }); +})(); +//# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsiZGVza3RvcC9jbGlwYm9hcmQuanMiLCAibm9kZV9tb2R1bGVzL25hbm9pZC9ub24tc2VjdXJlL2luZGV4LmpzIiwgImRlc2t0b3AvcnVudGltZS5qcyIsICJkZXNrdG9wL2FwcGxpY2F0aW9uLmpzIiwgImRlc2t0b3Avc2NyZWVucy5qcyIsICJkZXNrdG9wL3N5c3RlbS5qcyIsICJkZXNrdG9wL2Jyb3dzZXIuanMiLCAiZGVza3RvcC9jYWxscy5qcyIsICJkZXNrdG9wL3dpbmRvdy5qcyIsICJkZXNrdG9wL2V2ZW50cy5qcyIsICJkZXNrdG9wL2RpYWxvZ3MuanMiLCAiZGVza3RvcC9jb250ZXh0bWVudS5qcyIsICJkZXNrdG9wL3dtbC5qcyIsICJkZXNrdG9wL2ludm9rZS5qcyIsICJkZXNrdG9wL2ZsYWdzLmpzIiwgImRlc2t0b3AvZHJhZy5qcyIsICJkZXNrdG9wL21haW4uanMiXSwKICAic291cmNlc0NvbnRlbnQiOiBbIi8qXHJcbiBfXHQgICBfX1x0ICBfIF9fXHJcbnwgfFx0IC8gL19fXyBfKF8pIC9fX19fXHJcbnwgfCAvfCAvIC8gX18gYC8gLyAvIF9fXy9cclxufCB8LyB8LyAvIC9fLyAvIC8gKF9fICApXHJcbnxfXy98X18vXFxfXyxfL18vXy9fX19fL1xyXG5UaGUgZWxlY3Ryb24gYWx0ZXJuYXRpdmUgZm9yIEdvXHJcbihjKSBMZWEgQW50aG9ueSAyMDE5LXByZXNlbnRcclxuKi9cclxuXHJcbi8qIGpzaGludCBlc3ZlcnNpb246IDkgKi9cclxuXHJcbmltcG9ydCB7bmV3UnVudGltZUNhbGxlcldpdGhJRCwgb2JqZWN0TmFtZXN9IGZyb20gXCIuL3J1bnRpbWVcIjtcclxuXHJcbmxldCBjYWxsID0gbmV3UnVudGltZUNhbGxlcldpdGhJRChvYmplY3ROYW1lcy5DbGlwYm9hcmQpO1xyXG5cclxubGV0IENsaXBib2FyZFNldFRleHQgPSAwO1xyXG5sZXQgQ2xpcGJvYXJkVGV4dCA9IDE7XHJcblxyXG4vKipcclxuICogU2V0IHRoZSBDbGlwYm9hcmQgdGV4dFxyXG4gKi9cclxuZXhwb3J0IGZ1bmN0aW9uIFNldFRleHQodGV4dCkge1xyXG4gICAgdm9pZCBjYWxsKENsaXBib2FyZFNldFRleHQsIHt0ZXh0fSk7XHJcbn1cclxuXHJcbi8qKlxyXG4gKiBHZXQgdGhlIENsaXBib2FyZCB0ZXh0XHJcbiAqIEByZXR1cm5zIHtQcm9taXNlPHN0cmluZz59XHJcbiAqL1xyXG5leHBvcnQgZnVuY3Rpb24gVGV4dCgpIHtcclxuICAgIHJldHVybiBjYWxsKENsaXBib2FyZFRleHQpO1xyXG59IiwgImxldCB1cmxBbHBoYWJldCA9XG4gICd1c2VhbmRvbS0yNlQxOTgzNDBQWDc1cHhKQUNLVkVSWU1JTkRCVVNIV09MRl9HUVpiZmdoamtscXZ3eXpyaWN0J1xuZXhwb3J0IGxldCBjdXN0b21BbHBoYWJldCA9IChhbHBoYWJldCwgZGVmYXVsdFNpemUgPSAyMSkgPT4ge1xuICByZXR1cm4gKHNpemUgPSBkZWZhdWx0U2l6ZSkgPT4ge1xuICAgIGxldCBpZCA9ICcnXG4gICAgbGV0IGkgPSBzaXplXG4gICAgd2hpbGUgKGktLSkge1xuICAgICAgaWQgKz0gYWxwaGFiZXRbKE1hdGgucmFuZG9tKCkgKiBhbHBoYWJldC5sZW5ndGgpIHwgMF1cbiAgICB9XG4gICAgcmV0dXJuIGlkXG4gIH1cbn1cbmV4cG9ydCBsZXQgbmFub2lkID0gKHNpemUgPSAyMSkgPT4ge1xuICBsZXQgaWQgPSAnJ1xuICBsZXQgaSA9IHNpemVcbiAgd2hpbGUgKGktLSkge1xuICAgIGlkICs9IHVybEFscGhhYmV0WyhNYXRoLnJhbmRvbSgpICogNjQpIHwgMF1cbiAgfVxuICByZXR1cm4gaWRcbn1cbiIsICIvKlxyXG4gXyAgICAgX18gICAgIF8gX19cclxufCB8ICAvIC9fX18gXyhfKSAvX19fX1xyXG58IHwgL3wgLyAvIF9fIGAvIC8gLyBfX18vXHJcbnwgfC8gfC8gLyAvXy8gLyAvIChfXyAgKVxyXG58X18vfF9fL1xcX18sXy9fL18vX19fXy9cclxuVGhlIGVsZWN0cm9uIGFsdGVybmF0aXZlIGZvciBHb1xyXG4oYykgTGVhIEFudGhvbnkgMjAxOS1wcmVzZW50XHJcbiovXHJcblxyXG4vKiBqc2hpbnQgZXN2ZXJzaW9uOiA5ICovXHJcbmltcG9ydCB7IG5hbm9pZCB9IGZyb20gJ25hbm9pZC9ub24tc2VjdXJlJztcclxuXHJcbmNvbnN0IHJ1bnRpbWVVUkwgPSB3aW5kb3cubG9jYXRpb24ub3JpZ2luICsgXCIvd2FpbHMvcnVudGltZVwiO1xyXG4vLyBPYmplY3QgTmFtZXNcclxuZXhwb3J0IGNvbnN0IG9iamVjdE5hbWVzID0ge1xyXG4gICAgQ2FsbDogMCxcclxuICAgIENsaXBib2FyZDogMSxcclxuICAgIEFwcGxpY2F0aW9uOiAyLFxyXG4gICAgRXZlbnRzOiAzLFxyXG4gICAgQ29udGV4dE1lbnU6IDQsXHJcbiAgICBEaWFsb2c6IDUsXHJcbiAgICBXaW5kb3c6IDYsXHJcbiAgICBTY3JlZW5zOiA3LFxyXG4gICAgU3lzdGVtOiA4LFxyXG4gICAgQnJvd3NlcjogOSxcclxufVxyXG5leHBvcnQgbGV0IGNsaWVudElkID0gbmFub2lkKCk7XHJcblxyXG5mdW5jdGlvbiBydW50aW1lQ2FsbChtZXRob2QsIHdpbmRvd05hbWUsIGFyZ3MpIHtcclxuICAgIGxldCB1cmwgPSBuZXcgVVJMKHJ1bnRpbWVVUkwpO1xyXG4gICAgaWYoIG1ldGhvZCApIHtcclxuICAgICAgICB1cmwuc2VhcmNoUGFyYW1zLmFwcGVuZChcIm1ldGhvZFwiLCBtZXRob2QpO1xyXG4gICAgfVxyXG4gICAgbGV0IGZldGNoT3B0aW9ucyA9IHtcclxuICAgICAgICBoZWFkZXJzOiB7fSxcclxuICAgIH07XHJcbiAgICBpZiAod2luZG93TmFtZSkge1xyXG4gICAgICAgIGZldGNoT3B0aW9ucy5oZWFkZXJzW1wieC13YWlscy13aW5kb3ctbmFtZVwiXSA9IHdpbmRvd05hbWU7XHJcbiAgICB9XHJcbiAgICBpZiAoYXJncykge1xyXG4gICAgICAgIHVybC5zZWFyY2hQYXJhbXMuYXBwZW5kKFwiYXJnc1wiLCBKU09OLnN0cmluZ2lmeShhcmdzKSk7XHJcbiAgICB9XHJcbiAgICBmZXRjaE9wdGlvbnMuaGVhZGVyc1tcIngtd2FpbHMtY2xpZW50LWlkXCJdID0gY2xpZW50SWQ7XHJcblxyXG4gICAgcmV0dXJuIG5ldyBQcm9taXNlKChyZXNvbHZlLCByZWplY3QpID0+IHtcclxuICAgICAgICBmZXRjaCh1cmwsIGZldGNoT3B0aW9ucylcclxuICAgICAgICAgICAgLnRoZW4ocmVzcG9uc2UgPT4ge1xyXG4gICAgICAgICAgICAgICAgaWYgKHJlc3BvbnNlLm9rKSB7XHJcbiAgICAgICAgICAgICAgICAgICAgLy8gY2hlY2sgY29udGVudCB0eXBlXHJcbiAgICAgICAgICAgICAgICAgICAgaWYgKHJlc3BvbnNlLmhlYWRlcnMuZ2V0KFwiQ29udGVudC1UeXBlXCIpICYmIHJlc3BvbnNlLmhlYWRlcnMuZ2V0KFwiQ29udGVudC1UeXBlXCIpLmluZGV4T2YoXCJhcHBsaWNhdGlvbi9qc29uXCIpICE9PSAtMSkge1xyXG4gICAgICAgICAgICAgICAgICAgICAgICByZXR1cm4gcmVzcG9uc2UuanNvbigpO1xyXG4gICAgICAgICAgICAgICAgICAgIH0gZWxzZSB7XHJcbiAgICAgICAgICAgICAgICAgICAgICAgIHJldHVybiByZXNwb25zZS50ZXh0KCk7XHJcbiAgICAgICAgICAgICAgICAgICAgfVxyXG4gICAgICAgICAgICAgICAgfVxyXG4gICAgICAgICAgICAgICAgcmVqZWN0KEVycm9yKHJlc3BvbnNlLnN0YXR1c1RleHQpKTtcclxuICAgICAgICAgICAgfSlcclxuICAgICAgICAgICAgLnRoZW4oZGF0YSA9PiByZXNvbHZlKGRhdGEpKVxyXG4gICAgICAgICAgICAuY2F0Y2goZXJyb3IgPT4gcmVqZWN0KGVycm9yKSk7XHJcbiAgICB9KTtcclxufVxyXG5cclxuZXhwb3J0IGZ1bmN0aW9uIG5ld1J1bnRpbWVDYWxsZXIob2JqZWN0LCB3aW5kb3dOYW1lKSB7XHJcbiAgICByZXR1cm4gZnVuY3Rpb24gKG1ldGhvZCwgYXJncz1udWxsKSB7XHJcbiAgICAgICAgcmV0dXJuIHJ1bnRpbWVDYWxsKG9iamVjdCArIFwiLlwiICsgbWV0aG9kLCB3aW5kb3dOYW1lLCBhcmdzKTtcclxuICAgIH07XHJcbn1cclxuXHJcbmZ1bmN0aW9uIHJ1bnRpbWVDYWxsV2l0aElEKG9iamVjdElELCBtZXRob2QsIHdpbmRvd05hbWUsIGFyZ3MpIHtcclxuICAgIGxldCB1cmwgPSBuZXcgVVJMKHJ1bnRpbWVVUkwpO1xyXG4gICAgdXJsLnNlYXJjaFBhcmFtcy5hcHBlbmQoXCJvYmplY3RcIiwgb2JqZWN0SUQpO1xyXG4gICAgdXJsLnNlYXJjaFBhcmFtcy5hcHBlbmQoXCJtZXRob2RcIiwgbWV0aG9kKTtcclxuICAgIGxldCBmZXRjaE9wdGlvbnMgPSB7XHJcbiAgICAgICAgaGVhZGVyczoge30sXHJcbiAgICB9O1xyXG4gICAgaWYgKHdpbmRvd05hbWUpIHtcclxuICAgICAgICBmZXRjaE9wdGlvbnMuaGVhZGVyc1tcIngtd2FpbHMtd2luZG93LW5hbWVcIl0gPSB3aW5kb3dOYW1lO1xyXG4gICAgfVxyXG4gICAgaWYgKGFyZ3MpIHtcclxuICAgICAgICB1cmwuc2VhcmNoUGFyYW1zLmFwcGVuZChcImFyZ3NcIiwgSlNPTi5zdHJpbmdpZnkoYXJncykpO1xyXG4gICAgfVxyXG4gICAgZmV0Y2hPcHRpb25zLmhlYWRlcnNbXCJ4LXdhaWxzLWNsaWVudC1pZFwiXSA9IGNsaWVudElkO1xyXG4gICAgcmV0dXJuIG5ldyBQcm9taXNlKChyZXNvbHZlLCByZWplY3QpID0+IHtcclxuICAgICAgICBmZXRjaCh1cmwsIGZldGNoT3B0aW9ucylcclxuICAgICAgICAgICAgLnRoZW4ocmVzcG9uc2UgPT4ge1xyXG4gICAgICAgICAgICAgICAgaWYgKHJlc3BvbnNlLm9rKSB7XHJcbiAgICAgICAgICAgICAgICAgICAgLy8gY2hlY2sgY29udGVudCB0eXBlXHJcbiAgICAgICAgICAgICAgICAgICAgaWYgKHJlc3BvbnNlLmhlYWRlcnMuZ2V0KFwiQ29udGVudC1UeXBlXCIpICYmIHJlc3BvbnNlLmhlYWRlcnMuZ2V0KFwiQ29udGVudC1UeXBlXCIpLmluZGV4T2YoXCJhcHBsaWNhdGlvbi9qc29uXCIpICE9PSAtMSkge1xyXG4gICAgICAgICAgICAgICAgICAgICAgICByZXR1cm4gcmVzcG9uc2UuanNvbigpO1xyXG4gICAgICAgICAgICAgICAgICAgIH0gZWxzZSB7XHJcbiAgICAgICAgICAgICAgICAgICAgICAgIHJldHVybiByZXNwb25zZS50ZXh0KCk7XHJcbiAgICAgICAgICAgICAgICAgICAgfVxyXG4gICAgICAgICAgICAgICAgfVxyXG4gICAgICAgICAgICAgICAgcmVqZWN0KEVycm9yKHJlc3BvbnNlLnN0YXR1c1RleHQpKTtcclxuICAgICAgICAgICAgfSlcclxuICAgICAgICAgICAgLnRoZW4oZGF0YSA9PiByZXNvbHZlKGRhdGEpKVxyXG4gICAgICAgICAgICAuY2F0Y2goZXJyb3IgPT4gcmVqZWN0KGVycm9yKSk7XHJcbiAgICB9KTtcclxufVxyXG5cclxuZXhwb3J0IGZ1bmN0aW9uIG5ld1J1bnRpbWVDYWxsZXJXaXRoSUQob2JqZWN0LCB3aW5kb3dOYW1lKSB7XHJcbiAgICByZXR1cm4gZnVuY3Rpb24gKG1ldGhvZCwgYXJncz1udWxsKSB7XHJcbiAgICAgICAgcmV0dXJuIHJ1bnRpbWVDYWxsV2l0aElEKG9iamVjdCwgbWV0aG9kLCB3aW5kb3dOYW1lLCBhcmdzKTtcclxuICAgIH07XHJcbn1cclxuIiwgIi8qXHJcbiBfXHQgICBfX1x0ICBfIF9fXHJcbnwgfFx0IC8gL19fXyBfKF8pIC9fX19fXHJcbnwgfCAvfCAvIC8gX18gYC8gLyAvIF9fXy9cclxufCB8LyB8LyAvIC9fLyAvIC8gKF9fICApXHJcbnxfXy98X18vXFxfXyxfL18vXy9fX19fL1xyXG5UaGUgZWxlY3Ryb24gYWx0ZXJuYXRpdmUgZm9yIEdvXHJcbihjKSBMZWEgQW50aG9ueSAyMDE5LXByZXNlbnRcclxuKi9cclxuXHJcbi8qIGpzaGludCBlc3ZlcnNpb246IDkgKi9cclxuXHJcbmltcG9ydCB7bmV3UnVudGltZUNhbGxlcldpdGhJRCwgb2JqZWN0TmFtZXN9IGZyb20gXCIuL3J1bnRpbWVcIjtcclxuXHJcbmxldCBjYWxsID0gbmV3UnVudGltZUNhbGxlcldpdGhJRChvYmplY3ROYW1lcy5BcHBsaWNhdGlvbik7XHJcblxyXG5sZXQgbWV0aG9kcyA9IHtcclxuICAgIEhpZGU6IDAsXHJcbiAgICBTaG93OiAxLFxyXG4gICAgUXVpdDogMixcclxufVxyXG5cclxuLyoqXHJcbiAqIEhpZGUgdGhlIGFwcGxpY2F0aW9uXHJcbiAqL1xyXG5leHBvcnQgZnVuY3Rpb24gSGlkZSgpIHtcclxuICAgIHZvaWQgY2FsbChtZXRob2RzLkhpZGUpO1xyXG59XHJcblxyXG4vKipcclxuICogU2hvdyB0aGUgYXBwbGljYXRpb25cclxuICovXHJcbmV4cG9ydCBmdW5jdGlvbiBTaG93KCkge1xyXG4gICAgdm9pZCBjYWxsKG1ldGhvZHMuU2hvdyk7XHJcbn1cclxuXHJcblxyXG4vKipcclxuICogUXVpdCB0aGUgYXBwbGljYXRpb25cclxuICovXHJcbmV4cG9ydCBmdW5jdGlvbiBRdWl0KCkge1xyXG4gICAgdm9pZCBjYWxsKG1ldGhvZHMuUXVpdCk7XHJcbn0iLCAiLypcclxuIF9cdCAgIF9fXHQgIF8gX19cclxufCB8XHQgLyAvX19fIF8oXykgL19fX19cclxufCB8IC98IC8gLyBfXyBgLyAvIC8gX19fL1xyXG58IHwvIHwvIC8gL18vIC8gLyAoX18gIClcclxufF9fL3xfXy9cXF9fLF8vXy9fL19fX18vXHJcblRoZSBlbGVjdHJvbiBhbHRlcm5hdGl2ZSBmb3IgR29cclxuKGMpIExlYSBBbnRob255IDIwMTktcHJlc2VudFxyXG4qL1xyXG5cclxuLyoganNoaW50IGVzdmVyc2lvbjogOSAqL1xyXG5cclxuLyoqXHJcbiAqIEB0eXBlZGVmIHtpbXBvcnQoXCIuL2FwaS90eXBlc1wiKS5TY3JlZW59IFNjcmVlblxyXG4gKi9cclxuXHJcbmltcG9ydCB7bmV3UnVudGltZUNhbGxlcldpdGhJRCwgb2JqZWN0TmFtZXN9IGZyb20gXCIuL3J1bnRpbWVcIjtcclxuXHJcbmxldCBjYWxsID0gbmV3UnVudGltZUNhbGxlcldpdGhJRChvYmplY3ROYW1lcy5TY3JlZW5zKTtcclxuXHJcbmxldCBTY3JlZW5zR2V0QWxsID0gMDtcclxubGV0IFNjcmVlbnNHZXRQcmltYXJ5ID0gMTtcclxubGV0IFNjcmVlbnNHZXRDdXJyZW50ID0gMjtcclxuXHJcbi8qKlxyXG4gKiBHZXRzIGFsbCBzY3JlZW5zLlxyXG4gKiBAcmV0dXJucyB7UHJvbWlzZTxTY3JlZW5bXT59XHJcbiAqL1xyXG5leHBvcnQgZnVuY3Rpb24gR2V0QWxsKCkge1xyXG4gICAgcmV0dXJuIGNhbGwoU2NyZWVuc0dldEFsbCk7XHJcbn1cclxuXHJcbi8qKlxyXG4gKiBHZXRzIHRoZSBwcmltYXJ5IHNjcmVlbi5cclxuICogQHJldHVybnMge1Byb21pc2U8U2NyZWVuPn1cclxuICovXHJcbmV4cG9ydCBmdW5jdGlvbiBHZXRQcmltYXJ5KCkge1xyXG4gICAgcmV0dXJuIGNhbGwoU2NyZWVuc0dldFByaW1hcnkpO1xyXG59XHJcblxyXG4vKipcclxuICogR2V0cyB0aGUgY3VycmVudCBhY3RpdmUgc2NyZWVuLlxyXG4gKiBAcmV0dXJucyB7UHJvbWlzZTxTY3JlZW4+fVxyXG4gKiBAY29uc3RydWN0b3JcclxuICovXHJcbmV4cG9ydCBmdW5jdGlvbiBHZXRDdXJyZW50KCkge1xyXG4gICAgcmV0dXJuIGNhbGwoU2NyZWVuc0dldEN1cnJlbnQpO1xyXG59IiwgIi8qXHJcbiBfXHQgICBfX1x0ICBfIF9fXHJcbnwgfFx0IC8gL19fXyBfKF8pIC9fX19fXHJcbnwgfCAvfCAvIC8gX18gYC8gLyAvIF9fXy9cclxufCB8LyB8LyAvIC9fLyAvIC8gKF9fICApXHJcbnxfXy98X18vXFxfXyxfL18vXy9fX19fL1xyXG5UaGUgZWxlY3Ryb24gYWx0ZXJuYXRpdmUgZm9yIEdvXHJcbihjKSBMZWEgQW50aG9ueSAyMDE5LXByZXNlbnRcclxuKi9cclxuXHJcbi8qIGpzaGludCBlc3ZlcnNpb246IDkgKi9cclxuXHJcbmltcG9ydCB7bmV3UnVudGltZUNhbGxlcldpdGhJRCwgb2JqZWN0TmFtZXN9IGZyb20gXCIuL3J1bnRpbWVcIjtcclxuXHJcbmxldCBjYWxsID0gbmV3UnVudGltZUNhbGxlcldpdGhJRChvYmplY3ROYW1lcy5TeXN0ZW0pO1xyXG5cclxubGV0IFN5c3RlbUlzRGFya01vZGUgPSAwO1xyXG5cclxuLyoqXHJcbiAqIERldGVybWluZXMgaWYgdGhlIHN5c3RlbSBpcyBjdXJyZW50bHkgdXNpbmcgZGFyayBtb2RlXHJcbiAqIEByZXR1cm5zIHtQcm9taXNlPGJvb2xlYW4+fVxyXG4gKi9cclxuZXhwb3J0IGZ1bmN0aW9uIElzRGFya01vZGUoKSB7XHJcbiAgICByZXR1cm4gY2FsbChTeXN0ZW1Jc0RhcmtNb2RlKTtcclxufSIsICIvKlxyXG4gX1x0ICAgX19cdCAgXyBfX1xyXG58IHxcdCAvIC9fX18gXyhfKSAvX19fX1xyXG58IHwgL3wgLyAvIF9fIGAvIC8gLyBfX18vXHJcbnwgfC8gfC8gLyAvXy8gLyAvIChfXyAgKVxyXG58X18vfF9fL1xcX18sXy9fL18vX19fXy9cclxuVGhlIGVsZWN0cm9uIGFsdGVybmF0aXZlIGZvciBHb1xyXG4oYykgTGVhIEFudGhvbnkgMjAxOS1wcmVzZW50XHJcbiovXHJcblxyXG4vKiBqc2hpbnQgZXN2ZXJzaW9uOiA5ICovXHJcblxyXG5pbXBvcnQge25ld1J1bnRpbWVDYWxsZXJXaXRoSUQsIG9iamVjdE5hbWVzfSBmcm9tIFwiLi9ydW50aW1lXCI7XHJcblxyXG5sZXQgY2FsbCA9IG5ld1J1bnRpbWVDYWxsZXJXaXRoSUQob2JqZWN0TmFtZXMuQnJvd3Nlcik7XHJcblxyXG5sZXQgQnJvd3Nlck9wZW5VUkwgPSAwO1xyXG5cclxuLyoqXHJcbiAqIE9wZW4gYSBicm93c2VyIHdpbmRvdyB0byB0aGUgZ2l2ZW4gVVJMXHJcbiAqIEBwYXJhbSB7c3RyaW5nfSB1cmwgLSBUaGUgVVJMIHRvIG9wZW5cclxuICovXHJcbmV4cG9ydCBmdW5jdGlvbiBPcGVuVVJMKHVybCkge1xyXG4gICAgdm9pZCBjYWxsKEJyb3dzZXJPcGVuVVJMLCB7dXJsfSk7XHJcbn1cclxuIiwgIi8qXHJcbiBfXHQgICBfX1x0ICBfIF9fXHJcbnwgfFx0IC8gL19fXyBfKF8pIC9fX19fXHJcbnwgfCAvfCAvIC8gX18gYC8gLyAvIF9fXy9cclxufCB8LyB8LyAvIC9fLyAvIC8gKF9fICApXHJcbnxfXy98X18vXFxfXyxfL18vXy9fX19fL1xyXG5UaGUgZWxlY3Ryb24gYWx0ZXJuYXRpdmUgZm9yIEdvXHJcbihjKSBMZWEgQW50aG9ueSAyMDE5LXByZXNlbnRcclxuKi9cclxuXHJcbi8qIGpzaGludCBlc3ZlcnNpb246IDkgKi9cclxuXHJcbmltcG9ydCB7bmV3UnVudGltZUNhbGxlcldpdGhJRCwgb2JqZWN0TmFtZXN9IGZyb20gXCIuL3J1bnRpbWVcIjtcclxuXHJcbmltcG9ydCB7IG5hbm9pZCB9IGZyb20gJ25hbm9pZC9ub24tc2VjdXJlJztcclxuXHJcbmxldCBjYWxsID0gbmV3UnVudGltZUNhbGxlcldpdGhJRChvYmplY3ROYW1lcy5DYWxsKTtcclxuXHJcbmxldCBDYWxsQmluZGluZyA9IDA7XHJcblxyXG5sZXQgY2FsbFJlc3BvbnNlcyA9IG5ldyBNYXAoKTtcclxuXHJcbmZ1bmN0aW9uIGdlbmVyYXRlSUQoKSB7XHJcbiAgICBsZXQgcmVzdWx0O1xyXG4gICAgZG8ge1xyXG4gICAgICAgIHJlc3VsdCA9IG5hbm9pZCgpO1xyXG4gICAgfSB3aGlsZSAoY2FsbFJlc3BvbnNlcy5oYXMocmVzdWx0KSk7XHJcbiAgICByZXR1cm4gcmVzdWx0O1xyXG59XHJcblxyXG5leHBvcnQgZnVuY3Rpb24gY2FsbENhbGxiYWNrKGlkLCBkYXRhLCBpc0pTT04pIHtcclxuICAgIGxldCBwID0gY2FsbFJlc3BvbnNlcy5nZXQoaWQpO1xyXG4gICAgaWYgKHApIHtcclxuICAgICAgICBpZiAoaXNKU09OKSB7XHJcbiAgICAgICAgICAgIHAucmVzb2x2ZShKU09OLnBhcnNlKGRhdGEpKTtcclxuICAgICAgICB9IGVsc2Uge1xyXG4gICAgICAgICAgICBwLnJlc29sdmUoZGF0YSk7XHJcbiAgICAgICAgfVxyXG4gICAgICAgIGNhbGxSZXNwb25zZXMuZGVsZXRlKGlkKTtcclxuICAgIH1cclxufVxyXG5cclxuZXhwb3J0IGZ1bmN0aW9uIGNhbGxFcnJvckNhbGxiYWNrKGlkLCBtZXNzYWdlKSB7XHJcbiAgICBsZXQgcCA9IGNhbGxSZXNwb25zZXMuZ2V0KGlkKTtcclxuICAgIGlmIChwKSB7XHJcbiAgICAgICAgcC5yZWplY3QobWVzc2FnZSk7XHJcbiAgICAgICAgY2FsbFJlc3BvbnNlcy5kZWxldGUoaWQpO1xyXG4gICAgfVxyXG59XHJcblxyXG5mdW5jdGlvbiBjYWxsQmluZGluZyh0eXBlLCBvcHRpb25zKSB7XHJcbiAgICByZXR1cm4gbmV3IFByb21pc2UoKHJlc29sdmUsIHJlamVjdCkgPT4ge1xyXG4gICAgICAgIGxldCBpZCA9IGdlbmVyYXRlSUQoKTtcclxuICAgICAgICBvcHRpb25zID0gb3B0aW9ucyB8fCB7fTtcclxuICAgICAgICBvcHRpb25zW1wiY2FsbC1pZFwiXSA9IGlkO1xyXG5cclxuICAgICAgICBjYWxsUmVzcG9uc2VzLnNldChpZCwge3Jlc29sdmUsIHJlamVjdH0pO1xyXG4gICAgICAgIGNhbGwodHlwZSwgb3B0aW9ucykuY2F0Y2goKGVycm9yKSA9PiB7XHJcbiAgICAgICAgICAgIHJlamVjdChlcnJvcik7XHJcbiAgICAgICAgICAgIGNhbGxSZXNwb25zZXMuZGVsZXRlKGlkKTtcclxuICAgICAgICB9KTtcclxuICAgIH0pO1xyXG59XHJcblxyXG5leHBvcnQgZnVuY3Rpb24gQ2FsbChvcHRpb25zKSB7XHJcbiAgICByZXR1cm4gY2FsbEJpbmRpbmcoQ2FsbEJpbmRpbmcsIG9wdGlvbnMpO1xyXG59XHJcblxyXG5leHBvcnQgZnVuY3Rpb24gQ2FsbEJ5TmFtZShuYW1lLCAuLi5hcmdzKSB7XHJcblxyXG4gICAgLy8gRW5zdXJlIGZpcnN0IGFyZ3VtZW50IGlzIGEgc3RyaW5nIGFuZCBoYXMgMiBkb3RzXHJcbiAgICBpZiAodHlwZW9mIG5hbWUgIT09IFwic3RyaW5nXCIgfHwgbmFtZS5zcGxpdChcIi5cIikubGVuZ3RoICE9PSAzKSB7XHJcbiAgICAgICAgdGhyb3cgbmV3IEVycm9yKFwiQ2FsbEJ5TmFtZSByZXF1aXJlcyBhIHN0cmluZyBpbiB0aGUgZm9ybWF0ICdwYWNrYWdlLnN0cnVjdC5tZXRob2QnXCIpO1xyXG4gICAgfVxyXG4gICAgLy8gU3BsaXQgaW5wdXRzXHJcbiAgICBsZXQgcGFydHMgPSBuYW1lLnNwbGl0KFwiLlwiKTtcclxuXHJcbiAgICByZXR1cm4gY2FsbEJpbmRpbmcoQ2FsbEJpbmRpbmcsIHtcclxuICAgICAgICBwYWNrYWdlTmFtZTogcGFydHNbMF0sXHJcbiAgICAgICAgc3RydWN0TmFtZTogcGFydHNbMV0sXHJcbiAgICAgICAgbWV0aG9kTmFtZTogcGFydHNbMl0sXHJcbiAgICAgICAgYXJnczogYXJncyxcclxuICAgIH0pO1xyXG59XHJcblxyXG5leHBvcnQgZnVuY3Rpb24gQ2FsbEJ5SUQobWV0aG9kSUQsIC4uLmFyZ3MpIHtcclxuICAgIHJldHVybiBjYWxsQmluZGluZyhDYWxsQmluZGluZywge1xyXG4gICAgICAgIG1ldGhvZElEOiBtZXRob2RJRCxcclxuICAgICAgICBhcmdzOiBhcmdzLFxyXG4gICAgfSk7XHJcbn1cclxuXHJcbi8qKlxyXG4gKiBDYWxsIGEgcGx1Z2luIG1ldGhvZFxyXG4gKiBAcGFyYW0ge3N0cmluZ30gcGx1Z2luTmFtZSAtIG5hbWUgb2YgdGhlIHBsdWdpblxyXG4gKiBAcGFyYW0ge3N0cmluZ30gbWV0aG9kTmFtZSAtIG5hbWUgb2YgdGhlIG1ldGhvZFxyXG4gKiBAcGFyYW0gey4uLmFueX0gYXJncyAtIGFyZ3VtZW50cyB0byBwYXNzIHRvIHRoZSBtZXRob2RcclxuICogQHJldHVybnMge1Byb21pc2U8YW55Pn0gLSBwcm9taXNlIHRoYXQgcmVzb2x2ZXMgd2l0aCB0aGUgcmVzdWx0XHJcbiAqL1xyXG5leHBvcnQgZnVuY3Rpb24gUGx1Z2luKHBsdWdpbk5hbWUsIG1ldGhvZE5hbWUsIC4uLmFyZ3MpIHtcclxuICAgIHJldHVybiBjYWxsQmluZGluZyhDYWxsQmluZGluZywge1xyXG4gICAgICAgIHBhY2thZ2VOYW1lOiBcIndhaWxzLXBsdWdpbnNcIixcclxuICAgICAgICBzdHJ1Y3ROYW1lOiBwbHVnaW5OYW1lLFxyXG4gICAgICAgIG1ldGhvZE5hbWU6IG1ldGhvZE5hbWUsXHJcbiAgICAgICAgYXJnczogYXJncyxcclxuICAgIH0pO1xyXG59IiwgIi8qXHJcbiBfXHQgICBfX1x0ICBfIF9fXHJcbnwgfFx0IC8gL19fXyBfKF8pIC9fX19fXHJcbnwgfCAvfCAvIC8gX18gYC8gLyAvIF9fXy9cclxufCB8LyB8LyAvIC9fLyAvIC8gKF9fICApXHJcbnxfXy98X18vXFxfXyxfL18vXy9fX19fL1xyXG5UaGUgZWxlY3Ryb24gYWx0ZXJuYXRpdmUgZm9yIEdvXHJcbihjKSBMZWEgQW50aG9ueSAyMDE5LXByZXNlbnRcclxuKi9cclxuXHJcbi8qIGpzaGludCBlc3ZlcnNpb246IDkgKi9cclxuXHJcbi8qKlxyXG4gKiBAdHlwZWRlZiB7aW1wb3J0KFwiLi4vYXBpL3R5cGVzXCIpLlNpemV9IFNpemVcclxuICogQHR5cGVkZWYge2ltcG9ydChcIi4uL2FwaS90eXBlc1wiKS5Qb3NpdGlvbn0gUG9zaXRpb25cclxuICogQHR5cGVkZWYge2ltcG9ydChcIi4uL2FwaS90eXBlc1wiKS5TY3JlZW59IFNjcmVlblxyXG4gKi9cclxuXHJcbmltcG9ydCB7bmV3UnVudGltZUNhbGxlcldpdGhJRCwgb2JqZWN0TmFtZXN9IGZyb20gXCIuL3J1bnRpbWVcIjtcclxuXHJcbmxldCBXaW5kb3dDZW50ZXIgPSAwO1xyXG5sZXQgV2luZG93U2V0VGl0bGUgPSAxO1xyXG5sZXQgV2luZG93RnVsbHNjcmVlbiA9IDI7XHJcbmxldCBXaW5kb3dVbkZ1bGxzY3JlZW4gPSAzO1xyXG5sZXQgV2luZG93U2V0U2l6ZSA9IDQ7XHJcbmxldCBXaW5kb3dTaXplID0gNTtcclxubGV0IFdpbmRvd1NldE1heFNpemUgPSA2O1xyXG5sZXQgV2luZG93U2V0TWluU2l6ZSA9IDc7XHJcbmxldCBXaW5kb3dTZXRBbHdheXNPblRvcCA9IDg7XHJcbmxldCBXaW5kb3dTZXRSZWxhdGl2ZVBvc2l0aW9uID0gOTtcclxubGV0IFdpbmRvd1JlbGF0aXZlUG9zaXRpb24gPSAxMDtcclxubGV0IFdpbmRvd1NjcmVlbiA9IDExO1xyXG5sZXQgV2luZG93SGlkZSA9IDEyO1xyXG5sZXQgV2luZG93TWF4aW1pc2UgPSAxMztcclxubGV0IFdpbmRvd1VuTWF4aW1pc2UgPSAxNDtcclxubGV0IFdpbmRvd1RvZ2dsZU1heGltaXNlID0gMTU7XHJcbmxldCBXaW5kb3dNaW5pbWlzZSA9IDE2O1xyXG5sZXQgV2luZG93VW5NaW5pbWlzZSA9IDE3O1xyXG5sZXQgV2luZG93UmVzdG9yZSA9IDE4O1xyXG5sZXQgV2luZG93U2hvdyA9IDE5O1xyXG5sZXQgV2luZG93Q2xvc2UgPSAyMDtcclxubGV0IFdpbmRvd1NldEJhY2tncm91bmRDb2xvdXIgPSAyMTtcclxubGV0IFdpbmRvd1NldFJlc2l6YWJsZSA9IDIyO1xyXG5sZXQgV2luZG93V2lkdGggPSAyMztcclxubGV0IFdpbmRvd0hlaWdodCA9IDI0O1xyXG5sZXQgV2luZG93Wm9vbUluID0gMjU7XHJcbmxldCBXaW5kb3dab29tT3V0ID0gMjY7XHJcbmxldCBXaW5kb3dab29tUmVzZXQgPSAyNztcclxubGV0IFdpbmRvd0dldFpvb21MZXZlbCA9IDI4O1xyXG5sZXQgV2luZG93U2V0Wm9vbUxldmVsID0gMjk7XHJcblxyXG5leHBvcnQgZnVuY3Rpb24gbmV3V2luZG93KHdpbmRvd05hbWUpIHtcclxuICAgIGxldCBjYWxsID0gbmV3UnVudGltZUNhbGxlcldpdGhJRChvYmplY3ROYW1lcy5XaW5kb3csIHdpbmRvd05hbWUpO1xyXG4gICAgcmV0dXJuIHtcclxuXHJcbiAgICAgICAgLyoqXHJcbiAgICAgICAgICogQ2VudGVycyB0aGUgd2luZG93LlxyXG4gICAgICAgICAqL1xyXG4gICAgICAgIENlbnRlcjogKCkgPT4gdm9pZCBjYWxsKFdpbmRvd0NlbnRlciksXHJcblxyXG4gICAgICAgIC8qKlxyXG4gICAgICAgICAqIFNldCB0aGUgd2luZG93IHRpdGxlLlxyXG4gICAgICAgICAqIEBwYXJhbSB0aXRsZVxyXG4gICAgICAgICAqL1xyXG4gICAgICAgIFNldFRpdGxlOiAodGl0bGUpID0+IHZvaWQgY2FsbChXaW5kb3dTZXRUaXRsZSwge3RpdGxlfSksXHJcblxyXG4gICAgICAgIC8qKlxyXG4gICAgICAgICAqIE1ha2VzIHRoZSB3aW5kb3cgZnVsbHNjcmVlbi5cclxuICAgICAgICAgKi9cclxuICAgICAgICBGdWxsc2NyZWVuOiAoKSA9PiB2b2lkIGNhbGwoV2luZG93RnVsbHNjcmVlbiksXHJcblxyXG4gICAgICAgIC8qKlxyXG4gICAgICAgICAqIFVuZnVsbHNjcmVlbiB0aGUgd2luZG93LlxyXG4gICAgICAgICAqL1xyXG4gICAgICAgIFVuRnVsbHNjcmVlbjogKCkgPT4gdm9pZCBjYWxsKFdpbmRvd1VuRnVsbHNjcmVlbiksXHJcblxyXG4gICAgICAgIC8qKlxyXG4gICAgICAgICAqIFNldCB0aGUgd2luZG93IHNpemUuXHJcbiAgICAgICAgICogQHBhcmFtIHtudW1iZXJ9IHdpZHRoIFRoZSB3aW5kb3cgd2lkdGhcclxuICAgICAgICAgKiBAcGFyYW0ge251bWJlcn0gaGVpZ2h0IFRoZSB3aW5kb3cgaGVpZ2h0XHJcbiAgICAgICAgICovXHJcbiAgICAgICAgU2V0U2l6ZTogKHdpZHRoLCBoZWlnaHQpID0+IGNhbGwoV2luZG93U2V0U2l6ZSwge3dpZHRoLGhlaWdodH0pLFxyXG5cclxuICAgICAgICAvKipcclxuICAgICAgICAgKiBHZXQgdGhlIHdpbmRvdyBzaXplLlxyXG4gICAgICAgICAqIEByZXR1cm5zIHtQcm9taXNlPFNpemU+fSBUaGUgd2luZG93IHNpemVcclxuICAgICAgICAgKi9cclxuICAgICAgICBTaXplOiAoKSA9PiB7IHJldHVybiBjYWxsKFdpbmRvd1NpemUpOyB9LFxyXG5cclxuICAgICAgICAvKipcclxuICAgICAgICAgKiBTZXQgdGhlIHdpbmRvdyBtYXhpbXVtIHNpemUuXHJcbiAgICAgICAgICogQHBhcmFtIHtudW1iZXJ9IHdpZHRoXHJcbiAgICAgICAgICogQHBhcmFtIHtudW1iZXJ9IGhlaWdodFxyXG4gICAgICAgICAqL1xyXG4gICAgICAgIFNldE1heFNpemU6ICh3aWR0aCwgaGVpZ2h0KSA9PiB2b2lkIGNhbGwoV2luZG93U2V0TWF4U2l6ZSwge3dpZHRoLGhlaWdodH0pLFxyXG5cclxuICAgICAgICAvKipcclxuICAgICAgICAgKiBTZXQgdGhlIHdpbmRvdyBtaW5pbXVtIHNpemUuXHJcbiAgICAgICAgICogQHBhcmFtIHtudW1iZXJ9IHdpZHRoXHJcbiAgICAgICAgICogQHBhcmFtIHtudW1iZXJ9IGhlaWdodFxyXG4gICAgICAgICAqL1xyXG4gICAgICAgIFNldE1pblNpemU6ICh3aWR0aCwgaGVpZ2h0KSA9PiB2b2lkIGNhbGwoV2luZG93U2V0TWluU2l6ZSwge3dpZHRoLGhlaWdodH0pLFxyXG5cclxuICAgICAgICAvKipcclxuICAgICAgICAgKiBTZXQgd2luZG93IHRvIGJlIGFsd2F5cyBvbiB0b3AuXHJcbiAgICAgICAgICogQHBhcmFtIHtib29sZWFufSBvblRvcCBXaGV0aGVyIHRoZSB3aW5kb3cgc2hvdWxkIGJlIGFsd2F5cyBvbiB0b3BcclxuICAgICAgICAgKi9cclxuICAgICAgICBTZXRBbHdheXNPblRvcDogKG9uVG9wKSA9PiB2b2lkIGNhbGwoV2luZG93U2V0QWx3YXlzT25Ub3AsIHthbHdheXNPblRvcDpvblRvcH0pLFxyXG5cclxuICAgICAgICAvKipcclxuICAgICAgICAgKiBTZXQgdGhlIHdpbmRvdyByZWxhdGl2ZSBwb3NpdGlvbi5cclxuICAgICAgICAgKiBAcGFyYW0ge251bWJlcn0geFxyXG4gICAgICAgICAqIEBwYXJhbSB7bnVtYmVyfSB5XHJcbiAgICAgICAgICovXHJcbiAgICAgICAgU2V0UmVsYXRpdmVQb3NpdGlvbjogKHgsIHkpID0+IGNhbGwoV2luZG93U2V0UmVsYXRpdmVQb3NpdGlvbiwge3gseX0pLFxyXG5cclxuICAgICAgICAvKipcclxuICAgICAgICAgKiBHZXQgdGhlIHdpbmRvdyBwb3NpdGlvbi5cclxuICAgICAgICAgKiBAcmV0dXJucyB7UHJvbWlzZTxQb3NpdGlvbj59IFRoZSB3aW5kb3cgcG9zaXRpb25cclxuICAgICAgICAgKi9cclxuICAgICAgICBSZWxhdGl2ZVBvc2l0aW9uOiAoKSA9PiB7IHJldHVybiBjYWxsKFdpbmRvd1JlbGF0aXZlUG9zaXRpb24pOyB9LFxyXG5cclxuICAgICAgICAvKipcclxuICAgICAgICAgKiBHZXQgdGhlIHNjcmVlbiB0aGUgd2luZG93IGlzIG9uLlxyXG4gICAgICAgICAqIEByZXR1cm5zIHtQcm9taXNlPFNjcmVlbj59XHJcbiAgICAgICAgICovXHJcbiAgICAgICAgU2NyZWVuOiAoKSA9PiB7IHJldHVybiBjYWxsKFdpbmRvd1NjcmVlbik7IH0sXHJcblxyXG4gICAgICAgIC8qKlxyXG4gICAgICAgICAqIEhpZGUgdGhlIHdpbmRvd1xyXG4gICAgICAgICAqL1xyXG4gICAgICAgIEhpZGU6ICgpID0+IHZvaWQgY2FsbChXaW5kb3dIaWRlKSxcclxuXHJcbiAgICAgICAgLyoqXHJcbiAgICAgICAgICogTWF4aW1pc2UgdGhlIHdpbmRvd1xyXG4gICAgICAgICAqL1xyXG4gICAgICAgIE1heGltaXNlOiAoKSA9PiB2b2lkIGNhbGwoV2luZG93TWF4aW1pc2UpLFxyXG5cclxuICAgICAgICAvKipcclxuICAgICAgICAgKiBTaG93IHRoZSB3aW5kb3dcclxuICAgICAgICAgKi9cclxuICAgICAgICBTaG93OiAoKSA9PiB2b2lkIGNhbGwoV2luZG93U2hvdyksXHJcblxyXG4gICAgICAgIC8qKlxyXG4gICAgICAgICAqIENsb3NlIHRoZSB3aW5kb3dcclxuICAgICAgICAgKi9cclxuICAgICAgICBDbG9zZTogKCkgPT4gdm9pZCBjYWxsKFdpbmRvd0Nsb3NlKSxcclxuXHJcbiAgICAgICAgLyoqXHJcbiAgICAgICAgICogVG9nZ2xlIHRoZSB3aW5kb3cgbWF4aW1pc2Ugc3RhdGVcclxuICAgICAgICAgKi9cclxuICAgICAgICBUb2dnbGVNYXhpbWlzZTogKCkgPT4gdm9pZCBjYWxsKFdpbmRvd1RvZ2dsZU1heGltaXNlKSxcclxuXHJcbiAgICAgICAgLyoqXHJcbiAgICAgICAgICogVW5tYXhpbWlzZSB0aGUgd2luZG93XHJcbiAgICAgICAgICovXHJcbiAgICAgICAgVW5NYXhpbWlzZTogKCkgPT4gdm9pZCBjYWxsKFdpbmRvd1VuTWF4aW1pc2UpLFxyXG5cclxuICAgICAgICAvKipcclxuICAgICAgICAgKiBNaW5pbWlzZSB0aGUgd2luZG93XHJcbiAgICAgICAgICovXHJcbiAgICAgICAgTWluaW1pc2U6ICgpID0+IHZvaWQgY2FsbChXaW5kb3dNaW5pbWlzZSksXHJcblxyXG4gICAgICAgIC8qKlxyXG4gICAgICAgICAqIFVubWluaW1pc2UgdGhlIHdpbmRvd1xyXG4gICAgICAgICAqL1xyXG4gICAgICAgIFVuTWluaW1pc2U6ICgpID0+IHZvaWQgY2FsbChXaW5kb3dVbk1pbmltaXNlKSxcclxuXHJcbiAgICAgICAgLyoqXHJcbiAgICAgICAgICogUmVzdG9yZSB0aGUgd2luZG93XHJcbiAgICAgICAgICovXHJcbiAgICAgICAgUmVzdG9yZTogKCkgPT4gdm9pZCBjYWxsKFdpbmRvd1Jlc3RvcmUpLFxyXG5cclxuICAgICAgICAvKipcclxuICAgICAgICAgKiBTZXQgdGhlIGJhY2tncm91bmQgY29sb3VyIG9mIHRoZSB3aW5kb3cuXHJcbiAgICAgICAgICogQHBhcmFtIHtudW1iZXJ9IHIgLSBBIHZhbHVlIGJldHdlZW4gMCBhbmQgMjU1XHJcbiAgICAgICAgICogQHBhcmFtIHtudW1iZXJ9IGcgLSBBIHZhbHVlIGJldHdlZW4gMCBhbmQgMjU1XHJcbiAgICAgICAgICogQHBhcmFtIHtudW1iZXJ9IGIgLSBBIHZhbHVlIGJldHdlZW4gMCBhbmQgMjU1XHJcbiAgICAgICAgICogQHBhcmFtIHtudW1iZXJ9IGEgLSBBIHZhbHVlIGJldHdlZW4gMCBhbmQgMjU1XHJcbiAgICAgICAgICovXHJcbiAgICAgICAgU2V0QmFja2dyb3VuZENvbG91cjogKHIsIGcsIGIsIGEpID0+IHZvaWQgY2FsbChXaW5kb3dTZXRCYWNrZ3JvdW5kQ29sb3VyLCB7ciwgZywgYiwgYX0pLFxyXG5cclxuICAgICAgICAvKipcclxuICAgICAgICAgKiBTZXQgd2hldGhlciB0aGUgd2luZG93IGNhbiBiZSByZXNpemVkIG9yIG5vdFxyXG4gICAgICAgICAqIEBwYXJhbSB7Ym9vbGVhbn0gcmVzaXphYmxlXHJcbiAgICAgICAgICovXHJcbiAgICAgICAgU2V0UmVzaXphYmxlOiAocmVzaXphYmxlKSA9PiB2b2lkIGNhbGwoV2luZG93U2V0UmVzaXphYmxlLCB7cmVzaXphYmxlfSksXHJcblxyXG4gICAgICAgIC8qKlxyXG4gICAgICAgICAqIEdldCB0aGUgd2luZG93IHdpZHRoXHJcbiAgICAgICAgICogQHJldHVybnMge1Byb21pc2U8bnVtYmVyPn1cclxuICAgICAgICAgKi9cclxuICAgICAgICBXaWR0aDogKCkgPT4geyByZXR1cm4gY2FsbChXaW5kb3dXaWR0aCk7IH0sXHJcblxyXG4gICAgICAgIC8qKlxyXG4gICAgICAgICAqIEdldCB0aGUgd2luZG93IGhlaWdodFxyXG4gICAgICAgICAqIEByZXR1cm5zIHtQcm9taXNlPG51bWJlcj59XHJcbiAgICAgICAgICovXHJcbiAgICAgICAgSGVpZ2h0OiAoKSA9PiB7IHJldHVybiBjYWxsKFdpbmRvd0hlaWdodCk7IH0sXHJcblxyXG4gICAgICAgIC8qKlxyXG4gICAgICAgICAqIFpvb20gaW4gdGhlIHdpbmRvd1xyXG4gICAgICAgICAqL1xyXG4gICAgICAgIFpvb21JbjogKCkgPT4gdm9pZCBjYWxsKFdpbmRvd1pvb21JbiksXHJcblxyXG4gICAgICAgIC8qKlxyXG4gICAgICAgICAqIFpvb20gb3V0IHRoZSB3aW5kb3dcclxuICAgICAgICAgKi9cclxuICAgICAgICBab29tT3V0OiAoKSA9PiB2b2lkIGNhbGwoV2luZG93Wm9vbU91dCksXHJcblxyXG4gICAgICAgIC8qKlxyXG4gICAgICAgICAqIFJlc2V0IHRoZSB3aW5kb3cgem9vbVxyXG4gICAgICAgICAqL1xyXG4gICAgICAgIFpvb21SZXNldDogKCkgPT4gdm9pZCBjYWxsKFdpbmRvd1pvb21SZXNldCksXHJcblxyXG4gICAgICAgIC8qKlxyXG4gICAgICAgICAqIEdldCB0aGUgd2luZG93IHpvb21cclxuICAgICAgICAgKiBAcmV0dXJucyB7UHJvbWlzZTxudW1iZXI+fVxyXG4gICAgICAgICAqL1xyXG4gICAgICAgIEdldFpvb21MZXZlbDogKCkgPT4geyByZXR1cm4gY2FsbChXaW5kb3dHZXRab29tTGV2ZWwpOyB9LFxyXG5cclxuICAgICAgICAvKipcclxuICAgICAgICAgKiBTZXQgdGhlIHdpbmRvdyB6b29tIGxldmVsXHJcbiAgICAgICAgICogQHBhcmFtIHtudW1iZXJ9IHpvb21MZXZlbFxyXG4gICAgICAgICAqL1xyXG4gICAgICAgIFNldFpvb21MZXZlbDogKHpvb21MZXZlbCkgPT4gdm9pZCBjYWxsKFdpbmRvd1NldFpvb21MZXZlbCwge3pvb21MZXZlbH0pLFxyXG4gICAgfTtcclxufVxyXG4iLCAiLypcclxuIF9cdCAgIF9fXHQgIF8gX19cclxufCB8XHQgLyAvX19fIF8oXykgL19fX19cclxufCB8IC98IC8gLyBfXyBgLyAvIC8gX19fL1xyXG58IHwvIHwvIC8gL18vIC8gLyAoX18gIClcclxufF9fL3xfXy9cXF9fLF8vXy9fL19fX18vXHJcblRoZSBlbGVjdHJvbiBhbHRlcm5hdGl2ZSBmb3IgR29cclxuKGMpIExlYSBBbnRob255IDIwMTktcHJlc2VudFxyXG4qL1xyXG5cclxuLyoganNoaW50IGVzdmVyc2lvbjogOSAqL1xyXG5cclxuLyoqXHJcbiAqIEB0eXBlZGVmIHtpbXBvcnQoXCIuL2FwaS90eXBlc1wiKS5XYWlsc0V2ZW50fSBXYWlsc0V2ZW50XHJcbiAqL1xyXG5cclxuaW1wb3J0IHtuZXdSdW50aW1lQ2FsbGVyV2l0aElELCBvYmplY3ROYW1lc30gZnJvbSBcIi4vcnVudGltZVwiO1xyXG5cclxubGV0IGNhbGwgPSBuZXdSdW50aW1lQ2FsbGVyV2l0aElEKG9iamVjdE5hbWVzLkV2ZW50cyk7XHJcbmxldCBFdmVudEVtaXQgPSAwO1xyXG5cclxuLyoqXHJcbiAqIFRoZSBMaXN0ZW5lciBjbGFzcyBkZWZpbmVzIGEgbGlzdGVuZXIhIDotKVxyXG4gKlxyXG4gKiBAY2xhc3MgTGlzdGVuZXJcclxuICovXHJcbmNsYXNzIExpc3RlbmVyIHtcclxuICAgIC8qKlxyXG4gICAgICogQ3JlYXRlcyBhbiBpbnN0YW5jZSBvZiBMaXN0ZW5lci5cclxuICAgICAqIEBwYXJhbSB7c3RyaW5nfSBldmVudE5hbWVcclxuICAgICAqIEBwYXJhbSB7ZnVuY3Rpb259IGNhbGxiYWNrXHJcbiAgICAgKiBAcGFyYW0ge251bWJlcn0gbWF4Q2FsbGJhY2tzXHJcbiAgICAgKiBAbWVtYmVyb2YgTGlzdGVuZXJcclxuICAgICAqL1xyXG4gICAgY29uc3RydWN0b3IoZXZlbnROYW1lLCBjYWxsYmFjaywgbWF4Q2FsbGJhY2tzKSB7XHJcbiAgICAgICAgdGhpcy5ldmVudE5hbWUgPSBldmVudE5hbWU7XHJcbiAgICAgICAgLy8gRGVmYXVsdCBvZiAtMSBtZWFucyBpbmZpbml0ZVxyXG4gICAgICAgIHRoaXMubWF4Q2FsbGJhY2tzID0gbWF4Q2FsbGJhY2tzIHx8IC0xO1xyXG4gICAgICAgIC8vIENhbGxiYWNrIGludm9rZXMgdGhlIGNhbGxiYWNrIHdpdGggdGhlIGdpdmVuIGRhdGFcclxuICAgICAgICAvLyBSZXR1cm5zIHRydWUgaWYgdGhpcyBsaXN0ZW5lciBzaG91bGQgYmUgZGVzdHJveWVkXHJcbiAgICAgICAgdGhpcy5DYWxsYmFjayA9IChkYXRhKSA9PiB7XHJcbiAgICAgICAgICAgIGNhbGxiYWNrKGRhdGEpO1xyXG4gICAgICAgICAgICAvLyBJZiBtYXhDYWxsYmFja3MgaXMgaW5maW5pdGUsIHJldHVybiBmYWxzZSAoZG8gbm90IGRlc3Ryb3kpXHJcbiAgICAgICAgICAgIGlmICh0aGlzLm1heENhbGxiYWNrcyA9PT0gLTEpIHtcclxuICAgICAgICAgICAgICAgIHJldHVybiBmYWxzZTtcclxuICAgICAgICAgICAgfVxyXG4gICAgICAgICAgICAvLyBEZWNyZW1lbnQgbWF4Q2FsbGJhY2tzLiBSZXR1cm4gdHJ1ZSBpZiBub3cgMCwgb3RoZXJ3aXNlIGZhbHNlXHJcbiAgICAgICAgICAgIHRoaXMubWF4Q2FsbGJhY2tzIC09IDE7XHJcbiAgICAgICAgICAgIHJldHVybiB0aGlzLm1heENhbGxiYWNrcyA9PT0gMDtcclxuICAgICAgICB9O1xyXG4gICAgfVxyXG59XHJcblxyXG5cclxuLyoqXHJcbiAqIFdhaWxzRXZlbnQgZGVmaW5lcyBhIGN1c3RvbSBldmVudC4gSXQgaXMgcGFzc2VkIHRvIGV2ZW50IGxpc3RlbmVycy5cclxuICpcclxuICogQGNsYXNzIFdhaWxzRXZlbnRcclxuICogQHByb3BlcnR5IHtzdHJpbmd9IG5hbWUgLSBOYW1lIG9mIHRoZSBldmVudFxyXG4gKiBAcHJvcGVydHkge2FueX0gZGF0YSAtIERhdGEgYXNzb2NpYXRlZCB3aXRoIHRoZSBldmVudFxyXG4gKi9cclxuZXhwb3J0IGNsYXNzIFdhaWxzRXZlbnQge1xyXG4gICAgLyoqXHJcbiAgICAgKiBDcmVhdGVzIGFuIGluc3RhbmNlIG9mIFdhaWxzRXZlbnQuXHJcbiAgICAgKiBAcGFyYW0ge3N0cmluZ30gbmFtZSAtIE5hbWUgb2YgdGhlIGV2ZW50XHJcbiAgICAgKiBAcGFyYW0ge2FueT1udWxsfSBkYXRhIC0gRGF0YSBhc3NvY2lhdGVkIHdpdGggdGhlIGV2ZW50XHJcbiAgICAgKiBAbWVtYmVyb2YgV2FpbHNFdmVudFxyXG4gICAgICovXHJcbiAgICBjb25zdHJ1Y3RvcihuYW1lLCBkYXRhID0gbnVsbCkge1xyXG4gICAgICAgIHRoaXMubmFtZSA9IG5hbWU7XHJcbiAgICAgICAgdGhpcy5kYXRhID0gZGF0YTtcclxuICAgIH1cclxufVxyXG5cclxuZXhwb3J0IGNvbnN0IGV2ZW50TGlzdGVuZXJzID0gbmV3IE1hcCgpO1xyXG5cclxuLyoqXHJcbiAqIFJlZ2lzdGVycyBhbiBldmVudCBsaXN0ZW5lciB0aGF0IHdpbGwgYmUgaW52b2tlZCBgbWF4Q2FsbGJhY2tzYCB0aW1lcyBiZWZvcmUgYmVpbmcgZGVzdHJveWVkXHJcbiAqXHJcbiAqIEBleHBvcnRcclxuICogQHBhcmFtIHtzdHJpbmd9IGV2ZW50TmFtZVxyXG4gKiBAcGFyYW0ge2Z1bmN0aW9uKFdhaWxzRXZlbnQpOiB2b2lkfSBjYWxsYmFja1xyXG4gKiBAcGFyYW0ge251bWJlcn0gbWF4Q2FsbGJhY2tzXHJcbiAqIEByZXR1cm5zIHtmdW5jdGlvbn0gQSBmdW5jdGlvbiB0byBjYW5jZWwgdGhlIGxpc3RlbmVyXHJcbiAqL1xyXG5leHBvcnQgZnVuY3Rpb24gT25NdWx0aXBsZShldmVudE5hbWUsIGNhbGxiYWNrLCBtYXhDYWxsYmFja3MpIHtcclxuICAgIGxldCBsaXN0ZW5lcnMgPSBldmVudExpc3RlbmVycy5nZXQoZXZlbnROYW1lKSB8fCBbXTtcclxuICAgIGNvbnN0IHRoaXNMaXN0ZW5lciA9IG5ldyBMaXN0ZW5lcihldmVudE5hbWUsIGNhbGxiYWNrLCBtYXhDYWxsYmFja3MpO1xyXG4gICAgbGlzdGVuZXJzLnB1c2godGhpc0xpc3RlbmVyKTtcclxuICAgIGV2ZW50TGlzdGVuZXJzLnNldChldmVudE5hbWUsIGxpc3RlbmVycyk7XHJcbiAgICByZXR1cm4gKCkgPT4gbGlzdGVuZXJPZmYodGhpc0xpc3RlbmVyKTtcclxufVxyXG5cclxuLyoqXHJcbiAqIFJlZ2lzdGVycyBhbiBldmVudCBsaXN0ZW5lciB0aGF0IHdpbGwgYmUgaW52b2tlZCBldmVyeSB0aW1lIHRoZSBldmVudCBpcyBlbWl0dGVkXHJcbiAqXHJcbiAqIEBleHBvcnRcclxuICogQHBhcmFtIHtzdHJpbmd9IGV2ZW50TmFtZVxyXG4gKiBAcGFyYW0ge2Z1bmN0aW9uKFdhaWxzRXZlbnQpOiB2b2lkfSBjYWxsYmFja1xyXG4gKiBAcmV0dXJucyB7ZnVuY3Rpb259IEEgZnVuY3Rpb24gdG8gY2FuY2VsIHRoZSBsaXN0ZW5lclxyXG4gKi9cclxuZXhwb3J0IGZ1bmN0aW9uIE9uKGV2ZW50TmFtZSwgY2FsbGJhY2spIHtcclxuICAgIHJldHVybiBPbk11bHRpcGxlKGV2ZW50TmFtZSwgY2FsbGJhY2ssIC0xKTtcclxufVxyXG5cclxuLyoqXHJcbiAqIFJlZ2lzdGVycyBhbiBldmVudCBsaXN0ZW5lciB0aGF0IHdpbGwgYmUgaW52b2tlZCBvbmNlIHRoZW4gZGVzdHJveWVkXHJcbiAqXHJcbiAqIEBleHBvcnRcclxuICogQHBhcmFtIHtzdHJpbmd9IGV2ZW50TmFtZVxyXG4gKiBAcGFyYW0ge2Z1bmN0aW9uKFdhaWxzRXZlbnQpOiB2b2lkfSBjYWxsYmFja1xyXG4gQHJldHVybnMge2Z1bmN0aW9ufSBBIGZ1bmN0aW9uIHRvIGNhbmNlbCB0aGUgbGlzdGVuZXJcclxuICovXHJcbmV4cG9ydCBmdW5jdGlvbiBPbmNlKGV2ZW50TmFtZSwgY2FsbGJhY2spIHtcclxuICAgIHJldHVybiBPbk11bHRpcGxlKGV2ZW50TmFtZSwgY2FsbGJhY2ssIDEpO1xyXG59XHJcblxyXG4vKipcclxuICogbGlzdGVuZXJPZmYgdW5yZWdpc3RlcnMgYSBsaXN0ZW5lciBwcmV2aW91c2x5IHJlZ2lzdGVyZWQgd2l0aCBPblxyXG4gKlxyXG4gKiBAcGFyYW0ge0xpc3RlbmVyfSBsaXN0ZW5lclxyXG4gKi9cclxuZnVuY3Rpb24gbGlzdGVuZXJPZmYobGlzdGVuZXIpIHtcclxuICAgIGNvbnN0IGV2ZW50TmFtZSA9IGxpc3RlbmVyLmV2ZW50TmFtZTtcclxuICAgIC8vIFJlbW92ZSBsb2NhbCBsaXN0ZW5lclxyXG4gICAgbGV0IGxpc3RlbmVycyA9IGV2ZW50TGlzdGVuZXJzLmdldChldmVudE5hbWUpLmZpbHRlcihsID0+IGwgIT09IGxpc3RlbmVyKTtcclxuICAgIGlmIChsaXN0ZW5lcnMubGVuZ3RoID09PSAwKSB7XHJcbiAgICAgICAgZXZlbnRMaXN0ZW5lcnMuZGVsZXRlKGV2ZW50TmFtZSk7XHJcbiAgICB9IGVsc2Uge1xyXG4gICAgICAgIGV2ZW50TGlzdGVuZXJzLnNldChldmVudE5hbWUsIGxpc3RlbmVycyk7XHJcbiAgICB9XHJcbn1cclxuXHJcbi8qKlxyXG4gKiBkaXNwYXRjaGVzIGFuIGV2ZW50IHRvIGFsbCBsaXN0ZW5lcnNcclxuICpcclxuICogQGV4cG9ydFxyXG4gKiBAcGFyYW0ge1dhaWxzRXZlbnR9IGV2ZW50XHJcbiAqL1xyXG5leHBvcnQgZnVuY3Rpb24gZGlzcGF0Y2hXYWlsc0V2ZW50KGV2ZW50KSB7XHJcbiAgICBsZXQgbGlzdGVuZXJzID0gZXZlbnRMaXN0ZW5lcnMuZ2V0KGV2ZW50Lm5hbWUpO1xyXG4gICAgaWYgKGxpc3RlbmVycykge1xyXG4gICAgICAgIC8vIGl0ZXJhdGUgbGlzdGVuZXJzIGFuZCBjYWxsIGNhbGxiYWNrLiBJZiBjYWxsYmFjayByZXR1cm5zIHRydWUsIHJlbW92ZSBsaXN0ZW5lclxyXG4gICAgICAgIGxldCB0b1JlbW92ZSA9IFtdO1xyXG4gICAgICAgIGxpc3RlbmVycy5mb3JFYWNoKGxpc3RlbmVyID0+IHtcclxuICAgICAgICAgICAgbGV0IHJlbW92ZSA9IGxpc3RlbmVyLkNhbGxiYWNrKGV2ZW50KTtcclxuICAgICAgICAgICAgaWYgKHJlbW92ZSkge1xyXG4gICAgICAgICAgICAgICAgdG9SZW1vdmUucHVzaChsaXN0ZW5lcik7XHJcbiAgICAgICAgICAgIH1cclxuICAgICAgICB9KTtcclxuICAgICAgICAvLyByZW1vdmUgbGlzdGVuZXJzXHJcbiAgICAgICAgaWYgKHRvUmVtb3ZlLmxlbmd0aCA+IDApIHtcclxuICAgICAgICAgICAgbGlzdGVuZXJzID0gbGlzdGVuZXJzLmZpbHRlcihsID0+ICF0b1JlbW92ZS5pbmNsdWRlcyhsKSk7XHJcbiAgICAgICAgICAgIGlmIChsaXN0ZW5lcnMubGVuZ3RoID09PSAwKSB7XHJcbiAgICAgICAgICAgICAgICBldmVudExpc3RlbmVycy5kZWxldGUoZXZlbnQubmFtZSk7XHJcbiAgICAgICAgICAgIH0gZWxzZSB7XHJcbiAgICAgICAgICAgICAgICBldmVudExpc3RlbmVycy5zZXQoZXZlbnQubmFtZSwgbGlzdGVuZXJzKTtcclxuICAgICAgICAgICAgfVxyXG4gICAgICAgIH1cclxuICAgIH1cclxufVxyXG5cclxuLyoqXHJcbiAqIE9mZiB1bnJlZ2lzdGVycyBhIGxpc3RlbmVyIHByZXZpb3VzbHkgcmVnaXN0ZXJlZCB3aXRoIE9uLFxyXG4gKiBvcHRpb25hbGx5IG11bHRpcGxlIGxpc3RlbmVycyBjYW4gYmUgdW5yZWdpc3RlcmVkIHZpYSBgYWRkaXRpb25hbEV2ZW50TmFtZXNgXHJcbiAqXHJcbiBbdjMgQ0hBTkdFXSBPZmYgb25seSB1bnJlZ2lzdGVycyBsaXN0ZW5lcnMgd2l0aGluIHRoZSBjdXJyZW50IHdpbmRvd1xyXG4gKlxyXG4gKiBAcGFyYW0ge3N0cmluZ30gZXZlbnROYW1lXHJcbiAqIEBwYXJhbSAgey4uLnN0cmluZ30gYWRkaXRpb25hbEV2ZW50TmFtZXNcclxuICovXHJcbmV4cG9ydCBmdW5jdGlvbiBPZmYoZXZlbnROYW1lLCAuLi5hZGRpdGlvbmFsRXZlbnROYW1lcykge1xyXG4gICAgbGV0IGV2ZW50c1RvUmVtb3ZlID0gW2V2ZW50TmFtZSwgLi4uYWRkaXRpb25hbEV2ZW50TmFtZXNdO1xyXG4gICAgZXZlbnRzVG9SZW1vdmUuZm9yRWFjaChldmVudE5hbWUgPT4ge1xyXG4gICAgICAgIGV2ZW50TGlzdGVuZXJzLmRlbGV0ZShldmVudE5hbWUpO1xyXG4gICAgfSk7XHJcbn1cclxuXHJcbi8qKlxyXG4gKiBPZmZBbGwgdW5yZWdpc3RlcnMgYWxsIGxpc3RlbmVyc1xyXG4gKiBbdjMgQ0hBTkdFXSBPZmZBbGwgb25seSB1bnJlZ2lzdGVycyBsaXN0ZW5lcnMgd2l0aGluIHRoZSBjdXJyZW50IHdpbmRvd1xyXG4gKlxyXG4gKi9cclxuZXhwb3J0IGZ1bmN0aW9uIE9mZkFsbCgpIHtcclxuICAgIGV2ZW50TGlzdGVuZXJzLmNsZWFyKCk7XHJcbn1cclxuXHJcbi8qKlxyXG4gKiBFbWl0IGFuIGV2ZW50XHJcbiAqIEBwYXJhbSB7V2FpbHNFdmVudH0gZXZlbnQgVGhlIGV2ZW50IHRvIGVtaXRcclxuICovXHJcbmV4cG9ydCBmdW5jdGlvbiBFbWl0KGV2ZW50KSB7XHJcbiAgICB2b2lkIGNhbGwoRXZlbnRFbWl0LCBldmVudCk7XHJcbn0iLCAiLypcclxuIF9cdCAgIF9fXHQgIF8gX19cclxufCB8XHQgLyAvX19fIF8oXykgL19fX19cclxufCB8IC98IC8gLyBfXyBgLyAvIC8gX19fL1xyXG58IHwvIHwvIC8gL18vIC8gLyAoX18gIClcclxufF9fL3xfXy9cXF9fLF8vXy9fL19fX18vXHJcblRoZSBlbGVjdHJvbiBhbHRlcm5hdGl2ZSBmb3IgR29cclxuKGMpIExlYSBBbnRob255IDIwMTktcHJlc2VudFxyXG4qL1xyXG5cclxuLyoganNoaW50IGVzdmVyc2lvbjogOSAqL1xyXG5cclxuLyoqXHJcbiAqIEB0eXBlZGVmIHtpbXBvcnQoXCIuL2FwaS90eXBlc1wiKS5NZXNzYWdlRGlhbG9nT3B0aW9uc30gTWVzc2FnZURpYWxvZ09wdGlvbnNcclxuICogQHR5cGVkZWYge2ltcG9ydChcIi4vYXBpL3R5cGVzXCIpLk9wZW5EaWFsb2dPcHRpb25zfSBPcGVuRGlhbG9nT3B0aW9uc1xyXG4gKiBAdHlwZWRlZiB7aW1wb3J0KFwiLi9hcGkvdHlwZXNcIikuU2F2ZURpYWxvZ09wdGlvbnN9IFNhdmVEaWFsb2dPcHRpb25zXHJcbiAqL1xyXG5cclxuaW1wb3J0IHtuZXdSdW50aW1lQ2FsbGVyV2l0aElELCBvYmplY3ROYW1lc30gZnJvbSBcIi4vcnVudGltZVwiO1xyXG5cclxuaW1wb3J0IHsgbmFub2lkIH0gZnJvbSAnbmFub2lkL25vbi1zZWN1cmUnO1xyXG5cclxubGV0IGNhbGwgPSBuZXdSdW50aW1lQ2FsbGVyV2l0aElEKG9iamVjdE5hbWVzLkRpYWxvZyk7XHJcblxyXG5sZXQgRGlhbG9nSW5mbyA9IDA7XHJcbmxldCBEaWFsb2dXYXJuaW5nID0gMTtcclxubGV0IERpYWxvZ0Vycm9yID0gMjtcclxubGV0IERpYWxvZ1F1ZXN0aW9uID0gMztcclxubGV0IERpYWxvZ09wZW5GaWxlID0gNDtcclxubGV0IERpYWxvZ1NhdmVGaWxlID0gNTtcclxuXHJcblxyXG5sZXQgZGlhbG9nUmVzcG9uc2VzID0gbmV3IE1hcCgpO1xyXG5cclxuZnVuY3Rpb24gZ2VuZXJhdGVJRCgpIHtcclxuICAgIGxldCByZXN1bHQ7XHJcbiAgICBkbyB7XHJcbiAgICAgICAgcmVzdWx0ID0gbmFub2lkKCk7XHJcbiAgICB9IHdoaWxlIChkaWFsb2dSZXNwb25zZXMuaGFzKHJlc3VsdCkpO1xyXG4gICAgcmV0dXJuIHJlc3VsdDtcclxufVxyXG5cclxuZXhwb3J0IGZ1bmN0aW9uIGRpYWxvZ0NhbGxiYWNrKGlkLCBkYXRhLCBpc0pTT04pIHtcclxuICAgIGxldCBwID0gZGlhbG9nUmVzcG9uc2VzLmdldChpZCk7XHJcbiAgICBpZiAocCkge1xyXG4gICAgICAgIGlmIChpc0pTT04pIHtcclxuICAgICAgICAgICAgcC5yZXNvbHZlKEpTT04ucGFyc2UoZGF0YSkpO1xyXG4gICAgICAgIH0gZWxzZSB7XHJcbiAgICAgICAgICAgIHAucmVzb2x2ZShkYXRhKTtcclxuICAgICAgICB9XHJcbiAgICAgICAgZGlhbG9nUmVzcG9uc2VzLmRlbGV0ZShpZCk7XHJcbiAgICB9XHJcbn1cclxuZXhwb3J0IGZ1bmN0aW9uIGRpYWxvZ0Vycm9yQ2FsbGJhY2soaWQsIG1lc3NhZ2UpIHtcclxuICAgIGxldCBwID0gZGlhbG9nUmVzcG9uc2VzLmdldChpZCk7XHJcbiAgICBpZiAocCkge1xyXG4gICAgICAgIHAucmVqZWN0KG1lc3NhZ2UpO1xyXG4gICAgICAgIGRpYWxvZ1Jlc3BvbnNlcy5kZWxldGUoaWQpO1xyXG4gICAgfVxyXG59XHJcblxyXG5mdW5jdGlvbiBkaWFsb2codHlwZSwgb3B0aW9ucykge1xyXG4gICAgcmV0dXJuIG5ldyBQcm9taXNlKChyZXNvbHZlLCByZWplY3QpID0+IHtcclxuICAgICAgICBsZXQgaWQgPSBnZW5lcmF0ZUlEKCk7XHJcbiAgICAgICAgb3B0aW9ucyA9IG9wdGlvbnMgfHwge307XHJcbiAgICAgICAgb3B0aW9uc1tcImRpYWxvZy1pZFwiXSA9IGlkO1xyXG4gICAgICAgIGRpYWxvZ1Jlc3BvbnNlcy5zZXQoaWQsIHtyZXNvbHZlLCByZWplY3R9KTtcclxuICAgICAgICBjYWxsKHR5cGUsIG9wdGlvbnMpLmNhdGNoKChlcnJvcikgPT4ge1xyXG4gICAgICAgICAgICByZWplY3QoZXJyb3IpO1xyXG4gICAgICAgICAgICBkaWFsb2dSZXNwb25zZXMuZGVsZXRlKGlkKTtcclxuICAgICAgICB9KTtcclxuICAgIH0pO1xyXG59XHJcblxyXG5cclxuLyoqXHJcbiAqIFNob3dzIGFuIEluZm8gZGlhbG9nIHdpdGggdGhlIGdpdmVuIG9wdGlvbnMuXHJcbiAqIEBwYXJhbSB7TWVzc2FnZURpYWxvZ09wdGlvbnN9IG9wdGlvbnNcclxuICogQHJldHVybnMge1Byb21pc2U8c3RyaW5nPn0gVGhlIGxhYmVsIG9mIHRoZSBidXR0b24gcHJlc3NlZFxyXG4gKi9cclxuZXhwb3J0IGZ1bmN0aW9uIEluZm8ob3B0aW9ucykge1xyXG4gICAgcmV0dXJuIGRpYWxvZyhEaWFsb2dJbmZvLCBvcHRpb25zKTtcclxufVxyXG5cclxuLyoqXHJcbiAqIFNob3dzIGEgV2FybmluZyBkaWFsb2cgd2l0aCB0aGUgZ2l2ZW4gb3B0aW9ucy5cclxuICogQHBhcmFtIHtNZXNzYWdlRGlhbG9nT3B0aW9uc30gb3B0aW9uc1xyXG4gKiBAcmV0dXJucyB7UHJvbWlzZTxzdHJpbmc+fSBUaGUgbGFiZWwgb2YgdGhlIGJ1dHRvbiBwcmVzc2VkXHJcbiAqL1xyXG5leHBvcnQgZnVuY3Rpb24gV2FybmluZyhvcHRpb25zKSB7XHJcbiAgICByZXR1cm4gZGlhbG9nKERpYWxvZ1dhcm5pbmcsIG9wdGlvbnMpO1xyXG59XHJcblxyXG4vKipcclxuICogU2hvd3MgYW4gRXJyb3IgZGlhbG9nIHdpdGggdGhlIGdpdmVuIG9wdGlvbnMuXHJcbiAqIEBwYXJhbSB7TWVzc2FnZURpYWxvZ09wdGlvbnN9IG9wdGlvbnNcclxuICogQHJldHVybnMge1Byb21pc2U8c3RyaW5nPn0gVGhlIGxhYmVsIG9mIHRoZSBidXR0b24gcHJlc3NlZFxyXG4gKi9cclxuZXhwb3J0IGZ1bmN0aW9uIEVycm9yKG9wdGlvbnMpIHtcclxuICAgIHJldHVybiBkaWFsb2coRGlhbG9nRXJyb3IsIG9wdGlvbnMpO1xyXG59XHJcblxyXG4vKipcclxuICogU2hvd3MgYSBRdWVzdGlvbiBkaWFsb2cgd2l0aCB0aGUgZ2l2ZW4gb3B0aW9ucy5cclxuICogQHBhcmFtIHtNZXNzYWdlRGlhbG9nT3B0aW9uc30gb3B0aW9uc1xyXG4gKiBAcmV0dXJucyB7UHJvbWlzZTxzdHJpbmc+fSBUaGUgbGFiZWwgb2YgdGhlIGJ1dHRvbiBwcmVzc2VkXHJcbiAqL1xyXG5leHBvcnQgZnVuY3Rpb24gUXVlc3Rpb24ob3B0aW9ucykge1xyXG4gICAgcmV0dXJuIGRpYWxvZyhEaWFsb2dRdWVzdGlvbiwgb3B0aW9ucyk7XHJcbn1cclxuXHJcbi8qKlxyXG4gKiBTaG93cyBhbiBPcGVuIGRpYWxvZyB3aXRoIHRoZSBnaXZlbiBvcHRpb25zLlxyXG4gKiBAcGFyYW0ge09wZW5EaWFsb2dPcHRpb25zfSBvcHRpb25zXHJcbiAqIEByZXR1cm5zIHtQcm9taXNlPHN0cmluZ1tdfHN0cmluZz59IFJldHVybnMgdGhlIHNlbGVjdGVkIGZpbGUgb3IgYW4gYXJyYXkgb2Ygc2VsZWN0ZWQgZmlsZXMgaWYgQWxsb3dzTXVsdGlwbGVTZWxlY3Rpb24gaXMgdHJ1ZS4gQSBibGFuayBzdHJpbmcgaXMgcmV0dXJuZWQgaWYgbm8gZmlsZSB3YXMgc2VsZWN0ZWQuXHJcbiAqL1xyXG5leHBvcnQgZnVuY3Rpb24gT3BlbkZpbGUob3B0aW9ucykge1xyXG4gICAgcmV0dXJuIGRpYWxvZyhEaWFsb2dPcGVuRmlsZSwgb3B0aW9ucyk7XHJcbn1cclxuXHJcbi8qKlxyXG4gKiBTaG93cyBhIFNhdmUgZGlhbG9nIHdpdGggdGhlIGdpdmVuIG9wdGlvbnMuXHJcbiAqIEBwYXJhbSB7U2F2ZURpYWxvZ09wdGlvbnN9IG9wdGlvbnNcclxuICogQHJldHVybnMge1Byb21pc2U8c3RyaW5nPn0gUmV0dXJucyB0aGUgc2VsZWN0ZWQgZmlsZS4gQSBibGFuayBzdHJpbmcgaXMgcmV0dXJuZWQgaWYgbm8gZmlsZSB3YXMgc2VsZWN0ZWQuXHJcbiAqL1xyXG5leHBvcnQgZnVuY3Rpb24gU2F2ZUZpbGUob3B0aW9ucykge1xyXG4gICAgcmV0dXJuIGRpYWxvZyhEaWFsb2dTYXZlRmlsZSwgb3B0aW9ucyk7XHJcbn1cclxuXHJcbiIsICJpbXBvcnQge25ld1J1bnRpbWVDYWxsZXJXaXRoSUQsIG9iamVjdE5hbWVzfSBmcm9tIFwiLi9ydW50aW1lXCI7XHJcblxyXG5sZXQgY2FsbCA9IG5ld1J1bnRpbWVDYWxsZXJXaXRoSUQob2JqZWN0TmFtZXMuQ29udGV4dE1lbnUpO1xyXG5cclxubGV0IENvbnRleHRNZW51T3BlbiA9IDA7XHJcblxyXG5mdW5jdGlvbiBvcGVuQ29udGV4dE1lbnUoaWQsIHgsIHksIGRhdGEpIHtcclxuICAgIHZvaWQgY2FsbChDb250ZXh0TWVudU9wZW4sIHtpZCwgeCwgeSwgZGF0YX0pO1xyXG59XHJcblxyXG5leHBvcnQgZnVuY3Rpb24gc2V0dXBDb250ZXh0TWVudXMoKSB7XHJcbiAgICB3aW5kb3cuYWRkRXZlbnRMaXN0ZW5lcignY29udGV4dG1lbnUnLCBjb250ZXh0TWVudUhhbmRsZXIpO1xyXG59XHJcblxyXG5mdW5jdGlvbiBjb250ZXh0TWVudUhhbmRsZXIoZXZlbnQpIHtcclxuICAgIC8vIENoZWNrIGZvciBjdXN0b20gY29udGV4dCBtZW51XHJcbiAgICBsZXQgZWxlbWVudCA9IGV2ZW50LnRhcmdldDtcclxuICAgIGxldCBjdXN0b21Db250ZXh0TWVudSA9IHdpbmRvdy5nZXRDb21wdXRlZFN0eWxlKGVsZW1lbnQpLmdldFByb3BlcnR5VmFsdWUoXCItLWN1c3RvbS1jb250ZXh0bWVudVwiKTtcclxuICAgIGN1c3RvbUNvbnRleHRNZW51ID0gY3VzdG9tQ29udGV4dE1lbnUgPyBjdXN0b21Db250ZXh0TWVudS50cmltKCkgOiBcIlwiO1xyXG4gICAgaWYgKGN1c3RvbUNvbnRleHRNZW51KSB7XHJcbiAgICAgICAgZXZlbnQucHJldmVudERlZmF1bHQoKTtcclxuICAgICAgICBsZXQgY3VzdG9tQ29udGV4dE1lbnVEYXRhID0gd2luZG93LmdldENvbXB1dGVkU3R5bGUoZWxlbWVudCkuZ2V0UHJvcGVydHlWYWx1ZShcIi0tY3VzdG9tLWNvbnRleHRtZW51LWRhdGFcIik7XHJcbiAgICAgICAgb3BlbkNvbnRleHRNZW51KGN1c3RvbUNvbnRleHRNZW51LCBldmVudC5jbGllbnRYLCBldmVudC5jbGllbnRZLCBjdXN0b21Db250ZXh0TWVudURhdGEpO1xyXG4gICAgICAgIHJldHVyblxyXG4gICAgfVxyXG5cclxuICAgIHByb2Nlc3NEZWZhdWx0Q29udGV4dE1lbnUoZXZlbnQpO1xyXG59XHJcblxyXG5cclxuLypcclxuLS1kZWZhdWx0LWNvbnRleHRtZW51OiBhdXRvOyAoZGVmYXVsdCkgd2lsbCBzaG93IHRoZSBkZWZhdWx0IGNvbnRleHQgbWVudSBpZiBjb250ZW50RWRpdGFibGUgaXMgdHJ1ZSBPUiB0ZXh0IGhhcyBiZWVuIHNlbGVjdGVkIE9SIGVsZW1lbnQgaXMgaW5wdXQgb3IgdGV4dGFyZWFcclxuLS1kZWZhdWx0LWNvbnRleHRtZW51OiBzaG93OyB3aWxsIGFsd2F5cyBzaG93IHRoZSBkZWZhdWx0IGNvbnRleHQgbWVudVxyXG4tLWRlZmF1bHQtY29udGV4dG1lbnU6IGhpZGU7IHdpbGwgYWx3YXlzIGhpZGUgdGhlIGRlZmF1bHQgY29udGV4dCBtZW51XHJcblxyXG5UaGlzIHJ1bGUgaXMgaW5oZXJpdGVkIGxpa2Ugbm9ybWFsIENTUyBydWxlcywgc28gbmVzdGluZyB3b3JrcyBhcyBleHBlY3RlZFxyXG4qL1xyXG5mdW5jdGlvbiBwcm9jZXNzRGVmYXVsdENvbnRleHRNZW51KGV2ZW50KSB7XHJcbiAgICAvLyBEZWJ1ZyBidWlsZHMgYWx3YXlzIHNob3cgdGhlIG1lbnVcclxuICAgIGlmIChERUJVRykge1xyXG4gICAgICAgIHJldHVybjtcclxuICAgIH1cclxuXHJcbiAgICAvLyBQcm9jZXNzIGRlZmF1bHQgY29udGV4dCBtZW51XHJcbiAgICBjb25zdCBlbGVtZW50ID0gZXZlbnQudGFyZ2V0O1xyXG4gICAgY29uc3QgY29tcHV0ZWRTdHlsZSA9IHdpbmRvdy5nZXRDb21wdXRlZFN0eWxlKGVsZW1lbnQpO1xyXG4gICAgY29uc3QgZGVmYXVsdENvbnRleHRNZW51QWN0aW9uID0gY29tcHV0ZWRTdHlsZS5nZXRQcm9wZXJ0eVZhbHVlKFwiLS1kZWZhdWx0LWNvbnRleHRtZW51XCIpLnRyaW0oKTtcclxuICAgIHN3aXRjaCAoZGVmYXVsdENvbnRleHRNZW51QWN0aW9uKSB7XHJcbiAgICAgICAgY2FzZSBcInNob3dcIjpcclxuICAgICAgICAgICAgcmV0dXJuO1xyXG4gICAgICAgIGNhc2UgXCJoaWRlXCI6XHJcbiAgICAgICAgICAgIGV2ZW50LnByZXZlbnREZWZhdWx0KCk7XHJcbiAgICAgICAgICAgIHJldHVybjtcclxuICAgICAgICBkZWZhdWx0OlxyXG4gICAgICAgICAgICAvLyBDaGVjayBpZiBjb250ZW50RWRpdGFibGUgaXMgdHJ1ZVxyXG4gICAgICAgICAgICBpZiAoZWxlbWVudC5pc0NvbnRlbnRFZGl0YWJsZSkge1xyXG4gICAgICAgICAgICAgICAgcmV0dXJuO1xyXG4gICAgICAgICAgICB9XHJcblxyXG4gICAgICAgICAgICAvLyBDaGVjayBpZiB0ZXh0IGhhcyBiZWVuIHNlbGVjdGVkXHJcbiAgICAgICAgICAgIGNvbnN0IHNlbGVjdGlvbiA9IHdpbmRvdy5nZXRTZWxlY3Rpb24oKTtcclxuICAgICAgICAgICAgY29uc3QgaGFzU2VsZWN0aW9uID0gKHNlbGVjdGlvbi50b1N0cmluZygpLmxlbmd0aCA+IDApXHJcbiAgICAgICAgICAgIGlmIChoYXNTZWxlY3Rpb24pIHtcclxuICAgICAgICAgICAgICAgIGZvciAobGV0IGkgPSAwOyBpIDwgc2VsZWN0aW9uLnJhbmdlQ291bnQ7IGkrKykge1xyXG4gICAgICAgICAgICAgICAgICAgIGNvbnN0IHJhbmdlID0gc2VsZWN0aW9uLmdldFJhbmdlQXQoaSk7XHJcbiAgICAgICAgICAgICAgICAgICAgY29uc3QgcmVjdHMgPSByYW5nZS5nZXRDbGllbnRSZWN0cygpO1xyXG4gICAgICAgICAgICAgICAgICAgIGZvciAobGV0IGogPSAwOyBqIDwgcmVjdHMubGVuZ3RoOyBqKyspIHtcclxuICAgICAgICAgICAgICAgICAgICAgICAgY29uc3QgcmVjdCA9IHJlY3RzW2pdO1xyXG4gICAgICAgICAgICAgICAgICAgICAgICBpZiAoZG9jdW1lbnQuZWxlbWVudEZyb21Qb2ludChyZWN0LmxlZnQsIHJlY3QudG9wKSA9PT0gZWxlbWVudCkge1xyXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgcmV0dXJuO1xyXG4gICAgICAgICAgICAgICAgICAgICAgICB9XHJcbiAgICAgICAgICAgICAgICAgICAgfVxyXG4gICAgICAgICAgICAgICAgfVxyXG4gICAgICAgICAgICB9XHJcbiAgICAgICAgICAgIC8vIENoZWNrIGlmIHRhZ25hbWUgaXMgaW5wdXQgb3IgdGV4dGFyZWFcclxuICAgICAgICAgICAgaWYgKGVsZW1lbnQudGFnTmFtZSA9PT0gXCJJTlBVVFwiIHx8IGVsZW1lbnQudGFnTmFtZSA9PT0gXCJURVhUQVJFQVwiKSB7XHJcbiAgICAgICAgICAgICAgICBpZiAoaGFzU2VsZWN0aW9uIHx8ICghZWxlbWVudC5yZWFkT25seSAmJiAhZWxlbWVudC5kaXNhYmxlZCkpIHtcclxuICAgICAgICAgICAgICAgICAgICByZXR1cm47XHJcbiAgICAgICAgICAgICAgICB9XHJcbiAgICAgICAgICAgIH1cclxuXHJcbiAgICAgICAgICAgIC8vIGhpZGUgZGVmYXVsdCBjb250ZXh0IG1lbnVcclxuICAgICAgICAgICAgZXZlbnQucHJldmVudERlZmF1bHQoKTtcclxuICAgIH1cclxufVxyXG4iLCAiXHJcbmltcG9ydCB7RW1pdCwgV2FpbHNFdmVudH0gZnJvbSBcIi4vZXZlbnRzXCI7XHJcbmltcG9ydCB7UXVlc3Rpb259IGZyb20gXCIuL2RpYWxvZ3NcIjtcclxuXHJcbmZ1bmN0aW9uIHNlbmRFdmVudChldmVudE5hbWUsIGRhdGE9bnVsbCkge1xyXG4gICAgbGV0IGV2ZW50ID0gbmV3IFdhaWxzRXZlbnQoZXZlbnROYW1lLCBkYXRhKTtcclxuICAgIEVtaXQoZXZlbnQpO1xyXG59XHJcblxyXG5mdW5jdGlvbiBhZGRXTUxFdmVudExpc3RlbmVycygpIHtcclxuICAgIGNvbnN0IGVsZW1lbnRzID0gZG9jdW1lbnQucXVlcnlTZWxlY3RvckFsbCgnW3dtbC1ldmVudF0nKTtcclxuICAgIGVsZW1lbnRzLmZvckVhY2goZnVuY3Rpb24gKGVsZW1lbnQpIHtcclxuICAgICAgICBjb25zdCBldmVudFR5cGUgPSBlbGVtZW50LmdldEF0dHJpYnV0ZSgnd21sLWV2ZW50Jyk7XHJcbiAgICAgICAgY29uc3QgY29uZmlybSA9IGVsZW1lbnQuZ2V0QXR0cmlidXRlKCd3bWwtY29uZmlybScpO1xyXG4gICAgICAgIGNvbnN0IHRyaWdnZXIgPSBlbGVtZW50LmdldEF0dHJpYnV0ZSgnd21sLXRyaWdnZXInKSB8fCBcImNsaWNrXCI7XHJcblxyXG4gICAgICAgIGxldCBjYWxsYmFjayA9IGZ1bmN0aW9uICgpIHtcclxuICAgICAgICAgICAgaWYgKGNvbmZpcm0pIHtcclxuICAgICAgICAgICAgICAgIFF1ZXN0aW9uKHtUaXRsZTogXCJDb25maXJtXCIsIE1lc3NhZ2U6Y29uZmlybSwgRGV0YWNoZWQ6IGZhbHNlLCBCdXR0b25zOlt7TGFiZWw6XCJZZXNcIn0se0xhYmVsOlwiTm9cIiwgSXNEZWZhdWx0OnRydWV9XX0pLnRoZW4oZnVuY3Rpb24gKHJlc3VsdCkge1xyXG4gICAgICAgICAgICAgICAgICAgIGlmIChyZXN1bHQgIT09IFwiTm9cIikge1xyXG4gICAgICAgICAgICAgICAgICAgICAgICBzZW5kRXZlbnQoZXZlbnRUeXBlKTtcclxuICAgICAgICAgICAgICAgICAgICB9XHJcbiAgICAgICAgICAgICAgICB9KTtcclxuICAgICAgICAgICAgICAgIHJldHVybjtcclxuICAgICAgICAgICAgfVxyXG4gICAgICAgICAgICBzZW5kRXZlbnQoZXZlbnRUeXBlKTtcclxuICAgICAgICB9O1xyXG4gICAgICAgIC8vIFJlbW92ZSBleGlzdGluZyBsaXN0ZW5lcnNcclxuXHJcbiAgICAgICAgZWxlbWVudC5yZW1vdmVFdmVudExpc3RlbmVyKHRyaWdnZXIsIGNhbGxiYWNrKTtcclxuXHJcbiAgICAgICAgLy8gQWRkIG5ldyBsaXN0ZW5lclxyXG4gICAgICAgIGVsZW1lbnQuYWRkRXZlbnRMaXN0ZW5lcih0cmlnZ2VyLCBjYWxsYmFjayk7XHJcbiAgICB9KTtcclxufVxyXG5cclxuZnVuY3Rpb24gY2FsbFdpbmRvd01ldGhvZChtZXRob2QpIHtcclxuICAgIGlmICh3YWlscy5XaW5kb3dbbWV0aG9kXSA9PT0gdW5kZWZpbmVkKSB7XHJcbiAgICAgICAgY29uc29sZS5sb2coXCJXaW5kb3cgbWV0aG9kIFwiICsgbWV0aG9kICsgXCIgbm90IGZvdW5kXCIpO1xyXG4gICAgfVxyXG4gICAgd2FpbHMuV2luZG93W21ldGhvZF0oKTtcclxufVxyXG5cclxuZnVuY3Rpb24gYWRkV01MV2luZG93TGlzdGVuZXJzKCkge1xyXG4gICAgY29uc3QgZWxlbWVudHMgPSBkb2N1bWVudC5xdWVyeVNlbGVjdG9yQWxsKCdbd21sLXdpbmRvd10nKTtcclxuICAgIGVsZW1lbnRzLmZvckVhY2goZnVuY3Rpb24gKGVsZW1lbnQpIHtcclxuICAgICAgICBjb25zdCB3aW5kb3dNZXRob2QgPSBlbGVtZW50LmdldEF0dHJpYnV0ZSgnd21sLXdpbmRvdycpO1xyXG4gICAgICAgIGNvbnN0IGNvbmZpcm0gPSBlbGVtZW50LmdldEF0dHJpYnV0ZSgnd21sLWNvbmZpcm0nKTtcclxuICAgICAgICBjb25zdCB0cmlnZ2VyID0gZWxlbWVudC5nZXRBdHRyaWJ1dGUoJ3dtbC10cmlnZ2VyJykgfHwgXCJjbGlja1wiO1xyXG5cclxuICAgICAgICBsZXQgY2FsbGJhY2sgPSBmdW5jdGlvbiAoKSB7XHJcbiAgICAgICAgICAgIGlmIChjb25maXJtKSB7XHJcbiAgICAgICAgICAgICAgICBRdWVzdGlvbih7VGl0bGU6IFwiQ29uZmlybVwiLCBNZXNzYWdlOmNvbmZpcm0sIEJ1dHRvbnM6W3tMYWJlbDpcIlllc1wifSx7TGFiZWw6XCJOb1wiLCBJc0RlZmF1bHQ6dHJ1ZX1dfSkudGhlbihmdW5jdGlvbiAocmVzdWx0KSB7XHJcbiAgICAgICAgICAgICAgICAgICAgaWYgKHJlc3VsdCAhPT0gXCJOb1wiKSB7XHJcbiAgICAgICAgICAgICAgICAgICAgICAgIGNhbGxXaW5kb3dNZXRob2Qod2luZG93TWV0aG9kKTtcclxuICAgICAgICAgICAgICAgICAgICB9XHJcbiAgICAgICAgICAgICAgICB9KTtcclxuICAgICAgICAgICAgICAgIHJldHVybjtcclxuICAgICAgICAgICAgfVxyXG4gICAgICAgICAgICBjYWxsV2luZG93TWV0aG9kKHdpbmRvd01ldGhvZCk7XHJcbiAgICAgICAgfTtcclxuXHJcbiAgICAgICAgLy8gUmVtb3ZlIGV4aXN0aW5nIGxpc3RlbmVyc1xyXG4gICAgICAgIGVsZW1lbnQucmVtb3ZlRXZlbnRMaXN0ZW5lcih0cmlnZ2VyLCBjYWxsYmFjayk7XHJcblxyXG4gICAgICAgIC8vIEFkZCBuZXcgbGlzdGVuZXJcclxuICAgICAgICBlbGVtZW50LmFkZEV2ZW50TGlzdGVuZXIodHJpZ2dlciwgY2FsbGJhY2spO1xyXG4gICAgfSk7XHJcbn1cclxuXHJcbmZ1bmN0aW9uIGFkZFdNTE9wZW5Ccm93c2VyTGlzdGVuZXIoKSB7XHJcbiAgICBjb25zdCBlbGVtZW50cyA9IGRvY3VtZW50LnF1ZXJ5U2VsZWN0b3JBbGwoJ1t3bWwtb3BlbnVybF0nKTtcclxuICAgIGVsZW1lbnRzLmZvckVhY2goZnVuY3Rpb24gKGVsZW1lbnQpIHtcclxuICAgICAgICBjb25zdCB1cmwgPSBlbGVtZW50LmdldEF0dHJpYnV0ZSgnd21sLW9wZW51cmwnKTtcclxuICAgICAgICBjb25zdCBjb25maXJtID0gZWxlbWVudC5nZXRBdHRyaWJ1dGUoJ3dtbC1jb25maXJtJyk7XHJcbiAgICAgICAgY29uc3QgdHJpZ2dlciA9IGVsZW1lbnQuZ2V0QXR0cmlidXRlKCd3bWwtdHJpZ2dlcicpIHx8IFwiY2xpY2tcIjtcclxuXHJcbiAgICAgICAgbGV0IGNhbGxiYWNrID0gZnVuY3Rpb24gKCkge1xyXG4gICAgICAgICAgICBpZiAoY29uZmlybSkge1xyXG4gICAgICAgICAgICAgICAgUXVlc3Rpb24oe1RpdGxlOiBcIkNvbmZpcm1cIiwgTWVzc2FnZTpjb25maXJtLCBCdXR0b25zOlt7TGFiZWw6XCJZZXNcIn0se0xhYmVsOlwiTm9cIiwgSXNEZWZhdWx0OnRydWV9XX0pLnRoZW4oZnVuY3Rpb24gKHJlc3VsdCkge1xyXG4gICAgICAgICAgICAgICAgICAgIGlmIChyZXN1bHQgIT09IFwiTm9cIikge1xyXG4gICAgICAgICAgICAgICAgICAgICAgICB2b2lkIHdhaWxzLkJyb3dzZXIuT3BlblVSTCh1cmwpO1xyXG4gICAgICAgICAgICAgICAgICAgIH1cclxuICAgICAgICAgICAgICAgIH0pO1xyXG4gICAgICAgICAgICAgICAgcmV0dXJuO1xyXG4gICAgICAgICAgICB9XHJcbiAgICAgICAgICAgIHZvaWQgd2FpbHMuQnJvd3Nlci5PcGVuVVJMKHVybCk7XHJcbiAgICAgICAgfTtcclxuXHJcbiAgICAgICAgLy8gUmVtb3ZlIGV4aXN0aW5nIGxpc3RlbmVyc1xyXG4gICAgICAgIGVsZW1lbnQucmVtb3ZlRXZlbnRMaXN0ZW5lcih0cmlnZ2VyLCBjYWxsYmFjayk7XHJcblxyXG4gICAgICAgIC8vIEFkZCBuZXcgbGlzdGVuZXJcclxuICAgICAgICBlbGVtZW50LmFkZEV2ZW50TGlzdGVuZXIodHJpZ2dlciwgY2FsbGJhY2spO1xyXG4gICAgfSk7XHJcbn1cclxuXHJcbmV4cG9ydCBmdW5jdGlvbiByZWxvYWRXTUwoKSB7XHJcbiAgICBhZGRXTUxFdmVudExpc3RlbmVycygpO1xyXG4gICAgYWRkV01MV2luZG93TGlzdGVuZXJzKCk7XHJcbiAgICBhZGRXTUxPcGVuQnJvd3Nlckxpc3RlbmVyKCk7XHJcbn1cclxuIiwgIi8qXHJcbiBfXHQgICBfX1x0ICBfIF9fXHJcbnwgfFx0IC8gL19fXyBfKF8pIC9fX19fXHJcbnwgfCAvfCAvIC8gX18gYC8gLyAvIF9fXy9cclxufCB8LyB8LyAvIC9fLyAvIC8gKF9fICApXHJcbnxfXy98X18vXFxfXyxfL18vXy9fX19fL1xyXG5UaGUgZWxlY3Ryb24gYWx0ZXJuYXRpdmUgZm9yIEdvXHJcbihjKSBMZWEgQW50aG9ueSAyMDE5LXByZXNlbnRcclxuKi9cclxuXHJcbi8qIGpzaGludCBlc3ZlcnNpb246IDkgKi9cclxuXHJcbi8vIGRlZmluZWQgaW4gdGhlIFRhc2tmaWxlXHJcbmV4cG9ydCBsZXQgaW52b2tlID0gZnVuY3Rpb24oaW5wdXQpIHtcclxuICAgIGlmKFdJTkRPV1MpIHtcclxuICAgICAgICBjaHJvbWUud2Vidmlldy5wb3N0TWVzc2FnZShpbnB1dCk7XHJcbiAgICB9IGVsc2Uge1xyXG4gICAgICAgIHdlYmtpdC5tZXNzYWdlSGFuZGxlcnMuZXh0ZXJuYWwucG9zdE1lc3NhZ2UoaW5wdXQpO1xyXG4gICAgfVxyXG59XHJcbiIsICIvKlxyXG4gX1x0ICAgX19cdCAgXyBfX1xyXG58IHxcdCAvIC9fX18gXyhfKSAvX19fX1xyXG58IHwgL3wgLyAvIF9fIGAvIC8gLyBfX18vXHJcbnwgfC8gfC8gLyAvXy8gLyAvIChfXyAgKVxyXG58X18vfF9fL1xcX18sXy9fL18vX19fXy9cclxuVGhlIGVsZWN0cm9uIGFsdGVybmF0aXZlIGZvciBHb1xyXG4oYykgTGVhIEFudGhvbnkgMjAxOS1wcmVzZW50XHJcbiovXHJcblxyXG4vKiBqc2hpbnQgZXN2ZXJzaW9uOiA5ICovXHJcblxyXG5sZXQgZmxhZ3MgPSBuZXcgTWFwKCk7XHJcblxyXG5mdW5jdGlvbiBjb252ZXJ0VG9NYXAob2JqKSB7XHJcbiAgICBjb25zdCBtYXAgPSBuZXcgTWFwKCk7XHJcblxyXG4gICAgZm9yIChjb25zdCBba2V5LCB2YWx1ZV0gb2YgT2JqZWN0LmVudHJpZXMob2JqKSkge1xyXG4gICAgICAgIGlmICh0eXBlb2YgdmFsdWUgPT09ICdvYmplY3QnICYmIHZhbHVlICE9PSBudWxsKSB7XHJcbiAgICAgICAgICAgIG1hcC5zZXQoa2V5LCBjb252ZXJ0VG9NYXAodmFsdWUpKTsgLy8gUmVjdXJzaXZlbHkgY29udmVydCBuZXN0ZWQgb2JqZWN0XHJcbiAgICAgICAgfSBlbHNlIHtcclxuICAgICAgICAgICAgbWFwLnNldChrZXksIHZhbHVlKTtcclxuICAgICAgICB9XHJcbiAgICB9XHJcblxyXG4gICAgcmV0dXJuIG1hcDtcclxufVxyXG5cclxuZmV0Y2goXCIvd2FpbHMvZmxhZ3NcIikudGhlbigocmVzcG9uc2UpID0+IHtcclxuICAgIHJlc3BvbnNlLmpzb24oKS50aGVuKChkYXRhKSA9PiB7XHJcbiAgICAgICAgZmxhZ3MgPSBjb252ZXJ0VG9NYXAoZGF0YSk7XHJcbiAgICB9KTtcclxufSk7XHJcblxyXG5cclxuZnVuY3Rpb24gZ2V0VmFsdWVGcm9tTWFwKGtleVN0cmluZykge1xyXG4gICAgY29uc3Qga2V5cyA9IGtleVN0cmluZy5zcGxpdCgnLicpO1xyXG4gICAgbGV0IHZhbHVlID0gZmxhZ3M7XHJcblxyXG4gICAgZm9yIChjb25zdCBrZXkgb2Yga2V5cykge1xyXG4gICAgICAgIGlmICh2YWx1ZSBpbnN0YW5jZW9mIE1hcCkge1xyXG4gICAgICAgICAgICB2YWx1ZSA9IHZhbHVlLmdldChrZXkpO1xyXG4gICAgICAgIH0gZWxzZSB7XHJcbiAgICAgICAgICAgIHZhbHVlID0gdmFsdWVba2V5XTtcclxuICAgICAgICB9XHJcblxyXG4gICAgICAgIGlmICh2YWx1ZSA9PT0gdW5kZWZpbmVkKSB7XHJcbiAgICAgICAgICAgIGJyZWFrO1xyXG4gICAgICAgIH1cclxuICAgIH1cclxuXHJcbiAgICByZXR1cm4gdmFsdWU7XHJcbn1cclxuXHJcbmV4cG9ydCBmdW5jdGlvbiBHZXRGbGFnKGtleVN0cmluZykge1xyXG4gICAgcmV0dXJuIGdldFZhbHVlRnJvbU1hcChrZXlTdHJpbmcpO1xyXG59XHJcbiIsICIvKlxyXG4gX1x0ICAgX19cdCAgXyBfX1xyXG58IHxcdCAvIC9fX18gXyhfKSAvX19fX1xyXG58IHwgL3wgLyAvIF9fIGAvIC8gLyBfX18vXHJcbnwgfC8gfC8gLyAvXy8gLyAvIChfXyAgKVxyXG58X18vfF9fL1xcX18sXy9fL18vX19fXy9cclxuVGhlIGVsZWN0cm9uIGFsdGVybmF0aXZlIGZvciBHb1xyXG4oYykgTGVhIEFudGhvbnkgMjAxOS1wcmVzZW50XHJcbiovXHJcblxyXG4vKiBqc2hpbnQgZXN2ZXJzaW9uOiA5ICovXHJcblxyXG5pbXBvcnQge2ludm9rZX0gZnJvbSBcIi4vaW52b2tlXCI7XHJcbmltcG9ydCB7R2V0RmxhZ30gZnJvbSBcIi4vZmxhZ3NcIjtcclxuXHJcbmxldCBzaG91bGREcmFnID0gZmFsc2U7XHJcblxyXG5leHBvcnQgZnVuY3Rpb24gZHJhZ1Rlc3QoZSkge1xyXG4gICAgbGV0IHZhbCA9IHdpbmRvdy5nZXRDb21wdXRlZFN0eWxlKGUudGFyZ2V0KS5nZXRQcm9wZXJ0eVZhbHVlKFwiLS13ZWJraXQtYXBwLXJlZ2lvblwiKTtcclxuICAgIGlmICh2YWwpIHtcclxuICAgICAgICB2YWwgPSB2YWwudHJpbSgpO1xyXG4gICAgfVxyXG5cclxuICAgIGlmICh2YWwgIT09IFwiZHJhZ1wiKSB7XHJcbiAgICAgICAgcmV0dXJuIGZhbHNlO1xyXG4gICAgfVxyXG5cclxuICAgIC8vIE9ubHkgcHJvY2VzcyB0aGUgcHJpbWFyeSBidXR0b25cclxuICAgIGlmIChlLmJ1dHRvbnMgIT09IDEpIHtcclxuICAgICAgICByZXR1cm4gZmFsc2U7XHJcbiAgICB9XHJcblxyXG4gICAgcmV0dXJuIGUuZGV0YWlsID09PSAxO1xyXG59XHJcblxyXG5leHBvcnQgZnVuY3Rpb24gc2V0dXBEcmFnKCkge1xyXG4gICAgd2luZG93LmFkZEV2ZW50TGlzdGVuZXIoJ21vdXNlZG93bicsIG9uTW91c2VEb3duKTtcclxuICAgIHdpbmRvdy5hZGRFdmVudExpc3RlbmVyKCdtb3VzZW1vdmUnLCBvbk1vdXNlTW92ZSk7XHJcbiAgICB3aW5kb3cuYWRkRXZlbnRMaXN0ZW5lcignbW91c2V1cCcsIG9uTW91c2VVcCk7XHJcbn1cclxuXHJcbmxldCByZXNpemVFZGdlID0gbnVsbDtcclxubGV0IHJlc2l6YWJsZSA9IGZhbHNlO1xyXG5cclxuZXhwb3J0IGZ1bmN0aW9uIHNldFJlc2l6YWJsZSh2YWx1ZSkge1xyXG4gICAgcmVzaXphYmxlID0gdmFsdWU7XHJcbn1cclxuXHJcbmZ1bmN0aW9uIHRlc3RSZXNpemUoZSkge1xyXG4gICAgaWYoIHJlc2l6ZUVkZ2UgKSB7XHJcbiAgICAgICAgaW52b2tlKFwicmVzaXplOlwiICsgcmVzaXplRWRnZSk7XHJcbiAgICAgICAgcmV0dXJuIHRydWVcclxuICAgIH1cclxuICAgIHJldHVybiBmYWxzZTtcclxufVxyXG5cclxuZnVuY3Rpb24gb25Nb3VzZURvd24oZSkge1xyXG5cclxuICAgIC8vIENoZWNrIGZvciByZXNpemluZyBvbiBXaW5kb3dzXHJcbiAgICBpZiggV0lORE9XUyApIHtcclxuICAgICAgICBpZiAodGVzdFJlc2l6ZSgpKSB7XHJcbiAgICAgICAgICAgIHJldHVybjtcclxuICAgICAgICB9XHJcbiAgICB9XHJcbiAgICBpZiAoZHJhZ1Rlc3QoZSkpIHtcclxuICAgICAgICAvLyBJZ25vcmUgZHJhZyBvbiBzY3JvbGxiYXJzXHJcbiAgICAgICAgaWYgKGUub2Zmc2V0WCA+IGUudGFyZ2V0LmNsaWVudFdpZHRoIHx8IGUub2Zmc2V0WSA+IGUudGFyZ2V0LmNsaWVudEhlaWdodCkge1xyXG4gICAgICAgICAgICByZXR1cm47XHJcbiAgICAgICAgfVxyXG4gICAgICAgIHNob3VsZERyYWcgPSB0cnVlO1xyXG4gICAgfSBlbHNlIHtcclxuICAgICAgICBzaG91bGREcmFnID0gZmFsc2U7XHJcbiAgICB9XHJcbn1cclxuXHJcbmZ1bmN0aW9uIG9uTW91c2VVcChlKSB7XHJcbiAgICBsZXQgbW91c2VQcmVzc2VkID0gZS5idXR0b25zICE9PSB1bmRlZmluZWQgPyBlLmJ1dHRvbnMgOiBlLndoaWNoO1xyXG4gICAgaWYgKG1vdXNlUHJlc3NlZCA+IDApIHtcclxuICAgICAgICBlbmREcmFnKCk7XHJcbiAgICB9XHJcbn1cclxuXHJcbmV4cG9ydCBmdW5jdGlvbiBlbmREcmFnKCkge1xyXG4gICAgZG9jdW1lbnQuYm9keS5zdHlsZS5jdXJzb3IgPSAnZGVmYXVsdCc7XHJcbiAgICBzaG91bGREcmFnID0gZmFsc2U7XHJcbn1cclxuXHJcbmZ1bmN0aW9uIHNldFJlc2l6ZShjdXJzb3IpIHtcclxuICAgIGRvY3VtZW50LmRvY3VtZW50RWxlbWVudC5zdHlsZS5jdXJzb3IgPSBjdXJzb3IgfHwgZGVmYXVsdEN1cnNvcjtcclxuICAgIHJlc2l6ZUVkZ2UgPSBjdXJzb3I7XHJcbn1cclxuXHJcbmZ1bmN0aW9uIG9uTW91c2VNb3ZlKGUpIHtcclxuICAgIGlmIChzaG91bGREcmFnKSB7XHJcbiAgICAgICAgc2hvdWxkRHJhZyA9IGZhbHNlO1xyXG4gICAgICAgIGxldCBtb3VzZVByZXNzZWQgPSBlLmJ1dHRvbnMgIT09IHVuZGVmaW5lZCA/IGUuYnV0dG9ucyA6IGUud2hpY2g7XHJcbiAgICAgICAgaWYgKG1vdXNlUHJlc3NlZCA+IDApIHtcclxuICAgICAgICAgICAgaW52b2tlKFwiZHJhZ1wiKTtcclxuICAgICAgICB9XHJcbiAgICAgICAgcmV0dXJuO1xyXG4gICAgfVxyXG5cclxuICAgIGlmIChXSU5ET1dTKSB7XHJcbiAgICAgICAgaWYgKHJlc2l6YWJsZSkge1xyXG4gICAgICAgICAgICBoYW5kbGVSZXNpemUoZSk7XHJcbiAgICAgICAgfVxyXG4gICAgfVxyXG59XHJcblxyXG5sZXQgZGVmYXVsdEN1cnNvciA9IFwiYXV0b1wiO1xyXG5cclxuZnVuY3Rpb24gaGFuZGxlUmVzaXplKGUpIHtcclxuICAgIGxldCByZXNpemVIYW5kbGVIZWlnaHQgPSBHZXRGbGFnKFwic3lzdGVtLnJlc2l6ZUhhbmRsZUhlaWdodFwiKSB8fCA1O1xyXG4gICAgbGV0IHJlc2l6ZUhhbmRsZVdpZHRoID0gR2V0RmxhZyhcInN5c3RlbS5yZXNpemVIYW5kbGVXaWR0aFwiKSB8fCA1O1xyXG5cclxuICAgIC8vIEV4dHJhIHBpeGVscyBmb3IgdGhlIGNvcm5lciBhcmVhc1xyXG4gICAgbGV0IGNvcm5lckV4dHJhID0gR2V0RmxhZyhcInJlc2l6ZUNvcm5lckV4dHJhXCIpIHx8IDEwO1xyXG5cclxuICAgIGxldCByaWdodEJvcmRlciA9IHdpbmRvdy5vdXRlcldpZHRoIC0gZS5jbGllbnRYIDwgcmVzaXplSGFuZGxlV2lkdGg7XHJcbiAgICBsZXQgbGVmdEJvcmRlciA9IGUuY2xpZW50WCA8IHJlc2l6ZUhhbmRsZVdpZHRoO1xyXG4gICAgbGV0IHRvcEJvcmRlciA9IGUuY2xpZW50WSA8IHJlc2l6ZUhhbmRsZUhlaWdodDtcclxuICAgIGxldCBib3R0b21Cb3JkZXIgPSB3aW5kb3cub3V0ZXJIZWlnaHQgLSBlLmNsaWVudFkgPCByZXNpemVIYW5kbGVIZWlnaHQ7XHJcblxyXG4gICAgLy8gQWRqdXN0IGZvciBjb3JuZXJzXHJcbiAgICBsZXQgcmlnaHRDb3JuZXIgPSB3aW5kb3cub3V0ZXJXaWR0aCAtIGUuY2xpZW50WCA8IChyZXNpemVIYW5kbGVXaWR0aCArIGNvcm5lckV4dHJhKTtcclxuICAgIGxldCBsZWZ0Q29ybmVyID0gZS5jbGllbnRYIDwgKHJlc2l6ZUhhbmRsZVdpZHRoICsgY29ybmVyRXh0cmEpO1xyXG4gICAgbGV0IHRvcENvcm5lciA9IGUuY2xpZW50WSA8IChyZXNpemVIYW5kbGVIZWlnaHQgKyBjb3JuZXJFeHRyYSk7XHJcbiAgICBsZXQgYm90dG9tQ29ybmVyID0gd2luZG93Lm91dGVySGVpZ2h0IC0gZS5jbGllbnRZIDwgKHJlc2l6ZUhhbmRsZUhlaWdodCArIGNvcm5lckV4dHJhKTtcclxuXHJcbiAgICAvLyBJZiB3ZSBhcmVuJ3Qgb24gYW4gZWRnZSwgYnV0IHdlcmUsIHJlc2V0IHRoZSBjdXJzb3IgdG8gZGVmYXVsdFxyXG4gICAgaWYgKCFsZWZ0Qm9yZGVyICYmICFyaWdodEJvcmRlciAmJiAhdG9wQm9yZGVyICYmICFib3R0b21Cb3JkZXIgJiYgcmVzaXplRWRnZSAhPT0gdW5kZWZpbmVkKSB7XHJcbiAgICAgICAgc2V0UmVzaXplKCk7XHJcbiAgICB9XHJcbiAgICAvLyBBZGp1c3RlZCBmb3IgY29ybmVyIGFyZWFzXHJcbiAgICBlbHNlIGlmIChyaWdodENvcm5lciAmJiBib3R0b21Db3JuZXIpIHNldFJlc2l6ZShcInNlLXJlc2l6ZVwiKTtcclxuICAgIGVsc2UgaWYgKGxlZnRDb3JuZXIgJiYgYm90dG9tQ29ybmVyKSBzZXRSZXNpemUoXCJzdy1yZXNpemVcIik7XHJcbiAgICBlbHNlIGlmIChsZWZ0Q29ybmVyICYmIHRvcENvcm5lcikgc2V0UmVzaXplKFwibnctcmVzaXplXCIpO1xyXG4gICAgZWxzZSBpZiAodG9wQ29ybmVyICYmIHJpZ2h0Q29ybmVyKSBzZXRSZXNpemUoXCJuZS1yZXNpemVcIik7XHJcbiAgICBlbHNlIGlmIChsZWZ0Qm9yZGVyKSBzZXRSZXNpemUoXCJ3LXJlc2l6ZVwiKTtcclxuICAgIGVsc2UgaWYgKHRvcEJvcmRlcikgc2V0UmVzaXplKFwibi1yZXNpemVcIik7XHJcbiAgICBlbHNlIGlmIChib3R0b21Cb3JkZXIpIHNldFJlc2l6ZShcInMtcmVzaXplXCIpO1xyXG4gICAgZWxzZSBpZiAocmlnaHRCb3JkZXIpIHNldFJlc2l6ZShcImUtcmVzaXplXCIpO1xyXG59XHJcbiIsICIvKlxyXG4gXyAgICAgX18gICAgIF8gX19cclxufCB8ICAvIC9fX18gXyhfKSAvX19fX1xyXG58IHwgL3wgLyAvIF9fIGAvIC8gLyBfX18vXHJcbnwgfC8gfC8gLyAvXy8gLyAvIChfXyAgKVxyXG58X18vfF9fL1xcX18sXy9fL18vX19fXy9cclxuVGhlIGVsZWN0cm9uIGFsdGVybmF0aXZlIGZvciBHb1xyXG4oYykgTGVhIEFudGhvbnkgMjAxOS1wcmVzZW50XHJcbiovXHJcbi8qIGpzaGludCBlc3ZlcnNpb246IDkgKi9cclxuXHJcblxyXG5pbXBvcnQgKiBhcyBDbGlwYm9hcmQgZnJvbSAnLi9jbGlwYm9hcmQnO1xyXG5pbXBvcnQgKiBhcyBBcHBsaWNhdGlvbiBmcm9tICcuL2FwcGxpY2F0aW9uJztcclxuaW1wb3J0ICogYXMgU2NyZWVucyBmcm9tICcuL3NjcmVlbnMnO1xyXG5pbXBvcnQgKiBhcyBTeXN0ZW0gZnJvbSAnLi9zeXN0ZW0nO1xyXG5pbXBvcnQgKiBhcyBCcm93c2VyIGZyb20gJy4vYnJvd3Nlcic7XHJcbmltcG9ydCB7UGx1Z2luLCBDYWxsLCBjYWxsRXJyb3JDYWxsYmFjaywgY2FsbENhbGxiYWNrLCBDYWxsQnlJRCwgQ2FsbEJ5TmFtZX0gZnJvbSBcIi4vY2FsbHNcIjtcclxuaW1wb3J0IHtjbGllbnRJZH0gZnJvbSAnLi9ydW50aW1lJztcclxuaW1wb3J0IHtuZXdXaW5kb3d9IGZyb20gXCIuL3dpbmRvd1wiO1xyXG5pbXBvcnQge2Rpc3BhdGNoV2FpbHNFdmVudCwgRW1pdCwgT2ZmLCBPZmZBbGwsIE9uLCBPbmNlLCBPbk11bHRpcGxlfSBmcm9tIFwiLi9ldmVudHNcIjtcclxuaW1wb3J0IHtkaWFsb2dDYWxsYmFjaywgZGlhbG9nRXJyb3JDYWxsYmFjaywgRXJyb3IsIEluZm8sIE9wZW5GaWxlLCBRdWVzdGlvbiwgU2F2ZUZpbGUsIFdhcm5pbmcsfSBmcm9tIFwiLi9kaWFsb2dzXCI7XHJcbmltcG9ydCB7c2V0dXBDb250ZXh0TWVudXN9IGZyb20gXCIuL2NvbnRleHRtZW51XCI7XHJcbmltcG9ydCB7cmVsb2FkV01MfSBmcm9tIFwiLi93bWxcIjtcclxuaW1wb3J0IHtzZXR1cERyYWcsIGVuZERyYWcsIHNldFJlc2l6YWJsZX0gZnJvbSBcIi4vZHJhZ1wiO1xyXG5cclxud2luZG93LndhaWxzID0ge1xyXG4gICAgLi4ubmV3UnVudGltZShudWxsKSxcclxuICAgIENhcGFiaWxpdGllczoge30sXHJcbiAgICBjbGllbnRJZDogY2xpZW50SWQsXHJcbn07XHJcblxyXG5mZXRjaChcIi93YWlscy9jYXBhYmlsaXRpZXNcIikudGhlbigocmVzcG9uc2UpID0+IHtcclxuICAgIHJlc3BvbnNlLmpzb24oKS50aGVuKChkYXRhKSA9PiB7XHJcbiAgICAgICAgd2luZG93LndhaWxzLkNhcGFiaWxpdGllcyA9IGRhdGE7XHJcbiAgICB9KTtcclxufSk7XHJcblxyXG4vLyBJbnRlcm5hbCB3YWlscyBlbmRwb2ludHNcclxud2luZG93Ll93YWlscyA9IHtcclxuICAgIGRpYWxvZ0NhbGxiYWNrLFxyXG4gICAgZGlhbG9nRXJyb3JDYWxsYmFjayxcclxuICAgIGRpc3BhdGNoV2FpbHNFdmVudCxcclxuICAgIGNhbGxDYWxsYmFjayxcclxuICAgIGNhbGxFcnJvckNhbGxiYWNrLFxyXG4gICAgZW5kRHJhZyxcclxuICAgIHNldFJlc2l6YWJsZSxcclxufTtcclxuXHJcbmV4cG9ydCBmdW5jdGlvbiBuZXdSdW50aW1lKHdpbmRvd05hbWUpIHtcclxuICAgIHJldHVybiB7XHJcbiAgICAgICAgQ2xpcGJvYXJkOiB7XHJcbiAgICAgICAgICAgIC4uLkNsaXBib2FyZFxyXG4gICAgICAgIH0sXHJcbiAgICAgICAgQXBwbGljYXRpb246IHtcclxuICAgICAgICAgICAgLi4uQXBwbGljYXRpb24sXHJcbiAgICAgICAgICAgIEdldFdpbmRvd0J5TmFtZSh3aW5kb3dOYW1lKSB7XHJcbiAgICAgICAgICAgICAgICByZXR1cm4gbmV3UnVudGltZSh3aW5kb3dOYW1lKTtcclxuICAgICAgICAgICAgfVxyXG4gICAgICAgIH0sXHJcbiAgICAgICAgU3lzdGVtLFxyXG4gICAgICAgIFNjcmVlbnMsXHJcbiAgICAgICAgQnJvd3NlcixcclxuICAgICAgICBDYWxsLFxyXG4gICAgICAgIENhbGxCeUlELFxyXG4gICAgICAgIENhbGxCeU5hbWUsXHJcbiAgICAgICAgUGx1Z2luLFxyXG4gICAgICAgIFdNTDoge1xyXG4gICAgICAgICAgICBSZWxvYWQ6IHJlbG9hZFdNTCxcclxuICAgICAgICB9LFxyXG4gICAgICAgIERpYWxvZzoge1xyXG4gICAgICAgICAgICBJbmZvLFxyXG4gICAgICAgICAgICBXYXJuaW5nLFxyXG4gICAgICAgICAgICBFcnJvcixcclxuICAgICAgICAgICAgUXVlc3Rpb24sXHJcbiAgICAgICAgICAgIE9wZW5GaWxlLFxyXG4gICAgICAgICAgICBTYXZlRmlsZSxcclxuICAgICAgICB9LFxyXG4gICAgICAgIEV2ZW50czoge1xyXG4gICAgICAgICAgICBFbWl0LFxyXG4gICAgICAgICAgICBPbixcclxuICAgICAgICAgICAgT25jZSxcclxuICAgICAgICAgICAgT25NdWx0aXBsZSxcclxuICAgICAgICAgICAgT2ZmLFxyXG4gICAgICAgICAgICBPZmZBbGwsXHJcbiAgICAgICAgfSxcclxuICAgICAgICBXaW5kb3c6IG5ld1dpbmRvdyh3aW5kb3dOYW1lKSxcclxuICAgIH07XHJcbn1cclxuXHJcbmlmIChERUJVRykge1xyXG4gICAgY29uc29sZS5sb2coXCJXYWlscyB2My4wLjAgRGVidWcgTW9kZSBFbmFibGVkXCIpO1xyXG59XHJcblxyXG5zZXR1cENvbnRleHRNZW51cygpO1xyXG5zZXR1cERyYWcoKTtcclxuXHJcbmRvY3VtZW50LmFkZEV2ZW50TGlzdGVuZXIoXCJET01Db250ZW50TG9hZGVkXCIsIGZ1bmN0aW9uKCkge1xyXG4gICAgcmVsb2FkV01MKCk7XHJcbn0pO1xyXG4iXSwKICAibWFwcGluZ3MiOiAiOzs7Ozs7OztBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7OztBQ0FBLE1BQUksY0FDRjtBQVdLLE1BQUksU0FBUyxDQUFDLE9BQU8sT0FBTztBQUNqQyxRQUFJLEtBQUs7QUFDVCxRQUFJLElBQUk7QUFDUixXQUFPLEtBQUs7QUFDVixZQUFNLFlBQWEsS0FBSyxPQUFPLElBQUksS0FBTSxDQUFDO0FBQUEsSUFDNUM7QUFDQSxXQUFPO0FBQUEsRUFDVDs7O0FDTkEsTUFBTSxhQUFhLE9BQU8sU0FBUyxTQUFTO0FBRXJDLE1BQU0sY0FBYztBQUFBLElBQ3ZCLE1BQU07QUFBQSxJQUNOLFdBQVc7QUFBQSxJQUNYLGFBQWE7QUFBQSxJQUNiLFFBQVE7QUFBQSxJQUNSLGFBQWE7QUFBQSxJQUNiLFFBQVE7QUFBQSxJQUNSLFFBQVE7QUFBQSxJQUNSLFNBQVM7QUFBQSxJQUNULFFBQVE7QUFBQSxJQUNSLFNBQVM7QUFBQSxFQUNiO0FBQ08sTUFBSSxXQUFXLE9BQU87QUEwQzdCLFdBQVMsa0JBQWtCLFVBQVUsUUFBUSxZQUFZLE1BQU07QUFDM0QsUUFBSSxNQUFNLElBQUksSUFBSSxVQUFVO0FBQzVCLFFBQUksYUFBYSxPQUFPLFVBQVUsUUFBUTtBQUMxQyxRQUFJLGFBQWEsT0FBTyxVQUFVLE1BQU07QUFDeEMsUUFBSSxlQUFlO0FBQUEsTUFDZixTQUFTLENBQUM7QUFBQSxJQUNkO0FBQ0EsUUFBSSxZQUFZO0FBQ1osbUJBQWEsUUFBUSxxQkFBcUIsSUFBSTtBQUFBLElBQ2xEO0FBQ0EsUUFBSSxNQUFNO0FBQ04sVUFBSSxhQUFhLE9BQU8sUUFBUSxLQUFLLFVBQVUsSUFBSSxDQUFDO0FBQUEsSUFDeEQ7QUFDQSxpQkFBYSxRQUFRLG1CQUFtQixJQUFJO0FBQzVDLFdBQU8sSUFBSSxRQUFRLENBQUMsU0FBUyxXQUFXO0FBQ3BDLFlBQU0sS0FBSyxZQUFZLEVBQ2xCLEtBQUssY0FBWTtBQUNkLFlBQUksU0FBUyxJQUFJO0FBRWIsY0FBSSxTQUFTLFFBQVEsSUFBSSxjQUFjLEtBQUssU0FBUyxRQUFRLElBQUksY0FBYyxFQUFFLFFBQVEsa0JBQWtCLE1BQU0sSUFBSTtBQUNqSCxtQkFBTyxTQUFTLEtBQUs7QUFBQSxVQUN6QixPQUFPO0FBQ0gsbUJBQU8sU0FBUyxLQUFLO0FBQUEsVUFDekI7QUFBQSxRQUNKO0FBQ0EsZUFBTyxNQUFNLFNBQVMsVUFBVSxDQUFDO0FBQUEsTUFDckMsQ0FBQyxFQUNBLEtBQUssVUFBUSxRQUFRLElBQUksQ0FBQyxFQUMxQixNQUFNLFdBQVMsT0FBTyxLQUFLLENBQUM7QUFBQSxJQUNyQyxDQUFDO0FBQUEsRUFDTDtBQUVPLFdBQVMsdUJBQXVCLFFBQVEsWUFBWTtBQUN2RCxXQUFPLFNBQVUsUUFBUSxPQUFLLE1BQU07QUFDaEMsYUFBTyxrQkFBa0IsUUFBUSxRQUFRLFlBQVksSUFBSTtBQUFBLElBQzdEO0FBQUEsRUFDSjs7O0FGM0ZBLE1BQUksT0FBTyx1QkFBdUIsWUFBWSxTQUFTO0FBRXZELE1BQUksbUJBQW1CO0FBQ3ZCLE1BQUksZ0JBQWdCO0FBS2IsV0FBUyxRQUFRLE1BQU07QUFDMUIsU0FBSyxLQUFLLGtCQUFrQixFQUFDLEtBQUksQ0FBQztBQUFBLEVBQ3RDO0FBTU8sV0FBUyxPQUFPO0FBQ25CLFdBQU8sS0FBSyxhQUFhO0FBQUEsRUFDN0I7OztBR2hDQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFjQSxNQUFJQSxRQUFPLHVCQUF1QixZQUFZLFdBQVc7QUFFekQsTUFBSSxVQUFVO0FBQUEsSUFDVixNQUFNO0FBQUEsSUFDTixNQUFNO0FBQUEsSUFDTixNQUFNO0FBQUEsRUFDVjtBQUtPLFdBQVMsT0FBTztBQUNuQixTQUFLQSxNQUFLLFFBQVEsSUFBSTtBQUFBLEVBQzFCO0FBS08sV0FBUyxPQUFPO0FBQ25CLFNBQUtBLE1BQUssUUFBUSxJQUFJO0FBQUEsRUFDMUI7QUFNTyxXQUFTLE9BQU87QUFDbkIsU0FBS0EsTUFBSyxRQUFRLElBQUk7QUFBQSxFQUMxQjs7O0FDMUNBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQWtCQSxNQUFJQyxRQUFPLHVCQUF1QixZQUFZLE9BQU87QUFFckQsTUFBSSxnQkFBZ0I7QUFDcEIsTUFBSSxvQkFBb0I7QUFDeEIsTUFBSSxvQkFBb0I7QUFNakIsV0FBUyxTQUFTO0FBQ3JCLFdBQU9BLE1BQUssYUFBYTtBQUFBLEVBQzdCO0FBTU8sV0FBUyxhQUFhO0FBQ3pCLFdBQU9BLE1BQUssaUJBQWlCO0FBQUEsRUFDakM7QUFPTyxXQUFTLGFBQWE7QUFDekIsV0FBT0EsTUFBSyxpQkFBaUI7QUFBQSxFQUNqQzs7O0FDL0NBO0FBQUE7QUFBQTtBQUFBO0FBY0EsTUFBSUMsUUFBTyx1QkFBdUIsWUFBWSxNQUFNO0FBRXBELE1BQUksbUJBQW1CO0FBTWhCLFdBQVMsYUFBYTtBQUN6QixXQUFPQSxNQUFLLGdCQUFnQjtBQUFBLEVBQ2hDOzs7QUN4QkE7QUFBQTtBQUFBO0FBQUE7QUFjQSxNQUFJQyxRQUFPLHVCQUF1QixZQUFZLE9BQU87QUFFckQsTUFBSSxpQkFBaUI7QUFNZCxXQUFTLFFBQVEsS0FBSztBQUN6QixTQUFLQSxNQUFLLGdCQUFnQixFQUFDLElBQUcsQ0FBQztBQUFBLEVBQ25DOzs7QUNSQSxNQUFJQyxRQUFPLHVCQUF1QixZQUFZLElBQUk7QUFFbEQsTUFBSSxjQUFjO0FBRWxCLE1BQUksZ0JBQWdCLG9CQUFJLElBQUk7QUFFNUIsV0FBUyxhQUFhO0FBQ2xCLFFBQUk7QUFDSixPQUFHO0FBQ0MsZUFBUyxPQUFPO0FBQUEsSUFDcEIsU0FBUyxjQUFjLElBQUksTUFBTTtBQUNqQyxXQUFPO0FBQUEsRUFDWDtBQUVPLFdBQVMsYUFBYSxJQUFJLE1BQU0sUUFBUTtBQUMzQyxRQUFJLElBQUksY0FBYyxJQUFJLEVBQUU7QUFDNUIsUUFBSSxHQUFHO0FBQ0gsVUFBSSxRQUFRO0FBQ1IsVUFBRSxRQUFRLEtBQUssTUFBTSxJQUFJLENBQUM7QUFBQSxNQUM5QixPQUFPO0FBQ0gsVUFBRSxRQUFRLElBQUk7QUFBQSxNQUNsQjtBQUNBLG9CQUFjLE9BQU8sRUFBRTtBQUFBLElBQzNCO0FBQUEsRUFDSjtBQUVPLFdBQVMsa0JBQWtCLElBQUksU0FBUztBQUMzQyxRQUFJLElBQUksY0FBYyxJQUFJLEVBQUU7QUFDNUIsUUFBSSxHQUFHO0FBQ0gsUUFBRSxPQUFPLE9BQU87QUFDaEIsb0JBQWMsT0FBTyxFQUFFO0FBQUEsSUFDM0I7QUFBQSxFQUNKO0FBRUEsV0FBUyxZQUFZLE1BQU0sU0FBUztBQUNoQyxXQUFPLElBQUksUUFBUSxDQUFDLFNBQVMsV0FBVztBQUNwQyxVQUFJLEtBQUssV0FBVztBQUNwQixnQkFBVSxXQUFXLENBQUM7QUFDdEIsY0FBUSxTQUFTLElBQUk7QUFFckIsb0JBQWMsSUFBSSxJQUFJLEVBQUMsU0FBUyxPQUFNLENBQUM7QUFDdkMsTUFBQUEsTUFBSyxNQUFNLE9BQU8sRUFBRSxNQUFNLENBQUMsVUFBVTtBQUNqQyxlQUFPLEtBQUs7QUFDWixzQkFBYyxPQUFPLEVBQUU7QUFBQSxNQUMzQixDQUFDO0FBQUEsSUFDTCxDQUFDO0FBQUEsRUFDTDtBQUVPLFdBQVMsS0FBSyxTQUFTO0FBQzFCLFdBQU8sWUFBWSxhQUFhLE9BQU87QUFBQSxFQUMzQztBQUVPLFdBQVMsV0FBVyxTQUFTLE1BQU07QUFHdEMsUUFBSSxPQUFPLFNBQVMsWUFBWSxLQUFLLE1BQU0sR0FBRyxFQUFFLFdBQVcsR0FBRztBQUMxRCxZQUFNLElBQUksTUFBTSxvRUFBb0U7QUFBQSxJQUN4RjtBQUVBLFFBQUksUUFBUSxLQUFLLE1BQU0sR0FBRztBQUUxQixXQUFPLFlBQVksYUFBYTtBQUFBLE1BQzVCLGFBQWEsTUFBTSxDQUFDO0FBQUEsTUFDcEIsWUFBWSxNQUFNLENBQUM7QUFBQSxNQUNuQixZQUFZLE1BQU0sQ0FBQztBQUFBLE1BQ25CO0FBQUEsSUFDSixDQUFDO0FBQUEsRUFDTDtBQUVPLFdBQVMsU0FBUyxhQUFhLE1BQU07QUFDeEMsV0FBTyxZQUFZLGFBQWE7QUFBQSxNQUM1QjtBQUFBLE1BQ0E7QUFBQSxJQUNKLENBQUM7QUFBQSxFQUNMO0FBU08sV0FBUyxPQUFPLFlBQVksZUFBZSxNQUFNO0FBQ3BELFdBQU8sWUFBWSxhQUFhO0FBQUEsTUFDNUIsYUFBYTtBQUFBLE1BQ2IsWUFBWTtBQUFBLE1BQ1o7QUFBQSxNQUNBO0FBQUEsSUFDSixDQUFDO0FBQUEsRUFDTDs7O0FDdEZBLE1BQUksZUFBZTtBQUNuQixNQUFJLGlCQUFpQjtBQUNyQixNQUFJLG1CQUFtQjtBQUN2QixNQUFJLHFCQUFxQjtBQUN6QixNQUFJLGdCQUFnQjtBQUNwQixNQUFJLGFBQWE7QUFDakIsTUFBSSxtQkFBbUI7QUFDdkIsTUFBSSxtQkFBbUI7QUFDdkIsTUFBSSx1QkFBdUI7QUFDM0IsTUFBSSw0QkFBNEI7QUFDaEMsTUFBSSx5QkFBeUI7QUFDN0IsTUFBSSxlQUFlO0FBQ25CLE1BQUksYUFBYTtBQUNqQixNQUFJLGlCQUFpQjtBQUNyQixNQUFJLG1CQUFtQjtBQUN2QixNQUFJLHVCQUF1QjtBQUMzQixNQUFJLGlCQUFpQjtBQUNyQixNQUFJLG1CQUFtQjtBQUN2QixNQUFJLGdCQUFnQjtBQUNwQixNQUFJLGFBQWE7QUFDakIsTUFBSSxjQUFjO0FBQ2xCLE1BQUksNEJBQTRCO0FBQ2hDLE1BQUkscUJBQXFCO0FBQ3pCLE1BQUksY0FBYztBQUNsQixNQUFJLGVBQWU7QUFDbkIsTUFBSSxlQUFlO0FBQ25CLE1BQUksZ0JBQWdCO0FBQ3BCLE1BQUksa0JBQWtCO0FBQ3RCLE1BQUkscUJBQXFCO0FBQ3pCLE1BQUkscUJBQXFCO0FBRWxCLFdBQVMsVUFBVSxZQUFZO0FBQ2xDLFFBQUlDLFNBQU8sdUJBQXVCLFlBQVksUUFBUSxVQUFVO0FBQ2hFLFdBQU87QUFBQTtBQUFBO0FBQUE7QUFBQSxNQUtILFFBQVEsTUFBTSxLQUFLQSxPQUFLLFlBQVk7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBLE1BTXBDLFVBQVUsQ0FBQyxVQUFVLEtBQUtBLE9BQUssZ0JBQWdCLEVBQUMsTUFBSyxDQUFDO0FBQUE7QUFBQTtBQUFBO0FBQUEsTUFLdEQsWUFBWSxNQUFNLEtBQUtBLE9BQUssZ0JBQWdCO0FBQUE7QUFBQTtBQUFBO0FBQUEsTUFLNUMsY0FBYyxNQUFNLEtBQUtBLE9BQUssa0JBQWtCO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBLE1BT2hELFNBQVMsQ0FBQyxPQUFPLFdBQVdBLE9BQUssZUFBZSxFQUFDLE9BQU0sT0FBTSxDQUFDO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQSxNQU05RCxNQUFNLE1BQU07QUFBRSxlQUFPQSxPQUFLLFVBQVU7QUFBQSxNQUFHO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBLE1BT3ZDLFlBQVksQ0FBQyxPQUFPLFdBQVcsS0FBS0EsT0FBSyxrQkFBa0IsRUFBQyxPQUFNLE9BQU0sQ0FBQztBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQSxNQU96RSxZQUFZLENBQUMsT0FBTyxXQUFXLEtBQUtBLE9BQUssa0JBQWtCLEVBQUMsT0FBTSxPQUFNLENBQUM7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBLE1BTXpFLGdCQUFnQixDQUFDLFVBQVUsS0FBS0EsT0FBSyxzQkFBc0IsRUFBQyxhQUFZLE1BQUssQ0FBQztBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQSxNQU85RSxxQkFBcUIsQ0FBQyxHQUFHLE1BQU1BLE9BQUssMkJBQTJCLEVBQUMsR0FBRSxFQUFDLENBQUM7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBLE1BTXBFLGtCQUFrQixNQUFNO0FBQUUsZUFBT0EsT0FBSyxzQkFBc0I7QUFBQSxNQUFHO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQSxNQU0vRCxRQUFRLE1BQU07QUFBRSxlQUFPQSxPQUFLLFlBQVk7QUFBQSxNQUFHO0FBQUE7QUFBQTtBQUFBO0FBQUEsTUFLM0MsTUFBTSxNQUFNLEtBQUtBLE9BQUssVUFBVTtBQUFBO0FBQUE7QUFBQTtBQUFBLE1BS2hDLFVBQVUsTUFBTSxLQUFLQSxPQUFLLGNBQWM7QUFBQTtBQUFBO0FBQUE7QUFBQSxNQUt4QyxNQUFNLE1BQU0sS0FBS0EsT0FBSyxVQUFVO0FBQUE7QUFBQTtBQUFBO0FBQUEsTUFLaEMsT0FBTyxNQUFNLEtBQUtBLE9BQUssV0FBVztBQUFBO0FBQUE7QUFBQTtBQUFBLE1BS2xDLGdCQUFnQixNQUFNLEtBQUtBLE9BQUssb0JBQW9CO0FBQUE7QUFBQTtBQUFBO0FBQUEsTUFLcEQsWUFBWSxNQUFNLEtBQUtBLE9BQUssZ0JBQWdCO0FBQUE7QUFBQTtBQUFBO0FBQUEsTUFLNUMsVUFBVSxNQUFNLEtBQUtBLE9BQUssY0FBYztBQUFBO0FBQUE7QUFBQTtBQUFBLE1BS3hDLFlBQVksTUFBTSxLQUFLQSxPQUFLLGdCQUFnQjtBQUFBO0FBQUE7QUFBQTtBQUFBLE1BSzVDLFNBQVMsTUFBTSxLQUFLQSxPQUFLLGFBQWE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBLE1BU3RDLHFCQUFxQixDQUFDLEdBQUcsR0FBRyxHQUFHLE1BQU0sS0FBS0EsT0FBSywyQkFBMkIsRUFBQyxHQUFHLEdBQUcsR0FBRyxFQUFDLENBQUM7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBLE1BTXRGLGNBQWMsQ0FBQ0MsZUFBYyxLQUFLRCxPQUFLLG9CQUFvQixFQUFDLFdBQUFDLFdBQVMsQ0FBQztBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUEsTUFNdEUsT0FBTyxNQUFNO0FBQUUsZUFBT0QsT0FBSyxXQUFXO0FBQUEsTUFBRztBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUEsTUFNekMsUUFBUSxNQUFNO0FBQUUsZUFBT0EsT0FBSyxZQUFZO0FBQUEsTUFBRztBQUFBO0FBQUE7QUFBQTtBQUFBLE1BSzNDLFFBQVEsTUFBTSxLQUFLQSxPQUFLLFlBQVk7QUFBQTtBQUFBO0FBQUE7QUFBQSxNQUtwQyxTQUFTLE1BQU0sS0FBS0EsT0FBSyxhQUFhO0FBQUE7QUFBQTtBQUFBO0FBQUEsTUFLdEMsV0FBVyxNQUFNLEtBQUtBLE9BQUssZUFBZTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUEsTUFNMUMsY0FBYyxNQUFNO0FBQUUsZUFBT0EsT0FBSyxrQkFBa0I7QUFBQSxNQUFHO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQSxNQU12RCxjQUFjLENBQUMsY0FBYyxLQUFLQSxPQUFLLG9CQUFvQixFQUFDLFVBQVMsQ0FBQztBQUFBLElBQzFFO0FBQUEsRUFDSjs7O0FDak5BLE1BQUlFLFFBQU8sdUJBQXVCLFlBQVksTUFBTTtBQUNwRCxNQUFJLFlBQVk7QUFPaEIsTUFBTSxXQUFOLE1BQWU7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBLElBUVgsWUFBWSxXQUFXLFVBQVUsY0FBYztBQUMzQyxXQUFLLFlBQVk7QUFFakIsV0FBSyxlQUFlLGdCQUFnQjtBQUdwQyxXQUFLLFdBQVcsQ0FBQyxTQUFTO0FBQ3RCLGlCQUFTLElBQUk7QUFFYixZQUFJLEtBQUssaUJBQWlCLElBQUk7QUFDMUIsaUJBQU87QUFBQSxRQUNYO0FBRUEsYUFBSyxnQkFBZ0I7QUFDckIsZUFBTyxLQUFLLGlCQUFpQjtBQUFBLE1BQ2pDO0FBQUEsSUFDSjtBQUFBLEVBQ0o7QUFVTyxNQUFNLGFBQU4sTUFBaUI7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQSxJQU9wQixZQUFZLE1BQU0sT0FBTyxNQUFNO0FBQzNCLFdBQUssT0FBTztBQUNaLFdBQUssT0FBTztBQUFBLElBQ2hCO0FBQUEsRUFDSjtBQUVPLE1BQU0saUJBQWlCLG9CQUFJLElBQUk7QUFXL0IsV0FBUyxXQUFXLFdBQVcsVUFBVSxjQUFjO0FBQzFELFFBQUksWUFBWSxlQUFlLElBQUksU0FBUyxLQUFLLENBQUM7QUFDbEQsVUFBTSxlQUFlLElBQUksU0FBUyxXQUFXLFVBQVUsWUFBWTtBQUNuRSxjQUFVLEtBQUssWUFBWTtBQUMzQixtQkFBZSxJQUFJLFdBQVcsU0FBUztBQUN2QyxXQUFPLE1BQU0sWUFBWSxZQUFZO0FBQUEsRUFDekM7QUFVTyxXQUFTLEdBQUcsV0FBVyxVQUFVO0FBQ3BDLFdBQU8sV0FBVyxXQUFXLFVBQVUsRUFBRTtBQUFBLEVBQzdDO0FBVU8sV0FBUyxLQUFLLFdBQVcsVUFBVTtBQUN0QyxXQUFPLFdBQVcsV0FBVyxVQUFVLENBQUM7QUFBQSxFQUM1QztBQU9BLFdBQVMsWUFBWSxVQUFVO0FBQzNCLFVBQU0sWUFBWSxTQUFTO0FBRTNCLFFBQUksWUFBWSxlQUFlLElBQUksU0FBUyxFQUFFLE9BQU8sT0FBSyxNQUFNLFFBQVE7QUFDeEUsUUFBSSxVQUFVLFdBQVcsR0FBRztBQUN4QixxQkFBZSxPQUFPLFNBQVM7QUFBQSxJQUNuQyxPQUFPO0FBQ0gscUJBQWUsSUFBSSxXQUFXLFNBQVM7QUFBQSxJQUMzQztBQUFBLEVBQ0o7QUFRTyxXQUFTLG1CQUFtQixPQUFPO0FBQ3RDLFFBQUksWUFBWSxlQUFlLElBQUksTUFBTSxJQUFJO0FBQzdDLFFBQUksV0FBVztBQUVYLFVBQUksV0FBVyxDQUFDO0FBQ2hCLGdCQUFVLFFBQVEsY0FBWTtBQUMxQixZQUFJLFNBQVMsU0FBUyxTQUFTLEtBQUs7QUFDcEMsWUFBSSxRQUFRO0FBQ1IsbUJBQVMsS0FBSyxRQUFRO0FBQUEsUUFDMUI7QUFBQSxNQUNKLENBQUM7QUFFRCxVQUFJLFNBQVMsU0FBUyxHQUFHO0FBQ3JCLG9CQUFZLFVBQVUsT0FBTyxPQUFLLENBQUMsU0FBUyxTQUFTLENBQUMsQ0FBQztBQUN2RCxZQUFJLFVBQVUsV0FBVyxHQUFHO0FBQ3hCLHlCQUFlLE9BQU8sTUFBTSxJQUFJO0FBQUEsUUFDcEMsT0FBTztBQUNILHlCQUFlLElBQUksTUFBTSxNQUFNLFNBQVM7QUFBQSxRQUM1QztBQUFBLE1BQ0o7QUFBQSxJQUNKO0FBQUEsRUFDSjtBQVdPLFdBQVMsSUFBSSxjQUFjLHNCQUFzQjtBQUNwRCxRQUFJLGlCQUFpQixDQUFDLFdBQVcsR0FBRyxvQkFBb0I7QUFDeEQsbUJBQWUsUUFBUSxDQUFBQyxlQUFhO0FBQ2hDLHFCQUFlLE9BQU9BLFVBQVM7QUFBQSxJQUNuQyxDQUFDO0FBQUEsRUFDTDtBQU9PLFdBQVMsU0FBUztBQUNyQixtQkFBZSxNQUFNO0FBQUEsRUFDekI7QUFNTyxXQUFTLEtBQUssT0FBTztBQUN4QixTQUFLRCxNQUFLLFdBQVcsS0FBSztBQUFBLEVBQzlCOzs7QUMzS0EsTUFBSUUsUUFBTyx1QkFBdUIsWUFBWSxNQUFNO0FBRXBELE1BQUksYUFBYTtBQUNqQixNQUFJLGdCQUFnQjtBQUNwQixNQUFJLGNBQWM7QUFDbEIsTUFBSSxpQkFBaUI7QUFDckIsTUFBSSxpQkFBaUI7QUFDckIsTUFBSSxpQkFBaUI7QUFHckIsTUFBSSxrQkFBa0Isb0JBQUksSUFBSTtBQUU5QixXQUFTQyxjQUFhO0FBQ2xCLFFBQUk7QUFDSixPQUFHO0FBQ0MsZUFBUyxPQUFPO0FBQUEsSUFDcEIsU0FBUyxnQkFBZ0IsSUFBSSxNQUFNO0FBQ25DLFdBQU87QUFBQSxFQUNYO0FBRU8sV0FBUyxlQUFlLElBQUksTUFBTSxRQUFRO0FBQzdDLFFBQUksSUFBSSxnQkFBZ0IsSUFBSSxFQUFFO0FBQzlCLFFBQUksR0FBRztBQUNILFVBQUksUUFBUTtBQUNSLFVBQUUsUUFBUSxLQUFLLE1BQU0sSUFBSSxDQUFDO0FBQUEsTUFDOUIsT0FBTztBQUNILFVBQUUsUUFBUSxJQUFJO0FBQUEsTUFDbEI7QUFDQSxzQkFBZ0IsT0FBTyxFQUFFO0FBQUEsSUFDN0I7QUFBQSxFQUNKO0FBQ08sV0FBUyxvQkFBb0IsSUFBSSxTQUFTO0FBQzdDLFFBQUksSUFBSSxnQkFBZ0IsSUFBSSxFQUFFO0FBQzlCLFFBQUksR0FBRztBQUNILFFBQUUsT0FBTyxPQUFPO0FBQ2hCLHNCQUFnQixPQUFPLEVBQUU7QUFBQSxJQUM3QjtBQUFBLEVBQ0o7QUFFQSxXQUFTLE9BQU8sTUFBTSxTQUFTO0FBQzNCLFdBQU8sSUFBSSxRQUFRLENBQUMsU0FBUyxXQUFXO0FBQ3BDLFVBQUksS0FBS0EsWUFBVztBQUNwQixnQkFBVSxXQUFXLENBQUM7QUFDdEIsY0FBUSxXQUFXLElBQUk7QUFDdkIsc0JBQWdCLElBQUksSUFBSSxFQUFDLFNBQVMsT0FBTSxDQUFDO0FBQ3pDLE1BQUFELE1BQUssTUFBTSxPQUFPLEVBQUUsTUFBTSxDQUFDLFVBQVU7QUFDakMsZUFBTyxLQUFLO0FBQ1osd0JBQWdCLE9BQU8sRUFBRTtBQUFBLE1BQzdCLENBQUM7QUFBQSxJQUNMLENBQUM7QUFBQSxFQUNMO0FBUU8sV0FBUyxLQUFLLFNBQVM7QUFDMUIsV0FBTyxPQUFPLFlBQVksT0FBTztBQUFBLEVBQ3JDO0FBT08sV0FBUyxRQUFRLFNBQVM7QUFDN0IsV0FBTyxPQUFPLGVBQWUsT0FBTztBQUFBLEVBQ3hDO0FBT08sV0FBU0UsT0FBTSxTQUFTO0FBQzNCLFdBQU8sT0FBTyxhQUFhLE9BQU87QUFBQSxFQUN0QztBQU9PLFdBQVMsU0FBUyxTQUFTO0FBQzlCLFdBQU8sT0FBTyxnQkFBZ0IsT0FBTztBQUFBLEVBQ3pDO0FBT08sV0FBUyxTQUFTLFNBQVM7QUFDOUIsV0FBTyxPQUFPLGdCQUFnQixPQUFPO0FBQUEsRUFDekM7QUFPTyxXQUFTLFNBQVMsU0FBUztBQUM5QixXQUFPLE9BQU8sZ0JBQWdCLE9BQU87QUFBQSxFQUN6Qzs7O0FDN0hBLE1BQUlDLFFBQU8sdUJBQXVCLFlBQVksV0FBVztBQUV6RCxNQUFJLGtCQUFrQjtBQUV0QixXQUFTLGdCQUFnQixJQUFJLEdBQUcsR0FBRyxNQUFNO0FBQ3JDLFNBQUtBLE1BQUssaUJBQWlCLEVBQUMsSUFBSSxHQUFHLEdBQUcsS0FBSSxDQUFDO0FBQUEsRUFDL0M7QUFFTyxXQUFTLG9CQUFvQjtBQUNoQyxXQUFPLGlCQUFpQixlQUFlLGtCQUFrQjtBQUFBLEVBQzdEO0FBRUEsV0FBUyxtQkFBbUIsT0FBTztBQUUvQixRQUFJLFVBQVUsTUFBTTtBQUNwQixRQUFJLG9CQUFvQixPQUFPLGlCQUFpQixPQUFPLEVBQUUsaUJBQWlCLHNCQUFzQjtBQUNoRyx3QkFBb0Isb0JBQW9CLGtCQUFrQixLQUFLLElBQUk7QUFDbkUsUUFBSSxtQkFBbUI7QUFDbkIsWUFBTSxlQUFlO0FBQ3JCLFVBQUksd0JBQXdCLE9BQU8saUJBQWlCLE9BQU8sRUFBRSxpQkFBaUIsMkJBQTJCO0FBQ3pHLHNCQUFnQixtQkFBbUIsTUFBTSxTQUFTLE1BQU0sU0FBUyxxQkFBcUI7QUFDdEY7QUFBQSxJQUNKO0FBRUEsOEJBQTBCLEtBQUs7QUFBQSxFQUNuQztBQVVBLFdBQVMsMEJBQTBCLE9BQU87QUFFdEMsUUFBSSxNQUFPO0FBQ1A7QUFBQSxJQUNKO0FBR0EsVUFBTSxVQUFVLE1BQU07QUFDdEIsVUFBTSxnQkFBZ0IsT0FBTyxpQkFBaUIsT0FBTztBQUNyRCxVQUFNLDJCQUEyQixjQUFjLGlCQUFpQix1QkFBdUIsRUFBRSxLQUFLO0FBQzlGLFlBQVEsMEJBQTBCO0FBQUEsTUFDOUIsS0FBSztBQUNEO0FBQUEsTUFDSixLQUFLO0FBQ0QsY0FBTSxlQUFlO0FBQ3JCO0FBQUEsTUFDSjtBQUVJLFlBQUksUUFBUSxtQkFBbUI7QUFDM0I7QUFBQSxRQUNKO0FBR0EsY0FBTSxZQUFZLE9BQU8sYUFBYTtBQUN0QyxjQUFNLGVBQWdCLFVBQVUsU0FBUyxFQUFFLFNBQVM7QUFDcEQsWUFBSSxjQUFjO0FBQ2QsbUJBQVMsSUFBSSxHQUFHLElBQUksVUFBVSxZQUFZLEtBQUs7QUFDM0Msa0JBQU0sUUFBUSxVQUFVLFdBQVcsQ0FBQztBQUNwQyxrQkFBTSxRQUFRLE1BQU0sZUFBZTtBQUNuQyxxQkFBUyxJQUFJLEdBQUcsSUFBSSxNQUFNLFFBQVEsS0FBSztBQUNuQyxvQkFBTSxPQUFPLE1BQU0sQ0FBQztBQUNwQixrQkFBSSxTQUFTLGlCQUFpQixLQUFLLE1BQU0sS0FBSyxHQUFHLE1BQU0sU0FBUztBQUM1RDtBQUFBLGNBQ0o7QUFBQSxZQUNKO0FBQUEsVUFDSjtBQUFBLFFBQ0o7QUFFQSxZQUFJLFFBQVEsWUFBWSxXQUFXLFFBQVEsWUFBWSxZQUFZO0FBQy9ELGNBQUksZ0JBQWlCLENBQUMsUUFBUSxZQUFZLENBQUMsUUFBUSxVQUFXO0FBQzFEO0FBQUEsVUFDSjtBQUFBLFFBQ0o7QUFHQSxjQUFNLGVBQWU7QUFBQSxJQUM3QjtBQUFBLEVBQ0o7OztBQ2hGQSxXQUFTLFVBQVUsV0FBVyxPQUFLLE1BQU07QUFDckMsUUFBSSxRQUFRLElBQUksV0FBVyxXQUFXLElBQUk7QUFDMUMsU0FBSyxLQUFLO0FBQUEsRUFDZDtBQUVBLFdBQVMsdUJBQXVCO0FBQzVCLFVBQU0sV0FBVyxTQUFTLGlCQUFpQixhQUFhO0FBQ3hELGFBQVMsUUFBUSxTQUFVLFNBQVM7QUFDaEMsWUFBTSxZQUFZLFFBQVEsYUFBYSxXQUFXO0FBQ2xELFlBQU0sVUFBVSxRQUFRLGFBQWEsYUFBYTtBQUNsRCxZQUFNLFVBQVUsUUFBUSxhQUFhLGFBQWEsS0FBSztBQUV2RCxVQUFJLFdBQVcsV0FBWTtBQUN2QixZQUFJLFNBQVM7QUFDVCxtQkFBUyxFQUFDLE9BQU8sV0FBVyxTQUFRLFNBQVMsVUFBVSxPQUFPLFNBQVEsQ0FBQyxFQUFDLE9BQU0sTUFBSyxHQUFFLEVBQUMsT0FBTSxNQUFNLFdBQVUsS0FBSSxDQUFDLEVBQUMsQ0FBQyxFQUFFLEtBQUssU0FBVSxRQUFRO0FBQ3hJLGdCQUFJLFdBQVcsTUFBTTtBQUNqQix3QkFBVSxTQUFTO0FBQUEsWUFDdkI7QUFBQSxVQUNKLENBQUM7QUFDRDtBQUFBLFFBQ0o7QUFDQSxrQkFBVSxTQUFTO0FBQUEsTUFDdkI7QUFHQSxjQUFRLG9CQUFvQixTQUFTLFFBQVE7QUFHN0MsY0FBUSxpQkFBaUIsU0FBUyxRQUFRO0FBQUEsSUFDOUMsQ0FBQztBQUFBLEVBQ0w7QUFFQSxXQUFTLGlCQUFpQixRQUFRO0FBQzlCLFFBQUksTUFBTSxPQUFPLE1BQU0sTUFBTSxRQUFXO0FBQ3BDLGNBQVEsSUFBSSxtQkFBbUIsU0FBUyxZQUFZO0FBQUEsSUFDeEQ7QUFDQSxVQUFNLE9BQU8sTUFBTSxFQUFFO0FBQUEsRUFDekI7QUFFQSxXQUFTLHdCQUF3QjtBQUM3QixVQUFNLFdBQVcsU0FBUyxpQkFBaUIsY0FBYztBQUN6RCxhQUFTLFFBQVEsU0FBVSxTQUFTO0FBQ2hDLFlBQU0sZUFBZSxRQUFRLGFBQWEsWUFBWTtBQUN0RCxZQUFNLFVBQVUsUUFBUSxhQUFhLGFBQWE7QUFDbEQsWUFBTSxVQUFVLFFBQVEsYUFBYSxhQUFhLEtBQUs7QUFFdkQsVUFBSSxXQUFXLFdBQVk7QUFDdkIsWUFBSSxTQUFTO0FBQ1QsbUJBQVMsRUFBQyxPQUFPLFdBQVcsU0FBUSxTQUFTLFNBQVEsQ0FBQyxFQUFDLE9BQU0sTUFBSyxHQUFFLEVBQUMsT0FBTSxNQUFNLFdBQVUsS0FBSSxDQUFDLEVBQUMsQ0FBQyxFQUFFLEtBQUssU0FBVSxRQUFRO0FBQ3ZILGdCQUFJLFdBQVcsTUFBTTtBQUNqQiwrQkFBaUIsWUFBWTtBQUFBLFlBQ2pDO0FBQUEsVUFDSixDQUFDO0FBQ0Q7QUFBQSxRQUNKO0FBQ0EseUJBQWlCLFlBQVk7QUFBQSxNQUNqQztBQUdBLGNBQVEsb0JBQW9CLFNBQVMsUUFBUTtBQUc3QyxjQUFRLGlCQUFpQixTQUFTLFFBQVE7QUFBQSxJQUM5QyxDQUFDO0FBQUEsRUFDTDtBQUVBLFdBQVMsNEJBQTRCO0FBQ2pDLFVBQU0sV0FBVyxTQUFTLGlCQUFpQixlQUFlO0FBQzFELGFBQVMsUUFBUSxTQUFVLFNBQVM7QUFDaEMsWUFBTSxNQUFNLFFBQVEsYUFBYSxhQUFhO0FBQzlDLFlBQU0sVUFBVSxRQUFRLGFBQWEsYUFBYTtBQUNsRCxZQUFNLFVBQVUsUUFBUSxhQUFhLGFBQWEsS0FBSztBQUV2RCxVQUFJLFdBQVcsV0FBWTtBQUN2QixZQUFJLFNBQVM7QUFDVCxtQkFBUyxFQUFDLE9BQU8sV0FBVyxTQUFRLFNBQVMsU0FBUSxDQUFDLEVBQUMsT0FBTSxNQUFLLEdBQUUsRUFBQyxPQUFNLE1BQU0sV0FBVSxLQUFJLENBQUMsRUFBQyxDQUFDLEVBQUUsS0FBSyxTQUFVLFFBQVE7QUFDdkgsZ0JBQUksV0FBVyxNQUFNO0FBQ2pCLG1CQUFLLE1BQU0sUUFBUSxRQUFRLEdBQUc7QUFBQSxZQUNsQztBQUFBLFVBQ0osQ0FBQztBQUNEO0FBQUEsUUFDSjtBQUNBLGFBQUssTUFBTSxRQUFRLFFBQVEsR0FBRztBQUFBLE1BQ2xDO0FBR0EsY0FBUSxvQkFBb0IsU0FBUyxRQUFRO0FBRzdDLGNBQVEsaUJBQWlCLFNBQVMsUUFBUTtBQUFBLElBQzlDLENBQUM7QUFBQSxFQUNMO0FBRU8sV0FBUyxZQUFZO0FBQ3hCLHlCQUFxQjtBQUNyQiwwQkFBc0I7QUFDdEIsOEJBQTBCO0FBQUEsRUFDOUI7OztBQ3hGTyxNQUFJLFNBQVMsU0FBUyxPQUFPO0FBQ2hDLFFBQUcsT0FBUztBQUNSLGFBQU8sUUFBUSxZQUFZLEtBQUs7QUFBQSxJQUNwQyxPQUFPO0FBQ0gsYUFBTyxnQkFBZ0IsU0FBUyxZQUFZLEtBQUs7QUFBQSxJQUNyRDtBQUFBLEVBQ0o7OztBQ1BBLE1BQUksUUFBUSxvQkFBSSxJQUFJO0FBRXBCLFdBQVMsYUFBYSxLQUFLO0FBQ3ZCLFVBQU0sTUFBTSxvQkFBSSxJQUFJO0FBRXBCLGVBQVcsQ0FBQyxLQUFLLEtBQUssS0FBSyxPQUFPLFFBQVEsR0FBRyxHQUFHO0FBQzVDLFVBQUksT0FBTyxVQUFVLFlBQVksVUFBVSxNQUFNO0FBQzdDLFlBQUksSUFBSSxLQUFLLGFBQWEsS0FBSyxDQUFDO0FBQUEsTUFDcEMsT0FBTztBQUNILFlBQUksSUFBSSxLQUFLLEtBQUs7QUFBQSxNQUN0QjtBQUFBLElBQ0o7QUFFQSxXQUFPO0FBQUEsRUFDWDtBQUVBLFFBQU0sY0FBYyxFQUFFLEtBQUssQ0FBQyxhQUFhO0FBQ3JDLGFBQVMsS0FBSyxFQUFFLEtBQUssQ0FBQyxTQUFTO0FBQzNCLGNBQVEsYUFBYSxJQUFJO0FBQUEsSUFDN0IsQ0FBQztBQUFBLEVBQ0wsQ0FBQzs7O0FDakJELE1BQUksYUFBYTtBQUVWLFdBQVMsU0FBUyxHQUFHO0FBQ3hCLFFBQUksTUFBTSxPQUFPLGlCQUFpQixFQUFFLE1BQU0sRUFBRSxpQkFBaUIscUJBQXFCO0FBQ2xGLFFBQUksS0FBSztBQUNMLFlBQU0sSUFBSSxLQUFLO0FBQUEsSUFDbkI7QUFFQSxRQUFJLFFBQVEsUUFBUTtBQUNoQixhQUFPO0FBQUEsSUFDWDtBQUdBLFFBQUksRUFBRSxZQUFZLEdBQUc7QUFDakIsYUFBTztBQUFBLElBQ1g7QUFFQSxXQUFPLEVBQUUsV0FBVztBQUFBLEVBQ3hCO0FBRU8sV0FBUyxZQUFZO0FBQ3hCLFdBQU8saUJBQWlCLGFBQWEsV0FBVztBQUNoRCxXQUFPLGlCQUFpQixhQUFhLFdBQVc7QUFDaEQsV0FBTyxpQkFBaUIsV0FBVyxTQUFTO0FBQUEsRUFDaEQ7QUFHQSxNQUFJLFlBQVk7QUFFVCxXQUFTLGFBQWEsT0FBTztBQUNoQyxnQkFBWTtBQUFBLEVBQ2hCO0FBVUEsV0FBUyxZQUFZLEdBQUc7QUFHcEIsUUFBSSxPQUFVO0FBQ1YsVUFBSSxXQUFXLEdBQUc7QUFDZDtBQUFBLE1BQ0o7QUFBQSxJQUNKO0FBQ0EsUUFBSSxTQUFTLENBQUMsR0FBRztBQUViLFVBQUksRUFBRSxVQUFVLEVBQUUsT0FBTyxlQUFlLEVBQUUsVUFBVSxFQUFFLE9BQU8sY0FBYztBQUN2RTtBQUFBLE1BQ0o7QUFDQSxtQkFBYTtBQUFBLElBQ2pCLE9BQU87QUFDSCxtQkFBYTtBQUFBLElBQ2pCO0FBQUEsRUFDSjtBQUVBLFdBQVMsVUFBVSxHQUFHO0FBQ2xCLFFBQUksZUFBZSxFQUFFLFlBQVksU0FBWSxFQUFFLFVBQVUsRUFBRTtBQUMzRCxRQUFJLGVBQWUsR0FBRztBQUNsQixjQUFRO0FBQUEsSUFDWjtBQUFBLEVBQ0o7QUFFTyxXQUFTLFVBQVU7QUFDdEIsYUFBUyxLQUFLLE1BQU0sU0FBUztBQUM3QixpQkFBYTtBQUFBLEVBQ2pCO0FBT0EsV0FBUyxZQUFZLEdBQUc7QUFDcEIsUUFBSSxZQUFZO0FBQ1osbUJBQWE7QUFDYixVQUFJLGVBQWUsRUFBRSxZQUFZLFNBQVksRUFBRSxVQUFVLEVBQUU7QUFDM0QsVUFBSSxlQUFlLEdBQUc7QUFDbEIsZUFBTyxNQUFNO0FBQUEsTUFDakI7QUFDQTtBQUFBLElBQ0o7QUFFQSxRQUFJLE9BQVM7QUFDVCxVQUFJLFdBQVc7QUFDWCxxQkFBYSxDQUFDO0FBQUEsTUFDbEI7QUFBQSxJQUNKO0FBQUEsRUFDSjs7O0FDakZBLFNBQU8sUUFBUTtBQUFBLElBQ1gsR0FBRyxXQUFXLElBQUk7QUFBQSxJQUNsQixjQUFjLENBQUM7QUFBQSxJQUNmO0FBQUEsRUFDSjtBQUVBLFFBQU0scUJBQXFCLEVBQUUsS0FBSyxDQUFDLGFBQWE7QUFDNUMsYUFBUyxLQUFLLEVBQUUsS0FBSyxDQUFDLFNBQVM7QUFDM0IsYUFBTyxNQUFNLGVBQWU7QUFBQSxJQUNoQyxDQUFDO0FBQUEsRUFDTCxDQUFDO0FBR0QsU0FBTyxTQUFTO0FBQUEsSUFDWjtBQUFBLElBQ0E7QUFBQSxJQUNBO0FBQUEsSUFDQTtBQUFBLElBQ0E7QUFBQSxJQUNBO0FBQUEsSUFDQTtBQUFBLEVBQ0o7QUFFTyxXQUFTLFdBQVcsWUFBWTtBQUNuQyxXQUFPO0FBQUEsTUFDSCxXQUFXO0FBQUEsUUFDUCxHQUFHO0FBQUEsTUFDUDtBQUFBLE1BQ0EsYUFBYTtBQUFBLFFBQ1QsR0FBRztBQUFBLFFBQ0gsZ0JBQWdCQyxhQUFZO0FBQ3hCLGlCQUFPLFdBQVdBLFdBQVU7QUFBQSxRQUNoQztBQUFBLE1BQ0o7QUFBQSxNQUNBO0FBQUEsTUFDQTtBQUFBLE1BQ0E7QUFBQSxNQUNBO0FBQUEsTUFDQTtBQUFBLE1BQ0E7QUFBQSxNQUNBO0FBQUEsTUFDQSxLQUFLO0FBQUEsUUFDRCxRQUFRO0FBQUEsTUFDWjtBQUFBLE1BQ0EsUUFBUTtBQUFBLFFBQ0o7QUFBQSxRQUNBO0FBQUEsUUFDQSxPQUFBQztBQUFBLFFBQ0E7QUFBQSxRQUNBO0FBQUEsUUFDQTtBQUFBLE1BQ0o7QUFBQSxNQUNBLFFBQVE7QUFBQSxRQUNKO0FBQUEsUUFDQTtBQUFBLFFBQ0E7QUFBQSxRQUNBO0FBQUEsUUFDQTtBQUFBLFFBQ0E7QUFBQSxNQUNKO0FBQUEsTUFDQSxRQUFRLFVBQVUsVUFBVTtBQUFBLElBQ2hDO0FBQUEsRUFDSjtBQUVBLE1BQUksTUFBTztBQUNQLFlBQVEsSUFBSSxpQ0FBaUM7QUFBQSxFQUNqRDtBQUVBLG9CQUFrQjtBQUNsQixZQUFVO0FBRVYsV0FBUyxpQkFBaUIsb0JBQW9CLFdBQVc7QUFDckQsY0FBVTtBQUFBLEVBQ2QsQ0FBQzsiLAogICJuYW1lcyI6IFsiY2FsbCIsICJjYWxsIiwgImNhbGwiLCAiY2FsbCIsICJjYWxsIiwgImNhbGwiLCAicmVzaXphYmxlIiwgImNhbGwiLCAiZXZlbnROYW1lIiwgImNhbGwiLCAiZ2VuZXJhdGVJRCIsICJFcnJvciIsICJjYWxsIiwgIndpbmRvd05hbWUiLCAiRXJyb3IiXQp9Cg== diff --git a/v3/internal/runtime/runtime_debug_desktop_linux.js b/v3/internal/runtime/runtime_debug_desktop_linux.js new file mode 100644 index 000000000..3e0861b89 --- /dev/null +++ b/v3/internal/runtime/runtime_debug_desktop_linux.js @@ -0,0 +1,873 @@ +(() => { + var __defProp = Object.defineProperty; + var __export = (target, all) => { + for (var name in all) + __defProp(target, name, { get: all[name], enumerable: true }); + }; + + // desktop/clipboard.js + var clipboard_exports = {}; + __export(clipboard_exports, { + SetText: () => SetText, + Text: () => Text + }); + + // node_modules/nanoid/non-secure/index.js + var urlAlphabet = "useandom-26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict"; + var nanoid = (size = 21) => { + let id = ""; + let i = size; + while (i--) { + id += urlAlphabet[Math.random() * 64 | 0]; + } + return id; + }; + + // desktop/runtime.js + var runtimeURL = window.location.origin + "/wails/runtime"; + var objectNames = { + Call: 0, + Clipboard: 1, + Application: 2, + Events: 3, + ContextMenu: 4, + Dialog: 5, + Window: 6, + Screens: 7, + System: 8, + Browser: 9 + }; + var clientId = nanoid(); + 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 (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) { + 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)); + }); + } + function newRuntimeCallerWithID(object, windowName) { + return function(method, args = null) { + return runtimeCallWithID(object, method, windowName, args); + }; + } + + // desktop/clipboard.js + var call = newRuntimeCallerWithID(objectNames.Clipboard); + var ClipboardSetText = 0; + var ClipboardText = 1; + function SetText(text) { + void call(ClipboardSetText, { text }); + } + function Text() { + return call(ClipboardText); + } + + // desktop/application.js + var application_exports = {}; + __export(application_exports, { + Hide: () => Hide, + Quit: () => Quit, + Show: () => Show + }); + var call2 = newRuntimeCallerWithID(objectNames.Application); + var methods = { + Hide: 0, + Show: 1, + Quit: 2 + }; + function Hide() { + void call2(methods.Hide); + } + function Show() { + void call2(methods.Show); + } + function Quit() { + void call2(methods.Quit); + } + + // desktop/screens.js + var screens_exports = {}; + __export(screens_exports, { + GetAll: () => GetAll, + GetCurrent: () => GetCurrent, + GetPrimary: () => GetPrimary + }); + var call3 = newRuntimeCallerWithID(objectNames.Screens); + var ScreensGetAll = 0; + var ScreensGetPrimary = 1; + var ScreensGetCurrent = 2; + function GetAll() { + return call3(ScreensGetAll); + } + function GetPrimary() { + return call3(ScreensGetPrimary); + } + function GetCurrent() { + return call3(ScreensGetCurrent); + } + + // desktop/system.js + var system_exports = {}; + __export(system_exports, { + IsDarkMode: () => IsDarkMode + }); + var call4 = newRuntimeCallerWithID(objectNames.System); + var SystemIsDarkMode = 0; + function IsDarkMode() { + return call4(SystemIsDarkMode); + } + + // desktop/browser.js + var browser_exports = {}; + __export(browser_exports, { + OpenURL: () => OpenURL + }); + var call5 = newRuntimeCallerWithID(objectNames.Browser); + var BrowserOpenURL = 0; + function OpenURL(url) { + void call5(BrowserOpenURL, { url }); + } + + // desktop/calls.js + var call6 = newRuntimeCallerWithID(objectNames.Call); + var CallBinding = 0; + var callResponses = /* @__PURE__ */ new Map(); + function generateID() { + let result; + do { + result = nanoid(); + } while (callResponses.has(result)); + return result; + } + function callCallback(id, data, isJSON) { + let p = callResponses.get(id); + if (p) { + if (isJSON) { + p.resolve(JSON.parse(data)); + } else { + p.resolve(data); + } + callResponses.delete(id); + } + } + function callErrorCallback(id, message) { + let p = callResponses.get(id); + if (p) { + p.reject(message); + callResponses.delete(id); + } + } + function callBinding(type, options) { + return new Promise((resolve, reject) => { + let id = generateID(); + options = options || {}; + options["call-id"] = id; + callResponses.set(id, { resolve, reject }); + call6(type, options).catch((error) => { + reject(error); + callResponses.delete(id); + }); + }); + } + function Call(options) { + return callBinding(CallBinding, options); + } + function CallByName(name, ...args) { + if (typeof name !== "string" || name.split(".").length !== 3) { + throw new Error("CallByName requires a string in the format 'package.struct.method'"); + } + let parts = name.split("."); + return callBinding(CallBinding, { + packageName: parts[0], + structName: parts[1], + methodName: parts[2], + args + }); + } + function CallByID(methodID, ...args) { + return callBinding(CallBinding, { + methodID, + args + }); + } + function Plugin(pluginName, methodName, ...args) { + return callBinding(CallBinding, { + packageName: "wails-plugins", + structName: pluginName, + methodName, + args + }); + } + + // desktop/window.js + var WindowCenter = 0; + var WindowSetTitle = 1; + var WindowFullscreen = 2; + var WindowUnFullscreen = 3; + var WindowSetSize = 4; + var WindowSize = 5; + var WindowSetMaxSize = 6; + var WindowSetMinSize = 7; + var WindowSetAlwaysOnTop = 8; + var WindowSetRelativePosition = 9; + var WindowRelativePosition = 10; + var WindowScreen = 11; + var WindowHide = 12; + var WindowMaximise = 13; + var WindowUnMaximise = 14; + var WindowToggleMaximise = 15; + var WindowMinimise = 16; + var WindowUnMinimise = 17; + var WindowRestore = 18; + var WindowShow = 19; + var WindowClose = 20; + var WindowSetBackgroundColour = 21; + var WindowSetResizable = 22; + var WindowWidth = 23; + var WindowHeight = 24; + var WindowZoomIn = 25; + var WindowZoomOut = 26; + var WindowZoomReset = 27; + var WindowGetZoomLevel = 28; + var WindowSetZoomLevel = 29; + function newWindow(windowName) { + let call10 = newRuntimeCallerWithID(objectNames.Window, windowName); + return { + /** + * Centers the window. + */ + Center: () => void call10(WindowCenter), + /** + * Set the window title. + * @param title + */ + SetTitle: (title) => void call10(WindowSetTitle, { title }), + /** + * Makes the window fullscreen. + */ + Fullscreen: () => void call10(WindowFullscreen), + /** + * Unfullscreen the window. + */ + UnFullscreen: () => void call10(WindowUnFullscreen), + /** + * Set the window size. + * @param {number} width The window width + * @param {number} height The window height + */ + SetSize: (width, height) => call10(WindowSetSize, { width, height }), + /** + * Get the window size. + * @returns {Promise} The window size + */ + Size: () => { + return call10(WindowSize); + }, + /** + * Set the window maximum size. + * @param {number} width + * @param {number} height + */ + SetMaxSize: (width, height) => void call10(WindowSetMaxSize, { width, height }), + /** + * Set the window minimum size. + * @param {number} width + * @param {number} height + */ + SetMinSize: (width, height) => void call10(WindowSetMinSize, { width, height }), + /** + * Set window to be always on top. + * @param {boolean} onTop Whether the window should be always on top + */ + SetAlwaysOnTop: (onTop) => void call10(WindowSetAlwaysOnTop, { alwaysOnTop: onTop }), + /** + * Set the window relative position. + * @param {number} x + * @param {number} y + */ + SetRelativePosition: (x, y) => call10(WindowSetRelativePosition, { x, y }), + /** + * Get the window position. + * @returns {Promise} The window position + */ + RelativePosition: () => { + return call10(WindowRelativePosition); + }, + /** + * Get the screen the window is on. + * @returns {Promise} + */ + Screen: () => { + return call10(WindowScreen); + }, + /** + * Hide the window + */ + Hide: () => void call10(WindowHide), + /** + * Maximise the window + */ + Maximise: () => void call10(WindowMaximise), + /** + * Show the window + */ + Show: () => void call10(WindowShow), + /** + * Close the window + */ + Close: () => void call10(WindowClose), + /** + * Toggle the window maximise state + */ + ToggleMaximise: () => void call10(WindowToggleMaximise), + /** + * Unmaximise the window + */ + UnMaximise: () => void call10(WindowUnMaximise), + /** + * Minimise the window + */ + Minimise: () => void call10(WindowMinimise), + /** + * Unminimise the window + */ + UnMinimise: () => void call10(WindowUnMinimise), + /** + * Restore the window + */ + Restore: () => void call10(WindowRestore), + /** + * Set the background colour of the window. + * @param {number} r - A value between 0 and 255 + * @param {number} g - A value between 0 and 255 + * @param {number} b - A value between 0 and 255 + * @param {number} a - A value between 0 and 255 + */ + SetBackgroundColour: (r, g, b, a) => void call10(WindowSetBackgroundColour, { r, g, b, a }), + /** + * Set whether the window can be resized or not + * @param {boolean} resizable + */ + SetResizable: (resizable2) => void call10(WindowSetResizable, { resizable: resizable2 }), + /** + * Get the window width + * @returns {Promise} + */ + Width: () => { + return call10(WindowWidth); + }, + /** + * Get the window height + * @returns {Promise} + */ + Height: () => { + return call10(WindowHeight); + }, + /** + * Zoom in the window + */ + ZoomIn: () => void call10(WindowZoomIn), + /** + * Zoom out the window + */ + ZoomOut: () => void call10(WindowZoomOut), + /** + * Reset the window zoom + */ + ZoomReset: () => void call10(WindowZoomReset), + /** + * Get the window zoom + * @returns {Promise} + */ + GetZoomLevel: () => { + return call10(WindowGetZoomLevel); + }, + /** + * Set the window zoom level + * @param {number} zoomLevel + */ + SetZoomLevel: (zoomLevel) => void call10(WindowSetZoomLevel, { zoomLevel }) + }; + } + + // desktop/events.js + var call7 = newRuntimeCallerWithID(objectNames.Events); + var EventEmit = 0; + var Listener = class { + /** + * Creates an instance of Listener. + * @param {string} eventName + * @param {function} callback + * @param {number} maxCallbacks + * @memberof Listener + */ + constructor(eventName, callback, maxCallbacks) { + this.eventName = eventName; + this.maxCallbacks = maxCallbacks || -1; + this.Callback = (data) => { + callback(data); + if (this.maxCallbacks === -1) { + return false; + } + this.maxCallbacks -= 1; + return this.maxCallbacks === 0; + }; + } + }; + var WailsEvent = class { + /** + * Creates an instance of WailsEvent. + * @param {string} name - Name of the event + * @param {any=null} data - Data associated with the event + * @memberof WailsEvent + */ + constructor(name, data = null) { + this.name = name; + this.data = data; + } + }; + var eventListeners = /* @__PURE__ */ new Map(); + function OnMultiple(eventName, callback, maxCallbacks) { + let listeners = eventListeners.get(eventName) || []; + const thisListener = new Listener(eventName, callback, maxCallbacks); + listeners.push(thisListener); + eventListeners.set(eventName, listeners); + return () => listenerOff(thisListener); + } + function On(eventName, callback) { + return OnMultiple(eventName, callback, -1); + } + function Once(eventName, callback) { + return OnMultiple(eventName, callback, 1); + } + function listenerOff(listener) { + const eventName = listener.eventName; + let listeners = eventListeners.get(eventName).filter((l) => l !== listener); + if (listeners.length === 0) { + eventListeners.delete(eventName); + } else { + eventListeners.set(eventName, listeners); + } + } + function dispatchWailsEvent(event) { + let listeners = eventListeners.get(event.name); + if (listeners) { + let toRemove = []; + listeners.forEach((listener) => { + let remove = listener.Callback(event); + if (remove) { + toRemove.push(listener); + } + }); + if (toRemove.length > 0) { + listeners = listeners.filter((l) => !toRemove.includes(l)); + if (listeners.length === 0) { + eventListeners.delete(event.name); + } else { + eventListeners.set(event.name, listeners); + } + } + } + } + function Off(eventName, ...additionalEventNames) { + let eventsToRemove = [eventName, ...additionalEventNames]; + eventsToRemove.forEach((eventName2) => { + eventListeners.delete(eventName2); + }); + } + function OffAll() { + eventListeners.clear(); + } + function Emit(event) { + void call7(EventEmit, event); + } + + // desktop/dialogs.js + var call8 = newRuntimeCallerWithID(objectNames.Dialog); + var DialogInfo = 0; + var DialogWarning = 1; + var DialogError = 2; + var DialogQuestion = 3; + var DialogOpenFile = 4; + var DialogSaveFile = 5; + var dialogResponses = /* @__PURE__ */ new Map(); + function generateID2() { + let result; + do { + result = nanoid(); + } while (dialogResponses.has(result)); + return result; + } + function dialogCallback(id, data, isJSON) { + let p = dialogResponses.get(id); + if (p) { + if (isJSON) { + p.resolve(JSON.parse(data)); + } else { + p.resolve(data); + } + dialogResponses.delete(id); + } + } + function dialogErrorCallback(id, message) { + let p = dialogResponses.get(id); + if (p) { + p.reject(message); + dialogResponses.delete(id); + } + } + function dialog(type, options) { + return new Promise((resolve, reject) => { + let id = generateID2(); + options = options || {}; + options["dialog-id"] = id; + dialogResponses.set(id, { resolve, reject }); + call8(type, options).catch((error) => { + reject(error); + dialogResponses.delete(id); + }); + }); + } + function Info(options) { + return dialog(DialogInfo, options); + } + function Warning(options) { + return dialog(DialogWarning, options); + } + function Error2(options) { + return dialog(DialogError, options); + } + function Question(options) { + return dialog(DialogQuestion, options); + } + function OpenFile(options) { + return dialog(DialogOpenFile, options); + } + function SaveFile(options) { + return dialog(DialogSaveFile, options); + } + + // desktop/contextmenu.js + var call9 = newRuntimeCallerWithID(objectNames.ContextMenu); + var ContextMenuOpen = 0; + function openContextMenu(id, x, y, data) { + void call9(ContextMenuOpen, { id, x, y, data }); + } + function setupContextMenus() { + window.addEventListener("contextmenu", contextMenuHandler); + } + function contextMenuHandler(event) { + let element = event.target; + let customContextMenu = window.getComputedStyle(element).getPropertyValue("--custom-contextmenu"); + customContextMenu = customContextMenu ? customContextMenu.trim() : ""; + if (customContextMenu) { + event.preventDefault(); + let customContextMenuData = window.getComputedStyle(element).getPropertyValue("--custom-contextmenu-data"); + openContextMenu(customContextMenu, event.clientX, event.clientY, customContextMenuData); + return; + } + processDefaultContextMenu(event); + } + function processDefaultContextMenu(event) { + if (true) { + return; + } + const element = event.target; + const computedStyle = window.getComputedStyle(element); + const defaultContextMenuAction = computedStyle.getPropertyValue("--default-contextmenu").trim(); + switch (defaultContextMenuAction) { + case "show": + return; + case "hide": + event.preventDefault(); + return; + default: + if (element.isContentEditable) { + return; + } + const selection = window.getSelection(); + const hasSelection = selection.toString().length > 0; + if (hasSelection) { + for (let i = 0; i < selection.rangeCount; i++) { + const range = selection.getRangeAt(i); + const rects = range.getClientRects(); + for (let j = 0; j < rects.length; j++) { + const rect = rects[j]; + if (document.elementFromPoint(rect.left, rect.top) === element) { + return; + } + } + } + } + if (element.tagName === "INPUT" || element.tagName === "TEXTAREA") { + if (hasSelection || !element.readOnly && !element.disabled) { + return; + } + } + event.preventDefault(); + } + } + + // desktop/wml.js + function sendEvent(eventName, data = null) { + let event = new WailsEvent(eventName, data); + Emit(event); + } + function addWMLEventListeners() { + const elements = document.querySelectorAll("[wml-event]"); + elements.forEach(function(element) { + const eventType = element.getAttribute("wml-event"); + const confirm = element.getAttribute("wml-confirm"); + const trigger = element.getAttribute("wml-trigger") || "click"; + let callback = function() { + if (confirm) { + Question({ Title: "Confirm", Message: confirm, Detached: false, Buttons: [{ Label: "Yes" }, { Label: "No", IsDefault: true }] }).then(function(result) { + if (result !== "No") { + sendEvent(eventType); + } + }); + return; + } + sendEvent(eventType); + }; + element.removeEventListener(trigger, callback); + element.addEventListener(trigger, callback); + }); + } + function callWindowMethod(method) { + if (wails.Window[method] === void 0) { + console.log("Window method " + method + " not found"); + } + wails.Window[method](); + } + function addWMLWindowListeners() { + const elements = document.querySelectorAll("[wml-window]"); + elements.forEach(function(element) { + const windowMethod = element.getAttribute("wml-window"); + const confirm = element.getAttribute("wml-confirm"); + const trigger = element.getAttribute("wml-trigger") || "click"; + let callback = function() { + if (confirm) { + Question({ Title: "Confirm", Message: confirm, Buttons: [{ Label: "Yes" }, { Label: "No", IsDefault: true }] }).then(function(result) { + if (result !== "No") { + callWindowMethod(windowMethod); + } + }); + return; + } + callWindowMethod(windowMethod); + }; + element.removeEventListener(trigger, callback); + element.addEventListener(trigger, callback); + }); + } + function addWMLOpenBrowserListener() { + const elements = document.querySelectorAll("[wml-openurl]"); + elements.forEach(function(element) { + const url = element.getAttribute("wml-openurl"); + const confirm = element.getAttribute("wml-confirm"); + const trigger = element.getAttribute("wml-trigger") || "click"; + let callback = function() { + if (confirm) { + Question({ Title: "Confirm", Message: confirm, Buttons: [{ Label: "Yes" }, { Label: "No", IsDefault: true }] }).then(function(result) { + if (result !== "No") { + void wails.Browser.OpenURL(url); + } + }); + return; + } + void wails.Browser.OpenURL(url); + }; + element.removeEventListener(trigger, callback); + element.addEventListener(trigger, callback); + }); + } + function reloadWML() { + addWMLEventListeners(); + addWMLWindowListeners(); + addWMLOpenBrowserListener(); + } + + // desktop/invoke.js + var invoke = function(input) { + if (false) { + chrome.webview.postMessage(input); + } else { + webkit.messageHandlers.external.postMessage(input); + } + }; + + // desktop/flags.js + var flags = /* @__PURE__ */ new Map(); + function convertToMap(obj) { + const map = /* @__PURE__ */ new Map(); + for (const [key, value] of Object.entries(obj)) { + if (typeof value === "object" && value !== null) { + map.set(key, convertToMap(value)); + } else { + map.set(key, value); + } + } + return map; + } + fetch("/wails/flags").then((response) => { + response.json().then((data) => { + flags = convertToMap(data); + }); + }); + + // desktop/drag.js + var shouldDrag = false; + function dragTest(e) { + let val = window.getComputedStyle(e.target).getPropertyValue("--webkit-app-region"); + if (val) { + val = val.trim(); + } + if (val !== "drag") { + return false; + } + if (e.buttons !== 1) { + return false; + } + return e.detail === 1; + } + function setupDrag() { + window.addEventListener("mousedown", onMouseDown); + window.addEventListener("mousemove", onMouseMove); + window.addEventListener("mouseup", onMouseUp); + } + var resizable = false; + function setResizable(value) { + resizable = value; + } + function onMouseDown(e) { + if (false) { + if (testResize()) { + return; + } + } + if (dragTest(e)) { + if (e.offsetX > e.target.clientWidth || e.offsetY > e.target.clientHeight) { + return; + } + shouldDrag = true; + } else { + shouldDrag = false; + } + } + function onMouseUp(e) { + let mousePressed = e.buttons !== void 0 ? e.buttons : e.which; + if (mousePressed > 0) { + endDrag(); + } + } + function endDrag() { + document.body.style.cursor = "default"; + shouldDrag = false; + } + function onMouseMove(e) { + if (shouldDrag) { + shouldDrag = false; + let mousePressed = e.buttons !== void 0 ? e.buttons : e.which; + if (mousePressed > 0) { + invoke("drag"); + } + return; + } + if (false) { + if (resizable) { + handleResize(e); + } + } + } + + // desktop/main.js + window.wails = { + ...newRuntime(null), + Capabilities: {}, + clientId + }; + fetch("/wails/capabilities").then((response) => { + response.json().then((data) => { + window.wails.Capabilities = data; + }); + }); + window._wails = { + dialogCallback, + dialogErrorCallback, + dispatchWailsEvent, + callCallback, + callErrorCallback, + endDrag, + setResizable + }; + function newRuntime(windowName) { + return { + Clipboard: { + ...clipboard_exports + }, + Application: { + ...application_exports, + GetWindowByName(windowName2) { + return newRuntime(windowName2); + } + }, + System: system_exports, + Screens: screens_exports, + Browser: browser_exports, + Call, + CallByID, + CallByName, + Plugin, + WML: { + Reload: reloadWML + }, + Dialog: { + Info, + Warning, + Error: Error2, + Question, + OpenFile, + SaveFile + }, + Events: { + Emit, + On, + Once, + OnMultiple, + Off, + OffAll + }, + Window: newWindow(windowName) + }; + } + if (true) { + console.log("Wails v3.0.0 Debug Mode Enabled"); + } + setupContextMenus(); + setupDrag(); + document.addEventListener("DOMContentLoaded", function() { + reloadWML(); + }); +})(); +//# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsiZGVza3RvcC9jbGlwYm9hcmQuanMiLCAibm9kZV9tb2R1bGVzL25hbm9pZC9ub24tc2VjdXJlL2luZGV4LmpzIiwgImRlc2t0b3AvcnVudGltZS5qcyIsICJkZXNrdG9wL2FwcGxpY2F0aW9uLmpzIiwgImRlc2t0b3Avc2NyZWVucy5qcyIsICJkZXNrdG9wL3N5c3RlbS5qcyIsICJkZXNrdG9wL2Jyb3dzZXIuanMiLCAiZGVza3RvcC9jYWxscy5qcyIsICJkZXNrdG9wL3dpbmRvdy5qcyIsICJkZXNrdG9wL2V2ZW50cy5qcyIsICJkZXNrdG9wL2RpYWxvZ3MuanMiLCAiZGVza3RvcC9jb250ZXh0bWVudS5qcyIsICJkZXNrdG9wL3dtbC5qcyIsICJkZXNrdG9wL2ludm9rZS5qcyIsICJkZXNrdG9wL2ZsYWdzLmpzIiwgImRlc2t0b3AvZHJhZy5qcyIsICJkZXNrdG9wL21haW4uanMiXSwKICAic291cmNlc0NvbnRlbnQiOiBbIi8qXHJcbiBfXHQgICBfX1x0ICBfIF9fXHJcbnwgfFx0IC8gL19fXyBfKF8pIC9fX19fXHJcbnwgfCAvfCAvIC8gX18gYC8gLyAvIF9fXy9cclxufCB8LyB8LyAvIC9fLyAvIC8gKF9fICApXHJcbnxfXy98X18vXFxfXyxfL18vXy9fX19fL1xyXG5UaGUgZWxlY3Ryb24gYWx0ZXJuYXRpdmUgZm9yIEdvXHJcbihjKSBMZWEgQW50aG9ueSAyMDE5LXByZXNlbnRcclxuKi9cclxuXHJcbi8qIGpzaGludCBlc3ZlcnNpb246IDkgKi9cclxuXHJcbmltcG9ydCB7bmV3UnVudGltZUNhbGxlcldpdGhJRCwgb2JqZWN0TmFtZXN9IGZyb20gXCIuL3J1bnRpbWVcIjtcclxuXHJcbmxldCBjYWxsID0gbmV3UnVudGltZUNhbGxlcldpdGhJRChvYmplY3ROYW1lcy5DbGlwYm9hcmQpO1xyXG5cclxubGV0IENsaXBib2FyZFNldFRleHQgPSAwO1xyXG5sZXQgQ2xpcGJvYXJkVGV4dCA9IDE7XHJcblxyXG4vKipcclxuICogU2V0IHRoZSBDbGlwYm9hcmQgdGV4dFxyXG4gKi9cclxuZXhwb3J0IGZ1bmN0aW9uIFNldFRleHQodGV4dCkge1xyXG4gICAgdm9pZCBjYWxsKENsaXBib2FyZFNldFRleHQsIHt0ZXh0fSk7XHJcbn1cclxuXHJcbi8qKlxyXG4gKiBHZXQgdGhlIENsaXBib2FyZCB0ZXh0XHJcbiAqIEByZXR1cm5zIHtQcm9taXNlPHN0cmluZz59XHJcbiAqL1xyXG5leHBvcnQgZnVuY3Rpb24gVGV4dCgpIHtcclxuICAgIHJldHVybiBjYWxsKENsaXBib2FyZFRleHQpO1xyXG59IiwgImxldCB1cmxBbHBoYWJldCA9XG4gICd1c2VhbmRvbS0yNlQxOTgzNDBQWDc1cHhKQUNLVkVSWU1JTkRCVVNIV09MRl9HUVpiZmdoamtscXZ3eXpyaWN0J1xuZXhwb3J0IGxldCBjdXN0b21BbHBoYWJldCA9IChhbHBoYWJldCwgZGVmYXVsdFNpemUgPSAyMSkgPT4ge1xuICByZXR1cm4gKHNpemUgPSBkZWZhdWx0U2l6ZSkgPT4ge1xuICAgIGxldCBpZCA9ICcnXG4gICAgbGV0IGkgPSBzaXplXG4gICAgd2hpbGUgKGktLSkge1xuICAgICAgaWQgKz0gYWxwaGFiZXRbKE1hdGgucmFuZG9tKCkgKiBhbHBoYWJldC5sZW5ndGgpIHwgMF1cbiAgICB9XG4gICAgcmV0dXJuIGlkXG4gIH1cbn1cbmV4cG9ydCBsZXQgbmFub2lkID0gKHNpemUgPSAyMSkgPT4ge1xuICBsZXQgaWQgPSAnJ1xuICBsZXQgaSA9IHNpemVcbiAgd2hpbGUgKGktLSkge1xuICAgIGlkICs9IHVybEFscGhhYmV0WyhNYXRoLnJhbmRvbSgpICogNjQpIHwgMF1cbiAgfVxuICByZXR1cm4gaWRcbn1cbiIsICIvKlxyXG4gXyAgICAgX18gICAgIF8gX19cclxufCB8ICAvIC9fX18gXyhfKSAvX19fX1xyXG58IHwgL3wgLyAvIF9fIGAvIC8gLyBfX18vXHJcbnwgfC8gfC8gLyAvXy8gLyAvIChfXyAgKVxyXG58X18vfF9fL1xcX18sXy9fL18vX19fXy9cclxuVGhlIGVsZWN0cm9uIGFsdGVybmF0aXZlIGZvciBHb1xyXG4oYykgTGVhIEFudGhvbnkgMjAxOS1wcmVzZW50XHJcbiovXHJcblxyXG4vKiBqc2hpbnQgZXN2ZXJzaW9uOiA5ICovXHJcbmltcG9ydCB7IG5hbm9pZCB9IGZyb20gJ25hbm9pZC9ub24tc2VjdXJlJztcclxuXHJcbmNvbnN0IHJ1bnRpbWVVUkwgPSB3aW5kb3cubG9jYXRpb24ub3JpZ2luICsgXCIvd2FpbHMvcnVudGltZVwiO1xyXG4vLyBPYmplY3QgTmFtZXNcclxuZXhwb3J0IGNvbnN0IG9iamVjdE5hbWVzID0ge1xyXG4gICAgQ2FsbDogMCxcclxuICAgIENsaXBib2FyZDogMSxcclxuICAgIEFwcGxpY2F0aW9uOiAyLFxyXG4gICAgRXZlbnRzOiAzLFxyXG4gICAgQ29udGV4dE1lbnU6IDQsXHJcbiAgICBEaWFsb2c6IDUsXHJcbiAgICBXaW5kb3c6IDYsXHJcbiAgICBTY3JlZW5zOiA3LFxyXG4gICAgU3lzdGVtOiA4LFxyXG4gICAgQnJvd3NlcjogOSxcclxufVxyXG5leHBvcnQgbGV0IGNsaWVudElkID0gbmFub2lkKCk7XHJcblxyXG5mdW5jdGlvbiBydW50aW1lQ2FsbChtZXRob2QsIHdpbmRvd05hbWUsIGFyZ3MpIHtcclxuICAgIGxldCB1cmwgPSBuZXcgVVJMKHJ1bnRpbWVVUkwpO1xyXG4gICAgaWYoIG1ldGhvZCApIHtcclxuICAgICAgICB1cmwuc2VhcmNoUGFyYW1zLmFwcGVuZChcIm1ldGhvZFwiLCBtZXRob2QpO1xyXG4gICAgfVxyXG4gICAgbGV0IGZldGNoT3B0aW9ucyA9IHtcclxuICAgICAgICBoZWFkZXJzOiB7fSxcclxuICAgIH07XHJcbiAgICBpZiAod2luZG93TmFtZSkge1xyXG4gICAgICAgIGZldGNoT3B0aW9ucy5oZWFkZXJzW1wieC13YWlscy13aW5kb3ctbmFtZVwiXSA9IHdpbmRvd05hbWU7XHJcbiAgICB9XHJcbiAgICBpZiAoYXJncykge1xyXG4gICAgICAgIHVybC5zZWFyY2hQYXJhbXMuYXBwZW5kKFwiYXJnc1wiLCBKU09OLnN0cmluZ2lmeShhcmdzKSk7XHJcbiAgICB9XHJcbiAgICBmZXRjaE9wdGlvbnMuaGVhZGVyc1tcIngtd2FpbHMtY2xpZW50LWlkXCJdID0gY2xpZW50SWQ7XHJcblxyXG4gICAgcmV0dXJuIG5ldyBQcm9taXNlKChyZXNvbHZlLCByZWplY3QpID0+IHtcclxuICAgICAgICBmZXRjaCh1cmwsIGZldGNoT3B0aW9ucylcclxuICAgICAgICAgICAgLnRoZW4ocmVzcG9uc2UgPT4ge1xyXG4gICAgICAgICAgICAgICAgaWYgKHJlc3BvbnNlLm9rKSB7XHJcbiAgICAgICAgICAgICAgICAgICAgLy8gY2hlY2sgY29udGVudCB0eXBlXHJcbiAgICAgICAgICAgICAgICAgICAgaWYgKHJlc3BvbnNlLmhlYWRlcnMuZ2V0KFwiQ29udGVudC1UeXBlXCIpICYmIHJlc3BvbnNlLmhlYWRlcnMuZ2V0KFwiQ29udGVudC1UeXBlXCIpLmluZGV4T2YoXCJhcHBsaWNhdGlvbi9qc29uXCIpICE9PSAtMSkge1xyXG4gICAgICAgICAgICAgICAgICAgICAgICByZXR1cm4gcmVzcG9uc2UuanNvbigpO1xyXG4gICAgICAgICAgICAgICAgICAgIH0gZWxzZSB7XHJcbiAgICAgICAgICAgICAgICAgICAgICAgIHJldHVybiByZXNwb25zZS50ZXh0KCk7XHJcbiAgICAgICAgICAgICAgICAgICAgfVxyXG4gICAgICAgICAgICAgICAgfVxyXG4gICAgICAgICAgICAgICAgcmVqZWN0KEVycm9yKHJlc3BvbnNlLnN0YXR1c1RleHQpKTtcclxuICAgICAgICAgICAgfSlcclxuICAgICAgICAgICAgLnRoZW4oZGF0YSA9PiByZXNvbHZlKGRhdGEpKVxyXG4gICAgICAgICAgICAuY2F0Y2goZXJyb3IgPT4gcmVqZWN0KGVycm9yKSk7XHJcbiAgICB9KTtcclxufVxyXG5cclxuZXhwb3J0IGZ1bmN0aW9uIG5ld1J1bnRpbWVDYWxsZXIob2JqZWN0LCB3aW5kb3dOYW1lKSB7XHJcbiAgICByZXR1cm4gZnVuY3Rpb24gKG1ldGhvZCwgYXJncz1udWxsKSB7XHJcbiAgICAgICAgcmV0dXJuIHJ1bnRpbWVDYWxsKG9iamVjdCArIFwiLlwiICsgbWV0aG9kLCB3aW5kb3dOYW1lLCBhcmdzKTtcclxuICAgIH07XHJcbn1cclxuXHJcbmZ1bmN0aW9uIHJ1bnRpbWVDYWxsV2l0aElEKG9iamVjdElELCBtZXRob2QsIHdpbmRvd05hbWUsIGFyZ3MpIHtcclxuICAgIGxldCB1cmwgPSBuZXcgVVJMKHJ1bnRpbWVVUkwpO1xyXG4gICAgdXJsLnNlYXJjaFBhcmFtcy5hcHBlbmQoXCJvYmplY3RcIiwgb2JqZWN0SUQpO1xyXG4gICAgdXJsLnNlYXJjaFBhcmFtcy5hcHBlbmQoXCJtZXRob2RcIiwgbWV0aG9kKTtcclxuICAgIGxldCBmZXRjaE9wdGlvbnMgPSB7XHJcbiAgICAgICAgaGVhZGVyczoge30sXHJcbiAgICB9O1xyXG4gICAgaWYgKHdpbmRvd05hbWUpIHtcclxuICAgICAgICBmZXRjaE9wdGlvbnMuaGVhZGVyc1tcIngtd2FpbHMtd2luZG93LW5hbWVcIl0gPSB3aW5kb3dOYW1lO1xyXG4gICAgfVxyXG4gICAgaWYgKGFyZ3MpIHtcclxuICAgICAgICB1cmwuc2VhcmNoUGFyYW1zLmFwcGVuZChcImFyZ3NcIiwgSlNPTi5zdHJpbmdpZnkoYXJncykpO1xyXG4gICAgfVxyXG4gICAgZmV0Y2hPcHRpb25zLmhlYWRlcnNbXCJ4LXdhaWxzLWNsaWVudC1pZFwiXSA9IGNsaWVudElkO1xyXG4gICAgcmV0dXJuIG5ldyBQcm9taXNlKChyZXNvbHZlLCByZWplY3QpID0+IHtcclxuICAgICAgICBmZXRjaCh1cmwsIGZldGNoT3B0aW9ucylcclxuICAgICAgICAgICAgLnRoZW4ocmVzcG9uc2UgPT4ge1xyXG4gICAgICAgICAgICAgICAgaWYgKHJlc3BvbnNlLm9rKSB7XHJcbiAgICAgICAgICAgICAgICAgICAgLy8gY2hlY2sgY29udGVudCB0eXBlXHJcbiAgICAgICAgICAgICAgICAgICAgaWYgKHJlc3BvbnNlLmhlYWRlcnMuZ2V0KFwiQ29udGVudC1UeXBlXCIpICYmIHJlc3BvbnNlLmhlYWRlcnMuZ2V0KFwiQ29udGVudC1UeXBlXCIpLmluZGV4T2YoXCJhcHBsaWNhdGlvbi9qc29uXCIpICE9PSAtMSkge1xyXG4gICAgICAgICAgICAgICAgICAgICAgICByZXR1cm4gcmVzcG9uc2UuanNvbigpO1xyXG4gICAgICAgICAgICAgICAgICAgIH0gZWxzZSB7XHJcbiAgICAgICAgICAgICAgICAgICAgICAgIHJldHVybiByZXNwb25zZS50ZXh0KCk7XHJcbiAgICAgICAgICAgICAgICAgICAgfVxyXG4gICAgICAgICAgICAgICAgfVxyXG4gICAgICAgICAgICAgICAgcmVqZWN0KEVycm9yKHJlc3BvbnNlLnN0YXR1c1RleHQpKTtcclxuICAgICAgICAgICAgfSlcclxuICAgICAgICAgICAgLnRoZW4oZGF0YSA9PiByZXNvbHZlKGRhdGEpKVxyXG4gICAgICAgICAgICAuY2F0Y2goZXJyb3IgPT4gcmVqZWN0KGVycm9yKSk7XHJcbiAgICB9KTtcclxufVxyXG5cclxuZXhwb3J0IGZ1bmN0aW9uIG5ld1J1bnRpbWVDYWxsZXJXaXRoSUQob2JqZWN0LCB3aW5kb3dOYW1lKSB7XHJcbiAgICByZXR1cm4gZnVuY3Rpb24gKG1ldGhvZCwgYXJncz1udWxsKSB7XHJcbiAgICAgICAgcmV0dXJuIHJ1bnRpbWVDYWxsV2l0aElEKG9iamVjdCwgbWV0aG9kLCB3aW5kb3dOYW1lLCBhcmdzKTtcclxuICAgIH07XHJcbn1cclxuIiwgIi8qXHJcbiBfXHQgICBfX1x0ICBfIF9fXHJcbnwgfFx0IC8gL19fXyBfKF8pIC9fX19fXHJcbnwgfCAvfCAvIC8gX18gYC8gLyAvIF9fXy9cclxufCB8LyB8LyAvIC9fLyAvIC8gKF9fICApXHJcbnxfXy98X18vXFxfXyxfL18vXy9fX19fL1xyXG5UaGUgZWxlY3Ryb24gYWx0ZXJuYXRpdmUgZm9yIEdvXHJcbihjKSBMZWEgQW50aG9ueSAyMDE5LXByZXNlbnRcclxuKi9cclxuXHJcbi8qIGpzaGludCBlc3ZlcnNpb246IDkgKi9cclxuXHJcbmltcG9ydCB7bmV3UnVudGltZUNhbGxlcldpdGhJRCwgb2JqZWN0TmFtZXN9IGZyb20gXCIuL3J1bnRpbWVcIjtcclxuXHJcbmxldCBjYWxsID0gbmV3UnVudGltZUNhbGxlcldpdGhJRChvYmplY3ROYW1lcy5BcHBsaWNhdGlvbik7XHJcblxyXG5sZXQgbWV0aG9kcyA9IHtcclxuICAgIEhpZGU6IDAsXHJcbiAgICBTaG93OiAxLFxyXG4gICAgUXVpdDogMixcclxufVxyXG5cclxuLyoqXHJcbiAqIEhpZGUgdGhlIGFwcGxpY2F0aW9uXHJcbiAqL1xyXG5leHBvcnQgZnVuY3Rpb24gSGlkZSgpIHtcclxuICAgIHZvaWQgY2FsbChtZXRob2RzLkhpZGUpO1xyXG59XHJcblxyXG4vKipcclxuICogU2hvdyB0aGUgYXBwbGljYXRpb25cclxuICovXHJcbmV4cG9ydCBmdW5jdGlvbiBTaG93KCkge1xyXG4gICAgdm9pZCBjYWxsKG1ldGhvZHMuU2hvdyk7XHJcbn1cclxuXHJcblxyXG4vKipcclxuICogUXVpdCB0aGUgYXBwbGljYXRpb25cclxuICovXHJcbmV4cG9ydCBmdW5jdGlvbiBRdWl0KCkge1xyXG4gICAgdm9pZCBjYWxsKG1ldGhvZHMuUXVpdCk7XHJcbn0iLCAiLypcclxuIF9cdCAgIF9fXHQgIF8gX19cclxufCB8XHQgLyAvX19fIF8oXykgL19fX19cclxufCB8IC98IC8gLyBfXyBgLyAvIC8gX19fL1xyXG58IHwvIHwvIC8gL18vIC8gLyAoX18gIClcclxufF9fL3xfXy9cXF9fLF8vXy9fL19fX18vXHJcblRoZSBlbGVjdHJvbiBhbHRlcm5hdGl2ZSBmb3IgR29cclxuKGMpIExlYSBBbnRob255IDIwMTktcHJlc2VudFxyXG4qL1xyXG5cclxuLyoganNoaW50IGVzdmVyc2lvbjogOSAqL1xyXG5cclxuLyoqXHJcbiAqIEB0eXBlZGVmIHtpbXBvcnQoXCIuL2FwaS90eXBlc1wiKS5TY3JlZW59IFNjcmVlblxyXG4gKi9cclxuXHJcbmltcG9ydCB7bmV3UnVudGltZUNhbGxlcldpdGhJRCwgb2JqZWN0TmFtZXN9IGZyb20gXCIuL3J1bnRpbWVcIjtcclxuXHJcbmxldCBjYWxsID0gbmV3UnVudGltZUNhbGxlcldpdGhJRChvYmplY3ROYW1lcy5TY3JlZW5zKTtcclxuXHJcbmxldCBTY3JlZW5zR2V0QWxsID0gMDtcclxubGV0IFNjcmVlbnNHZXRQcmltYXJ5ID0gMTtcclxubGV0IFNjcmVlbnNHZXRDdXJyZW50ID0gMjtcclxuXHJcbi8qKlxyXG4gKiBHZXRzIGFsbCBzY3JlZW5zLlxyXG4gKiBAcmV0dXJucyB7UHJvbWlzZTxTY3JlZW5bXT59XHJcbiAqL1xyXG5leHBvcnQgZnVuY3Rpb24gR2V0QWxsKCkge1xyXG4gICAgcmV0dXJuIGNhbGwoU2NyZWVuc0dldEFsbCk7XHJcbn1cclxuXHJcbi8qKlxyXG4gKiBHZXRzIHRoZSBwcmltYXJ5IHNjcmVlbi5cclxuICogQHJldHVybnMge1Byb21pc2U8U2NyZWVuPn1cclxuICovXHJcbmV4cG9ydCBmdW5jdGlvbiBHZXRQcmltYXJ5KCkge1xyXG4gICAgcmV0dXJuIGNhbGwoU2NyZWVuc0dldFByaW1hcnkpO1xyXG59XHJcblxyXG4vKipcclxuICogR2V0cyB0aGUgY3VycmVudCBhY3RpdmUgc2NyZWVuLlxyXG4gKiBAcmV0dXJucyB7UHJvbWlzZTxTY3JlZW4+fVxyXG4gKiBAY29uc3RydWN0b3JcclxuICovXHJcbmV4cG9ydCBmdW5jdGlvbiBHZXRDdXJyZW50KCkge1xyXG4gICAgcmV0dXJuIGNhbGwoU2NyZWVuc0dldEN1cnJlbnQpO1xyXG59IiwgIi8qXHJcbiBfXHQgICBfX1x0ICBfIF9fXHJcbnwgfFx0IC8gL19fXyBfKF8pIC9fX19fXHJcbnwgfCAvfCAvIC8gX18gYC8gLyAvIF9fXy9cclxufCB8LyB8LyAvIC9fLyAvIC8gKF9fICApXHJcbnxfXy98X18vXFxfXyxfL18vXy9fX19fL1xyXG5UaGUgZWxlY3Ryb24gYWx0ZXJuYXRpdmUgZm9yIEdvXHJcbihjKSBMZWEgQW50aG9ueSAyMDE5LXByZXNlbnRcclxuKi9cclxuXHJcbi8qIGpzaGludCBlc3ZlcnNpb246IDkgKi9cclxuXHJcbmltcG9ydCB7bmV3UnVudGltZUNhbGxlcldpdGhJRCwgb2JqZWN0TmFtZXN9IGZyb20gXCIuL3J1bnRpbWVcIjtcclxuXHJcbmxldCBjYWxsID0gbmV3UnVudGltZUNhbGxlcldpdGhJRChvYmplY3ROYW1lcy5TeXN0ZW0pO1xyXG5cclxubGV0IFN5c3RlbUlzRGFya01vZGUgPSAwO1xyXG5cclxuLyoqXHJcbiAqIERldGVybWluZXMgaWYgdGhlIHN5c3RlbSBpcyBjdXJyZW50bHkgdXNpbmcgZGFyayBtb2RlXHJcbiAqIEByZXR1cm5zIHtQcm9taXNlPGJvb2xlYW4+fVxyXG4gKi9cclxuZXhwb3J0IGZ1bmN0aW9uIElzRGFya01vZGUoKSB7XHJcbiAgICByZXR1cm4gY2FsbChTeXN0ZW1Jc0RhcmtNb2RlKTtcclxufSIsICIvKlxyXG4gX1x0ICAgX19cdCAgXyBfX1xyXG58IHxcdCAvIC9fX18gXyhfKSAvX19fX1xyXG58IHwgL3wgLyAvIF9fIGAvIC8gLyBfX18vXHJcbnwgfC8gfC8gLyAvXy8gLyAvIChfXyAgKVxyXG58X18vfF9fL1xcX18sXy9fL18vX19fXy9cclxuVGhlIGVsZWN0cm9uIGFsdGVybmF0aXZlIGZvciBHb1xyXG4oYykgTGVhIEFudGhvbnkgMjAxOS1wcmVzZW50XHJcbiovXHJcblxyXG4vKiBqc2hpbnQgZXN2ZXJzaW9uOiA5ICovXHJcblxyXG5pbXBvcnQge25ld1J1bnRpbWVDYWxsZXJXaXRoSUQsIG9iamVjdE5hbWVzfSBmcm9tIFwiLi9ydW50aW1lXCI7XHJcblxyXG5sZXQgY2FsbCA9IG5ld1J1bnRpbWVDYWxsZXJXaXRoSUQob2JqZWN0TmFtZXMuQnJvd3Nlcik7XHJcblxyXG5sZXQgQnJvd3Nlck9wZW5VUkwgPSAwO1xyXG5cclxuLyoqXHJcbiAqIE9wZW4gYSBicm93c2VyIHdpbmRvdyB0byB0aGUgZ2l2ZW4gVVJMXHJcbiAqIEBwYXJhbSB7c3RyaW5nfSB1cmwgLSBUaGUgVVJMIHRvIG9wZW5cclxuICovXHJcbmV4cG9ydCBmdW5jdGlvbiBPcGVuVVJMKHVybCkge1xyXG4gICAgdm9pZCBjYWxsKEJyb3dzZXJPcGVuVVJMLCB7dXJsfSk7XHJcbn1cclxuIiwgIi8qXHJcbiBfXHQgICBfX1x0ICBfIF9fXHJcbnwgfFx0IC8gL19fXyBfKF8pIC9fX19fXHJcbnwgfCAvfCAvIC8gX18gYC8gLyAvIF9fXy9cclxufCB8LyB8LyAvIC9fLyAvIC8gKF9fICApXHJcbnxfXy98X18vXFxfXyxfL18vXy9fX19fL1xyXG5UaGUgZWxlY3Ryb24gYWx0ZXJuYXRpdmUgZm9yIEdvXHJcbihjKSBMZWEgQW50aG9ueSAyMDE5LXByZXNlbnRcclxuKi9cclxuXHJcbi8qIGpzaGludCBlc3ZlcnNpb246IDkgKi9cclxuXHJcbmltcG9ydCB7bmV3UnVudGltZUNhbGxlcldpdGhJRCwgb2JqZWN0TmFtZXN9IGZyb20gXCIuL3J1bnRpbWVcIjtcclxuXHJcbmltcG9ydCB7IG5hbm9pZCB9IGZyb20gJ25hbm9pZC9ub24tc2VjdXJlJztcclxuXHJcbmxldCBjYWxsID0gbmV3UnVudGltZUNhbGxlcldpdGhJRChvYmplY3ROYW1lcy5DYWxsKTtcclxuXHJcbmxldCBDYWxsQmluZGluZyA9IDA7XHJcblxyXG5sZXQgY2FsbFJlc3BvbnNlcyA9IG5ldyBNYXAoKTtcclxuXHJcbmZ1bmN0aW9uIGdlbmVyYXRlSUQoKSB7XHJcbiAgICBsZXQgcmVzdWx0O1xyXG4gICAgZG8ge1xyXG4gICAgICAgIHJlc3VsdCA9IG5hbm9pZCgpO1xyXG4gICAgfSB3aGlsZSAoY2FsbFJlc3BvbnNlcy5oYXMocmVzdWx0KSk7XHJcbiAgICByZXR1cm4gcmVzdWx0O1xyXG59XHJcblxyXG5leHBvcnQgZnVuY3Rpb24gY2FsbENhbGxiYWNrKGlkLCBkYXRhLCBpc0pTT04pIHtcclxuICAgIGxldCBwID0gY2FsbFJlc3BvbnNlcy5nZXQoaWQpO1xyXG4gICAgaWYgKHApIHtcclxuICAgICAgICBpZiAoaXNKU09OKSB7XHJcbiAgICAgICAgICAgIHAucmVzb2x2ZShKU09OLnBhcnNlKGRhdGEpKTtcclxuICAgICAgICB9IGVsc2Uge1xyXG4gICAgICAgICAgICBwLnJlc29sdmUoZGF0YSk7XHJcbiAgICAgICAgfVxyXG4gICAgICAgIGNhbGxSZXNwb25zZXMuZGVsZXRlKGlkKTtcclxuICAgIH1cclxufVxyXG5cclxuZXhwb3J0IGZ1bmN0aW9uIGNhbGxFcnJvckNhbGxiYWNrKGlkLCBtZXNzYWdlKSB7XHJcbiAgICBsZXQgcCA9IGNhbGxSZXNwb25zZXMuZ2V0KGlkKTtcclxuICAgIGlmIChwKSB7XHJcbiAgICAgICAgcC5yZWplY3QobWVzc2FnZSk7XHJcbiAgICAgICAgY2FsbFJlc3BvbnNlcy5kZWxldGUoaWQpO1xyXG4gICAgfVxyXG59XHJcblxyXG5mdW5jdGlvbiBjYWxsQmluZGluZyh0eXBlLCBvcHRpb25zKSB7XHJcbiAgICByZXR1cm4gbmV3IFByb21pc2UoKHJlc29sdmUsIHJlamVjdCkgPT4ge1xyXG4gICAgICAgIGxldCBpZCA9IGdlbmVyYXRlSUQoKTtcclxuICAgICAgICBvcHRpb25zID0gb3B0aW9ucyB8fCB7fTtcclxuICAgICAgICBvcHRpb25zW1wiY2FsbC1pZFwiXSA9IGlkO1xyXG5cclxuICAgICAgICBjYWxsUmVzcG9uc2VzLnNldChpZCwge3Jlc29sdmUsIHJlamVjdH0pO1xyXG4gICAgICAgIGNhbGwodHlwZSwgb3B0aW9ucykuY2F0Y2goKGVycm9yKSA9PiB7XHJcbiAgICAgICAgICAgIHJlamVjdChlcnJvcik7XHJcbiAgICAgICAgICAgIGNhbGxSZXNwb25zZXMuZGVsZXRlKGlkKTtcclxuICAgICAgICB9KTtcclxuICAgIH0pO1xyXG59XHJcblxyXG5leHBvcnQgZnVuY3Rpb24gQ2FsbChvcHRpb25zKSB7XHJcbiAgICByZXR1cm4gY2FsbEJpbmRpbmcoQ2FsbEJpbmRpbmcsIG9wdGlvbnMpO1xyXG59XHJcblxyXG5leHBvcnQgZnVuY3Rpb24gQ2FsbEJ5TmFtZShuYW1lLCAuLi5hcmdzKSB7XHJcblxyXG4gICAgLy8gRW5zdXJlIGZpcnN0IGFyZ3VtZW50IGlzIGEgc3RyaW5nIGFuZCBoYXMgMiBkb3RzXHJcbiAgICBpZiAodHlwZW9mIG5hbWUgIT09IFwic3RyaW5nXCIgfHwgbmFtZS5zcGxpdChcIi5cIikubGVuZ3RoICE9PSAzKSB7XHJcbiAgICAgICAgdGhyb3cgbmV3IEVycm9yKFwiQ2FsbEJ5TmFtZSByZXF1aXJlcyBhIHN0cmluZyBpbiB0aGUgZm9ybWF0ICdwYWNrYWdlLnN0cnVjdC5tZXRob2QnXCIpO1xyXG4gICAgfVxyXG4gICAgLy8gU3BsaXQgaW5wdXRzXHJcbiAgICBsZXQgcGFydHMgPSBuYW1lLnNwbGl0KFwiLlwiKTtcclxuXHJcbiAgICByZXR1cm4gY2FsbEJpbmRpbmcoQ2FsbEJpbmRpbmcsIHtcclxuICAgICAgICBwYWNrYWdlTmFtZTogcGFydHNbMF0sXHJcbiAgICAgICAgc3RydWN0TmFtZTogcGFydHNbMV0sXHJcbiAgICAgICAgbWV0aG9kTmFtZTogcGFydHNbMl0sXHJcbiAgICAgICAgYXJnczogYXJncyxcclxuICAgIH0pO1xyXG59XHJcblxyXG5leHBvcnQgZnVuY3Rpb24gQ2FsbEJ5SUQobWV0aG9kSUQsIC4uLmFyZ3MpIHtcclxuICAgIHJldHVybiBjYWxsQmluZGluZyhDYWxsQmluZGluZywge1xyXG4gICAgICAgIG1ldGhvZElEOiBtZXRob2RJRCxcclxuICAgICAgICBhcmdzOiBhcmdzLFxyXG4gICAgfSk7XHJcbn1cclxuXHJcbi8qKlxyXG4gKiBDYWxsIGEgcGx1Z2luIG1ldGhvZFxyXG4gKiBAcGFyYW0ge3N0cmluZ30gcGx1Z2luTmFtZSAtIG5hbWUgb2YgdGhlIHBsdWdpblxyXG4gKiBAcGFyYW0ge3N0cmluZ30gbWV0aG9kTmFtZSAtIG5hbWUgb2YgdGhlIG1ldGhvZFxyXG4gKiBAcGFyYW0gey4uLmFueX0gYXJncyAtIGFyZ3VtZW50cyB0byBwYXNzIHRvIHRoZSBtZXRob2RcclxuICogQHJldHVybnMge1Byb21pc2U8YW55Pn0gLSBwcm9taXNlIHRoYXQgcmVzb2x2ZXMgd2l0aCB0aGUgcmVzdWx0XHJcbiAqL1xyXG5leHBvcnQgZnVuY3Rpb24gUGx1Z2luKHBsdWdpbk5hbWUsIG1ldGhvZE5hbWUsIC4uLmFyZ3MpIHtcclxuICAgIHJldHVybiBjYWxsQmluZGluZyhDYWxsQmluZGluZywge1xyXG4gICAgICAgIHBhY2thZ2VOYW1lOiBcIndhaWxzLXBsdWdpbnNcIixcclxuICAgICAgICBzdHJ1Y3ROYW1lOiBwbHVnaW5OYW1lLFxyXG4gICAgICAgIG1ldGhvZE5hbWU6IG1ldGhvZE5hbWUsXHJcbiAgICAgICAgYXJnczogYXJncyxcclxuICAgIH0pO1xyXG59IiwgIi8qXHJcbiBfXHQgICBfX1x0ICBfIF9fXHJcbnwgfFx0IC8gL19fXyBfKF8pIC9fX19fXHJcbnwgfCAvfCAvIC8gX18gYC8gLyAvIF9fXy9cclxufCB8LyB8LyAvIC9fLyAvIC8gKF9fICApXHJcbnxfXy98X18vXFxfXyxfL18vXy9fX19fL1xyXG5UaGUgZWxlY3Ryb24gYWx0ZXJuYXRpdmUgZm9yIEdvXHJcbihjKSBMZWEgQW50aG9ueSAyMDE5LXByZXNlbnRcclxuKi9cclxuXHJcbi8qIGpzaGludCBlc3ZlcnNpb246IDkgKi9cclxuXHJcbi8qKlxyXG4gKiBAdHlwZWRlZiB7aW1wb3J0KFwiLi4vYXBpL3R5cGVzXCIpLlNpemV9IFNpemVcclxuICogQHR5cGVkZWYge2ltcG9ydChcIi4uL2FwaS90eXBlc1wiKS5Qb3NpdGlvbn0gUG9zaXRpb25cclxuICogQHR5cGVkZWYge2ltcG9ydChcIi4uL2FwaS90eXBlc1wiKS5TY3JlZW59IFNjcmVlblxyXG4gKi9cclxuXHJcbmltcG9ydCB7bmV3UnVudGltZUNhbGxlcldpdGhJRCwgb2JqZWN0TmFtZXN9IGZyb20gXCIuL3J1bnRpbWVcIjtcclxuXHJcbmxldCBXaW5kb3dDZW50ZXIgPSAwO1xyXG5sZXQgV2luZG93U2V0VGl0bGUgPSAxO1xyXG5sZXQgV2luZG93RnVsbHNjcmVlbiA9IDI7XHJcbmxldCBXaW5kb3dVbkZ1bGxzY3JlZW4gPSAzO1xyXG5sZXQgV2luZG93U2V0U2l6ZSA9IDQ7XHJcbmxldCBXaW5kb3dTaXplID0gNTtcclxubGV0IFdpbmRvd1NldE1heFNpemUgPSA2O1xyXG5sZXQgV2luZG93U2V0TWluU2l6ZSA9IDc7XHJcbmxldCBXaW5kb3dTZXRBbHdheXNPblRvcCA9IDg7XHJcbmxldCBXaW5kb3dTZXRSZWxhdGl2ZVBvc2l0aW9uID0gOTtcclxubGV0IFdpbmRvd1JlbGF0aXZlUG9zaXRpb24gPSAxMDtcclxubGV0IFdpbmRvd1NjcmVlbiA9IDExO1xyXG5sZXQgV2luZG93SGlkZSA9IDEyO1xyXG5sZXQgV2luZG93TWF4aW1pc2UgPSAxMztcclxubGV0IFdpbmRvd1VuTWF4aW1pc2UgPSAxNDtcclxubGV0IFdpbmRvd1RvZ2dsZU1heGltaXNlID0gMTU7XHJcbmxldCBXaW5kb3dNaW5pbWlzZSA9IDE2O1xyXG5sZXQgV2luZG93VW5NaW5pbWlzZSA9IDE3O1xyXG5sZXQgV2luZG93UmVzdG9yZSA9IDE4O1xyXG5sZXQgV2luZG93U2hvdyA9IDE5O1xyXG5sZXQgV2luZG93Q2xvc2UgPSAyMDtcclxubGV0IFdpbmRvd1NldEJhY2tncm91bmRDb2xvdXIgPSAyMTtcclxubGV0IFdpbmRvd1NldFJlc2l6YWJsZSA9IDIyO1xyXG5sZXQgV2luZG93V2lkdGggPSAyMztcclxubGV0IFdpbmRvd0hlaWdodCA9IDI0O1xyXG5sZXQgV2luZG93Wm9vbUluID0gMjU7XHJcbmxldCBXaW5kb3dab29tT3V0ID0gMjY7XHJcbmxldCBXaW5kb3dab29tUmVzZXQgPSAyNztcclxubGV0IFdpbmRvd0dldFpvb21MZXZlbCA9IDI4O1xyXG5sZXQgV2luZG93U2V0Wm9vbUxldmVsID0gMjk7XHJcblxyXG5leHBvcnQgZnVuY3Rpb24gbmV3V2luZG93KHdpbmRvd05hbWUpIHtcclxuICAgIGxldCBjYWxsID0gbmV3UnVudGltZUNhbGxlcldpdGhJRChvYmplY3ROYW1lcy5XaW5kb3csIHdpbmRvd05hbWUpO1xyXG4gICAgcmV0dXJuIHtcclxuXHJcbiAgICAgICAgLyoqXHJcbiAgICAgICAgICogQ2VudGVycyB0aGUgd2luZG93LlxyXG4gICAgICAgICAqL1xyXG4gICAgICAgIENlbnRlcjogKCkgPT4gdm9pZCBjYWxsKFdpbmRvd0NlbnRlciksXHJcblxyXG4gICAgICAgIC8qKlxyXG4gICAgICAgICAqIFNldCB0aGUgd2luZG93IHRpdGxlLlxyXG4gICAgICAgICAqIEBwYXJhbSB0aXRsZVxyXG4gICAgICAgICAqL1xyXG4gICAgICAgIFNldFRpdGxlOiAodGl0bGUpID0+IHZvaWQgY2FsbChXaW5kb3dTZXRUaXRsZSwge3RpdGxlfSksXHJcblxyXG4gICAgICAgIC8qKlxyXG4gICAgICAgICAqIE1ha2VzIHRoZSB3aW5kb3cgZnVsbHNjcmVlbi5cclxuICAgICAgICAgKi9cclxuICAgICAgICBGdWxsc2NyZWVuOiAoKSA9PiB2b2lkIGNhbGwoV2luZG93RnVsbHNjcmVlbiksXHJcblxyXG4gICAgICAgIC8qKlxyXG4gICAgICAgICAqIFVuZnVsbHNjcmVlbiB0aGUgd2luZG93LlxyXG4gICAgICAgICAqL1xyXG4gICAgICAgIFVuRnVsbHNjcmVlbjogKCkgPT4gdm9pZCBjYWxsKFdpbmRvd1VuRnVsbHNjcmVlbiksXHJcblxyXG4gICAgICAgIC8qKlxyXG4gICAgICAgICAqIFNldCB0aGUgd2luZG93IHNpemUuXHJcbiAgICAgICAgICogQHBhcmFtIHtudW1iZXJ9IHdpZHRoIFRoZSB3aW5kb3cgd2lkdGhcclxuICAgICAgICAgKiBAcGFyYW0ge251bWJlcn0gaGVpZ2h0IFRoZSB3aW5kb3cgaGVpZ2h0XHJcbiAgICAgICAgICovXHJcbiAgICAgICAgU2V0U2l6ZTogKHdpZHRoLCBoZWlnaHQpID0+IGNhbGwoV2luZG93U2V0U2l6ZSwge3dpZHRoLGhlaWdodH0pLFxyXG5cclxuICAgICAgICAvKipcclxuICAgICAgICAgKiBHZXQgdGhlIHdpbmRvdyBzaXplLlxyXG4gICAgICAgICAqIEByZXR1cm5zIHtQcm9taXNlPFNpemU+fSBUaGUgd2luZG93IHNpemVcclxuICAgICAgICAgKi9cclxuICAgICAgICBTaXplOiAoKSA9PiB7IHJldHVybiBjYWxsKFdpbmRvd1NpemUpOyB9LFxyXG5cclxuICAgICAgICAvKipcclxuICAgICAgICAgKiBTZXQgdGhlIHdpbmRvdyBtYXhpbXVtIHNpemUuXHJcbiAgICAgICAgICogQHBhcmFtIHtudW1iZXJ9IHdpZHRoXHJcbiAgICAgICAgICogQHBhcmFtIHtudW1iZXJ9IGhlaWdodFxyXG4gICAgICAgICAqL1xyXG4gICAgICAgIFNldE1heFNpemU6ICh3aWR0aCwgaGVpZ2h0KSA9PiB2b2lkIGNhbGwoV2luZG93U2V0TWF4U2l6ZSwge3dpZHRoLGhlaWdodH0pLFxyXG5cclxuICAgICAgICAvKipcclxuICAgICAgICAgKiBTZXQgdGhlIHdpbmRvdyBtaW5pbXVtIHNpemUuXHJcbiAgICAgICAgICogQHBhcmFtIHtudW1iZXJ9IHdpZHRoXHJcbiAgICAgICAgICogQHBhcmFtIHtudW1iZXJ9IGhlaWdodFxyXG4gICAgICAgICAqL1xyXG4gICAgICAgIFNldE1pblNpemU6ICh3aWR0aCwgaGVpZ2h0KSA9PiB2b2lkIGNhbGwoV2luZG93U2V0TWluU2l6ZSwge3dpZHRoLGhlaWdodH0pLFxyXG5cclxuICAgICAgICAvKipcclxuICAgICAgICAgKiBTZXQgd2luZG93IHRvIGJlIGFsd2F5cyBvbiB0b3AuXHJcbiAgICAgICAgICogQHBhcmFtIHtib29sZWFufSBvblRvcCBXaGV0aGVyIHRoZSB3aW5kb3cgc2hvdWxkIGJlIGFsd2F5cyBvbiB0b3BcclxuICAgICAgICAgKi9cclxuICAgICAgICBTZXRBbHdheXNPblRvcDogKG9uVG9wKSA9PiB2b2lkIGNhbGwoV2luZG93U2V0QWx3YXlzT25Ub3AsIHthbHdheXNPblRvcDpvblRvcH0pLFxyXG5cclxuICAgICAgICAvKipcclxuICAgICAgICAgKiBTZXQgdGhlIHdpbmRvdyByZWxhdGl2ZSBwb3NpdGlvbi5cclxuICAgICAgICAgKiBAcGFyYW0ge251bWJlcn0geFxyXG4gICAgICAgICAqIEBwYXJhbSB7bnVtYmVyfSB5XHJcbiAgICAgICAgICovXHJcbiAgICAgICAgU2V0UmVsYXRpdmVQb3NpdGlvbjogKHgsIHkpID0+IGNhbGwoV2luZG93U2V0UmVsYXRpdmVQb3NpdGlvbiwge3gseX0pLFxyXG5cclxuICAgICAgICAvKipcclxuICAgICAgICAgKiBHZXQgdGhlIHdpbmRvdyBwb3NpdGlvbi5cclxuICAgICAgICAgKiBAcmV0dXJucyB7UHJvbWlzZTxQb3NpdGlvbj59IFRoZSB3aW5kb3cgcG9zaXRpb25cclxuICAgICAgICAgKi9cclxuICAgICAgICBSZWxhdGl2ZVBvc2l0aW9uOiAoKSA9PiB7IHJldHVybiBjYWxsKFdpbmRvd1JlbGF0aXZlUG9zaXRpb24pOyB9LFxyXG5cclxuICAgICAgICAvKipcclxuICAgICAgICAgKiBHZXQgdGhlIHNjcmVlbiB0aGUgd2luZG93IGlzIG9uLlxyXG4gICAgICAgICAqIEByZXR1cm5zIHtQcm9taXNlPFNjcmVlbj59XHJcbiAgICAgICAgICovXHJcbiAgICAgICAgU2NyZWVuOiAoKSA9PiB7IHJldHVybiBjYWxsKFdpbmRvd1NjcmVlbik7IH0sXHJcblxyXG4gICAgICAgIC8qKlxyXG4gICAgICAgICAqIEhpZGUgdGhlIHdpbmRvd1xyXG4gICAgICAgICAqL1xyXG4gICAgICAgIEhpZGU6ICgpID0+IHZvaWQgY2FsbChXaW5kb3dIaWRlKSxcclxuXHJcbiAgICAgICAgLyoqXHJcbiAgICAgICAgICogTWF4aW1pc2UgdGhlIHdpbmRvd1xyXG4gICAgICAgICAqL1xyXG4gICAgICAgIE1heGltaXNlOiAoKSA9PiB2b2lkIGNhbGwoV2luZG93TWF4aW1pc2UpLFxyXG5cclxuICAgICAgICAvKipcclxuICAgICAgICAgKiBTaG93IHRoZSB3aW5kb3dcclxuICAgICAgICAgKi9cclxuICAgICAgICBTaG93OiAoKSA9PiB2b2lkIGNhbGwoV2luZG93U2hvdyksXHJcblxyXG4gICAgICAgIC8qKlxyXG4gICAgICAgICAqIENsb3NlIHRoZSB3aW5kb3dcclxuICAgICAgICAgKi9cclxuICAgICAgICBDbG9zZTogKCkgPT4gdm9pZCBjYWxsKFdpbmRvd0Nsb3NlKSxcclxuXHJcbiAgICAgICAgLyoqXHJcbiAgICAgICAgICogVG9nZ2xlIHRoZSB3aW5kb3cgbWF4aW1pc2Ugc3RhdGVcclxuICAgICAgICAgKi9cclxuICAgICAgICBUb2dnbGVNYXhpbWlzZTogKCkgPT4gdm9pZCBjYWxsKFdpbmRvd1RvZ2dsZU1heGltaXNlKSxcclxuXHJcbiAgICAgICAgLyoqXHJcbiAgICAgICAgICogVW5tYXhpbWlzZSB0aGUgd2luZG93XHJcbiAgICAgICAgICovXHJcbiAgICAgICAgVW5NYXhpbWlzZTogKCkgPT4gdm9pZCBjYWxsKFdpbmRvd1VuTWF4aW1pc2UpLFxyXG5cclxuICAgICAgICAvKipcclxuICAgICAgICAgKiBNaW5pbWlzZSB0aGUgd2luZG93XHJcbiAgICAgICAgICovXHJcbiAgICAgICAgTWluaW1pc2U6ICgpID0+IHZvaWQgY2FsbChXaW5kb3dNaW5pbWlzZSksXHJcblxyXG4gICAgICAgIC8qKlxyXG4gICAgICAgICAqIFVubWluaW1pc2UgdGhlIHdpbmRvd1xyXG4gICAgICAgICAqL1xyXG4gICAgICAgIFVuTWluaW1pc2U6ICgpID0+IHZvaWQgY2FsbChXaW5kb3dVbk1pbmltaXNlKSxcclxuXHJcbiAgICAgICAgLyoqXHJcbiAgICAgICAgICogUmVzdG9yZSB0aGUgd2luZG93XHJcbiAgICAgICAgICovXHJcbiAgICAgICAgUmVzdG9yZTogKCkgPT4gdm9pZCBjYWxsKFdpbmRvd1Jlc3RvcmUpLFxyXG5cclxuICAgICAgICAvKipcclxuICAgICAgICAgKiBTZXQgdGhlIGJhY2tncm91bmQgY29sb3VyIG9mIHRoZSB3aW5kb3cuXHJcbiAgICAgICAgICogQHBhcmFtIHtudW1iZXJ9IHIgLSBBIHZhbHVlIGJldHdlZW4gMCBhbmQgMjU1XHJcbiAgICAgICAgICogQHBhcmFtIHtudW1iZXJ9IGcgLSBBIHZhbHVlIGJldHdlZW4gMCBhbmQgMjU1XHJcbiAgICAgICAgICogQHBhcmFtIHtudW1iZXJ9IGIgLSBBIHZhbHVlIGJldHdlZW4gMCBhbmQgMjU1XHJcbiAgICAgICAgICogQHBhcmFtIHtudW1iZXJ9IGEgLSBBIHZhbHVlIGJldHdlZW4gMCBhbmQgMjU1XHJcbiAgICAgICAgICovXHJcbiAgICAgICAgU2V0QmFja2dyb3VuZENvbG91cjogKHIsIGcsIGIsIGEpID0+IHZvaWQgY2FsbChXaW5kb3dTZXRCYWNrZ3JvdW5kQ29sb3VyLCB7ciwgZywgYiwgYX0pLFxyXG5cclxuICAgICAgICAvKipcclxuICAgICAgICAgKiBTZXQgd2hldGhlciB0aGUgd2luZG93IGNhbiBiZSByZXNpemVkIG9yIG5vdFxyXG4gICAgICAgICAqIEBwYXJhbSB7Ym9vbGVhbn0gcmVzaXphYmxlXHJcbiAgICAgICAgICovXHJcbiAgICAgICAgU2V0UmVzaXphYmxlOiAocmVzaXphYmxlKSA9PiB2b2lkIGNhbGwoV2luZG93U2V0UmVzaXphYmxlLCB7cmVzaXphYmxlfSksXHJcblxyXG4gICAgICAgIC8qKlxyXG4gICAgICAgICAqIEdldCB0aGUgd2luZG93IHdpZHRoXHJcbiAgICAgICAgICogQHJldHVybnMge1Byb21pc2U8bnVtYmVyPn1cclxuICAgICAgICAgKi9cclxuICAgICAgICBXaWR0aDogKCkgPT4geyByZXR1cm4gY2FsbChXaW5kb3dXaWR0aCk7IH0sXHJcblxyXG4gICAgICAgIC8qKlxyXG4gICAgICAgICAqIEdldCB0aGUgd2luZG93IGhlaWdodFxyXG4gICAgICAgICAqIEByZXR1cm5zIHtQcm9taXNlPG51bWJlcj59XHJcbiAgICAgICAgICovXHJcbiAgICAgICAgSGVpZ2h0OiAoKSA9PiB7IHJldHVybiBjYWxsKFdpbmRvd0hlaWdodCk7IH0sXHJcblxyXG4gICAgICAgIC8qKlxyXG4gICAgICAgICAqIFpvb20gaW4gdGhlIHdpbmRvd1xyXG4gICAgICAgICAqL1xyXG4gICAgICAgIFpvb21JbjogKCkgPT4gdm9pZCBjYWxsKFdpbmRvd1pvb21JbiksXHJcblxyXG4gICAgICAgIC8qKlxyXG4gICAgICAgICAqIFpvb20gb3V0IHRoZSB3aW5kb3dcclxuICAgICAgICAgKi9cclxuICAgICAgICBab29tT3V0OiAoKSA9PiB2b2lkIGNhbGwoV2luZG93Wm9vbU91dCksXHJcblxyXG4gICAgICAgIC8qKlxyXG4gICAgICAgICAqIFJlc2V0IHRoZSB3aW5kb3cgem9vbVxyXG4gICAgICAgICAqL1xyXG4gICAgICAgIFpvb21SZXNldDogKCkgPT4gdm9pZCBjYWxsKFdpbmRvd1pvb21SZXNldCksXHJcblxyXG4gICAgICAgIC8qKlxyXG4gICAgICAgICAqIEdldCB0aGUgd2luZG93IHpvb21cclxuICAgICAgICAgKiBAcmV0dXJucyB7UHJvbWlzZTxudW1iZXI+fVxyXG4gICAgICAgICAqL1xyXG4gICAgICAgIEdldFpvb21MZXZlbDogKCkgPT4geyByZXR1cm4gY2FsbChXaW5kb3dHZXRab29tTGV2ZWwpOyB9LFxyXG5cclxuICAgICAgICAvKipcclxuICAgICAgICAgKiBTZXQgdGhlIHdpbmRvdyB6b29tIGxldmVsXHJcbiAgICAgICAgICogQHBhcmFtIHtudW1iZXJ9IHpvb21MZXZlbFxyXG4gICAgICAgICAqL1xyXG4gICAgICAgIFNldFpvb21MZXZlbDogKHpvb21MZXZlbCkgPT4gdm9pZCBjYWxsKFdpbmRvd1NldFpvb21MZXZlbCwge3pvb21MZXZlbH0pLFxyXG4gICAgfTtcclxufVxyXG4iLCAiLypcclxuIF9cdCAgIF9fXHQgIF8gX19cclxufCB8XHQgLyAvX19fIF8oXykgL19fX19cclxufCB8IC98IC8gLyBfXyBgLyAvIC8gX19fL1xyXG58IHwvIHwvIC8gL18vIC8gLyAoX18gIClcclxufF9fL3xfXy9cXF9fLF8vXy9fL19fX18vXHJcblRoZSBlbGVjdHJvbiBhbHRlcm5hdGl2ZSBmb3IgR29cclxuKGMpIExlYSBBbnRob255IDIwMTktcHJlc2VudFxyXG4qL1xyXG5cclxuLyoganNoaW50IGVzdmVyc2lvbjogOSAqL1xyXG5cclxuLyoqXHJcbiAqIEB0eXBlZGVmIHtpbXBvcnQoXCIuL2FwaS90eXBlc1wiKS5XYWlsc0V2ZW50fSBXYWlsc0V2ZW50XHJcbiAqL1xyXG5cclxuaW1wb3J0IHtuZXdSdW50aW1lQ2FsbGVyV2l0aElELCBvYmplY3ROYW1lc30gZnJvbSBcIi4vcnVudGltZVwiO1xyXG5cclxubGV0IGNhbGwgPSBuZXdSdW50aW1lQ2FsbGVyV2l0aElEKG9iamVjdE5hbWVzLkV2ZW50cyk7XHJcbmxldCBFdmVudEVtaXQgPSAwO1xyXG5cclxuLyoqXHJcbiAqIFRoZSBMaXN0ZW5lciBjbGFzcyBkZWZpbmVzIGEgbGlzdGVuZXIhIDotKVxyXG4gKlxyXG4gKiBAY2xhc3MgTGlzdGVuZXJcclxuICovXHJcbmNsYXNzIExpc3RlbmVyIHtcclxuICAgIC8qKlxyXG4gICAgICogQ3JlYXRlcyBhbiBpbnN0YW5jZSBvZiBMaXN0ZW5lci5cclxuICAgICAqIEBwYXJhbSB7c3RyaW5nfSBldmVudE5hbWVcclxuICAgICAqIEBwYXJhbSB7ZnVuY3Rpb259IGNhbGxiYWNrXHJcbiAgICAgKiBAcGFyYW0ge251bWJlcn0gbWF4Q2FsbGJhY2tzXHJcbiAgICAgKiBAbWVtYmVyb2YgTGlzdGVuZXJcclxuICAgICAqL1xyXG4gICAgY29uc3RydWN0b3IoZXZlbnROYW1lLCBjYWxsYmFjaywgbWF4Q2FsbGJhY2tzKSB7XHJcbiAgICAgICAgdGhpcy5ldmVudE5hbWUgPSBldmVudE5hbWU7XHJcbiAgICAgICAgLy8gRGVmYXVsdCBvZiAtMSBtZWFucyBpbmZpbml0ZVxyXG4gICAgICAgIHRoaXMubWF4Q2FsbGJhY2tzID0gbWF4Q2FsbGJhY2tzIHx8IC0xO1xyXG4gICAgICAgIC8vIENhbGxiYWNrIGludm9rZXMgdGhlIGNhbGxiYWNrIHdpdGggdGhlIGdpdmVuIGRhdGFcclxuICAgICAgICAvLyBSZXR1cm5zIHRydWUgaWYgdGhpcyBsaXN0ZW5lciBzaG91bGQgYmUgZGVzdHJveWVkXHJcbiAgICAgICAgdGhpcy5DYWxsYmFjayA9IChkYXRhKSA9PiB7XHJcbiAgICAgICAgICAgIGNhbGxiYWNrKGRhdGEpO1xyXG4gICAgICAgICAgICAvLyBJZiBtYXhDYWxsYmFja3MgaXMgaW5maW5pdGUsIHJldHVybiBmYWxzZSAoZG8gbm90IGRlc3Ryb3kpXHJcbiAgICAgICAgICAgIGlmICh0aGlzLm1heENhbGxiYWNrcyA9PT0gLTEpIHtcclxuICAgICAgICAgICAgICAgIHJldHVybiBmYWxzZTtcclxuICAgICAgICAgICAgfVxyXG4gICAgICAgICAgICAvLyBEZWNyZW1lbnQgbWF4Q2FsbGJhY2tzLiBSZXR1cm4gdHJ1ZSBpZiBub3cgMCwgb3RoZXJ3aXNlIGZhbHNlXHJcbiAgICAgICAgICAgIHRoaXMubWF4Q2FsbGJhY2tzIC09IDE7XHJcbiAgICAgICAgICAgIHJldHVybiB0aGlzLm1heENhbGxiYWNrcyA9PT0gMDtcclxuICAgICAgICB9O1xyXG4gICAgfVxyXG59XHJcblxyXG5cclxuLyoqXHJcbiAqIFdhaWxzRXZlbnQgZGVmaW5lcyBhIGN1c3RvbSBldmVudC4gSXQgaXMgcGFzc2VkIHRvIGV2ZW50IGxpc3RlbmVycy5cclxuICpcclxuICogQGNsYXNzIFdhaWxzRXZlbnRcclxuICogQHByb3BlcnR5IHtzdHJpbmd9IG5hbWUgLSBOYW1lIG9mIHRoZSBldmVudFxyXG4gKiBAcHJvcGVydHkge2FueX0gZGF0YSAtIERhdGEgYXNzb2NpYXRlZCB3aXRoIHRoZSBldmVudFxyXG4gKi9cclxuZXhwb3J0IGNsYXNzIFdhaWxzRXZlbnQge1xyXG4gICAgLyoqXHJcbiAgICAgKiBDcmVhdGVzIGFuIGluc3RhbmNlIG9mIFdhaWxzRXZlbnQuXHJcbiAgICAgKiBAcGFyYW0ge3N0cmluZ30gbmFtZSAtIE5hbWUgb2YgdGhlIGV2ZW50XHJcbiAgICAgKiBAcGFyYW0ge2FueT1udWxsfSBkYXRhIC0gRGF0YSBhc3NvY2lhdGVkIHdpdGggdGhlIGV2ZW50XHJcbiAgICAgKiBAbWVtYmVyb2YgV2FpbHNFdmVudFxyXG4gICAgICovXHJcbiAgICBjb25zdHJ1Y3RvcihuYW1lLCBkYXRhID0gbnVsbCkge1xyXG4gICAgICAgIHRoaXMubmFtZSA9IG5hbWU7XHJcbiAgICAgICAgdGhpcy5kYXRhID0gZGF0YTtcclxuICAgIH1cclxufVxyXG5cclxuZXhwb3J0IGNvbnN0IGV2ZW50TGlzdGVuZXJzID0gbmV3IE1hcCgpO1xyXG5cclxuLyoqXHJcbiAqIFJlZ2lzdGVycyBhbiBldmVudCBsaXN0ZW5lciB0aGF0IHdpbGwgYmUgaW52b2tlZCBgbWF4Q2FsbGJhY2tzYCB0aW1lcyBiZWZvcmUgYmVpbmcgZGVzdHJveWVkXHJcbiAqXHJcbiAqIEBleHBvcnRcclxuICogQHBhcmFtIHtzdHJpbmd9IGV2ZW50TmFtZVxyXG4gKiBAcGFyYW0ge2Z1bmN0aW9uKFdhaWxzRXZlbnQpOiB2b2lkfSBjYWxsYmFja1xyXG4gKiBAcGFyYW0ge251bWJlcn0gbWF4Q2FsbGJhY2tzXHJcbiAqIEByZXR1cm5zIHtmdW5jdGlvbn0gQSBmdW5jdGlvbiB0byBjYW5jZWwgdGhlIGxpc3RlbmVyXHJcbiAqL1xyXG5leHBvcnQgZnVuY3Rpb24gT25NdWx0aXBsZShldmVudE5hbWUsIGNhbGxiYWNrLCBtYXhDYWxsYmFja3MpIHtcclxuICAgIGxldCBsaXN0ZW5lcnMgPSBldmVudExpc3RlbmVycy5nZXQoZXZlbnROYW1lKSB8fCBbXTtcclxuICAgIGNvbnN0IHRoaXNMaXN0ZW5lciA9IG5ldyBMaXN0ZW5lcihldmVudE5hbWUsIGNhbGxiYWNrLCBtYXhDYWxsYmFja3MpO1xyXG4gICAgbGlzdGVuZXJzLnB1c2godGhpc0xpc3RlbmVyKTtcclxuICAgIGV2ZW50TGlzdGVuZXJzLnNldChldmVudE5hbWUsIGxpc3RlbmVycyk7XHJcbiAgICByZXR1cm4gKCkgPT4gbGlzdGVuZXJPZmYodGhpc0xpc3RlbmVyKTtcclxufVxyXG5cclxuLyoqXHJcbiAqIFJlZ2lzdGVycyBhbiBldmVudCBsaXN0ZW5lciB0aGF0IHdpbGwgYmUgaW52b2tlZCBldmVyeSB0aW1lIHRoZSBldmVudCBpcyBlbWl0dGVkXHJcbiAqXHJcbiAqIEBleHBvcnRcclxuICogQHBhcmFtIHtzdHJpbmd9IGV2ZW50TmFtZVxyXG4gKiBAcGFyYW0ge2Z1bmN0aW9uKFdhaWxzRXZlbnQpOiB2b2lkfSBjYWxsYmFja1xyXG4gKiBAcmV0dXJucyB7ZnVuY3Rpb259IEEgZnVuY3Rpb24gdG8gY2FuY2VsIHRoZSBsaXN0ZW5lclxyXG4gKi9cclxuZXhwb3J0IGZ1bmN0aW9uIE9uKGV2ZW50TmFtZSwgY2FsbGJhY2spIHtcclxuICAgIHJldHVybiBPbk11bHRpcGxlKGV2ZW50TmFtZSwgY2FsbGJhY2ssIC0xKTtcclxufVxyXG5cclxuLyoqXHJcbiAqIFJlZ2lzdGVycyBhbiBldmVudCBsaXN0ZW5lciB0aGF0IHdpbGwgYmUgaW52b2tlZCBvbmNlIHRoZW4gZGVzdHJveWVkXHJcbiAqXHJcbiAqIEBleHBvcnRcclxuICogQHBhcmFtIHtzdHJpbmd9IGV2ZW50TmFtZVxyXG4gKiBAcGFyYW0ge2Z1bmN0aW9uKFdhaWxzRXZlbnQpOiB2b2lkfSBjYWxsYmFja1xyXG4gQHJldHVybnMge2Z1bmN0aW9ufSBBIGZ1bmN0aW9uIHRvIGNhbmNlbCB0aGUgbGlzdGVuZXJcclxuICovXHJcbmV4cG9ydCBmdW5jdGlvbiBPbmNlKGV2ZW50TmFtZSwgY2FsbGJhY2spIHtcclxuICAgIHJldHVybiBPbk11bHRpcGxlKGV2ZW50TmFtZSwgY2FsbGJhY2ssIDEpO1xyXG59XHJcblxyXG4vKipcclxuICogbGlzdGVuZXJPZmYgdW5yZWdpc3RlcnMgYSBsaXN0ZW5lciBwcmV2aW91c2x5IHJlZ2lzdGVyZWQgd2l0aCBPblxyXG4gKlxyXG4gKiBAcGFyYW0ge0xpc3RlbmVyfSBsaXN0ZW5lclxyXG4gKi9cclxuZnVuY3Rpb24gbGlzdGVuZXJPZmYobGlzdGVuZXIpIHtcclxuICAgIGNvbnN0IGV2ZW50TmFtZSA9IGxpc3RlbmVyLmV2ZW50TmFtZTtcclxuICAgIC8vIFJlbW92ZSBsb2NhbCBsaXN0ZW5lclxyXG4gICAgbGV0IGxpc3RlbmVycyA9IGV2ZW50TGlzdGVuZXJzLmdldChldmVudE5hbWUpLmZpbHRlcihsID0+IGwgIT09IGxpc3RlbmVyKTtcclxuICAgIGlmIChsaXN0ZW5lcnMubGVuZ3RoID09PSAwKSB7XHJcbiAgICAgICAgZXZlbnRMaXN0ZW5lcnMuZGVsZXRlKGV2ZW50TmFtZSk7XHJcbiAgICB9IGVsc2Uge1xyXG4gICAgICAgIGV2ZW50TGlzdGVuZXJzLnNldChldmVudE5hbWUsIGxpc3RlbmVycyk7XHJcbiAgICB9XHJcbn1cclxuXHJcbi8qKlxyXG4gKiBkaXNwYXRjaGVzIGFuIGV2ZW50IHRvIGFsbCBsaXN0ZW5lcnNcclxuICpcclxuICogQGV4cG9ydFxyXG4gKiBAcGFyYW0ge1dhaWxzRXZlbnR9IGV2ZW50XHJcbiAqL1xyXG5leHBvcnQgZnVuY3Rpb24gZGlzcGF0Y2hXYWlsc0V2ZW50KGV2ZW50KSB7XHJcbiAgICBsZXQgbGlzdGVuZXJzID0gZXZlbnRMaXN0ZW5lcnMuZ2V0KGV2ZW50Lm5hbWUpO1xyXG4gICAgaWYgKGxpc3RlbmVycykge1xyXG4gICAgICAgIC8vIGl0ZXJhdGUgbGlzdGVuZXJzIGFuZCBjYWxsIGNhbGxiYWNrLiBJZiBjYWxsYmFjayByZXR1cm5zIHRydWUsIHJlbW92ZSBsaXN0ZW5lclxyXG4gICAgICAgIGxldCB0b1JlbW92ZSA9IFtdO1xyXG4gICAgICAgIGxpc3RlbmVycy5mb3JFYWNoKGxpc3RlbmVyID0+IHtcclxuICAgICAgICAgICAgbGV0IHJlbW92ZSA9IGxpc3RlbmVyLkNhbGxiYWNrKGV2ZW50KTtcclxuICAgICAgICAgICAgaWYgKHJlbW92ZSkge1xyXG4gICAgICAgICAgICAgICAgdG9SZW1vdmUucHVzaChsaXN0ZW5lcik7XHJcbiAgICAgICAgICAgIH1cclxuICAgICAgICB9KTtcclxuICAgICAgICAvLyByZW1vdmUgbGlzdGVuZXJzXHJcbiAgICAgICAgaWYgKHRvUmVtb3ZlLmxlbmd0aCA+IDApIHtcclxuICAgICAgICAgICAgbGlzdGVuZXJzID0gbGlzdGVuZXJzLmZpbHRlcihsID0+ICF0b1JlbW92ZS5pbmNsdWRlcyhsKSk7XHJcbiAgICAgICAgICAgIGlmIChsaXN0ZW5lcnMubGVuZ3RoID09PSAwKSB7XHJcbiAgICAgICAgICAgICAgICBldmVudExpc3RlbmVycy5kZWxldGUoZXZlbnQubmFtZSk7XHJcbiAgICAgICAgICAgIH0gZWxzZSB7XHJcbiAgICAgICAgICAgICAgICBldmVudExpc3RlbmVycy5zZXQoZXZlbnQubmFtZSwgbGlzdGVuZXJzKTtcclxuICAgICAgICAgICAgfVxyXG4gICAgICAgIH1cclxuICAgIH1cclxufVxyXG5cclxuLyoqXHJcbiAqIE9mZiB1bnJlZ2lzdGVycyBhIGxpc3RlbmVyIHByZXZpb3VzbHkgcmVnaXN0ZXJlZCB3aXRoIE9uLFxyXG4gKiBvcHRpb25hbGx5IG11bHRpcGxlIGxpc3RlbmVycyBjYW4gYmUgdW5yZWdpc3RlcmVkIHZpYSBgYWRkaXRpb25hbEV2ZW50TmFtZXNgXHJcbiAqXHJcbiBbdjMgQ0hBTkdFXSBPZmYgb25seSB1bnJlZ2lzdGVycyBsaXN0ZW5lcnMgd2l0aGluIHRoZSBjdXJyZW50IHdpbmRvd1xyXG4gKlxyXG4gKiBAcGFyYW0ge3N0cmluZ30gZXZlbnROYW1lXHJcbiAqIEBwYXJhbSAgey4uLnN0cmluZ30gYWRkaXRpb25hbEV2ZW50TmFtZXNcclxuICovXHJcbmV4cG9ydCBmdW5jdGlvbiBPZmYoZXZlbnROYW1lLCAuLi5hZGRpdGlvbmFsRXZlbnROYW1lcykge1xyXG4gICAgbGV0IGV2ZW50c1RvUmVtb3ZlID0gW2V2ZW50TmFtZSwgLi4uYWRkaXRpb25hbEV2ZW50TmFtZXNdO1xyXG4gICAgZXZlbnRzVG9SZW1vdmUuZm9yRWFjaChldmVudE5hbWUgPT4ge1xyXG4gICAgICAgIGV2ZW50TGlzdGVuZXJzLmRlbGV0ZShldmVudE5hbWUpO1xyXG4gICAgfSk7XHJcbn1cclxuXHJcbi8qKlxyXG4gKiBPZmZBbGwgdW5yZWdpc3RlcnMgYWxsIGxpc3RlbmVyc1xyXG4gKiBbdjMgQ0hBTkdFXSBPZmZBbGwgb25seSB1bnJlZ2lzdGVycyBsaXN0ZW5lcnMgd2l0aGluIHRoZSBjdXJyZW50IHdpbmRvd1xyXG4gKlxyXG4gKi9cclxuZXhwb3J0IGZ1bmN0aW9uIE9mZkFsbCgpIHtcclxuICAgIGV2ZW50TGlzdGVuZXJzLmNsZWFyKCk7XHJcbn1cclxuXHJcbi8qKlxyXG4gKiBFbWl0IGFuIGV2ZW50XHJcbiAqIEBwYXJhbSB7V2FpbHNFdmVudH0gZXZlbnQgVGhlIGV2ZW50IHRvIGVtaXRcclxuICovXHJcbmV4cG9ydCBmdW5jdGlvbiBFbWl0KGV2ZW50KSB7XHJcbiAgICB2b2lkIGNhbGwoRXZlbnRFbWl0LCBldmVudCk7XHJcbn0iLCAiLypcclxuIF9cdCAgIF9fXHQgIF8gX19cclxufCB8XHQgLyAvX19fIF8oXykgL19fX19cclxufCB8IC98IC8gLyBfXyBgLyAvIC8gX19fL1xyXG58IHwvIHwvIC8gL18vIC8gLyAoX18gIClcclxufF9fL3xfXy9cXF9fLF8vXy9fL19fX18vXHJcblRoZSBlbGVjdHJvbiBhbHRlcm5hdGl2ZSBmb3IgR29cclxuKGMpIExlYSBBbnRob255IDIwMTktcHJlc2VudFxyXG4qL1xyXG5cclxuLyoganNoaW50IGVzdmVyc2lvbjogOSAqL1xyXG5cclxuLyoqXHJcbiAqIEB0eXBlZGVmIHtpbXBvcnQoXCIuL2FwaS90eXBlc1wiKS5NZXNzYWdlRGlhbG9nT3B0aW9uc30gTWVzc2FnZURpYWxvZ09wdGlvbnNcclxuICogQHR5cGVkZWYge2ltcG9ydChcIi4vYXBpL3R5cGVzXCIpLk9wZW5EaWFsb2dPcHRpb25zfSBPcGVuRGlhbG9nT3B0aW9uc1xyXG4gKiBAdHlwZWRlZiB7aW1wb3J0KFwiLi9hcGkvdHlwZXNcIikuU2F2ZURpYWxvZ09wdGlvbnN9IFNhdmVEaWFsb2dPcHRpb25zXHJcbiAqL1xyXG5cclxuaW1wb3J0IHtuZXdSdW50aW1lQ2FsbGVyV2l0aElELCBvYmplY3ROYW1lc30gZnJvbSBcIi4vcnVudGltZVwiO1xyXG5cclxuaW1wb3J0IHsgbmFub2lkIH0gZnJvbSAnbmFub2lkL25vbi1zZWN1cmUnO1xyXG5cclxubGV0IGNhbGwgPSBuZXdSdW50aW1lQ2FsbGVyV2l0aElEKG9iamVjdE5hbWVzLkRpYWxvZyk7XHJcblxyXG5sZXQgRGlhbG9nSW5mbyA9IDA7XHJcbmxldCBEaWFsb2dXYXJuaW5nID0gMTtcclxubGV0IERpYWxvZ0Vycm9yID0gMjtcclxubGV0IERpYWxvZ1F1ZXN0aW9uID0gMztcclxubGV0IERpYWxvZ09wZW5GaWxlID0gNDtcclxubGV0IERpYWxvZ1NhdmVGaWxlID0gNTtcclxuXHJcblxyXG5sZXQgZGlhbG9nUmVzcG9uc2VzID0gbmV3IE1hcCgpO1xyXG5cclxuZnVuY3Rpb24gZ2VuZXJhdGVJRCgpIHtcclxuICAgIGxldCByZXN1bHQ7XHJcbiAgICBkbyB7XHJcbiAgICAgICAgcmVzdWx0ID0gbmFub2lkKCk7XHJcbiAgICB9IHdoaWxlIChkaWFsb2dSZXNwb25zZXMuaGFzKHJlc3VsdCkpO1xyXG4gICAgcmV0dXJuIHJlc3VsdDtcclxufVxyXG5cclxuZXhwb3J0IGZ1bmN0aW9uIGRpYWxvZ0NhbGxiYWNrKGlkLCBkYXRhLCBpc0pTT04pIHtcclxuICAgIGxldCBwID0gZGlhbG9nUmVzcG9uc2VzLmdldChpZCk7XHJcbiAgICBpZiAocCkge1xyXG4gICAgICAgIGlmIChpc0pTT04pIHtcclxuICAgICAgICAgICAgcC5yZXNvbHZlKEpTT04ucGFyc2UoZGF0YSkpO1xyXG4gICAgICAgIH0gZWxzZSB7XHJcbiAgICAgICAgICAgIHAucmVzb2x2ZShkYXRhKTtcclxuICAgICAgICB9XHJcbiAgICAgICAgZGlhbG9nUmVzcG9uc2VzLmRlbGV0ZShpZCk7XHJcbiAgICB9XHJcbn1cclxuZXhwb3J0IGZ1bmN0aW9uIGRpYWxvZ0Vycm9yQ2FsbGJhY2soaWQsIG1lc3NhZ2UpIHtcclxuICAgIGxldCBwID0gZGlhbG9nUmVzcG9uc2VzLmdldChpZCk7XHJcbiAgICBpZiAocCkge1xyXG4gICAgICAgIHAucmVqZWN0KG1lc3NhZ2UpO1xyXG4gICAgICAgIGRpYWxvZ1Jlc3BvbnNlcy5kZWxldGUoaWQpO1xyXG4gICAgfVxyXG59XHJcblxyXG5mdW5jdGlvbiBkaWFsb2codHlwZSwgb3B0aW9ucykge1xyXG4gICAgcmV0dXJuIG5ldyBQcm9taXNlKChyZXNvbHZlLCByZWplY3QpID0+IHtcclxuICAgICAgICBsZXQgaWQgPSBnZW5lcmF0ZUlEKCk7XHJcbiAgICAgICAgb3B0aW9ucyA9IG9wdGlvbnMgfHwge307XHJcbiAgICAgICAgb3B0aW9uc1tcImRpYWxvZy1pZFwiXSA9IGlkO1xyXG4gICAgICAgIGRpYWxvZ1Jlc3BvbnNlcy5zZXQoaWQsIHtyZXNvbHZlLCByZWplY3R9KTtcclxuICAgICAgICBjYWxsKHR5cGUsIG9wdGlvbnMpLmNhdGNoKChlcnJvcikgPT4ge1xyXG4gICAgICAgICAgICByZWplY3QoZXJyb3IpO1xyXG4gICAgICAgICAgICBkaWFsb2dSZXNwb25zZXMuZGVsZXRlKGlkKTtcclxuICAgICAgICB9KTtcclxuICAgIH0pO1xyXG59XHJcblxyXG5cclxuLyoqXHJcbiAqIFNob3dzIGFuIEluZm8gZGlhbG9nIHdpdGggdGhlIGdpdmVuIG9wdGlvbnMuXHJcbiAqIEBwYXJhbSB7TWVzc2FnZURpYWxvZ09wdGlvbnN9IG9wdGlvbnNcclxuICogQHJldHVybnMge1Byb21pc2U8c3RyaW5nPn0gVGhlIGxhYmVsIG9mIHRoZSBidXR0b24gcHJlc3NlZFxyXG4gKi9cclxuZXhwb3J0IGZ1bmN0aW9uIEluZm8ob3B0aW9ucykge1xyXG4gICAgcmV0dXJuIGRpYWxvZyhEaWFsb2dJbmZvLCBvcHRpb25zKTtcclxufVxyXG5cclxuLyoqXHJcbiAqIFNob3dzIGEgV2FybmluZyBkaWFsb2cgd2l0aCB0aGUgZ2l2ZW4gb3B0aW9ucy5cclxuICogQHBhcmFtIHtNZXNzYWdlRGlhbG9nT3B0aW9uc30gb3B0aW9uc1xyXG4gKiBAcmV0dXJucyB7UHJvbWlzZTxzdHJpbmc+fSBUaGUgbGFiZWwgb2YgdGhlIGJ1dHRvbiBwcmVzc2VkXHJcbiAqL1xyXG5leHBvcnQgZnVuY3Rpb24gV2FybmluZyhvcHRpb25zKSB7XHJcbiAgICByZXR1cm4gZGlhbG9nKERpYWxvZ1dhcm5pbmcsIG9wdGlvbnMpO1xyXG59XHJcblxyXG4vKipcclxuICogU2hvd3MgYW4gRXJyb3IgZGlhbG9nIHdpdGggdGhlIGdpdmVuIG9wdGlvbnMuXHJcbiAqIEBwYXJhbSB7TWVzc2FnZURpYWxvZ09wdGlvbnN9IG9wdGlvbnNcclxuICogQHJldHVybnMge1Byb21pc2U8c3RyaW5nPn0gVGhlIGxhYmVsIG9mIHRoZSBidXR0b24gcHJlc3NlZFxyXG4gKi9cclxuZXhwb3J0IGZ1bmN0aW9uIEVycm9yKG9wdGlvbnMpIHtcclxuICAgIHJldHVybiBkaWFsb2coRGlhbG9nRXJyb3IsIG9wdGlvbnMpO1xyXG59XHJcblxyXG4vKipcclxuICogU2hvd3MgYSBRdWVzdGlvbiBkaWFsb2cgd2l0aCB0aGUgZ2l2ZW4gb3B0aW9ucy5cclxuICogQHBhcmFtIHtNZXNzYWdlRGlhbG9nT3B0aW9uc30gb3B0aW9uc1xyXG4gKiBAcmV0dXJucyB7UHJvbWlzZTxzdHJpbmc+fSBUaGUgbGFiZWwgb2YgdGhlIGJ1dHRvbiBwcmVzc2VkXHJcbiAqL1xyXG5leHBvcnQgZnVuY3Rpb24gUXVlc3Rpb24ob3B0aW9ucykge1xyXG4gICAgcmV0dXJuIGRpYWxvZyhEaWFsb2dRdWVzdGlvbiwgb3B0aW9ucyk7XHJcbn1cclxuXHJcbi8qKlxyXG4gKiBTaG93cyBhbiBPcGVuIGRpYWxvZyB3aXRoIHRoZSBnaXZlbiBvcHRpb25zLlxyXG4gKiBAcGFyYW0ge09wZW5EaWFsb2dPcHRpb25zfSBvcHRpb25zXHJcbiAqIEByZXR1cm5zIHtQcm9taXNlPHN0cmluZ1tdfHN0cmluZz59IFJldHVybnMgdGhlIHNlbGVjdGVkIGZpbGUgb3IgYW4gYXJyYXkgb2Ygc2VsZWN0ZWQgZmlsZXMgaWYgQWxsb3dzTXVsdGlwbGVTZWxlY3Rpb24gaXMgdHJ1ZS4gQSBibGFuayBzdHJpbmcgaXMgcmV0dXJuZWQgaWYgbm8gZmlsZSB3YXMgc2VsZWN0ZWQuXHJcbiAqL1xyXG5leHBvcnQgZnVuY3Rpb24gT3BlbkZpbGUob3B0aW9ucykge1xyXG4gICAgcmV0dXJuIGRpYWxvZyhEaWFsb2dPcGVuRmlsZSwgb3B0aW9ucyk7XHJcbn1cclxuXHJcbi8qKlxyXG4gKiBTaG93cyBhIFNhdmUgZGlhbG9nIHdpdGggdGhlIGdpdmVuIG9wdGlvbnMuXHJcbiAqIEBwYXJhbSB7U2F2ZURpYWxvZ09wdGlvbnN9IG9wdGlvbnNcclxuICogQHJldHVybnMge1Byb21pc2U8c3RyaW5nPn0gUmV0dXJucyB0aGUgc2VsZWN0ZWQgZmlsZS4gQSBibGFuayBzdHJpbmcgaXMgcmV0dXJuZWQgaWYgbm8gZmlsZSB3YXMgc2VsZWN0ZWQuXHJcbiAqL1xyXG5leHBvcnQgZnVuY3Rpb24gU2F2ZUZpbGUob3B0aW9ucykge1xyXG4gICAgcmV0dXJuIGRpYWxvZyhEaWFsb2dTYXZlRmlsZSwgb3B0aW9ucyk7XHJcbn1cclxuXHJcbiIsICJpbXBvcnQge25ld1J1bnRpbWVDYWxsZXJXaXRoSUQsIG9iamVjdE5hbWVzfSBmcm9tIFwiLi9ydW50aW1lXCI7XHJcblxyXG5sZXQgY2FsbCA9IG5ld1J1bnRpbWVDYWxsZXJXaXRoSUQob2JqZWN0TmFtZXMuQ29udGV4dE1lbnUpO1xyXG5cclxubGV0IENvbnRleHRNZW51T3BlbiA9IDA7XHJcblxyXG5mdW5jdGlvbiBvcGVuQ29udGV4dE1lbnUoaWQsIHgsIHksIGRhdGEpIHtcclxuICAgIHZvaWQgY2FsbChDb250ZXh0TWVudU9wZW4sIHtpZCwgeCwgeSwgZGF0YX0pO1xyXG59XHJcblxyXG5leHBvcnQgZnVuY3Rpb24gc2V0dXBDb250ZXh0TWVudXMoKSB7XHJcbiAgICB3aW5kb3cuYWRkRXZlbnRMaXN0ZW5lcignY29udGV4dG1lbnUnLCBjb250ZXh0TWVudUhhbmRsZXIpO1xyXG59XHJcblxyXG5mdW5jdGlvbiBjb250ZXh0TWVudUhhbmRsZXIoZXZlbnQpIHtcclxuICAgIC8vIENoZWNrIGZvciBjdXN0b20gY29udGV4dCBtZW51XHJcbiAgICBsZXQgZWxlbWVudCA9IGV2ZW50LnRhcmdldDtcclxuICAgIGxldCBjdXN0b21Db250ZXh0TWVudSA9IHdpbmRvdy5nZXRDb21wdXRlZFN0eWxlKGVsZW1lbnQpLmdldFByb3BlcnR5VmFsdWUoXCItLWN1c3RvbS1jb250ZXh0bWVudVwiKTtcclxuICAgIGN1c3RvbUNvbnRleHRNZW51ID0gY3VzdG9tQ29udGV4dE1lbnUgPyBjdXN0b21Db250ZXh0TWVudS50cmltKCkgOiBcIlwiO1xyXG4gICAgaWYgKGN1c3RvbUNvbnRleHRNZW51KSB7XHJcbiAgICAgICAgZXZlbnQucHJldmVudERlZmF1bHQoKTtcclxuICAgICAgICBsZXQgY3VzdG9tQ29udGV4dE1lbnVEYXRhID0gd2luZG93LmdldENvbXB1dGVkU3R5bGUoZWxlbWVudCkuZ2V0UHJvcGVydHlWYWx1ZShcIi0tY3VzdG9tLWNvbnRleHRtZW51LWRhdGFcIik7XHJcbiAgICAgICAgb3BlbkNvbnRleHRNZW51KGN1c3RvbUNvbnRleHRNZW51LCBldmVudC5jbGllbnRYLCBldmVudC5jbGllbnRZLCBjdXN0b21Db250ZXh0TWVudURhdGEpO1xyXG4gICAgICAgIHJldHVyblxyXG4gICAgfVxyXG5cclxuICAgIHByb2Nlc3NEZWZhdWx0Q29udGV4dE1lbnUoZXZlbnQpO1xyXG59XHJcblxyXG5cclxuLypcclxuLS1kZWZhdWx0LWNvbnRleHRtZW51OiBhdXRvOyAoZGVmYXVsdCkgd2lsbCBzaG93IHRoZSBkZWZhdWx0IGNvbnRleHQgbWVudSBpZiBjb250ZW50RWRpdGFibGUgaXMgdHJ1ZSBPUiB0ZXh0IGhhcyBiZWVuIHNlbGVjdGVkIE9SIGVsZW1lbnQgaXMgaW5wdXQgb3IgdGV4dGFyZWFcclxuLS1kZWZhdWx0LWNvbnRleHRtZW51OiBzaG93OyB3aWxsIGFsd2F5cyBzaG93IHRoZSBkZWZhdWx0IGNvbnRleHQgbWVudVxyXG4tLWRlZmF1bHQtY29udGV4dG1lbnU6IGhpZGU7IHdpbGwgYWx3YXlzIGhpZGUgdGhlIGRlZmF1bHQgY29udGV4dCBtZW51XHJcblxyXG5UaGlzIHJ1bGUgaXMgaW5oZXJpdGVkIGxpa2Ugbm9ybWFsIENTUyBydWxlcywgc28gbmVzdGluZyB3b3JrcyBhcyBleHBlY3RlZFxyXG4qL1xyXG5mdW5jdGlvbiBwcm9jZXNzRGVmYXVsdENvbnRleHRNZW51KGV2ZW50KSB7XHJcbiAgICAvLyBEZWJ1ZyBidWlsZHMgYWx3YXlzIHNob3cgdGhlIG1lbnVcclxuICAgIGlmIChERUJVRykge1xyXG4gICAgICAgIHJldHVybjtcclxuICAgIH1cclxuXHJcbiAgICAvLyBQcm9jZXNzIGRlZmF1bHQgY29udGV4dCBtZW51XHJcbiAgICBjb25zdCBlbGVtZW50ID0gZXZlbnQudGFyZ2V0O1xyXG4gICAgY29uc3QgY29tcHV0ZWRTdHlsZSA9IHdpbmRvdy5nZXRDb21wdXRlZFN0eWxlKGVsZW1lbnQpO1xyXG4gICAgY29uc3QgZGVmYXVsdENvbnRleHRNZW51QWN0aW9uID0gY29tcHV0ZWRTdHlsZS5nZXRQcm9wZXJ0eVZhbHVlKFwiLS1kZWZhdWx0LWNvbnRleHRtZW51XCIpLnRyaW0oKTtcclxuICAgIHN3aXRjaCAoZGVmYXVsdENvbnRleHRNZW51QWN0aW9uKSB7XHJcbiAgICAgICAgY2FzZSBcInNob3dcIjpcclxuICAgICAgICAgICAgcmV0dXJuO1xyXG4gICAgICAgIGNhc2UgXCJoaWRlXCI6XHJcbiAgICAgICAgICAgIGV2ZW50LnByZXZlbnREZWZhdWx0KCk7XHJcbiAgICAgICAgICAgIHJldHVybjtcclxuICAgICAgICBkZWZhdWx0OlxyXG4gICAgICAgICAgICAvLyBDaGVjayBpZiBjb250ZW50RWRpdGFibGUgaXMgdHJ1ZVxyXG4gICAgICAgICAgICBpZiAoZWxlbWVudC5pc0NvbnRlbnRFZGl0YWJsZSkge1xyXG4gICAgICAgICAgICAgICAgcmV0dXJuO1xyXG4gICAgICAgICAgICB9XHJcblxyXG4gICAgICAgICAgICAvLyBDaGVjayBpZiB0ZXh0IGhhcyBiZWVuIHNlbGVjdGVkXHJcbiAgICAgICAgICAgIGNvbnN0IHNlbGVjdGlvbiA9IHdpbmRvdy5nZXRTZWxlY3Rpb24oKTtcclxuICAgICAgICAgICAgY29uc3QgaGFzU2VsZWN0aW9uID0gKHNlbGVjdGlvbi50b1N0cmluZygpLmxlbmd0aCA+IDApXHJcbiAgICAgICAgICAgIGlmIChoYXNTZWxlY3Rpb24pIHtcclxuICAgICAgICAgICAgICAgIGZvciAobGV0IGkgPSAwOyBpIDwgc2VsZWN0aW9uLnJhbmdlQ291bnQ7IGkrKykge1xyXG4gICAgICAgICAgICAgICAgICAgIGNvbnN0IHJhbmdlID0gc2VsZWN0aW9uLmdldFJhbmdlQXQoaSk7XHJcbiAgICAgICAgICAgICAgICAgICAgY29uc3QgcmVjdHMgPSByYW5nZS5nZXRDbGllbnRSZWN0cygpO1xyXG4gICAgICAgICAgICAgICAgICAgIGZvciAobGV0IGogPSAwOyBqIDwgcmVjdHMubGVuZ3RoOyBqKyspIHtcclxuICAgICAgICAgICAgICAgICAgICAgICAgY29uc3QgcmVjdCA9IHJlY3RzW2pdO1xyXG4gICAgICAgICAgICAgICAgICAgICAgICBpZiAoZG9jdW1lbnQuZWxlbWVudEZyb21Qb2ludChyZWN0LmxlZnQsIHJlY3QudG9wKSA9PT0gZWxlbWVudCkge1xyXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgcmV0dXJuO1xyXG4gICAgICAgICAgICAgICAgICAgICAgICB9XHJcbiAgICAgICAgICAgICAgICAgICAgfVxyXG4gICAgICAgICAgICAgICAgfVxyXG4gICAgICAgICAgICB9XHJcbiAgICAgICAgICAgIC8vIENoZWNrIGlmIHRhZ25hbWUgaXMgaW5wdXQgb3IgdGV4dGFyZWFcclxuICAgICAgICAgICAgaWYgKGVsZW1lbnQudGFnTmFtZSA9PT0gXCJJTlBVVFwiIHx8IGVsZW1lbnQudGFnTmFtZSA9PT0gXCJURVhUQVJFQVwiKSB7XHJcbiAgICAgICAgICAgICAgICBpZiAoaGFzU2VsZWN0aW9uIHx8ICghZWxlbWVudC5yZWFkT25seSAmJiAhZWxlbWVudC5kaXNhYmxlZCkpIHtcclxuICAgICAgICAgICAgICAgICAgICByZXR1cm47XHJcbiAgICAgICAgICAgICAgICB9XHJcbiAgICAgICAgICAgIH1cclxuXHJcbiAgICAgICAgICAgIC8vIGhpZGUgZGVmYXVsdCBjb250ZXh0IG1lbnVcclxuICAgICAgICAgICAgZXZlbnQucHJldmVudERlZmF1bHQoKTtcclxuICAgIH1cclxufVxyXG4iLCAiXHJcbmltcG9ydCB7RW1pdCwgV2FpbHNFdmVudH0gZnJvbSBcIi4vZXZlbnRzXCI7XHJcbmltcG9ydCB7UXVlc3Rpb259IGZyb20gXCIuL2RpYWxvZ3NcIjtcclxuXHJcbmZ1bmN0aW9uIHNlbmRFdmVudChldmVudE5hbWUsIGRhdGE9bnVsbCkge1xyXG4gICAgbGV0IGV2ZW50ID0gbmV3IFdhaWxzRXZlbnQoZXZlbnROYW1lLCBkYXRhKTtcclxuICAgIEVtaXQoZXZlbnQpO1xyXG59XHJcblxyXG5mdW5jdGlvbiBhZGRXTUxFdmVudExpc3RlbmVycygpIHtcclxuICAgIGNvbnN0IGVsZW1lbnRzID0gZG9jdW1lbnQucXVlcnlTZWxlY3RvckFsbCgnW3dtbC1ldmVudF0nKTtcclxuICAgIGVsZW1lbnRzLmZvckVhY2goZnVuY3Rpb24gKGVsZW1lbnQpIHtcclxuICAgICAgICBjb25zdCBldmVudFR5cGUgPSBlbGVtZW50LmdldEF0dHJpYnV0ZSgnd21sLWV2ZW50Jyk7XHJcbiAgICAgICAgY29uc3QgY29uZmlybSA9IGVsZW1lbnQuZ2V0QXR0cmlidXRlKCd3bWwtY29uZmlybScpO1xyXG4gICAgICAgIGNvbnN0IHRyaWdnZXIgPSBlbGVtZW50LmdldEF0dHJpYnV0ZSgnd21sLXRyaWdnZXInKSB8fCBcImNsaWNrXCI7XHJcblxyXG4gICAgICAgIGxldCBjYWxsYmFjayA9IGZ1bmN0aW9uICgpIHtcclxuICAgICAgICAgICAgaWYgKGNvbmZpcm0pIHtcclxuICAgICAgICAgICAgICAgIFF1ZXN0aW9uKHtUaXRsZTogXCJDb25maXJtXCIsIE1lc3NhZ2U6Y29uZmlybSwgRGV0YWNoZWQ6IGZhbHNlLCBCdXR0b25zOlt7TGFiZWw6XCJZZXNcIn0se0xhYmVsOlwiTm9cIiwgSXNEZWZhdWx0OnRydWV9XX0pLnRoZW4oZnVuY3Rpb24gKHJlc3VsdCkge1xyXG4gICAgICAgICAgICAgICAgICAgIGlmIChyZXN1bHQgIT09IFwiTm9cIikge1xyXG4gICAgICAgICAgICAgICAgICAgICAgICBzZW5kRXZlbnQoZXZlbnRUeXBlKTtcclxuICAgICAgICAgICAgICAgICAgICB9XHJcbiAgICAgICAgICAgICAgICB9KTtcclxuICAgICAgICAgICAgICAgIHJldHVybjtcclxuICAgICAgICAgICAgfVxyXG4gICAgICAgICAgICBzZW5kRXZlbnQoZXZlbnRUeXBlKTtcclxuICAgICAgICB9O1xyXG4gICAgICAgIC8vIFJlbW92ZSBleGlzdGluZyBsaXN0ZW5lcnNcclxuXHJcbiAgICAgICAgZWxlbWVudC5yZW1vdmVFdmVudExpc3RlbmVyKHRyaWdnZXIsIGNhbGxiYWNrKTtcclxuXHJcbiAgICAgICAgLy8gQWRkIG5ldyBsaXN0ZW5lclxyXG4gICAgICAgIGVsZW1lbnQuYWRkRXZlbnRMaXN0ZW5lcih0cmlnZ2VyLCBjYWxsYmFjayk7XHJcbiAgICB9KTtcclxufVxyXG5cclxuZnVuY3Rpb24gY2FsbFdpbmRvd01ldGhvZChtZXRob2QpIHtcclxuICAgIGlmICh3YWlscy5XaW5kb3dbbWV0aG9kXSA9PT0gdW5kZWZpbmVkKSB7XHJcbiAgICAgICAgY29uc29sZS5sb2coXCJXaW5kb3cgbWV0aG9kIFwiICsgbWV0aG9kICsgXCIgbm90IGZvdW5kXCIpO1xyXG4gICAgfVxyXG4gICAgd2FpbHMuV2luZG93W21ldGhvZF0oKTtcclxufVxyXG5cclxuZnVuY3Rpb24gYWRkV01MV2luZG93TGlzdGVuZXJzKCkge1xyXG4gICAgY29uc3QgZWxlbWVudHMgPSBkb2N1bWVudC5xdWVyeVNlbGVjdG9yQWxsKCdbd21sLXdpbmRvd10nKTtcclxuICAgIGVsZW1lbnRzLmZvckVhY2goZnVuY3Rpb24gKGVsZW1lbnQpIHtcclxuICAgICAgICBjb25zdCB3aW5kb3dNZXRob2QgPSBlbGVtZW50LmdldEF0dHJpYnV0ZSgnd21sLXdpbmRvdycpO1xyXG4gICAgICAgIGNvbnN0IGNvbmZpcm0gPSBlbGVtZW50LmdldEF0dHJpYnV0ZSgnd21sLWNvbmZpcm0nKTtcclxuICAgICAgICBjb25zdCB0cmlnZ2VyID0gZWxlbWVudC5nZXRBdHRyaWJ1dGUoJ3dtbC10cmlnZ2VyJykgfHwgXCJjbGlja1wiO1xyXG5cclxuICAgICAgICBsZXQgY2FsbGJhY2sgPSBmdW5jdGlvbiAoKSB7XHJcbiAgICAgICAgICAgIGlmIChjb25maXJtKSB7XHJcbiAgICAgICAgICAgICAgICBRdWVzdGlvbih7VGl0bGU6IFwiQ29uZmlybVwiLCBNZXNzYWdlOmNvbmZpcm0sIEJ1dHRvbnM6W3tMYWJlbDpcIlllc1wifSx7TGFiZWw6XCJOb1wiLCBJc0RlZmF1bHQ6dHJ1ZX1dfSkudGhlbihmdW5jdGlvbiAocmVzdWx0KSB7XHJcbiAgICAgICAgICAgICAgICAgICAgaWYgKHJlc3VsdCAhPT0gXCJOb1wiKSB7XHJcbiAgICAgICAgICAgICAgICAgICAgICAgIGNhbGxXaW5kb3dNZXRob2Qod2luZG93TWV0aG9kKTtcclxuICAgICAgICAgICAgICAgICAgICB9XHJcbiAgICAgICAgICAgICAgICB9KTtcclxuICAgICAgICAgICAgICAgIHJldHVybjtcclxuICAgICAgICAgICAgfVxyXG4gICAgICAgICAgICBjYWxsV2luZG93TWV0aG9kKHdpbmRvd01ldGhvZCk7XHJcbiAgICAgICAgfTtcclxuXHJcbiAgICAgICAgLy8gUmVtb3ZlIGV4aXN0aW5nIGxpc3RlbmVyc1xyXG4gICAgICAgIGVsZW1lbnQucmVtb3ZlRXZlbnRMaXN0ZW5lcih0cmlnZ2VyLCBjYWxsYmFjayk7XHJcblxyXG4gICAgICAgIC8vIEFkZCBuZXcgbGlzdGVuZXJcclxuICAgICAgICBlbGVtZW50LmFkZEV2ZW50TGlzdGVuZXIodHJpZ2dlciwgY2FsbGJhY2spO1xyXG4gICAgfSk7XHJcbn1cclxuXHJcbmZ1bmN0aW9uIGFkZFdNTE9wZW5Ccm93c2VyTGlzdGVuZXIoKSB7XHJcbiAgICBjb25zdCBlbGVtZW50cyA9IGRvY3VtZW50LnF1ZXJ5U2VsZWN0b3JBbGwoJ1t3bWwtb3BlbnVybF0nKTtcclxuICAgIGVsZW1lbnRzLmZvckVhY2goZnVuY3Rpb24gKGVsZW1lbnQpIHtcclxuICAgICAgICBjb25zdCB1cmwgPSBlbGVtZW50LmdldEF0dHJpYnV0ZSgnd21sLW9wZW51cmwnKTtcclxuICAgICAgICBjb25zdCBjb25maXJtID0gZWxlbWVudC5nZXRBdHRyaWJ1dGUoJ3dtbC1jb25maXJtJyk7XHJcbiAgICAgICAgY29uc3QgdHJpZ2dlciA9IGVsZW1lbnQuZ2V0QXR0cmlidXRlKCd3bWwtdHJpZ2dlcicpIHx8IFwiY2xpY2tcIjtcclxuXHJcbiAgICAgICAgbGV0IGNhbGxiYWNrID0gZnVuY3Rpb24gKCkge1xyXG4gICAgICAgICAgICBpZiAoY29uZmlybSkge1xyXG4gICAgICAgICAgICAgICAgUXVlc3Rpb24oe1RpdGxlOiBcIkNvbmZpcm1cIiwgTWVzc2FnZTpjb25maXJtLCBCdXR0b25zOlt7TGFiZWw6XCJZZXNcIn0se0xhYmVsOlwiTm9cIiwgSXNEZWZhdWx0OnRydWV9XX0pLnRoZW4oZnVuY3Rpb24gKHJlc3VsdCkge1xyXG4gICAgICAgICAgICAgICAgICAgIGlmIChyZXN1bHQgIT09IFwiTm9cIikge1xyXG4gICAgICAgICAgICAgICAgICAgICAgICB2b2lkIHdhaWxzLkJyb3dzZXIuT3BlblVSTCh1cmwpO1xyXG4gICAgICAgICAgICAgICAgICAgIH1cclxuICAgICAgICAgICAgICAgIH0pO1xyXG4gICAgICAgICAgICAgICAgcmV0dXJuO1xyXG4gICAgICAgICAgICB9XHJcbiAgICAgICAgICAgIHZvaWQgd2FpbHMuQnJvd3Nlci5PcGVuVVJMKHVybCk7XHJcbiAgICAgICAgfTtcclxuXHJcbiAgICAgICAgLy8gUmVtb3ZlIGV4aXN0aW5nIGxpc3RlbmVyc1xyXG4gICAgICAgIGVsZW1lbnQucmVtb3ZlRXZlbnRMaXN0ZW5lcih0cmlnZ2VyLCBjYWxsYmFjayk7XHJcblxyXG4gICAgICAgIC8vIEFkZCBuZXcgbGlzdGVuZXJcclxuICAgICAgICBlbGVtZW50LmFkZEV2ZW50TGlzdGVuZXIodHJpZ2dlciwgY2FsbGJhY2spO1xyXG4gICAgfSk7XHJcbn1cclxuXHJcbmV4cG9ydCBmdW5jdGlvbiByZWxvYWRXTUwoKSB7XHJcbiAgICBhZGRXTUxFdmVudExpc3RlbmVycygpO1xyXG4gICAgYWRkV01MV2luZG93TGlzdGVuZXJzKCk7XHJcbiAgICBhZGRXTUxPcGVuQnJvd3Nlckxpc3RlbmVyKCk7XHJcbn1cclxuIiwgIi8qXHJcbiBfXHQgICBfX1x0ICBfIF9fXHJcbnwgfFx0IC8gL19fXyBfKF8pIC9fX19fXHJcbnwgfCAvfCAvIC8gX18gYC8gLyAvIF9fXy9cclxufCB8LyB8LyAvIC9fLyAvIC8gKF9fICApXHJcbnxfXy98X18vXFxfXyxfL18vXy9fX19fL1xyXG5UaGUgZWxlY3Ryb24gYWx0ZXJuYXRpdmUgZm9yIEdvXHJcbihjKSBMZWEgQW50aG9ueSAyMDE5LXByZXNlbnRcclxuKi9cclxuXHJcbi8qIGpzaGludCBlc3ZlcnNpb246IDkgKi9cclxuXHJcbi8vIGRlZmluZWQgaW4gdGhlIFRhc2tmaWxlXHJcbmV4cG9ydCBsZXQgaW52b2tlID0gZnVuY3Rpb24oaW5wdXQpIHtcclxuICAgIGlmKFdJTkRPV1MpIHtcclxuICAgICAgICBjaHJvbWUud2Vidmlldy5wb3N0TWVzc2FnZShpbnB1dCk7XHJcbiAgICB9IGVsc2Uge1xyXG4gICAgICAgIHdlYmtpdC5tZXNzYWdlSGFuZGxlcnMuZXh0ZXJuYWwucG9zdE1lc3NhZ2UoaW5wdXQpO1xyXG4gICAgfVxyXG59XHJcbiIsICIvKlxyXG4gX1x0ICAgX19cdCAgXyBfX1xyXG58IHxcdCAvIC9fX18gXyhfKSAvX19fX1xyXG58IHwgL3wgLyAvIF9fIGAvIC8gLyBfX18vXHJcbnwgfC8gfC8gLyAvXy8gLyAvIChfXyAgKVxyXG58X18vfF9fL1xcX18sXy9fL18vX19fXy9cclxuVGhlIGVsZWN0cm9uIGFsdGVybmF0aXZlIGZvciBHb1xyXG4oYykgTGVhIEFudGhvbnkgMjAxOS1wcmVzZW50XHJcbiovXHJcblxyXG4vKiBqc2hpbnQgZXN2ZXJzaW9uOiA5ICovXHJcblxyXG5sZXQgZmxhZ3MgPSBuZXcgTWFwKCk7XHJcblxyXG5mdW5jdGlvbiBjb252ZXJ0VG9NYXAob2JqKSB7XHJcbiAgICBjb25zdCBtYXAgPSBuZXcgTWFwKCk7XHJcblxyXG4gICAgZm9yIChjb25zdCBba2V5LCB2YWx1ZV0gb2YgT2JqZWN0LmVudHJpZXMob2JqKSkge1xyXG4gICAgICAgIGlmICh0eXBlb2YgdmFsdWUgPT09ICdvYmplY3QnICYmIHZhbHVlICE9PSBudWxsKSB7XHJcbiAgICAgICAgICAgIG1hcC5zZXQoa2V5LCBjb252ZXJ0VG9NYXAodmFsdWUpKTsgLy8gUmVjdXJzaXZlbHkgY29udmVydCBuZXN0ZWQgb2JqZWN0XHJcbiAgICAgICAgfSBlbHNlIHtcclxuICAgICAgICAgICAgbWFwLnNldChrZXksIHZhbHVlKTtcclxuICAgICAgICB9XHJcbiAgICB9XHJcblxyXG4gICAgcmV0dXJuIG1hcDtcclxufVxyXG5cclxuZmV0Y2goXCIvd2FpbHMvZmxhZ3NcIikudGhlbigocmVzcG9uc2UpID0+IHtcclxuICAgIHJlc3BvbnNlLmpzb24oKS50aGVuKChkYXRhKSA9PiB7XHJcbiAgICAgICAgZmxhZ3MgPSBjb252ZXJ0VG9NYXAoZGF0YSk7XHJcbiAgICB9KTtcclxufSk7XHJcblxyXG5cclxuZnVuY3Rpb24gZ2V0VmFsdWVGcm9tTWFwKGtleVN0cmluZykge1xyXG4gICAgY29uc3Qga2V5cyA9IGtleVN0cmluZy5zcGxpdCgnLicpO1xyXG4gICAgbGV0IHZhbHVlID0gZmxhZ3M7XHJcblxyXG4gICAgZm9yIChjb25zdCBrZXkgb2Yga2V5cykge1xyXG4gICAgICAgIGlmICh2YWx1ZSBpbnN0YW5jZW9mIE1hcCkge1xyXG4gICAgICAgICAgICB2YWx1ZSA9IHZhbHVlLmdldChrZXkpO1xyXG4gICAgICAgIH0gZWxzZSB7XHJcbiAgICAgICAgICAgIHZhbHVlID0gdmFsdWVba2V5XTtcclxuICAgICAgICB9XHJcblxyXG4gICAgICAgIGlmICh2YWx1ZSA9PT0gdW5kZWZpbmVkKSB7XHJcbiAgICAgICAgICAgIGJyZWFrO1xyXG4gICAgICAgIH1cclxuICAgIH1cclxuXHJcbiAgICByZXR1cm4gdmFsdWU7XHJcbn1cclxuXHJcbmV4cG9ydCBmdW5jdGlvbiBHZXRGbGFnKGtleVN0cmluZykge1xyXG4gICAgcmV0dXJuIGdldFZhbHVlRnJvbU1hcChrZXlTdHJpbmcpO1xyXG59XHJcbiIsICIvKlxyXG4gX1x0ICAgX19cdCAgXyBfX1xyXG58IHxcdCAvIC9fX18gXyhfKSAvX19fX1xyXG58IHwgL3wgLyAvIF9fIGAvIC8gLyBfX18vXHJcbnwgfC8gfC8gLyAvXy8gLyAvIChfXyAgKVxyXG58X18vfF9fL1xcX18sXy9fL18vX19fXy9cclxuVGhlIGVsZWN0cm9uIGFsdGVybmF0aXZlIGZvciBHb1xyXG4oYykgTGVhIEFudGhvbnkgMjAxOS1wcmVzZW50XHJcbiovXHJcblxyXG4vKiBqc2hpbnQgZXN2ZXJzaW9uOiA5ICovXHJcblxyXG5pbXBvcnQge2ludm9rZX0gZnJvbSBcIi4vaW52b2tlXCI7XHJcbmltcG9ydCB7R2V0RmxhZ30gZnJvbSBcIi4vZmxhZ3NcIjtcclxuXHJcbmxldCBzaG91bGREcmFnID0gZmFsc2U7XHJcblxyXG5leHBvcnQgZnVuY3Rpb24gZHJhZ1Rlc3QoZSkge1xyXG4gICAgbGV0IHZhbCA9IHdpbmRvdy5nZXRDb21wdXRlZFN0eWxlKGUudGFyZ2V0KS5nZXRQcm9wZXJ0eVZhbHVlKFwiLS13ZWJraXQtYXBwLXJlZ2lvblwiKTtcclxuICAgIGlmICh2YWwpIHtcclxuICAgICAgICB2YWwgPSB2YWwudHJpbSgpO1xyXG4gICAgfVxyXG5cclxuICAgIGlmICh2YWwgIT09IFwiZHJhZ1wiKSB7XHJcbiAgICAgICAgcmV0dXJuIGZhbHNlO1xyXG4gICAgfVxyXG5cclxuICAgIC8vIE9ubHkgcHJvY2VzcyB0aGUgcHJpbWFyeSBidXR0b25cclxuICAgIGlmIChlLmJ1dHRvbnMgIT09IDEpIHtcclxuICAgICAgICByZXR1cm4gZmFsc2U7XHJcbiAgICB9XHJcblxyXG4gICAgcmV0dXJuIGUuZGV0YWlsID09PSAxO1xyXG59XHJcblxyXG5leHBvcnQgZnVuY3Rpb24gc2V0dXBEcmFnKCkge1xyXG4gICAgd2luZG93LmFkZEV2ZW50TGlzdGVuZXIoJ21vdXNlZG93bicsIG9uTW91c2VEb3duKTtcclxuICAgIHdpbmRvdy5hZGRFdmVudExpc3RlbmVyKCdtb3VzZW1vdmUnLCBvbk1vdXNlTW92ZSk7XHJcbiAgICB3aW5kb3cuYWRkRXZlbnRMaXN0ZW5lcignbW91c2V1cCcsIG9uTW91c2VVcCk7XHJcbn1cclxuXHJcbmxldCByZXNpemVFZGdlID0gbnVsbDtcclxubGV0IHJlc2l6YWJsZSA9IGZhbHNlO1xyXG5cclxuZXhwb3J0IGZ1bmN0aW9uIHNldFJlc2l6YWJsZSh2YWx1ZSkge1xyXG4gICAgcmVzaXphYmxlID0gdmFsdWU7XHJcbn1cclxuXHJcbmZ1bmN0aW9uIHRlc3RSZXNpemUoZSkge1xyXG4gICAgaWYoIHJlc2l6ZUVkZ2UgKSB7XHJcbiAgICAgICAgaW52b2tlKFwicmVzaXplOlwiICsgcmVzaXplRWRnZSk7XHJcbiAgICAgICAgcmV0dXJuIHRydWVcclxuICAgIH1cclxuICAgIHJldHVybiBmYWxzZTtcclxufVxyXG5cclxuZnVuY3Rpb24gb25Nb3VzZURvd24oZSkge1xyXG5cclxuICAgIC8vIENoZWNrIGZvciByZXNpemluZyBvbiBXaW5kb3dzXHJcbiAgICBpZiggV0lORE9XUyApIHtcclxuICAgICAgICBpZiAodGVzdFJlc2l6ZSgpKSB7XHJcbiAgICAgICAgICAgIHJldHVybjtcclxuICAgICAgICB9XHJcbiAgICB9XHJcbiAgICBpZiAoZHJhZ1Rlc3QoZSkpIHtcclxuICAgICAgICAvLyBJZ25vcmUgZHJhZyBvbiBzY3JvbGxiYXJzXHJcbiAgICAgICAgaWYgKGUub2Zmc2V0WCA+IGUudGFyZ2V0LmNsaWVudFdpZHRoIHx8IGUub2Zmc2V0WSA+IGUudGFyZ2V0LmNsaWVudEhlaWdodCkge1xyXG4gICAgICAgICAgICByZXR1cm47XHJcbiAgICAgICAgfVxyXG4gICAgICAgIHNob3VsZERyYWcgPSB0cnVlO1xyXG4gICAgfSBlbHNlIHtcclxuICAgICAgICBzaG91bGREcmFnID0gZmFsc2U7XHJcbiAgICB9XHJcbn1cclxuXHJcbmZ1bmN0aW9uIG9uTW91c2VVcChlKSB7XHJcbiAgICBsZXQgbW91c2VQcmVzc2VkID0gZS5idXR0b25zICE9PSB1bmRlZmluZWQgPyBlLmJ1dHRvbnMgOiBlLndoaWNoO1xyXG4gICAgaWYgKG1vdXNlUHJlc3NlZCA+IDApIHtcclxuICAgICAgICBlbmREcmFnKCk7XHJcbiAgICB9XHJcbn1cclxuXHJcbmV4cG9ydCBmdW5jdGlvbiBlbmREcmFnKCkge1xyXG4gICAgZG9jdW1lbnQuYm9keS5zdHlsZS5jdXJzb3IgPSAnZGVmYXVsdCc7XHJcbiAgICBzaG91bGREcmFnID0gZmFsc2U7XHJcbn1cclxuXHJcbmZ1bmN0aW9uIHNldFJlc2l6ZShjdXJzb3IpIHtcclxuICAgIGRvY3VtZW50LmRvY3VtZW50RWxlbWVudC5zdHlsZS5jdXJzb3IgPSBjdXJzb3IgfHwgZGVmYXVsdEN1cnNvcjtcclxuICAgIHJlc2l6ZUVkZ2UgPSBjdXJzb3I7XHJcbn1cclxuXHJcbmZ1bmN0aW9uIG9uTW91c2VNb3ZlKGUpIHtcclxuICAgIGlmIChzaG91bGREcmFnKSB7XHJcbiAgICAgICAgc2hvdWxkRHJhZyA9IGZhbHNlO1xyXG4gICAgICAgIGxldCBtb3VzZVByZXNzZWQgPSBlLmJ1dHRvbnMgIT09IHVuZGVmaW5lZCA/IGUuYnV0dG9ucyA6IGUud2hpY2g7XHJcbiAgICAgICAgaWYgKG1vdXNlUHJlc3NlZCA+IDApIHtcclxuICAgICAgICAgICAgaW52b2tlKFwiZHJhZ1wiKTtcclxuICAgICAgICB9XHJcbiAgICAgICAgcmV0dXJuO1xyXG4gICAgfVxyXG5cclxuICAgIGlmIChXSU5ET1dTKSB7XHJcbiAgICAgICAgaWYgKHJlc2l6YWJsZSkge1xyXG4gICAgICAgICAgICBoYW5kbGVSZXNpemUoZSk7XHJcbiAgICAgICAgfVxyXG4gICAgfVxyXG59XHJcblxyXG5sZXQgZGVmYXVsdEN1cnNvciA9IFwiYXV0b1wiO1xyXG5cclxuZnVuY3Rpb24gaGFuZGxlUmVzaXplKGUpIHtcclxuICAgIGxldCByZXNpemVIYW5kbGVIZWlnaHQgPSBHZXRGbGFnKFwic3lzdGVtLnJlc2l6ZUhhbmRsZUhlaWdodFwiKSB8fCA1O1xyXG4gICAgbGV0IHJlc2l6ZUhhbmRsZVdpZHRoID0gR2V0RmxhZyhcInN5c3RlbS5yZXNpemVIYW5kbGVXaWR0aFwiKSB8fCA1O1xyXG5cclxuICAgIC8vIEV4dHJhIHBpeGVscyBmb3IgdGhlIGNvcm5lciBhcmVhc1xyXG4gICAgbGV0IGNvcm5lckV4dHJhID0gR2V0RmxhZyhcInJlc2l6ZUNvcm5lckV4dHJhXCIpIHx8IDEwO1xyXG5cclxuICAgIGxldCByaWdodEJvcmRlciA9IHdpbmRvdy5vdXRlcldpZHRoIC0gZS5jbGllbnRYIDwgcmVzaXplSGFuZGxlV2lkdGg7XHJcbiAgICBsZXQgbGVmdEJvcmRlciA9IGUuY2xpZW50WCA8IHJlc2l6ZUhhbmRsZVdpZHRoO1xyXG4gICAgbGV0IHRvcEJvcmRlciA9IGUuY2xpZW50WSA8IHJlc2l6ZUhhbmRsZUhlaWdodDtcclxuICAgIGxldCBib3R0b21Cb3JkZXIgPSB3aW5kb3cub3V0ZXJIZWlnaHQgLSBlLmNsaWVudFkgPCByZXNpemVIYW5kbGVIZWlnaHQ7XHJcblxyXG4gICAgLy8gQWRqdXN0IGZvciBjb3JuZXJzXHJcbiAgICBsZXQgcmlnaHRDb3JuZXIgPSB3aW5kb3cub3V0ZXJXaWR0aCAtIGUuY2xpZW50WCA8IChyZXNpemVIYW5kbGVXaWR0aCArIGNvcm5lckV4dHJhKTtcclxuICAgIGxldCBsZWZ0Q29ybmVyID0gZS5jbGllbnRYIDwgKHJlc2l6ZUhhbmRsZVdpZHRoICsgY29ybmVyRXh0cmEpO1xyXG4gICAgbGV0IHRvcENvcm5lciA9IGUuY2xpZW50WSA8IChyZXNpemVIYW5kbGVIZWlnaHQgKyBjb3JuZXJFeHRyYSk7XHJcbiAgICBsZXQgYm90dG9tQ29ybmVyID0gd2luZG93Lm91dGVySGVpZ2h0IC0gZS5jbGllbnRZIDwgKHJlc2l6ZUhhbmRsZUhlaWdodCArIGNvcm5lckV4dHJhKTtcclxuXHJcbiAgICAvLyBJZiB3ZSBhcmVuJ3Qgb24gYW4gZWRnZSwgYnV0IHdlcmUsIHJlc2V0IHRoZSBjdXJzb3IgdG8gZGVmYXVsdFxyXG4gICAgaWYgKCFsZWZ0Qm9yZGVyICYmICFyaWdodEJvcmRlciAmJiAhdG9wQm9yZGVyICYmICFib3R0b21Cb3JkZXIgJiYgcmVzaXplRWRnZSAhPT0gdW5kZWZpbmVkKSB7XHJcbiAgICAgICAgc2V0UmVzaXplKCk7XHJcbiAgICB9XHJcbiAgICAvLyBBZGp1c3RlZCBmb3IgY29ybmVyIGFyZWFzXHJcbiAgICBlbHNlIGlmIChyaWdodENvcm5lciAmJiBib3R0b21Db3JuZXIpIHNldFJlc2l6ZShcInNlLXJlc2l6ZVwiKTtcclxuICAgIGVsc2UgaWYgKGxlZnRDb3JuZXIgJiYgYm90dG9tQ29ybmVyKSBzZXRSZXNpemUoXCJzdy1yZXNpemVcIik7XHJcbiAgICBlbHNlIGlmIChsZWZ0Q29ybmVyICYmIHRvcENvcm5lcikgc2V0UmVzaXplKFwibnctcmVzaXplXCIpO1xyXG4gICAgZWxzZSBpZiAodG9wQ29ybmVyICYmIHJpZ2h0Q29ybmVyKSBzZXRSZXNpemUoXCJuZS1yZXNpemVcIik7XHJcbiAgICBlbHNlIGlmIChsZWZ0Qm9yZGVyKSBzZXRSZXNpemUoXCJ3LXJlc2l6ZVwiKTtcclxuICAgIGVsc2UgaWYgKHRvcEJvcmRlcikgc2V0UmVzaXplKFwibi1yZXNpemVcIik7XHJcbiAgICBlbHNlIGlmIChib3R0b21Cb3JkZXIpIHNldFJlc2l6ZShcInMtcmVzaXplXCIpO1xyXG4gICAgZWxzZSBpZiAocmlnaHRCb3JkZXIpIHNldFJlc2l6ZShcImUtcmVzaXplXCIpO1xyXG59XHJcbiIsICIvKlxyXG4gXyAgICAgX18gICAgIF8gX19cclxufCB8ICAvIC9fX18gXyhfKSAvX19fX1xyXG58IHwgL3wgLyAvIF9fIGAvIC8gLyBfX18vXHJcbnwgfC8gfC8gLyAvXy8gLyAvIChfXyAgKVxyXG58X18vfF9fL1xcX18sXy9fL18vX19fXy9cclxuVGhlIGVsZWN0cm9uIGFsdGVybmF0aXZlIGZvciBHb1xyXG4oYykgTGVhIEFudGhvbnkgMjAxOS1wcmVzZW50XHJcbiovXHJcbi8qIGpzaGludCBlc3ZlcnNpb246IDkgKi9cclxuXHJcblxyXG5pbXBvcnQgKiBhcyBDbGlwYm9hcmQgZnJvbSAnLi9jbGlwYm9hcmQnO1xyXG5pbXBvcnQgKiBhcyBBcHBsaWNhdGlvbiBmcm9tICcuL2FwcGxpY2F0aW9uJztcclxuaW1wb3J0ICogYXMgU2NyZWVucyBmcm9tICcuL3NjcmVlbnMnO1xyXG5pbXBvcnQgKiBhcyBTeXN0ZW0gZnJvbSAnLi9zeXN0ZW0nO1xyXG5pbXBvcnQgKiBhcyBCcm93c2VyIGZyb20gJy4vYnJvd3Nlcic7XHJcbmltcG9ydCB7UGx1Z2luLCBDYWxsLCBjYWxsRXJyb3JDYWxsYmFjaywgY2FsbENhbGxiYWNrLCBDYWxsQnlJRCwgQ2FsbEJ5TmFtZX0gZnJvbSBcIi4vY2FsbHNcIjtcclxuaW1wb3J0IHtjbGllbnRJZH0gZnJvbSAnLi9ydW50aW1lJztcclxuaW1wb3J0IHtuZXdXaW5kb3d9IGZyb20gXCIuL3dpbmRvd1wiO1xyXG5pbXBvcnQge2Rpc3BhdGNoV2FpbHNFdmVudCwgRW1pdCwgT2ZmLCBPZmZBbGwsIE9uLCBPbmNlLCBPbk11bHRpcGxlfSBmcm9tIFwiLi9ldmVudHNcIjtcclxuaW1wb3J0IHtkaWFsb2dDYWxsYmFjaywgZGlhbG9nRXJyb3JDYWxsYmFjaywgRXJyb3IsIEluZm8sIE9wZW5GaWxlLCBRdWVzdGlvbiwgU2F2ZUZpbGUsIFdhcm5pbmcsfSBmcm9tIFwiLi9kaWFsb2dzXCI7XHJcbmltcG9ydCB7c2V0dXBDb250ZXh0TWVudXN9IGZyb20gXCIuL2NvbnRleHRtZW51XCI7XHJcbmltcG9ydCB7cmVsb2FkV01MfSBmcm9tIFwiLi93bWxcIjtcclxuaW1wb3J0IHtzZXR1cERyYWcsIGVuZERyYWcsIHNldFJlc2l6YWJsZX0gZnJvbSBcIi4vZHJhZ1wiO1xyXG5cclxud2luZG93LndhaWxzID0ge1xyXG4gICAgLi4ubmV3UnVudGltZShudWxsKSxcclxuICAgIENhcGFiaWxpdGllczoge30sXHJcbiAgICBjbGllbnRJZDogY2xpZW50SWQsXHJcbn07XHJcblxyXG5mZXRjaChcIi93YWlscy9jYXBhYmlsaXRpZXNcIikudGhlbigocmVzcG9uc2UpID0+IHtcclxuICAgIHJlc3BvbnNlLmpzb24oKS50aGVuKChkYXRhKSA9PiB7XHJcbiAgICAgICAgd2luZG93LndhaWxzLkNhcGFiaWxpdGllcyA9IGRhdGE7XHJcbiAgICB9KTtcclxufSk7XHJcblxyXG4vLyBJbnRlcm5hbCB3YWlscyBlbmRwb2ludHNcclxud2luZG93Ll93YWlscyA9IHtcclxuICAgIGRpYWxvZ0NhbGxiYWNrLFxyXG4gICAgZGlhbG9nRXJyb3JDYWxsYmFjayxcclxuICAgIGRpc3BhdGNoV2FpbHNFdmVudCxcclxuICAgIGNhbGxDYWxsYmFjayxcclxuICAgIGNhbGxFcnJvckNhbGxiYWNrLFxyXG4gICAgZW5kRHJhZyxcclxuICAgIHNldFJlc2l6YWJsZSxcclxufTtcclxuXHJcbmV4cG9ydCBmdW5jdGlvbiBuZXdSdW50aW1lKHdpbmRvd05hbWUpIHtcclxuICAgIHJldHVybiB7XHJcbiAgICAgICAgQ2xpcGJvYXJkOiB7XHJcbiAgICAgICAgICAgIC4uLkNsaXBib2FyZFxyXG4gICAgICAgIH0sXHJcbiAgICAgICAgQXBwbGljYXRpb246IHtcclxuICAgICAgICAgICAgLi4uQXBwbGljYXRpb24sXHJcbiAgICAgICAgICAgIEdldFdpbmRvd0J5TmFtZSh3aW5kb3dOYW1lKSB7XHJcbiAgICAgICAgICAgICAgICByZXR1cm4gbmV3UnVudGltZSh3aW5kb3dOYW1lKTtcclxuICAgICAgICAgICAgfVxyXG4gICAgICAgIH0sXHJcbiAgICAgICAgU3lzdGVtLFxyXG4gICAgICAgIFNjcmVlbnMsXHJcbiAgICAgICAgQnJvd3NlcixcclxuICAgICAgICBDYWxsLFxyXG4gICAgICAgIENhbGxCeUlELFxyXG4gICAgICAgIENhbGxCeU5hbWUsXHJcbiAgICAgICAgUGx1Z2luLFxyXG4gICAgICAgIFdNTDoge1xyXG4gICAgICAgICAgICBSZWxvYWQ6IHJlbG9hZFdNTCxcclxuICAgICAgICB9LFxyXG4gICAgICAgIERpYWxvZzoge1xyXG4gICAgICAgICAgICBJbmZvLFxyXG4gICAgICAgICAgICBXYXJuaW5nLFxyXG4gICAgICAgICAgICBFcnJvcixcclxuICAgICAgICAgICAgUXVlc3Rpb24sXHJcbiAgICAgICAgICAgIE9wZW5GaWxlLFxyXG4gICAgICAgICAgICBTYXZlRmlsZSxcclxuICAgICAgICB9LFxyXG4gICAgICAgIEV2ZW50czoge1xyXG4gICAgICAgICAgICBFbWl0LFxyXG4gICAgICAgICAgICBPbixcclxuICAgICAgICAgICAgT25jZSxcclxuICAgICAgICAgICAgT25NdWx0aXBsZSxcclxuICAgICAgICAgICAgT2ZmLFxyXG4gICAgICAgICAgICBPZmZBbGwsXHJcbiAgICAgICAgfSxcclxuICAgICAgICBXaW5kb3c6IG5ld1dpbmRvdyh3aW5kb3dOYW1lKSxcclxuICAgIH07XHJcbn1cclxuXHJcbmlmIChERUJVRykge1xyXG4gICAgY29uc29sZS5sb2coXCJXYWlscyB2My4wLjAgRGVidWcgTW9kZSBFbmFibGVkXCIpO1xyXG59XHJcblxyXG5zZXR1cENvbnRleHRNZW51cygpO1xyXG5zZXR1cERyYWcoKTtcclxuXHJcbmRvY3VtZW50LmFkZEV2ZW50TGlzdGVuZXIoXCJET01Db250ZW50TG9hZGVkXCIsIGZ1bmN0aW9uKCkge1xyXG4gICAgcmVsb2FkV01MKCk7XHJcbn0pO1xyXG4iXSwKICAibWFwcGluZ3MiOiAiOzs7Ozs7OztBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7OztBQ0FBLE1BQUksY0FDRjtBQVdLLE1BQUksU0FBUyxDQUFDLE9BQU8sT0FBTztBQUNqQyxRQUFJLEtBQUs7QUFDVCxRQUFJLElBQUk7QUFDUixXQUFPLEtBQUs7QUFDVixZQUFNLFlBQWEsS0FBSyxPQUFPLElBQUksS0FBTSxDQUFDO0FBQUEsSUFDNUM7QUFDQSxXQUFPO0FBQUEsRUFDVDs7O0FDTkEsTUFBTSxhQUFhLE9BQU8sU0FBUyxTQUFTO0FBRXJDLE1BQU0sY0FBYztBQUFBLElBQ3ZCLE1BQU07QUFBQSxJQUNOLFdBQVc7QUFBQSxJQUNYLGFBQWE7QUFBQSxJQUNiLFFBQVE7QUFBQSxJQUNSLGFBQWE7QUFBQSxJQUNiLFFBQVE7QUFBQSxJQUNSLFFBQVE7QUFBQSxJQUNSLFNBQVM7QUFBQSxJQUNULFFBQVE7QUFBQSxJQUNSLFNBQVM7QUFBQSxFQUNiO0FBQ08sTUFBSSxXQUFXLE9BQU87QUEwQzdCLFdBQVMsa0JBQWtCLFVBQVUsUUFBUSxZQUFZLE1BQU07QUFDM0QsUUFBSSxNQUFNLElBQUksSUFBSSxVQUFVO0FBQzVCLFFBQUksYUFBYSxPQUFPLFVBQVUsUUFBUTtBQUMxQyxRQUFJLGFBQWEsT0FBTyxVQUFVLE1BQU07QUFDeEMsUUFBSSxlQUFlO0FBQUEsTUFDZixTQUFTLENBQUM7QUFBQSxJQUNkO0FBQ0EsUUFBSSxZQUFZO0FBQ1osbUJBQWEsUUFBUSxxQkFBcUIsSUFBSTtBQUFBLElBQ2xEO0FBQ0EsUUFBSSxNQUFNO0FBQ04sVUFBSSxhQUFhLE9BQU8sUUFBUSxLQUFLLFVBQVUsSUFBSSxDQUFDO0FBQUEsSUFDeEQ7QUFDQSxpQkFBYSxRQUFRLG1CQUFtQixJQUFJO0FBQzVDLFdBQU8sSUFBSSxRQUFRLENBQUMsU0FBUyxXQUFXO0FBQ3BDLFlBQU0sS0FBSyxZQUFZLEVBQ2xCLEtBQUssY0FBWTtBQUNkLFlBQUksU0FBUyxJQUFJO0FBRWIsY0FBSSxTQUFTLFFBQVEsSUFBSSxjQUFjLEtBQUssU0FBUyxRQUFRLElBQUksY0FBYyxFQUFFLFFBQVEsa0JBQWtCLE1BQU0sSUFBSTtBQUNqSCxtQkFBTyxTQUFTLEtBQUs7QUFBQSxVQUN6QixPQUFPO0FBQ0gsbUJBQU8sU0FBUyxLQUFLO0FBQUEsVUFDekI7QUFBQSxRQUNKO0FBQ0EsZUFBTyxNQUFNLFNBQVMsVUFBVSxDQUFDO0FBQUEsTUFDckMsQ0FBQyxFQUNBLEtBQUssVUFBUSxRQUFRLElBQUksQ0FBQyxFQUMxQixNQUFNLFdBQVMsT0FBTyxLQUFLLENBQUM7QUFBQSxJQUNyQyxDQUFDO0FBQUEsRUFDTDtBQUVPLFdBQVMsdUJBQXVCLFFBQVEsWUFBWTtBQUN2RCxXQUFPLFNBQVUsUUFBUSxPQUFLLE1BQU07QUFDaEMsYUFBTyxrQkFBa0IsUUFBUSxRQUFRLFlBQVksSUFBSTtBQUFBLElBQzdEO0FBQUEsRUFDSjs7O0FGM0ZBLE1BQUksT0FBTyx1QkFBdUIsWUFBWSxTQUFTO0FBRXZELE1BQUksbUJBQW1CO0FBQ3ZCLE1BQUksZ0JBQWdCO0FBS2IsV0FBUyxRQUFRLE1BQU07QUFDMUIsU0FBSyxLQUFLLGtCQUFrQixFQUFDLEtBQUksQ0FBQztBQUFBLEVBQ3RDO0FBTU8sV0FBUyxPQUFPO0FBQ25CLFdBQU8sS0FBSyxhQUFhO0FBQUEsRUFDN0I7OztBR2hDQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFjQSxNQUFJQSxRQUFPLHVCQUF1QixZQUFZLFdBQVc7QUFFekQsTUFBSSxVQUFVO0FBQUEsSUFDVixNQUFNO0FBQUEsSUFDTixNQUFNO0FBQUEsSUFDTixNQUFNO0FBQUEsRUFDVjtBQUtPLFdBQVMsT0FBTztBQUNuQixTQUFLQSxNQUFLLFFBQVEsSUFBSTtBQUFBLEVBQzFCO0FBS08sV0FBUyxPQUFPO0FBQ25CLFNBQUtBLE1BQUssUUFBUSxJQUFJO0FBQUEsRUFDMUI7QUFNTyxXQUFTLE9BQU87QUFDbkIsU0FBS0EsTUFBSyxRQUFRLElBQUk7QUFBQSxFQUMxQjs7O0FDMUNBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQWtCQSxNQUFJQyxRQUFPLHVCQUF1QixZQUFZLE9BQU87QUFFckQsTUFBSSxnQkFBZ0I7QUFDcEIsTUFBSSxvQkFBb0I7QUFDeEIsTUFBSSxvQkFBb0I7QUFNakIsV0FBUyxTQUFTO0FBQ3JCLFdBQU9BLE1BQUssYUFBYTtBQUFBLEVBQzdCO0FBTU8sV0FBUyxhQUFhO0FBQ3pCLFdBQU9BLE1BQUssaUJBQWlCO0FBQUEsRUFDakM7QUFPTyxXQUFTLGFBQWE7QUFDekIsV0FBT0EsTUFBSyxpQkFBaUI7QUFBQSxFQUNqQzs7O0FDL0NBO0FBQUE7QUFBQTtBQUFBO0FBY0EsTUFBSUMsUUFBTyx1QkFBdUIsWUFBWSxNQUFNO0FBRXBELE1BQUksbUJBQW1CO0FBTWhCLFdBQVMsYUFBYTtBQUN6QixXQUFPQSxNQUFLLGdCQUFnQjtBQUFBLEVBQ2hDOzs7QUN4QkE7QUFBQTtBQUFBO0FBQUE7QUFjQSxNQUFJQyxRQUFPLHVCQUF1QixZQUFZLE9BQU87QUFFckQsTUFBSSxpQkFBaUI7QUFNZCxXQUFTLFFBQVEsS0FBSztBQUN6QixTQUFLQSxNQUFLLGdCQUFnQixFQUFDLElBQUcsQ0FBQztBQUFBLEVBQ25DOzs7QUNSQSxNQUFJQyxRQUFPLHVCQUF1QixZQUFZLElBQUk7QUFFbEQsTUFBSSxjQUFjO0FBRWxCLE1BQUksZ0JBQWdCLG9CQUFJLElBQUk7QUFFNUIsV0FBUyxhQUFhO0FBQ2xCLFFBQUk7QUFDSixPQUFHO0FBQ0MsZUFBUyxPQUFPO0FBQUEsSUFDcEIsU0FBUyxjQUFjLElBQUksTUFBTTtBQUNqQyxXQUFPO0FBQUEsRUFDWDtBQUVPLFdBQVMsYUFBYSxJQUFJLE1BQU0sUUFBUTtBQUMzQyxRQUFJLElBQUksY0FBYyxJQUFJLEVBQUU7QUFDNUIsUUFBSSxHQUFHO0FBQ0gsVUFBSSxRQUFRO0FBQ1IsVUFBRSxRQUFRLEtBQUssTUFBTSxJQUFJLENBQUM7QUFBQSxNQUM5QixPQUFPO0FBQ0gsVUFBRSxRQUFRLElBQUk7QUFBQSxNQUNsQjtBQUNBLG9CQUFjLE9BQU8sRUFBRTtBQUFBLElBQzNCO0FBQUEsRUFDSjtBQUVPLFdBQVMsa0JBQWtCLElBQUksU0FBUztBQUMzQyxRQUFJLElBQUksY0FBYyxJQUFJLEVBQUU7QUFDNUIsUUFBSSxHQUFHO0FBQ0gsUUFBRSxPQUFPLE9BQU87QUFDaEIsb0JBQWMsT0FBTyxFQUFFO0FBQUEsSUFDM0I7QUFBQSxFQUNKO0FBRUEsV0FBUyxZQUFZLE1BQU0sU0FBUztBQUNoQyxXQUFPLElBQUksUUFBUSxDQUFDLFNBQVMsV0FBVztBQUNwQyxVQUFJLEtBQUssV0FBVztBQUNwQixnQkFBVSxXQUFXLENBQUM7QUFDdEIsY0FBUSxTQUFTLElBQUk7QUFFckIsb0JBQWMsSUFBSSxJQUFJLEVBQUMsU0FBUyxPQUFNLENBQUM7QUFDdkMsTUFBQUEsTUFBSyxNQUFNLE9BQU8sRUFBRSxNQUFNLENBQUMsVUFBVTtBQUNqQyxlQUFPLEtBQUs7QUFDWixzQkFBYyxPQUFPLEVBQUU7QUFBQSxNQUMzQixDQUFDO0FBQUEsSUFDTCxDQUFDO0FBQUEsRUFDTDtBQUVPLFdBQVMsS0FBSyxTQUFTO0FBQzFCLFdBQU8sWUFBWSxhQUFhLE9BQU87QUFBQSxFQUMzQztBQUVPLFdBQVMsV0FBVyxTQUFTLE1BQU07QUFHdEMsUUFBSSxPQUFPLFNBQVMsWUFBWSxLQUFLLE1BQU0sR0FBRyxFQUFFLFdBQVcsR0FBRztBQUMxRCxZQUFNLElBQUksTUFBTSxvRUFBb0U7QUFBQSxJQUN4RjtBQUVBLFFBQUksUUFBUSxLQUFLLE1BQU0sR0FBRztBQUUxQixXQUFPLFlBQVksYUFBYTtBQUFBLE1BQzVCLGFBQWEsTUFBTSxDQUFDO0FBQUEsTUFDcEIsWUFBWSxNQUFNLENBQUM7QUFBQSxNQUNuQixZQUFZLE1BQU0sQ0FBQztBQUFBLE1BQ25CO0FBQUEsSUFDSixDQUFDO0FBQUEsRUFDTDtBQUVPLFdBQVMsU0FBUyxhQUFhLE1BQU07QUFDeEMsV0FBTyxZQUFZLGFBQWE7QUFBQSxNQUM1QjtBQUFBLE1BQ0E7QUFBQSxJQUNKLENBQUM7QUFBQSxFQUNMO0FBU08sV0FBUyxPQUFPLFlBQVksZUFBZSxNQUFNO0FBQ3BELFdBQU8sWUFBWSxhQUFhO0FBQUEsTUFDNUIsYUFBYTtBQUFBLE1BQ2IsWUFBWTtBQUFBLE1BQ1o7QUFBQSxNQUNBO0FBQUEsSUFDSixDQUFDO0FBQUEsRUFDTDs7O0FDdEZBLE1BQUksZUFBZTtBQUNuQixNQUFJLGlCQUFpQjtBQUNyQixNQUFJLG1CQUFtQjtBQUN2QixNQUFJLHFCQUFxQjtBQUN6QixNQUFJLGdCQUFnQjtBQUNwQixNQUFJLGFBQWE7QUFDakIsTUFBSSxtQkFBbUI7QUFDdkIsTUFBSSxtQkFBbUI7QUFDdkIsTUFBSSx1QkFBdUI7QUFDM0IsTUFBSSw0QkFBNEI7QUFDaEMsTUFBSSx5QkFBeUI7QUFDN0IsTUFBSSxlQUFlO0FBQ25CLE1BQUksYUFBYTtBQUNqQixNQUFJLGlCQUFpQjtBQUNyQixNQUFJLG1CQUFtQjtBQUN2QixNQUFJLHVCQUF1QjtBQUMzQixNQUFJLGlCQUFpQjtBQUNyQixNQUFJLG1CQUFtQjtBQUN2QixNQUFJLGdCQUFnQjtBQUNwQixNQUFJLGFBQWE7QUFDakIsTUFBSSxjQUFjO0FBQ2xCLE1BQUksNEJBQTRCO0FBQ2hDLE1BQUkscUJBQXFCO0FBQ3pCLE1BQUksY0FBYztBQUNsQixNQUFJLGVBQWU7QUFDbkIsTUFBSSxlQUFlO0FBQ25CLE1BQUksZ0JBQWdCO0FBQ3BCLE1BQUksa0JBQWtCO0FBQ3RCLE1BQUkscUJBQXFCO0FBQ3pCLE1BQUkscUJBQXFCO0FBRWxCLFdBQVMsVUFBVSxZQUFZO0FBQ2xDLFFBQUlDLFNBQU8sdUJBQXVCLFlBQVksUUFBUSxVQUFVO0FBQ2hFLFdBQU87QUFBQTtBQUFBO0FBQUE7QUFBQSxNQUtILFFBQVEsTUFBTSxLQUFLQSxPQUFLLFlBQVk7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBLE1BTXBDLFVBQVUsQ0FBQyxVQUFVLEtBQUtBLE9BQUssZ0JBQWdCLEVBQUMsTUFBSyxDQUFDO0FBQUE7QUFBQTtBQUFBO0FBQUEsTUFLdEQsWUFBWSxNQUFNLEtBQUtBLE9BQUssZ0JBQWdCO0FBQUE7QUFBQTtBQUFBO0FBQUEsTUFLNUMsY0FBYyxNQUFNLEtBQUtBLE9BQUssa0JBQWtCO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBLE1BT2hELFNBQVMsQ0FBQyxPQUFPLFdBQVdBLE9BQUssZUFBZSxFQUFDLE9BQU0sT0FBTSxDQUFDO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQSxNQU05RCxNQUFNLE1BQU07QUFBRSxlQUFPQSxPQUFLLFVBQVU7QUFBQSxNQUFHO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBLE1BT3ZDLFlBQVksQ0FBQyxPQUFPLFdBQVcsS0FBS0EsT0FBSyxrQkFBa0IsRUFBQyxPQUFNLE9BQU0sQ0FBQztBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQSxNQU96RSxZQUFZLENBQUMsT0FBTyxXQUFXLEtBQUtBLE9BQUssa0JBQWtCLEVBQUMsT0FBTSxPQUFNLENBQUM7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBLE1BTXpFLGdCQUFnQixDQUFDLFVBQVUsS0FBS0EsT0FBSyxzQkFBc0IsRUFBQyxhQUFZLE1BQUssQ0FBQztBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQSxNQU85RSxxQkFBcUIsQ0FBQyxHQUFHLE1BQU1BLE9BQUssMkJBQTJCLEVBQUMsR0FBRSxFQUFDLENBQUM7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBLE1BTXBFLGtCQUFrQixNQUFNO0FBQUUsZUFBT0EsT0FBSyxzQkFBc0I7QUFBQSxNQUFHO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQSxNQU0vRCxRQUFRLE1BQU07QUFBRSxlQUFPQSxPQUFLLFlBQVk7QUFBQSxNQUFHO0FBQUE7QUFBQTtBQUFBO0FBQUEsTUFLM0MsTUFBTSxNQUFNLEtBQUtBLE9BQUssVUFBVTtBQUFBO0FBQUE7QUFBQTtBQUFBLE1BS2hDLFVBQVUsTUFBTSxLQUFLQSxPQUFLLGNBQWM7QUFBQTtBQUFBO0FBQUE7QUFBQSxNQUt4QyxNQUFNLE1BQU0sS0FBS0EsT0FBSyxVQUFVO0FBQUE7QUFBQTtBQUFBO0FBQUEsTUFLaEMsT0FBTyxNQUFNLEtBQUtBLE9BQUssV0FBVztBQUFBO0FBQUE7QUFBQTtBQUFBLE1BS2xDLGdCQUFnQixNQUFNLEtBQUtBLE9BQUssb0JBQW9CO0FBQUE7QUFBQTtBQUFBO0FBQUEsTUFLcEQsWUFBWSxNQUFNLEtBQUtBLE9BQUssZ0JBQWdCO0FBQUE7QUFBQTtBQUFBO0FBQUEsTUFLNUMsVUFBVSxNQUFNLEtBQUtBLE9BQUssY0FBYztBQUFBO0FBQUE7QUFBQTtBQUFBLE1BS3hDLFlBQVksTUFBTSxLQUFLQSxPQUFLLGdCQUFnQjtBQUFBO0FBQUE7QUFBQTtBQUFBLE1BSzVDLFNBQVMsTUFBTSxLQUFLQSxPQUFLLGFBQWE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBLE1BU3RDLHFCQUFxQixDQUFDLEdBQUcsR0FBRyxHQUFHLE1BQU0sS0FBS0EsT0FBSywyQkFBMkIsRUFBQyxHQUFHLEdBQUcsR0FBRyxFQUFDLENBQUM7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBLE1BTXRGLGNBQWMsQ0FBQ0MsZUFBYyxLQUFLRCxPQUFLLG9CQUFvQixFQUFDLFdBQUFDLFdBQVMsQ0FBQztBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUEsTUFNdEUsT0FBTyxNQUFNO0FBQUUsZUFBT0QsT0FBSyxXQUFXO0FBQUEsTUFBRztBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUEsTUFNekMsUUFBUSxNQUFNO0FBQUUsZUFBT0EsT0FBSyxZQUFZO0FBQUEsTUFBRztBQUFBO0FBQUE7QUFBQTtBQUFBLE1BSzNDLFFBQVEsTUFBTSxLQUFLQSxPQUFLLFlBQVk7QUFBQTtBQUFBO0FBQUE7QUFBQSxNQUtwQyxTQUFTLE1BQU0sS0FBS0EsT0FBSyxhQUFhO0FBQUE7QUFBQTtBQUFBO0FBQUEsTUFLdEMsV0FBVyxNQUFNLEtBQUtBLE9BQUssZUFBZTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUEsTUFNMUMsY0FBYyxNQUFNO0FBQUUsZUFBT0EsT0FBSyxrQkFBa0I7QUFBQSxNQUFHO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQSxNQU12RCxjQUFjLENBQUMsY0FBYyxLQUFLQSxPQUFLLG9CQUFvQixFQUFDLFVBQVMsQ0FBQztBQUFBLElBQzFFO0FBQUEsRUFDSjs7O0FDak5BLE1BQUlFLFFBQU8sdUJBQXVCLFlBQVksTUFBTTtBQUNwRCxNQUFJLFlBQVk7QUFPaEIsTUFBTSxXQUFOLE1BQWU7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBLElBUVgsWUFBWSxXQUFXLFVBQVUsY0FBYztBQUMzQyxXQUFLLFlBQVk7QUFFakIsV0FBSyxlQUFlLGdCQUFnQjtBQUdwQyxXQUFLLFdBQVcsQ0FBQyxTQUFTO0FBQ3RCLGlCQUFTLElBQUk7QUFFYixZQUFJLEtBQUssaUJBQWlCLElBQUk7QUFDMUIsaUJBQU87QUFBQSxRQUNYO0FBRUEsYUFBSyxnQkFBZ0I7QUFDckIsZUFBTyxLQUFLLGlCQUFpQjtBQUFBLE1BQ2pDO0FBQUEsSUFDSjtBQUFBLEVBQ0o7QUFVTyxNQUFNLGFBQU4sTUFBaUI7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQSxJQU9wQixZQUFZLE1BQU0sT0FBTyxNQUFNO0FBQzNCLFdBQUssT0FBTztBQUNaLFdBQUssT0FBTztBQUFBLElBQ2hCO0FBQUEsRUFDSjtBQUVPLE1BQU0saUJBQWlCLG9CQUFJLElBQUk7QUFXL0IsV0FBUyxXQUFXLFdBQVcsVUFBVSxjQUFjO0FBQzFELFFBQUksWUFBWSxlQUFlLElBQUksU0FBUyxLQUFLLENBQUM7QUFDbEQsVUFBTSxlQUFlLElBQUksU0FBUyxXQUFXLFVBQVUsWUFBWTtBQUNuRSxjQUFVLEtBQUssWUFBWTtBQUMzQixtQkFBZSxJQUFJLFdBQVcsU0FBUztBQUN2QyxXQUFPLE1BQU0sWUFBWSxZQUFZO0FBQUEsRUFDekM7QUFVTyxXQUFTLEdBQUcsV0FBVyxVQUFVO0FBQ3BDLFdBQU8sV0FBVyxXQUFXLFVBQVUsRUFBRTtBQUFBLEVBQzdDO0FBVU8sV0FBUyxLQUFLLFdBQVcsVUFBVTtBQUN0QyxXQUFPLFdBQVcsV0FBVyxVQUFVLENBQUM7QUFBQSxFQUM1QztBQU9BLFdBQVMsWUFBWSxVQUFVO0FBQzNCLFVBQU0sWUFBWSxTQUFTO0FBRTNCLFFBQUksWUFBWSxlQUFlLElBQUksU0FBUyxFQUFFLE9BQU8sT0FBSyxNQUFNLFFBQVE7QUFDeEUsUUFBSSxVQUFVLFdBQVcsR0FBRztBQUN4QixxQkFBZSxPQUFPLFNBQVM7QUFBQSxJQUNuQyxPQUFPO0FBQ0gscUJBQWUsSUFBSSxXQUFXLFNBQVM7QUFBQSxJQUMzQztBQUFBLEVBQ0o7QUFRTyxXQUFTLG1CQUFtQixPQUFPO0FBQ3RDLFFBQUksWUFBWSxlQUFlLElBQUksTUFBTSxJQUFJO0FBQzdDLFFBQUksV0FBVztBQUVYLFVBQUksV0FBVyxDQUFDO0FBQ2hCLGdCQUFVLFFBQVEsY0FBWTtBQUMxQixZQUFJLFNBQVMsU0FBUyxTQUFTLEtBQUs7QUFDcEMsWUFBSSxRQUFRO0FBQ1IsbUJBQVMsS0FBSyxRQUFRO0FBQUEsUUFDMUI7QUFBQSxNQUNKLENBQUM7QUFFRCxVQUFJLFNBQVMsU0FBUyxHQUFHO0FBQ3JCLG9CQUFZLFVBQVUsT0FBTyxPQUFLLENBQUMsU0FBUyxTQUFTLENBQUMsQ0FBQztBQUN2RCxZQUFJLFVBQVUsV0FBVyxHQUFHO0FBQ3hCLHlCQUFlLE9BQU8sTUFBTSxJQUFJO0FBQUEsUUFDcEMsT0FBTztBQUNILHlCQUFlLElBQUksTUFBTSxNQUFNLFNBQVM7QUFBQSxRQUM1QztBQUFBLE1BQ0o7QUFBQSxJQUNKO0FBQUEsRUFDSjtBQVdPLFdBQVMsSUFBSSxjQUFjLHNCQUFzQjtBQUNwRCxRQUFJLGlCQUFpQixDQUFDLFdBQVcsR0FBRyxvQkFBb0I7QUFDeEQsbUJBQWUsUUFBUSxDQUFBQyxlQUFhO0FBQ2hDLHFCQUFlLE9BQU9BLFVBQVM7QUFBQSxJQUNuQyxDQUFDO0FBQUEsRUFDTDtBQU9PLFdBQVMsU0FBUztBQUNyQixtQkFBZSxNQUFNO0FBQUEsRUFDekI7QUFNTyxXQUFTLEtBQUssT0FBTztBQUN4QixTQUFLRCxNQUFLLFdBQVcsS0FBSztBQUFBLEVBQzlCOzs7QUMzS0EsTUFBSUUsUUFBTyx1QkFBdUIsWUFBWSxNQUFNO0FBRXBELE1BQUksYUFBYTtBQUNqQixNQUFJLGdCQUFnQjtBQUNwQixNQUFJLGNBQWM7QUFDbEIsTUFBSSxpQkFBaUI7QUFDckIsTUFBSSxpQkFBaUI7QUFDckIsTUFBSSxpQkFBaUI7QUFHckIsTUFBSSxrQkFBa0Isb0JBQUksSUFBSTtBQUU5QixXQUFTQyxjQUFhO0FBQ2xCLFFBQUk7QUFDSixPQUFHO0FBQ0MsZUFBUyxPQUFPO0FBQUEsSUFDcEIsU0FBUyxnQkFBZ0IsSUFBSSxNQUFNO0FBQ25DLFdBQU87QUFBQSxFQUNYO0FBRU8sV0FBUyxlQUFlLElBQUksTUFBTSxRQUFRO0FBQzdDLFFBQUksSUFBSSxnQkFBZ0IsSUFBSSxFQUFFO0FBQzlCLFFBQUksR0FBRztBQUNILFVBQUksUUFBUTtBQUNSLFVBQUUsUUFBUSxLQUFLLE1BQU0sSUFBSSxDQUFDO0FBQUEsTUFDOUIsT0FBTztBQUNILFVBQUUsUUFBUSxJQUFJO0FBQUEsTUFDbEI7QUFDQSxzQkFBZ0IsT0FBTyxFQUFFO0FBQUEsSUFDN0I7QUFBQSxFQUNKO0FBQ08sV0FBUyxvQkFBb0IsSUFBSSxTQUFTO0FBQzdDLFFBQUksSUFBSSxnQkFBZ0IsSUFBSSxFQUFFO0FBQzlCLFFBQUksR0FBRztBQUNILFFBQUUsT0FBTyxPQUFPO0FBQ2hCLHNCQUFnQixPQUFPLEVBQUU7QUFBQSxJQUM3QjtBQUFBLEVBQ0o7QUFFQSxXQUFTLE9BQU8sTUFBTSxTQUFTO0FBQzNCLFdBQU8sSUFBSSxRQUFRLENBQUMsU0FBUyxXQUFXO0FBQ3BDLFVBQUksS0FBS0EsWUFBVztBQUNwQixnQkFBVSxXQUFXLENBQUM7QUFDdEIsY0FBUSxXQUFXLElBQUk7QUFDdkIsc0JBQWdCLElBQUksSUFBSSxFQUFDLFNBQVMsT0FBTSxDQUFDO0FBQ3pDLE1BQUFELE1BQUssTUFBTSxPQUFPLEVBQUUsTUFBTSxDQUFDLFVBQVU7QUFDakMsZUFBTyxLQUFLO0FBQ1osd0JBQWdCLE9BQU8sRUFBRTtBQUFBLE1BQzdCLENBQUM7QUFBQSxJQUNMLENBQUM7QUFBQSxFQUNMO0FBUU8sV0FBUyxLQUFLLFNBQVM7QUFDMUIsV0FBTyxPQUFPLFlBQVksT0FBTztBQUFBLEVBQ3JDO0FBT08sV0FBUyxRQUFRLFNBQVM7QUFDN0IsV0FBTyxPQUFPLGVBQWUsT0FBTztBQUFBLEVBQ3hDO0FBT08sV0FBU0UsT0FBTSxTQUFTO0FBQzNCLFdBQU8sT0FBTyxhQUFhLE9BQU87QUFBQSxFQUN0QztBQU9PLFdBQVMsU0FBUyxTQUFTO0FBQzlCLFdBQU8sT0FBTyxnQkFBZ0IsT0FBTztBQUFBLEVBQ3pDO0FBT08sV0FBUyxTQUFTLFNBQVM7QUFDOUIsV0FBTyxPQUFPLGdCQUFnQixPQUFPO0FBQUEsRUFDekM7QUFPTyxXQUFTLFNBQVMsU0FBUztBQUM5QixXQUFPLE9BQU8sZ0JBQWdCLE9BQU87QUFBQSxFQUN6Qzs7O0FDN0hBLE1BQUlDLFFBQU8sdUJBQXVCLFlBQVksV0FBVztBQUV6RCxNQUFJLGtCQUFrQjtBQUV0QixXQUFTLGdCQUFnQixJQUFJLEdBQUcsR0FBRyxNQUFNO0FBQ3JDLFNBQUtBLE1BQUssaUJBQWlCLEVBQUMsSUFBSSxHQUFHLEdBQUcsS0FBSSxDQUFDO0FBQUEsRUFDL0M7QUFFTyxXQUFTLG9CQUFvQjtBQUNoQyxXQUFPLGlCQUFpQixlQUFlLGtCQUFrQjtBQUFBLEVBQzdEO0FBRUEsV0FBUyxtQkFBbUIsT0FBTztBQUUvQixRQUFJLFVBQVUsTUFBTTtBQUNwQixRQUFJLG9CQUFvQixPQUFPLGlCQUFpQixPQUFPLEVBQUUsaUJBQWlCLHNCQUFzQjtBQUNoRyx3QkFBb0Isb0JBQW9CLGtCQUFrQixLQUFLLElBQUk7QUFDbkUsUUFBSSxtQkFBbUI7QUFDbkIsWUFBTSxlQUFlO0FBQ3JCLFVBQUksd0JBQXdCLE9BQU8saUJBQWlCLE9BQU8sRUFBRSxpQkFBaUIsMkJBQTJCO0FBQ3pHLHNCQUFnQixtQkFBbUIsTUFBTSxTQUFTLE1BQU0sU0FBUyxxQkFBcUI7QUFDdEY7QUFBQSxJQUNKO0FBRUEsOEJBQTBCLEtBQUs7QUFBQSxFQUNuQztBQVVBLFdBQVMsMEJBQTBCLE9BQU87QUFFdEMsUUFBSSxNQUFPO0FBQ1A7QUFBQSxJQUNKO0FBR0EsVUFBTSxVQUFVLE1BQU07QUFDdEIsVUFBTSxnQkFBZ0IsT0FBTyxpQkFBaUIsT0FBTztBQUNyRCxVQUFNLDJCQUEyQixjQUFjLGlCQUFpQix1QkFBdUIsRUFBRSxLQUFLO0FBQzlGLFlBQVEsMEJBQTBCO0FBQUEsTUFDOUIsS0FBSztBQUNEO0FBQUEsTUFDSixLQUFLO0FBQ0QsY0FBTSxlQUFlO0FBQ3JCO0FBQUEsTUFDSjtBQUVJLFlBQUksUUFBUSxtQkFBbUI7QUFDM0I7QUFBQSxRQUNKO0FBR0EsY0FBTSxZQUFZLE9BQU8sYUFBYTtBQUN0QyxjQUFNLGVBQWdCLFVBQVUsU0FBUyxFQUFFLFNBQVM7QUFDcEQsWUFBSSxjQUFjO0FBQ2QsbUJBQVMsSUFBSSxHQUFHLElBQUksVUFBVSxZQUFZLEtBQUs7QUFDM0Msa0JBQU0sUUFBUSxVQUFVLFdBQVcsQ0FBQztBQUNwQyxrQkFBTSxRQUFRLE1BQU0sZUFBZTtBQUNuQyxxQkFBUyxJQUFJLEdBQUcsSUFBSSxNQUFNLFFBQVEsS0FBSztBQUNuQyxvQkFBTSxPQUFPLE1BQU0sQ0FBQztBQUNwQixrQkFBSSxTQUFTLGlCQUFpQixLQUFLLE1BQU0sS0FBSyxHQUFHLE1BQU0sU0FBUztBQUM1RDtBQUFBLGNBQ0o7QUFBQSxZQUNKO0FBQUEsVUFDSjtBQUFBLFFBQ0o7QUFFQSxZQUFJLFFBQVEsWUFBWSxXQUFXLFFBQVEsWUFBWSxZQUFZO0FBQy9ELGNBQUksZ0JBQWlCLENBQUMsUUFBUSxZQUFZLENBQUMsUUFBUSxVQUFXO0FBQzFEO0FBQUEsVUFDSjtBQUFBLFFBQ0o7QUFHQSxjQUFNLGVBQWU7QUFBQSxJQUM3QjtBQUFBLEVBQ0o7OztBQ2hGQSxXQUFTLFVBQVUsV0FBVyxPQUFLLE1BQU07QUFDckMsUUFBSSxRQUFRLElBQUksV0FBVyxXQUFXLElBQUk7QUFDMUMsU0FBSyxLQUFLO0FBQUEsRUFDZDtBQUVBLFdBQVMsdUJBQXVCO0FBQzVCLFVBQU0sV0FBVyxTQUFTLGlCQUFpQixhQUFhO0FBQ3hELGFBQVMsUUFBUSxTQUFVLFNBQVM7QUFDaEMsWUFBTSxZQUFZLFFBQVEsYUFBYSxXQUFXO0FBQ2xELFlBQU0sVUFBVSxRQUFRLGFBQWEsYUFBYTtBQUNsRCxZQUFNLFVBQVUsUUFBUSxhQUFhLGFBQWEsS0FBSztBQUV2RCxVQUFJLFdBQVcsV0FBWTtBQUN2QixZQUFJLFNBQVM7QUFDVCxtQkFBUyxFQUFDLE9BQU8sV0FBVyxTQUFRLFNBQVMsVUFBVSxPQUFPLFNBQVEsQ0FBQyxFQUFDLE9BQU0sTUFBSyxHQUFFLEVBQUMsT0FBTSxNQUFNLFdBQVUsS0FBSSxDQUFDLEVBQUMsQ0FBQyxFQUFFLEtBQUssU0FBVSxRQUFRO0FBQ3hJLGdCQUFJLFdBQVcsTUFBTTtBQUNqQix3QkFBVSxTQUFTO0FBQUEsWUFDdkI7QUFBQSxVQUNKLENBQUM7QUFDRDtBQUFBLFFBQ0o7QUFDQSxrQkFBVSxTQUFTO0FBQUEsTUFDdkI7QUFHQSxjQUFRLG9CQUFvQixTQUFTLFFBQVE7QUFHN0MsY0FBUSxpQkFBaUIsU0FBUyxRQUFRO0FBQUEsSUFDOUMsQ0FBQztBQUFBLEVBQ0w7QUFFQSxXQUFTLGlCQUFpQixRQUFRO0FBQzlCLFFBQUksTUFBTSxPQUFPLE1BQU0sTUFBTSxRQUFXO0FBQ3BDLGNBQVEsSUFBSSxtQkFBbUIsU0FBUyxZQUFZO0FBQUEsSUFDeEQ7QUFDQSxVQUFNLE9BQU8sTUFBTSxFQUFFO0FBQUEsRUFDekI7QUFFQSxXQUFTLHdCQUF3QjtBQUM3QixVQUFNLFdBQVcsU0FBUyxpQkFBaUIsY0FBYztBQUN6RCxhQUFTLFFBQVEsU0FBVSxTQUFTO0FBQ2hDLFlBQU0sZUFBZSxRQUFRLGFBQWEsWUFBWTtBQUN0RCxZQUFNLFVBQVUsUUFBUSxhQUFhLGFBQWE7QUFDbEQsWUFBTSxVQUFVLFFBQVEsYUFBYSxhQUFhLEtBQUs7QUFFdkQsVUFBSSxXQUFXLFdBQVk7QUFDdkIsWUFBSSxTQUFTO0FBQ1QsbUJBQVMsRUFBQyxPQUFPLFdBQVcsU0FBUSxTQUFTLFNBQVEsQ0FBQyxFQUFDLE9BQU0sTUFBSyxHQUFFLEVBQUMsT0FBTSxNQUFNLFdBQVUsS0FBSSxDQUFDLEVBQUMsQ0FBQyxFQUFFLEtBQUssU0FBVSxRQUFRO0FBQ3ZILGdCQUFJLFdBQVcsTUFBTTtBQUNqQiwrQkFBaUIsWUFBWTtBQUFBLFlBQ2pDO0FBQUEsVUFDSixDQUFDO0FBQ0Q7QUFBQSxRQUNKO0FBQ0EseUJBQWlCLFlBQVk7QUFBQSxNQUNqQztBQUdBLGNBQVEsb0JBQW9CLFNBQVMsUUFBUTtBQUc3QyxjQUFRLGlCQUFpQixTQUFTLFFBQVE7QUFBQSxJQUM5QyxDQUFDO0FBQUEsRUFDTDtBQUVBLFdBQVMsNEJBQTRCO0FBQ2pDLFVBQU0sV0FBVyxTQUFTLGlCQUFpQixlQUFlO0FBQzFELGFBQVMsUUFBUSxTQUFVLFNBQVM7QUFDaEMsWUFBTSxNQUFNLFFBQVEsYUFBYSxhQUFhO0FBQzlDLFlBQU0sVUFBVSxRQUFRLGFBQWEsYUFBYTtBQUNsRCxZQUFNLFVBQVUsUUFBUSxhQUFhLGFBQWEsS0FBSztBQUV2RCxVQUFJLFdBQVcsV0FBWTtBQUN2QixZQUFJLFNBQVM7QUFDVCxtQkFBUyxFQUFDLE9BQU8sV0FBVyxTQUFRLFNBQVMsU0FBUSxDQUFDLEVBQUMsT0FBTSxNQUFLLEdBQUUsRUFBQyxPQUFNLE1BQU0sV0FBVSxLQUFJLENBQUMsRUFBQyxDQUFDLEVBQUUsS0FBSyxTQUFVLFFBQVE7QUFDdkgsZ0JBQUksV0FBVyxNQUFNO0FBQ2pCLG1CQUFLLE1BQU0sUUFBUSxRQUFRLEdBQUc7QUFBQSxZQUNsQztBQUFBLFVBQ0osQ0FBQztBQUNEO0FBQUEsUUFDSjtBQUNBLGFBQUssTUFBTSxRQUFRLFFBQVEsR0FBRztBQUFBLE1BQ2xDO0FBR0EsY0FBUSxvQkFBb0IsU0FBUyxRQUFRO0FBRzdDLGNBQVEsaUJBQWlCLFNBQVMsUUFBUTtBQUFBLElBQzlDLENBQUM7QUFBQSxFQUNMO0FBRU8sV0FBUyxZQUFZO0FBQ3hCLHlCQUFxQjtBQUNyQiwwQkFBc0I7QUFDdEIsOEJBQTBCO0FBQUEsRUFDOUI7OztBQ3hGTyxNQUFJLFNBQVMsU0FBUyxPQUFPO0FBQ2hDLFFBQUcsT0FBUztBQUNSLGFBQU8sUUFBUSxZQUFZLEtBQUs7QUFBQSxJQUNwQyxPQUFPO0FBQ0gsYUFBTyxnQkFBZ0IsU0FBUyxZQUFZLEtBQUs7QUFBQSxJQUNyRDtBQUFBLEVBQ0o7OztBQ1BBLE1BQUksUUFBUSxvQkFBSSxJQUFJO0FBRXBCLFdBQVMsYUFBYSxLQUFLO0FBQ3ZCLFVBQU0sTUFBTSxvQkFBSSxJQUFJO0FBRXBCLGVBQVcsQ0FBQyxLQUFLLEtBQUssS0FBSyxPQUFPLFFBQVEsR0FBRyxHQUFHO0FBQzVDLFVBQUksT0FBTyxVQUFVLFlBQVksVUFBVSxNQUFNO0FBQzdDLFlBQUksSUFBSSxLQUFLLGFBQWEsS0FBSyxDQUFDO0FBQUEsTUFDcEMsT0FBTztBQUNILFlBQUksSUFBSSxLQUFLLEtBQUs7QUFBQSxNQUN0QjtBQUFBLElBQ0o7QUFFQSxXQUFPO0FBQUEsRUFDWDtBQUVBLFFBQU0sY0FBYyxFQUFFLEtBQUssQ0FBQyxhQUFhO0FBQ3JDLGFBQVMsS0FBSyxFQUFFLEtBQUssQ0FBQyxTQUFTO0FBQzNCLGNBQVEsYUFBYSxJQUFJO0FBQUEsSUFDN0IsQ0FBQztBQUFBLEVBQ0wsQ0FBQzs7O0FDakJELE1BQUksYUFBYTtBQUVWLFdBQVMsU0FBUyxHQUFHO0FBQ3hCLFFBQUksTUFBTSxPQUFPLGlCQUFpQixFQUFFLE1BQU0sRUFBRSxpQkFBaUIscUJBQXFCO0FBQ2xGLFFBQUksS0FBSztBQUNMLFlBQU0sSUFBSSxLQUFLO0FBQUEsSUFDbkI7QUFFQSxRQUFJLFFBQVEsUUFBUTtBQUNoQixhQUFPO0FBQUEsSUFDWDtBQUdBLFFBQUksRUFBRSxZQUFZLEdBQUc7QUFDakIsYUFBTztBQUFBLElBQ1g7QUFFQSxXQUFPLEVBQUUsV0FBVztBQUFBLEVBQ3hCO0FBRU8sV0FBUyxZQUFZO0FBQ3hCLFdBQU8saUJBQWlCLGFBQWEsV0FBVztBQUNoRCxXQUFPLGlCQUFpQixhQUFhLFdBQVc7QUFDaEQsV0FBTyxpQkFBaUIsV0FBVyxTQUFTO0FBQUEsRUFDaEQ7QUFHQSxNQUFJLFlBQVk7QUFFVCxXQUFTLGFBQWEsT0FBTztBQUNoQyxnQkFBWTtBQUFBLEVBQ2hCO0FBVUEsV0FBUyxZQUFZLEdBQUc7QUFHcEIsUUFBSSxPQUFVO0FBQ1YsVUFBSSxXQUFXLEdBQUc7QUFDZDtBQUFBLE1BQ0o7QUFBQSxJQUNKO0FBQ0EsUUFBSSxTQUFTLENBQUMsR0FBRztBQUViLFVBQUksRUFBRSxVQUFVLEVBQUUsT0FBTyxlQUFlLEVBQUUsVUFBVSxFQUFFLE9BQU8sY0FBYztBQUN2RTtBQUFBLE1BQ0o7QUFDQSxtQkFBYTtBQUFBLElBQ2pCLE9BQU87QUFDSCxtQkFBYTtBQUFBLElBQ2pCO0FBQUEsRUFDSjtBQUVBLFdBQVMsVUFBVSxHQUFHO0FBQ2xCLFFBQUksZUFBZSxFQUFFLFlBQVksU0FBWSxFQUFFLFVBQVUsRUFBRTtBQUMzRCxRQUFJLGVBQWUsR0FBRztBQUNsQixjQUFRO0FBQUEsSUFDWjtBQUFBLEVBQ0o7QUFFTyxXQUFTLFVBQVU7QUFDdEIsYUFBUyxLQUFLLE1BQU0sU0FBUztBQUM3QixpQkFBYTtBQUFBLEVBQ2pCO0FBT0EsV0FBUyxZQUFZLEdBQUc7QUFDcEIsUUFBSSxZQUFZO0FBQ1osbUJBQWE7QUFDYixVQUFJLGVBQWUsRUFBRSxZQUFZLFNBQVksRUFBRSxVQUFVLEVBQUU7QUFDM0QsVUFBSSxlQUFlLEdBQUc7QUFDbEIsZUFBTyxNQUFNO0FBQUEsTUFDakI7QUFDQTtBQUFBLElBQ0o7QUFFQSxRQUFJLE9BQVM7QUFDVCxVQUFJLFdBQVc7QUFDWCxxQkFBYSxDQUFDO0FBQUEsTUFDbEI7QUFBQSxJQUNKO0FBQUEsRUFDSjs7O0FDakZBLFNBQU8sUUFBUTtBQUFBLElBQ1gsR0FBRyxXQUFXLElBQUk7QUFBQSxJQUNsQixjQUFjLENBQUM7QUFBQSxJQUNmO0FBQUEsRUFDSjtBQUVBLFFBQU0scUJBQXFCLEVBQUUsS0FBSyxDQUFDLGFBQWE7QUFDNUMsYUFBUyxLQUFLLEVBQUUsS0FBSyxDQUFDLFNBQVM7QUFDM0IsYUFBTyxNQUFNLGVBQWU7QUFBQSxJQUNoQyxDQUFDO0FBQUEsRUFDTCxDQUFDO0FBR0QsU0FBTyxTQUFTO0FBQUEsSUFDWjtBQUFBLElBQ0E7QUFBQSxJQUNBO0FBQUEsSUFDQTtBQUFBLElBQ0E7QUFBQSxJQUNBO0FBQUEsSUFDQTtBQUFBLEVBQ0o7QUFFTyxXQUFTLFdBQVcsWUFBWTtBQUNuQyxXQUFPO0FBQUEsTUFDSCxXQUFXO0FBQUEsUUFDUCxHQUFHO0FBQUEsTUFDUDtBQUFBLE1BQ0EsYUFBYTtBQUFBLFFBQ1QsR0FBRztBQUFBLFFBQ0gsZ0JBQWdCQyxhQUFZO0FBQ3hCLGlCQUFPLFdBQVdBLFdBQVU7QUFBQSxRQUNoQztBQUFBLE1BQ0o7QUFBQSxNQUNBO0FBQUEsTUFDQTtBQUFBLE1BQ0E7QUFBQSxNQUNBO0FBQUEsTUFDQTtBQUFBLE1BQ0E7QUFBQSxNQUNBO0FBQUEsTUFDQSxLQUFLO0FBQUEsUUFDRCxRQUFRO0FBQUEsTUFDWjtBQUFBLE1BQ0EsUUFBUTtBQUFBLFFBQ0o7QUFBQSxRQUNBO0FBQUEsUUFDQSxPQUFBQztBQUFBLFFBQ0E7QUFBQSxRQUNBO0FBQUEsUUFDQTtBQUFBLE1BQ0o7QUFBQSxNQUNBLFFBQVE7QUFBQSxRQUNKO0FBQUEsUUFDQTtBQUFBLFFBQ0E7QUFBQSxRQUNBO0FBQUEsUUFDQTtBQUFBLFFBQ0E7QUFBQSxNQUNKO0FBQUEsTUFDQSxRQUFRLFVBQVUsVUFBVTtBQUFBLElBQ2hDO0FBQUEsRUFDSjtBQUVBLE1BQUksTUFBTztBQUNQLFlBQVEsSUFBSSxpQ0FBaUM7QUFBQSxFQUNqRDtBQUVBLG9CQUFrQjtBQUNsQixZQUFVO0FBRVYsV0FBUyxpQkFBaUIsb0JBQW9CLFdBQVc7QUFDckQsY0FBVTtBQUFBLEVBQ2QsQ0FBQzsiLAogICJuYW1lcyI6IFsiY2FsbCIsICJjYWxsIiwgImNhbGwiLCAiY2FsbCIsICJjYWxsIiwgImNhbGwiLCAicmVzaXphYmxlIiwgImNhbGwiLCAiZXZlbnROYW1lIiwgImNhbGwiLCAiZ2VuZXJhdGVJRCIsICJFcnJvciIsICJjYWxsIiwgIndpbmRvd05hbWUiLCAiRXJyb3IiXQp9Cg== diff --git a/v3/internal/runtime/runtime_debug_desktop_windows.js b/v3/internal/runtime/runtime_debug_desktop_windows.js new file mode 100644 index 000000000..1e90fd511 --- /dev/null +++ b/v3/internal/runtime/runtime_debug_desktop_windows.js @@ -0,0 +1,935 @@ +(() => { + var __defProp = Object.defineProperty; + var __export = (target, all) => { + for (var name in all) + __defProp(target, name, { get: all[name], enumerable: true }); + }; + + // desktop/clipboard.js + var clipboard_exports = {}; + __export(clipboard_exports, { + SetText: () => SetText, + Text: () => Text + }); + + // node_modules/nanoid/non-secure/index.js + var urlAlphabet = "useandom-26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict"; + var nanoid = (size = 21) => { + let id = ""; + let i = size; + while (i--) { + id += urlAlphabet[Math.random() * 64 | 0]; + } + return id; + }; + + // desktop/runtime.js + var runtimeURL = window.location.origin + "/wails/runtime"; + var objectNames = { + Call: 0, + Clipboard: 1, + Application: 2, + Events: 3, + ContextMenu: 4, + Dialog: 5, + Window: 6, + Screens: 7, + System: 8, + Browser: 9 + }; + var clientId = nanoid(); + 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 (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) { + 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)); + }); + } + function newRuntimeCallerWithID(object, windowName) { + return function(method, args = null) { + return runtimeCallWithID(object, method, windowName, args); + }; + } + + // desktop/clipboard.js + var call = newRuntimeCallerWithID(objectNames.Clipboard); + var ClipboardSetText = 0; + var ClipboardText = 1; + function SetText(text) { + void call(ClipboardSetText, { text }); + } + function Text() { + return call(ClipboardText); + } + + // desktop/application.js + var application_exports = {}; + __export(application_exports, { + Hide: () => Hide, + Quit: () => Quit, + Show: () => Show + }); + var call2 = newRuntimeCallerWithID(objectNames.Application); + var methods = { + Hide: 0, + Show: 1, + Quit: 2 + }; + function Hide() { + void call2(methods.Hide); + } + function Show() { + void call2(methods.Show); + } + function Quit() { + void call2(methods.Quit); + } + + // desktop/screens.js + var screens_exports = {}; + __export(screens_exports, { + GetAll: () => GetAll, + GetCurrent: () => GetCurrent, + GetPrimary: () => GetPrimary + }); + var call3 = newRuntimeCallerWithID(objectNames.Screens); + var ScreensGetAll = 0; + var ScreensGetPrimary = 1; + var ScreensGetCurrent = 2; + function GetAll() { + return call3(ScreensGetAll); + } + function GetPrimary() { + return call3(ScreensGetPrimary); + } + function GetCurrent() { + return call3(ScreensGetCurrent); + } + + // desktop/system.js + var system_exports = {}; + __export(system_exports, { + IsDarkMode: () => IsDarkMode + }); + var call4 = newRuntimeCallerWithID(objectNames.System); + var SystemIsDarkMode = 0; + function IsDarkMode() { + return call4(SystemIsDarkMode); + } + + // desktop/browser.js + var browser_exports = {}; + __export(browser_exports, { + OpenURL: () => OpenURL + }); + var call5 = newRuntimeCallerWithID(objectNames.Browser); + var BrowserOpenURL = 0; + function OpenURL(url) { + void call5(BrowserOpenURL, { url }); + } + + // desktop/calls.js + var call6 = newRuntimeCallerWithID(objectNames.Call); + var CallBinding = 0; + var callResponses = /* @__PURE__ */ new Map(); + function generateID() { + let result; + do { + result = nanoid(); + } while (callResponses.has(result)); + return result; + } + function callCallback(id, data, isJSON) { + let p = callResponses.get(id); + if (p) { + if (isJSON) { + p.resolve(JSON.parse(data)); + } else { + p.resolve(data); + } + callResponses.delete(id); + } + } + function callErrorCallback(id, message) { + let p = callResponses.get(id); + if (p) { + p.reject(message); + callResponses.delete(id); + } + } + function callBinding(type, options) { + return new Promise((resolve, reject) => { + let id = generateID(); + options = options || {}; + options["call-id"] = id; + callResponses.set(id, { resolve, reject }); + call6(type, options).catch((error) => { + reject(error); + callResponses.delete(id); + }); + }); + } + function Call(options) { + return callBinding(CallBinding, options); + } + function CallByName(name, ...args) { + if (typeof name !== "string" || name.split(".").length !== 3) { + throw new Error("CallByName requires a string in the format 'package.struct.method'"); + } + let parts = name.split("."); + return callBinding(CallBinding, { + packageName: parts[0], + structName: parts[1], + methodName: parts[2], + args + }); + } + function CallByID(methodID, ...args) { + return callBinding(CallBinding, { + methodID, + args + }); + } + function Plugin(pluginName, methodName, ...args) { + return callBinding(CallBinding, { + packageName: "wails-plugins", + structName: pluginName, + methodName, + args + }); + } + + // desktop/window.js + var WindowCenter = 0; + var WindowSetTitle = 1; + var WindowFullscreen = 2; + var WindowUnFullscreen = 3; + var WindowSetSize = 4; + var WindowSize = 5; + var WindowSetMaxSize = 6; + var WindowSetMinSize = 7; + var WindowSetAlwaysOnTop = 8; + var WindowSetRelativePosition = 9; + var WindowRelativePosition = 10; + var WindowScreen = 11; + var WindowHide = 12; + var WindowMaximise = 13; + var WindowUnMaximise = 14; + var WindowToggleMaximise = 15; + var WindowMinimise = 16; + var WindowUnMinimise = 17; + var WindowRestore = 18; + var WindowShow = 19; + var WindowClose = 20; + var WindowSetBackgroundColour = 21; + var WindowSetResizable = 22; + var WindowWidth = 23; + var WindowHeight = 24; + var WindowZoomIn = 25; + var WindowZoomOut = 26; + var WindowZoomReset = 27; + var WindowGetZoomLevel = 28; + var WindowSetZoomLevel = 29; + function newWindow(windowName) { + let call10 = newRuntimeCallerWithID(objectNames.Window, windowName); + return { + /** + * Centers the window. + */ + Center: () => void call10(WindowCenter), + /** + * Set the window title. + * @param title + */ + SetTitle: (title) => void call10(WindowSetTitle, { title }), + /** + * Makes the window fullscreen. + */ + Fullscreen: () => void call10(WindowFullscreen), + /** + * Unfullscreen the window. + */ + UnFullscreen: () => void call10(WindowUnFullscreen), + /** + * Set the window size. + * @param {number} width The window width + * @param {number} height The window height + */ + SetSize: (width, height) => call10(WindowSetSize, { width, height }), + /** + * Get the window size. + * @returns {Promise} The window size + */ + Size: () => { + return call10(WindowSize); + }, + /** + * Set the window maximum size. + * @param {number} width + * @param {number} height + */ + SetMaxSize: (width, height) => void call10(WindowSetMaxSize, { width, height }), + /** + * Set the window minimum size. + * @param {number} width + * @param {number} height + */ + SetMinSize: (width, height) => void call10(WindowSetMinSize, { width, height }), + /** + * Set window to be always on top. + * @param {boolean} onTop Whether the window should be always on top + */ + SetAlwaysOnTop: (onTop) => void call10(WindowSetAlwaysOnTop, { alwaysOnTop: onTop }), + /** + * Set the window relative position. + * @param {number} x + * @param {number} y + */ + SetRelativePosition: (x, y) => call10(WindowSetRelativePosition, { x, y }), + /** + * Get the window position. + * @returns {Promise} The window position + */ + RelativePosition: () => { + return call10(WindowRelativePosition); + }, + /** + * Get the screen the window is on. + * @returns {Promise} + */ + Screen: () => { + return call10(WindowScreen); + }, + /** + * Hide the window + */ + Hide: () => void call10(WindowHide), + /** + * Maximise the window + */ + Maximise: () => void call10(WindowMaximise), + /** + * Show the window + */ + Show: () => void call10(WindowShow), + /** + * Close the window + */ + Close: () => void call10(WindowClose), + /** + * Toggle the window maximise state + */ + ToggleMaximise: () => void call10(WindowToggleMaximise), + /** + * Unmaximise the window + */ + UnMaximise: () => void call10(WindowUnMaximise), + /** + * Minimise the window + */ + Minimise: () => void call10(WindowMinimise), + /** + * Unminimise the window + */ + UnMinimise: () => void call10(WindowUnMinimise), + /** + * Restore the window + */ + Restore: () => void call10(WindowRestore), + /** + * Set the background colour of the window. + * @param {number} r - A value between 0 and 255 + * @param {number} g - A value between 0 and 255 + * @param {number} b - A value between 0 and 255 + * @param {number} a - A value between 0 and 255 + */ + SetBackgroundColour: (r, g, b, a) => void call10(WindowSetBackgroundColour, { r, g, b, a }), + /** + * Set whether the window can be resized or not + * @param {boolean} resizable + */ + SetResizable: (resizable2) => void call10(WindowSetResizable, { resizable: resizable2 }), + /** + * Get the window width + * @returns {Promise} + */ + Width: () => { + return call10(WindowWidth); + }, + /** + * Get the window height + * @returns {Promise} + */ + Height: () => { + return call10(WindowHeight); + }, + /** + * Zoom in the window + */ + ZoomIn: () => void call10(WindowZoomIn), + /** + * Zoom out the window + */ + ZoomOut: () => void call10(WindowZoomOut), + /** + * Reset the window zoom + */ + ZoomReset: () => void call10(WindowZoomReset), + /** + * Get the window zoom + * @returns {Promise} + */ + GetZoomLevel: () => { + return call10(WindowGetZoomLevel); + }, + /** + * Set the window zoom level + * @param {number} zoomLevel + */ + SetZoomLevel: (zoomLevel) => void call10(WindowSetZoomLevel, { zoomLevel }) + }; + } + + // desktop/events.js + var call7 = newRuntimeCallerWithID(objectNames.Events); + var EventEmit = 0; + var Listener = class { + /** + * Creates an instance of Listener. + * @param {string} eventName + * @param {function} callback + * @param {number} maxCallbacks + * @memberof Listener + */ + constructor(eventName, callback, maxCallbacks) { + this.eventName = eventName; + this.maxCallbacks = maxCallbacks || -1; + this.Callback = (data) => { + callback(data); + if (this.maxCallbacks === -1) { + return false; + } + this.maxCallbacks -= 1; + return this.maxCallbacks === 0; + }; + } + }; + var WailsEvent = class { + /** + * Creates an instance of WailsEvent. + * @param {string} name - Name of the event + * @param {any=null} data - Data associated with the event + * @memberof WailsEvent + */ + constructor(name, data = null) { + this.name = name; + this.data = data; + } + }; + var eventListeners = /* @__PURE__ */ new Map(); + function OnMultiple(eventName, callback, maxCallbacks) { + let listeners = eventListeners.get(eventName) || []; + const thisListener = new Listener(eventName, callback, maxCallbacks); + listeners.push(thisListener); + eventListeners.set(eventName, listeners); + return () => listenerOff(thisListener); + } + function On(eventName, callback) { + return OnMultiple(eventName, callback, -1); + } + function Once(eventName, callback) { + return OnMultiple(eventName, callback, 1); + } + function listenerOff(listener) { + const eventName = listener.eventName; + let listeners = eventListeners.get(eventName).filter((l) => l !== listener); + if (listeners.length === 0) { + eventListeners.delete(eventName); + } else { + eventListeners.set(eventName, listeners); + } + } + function dispatchWailsEvent(event) { + let listeners = eventListeners.get(event.name); + if (listeners) { + let toRemove = []; + listeners.forEach((listener) => { + let remove = listener.Callback(event); + if (remove) { + toRemove.push(listener); + } + }); + if (toRemove.length > 0) { + listeners = listeners.filter((l) => !toRemove.includes(l)); + if (listeners.length === 0) { + eventListeners.delete(event.name); + } else { + eventListeners.set(event.name, listeners); + } + } + } + } + function Off(eventName, ...additionalEventNames) { + let eventsToRemove = [eventName, ...additionalEventNames]; + eventsToRemove.forEach((eventName2) => { + eventListeners.delete(eventName2); + }); + } + function OffAll() { + eventListeners.clear(); + } + function Emit(event) { + void call7(EventEmit, event); + } + + // desktop/dialogs.js + var call8 = newRuntimeCallerWithID(objectNames.Dialog); + var DialogInfo = 0; + var DialogWarning = 1; + var DialogError = 2; + var DialogQuestion = 3; + var DialogOpenFile = 4; + var DialogSaveFile = 5; + var dialogResponses = /* @__PURE__ */ new Map(); + function generateID2() { + let result; + do { + result = nanoid(); + } while (dialogResponses.has(result)); + return result; + } + function dialogCallback(id, data, isJSON) { + let p = dialogResponses.get(id); + if (p) { + if (isJSON) { + p.resolve(JSON.parse(data)); + } else { + p.resolve(data); + } + dialogResponses.delete(id); + } + } + function dialogErrorCallback(id, message) { + let p = dialogResponses.get(id); + if (p) { + p.reject(message); + dialogResponses.delete(id); + } + } + function dialog(type, options) { + return new Promise((resolve, reject) => { + let id = generateID2(); + options = options || {}; + options["dialog-id"] = id; + dialogResponses.set(id, { resolve, reject }); + call8(type, options).catch((error) => { + reject(error); + dialogResponses.delete(id); + }); + }); + } + function Info(options) { + return dialog(DialogInfo, options); + } + function Warning(options) { + return dialog(DialogWarning, options); + } + function Error2(options) { + return dialog(DialogError, options); + } + function Question(options) { + return dialog(DialogQuestion, options); + } + function OpenFile(options) { + return dialog(DialogOpenFile, options); + } + function SaveFile(options) { + return dialog(DialogSaveFile, options); + } + + // desktop/contextmenu.js + var call9 = newRuntimeCallerWithID(objectNames.ContextMenu); + var ContextMenuOpen = 0; + function openContextMenu(id, x, y, data) { + void call9(ContextMenuOpen, { id, x, y, data }); + } + function setupContextMenus() { + window.addEventListener("contextmenu", contextMenuHandler); + } + function contextMenuHandler(event) { + let element = event.target; + let customContextMenu = window.getComputedStyle(element).getPropertyValue("--custom-contextmenu"); + customContextMenu = customContextMenu ? customContextMenu.trim() : ""; + if (customContextMenu) { + event.preventDefault(); + let customContextMenuData = window.getComputedStyle(element).getPropertyValue("--custom-contextmenu-data"); + openContextMenu(customContextMenu, event.clientX, event.clientY, customContextMenuData); + return; + } + processDefaultContextMenu(event); + } + function processDefaultContextMenu(event) { + if (true) { + return; + } + const element = event.target; + const computedStyle = window.getComputedStyle(element); + const defaultContextMenuAction = computedStyle.getPropertyValue("--default-contextmenu").trim(); + switch (defaultContextMenuAction) { + case "show": + return; + case "hide": + event.preventDefault(); + return; + default: + if (element.isContentEditable) { + return; + } + const selection = window.getSelection(); + const hasSelection = selection.toString().length > 0; + if (hasSelection) { + for (let i = 0; i < selection.rangeCount; i++) { + const range = selection.getRangeAt(i); + const rects = range.getClientRects(); + for (let j = 0; j < rects.length; j++) { + const rect = rects[j]; + if (document.elementFromPoint(rect.left, rect.top) === element) { + return; + } + } + } + } + if (element.tagName === "INPUT" || element.tagName === "TEXTAREA") { + if (hasSelection || !element.readOnly && !element.disabled) { + return; + } + } + event.preventDefault(); + } + } + + // desktop/wml.js + function sendEvent(eventName, data = null) { + let event = new WailsEvent(eventName, data); + Emit(event); + } + function addWMLEventListeners() { + const elements = document.querySelectorAll("[wml-event]"); + elements.forEach(function(element) { + const eventType = element.getAttribute("wml-event"); + const confirm = element.getAttribute("wml-confirm"); + const trigger = element.getAttribute("wml-trigger") || "click"; + let callback = function() { + if (confirm) { + Question({ Title: "Confirm", Message: confirm, Detached: false, Buttons: [{ Label: "Yes" }, { Label: "No", IsDefault: true }] }).then(function(result) { + if (result !== "No") { + sendEvent(eventType); + } + }); + return; + } + sendEvent(eventType); + }; + element.removeEventListener(trigger, callback); + element.addEventListener(trigger, callback); + }); + } + function callWindowMethod(method) { + if (wails.Window[method] === void 0) { + console.log("Window method " + method + " not found"); + } + wails.Window[method](); + } + function addWMLWindowListeners() { + const elements = document.querySelectorAll("[wml-window]"); + elements.forEach(function(element) { + const windowMethod = element.getAttribute("wml-window"); + const confirm = element.getAttribute("wml-confirm"); + const trigger = element.getAttribute("wml-trigger") || "click"; + let callback = function() { + if (confirm) { + Question({ Title: "Confirm", Message: confirm, Buttons: [{ Label: "Yes" }, { Label: "No", IsDefault: true }] }).then(function(result) { + if (result !== "No") { + callWindowMethod(windowMethod); + } + }); + return; + } + callWindowMethod(windowMethod); + }; + element.removeEventListener(trigger, callback); + element.addEventListener(trigger, callback); + }); + } + function addWMLOpenBrowserListener() { + const elements = document.querySelectorAll("[wml-openurl]"); + elements.forEach(function(element) { + const url = element.getAttribute("wml-openurl"); + const confirm = element.getAttribute("wml-confirm"); + const trigger = element.getAttribute("wml-trigger") || "click"; + let callback = function() { + if (confirm) { + Question({ Title: "Confirm", Message: confirm, Buttons: [{ Label: "Yes" }, { Label: "No", IsDefault: true }] }).then(function(result) { + if (result !== "No") { + void wails.Browser.OpenURL(url); + } + }); + return; + } + void wails.Browser.OpenURL(url); + }; + element.removeEventListener(trigger, callback); + element.addEventListener(trigger, callback); + }); + } + function reloadWML() { + addWMLEventListeners(); + addWMLWindowListeners(); + addWMLOpenBrowserListener(); + } + + // desktop/invoke.js + var invoke = function(input) { + if (true) { + chrome.webview.postMessage(input); + } else { + webkit.messageHandlers.external.postMessage(input); + } + }; + + // desktop/flags.js + var flags = /* @__PURE__ */ new Map(); + function convertToMap(obj) { + const map = /* @__PURE__ */ new Map(); + for (const [key, value] of Object.entries(obj)) { + if (typeof value === "object" && value !== null) { + map.set(key, convertToMap(value)); + } else { + map.set(key, value); + } + } + return map; + } + fetch("/wails/flags").then((response) => { + response.json().then((data) => { + flags = convertToMap(data); + }); + }); + function getValueFromMap(keyString) { + const keys = keyString.split("."); + let value = flags; + for (const key of keys) { + if (value instanceof Map) { + value = value.get(key); + } else { + value = value[key]; + } + if (value === void 0) { + break; + } + } + return value; + } + function GetFlag(keyString) { + return getValueFromMap(keyString); + } + + // desktop/drag.js + var shouldDrag = false; + function dragTest(e) { + let val = window.getComputedStyle(e.target).getPropertyValue("--webkit-app-region"); + if (val) { + val = val.trim(); + } + if (val !== "drag") { + return false; + } + if (e.buttons !== 1) { + return false; + } + return e.detail === 1; + } + function setupDrag() { + window.addEventListener("mousedown", onMouseDown); + window.addEventListener("mousemove", onMouseMove); + window.addEventListener("mouseup", onMouseUp); + } + var resizeEdge = null; + var resizable = false; + function setResizable(value) { + resizable = value; + } + function testResize(e) { + if (resizeEdge) { + invoke("resize:" + resizeEdge); + return true; + } + return false; + } + function onMouseDown(e) { + if (true) { + if (testResize()) { + return; + } + } + if (dragTest(e)) { + if (e.offsetX > e.target.clientWidth || e.offsetY > e.target.clientHeight) { + return; + } + shouldDrag = true; + } else { + shouldDrag = false; + } + } + function onMouseUp(e) { + let mousePressed = e.buttons !== void 0 ? e.buttons : e.which; + if (mousePressed > 0) { + endDrag(); + } + } + function endDrag() { + document.body.style.cursor = "default"; + shouldDrag = false; + } + function setResize(cursor) { + document.documentElement.style.cursor = cursor || defaultCursor; + resizeEdge = cursor; + } + function onMouseMove(e) { + if (shouldDrag) { + shouldDrag = false; + let mousePressed = e.buttons !== void 0 ? e.buttons : e.which; + if (mousePressed > 0) { + invoke("drag"); + } + return; + } + if (true) { + if (resizable) { + handleResize(e); + } + } + } + var defaultCursor = "auto"; + function handleResize(e) { + let resizeHandleHeight = GetFlag("system.resizeHandleHeight") || 5; + let resizeHandleWidth = GetFlag("system.resizeHandleWidth") || 5; + let cornerExtra = GetFlag("resizeCornerExtra") || 10; + let rightBorder = window.outerWidth - e.clientX < resizeHandleWidth; + let leftBorder = e.clientX < resizeHandleWidth; + let topBorder = e.clientY < resizeHandleHeight; + let bottomBorder = window.outerHeight - e.clientY < resizeHandleHeight; + let rightCorner = window.outerWidth - e.clientX < resizeHandleWidth + cornerExtra; + let leftCorner = e.clientX < resizeHandleWidth + cornerExtra; + let topCorner = e.clientY < resizeHandleHeight + cornerExtra; + let bottomCorner = window.outerHeight - e.clientY < resizeHandleHeight + cornerExtra; + if (!leftBorder && !rightBorder && !topBorder && !bottomBorder && resizeEdge !== void 0) { + setResize(); + } else if (rightCorner && bottomCorner) + setResize("se-resize"); + else if (leftCorner && bottomCorner) + setResize("sw-resize"); + else if (leftCorner && topCorner) + setResize("nw-resize"); + else if (topCorner && rightCorner) + setResize("ne-resize"); + else if (leftBorder) + setResize("w-resize"); + else if (topBorder) + setResize("n-resize"); + else if (bottomBorder) + setResize("s-resize"); + else if (rightBorder) + setResize("e-resize"); + } + + // desktop/main.js + window.wails = { + ...newRuntime(null), + Capabilities: {}, + clientId + }; + fetch("/wails/capabilities").then((response) => { + response.json().then((data) => { + window.wails.Capabilities = data; + }); + }); + window._wails = { + dialogCallback, + dialogErrorCallback, + dispatchWailsEvent, + callCallback, + callErrorCallback, + endDrag, + setResizable + }; + function newRuntime(windowName) { + return { + Clipboard: { + ...clipboard_exports + }, + Application: { + ...application_exports, + GetWindowByName(windowName2) { + return newRuntime(windowName2); + } + }, + System: system_exports, + Screens: screens_exports, + Browser: browser_exports, + Call, + CallByID, + CallByName, + Plugin, + WML: { + Reload: reloadWML + }, + Dialog: { + Info, + Warning, + Error: Error2, + Question, + OpenFile, + SaveFile + }, + Events: { + Emit, + On, + Once, + OnMultiple, + Off, + OffAll + }, + Window: newWindow(windowName) + }; + } + if (true) { + console.log("Wails v3.0.0 Debug Mode Enabled"); + } + setupContextMenus(); + setupDrag(); + document.addEventListener("DOMContentLoaded", function() { + reloadWML(); + }); +})(); +//# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsiZGVza3RvcC9jbGlwYm9hcmQuanMiLCAibm9kZV9tb2R1bGVzL25hbm9pZC9ub24tc2VjdXJlL2luZGV4LmpzIiwgImRlc2t0b3AvcnVudGltZS5qcyIsICJkZXNrdG9wL2FwcGxpY2F0aW9uLmpzIiwgImRlc2t0b3Avc2NyZWVucy5qcyIsICJkZXNrdG9wL3N5c3RlbS5qcyIsICJkZXNrdG9wL2Jyb3dzZXIuanMiLCAiZGVza3RvcC9jYWxscy5qcyIsICJkZXNrdG9wL3dpbmRvdy5qcyIsICJkZXNrdG9wL2V2ZW50cy5qcyIsICJkZXNrdG9wL2RpYWxvZ3MuanMiLCAiZGVza3RvcC9jb250ZXh0bWVudS5qcyIsICJkZXNrdG9wL3dtbC5qcyIsICJkZXNrdG9wL2ludm9rZS5qcyIsICJkZXNrdG9wL2ZsYWdzLmpzIiwgImRlc2t0b3AvZHJhZy5qcyIsICJkZXNrdG9wL21haW4uanMiXSwKICAic291cmNlc0NvbnRlbnQiOiBbIi8qXHJcbiBfXHQgICBfX1x0ICBfIF9fXHJcbnwgfFx0IC8gL19fXyBfKF8pIC9fX19fXHJcbnwgfCAvfCAvIC8gX18gYC8gLyAvIF9fXy9cclxufCB8LyB8LyAvIC9fLyAvIC8gKF9fICApXHJcbnxfXy98X18vXFxfXyxfL18vXy9fX19fL1xyXG5UaGUgZWxlY3Ryb24gYWx0ZXJuYXRpdmUgZm9yIEdvXHJcbihjKSBMZWEgQW50aG9ueSAyMDE5LXByZXNlbnRcclxuKi9cclxuXHJcbi8qIGpzaGludCBlc3ZlcnNpb246IDkgKi9cclxuXHJcbmltcG9ydCB7bmV3UnVudGltZUNhbGxlcldpdGhJRCwgb2JqZWN0TmFtZXN9IGZyb20gXCIuL3J1bnRpbWVcIjtcclxuXHJcbmxldCBjYWxsID0gbmV3UnVudGltZUNhbGxlcldpdGhJRChvYmplY3ROYW1lcy5DbGlwYm9hcmQpO1xyXG5cclxubGV0IENsaXBib2FyZFNldFRleHQgPSAwO1xyXG5sZXQgQ2xpcGJvYXJkVGV4dCA9IDE7XHJcblxyXG4vKipcclxuICogU2V0IHRoZSBDbGlwYm9hcmQgdGV4dFxyXG4gKi9cclxuZXhwb3J0IGZ1bmN0aW9uIFNldFRleHQodGV4dCkge1xyXG4gICAgdm9pZCBjYWxsKENsaXBib2FyZFNldFRleHQsIHt0ZXh0fSk7XHJcbn1cclxuXHJcbi8qKlxyXG4gKiBHZXQgdGhlIENsaXBib2FyZCB0ZXh0XHJcbiAqIEByZXR1cm5zIHtQcm9taXNlPHN0cmluZz59XHJcbiAqL1xyXG5leHBvcnQgZnVuY3Rpb24gVGV4dCgpIHtcclxuICAgIHJldHVybiBjYWxsKENsaXBib2FyZFRleHQpO1xyXG59IiwgImxldCB1cmxBbHBoYWJldCA9XG4gICd1c2VhbmRvbS0yNlQxOTgzNDBQWDc1cHhKQUNLVkVSWU1JTkRCVVNIV09MRl9HUVpiZmdoamtscXZ3eXpyaWN0J1xuZXhwb3J0IGxldCBjdXN0b21BbHBoYWJldCA9IChhbHBoYWJldCwgZGVmYXVsdFNpemUgPSAyMSkgPT4ge1xuICByZXR1cm4gKHNpemUgPSBkZWZhdWx0U2l6ZSkgPT4ge1xuICAgIGxldCBpZCA9ICcnXG4gICAgbGV0IGkgPSBzaXplXG4gICAgd2hpbGUgKGktLSkge1xuICAgICAgaWQgKz0gYWxwaGFiZXRbKE1hdGgucmFuZG9tKCkgKiBhbHBoYWJldC5sZW5ndGgpIHwgMF1cbiAgICB9XG4gICAgcmV0dXJuIGlkXG4gIH1cbn1cbmV4cG9ydCBsZXQgbmFub2lkID0gKHNpemUgPSAyMSkgPT4ge1xuICBsZXQgaWQgPSAnJ1xuICBsZXQgaSA9IHNpemVcbiAgd2hpbGUgKGktLSkge1xuICAgIGlkICs9IHVybEFscGhhYmV0WyhNYXRoLnJhbmRvbSgpICogNjQpIHwgMF1cbiAgfVxuICByZXR1cm4gaWRcbn1cbiIsICIvKlxyXG4gXyAgICAgX18gICAgIF8gX19cclxufCB8ICAvIC9fX18gXyhfKSAvX19fX1xyXG58IHwgL3wgLyAvIF9fIGAvIC8gLyBfX18vXHJcbnwgfC8gfC8gLyAvXy8gLyAvIChfXyAgKVxyXG58X18vfF9fL1xcX18sXy9fL18vX19fXy9cclxuVGhlIGVsZWN0cm9uIGFsdGVybmF0aXZlIGZvciBHb1xyXG4oYykgTGVhIEFudGhvbnkgMjAxOS1wcmVzZW50XHJcbiovXHJcblxyXG4vKiBqc2hpbnQgZXN2ZXJzaW9uOiA5ICovXHJcbmltcG9ydCB7IG5hbm9pZCB9IGZyb20gJ25hbm9pZC9ub24tc2VjdXJlJztcclxuXHJcbmNvbnN0IHJ1bnRpbWVVUkwgPSB3aW5kb3cubG9jYXRpb24ub3JpZ2luICsgXCIvd2FpbHMvcnVudGltZVwiO1xyXG4vLyBPYmplY3QgTmFtZXNcclxuZXhwb3J0IGNvbnN0IG9iamVjdE5hbWVzID0ge1xyXG4gICAgQ2FsbDogMCxcclxuICAgIENsaXBib2FyZDogMSxcclxuICAgIEFwcGxpY2F0aW9uOiAyLFxyXG4gICAgRXZlbnRzOiAzLFxyXG4gICAgQ29udGV4dE1lbnU6IDQsXHJcbiAgICBEaWFsb2c6IDUsXHJcbiAgICBXaW5kb3c6IDYsXHJcbiAgICBTY3JlZW5zOiA3LFxyXG4gICAgU3lzdGVtOiA4LFxyXG4gICAgQnJvd3NlcjogOSxcclxufVxyXG5leHBvcnQgbGV0IGNsaWVudElkID0gbmFub2lkKCk7XHJcblxyXG5mdW5jdGlvbiBydW50aW1lQ2FsbChtZXRob2QsIHdpbmRvd05hbWUsIGFyZ3MpIHtcclxuICAgIGxldCB1cmwgPSBuZXcgVVJMKHJ1bnRpbWVVUkwpO1xyXG4gICAgaWYoIG1ldGhvZCApIHtcclxuICAgICAgICB1cmwuc2VhcmNoUGFyYW1zLmFwcGVuZChcIm1ldGhvZFwiLCBtZXRob2QpO1xyXG4gICAgfVxyXG4gICAgbGV0IGZldGNoT3B0aW9ucyA9IHtcclxuICAgICAgICBoZWFkZXJzOiB7fSxcclxuICAgIH07XHJcbiAgICBpZiAod2luZG93TmFtZSkge1xyXG4gICAgICAgIGZldGNoT3B0aW9ucy5oZWFkZXJzW1wieC13YWlscy13aW5kb3ctbmFtZVwiXSA9IHdpbmRvd05hbWU7XHJcbiAgICB9XHJcbiAgICBpZiAoYXJncykge1xyXG4gICAgICAgIHVybC5zZWFyY2hQYXJhbXMuYXBwZW5kKFwiYXJnc1wiLCBKU09OLnN0cmluZ2lmeShhcmdzKSk7XHJcbiAgICB9XHJcbiAgICBmZXRjaE9wdGlvbnMuaGVhZGVyc1tcIngtd2FpbHMtY2xpZW50LWlkXCJdID0gY2xpZW50SWQ7XHJcblxyXG4gICAgcmV0dXJuIG5ldyBQcm9taXNlKChyZXNvbHZlLCByZWplY3QpID0+IHtcclxuICAgICAgICBmZXRjaCh1cmwsIGZldGNoT3B0aW9ucylcclxuICAgICAgICAgICAgLnRoZW4ocmVzcG9uc2UgPT4ge1xyXG4gICAgICAgICAgICAgICAgaWYgKHJlc3BvbnNlLm9rKSB7XHJcbiAgICAgICAgICAgICAgICAgICAgLy8gY2hlY2sgY29udGVudCB0eXBlXHJcbiAgICAgICAgICAgICAgICAgICAgaWYgKHJlc3BvbnNlLmhlYWRlcnMuZ2V0KFwiQ29udGVudC1UeXBlXCIpICYmIHJlc3BvbnNlLmhlYWRlcnMuZ2V0KFwiQ29udGVudC1UeXBlXCIpLmluZGV4T2YoXCJhcHBsaWNhdGlvbi9qc29uXCIpICE9PSAtMSkge1xyXG4gICAgICAgICAgICAgICAgICAgICAgICByZXR1cm4gcmVzcG9uc2UuanNvbigpO1xyXG4gICAgICAgICAgICAgICAgICAgIH0gZWxzZSB7XHJcbiAgICAgICAgICAgICAgICAgICAgICAgIHJldHVybiByZXNwb25zZS50ZXh0KCk7XHJcbiAgICAgICAgICAgICAgICAgICAgfVxyXG4gICAgICAgICAgICAgICAgfVxyXG4gICAgICAgICAgICAgICAgcmVqZWN0KEVycm9yKHJlc3BvbnNlLnN0YXR1c1RleHQpKTtcclxuICAgICAgICAgICAgfSlcclxuICAgICAgICAgICAgLnRoZW4oZGF0YSA9PiByZXNvbHZlKGRhdGEpKVxyXG4gICAgICAgICAgICAuY2F0Y2goZXJyb3IgPT4gcmVqZWN0KGVycm9yKSk7XHJcbiAgICB9KTtcclxufVxyXG5cclxuZXhwb3J0IGZ1bmN0aW9uIG5ld1J1bnRpbWVDYWxsZXIob2JqZWN0LCB3aW5kb3dOYW1lKSB7XHJcbiAgICByZXR1cm4gZnVuY3Rpb24gKG1ldGhvZCwgYXJncz1udWxsKSB7XHJcbiAgICAgICAgcmV0dXJuIHJ1bnRpbWVDYWxsKG9iamVjdCArIFwiLlwiICsgbWV0aG9kLCB3aW5kb3dOYW1lLCBhcmdzKTtcclxuICAgIH07XHJcbn1cclxuXHJcbmZ1bmN0aW9uIHJ1bnRpbWVDYWxsV2l0aElEKG9iamVjdElELCBtZXRob2QsIHdpbmRvd05hbWUsIGFyZ3MpIHtcclxuICAgIGxldCB1cmwgPSBuZXcgVVJMKHJ1bnRpbWVVUkwpO1xyXG4gICAgdXJsLnNlYXJjaFBhcmFtcy5hcHBlbmQoXCJvYmplY3RcIiwgb2JqZWN0SUQpO1xyXG4gICAgdXJsLnNlYXJjaFBhcmFtcy5hcHBlbmQoXCJtZXRob2RcIiwgbWV0aG9kKTtcclxuICAgIGxldCBmZXRjaE9wdGlvbnMgPSB7XHJcbiAgICAgICAgaGVhZGVyczoge30sXHJcbiAgICB9O1xyXG4gICAgaWYgKHdpbmRvd05hbWUpIHtcclxuICAgICAgICBmZXRjaE9wdGlvbnMuaGVhZGVyc1tcIngtd2FpbHMtd2luZG93LW5hbWVcIl0gPSB3aW5kb3dOYW1lO1xyXG4gICAgfVxyXG4gICAgaWYgKGFyZ3MpIHtcclxuICAgICAgICB1cmwuc2VhcmNoUGFyYW1zLmFwcGVuZChcImFyZ3NcIiwgSlNPTi5zdHJpbmdpZnkoYXJncykpO1xyXG4gICAgfVxyXG4gICAgZmV0Y2hPcHRpb25zLmhlYWRlcnNbXCJ4LXdhaWxzLWNsaWVudC1pZFwiXSA9IGNsaWVudElkO1xyXG4gICAgcmV0dXJuIG5ldyBQcm9taXNlKChyZXNvbHZlLCByZWplY3QpID0+IHtcclxuICAgICAgICBmZXRjaCh1cmwsIGZldGNoT3B0aW9ucylcclxuICAgICAgICAgICAgLnRoZW4ocmVzcG9uc2UgPT4ge1xyXG4gICAgICAgICAgICAgICAgaWYgKHJlc3BvbnNlLm9rKSB7XHJcbiAgICAgICAgICAgICAgICAgICAgLy8gY2hlY2sgY29udGVudCB0eXBlXHJcbiAgICAgICAgICAgICAgICAgICAgaWYgKHJlc3BvbnNlLmhlYWRlcnMuZ2V0KFwiQ29udGVudC1UeXBlXCIpICYmIHJlc3BvbnNlLmhlYWRlcnMuZ2V0KFwiQ29udGVudC1UeXBlXCIpLmluZGV4T2YoXCJhcHBsaWNhdGlvbi9qc29uXCIpICE9PSAtMSkge1xyXG4gICAgICAgICAgICAgICAgICAgICAgICByZXR1cm4gcmVzcG9uc2UuanNvbigpO1xyXG4gICAgICAgICAgICAgICAgICAgIH0gZWxzZSB7XHJcbiAgICAgICAgICAgICAgICAgICAgICAgIHJldHVybiByZXNwb25zZS50ZXh0KCk7XHJcbiAgICAgICAgICAgICAgICAgICAgfVxyXG4gICAgICAgICAgICAgICAgfVxyXG4gICAgICAgICAgICAgICAgcmVqZWN0KEVycm9yKHJlc3BvbnNlLnN0YXR1c1RleHQpKTtcclxuICAgICAgICAgICAgfSlcclxuICAgICAgICAgICAgLnRoZW4oZGF0YSA9PiByZXNvbHZlKGRhdGEpKVxyXG4gICAgICAgICAgICAuY2F0Y2goZXJyb3IgPT4gcmVqZWN0KGVycm9yKSk7XHJcbiAgICB9KTtcclxufVxyXG5cclxuZXhwb3J0IGZ1bmN0aW9uIG5ld1J1bnRpbWVDYWxsZXJXaXRoSUQob2JqZWN0LCB3aW5kb3dOYW1lKSB7XHJcbiAgICByZXR1cm4gZnVuY3Rpb24gKG1ldGhvZCwgYXJncz1udWxsKSB7XHJcbiAgICAgICAgcmV0dXJuIHJ1bnRpbWVDYWxsV2l0aElEKG9iamVjdCwgbWV0aG9kLCB3aW5kb3dOYW1lLCBhcmdzKTtcclxuICAgIH07XHJcbn1cclxuIiwgIi8qXHJcbiBfXHQgICBfX1x0ICBfIF9fXHJcbnwgfFx0IC8gL19fXyBfKF8pIC9fX19fXHJcbnwgfCAvfCAvIC8gX18gYC8gLyAvIF9fXy9cclxufCB8LyB8LyAvIC9fLyAvIC8gKF9fICApXHJcbnxfXy98X18vXFxfXyxfL18vXy9fX19fL1xyXG5UaGUgZWxlY3Ryb24gYWx0ZXJuYXRpdmUgZm9yIEdvXHJcbihjKSBMZWEgQW50aG9ueSAyMDE5LXByZXNlbnRcclxuKi9cclxuXHJcbi8qIGpzaGludCBlc3ZlcnNpb246IDkgKi9cclxuXHJcbmltcG9ydCB7bmV3UnVudGltZUNhbGxlcldpdGhJRCwgb2JqZWN0TmFtZXN9IGZyb20gXCIuL3J1bnRpbWVcIjtcclxuXHJcbmxldCBjYWxsID0gbmV3UnVudGltZUNhbGxlcldpdGhJRChvYmplY3ROYW1lcy5BcHBsaWNhdGlvbik7XHJcblxyXG5sZXQgbWV0aG9kcyA9IHtcclxuICAgIEhpZGU6IDAsXHJcbiAgICBTaG93OiAxLFxyXG4gICAgUXVpdDogMixcclxufVxyXG5cclxuLyoqXHJcbiAqIEhpZGUgdGhlIGFwcGxpY2F0aW9uXHJcbiAqL1xyXG5leHBvcnQgZnVuY3Rpb24gSGlkZSgpIHtcclxuICAgIHZvaWQgY2FsbChtZXRob2RzLkhpZGUpO1xyXG59XHJcblxyXG4vKipcclxuICogU2hvdyB0aGUgYXBwbGljYXRpb25cclxuICovXHJcbmV4cG9ydCBmdW5jdGlvbiBTaG93KCkge1xyXG4gICAgdm9pZCBjYWxsKG1ldGhvZHMuU2hvdyk7XHJcbn1cclxuXHJcblxyXG4vKipcclxuICogUXVpdCB0aGUgYXBwbGljYXRpb25cclxuICovXHJcbmV4cG9ydCBmdW5jdGlvbiBRdWl0KCkge1xyXG4gICAgdm9pZCBjYWxsKG1ldGhvZHMuUXVpdCk7XHJcbn0iLCAiLypcclxuIF9cdCAgIF9fXHQgIF8gX19cclxufCB8XHQgLyAvX19fIF8oXykgL19fX19cclxufCB8IC98IC8gLyBfXyBgLyAvIC8gX19fL1xyXG58IHwvIHwvIC8gL18vIC8gLyAoX18gIClcclxufF9fL3xfXy9cXF9fLF8vXy9fL19fX18vXHJcblRoZSBlbGVjdHJvbiBhbHRlcm5hdGl2ZSBmb3IgR29cclxuKGMpIExlYSBBbnRob255IDIwMTktcHJlc2VudFxyXG4qL1xyXG5cclxuLyoganNoaW50IGVzdmVyc2lvbjogOSAqL1xyXG5cclxuLyoqXHJcbiAqIEB0eXBlZGVmIHtpbXBvcnQoXCIuL2FwaS90eXBlc1wiKS5TY3JlZW59IFNjcmVlblxyXG4gKi9cclxuXHJcbmltcG9ydCB7bmV3UnVudGltZUNhbGxlcldpdGhJRCwgb2JqZWN0TmFtZXN9IGZyb20gXCIuL3J1bnRpbWVcIjtcclxuXHJcbmxldCBjYWxsID0gbmV3UnVudGltZUNhbGxlcldpdGhJRChvYmplY3ROYW1lcy5TY3JlZW5zKTtcclxuXHJcbmxldCBTY3JlZW5zR2V0QWxsID0gMDtcclxubGV0IFNjcmVlbnNHZXRQcmltYXJ5ID0gMTtcclxubGV0IFNjcmVlbnNHZXRDdXJyZW50ID0gMjtcclxuXHJcbi8qKlxyXG4gKiBHZXRzIGFsbCBzY3JlZW5zLlxyXG4gKiBAcmV0dXJucyB7UHJvbWlzZTxTY3JlZW5bXT59XHJcbiAqL1xyXG5leHBvcnQgZnVuY3Rpb24gR2V0QWxsKCkge1xyXG4gICAgcmV0dXJuIGNhbGwoU2NyZWVuc0dldEFsbCk7XHJcbn1cclxuXHJcbi8qKlxyXG4gKiBHZXRzIHRoZSBwcmltYXJ5IHNjcmVlbi5cclxuICogQHJldHVybnMge1Byb21pc2U8U2NyZWVuPn1cclxuICovXHJcbmV4cG9ydCBmdW5jdGlvbiBHZXRQcmltYXJ5KCkge1xyXG4gICAgcmV0dXJuIGNhbGwoU2NyZWVuc0dldFByaW1hcnkpO1xyXG59XHJcblxyXG4vKipcclxuICogR2V0cyB0aGUgY3VycmVudCBhY3RpdmUgc2NyZWVuLlxyXG4gKiBAcmV0dXJucyB7UHJvbWlzZTxTY3JlZW4+fVxyXG4gKiBAY29uc3RydWN0b3JcclxuICovXHJcbmV4cG9ydCBmdW5jdGlvbiBHZXRDdXJyZW50KCkge1xyXG4gICAgcmV0dXJuIGNhbGwoU2NyZWVuc0dldEN1cnJlbnQpO1xyXG59IiwgIi8qXHJcbiBfXHQgICBfX1x0ICBfIF9fXHJcbnwgfFx0IC8gL19fXyBfKF8pIC9fX19fXHJcbnwgfCAvfCAvIC8gX18gYC8gLyAvIF9fXy9cclxufCB8LyB8LyAvIC9fLyAvIC8gKF9fICApXHJcbnxfXy98X18vXFxfXyxfL18vXy9fX19fL1xyXG5UaGUgZWxlY3Ryb24gYWx0ZXJuYXRpdmUgZm9yIEdvXHJcbihjKSBMZWEgQW50aG9ueSAyMDE5LXByZXNlbnRcclxuKi9cclxuXHJcbi8qIGpzaGludCBlc3ZlcnNpb246IDkgKi9cclxuXHJcbmltcG9ydCB7bmV3UnVudGltZUNhbGxlcldpdGhJRCwgb2JqZWN0TmFtZXN9IGZyb20gXCIuL3J1bnRpbWVcIjtcclxuXHJcbmxldCBjYWxsID0gbmV3UnVudGltZUNhbGxlcldpdGhJRChvYmplY3ROYW1lcy5TeXN0ZW0pO1xyXG5cclxubGV0IFN5c3RlbUlzRGFya01vZGUgPSAwO1xyXG5cclxuLyoqXHJcbiAqIERldGVybWluZXMgaWYgdGhlIHN5c3RlbSBpcyBjdXJyZW50bHkgdXNpbmcgZGFyayBtb2RlXHJcbiAqIEByZXR1cm5zIHtQcm9taXNlPGJvb2xlYW4+fVxyXG4gKi9cclxuZXhwb3J0IGZ1bmN0aW9uIElzRGFya01vZGUoKSB7XHJcbiAgICByZXR1cm4gY2FsbChTeXN0ZW1Jc0RhcmtNb2RlKTtcclxufSIsICIvKlxyXG4gX1x0ICAgX19cdCAgXyBfX1xyXG58IHxcdCAvIC9fX18gXyhfKSAvX19fX1xyXG58IHwgL3wgLyAvIF9fIGAvIC8gLyBfX18vXHJcbnwgfC8gfC8gLyAvXy8gLyAvIChfXyAgKVxyXG58X18vfF9fL1xcX18sXy9fL18vX19fXy9cclxuVGhlIGVsZWN0cm9uIGFsdGVybmF0aXZlIGZvciBHb1xyXG4oYykgTGVhIEFudGhvbnkgMjAxOS1wcmVzZW50XHJcbiovXHJcblxyXG4vKiBqc2hpbnQgZXN2ZXJzaW9uOiA5ICovXHJcblxyXG5pbXBvcnQge25ld1J1bnRpbWVDYWxsZXJXaXRoSUQsIG9iamVjdE5hbWVzfSBmcm9tIFwiLi9ydW50aW1lXCI7XHJcblxyXG5sZXQgY2FsbCA9IG5ld1J1bnRpbWVDYWxsZXJXaXRoSUQob2JqZWN0TmFtZXMuQnJvd3Nlcik7XHJcblxyXG5sZXQgQnJvd3Nlck9wZW5VUkwgPSAwO1xyXG5cclxuLyoqXHJcbiAqIE9wZW4gYSBicm93c2VyIHdpbmRvdyB0byB0aGUgZ2l2ZW4gVVJMXHJcbiAqIEBwYXJhbSB7c3RyaW5nfSB1cmwgLSBUaGUgVVJMIHRvIG9wZW5cclxuICovXHJcbmV4cG9ydCBmdW5jdGlvbiBPcGVuVVJMKHVybCkge1xyXG4gICAgdm9pZCBjYWxsKEJyb3dzZXJPcGVuVVJMLCB7dXJsfSk7XHJcbn1cclxuIiwgIi8qXHJcbiBfXHQgICBfX1x0ICBfIF9fXHJcbnwgfFx0IC8gL19fXyBfKF8pIC9fX19fXHJcbnwgfCAvfCAvIC8gX18gYC8gLyAvIF9fXy9cclxufCB8LyB8LyAvIC9fLyAvIC8gKF9fICApXHJcbnxfXy98X18vXFxfXyxfL18vXy9fX19fL1xyXG5UaGUgZWxlY3Ryb24gYWx0ZXJuYXRpdmUgZm9yIEdvXHJcbihjKSBMZWEgQW50aG9ueSAyMDE5LXByZXNlbnRcclxuKi9cclxuXHJcbi8qIGpzaGludCBlc3ZlcnNpb246IDkgKi9cclxuXHJcbmltcG9ydCB7bmV3UnVudGltZUNhbGxlcldpdGhJRCwgb2JqZWN0TmFtZXN9IGZyb20gXCIuL3J1bnRpbWVcIjtcclxuXHJcbmltcG9ydCB7IG5hbm9pZCB9IGZyb20gJ25hbm9pZC9ub24tc2VjdXJlJztcclxuXHJcbmxldCBjYWxsID0gbmV3UnVudGltZUNhbGxlcldpdGhJRChvYmplY3ROYW1lcy5DYWxsKTtcclxuXHJcbmxldCBDYWxsQmluZGluZyA9IDA7XHJcblxyXG5sZXQgY2FsbFJlc3BvbnNlcyA9IG5ldyBNYXAoKTtcclxuXHJcbmZ1bmN0aW9uIGdlbmVyYXRlSUQoKSB7XHJcbiAgICBsZXQgcmVzdWx0O1xyXG4gICAgZG8ge1xyXG4gICAgICAgIHJlc3VsdCA9IG5hbm9pZCgpO1xyXG4gICAgfSB3aGlsZSAoY2FsbFJlc3BvbnNlcy5oYXMocmVzdWx0KSk7XHJcbiAgICByZXR1cm4gcmVzdWx0O1xyXG59XHJcblxyXG5leHBvcnQgZnVuY3Rpb24gY2FsbENhbGxiYWNrKGlkLCBkYXRhLCBpc0pTT04pIHtcclxuICAgIGxldCBwID0gY2FsbFJlc3BvbnNlcy5nZXQoaWQpO1xyXG4gICAgaWYgKHApIHtcclxuICAgICAgICBpZiAoaXNKU09OKSB7XHJcbiAgICAgICAgICAgIHAucmVzb2x2ZShKU09OLnBhcnNlKGRhdGEpKTtcclxuICAgICAgICB9IGVsc2Uge1xyXG4gICAgICAgICAgICBwLnJlc29sdmUoZGF0YSk7XHJcbiAgICAgICAgfVxyXG4gICAgICAgIGNhbGxSZXNwb25zZXMuZGVsZXRlKGlkKTtcclxuICAgIH1cclxufVxyXG5cclxuZXhwb3J0IGZ1bmN0aW9uIGNhbGxFcnJvckNhbGxiYWNrKGlkLCBtZXNzYWdlKSB7XHJcbiAgICBsZXQgcCA9IGNhbGxSZXNwb25zZXMuZ2V0KGlkKTtcclxuICAgIGlmIChwKSB7XHJcbiAgICAgICAgcC5yZWplY3QobWVzc2FnZSk7XHJcbiAgICAgICAgY2FsbFJlc3BvbnNlcy5kZWxldGUoaWQpO1xyXG4gICAgfVxyXG59XHJcblxyXG5mdW5jdGlvbiBjYWxsQmluZGluZyh0eXBlLCBvcHRpb25zKSB7XHJcbiAgICByZXR1cm4gbmV3IFByb21pc2UoKHJlc29sdmUsIHJlamVjdCkgPT4ge1xyXG4gICAgICAgIGxldCBpZCA9IGdlbmVyYXRlSUQoKTtcclxuICAgICAgICBvcHRpb25zID0gb3B0aW9ucyB8fCB7fTtcclxuICAgICAgICBvcHRpb25zW1wiY2FsbC1pZFwiXSA9IGlkO1xyXG5cclxuICAgICAgICBjYWxsUmVzcG9uc2VzLnNldChpZCwge3Jlc29sdmUsIHJlamVjdH0pO1xyXG4gICAgICAgIGNhbGwodHlwZSwgb3B0aW9ucykuY2F0Y2goKGVycm9yKSA9PiB7XHJcbiAgICAgICAgICAgIHJlamVjdChlcnJvcik7XHJcbiAgICAgICAgICAgIGNhbGxSZXNwb25zZXMuZGVsZXRlKGlkKTtcclxuICAgICAgICB9KTtcclxuICAgIH0pO1xyXG59XHJcblxyXG5leHBvcnQgZnVuY3Rpb24gQ2FsbChvcHRpb25zKSB7XHJcbiAgICByZXR1cm4gY2FsbEJpbmRpbmcoQ2FsbEJpbmRpbmcsIG9wdGlvbnMpO1xyXG59XHJcblxyXG5leHBvcnQgZnVuY3Rpb24gQ2FsbEJ5TmFtZShuYW1lLCAuLi5hcmdzKSB7XHJcblxyXG4gICAgLy8gRW5zdXJlIGZpcnN0IGFyZ3VtZW50IGlzIGEgc3RyaW5nIGFuZCBoYXMgMiBkb3RzXHJcbiAgICBpZiAodHlwZW9mIG5hbWUgIT09IFwic3RyaW5nXCIgfHwgbmFtZS5zcGxpdChcIi5cIikubGVuZ3RoICE9PSAzKSB7XHJcbiAgICAgICAgdGhyb3cgbmV3IEVycm9yKFwiQ2FsbEJ5TmFtZSByZXF1aXJlcyBhIHN0cmluZyBpbiB0aGUgZm9ybWF0ICdwYWNrYWdlLnN0cnVjdC5tZXRob2QnXCIpO1xyXG4gICAgfVxyXG4gICAgLy8gU3BsaXQgaW5wdXRzXHJcbiAgICBsZXQgcGFydHMgPSBuYW1lLnNwbGl0KFwiLlwiKTtcclxuXHJcbiAgICByZXR1cm4gY2FsbEJpbmRpbmcoQ2FsbEJpbmRpbmcsIHtcclxuICAgICAgICBwYWNrYWdlTmFtZTogcGFydHNbMF0sXHJcbiAgICAgICAgc3RydWN0TmFtZTogcGFydHNbMV0sXHJcbiAgICAgICAgbWV0aG9kTmFtZTogcGFydHNbMl0sXHJcbiAgICAgICAgYXJnczogYXJncyxcclxuICAgIH0pO1xyXG59XHJcblxyXG5leHBvcnQgZnVuY3Rpb24gQ2FsbEJ5SUQobWV0aG9kSUQsIC4uLmFyZ3MpIHtcclxuICAgIHJldHVybiBjYWxsQmluZGluZyhDYWxsQmluZGluZywge1xyXG4gICAgICAgIG1ldGhvZElEOiBtZXRob2RJRCxcclxuICAgICAgICBhcmdzOiBhcmdzLFxyXG4gICAgfSk7XHJcbn1cclxuXHJcbi8qKlxyXG4gKiBDYWxsIGEgcGx1Z2luIG1ldGhvZFxyXG4gKiBAcGFyYW0ge3N0cmluZ30gcGx1Z2luTmFtZSAtIG5hbWUgb2YgdGhlIHBsdWdpblxyXG4gKiBAcGFyYW0ge3N0cmluZ30gbWV0aG9kTmFtZSAtIG5hbWUgb2YgdGhlIG1ldGhvZFxyXG4gKiBAcGFyYW0gey4uLmFueX0gYXJncyAtIGFyZ3VtZW50cyB0byBwYXNzIHRvIHRoZSBtZXRob2RcclxuICogQHJldHVybnMge1Byb21pc2U8YW55Pn0gLSBwcm9taXNlIHRoYXQgcmVzb2x2ZXMgd2l0aCB0aGUgcmVzdWx0XHJcbiAqL1xyXG5leHBvcnQgZnVuY3Rpb24gUGx1Z2luKHBsdWdpbk5hbWUsIG1ldGhvZE5hbWUsIC4uLmFyZ3MpIHtcclxuICAgIHJldHVybiBjYWxsQmluZGluZyhDYWxsQmluZGluZywge1xyXG4gICAgICAgIHBhY2thZ2VOYW1lOiBcIndhaWxzLXBsdWdpbnNcIixcclxuICAgICAgICBzdHJ1Y3ROYW1lOiBwbHVnaW5OYW1lLFxyXG4gICAgICAgIG1ldGhvZE5hbWU6IG1ldGhvZE5hbWUsXHJcbiAgICAgICAgYXJnczogYXJncyxcclxuICAgIH0pO1xyXG59IiwgIi8qXHJcbiBfXHQgICBfX1x0ICBfIF9fXHJcbnwgfFx0IC8gL19fXyBfKF8pIC9fX19fXHJcbnwgfCAvfCAvIC8gX18gYC8gLyAvIF9fXy9cclxufCB8LyB8LyAvIC9fLyAvIC8gKF9fICApXHJcbnxfXy98X18vXFxfXyxfL18vXy9fX19fL1xyXG5UaGUgZWxlY3Ryb24gYWx0ZXJuYXRpdmUgZm9yIEdvXHJcbihjKSBMZWEgQW50aG9ueSAyMDE5LXByZXNlbnRcclxuKi9cclxuXHJcbi8qIGpzaGludCBlc3ZlcnNpb246IDkgKi9cclxuXHJcbi8qKlxyXG4gKiBAdHlwZWRlZiB7aW1wb3J0KFwiLi4vYXBpL3R5cGVzXCIpLlNpemV9IFNpemVcclxuICogQHR5cGVkZWYge2ltcG9ydChcIi4uL2FwaS90eXBlc1wiKS5Qb3NpdGlvbn0gUG9zaXRpb25cclxuICogQHR5cGVkZWYge2ltcG9ydChcIi4uL2FwaS90eXBlc1wiKS5TY3JlZW59IFNjcmVlblxyXG4gKi9cclxuXHJcbmltcG9ydCB7bmV3UnVudGltZUNhbGxlcldpdGhJRCwgb2JqZWN0TmFtZXN9IGZyb20gXCIuL3J1bnRpbWVcIjtcclxuXHJcbmxldCBXaW5kb3dDZW50ZXIgPSAwO1xyXG5sZXQgV2luZG93U2V0VGl0bGUgPSAxO1xyXG5sZXQgV2luZG93RnVsbHNjcmVlbiA9IDI7XHJcbmxldCBXaW5kb3dVbkZ1bGxzY3JlZW4gPSAzO1xyXG5sZXQgV2luZG93U2V0U2l6ZSA9IDQ7XHJcbmxldCBXaW5kb3dTaXplID0gNTtcclxubGV0IFdpbmRvd1NldE1heFNpemUgPSA2O1xyXG5sZXQgV2luZG93U2V0TWluU2l6ZSA9IDc7XHJcbmxldCBXaW5kb3dTZXRBbHdheXNPblRvcCA9IDg7XHJcbmxldCBXaW5kb3dTZXRSZWxhdGl2ZVBvc2l0aW9uID0gOTtcclxubGV0IFdpbmRvd1JlbGF0aXZlUG9zaXRpb24gPSAxMDtcclxubGV0IFdpbmRvd1NjcmVlbiA9IDExO1xyXG5sZXQgV2luZG93SGlkZSA9IDEyO1xyXG5sZXQgV2luZG93TWF4aW1pc2UgPSAxMztcclxubGV0IFdpbmRvd1VuTWF4aW1pc2UgPSAxNDtcclxubGV0IFdpbmRvd1RvZ2dsZU1heGltaXNlID0gMTU7XHJcbmxldCBXaW5kb3dNaW5pbWlzZSA9IDE2O1xyXG5sZXQgV2luZG93VW5NaW5pbWlzZSA9IDE3O1xyXG5sZXQgV2luZG93UmVzdG9yZSA9IDE4O1xyXG5sZXQgV2luZG93U2hvdyA9IDE5O1xyXG5sZXQgV2luZG93Q2xvc2UgPSAyMDtcclxubGV0IFdpbmRvd1NldEJhY2tncm91bmRDb2xvdXIgPSAyMTtcclxubGV0IFdpbmRvd1NldFJlc2l6YWJsZSA9IDIyO1xyXG5sZXQgV2luZG93V2lkdGggPSAyMztcclxubGV0IFdpbmRvd0hlaWdodCA9IDI0O1xyXG5sZXQgV2luZG93Wm9vbUluID0gMjU7XHJcbmxldCBXaW5kb3dab29tT3V0ID0gMjY7XHJcbmxldCBXaW5kb3dab29tUmVzZXQgPSAyNztcclxubGV0IFdpbmRvd0dldFpvb21MZXZlbCA9IDI4O1xyXG5sZXQgV2luZG93U2V0Wm9vbUxldmVsID0gMjk7XHJcblxyXG5leHBvcnQgZnVuY3Rpb24gbmV3V2luZG93KHdpbmRvd05hbWUpIHtcclxuICAgIGxldCBjYWxsID0gbmV3UnVudGltZUNhbGxlcldpdGhJRChvYmplY3ROYW1lcy5XaW5kb3csIHdpbmRvd05hbWUpO1xyXG4gICAgcmV0dXJuIHtcclxuXHJcbiAgICAgICAgLyoqXHJcbiAgICAgICAgICogQ2VudGVycyB0aGUgd2luZG93LlxyXG4gICAgICAgICAqL1xyXG4gICAgICAgIENlbnRlcjogKCkgPT4gdm9pZCBjYWxsKFdpbmRvd0NlbnRlciksXHJcblxyXG4gICAgICAgIC8qKlxyXG4gICAgICAgICAqIFNldCB0aGUgd2luZG93IHRpdGxlLlxyXG4gICAgICAgICAqIEBwYXJhbSB0aXRsZVxyXG4gICAgICAgICAqL1xyXG4gICAgICAgIFNldFRpdGxlOiAodGl0bGUpID0+IHZvaWQgY2FsbChXaW5kb3dTZXRUaXRsZSwge3RpdGxlfSksXHJcblxyXG4gICAgICAgIC8qKlxyXG4gICAgICAgICAqIE1ha2VzIHRoZSB3aW5kb3cgZnVsbHNjcmVlbi5cclxuICAgICAgICAgKi9cclxuICAgICAgICBGdWxsc2NyZWVuOiAoKSA9PiB2b2lkIGNhbGwoV2luZG93RnVsbHNjcmVlbiksXHJcblxyXG4gICAgICAgIC8qKlxyXG4gICAgICAgICAqIFVuZnVsbHNjcmVlbiB0aGUgd2luZG93LlxyXG4gICAgICAgICAqL1xyXG4gICAgICAgIFVuRnVsbHNjcmVlbjogKCkgPT4gdm9pZCBjYWxsKFdpbmRvd1VuRnVsbHNjcmVlbiksXHJcblxyXG4gICAgICAgIC8qKlxyXG4gICAgICAgICAqIFNldCB0aGUgd2luZG93IHNpemUuXHJcbiAgICAgICAgICogQHBhcmFtIHtudW1iZXJ9IHdpZHRoIFRoZSB3aW5kb3cgd2lkdGhcclxuICAgICAgICAgKiBAcGFyYW0ge251bWJlcn0gaGVpZ2h0IFRoZSB3aW5kb3cgaGVpZ2h0XHJcbiAgICAgICAgICovXHJcbiAgICAgICAgU2V0U2l6ZTogKHdpZHRoLCBoZWlnaHQpID0+IGNhbGwoV2luZG93U2V0U2l6ZSwge3dpZHRoLGhlaWdodH0pLFxyXG5cclxuICAgICAgICAvKipcclxuICAgICAgICAgKiBHZXQgdGhlIHdpbmRvdyBzaXplLlxyXG4gICAgICAgICAqIEByZXR1cm5zIHtQcm9taXNlPFNpemU+fSBUaGUgd2luZG93IHNpemVcclxuICAgICAgICAgKi9cclxuICAgICAgICBTaXplOiAoKSA9PiB7IHJldHVybiBjYWxsKFdpbmRvd1NpemUpOyB9LFxyXG5cclxuICAgICAgICAvKipcclxuICAgICAgICAgKiBTZXQgdGhlIHdpbmRvdyBtYXhpbXVtIHNpemUuXHJcbiAgICAgICAgICogQHBhcmFtIHtudW1iZXJ9IHdpZHRoXHJcbiAgICAgICAgICogQHBhcmFtIHtudW1iZXJ9IGhlaWdodFxyXG4gICAgICAgICAqL1xyXG4gICAgICAgIFNldE1heFNpemU6ICh3aWR0aCwgaGVpZ2h0KSA9PiB2b2lkIGNhbGwoV2luZG93U2V0TWF4U2l6ZSwge3dpZHRoLGhlaWdodH0pLFxyXG5cclxuICAgICAgICAvKipcclxuICAgICAgICAgKiBTZXQgdGhlIHdpbmRvdyBtaW5pbXVtIHNpemUuXHJcbiAgICAgICAgICogQHBhcmFtIHtudW1iZXJ9IHdpZHRoXHJcbiAgICAgICAgICogQHBhcmFtIHtudW1iZXJ9IGhlaWdodFxyXG4gICAgICAgICAqL1xyXG4gICAgICAgIFNldE1pblNpemU6ICh3aWR0aCwgaGVpZ2h0KSA9PiB2b2lkIGNhbGwoV2luZG93U2V0TWluU2l6ZSwge3dpZHRoLGhlaWdodH0pLFxyXG5cclxuICAgICAgICAvKipcclxuICAgICAgICAgKiBTZXQgd2luZG93IHRvIGJlIGFsd2F5cyBvbiB0b3AuXHJcbiAgICAgICAgICogQHBhcmFtIHtib29sZWFufSBvblRvcCBXaGV0aGVyIHRoZSB3aW5kb3cgc2hvdWxkIGJlIGFsd2F5cyBvbiB0b3BcclxuICAgICAgICAgKi9cclxuICAgICAgICBTZXRBbHdheXNPblRvcDogKG9uVG9wKSA9PiB2b2lkIGNhbGwoV2luZG93U2V0QWx3YXlzT25Ub3AsIHthbHdheXNPblRvcDpvblRvcH0pLFxyXG5cclxuICAgICAgICAvKipcclxuICAgICAgICAgKiBTZXQgdGhlIHdpbmRvdyByZWxhdGl2ZSBwb3NpdGlvbi5cclxuICAgICAgICAgKiBAcGFyYW0ge251bWJlcn0geFxyXG4gICAgICAgICAqIEBwYXJhbSB7bnVtYmVyfSB5XHJcbiAgICAgICAgICovXHJcbiAgICAgICAgU2V0UmVsYXRpdmVQb3NpdGlvbjogKHgsIHkpID0+IGNhbGwoV2luZG93U2V0UmVsYXRpdmVQb3NpdGlvbiwge3gseX0pLFxyXG5cclxuICAgICAgICAvKipcclxuICAgICAgICAgKiBHZXQgdGhlIHdpbmRvdyBwb3NpdGlvbi5cclxuICAgICAgICAgKiBAcmV0dXJucyB7UHJvbWlzZTxQb3NpdGlvbj59IFRoZSB3aW5kb3cgcG9zaXRpb25cclxuICAgICAgICAgKi9cclxuICAgICAgICBSZWxhdGl2ZVBvc2l0aW9uOiAoKSA9PiB7IHJldHVybiBjYWxsKFdpbmRvd1JlbGF0aXZlUG9zaXRpb24pOyB9LFxyXG5cclxuICAgICAgICAvKipcclxuICAgICAgICAgKiBHZXQgdGhlIHNjcmVlbiB0aGUgd2luZG93IGlzIG9uLlxyXG4gICAgICAgICAqIEByZXR1cm5zIHtQcm9taXNlPFNjcmVlbj59XHJcbiAgICAgICAgICovXHJcbiAgICAgICAgU2NyZWVuOiAoKSA9PiB7IHJldHVybiBjYWxsKFdpbmRvd1NjcmVlbik7IH0sXHJcblxyXG4gICAgICAgIC8qKlxyXG4gICAgICAgICAqIEhpZGUgdGhlIHdpbmRvd1xyXG4gICAgICAgICAqL1xyXG4gICAgICAgIEhpZGU6ICgpID0+IHZvaWQgY2FsbChXaW5kb3dIaWRlKSxcclxuXHJcbiAgICAgICAgLyoqXHJcbiAgICAgICAgICogTWF4aW1pc2UgdGhlIHdpbmRvd1xyXG4gICAgICAgICAqL1xyXG4gICAgICAgIE1heGltaXNlOiAoKSA9PiB2b2lkIGNhbGwoV2luZG93TWF4aW1pc2UpLFxyXG5cclxuICAgICAgICAvKipcclxuICAgICAgICAgKiBTaG93IHRoZSB3aW5kb3dcclxuICAgICAgICAgKi9cclxuICAgICAgICBTaG93OiAoKSA9PiB2b2lkIGNhbGwoV2luZG93U2hvdyksXHJcblxyXG4gICAgICAgIC8qKlxyXG4gICAgICAgICAqIENsb3NlIHRoZSB3aW5kb3dcclxuICAgICAgICAgKi9cclxuICAgICAgICBDbG9zZTogKCkgPT4gdm9pZCBjYWxsKFdpbmRvd0Nsb3NlKSxcclxuXHJcbiAgICAgICAgLyoqXHJcbiAgICAgICAgICogVG9nZ2xlIHRoZSB3aW5kb3cgbWF4aW1pc2Ugc3RhdGVcclxuICAgICAgICAgKi9cclxuICAgICAgICBUb2dnbGVNYXhpbWlzZTogKCkgPT4gdm9pZCBjYWxsKFdpbmRvd1RvZ2dsZU1heGltaXNlKSxcclxuXHJcbiAgICAgICAgLyoqXHJcbiAgICAgICAgICogVW5tYXhpbWlzZSB0aGUgd2luZG93XHJcbiAgICAgICAgICovXHJcbiAgICAgICAgVW5NYXhpbWlzZTogKCkgPT4gdm9pZCBjYWxsKFdpbmRvd1VuTWF4aW1pc2UpLFxyXG5cclxuICAgICAgICAvKipcclxuICAgICAgICAgKiBNaW5pbWlzZSB0aGUgd2luZG93XHJcbiAgICAgICAgICovXHJcbiAgICAgICAgTWluaW1pc2U6ICgpID0+IHZvaWQgY2FsbChXaW5kb3dNaW5pbWlzZSksXHJcblxyXG4gICAgICAgIC8qKlxyXG4gICAgICAgICAqIFVubWluaW1pc2UgdGhlIHdpbmRvd1xyXG4gICAgICAgICAqL1xyXG4gICAgICAgIFVuTWluaW1pc2U6ICgpID0+IHZvaWQgY2FsbChXaW5kb3dVbk1pbmltaXNlKSxcclxuXHJcbiAgICAgICAgLyoqXHJcbiAgICAgICAgICogUmVzdG9yZSB0aGUgd2luZG93XHJcbiAgICAgICAgICovXHJcbiAgICAgICAgUmVzdG9yZTogKCkgPT4gdm9pZCBjYWxsKFdpbmRvd1Jlc3RvcmUpLFxyXG5cclxuICAgICAgICAvKipcclxuICAgICAgICAgKiBTZXQgdGhlIGJhY2tncm91bmQgY29sb3VyIG9mIHRoZSB3aW5kb3cuXHJcbiAgICAgICAgICogQHBhcmFtIHtudW1iZXJ9IHIgLSBBIHZhbHVlIGJldHdlZW4gMCBhbmQgMjU1XHJcbiAgICAgICAgICogQHBhcmFtIHtudW1iZXJ9IGcgLSBBIHZhbHVlIGJldHdlZW4gMCBhbmQgMjU1XHJcbiAgICAgICAgICogQHBhcmFtIHtudW1iZXJ9IGIgLSBBIHZhbHVlIGJldHdlZW4gMCBhbmQgMjU1XHJcbiAgICAgICAgICogQHBhcmFtIHtudW1iZXJ9IGEgLSBBIHZhbHVlIGJldHdlZW4gMCBhbmQgMjU1XHJcbiAgICAgICAgICovXHJcbiAgICAgICAgU2V0QmFja2dyb3VuZENvbG91cjogKHIsIGcsIGIsIGEpID0+IHZvaWQgY2FsbChXaW5kb3dTZXRCYWNrZ3JvdW5kQ29sb3VyLCB7ciwgZywgYiwgYX0pLFxyXG5cclxuICAgICAgICAvKipcclxuICAgICAgICAgKiBTZXQgd2hldGhlciB0aGUgd2luZG93IGNhbiBiZSByZXNpemVkIG9yIG5vdFxyXG4gICAgICAgICAqIEBwYXJhbSB7Ym9vbGVhbn0gcmVzaXphYmxlXHJcbiAgICAgICAgICovXHJcbiAgICAgICAgU2V0UmVzaXphYmxlOiAocmVzaXphYmxlKSA9PiB2b2lkIGNhbGwoV2luZG93U2V0UmVzaXphYmxlLCB7cmVzaXphYmxlfSksXHJcblxyXG4gICAgICAgIC8qKlxyXG4gICAgICAgICAqIEdldCB0aGUgd2luZG93IHdpZHRoXHJcbiAgICAgICAgICogQHJldHVybnMge1Byb21pc2U8bnVtYmVyPn1cclxuICAgICAgICAgKi9cclxuICAgICAgICBXaWR0aDogKCkgPT4geyByZXR1cm4gY2FsbChXaW5kb3dXaWR0aCk7IH0sXHJcblxyXG4gICAgICAgIC8qKlxyXG4gICAgICAgICAqIEdldCB0aGUgd2luZG93IGhlaWdodFxyXG4gICAgICAgICAqIEByZXR1cm5zIHtQcm9taXNlPG51bWJlcj59XHJcbiAgICAgICAgICovXHJcbiAgICAgICAgSGVpZ2h0OiAoKSA9PiB7IHJldHVybiBjYWxsKFdpbmRvd0hlaWdodCk7IH0sXHJcblxyXG4gICAgICAgIC8qKlxyXG4gICAgICAgICAqIFpvb20gaW4gdGhlIHdpbmRvd1xyXG4gICAgICAgICAqL1xyXG4gICAgICAgIFpvb21JbjogKCkgPT4gdm9pZCBjYWxsKFdpbmRvd1pvb21JbiksXHJcblxyXG4gICAgICAgIC8qKlxyXG4gICAgICAgICAqIFpvb20gb3V0IHRoZSB3aW5kb3dcclxuICAgICAgICAgKi9cclxuICAgICAgICBab29tT3V0OiAoKSA9PiB2b2lkIGNhbGwoV2luZG93Wm9vbU91dCksXHJcblxyXG4gICAgICAgIC8qKlxyXG4gICAgICAgICAqIFJlc2V0IHRoZSB3aW5kb3cgem9vbVxyXG4gICAgICAgICAqL1xyXG4gICAgICAgIFpvb21SZXNldDogKCkgPT4gdm9pZCBjYWxsKFdpbmRvd1pvb21SZXNldCksXHJcblxyXG4gICAgICAgIC8qKlxyXG4gICAgICAgICAqIEdldCB0aGUgd2luZG93IHpvb21cclxuICAgICAgICAgKiBAcmV0dXJucyB7UHJvbWlzZTxudW1iZXI+fVxyXG4gICAgICAgICAqL1xyXG4gICAgICAgIEdldFpvb21MZXZlbDogKCkgPT4geyByZXR1cm4gY2FsbChXaW5kb3dHZXRab29tTGV2ZWwpOyB9LFxyXG5cclxuICAgICAgICAvKipcclxuICAgICAgICAgKiBTZXQgdGhlIHdpbmRvdyB6b29tIGxldmVsXHJcbiAgICAgICAgICogQHBhcmFtIHtudW1iZXJ9IHpvb21MZXZlbFxyXG4gICAgICAgICAqL1xyXG4gICAgICAgIFNldFpvb21MZXZlbDogKHpvb21MZXZlbCkgPT4gdm9pZCBjYWxsKFdpbmRvd1NldFpvb21MZXZlbCwge3pvb21MZXZlbH0pLFxyXG4gICAgfTtcclxufVxyXG4iLCAiLypcclxuIF9cdCAgIF9fXHQgIF8gX19cclxufCB8XHQgLyAvX19fIF8oXykgL19fX19cclxufCB8IC98IC8gLyBfXyBgLyAvIC8gX19fL1xyXG58IHwvIHwvIC8gL18vIC8gLyAoX18gIClcclxufF9fL3xfXy9cXF9fLF8vXy9fL19fX18vXHJcblRoZSBlbGVjdHJvbiBhbHRlcm5hdGl2ZSBmb3IgR29cclxuKGMpIExlYSBBbnRob255IDIwMTktcHJlc2VudFxyXG4qL1xyXG5cclxuLyoganNoaW50IGVzdmVyc2lvbjogOSAqL1xyXG5cclxuLyoqXHJcbiAqIEB0eXBlZGVmIHtpbXBvcnQoXCIuL2FwaS90eXBlc1wiKS5XYWlsc0V2ZW50fSBXYWlsc0V2ZW50XHJcbiAqL1xyXG5cclxuaW1wb3J0IHtuZXdSdW50aW1lQ2FsbGVyV2l0aElELCBvYmplY3ROYW1lc30gZnJvbSBcIi4vcnVudGltZVwiO1xyXG5cclxubGV0IGNhbGwgPSBuZXdSdW50aW1lQ2FsbGVyV2l0aElEKG9iamVjdE5hbWVzLkV2ZW50cyk7XHJcbmxldCBFdmVudEVtaXQgPSAwO1xyXG5cclxuLyoqXHJcbiAqIFRoZSBMaXN0ZW5lciBjbGFzcyBkZWZpbmVzIGEgbGlzdGVuZXIhIDotKVxyXG4gKlxyXG4gKiBAY2xhc3MgTGlzdGVuZXJcclxuICovXHJcbmNsYXNzIExpc3RlbmVyIHtcclxuICAgIC8qKlxyXG4gICAgICogQ3JlYXRlcyBhbiBpbnN0YW5jZSBvZiBMaXN0ZW5lci5cclxuICAgICAqIEBwYXJhbSB7c3RyaW5nfSBldmVudE5hbWVcclxuICAgICAqIEBwYXJhbSB7ZnVuY3Rpb259IGNhbGxiYWNrXHJcbiAgICAgKiBAcGFyYW0ge251bWJlcn0gbWF4Q2FsbGJhY2tzXHJcbiAgICAgKiBAbWVtYmVyb2YgTGlzdGVuZXJcclxuICAgICAqL1xyXG4gICAgY29uc3RydWN0b3IoZXZlbnROYW1lLCBjYWxsYmFjaywgbWF4Q2FsbGJhY2tzKSB7XHJcbiAgICAgICAgdGhpcy5ldmVudE5hbWUgPSBldmVudE5hbWU7XHJcbiAgICAgICAgLy8gRGVmYXVsdCBvZiAtMSBtZWFucyBpbmZpbml0ZVxyXG4gICAgICAgIHRoaXMubWF4Q2FsbGJhY2tzID0gbWF4Q2FsbGJhY2tzIHx8IC0xO1xyXG4gICAgICAgIC8vIENhbGxiYWNrIGludm9rZXMgdGhlIGNhbGxiYWNrIHdpdGggdGhlIGdpdmVuIGRhdGFcclxuICAgICAgICAvLyBSZXR1cm5zIHRydWUgaWYgdGhpcyBsaXN0ZW5lciBzaG91bGQgYmUgZGVzdHJveWVkXHJcbiAgICAgICAgdGhpcy5DYWxsYmFjayA9IChkYXRhKSA9PiB7XHJcbiAgICAgICAgICAgIGNhbGxiYWNrKGRhdGEpO1xyXG4gICAgICAgICAgICAvLyBJZiBtYXhDYWxsYmFja3MgaXMgaW5maW5pdGUsIHJldHVybiBmYWxzZSAoZG8gbm90IGRlc3Ryb3kpXHJcbiAgICAgICAgICAgIGlmICh0aGlzLm1heENhbGxiYWNrcyA9PT0gLTEpIHtcclxuICAgICAgICAgICAgICAgIHJldHVybiBmYWxzZTtcclxuICAgICAgICAgICAgfVxyXG4gICAgICAgICAgICAvLyBEZWNyZW1lbnQgbWF4Q2FsbGJhY2tzLiBSZXR1cm4gdHJ1ZSBpZiBub3cgMCwgb3RoZXJ3aXNlIGZhbHNlXHJcbiAgICAgICAgICAgIHRoaXMubWF4Q2FsbGJhY2tzIC09IDE7XHJcbiAgICAgICAgICAgIHJldHVybiB0aGlzLm1heENhbGxiYWNrcyA9PT0gMDtcclxuICAgICAgICB9O1xyXG4gICAgfVxyXG59XHJcblxyXG5cclxuLyoqXHJcbiAqIFdhaWxzRXZlbnQgZGVmaW5lcyBhIGN1c3RvbSBldmVudC4gSXQgaXMgcGFzc2VkIHRvIGV2ZW50IGxpc3RlbmVycy5cclxuICpcclxuICogQGNsYXNzIFdhaWxzRXZlbnRcclxuICogQHByb3BlcnR5IHtzdHJpbmd9IG5hbWUgLSBOYW1lIG9mIHRoZSBldmVudFxyXG4gKiBAcHJvcGVydHkge2FueX0gZGF0YSAtIERhdGEgYXNzb2NpYXRlZCB3aXRoIHRoZSBldmVudFxyXG4gKi9cclxuZXhwb3J0IGNsYXNzIFdhaWxzRXZlbnQge1xyXG4gICAgLyoqXHJcbiAgICAgKiBDcmVhdGVzIGFuIGluc3RhbmNlIG9mIFdhaWxzRXZlbnQuXHJcbiAgICAgKiBAcGFyYW0ge3N0cmluZ30gbmFtZSAtIE5hbWUgb2YgdGhlIGV2ZW50XHJcbiAgICAgKiBAcGFyYW0ge2FueT1udWxsfSBkYXRhIC0gRGF0YSBhc3NvY2lhdGVkIHdpdGggdGhlIGV2ZW50XHJcbiAgICAgKiBAbWVtYmVyb2YgV2FpbHNFdmVudFxyXG4gICAgICovXHJcbiAgICBjb25zdHJ1Y3RvcihuYW1lLCBkYXRhID0gbnVsbCkge1xyXG4gICAgICAgIHRoaXMubmFtZSA9IG5hbWU7XHJcbiAgICAgICAgdGhpcy5kYXRhID0gZGF0YTtcclxuICAgIH1cclxufVxyXG5cclxuZXhwb3J0IGNvbnN0IGV2ZW50TGlzdGVuZXJzID0gbmV3IE1hcCgpO1xyXG5cclxuLyoqXHJcbiAqIFJlZ2lzdGVycyBhbiBldmVudCBsaXN0ZW5lciB0aGF0IHdpbGwgYmUgaW52b2tlZCBgbWF4Q2FsbGJhY2tzYCB0aW1lcyBiZWZvcmUgYmVpbmcgZGVzdHJveWVkXHJcbiAqXHJcbiAqIEBleHBvcnRcclxuICogQHBhcmFtIHtzdHJpbmd9IGV2ZW50TmFtZVxyXG4gKiBAcGFyYW0ge2Z1bmN0aW9uKFdhaWxzRXZlbnQpOiB2b2lkfSBjYWxsYmFja1xyXG4gKiBAcGFyYW0ge251bWJlcn0gbWF4Q2FsbGJhY2tzXHJcbiAqIEByZXR1cm5zIHtmdW5jdGlvbn0gQSBmdW5jdGlvbiB0byBjYW5jZWwgdGhlIGxpc3RlbmVyXHJcbiAqL1xyXG5leHBvcnQgZnVuY3Rpb24gT25NdWx0aXBsZShldmVudE5hbWUsIGNhbGxiYWNrLCBtYXhDYWxsYmFja3MpIHtcclxuICAgIGxldCBsaXN0ZW5lcnMgPSBldmVudExpc3RlbmVycy5nZXQoZXZlbnROYW1lKSB8fCBbXTtcclxuICAgIGNvbnN0IHRoaXNMaXN0ZW5lciA9IG5ldyBMaXN0ZW5lcihldmVudE5hbWUsIGNhbGxiYWNrLCBtYXhDYWxsYmFja3MpO1xyXG4gICAgbGlzdGVuZXJzLnB1c2godGhpc0xpc3RlbmVyKTtcclxuICAgIGV2ZW50TGlzdGVuZXJzLnNldChldmVudE5hbWUsIGxpc3RlbmVycyk7XHJcbiAgICByZXR1cm4gKCkgPT4gbGlzdGVuZXJPZmYodGhpc0xpc3RlbmVyKTtcclxufVxyXG5cclxuLyoqXHJcbiAqIFJlZ2lzdGVycyBhbiBldmVudCBsaXN0ZW5lciB0aGF0IHdpbGwgYmUgaW52b2tlZCBldmVyeSB0aW1lIHRoZSBldmVudCBpcyBlbWl0dGVkXHJcbiAqXHJcbiAqIEBleHBvcnRcclxuICogQHBhcmFtIHtzdHJpbmd9IGV2ZW50TmFtZVxyXG4gKiBAcGFyYW0ge2Z1bmN0aW9uKFdhaWxzRXZlbnQpOiB2b2lkfSBjYWxsYmFja1xyXG4gKiBAcmV0dXJucyB7ZnVuY3Rpb259IEEgZnVuY3Rpb24gdG8gY2FuY2VsIHRoZSBsaXN0ZW5lclxyXG4gKi9cclxuZXhwb3J0IGZ1bmN0aW9uIE9uKGV2ZW50TmFtZSwgY2FsbGJhY2spIHtcclxuICAgIHJldHVybiBPbk11bHRpcGxlKGV2ZW50TmFtZSwgY2FsbGJhY2ssIC0xKTtcclxufVxyXG5cclxuLyoqXHJcbiAqIFJlZ2lzdGVycyBhbiBldmVudCBsaXN0ZW5lciB0aGF0IHdpbGwgYmUgaW52b2tlZCBvbmNlIHRoZW4gZGVzdHJveWVkXHJcbiAqXHJcbiAqIEBleHBvcnRcclxuICogQHBhcmFtIHtzdHJpbmd9IGV2ZW50TmFtZVxyXG4gKiBAcGFyYW0ge2Z1bmN0aW9uKFdhaWxzRXZlbnQpOiB2b2lkfSBjYWxsYmFja1xyXG4gQHJldHVybnMge2Z1bmN0aW9ufSBBIGZ1bmN0aW9uIHRvIGNhbmNlbCB0aGUgbGlzdGVuZXJcclxuICovXHJcbmV4cG9ydCBmdW5jdGlvbiBPbmNlKGV2ZW50TmFtZSwgY2FsbGJhY2spIHtcclxuICAgIHJldHVybiBPbk11bHRpcGxlKGV2ZW50TmFtZSwgY2FsbGJhY2ssIDEpO1xyXG59XHJcblxyXG4vKipcclxuICogbGlzdGVuZXJPZmYgdW5yZWdpc3RlcnMgYSBsaXN0ZW5lciBwcmV2aW91c2x5IHJlZ2lzdGVyZWQgd2l0aCBPblxyXG4gKlxyXG4gKiBAcGFyYW0ge0xpc3RlbmVyfSBsaXN0ZW5lclxyXG4gKi9cclxuZnVuY3Rpb24gbGlzdGVuZXJPZmYobGlzdGVuZXIpIHtcclxuICAgIGNvbnN0IGV2ZW50TmFtZSA9IGxpc3RlbmVyLmV2ZW50TmFtZTtcclxuICAgIC8vIFJlbW92ZSBsb2NhbCBsaXN0ZW5lclxyXG4gICAgbGV0IGxpc3RlbmVycyA9IGV2ZW50TGlzdGVuZXJzLmdldChldmVudE5hbWUpLmZpbHRlcihsID0+IGwgIT09IGxpc3RlbmVyKTtcclxuICAgIGlmIChsaXN0ZW5lcnMubGVuZ3RoID09PSAwKSB7XHJcbiAgICAgICAgZXZlbnRMaXN0ZW5lcnMuZGVsZXRlKGV2ZW50TmFtZSk7XHJcbiAgICB9IGVsc2Uge1xyXG4gICAgICAgIGV2ZW50TGlzdGVuZXJzLnNldChldmVudE5hbWUsIGxpc3RlbmVycyk7XHJcbiAgICB9XHJcbn1cclxuXHJcbi8qKlxyXG4gKiBkaXNwYXRjaGVzIGFuIGV2ZW50IHRvIGFsbCBsaXN0ZW5lcnNcclxuICpcclxuICogQGV4cG9ydFxyXG4gKiBAcGFyYW0ge1dhaWxzRXZlbnR9IGV2ZW50XHJcbiAqL1xyXG5leHBvcnQgZnVuY3Rpb24gZGlzcGF0Y2hXYWlsc0V2ZW50KGV2ZW50KSB7XHJcbiAgICBsZXQgbGlzdGVuZXJzID0gZXZlbnRMaXN0ZW5lcnMuZ2V0KGV2ZW50Lm5hbWUpO1xyXG4gICAgaWYgKGxpc3RlbmVycykge1xyXG4gICAgICAgIC8vIGl0ZXJhdGUgbGlzdGVuZXJzIGFuZCBjYWxsIGNhbGxiYWNrLiBJZiBjYWxsYmFjayByZXR1cm5zIHRydWUsIHJlbW92ZSBsaXN0ZW5lclxyXG4gICAgICAgIGxldCB0b1JlbW92ZSA9IFtdO1xyXG4gICAgICAgIGxpc3RlbmVycy5mb3JFYWNoKGxpc3RlbmVyID0+IHtcclxuICAgICAgICAgICAgbGV0IHJlbW92ZSA9IGxpc3RlbmVyLkNhbGxiYWNrKGV2ZW50KTtcclxuICAgICAgICAgICAgaWYgKHJlbW92ZSkge1xyXG4gICAgICAgICAgICAgICAgdG9SZW1vdmUucHVzaChsaXN0ZW5lcik7XHJcbiAgICAgICAgICAgIH1cclxuICAgICAgICB9KTtcclxuICAgICAgICAvLyByZW1vdmUgbGlzdGVuZXJzXHJcbiAgICAgICAgaWYgKHRvUmVtb3ZlLmxlbmd0aCA+IDApIHtcclxuICAgICAgICAgICAgbGlzdGVuZXJzID0gbGlzdGVuZXJzLmZpbHRlcihsID0+ICF0b1JlbW92ZS5pbmNsdWRlcyhsKSk7XHJcbiAgICAgICAgICAgIGlmIChsaXN0ZW5lcnMubGVuZ3RoID09PSAwKSB7XHJcbiAgICAgICAgICAgICAgICBldmVudExpc3RlbmVycy5kZWxldGUoZXZlbnQubmFtZSk7XHJcbiAgICAgICAgICAgIH0gZWxzZSB7XHJcbiAgICAgICAgICAgICAgICBldmVudExpc3RlbmVycy5zZXQoZXZlbnQubmFtZSwgbGlzdGVuZXJzKTtcclxuICAgICAgICAgICAgfVxyXG4gICAgICAgIH1cclxuICAgIH1cclxufVxyXG5cclxuLyoqXHJcbiAqIE9mZiB1bnJlZ2lzdGVycyBhIGxpc3RlbmVyIHByZXZpb3VzbHkgcmVnaXN0ZXJlZCB3aXRoIE9uLFxyXG4gKiBvcHRpb25hbGx5IG11bHRpcGxlIGxpc3RlbmVycyBjYW4gYmUgdW5yZWdpc3RlcmVkIHZpYSBgYWRkaXRpb25hbEV2ZW50TmFtZXNgXHJcbiAqXHJcbiBbdjMgQ0hBTkdFXSBPZmYgb25seSB1bnJlZ2lzdGVycyBsaXN0ZW5lcnMgd2l0aGluIHRoZSBjdXJyZW50IHdpbmRvd1xyXG4gKlxyXG4gKiBAcGFyYW0ge3N0cmluZ30gZXZlbnROYW1lXHJcbiAqIEBwYXJhbSAgey4uLnN0cmluZ30gYWRkaXRpb25hbEV2ZW50TmFtZXNcclxuICovXHJcbmV4cG9ydCBmdW5jdGlvbiBPZmYoZXZlbnROYW1lLCAuLi5hZGRpdGlvbmFsRXZlbnROYW1lcykge1xyXG4gICAgbGV0IGV2ZW50c1RvUmVtb3ZlID0gW2V2ZW50TmFtZSwgLi4uYWRkaXRpb25hbEV2ZW50TmFtZXNdO1xyXG4gICAgZXZlbnRzVG9SZW1vdmUuZm9yRWFjaChldmVudE5hbWUgPT4ge1xyXG4gICAgICAgIGV2ZW50TGlzdGVuZXJzLmRlbGV0ZShldmVudE5hbWUpO1xyXG4gICAgfSk7XHJcbn1cclxuXHJcbi8qKlxyXG4gKiBPZmZBbGwgdW5yZWdpc3RlcnMgYWxsIGxpc3RlbmVyc1xyXG4gKiBbdjMgQ0hBTkdFXSBPZmZBbGwgb25seSB1bnJlZ2lzdGVycyBsaXN0ZW5lcnMgd2l0aGluIHRoZSBjdXJyZW50IHdpbmRvd1xyXG4gKlxyXG4gKi9cclxuZXhwb3J0IGZ1bmN0aW9uIE9mZkFsbCgpIHtcclxuICAgIGV2ZW50TGlzdGVuZXJzLmNsZWFyKCk7XHJcbn1cclxuXHJcbi8qKlxyXG4gKiBFbWl0IGFuIGV2ZW50XHJcbiAqIEBwYXJhbSB7V2FpbHNFdmVudH0gZXZlbnQgVGhlIGV2ZW50IHRvIGVtaXRcclxuICovXHJcbmV4cG9ydCBmdW5jdGlvbiBFbWl0KGV2ZW50KSB7XHJcbiAgICB2b2lkIGNhbGwoRXZlbnRFbWl0LCBldmVudCk7XHJcbn0iLCAiLypcclxuIF9cdCAgIF9fXHQgIF8gX19cclxufCB8XHQgLyAvX19fIF8oXykgL19fX19cclxufCB8IC98IC8gLyBfXyBgLyAvIC8gX19fL1xyXG58IHwvIHwvIC8gL18vIC8gLyAoX18gIClcclxufF9fL3xfXy9cXF9fLF8vXy9fL19fX18vXHJcblRoZSBlbGVjdHJvbiBhbHRlcm5hdGl2ZSBmb3IgR29cclxuKGMpIExlYSBBbnRob255IDIwMTktcHJlc2VudFxyXG4qL1xyXG5cclxuLyoganNoaW50IGVzdmVyc2lvbjogOSAqL1xyXG5cclxuLyoqXHJcbiAqIEB0eXBlZGVmIHtpbXBvcnQoXCIuL2FwaS90eXBlc1wiKS5NZXNzYWdlRGlhbG9nT3B0aW9uc30gTWVzc2FnZURpYWxvZ09wdGlvbnNcclxuICogQHR5cGVkZWYge2ltcG9ydChcIi4vYXBpL3R5cGVzXCIpLk9wZW5EaWFsb2dPcHRpb25zfSBPcGVuRGlhbG9nT3B0aW9uc1xyXG4gKiBAdHlwZWRlZiB7aW1wb3J0KFwiLi9hcGkvdHlwZXNcIikuU2F2ZURpYWxvZ09wdGlvbnN9IFNhdmVEaWFsb2dPcHRpb25zXHJcbiAqL1xyXG5cclxuaW1wb3J0IHtuZXdSdW50aW1lQ2FsbGVyV2l0aElELCBvYmplY3ROYW1lc30gZnJvbSBcIi4vcnVudGltZVwiO1xyXG5cclxuaW1wb3J0IHsgbmFub2lkIH0gZnJvbSAnbmFub2lkL25vbi1zZWN1cmUnO1xyXG5cclxubGV0IGNhbGwgPSBuZXdSdW50aW1lQ2FsbGVyV2l0aElEKG9iamVjdE5hbWVzLkRpYWxvZyk7XHJcblxyXG5sZXQgRGlhbG9nSW5mbyA9IDA7XHJcbmxldCBEaWFsb2dXYXJuaW5nID0gMTtcclxubGV0IERpYWxvZ0Vycm9yID0gMjtcclxubGV0IERpYWxvZ1F1ZXN0aW9uID0gMztcclxubGV0IERpYWxvZ09wZW5GaWxlID0gNDtcclxubGV0IERpYWxvZ1NhdmVGaWxlID0gNTtcclxuXHJcblxyXG5sZXQgZGlhbG9nUmVzcG9uc2VzID0gbmV3IE1hcCgpO1xyXG5cclxuZnVuY3Rpb24gZ2VuZXJhdGVJRCgpIHtcclxuICAgIGxldCByZXN1bHQ7XHJcbiAgICBkbyB7XHJcbiAgICAgICAgcmVzdWx0ID0gbmFub2lkKCk7XHJcbiAgICB9IHdoaWxlIChkaWFsb2dSZXNwb25zZXMuaGFzKHJlc3VsdCkpO1xyXG4gICAgcmV0dXJuIHJlc3VsdDtcclxufVxyXG5cclxuZXhwb3J0IGZ1bmN0aW9uIGRpYWxvZ0NhbGxiYWNrKGlkLCBkYXRhLCBpc0pTT04pIHtcclxuICAgIGxldCBwID0gZGlhbG9nUmVzcG9uc2VzLmdldChpZCk7XHJcbiAgICBpZiAocCkge1xyXG4gICAgICAgIGlmIChpc0pTT04pIHtcclxuICAgICAgICAgICAgcC5yZXNvbHZlKEpTT04ucGFyc2UoZGF0YSkpO1xyXG4gICAgICAgIH0gZWxzZSB7XHJcbiAgICAgICAgICAgIHAucmVzb2x2ZShkYXRhKTtcclxuICAgICAgICB9XHJcbiAgICAgICAgZGlhbG9nUmVzcG9uc2VzLmRlbGV0ZShpZCk7XHJcbiAgICB9XHJcbn1cclxuZXhwb3J0IGZ1bmN0aW9uIGRpYWxvZ0Vycm9yQ2FsbGJhY2soaWQsIG1lc3NhZ2UpIHtcclxuICAgIGxldCBwID0gZGlhbG9nUmVzcG9uc2VzLmdldChpZCk7XHJcbiAgICBpZiAocCkge1xyXG4gICAgICAgIHAucmVqZWN0KG1lc3NhZ2UpO1xyXG4gICAgICAgIGRpYWxvZ1Jlc3BvbnNlcy5kZWxldGUoaWQpO1xyXG4gICAgfVxyXG59XHJcblxyXG5mdW5jdGlvbiBkaWFsb2codHlwZSwgb3B0aW9ucykge1xyXG4gICAgcmV0dXJuIG5ldyBQcm9taXNlKChyZXNvbHZlLCByZWplY3QpID0+IHtcclxuICAgICAgICBsZXQgaWQgPSBnZW5lcmF0ZUlEKCk7XHJcbiAgICAgICAgb3B0aW9ucyA9IG9wdGlvbnMgfHwge307XHJcbiAgICAgICAgb3B0aW9uc1tcImRpYWxvZy1pZFwiXSA9IGlkO1xyXG4gICAgICAgIGRpYWxvZ1Jlc3BvbnNlcy5zZXQoaWQsIHtyZXNvbHZlLCByZWplY3R9KTtcclxuICAgICAgICBjYWxsKHR5cGUsIG9wdGlvbnMpLmNhdGNoKChlcnJvcikgPT4ge1xyXG4gICAgICAgICAgICByZWplY3QoZXJyb3IpO1xyXG4gICAgICAgICAgICBkaWFsb2dSZXNwb25zZXMuZGVsZXRlKGlkKTtcclxuICAgICAgICB9KTtcclxuICAgIH0pO1xyXG59XHJcblxyXG5cclxuLyoqXHJcbiAqIFNob3dzIGFuIEluZm8gZGlhbG9nIHdpdGggdGhlIGdpdmVuIG9wdGlvbnMuXHJcbiAqIEBwYXJhbSB7TWVzc2FnZURpYWxvZ09wdGlvbnN9IG9wdGlvbnNcclxuICogQHJldHVybnMge1Byb21pc2U8c3RyaW5nPn0gVGhlIGxhYmVsIG9mIHRoZSBidXR0b24gcHJlc3NlZFxyXG4gKi9cclxuZXhwb3J0IGZ1bmN0aW9uIEluZm8ob3B0aW9ucykge1xyXG4gICAgcmV0dXJuIGRpYWxvZyhEaWFsb2dJbmZvLCBvcHRpb25zKTtcclxufVxyXG5cclxuLyoqXHJcbiAqIFNob3dzIGEgV2FybmluZyBkaWFsb2cgd2l0aCB0aGUgZ2l2ZW4gb3B0aW9ucy5cclxuICogQHBhcmFtIHtNZXNzYWdlRGlhbG9nT3B0aW9uc30gb3B0aW9uc1xyXG4gKiBAcmV0dXJucyB7UHJvbWlzZTxzdHJpbmc+fSBUaGUgbGFiZWwgb2YgdGhlIGJ1dHRvbiBwcmVzc2VkXHJcbiAqL1xyXG5leHBvcnQgZnVuY3Rpb24gV2FybmluZyhvcHRpb25zKSB7XHJcbiAgICByZXR1cm4gZGlhbG9nKERpYWxvZ1dhcm5pbmcsIG9wdGlvbnMpO1xyXG59XHJcblxyXG4vKipcclxuICogU2hvd3MgYW4gRXJyb3IgZGlhbG9nIHdpdGggdGhlIGdpdmVuIG9wdGlvbnMuXHJcbiAqIEBwYXJhbSB7TWVzc2FnZURpYWxvZ09wdGlvbnN9IG9wdGlvbnNcclxuICogQHJldHVybnMge1Byb21pc2U8c3RyaW5nPn0gVGhlIGxhYmVsIG9mIHRoZSBidXR0b24gcHJlc3NlZFxyXG4gKi9cclxuZXhwb3J0IGZ1bmN0aW9uIEVycm9yKG9wdGlvbnMpIHtcclxuICAgIHJldHVybiBkaWFsb2coRGlhbG9nRXJyb3IsIG9wdGlvbnMpO1xyXG59XHJcblxyXG4vKipcclxuICogU2hvd3MgYSBRdWVzdGlvbiBkaWFsb2cgd2l0aCB0aGUgZ2l2ZW4gb3B0aW9ucy5cclxuICogQHBhcmFtIHtNZXNzYWdlRGlhbG9nT3B0aW9uc30gb3B0aW9uc1xyXG4gKiBAcmV0dXJucyB7UHJvbWlzZTxzdHJpbmc+fSBUaGUgbGFiZWwgb2YgdGhlIGJ1dHRvbiBwcmVzc2VkXHJcbiAqL1xyXG5leHBvcnQgZnVuY3Rpb24gUXVlc3Rpb24ob3B0aW9ucykge1xyXG4gICAgcmV0dXJuIGRpYWxvZyhEaWFsb2dRdWVzdGlvbiwgb3B0aW9ucyk7XHJcbn1cclxuXHJcbi8qKlxyXG4gKiBTaG93cyBhbiBPcGVuIGRpYWxvZyB3aXRoIHRoZSBnaXZlbiBvcHRpb25zLlxyXG4gKiBAcGFyYW0ge09wZW5EaWFsb2dPcHRpb25zfSBvcHRpb25zXHJcbiAqIEByZXR1cm5zIHtQcm9taXNlPHN0cmluZ1tdfHN0cmluZz59IFJldHVybnMgdGhlIHNlbGVjdGVkIGZpbGUgb3IgYW4gYXJyYXkgb2Ygc2VsZWN0ZWQgZmlsZXMgaWYgQWxsb3dzTXVsdGlwbGVTZWxlY3Rpb24gaXMgdHJ1ZS4gQSBibGFuayBzdHJpbmcgaXMgcmV0dXJuZWQgaWYgbm8gZmlsZSB3YXMgc2VsZWN0ZWQuXHJcbiAqL1xyXG5leHBvcnQgZnVuY3Rpb24gT3BlbkZpbGUob3B0aW9ucykge1xyXG4gICAgcmV0dXJuIGRpYWxvZyhEaWFsb2dPcGVuRmlsZSwgb3B0aW9ucyk7XHJcbn1cclxuXHJcbi8qKlxyXG4gKiBTaG93cyBhIFNhdmUgZGlhbG9nIHdpdGggdGhlIGdpdmVuIG9wdGlvbnMuXHJcbiAqIEBwYXJhbSB7U2F2ZURpYWxvZ09wdGlvbnN9IG9wdGlvbnNcclxuICogQHJldHVybnMge1Byb21pc2U8c3RyaW5nPn0gUmV0dXJucyB0aGUgc2VsZWN0ZWQgZmlsZS4gQSBibGFuayBzdHJpbmcgaXMgcmV0dXJuZWQgaWYgbm8gZmlsZSB3YXMgc2VsZWN0ZWQuXHJcbiAqL1xyXG5leHBvcnQgZnVuY3Rpb24gU2F2ZUZpbGUob3B0aW9ucykge1xyXG4gICAgcmV0dXJuIGRpYWxvZyhEaWFsb2dTYXZlRmlsZSwgb3B0aW9ucyk7XHJcbn1cclxuXHJcbiIsICJpbXBvcnQge25ld1J1bnRpbWVDYWxsZXJXaXRoSUQsIG9iamVjdE5hbWVzfSBmcm9tIFwiLi9ydW50aW1lXCI7XHJcblxyXG5sZXQgY2FsbCA9IG5ld1J1bnRpbWVDYWxsZXJXaXRoSUQob2JqZWN0TmFtZXMuQ29udGV4dE1lbnUpO1xyXG5cclxubGV0IENvbnRleHRNZW51T3BlbiA9IDA7XHJcblxyXG5mdW5jdGlvbiBvcGVuQ29udGV4dE1lbnUoaWQsIHgsIHksIGRhdGEpIHtcclxuICAgIHZvaWQgY2FsbChDb250ZXh0TWVudU9wZW4sIHtpZCwgeCwgeSwgZGF0YX0pO1xyXG59XHJcblxyXG5leHBvcnQgZnVuY3Rpb24gc2V0dXBDb250ZXh0TWVudXMoKSB7XHJcbiAgICB3aW5kb3cuYWRkRXZlbnRMaXN0ZW5lcignY29udGV4dG1lbnUnLCBjb250ZXh0TWVudUhhbmRsZXIpO1xyXG59XHJcblxyXG5mdW5jdGlvbiBjb250ZXh0TWVudUhhbmRsZXIoZXZlbnQpIHtcclxuICAgIC8vIENoZWNrIGZvciBjdXN0b20gY29udGV4dCBtZW51XHJcbiAgICBsZXQgZWxlbWVudCA9IGV2ZW50LnRhcmdldDtcclxuICAgIGxldCBjdXN0b21Db250ZXh0TWVudSA9IHdpbmRvdy5nZXRDb21wdXRlZFN0eWxlKGVsZW1lbnQpLmdldFByb3BlcnR5VmFsdWUoXCItLWN1c3RvbS1jb250ZXh0bWVudVwiKTtcclxuICAgIGN1c3RvbUNvbnRleHRNZW51ID0gY3VzdG9tQ29udGV4dE1lbnUgPyBjdXN0b21Db250ZXh0TWVudS50cmltKCkgOiBcIlwiO1xyXG4gICAgaWYgKGN1c3RvbUNvbnRleHRNZW51KSB7XHJcbiAgICAgICAgZXZlbnQucHJldmVudERlZmF1bHQoKTtcclxuICAgICAgICBsZXQgY3VzdG9tQ29udGV4dE1lbnVEYXRhID0gd2luZG93LmdldENvbXB1dGVkU3R5bGUoZWxlbWVudCkuZ2V0UHJvcGVydHlWYWx1ZShcIi0tY3VzdG9tLWNvbnRleHRtZW51LWRhdGFcIik7XHJcbiAgICAgICAgb3BlbkNvbnRleHRNZW51KGN1c3RvbUNvbnRleHRNZW51LCBldmVudC5jbGllbnRYLCBldmVudC5jbGllbnRZLCBjdXN0b21Db250ZXh0TWVudURhdGEpO1xyXG4gICAgICAgIHJldHVyblxyXG4gICAgfVxyXG5cclxuICAgIHByb2Nlc3NEZWZhdWx0Q29udGV4dE1lbnUoZXZlbnQpO1xyXG59XHJcblxyXG5cclxuLypcclxuLS1kZWZhdWx0LWNvbnRleHRtZW51OiBhdXRvOyAoZGVmYXVsdCkgd2lsbCBzaG93IHRoZSBkZWZhdWx0IGNvbnRleHQgbWVudSBpZiBjb250ZW50RWRpdGFibGUgaXMgdHJ1ZSBPUiB0ZXh0IGhhcyBiZWVuIHNlbGVjdGVkIE9SIGVsZW1lbnQgaXMgaW5wdXQgb3IgdGV4dGFyZWFcclxuLS1kZWZhdWx0LWNvbnRleHRtZW51OiBzaG93OyB3aWxsIGFsd2F5cyBzaG93IHRoZSBkZWZhdWx0IGNvbnRleHQgbWVudVxyXG4tLWRlZmF1bHQtY29udGV4dG1lbnU6IGhpZGU7IHdpbGwgYWx3YXlzIGhpZGUgdGhlIGRlZmF1bHQgY29udGV4dCBtZW51XHJcblxyXG5UaGlzIHJ1bGUgaXMgaW5oZXJpdGVkIGxpa2Ugbm9ybWFsIENTUyBydWxlcywgc28gbmVzdGluZyB3b3JrcyBhcyBleHBlY3RlZFxyXG4qL1xyXG5mdW5jdGlvbiBwcm9jZXNzRGVmYXVsdENvbnRleHRNZW51KGV2ZW50KSB7XHJcbiAgICAvLyBEZWJ1ZyBidWlsZHMgYWx3YXlzIHNob3cgdGhlIG1lbnVcclxuICAgIGlmIChERUJVRykge1xyXG4gICAgICAgIHJldHVybjtcclxuICAgIH1cclxuXHJcbiAgICAvLyBQcm9jZXNzIGRlZmF1bHQgY29udGV4dCBtZW51XHJcbiAgICBjb25zdCBlbGVtZW50ID0gZXZlbnQudGFyZ2V0O1xyXG4gICAgY29uc3QgY29tcHV0ZWRTdHlsZSA9IHdpbmRvdy5nZXRDb21wdXRlZFN0eWxlKGVsZW1lbnQpO1xyXG4gICAgY29uc3QgZGVmYXVsdENvbnRleHRNZW51QWN0aW9uID0gY29tcHV0ZWRTdHlsZS5nZXRQcm9wZXJ0eVZhbHVlKFwiLS1kZWZhdWx0LWNvbnRleHRtZW51XCIpLnRyaW0oKTtcclxuICAgIHN3aXRjaCAoZGVmYXVsdENvbnRleHRNZW51QWN0aW9uKSB7XHJcbiAgICAgICAgY2FzZSBcInNob3dcIjpcclxuICAgICAgICAgICAgcmV0dXJuO1xyXG4gICAgICAgIGNhc2UgXCJoaWRlXCI6XHJcbiAgICAgICAgICAgIGV2ZW50LnByZXZlbnREZWZhdWx0KCk7XHJcbiAgICAgICAgICAgIHJldHVybjtcclxuICAgICAgICBkZWZhdWx0OlxyXG4gICAgICAgICAgICAvLyBDaGVjayBpZiBjb250ZW50RWRpdGFibGUgaXMgdHJ1ZVxyXG4gICAgICAgICAgICBpZiAoZWxlbWVudC5pc0NvbnRlbnRFZGl0YWJsZSkge1xyXG4gICAgICAgICAgICAgICAgcmV0dXJuO1xyXG4gICAgICAgICAgICB9XHJcblxyXG4gICAgICAgICAgICAvLyBDaGVjayBpZiB0ZXh0IGhhcyBiZWVuIHNlbGVjdGVkXHJcbiAgICAgICAgICAgIGNvbnN0IHNlbGVjdGlvbiA9IHdpbmRvdy5nZXRTZWxlY3Rpb24oKTtcclxuICAgICAgICAgICAgY29uc3QgaGFzU2VsZWN0aW9uID0gKHNlbGVjdGlvbi50b1N0cmluZygpLmxlbmd0aCA+IDApXHJcbiAgICAgICAgICAgIGlmIChoYXNTZWxlY3Rpb24pIHtcclxuICAgICAgICAgICAgICAgIGZvciAobGV0IGkgPSAwOyBpIDwgc2VsZWN0aW9uLnJhbmdlQ291bnQ7IGkrKykge1xyXG4gICAgICAgICAgICAgICAgICAgIGNvbnN0IHJhbmdlID0gc2VsZWN0aW9uLmdldFJhbmdlQXQoaSk7XHJcbiAgICAgICAgICAgICAgICAgICAgY29uc3QgcmVjdHMgPSByYW5nZS5nZXRDbGllbnRSZWN0cygpO1xyXG4gICAgICAgICAgICAgICAgICAgIGZvciAobGV0IGogPSAwOyBqIDwgcmVjdHMubGVuZ3RoOyBqKyspIHtcclxuICAgICAgICAgICAgICAgICAgICAgICAgY29uc3QgcmVjdCA9IHJlY3RzW2pdO1xyXG4gICAgICAgICAgICAgICAgICAgICAgICBpZiAoZG9jdW1lbnQuZWxlbWVudEZyb21Qb2ludChyZWN0LmxlZnQsIHJlY3QudG9wKSA9PT0gZWxlbWVudCkge1xyXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgcmV0dXJuO1xyXG4gICAgICAgICAgICAgICAgICAgICAgICB9XHJcbiAgICAgICAgICAgICAgICAgICAgfVxyXG4gICAgICAgICAgICAgICAgfVxyXG4gICAgICAgICAgICB9XHJcbiAgICAgICAgICAgIC8vIENoZWNrIGlmIHRhZ25hbWUgaXMgaW5wdXQgb3IgdGV4dGFyZWFcclxuICAgICAgICAgICAgaWYgKGVsZW1lbnQudGFnTmFtZSA9PT0gXCJJTlBVVFwiIHx8IGVsZW1lbnQudGFnTmFtZSA9PT0gXCJURVhUQVJFQVwiKSB7XHJcbiAgICAgICAgICAgICAgICBpZiAoaGFzU2VsZWN0aW9uIHx8ICghZWxlbWVudC5yZWFkT25seSAmJiAhZWxlbWVudC5kaXNhYmxlZCkpIHtcclxuICAgICAgICAgICAgICAgICAgICByZXR1cm47XHJcbiAgICAgICAgICAgICAgICB9XHJcbiAgICAgICAgICAgIH1cclxuXHJcbiAgICAgICAgICAgIC8vIGhpZGUgZGVmYXVsdCBjb250ZXh0IG1lbnVcclxuICAgICAgICAgICAgZXZlbnQucHJldmVudERlZmF1bHQoKTtcclxuICAgIH1cclxufVxyXG4iLCAiXHJcbmltcG9ydCB7RW1pdCwgV2FpbHNFdmVudH0gZnJvbSBcIi4vZXZlbnRzXCI7XHJcbmltcG9ydCB7UXVlc3Rpb259IGZyb20gXCIuL2RpYWxvZ3NcIjtcclxuXHJcbmZ1bmN0aW9uIHNlbmRFdmVudChldmVudE5hbWUsIGRhdGE9bnVsbCkge1xyXG4gICAgbGV0IGV2ZW50ID0gbmV3IFdhaWxzRXZlbnQoZXZlbnROYW1lLCBkYXRhKTtcclxuICAgIEVtaXQoZXZlbnQpO1xyXG59XHJcblxyXG5mdW5jdGlvbiBhZGRXTUxFdmVudExpc3RlbmVycygpIHtcclxuICAgIGNvbnN0IGVsZW1lbnRzID0gZG9jdW1lbnQucXVlcnlTZWxlY3RvckFsbCgnW3dtbC1ldmVudF0nKTtcclxuICAgIGVsZW1lbnRzLmZvckVhY2goZnVuY3Rpb24gKGVsZW1lbnQpIHtcclxuICAgICAgICBjb25zdCBldmVudFR5cGUgPSBlbGVtZW50LmdldEF0dHJpYnV0ZSgnd21sLWV2ZW50Jyk7XHJcbiAgICAgICAgY29uc3QgY29uZmlybSA9IGVsZW1lbnQuZ2V0QXR0cmlidXRlKCd3bWwtY29uZmlybScpO1xyXG4gICAgICAgIGNvbnN0IHRyaWdnZXIgPSBlbGVtZW50LmdldEF0dHJpYnV0ZSgnd21sLXRyaWdnZXInKSB8fCBcImNsaWNrXCI7XHJcblxyXG4gICAgICAgIGxldCBjYWxsYmFjayA9IGZ1bmN0aW9uICgpIHtcclxuICAgICAgICAgICAgaWYgKGNvbmZpcm0pIHtcclxuICAgICAgICAgICAgICAgIFF1ZXN0aW9uKHtUaXRsZTogXCJDb25maXJtXCIsIE1lc3NhZ2U6Y29uZmlybSwgRGV0YWNoZWQ6IGZhbHNlLCBCdXR0b25zOlt7TGFiZWw6XCJZZXNcIn0se0xhYmVsOlwiTm9cIiwgSXNEZWZhdWx0OnRydWV9XX0pLnRoZW4oZnVuY3Rpb24gKHJlc3VsdCkge1xyXG4gICAgICAgICAgICAgICAgICAgIGlmIChyZXN1bHQgIT09IFwiTm9cIikge1xyXG4gICAgICAgICAgICAgICAgICAgICAgICBzZW5kRXZlbnQoZXZlbnRUeXBlKTtcclxuICAgICAgICAgICAgICAgICAgICB9XHJcbiAgICAgICAgICAgICAgICB9KTtcclxuICAgICAgICAgICAgICAgIHJldHVybjtcclxuICAgICAgICAgICAgfVxyXG4gICAgICAgICAgICBzZW5kRXZlbnQoZXZlbnRUeXBlKTtcclxuICAgICAgICB9O1xyXG4gICAgICAgIC8vIFJlbW92ZSBleGlzdGluZyBsaXN0ZW5lcnNcclxuXHJcbiAgICAgICAgZWxlbWVudC5yZW1vdmVFdmVudExpc3RlbmVyKHRyaWdnZXIsIGNhbGxiYWNrKTtcclxuXHJcbiAgICAgICAgLy8gQWRkIG5ldyBsaXN0ZW5lclxyXG4gICAgICAgIGVsZW1lbnQuYWRkRXZlbnRMaXN0ZW5lcih0cmlnZ2VyLCBjYWxsYmFjayk7XHJcbiAgICB9KTtcclxufVxyXG5cclxuZnVuY3Rpb24gY2FsbFdpbmRvd01ldGhvZChtZXRob2QpIHtcclxuICAgIGlmICh3YWlscy5XaW5kb3dbbWV0aG9kXSA9PT0gdW5kZWZpbmVkKSB7XHJcbiAgICAgICAgY29uc29sZS5sb2coXCJXaW5kb3cgbWV0aG9kIFwiICsgbWV0aG9kICsgXCIgbm90IGZvdW5kXCIpO1xyXG4gICAgfVxyXG4gICAgd2FpbHMuV2luZG93W21ldGhvZF0oKTtcclxufVxyXG5cclxuZnVuY3Rpb24gYWRkV01MV2luZG93TGlzdGVuZXJzKCkge1xyXG4gICAgY29uc3QgZWxlbWVudHMgPSBkb2N1bWVudC5xdWVyeVNlbGVjdG9yQWxsKCdbd21sLXdpbmRvd10nKTtcclxuICAgIGVsZW1lbnRzLmZvckVhY2goZnVuY3Rpb24gKGVsZW1lbnQpIHtcclxuICAgICAgICBjb25zdCB3aW5kb3dNZXRob2QgPSBlbGVtZW50LmdldEF0dHJpYnV0ZSgnd21sLXdpbmRvdycpO1xyXG4gICAgICAgIGNvbnN0IGNvbmZpcm0gPSBlbGVtZW50LmdldEF0dHJpYnV0ZSgnd21sLWNvbmZpcm0nKTtcclxuICAgICAgICBjb25zdCB0cmlnZ2VyID0gZWxlbWVudC5nZXRBdHRyaWJ1dGUoJ3dtbC10cmlnZ2VyJykgfHwgXCJjbGlja1wiO1xyXG5cclxuICAgICAgICBsZXQgY2FsbGJhY2sgPSBmdW5jdGlvbiAoKSB7XHJcbiAgICAgICAgICAgIGlmIChjb25maXJtKSB7XHJcbiAgICAgICAgICAgICAgICBRdWVzdGlvbih7VGl0bGU6IFwiQ29uZmlybVwiLCBNZXNzYWdlOmNvbmZpcm0sIEJ1dHRvbnM6W3tMYWJlbDpcIlllc1wifSx7TGFiZWw6XCJOb1wiLCBJc0RlZmF1bHQ6dHJ1ZX1dfSkudGhlbihmdW5jdGlvbiAocmVzdWx0KSB7XHJcbiAgICAgICAgICAgICAgICAgICAgaWYgKHJlc3VsdCAhPT0gXCJOb1wiKSB7XHJcbiAgICAgICAgICAgICAgICAgICAgICAgIGNhbGxXaW5kb3dNZXRob2Qod2luZG93TWV0aG9kKTtcclxuICAgICAgICAgICAgICAgICAgICB9XHJcbiAgICAgICAgICAgICAgICB9KTtcclxuICAgICAgICAgICAgICAgIHJldHVybjtcclxuICAgICAgICAgICAgfVxyXG4gICAgICAgICAgICBjYWxsV2luZG93TWV0aG9kKHdpbmRvd01ldGhvZCk7XHJcbiAgICAgICAgfTtcclxuXHJcbiAgICAgICAgLy8gUmVtb3ZlIGV4aXN0aW5nIGxpc3RlbmVyc1xyXG4gICAgICAgIGVsZW1lbnQucmVtb3ZlRXZlbnRMaXN0ZW5lcih0cmlnZ2VyLCBjYWxsYmFjayk7XHJcblxyXG4gICAgICAgIC8vIEFkZCBuZXcgbGlzdGVuZXJcclxuICAgICAgICBlbGVtZW50LmFkZEV2ZW50TGlzdGVuZXIodHJpZ2dlciwgY2FsbGJhY2spO1xyXG4gICAgfSk7XHJcbn1cclxuXHJcbmZ1bmN0aW9uIGFkZFdNTE9wZW5Ccm93c2VyTGlzdGVuZXIoKSB7XHJcbiAgICBjb25zdCBlbGVtZW50cyA9IGRvY3VtZW50LnF1ZXJ5U2VsZWN0b3JBbGwoJ1t3bWwtb3BlbnVybF0nKTtcclxuICAgIGVsZW1lbnRzLmZvckVhY2goZnVuY3Rpb24gKGVsZW1lbnQpIHtcclxuICAgICAgICBjb25zdCB1cmwgPSBlbGVtZW50LmdldEF0dHJpYnV0ZSgnd21sLW9wZW51cmwnKTtcclxuICAgICAgICBjb25zdCBjb25maXJtID0gZWxlbWVudC5nZXRBdHRyaWJ1dGUoJ3dtbC1jb25maXJtJyk7XHJcbiAgICAgICAgY29uc3QgdHJpZ2dlciA9IGVsZW1lbnQuZ2V0QXR0cmlidXRlKCd3bWwtdHJpZ2dlcicpIHx8IFwiY2xpY2tcIjtcclxuXHJcbiAgICAgICAgbGV0IGNhbGxiYWNrID0gZnVuY3Rpb24gKCkge1xyXG4gICAgICAgICAgICBpZiAoY29uZmlybSkge1xyXG4gICAgICAgICAgICAgICAgUXVlc3Rpb24oe1RpdGxlOiBcIkNvbmZpcm1cIiwgTWVzc2FnZTpjb25maXJtLCBCdXR0b25zOlt7TGFiZWw6XCJZZXNcIn0se0xhYmVsOlwiTm9cIiwgSXNEZWZhdWx0OnRydWV9XX0pLnRoZW4oZnVuY3Rpb24gKHJlc3VsdCkge1xyXG4gICAgICAgICAgICAgICAgICAgIGlmIChyZXN1bHQgIT09IFwiTm9cIikge1xyXG4gICAgICAgICAgICAgICAgICAgICAgICB2b2lkIHdhaWxzLkJyb3dzZXIuT3BlblVSTCh1cmwpO1xyXG4gICAgICAgICAgICAgICAgICAgIH1cclxuICAgICAgICAgICAgICAgIH0pO1xyXG4gICAgICAgICAgICAgICAgcmV0dXJuO1xyXG4gICAgICAgICAgICB9XHJcbiAgICAgICAgICAgIHZvaWQgd2FpbHMuQnJvd3Nlci5PcGVuVVJMKHVybCk7XHJcbiAgICAgICAgfTtcclxuXHJcbiAgICAgICAgLy8gUmVtb3ZlIGV4aXN0aW5nIGxpc3RlbmVyc1xyXG4gICAgICAgIGVsZW1lbnQucmVtb3ZlRXZlbnRMaXN0ZW5lcih0cmlnZ2VyLCBjYWxsYmFjayk7XHJcblxyXG4gICAgICAgIC8vIEFkZCBuZXcgbGlzdGVuZXJcclxuICAgICAgICBlbGVtZW50LmFkZEV2ZW50TGlzdGVuZXIodHJpZ2dlciwgY2FsbGJhY2spO1xyXG4gICAgfSk7XHJcbn1cclxuXHJcbmV4cG9ydCBmdW5jdGlvbiByZWxvYWRXTUwoKSB7XHJcbiAgICBhZGRXTUxFdmVudExpc3RlbmVycygpO1xyXG4gICAgYWRkV01MV2luZG93TGlzdGVuZXJzKCk7XHJcbiAgICBhZGRXTUxPcGVuQnJvd3Nlckxpc3RlbmVyKCk7XHJcbn1cclxuIiwgIi8qXHJcbiBfXHQgICBfX1x0ICBfIF9fXHJcbnwgfFx0IC8gL19fXyBfKF8pIC9fX19fXHJcbnwgfCAvfCAvIC8gX18gYC8gLyAvIF9fXy9cclxufCB8LyB8LyAvIC9fLyAvIC8gKF9fICApXHJcbnxfXy98X18vXFxfXyxfL18vXy9fX19fL1xyXG5UaGUgZWxlY3Ryb24gYWx0ZXJuYXRpdmUgZm9yIEdvXHJcbihjKSBMZWEgQW50aG9ueSAyMDE5LXByZXNlbnRcclxuKi9cclxuXHJcbi8qIGpzaGludCBlc3ZlcnNpb246IDkgKi9cclxuXHJcbi8vIGRlZmluZWQgaW4gdGhlIFRhc2tmaWxlXHJcbmV4cG9ydCBsZXQgaW52b2tlID0gZnVuY3Rpb24oaW5wdXQpIHtcclxuICAgIGlmKFdJTkRPV1MpIHtcclxuICAgICAgICBjaHJvbWUud2Vidmlldy5wb3N0TWVzc2FnZShpbnB1dCk7XHJcbiAgICB9IGVsc2Uge1xyXG4gICAgICAgIHdlYmtpdC5tZXNzYWdlSGFuZGxlcnMuZXh0ZXJuYWwucG9zdE1lc3NhZ2UoaW5wdXQpO1xyXG4gICAgfVxyXG59XHJcbiIsICIvKlxyXG4gX1x0ICAgX19cdCAgXyBfX1xyXG58IHxcdCAvIC9fX18gXyhfKSAvX19fX1xyXG58IHwgL3wgLyAvIF9fIGAvIC8gLyBfX18vXHJcbnwgfC8gfC8gLyAvXy8gLyAvIChfXyAgKVxyXG58X18vfF9fL1xcX18sXy9fL18vX19fXy9cclxuVGhlIGVsZWN0cm9uIGFsdGVybmF0aXZlIGZvciBHb1xyXG4oYykgTGVhIEFudGhvbnkgMjAxOS1wcmVzZW50XHJcbiovXHJcblxyXG4vKiBqc2hpbnQgZXN2ZXJzaW9uOiA5ICovXHJcblxyXG5sZXQgZmxhZ3MgPSBuZXcgTWFwKCk7XHJcblxyXG5mdW5jdGlvbiBjb252ZXJ0VG9NYXAob2JqKSB7XHJcbiAgICBjb25zdCBtYXAgPSBuZXcgTWFwKCk7XHJcblxyXG4gICAgZm9yIChjb25zdCBba2V5LCB2YWx1ZV0gb2YgT2JqZWN0LmVudHJpZXMob2JqKSkge1xyXG4gICAgICAgIGlmICh0eXBlb2YgdmFsdWUgPT09ICdvYmplY3QnICYmIHZhbHVlICE9PSBudWxsKSB7XHJcbiAgICAgICAgICAgIG1hcC5zZXQoa2V5LCBjb252ZXJ0VG9NYXAodmFsdWUpKTsgLy8gUmVjdXJzaXZlbHkgY29udmVydCBuZXN0ZWQgb2JqZWN0XHJcbiAgICAgICAgfSBlbHNlIHtcclxuICAgICAgICAgICAgbWFwLnNldChrZXksIHZhbHVlKTtcclxuICAgICAgICB9XHJcbiAgICB9XHJcblxyXG4gICAgcmV0dXJuIG1hcDtcclxufVxyXG5cclxuZmV0Y2goXCIvd2FpbHMvZmxhZ3NcIikudGhlbigocmVzcG9uc2UpID0+IHtcclxuICAgIHJlc3BvbnNlLmpzb24oKS50aGVuKChkYXRhKSA9PiB7XHJcbiAgICAgICAgZmxhZ3MgPSBjb252ZXJ0VG9NYXAoZGF0YSk7XHJcbiAgICB9KTtcclxufSk7XHJcblxyXG5cclxuZnVuY3Rpb24gZ2V0VmFsdWVGcm9tTWFwKGtleVN0cmluZykge1xyXG4gICAgY29uc3Qga2V5cyA9IGtleVN0cmluZy5zcGxpdCgnLicpO1xyXG4gICAgbGV0IHZhbHVlID0gZmxhZ3M7XHJcblxyXG4gICAgZm9yIChjb25zdCBrZXkgb2Yga2V5cykge1xyXG4gICAgICAgIGlmICh2YWx1ZSBpbnN0YW5jZW9mIE1hcCkge1xyXG4gICAgICAgICAgICB2YWx1ZSA9IHZhbHVlLmdldChrZXkpO1xyXG4gICAgICAgIH0gZWxzZSB7XHJcbiAgICAgICAgICAgIHZhbHVlID0gdmFsdWVba2V5XTtcclxuICAgICAgICB9XHJcblxyXG4gICAgICAgIGlmICh2YWx1ZSA9PT0gdW5kZWZpbmVkKSB7XHJcbiAgICAgICAgICAgIGJyZWFrO1xyXG4gICAgICAgIH1cclxuICAgIH1cclxuXHJcbiAgICByZXR1cm4gdmFsdWU7XHJcbn1cclxuXHJcbmV4cG9ydCBmdW5jdGlvbiBHZXRGbGFnKGtleVN0cmluZykge1xyXG4gICAgcmV0dXJuIGdldFZhbHVlRnJvbU1hcChrZXlTdHJpbmcpO1xyXG59XHJcbiIsICIvKlxyXG4gX1x0ICAgX19cdCAgXyBfX1xyXG58IHxcdCAvIC9fX18gXyhfKSAvX19fX1xyXG58IHwgL3wgLyAvIF9fIGAvIC8gLyBfX18vXHJcbnwgfC8gfC8gLyAvXy8gLyAvIChfXyAgKVxyXG58X18vfF9fL1xcX18sXy9fL18vX19fXy9cclxuVGhlIGVsZWN0cm9uIGFsdGVybmF0aXZlIGZvciBHb1xyXG4oYykgTGVhIEFudGhvbnkgMjAxOS1wcmVzZW50XHJcbiovXHJcblxyXG4vKiBqc2hpbnQgZXN2ZXJzaW9uOiA5ICovXHJcblxyXG5pbXBvcnQge2ludm9rZX0gZnJvbSBcIi4vaW52b2tlXCI7XHJcbmltcG9ydCB7R2V0RmxhZ30gZnJvbSBcIi4vZmxhZ3NcIjtcclxuXHJcbmxldCBzaG91bGREcmFnID0gZmFsc2U7XHJcblxyXG5leHBvcnQgZnVuY3Rpb24gZHJhZ1Rlc3QoZSkge1xyXG4gICAgbGV0IHZhbCA9IHdpbmRvdy5nZXRDb21wdXRlZFN0eWxlKGUudGFyZ2V0KS5nZXRQcm9wZXJ0eVZhbHVlKFwiLS13ZWJraXQtYXBwLXJlZ2lvblwiKTtcclxuICAgIGlmICh2YWwpIHtcclxuICAgICAgICB2YWwgPSB2YWwudHJpbSgpO1xyXG4gICAgfVxyXG5cclxuICAgIGlmICh2YWwgIT09IFwiZHJhZ1wiKSB7XHJcbiAgICAgICAgcmV0dXJuIGZhbHNlO1xyXG4gICAgfVxyXG5cclxuICAgIC8vIE9ubHkgcHJvY2VzcyB0aGUgcHJpbWFyeSBidXR0b25cclxuICAgIGlmIChlLmJ1dHRvbnMgIT09IDEpIHtcclxuICAgICAgICByZXR1cm4gZmFsc2U7XHJcbiAgICB9XHJcblxyXG4gICAgcmV0dXJuIGUuZGV0YWlsID09PSAxO1xyXG59XHJcblxyXG5leHBvcnQgZnVuY3Rpb24gc2V0dXBEcmFnKCkge1xyXG4gICAgd2luZG93LmFkZEV2ZW50TGlzdGVuZXIoJ21vdXNlZG93bicsIG9uTW91c2VEb3duKTtcclxuICAgIHdpbmRvdy5hZGRFdmVudExpc3RlbmVyKCdtb3VzZW1vdmUnLCBvbk1vdXNlTW92ZSk7XHJcbiAgICB3aW5kb3cuYWRkRXZlbnRMaXN0ZW5lcignbW91c2V1cCcsIG9uTW91c2VVcCk7XHJcbn1cclxuXHJcbmxldCByZXNpemVFZGdlID0gbnVsbDtcclxubGV0IHJlc2l6YWJsZSA9IGZhbHNlO1xyXG5cclxuZXhwb3J0IGZ1bmN0aW9uIHNldFJlc2l6YWJsZSh2YWx1ZSkge1xyXG4gICAgcmVzaXphYmxlID0gdmFsdWU7XHJcbn1cclxuXHJcbmZ1bmN0aW9uIHRlc3RSZXNpemUoZSkge1xyXG4gICAgaWYoIHJlc2l6ZUVkZ2UgKSB7XHJcbiAgICAgICAgaW52b2tlKFwicmVzaXplOlwiICsgcmVzaXplRWRnZSk7XHJcbiAgICAgICAgcmV0dXJuIHRydWVcclxuICAgIH1cclxuICAgIHJldHVybiBmYWxzZTtcclxufVxyXG5cclxuZnVuY3Rpb24gb25Nb3VzZURvd24oZSkge1xyXG5cclxuICAgIC8vIENoZWNrIGZvciByZXNpemluZyBvbiBXaW5kb3dzXHJcbiAgICBpZiggV0lORE9XUyApIHtcclxuICAgICAgICBpZiAodGVzdFJlc2l6ZSgpKSB7XHJcbiAgICAgICAgICAgIHJldHVybjtcclxuICAgICAgICB9XHJcbiAgICB9XHJcbiAgICBpZiAoZHJhZ1Rlc3QoZSkpIHtcclxuICAgICAgICAvLyBJZ25vcmUgZHJhZyBvbiBzY3JvbGxiYXJzXHJcbiAgICAgICAgaWYgKGUub2Zmc2V0WCA+IGUudGFyZ2V0LmNsaWVudFdpZHRoIHx8IGUub2Zmc2V0WSA+IGUudGFyZ2V0LmNsaWVudEhlaWdodCkge1xyXG4gICAgICAgICAgICByZXR1cm47XHJcbiAgICAgICAgfVxyXG4gICAgICAgIHNob3VsZERyYWcgPSB0cnVlO1xyXG4gICAgfSBlbHNlIHtcclxuICAgICAgICBzaG91bGREcmFnID0gZmFsc2U7XHJcbiAgICB9XHJcbn1cclxuXHJcbmZ1bmN0aW9uIG9uTW91c2VVcChlKSB7XHJcbiAgICBsZXQgbW91c2VQcmVzc2VkID0gZS5idXR0b25zICE9PSB1bmRlZmluZWQgPyBlLmJ1dHRvbnMgOiBlLndoaWNoO1xyXG4gICAgaWYgKG1vdXNlUHJlc3NlZCA+IDApIHtcclxuICAgICAgICBlbmREcmFnKCk7XHJcbiAgICB9XHJcbn1cclxuXHJcbmV4cG9ydCBmdW5jdGlvbiBlbmREcmFnKCkge1xyXG4gICAgZG9jdW1lbnQuYm9keS5zdHlsZS5jdXJzb3IgPSAnZGVmYXVsdCc7XHJcbiAgICBzaG91bGREcmFnID0gZmFsc2U7XHJcbn1cclxuXHJcbmZ1bmN0aW9uIHNldFJlc2l6ZShjdXJzb3IpIHtcclxuICAgIGRvY3VtZW50LmRvY3VtZW50RWxlbWVudC5zdHlsZS5jdXJzb3IgPSBjdXJzb3IgfHwgZGVmYXVsdEN1cnNvcjtcclxuICAgIHJlc2l6ZUVkZ2UgPSBjdXJzb3I7XHJcbn1cclxuXHJcbmZ1bmN0aW9uIG9uTW91c2VNb3ZlKGUpIHtcclxuICAgIGlmIChzaG91bGREcmFnKSB7XHJcbiAgICAgICAgc2hvdWxkRHJhZyA9IGZhbHNlO1xyXG4gICAgICAgIGxldCBtb3VzZVByZXNzZWQgPSBlLmJ1dHRvbnMgIT09IHVuZGVmaW5lZCA/IGUuYnV0dG9ucyA6IGUud2hpY2g7XHJcbiAgICAgICAgaWYgKG1vdXNlUHJlc3NlZCA+IDApIHtcclxuICAgICAgICAgICAgaW52b2tlKFwiZHJhZ1wiKTtcclxuICAgICAgICB9XHJcbiAgICAgICAgcmV0dXJuO1xyXG4gICAgfVxyXG5cclxuICAgIGlmIChXSU5ET1dTKSB7XHJcbiAgICAgICAgaWYgKHJlc2l6YWJsZSkge1xyXG4gICAgICAgICAgICBoYW5kbGVSZXNpemUoZSk7XHJcbiAgICAgICAgfVxyXG4gICAgfVxyXG59XHJcblxyXG5sZXQgZGVmYXVsdEN1cnNvciA9IFwiYXV0b1wiO1xyXG5cclxuZnVuY3Rpb24gaGFuZGxlUmVzaXplKGUpIHtcclxuICAgIGxldCByZXNpemVIYW5kbGVIZWlnaHQgPSBHZXRGbGFnKFwic3lzdGVtLnJlc2l6ZUhhbmRsZUhlaWdodFwiKSB8fCA1O1xyXG4gICAgbGV0IHJlc2l6ZUhhbmRsZVdpZHRoID0gR2V0RmxhZyhcInN5c3RlbS5yZXNpemVIYW5kbGVXaWR0aFwiKSB8fCA1O1xyXG5cclxuICAgIC8vIEV4dHJhIHBpeGVscyBmb3IgdGhlIGNvcm5lciBhcmVhc1xyXG4gICAgbGV0IGNvcm5lckV4dHJhID0gR2V0RmxhZyhcInJlc2l6ZUNvcm5lckV4dHJhXCIpIHx8IDEwO1xyXG5cclxuICAgIGxldCByaWdodEJvcmRlciA9IHdpbmRvdy5vdXRlcldpZHRoIC0gZS5jbGllbnRYIDwgcmVzaXplSGFuZGxlV2lkdGg7XHJcbiAgICBsZXQgbGVmdEJvcmRlciA9IGUuY2xpZW50WCA8IHJlc2l6ZUhhbmRsZVdpZHRoO1xyXG4gICAgbGV0IHRvcEJvcmRlciA9IGUuY2xpZW50WSA8IHJlc2l6ZUhhbmRsZUhlaWdodDtcclxuICAgIGxldCBib3R0b21Cb3JkZXIgPSB3aW5kb3cub3V0ZXJIZWlnaHQgLSBlLmNsaWVudFkgPCByZXNpemVIYW5kbGVIZWlnaHQ7XHJcblxyXG4gICAgLy8gQWRqdXN0IGZvciBjb3JuZXJzXHJcbiAgICBsZXQgcmlnaHRDb3JuZXIgPSB3aW5kb3cub3V0ZXJXaWR0aCAtIGUuY2xpZW50WCA8IChyZXNpemVIYW5kbGVXaWR0aCArIGNvcm5lckV4dHJhKTtcclxuICAgIGxldCBsZWZ0Q29ybmVyID0gZS5jbGllbnRYIDwgKHJlc2l6ZUhhbmRsZVdpZHRoICsgY29ybmVyRXh0cmEpO1xyXG4gICAgbGV0IHRvcENvcm5lciA9IGUuY2xpZW50WSA8IChyZXNpemVIYW5kbGVIZWlnaHQgKyBjb3JuZXJFeHRyYSk7XHJcbiAgICBsZXQgYm90dG9tQ29ybmVyID0gd2luZG93Lm91dGVySGVpZ2h0IC0gZS5jbGllbnRZIDwgKHJlc2l6ZUhhbmRsZUhlaWdodCArIGNvcm5lckV4dHJhKTtcclxuXHJcbiAgICAvLyBJZiB3ZSBhcmVuJ3Qgb24gYW4gZWRnZSwgYnV0IHdlcmUsIHJlc2V0IHRoZSBjdXJzb3IgdG8gZGVmYXVsdFxyXG4gICAgaWYgKCFsZWZ0Qm9yZGVyICYmICFyaWdodEJvcmRlciAmJiAhdG9wQm9yZGVyICYmICFib3R0b21Cb3JkZXIgJiYgcmVzaXplRWRnZSAhPT0gdW5kZWZpbmVkKSB7XHJcbiAgICAgICAgc2V0UmVzaXplKCk7XHJcbiAgICB9XHJcbiAgICAvLyBBZGp1c3RlZCBmb3IgY29ybmVyIGFyZWFzXHJcbiAgICBlbHNlIGlmIChyaWdodENvcm5lciAmJiBib3R0b21Db3JuZXIpIHNldFJlc2l6ZShcInNlLXJlc2l6ZVwiKTtcclxuICAgIGVsc2UgaWYgKGxlZnRDb3JuZXIgJiYgYm90dG9tQ29ybmVyKSBzZXRSZXNpemUoXCJzdy1yZXNpemVcIik7XHJcbiAgICBlbHNlIGlmIChsZWZ0Q29ybmVyICYmIHRvcENvcm5lcikgc2V0UmVzaXplKFwibnctcmVzaXplXCIpO1xyXG4gICAgZWxzZSBpZiAodG9wQ29ybmVyICYmIHJpZ2h0Q29ybmVyKSBzZXRSZXNpemUoXCJuZS1yZXNpemVcIik7XHJcbiAgICBlbHNlIGlmIChsZWZ0Qm9yZGVyKSBzZXRSZXNpemUoXCJ3LXJlc2l6ZVwiKTtcclxuICAgIGVsc2UgaWYgKHRvcEJvcmRlcikgc2V0UmVzaXplKFwibi1yZXNpemVcIik7XHJcbiAgICBlbHNlIGlmIChib3R0b21Cb3JkZXIpIHNldFJlc2l6ZShcInMtcmVzaXplXCIpO1xyXG4gICAgZWxzZSBpZiAocmlnaHRCb3JkZXIpIHNldFJlc2l6ZShcImUtcmVzaXplXCIpO1xyXG59XHJcbiIsICIvKlxyXG4gXyAgICAgX18gICAgIF8gX19cclxufCB8ICAvIC9fX18gXyhfKSAvX19fX1xyXG58IHwgL3wgLyAvIF9fIGAvIC8gLyBfX18vXHJcbnwgfC8gfC8gLyAvXy8gLyAvIChfXyAgKVxyXG58X18vfF9fL1xcX18sXy9fL18vX19fXy9cclxuVGhlIGVsZWN0cm9uIGFsdGVybmF0aXZlIGZvciBHb1xyXG4oYykgTGVhIEFudGhvbnkgMjAxOS1wcmVzZW50XHJcbiovXHJcbi8qIGpzaGludCBlc3ZlcnNpb246IDkgKi9cclxuXHJcblxyXG5pbXBvcnQgKiBhcyBDbGlwYm9hcmQgZnJvbSAnLi9jbGlwYm9hcmQnO1xyXG5pbXBvcnQgKiBhcyBBcHBsaWNhdGlvbiBmcm9tICcuL2FwcGxpY2F0aW9uJztcclxuaW1wb3J0ICogYXMgU2NyZWVucyBmcm9tICcuL3NjcmVlbnMnO1xyXG5pbXBvcnQgKiBhcyBTeXN0ZW0gZnJvbSAnLi9zeXN0ZW0nO1xyXG5pbXBvcnQgKiBhcyBCcm93c2VyIGZyb20gJy4vYnJvd3Nlcic7XHJcbmltcG9ydCB7UGx1Z2luLCBDYWxsLCBjYWxsRXJyb3JDYWxsYmFjaywgY2FsbENhbGxiYWNrLCBDYWxsQnlJRCwgQ2FsbEJ5TmFtZX0gZnJvbSBcIi4vY2FsbHNcIjtcclxuaW1wb3J0IHtjbGllbnRJZH0gZnJvbSAnLi9ydW50aW1lJztcclxuaW1wb3J0IHtuZXdXaW5kb3d9IGZyb20gXCIuL3dpbmRvd1wiO1xyXG5pbXBvcnQge2Rpc3BhdGNoV2FpbHNFdmVudCwgRW1pdCwgT2ZmLCBPZmZBbGwsIE9uLCBPbmNlLCBPbk11bHRpcGxlfSBmcm9tIFwiLi9ldmVudHNcIjtcclxuaW1wb3J0IHtkaWFsb2dDYWxsYmFjaywgZGlhbG9nRXJyb3JDYWxsYmFjaywgRXJyb3IsIEluZm8sIE9wZW5GaWxlLCBRdWVzdGlvbiwgU2F2ZUZpbGUsIFdhcm5pbmcsfSBmcm9tIFwiLi9kaWFsb2dzXCI7XHJcbmltcG9ydCB7c2V0dXBDb250ZXh0TWVudXN9IGZyb20gXCIuL2NvbnRleHRtZW51XCI7XHJcbmltcG9ydCB7cmVsb2FkV01MfSBmcm9tIFwiLi93bWxcIjtcclxuaW1wb3J0IHtzZXR1cERyYWcsIGVuZERyYWcsIHNldFJlc2l6YWJsZX0gZnJvbSBcIi4vZHJhZ1wiO1xyXG5cclxud2luZG93LndhaWxzID0ge1xyXG4gICAgLi4ubmV3UnVudGltZShudWxsKSxcclxuICAgIENhcGFiaWxpdGllczoge30sXHJcbiAgICBjbGllbnRJZDogY2xpZW50SWQsXHJcbn07XHJcblxyXG5mZXRjaChcIi93YWlscy9jYXBhYmlsaXRpZXNcIikudGhlbigocmVzcG9uc2UpID0+IHtcclxuICAgIHJlc3BvbnNlLmpzb24oKS50aGVuKChkYXRhKSA9PiB7XHJcbiAgICAgICAgd2luZG93LndhaWxzLkNhcGFiaWxpdGllcyA9IGRhdGE7XHJcbiAgICB9KTtcclxufSk7XHJcblxyXG4vLyBJbnRlcm5hbCB3YWlscyBlbmRwb2ludHNcclxud2luZG93Ll93YWlscyA9IHtcclxuICAgIGRpYWxvZ0NhbGxiYWNrLFxyXG4gICAgZGlhbG9nRXJyb3JDYWxsYmFjayxcclxuICAgIGRpc3BhdGNoV2FpbHNFdmVudCxcclxuICAgIGNhbGxDYWxsYmFjayxcclxuICAgIGNhbGxFcnJvckNhbGxiYWNrLFxyXG4gICAgZW5kRHJhZyxcclxuICAgIHNldFJlc2l6YWJsZSxcclxufTtcclxuXHJcbmV4cG9ydCBmdW5jdGlvbiBuZXdSdW50aW1lKHdpbmRvd05hbWUpIHtcclxuICAgIHJldHVybiB7XHJcbiAgICAgICAgQ2xpcGJvYXJkOiB7XHJcbiAgICAgICAgICAgIC4uLkNsaXBib2FyZFxyXG4gICAgICAgIH0sXHJcbiAgICAgICAgQXBwbGljYXRpb246IHtcclxuICAgICAgICAgICAgLi4uQXBwbGljYXRpb24sXHJcbiAgICAgICAgICAgIEdldFdpbmRvd0J5TmFtZSh3aW5kb3dOYW1lKSB7XHJcbiAgICAgICAgICAgICAgICByZXR1cm4gbmV3UnVudGltZSh3aW5kb3dOYW1lKTtcclxuICAgICAgICAgICAgfVxyXG4gICAgICAgIH0sXHJcbiAgICAgICAgU3lzdGVtLFxyXG4gICAgICAgIFNjcmVlbnMsXHJcbiAgICAgICAgQnJvd3NlcixcclxuICAgICAgICBDYWxsLFxyXG4gICAgICAgIENhbGxCeUlELFxyXG4gICAgICAgIENhbGxCeU5hbWUsXHJcbiAgICAgICAgUGx1Z2luLFxyXG4gICAgICAgIFdNTDoge1xyXG4gICAgICAgICAgICBSZWxvYWQ6IHJlbG9hZFdNTCxcclxuICAgICAgICB9LFxyXG4gICAgICAgIERpYWxvZzoge1xyXG4gICAgICAgICAgICBJbmZvLFxyXG4gICAgICAgICAgICBXYXJuaW5nLFxyXG4gICAgICAgICAgICBFcnJvcixcclxuICAgICAgICAgICAgUXVlc3Rpb24sXHJcbiAgICAgICAgICAgIE9wZW5GaWxlLFxyXG4gICAgICAgICAgICBTYXZlRmlsZSxcclxuICAgICAgICB9LFxyXG4gICAgICAgIEV2ZW50czoge1xyXG4gICAgICAgICAgICBFbWl0LFxyXG4gICAgICAgICAgICBPbixcclxuICAgICAgICAgICAgT25jZSxcclxuICAgICAgICAgICAgT25NdWx0aXBsZSxcclxuICAgICAgICAgICAgT2ZmLFxyXG4gICAgICAgICAgICBPZmZBbGwsXHJcbiAgICAgICAgfSxcclxuICAgICAgICBXaW5kb3c6IG5ld1dpbmRvdyh3aW5kb3dOYW1lKSxcclxuICAgIH07XHJcbn1cclxuXHJcbmlmIChERUJVRykge1xyXG4gICAgY29uc29sZS5sb2coXCJXYWlscyB2My4wLjAgRGVidWcgTW9kZSBFbmFibGVkXCIpO1xyXG59XHJcblxyXG5zZXR1cENvbnRleHRNZW51cygpO1xyXG5zZXR1cERyYWcoKTtcclxuXHJcbmRvY3VtZW50LmFkZEV2ZW50TGlzdGVuZXIoXCJET01Db250ZW50TG9hZGVkXCIsIGZ1bmN0aW9uKCkge1xyXG4gICAgcmVsb2FkV01MKCk7XHJcbn0pO1xyXG4iXSwKICAibWFwcGluZ3MiOiAiOzs7Ozs7OztBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7OztBQ0FBLE1BQUksY0FDRjtBQVdLLE1BQUksU0FBUyxDQUFDLE9BQU8sT0FBTztBQUNqQyxRQUFJLEtBQUs7QUFDVCxRQUFJLElBQUk7QUFDUixXQUFPLEtBQUs7QUFDVixZQUFNLFlBQWEsS0FBSyxPQUFPLElBQUksS0FBTSxDQUFDO0FBQUEsSUFDNUM7QUFDQSxXQUFPO0FBQUEsRUFDVDs7O0FDTkEsTUFBTSxhQUFhLE9BQU8sU0FBUyxTQUFTO0FBRXJDLE1BQU0sY0FBYztBQUFBLElBQ3ZCLE1BQU07QUFBQSxJQUNOLFdBQVc7QUFBQSxJQUNYLGFBQWE7QUFBQSxJQUNiLFFBQVE7QUFBQSxJQUNSLGFBQWE7QUFBQSxJQUNiLFFBQVE7QUFBQSxJQUNSLFFBQVE7QUFBQSxJQUNSLFNBQVM7QUFBQSxJQUNULFFBQVE7QUFBQSxJQUNSLFNBQVM7QUFBQSxFQUNiO0FBQ08sTUFBSSxXQUFXLE9BQU87QUEwQzdCLFdBQVMsa0JBQWtCLFVBQVUsUUFBUSxZQUFZLE1BQU07QUFDM0QsUUFBSSxNQUFNLElBQUksSUFBSSxVQUFVO0FBQzVCLFFBQUksYUFBYSxPQUFPLFVBQVUsUUFBUTtBQUMxQyxRQUFJLGFBQWEsT0FBTyxVQUFVLE1BQU07QUFDeEMsUUFBSSxlQUFlO0FBQUEsTUFDZixTQUFTLENBQUM7QUFBQSxJQUNkO0FBQ0EsUUFBSSxZQUFZO0FBQ1osbUJBQWEsUUFBUSxxQkFBcUIsSUFBSTtBQUFBLElBQ2xEO0FBQ0EsUUFBSSxNQUFNO0FBQ04sVUFBSSxhQUFhLE9BQU8sUUFBUSxLQUFLLFVBQVUsSUFBSSxDQUFDO0FBQUEsSUFDeEQ7QUFDQSxpQkFBYSxRQUFRLG1CQUFtQixJQUFJO0FBQzVDLFdBQU8sSUFBSSxRQUFRLENBQUMsU0FBUyxXQUFXO0FBQ3BDLFlBQU0sS0FBSyxZQUFZLEVBQ2xCLEtBQUssY0FBWTtBQUNkLFlBQUksU0FBUyxJQUFJO0FBRWIsY0FBSSxTQUFTLFFBQVEsSUFBSSxjQUFjLEtBQUssU0FBUyxRQUFRLElBQUksY0FBYyxFQUFFLFFBQVEsa0JBQWtCLE1BQU0sSUFBSTtBQUNqSCxtQkFBTyxTQUFTLEtBQUs7QUFBQSxVQUN6QixPQUFPO0FBQ0gsbUJBQU8sU0FBUyxLQUFLO0FBQUEsVUFDekI7QUFBQSxRQUNKO0FBQ0EsZUFBTyxNQUFNLFNBQVMsVUFBVSxDQUFDO0FBQUEsTUFDckMsQ0FBQyxFQUNBLEtBQUssVUFBUSxRQUFRLElBQUksQ0FBQyxFQUMxQixNQUFNLFdBQVMsT0FBTyxLQUFLLENBQUM7QUFBQSxJQUNyQyxDQUFDO0FBQUEsRUFDTDtBQUVPLFdBQVMsdUJBQXVCLFFBQVEsWUFBWTtBQUN2RCxXQUFPLFNBQVUsUUFBUSxPQUFLLE1BQU07QUFDaEMsYUFBTyxrQkFBa0IsUUFBUSxRQUFRLFlBQVksSUFBSTtBQUFBLElBQzdEO0FBQUEsRUFDSjs7O0FGM0ZBLE1BQUksT0FBTyx1QkFBdUIsWUFBWSxTQUFTO0FBRXZELE1BQUksbUJBQW1CO0FBQ3ZCLE1BQUksZ0JBQWdCO0FBS2IsV0FBUyxRQUFRLE1BQU07QUFDMUIsU0FBSyxLQUFLLGtCQUFrQixFQUFDLEtBQUksQ0FBQztBQUFBLEVBQ3RDO0FBTU8sV0FBUyxPQUFPO0FBQ25CLFdBQU8sS0FBSyxhQUFhO0FBQUEsRUFDN0I7OztBR2hDQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFjQSxNQUFJQSxRQUFPLHVCQUF1QixZQUFZLFdBQVc7QUFFekQsTUFBSSxVQUFVO0FBQUEsSUFDVixNQUFNO0FBQUEsSUFDTixNQUFNO0FBQUEsSUFDTixNQUFNO0FBQUEsRUFDVjtBQUtPLFdBQVMsT0FBTztBQUNuQixTQUFLQSxNQUFLLFFBQVEsSUFBSTtBQUFBLEVBQzFCO0FBS08sV0FBUyxPQUFPO0FBQ25CLFNBQUtBLE1BQUssUUFBUSxJQUFJO0FBQUEsRUFDMUI7QUFNTyxXQUFTLE9BQU87QUFDbkIsU0FBS0EsTUFBSyxRQUFRLElBQUk7QUFBQSxFQUMxQjs7O0FDMUNBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQWtCQSxNQUFJQyxRQUFPLHVCQUF1QixZQUFZLE9BQU87QUFFckQsTUFBSSxnQkFBZ0I7QUFDcEIsTUFBSSxvQkFBb0I7QUFDeEIsTUFBSSxvQkFBb0I7QUFNakIsV0FBUyxTQUFTO0FBQ3JCLFdBQU9BLE1BQUssYUFBYTtBQUFBLEVBQzdCO0FBTU8sV0FBUyxhQUFhO0FBQ3pCLFdBQU9BLE1BQUssaUJBQWlCO0FBQUEsRUFDakM7QUFPTyxXQUFTLGFBQWE7QUFDekIsV0FBT0EsTUFBSyxpQkFBaUI7QUFBQSxFQUNqQzs7O0FDL0NBO0FBQUE7QUFBQTtBQUFBO0FBY0EsTUFBSUMsUUFBTyx1QkFBdUIsWUFBWSxNQUFNO0FBRXBELE1BQUksbUJBQW1CO0FBTWhCLFdBQVMsYUFBYTtBQUN6QixXQUFPQSxNQUFLLGdCQUFnQjtBQUFBLEVBQ2hDOzs7QUN4QkE7QUFBQTtBQUFBO0FBQUE7QUFjQSxNQUFJQyxRQUFPLHVCQUF1QixZQUFZLE9BQU87QUFFckQsTUFBSSxpQkFBaUI7QUFNZCxXQUFTLFFBQVEsS0FBSztBQUN6QixTQUFLQSxNQUFLLGdCQUFnQixFQUFDLElBQUcsQ0FBQztBQUFBLEVBQ25DOzs7QUNSQSxNQUFJQyxRQUFPLHVCQUF1QixZQUFZLElBQUk7QUFFbEQsTUFBSSxjQUFjO0FBRWxCLE1BQUksZ0JBQWdCLG9CQUFJLElBQUk7QUFFNUIsV0FBUyxhQUFhO0FBQ2xCLFFBQUk7QUFDSixPQUFHO0FBQ0MsZUFBUyxPQUFPO0FBQUEsSUFDcEIsU0FBUyxjQUFjLElBQUksTUFBTTtBQUNqQyxXQUFPO0FBQUEsRUFDWDtBQUVPLFdBQVMsYUFBYSxJQUFJLE1BQU0sUUFBUTtBQUMzQyxRQUFJLElBQUksY0FBYyxJQUFJLEVBQUU7QUFDNUIsUUFBSSxHQUFHO0FBQ0gsVUFBSSxRQUFRO0FBQ1IsVUFBRSxRQUFRLEtBQUssTUFBTSxJQUFJLENBQUM7QUFBQSxNQUM5QixPQUFPO0FBQ0gsVUFBRSxRQUFRLElBQUk7QUFBQSxNQUNsQjtBQUNBLG9CQUFjLE9BQU8sRUFBRTtBQUFBLElBQzNCO0FBQUEsRUFDSjtBQUVPLFdBQVMsa0JBQWtCLElBQUksU0FBUztBQUMzQyxRQUFJLElBQUksY0FBYyxJQUFJLEVBQUU7QUFDNUIsUUFBSSxHQUFHO0FBQ0gsUUFBRSxPQUFPLE9BQU87QUFDaEIsb0JBQWMsT0FBTyxFQUFFO0FBQUEsSUFDM0I7QUFBQSxFQUNKO0FBRUEsV0FBUyxZQUFZLE1BQU0sU0FBUztBQUNoQyxXQUFPLElBQUksUUFBUSxDQUFDLFNBQVMsV0FBVztBQUNwQyxVQUFJLEtBQUssV0FBVztBQUNwQixnQkFBVSxXQUFXLENBQUM7QUFDdEIsY0FBUSxTQUFTLElBQUk7QUFFckIsb0JBQWMsSUFBSSxJQUFJLEVBQUMsU0FBUyxPQUFNLENBQUM7QUFDdkMsTUFBQUEsTUFBSyxNQUFNLE9BQU8sRUFBRSxNQUFNLENBQUMsVUFBVTtBQUNqQyxlQUFPLEtBQUs7QUFDWixzQkFBYyxPQUFPLEVBQUU7QUFBQSxNQUMzQixDQUFDO0FBQUEsSUFDTCxDQUFDO0FBQUEsRUFDTDtBQUVPLFdBQVMsS0FBSyxTQUFTO0FBQzFCLFdBQU8sWUFBWSxhQUFhLE9BQU87QUFBQSxFQUMzQztBQUVPLFdBQVMsV0FBVyxTQUFTLE1BQU07QUFHdEMsUUFBSSxPQUFPLFNBQVMsWUFBWSxLQUFLLE1BQU0sR0FBRyxFQUFFLFdBQVcsR0FBRztBQUMxRCxZQUFNLElBQUksTUFBTSxvRUFBb0U7QUFBQSxJQUN4RjtBQUVBLFFBQUksUUFBUSxLQUFLLE1BQU0sR0FBRztBQUUxQixXQUFPLFlBQVksYUFBYTtBQUFBLE1BQzVCLGFBQWEsTUFBTSxDQUFDO0FBQUEsTUFDcEIsWUFBWSxNQUFNLENBQUM7QUFBQSxNQUNuQixZQUFZLE1BQU0sQ0FBQztBQUFBLE1BQ25CO0FBQUEsSUFDSixDQUFDO0FBQUEsRUFDTDtBQUVPLFdBQVMsU0FBUyxhQUFhLE1BQU07QUFDeEMsV0FBTyxZQUFZLGFBQWE7QUFBQSxNQUM1QjtBQUFBLE1BQ0E7QUFBQSxJQUNKLENBQUM7QUFBQSxFQUNMO0FBU08sV0FBUyxPQUFPLFlBQVksZUFBZSxNQUFNO0FBQ3BELFdBQU8sWUFBWSxhQUFhO0FBQUEsTUFDNUIsYUFBYTtBQUFBLE1BQ2IsWUFBWTtBQUFBLE1BQ1o7QUFBQSxNQUNBO0FBQUEsSUFDSixDQUFDO0FBQUEsRUFDTDs7O0FDdEZBLE1BQUksZUFBZTtBQUNuQixNQUFJLGlCQUFpQjtBQUNyQixNQUFJLG1CQUFtQjtBQUN2QixNQUFJLHFCQUFxQjtBQUN6QixNQUFJLGdCQUFnQjtBQUNwQixNQUFJLGFBQWE7QUFDakIsTUFBSSxtQkFBbUI7QUFDdkIsTUFBSSxtQkFBbUI7QUFDdkIsTUFBSSx1QkFBdUI7QUFDM0IsTUFBSSw0QkFBNEI7QUFDaEMsTUFBSSx5QkFBeUI7QUFDN0IsTUFBSSxlQUFlO0FBQ25CLE1BQUksYUFBYTtBQUNqQixNQUFJLGlCQUFpQjtBQUNyQixNQUFJLG1CQUFtQjtBQUN2QixNQUFJLHVCQUF1QjtBQUMzQixNQUFJLGlCQUFpQjtBQUNyQixNQUFJLG1CQUFtQjtBQUN2QixNQUFJLGdCQUFnQjtBQUNwQixNQUFJLGFBQWE7QUFDakIsTUFBSSxjQUFjO0FBQ2xCLE1BQUksNEJBQTRCO0FBQ2hDLE1BQUkscUJBQXFCO0FBQ3pCLE1BQUksY0FBYztBQUNsQixNQUFJLGVBQWU7QUFDbkIsTUFBSSxlQUFlO0FBQ25CLE1BQUksZ0JBQWdCO0FBQ3BCLE1BQUksa0JBQWtCO0FBQ3RCLE1BQUkscUJBQXFCO0FBQ3pCLE1BQUkscUJBQXFCO0FBRWxCLFdBQVMsVUFBVSxZQUFZO0FBQ2xDLFFBQUlDLFNBQU8sdUJBQXVCLFlBQVksUUFBUSxVQUFVO0FBQ2hFLFdBQU87QUFBQTtBQUFBO0FBQUE7QUFBQSxNQUtILFFBQVEsTUFBTSxLQUFLQSxPQUFLLFlBQVk7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBLE1BTXBDLFVBQVUsQ0FBQyxVQUFVLEtBQUtBLE9BQUssZ0JBQWdCLEVBQUMsTUFBSyxDQUFDO0FBQUE7QUFBQTtBQUFBO0FBQUEsTUFLdEQsWUFBWSxNQUFNLEtBQUtBLE9BQUssZ0JBQWdCO0FBQUE7QUFBQTtBQUFBO0FBQUEsTUFLNUMsY0FBYyxNQUFNLEtBQUtBLE9BQUssa0JBQWtCO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBLE1BT2hELFNBQVMsQ0FBQyxPQUFPLFdBQVdBLE9BQUssZUFBZSxFQUFDLE9BQU0sT0FBTSxDQUFDO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQSxNQU05RCxNQUFNLE1BQU07QUFBRSxlQUFPQSxPQUFLLFVBQVU7QUFBQSxNQUFHO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBLE1BT3ZDLFlBQVksQ0FBQyxPQUFPLFdBQVcsS0FBS0EsT0FBSyxrQkFBa0IsRUFBQyxPQUFNLE9BQU0sQ0FBQztBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQSxNQU96RSxZQUFZLENBQUMsT0FBTyxXQUFXLEtBQUtBLE9BQUssa0JBQWtCLEVBQUMsT0FBTSxPQUFNLENBQUM7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBLE1BTXpFLGdCQUFnQixDQUFDLFVBQVUsS0FBS0EsT0FBSyxzQkFBc0IsRUFBQyxhQUFZLE1BQUssQ0FBQztBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQSxNQU85RSxxQkFBcUIsQ0FBQyxHQUFHLE1BQU1BLE9BQUssMkJBQTJCLEVBQUMsR0FBRSxFQUFDLENBQUM7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBLE1BTXBFLGtCQUFrQixNQUFNO0FBQUUsZUFBT0EsT0FBSyxzQkFBc0I7QUFBQSxNQUFHO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQSxNQU0vRCxRQUFRLE1BQU07QUFBRSxlQUFPQSxPQUFLLFlBQVk7QUFBQSxNQUFHO0FBQUE7QUFBQTtBQUFBO0FBQUEsTUFLM0MsTUFBTSxNQUFNLEtBQUtBLE9BQUssVUFBVTtBQUFBO0FBQUE7QUFBQTtBQUFBLE1BS2hDLFVBQVUsTUFBTSxLQUFLQSxPQUFLLGNBQWM7QUFBQTtBQUFBO0FBQUE7QUFBQSxNQUt4QyxNQUFNLE1BQU0sS0FBS0EsT0FBSyxVQUFVO0FBQUE7QUFBQTtBQUFBO0FBQUEsTUFLaEMsT0FBTyxNQUFNLEtBQUtBLE9BQUssV0FBVztBQUFBO0FBQUE7QUFBQTtBQUFBLE1BS2xDLGdCQUFnQixNQUFNLEtBQUtBLE9BQUssb0JBQW9CO0FBQUE7QUFBQTtBQUFBO0FBQUEsTUFLcEQsWUFBWSxNQUFNLEtBQUtBLE9BQUssZ0JBQWdCO0FBQUE7QUFBQTtBQUFBO0FBQUEsTUFLNUMsVUFBVSxNQUFNLEtBQUtBLE9BQUssY0FBYztBQUFBO0FBQUE7QUFBQTtBQUFBLE1BS3hDLFlBQVksTUFBTSxLQUFLQSxPQUFLLGdCQUFnQjtBQUFBO0FBQUE7QUFBQTtBQUFBLE1BSzVDLFNBQVMsTUFBTSxLQUFLQSxPQUFLLGFBQWE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBLE1BU3RDLHFCQUFxQixDQUFDLEdBQUcsR0FBRyxHQUFHLE1BQU0sS0FBS0EsT0FBSywyQkFBMkIsRUFBQyxHQUFHLEdBQUcsR0FBRyxFQUFDLENBQUM7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBLE1BTXRGLGNBQWMsQ0FBQ0MsZUFBYyxLQUFLRCxPQUFLLG9CQUFvQixFQUFDLFdBQUFDLFdBQVMsQ0FBQztBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUEsTUFNdEUsT0FBTyxNQUFNO0FBQUUsZUFBT0QsT0FBSyxXQUFXO0FBQUEsTUFBRztBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUEsTUFNekMsUUFBUSxNQUFNO0FBQUUsZUFBT0EsT0FBSyxZQUFZO0FBQUEsTUFBRztBQUFBO0FBQUE7QUFBQTtBQUFBLE1BSzNDLFFBQVEsTUFBTSxLQUFLQSxPQUFLLFlBQVk7QUFBQTtBQUFBO0FBQUE7QUFBQSxNQUtwQyxTQUFTLE1BQU0sS0FBS0EsT0FBSyxhQUFhO0FBQUE7QUFBQTtBQUFBO0FBQUEsTUFLdEMsV0FBVyxNQUFNLEtBQUtBLE9BQUssZUFBZTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUEsTUFNMUMsY0FBYyxNQUFNO0FBQUUsZUFBT0EsT0FBSyxrQkFBa0I7QUFBQSxNQUFHO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQSxNQU12RCxjQUFjLENBQUMsY0FBYyxLQUFLQSxPQUFLLG9CQUFvQixFQUFDLFVBQVMsQ0FBQztBQUFBLElBQzFFO0FBQUEsRUFDSjs7O0FDak5BLE1BQUlFLFFBQU8sdUJBQXVCLFlBQVksTUFBTTtBQUNwRCxNQUFJLFlBQVk7QUFPaEIsTUFBTSxXQUFOLE1BQWU7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBLElBUVgsWUFBWSxXQUFXLFVBQVUsY0FBYztBQUMzQyxXQUFLLFlBQVk7QUFFakIsV0FBSyxlQUFlLGdCQUFnQjtBQUdwQyxXQUFLLFdBQVcsQ0FBQyxTQUFTO0FBQ3RCLGlCQUFTLElBQUk7QUFFYixZQUFJLEtBQUssaUJBQWlCLElBQUk7QUFDMUIsaUJBQU87QUFBQSxRQUNYO0FBRUEsYUFBSyxnQkFBZ0I7QUFDckIsZUFBTyxLQUFLLGlCQUFpQjtBQUFBLE1BQ2pDO0FBQUEsSUFDSjtBQUFBLEVBQ0o7QUFVTyxNQUFNLGFBQU4sTUFBaUI7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQSxJQU9wQixZQUFZLE1BQU0sT0FBTyxNQUFNO0FBQzNCLFdBQUssT0FBTztBQUNaLFdBQUssT0FBTztBQUFBLElBQ2hCO0FBQUEsRUFDSjtBQUVPLE1BQU0saUJBQWlCLG9CQUFJLElBQUk7QUFXL0IsV0FBUyxXQUFXLFdBQVcsVUFBVSxjQUFjO0FBQzFELFFBQUksWUFBWSxlQUFlLElBQUksU0FBUyxLQUFLLENBQUM7QUFDbEQsVUFBTSxlQUFlLElBQUksU0FBUyxXQUFXLFVBQVUsWUFBWTtBQUNuRSxjQUFVLEtBQUssWUFBWTtBQUMzQixtQkFBZSxJQUFJLFdBQVcsU0FBUztBQUN2QyxXQUFPLE1BQU0sWUFBWSxZQUFZO0FBQUEsRUFDekM7QUFVTyxXQUFTLEdBQUcsV0FBVyxVQUFVO0FBQ3BDLFdBQU8sV0FBVyxXQUFXLFVBQVUsRUFBRTtBQUFBLEVBQzdDO0FBVU8sV0FBUyxLQUFLLFdBQVcsVUFBVTtBQUN0QyxXQUFPLFdBQVcsV0FBVyxVQUFVLENBQUM7QUFBQSxFQUM1QztBQU9BLFdBQVMsWUFBWSxVQUFVO0FBQzNCLFVBQU0sWUFBWSxTQUFTO0FBRTNCLFFBQUksWUFBWSxlQUFlLElBQUksU0FBUyxFQUFFLE9BQU8sT0FBSyxNQUFNLFFBQVE7QUFDeEUsUUFBSSxVQUFVLFdBQVcsR0FBRztBQUN4QixxQkFBZSxPQUFPLFNBQVM7QUFBQSxJQUNuQyxPQUFPO0FBQ0gscUJBQWUsSUFBSSxXQUFXLFNBQVM7QUFBQSxJQUMzQztBQUFBLEVBQ0o7QUFRTyxXQUFTLG1CQUFtQixPQUFPO0FBQ3RDLFFBQUksWUFBWSxlQUFlLElBQUksTUFBTSxJQUFJO0FBQzdDLFFBQUksV0FBVztBQUVYLFVBQUksV0FBVyxDQUFDO0FBQ2hCLGdCQUFVLFFBQVEsY0FBWTtBQUMxQixZQUFJLFNBQVMsU0FBUyxTQUFTLEtBQUs7QUFDcEMsWUFBSSxRQUFRO0FBQ1IsbUJBQVMsS0FBSyxRQUFRO0FBQUEsUUFDMUI7QUFBQSxNQUNKLENBQUM7QUFFRCxVQUFJLFNBQVMsU0FBUyxHQUFHO0FBQ3JCLG9CQUFZLFVBQVUsT0FBTyxPQUFLLENBQUMsU0FBUyxTQUFTLENBQUMsQ0FBQztBQUN2RCxZQUFJLFVBQVUsV0FBVyxHQUFHO0FBQ3hCLHlCQUFlLE9BQU8sTUFBTSxJQUFJO0FBQUEsUUFDcEMsT0FBTztBQUNILHlCQUFlLElBQUksTUFBTSxNQUFNLFNBQVM7QUFBQSxRQUM1QztBQUFBLE1BQ0o7QUFBQSxJQUNKO0FBQUEsRUFDSjtBQVdPLFdBQVMsSUFBSSxjQUFjLHNCQUFzQjtBQUNwRCxRQUFJLGlCQUFpQixDQUFDLFdBQVcsR0FBRyxvQkFBb0I7QUFDeEQsbUJBQWUsUUFBUSxDQUFBQyxlQUFhO0FBQ2hDLHFCQUFlLE9BQU9BLFVBQVM7QUFBQSxJQUNuQyxDQUFDO0FBQUEsRUFDTDtBQU9PLFdBQVMsU0FBUztBQUNyQixtQkFBZSxNQUFNO0FBQUEsRUFDekI7QUFNTyxXQUFTLEtBQUssT0FBTztBQUN4QixTQUFLRCxNQUFLLFdBQVcsS0FBSztBQUFBLEVBQzlCOzs7QUMzS0EsTUFBSUUsUUFBTyx1QkFBdUIsWUFBWSxNQUFNO0FBRXBELE1BQUksYUFBYTtBQUNqQixNQUFJLGdCQUFnQjtBQUNwQixNQUFJLGNBQWM7QUFDbEIsTUFBSSxpQkFBaUI7QUFDckIsTUFBSSxpQkFBaUI7QUFDckIsTUFBSSxpQkFBaUI7QUFHckIsTUFBSSxrQkFBa0Isb0JBQUksSUFBSTtBQUU5QixXQUFTQyxjQUFhO0FBQ2xCLFFBQUk7QUFDSixPQUFHO0FBQ0MsZUFBUyxPQUFPO0FBQUEsSUFDcEIsU0FBUyxnQkFBZ0IsSUFBSSxNQUFNO0FBQ25DLFdBQU87QUFBQSxFQUNYO0FBRU8sV0FBUyxlQUFlLElBQUksTUFBTSxRQUFRO0FBQzdDLFFBQUksSUFBSSxnQkFBZ0IsSUFBSSxFQUFFO0FBQzlCLFFBQUksR0FBRztBQUNILFVBQUksUUFBUTtBQUNSLFVBQUUsUUFBUSxLQUFLLE1BQU0sSUFBSSxDQUFDO0FBQUEsTUFDOUIsT0FBTztBQUNILFVBQUUsUUFBUSxJQUFJO0FBQUEsTUFDbEI7QUFDQSxzQkFBZ0IsT0FBTyxFQUFFO0FBQUEsSUFDN0I7QUFBQSxFQUNKO0FBQ08sV0FBUyxvQkFBb0IsSUFBSSxTQUFTO0FBQzdDLFFBQUksSUFBSSxnQkFBZ0IsSUFBSSxFQUFFO0FBQzlCLFFBQUksR0FBRztBQUNILFFBQUUsT0FBTyxPQUFPO0FBQ2hCLHNCQUFnQixPQUFPLEVBQUU7QUFBQSxJQUM3QjtBQUFBLEVBQ0o7QUFFQSxXQUFTLE9BQU8sTUFBTSxTQUFTO0FBQzNCLFdBQU8sSUFBSSxRQUFRLENBQUMsU0FBUyxXQUFXO0FBQ3BDLFVBQUksS0FBS0EsWUFBVztBQUNwQixnQkFBVSxXQUFXLENBQUM7QUFDdEIsY0FBUSxXQUFXLElBQUk7QUFDdkIsc0JBQWdCLElBQUksSUFBSSxFQUFDLFNBQVMsT0FBTSxDQUFDO0FBQ3pDLE1BQUFELE1BQUssTUFBTSxPQUFPLEVBQUUsTUFBTSxDQUFDLFVBQVU7QUFDakMsZUFBTyxLQUFLO0FBQ1osd0JBQWdCLE9BQU8sRUFBRTtBQUFBLE1BQzdCLENBQUM7QUFBQSxJQUNMLENBQUM7QUFBQSxFQUNMO0FBUU8sV0FBUyxLQUFLLFNBQVM7QUFDMUIsV0FBTyxPQUFPLFlBQVksT0FBTztBQUFBLEVBQ3JDO0FBT08sV0FBUyxRQUFRLFNBQVM7QUFDN0IsV0FBTyxPQUFPLGVBQWUsT0FBTztBQUFBLEVBQ3hDO0FBT08sV0FBU0UsT0FBTSxTQUFTO0FBQzNCLFdBQU8sT0FBTyxhQUFhLE9BQU87QUFBQSxFQUN0QztBQU9PLFdBQVMsU0FBUyxTQUFTO0FBQzlCLFdBQU8sT0FBTyxnQkFBZ0IsT0FBTztBQUFBLEVBQ3pDO0FBT08sV0FBUyxTQUFTLFNBQVM7QUFDOUIsV0FBTyxPQUFPLGdCQUFnQixPQUFPO0FBQUEsRUFDekM7QUFPTyxXQUFTLFNBQVMsU0FBUztBQUM5QixXQUFPLE9BQU8sZ0JBQWdCLE9BQU87QUFBQSxFQUN6Qzs7O0FDN0hBLE1BQUlDLFFBQU8sdUJBQXVCLFlBQVksV0FBVztBQUV6RCxNQUFJLGtCQUFrQjtBQUV0QixXQUFTLGdCQUFnQixJQUFJLEdBQUcsR0FBRyxNQUFNO0FBQ3JDLFNBQUtBLE1BQUssaUJBQWlCLEVBQUMsSUFBSSxHQUFHLEdBQUcsS0FBSSxDQUFDO0FBQUEsRUFDL0M7QUFFTyxXQUFTLG9CQUFvQjtBQUNoQyxXQUFPLGlCQUFpQixlQUFlLGtCQUFrQjtBQUFBLEVBQzdEO0FBRUEsV0FBUyxtQkFBbUIsT0FBTztBQUUvQixRQUFJLFVBQVUsTUFBTTtBQUNwQixRQUFJLG9CQUFvQixPQUFPLGlCQUFpQixPQUFPLEVBQUUsaUJBQWlCLHNCQUFzQjtBQUNoRyx3QkFBb0Isb0JBQW9CLGtCQUFrQixLQUFLLElBQUk7QUFDbkUsUUFBSSxtQkFBbUI7QUFDbkIsWUFBTSxlQUFlO0FBQ3JCLFVBQUksd0JBQXdCLE9BQU8saUJBQWlCLE9BQU8sRUFBRSxpQkFBaUIsMkJBQTJCO0FBQ3pHLHNCQUFnQixtQkFBbUIsTUFBTSxTQUFTLE1BQU0sU0FBUyxxQkFBcUI7QUFDdEY7QUFBQSxJQUNKO0FBRUEsOEJBQTBCLEtBQUs7QUFBQSxFQUNuQztBQVVBLFdBQVMsMEJBQTBCLE9BQU87QUFFdEMsUUFBSSxNQUFPO0FBQ1A7QUFBQSxJQUNKO0FBR0EsVUFBTSxVQUFVLE1BQU07QUFDdEIsVUFBTSxnQkFBZ0IsT0FBTyxpQkFBaUIsT0FBTztBQUNyRCxVQUFNLDJCQUEyQixjQUFjLGlCQUFpQix1QkFBdUIsRUFBRSxLQUFLO0FBQzlGLFlBQVEsMEJBQTBCO0FBQUEsTUFDOUIsS0FBSztBQUNEO0FBQUEsTUFDSixLQUFLO0FBQ0QsY0FBTSxlQUFlO0FBQ3JCO0FBQUEsTUFDSjtBQUVJLFlBQUksUUFBUSxtQkFBbUI7QUFDM0I7QUFBQSxRQUNKO0FBR0EsY0FBTSxZQUFZLE9BQU8sYUFBYTtBQUN0QyxjQUFNLGVBQWdCLFVBQVUsU0FBUyxFQUFFLFNBQVM7QUFDcEQsWUFBSSxjQUFjO0FBQ2QsbUJBQVMsSUFBSSxHQUFHLElBQUksVUFBVSxZQUFZLEtBQUs7QUFDM0Msa0JBQU0sUUFBUSxVQUFVLFdBQVcsQ0FBQztBQUNwQyxrQkFBTSxRQUFRLE1BQU0sZUFBZTtBQUNuQyxxQkFBUyxJQUFJLEdBQUcsSUFBSSxNQUFNLFFBQVEsS0FBSztBQUNuQyxvQkFBTSxPQUFPLE1BQU0sQ0FBQztBQUNwQixrQkFBSSxTQUFTLGlCQUFpQixLQUFLLE1BQU0sS0FBSyxHQUFHLE1BQU0sU0FBUztBQUM1RDtBQUFBLGNBQ0o7QUFBQSxZQUNKO0FBQUEsVUFDSjtBQUFBLFFBQ0o7QUFFQSxZQUFJLFFBQVEsWUFBWSxXQUFXLFFBQVEsWUFBWSxZQUFZO0FBQy9ELGNBQUksZ0JBQWlCLENBQUMsUUFBUSxZQUFZLENBQUMsUUFBUSxVQUFXO0FBQzFEO0FBQUEsVUFDSjtBQUFBLFFBQ0o7QUFHQSxjQUFNLGVBQWU7QUFBQSxJQUM3QjtBQUFBLEVBQ0o7OztBQ2hGQSxXQUFTLFVBQVUsV0FBVyxPQUFLLE1BQU07QUFDckMsUUFBSSxRQUFRLElBQUksV0FBVyxXQUFXLElBQUk7QUFDMUMsU0FBSyxLQUFLO0FBQUEsRUFDZDtBQUVBLFdBQVMsdUJBQXVCO0FBQzVCLFVBQU0sV0FBVyxTQUFTLGlCQUFpQixhQUFhO0FBQ3hELGFBQVMsUUFBUSxTQUFVLFNBQVM7QUFDaEMsWUFBTSxZQUFZLFFBQVEsYUFBYSxXQUFXO0FBQ2xELFlBQU0sVUFBVSxRQUFRLGFBQWEsYUFBYTtBQUNsRCxZQUFNLFVBQVUsUUFBUSxhQUFhLGFBQWEsS0FBSztBQUV2RCxVQUFJLFdBQVcsV0FBWTtBQUN2QixZQUFJLFNBQVM7QUFDVCxtQkFBUyxFQUFDLE9BQU8sV0FBVyxTQUFRLFNBQVMsVUFBVSxPQUFPLFNBQVEsQ0FBQyxFQUFDLE9BQU0sTUFBSyxHQUFFLEVBQUMsT0FBTSxNQUFNLFdBQVUsS0FBSSxDQUFDLEVBQUMsQ0FBQyxFQUFFLEtBQUssU0FBVSxRQUFRO0FBQ3hJLGdCQUFJLFdBQVcsTUFBTTtBQUNqQix3QkFBVSxTQUFTO0FBQUEsWUFDdkI7QUFBQSxVQUNKLENBQUM7QUFDRDtBQUFBLFFBQ0o7QUFDQSxrQkFBVSxTQUFTO0FBQUEsTUFDdkI7QUFHQSxjQUFRLG9CQUFvQixTQUFTLFFBQVE7QUFHN0MsY0FBUSxpQkFBaUIsU0FBUyxRQUFRO0FBQUEsSUFDOUMsQ0FBQztBQUFBLEVBQ0w7QUFFQSxXQUFTLGlCQUFpQixRQUFRO0FBQzlCLFFBQUksTUFBTSxPQUFPLE1BQU0sTUFBTSxRQUFXO0FBQ3BDLGNBQVEsSUFBSSxtQkFBbUIsU0FBUyxZQUFZO0FBQUEsSUFDeEQ7QUFDQSxVQUFNLE9BQU8sTUFBTSxFQUFFO0FBQUEsRUFDekI7QUFFQSxXQUFTLHdCQUF3QjtBQUM3QixVQUFNLFdBQVcsU0FBUyxpQkFBaUIsY0FBYztBQUN6RCxhQUFTLFFBQVEsU0FBVSxTQUFTO0FBQ2hDLFlBQU0sZUFBZSxRQUFRLGFBQWEsWUFBWTtBQUN0RCxZQUFNLFVBQVUsUUFBUSxhQUFhLGFBQWE7QUFDbEQsWUFBTSxVQUFVLFFBQVEsYUFBYSxhQUFhLEtBQUs7QUFFdkQsVUFBSSxXQUFXLFdBQVk7QUFDdkIsWUFBSSxTQUFTO0FBQ1QsbUJBQVMsRUFBQyxPQUFPLFdBQVcsU0FBUSxTQUFTLFNBQVEsQ0FBQyxFQUFDLE9BQU0sTUFBSyxHQUFFLEVBQUMsT0FBTSxNQUFNLFdBQVUsS0FBSSxDQUFDLEVBQUMsQ0FBQyxFQUFFLEtBQUssU0FBVSxRQUFRO0FBQ3ZILGdCQUFJLFdBQVcsTUFBTTtBQUNqQiwrQkFBaUIsWUFBWTtBQUFBLFlBQ2pDO0FBQUEsVUFDSixDQUFDO0FBQ0Q7QUFBQSxRQUNKO0FBQ0EseUJBQWlCLFlBQVk7QUFBQSxNQUNqQztBQUdBLGNBQVEsb0JBQW9CLFNBQVMsUUFBUTtBQUc3QyxjQUFRLGlCQUFpQixTQUFTLFFBQVE7QUFBQSxJQUM5QyxDQUFDO0FBQUEsRUFDTDtBQUVBLFdBQVMsNEJBQTRCO0FBQ2pDLFVBQU0sV0FBVyxTQUFTLGlCQUFpQixlQUFlO0FBQzFELGFBQVMsUUFBUSxTQUFVLFNBQVM7QUFDaEMsWUFBTSxNQUFNLFFBQVEsYUFBYSxhQUFhO0FBQzlDLFlBQU0sVUFBVSxRQUFRLGFBQWEsYUFBYTtBQUNsRCxZQUFNLFVBQVUsUUFBUSxhQUFhLGFBQWEsS0FBSztBQUV2RCxVQUFJLFdBQVcsV0FBWTtBQUN2QixZQUFJLFNBQVM7QUFDVCxtQkFBUyxFQUFDLE9BQU8sV0FBVyxTQUFRLFNBQVMsU0FBUSxDQUFDLEVBQUMsT0FBTSxNQUFLLEdBQUUsRUFBQyxPQUFNLE1BQU0sV0FBVSxLQUFJLENBQUMsRUFBQyxDQUFDLEVBQUUsS0FBSyxTQUFVLFFBQVE7QUFDdkgsZ0JBQUksV0FBVyxNQUFNO0FBQ2pCLG1CQUFLLE1BQU0sUUFBUSxRQUFRLEdBQUc7QUFBQSxZQUNsQztBQUFBLFVBQ0osQ0FBQztBQUNEO0FBQUEsUUFDSjtBQUNBLGFBQUssTUFBTSxRQUFRLFFBQVEsR0FBRztBQUFBLE1BQ2xDO0FBR0EsY0FBUSxvQkFBb0IsU0FBUyxRQUFRO0FBRzdDLGNBQVEsaUJBQWlCLFNBQVMsUUFBUTtBQUFBLElBQzlDLENBQUM7QUFBQSxFQUNMO0FBRU8sV0FBUyxZQUFZO0FBQ3hCLHlCQUFxQjtBQUNyQiwwQkFBc0I7QUFDdEIsOEJBQTBCO0FBQUEsRUFDOUI7OztBQ3hGTyxNQUFJLFNBQVMsU0FBUyxPQUFPO0FBQ2hDLFFBQUcsTUFBUztBQUNSLGFBQU8sUUFBUSxZQUFZLEtBQUs7QUFBQSxJQUNwQyxPQUFPO0FBQ0gsYUFBTyxnQkFBZ0IsU0FBUyxZQUFZLEtBQUs7QUFBQSxJQUNyRDtBQUFBLEVBQ0o7OztBQ1BBLE1BQUksUUFBUSxvQkFBSSxJQUFJO0FBRXBCLFdBQVMsYUFBYSxLQUFLO0FBQ3ZCLFVBQU0sTUFBTSxvQkFBSSxJQUFJO0FBRXBCLGVBQVcsQ0FBQyxLQUFLLEtBQUssS0FBSyxPQUFPLFFBQVEsR0FBRyxHQUFHO0FBQzVDLFVBQUksT0FBTyxVQUFVLFlBQVksVUFBVSxNQUFNO0FBQzdDLFlBQUksSUFBSSxLQUFLLGFBQWEsS0FBSyxDQUFDO0FBQUEsTUFDcEMsT0FBTztBQUNILFlBQUksSUFBSSxLQUFLLEtBQUs7QUFBQSxNQUN0QjtBQUFBLElBQ0o7QUFFQSxXQUFPO0FBQUEsRUFDWDtBQUVBLFFBQU0sY0FBYyxFQUFFLEtBQUssQ0FBQyxhQUFhO0FBQ3JDLGFBQVMsS0FBSyxFQUFFLEtBQUssQ0FBQyxTQUFTO0FBQzNCLGNBQVEsYUFBYSxJQUFJO0FBQUEsSUFDN0IsQ0FBQztBQUFBLEVBQ0wsQ0FBQztBQUdELFdBQVMsZ0JBQWdCLFdBQVc7QUFDaEMsVUFBTSxPQUFPLFVBQVUsTUFBTSxHQUFHO0FBQ2hDLFFBQUksUUFBUTtBQUVaLGVBQVcsT0FBTyxNQUFNO0FBQ3BCLFVBQUksaUJBQWlCLEtBQUs7QUFDdEIsZ0JBQVEsTUFBTSxJQUFJLEdBQUc7QUFBQSxNQUN6QixPQUFPO0FBQ0gsZ0JBQVEsTUFBTSxHQUFHO0FBQUEsTUFDckI7QUFFQSxVQUFJLFVBQVUsUUFBVztBQUNyQjtBQUFBLE1BQ0o7QUFBQSxJQUNKO0FBRUEsV0FBTztBQUFBLEVBQ1g7QUFFTyxXQUFTLFFBQVEsV0FBVztBQUMvQixXQUFPLGdCQUFnQixTQUFTO0FBQUEsRUFDcEM7OztBQ3pDQSxNQUFJLGFBQWE7QUFFVixXQUFTLFNBQVMsR0FBRztBQUN4QixRQUFJLE1BQU0sT0FBTyxpQkFBaUIsRUFBRSxNQUFNLEVBQUUsaUJBQWlCLHFCQUFxQjtBQUNsRixRQUFJLEtBQUs7QUFDTCxZQUFNLElBQUksS0FBSztBQUFBLElBQ25CO0FBRUEsUUFBSSxRQUFRLFFBQVE7QUFDaEIsYUFBTztBQUFBLElBQ1g7QUFHQSxRQUFJLEVBQUUsWUFBWSxHQUFHO0FBQ2pCLGFBQU87QUFBQSxJQUNYO0FBRUEsV0FBTyxFQUFFLFdBQVc7QUFBQSxFQUN4QjtBQUVPLFdBQVMsWUFBWTtBQUN4QixXQUFPLGlCQUFpQixhQUFhLFdBQVc7QUFDaEQsV0FBTyxpQkFBaUIsYUFBYSxXQUFXO0FBQ2hELFdBQU8saUJBQWlCLFdBQVcsU0FBUztBQUFBLEVBQ2hEO0FBRUEsTUFBSSxhQUFhO0FBQ2pCLE1BQUksWUFBWTtBQUVULFdBQVMsYUFBYSxPQUFPO0FBQ2hDLGdCQUFZO0FBQUEsRUFDaEI7QUFFQSxXQUFTLFdBQVcsR0FBRztBQUNuQixRQUFJLFlBQWE7QUFDYixhQUFPLFlBQVksVUFBVTtBQUM3QixhQUFPO0FBQUEsSUFDWDtBQUNBLFdBQU87QUFBQSxFQUNYO0FBRUEsV0FBUyxZQUFZLEdBQUc7QUFHcEIsUUFBSSxNQUFVO0FBQ1YsVUFBSSxXQUFXLEdBQUc7QUFDZDtBQUFBLE1BQ0o7QUFBQSxJQUNKO0FBQ0EsUUFBSSxTQUFTLENBQUMsR0FBRztBQUViLFVBQUksRUFBRSxVQUFVLEVBQUUsT0FBTyxlQUFlLEVBQUUsVUFBVSxFQUFFLE9BQU8sY0FBYztBQUN2RTtBQUFBLE1BQ0o7QUFDQSxtQkFBYTtBQUFBLElBQ2pCLE9BQU87QUFDSCxtQkFBYTtBQUFBLElBQ2pCO0FBQUEsRUFDSjtBQUVBLFdBQVMsVUFBVSxHQUFHO0FBQ2xCLFFBQUksZUFBZSxFQUFFLFlBQVksU0FBWSxFQUFFLFVBQVUsRUFBRTtBQUMzRCxRQUFJLGVBQWUsR0FBRztBQUNsQixjQUFRO0FBQUEsSUFDWjtBQUFBLEVBQ0o7QUFFTyxXQUFTLFVBQVU7QUFDdEIsYUFBUyxLQUFLLE1BQU0sU0FBUztBQUM3QixpQkFBYTtBQUFBLEVBQ2pCO0FBRUEsV0FBUyxVQUFVLFFBQVE7QUFDdkIsYUFBUyxnQkFBZ0IsTUFBTSxTQUFTLFVBQVU7QUFDbEQsaUJBQWE7QUFBQSxFQUNqQjtBQUVBLFdBQVMsWUFBWSxHQUFHO0FBQ3BCLFFBQUksWUFBWTtBQUNaLG1CQUFhO0FBQ2IsVUFBSSxlQUFlLEVBQUUsWUFBWSxTQUFZLEVBQUUsVUFBVSxFQUFFO0FBQzNELFVBQUksZUFBZSxHQUFHO0FBQ2xCLGVBQU8sTUFBTTtBQUFBLE1BQ2pCO0FBQ0E7QUFBQSxJQUNKO0FBRUEsUUFBSSxNQUFTO0FBQ1QsVUFBSSxXQUFXO0FBQ1gscUJBQWEsQ0FBQztBQUFBLE1BQ2xCO0FBQUEsSUFDSjtBQUFBLEVBQ0o7QUFFQSxNQUFJLGdCQUFnQjtBQUVwQixXQUFTLGFBQWEsR0FBRztBQUNyQixRQUFJLHFCQUFxQixRQUFRLDJCQUEyQixLQUFLO0FBQ2pFLFFBQUksb0JBQW9CLFFBQVEsMEJBQTBCLEtBQUs7QUFHL0QsUUFBSSxjQUFjLFFBQVEsbUJBQW1CLEtBQUs7QUFFbEQsUUFBSSxjQUFjLE9BQU8sYUFBYSxFQUFFLFVBQVU7QUFDbEQsUUFBSSxhQUFhLEVBQUUsVUFBVTtBQUM3QixRQUFJLFlBQVksRUFBRSxVQUFVO0FBQzVCLFFBQUksZUFBZSxPQUFPLGNBQWMsRUFBRSxVQUFVO0FBR3BELFFBQUksY0FBYyxPQUFPLGFBQWEsRUFBRSxVQUFXLG9CQUFvQjtBQUN2RSxRQUFJLGFBQWEsRUFBRSxVQUFXLG9CQUFvQjtBQUNsRCxRQUFJLFlBQVksRUFBRSxVQUFXLHFCQUFxQjtBQUNsRCxRQUFJLGVBQWUsT0FBTyxjQUFjLEVBQUUsVUFBVyxxQkFBcUI7QUFHMUUsUUFBSSxDQUFDLGNBQWMsQ0FBQyxlQUFlLENBQUMsYUFBYSxDQUFDLGdCQUFnQixlQUFlLFFBQVc7QUFDeEYsZ0JBQVU7QUFBQSxJQUNkLFdBRVMsZUFBZTtBQUFjLGdCQUFVLFdBQVc7QUFBQSxhQUNsRCxjQUFjO0FBQWMsZ0JBQVUsV0FBVztBQUFBLGFBQ2pELGNBQWM7QUFBVyxnQkFBVSxXQUFXO0FBQUEsYUFDOUMsYUFBYTtBQUFhLGdCQUFVLFdBQVc7QUFBQSxhQUMvQztBQUFZLGdCQUFVLFVBQVU7QUFBQSxhQUNoQztBQUFXLGdCQUFVLFVBQVU7QUFBQSxhQUMvQjtBQUFjLGdCQUFVLFVBQVU7QUFBQSxhQUNsQztBQUFhLGdCQUFVLFVBQVU7QUFBQSxFQUM5Qzs7O0FDcEhBLFNBQU8sUUFBUTtBQUFBLElBQ1gsR0FBRyxXQUFXLElBQUk7QUFBQSxJQUNsQixjQUFjLENBQUM7QUFBQSxJQUNmO0FBQUEsRUFDSjtBQUVBLFFBQU0scUJBQXFCLEVBQUUsS0FBSyxDQUFDLGFBQWE7QUFDNUMsYUFBUyxLQUFLLEVBQUUsS0FBSyxDQUFDLFNBQVM7QUFDM0IsYUFBTyxNQUFNLGVBQWU7QUFBQSxJQUNoQyxDQUFDO0FBQUEsRUFDTCxDQUFDO0FBR0QsU0FBTyxTQUFTO0FBQUEsSUFDWjtBQUFBLElBQ0E7QUFBQSxJQUNBO0FBQUEsSUFDQTtBQUFBLElBQ0E7QUFBQSxJQUNBO0FBQUEsSUFDQTtBQUFBLEVBQ0o7QUFFTyxXQUFTLFdBQVcsWUFBWTtBQUNuQyxXQUFPO0FBQUEsTUFDSCxXQUFXO0FBQUEsUUFDUCxHQUFHO0FBQUEsTUFDUDtBQUFBLE1BQ0EsYUFBYTtBQUFBLFFBQ1QsR0FBRztBQUFBLFFBQ0gsZ0JBQWdCQyxhQUFZO0FBQ3hCLGlCQUFPLFdBQVdBLFdBQVU7QUFBQSxRQUNoQztBQUFBLE1BQ0o7QUFBQSxNQUNBO0FBQUEsTUFDQTtBQUFBLE1BQ0E7QUFBQSxNQUNBO0FBQUEsTUFDQTtBQUFBLE1BQ0E7QUFBQSxNQUNBO0FBQUEsTUFDQSxLQUFLO0FBQUEsUUFDRCxRQUFRO0FBQUEsTUFDWjtBQUFBLE1BQ0EsUUFBUTtBQUFBLFFBQ0o7QUFBQSxRQUNBO0FBQUEsUUFDQSxPQUFBQztBQUFBLFFBQ0E7QUFBQSxRQUNBO0FBQUEsUUFDQTtBQUFBLE1BQ0o7QUFBQSxNQUNBLFFBQVE7QUFBQSxRQUNKO0FBQUEsUUFDQTtBQUFBLFFBQ0E7QUFBQSxRQUNBO0FBQUEsUUFDQTtBQUFBLFFBQ0E7QUFBQSxNQUNKO0FBQUEsTUFDQSxRQUFRLFVBQVUsVUFBVTtBQUFBLElBQ2hDO0FBQUEsRUFDSjtBQUVBLE1BQUksTUFBTztBQUNQLFlBQVEsSUFBSSxpQ0FBaUM7QUFBQSxFQUNqRDtBQUVBLG9CQUFrQjtBQUNsQixZQUFVO0FBRVYsV0FBUyxpQkFBaUIsb0JBQW9CLFdBQVc7QUFDckQsY0FBVTtBQUFBLEVBQ2QsQ0FBQzsiLAogICJuYW1lcyI6IFsiY2FsbCIsICJjYWxsIiwgImNhbGwiLCAiY2FsbCIsICJjYWxsIiwgImNhbGwiLCAicmVzaXphYmxlIiwgImNhbGwiLCAiZXZlbnROYW1lIiwgImNhbGwiLCAiZ2VuZXJhdGVJRCIsICJFcnJvciIsICJjYWxsIiwgIndpbmRvd05hbWUiLCAiRXJyb3IiXQp9Cg== diff --git a/v3/internal/runtime/runtime_debug_linux.go b/v3/internal/runtime/runtime_debug_linux.go new file mode 100644 index 000000000..c29408392 --- /dev/null +++ b/v3/internal/runtime/runtime_debug_linux.go @@ -0,0 +1,8 @@ +//go:build linux && !production + +package runtime + +import _ "embed" + +//go:embed runtime_debug_desktop_linux.js +var DesktopRuntime []byte diff --git a/v3/internal/runtime/runtime_debug_windows.go b/v3/internal/runtime/runtime_debug_windows.go new file mode 100644 index 000000000..09a3a9198 --- /dev/null +++ b/v3/internal/runtime/runtime_debug_windows.go @@ -0,0 +1,8 @@ +//go:build windows && !production + +package runtime + +import _ "embed" + +//go:embed runtime_debug_desktop_windows.js +var DesktopRuntime []byte diff --git a/v3/internal/runtime/runtime_production_darwin.go b/v3/internal/runtime/runtime_production_darwin.go new file mode 100644 index 000000000..be2411d9e --- /dev/null +++ b/v3/internal/runtime/runtime_production_darwin.go @@ -0,0 +1,8 @@ +//go:build darwin && production + +package runtime + +import _ "embed" + +//go:embed runtime_production_desktop_darwin.js +var DesktopRuntime []byte diff --git a/v3/internal/runtime/runtime_production_desktop_darwin.js b/v3/internal/runtime/runtime_production_desktop_darwin.js new file mode 100644 index 000000000..5fdb99e37 --- /dev/null +++ b/v3/internal/runtime/runtime_production_desktop_darwin.js @@ -0,0 +1 @@ +(()=>{var fe=Object.defineProperty;var g=(t,e)=>{for(var n in e)fe(t,n,{get:e[n],enumerable:!0})};var E={};g(E,{SetText:()=>xe,Text:()=>Ce});var me="useandom-26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict";var m=(t=21)=>{let e="",n=t;for(;n--;)e+=me[Math.random()*64|0];return e};var pe=window.location.origin+"/wails/runtime",l={Call:0,Clipboard:1,Application:2,Events:3,ContextMenu:4,Dialog:5,Window:6,Screens:7,System:8,Browser:9},D=m();function we(t,e,n,o){let i=new URL(pe);i.searchParams.append("object",t),i.searchParams.append("method",e);let r={headers:{}};return n&&(r.headers["x-wails-window-name"]=n),o&&i.searchParams.append("args",JSON.stringify(o)),r.headers["x-wails-client-id"]=D,new Promise((u,S)=>{fetch(i,r).then(s=>{if(s.ok)return s.headers.get("Content-Type")&&s.headers.get("Content-Type").indexOf("application/json")!==-1?s.json():s.text();S(Error(s.statusText))}).then(s=>u(s)).catch(s=>S(s))})}function a(t,e){return function(n,o=null){return we(t,n,e,o)}}var B=a(l.Clipboard),ge=0,he=1;function xe(t){B(ge,{text:t})}function Ce(){return B(he)}var L={};g(L,{Hide:()=>ve,Quit:()=>We,Show:()=>be});var y=a(l.Application),R={Hide:0,Show:1,Quit:2};function ve(){y(R.Hide)}function be(){y(R.Show)}function We(){y(R.Quit)}var k={};g(k,{GetAll:()=>Ee,GetCurrent:()=>Re,GetPrimary:()=>ye});var O=a(l.Screens),Se=0,Me=1,De=2;function Ee(){return O(Se)}function ye(){return O(Me)}function Re(){return O(De)}var N={};g(N,{IsDarkMode:()=>ke});var Le=a(l.System),Oe=0;function ke(){return Le(Oe)}var z={};g(z,{OpenURL:()=>Ie});var Ne=a(l.Browser),ze=0;function Ie(t){Ne(ze,{url:t})}var Ae=a(l.Call),x=0,d=new Map;function Te(){let t;do t=m();while(d.has(t));return t}function j(t,e,n){let o=d.get(t);o&&(n?o.resolve(JSON.parse(e)):o.resolve(e),d.delete(t))}function H(t,e){let n=d.get(t);n&&(n.reject(e),d.delete(t))}function C(t,e){return new Promise((n,o)=>{let i=Te();e=e||{},e["call-id"]=i,d.set(i,{resolve:n,reject:o}),Ae(t,e).catch(r=>{o(r),d.delete(i)})})}function U(t){return C(x,t)}function F(t,...e){if(typeof t!="string"||t.split(".").length!==3)throw new Error("CallByName requires a string in the format 'package.struct.method'");let n=t.split(".");return C(x,{packageName:n[0],structName:n[1],methodName:n[2],args:e})}function G(t,...e){return C(x,{methodID:t,args:e})}function Z(t,e,...n){return C(x,{packageName:"wails-plugins",structName:t,methodName:e,args:n})}var Pe=0,Be=1,je=2,He=3,Ue=4,Fe=5,Ge=6,Ze=7,Ye=8,Qe=9,Xe=10,Ve=11,qe=12,Je=13,_e=14,Ke=15,$e=16,et=17,tt=18,nt=19,ot=20,it=21,rt=22,lt=23,at=24,st=25,ut=26,ct=27,dt=28,ft=29;function Y(t){let e=a(l.Window,t);return{Center:()=>void e(Pe),SetTitle:n=>void e(Be,{title:n}),Fullscreen:()=>void e(je),UnFullscreen:()=>void e(He),SetSize:(n,o)=>e(Ue,{width:n,height:o}),Size:()=>e(Fe),SetMaxSize:(n,o)=>void e(Ge,{width:n,height:o}),SetMinSize:(n,o)=>void e(Ze,{width:n,height:o}),SetAlwaysOnTop:n=>void e(Ye,{alwaysOnTop:n}),SetRelativePosition:(n,o)=>e(Qe,{x:n,y:o}),RelativePosition:()=>e(Xe),Screen:()=>e(Ve),Hide:()=>void e(qe),Maximise:()=>void e(Je),Show:()=>void e(nt),Close:()=>void e(ot),ToggleMaximise:()=>void e(Ke),UnMaximise:()=>void e(_e),Minimise:()=>void e($e),UnMinimise:()=>void e(et),Restore:()=>void e(tt),SetBackgroundColour:(n,o,i,r)=>void e(it,{r:n,g:o,b:i,a:r}),SetResizable:n=>void e(rt,{resizable:n}),Width:()=>e(lt),Height:()=>e(at),ZoomIn:()=>void e(st),ZoomOut:()=>void e(ut),ZoomReset:()=>void e(ct),GetZoomLevel:()=>e(dt),SetZoomLevel:n=>void e(ft,{zoomLevel:n})}}var mt=a(l.Events),pt=0,I=class{constructor(e,n,o){this.eventName=e,this.maxCallbacks=o||-1,this.Callback=i=>(n(i),this.maxCallbacks===-1?!1:(this.maxCallbacks-=1,this.maxCallbacks===0))}},v=class{constructor(e,n=null){this.name=e,this.data=n}},c=new Map;function b(t,e,n){let o=c.get(t)||[],i=new I(t,e,n);return o.push(i),c.set(t,o),()=>wt(i)}function Q(t,e){return b(t,e,-1)}function X(t,e){return b(t,e,1)}function wt(t){let e=t.eventName,n=c.get(e).filter(o=>o!==t);n.length===0?c.delete(e):c.set(e,n)}function V(t){let e=c.get(t.name);if(e){let n=[];e.forEach(o=>{o.Callback(t)&&n.push(o)}),n.length>0&&(e=e.filter(o=>!n.includes(o)),e.length===0?c.delete(t.name):c.set(t.name,e))}}function q(t,...e){[t,...e].forEach(o=>{c.delete(o)})}function J(){c.clear()}function W(t){mt(pt,t)}var gt=a(l.Dialog),ht=0,xt=1,Ct=2,vt=3,bt=4,Wt=5,f=new Map;function St(){let t;do t=m();while(f.has(t));return t}function _(t,e,n){let o=f.get(t);o&&(n?o.resolve(JSON.parse(e)):o.resolve(e),f.delete(t))}function K(t,e){let n=f.get(t);n&&(n.reject(e),f.delete(t))}function p(t,e){return new Promise((n,o)=>{let i=St();e=e||{},e["dialog-id"]=i,f.set(i,{resolve:n,reject:o}),gt(t,e).catch(r=>{o(r),f.delete(i)})})}function $(t){return p(ht,t)}function ee(t){return p(xt,t)}function te(t){return p(Ct,t)}function w(t){return p(vt,t)}function ne(t){return p(bt,t)}function oe(t){return p(Wt,t)}var Mt=a(l.ContextMenu),Dt=0;function Et(t,e,n,o){Mt(Dt,{id:t,x:e,y:n,data:o})}function ie(){window.addEventListener("contextmenu",yt)}function yt(t){let e=t.target,n=window.getComputedStyle(e).getPropertyValue("--custom-contextmenu");if(n=n?n.trim():"",n){t.preventDefault();let o=window.getComputedStyle(e).getPropertyValue("--custom-contextmenu-data");Et(n,t.clientX,t.clientY,o);return}Rt(t)}function Rt(t){let e=t.target;switch(window.getComputedStyle(e).getPropertyValue("--default-contextmenu").trim()){case"show":return;case"hide":t.preventDefault();return;default:if(e.isContentEditable)return;let i=window.getSelection(),r=i.toString().length>0;if(r)for(let u=0;u{t.json().then(e=>{Nt=se(e)})});var h=!1;function zt(t){let e=window.getComputedStyle(t.target).getPropertyValue("--webkit-app-region");return e&&(e=e.trim()),e!=="drag"||t.buttons!==1?!1:t.detail===1}function ue(){window.addEventListener("mousedown",At),window.addEventListener("mousemove",Pt),window.addEventListener("mouseup",Tt)}var It=!1;function ce(t){It=t}function At(t){if(zt(t)){if(t.offsetX>t.target.clientWidth||t.offsetY>t.target.clientHeight)return;h=!0}else h=!1}function Tt(t){(t.buttons!==void 0?t.buttons:t.which)>0&&T()}function T(){document.body.style.cursor="default",h=!1}function Pt(t){if(h){h=!1,(t.buttons!==void 0?t.buttons:t.which)>0&&ae("drag");return}}window.wails={...de(null),Capabilities:{},clientId:D};fetch("/wails/capabilities").then(t=>{t.json().then(e=>{window.wails.Capabilities=e})});window._wails={dialogCallback:_,dialogErrorCallback:K,dispatchWailsEvent:V,callCallback:j,callErrorCallback:H,endDrag:T,setResizable:ce};function de(t){return{Clipboard:{...E},Application:{...L,GetWindowByName(e){return de(e)}},System:N,Screens:k,Browser:z,Call:U,CallByID:G,CallByName:F,Plugin:Z,WML:{Reload:A},Dialog:{Info:$,Warning:ee,Error:te,Question:w,OpenFile:ne,SaveFile:oe},Events:{Emit:W,On:Q,Once:X,OnMultiple:b,Off:q,OffAll:J},Window:Y(t)}}ie();ue();document.addEventListener("DOMContentLoaded",function(){A()});})(); diff --git a/v3/internal/runtime/runtime_production_desktop_linux.js b/v3/internal/runtime/runtime_production_desktop_linux.js new file mode 100644 index 000000000..5fdb99e37 --- /dev/null +++ b/v3/internal/runtime/runtime_production_desktop_linux.js @@ -0,0 +1 @@ +(()=>{var fe=Object.defineProperty;var g=(t,e)=>{for(var n in e)fe(t,n,{get:e[n],enumerable:!0})};var E={};g(E,{SetText:()=>xe,Text:()=>Ce});var me="useandom-26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict";var m=(t=21)=>{let e="",n=t;for(;n--;)e+=me[Math.random()*64|0];return e};var pe=window.location.origin+"/wails/runtime",l={Call:0,Clipboard:1,Application:2,Events:3,ContextMenu:4,Dialog:5,Window:6,Screens:7,System:8,Browser:9},D=m();function we(t,e,n,o){let i=new URL(pe);i.searchParams.append("object",t),i.searchParams.append("method",e);let r={headers:{}};return n&&(r.headers["x-wails-window-name"]=n),o&&i.searchParams.append("args",JSON.stringify(o)),r.headers["x-wails-client-id"]=D,new Promise((u,S)=>{fetch(i,r).then(s=>{if(s.ok)return s.headers.get("Content-Type")&&s.headers.get("Content-Type").indexOf("application/json")!==-1?s.json():s.text();S(Error(s.statusText))}).then(s=>u(s)).catch(s=>S(s))})}function a(t,e){return function(n,o=null){return we(t,n,e,o)}}var B=a(l.Clipboard),ge=0,he=1;function xe(t){B(ge,{text:t})}function Ce(){return B(he)}var L={};g(L,{Hide:()=>ve,Quit:()=>We,Show:()=>be});var y=a(l.Application),R={Hide:0,Show:1,Quit:2};function ve(){y(R.Hide)}function be(){y(R.Show)}function We(){y(R.Quit)}var k={};g(k,{GetAll:()=>Ee,GetCurrent:()=>Re,GetPrimary:()=>ye});var O=a(l.Screens),Se=0,Me=1,De=2;function Ee(){return O(Se)}function ye(){return O(Me)}function Re(){return O(De)}var N={};g(N,{IsDarkMode:()=>ke});var Le=a(l.System),Oe=0;function ke(){return Le(Oe)}var z={};g(z,{OpenURL:()=>Ie});var Ne=a(l.Browser),ze=0;function Ie(t){Ne(ze,{url:t})}var Ae=a(l.Call),x=0,d=new Map;function Te(){let t;do t=m();while(d.has(t));return t}function j(t,e,n){let o=d.get(t);o&&(n?o.resolve(JSON.parse(e)):o.resolve(e),d.delete(t))}function H(t,e){let n=d.get(t);n&&(n.reject(e),d.delete(t))}function C(t,e){return new Promise((n,o)=>{let i=Te();e=e||{},e["call-id"]=i,d.set(i,{resolve:n,reject:o}),Ae(t,e).catch(r=>{o(r),d.delete(i)})})}function U(t){return C(x,t)}function F(t,...e){if(typeof t!="string"||t.split(".").length!==3)throw new Error("CallByName requires a string in the format 'package.struct.method'");let n=t.split(".");return C(x,{packageName:n[0],structName:n[1],methodName:n[2],args:e})}function G(t,...e){return C(x,{methodID:t,args:e})}function Z(t,e,...n){return C(x,{packageName:"wails-plugins",structName:t,methodName:e,args:n})}var Pe=0,Be=1,je=2,He=3,Ue=4,Fe=5,Ge=6,Ze=7,Ye=8,Qe=9,Xe=10,Ve=11,qe=12,Je=13,_e=14,Ke=15,$e=16,et=17,tt=18,nt=19,ot=20,it=21,rt=22,lt=23,at=24,st=25,ut=26,ct=27,dt=28,ft=29;function Y(t){let e=a(l.Window,t);return{Center:()=>void e(Pe),SetTitle:n=>void e(Be,{title:n}),Fullscreen:()=>void e(je),UnFullscreen:()=>void e(He),SetSize:(n,o)=>e(Ue,{width:n,height:o}),Size:()=>e(Fe),SetMaxSize:(n,o)=>void e(Ge,{width:n,height:o}),SetMinSize:(n,o)=>void e(Ze,{width:n,height:o}),SetAlwaysOnTop:n=>void e(Ye,{alwaysOnTop:n}),SetRelativePosition:(n,o)=>e(Qe,{x:n,y:o}),RelativePosition:()=>e(Xe),Screen:()=>e(Ve),Hide:()=>void e(qe),Maximise:()=>void e(Je),Show:()=>void e(nt),Close:()=>void e(ot),ToggleMaximise:()=>void e(Ke),UnMaximise:()=>void e(_e),Minimise:()=>void e($e),UnMinimise:()=>void e(et),Restore:()=>void e(tt),SetBackgroundColour:(n,o,i,r)=>void e(it,{r:n,g:o,b:i,a:r}),SetResizable:n=>void e(rt,{resizable:n}),Width:()=>e(lt),Height:()=>e(at),ZoomIn:()=>void e(st),ZoomOut:()=>void e(ut),ZoomReset:()=>void e(ct),GetZoomLevel:()=>e(dt),SetZoomLevel:n=>void e(ft,{zoomLevel:n})}}var mt=a(l.Events),pt=0,I=class{constructor(e,n,o){this.eventName=e,this.maxCallbacks=o||-1,this.Callback=i=>(n(i),this.maxCallbacks===-1?!1:(this.maxCallbacks-=1,this.maxCallbacks===0))}},v=class{constructor(e,n=null){this.name=e,this.data=n}},c=new Map;function b(t,e,n){let o=c.get(t)||[],i=new I(t,e,n);return o.push(i),c.set(t,o),()=>wt(i)}function Q(t,e){return b(t,e,-1)}function X(t,e){return b(t,e,1)}function wt(t){let e=t.eventName,n=c.get(e).filter(o=>o!==t);n.length===0?c.delete(e):c.set(e,n)}function V(t){let e=c.get(t.name);if(e){let n=[];e.forEach(o=>{o.Callback(t)&&n.push(o)}),n.length>0&&(e=e.filter(o=>!n.includes(o)),e.length===0?c.delete(t.name):c.set(t.name,e))}}function q(t,...e){[t,...e].forEach(o=>{c.delete(o)})}function J(){c.clear()}function W(t){mt(pt,t)}var gt=a(l.Dialog),ht=0,xt=1,Ct=2,vt=3,bt=4,Wt=5,f=new Map;function St(){let t;do t=m();while(f.has(t));return t}function _(t,e,n){let o=f.get(t);o&&(n?o.resolve(JSON.parse(e)):o.resolve(e),f.delete(t))}function K(t,e){let n=f.get(t);n&&(n.reject(e),f.delete(t))}function p(t,e){return new Promise((n,o)=>{let i=St();e=e||{},e["dialog-id"]=i,f.set(i,{resolve:n,reject:o}),gt(t,e).catch(r=>{o(r),f.delete(i)})})}function $(t){return p(ht,t)}function ee(t){return p(xt,t)}function te(t){return p(Ct,t)}function w(t){return p(vt,t)}function ne(t){return p(bt,t)}function oe(t){return p(Wt,t)}var Mt=a(l.ContextMenu),Dt=0;function Et(t,e,n,o){Mt(Dt,{id:t,x:e,y:n,data:o})}function ie(){window.addEventListener("contextmenu",yt)}function yt(t){let e=t.target,n=window.getComputedStyle(e).getPropertyValue("--custom-contextmenu");if(n=n?n.trim():"",n){t.preventDefault();let o=window.getComputedStyle(e).getPropertyValue("--custom-contextmenu-data");Et(n,t.clientX,t.clientY,o);return}Rt(t)}function Rt(t){let e=t.target;switch(window.getComputedStyle(e).getPropertyValue("--default-contextmenu").trim()){case"show":return;case"hide":t.preventDefault();return;default:if(e.isContentEditable)return;let i=window.getSelection(),r=i.toString().length>0;if(r)for(let u=0;u{t.json().then(e=>{Nt=se(e)})});var h=!1;function zt(t){let e=window.getComputedStyle(t.target).getPropertyValue("--webkit-app-region");return e&&(e=e.trim()),e!=="drag"||t.buttons!==1?!1:t.detail===1}function ue(){window.addEventListener("mousedown",At),window.addEventListener("mousemove",Pt),window.addEventListener("mouseup",Tt)}var It=!1;function ce(t){It=t}function At(t){if(zt(t)){if(t.offsetX>t.target.clientWidth||t.offsetY>t.target.clientHeight)return;h=!0}else h=!1}function Tt(t){(t.buttons!==void 0?t.buttons:t.which)>0&&T()}function T(){document.body.style.cursor="default",h=!1}function Pt(t){if(h){h=!1,(t.buttons!==void 0?t.buttons:t.which)>0&&ae("drag");return}}window.wails={...de(null),Capabilities:{},clientId:D};fetch("/wails/capabilities").then(t=>{t.json().then(e=>{window.wails.Capabilities=e})});window._wails={dialogCallback:_,dialogErrorCallback:K,dispatchWailsEvent:V,callCallback:j,callErrorCallback:H,endDrag:T,setResizable:ce};function de(t){return{Clipboard:{...E},Application:{...L,GetWindowByName(e){return de(e)}},System:N,Screens:k,Browser:z,Call:U,CallByID:G,CallByName:F,Plugin:Z,WML:{Reload:A},Dialog:{Info:$,Warning:ee,Error:te,Question:w,OpenFile:ne,SaveFile:oe},Events:{Emit:W,On:Q,Once:X,OnMultiple:b,Off:q,OffAll:J},Window:Y(t)}}ie();ue();document.addEventListener("DOMContentLoaded",function(){A()});})(); diff --git a/v3/internal/runtime/runtime_production_desktop_windows.js b/v3/internal/runtime/runtime_production_desktop_windows.js new file mode 100644 index 000000000..f83dfd673 --- /dev/null +++ b/v3/internal/runtime/runtime_production_desktop_windows.js @@ -0,0 +1 @@ +(()=>{var xe=Object.defineProperty;var v=(e,t)=>{for(var n in t)xe(e,n,{get:t[n],enumerable:!0})};var O={};v(O,{SetText:()=>Me,Text:()=>De});var Ce="useandom-26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict";var g=(e=21)=>{let t="",n=e;for(;n--;)t+=Ce[Math.random()*64|0];return t};var ve=window.location.origin+"/wails/runtime",l={Call:0,Clipboard:1,Application:2,Events:3,ContextMenu:4,Dialog:5,Window:6,Screens:7,System:8,Browser:9},L=g();function We(e,t,n,o){let i=new URL(ve);i.searchParams.append("object",e),i.searchParams.append("method",t);let r={headers:{}};return n&&(r.headers["x-wails-window-name"]=n),o&&i.searchParams.append("args",JSON.stringify(o)),r.headers["x-wails-client-id"]=L,new Promise((u,p)=>{fetch(i,r).then(s=>{if(s.ok)return s.headers.get("Content-Type")&&s.headers.get("Content-Type").indexOf("application/json")!==-1?s.json():s.text();p(Error(s.statusText))}).then(s=>u(s)).catch(s=>p(s))})}function a(e,t){return function(n,o=null){return We(e,n,t,o)}}var G=a(l.Clipboard),be=0,Se=1;function Me(e){G(be,{text:e})}function De(){return G(Se)}var I={};v(I,{Hide:()=>Ee,Quit:()=>Re,Show:()=>ye});var k=a(l.Application),N={Hide:0,Show:1,Quit:2};function Ee(){k(N.Hide)}function ye(){k(N.Show)}function Re(){k(N.Quit)}var A={};v(A,{GetAll:()=>Ne,GetCurrent:()=>ze,GetPrimary:()=>Ie});var z=a(l.Screens),Le=0,Oe=1,ke=2;function Ne(){return z(Le)}function Ie(){return z(Oe)}function ze(){return z(ke)}var T={};v(T,{IsDarkMode:()=>Pe});var Ae=a(l.System),Te=0;function Pe(){return Ae(Te)}var P={};v(P,{OpenURL:()=>He});var Be=a(l.Browser),je=0;function He(e){Be(je,{url:e})}var Ue=a(l.Call),b=0,f=new Map;function Fe(){let e;do e=g();while(f.has(e));return e}function Z(e,t,n){let o=f.get(e);o&&(n?o.resolve(JSON.parse(t)):o.resolve(t),f.delete(e))}function Y(e,t){let n=f.get(e);n&&(n.reject(t),f.delete(e))}function S(e,t){return new Promise((n,o)=>{let i=Fe();t=t||{},t["call-id"]=i,f.set(i,{resolve:n,reject:o}),Ue(e,t).catch(r=>{o(r),f.delete(i)})})}function Q(e){return S(b,e)}function X(e,...t){if(typeof e!="string"||e.split(".").length!==3)throw new Error("CallByName requires a string in the format 'package.struct.method'");let n=e.split(".");return S(b,{packageName:n[0],structName:n[1],methodName:n[2],args:t})}function V(e,...t){return S(b,{methodID:e,args:t})}function q(e,t,...n){return S(b,{packageName:"wails-plugins",structName:e,methodName:t,args:n})}var Ge=0,Ze=1,Ye=2,Qe=3,Xe=4,Ve=5,qe=6,Je=7,_e=8,Ke=9,$e=10,et=11,tt=12,nt=13,ot=14,it=15,rt=16,lt=17,at=18,st=19,ut=20,ct=21,dt=22,ft=23,mt=24,pt=25,wt=26,gt=27,ht=28,xt=29;function J(e){let t=a(l.Window,e);return{Center:()=>void t(Ge),SetTitle:n=>void t(Ze,{title:n}),Fullscreen:()=>void t(Ye),UnFullscreen:()=>void t(Qe),SetSize:(n,o)=>t(Xe,{width:n,height:o}),Size:()=>t(Ve),SetMaxSize:(n,o)=>void t(qe,{width:n,height:o}),SetMinSize:(n,o)=>void t(Je,{width:n,height:o}),SetAlwaysOnTop:n=>void t(_e,{alwaysOnTop:n}),SetRelativePosition:(n,o)=>t(Ke,{x:n,y:o}),RelativePosition:()=>t($e),Screen:()=>t(et),Hide:()=>void t(tt),Maximise:()=>void t(nt),Show:()=>void t(st),Close:()=>void t(ut),ToggleMaximise:()=>void t(it),UnMaximise:()=>void t(ot),Minimise:()=>void t(rt),UnMinimise:()=>void t(lt),Restore:()=>void t(at),SetBackgroundColour:(n,o,i,r)=>void t(ct,{r:n,g:o,b:i,a:r}),SetResizable:n=>void t(dt,{resizable:n}),Width:()=>t(ft),Height:()=>t(mt),ZoomIn:()=>void t(pt),ZoomOut:()=>void t(wt),ZoomReset:()=>void t(gt),GetZoomLevel:()=>t(ht),SetZoomLevel:n=>void t(xt,{zoomLevel:n})}}var Ct=a(l.Events),vt=0,B=class{constructor(t,n,o){this.eventName=t,this.maxCallbacks=o||-1,this.Callback=i=>(n(i),this.maxCallbacks===-1?!1:(this.maxCallbacks-=1,this.maxCallbacks===0))}},M=class{constructor(t,n=null){this.name=t,this.data=n}},c=new Map;function D(e,t,n){let o=c.get(e)||[],i=new B(e,t,n);return o.push(i),c.set(e,o),()=>Wt(i)}function _(e,t){return D(e,t,-1)}function K(e,t){return D(e,t,1)}function Wt(e){let t=e.eventName,n=c.get(t).filter(o=>o!==e);n.length===0?c.delete(t):c.set(t,n)}function $(e){let t=c.get(e.name);if(t){let n=[];t.forEach(o=>{o.Callback(e)&&n.push(o)}),n.length>0&&(t=t.filter(o=>!n.includes(o)),t.length===0?c.delete(e.name):c.set(e.name,t))}}function ee(e,...t){[e,...t].forEach(o=>{c.delete(o)})}function te(){c.clear()}function E(e){Ct(vt,e)}var bt=a(l.Dialog),St=0,Mt=1,Dt=2,Et=3,yt=4,Rt=5,m=new Map;function Lt(){let e;do e=g();while(m.has(e));return e}function ne(e,t,n){let o=m.get(e);o&&(n?o.resolve(JSON.parse(t)):o.resolve(t),m.delete(e))}function oe(e,t){let n=m.get(e);n&&(n.reject(t),m.delete(e))}function h(e,t){return new Promise((n,o)=>{let i=Lt();t=t||{},t["dialog-id"]=i,m.set(i,{resolve:n,reject:o}),bt(e,t).catch(r=>{o(r),m.delete(i)})})}function ie(e){return h(St,e)}function re(e){return h(Mt,e)}function le(e){return h(Dt,e)}function x(e){return h(Et,e)}function ae(e){return h(yt,e)}function se(e){return h(Rt,e)}var Ot=a(l.ContextMenu),kt=0;function Nt(e,t,n,o){Ot(kt,{id:e,x:t,y:n,data:o})}function ue(){window.addEventListener("contextmenu",It)}function It(e){let t=e.target,n=window.getComputedStyle(t).getPropertyValue("--custom-contextmenu");if(n=n?n.trim():"",n){e.preventDefault();let o=window.getComputedStyle(t).getPropertyValue("--custom-contextmenu-data");Nt(n,e.clientX,e.clientY,o);return}zt(e)}function zt(e){let t=e.target;switch(window.getComputedStyle(t).getPropertyValue("--default-contextmenu").trim()){case"show":return;case"hide":e.preventDefault();return;default:if(t.isContentEditable)return;let i=window.getSelection(),r=i.toString().length>0;if(r)for(let u=0;u{e.json().then(t=>{fe=me(t)})});function Bt(e){let t=e.split("."),n=fe;for(let o of t)if(n instanceof Map?n=n.get(o):n=n[o],n===void 0)break;return n}function y(e){return Bt(e)}var W=!1;function jt(e){let t=window.getComputedStyle(e.target).getPropertyValue("--webkit-app-region");return t&&(t=t.trim()),t!=="drag"||e.buttons!==1?!1:e.detail===1}function pe(){window.addEventListener("mousedown",Ut),window.addEventListener("mousemove",Gt),window.addEventListener("mouseup",Ft)}var R=null,we=!1;function ge(e){we=e}function Ht(e){return R?(H("resize:"+R),!0):!1}function Ut(e){if(!Ht())if(jt(e)){if(e.offsetX>e.target.clientWidth||e.offsetY>e.target.clientHeight)return;W=!0}else W=!1}function Ft(e){(e.buttons!==void 0?e.buttons:e.which)>0&&U()}function U(){document.body.style.cursor="default",W=!1}function d(e){document.documentElement.style.cursor=e||Zt,R=e}function Gt(e){if(W){W=!1,(e.buttons!==void 0?e.buttons:e.which)>0&&H("drag");return}we&&Yt(e)}var Zt="auto";function Yt(e){let t=y("system.resizeHandleHeight")||5,n=y("system.resizeHandleWidth")||5,o=y("resizeCornerExtra")||10,i=window.outerWidth-e.clientX{e.json().then(t=>{window.wails.Capabilities=t})});window._wails={dialogCallback:ne,dialogErrorCallback:oe,dispatchWailsEvent:$,callCallback:Z,callErrorCallback:Y,endDrag:U,setResizable:ge};function he(e){return{Clipboard:{...O},Application:{...I,GetWindowByName(t){return he(t)}},System:T,Screens:A,Browser:P,Call:Q,CallByID:V,CallByName:X,Plugin:q,WML:{Reload:j},Dialog:{Info:ie,Warning:re,Error:le,Question:x,OpenFile:ae,SaveFile:se},Events:{Emit:E,On:_,Once:K,OnMultiple:D,Off:ee,OffAll:te},Window:J(e)}}ue();pe();document.addEventListener("DOMContentLoaded",function(){j()});})(); diff --git a/v3/internal/runtime/runtime_production_linux.go b/v3/internal/runtime/runtime_production_linux.go new file mode 100644 index 000000000..d7c2bd1ef --- /dev/null +++ b/v3/internal/runtime/runtime_production_linux.go @@ -0,0 +1,8 @@ +//go:build linux && production + +package runtime + +import _ "embed" + +//go:embed runtime_production_desktop_linux.js +var DesktopRuntime []byte diff --git a/v3/internal/runtime/runtime_production_windows.go b/v3/internal/runtime/runtime_production_windows.go new file mode 100644 index 000000000..8ab6d6199 --- /dev/null +++ b/v3/internal/runtime/runtime_production_windows.go @@ -0,0 +1,8 @@ +//go:build windows && production + +package runtime + +import _ "embed" + +//go:embed runtime_production_desktop_windows.js +var DesktopRuntime []byte diff --git a/v3/internal/runtime/vite.config.ts b/v3/internal/runtime/vite.config.ts new file mode 100644 index 000000000..eb0831c65 --- /dev/null +++ b/v3/internal/runtime/vite.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from 'vitest/config' + +export default defineConfig({ + test: { + environment: 'happy-dom', + }, +}) \ No newline at end of file diff --git a/v3/internal/templates/_base/default/Taskfile.tmpl.yml b/v3/internal/templates/_base/default/Taskfile.tmpl.yml new file mode 100644 index 000000000..924d31598 --- /dev/null +++ b/v3/internal/templates/_base/default/Taskfile.tmpl.yml @@ -0,0 +1,189 @@ +version: '3' + +vars: + APP_NAME: "{{.ProjectName}}{{exeExt}}" + APP_VERSION: "0.1.0" + PRODUCT_NAME: "My Product" + PRODUCT_DESCRIPTION: "My Product Description" + PRODUCT_VERSION: "0.1.0" + PRODUCT_COMPANY: "My Company" + PRODUCT_COPYRIGHT: "(c) 2023 My Company" + PRODUCT_COMMENTS: "My Comments" + PRODUCT_IDENTIFIER: "com.mycompany.myproduct" + BUILD_DIR: "build" + +tasks: + + generate:build-assets: + summary: Generates the build assets + cmds: + - wails3 generate build-assets -dir {{.BUILD_DIR}} -name "{{.APP_NAME}}" -productname "{{.PRODUCT_NAME}}" -productdescription "{{.PRODUCT_DESCRIPTION}}" -productversion "{{.PRODUCT_VERSION}}" -productcompany "{{.PRODUCT_COMPANY}}" -productcopyright "{{.PRODUCT_COPYRIGHT}}" -productcomments "{{.PRODUCT_COMMENTS}}" -productidentifier "{{.PRODUCT_IDENTIFIER}}" + + pre-build: + summary: Pre-build hooks + + post-build: + summary: Post-build hooks + + install-frontend-deps: + summary: Install frontend dependencies + dir: frontend + sources: + - package.json + - package-lock.json + generates: + - node_modules/* + preconditions: + - sh: npm version + msg: "Looks like npm isn't installed. Npm is part of the Node installer: https://nodejs.org/en/download/" + cmds: + - npm install + + build-frontend: + summary: Build the frontend project + dir: frontend + generates: + - frontend/dist/* + deps: + - install-frontend-deps + cmds: + - npm run build + + build:darwin: + summary: Builds the application + platforms: + - darwin + cmds: + - task: pre-build + - task: build-frontend + - go build -gcflags=all="-N -l" -o bin/{{.APP_NAME}} + - task: post-build + env: + CGO_CFLAGS: "-mmacosx-version-min=10.13" + CGO_LDFLAGS: "-mmacosx-version-min=10.13" + MACOSX_DEPLOYMENT_TARGET: "10.13" + + build:backend:darwin: + summary: Builds the application + platforms: + - darwin + cmds: + - task: pre-build + - go build -gcflags=all="-N -l" -o bin/{{ "{{.APP_NAME}}" }} + - task: post-build + env: + CGO_CFLAGS: "-mmacosx-version-min=10.13" + CGO_LDFLAGS: "-mmacosx-version-min=10.13" + MACOSX_DEPLOYMENT_TARGET: "10.13" + + build:windows: + summary: Builds the application for Windows + platforms: + - windows + cmds: + - task: pre-build + - task: build-frontend + - go build -gcflags=all="-N -l" -o bin/{{ "{{.APP_NAME}}" }} + - task: post-build + + + build:backend:windows: + summary: Builds the backend application for Windows + platforms: + - windows + cmds: + - task: pre-build + - go build -gcflags=all="-N -l" -o bin/{{ "{{.APP_NAME}}" }} + - task: post-build + + build: + summary: Builds the application + cmds: + - task: build:darwin + - task: build:windows + + build:backend: + summary: Builds the backend application + cmds: + - task: build:backend:darwin + - task: build:backend:windows + + generate-icons: + summary: Generates Windows `.ico` and Mac `.icns` files from an image + dir: build + cmds: + # Generates both .ico and .icns files + - wails generate icons -input appicon.png + + build-app-prod-darwin: + summary: Creates a production build of the application + cmds: + - task: pre-build + - task: build-frontend + - GOOS=darwin GOARCH={{ "{{.ARCH}}" }} go build -tags production -ldflags="-w -s" -o build/bin/{{ "{{.APP_NAME}}" }} + - task: post-build + env: + CGO_CFLAGS: "-mmacosx-version-min=10.13" + CGO_LDFLAGS: "-mmacosx-version-min=10.13" + MACOSX_DEPLOYMENT_TARGET: "10.13" + vars: + ARCH: $GOARCH + + frontend:dev: + summary: Runs the frontend in development mode + dir: frontend + cmds: + - npm run dev + + run: + summary: Runs the application + cmds: + - ./bin/{{ "{{.APP_NAME}}" }} + + dev: + summary: Runs the application in development mode + preconditions: + - sh: 'wails3 tool checkport -p 5173' + msg: "Vite does not appear to be running." + cmds: + - task: build:backend + - task: run + + create-app-bundle: + summary: Builds a `.app` bundle + cmds: + - mkdir -p {{ "{{.APP_NAME}}" }}.app/Contents/{MacOS,Resources} + - cp build/icons.icns {{ "{{.APP_NAME}}" }}.app/Contents/Resources + - cp build/bin/{{ "{{.APP_NAME}}" }} {{ "{{.APP_NAME}}" }}.app/Contents/MacOS + - cp build/Info.plist {{ "{{.APP_NAME}}" }}.app/Contents + + package-darwin-arm64: + summary: Packages a production build of the application into a `.app` bundle + platform: darwin + deps: + - task: build-app-prod-darwin + vars: + ARCH: arm64 + - generate-icons + cmds: + - task: create-app-bundle + + generate:syso: + dir: build + platform: windows + cmds: + - wails generate syso -arch {{ "{{.ARCH}}" }} -icon icon.ico -manifest wails.exe.manifest -info info.json -out ../wails.syso + vars: + ARCH: $GOARCH + + package:windows: + summary: Packages a production build of the application into a `.exe` bundle + platform: windows + deps: + - generate-icons + cmds: + - task: generate:syso + vars: + ARCH: amd64 + - go build -tags production -ldflags="-w -s -H windowsgui" -o bin/{{ "{{.APP_NAME}}" }}.exe + - powershell Remove-item wails.syso diff --git a/v3/internal/templates/_base/default/build/Info.dev.plist.tmpl b/v3/internal/templates/_base/default/build/Info.dev.plist.tmpl new file mode 100644 index 000000000..7efa134f4 --- /dev/null +++ b/v3/internal/templates/_base/default/build/Info.dev.plist.tmpl @@ -0,0 +1,32 @@ + + + + CFBundlePackageType + APPL + CFBundleName + My Product Name + CFBundleExecutable + {{.ProjectName}} + CFBundleIdentifier + com.wails.{{.ProjectName}} + CFBundleVersion + v1.0.0 + CFBundleGetInfoString + This is a comment + CFBundleShortVersionString + v1.0.0 + CFBundleIconFile + icons + LSMinimumSystemVersion + 10.13.0 + NSHighResolutionCapable + true + NSHumanReadableCopyright + (c) 2023 My Company Name + NSAppTransportSecurity + + NSAllowsLocalNetworking + + + + \ No newline at end of file diff --git a/v3/internal/templates/_base/default/build/Info.plist.tmpl b/v3/internal/templates/_base/default/build/Info.plist.tmpl new file mode 100644 index 000000000..6bfa8c316 --- /dev/null +++ b/v3/internal/templates/_base/default/build/Info.plist.tmpl @@ -0,0 +1,27 @@ + + + + CFBundlePackageType + APPL + CFBundleName + My Product Name + CFBundleExecutable + {{.ProjectName}} + CFBundleIdentifier + com.wails.{{.ProjectName}} + CFBundleVersion + v1.0.0 + CFBundleGetInfoString + This is a comment + CFBundleShortVersionString + v1.0.0 + CFBundleIconFile + icons + LSMinimumSystemVersion + 10.13.0 + NSHighResolutionCapable + true + NSHumanReadableCopyright + (c) 2023 My Company Name + + \ No newline at end of file diff --git a/v3/internal/templates/_base/default/build/appicon.png b/v3/internal/templates/_base/default/build/appicon.png new file mode 100644 index 000000000..63617fe4f Binary files /dev/null and b/v3/internal/templates/_base/default/build/appicon.png differ diff --git a/v3/internal/templates/_base/default/build/icons.icns b/v3/internal/templates/_base/default/build/icons.icns new file mode 100644 index 000000000..1b5bd4c86 Binary files /dev/null and b/v3/internal/templates/_base/default/build/icons.icns differ diff --git a/v3/internal/templates/_base/default/frontend/public/wails.png b/v3/internal/templates/_base/default/frontend/public/wails.png new file mode 100644 index 000000000..8bdf42483 Binary files /dev/null and b/v3/internal/templates/_base/default/frontend/public/wails.png differ diff --git a/v3/internal/templates/_base/default/go.mod.tmpl b/v3/internal/templates/_base/default/go.mod.tmpl new file mode 100644 index 000000000..3dad9b6fa --- /dev/null +++ b/v3/internal/templates/_base/default/go.mod.tmpl @@ -0,0 +1,19 @@ +module changeme + +go 1.21 + +require github.com/wailsapp/wails/v3 v3.0.0-alpha.0 + +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 +) +{{if gt (len .LocalModulePath) 0}} +replace github.com/wailsapp/wails/v3 => {{.LocalModulePath}}v3 +{{end}} diff --git a/v3/internal/templates/_base/default/go.sum.tmpl b/v3/internal/templates/_base/default/go.sum.tmpl new file mode 100644 index 000000000..c06e0dbc6 --- /dev/null +++ b/v3/internal/templates/_base/default/go.sum.tmpl @@ -0,0 +1,33 @@ +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/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/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/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= +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/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/v3/internal/templates/_base/default/main.go.tmpl b/v3/internal/templates/_base/default/main.go.tmpl new file mode 100644 index 000000000..7edc1c8b1 --- /dev/null +++ b/v3/internal/templates/_base/default/main.go.tmpl @@ -0,0 +1,42 @@ +package main + +import ( + "embed" + _ "embed" + "log" + + "github.com/wailsapp/wails/v3/pkg/application" +) + +//go:embed frontend/dist +var assets embed.FS + +func main() { + app := application.New(application.Options{ + Name: "{{.ProjectName}}", + Description: "A demo of using raw HTML & CSS", + Assets: application.AssetOptions{ + FS: assets, + }, + Mac: application.MacOptions{ + ApplicationShouldTerminateAfterLastWindowClosed: true, + }, + }) + // Create window + app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{ + Title: "Plain Bundle", + CSS: `body { background-color: rgba(255, 255, 255, 0); } .main { color: white; margin: 20%; }`, + Mac: application.MacWindow{ + InvisibleTitleBarHeight: 50, + Backdrop: application.MacBackdropTranslucent, + TitleBar: application.MacTitleBarHiddenInset, + }, + URL: "/", + }) + + err := app.Run() + + if err != nil { + log.Fatal(err) + } +} diff --git a/v3/internal/templates/_base/lit-ts/frontend/index.html b/v3/internal/templates/_base/lit-ts/frontend/index.html new file mode 100644 index 000000000..b4898b214 --- /dev/null +++ b/v3/internal/templates/_base/lit-ts/frontend/index.html @@ -0,0 +1,16 @@ + + + + + + + Wails + Lit + TS + + + + + +

Wails + Lit

+
+ + diff --git a/v3/internal/templates/_base/lit/frontend/index.html b/v3/internal/templates/_base/lit/frontend/index.html new file mode 100644 index 000000000..0f030a2f4 --- /dev/null +++ b/v3/internal/templates/_base/lit/frontend/index.html @@ -0,0 +1,16 @@ + + + + + + + Wails + Lit + + + + + +

Wails + Lit

+
+ + diff --git a/v3/internal/templates/_base/preact-ts/frontend/index.html b/v3/internal/templates/_base/preact-ts/frontend/index.html new file mode 100644 index 000000000..c467c421b --- /dev/null +++ b/v3/internal/templates/_base/preact-ts/frontend/index.html @@ -0,0 +1,13 @@ + + + + + + + Wails + Preact + TS + + +
+ + + diff --git a/v3/internal/templates/_base/preact/frontend/index.html b/v3/internal/templates/_base/preact/frontend/index.html new file mode 100644 index 000000000..a3c698def --- /dev/null +++ b/v3/internal/templates/_base/preact/frontend/index.html @@ -0,0 +1,13 @@ + + + + + + + Wails + Preact + + +
+ + + diff --git a/v3/internal/templates/_base/react-swc-ts/frontend/index.html b/v3/internal/templates/_base/react-swc-ts/frontend/index.html new file mode 100644 index 000000000..44b5c452b --- /dev/null +++ b/v3/internal/templates/_base/react-swc-ts/frontend/index.html @@ -0,0 +1,13 @@ + + + + + + + Wails + React + TS + + +
+ + + diff --git a/v3/internal/templates/_base/react-swc/frontend/index.html b/v3/internal/templates/_base/react-swc/frontend/index.html new file mode 100644 index 000000000..cc495265c --- /dev/null +++ b/v3/internal/templates/_base/react-swc/frontend/index.html @@ -0,0 +1,13 @@ + + + + + + + Wails + React + + +
+ + + diff --git a/v3/internal/templates/_base/react-ts/frontend/index.html b/v3/internal/templates/_base/react-ts/frontend/index.html new file mode 100644 index 000000000..44b5c452b --- /dev/null +++ b/v3/internal/templates/_base/react-ts/frontend/index.html @@ -0,0 +1,13 @@ + + + + + + + Wails + React + TS + + +
+ + + diff --git a/v3/internal/templates/_base/react/frontend/index.html b/v3/internal/templates/_base/react/frontend/index.html new file mode 100644 index 000000000..cc495265c --- /dev/null +++ b/v3/internal/templates/_base/react/frontend/index.html @@ -0,0 +1,13 @@ + + + + + + + Wails + React + + +
+ + + diff --git a/v3/internal/templates/_base/svelte-ts/frontend/index.html b/v3/internal/templates/_base/svelte-ts/frontend/index.html new file mode 100644 index 000000000..2d5c0f2de --- /dev/null +++ b/v3/internal/templates/_base/svelte-ts/frontend/index.html @@ -0,0 +1,13 @@ + + + + + + + Wails + Svelte + TS + + +
+ + + diff --git a/v3/internal/templates/_base/svelte/frontend/README.md b/v3/internal/templates/_base/svelte/frontend/README.md new file mode 100644 index 000000000..fd6a7082f --- /dev/null +++ b/v3/internal/templates/_base/svelte/frontend/README.md @@ -0,0 +1 @@ +# Wails + Svelte \ No newline at end of file diff --git a/v3/internal/templates/_base/svelte/frontend/index.html b/v3/internal/templates/_base/svelte/frontend/index.html new file mode 100644 index 000000000..d12bc9069 --- /dev/null +++ b/v3/internal/templates/_base/svelte/frontend/index.html @@ -0,0 +1,13 @@ + + + + + + + Wails + Svelte + + +
+ + + diff --git a/v3/internal/templates/_base/svelte/frontend/src/App.svelte b/v3/internal/templates/_base/svelte/frontend/src/App.svelte new file mode 100644 index 000000000..c677e3838 --- /dev/null +++ b/v3/internal/templates/_base/svelte/frontend/src/App.svelte @@ -0,0 +1,45 @@ + + +
+ +

Wails + Svelte

+ +
+ +
+ +

+ Check out SvelteKit, the official Svelte app framework powered by Wails! +

+ +

+ Click on the Wails and Svelte logos to learn more +

+
+ + diff --git a/v3/internal/templates/_base/vanilla-ts/frontend/index.html b/v3/internal/templates/_base/vanilla-ts/frontend/index.html new file mode 100644 index 000000000..8c692073a --- /dev/null +++ b/v3/internal/templates/_base/vanilla-ts/frontend/index.html @@ -0,0 +1,13 @@ + + + + + + + Wails + TS + + +
+ + + diff --git a/v3/internal/templates/_base/vanilla/frontend/index.html b/v3/internal/templates/_base/vanilla/frontend/index.html new file mode 100644 index 000000000..c4eb3cc44 --- /dev/null +++ b/v3/internal/templates/_base/vanilla/frontend/index.html @@ -0,0 +1,13 @@ + + + + + + + Wails App + + +
+ + + diff --git a/v3/internal/templates/_base/vue-ts/frontend/index.html b/v3/internal/templates/_base/vue-ts/frontend/index.html new file mode 100644 index 000000000..28870032c --- /dev/null +++ b/v3/internal/templates/_base/vue-ts/frontend/index.html @@ -0,0 +1,13 @@ + + + + + + + Wails + Vue + TS + + +
+ + + diff --git a/v3/internal/templates/_base/vue/frontend/index.html b/v3/internal/templates/_base/vue/frontend/index.html new file mode 100644 index 000000000..5057cc3c1 --- /dev/null +++ b/v3/internal/templates/_base/vue/frontend/index.html @@ -0,0 +1,13 @@ + + + + + + + Wails + Vue + + +
+ + + diff --git a/v3/internal/templates/lit-ts/Taskfile.tmpl.yml b/v3/internal/templates/lit-ts/Taskfile.tmpl.yml new file mode 100644 index 000000000..975af7335 --- /dev/null +++ b/v3/internal/templates/lit-ts/Taskfile.tmpl.yml @@ -0,0 +1,125 @@ +version: '3' + +vars: + APP_NAME: "{{.ProjectName}}" + +tasks: + + pre-build: + summary: Pre-build hooks + + post-build: + summary: Post-build hooks + + install-frontend-deps: + summary: Install frontend dependencies + dir: frontend + sources: + - package.json + - package-lock.json + generates: + - node_modules/* + preconditions: + - sh: npm version + msg: "Looks like npm isn't installed. Npm is part of the Node installer: https://nodejs.org/en/download/" + cmds: + - npm install + + build-frontend: + summary: Build the frontend project + dir: frontend + deps: + - install-frontend-deps + cmds: + - npm run build + + build:darwin: + summary: Builds the application + platforms: + - darwin + cmds: + - task: pre-build + - task: build-frontend + - go build -gcflags=all="-N -l" -o bin/testapp + - task: post-build + env: + CGO_CFLAGS: "-mmacosx-version-min=10.13" + CGO_LDFLAGS: "-mmacosx-version-min=10.13" + MACOSX_DEPLOYMENT_TARGET: "10.13" + + build:windows: + summary: Builds the application for Windows + platforms: + - windows + cmds: + - task: pre-build + - task: build-frontend + - go build -gcflags=all="-N -l" -o bin/testapp.exe + - task: post-build + + build: + summary: Builds the application + cmds: + - task: build:darwin + - task: build:windows + + generate-icons: + summary: Generates Windows `.ico` and Mac `.icns` files from an image + dir: build + cmds: + # Generates both .ico and .icns files + - wails generate icons -input appicon.png + + build-app-prod-darwin: + summary: Creates a production build of the application + cmds: + - task: pre-build + - task: build-frontend + - GOOS=darwin GOARCH={{ "{{.ARCH}}" }} go build -tags production -ldflags="-w -s" -o build/bin/{{ "{{.APP_NAME}}" }} + - task: post-build + env: + CGO_CFLAGS: "-mmacosx-version-min=10.13" + CGO_LDFLAGS: "-mmacosx-version-min=10.13" + MACOSX_DEPLOYMENT_TARGET: "10.13" + vars: + ARCH: $GOARCH + + + create-app-bundle: + summary: Builds a `.app` bundle + cmds: + - mkdir -p {{ "{{.APP_NAME}}" }}.app/Contents/{MacOS,Resources} + - cp build/icons.icns {{ "{{.APP_NAME}}" }}.app/Contents/Resources + - cp build/bin/{{ "{{.APP_NAME}}" }} {{ "{{.APP_NAME}}" }}.app/Contents/MacOS + - cp build/Info.plist {{ "{{.APP_NAME}}" }}.app/Contents + + package-darwin-arm64: + summary: Packages a production build of the application into a `.app` bundle + platform: darwin + deps: + - task: build-app-prod-darwin + vars: + ARCH: arm64 + - generate-icons + cmds: + - task: create-app-bundle + + generate:syso: + dir: build + platform: windows + cmds: + - wails generate syso -arch {{ "{{.ARCH}}" }} -icon icon.ico -manifest wails.exe.manifest -info info.json -out ../wails.syso + vars: + ARCH: $GOARCH + + package:windows: + summary: Packages a production build of the application into a `.exe` bundle + platform: windows + deps: + - generate-icons + cmds: + - task: generate:syso + vars: + ARCH: amd64 + - go build -tags production -ldflags="-w -s -H windowsgui" -o bin/{{ "{{.APP_NAME}}" }}.exe + - powershell Remove-item wails.syso diff --git a/v3/internal/templates/lit-ts/build/Info.dev.plist.tmpl b/v3/internal/templates/lit-ts/build/Info.dev.plist.tmpl new file mode 100644 index 000000000..7efa134f4 --- /dev/null +++ b/v3/internal/templates/lit-ts/build/Info.dev.plist.tmpl @@ -0,0 +1,32 @@ + + + + CFBundlePackageType + APPL + CFBundleName + My Product Name + CFBundleExecutable + {{.ProjectName}} + CFBundleIdentifier + com.wails.{{.ProjectName}} + CFBundleVersion + v1.0.0 + CFBundleGetInfoString + This is a comment + CFBundleShortVersionString + v1.0.0 + CFBundleIconFile + icons + LSMinimumSystemVersion + 10.13.0 + NSHighResolutionCapable + true + NSHumanReadableCopyright + (c) 2023 My Company Name + NSAppTransportSecurity + + NSAllowsLocalNetworking + + + + \ No newline at end of file diff --git a/v3/internal/templates/lit-ts/build/Info.plist.tmpl b/v3/internal/templates/lit-ts/build/Info.plist.tmpl new file mode 100644 index 000000000..6bfa8c316 --- /dev/null +++ b/v3/internal/templates/lit-ts/build/Info.plist.tmpl @@ -0,0 +1,27 @@ + + + + CFBundlePackageType + APPL + CFBundleName + My Product Name + CFBundleExecutable + {{.ProjectName}} + CFBundleIdentifier + com.wails.{{.ProjectName}} + CFBundleVersion + v1.0.0 + CFBundleGetInfoString + This is a comment + CFBundleShortVersionString + v1.0.0 + CFBundleIconFile + icons + LSMinimumSystemVersion + 10.13.0 + NSHighResolutionCapable + true + NSHumanReadableCopyright + (c) 2023 My Company Name + + \ No newline at end of file diff --git a/v3/internal/templates/lit-ts/build/appicon.png b/v3/internal/templates/lit-ts/build/appicon.png new file mode 100644 index 000000000..63617fe4f Binary files /dev/null and b/v3/internal/templates/lit-ts/build/appicon.png differ diff --git a/v3/internal/templates/lit-ts/build/icons.icns b/v3/internal/templates/lit-ts/build/icons.icns new file mode 100644 index 000000000..1b5bd4c86 Binary files /dev/null and b/v3/internal/templates/lit-ts/build/icons.icns differ diff --git a/v3/internal/templates/lit-ts/frontend/.gitignore b/v3/internal/templates/lit-ts/frontend/.gitignore new file mode 100644 index 000000000..a547bf36d --- /dev/null +++ b/v3/internal/templates/lit-ts/frontend/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/v3/internal/templates/lit-ts/frontend/index.html b/v3/internal/templates/lit-ts/frontend/index.html new file mode 100644 index 000000000..ca539730d --- /dev/null +++ b/v3/internal/templates/lit-ts/frontend/index.html @@ -0,0 +1,16 @@ + + + + + + + Wails + Lit + TS + + + + + +

Wails + Lit

+
+ + diff --git a/v3/internal/templates/lit-ts/frontend/package.json b/v3/internal/templates/lit-ts/frontend/package.json new file mode 100644 index 000000000..42a86bd4b --- /dev/null +++ b/v3/internal/templates/lit-ts/frontend/package.json @@ -0,0 +1,26 @@ +{ + "name": "frontend", + "private": true, + "version": "0.0.0", + "type": "module", + "main": "dist/my-element.es.js", + "exports": { + ".": "./dist/my-element.es.js" + }, + "types": "types/my-element.d.ts", + "files": [ + "dist", + "types" + ], + "scripts": { + "dev": "vite", + "build": "tsc && vite build" + }, + "dependencies": { + "lit": "^2.4.1" + }, + "devDependencies": { + "typescript": "^4.9.3", + "vite": "^4.0.0" + } +} \ No newline at end of file diff --git a/v3/internal/templates/lit-ts/frontend/public/wails.png b/v3/internal/templates/lit-ts/frontend/public/wails.png new file mode 100644 index 000000000..8bdf42483 Binary files /dev/null and b/v3/internal/templates/lit-ts/frontend/public/wails.png differ diff --git a/v3/internal/templates/lit-ts/frontend/src/assets/lit.svg b/v3/internal/templates/lit-ts/frontend/src/assets/lit.svg new file mode 100644 index 000000000..4a9c1fe66 --- /dev/null +++ b/v3/internal/templates/lit-ts/frontend/src/assets/lit.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/v3/internal/templates/lit-ts/frontend/src/index.css b/v3/internal/templates/lit-ts/frontend/src/index.css new file mode 100644 index 000000000..d39ac2e34 --- /dev/null +++ b/v3/internal/templates/lit-ts/frontend/src/index.css @@ -0,0 +1,40 @@ +:root { + font-family: Inter, Avenir, Helvetica, Arial, sans-serif; + font-size: 16px; + line-height: 24px; + font-weight: 400; + + color-scheme: light dark; + color: rgba(255, 255, 255, 0.87); + background-color: #242424; + + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + -webkit-text-size-adjust: 100%; +} + +a { + font-weight: 500; + color: #646cff; + text-decoration: inherit; +} +a:hover { + color: #535bf2; +} + +body { + margin: 0; + display: flex; + place-items: center; + min-width: 320px; + min-height: 100vh; +} + +@media (prefers-color-scheme: light) { + :root { + color: #213547; + background-color: #ffffff; + } +} diff --git a/v3/internal/templates/lit-ts/frontend/src/my-element.ts b/v3/internal/templates/lit-ts/frontend/src/my-element.ts new file mode 100644 index 000000000..9f1c76e89 --- /dev/null +++ b/v3/internal/templates/lit-ts/frontend/src/my-element.ts @@ -0,0 +1,125 @@ +import { LitElement, css, html } from 'lit' +import { customElement, property } from 'lit/decorators.js' +import litLogo from './assets/lit.svg' + +/** + * An example element. + * + * @slot - This element has a slot + * @csspart button - The button + */ +@customElement('my-element') +export class MyElement extends LitElement { + /** + * Copy for the read the docs hint. + */ + @property() + docsHint = 'Click on the Wails and Lit logos to learn more' + + /** + * The number of times the button has been clicked. + */ + @property({ type: Number }) + count = 0 + + render() { + return html` + + +
+ +
+

${this.docsHint}

+ ` + } + + private _onClick() { + this.count++ + } + + static styles = css` + :host { + max-width: 1280px; + margin: 0 auto; + padding: 2rem; + text-align: center; + } + + .logo { + height: 6em; + padding: 1.5em; + will-change: filter; + } + .logo:hover { + filter: drop-shadow(0 0 2em #646cffaa); + } + .logo.lit:hover { + filter: drop-shadow(0 0 2em #325cffaa); + } + + .card { + padding: 2em; + } + + .read-the-docs { + color: #888; + } + + h1 { + font-size: 3.2em; + line-height: 1.1; + } + + a { + font-weight: 500; + color: #646cff; + text-decoration: inherit; + } + a:hover { + color: #535bf2; + } + + button { + border-radius: 8px; + border: 1px solid transparent; + padding: 0.6em 1.2em; + font-size: 1em; + font-weight: 500; + font-family: inherit; + background-color: #1a1a1a; + cursor: pointer; + transition: border-color 0.25s; + } + button:hover { + border-color: #646cff; + } + button:focus, + button:focus-visible { + outline: 4px auto -webkit-focus-ring-color; + } + + @media (prefers-color-scheme: light) { + a:hover { + color: #747bff; + } + button { + background-color: #f9f9f9; + } + } + ` +} + +declare global { + interface HTMLElementTagNameMap { + 'my-element': MyElement + } +} diff --git a/v3/internal/templates/lit-ts/frontend/src/vite-env.d.ts b/v3/internal/templates/lit-ts/frontend/src/vite-env.d.ts new file mode 100644 index 000000000..11f02fe2a --- /dev/null +++ b/v3/internal/templates/lit-ts/frontend/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/v3/internal/templates/lit-ts/frontend/tsconfig.json b/v3/internal/templates/lit-ts/frontend/tsconfig.json new file mode 100644 index 000000000..b080b2b2c --- /dev/null +++ b/v3/internal/templates/lit-ts/frontend/tsconfig.json @@ -0,0 +1,23 @@ +{ + "compilerOptions": { + "module": "ESNext", + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "declaration": true, + "emitDeclarationOnly": true, + "outDir": "./types", + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "moduleResolution": "Node", + "isolatedModules": true, + "allowSyntheticDefaultImports": true, + "experimentalDecorators": true, + "forceConsistentCasingInFileNames": true, + "useDefineForClassFields": false, + "skipLibCheck": true + }, + "include": ["src/**/*.ts"], + "references": [{ "path": "./tsconfig.node.json" }] +} diff --git a/v3/internal/templates/lit-ts/frontend/tsconfig.node.json b/v3/internal/templates/lit-ts/frontend/tsconfig.node.json new file mode 100644 index 000000000..9d31e2aed --- /dev/null +++ b/v3/internal/templates/lit-ts/frontend/tsconfig.node.json @@ -0,0 +1,9 @@ +{ + "compilerOptions": { + "composite": true, + "module": "ESNext", + "moduleResolution": "Node", + "allowSyntheticDefaultImports": true + }, + "include": ["vite.config.ts"] +} diff --git a/v3/internal/templates/lit-ts/frontend/vite.config.ts b/v3/internal/templates/lit-ts/frontend/vite.config.ts new file mode 100644 index 000000000..fe69491e3 --- /dev/null +++ b/v3/internal/templates/lit-ts/frontend/vite.config.ts @@ -0,0 +1,14 @@ +import { defineConfig } from 'vite' + +// https://vitejs.dev/config/ +export default defineConfig({ + build: { + lib: { + entry: 'src/my-element.ts', + formats: ['es'], + }, + rollupOptions: { + external: /^lit/, + }, + }, +}) diff --git a/v3/internal/templates/lit-ts/go.mod.tmpl b/v3/internal/templates/lit-ts/go.mod.tmpl new file mode 100644 index 000000000..3dad9b6fa --- /dev/null +++ b/v3/internal/templates/lit-ts/go.mod.tmpl @@ -0,0 +1,19 @@ +module changeme + +go 1.21 + +require github.com/wailsapp/wails/v3 v3.0.0-alpha.0 + +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 +) +{{if gt (len .LocalModulePath) 0}} +replace github.com/wailsapp/wails/v3 => {{.LocalModulePath}}v3 +{{end}} diff --git a/v3/internal/templates/lit-ts/go.sum.tmpl b/v3/internal/templates/lit-ts/go.sum.tmpl new file mode 100644 index 000000000..c06e0dbc6 --- /dev/null +++ b/v3/internal/templates/lit-ts/go.sum.tmpl @@ -0,0 +1,33 @@ +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/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/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/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= +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/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/v3/internal/templates/lit-ts/main.go.tmpl b/v3/internal/templates/lit-ts/main.go.tmpl new file mode 100644 index 000000000..c30a939dc --- /dev/null +++ b/v3/internal/templates/lit-ts/main.go.tmpl @@ -0,0 +1,43 @@ +package main + +import ( + "embed" + _ "embed" + "log" + + "github.com/wailsapp/wails/v3/pkg/application" +) + +//go:embed frontend/dist +var assets embed.FS + +func main() { + app := application.New(application.Options{ + Name: "{{.ProjectName}}", + Description: "A demo of using raw HTML & CSS", + Assets: application.AssetOptions{ + FS: assets, + }, + Mac: application.MacOptions{ + ApplicationShouldTerminateAfterLastWindowClosed: true, + }, + }) + // Create window + app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{ + Title: "Plain Bundle", + CSS: `body { background-color: rgba(255, 255, 255, 0); } .main { color: white; margin: 20%; }`, + Mac: application.MacWindow{ + InvisibleTitleBarHeight: 50, + Backdrop: application.MacBackdropTranslucent, + TitleBar: application.MacTitleBarHiddenInset, + }, + + URL: "/", + }) + + err := app.Run() + + if err != nil { + log.Fatal(err) + } +} diff --git a/v3/internal/templates/lit-ts/template.json b/v3/internal/templates/lit-ts/template.json new file mode 100644 index 000000000..f6aada621 --- /dev/null +++ b/v3/internal/templates/lit-ts/template.json @@ -0,0 +1,8 @@ +{ + "name": "Lit + Vite (Typescript)", + "shortname": "lit-ts", + "author": "Lea Anthony", + "description": "Lit + TS + Vite development server", + "helpurl": "https://wails.io", + "version": 3 +} \ No newline at end of file diff --git a/v3/internal/templates/lit/Taskfile.tmpl.yml b/v3/internal/templates/lit/Taskfile.tmpl.yml new file mode 100644 index 000000000..0be9948e1 --- /dev/null +++ b/v3/internal/templates/lit/Taskfile.tmpl.yml @@ -0,0 +1,125 @@ +version: '3' + +vars: + APP_NAME: "{{.ProjectName}}" + +tasks: + + pre-build: + summary: Pre-build hooks + + post-build: + summary: Post-build hooks + + install-frontend-deps: + summary: Install frontend dependencies + dir: frontend + sources: + - package.json + - package-lock.json + generates: + - node_modules/* + preconditions: + - sh: npm version + msg: "Looks like npm isn't installed. Npm is part of the Node installer: https://nodejs.org/en/download/" + cmds: + - npm install + + build-frontend: + summary: Build the frontend project + dir: frontend + deps: + - install-frontend-deps + cmds: + - npm run build + + build:darwin: + summary: Builds the application + platforms: + - darwin + cmds: + - task: pre-build + - task: build-frontend + - go build -gcflags=all="-N -l" -o bin/testapp + - task: post-build + env: + CGO_CFLAGS: "-mmacosx-version-min=10.13" + CGO_LDFLAGS: "-mmacosx-version-min=10.13" + MACOSX_DEPLOYMENT_TARGET: "10.13" + + build:windows: + summary: Builds the application for Windows + platforms: + - windows + cmds: + - task: pre-build + - task: build-frontend + - go build -gcflags=all="-N -l" -o bin/testapp.exe + - task: post-build + + build: + summary: Builds the application + cmds: + - task: build:darwin + - task: build:windows + + generate-icons: + summary: Generates Windows `.ico` and Mac `.icns` files from an image + dir: build + cmds: + # Generates both .ico and .icns files + - wails generate icons -input appicon.png + + build-app-prod-darwin: + summary: Creates a production build of the application + cmds: + - task: pre-build + - task: build-frontend + - GOOS=darwin GOARCH={{ "{{.ARCH}}" }} go build -tags production -ldflags="-w -s" -o build/bin/{{ "{{.APP_NAME}}" }} + - task: post-build + env: + CGO_CFLAGS: "-mmacosx-version-min=10.13" + CGO_LDFLAGS: "-mmacosx-version-min=10.13" + MACOSX_DEPLOYMENT_TARGET: "10.13" + vars: + ARCH: $GOARCH + + + create-app-bundle: + summary: Builds a `.app` bundle + cmds: + - mkdir -p {{ "{{.APP_NAME}}" }}.app/Contents/{MacOS,Resources} + - cp build/icons.icns {{ "{{.APP_NAME}}" }}.app/Contents/Resources + - cp build/bin/{{ "{{.APP_NAME}}" }} {{ "{{.APP_NAME}}" }}.app/Contents/MacOS + - cp build/Info.plist {{ "{{.APP_NAME}}" }}.app/Contents + + package-darwin-arm64: + summary: Packages a production build of the application into a `.app` bundle + platform: darwin + deps: + - task: build-app-prod-darwin + vars: + ARCH: arm64 + - generate-icons + cmds: + - task: create-app-bundle + + generate:syso: + dir: build + platform: windows + cmds: + - wails generate syso -arch {{ "{{.ARCH}}" }} -icon icon.ico -manifest wails.exe.manifest -info info.json -out ../wails.syso + vars: + ARCH: $GOARCH + + package:windows: + summary: Packages a production build of the application into a `.exe` bundle + platform: windows + deps: + - generate-icons + cmds: + - task: generate:syso + vars: + ARCH: amd64 + - go build -tags production -ldflags="-w -s -H windowsgui" -o bin/{{ "{{.APP_NAME}}" }}.exe + - powershell Remove-item wails.syso diff --git a/v3/internal/templates/lit/build/Info.dev.plist.tmpl b/v3/internal/templates/lit/build/Info.dev.plist.tmpl new file mode 100644 index 000000000..7efa134f4 --- /dev/null +++ b/v3/internal/templates/lit/build/Info.dev.plist.tmpl @@ -0,0 +1,32 @@ + + + + CFBundlePackageType + APPL + CFBundleName + My Product Name + CFBundleExecutable + {{.ProjectName}} + CFBundleIdentifier + com.wails.{{.ProjectName}} + CFBundleVersion + v1.0.0 + CFBundleGetInfoString + This is a comment + CFBundleShortVersionString + v1.0.0 + CFBundleIconFile + icons + LSMinimumSystemVersion + 10.13.0 + NSHighResolutionCapable + true + NSHumanReadableCopyright + (c) 2023 My Company Name + NSAppTransportSecurity + + NSAllowsLocalNetworking + + + + \ No newline at end of file diff --git a/v3/internal/templates/lit/build/Info.plist.tmpl b/v3/internal/templates/lit/build/Info.plist.tmpl new file mode 100644 index 000000000..6bfa8c316 --- /dev/null +++ b/v3/internal/templates/lit/build/Info.plist.tmpl @@ -0,0 +1,27 @@ + + + + CFBundlePackageType + APPL + CFBundleName + My Product Name + CFBundleExecutable + {{.ProjectName}} + CFBundleIdentifier + com.wails.{{.ProjectName}} + CFBundleVersion + v1.0.0 + CFBundleGetInfoString + This is a comment + CFBundleShortVersionString + v1.0.0 + CFBundleIconFile + icons + LSMinimumSystemVersion + 10.13.0 + NSHighResolutionCapable + true + NSHumanReadableCopyright + (c) 2023 My Company Name + + \ No newline at end of file diff --git a/v3/internal/templates/lit/build/appicon.png b/v3/internal/templates/lit/build/appicon.png new file mode 100644 index 000000000..63617fe4f Binary files /dev/null and b/v3/internal/templates/lit/build/appicon.png differ diff --git a/v3/internal/templates/lit/build/icons.icns b/v3/internal/templates/lit/build/icons.icns new file mode 100644 index 000000000..1b5bd4c86 Binary files /dev/null and b/v3/internal/templates/lit/build/icons.icns differ diff --git a/v3/internal/templates/lit/frontend/.gitignore b/v3/internal/templates/lit/frontend/.gitignore new file mode 100644 index 000000000..a547bf36d --- /dev/null +++ b/v3/internal/templates/lit/frontend/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/v3/internal/templates/lit/frontend/index.html b/v3/internal/templates/lit/frontend/index.html new file mode 100644 index 000000000..9da997ad7 --- /dev/null +++ b/v3/internal/templates/lit/frontend/index.html @@ -0,0 +1,16 @@ + + + + + + + Wails + Lit + + + + + +

Wails + Lit

+
+ + diff --git a/v3/internal/templates/lit/frontend/package.json b/v3/internal/templates/lit/frontend/package.json new file mode 100644 index 000000000..1c4979def --- /dev/null +++ b/v3/internal/templates/lit/frontend/package.json @@ -0,0 +1,23 @@ +{ + "name": "frontend", + "private": true, + "version": "0.0.0", + "type": "module", + "main": "dist/my-element.es.js", + "exports": { + ".": "./dist/my-element.es.js" + }, + "files": [ + "dist" + ], + "scripts": { + "dev": "vite", + "build": "vite build" + }, + "dependencies": { + "lit": "^2.4.1" + }, + "devDependencies": { + "vite": "^4.0.0" + } +} \ No newline at end of file diff --git a/v3/internal/templates/lit/frontend/public/wails.png b/v3/internal/templates/lit/frontend/public/wails.png new file mode 100644 index 000000000..8bdf42483 Binary files /dev/null and b/v3/internal/templates/lit/frontend/public/wails.png differ diff --git a/v3/internal/templates/lit/frontend/src/assets/lit.svg b/v3/internal/templates/lit/frontend/src/assets/lit.svg new file mode 100644 index 000000000..4a9c1fe66 --- /dev/null +++ b/v3/internal/templates/lit/frontend/src/assets/lit.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/v3/internal/templates/lit/frontend/src/index.css b/v3/internal/templates/lit/frontend/src/index.css new file mode 100644 index 000000000..b52d4c639 --- /dev/null +++ b/v3/internal/templates/lit/frontend/src/index.css @@ -0,0 +1,31 @@ +:root { + font-family: Inter, Avenir, Helvetica, Arial, sans-serif; + font-size: 16px; + line-height: 24px; + font-weight: 400; + + color-scheme: light dark; + color: rgba(255, 255, 255, 0.87); + background-color: #242424; + + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + -webkit-text-size-adjust: 100%; +} + +body { + margin: 0; + display: flex; + place-items: center; + min-width: 320px; + min-height: 100vh; +} + +@media (prefers-color-scheme: light) { + :root { + color: #213547; + background-color: #ffffff; + } +} diff --git a/v3/internal/templates/lit/frontend/src/my-element.js b/v3/internal/templates/lit/frontend/src/my-element.js new file mode 100644 index 000000000..515f8fa58 --- /dev/null +++ b/v3/internal/templates/lit/frontend/src/my-element.js @@ -0,0 +1,129 @@ +import { LitElement, css, html } from 'lit' +import litLogo from './assets/lit.svg' + +/** + * An example element. + * + * @slot - This element has a slot + * @csspart button - The button + */ +export class MyElement extends LitElement { + static get properties() { + return { + /** + * Copy for the read the docs hint. + */ + docsHint: { type: String }, + + /** + * The number of times the button has been clicked. + */ + count: { type: Number }, + } + } + + constructor() { + super() + this.docsHint = 'Click on the Wails and Lit logos to learn more' + this.count = 0 + } + + render() { + return html` + + +
+ +
+

${this.docsHint}

+ ` + } + + _onClick() { + this.count++ + } + + static get styles() { + return css` + :host { + max-width: 1280px; + margin: 0 auto; + padding: 2rem; + text-align: center; + } + + .logo { + height: 6em; + padding: 1.5em; + will-change: filter; + } + .logo:hover { + filter: drop-shadow(0 0 2em #646cffaa); + } + .logo.lit:hover { + filter: drop-shadow(0 0 2em #325cffaa); + } + + .card { + padding: 2em; + } + + .read-the-docs { + color: #888; + } + + a { + font-weight: 500; + color: #646cff; + text-decoration: inherit; + } + a:hover { + color: #535bf2; + } + + h1 { + font-size: 3.2em; + line-height: 1.1; + } + + button { + border-radius: 8px; + border: 1px solid transparent; + padding: 0.6em 1.2em; + font-size: 1em; + font-weight: 500; + font-family: inherit; + background-color: #1a1a1a; + cursor: pointer; + transition: border-color 0.25s; + } + button:hover { + border-color: #646cff; + } + button:focus, + button:focus-visible { + outline: 4px auto -webkit-focus-ring-color; + } + + @media (prefers-color-scheme: light) { + a:hover { + color: #747bff; + } + button { + background-color: #f9f9f9; + } + } + ` + } +} + +window.customElements.define('my-element', MyElement) diff --git a/v3/internal/templates/lit/frontend/vite.config.js b/v3/internal/templates/lit/frontend/vite.config.js new file mode 100644 index 000000000..3847c1f38 --- /dev/null +++ b/v3/internal/templates/lit/frontend/vite.config.js @@ -0,0 +1,14 @@ +import { defineConfig } from 'vite' + +// https://vitejs.dev/config/ +export default defineConfig({ + build: { + lib: { + entry: 'src/my-element.js', + formats: ['es'], + }, + rollupOptions: { + external: /^lit/, + }, + }, +}) diff --git a/v3/internal/templates/lit/go.mod.tmpl b/v3/internal/templates/lit/go.mod.tmpl new file mode 100644 index 000000000..3dad9b6fa --- /dev/null +++ b/v3/internal/templates/lit/go.mod.tmpl @@ -0,0 +1,19 @@ +module changeme + +go 1.21 + +require github.com/wailsapp/wails/v3 v3.0.0-alpha.0 + +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 +) +{{if gt (len .LocalModulePath) 0}} +replace github.com/wailsapp/wails/v3 => {{.LocalModulePath}}v3 +{{end}} diff --git a/v3/internal/templates/lit/go.sum.tmpl b/v3/internal/templates/lit/go.sum.tmpl new file mode 100644 index 000000000..c06e0dbc6 --- /dev/null +++ b/v3/internal/templates/lit/go.sum.tmpl @@ -0,0 +1,33 @@ +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/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/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/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= +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/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/v3/internal/templates/lit/main.go.tmpl b/v3/internal/templates/lit/main.go.tmpl new file mode 100644 index 000000000..c30a939dc --- /dev/null +++ b/v3/internal/templates/lit/main.go.tmpl @@ -0,0 +1,43 @@ +package main + +import ( + "embed" + _ "embed" + "log" + + "github.com/wailsapp/wails/v3/pkg/application" +) + +//go:embed frontend/dist +var assets embed.FS + +func main() { + app := application.New(application.Options{ + Name: "{{.ProjectName}}", + Description: "A demo of using raw HTML & CSS", + Assets: application.AssetOptions{ + FS: assets, + }, + Mac: application.MacOptions{ + ApplicationShouldTerminateAfterLastWindowClosed: true, + }, + }) + // Create window + app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{ + Title: "Plain Bundle", + CSS: `body { background-color: rgba(255, 255, 255, 0); } .main { color: white; margin: 20%; }`, + Mac: application.MacWindow{ + InvisibleTitleBarHeight: 50, + Backdrop: application.MacBackdropTranslucent, + TitleBar: application.MacTitleBarHiddenInset, + }, + + URL: "/", + }) + + err := app.Run() + + if err != nil { + log.Fatal(err) + } +} diff --git a/v3/internal/templates/lit/template.json b/v3/internal/templates/lit/template.json new file mode 100644 index 000000000..76557a94f --- /dev/null +++ b/v3/internal/templates/lit/template.json @@ -0,0 +1,8 @@ +{ + "name": "Lit + Vite", + "shortname": "lit", + "author": "Lea Anthony", + "description": "Lit + Vite development server", + "helpurl": "https://wails.io", + "version": 3 +} \ No newline at end of file diff --git a/v3/internal/templates/preact-ts/Taskfile.tmpl.yml b/v3/internal/templates/preact-ts/Taskfile.tmpl.yml new file mode 100644 index 000000000..f9646a735 --- /dev/null +++ b/v3/internal/templates/preact-ts/Taskfile.tmpl.yml @@ -0,0 +1,125 @@ +version: '3' + +vars: + APP_NAME: "{{.ProjectName}}" + +tasks: + + pre-build: + summary: Pre-build hooks + + post-build: + summary: Post-build hooks + + install-frontend-deps: + summary: Install frontend dependencies + dir: frontend + sources: + - package.json + - package-lock.json + generates: + - node_modules/* + preconditions: + - sh: npm version + msg: "Looks like npm isn't installed. Npm is part of the Node installer: https://nodejs.org/en/download/" + cmds: + - npm install + + build-frontend: + summary: Build the frontend project + dir: frontend + deps: + - install-frontend-deps + cmds: + - npm run build + + build:darwin: + summary: Builds the application + platforms: + - darwin + cmds: + - task: pre-build + - task: build-frontend + - go build -gcflags=all="-N -l" -o bin/testapp + - task: post-build + env: + CGO_CFLAGS: "-mmacosx-version-min=10.13" + CGO_LDFLAGS: "-mmacosx-version-min=10.13" + MACOSX_DEPLOYMENT_TARGET: "10.13" + + build:windows: + summary: Builds the application for Windows + platforms: + - windows + cmds: + - task: pre-build + - task: build-frontend + - go build -gcflags=all="-N -l" -o bin/testapp.exe + - task: post-build + + build: + summary: Builds the application + cmds: + - task: build:darwin + - task: build:windows + + generate-icons: + summary: Generates Windows `.ico` and Mac `.icns` files from an image + dir: build + cmds: + # Generates both .ico and .icns files + - wails generate icons -input appicon.png + + build-app-prod-darwin: + summary: Creates a production build of the application + cmds: + - task: pre-build + - task: build-frontend + - GOOS=darwin GOARCH={{ "{{.ARCH}}" }} go build -tags production -ldflags="-w -s" -o build/bin/{{ "{{.APP_NAME}}" }} + - task: post-build + env: + CGO_CFLAGS: "-mmacosx-version-min=10.13" + CGO_LDFLAGS: "-mmacosx-version-min=10.13" + MACOSX_DEPLOYMENT_TARGET: "10.13" + vars: + ARCH: $GOARCH + + + create-app-bundle: + summary: Builds a `.app` bundle + cmds: + - mkdir -p {{ "{{.APP_NAME}}" }}.app/Contents/{MacOS,Resources} + - cp build/icons.icns {{ "{{.APP_NAME}}" }}.app/Contents/Resources + - cp build/bin/{{ "{{.APP_NAME}}" }} {{ "{{.APP_NAME}}" }}.app/Contents/MacOS + - cp build/Info.plist {{ "{{.APP_NAME}}" }}.app/Contents + + package-darwin-arm64: + summary: Packages a production build of the application into a `.app` bundle + platform: darwin + deps: + - task: build-app-prod-darwin + vars: + ARCH: arm64 + - generate-icons + cmds: + - task: create-app-bundle + + generate:syso: + dir: build + platform: windows + cmds: + - wails generate syso -arch {{ "{{.ARCH}}" }} -icon icon.ico -manifest wails.exe.manifest -info info.json -out ../wails.syso + vars: + ARCH: $GOARCH + + package:windows: + summary: Packages a production build of the application into a `.exe` bundle + platform: windows + deps: + - generate-icons + cmds: + - task: generate:syso + vars: + ARCH: amd64 + - go build -tags production -ldflags="-w -s -H windowsgui" -o bin/{{ "{{.APP_NAME}}" }}.exe + - powershell Remove-item wails.syso diff --git a/v3/internal/templates/preact-ts/build/Info.dev.plist.tmpl b/v3/internal/templates/preact-ts/build/Info.dev.plist.tmpl new file mode 100644 index 000000000..7efa134f4 --- /dev/null +++ b/v3/internal/templates/preact-ts/build/Info.dev.plist.tmpl @@ -0,0 +1,32 @@ + + + + CFBundlePackageType + APPL + CFBundleName + My Product Name + CFBundleExecutable + {{.ProjectName}} + CFBundleIdentifier + com.wails.{{.ProjectName}} + CFBundleVersion + v1.0.0 + CFBundleGetInfoString + This is a comment + CFBundleShortVersionString + v1.0.0 + CFBundleIconFile + icons + LSMinimumSystemVersion + 10.13.0 + NSHighResolutionCapable + true + NSHumanReadableCopyright + (c) 2023 My Company Name + NSAppTransportSecurity + + NSAllowsLocalNetworking + + + + \ No newline at end of file diff --git a/v3/internal/templates/preact-ts/build/Info.plist.tmpl b/v3/internal/templates/preact-ts/build/Info.plist.tmpl new file mode 100644 index 000000000..6bfa8c316 --- /dev/null +++ b/v3/internal/templates/preact-ts/build/Info.plist.tmpl @@ -0,0 +1,27 @@ + + + + CFBundlePackageType + APPL + CFBundleName + My Product Name + CFBundleExecutable + {{.ProjectName}} + CFBundleIdentifier + com.wails.{{.ProjectName}} + CFBundleVersion + v1.0.0 + CFBundleGetInfoString + This is a comment + CFBundleShortVersionString + v1.0.0 + CFBundleIconFile + icons + LSMinimumSystemVersion + 10.13.0 + NSHighResolutionCapable + true + NSHumanReadableCopyright + (c) 2023 My Company Name + + \ No newline at end of file diff --git a/v3/internal/templates/preact-ts/build/appicon.png b/v3/internal/templates/preact-ts/build/appicon.png new file mode 100644 index 000000000..63617fe4f Binary files /dev/null and b/v3/internal/templates/preact-ts/build/appicon.png differ diff --git a/v3/internal/templates/preact-ts/build/icons.icns b/v3/internal/templates/preact-ts/build/icons.icns new file mode 100644 index 000000000..1b5bd4c86 Binary files /dev/null and b/v3/internal/templates/preact-ts/build/icons.icns differ diff --git a/v3/internal/templates/preact-ts/frontend/.gitignore b/v3/internal/templates/preact-ts/frontend/.gitignore new file mode 100644 index 000000000..a547bf36d --- /dev/null +++ b/v3/internal/templates/preact-ts/frontend/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/v3/internal/templates/preact-ts/frontend/index.html b/v3/internal/templates/preact-ts/frontend/index.html new file mode 100644 index 000000000..9270ebd4e --- /dev/null +++ b/v3/internal/templates/preact-ts/frontend/index.html @@ -0,0 +1,13 @@ + + + + + + + Wails + Preact + TS + + +
+ + + diff --git a/v3/internal/templates/preact-ts/frontend/package.json b/v3/internal/templates/preact-ts/frontend/package.json new file mode 100644 index 000000000..cab9db654 --- /dev/null +++ b/v3/internal/templates/preact-ts/frontend/package.json @@ -0,0 +1,19 @@ +{ + "name": "frontend", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc && vite build", + "preview": "vite preview" + }, + "dependencies": { + "preact": "^10.11.3" + }, + "devDependencies": { + "@preact/preset-vite": "^2.4.0", + "typescript": "^4.9.3", + "vite": "^4.0.0" + } +} \ No newline at end of file diff --git a/v3/internal/templates/preact-ts/frontend/public/wails.png b/v3/internal/templates/preact-ts/frontend/public/wails.png new file mode 100644 index 000000000..8bdf42483 Binary files /dev/null and b/v3/internal/templates/preact-ts/frontend/public/wails.png differ diff --git a/v3/internal/templates/preact-ts/frontend/src/app.css b/v3/internal/templates/preact-ts/frontend/src/app.css new file mode 100644 index 000000000..088ed3ace --- /dev/null +++ b/v3/internal/templates/preact-ts/frontend/src/app.css @@ -0,0 +1,25 @@ +#app { + max-width: 1280px; + margin: 0 auto; + padding: 2rem; + text-align: center; +} + +.logo { + height: 6em; + padding: 1.5em; +} +.logo:hover { + filter: drop-shadow(0 0 2em #646cffaa); +} +.logo.preact:hover { + filter: drop-shadow(0 0 2em #673ab8aa); +} + +.card { + padding: 2em; +} + +.read-the-docs { + color: #888; +} diff --git a/v3/internal/templates/preact-ts/frontend/src/app.tsx b/v3/internal/templates/preact-ts/frontend/src/app.tsx new file mode 100644 index 000000000..ccde5b9f3 --- /dev/null +++ b/v3/internal/templates/preact-ts/frontend/src/app.tsx @@ -0,0 +1,32 @@ +import { useState } from 'preact/hooks' +import preactLogo from './assets/preact.svg' +import './app.css' + +export function App() { + const [count, setCount] = useState(0) + + return ( + <> + +

Vite + Preact

+
+ +

+ Edit src/app.tsx and save to test HMR +

+
+

+ Click on the Vite and Preact logos to learn more +

+ + ) +} diff --git a/v3/internal/templates/preact-ts/frontend/src/assets/preact.svg b/v3/internal/templates/preact-ts/frontend/src/assets/preact.svg new file mode 100644 index 000000000..908f17def --- /dev/null +++ b/v3/internal/templates/preact-ts/frontend/src/assets/preact.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/v3/internal/templates/preact-ts/frontend/src/index.css b/v3/internal/templates/preact-ts/frontend/src/index.css new file mode 100644 index 000000000..917888c1d --- /dev/null +++ b/v3/internal/templates/preact-ts/frontend/src/index.css @@ -0,0 +1,70 @@ +:root { + font-family: Inter, Avenir, Helvetica, Arial, sans-serif; + font-size: 16px; + line-height: 24px; + font-weight: 400; + + color-scheme: light dark; + color: rgba(255, 255, 255, 0.87); + background-color: #242424; + + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + -webkit-text-size-adjust: 100%; +} + +a { + font-weight: 500; + color: #646cff; + text-decoration: inherit; +} +a:hover { + color: #535bf2; +} + +body { + margin: 0; + display: flex; + place-items: center; + min-width: 320px; + min-height: 100vh; +} + +h1 { + font-size: 3.2em; + line-height: 1.1; +} + +button { + border-radius: 8px; + border: 1px solid transparent; + padding: 0.6em 1.2em; + font-size: 1em; + font-weight: 500; + font-family: inherit; + background-color: #1a1a1a; + cursor: pointer; + transition: border-color 0.25s; +} +button:hover { + border-color: #646cff; +} +button:focus, +button:focus-visible { + outline: 4px auto -webkit-focus-ring-color; +} + +@media (prefers-color-scheme: light) { + :root { + color: #213547; + background-color: #ffffff; + } + a:hover { + color: #747bff; + } + button { + background-color: #f9f9f9; + } +} diff --git a/v3/internal/templates/preact-ts/frontend/src/main.tsx b/v3/internal/templates/preact-ts/frontend/src/main.tsx new file mode 100644 index 000000000..e0ce3e998 --- /dev/null +++ b/v3/internal/templates/preact-ts/frontend/src/main.tsx @@ -0,0 +1,5 @@ +import { render } from 'preact' +import { App } from './app' +import './index.css' + +render(, document.getElementById('app') as HTMLElement) diff --git a/v3/internal/templates/preact-ts/frontend/src/vite-env.d.ts b/v3/internal/templates/preact-ts/frontend/src/vite-env.d.ts new file mode 100644 index 000000000..11f02fe2a --- /dev/null +++ b/v3/internal/templates/preact-ts/frontend/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/v3/internal/templates/preact-ts/frontend/tsconfig.json b/v3/internal/templates/preact-ts/frontend/tsconfig.json new file mode 100644 index 000000000..9c1b1e0aa --- /dev/null +++ b/v3/internal/templates/preact-ts/frontend/tsconfig.json @@ -0,0 +1,22 @@ +{ + "compilerOptions": { + "target": "ESNext", + "useDefineForClassFields": true, + "lib": ["DOM", "DOM.Iterable", "ESNext"], + "allowJs": false, + "skipLibCheck": true, + "esModuleInterop": false, + "allowSyntheticDefaultImports": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "module": "ESNext", + "moduleResolution": "Node", + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + "jsxImportSource": "preact" + }, + "include": ["src"], + "references": [{ "path": "./tsconfig.node.json" }] +} diff --git a/v3/internal/templates/preact-ts/frontend/tsconfig.node.json b/v3/internal/templates/preact-ts/frontend/tsconfig.node.json new file mode 100644 index 000000000..9d31e2aed --- /dev/null +++ b/v3/internal/templates/preact-ts/frontend/tsconfig.node.json @@ -0,0 +1,9 @@ +{ + "compilerOptions": { + "composite": true, + "module": "ESNext", + "moduleResolution": "Node", + "allowSyntheticDefaultImports": true + }, + "include": ["vite.config.ts"] +} diff --git a/v3/internal/templates/preact-ts/frontend/vite.config.ts b/v3/internal/templates/preact-ts/frontend/vite.config.ts new file mode 100644 index 000000000..29b326faf --- /dev/null +++ b/v3/internal/templates/preact-ts/frontend/vite.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite' +import preact from '@preact/preset-vite' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [preact()], +}) diff --git a/v3/internal/templates/preact-ts/go.mod.tmpl b/v3/internal/templates/preact-ts/go.mod.tmpl new file mode 100644 index 000000000..3dad9b6fa --- /dev/null +++ b/v3/internal/templates/preact-ts/go.mod.tmpl @@ -0,0 +1,19 @@ +module changeme + +go 1.21 + +require github.com/wailsapp/wails/v3 v3.0.0-alpha.0 + +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 +) +{{if gt (len .LocalModulePath) 0}} +replace github.com/wailsapp/wails/v3 => {{.LocalModulePath}}v3 +{{end}} diff --git a/v3/internal/templates/preact-ts/go.sum.tmpl b/v3/internal/templates/preact-ts/go.sum.tmpl new file mode 100644 index 000000000..c06e0dbc6 --- /dev/null +++ b/v3/internal/templates/preact-ts/go.sum.tmpl @@ -0,0 +1,33 @@ +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/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/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/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= +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/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/v3/internal/templates/preact-ts/main.go.tmpl b/v3/internal/templates/preact-ts/main.go.tmpl new file mode 100644 index 000000000..96c4fd5f0 --- /dev/null +++ b/v3/internal/templates/preact-ts/main.go.tmpl @@ -0,0 +1,43 @@ +package main + +import ( + "embed" + _ "embed" + "log" + + "github.com/wailsapp/wails/v3/pkg/application" +) + +//go:embed frontend/dist +var assets embed.FS + +func main() { + app := application.New(application.Options{ + Name: "{{.ProjectName}}", + Description: "A demo of using raw HTML & CSS", + Assets: application.AssetOptions{ + FS: assets, + }, + Mac: application.MacOptions{ + ApplicationShouldTerminateAfterLastWindowClosed: true, + }, + }) + // Create window + app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{ + Title: "Plain Bundle", + CSS: `body { background-color: rgba(255, 255, 255, 0); } .main { color: white; margin: 20%; }`, + Mac: application.MacWindow{ + InvisibleTitleBarHeight: 50, + Backdrop: application.MacBackdropTranslucent, + TitleBar: application.MacTitleBarHiddenInset, + }, + + URL: "/", + }) + + err := app.Run() + + if err != nil { + log.Fatal(err) + } +} diff --git a/v3/internal/templates/preact-ts/template.json b/v3/internal/templates/preact-ts/template.json new file mode 100644 index 000000000..bd4ccc20b --- /dev/null +++ b/v3/internal/templates/preact-ts/template.json @@ -0,0 +1,8 @@ +{ + "name": "Preact + Vite (Typescript)", + "shortname": "preact-ts", + "author": "Lea Anthony", + "description": "Preact + Vite development server", + "helpurl": "https://wails.io", + "version": 3 +} \ No newline at end of file diff --git a/v3/internal/templates/preact/Taskfile.tmpl.yml b/v3/internal/templates/preact/Taskfile.tmpl.yml new file mode 100644 index 000000000..f9646a735 --- /dev/null +++ b/v3/internal/templates/preact/Taskfile.tmpl.yml @@ -0,0 +1,125 @@ +version: '3' + +vars: + APP_NAME: "{{.ProjectName}}" + +tasks: + + pre-build: + summary: Pre-build hooks + + post-build: + summary: Post-build hooks + + install-frontend-deps: + summary: Install frontend dependencies + dir: frontend + sources: + - package.json + - package-lock.json + generates: + - node_modules/* + preconditions: + - sh: npm version + msg: "Looks like npm isn't installed. Npm is part of the Node installer: https://nodejs.org/en/download/" + cmds: + - npm install + + build-frontend: + summary: Build the frontend project + dir: frontend + deps: + - install-frontend-deps + cmds: + - npm run build + + build:darwin: + summary: Builds the application + platforms: + - darwin + cmds: + - task: pre-build + - task: build-frontend + - go build -gcflags=all="-N -l" -o bin/testapp + - task: post-build + env: + CGO_CFLAGS: "-mmacosx-version-min=10.13" + CGO_LDFLAGS: "-mmacosx-version-min=10.13" + MACOSX_DEPLOYMENT_TARGET: "10.13" + + build:windows: + summary: Builds the application for Windows + platforms: + - windows + cmds: + - task: pre-build + - task: build-frontend + - go build -gcflags=all="-N -l" -o bin/testapp.exe + - task: post-build + + build: + summary: Builds the application + cmds: + - task: build:darwin + - task: build:windows + + generate-icons: + summary: Generates Windows `.ico` and Mac `.icns` files from an image + dir: build + cmds: + # Generates both .ico and .icns files + - wails generate icons -input appicon.png + + build-app-prod-darwin: + summary: Creates a production build of the application + cmds: + - task: pre-build + - task: build-frontend + - GOOS=darwin GOARCH={{ "{{.ARCH}}" }} go build -tags production -ldflags="-w -s" -o build/bin/{{ "{{.APP_NAME}}" }} + - task: post-build + env: + CGO_CFLAGS: "-mmacosx-version-min=10.13" + CGO_LDFLAGS: "-mmacosx-version-min=10.13" + MACOSX_DEPLOYMENT_TARGET: "10.13" + vars: + ARCH: $GOARCH + + + create-app-bundle: + summary: Builds a `.app` bundle + cmds: + - mkdir -p {{ "{{.APP_NAME}}" }}.app/Contents/{MacOS,Resources} + - cp build/icons.icns {{ "{{.APP_NAME}}" }}.app/Contents/Resources + - cp build/bin/{{ "{{.APP_NAME}}" }} {{ "{{.APP_NAME}}" }}.app/Contents/MacOS + - cp build/Info.plist {{ "{{.APP_NAME}}" }}.app/Contents + + package-darwin-arm64: + summary: Packages a production build of the application into a `.app` bundle + platform: darwin + deps: + - task: build-app-prod-darwin + vars: + ARCH: arm64 + - generate-icons + cmds: + - task: create-app-bundle + + generate:syso: + dir: build + platform: windows + cmds: + - wails generate syso -arch {{ "{{.ARCH}}" }} -icon icon.ico -manifest wails.exe.manifest -info info.json -out ../wails.syso + vars: + ARCH: $GOARCH + + package:windows: + summary: Packages a production build of the application into a `.exe` bundle + platform: windows + deps: + - generate-icons + cmds: + - task: generate:syso + vars: + ARCH: amd64 + - go build -tags production -ldflags="-w -s -H windowsgui" -o bin/{{ "{{.APP_NAME}}" }}.exe + - powershell Remove-item wails.syso diff --git a/v3/internal/templates/preact/build/Info.dev.plist.tmpl b/v3/internal/templates/preact/build/Info.dev.plist.tmpl new file mode 100644 index 000000000..7efa134f4 --- /dev/null +++ b/v3/internal/templates/preact/build/Info.dev.plist.tmpl @@ -0,0 +1,32 @@ + + + + CFBundlePackageType + APPL + CFBundleName + My Product Name + CFBundleExecutable + {{.ProjectName}} + CFBundleIdentifier + com.wails.{{.ProjectName}} + CFBundleVersion + v1.0.0 + CFBundleGetInfoString + This is a comment + CFBundleShortVersionString + v1.0.0 + CFBundleIconFile + icons + LSMinimumSystemVersion + 10.13.0 + NSHighResolutionCapable + true + NSHumanReadableCopyright + (c) 2023 My Company Name + NSAppTransportSecurity + + NSAllowsLocalNetworking + + + + \ No newline at end of file diff --git a/v3/internal/templates/preact/build/Info.plist.tmpl b/v3/internal/templates/preact/build/Info.plist.tmpl new file mode 100644 index 000000000..6bfa8c316 --- /dev/null +++ b/v3/internal/templates/preact/build/Info.plist.tmpl @@ -0,0 +1,27 @@ + + + + CFBundlePackageType + APPL + CFBundleName + My Product Name + CFBundleExecutable + {{.ProjectName}} + CFBundleIdentifier + com.wails.{{.ProjectName}} + CFBundleVersion + v1.0.0 + CFBundleGetInfoString + This is a comment + CFBundleShortVersionString + v1.0.0 + CFBundleIconFile + icons + LSMinimumSystemVersion + 10.13.0 + NSHighResolutionCapable + true + NSHumanReadableCopyright + (c) 2023 My Company Name + + \ No newline at end of file diff --git a/v3/internal/templates/preact/build/appicon.png b/v3/internal/templates/preact/build/appicon.png new file mode 100644 index 000000000..63617fe4f Binary files /dev/null and b/v3/internal/templates/preact/build/appicon.png differ diff --git a/v3/internal/templates/preact/build/icons.icns b/v3/internal/templates/preact/build/icons.icns new file mode 100644 index 000000000..1b5bd4c86 Binary files /dev/null and b/v3/internal/templates/preact/build/icons.icns differ diff --git a/v3/internal/templates/preact/frontend/.gitignore b/v3/internal/templates/preact/frontend/.gitignore new file mode 100644 index 000000000..a547bf36d --- /dev/null +++ b/v3/internal/templates/preact/frontend/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/v3/internal/templates/preact/frontend/index.html b/v3/internal/templates/preact/frontend/index.html new file mode 100644 index 000000000..139931e70 --- /dev/null +++ b/v3/internal/templates/preact/frontend/index.html @@ -0,0 +1,13 @@ + + + + + + + Wails + Preact + + +
+ + + diff --git a/v3/internal/templates/preact/frontend/package.json b/v3/internal/templates/preact/frontend/package.json new file mode 100644 index 000000000..f44069b31 --- /dev/null +++ b/v3/internal/templates/preact/frontend/package.json @@ -0,0 +1,18 @@ +{ + "name": "frontend", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview" + }, + "dependencies": { + "preact": "^10.11.3" + }, + "devDependencies": { + "@preact/preset-vite": "^2.4.0", + "vite": "^4.0.0" + } +} \ No newline at end of file diff --git a/v3/internal/templates/preact/frontend/public/wails.png b/v3/internal/templates/preact/frontend/public/wails.png new file mode 100644 index 000000000..8bdf42483 Binary files /dev/null and b/v3/internal/templates/preact/frontend/public/wails.png differ diff --git a/v3/internal/templates/preact/frontend/src/app.css b/v3/internal/templates/preact/frontend/src/app.css new file mode 100644 index 000000000..088ed3ace --- /dev/null +++ b/v3/internal/templates/preact/frontend/src/app.css @@ -0,0 +1,25 @@ +#app { + max-width: 1280px; + margin: 0 auto; + padding: 2rem; + text-align: center; +} + +.logo { + height: 6em; + padding: 1.5em; +} +.logo:hover { + filter: drop-shadow(0 0 2em #646cffaa); +} +.logo.preact:hover { + filter: drop-shadow(0 0 2em #673ab8aa); +} + +.card { + padding: 2em; +} + +.read-the-docs { + color: #888; +} diff --git a/v3/internal/templates/preact/frontend/src/app.jsx b/v3/internal/templates/preact/frontend/src/app.jsx new file mode 100644 index 000000000..2679f4886 --- /dev/null +++ b/v3/internal/templates/preact/frontend/src/app.jsx @@ -0,0 +1,32 @@ +import { useState } from 'preact/hooks' +import preactLogo from './assets/preact.svg' +import './app.css' + +export function App() { + const [count, setCount] = useState(0) + + return ( + <> + +

Vite + Preact

+
+ +

+ Edit src/app.jsx and save to test HMR +

+
+

+ Click on the Vite and Preact logos to learn more +

+ + ) +} diff --git a/v3/internal/templates/preact/frontend/src/assets/preact.svg b/v3/internal/templates/preact/frontend/src/assets/preact.svg new file mode 100644 index 000000000..908f17def --- /dev/null +++ b/v3/internal/templates/preact/frontend/src/assets/preact.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/v3/internal/templates/preact/frontend/src/index.css b/v3/internal/templates/preact/frontend/src/index.css new file mode 100644 index 000000000..917888c1d --- /dev/null +++ b/v3/internal/templates/preact/frontend/src/index.css @@ -0,0 +1,70 @@ +:root { + font-family: Inter, Avenir, Helvetica, Arial, sans-serif; + font-size: 16px; + line-height: 24px; + font-weight: 400; + + color-scheme: light dark; + color: rgba(255, 255, 255, 0.87); + background-color: #242424; + + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + -webkit-text-size-adjust: 100%; +} + +a { + font-weight: 500; + color: #646cff; + text-decoration: inherit; +} +a:hover { + color: #535bf2; +} + +body { + margin: 0; + display: flex; + place-items: center; + min-width: 320px; + min-height: 100vh; +} + +h1 { + font-size: 3.2em; + line-height: 1.1; +} + +button { + border-radius: 8px; + border: 1px solid transparent; + padding: 0.6em 1.2em; + font-size: 1em; + font-weight: 500; + font-family: inherit; + background-color: #1a1a1a; + cursor: pointer; + transition: border-color 0.25s; +} +button:hover { + border-color: #646cff; +} +button:focus, +button:focus-visible { + outline: 4px auto -webkit-focus-ring-color; +} + +@media (prefers-color-scheme: light) { + :root { + color: #213547; + background-color: #ffffff; + } + a:hover { + color: #747bff; + } + button { + background-color: #f9f9f9; + } +} diff --git a/v3/internal/templates/preact/frontend/src/main.jsx b/v3/internal/templates/preact/frontend/src/main.jsx new file mode 100644 index 000000000..be3fbce92 --- /dev/null +++ b/v3/internal/templates/preact/frontend/src/main.jsx @@ -0,0 +1,5 @@ +import { render } from 'preact' +import { App } from './app' +import './index.css' + +render(, document.getElementById('app')) diff --git a/v3/internal/templates/preact/frontend/vite.config.js b/v3/internal/templates/preact/frontend/vite.config.js new file mode 100644 index 000000000..29b326faf --- /dev/null +++ b/v3/internal/templates/preact/frontend/vite.config.js @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite' +import preact from '@preact/preset-vite' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [preact()], +}) diff --git a/v3/internal/templates/preact/go.mod.tmpl b/v3/internal/templates/preact/go.mod.tmpl new file mode 100644 index 000000000..5a7721646 --- /dev/null +++ b/v3/internal/templates/preact/go.mod.tmpl @@ -0,0 +1,15 @@ +module changeme + +go 1.21 + +require github.com/wailsapp/wails/v3 v3.0.0-alpha.0 + +require ( + github.com/imdario/mergo v0.3.12 // indirect + github.com/leaanthony/slicer v1.5.0 // indirect + github.com/wailsapp/mimetype v1.4.1 // indirect + golang.org/x/net v0.0.0-20220722155237-a158d28d115b // indirect +) +{{if gt (len .LocalModulePath) 0}} +replace github.com/wailsapp/wails/v3 => {{.LocalModulePath}}v3 +{{end}} diff --git a/v3/internal/templates/preact/go.sum.tmpl b/v3/internal/templates/preact/go.sum.tmpl new file mode 100644 index 000000000..c06e0dbc6 --- /dev/null +++ b/v3/internal/templates/preact/go.sum.tmpl @@ -0,0 +1,33 @@ +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/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/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/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= +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/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/v3/internal/templates/preact/main.go.tmpl b/v3/internal/templates/preact/main.go.tmpl new file mode 100644 index 000000000..96c4fd5f0 --- /dev/null +++ b/v3/internal/templates/preact/main.go.tmpl @@ -0,0 +1,43 @@ +package main + +import ( + "embed" + _ "embed" + "log" + + "github.com/wailsapp/wails/v3/pkg/application" +) + +//go:embed frontend/dist +var assets embed.FS + +func main() { + app := application.New(application.Options{ + Name: "{{.ProjectName}}", + Description: "A demo of using raw HTML & CSS", + Assets: application.AssetOptions{ + FS: assets, + }, + Mac: application.MacOptions{ + ApplicationShouldTerminateAfterLastWindowClosed: true, + }, + }) + // Create window + app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{ + Title: "Plain Bundle", + CSS: `body { background-color: rgba(255, 255, 255, 0); } .main { color: white; margin: 20%; }`, + Mac: application.MacWindow{ + InvisibleTitleBarHeight: 50, + Backdrop: application.MacBackdropTranslucent, + TitleBar: application.MacTitleBarHiddenInset, + }, + + URL: "/", + }) + + err := app.Run() + + if err != nil { + log.Fatal(err) + } +} diff --git a/v3/internal/templates/preact/template.json b/v3/internal/templates/preact/template.json new file mode 100644 index 000000000..bf14fc655 --- /dev/null +++ b/v3/internal/templates/preact/template.json @@ -0,0 +1,8 @@ +{ + "name": "Preact + Vite", + "shortname": "preact", + "author": "Lea Anthony", + "description": "Preact + Vite development server", + "helpurl": "https://wails.io", + "version": 3 +} \ No newline at end of file diff --git a/v3/internal/templates/react-swc-ts/Taskfile.tmpl.yml b/v3/internal/templates/react-swc-ts/Taskfile.tmpl.yml new file mode 100644 index 000000000..975af7335 --- /dev/null +++ b/v3/internal/templates/react-swc-ts/Taskfile.tmpl.yml @@ -0,0 +1,125 @@ +version: '3' + +vars: + APP_NAME: "{{.ProjectName}}" + +tasks: + + pre-build: + summary: Pre-build hooks + + post-build: + summary: Post-build hooks + + install-frontend-deps: + summary: Install frontend dependencies + dir: frontend + sources: + - package.json + - package-lock.json + generates: + - node_modules/* + preconditions: + - sh: npm version + msg: "Looks like npm isn't installed. Npm is part of the Node installer: https://nodejs.org/en/download/" + cmds: + - npm install + + build-frontend: + summary: Build the frontend project + dir: frontend + deps: + - install-frontend-deps + cmds: + - npm run build + + build:darwin: + summary: Builds the application + platforms: + - darwin + cmds: + - task: pre-build + - task: build-frontend + - go build -gcflags=all="-N -l" -o bin/testapp + - task: post-build + env: + CGO_CFLAGS: "-mmacosx-version-min=10.13" + CGO_LDFLAGS: "-mmacosx-version-min=10.13" + MACOSX_DEPLOYMENT_TARGET: "10.13" + + build:windows: + summary: Builds the application for Windows + platforms: + - windows + cmds: + - task: pre-build + - task: build-frontend + - go build -gcflags=all="-N -l" -o bin/testapp.exe + - task: post-build + + build: + summary: Builds the application + cmds: + - task: build:darwin + - task: build:windows + + generate-icons: + summary: Generates Windows `.ico` and Mac `.icns` files from an image + dir: build + cmds: + # Generates both .ico and .icns files + - wails generate icons -input appicon.png + + build-app-prod-darwin: + summary: Creates a production build of the application + cmds: + - task: pre-build + - task: build-frontend + - GOOS=darwin GOARCH={{ "{{.ARCH}}" }} go build -tags production -ldflags="-w -s" -o build/bin/{{ "{{.APP_NAME}}" }} + - task: post-build + env: + CGO_CFLAGS: "-mmacosx-version-min=10.13" + CGO_LDFLAGS: "-mmacosx-version-min=10.13" + MACOSX_DEPLOYMENT_TARGET: "10.13" + vars: + ARCH: $GOARCH + + + create-app-bundle: + summary: Builds a `.app` bundle + cmds: + - mkdir -p {{ "{{.APP_NAME}}" }}.app/Contents/{MacOS,Resources} + - cp build/icons.icns {{ "{{.APP_NAME}}" }}.app/Contents/Resources + - cp build/bin/{{ "{{.APP_NAME}}" }} {{ "{{.APP_NAME}}" }}.app/Contents/MacOS + - cp build/Info.plist {{ "{{.APP_NAME}}" }}.app/Contents + + package-darwin-arm64: + summary: Packages a production build of the application into a `.app` bundle + platform: darwin + deps: + - task: build-app-prod-darwin + vars: + ARCH: arm64 + - generate-icons + cmds: + - task: create-app-bundle + + generate:syso: + dir: build + platform: windows + cmds: + - wails generate syso -arch {{ "{{.ARCH}}" }} -icon icon.ico -manifest wails.exe.manifest -info info.json -out ../wails.syso + vars: + ARCH: $GOARCH + + package:windows: + summary: Packages a production build of the application into a `.exe` bundle + platform: windows + deps: + - generate-icons + cmds: + - task: generate:syso + vars: + ARCH: amd64 + - go build -tags production -ldflags="-w -s -H windowsgui" -o bin/{{ "{{.APP_NAME}}" }}.exe + - powershell Remove-item wails.syso diff --git a/v3/internal/templates/react-swc-ts/build/Info.dev.plist.tmpl b/v3/internal/templates/react-swc-ts/build/Info.dev.plist.tmpl new file mode 100644 index 000000000..7efa134f4 --- /dev/null +++ b/v3/internal/templates/react-swc-ts/build/Info.dev.plist.tmpl @@ -0,0 +1,32 @@ + + + + CFBundlePackageType + APPL + CFBundleName + My Product Name + CFBundleExecutable + {{.ProjectName}} + CFBundleIdentifier + com.wails.{{.ProjectName}} + CFBundleVersion + v1.0.0 + CFBundleGetInfoString + This is a comment + CFBundleShortVersionString + v1.0.0 + CFBundleIconFile + icons + LSMinimumSystemVersion + 10.13.0 + NSHighResolutionCapable + true + NSHumanReadableCopyright + (c) 2023 My Company Name + NSAppTransportSecurity + + NSAllowsLocalNetworking + + + + \ No newline at end of file diff --git a/v3/internal/templates/react-swc-ts/build/Info.plist.tmpl b/v3/internal/templates/react-swc-ts/build/Info.plist.tmpl new file mode 100644 index 000000000..6bfa8c316 --- /dev/null +++ b/v3/internal/templates/react-swc-ts/build/Info.plist.tmpl @@ -0,0 +1,27 @@ + + + + CFBundlePackageType + APPL + CFBundleName + My Product Name + CFBundleExecutable + {{.ProjectName}} + CFBundleIdentifier + com.wails.{{.ProjectName}} + CFBundleVersion + v1.0.0 + CFBundleGetInfoString + This is a comment + CFBundleShortVersionString + v1.0.0 + CFBundleIconFile + icons + LSMinimumSystemVersion + 10.13.0 + NSHighResolutionCapable + true + NSHumanReadableCopyright + (c) 2023 My Company Name + + \ No newline at end of file diff --git a/v3/internal/templates/react-swc-ts/build/appicon.png b/v3/internal/templates/react-swc-ts/build/appicon.png new file mode 100644 index 000000000..63617fe4f Binary files /dev/null and b/v3/internal/templates/react-swc-ts/build/appicon.png differ diff --git a/v3/internal/templates/react-swc-ts/build/icons.icns b/v3/internal/templates/react-swc-ts/build/icons.icns new file mode 100644 index 000000000..1b5bd4c86 Binary files /dev/null and b/v3/internal/templates/react-swc-ts/build/icons.icns differ diff --git a/v3/internal/templates/react-swc-ts/frontend/.gitignore b/v3/internal/templates/react-swc-ts/frontend/.gitignore new file mode 100644 index 000000000..a547bf36d --- /dev/null +++ b/v3/internal/templates/react-swc-ts/frontend/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/v3/internal/templates/react-swc-ts/frontend/index.html b/v3/internal/templates/react-swc-ts/frontend/index.html new file mode 100644 index 000000000..28868c572 --- /dev/null +++ b/v3/internal/templates/react-swc-ts/frontend/index.html @@ -0,0 +1,13 @@ + + + + + + + Wails + React + TS + + +
+ + + diff --git a/v3/internal/templates/react-swc-ts/frontend/package.json b/v3/internal/templates/react-swc-ts/frontend/package.json new file mode 100644 index 000000000..26dc1bca8 --- /dev/null +++ b/v3/internal/templates/react-swc-ts/frontend/package.json @@ -0,0 +1,22 @@ +{ + "name": "frontend", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc && vite build", + "preview": "vite preview" + }, + "dependencies": { + "react": "^18.2.0", + "react-dom": "^18.2.0" + }, + "devDependencies": { + "@types/react": "^18.0.26", + "@types/react-dom": "^18.0.9", + "@vitejs/plugin-react-swc": "^3.0.0", + "typescript": "^4.9.3", + "vite": "^4.0.0" + } +} \ No newline at end of file diff --git a/v3/internal/templates/react-swc-ts/frontend/public/wails.png b/v3/internal/templates/react-swc-ts/frontend/public/wails.png new file mode 100644 index 000000000..8bdf42483 Binary files /dev/null and b/v3/internal/templates/react-swc-ts/frontend/public/wails.png differ diff --git a/v3/internal/templates/react-swc-ts/frontend/src/App.css b/v3/internal/templates/react-swc-ts/frontend/src/App.css new file mode 100644 index 000000000..2c5e2ef5c --- /dev/null +++ b/v3/internal/templates/react-swc-ts/frontend/src/App.css @@ -0,0 +1,41 @@ +#root { + max-width: 1280px; + margin: 0 auto; + padding: 2rem; + text-align: center; +} + +.logo { + height: 6em; + padding: 1.5em; + will-change: filter; +} +.logo:hover { + filter: drop-shadow(0 0 2em #646cffaa); +} +.logo.react:hover { + filter: drop-shadow(0 0 2em #61dafbaa); +} + +@keyframes logo-spin { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } +} + +@media (prefers-reduced-motion: no-preference) { + a:nth-of-type(2) .logo { + animation: logo-spin infinite 20s linear; + } +} + +.card { + padding: 2em; +} + +.read-the-docs { + color: #888; +} diff --git a/v3/internal/templates/react-swc-ts/frontend/src/App.tsx b/v3/internal/templates/react-swc-ts/frontend/src/App.tsx new file mode 100644 index 000000000..cd201360b --- /dev/null +++ b/v3/internal/templates/react-swc-ts/frontend/src/App.tsx @@ -0,0 +1,34 @@ +import { useState } from 'react' +import reactLogo from './assets/react.svg' +import './App.css' + +function App() { + const [count, setCount] = useState(0) + + return ( +
+ +

Vite + React

+
+ +

+ Edit src/App.tsx and save to test HMR +

+
+

+ Click on the Vite and React logos to learn more +

+
+ ) +} + +export default App diff --git a/v3/internal/templates/react-swc-ts/frontend/src/assets/react.svg b/v3/internal/templates/react-swc-ts/frontend/src/assets/react.svg new file mode 100644 index 000000000..6c87de9bb --- /dev/null +++ b/v3/internal/templates/react-swc-ts/frontend/src/assets/react.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/v3/internal/templates/react-swc-ts/frontend/src/index.css b/v3/internal/templates/react-swc-ts/frontend/src/index.css new file mode 100644 index 000000000..917888c1d --- /dev/null +++ b/v3/internal/templates/react-swc-ts/frontend/src/index.css @@ -0,0 +1,70 @@ +:root { + font-family: Inter, Avenir, Helvetica, Arial, sans-serif; + font-size: 16px; + line-height: 24px; + font-weight: 400; + + color-scheme: light dark; + color: rgba(255, 255, 255, 0.87); + background-color: #242424; + + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + -webkit-text-size-adjust: 100%; +} + +a { + font-weight: 500; + color: #646cff; + text-decoration: inherit; +} +a:hover { + color: #535bf2; +} + +body { + margin: 0; + display: flex; + place-items: center; + min-width: 320px; + min-height: 100vh; +} + +h1 { + font-size: 3.2em; + line-height: 1.1; +} + +button { + border-radius: 8px; + border: 1px solid transparent; + padding: 0.6em 1.2em; + font-size: 1em; + font-weight: 500; + font-family: inherit; + background-color: #1a1a1a; + cursor: pointer; + transition: border-color 0.25s; +} +button:hover { + border-color: #646cff; +} +button:focus, +button:focus-visible { + outline: 4px auto -webkit-focus-ring-color; +} + +@media (prefers-color-scheme: light) { + :root { + color: #213547; + background-color: #ffffff; + } + a:hover { + color: #747bff; + } + button { + background-color: #f9f9f9; + } +} diff --git a/v3/internal/templates/react-swc-ts/frontend/src/main.tsx b/v3/internal/templates/react-swc-ts/frontend/src/main.tsx new file mode 100644 index 000000000..791f139e2 --- /dev/null +++ b/v3/internal/templates/react-swc-ts/frontend/src/main.tsx @@ -0,0 +1,10 @@ +import React from 'react' +import ReactDOM from 'react-dom/client' +import App from './App' +import './index.css' + +ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render( + + + , +) diff --git a/v3/internal/templates/react-swc-ts/frontend/src/vite-env.d.ts b/v3/internal/templates/react-swc-ts/frontend/src/vite-env.d.ts new file mode 100644 index 000000000..11f02fe2a --- /dev/null +++ b/v3/internal/templates/react-swc-ts/frontend/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/v3/internal/templates/react-swc-ts/frontend/tsconfig.json b/v3/internal/templates/react-swc-ts/frontend/tsconfig.json new file mode 100644 index 000000000..3d0a51a86 --- /dev/null +++ b/v3/internal/templates/react-swc-ts/frontend/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "target": "ESNext", + "useDefineForClassFields": true, + "lib": ["DOM", "DOM.Iterable", "ESNext"], + "allowJs": false, + "skipLibCheck": true, + "esModuleInterop": false, + "allowSyntheticDefaultImports": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "module": "ESNext", + "moduleResolution": "Node", + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx" + }, + "include": ["src"], + "references": [{ "path": "./tsconfig.node.json" }] +} diff --git a/v3/internal/templates/react-swc-ts/frontend/tsconfig.node.json b/v3/internal/templates/react-swc-ts/frontend/tsconfig.node.json new file mode 100644 index 000000000..9d31e2aed --- /dev/null +++ b/v3/internal/templates/react-swc-ts/frontend/tsconfig.node.json @@ -0,0 +1,9 @@ +{ + "compilerOptions": { + "composite": true, + "module": "ESNext", + "moduleResolution": "Node", + "allowSyntheticDefaultImports": true + }, + "include": ["vite.config.ts"] +} diff --git a/v3/internal/templates/react-swc-ts/frontend/vite.config.ts b/v3/internal/templates/react-swc-ts/frontend/vite.config.ts new file mode 100644 index 000000000..861b04b35 --- /dev/null +++ b/v3/internal/templates/react-swc-ts/frontend/vite.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite' +import react from '@vitejs/plugin-react-swc' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [react()], +}) diff --git a/v3/internal/templates/react-swc-ts/go.mod.tmpl b/v3/internal/templates/react-swc-ts/go.mod.tmpl new file mode 100644 index 000000000..3dad9b6fa --- /dev/null +++ b/v3/internal/templates/react-swc-ts/go.mod.tmpl @@ -0,0 +1,19 @@ +module changeme + +go 1.21 + +require github.com/wailsapp/wails/v3 v3.0.0-alpha.0 + +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 +) +{{if gt (len .LocalModulePath) 0}} +replace github.com/wailsapp/wails/v3 => {{.LocalModulePath}}v3 +{{end}} diff --git a/v3/internal/templates/react-swc-ts/go.sum.tmpl b/v3/internal/templates/react-swc-ts/go.sum.tmpl new file mode 100644 index 000000000..c06e0dbc6 --- /dev/null +++ b/v3/internal/templates/react-swc-ts/go.sum.tmpl @@ -0,0 +1,33 @@ +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/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/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/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= +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/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/v3/internal/templates/react-swc-ts/main.go.tmpl b/v3/internal/templates/react-swc-ts/main.go.tmpl new file mode 100644 index 000000000..96c4fd5f0 --- /dev/null +++ b/v3/internal/templates/react-swc-ts/main.go.tmpl @@ -0,0 +1,43 @@ +package main + +import ( + "embed" + _ "embed" + "log" + + "github.com/wailsapp/wails/v3/pkg/application" +) + +//go:embed frontend/dist +var assets embed.FS + +func main() { + app := application.New(application.Options{ + Name: "{{.ProjectName}}", + Description: "A demo of using raw HTML & CSS", + Assets: application.AssetOptions{ + FS: assets, + }, + Mac: application.MacOptions{ + ApplicationShouldTerminateAfterLastWindowClosed: true, + }, + }) + // Create window + app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{ + Title: "Plain Bundle", + CSS: `body { background-color: rgba(255, 255, 255, 0); } .main { color: white; margin: 20%; }`, + Mac: application.MacWindow{ + InvisibleTitleBarHeight: 50, + Backdrop: application.MacBackdropTranslucent, + TitleBar: application.MacTitleBarHiddenInset, + }, + + URL: "/", + }) + + err := app.Run() + + if err != nil { + log.Fatal(err) + } +} diff --git a/v3/internal/templates/react-swc-ts/template.json b/v3/internal/templates/react-swc-ts/template.json new file mode 100644 index 000000000..f708a5b5d --- /dev/null +++ b/v3/internal/templates/react-swc-ts/template.json @@ -0,0 +1,8 @@ +{ + "name": "React + SWC + Vite (Typescript)", + "shortname": "react-swc-ts", + "author": "Lea Anthony", + "description": "React + SWC + Vite development server", + "helpurl": "https://wails.io", + "version": 3 +} \ No newline at end of file diff --git a/v3/internal/templates/react-swc/Taskfile.tmpl.yml b/v3/internal/templates/react-swc/Taskfile.tmpl.yml new file mode 100644 index 000000000..f9646a735 --- /dev/null +++ b/v3/internal/templates/react-swc/Taskfile.tmpl.yml @@ -0,0 +1,125 @@ +version: '3' + +vars: + APP_NAME: "{{.ProjectName}}" + +tasks: + + pre-build: + summary: Pre-build hooks + + post-build: + summary: Post-build hooks + + install-frontend-deps: + summary: Install frontend dependencies + dir: frontend + sources: + - package.json + - package-lock.json + generates: + - node_modules/* + preconditions: + - sh: npm version + msg: "Looks like npm isn't installed. Npm is part of the Node installer: https://nodejs.org/en/download/" + cmds: + - npm install + + build-frontend: + summary: Build the frontend project + dir: frontend + deps: + - install-frontend-deps + cmds: + - npm run build + + build:darwin: + summary: Builds the application + platforms: + - darwin + cmds: + - task: pre-build + - task: build-frontend + - go build -gcflags=all="-N -l" -o bin/testapp + - task: post-build + env: + CGO_CFLAGS: "-mmacosx-version-min=10.13" + CGO_LDFLAGS: "-mmacosx-version-min=10.13" + MACOSX_DEPLOYMENT_TARGET: "10.13" + + build:windows: + summary: Builds the application for Windows + platforms: + - windows + cmds: + - task: pre-build + - task: build-frontend + - go build -gcflags=all="-N -l" -o bin/testapp.exe + - task: post-build + + build: + summary: Builds the application + cmds: + - task: build:darwin + - task: build:windows + + generate-icons: + summary: Generates Windows `.ico` and Mac `.icns` files from an image + dir: build + cmds: + # Generates both .ico and .icns files + - wails generate icons -input appicon.png + + build-app-prod-darwin: + summary: Creates a production build of the application + cmds: + - task: pre-build + - task: build-frontend + - GOOS=darwin GOARCH={{ "{{.ARCH}}" }} go build -tags production -ldflags="-w -s" -o build/bin/{{ "{{.APP_NAME}}" }} + - task: post-build + env: + CGO_CFLAGS: "-mmacosx-version-min=10.13" + CGO_LDFLAGS: "-mmacosx-version-min=10.13" + MACOSX_DEPLOYMENT_TARGET: "10.13" + vars: + ARCH: $GOARCH + + + create-app-bundle: + summary: Builds a `.app` bundle + cmds: + - mkdir -p {{ "{{.APP_NAME}}" }}.app/Contents/{MacOS,Resources} + - cp build/icons.icns {{ "{{.APP_NAME}}" }}.app/Contents/Resources + - cp build/bin/{{ "{{.APP_NAME}}" }} {{ "{{.APP_NAME}}" }}.app/Contents/MacOS + - cp build/Info.plist {{ "{{.APP_NAME}}" }}.app/Contents + + package-darwin-arm64: + summary: Packages a production build of the application into a `.app` bundle + platform: darwin + deps: + - task: build-app-prod-darwin + vars: + ARCH: arm64 + - generate-icons + cmds: + - task: create-app-bundle + + generate:syso: + dir: build + platform: windows + cmds: + - wails generate syso -arch {{ "{{.ARCH}}" }} -icon icon.ico -manifest wails.exe.manifest -info info.json -out ../wails.syso + vars: + ARCH: $GOARCH + + package:windows: + summary: Packages a production build of the application into a `.exe` bundle + platform: windows + deps: + - generate-icons + cmds: + - task: generate:syso + vars: + ARCH: amd64 + - go build -tags production -ldflags="-w -s -H windowsgui" -o bin/{{ "{{.APP_NAME}}" }}.exe + - powershell Remove-item wails.syso diff --git a/v3/internal/templates/react-swc/build/Info.dev.plist.tmpl b/v3/internal/templates/react-swc/build/Info.dev.plist.tmpl new file mode 100644 index 000000000..7efa134f4 --- /dev/null +++ b/v3/internal/templates/react-swc/build/Info.dev.plist.tmpl @@ -0,0 +1,32 @@ + + + + CFBundlePackageType + APPL + CFBundleName + My Product Name + CFBundleExecutable + {{.ProjectName}} + CFBundleIdentifier + com.wails.{{.ProjectName}} + CFBundleVersion + v1.0.0 + CFBundleGetInfoString + This is a comment + CFBundleShortVersionString + v1.0.0 + CFBundleIconFile + icons + LSMinimumSystemVersion + 10.13.0 + NSHighResolutionCapable + true + NSHumanReadableCopyright + (c) 2023 My Company Name + NSAppTransportSecurity + + NSAllowsLocalNetworking + + + + \ No newline at end of file diff --git a/v3/internal/templates/react-swc/build/Info.plist.tmpl b/v3/internal/templates/react-swc/build/Info.plist.tmpl new file mode 100644 index 000000000..6bfa8c316 --- /dev/null +++ b/v3/internal/templates/react-swc/build/Info.plist.tmpl @@ -0,0 +1,27 @@ + + + + CFBundlePackageType + APPL + CFBundleName + My Product Name + CFBundleExecutable + {{.ProjectName}} + CFBundleIdentifier + com.wails.{{.ProjectName}} + CFBundleVersion + v1.0.0 + CFBundleGetInfoString + This is a comment + CFBundleShortVersionString + v1.0.0 + CFBundleIconFile + icons + LSMinimumSystemVersion + 10.13.0 + NSHighResolutionCapable + true + NSHumanReadableCopyright + (c) 2023 My Company Name + + \ No newline at end of file diff --git a/v3/internal/templates/react-swc/build/appicon.png b/v3/internal/templates/react-swc/build/appicon.png new file mode 100644 index 000000000..63617fe4f Binary files /dev/null and b/v3/internal/templates/react-swc/build/appicon.png differ diff --git a/v3/internal/templates/react-swc/build/icons.icns b/v3/internal/templates/react-swc/build/icons.icns new file mode 100644 index 000000000..1b5bd4c86 Binary files /dev/null and b/v3/internal/templates/react-swc/build/icons.icns differ diff --git a/v3/internal/templates/react-swc/frontend/.gitignore b/v3/internal/templates/react-swc/frontend/.gitignore new file mode 100644 index 000000000..a547bf36d --- /dev/null +++ b/v3/internal/templates/react-swc/frontend/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/v3/internal/templates/react-swc/frontend/index.html b/v3/internal/templates/react-swc/frontend/index.html new file mode 100644 index 000000000..fe5530dd4 --- /dev/null +++ b/v3/internal/templates/react-swc/frontend/index.html @@ -0,0 +1,13 @@ + + + + + + + Wails + React + + +
+ + + diff --git a/v3/internal/templates/react-swc/frontend/package.json b/v3/internal/templates/react-swc/frontend/package.json new file mode 100644 index 000000000..6583b9c4b --- /dev/null +++ b/v3/internal/templates/react-swc/frontend/package.json @@ -0,0 +1,21 @@ +{ + "name": "frontend", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview" + }, + "dependencies": { + "react": "^18.2.0", + "react-dom": "^18.2.0" + }, + "devDependencies": { + "@types/react": "^18.0.26", + "@types/react-dom": "^18.0.9", + "@vitejs/plugin-react-swc": "^3.0.0", + "vite": "^4.0.0" + } +} \ No newline at end of file diff --git a/v3/internal/templates/react-swc/frontend/public/wails.png b/v3/internal/templates/react-swc/frontend/public/wails.png new file mode 100644 index 000000000..8bdf42483 Binary files /dev/null and b/v3/internal/templates/react-swc/frontend/public/wails.png differ diff --git a/v3/internal/templates/react-swc/frontend/src/App.css b/v3/internal/templates/react-swc/frontend/src/App.css new file mode 100644 index 000000000..2c5e2ef5c --- /dev/null +++ b/v3/internal/templates/react-swc/frontend/src/App.css @@ -0,0 +1,41 @@ +#root { + max-width: 1280px; + margin: 0 auto; + padding: 2rem; + text-align: center; +} + +.logo { + height: 6em; + padding: 1.5em; + will-change: filter; +} +.logo:hover { + filter: drop-shadow(0 0 2em #646cffaa); +} +.logo.react:hover { + filter: drop-shadow(0 0 2em #61dafbaa); +} + +@keyframes logo-spin { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } +} + +@media (prefers-reduced-motion: no-preference) { + a:nth-of-type(2) .logo { + animation: logo-spin infinite 20s linear; + } +} + +.card { + padding: 2em; +} + +.read-the-docs { + color: #888; +} diff --git a/v3/internal/templates/react-swc/frontend/src/App.jsx b/v3/internal/templates/react-swc/frontend/src/App.jsx new file mode 100644 index 000000000..ef0adc0d5 --- /dev/null +++ b/v3/internal/templates/react-swc/frontend/src/App.jsx @@ -0,0 +1,34 @@ +import { useState } from 'react' +import reactLogo from './assets/react.svg' +import './App.css' + +function App() { + const [count, setCount] = useState(0) + + return ( +
+ +

Vite + React

+
+ +

+ Edit src/App.jsx and save to test HMR +

+
+

+ Click on the Vite and React logos to learn more +

+
+ ) +} + +export default App diff --git a/v3/internal/templates/react-swc/frontend/src/assets/react.svg b/v3/internal/templates/react-swc/frontend/src/assets/react.svg new file mode 100644 index 000000000..6c87de9bb --- /dev/null +++ b/v3/internal/templates/react-swc/frontend/src/assets/react.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/v3/internal/templates/react-swc/frontend/src/index.css b/v3/internal/templates/react-swc/frontend/src/index.css new file mode 100644 index 000000000..917888c1d --- /dev/null +++ b/v3/internal/templates/react-swc/frontend/src/index.css @@ -0,0 +1,70 @@ +:root { + font-family: Inter, Avenir, Helvetica, Arial, sans-serif; + font-size: 16px; + line-height: 24px; + font-weight: 400; + + color-scheme: light dark; + color: rgba(255, 255, 255, 0.87); + background-color: #242424; + + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + -webkit-text-size-adjust: 100%; +} + +a { + font-weight: 500; + color: #646cff; + text-decoration: inherit; +} +a:hover { + color: #535bf2; +} + +body { + margin: 0; + display: flex; + place-items: center; + min-width: 320px; + min-height: 100vh; +} + +h1 { + font-size: 3.2em; + line-height: 1.1; +} + +button { + border-radius: 8px; + border: 1px solid transparent; + padding: 0.6em 1.2em; + font-size: 1em; + font-weight: 500; + font-family: inherit; + background-color: #1a1a1a; + cursor: pointer; + transition: border-color 0.25s; +} +button:hover { + border-color: #646cff; +} +button:focus, +button:focus-visible { + outline: 4px auto -webkit-focus-ring-color; +} + +@media (prefers-color-scheme: light) { + :root { + color: #213547; + background-color: #ffffff; + } + a:hover { + color: #747bff; + } + button { + background-color: #f9f9f9; + } +} diff --git a/v3/internal/templates/react-swc/frontend/src/main.jsx b/v3/internal/templates/react-swc/frontend/src/main.jsx new file mode 100644 index 000000000..5cc599199 --- /dev/null +++ b/v3/internal/templates/react-swc/frontend/src/main.jsx @@ -0,0 +1,10 @@ +import React from 'react' +import ReactDOM from 'react-dom/client' +import App from './App' +import './index.css' + +ReactDOM.createRoot(document.getElementById('root')).render( + + + , +) diff --git a/v3/internal/templates/react-swc/frontend/vite.config.js b/v3/internal/templates/react-swc/frontend/vite.config.js new file mode 100644 index 000000000..861b04b35 --- /dev/null +++ b/v3/internal/templates/react-swc/frontend/vite.config.js @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite' +import react from '@vitejs/plugin-react-swc' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [react()], +}) diff --git a/v3/internal/templates/react-swc/go.mod.tmpl b/v3/internal/templates/react-swc/go.mod.tmpl new file mode 100644 index 000000000..3dad9b6fa --- /dev/null +++ b/v3/internal/templates/react-swc/go.mod.tmpl @@ -0,0 +1,19 @@ +module changeme + +go 1.21 + +require github.com/wailsapp/wails/v3 v3.0.0-alpha.0 + +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 +) +{{if gt (len .LocalModulePath) 0}} +replace github.com/wailsapp/wails/v3 => {{.LocalModulePath}}v3 +{{end}} diff --git a/v3/internal/templates/react-swc/go.sum.tmpl b/v3/internal/templates/react-swc/go.sum.tmpl new file mode 100644 index 000000000..c06e0dbc6 --- /dev/null +++ b/v3/internal/templates/react-swc/go.sum.tmpl @@ -0,0 +1,33 @@ +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/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/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/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= +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/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/v3/internal/templates/react-swc/main.go.tmpl b/v3/internal/templates/react-swc/main.go.tmpl new file mode 100644 index 000000000..96c4fd5f0 --- /dev/null +++ b/v3/internal/templates/react-swc/main.go.tmpl @@ -0,0 +1,43 @@ +package main + +import ( + "embed" + _ "embed" + "log" + + "github.com/wailsapp/wails/v3/pkg/application" +) + +//go:embed frontend/dist +var assets embed.FS + +func main() { + app := application.New(application.Options{ + Name: "{{.ProjectName}}", + Description: "A demo of using raw HTML & CSS", + Assets: application.AssetOptions{ + FS: assets, + }, + Mac: application.MacOptions{ + ApplicationShouldTerminateAfterLastWindowClosed: true, + }, + }) + // Create window + app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{ + Title: "Plain Bundle", + CSS: `body { background-color: rgba(255, 255, 255, 0); } .main { color: white; margin: 20%; }`, + Mac: application.MacWindow{ + InvisibleTitleBarHeight: 50, + Backdrop: application.MacBackdropTranslucent, + TitleBar: application.MacTitleBarHiddenInset, + }, + + URL: "/", + }) + + err := app.Run() + + if err != nil { + log.Fatal(err) + } +} diff --git a/v3/internal/templates/react-swc/template.json b/v3/internal/templates/react-swc/template.json new file mode 100644 index 000000000..0cf5958e3 --- /dev/null +++ b/v3/internal/templates/react-swc/template.json @@ -0,0 +1,8 @@ +{ + "name": "React + SWC + Vite", + "shortname": "react-swc", + "author": "Lea Anthony", + "description": "React + SWC + Vite development server", + "helpurl": "https://wails.io", + "version": 3 +} \ No newline at end of file diff --git a/v3/internal/templates/react-ts/Taskfile.tmpl.yml b/v3/internal/templates/react-ts/Taskfile.tmpl.yml new file mode 100644 index 000000000..f9646a735 --- /dev/null +++ b/v3/internal/templates/react-ts/Taskfile.tmpl.yml @@ -0,0 +1,125 @@ +version: '3' + +vars: + APP_NAME: "{{.ProjectName}}" + +tasks: + + pre-build: + summary: Pre-build hooks + + post-build: + summary: Post-build hooks + + install-frontend-deps: + summary: Install frontend dependencies + dir: frontend + sources: + - package.json + - package-lock.json + generates: + - node_modules/* + preconditions: + - sh: npm version + msg: "Looks like npm isn't installed. Npm is part of the Node installer: https://nodejs.org/en/download/" + cmds: + - npm install + + build-frontend: + summary: Build the frontend project + dir: frontend + deps: + - install-frontend-deps + cmds: + - npm run build + + build:darwin: + summary: Builds the application + platforms: + - darwin + cmds: + - task: pre-build + - task: build-frontend + - go build -gcflags=all="-N -l" -o bin/testapp + - task: post-build + env: + CGO_CFLAGS: "-mmacosx-version-min=10.13" + CGO_LDFLAGS: "-mmacosx-version-min=10.13" + MACOSX_DEPLOYMENT_TARGET: "10.13" + + build:windows: + summary: Builds the application for Windows + platforms: + - windows + cmds: + - task: pre-build + - task: build-frontend + - go build -gcflags=all="-N -l" -o bin/testapp.exe + - task: post-build + + build: + summary: Builds the application + cmds: + - task: build:darwin + - task: build:windows + + generate-icons: + summary: Generates Windows `.ico` and Mac `.icns` files from an image + dir: build + cmds: + # Generates both .ico and .icns files + - wails generate icons -input appicon.png + + build-app-prod-darwin: + summary: Creates a production build of the application + cmds: + - task: pre-build + - task: build-frontend + - GOOS=darwin GOARCH={{ "{{.ARCH}}" }} go build -tags production -ldflags="-w -s" -o build/bin/{{ "{{.APP_NAME}}" }} + - task: post-build + env: + CGO_CFLAGS: "-mmacosx-version-min=10.13" + CGO_LDFLAGS: "-mmacosx-version-min=10.13" + MACOSX_DEPLOYMENT_TARGET: "10.13" + vars: + ARCH: $GOARCH + + + create-app-bundle: + summary: Builds a `.app` bundle + cmds: + - mkdir -p {{ "{{.APP_NAME}}" }}.app/Contents/{MacOS,Resources} + - cp build/icons.icns {{ "{{.APP_NAME}}" }}.app/Contents/Resources + - cp build/bin/{{ "{{.APP_NAME}}" }} {{ "{{.APP_NAME}}" }}.app/Contents/MacOS + - cp build/Info.plist {{ "{{.APP_NAME}}" }}.app/Contents + + package-darwin-arm64: + summary: Packages a production build of the application into a `.app` bundle + platform: darwin + deps: + - task: build-app-prod-darwin + vars: + ARCH: arm64 + - generate-icons + cmds: + - task: create-app-bundle + + generate:syso: + dir: build + platform: windows + cmds: + - wails generate syso -arch {{ "{{.ARCH}}" }} -icon icon.ico -manifest wails.exe.manifest -info info.json -out ../wails.syso + vars: + ARCH: $GOARCH + + package:windows: + summary: Packages a production build of the application into a `.exe` bundle + platform: windows + deps: + - generate-icons + cmds: + - task: generate:syso + vars: + ARCH: amd64 + - go build -tags production -ldflags="-w -s -H windowsgui" -o bin/{{ "{{.APP_NAME}}" }}.exe + - powershell Remove-item wails.syso diff --git a/v3/internal/templates/react-ts/build/Info.dev.plist.tmpl b/v3/internal/templates/react-ts/build/Info.dev.plist.tmpl new file mode 100644 index 000000000..7efa134f4 --- /dev/null +++ b/v3/internal/templates/react-ts/build/Info.dev.plist.tmpl @@ -0,0 +1,32 @@ + + + + CFBundlePackageType + APPL + CFBundleName + My Product Name + CFBundleExecutable + {{.ProjectName}} + CFBundleIdentifier + com.wails.{{.ProjectName}} + CFBundleVersion + v1.0.0 + CFBundleGetInfoString + This is a comment + CFBundleShortVersionString + v1.0.0 + CFBundleIconFile + icons + LSMinimumSystemVersion + 10.13.0 + NSHighResolutionCapable + true + NSHumanReadableCopyright + (c) 2023 My Company Name + NSAppTransportSecurity + + NSAllowsLocalNetworking + + + + \ No newline at end of file diff --git a/v3/internal/templates/react-ts/build/Info.plist.tmpl b/v3/internal/templates/react-ts/build/Info.plist.tmpl new file mode 100644 index 000000000..6bfa8c316 --- /dev/null +++ b/v3/internal/templates/react-ts/build/Info.plist.tmpl @@ -0,0 +1,27 @@ + + + + CFBundlePackageType + APPL + CFBundleName + My Product Name + CFBundleExecutable + {{.ProjectName}} + CFBundleIdentifier + com.wails.{{.ProjectName}} + CFBundleVersion + v1.0.0 + CFBundleGetInfoString + This is a comment + CFBundleShortVersionString + v1.0.0 + CFBundleIconFile + icons + LSMinimumSystemVersion + 10.13.0 + NSHighResolutionCapable + true + NSHumanReadableCopyright + (c) 2023 My Company Name + + \ No newline at end of file diff --git a/v3/internal/templates/react-ts/build/appicon.png b/v3/internal/templates/react-ts/build/appicon.png new file mode 100644 index 000000000..63617fe4f Binary files /dev/null and b/v3/internal/templates/react-ts/build/appicon.png differ diff --git a/v3/internal/templates/react-ts/build/icons.icns b/v3/internal/templates/react-ts/build/icons.icns new file mode 100644 index 000000000..1b5bd4c86 Binary files /dev/null and b/v3/internal/templates/react-ts/build/icons.icns differ diff --git a/v3/internal/templates/react-ts/frontend/.gitignore b/v3/internal/templates/react-ts/frontend/.gitignore new file mode 100644 index 000000000..a547bf36d --- /dev/null +++ b/v3/internal/templates/react-ts/frontend/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/v3/internal/templates/react-ts/frontend/index.html b/v3/internal/templates/react-ts/frontend/index.html new file mode 100644 index 000000000..28868c572 --- /dev/null +++ b/v3/internal/templates/react-ts/frontend/index.html @@ -0,0 +1,13 @@ + + + + + + + Wails + React + TS + + +
+ + + diff --git a/v3/internal/templates/react-ts/frontend/package.json b/v3/internal/templates/react-ts/frontend/package.json new file mode 100644 index 000000000..e846a02c1 --- /dev/null +++ b/v3/internal/templates/react-ts/frontend/package.json @@ -0,0 +1,22 @@ +{ + "name": "frontend", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc && vite build", + "preview": "vite preview" + }, + "dependencies": { + "react": "^18.2.0", + "react-dom": "^18.2.0" + }, + "devDependencies": { + "@types/react": "^18.0.26", + "@types/react-dom": "^18.0.9", + "@vitejs/plugin-react": "^3.0.0", + "typescript": "^4.9.3", + "vite": "^4.0.0" + } +} \ No newline at end of file diff --git a/v3/internal/templates/react-ts/frontend/public/wails.png b/v3/internal/templates/react-ts/frontend/public/wails.png new file mode 100644 index 000000000..8bdf42483 Binary files /dev/null and b/v3/internal/templates/react-ts/frontend/public/wails.png differ diff --git a/v3/internal/templates/react-ts/frontend/src/App.css b/v3/internal/templates/react-ts/frontend/src/App.css new file mode 100644 index 000000000..2c5e2ef5c --- /dev/null +++ b/v3/internal/templates/react-ts/frontend/src/App.css @@ -0,0 +1,41 @@ +#root { + max-width: 1280px; + margin: 0 auto; + padding: 2rem; + text-align: center; +} + +.logo { + height: 6em; + padding: 1.5em; + will-change: filter; +} +.logo:hover { + filter: drop-shadow(0 0 2em #646cffaa); +} +.logo.react:hover { + filter: drop-shadow(0 0 2em #61dafbaa); +} + +@keyframes logo-spin { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } +} + +@media (prefers-reduced-motion: no-preference) { + a:nth-of-type(2) .logo { + animation: logo-spin infinite 20s linear; + } +} + +.card { + padding: 2em; +} + +.read-the-docs { + color: #888; +} diff --git a/v3/internal/templates/react-ts/frontend/src/App.tsx b/v3/internal/templates/react-ts/frontend/src/App.tsx new file mode 100644 index 000000000..cd201360b --- /dev/null +++ b/v3/internal/templates/react-ts/frontend/src/App.tsx @@ -0,0 +1,34 @@ +import { useState } from 'react' +import reactLogo from './assets/react.svg' +import './App.css' + +function App() { + const [count, setCount] = useState(0) + + return ( +
+ +

Vite + React

+
+ +

+ Edit src/App.tsx and save to test HMR +

+
+

+ Click on the Vite and React logos to learn more +

+
+ ) +} + +export default App diff --git a/v3/internal/templates/react-ts/frontend/src/assets/react.svg b/v3/internal/templates/react-ts/frontend/src/assets/react.svg new file mode 100644 index 000000000..6c87de9bb --- /dev/null +++ b/v3/internal/templates/react-ts/frontend/src/assets/react.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/v3/internal/templates/react-ts/frontend/src/index.css b/v3/internal/templates/react-ts/frontend/src/index.css new file mode 100644 index 000000000..917888c1d --- /dev/null +++ b/v3/internal/templates/react-ts/frontend/src/index.css @@ -0,0 +1,70 @@ +:root { + font-family: Inter, Avenir, Helvetica, Arial, sans-serif; + font-size: 16px; + line-height: 24px; + font-weight: 400; + + color-scheme: light dark; + color: rgba(255, 255, 255, 0.87); + background-color: #242424; + + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + -webkit-text-size-adjust: 100%; +} + +a { + font-weight: 500; + color: #646cff; + text-decoration: inherit; +} +a:hover { + color: #535bf2; +} + +body { + margin: 0; + display: flex; + place-items: center; + min-width: 320px; + min-height: 100vh; +} + +h1 { + font-size: 3.2em; + line-height: 1.1; +} + +button { + border-radius: 8px; + border: 1px solid transparent; + padding: 0.6em 1.2em; + font-size: 1em; + font-weight: 500; + font-family: inherit; + background-color: #1a1a1a; + cursor: pointer; + transition: border-color 0.25s; +} +button:hover { + border-color: #646cff; +} +button:focus, +button:focus-visible { + outline: 4px auto -webkit-focus-ring-color; +} + +@media (prefers-color-scheme: light) { + :root { + color: #213547; + background-color: #ffffff; + } + a:hover { + color: #747bff; + } + button { + background-color: #f9f9f9; + } +} diff --git a/v3/internal/templates/react-ts/frontend/src/main.tsx b/v3/internal/templates/react-ts/frontend/src/main.tsx new file mode 100644 index 000000000..791f139e2 --- /dev/null +++ b/v3/internal/templates/react-ts/frontend/src/main.tsx @@ -0,0 +1,10 @@ +import React from 'react' +import ReactDOM from 'react-dom/client' +import App from './App' +import './index.css' + +ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render( + + + , +) diff --git a/v3/internal/templates/react-ts/frontend/src/vite-env.d.ts b/v3/internal/templates/react-ts/frontend/src/vite-env.d.ts new file mode 100644 index 000000000..11f02fe2a --- /dev/null +++ b/v3/internal/templates/react-ts/frontend/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/v3/internal/templates/react-ts/frontend/tsconfig.json b/v3/internal/templates/react-ts/frontend/tsconfig.json new file mode 100644 index 000000000..3d0a51a86 --- /dev/null +++ b/v3/internal/templates/react-ts/frontend/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "target": "ESNext", + "useDefineForClassFields": true, + "lib": ["DOM", "DOM.Iterable", "ESNext"], + "allowJs": false, + "skipLibCheck": true, + "esModuleInterop": false, + "allowSyntheticDefaultImports": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "module": "ESNext", + "moduleResolution": "Node", + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx" + }, + "include": ["src"], + "references": [{ "path": "./tsconfig.node.json" }] +} diff --git a/v3/internal/templates/react-ts/frontend/tsconfig.node.json b/v3/internal/templates/react-ts/frontend/tsconfig.node.json new file mode 100644 index 000000000..9d31e2aed --- /dev/null +++ b/v3/internal/templates/react-ts/frontend/tsconfig.node.json @@ -0,0 +1,9 @@ +{ + "compilerOptions": { + "composite": true, + "module": "ESNext", + "moduleResolution": "Node", + "allowSyntheticDefaultImports": true + }, + "include": ["vite.config.ts"] +} diff --git a/v3/internal/templates/react-ts/frontend/vite.config.ts b/v3/internal/templates/react-ts/frontend/vite.config.ts new file mode 100644 index 000000000..5a33944a9 --- /dev/null +++ b/v3/internal/templates/react-ts/frontend/vite.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite' +import react from '@vitejs/plugin-react' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [react()], +}) diff --git a/v3/internal/templates/react-ts/go.mod.tmpl b/v3/internal/templates/react-ts/go.mod.tmpl new file mode 100644 index 000000000..3dad9b6fa --- /dev/null +++ b/v3/internal/templates/react-ts/go.mod.tmpl @@ -0,0 +1,19 @@ +module changeme + +go 1.21 + +require github.com/wailsapp/wails/v3 v3.0.0-alpha.0 + +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 +) +{{if gt (len .LocalModulePath) 0}} +replace github.com/wailsapp/wails/v3 => {{.LocalModulePath}}v3 +{{end}} diff --git a/v3/internal/templates/react-ts/go.sum.tmpl b/v3/internal/templates/react-ts/go.sum.tmpl new file mode 100644 index 000000000..c06e0dbc6 --- /dev/null +++ b/v3/internal/templates/react-ts/go.sum.tmpl @@ -0,0 +1,33 @@ +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/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/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/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= +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/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/v3/internal/templates/react-ts/main.go.tmpl b/v3/internal/templates/react-ts/main.go.tmpl new file mode 100644 index 000000000..96c4fd5f0 --- /dev/null +++ b/v3/internal/templates/react-ts/main.go.tmpl @@ -0,0 +1,43 @@ +package main + +import ( + "embed" + _ "embed" + "log" + + "github.com/wailsapp/wails/v3/pkg/application" +) + +//go:embed frontend/dist +var assets embed.FS + +func main() { + app := application.New(application.Options{ + Name: "{{.ProjectName}}", + Description: "A demo of using raw HTML & CSS", + Assets: application.AssetOptions{ + FS: assets, + }, + Mac: application.MacOptions{ + ApplicationShouldTerminateAfterLastWindowClosed: true, + }, + }) + // Create window + app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{ + Title: "Plain Bundle", + CSS: `body { background-color: rgba(255, 255, 255, 0); } .main { color: white; margin: 20%; }`, + Mac: application.MacWindow{ + InvisibleTitleBarHeight: 50, + Backdrop: application.MacBackdropTranslucent, + TitleBar: application.MacTitleBarHiddenInset, + }, + + URL: "/", + }) + + err := app.Run() + + if err != nil { + log.Fatal(err) + } +} diff --git a/v3/internal/templates/react-ts/template.json b/v3/internal/templates/react-ts/template.json new file mode 100644 index 000000000..4208f72be --- /dev/null +++ b/v3/internal/templates/react-ts/template.json @@ -0,0 +1,8 @@ +{ + "name": "React + Vite (Typescript)", + "shortname": "react-ts", + "author": "Lea Anthony", + "description": "React + Vite development server", + "helpurl": "https://wails.io", + "version": 3 +} \ No newline at end of file diff --git a/v3/internal/templates/react/Taskfile.tmpl.yml b/v3/internal/templates/react/Taskfile.tmpl.yml new file mode 100644 index 000000000..f9646a735 --- /dev/null +++ b/v3/internal/templates/react/Taskfile.tmpl.yml @@ -0,0 +1,125 @@ +version: '3' + +vars: + APP_NAME: "{{.ProjectName}}" + +tasks: + + pre-build: + summary: Pre-build hooks + + post-build: + summary: Post-build hooks + + install-frontend-deps: + summary: Install frontend dependencies + dir: frontend + sources: + - package.json + - package-lock.json + generates: + - node_modules/* + preconditions: + - sh: npm version + msg: "Looks like npm isn't installed. Npm is part of the Node installer: https://nodejs.org/en/download/" + cmds: + - npm install + + build-frontend: + summary: Build the frontend project + dir: frontend + deps: + - install-frontend-deps + cmds: + - npm run build + + build:darwin: + summary: Builds the application + platforms: + - darwin + cmds: + - task: pre-build + - task: build-frontend + - go build -gcflags=all="-N -l" -o bin/testapp + - task: post-build + env: + CGO_CFLAGS: "-mmacosx-version-min=10.13" + CGO_LDFLAGS: "-mmacosx-version-min=10.13" + MACOSX_DEPLOYMENT_TARGET: "10.13" + + build:windows: + summary: Builds the application for Windows + platforms: + - windows + cmds: + - task: pre-build + - task: build-frontend + - go build -gcflags=all="-N -l" -o bin/testapp.exe + - task: post-build + + build: + summary: Builds the application + cmds: + - task: build:darwin + - task: build:windows + + generate-icons: + summary: Generates Windows `.ico` and Mac `.icns` files from an image + dir: build + cmds: + # Generates both .ico and .icns files + - wails generate icons -input appicon.png + + build-app-prod-darwin: + summary: Creates a production build of the application + cmds: + - task: pre-build + - task: build-frontend + - GOOS=darwin GOARCH={{ "{{.ARCH}}" }} go build -tags production -ldflags="-w -s" -o build/bin/{{ "{{.APP_NAME}}" }} + - task: post-build + env: + CGO_CFLAGS: "-mmacosx-version-min=10.13" + CGO_LDFLAGS: "-mmacosx-version-min=10.13" + MACOSX_DEPLOYMENT_TARGET: "10.13" + vars: + ARCH: $GOARCH + + + create-app-bundle: + summary: Builds a `.app` bundle + cmds: + - mkdir -p {{ "{{.APP_NAME}}" }}.app/Contents/{MacOS,Resources} + - cp build/icons.icns {{ "{{.APP_NAME}}" }}.app/Contents/Resources + - cp build/bin/{{ "{{.APP_NAME}}" }} {{ "{{.APP_NAME}}" }}.app/Contents/MacOS + - cp build/Info.plist {{ "{{.APP_NAME}}" }}.app/Contents + + package-darwin-arm64: + summary: Packages a production build of the application into a `.app` bundle + platform: darwin + deps: + - task: build-app-prod-darwin + vars: + ARCH: arm64 + - generate-icons + cmds: + - task: create-app-bundle + + generate:syso: + dir: build + platform: windows + cmds: + - wails generate syso -arch {{ "{{.ARCH}}" }} -icon icon.ico -manifest wails.exe.manifest -info info.json -out ../wails.syso + vars: + ARCH: $GOARCH + + package:windows: + summary: Packages a production build of the application into a `.exe` bundle + platform: windows + deps: + - generate-icons + cmds: + - task: generate:syso + vars: + ARCH: amd64 + - go build -tags production -ldflags="-w -s -H windowsgui" -o bin/{{ "{{.APP_NAME}}" }}.exe + - powershell Remove-item wails.syso diff --git a/v3/internal/templates/react/build/Info.dev.plist.tmpl b/v3/internal/templates/react/build/Info.dev.plist.tmpl new file mode 100644 index 000000000..7efa134f4 --- /dev/null +++ b/v3/internal/templates/react/build/Info.dev.plist.tmpl @@ -0,0 +1,32 @@ + + + + CFBundlePackageType + APPL + CFBundleName + My Product Name + CFBundleExecutable + {{.ProjectName}} + CFBundleIdentifier + com.wails.{{.ProjectName}} + CFBundleVersion + v1.0.0 + CFBundleGetInfoString + This is a comment + CFBundleShortVersionString + v1.0.0 + CFBundleIconFile + icons + LSMinimumSystemVersion + 10.13.0 + NSHighResolutionCapable + true + NSHumanReadableCopyright + (c) 2023 My Company Name + NSAppTransportSecurity + + NSAllowsLocalNetworking + + + + \ No newline at end of file diff --git a/v3/internal/templates/react/build/Info.plist.tmpl b/v3/internal/templates/react/build/Info.plist.tmpl new file mode 100644 index 000000000..6bfa8c316 --- /dev/null +++ b/v3/internal/templates/react/build/Info.plist.tmpl @@ -0,0 +1,27 @@ + + + + CFBundlePackageType + APPL + CFBundleName + My Product Name + CFBundleExecutable + {{.ProjectName}} + CFBundleIdentifier + com.wails.{{.ProjectName}} + CFBundleVersion + v1.0.0 + CFBundleGetInfoString + This is a comment + CFBundleShortVersionString + v1.0.0 + CFBundleIconFile + icons + LSMinimumSystemVersion + 10.13.0 + NSHighResolutionCapable + true + NSHumanReadableCopyright + (c) 2023 My Company Name + + \ No newline at end of file diff --git a/v3/internal/templates/react/build/appicon.png b/v3/internal/templates/react/build/appicon.png new file mode 100644 index 000000000..63617fe4f Binary files /dev/null and b/v3/internal/templates/react/build/appicon.png differ diff --git a/v3/internal/templates/react/build/icons.icns b/v3/internal/templates/react/build/icons.icns new file mode 100644 index 000000000..1b5bd4c86 Binary files /dev/null and b/v3/internal/templates/react/build/icons.icns differ diff --git a/v3/internal/templates/react/frontend/.gitignore b/v3/internal/templates/react/frontend/.gitignore new file mode 100644 index 000000000..a547bf36d --- /dev/null +++ b/v3/internal/templates/react/frontend/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/v3/internal/templates/react/frontend/index.html b/v3/internal/templates/react/frontend/index.html new file mode 100644 index 000000000..fe5530dd4 --- /dev/null +++ b/v3/internal/templates/react/frontend/index.html @@ -0,0 +1,13 @@ + + + + + + + Wails + React + + +
+ + + diff --git a/v3/internal/templates/react/frontend/package.json b/v3/internal/templates/react/frontend/package.json new file mode 100644 index 000000000..2371d17c8 --- /dev/null +++ b/v3/internal/templates/react/frontend/package.json @@ -0,0 +1,21 @@ +{ + "name": "frontend", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview" + }, + "dependencies": { + "react": "^18.2.0", + "react-dom": "^18.2.0" + }, + "devDependencies": { + "@types/react": "^18.0.26", + "@types/react-dom": "^18.0.9", + "@vitejs/plugin-react": "^3.0.0", + "vite": "^4.0.0" + } +} \ No newline at end of file diff --git a/v3/internal/templates/react/frontend/public/wails.png b/v3/internal/templates/react/frontend/public/wails.png new file mode 100644 index 000000000..8bdf42483 Binary files /dev/null and b/v3/internal/templates/react/frontend/public/wails.png differ diff --git a/v3/internal/templates/react/frontend/src/App.css b/v3/internal/templates/react/frontend/src/App.css new file mode 100644 index 000000000..2c5e2ef5c --- /dev/null +++ b/v3/internal/templates/react/frontend/src/App.css @@ -0,0 +1,41 @@ +#root { + max-width: 1280px; + margin: 0 auto; + padding: 2rem; + text-align: center; +} + +.logo { + height: 6em; + padding: 1.5em; + will-change: filter; +} +.logo:hover { + filter: drop-shadow(0 0 2em #646cffaa); +} +.logo.react:hover { + filter: drop-shadow(0 0 2em #61dafbaa); +} + +@keyframes logo-spin { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } +} + +@media (prefers-reduced-motion: no-preference) { + a:nth-of-type(2) .logo { + animation: logo-spin infinite 20s linear; + } +} + +.card { + padding: 2em; +} + +.read-the-docs { + color: #888; +} diff --git a/v3/internal/templates/react/frontend/src/App.jsx b/v3/internal/templates/react/frontend/src/App.jsx new file mode 100644 index 000000000..ef0adc0d5 --- /dev/null +++ b/v3/internal/templates/react/frontend/src/App.jsx @@ -0,0 +1,34 @@ +import { useState } from 'react' +import reactLogo from './assets/react.svg' +import './App.css' + +function App() { + const [count, setCount] = useState(0) + + return ( +
+ +

Vite + React

+
+ +

+ Edit src/App.jsx and save to test HMR +

+
+

+ Click on the Vite and React logos to learn more +

+
+ ) +} + +export default App diff --git a/v3/internal/templates/react/frontend/src/assets/react.svg b/v3/internal/templates/react/frontend/src/assets/react.svg new file mode 100644 index 000000000..6c87de9bb --- /dev/null +++ b/v3/internal/templates/react/frontend/src/assets/react.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/v3/internal/templates/react/frontend/src/index.css b/v3/internal/templates/react/frontend/src/index.css new file mode 100644 index 000000000..917888c1d --- /dev/null +++ b/v3/internal/templates/react/frontend/src/index.css @@ -0,0 +1,70 @@ +:root { + font-family: Inter, Avenir, Helvetica, Arial, sans-serif; + font-size: 16px; + line-height: 24px; + font-weight: 400; + + color-scheme: light dark; + color: rgba(255, 255, 255, 0.87); + background-color: #242424; + + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + -webkit-text-size-adjust: 100%; +} + +a { + font-weight: 500; + color: #646cff; + text-decoration: inherit; +} +a:hover { + color: #535bf2; +} + +body { + margin: 0; + display: flex; + place-items: center; + min-width: 320px; + min-height: 100vh; +} + +h1 { + font-size: 3.2em; + line-height: 1.1; +} + +button { + border-radius: 8px; + border: 1px solid transparent; + padding: 0.6em 1.2em; + font-size: 1em; + font-weight: 500; + font-family: inherit; + background-color: #1a1a1a; + cursor: pointer; + transition: border-color 0.25s; +} +button:hover { + border-color: #646cff; +} +button:focus, +button:focus-visible { + outline: 4px auto -webkit-focus-ring-color; +} + +@media (prefers-color-scheme: light) { + :root { + color: #213547; + background-color: #ffffff; + } + a:hover { + color: #747bff; + } + button { + background-color: #f9f9f9; + } +} diff --git a/v3/internal/templates/react/frontend/src/main.jsx b/v3/internal/templates/react/frontend/src/main.jsx new file mode 100644 index 000000000..5cc599199 --- /dev/null +++ b/v3/internal/templates/react/frontend/src/main.jsx @@ -0,0 +1,10 @@ +import React from 'react' +import ReactDOM from 'react-dom/client' +import App from './App' +import './index.css' + +ReactDOM.createRoot(document.getElementById('root')).render( + + + , +) diff --git a/v3/internal/templates/react/frontend/vite.config.js b/v3/internal/templates/react/frontend/vite.config.js new file mode 100644 index 000000000..5a33944a9 --- /dev/null +++ b/v3/internal/templates/react/frontend/vite.config.js @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite' +import react from '@vitejs/plugin-react' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [react()], +}) diff --git a/v3/internal/templates/react/go.mod.tmpl b/v3/internal/templates/react/go.mod.tmpl new file mode 100644 index 000000000..3dad9b6fa --- /dev/null +++ b/v3/internal/templates/react/go.mod.tmpl @@ -0,0 +1,19 @@ +module changeme + +go 1.21 + +require github.com/wailsapp/wails/v3 v3.0.0-alpha.0 + +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 +) +{{if gt (len .LocalModulePath) 0}} +replace github.com/wailsapp/wails/v3 => {{.LocalModulePath}}v3 +{{end}} diff --git a/v3/internal/templates/react/go.sum.tmpl b/v3/internal/templates/react/go.sum.tmpl new file mode 100644 index 000000000..c06e0dbc6 --- /dev/null +++ b/v3/internal/templates/react/go.sum.tmpl @@ -0,0 +1,33 @@ +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/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/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/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= +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/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/v3/internal/templates/react/main.go.tmpl b/v3/internal/templates/react/main.go.tmpl new file mode 100644 index 000000000..96c4fd5f0 --- /dev/null +++ b/v3/internal/templates/react/main.go.tmpl @@ -0,0 +1,43 @@ +package main + +import ( + "embed" + _ "embed" + "log" + + "github.com/wailsapp/wails/v3/pkg/application" +) + +//go:embed frontend/dist +var assets embed.FS + +func main() { + app := application.New(application.Options{ + Name: "{{.ProjectName}}", + Description: "A demo of using raw HTML & CSS", + Assets: application.AssetOptions{ + FS: assets, + }, + Mac: application.MacOptions{ + ApplicationShouldTerminateAfterLastWindowClosed: true, + }, + }) + // Create window + app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{ + Title: "Plain Bundle", + CSS: `body { background-color: rgba(255, 255, 255, 0); } .main { color: white; margin: 20%; }`, + Mac: application.MacWindow{ + InvisibleTitleBarHeight: 50, + Backdrop: application.MacBackdropTranslucent, + TitleBar: application.MacTitleBarHiddenInset, + }, + + URL: "/", + }) + + err := app.Run() + + if err != nil { + log.Fatal(err) + } +} diff --git a/v3/internal/templates/react/template.json b/v3/internal/templates/react/template.json new file mode 100644 index 000000000..c84df2629 --- /dev/null +++ b/v3/internal/templates/react/template.json @@ -0,0 +1,8 @@ +{ + "name": "React + Vite", + "shortname": "react", + "author": "Lea Anthony", + "description": "React + Vite development server", + "helpurl": "https://wails.io", + "version": 3 +} \ No newline at end of file diff --git a/v3/internal/templates/svelte-ts/Taskfile.tmpl.yml b/v3/internal/templates/svelte-ts/Taskfile.tmpl.yml new file mode 100644 index 000000000..f9646a735 --- /dev/null +++ b/v3/internal/templates/svelte-ts/Taskfile.tmpl.yml @@ -0,0 +1,125 @@ +version: '3' + +vars: + APP_NAME: "{{.ProjectName}}" + +tasks: + + pre-build: + summary: Pre-build hooks + + post-build: + summary: Post-build hooks + + install-frontend-deps: + summary: Install frontend dependencies + dir: frontend + sources: + - package.json + - package-lock.json + generates: + - node_modules/* + preconditions: + - sh: npm version + msg: "Looks like npm isn't installed. Npm is part of the Node installer: https://nodejs.org/en/download/" + cmds: + - npm install + + build-frontend: + summary: Build the frontend project + dir: frontend + deps: + - install-frontend-deps + cmds: + - npm run build + + build:darwin: + summary: Builds the application + platforms: + - darwin + cmds: + - task: pre-build + - task: build-frontend + - go build -gcflags=all="-N -l" -o bin/testapp + - task: post-build + env: + CGO_CFLAGS: "-mmacosx-version-min=10.13" + CGO_LDFLAGS: "-mmacosx-version-min=10.13" + MACOSX_DEPLOYMENT_TARGET: "10.13" + + build:windows: + summary: Builds the application for Windows + platforms: + - windows + cmds: + - task: pre-build + - task: build-frontend + - go build -gcflags=all="-N -l" -o bin/testapp.exe + - task: post-build + + build: + summary: Builds the application + cmds: + - task: build:darwin + - task: build:windows + + generate-icons: + summary: Generates Windows `.ico` and Mac `.icns` files from an image + dir: build + cmds: + # Generates both .ico and .icns files + - wails generate icons -input appicon.png + + build-app-prod-darwin: + summary: Creates a production build of the application + cmds: + - task: pre-build + - task: build-frontend + - GOOS=darwin GOARCH={{ "{{.ARCH}}" }} go build -tags production -ldflags="-w -s" -o build/bin/{{ "{{.APP_NAME}}" }} + - task: post-build + env: + CGO_CFLAGS: "-mmacosx-version-min=10.13" + CGO_LDFLAGS: "-mmacosx-version-min=10.13" + MACOSX_DEPLOYMENT_TARGET: "10.13" + vars: + ARCH: $GOARCH + + + create-app-bundle: + summary: Builds a `.app` bundle + cmds: + - mkdir -p {{ "{{.APP_NAME}}" }}.app/Contents/{MacOS,Resources} + - cp build/icons.icns {{ "{{.APP_NAME}}" }}.app/Contents/Resources + - cp build/bin/{{ "{{.APP_NAME}}" }} {{ "{{.APP_NAME}}" }}.app/Contents/MacOS + - cp build/Info.plist {{ "{{.APP_NAME}}" }}.app/Contents + + package-darwin-arm64: + summary: Packages a production build of the application into a `.app` bundle + platform: darwin + deps: + - task: build-app-prod-darwin + vars: + ARCH: arm64 + - generate-icons + cmds: + - task: create-app-bundle + + generate:syso: + dir: build + platform: windows + cmds: + - wails generate syso -arch {{ "{{.ARCH}}" }} -icon icon.ico -manifest wails.exe.manifest -info info.json -out ../wails.syso + vars: + ARCH: $GOARCH + + package:windows: + summary: Packages a production build of the application into a `.exe` bundle + platform: windows + deps: + - generate-icons + cmds: + - task: generate:syso + vars: + ARCH: amd64 + - go build -tags production -ldflags="-w -s -H windowsgui" -o bin/{{ "{{.APP_NAME}}" }}.exe + - powershell Remove-item wails.syso diff --git a/v3/internal/templates/svelte-ts/build/Info.dev.plist.tmpl b/v3/internal/templates/svelte-ts/build/Info.dev.plist.tmpl new file mode 100644 index 000000000..7efa134f4 --- /dev/null +++ b/v3/internal/templates/svelte-ts/build/Info.dev.plist.tmpl @@ -0,0 +1,32 @@ + + + + CFBundlePackageType + APPL + CFBundleName + My Product Name + CFBundleExecutable + {{.ProjectName}} + CFBundleIdentifier + com.wails.{{.ProjectName}} + CFBundleVersion + v1.0.0 + CFBundleGetInfoString + This is a comment + CFBundleShortVersionString + v1.0.0 + CFBundleIconFile + icons + LSMinimumSystemVersion + 10.13.0 + NSHighResolutionCapable + true + NSHumanReadableCopyright + (c) 2023 My Company Name + NSAppTransportSecurity + + NSAllowsLocalNetworking + + + + \ No newline at end of file diff --git a/v3/internal/templates/svelte-ts/build/Info.plist.tmpl b/v3/internal/templates/svelte-ts/build/Info.plist.tmpl new file mode 100644 index 000000000..6bfa8c316 --- /dev/null +++ b/v3/internal/templates/svelte-ts/build/Info.plist.tmpl @@ -0,0 +1,27 @@ + + + + CFBundlePackageType + APPL + CFBundleName + My Product Name + CFBundleExecutable + {{.ProjectName}} + CFBundleIdentifier + com.wails.{{.ProjectName}} + CFBundleVersion + v1.0.0 + CFBundleGetInfoString + This is a comment + CFBundleShortVersionString + v1.0.0 + CFBundleIconFile + icons + LSMinimumSystemVersion + 10.13.0 + NSHighResolutionCapable + true + NSHumanReadableCopyright + (c) 2023 My Company Name + + \ No newline at end of file diff --git a/v3/internal/templates/svelte-ts/build/appicon.png b/v3/internal/templates/svelte-ts/build/appicon.png new file mode 100644 index 000000000..63617fe4f Binary files /dev/null and b/v3/internal/templates/svelte-ts/build/appicon.png differ diff --git a/v3/internal/templates/svelte-ts/build/icons.icns b/v3/internal/templates/svelte-ts/build/icons.icns new file mode 100644 index 000000000..1b5bd4c86 Binary files /dev/null and b/v3/internal/templates/svelte-ts/build/icons.icns differ diff --git a/v3/internal/templates/svelte-ts/frontend/.gitignore b/v3/internal/templates/svelte-ts/frontend/.gitignore new file mode 100644 index 000000000..a547bf36d --- /dev/null +++ b/v3/internal/templates/svelte-ts/frontend/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/v3/internal/templates/svelte-ts/frontend/.vscode/extensions.json b/v3/internal/templates/svelte-ts/frontend/.vscode/extensions.json new file mode 100644 index 000000000..bdef82015 --- /dev/null +++ b/v3/internal/templates/svelte-ts/frontend/.vscode/extensions.json @@ -0,0 +1,3 @@ +{ + "recommendations": ["svelte.svelte-vscode"] +} diff --git a/v3/internal/templates/svelte-ts/frontend/README.md b/v3/internal/templates/svelte-ts/frontend/README.md new file mode 100644 index 000000000..e6cd94fce --- /dev/null +++ b/v3/internal/templates/svelte-ts/frontend/README.md @@ -0,0 +1,47 @@ +# Svelte + TS + Vite + +This template should help get you started developing with Svelte and TypeScript in Vite. + +## Recommended IDE Setup + +[VS Code](https://code.visualstudio.com/) + [Svelte](https://marketplace.visualstudio.com/items?itemName=svelte.svelte-vscode). + +## Need an official Svelte framework? + +Check out [SvelteKit](https://github.com/sveltejs/kit#readme), which is also powered by Vite. Deploy anywhere with its serverless-first approach and adapt to various platforms, with out of the box support for TypeScript, SCSS, and Less, and easily-added support for mdsvex, GraphQL, PostCSS, Tailwind CSS, and more. + +## Technical considerations + +**Why use this over SvelteKit?** + +- It brings its own routing solution which might not be preferable for some users. +- It is first and foremost a framework that just happens to use Vite under the hood, not a Vite app. + +This template contains as little as possible to get started with Vite + TypeScript + Svelte, while taking into account the developer experience with regards to HMR and intellisense. It demonstrates capabilities on par with the other `create-vite` templates and is a good starting point for beginners dipping their toes into a Vite + Svelte project. + +Should you later need the extended capabilities and extensibility provided by SvelteKit, the template has been structured similarly to SvelteKit so that it is easy to migrate. + +**Why `global.d.ts` instead of `compilerOptions.types` inside `jsconfig.json` or `tsconfig.json`?** + +Setting `compilerOptions.types` shuts out all other types not explicitly listed in the configuration. Using triple-slash references keeps the default TypeScript setting of accepting type information from the entire workspace, while also adding `svelte` and `vite/client` type information. + +**Why include `.vscode/extensions.json`?** + +Other templates indirectly recommend extensions via the README, but this file allows VS Code to prompt the user to install the recommended extension upon opening the project. + +**Why enable `allowJs` in the TS template?** + +While `allowJs: false` would indeed prevent the use of `.js` files in the project, it does not prevent the use of JavaScript syntax in `.svelte` files. In addition, it would force `checkJs: false`, bringing the worst of both worlds: not being able to guarantee the entire codebase is TypeScript, and also having worse typechecking for the existing JavaScript. In addition, there are valid use cases in which a mixed codebase may be relevant. + +**Why is HMR not preserving my local component state?** + +HMR state preservation comes with a number of gotchas! It has been disabled by default in both `svelte-hmr` and `@sveltejs/vite-plugin-svelte` due to its often surprising behavior. You can read the details [here](https://github.com/rixo/svelte-hmr#svelte-hmr). + +If you have state that's important to retain within a component, consider creating an external store which would not be replaced by HMR. + +```ts +// store.ts +// An extremely simple external store +import { writable } from 'svelte/store' +export default writable(0) +``` diff --git a/v3/internal/templates/svelte-ts/frontend/index.html b/v3/internal/templates/svelte-ts/frontend/index.html new file mode 100644 index 000000000..a7eaac78d --- /dev/null +++ b/v3/internal/templates/svelte-ts/frontend/index.html @@ -0,0 +1,13 @@ + + + + + + + Wails + Svelte + TS + + +
+ + + diff --git a/v3/internal/templates/svelte-ts/frontend/package.json b/v3/internal/templates/svelte-ts/frontend/package.json new file mode 100644 index 000000000..222683e0d --- /dev/null +++ b/v3/internal/templates/svelte-ts/frontend/package.json @@ -0,0 +1,21 @@ +{ + "name": "frontend", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview", + "check": "svelte-check --tsconfig ./tsconfig.json" + }, + "devDependencies": { + "@sveltejs/vite-plugin-svelte": "^2.0.0", + "@tsconfig/svelte": "^3.0.0", + "svelte": "^3.54.0", + "svelte-check": "^2.10.0", + "tslib": "^2.4.1", + "typescript": "^4.9.3", + "vite": "^4.0.0" + } +} \ No newline at end of file diff --git a/v3/internal/templates/svelte-ts/frontend/public/wails.png b/v3/internal/templates/svelte-ts/frontend/public/wails.png new file mode 100644 index 000000000..8bdf42483 Binary files /dev/null and b/v3/internal/templates/svelte-ts/frontend/public/wails.png differ diff --git a/v3/internal/templates/svelte-ts/frontend/src/App.svelte b/v3/internal/templates/svelte-ts/frontend/src/App.svelte new file mode 100644 index 000000000..1e9dc0394 --- /dev/null +++ b/v3/internal/templates/svelte-ts/frontend/src/App.svelte @@ -0,0 +1,45 @@ + + +
+ +

Vite + Svelte

+ +
+ +
+ +

+ Check out SvelteKit, the official Svelte app framework powered by Vite! +

+ +

+ Click on the Vite and Svelte logos to learn more +

+
+ + \ No newline at end of file diff --git a/v3/internal/templates/svelte-ts/frontend/src/app.css b/v3/internal/templates/svelte-ts/frontend/src/app.css new file mode 100644 index 000000000..bcc7233dd --- /dev/null +++ b/v3/internal/templates/svelte-ts/frontend/src/app.css @@ -0,0 +1,81 @@ +:root { + font-family: Inter, Avenir, Helvetica, Arial, sans-serif; + font-size: 16px; + line-height: 24px; + font-weight: 400; + + color-scheme: light dark; + color: rgba(255, 255, 255, 0.87); + background-color: #242424; + + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + -webkit-text-size-adjust: 100%; +} + +a { + font-weight: 500; + color: #646cff; + text-decoration: inherit; +} +a:hover { + color: #535bf2; +} + +body { + margin: 0; + display: flex; + place-items: center; + min-width: 320px; + min-height: 100vh; +} + +h1 { + font-size: 3.2em; + line-height: 1.1; +} + +.card { + padding: 2em; +} + +#app { + max-width: 1280px; + margin: 0 auto; + padding: 2rem; + text-align: center; +} + +button { + border-radius: 8px; + border: 1px solid transparent; + padding: 0.6em 1.2em; + font-size: 1em; + font-weight: 500; + font-family: inherit; + background-color: #1a1a1a; + cursor: pointer; + transition: border-color 0.25s; +} +button:hover { + border-color: #646cff; +} +button:focus, +button:focus-visible { + outline: 4px auto -webkit-focus-ring-color; +} + +@media (prefers-color-scheme: light) { + :root { + color: #213547; + background-color: #ffffff; + } + a:hover { + color: #747bff; + } + button { + background-color: #f9f9f9; + } +} diff --git a/v3/internal/templates/svelte-ts/frontend/src/assets/svelte.svg b/v3/internal/templates/svelte-ts/frontend/src/assets/svelte.svg new file mode 100644 index 000000000..c5e08481f --- /dev/null +++ b/v3/internal/templates/svelte-ts/frontend/src/assets/svelte.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/v3/internal/templates/svelte-ts/frontend/src/lib/Counter.svelte b/v3/internal/templates/svelte-ts/frontend/src/lib/Counter.svelte new file mode 100644 index 000000000..979b4dfc9 --- /dev/null +++ b/v3/internal/templates/svelte-ts/frontend/src/lib/Counter.svelte @@ -0,0 +1,10 @@ + + + diff --git a/v3/internal/templates/svelte-ts/frontend/src/main.ts b/v3/internal/templates/svelte-ts/frontend/src/main.ts new file mode 100644 index 000000000..8a909a15a --- /dev/null +++ b/v3/internal/templates/svelte-ts/frontend/src/main.ts @@ -0,0 +1,8 @@ +import './app.css' +import App from './App.svelte' + +const app = new App({ + target: document.getElementById('app'), +}) + +export default app diff --git a/v3/internal/templates/svelte-ts/frontend/src/vite-env.d.ts b/v3/internal/templates/svelte-ts/frontend/src/vite-env.d.ts new file mode 100644 index 000000000..4078e7476 --- /dev/null +++ b/v3/internal/templates/svelte-ts/frontend/src/vite-env.d.ts @@ -0,0 +1,2 @@ +/// +/// diff --git a/v3/internal/templates/svelte-ts/frontend/svelte.config.js b/v3/internal/templates/svelte-ts/frontend/svelte.config.js new file mode 100644 index 000000000..b0683fd24 --- /dev/null +++ b/v3/internal/templates/svelte-ts/frontend/svelte.config.js @@ -0,0 +1,7 @@ +import { vitePreprocess } from '@sveltejs/vite-plugin-svelte' + +export default { + // Consult https://svelte.dev/docs#compile-time-svelte-preprocess + // for more information about preprocessors + preprocess: vitePreprocess(), +} diff --git a/v3/internal/templates/svelte-ts/frontend/tsconfig.json b/v3/internal/templates/svelte-ts/frontend/tsconfig.json new file mode 100644 index 000000000..c4e1c5fe6 --- /dev/null +++ b/v3/internal/templates/svelte-ts/frontend/tsconfig.json @@ -0,0 +1,20 @@ +{ + "extends": "@tsconfig/svelte/tsconfig.json", + "compilerOptions": { + "target": "ESNext", + "useDefineForClassFields": true, + "module": "ESNext", + "resolveJsonModule": true, + /** + * Typecheck JS in `.svelte` and `.js` files by default. + * Disable checkJs if you'd like to use dynamic types in JS. + * Note that setting allowJs false does not prevent the use + * of JS in `.svelte` files. + */ + "allowJs": true, + "checkJs": true, + "isolatedModules": true + }, + "include": ["src/**/*.d.ts", "src/**/*.ts", "src/**/*.js", "src/**/*.svelte"], + "references": [{ "path": "./tsconfig.node.json" }] +} diff --git a/v3/internal/templates/svelte-ts/frontend/tsconfig.node.json b/v3/internal/templates/svelte-ts/frontend/tsconfig.node.json new file mode 100644 index 000000000..65dbdb96a --- /dev/null +++ b/v3/internal/templates/svelte-ts/frontend/tsconfig.node.json @@ -0,0 +1,8 @@ +{ + "compilerOptions": { + "composite": true, + "module": "ESNext", + "moduleResolution": "Node" + }, + "include": ["vite.config.ts"] +} diff --git a/v3/internal/templates/svelte-ts/frontend/vite.config.ts b/v3/internal/templates/svelte-ts/frontend/vite.config.ts new file mode 100644 index 000000000..d70196943 --- /dev/null +++ b/v3/internal/templates/svelte-ts/frontend/vite.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite' +import { svelte } from '@sveltejs/vite-plugin-svelte' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [svelte()], +}) diff --git a/v3/internal/templates/svelte-ts/go.mod.tmpl b/v3/internal/templates/svelte-ts/go.mod.tmpl new file mode 100644 index 000000000..3dad9b6fa --- /dev/null +++ b/v3/internal/templates/svelte-ts/go.mod.tmpl @@ -0,0 +1,19 @@ +module changeme + +go 1.21 + +require github.com/wailsapp/wails/v3 v3.0.0-alpha.0 + +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 +) +{{if gt (len .LocalModulePath) 0}} +replace github.com/wailsapp/wails/v3 => {{.LocalModulePath}}v3 +{{end}} diff --git a/v3/internal/templates/svelte-ts/go.sum.tmpl b/v3/internal/templates/svelte-ts/go.sum.tmpl new file mode 100644 index 000000000..c06e0dbc6 --- /dev/null +++ b/v3/internal/templates/svelte-ts/go.sum.tmpl @@ -0,0 +1,33 @@ +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/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/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/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= +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/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/v3/internal/templates/svelte-ts/main.go.tmpl b/v3/internal/templates/svelte-ts/main.go.tmpl new file mode 100644 index 000000000..96c4fd5f0 --- /dev/null +++ b/v3/internal/templates/svelte-ts/main.go.tmpl @@ -0,0 +1,43 @@ +package main + +import ( + "embed" + _ "embed" + "log" + + "github.com/wailsapp/wails/v3/pkg/application" +) + +//go:embed frontend/dist +var assets embed.FS + +func main() { + app := application.New(application.Options{ + Name: "{{.ProjectName}}", + Description: "A demo of using raw HTML & CSS", + Assets: application.AssetOptions{ + FS: assets, + }, + Mac: application.MacOptions{ + ApplicationShouldTerminateAfterLastWindowClosed: true, + }, + }) + // Create window + app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{ + Title: "Plain Bundle", + CSS: `body { background-color: rgba(255, 255, 255, 0); } .main { color: white; margin: 20%; }`, + Mac: application.MacWindow{ + InvisibleTitleBarHeight: 50, + Backdrop: application.MacBackdropTranslucent, + TitleBar: application.MacTitleBarHiddenInset, + }, + + URL: "/", + }) + + err := app.Run() + + if err != nil { + log.Fatal(err) + } +} diff --git a/v3/internal/templates/svelte-ts/template.json b/v3/internal/templates/svelte-ts/template.json new file mode 100644 index 000000000..82fbc8cd9 --- /dev/null +++ b/v3/internal/templates/svelte-ts/template.json @@ -0,0 +1,8 @@ +{ + "name": "Svelte + Vite (Typescript)", + "shortname": "svelte-ts", + "author": "Lea Anthony", + "description": "Svelte + TS + Vite development server", + "helpurl": "https://wails.io", + "version": 3 +} \ No newline at end of file diff --git a/v3/internal/templates/svelte/Taskfile.tmpl.yml b/v3/internal/templates/svelte/Taskfile.tmpl.yml new file mode 100644 index 000000000..f9646a735 --- /dev/null +++ b/v3/internal/templates/svelte/Taskfile.tmpl.yml @@ -0,0 +1,125 @@ +version: '3' + +vars: + APP_NAME: "{{.ProjectName}}" + +tasks: + + pre-build: + summary: Pre-build hooks + + post-build: + summary: Post-build hooks + + install-frontend-deps: + summary: Install frontend dependencies + dir: frontend + sources: + - package.json + - package-lock.json + generates: + - node_modules/* + preconditions: + - sh: npm version + msg: "Looks like npm isn't installed. Npm is part of the Node installer: https://nodejs.org/en/download/" + cmds: + - npm install + + build-frontend: + summary: Build the frontend project + dir: frontend + deps: + - install-frontend-deps + cmds: + - npm run build + + build:darwin: + summary: Builds the application + platforms: + - darwin + cmds: + - task: pre-build + - task: build-frontend + - go build -gcflags=all="-N -l" -o bin/testapp + - task: post-build + env: + CGO_CFLAGS: "-mmacosx-version-min=10.13" + CGO_LDFLAGS: "-mmacosx-version-min=10.13" + MACOSX_DEPLOYMENT_TARGET: "10.13" + + build:windows: + summary: Builds the application for Windows + platforms: + - windows + cmds: + - task: pre-build + - task: build-frontend + - go build -gcflags=all="-N -l" -o bin/testapp.exe + - task: post-build + + build: + summary: Builds the application + cmds: + - task: build:darwin + - task: build:windows + + generate-icons: + summary: Generates Windows `.ico` and Mac `.icns` files from an image + dir: build + cmds: + # Generates both .ico and .icns files + - wails generate icons -input appicon.png + + build-app-prod-darwin: + summary: Creates a production build of the application + cmds: + - task: pre-build + - task: build-frontend + - GOOS=darwin GOARCH={{ "{{.ARCH}}" }} go build -tags production -ldflags="-w -s" -o build/bin/{{ "{{.APP_NAME}}" }} + - task: post-build + env: + CGO_CFLAGS: "-mmacosx-version-min=10.13" + CGO_LDFLAGS: "-mmacosx-version-min=10.13" + MACOSX_DEPLOYMENT_TARGET: "10.13" + vars: + ARCH: $GOARCH + + + create-app-bundle: + summary: Builds a `.app` bundle + cmds: + - mkdir -p {{ "{{.APP_NAME}}" }}.app/Contents/{MacOS,Resources} + - cp build/icons.icns {{ "{{.APP_NAME}}" }}.app/Contents/Resources + - cp build/bin/{{ "{{.APP_NAME}}" }} {{ "{{.APP_NAME}}" }}.app/Contents/MacOS + - cp build/Info.plist {{ "{{.APP_NAME}}" }}.app/Contents + + package-darwin-arm64: + summary: Packages a production build of the application into a `.app` bundle + platform: darwin + deps: + - task: build-app-prod-darwin + vars: + ARCH: arm64 + - generate-icons + cmds: + - task: create-app-bundle + + generate:syso: + dir: build + platform: windows + cmds: + - wails generate syso -arch {{ "{{.ARCH}}" }} -icon icon.ico -manifest wails.exe.manifest -info info.json -out ../wails.syso + vars: + ARCH: $GOARCH + + package:windows: + summary: Packages a production build of the application into a `.exe` bundle + platform: windows + deps: + - generate-icons + cmds: + - task: generate:syso + vars: + ARCH: amd64 + - go build -tags production -ldflags="-w -s -H windowsgui" -o bin/{{ "{{.APP_NAME}}" }}.exe + - powershell Remove-item wails.syso diff --git a/v3/internal/templates/svelte/build/Info.dev.plist.tmpl b/v3/internal/templates/svelte/build/Info.dev.plist.tmpl new file mode 100644 index 000000000..7efa134f4 --- /dev/null +++ b/v3/internal/templates/svelte/build/Info.dev.plist.tmpl @@ -0,0 +1,32 @@ + + + + CFBundlePackageType + APPL + CFBundleName + My Product Name + CFBundleExecutable + {{.ProjectName}} + CFBundleIdentifier + com.wails.{{.ProjectName}} + CFBundleVersion + v1.0.0 + CFBundleGetInfoString + This is a comment + CFBundleShortVersionString + v1.0.0 + CFBundleIconFile + icons + LSMinimumSystemVersion + 10.13.0 + NSHighResolutionCapable + true + NSHumanReadableCopyright + (c) 2023 My Company Name + NSAppTransportSecurity + + NSAllowsLocalNetworking + + + + \ No newline at end of file diff --git a/v3/internal/templates/svelte/build/Info.plist.tmpl b/v3/internal/templates/svelte/build/Info.plist.tmpl new file mode 100644 index 000000000..6bfa8c316 --- /dev/null +++ b/v3/internal/templates/svelte/build/Info.plist.tmpl @@ -0,0 +1,27 @@ + + + + CFBundlePackageType + APPL + CFBundleName + My Product Name + CFBundleExecutable + {{.ProjectName}} + CFBundleIdentifier + com.wails.{{.ProjectName}} + CFBundleVersion + v1.0.0 + CFBundleGetInfoString + This is a comment + CFBundleShortVersionString + v1.0.0 + CFBundleIconFile + icons + LSMinimumSystemVersion + 10.13.0 + NSHighResolutionCapable + true + NSHumanReadableCopyright + (c) 2023 My Company Name + + \ No newline at end of file diff --git a/v3/internal/templates/svelte/build/appicon.png b/v3/internal/templates/svelte/build/appicon.png new file mode 100644 index 000000000..63617fe4f Binary files /dev/null and b/v3/internal/templates/svelte/build/appicon.png differ diff --git a/v3/internal/templates/svelte/build/icons.icns b/v3/internal/templates/svelte/build/icons.icns new file mode 100644 index 000000000..1b5bd4c86 Binary files /dev/null and b/v3/internal/templates/svelte/build/icons.icns differ diff --git a/v3/internal/templates/svelte/frontend/.gitignore b/v3/internal/templates/svelte/frontend/.gitignore new file mode 100644 index 000000000..a547bf36d --- /dev/null +++ b/v3/internal/templates/svelte/frontend/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/v3/internal/templates/svelte/frontend/.vscode/extensions.json b/v3/internal/templates/svelte/frontend/.vscode/extensions.json new file mode 100644 index 000000000..bdef82015 --- /dev/null +++ b/v3/internal/templates/svelte/frontend/.vscode/extensions.json @@ -0,0 +1,3 @@ +{ + "recommendations": ["svelte.svelte-vscode"] +} diff --git a/v3/internal/templates/svelte/frontend/README.md b/v3/internal/templates/svelte/frontend/README.md new file mode 100644 index 000000000..fd6a7082f --- /dev/null +++ b/v3/internal/templates/svelte/frontend/README.md @@ -0,0 +1 @@ +# Wails + Svelte \ No newline at end of file diff --git a/v3/internal/templates/svelte/frontend/index.html b/v3/internal/templates/svelte/frontend/index.html new file mode 100644 index 000000000..1ea50f904 --- /dev/null +++ b/v3/internal/templates/svelte/frontend/index.html @@ -0,0 +1,13 @@ + + + + + + + Wails + Svelte + + +
+ + + diff --git a/v3/internal/templates/svelte/frontend/jsconfig.json b/v3/internal/templates/svelte/frontend/jsconfig.json new file mode 100644 index 000000000..e596c5823 --- /dev/null +++ b/v3/internal/templates/svelte/frontend/jsconfig.json @@ -0,0 +1,33 @@ +{ + "compilerOptions": { + "moduleResolution": "Node", + "target": "ESNext", + "module": "ESNext", + /** + * svelte-preprocess cannot figure out whether you have + * a value or a type, so tell TypeScript to enforce using + * `import type` instead of `import` for Types. + */ + "importsNotUsedAsValues": "error", + "isolatedModules": true, + "resolveJsonModule": true, + /** + * To have warnings / errors of the Svelte compiler at the + * correct position, enable source maps by default. + */ + "sourceMap": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + /** + * Typecheck JS in `.svelte` and `.js` files by default. + * Disable this if you'd like to use dynamic types. + */ + "checkJs": true + }, + /** + * Use global.d.ts instead of compilerOptions.types + * to avoid limiting type declarations. + */ + "include": ["src/**/*.d.ts", "src/**/*.js", "src/**/*.svelte"] +} diff --git a/v3/internal/templates/svelte/frontend/package.json b/v3/internal/templates/svelte/frontend/package.json new file mode 100644 index 000000000..2e166feea --- /dev/null +++ b/v3/internal/templates/svelte/frontend/package.json @@ -0,0 +1,16 @@ +{ + "name": "frontend", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview" + }, + "devDependencies": { + "@sveltejs/vite-plugin-svelte": "^2.0.0", + "svelte": "^3.54.0", + "vite": "^4.0.0" + } +} \ No newline at end of file diff --git a/v3/internal/templates/svelte/frontend/public/wails.png b/v3/internal/templates/svelte/frontend/public/wails.png new file mode 100644 index 000000000..8bdf42483 Binary files /dev/null and b/v3/internal/templates/svelte/frontend/public/wails.png differ diff --git a/v3/internal/templates/svelte/frontend/src/App.svelte b/v3/internal/templates/svelte/frontend/src/App.svelte new file mode 100644 index 000000000..539c395dd --- /dev/null +++ b/v3/internal/templates/svelte/frontend/src/App.svelte @@ -0,0 +1,45 @@ + + +
+ +

Wails + Svelte

+ +
+ +
+ +

+ Check out SvelteKit, the official Svelte app framework powered by Vite! +

+ +

+ Click on the Wails and Svelte logos to learn more +

+
+ + diff --git a/v3/internal/templates/svelte/frontend/src/app.css b/v3/internal/templates/svelte/frontend/src/app.css new file mode 100644 index 000000000..bcc7233dd --- /dev/null +++ b/v3/internal/templates/svelte/frontend/src/app.css @@ -0,0 +1,81 @@ +:root { + font-family: Inter, Avenir, Helvetica, Arial, sans-serif; + font-size: 16px; + line-height: 24px; + font-weight: 400; + + color-scheme: light dark; + color: rgba(255, 255, 255, 0.87); + background-color: #242424; + + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + -webkit-text-size-adjust: 100%; +} + +a { + font-weight: 500; + color: #646cff; + text-decoration: inherit; +} +a:hover { + color: #535bf2; +} + +body { + margin: 0; + display: flex; + place-items: center; + min-width: 320px; + min-height: 100vh; +} + +h1 { + font-size: 3.2em; + line-height: 1.1; +} + +.card { + padding: 2em; +} + +#app { + max-width: 1280px; + margin: 0 auto; + padding: 2rem; + text-align: center; +} + +button { + border-radius: 8px; + border: 1px solid transparent; + padding: 0.6em 1.2em; + font-size: 1em; + font-weight: 500; + font-family: inherit; + background-color: #1a1a1a; + cursor: pointer; + transition: border-color 0.25s; +} +button:hover { + border-color: #646cff; +} +button:focus, +button:focus-visible { + outline: 4px auto -webkit-focus-ring-color; +} + +@media (prefers-color-scheme: light) { + :root { + color: #213547; + background-color: #ffffff; + } + a:hover { + color: #747bff; + } + button { + background-color: #f9f9f9; + } +} diff --git a/v3/internal/templates/svelte/frontend/src/assets/svelte.svg b/v3/internal/templates/svelte/frontend/src/assets/svelte.svg new file mode 100644 index 000000000..c5e08481f --- /dev/null +++ b/v3/internal/templates/svelte/frontend/src/assets/svelte.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/v3/internal/templates/svelte/frontend/src/lib/Counter.svelte b/v3/internal/templates/svelte/frontend/src/lib/Counter.svelte new file mode 100644 index 000000000..e45f90310 --- /dev/null +++ b/v3/internal/templates/svelte/frontend/src/lib/Counter.svelte @@ -0,0 +1,10 @@ + + + diff --git a/v3/internal/templates/svelte/frontend/src/main.js b/v3/internal/templates/svelte/frontend/src/main.js new file mode 100644 index 000000000..8a909a15a --- /dev/null +++ b/v3/internal/templates/svelte/frontend/src/main.js @@ -0,0 +1,8 @@ +import './app.css' +import App from './App.svelte' + +const app = new App({ + target: document.getElementById('app'), +}) + +export default app diff --git a/v3/internal/templates/svelte/frontend/src/vite-env.d.ts b/v3/internal/templates/svelte/frontend/src/vite-env.d.ts new file mode 100644 index 000000000..4078e7476 --- /dev/null +++ b/v3/internal/templates/svelte/frontend/src/vite-env.d.ts @@ -0,0 +1,2 @@ +/// +/// diff --git a/v3/internal/templates/svelte/frontend/vite.config.js b/v3/internal/templates/svelte/frontend/vite.config.js new file mode 100644 index 000000000..d70196943 --- /dev/null +++ b/v3/internal/templates/svelte/frontend/vite.config.js @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite' +import { svelte } from '@sveltejs/vite-plugin-svelte' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [svelte()], +}) diff --git a/v3/internal/templates/svelte/go.mod.tmpl b/v3/internal/templates/svelte/go.mod.tmpl new file mode 100644 index 000000000..3dad9b6fa --- /dev/null +++ b/v3/internal/templates/svelte/go.mod.tmpl @@ -0,0 +1,19 @@ +module changeme + +go 1.21 + +require github.com/wailsapp/wails/v3 v3.0.0-alpha.0 + +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 +) +{{if gt (len .LocalModulePath) 0}} +replace github.com/wailsapp/wails/v3 => {{.LocalModulePath}}v3 +{{end}} diff --git a/v3/internal/templates/svelte/go.sum.tmpl b/v3/internal/templates/svelte/go.sum.tmpl new file mode 100644 index 000000000..c06e0dbc6 --- /dev/null +++ b/v3/internal/templates/svelte/go.sum.tmpl @@ -0,0 +1,33 @@ +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/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/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/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= +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/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/v3/internal/templates/svelte/main.go.tmpl b/v3/internal/templates/svelte/main.go.tmpl new file mode 100644 index 000000000..96c4fd5f0 --- /dev/null +++ b/v3/internal/templates/svelte/main.go.tmpl @@ -0,0 +1,43 @@ +package main + +import ( + "embed" + _ "embed" + "log" + + "github.com/wailsapp/wails/v3/pkg/application" +) + +//go:embed frontend/dist +var assets embed.FS + +func main() { + app := application.New(application.Options{ + Name: "{{.ProjectName}}", + Description: "A demo of using raw HTML & CSS", + Assets: application.AssetOptions{ + FS: assets, + }, + Mac: application.MacOptions{ + ApplicationShouldTerminateAfterLastWindowClosed: true, + }, + }) + // Create window + app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{ + Title: "Plain Bundle", + CSS: `body { background-color: rgba(255, 255, 255, 0); } .main { color: white; margin: 20%; }`, + Mac: application.MacWindow{ + InvisibleTitleBarHeight: 50, + Backdrop: application.MacBackdropTranslucent, + TitleBar: application.MacTitleBarHiddenInset, + }, + + URL: "/", + }) + + err := app.Run() + + if err != nil { + log.Fatal(err) + } +} diff --git a/v3/internal/templates/svelte/template.json b/v3/internal/templates/svelte/template.json new file mode 100644 index 000000000..b4d6a7e69 --- /dev/null +++ b/v3/internal/templates/svelte/template.json @@ -0,0 +1,8 @@ +{ + "name": "Svelte + Vite", + "shortname": "svelte", + "author": "Lea Anthony", + "description": "Svelte + Vite development server", + "helpurl": "https://wails.io", + "version": 3 +} \ No newline at end of file diff --git a/v3/internal/templates/templates.go b/v3/internal/templates/templates.go new file mode 100644 index 000000000..2d35d97db --- /dev/null +++ b/v3/internal/templates/templates.go @@ -0,0 +1,373 @@ +package templates + +import ( + "embed" + "encoding/json" + "fmt" + "github.com/go-git/go-git/v5" + "github.com/go-git/go-git/v5/plumbing" + "github.com/pkg/errors" + "github.com/pterm/pterm" + "github.com/wailsapp/wails/v3/internal/debug" + "io/fs" + "os" + "os/exec" + "path/filepath" + "strings" + + "github.com/wailsapp/wails/v3/internal/flags" + + "github.com/leaanthony/gosod" + + "github.com/samber/lo" +) + +//go:embed lit +var lit embed.FS + +//go:embed lit-ts +var litTS embed.FS + +//go:embed vue +var vue embed.FS + +//go:embed vue-ts +var vueTS embed.FS + +//go:embed react +var react embed.FS + +//go:embed react-ts +var reactTS embed.FS + +//go:embed react-swc +var reactSWC embed.FS + +//go:embed react-swc-ts +var reactSWCTS embed.FS + +//go:embed svelte +var svelte embed.FS + +//go:embed svelte-ts +var svelteTS embed.FS + +//go:embed preact +var preact embed.FS + +//go:embed preact-ts +var preactTS embed.FS + +//go:embed vanilla +var vanilla embed.FS + +//go:embed vanilla-ts +var vanillaTS embed.FS + +type TemplateData struct { + Name string + Description string + FS embed.FS +} + +var defaultTemplates = []TemplateData{ + { + Name: "lit", + Description: "Template using Lit Web Components: https://lit.dev", + FS: lit, + }, + { + Name: "lit-ts", + Description: "Template using Lit Web Components (TypeScript) : https://lit.dev", + FS: litTS, + }, + { + Name: "vue", + Description: "Template using Vue: https://vuejs.org", + FS: vue, + }, + { + Name: "vue-ts", + Description: "Template using Vue (TypeScript): https://vuejs.org", + FS: vueTS, + }, + { + Name: "react", + Description: "Template using React: https://reactjs.org", + FS: react, + }, + { + Name: "react-ts", + Description: "Template using React (TypeScript): https://reactjs.org", + FS: reactTS, + }, + { + Name: "react-swc", + Description: "Template using React with SWC: https://reactjs.org & https://swc.rs", + FS: reactSWC, + }, + { + Name: "react-swc-ts", + Description: "Template using React with SWC (TypeScript): https://reactjs.org & https://swc.rs", + FS: reactSWCTS, + }, + { + Name: "svelte", + Description: "Template using Svelte: https://svelte.dev", + FS: svelte, + }, + { + Name: "svelte-ts", + Description: "Template using Svelte (TypeScript): https://svelte.dev", + FS: svelteTS, + }, + { + Name: "preact", + Description: "Template using Preact: https://preactjs.com", + FS: preact, + }, + { + Name: "preact-ts", + Description: "Template using Preact (TypeScript): https://preactjs.com", + FS: preactTS, + }, + { + Name: "vanilla", + Description: "Template using Vanilla JS", + FS: vanilla, + }, + { + Name: "vanilla-ts", + Description: "Template using Vanilla JS (TypeScript)", + FS: vanillaTS, + }, +} + +func ValidTemplateName(name string) bool { + return lo.ContainsBy(defaultTemplates, func(template TemplateData) bool { + return template.Name == name + }) +} + +func GetDefaultTemplates() []TemplateData { + return defaultTemplates +} + +type TemplateOptions struct { + *flags.Init + LocalModulePath string +} + +func getInternalTemplate(templateName string) (*Template, error) { + templateData, found := lo.Find(defaultTemplates, func(template TemplateData) bool { + return template.Name == templateName + }) + + if !found { + return nil, nil + } + + template, err := parseTemplate(templateData.FS, templateData.Name) + if err != nil { + return nil, err + } + + return &template, nil +} + +func getLocalTemplate(templateName string) (*Template, error) { + var template Template + var err error + _, err = os.Stat(templateName) + if err != nil { + return nil, nil + } + + template, err = parseTemplate(os.DirFS(templateName), templateName) + if err != nil { + return nil, err + } + + return &template, nil +} + +// Template holds data relating to a template including the metadata stored in template.yaml +type Template struct { + + // Template details + Name string `json:"name"` + ShortName string `json:"shortname"` + Author string `json:"author"` + Description string `json:"description"` + HelpURL string `json:"helpurl"` + Version int8 `json:"version"` + + // Other data + FS fs.FS `json:"-"` +} + +func parseTemplate(template fs.FS, templateName string) (Template, error) { + var result Template + jsonFile := "template.json" + if templateName != "" { + jsonFile = templateName + "/template.json" + } + data, err := fs.ReadFile(template, jsonFile) + if err != nil { + return result, errors.Wrap(err, "Error parsing template") + } + err = json.Unmarshal(data, &result) + if err != nil { + return result, err + } + result.FS = template + + // We need to do a version check here + if result.Version == 0 { + return result, fmt.Errorf("template not supported by wails 3. This template is probably for wails 2") + } + if result.Version != 3 { + return result, fmt.Errorf("template version %d is not supported by wails 3. Ensure 'version' is set to 3 in the `template.json` file", result.Version) + } + + return result, nil +} + +// Clones the given uri and returns the temporary cloned directory +func gitclone(uri string) (string, error) { + // Create temporary directory + dirname, err := os.MkdirTemp("", "wails-template-*") + if err != nil { + return "", err + } + + // Parse remote template url and version number + templateInfo := strings.Split(uri, "@") + cloneOption := &git.CloneOptions{ + URL: templateInfo[0], + } + if len(templateInfo) > 1 { + cloneOption.ReferenceName = plumbing.NewTagReferenceName(templateInfo[1]) + } + + _, err = git.PlainClone(dirname, false, cloneOption) + + return dirname, err + +} + +func getRemoteTemplate(uri string) (template *Template, err error) { + // git clone to temporary dir + var tempDir string + tempDir, err = gitclone(uri) + + defer func(path string) { + _ = os.RemoveAll(path) + }(tempDir) + + if err != nil { + return + } + + // Remove the .git directory + err = os.RemoveAll(filepath.Join(tempDir, ".git")) + if err != nil { + return + } + + templateFS := os.DirFS(tempDir) + var parsedTemplate Template + parsedTemplate, err = parseTemplate(templateFS, "") + if err != nil { + return + } + return &parsedTemplate, nil +} + +func Install(options *flags.Init) error { + + templateData := TemplateOptions{ + options, + filepath.FromSlash(debug.LocalModulePath + "/"), + } + + defer func() { + // if `template.json` exists, remove it + _ = os.Remove(filepath.Join(templateData.ProjectDir, "template.json")) + }() + + var err error + var template *Template + template, err = getInternalTemplate(options.TemplateName) + if err != nil { + return err + } + if template == nil { + template, err = getLocalTemplate(options.TemplateName) + } + if err != nil { + return err + } + if template == nil { + template, err = getRemoteTemplate(options.TemplateName) + } + if err != nil { + return err + } + + if template == nil { + return fmt.Errorf("invalid template name: %s. Use -l flag to view available templates or use a valid filepath / url to a template", options.TemplateName) + } + + if options.ProjectDir == "." || options.ProjectDir == "" { + templateData.ProjectDir = lo.Must(os.Getwd()) + } + templateData.ProjectDir = filepath.Join(options.ProjectDir, options.ProjectName) + + // If project directory already exists and is not empty, error + if _, err := os.Stat(templateData.ProjectDir); !os.IsNotExist(err) { + // Check if the directory is empty + files := lo.Must(os.ReadDir(templateData.ProjectDir)) + if len(files) > 0 { + return fmt.Errorf("project directory '%s' already exists and is not empty", templateData.ProjectDir) + } + } + + pterm.Printf("Creating project\n") + pterm.Printf("----------------\n\n") + table := pterm.TableData{ + {"Project Name", options.ProjectName}, + {"Project Directory", filepath.FromSlash(options.ProjectDir)}, + {"Template", template.Name}, + {"Template Source", template.HelpURL}, + } + err = pterm.DefaultTable.WithData(table).Render() + if err != nil { + return err + } + tfs, err := fs.Sub(template.FS, options.TemplateName) + if err != nil { + return err + } + + err = gosod.New(tfs).Extract(options.ProjectDir, templateData) + if err != nil { + return err + } + + // Change to project directory + err = os.Chdir(templateData.ProjectDir) + if err != nil { + return err + } + // Run `go mod tidy` + err = exec.Command("go", "mod", "tidy").Run() + if err != nil { + return err + } + + pterm.Printf("\nProject '%s' created successfully.\n", options.ProjectName) + + return nil + +} diff --git a/v3/internal/templates/templates_test.go b/v3/internal/templates/templates_test.go new file mode 100644 index 000000000..25263106e --- /dev/null +++ b/v3/internal/templates/templates_test.go @@ -0,0 +1,38 @@ +package templates + +import ( + "os" + "testing" + + "github.com/wailsapp/wails/v3/internal/flags" +) + +func TestInstall(t *testing.T) { + + tests := []struct { + name string + options *flags.Init + wantErr bool + }{ + { + name: "should install template", + options: &flags.Init{ + ProjectName: "test", + TemplateName: "svelte", + Quiet: false, + }, + wantErr: false, + }, + } + for _, tt := range tests { + // Remove test directory if it exists + if _, err := os.Stat(tt.options.ProjectName); err == nil { + _ = os.RemoveAll(tt.options.ProjectName) + } + t.Run(tt.name, func(t *testing.T) { + if err := Install(tt.options); (err != nil) != tt.wantErr { + t.Errorf("Install() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} diff --git a/v3/internal/templates/vanilla-ts/Taskfile.tmpl.yml b/v3/internal/templates/vanilla-ts/Taskfile.tmpl.yml new file mode 100644 index 000000000..f9646a735 --- /dev/null +++ b/v3/internal/templates/vanilla-ts/Taskfile.tmpl.yml @@ -0,0 +1,125 @@ +version: '3' + +vars: + APP_NAME: "{{.ProjectName}}" + +tasks: + + pre-build: + summary: Pre-build hooks + + post-build: + summary: Post-build hooks + + install-frontend-deps: + summary: Install frontend dependencies + dir: frontend + sources: + - package.json + - package-lock.json + generates: + - node_modules/* + preconditions: + - sh: npm version + msg: "Looks like npm isn't installed. Npm is part of the Node installer: https://nodejs.org/en/download/" + cmds: + - npm install + + build-frontend: + summary: Build the frontend project + dir: frontend + deps: + - install-frontend-deps + cmds: + - npm run build + + build:darwin: + summary: Builds the application + platforms: + - darwin + cmds: + - task: pre-build + - task: build-frontend + - go build -gcflags=all="-N -l" -o bin/testapp + - task: post-build + env: + CGO_CFLAGS: "-mmacosx-version-min=10.13" + CGO_LDFLAGS: "-mmacosx-version-min=10.13" + MACOSX_DEPLOYMENT_TARGET: "10.13" + + build:windows: + summary: Builds the application for Windows + platforms: + - windows + cmds: + - task: pre-build + - task: build-frontend + - go build -gcflags=all="-N -l" -o bin/testapp.exe + - task: post-build + + build: + summary: Builds the application + cmds: + - task: build:darwin + - task: build:windows + + generate-icons: + summary: Generates Windows `.ico` and Mac `.icns` files from an image + dir: build + cmds: + # Generates both .ico and .icns files + - wails generate icons -input appicon.png + + build-app-prod-darwin: + summary: Creates a production build of the application + cmds: + - task: pre-build + - task: build-frontend + - GOOS=darwin GOARCH={{ "{{.ARCH}}" }} go build -tags production -ldflags="-w -s" -o build/bin/{{ "{{.APP_NAME}}" }} + - task: post-build + env: + CGO_CFLAGS: "-mmacosx-version-min=10.13" + CGO_LDFLAGS: "-mmacosx-version-min=10.13" + MACOSX_DEPLOYMENT_TARGET: "10.13" + vars: + ARCH: $GOARCH + + + create-app-bundle: + summary: Builds a `.app` bundle + cmds: + - mkdir -p {{ "{{.APP_NAME}}" }}.app/Contents/{MacOS,Resources} + - cp build/icons.icns {{ "{{.APP_NAME}}" }}.app/Contents/Resources + - cp build/bin/{{ "{{.APP_NAME}}" }} {{ "{{.APP_NAME}}" }}.app/Contents/MacOS + - cp build/Info.plist {{ "{{.APP_NAME}}" }}.app/Contents + + package-darwin-arm64: + summary: Packages a production build of the application into a `.app` bundle + platform: darwin + deps: + - task: build-app-prod-darwin + vars: + ARCH: arm64 + - generate-icons + cmds: + - task: create-app-bundle + + generate:syso: + dir: build + platform: windows + cmds: + - wails generate syso -arch {{ "{{.ARCH}}" }} -icon icon.ico -manifest wails.exe.manifest -info info.json -out ../wails.syso + vars: + ARCH: $GOARCH + + package:windows: + summary: Packages a production build of the application into a `.exe` bundle + platform: windows + deps: + - generate-icons + cmds: + - task: generate:syso + vars: + ARCH: amd64 + - go build -tags production -ldflags="-w -s -H windowsgui" -o bin/{{ "{{.APP_NAME}}" }}.exe + - powershell Remove-item wails.syso diff --git a/v3/internal/templates/vanilla-ts/build/Info.dev.plist.tmpl b/v3/internal/templates/vanilla-ts/build/Info.dev.plist.tmpl new file mode 100644 index 000000000..7efa134f4 --- /dev/null +++ b/v3/internal/templates/vanilla-ts/build/Info.dev.plist.tmpl @@ -0,0 +1,32 @@ + + + + CFBundlePackageType + APPL + CFBundleName + My Product Name + CFBundleExecutable + {{.ProjectName}} + CFBundleIdentifier + com.wails.{{.ProjectName}} + CFBundleVersion + v1.0.0 + CFBundleGetInfoString + This is a comment + CFBundleShortVersionString + v1.0.0 + CFBundleIconFile + icons + LSMinimumSystemVersion + 10.13.0 + NSHighResolutionCapable + true + NSHumanReadableCopyright + (c) 2023 My Company Name + NSAppTransportSecurity + + NSAllowsLocalNetworking + + + + \ No newline at end of file diff --git a/v3/internal/templates/vanilla-ts/build/Info.plist.tmpl b/v3/internal/templates/vanilla-ts/build/Info.plist.tmpl new file mode 100644 index 000000000..6bfa8c316 --- /dev/null +++ b/v3/internal/templates/vanilla-ts/build/Info.plist.tmpl @@ -0,0 +1,27 @@ + + + + CFBundlePackageType + APPL + CFBundleName + My Product Name + CFBundleExecutable + {{.ProjectName}} + CFBundleIdentifier + com.wails.{{.ProjectName}} + CFBundleVersion + v1.0.0 + CFBundleGetInfoString + This is a comment + CFBundleShortVersionString + v1.0.0 + CFBundleIconFile + icons + LSMinimumSystemVersion + 10.13.0 + NSHighResolutionCapable + true + NSHumanReadableCopyright + (c) 2023 My Company Name + + \ No newline at end of file diff --git a/v3/internal/templates/vanilla-ts/build/appicon.png b/v3/internal/templates/vanilla-ts/build/appicon.png new file mode 100644 index 000000000..63617fe4f Binary files /dev/null and b/v3/internal/templates/vanilla-ts/build/appicon.png differ diff --git a/v3/internal/templates/vanilla-ts/build/icons.icns b/v3/internal/templates/vanilla-ts/build/icons.icns new file mode 100644 index 000000000..1b5bd4c86 Binary files /dev/null and b/v3/internal/templates/vanilla-ts/build/icons.icns differ diff --git a/v3/internal/templates/vanilla-ts/frontend/.gitignore b/v3/internal/templates/vanilla-ts/frontend/.gitignore new file mode 100644 index 000000000..a547bf36d --- /dev/null +++ b/v3/internal/templates/vanilla-ts/frontend/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/v3/internal/templates/vanilla-ts/frontend/index.html b/v3/internal/templates/vanilla-ts/frontend/index.html new file mode 100644 index 000000000..3da9b4918 --- /dev/null +++ b/v3/internal/templates/vanilla-ts/frontend/index.html @@ -0,0 +1,13 @@ + + + + + + + Wails + TS + + +
+ + + diff --git a/v3/internal/templates/vanilla-ts/frontend/package.json b/v3/internal/templates/vanilla-ts/frontend/package.json new file mode 100644 index 000000000..fddd59a6c --- /dev/null +++ b/v3/internal/templates/vanilla-ts/frontend/package.json @@ -0,0 +1,15 @@ +{ + "name": "frontend", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc && vite build", + "preview": "vite preview" + }, + "devDependencies": { + "typescript": "^4.9.3", + "vite": "^4.0.0" + } +} \ No newline at end of file diff --git a/v3/internal/templates/vanilla-ts/frontend/public/wails.png b/v3/internal/templates/vanilla-ts/frontend/public/wails.png new file mode 100644 index 000000000..8bdf42483 Binary files /dev/null and b/v3/internal/templates/vanilla-ts/frontend/public/wails.png differ diff --git a/v3/internal/templates/vanilla-ts/frontend/src/counter.ts b/v3/internal/templates/vanilla-ts/frontend/src/counter.ts new file mode 100644 index 000000000..09e5afd2d --- /dev/null +++ b/v3/internal/templates/vanilla-ts/frontend/src/counter.ts @@ -0,0 +1,9 @@ +export function setupCounter(element: HTMLButtonElement) { + let counter = 0 + const setCounter = (count: number) => { + counter = count + element.innerHTML = `count is ${counter}` + } + element.addEventListener('click', () => setCounter(counter + 1)) + setCounter(0) +} diff --git a/v3/internal/templates/vanilla-ts/frontend/src/main.ts b/v3/internal/templates/vanilla-ts/frontend/src/main.ts new file mode 100644 index 000000000..b386148ad --- /dev/null +++ b/v3/internal/templates/vanilla-ts/frontend/src/main.ts @@ -0,0 +1,23 @@ +import './style.css' +import typescriptLogo from './typescript.svg' +import { setupCounter } from './counter' + +document.querySelector('#app')!.innerHTML = ` +
+ + + + + + +

Wails + TypeScript

+
+ +
+

+ Click on the Wails and TypeScript logos to learn more +

+
+` + +setupCounter(document.querySelector('#counter')!) diff --git a/v3/internal/templates/vanilla-ts/frontend/src/style.css b/v3/internal/templates/vanilla-ts/frontend/src/style.css new file mode 100644 index 000000000..ac37d84b9 --- /dev/null +++ b/v3/internal/templates/vanilla-ts/frontend/src/style.css @@ -0,0 +1,97 @@ +:root { + font-family: Inter, Avenir, Helvetica, Arial, sans-serif; + font-size: 16px; + line-height: 24px; + font-weight: 400; + + color-scheme: light dark; + color: rgba(255, 255, 255, 0.87); + background-color: #242424; + + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + -webkit-text-size-adjust: 100%; +} + +a { + font-weight: 500; + color: #646cff; + text-decoration: inherit; +} +a:hover { + color: #535bf2; +} + +body { + margin: 0; + display: flex; + place-items: center; + min-width: 320px; + min-height: 100vh; +} + +h1 { + font-size: 3.2em; + line-height: 1.1; +} + +#app { + max-width: 1280px; + margin: 0 auto; + padding: 2rem; + text-align: center; +} + +.logo { + height: 6em; + padding: 1.5em; + will-change: filter; +} +.logo:hover { + filter: drop-shadow(0 0 2em #646cffaa); +} +.logo.vanilla:hover { + filter: drop-shadow(0 0 2em #3178c6aa); +} + +.card { + padding: 2em; +} + +.read-the-docs { + color: #888; +} + +button { + border-radius: 8px; + border: 1px solid transparent; + padding: 0.6em 1.2em; + font-size: 1em; + font-weight: 500; + font-family: inherit; + background-color: #1a1a1a; + cursor: pointer; + transition: border-color 0.25s; +} +button:hover { + border-color: #646cff; +} +button:focus, +button:focus-visible { + outline: 4px auto -webkit-focus-ring-color; +} + +@media (prefers-color-scheme: light) { + :root { + color: #213547; + background-color: #ffffff; + } + a:hover { + color: #747bff; + } + button { + background-color: #f9f9f9; + } +} diff --git a/v3/internal/templates/vanilla-ts/frontend/src/typescript.svg b/v3/internal/templates/vanilla-ts/frontend/src/typescript.svg new file mode 100644 index 000000000..d91c910cc --- /dev/null +++ b/v3/internal/templates/vanilla-ts/frontend/src/typescript.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/v3/internal/templates/vanilla-ts/frontend/src/vite-env.d.ts b/v3/internal/templates/vanilla-ts/frontend/src/vite-env.d.ts new file mode 100644 index 000000000..11f02fe2a --- /dev/null +++ b/v3/internal/templates/vanilla-ts/frontend/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/v3/internal/templates/vanilla-ts/frontend/tsconfig.json b/v3/internal/templates/vanilla-ts/frontend/tsconfig.json new file mode 100644 index 000000000..eac16d14a --- /dev/null +++ b/v3/internal/templates/vanilla-ts/frontend/tsconfig.json @@ -0,0 +1,19 @@ +{ + "compilerOptions": { + "target": "ESNext", + "useDefineForClassFields": true, + "module": "ESNext", + "lib": ["ESNext", "DOM"], + "moduleResolution": "Node", + "strict": true, + "resolveJsonModule": true, + "isolatedModules": true, + "esModuleInterop": true, + "noEmit": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noImplicitReturns": true, + "skipLibCheck": true + }, + "include": ["src"] +} diff --git a/v3/internal/templates/vanilla-ts/go.mod.tmpl b/v3/internal/templates/vanilla-ts/go.mod.tmpl new file mode 100644 index 000000000..3dad9b6fa --- /dev/null +++ b/v3/internal/templates/vanilla-ts/go.mod.tmpl @@ -0,0 +1,19 @@ +module changeme + +go 1.21 + +require github.com/wailsapp/wails/v3 v3.0.0-alpha.0 + +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 +) +{{if gt (len .LocalModulePath) 0}} +replace github.com/wailsapp/wails/v3 => {{.LocalModulePath}}v3 +{{end}} diff --git a/v3/internal/templates/vanilla-ts/go.sum.tmpl b/v3/internal/templates/vanilla-ts/go.sum.tmpl new file mode 100644 index 000000000..c06e0dbc6 --- /dev/null +++ b/v3/internal/templates/vanilla-ts/go.sum.tmpl @@ -0,0 +1,33 @@ +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/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/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/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= +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/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/v3/internal/templates/vanilla-ts/main.go.tmpl b/v3/internal/templates/vanilla-ts/main.go.tmpl new file mode 100644 index 000000000..96c4fd5f0 --- /dev/null +++ b/v3/internal/templates/vanilla-ts/main.go.tmpl @@ -0,0 +1,43 @@ +package main + +import ( + "embed" + _ "embed" + "log" + + "github.com/wailsapp/wails/v3/pkg/application" +) + +//go:embed frontend/dist +var assets embed.FS + +func main() { + app := application.New(application.Options{ + Name: "{{.ProjectName}}", + Description: "A demo of using raw HTML & CSS", + Assets: application.AssetOptions{ + FS: assets, + }, + Mac: application.MacOptions{ + ApplicationShouldTerminateAfterLastWindowClosed: true, + }, + }) + // Create window + app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{ + Title: "Plain Bundle", + CSS: `body { background-color: rgba(255, 255, 255, 0); } .main { color: white; margin: 20%; }`, + Mac: application.MacWindow{ + InvisibleTitleBarHeight: 50, + Backdrop: application.MacBackdropTranslucent, + TitleBar: application.MacTitleBarHiddenInset, + }, + + URL: "/", + }) + + err := app.Run() + + if err != nil { + log.Fatal(err) + } +} diff --git a/v3/internal/templates/vanilla-ts/template.json b/v3/internal/templates/vanilla-ts/template.json new file mode 100644 index 000000000..0fc3c7241 --- /dev/null +++ b/v3/internal/templates/vanilla-ts/template.json @@ -0,0 +1,8 @@ +{ + "name": "Vanilla + Vite (Typescript)", + "shortname": "vanilla-ts", + "author": "Lea Anthony", + "description": "Vanilla + Vite development server", + "helpurl": "https://wails.io", + "version": 3 +} \ No newline at end of file diff --git a/v3/internal/templates/vanilla/Taskfile.tmpl.yml b/v3/internal/templates/vanilla/Taskfile.tmpl.yml new file mode 100644 index 000000000..dd6041b7c --- /dev/null +++ b/v3/internal/templates/vanilla/Taskfile.tmpl.yml @@ -0,0 +1,138 @@ +version: '3' + +vars: + APP_NAME: "{{.ProjectName}}" + APP_VERSION: "0.1.0" + PRODUCT_NAME: "My Product" + PRODUCT_DESCRIPTION: "My Product Description" + PRODUCT_VERSION: "0.1.0" + PRODUCT_COMPANY: "My Company" + PRODUCT_COPYRIGHT: "(c) 2023 My Company" + PRODUCT_COMMENTS: "My Comments" + PRODUCT_IDENTIFIER: "com.mycompany.myproduct" + BUILD_DIR: "build" + +tasks: + + generate:build-assets: + summary: Generates the build assets + cmds: + - wails3 generate build-assets -dir "{{ "{{.BUILD_DIR}}" }}" -name "{{ "{{.APP_NAME}}" }}" -productname "{{ "{{.PRODUCT_NAME}}" }}" -productdescription "{{ "{{.PRODUCT_DESCRIPTION}}" }}" -productversion "{{ "{{.PRODUCT_VERSION}}" }}" -productcompany "{{ "{{.PRODUCT_COMPANY}}" }}" -productcopyright "{{ "{{.PRODUCT_COPYRIGHT}}" }}" -productcomments "{{ "{{.PRODUCT_COMMENTS}}" }}" -productidentifier "{{ "{{.PRODUCT_IDENTIFIER}}" }}" + + pre-build: + summary: Pre-build hooks + + post-build: + summary: Post-build hooks + + install-frontend-deps: + summary: Install frontend dependencies + dir: frontend + sources: + - package.json + - package-lock.json + generates: + - node_modules/* + preconditions: + - sh: npm version + msg: "Looks like npm isn't installed. Npm is part of the Node installer: https://nodejs.org/en/download/" + cmds: + - npm install + + build-frontend: + summary: Build the frontend project + dir: frontend + deps: + - install-frontend-deps + cmds: + - npm run build + + build:darwin: + summary: Builds the application + platforms: + - darwin + cmds: + - task: pre-build + - task: build-frontend + - go build -gcflags=all="-N -l" -o bin/testapp + - task: post-build + env: + CGO_CFLAGS: "-mmacosx-version-min=10.13" + CGO_LDFLAGS: "-mmacosx-version-min=10.13" + MACOSX_DEPLOYMENT_TARGET: "10.13" + + build:windows: + summary: Builds the application for Windows + platforms: + - windows + cmds: + - task: pre-build + - task: build-frontend + - go build -gcflags=all="-N -l" -o bin/testapp.exe + - task: post-build + + build: + summary: Builds the application + cmds: + - task: build:darwin + - task: build:windows + + generate-icons: + summary: Generates Windows `.ico` and Mac `.icns` files from an image + cmds: + # Generates both .ico and .icns files + - wails3 generate icons -input {{ "{{.BUILD_DIR}}" }}/appicon.png + + build-app-prod-darwin: + summary: Creates a production build of the application + cmds: + - task: pre-build + - task: build-frontend + - GOOS=darwin GOARCH={{ "{{.ARCH}}" }} go build -tags production -ldflags="-w -s" -o bin/{{ "{{.APP_NAME}}" }} + - task: post-build + env: + CGO_CFLAGS: "-mmacosx-version-min=10.13" + CGO_LDFLAGS: "-mmacosx-version-min=10.13" + MACOSX_DEPLOYMENT_TARGET: "10.13" + vars: + ARCH: $GOARCH + + + create-app-bundle: + summary: Builds a `.app` bundle + cmds: + - mkdir -p {{ "{{.APP_NAME}}" }}.app/Contents/{MacOS,Resources} + - cp {{ "{{.BUILD_DIR}}" }}/icons.icns {{ "{{.APP_NAME}}" }}.app/Contents/Resources + - cp bin/{{ "{{.APP_NAME}}" }} {{ "{{.APP_NAME}}" }}.app/Contents/MacOS + - cp {{ "{{.BUILD_DIR}}" }}/Info.plist {{ "{{.APP_NAME}}" }}.app/Contents + + package-darwin-arm64: + summary: Packages a production build of the application into a `.app` bundle + platform: darwin + deps: + - task: build-app-prod-darwin + vars: + ARCH: arm64 + - generate-icons + cmds: + - task: create-app-bundle + + generate:syso: + dir: build + platform: windows + cmds: + - wails3 generate syso -arch {{ "{{.ARCH}}" }} -icon {{ "{{.BUILD_DIR}}" }}/icon.ico -manifest {{ "{{.BUILD_DIR}}" }}/wails.exe.manifest -info {{ "{{.BUILD_DIR}}" }}/info.json -out ../wails.syso + vars: + ARCH: $GOARCH + + package:windows: + summary: Packages a production build of the application into a `.exe` bundle + platform: windows + deps: + - generate-icons + cmds: + - task: generate:syso + vars: + ARCH: amd64 + - go build -tags production -ldflags="-w -s -H windowsgui" -o "{{ "{{.BUILD_DIR}}" }}"/bin/{{ "{{.APP_NAME}}" }}.exe + - powershell Remove-item wails.syso diff --git a/v3/internal/templates/vanilla/frontend/.gitignore b/v3/internal/templates/vanilla/frontend/.gitignore new file mode 100644 index 000000000..a547bf36d --- /dev/null +++ b/v3/internal/templates/vanilla/frontend/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/v3/internal/templates/vanilla/frontend/counter.js b/v3/internal/templates/vanilla/frontend/counter.js new file mode 100644 index 000000000..881e2d7ad --- /dev/null +++ b/v3/internal/templates/vanilla/frontend/counter.js @@ -0,0 +1,9 @@ +export function setupCounter(element) { + let counter = 0 + const setCounter = (count) => { + counter = count + element.innerHTML = `count is ${counter}` + } + element.addEventListener('click', () => setCounter(counter + 1)) + setCounter(0) +} diff --git a/v3/internal/templates/vanilla/frontend/index.html b/v3/internal/templates/vanilla/frontend/index.html new file mode 100644 index 000000000..a13d62487 --- /dev/null +++ b/v3/internal/templates/vanilla/frontend/index.html @@ -0,0 +1,13 @@ + + + + + + + Wails App + + +
+ + + diff --git a/v3/internal/templates/vanilla/frontend/javascript.svg b/v3/internal/templates/vanilla/frontend/javascript.svg new file mode 100644 index 000000000..f9abb2b72 --- /dev/null +++ b/v3/internal/templates/vanilla/frontend/javascript.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/v3/internal/templates/vanilla/frontend/main.js b/v3/internal/templates/vanilla/frontend/main.js new file mode 100644 index 000000000..5a926e5b8 --- /dev/null +++ b/v3/internal/templates/vanilla/frontend/main.js @@ -0,0 +1,23 @@ +import './style.css' +import javascriptLogo from './javascript.svg' +import { setupCounter } from './counter.js' + +document.querySelector('#app').innerHTML = ` +
+ + + + + + +

Hello Wails!

+
+ +
+

+ Click on the Wails logo to learn more +

+
+` + +setupCounter(document.querySelector('#counter')) diff --git a/v3/internal/templates/vanilla/frontend/package.json b/v3/internal/templates/vanilla/frontend/package.json new file mode 100644 index 000000000..63288c9ef --- /dev/null +++ b/v3/internal/templates/vanilla/frontend/package.json @@ -0,0 +1,14 @@ +{ + "name": "frontend", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview" + }, + "devDependencies": { + "vite": "^4.0.0" + } +} \ No newline at end of file diff --git a/v3/internal/templates/vanilla/frontend/public/wails.png b/v3/internal/templates/vanilla/frontend/public/wails.png new file mode 100644 index 000000000..8bdf42483 Binary files /dev/null and b/v3/internal/templates/vanilla/frontend/public/wails.png differ diff --git a/v3/internal/templates/vanilla/frontend/style.css b/v3/internal/templates/vanilla/frontend/style.css new file mode 100644 index 000000000..12320801d --- /dev/null +++ b/v3/internal/templates/vanilla/frontend/style.css @@ -0,0 +1,97 @@ +:root { + font-family: Inter, Avenir, Helvetica, Arial, sans-serif; + font-size: 16px; + line-height: 24px; + font-weight: 400; + + color-scheme: light dark; + color: rgba(255, 255, 255, 0.87); + background-color: #242424; + + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + -webkit-text-size-adjust: 100%; +} + +a { + font-weight: 500; + color: #646cff; + text-decoration: inherit; +} +a:hover { + color: #535bf2; +} + +body { + margin: 0; + display: flex; + place-items: center; + min-width: 320px; + min-height: 100vh; +} + +h1 { + font-size: 3.2em; + line-height: 1.1; +} + +#app { + max-width: 1280px; + margin: 0 auto; + padding: 2rem; + text-align: center; +} + +.logo { + height: 6em; + padding: 1.5em; + will-change: filter; +} +.logo:hover { + filter: drop-shadow(0 0 2em #646cffaa); +} +.logo.vanilla:hover { + filter: drop-shadow(0 0 2em #f7df1eaa); +} + +.card { + padding: 2em; +} + +.read-the-docs { + color: #888; +} + +button { + border-radius: 8px; + border: 1px solid transparent; + padding: 0.6em 1.2em; + font-size: 1em; + font-weight: 500; + font-family: inherit; + background-color: #1a1a1a; + cursor: pointer; + transition: border-color 0.25s; +} +button:hover { + border-color: #646cff; +} +button:focus, +button:focus-visible { + outline: 4px auto -webkit-focus-ring-color; +} + +@media (prefers-color-scheme: light) { + :root { + color: #213547; + background-color: #ffffff; + } + a:hover { + color: #747bff; + } + button { + background-color: #f9f9f9; + } +} diff --git a/v3/internal/templates/vanilla/go.mod.tmpl b/v3/internal/templates/vanilla/go.mod.tmpl new file mode 100644 index 000000000..3dad9b6fa --- /dev/null +++ b/v3/internal/templates/vanilla/go.mod.tmpl @@ -0,0 +1,19 @@ +module changeme + +go 1.21 + +require github.com/wailsapp/wails/v3 v3.0.0-alpha.0 + +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 +) +{{if gt (len .LocalModulePath) 0}} +replace github.com/wailsapp/wails/v3 => {{.LocalModulePath}}v3 +{{end}} diff --git a/v3/internal/templates/vanilla/go.sum.tmpl b/v3/internal/templates/vanilla/go.sum.tmpl new file mode 100644 index 000000000..c06e0dbc6 --- /dev/null +++ b/v3/internal/templates/vanilla/go.sum.tmpl @@ -0,0 +1,33 @@ +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/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/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/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= +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/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/v3/internal/templates/vanilla/main.go.tmpl b/v3/internal/templates/vanilla/main.go.tmpl new file mode 100644 index 000000000..96c4fd5f0 --- /dev/null +++ b/v3/internal/templates/vanilla/main.go.tmpl @@ -0,0 +1,43 @@ +package main + +import ( + "embed" + _ "embed" + "log" + + "github.com/wailsapp/wails/v3/pkg/application" +) + +//go:embed frontend/dist +var assets embed.FS + +func main() { + app := application.New(application.Options{ + Name: "{{.ProjectName}}", + Description: "A demo of using raw HTML & CSS", + Assets: application.AssetOptions{ + FS: assets, + }, + Mac: application.MacOptions{ + ApplicationShouldTerminateAfterLastWindowClosed: true, + }, + }) + // Create window + app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{ + Title: "Plain Bundle", + CSS: `body { background-color: rgba(255, 255, 255, 0); } .main { color: white; margin: 20%; }`, + Mac: application.MacWindow{ + InvisibleTitleBarHeight: 50, + Backdrop: application.MacBackdropTranslucent, + TitleBar: application.MacTitleBarHiddenInset, + }, + + URL: "/", + }) + + err := app.Run() + + if err != nil { + log.Fatal(err) + } +} diff --git a/v3/internal/templates/vanilla/template.json b/v3/internal/templates/vanilla/template.json new file mode 100644 index 000000000..0c7f8ba82 --- /dev/null +++ b/v3/internal/templates/vanilla/template.json @@ -0,0 +1,8 @@ +{ + "name": "Vanilla + Vite", + "shortname": "vanilla", + "author": "Lea Anthony", + "description": "Vanilla + Vite development server", + "helpurl": "https://wails.io", + "version": 3 +} \ No newline at end of file diff --git a/v3/internal/templates/vue-ts/Taskfile.tmpl.yml b/v3/internal/templates/vue-ts/Taskfile.tmpl.yml new file mode 100644 index 000000000..f9646a735 --- /dev/null +++ b/v3/internal/templates/vue-ts/Taskfile.tmpl.yml @@ -0,0 +1,125 @@ +version: '3' + +vars: + APP_NAME: "{{.ProjectName}}" + +tasks: + + pre-build: + summary: Pre-build hooks + + post-build: + summary: Post-build hooks + + install-frontend-deps: + summary: Install frontend dependencies + dir: frontend + sources: + - package.json + - package-lock.json + generates: + - node_modules/* + preconditions: + - sh: npm version + msg: "Looks like npm isn't installed. Npm is part of the Node installer: https://nodejs.org/en/download/" + cmds: + - npm install + + build-frontend: + summary: Build the frontend project + dir: frontend + deps: + - install-frontend-deps + cmds: + - npm run build + + build:darwin: + summary: Builds the application + platforms: + - darwin + cmds: + - task: pre-build + - task: build-frontend + - go build -gcflags=all="-N -l" -o bin/testapp + - task: post-build + env: + CGO_CFLAGS: "-mmacosx-version-min=10.13" + CGO_LDFLAGS: "-mmacosx-version-min=10.13" + MACOSX_DEPLOYMENT_TARGET: "10.13" + + build:windows: + summary: Builds the application for Windows + platforms: + - windows + cmds: + - task: pre-build + - task: build-frontend + - go build -gcflags=all="-N -l" -o bin/testapp.exe + - task: post-build + + build: + summary: Builds the application + cmds: + - task: build:darwin + - task: build:windows + + generate-icons: + summary: Generates Windows `.ico` and Mac `.icns` files from an image + dir: build + cmds: + # Generates both .ico and .icns files + - wails generate icons -input appicon.png + + build-app-prod-darwin: + summary: Creates a production build of the application + cmds: + - task: pre-build + - task: build-frontend + - GOOS=darwin GOARCH={{ "{{.ARCH}}" }} go build -tags production -ldflags="-w -s" -o build/bin/{{ "{{.APP_NAME}}" }} + - task: post-build + env: + CGO_CFLAGS: "-mmacosx-version-min=10.13" + CGO_LDFLAGS: "-mmacosx-version-min=10.13" + MACOSX_DEPLOYMENT_TARGET: "10.13" + vars: + ARCH: $GOARCH + + + create-app-bundle: + summary: Builds a `.app` bundle + cmds: + - mkdir -p {{ "{{.APP_NAME}}" }}.app/Contents/{MacOS,Resources} + - cp build/icons.icns {{ "{{.APP_NAME}}" }}.app/Contents/Resources + - cp build/bin/{{ "{{.APP_NAME}}" }} {{ "{{.APP_NAME}}" }}.app/Contents/MacOS + - cp build/Info.plist {{ "{{.APP_NAME}}" }}.app/Contents + + package-darwin-arm64: + summary: Packages a production build of the application into a `.app` bundle + platform: darwin + deps: + - task: build-app-prod-darwin + vars: + ARCH: arm64 + - generate-icons + cmds: + - task: create-app-bundle + + generate:syso: + dir: build + platform: windows + cmds: + - wails generate syso -arch {{ "{{.ARCH}}" }} -icon icon.ico -manifest wails.exe.manifest -info info.json -out ../wails.syso + vars: + ARCH: $GOARCH + + package:windows: + summary: Packages a production build of the application into a `.exe` bundle + platform: windows + deps: + - generate-icons + cmds: + - task: generate:syso + vars: + ARCH: amd64 + - go build -tags production -ldflags="-w -s -H windowsgui" -o bin/{{ "{{.APP_NAME}}" }}.exe + - powershell Remove-item wails.syso diff --git a/v3/internal/templates/vue-ts/build/Info.dev.plist.tmpl b/v3/internal/templates/vue-ts/build/Info.dev.plist.tmpl new file mode 100644 index 000000000..7efa134f4 --- /dev/null +++ b/v3/internal/templates/vue-ts/build/Info.dev.plist.tmpl @@ -0,0 +1,32 @@ + + + + CFBundlePackageType + APPL + CFBundleName + My Product Name + CFBundleExecutable + {{.ProjectName}} + CFBundleIdentifier + com.wails.{{.ProjectName}} + CFBundleVersion + v1.0.0 + CFBundleGetInfoString + This is a comment + CFBundleShortVersionString + v1.0.0 + CFBundleIconFile + icons + LSMinimumSystemVersion + 10.13.0 + NSHighResolutionCapable + true + NSHumanReadableCopyright + (c) 2023 My Company Name + NSAppTransportSecurity + + NSAllowsLocalNetworking + + + + \ No newline at end of file diff --git a/v3/internal/templates/vue-ts/build/Info.plist.tmpl b/v3/internal/templates/vue-ts/build/Info.plist.tmpl new file mode 100644 index 000000000..6bfa8c316 --- /dev/null +++ b/v3/internal/templates/vue-ts/build/Info.plist.tmpl @@ -0,0 +1,27 @@ + + + + CFBundlePackageType + APPL + CFBundleName + My Product Name + CFBundleExecutable + {{.ProjectName}} + CFBundleIdentifier + com.wails.{{.ProjectName}} + CFBundleVersion + v1.0.0 + CFBundleGetInfoString + This is a comment + CFBundleShortVersionString + v1.0.0 + CFBundleIconFile + icons + LSMinimumSystemVersion + 10.13.0 + NSHighResolutionCapable + true + NSHumanReadableCopyright + (c) 2023 My Company Name + + \ No newline at end of file diff --git a/v3/internal/templates/vue-ts/build/appicon.png b/v3/internal/templates/vue-ts/build/appicon.png new file mode 100644 index 000000000..63617fe4f Binary files /dev/null and b/v3/internal/templates/vue-ts/build/appicon.png differ diff --git a/v3/internal/templates/vue-ts/build/icons.icns b/v3/internal/templates/vue-ts/build/icons.icns new file mode 100644 index 000000000..1b5bd4c86 Binary files /dev/null and b/v3/internal/templates/vue-ts/build/icons.icns differ diff --git a/v3/internal/templates/vue-ts/frontend/.gitignore b/v3/internal/templates/vue-ts/frontend/.gitignore new file mode 100644 index 000000000..a547bf36d --- /dev/null +++ b/v3/internal/templates/vue-ts/frontend/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/v3/internal/templates/vue-ts/frontend/.vscode/extensions.json b/v3/internal/templates/vue-ts/frontend/.vscode/extensions.json new file mode 100644 index 000000000..c0a6e5a48 --- /dev/null +++ b/v3/internal/templates/vue-ts/frontend/.vscode/extensions.json @@ -0,0 +1,3 @@ +{ + "recommendations": ["Vue.volar", "Vue.vscode-typescript-vue-plugin"] +} diff --git a/v3/internal/templates/vue-ts/frontend/README.md b/v3/internal/templates/vue-ts/frontend/README.md new file mode 100644 index 000000000..ef72fd524 --- /dev/null +++ b/v3/internal/templates/vue-ts/frontend/README.md @@ -0,0 +1,18 @@ +# Vue 3 + TypeScript + Vite + +This template should help get you started developing with Vue 3 and TypeScript in Vite. The template uses Vue 3 ` + + diff --git a/v3/internal/templates/vue-ts/frontend/package.json b/v3/internal/templates/vue-ts/frontend/package.json new file mode 100644 index 000000000..129f6aef7 --- /dev/null +++ b/v3/internal/templates/vue-ts/frontend/package.json @@ -0,0 +1,20 @@ +{ + "name": "frontend", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vue-tsc && vite build", + "preview": "vite preview" + }, + "dependencies": { + "vue": "^3.2.45" + }, + "devDependencies": { + "@vitejs/plugin-vue": "^4.0.0", + "typescript": "^4.9.3", + "vite": "^4.0.0", + "vue-tsc": "^1.0.11" + } +} \ No newline at end of file diff --git a/v3/internal/templates/vue-ts/frontend/public/wails.png b/v3/internal/templates/vue-ts/frontend/public/wails.png new file mode 100644 index 000000000..8bdf42483 Binary files /dev/null and b/v3/internal/templates/vue-ts/frontend/public/wails.png differ diff --git a/v3/internal/templates/vue-ts/frontend/src/App.vue b/v3/internal/templates/vue-ts/frontend/src/App.vue new file mode 100644 index 000000000..9f88fe6d8 --- /dev/null +++ b/v3/internal/templates/vue-ts/frontend/src/App.vue @@ -0,0 +1,29 @@ + + + + + diff --git a/v3/internal/templates/vue-ts/frontend/src/assets/vue.svg b/v3/internal/templates/vue-ts/frontend/src/assets/vue.svg new file mode 100644 index 000000000..770e9d333 --- /dev/null +++ b/v3/internal/templates/vue-ts/frontend/src/assets/vue.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/v3/internal/templates/vue-ts/frontend/src/components/HelloWorld.vue b/v3/internal/templates/vue-ts/frontend/src/components/HelloWorld.vue new file mode 100644 index 000000000..523091033 --- /dev/null +++ b/v3/internal/templates/vue-ts/frontend/src/components/HelloWorld.vue @@ -0,0 +1,38 @@ + + + + + diff --git a/v3/internal/templates/vue-ts/frontend/src/main.ts b/v3/internal/templates/vue-ts/frontend/src/main.ts new file mode 100644 index 000000000..2425c0f74 --- /dev/null +++ b/v3/internal/templates/vue-ts/frontend/src/main.ts @@ -0,0 +1,5 @@ +import { createApp } from 'vue' +import './style.css' +import App from './App.vue' + +createApp(App).mount('#app') diff --git a/v3/internal/templates/vue-ts/frontend/src/style.css b/v3/internal/templates/vue-ts/frontend/src/style.css new file mode 100644 index 000000000..0192f9aac --- /dev/null +++ b/v3/internal/templates/vue-ts/frontend/src/style.css @@ -0,0 +1,81 @@ +:root { + font-family: Inter, Avenir, Helvetica, Arial, sans-serif; + font-size: 16px; + line-height: 24px; + font-weight: 400; + + color-scheme: light dark; + color: rgba(255, 255, 255, 0.87); + background-color: #242424; + + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + -webkit-text-size-adjust: 100%; +} + +a { + font-weight: 500; + color: #646cff; + text-decoration: inherit; +} +a:hover { + color: #535bf2; +} + +body { + margin: 0; + display: flex; + place-items: center; + min-width: 320px; + min-height: 100vh; +} + +h1 { + font-size: 3.2em; + line-height: 1.1; +} + +button { + border-radius: 8px; + border: 1px solid transparent; + padding: 0.6em 1.2em; + font-size: 1em; + font-weight: 500; + font-family: inherit; + background-color: #1a1a1a; + cursor: pointer; + transition: border-color 0.25s; +} +button:hover { + border-color: #646cff; +} +button:focus, +button:focus-visible { + outline: 4px auto -webkit-focus-ring-color; +} + +.card { + padding: 2em; +} + +#app { + max-width: 1280px; + margin: 0 auto; + padding: 2rem; + text-align: center; +} + +@media (prefers-color-scheme: light) { + :root { + color: #213547; + background-color: #ffffff; + } + a:hover { + color: #747bff; + } + button { + background-color: #f9f9f9; + } +} diff --git a/v3/internal/templates/vue-ts/frontend/src/vite-env.d.ts b/v3/internal/templates/vue-ts/frontend/src/vite-env.d.ts new file mode 100644 index 000000000..11f02fe2a --- /dev/null +++ b/v3/internal/templates/vue-ts/frontend/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/v3/internal/templates/vue-ts/frontend/tsconfig.json b/v3/internal/templates/vue-ts/frontend/tsconfig.json new file mode 100644 index 000000000..b557c4047 --- /dev/null +++ b/v3/internal/templates/vue-ts/frontend/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "target": "ESNext", + "useDefineForClassFields": true, + "module": "ESNext", + "moduleResolution": "Node", + "strict": true, + "jsx": "preserve", + "resolveJsonModule": true, + "isolatedModules": true, + "esModuleInterop": true, + "lib": ["ESNext", "DOM"], + "skipLibCheck": true, + "noEmit": true + }, + "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"], + "references": [{ "path": "./tsconfig.node.json" }] +} diff --git a/v3/internal/templates/vue-ts/frontend/tsconfig.node.json b/v3/internal/templates/vue-ts/frontend/tsconfig.node.json new file mode 100644 index 000000000..9d31e2aed --- /dev/null +++ b/v3/internal/templates/vue-ts/frontend/tsconfig.node.json @@ -0,0 +1,9 @@ +{ + "compilerOptions": { + "composite": true, + "module": "ESNext", + "moduleResolution": "Node", + "allowSyntheticDefaultImports": true + }, + "include": ["vite.config.ts"] +} diff --git a/v3/internal/templates/vue-ts/frontend/vite.config.ts b/v3/internal/templates/vue-ts/frontend/vite.config.ts new file mode 100644 index 000000000..05c17402a --- /dev/null +++ b/v3/internal/templates/vue-ts/frontend/vite.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite' +import vue from '@vitejs/plugin-vue' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [vue()], +}) diff --git a/v3/internal/templates/vue-ts/go.mod.tmpl b/v3/internal/templates/vue-ts/go.mod.tmpl new file mode 100644 index 000000000..3dad9b6fa --- /dev/null +++ b/v3/internal/templates/vue-ts/go.mod.tmpl @@ -0,0 +1,19 @@ +module changeme + +go 1.21 + +require github.com/wailsapp/wails/v3 v3.0.0-alpha.0 + +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 +) +{{if gt (len .LocalModulePath) 0}} +replace github.com/wailsapp/wails/v3 => {{.LocalModulePath}}v3 +{{end}} diff --git a/v3/internal/templates/vue-ts/go.sum.tmpl b/v3/internal/templates/vue-ts/go.sum.tmpl new file mode 100644 index 000000000..c06e0dbc6 --- /dev/null +++ b/v3/internal/templates/vue-ts/go.sum.tmpl @@ -0,0 +1,33 @@ +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/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/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/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= +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/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/v3/internal/templates/vue-ts/main.go.tmpl b/v3/internal/templates/vue-ts/main.go.tmpl new file mode 100644 index 000000000..96c4fd5f0 --- /dev/null +++ b/v3/internal/templates/vue-ts/main.go.tmpl @@ -0,0 +1,43 @@ +package main + +import ( + "embed" + _ "embed" + "log" + + "github.com/wailsapp/wails/v3/pkg/application" +) + +//go:embed frontend/dist +var assets embed.FS + +func main() { + app := application.New(application.Options{ + Name: "{{.ProjectName}}", + Description: "A demo of using raw HTML & CSS", + Assets: application.AssetOptions{ + FS: assets, + }, + Mac: application.MacOptions{ + ApplicationShouldTerminateAfterLastWindowClosed: true, + }, + }) + // Create window + app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{ + Title: "Plain Bundle", + CSS: `body { background-color: rgba(255, 255, 255, 0); } .main { color: white; margin: 20%; }`, + Mac: application.MacWindow{ + InvisibleTitleBarHeight: 50, + Backdrop: application.MacBackdropTranslucent, + TitleBar: application.MacTitleBarHiddenInset, + }, + + URL: "/", + }) + + err := app.Run() + + if err != nil { + log.Fatal(err) + } +} diff --git a/v3/internal/templates/vue-ts/template.json b/v3/internal/templates/vue-ts/template.json new file mode 100644 index 000000000..720ac3db6 --- /dev/null +++ b/v3/internal/templates/vue-ts/template.json @@ -0,0 +1,8 @@ +{ + "name": "Vue + Vite (Typescript)", + "shortname": "vue-ts", + "author": "Lea Anthony", + "description": "Vue + Vite development server", + "helpurl": "https://wails.io", + "version": 3 +} \ No newline at end of file diff --git a/v3/internal/templates/vue/Taskfile.tmpl.yml b/v3/internal/templates/vue/Taskfile.tmpl.yml new file mode 100644 index 000000000..f9646a735 --- /dev/null +++ b/v3/internal/templates/vue/Taskfile.tmpl.yml @@ -0,0 +1,125 @@ +version: '3' + +vars: + APP_NAME: "{{.ProjectName}}" + +tasks: + + pre-build: + summary: Pre-build hooks + + post-build: + summary: Post-build hooks + + install-frontend-deps: + summary: Install frontend dependencies + dir: frontend + sources: + - package.json + - package-lock.json + generates: + - node_modules/* + preconditions: + - sh: npm version + msg: "Looks like npm isn't installed. Npm is part of the Node installer: https://nodejs.org/en/download/" + cmds: + - npm install + + build-frontend: + summary: Build the frontend project + dir: frontend + deps: + - install-frontend-deps + cmds: + - npm run build + + build:darwin: + summary: Builds the application + platforms: + - darwin + cmds: + - task: pre-build + - task: build-frontend + - go build -gcflags=all="-N -l" -o bin/testapp + - task: post-build + env: + CGO_CFLAGS: "-mmacosx-version-min=10.13" + CGO_LDFLAGS: "-mmacosx-version-min=10.13" + MACOSX_DEPLOYMENT_TARGET: "10.13" + + build:windows: + summary: Builds the application for Windows + platforms: + - windows + cmds: + - task: pre-build + - task: build-frontend + - go build -gcflags=all="-N -l" -o bin/testapp.exe + - task: post-build + + build: + summary: Builds the application + cmds: + - task: build:darwin + - task: build:windows + + generate-icons: + summary: Generates Windows `.ico` and Mac `.icns` files from an image + dir: build + cmds: + # Generates both .ico and .icns files + - wails generate icons -input appicon.png + + build-app-prod-darwin: + summary: Creates a production build of the application + cmds: + - task: pre-build + - task: build-frontend + - GOOS=darwin GOARCH={{ "{{.ARCH}}" }} go build -tags production -ldflags="-w -s" -o build/bin/{{ "{{.APP_NAME}}" }} + - task: post-build + env: + CGO_CFLAGS: "-mmacosx-version-min=10.13" + CGO_LDFLAGS: "-mmacosx-version-min=10.13" + MACOSX_DEPLOYMENT_TARGET: "10.13" + vars: + ARCH: $GOARCH + + + create-app-bundle: + summary: Builds a `.app` bundle + cmds: + - mkdir -p {{ "{{.APP_NAME}}" }}.app/Contents/{MacOS,Resources} + - cp build/icons.icns {{ "{{.APP_NAME}}" }}.app/Contents/Resources + - cp build/bin/{{ "{{.APP_NAME}}" }} {{ "{{.APP_NAME}}" }}.app/Contents/MacOS + - cp build/Info.plist {{ "{{.APP_NAME}}" }}.app/Contents + + package-darwin-arm64: + summary: Packages a production build of the application into a `.app` bundle + platform: darwin + deps: + - task: build-app-prod-darwin + vars: + ARCH: arm64 + - generate-icons + cmds: + - task: create-app-bundle + + generate:syso: + dir: build + platform: windows + cmds: + - wails generate syso -arch {{ "{{.ARCH}}" }} -icon icon.ico -manifest wails.exe.manifest -info info.json -out ../wails.syso + vars: + ARCH: $GOARCH + + package:windows: + summary: Packages a production build of the application into a `.exe` bundle + platform: windows + deps: + - generate-icons + cmds: + - task: generate:syso + vars: + ARCH: amd64 + - go build -tags production -ldflags="-w -s -H windowsgui" -o bin/{{ "{{.APP_NAME}}" }}.exe + - powershell Remove-item wails.syso diff --git a/v3/internal/templates/vue/build/Info.dev.plist.tmpl b/v3/internal/templates/vue/build/Info.dev.plist.tmpl new file mode 100644 index 000000000..7efa134f4 --- /dev/null +++ b/v3/internal/templates/vue/build/Info.dev.plist.tmpl @@ -0,0 +1,32 @@ + + + + CFBundlePackageType + APPL + CFBundleName + My Product Name + CFBundleExecutable + {{.ProjectName}} + CFBundleIdentifier + com.wails.{{.ProjectName}} + CFBundleVersion + v1.0.0 + CFBundleGetInfoString + This is a comment + CFBundleShortVersionString + v1.0.0 + CFBundleIconFile + icons + LSMinimumSystemVersion + 10.13.0 + NSHighResolutionCapable + true + NSHumanReadableCopyright + (c) 2023 My Company Name + NSAppTransportSecurity + + NSAllowsLocalNetworking + + + + \ No newline at end of file diff --git a/v3/internal/templates/vue/build/Info.plist.tmpl b/v3/internal/templates/vue/build/Info.plist.tmpl new file mode 100644 index 000000000..6bfa8c316 --- /dev/null +++ b/v3/internal/templates/vue/build/Info.plist.tmpl @@ -0,0 +1,27 @@ + + + + CFBundlePackageType + APPL + CFBundleName + My Product Name + CFBundleExecutable + {{.ProjectName}} + CFBundleIdentifier + com.wails.{{.ProjectName}} + CFBundleVersion + v1.0.0 + CFBundleGetInfoString + This is a comment + CFBundleShortVersionString + v1.0.0 + CFBundleIconFile + icons + LSMinimumSystemVersion + 10.13.0 + NSHighResolutionCapable + true + NSHumanReadableCopyright + (c) 2023 My Company Name + + \ No newline at end of file diff --git a/v3/internal/templates/vue/build/appicon.png b/v3/internal/templates/vue/build/appicon.png new file mode 100644 index 000000000..63617fe4f Binary files /dev/null and b/v3/internal/templates/vue/build/appicon.png differ diff --git a/v3/internal/templates/vue/build/icons.icns b/v3/internal/templates/vue/build/icons.icns new file mode 100644 index 000000000..1b5bd4c86 Binary files /dev/null and b/v3/internal/templates/vue/build/icons.icns differ diff --git a/v3/internal/templates/vue/frontend/.gitignore b/v3/internal/templates/vue/frontend/.gitignore new file mode 100644 index 000000000..a547bf36d --- /dev/null +++ b/v3/internal/templates/vue/frontend/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/v3/internal/templates/vue/frontend/.vscode/extensions.json b/v3/internal/templates/vue/frontend/.vscode/extensions.json new file mode 100644 index 000000000..c0a6e5a48 --- /dev/null +++ b/v3/internal/templates/vue/frontend/.vscode/extensions.json @@ -0,0 +1,3 @@ +{ + "recommendations": ["Vue.volar", "Vue.vscode-typescript-vue-plugin"] +} diff --git a/v3/internal/templates/vue/frontend/README.md b/v3/internal/templates/vue/frontend/README.md new file mode 100644 index 000000000..e62e093e7 --- /dev/null +++ b/v3/internal/templates/vue/frontend/README.md @@ -0,0 +1,7 @@ +# Vue 3 + Vite + +This template should help get you started developing with Vue 3 in Vite. The template uses Vue 3 ` + + diff --git a/v3/internal/templates/vue/frontend/package.json b/v3/internal/templates/vue/frontend/package.json new file mode 100644 index 000000000..b779f58b4 --- /dev/null +++ b/v3/internal/templates/vue/frontend/package.json @@ -0,0 +1,18 @@ +{ + "name": "frontend", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview" + }, + "dependencies": { + "vue": "^3.2.45" + }, + "devDependencies": { + "@vitejs/plugin-vue": "^4.0.0", + "vite": "^4.0.0" + } +} \ No newline at end of file diff --git a/v3/internal/templates/vue/frontend/public/wails.png b/v3/internal/templates/vue/frontend/public/wails.png new file mode 100644 index 000000000..8bdf42483 Binary files /dev/null and b/v3/internal/templates/vue/frontend/public/wails.png differ diff --git a/v3/internal/templates/vue/frontend/src/App.vue b/v3/internal/templates/vue/frontend/src/App.vue new file mode 100644 index 000000000..d2e89b824 --- /dev/null +++ b/v3/internal/templates/vue/frontend/src/App.vue @@ -0,0 +1,29 @@ + + + + + diff --git a/v3/internal/templates/vue/frontend/src/assets/vue.svg b/v3/internal/templates/vue/frontend/src/assets/vue.svg new file mode 100644 index 000000000..770e9d333 --- /dev/null +++ b/v3/internal/templates/vue/frontend/src/assets/vue.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/v3/internal/templates/vue/frontend/src/components/HelloWorld.vue b/v3/internal/templates/vue/frontend/src/components/HelloWorld.vue new file mode 100644 index 000000000..d3c3f15cd --- /dev/null +++ b/v3/internal/templates/vue/frontend/src/components/HelloWorld.vue @@ -0,0 +1,40 @@ + + + + + diff --git a/v3/internal/templates/vue/frontend/src/main.js b/v3/internal/templates/vue/frontend/src/main.js new file mode 100644 index 000000000..2425c0f74 --- /dev/null +++ b/v3/internal/templates/vue/frontend/src/main.js @@ -0,0 +1,5 @@ +import { createApp } from 'vue' +import './style.css' +import App from './App.vue' + +createApp(App).mount('#app') diff --git a/v3/internal/templates/vue/frontend/src/style.css b/v3/internal/templates/vue/frontend/src/style.css new file mode 100644 index 000000000..a566a347d --- /dev/null +++ b/v3/internal/templates/vue/frontend/src/style.css @@ -0,0 +1,90 @@ +:root { + font-family: Inter, Avenir, Helvetica, Arial, sans-serif; + font-size: 16px; + line-height: 24px; + font-weight: 400; + + color-scheme: light dark; + color: rgba(255, 255, 255, 0.87); + background-color: #242424; + + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + -webkit-text-size-adjust: 100%; +} + +a { + font-weight: 500; + color: #646cff; + text-decoration: inherit; +} +a:hover { + color: #535bf2; +} + +a { + font-weight: 500; + color: #646cff; + text-decoration: inherit; +} +a:hover { + color: #535bf2; +} + +body { + margin: 0; + display: flex; + place-items: center; + min-width: 320px; + min-height: 100vh; +} + +h1 { + font-size: 3.2em; + line-height: 1.1; +} + +button { + border-radius: 8px; + border: 1px solid transparent; + padding: 0.6em 1.2em; + font-size: 1em; + font-weight: 500; + font-family: inherit; + background-color: #1a1a1a; + cursor: pointer; + transition: border-color 0.25s; +} +button:hover { + border-color: #646cff; +} +button:focus, +button:focus-visible { + outline: 4px auto -webkit-focus-ring-color; +} + +.card { + padding: 2em; +} + +#app { + max-width: 1280px; + margin: 0 auto; + padding: 2rem; + text-align: center; +} + +@media (prefers-color-scheme: light) { + :root { + color: #213547; + background-color: #ffffff; + } + a:hover { + color: #747bff; + } + button { + background-color: #f9f9f9; + } +} diff --git a/v3/internal/templates/vue/frontend/vite.config.js b/v3/internal/templates/vue/frontend/vite.config.js new file mode 100644 index 000000000..05c17402a --- /dev/null +++ b/v3/internal/templates/vue/frontend/vite.config.js @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite' +import vue from '@vitejs/plugin-vue' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [vue()], +}) diff --git a/v3/internal/templates/vue/go.mod.tmpl b/v3/internal/templates/vue/go.mod.tmpl new file mode 100644 index 000000000..3dad9b6fa --- /dev/null +++ b/v3/internal/templates/vue/go.mod.tmpl @@ -0,0 +1,19 @@ +module changeme + +go 1.21 + +require github.com/wailsapp/wails/v3 v3.0.0-alpha.0 + +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 +) +{{if gt (len .LocalModulePath) 0}} +replace github.com/wailsapp/wails/v3 => {{.LocalModulePath}}v3 +{{end}} diff --git a/v3/internal/templates/vue/go.sum.tmpl b/v3/internal/templates/vue/go.sum.tmpl new file mode 100644 index 000000000..c06e0dbc6 --- /dev/null +++ b/v3/internal/templates/vue/go.sum.tmpl @@ -0,0 +1,33 @@ +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/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/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/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= +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/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/v3/internal/templates/vue/main.go.tmpl b/v3/internal/templates/vue/main.go.tmpl new file mode 100644 index 000000000..96c4fd5f0 --- /dev/null +++ b/v3/internal/templates/vue/main.go.tmpl @@ -0,0 +1,43 @@ +package main + +import ( + "embed" + _ "embed" + "log" + + "github.com/wailsapp/wails/v3/pkg/application" +) + +//go:embed frontend/dist +var assets embed.FS + +func main() { + app := application.New(application.Options{ + Name: "{{.ProjectName}}", + Description: "A demo of using raw HTML & CSS", + Assets: application.AssetOptions{ + FS: assets, + }, + Mac: application.MacOptions{ + ApplicationShouldTerminateAfterLastWindowClosed: true, + }, + }) + // Create window + app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{ + Title: "Plain Bundle", + CSS: `body { background-color: rgba(255, 255, 255, 0); } .main { color: white; margin: 20%; }`, + Mac: application.MacWindow{ + InvisibleTitleBarHeight: 50, + Backdrop: application.MacBackdropTranslucent, + TitleBar: application.MacTitleBarHiddenInset, + }, + + URL: "/", + }) + + err := app.Run() + + if err != nil { + log.Fatal(err) + } +} diff --git a/v3/internal/templates/vue/template.json b/v3/internal/templates/vue/template.json new file mode 100644 index 000000000..f5c66171c --- /dev/null +++ b/v3/internal/templates/vue/template.json @@ -0,0 +1,8 @@ +{ + "name": "Vue + Vite", + "shortname": "vue", + "author": "Lea Anthony", + "description": "Vue + Vite development server", + "helpurl": "https://wails.io", + "version": 3 +} \ No newline at end of file diff --git a/v3/internal/version/version.go b/v3/internal/version/version.go new file mode 100644 index 000000000..d8b4b0c2b --- /dev/null +++ b/v3/internal/version/version.go @@ -0,0 +1,8 @@ +package version + +import ( + _ "embed" +) + +//go:embed version.txt +var VersionString string diff --git a/v3/internal/version/version.txt b/v3/internal/version/version.txt new file mode 100644 index 000000000..906693d17 --- /dev/null +++ b/v3/internal/version/version.txt @@ -0,0 +1 @@ +v3.0.0-alpha.0 \ No newline at end of file diff --git a/v3/pkg/application/TODO.md b/v3/pkg/application/TODO.md new file mode 100644 index 000000000..ae4700425 --- /dev/null +++ b/v3/pkg/application/TODO.md @@ -0,0 +1,10 @@ + +Features +- [ ] AssetServer +- [ ] Offline page if navigating to external URL +- [x] Application menu + +Bugs +- [ ] Resize Window +- [ ] Fullscreen/Maximise/Minimise/Restore + diff --git a/v3/pkg/application/application.go b/v3/pkg/application/application.go new file mode 100644 index 000000000..8ffea8087 --- /dev/null +++ b/v3/pkg/application/application.go @@ -0,0 +1,824 @@ +package application + +import ( + "embed" + "encoding/json" + "github.com/pkg/browser" + "io" + "log" + "log/slog" + "net/http" + "os" + "runtime" + "strconv" + "sync" + + "github.com/wailsapp/wails/v3/internal/capabilities" + + "github.com/wailsapp/wails/v3/pkg/icons" + + "github.com/samber/lo" + + "github.com/wailsapp/wails/v3/internal/assetserver" + "github.com/wailsapp/wails/v3/internal/assetserver/webview" + + wailsruntime "github.com/wailsapp/wails/v3/internal/runtime" + "github.com/wailsapp/wails/v3/pkg/events" +) + +//go:embed assets/* +var alphaAssets embed.FS + +var globalApplication *App + +// AlphaAssets is the default assets for the alpha application +var AlphaAssets = AssetOptions{ + FS: alphaAssets, +} + +func init() { + runtime.LockOSThread() +} + +type EventListener struct { + callback func(app *Event) +} + +func Get() *App { + return globalApplication +} + +func New(appOptions Options) *App { + if globalApplication != nil { + return globalApplication + } + + mergeApplicationDefaults(&appOptions) + + result := newApplication(appOptions) + globalApplication = result + + if result.Logger == nil { + if result.isDebugMode { + result.Logger = DefaultLogger(result.options.LogLevel) + } else { + result.Logger = slog.New(slog.NewTextHandler(io.Discard, nil)) + } + } + + result.logStartup() + result.logPlatformInfo() + + result.Events = NewWailsEventProcessor(result.dispatchEventToWindows) + + opts := &assetserver.Options{ + Assets: appOptions.Assets.FS, + Handler: appOptions.Assets.Handler, + Middleware: assetserver.Middleware(appOptions.Assets.Middleware), + ExternalURL: appOptions.Assets.ExternalURL, + } + + srv, err := assetserver.NewAssetServer(opts, false, result.Logger, wailsruntime.RuntimeAssetsBundle, result.isDebugMode, NewMessageProcessor(result.Logger)) + if err != nil { + result.fatal(err.Error()) + } + + // Pass through the capabilities + srv.GetCapabilities = func() []byte { + return globalApplication.capabilities.AsBytes() + } + + srv.GetFlags = func() []byte { + updatedOptions := result.impl.GetFlags(appOptions) + flags, err := json.Marshal(updatedOptions) + if err != nil { + log.Fatal("Invalid flags provided to application: ", err.Error()) + } + return flags + } + + result.assets = srv + + result.assets.LogDetails() + + result.bindings, err = NewBindings(appOptions.Bind, appOptions.BindAliases) + if err != nil { + globalApplication.fatal("Fatal error in application initialisation: " + err.Error()) + os.Exit(1) + } + + result.plugins = NewPluginManager(appOptions.Plugins, srv) + err = result.plugins.Init() + if err != nil { + result.Quit() + os.Exit(1) + } + + err = result.bindings.AddPlugins(appOptions.Plugins) + if err != nil { + globalApplication.fatal("Fatal error in application initialisation: " + err.Error()) + os.Exit(1) + } + + // Process keybindings + if result.options.KeyBindings != nil { + result.keyBindings = processKeyBindingOptions(result.options.KeyBindings) + } + + return result +} + +func mergeApplicationDefaults(o *Options) { + if o.Name == "" { + o.Name = "My Wails Application" + } + if o.Description == "" { + o.Description = "An application written using Wails" + } + if o.Icon == nil { + o.Icon = icons.ApplicationLightMode256 + } +} + +type ( + platformApp interface { + run() error + destroy() + setApplicationMenu(menu *Menu) + name() string + getCurrentWindowID() uint + showAboutDialog(name string, description string, icon []byte) + setIcon(icon []byte) + on(id uint) + dispatchOnMainThread(id uint) + hide() + show() + getPrimaryScreen() (*Screen, error) + getScreens() ([]*Screen, error) + GetFlags(options Options) map[string]any + isOnMainThread() bool + isDarkMode() bool + } + + runnable interface { + Run() + } +) + +func processPanicHandlerRecover() { + h := globalApplication.options.PanicHandler + if h == nil { + return + } + + if err := recover(); err != nil { + h(err) + } +} + +// Messages sent from javascript get routed here +type windowMessage struct { + windowId uint + message string +} + +var windowMessageBuffer = make(chan *windowMessage) + +type dragAndDropMessage struct { + windowId uint + filenames []string +} + +var windowDragAndDropBuffer = make(chan *dragAndDropMessage) + +func addDragAndDropMessage(windowId uint, filenames []string) { + windowDragAndDropBuffer <- &dragAndDropMessage{ + windowId: windowId, + filenames: filenames, + } +} + +var _ webview.Request = &webViewAssetRequest{} + +const webViewRequestHeaderWindowId = "x-wails-window-id" +const webViewRequestHeaderWindowName = "x-wails-window-name" + +type webViewAssetRequest struct { + webview.Request + windowId uint + windowName string +} + +var windowKeyEvents = make(chan *windowKeyEvent) + +type windowKeyEvent struct { + windowId uint + acceleratorString string +} + +func (r *webViewAssetRequest) Header() (http.Header, error) { + h, err := r.Request.Header() + if err != nil { + return nil, err + } + + hh := h.Clone() + hh.Set(webViewRequestHeaderWindowId, strconv.FormatUint(uint64(r.windowId), 10)) + return hh, nil +} + +var webviewRequests = make(chan *webViewAssetRequest) + +type eventHook struct { + callback func(event *Event) +} + +type App struct { + options Options + applicationEventListeners map[uint][]*EventListener + applicationEventListenersLock sync.RWMutex + applicationEventHooks map[uint][]*eventHook + applicationEventHooksLock sync.RWMutex + + // Windows + windows map[uint]Window + windowsLock sync.RWMutex + + // System Trays + systemTrays map[uint]*SystemTray + systemTraysLock sync.Mutex + systemTrayID uint + systemTrayIDLock sync.RWMutex + + // MenuItems + menuItems map[uint]*MenuItem + menuItemsLock sync.Mutex + + // Running + running bool + runLock sync.Mutex + pendingRun []runnable + + bindings *Bindings + plugins *PluginManager + + // platform app + impl platformApp + + // The main application menu + ApplicationMenu *Menu + + clipboard *Clipboard + Events *EventProcessor + Logger *slog.Logger + + contextMenus map[string]*Menu + contextMenusLock sync.Mutex + + assets *assetserver.AssetServer + startURL string + + // Hooks + windowCreatedCallbacks []func(window Window) + pid int + + // Capabilities + capabilities capabilities.Capabilities + isDebugMode bool + + // Keybindings + keyBindings map[string]func(window *WebviewWindow) +} + +func (a *App) init() { + a.applicationEventListeners = make(map[uint][]*EventListener) + a.windows = make(map[uint]Window) + a.systemTrays = make(map[uint]*SystemTray) + a.contextMenus = make(map[string]*Menu) + a.keyBindings = make(map[string]func(window *WebviewWindow)) + a.Logger = a.options.Logger + a.pid = os.Getpid() +} + +func (a *App) getSystemTrayID() uint { + a.systemTrayIDLock.Lock() + defer a.systemTrayIDLock.Unlock() + a.systemTrayID++ + return a.systemTrayID +} + +func (a *App) getWindowForID(id uint) Window { + a.windowsLock.RLock() + defer a.windowsLock.RUnlock() + return a.windows[id] +} + +func (a *App) deleteWindowByID(id uint) { + a.windowsLock.Lock() + defer a.windowsLock.Unlock() + delete(a.windows, id) +} + +func (a *App) Capabilities() capabilities.Capabilities { + return a.capabilities +} + +func (a *App) On(eventType events.ApplicationEventType, callback func(event *Event)) func() { + eventID := uint(eventType) + a.applicationEventListenersLock.Lock() + defer a.applicationEventListenersLock.Unlock() + listener := &EventListener{ + callback: callback, + } + a.applicationEventListeners[eventID] = append(a.applicationEventListeners[eventID], listener) + if a.impl != nil { + go a.impl.on(eventID) + } + + return func() { + // lock the map + a.applicationEventListenersLock.Lock() + defer a.applicationEventListenersLock.Unlock() + // Remove listener + a.applicationEventListeners[eventID] = lo.Without(a.applicationEventListeners[eventID], listener) + } +} + +// RegisterHook registers a hook for the given event type. Hooks are called before the event listeners and can cancel the event. +// The returned function can be called to remove the hook. +func (a *App) RegisterHook(eventType events.ApplicationEventType, callback func(event *Event)) func() { + eventID := uint(eventType) + a.applicationEventHooksLock.Lock() + defer a.applicationEventHooksLock.Unlock() + thisHook := &eventHook{ + callback: callback, + } + a.applicationEventHooks[eventID] = append(a.applicationEventHooks[eventID], thisHook) + + return func() { + a.applicationEventHooksLock.Lock() + a.applicationEventHooks[eventID] = lo.Without(a.applicationEventHooks[eventID], thisHook) + a.applicationEventHooksLock.Unlock() + } +} + +func (a *App) NewWebviewWindow() *WebviewWindow { + return a.NewWebviewWindowWithOptions(WebviewWindowOptions{}) +} + +func (a *App) GetPID() int { + return a.pid +} + +func (a *App) info(message string, args ...any) { + if a.Logger != nil { + go a.Logger.Info(message, args...) + } +} + +func (a *App) debug(message string, args ...any) { + if a.Logger != nil { + go a.Logger.Debug(message, args...) + } +} + +func (a *App) fatal(message string, args ...any) { + msg := "A FATAL ERROR HAS OCCURRED: " + message + if a.Logger != nil { + go a.Logger.Error(msg, args...) + } else { + println(msg) + } + os.Exit(1) +} + +func (a *App) error(message string, args ...any) { + if a.Logger != nil { + go a.Logger.Error(message, args...) + } +} + +func (a *App) NewWebviewWindowWithOptions(windowOptions WebviewWindowOptions) *WebviewWindow { + newWindow := NewWindow(windowOptions) + id := newWindow.ID() + + a.windowsLock.Lock() + a.windows[id] = newWindow + a.windowsLock.Unlock() + + // Call hooks + for _, hook := range a.windowCreatedCallbacks { + hook(newWindow) + } + + a.runOrDeferToAppRun(newWindow) + + return newWindow +} + +func (a *App) NewSystemTray() *SystemTray { + id := a.getSystemTrayID() + newSystemTray := newSystemTray(id) + + a.systemTraysLock.Lock() + a.systemTrays[id] = newSystemTray + a.systemTraysLock.Unlock() + + a.runOrDeferToAppRun(newSystemTray) + + return newSystemTray +} + +func (a *App) Run() error { + + // Setup panic handler + defer processPanicHandlerRecover() + + a.impl = newPlatformApp(a) + go func() { + for { + event := <-applicationEvents + a.handleApplicationEvent(event) + } + }() + go func() { + for { + event := <-windowEvents + a.handleWindowEvent(event) + } + }() + go func() { + for { + request := <-webviewRequests + a.handleWebViewRequest(request) + } + }() + go func() { + for { + event := <-windowMessageBuffer + a.handleWindowMessage(event) + } + }() + go func() { + for { + event := <-windowKeyEvents + a.handleWindowKeyEvent(event) + } + }() + go func() { + for { + dragAndDropMessage := <-windowDragAndDropBuffer + a.handleDragAndDropMessage(dragAndDropMessage) + } + }() + + go func() { + for { + menuItemID := <-menuItemClicked + a.handleMenuItemClicked(menuItemID) + } + }() + + a.runLock.Lock() + a.running = true + + for _, systray := range a.pendingRun { + go systray.Run() + } + a.pendingRun = nil + + a.runLock.Unlock() + + // set the application menu + if runtime.GOOS == "darwin" { + a.impl.setApplicationMenu(a.ApplicationMenu) + } + a.impl.setIcon(a.options.Icon) + + err := a.impl.run() + if err != nil { + return err + } + + a.plugins.Shutdown() + + return nil +} + +func (a *App) handleApplicationEvent(event *Event) { + a.applicationEventListenersLock.RLock() + listeners, ok := a.applicationEventListeners[event.Id] + a.applicationEventListenersLock.RUnlock() + if !ok { + return + } + + // Process Hooks + a.applicationEventHooksLock.RLock() + hooks, ok := a.applicationEventHooks[event.Id] + a.applicationEventHooksLock.RUnlock() + if ok { + for _, thisHook := range hooks { + thisHook.callback(event) + if event.Cancelled { + return + } + } + } + + for _, listener := range listeners { + go listener.callback(event) + } +} + +func (a *App) handleDragAndDropMessage(event *dragAndDropMessage) { + // Get window from window map + a.windowsLock.Lock() + window, ok := a.windows[event.windowId] + a.windowsLock.Unlock() + if !ok { + log.Printf("WebviewWindow #%d not found", event.windowId) + return + } + // Get callback from window + window.HandleDragAndDropMessage(event.filenames) +} + +func (a *App) handleWindowMessage(event *windowMessage) { + // Get window from window map + a.windowsLock.RLock() + window, ok := a.windows[event.windowId] + a.windowsLock.RUnlock() + if !ok { + log.Printf("WebviewWindow #%d not found", event.windowId) + return + } + // Get callback from window + window.HandleMessage(event.message) +} + +func (a *App) handleWebViewRequest(request *webViewAssetRequest) { + a.assets.ServeWebViewRequest(request) +} + +func (a *App) handleWindowEvent(event *windowEvent) { + // Get window from window map + a.windowsLock.RLock() + window, ok := a.windows[event.WindowID] + a.windowsLock.RUnlock() + if !ok { + log.Printf("Window #%d not found", event.WindowID) + return + } + window.HandleWindowEvent(event.EventID) +} + +func (a *App) handleMenuItemClicked(menuItemID uint) { + menuItem := getMenuItemByID(menuItemID) + if menuItem == nil { + log.Printf("MenuItem #%d not found", menuItemID) + return + } + menuItem.handleClick() +} + +func (a *App) CurrentWindow() *WebviewWindow { + if a.impl == nil { + return nil + } + id := a.impl.getCurrentWindowID() + a.windowsLock.RLock() + defer a.windowsLock.RUnlock() + result := a.windows[id] + if result == nil { + return nil + } + return result.(*WebviewWindow) +} + +func (a *App) Quit() { + InvokeSync(func() { + a.windowsLock.RLock() + for _, window := range a.windows { + window.Destroy() + } + a.windowsLock.RUnlock() + a.systemTraysLock.Lock() + for _, systray := range a.systemTrays { + systray.Destroy() + } + a.systemTraysLock.Unlock() + if a.impl != nil { + a.impl.destroy() + } + }) +} + +func (a *App) SetMenu(menu *Menu) { + a.ApplicationMenu = menu + if a.impl != nil { + a.impl.setApplicationMenu(menu) + } +} +func (a *App) ShowAboutDialog() { + if a.impl != nil { + a.impl.showAboutDialog(a.options.Name, a.options.Description, a.options.Icon) + } +} + +func InfoDialog() *MessageDialog { + return newMessageDialog(InfoDialogType) +} + +func QuestionDialog() *MessageDialog { + return newMessageDialog(QuestionDialogType) +} + +func WarningDialog() *MessageDialog { + return newMessageDialog(WarningDialogType) +} + +func ErrorDialog() *MessageDialog { + return newMessageDialog(ErrorDialogType) +} + +func OpenDirectoryDialog() *MessageDialog { + return newMessageDialog(OpenDirectoryDialogType) +} + +func OpenFileDialog() *OpenFileDialogStruct { + return newOpenFileDialog() +} + +func SaveFileDialog() *SaveFileDialogStruct { + return newSaveFileDialog() +} + +func (a *App) GetPrimaryScreen() (*Screen, error) { + return a.impl.getPrimaryScreen() +} + +func (a *App) GetScreens() ([]*Screen, error) { + return a.impl.getScreens() +} + +func (a *App) Clipboard() *Clipboard { + if a.clipboard == nil { + a.clipboard = newClipboard() + } + return a.clipboard +} + +func (a *App) dispatchOnMainThread(fn func()) { + // If we are on the main thread, just call the function + if a.impl.isOnMainThread() { + fn() + return + } + + mainThreadFunctionStoreLock.Lock() + id := generateFunctionStoreID() + mainThreadFunctionStore[id] = fn + mainThreadFunctionStoreLock.Unlock() + // Call platform specific dispatch function + a.impl.dispatchOnMainThread(id) +} + +func OpenFileDialogWithOptions(options *OpenFileDialogOptions) *OpenFileDialogStruct { + result := OpenFileDialog() + result.SetOptions(options) + return result +} + +func SaveFileDialogWithOptions(s *SaveFileDialogOptions) *SaveFileDialogStruct { + result := SaveFileDialog() + result.SetOptions(s) + return result +} + +func (a *App) dispatchEventToWindows(event *WailsEvent) { + for _, window := range a.windows { + window.DispatchWailsEvent(event) + } +} + +func (a *App) IsDarkMode() bool { + if a.impl == nil { + return false + } + return a.impl.isDarkMode() +} + +func (a *App) Hide() { + if a.impl != nil { + a.impl.hide() + } +} + +func (a *App) Show() { + if a.impl != nil { + a.impl.show() + } +} + +func (a *App) RegisterContextMenu(name string, menu *Menu) { + a.contextMenusLock.Lock() + defer a.contextMenusLock.Unlock() + a.contextMenus[name] = menu +} + +func (a *App) getContextMenu(name string) (*Menu, bool) { + a.contextMenusLock.Lock() + defer a.contextMenusLock.Unlock() + menu, ok := a.contextMenus[name] + return menu, ok + +} + +func (a *App) OnWindowCreation(callback func(window Window)) { + a.windowCreatedCallbacks = append(a.windowCreatedCallbacks, callback) +} + +func (a *App) GetWindowByName(name string) Window { + a.windowsLock.RLock() + defer a.windowsLock.RUnlock() + for _, window := range a.windows { + if window.Name() == name { + return window + } + } + return nil +} + +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() + } +} + +func (a *App) processKeyBinding(acceleratorString string, window *WebviewWindow) bool { + + if a.keyBindings == nil { + return false + } + + // Check key bindings + callback, ok := a.keyBindings[acceleratorString] + if !ok { + return false + } + + // Execute callback + go callback(window) + + return true +} + +func (a *App) handleWindowKeyEvent(event *windowKeyEvent) { + // Get window from window map + a.windowsLock.RLock() + window, ok := a.windows[event.windowId] + a.windowsLock.RUnlock() + if !ok { + log.Printf("WebviewWindow #%d not found", event.windowId) + return + } + // Get callback from window + window.HandleKeyEvent(event.acceleratorString) +} + +func (a *App) AssetServerHandler() func(rw http.ResponseWriter, req *http.Request) { + return a.assets.ServeHTTP +} + +func (a *App) RegisterWindow(window Window) uint { + id := getWindowID() + if a.windows == nil { + a.windows = make(map[uint]Window) + } + a.windowsLock.Lock() + defer a.windowsLock.Unlock() + a.windows[id] = window + return id +} + +func (a *App) UnregisterWindow(id uint) { + a.windowsLock.Lock() + defer a.windowsLock.Unlock() + delete(a.windows, id) +} + +func (a *App) BrowserOpenURL(url string) error { + return browser.OpenURL(url) +} + +func (a *App) BrowserOpenFile(path string) error { + return browser.OpenFile(path) +} diff --git a/v3/pkg/application/application_darwin.go b/v3/pkg/application/application_darwin.go new file mode 100644 index 000000000..32fee5f6b --- /dev/null +++ b/v3/pkg/application/application_darwin.go @@ -0,0 +1,346 @@ +//go:build darwin + +package application + +/* + +#cgo CFLAGS: -mmacosx-version-min=10.13 -x objective-c +#cgo LDFLAGS: -framework Cocoa -mmacosx-version-min=10.13 + +#include "application_darwin.h" +#include "application_darwin_delegate.h" +#include "webview_window_darwin.h" +#include + +extern void registerListener(unsigned int event); + +#import + +static AppDelegate *appDelegate = nil; + +static void init(void) { + [NSApplication sharedApplication]; + appDelegate = [[AppDelegate alloc] init]; + [NSApp setDelegate:appDelegate]; + + [NSEvent addLocalMonitorForEventsMatchingMask:NSEventMaskLeftMouseDown handler:^NSEvent * _Nullable(NSEvent * _Nonnull event) { + NSWindow* eventWindow = [event window]; + if (eventWindow == nil ) { + return event; + } + WebviewWindowDelegate* windowDelegate = (WebviewWindowDelegate*)[eventWindow delegate]; + if (windowDelegate == nil) { + return event; + } + if ([windowDelegate respondsToSelector:@selector(handleLeftMouseDown:)]) { + [windowDelegate handleLeftMouseDown:event]; + } + return event; + }]; + + [NSEvent addLocalMonitorForEventsMatchingMask:NSEventMaskLeftMouseUp handler:^NSEvent * _Nullable(NSEvent * _Nonnull event) { + NSWindow* eventWindow = [event window]; + if (eventWindow == nil ) { + return event; + } + WebviewWindowDelegate* windowDelegate = (WebviewWindowDelegate*)[eventWindow delegate]; + if (windowDelegate == nil) { + return event; + } + if ([windowDelegate respondsToSelector:@selector(handleLeftMouseUp:)]) { + [windowDelegate handleLeftMouseUp:eventWindow]; + } + return event; + }]; + + NSDistributedNotificationCenter *center = [NSDistributedNotificationCenter defaultCenter]; + [center addObserver:appDelegate selector:@selector(themeChanged:) name:@"AppleInterfaceThemeChangedNotification" object:nil]; + +} + +static bool isDarkMode(void) { + NSUserDefaults* userDefaults = [NSUserDefaults standardUserDefaults]; + if (userDefaults == nil) { + return false; + } + + NSString *interfaceStyle = [userDefaults stringForKey:@"AppleInterfaceStyle"]; + if (interfaceStyle == nil) { + return false; + } + + return [interfaceStyle isEqualToString:@"Dark"]; +} + +static void setApplicationShouldTerminateAfterLastWindowClosed(bool shouldTerminate) { + // Get the NSApp delegate + AppDelegate *appDelegate = (AppDelegate*)[NSApp delegate]; + // Set the applicationShouldTerminateAfterLastWindowClosed boolean + appDelegate.shouldTerminateWhenLastWindowClosed = shouldTerminate; +} + +static void setActivationPolicy(int policy) { + [NSApp setActivationPolicy:policy]; +} + +static void activateIgnoringOtherApps() { + [NSApp activateIgnoringOtherApps:YES]; +} + +static void run(void) { + @autoreleasepool { + [NSApp run]; + [appDelegate release]; + } +} + +// Destroy application +static void destroyApp(void) { + [NSApp terminate:nil]; +} + +// Set the application menu +static void setApplicationMenu(void *menu) { + NSMenu *nsMenu = (__bridge NSMenu *)menu; + [NSApp setMainMenu:menu]; +} + +// Get the application name +static char* getAppName(void) { + NSString *appName = [NSRunningApplication currentApplication].localizedName; + if( appName == nil ) { + appName = [[NSProcessInfo processInfo] processName]; + } + return strdup([appName UTF8String]); +} + +// get the current window ID +static unsigned int getCurrentWindowID(void) { + NSWindow *window = [NSApp keyWindow]; + // Get the window delegate + WebviewWindowDelegate *delegate = (WebviewWindowDelegate*)[window delegate]; + return delegate.windowId; +} + +// Set the application icon +static void setApplicationIcon(void *icon, int length) { + // On main thread + dispatch_async(dispatch_get_main_queue(), ^{ + NSImage *image = [[NSImage alloc] initWithData:[NSData dataWithBytes:icon length:length]]; + [NSApp setApplicationIconImage:image]; + }); +} + +// Hide the application +static void hide(void) { + [NSApp hide:nil]; +} + +// Show the application +static void show(void) { + [NSApp unhide:nil]; +} + +static const char* serializationNSDictionary(void *dict) { + @autoreleasepool { + NSDictionary *nsDict = (__bridge NSDictionary *)dict; + + if ([NSJSONSerialization isValidJSONObject:nsDict]) { + NSError *error; + NSData *data = [NSJSONSerialization dataWithJSONObject:nsDict options:kNilOptions error:&error]; + NSString *result = [[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding]; + + return strdup([result UTF8String]); + } + } + + return nil; +} +*/ +import "C" +import ( + "encoding/json" + "unsafe" + + "github.com/wailsapp/wails/v3/internal/operatingsystem" + + "github.com/wailsapp/wails/v3/internal/assetserver/webview" + "github.com/wailsapp/wails/v3/pkg/events" +) + +type macosApp struct { + applicationMenu unsafe.Pointer + parent *App +} + +func (m *macosApp) isDarkMode() bool { + return bool(C.isDarkMode()) +} + +func getNativeApplication() *macosApp { + return globalApplication.impl.(*macosApp) +} + +func (m *macosApp) hide() { + C.hide() +} + +func (m *macosApp) show() { + C.show() +} + +func (m *macosApp) on(eventID uint) { + C.registerListener(C.uint(eventID)) +} + +func (m *macosApp) setIcon(icon []byte) { + C.setApplicationIcon(unsafe.Pointer(&icon[0]), C.int(len(icon))) +} + +func (m *macosApp) name() string { + appName := C.getAppName() + defer C.free(unsafe.Pointer(appName)) + return C.GoString(appName) +} + +func (m *macosApp) getCurrentWindowID() uint { + return uint(C.getCurrentWindowID()) +} + +func (m *macosApp) setApplicationMenu(menu *Menu) { + if menu == nil { + // Create a default menu for mac + menu = defaultApplicationMenu() + } + menu.Update() + + // Convert impl to macosMenu object + m.applicationMenu = (menu.impl).(*macosMenu).nsMenu + C.setApplicationMenu(m.applicationMenu) +} + +func (m *macosApp) run() error { + // Add a hook to the ApplicationDidFinishLaunching event + m.parent.On(events.Mac.ApplicationDidFinishLaunching, func(*Event) { + C.setApplicationShouldTerminateAfterLastWindowClosed(C.bool(m.parent.options.Mac.ApplicationShouldTerminateAfterLastWindowClosed)) + C.setActivationPolicy(C.int(m.parent.options.Mac.ActivationPolicy)) + C.activateIgnoringOtherApps() + }) + m.setupCommonEvents() + // setup event listeners + for eventID := range m.parent.applicationEventListeners { + m.on(eventID) + } + C.run() + return nil +} + +func (m *macosApp) destroy() { + C.destroyApp() +} + +func (m *macosApp) GetFlags(options Options) map[string]any { + if options.Flags == nil { + options.Flags = make(map[string]any) + } + return options.Flags +} + +func newPlatformApp(app *App) *macosApp { + C.init() + return &macosApp{ + parent: app, + } +} + +//export processApplicationEvent +func processApplicationEvent(eventID C.uint, data unsafe.Pointer) { + event := newApplicationEvent(int(eventID)) + + if data != nil { + dataCStrJSON := C.serializationNSDictionary(data) + if dataCStrJSON != nil { + defer C.free(unsafe.Pointer(dataCStrJSON)) + + dataJSON := C.GoString(dataCStrJSON) + var result map[string]any + err := json.Unmarshal([]byte(dataJSON), &result) + + if err != nil { + panic(err) + } + + event.Context().setData(result) + } + } + + switch event.Id { + case uint(events.Mac.ApplicationDidChangeTheme): + isDark := globalApplication.IsDarkMode() + event.Context().setIsDarkMode(isDark) + } + applicationEvents <- event +} + +//export processWindowEvent +func processWindowEvent(windowID C.uint, eventID C.uint) { + windowEvents <- &windowEvent{ + WindowID: uint(windowID), + EventID: uint(eventID), + } +} + +//export processMessage +func processMessage(windowID C.uint, message *C.char) { + windowMessageBuffer <- &windowMessage{ + windowId: uint(windowID), + message: C.GoString(message), + } +} + +//export processURLRequest +func processURLRequest(windowID C.uint, wkUrlSchemeTask unsafe.Pointer) { + webviewRequests <- &webViewAssetRequest{ + Request: webview.NewRequest(wkUrlSchemeTask), + windowId: uint(windowID), + windowName: globalApplication.getWindowForID(uint(windowID)).Name(), + } +} + +//export processWindowKeyDownEvent +func processWindowKeyDownEvent(windowID C.uint, acceleratorString *C.char) { + windowKeyEvents <- &windowKeyEvent{ + windowId: uint(windowID), + acceleratorString: C.GoString(acceleratorString), + } +} + +//export processDragItems +func processDragItems(windowID C.uint, arr **C.char, length C.int) { + var filenames []string + // Convert the C array to a Go slice + goSlice := (*[1 << 30]*C.char)(unsafe.Pointer(arr))[:length:length] + for _, str := range goSlice { + filenames = append(filenames, C.GoString(str)) + } + windowDragAndDropBuffer <- &dragAndDropMessage{ + windowId: uint(windowID), + filenames: filenames, + } +} + +//export processMenuItemClick +func processMenuItemClick(menuID C.uint) { + menuItemClicked <- uint(menuID) +} + +func (a *App) logPlatformInfo() { + info, err := operatingsystem.Info() + if err != nil { + a.error("Error getting OS info", "error", err.Error()) + return + } + + a.info("Platform Info:", info.AsLogSlice()...) + +} diff --git a/v3/pkg/application/application_darwin.h b/v3/pkg/application/application_darwin.h new file mode 100644 index 000000000..e67384397 --- /dev/null +++ b/v3/pkg/application/application_darwin.h @@ -0,0 +1,11 @@ +//go:build darwin + +#ifndef application_h +#define application_h + +static void init(void); +static void run(void); +static void setActivationPolicy(int policy); +static char *getAppName(void); + +#endif \ No newline at end of file diff --git a/v3/pkg/application/application_darwin_delegate.h b/v3/pkg/application/application_darwin_delegate.h new file mode 100644 index 000000000..9c11eb8a1 --- /dev/null +++ b/v3/pkg/application/application_darwin_delegate.h @@ -0,0 +1,13 @@ +//go:build darwin + +#ifndef appdelegate_h +#define appdelegate_h + +#import + +@interface AppDelegate : NSObject +@property bool shouldTerminateWhenLastWindowClosed; +- (BOOL)applicationSupportsSecureRestorableState:(NSApplication *)app; +@end + +#endif diff --git a/v3/pkg/application/application_darwin_delegate.m b/v3/pkg/application/application_darwin_delegate.m new file mode 100644 index 000000000..99825c7db --- /dev/null +++ b/v3/pkg/application/application_darwin_delegate.m @@ -0,0 +1,156 @@ +//go:build darwin +#import "application_darwin_delegate.h" +#import "../events/events_darwin.h" +extern bool hasListeners(unsigned int); +@implementation AppDelegate +- (void)dealloc +{ + [super dealloc]; +} +// Create the applicationShouldTerminateAfterLastWindowClosed: method +- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)theApplication +{ + return self.shouldTerminateWhenLastWindowClosed; +} +- (void)themeChanged:(NSNotification *)notification { + if( hasListeners(EventApplicationDidChangeTheme) ) { + processApplicationEvent(EventApplicationDidChangeTheme, NULL); + } +} + +- (BOOL)applicationSupportsSecureRestorableState:(NSApplication *)app +{ + return YES; +} + +- (BOOL)applicationShouldHandleReopen:(NSNotification *)notification + hasVisibleWindows:(BOOL)flag { + if( hasListeners(EventApplicationShouldHandleReopen) ) { + processApplicationEvent(EventApplicationShouldHandleReopen, @{@"hasVisibleWindows": @(flag)}); + } + + return TRUE; +} +// GENERATED EVENTS START +- (void)applicationDidBecomeActive:(NSNotification *)notification { + if( hasListeners(EventApplicationDidBecomeActive) ) { + processApplicationEvent(EventApplicationDidBecomeActive, NULL); + } +} + +- (void)applicationDidChangeBackingProperties:(NSNotification *)notification { + if( hasListeners(EventApplicationDidChangeBackingProperties) ) { + processApplicationEvent(EventApplicationDidChangeBackingProperties, NULL); + } +} + +- (void)applicationDidChangeEffectiveAppearance:(NSNotification *)notification { + if( hasListeners(EventApplicationDidChangeEffectiveAppearance) ) { + processApplicationEvent(EventApplicationDidChangeEffectiveAppearance, NULL); + } +} + +- (void)applicationDidChangeIcon:(NSNotification *)notification { + if( hasListeners(EventApplicationDidChangeIcon) ) { + processApplicationEvent(EventApplicationDidChangeIcon, NULL); + } +} + +- (void)applicationDidChangeOcclusionState:(NSNotification *)notification { + if( hasListeners(EventApplicationDidChangeOcclusionState) ) { + processApplicationEvent(EventApplicationDidChangeOcclusionState, NULL); + } +} + +- (void)applicationDidChangeScreenParameters:(NSNotification *)notification { + if( hasListeners(EventApplicationDidChangeScreenParameters) ) { + processApplicationEvent(EventApplicationDidChangeScreenParameters, NULL); + } +} + +- (void)applicationDidChangeStatusBarFrame:(NSNotification *)notification { + if( hasListeners(EventApplicationDidChangeStatusBarFrame) ) { + processApplicationEvent(EventApplicationDidChangeStatusBarFrame, NULL); + } +} + +- (void)applicationDidChangeStatusBarOrientation:(NSNotification *)notification { + if( hasListeners(EventApplicationDidChangeStatusBarOrientation) ) { + processApplicationEvent(EventApplicationDidChangeStatusBarOrientation, NULL); + } +} + +- (void)applicationDidFinishLaunching:(NSNotification *)notification { + if( hasListeners(EventApplicationDidFinishLaunching) ) { + processApplicationEvent(EventApplicationDidFinishLaunching, NULL); + } +} + +- (void)applicationDidHide:(NSNotification *)notification { + if( hasListeners(EventApplicationDidHide) ) { + processApplicationEvent(EventApplicationDidHide, NULL); + } +} + +- (void)applicationDidResignActiveNotification:(NSNotification *)notification { + if( hasListeners(EventApplicationDidResignActiveNotification) ) { + processApplicationEvent(EventApplicationDidResignActiveNotification, NULL); + } +} + +- (void)applicationDidUnhide:(NSNotification *)notification { + if( hasListeners(EventApplicationDidUnhide) ) { + processApplicationEvent(EventApplicationDidUnhide, NULL); + } +} + +- (void)applicationDidUpdate:(NSNotification *)notification { + if( hasListeners(EventApplicationDidUpdate) ) { + processApplicationEvent(EventApplicationDidUpdate, NULL); + } +} + +- (void)applicationWillBecomeActive:(NSNotification *)notification { + if( hasListeners(EventApplicationWillBecomeActive) ) { + processApplicationEvent(EventApplicationWillBecomeActive, NULL); + } +} + +- (void)applicationWillFinishLaunching:(NSNotification *)notification { + if( hasListeners(EventApplicationWillFinishLaunching) ) { + processApplicationEvent(EventApplicationWillFinishLaunching, NULL); + } +} + +- (void)applicationWillHide:(NSNotification *)notification { + if( hasListeners(EventApplicationWillHide) ) { + processApplicationEvent(EventApplicationWillHide, NULL); + } +} + +- (void)applicationWillResignActive:(NSNotification *)notification { + if( hasListeners(EventApplicationWillResignActive) ) { + processApplicationEvent(EventApplicationWillResignActive, NULL); + } +} + +- (void)applicationWillTerminate:(NSNotification *)notification { + if( hasListeners(EventApplicationWillTerminate) ) { + processApplicationEvent(EventApplicationWillTerminate, NULL); + } +} + +- (void)applicationWillUnhide:(NSNotification *)notification { + if( hasListeners(EventApplicationWillUnhide) ) { + processApplicationEvent(EventApplicationWillUnhide, NULL); + } +} + +- (void)applicationWillUpdate:(NSNotification *)notification { + if( hasListeners(EventApplicationWillUpdate) ) { + processApplicationEvent(EventApplicationWillUpdate, NULL); + } +} + +// GENERATED EVENTS END +@end diff --git a/v3/pkg/application/application_debug.go b/v3/pkg/application/application_debug.go new file mode 100644 index 000000000..52e2b185d --- /dev/null +++ b/v3/pkg/application/application_debug.go @@ -0,0 +1,66 @@ +//go:build !production + +package application + +import ( + "github.com/go-git/go-git/v5" + "github.com/samber/lo" + "github.com/wailsapp/wails/v3/internal/version" + "path/filepath" + "runtime/debug" +) + +// BuildSettings contains the build settings for the application +var BuildSettings map[string]string + +// BuildInfo contains the build info for the application +var BuildInfo *debug.BuildInfo + +func init() { + var ok bool + BuildInfo, ok = debug.ReadBuildInfo() + if !ok { + return + } + BuildSettings = lo.Associate(BuildInfo.Settings, func(setting debug.BuildSetting) (string, string) { + return setting.Key, setting.Value + }) +} + +// We use this to patch the application to production mode. +func newApplication(options Options) *App { + result := &App{ + isDebugMode: true, + options: options, + } + result.init() + return result +} + +func (a *App) logStartup() { + var args []any + + wailsPackage, _ := lo.Find(BuildInfo.Deps, func(dep *debug.Module) bool { + return dep.Path == "github.com/wailsapp/wails/v3" + }) + + wailsVersion := version.VersionString + if wailsPackage != nil && wailsPackage.Replace != nil { + wailsVersion = "(local) => " + filepath.ToSlash(wailsPackage.Replace.Path) + // Get the latest commit hash + repo, err := git.PlainOpen(filepath.Join(wailsPackage.Replace.Path, "..")) + if err == nil { + head, err := repo.Head() + if err == nil { + wailsVersion += " (" + head.Hash().String()[:8] + ")" + } + } + } + args = append(args, "Wails", wailsVersion) + args = append(args, "Compiler", BuildInfo.GoVersion) + for key, value := range BuildSettings { + args = append(args, key, value) + } + + a.info("Build Info:", args...) +} diff --git a/v3/pkg/application/application_linux.go b/v3/pkg/application/application_linux.go new file mode 100644 index 000000000..17dc6a7cd --- /dev/null +++ b/v3/pkg/application/application_linux.go @@ -0,0 +1,199 @@ +//go:build linux + +package application + +import ( + "fmt" + "log" + "os" + "strings" + "sync" + + "github.com/wailsapp/wails/v3/pkg/events" +) + +func init() { + // FIXME: This should be handled appropriately in the individual files most likely. + // Set GDK_BACKEND=x11 if currently unset and XDG_SESSION_TYPE is unset, unspecified or x11 to prevent warnings + _ = os.Setenv("GDK_BACKEND", "x11") +} + +type linuxApp struct { + application pointer + applicationMenu pointer + parent *App + + startupActions []func() + + // Native -> uint + windows map[windowPointer]uint + windowsLock sync.Mutex +} + +func (m *linuxApp) GetFlags(options Options) map[string]any { + if options.Flags == nil { + options.Flags = make(map[string]any) + } + return options.Flags +} + +func getNativeApplication() *linuxApp { + return globalApplication.impl.(*linuxApp) +} + +func (m *linuxApp) hide() { + hideAllWindows(m.application) +} + +func (m *linuxApp) show() { + showAllWindows(m.application) +} + +func (m *linuxApp) on(eventID uint) { + // TODO: What do we need to do here? + // log.Println("linuxApp.on()", eventID) +} + +func (m *linuxApp) setIcon(icon []byte) { + + log.Println("linuxApp.setIcon", "not implemented") +} + +func (m *linuxApp) name() string { + return appName() +} + +func (m *linuxApp) getCurrentWindowID() uint { + return getCurrentWindowID(m.application, m.windows) +} + +type rnr struct { + f func() +} + +func (r rnr) run() { + r.f() +} + +func (m *linuxApp) getApplicationMenu() pointer { + if m.applicationMenu != nilPointer { + return m.applicationMenu + } + + menu := globalApplication.ApplicationMenu + if menu != nil { + InvokeSync(func() { + menu.Update() + }) + m.applicationMenu = (menu.impl).(*linuxMenu).native + } + return m.applicationMenu +} + +func (m *linuxApp) setApplicationMenu(menu *Menu) { + // FIXME: How do we avoid putting a menu? + if menu == nil { + // Create a default menu + menu = defaultApplicationMenu() + globalApplication.ApplicationMenu = menu + } +} + +func (m *linuxApp) run() error { + + // Add a hook to the ApplicationDidFinishLaunching event + // FIXME: add Wails specific events - i.e. Shouldn't platform specific ones be translated to Wails events? + m.parent.On(events.Mac.ApplicationDidFinishLaunching, func(evt *Event) { + // Do we need to do anything now? + fmt.Println("events.Mac.ApplicationDidFinishLaunching received!") + }) + + return appRun(m.application) +} + +func (m *linuxApp) destroy() { + appDestroy(m.application) +} + +func (m *linuxApp) isOnMainThread() bool { + return isOnMainThread() +} + +// register our window to our parent mapping +func (m *linuxApp) registerWindow(window pointer, id uint) { + m.windowsLock.Lock() + m.windows[windowPointer(window)] = id + m.windowsLock.Unlock() +} + +func (m *linuxApp) isDarkMode() bool { + // FIXME: How do we detect this? + // Maybe this helps: https://askubuntu.com/questions/1469869/how-does-firefox-detect-light-dark-theme-change-on-kde-systems + return false +} + +func newPlatformApp(parent *App) *linuxApp { + name := strings.ToLower(strings.Replace(parent.options.Name, " ", "", -1)) + if name == "" { + name = "undefined" + } + app := &linuxApp{ + parent: parent, + application: appNew(name), + windows: map[windowPointer]uint{}, + } + return app +} + +/* +//export processApplicationEvent +func processApplicationEvent(eventID C.uint) { + // TODO: add translation to Wails events + // currently reusing Mac specific values + applicationEvents <- uint(eventID) +} + +//export processWindowEvent +func processWindowEvent(windowID C.uint, eventID C.uint) { + windowEvents <- &windowEvent{ + WindowID: uint(windowID), + EventID: uint(eventID), + } +} + +//export processMessage +func processMessage(windowID C.uint, message *C.char) { + windowMessageBuffer <- &windowMessage{ + windowId: uint(windowID), + message: C.GoString(message), + } +} + +//export processDragItems +func processDragItems(windowID C.uint, arr **C.char, length C.int) { + var filenames []string + // Convert the C array to a Go slice + goSlice := (*[1 << 30]*C.char)(unsafe.Pointer(arr))[:length:length] + for _, str := range goSlice { + filenames = append(filenames, C.GoString(str)) + } + windowDragAndDropBuffer <- &dragAndDropMessage{ + windowId: uint(windowID), + filenames: filenames, + } +} + +//export processMenuItemClick +func processMenuItemClick(menuID identifier) { + menuItemClicked <- uint(menuID) +} + +func setIcon(icon []byte) { + if icon == nil { + return + } + //C.setApplicationIcon(unsafe.Pointer(&icon[0]), C.int(len(icon))) +} +*/ + +func (a *App) logPlatformInfo() {} diff --git a/v3/pkg/application/application_production.go b/v3/pkg/application/application_production.go new file mode 100644 index 000000000..7b4b2e6c6 --- /dev/null +++ b/v3/pkg/application/application_production.go @@ -0,0 +1,14 @@ +//go:build production + +package application + +func newApplication(options Options) *App { + result := &App{ + isDebugMode: false, + options: options, + } + result.init() + return result +} + +func (a *App) logStartup() {} diff --git a/v3/pkg/application/application_windows.go b/v3/pkg/application/application_windows.go new file mode 100644 index 000000000..4bdc166a6 --- /dev/null +++ b/v3/pkg/application/application_windows.go @@ -0,0 +1,347 @@ +//go:build windows + +package application + +import ( + "fmt" + "github.com/wailsapp/go-webview2/webviewloader" + "github.com/wailsapp/wails/v3/internal/operatingsystem" + "golang.org/x/sys/windows" + "os" + "strconv" + "sync" + "syscall" + "unsafe" + + "github.com/wailsapp/wails/v3/pkg/events" + "github.com/wailsapp/wails/v3/pkg/w32" + + "github.com/samber/lo" +) + +var windowClassName = lo.Must(syscall.UTF16PtrFromString("WailsWebviewWindow")) + +type windowsApp struct { + parent *App + + windowClass w32.WNDCLASSEX + instance w32.HINSTANCE + + windowMap map[w32.HWND]*windowsWebviewWindow + windowMapLock sync.RWMutex + + systrayMap map[w32.HMENU]*windowsSystemTray + systrayMapLock sync.RWMutex + + mainThreadID w32.HANDLE + mainThreadWindowHWND w32.HWND + + // Windows hidden by application.Hide() + hiddenWindows []*windowsWebviewWindow + focusedWindow w32.HWND + + // system theme + isCurrentlyDarkMode bool + currentWindowID uint +} + +func (m *windowsApp) isDarkMode() bool { + return w32.IsCurrentlyDarkMode() +} + +func (m *windowsApp) isOnMainThread() bool { + return m.mainThreadID == w32.GetCurrentThreadId() +} + +func (m *windowsApp) GetFlags(options Options) map[string]any { + if options.Flags == nil { + options.Flags = make(map[string]any) + } + options.Flags["system"] = map[string]any{ + "resizeHandleWidth": w32.GetSystemMetrics(w32.SM_CXSIZEFRAME), + "resizeHandleHeight": w32.GetSystemMetrics(w32.SM_CYSIZEFRAME), + } + return options.Flags +} + +func (m *windowsApp) getWindowForHWND(hwnd w32.HWND) *windowsWebviewWindow { + m.windowMapLock.RLock() + defer m.windowMapLock.RUnlock() + return m.windowMap[hwnd] +} + +func getNativeApplication() *windowsApp { + return globalApplication.impl.(*windowsApp) +} + +func (m *windowsApp) getPrimaryScreen() (*Screen, error) { + screens, err := m.getScreens() + if err != nil { + return nil, err + } + for _, screen := range screens { + if screen.IsPrimary { + return screen, nil + } + } + return nil, fmt.Errorf("no primary screen found") +} + +func (m *windowsApp) getScreens() ([]*Screen, error) { + allScreens, err := w32.GetAllScreens() + if err != nil { + return nil, err + } + // Convert result to []*Screen + screens := make([]*Screen, len(allScreens)) + for id, screen := range allScreens { + x := int(screen.MONITORINFOEX.RcMonitor.Left) + y := int(screen.MONITORINFOEX.RcMonitor.Top) + right := int(screen.MONITORINFOEX.RcMonitor.Right) + bottom := int(screen.MONITORINFOEX.RcMonitor.Bottom) + width := right - x + height := bottom - y + screens[id] = &Screen{ + ID: strconv.Itoa(id), + Name: windows.UTF16ToString(screen.MONITORINFOEX.SzDevice[:]), + X: x, + Y: y, + Size: Size{Width: width, Height: height}, + Bounds: Rect{X: x, Y: y, Width: width, Height: height}, + WorkArea: Rect{ + X: int(screen.MONITORINFOEX.RcWork.Left), + Y: int(screen.MONITORINFOEX.RcWork.Top), + Width: int(screen.MONITORINFOEX.RcWork.Right - screen.MONITORINFOEX.RcWork.Left), + Height: int(screen.MONITORINFOEX.RcWork.Bottom - screen.MONITORINFOEX.RcWork.Top), + }, + IsPrimary: screen.IsPrimary, + Scale: screen.Scale, + Rotation: 0, + } + } + return screens, nil +} + +func (m *windowsApp) hide() { + // Get the current focussed window + m.focusedWindow = w32.GetForegroundWindow() + + // Iterate over all windows and hide them if they aren't already hidden + for _, window := range m.windowMap { + if window.isVisible() { + // Add to hidden windows + m.hiddenWindows = append(m.hiddenWindows, window) + window.hide() + } + } + // Switch focus to the next application + hwndNext := w32.GetWindow(m.mainThreadWindowHWND, w32.GW_HWNDNEXT) + w32.SetForegroundWindow(hwndNext) +} + +func (m *windowsApp) show() { + // Iterate over all windows and show them if they were previously hidden + for _, window := range m.hiddenWindows { + window.show() + } + // Show the foreground window + w32.SetForegroundWindow(m.focusedWindow) +} + +func (m *windowsApp) on(_ uint) { +} + +func (m *windowsApp) setIcon(_ []byte) { +} + +func (m *windowsApp) name() string { + //appName := C.getAppName() + //defer C.free(unsafe.Pointer(appName)) + //return C.GoString(appName) + return "" +} + +func (m *windowsApp) getCurrentWindowID() uint { + return m.currentWindowID +} + +func (m *windowsApp) setApplicationMenu(menu *Menu) { + if menu == nil { + // Create a default menu for windows + menu = defaultApplicationMenu() + } + menu.Update() + + m.parent.ApplicationMenu = menu +} + +func (m *windowsApp) run() error { + m.setupCommonEvents() + for eventID := range m.parent.applicationEventListeners { + m.on(eventID) + } + // Emit application started event + applicationEvents <- &Event{ + Id: uint(events.Windows.ApplicationStarted), + ctx: blankApplicationEventContext, + } + _ = m.runMainLoop() + + return nil +} + +func (m *windowsApp) destroy() { + // Post a quit message to the main thread + w32.PostQuitMessage(0) +} + +func (m *windowsApp) init() { + // Register the window class + + icon := w32.LoadIconWithResourceID(m.instance, w32.IDI_APPLICATION) + + m.windowClass.Size = uint32(unsafe.Sizeof(m.windowClass)) + m.windowClass.Style = w32.CS_HREDRAW | w32.CS_VREDRAW + m.windowClass.WndProc = syscall.NewCallback(m.wndProc) + m.windowClass.Instance = m.instance + m.windowClass.Background = w32.COLOR_BTNFACE + 1 + m.windowClass.Icon = icon + m.windowClass.Cursor = w32.LoadCursorWithResourceID(0, w32.IDC_ARROW) + m.windowClass.ClassName = windowClassName + m.windowClass.MenuName = nil + m.windowClass.IconSm = icon + + if ret := w32.RegisterClassEx(&m.windowClass); ret == 0 { + panic(syscall.GetLastError()) + } + m.isCurrentlyDarkMode = w32.IsCurrentlyDarkMode() +} + +func (m *windowsApp) wndProc(hwnd w32.HWND, msg uint32, wParam, lParam uintptr) uintptr { + + // Handle the invoke callback + if msg == wmInvokeCallback { + m.invokeCallback(wParam, lParam) + return 0 + } + + // If the WndProcInterceptor is set in options, pass the message on + if m.parent.options.Windows.WndProcInterceptor != nil { + returnValue, shouldReturn := m.parent.options.Windows.WndProcInterceptor(hwnd, msg, wParam, lParam) + if shouldReturn { + return returnValue + } + } + + switch msg { + case w32.WM_SETTINGCHANGE: + settingChanged := w32.UTF16PtrToString((*uint16)(unsafe.Pointer(lParam))) + if settingChanged == "ImmersiveColorSet" { + isDarkMode := w32.IsCurrentlyDarkMode() + if isDarkMode != m.isCurrentlyDarkMode { + eventContext := newApplicationEventContext() + eventContext.setIsDarkMode(isDarkMode) + applicationEvents <- &Event{ + Id: uint(events.Windows.SystemThemeChanged), + ctx: eventContext, + } + m.isCurrentlyDarkMode = isDarkMode + } + } + return 0 + case w32.WM_POWERBROADCAST: + switch wParam { + case w32.PBT_APMPOWERSTATUSCHANGE: + applicationEvents <- newApplicationEvent(int(events.Windows.APMPowerStatusChange)) + case w32.PBT_APMSUSPEND: + applicationEvents <- newApplicationEvent(int(events.Windows.APMSuspend)) + case w32.PBT_APMRESUMEAUTOMATIC: + applicationEvents <- newApplicationEvent(int(events.Windows.APMResumeAutomatic)) + case w32.PBT_APMRESUMESUSPEND: + applicationEvents <- newApplicationEvent(int(events.Windows.APMResumeSuspend)) + case w32.PBT_POWERSETTINGCHANGE: + applicationEvents <- newApplicationEvent(int(events.Windows.APMPowerSettingChange)) + } + return 0 + } + + if window, ok := m.windowMap[hwnd]; ok { + return window.WndProc(msg, wParam, lParam) + } + + m.systrayMapLock.Lock() + systray, ok := m.systrayMap[hwnd] + m.systrayMapLock.Unlock() + if ok { + return systray.wndProc(msg, wParam, lParam) + } + + // Dispatch the message to the appropriate window + + return w32.DefWindowProc(hwnd, msg, wParam, lParam) +} + +func (m *windowsApp) registerWindow(result *windowsWebviewWindow) { + m.windowMapLock.Lock() + m.windowMap[result.hwnd] = result + m.windowMapLock.Unlock() +} + +func (m *windowsApp) registerSystemTray(result *windowsSystemTray) { + m.systrayMapLock.Lock() + defer m.systrayMapLock.Unlock() + m.systrayMap[result.hwnd] = result +} + +func (m *windowsApp) unregisterSystemTray(result *windowsSystemTray) { + m.systrayMapLock.Lock() + defer m.systrayMapLock.Unlock() + delete(m.systrayMap, result.hwnd) +} + +func (m *windowsApp) unregisterWindow(w *windowsWebviewWindow) { + m.windowMapLock.Lock() + delete(m.windowMap, w.hwnd) + m.windowMapLock.Unlock() + + // If this was the last window... + if len(m.windowMap) == 0 && !m.parent.options.Windows.DisableQuitOnLastWindowClosed { + w32.PostQuitMessage(0) + } +} + +func newPlatformApp(app *App) *windowsApp { + err := w32.SetProcessDPIAware() + if err != nil { + globalApplication.fatal("Fatal error in application initialisation: ", err.Error()) + os.Exit(1) + } + + result := &windowsApp{ + parent: app, + instance: w32.GetModuleHandle(""), + windowMap: make(map[w32.HWND]*windowsWebviewWindow), + systrayMap: make(map[w32.HWND]*windowsSystemTray), + } + + result.init() + result.initMainLoop() + + return result +} + +func (a *App) logPlatformInfo() { + var args []any + args = append(args, "Go-WebView2Loader", webviewloader.UsingGoWebview2Loader) + webviewVersion, err := webviewloader.GetAvailableCoreWebView2BrowserVersionString(a.options.Windows.WebviewBrowserPath) + if err != nil { + args = append(args, "WebView2", "Error: "+err.Error()) + } else { + args = append(args, "WebView2", webviewVersion) + } + + osInfo, _ := operatingsystem.Info() + args = append(args, osInfo.AsLogSlice()...) + + a.info("Platform Info:", args...) +} diff --git a/v3/pkg/application/assets/alpha/index.html b/v3/pkg/application/assets/alpha/index.html new file mode 100644 index 000000000..76621b342 --- /dev/null +++ b/v3/pkg/application/assets/alpha/index.html @@ -0,0 +1,79 @@ + + + + + Wails Alpha + + + + +
Alpha
+
+

Documentation

+

Feedback

+
+ + \ No newline at end of file diff --git a/v3/pkg/application/bindings.go b/v3/pkg/application/bindings.go new file mode 100644 index 000000000..21f763450 --- /dev/null +++ b/v3/pkg/application/bindings.go @@ -0,0 +1,392 @@ +package application + +import ( + "fmt" + "reflect" + "runtime" + "strings" + + "github.com/wailsapp/wails/v3/internal/hash" + + "github.com/samber/lo" +) + +type CallOptions struct { + MethodID uint32 `json:"methodID"` + PackageName string `json:"packageName"` + StructName string `json:"structName"` + MethodName string `json:"methodName"` + Args []any `json:"args"` +} + +func (c CallOptions) Name() string { + return fmt.Sprintf("%s.%s.%s", c.PackageName, c.StructName, c.MethodName) +} + +type PluginCallOptions struct { + Name string `json:"name"` + Args []any `json:"args"` +} + +var reservedPluginMethods = []string{ + "Name", + "Init", + "Shutdown", + "Exported", +} + +// Parameter defines a Go method parameter +type Parameter struct { + Name string `json:"name,omitempty"` + TypeName string `json:"type"` + ReflectType reflect.Type +} + +func newParameter(Name string, Type reflect.Type) *Parameter { + return &Parameter{ + Name: Name, + TypeName: Type.String(), + ReflectType: Type, + } +} + +// IsType returns true if the given +func (p *Parameter) IsType(typename string) bool { + return p.TypeName == typename +} + +// IsError returns true if the parameter type is an error +func (p *Parameter) IsError() bool { + return p.IsType("error") +} + +// 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:"-"` + PackageName string + StructName string + PackagePath string +} + +type Bindings struct { + boundMethods map[string]map[string]map[string]*BoundMethod + boundByID map[uint32]*BoundMethod + methodAliases map[uint32]uint32 +} + +func NewBindings(structs []any, aliases map[uint32]uint32) (*Bindings, error) { + b := &Bindings{ + boundMethods: make(map[string]map[string]map[string]*BoundMethod), + boundByID: make(map[uint32]*BoundMethod), + methodAliases: aliases, + } + for _, binding := range structs { + err := b.Add(binding) + if err != nil { + return nil, err + } + } + return b, nil +} + +// Add the given struct methods to the Bindings +func (b *Bindings) Add(structPtr interface{}) error { + + methods, err := b.getMethods(structPtr, false) + if err != nil { + return fmt.Errorf("cannot bind value to app: %s", err.Error()) + } + + for _, method := range methods { + packageName := method.PackageName + structName := method.StructName + methodName := method.Name + + // Add it as a regular method + if _, ok := b.boundMethods[packageName]; !ok { + b.boundMethods[packageName] = make(map[string]map[string]*BoundMethod) + } + if _, ok := b.boundMethods[packageName][structName]; !ok { + b.boundMethods[packageName][structName] = make(map[string]*BoundMethod) + } + b.boundMethods[packageName][structName][methodName] = method + b.boundByID[method.ID] = method + } + return nil +} + +func (b *Bindings) AddPlugins(plugins map[string]Plugin) error { + for pluginID, plugin := range plugins { + methods, err := b.getMethods(plugin, true) + if err != nil { + return fmt.Errorf("cannot add plugin '%s' to app: %s", pluginID, err.Error()) + } + + exportedMethods := plugin.CallableByJS() + + for _, method := range methods { + // Do not expose reserved methods + if lo.Contains(reservedPluginMethods, method.Name) { + continue + } + // Do not expose methods that are not in the exported list + if !lo.Contains(exportedMethods, method.Name) { + continue + } + packageName := "wails-plugins" + structName := pluginID + methodName := method.Name + + // Add it as a regular method + if _, ok := b.boundMethods[packageName]; !ok { + b.boundMethods[packageName] = make(map[string]map[string]*BoundMethod) + } + if _, ok := b.boundMethods[packageName][structName]; !ok { + b.boundMethods[packageName][structName] = make(map[string]*BoundMethod) + } + b.boundMethods[packageName][structName][methodName] = method + b.boundByID[method.ID] = method + globalApplication.debug("Added plugin method: "+structName+"."+methodName, "id", method.ID) + } + } + return nil +} + +// Get returns the bound method with the given name +func (b *Bindings) Get(options *CallOptions) *BoundMethod { + _, ok := b.boundMethods[options.PackageName] + if !ok { + return nil + } + _, ok = b.boundMethods[options.PackageName][options.StructName] + if !ok { + return nil + } + method, ok := b.boundMethods[options.PackageName][options.StructName][options.MethodName] + if !ok { + return nil + } + return method +} + +// GetByID returns the bound method with the given ID +func (b *Bindings) GetByID(id uint32) *BoundMethod { + // Check method aliases + if b.methodAliases != nil { + if alias, ok := b.methodAliases[id]; ok { + id = alias + } + } + result := b.boundByID[id] + return result +} + +// 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 +} + +func (b *BoundMethod) String() string { + return fmt.Sprintf("%s.%s.%s", b.PackageName, b.StructName, b.Name) +} + +func (b *Bindings) getMethods(value interface{}, isPlugin bool) ([]*BoundMethod, error) { + + // Create result placeholder + var result []*BoundMethod + + // Check type + if !isStructPtr(value) { + + if isStruct(value) { + name := reflect.ValueOf(value).Type().Name() + return nil, fmt.Errorf("%s is a struct, not a pointer to a struct", name) + } + + if isFunction(value) { + name := runtime.FuncForPC(reflect.ValueOf(value).Pointer()).Name() + return nil, fmt.Errorf("%s is a function, not a pointer to a struct. Wails v2 has deprecated the binding of functions. Please wrap your functions up in a struct and bind a pointer to that struct", name) + } + + return nil, fmt.Errorf("not a pointer to a struct") + } + + // Process Struct + structType := reflect.TypeOf(value) + structValue := reflect.ValueOf(value) + structTypeString := structType.String() + baseName := structTypeString[1:] + + // Process Methods + for i := 0; i < structType.NumMethod(); i++ { + methodDef := structType.Method(i) + methodName := methodDef.Name + packageName, structName, _ := strings.Cut(baseName, ".") + method := structValue.MethodByName(methodName) + packagePath, _ := lo.Coalesce(structType.PkgPath(), "main") + + // Create new method + boundMethod := &BoundMethod{ + Name: methodName, + PackageName: packageName, + PackagePath: packagePath, + StructName: structName, + Inputs: nil, + Outputs: nil, + Comments: "", + Method: method, + } + var err error + boundMethod.ID, err = hash.Fnv(boundMethod.String()) + if err != nil { + return nil, err + } + + if !isPlugin { + 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() + inputParamCount := methodType.NumIn() + var inputs []*Parameter + for inputIndex := 0; inputIndex < inputParamCount; inputIndex++ { + input := methodType.In(inputIndex) + thisParam := newParameter("", input) + inputs = append(inputs, thisParam) + } + + boundMethod.Inputs = inputs + + outputParamCount := methodType.NumOut() + var outputs []*Parameter + for outputIndex := 0; outputIndex < outputParamCount; outputIndex++ { + output := methodType.Out(outputIndex) + thisParam := newParameter("", output) + outputs = append(outputs, thisParam) + } + boundMethod.Outputs = outputs + + // Save method in result + result = append(result, boundMethod) + + } + return result, nil +} + +// Call will attempt to call this bound method with the given args +func (b *BoundMethod) Call(args []interface{}) (returnValue interface{}, err error) { + + // Use a defer statement to capture panics + defer func() { + if r := recover(); r != nil { + if str, ok := r.(string); ok { + if strings.HasPrefix(str, "reflect: Call using") { + // Remove prefix + str = strings.Replace(str, "reflect: Call using ", "", 1) + // Split on "as" + parts := strings.Split(str, " as type ") + if len(parts) == 2 { + err = fmt.Errorf("invalid argument type: got '%s', expected '%s'", parts[0], parts[1]) + return + } + } + } + err = fmt.Errorf("%v", r) + } + }() + + // Check inputs + expectedInputLength := len(b.Inputs) + actualInputLength := len(args) + + // If the method is variadic, we need to check the minimum number of inputs + if b.Method.Type().IsVariadic() { + if actualInputLength < expectedInputLength-1 { + return nil, fmt.Errorf("%s takes at least %d inputs. Received %d", b.Name, expectedInputLength, actualInputLength) + } + } else { + if expectedInputLength != actualInputLength { + return nil, fmt.Errorf("%s takes %d inputs. Received %d", b.Name, expectedInputLength, actualInputLength) + } + } + + /** Convert inputs to reflect values **/ + + // Create slice for the input arguments to the method call + callArgs := make([]reflect.Value, actualInputLength) + + // Iterate over given arguments + for index, arg := range args { + // Save the converted argument + if arg == nil { + callArgs[index] = reflect.Zero(b.Inputs[index].ReflectType) + continue + } + callArgs[index] = reflect.ValueOf(arg) + } + + // Do the call + callResults := b.Method.Call(callArgs) + + //** Check results **// + switch len(b.Outputs) { + case 1: + // Loop over results and determine if the result + // is an error or not + for _, result := range callResults { + interfac := result.Interface() + temp, ok := interfac.(error) + if ok { + err = temp + } else { + returnValue = interfac + } + } + case 2: + returnValue = callResults[0].Interface() + if temp, ok := callResults[1].Interface().(error); ok { + err = temp + } + } + + return returnValue, err +} + +// isStructPtr returns true if the value given is a +// pointer to a struct +func isStructPtr(value interface{}) bool { + return reflect.ValueOf(value).Kind() == reflect.Ptr && + reflect.ValueOf(value).Elem().Kind() == reflect.Struct +} + +// isFunction returns true if the given value is a function +func isFunction(value interface{}) bool { + return reflect.ValueOf(value).Kind() == reflect.Func +} + +// isStructPtr returns true if the value given is a struct +func isStruct(value interface{}) bool { + return reflect.ValueOf(value).Kind() == reflect.Struct +} diff --git a/v3/pkg/application/clipboard.go b/v3/pkg/application/clipboard.go new file mode 100644 index 000000000..f21b597e2 --- /dev/null +++ b/v3/pkg/application/clipboard.go @@ -0,0 +1,26 @@ +package application + +type clipboardImpl interface { + setText(text string) bool + text() (string, bool) +} + +type Clipboard struct { + impl clipboardImpl +} + +func newClipboard() *Clipboard { + return &Clipboard{ + impl: newClipboardImpl(), + } +} + +func (c *Clipboard) SetText(text string) bool { + return InvokeSyncWithResult(func() bool { + return c.impl.setText(text) + }) +} + +func (c *Clipboard) Text() (string, bool) { + return InvokeSyncWithResultAndOther(c.impl.text) +} diff --git a/v3/pkg/application/clipboard_darwin.go b/v3/pkg/application/clipboard_darwin.go new file mode 100644 index 000000000..3c0c80ac6 --- /dev/null +++ b/v3/pkg/application/clipboard_darwin.go @@ -0,0 +1,56 @@ +//go:build darwin + +package application + +/* +#cgo CFLAGS: -mmacosx-version-min=10.13 -x objective-c +#cgo LDFLAGS: -framework Cocoa -mmacosx-version-min=10.13 + +#import +#import + +bool setClipboardText(const char* text) { + NSPasteboard *pasteBoard = [NSPasteboard generalPasteboard]; + NSError *error = nil; + NSString *string = [NSString stringWithUTF8String:text]; + [pasteBoard clearContents]; + return [pasteBoard setString:string forType:NSPasteboardTypeString]; +} + +const char* getClipboardText() { + NSPasteboard *pasteboard = [NSPasteboard generalPasteboard]; + NSString *text = [pasteboard stringForType:NSPasteboardTypeString]; + return [text UTF8String]; +} + +*/ +import "C" +import ( + "sync" + "unsafe" +) + +var clipboardLock sync.RWMutex + +type macosClipboard struct{} + +func (m macosClipboard) setText(text string) bool { + clipboardLock.Lock() + defer clipboardLock.Unlock() + cText := C.CString(text) + success := C.setClipboardText(cText) + C.free(unsafe.Pointer(cText)) + return bool(success) +} + +func (m macosClipboard) text() (string, bool) { + clipboardLock.RLock() + defer clipboardLock.RUnlock() + clipboardText := C.getClipboardText() + result := C.GoString(clipboardText) + return result, true +} + +func newClipboardImpl() *macosClipboard { + return &macosClipboard{} +} diff --git a/v3/pkg/application/clipboard_linux.go b/v3/pkg/application/clipboard_linux.go new file mode 100644 index 000000000..1c662cd6f --- /dev/null +++ b/v3/pkg/application/clipboard_linux.go @@ -0,0 +1,28 @@ +//go:build linux + +package application + +import ( + "sync" +) + +var clipboardLock sync.RWMutex + +type linuxClipboard struct{} + +func (m linuxClipboard) setText(text string) bool { + clipboardLock.Lock() + defer clipboardLock.Unlock() + clipboardSet(text) + return true +} + +func (m linuxClipboard) text() (string, bool) { + clipboardLock.RLock() + defer clipboardLock.RUnlock() + return clipboardGet(), true +} + +func newClipboardImpl() *linuxClipboard { + return &linuxClipboard{} +} diff --git a/v3/pkg/application/clipboard_windows.go b/v3/pkg/application/clipboard_windows.go new file mode 100644 index 000000000..507eac3da --- /dev/null +++ b/v3/pkg/application/clipboard_windows.go @@ -0,0 +1,29 @@ +//go:build windows + +package application + +import ( + "github.com/wailsapp/wails/v3/pkg/w32" + "sync" +) + +type windowsClipboard struct { + lock sync.RWMutex +} + +func (m *windowsClipboard) setText(text string) bool { + m.lock.Lock() + defer m.lock.Unlock() + return w32.SetClipboardText(text) == nil +} + +func (m *windowsClipboard) text() (string, bool) { + m.lock.Lock() + defer m.lock.Unlock() + text, err := w32.GetClipboardText() + return text, err == nil +} + +func newClipboardImpl() *windowsClipboard { + return &windowsClipboard{} +} diff --git a/v3/pkg/application/context.go b/v3/pkg/application/context.go new file mode 100644 index 000000000..56b213350 --- /dev/null +++ b/v3/pkg/application/context.go @@ -0,0 +1,54 @@ +package application + +type Context struct { + // contains filtered or unexported fields + data map[string]any +} + +func newContext() *Context { + return &Context{ + data: make(map[string]any), + } +} + +const ( + clickedMenuItem string = "clickedMenuItem" + menuItemIsChecked string = "menuItemIsChecked" + contextMenuData string = "contextMenuData" +) + +func (c *Context) ClickedMenuItem() *MenuItem { + result, exists := c.data[clickedMenuItem] + if !exists { + return nil + } + return result.(*MenuItem) +} + +func (c *Context) IsChecked() bool { + result, exists := c.data[menuItemIsChecked] + if !exists { + return false + } + return result.(bool) +} +func (c *Context) ContextMenuData() any { + return c.data[contextMenuData] +} + +func (c *Context) withClickedMenuItem(menuItem *MenuItem) *Context { + c.data[clickedMenuItem] = menuItem + return c +} + +func (c *Context) withChecked(checked bool) { + c.data[menuItemIsChecked] = checked +} + +func (c *Context) withContextMenuData(data *ContextMenuData) *Context { + if data == nil { + return c + } + c.data[contextMenuData] = data.Data + return c +} diff --git a/v3/pkg/application/context_application_event.go b/v3/pkg/application/context_application_event.go new file mode 100644 index 000000000..2cd1ca2c0 --- /dev/null +++ b/v3/pkg/application/context_application_event.go @@ -0,0 +1,62 @@ +package application + +var blankApplicationEventContext = &ApplicationEventContext{} + +const ( + openedFiles = "openedFiles" +) + +type ApplicationEventContext struct { + // contains filtered or unexported fields + data map[string]any +} + +func (c ApplicationEventContext) OpenedFiles() []string { + files, ok := c.data[openedFiles] + if !ok { + return nil + } + result, ok := files.([]string) + if !ok { + return nil + } + return result +} + +func (c ApplicationEventContext) setOpenedFiles(files []string) { + c.data[openedFiles] = files +} + +func (c ApplicationEventContext) setIsDarkMode(mode bool) { + c.data["isDarkMode"] = mode +} + +func (c ApplicationEventContext) getBool(key string) bool { + mode, ok := c.data[key] + if !ok { + return false + } + result, ok := mode.(bool) + if !ok { + return false + } + return result +} + +func (c ApplicationEventContext) IsDarkMode() bool { + return c.getBool("isDarkMode") +} + +func (c ApplicationEventContext) HasVisibleWindows() bool { + return c.getBool("hasVisibleWindows") +} + +func (c ApplicationEventContext) setData(data map[string]any) { + c.data = data +} + +func newApplicationEventContext() *ApplicationEventContext { + return &ApplicationEventContext{ + data: make(map[string]any), + } +} diff --git a/v3/pkg/application/context_window_event.go b/v3/pkg/application/context_window_event.go new file mode 100644 index 000000000..f5063b19d --- /dev/null +++ b/v3/pkg/application/context_window_event.go @@ -0,0 +1,34 @@ +package application + +var blankWindowEventContext = &WindowEventContext{} + +const ( + droppedFiles = "droppedFiles" +) + +type WindowEventContext struct { + // contains filtered or unexported fields + data map[string]any +} + +func (c WindowEventContext) DroppedFiles() []string { + files, ok := c.data[droppedFiles] + if !ok { + return nil + } + result, ok := files.([]string) + if !ok { + return nil + } + return result +} + +func (c WindowEventContext) setDroppedFiles(files []string) { + c.data[droppedFiles] = files +} + +func newWindowEventContext() *WindowEventContext { + return &WindowEventContext{ + data: make(map[string]any), + } +} diff --git a/v3/pkg/application/dialogs.go b/v3/pkg/application/dialogs.go new file mode 100644 index 000000000..bd54059f3 --- /dev/null +++ b/v3/pkg/application/dialogs.go @@ -0,0 +1,495 @@ +package application + +import ( + "fmt" + "strings" + "sync" +) + +type DialogType int + +var dialogMapID = make(map[uint]struct{}) +var dialogIDLock sync.RWMutex + +func getDialogID() uint { + dialogIDLock.Lock() + defer dialogIDLock.Unlock() + var dialogID uint + for { + if _, ok := dialogMapID[dialogID]; !ok { + dialogMapID[dialogID] = struct{}{} + break + } + dialogID++ + if dialogID == 0 { + panic("no more dialog IDs") + } + } + return dialogID +} + +func freeDialogID(id uint) { + dialogIDLock.Lock() + defer dialogIDLock.Unlock() + delete(dialogMapID, id) +} + +var openFileResponses = make(map[uint]chan string) +var saveFileResponses = make(map[uint]chan string) + +const ( + InfoDialogType DialogType = iota + QuestionDialogType + WarningDialogType + ErrorDialogType + OpenDirectoryDialogType +) + +type Button struct { + Label string + IsCancel bool + IsDefault bool + Callback func() +} + +func (b *Button) OnClick(callback func()) *Button { + b.Callback = callback + return b +} + +func (b *Button) SetAsDefault() *Button { + b.IsDefault = true + return b +} + +func (b *Button) SetAsCancel() *Button { + b.IsCancel = true + return b +} + +type messageDialogImpl interface { + show() +} + +type MessageDialogOptions struct { + DialogType DialogType + Title string + Message string + Buttons []*Button + Icon []byte + window *WebviewWindow +} + +type MessageDialog struct { + MessageDialogOptions + + // platform independent + impl messageDialogImpl +} + +var defaultTitles = map[DialogType]string{ + InfoDialogType: "Information", + QuestionDialogType: "Question", + WarningDialogType: "Warning", + ErrorDialogType: "Error", +} + +func newMessageDialog(dialogType DialogType) *MessageDialog { + return &MessageDialog{ + MessageDialogOptions: MessageDialogOptions{ + DialogType: dialogType, + }, + impl: nil, + } +} + +func (d *MessageDialog) SetTitle(title string) *MessageDialog { + d.Title = title + return d +} + +func (d *MessageDialog) Show() { + if d.impl == nil { + d.impl = newDialogImpl(d) + } + InvokeSync(d.impl.show) +} + +func (d *MessageDialog) SetIcon(icon []byte) *MessageDialog { + d.Icon = icon + return d +} + +func (d *MessageDialog) AddButton(s string) *Button { + result := &Button{ + Label: s, + } + d.Buttons = append(d.Buttons, result) + return result +} + +func (d *MessageDialog) AddButtons(buttons []*Button) *MessageDialog { + d.Buttons = buttons + return d +} + +func (d *MessageDialog) AttachToWindow(window Window) *MessageDialog { + d.window = window.(*WebviewWindow) + return d +} + +func (d *MessageDialog) SetDefaultButton(button *Button) *MessageDialog { + for _, b := range d.Buttons { + b.IsDefault = false + } + button.IsDefault = true + return d +} + +func (d *MessageDialog) SetCancelButton(button *Button) *MessageDialog { + for _, b := range d.Buttons { + b.IsCancel = false + } + button.IsCancel = true + return d +} + +func (d *MessageDialog) SetMessage(message string) *MessageDialog { + d.Message = message + return d +} + +type openFileDialogImpl interface { + show() (chan string, error) +} + +type FileFilter struct { + DisplayName string // Filter information EG: "Image Files (*.jpg, *.png)" + Pattern string // semicolon separated list of extensions, EG: "*.jpg;*.png" +} + +type OpenFileDialogOptions struct { + CanChooseDirectories bool + CanChooseFiles bool + CanCreateDirectories bool + ShowHiddenFiles bool + ResolvesAliases bool + AllowsMultipleSelection bool + HideExtension bool + CanSelectHiddenExtension bool + TreatsFilePackagesAsDirectories bool + AllowsOtherFileTypes bool + Filters []FileFilter + Window *WebviewWindow + + Title string + Message string + ButtonText string + Directory string +} + +type OpenFileDialogStruct struct { + id uint + canChooseDirectories bool + canChooseFiles bool + canCreateDirectories bool + showHiddenFiles bool + resolvesAliases bool + allowsMultipleSelection bool + hideExtension bool + canSelectHiddenExtension bool + treatsFilePackagesAsDirectories bool + allowsOtherFileTypes bool + filters []FileFilter + + title string + message string + buttonText string + directory string + window *WebviewWindow + + impl openFileDialogImpl +} + +func (d *OpenFileDialogStruct) CanChooseFiles(canChooseFiles bool) *OpenFileDialogStruct { + d.canChooseFiles = canChooseFiles + return d +} + +func (d *OpenFileDialogStruct) CanChooseDirectories(canChooseDirectories bool) *OpenFileDialogStruct { + d.canChooseDirectories = canChooseDirectories + return d +} + +func (d *OpenFileDialogStruct) CanCreateDirectories(canCreateDirectories bool) *OpenFileDialogStruct { + d.canCreateDirectories = canCreateDirectories + return d +} + +func (d *OpenFileDialogStruct) AllowsOtherFileTypes(allowsOtherFileTypes bool) *OpenFileDialogStruct { + d.allowsOtherFileTypes = allowsOtherFileTypes + return d +} + +func (d *OpenFileDialogStruct) ShowHiddenFiles(showHiddenFiles bool) *OpenFileDialogStruct { + d.showHiddenFiles = showHiddenFiles + return d +} + +func (d *OpenFileDialogStruct) HideExtension(hideExtension bool) *OpenFileDialogStruct { + d.hideExtension = hideExtension + return d +} + +func (d *OpenFileDialogStruct) TreatsFilePackagesAsDirectories(treatsFilePackagesAsDirectories bool) *OpenFileDialogStruct { + d.treatsFilePackagesAsDirectories = treatsFilePackagesAsDirectories + return d +} + +func (d *OpenFileDialogStruct) AttachToWindow(window Window) *OpenFileDialogStruct { + d.window = window.(*WebviewWindow) + return d +} + +func (d *OpenFileDialogStruct) ResolvesAliases(resolvesAliases bool) *OpenFileDialogStruct { + d.resolvesAliases = resolvesAliases + return d +} + +func (d *OpenFileDialogStruct) SetTitle(title string) *OpenFileDialogStruct { + d.title = title + return d +} + +func (d *OpenFileDialogStruct) PromptForSingleSelection() (string, error) { + d.allowsMultipleSelection = false + if d.impl == nil { + d.impl = newOpenFileDialogImpl(d) + } + + var result string + selections, err := InvokeSyncWithResultAndError(d.impl.show) + if err == nil { + result = <-selections + } + + return result, err +} + +// AddFilter adds a filter to the dialog. The filter is a display name and a semicolon separated list of extensions. +// EG: AddFilter("Image Files", "*.jpg;*.png") +func (d *OpenFileDialogStruct) AddFilter(displayName, pattern string) *OpenFileDialogStruct { + d.filters = append(d.filters, FileFilter{ + DisplayName: strings.TrimSpace(displayName), + Pattern: strings.TrimSpace(pattern), + }) + return d +} + +func (d *OpenFileDialogStruct) PromptForMultipleSelection() ([]string, error) { + d.allowsMultipleSelection = true + if d.impl == nil { + d.impl = newOpenFileDialogImpl(d) + } + + selections, err := InvokeSyncWithResultAndError(d.impl.show) + + var result []string + fmt.Println("Waiting for results:") + for filename := range selections { + fmt.Println(filename) + result = append(result, filename) + } + + return result, err +} + +func (d *OpenFileDialogStruct) SetMessage(message string) *OpenFileDialogStruct { + d.message = message + return d +} + +func (d *OpenFileDialogStruct) SetButtonText(text string) *OpenFileDialogStruct { + d.buttonText = text + return d +} + +func (d *OpenFileDialogStruct) SetDirectory(directory string) *OpenFileDialogStruct { + d.directory = directory + return d +} + +func (d *OpenFileDialogStruct) CanSelectHiddenExtension(canSelectHiddenExtension bool) *OpenFileDialogStruct { + d.canSelectHiddenExtension = canSelectHiddenExtension + return d +} + +func (d *OpenFileDialogStruct) SetOptions(options *OpenFileDialogOptions) { + d.title = options.Title + d.message = options.Message + d.buttonText = options.ButtonText + d.directory = options.Directory + d.canChooseDirectories = options.CanChooseDirectories + d.canChooseFiles = options.CanChooseFiles + d.canCreateDirectories = options.CanCreateDirectories + d.showHiddenFiles = options.ShowHiddenFiles + d.resolvesAliases = options.ResolvesAliases + d.allowsMultipleSelection = options.AllowsMultipleSelection + d.hideExtension = options.HideExtension + d.canSelectHiddenExtension = options.CanSelectHiddenExtension + d.treatsFilePackagesAsDirectories = options.TreatsFilePackagesAsDirectories + d.allowsOtherFileTypes = options.AllowsOtherFileTypes + d.filters = options.Filters + d.window = options.Window +} + +func newOpenFileDialog() *OpenFileDialogStruct { + return &OpenFileDialogStruct{ + id: getDialogID(), + canChooseDirectories: false, + canChooseFiles: true, + canCreateDirectories: true, + resolvesAliases: false, + } +} + +func newSaveFileDialog() *SaveFileDialogStruct { + return &SaveFileDialogStruct{ + id: getDialogID(), + canCreateDirectories: true, + } +} + +type SaveFileDialogOptions struct { + CanCreateDirectories bool + ShowHiddenFiles bool + CanSelectHiddenExtension bool + AllowOtherFileTypes bool + HideExtension bool + TreatsFilePackagesAsDirectories bool + Title string + Message string + Directory string + Filename string + ButtonText string + Filters []FileFilter + Window *WebviewWindow +} + +type SaveFileDialogStruct struct { + id uint + canCreateDirectories bool + showHiddenFiles bool + canSelectHiddenExtension bool + allowOtherFileTypes bool + hideExtension bool + treatsFilePackagesAsDirectories bool + message string + directory string + filename string + buttonText string + filters []FileFilter + + window *WebviewWindow + + impl saveFileDialogImpl + title string +} + +type saveFileDialogImpl interface { + show() (chan string, error) +} + +func (d *SaveFileDialogStruct) SetOptions(options *SaveFileDialogOptions) { + d.title = options.Title + d.canCreateDirectories = options.CanCreateDirectories + d.showHiddenFiles = options.ShowHiddenFiles + d.canSelectHiddenExtension = options.CanSelectHiddenExtension + d.allowOtherFileTypes = options.AllowOtherFileTypes + d.hideExtension = options.HideExtension + d.treatsFilePackagesAsDirectories = options.TreatsFilePackagesAsDirectories + d.message = options.Message + d.directory = options.Directory + d.filename = options.Filename + d.buttonText = options.ButtonText + d.filters = options.Filters + d.window = options.Window +} + +// AddFilter adds a filter to the dialog. The filter is a display name and a semicolon separated list of extensions. +// EG: AddFilter("Image Files", "*.jpg;*.png") +func (d *SaveFileDialogStruct) AddFilter(displayName, pattern string) *SaveFileDialogStruct { + d.filters = append(d.filters, FileFilter{ + DisplayName: strings.TrimSpace(displayName), + Pattern: strings.TrimSpace(pattern), + }) + return d +} + +func (d *SaveFileDialogStruct) CanCreateDirectories(canCreateDirectories bool) *SaveFileDialogStruct { + d.canCreateDirectories = canCreateDirectories + return d +} + +func (d *SaveFileDialogStruct) CanSelectHiddenExtension(canSelectHiddenExtension bool) *SaveFileDialogStruct { + d.canSelectHiddenExtension = canSelectHiddenExtension + return d +} + +func (d *SaveFileDialogStruct) ShowHiddenFiles(showHiddenFiles bool) *SaveFileDialogStruct { + d.showHiddenFiles = showHiddenFiles + return d +} + +func (d *SaveFileDialogStruct) SetMessage(message string) *SaveFileDialogStruct { + d.message = message + return d +} + +func (d *SaveFileDialogStruct) SetDirectory(directory string) *SaveFileDialogStruct { + d.directory = directory + return d +} + +func (d *SaveFileDialogStruct) AttachToWindow(window Window) *SaveFileDialogStruct { + d.window = window.(*WebviewWindow) + return d +} + +func (d *SaveFileDialogStruct) PromptForSingleSelection() (string, error) { + if d.impl == nil { + d.impl = newSaveFileDialogImpl(d) + } + + var result string + selections, err := InvokeSyncWithResultAndError(d.impl.show) + if err == nil { + result = <-selections + } + return result, err +} + +func (d *SaveFileDialogStruct) SetButtonText(text string) *SaveFileDialogStruct { + d.buttonText = text + return d +} + +func (d *SaveFileDialogStruct) SetFilename(filename string) *SaveFileDialogStruct { + d.filename = filename + return d +} + +func (d *SaveFileDialogStruct) AllowsOtherFileTypes(allowOtherFileTypes bool) *SaveFileDialogStruct { + d.allowOtherFileTypes = allowOtherFileTypes + return d +} + +func (d *SaveFileDialogStruct) HideExtension(hideExtension bool) *SaveFileDialogStruct { + d.hideExtension = hideExtension + return d +} + +func (d *SaveFileDialogStruct) TreatsFilePackagesAsDirectories(treatsFilePackagesAsDirectories bool) *SaveFileDialogStruct { + d.treatsFilePackagesAsDirectories = treatsFilePackagesAsDirectories + return d +} diff --git a/v3/pkg/application/dialogs_darwin.go b/v3/pkg/application/dialogs_darwin.go new file mode 100644 index 000000000..bdd7aa289 --- /dev/null +++ b/v3/pkg/application/dialogs_darwin.go @@ -0,0 +1,544 @@ +//go:build darwin + +package application + +/* +#cgo CFLAGS: -mmacosx-version-min=10.13 -x objective-c +#cgo LDFLAGS: -framework Cocoa -mmacosx-version-min=10.13 -framework UniformTypeIdentifiers + +#import + +#import +#import "dialogs_darwin_delegate.h" + +extern void openFileDialogCallback(uint id, char* path); +extern void openFileDialogCallbackEnd(uint id); +extern void saveFileDialogCallback(uint id, char* path); + +static void showAboutBox(char* title, char *message, void *icon, int length) { + + // run on main thread + // dispatch_async(dispatch_get_main_queue(), ^{ + NSAlert *alert = [[NSAlert alloc] init]; + if (title != NULL) { + [alert setMessageText:[NSString stringWithUTF8String:title]]; + free(title); + } + if (message != NULL) { + [alert setInformativeText:[NSString stringWithUTF8String:message]]; + free(message); + } + if (icon != NULL) { + NSImage *image = [[NSImage alloc] initWithData:[NSData dataWithBytes:icon length:length]]; + [alert setIcon:image]; + } + [alert setAlertStyle:NSAlertStyleInformational]; + [alert runModal]; + // }); +} + + +// Create an NSAlert +static void* createAlert(int alertType, char* title, char *message, void *icon, int length) { + NSAlert *alert = [[NSAlert alloc] init]; + [alert setAlertStyle:alertType]; + if (title != NULL) { + [alert setMessageText:[NSString stringWithUTF8String:title]]; + free(title); + } + if (message != NULL) { + [alert setInformativeText:[NSString stringWithUTF8String:message]]; + free(message); + } + if (icon != NULL) { + NSImage *image = [[NSImage alloc] initWithData:[NSData dataWithBytes:icon length:length]]; + [alert setIcon:image]; + } else { + if(alertType == NSAlertStyleCritical || alertType == NSAlertStyleWarning) { + NSImage *image = [NSImage imageNamed:NSImageNameCaution]; + [alert setIcon:image]; + } else { + NSImage *image = [NSImage imageNamed:NSImageNameInfo]; + [alert setIcon:image]; + } + } + return alert; + +} + +// Run the dialog +static int dialogRunModal(void *dialog, void *parent) { + NSAlert *alert = (__bridge NSAlert *)dialog; + + __block long response; + //if( parent != NULL ) { + // NSWindow *window = (__bridge NSWindow *)parent; + // response = [alert runModalSheetForWindow:window]; + //} else { + // response = [alert runModal]; + //} + + // If the parent is NULL, we are running a modal dialog, otherwise attach the alert to the parent + if( parent == NULL ) { + response = [alert runModal]; + } else { + NSWindow *window = (__bridge NSWindow *)parent; + [alert beginSheetModalForWindow:window completionHandler:^(NSModalResponse returnCode) { + response = returnCode; + }]; + } + + int result; + + if( response == NSAlertFirstButtonReturn ) { + result = 0; + } + else if( response == NSAlertSecondButtonReturn ) { + result = 1; + } + else if( response == NSAlertThirdButtonReturn ) { + result = 2; + } else { + result = 3; + } + return result; +} + +// Release the dialog +static void releaseDialog(void *dialog) { + NSAlert *alert = (__bridge NSAlert *)dialog; + [alert release]; +} + +// Add a button to the dialog +static void alertAddButton(void *dialog, char *label, bool isDefault, bool isCancel) { + NSAlert *alert = (__bridge NSAlert *)dialog; + NSButton *button = [alert addButtonWithTitle:[NSString stringWithUTF8String:label]]; + free(label); + if( isDefault ) { + [button setKeyEquivalent:@"\r"]; + } else if( isCancel ) { + [button setKeyEquivalent:@"\033"]; + } else { + [button setKeyEquivalent:@""]; + } +} + +static void processOpenFileDialogResults(NSOpenPanel *panel, NSInteger result, uint dialogID) { + const char *path = NULL; + if (result == NSModalResponseOK) { + NSArray *urls = [panel URLs]; + if ([urls count] > 0) { + NSArray *urls = [panel URLs]; + for (NSURL *url in urls) { + path = [[url path] UTF8String]; + openFileDialogCallback(dialogID, (char *)path); + } + } else { + NSURL *url = [panel URL]; + path = [[url path] UTF8String]; + openFileDialogCallback(dialogID, (char *)path); + } + } + openFileDialogCallbackEnd(dialogID); +} + + +static void showOpenFileDialog(unsigned int dialogID, + bool canChooseFiles, + bool canChooseDirectories, + bool canCreateDirectories, + bool showHiddenFiles, + bool allowsMultipleSelection, + bool resolvesAliases, + bool hideExtension, + bool treatsFilePackagesAsDirectories, + bool allowsOtherFileTypes, + char *filterPatterns, + unsigned int filterPatternsCount, + char* message, + char* directory, + char* buttonText, + + void *window) { + + // run on main thread + dispatch_async(dispatch_get_main_queue(), ^{ + + NSOpenPanel *panel = [NSOpenPanel openPanel]; + + // print out filterPatterns if length > 0 + if (filterPatternsCount > 0) { + OpenPanelDelegate *delegate = [[OpenPanelDelegate alloc] init]; + [panel setDelegate:delegate]; + // Initialise NSString with bytes and UTF8 encoding + NSString *filterPatternsString = [[NSString alloc] initWithBytes:filterPatterns length:filterPatternsCount encoding:NSUTF8StringEncoding]; + // Convert NSString to NSArray + delegate.allowedExtensions = [filterPatternsString componentsSeparatedByString:@";"]; + + // Use UTType if macOS 11 or higher to add file filters + if (@available(macOS 11, *)) { + NSMutableArray *filterTypes = [NSMutableArray array]; + // Iterate the filtertypes, create uti's that are limited to the file extensions then add + for (NSString *filterType in delegate.allowedExtensions) { + [filterTypes addObject:[UTType typeWithFilenameExtension:filterType]]; + } + [panel setAllowedContentTypes:filterTypes]; + } else { + [panel setAllowedFileTypes:delegate.allowedExtensions]; + } + + // Free the memory + free(filterPatterns); + } + + + if (message != NULL) { + [panel setMessage:[NSString stringWithUTF8String:message]]; + free(message); + } + + if (directory != NULL) { + [panel setDirectoryURL:[NSURL fileURLWithPath:[NSString stringWithUTF8String:directory]]]; + free(directory); + } + + if (buttonText != NULL) { + [panel setPrompt:[NSString stringWithUTF8String:buttonText]]; + free(buttonText); + } + + [panel setCanChooseFiles:canChooseFiles]; + [panel setCanChooseDirectories:canChooseDirectories]; + [panel setCanCreateDirectories:canCreateDirectories]; + [panel setShowsHiddenFiles:showHiddenFiles]; + [panel setAllowsMultipleSelection:allowsMultipleSelection]; + [panel setResolvesAliases:resolvesAliases]; + [panel setExtensionHidden:hideExtension]; + [panel setTreatsFilePackagesAsDirectories:treatsFilePackagesAsDirectories]; + [panel setAllowsOtherFileTypes:allowsOtherFileTypes]; + + + + if (window != NULL) { + [panel beginSheetModalForWindow:(__bridge NSWindow *)window completionHandler:^(NSInteger result) { + processOpenFileDialogResults(panel, result, dialogID); + }]; + } else { + [panel beginWithCompletionHandler:^(NSInteger result) { + processOpenFileDialogResults(panel, result, dialogID); + }]; + } + }); +} + +static void showSaveFileDialog(unsigned int dialogID, + bool canCreateDirectories, + bool showHiddenFiles, + bool canSelectHiddenExtension, + bool hideExtension, + bool treatsFilePackagesAsDirectories, + bool allowOtherFileTypes, + char* message, + char* directory, + char* buttonText, + char* filename, + void *window) { + + // run on main thread + dispatch_async(dispatch_get_main_queue(), ^{ + NSSavePanel *panel = [NSSavePanel savePanel]; + + if (message != NULL) { + [panel setMessage:[NSString stringWithUTF8String:message]]; + free(message); + } + + if (directory != NULL) { + [panel setDirectoryURL:[NSURL fileURLWithPath:[NSString stringWithUTF8String:directory]]]; + free(directory); + } + + if (filename != NULL) { + [panel setNameFieldStringValue:[NSString stringWithUTF8String:filename]]; + free(filename); + } + + if (buttonText != NULL) { + [panel setPrompt:[NSString stringWithUTF8String:buttonText]]; + free(buttonText); + } + + [panel setCanCreateDirectories:canCreateDirectories]; + [panel setShowsHiddenFiles:showHiddenFiles]; + [panel setCanSelectHiddenExtension:canSelectHiddenExtension]; + [panel setExtensionHidden:hideExtension]; + [panel setTreatsFilePackagesAsDirectories:treatsFilePackagesAsDirectories]; + [panel setAllowsOtherFileTypes:allowOtherFileTypes]; + + if (window != NULL) { + [panel beginSheetModalForWindow:(__bridge NSWindow *)window completionHandler:^(NSInteger result) { + const char *path = NULL; + if (result == NSModalResponseOK) { + NSURL *url = [panel URL]; + path = [[url path] UTF8String]; + } + saveFileDialogCallback(dialogID, (char *)path); + }]; + } else { + [panel beginWithCompletionHandler:^(NSInteger result) { + const char *path = NULL; + if (result == NSModalResponseOK) { + NSURL *url = [panel URL]; + path = [[url path] UTF8String]; + } + saveFileDialogCallback(dialogID, (char *)path); + }]; + } + }); +} + +*/ +import "C" +import ( + "strings" + "unsafe" +) + +const NSAlertStyleWarning = C.int(0) +const NSAlertStyleInformational = C.int(1) +const NSAlertStyleCritical = C.int(2) + +var alertTypeMap = map[DialogType]C.int{ + WarningDialogType: NSAlertStyleWarning, + InfoDialogType: NSAlertStyleInformational, + ErrorDialogType: NSAlertStyleCritical, + QuestionDialogType: NSAlertStyleInformational, +} + +func (m *macosApp) showAboutDialog(title string, message string, icon []byte) { + var iconData unsafe.Pointer + if icon != nil { + iconData = unsafe.Pointer(&icon[0]) + } + InvokeAsync(func() { + C.showAboutBox(C.CString(title), C.CString(message), iconData, C.int(len(icon))) + }) +} + +type macosDialog struct { + dialog *MessageDialog + + nsDialog unsafe.Pointer +} + +func (m *macosDialog) show() { + InvokeAsync(func() { + + // Mac can only have 4 Buttons on a dialog + if len(m.dialog.Buttons) > 4 { + m.dialog.Buttons = m.dialog.Buttons[:4] + } + + if m.nsDialog != nil { + C.releaseDialog(m.nsDialog) + } + var title *C.char + if m.dialog.Title != "" { + title = C.CString(m.dialog.Title) + } + var message *C.char + if m.dialog.Message != "" { + message = C.CString(m.dialog.Message) + } + var iconData unsafe.Pointer + var iconLength C.int + if m.dialog.Icon != nil { + iconData = unsafe.Pointer(&m.dialog.Icon[0]) + iconLength = C.int(len(m.dialog.Icon)) + } else { + // if it's an error, use the application Icon + if m.dialog.DialogType == ErrorDialogType { + iconData = unsafe.Pointer(&globalApplication.options.Icon[0]) + iconLength = C.int(len(globalApplication.options.Icon)) + } + } + var parent unsafe.Pointer + if m.dialog.window != nil { + // get NSWindow from window + window, _ := m.dialog.window.NativeWindowHandle() + parent = unsafe.Pointer(window) + } + + alertType, ok := alertTypeMap[m.dialog.DialogType] + if !ok { + alertType = C.NSAlertStyleInformational + } + + m.nsDialog = C.createAlert(alertType, title, message, iconData, iconLength) + + // Reverse the Buttons so that the default is on the right + reversedButtons := make([]*Button, len(m.dialog.Buttons)) + var count = 0 + for i := len(m.dialog.Buttons) - 1; i >= 0; i-- { + button := m.dialog.Buttons[i] + C.alertAddButton(m.nsDialog, C.CString(button.Label), C.bool(button.IsDefault), C.bool(button.IsCancel)) + reversedButtons[count] = m.dialog.Buttons[i] + count++ + } + + buttonPressed := int(C.dialogRunModal(m.nsDialog, parent)) + if len(m.dialog.Buttons) > buttonPressed { + button := reversedButtons[buttonPressed] + if button.Callback != nil { + button.Callback() + } + } + }) + +} + +func newDialogImpl(d *MessageDialog) *macosDialog { + return &macosDialog{ + dialog: d, + } +} + +type macosOpenFileDialog struct { + dialog *OpenFileDialogStruct +} + +func newOpenFileDialogImpl(d *OpenFileDialogStruct) *macosOpenFileDialog { + return &macosOpenFileDialog{ + dialog: d, + } +} + +func toCString(s string) *C.char { + if s == "" { + return nil + } + return C.CString(s) +} + +func (m *macosOpenFileDialog) show() (chan string, error) { + openFileResponses[m.dialog.id] = make(chan string) + nsWindow := unsafe.Pointer(nil) + if m.dialog.window != nil { + // get NSWindow from window + window, _ := m.dialog.window.NativeWindowHandle() + nsWindow = unsafe.Pointer(window) + } + + // Massage filter patterns into macOS format + // We iterate all filter patterns, tidy them up and then join them with a semicolon + // This should produce a single string of extensions like "png;jpg;gif" + var filterPatterns string + if len(m.dialog.filters) > 0 { + var allPatterns []string + for _, filter := range m.dialog.filters { + patternComponents := strings.Split(filter.Pattern, ";") + for i, component := range patternComponents { + filterPattern := strings.TrimSpace(component) + filterPattern = strings.TrimPrefix(filterPattern, "*.") + patternComponents[i] = filterPattern + } + allPatterns = append(allPatterns, strings.Join(patternComponents, ";")) + } + filterPatterns = strings.Join(allPatterns, ";") + } + C.showOpenFileDialog(C.uint(m.dialog.id), + C.bool(m.dialog.canChooseFiles), + C.bool(m.dialog.canChooseDirectories), + C.bool(m.dialog.canCreateDirectories), + C.bool(m.dialog.showHiddenFiles), + C.bool(m.dialog.allowsMultipleSelection), + C.bool(m.dialog.resolvesAliases), + C.bool(m.dialog.hideExtension), + C.bool(m.dialog.treatsFilePackagesAsDirectories), + C.bool(m.dialog.allowsOtherFileTypes), + toCString(filterPatterns), + C.uint(len(filterPatterns)), + toCString(m.dialog.message), + toCString(m.dialog.directory), + toCString(m.dialog.buttonText), + nsWindow) + + return openFileResponses[m.dialog.id], nil +} + +//export openFileDialogCallback +func openFileDialogCallback(cid C.uint, cpath *C.char) { + path := C.GoString(cpath) + id := uint(cid) + channel, ok := openFileResponses[id] + if ok { + channel <- path + } else { + panic("No channel found for open file dialog") + } +} + +//export openFileDialogCallbackEnd +func openFileDialogCallbackEnd(cid C.uint) { + id := uint(cid) + channel, ok := openFileResponses[id] + if ok { + close(channel) + delete(openFileResponses, id) + freeDialogID(id) + } else { + panic("No channel found for open file dialog") + } +} + +type macosSaveFileDialog struct { + dialog *SaveFileDialogStruct +} + +func newSaveFileDialogImpl(d *SaveFileDialogStruct) *macosSaveFileDialog { + return &macosSaveFileDialog{ + dialog: d, + } +} + +func (m *macosSaveFileDialog) show() (chan string, error) { + saveFileResponses[m.dialog.id] = make(chan string) + nsWindow := unsafe.Pointer(nil) + if m.dialog.window != nil { + // get NSWindow from window + window, _ := m.dialog.window.NativeWindowHandle() + nsWindow = unsafe.Pointer(window) + } + C.showSaveFileDialog(C.uint(m.dialog.id), + C.bool(m.dialog.canCreateDirectories), + C.bool(m.dialog.showHiddenFiles), + C.bool(m.dialog.canSelectHiddenExtension), + C.bool(m.dialog.hideExtension), + C.bool(m.dialog.treatsFilePackagesAsDirectories), + C.bool(m.dialog.allowOtherFileTypes), + toCString(m.dialog.message), + toCString(m.dialog.directory), + toCString(m.dialog.buttonText), + toCString(m.dialog.filename), + nsWindow) + return saveFileResponses[m.dialog.id], nil +} + +//export saveFileDialogCallback +func saveFileDialogCallback(cid C.uint, cpath *C.char) { + // Covert the path to a string + path := C.GoString(cpath) + id := uint(cid) + // put response on channel + channel, ok := saveFileResponses[id] + if ok { + channel <- path + close(channel) + delete(saveFileResponses, id) + freeDialogID(id) + + } else { + panic("No channel found for save file dialog") + } +} diff --git a/v3/pkg/application/dialogs_darwin_delegate.h b/v3/pkg/application/dialogs_darwin_delegate.h new file mode 100644 index 000000000..07657f8b9 --- /dev/null +++ b/v3/pkg/application/dialogs_darwin_delegate.h @@ -0,0 +1,14 @@ +//go:build darwin + +#ifndef _DIALOGS_DELEGATE_H_ +#define _DIALOGS_DELEGATE_H_ + +#import +#import + +// create an NSOpenPanel delegate to handle the callback +@interface OpenPanelDelegate : NSObject +@property (nonatomic, strong) NSArray *allowedExtensions; +@end + +#endif \ No newline at end of file diff --git a/v3/pkg/application/dialogs_darwin_delegate.m b/v3/pkg/application/dialogs_darwin_delegate.m new file mode 100644 index 000000000..5cbd46a2b --- /dev/null +++ b/v3/pkg/application/dialogs_darwin_delegate.m @@ -0,0 +1,36 @@ +//go:build darwin + +#import "dialogs_darwin_delegate.h" + +// Override shouldEnableURL +@implementation OpenPanelDelegate +- (BOOL)panel:(id)sender shouldEnableURL:(NSURL *)url { + if (url == nil) { + return NO; + } + NSFileManager *fileManager = [NSFileManager defaultManager]; + BOOL isDirectory = NO; + if ([fileManager fileExistsAtPath:url.path isDirectory:&isDirectory] && isDirectory) { + return YES; + } + if (self.allowedExtensions == nil) { + return YES; + } + NSString *extension = url.pathExtension; + if (extension == nil) { + return NO; + } + if ([extension isEqualToString:@""]) { + return NO; + } + if ([self.allowedExtensions containsObject:extension]) { + return YES; + } + return NO; +} + +@end + + + + diff --git a/v3/pkg/application/dialogs_linux.go b/v3/pkg/application/dialogs_linux.go new file mode 100644 index 000000000..4943766f0 --- /dev/null +++ b/v3/pkg/application/dialogs_linux.go @@ -0,0 +1,72 @@ +package application + +func (m *linuxApp) showAboutDialog(title string, message string, icon []byte) { + window := globalApplication.getWindowForID(m.getCurrentWindowID()) + var parent uintptr + if window != nil { + parent, _ = window.(*WebviewWindow).NativeWindowHandle() + } + about := newMessageDialog(InfoDialogType) + about.SetTitle(title). + SetMessage(message). + SetIcon(icon) + runQuestionDialog( + pointer(parent), + about, + ) +} + +type linuxDialog struct { + dialog *MessageDialog +} + +func (m *linuxDialog) show() { + windowId := getNativeApplication().getCurrentWindowID() + window := globalApplication.getWindowForID(windowId) + var parent uintptr + if window != nil { + parent, _ = window.(*WebviewWindow).NativeWindowHandle() + } + + response := runQuestionDialog(pointer(parent), m.dialog) + if response >= 0 && response < len(m.dialog.Buttons) { + button := m.dialog.Buttons[response] + if button.Callback != nil { + go button.Callback() + } + } +} + +func newDialogImpl(d *MessageDialog) *linuxDialog { + return &linuxDialog{ + dialog: d, + } +} + +type linuxOpenFileDialog struct { + dialog *OpenFileDialogStruct +} + +func newOpenFileDialogImpl(d *OpenFileDialogStruct) *linuxOpenFileDialog { + return &linuxOpenFileDialog{ + dialog: d, + } +} + +func (m *linuxOpenFileDialog) show() (chan string, error) { + return runOpenFileDialog(m.dialog) +} + +type linuxSaveFileDialog struct { + dialog *SaveFileDialogStruct +} + +func newSaveFileDialogImpl(d *SaveFileDialogStruct) *linuxSaveFileDialog { + return &linuxSaveFileDialog{ + dialog: d, + } +} + +func (m *linuxSaveFileDialog) show() (chan string, error) { + return runSaveFileDialog(m.dialog) +} diff --git a/v3/pkg/application/dialogs_windows.go b/v3/pkg/application/dialogs_windows.go new file mode 100644 index 000000000..133e70da9 --- /dev/null +++ b/v3/pkg/application/dialogs_windows.go @@ -0,0 +1,228 @@ +//go:build windows + +package application + +import ( + "github.com/wailsapp/wails/v3/internal/go-common-file-dialog/cfd" + "github.com/wailsapp/wails/v3/pkg/w32" + "golang.org/x/sys/windows" + "path/filepath" + "strings" +) + +func (m *windowsApp) showAboutDialog(title string, message string, _ []byte) { + about := newDialogImpl(&MessageDialog{ + MessageDialogOptions: MessageDialogOptions{ + DialogType: InfoDialogType, + Title: title, + Message: message, + }, + }) + about.UseAppIcon = true + about.show() +} + +type windowsDialog struct { + dialog *MessageDialog + + //dialogImpl unsafe.Pointer + UseAppIcon bool +} + +func (m *windowsDialog) show() { + + title := w32.MustStringToUTF16Ptr(m.dialog.Title) + message := w32.MustStringToUTF16Ptr(m.dialog.Message) + flags := calculateMessageDialogFlags(m.dialog.MessageDialogOptions) + var button int32 + + var parentWindow uintptr + var err error + if m.dialog.window != nil { + parentWindow, err = m.dialog.window.NativeWindowHandle() + if err != nil { + w32.Fatal(err.Error()) + } + } + + if m.UseAppIcon || m.dialog.Icon != nil { + // 3 is the application icon + button, _ = w32.MessageBoxWithIcon(parentWindow, message, title, 3, windows.MB_OK|windows.MB_USERICON) + } else { + button, _ = windows.MessageBox(windows.HWND(parentWindow), message, title, flags|windows.MB_SYSTEMMODAL) + } + // This maps MessageBox return values to strings + responses := []string{"", "Ok", "Cancel", "Abort", "Retry", "Ignore", "Yes", "No", "", "", "Try Again", "Continue"} + result := "Error" + if int(button) < len(responses) { + result = responses[button] + } + // Check if there's a callback for the button pressed + for _, button := range m.dialog.Buttons { + if button.Label == result { + if button.Callback != nil { + button.Callback() + } + } + } +} + +func newDialogImpl(d *MessageDialog) *windowsDialog { + return &windowsDialog{ + dialog: d, + } +} + +type windowOpenFileDialog struct { + dialog *OpenFileDialogStruct +} + +func newOpenFileDialogImpl(d *OpenFileDialogStruct) *windowOpenFileDialog { + return &windowOpenFileDialog{ + dialog: d, + } +} + +func getDefaultFolder(folder string) (string, error) { + if folder == "" { + return "", nil + } + return filepath.Abs(folder) +} + +func (m *windowOpenFileDialog) show() (chan string, error) { + + defaultFolder, err := getDefaultFolder(m.dialog.directory) + if err != nil { + return nil, err + } + + config := cfd.DialogConfig{ + Title: m.dialog.title, + Role: "PickFolder", + FileFilters: convertFilters(m.dialog.filters), + Folder: defaultFolder, + } + + if m.dialog.window != nil { + config.ParentWindowHandle, err = m.dialog.window.NativeWindowHandle() + if err != nil { + w32.Fatal(err.Error()) + } + } + + var result []string + if m.dialog.allowsMultipleSelection { + temp, err := showCfdDialog( + func() (cfd.Dialog, error) { + return cfd.NewOpenMultipleFilesDialog(config) + }, true) + if err != nil { + return nil, err + } + result = temp.([]string) + } else { + temp, err := showCfdDialog( + func() (cfd.Dialog, error) { + return cfd.NewOpenFileDialog(config) + }, false) + if err != nil { + return nil, err + } + result = []string{temp.(string)} + } + + files := make(chan string) + go func() { + for _, file := range result { + files <- file + } + close(files) + }() + return files, nil +} + +type windowSaveFileDialog struct { + dialog *SaveFileDialogStruct +} + +func newSaveFileDialogImpl(d *SaveFileDialogStruct) *windowSaveFileDialog { + return &windowSaveFileDialog{ + dialog: d, + } +} + +func (m *windowSaveFileDialog) show() (chan string, error) { + files := make(chan string) + defaultFolder, err := getDefaultFolder(m.dialog.directory) + if err != nil { + close(files) + return files, err + } + + config := cfd.DialogConfig{ + Title: m.dialog.title, + Role: "SaveFile", + FileFilters: convertFilters(m.dialog.filters), + FileName: m.dialog.filename, + Folder: defaultFolder, + } + + result, err := showCfdDialog( + func() (cfd.Dialog, error) { + return cfd.NewSaveFileDialog(config) + }, false) + go func() { + files <- result.(string) + close(files) + }() + return files, err +} + +func calculateMessageDialogFlags(options MessageDialogOptions) uint32 { + var flags uint32 + + switch options.DialogType { + case InfoDialogType: + flags = windows.MB_OK | windows.MB_ICONINFORMATION + case ErrorDialogType: + flags = windows.MB_ICONERROR | windows.MB_OK + case QuestionDialogType: + flags = windows.MB_YESNO + for _, button := range options.Buttons { + if strings.TrimSpace(strings.ToLower(button.Label)) == "no" && button.IsDefault { + flags |= windows.MB_DEFBUTTON2 + } + } + case WarningDialogType: + flags = windows.MB_OK | windows.MB_ICONWARNING + } + + return flags +} + +func convertFilters(filters []FileFilter) []cfd.FileFilter { + var result []cfd.FileFilter + for _, filter := range filters { + result = append(result, cfd.FileFilter(filter)) + } + return result +} + +func showCfdDialog(newDlg func() (cfd.Dialog, error), isMultiSelect bool) (any, error) { + dlg, err := newDlg() + if err != nil { + return nil, err + } + defer func() { + err := dlg.Release() + if err != nil { + globalApplication.error("Unable to release dialog: " + err.Error()) + } + }() + + if multi, _ := dlg.(cfd.OpenMultipleFilesDialog); multi != nil && isMultiSelect { + return multi.ShowAndGetResults() + } + return dlg.ShowAndGetResult() +} diff --git a/v3/pkg/application/errors.go b/v3/pkg/application/errors.go new file mode 100644 index 000000000..747142328 --- /dev/null +++ b/v3/pkg/application/errors.go @@ -0,0 +1,16 @@ +package application + +import ( + "fmt" + "os" +) + +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 ***********************") + os.Exit(1) +} diff --git a/v3/pkg/application/events.go b/v3/pkg/application/events.go new file mode 100644 index 000000000..90b04f8b1 --- /dev/null +++ b/v3/pkg/application/events.go @@ -0,0 +1,229 @@ +package application + +import ( + "encoding/json" + "sync" + + "github.com/samber/lo" +) + +type Event struct { + Id uint + ctx *ApplicationEventContext + Cancelled bool +} + +func (w *Event) Context() *ApplicationEventContext { + return w.ctx +} + +func newApplicationEvent(id int) *Event { + return &Event{ + Id: uint(id), + ctx: newApplicationEventContext(), + } +} + +func (w *Event) Cancel() { + w.Cancelled = true +} + +var applicationEvents = make(chan *Event) + +type windowEvent struct { + WindowID uint + EventID uint +} + +var windowEvents = make(chan *windowEvent) + +var menuItemClicked = make(chan uint) + +type WailsEvent struct { + Name string `json:"name"` + Data any `json:"data"` + Sender string `json:"sender"` + Cancelled bool +} + +func (e *WailsEvent) Cancel() { + e.Cancelled = true +} + +func (e WailsEvent) ToJSON() string { + marshal, err := json.Marshal(&e) + if err != nil { + // TODO: Fatal error? log? + return "" + } + return string(marshal) +} + +type hook struct { + callback func(*WailsEvent) +} + +// eventListener holds a callback function which is invoked when +// the event listened for is emitted. It has a counter which indicates +// how the total number of events it is interested in. A value of zero +// means it does not expire (default). +type eventListener struct { + callback func(*WailsEvent) // Function to call with emitted event data + counter int // The number of times this callback may be called. -1 = infinite + delete bool // Flag to indicate that this listener should be deleted +} + +// EventProcessor handles custom events +type EventProcessor struct { + // Go event listeners + listeners map[string][]*eventListener + notifyLock sync.RWMutex + dispatchEventToWindows func(*WailsEvent) + hooks map[string][]*hook + hookLock sync.RWMutex +} + +func NewWailsEventProcessor(dispatchEventToWindows func(*WailsEvent)) *EventProcessor { + return &EventProcessor{ + listeners: make(map[string][]*eventListener), + dispatchEventToWindows: dispatchEventToWindows, + hooks: make(map[string][]*hook), + } +} + +// On is the equivalent of Javascript's `addEventListener` +func (e *EventProcessor) On(eventName string, callback func(event *WailsEvent)) func() { + return e.registerListener(eventName, callback, -1) +} + +// OnMultiple is the same as `On` but will unregister after `count` events +func (e *EventProcessor) OnMultiple(eventName string, callback func(event *WailsEvent), counter int) func() { + return e.registerListener(eventName, callback, counter) +} + +// Once is the same as `On` but will unregister after the first event +func (e *EventProcessor) Once(eventName string, callback func(event *WailsEvent)) func() { + return e.registerListener(eventName, callback, 1) +} + +// Emit sends an event to all listeners +func (e *EventProcessor) Emit(thisEvent *WailsEvent) { + if thisEvent == nil { + return + } + + // If we have any hooks, run them first and check if the event was cancelled + if e.hooks != nil { + if hooks, ok := e.hooks[thisEvent.Name]; ok { + for _, thisHook := range hooks { + thisHook.callback(thisEvent) + if thisEvent.Cancelled { + return + } + } + } + } + + go e.dispatchEventToListeners(thisEvent) + go e.dispatchEventToWindows(thisEvent) +} + +func (e *EventProcessor) Off(eventName string) { + e.unRegisterListener(eventName) +} + +func (e *EventProcessor) OffAll() { + e.notifyLock.Lock() + defer e.notifyLock.Unlock() + e.listeners = make(map[string][]*eventListener) +} + +// registerListener provides a means of subscribing to events of type "eventName" +func (e *EventProcessor) registerListener(eventName string, callback func(*WailsEvent), counter int) func() { + // Create new eventListener + thisListener := &eventListener{ + callback: callback, + counter: counter, + delete: false, + } + e.notifyLock.Lock() + // Append the new listener to the listeners slice + e.listeners[eventName] = append(e.listeners[eventName], thisListener) + e.notifyLock.Unlock() + return func() { + e.notifyLock.Lock() + defer e.notifyLock.Unlock() + + if _, ok := e.listeners[eventName]; !ok { + return + } + e.listeners[eventName] = lo.Filter(e.listeners[eventName], func(l *eventListener, i int) bool { + return l != thisListener + }) + } +} + +// RegisterHook provides a means of registering methods to be called before emitting the event +func (e *EventProcessor) RegisterHook(eventName string, callback func(*WailsEvent)) func() { + // Create new hook + thisHook := &hook{ + callback: callback, + } + e.hookLock.Lock() + // Append the new listener to the listeners slice + e.hooks[eventName] = append(e.hooks[eventName], thisHook) + e.hookLock.Unlock() + return func() { + e.hookLock.Lock() + defer e.hookLock.Unlock() + + if _, ok := e.hooks[eventName]; !ok { + return + } + e.hooks[eventName] = lo.Filter(e.hooks[eventName], func(l *hook, i int) bool { + return l != thisHook + }) + } +} + +// unRegisterListener provides a means of unsubscribing to events of type "eventName" +func (e *EventProcessor) unRegisterListener(eventName string) { + e.notifyLock.Lock() + defer e.notifyLock.Unlock() + delete(e.listeners, eventName) +} + +// dispatchEventToListeners calls all registered listeners event name +func (e *EventProcessor) dispatchEventToListeners(event *WailsEvent) { + + e.notifyLock.Lock() + defer e.notifyLock.Unlock() + + listeners := e.listeners[event.Name] + if listeners == nil { + return + } + + // We have a dirty flag to indicate that there are items to delete + itemsToDelete := false + + // Callback in goroutine + for _, listener := range listeners { + if listener.counter > 0 { + listener.counter-- + } + go listener.callback(event) + + if listener.counter == 0 { + listener.delete = true + itemsToDelete = true + } + } + + // Do we have items to delete? + if itemsToDelete == true { + e.listeners[event.Name] = lo.Filter(listeners, func(l *eventListener, i int) bool { + return l.delete == false + }) + } +} diff --git a/v3/pkg/application/events_common_darwin.go b/v3/pkg/application/events_common_darwin.go new file mode 100644 index 000000000..1f4bac518 --- /dev/null +++ b/v3/pkg/application/events_common_darwin.go @@ -0,0 +1,21 @@ +//go:build darwin + +package application + +import "github.com/wailsapp/wails/v3/pkg/events" + +var commonApplicationEventMap = map[events.ApplicationEventType]events.ApplicationEventType{ + events.Mac.ApplicationDidFinishLaunching: events.Common.ApplicationStarted, + events.Mac.ApplicationDidChangeTheme: events.Common.ThemeChanged, +} + +func (m *macosApp) setupCommonEvents() { + for sourceEvent, targetEvent := range commonApplicationEventMap { + sourceEvent := sourceEvent + targetEvent := targetEvent + m.parent.On(sourceEvent, func(event *Event) { + event.Id = uint(targetEvent) + applicationEvents <- event + }) + } +} diff --git a/v3/pkg/application/events_common_windows.go b/v3/pkg/application/events_common_windows.go new file mode 100644 index 000000000..98db18b3d --- /dev/null +++ b/v3/pkg/application/events_common_windows.go @@ -0,0 +1,21 @@ +//go:build windows + +package application + +import "github.com/wailsapp/wails/v3/pkg/events" + +var commonApplicationEventMap = map[events.ApplicationEventType]events.ApplicationEventType{ + events.Windows.SystemThemeChanged: events.Common.ThemeChanged, + events.Windows.ApplicationStarted: events.Common.ApplicationStarted, +} + +func (m *windowsApp) setupCommonEvents() { + for sourceEvent, targetEvent := range commonApplicationEventMap { + sourceEvent := sourceEvent + targetEvent := targetEvent + m.parent.On(sourceEvent, func(event *Event) { + event.Id = uint(targetEvent) + applicationEvents <- event + }) + } +} diff --git a/v3/pkg/application/events_test.go b/v3/pkg/application/events_test.go new file mode 100644 index 000000000..fe921e4fd --- /dev/null +++ b/v3/pkg/application/events_test.go @@ -0,0 +1,135 @@ +package application_test + +import ( + "sync" + "testing" + + "github.com/wailsapp/wails/v3/pkg/application" + + "github.com/matryer/is" +) + +type mockNotifier struct { + Events []*application.WailsEvent +} + +func (m *mockNotifier) dispatchEventToWindows(event *application.WailsEvent) { + m.Events = append(m.Events, event) +} + +func (m *mockNotifier) Reset() { + m.Events = []*application.WailsEvent{} +} + +func Test_EventsOn(t *testing.T) { + i := is.New(t) + notifier := &mockNotifier{} + eventProcessor := application.NewWailsEventProcessor(notifier.dispatchEventToWindows) + + // Test On + eventName := "test" + counter := 0 + var wg sync.WaitGroup + wg.Add(1) + unregisterFn := eventProcessor.On(eventName, func(event *application.WailsEvent) { + // This is called in a goroutine + counter++ + wg.Done() + }) + eventProcessor.Emit(&application.WailsEvent{ + Name: "test", + Data: "test payload", + }) + wg.Wait() + i.Equal(1, counter) + + // Unregister + notifier.Reset() + unregisterFn() + counter = 0 + eventProcessor.Emit(&application.WailsEvent{ + Name: "test", + Data: "test payload", + }) + i.Equal(0, counter) + +} + +func Test_EventsOnce(t *testing.T) { + i := is.New(t) + notifier := &mockNotifier{} + eventProcessor := application.NewWailsEventProcessor(notifier.dispatchEventToWindows) + + // Test On + eventName := "test" + counter := 0 + var wg sync.WaitGroup + wg.Add(1) + unregisterFn := eventProcessor.Once(eventName, func(event *application.WailsEvent) { + // This is called in a goroutine + counter++ + wg.Done() + }) + eventProcessor.Emit(&application.WailsEvent{ + Name: "test", + Data: "test payload", + }) + eventProcessor.Emit(&application.WailsEvent{ + Name: "test", + Data: "test payload", + }) + wg.Wait() + i.Equal(1, counter) + + // Unregister + notifier.Reset() + unregisterFn() + counter = 0 + eventProcessor.Emit(&application.WailsEvent{ + Name: "test", + Data: "test payload", + }) + i.Equal(0, counter) + +} +func Test_EventsOnMultiple(t *testing.T) { + i := is.New(t) + notifier := &mockNotifier{} + eventProcessor := application.NewWailsEventProcessor(notifier.dispatchEventToWindows) + + // Test On + eventName := "test" + counter := 0 + var wg sync.WaitGroup + wg.Add(2) + unregisterFn := eventProcessor.OnMultiple(eventName, func(event *application.WailsEvent) { + // This is called in a goroutine + counter++ + wg.Done() + }, 2) + eventProcessor.Emit(&application.WailsEvent{ + Name: "test", + Data: "test payload", + }) + eventProcessor.Emit(&application.WailsEvent{ + Name: "test", + Data: "test payload", + }) + eventProcessor.Emit(&application.WailsEvent{ + Name: "test", + Data: "test payload", + }) + wg.Wait() + i.Equal(2, counter) + + // Unregister + notifier.Reset() + unregisterFn() + counter = 0 + eventProcessor.Emit(&application.WailsEvent{ + Name: "test", + Data: "test payload", + }) + i.Equal(0, counter) + +} diff --git a/v3/pkg/application/http.go b/v3/pkg/application/http.go new file mode 100644 index 000000000..6cec4584c --- /dev/null +++ b/v3/pkg/application/http.go @@ -0,0 +1,126 @@ +package application + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "io" + "net/http" + "strings" + "time" +) + +// HTTPRequest represents an HTTP request from the frontend +type HTTPRequest struct { + Method string `json:"method"` + URL string `json:"url"` + Headers map[string]string `json:"headers,omitempty"` + Body string `json:"body,omitempty"` + Timeout int `json:"timeout,omitempty"` // timeout in seconds +} + +// HTTPResponse represents an HTTP response to send back to the frontend +type HTTPResponse struct { + StatusCode int `json:"statusCode"` + Headers map[string]string `json:"headers"` + Body string `json:"body"` + Error string `json:"error,omitempty"` +} + +// PerformHTTPRequest performs an HTTP request on behalf of the frontend +func PerformHTTPRequest(request HTTPRequest) HTTPResponse { + // Validate method + method := strings.ToUpper(request.Method) + if method != "GET" && method != "POST" && method != "PUT" && method != "DELETE" && method != "PATCH" && method != "HEAD" && method != "OPTIONS" { + return HTTPResponse{ + StatusCode: 0, + Error: fmt.Sprintf("Invalid HTTP method: %s", request.Method), + } + } + + // Create HTTP client with timeout + timeout := 30 * time.Second + if request.Timeout > 0 { + timeout = time.Duration(request.Timeout) * time.Second + } + client := &http.Client{ + Timeout: timeout, + } + + // Create request + var body io.Reader + if request.Body != "" { + body = bytes.NewBufferString(request.Body) + } + + req, err := http.NewRequestWithContext(context.Background(), method, request.URL, body) + if err != nil { + return HTTPResponse{ + StatusCode: 0, + Error: fmt.Sprintf("Failed to create request: %v", err), + } + } + + // Set headers + for key, value := range request.Headers { + req.Header.Set(key, value) + } + + // Set default User-Agent if not provided + if req.Header.Get("User-Agent") == "" { + req.Header.Set("User-Agent", "Wails/3.0") + } + + // Perform the request + resp, err := client.Do(req) + if err != nil { + return HTTPResponse{ + StatusCode: 0, + Error: fmt.Sprintf("Request failed: %v", err), + } + } + defer resp.Body.Close() + + // Read response body + bodyBytes, err := io.ReadAll(resp.Body) + if err != nil { + return HTTPResponse{ + StatusCode: resp.StatusCode, + Error: fmt.Sprintf("Failed to read response body: %v", err), + } + } + + // Build response headers + responseHeaders := make(map[string]string) + for key, values := range resp.Header { + if len(values) > 0 { + responseHeaders[key] = values[0] + } + } + + return HTTPResponse{ + StatusCode: resp.StatusCode, + Headers: responseHeaders, + Body: string(bodyBytes), + } +} + +// parseHTTPRequest parses the HTTP request from JSON +func parseHTTPRequest(data string) (*HTTPRequest, error) { + var request HTTPRequest + err := json.Unmarshal([]byte(data), &request) + if err != nil { + return nil, fmt.Errorf("failed to parse HTTP request: %v", err) + } + + // Validate required fields + if request.URL == "" { + return nil, fmt.Errorf("URL is required") + } + if request.Method == "" { + request.Method = "GET" + } + + return &request, nil +} \ No newline at end of file diff --git a/v3/pkg/application/image.go b/v3/pkg/application/image.go new file mode 100644 index 000000000..81c519739 --- /dev/null +++ b/v3/pkg/application/image.go @@ -0,0 +1,37 @@ +package application + +import ( + "bytes" + "image" + "image/draw" + "image/png" +) + +func pngToImage(data []byte) (*image.RGBA, error) { + img, err := png.Decode(bytes.NewReader(data)) + if err != nil { + return nil, err + } + + bounds := img.Bounds() + rgba := image.NewRGBA(bounds) + draw.Draw(rgba, bounds, img, bounds.Min, draw.Src) + return rgba, nil +} + +func ToARGB(img *image.RGBA) (int, int, []byte) { + w, h := img.Bounds().Dx(), img.Bounds().Dy() + data := make([]byte, w*h*4) + i := 0 + for y := 0; y < h; y++ { + for x := 0; x < w; x++ { + r, g, b, a := img.At(x, y).RGBA() + data[i] = byte(a) + data[i+1] = byte(r) + data[i+2] = byte(g) + data[i+3] = byte(b) + i += 4 + } + } + return w, h, data +} diff --git a/v3/pkg/application/keys.go b/v3/pkg/application/keys.go new file mode 100644 index 000000000..4a6fc0e64 --- /dev/null +++ b/v3/pkg/application/keys.go @@ -0,0 +1,215 @@ +package application + +import ( + "fmt" + "runtime" + "slices" + "strconv" + "strings" +) + +// modifier is actually a string +type modifier int + +const ( + // CmdOrCtrlKey represents Command on Mac and Control on other platforms + CmdOrCtrlKey modifier = 0 << iota + // OptionOrAltKey represents Option on Mac and Alt on other platforms + OptionOrAltKey modifier = 1 << iota + // ShiftKey represents the shift key on all systems + ShiftKey modifier = 2 << iota + // SuperKey represents Command on Mac and the Windows key on the other platforms + SuperKey modifier = 3 << iota + // ControlKey represents the control key on all systems + ControlKey modifier = 4 << iota +) + +func (m modifier) String() string { + return modifierStringMap[runtime.GOOS][m] +} + +var modifierStringMap = map[string]map[modifier]string{ + "windows": { + CmdOrCtrlKey: "Ctrl", + ControlKey: "Ctrl", + OptionOrAltKey: "Alt", + ShiftKey: "Shift", + SuperKey: "Win", + }, + "darwin": { + CmdOrCtrlKey: "Cmd", + ControlKey: "Ctrl", + OptionOrAltKey: "Option", + ShiftKey: "Shift", + SuperKey: "Cmd", + }, + "linux": { + CmdOrCtrlKey: "Ctrl", + ControlKey: "Ctrl", + OptionOrAltKey: "Alt", + ShiftKey: "Shift", + SuperKey: "Super", + }, +} + +var modifierMap = map[string]modifier{ + "cmdorctrl": CmdOrCtrlKey, + "cmd": CmdOrCtrlKey, + "command": CmdOrCtrlKey, + "ctrl": CmdOrCtrlKey, + "optionoralt": OptionOrAltKey, + "alt": OptionOrAltKey, + "option": OptionOrAltKey, + "shift": ShiftKey, + "super": SuperKey, +} + +// accelerator holds the keyboard shortcut for a menu item +type accelerator struct { + Key string + Modifiers []modifier +} + +func (a *accelerator) String() string { + var result []string + // Sort modifiers + for _, modifier := range a.Modifiers { + result = append(result, modifier.String()) + } + slices.Sort(result) + if len(a.Key) > 0 { + result = append(result, a.Key) + } + return strings.ToLower(strings.Join(result, "+")) +} + +var namedKeys = map[string]struct{}{ + "backspace": {}, + "tab": {}, + "return": {}, + "enter": {}, + "escape": {}, + "left": {}, + "right": {}, + "up": {}, + "down": {}, + "space": {}, + "delete": {}, + "home": {}, + "end": {}, + "page up": {}, + "page down": {}, + "f1": {}, + "f2": {}, + "f3": {}, + "f4": {}, + "f5": {}, + "f6": {}, + "f7": {}, + "f8": {}, + "f9": {}, + "f10": {}, + "f11": {}, + "f12": {}, + "f13": {}, + "f14": {}, + "f15": {}, + "f16": {}, + "f17": {}, + "f18": {}, + "f19": {}, + "f20": {}, + "f21": {}, + "f22": {}, + "f23": {}, + "f24": {}, + "f25": {}, + "f26": {}, + "f27": {}, + "f28": {}, + "f29": {}, + "f30": {}, + "f31": {}, + "f32": {}, + "f33": {}, + "f34": {}, + "f35": {}, + "numlock": {}, +} + +func parseKey(key string) (string, bool) { + + // Lowercase! + key = strings.ToLower(key) + + // Check special case + if key == "plus" { + return "+", true + } + + // Handle named keys + _, namedKey := namedKeys[key] + if namedKey { + return key, true + } + + // Check we only have a single character + if len(key) != 1 { + return "", false + } + + runeKey := rune(key[0]) + + // This may be too inclusive + if strconv.IsPrint(runeKey) { + return key, true + } + + return "", false + +} + +// parseAccelerator parses a string into an accelerator +func parseAccelerator(shortcut string) (*accelerator, error) { + + var result accelerator + + // Split the shortcut by + + components := strings.Split(shortcut, "+") + + // If we only have one it should be a key + // We require components + if len(components) == 0 { + return nil, fmt.Errorf("no components given to validateComponents") + } + + modifiers := map[modifier]struct{}{} + + // Check components + for index, component := range components { + + // If last component + if index == len(components)-1 { + processedKey, validKey := parseKey(component) + if !validKey { + return nil, fmt.Errorf("'%s' is not a valid key", component) + } + result.Key = processedKey + continue + } + + // Not last component - needs to be modifier + lowercaseComponent := strings.ToLower(component) + thisModifier, valid := modifierMap[lowercaseComponent] + if !valid { + return nil, fmt.Errorf("'%s' is not a valid modifier", component) + } + // Save this data + modifiers[thisModifier] = struct{}{} + } + // return the keys as a slice + for thisModifier := range modifiers { + result.Modifiers = append(result.Modifiers, thisModifier) + } + return &result, nil +} diff --git a/v3/pkg/application/keys_darwin.go b/v3/pkg/application/keys_darwin.go new file mode 100644 index 000000000..42e1c4686 --- /dev/null +++ b/v3/pkg/application/keys_darwin.go @@ -0,0 +1,28 @@ +//go:build darwin + +package application + +const ( + NSEventModifierFlagShift = 1 << 17 // Set if Shift key is pressed. + NSEventModifierFlagControl = 1 << 18 // Set if Control key is pressed. + NSEventModifierFlagOption = 1 << 19 // Set if Option or Alternate key is pressed. + NSEventModifierFlagCommand = 1 << 20 // Set if Command key is pressed. +) + +// macModifierMap maps accelerator modifiers to macOS modifiers. +var macModifierMap = map[modifier]int{ + CmdOrCtrlKey: NSEventModifierFlagCommand, + ControlKey: NSEventModifierFlagControl, + OptionOrAltKey: NSEventModifierFlagOption, + ShiftKey: NSEventModifierFlagShift, + SuperKey: NSEventModifierFlagCommand, +} + +// toMacModifier converts the accelerator to a macOS modifier. +func toMacModifier(modifiers []modifier) int { + result := 0 + for _, modifier := range modifiers { + result |= macModifierMap[modifier] + } + return result +} diff --git a/v3/pkg/application/keys_windows.go b/v3/pkg/application/keys_windows.go new file mode 100644 index 000000000..8011a837d --- /dev/null +++ b/v3/pkg/application/keys_windows.go @@ -0,0 +1,230 @@ +//go:build windows + +package application + +var VirtualKeyCodes = map[uint]string{ + 0x01: "lbutton", + 0x02: "rbutton", + 0x03: "cancel", + 0x04: "mbutton", + 0x05: "xbutton1", + 0x06: "xbutton2", + 0x08: "back", + 0x09: "tab", + 0x0C: "clear", + 0x0D: "return", + 0x10: "shift", + 0x11: "ctrl", + 0x12: "menu", + 0x13: "pause", + 0x14: "capital", + 0x15: "kana", + 0x17: "junja", + 0x18: "final", + 0x19: "hanja", + 0x1B: "escape", + 0x1C: "convert", + 0x1D: "nonconvert", + 0x1E: "accept", + 0x1F: "modechange", + 0x20: "space", + 0x21: "prior", + 0x22: "next", + 0x23: "end", + 0x24: "home", + 0x25: "left", + 0x26: "up", + 0x27: "right", + 0x28: "down", + 0x29: "select", + 0x2A: "print", + 0x2B: "execute", + 0x2C: "snapshot", + 0x2D: "insert", + 0x2E: "delete", + 0x2F: "help", + 0x30: "0", + 0x31: "1", + 0x32: "2", + 0x33: "3", + 0x34: "4", + 0x35: "5", + 0x36: "6", + 0x37: "7", + 0x38: "8", + 0x39: "9", + 0x41: "a", + 0x42: "b", + 0x43: "c", + 0x44: "d", + 0x45: "e", + 0x46: "f", + 0x47: "g", + 0x48: "h", + 0x49: "i", + 0x4A: "j", + 0x4B: "k", + 0x4C: "l", + 0x4D: "m", + 0x4E: "n", + 0x4F: "o", + 0x50: "p", + 0x51: "q", + 0x52: "r", + 0x53: "s", + 0x54: "t", + 0x55: "u", + 0x56: "v", + 0x57: "w", + 0x58: "x", + 0x59: "y", + 0x5A: "z", + 0x5B: "lwin", + 0x5C: "rwin", + 0x5D: "apps", + 0x5F: "sleep", + 0x60: "numpad0", + 0x61: "numpad1", + 0x62: "numpad2", + 0x63: "numpad3", + 0x64: "numpad4", + 0x65: "numpad5", + 0x66: "numpad6", + 0x67: "numpad7", + 0x68: "numpad8", + 0x69: "numpad9", + 0x6A: "multiply", + 0x6B: "add", + 0x6C: "separator", + 0x6D: "subtract", + 0x6E: "decimal", + 0x6F: "divide", + 0x70: "f1", + 0x71: "f2", + 0x72: "f3", + 0x73: "f4", + 0x74: "f5", + 0x75: "f6", + 0x76: "f7", + 0x77: "f8", + 0x78: "f9", + 0x79: "f10", + 0x7A: "f11", + 0x7B: "f12", + 0x7C: "f13", + 0x7D: "f14", + 0x7E: "f15", + 0x7F: "f16", + 0x80: "f17", + 0x81: "f18", + 0x82: "f19", + 0x83: "f20", + 0x84: "f21", + 0x85: "f22", + 0x86: "f23", + 0x87: "f24", + 0x88: "navigation_view", + 0x89: "navigation_menu", + 0x8A: "navigation_up", + 0x8B: "navigation_down", + 0x8C: "navigation_left", + 0x8D: "navigation_right", + 0x8E: "navigation_accept", + 0x8F: "navigation_cancel", + 0x90: "numlock", + 0x91: "scroll", + 0x92: "oem_nec_equal", + 0x93: "oem_fj_masshou", + 0x94: "oem_fj_touroku", + 0x95: "oem_fj_loya", + 0x96: "oem_fj_roya", + 0xA0: "lshift", + 0xA1: "rshift", + 0xA2: "lcontrol", + 0xA3: "rcontrol", + 0xA4: "lmenu", + 0xA5: "rmenu", + 0xA6: "browser_back", + 0xA7: "browser_forward", + 0xA8: "browser_refresh", + 0xA9: "browser_stop", + 0xAA: "browser_search", + 0xAB: "browser_favorites", + 0xAC: "browser_home", + 0xAD: "volume_mute", + 0xAE: "volume_down", + 0xAF: "volume_up", + 0xB0: "media_next_track", + 0xB1: "media_prev_track", + 0xB2: "media_stop", + 0xB3: "media_play_pause", + 0xB4: "launch_mail", + 0xB5: "launch_media_select", + 0xB6: "launch_app1", + 0xB7: "launch_app2", + 0xBA: "oem_1", + 0xBB: "oem_plus", + 0xBC: "oem_comma", + 0xBD: "oem_minus", + 0xBE: "oem_period", + 0xBF: "oem_2", + 0xC0: "oem_3", + 0xC3: "gamepad_a", + 0xC4: "gamepad_b", + 0xC5: "gamepad_x", + 0xC6: "gamepad_y", + 0xC7: "gamepad_right_shoulder", + 0xC8: "gamepad_left_shoulder", + 0xC9: "gamepad_left_trigger", + 0xCA: "gamepad_right_trigger", + 0xCB: "gamepad_dpad_up", + 0xCC: "gamepad_dpad_down", + 0xCD: "gamepad_dpad_left", + 0xCE: "gamepad_dpad_right", + 0xCF: "gamepad_menu", + 0xD0: "gamepad_view", + 0xD1: "gamepad_left_thumbstick_button", + 0xD2: "gamepad_right_thumbstick_button", + 0xD3: "gamepad_left_thumbstick_up", + 0xD4: "gamepad_left_thumbstick_down", + 0xD5: "gamepad_left_thumbstick_right", + 0xD6: "gamepad_left_thumbstick_left", + 0xD7: "gamepad_right_thumbstick_up", + 0xD8: "gamepad_right_thumbstick_down", + 0xD9: "gamepad_right_thumbstick_right", + 0xDA: "gamepad_right_thumbstick_left", + 0xDB: "oem_4", + 0xDC: "oem_5", + 0xDD: "oem_6", + 0xDE: "oem_7", + 0xDF: "oem_8", + 0xE1: "oem_ax", + 0xE2: "oem_102", + 0xE3: "ico_help", + 0xE4: "ico_00", + 0xE5: "processkey", + 0xE6: "ico_clear", + 0xE7: "packet", + 0xE9: "oem_reset", + 0xEA: "oem_jump", + 0xEB: "oem_pa1", + 0xEC: "oem_pa2", + 0xED: "oem_pa3", + 0xEE: "oem_wsctrl", + 0xEF: "oem_cusel", + 0xF0: "oem_attn", + 0xF1: "oem_finish", + 0xF2: "oem_copy", + 0xF3: "oem_auto", + 0xF4: "oem_enlw", + 0xF5: "oem_backtab", + 0xF6: "attn", + 0xF7: "crsel", + 0xF8: "exsel", + 0xF9: "ereof", + 0xFA: "play", + 0xFB: "zoom", + 0xFC: "noname", + 0xFD: "pa1", + 0xFE: "oem_clear", +} diff --git a/v3/pkg/application/linux_cgo.go b/v3/pkg/application/linux_cgo.go new file mode 100644 index 000000000..06b20a7ef --- /dev/null +++ b/v3/pkg/application/linux_cgo.go @@ -0,0 +1,1264 @@ +//go:build linux && cgo + +package application + +import ( + "fmt" + "strings" + "unsafe" + + "github.com/wailsapp/wails/v3/internal/assetserver/webview" + "github.com/wailsapp/wails/v3/pkg/events" +) + +/* +#cgo linux pkg-config: gtk+-3.0 webkit2gtk-4.0 + +#include +#include +#include +#include +#include +#include +#ifdef G_APPLICATION_DEFAULT_FLAGS + #define APPLICATION_DEFAULT_FLAGS G_APPLICATION_DEFAULT_FLAGS +#else + #define APPLICATION_DEFAULT_FLAGS G_APPLICATION_FLAGS_NONE +#endif + +typedef struct CallbackID +{ + unsigned int value; +} CallbackID; + +extern void dispatchOnMainThreadCallback(unsigned int); + +static gboolean dispatchCallback(gpointer data) { + struct CallbackID *args = data; + unsigned int cid = args->value; + dispatchOnMainThreadCallback(cid); + free(args); + + return G_SOURCE_REMOVE; +}; + +static void dispatchOnMainThread(unsigned int id) { + CallbackID *args = malloc(sizeof(CallbackID)); + args->value = id; + g_idle_add((GSourceFunc)dispatchCallback, (gpointer)args); +} + +typedef struct WindowEvent { + uint id; + uint event; +} WindowEvent; + +// exported below +void activateLinux(gpointer data); +extern void emit(WindowEvent* data); +void handleClick(void*); +extern gboolean onButtonEvent(GtkWidget *widget, GdkEventButton *event, gpointer user_data); +extern void onDragNDrop( + void *target, + GdkDragContext* context, + gint x, + gint y, + gpointer seldata, + guint info, + guint time, + gpointer data); +extern gboolean onKeyPressEvent (GtkWidget *widget, GdkEventKey *event, gpointer user_data); +extern void onProcessRequest(void *request, gpointer user_data); +// exported below (end) + +static void signal_connect(GtkWidget *widget, char *event, void *cb, void* data) { + // g_signal_connect is a macro and can't be called directly + g_signal_connect(widget, event, cb, data); +} + +static void* new_message_dialog(GtkWindow *parent, const gchar *msg, int dialogType, bool hasButtons) { + // gtk_message_dialog_new is variadic! Can't call from cgo directly + GtkWidget *dialog; + int buttonMask; + + // buttons will be added after creation + buttonMask = GTK_BUTTONS_OK; + if (hasButtons) { + buttonMask = GTK_BUTTONS_NONE; + } + + dialog = gtk_message_dialog_new( + parent, + GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT, + dialogType, + buttonMask, + "%s", + msg); + + // g_signal_connect_swapped (dialog, + // "response", + // G_CALLBACK (callback), + // dialog); + return dialog; +}; + +extern void messageDialogCB(gint button); + +static void* gtkFileChooserDialogNew(char* title, GtkWindow* window, GtkFileChooserAction action, char* cancelLabel, char* acceptLabel) { + // gtk_file_chooser_dialog_new is variadic! Can't call from cgo directly + return (GtkFileChooser*)gtk_file_chooser_dialog_new( + title, + window, + action, + cancelLabel, + GTK_RESPONSE_CANCEL, + acceptLabel, + GTK_RESPONSE_ACCEPT, + NULL); +} + +typedef struct Screen { + const char* id; + const char* name; + int p_width; + int p_height; + int width; + int height; + int x; + int y; + int w_width; + int w_height; + int w_x; + int w_y; + float scale; + double rotation; + bool isPrimary; +} Screen; + + +static int GetNumScreens(){ + return 0; +} +*/ +import "C" + +type windowPointer *C.GtkWindow +type identifier C.uint +type pointer unsafe.Pointer +type GSList C.GSList +type GSListPointer *GSList + +var ( + nilPointer pointer = nil + nilRadioGroup GSListPointer = nil +) + +var ( + gtkSignalToMenuItem map[uint]*MenuItem + mainThreadId *C.GThread +) + +func init() { + gtkSignalToMenuItem = map[uint]*MenuItem{} + + mainThreadId = C.g_thread_self() +} + +// mainthread stuff +func dispatchOnMainThread(id uint) { + C.dispatchOnMainThread(C.uint(id)) +} + +//export dispatchOnMainThreadCallback +func dispatchOnMainThreadCallback(callbackID C.uint) { + executeOnMainThread(uint(callbackID)) +} + +//export activateLinux +func activateLinux(data pointer) { + // NOOP: Callback for now +} + +func isOnMainThread() bool { + threadId := C.g_thread_self() + return threadId == mainThreadId +} + +// implementation below +func appName() string { + name := C.g_get_application_name() + defer C.free(unsafe.Pointer(name)) + return C.GoString(name) +} + +func appNew(name string) pointer { + nameC := C.CString(fmt.Sprintf("org.wails.%s", name)) + defer C.free(unsafe.Pointer(nameC)) + return pointer(C.gtk_application_new(nameC, C.APPLICATION_DEFAULT_FLAGS)) +} + +func appRun(app pointer) error { + application := (*C.GApplication)(app) + C.g_application_hold(application) // allows it to run without a window + + signal := C.CString("activate") + defer C.free(unsafe.Pointer(signal)) + C.g_signal_connect_data( + C.gpointer(application), + signal, + C.GCallback(C.activateLinux), + nil, + nil, + 0) + status := C.g_application_run(application, 0, nil) + C.g_application_release(application) + C.g_object_unref(C.gpointer(app)) + + var err error + if status != 0 { + err = fmt.Errorf("exit code: %d", status) + } + return err +} + +func appDestroy(application pointer) { + C.g_application_quit((*C.GApplication)(application)) +} + +func getCurrentWindowID(application pointer, windows map[windowPointer]uint) uint { + // TODO: Add extra metadata to window and use it! + window := (*C.GtkWindow)(C.gtk_application_get_active_window((*C.GtkApplication)(application))) + if window == nil { + return uint(1) + } + identifier, ok := windows[window] + if ok { + return identifier + } + // FIXME: Should we panic here if not found? + return uint(1) +} + +func getWindows(application pointer) []pointer { + result := []pointer{} + windows := C.gtk_application_get_windows((*C.GtkApplication)(application)) + for { + result = append(result, pointer(windows.data)) + windows = windows.next + if windows == nil { + return result + } + } +} + +func hideAllWindows(application pointer) { + for _, window := range getWindows(application) { + C.gtk_widget_hide((*C.GtkWidget)(window)) + } +} + +func showAllWindows(application pointer) { + for _, window := range getWindows(application) { + C.gtk_window_present((*C.GtkWindow)(window)) + } +} + +// Clipboard +func clipboardGet() string { + clip := C.gtk_clipboard_get(C.GDK_SELECTION_CLIPBOARD) + text := C.gtk_clipboard_wait_for_text(clip) + return C.GoString(text) +} + +func clipboardSet(text string) { + cText := C.CString(text) + clip := C.gtk_clipboard_get(C.GDK_SELECTION_CLIPBOARD) + C.gtk_clipboard_set_text(clip, cText, -1) + + clip = C.gtk_clipboard_get(C.GDK_SELECTION_PRIMARY) + C.gtk_clipboard_set_text(clip, cText, -1) + C.free(unsafe.Pointer(cText)) +} + +// Menu +func menuAddSeparator(menu *Menu) { + C.gtk_menu_shell_append( + (*C.GtkMenuShell)((menu.impl).(*linuxMenu).native), + C.gtk_separator_menu_item_new()) +} + +func menuAppend(parent *Menu, menu *MenuItem) { + C.gtk_menu_shell_append( + (*C.GtkMenuShell)((parent.impl).(*linuxMenu).native), + (*C.GtkWidget)((menu.impl).(*linuxMenuItem).native), + ) + /* gtk4 + C.gtk_menu_item_set_submenu( + (*C.struct__GtkMenuItem)((menu.impl).(*linuxMenuItem).native), + (*C.struct__GtkWidget)((parent.impl).(*linuxMenu).native), + ) + */ +} + +func menuBarNew() pointer { + return pointer(C.gtk_menu_bar_new()) +} + +func menuNew() pointer { + return pointer(C.gtk_menu_new()) +} + +func menuSetSubmenu(item *MenuItem, menu *Menu) { + C.gtk_menu_item_set_submenu( + (*C.GtkMenuItem)((item.impl).(*linuxMenuItem).native), + (*C.GtkWidget)((menu.impl).(*linuxMenu).native)) +} + +func menuGetRadioGroup(item *linuxMenuItem) *GSList { + return (*GSList)(C.gtk_radio_menu_item_get_group((*C.GtkRadioMenuItem)(item.native))) +} + +//export handleClick +func handleClick(idPtr unsafe.Pointer) { + ident := C.CString("id") + defer C.free(unsafe.Pointer(ident)) + value := C.g_object_get_data((*C.GObject)(idPtr), ident) + id := uint(*(*C.uint)(value)) + item, ok := gtkSignalToMenuItem[id] + if !ok { + return + } + switch item.itemType { + case text, checkbox: + menuItemClicked <- item.id + case radio: + menuItem := (item.impl).(*linuxMenuItem) + if menuItem.isChecked() { + menuItemClicked <- item.id + } + } +} + +func attachMenuHandler(item *MenuItem) uint { + signal := C.CString("activate") + defer C.free(unsafe.Pointer(signal)) + impl := (item.impl).(*linuxMenuItem) + widget := impl.native + flags := C.GConnectFlags(0) + handlerId := C.g_signal_connect_object( + C.gpointer(widget), + signal, + C.GCallback(C.handleClick), + C.gpointer(widget), + flags) + + id := C.uint(item.id) + ident := C.CString("id") + defer C.free(unsafe.Pointer(ident)) + C.g_object_set_data( + (*C.GObject)(widget), + ident, + C.gpointer(&id), + ) + + gtkSignalToMenuItem[item.id] = item + return uint(handlerId) +} + +// menuItem +func menuItemChecked(widget pointer) bool { + if C.gtk_check_menu_item_get_active((*C.GtkCheckMenuItem)(widget)) == C.int(1) { + return true + } + return false +} + +func menuItemNew(label string, bitmap []byte) pointer { + return menuItemAddProperties(C.gtk_menu_item_new(), label, bitmap) +} + +func menuItemAddProperties(menuItem *C.GtkWidget, label string, bitmap []byte) pointer { + /* + // FIXME: Support accelerator configuration + activate := C.CString("activate") + defer C.free(unsafe.Pointer(activate)) + accelGroup := C.gtk_accel_group_new() + C.gtk_widget_add_accelerator(menuItem, activate, accelGroup, + C.GDK_KEY_m, C.GDK_CONTROL_MASK, C.GTK_ACCEL_VISIBLE) + */ + cLabel := C.CString(label) + defer C.free(unsafe.Pointer(cLabel)) + lbl := unsafe.Pointer(C.gtk_accel_label_new(cLabel)) + C.gtk_label_set_use_underline((*C.GtkLabel)(lbl), 1) + C.gtk_label_set_xalign((*C.GtkLabel)(lbl), 0.0) + C.gtk_accel_label_set_accel_widget( + (*C.GtkAccelLabel)(lbl), + (*C.GtkWidget)(unsafe.Pointer(menuItem))) + + box := C.gtk_box_new(C.GTK_ORIENTATION_HORIZONTAL, 6) + if img, err := pngToImage(bitmap); err == nil { + gbytes := C.g_bytes_new_static(C.gconstpointer(unsafe.Pointer(&img.Pix[0])), + C.ulong(len(img.Pix))) + defer C.g_bytes_unref(gbytes) + pixBuf := C.gdk_pixbuf_new_from_bytes( + gbytes, + C.GDK_COLORSPACE_RGB, + 1, // has_alpha + 8, + C.int(img.Bounds().Dx()), + C.int(img.Bounds().Dy()), + C.int(img.Stride), + ) + image := C.gtk_image_new_from_pixbuf(pixBuf) + C.gtk_widget_set_visible((*C.GtkWidget)(image), C.gboolean(1)) + C.gtk_container_add( + (*C.GtkContainer)(unsafe.Pointer(box)), + (*C.GtkWidget)(unsafe.Pointer(image))) + } + + C.gtk_box_pack_end( + (*C.GtkBox)(unsafe.Pointer(box)), + (*C.GtkWidget)(lbl), 1, 1, 0) + C.gtk_container_add( + (*C.GtkContainer)(unsafe.Pointer(menuItem)), + (*C.GtkWidget)(unsafe.Pointer(box))) + C.gtk_widget_show_all(menuItem) + return pointer(menuItem) +} + +func menuCheckItemNew(label string, bitmap []byte) pointer { + return menuItemAddProperties(C.gtk_check_menu_item_new(), label, bitmap) +} + +func menuItemSetChecked(widget pointer, checked bool) { + value := C.int(0) + if checked { + value = C.int(1) + } + C.gtk_check_menu_item_set_active( + (*C.GtkCheckMenuItem)(widget), + value) +} + +func menuItemSetDisabled(widget pointer, disabled bool) { + value := C.int(1) + if disabled { + value = C.int(0) + } + C.gtk_widget_set_sensitive( + (*C.GtkWidget)(widget), + value) +} + +func menuItemSetLabel(widget pointer, label string) { + value := C.CString(label) + C.gtk_menu_item_set_label( + (*C.GtkMenuItem)(widget), + value) + C.free(unsafe.Pointer(value)) +} + +func menuItemRemoveBitmap(widget pointer) { + box := C.gtk_bin_get_child((*C.GtkBin)(widget)) + if box == nil { + return + } + + children := C.gtk_container_get_children((*C.GtkContainer)(unsafe.Pointer(box))) + defer C.g_list_free(children) + count := int(C.g_list_length(children)) + if count == 2 { + C.gtk_container_remove((*C.GtkContainer)(unsafe.Pointer(box)), + (*C.GtkWidget)(children.data)) + } +} + +func menuItemSetBitmap(widget pointer, bitmap []byte) { + menuItemRemoveBitmap(widget) + box := C.gtk_bin_get_child((*C.GtkBin)(widget)) + if img, err := pngToImage(bitmap); err == nil { + gbytes := C.g_bytes_new_static(C.gconstpointer(unsafe.Pointer(&img.Pix[0])), + C.ulong(len(img.Pix))) + defer C.g_bytes_unref(gbytes) + pixBuf := C.gdk_pixbuf_new_from_bytes( + gbytes, + C.GDK_COLORSPACE_RGB, + 1, // has_alpha + 8, + C.int(img.Bounds().Dx()), + C.int(img.Bounds().Dy()), + C.int(img.Stride), + ) + image := C.gtk_image_new_from_pixbuf(pixBuf) + C.gtk_widget_set_visible((*C.GtkWidget)(image), C.gboolean(1)) + C.gtk_container_add( + (*C.GtkContainer)(unsafe.Pointer(box)), + (*C.GtkWidget)(unsafe.Pointer(image))) + } + +} + +func menuItemSetToolTip(widget pointer, tooltip string) { + value := C.CString(tooltip) + C.gtk_widget_set_tooltip_text( + (*C.GtkWidget)(widget), + value) + C.free(unsafe.Pointer(value)) +} + +func menuItemSignalBlock(widget pointer, handlerId uint, block bool) { + if block { + C.g_signal_handler_block(C.gpointer(widget), C.ulong(handlerId)) + } else { + C.g_signal_handler_unblock(C.gpointer(widget), C.ulong(handlerId)) + } +} + +func menuRadioItemNew(group *GSList, label string) pointer { + cLabel := C.CString(label) + defer C.free(unsafe.Pointer(cLabel)) + return pointer(C.gtk_radio_menu_item_new_with_label((*C.GSList)(group), cLabel)) +} + +// screen related + +func getScreenByIndex(display *C.struct__GdkDisplay, index int) *Screen { + monitor := C.gdk_display_get_monitor(display, C.int(index)) + // TODO: Do we need to update Screen to contain current info? + // currentMonitor := C.gdk_display_get_monitor_at_window(display, window) + + var geometry C.GdkRectangle + C.gdk_monitor_get_geometry(monitor, &geometry) + primary := false + if C.gdk_monitor_is_primary(monitor) == 1 { + primary = true + } + + return &Screen{ + IsPrimary: primary, + Scale: 1.0, + X: int(geometry.x), + Y: int(geometry.y), + Size: Size{ + Height: int(geometry.height), + Width: int(geometry.width), + }, + } +} + +func getScreens(app pointer) ([]*Screen, error) { + var screens []*Screen + window := C.gtk_application_get_active_window((*C.GtkApplication)(app)) + display := C.gdk_window_get_display((*C.GdkWindow)(unsafe.Pointer(window))) + count := C.gdk_display_get_n_monitors(display) + for i := 0; i < int(count); i++ { + screens = append(screens, getScreenByIndex(display, i)) + } + return screens, nil +} + +// widgets +func widgetSetSensitive(widget pointer, enabled bool) { + value := C.int(0) + if enabled { + value = C.int(1) + } + + C.gtk_widget_set_sensitive((*C.GtkWidget)(widget), value) +} + +func widgetSetVisible(widget pointer, hidden bool) { + if hidden { + C.gtk_widget_hide((*C.GtkWidget)(widget)) + } else { + C.gtk_widget_show((*C.GtkWidget)(widget)) + } +} + +// window related functions +func windowClose(window pointer) { + C.gtk_window_close((*C.GtkWindow)(window)) +} + +func windowEnableDND(id uint, webview pointer) { + dnd := C.CString("text/uri-list") + defer C.free(unsafe.Pointer(dnd)) + targetentry := C.gtk_target_entry_new(dnd, 0, C.guint(id)) + defer C.gtk_target_entry_free(targetentry) + C.gtk_drag_dest_set((*C.GtkWidget)(webview), C.GTK_DEST_DEFAULT_DROP, targetentry, 1, C.GDK_ACTION_COPY) + event := C.CString("drag-data-received") + defer C.free(unsafe.Pointer(event)) + windowId := C.uint(id) + C.signal_connect((*C.GtkWidget)(unsafe.Pointer(webview)), event, C.onDragNDrop, unsafe.Pointer(C.gpointer(&windowId))) +} + +func windowExecJS(webview pointer, js string) { + value := C.CString(js) + C.webkit_web_view_evaluate_javascript((*C.WebKitWebView)(webview), + value, + C.long(len(js)), + nil, + C.CString(""), + nil, + nil, + nil) + C.free(unsafe.Pointer(value)) +} + +func windowDestroy(window pointer) { + // Should this truly 'destroy' ? + C.gtk_window_close((*C.GtkWindow)(window)) + //C.gtk_widget_destroy((*C.GtkWidget)(window)) +} + +func windowFullscreen(window pointer) { + C.gtk_window_fullscreen((*C.GtkWindow)(window)) +} + +func windowGetCurrentMonitor(window pointer) *C.GdkMonitor { + // Get the monitor that the window is currently on + display := C.gtk_widget_get_display((*C.GtkWidget)(window)) + gdk_window := C.gtk_widget_get_window((*C.GtkWidget)(window)) + if gdk_window == nil { + return nil + } + return C.gdk_display_get_monitor_at_window(display, gdk_window) +} + +func windowGetCurrentMonitorGeometry(window pointer) (x int, y int, width int, height int, scale int) { + monitor := windowGetCurrentMonitor(window) + if monitor == nil { + return -1, -1, -1, -1, 1 + } + var result C.GdkRectangle + C.gdk_monitor_get_geometry(monitor, &result) + scale = int(C.gdk_monitor_get_scale_factor(monitor)) + return int(result.x), int(result.y), int(result.width), int(result.height), scale +} + +func windowGetAbsolutePosition(window pointer) (int, int) { + var x C.int + var y C.int + C.gtk_window_get_position((*C.GtkWindow)(window), &x, &y) + return int(x), int(y) +} + +func windowGetSize(window pointer) (int, int) { + var windowWidth C.int + var windowHeight C.int + C.gtk_window_get_size((*C.GtkWindow)(window), &windowWidth, &windowHeight) + return int(windowWidth), int(windowHeight) +} + +func windowGetRelativePosition(window pointer) (int, int) { + x, y := windowGetAbsolutePosition(window) + // The position must be relative to the screen it is on + // We need to get the screen it is on + monitor := windowGetCurrentMonitor(window) + geometry := C.GdkRectangle{} + C.gdk_monitor_get_geometry(monitor, &geometry) + x = x - int(geometry.x) + y = y - int(geometry.y) + + // TODO: Scale based on DPI + + return x, y +} + +func windowHide(window pointer) { + C.gtk_widget_hide((*C.GtkWidget)(window)) +} + +func windowIsFullscreen(window pointer) bool { + gdkwindow := C.gtk_widget_get_window((*C.GtkWidget)(window)) + state := C.gdk_window_get_state(gdkwindow) + return state&C.GDK_WINDOW_STATE_FULLSCREEN > 0 +} + +func windowIsFocused(window pointer) bool { + // returns true if window is focused + return C.gtk_window_has_toplevel_focus((*C.GtkWindow)(window)) == 1 +} + +func windowIsMaximized(window pointer) bool { + gdkwindow := C.gtk_widget_get_window((*C.GtkWidget)(window)) + state := C.gdk_window_get_state(gdkwindow) + return state&C.GDK_WINDOW_STATE_MAXIMIZED > 0 && state&C.GDK_WINDOW_STATE_FULLSCREEN == 0 +} + +func windowIsMinimized(window pointer) bool { + gdkwindow := C.gtk_widget_get_window((*C.GtkWidget)(window)) + state := C.gdk_window_get_state(gdkwindow) + return state&C.GDK_WINDOW_STATE_ICONIFIED > 0 +} + +func windowIsVisible(window pointer) bool { + if C.gtk_widget_is_visible((*C.GtkWidget)(window)) == 1 { + return true + } + return false +} + +func windowMaximize(window pointer) { + C.gtk_window_maximize((*C.GtkWindow)(window)) +} + +func windowMinimize(window pointer) { + C.gtk_window_iconify((*C.GtkWindow)(window)) +} + +func windowNew(application pointer, menu pointer, windowId uint, gpuPolicy int) (window, webview, vbox pointer) { + window = pointer(C.gtk_application_window_new((*C.GtkApplication)(application))) + C.g_object_ref_sink(C.gpointer(window)) + webview = windowNewWebview(windowId, gpuPolicy) + vbox = pointer(C.gtk_box_new(C.GTK_ORIENTATION_VERTICAL, 0)) + name := C.CString("webview-box") + defer C.free(unsafe.Pointer(name)) + C.gtk_widget_set_name((*C.GtkWidget)(vbox), name) + + C.gtk_container_add((*C.GtkContainer)(window), (*C.GtkWidget)(vbox)) + if menu != nil { + C.gtk_box_pack_start((*C.GtkBox)(vbox), (*C.GtkWidget)(menu), 0, 0, 0) + } + C.gtk_box_pack_start((*C.GtkBox)(unsafe.Pointer(vbox)), (*C.GtkWidget)(webview), 1, 1, 0) + return +} + +func windowNewWebview(parentId uint, gpuPolicy int) pointer { + manager := C.webkit_user_content_manager_new() + external := C.CString("external") + C.webkit_user_content_manager_register_script_message_handler(manager, external) + C.free(unsafe.Pointer(external)) + webview := C.webkit_web_view_new_with_user_content_manager(manager) + id := C.uint(parentId) + if !registered { + wails := C.CString("wails") + C.webkit_web_context_register_uri_scheme( + C.webkit_web_context_get_default(), + wails, + C.WebKitURISchemeRequestCallback(C.onProcessRequest), + C.gpointer(&id), + nil) + registered = true + C.free(unsafe.Pointer(wails)) + } + settings := C.webkit_web_view_get_settings((*C.WebKitWebView)(unsafe.Pointer(webview))) + wails_io := C.CString("wails.io") + empty := C.CString("") + defer C.free(unsafe.Pointer(wails_io)) + defer C.free(unsafe.Pointer(empty)) + C.webkit_settings_set_user_agent_with_application_details(settings, wails_io, empty) + + switch gpuPolicy { + case 0: + C.webkit_settings_set_hardware_acceleration_policy(settings, C.WEBKIT_HARDWARE_ACCELERATION_POLICY_ALWAYS) + break + case 1: + C.webkit_settings_set_hardware_acceleration_policy(settings, C.WEBKIT_HARDWARE_ACCELERATION_POLICY_ON_DEMAND) + break + case 2: + C.webkit_settings_set_hardware_acceleration_policy(settings, C.WEBKIT_HARDWARE_ACCELERATION_POLICY_NEVER) + break + default: + C.webkit_settings_set_hardware_acceleration_policy(settings, C.WEBKIT_HARDWARE_ACCELERATION_POLICY_ON_DEMAND) + } + return pointer(webview) +} + +func windowPresent(window pointer) { + C.gtk_window_present((*C.GtkWindow)(window)) + // gtk_window_unminimize ((*C.GtkWindow)(w.window)) /// gtk4 +} + +func windowReload(webview pointer, address string) { + uri := C.CString(address) + C.webkit_web_view_load_uri((*C.WebKitWebView)(webview), uri) + C.free(unsafe.Pointer(uri)) +} + +func windowResize(window pointer, width, height int) { + C.gtk_window_resize( + (*C.GtkWindow)(window), + C.gint(width), + C.gint(height)) +} + +func windowShow(window pointer) { + C.gtk_widget_show_all((*C.GtkWidget)(window)) +} + +func windowSetBackgroundColour(vbox, webview pointer, colour RGBA) { + rgba := C.GdkRGBA{C.double(colour.Red) / 255.0, C.double(colour.Green) / 255.0, C.double(colour.Blue) / 255.0, C.double(colour.Alpha) / 255.0} + C.webkit_web_view_set_background_color((*C.WebKitWebView)(webview), &rgba) + + colour.Alpha = 255 + cssStr := C.CString(fmt.Sprintf("#webview-box {background-color: rgba(%d, %d, %d, %1.1f);}", colour.Red, colour.Green, colour.Blue, float32(colour.Alpha)/255.0)) + provider := C.gtk_css_provider_new() + C.gtk_style_context_add_provider( + C.gtk_widget_get_style_context((*C.GtkWidget)(vbox)), + (*C.GtkStyleProvider)(unsafe.Pointer(provider)), + C.GTK_STYLE_PROVIDER_PRIORITY_USER) + C.g_object_unref(C.gpointer(provider)) + C.gtk_css_provider_load_from_data(provider, cssStr, -1, nil) + C.free(unsafe.Pointer(cssStr)) +} + +func windowSetGeometryHints(window pointer, minWidth, minHeight, maxWidth, maxHeight int) { + size := C.GdkGeometry{ + min_width: C.int(minWidth), + min_height: C.int(minHeight), + max_width: C.int(maxWidth), + max_height: C.int(maxHeight), + } + C.gtk_window_set_geometry_hints((*C.GtkWindow)(window), nil, &size, C.GDK_HINT_MAX_SIZE|C.GDK_HINT_MIN_SIZE) +} + +func windowSetFrameless(window pointer, frameless bool) { + C.gtk_window_set_decorated((*C.GtkWindow)(window), gtkBool(!frameless)) + // TODO: Deal with transparency for the titlebar if possible when !frameless + // Perhaps we just make it undecorated and add a menu bar inside? +} + +// TODO: confirm this is working properly +func windowSetHTML(webview pointer, html string) { + cHTML := C.CString(html) + uri := C.CString("wails://") + empty := C.CString("") + defer C.free(unsafe.Pointer(cHTML)) + defer C.free(unsafe.Pointer(uri)) + defer C.free(unsafe.Pointer(empty)) + C.webkit_web_view_load_alternate_html( + (*C.WebKitWebView)(webview), + cHTML, + uri, + empty) +} + +func windowSetKeepAbove(window pointer, alwaysOnTop bool) { + C.gtk_window_set_keep_above((*C.GtkWindow)(window), gtkBool(alwaysOnTop)) +} + +func windowSetResizable(window pointer, resizable bool) { + C.gtk_window_set_resizable((*C.GtkWindow)(window), gtkBool(resizable)) +} + +func windowSetTitle(window pointer, title string) { + cTitle := C.CString(title) + C.gtk_window_set_title((*C.GtkWindow)(window), cTitle) + C.free(unsafe.Pointer(cTitle)) +} + +func windowSetTransparent(window pointer) { + screen := C.gtk_widget_get_screen((*C.GtkWidget)(window)) + visual := C.gdk_screen_get_rgba_visual(screen) + + if visual != nil && C.gdk_screen_is_composited(screen) == C.int(1) { + C.gtk_widget_set_app_paintable((*C.GtkWidget)(window), C.gboolean(1)) + C.gtk_widget_set_visual((*C.GtkWidget)(window), visual) + } +} + +func windowSetURL(webview pointer, uri string) { + target := C.CString(uri) + C.webkit_web_view_load_uri((*C.WebKitWebView)(webview), target) + C.free(unsafe.Pointer(target)) +} + +//export emit +func emit(we *C.WindowEvent) { + window := globalApplication.getWindowForID(uint(we.id)) + if window != nil { + windowEvents <- &windowEvent{ + WindowID: window.ID(), + EventID: uint(events.WindowEventType(we.event)), + } + } +} + +func windowSetupSignalHandlers(windowId uint, window, webview pointer, emit func(e events.WindowEventType)) { + event := C.CString("delete-event") + defer C.free(unsafe.Pointer(event)) + wEvent := C.WindowEvent{ + id: C.uint(windowId), + event: C.uint(events.Common.WindowClosing), + } + C.signal_connect((*C.GtkWidget)(window), event, C.emit, unsafe.Pointer(&wEvent)) + + /* + event = C.CString("load-changed") + defer C.free(unsafe.Pointer(event)) + C.signal_connect(webview, event, C.webviewLoadChanged, unsafe.Pointer(&w.parent.id)) + */ + id := C.uint(windowId) + event = C.CString("button-press-event") + C.signal_connect((*C.GtkWidget)(unsafe.Pointer(webview)), event, C.onButtonEvent, unsafe.Pointer(&id)) + C.free(unsafe.Pointer(event)) + event = C.CString("button-release-event") + defer C.free(unsafe.Pointer(event)) + C.signal_connect((*C.GtkWidget)(unsafe.Pointer(webview)), event, C.onButtonEvent, unsafe.Pointer(&id)) + + event = C.CString("key-press-event") + defer C.free(unsafe.Pointer(event)) + C.signal_connect((*C.GtkWidget)(unsafe.Pointer(webview)), event, C.onKeyPressEvent, unsafe.Pointer(&id)) +} + +func windowToggleDevTools(webview pointer) { + settings := C.webkit_web_view_get_settings((*C.WebKitWebView)(webview)) + enabled := C.webkit_settings_get_enable_developer_extras(settings) + switch enabled { + case C.int(0): + enabled = C.int(1) + case C.int(1): + enabled = C.int(0) + } + C.webkit_settings_set_enable_developer_extras(settings, enabled) +} + +func windowUnfullscreen(window pointer) { + C.gtk_window_unfullscreen((*C.GtkWindow)(window)) +} + +func windowUnmaximize(window pointer) { + C.gtk_window_unmaximize((*C.GtkWindow)(window)) +} + +func windowZoom(webview pointer) float64 { + return float64(C.webkit_web_view_get_zoom_level((*C.WebKitWebView)(webview))) +} + +// FIXME: ZoomIn/Out is assumed to be incorrect! +func windowZoomIn(webview pointer) { + ZoomInFactor := 1.10 + windowZoomSet(webview, windowZoom(webview)*ZoomInFactor) +} +func windowZoomOut(webview pointer) { + ZoomOutFactor := -1.10 + windowZoomSet(webview, windowZoom(webview)*ZoomOutFactor) +} + +func windowZoomSet(webview pointer, zoom float64) { + if zoom < 1 { // 1.0 is the smallest allowable + zoom = 1 + } + C.webkit_web_view_set_zoom_level((*C.WebKitWebView)(webview), C.double(zoom)) +} + +func windowMove(window pointer, x, y int) { + C.gtk_window_move((*C.GtkWindow)(window), C.int(x), C.int(y)) +} + +//export onButtonEvent +func onButtonEvent(_ *C.GtkWidget, event *C.GdkEventButton, data unsafe.Pointer) C.gboolean { + // Constants (defined here to be easier to use with ) + GdkButtonPress := C.GDK_BUTTON_PRESS // 4 + Gdk2ButtonPress := C.GDK_2BUTTON_PRESS // 5 for double-click + GdkButtonRelease := C.GDK_BUTTON_RELEASE // 7 + + windowId := uint(*((*C.uint)(data))) + window := globalApplication.getWindowForID(windowId) + if window == nil { + return C.gboolean(0) + } + lw, ok := (window.(*WebviewWindow).impl).(*linuxWebviewWindow) + if !ok { + return C.gboolean(0) + } + + if event == nil { + return C.gboolean(0) + } + if event.button == 3 { + return C.gboolean(0) + } + + switch int(event._type) { + case GdkButtonPress: + lw.startDrag() //uint(event.button), int(event.x_root), int(event.y_root)) + case Gdk2ButtonPress: + // do we need something here? + case GdkButtonRelease: + lw.endDrag(uint(event.button), int(event.x_root), int(event.y_root)) + } + + return C.gboolean(0) +} + +//export onDragNDrop +func onDragNDrop(target unsafe.Pointer, context *C.GdkDragContext, x C.gint, y C.gint, seldata unsafe.Pointer, info C.guint, time C.guint, data unsafe.Pointer) { + var length C.gint + selection := unsafe.Pointer(C.gtk_selection_data_get_data_with_length((*C.GtkSelectionData)(seldata), &length)) + extracted := C.g_uri_list_extract_uris((*C.char)(selection)) + defer C.g_strfreev(extracted) + + uris := unsafe.Slice( + (**C.char)(unsafe.Pointer(extracted)), + int(length)) + + var filenames []string + for _, uri := range uris { + if uri == nil { + break + } + filenames = append(filenames, strings.TrimPrefix(C.GoString(uri), "file://")) + } + windowDragAndDropBuffer <- &dragAndDropMessage{ + windowId: uint(*((*C.uint)(data))), + filenames: filenames, + } + C.gtk_drag_finish(context, C.true, C.false, time) +} + +//export onKeyPressEvent +func onKeyPressEvent(widget *C.GtkWidget, event *C.GdkEventKey, userData unsafe.Pointer) C.gboolean { + // windowId := uint(*((*C.uint)(userData))) + // fmt.Println("onKeyPressEvent", windowId) + /* + windowKeyEvents <- &windowKeyEvent{ + windowId: windowID, + acceleratorString: C.GoString(acceleratorString), + } + */ + return C.gboolean(0) +} + +//export onProcessRequest +func onProcessRequest(request unsafe.Pointer, data unsafe.Pointer) { + windowId := uint(*((*C.uint)(data))) + webviewRequests <- &webViewAssetRequest{ + Request: webview.NewRequest(request), + windowId: windowId, + windowName: globalApplication.getWindowForID(windowId).Name(), + } +} + +func gtkBool(input bool) C.gboolean { + if input { + return C.gboolean(1) + } + return C.gboolean(0) +} + +// dialog related + +func setWindowIcon(window pointer, icon []byte) { + loader := C.gdk_pixbuf_loader_new() + if loader == nil { + return + } + written := C.gdk_pixbuf_loader_write( + loader, + (*C.uchar)(&icon[0]), + C.ulong(len(icon)), + nil) + if written == 0 { + return + } + C.gdk_pixbuf_loader_close(loader, nil) + pixbuf := C.gdk_pixbuf_loader_get_pixbuf(loader) + if pixbuf != nil { + C.gtk_window_set_icon((*C.GtkWindow)(window), pixbuf) + } + C.g_object_unref(C.gpointer(loader)) +} + +//export messageDialogCB +func messageDialogCB(button C.int) { + fmt.Println("messageDialogCB", button) +} + +func runChooserDialog(window pointer, allowMultiple, createFolders, showHidden bool, currentFolder, title string, action int, acceptLabel string, filters []FileFilter) (chan string, error) { + titleStr := C.CString(title) + defer C.free(unsafe.Pointer(titleStr)) + cancelStr := C.CString("_Cancel") + defer C.free(unsafe.Pointer(cancelStr)) + acceptLabelStr := C.CString(acceptLabel) + defer C.free(unsafe.Pointer(acceptLabelStr)) + + fc := C.gtkFileChooserDialogNew( + titleStr, + (*C.GtkWindow)(window), + C.GtkFileChooserAction(action), + cancelStr, + acceptLabelStr) + + C.gtk_file_chooser_set_action((*C.GtkFileChooser)(fc), C.GtkFileChooserAction(action)) + + gtkFilters := []*C.GtkFileFilter{} + for _, filter := range filters { + f := (C.gtk_file_filter_new()) + displayStr := C.CString(filter.DisplayName) + C.gtk_file_filter_set_name(f, displayStr) + C.free(unsafe.Pointer(displayStr)) + patternStr := C.CString(filter.Pattern) + C.gtk_file_filter_add_pattern(f, patternStr) + C.free(unsafe.Pointer(patternStr)) + C.gtk_file_chooser_add_filter((*C.GtkFileChooser)(fc), f) + gtkFilters = append(gtkFilters, f) + } + C.gtk_file_chooser_set_select_multiple( + (*C.GtkFileChooser)(fc), + gtkBool(allowMultiple)) + C.gtk_file_chooser_set_create_folders( + (*C.GtkFileChooser)(fc), + gtkBool(createFolders)) + C.gtk_file_chooser_set_show_hidden( + (*C.GtkFileChooser)(fc), + gtkBool(showHidden)) + + if currentFolder != "" { + path := C.CString(currentFolder) + C.gtk_file_chooser_set_current_folder( + (*C.GtkFileChooser)(fc), + path) + C.free(unsafe.Pointer(path)) + } + + // FIXME: This should be consolidated - duplicate exists in linux_purego.go + buildStringAndFree := func(s C.gpointer) string { + bytes := []byte{} + p := unsafe.Pointer(s) + for { + val := *(*byte)(p) + if val == 0 { // this is the null terminator + break + } + bytes = append(bytes, val) + p = unsafe.Add(p, 1) + } + C.g_free(s) // so we don't have to iterate a second time + return string(bytes) + } + + selections := make(chan string) + // run this on the gtk thread + InvokeAsync(func() { + go func() { + response := C.gtk_dialog_run((*C.GtkDialog)(fc)) + if response == C.GTK_RESPONSE_ACCEPT { + filenames := C.gtk_file_chooser_get_filenames((*C.GtkFileChooser)(fc)) + iter := filenames + count := 0 + for { + selections <- buildStringAndFree(C.gpointer(iter.data)) + iter = iter.next + if iter == nil || count == 1024 { + break + } + count++ + } + close(selections) + C.gtk_widget_destroy((*C.GtkWidget)(unsafe.Pointer(fc))) + } + }() + }) + return selections, nil +} + +func runOpenFileDialog(dialog *OpenFileDialogStruct) (chan string, error) { + const GtkFileChooserActionOpen = C.GTK_FILE_CHOOSER_ACTION_OPEN + + window := nilPointer + if dialog.window != nil { + window = (dialog.window.impl).(*linuxWebviewWindow).window + } + + buttonText := dialog.buttonText + if buttonText == "" { + buttonText = "_Open" + } + + return runChooserDialog( + window, + dialog.allowsMultipleSelection, + dialog.canCreateDirectories, + dialog.showHiddenFiles, + dialog.directory, + dialog.title, + GtkFileChooserActionOpen, + buttonText, + dialog.filters) +} + +func runQuestionDialog(parent pointer, options *MessageDialog) int { + cMsg := C.CString(options.Message) + cTitle := C.CString(options.Title) + defer C.free(unsafe.Pointer(cMsg)) + defer C.free(unsafe.Pointer(cTitle)) + hasButtons := false + if len(options.Buttons) > 0 { + hasButtons = true + } + + dType, ok := map[DialogType]C.int{ + InfoDialogType: C.GTK_MESSAGE_INFO, + // ErrorDialogType: + QuestionDialogType: C.GTK_MESSAGE_QUESTION, + WarningDialogType: C.GTK_MESSAGE_WARNING, + }[options.DialogType] + if !ok { + // FIXME: Add logging here! + dType = C.GTK_MESSAGE_INFO + } + + dialog := C.new_message_dialog((*C.GtkWindow)(parent), cMsg, dType, C.bool(hasButtons)) + if options.Title != "" { + C.gtk_window_set_title( + (*C.GtkWindow)(unsafe.Pointer(dialog)), + cTitle) + } + + if img, err := pngToImage(options.Icon); err == nil { + gbytes := C.g_bytes_new_static( + C.gconstpointer(unsafe.Pointer(&img.Pix[0])), + C.ulong(len(img.Pix))) + defer C.g_bytes_unref(gbytes) + pixBuf := C.gdk_pixbuf_new_from_bytes( + gbytes, + C.GDK_COLORSPACE_RGB, + 1, // has_alpha + 8, + C.int(img.Bounds().Dx()), + C.int(img.Bounds().Dy()), + C.int(img.Stride), + ) + image := C.gtk_image_new_from_pixbuf(pixBuf) + C.gtk_widget_set_visible((*C.GtkWidget)(image), C.gboolean(1)) + contentArea := C.gtk_dialog_get_content_area((*C.GtkDialog)(dialog)) + C.gtk_container_add( + (*C.GtkContainer)(unsafe.Pointer(contentArea)), + (*C.GtkWidget)(image)) + } + for i, button := range options.Buttons { + cLabel := C.CString(button.Label) + defer C.free(unsafe.Pointer(cLabel)) + index := C.int(i) + C.gtk_dialog_add_button( + (*C.GtkDialog)(dialog), cLabel, index) + if button.IsDefault { + C.gtk_dialog_set_default_response((*C.GtkDialog)(dialog), index) + } + } + + defer C.gtk_widget_destroy((*C.GtkWidget)(dialog)) + return int(C.gtk_dialog_run((*C.GtkDialog)(unsafe.Pointer(dialog)))) +} + +func runSaveFileDialog(dialog *SaveFileDialogStruct) (chan string, error) { + window := nilPointer + buttonText := dialog.buttonText + if buttonText == "" { + buttonText = "_Save" + } + results, err := runChooserDialog( + window, + false, // multiple selection + dialog.canCreateDirectories, + dialog.showHiddenFiles, + dialog.directory, + dialog.title, + C.GTK_FILE_CHOOSER_ACTION_SAVE, + buttonText, + dialog.filters) + + return results, err +} diff --git a/v3/pkg/application/linux_purego.go b/v3/pkg/application/linux_purego.go new file mode 100644 index 000000000..682bf195e --- /dev/null +++ b/v3/pkg/application/linux_purego.go @@ -0,0 +1,1199 @@ +//go:build linux && purego + +package application + +import ( + "fmt" + "os" + "strings" + "unsafe" + + "github.com/ebitengine/purego" + "github.com/wailsapp/wails/v3/internal/assetserver/webview" + "github.com/wailsapp/wails/v3/pkg/events" +) + +type windowPointer uintptr +type identifier uint +type pointer uintptr + +// type GSList uintptr +type GSList struct { + data pointer + next *GSList +} + +type GSListPointer *GSList + +const ( + nilPointer pointer = 0 +) + +const ( + GSourceRemove int = 0 + + // https://gitlab.gnome.org/GNOME/gtk/-/blob/gtk-3-24/gdk/gdkwindow.h#L121 + GdkHintMinSize = 1 << 1 + GdkHintMaxSize = 1 << 2 + // https://gitlab.gnome.org/GNOME/gtk/-/blob/gtk-3-24/gdk/gdkevents.h#L512 + GdkWindowStateIconified = 1 << 1 + GdkWindowStateMaximized = 1 << 2 + GdkWindowStateFullscreen = 1 << 4 + + // https://gitlab.gnome.org/GNOME/gtk/-/blob/gtk-3-24/gtk/gtkmessagedialog.h#L87 + GtkButtonsNone int = 0 + GtkButtonsOk = 1 + GtkButtonsClose = 2 + GtkButtonsCancel = 3 + GtkButtonsYesNo = 4 + GtkButtonsOkCancel = 5 + + // https://gitlab.gnome.org/GNOME/gtk/-/blob/gtk-3-24/gtk/gtkdialog.h#L36 + GtkDialogModal = 1 << 0 + GtkDialogDestroyWithParent = 1 << 1 + GtkDialogUseHeaderBar = 1 << 2 // actions in header bar instead of action area + + GtkOrientationVertical = 1 + + // enum GtkMessageType + GtkMessageInfo = 0 + GtkMessageWarning = 1 + GtkMessageQuestion = 2 + GtkMessageError = 3 +) + +type GdkGeometry struct { + minWidth int32 + minHeight int32 + maxWidth int32 + maxHeight int32 + baseWidth int32 + baseHeight int32 + widthInc int32 + heightInc int32 + padding int32 + minAspect float64 + maxAspect float64 + GdkGravity int32 +} + +var ( + nilRadioGroup GSListPointer = nil + gtkSignalHandlers map[pointer]uint = map[pointer]uint{} + gtkSignalToMenuItem map[pointer]*MenuItem = map[pointer]*MenuItem{} + mainThreadId uint64 +) + +const ( + // TODO: map distro => so filename - with fallback? + gtk3 = "libgtk-3.so.0" + gtk4 = "libgtk-4.so.1" + webkit4 = "libwebkit2gtk-4.1.so.0" +) + +var ( + gtk uintptr + gtkVersion int + webkit uintptr + + // function references + gApplicationHold func(pointer) + gApplicationQuit func(pointer) + gApplicationName func() string + gApplicationRelease func(pointer) + gApplicationRun func(pointer, int, []string) int + gBytesNewStatic func(uintptr, int) uintptr + gBytesUnref func(uintptr) + gFree func(pointer) + gIdleAdd func(uintptr) + gObjectRefSink func(pointer) + gObjectUnref func(pointer) + gSignalConnectData func(pointer, string, uintptr, pointer, bool, int) int + gSignalConnectObject func(pointer, string, pointer, pointer, int) uint + gSignalHandlerBlock func(pointer, uint) + gSignalHandlerUnblock func(pointer, uint) + gThreadSelf func() uint64 + + // gdk functions + gdkDisplayGetMonitor func(pointer, int) pointer + gdkDisplayGetMonitorAtWindow func(pointer, pointer) pointer + gdkDisplayGetNMonitors func(pointer) int + gdkMonitorGetGeometry func(pointer, pointer) pointer + gdkMonitorGetScaleFactor func(pointer) int + gdkMonitorIsPrimary func(pointer) int + gdkPixbufNewFromBytes func(uintptr, int, int, int, int, int, int) pointer + gdkRgbaParse func(pointer, string) bool + gdkScreenGetRgbaVisual func(pointer) pointer + gdkScreenIsComposited func(pointer) int + gdkWindowGetState func(pointer) int + gdkWindowGetDisplay func(pointer) pointer + + // gtk functions + gtkApplicationNew func(string, uint) pointer + gtkApplicationGetActiveWindow func(pointer) pointer + gtkApplicationGetWindows func(pointer) *GList + gtkApplicationWindowNew func(pointer) pointer + gtkBoxNew func(int, int) pointer + gtkBoxPackStart func(pointer, pointer, int, int, int) + gtkCheckMenuItemGetActive func(pointer) int + gtkCheckMenuItemNewWithLabel func(string) pointer + gtkCheckMenuItemSetActive func(pointer, int) + gtkContainerAdd func(pointer, pointer) + gtkCSSProviderLoadFromData func(pointer, string, int, pointer) + gtkCSSProviderNew func() pointer + gtkDialogAddButton func(pointer, string, int) + gtkDialogGetContentArea func(pointer) pointer + gtkDialogRun func(pointer) int + gtkDialogSetDefaultResponse func(pointer, int) + gtkDragDestSet func(pointer, uint, pointer, uint, uint) + gtkFileChooserAddFilter func(pointer, pointer) + gtkFileChooserDialogNew func(string, pointer, int, string, int, string, int, pointer) pointer + gtkFileChooserGetFilenames func(pointer) *GSList + gtkFileChooserSetAction func(pointer, int) + gtkFileChooserSetCreateFolders func(pointer, bool) + gtkFileChooserSetCurrentFolder func(pointer, string) + gtkFileChooserSetSelectMultiple func(pointer, bool) + gtkFileChooserSetShowHidden func(pointer, bool) + gtkFileFilterAddPattern func(pointer, string) + gtkFileFilterNew func() pointer + gtkFileFilterSetName func(pointer, string) + gtkImageNewFromPixbuf func(pointer) pointer + gtkMenuBarNew func() pointer + gtkMenuItemNewWithLabel func(string) pointer + gtkMenuItemSetLabel func(pointer, string) + gtkMenuItemSetSubmenu func(pointer, pointer) + gtkMenuNew func() pointer + gtkMenuShellAppend func(pointer, pointer) + gtkMessageDialogNew func(pointer, int, int, int, string) pointer + gtkRadioMenuItemGetGroup func(pointer) GSListPointer + gtkRadioMenuItemNewWithLabel func(GSListPointer, string) pointer + gtkSeparatorMenuItemNew func() pointer + gtkStyleContextAddProvider func(pointer, pointer, int) + gtkTargetEntryFree func(pointer) + gtkTargetEntryNew func(string, int, uint) pointer + gtkWidgetDestroy func(pointer) + gtkWidgetGetDisplay func(pointer) pointer + gtkWidgetGetScreen func(pointer) pointer + gtkWidgetGetStyleContext func(pointer) pointer + gtkWidgetGetWindow func(pointer) pointer + gtkWidgetHide func(pointer) + gtkWidgetIsVisible func(pointer) bool + gtkWidgetShow func(pointer) + gtkWidgetShowAll func(pointer) + gtkWidgetSetAppPaintable func(pointer, int) + gtkWidgetSetName func(pointer, string) + gtkWidgetSetSensitive func(pointer, int) + gtkWidgetSetToolTipText func(pointer, string) + gtkWidgetSetVisual func(pointer, pointer) + gtkWindowClose func(pointer) + gtkWindowFullScreen func(pointer) + gtkWindowGetPosition func(pointer, *int, *int) bool + gtkWindowGetSize func(pointer, *int, *int) + gtkWindowHasToplevelFocus func(pointer) int + gtkWindowKeepAbove func(pointer, bool) + gtkWindowMaximize func(pointer) + gtkWindowMinimize func(pointer) + gtkWindowMove func(pointer, int, int) + gtkWindowPresent func(pointer) + gtkWindowResize func(pointer, int, int) + gtkWindowSetDecorated func(pointer, int) + gtkWindowSetGeometryHints func(pointer, pointer, pointer, int) + gtkWindowSetKeepAbove func(pointer, bool) + gtkWindowSetResizable func(pointer, bool) + gtkWindowSetTitle func(pointer, string) + gtkWindowUnfullscreen func(pointer) + gtkWindowUnmaximize func(pointer) + + // webkit + webkitNewWithUserContentManager func(pointer) pointer + webkitRegisterUriScheme func(pointer, string, pointer, int, int) + webkitSettingsGetEnableDeveloperExtras func(pointer) bool + webkitSettingsSetHardwareAccelerationPolicy func(pointer, int) + webkitSettingsSetEnableDeveloperExtras func(pointer, bool) + webkitSettingsSetUserAgentWithApplicationDetails func(pointer, string, string) + webkitUserContentManagerNew func() pointer + webkitUserContentManagerRegisterScriptMessageHandler func(pointer, string) + webkitWebContextGetDefault func() pointer + webkitWebViewEvaluateJS func(pointer, string, int, pointer, string, pointer, pointer, pointer) + webkitWebViewGetSettings func(pointer) pointer + webkitWebViewGetZoom func(pointer) float64 + webkitWebViewLoadAlternateHTML func(pointer, string, string, *string) + webkitWebViewLoadUri func(pointer, string) + webkitWebViewSetBackgroundColor func(pointer, pointer) + webkitWebViewSetSettings func(pointer, pointer) + webkitWebViewSetZoomLevel func(pointer, float64) +) + +func init() { + // needed for GTK4 to function + _ = os.Setenv("GDK_BACKEND", "x11") + var err error + + // gtk, err = purego.Dlopen(gtk4, purego.RTLD_NOW|purego.RTLD_GLOBAL) + // if err == nil { + // version = 4 + // return + // } + + // log.Println("Failed to open GTK4: Falling back to GTK3") + + gtk, err = purego.Dlopen(gtk3, purego.RTLD_NOW|purego.RTLD_GLOBAL) + if err != nil { + panic(err) + } + gtkVersion = 3 + + webkit, err = purego.Dlopen(webkit4, purego.RTLD_NOW|purego.RTLD_GLOBAL) + if err != nil { + panic(err) + } + + // Function registration + // GLib + purego.RegisterLibFunc(&gApplicationHold, gtk, "g_application_hold") + purego.RegisterLibFunc(&gApplicationName, gtk, "g_get_application_name") + purego.RegisterLibFunc(&gApplicationQuit, gtk, "g_application_quit") + purego.RegisterLibFunc(&gApplicationRelease, gtk, "g_application_release") + purego.RegisterLibFunc(&gApplicationRun, gtk, "g_application_run") + purego.RegisterLibFunc(&gBytesNewStatic, gtk, "g_bytes_new_static") + purego.RegisterLibFunc(&gBytesUnref, gtk, "g_bytes_unref") + purego.RegisterLibFunc(&gFree, gtk, "g_free") + purego.RegisterLibFunc(&gIdleAdd, gtk, "g_idle_add") + purego.RegisterLibFunc(&gObjectRefSink, gtk, "g_object_ref_sink") + purego.RegisterLibFunc(&gObjectUnref, gtk, "g_object_unref") + purego.RegisterLibFunc(&gSignalConnectData, gtk, "g_signal_connect_data") + purego.RegisterLibFunc(&gSignalConnectObject, gtk, "g_signal_connect_object") + purego.RegisterLibFunc(&gSignalHandlerBlock, gtk, "g_signal_handler_block") + purego.RegisterLibFunc(&gSignalHandlerUnblock, gtk, "g_signal_handler_unblock") + purego.RegisterLibFunc(&gThreadSelf, gtk, "g_thread_self") + + // GDK + purego.RegisterLibFunc(&gdkDisplayGetMonitor, gtk, "gdk_display_get_monitor") + purego.RegisterLibFunc(&gdkDisplayGetMonitorAtWindow, gtk, "gdk_display_get_monitor_at_window") + purego.RegisterLibFunc(&gdkDisplayGetNMonitors, gtk, "gdk_display_get_n_monitors") + purego.RegisterLibFunc(&gdkMonitorGetGeometry, gtk, "gdk_monitor_get_geometry") + purego.RegisterLibFunc(&gdkMonitorGetScaleFactor, gtk, "gdk_monitor_get_scale_factor") + purego.RegisterLibFunc(&gdkMonitorIsPrimary, gtk, "gdk_monitor_is_primary") + purego.RegisterLibFunc(&gdkPixbufNewFromBytes, gtk, "gdk_pixbuf_new_from_bytes") + purego.RegisterLibFunc(&gdkRgbaParse, gtk, "gdk_rgba_parse") + purego.RegisterLibFunc(&gdkScreenGetRgbaVisual, gtk, "gdk_screen_get_rgba_visual") + purego.RegisterLibFunc(&gdkScreenIsComposited, gtk, "gdk_screen_is_composited") + purego.RegisterLibFunc(&gdkWindowGetDisplay, gtk, "gdk_window_get_display") + purego.RegisterLibFunc(&gdkWindowGetState, gtk, "gdk_window_get_state") + + // GTK3 + purego.RegisterLibFunc(>kApplicationNew, gtk, "gtk_application_new") + purego.RegisterLibFunc(>kApplicationGetActiveWindow, gtk, "gtk_application_get_active_window") + purego.RegisterLibFunc(>kApplicationGetWindows, gtk, "gtk_application_get_windows") + purego.RegisterLibFunc(>kApplicationWindowNew, gtk, "gtk_application_window_new") + purego.RegisterLibFunc(>kBoxNew, gtk, "gtk_box_new") + purego.RegisterLibFunc(>kBoxPackStart, gtk, "gtk_box_pack_start") + purego.RegisterLibFunc(>kCheckMenuItemGetActive, gtk, "gtk_check_menu_item_get_active") + purego.RegisterLibFunc(>kCheckMenuItemNewWithLabel, gtk, "gtk_check_menu_item_new_with_label") + purego.RegisterLibFunc(>kCheckMenuItemSetActive, gtk, "gtk_check_menu_item_set_active") + purego.RegisterLibFunc(>kContainerAdd, gtk, "gtk_container_add") + purego.RegisterLibFunc(>kCSSProviderLoadFromData, gtk, "gtk_css_provider_load_from_data") + purego.RegisterLibFunc(>kDialogAddButton, gtk, "gtk_dialog_add_button") + purego.RegisterLibFunc(>kDialogGetContentArea, gtk, "gtk_dialog_get_content_area") + purego.RegisterLibFunc(>kDialogRun, gtk, "gtk_dialog_run") + purego.RegisterLibFunc(>kDialogSetDefaultResponse, gtk, "gtk_dialog_set_default_response") + purego.RegisterLibFunc(>kDragDestSet, gtk, "gtk_drag_dest_set") + purego.RegisterLibFunc(>kFileChooserAddFilter, gtk, "gtk_file_chooser_add_filter") + purego.RegisterLibFunc(>kFileChooserDialogNew, gtk, "gtk_file_chooser_dialog_new") + purego.RegisterLibFunc(>kFileChooserGetFilenames, gtk, "gtk_file_chooser_get_filenames") + purego.RegisterLibFunc(>kFileChooserSetAction, gtk, "gtk_file_chooser_set_action") + purego.RegisterLibFunc(>kFileChooserSetCreateFolders, gtk, "gtk_file_chooser_set_create_folders") + purego.RegisterLibFunc(>kFileChooserSetCurrentFolder, gtk, "gtk_file_chooser_set_current_folder") + purego.RegisterLibFunc(>kFileChooserSetSelectMultiple, gtk, "gtk_file_chooser_set_select_multiple") + purego.RegisterLibFunc(>kFileChooserSetShowHidden, gtk, "gtk_file_chooser_set_show_hidden") + purego.RegisterLibFunc(>kFileFilterAddPattern, gtk, "gtk_file_filter_add_pattern") + purego.RegisterLibFunc(>kFileFilterNew, gtk, "gtk_file_filter_new") + purego.RegisterLibFunc(>kFileFilterSetName, gtk, "gtk_file_filter_set_name") + purego.RegisterLibFunc(>kImageNewFromPixbuf, gtk, "gtk_image_new_from_pixbuf") + purego.RegisterLibFunc(>kMenuItemSetLabel, gtk, "gtk_menu_item_set_label") + purego.RegisterLibFunc(>kMenuBarNew, gtk, "gtk_menu_bar_new") + purego.RegisterLibFunc(>kMenuItemNewWithLabel, gtk, "gtk_menu_item_new_with_label") + purego.RegisterLibFunc(>kMenuItemSetSubmenu, gtk, "gtk_menu_item_set_submenu") + purego.RegisterLibFunc(>kMenuNew, gtk, "gtk_menu_new") + purego.RegisterLibFunc(>kMenuShellAppend, gtk, "gtk_menu_shell_append") + purego.RegisterLibFunc(>kMessageDialogNew, gtk, "gtk_message_dialog_new") + purego.RegisterLibFunc(>kRadioMenuItemGetGroup, gtk, "gtk_radio_menu_item_get_group") + purego.RegisterLibFunc(>kRadioMenuItemNewWithLabel, gtk, "gtk_radio_menu_item_new_with_label") + purego.RegisterLibFunc(>kSeparatorMenuItemNew, gtk, "gtk_separator_menu_item_new") + purego.RegisterLibFunc(>kStyleContextAddProvider, gtk, "gtk_style_context_add_provider") + purego.RegisterLibFunc(>kTargetEntryFree, gtk, "gtk_target_entry_free") + purego.RegisterLibFunc(>kTargetEntryNew, gtk, "gtk_target_entry_new") + purego.RegisterLibFunc(>kWidgetDestroy, gtk, "gtk_widget_destroy") + purego.RegisterLibFunc(>kWidgetGetDisplay, gtk, "gtk_widget_get_display") + purego.RegisterLibFunc(>kWidgetGetScreen, gtk, "gtk_widget_get_screen") + purego.RegisterLibFunc(>kWidgetGetStyleContext, gtk, "gtk_widget_get_style_context") + purego.RegisterLibFunc(>kWidgetGetWindow, gtk, "gtk_widget_get_window") + purego.RegisterLibFunc(>kWidgetHide, gtk, "gtk_widget_hide") + purego.RegisterLibFunc(>kWidgetIsVisible, gtk, "gtk_widget_is_visible") + purego.RegisterLibFunc(>kWidgetSetAppPaintable, gtk, "gtk_widget_set_app_paintable") + purego.RegisterLibFunc(>kWidgetSetName, gtk, "gtk_widget_set_name") + purego.RegisterLibFunc(>kWidgetSetSensitive, gtk, "gtk_widget_set_sensitive") + purego.RegisterLibFunc(>kWidgetSetToolTipText, gtk, "gtk_widget_set_tooltip_text") + purego.RegisterLibFunc(>kWidgetSetVisual, gtk, "gtk_widget_set_visual") + purego.RegisterLibFunc(>kWidgetShow, gtk, "gtk_widget_show") + purego.RegisterLibFunc(>kWidgetShowAll, gtk, "gtk_widget_show_all") + purego.RegisterLibFunc(>kWindowFullScreen, gtk, "gtk_window_fullscreen") + purego.RegisterLibFunc(>kWindowClose, gtk, "gtk_window_close") + purego.RegisterLibFunc(>kWindowGetPosition, gtk, "gtk_window_get_position") + purego.RegisterLibFunc(>kWindowGetSize, gtk, "gtk_window_get_size") + purego.RegisterLibFunc(>kWindowMaximize, gtk, "gtk_window_maximize") + purego.RegisterLibFunc(>kWindowMove, gtk, "gtk_window_move") + purego.RegisterLibFunc(>kWindowPresent, gtk, "gtk_window_present") + //purego.RegisterLibFunc(>kWindowPresent, gtk, "gtk_window_unminimize") // gtk4 + purego.RegisterLibFunc(>kWindowHasToplevelFocus, gtk, "gtk_window_has_toplevel_focus") + purego.RegisterLibFunc(>kWindowMinimize, gtk, "gtk_window_iconify") // gtk3 + // purego.RegisterLibFunc(>kWindowMinimize, gtk, "gtk_window_minimize") // gtk4 + purego.RegisterLibFunc(>kWindowResize, gtk, "gtk_window_resize") + purego.RegisterLibFunc(>kWindowSetGeometryHints, gtk, "gtk_window_set_geometry_hints") + purego.RegisterLibFunc(>kWindowSetDecorated, gtk, "gtk_window_set_decorated") + purego.RegisterLibFunc(>kWindowKeepAbove, gtk, "gtk_window_set_keep_above") + purego.RegisterLibFunc(>kWindowSetResizable, gtk, "gtk_window_set_resizable") + purego.RegisterLibFunc(>kWindowSetTitle, gtk, "gtk_window_set_title") + purego.RegisterLibFunc(>kWindowUnfullscreen, gtk, "gtk_window_unfullscreen") + purego.RegisterLibFunc(>kWindowUnmaximize, gtk, "gtk_window_unmaximize") + + // webkit + purego.RegisterLibFunc(&webkitNewWithUserContentManager, webkit, "webkit_web_view_new_with_user_content_manager") + purego.RegisterLibFunc(&webkitRegisterUriScheme, webkit, "webkit_web_context_register_uri_scheme") + purego.RegisterLibFunc(&webkitSettingsGetEnableDeveloperExtras, webkit, "webkit_settings_get_enable_developer_extras") + purego.RegisterLibFunc(&webkitSettingsSetEnableDeveloperExtras, webkit, "webkit_settings_set_enable_developer_extras") + purego.RegisterLibFunc(&webkitSettingsSetHardwareAccelerationPolicy, webkit, "webkit_settings_set_hardware_acceleration_policy") + purego.RegisterLibFunc(&webkitSettingsSetUserAgentWithApplicationDetails, webkit, "webkit_settings_set_user_agent_with_application_details") + purego.RegisterLibFunc(&webkitUserContentManagerNew, webkit, "webkit_user_content_manager_new") + purego.RegisterLibFunc(&webkitUserContentManagerRegisterScriptMessageHandler, webkit, "webkit_user_content_manager_register_script_message_handler") + purego.RegisterLibFunc(&webkitWebContextGetDefault, webkit, "webkit_web_context_get_default") + purego.RegisterLibFunc(&webkitWebViewEvaluateJS, webkit, "webkit_web_view_evaluate_javascript") + purego.RegisterLibFunc(&webkitWebViewGetSettings, webkit, "webkit_web_view_get_settings") + purego.RegisterLibFunc(&webkitWebViewGetZoom, webkit, "webkit_web_view_get_zoom_level") + purego.RegisterLibFunc(&webkitWebViewLoadAlternateHTML, webkit, "webkit_web_view_load_alternate_html") + purego.RegisterLibFunc(&webkitWebViewLoadUri, webkit, "webkit_web_view_load_uri") + purego.RegisterLibFunc(&webkitWebViewSetBackgroundColor, webkit, "webkit_web_view_set_background_color") + purego.RegisterLibFunc(&webkitWebViewSetSettings, webkit, "webkit_web_view_set_settings") + purego.RegisterLibFunc(&webkitWebViewSetZoomLevel, webkit, "webkit_web_view_set_zoom_level") +} + +// mainthread stuff +func dispatchOnMainThread(id uint) { + gIdleAdd(purego.NewCallback(func(pointer) int { + executeOnMainThread(id) + return GSourceRemove + })) +} + +// implementation below +func appName() string { + return gApplicationName() +} + +func appNew(name string) pointer { + GApplicationDefaultFlags := uint(0) + + name = strings.ToLower(name) + if name == "" { + name = "undefined" + } + identifier := fmt.Sprintf("org.wails.%s", strings.Replace(name, " ", "-", -1)) + + return pointer(gtkApplicationNew(identifier, GApplicationDefaultFlags)) +} + +func appRun(application pointer) error { + mainThreadId = gThreadSelf() + fmt.Println("linux_purego: appRun threadID", mainThreadId) + + app := pointer(application) + activate := func() { + // TODO: Do we care? + fmt.Println("linux.activated!") + gApplicationHold(app) // allow running without a window + } + gSignalConnectData( + application, + "activate", + purego.NewCallback(activate), + app, + false, + 0) + + status := gApplicationRun(app, 0, nil) + gApplicationRelease(app) + gObjectUnref(app) + + var err error + if status != 0 { + err = fmt.Errorf("exit code: %d", status) + } + return err +} + +func appDestroy(application pointer) { + gApplicationQuit(pointer(application)) +} + +func getCurrentWindowID(application pointer, windows map[windowPointer]uint) uint { + // TODO: Add extra metadata to window and use it! + window := gtkApplicationGetActiveWindow(pointer(application)) + identifier, ok := windows[windowPointer(window)] + if ok { + return identifier + } + // FIXME: Should we panic here if not found? + return uint(1) +} + +type GList struct { + data pointer + next *GList + prev *GList +} + +func getWindows(application pointer) []pointer { + result := []pointer{} + windows := gtkApplicationGetWindows(pointer(application)) + // FIXME: Need to make a struct here to deal with response data + for { + result = append(result, pointer(windows.data)) + windows = windows.next + if windows == nil { + return result + } + } +} + +func hideAllWindows(application pointer) { + for _, window := range getWindows(application) { + gtkWidgetHide(window) + } +} + +func showAllWindows(application pointer) { + for _, window := range getWindows(application) { + gtkWidgetShowAll(window) + } +} + +// Menu +func menuAddSeparator(menu *Menu) { + gtkMenuShellAppend( + pointer((menu.impl).(*linuxMenu).native), + gtkSeparatorMenuItemNew()) +} + +func menuAppend(parent *Menu, menu *MenuItem) { + // TODO: override this with the GTK4 version if needed - possibly rename to imply it's an alias + gtkMenuShellAppend( + pointer((parent.impl).(*linuxMenu).native), + pointer((menu.impl).(*linuxMenuItem).native)) + + /* gtk4 + C.gtk_menu_item_set_submenu( + (*C.struct__GtkMenuItem)((menu.impl).(*linuxMenuItem).native), + (*C.struct__GtkWidget)((parent.impl).(*linuxMenu).native), + ) + */ +} + +func menuBarNew() pointer { + return pointer(gtkMenuBarNew()) +} + +func menuNew() pointer { + return pointer(gtkMenuNew()) +} + +func menuSetSubmenu(item *MenuItem, menu *Menu) { + // FIXME: How is this different than `menuAppend` above? + gtkMenuItemSetSubmenu( + pointer((item.impl).(*linuxMenuItem).native), + pointer((menu.impl).(*linuxMenu).native)) +} + +func menuGetRadioGroup(item *linuxMenuItem) *GSList { + return (*GSList)(gtkRadioMenuItemGetGroup(pointer(item.native))) +} + +func attachMenuHandler(item *MenuItem) { + handleClick := func() { + item := item + switch item.itemType { + case text, checkbox: + menuItemClicked <- item.id + case radio: + menuItem := (item.impl).(*linuxMenuItem) + if menuItem.isChecked() { + menuItemClicked <- item.id + } + } + } + + impl := (item.impl).(*linuxMenuItem) + widget := impl.native + flags := 0 + handlerId := gSignalConnectObject( + pointer(widget), + "activate", + pointer(purego.NewCallback(handleClick)), + pointer(widget), + flags) + impl.handlerId = uint(handlerId) +} + +// menuItem +func menuItemChecked(widget pointer) bool { + if gtkCheckMenuItemGetActive(widget) == 1 { + return true + } + return false +} + +func menuItemNew(label string) pointer { + return pointer(gtkMenuItemNewWithLabel(label)) +} + +func menuCheckItemNew(label string) pointer { + return pointer(gtkCheckMenuItemNewWithLabel(label)) +} + +func menuItemSetChecked(widget pointer, checked bool) { + value := 0 + if checked { + value = 1 + } + gtkCheckMenuItemSetActive(pointer(widget), value) +} + +func menuItemSetDisabled(widget pointer, disabled bool) { + value := 1 + if disabled { + value = 0 + } + gtkWidgetSetSensitive(widget, value) +} + +func menuItemSetLabel(widget pointer, label string) { + gtkMenuItemSetLabel( + pointer(widget), + label) +} + +func menuItemSetToolTip(widget pointer, tooltip string) { + gtkWidgetSetToolTipText( + pointer(widget), + tooltip) +} + +func menuItemSignalBlock(widget pointer, handlerId uint, block bool) { + if block { + gSignalHandlerBlock(widget, handlerId) + } else { + gSignalHandlerUnblock(widget, handlerId) + } +} + +func menuRadioItemNew(group GSListPointer, label string) pointer { + return pointer(gtkRadioMenuItemNewWithLabel(group, label)) +} + +// screen related + +func getScreenByIndex(display pointer, index int) *Screen { + monitor := gdkDisplayGetMonitor(display, index) + // TODO: Do we need to update Screen to contain current info? + // currentMonitor := C.gdk_display_get_monitor_at_window(display, window) + + geometry := struct { + x int32 + y int32 + width int32 + height int32 + }{} + result := pointer(unsafe.Pointer(&geometry)) + gdkMonitorGetGeometry(monitor, result) + + primary := false + if gdkMonitorIsPrimary(monitor) == 1 { + primary = true + } + + return &Screen{ + IsPrimary: primary, + Scale: 1.0, + X: int(geometry.x), + Y: int(geometry.y), + Size: Size{ + Height: int(geometry.height), + Width: int(geometry.width), + }, + } +} + +func getScreens(app pointer) ([]*Screen, error) { + var screens []*Screen + window := gtkApplicationGetActiveWindow(app) + display := gdkWindowGetDisplay(window) + count := gdkDisplayGetNMonitors(display) + for i := 0; i < int(count); i++ { + screens = append(screens, getScreenByIndex(display, i)) + } + return screens, nil +} + +// widgets +func widgetSetSensitive(widget pointer, enabled bool) { + value := 0 + if enabled { + value = 1 + } + gtkWidgetSetSensitive(widget, value) +} + +func widgetSetVisible(widget pointer, hidden bool) { + if hidden { + gtkWidgetHide(widget) + } else { + gtkWidgetShow(widget) + } +} + +// window related functions +func windowClose(window pointer) { + gtkWindowClose(window) +} + +func windowEnableDND(id uint, webview pointer) { + targetentry := gtkTargetEntryNew("text/uri-list", 0, id) + defer gtkTargetEntryFree(targetentry) + + GtkDestDefaultDrop := uint(0) + GdkActionCopy := uint(0) //? + gtkDragDestSet(webview, GtkDestDefaultDrop, targetentry, 1, GdkActionCopy) + + // FIXME: enable and process + /* gSignalConnectData(webview, + "drag-data-received", + purego.NewCallback(onDragNDrop), + 0, + false, + 0)*/ +} + +func windowExecJS(webview pointer, js string) { + webkitWebViewEvaluateJS( + webview, + js, + len(js), + 0, + "", + 0, + 0, + 0) +} + +func windowDestroy(window pointer) { + // Should this truly 'destroy' ? + gtkWindowClose(window) +} + +func windowFullscreen(window pointer) { + gtkWindowFullScreen(window) +} + +func windowGetAbsolutePosition(window pointer) (int, int) { + var x, y int + gtkWindowGetPosition(window, &x, &y) + return x, y +} + +func windowGetCurrentMonitor(window pointer) pointer { + // Get the monitor that the window is currently on + display := gtkWidgetGetDisplay(window) + window = gtkWidgetGetWindow(window) + if window == 0 { + return 0 + } + return gdkDisplayGetMonitorAtWindow(display, window) +} + +func windowGetCurrentMonitorGeometry(window pointer) (x int, y int, width int, height int, scale int) { + monitor := windowGetCurrentMonitor(window) + if monitor == 0 { + return -1, -1, -1, -1, 1 + } + + result := struct { + x int32 + y int32 + width int32 + height int32 + }{} + gdkMonitorGetGeometry(monitor, pointer(unsafe.Pointer(&result))) + return int(result.x), int(result.y), int(result.width), int(result.height), gdkMonitorGetScaleFactor(monitor) +} + +func windowGetRelativePosition(window pointer) (int, int) { + absX, absY := windowGetAbsolutePosition(window) + x, y, _, _, _ := windowGetCurrentMonitorGeometry(window) + + relX := absX - x + relY := absY - y + + // TODO: Scale based on DPI + return relX, relY +} + +func windowGetSize(window pointer) (int, int) { + // TODO: dispatchOnMainThread? + var width, height int + gtkWindowGetSize(window, &width, &height) + return width, height +} + +func windowGetPosition(window pointer) (int, int) { + // TODO: dispatchOnMainThread? + var x, y int + gtkWindowGetPosition(window, &x, &y) + return x, y +} + +func windowHide(window pointer) { + gtkWidgetHide(window) +} + +func windowIsFocused(window pointer) bool { + return gtkWindowHasToplevelFocus(window) == 1 +} + +func windowIsFullscreen(window pointer) bool { + gdkwindow := gtkWidgetGetWindow(window) + state := gdkWindowGetState(gdkwindow) + return state&GdkWindowStateFullscreen > 0 +} + +func windowIsMaximized(window pointer) bool { + gdkwindow := gtkWidgetGetWindow(window) + state := gdkWindowGetState(gdkwindow) + return state&GdkWindowStateMaximized > 0 && state&GdkWindowStateFullscreen == 0 +} + +func windowIsMinimized(window pointer) bool { + gdkwindow := gtkWidgetGetWindow(window) + state := gdkWindowGetState(gdkwindow) + return state&GdkWindowStateIconified > 0 +} + +func windowIsVisible(window pointer) bool { + // TODO: validate this works.. (used a `bool` in the registration) + return gtkWidgetIsVisible(window) +} + +func windowMaximize(window pointer) { + gtkWindowMaximize(window) +} + +func windowMinimize(window pointer) { + gtkWindowMinimize(window) +} + +func windowNew(application pointer, menu pointer, windowId uint, gpuPolicy int) (pointer, pointer, pointer) { + window := gtkApplicationWindowNew(application) + gObjectRefSink(window) + webview := windowNewWebview(windowId, gpuPolicy) + vbox := gtkBoxNew(GtkOrientationVertical, 0) + gtkContainerAdd(window, vbox) + gtkWidgetSetName(vbox, "webview-box") + + if menu != 0 { + gtkBoxPackStart(vbox, menu, 0, 0, 0) + } + gtkBoxPackStart(vbox, webview, 1, 1, 0) + return pointer(window), pointer(webview), pointer(vbox) +} + +func windowNewWebview(parentId uint, gpuPolicy int) pointer { + manager := webkitUserContentManagerNew() + webkitUserContentManagerRegisterScriptMessageHandler(manager, "external") + wv := webkitNewWithUserContentManager(manager) + if !registered { + webkitRegisterUriScheme( + webkitWebContextGetDefault(), + "wails", + pointer(purego.NewCallback(func(request uintptr) { + webviewRequests <- &webViewAssetRequest{ + Request: webview.NewRequest(request), + windowId: parentId, + windowName: globalApplication.getWindowForID(parentId).Name(), + } + })), + 0, + 0, + ) + registered = true + } + + settings := webkitWebViewGetSettings(wv) + webkitSettingsSetUserAgentWithApplicationDetails( + settings, + "wails.io", + "") + webkitSettingsSetHardwareAccelerationPolicy(settings, gpuPolicy) + webkitWebViewSetSettings(wv, settings) + return wv +} + +func windowPresent(window pointer) { + gtkWindowPresent(pointer(window)) +} + +func windowReload(webview pointer, address string) { + webkitWebViewLoadUri(pointer(webview), address) +} + +func windowResize(window pointer, width, height int) { + gtkWindowResize(window, width, height) +} + +func windowShow(window pointer) { + gtkWidgetShowAll(pointer(window)) +} + +func windowSetBackgroundColour(vbox, webview pointer, colour RGBA) { + const GtkStyleProviderPriorityUser = 800 + + // FIXME: Use a struct! + rgba := make([]byte, 4*8) // C.sizeof_GdkRGBA == 32 + rgbaPointer := pointer(unsafe.Pointer(&rgba[0])) + if !gdkRgbaParse( + rgbaPointer, + fmt.Sprintf("rgba(%v,%v,%v,%v)", + colour.Red, + colour.Green, + colour.Blue, + float32(colour.Alpha)/255.0, + )) { + return + } + webkitWebViewSetBackgroundColor(pointer(webview), rgbaPointer) + + colour.Alpha = 255 + css := fmt.Sprintf("#webview-box {background-color: rgba(%d, %d, %d, %1.1f);}", colour.Red, colour.Green, colour.Blue, float32(colour.Alpha)/255.0) + provider := gtkCSSProviderNew() + defer gObjectUnref(provider) + gtkStyleContextAddProvider( + gtkWidgetGetStyleContext(vbox), + provider, + GtkStyleProviderPriorityUser, + ) + gtkCSSProviderLoadFromData(provider, css, -1, 0) +} + +func windowSetGeometryHints(window pointer, minWidth, minHeight, maxWidth, maxHeight int) { + size := GdkGeometry{ + minWidth: int32(minWidth), + minHeight: int32(minHeight), + maxWidth: int32(maxWidth), + maxHeight: int32(maxHeight), + } + gtkWindowSetGeometryHints( + pointer(window), + pointer(0), + pointer(unsafe.Pointer(&size)), + GdkHintMinSize|GdkHintMaxSize) +} + +func windowSetFrameless(window pointer, frameless bool) { + decorated := 1 + if frameless { + decorated = 0 + } + gtkWindowSetDecorated(pointer(window), decorated) + + // TODO: Deal with transparency for the titlebar if possible when !frameless + // Perhaps we just make it undecorated and add a menu bar inside? +} + +// TODO: confirm this is working properly +func windowSetHTML(webview pointer, html string) { + webkitWebViewLoadAlternateHTML(webview, html, "wails://", nil) +} + +func windowSetKeepAbove(window pointer, alwaysOnTop bool) { + gtkWindowKeepAbove(window, alwaysOnTop) +} + +func windowSetResizable(window pointer, resizable bool) { + // FIXME: Does this work? + gtkWindowSetResizable( + pointer(window), + resizable, + ) +} + +func windowSetTitle(window pointer, title string) { + gtkWindowSetTitle(pointer(window), title) +} + +func windowSetTransparent(window pointer) { + screen := gtkWidgetGetScreen(pointer(window)) + visual := gdkScreenGetRgbaVisual(screen) + if visual == 0 { + return + } + if gdkScreenIsComposited(screen) == 1 { + gtkWidgetSetAppPaintable(pointer(window), 1) + gtkWidgetSetVisual(pointer(window), visual) + } +} + +func windowSetURL(webview pointer, uri string) { + webkitWebViewLoadUri(webview, uri) +} + +func windowSetupSignalHandlers(windowId uint, window, webview pointer, emit func(e events.WindowEventType)) { + handleDelete := purego.NewCallback(func(pointer) { + emit(events.Common.WindowClosing) + }) + gSignalConnectData(window, "delete-event", handleDelete, 0, false, 0) + + /* + event = C.CString("load-changed") + defer C.free(unsafe.Pointer(event)) + C.signal_connect(webview, event, C.webviewLoadChanged, unsafe.Pointer(&w.parent.id)) + */ + + // TODO: Handle mouse button / drag events + /* id := C.uint(windowId) + event = C.CString("button-press-event") + C.signal_connect((*C.GtkWidget)(unsafe.Pointer(webview)), event, C.onButtonEvent, unsafe.Pointer(&id)) + C.free(unsafe.Pointer(event)) + event = C.CString("button-release-event") + defer C.free(unsafe.Pointer(event)) + C.signal_connect((*C.GtkWidget)(unsafe.Pointer(webview)), event, onButtonEvent, unsafe.Pointer(&id)) + */ +} + +func windowToggleDevTools(webview pointer) { + settings := webkitWebViewGetSettings(pointer(webview)) + webkitSettingsSetEnableDeveloperExtras( + settings, + !webkitSettingsGetEnableDeveloperExtras(settings)) +} + +func windowUnfullscreen(window pointer) { + gtkWindowUnfullscreen(window) +} + +func windowUnmaximize(window pointer) { + gtkWindowUnmaximize(window) +} + +func windowZoom(webview pointer) float64 { + return webkitWebViewGetZoom(webview) +} + +func windowZoomIn(webview pointer) { + ZoomInFactor := 1.10 + windowZoomSet(webview, windowZoom(webview)*ZoomInFactor) +} +func windowZoomOut(webview pointer) { + ZoomOutFactor := -1.10 + windowZoomSet(webview, windowZoom(webview)*ZoomOutFactor) +} + +func windowZoomSet(webview pointer, zoom float64) { + if zoom < 1.0 { // 1.0 is the smallest allowable + zoom = 1.0 + } + webkitWebViewSetZoomLevel(webview, zoom) +} + +func windowMove(window pointer, x, y int) { + gtkWindowMove(window, x, y) +} + +func runChooserDialog(window pointer, allowMultiple, createFolders, showHidden bool, currentFolder, title string, action int, acceptLabel string, filters []FileFilter) ([]string, error) { + GtkResponseCancel := 0 + GtkResponseAccept := 1 + + fc := gtkFileChooserDialogNew( + title, + window, + action, + "_Cancel", + GtkResponseCancel, + acceptLabel, + GtkResponseAccept, + 0) + + gtkFileChooserSetAction(fc, action) + + gtkFilters := []pointer{} + for _, filter := range filters { + f := gtkFileFilterNew() + gtkFileFilterSetName(f, filter.DisplayName) + gtkFileFilterAddPattern(f, filter.Pattern) + gtkFileChooserAddFilter(fc, f) + gtkFilters = append(gtkFilters, f) + } + gtkFileChooserSetSelectMultiple(fc, allowMultiple) + gtkFileChooserSetCreateFolders(fc, createFolders) + gtkFileChooserSetShowHidden(fc, showHidden) + + if currentFolder != "" { + gtkFileChooserSetCurrentFolder(fc, currentFolder) + } + + buildStringAndFree := func(s pointer) string { + bytes := []byte{} + p := unsafe.Pointer(s) + for { + val := *(*byte)(p) + if val == 0 { // this is the null terminator + break + } + bytes = append(bytes, val) + p = unsafe.Add(p, 1) + } + gFree(s) // so we don't have to iterate a second time + return string(bytes) + } + + response := gtkDialogRun(fc) + selections := []string{} + if response == GtkResponseAccept { + filenames := gtkFileChooserGetFilenames(fc) + iter := filenames + count := 0 + for { + selections = append(selections, buildStringAndFree(iter.data)) + iter = iter.next + if iter == nil || count == 1024 { + break + } + count++ + } + } + defer gtkWidgetDestroy(fc) + return selections, nil +} + +// dialog related +func runOpenFileDialog(dialog *OpenFileDialogStruct) ([]string, error) { + const GtkFileChooserActionOpen = 0 + + window := pointer(0) + if dialog.window != nil { + window = (dialog.window.(*WebviewWindow).impl).(*linuxWebviewWindow).window + } + + buttonText := dialog.buttonText + if buttonText == "" { + buttonText = "_Open" + } + + return runChooserDialog( + window, + dialog.allowsMultipleSelection, + dialog.canCreateDirectories, + dialog.showHiddenFiles, + dialog.directory, + dialog.title, + GtkFileChooserActionOpen, + buttonText, + dialog.filters) +} + +func runQuestionDialog(parent pointer, options *MessageDialog) int { + dType, ok := map[DialogType]int{ + InfoDialogType: GtkMessageInfo, + WarningDialogType: GtkMessageWarning, + QuestionDialogType: GtkMessageQuestion, + }[options.DialogType] + if !ok { + // FIXME: Add logging here! + dType = GtkMessageInfo + } + buttonMask := GtkButtonsOk + if len(options.Buttons) > 0 { + buttonMask = GtkButtonsNone + } + + dialog := gtkMessageDialogNew( + pointer(parent), + GtkDialogModal|GtkDialogDestroyWithParent, + dType, + buttonMask, + options.Message) + + if options.Title != "" { + gtkWindowSetTitle(dialog, options.Title) + } + + GdkColorspaceRGB := 0 + + if img, err := pngToImage(options.Icon); err == nil { + gbytes := gBytesNewStatic(uintptr(unsafe.Pointer(&img.Pix[0])), len(img.Pix)) + + defer gBytesUnref(gbytes) + pixBuf := gdkPixbufNewFromBytes( + gbytes, + GdkColorspaceRGB, + 1, // has_alpha + 8, + img.Bounds().Dx(), + img.Bounds().Dy(), + img.Stride, + ) + image := gtkImageNewFromPixbuf(pixBuf) + widgetSetVisible(image, false) + contentArea := gtkDialogGetContentArea(dialog) + gtkContainerAdd(contentArea, image) + } + + for i, button := range options.Buttons { + gtkDialogAddButton( + dialog, + button.Label, + i, + ) + if button.IsDefault { + gtkDialogSetDefaultResponse(dialog, i) + } + } + defer gtkWidgetDestroy(dialog) + return gtkDialogRun(dialog) +} + +func runSaveFileDialog(dialog *SaveFileDialogStruct) (string, error) { + const GtkFileChooserActionSave = 1 + + window := pointer(0) + buttonText := dialog.buttonText + if buttonText == "" { + buttonText = "_Save" + } + results, err := runChooserDialog( + window, + false, // multiple selection + dialog.canCreateDirectories, + dialog.showHiddenFiles, + dialog.directory, + dialog.title, + GtkFileChooserActionSave, + buttonText, + dialog.filters) + + if err != nil || len(results) == 0 { + return "", err + } + + return results[0], nil +} + +func isOnMainThread() bool { + return mainThreadId == gThreadSelf() +} diff --git a/v3/pkg/application/logger_default.go b/v3/pkg/application/logger_default.go new file mode 100644 index 000000000..53401432e --- /dev/null +++ b/v3/pkg/application/logger_default.go @@ -0,0 +1,20 @@ +//go:build !windows + +package application + +import ( + "log/slog" + "os" + "time" + + "github.com/lmittmann/tint" + "github.com/mattn/go-isatty" +) + +func DefaultLogger(level slog.Level) *slog.Logger { + return slog.New(tint.NewHandler(os.Stderr, &tint.Options{ + TimeFormat: time.Kitchen, + NoColor: !isatty.IsTerminal(os.Stderr.Fd()), + Level: level, + })) +} diff --git a/v3/pkg/application/logger_windows.go b/v3/pkg/application/logger_windows.go new file mode 100644 index 000000000..684c6ae0b --- /dev/null +++ b/v3/pkg/application/logger_windows.go @@ -0,0 +1,20 @@ +//go:build windows + +package application + +import ( + "github.com/lmittmann/tint" + "github.com/mattn/go-colorable" + "github.com/mattn/go-isatty" + "log/slog" + "os" + "time" +) + +func DefaultLogger(level slog.Level) *slog.Logger { + return slog.New(tint.NewHandler(colorable.NewColorable(os.Stderr), &tint.Options{ + TimeFormat: time.Kitchen, + NoColor: !isatty.IsTerminal(os.Stderr.Fd()), + Level: level, + })) +} diff --git a/v3/pkg/application/mainthread.go b/v3/pkg/application/mainthread.go new file mode 100644 index 000000000..b76663aa2 --- /dev/null +++ b/v3/pkg/application/mainthread.go @@ -0,0 +1,87 @@ +package application + +import ( + "sync" +) + +var mainThreadFunctionStore = make(map[uint]func()) +var mainThreadFunctionStoreLock sync.RWMutex + +func generateFunctionStoreID() uint { + startID := 0 + for { + if _, ok := mainThreadFunctionStore[uint(startID)]; !ok { + return uint(startID) + } + startID++ + if startID == 0 { + Fatal("Too many functions have been dispatched to the main thread") + } + } +} + +func InvokeSync(fn func()) { + var wg sync.WaitGroup + wg.Add(1) + globalApplication.dispatchOnMainThread(func() { + defer processPanicHandlerRecover() + fn() + wg.Done() + }) + wg.Wait() +} + +func InvokeSyncWithResult[T any](fn func() T) (res T) { + var wg sync.WaitGroup + wg.Add(1) + globalApplication.dispatchOnMainThread(func() { + defer processPanicHandlerRecover() + res = fn() + wg.Done() + }) + wg.Wait() + return res +} + +func InvokeSyncWithError(fn func() error) (err error) { + var wg sync.WaitGroup + wg.Add(1) + globalApplication.dispatchOnMainThread(func() { + defer processPanicHandlerRecover() + err = fn() + wg.Done() + }) + wg.Wait() + return +} + +func InvokeSyncWithResultAndError[T any](fn func() (T, error)) (res T, err error) { + var wg sync.WaitGroup + wg.Add(1) + globalApplication.dispatchOnMainThread(func() { + defer processPanicHandlerRecover() + res, err = fn() + wg.Done() + }) + wg.Wait() + return res, err +} + +func InvokeSyncWithResultAndOther[T any, U any](fn func() (T, U)) (res T, other U) { + var wg sync.WaitGroup + wg.Add(1) + globalApplication.dispatchOnMainThread(func() { + defer processPanicHandlerRecover() + res, other = fn() + wg.Done() + }) + wg.Wait() + return res, other +} + +func InvokeAsync(fn func()) { + globalApplication.dispatchOnMainThread(func() { + defer processPanicHandlerRecover() + fn() + }) +} diff --git a/v3/pkg/application/mainthread_darwin.go b/v3/pkg/application/mainthread_darwin.go new file mode 100644 index 000000000..70e4d70b1 --- /dev/null +++ b/v3/pkg/application/mainthread_darwin.go @@ -0,0 +1,45 @@ +//go:build darwin + +package application + +/* +#cgo CFLAGS: -mmacosx-version-min=10.13 -x objective-c +#cgo LDFLAGS: -framework Cocoa + +#include "Cocoa/Cocoa.h" + +extern void dispatchOnMainThreadCallback(unsigned int); + +static void dispatchOnMainThread(unsigned int id) { + dispatch_async(dispatch_get_main_queue(), ^{ + dispatchOnMainThreadCallback(id); + }); +} + +static bool onMainThread() { + return [NSThread isMainThread]; +} + +*/ +import "C" + +func (m *macosApp) isOnMainThread() bool { + return bool(C.onMainThread()) +} + +func (m *macosApp) dispatchOnMainThread(id uint) { + C.dispatchOnMainThread(C.uint(id)) +} + +//export dispatchOnMainThreadCallback +func dispatchOnMainThreadCallback(callbackID C.uint) { + mainThreadFunctionStoreLock.RLock() + id := uint(callbackID) + fn := mainThreadFunctionStore[id] + if fn == nil { + Fatal("dispatchCallback called with invalid id: %v", id) + } + delete(mainThreadFunctionStore, id) + mainThreadFunctionStoreLock.RUnlock() + fn() +} diff --git a/v3/pkg/application/mainthread_linux.go b/v3/pkg/application/mainthread_linux.go new file mode 100644 index 000000000..572b081be --- /dev/null +++ b/v3/pkg/application/mainthread_linux.go @@ -0,0 +1,18 @@ +//go:build linux + +package application + +func (m *linuxApp) dispatchOnMainThread(id uint) { + dispatchOnMainThread(id) +} + +func executeOnMainThread(callbackID uint) { + mainThreadFunctionStoreLock.RLock() + fn := mainThreadFunctionStore[callbackID] + if fn == nil { + Fatal("dispatchCallback called with invalid id: %v", callbackID) + } + delete(mainThreadFunctionStore, callbackID) + mainThreadFunctionStoreLock.RUnlock() + fn() +} diff --git a/v3/pkg/application/mainthread_windows.go b/v3/pkg/application/mainthread_windows.go new file mode 100644 index 000000000..fd59ff1e7 --- /dev/null +++ b/v3/pkg/application/mainthread_windows.go @@ -0,0 +1,127 @@ +//go:build windows + +package application + +import ( + "runtime" + "sort" + "unsafe" + + "github.com/wailsapp/wails/v3/pkg/w32" +) + +var ( + wmInvokeCallback uint32 +) + +func init() { + wmInvokeCallback = w32.RegisterWindowMessage(w32.MustStringToUTF16Ptr("WailsV0.InvokeCallback")) +} + +// initMainLoop must be called with the same OSThread that is used to call runMainLoop() later. +func (m *windowsApp) initMainLoop() { + runtime.LockOSThread() + defer runtime.UnlockOSThread() + if m.mainThreadWindowHWND != 0 { + panic("initMainLoop was already called") + } + + // We need a hidden window so we can PostMessage to it, if we don't use PostMessage for dispatching to a HWND + // messages might get lost if a modal inner loop is being run. + // We had this once in V2: https://github.com/wailsapp/wails/issues/969 + // See: https://devblogs.microsoft.com/oldnewthing/20050426-18/?p=35783 + // See also: https://learn.microsoft.com/en-us/windows/win32/winmsg/using-messages-and-message-queues#creating-a-message-loop + // > Because the system directs messages to individual windows in an application, a thread must create at least one window before starting its message loop. + m.mainThreadWindowHWND = w32.CreateWindowEx( + 0, + windowClassName, + w32.MustStringToUTF16Ptr("__wails_hidden_mainthread"), + w32.WS_DISABLED, + w32.CW_USEDEFAULT, + w32.CW_USEDEFAULT, + 0, + 0, + 0, + 0, + w32.GetModuleHandle(""), + nil) + + m.mainThreadID, _ = w32.GetWindowThreadProcessId(m.mainThreadWindowHWND) +} + +func (m *windowsApp) runMainLoop() int { + runtime.LockOSThread() + defer runtime.UnlockOSThread() + if m.invokeRequired() { + panic("invokeRequired for runMainLoop, the mainloop must be running on the same OSThread as the mainThreadWindow has been created on") + } + + msg := (*w32.MSG)(unsafe.Pointer(w32.GlobalAlloc(0, uint32(unsafe.Sizeof(w32.MSG{}))))) + defer w32.GlobalFree(w32.HGLOBAL(unsafe.Pointer(msg))) + + for w32.GetMessage(msg, 0, 0, 0) != 0 { + w32.TranslateMessage(msg) + w32.DispatchMessage(msg) + } + + return int(msg.WParam) +} + +func (m *windowsApp) dispatchOnMainThread(id uint) { + mainThreadHWND := m.mainThreadWindowHWND + if mainThreadHWND == 0 { + panic("initMainLoop was not called") + } + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + if m.invokeRequired() { + w32.PostMessage(mainThreadHWND, wmInvokeCallback, uintptr(id), 0) + } else { + mainThreadFunctionStoreLock.Lock() + fn := mainThreadFunctionStore[id] + delete(mainThreadFunctionStore, id) + mainThreadFunctionStoreLock.Unlock() + + if fn == nil { + Fatal("dispatchOnMainThread called with invalid id: %v", id) + } + fn() + } +} + +func (m *windowsApp) invokeRequired() bool { + mainThreadID := m.mainThreadID + if mainThreadID == 0 { + panic("initMainLoop was not called") + } + + return mainThreadID != w32.GetCurrentThreadId() +} + +func (m *windowsApp) invokeCallback(wParam, lParam uintptr) { + // TODO: Should we invoke just one or all queued? In v2 we always invoked all pendings... + runtime.LockOSThread() + defer runtime.UnlockOSThread() + if m.invokeRequired() { + panic("invokeCallback must always be called on the MainOSThread") + } + + mainThreadFunctionStoreLock.Lock() + fnIDs := make([]uint, 0, len(mainThreadFunctionStore)) + for id := range mainThreadFunctionStore { + fnIDs = append(fnIDs, id) + } + sort.Slice(fnIDs, func(i, j int) bool { return fnIDs[i] < fnIDs[j] }) + + fns := make([]func(), len(fnIDs)) + for i, id := range fnIDs { + fns[i] = mainThreadFunctionStore[id] + delete(mainThreadFunctionStore, id) + } + mainThreadFunctionStoreLock.Unlock() + + for _, fn := range fns { + fn() + } +} diff --git a/v3/pkg/application/menu.go b/v3/pkg/application/menu.go new file mode 100644 index 000000000..f4a18f39a --- /dev/null +++ b/v3/pkg/application/menu.go @@ -0,0 +1,100 @@ +package application + +type menuImpl interface { + update() +} + +type Menu struct { + items []*MenuItem + label string + + impl menuImpl +} + +func NewMenu() *Menu { + return &Menu{} +} + +func (m *Menu) Add(label string) *MenuItem { + result := newMenuItem(label) + m.items = append(m.items, result) + return result +} + +func (m *Menu) AddSeparator() { + result := newMenuItemSeparator() + m.items = append(m.items, result) +} + +func (m *Menu) AddCheckbox(label string, enabled bool) *MenuItem { + result := newMenuItemCheckbox(label, enabled) + m.items = append(m.items, result) + return result +} + +func (m *Menu) AddRadio(label string, enabled bool) *MenuItem { + result := newMenuItemRadio(label, enabled) + m.items = append(m.items, result) + return result +} + +func (m *Menu) Update() { + m.processRadioGroups() + if m.impl == nil { + m.impl = newMenuImpl(m) + } + m.impl.update() +} + +func (m *Menu) AddSubmenu(s string) *Menu { + result := newSubMenuItem(s) + m.items = append(m.items, result) + return result.submenu +} + +func (m *Menu) AddRole(role Role) *Menu { + result := newRole(role) + if result != nil { + m.items = append(m.items, result) + } + return m +} + +func (m *Menu) processRadioGroups() { + var radioGroup []*MenuItem + for _, item := range m.items { + if item.itemType == submenu { + item.submenu.processRadioGroups() + continue + } + if item.itemType == radio { + radioGroup = append(radioGroup, item) + } else { + if len(radioGroup) > 0 { + for _, item := range radioGroup { + item.radioGroupMembers = radioGroup + } + radioGroup = nil + } + } + } + if len(radioGroup) > 0 { + for _, item := range radioGroup { + item.radioGroupMembers = radioGroup + } + } +} + +func (m *Menu) SetLabel(label string) { + m.label = label +} + +func (m *Menu) setContextData(data *ContextMenuData) { + for _, item := range m.items { + item.setContextData(data) + } +} + +func (a *App) NewMenu() *Menu { + return &Menu{} +} diff --git a/v3/pkg/application/menu_darwin.go b/v3/pkg/application/menu_darwin.go new file mode 100644 index 000000000..1c9c98e19 --- /dev/null +++ b/v3/pkg/application/menu_darwin.go @@ -0,0 +1,121 @@ +//go:build darwin + +package application + +/* +#cgo CFLAGS: -mmacosx-version-min=10.10 -x objective-c +#cgo LDFLAGS: -framework Cocoa + +#include "menuitem_darwin.h" + +extern void setMenuItemChecked(void*, unsigned int, bool); +extern void setMenuItemBitmap(void*, unsigned char*, int); + +// Clear and release all menu items in the menu +void clearMenu(void* nsMenu) { + NSMenu *menu = (NSMenu *)nsMenu; + [menu removeAllItems]; +} + + +// Create a new NSMenu +void* createNSMenu(char* label) { + NSMenu *menu = [[NSMenu alloc] init]; + if( label != NULL && strlen(label) > 0 ) { + menu.title = [NSString stringWithUTF8String:label]; + free(label); + } + [menu setAutoenablesItems:NO]; + return (void*)menu; +} + +void addMenuItem(void* nsMenu, void* nsMenuItem) { + NSMenu *menu = (NSMenu *)nsMenu; + [menu addItem:nsMenuItem]; +} + +// add seperator to menu +void addMenuSeparator(void* nsMenu) { + NSMenu *menu = (NSMenu *)nsMenu; + [menu addItem:[NSMenuItem separatorItem]]; +} + +// Set the submenu of a menu item +void setMenuItemSubmenu(void* nsMenuItem, void* nsMenu) { + NSMenuItem *menuItem = (NSMenuItem *)nsMenuItem; + NSMenu *menu = (NSMenu *)nsMenu; + [menuItem setSubmenu:menu]; +} + +// Add services menu +static void addServicesMenu(void* menu) { + NSMenu *nsMenu = (__bridge NSMenu *)menu; + [NSApp setServicesMenu:nsMenu]; +} + + +*/ +import "C" +import "unsafe" + +type macosMenu struct { + menu *Menu + + nsMenu unsafe.Pointer +} + +func newMenuImpl(menu *Menu) *macosMenu { + result := &macosMenu{ + menu: menu, + } + return result +} + +func (m *macosMenu) update() { + if m.nsMenu == nil { + m.nsMenu = C.createNSMenu(C.CString(m.menu.label)) + } else { + C.clearMenu(m.nsMenu) + } + m.processMenu(m.nsMenu, m.menu) +} + +func (m *macosMenu) processMenu(parent unsafe.Pointer, menu *Menu) { + for _, item := range menu.items { + switch item.itemType { + case submenu: + submenu := item.submenu + nsSubmenu := C.createNSMenu(C.CString(item.label)) + m.processMenu(nsSubmenu, submenu) + menuItem := newMenuItemImpl(item) + item.impl = menuItem + C.addMenuItem(parent, menuItem.nsMenuItem) + C.setMenuItemSubmenu(menuItem.nsMenuItem, nsSubmenu) + if item.role == ServicesMenu { + C.addServicesMenu(nsSubmenu) + } + case text, checkbox, radio: + menuItem := newMenuItemImpl(item) + item.impl = menuItem + C.addMenuItem(parent, menuItem.nsMenuItem) + case separator: + C.addMenuSeparator(parent) + } + if item.bitmap != nil { + macMenuItem := item.impl.(*macosMenuItem) + C.setMenuItemBitmap(macMenuItem.nsMenuItem, (*C.uchar)(&item.bitmap[0]), C.int(len(item.bitmap))) + } + + } +} + +func defaultApplicationMenu() *Menu { + menu := NewMenu() + menu.AddRole(AppMenu) + menu.AddRole(FileMenu) + menu.AddRole(EditMenu) + menu.AddRole(ViewMenu) + menu.AddRole(WindowMenu) + menu.AddRole(HelpMenu) + return menu +} diff --git a/v3/pkg/application/menu_linux.go b/v3/pkg/application/menu_linux.go new file mode 100644 index 000000000..045614cdc --- /dev/null +++ b/v3/pkg/application/menu_linux.go @@ -0,0 +1,118 @@ +//go:build linux + +package application + +type linuxMenu struct { + menu *Menu + native pointer +} + +func newMenuImpl(menu *Menu) *linuxMenu { + result := &linuxMenu{ + menu: menu, + native: menuBarNew(), + } + return result +} + +func (m *linuxMenu) run() { + m.update() +} + +func (m *linuxMenu) update() { + m.processMenu(m.menu) +} + +func (m *linuxMenu) processMenu(menu *Menu) { + if menu.impl == nil { + menu.impl = &linuxMenu{ + menu: menu, + native: menuNew(), + } + } + var currentRadioGroup GSListPointer + + for _, item := range menu.items { + // drop the group if we have run out of radio items + if item.itemType != radio { + currentRadioGroup = nilRadioGroup + } + + switch item.itemType { + case submenu: + menuItem := newMenuItemImpl(item) + item.impl = menuItem + m.processMenu(item.submenu) + m.addSubMenuToItem(item.submenu, item) + m.addMenuItem(menu, item) + case text, checkbox: + menuItem := newMenuItemImpl(item) + item.impl = menuItem + m.addMenuItem(menu, item) + case radio: + menuItem := newRadioItemImpl(item, currentRadioGroup) + item.impl = menuItem + m.addMenuItem(menu, item) + currentRadioGroup = menuGetRadioGroup(menuItem) + case separator: + m.addMenuSeparator(menu) + } + + } + + for _, item := range menu.items { + if item.callback != nil { + m.attachHandler(item) + } + } + +} + +func (m *linuxMenu) attachHandler(item *MenuItem) { + (item.impl).(*linuxMenuItem).handlerId = attachMenuHandler(item) +} + +func (m *linuxMenu) addSubMenuToItem(menu *Menu, item *MenuItem) { + if menu.impl == nil { + menu.impl = &linuxMenu{ + menu: menu, + native: menuNew(), + } + } + menuSetSubmenu(item, menu) +} + +func (m *linuxMenu) addMenuItem(parent *Menu, menu *MenuItem) { + menuAppend(parent, menu) +} + +func (m *linuxMenu) addMenuSeparator(menu *Menu) { + menuAddSeparator(menu) + +} + +func (m *linuxMenu) addServicesMenu(menu *Menu) { + // FIXME: Should this be required? +} + +func (l *linuxMenu) createMenu(name string, items []*MenuItem) *Menu { + impl := newMenuImpl(&Menu{label: name}) + menu := &Menu{ + label: name, + items: items, + impl: impl, + } + impl.menu = menu + return menu +} + +func defaultApplicationMenu() *Menu { + menu := NewMenu() + menu.AddRole(AppMenu) + menu.AddRole(FileMenu) + menu.AddRole(EditMenu) + menu.AddRole(ViewMenu) + menu.AddRole(WindowMenu) + menu.AddRole(HelpMenu) + return menu +} diff --git a/v3/pkg/application/menu_windows.go b/v3/pkg/application/menu_windows.go new file mode 100644 index 000000000..fbb58caf7 --- /dev/null +++ b/v3/pkg/application/menu_windows.go @@ -0,0 +1,124 @@ +//go:build windows + +package application + +import ( + "github.com/wailsapp/wails/v3/pkg/w32" +) + +type windowsMenu struct { + menu *Menu + + hWnd w32.HWND + hMenu w32.HMENU + currentMenuID int + menuMapping map[int]*MenuItem + checkboxItems []*Menu +} + +func newMenuImpl(menu *Menu) *windowsMenu { + result := &windowsMenu{ + menu: menu, + menuMapping: make(map[int]*MenuItem), + } + + return result +} + +func (w *windowsMenu) update() { + if w.hMenu != 0 { + w32.DestroyMenu(w.hMenu) + } + w.hMenu = w32.NewPopupMenu() + w.processMenu(w.hMenu, w.menu) +} + +func (w *windowsMenu) processMenu(parentMenu w32.HMENU, inputMenu *Menu) { + for _, item := range inputMenu.items { + if item.Hidden() { + continue + } + w.currentMenuID++ + itemID := w.currentMenuID + w.menuMapping[itemID] = item + + flags := uint32(w32.MF_STRING) + if item.disabled { + flags = flags | w32.MF_GRAYED + } + if item.checked { + flags = flags | w32.MF_CHECKED + } + if item.IsSeparator() { + flags = flags | w32.MF_SEPARATOR + } + // + //if item.IsCheckbox() { + // w.checkboxItems[item] = append(w.checkboxItems[item], itemID) + //} + //if item.IsRadio() { + // currentRadioGroup.Add(itemID, item) + //} else { + // if len(currentRadioGroup) > 0 { + // for _, radioMember := range currentRadioGroup { + // currentRadioGroup := currentRadioGroup + // p.radioGroups[radioMember.MenuItem] = append(p.radioGroups[radioMember.MenuItem], ¤tRadioGroup) + // } + // currentRadioGroup = RadioGroup{} + // } + //} + + if item.submenu != nil { + flags = flags | w32.MF_POPUP + newSubmenu := w32.CreateMenu() + w.processMenu(newSubmenu, item.submenu) + itemID = int(newSubmenu) + } + + var menuText = w32.MustStringToUTF16Ptr(item.Label()) + + w32.AppendMenu(parentMenu, flags, uintptr(itemID), menuText) + if item.bitmap != nil { + w32.SetMenuIcons(parentMenu, itemID, item.bitmap, nil) + } + } +} + +func (w *windowsMenu) ShowAtCursor() { + InvokeSync(func() { + x, y, ok := w32.GetCursorPos() + if !ok { + return + } + w.ShowAt(x, y) + }) +} + +func (w *windowsMenu) ShowAt(x int, y int) { + w.update() + w32.TrackPopupMenuEx(w.hMenu, + w32.TPM_LEFTALIGN, + int32(x), + int32(y), + w.hWnd, + nil) + w32.PostMessage(w.hWnd, w32.WM_NULL, 0, 0) +} + +func (w *windowsMenu) ProcessCommand(cmdMsgID int) { + item := w.menuMapping[cmdMsgID] + if item == nil { + return + } + item.handleClick() +} + +func defaultApplicationMenu() *Menu { + menu := NewMenu() + menu.AddRole(FileMenu) + menu.AddRole(EditMenu) + menu.AddRole(ViewMenu) + menu.AddRole(WindowMenu) + menu.AddRole(HelpMenu) + return menu +} diff --git a/v3/pkg/application/menuitem.go b/v3/pkg/application/menuitem.go new file mode 100644 index 000000000..b16619b5d --- /dev/null +++ b/v3/pkg/application/menuitem.go @@ -0,0 +1,334 @@ +package application + +import ( + "fmt" + "os" + "sync" + "sync/atomic" +) + +type menuItemType int + +const ( + text menuItemType = iota + separator + checkbox + radio + submenu +) + +var menuItemID uintptr +var menuItemMap = make(map[uint]*MenuItem) +var menuItemMapLock sync.Mutex + +func addToMenuItemMap(menuItem *MenuItem) { + menuItemMapLock.Lock() + menuItemMap[menuItem.id] = menuItem + menuItemMapLock.Unlock() +} + +func getMenuItemByID(id uint) *MenuItem { + menuItemMapLock.Lock() + defer menuItemMapLock.Unlock() + return menuItemMap[id] +} + +type menuItemImpl interface { + setTooltip(s string) + setLabel(s string) + setDisabled(disabled bool) + setChecked(checked bool) + setAccelerator(accelerator *accelerator) + setHidden(hidden bool) + setBitmap(bitmap []byte) +} + +type MenuItem struct { + id uint + label string + tooltip string + disabled bool + checked bool + hidden bool + bitmap []byte + submenu *Menu + callback func(*Context) + itemType menuItemType + accelerator *accelerator + role Role + contextMenuData *ContextMenuData + + impl menuItemImpl + radioGroupMembers []*MenuItem +} + +func newMenuItem(label string) *MenuItem { + result := &MenuItem{ + id: uint(atomic.AddUintptr(&menuItemID, 1)), + label: label, + itemType: text, + } + addToMenuItemMap(result) + return result +} + +func newMenuItemSeparator() *MenuItem { + result := &MenuItem{ + id: uint(atomic.AddUintptr(&menuItemID, 1)), + itemType: separator, + } + return result +} + +func newMenuItemCheckbox(label string, checked bool) *MenuItem { + result := &MenuItem{ + id: uint(atomic.AddUintptr(&menuItemID, 1)), + label: label, + checked: checked, + itemType: checkbox, + } + addToMenuItemMap(result) + return result +} + +func newMenuItemRadio(label string, checked bool) *MenuItem { + result := &MenuItem{ + id: uint(atomic.AddUintptr(&menuItemID, 1)), + label: label, + checked: checked, + itemType: radio, + } + addToMenuItemMap(result) + return result +} + +func newSubMenuItem(label string) *MenuItem { + result := &MenuItem{ + id: uint(atomic.AddUintptr(&menuItemID, 1)), + label: label, + itemType: submenu, + submenu: &Menu{ + label: label, + }, + } + addToMenuItemMap(result) + return result +} + +func newRole(role Role) *MenuItem { + switch role { + case AppMenu: + return newAppMenu() + case EditMenu: + return newEditMenu() + case FileMenu: + return newFileMenu() + case ViewMenu: + return newViewMenu() + case ServicesMenu: + return newServicesMenu() + case SpeechMenu: + return newSpeechMenu() + case WindowMenu: + return newWindowMenu() + case HelpMenu: + return newHelpMenu() + case Hide: + return newHideMenuItem() + case HideOthers: + return newHideOthersMenuItem() + case UnHide: + return newUnhideMenuItem() + case Undo: + return newUndoMenuItem() + case Redo: + return newRedoMenuItem() + case Cut: + return newCutMenuItem() + case Copy: + return newCopyMenuItem() + case Paste: + return newPasteMenuItem() + case PasteAndMatchStyle: + return newPasteAndMatchStyleMenuItem() + case SelectAll: + return newSelectAllMenuItem() + case Delete: + return newDeleteMenuItem() + case Quit: + return newQuitMenuItem() + case Close: + return newCloseMenuItem() + case About: + return newAboutMenuItem() + case Reload: + return newReloadMenuItem() + case ForceReload: + return newForceReloadMenuItem() + case ToggleFullscreen: + return newToggleFullscreenMenuItem() + case ToggleDevTools: + return newToggleDevToolsMenuItem() + case ResetZoom: + return newZoomResetMenuItem() + case ZoomIn: + return newZoomInMenuItem() + case ZoomOut: + return newZoomOutMenuItem() + case Minimize: + return newMinimizeMenuItem() + case Zoom: + return newZoomMenuItem() + case FullScreen: + return newFullScreenMenuItem() + + default: + globalApplication.error(fmt.Sprintf("No support for role: %v", role)) + os.Exit(1) + } + return nil +} + +func newServicesMenu() *MenuItem { + serviceMenu := newSubMenuItem("Services") + serviceMenu.role = ServicesMenu + return serviceMenu +} + +func (m *MenuItem) handleClick() { + var ctx = newContext(). + withClickedMenuItem(m). + withContextMenuData(m.contextMenuData) + if m.itemType == checkbox { + m.checked = !m.checked + ctx.withChecked(m.checked) + if m.impl != nil { + m.impl.setChecked(m.checked) + } + } + if m.itemType == radio { + for _, member := range m.radioGroupMembers { + member.checked = false + if member.impl != nil { + member.impl.setChecked(false) + } + } + m.checked = true + ctx.withChecked(true) + if m.impl != nil { + m.impl.setChecked(true) + } + } + if m.callback != nil { + go m.callback(ctx) + } +} + +func (m *MenuItem) SetAccelerator(shortcut string) *MenuItem { + accelerator, err := parseAccelerator(shortcut) + if err != nil { + globalApplication.error("invalid accelerator:", err.Error()) + return m + } + m.accelerator = accelerator + if m.impl != nil { + m.impl.setAccelerator(accelerator) + } + return m +} + +func (m *MenuItem) SetTooltip(s string) *MenuItem { + m.tooltip = s + if m.impl != nil { + m.impl.setTooltip(s) + } + return m +} + +func (m *MenuItem) SetLabel(s string) *MenuItem { + m.label = s + if m.impl != nil { + m.impl.setLabel(s) + } + return m +} + +func (m *MenuItem) SetEnabled(enabled bool) *MenuItem { + m.disabled = !enabled + if m.impl != nil { + m.impl.setDisabled(m.disabled) + } + return m +} + +func (m *MenuItem) SetBitmap(bitmap []byte) *MenuItem { + m.bitmap = bitmap + if m.impl != nil { + m.impl.setBitmap(bitmap) + } + return m +} + +func (m *MenuItem) SetChecked(checked bool) *MenuItem { + m.checked = checked + if m.impl != nil { + m.impl.setChecked(m.checked) + } + return m +} + +func (m *MenuItem) SetHidden(hidden bool) *MenuItem { + m.hidden = hidden + if m.impl != nil { + m.impl.setHidden(m.hidden) + } + return m +} + +func (m *MenuItem) Checked() bool { + return m.checked +} + +func (m *MenuItem) IsSeparator() bool { + return m.itemType == separator +} + +func (m *MenuItem) IsSubmenu() bool { + return m.itemType == submenu +} + +func (m *MenuItem) IsCheckbox() bool { + return m.itemType == checkbox +} + +func (m *MenuItem) IsRadio() bool { + return m.itemType == radio +} + +func (m *MenuItem) Hidden() bool { + return m.hidden +} + +func (m *MenuItem) OnClick(f func(*Context)) *MenuItem { + m.callback = f + return m +} + +func (m *MenuItem) Label() string { + return m.label +} + +func (m *MenuItem) Tooltip() string { + return m.tooltip +} + +func (m *MenuItem) Enabled() bool { + return !m.disabled +} + +func (m *MenuItem) setContextData(data *ContextMenuData) { + m.contextMenuData = data + if m.submenu != nil { + m.submenu.setContextData(data) + } +} diff --git a/v3/pkg/application/menuitem_darwin.go b/v3/pkg/application/menuitem_darwin.go new file mode 100644 index 000000000..2093d15ff --- /dev/null +++ b/v3/pkg/application/menuitem_darwin.go @@ -0,0 +1,653 @@ +//go:build darwin + +package application + +/* +#cgo CFLAGS: -mmacosx-version-min=10.13 -x objective-c +#cgo LDFLAGS: -framework Cocoa -framework WebKit + +#include "Cocoa/Cocoa.h" +#include "menuitem_darwin.h" +#include "application_darwin.h" + +#define unicode(input) [NSString stringWithFormat:@"%C", input] + +// Create menu item +void* newMenuItem(unsigned int menuItemID, char *label, bool disabled, char* tooltip) { + MenuItem *menuItem = [MenuItem new]; + + // Label + menuItem.title = [NSString stringWithUTF8String:label]; + + if( disabled ) { + [menuItem setTarget:nil]; + } else { + [menuItem setTarget:menuItem]; + } + menuItem.menuItemID = menuItemID; + menuItem.action = @selector(handleClick); + menuItem.enabled = !disabled; + + // Tooltip + if( tooltip != NULL ) { + menuItem.toolTip = [NSString stringWithUTF8String:tooltip]; + free(tooltip); + } + + // Set the tag + [menuItem setTag:menuItemID]; + + return (void*)menuItem; +} + +// set menu item label +void setMenuItemLabel(void* nsMenuItem, char *label) { + MenuItem *menuItem = (MenuItem *)nsMenuItem; + menuItem.title = [NSString stringWithUTF8String:label]; +} + +// set menu item disabled +void setMenuItemDisabled(void* nsMenuItem, bool disabled) { + dispatch_async(dispatch_get_main_queue(), ^{ + MenuItem *menuItem = (MenuItem *)nsMenuItem; + [menuItem setEnabled:!disabled]; + // remove target + if( disabled ) { + [menuItem setTarget:nil]; + } else { + [menuItem setTarget:menuItem]; + } + }); +} + +// set menu item hidden +void setMenuItemHidden(void* nsMenuItem, bool hidden) { + dispatch_async(dispatch_get_main_queue(), ^{ + MenuItem *menuItem = (MenuItem *)nsMenuItem; + [menuItem setHidden:hidden]; + }); +} + +// set menu item tooltip +void setMenuItemTooltip(void* nsMenuItem, char *tooltip) { + MenuItem *menuItem = (MenuItem *)nsMenuItem; + menuItem.toolTip = [NSString stringWithUTF8String:tooltip]; +} + +// Check menu item +void setMenuItemChecked(void* nsMenuItem, bool checked) { + MenuItem *menuItem = (MenuItem *)nsMenuItem; + menuItem.state = checked ? NSControlStateValueOn : NSControlStateValueOff; +} + +NSString* translateKey(NSString* key) { + + // Guard against no accelerator key + if( key == NULL ) { + return @""; + } + + if( [key isEqualToString:@"backspace"] ) { + return unicode(0x0008); + } + if( [key isEqualToString:@"tab"] ) { + return unicode(0x0009); + } + if( [key isEqualToString:@"return"] ) { + return unicode(0x000d); + } + if( [key isEqualToString:@"enter"] ) { + return unicode(0x000d); + } + if( [key isEqualToString:@"escape"] ) { + return unicode(0x001b); + } + if( [key isEqualToString:@"left"] ) { + return unicode(0x001c); + } + if( [key isEqualToString:@"right"] ) { + return unicode(0x001d); + } + if( [key isEqualToString:@"up"] ) { + return unicode(0x001e); + } + if( [key isEqualToString:@"down"] ) { + return unicode(0x001f); + } + if( [key isEqualToString:@"space"] ) { + return unicode(0x0020); + } + if( [key isEqualToString:@"delete"] ) { + return unicode(0x007f); + } + if( [key isEqualToString:@"home"] ) { + return unicode(0x2196); + } + if( [key isEqualToString:@"end"] ) { + return unicode(0x2198); + } + if( [key isEqualToString:@"page up"] ) { + return unicode(0x21de); + } + if( [key isEqualToString:@"page down"] ) { + return unicode(0x21df); + } + if( [key isEqualToString:@"f1"] ) { + return unicode(0xf704); + } + if( [key isEqualToString:@"f2"] ) { + return unicode(0xf705); + } + if( [key isEqualToString:@"f3"] ) { + return unicode(0xf706); + } + if( [key isEqualToString:@"f4"] ) { + return unicode(0xf707); + } + if( [key isEqualToString:@"f5"] ) { + return unicode(0xf708); + } + if( [key isEqualToString:@"f6"] ) { + return unicode(0xf709); + } + if( [key isEqualToString:@"f7"] ) { + return unicode(0xf70a); + } + if( [key isEqualToString:@"f8"] ) { + return unicode(0xf70b); + } + if( [key isEqualToString:@"f9"] ) { + return unicode(0xf70c); + } + if( [key isEqualToString:@"f10"] ) { + return unicode(0xf70d); + } + if( [key isEqualToString:@"f11"] ) { + return unicode(0xf70e); + } + if( [key isEqualToString:@"f12"] ) { + return unicode(0xf70f); + } + if( [key isEqualToString:@"f13"] ) { + return unicode(0xf710); + } + if( [key isEqualToString:@"f14"] ) { + return unicode(0xf711); + } + if( [key isEqualToString:@"f15"] ) { + return unicode(0xf712); + } + if( [key isEqualToString:@"f16"] ) { + return unicode(0xf713); + } + if( [key isEqualToString:@"f17"] ) { + return unicode(0xf714); + } + if( [key isEqualToString:@"f18"] ) { + return unicode(0xf715); + } + if( [key isEqualToString:@"f19"] ) { + return unicode(0xf716); + } + if( [key isEqualToString:@"f20"] ) { + return unicode(0xf717); + } + if( [key isEqualToString:@"f21"] ) { + return unicode(0xf718); + } + if( [key isEqualToString:@"f22"] ) { + return unicode(0xf719); + } + if( [key isEqualToString:@"f23"] ) { + return unicode(0xf71a); + } + if( [key isEqualToString:@"f24"] ) { + return unicode(0xf71b); + } + if( [key isEqualToString:@"f25"] ) { + return unicode(0xf71c); + } + if( [key isEqualToString:@"f26"] ) { + return unicode(0xf71d); + } + if( [key isEqualToString:@"f27"] ) { + return unicode(0xf71e); + } + if( [key isEqualToString:@"f28"] ) { + return unicode(0xf71f); + } + if( [key isEqualToString:@"f29"] ) { + return unicode(0xf720); + } + if( [key isEqualToString:@"f30"] ) { + return unicode(0xf721); + } + if( [key isEqualToString:@"f31"] ) { + return unicode(0xf722); + } + if( [key isEqualToString:@"f32"] ) { + return unicode(0xf723); + } + if( [key isEqualToString:@"f33"] ) { + return unicode(0xf724); + } + if( [key isEqualToString:@"f34"] ) { + return unicode(0xf725); + } + if( [key isEqualToString:@"f35"] ) { + return unicode(0xf726); + } + if( [key isEqualToString:@"numLock"] ) { + return unicode(0xf739); + } + return key; +} + +// Set the menuitem key equivalent +void setMenuItemKeyEquivalent(void* nsMenuItem, char *key, int modifier) { + MenuItem *menuItem = (MenuItem *)nsMenuItem; + NSString *nskey = [NSString stringWithUTF8String:key]; + menuItem.keyEquivalent = translateKey(nskey); + menuItem.keyEquivalentModifierMask = modifier; + free(key); +} + +// Call the copy selector on the pasteboard +static void copyToPasteboard(char *text) { + NSPasteboard *pasteboard = [NSPasteboard generalPasteboard]; + [pasteboard clearContents]; + [pasteboard setString:[NSString stringWithUTF8String:text] forType:NSPasteboardTypeString]; +} + +// Call the paste selector on the pasteboard +static char *pasteFromPasteboard(void) { + NSPasteboard *pasteboard = [NSPasteboard generalPasteboard]; + NSString *text = [pasteboard stringForType:NSPasteboardTypeString]; + if( text == nil ) { + return NULL; + } + return strdup([text UTF8String]); +} + +// Call paste selector to paste text +static void paste(void) { + [NSApp sendAction:@selector(paste:) to:nil from:nil]; +} + +// Call copy selector to copy text +static void copy(void) { + [NSApp sendAction:@selector(copy:) to:nil from:nil]; +} + +// Call cut selector to cut text +static void cut(void) { + [NSApp sendAction:@selector(cut:) to:nil from:nil]; +} + +// Call selectAll selector to select all text +static void selectAll(void) { + [NSApp sendAction:@selector(selectAll:) to:nil from:nil]; +} + +// Call delete selector to delete text +static void delete(void) { + [NSApp sendAction:@selector(delete:) to:nil from:nil]; +} + +// Call undo selector to undo text +static void undo(void) { + [NSApp sendAction:@selector(undo:) to:nil from:nil]; +} + +// Call redo selector to redo text +static void redo(void) { + [NSApp sendAction:@selector(redo:) to:nil from:nil]; +} + +// Call startSpeaking selector to start speaking text +static void startSpeaking(void) { + [NSApp sendAction:@selector(startSpeaking:) to:nil from:nil]; +} + +// Call stopSpeaking selector to stop speaking text +static void stopSpeaking(void) { + [NSApp sendAction:@selector(stopSpeaking:) to:nil from:nil]; +} + +static void pasteAndMatchStyle(void) { + [NSApp sendAction:@selector(pasteAndMatchStyle:) to:nil from:nil]; +} + +static void hideApplication(void) { + [[NSApplication sharedApplication] hide:nil]; +} + +// hideOthers hides all other applications +static void hideOthers(void) { + [[NSApplication sharedApplication] hideOtherApplications:nil]; +} + +// showAll shows all hidden applications +static void showAll(void) { + [[NSApplication sharedApplication] unhideAllApplications:nil]; +} + +void setMenuItemBitmap(void* nsMenuItem, unsigned char *bitmap, int length) { + MenuItem *menuItem = (MenuItem *)nsMenuItem; + NSImage *image = [[NSImage alloc] initWithData:[NSData dataWithBytes:bitmap length:length]]; + [menuItem setImage:image]; +} +*/ +import "C" +import ( + "runtime" + "unsafe" +) + +type macosMenuItem struct { + menuItem *MenuItem + + nsMenuItem unsafe.Pointer +} + +func (m macosMenuItem) setTooltip(tooltip string) { + C.setMenuItemTooltip(m.nsMenuItem, C.CString(tooltip)) +} + +func (m macosMenuItem) setLabel(s string) { + C.setMenuItemLabel(m.nsMenuItem, C.CString(s)) +} + +func (m macosMenuItem) setDisabled(disabled bool) { + C.setMenuItemDisabled(m.nsMenuItem, C.bool(disabled)) +} + +func (m macosMenuItem) setChecked(checked bool) { + C.setMenuItemChecked(m.nsMenuItem, C.bool(checked)) +} + +func (m macosMenuItem) setHidden(hidden bool) { + C.setMenuItemHidden(m.nsMenuItem, C.bool(hidden)) +} + +func (m macosMenuItem) setBitmap(bitmap []byte) { + C.setMenuItemBitmap(m.nsMenuItem, (*C.uchar)(&bitmap[0]), C.int(len(bitmap))) +} + +func (m macosMenuItem) setAccelerator(accelerator *accelerator) { + // Set the keyboard shortcut of the menu item + var modifier C.int + var key *C.char + if accelerator != nil { + modifier = C.int(toMacModifier(accelerator.Modifiers)) + key = C.CString(accelerator.Key) + } + + // Convert the key to a string + C.setMenuItemKeyEquivalent(m.nsMenuItem, key, modifier) +} + +func newMenuItemImpl(item *MenuItem) *macosMenuItem { + result := &macosMenuItem{ + menuItem: item, + } + + switch item.itemType { + case text, checkbox, submenu, radio: + result.nsMenuItem = unsafe.Pointer(C.newMenuItem(C.uint(item.id), C.CString(item.label), C.bool(item.disabled), C.CString(item.tooltip))) + if item.itemType == checkbox || item.itemType == radio { + C.setMenuItemChecked(result.nsMenuItem, C.bool(item.checked)) + } + if item.accelerator != nil { + result.setAccelerator(item.accelerator) + } + default: + panic("WTF") + } + return result +} + +func newSpeechMenu() *MenuItem { + speechMenu := NewMenu() + speechMenu.Add("Start Speaking"). + SetAccelerator("CmdOrCtrl+OptionOrAlt+Shift+."). + OnClick(func(ctx *Context) { + C.startSpeaking() + }) + speechMenu.Add("Stop Speaking"). + SetAccelerator("CmdOrCtrl+OptionOrAlt+Shift+,"). + OnClick(func(ctx *Context) { + C.stopSpeaking() + }) + subMenu := newSubMenuItem("Speech") + subMenu.submenu = speechMenu + return subMenu +} + +func newHideMenuItem() *MenuItem { + return newMenuItem("Hide " + globalApplication.options.Name). + SetAccelerator("CmdOrCtrl+h"). + OnClick(func(ctx *Context) { + C.hideApplication() + }) +} + +func newHideOthersMenuItem() *MenuItem { + return newMenuItem("Hide Others"). + SetAccelerator("CmdOrCtrl+OptionOrAlt+h"). + OnClick(func(ctx *Context) { + C.hideOthers() + }) +} + +func newUnhideMenuItem() *MenuItem { + return newMenuItem("Show All"). + OnClick(func(ctx *Context) { + C.showAll() + }) +} + +func newUndoMenuItem() *MenuItem { + return newMenuItem("Undo"). + SetAccelerator("CmdOrCtrl+z"). + OnClick(func(ctx *Context) { + C.undo() + }) +} + +// newRedoMenuItem creates a new menu item for redoing the last action +func newRedoMenuItem() *MenuItem { + return newMenuItem("Redo"). + SetAccelerator("CmdOrCtrl+Shift+z"). + OnClick(func(ctx *Context) { + C.redo() + }) +} + +func newCutMenuItem() *MenuItem { + return newMenuItem("Cut"). + SetAccelerator("CmdOrCtrl+x"). + OnClick(func(ctx *Context) { + C.cut() + }) +} + +func newCopyMenuItem() *MenuItem { + return newMenuItem("Copy"). + SetAccelerator("CmdOrCtrl+c"). + OnClick(func(ctx *Context) { + C.copy() + }) +} + +func newPasteMenuItem() *MenuItem { + return newMenuItem("Paste"). + SetAccelerator("CmdOrCtrl+v"). + OnClick(func(ctx *Context) { + C.paste() + }) +} + +func newPasteAndMatchStyleMenuItem() *MenuItem { + return newMenuItem("Paste and Match Style"). + SetAccelerator("CmdOrCtrl+OptionOrAlt+Shift+v"). + OnClick(func(ctx *Context) { + C.pasteAndMatchStyle() + }) +} + +func newDeleteMenuItem() *MenuItem { + return newMenuItem("Delete"). + SetAccelerator("backspace"). + OnClick(func(ctx *Context) { + C.delete() + }) +} + +func newQuitMenuItem() *MenuItem { + return newMenuItem("Quit " + globalApplication.options.Name). + SetAccelerator("CmdOrCtrl+q"). + OnClick(func(ctx *Context) { + globalApplication.Quit() + }) +} + +func newSelectAllMenuItem() *MenuItem { + return newMenuItem("Select All"). + SetAccelerator("CmdOrCtrl+a"). + OnClick(func(ctx *Context) { + C.selectAll() + }) +} + +func newAboutMenuItem() *MenuItem { + return newMenuItem("About " + globalApplication.options.Name). + OnClick(func(ctx *Context) { + globalApplication.ShowAboutDialog() + }) +} + +func newCloseMenuItem() *MenuItem { + return newMenuItem("Close"). + SetAccelerator("CmdOrCtrl+w"). + OnClick(func(ctx *Context) { + currentWindow := globalApplication.CurrentWindow() + if currentWindow != nil { + currentWindow.Close() + } + }) +} + +func newReloadMenuItem() *MenuItem { + return newMenuItem("Reload"). + SetAccelerator("CmdOrCtrl+r"). + OnClick(func(ctx *Context) { + currentWindow := globalApplication.CurrentWindow() + if currentWindow != nil { + currentWindow.Reload() + } + }) +} + +func newForceReloadMenuItem() *MenuItem { + return newMenuItem("Force Reload"). + SetAccelerator("CmdOrCtrl+Shift+r"). + OnClick(func(ctx *Context) { + currentWindow := globalApplication.CurrentWindow() + if currentWindow != nil { + currentWindow.ForceReload() + } + }) +} + +func newToggleFullscreenMenuItem() *MenuItem { + result := newMenuItem("Toggle Full Screen"). + OnClick(func(ctx *Context) { + currentWindow := globalApplication.CurrentWindow() + if currentWindow != nil { + currentWindow.ToggleFullscreen() + } + }) + if runtime.GOOS == "darwin" { + result.SetAccelerator("Ctrl+Command+F") + } else { + result.SetAccelerator("F11") + } + return result +} + +func newToggleDevToolsMenuItem() *MenuItem { + return newMenuItem("Toggle Developer Tools"). + SetAccelerator("Alt+Command+I"). + OnClick(func(ctx *Context) { + currentWindow := globalApplication.CurrentWindow() + if currentWindow != nil { + currentWindow.ToggleDevTools() + } + }) +} + +func newZoomResetMenuItem() *MenuItem { + // reset zoom menu item + return newMenuItem("Actual Size"). + SetAccelerator("CmdOrCtrl+0"). + OnClick(func(ctx *Context) { + currentWindow := globalApplication.CurrentWindow() + if currentWindow != nil { + currentWindow.ZoomReset() + } + }) +} + +func newZoomInMenuItem() *MenuItem { + return newMenuItem("Zoom In"). + SetAccelerator("CmdOrCtrl+plus"). + OnClick(func(ctx *Context) { + currentWindow := globalApplication.CurrentWindow() + if currentWindow != nil { + currentWindow.ZoomIn() + } + }) +} + +func newZoomOutMenuItem() *MenuItem { + return newMenuItem("Zoom Out"). + SetAccelerator("CmdOrCtrl+-"). + OnClick(func(ctx *Context) { + currentWindow := globalApplication.CurrentWindow() + if currentWindow != nil { + currentWindow.ZoomOut() + } + }) +} + +func newMinimizeMenuItem() *MenuItem { + return newMenuItem("Minimize"). + SetAccelerator("CmdOrCtrl+M"). + OnClick(func(ctx *Context) { + currentWindow := globalApplication.CurrentWindow() + if currentWindow != nil { + currentWindow.Minimise() + } + }) +} + +func newZoomMenuItem() *MenuItem { + return newMenuItem("Zoom"). + OnClick(func(ctx *Context) { + currentWindow := globalApplication.CurrentWindow() + if currentWindow != nil { + currentWindow.Zoom() + } + }) +} + +func newFullScreenMenuItem() *MenuItem { + return newMenuItem("Fullscreen"). + OnClick(func(ctx *Context) { + currentWindow := globalApplication.CurrentWindow() + if currentWindow != nil { + currentWindow.Fullscreen() + } + }) +} diff --git a/v3/pkg/application/menuitem_darwin.h b/v3/pkg/application/menuitem_darwin.h new file mode 100644 index 000000000..91fce726e --- /dev/null +++ b/v3/pkg/application/menuitem_darwin.h @@ -0,0 +1,18 @@ + +#ifndef MenuItemDelegate_h +#define MenuItemDelegate_h + +#import + +extern void processMenuItemClick(unsigned int); + +@interface MenuItem : NSMenuItem + +@property unsigned int menuItemID; + +- (void) handleClick; + +@end + + +#endif /* MenuItemDelegate_h */ diff --git a/v3/pkg/application/menuitem_darwin.m b/v3/pkg/application/menuitem_darwin.m new file mode 100644 index 000000000..39369502d --- /dev/null +++ b/v3/pkg/application/menuitem_darwin.m @@ -0,0 +1,13 @@ +//go:build darwin + +#import + +#import "menuitem_darwin.h" + +@implementation MenuItem + +- (void) handleClick { + processMenuItemClick(self.menuItemID); +} + +@end diff --git a/v3/pkg/application/menuitem_linux.go b/v3/pkg/application/menuitem_linux.go new file mode 100644 index 000000000..058b58609 --- /dev/null +++ b/v3/pkg/application/menuitem_linux.go @@ -0,0 +1,371 @@ +//go:build linux + +package application + +import ( + "fmt" + "runtime" +) + +type linuxMenuItem struct { + menuItem *MenuItem + native pointer + handlerId uint +} + +func (l linuxMenuItem) setTooltip(tooltip string) { + InvokeSync(func() { + l.blockSignal() + defer l.unBlockSignal() + menuItemSetToolTip(l.native, tooltip) + }) +} + +func (l linuxMenuItem) blockSignal() { + if l.handlerId != 0 { + menuItemSignalBlock(l.native, l.handlerId, true) + } +} +func (l linuxMenuItem) setBitmap(data []byte) { + InvokeSync(func() { + l.blockSignal() + defer l.unBlockSignal() + menuItemSetBitmap(l.native, data) + }) +} + +func (l linuxMenuItem) unBlockSignal() { + if l.handlerId != 0 { + menuItemSignalBlock(l.native, l.handlerId, false) + } +} + +func (l linuxMenuItem) setLabel(s string) { + InvokeSync(func() { + l.blockSignal() + defer l.unBlockSignal() + menuItemSetLabel(l.native, s) + }) +} + +func (l linuxMenuItem) isChecked() bool { + return menuItemChecked(l.native) +} + +func (l linuxMenuItem) setDisabled(disabled bool) { + InvokeSync(func() { + l.blockSignal() + defer l.unBlockSignal() + menuItemSetDisabled(l.native, disabled) + }) +} + +func (l linuxMenuItem) setChecked(checked bool) { + InvokeSync(func() { + l.blockSignal() + defer l.unBlockSignal() + menuItemSetChecked(l.native, checked) + }) +} + +func (l linuxMenuItem) setHidden(hidden bool) { + InvokeSync(func() { + l.blockSignal() + defer l.unBlockSignal() + widgetSetVisible(l.native, hidden) + }) +} + +func (l linuxMenuItem) setAccelerator(accelerator *accelerator) { + fmt.Println("setAccelerator", accelerator) + // Set the keyboard shortcut of the menu item + // var modifier C.int + // var key *C.char + if accelerator != nil { + // modifier = C.int(toMacModifier(accelerator.Modifiers)) + // key = C.CString(accelerator.Key) + } + + // Convert the key to a string + // C.setMenuItemKeyEquivalent(m.nsMenuItem, key, modifier) +} + +func newMenuItemImpl(item *MenuItem) *linuxMenuItem { + result := &linuxMenuItem{ + menuItem: item, + } + switch item.itemType { + case text: + result.native = menuItemNew(item.label, item.bitmap) + + case checkbox: + result.native = menuCheckItemNew(item.label, item.bitmap) + result.setChecked(item.checked) + if item.accelerator != nil { + result.setAccelerator(item.accelerator) + } + case submenu: + result.native = menuItemNew(item.label, item.bitmap) + + default: + panic(fmt.Sprintf("Unknown menu type: %v", item.itemType)) + } + result.setDisabled(result.menuItem.disabled) + return result +} + +func newRadioItemImpl(item *MenuItem, group GSListPointer) *linuxMenuItem { + result := &linuxMenuItem{ + menuItem: item, + native: menuRadioItemNew(group, item.label), + } + result.setChecked(item.checked) + result.setDisabled(result.menuItem.disabled) + return result +} + +func newSpeechMenu() *MenuItem { + speechMenu := NewMenu() + speechMenu.Add("Start Speaking"). + SetAccelerator("CmdOrCtrl+OptionOrAlt+Shift+."). + OnClick(func(ctx *Context) { + // C.startSpeaking() + }) + speechMenu.Add("Stop Speaking"). + SetAccelerator("CmdOrCtrl+OptionOrAlt+Shift+,"). + OnClick(func(ctx *Context) { + // C.stopSpeaking() + }) + subMenu := newSubMenuItem("Speech") + subMenu.submenu = speechMenu + return subMenu +} + +func newHideMenuItem() *MenuItem { + return newMenuItem("Hide " + globalApplication.options.Name). + SetAccelerator("CmdOrCtrl+h"). + OnClick(func(ctx *Context) { + + // C.hideApplication() + }) +} + +func newHideOthersMenuItem() *MenuItem { + return newMenuItem("Hide Others"). + SetAccelerator("CmdOrCtrl+OptionOrAlt+h"). + OnClick(func(ctx *Context) { + // C.hideOthers() + }) +} + +func newUnhideMenuItem() *MenuItem { + return newMenuItem("Show All"). + OnClick(func(ctx *Context) { + // C.showAll() + }) +} + +func newUndoMenuItem() *MenuItem { + return newMenuItem("Undo"). + SetAccelerator("CmdOrCtrl+z"). + OnClick(func(ctx *Context) { + // C.undo() + }) +} + +// newRedoMenuItem creates a new menu item for redoing the last action +func newRedoMenuItem() *MenuItem { + return newMenuItem("Redo"). + SetAccelerator("CmdOrCtrl+Shift+z"). + OnClick(func(ctx *Context) { + // C.redo() + }) +} + +func newCutMenuItem() *MenuItem { + return newMenuItem("Cut"). + SetAccelerator("CmdOrCtrl+x"). + OnClick(func(ctx *Context) { + // C.cut() + }) +} + +func newCopyMenuItem() *MenuItem { + return newMenuItem("Copy"). + SetAccelerator("CmdOrCtrl+c"). + OnClick(func(ctx *Context) { + // C.copy() + }) +} + +func newPasteMenuItem() *MenuItem { + return newMenuItem("Paste"). + SetAccelerator("CmdOrCtrl+v"). + OnClick(func(ctx *Context) { + // C.paste() + }) +} + +func newPasteAndMatchStyleMenuItem() *MenuItem { + return newMenuItem("Paste and Match Style"). + SetAccelerator("CmdOrCtrl+OptionOrAlt+Shift+v"). + OnClick(func(ctx *Context) { + // C.pasteAndMatchStyle() + }) +} + +func newDeleteMenuItem() *MenuItem { + return newMenuItem("Delete"). + SetAccelerator("backspace"). + OnClick(func(ctx *Context) { + // C.delete() + }) +} + +func newQuitMenuItem() *MenuItem { + return newMenuItem("Quit " + globalApplication.options.Name). + SetAccelerator("CmdOrCtrl+q"). + OnClick(func(ctx *Context) { + globalApplication.Quit() + }) +} + +func newSelectAllMenuItem() *MenuItem { + return newMenuItem("Select All"). + SetAccelerator("CmdOrCtrl+a"). + OnClick(func(ctx *Context) { + // C.selectAll() + }) +} + +func newAboutMenuItem() *MenuItem { + return newMenuItem("About " + globalApplication.options.Name). + OnClick(func(ctx *Context) { + globalApplication.ShowAboutDialog() + }) +} + +func newCloseMenuItem() *MenuItem { + return newMenuItem("Close"). + SetAccelerator("CmdOrCtrl+w"). + OnClick(func(ctx *Context) { + currentWindow := globalApplication.CurrentWindow() + if currentWindow != nil { + currentWindow.Close() + } + }) +} + +func newReloadMenuItem() *MenuItem { + return newMenuItem("Reload"). + SetAccelerator("CmdOrCtrl+r"). + OnClick(func(ctx *Context) { + currentWindow := globalApplication.CurrentWindow() + if currentWindow != nil { + currentWindow.Reload() + } + }) +} + +func newForceReloadMenuItem() *MenuItem { + return newMenuItem("Force Reload"). + SetAccelerator("CmdOrCtrl+Shift+r"). + OnClick(func(ctx *Context) { + currentWindow := globalApplication.CurrentWindow() + if currentWindow != nil { + currentWindow.ForceReload() + } + }) +} + +func newToggleFullscreenMenuItem() *MenuItem { + result := newMenuItem("Toggle Full Screen"). + OnClick(func(ctx *Context) { + currentWindow := globalApplication.CurrentWindow() + if currentWindow != nil { + currentWindow.ToggleFullscreen() + } + }) + if runtime.GOOS == "darwin" { + result.SetAccelerator("Ctrl+Command+F") + } else { + result.SetAccelerator("F11") + } + return result +} + +func newToggleDevToolsMenuItem() *MenuItem { + return newMenuItem("Toggle Developer Tools"). + SetAccelerator("Alt+Command+I"). + OnClick(func(ctx *Context) { + currentWindow := globalApplication.CurrentWindow() + if currentWindow != nil { + currentWindow.ToggleDevTools() + } + }) +} + +func newZoomResetMenuItem() *MenuItem { + // reset zoom menu item + return newMenuItem("Actual Size"). + SetAccelerator("CmdOrCtrl+0"). + OnClick(func(ctx *Context) { + currentWindow := globalApplication.CurrentWindow() + if currentWindow != nil { + currentWindow.ZoomReset() + } + }) +} + +func newZoomInMenuItem() *MenuItem { + return newMenuItem("Zoom In"). + SetAccelerator("CmdOrCtrl+plus"). + OnClick(func(ctx *Context) { + currentWindow := globalApplication.CurrentWindow() + if currentWindow != nil { + currentWindow.ZoomIn() + } + }) +} + +func newZoomOutMenuItem() *MenuItem { + return newMenuItem("Zoom Out"). + SetAccelerator("CmdOrCtrl+-"). + OnClick(func(ctx *Context) { + currentWindow := globalApplication.CurrentWindow() + if currentWindow != nil { + currentWindow.ZoomOut() + } + }) +} + +func newMinimizeMenuItem() *MenuItem { + return newMenuItem("Minimize"). + SetAccelerator("CmdOrCtrl+M"). + OnClick(func(ctx *Context) { + currentWindow := globalApplication.CurrentWindow() + if currentWindow != nil { + currentWindow.Minimise() + } + }) +} + +func newZoomMenuItem() *MenuItem { + return newMenuItem("Zoom"). + OnClick(func(ctx *Context) { + currentWindow := globalApplication.CurrentWindow() + if currentWindow != nil { + currentWindow.Zoom() + } + }) +} + +func newFullScreenMenuItem() *MenuItem { + return newMenuItem("Fullscreen"). + OnClick(func(ctx *Context) { + currentWindow := globalApplication.CurrentWindow() + if currentWindow != nil { + currentWindow.Fullscreen() + } + }) +} diff --git a/v3/pkg/application/menuitem_windows.go b/v3/pkg/application/menuitem_windows.go new file mode 100644 index 000000000..abea7a17d --- /dev/null +++ b/v3/pkg/application/menuitem_windows.go @@ -0,0 +1,303 @@ +//go:build windows + +package application + +import ( + "github.com/wailsapp/wails/v3/pkg/w32" + "unsafe" +) + +type windowsMenuItem struct { + parent *Menu + menuItem *MenuItem + + hMenu w32.HMENU + id int + label string + disabled bool + checked bool + itemType menuItemType + hidden bool + submenu w32.HMENU + itemAfter *MenuItem +} + +func (m *windowsMenuItem) setHidden(hidden bool) { + m.hidden = hidden + if m.hidden { + // iterate the parent items and find the menu item before us + for i, item := range m.parent.items { + if item == m.menuItem { + if i < len(m.parent.items)-1 { + m.itemAfter = m.parent.items[i+1] + } else { + m.itemAfter = nil + } + break + } + } + // Get the position of this menu item in the parent menu + // m.pos = w32.GetMenuItemPosition(m.hMenu, uint32(m.id)) + // Remove from parent menu + w32.RemoveMenu(m.hMenu, m.id, w32.MF_BYCOMMAND) + } else { + // Add to parent menu + // Get the position of the item before us + var pos int + if m.itemAfter != nil { + for i, item := range m.parent.items { + if item == m.itemAfter { + pos = i - 1 + break + } + } + m.itemAfter = nil + } + w32.InsertMenuItem(m.hMenu, uint32(pos), true, m.getMenuInfo()) + } +} + +func (m *windowsMenuItem) Checked() bool { + return m.checked +} + +func (m *windowsMenuItem) IsSeparator() bool { + return m.itemType == separator +} + +func (m *windowsMenuItem) IsCheckbox() bool { + return m.itemType == checkbox +} + +func (m *windowsMenuItem) Enabled() bool { + return !m.disabled +} + +func (m *windowsMenuItem) update() { + w32.SetMenuItemInfo(m.hMenu, uint32(m.id), false, m.getMenuInfo()) +} + +func (m *windowsMenuItem) setLabel(label string) { + m.label = label + m.update() +} + +func (m *windowsMenuItem) setDisabled(disabled bool) { + m.disabled = disabled + m.update() +} + +func (m *windowsMenuItem) setChecked(checked bool) { + m.checked = checked + m.update() +} + +func (m *windowsMenuItem) setAccelerator(accelerator *accelerator) { + //// Set the keyboard shortcut of the menu item + //var modifier C.int + //var key *C.char + //if accelerator != nil { + // modifier = C.int(toMacModifier(accelerator.Modifiers)) + // key = C.CString(accelerator.Key) + //} + // + //// Convert the key to a string + //C.setMenuItemKeyEquivalent(m.nsMenuItem, key, modifier) +} + +func (m *windowsMenuItem) setBitmap(bitmap []byte) { + if m.menuItem.bitmap == nil { + return + } + + // Set the icon + err := w32.SetMenuIcons(m.hMenu, m.id, bitmap, nil) + if err != nil { + globalApplication.error("Unable to set bitmap on menu item", "error", err.Error()) + return + } + m.update() +} + +func newMenuItemImpl(item *MenuItem, parentMenu w32.HMENU, ID int) *windowsMenuItem { + result := &windowsMenuItem{ + menuItem: item, + hMenu: parentMenu, + id: ID, + disabled: item.disabled, + checked: item.checked, + itemType: item.itemType, + label: item.label, + hidden: item.hidden, + } + + return result +} + +func newSpeechMenu() *MenuItem { + panic("implement me") +} + +func newHideMenuItem() *MenuItem { + panic("implement me") + +} + +func newHideOthersMenuItem() *MenuItem { + panic("implement me") + +} + +func newUnhideMenuItem() *MenuItem { + panic("implement me") + +} + +func newUndoMenuItem() *MenuItem { + panic("implement me") + +} + +// newRedoMenuItem creates a new menu item for redoing the last action +func newRedoMenuItem() *MenuItem { + panic("implement me") + +} + +func newCutMenuItem() *MenuItem { + panic("implement me") + +} + +func newCopyMenuItem() *MenuItem { + panic("implement me") + +} + +func newPasteMenuItem() *MenuItem { + panic("implement me") + +} + +func newPasteAndMatchStyleMenuItem() *MenuItem { + panic("implement me") + +} + +func newDeleteMenuItem() *MenuItem { + panic("implement me") + +} + +func newQuitMenuItem() *MenuItem { + panic("implement me") + +} + +func newSelectAllMenuItem() *MenuItem { + panic("implement me") + +} + +func newAboutMenuItem() *MenuItem { + return newMenuItem("About " + globalApplication.options.Name). + OnClick(func(ctx *Context) { + globalApplication.ShowAboutDialog() + }) +} + +func newCloseMenuItem() *MenuItem { + panic("implement me") + +} + +func newReloadMenuItem() *MenuItem { + panic("implement me") + +} + +func newForceReloadMenuItem() *MenuItem { + panic("implement me") + +} + +func newToggleFullscreenMenuItem() *MenuItem { + panic("implement me") + +} + +func newToggleDevToolsMenuItem() *MenuItem { + panic("implement me") +} + +func newZoomResetMenuItem() *MenuItem { + panic("implement me") + +} + +func newZoomInMenuItem() *MenuItem { + panic("implement me") + +} + +func newZoomOutMenuItem() *MenuItem { + panic("implement me") + +} + +func newMinimizeMenuItem() *MenuItem { + panic("implement me") +} + +func newZoomMenuItem() *MenuItem { + panic("implement me") +} + +func newFullScreenMenuItem() *MenuItem { + panic("implement me") +} + +// ---------- unsupported on windows ---------- + +func (m *windowsMenuItem) setTooltip(_ string) { + // Unsupported +} + +func (m *windowsMenuItem) getMenuInfo() *w32.MENUITEMINFO { + var mii w32.MENUITEMINFO + mii.CbSize = uint32(unsafe.Sizeof(mii)) + mii.FMask = w32.MIIM_FTYPE | w32.MIIM_ID | w32.MIIM_STATE | w32.MIIM_STRING + if m.IsSeparator() { + mii.FType = w32.MFT_SEPARATOR + } else { + mii.FType = w32.MFT_STRING + //var text string + //if s := a.shortcut; s.Key != 0 { + // text = fmt.Sprintf("%s\t%s", a.text, s.String()) + // shortcut2Action[a.shortcut] = a + //} else { + // text = a.text + //} + mii.DwTypeData = w32.MustStringToUTF16Ptr(m.label) + mii.Cch = uint32(len([]rune(m.label))) + } + mii.WID = uint32(m.id) + if m.Enabled() { + mii.FState &^= w32.MFS_DISABLED + } else { + mii.FState |= w32.MFS_DISABLED + } + + if m.IsCheckbox() { + mii.FMask |= w32.MIIM_CHECKMARKS + } + if m.Checked() { + mii.FState |= w32.MFS_CHECKED + } + + if m.menuItem.submenu != nil { + mii.FMask |= w32.MIIM_SUBMENU + mii.HSubMenu = m.submenu + } + return &mii +} diff --git a/v3/pkg/application/messageprocessor.go b/v3/pkg/application/messageprocessor.go new file mode 100644 index 000000000..e1888cc76 --- /dev/null +++ b/v3/pkg/application/messageprocessor.go @@ -0,0 +1,193 @@ +package application + +import ( + "fmt" + jsoniter "github.com/json-iterator/go" + "log/slog" + "net/http" + "strconv" +) + +// TODO maybe we could use a new struct that has the targetWindow as an attribute so we could get rid of passing the targetWindow +// as parameter through every function call. + +const ( + callRequest int = 0 + clipboardRequest = 1 + applicationRequest = 2 + eventsRequest = 3 + contextMenuRequest = 4 + dialogRequest = 5 + windowRequest = 6 + screensRequest = 7 + systemRequest = 8 + browserRequest = 9 + httpRequest = 10 +) + +type MessageProcessor struct { + pluginManager *PluginManager + logger *slog.Logger +} + +func NewMessageProcessor(logger *slog.Logger) *MessageProcessor { + return &MessageProcessor{ + logger: logger, + } +} + +func (m *MessageProcessor) httpError(rw http.ResponseWriter, message string, args ...any) { + m.Error(message, args...) + rw.WriteHeader(http.StatusBadRequest) + rw.Write([]byte(fmt.Sprintf(message, args...))) +} + +func (m *MessageProcessor) getTargetWindow(r *http.Request) Window { + windowName := r.Header.Get(webViewRequestHeaderWindowName) + if windowName != "" { + return globalApplication.GetWindowByName(windowName) + } + windowID := r.Header.Get(webViewRequestHeaderWindowId) + if windowID == "" { + return nil + } + wID, err := strconv.ParseUint(windowID, 10, 64) + if err != nil { + m.Error("Window ID '%s' not parsable: %s", windowID, err) + return nil + } + targetWindow := globalApplication.getWindowForID(uint(wID)) + if targetWindow == nil { + m.Error("Window ID %d not found", wID) + return nil + } + return targetWindow +} + +func (m *MessageProcessor) HandleRuntimeCall(rw http.ResponseWriter, r *http.Request) { + object := r.URL.Query().Get("object") + if object != "" { + m.HandleRuntimeCallWithIDs(rw, r) + return + } + + //// Read "method" from query string + //method := r.URL.Query().Get("method") + //if method == "" { + // m.httpError(rw, "No method specified") + // return + //} + //splitMethod := strings.Split(method, ".") + //if len(splitMethod) != 2 { + // m.httpError(rw, "Invalid method format") + // return + //} + //// Get the object + //object = splitMethod[0] + //// Get the method + //method = splitMethod[1] + // + //params := QueryParams(r.URL.Query()) + // + //targetWindow := m.getTargetWindow(r) + //if targetWindow == nil { + // m.httpError(rw, "No valid window found") + // return + //} + // + //switch object { + //case "call": + // m.processCallMethod(method, rw, r, targetWindow, params) + //default: + // m.httpError(rw, "Unknown runtime call: %s", object) + //} +} + +func (m *MessageProcessor) HandleRuntimeCallWithIDs(rw http.ResponseWriter, r *http.Request) { + object, err := strconv.Atoi(r.URL.Query().Get("object")) + if err != nil { + m.httpError(rw, "Error decoding object value: "+err.Error()) + return + } + method, err := strconv.Atoi(r.URL.Query().Get("method")) + if err != nil { + m.httpError(rw, "Error decoding method value: "+err.Error()) + return + } + params := QueryParams(r.URL.Query()) + + targetWindow := m.getTargetWindow(r) + if targetWindow == nil { + m.httpError(rw, "No valid window found") + return + } + + switch object { + case windowRequest: + m.processWindowMethod(method, rw, r, targetWindow, params) + case clipboardRequest: + m.processClipboardMethod(method, rw, r, targetWindow, params) + case dialogRequest: + m.processDialogMethod(method, rw, r, targetWindow, params) + case eventsRequest: + m.processEventsMethod(method, rw, r, targetWindow, params) + case applicationRequest: + m.processApplicationMethod(method, rw, r, targetWindow, params) + case contextMenuRequest: + m.processContextMenuMethod(method, rw, r, targetWindow, params) + case screensRequest: + m.processScreensMethod(method, rw, r, targetWindow, params) + case callRequest: + m.processCallMethod(method, rw, r, targetWindow, params) + case systemRequest: + m.processSystemMethod(method, rw, r, targetWindow, params) + case browserRequest: + m.processBrowserMethod(method, rw, r, targetWindow, params) + case httpRequest: + m.processHTTPMethod(method, rw, r, targetWindow, params) + default: + m.httpError(rw, "Unknown runtime call: %d", object) + } +} + +func (m *MessageProcessor) Error(message string, args ...any) { + m.logger.Error(message, args...) +} + +func (m *MessageProcessor) Info(message string, args ...any) { + m.logger.Info(message, args...) +} + +func (m *MessageProcessor) json(rw http.ResponseWriter, data any) { + rw.Header().Set("Content-Type", "application/json") + // convert data to json + var jsonPayload = []byte("{}") + var err error + if data != nil { + jsonPayload, err = jsoniter.Marshal(data) + if err != nil { + m.Error("Unable to convert data to JSON. Please report this to the Wails team! Error: %s", 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) + return + } + m.ok(rw) +} + +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) + return + } + rw.Header().Set("Content-Type", "text/plain") + rw.WriteHeader(http.StatusOK) +} + +func (m *MessageProcessor) ok(rw http.ResponseWriter) { + rw.WriteHeader(http.StatusOK) +} diff --git a/v3/pkg/application/messageprocessor_application.go b/v3/pkg/application/messageprocessor_application.go new file mode 100644 index 000000000..fae7b55a6 --- /dev/null +++ b/v3/pkg/application/messageprocessor_application.go @@ -0,0 +1,36 @@ +package application + +import ( + "net/http" +) + +const ( + ApplicationHide = 0 + ApplicationShow = 1 + ApplicationQuit = 2 +) + +var applicationMethodNames = map[int]string{ + ApplicationQuit: "Quit", + ApplicationHide: "Hide", + ApplicationShow: "Show", +} + +func (m *MessageProcessor) processApplicationMethod(method int, rw http.ResponseWriter, r *http.Request, window Window, params QueryParams) { + switch method { + case ApplicationQuit: + globalApplication.Quit() + m.ok(rw) + case ApplicationHide: + globalApplication.Hide() + m.ok(rw) + case ApplicationShow: + globalApplication.Show() + m.ok(rw) + default: + m.httpError(rw, "Unknown application method: %d", method) + } + + m.Info("Runtime Call:", "method", "Application."+applicationMethodNames[method]) + +} diff --git a/v3/pkg/application/messageprocessor_browser.go b/v3/pkg/application/messageprocessor_browser.go new file mode 100644 index 000000000..164d0fdab --- /dev/null +++ b/v3/pkg/application/messageprocessor_browser.go @@ -0,0 +1,43 @@ +package application + +import ( + "github.com/pkg/browser" + "net/http" +) + +const ( + BrowserOpenURL = 0 +) + +var browserMethods = map[int]string{ + BrowserOpenURL: "OpenURL", +} + +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()) + return + } + + switch method { + case BrowserOpenURL: + url := args.String("url") + if url == nil { + m.Error("OpenURL: url is required") + return + } + err := browser.OpenURL(*url) + if err != nil { + m.Error("OpenURL: %s", err.Error()) + return + } + m.ok(rw) + m.Info("Runtime Call:", "method", "Browser."+browserMethods[method], "url", *url) + default: + m.httpError(rw, "Unknown browser method: %d", method) + return + } + +} diff --git a/v3/pkg/application/messageprocessor_call.go b/v3/pkg/application/messageprocessor_call.go new file mode 100644 index 000000000..6fe7965de --- /dev/null +++ b/v3/pkg/application/messageprocessor_call.go @@ -0,0 +1,80 @@ +package application + +import ( + "encoding/json" + "fmt" + "net/http" +) + +const ( + CallBinding = 0 +) + +func (m *MessageProcessor) callErrorCallback(window Window, message string, callID *string, err error) { + errorMsg := fmt.Sprintf(message, err) + m.Error(errorMsg) + window.CallError(*callID, errorMsg) +} + +func (m *MessageProcessor) callCallback(window Window, callID *string, result string, isJSON bool) { + window.CallResponse(*callID, result) +} + +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()) + return + } + fmt.Println("processCallMethod", args) + callID := args.String("call-id") + if callID == nil { + m.Error("call-id is required") + return + } + switch method { + case CallBinding: + 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.PackageName != "" { + 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.Name())) + 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 '%s' not found", options.Name())) + return + } + go func() { + result, err := boundMethod.Call(options.Args) + if err != nil { + m.callErrorCallback(window, "Error calling method: %s", callID, err) + 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) + return + } + } + m.callCallback(window, callID, string(jsonResult), true) + m.Info("Call Binding:", "method", boundMethod, "args", options.Args, "result", result) + }() + m.ok(rw) + default: + m.httpError(rw, "Unknown call method: %d", method) + } + +} diff --git a/v3/pkg/application/messageprocessor_clipboard.go b/v3/pkg/application/messageprocessor_clipboard.go new file mode 100644 index 000000000..8ba3b35d9 --- /dev/null +++ b/v3/pkg/application/messageprocessor_clipboard.go @@ -0,0 +1,44 @@ +package application + +import ( + "net/http" +) + +const ( + ClipboardSetText = 0 + ClipboardText = 1 +) + +var clipboardMethods = map[int]string{ + ClipboardSetText: "SetText", + ClipboardText: "Text", +} + +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()) + return + } + + switch method { + case ClipboardSetText: + text := args.String("text") + if text == nil { + m.Error("SetText: text is required") + return + } + globalApplication.Clipboard().SetText(*text) + m.ok(rw) + m.Info("Runtime Call:", "method", "Clipboard."+clipboardMethods[method], "text", *text) + case ClipboardText: + 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) + return + } + +} diff --git a/v3/pkg/application/messageprocessor_contextmenu.go b/v3/pkg/application/messageprocessor_contextmenu.go new file mode 100644 index 000000000..8b655d0eb --- /dev/null +++ b/v3/pkg/application/messageprocessor_contextmenu.go @@ -0,0 +1,40 @@ +package application + +import ( + "net/http" +) + +type ContextMenuData struct { + Id string `json:"id"` + X int `json:"x"` + Y int `json:"y"` + Data any `json:"data"` +} + +const ( + ContextMenuOpen = 0 +) + +var contextmenuMethodNames = map[int]string{ + ContextMenuOpen: "Open", +} + +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()) + return + } + window.OpenContextMenu(&data) + m.ok(rw) + default: + m.httpError(rw, "Unknown contextmenu method: %d", method) + } + + m.Info("Runtime:", "method", "ContextMenu."+contextmenuMethodNames[method]) + +} diff --git a/v3/pkg/application/messageprocessor_dialog.go b/v3/pkg/application/messageprocessor_dialog.go new file mode 100644 index 000000000..9c8dd73f9 --- /dev/null +++ b/v3/pkg/application/messageprocessor_dialog.go @@ -0,0 +1,166 @@ +package application + +import ( + "encoding/json" + "fmt" + "net/http" + "runtime" +) + +const ( + DialogInfo = 0 + DialogWarning = 1 + DialogError = 2 + DialogQuestion = 3 + DialogOpenFile = 4 + DialogSaveFile = 5 +) + +var dialogMethodNames = map[int]string{ + DialogInfo: "Info", + DialogWarning: "Warning", + DialogError: "Error", + DialogQuestion: "Question", + DialogOpenFile: "OpenFile", + DialogSaveFile: "SaveFile", +} + +func (m *MessageProcessor) dialogErrorCallback(window Window, message string, dialogID *string, err error) { + errorMsg := fmt.Sprintf(message, err) + m.Error(errorMsg) + window.DialogError(*dialogID, errorMsg) +} + +func (m *MessageProcessor) dialogCallback(window Window, dialogID *string, result string, isJSON bool) { + window.DialogResponse(*dialogID, result, isJSON) +} + +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()) + return + } + dialogID := args.String("dialog-id") + if dialogID == nil { + m.Error("dialog-id is required") + return + } + + var methodName = "Dialog." + dialogMethodNames[method] + + switch method { + case DialogInfo, DialogWarning, DialogError, DialogQuestion: + var options MessageDialogOptions + err := params.ToStruct(&options) + if err != nil { + m.dialogErrorCallback(window, "Error parsing dialog options: %s", dialogID, err) + return + } + if len(options.Buttons) == 0 { + switch runtime.GOOS { + case "darwin": + options.Buttons = []*Button{{Label: "OK", IsDefault: true}} + } + } + var dialog *MessageDialog + switch method { + case DialogInfo: + dialog = InfoDialog() + case DialogWarning: + dialog = WarningDialog() + case DialogError: + dialog = ErrorDialog() + case DialogQuestion: + dialog = QuestionDialog() + } + var detached = args.Bool("Detached") + if detached == nil || !*detached { + dialog.AttachToWindow(window) + } + + dialog.SetTitle(options.Title) + dialog.SetMessage(options.Message) + for _, button := range options.Buttons { + label := button.Label + button.OnClick(func() { + m.dialogCallback(window, dialogID, label, false) + }) + } + dialog.AddButtons(options.Buttons) + dialog.Show() + m.ok(rw) + m.Info("Runtime:", "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()) + return + } + var detached = args.Bool("Detached") + if detached == nil || !*detached { + options.Window = window.(*WebviewWindow) + } + dialog := OpenFileDialogWithOptions(&options) + + go func() { + if options.AllowsMultipleSelection { + files, err := dialog.PromptForMultipleSelection() + if err != nil { + m.dialogErrorCallback(window, "Error getting selection: %s", dialogID, err) + return + } else { + result, err := json.Marshal(files) + if err != nil { + m.dialogErrorCallback(window, "Error marshalling files: %s", dialogID, err) + return + } + m.dialogCallback(window, dialogID, string(result), true) + m.Info("Runtime:", "method", methodName, "result", result) + } + } else { + file, err := dialog.PromptForSingleSelection() + if err != nil { + m.dialogErrorCallback(window, "Error getting selection: %s", dialogID, err) + return + } + m.dialogCallback(window, dialogID, file, false) + m.Info("Runtime:", "method", methodName, "result", file) + } + }() + m.ok(rw) + m.Info("Runtime:", "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()) + return + } + var detached = args.Bool("Detached") + if detached == nil || !*detached { + options.Window = window.(*WebviewWindow) + } + dialog := SaveFileDialogWithOptions(&options) + + go func() { + file, err := dialog.PromptForSingleSelection() + if err != nil { + m.dialogErrorCallback(window, "Error getting selection: %s", dialogID, err) + return + } + m.dialogCallback(window, dialogID, file, false) + m.Info("Runtime:", "method", methodName, "result", file) + }() + m.ok(rw) + m.Info("Runtime:", "method", methodName, "options", options) + + default: + m.httpError(rw, "Unknown dialog method: %d", method) + } + +} diff --git a/v3/pkg/application/messageprocessor_events.go b/v3/pkg/application/messageprocessor_events.go new file mode 100644 index 000000000..7831f94d1 --- /dev/null +++ b/v3/pkg/application/messageprocessor_events.go @@ -0,0 +1,40 @@ +package application + +import ( + "net/http" +) + +const ( + EventsEmit = 0 +) + +var eventsMethodNames = map[int]string{ + EventsEmit: "Emit", +} + +func (m *MessageProcessor) processEventsMethod(method int, rw http.ResponseWriter, _ *http.Request, window Window, params QueryParams) { + + var event WailsEvent + + switch method { + case EventsEmit: + err := params.ToStruct(&event) + if err != nil { + m.httpError(rw, "Error parsing event: %s", err.Error()) + return + } + if event.Name == "" { + m.httpError(rw, "Event name must be specified") + return + } + event.Sender = window.Name() + globalApplication.Events.Emit(&event) + m.ok(rw) + default: + m.httpError(rw, "Unknown event method: %d", method) + return + } + + m.Info("Runtime:", "method", "Events."+eventsMethodNames[method], "name", event.Name, "sender", event.Sender, "data", event.Data, "cancelled", event.Cancelled) + +} diff --git a/v3/pkg/application/messageprocessor_http.go b/v3/pkg/application/messageprocessor_http.go new file mode 100644 index 000000000..7e94583c3 --- /dev/null +++ b/v3/pkg/application/messageprocessor_http.go @@ -0,0 +1,50 @@ +package application + +import ( + "encoding/json" + "io" + "net/http" +) + +const ( + httpFetch = 0 +) + +func (m *MessageProcessor) processHTTPMethod(method int, rw http.ResponseWriter, r *http.Request, window Window, params QueryParams) { + switch method { + case httpFetch: + m.httpFetch(rw, r, window) + default: + m.httpError(rw, "Unknown HTTP method: %d", method) + } +} + +func (m *MessageProcessor) httpFetch(rw http.ResponseWriter, r *http.Request, window Window) { + // Read the request body + body, err := io.ReadAll(r.Body) + if err != nil { + m.httpError(rw, "Failed to read request body: %v", err) + return + } + + // Parse the HTTP request + request, err := parseHTTPRequest(string(body)) + if err != nil { + m.httpError(rw, "Invalid HTTP request: %v", err) + return + } + + // Perform the HTTP request + response := PerformHTTPRequest(*request) + + // Marshal response to JSON + responseJSON, err := json.Marshal(response) + if err != nil { + m.httpError(rw, "Failed to marshal response: %v", err) + return + } + + // Send response + rw.Header().Set("Content-Type", "application/json") + rw.Write(responseJSON) +} \ No newline at end of file diff --git a/v3/pkg/application/messageprocessor_params.go b/v3/pkg/application/messageprocessor_params.go new file mode 100644 index 000000000..b3030da43 --- /dev/null +++ b/v3/pkg/application/messageprocessor_params.go @@ -0,0 +1,201 @@ +package application + +import ( + "encoding/json" + "fmt" + "strconv" +) + +type QueryParams map[string][]string + +func (qp QueryParams) String(key string) *string { + if qp == nil { + return nil + } + values := qp[key] + if len(values) == 0 { + return nil + } + return &values[0] +} + +func (qp QueryParams) Int(key string) *int { + val := qp.String(key) + if val == nil { + return nil + } + result, err := strconv.Atoi(*val) + if err != nil { + return nil + } + return &result +} + +func (qp QueryParams) UInt8(key string) *uint8 { + val := qp.String(key) + if val == nil { + return nil + } + intResult, err := strconv.Atoi(*val) + if err != nil { + return nil + } + + if intResult < 0 { + intResult = 0 + } + if intResult > 255 { + intResult = 255 + } + + var result = uint8(intResult) + + return &result +} +func (qp QueryParams) UInt(key string) *uint { + val := qp.String(key) + if val == nil { + return nil + } + intResult, err := strconv.Atoi(*val) + if err != nil { + return nil + } + + if intResult < 0 { + intResult = 0 + } + if intResult > 255 { + intResult = 255 + } + + var result = uint(intResult) + + return &result +} + +func (qp QueryParams) Bool(key string) *bool { + val := qp.String(key) + if val == nil { + return nil + } + result, err := strconv.ParseBool(*val) + if err != nil { + return nil + } + return &result +} + +func (qp QueryParams) Float64(key string) *float64 { + val := qp.String(key) + if val == nil { + return nil + } + result, err := strconv.ParseFloat(*val, 64) + if err != nil { + return nil + } + return &result +} + +func (qp QueryParams) ToStruct(str any) error { + args := qp["args"] + if len(args) == 1 { + return json.Unmarshal([]byte(args[0]), &str) + } + return nil +} + +type Args struct { + data map[string]any +} + +func (a *Args) String(key string) *string { + if a == nil { + return nil + } + if val := a.data[key]; val != nil { + result := fmt.Sprintf("%v", val) + return &result + } + return nil +} + +func (a *Args) Int(s string) *int { + if a == nil { + return nil + } + if val := a.data[s]; val != nil { + return convertNumber[int](val) + } + return nil +} + +func convertNumber[T int | int8 | int16 | int32 | int64 | uint | uint8 | uint16 | uint32 | uint64](val any) *T { + if val == nil { + return nil + } + var result T + switch v := val.(type) { + case T: + result = v + case float64: + result = T(v) + } + return &result +} + +func (a *Args) UInt8(s string) *uint8 { + if a == nil { + return nil + } + if val := a.data[s]; val != nil { + return convertNumber[uint8](val) + } + return nil +} +func (a *Args) UInt(s string) *uint { + if a == nil { + return nil + } + if val := a.data[s]; val != nil { + return convertNumber[uint](val) + } + return nil +} + +func (a *Args) Float64(s string) *float64 { + if a == nil { + return nil + } + if val := a.data[s]; val != nil { + result := val.(float64) + return &result + } + return nil +} + +func (a *Args) Bool(s string) *bool { + if a == nil { + return nil + } + if val := a.data[s]; val != nil { + result := val.(bool) + return &result + } + return nil +} + +func (qp QueryParams) Args() (*Args, error) { + argData := qp["args"] + var result = &Args{ + data: make(map[string]any), + } + if len(argData) == 1 { + err := json.Unmarshal([]byte(argData[0]), &result.data) + if err != nil { + return nil, err + } + } + return result, nil +} diff --git a/v3/pkg/application/messageprocessor_screens.go b/v3/pkg/application/messageprocessor_screens.go new file mode 100644 index 000000000..4d379a5cb --- /dev/null +++ b/v3/pkg/application/messageprocessor_screens.go @@ -0,0 +1,49 @@ +package application + +import ( + "net/http" +) + +const ( + ScreensGetAll = 0 + ScreensGetPrimary = 1 + ScreensGetCurrent = 2 +) + +var screensMethodNames = map[int]string{ + ScreensGetAll: "GetAll", + ScreensGetPrimary: "GetPrimary", + ScreensGetCurrent: "GetCurrent", +} + +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()) + return + } + m.json(rw, screens) + case ScreensGetPrimary: + screen, err := globalApplication.GetPrimaryScreen() + if err != nil { + m.Error("GetPrimary: %s", err.Error()) + return + } + m.json(rw, screen) + case ScreensGetCurrent: + screen, err := globalApplication.CurrentWindow().GetScreen() + if err != nil { + m.Error("GetCurrent: %s", err.Error()) + return + } + m.json(rw, screen) + default: + m.httpError(rw, "Unknown screens method: %d", method) + } + + m.Info("Runtime:", "method", "Screens."+screensMethodNames[method]) + +} diff --git a/v3/pkg/application/messageprocessor_system.go b/v3/pkg/application/messageprocessor_system.go new file mode 100644 index 000000000..fcd44a1a4 --- /dev/null +++ b/v3/pkg/application/messageprocessor_system.go @@ -0,0 +1,26 @@ +package application + +import ( + "net/http" +) + +const ( + SystemIsDarkMode = 0 +) + +var systemMethodNames = map[int]string{ + SystemIsDarkMode: "IsDarkMode", +} + +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()) + default: + m.httpError(rw, "Unknown system method: %d", method) + } + + m.Info("Runtime:", "method", "System."+systemMethodNames[method]) + +} diff --git a/v3/pkg/application/messageprocessor_window.go b/v3/pkg/application/messageprocessor_window.go new file mode 100644 index 000000000..d5463c35f --- /dev/null +++ b/v3/pkg/application/messageprocessor_window.go @@ -0,0 +1,259 @@ +package application + +import ( + "net/http" +) + +const ( + WindowCenter = 0 + WindowSetTitle = 1 + WindowFullscreen = 2 + WindowUnFullscreen = 3 + WindowSetSize = 4 + WindowSize = 5 + WindowSetMaxSize = 6 + WindowSetMinSize = 7 + WindowSetAlwaysOnTop = 8 + WindowSetRelativePosition = 9 + WindowRelativePosition = 10 + WindowScreen = 11 + WindowHide = 12 + WindowMaximise = 13 + WindowUnMaximise = 14 + WindowToggleMaximise = 15 + WindowMinimise = 16 + WindowUnMinimise = 17 + WindowRestore = 18 + WindowShow = 19 + WindowClose = 20 + WindowSetBackgroundColour = 21 + WindowSetResizable = 22 + WindowWidth = 23 + WindowHeight = 24 + WindowZoomIn = 25 + WindowZoomOut = 26 + WindowZoomReset = 27 + WindowGetZoomLevel = 28 + WindowSetZoomLevel = 29 +) + +var windowMethodNames = map[int]string{ + WindowCenter: "Center", + WindowSetTitle: "SetTitle", + WindowFullscreen: "Fullscreen", + WindowUnFullscreen: "UnFullscreen", + WindowSetSize: "SetSize", + WindowSize: "Size", + WindowSetMaxSize: "SetMaxSize", + WindowSetMinSize: "SetMinSize", + WindowSetAlwaysOnTop: "SetAlwaysOnTop", + WindowSetRelativePosition: "SetRelativePosition", + WindowRelativePosition: "RelativePosition", + WindowScreen: "Screen", + WindowHide: "Hide", + WindowMaximise: "Maximise", + WindowUnMaximise: "UnMaximise", + WindowToggleMaximise: "ToggleMaximise", + WindowMinimise: "Minimise", + WindowUnMinimise: "UnMinimise", + WindowRestore: "Restore", + WindowShow: "Show", + WindowClose: "Close", + WindowSetBackgroundColour: "SetBackgroundColour", + WindowSetResizable: "SetResizable", + WindowWidth: "Width", + WindowHeight: "Height", + WindowZoomIn: "ZoomIn", + WindowZoomOut: "ZoomOut", + WindowZoomReset: "ZoomReset", + WindowGetZoomLevel: "GetZoomLevel", + WindowSetZoomLevel: "SetZoomLevel", +} + +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()) + return + } + + switch method { + case WindowSetTitle: + title := args.String("title") + if title == nil { + m.Error("SetTitle: title is required") + return + } + window.SetTitle(*title) + m.ok(rw) + case WindowSetSize: + width := args.Int("width") + height := args.Int("height") + if width == nil || height == nil { + m.Error("Invalid SetSize Message") + return + } + window.SetSize(*width, *height) + m.ok(rw) + case WindowSetRelativePosition: + x := args.Int("x") + y := args.Int("y") + if x == nil || y == nil { + m.Error("Invalid SetRelativePosition Message") + return + } + window.SetRelativePosition(*x, *y) + m.ok(rw) + case WindowFullscreen: + window.Fullscreen() + m.ok(rw) + case WindowUnFullscreen: + window.UnFullscreen() + m.ok(rw) + case WindowMinimise: + window.Minimise() + m.ok(rw) + case WindowUnMinimise: + window.UnMinimise() + m.ok(rw) + case WindowMaximise: + window.Maximise() + m.ok(rw) + case WindowUnMaximise: + window.UnMaximise() + m.ok(rw) + case WindowRestore: + window.Restore() + m.ok(rw) + case WindowShow: + window.Show() + m.ok(rw) + case WindowHide: + window.Hide() + m.ok(rw) + case WindowClose: + window.Close() + m.ok(rw) + case WindowCenter: + window.Center() + m.ok(rw) + case WindowSize: + width, height := window.Size() + m.json(rw, map[string]interface{}{ + "width": width, + "height": height, + }) + case WindowRelativePosition: + x, y := window.RelativePosition() + m.json(rw, map[string]interface{}{ + "x": x, + "y": y, + }) + case WindowSetBackgroundColour: + r := args.UInt8("r") + if r == nil { + m.Error("Invalid SetBackgroundColour Message: 'r' value required") + return + } + g := args.UInt8("g") + if g == nil { + m.Error("Invalid SetBackgroundColour Message: 'g' value required") + return + } + b := args.UInt8("b") + if b == nil { + m.Error("Invalid SetBackgroundColour Message: 'b' value required") + return + } + a := args.UInt8("a") + if a == nil { + m.Error("Invalid SetBackgroundColour Message: 'a' value required") + return + } + window.SetBackgroundColour(RGBA{ + Red: *r, + Green: *g, + Blue: *b, + Alpha: *a, + }) + m.ok(rw) + case WindowSetAlwaysOnTop: + alwaysOnTop := args.Bool("alwaysOnTop") + if alwaysOnTop == nil { + m.Error("Invalid SetAlwaysOnTop Message: 'alwaysOnTop' value required") + return + } + window.SetAlwaysOnTop(*alwaysOnTop) + m.ok(rw) + case WindowSetResizable: + resizable := args.Bool("resizable") + if resizable == nil { + m.Error("Invalid SetResizable Message: 'resizable' value required") + return + } + window.SetResizable(*resizable) + m.ok(rw) + case WindowSetMinSize: + width := args.Int("width") + height := args.Int("height") + if width == nil || height == nil { + m.Error("Invalid SetMinSize Message") + return + } + window.SetMinSize(*width, *height) + m.ok(rw) + case WindowSetMaxSize: + width := args.Int("width") + height := args.Int("height") + if width == nil || height == nil { + m.Error("Invalid SetMaxSize Message") + return + } + window.SetMaxSize(*width, *height) + m.ok(rw) + case WindowWidth: + width := window.Width() + m.json(rw, map[string]interface{}{ + "width": width, + }) + case WindowHeight: + height := window.Height() + m.json(rw, map[string]interface{}{ + "height": height, + }) + case WindowZoomIn: + window.ZoomIn() + m.ok(rw) + case WindowZoomOut: + window.ZoomOut() + m.ok(rw) + case WindowZoomReset: + window.ZoomReset() + m.ok(rw) + case WindowGetZoomLevel: + zoomLevel := window.GetZoom() + m.json(rw, map[string]interface{}{ + "zoomLevel": zoomLevel, + }) + case WindowScreen: + screen, err := window.GetScreen() + if err != nil { + m.httpError(rw, err.Error()) + return + } + m.json(rw, screen) + case WindowSetZoomLevel: + zoomLevel := args.Float64("zoomLevel") + if zoomLevel == nil { + m.Error("Invalid SetZoom Message: invalid 'zoomLevel' value") + return + } + window.SetZoom(*zoomLevel) + m.ok(rw) + default: + m.httpError(rw, "Unknown window method id: %d", method) + } + + m.Info("Runtime:", "method", "Window."+windowMethodNames[method]) +} diff --git a/v3/pkg/application/options_application.go b/v3/pkg/application/options_application.go new file mode 100644 index 000000000..ae69f39df --- /dev/null +++ b/v3/pkg/application/options_application.go @@ -0,0 +1,102 @@ +package application + +import ( + "io/fs" + "log/slog" + "net/http" +) + +type Options struct { + // Name is the name of the application + Name string + + // Description is the description of the application (used in the default about box) + Description string + + // Icon is the icon of the application (used in the default about box) + Icon []byte + + // Mac is the Mac specific configuration for Mac builds + Mac MacOptions + + // Windows is the Windows specific configuration for Windows builds + Windows WindowsOptions + + // Bind allows you to bind Go methods to the frontend. + Bind []any + + // 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). + // If not defined, a default logger is used. + Logger *slog.Logger + + // LogLevel defines the log level of the Wails system logger. + LogLevel slog.Level + + // Assets are the application assets to be used. + Assets AssetOptions + + // Plugins is a map of plugins used by the application + Plugins map[string]Plugin + + // Flags are key value pairs that are available to the frontend. + // This is also used by Wails to provide information to the frontend. + Flags map[string]any + + // PanicHandler is a way to register a custom panic handler + PanicHandler func(any) + + // KeyBindings is a map of key bindings to functions + KeyBindings map[string]func(window *WebviewWindow) +} + +// AssetOptions defines the configuration of the AssetServer. +type AssetOptions struct { + // FS defines the static assets to be used. A GET request is first tried to be served from this FS. If the FS returns + // `os.ErrNotExist` for that file, the request handling will fallback to the Handler and tries to serve the GET + // request from it. + // + // If set to nil, all GET requests will be forwarded to Handler. + FS fs.FS + + // Handler will be called for every GET request that can't be served from FS, due to `os.ErrNotExist`. Furthermore all + // non GET requests will always be served from this Handler. + // + // If not defined, the result is the following in cases where the Handler would have been called: + // GET request: `http.StatusNotFound` + // Other request: `http.StatusMethodNotAllowed` + Handler http.Handler + + // Middleware is HTTP Middleware which allows to hook into the AssetServer request chain. It allows to skip the default + // request handler dynamically, e.g. implement specialized Routing etc. + // The Middleware is called to build a new `http.Handler` used by the AssetSever and it also receives the default + // handler used by the AssetServer as an argument. + // + // If not defined, the default AssetServer request chain is executed. + // + // Multiple Middlewares can be chained together with: + // ChainMiddleware(middleware ...Middleware) Middleware + Middleware Middleware + + // External URL can be set to a development server URL so that all requests are forwarded to it. This is useful + // when using a development server like `vite` or `snowpack` which serves the assets on a different port. + ExternalURL string +} + +// Middleware defines HTTP middleware that can be applied to the AssetServer. +// The handler passed as next is the next handler in the chain. One can decide to call the next handler +// or implement a specialized handling. +type Middleware func(next http.Handler) http.Handler + +// ChainMiddleware allows chaining multiple middlewares to one middleware. +func ChainMiddleware(middleware ...Middleware) Middleware { + return func(h http.Handler) http.Handler { + for i := len(middleware) - 1; i >= 0; i-- { + h = middleware[i](h) + } + return h + } +} diff --git a/v3/pkg/application/options_application_mac.go b/v3/pkg/application/options_application_mac.go new file mode 100644 index 000000000..ddfbc80e6 --- /dev/null +++ b/v3/pkg/application/options_application_mac.go @@ -0,0 +1,22 @@ +package application + +// ActivationPolicy is the activation policy for the application. +type ActivationPolicy int + +const ( + // ActivationPolicyRegular is used for applications that have a user interface, + ActivationPolicyRegular ActivationPolicy = iota + // ActivationPolicyAccessory is used for applications that do not have a main window, + // such as system tray applications or background applications. + ActivationPolicyAccessory + ActivationPolicyProhibited +) + +// MacOptions contains options for macOS applications. +type MacOptions struct { + // ActivationPolicy is the activation policy for the application. Defaults to + // applicationActivationPolicyRegular. + ActivationPolicy ActivationPolicy + // If set to true, the application will terminate when the last window is closed. + ApplicationShouldTerminateAfterLastWindowClosed bool +} diff --git a/v3/pkg/application/options_application_win.go b/v3/pkg/application/options_application_win.go new file mode 100644 index 000000000..b5a7afdb7 --- /dev/null +++ b/v3/pkg/application/options_application_win.go @@ -0,0 +1,21 @@ +package application + +// WindowsOptions contains options for Windows applications. +type WindowsOptions struct { + + // WndProcInterceptor is a function that will be called for every message sent in the application. + // Use this to hook into the main message loop. This is useful for handling custom window messages. + // If `shouldReturn` is `true` then `returnCode` will be returned by the main message loop. + // If `shouldReturn` is `false` then returnCode will be ignored and the message will be processed by the main message loop. + WndProcInterceptor func(hwnd uintptr, msg uint32, wParam, lParam uintptr) (returnCode uintptr, shouldReturn bool) + + // DisableQuitOnLastWindowClosed disables the auto quit of the application if the last window has been closed. + DisableQuitOnLastWindowClosed bool + + // Path where the WebView2 stores the user data. If empty %APPDATA%\[BinaryName.exe] will be used. + // If the path is not valid, a messagebox will be displayed with the error and the app will exit with error code. + WebviewUserDataPath string + + // Path to the directory with WebView2 executables. If empty WebView2 installed in the system will be used. + WebviewBrowserPath string +} diff --git a/v3/pkg/application/options_linux.go b/v3/pkg/application/options_linux.go new file mode 100644 index 000000000..bf3d0c308 --- /dev/null +++ b/v3/pkg/application/options_linux.go @@ -0,0 +1,6 @@ +package application + +// LinuxWindow contains macOS specific options +type LinuxWindow struct { + ShowApplicationMenu bool +} diff --git a/v3/pkg/application/options_mac.go b/v3/pkg/application/options_mac.go new file mode 100644 index 000000000..c79287fc6 --- /dev/null +++ b/v3/pkg/application/options_mac.go @@ -0,0 +1,152 @@ +package application + +import "github.com/wailsapp/wails/v3/pkg/events" +import "github.com/leaanthony/u" + +// MacBackdrop is the backdrop type for macOS +type MacBackdrop int + +const ( + // MacBackdropNormal - The default value. The window will have a normal opaque background. + MacBackdropNormal MacBackdrop = iota + // MacBackdropTransparent - The window will have a transparent background, with the content underneath it being visible + MacBackdropTransparent + // MacBackdropTranslucent - The window will have a translucent background, with the content underneath it being "fuzzy" or "frosted" + MacBackdropTranslucent +) + +// MacToolbarStyle is the style of toolbar for macOS +type MacToolbarStyle int + +const ( + // MacToolbarStyleAutomatic - The default value. The style will be determined by the window's given configuration + MacToolbarStyleAutomatic MacToolbarStyle = iota + // MacToolbarStyleExpanded - The toolbar will appear below the window title + MacToolbarStyleExpanded + // MacToolbarStylePreference - The toolbar will appear below the window title and the items in the toolbar will attempt to have equal widths when possible + MacToolbarStylePreference + // MacToolbarStyleUnified - The window title will appear inline with the toolbar when visible + MacToolbarStyleUnified + // MacToolbarStyleUnifiedCompact - Same as MacToolbarStyleUnified, but with reduced margins in the toolbar allowing more focus to be on the contents of the window + MacToolbarStyleUnifiedCompact +) + +// MacWindow contains macOS specific options for Webview Windows +type MacWindow struct { + // Backdrop is the backdrop type for the window + Backdrop MacBackdrop + // DisableShadow will disable the window shadow + DisableShadow bool + // TitleBar contains options for the Mac titlebar + TitleBar MacTitleBar + // Appearance is the appearance type for the window + Appearance MacAppearanceType + // InvisibleTitleBarHeight defines the height of an invisible titlebar which responds to dragging + InvisibleTitleBarHeight int + // Maps events from platform specific to common event types + EventMapping map[events.WindowEventType]events.WindowEventType + + // EnableFraudulentWebsiteWarnings will enable warnings for fraudulent websites. + // Default: false + EnableFraudulentWebsiteWarnings bool + + // WebviewPreferences contains preferences for the webview + WebviewPreferences MacWebviewPreferences +} + +// MacWebviewPreferences contains preferences for the Mac webview +type MacWebviewPreferences struct { + // TabFocusesLinks will enable tabbing to links + TabFocusesLinks u.Bool + // TextInteractionEnabled will enable text interaction + TextInteractionEnabled u.Bool + // FullscreenEnabled will enable fullscreen + FullscreenEnabled u.Bool +} + +// MacTitleBar contains options for the Mac titlebar +type MacTitleBar struct { + // AppearsTransparent will make the titlebar transparent + AppearsTransparent bool + // Hide will hide the titlebar + Hide bool + // HideTitle will hide the title + HideTitle bool + // FullSizeContent will extend the window content to the full size of the window + FullSizeContent bool + // UseToolbar will use a toolbar instead of a titlebar + UseToolbar bool + // HideToolbarSeparator will hide the toolbar separator + HideToolbarSeparator bool + // ToolbarStyle is the style of toolbar to use + ToolbarStyle MacToolbarStyle +} + +// MacTitleBarDefault results in the default Mac MacTitleBar +var MacTitleBarDefault = MacTitleBar{ + AppearsTransparent: false, + Hide: false, + HideTitle: false, + FullSizeContent: false, + UseToolbar: false, + HideToolbarSeparator: false, +} + +// Credit: Comments from Electron site + +// MacTitleBarHidden results in a hidden title bar and a full size content window, +// yet the title bar still has the standard window controls (“traffic lights”) +// in the top left. +var MacTitleBarHidden = MacTitleBar{ + AppearsTransparent: true, + Hide: false, + HideTitle: true, + FullSizeContent: true, + UseToolbar: false, + HideToolbarSeparator: false, +} + +// MacTitleBarHiddenInset results in a hidden title bar with an alternative look where +// the traffic light buttons are slightly more inset from the window edge. +var MacTitleBarHiddenInset = MacTitleBar{ + AppearsTransparent: true, + Hide: false, + HideTitle: true, + FullSizeContent: true, + UseToolbar: true, + HideToolbarSeparator: true, +} + +// MacTitleBarHiddenInsetUnified results in a hidden title bar with an alternative look where +// the traffic light buttons are even more inset from the window edge. +var MacTitleBarHiddenInsetUnified = MacTitleBar{ + AppearsTransparent: true, + Hide: false, + HideTitle: true, + FullSizeContent: true, + UseToolbar: true, + HideToolbarSeparator: true, + ToolbarStyle: MacToolbarStyleUnified, +} + +// MacAppearanceType is a type of Appearance for Cocoa windows +type MacAppearanceType string + +const ( + // DefaultAppearance uses the default system value + DefaultAppearance MacAppearanceType = "" + // NSAppearanceNameAqua - The standard light system appearance. + NSAppearanceNameAqua MacAppearanceType = "NSAppearanceNameAqua" + // NSAppearanceNameDarkAqua - The standard dark system appearance. + NSAppearanceNameDarkAqua MacAppearanceType = "NSAppearanceNameDarkAqua" + // NSAppearanceNameVibrantLight - The light vibrant appearance + NSAppearanceNameVibrantLight MacAppearanceType = "NSAppearanceNameVibrantLight" + // NSAppearanceNameAccessibilityHighContrastAqua - A high-contrast version of the standard light system appearance. + NSAppearanceNameAccessibilityHighContrastAqua MacAppearanceType = "NSAppearanceNameAccessibilityHighContrastAqua" + // NSAppearanceNameAccessibilityHighContrastDarkAqua - A high-contrast version of the standard dark system appearance. + NSAppearanceNameAccessibilityHighContrastDarkAqua MacAppearanceType = "NSAppearanceNameAccessibilityHighContrastDarkAqua" + // NSAppearanceNameAccessibilityHighContrastVibrantLight - A high-contrast version of the light vibrant appearance. + NSAppearanceNameAccessibilityHighContrastVibrantLight MacAppearanceType = "NSAppearanceNameAccessibilityHighContrastVibrantLight" + // NSAppearanceNameAccessibilityHighContrastVibrantDark - A high-contrast version of the dark vibrant appearance. + NSAppearanceNameAccessibilityHighContrastVibrantDark MacAppearanceType = "NSAppearanceNameAccessibilityHighContrastVibrantDark" +) diff --git a/v3/pkg/application/options_webview_window.go b/v3/pkg/application/options_webview_window.go new file mode 100644 index 000000000..daabe69f8 --- /dev/null +++ b/v3/pkg/application/options_webview_window.go @@ -0,0 +1,164 @@ +package application + +type WindowState int + +const ( + WindowStateNormal WindowState = iota + WindowStateMinimised + WindowStateMaximised + WindowStateFullscreen +) + +type WebviewWindowOptions struct { + // Name is a unique identifier that can be given to a window. + Name string + + // Title is the title of the window. + Title string + + // Width is the starting width of the window. + Width int + + // Height is the starting height of the window. + Height int + + // AlwaysOnTop will make the window float above other windows. + AlwaysOnTop bool + + // URL is the URL to load in the window. + URL string + + // DisableResize will disable the ability to resize the window. + DisableResize bool + + // Frameless will remove the window frame. + Frameless bool + + // MinWidth is the minimum width of the window. + MinWidth int + + // MinHeight is the minimum height of the window. + MinHeight int + + // MaxWidth is the maximum width of the window. + MaxWidth int + + // MaxHeight is the maximum height of the window. + MaxHeight int + + // StartState indicates the state of the window when it is first shown. + // Default: WindowStateNormal + StartState WindowState + + // Centered will center the window on the screen. + Centered bool + + // BackgroundType is the type of background to use for the window. + // Default: BackgroundTypeSolid + BackgroundType BackgroundType + + // BackgroundColour is the colour to use for the window background. + BackgroundColour RGBA + + // HTML is the HTML to load in the window. + HTML string + + // JS is the JavaScript to load in the window. + JS string + + // CSS is the CSS to load in the window. + CSS string + + // X is the starting X position of the window. + X int + + // Y is the starting Y position of the window. + Y int + + // TransparentTitlebar will make the titlebar transparent. + // TODO: Move to mac window options + FullscreenButtonEnabled bool + + // Hidden will hide the window when it is first created. + Hidden bool + + // Zoom is the zoom level of the window. + Zoom float64 + + // ZoomControlEnabled will enable the zoom control. + ZoomControlEnabled bool + + // EnableDragAndDrop will enable drag and drop. + EnableDragAndDrop bool + + // OpenInspectorOnStartup will open the inspector when the window is first shown. + OpenInspectorOnStartup bool + + // Mac options + Mac MacWindow + + // Windows options + Windows WindowsWindow + + // Focused indicates the window should be focused when initially shown + Focused bool + + // ShouldClose is called when the window is about to close. + // Return true to allow the window to close, or false to prevent it from closing. + ShouldClose func(window *WebviewWindow) bool + + // If true, the window's devtools will be available (default true in builds without the `production` build tag) + DevToolsEnabled bool + + // If true, the window's default context menu will be disabled (default false) + DefaultContextMenuDisabled bool + + // KeyBindings is a map of key bindings to functions + KeyBindings map[string]func(window *WebviewWindow) + + // IgnoreMouseEvents will ignore mouse events in the window + IgnoreMouseEvents bool +} + +var WebviewWindowDefaults = &WebviewWindowOptions{ + Title: "", + Width: 800, + Height: 600, + URL: "", + BackgroundColour: RGBA{ + Red: 255, + Green: 255, + Blue: 255, + Alpha: 255, + }, +} + +type RGBA struct { + Red, Green, Blue, Alpha uint8 +} + +func NewRGBA(red, green, blue, alpha uint8) RGBA { + return RGBA{ + Red: red, + Green: green, + Blue: blue, + Alpha: alpha, + } +} + +func NewRGB(red, green, blue uint8) RGBA { + return RGBA{ + Red: red, + Green: green, + Blue: blue, + Alpha: 255, + } +} + +type BackgroundType int + +const ( + BackgroundTypeSolid BackgroundType = iota + BackgroundTypeTransparent + BackgroundTypeTranslucent +) diff --git a/v3/pkg/application/options_webview_window_win.go b/v3/pkg/application/options_webview_window_win.go new file mode 100644 index 000000000..1533aa234 --- /dev/null +++ b/v3/pkg/application/options_webview_window_win.go @@ -0,0 +1,148 @@ +package application + +import ( + "github.com/wailsapp/wails/v3/pkg/events" +) + +type BackdropType int32 +type DragEffect int32 + +const ( + // DragEffectNone is used to indicate that the drop target cannot accept the data. + DragEffectNone DragEffect = 1 + // DragEffectCopy is used to indicate that the data is copied to the drop target. + DragEffectCopy DragEffect = 2 + // DragEffectMove is used to indicate that the data is removed from the drag source. + DragEffectMove DragEffect = 3 + // DragEffectLink is used to indicate that a link to the original data is established. + DragEffectLink DragEffect = 4 + // DragEffectScroll is used to indicate that the target can be scrolled while dragging to locate a drop position that is not currently visible in the target. + +) + +const ( + Auto BackdropType = 0 + None BackdropType = 1 + Mica BackdropType = 2 + Acrylic BackdropType = 3 + Tabbed BackdropType = 4 +) + +type CoreWebView2PermissionKind uint32 + +const ( + CoreWebView2PermissionKindUnknownPermission CoreWebView2PermissionKind = iota + CoreWebView2PermissionKindMicrophone + CoreWebView2PermissionKindCamera + CoreWebView2PermissionKindGeolocation + CoreWebView2PermissionKindNotifications + CoreWebView2PermissionKindOtherSensors + CoreWebView2PermissionKindClipboardRead +) + +type CoreWebView2PermissionState uint32 + +const ( + CoreWebView2PermissionStateDefault CoreWebView2PermissionState = iota + CoreWebView2PermissionStateAllow + CoreWebView2PermissionStateDeny +) + +type WindowsWindow struct { + // Select the type of translucent backdrop. Requires Windows 11 22621 or later. + // Only used when window's `BackgroundType` is set to `BackgroundTypeTranslucent`. + // Default: Auto + BackdropType BackdropType + + // Disable the icon in the titlebar + // Default: false + DisableIcon bool + + // Theme (Dark / Light / SystemDefault) + // Default: SystemDefault - The application will follow system theme changes. + Theme Theme + + // Specify custom colours to use for dark/light mode + // Default: nil + CustomTheme *ThemeSettings + + // Disable all window decorations in Frameless mode, which means no "Aero Shadow" and no "Rounded Corner" will be shown. + // "Rounded Corners" are only available on Windows 11. + // Default: false + DisableFramelessWindowDecorations bool + + // WindowMask is used to set the window shape. Use a PNG with an alpha channel to create a custom shape. + // Default: nil + WindowMask []byte + + // WindowMaskDraggable is used to make the window draggable by clicking on the window mask. + // Default: false + WindowMaskDraggable bool + + // WebviewGpuIsDisabled is used to enable / disable GPU acceleration for the webview + // Default: false + WebviewGpuIsDisabled bool + + // ResizeDebounceMS is the amount of time to debounce redraws of webview2 + // when resizing the window + // Default: 0 + ResizeDebounceMS uint16 + + // Disable the menu bar for this window + // Default: false + DisableMenu bool + + // Event mapping for the window. This allows you to define a translation from one event to another. + // Default: nil + EventMapping map[events.WindowEventType]events.WindowEventType + + // HiddenOnTaskbar hides the window from the taskbar + // Default: false + HiddenOnTaskbar bool + + // EnableSwipeGestures enables swipe gestures for the window + // Default: false + EnableSwipeGestures bool + + // EnableFraudulentWebsiteWarnings will enable warnings for fraudulent websites. + // Default: false + EnableFraudulentWebsiteWarnings bool + + // Menu is the menu to use for the window. + Menu *Menu + + // Drag Cursor Effects + OnEnterEffect DragEffect + OnOverEffect DragEffect + + // Permissions map for WebView2. If empty, default permissions will be granted. + Permissions map[CoreWebView2PermissionKind]CoreWebView2PermissionState +} + +type Theme int + +const ( + // SystemDefault will use whatever the system theme is. The application will follow system theme changes. + SystemDefault Theme = 0 + // Dark Mode + Dark Theme = 1 + // Light Mode + Light Theme = 2 +) + +// ThemeSettings defines custom colours to use in dark or light mode. +// They may be set using the hex values: 0x00BBGGRR +type ThemeSettings struct { + DarkModeTitleBar int32 + DarkModeTitleBarInactive int32 + DarkModeTitleText int32 + DarkModeTitleTextInactive int32 + DarkModeBorder int32 + DarkModeBorderInactive int32 + LightModeTitleBar int32 + LightModeTitleBarInactive int32 + LightModeTitleText int32 + LightModeTitleTextInactive int32 + LightModeBorder int32 + LightModeBorderInactive int32 +} diff --git a/v3/pkg/application/plugins.go b/v3/pkg/application/plugins.go new file mode 100644 index 000000000..423ce2ac2 --- /dev/null +++ b/v3/pkg/application/plugins.go @@ -0,0 +1,50 @@ +package application + +import "github.com/wailsapp/wails/v3/internal/assetserver" + +type Plugin interface { + Name() string + Init() error + Shutdown() + CallableByJS() []string + InjectJS() string +} + +type PluginManager struct { + plugins map[string]Plugin + assetServer *assetserver.AssetServer + initialisedPlugins []Plugin +} + +func NewPluginManager(plugins map[string]Plugin, assetServer *assetserver.AssetServer) *PluginManager { + result := &PluginManager{ + plugins: plugins, + assetServer: assetServer, + } + return result +} + +func (p *PluginManager) Init() error { + for _, plugin := range p.plugins { + err := plugin.Init() + if err != nil { + globalApplication.error("Plugin failed to initialise:", "plugin", plugin.Name(), "error", err.Error()) + p.Shutdown() + return err + } + p.initialisedPlugins = append(p.initialisedPlugins, plugin) + injectJS := plugin.InjectJS() + if injectJS != "" { + p.assetServer.AddPluginScript(plugin.Name(), injectJS) + } + globalApplication.debug("Plugin initialised: " + plugin.Name()) + } + return nil +} + +func (p *PluginManager) Shutdown() { + for _, plugin := range p.initialisedPlugins { + plugin.Shutdown() + globalApplication.debug("Plugin shutdown: " + plugin.Name()) + } +} diff --git a/v3/pkg/application/popupmenu_windows.go b/v3/pkg/application/popupmenu_windows.go new file mode 100644 index 000000000..0f5aaa6b3 --- /dev/null +++ b/v3/pkg/application/popupmenu_windows.go @@ -0,0 +1,249 @@ +package application + +import ( + "fmt" + "github.com/wailsapp/wails/v3/pkg/w32" +) + +const ( + MenuItemMsgID = w32.WM_APP + 1024 +) + +type RadioGroupMember struct { + ID int + MenuItem *MenuItem +} + +type RadioGroup []*RadioGroupMember + +func (r *RadioGroup) Add(id int, item *MenuItem) { + *r = append(*r, &RadioGroupMember{ + ID: id, + MenuItem: item, + }) +} + +func (r *RadioGroup) Bounds() (int, int) { + p := *r + return p[0].ID, p[len(p)-1].ID +} + +func (r *RadioGroup) MenuID(item *MenuItem) int { + for _, member := range *r { + if member.MenuItem == item { + return member.ID + } + } + panic("RadioGroup.MenuID: item not found:") +} + +type Win32Menu struct { + isPopup bool + menu w32.HMENU + parent w32.HWND + menuMapping map[int]*MenuItem + checkboxItems map[*MenuItem][]int + radioGroups map[*MenuItem][]*RadioGroup + menuData *Menu + currentMenuID int + onMenuClose func() + onMenuOpen func() +} + +func (p *Win32Menu) newMenu() w32.HMENU { + if p.isPopup { + return w32.NewPopupMenu() + } + return w32.CreateMenu() +} + +func (p *Win32Menu) buildMenu(parentMenu w32.HMENU, inputMenu *Menu) { + var currentRadioGroup RadioGroup + for _, item := range inputMenu.items { + if item.Hidden() { + continue + } + p.currentMenuID++ + itemID := p.currentMenuID + p.menuMapping[itemID] = item + + menuItemImpl := newMenuItemImpl(item, parentMenu, itemID) + menuItemImpl.parent = inputMenu + + flags := uint32(w32.MF_STRING) + if item.disabled { + flags = flags | w32.MF_GRAYED + } + if item.checked && item.IsCheckbox() { + flags = flags | w32.MF_CHECKED + } + if item.IsSeparator() { + flags = flags | w32.MF_SEPARATOR + } + + if item.IsCheckbox() { + p.checkboxItems[item] = append(p.checkboxItems[item], itemID) + } + if item.IsRadio() { + currentRadioGroup.Add(itemID, item) + } else { + if len(currentRadioGroup) > 0 { + for _, radioMember := range currentRadioGroup { + currentRadioGroup := currentRadioGroup + p.radioGroups[radioMember.MenuItem] = append(p.radioGroups[radioMember.MenuItem], ¤tRadioGroup) + } + currentRadioGroup = RadioGroup{} + } + } + + if item.submenu != nil { + flags = flags | w32.MF_POPUP + newSubmenu := p.newMenu() + p.buildMenu(newSubmenu, item.submenu) + itemID = int(newSubmenu) + menuItemImpl.submenu = newSubmenu + } + + var menuText = item.Label() + ok := w32.AppendMenu(parentMenu, flags, uintptr(itemID), w32.MustStringToUTF16Ptr(menuText)) + if !ok { + w32.Fatal(fmt.Sprintf("Error adding menu item: %s", menuText)) + } + if item.bitmap != nil { + err := w32.SetMenuIcons(parentMenu, itemID, item.bitmap, nil) + if err != nil { + w32.Fatal(fmt.Sprintf("Error setting menu icons: %s", err.Error())) + } + } + + item.impl = menuItemImpl + } + if len(currentRadioGroup) > 0 { + for _, radioMember := range currentRadioGroup { + currentRadioGroup := currentRadioGroup + p.radioGroups[radioMember.MenuItem] = append(p.radioGroups[radioMember.MenuItem], ¤tRadioGroup) + } + currentRadioGroup = RadioGroup{} + } +} + +func (p *Win32Menu) Update() { + p.menu = p.newMenu() + p.menuMapping = make(map[int]*MenuItem) + p.currentMenuID = MenuItemMsgID + p.buildMenu(p.menu, p.menuData) + p.updateRadioGroups() +} + +func NewPopupMenu(parent w32.HWND, inputMenu *Menu) *Win32Menu { + result := &Win32Menu{ + isPopup: true, + parent: parent, + menuData: inputMenu, + checkboxItems: make(map[*MenuItem][]int), + radioGroups: make(map[*MenuItem][]*RadioGroup), + } + result.Update() + return result +} +func NewApplicationMenu(parent w32.HWND, inputMenu *Menu) *Win32Menu { + result := &Win32Menu{ + parent: parent, + menuData: inputMenu, + checkboxItems: make(map[*MenuItem][]int), + radioGroups: make(map[*MenuItem][]*RadioGroup), + } + result.Update() + return result +} + +func (p *Win32Menu) ShowAt(x int, y int) { + + w32.SetForegroundWindow(p.parent) + + if p.onMenuOpen != nil { + p.onMenuOpen() + } + + if !w32.TrackPopupMenuEx(p.menu, w32.TPM_LEFTALIGN, int32(x), int32(y-5), p.parent, nil) { + w32.Fatal("TrackPopupMenu failed") + } + + if p.onMenuClose != nil { + p.onMenuClose() + } + + if !w32.PostMessage(p.parent, w32.WM_NULL, 0, 0) { + w32.Fatal("PostMessage failed") + } + +} + +func (p *Win32Menu) ShowAtCursor() { + x, y, ok := w32.GetCursorPos() + if ok == false { + w32.Fatal("GetCursorPos failed") + } + + p.ShowAt(x, y) +} + +func (p *Win32Menu) ProcessCommand(cmdMsgID int) bool { + item := p.menuMapping[cmdMsgID] + if item == nil { + return false + } + if item.IsRadio() { + item.checked = true + p.updateRadioGroup(item) + } + if item.callback != nil { + item.handleClick() + } + return true +} + +func (p *Win32Menu) Destroy() { + w32.DestroyMenu(p.menu) +} + +func (p *Win32Menu) UpdateMenuItem(item *MenuItem) { + if item.IsCheckbox() { + for _, itemID := range p.checkboxItems[item] { + var checkState uint = w32.MF_UNCHECKED + if item.checked { + checkState = w32.MF_CHECKED + } + w32.CheckMenuItem(p.menu, uintptr(itemID), checkState) + } + return + } + if item.IsRadio() && item.checked == true { + p.updateRadioGroup(item) + } +} + +func (p *Win32Menu) updateRadioGroups() { + for menuItem := range p.radioGroups { + if menuItem.checked { + p.updateRadioGroup(menuItem) + } + } +} + +func (p *Win32Menu) updateRadioGroup(item *MenuItem) { + for _, radioGroup := range p.radioGroups[item] { + thisMenuID := radioGroup.MenuID(item) + startID, endID := radioGroup.Bounds() + w32.CheckRadio(p.menu, startID, endID, thisMenuID) + + } +} + +func (p *Win32Menu) OnMenuOpen(fn func()) { + p.onMenuOpen = fn +} + +func (p *Win32Menu) OnMenuClose(fn func()) { + p.onMenuClose = fn +} diff --git a/v3/pkg/application/roles.go b/v3/pkg/application/roles.go new file mode 100644 index 000000000..fd8f46ed1 --- /dev/null +++ b/v3/pkg/application/roles.go @@ -0,0 +1,158 @@ +package application + +import "runtime" + +// Heavily inspired by Electron (c) 2013-2020 Github Inc. +// Electron License: https://github.com/electron/electron/blob/master/LICENSE + +// Role is a type to identify menu roles +type Role uint + +// These constants need to be kept in sync with `v2/internal/frontend/desktop/darwin/Role.h` +const ( + NoRole Role = iota + AppMenu Role = iota + EditMenu Role = iota + ViewMenu Role = iota + WindowMenu Role = iota + ServicesMenu Role = iota + HelpMenu Role = iota + + Hide Role = iota + HideOthers Role = iota + UnHide Role = iota + About Role = iota + Undo Role = iota + Redo Role = iota + Cut Role = iota + Copy Role = iota + Paste Role = iota + PasteAndMatchStyle Role = iota + SelectAll Role = iota + Delete Role = iota + SpeechMenu Role = iota + Quit Role = iota + FileMenu Role = iota + Close Role = iota + Reload Role = iota + ForceReload Role = iota + ToggleDevTools Role = iota + ResetZoom Role = iota + ZoomIn Role = iota + ZoomOut Role = iota + ToggleFullscreen Role = iota + + Minimize Role = iota + Zoom Role = iota + FullScreen Role = iota + //Front Role = iota + //WindowRole Role = iota + + //QuitRole Role = + //TogglefullscreenRole Role = "togglefullscreen" + //ViewMenuRole Role = "viewMenu" + //WindowMenuRole Role = "windowMenu" + + //FrontRole Role = "front" + //ZoomRole Role = "zoom" + //WindowSubMenuRole Role = "windowSubMenu" + //HelpSubMenuRole Role = "helpSubMenu" + //SeparatorItemRole Role = "separatorItem" +) + +func newFileMenu() *MenuItem { + fileMenu := NewMenu() + if runtime.GOOS == "darwin" { + fileMenu.AddRole(Close) + } else { + fileMenu.AddRole(Quit) + } + subMenu := newSubMenuItem("File") + subMenu.submenu = fileMenu + return subMenu +} + +func newViewMenu() *MenuItem { + viewMenu := NewMenu() + viewMenu.AddRole(Reload) + viewMenu.AddRole(ForceReload) + viewMenu.AddRole(ToggleDevTools) + viewMenu.AddSeparator() + viewMenu.AddRole(ResetZoom) + viewMenu.AddRole(ZoomIn) + viewMenu.AddRole(ZoomOut) + viewMenu.AddSeparator() + viewMenu.AddRole(ToggleFullscreen) + subMenu := newSubMenuItem("View") + subMenu.submenu = viewMenu + return subMenu +} + +func newAppMenu() *MenuItem { + if runtime.GOOS != "darwin" { + return nil + } + appMenu := NewMenu() + appMenu.AddRole(About) + appMenu.AddSeparator() + appMenu.AddRole(ServicesMenu) + appMenu.AddSeparator() + appMenu.AddRole(Hide) + appMenu.AddRole(HideOthers) + appMenu.AddRole(UnHide) + appMenu.AddSeparator() + appMenu.AddRole(Quit) + subMenu := newSubMenuItem(globalApplication.options.Name) + subMenu.submenu = appMenu + return subMenu +} + +func newEditMenu() *MenuItem { + editMenu := NewMenu() + editMenu.AddRole(Undo) + editMenu.AddRole(Redo) + editMenu.AddSeparator() + editMenu.AddRole(Cut) + editMenu.AddRole(Copy) + editMenu.AddRole(Paste) + if runtime.GOOS == "darwin" { + editMenu.AddRole(PasteAndMatchStyle) + editMenu.AddRole(PasteAndMatchStyle) + editMenu.AddRole(Delete) + editMenu.AddRole(SelectAll) + editMenu.AddSeparator() + editMenu.AddRole(SpeechMenu) + } else { + editMenu.AddRole(Delete) + editMenu.AddSeparator() + editMenu.AddRole(SelectAll) + } + subMenu := newSubMenuItem("Edit") + subMenu.submenu = editMenu + return subMenu +} + +func newWindowMenu() *MenuItem { + menu := NewMenu() + menu.AddRole(Minimize) + menu.AddRole(Zoom) + if runtime.GOOS == "darwin" { + menu.AddSeparator() + menu.AddRole(FullScreen) + } else { + menu.AddRole(Close) + } + subMenu := newSubMenuItem("Window") + subMenu.submenu = menu + return subMenu +} + +func newHelpMenu() *MenuItem { + menu := NewMenu() + menu.Add("Learn More").OnClick(func(ctx *Context) { + globalApplication.CurrentWindow().SetURL("https://wails.io") + }) + subMenu := newSubMenuItem("Help") + subMenu.submenu = menu + return subMenu +} diff --git a/v3/pkg/application/screen.go b/v3/pkg/application/screen.go new file mode 100644 index 000000000..b3712c9df --- /dev/null +++ b/v3/pkg/application/screen.go @@ -0,0 +1,26 @@ +package application + +type Screen struct { + ID string // A unique identifier for the display + Name string // The name of the display + Scale float32 // The scale factor of the display + X int // The x-coordinate of the top-left corner of the rectangle + Y int // The y-coordinate of the top-left corner of the rectangle + Size Size // The size of the display + Bounds Rect // The bounds of the display + WorkArea Rect // The work area of the display + IsPrimary bool // Whether this is the primary display + Rotation float32 // The rotation of the display +} + +type Rect struct { + X int + Y int + Width int + Height int +} + +type Size struct { + Width int + Height int +} diff --git a/v3/pkg/application/screen_darwin.go b/v3/pkg/application/screen_darwin.go new file mode 100644 index 000000000..d35cf4611 --- /dev/null +++ b/v3/pkg/application/screen_darwin.go @@ -0,0 +1,181 @@ +//go:build darwin + +package application + +/* +#cgo CFLAGS: -x objective-c +#cgo LDFLAGS: -framework Foundation -framework Cocoa -framework WebKit -framework AppKit +#import +#import +#import +#import +#include + +typedef struct Screen { + const char* id; + const char* name; + int p_width; + int p_height; + int width; + int height; + int x; + int y; + int w_width; + int w_height; + int w_x; + int w_y; + float scale; + double rotation; + bool isPrimary; +} Screen; + + +int GetNumScreens(){ + return [[NSScreen screens] count]; +} + +Screen processScreen(NSScreen* screen){ + Screen returnScreen; + returnScreen.scale = screen.backingScaleFactor; + + // screen bounds + returnScreen.height = screen.frame.size.height; + returnScreen.width = screen.frame.size.width; + returnScreen.x = screen.frame.origin.x; + returnScreen.y = screen.frame.origin.y; + + // work area + NSRect workArea = [screen visibleFrame]; + returnScreen.w_height = workArea.size.height; + returnScreen.w_width = workArea.size.width; + returnScreen.w_x = workArea.origin.x; + returnScreen.w_y = workArea.origin.y; + + + // adapted from https://stackoverflow.com/a/1237490/4188138 + NSDictionary* screenDictionary = [screen deviceDescription]; + NSNumber* screenID = [screenDictionary objectForKey:@"NSScreenNumber"]; + CGDirectDisplayID displayID = [screenID unsignedIntValue]; + returnScreen.id = [[NSString stringWithFormat:@"%d", displayID] UTF8String]; + + // Get physical monitor size + NSValue *sizeValue = [screenDictionary objectForKey:@"NSDeviceSize"]; + NSSize physicalSize = sizeValue.sizeValue; + returnScreen.p_height = physicalSize.height; + returnScreen.p_width = physicalSize.width; + + // Get the rotation + double rotation = CGDisplayRotation(displayID); + returnScreen.rotation = rotation; + + if( @available(macOS 10.15, *) ){ + returnScreen.name = [screen.localizedName UTF8String]; + } + + return returnScreen; +} + +// Get primary screen +Screen GetPrimaryScreen(){ + // Get primary screen + NSScreen *mainScreen = [NSScreen mainScreen]; + return processScreen(mainScreen); +} + +Screen* getAllScreens() { + NSArray *screens = [NSScreen screens]; + Screen* returnScreens = malloc(sizeof(Screen) * screens.count); + for (int i = 0; i < screens.count; i++) { + NSScreen* screen = [screens objectAtIndex:i]; + returnScreens[i] = processScreen(screen); + } + return returnScreens; +} + +Screen getScreenForWindow(void* window){ + NSScreen* screen = ((NSWindow*)window).screen; + return processScreen(screen); +} + +// Get the screen for the system tray +Screen getScreenForSystemTray(void* nsStatusItem) { + NSStatusItem *statusItem = (NSStatusItem *)nsStatusItem; + NSRect frame = statusItem.button.frame; + NSArray *screens = NSScreen.screens; + NSScreen *associatedScreen = nil; + + for (NSScreen *screen in screens) { + if (NSPointInRect(frame.origin, screen.frame)) { + associatedScreen = screen; + break; + } + } + return processScreen(associatedScreen); +} + +void* getWindowForSystray(void* nsStatusItem) { + NSStatusItem *statusItem = (NSStatusItem *)nsStatusItem; + return statusItem.button.window; +} + + +*/ +import "C" +import "unsafe" + +func cScreenToScreen(screen C.Screen) *Screen { + + return &Screen{ + Size: Size{ + Width: int(screen.p_width), + Height: int(screen.p_height), + }, + Bounds: Rect{ + X: int(screen.x), + Y: int(screen.y), + Height: int(screen.height), + Width: int(screen.width), + }, + WorkArea: Rect{ + X: int(screen.w_x), + Y: int(screen.w_y), + Height: int(screen.w_height), + Width: int(screen.w_width), + }, + Scale: float32(screen.scale), + ID: C.GoString(screen.id), + Name: C.GoString(screen.name), + IsPrimary: bool(screen.isPrimary), + Rotation: float32(screen.rotation), + } +} + +func (m *macosApp) getPrimaryScreen() (*Screen, error) { + cScreen := C.GetPrimaryScreen() + return cScreenToScreen(cScreen), nil +} + +func (m *macosApp) getScreens() ([]*Screen, error) { + cScreens := C.getAllScreens() + defer C.free(unsafe.Pointer(cScreens)) + numScreens := int(C.GetNumScreens()) + displays := make([]*Screen, numScreens) + cScreenHeaders := (*[1 << 30]C.Screen)(unsafe.Pointer(cScreens))[:numScreens:numScreens] + for i := 0; i < numScreens; i++ { + displays[i] = cScreenToScreen(cScreenHeaders[i]) + } + return displays, nil +} + +func getScreenForWindow(window *macosWebviewWindow) (*Screen, error) { + cScreen := C.getScreenForWindow(window.nsWindow) + return cScreenToScreen(cScreen), nil +} + +func getScreenForSystray(systray *macosSystemTray) (*Screen, error) { + // Get the Window for the status item + // https://stackoverflow.com/a/5875019/4188138 + window := C.getWindowForSystray(systray.nsStatusItem) + cScreen := C.getScreenForWindow(window) + return cScreenToScreen(cScreen), nil +} diff --git a/v3/pkg/application/screen_linux.go b/v3/pkg/application/screen_linux.go new file mode 100644 index 000000000..364110454 --- /dev/null +++ b/v3/pkg/application/screen_linux.go @@ -0,0 +1,29 @@ +//go:build linux + +package application + +import ( + "fmt" + "sync" +) + +func (m *linuxApp) getPrimaryScreen() (*Screen, error) { + return nil, fmt.Errorf("not implemented") +} + +func (m *linuxApp) getScreens() ([]*Screen, error) { + var wg sync.WaitGroup + var screens []*Screen + var err error + wg.Add(1) + InvokeSync(func() { + screens, err = getScreens(m.application) + wg.Done() + }) + wg.Wait() + return screens, err +} + +func getScreenForWindow(window *linuxWebviewWindow) (*Screen, error) { + return window.getScreen() +} diff --git a/v3/pkg/application/systemtray.go b/v3/pkg/application/systemtray.go new file mode 100644 index 000000000..052c930f5 --- /dev/null +++ b/v3/pkg/application/systemtray.go @@ -0,0 +1,298 @@ +package application + +import ( + "fmt" + "runtime" + "sync" + "time" + + "github.com/wailsapp/wails/v3/pkg/events" +) + +type IconPosition int + +const ( + NSImageNone = iota + NSImageOnly + NSImageLeft + NSImageRight + NSImageBelow + NSImageAbove + NSImageOverlaps + NSImageLeading + NSImageTrailing +) + +type systemTrayImpl interface { + setLabel(label string) + run() + setIcon(icon []byte) + setMenu(menu *Menu) + setIconPosition(position int) + setTemplateIcon(icon []byte) + destroy() + setDarkModeIcon(icon []byte) + bounds() (*Rect, error) + getScreen() (*Screen, error) + positionWindow(window *WebviewWindow, offset int) error + openMenu() +} + +type PositionOptions struct { + Buffer int +} + +type SystemTray struct { + id uint + label string + icon []byte + darkModeIcon []byte + iconPosition int + + clickHandler func() + rightClickHandler func() + doubleClickHandler func() + rightDoubleClickHandler func() + mouseEnterHandler func() + mouseLeaveHandler func() + onMenuOpen func() + onMenuClose func() + + // Platform specific implementation + impl systemTrayImpl + menu *Menu + isTemplateIcon bool + attachedWindow WindowAttachConfig +} + +func newSystemTray(id uint) *SystemTray { + result := &SystemTray{ + id: id, + label: "", + iconPosition: NSImageLeading, + attachedWindow: WindowAttachConfig{ + Window: nil, + Offset: 0, + Debounce: 200 * time.Millisecond, + }, + } + result.clickHandler = result.defaultClickHandler + return result +} + +func (s *SystemTray) SetLabel(label string) { + if s.impl == nil { + s.label = label + return + } + InvokeSync(func() { + s.impl.setLabel(label) + }) +} + +func (s *SystemTray) Label() string { + return s.label +} + +func (s *SystemTray) Run() { + s.impl = newSystemTrayImpl(s) + + if s.attachedWindow.Window != nil { + // Setup listener + s.attachedWindow.Window.On(events.Common.WindowLostFocus, func(event *WindowEvent) { + s.attachedWindow.Window.Hide() + // Special handler for Windows + if runtime.GOOS == "windows" { + // We don't do this unless the window has already been shown + if s.attachedWindow.hasBeenShown == false { + return + } + s.attachedWindow.justClosed = true + go func() { + time.Sleep(s.attachedWindow.Debounce) + s.attachedWindow.justClosed = false + }() + } + }) + } + + InvokeSync(s.impl.run) +} + +func (s *SystemTray) PositionWindow(window *WebviewWindow, offset int) error { + if s.impl == nil { + return fmt.Errorf("system tray not running") + } + return InvokeSyncWithError(func() error { + return s.impl.positionWindow(window, offset) + }) +} + +func (s *SystemTray) SetIcon(icon []byte) *SystemTray { + if s.impl == nil { + s.icon = icon + } else { + InvokeSync(func() { + s.impl.setIcon(icon) + }) + } + return s +} + +func (s *SystemTray) SetDarkModeIcon(icon []byte) *SystemTray { + if s.impl == nil { + s.darkModeIcon = icon + } else { + InvokeSync(func() { + s.impl.setDarkModeIcon(icon) + }) + } + return s +} + +func (s *SystemTray) SetMenu(menu *Menu) *SystemTray { + if s.impl == nil { + s.menu = menu + } else { + InvokeSync(func() { + s.impl.setMenu(menu) + }) + } + return s +} + +func (s *SystemTray) SetIconPosition(iconPosition int) *SystemTray { + if s.impl == nil { + s.iconPosition = iconPosition + } else { + InvokeSync(func() { + s.impl.setIconPosition(iconPosition) + }) + } + return s +} + +func (s *SystemTray) SetTemplateIcon(icon []byte) *SystemTray { + if s.impl == nil { + s.icon = icon + s.isTemplateIcon = true + } else { + InvokeSync(func() { + s.impl.setTemplateIcon(icon) + }) + } + return s +} + +func (s *SystemTray) Destroy() { + if s.impl == nil { + return + } + s.impl.destroy() +} + +func (s *SystemTray) OnClick(handler func()) *SystemTray { + s.clickHandler = handler + return s +} + +func (s *SystemTray) OnRightClick(handler func()) *SystemTray { + s.rightClickHandler = handler + return s +} + +func (s *SystemTray) OnDoubleClick(handler func()) *SystemTray { + s.doubleClickHandler = handler + return s +} + +func (s *SystemTray) OnRightDoubleClick(handler func()) *SystemTray { + s.rightDoubleClickHandler = handler + return s +} + +func (s *SystemTray) OnMouseEnter(handler func()) *SystemTray { + s.mouseEnterHandler = handler + return s +} + +func (s *SystemTray) OnMouseLeave(handler func()) *SystemTray { + s.mouseLeaveHandler = handler + return s +} + +type WindowAttachConfig struct { + // Window is the window to attach to the system tray. If it's null, the request to attach will be ignored. + Window *WebviewWindow + + // Offset indicates the gap in pixels between the system tray and the window + Offset int + + // Debounce is used by Windows to indicate how long to wait before responding to a mouse + // up event on the notification icon. See https://stackoverflow.com/questions/4585283/alternate-showing-hiding-window-when-notify-icon-is-clicked + Debounce time.Duration + + // Indicates that the window has just been closed + justClosed bool + + // Indicates that the window has been shown a first time + hasBeenShown bool + + // Used to ensure that the window state is read on first click + initialClick sync.Once +} + +// AttachWindow attaches a window to the system tray. The window will be shown when the system tray icon is clicked. +// The window will be hidden when the system tray icon is clicked again, or when the window loses focus. +func (s *SystemTray) AttachWindow(window *WebviewWindow) *SystemTray { + s.attachedWindow.Window = window + return s +} + +// WindowOffset sets the gap in pixels between the system tray and the window +func (s *SystemTray) WindowOffset(offset int) *SystemTray { + s.attachedWindow.Offset = offset + return s +} + +// WindowDebounce is used by Windows to indicate how long to wait before responding to a mouse +// up event on the notification icon. This prevents the window from being hidden and then immediately +// shown when the user clicks on the system tray icon. +// See https://stackoverflow.com/questions/4585283/alternate-showing-hiding-window-when-notify-icon-is-clicked +func (s *SystemTray) WindowDebounce(debounce time.Duration) *SystemTray { + s.attachedWindow.Debounce = debounce + return s +} + +func (s *SystemTray) defaultClickHandler() { + if s.attachedWindow.Window == nil { + return + } + + // Check the initial visibility state + s.attachedWindow.initialClick.Do(func() { + s.attachedWindow.hasBeenShown = s.attachedWindow.Window.IsVisible() + }) + + if runtime.GOOS == "windows" && s.attachedWindow.justClosed { + return + } + + if s.attachedWindow.Window.IsVisible() { + s.attachedWindow.Window.Hide() + } else { + s.attachedWindow.hasBeenShown = true + _ = s.PositionWindow(s.attachedWindow.Window, s.attachedWindow.Offset) + s.attachedWindow.Window.Show().Focus() + } +} + +func (s *SystemTray) OpenMenu() { + if s.menu == nil { + return + } + if s.impl == nil { + return + } + InvokeSync(s.impl.openMenu) +} diff --git a/v3/pkg/application/systemtray_darwin.go b/v3/pkg/application/systemtray_darwin.go new file mode 100644 index 000000000..7ed0daaf9 --- /dev/null +++ b/v3/pkg/application/systemtray_darwin.go @@ -0,0 +1,265 @@ +//go:build darwin + +package application + +/* +#cgo CFLAGS: -mmacosx-version-min=10.13 -x objective-c +#cgo LDFLAGS: -framework Cocoa -framework WebKit + +#include "Cocoa/Cocoa.h" +#include "menuitem_darwin.h" +#include "systemtray_darwin.h" +*/ +import "C" +import ( + "unsafe" + + "github.com/leaanthony/go-ansi-parser" +) + +type macosSystemTray struct { + id uint + label string + icon []byte + menu *Menu + + nsStatusItem unsafe.Pointer + nsImage unsafe.Pointer + nsMenu unsafe.Pointer + iconPosition int + isTemplateIcon bool + parent *SystemTray +} + +func (s *macosSystemTray) openMenu() { + if s.nsMenu == nil { + return + } + C.showMenu(s.nsStatusItem, s.nsMenu) +} + +type button int + +const ( + leftButtonDown button = 1 + rightButtonDown button = 3 +) + +// system tray map +var systemTrayMap = make(map[uint]*macosSystemTray) + +//export systrayClickCallback +func systrayClickCallback(id C.long, buttonID C.int) { + // Get the system tray + systemTray := systemTrayMap[uint(id)] + if systemTray == nil { + globalApplication.error("system tray not found", "id", id) + return + } + systemTray.processClick(button(buttonID)) +} + +func (s *macosSystemTray) setIconPosition(position int) { + s.iconPosition = position +} + +func (s *macosSystemTray) setMenu(menu *Menu) { + s.menu = menu +} + +func (s *macosSystemTray) positionWindow(window *WebviewWindow, offset int) error { + + // Get the trayBounds of this system tray + trayBounds, err := s.bounds() + if err != nil { + return err + } + + // Get the current screen trayBounds + currentScreen, err := s.getScreen() + if err != nil { + return err + } + screenBounds := currentScreen.Bounds + + // Get the center height of the window + windowWidthCenter := window.Width() / 2 + + // Get the center height of the system tray + systemTrayWidthCenter := trayBounds.Width / 2 + + // The Y will be 0 and the X will make the center of the window line up with the center of the system tray + windowX := trayBounds.X + systemTrayWidthCenter - windowWidthCenter + + // If the end of the window goes off-screen, move it back enough to be on screen + if windowX+window.Width() > screenBounds.Width { + windowX = screenBounds.Width - window.Width() + } + window.SetRelativePosition(windowX, int(C.statusBarHeight())+offset) + + return nil +} + +func (s *macosSystemTray) getScreen() (*Screen, error) { + return getScreenForSystray(s) +} + +func (s *macosSystemTray) bounds() (*Rect, error) { + var rect C.NSRect + C.systemTrayGetBounds(s.nsStatusItem, &rect) + // Get the screen height for the screen that the systray is on + screen, err := getScreenForSystray(s) + if err != nil { + return nil, err + } + + // Invert Y axis based on screen height + rect.origin.y = C.double(screen.Bounds.Height) - rect.origin.y - rect.size.height + + return &Rect{ + X: int(rect.origin.x), + Y: int(rect.origin.y), + Width: int(rect.size.width), + Height: int(rect.size.height), + }, nil +} + +func (s *macosSystemTray) run() { + globalApplication.dispatchOnMainThread(func() { + if s.nsStatusItem != nil { + Fatal("System tray '%d' already running", s.id) + } + s.nsStatusItem = unsafe.Pointer(C.systemTrayNew(C.long(s.id))) + + if s.label != "" { + s.setLabel(s.label) + } + if s.icon != nil { + s.nsImage = unsafe.Pointer(C.imageFromBytes((*C.uchar)(&s.icon[0]), C.int(len(s.icon)))) + C.systemTraySetIcon(s.nsStatusItem, s.nsImage, C.int(s.iconPosition), C.bool(s.isTemplateIcon)) + } + if s.menu != nil { + s.menu.Update() + // Convert impl to macosMenu object + s.nsMenu = (s.menu.impl).(*macosMenu).nsMenu + // We only set the tray menu if we don't have an attached + // window. If we do, then we manually operate the menu using + // the right mouse button + if s.parent.attachedWindow.Window == nil { + C.systemTraySetMenu(s.nsStatusItem, s.nsMenu) + } + } + + }) +} + +func (s *macosSystemTray) setIcon(icon []byte) { + s.icon = icon + globalApplication.dispatchOnMainThread(func() { + s.nsImage = unsafe.Pointer(C.imageFromBytes((*C.uchar)(&icon[0]), C.int(len(icon)))) + C.systemTraySetIcon(s.nsStatusItem, s.nsImage, C.int(s.iconPosition), C.bool(s.isTemplateIcon)) + }) +} + +func (s *macosSystemTray) setDarkModeIcon(icon []byte) { + s.setIcon(icon) +} + +func (s *macosSystemTray) setTemplateIcon(icon []byte) { + s.icon = icon + s.isTemplateIcon = true + globalApplication.dispatchOnMainThread(func() { + s.nsImage = unsafe.Pointer(C.imageFromBytes((*C.uchar)(&icon[0]), C.int(len(icon)))) + C.systemTraySetIcon(s.nsStatusItem, s.nsImage, C.int(s.iconPosition), C.bool(s.isTemplateIcon)) + }) +} + +func newSystemTrayImpl(s *SystemTray) systemTrayImpl { + result := &macosSystemTray{ + parent: s, + id: s.id, + label: s.label, + icon: s.icon, + menu: s.menu, + iconPosition: s.iconPosition, + isTemplateIcon: s.isTemplateIcon, + } + systemTrayMap[s.id] = result + return result +} + +func extractAnsiTextParts(text *ansi.StyledText) (label *C.char, fg *C.char, bg *C.char) { + label = C.CString(text.Label) + if text.FgCol != nil { + fg = C.CString(text.FgCol.Hex) + } + if text.BgCol != nil { + bg = C.CString(text.BgCol.Hex) + } + return +} + +func (s *macosSystemTray) setLabel(label string) { + s.label = label + if !ansi.HasEscapeCodes(label) { + C.systemTraySetLabel(s.nsStatusItem, C.CString(label)) + } else { + parsed, err := ansi.Parse(label) + if err != nil { + C.systemTraySetLabel(s.nsStatusItem, C.CString(label)) + return + } + if len(parsed) == 0 { + return + } + label, fg, bg := extractAnsiTextParts(parsed[0]) + var attributedString = C.createAttributedString(label, fg, bg) + if len(parsed) > 1 { + for _, parsedPart := range parsed[1:] { + label, fg, bg = extractAnsiTextParts(parsedPart) + attributedString = C.appendAttributedString(attributedString, label, fg, bg) + } + } + + C.systemTraySetANSILabel(s.nsStatusItem, attributedString) + } +} + +func (s *macosSystemTray) destroy() { + // Remove the status item from the status bar and its associated menu + C.systemTrayDestroy(s.nsStatusItem) +} + +func (s *macosSystemTray) processClick(b button) { + switch b { + case leftButtonDown: + // Check if we have a callback + if s.parent.clickHandler != nil { + s.parent.clickHandler() + return + } + if s.parent.attachedWindow.Window != nil { + s.parent.defaultClickHandler() + return + } + if s.menu != nil { + C.showMenu(s.nsStatusItem, s.nsMenu) + } + case rightButtonDown: + // Check if we have a callback + if s.parent.rightClickHandler != nil { + s.parent.rightClickHandler() + return + } + if s.menu != nil { + if s.parent.attachedWindow.Window != nil { + s.parent.attachedWindow.Window.Hide() + } + C.showMenu(s.nsStatusItem, s.nsMenu) + return + } + if s.parent.attachedWindow.Window != nil { + s.parent.defaultClickHandler() + } + } +} diff --git a/v3/pkg/application/systemtray_darwin.h b/v3/pkg/application/systemtray_darwin.h new file mode 100644 index 000000000..50e6cee43 --- /dev/null +++ b/v3/pkg/application/systemtray_darwin.h @@ -0,0 +1,19 @@ +//go:build darwin + +@interface StatusItemController : NSObject +@property long id; +- (void)statusItemClicked:(id)sender; +@end + +void* systemTrayNew(long id); +void systemTraySetLabel(void* nsStatusItem, char *label); +void systemTraySetANSILabel(void* nsStatusItem, void* attributedString); +void* createAttributedString(char *title, char *FG, char *BG); +void* appendAttributedString(void* original, char* label, char* fg, char* bg); +NSImage* imageFromBytes(const unsigned char *bytes, int length); +void systemTraySetIcon(void* nsStatusItem, void* nsImage, int position, bool isTemplate); +void systemTraySetMenu(void* nsStatusItem, void* nsMenu); +void systemTrayDestroy(void* nsStatusItem); +void showMenu(void* nsStatusItem, void *nsMenu); +void systemTrayGetBounds(void* nsStatusItem, NSRect *rect); +int statusBarHeight(); \ No newline at end of file diff --git a/v3/pkg/application/systemtray_darwin.m b/v3/pkg/application/systemtray_darwin.m new file mode 100644 index 000000000..2af2b9bfc --- /dev/null +++ b/v3/pkg/application/systemtray_darwin.m @@ -0,0 +1,183 @@ +//go:build darwin + +#include "Cocoa/Cocoa.h" +#include "menuitem_darwin.h" +#include "systemtray_darwin.h" + +extern void systrayClickCallback(long, int); + +// StatusItemController.m +@implementation StatusItemController + +- (void)statusItemClicked:(id)sender { + NSEvent *event = [NSApp currentEvent]; + systrayClickCallback(self.id, event.type); +} + +@end + +// Create a new system tray +void* systemTrayNew(long id) { + StatusItemController *controller = [[StatusItemController alloc] init]; + controller.id = id; + NSStatusItem *statusItem = [[[NSStatusBar systemStatusBar] statusItemWithLength:NSVariableStatusItemLength] retain]; + [statusItem setTarget:controller]; + [statusItem setAction:@selector(statusItemClicked:)]; + NSButton *button = statusItem.button; + [button sendActionOn:(NSEventMaskLeftMouseDown|NSEventMaskRightMouseDown)]; + return (void*)statusItem; +} + +void systemTraySetLabel(void* nsStatusItem, char *label) { + if( label == NULL ) { + return; + } + // Set the label on the main thread + dispatch_async(dispatch_get_main_queue(), ^{ + NSStatusItem *statusItem = (NSStatusItem *)nsStatusItem; + statusItem.button.title = [NSString stringWithUTF8String:label]; + free(label); + }); +} + +void systemTraySetANSILabel(void* nsStatusItem, void* label) { + if( label == NULL ) { + return; + } + + NSMutableAttributedString* attributedString = (NSMutableAttributedString*) label; + + // Set the label + NSStatusItem *statusItem = (NSStatusItem *)nsStatusItem; + [statusItem setAttributedTitle:attributedString]; + // [attributedString release]; +} + +void* appendAttributedString(void *currentString, char *title, char *FG, char *BG) { + + NSMutableAttributedString* newString = createAttributedString(title, FG, BG); + if( currentString != NULL ) { + NSMutableAttributedString* current = (NSMutableAttributedString*)currentString; + [current appendAttributedString:newString]; + newString = current; + } + + return (void*)newString; +} + +void* createAttributedString(char *title, char *FG, char *BG) { + + NSMutableDictionary *dictionary = [NSMutableDictionary new]; + + // RGBA + if(FG != NULL && strlen(FG) > 0) { + unsigned short r, g, b, a; + + // white by default + r = g = b = a = 255; + int count = sscanf(FG, "#%02hx%02hx%02hx%02hx", &r, &g, &b, &a); + if (count > 0) { + NSColor *colour = [NSColor colorWithCalibratedRed:(CGFloat)r / 255.0 + green:(CGFloat)g / 255.0 + blue:(CGFloat)b / 255.0 + alpha:(CGFloat)a / 255.0]; + dictionary[NSForegroundColorAttributeName] = colour; + + } + } + + // Calculate BG colour + if(BG != NULL && strlen(BG) > 0) { + unsigned short r, g, b, a; + + // white by default + r = g = b = a = 255; + int count = sscanf(BG, "#%02hx%02hx%02hx%02hx", &r, &g, &b, &a); + if (count > 0) { + NSColor *colour = [NSColor colorWithCalibratedRed:(CGFloat)r / 255.0 + green:(CGFloat)g / 255.0 + blue:(CGFloat)b / 255.0 + alpha:(CGFloat)a / 255.0]; + dictionary[NSBackgroundColorAttributeName] = colour; + } + } + NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithString:[NSString stringWithUTF8String:title] attributes:dictionary]; + return (void*)attributedString; +} + +// Create an nsimage from a byte array +NSImage* imageFromBytes(const unsigned char *bytes, int length) { + NSData *data = [NSData dataWithBytes:bytes length:length]; + NSImage *image = [[NSImage alloc] initWithData:data]; + return image; +} + +// Set the icon on the system tray +void systemTraySetIcon(void* nsStatusItem, void* nsImage, int position, bool isTemplate) { + // Set the icon on the main thread + dispatch_async(dispatch_get_main_queue(), ^{ + NSStatusItem *statusItem = (NSStatusItem *)nsStatusItem; + NSImage *image = (NSImage *)nsImage; + + NSStatusBar *statusBar = [NSStatusBar systemStatusBar]; + CGFloat thickness = [statusBar thickness]; + [image setSize:NSMakeSize(thickness, thickness)]; + if( isTemplate ) { + [image setTemplate:YES]; + } + statusItem.button.image = [image autorelease]; + statusItem.button.imagePosition = position; + }); +} + +// Add menu to system tray +void systemTraySetMenu(void* nsStatusItem, void* nsMenu) { + // Set the menu on the main thread + dispatch_async(dispatch_get_main_queue(), ^{ + NSStatusItem *statusItem = (NSStatusItem *)nsStatusItem; + NSMenu *menu = (NSMenu *)nsMenu; + statusItem.menu = menu; + }); +} + +// Destroy system tray +void systemTrayDestroy(void* nsStatusItem) { + // Remove the status item from the status bar and its associated menu + dispatch_async(dispatch_get_main_queue(), ^{ + NSStatusItem *statusItem = (NSStatusItem *)nsStatusItem; + [[NSStatusBar systemStatusBar] removeStatusItem:statusItem]; + [statusItem release]; + }); +} + +void showMenu(void* nsStatusItem, void *nsMenu) { + // Show the menu on the main thread + dispatch_async(dispatch_get_main_queue(), ^{ + NSStatusItem *statusItem = (NSStatusItem *)nsStatusItem; + [statusItem popUpStatusItemMenu:(NSMenu *)nsMenu]; + // Post a mouse up event so the statusitem defocuses + NSEvent *event = [NSEvent mouseEventWithType:NSEventTypeLeftMouseUp + location:[NSEvent mouseLocation] + modifierFlags:0 + timestamp:[[NSProcessInfo processInfo] systemUptime] + windowNumber:0 + context:nil + eventNumber:0 + clickCount:1 + pressure:1]; + [NSApp postEvent:event atStart:NO]; + [statusItem.button highlight:NO]; + }); +} + +void systemTrayGetBounds(void* nsStatusItem, NSRect *rect) { + NSStatusItem *statusItem = (NSStatusItem *)nsStatusItem; + NSRect buttonFrame = statusItem.button.frame; + *rect = [statusItem.button.window convertRectToScreen:buttonFrame]; +} + +int statusBarHeight() { + NSMenu *mainMenu = [NSApp mainMenu]; + CGFloat menuBarHeight = [mainMenu menuBarHeight]; + return (int)menuBarHeight; +} diff --git a/v3/pkg/application/systemtray_linux.go b/v3/pkg/application/systemtray_linux.go new file mode 100644 index 000000000..43501c952 --- /dev/null +++ b/v3/pkg/application/systemtray_linux.go @@ -0,0 +1,663 @@ +//go:build linux + +/* +Portions of this code are derived from the project: +- https://github.com/fyne-io/systray +*/ +package application + +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" +) + +const ( + itemPath = "/StatusNotifierItem" + menuPath = "/StatusNotifierMenu" +) + +type linuxSystemTray struct { + id uint + label string + icon []byte + menu *Menu + + iconPosition int + isTemplateIcon bool + + quitChan chan struct{} + conn *dbus.Conn + props *prop.Properties + menuProps *prop.Properties + + menuVersion uint32 // need to bump this anytime we change anything + itemMap map[int32]*systrayMenuItem +} + +// dbusMenu is a named struct to map into generated bindings. +// It represents the layout of a menu item +type dbusMenu = struct { + V0 int32 // items' unique id + V1 map[string]dbus.Variant // layout properties + V2 []dbus.Variant // child menu(s) +} + +// systrayMenuItem is an implementation of the menuItemImpl interface +type systrayMenuItem struct { + sysTray *linuxSystemTray + menuItem *MenuItem + dbusItem *dbusMenu +} + +func (s *systrayMenuItem) setBitmap(data []byte) { + s.dbusItem.V1["icon-data"] = dbus.MakeVariant(data) + s.sysTray.update(s) +} + +func (s *systrayMenuItem) setTooltip(v string) { + s.dbusItem.V1["tooltip"] = dbus.MakeVariant(v) + s.sysTray.update(s) +} + +func (s *systrayMenuItem) setLabel(v string) { + s.dbusItem.V1["label"] = dbus.MakeVariant(v) + s.sysTray.update(s) +} + +func (s *systrayMenuItem) setDisabled(disabled bool) { + v := dbus.MakeVariant(!disabled) + if s.dbusItem.V1["toggle-state"] != v { + s.dbusItem.V1["enabled"] = v + s.sysTray.update(s) + } +} + +func (s *systrayMenuItem) setChecked(checked bool) { + v := dbus.MakeVariant(0) + if checked { + v = dbus.MakeVariant(1) + } + if s.dbusItem.V1["toggle-state"] != v { + s.dbusItem.V1["toggle-state"] = v + s.sysTray.update(s) + } +} + +func (s *systrayMenuItem) setAccelerator(accelerator *accelerator) {} +func (s *systrayMenuItem) setHidden(hidden bool) { + s.dbusItem.V1["visible"] = dbus.MakeVariant(!hidden) + s.sysTray.update(s) +} + +func (i systrayMenuItem) dbus() *dbusMenu { + item := &dbusMenu{ + V0: int32(i.menuItem.id), + V1: map[string]dbus.Variant{}, + V2: []dbus.Variant{}, + } + return item +} + +func (s *linuxSystemTray) setIconPosition(position int) { + s.iconPosition = position +} + +func (s *linuxSystemTray) processMenu(menu *Menu, parentId int32) { + parentItem, ok := s.itemMap[int32(parentId)] + if !ok { + return + } + parent := parentItem.dbusItem + + for _, item := range menu.items { + menuItem := &dbusMenu{ + V0: int32(item.id), + V1: map[string]dbus.Variant{}, + V2: []dbus.Variant{}, + } + item.impl = &systrayMenuItem{ + sysTray: s, + menuItem: item, + dbusItem: menuItem, + } + s.itemMap[int32(item.id)] = item.impl.(*systrayMenuItem) + + menuItem.V1["enabled"] = dbus.MakeVariant(!item.disabled) + menuItem.V1["visible"] = dbus.MakeVariant(!item.hidden) + if item.label != "" { + menuItem.V1["label"] = dbus.MakeVariant(item.label) + } + if item.bitmap != nil { + menuItem.V1["icon-data"] = dbus.MakeVariant(item.bitmap) + } + switch item.itemType { + case checkbox: + menuItem.V1["toggle-type"] = dbus.MakeVariant("checkmark") + v := dbus.MakeVariant(0) + if item.checked { + v = dbus.MakeVariant(1) + } + menuItem.V1["toggle-state"] = v + case submenu: + menuItem.V1["children-display"] = dbus.MakeVariant("submenu") + s.processMenu(item.submenu, int32(item.id)) + case text: + case radio: + menuItem.V1["toggle-type"] = dbus.MakeVariant("radio") + v := dbus.MakeVariant(0) + if item.checked { + v = dbus.MakeVariant(1) + } + menuItem.V1["toggle-state"] = v + case separator: + menuItem.V1["type"] = dbus.MakeVariant("separator") + } + + parent.V2 = append(parent.V2, dbus.MakeVariant(menuItem)) + } +} + +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) + return + } + if err := menu.Emit(s.conn, &menu.Dbusmenu_LayoutUpdatedSignal{ + Path: menuPath, + Body: &menu.Dbusmenu_LayoutUpdatedSignalBody{ + Revision: s.menuVersion, + }, + }); err != nil { + globalApplication.error("systray error: failed to emit layout updated signal: %v", err) + } +} + +func (s *linuxSystemTray) setMenu(menu *Menu) { + s.itemMap = map[int32]*systrayMenuItem{} + // our root menu element + s.itemMap[0] = &systrayMenuItem{ + menuItem: nil, + dbusItem: &dbusMenu{ + V0: int32(0), + V1: map[string]dbus.Variant{}, + V2: []dbus.Variant{}, + }, + } + menu.processRadioGroups() + s.processMenu(menu, 0) + s.menu = menu +} + +func (s *linuxSystemTray) positionWindow(window *WebviewWindow, offset int) error { + return nil +} + +func (s *linuxSystemTray) getScreen() (*Screen, error) { + // FIXME: How do we get the screen we are on? + return &Screen{}, nil +} + +func (s *linuxSystemTray) bounds() (*Rect, error) { + return &Rect{}, nil +} + +func (s *linuxSystemTray) run() { + conn, err := dbus.SessionBus() + if err != nil { + globalApplication.error("systray error: failed to connect to DBus: %v\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) + } + + err = menu.ExportDbusmenu(conn, menuPath, s) + if err != nil { + globalApplication.error("systray error: failed to export status notifier menu: %v", 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) + // 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) + 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) + return + } + + s.conn = conn + s.props = props + s.menuProps = menuProps + + node := introspect.Node{ + Name: itemPath, + Interfaces: []introspect.Interface{ + introspect.IntrospectData, + prop.IntrospectData, + notifier.IntrospectDataStatusNotifierItem, + }, + } + 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) + return + } + menuNode := introspect.Node{ + Name: menuPath, + Interfaces: []introspect.Interface{ + introspect.IntrospectData, + prop.IntrospectData, + menu.IntrospectDataDbusmenu, + }, + } + 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) + return + } + s.setLabel(s.label) + go func() { + s.register() + + if err := conn.AddMatchSignal( + dbus.WithMatchObjectPath("/org/freedesktop/DBus"), + dbus.WithMatchInterface("org.freedesktop.DBus"), + dbus.WithMatchSender("org.freedesktop.DBus"), + dbus.WithMatchMember("NameOwnerChanged"), + dbus.WithMatchArg(0, "org.kde.StatusNotifierWatcher"), + ); err != nil { + globalApplication.error("systray error: failed to register signal matching: %v\n", err) + return + } + + sc := make(chan *dbus.Signal, 10) + conn.Signal(sc) + + for { + select { + case sig := <-sc: + if sig == nil { + return // We get a nil signal when closing the window. + } + // sig.Body has the args, which are [name old_owner new_owner] + if sig.Body[2] != "" { + s.register() + } + + case <-s.quitChan: + return + } + } + }() + s.setMenu(s.menu) +} + +func (s *linuxSystemTray) setIcon(icon []byte) { + + s.icon = icon + + iconPx, err := iconToPX(icon) + if err != nil { + globalApplication.error("systray error: failed to convert icon to PX: %s\n", err) + return + } + s.props.SetMust("org.kde.StatusNotifierItem", "IconPixmap", iconPx) + + if s.conn == nil { + return + } + + err = notifier.Emit(s.conn, ¬ifier.StatusNotifierItem_NewIconSignal{ + Path: itemPath, + Body: ¬ifier.StatusNotifierItem_NewIconSignalBody{}, + }) + if err != nil { + globalApplication.error("systray error: failed to emit new icon signal: %s\n", err) + return + } +} + +func (s *linuxSystemTray) setDarkModeIcon(icon []byte) { + s.setIcon(icon) +} + +func (s *linuxSystemTray) setTemplateIcon(icon []byte) { + s.icon = icon + s.isTemplateIcon = true + s.setIcon(icon) +} + +func newSystemTrayImpl(s *SystemTray) systemTrayImpl { + label := s.label + if label == "" { + label = "Wails" + } + + return &linuxSystemTray{ + id: s.id, + label: label, + icon: s.icon, + menu: s.menu, + iconPosition: s.iconPosition, + isTemplateIcon: s.isTemplateIcon, + quitChan: make(chan struct{}), + menuVersion: 1, + } +} + +func (s *linuxSystemTray) openMenu() { + // FIXME: Use DBUS to open? + globalApplication.info("systray error: openMenu not implemented on Linux") +} + +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) + return + } + + if s.conn == nil { + return + } + + if err := notifier.Emit(s.conn, ¬ifier.StatusNotifierItem_NewTitleSignal{ + Path: itemPath, + Body: ¬ifier.StatusNotifierItem_NewTitleSignalBody{}, + }); err != nil { + globalApplication.error("systray error: failed to emit new title signal: %s", err) + return + } + +} + +func (s *linuxSystemTray) destroy() { + close(s.quitChan) +} + +func (s *linuxSystemTray) createMenuPropSpec() map[string]map[string]*prop.Prop { + return map[string]map[string]*prop.Prop{ + "com.canonical.dbusmenu": { + // update version each time we change something + "Version": { + Value: s.menuVersion, + Writable: true, + Emit: prop.EmitTrue, + Callback: nil, + }, + "TextDirection": { + Value: "ltr", + Writable: false, + Emit: prop.EmitTrue, + Callback: nil, + }, + "Status": { + Value: "normal", + Writable: false, + Emit: prop.EmitTrue, + Callback: nil, + }, + "IconThemePath": { + Value: []string{}, + Writable: false, + Emit: prop.EmitTrue, + Callback: nil, + }, + }, + } +} + +func (s *linuxSystemTray) createPropSpec() map[string]map[string]*prop.Prop { + props := map[string]*prop.Prop{ + "Status": { + Value: "Active", // Passive, Active or NeedsAttention + Writable: false, + Emit: prop.EmitTrue, + Callback: nil, + }, + "Title": { + Value: s.label, + Writable: true, + Emit: prop.EmitTrue, + Callback: nil, + }, + "Id": { + Value: s.label, + Writable: false, + Emit: prop.EmitTrue, + Callback: nil, + }, + "Category": { + Value: "ApplicationStatus", + Writable: false, + Emit: prop.EmitTrue, + Callback: nil, + }, + "IconData": { + Value: "", + Writable: false, + Emit: prop.EmitTrue, + Callback: nil, + }, + + "IconName": { + Value: "", + Writable: false, + Emit: prop.EmitTrue, + Callback: nil, + }, + "IconThemePath": { + Value: "", + Writable: false, + Emit: prop.EmitTrue, + Callback: nil, + }, + "ItemIsMenu": { + Value: true, + Writable: false, + Emit: prop.EmitTrue, + Callback: nil, + }, + "Menu": { + Value: dbus.ObjectPath(menuPath), + Writable: true, + Emit: prop.EmitTrue, + Callback: nil, + }, + "ToolTip": { + Value: tooltip{V2: s.label}, + Writable: true, + Emit: prop.EmitTrue, + Callback: nil, + }, + } + + if s.icon == nil { + // set a basic default one if one isn't set + s.icon = icons.WailsLogoWhiteTransparent + } + if iconPx, err := iconToPX(s.icon); err == nil { + props["IconPixmap"] = &prop.Prop{ + Value: []PX{iconPx}, + Writable: true, + Emit: prop.EmitTrue, + Callback: nil, + } + } + + return map[string]map[string]*prop.Prop{ + "org.kde.StatusNotifierItem": props, + } +} + +func (s *linuxSystemTray) update(i *systrayMenuItem) { + s.itemMap[int32(i.menuItem.id)] = i + s.refresh() +} + +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) + return false + } + + return true +} + +type PX struct { + W, H int + Pix []byte +} + +func iconToPX(icon []byte) (PX, error) { + img, err := pngToImage(icon) + if err != nil { + return PX{}, err + } + w, h, bytes := ToARGB(img) + return PX{ + W: w, + H: h, + Pix: bytes, + }, nil +} + +// AboutToShow is an implementation of the com.canonical.dbusmenu.AboutToShow method. +func (s *linuxSystemTray) AboutToShow(id int32) (needUpdate bool, err *dbus.Error) { + return +} + +// AboutToShowGroup is an implementation of the com.canonical.dbusmenu.AboutToShowGroup method. +func (s *linuxSystemTray) AboutToShowGroup(ids []int32) (updatesNeeded []int32, idErrors []int32, err *dbus.Error) { + return +} + +// GetProperty is an implementation of the com.canonical.dbusmenu.GetProperty method. +func (s *linuxSystemTray) GetProperty(id int32, name string) (value dbus.Variant, err *dbus.Error) { + if item, ok := s.itemMap[id]; ok { + if p, ok := item.dbusItem.V1[name]; ok { + return p, nil + } + } + return +} + +// Event is com.canonical.dbusmenu.Event method. +func (s *linuxSystemTray) Event(id int32, eventID string, data dbus.Variant, timestamp uint32) (err *dbus.Error) { + if eventID == "clicked" { + if item, ok := s.itemMap[id]; ok { + InvokeAsync(item.menuItem.handleClick) + } + } + return +} + +// EventGroup is an implementation of the com.canonical.dbusmenu.EventGroup method. +func (s *linuxSystemTray) EventGroup(events []struct { + V0 int32 + V1 string + V2 dbus.Variant + V3 uint32 +}) (idErrors []int32, err *dbus.Error) { + for _, event := range events { + if event.V1 == "clicked" { + item, ok := s.itemMap[event.V0] + if ok { + InvokeAsync(item.menuItem.handleClick) + } + } + } + return +} + +// GetGroupProperties is an implementation of the com.canonical.dbusmenu.GetGroupProperties method. +func (s *linuxSystemTray) GetGroupProperties(ids []int32, propertyNames []string) (properties []struct { + V0 int32 + V1 map[string]dbus.Variant +}, err *dbus.Error) { + // FIXME: RLock? + /* instance.menuLock.Lock() + defer instance.menuLock.Unlock() + */ + for _, id := range ids { + if m, ok := s.itemMap[id]; ok { + p := struct { + V0 int32 + V1 map[string]dbus.Variant + }{ + V0: m.dbusItem.V0, + V1: make(map[string]dbus.Variant, len(m.dbusItem.V1)), + } + for k, v := range m.dbusItem.V1 { + p.V1[k] = v + } + properties = append(properties, p) + } + } + return properties, nil +} + +// GetLayout is an implementation of the com.canonical.dbusmenu.GetLayout method. +func (s *linuxSystemTray) GetLayout(parentID int32, recursionDepth int32, propertyNames []string) (revision uint32, layout dbusMenu, err *dbus.Error) { + // FIXME: RLock? + if m, ok := s.itemMap[parentID]; ok { + return s.menuVersion, *m.dbusItem, nil + } + + return +} + +// Activate implements org.kde.StatusNotifierItem.Activate method. +func (s *linuxSystemTray) Activate(x int32, y int32) (err *dbus.Error) { + fmt.Println("Activate", x, y) + return +} + +// ContextMenu is org.kde.StatusNotifierItem.ContextMenu method +func (s *linuxSystemTray) ContextMenu(x int32, y int32) (err *dbus.Error) { + fmt.Println("ContextMenu", x, y) + return +} + +func (s *linuxSystemTray) Scroll(delta int32, orientation string) (err *dbus.Error) { + fmt.Println("Scroll", delta, orientation) + return +} + +// SecondaryActivate implements org.kde.StatusNotifierItem.SecondaryActivate method. +func (s *linuxSystemTray) SecondaryActivate(x int32, y int32) (err *dbus.Error) { + fmt.Println("SecondaryActivate", x, y) + return +} + +// tooltip is our data for a tooltip property. +// Param names need to match the generated code... +type tooltip = struct { + V0 string // name + V1 []PX // icons + V2 string // title + V3 string // description +} diff --git a/v3/pkg/application/systemtray_windows.go b/v3/pkg/application/systemtray_windows.go new file mode 100644 index 000000000..0915035c0 --- /dev/null +++ b/v3/pkg/application/systemtray_windows.go @@ -0,0 +1,352 @@ +//go:build windows + +package application + +import ( + "fmt" + "github.com/wailsapp/wails/v3/pkg/icons" + "syscall" + "unsafe" + + "github.com/samber/lo" + "github.com/wailsapp/wails/v3/pkg/events" + "github.com/wailsapp/wails/v3/pkg/w32" +) + +const ( + WM_USER_SYSTRAY = w32.WM_USER + 1 +) + +type windowsSystemTray struct { + parent *SystemTray + + menu *Win32Menu + + // Platform specific implementation + uid uint32 + hwnd w32.HWND + lightModeIcon w32.HICON + darkModeIcon w32.HICON + currentIcon w32.HICON +} + +func (s *windowsSystemTray) openMenu() { + if s.menu == nil { + return + } + // Get the system tray bounds + trayBounds, err := s.bounds() + if err != nil { + return + } + + // Show the menu at the tray bounds + s.menu.ShowAt(trayBounds.X, trayBounds.Y) + +} + +func (s *windowsSystemTray) positionWindow(window *WebviewWindow, offset int) error { + + // Get the trayBounds of this system tray + trayBounds, err := s.bounds() + if err != nil { + return err + } + + // Get the current screen trayBounds + currentScreen, err := s.getScreen() + if err != nil { + return err + } + + screenBounds := currentScreen.WorkArea + + newX := screenBounds.Width - window.Width() + newY := screenBounds.Height - window.Height() + + taskbarBounds := w32.GetTaskbarPosition() + switch taskbarBounds.UEdge { + case w32.ABE_LEFT: + if trayBounds != nil && trayBounds.Y-(window.Height()/2) >= 0 { + newY = trayBounds.Y - (window.Height() / 2) + } + window.SetRelativePosition(offset, newY) + case w32.ABE_TOP: + if trayBounds != nil && trayBounds.X-(window.Width()/2) <= newX { + newX = trayBounds.X - (window.Width() / 2) + } + window.SetRelativePosition(newX, offset) + case w32.ABE_RIGHT: + if trayBounds != nil && trayBounds.Y-(window.Height()/2) <= newY { + newY = trayBounds.Y - (window.Height() / 2) + } + window.SetRelativePosition(screenBounds.Width-window.Width()-offset, newY) + case w32.ABE_BOTTOM: + if trayBounds != nil && trayBounds.X-(window.Width()/2) <= newX { + newX = trayBounds.X - (window.Width() / 2) + } + window.SetRelativePosition(newX, screenBounds.Height-window.Height()-offset) + } + return nil +} + +func (s *windowsSystemTray) bounds() (*Rect, error) { + bounds, err := w32.GetSystrayBounds(s.hwnd, s.uid) + if err != nil { + return nil, err + } + + monitor := w32.MonitorFromWindow(s.hwnd, w32.MONITOR_DEFAULTTONEAREST) + if monitor == 0 { + return nil, fmt.Errorf("failed to get monitor") + } + + // Get the taskbar rect + taskbarRect := w32.GetTaskbarPosition() + + flyoutOpen := !w32.RectInRect(bounds, &taskbarRect.Rc) + if flyoutOpen { + return nil, nil + } + + return &Rect{ + X: int(bounds.Left), + Y: int(bounds.Top), + Width: int(bounds.Right - bounds.Left), + Height: int(bounds.Bottom - bounds.Top), + }, nil +} + +func (s *windowsSystemTray) getScreen() (*Screen, error) { + // Get the screen for this systray + return getScreen(s.hwnd) +} + +func (s *windowsSystemTray) setMenu(menu *Menu) { + s.updateMenu(menu) +} + +func (s *windowsSystemTray) run() { + s.hwnd = w32.CreateWindowEx( + 0, + windowClassName, + nil, + 0, + 0, + 0, + 0, + 0, + w32.HWND_MESSAGE, + 0, + 0, + nil) + if s.hwnd == 0 { + panic(syscall.GetLastError()) + } + + nid := w32.NOTIFYICONDATA{ + HWnd: s.hwnd, + UID: uint32(s.parent.id), + UFlags: w32.NIF_ICON | w32.NIF_MESSAGE, + HIcon: s.currentIcon, + UCallbackMessage: WM_USER_SYSTRAY, + } + nid.CbSize = uint32(unsafe.Sizeof(nid)) + + if !w32.ShellNotifyIcon(w32.NIM_ADD, &nid) { + panic(syscall.GetLastError()) + } + + nid.UVersion = w32.NOTIFYICON_VERSION + + if !w32.ShellNotifyIcon(w32.NIM_SETVERSION, &nid) { + panic(syscall.GetLastError()) + } + + if s.parent.icon != nil { + s.lightModeIcon = lo.Must(w32.CreateSmallHIconFromImage(s.parent.icon)) + } else { + s.lightModeIcon = lo.Must(w32.CreateSmallHIconFromImage(icons.SystrayLight)) + } + if s.parent.darkModeIcon != nil { + s.darkModeIcon = lo.Must(w32.CreateSmallHIconFromImage(s.parent.darkModeIcon)) + } else { + s.darkModeIcon = lo.Must(w32.CreateSmallHIconFromImage(icons.SystrayDark)) + } + s.uid = nid.UID + + if s.parent.menu != nil { + s.updateMenu(s.parent.menu) + } + + // Set Default Callbacks + if s.parent.clickHandler == nil { + s.parent.clickHandler = func() { + globalApplication.debug("Left Button Clicked") + } + } + if s.parent.rightClickHandler == nil { + s.parent.rightClickHandler = func() { + if s.menu != nil { + s.menu.ShowAtCursor() + } + } + } + + // Update the icon + s.updateIcon() + + // Listen for dark mode changes + globalApplication.On(events.Windows.SystemThemeChanged, func(event *Event) { + s.updateIcon() + }) + + // Register the system tray + getNativeApplication().registerSystemTray(s) + +} + +func (s *windowsSystemTray) updateIcon() { + + var newIcon w32.HICON + if w32.IsCurrentlyDarkMode() { + newIcon = s.darkModeIcon + } else { + newIcon = s.lightModeIcon + } + if s.currentIcon == newIcon { + return + } + + s.currentIcon = newIcon + nid := s.newNotifyIconData() + nid.UFlags = w32.NIF_ICON + if s.currentIcon != 0 { + nid.HIcon = s.currentIcon + } + + if !w32.ShellNotifyIcon(w32.NIM_MODIFY, &nid) { + panic(syscall.GetLastError()) + } + return +} + +func (s *windowsSystemTray) newNotifyIconData() w32.NOTIFYICONDATA { + nid := w32.NOTIFYICONDATA{ + UID: s.uid, + HWnd: s.hwnd, + } + nid.CbSize = uint32(unsafe.Sizeof(nid)) + return nid +} + +func (s *windowsSystemTray) setIcon(icon []byte) { + var err error + s.lightModeIcon, err = w32.CreateSmallHIconFromImage(icon) + if err != nil { + panic(syscall.GetLastError()) + } + if s.darkModeIcon == 0 { + s.darkModeIcon = s.lightModeIcon + } + // Update the icon + s.updateIcon() +} +func (s *windowsSystemTray) setDarkModeIcon(icon []byte) { + var err error + s.darkModeIcon, err = w32.CreateSmallHIconFromImage(icon) + if err != nil { + panic(syscall.GetLastError()) + } + if s.lightModeIcon == 0 { + s.lightModeIcon = s.darkModeIcon + } + // Update the icon + s.updateIcon() +} + +func newSystemTrayImpl(parent *SystemTray) systemTrayImpl { + return &windowsSystemTray{ + parent: parent, + } +} + +func (s *windowsSystemTray) wndProc(msg uint32, wParam, lParam uintptr) uintptr { + switch msg { + case WM_USER_SYSTRAY: + msg := lParam & 0xffff + switch msg { + case w32.WM_LBUTTONUP: + if s.parent.clickHandler != nil { + s.parent.clickHandler() + } + case w32.WM_RBUTTONUP: + if s.parent.rightClickHandler != nil { + s.parent.rightClickHandler() + } + case w32.WM_LBUTTONDBLCLK: + if s.parent.doubleClickHandler != nil { + s.parent.doubleClickHandler() + } + case w32.WM_RBUTTONDBLCLK: + if s.parent.rightDoubleClickHandler != nil { + s.parent.rightDoubleClickHandler() + } + case 0x0406: + if s.parent.mouseEnterHandler != nil { + s.parent.mouseEnterHandler() + } + case 0x0407: + if s.parent.mouseLeaveHandler != nil { + s.parent.mouseLeaveHandler() + } + } + //println(w32.WMMessageToString(msg)) + + // Menu processing + case w32.WM_COMMAND: + cmdMsgID := int(wParam & 0xffff) + switch cmdMsgID { + default: + s.menu.ProcessCommand(cmdMsgID) + } + default: + //msg := int(wParam & 0xffff) + //println(w32.WMMessageToString(uintptr(msg))) + } + + return w32.DefWindowProc(s.hwnd, msg, wParam, lParam) +} + +func (s *windowsSystemTray) updateMenu(menu *Menu) { + s.menu = NewPopupMenu(s.hwnd, menu) + s.menu.onMenuOpen = s.parent.onMenuOpen + s.menu.onMenuClose = s.parent.onMenuClose + s.menu.Update() +} + +// ---- Unsupported ---- + +func (s *windowsSystemTray) setLabel(_ string) { + // Unsupported - do nothing +} + +func (s *windowsSystemTray) setTemplateIcon(_ []byte) { + // Unsupported - do nothing +} + +func (s *windowsSystemTray) setIconPosition(position int) { + // Unsupported - do nothing +} + +func (s *windowsSystemTray) destroy() { + // Remove and delete the system tray + getNativeApplication().unregisterSystemTray(s) + s.menu.Destroy() + w32.DestroyWindow(s.hwnd) + // Destroy the notification icon + nid := s.newNotifyIconData() + if !w32.ShellNotifyIcon(w32.NIM_DELETE, &nid) { + globalApplication.debug(syscall.GetLastError().Error()) + } +} diff --git a/v3/pkg/application/webview_window.go b/v3/pkg/application/webview_window.go new file mode 100644 index 000000000..a074b607a --- /dev/null +++ b/v3/pkg/application/webview_window.go @@ -0,0 +1,1125 @@ +package application + +import ( + "encoding/json" + "errors" + "fmt" + "github.com/leaanthony/u" + "runtime" + "strings" + "sync" + + "github.com/samber/lo" + "github.com/wailsapp/wails/v3/pkg/events" +) + +// Enabled means the feature should be enabled +var Enabled = u.True + +// Disabled means the feature should be disabled +var Disabled = u.False + +type ( + webviewWindowImpl interface { + setTitle(title string) + setSize(width, height int) + setAlwaysOnTop(alwaysOnTop bool) + setURL(url string) + setResizable(resizable bool) + setMinSize(width, height int) + setMaxSize(width, height int) + execJS(js string) + setBackgroundColour(color RGBA) + run() + center() + size() (int, int) + width() int + height() int + relativePosition() (int, int) + destroy() + reload() + forceReload() + toggleDevTools() + zoomReset() + zoomIn() + zoomOut() + getZoom() float64 + setZoom(zoom float64) + close() + zoom() + setHTML(html string) + setRelativePosition(x int, y int) + on(eventID uint) + minimise() + unminimise() + maximise() + unmaximise() + fullscreen() + unfullscreen() + isMinimised() bool + isMaximised() bool + isFullscreen() bool + isNormal() bool + isVisible() bool + isFocused() bool + setFullscreenButtonEnabled(enabled bool) + focus() + show() + hide() + getScreen() (*Screen, error) + setFrameless(bool) + openContextMenu(menu *Menu, data *ContextMenuData) + nativeWindowHandle() uintptr + startDrag() error + startResize(border string) error + print() error + setEnabled(enabled bool) + absolutePosition() (int, int) + setAbsolutePosition(x int, y int) + flash(enabled bool) + handleKeyEvent(acceleratorString string) + } +) + +type WindowEvent struct { + ctx *WindowEventContext + Cancelled bool +} + +func (w *WindowEvent) Context() *WindowEventContext { + return w.ctx +} + +func NewWindowEvent() *WindowEvent { + return &WindowEvent{} +} + +func (w *WindowEvent) Cancel() { + w.Cancelled = true +} + +type WindowEventListener struct { + callback func(event *WindowEvent) +} + +type WebviewWindow struct { + options WebviewWindowOptions + impl webviewWindowImpl + id uint + + eventListeners map[uint][]*WindowEventListener + eventListenersLock sync.RWMutex + eventHooks map[uint][]*WindowEventListener + eventHooksLock sync.RWMutex + + contextMenus map[string]*Menu + contextMenusLock sync.RWMutex + + // A map of listener cancellation functions + cancellersLock sync.RWMutex + cancellers []func() + + // keyBindings holds the keybindings for the window + keyBindings map[string]func(window *WebviewWindow) +} + +var windowID uint +var windowIDLock sync.RWMutex + +func getWindowID() uint { + windowIDLock.Lock() + defer windowIDLock.Unlock() + windowID++ + return windowID +} + +// FIXME: This should like be an interface method (TDM) +// Use onApplicationEvent to register a callback for an application event from a window. +// This will handle tidying up the callback when the window is destroyed +func (w *WebviewWindow) onApplicationEvent(eventType events.ApplicationEventType, callback func(*Event)) { + cancelFn := globalApplication.On(eventType, callback) + w.addCancellationFunction(cancelFn) +} + +func (w *WebviewWindow) setupEventMapping() { + + var mapping map[events.WindowEventType]events.WindowEventType + switch runtime.GOOS { + case "darwin": + mapping = w.options.Mac.EventMapping + case "windows": + mapping = w.options.Windows.EventMapping + case "linux": + // TBD + } + if mapping == nil { + mapping = events.DefaultWindowEventMapping() + } + + for source, target := range mapping { + source := source + target := target + w.On(source, func(event *WindowEvent) { + w.emit(target) + }) + } +} + +// NewWindow creates a new window with the given options +func NewWindow(options WebviewWindowOptions) *WebviewWindow { + if options.Width == 0 { + options.Width = 800 + } + if options.Height == 0 { + options.Height = 600 + } + if options.URL == "" { + options.URL = "/" + } + + result := &WebviewWindow{ + id: getWindowID(), + options: options, + eventListeners: make(map[uint][]*WindowEventListener), + contextMenus: make(map[string]*Menu), + eventHooks: make(map[uint][]*WindowEventListener), + } + + result.setupEventMapping() + + // Listen for window closing events and de + result.On(events.Common.WindowClosing, func(event *WindowEvent) { + shouldClose := true + if result.options.ShouldClose != nil { + shouldClose = result.options.ShouldClose(result) + } + if shouldClose { + globalApplication.deleteWindowByID(result.id) + InvokeSync(result.impl.close) + } + }) + + // Process keybindings + if result.options.KeyBindings != nil { + result.keyBindings = processKeyBindingOptions(result.options.KeyBindings) + } + + return result +} + +func processKeyBindingOptions(keyBindings map[string]func(window *WebviewWindow)) map[string]func(window *WebviewWindow) { + result := make(map[string]func(window *WebviewWindow)) + for key, callback := range keyBindings { + // Parse the key to an accelerator + acc, err := parseAccelerator(key) + if err != nil { + globalApplication.error("Invalid keybinding: %s", err.Error()) + continue + } + result[acc.String()] = callback + globalApplication.debug("Added Keybinding", "accelerator", acc.String()) + } + return result +} + +func (w *WebviewWindow) addCancellationFunction(canceller func()) { + w.cancellersLock.Lock() + defer w.cancellersLock.Unlock() + 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) { + if w.impl != nil { + w.impl.execJS(w.formatJS("_wails.callErrorCallback('%s', %s);", callID, result)) + } +} + +func (w *WebviewWindow) CallResponse(callID string, result string) { + if w.impl != nil { + w.impl.execJS(w.formatJS("_wails.callCallback('%s', %s, true);", callID, result)) + } +} + +func (w *WebviewWindow) DialogError(dialogID string, result string) { + if w.impl != nil { + w.impl.execJS(w.formatJS("_wails.dialogErrorCallback('%s', %s);", dialogID, result)) + } +} + +func (w *WebviewWindow) DialogResponse(dialogID string, result string, isJSON bool) { + if w.impl != nil { + if isJSON { + w.impl.execJS(w.formatJS("_wails.dialogCallback('%s', %s, true);", dialogID, result)) + } else { + w.impl.execJS(fmt.Sprintf("_wails.dialogCallback('%s', '%s', false);", dialogID, result)) + } + } +} + +func (w *WebviewWindow) ID() uint { + return w.id +} + +// SetTitle sets the title of the window +func (w *WebviewWindow) SetTitle(title string) Window { + w.options.Title = title + if w.impl != nil { + InvokeSync(func() { + w.impl.setTitle(title) + }) + } + return w +} + +// Name returns the name of the window +func (w *WebviewWindow) Name() string { + return w.options.Name +} + +// SetSize sets the size of the window +func (w *WebviewWindow) SetSize(width, height int) Window { + // Don't set size if fullscreen + if w.IsFullscreen() { + return w + } + w.options.Width = width + w.options.Height = height + + var newMaxWidth = w.options.MaxWidth + var newMaxHeight = w.options.MaxHeight + if width > w.options.MaxWidth && w.options.MaxWidth != 0 { + newMaxWidth = width + } + if height > w.options.MaxHeight && w.options.MaxHeight != 0 { + newMaxHeight = height + } + + if newMaxWidth != 0 || newMaxHeight != 0 { + w.SetMaxSize(newMaxWidth, newMaxHeight) + } + + var newMinWidth = w.options.MinWidth + var newMinHeight = w.options.MinHeight + if width < w.options.MinWidth && w.options.MinWidth != 0 { + newMinWidth = width + } + if height < w.options.MinHeight && w.options.MinHeight != 0 { + newMinHeight = height + } + + if newMinWidth != 0 || newMinHeight != 0 { + w.SetMinSize(newMinWidth, newMinHeight) + } + + if w.impl != nil { + InvokeSync(func() { + w.impl.setSize(width, height) + }) + } + return w +} + +func (w *WebviewWindow) Run() { + if w.impl != nil { + return + } + w.impl = newWindowImpl(w) + InvokeSync(w.impl.run) +} + +// SetAlwaysOnTop sets the window to be always on top. +func (w *WebviewWindow) SetAlwaysOnTop(b bool) Window { + w.options.AlwaysOnTop = b + if w.impl != nil { + InvokeSync(func() { + w.impl.setAlwaysOnTop(b) + }) + } + return w +} + +// Show shows the window. +func (w *WebviewWindow) Show() Window { + if globalApplication.impl == nil { + return w + } + if w.impl == nil { + InvokeSync(w.Run) + return w + } + InvokeSync(w.impl.show) + w.emit(events.Common.WindowShow) + return w +} + +// Hide hides the window. +func (w *WebviewWindow) Hide() Window { + w.options.Hidden = true + if w.impl != nil { + InvokeSync(w.impl.hide) + w.emit(events.Common.WindowHide) + } + return w +} + +func (w *WebviewWindow) SetURL(s string) Window { + w.options.URL = s + if w.impl != nil { + InvokeSync(func() { + w.impl.setURL(s) + }) + } + return w +} + +// SetZoom sets the zoom level of the window. +func (w *WebviewWindow) SetZoom(magnification float64) Window { + w.options.Zoom = magnification + if w.impl != nil { + InvokeSync(func() { + w.impl.setZoom(magnification) + }) + } + return w +} + +// GetZoom returns the current zoom level of the window. +func (w *WebviewWindow) GetZoom() float64 { + if w.impl != nil { + return InvokeSyncWithResult(w.impl.getZoom) + } + return 1 +} + +// SetResizable sets whether the window is resizable. +func (w *WebviewWindow) SetResizable(b bool) Window { + w.options.DisableResize = !b + if w.impl != nil { + InvokeSync(func() { + w.impl.setResizable(b) + }) + } + return w +} + +// Resizable returns true if the window is resizable. +func (w *WebviewWindow) Resizable() bool { + return !w.options.DisableResize +} + +// SetMinSize sets the minimum size of the window. +func (w *WebviewWindow) SetMinSize(minWidth, minHeight int) Window { + w.options.MinWidth = minWidth + w.options.MinHeight = minHeight + + currentWidth, currentHeight := w.Size() + newWidth, newHeight := currentWidth, currentHeight + + var newSize bool + if minHeight != 0 && currentHeight < minHeight { + newHeight = minHeight + w.options.Height = newHeight + newSize = true + } + if minWidth != 0 && currentWidth < minWidth { + newWidth = minWidth + w.options.Width = newWidth + newSize = true + } + if w.impl != nil { + if newSize { + InvokeSync(func() { + w.impl.setSize(newWidth, newHeight) + }) + } + InvokeSync(func() { + w.impl.setMinSize(minWidth, minHeight) + }) + } + return w +} + +// SetMaxSize sets the maximum size of the window. +func (w *WebviewWindow) SetMaxSize(maxWidth, maxHeight int) Window { + w.options.MaxWidth = maxWidth + w.options.MaxHeight = maxHeight + + currentWidth, currentHeight := w.Size() + newWidth, newHeight := currentWidth, currentHeight + + var newSize bool + if maxHeight != 0 && currentHeight > maxHeight { + newHeight = maxHeight + w.options.Height = maxHeight + newSize = true + } + if maxWidth != 0 && currentWidth > maxWidth { + newWidth = maxWidth + w.options.Width = maxWidth + newSize = true + } + if w.impl != nil { + if newSize { + InvokeSync(func() { + w.impl.setSize(newWidth, newHeight) + }) + } + InvokeSync(func() { + w.impl.setMaxSize(maxWidth, maxHeight) + }) + } + return w +} + +// ExecJS executes the given javascript in the context of the window. +func (w *WebviewWindow) ExecJS(_callID, js string) { + if w.impl == nil { + return + } + w.impl.execJS(js) +} + +// Fullscreen sets the window to fullscreen mode. Min/Max size constraints are disabled. +func (w *WebviewWindow) Fullscreen() Window { + if w.impl == nil { + w.options.StartState = WindowStateFullscreen + return w + } + if !w.IsFullscreen() { + w.DisableSizeConstraints() + InvokeSync(w.impl.fullscreen) + } + return w +} + +func (w *WebviewWindow) SetFullscreenButtonEnabled(enabled bool) Window { + w.options.FullscreenButtonEnabled = enabled + if w.impl != nil { + InvokeSync(func() { + w.impl.setFullscreenButtonEnabled(enabled) + }) + } + return w +} + +// Flash flashes the window's taskbar button/icon. +// Useful to indicate that attention is required. Windows only. +func (w *WebviewWindow) Flash(enabled bool) { + if w.impl == nil { + return + } + InvokeSync(func() { + w.impl.flash(enabled) + }) +} + +// IsMinimised returns true if the window is minimised +func (w *WebviewWindow) IsMinimised() bool { + if w.impl == nil { + return false + } + return InvokeSyncWithResult(w.impl.isMinimised) +} + +// IsVisible returns true if the window is visible +func (w *WebviewWindow) IsVisible() bool { + if w.impl == nil { + return false + } + return InvokeSyncWithResult(w.impl.isVisible) +} + +// IsMaximised returns true if the window is maximised +func (w *WebviewWindow) IsMaximised() bool { + if w.impl == nil { + return false + } + return InvokeSyncWithResult(w.impl.isMaximised) +} + +// Size returns the size of the window +func (w *WebviewWindow) Size() (int, int) { + if w.impl == nil { + return 0, 0 + } + var width, height int + InvokeSync(func() { + width, height = w.impl.size() + }) + return width, height +} + +// IsFocused returns true if the window is currently focused +func (w *WebviewWindow) IsFocused() bool { + if w.impl == nil { + return false + } + return InvokeSyncWithResult(w.impl.isFocused) +} + +// IsFullscreen returns true if the window is fullscreen +func (w *WebviewWindow) IsFullscreen() bool { + if w.impl == nil { + return false + } + return InvokeSyncWithResult(w.impl.isFullscreen) +} + +// SetBackgroundColour sets the background colour of the window +func (w *WebviewWindow) SetBackgroundColour(colour RGBA) Window { + w.options.BackgroundColour = colour + if w.impl != nil { + InvokeSync(func() { + w.impl.setBackgroundColour(colour) + }) + } + return w +} + +func (w *WebviewWindow) HandleMessage(message string) { + // Check for special messages + if message == "drag" { + if !w.IsFullscreen() { + InvokeSync(func() { + err := w.startDrag() + if err != nil { + w.Error("Failed to start drag: %s", err) + } + }) + } + } + if strings.HasPrefix(message, "resize:") { + if !w.IsFullscreen() { + sl := strings.Split(message, ":") + if len(sl) != 2 { + w.Error("Unknown message returned from dispatcher: %+v", message) + return + } + err := w.startResize(sl[1]) + if err != nil { + w.Error(err.Error()) + } + } + return + } +} + +func (w *WebviewWindow) startResize(border string) error { + if w.impl == nil { + return nil + } + return InvokeSyncWithResult(func() error { + return w.impl.startResize(border) + }) +} + +// Center centers the window on the screen +func (w *WebviewWindow) Center() { + if w.impl == nil { + w.options.Centered = true + return + } + InvokeSync(w.impl.center) +} + +// On registers a callback for the given window event +func (w *WebviewWindow) On(eventType events.WindowEventType, callback func(event *WindowEvent)) func() { + eventID := uint(eventType) + w.eventListenersLock.Lock() + defer w.eventListenersLock.Unlock() + windowEventListener := &WindowEventListener{ + callback: callback, + } + w.eventListeners[eventID] = append(w.eventListeners[eventID], windowEventListener) + if w.impl != nil { + w.impl.on(eventID) + } + + return func() { + w.eventListenersLock.Lock() + defer w.eventListenersLock.Unlock() + w.eventListeners[eventID] = lo.Without(w.eventListeners[eventID], windowEventListener) + } +} + +// RegisterHook registers a hook for the given window event +func (w *WebviewWindow) RegisterHook(eventType events.WindowEventType, callback func(event *WindowEvent)) func() { + eventID := uint(eventType) + w.eventHooksLock.Lock() + defer w.eventHooksLock.Unlock() + windowEventHook := &WindowEventListener{ + callback: callback, + } + w.eventHooks[eventID] = append(w.eventHooks[eventID], windowEventHook) + + return func() { + w.eventHooksLock.Lock() + defer w.eventHooksLock.Unlock() + w.eventHooks[eventID] = lo.Without(w.eventHooks[eventID], windowEventHook) + } +} + +func (w *WebviewWindow) HandleWindowEvent(id uint) { + w.eventListenersLock.RLock() + defer w.eventListenersLock.RUnlock() + + // Get hooks + w.eventHooksLock.RLock() + hooks := w.eventHooks[id] + w.eventHooksLock.RUnlock() + + // Create new WindowEvent + thisEvent := NewWindowEvent() + + for _, thisHook := range hooks { + thisHook.callback(thisEvent) + if thisEvent.Cancelled { + return + } + } + + for _, listener := range w.eventListeners[id] { + go listener.callback(thisEvent) + } + w.dispatchWindowEvent(id) +} + +// Width returns the width of the window +func (w *WebviewWindow) Width() int { + if w.impl == nil { + return 0 + } + return InvokeSyncWithResult(w.impl.width) +} + +// Height returns the height of the window +func (w *WebviewWindow) Height() int { + if w.impl == nil { + return 0 + } + return InvokeSyncWithResult(w.impl.height) +} + +// RelativePosition returns the relative position of the window to the screen +func (w *WebviewWindow) RelativePosition() (int, int) { + if w.impl == nil { + return 0, 0 + } + var x, y int + InvokeSync(func() { + x, y = w.impl.relativePosition() + }) + return x, y +} + +// AbsolutePosition returns the absolute position of the window to the screen +func (w *WebviewWindow) AbsolutePosition() (int, int) { + if w.impl == nil { + return 0, 0 + } + var x, y int + InvokeSync(func() { + x, y = w.impl.absolutePosition() + }) + return x, y +} + +func (w *WebviewWindow) Destroy() { + if w.impl == nil { + return + } + + // Cancel the callbacks + for _, cancelFunc := range w.cancellers { + cancelFunc() + } + + InvokeSync(w.impl.destroy) +} + +// Reload reloads the page assets +func (w *WebviewWindow) Reload() { + if w.impl == nil { + return + } + InvokeSync(w.impl.reload) +} + +// ForceReload forces the window to reload the page assets +func (w *WebviewWindow) ForceReload() { + if w.impl == nil { + return + } + InvokeSync(w.impl.forceReload) +} + +// ToggleFullscreen toggles the window between fullscreen and normal +func (w *WebviewWindow) ToggleFullscreen() { + if w.impl == nil { + return + } + InvokeSync(func() { + if w.IsFullscreen() { + w.UnFullscreen() + } else { + w.Fullscreen() + } + }) +} + +func (w *WebviewWindow) ToggleDevTools() { + if w.impl == nil { + return + } + InvokeSync(w.impl.toggleDevTools) +} + +// ZoomReset resets the zoom level of the webview content to 100% +func (w *WebviewWindow) ZoomReset() Window { + if w.impl != nil { + InvokeSync(w.impl.zoomReset) + w.emit(events.Common.WindowZoomReset) + } + return w + +} + +// ZoomIn increases the zoom level of the webview content +func (w *WebviewWindow) ZoomIn() { + if w.impl == nil { + return + } + InvokeSync(w.impl.zoomIn) + w.emit(events.Common.WindowZoomIn) + +} + +// ZoomOut decreases the zoom level of the webview content +func (w *WebviewWindow) ZoomOut() { + if w.impl == nil { + return + } + InvokeSync(w.impl.zoomOut) + w.emit(events.Common.WindowZoomOut) +} + +// Close closes the window +func (w *WebviewWindow) Close() { + if w.impl == nil { + return + } + w.emit(events.Common.WindowClosing) +} + +func (w *WebviewWindow) Zoom() { + if w.impl == nil { + return + } + InvokeSync(w.impl.zoom) + w.emit(events.Common.WindowZoom) +} + +// SetHTML sets the HTML of the window to the given html string. +func (w *WebviewWindow) SetHTML(html string) Window { + w.options.HTML = html + if w.impl != nil { + InvokeSync(func() { + w.impl.setHTML(html) + }) + } + return w +} + +// SetRelativePosition sets the position of the window. +func (w *WebviewWindow) SetRelativePosition(x, y int) Window { + w.options.X = x + w.options.Y = y + if w.impl != nil { + InvokeSync(func() { + w.impl.setRelativePosition(x, y) + }) + } + return w +} + +// Minimise minimises the window. +func (w *WebviewWindow) Minimise() Window { + if w.impl == nil { + w.options.StartState = WindowStateMinimised + return w + } + if !w.IsMinimised() { + InvokeSync(w.impl.minimise) + w.emit(events.Common.WindowMinimise) + } + return w +} + +// Maximise maximises the window. Min/Max size constraints are disabled. +func (w *WebviewWindow) Maximise() Window { + if w.impl == nil { + w.options.StartState = WindowStateMaximised + return w + } + if !w.IsMaximised() { + w.DisableSizeConstraints() + InvokeSync(w.impl.maximise) + w.emit(events.Common.WindowMaximise) + } + return w +} + +// UnMinimise un-minimises the window. Min/Max size constraints are re-enabled. +func (w *WebviewWindow) UnMinimise() { + if w.impl == nil { + return + } + if w.IsMinimised() { + InvokeSync(w.impl.unminimise) + w.emit(events.Common.WindowUnMinimise) + } +} + +// UnMaximise un-maximises the window. +func (w *WebviewWindow) UnMaximise() { + if w.impl == nil { + return + } + if w.IsMaximised() { + w.EnableSizeConstraints() + InvokeSync(w.impl.unmaximise) + w.emit(events.Common.WindowUnMaximise) + } +} + +// UnFullscreen un-fullscreens the window. +func (w *WebviewWindow) UnFullscreen() { + if w.impl == nil { + return + } + if w.IsFullscreen() { + w.EnableSizeConstraints() + InvokeSync(w.impl.unfullscreen) + w.emit(events.Common.WindowUnFullscreen) + } +} + +// Restore restores the window to its previous state if it was previously minimised, maximised or fullscreen. +func (w *WebviewWindow) Restore() { + if w.impl == nil { + return + } + InvokeSync(func() { + if w.IsMinimised() { + w.UnMinimise() + } else if w.IsMaximised() { + w.UnMaximise() + } else if w.IsFullscreen() { + w.UnFullscreen() + } + w.emit(events.Common.WindowRestore) + }) +} + +func (w *WebviewWindow) DisableSizeConstraints() { + if w.impl == nil { + return + } + InvokeSync(func() { + if w.options.MinWidth > 0 && w.options.MinHeight > 0 { + w.impl.setMinSize(0, 0) + } + if w.options.MaxWidth > 0 && w.options.MaxHeight > 0 { + w.impl.setMaxSize(0, 0) + } + }) +} + +func (w *WebviewWindow) EnableSizeConstraints() { + if w.impl == nil { + return + } + InvokeSync(func() { + if w.options.MinWidth > 0 && w.options.MinHeight > 0 { + w.SetMinSize(w.options.MinWidth, w.options.MinHeight) + } + if w.options.MaxWidth > 0 && w.options.MaxHeight > 0 { + w.SetMaxSize(w.options.MaxWidth, w.options.MaxHeight) + } + }) +} + +// GetScreen returns the screen that the window is on +func (w *WebviewWindow) GetScreen() (*Screen, error) { + if w.impl == nil { + return nil, nil + } + return InvokeSyncWithResultAndError(w.impl.getScreen) +} + +// SetFrameless removes the window frame and title bar +func (w *WebviewWindow) SetFrameless(frameless bool) Window { + w.options.Frameless = frameless + if w.impl != nil { + InvokeSync(func() { + w.impl.setFrameless(frameless) + }) + } + return w +} + +func (w *WebviewWindow) DispatchWailsEvent(event *WailsEvent) { + msg := fmt.Sprintf("_wails.dispatchWailsEvent(%s);", event.ToJSON()) + w.ExecJS("", msg) +} + +func (w *WebviewWindow) dispatchWindowEvent(id uint) { + // TODO: Make this more efficient by keeping a list of which events have been registered + // and only dispatching those. + jsEvent := &WailsEvent{ + Name: events.JSEvent(id), + } + w.DispatchWailsEvent(jsEvent) +} + +func (w *WebviewWindow) Info(message string, args ...any) { + var messageArgs []interface{} + messageArgs = append(messageArgs, args...) + messageArgs = append(messageArgs, "sender", w.Name()) + globalApplication.info(message, messageArgs...) +} + +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...) +} + +func (w *WebviewWindow) HandleDragAndDropMessage(filenames []string) { + thisEvent := NewWindowEvent() + ctx := newWindowEventContext() + ctx.setDroppedFiles(filenames) + thisEvent.ctx = ctx + for _, listener := range w.eventListeners[uint(events.Common.WindowFilesDropped)] { + listener.callback(thisEvent) + } +} + +func (w *WebviewWindow) OpenContextMenu(data *ContextMenuData) { + menu, ok := w.contextMenus[data.Id] + if !ok { + // try application level context menu + menu, ok = globalApplication.getContextMenu(data.Id) + if !ok { + w.Error("No context menu found for id: %s", data.Id) + return + } + } + menu.setContextData(data) + if w.impl == nil { + return + } + InvokeSync(func() { + w.impl.openContextMenu(menu, data) + }) +} + +// RegisterContextMenu registers a context menu and assigns it the given name. +func (w *WebviewWindow) RegisterContextMenu(name string, menu *Menu) { + w.contextMenusLock.Lock() + defer w.contextMenusLock.Unlock() + w.contextMenus[name] = menu +} + +// NativeWindowHandle returns the platform native window handle for the window. +func (w *WebviewWindow) NativeWindowHandle() (uintptr, error) { + if w.impl == nil { + return 0, errors.New("native handle unavailable as window is not running") + } + return w.impl.nativeWindowHandle(), nil +} + +func (w *WebviewWindow) Focus() { + if w.impl == nil { + w.options.Focused = true + return + } + InvokeSync(w.impl.focus) + w.emit(events.Common.WindowFocus) +} + +func (w *WebviewWindow) emit(eventType events.WindowEventType) { + windowEvents <- &windowEvent{ + WindowID: w.id, + EventID: uint(eventType), + } +} + +func (w *WebviewWindow) startDrag() error { + if w.impl == nil { + return nil + } + return InvokeSyncWithError(w.impl.startDrag) +} + +func (w *WebviewWindow) Print() error { + if w.impl == nil { + return nil + } + return InvokeSyncWithError(w.impl.print) +} + +func (w *WebviewWindow) SetEnabled(enabled bool) { + if w.impl == nil { + return + } + InvokeSync(func() { + w.impl.setEnabled(enabled) + }) +} + +func (w *WebviewWindow) SetAbsolutePosition(x int, y int) { + // set absolute position + if w.impl == nil { + return + } + InvokeSync(func() { + w.impl.setAbsolutePosition(x, y) + }) +} + +func (w *WebviewWindow) processKeyBinding(acceleratorString string) bool { + + if w.keyBindings == nil { + return false + } + + // Check key bindings + callback, ok := w.keyBindings[acceleratorString] + if !ok { + return globalApplication.processKeyBinding(acceleratorString, w) + } + // Execute callback + go callback(w) + + return true +} + +func (w *WebviewWindow) HandleKeyEvent(acceleratorString string) { + if w.impl == nil { + return + } + InvokeSync(func() { + w.impl.handleKeyEvent(acceleratorString) + }) +} diff --git a/v3/pkg/application/webview_window_darwin.go b/v3/pkg/application/webview_window_darwin.go new file mode 100644 index 000000000..681ffbe9a --- /dev/null +++ b/v3/pkg/application/webview_window_darwin.go @@ -0,0 +1,1238 @@ +//go:build darwin + +package application + +/* +#cgo CFLAGS: -mmacosx-version-min=10.13 -x objective-c +#cgo LDFLAGS: -framework Cocoa -framework WebKit + +#include "application_darwin.h" +#include "webview_window_darwin.h" +#include +#include "Cocoa/Cocoa.h" +#import +#import +#import "webview_window_darwin_drag.h" + +struct WebviewPreferences { + bool *TabFocusesLinks; + bool *TextInteractionEnabled; + bool *FullscreenEnabled; +}; + +extern void registerListener(unsigned int event); + +// Create a new Window +void* windowNew(unsigned int id, int width, int height, bool fraudulentWebsiteWarningEnabled, bool frameless, bool enableDragAndDrop, struct WebviewPreferences preferences) { + NSWindowStyleMask styleMask = NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | NSWindowStyleMaskMiniaturizable | NSWindowStyleMaskResizable; + if (frameless) { + styleMask = NSWindowStyleMaskBorderless | NSWindowStyleMaskResizable; + } + WebviewWindow* window = [[WebviewWindow alloc] initWithContentRect:NSMakeRect(0, 0, width-1, height-1) + styleMask:styleMask + backing:NSBackingStoreBuffered + defer:NO]; + + // Create delegate + WebviewWindowDelegate* delegate = [[WebviewWindowDelegate alloc] init]; + [delegate autorelease]; + + // Set delegate + [window setDelegate:delegate]; + delegate.windowId = id; + + // Add NSView to window + NSView* view = [[NSView alloc] initWithFrame:NSMakeRect(0, 0, width-1, height-1)]; + [view autorelease]; + + [view setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable]; + if( frameless ) { + [view setWantsLayer:YES]; + view.layer.cornerRadius = 8.0; + } + [window setContentView:view]; + + // Embed wkwebview in window + NSRect frame = NSMakeRect(0, 0, width, height); + WKWebViewConfiguration* config = [[WKWebViewConfiguration alloc] init]; + [config autorelease]; + + // Set preferences + if (preferences.TabFocusesLinks != NULL) { + config.preferences.tabFocusesLinks = *preferences.TabFocusesLinks; + } + + if (@available(macOS 11.3, *)) { + if (preferences.TextInteractionEnabled != NULL) { + config.preferences.textInteractionEnabled = *preferences.TextInteractionEnabled; + } + } + + if (@available(macOS 12.3, *)) { + if (preferences.FullscreenEnabled != NULL) { + config.preferences.elementFullscreenEnabled = *preferences.FullscreenEnabled; + } + } + + + + config.suppressesIncrementalRendering = true; + config.applicationNameForUserAgent = @"wails.io"; + [config setURLSchemeHandler:delegate forURLScheme:@"wails"]; + if (@available(macOS 10.15, *)) { + config.preferences.fraudulentWebsiteWarningEnabled = fraudulentWebsiteWarningEnabled; + } + + // Setup user content controller + WKUserContentController* userContentController = [WKUserContentController new]; + [userContentController autorelease]; + + [userContentController addScriptMessageHandler:delegate name:@"external"]; + config.userContentController = userContentController; + + WKWebView* webView = [[WKWebView alloc] initWithFrame:frame configuration:config]; + [webView autorelease]; + + [view addSubview:webView]; + + // support webview events + [webView setNavigationDelegate:delegate]; + + // Ensure webview resizes with the window + [webView setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable]; + + if( enableDragAndDrop ) { + WebviewDrag* dragView = [[WebviewDrag alloc] initWithFrame:NSMakeRect(0, 0, width-1, height-1)]; + [dragView autorelease]; + + [view setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable]; + [view addSubview:dragView]; + dragView.windowId = id; + } + + window.webView = webView; + return window; +} + + +void printWindowStyle(void *window) { + WebviewWindow* nsWindow = (WebviewWindow*)window; + NSWindowStyleMask styleMask = [nsWindow styleMask]; + // Get delegate + WebviewWindowDelegate* windowDelegate = (WebviewWindowDelegate*)[nsWindow delegate]; + + printf("Window %d style mask: ", windowDelegate.windowId); + + if (styleMask & NSWindowStyleMaskTitled) + { + printf("NSWindowStyleMaskTitled "); + } + + if (styleMask & NSWindowStyleMaskClosable) + { + printf("NSWindowStyleMaskClosable "); + } + + if (styleMask & NSWindowStyleMaskMiniaturizable) + { + printf("NSWindowStyleMaskMiniaturizable "); + } + + if (styleMask & NSWindowStyleMaskResizable) + { + printf("NSWindowStyleMaskResizable "); + } + + if (styleMask & NSWindowStyleMaskFullSizeContentView) + { + printf("NSWindowStyleMaskFullSizeContentView "); + } + + if (styleMask & NSWindowStyleMaskNonactivatingPanel) + { + printf("NSWindowStyleMaskNonactivatingPanel "); + } + + if (styleMask & NSWindowStyleMaskFullScreen) + { + printf("NSWindowStyleMaskFullScreen "); + } + + if (styleMask & NSWindowStyleMaskBorderless) + { + printf("MSWindowStyleMaskBorderless "); + } + + printf("\n"); +} + + +// setInvisibleTitleBarHeight sets the invisible title bar height +void setInvisibleTitleBarHeight(void* window, unsigned int height) { + WebviewWindow* nsWindow = (WebviewWindow*)window; + // Get delegate + WebviewWindowDelegate* delegate = (WebviewWindowDelegate*)[nsWindow delegate]; + // Set height + delegate.invisibleTitleBarHeight = height; +} + +// Make NSWindow transparent +void windowSetTransparent(void* nsWindow) { + [(WebviewWindow*)nsWindow setOpaque:NO]; + [(WebviewWindow*)nsWindow setBackgroundColor:[NSColor clearColor]]; +} + +void windowSetInvisibleTitleBar(void* nsWindow, unsigned int height) { + WebviewWindow* window = (WebviewWindow*)nsWindow; + WebviewWindowDelegate* delegate = (WebviewWindowDelegate*)[window delegate]; + delegate.invisibleTitleBarHeight = height; +} + + +// Set the title of the NSWindow +void windowSetTitle(void* nsWindow, char* title) { + NSString* nsTitle = [NSString stringWithUTF8String:title]; + [(WebviewWindow*)nsWindow setTitle:nsTitle]; + free(title); +} + +// Set the size of the NSWindow +void windowSetSize(void* nsWindow, int width, int height) { + // Set window size on main thread + WebviewWindow* window = (WebviewWindow*)nsWindow; + NSSize contentSize = [window contentRectForFrameRect:NSMakeRect(0, 0, width, height)].size; + [window setContentSize:contentSize]; + [window setFrame:NSMakeRect(window.frame.origin.x, window.frame.origin.y, width, height) display:YES animate:YES]; +} + +// Set NSWindow always on top +void windowSetAlwaysOnTop(void* nsWindow, bool alwaysOnTop) { + // Set window always on top on main thread + [(WebviewWindow*)nsWindow setLevel:alwaysOnTop ? NSFloatingWindowLevel : NSNormalWindowLevel]; +} + +// Load URL in NSWindow +void navigationLoadURL(void* nsWindow, char* url) { + // Load URL on main thread + NSURL* nsURL = [NSURL URLWithString:[NSString stringWithUTF8String:url]]; + NSURLRequest* request = [NSURLRequest requestWithURL:nsURL]; + WebviewWindow* window = (WebviewWindow*)nsWindow; + [window.webView loadRequest:request]; + free(url); +} + +// Set NSWindow resizable +void windowSetResizable(void* nsWindow, bool resizable) { + // Set window resizable on main thread + WebviewWindow* window = (WebviewWindow*)nsWindow; + if (resizable) { + NSWindowStyleMask styleMask = [window styleMask] | NSWindowStyleMaskResizable; + [window setStyleMask:styleMask]; + } else { + NSWindowStyleMask styleMask = [window styleMask] & ~NSWindowStyleMaskResizable; + [window setStyleMask:styleMask]; + } +} + +// Set NSWindow min size +void windowSetMinSize(void* nsWindow, int width, int height) { + // Set window min size on main thread + WebviewWindow* window = (WebviewWindow*)nsWindow; + NSSize contentSize = [window contentRectForFrameRect:NSMakeRect(0, 0, width, height)].size; + [window setContentMinSize:contentSize]; + NSSize size = { width, height }; + [window setMinSize:size]; +} + +// Set NSWindow max size +void windowSetMaxSize(void* nsWindow, int width, int height) { + // Set window max size on main thread + NSSize size = { FLT_MAX, FLT_MAX }; + size.width = width > 0 ? width : FLT_MAX; + size.height = height > 0 ? height : FLT_MAX; + WebviewWindow* window = (WebviewWindow*)nsWindow; + NSSize contentSize = [window contentRectForFrameRect:NSMakeRect(0, 0, size.width, size.height)].size; + [window setContentMaxSize:contentSize]; + [window setMaxSize:size]; +} + +// Enable NSWindow devtools +void windowEnableDevTools(void* nsWindow) { + WebviewWindow* window = (WebviewWindow*)nsWindow; + // Enable devtools in webview + [window.webView.configuration.preferences setValue:@YES forKey:@"developerExtrasEnabled"]; +} + +// windowZoomReset +void windowZoomReset(void* nsWindow) { + WebviewWindow* window = (WebviewWindow*)nsWindow; + [window.webView setMagnification:1.0]; +} + +// windowZoomSet +void windowZoomSet(void* nsWindow, double zoom) { + WebviewWindow* window = (WebviewWindow*)nsWindow; + // Reset zoom + [window.webView setMagnification:zoom]; +} + +// windowZoomGet +float windowZoomGet(void* nsWindow) { + WebviewWindow* window = (WebviewWindow*)nsWindow; + // Get zoom + return [window.webView magnification]; +} + +// windowZoomIn +void windowZoomIn(void* nsWindow) { + WebviewWindow* window = (WebviewWindow*)nsWindow; + // Zoom in + [window.webView setMagnification:window.webView.magnification + 0.05]; +} + +// windowZoomOut +void windowZoomOut(void* nsWindow) { + WebviewWindow* window = (WebviewWindow*)nsWindow; + // Zoom out + if( window.webView.magnification > 1.05 ) { + [window.webView setMagnification:window.webView.magnification - 0.05]; + } else { + [window.webView setMagnification:1.0]; + } +} + +// set the window position relative to the screen +void windowSetRelativePosition(void* nsWindow, int x, int y) { + WebviewWindow* window = (WebviewWindow*)nsWindow; + NSScreen* screen = [window screen]; + if( screen == NULL ) { + screen = [NSScreen mainScreen]; + } + NSRect windowFrame = [window frame]; + NSRect screenFrame = [screen frame]; + windowFrame.origin.x = screenFrame.origin.x + (float)x; + windowFrame.origin.y = (screenFrame.origin.y + screenFrame.size.height) - windowFrame.size.height - (float)y; + + [window setFrame:windowFrame display:TRUE animate:FALSE]; +} + +// Execute JS in NSWindow +void windowExecJS(void* nsWindow, const char* js) { + WebviewWindow* window = (WebviewWindow*)nsWindow; + [window.webView evaluateJavaScript:[NSString stringWithUTF8String:js] completionHandler:nil]; + free((void*)js); +} + +// Make NSWindow backdrop translucent +void windowSetTranslucent(void* nsWindow) { + // Get window + WebviewWindow* window = (WebviewWindow*)nsWindow; + + id contentView = [window contentView]; + NSVisualEffectView *effectView = [NSVisualEffectView alloc]; + NSRect bounds = [contentView bounds]; + [effectView initWithFrame:bounds]; + [effectView setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable]; + [effectView setBlendingMode:NSVisualEffectBlendingModeBehindWindow]; + [effectView setState:NSVisualEffectStateActive]; + [contentView addSubview:effectView positioned:NSWindowBelow relativeTo:nil]; +} + +// Make webview background transparent +void webviewSetTransparent(void* nsWindow) { + WebviewWindow* window = (WebviewWindow*)nsWindow; + // Set webview background transparent + [window.webView setValue:@NO forKey:@"drawsBackground"]; +} + +// Set webview background colour +void webviewSetBackgroundColour(void* nsWindow, int r, int g, int b, int alpha) { + WebviewWindow* window = (WebviewWindow*)nsWindow; + // Set webview background color + [window.webView setValue:[NSColor colorWithRed:r/255.0 green:g/255.0 blue:b/255.0 alpha:alpha/255.0] forKey:@"backgroundColor"]; +} + +// Set the window background colour +void windowSetBackgroundColour(void* nsWindow, int r, int g, int b, int alpha) { + [(WebviewWindow*)nsWindow setBackgroundColor:[NSColor colorWithRed:r/255.0 green:g/255.0 blue:b/255.0 alpha:alpha/255.0]]; +} + +bool windowIsMaximised(void* nsWindow) { + return [(WebviewWindow*)nsWindow isZoomed]; +} + +bool windowIsFullscreen(void* nsWindow) { + return [(WebviewWindow*)nsWindow styleMask] & NSWindowStyleMaskFullScreen; +} + +bool windowIsMinimised(void* nsWindow) { + return [(WebviewWindow*)nsWindow isMiniaturized]; +} + +bool windowIsFocused(void* nsWindow) { + return [(WebviewWindow*)nsWindow isKeyWindow]; +} + +// Set Window fullscreen +void windowFullscreen(void* nsWindow) { + if( windowIsFullscreen(nsWindow) ) { + return; + } + dispatch_async(dispatch_get_main_queue(), ^{ + [(WebviewWindow*)nsWindow toggleFullScreen:nil]; + });} + +void windowUnFullscreen(void* nsWindow) { + if( !windowIsFullscreen(nsWindow) ) { + return; + } + dispatch_async(dispatch_get_main_queue(), ^{ + [(WebviewWindow*)nsWindow toggleFullScreen:nil]; + }); +} + +// restore window to normal size +void windowRestore(void* nsWindow) { + // If window is fullscreen + if([(WebviewWindow*)nsWindow styleMask] & NSWindowStyleMaskFullScreen) { + [(WebviewWindow*)nsWindow toggleFullScreen:nil]; + } + // If window is maximised + if([(WebviewWindow*)nsWindow isZoomed]) { + [(WebviewWindow*)nsWindow zoom:nil]; + } + // If window in minimised + if([(WebviewWindow*)nsWindow isMiniaturized]) { + [(WebviewWindow*)nsWindow deminiaturize:nil]; + } +} + +// disable window fullscreen button +void setFullscreenButtonEnabled(void* nsWindow, bool enabled) { + NSButton *fullscreenButton = [(WebviewWindow*)nsWindow standardWindowButton:NSWindowZoomButton]; + fullscreenButton.enabled = enabled; +} + +// Set the titlebar style +void windowSetTitleBarAppearsTransparent(void* nsWindow, bool transparent) { + if( transparent ) { + [(WebviewWindow*)nsWindow setTitlebarAppearsTransparent:true]; + } else { + [(WebviewWindow*)nsWindow setTitlebarAppearsTransparent:false]; + } +} + +// Set window fullsize content view +void windowSetFullSizeContent(void* nsWindow, bool fullSize) { + if( fullSize ) { + [(WebviewWindow*)nsWindow setStyleMask:[(WebviewWindow*)nsWindow styleMask] | NSWindowStyleMaskFullSizeContentView]; + } else { + [(WebviewWindow*)nsWindow setStyleMask:[(WebviewWindow*)nsWindow styleMask] & ~NSWindowStyleMaskFullSizeContentView]; + } +} + +// Set Hide Titlebar +void windowSetHideTitleBar(void* nsWindow, bool hideTitlebar) { + if( hideTitlebar ) { + [(WebviewWindow*)nsWindow setStyleMask:[(WebviewWindow*)nsWindow styleMask] & ~NSWindowStyleMaskTitled]; + } else { + [(WebviewWindow*)nsWindow setStyleMask:[(WebviewWindow*)nsWindow styleMask] | NSWindowStyleMaskTitled]; + } +} + +// Set Hide Title in Titlebar +void windowSetHideTitle(void* nsWindow, bool hideTitle) { + if( hideTitle ) { + [(WebviewWindow*)nsWindow setTitleVisibility:NSWindowTitleHidden]; + } else { + [(WebviewWindow*)nsWindow setTitleVisibility:NSWindowTitleVisible]; + } +} + +// Set Window use toolbar +void windowSetUseToolbar(void* nsWindow, bool useToolbar, int toolbarStyle) { + WebviewWindow* window = (WebviewWindow*)nsWindow; + if( useToolbar ) { + NSToolbar *toolbar = [[NSToolbar alloc] initWithIdentifier:@"wails.toolbar"]; + [toolbar autorelease]; + [window setToolbar:toolbar]; + + // If macos 11 or higher, set toolbar style + if (@available(macOS 11.0, *)) { + [window setToolbarStyle:toolbarStyle]; + } + + } else { + [window setToolbar:nil]; + } +} + +// Set window toolbar style +void windowSetToolbarStyle(void* nsWindow, int style) { + // use @available to check if the function is available + // if not, return + if (@available(macOS 11.0, *)) { + NSToolbar* toolbar = [(WebviewWindow*)nsWindow toolbar]; + [toolbar setShowsBaselineSeparator:style]; + } +} + +// Set Hide Toolbar Separator +void windowSetHideToolbarSeparator(void* nsWindow, bool hideSeparator) { + NSToolbar* toolbar = [(WebviewWindow*)nsWindow toolbar]; + if( toolbar == nil ) { + return; + } + [toolbar setShowsBaselineSeparator:!hideSeparator]; +} + +// Set Window appearance type +void windowSetAppearanceTypeByName(void* nsWindow, const char *appearanceName) { + // set window appearance type by name + // Convert appearance name to NSString + NSString* appearanceNameString = [NSString stringWithUTF8String:appearanceName]; + // Set appearance + [(WebviewWindow*)nsWindow setAppearance:[NSAppearance appearanceNamed:appearanceNameString]]; + + free((void*)appearanceName); +} + +// Center window on current monitor +void windowCenter(void* nsWindow) { + [(WebviewWindow*)nsWindow center]; +} + +// Get the current size of the window +void windowGetSize(void* nsWindow, int* width, int* height) { + NSRect frame = [(WebviewWindow*)nsWindow frame]; + *width = frame.size.width; + *height = frame.size.height; +} + +// Get window width +int windowGetWidth(void* nsWindow) { + return [(WebviewWindow*)nsWindow frame].size.width; +} + +// Get window height +int windowGetHeight(void* nsWindow) { + return [(WebviewWindow*)nsWindow frame].size.height; +} + +// Get window position +void windowGetRelativePosition(void* nsWindow, int* x, int* y) { + WebviewWindow* window = (WebviewWindow*)nsWindow; + NSRect frame = [window frame]; + *x = frame.origin.x; + + // Translate to screen coordinates so Y=0 is the top of the screen + NSScreen* screen = [window screen]; + if( screen == NULL ) { + screen = [NSScreen mainScreen]; + } + NSRect screenFrame = [screen frame]; + *y = screenFrame.size.height - frame.origin.y - frame.size.height; +} + +// Get absolute window position +void windowGetAbsolutePosition(void* nsWindow, int* x, int* y) { + NSRect frame = [(WebviewWindow*)nsWindow frame]; + *x = frame.origin.x; + *y = frame.origin.y; +} + +void windowSetAbsolutePosition(void* nsWindow, int x, int y) { + NSRect frame = [(WebviewWindow*)nsWindow frame]; + frame.origin.x = x; + frame.origin.y = y; + [(WebviewWindow*)nsWindow setFrame:frame display:YES]; +} + +// Destroy window +void windowDestroy(void* nsWindow) { + [(WebviewWindow*)nsWindow close]; +} + +// Remove drop shadow from window +void windowSetShadow(void* nsWindow, bool hasShadow) { + [(WebviewWindow*)nsWindow setHasShadow:hasShadow]; +} + + +// windowClose closes the current window +static void windowClose(void *window) { + [(WebviewWindow*)window close]; +} + +// windowZoom +static void windowZoom(void *window) { + [(WebviewWindow*)window zoom:nil]; +} + +// webviewRenderHTML renders the given HTML +static void windowRenderHTML(void *window, const char *html) { + WebviewWindow* nsWindow = (WebviewWindow*)window; + // get window delegate + WebviewWindowDelegate* windowDelegate = (WebviewWindowDelegate*)[nsWindow delegate]; + // render html + [nsWindow.webView loadHTMLString:[NSString stringWithUTF8String:html] baseURL:nil]; +} + +static void windowInjectCSS(void *window, const char *css) { + WebviewWindow* nsWindow = (WebviewWindow*)window; + // inject css + [nsWindow.webView evaluateJavaScript:[NSString stringWithFormat:@"(function() { var style = document.createElement('style'); style.appendChild(document.createTextNode('%@')); document.head.appendChild(style); })();", [NSString stringWithUTF8String:css]] completionHandler:nil]; + free((void*)css); +} + +static void windowMinimise(void *window) { + [(WebviewWindow*)window miniaturize:nil]; +} + +// zoom maximizes the window to the screen dimensions +static void windowMaximise(void *window) { + [(WebviewWindow*)window zoom:nil]; +} + +static bool isFullScreen(void *window) { + WebviewWindow* nsWindow = (WebviewWindow*)window; + long mask = [nsWindow styleMask]; + return (mask & NSWindowStyleMaskFullScreen) == NSWindowStyleMaskFullScreen; +} + +static bool isVisible(void *window) { + WebviewWindow* nsWindow = (WebviewWindow*)window; + return (nsWindow.occlusionState & NSWindowOcclusionStateVisible) == NSWindowOcclusionStateVisible; +} + +// windowSetFullScreen +static void windowSetFullScreen(void *window, bool fullscreen) { + if (isFullScreen(window)) { + return; + } + WebviewWindow* nsWindow = (WebviewWindow*)window; + windowSetMaxSize(nsWindow, 0, 0); + windowSetMinSize(nsWindow, 0, 0); + [nsWindow toggleFullScreen:nil]; +} + +// windowUnminimise +static void windowUnminimise(void *window) { + [(WebviewWindow*)window deminiaturize:nil]; +} + +// windowUnmaximise +static void windowUnmaximise(void *window) { + [(WebviewWindow*)window zoom:nil]; +} + +static void windowDisableSizeConstraints(void *window) { + WebviewWindow* nsWindow = (WebviewWindow*)window; + // disable size constraints + [nsWindow setContentMinSize:CGSizeZero]; + [nsWindow setContentMaxSize:CGSizeZero]; +} + +static void windowShow(void *window) { + [(WebviewWindow*)window makeKeyAndOrderFront:nil]; +} + +static void windowHide(void *window) { + [(WebviewWindow*)window orderOut:nil]; +} + +// windowShowMenu opens an NSMenu at the given coordinates +static void windowShowMenu(void *window, void *menu, int x, int y) { + NSMenu* nsMenu = (NSMenu*)menu; + WKWebView* webView = ((WebviewWindow*)window).webView; + NSPoint point = NSMakePoint(x, y); + [nsMenu popUpMenuPositioningItem:nil atLocation:point inView:webView]; +} + +// windowIgnoreMouseEvents makes the window ignore mouse events +static void windowIgnoreMouseEvents(void *window, bool ignore) { + WebviewWindow* nsWindow = (WebviewWindow*)window; + [nsWindow setIgnoresMouseEvents:ignore]; +} + +// Make the given window frameless +static void windowSetFrameless(void *window, bool frameless) { + WebviewWindow* nsWindow = (WebviewWindow*)window; + // set the window style to be frameless + if (frameless) { + [nsWindow setStyleMask:([nsWindow styleMask] | NSWindowStyleMaskFullSizeContentView)]; + } else { + [nsWindow setStyleMask:([nsWindow styleMask] & ~NSWindowStyleMaskFullSizeContentView)]; + } +} + +static void startDrag(void *window) { + WebviewWindow* nsWindow = (WebviewWindow*)window; + + // Get delegate + WebviewWindowDelegate* windowDelegate = (WebviewWindowDelegate*)[nsWindow delegate]; + + // start drag + [windowDelegate startDrag:nsWindow]; +} + +// Credit: https://stackoverflow.com/q/33319295 +static void windowPrint(void *window) { + + // Check if macOS 11.0 or newer + if (@available(macOS 11.0, *)) { + WebviewWindow* nsWindow = (WebviewWindow*)window; + WebviewWindowDelegate* windowDelegate = (WebviewWindowDelegate*)[nsWindow delegate]; + WKWebView* webView = nsWindow.webView; + + // TODO: Think about whether to expose this as config + NSPrintInfo *pInfo = [NSPrintInfo sharedPrintInfo]; + pInfo.horizontalPagination = NSPrintingPaginationModeAutomatic; + pInfo.verticalPagination = NSPrintingPaginationModeAutomatic; + pInfo.verticallyCentered = YES; + pInfo.horizontallyCentered = YES; + pInfo.orientation = NSPaperOrientationLandscape; + pInfo.leftMargin = 30; + pInfo.rightMargin = 30; + pInfo.topMargin = 30; + pInfo.bottomMargin = 30; + + NSPrintOperation *po = [webView printOperationWithPrintInfo:pInfo]; + po.showsPrintPanel = YES; + po.showsProgressPanel = YES; + + // Without the next line you get an exception. Also it seems to + // completely ignore the values in the rect. I tried changing them + // in both x and y direction to include content scrolled off screen. + // It had no effect whatsoever in either direction. + po.view.frame = webView.bounds; + + // [printOperation runOperation] DOES NOT WORK WITH WKWEBVIEW, use + [po runOperationModalForWindow:window delegate:windowDelegate didRunSelector:nil contextInfo:nil]; + } +} + +void setWindowEnabled(void *window, bool enabled) { + WebviewWindow* nsWindow = (WebviewWindow*)window; + [nsWindow setIgnoresMouseEvents:!enabled]; +} + +void windowSetEnabled(void *window, bool enabled) { + // TODO: Implement +} + +void windowFocus(void *window) { + WebviewWindow* nsWindow = (WebviewWindow*)window; + // If the current application is not active, activate it + if (![[NSApplication sharedApplication] isActive]) { + [[NSApplication sharedApplication] activateIgnoringOtherApps:YES]; + } + [nsWindow makeKeyAndOrderFront:nil]; + [nsWindow makeKeyWindow]; +} + +*/ +import "C" +import ( + "net/url" + "sync" + "unsafe" + + "github.com/wailsapp/wails/v3/pkg/events" +) + +var showDevTools = func(window unsafe.Pointer) {} + +type macosWebviewWindow struct { + nsWindow unsafe.Pointer + parent *WebviewWindow +} + +func (w *macosWebviewWindow) handleKeyEvent(acceleratorString string) { + // Parse acceleratorString + accelerator, err := parseAccelerator(acceleratorString) + if err != nil { + globalApplication.error("unable to parse accelerator: %s", err.Error()) + return + } + w.parent.processKeyBinding(accelerator.String()) +} + +func (w *macosWebviewWindow) isFocused() bool { + return bool(C.windowIsFocused(w.nsWindow)) +} + +func (w *macosWebviewWindow) setAbsolutePosition(x int, y int) { + C.windowSetAbsolutePosition(w.nsWindow, C.int(x), C.int(y)) +} + +func (w *macosWebviewWindow) print() error { + C.windowPrint(w.nsWindow) + return nil +} + +func (w *macosWebviewWindow) startResize(_ string) error { + // Never called. Handled natively by the OS. + return nil +} + +func (w *macosWebviewWindow) focus() { + // Make the window key and main + C.windowFocus(w.nsWindow) +} + +func (w *macosWebviewWindow) openContextMenu(menu *Menu, data *ContextMenuData) { + // Create the menu + thisMenu := newMenuImpl(menu) + thisMenu.update() + C.windowShowMenu(w.nsWindow, thisMenu.nsMenu, C.int(data.X), C.int(data.Y)) +} + +func (w *macosWebviewWindow) getZoom() float64 { + return float64(C.windowZoomGet(w.nsWindow)) +} + +func (w *macosWebviewWindow) setZoom(zoom float64) { + C.windowZoomSet(w.nsWindow, C.double(zoom)) +} + +func (w *macosWebviewWindow) setFrameless(frameless bool) { + C.windowSetFrameless(w.nsWindow, C.bool(frameless)) + if frameless { + C.windowSetTitleBarAppearsTransparent(w.nsWindow, C.bool(true)) + C.windowSetHideTitle(w.nsWindow, C.bool(true)) + } else { + macOptions := w.parent.options.Mac + appearsTransparent := macOptions.TitleBar.AppearsTransparent + hideTitle := macOptions.TitleBar.HideTitle + C.windowSetTitleBarAppearsTransparent(w.nsWindow, C.bool(appearsTransparent)) + C.windowSetHideTitle(w.nsWindow, C.bool(hideTitle)) + } +} + +func (w *macosWebviewWindow) setHasShadow(hasShadow bool) { + C.windowSetShadow(w.nsWindow, C.bool(hasShadow)) +} + +func (w *macosWebviewWindow) getScreen() (*Screen, error) { + return getScreenForWindow(w) +} + +func (w *macosWebviewWindow) show() { + C.windowShow(w.nsWindow) +} + +func (w *macosWebviewWindow) hide() { + C.windowHide(w.nsWindow) +} + +func (w *macosWebviewWindow) setFullscreenButtonEnabled(enabled bool) { + C.setFullscreenButtonEnabled(w.nsWindow, C.bool(enabled)) +} + +func (w *macosWebviewWindow) disableSizeConstraints() { + C.windowDisableSizeConstraints(w.nsWindow) +} + +func (w *macosWebviewWindow) unfullscreen() { + C.windowUnFullscreen(w.nsWindow) +} + +func (w *macosWebviewWindow) fullscreen() { + C.windowFullscreen(w.nsWindow) +} + +func (w *macosWebviewWindow) unminimise() { + C.windowUnminimise(w.nsWindow) +} + +func (w *macosWebviewWindow) unmaximise() { + C.windowUnmaximise(w.nsWindow) +} + +func (w *macosWebviewWindow) maximise() { + C.windowMaximise(w.nsWindow) +} + +func (w *macosWebviewWindow) minimise() { + C.windowMinimise(w.nsWindow) +} + +func (w *macosWebviewWindow) on(eventID uint) { + //C.registerListener(C.uint(eventID)) +} + +func (w *macosWebviewWindow) zoom() { + C.windowZoom(w.nsWindow) +} + +func (w *macosWebviewWindow) windowZoom() { + C.windowZoom(w.nsWindow) +} + +func (w *macosWebviewWindow) close() { + C.windowClose(w.nsWindow) +} + +func (w *macosWebviewWindow) zoomIn() { + C.windowZoomIn(w.nsWindow) +} + +func (w *macosWebviewWindow) zoomOut() { + C.windowZoomOut(w.nsWindow) +} + +func (w *macosWebviewWindow) zoomReset() { + C.windowZoomReset(w.nsWindow) +} + +func (w *macosWebviewWindow) toggleDevTools() { + showDevTools(w.nsWindow) +} + +func (w *macosWebviewWindow) reload() { + //TODO: Implement + globalApplication.debug("reload called on WebviewWindow", "parentID", w.parent.id) +} + +func (w *macosWebviewWindow) forceReload() { + //TODO: Implement + globalApplication.debug("force reload called on WebviewWindow", "parentID", w.parent.id) +} + +func (w *macosWebviewWindow) center() { + C.windowCenter(w.nsWindow) +} + +func (w *macosWebviewWindow) isMinimised() bool { + return w.syncMainThreadReturningBool(func() bool { + return bool(C.windowIsMinimised(w.nsWindow)) + }) +} + +func (w *macosWebviewWindow) isMaximised() bool { + return w.syncMainThreadReturningBool(func() bool { + return bool(C.windowIsMaximised(w.nsWindow)) + }) +} + +func (w *macosWebviewWindow) isFullscreen() bool { + return w.syncMainThreadReturningBool(func() bool { + return bool(C.windowIsFullscreen(w.nsWindow)) + }) +} + +func (w *macosWebviewWindow) isNormal() bool { + return !w.isMinimised() && !w.isMaximised() && !w.isFullscreen() +} + +func (w *macosWebviewWindow) isVisible() bool { + return bool(C.isVisible(w.nsWindow)) +} + +func (w *macosWebviewWindow) syncMainThreadReturningBool(fn func() bool) bool { + var wg sync.WaitGroup + wg.Add(1) + var result bool + globalApplication.dispatchOnMainThread(func() { + result = fn() + wg.Done() + }) + wg.Wait() + return result +} + +func (w *macosWebviewWindow) restore() { + // restore window to normal size + C.windowRestore(w.nsWindow) +} + +func (w *macosWebviewWindow) restoreWindow() { + C.windowRestore(w.nsWindow) +} + +func (w *macosWebviewWindow) setEnabled(enabled bool) { + C.windowSetEnabled(w.nsWindow, C.bool(enabled)) +} + +func (w *macosWebviewWindow) execJS(js string) { + InvokeAsync(func() { + C.windowExecJS(w.nsWindow, C.CString(js)) + }) +} + +func (w *macosWebviewWindow) setURL(uri string) { + if uri != "" { + parsedURL, err := url.Parse(uri) + if err == nil && parsedURL.Scheme == "" && parsedURL.Host == "" { + // TODO handle this in a central location, the scheme and host might be platform dependant. + parsedURL.Scheme = "wails" + parsedURL.Host = "wails" + uri = parsedURL.String() + } + } + C.navigationLoadURL(w.nsWindow, C.CString(uri)) +} + +func (w *macosWebviewWindow) setAlwaysOnTop(alwaysOnTop bool) { + C.windowSetAlwaysOnTop(w.nsWindow, C.bool(alwaysOnTop)) +} + +func newWindowImpl(parent *WebviewWindow) *macosWebviewWindow { + result := &macosWebviewWindow{ + parent: parent, + } + return result +} + +func (w *macosWebviewWindow) setTitle(title string) { + if !w.parent.options.Frameless { + cTitle := C.CString(title) + C.windowSetTitle(w.nsWindow, cTitle) + } +} + +func (w *macosWebviewWindow) flash(_ bool) { + // Not supported on macOS +} + +func (w *macosWebviewWindow) setSize(width, height int) { + C.windowSetSize(w.nsWindow, C.int(width), C.int(height)) +} + +func (w *macosWebviewWindow) setMinSize(width, height int) { + C.windowSetMinSize(w.nsWindow, C.int(width), C.int(height)) +} +func (w *macosWebviewWindow) setMaxSize(width, height int) { + C.windowSetMaxSize(w.nsWindow, C.int(width), C.int(height)) +} + +func (w *macosWebviewWindow) setResizable(resizable bool) { + C.windowSetResizable(w.nsWindow, C.bool(resizable)) +} +func (w *macosWebviewWindow) enableDevTools() { + C.windowEnableDevTools(w.nsWindow) +} + +func (w *macosWebviewWindow) size() (int, int) { + var width, height C.int + var wg sync.WaitGroup + wg.Add(1) + globalApplication.dispatchOnMainThread(func() { + C.windowGetSize(w.nsWindow, &width, &height) + wg.Done() + }) + wg.Wait() + return int(width), int(height) +} + +func (w *macosWebviewWindow) setRelativePosition(x, y int) { + C.windowSetRelativePosition(w.nsWindow, C.int(x), C.int(y)) +} + +func (w *macosWebviewWindow) width() int { + var width C.int + var wg sync.WaitGroup + wg.Add(1) + globalApplication.dispatchOnMainThread(func() { + width = C.windowGetWidth(w.nsWindow) + wg.Done() + }) + wg.Wait() + return int(width) +} +func (w *macosWebviewWindow) height() int { + var height C.int + var wg sync.WaitGroup + wg.Add(1) + globalApplication.dispatchOnMainThread(func() { + height = C.windowGetHeight(w.nsWindow) + wg.Done() + }) + wg.Wait() + return int(height) +} + +func bool2CboolPtr(value bool) *C.bool { + v := C.bool(value) + return &v +} + +func (w *macosWebviewWindow) getWebviewPreferences() C.struct_WebviewPreferences { + wvprefs := w.parent.options.Mac.WebviewPreferences + + var result C.struct_WebviewPreferences + + if wvprefs.TextInteractionEnabled.IsSet() { + result.TextInteractionEnabled = bool2CboolPtr(wvprefs.TextInteractionEnabled.Get()) + } + if wvprefs.TabFocusesLinks.IsSet() { + result.TabFocusesLinks = bool2CboolPtr(wvprefs.TabFocusesLinks.Get()) + } + if wvprefs.FullscreenEnabled.IsSet() { + result.FullscreenEnabled = bool2CboolPtr(wvprefs.FullscreenEnabled.Get()) + } + + return result +} + +func (w *macosWebviewWindow) run() { + for eventId := range w.parent.eventListeners { + w.on(eventId) + } + globalApplication.dispatchOnMainThread(func() { + options := w.parent.options + macOptions := options.Mac + + w.nsWindow = C.windowNew(C.uint(w.parent.id), + C.int(options.Width), + C.int(options.Height), + C.bool(macOptions.EnableFraudulentWebsiteWarnings), + C.bool(options.Frameless), + C.bool(options.EnableDragAndDrop), + w.getWebviewPreferences(), + ) + w.setTitle(options.Title) + w.setAlwaysOnTop(options.AlwaysOnTop) + w.setResizable(!options.DisableResize) + if options.MinWidth != 0 || options.MinHeight != 0 { + w.setMinSize(options.MinWidth, options.MinHeight) + } + if options.MaxWidth != 0 || options.MaxHeight != 0 { + w.setMaxSize(options.MaxWidth, options.MaxHeight) + } + //w.setZoom(options.Zoom) + if globalApplication.isDebugMode || options.DevToolsEnabled { + w.enableDevTools() + } + w.setBackgroundColour(options.BackgroundColour) + + switch macOptions.Backdrop { + case MacBackdropTransparent: + C.windowSetTransparent(w.nsWindow) + C.webviewSetTransparent(w.nsWindow) + case MacBackdropTranslucent: + C.windowSetTranslucent(w.nsWindow) + C.webviewSetTransparent(w.nsWindow) + } + + if options.IgnoreMouseEvents { + C.windowIgnoreMouseEvents(w.nsWindow, C.bool(true)) + } + + titleBarOptions := macOptions.TitleBar + if !w.parent.options.Frameless { + C.windowSetTitleBarAppearsTransparent(w.nsWindow, C.bool(titleBarOptions.AppearsTransparent)) + C.windowSetHideTitleBar(w.nsWindow, C.bool(titleBarOptions.Hide)) + C.windowSetHideTitle(w.nsWindow, C.bool(titleBarOptions.HideTitle)) + C.windowSetFullSizeContent(w.nsWindow, C.bool(titleBarOptions.FullSizeContent)) + if titleBarOptions.UseToolbar { + C.windowSetUseToolbar(w.nsWindow, C.bool(titleBarOptions.UseToolbar), C.int(titleBarOptions.ToolbarStyle)) + } + C.windowSetHideToolbarSeparator(w.nsWindow, C.bool(titleBarOptions.HideToolbarSeparator)) + } + if macOptions.Appearance != "" { + C.windowSetAppearanceTypeByName(w.nsWindow, C.CString(string(macOptions.Appearance))) + } + + if macOptions.InvisibleTitleBarHeight != 0 { + C.windowSetInvisibleTitleBar(w.nsWindow, C.uint(macOptions.InvisibleTitleBarHeight)) + } + + switch w.parent.options.StartState { + case WindowStateMaximised: + w.maximise() + case WindowStateMinimised: + w.minimise() + case WindowStateFullscreen: + w.fullscreen() + + } + C.windowCenter(w.nsWindow) + + if options.URL != "" { + w.setURL(options.URL) + } + // We need to wait for the HTML to load before we can execute the javascript + w.parent.On(events.Mac.WebViewDidFinishNavigation, func(_ *WindowEvent) { + if options.JS != "" { + w.execJS(options.JS) + } + if options.CSS != "" { + C.windowInjectCSS(w.nsWindow, C.CString(options.CSS)) + } + }) + + // Translate ShouldClose to common WindowClosing event + w.parent.On(events.Mac.WindowShouldClose, func(_ *WindowEvent) { + w.parent.emit(events.Common.WindowClosing) + }) + + // Translate WindowDidResignKey to common WindowLostFocus event + w.parent.On(events.Mac.WindowDidResignKey, func(_ *WindowEvent) { + w.parent.emit(events.Common.WindowLostFocus) + }) + w.parent.On(events.Mac.WindowDidResignMain, func(_ *WindowEvent) { + w.parent.emit(events.Common.WindowLostFocus) + }) + + if options.HTML != "" { + w.setHTML(options.HTML) + } + if options.Hidden == false { + C.windowShow(w.nsWindow) + w.setHasShadow(!options.Mac.DisableShadow) + } else { + // We have to wait until the window is shown before we can remove the shadow + var cancel func() + cancel = w.parent.On(events.Mac.WindowDidBecomeKey, func(_ *WindowEvent) { + w.setHasShadow(!options.Mac.DisableShadow) + cancel() + }) + } + + }) +} + +func (w *macosWebviewWindow) nativeWindowHandle() uintptr { + return uintptr(w.nsWindow) +} + +func (w *macosWebviewWindow) setBackgroundColour(colour RGBA) { + + C.windowSetBackgroundColour(w.nsWindow, C.int(colour.Red), C.int(colour.Green), C.int(colour.Blue), C.int(colour.Alpha)) +} + +func (w *macosWebviewWindow) relativePosition() (int, int) { + var x, y C.int + InvokeSync(func() { + C.windowGetRelativePosition(w.nsWindow, &x, &y) + }) + + return int(x), int(y) +} + +func (w *macosWebviewWindow) absolutePosition() (int, int) { + var x, y C.int + InvokeSync(func() { + C.windowGetAbsolutePosition(w.nsWindow, &x, &y) + }) + + return int(x), int(y) +} + +func (w *macosWebviewWindow) destroy() { + C.windowDestroy(w.nsWindow) +} + +func (w *macosWebviewWindow) setHTML(html string) { + // Convert HTML to C string + cHTML := C.CString(html) + // Render HTML + C.windowRenderHTML(w.nsWindow, cHTML) +} + +func (w *macosWebviewWindow) startDrag() error { + C.startDrag(w.nsWindow) + return nil +} diff --git a/v3/pkg/application/webview_window_darwin.h b/v3/pkg/application/webview_window_darwin.h new file mode 100644 index 000000000..aa67e1c3b --- /dev/null +++ b/v3/pkg/application/webview_window_darwin.h @@ -0,0 +1,35 @@ +//go:build darwin + +#ifndef WebviewWindowDelegate_h +#define WebviewWindowDelegate_h + +#import +#import + +@interface WebviewWindow : NSWindow +- (BOOL) canBecomeKeyWindow; +- (BOOL) canBecomeMainWindow; +- (BOOL) acceptsFirstResponder; +- (BOOL) becomeFirstResponder; +- (BOOL) resignFirstResponder; +- (WebviewWindow*) initWithContentRect:(NSRect)contentRect styleMask:(NSUInteger)windowStyle backing:(NSBackingStoreType)bufferingType defer:(BOOL)deferCreation; + +@property (assign) WKWebView* webView; // We already retain WKWebView since it's part of the Window. + +@end + +@interface WebviewWindowDelegate : NSObject + +@property unsigned int windowId; +@property (retain) NSEvent* leftMouseEvent; +@property unsigned int invisibleTitleBarHeight; +@property NSWindowStyleMask previousStyleMask; // Used to restore the window style mask when using frameless + +- (void)handleLeftMouseUp:(NSWindow *)window; +- (void)handleLeftMouseDown:(NSEvent*)event; +- (void)startDrag:(WebviewWindow*)window; + +@end + + +#endif /* WebviewWindowDelegate_h */ diff --git a/v3/pkg/application/webview_window_darwin.m b/v3/pkg/application/webview_window_darwin.m new file mode 100644 index 000000000..b2ee37e09 --- /dev/null +++ b/v3/pkg/application/webview_window_darwin.m @@ -0,0 +1,714 @@ +//go:build darwin +#import +#import +#import "webview_window_darwin.h" +#import "../events/events_darwin.h" +extern void processMessage(unsigned int, const char*); +extern void processURLRequest(unsigned int, void *); +extern void processWindowKeyDownEvent(unsigned int, const char*); +extern bool hasListeners(unsigned int); +@implementation WebviewWindow +- (WebviewWindow*) initWithContentRect:(NSRect)contentRect styleMask:(NSUInteger)windowStyle backing:(NSBackingStoreType)bufferingType defer:(BOOL)deferCreation; +{ + self = [super initWithContentRect:contentRect styleMask:windowStyle backing:bufferingType defer:deferCreation]; + [self setAlphaValue:1.0]; + [self setBackgroundColor:[NSColor clearColor]]; + [self setOpaque:NO]; + [self setMovableByWindowBackground:YES]; + return self; +} +- (void)keyDown:(NSEvent *)event { + NSUInteger modifierFlags = event.modifierFlags; + // Create an array to hold the modifier strings + NSMutableArray *modifierStrings = [NSMutableArray array]; + // Check for modifier flags and add corresponding strings to the array + if (modifierFlags & NSEventModifierFlagShift) { + [modifierStrings addObject:@"shift"]; + } + if (modifierFlags & NSEventModifierFlagControl) { + [modifierStrings addObject:@"ctrl"]; + } + if (modifierFlags & NSEventModifierFlagOption) { + [modifierStrings addObject:@"option"]; + } + if (modifierFlags & NSEventModifierFlagCommand) { + [modifierStrings addObject:@"cmd"]; + } + NSString *keyString = [self keyStringFromEvent:event]; + if (keyString.length > 0) { + [modifierStrings addObject:keyString]; + } + // Combine the modifier strings with the key character + NSString *keyEventString = [modifierStrings componentsJoinedByString:@"+"]; + const char* utf8String = [keyEventString UTF8String]; + WebviewWindowDelegate *delegate = (WebviewWindowDelegate*)self.delegate; + processWindowKeyDownEvent(delegate.windowId, utf8String); +} +- (NSString *)keyStringFromEvent:(NSEvent *)event { + // Get the pressed key + // Check for special keys like escape and tab + NSString *characters = [event characters]; + if (characters.length == 0) { + return @""; + } + if ([characters isEqualToString:@"\r"]) { + return @"enter"; + } + if ([characters isEqualToString:@"\b"]) { + return @"backspace"; + } + if ([characters isEqualToString:@"\e"]) { + return @"escape"; + } + // page down + if ([characters isEqualToString:@"\x0B"]) { + return @"page down"; + } + // page up + if ([characters isEqualToString:@"\x0E"]) { + return @"page up"; + } + // home + if ([characters isEqualToString:@"\x01"]) { + return @"home"; + } + // end + if ([characters isEqualToString:@"\x04"]) { + return @"end"; + } + // clear + if ([characters isEqualToString:@"\x0C"]) { + return @"clear"; + } + switch ([event keyCode]) { + // Function keys + case 122: return @"f1"; + case 120: return @"f2"; + case 99: return @"f3"; + case 118: return @"f4"; + case 96: return @"f5"; + case 97: return @"f6"; + case 98: return @"f7"; + case 100: return @"f8"; + case 101: return @"f9"; + case 109: return @"f10"; + case 103: return @"f11"; + case 111: return @"f12"; + case 105: return @"f13"; + case 107: return @"f14"; + case 113: return @"f15"; + case 106: return @"f16"; + case 64: return @"f17"; + case 79: return @"f18"; + case 80: return @"f19"; + case 90: return @"f20"; + // Letter keys + case 0: return @"a"; + case 11: return @"b"; + case 8: return @"c"; + case 2: return @"d"; + case 14: return @"e"; + case 3: return @"f"; + case 5: return @"g"; + case 4: return @"h"; + case 34: return @"i"; + case 38: return @"j"; + case 40: return @"k"; + case 37: return @"l"; + case 46: return @"m"; + case 45: return @"n"; + case 31: return @"o"; + case 35: return @"p"; + case 12: return @"q"; + case 15: return @"r"; + case 1: return @"s"; + case 17: return @"t"; + case 32: return @"u"; + case 9: return @"v"; + case 13: return @"w"; + case 7: return @"x"; + case 16: return @"y"; + case 6: return @"z"; + // Number keys + case 29: return @"0"; + case 18: return @"1"; + case 19: return @"2"; + case 20: return @"3"; + case 21: return @"4"; + case 23: return @"5"; + case 22: return @"6"; + case 26: return @"7"; + case 28: return @"8"; + case 25: return @"9"; + // Other special keys + case 51: return @"delete"; + case 117: return @"forward delete"; + case 123: return @"left"; + case 124: return @"right"; + case 126: return @"up"; + case 125: return @"down"; + case 48: return @"tab"; + case 53: return @"escape"; + case 49: return @"space"; + // Punctuation and other keys (for a standard US layout) + case 33: return @"["; + case 30: return @"]"; + case 43: return @","; + case 27: return @"-"; + case 39: return @"'"; + case 44: return @"/"; + case 47: return @"."; + case 41: return @";"; + case 24: return @"="; + case 50: return @"`"; + case 42: return @"\\"; + default: return @""; + } +} +- (BOOL)canBecomeKeyWindow { + return YES; +} +- (BOOL) canBecomeMainWindow { + return YES; +} +- (BOOL) acceptsFirstResponder { + return YES; +} +- (BOOL) becomeFirstResponder { + return YES; +} +- (BOOL) resignFirstResponder { + return YES; +} +- (void) setDelegate:(id) delegate { + [delegate retain]; + [super setDelegate: delegate]; +} +- (void) dealloc { + // Remove the script handler, otherwise WebviewWindowDelegate won't get deallocated + // See: https://stackoverflow.com/questions/26383031/wkwebview-causes-my-view-controller-to-leak + [self.webView.configuration.userContentController removeScriptMessageHandlerForName:@"external"]; + if (self.delegate) { + [self.delegate release]; + } + [super dealloc]; +} +@end +@implementation WebviewWindowDelegate +- (BOOL)windowShouldClose:(NSWindow *)sender { + processWindowEvent(self.windowId, EventWindowShouldClose); + return false; +} +- (void) dealloc { + // Makes sure to remove the retained properties so the reference counter of the retains are decreased + self.leftMouseEvent = nil; + [super dealloc]; +} +- (void) startDrag:(WebviewWindow*)window { + [window performWindowDragWithEvent:self.leftMouseEvent]; +} +// Handle script messages from the external bridge +- (void)userContentController:(nonnull WKUserContentController *)userContentController didReceiveScriptMessage:(nonnull WKScriptMessage *)message { + NSString *m = message.body; + const char *_m = [m UTF8String]; + processMessage(self.windowId, _m); +} +- (void)handleLeftMouseDown:(NSEvent *)event { + self.leftMouseEvent = event; + NSWindow *window = [event window]; + WebviewWindowDelegate* delegate = (WebviewWindowDelegate*)[window delegate]; + if( self.invisibleTitleBarHeight > 0 ) { + NSPoint location = [event locationInWindow]; + NSRect frame = [window frame]; + if( location.y > frame.size.height - self.invisibleTitleBarHeight ) { + [window performWindowDragWithEvent:event]; + return; + } + } +} +- (void)handleLeftMouseUp:(NSWindow *)window { + self.leftMouseEvent = nil; +} +- (void)webView:(nonnull WKWebView *)webView startURLSchemeTask:(nonnull id)urlSchemeTask { + processURLRequest(self.windowId, urlSchemeTask); +} +- (void)webView:(nonnull WKWebView *)webView stopURLSchemeTask:(nonnull id)urlSchemeTask { + NSInputStream *stream = urlSchemeTask.request.HTTPBodyStream; + if (stream) { + NSStreamStatus status = stream.streamStatus; + if (status != NSStreamStatusClosed && status != NSStreamStatusNotOpen) { + [stream close]; + } + } +} +// GENERATED EVENTS START +- (void)windowDidBecomeKey:(NSNotification *)notification { + if( hasListeners(EventWindowDidBecomeKey) ) { + processWindowEvent(self.windowId, EventWindowDidBecomeKey); + } +} + +- (void)windowDidBecomeMain:(NSNotification *)notification { + if( hasListeners(EventWindowDidBecomeMain) ) { + processWindowEvent(self.windowId, EventWindowDidBecomeMain); + } +} + +- (void)windowDidBeginSheet:(NSNotification *)notification { + if( hasListeners(EventWindowDidBeginSheet) ) { + processWindowEvent(self.windowId, EventWindowDidBeginSheet); + } +} + +- (void)windowDidChangeAlpha:(NSNotification *)notification { + if( hasListeners(EventWindowDidChangeAlpha) ) { + processWindowEvent(self.windowId, EventWindowDidChangeAlpha); + } +} + +- (void)windowDidChangeBackingLocation:(NSNotification *)notification { + if( hasListeners(EventWindowDidChangeBackingLocation) ) { + processWindowEvent(self.windowId, EventWindowDidChangeBackingLocation); + } +} + +- (void)windowDidChangeBackingProperties:(NSNotification *)notification { + if( hasListeners(EventWindowDidChangeBackingProperties) ) { + processWindowEvent(self.windowId, EventWindowDidChangeBackingProperties); + } +} + +- (void)windowDidChangeCollectionBehavior:(NSNotification *)notification { + if( hasListeners(EventWindowDidChangeCollectionBehavior) ) { + processWindowEvent(self.windowId, EventWindowDidChangeCollectionBehavior); + } +} + +- (void)windowDidChangeEffectiveAppearance:(NSNotification *)notification { + if( hasListeners(EventWindowDidChangeEffectiveAppearance) ) { + processWindowEvent(self.windowId, EventWindowDidChangeEffectiveAppearance); + } +} + +- (void)windowDidChangeOcclusionState:(NSNotification *)notification { + if( hasListeners(EventWindowDidChangeOcclusionState) ) { + processWindowEvent(self.windowId, EventWindowDidChangeOcclusionState); + } +} + +- (void)windowDidChangeOrderingMode:(NSNotification *)notification { + if( hasListeners(EventWindowDidChangeOrderingMode) ) { + processWindowEvent(self.windowId, EventWindowDidChangeOrderingMode); + } +} + +- (void)windowDidChangeScreen:(NSNotification *)notification { + if( hasListeners(EventWindowDidChangeScreen) ) { + processWindowEvent(self.windowId, EventWindowDidChangeScreen); + } +} + +- (void)windowDidChangeScreenParameters:(NSNotification *)notification { + if( hasListeners(EventWindowDidChangeScreenParameters) ) { + processWindowEvent(self.windowId, EventWindowDidChangeScreenParameters); + } +} + +- (void)windowDidChangeScreenProfile:(NSNotification *)notification { + if( hasListeners(EventWindowDidChangeScreenProfile) ) { + processWindowEvent(self.windowId, EventWindowDidChangeScreenProfile); + } +} + +- (void)windowDidChangeScreenSpace:(NSNotification *)notification { + if( hasListeners(EventWindowDidChangeScreenSpace) ) { + processWindowEvent(self.windowId, EventWindowDidChangeScreenSpace); + } +} + +- (void)windowDidChangeScreenSpaceProperties:(NSNotification *)notification { + if( hasListeners(EventWindowDidChangeScreenSpaceProperties) ) { + processWindowEvent(self.windowId, EventWindowDidChangeScreenSpaceProperties); + } +} + +- (void)windowDidChangeSharingType:(NSNotification *)notification { + if( hasListeners(EventWindowDidChangeSharingType) ) { + processWindowEvent(self.windowId, EventWindowDidChangeSharingType); + } +} + +- (void)windowDidChangeSpace:(NSNotification *)notification { + if( hasListeners(EventWindowDidChangeSpace) ) { + processWindowEvent(self.windowId, EventWindowDidChangeSpace); + } +} + +- (void)windowDidChangeSpaceOrderingMode:(NSNotification *)notification { + if( hasListeners(EventWindowDidChangeSpaceOrderingMode) ) { + processWindowEvent(self.windowId, EventWindowDidChangeSpaceOrderingMode); + } +} + +- (void)windowDidChangeTitle:(NSNotification *)notification { + if( hasListeners(EventWindowDidChangeTitle) ) { + processWindowEvent(self.windowId, EventWindowDidChangeTitle); + } +} + +- (void)windowDidChangeToolbar:(NSNotification *)notification { + if( hasListeners(EventWindowDidChangeToolbar) ) { + processWindowEvent(self.windowId, EventWindowDidChangeToolbar); + } +} + +- (void)windowDidChangeVisibility:(NSNotification *)notification { + if( hasListeners(EventWindowDidChangeVisibility) ) { + processWindowEvent(self.windowId, EventWindowDidChangeVisibility); + } +} + +- (void)windowDidDeminiaturize:(NSNotification *)notification { + if( hasListeners(EventWindowDidDeminiaturize) ) { + processWindowEvent(self.windowId, EventWindowDidDeminiaturize); + } +} + +- (void)windowDidEndSheet:(NSNotification *)notification { + if( hasListeners(EventWindowDidEndSheet) ) { + processWindowEvent(self.windowId, EventWindowDidEndSheet); + } +} + +- (void)windowDidEnterFullScreen:(NSNotification *)notification { + if( hasListeners(EventWindowDidEnterFullScreen) ) { + processWindowEvent(self.windowId, EventWindowDidEnterFullScreen); + } +} + +- (void)windowDidEnterVersionBrowser:(NSNotification *)notification { + if( hasListeners(EventWindowDidEnterVersionBrowser) ) { + processWindowEvent(self.windowId, EventWindowDidEnterVersionBrowser); + } +} + +- (void)windowDidExitFullScreen:(NSNotification *)notification { + if( hasListeners(EventWindowDidExitFullScreen) ) { + processWindowEvent(self.windowId, EventWindowDidExitFullScreen); + } +} + +- (void)windowDidExitVersionBrowser:(NSNotification *)notification { + if( hasListeners(EventWindowDidExitVersionBrowser) ) { + processWindowEvent(self.windowId, EventWindowDidExitVersionBrowser); + } +} + +- (void)windowDidExpose:(NSNotification *)notification { + if( hasListeners(EventWindowDidExpose) ) { + processWindowEvent(self.windowId, EventWindowDidExpose); + } +} + +- (void)windowDidFocus:(NSNotification *)notification { + if( hasListeners(EventWindowDidFocus) ) { + processWindowEvent(self.windowId, EventWindowDidFocus); + } +} + +- (void)windowDidMiniaturize:(NSNotification *)notification { + if( hasListeners(EventWindowDidMiniaturize) ) { + processWindowEvent(self.windowId, EventWindowDidMiniaturize); + } +} + +- (void)windowDidMove:(NSNotification *)notification { + if( hasListeners(EventWindowDidMove) ) { + processWindowEvent(self.windowId, EventWindowDidMove); + } +} + +- (void)windowDidOrderOffScreen:(NSNotification *)notification { + if( hasListeners(EventWindowDidOrderOffScreen) ) { + processWindowEvent(self.windowId, EventWindowDidOrderOffScreen); + } +} + +- (void)windowDidOrderOnScreen:(NSNotification *)notification { + if( hasListeners(EventWindowDidOrderOnScreen) ) { + processWindowEvent(self.windowId, EventWindowDidOrderOnScreen); + } +} + +- (void)windowDidResignKey:(NSNotification *)notification { + if( hasListeners(EventWindowDidResignKey) ) { + processWindowEvent(self.windowId, EventWindowDidResignKey); + } +} + +- (void)windowDidResignMain:(NSNotification *)notification { + if( hasListeners(EventWindowDidResignMain) ) { + processWindowEvent(self.windowId, EventWindowDidResignMain); + } +} + +- (void)windowDidResize:(NSNotification *)notification { + if( hasListeners(EventWindowDidResize) ) { + processWindowEvent(self.windowId, EventWindowDidResize); + } +} + +- (void)windowDidUpdate:(NSNotification *)notification { + if( hasListeners(EventWindowDidUpdate) ) { + processWindowEvent(self.windowId, EventWindowDidUpdate); + } +} + +- (void)windowDidUpdateAlpha:(NSNotification *)notification { + if( hasListeners(EventWindowDidUpdateAlpha) ) { + processWindowEvent(self.windowId, EventWindowDidUpdateAlpha); + } +} + +- (void)windowDidUpdateCollectionBehavior:(NSNotification *)notification { + if( hasListeners(EventWindowDidUpdateCollectionBehavior) ) { + processWindowEvent(self.windowId, EventWindowDidUpdateCollectionBehavior); + } +} + +- (void)windowDidUpdateCollectionProperties:(NSNotification *)notification { + if( hasListeners(EventWindowDidUpdateCollectionProperties) ) { + processWindowEvent(self.windowId, EventWindowDidUpdateCollectionProperties); + } +} + +- (void)windowDidUpdateShadow:(NSNotification *)notification { + if( hasListeners(EventWindowDidUpdateShadow) ) { + processWindowEvent(self.windowId, EventWindowDidUpdateShadow); + } +} + +- (void)windowDidUpdateTitle:(NSNotification *)notification { + if( hasListeners(EventWindowDidUpdateTitle) ) { + processWindowEvent(self.windowId, EventWindowDidUpdateTitle); + } +} + +- (void)windowDidUpdateToolbar:(NSNotification *)notification { + if( hasListeners(EventWindowDidUpdateToolbar) ) { + processWindowEvent(self.windowId, EventWindowDidUpdateToolbar); + } +} + +- (void)windowDidUpdateVisibility:(NSNotification *)notification { + if( hasListeners(EventWindowDidUpdateVisibility) ) { + processWindowEvent(self.windowId, EventWindowDidUpdateVisibility); + } +} + +- (void)windowWillBecomeKey:(NSNotification *)notification { + if( hasListeners(EventWindowWillBecomeKey) ) { + processWindowEvent(self.windowId, EventWindowWillBecomeKey); + } +} + +- (void)windowWillBecomeMain:(NSNotification *)notification { + if( hasListeners(EventWindowWillBecomeMain) ) { + processWindowEvent(self.windowId, EventWindowWillBecomeMain); + } +} + +- (void)windowWillBeginSheet:(NSNotification *)notification { + if( hasListeners(EventWindowWillBeginSheet) ) { + processWindowEvent(self.windowId, EventWindowWillBeginSheet); + } +} + +- (void)windowWillChangeOrderingMode:(NSNotification *)notification { + if( hasListeners(EventWindowWillChangeOrderingMode) ) { + processWindowEvent(self.windowId, EventWindowWillChangeOrderingMode); + } +} + +- (void)windowWillClose:(NSNotification *)notification { + if( hasListeners(EventWindowWillClose) ) { + processWindowEvent(self.windowId, EventWindowWillClose); + } +} + +- (void)windowWillDeminiaturize:(NSNotification *)notification { + if( hasListeners(EventWindowWillDeminiaturize) ) { + processWindowEvent(self.windowId, EventWindowWillDeminiaturize); + } +} + +- (void)windowWillEnterFullScreen:(NSNotification *)notification { + if( hasListeners(EventWindowWillEnterFullScreen) ) { + processWindowEvent(self.windowId, EventWindowWillEnterFullScreen); + } +} + +- (void)windowWillEnterVersionBrowser:(NSNotification *)notification { + if( hasListeners(EventWindowWillEnterVersionBrowser) ) { + processWindowEvent(self.windowId, EventWindowWillEnterVersionBrowser); + } +} + +- (void)windowWillExitFullScreen:(NSNotification *)notification { + if( hasListeners(EventWindowWillExitFullScreen) ) { + processWindowEvent(self.windowId, EventWindowWillExitFullScreen); + } +} + +- (void)windowWillExitVersionBrowser:(NSNotification *)notification { + if( hasListeners(EventWindowWillExitVersionBrowser) ) { + processWindowEvent(self.windowId, EventWindowWillExitVersionBrowser); + } +} + +- (void)windowWillFocus:(NSNotification *)notification { + if( hasListeners(EventWindowWillFocus) ) { + processWindowEvent(self.windowId, EventWindowWillFocus); + } +} + +- (void)windowWillMiniaturize:(NSNotification *)notification { + if( hasListeners(EventWindowWillMiniaturize) ) { + processWindowEvent(self.windowId, EventWindowWillMiniaturize); + } +} + +- (void)windowWillMove:(NSNotification *)notification { + if( hasListeners(EventWindowWillMove) ) { + processWindowEvent(self.windowId, EventWindowWillMove); + } +} + +- (void)windowWillOrderOffScreen:(NSNotification *)notification { + if( hasListeners(EventWindowWillOrderOffScreen) ) { + processWindowEvent(self.windowId, EventWindowWillOrderOffScreen); + } +} + +- (void)windowWillOrderOnScreen:(NSNotification *)notification { + if( hasListeners(EventWindowWillOrderOnScreen) ) { + processWindowEvent(self.windowId, EventWindowWillOrderOnScreen); + } +} + +- (void)windowWillResignMain:(NSNotification *)notification { + if( hasListeners(EventWindowWillResignMain) ) { + processWindowEvent(self.windowId, EventWindowWillResignMain); + } +} + +- (void)windowWillResize:(NSNotification *)notification { + if( hasListeners(EventWindowWillResize) ) { + processWindowEvent(self.windowId, EventWindowWillResize); + } +} + +- (void)windowWillUnfocus:(NSNotification *)notification { + if( hasListeners(EventWindowWillUnfocus) ) { + processWindowEvent(self.windowId, EventWindowWillUnfocus); + } +} + +- (void)windowWillUpdate:(NSNotification *)notification { + if( hasListeners(EventWindowWillUpdate) ) { + processWindowEvent(self.windowId, EventWindowWillUpdate); + } +} + +- (void)windowWillUpdateAlpha:(NSNotification *)notification { + if( hasListeners(EventWindowWillUpdateAlpha) ) { + processWindowEvent(self.windowId, EventWindowWillUpdateAlpha); + } +} + +- (void)windowWillUpdateCollectionBehavior:(NSNotification *)notification { + if( hasListeners(EventWindowWillUpdateCollectionBehavior) ) { + processWindowEvent(self.windowId, EventWindowWillUpdateCollectionBehavior); + } +} + +- (void)windowWillUpdateCollectionProperties:(NSNotification *)notification { + if( hasListeners(EventWindowWillUpdateCollectionProperties) ) { + processWindowEvent(self.windowId, EventWindowWillUpdateCollectionProperties); + } +} + +- (void)windowWillUpdateShadow:(NSNotification *)notification { + if( hasListeners(EventWindowWillUpdateShadow) ) { + processWindowEvent(self.windowId, EventWindowWillUpdateShadow); + } +} + +- (void)windowWillUpdateTitle:(NSNotification *)notification { + if( hasListeners(EventWindowWillUpdateTitle) ) { + processWindowEvent(self.windowId, EventWindowWillUpdateTitle); + } +} + +- (void)windowWillUpdateToolbar:(NSNotification *)notification { + if( hasListeners(EventWindowWillUpdateToolbar) ) { + processWindowEvent(self.windowId, EventWindowWillUpdateToolbar); + } +} + +- (void)windowWillUpdateVisibility:(NSNotification *)notification { + if( hasListeners(EventWindowWillUpdateVisibility) ) { + processWindowEvent(self.windowId, EventWindowWillUpdateVisibility); + } +} + +- (void)windowWillUseStandardFrame:(NSNotification *)notification { + if( hasListeners(EventWindowWillUseStandardFrame) ) { + processWindowEvent(self.windowId, EventWindowWillUseStandardFrame); + } +} + +- (void)windowFileDraggingEntered:(NSNotification *)notification { + if( hasListeners(EventWindowFileDraggingEntered) ) { + processWindowEvent(self.windowId, EventWindowFileDraggingEntered); + } +} + +- (void)windowFileDraggingPerformed:(NSNotification *)notification { + if( hasListeners(EventWindowFileDraggingPerformed) ) { + processWindowEvent(self.windowId, EventWindowFileDraggingPerformed); + } +} + +- (void)windowFileDraggingExited:(NSNotification *)notification { + if( hasListeners(EventWindowFileDraggingExited) ) { + processWindowEvent(self.windowId, EventWindowFileDraggingExited); + } +} + +- (void)webView:(WKWebView *)webview didStartProvisionalNavigation:(WKNavigation *)navigation { + if( hasListeners(EventWebViewDidStartProvisionalNavigation) ) { + processWindowEvent(self.windowId, EventWebViewDidStartProvisionalNavigation); + } +} + +- (void)webView:(WKWebView *)webview didReceiveServerRedirectForProvisionalNavigation:(WKNavigation *)navigation { + if( hasListeners(EventWebViewDidReceiveServerRedirectForProvisionalNavigation) ) { + processWindowEvent(self.windowId, EventWebViewDidReceiveServerRedirectForProvisionalNavigation); + } +} + +- (void)webView:(WKWebView *)webview didFinishNavigation:(WKNavigation *)navigation { + if( hasListeners(EventWebViewDidFinishNavigation) ) { + processWindowEvent(self.windowId, EventWebViewDidFinishNavigation); + } +} + +- (void)webView:(WKWebView *)webview didCommitNavigation:(WKNavigation *)navigation { + if( hasListeners(EventWebViewDidCommitNavigation) ) { + processWindowEvent(self.windowId, EventWebViewDidCommitNavigation); + } +} + +// GENERATED EVENTS END +@end diff --git a/v3/pkg/application/webview_window_darwin_devtools.go b/v3/pkg/application/webview_window_darwin_devtools.go new file mode 100644 index 000000000..2a6442156 --- /dev/null +++ b/v3/pkg/application/webview_window_darwin_devtools.go @@ -0,0 +1,38 @@ +//go:build darwin && !production + +package application + +/* +#cgo CFLAGS: -mmacosx-version-min=10.13 -x objective-c +#cgo LDFLAGS: -framework Cocoa + +#import + +#include "webview_window_darwin.h" + +@interface _WKInspector : NSObject +- (void)show; +- (void)detach; +@end + +@interface WKWebView () +- (_WKInspector *)_inspector; +@end + +void showDevTools(void *window) { + // get main window + WebviewWindow* nsWindow = (WebviewWindow*)window; + dispatch_async(dispatch_get_main_queue(), ^{ + [nsWindow.webView._inspector show]; + }); +} + +*/ +import "C" +import "unsafe" + +func init() { + showDevTools = func(window unsafe.Pointer) { + C.showDevTools(window) + } +} diff --git a/v3/pkg/application/webview_window_darwin_drag.h b/v3/pkg/application/webview_window_darwin_drag.h new file mode 100644 index 000000000..aca00d970 --- /dev/null +++ b/v3/pkg/application/webview_window_darwin_drag.h @@ -0,0 +1,7 @@ +//go:build darwin + +#import + +@interface WebviewDrag : NSView +@property unsigned int windowId; +@end \ No newline at end of file diff --git a/v3/pkg/application/webview_window_darwin_drag.m b/v3/pkg/application/webview_window_darwin_drag.m new file mode 100644 index 000000000..9a8e9c574 --- /dev/null +++ b/v3/pkg/application/webview_window_darwin_drag.m @@ -0,0 +1,60 @@ +//go:build darwin + +#import +#import +#import "webview_window_darwin_drag.h" + +#import "../events/events_darwin.h" + +extern void processDragItems(unsigned int windowId, char** arr, int length); + +@implementation WebviewDrag + +- (instancetype)initWithFrame:(NSRect)frameRect { + self = [super initWithFrame:frameRect]; + if (self) { + [self registerForDraggedTypes:@[NSFilenamesPboardType]]; + } + + return self; +} + +- (NSDragOperation)draggingEntered:(id)sender { + NSPasteboard *pasteboard = [sender draggingPasteboard]; + if ([[pasteboard types] containsObject:NSFilenamesPboardType]) { + processWindowEvent(self.windowId, EventWindowFileDraggingEntered); + return NSDragOperationCopy; + } + return NSDragOperationNone; +} + + +- (void)draggingExited:(id)sender { + processWindowEvent(self.windowId, EventWindowFileDraggingExited); +} + +- (BOOL)prepareForDragOperation:(id)sender { + return YES; +} + +- (BOOL)performDragOperation:(id)sender { + NSPasteboard *pasteboard = [sender draggingPasteboard]; + processWindowEvent(self.windowId, EventWindowFileDraggingPerformed); + if ([[pasteboard types] containsObject:NSFilenamesPboardType]) { + NSArray *files = [pasteboard propertyListForType:NSFilenamesPboardType]; + NSUInteger count = [files count]; + char** cArray = (char**)malloc(count * sizeof(char*)); + for (NSUInteger i = 0; i < count; i++) { + NSString* str = files[i]; + cArray[i] = (char*)[str UTF8String]; + } + processDragItems(self.windowId, cArray, (int)count); + free(cArray); + return YES; + } + return NO; +} + + +@end + diff --git a/v3/pkg/application/webview_window_linux.go b/v3/pkg/application/webview_window_linux.go new file mode 100644 index 000000000..a7afff6b8 --- /dev/null +++ b/v3/pkg/application/webview_window_linux.go @@ -0,0 +1,480 @@ +//go:build linux + +package application + +import ( + "fmt" + "net/url" + + "github.com/wailsapp/wails/v3/pkg/events" +) + +var showDevTools = func(window pointer) {} + +type dragInfo struct { + XRoot int + YRoot int + DragTime int + MouseButton uint +} + +type linuxWebviewWindow struct { + id uint + application pointer + window pointer + webview pointer + parent *WebviewWindow + menubar pointer + vbox pointer + menu *Menu + accels pointer + lastWidth int + lastHeight int + drag dragInfo +} + +var ( + registered bool = false // avoid 'already registered message' about 'wails://' +) + +func (w *linuxWebviewWindow) startDrag() error { + return nil +} + +func (w *linuxWebviewWindow) endDrag(button uint, x, y int) { + +} + +func (w *linuxWebviewWindow) enableDND() { + windowEnableDND(w.parent.id, w.webview) +} + +func (w *linuxWebviewWindow) connectSignals() { + cb := func(e events.WindowEventType) { + w.parent.emit(e) + } + windowSetupSignalHandlers(w.parent.id, w.window, w.webview, cb) +} + +func (w *linuxWebviewWindow) openContextMenu(menu *Menu, data *ContextMenuData) { + // Create the menu + thisMenu := newMenuImpl(menu) + thisMenu.update() + fmt.Println("linux.openContextMenu() - not implemented") + /* void + gtk_menu_popup_at_rect ( + GtkMenu* menu, + GdkWindow* rect_window, + const GdkRectangle* rect, + GdkGravity rect_anchor, + GdkGravity menu_anchor, + const GdkEvent* trigger_event + ) + */ +} + +func (w *linuxWebviewWindow) getZoom() float64 { + return windowZoom(w.webview) +} + +func (w *linuxWebviewWindow) setZoom(zoom float64) { + windowZoomSet(w.webview, zoom) +} + +func (w *linuxWebviewWindow) setFrameless(frameless bool) { + windowSetFrameless(w.window, frameless) +} + +func (w *linuxWebviewWindow) getScreen() (*Screen, error) { + mx, my, width, height, scale := windowGetCurrentMonitorGeometry(w.window) + return &Screen{ + ID: fmt.Sprintf("%d", w.id), // A unique identifier for the display + Name: w.parent.Name(), // The name of the display + Scale: float32(scale), // The scale factor of the display + X: mx, // The x-coordinate of the top-left corner of the rectangle + Y: my, // The y-coordinate of the top-left corner of the rectangle + Size: Size{Width: width, Height: height}, // The size of the display + Bounds: Rect{}, // The bounds of the display + WorkArea: Rect{}, // The work area of the display + IsPrimary: false, // Whether this is the primary display + Rotation: 0.0, // The rotation of the display + }, nil +} + +func (w *linuxWebviewWindow) focus() { + windowPresent(w.window) +} + +func (w *linuxWebviewWindow) show() { + windowShow(w.window) +} + +func (w *linuxWebviewWindow) hide() { + windowHide(w.window) +} + +func (w *linuxWebviewWindow) isNormal() bool { + return !w.isMinimised() && !w.isMaximised() && !w.isFullscreen() +} + +func (w *linuxWebviewWindow) isVisible() bool { + return windowIsVisible(w.window) +} + +func (w *linuxWebviewWindow) setFullscreenButtonEnabled(enabled bool) { + // C.setFullscreenButtonEnabled(w.nsWindow, C.bool(enabled)) + fmt.Println("setFullscreenButtonEnabled - not implemented") +} + +func (w *linuxWebviewWindow) disableSizeConstraints() { + x, y, width, height, scale := windowGetCurrentMonitorGeometry(w.window) + w.setMinMaxSize(x, y, width*scale, height*scale) +} + +func (w *linuxWebviewWindow) unfullscreen() { + windowUnfullscreen(w.window) + w.unmaximise() +} + +func (w *linuxWebviewWindow) fullscreen() { + w.maximise() + w.lastWidth, w.lastHeight = w.size() + x, y, width, height, scale := windowGetCurrentMonitorGeometry(w.window) + if x == -1 && y == -1 && width == -1 && height == -1 { + return + } + w.setMinMaxSize(0, 0, width*scale, height*scale) + w.setSize(width*scale, height*scale) + windowFullscreen(w.window) + w.setRelativePosition(0, 0) +} + +func (w *linuxWebviewWindow) unminimise() { + windowPresent(w.window) +} + +func (w *linuxWebviewWindow) unmaximise() { + windowUnmaximize(w.window) +} + +func (w *linuxWebviewWindow) maximise() { + windowMaximize(w.window) +} + +func (w *linuxWebviewWindow) minimise() { + windowMinimize(w.window) +} + +func (w *linuxWebviewWindow) flash(enabled bool) { + // Not supported on linux +} + +func (w *linuxWebviewWindow) on(eventID uint) { + // Don't think this is correct! + // GTK Events are strings + fmt.Println("on()", eventID) + //C.registerListener(C.uint(eventID)) +} + +func (w *linuxWebviewWindow) zoom() { + w.zoomIn() +} + +func (w *linuxWebviewWindow) windowZoom() { + w.zoom() // FIXME> This should be removed +} + +func (w *linuxWebviewWindow) close() { + windowClose(w.window) +} + +func (w *linuxWebviewWindow) zoomIn() { + windowZoomIn(w.webview) +} + +func (w *linuxWebviewWindow) zoomOut() { + windowZoomOut(w.webview) +} + +func (w *linuxWebviewWindow) zoomReset() { + windowZoomSet(w.webview, 1.0) +} + +func (w *linuxWebviewWindow) reload() { + windowReload(w.webview, "wails://") +} + +func (w *linuxWebviewWindow) forceReload() { + w.reload() +} + +func (w *linuxWebviewWindow) center() { + x, y, width, height, _ := windowGetCurrentMonitorGeometry(w.window) + if x == -1 && y == -1 && width == -1 && height == -1 { + return + } + windowWidth, windowHeight := windowGetSize(w.window) + + newX := ((width - int(windowWidth)) / 2) + x + newY := ((height - int(windowHeight)) / 2) + y + + // Place the window at the center of the monitor + windowMove(w.window, newX, newY) +} + +func (w *linuxWebviewWindow) isMinimised() bool { + return windowIsMinimized(w.window) +} + +func (w *linuxWebviewWindow) isMaximised() bool { + return windowIsMaximized(w.window) +} + +func (w *linuxWebviewWindow) isFocused() bool { + return windowIsFocused(w.window) +} + +func (w *linuxWebviewWindow) isFullscreen() bool { + return windowIsFullscreen(w.window) +} + +func (w *linuxWebviewWindow) restore() { + // restore window to normal size + // FIXME: never called! - remove from webviewImpl interface +} + +func (w *linuxWebviewWindow) execJS(js string) { + windowExecJS(w.webview, js) +} + +func (w *linuxWebviewWindow) setURL(uri string) { + if uri != "" { + url, err := url.Parse(uri) + if err == nil && url.Scheme == "" && url.Host == "" { + // TODO handle this in a central location, the scheme and host might be platform dependant. + url.Scheme = "wails" + url.Host = "wails" + uri = url.String() + } + } + windowSetURL(w.webview, uri) +} + +func (w *linuxWebviewWindow) setAlwaysOnTop(alwaysOnTop bool) { + windowSetKeepAbove(w.window, alwaysOnTop) +} + +func newWindowImpl(parent *WebviewWindow) *linuxWebviewWindow { + // (*C.struct__GtkWidget)(m.native) + //var menubar *C.struct__GtkWidget + return &linuxWebviewWindow{ + application: getNativeApplication().application, + parent: parent, + // menubar: menubar, + } +} + +func (w *linuxWebviewWindow) setTitle(title string) { + if !w.parent.options.Frameless { + windowSetTitle(w.window, title) + } +} + +func (w *linuxWebviewWindow) setSize(width, height int) { + windowResize(w.window, width, height) +} + +func (w *linuxWebviewWindow) setMinMaxSize(minWidth, minHeight, maxWidth, maxHeight int) { + if minWidth == 0 { + minWidth = -1 + } + if minHeight == 0 { + minHeight = -1 + } + if maxWidth == 0 { + maxWidth = -1 + } + if maxHeight == 0 { + maxHeight = -1 + } + windowSetGeometryHints(w.window, minWidth, minHeight, maxWidth, maxHeight) +} + +func (w *linuxWebviewWindow) setMinSize(width, height int) { + w.setMinMaxSize(width, height, w.parent.options.MaxWidth, w.parent.options.MaxHeight) +} + +func (w *linuxWebviewWindow) setMaxSize(width, height int) { + w.setMinMaxSize(w.parent.options.MinWidth, w.parent.options.MinHeight, width, height) +} + +func (w *linuxWebviewWindow) setResizable(resizable bool) { + windowSetResizable(w.window, resizable) +} + +func (w *linuxWebviewWindow) toggleDevTools() { + showDevTools(w.webview) +} + +func (w *linuxWebviewWindow) size() (int, int) { + return windowGetSize(w.window) +} + +func (w *linuxWebviewWindow) setRelativePosition(x, y int) { + mx, my, _, _, _ := windowGetCurrentMonitorGeometry(w.window) + windowMove(w.window, x+mx, y+my) +} + +func (w *linuxWebviewWindow) width() int { + width, _ := w.size() + return width +} + +func (w *linuxWebviewWindow) height() int { + _, height := w.size() + return height +} + +func (w *linuxWebviewWindow) setAbsolutePosition(x int, y int) { + // Set the window's absolute position + windowMove(w.window, x, y) +} + +func (w *linuxWebviewWindow) absolutePosition() (int, int) { + var x, y int + x, y = windowGetAbsolutePosition(w.window) + return x, y +} + +func (w *linuxWebviewWindow) run() { + for eventId := range w.parent.eventListeners { + w.on(eventId) + } + + app := getNativeApplication() + + menu := app.getApplicationMenu() + w.window, w.webview, w.vbox = windowNew(app.application, menu, w.parent.id, 1) + app.registerWindow(w.window, w.parent.id) // record our mapping + w.connectSignals() + if w.parent.options.EnableDragAndDrop { + w.enableDND() + } + w.setTitle(w.parent.options.Title) + w.setAlwaysOnTop(w.parent.options.AlwaysOnTop) + w.setResizable(!w.parent.options.DisableResize) + // only set min/max size if actually set + if w.parent.options.MinWidth != 0 && + w.parent.options.MinHeight != 0 && + w.parent.options.MaxWidth != 0 && + w.parent.options.MaxHeight != 0 { + w.setMinMaxSize( + w.parent.options.MinWidth, + w.parent.options.MinHeight, + w.parent.options.MaxWidth, + w.parent.options.MaxHeight, + ) + } + w.setSize(w.parent.options.Width, w.parent.options.Height) + w.setZoom(w.parent.options.Zoom) + if w.parent.options.BackgroundType != BackgroundTypeSolid { + w.setTransparent() + w.setBackgroundColour(w.parent.options.BackgroundColour) + } + + w.setFrameless(w.parent.options.Frameless) + + if w.parent.options.X != 0 || w.parent.options.Y != 0 { + w.setRelativePosition(w.parent.options.X, w.parent.options.Y) + } else { + fmt.Println("attempting to set in the center") + w.center() + } + switch w.parent.options.StartState { + case WindowStateMaximised: + w.maximise() + case WindowStateMinimised: + w.minimise() + case WindowStateFullscreen: + w.fullscreen() + } + + if w.parent.options.URL != "" { + w.setURL(w.parent.options.URL) + } + // We need to wait for the HTML to load before we can execute the javascript + // FIXME: What event is this? DomReady? + w.parent.On(events.Mac.WebViewDidFinishNavigation, func(_ *WindowEvent) { + if w.parent.options.JS != "" { + w.execJS(w.parent.options.JS) + } + if w.parent.options.CSS != "" { + js := fmt.Sprintf("(function() { var style = document.createElement('style'); style.appendChild(document.createTextNode('%s')); document.head.appendChild(style); })();", w.parent.options.CSS) + w.execJS(js) + } + }) + if w.parent.options.HTML != "" { + w.setHTML(w.parent.options.HTML) + } + if !w.parent.options.Hidden { + w.show() + if w.parent.options.X != 0 || w.parent.options.Y != 0 { + w.setRelativePosition(w.parent.options.X, w.parent.options.Y) + } else { + w.center() // needs to be queued until after GTK starts up! + } + } + if w.parent.options.DevToolsEnabled { + w.toggleDevTools() + } +} + +func (w *linuxWebviewWindow) setTransparent() { + windowSetTransparent(w.window) +} + +func (w *linuxWebviewWindow) setBackgroundColour(colour RGBA) { + windowSetBackgroundColour(w.vbox, w.webview, colour) +} + +func (w *linuxWebviewWindow) relativePosition() (int, int) { + var x, y int + x, y = windowGetRelativePosition(w.window) + return x, y +} + +func (w *linuxWebviewWindow) destroy() { + windowDestroy(w.window) +} + +func (w *linuxWebviewWindow) setEnabled(enabled bool) { + widgetSetSensitive(w.window, enabled) +} + +func (w *linuxWebviewWindow) setHTML(html string) { + windowSetHTML(w.webview, html) +} + +func (w *linuxWebviewWindow) startResize(border string) error { + // FIXME: what do we need to do here? + return nil +} + +func (w *linuxWebviewWindow) nativeWindowHandle() uintptr { + return uintptr(w.window) +} + +func (w *linuxWebviewWindow) print() error { + w.execJS("window.print();") + return nil +} + +func (w *linuxWebviewWindow) handleKeyEvent(acceleratorString string) { + // Parse acceleratorString + // accelerator, err := parseAccelerator(acceleratorString) + // if err != nil { + // globalApplication.error("unable to parse accelerator: %s", err.Error()) + // return + // } + w.parent.processKeyBinding(acceleratorString) +} diff --git a/v3/pkg/application/webview_window_linux_devtools.go b/v3/pkg/application/webview_window_linux_devtools.go new file mode 100644 index 000000000..eb883ae24 --- /dev/null +++ b/v3/pkg/application/webview_window_linux_devtools.go @@ -0,0 +1,9 @@ +//go:build linux && !production + +package application + +func init() { + showDevTools = func(wv pointer) { + windowToggleDevTools(wv) + } +} diff --git a/v3/pkg/application/webview_window_windows.go b/v3/pkg/application/webview_window_windows.go new file mode 100644 index 000000000..50c86b97b --- /dev/null +++ b/v3/pkg/application/webview_window_windows.go @@ -0,0 +1,1646 @@ +//go:build windows + +package application + +import ( + "errors" + "fmt" + "github.com/bep/debounce" + "github.com/wailsapp/go-webview2/webviewloader" + "github.com/wailsapp/wails/v3/internal/assetserver" + "github.com/wailsapp/wails/v3/internal/assetserver/webview" + "github.com/wailsapp/wails/v3/internal/capabilities" + "net/url" + "path" + "strconv" + "strings" + "sync" + "syscall" + "time" + "unicode/utf16" + "unsafe" + + "github.com/samber/lo" + + "github.com/wailsapp/go-webview2/pkg/edge" + "github.com/wailsapp/wails/v3/pkg/events" + "github.com/wailsapp/wails/v3/pkg/w32" +) + +var edgeMap = map[string]uintptr{ + "n-resize": w32.HTTOP, + "ne-resize": w32.HTTOPRIGHT, + "e-resize": w32.HTRIGHT, + "se-resize": w32.HTBOTTOMRIGHT, + "s-resize": w32.HTBOTTOM, + "sw-resize": w32.HTBOTTOMLEFT, + "w-resize": w32.HTLEFT, + "nw-resize": w32.HTTOPLEFT, +} + +var showDevTools = func(chromium *edge.Chromium) {} + +type windowsWebviewWindow struct { + windowImpl unsafe.Pointer + parent *WebviewWindow + hwnd w32.HWND + menu *Win32Menu + currentlyOpenContextMenu *Win32Menu + + // Fullscreen flags + isCurrentlyFullscreen bool + previousWindowStyle uint32 + previousWindowExStyle uint32 + previousWindowPlacement w32.WINDOWPLACEMENT + + // Webview + chromium *edge.Chromium + hasStarted bool + resizeDebouncer func(func()) + + // resizeBorder* is the width/height of the resize border in pixels. + resizeBorderWidth int32 + resizeBorderHeight int32 + focusingChromium bool + dropTarget *w32.DropTarget + onceDo sync.Once +} + +func (w *windowsWebviewWindow) handleKeyEvent(_ string) { + // Unused on windows +} + +func (w *windowsWebviewWindow) setAbsolutePosition(x int, y int) { + // Set the window's absolute position + w32.SetWindowPos(w.hwnd, 0, x, y, 0, 0, w32.SWP_NOSIZE|w32.SWP_NOZORDER) +} + +func (w *windowsWebviewWindow) absolutePosition() (int, int) { + rect := w32.GetWindowRect(w.hwnd) + left, right := w.scaleToDefaultDPI(int(rect.Left), int(rect.Right)) + return left, right +} + +func (w *windowsWebviewWindow) setEnabled(enabled bool) { + w32.EnableWindow(w.hwnd, enabled) +} + +func (w *windowsWebviewWindow) print() error { + w.execJS("window.print();") + return nil +} + +func (w *windowsWebviewWindow) startResize(border string) error { + if !w32.ReleaseCapture() { + return fmt.Errorf("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) + return nil +} + +func (w *windowsWebviewWindow) startDrag() error { + if !w32.ReleaseCapture() { + return fmt.Errorf("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) + return nil +} + +func (w *windowsWebviewWindow) nativeWindowHandle() uintptr { + return w.hwnd +} + +func (w *windowsWebviewWindow) setTitle(title string) { + w32.SetWindowText(w.hwnd, title) +} + +func (w *windowsWebviewWindow) setSize(width, height int) { + rect := w32.GetWindowRect(w.hwnd) + width, height = w.scaleWithWindowDPI(width, height) + w32.MoveWindow(w.hwnd, int(rect.Left), int(rect.Top), width, height, true) + w.chromium.Resize() +} + +func (w *windowsWebviewWindow) setAlwaysOnTop(alwaysOnTop bool) { + w32.SetWindowPos(w.hwnd, + lo.Ternary(alwaysOnTop, w32.HWND_TOPMOST, w32.HWND_NOTOPMOST), + 0, + 0, + 0, + 0, + uint(w32.SWP_NOMOVE|w32.SWP_NOSIZE)) +} + +func (w *windowsWebviewWindow) setURL(url string) { + // Navigate to the given URL in the webview + w.chromium.Navigate(url) +} + +func (w *windowsWebviewWindow) setResizable(resizable bool) { + w.setStyle(resizable, w32.WS_THICKFRAME) + w.execJS(fmt.Sprintf("window._wails.setResizable(%v);", resizable)) +} + +func (w *windowsWebviewWindow) setMinSize(width, height int) { + w.parent.options.MinWidth = width + w.parent.options.MinHeight = height +} + +func (w *windowsWebviewWindow) setMaxSize(width, height int) { + w.parent.options.MaxWidth = width + w.parent.options.MaxHeight = height +} + +func (w *windowsWebviewWindow) execJS(js string) { + if w.chromium == nil { + return + } + globalApplication.dispatchOnMainThread(func() { + w.chromium.Eval(js) + }) +} + +func (w *windowsWebviewWindow) setBackgroundColour(color RGBA) { + w32.SetBackgroundColour(w.hwnd, color.Red, color.Green, color.Blue) +} + +func (w *windowsWebviewWindow) framelessWithDecorations() bool { + return w.parent.options.Frameless && !w.parent.options.Windows.DisableFramelessWindowDecorations +} + +func (w *windowsWebviewWindow) run() { + + options := w.parent.options + + w.chromium = edge.NewChromium() + + var exStyle uint + exStyle = w32.WS_EX_CONTROLPARENT + if options.BackgroundType != BackgroundTypeSolid { + exStyle |= w32.WS_EX_NOREDIRECTIONBITMAP + if w.parent.options.IgnoreMouseEvents { + exStyle |= w32.WS_EX_TRANSPARENT | w32.WS_EX_LAYERED + } + } + if options.AlwaysOnTop { + exStyle |= w32.WS_EX_TOPMOST + } + // If we're frameless, we need to add the WS_EX_TOOLWINDOW style to hide the window from the taskbar + if options.Windows.HiddenOnTaskbar { + exStyle |= w32.WS_EX_TOOLWINDOW + } else { + exStyle |= w32.WS_EX_APPWINDOW + } + + var startX, _ = lo.Coalesce(options.X, w32.CW_USEDEFAULT) + var startY, _ = lo.Coalesce(options.Y, w32.CW_USEDEFAULT) + + var appMenu w32.HMENU + + // Process Menu + if !options.Windows.DisableMenu && !options.Frameless { + theMenu := globalApplication.ApplicationMenu + // Create the menu if we have one + if w.parent.options.Windows.Menu != nil { + theMenu = w.parent.options.Windows.Menu + } + if theMenu != nil { + w.menu = NewApplicationMenu(w.hwnd, theMenu) + appMenu = w.menu.menu + } + } + + var parent w32.HWND + + var style uint = w32.WS_OVERLAPPEDWINDOW + + w.hwnd = w32.CreateWindowEx( + exStyle, + windowClassName, + w32.MustStringToUTF16Ptr(options.Title), + style, + startX, + startY, + options.Width, + options.Height, + parent, + appMenu, + w32.GetModuleHandle(""), + nil) + + if w.hwnd == 0 { + panic("Unable to create window") + } + + w.setupChromium() + + // Register the window with the application + getNativeApplication().registerWindow(w) + + w.setResizable(!options.DisableResize) + + if options.Frameless { + // Inform the application of the frame change this is needed to trigger the WM_NCCALCSIZE event. + // => https://learn.microsoft.com/en-us/windows/win32/dwm/customframe#removing-the-standard-frame + // This is normally done in WM_CREATE but we can't handle that there because that is emitted during CreateWindowEx + // and at that time we can't yet register the window for calling our WndProc method. + // This must be called after setResizable above! + rcClient := w32.GetWindowRect(w.hwnd) + w32.SetWindowPos(w.hwnd, + 0, + int(rcClient.Left), + int(rcClient.Top), + int(rcClient.Right-rcClient.Left), + int(rcClient.Bottom-rcClient.Top), + w32.SWP_FRAMECHANGED) + } + + // Icon + if !options.Windows.DisableIcon { + // App icon ID is 3 + icon, err := NewIconFromResource(w32.GetModuleHandle(""), uint16(3)) + if err == nil { + w.setIcon(icon) + } + } else { + w.disableIcon() + } + + // Process the theme + switch options.Windows.Theme { + case SystemDefault: + w.updateTheme(w32.IsCurrentlyDarkMode()) + w.parent.onApplicationEvent(events.Windows.SystemThemeChanged, func(*Event) { + w.updateTheme(w32.IsCurrentlyDarkMode()) + }) + case Light: + w.updateTheme(false) + case Dark: + w.updateTheme(true) + } + + switch options.BackgroundType { + case BackgroundTypeSolid: + var col = options.BackgroundColour + w.setBackgroundColour(col) + w.chromium.SetBackgroundColour(col.Red, col.Green, col.Blue, col.Alpha) + case BackgroundTypeTransparent: + w.chromium.SetBackgroundColour(0, 0, 0, 0) + case BackgroundTypeTranslucent: + w.chromium.SetBackgroundColour(0, 0, 0, 0) + w.setBackdropType(options.Windows.BackdropType) + } + + // Process StartState + switch options.StartState { + case WindowStateMaximised: + if w.parent.Resizable() { + w.maximise() + } + case WindowStateMinimised: + w.minimise() + case WindowStateFullscreen: + w.fullscreen() + } + + // Process window mask + if options.Windows.WindowMask != nil { + w.setWindowMask(options.Windows.WindowMask) + } + + if options.Windows.ResizeDebounceMS > 0 { + w.resizeDebouncer = debounce.New(time.Duration(options.Windows.ResizeDebounceMS) * time.Millisecond) + } + + if options.Centered { + w.center() + } + + if options.Focused { + w.Focus() + } + + if options.Frameless { + // Trigger a resize to ensure the window is sized correctly + w.chromium.Resize() + } + + if !options.Hidden { + w.show() + w.update() + } +} + +func (w *windowsWebviewWindow) center() { + w32.CenterWindow(w.hwnd) +} + +func (w *windowsWebviewWindow) disableSizeConstraints() { + w.setMaxSize(0, 0) + w.setMinSize(0, 0) +} + +func (w *windowsWebviewWindow) enableSizeConstraints() { + options := w.parent.options + if options.MinWidth > 0 || options.MinHeight > 0 { + w.setMinSize(options.MinWidth, options.MinHeight) + } + if options.MaxWidth > 0 || options.MaxHeight > 0 { + w.setMaxSize(options.MaxWidth, options.MaxHeight) + } +} + +func (w *windowsWebviewWindow) size() (int, int) { + rect := w32.GetWindowRect(w.hwnd) + width := int(rect.Right - rect.Left) + height := int(rect.Bottom - rect.Top) + // Scaling appears to give invalid results... + //width, height = w.scaleToDefaultDPI(width, height) + return width, height +} + +func (w *windowsWebviewWindow) Focus() { + w32.SetForegroundWindow(w.hwnd) +} + +func (w *windowsWebviewWindow) update() { + w32.UpdateWindow(w.hwnd) +} + +func (w *windowsWebviewWindow) width() int { + width, _ := w.size() + return width +} + +func (w *windowsWebviewWindow) height() int { + _, height := w.size() + return height +} + +func (w *windowsWebviewWindow) relativePosition() (int, int) { + // Get monitor for window + monitor := w32.MonitorFromWindow(w.hwnd, w32.MONITOR_DEFAULTTONEAREST) + var monitorInfo w32.MONITORINFO + monitorInfo.CbSize = uint32(unsafe.Sizeof(monitorInfo)) + w32.GetMonitorInfo(monitor, &monitorInfo) + + // Get window rect + rect := w32.GetWindowRect(w.hwnd) + + // Calculate relative position + x := int(rect.Left) - int(monitorInfo.RcWork.Left) + y := int(rect.Top) - int(monitorInfo.RcWork.Top) + + return w.scaleToDefaultDPI(x, y) +} + +func (w *windowsWebviewWindow) destroy() { + // Not sure if we have anything to destroy... + if w.dropTarget != nil { + w.dropTarget.Release() + } +} + +func (w *windowsWebviewWindow) reload() { + w.execJS("window.location.reload();") +} + +func (w *windowsWebviewWindow) forceReload() { + //TODO implement me + panic("implement me") +} + +func (w *windowsWebviewWindow) toggleDevTools() { + showDevTools(w.chromium) +} + +func (w *windowsWebviewWindow) zoomReset() { + w.setZoom(1.0) +} + +func (w *windowsWebviewWindow) zoomIn() { + // Increase the zoom level by 0.05 + currentZoom := w.getZoom() + if currentZoom == -1 { + return + } + w.setZoom(currentZoom + 0.05) +} + +func (w *windowsWebviewWindow) zoomOut() { + // Decrease the zoom level by 0.05 + currentZoom := w.getZoom() + if currentZoom == -1 { + return + } + if currentZoom > 1.05 { + // Decrease the zoom level by 0.05 + w.setZoom(currentZoom - 0.05) + } else { + // Set the zoom level to 1.0 + w.setZoom(1.0) + } +} + +func (w *windowsWebviewWindow) getZoom() float64 { + controller := w.chromium.GetController() + factor, err := controller.GetZoomFactor() + if err != nil { + return -1 + } + return factor +} + +func (w *windowsWebviewWindow) setZoom(zoom float64) { + w.chromium.PutZoomFactor(zoom) +} + +func (w *windowsWebviewWindow) close() { + // Unregister the window with the application + windowsApp := globalApplication.impl.(*windowsApp) + windowsApp.unregisterWindow(w) + w32.SendMessage(w.hwnd, w32.WM_CLOSE, 0, 0) +} + +func (w *windowsWebviewWindow) zoom() { + //TODO implement me + panic("implement me") +} + +func (w *windowsWebviewWindow) setHTML(html string) { + // Render the given HTML in the webview window + w.execJS(fmt.Sprintf("document.documentElement.innerHTML = %q;", html)) +} + +func (w *windowsWebviewWindow) setRelativePosition(x int, y int) { + //x, y = w.scaleWithWindowDPI(x, y) + info := w32.GetMonitorInfoForWindow(w.hwnd) + workRect := info.RcWork + w32.SetWindowPos(w.hwnd, w32.HWND_TOP, int(workRect.Left)+x, int(workRect.Top)+y, 0, 0, w32.SWP_NOSIZE) +} + +// on is used to indicate that a particular event should be listened for +func (w *windowsWebviewWindow) on(_ uint) { + // We don't need to worry about this in Windows as we do not need + // to optimise cgo calls +} + +func (w *windowsWebviewWindow) minimise() { + w32.ShowWindow(w.hwnd, w32.SW_MINIMIZE) +} + +func (w *windowsWebviewWindow) unminimise() { + w.restore() +} + +func (w *windowsWebviewWindow) maximise() { + w32.ShowWindow(w.hwnd, w32.SW_MAXIMIZE) + w.chromium.Focus() +} + +func (w *windowsWebviewWindow) unmaximise() { + w.restore() +} + +func (w *windowsWebviewWindow) restore() { + w32.ShowWindow(w.hwnd, w32.SW_RESTORE) + w.chromium.Focus() +} + +func (w *windowsWebviewWindow) fullscreen() { + if w.isFullscreen() { + return + } + if w.framelessWithDecorations() { + w32.ExtendFrameIntoClientArea(w.hwnd, false) + } + w.disableSizeConstraints() + w.previousWindowStyle = uint32(w32.GetWindowLongPtr(w.hwnd, w32.GWL_STYLE)) + w.previousWindowExStyle = uint32(w32.GetWindowLong(w.hwnd, w32.GWL_EXSTYLE)) + monitor := w32.MonitorFromWindow(w.hwnd, w32.MONITOR_DEFAULTTOPRIMARY) + var monitorInfo w32.MONITORINFO + monitorInfo.CbSize = uint32(unsafe.Sizeof(monitorInfo)) + if !w32.GetMonitorInfo(monitor, &monitorInfo) { + return + } + if !w32.GetWindowPlacement(w.hwnd, &w.previousWindowPlacement) { + return + } + // According to https://devblogs.microsoft.com/oldnewthing/20050505-04/?p=35703 one should use w32.WS_POPUP | w32.WS_VISIBLE + w32.SetWindowLong(w.hwnd, w32.GWL_STYLE, w.previousWindowStyle & ^uint32(w32.WS_OVERLAPPEDWINDOW) | (w32.WS_POPUP|w32.WS_VISIBLE)) + w32.SetWindowLong(w.hwnd, w32.GWL_EXSTYLE, w.previousWindowExStyle & ^uint32(w32.WS_EX_DLGMODALFRAME)) + w.isCurrentlyFullscreen = true + w32.SetWindowPos(w.hwnd, w32.HWND_TOP, + int(monitorInfo.RcMonitor.Left), + int(monitorInfo.RcMonitor.Top), + int(monitorInfo.RcMonitor.Right-monitorInfo.RcMonitor.Left), + int(monitorInfo.RcMonitor.Bottom-monitorInfo.RcMonitor.Top), + w32.SWP_NOOWNERZORDER|w32.SWP_FRAMECHANGED) + w.chromium.Focus() +} + +func (w *windowsWebviewWindow) unfullscreen() { + if !w.isFullscreen() { + return + } + if w.framelessWithDecorations() { + w32.ExtendFrameIntoClientArea(w.hwnd, true) + } + w32.SetWindowLong(w.hwnd, w32.GWL_STYLE, w.previousWindowStyle) + w32.SetWindowLong(w.hwnd, w32.GWL_EXSTYLE, w.previousWindowExStyle) + w32.SetWindowPlacement(w.hwnd, &w.previousWindowPlacement) + w.isCurrentlyFullscreen = false + w32.SetWindowPos(w.hwnd, 0, 0, 0, 0, 0, + w32.SWP_NOMOVE|w32.SWP_NOSIZE|w32.SWP_NOZORDER|w32.SWP_NOOWNERZORDER|w32.SWP_FRAMECHANGED) + w.enableSizeConstraints() +} + +func (w *windowsWebviewWindow) isMinimised() bool { + style := uint32(w32.GetWindowLong(w.hwnd, w32.GWL_STYLE)) + return style&w32.WS_MINIMIZE != 0 +} + +func (w *windowsWebviewWindow) isMaximised() bool { + style := uint32(w32.GetWindowLong(w.hwnd, w32.GWL_STYLE)) + return style&w32.WS_MAXIMIZE != 0 +} + +func (w *windowsWebviewWindow) isFocused() bool { + // Returns true if the window is currently focused + return w32.GetForegroundWindow() == w.hwnd +} + +func (w *windowsWebviewWindow) isFullscreen() bool { + // TODO: Actually calculate this based on size of window against screen size + // => stffabi: This flag is essential since it indicates that we are in fullscreen mode even before the native properties + // reflect this, e.g. when needing to know if we are in fullscreen during a wndproc message. + // That's also why this flag is set before SetWindowPos in v2 in fullscreen/unfullscreen. + return w.isCurrentlyFullscreen +} + +func (w *windowsWebviewWindow) isNormal() bool { + return !w.isMinimised() && !w.isMaximised() && !w.isFullscreen() +} + +func (w *windowsWebviewWindow) isVisible() bool { + style := uint32(w32.GetWindowLong(w.hwnd, w32.GWL_STYLE)) + return style&w32.WS_VISIBLE != 0 +} + +func (w *windowsWebviewWindow) setFullscreenButtonEnabled(_ bool) { + // Unused in Windows +} + +func (w *windowsWebviewWindow) focus() { + w32.SetForegroundWindow(w.hwnd) + w.focusingChromium = true + w.chromium.Focus() + w.focusingChromium = false +} + +// printStyle takes a windows style and prints it in a human-readable format +// This is for debugging window style issues +func (w *windowsWebviewWindow) printStyle() { + style := uint32(w32.GetWindowLong(w.hwnd, w32.GWL_STYLE)) + fmt.Printf("Style: ") + if style&w32.WS_BORDER != 0 { + fmt.Printf("WS_BORDER ") + } + if style&w32.WS_CAPTION != 0 { + fmt.Printf("WS_CAPTION ") + } + if style&w32.WS_CHILD != 0 { + fmt.Printf("WS_CHILD ") + } + if style&w32.WS_CLIPCHILDREN != 0 { + fmt.Printf("WS_CLIPCHILDREN ") + } + if style&w32.WS_CLIPSIBLINGS != 0 { + fmt.Printf("WS_CLIPSIBLINGS ") + } + if style&w32.WS_DISABLED != 0 { + fmt.Printf("WS_DISABLED ") + } + if style&w32.WS_DLGFRAME != 0 { + fmt.Printf("WS_DLGFRAME ") + } + if style&w32.WS_GROUP != 0 { + fmt.Printf("WS_GROUP ") + } + if style&w32.WS_HSCROLL != 0 { + fmt.Printf("WS_HSCROLL ") + } + if style&w32.WS_MAXIMIZE != 0 { + fmt.Printf("WS_MAXIMIZE ") + } + if style&w32.WS_MAXIMIZEBOX != 0 { + fmt.Printf("WS_MAXIMIZEBOX ") + } + if style&w32.WS_MINIMIZE != 0 { + fmt.Printf("WS_MINIMIZE ") + } + if style&w32.WS_MINIMIZEBOX != 0 { + fmt.Printf("WS_MINIMIZEBOX ") + } + if style&w32.WS_OVERLAPPED != 0 { + fmt.Printf("WS_OVERLAPPED ") + } + if style&w32.WS_POPUP != 0 { + fmt.Printf("WS_POPUP ") + } + if style&w32.WS_SYSMENU != 0 { + fmt.Printf("WS_SYSMENU ") + } + if style&w32.WS_TABSTOP != 0 { + fmt.Printf("WS_TABSTOP ") + } + if style&w32.WS_THICKFRAME != 0 { + fmt.Printf("WS_THICKFRAME ") + } + if style&w32.WS_VISIBLE != 0 { + fmt.Printf("WS_VISIBLE ") + } + if style&w32.WS_VSCROLL != 0 { + fmt.Printf("WS_VSCROLL ") + } + fmt.Printf("\n") + + // Do the same for the extended style + extendedStyle := uint32(w32.GetWindowLong(w.hwnd, w32.GWL_EXSTYLE)) + fmt.Printf("Extended Style: ") + if extendedStyle&w32.WS_EX_ACCEPTFILES != 0 { + fmt.Printf("WS_EX_ACCEPTFILES ") + } + if extendedStyle&w32.WS_EX_APPWINDOW != 0 { + fmt.Printf("WS_EX_APPWINDOW ") + } + if extendedStyle&w32.WS_EX_CLIENTEDGE != 0 { + fmt.Printf("WS_EX_CLIENTEDGE ") + } + if extendedStyle&w32.WS_EX_COMPOSITED != 0 { + fmt.Printf("WS_EX_COMPOSITED ") + } + if extendedStyle&w32.WS_EX_CONTEXTHELP != 0 { + fmt.Printf("WS_EX_CONTEXTHELP ") + } + if extendedStyle&w32.WS_EX_CONTROLPARENT != 0 { + fmt.Printf("WS_EX_CONTROLPARENT ") + } + if extendedStyle&w32.WS_EX_DLGMODALFRAME != 0 { + fmt.Printf("WS_EX_DLGMODALFRAME ") + } + if extendedStyle&w32.WS_EX_LAYERED != 0 { + fmt.Printf("WS_EX_LAYERED ") + } + if extendedStyle&w32.WS_EX_LAYOUTRTL != 0 { + fmt.Printf("WS_EX_LAYOUTRTL ") + } + if extendedStyle&w32.WS_EX_LEFT != 0 { + fmt.Printf("WS_EX_LEFT ") + } + if extendedStyle&w32.WS_EX_LEFTSCROLLBAR != 0 { + fmt.Printf("WS_EX_LEFTSCROLLBAR ") + } + if extendedStyle&w32.WS_EX_LTRREADING != 0 { + fmt.Printf("WS_EX_LTRREADING ") + } + if extendedStyle&w32.WS_EX_MDICHILD != 0 { + fmt.Printf("WS_EX_MDICHILD ") + } + if extendedStyle&w32.WS_EX_NOACTIVATE != 0 { + fmt.Printf("WS_EX_NOACTIVATE ") + } + if extendedStyle&w32.WS_EX_NOINHERITLAYOUT != 0 { + fmt.Printf("WS_EX_NOINHERITLAYOUT ") + } + if extendedStyle&w32.WS_EX_NOPARENTNOTIFY != 0 { + fmt.Printf("WS_EX_NOPARENTNOTIFY ") + } + if extendedStyle&w32.WS_EX_NOREDIRECTIONBITMAP != 0 { + fmt.Printf("WS_EX_NOREDIRECTIONBITMAP ") + } + if extendedStyle&w32.WS_EX_OVERLAPPEDWINDOW != 0 { + fmt.Printf("WS_EX_OVERLAPPEDWINDOW ") + } + if extendedStyle&w32.WS_EX_PALETTEWINDOW != 0 { + fmt.Printf("WS_EX_PALETTEWINDOW ") + } + if extendedStyle&w32.WS_EX_RIGHT != 0 { + fmt.Printf("WS_EX_RIGHT ") + } + if extendedStyle&w32.WS_EX_RIGHTSCROLLBAR != 0 { + fmt.Printf("WS_EX_RIGHTSCROLLBAR ") + } + if extendedStyle&w32.WS_EX_RTLREADING != 0 { + fmt.Printf("WS_EX_RTLREADING ") + } + if extendedStyle&w32.WS_EX_STATICEDGE != 0 { + fmt.Printf("WS_EX_STATICEDGE ") + } + if extendedStyle&w32.WS_EX_TOOLWINDOW != 0 { + fmt.Printf("WS_EX_TOOLWINDOW ") + } + if extendedStyle&w32.WS_EX_TOPMOST != 0 { + fmt.Printf("WS_EX_TOPMOST ") + } + if extendedStyle&w32.WS_EX_TRANSPARENT != 0 { + fmt.Printf("WS_EX_TRANSPARENT ") + } + if extendedStyle&w32.WS_EX_WINDOWEDGE != 0 { + fmt.Printf("WS_EX_WINDOWEDGE ") + } + fmt.Printf("\n") + +} + +func (w *windowsWebviewWindow) show() { + w32.ShowWindow(w.hwnd, w32.SW_SHOW) +} + +func (w *windowsWebviewWindow) hide() { + w32.ShowWindow(w.hwnd, w32.SW_HIDE) +} + +func getScreen(hwnd w32.HWND) (*Screen, error) { + hMonitor := w32.MonitorFromWindow(hwnd, w32.MONITOR_DEFAULTTONEAREST) + var mi w32.MONITORINFOEX + mi.CbSize = uint32(unsafe.Sizeof(mi)) + w32.GetMonitorInfoEx(hMonitor, &mi) + var thisScreen Screen + thisScreen.X = int(mi.RcMonitor.Left) + thisScreen.Y = int(mi.RcMonitor.Top) + thisScreen.Size = Size{ + Width: int(mi.RcMonitor.Right - mi.RcMonitor.Left), + Height: int(mi.RcMonitor.Bottom - mi.RcMonitor.Top), + } + thisScreen.Bounds = Rect{ + X: int(mi.RcMonitor.Left), + Y: int(mi.RcMonitor.Top), + Width: int(mi.RcMonitor.Right - mi.RcMonitor.Left), + Height: int(mi.RcMonitor.Bottom - mi.RcMonitor.Top), + } + thisScreen.WorkArea = Rect{ + X: int(mi.RcWork.Left), + Y: int(mi.RcWork.Top), + Width: int(mi.RcWork.Right - mi.RcWork.Left), + Height: int(mi.RcWork.Bottom - mi.RcWork.Top), + } + thisScreen.ID = strconv.Itoa(int(hMonitor)) + thisScreen.Name = string(utf16.Decode(mi.SzDevice[:])) + var xdpi, ydpi w32.UINT + w32.GetDPIForMonitor(hMonitor, w32.MDT_EFFECTIVE_DPI, &xdpi, &ydpi) + thisScreen.Scale = float32(xdpi) / 96.0 + thisScreen.IsPrimary = mi.DwFlags&w32.MONITORINFOF_PRIMARY != 0 + + // TODO: Get screen rotation + + return &thisScreen, nil +} + +// Get the screen for the current window +func (w *windowsWebviewWindow) getScreen() (*Screen, error) { + return getScreen(w.hwnd) +} + +func (w *windowsWebviewWindow) setFrameless(b bool) { + // Remove or add the frame + if b { + w32.SetWindowLong(w.hwnd, w32.GWL_STYLE, w32.WS_VISIBLE|w32.WS_POPUP) + } else { + w32.SetWindowLong(w.hwnd, w32.GWL_STYLE, w32.WS_VISIBLE|w32.WS_OVERLAPPEDWINDOW) + } +} + +func newWindowImpl(parent *WebviewWindow) *windowsWebviewWindow { + result := &windowsWebviewWindow{ + parent: parent, + resizeBorderWidth: int32(w32.GetSystemMetrics(w32.SM_CXSIZEFRAME)), + resizeBorderHeight: int32(w32.GetSystemMetrics(w32.SM_CYSIZEFRAME)), + } + + return result +} + +func (w *windowsWebviewWindow) openContextMenu(menu *Menu, _ *ContextMenuData) { + // Create the menu + thisMenu := NewPopupMenu(w.hwnd, menu) + thisMenu.Update() + w.currentlyOpenContextMenu = thisMenu + thisMenu.ShowAtCursor() +} + +func (w *windowsWebviewWindow) setStyle(b bool, style int) { + currentStyle := int(w32.GetWindowLongPtr(w.hwnd, w32.GWL_STYLE)) + if currentStyle != 0 { + currentStyle = lo.Ternary(b, currentStyle|style, currentStyle&^style) + w32.SetWindowLongPtr(w.hwnd, w32.GWL_STYLE, uintptr(currentStyle)) + } +} +func (w *windowsWebviewWindow) setExStyle(b bool, style int) { + currentStyle := int(w32.GetWindowLongPtr(w.hwnd, w32.GWL_EXSTYLE)) + if currentStyle != 0 { + currentStyle = lo.Ternary(b, currentStyle|style, currentStyle&^style) + w32.SetWindowLongPtr(w.hwnd, w32.GWL_EXSTYLE, uintptr(currentStyle)) + } +} + +func (w *windowsWebviewWindow) setBackdropType(backdropType BackdropType) { + if !w32.SupportsBackdropTypes() { + var accent = w32.ACCENT_POLICY{ + AccentState: w32.ACCENT_ENABLE_BLURBEHIND, + } + var data w32.WINDOWCOMPOSITIONATTRIBDATA + data.Attrib = w32.WCA_ACCENT_POLICY + data.PvData = w32.PVOID(&accent) + data.CbData = unsafe.Sizeof(accent) + + w32.SetWindowCompositionAttribute(w.hwnd, &data) + } else { + w32.EnableTranslucency(w.hwnd, int32(backdropType)) + } +} + +func (w *windowsWebviewWindow) setIcon(icon w32.HICON) { + w32.SendMessage(w.hwnd, w32.BM_SETIMAGE, w32.IMAGE_ICON, icon) +} + +func (w *windowsWebviewWindow) disableIcon() { + + // TODO: If frameless, return + exStyle := w32.GetWindowLong(w.hwnd, w32.GWL_EXSTYLE) + w32.SetWindowLong(w.hwnd, w32.GWL_EXSTYLE, uint32(exStyle|w32.WS_EX_DLGMODALFRAME)) + w32.SetWindowPos(w.hwnd, 0, 0, 0, 0, 0, + uint( + w32.SWP_FRAMECHANGED| + w32.SWP_NOMOVE| + w32.SWP_NOSIZE| + w32.SWP_NOZORDER), + ) +} + +func (w *windowsWebviewWindow) updateTheme(isDarkMode bool) { + + if w32.IsCurrentlyHighContrastMode() { + return + } + + if !w32.SupportsThemes() { + return + } + + w32.SetTheme(w.hwnd, isDarkMode) + + // Custom theme processing + customTheme := w.parent.options.Windows.CustomTheme + // Custom theme + if w32.SupportsCustomThemes() && customTheme != nil { + if w.isActive() { + if isDarkMode { + w32.SetTitleBarColour(w.hwnd, customTheme.DarkModeTitleBar) + w32.SetTitleTextColour(w.hwnd, customTheme.DarkModeTitleText) + w32.SetBorderColour(w.hwnd, customTheme.DarkModeBorder) + } else { + w32.SetTitleBarColour(w.hwnd, customTheme.LightModeTitleBar) + w32.SetTitleTextColour(w.hwnd, customTheme.LightModeTitleText) + w32.SetBorderColour(w.hwnd, customTheme.LightModeBorder) + } + } else { + if isDarkMode { + w32.SetTitleBarColour(w.hwnd, customTheme.DarkModeTitleBarInactive) + w32.SetTitleTextColour(w.hwnd, customTheme.DarkModeTitleTextInactive) + w32.SetBorderColour(w.hwnd, customTheme.DarkModeBorderInactive) + } else { + w32.SetTitleBarColour(w.hwnd, customTheme.LightModeTitleBarInactive) + w32.SetTitleTextColour(w.hwnd, customTheme.LightModeTitleTextInactive) + w32.SetBorderColour(w.hwnd, customTheme.LightModeBorderInactive) + } + } + } +} + +func (w *windowsWebviewWindow) isActive() bool { + return w32.GetForegroundWindow() == w.hwnd +} + +func (w *windowsWebviewWindow) WndProc(msg uint32, wparam, lparam uintptr) uintptr { + switch msg { + case w32.WM_ACTIVATE: + if int(wparam&0xffff) == w32.WA_INACTIVE { + w.parent.emit(events.Windows.WindowInactive) + } + if wparam == w32.WA_ACTIVE { + getNativeApplication().currentWindowID = w.parent.id + w.parent.emit(events.Windows.WindowActive) + } + if wparam == w32.WA_CLICKACTIVE { + getNativeApplication().currentWindowID = w.parent.id + w.parent.emit(events.Windows.WindowClickActive) + } + // If we want to have a frameless window but with the default frame decorations, extend the DWM client area. + // This Option is not affected by returning 0 in WM_NCCALCSIZE. + // As a result we have hidden the titlebar but still have the default window frame styling. + // See: https://docs.microsoft.com/en-us/windows/win32/api/dwmapi/nf-dwmapi-dwmextendframeintoclientarea#remarks + if w.framelessWithDecorations() { + w32.ExtendFrameIntoClientArea(w.hwnd, true) + } + case w32.WM_CLOSE: + w.parent.emit(events.Windows.WindowClose) + return 0 + case w32.WM_KILLFOCUS: + if w.focusingChromium { + return 0 + } + w.parent.emit(events.Windows.WindowKillFocus) + case w32.WM_SETFOCUS: + w.parent.emit(events.Windows.WindowSetFocus) + case w32.WM_NCLBUTTONDOWN: + w32.SetFocus(w.hwnd) + case w32.WM_MOVE, w32.WM_MOVING: + _ = w.chromium.NotifyParentWindowPositionChanged() + // Check for keypress + case w32.WM_KEYDOWN: + w.processKeyBinding(uint(wparam)) + case w32.WM_SIZE: + switch wparam { + case w32.SIZE_MAXIMIZED: + w.parent.emit(events.Windows.WindowMaximise) + case w32.SIZE_RESTORED: + w.parent.emit(events.Windows.WindowRestore) + case w32.SIZE_MINIMIZED: + w.parent.emit(events.Windows.WindowMinimise) + } + if w.parent.options.Frameless && wparam == w32.SIZE_MINIMIZED { + // If the window is frameless, and we are minimizing, then we need to suppress the Resize on the + // WebView2. If we don't do this, restoring does not work as expected and first restores with some wrong + // size during the restore animation and only fully renders when the animation is done. This highly + // depends on the content in the WebView, see https://github.com/wailsapp/wails/issues/1319 + } else if w.resizeDebouncer != nil { + w.resizeDebouncer(func() { + InvokeSync(func() { + w.chromium.Resize() + }) + }) + } else { + w.chromium.Resize() + } + return 0 + + case w32.WM_GETMINMAXINFO: + mmi := (*w32.MINMAXINFO)(unsafe.Pointer(lparam)) + hasConstraints := false + options := w.parent.options + if options.MinWidth > 0 || options.MinHeight > 0 { + hasConstraints = true + + width, height := w.scaleWithWindowDPI(options.MinWidth, options.MinHeight) + if width > 0 { + mmi.PtMinTrackSize.X = int32(width) + } + if height > 0 { + mmi.PtMinTrackSize.Y = int32(height) + } + } + if options.MaxWidth > 0 || options.MaxHeight > 0 { + hasConstraints = true + + width, height := w.scaleWithWindowDPI(options.MaxWidth, options.MaxHeight) + if width > 0 { + mmi.PtMaxTrackSize.X = int32(width) + } + if height > 0 { + mmi.PtMaxTrackSize.Y = int32(height) + } + } + if hasConstraints { + return 0 + } + + case w32.WM_DPICHANGED: + newWindowSize := (*w32.RECT)(unsafe.Pointer(lparam)) + w32.SetWindowPos(w.hwnd, + uintptr(0), + int(newWindowSize.Left), + int(newWindowSize.Top), + int(newWindowSize.Right-newWindowSize.Left), + int(newWindowSize.Bottom-newWindowSize.Top), + w32.SWP_NOZORDER|w32.SWP_NOACTIVATE) + w.parent.emit(events.Common.WindowDPIChanged) + } + + if w.parent.options.Windows.WindowMask != nil { + switch msg { + case w32.WM_NCHITTEST: + if w.parent.options.Windows.WindowMaskDraggable { + return w32.HTCAPTION + } + return w32.HTCLIENT + } + } + + if w.menu != nil || w.currentlyOpenContextMenu != nil { + switch msg { + case w32.WM_COMMAND: + cmdMsgID := int(wparam & 0xffff) + switch cmdMsgID { + default: + var processed bool + if w.currentlyOpenContextMenu != nil { + processed = w.currentlyOpenContextMenu.ProcessCommand(cmdMsgID) + w.currentlyOpenContextMenu = nil + + } + if !processed && w.menu != nil { + processed = w.menu.ProcessCommand(cmdMsgID) + } + } + } + } + + if options := w.parent.options; options.Frameless { + switch msg { + case w32.WM_ACTIVATE: + // If we want to have a frameless window but with the default frame decorations, extend the DWM client area. + // This Option is not affected by returning 0 in WM_NCCALCSIZE. + // As a result we have hidden the titlebar but still have the default window frame styling. + // See: https://docs.microsoft.com/en-us/windows/win32/api/dwmapi/nf-dwmapi-dwmextendframeintoclientarea#remarks + if w.framelessWithDecorations() { + w32.ExtendFrameIntoClientArea(w.hwnd, true) + } + + case w32.WM_NCCALCSIZE: + // Disable the standard frame by allowing the client area to take the full + // window size. + // See: https://docs.microsoft.com/en-us/windows/win32/winmsg/wm-nccalcsize#remarks + // This hides the titlebar and also disables the resizing from user interaction because the standard frame is not + // shown. We still need the WS_THICKFRAME style to enable resizing from the frontend. + if wparam != 0 { + rgrc := (*w32.RECT)(unsafe.Pointer(lparam)) + if w.isCurrentlyFullscreen { + // In Full-Screen mode we don't need to adjust anything + // It essential we have the flag here, that is set before SetWindowPos in fullscreen/unfullscreen + // because the native size might not yet reflect we are in fullscreen during this event! + w.chromium.SetPadding(edge.Rect{}) + } else if w.isMaximised() { + // If the window is maximized we must adjust the client area to the work area of the monitor. Otherwise + // some content goes beyond the visible part of the monitor. + // Make sure to use the provided RECT to get the monitor, because during maximizig there might be + // a wrong monitor returned in multiscreen mode when using MonitorFromWindow. + // See: https://github.com/MicrosoftEdge/WebView2Feedback/issues/2549 + monitor := w32.MonitorFromRect(rgrc, w32.MONITOR_DEFAULTTONULL) + + var monitorInfo w32.MONITORINFO + monitorInfo.CbSize = uint32(unsafe.Sizeof(monitorInfo)) + if monitor != 0 && w32.GetMonitorInfo(monitor, &monitorInfo) { + *rgrc = monitorInfo.RcWork + + maxWidth := options.MaxWidth + maxHeight := options.MaxHeight + if maxWidth > 0 || maxHeight > 0 { + var dpiX, dpiY uint + w32.GetDPIForMonitor(monitor, w32.MDT_EFFECTIVE_DPI, &dpiX, &dpiY) + + maxWidth := int32(ScaleWithDPI(maxWidth, dpiX)) + if maxWidth > 0 && rgrc.Right-rgrc.Left > maxWidth { + rgrc.Right = rgrc.Left + maxWidth + } + + maxHeight := int32(ScaleWithDPI(maxHeight, dpiY)) + if maxHeight > 0 && rgrc.Bottom-rgrc.Top > maxHeight { + rgrc.Bottom = rgrc.Top + maxHeight + } + } + } + w.chromium.SetPadding(edge.Rect{}) + } else { + // This is needed to workaround the resize flickering in frameless mode with WindowDecorations + // See: https://stackoverflow.com/a/6558508 + // The workaround originally suggests to decrese the bottom 1px, but that seems to bring up a thin + // white line on some Windows-Versions, due to DrawBackground using also this reduces ClientSize. + // Increasing the bottom also worksaround the flickering but we would loose 1px of the WebView content + // therefore let's pad the content with 1px at the bottom. + rgrc.Bottom += 1 + w.chromium.SetPadding(edge.Rect{Bottom: 1}) + } + return 0 + } + } + } + return w32.DefWindowProc(w.hwnd, msg, wparam, lparam) +} + +func (w *windowsWebviewWindow) DPI() (w32.UINT, w32.UINT) { + if w32.HasGetDpiForWindowFunc() { + // GetDpiForWindow is supported beginning with Windows 10, 1607 and is the most accureate + // one, especially it is consistent with the WM_DPICHANGED event. + dpi := w32.GetDpiForWindow(w.hwnd) + return dpi, dpi + } + + if w32.HasGetDPIForMonitorFunc() { + // GetDpiForWindow is supported beginning with Windows 8.1 + monitor := w32.MonitorFromWindow(w.hwnd, w32.MONITOR_DEFAULTTONEAREST) + if monitor == 0 { + return 0, 0 + } + var dpiX, dpiY w32.UINT + w32.GetDPIForMonitor(monitor, w32.MDT_EFFECTIVE_DPI, &dpiX, &dpiY) + return dpiX, dpiY + } + + // If none of the above is supported fallback to the System DPI. + screen := w32.GetDC(0) + x := w32.GetDeviceCaps(screen, w32.LOGPIXELSX) + y := w32.GetDeviceCaps(screen, w32.LOGPIXELSY) + w32.ReleaseDC(0, screen) + return w32.UINT(x), w32.UINT(y) +} + +func (w *windowsWebviewWindow) scaleWithWindowDPI(width, height int) (int, int) { + dpix, dpiy := w.DPI() + scaledWidth := ScaleWithDPI(width, dpix) + scaledHeight := ScaleWithDPI(height, dpiy) + + return scaledWidth, scaledHeight +} + +func (w *windowsWebviewWindow) scaleToDefaultDPI(width, height int) (int, int) { + dpix, dpiy := w.DPI() + scaledWidth := ScaleToDefaultDPI(width, dpix) + scaledHeight := ScaleToDefaultDPI(height, dpiy) + + return scaledWidth, scaledHeight +} + +func (w *windowsWebviewWindow) setWindowMask(imageData []byte) { + + // Set the window to a WS_EX_LAYERED window + newStyle := w32.GetWindowLong(w.hwnd, w32.GWL_EXSTYLE) | w32.WS_EX_LAYERED + + if w.isAlwaysOnTop() { + newStyle |= w32.WS_EX_TOPMOST + } + // Save the current window style + w.previousWindowExStyle = uint32(w32.GetWindowLong(w.hwnd, w32.GWL_EXSTYLE)) + + w32.SetWindowLong(w.hwnd, w32.GWL_EXSTYLE, uint32(newStyle)) + + data, err := pngToImage(imageData) + if err != nil { + panic(err) + } + + bitmap, err := w32.CreateHBITMAPFromImage(data) + hdc := w32.CreateCompatibleDC(0) + defer w32.DeleteDC(hdc) + + oldBitmap := w32.SelectObject(hdc, bitmap) + defer w32.SelectObject(hdc, oldBitmap) + + screenDC := w32.GetDC(0) + defer w32.ReleaseDC(0, screenDC) + + size := w32.SIZE{CX: int32(data.Bounds().Dx()), CY: int32(data.Bounds().Dy())} + ptSrc := w32.POINT{X: 0, Y: 0} + ptDst := w32.POINT{X: int32(w.width()), Y: int32(w.height())} + blend := w32.BLENDFUNCTION{ + BlendOp: w32.AC_SRC_OVER, + BlendFlags: 0, + SourceConstantAlpha: 255, + AlphaFormat: w32.AC_SRC_ALPHA, + } + w32.UpdateLayeredWindow(w.hwnd, screenDC, &ptDst, &size, hdc, &ptSrc, 0, &blend, w32.ULW_ALPHA) +} + +func (w *windowsWebviewWindow) isAlwaysOnTop() bool { + return w32.GetWindowLong(w.hwnd, w32.GWL_EXSTYLE)&w32.WS_EX_TOPMOST != 0 +} + +// processMessage is given a message sent from JS via the postMessage API +// We put it on the global window message buffer to be processed centrally +func (w *windowsWebviewWindow) processMessage(message string) { + // We send all messages to the centralised window message buffer + windowMessageBuffer <- &windowMessage{ + windowId: w.parent.id, + message: message, + } +} + +func (w *windowsWebviewWindow) processRequest(req *edge.ICoreWebView2WebResourceRequest, args *edge.ICoreWebView2WebResourceRequestedEventArgs) { + + // Setting the UserAgent on the CoreWebView2Settings clears the whole default UserAgent of the Edge browser, but + // we want to just append our ApplicationIdentifier. So we adjust the UserAgent for every request. + if reqHeaders, err := req.GetHeaders(); err == nil { + useragent, _ := reqHeaders.GetHeader(assetserver.HeaderUserAgent) + useragent = strings.Join([]string{useragent, assetserver.WailsUserAgentValue}, " ") + reqHeaders.SetHeader(assetserver.HeaderUserAgent, useragent) + reqHeaders.SetHeader(webViewRequestHeaderWindowId, strconv.FormatUint(uint64(w.parent.id), 10)) + reqHeaders.Release() + } + + if globalApplication.assets == nil { + // We are using the devServer let the WebView2 handle the request with its default handler + return + } + + //Get the request + uri, _ := req.GetUri() + reqUri, err := url.ParseRequestURI(uri) + if err != nil { + globalApplication.error("Unable to parse request uri", "uri", uri, "error", err) + return + } + + if reqUri.Scheme != "http" { + // Let the WebView2 handle the request with its default handler + return + } else if !strings.HasPrefix(reqUri.Host, "wails.localhost") { + // Let the WebView2 handle the request with its default handler + return + } + + webviewRequest, err := webview.NewRequest( + w.chromium.Environment(), + args, + func(fn func()) { + InvokeSync(fn) + }) + if err != nil { + globalApplication.error("%s: NewRequest failed: %s", uri, err) + return + } + + webviewRequests <- &webViewAssetRequest{ + Request: webviewRequest, + windowId: w.parent.id, + windowName: globalApplication.getWindowForID(w.parent.id).Name(), + } +} + +func (w *windowsWebviewWindow) setupChromium() { + chromium := w.chromium + debugMode := globalApplication.isDebugMode + + opts := w.parent.options.Windows + + webview2version, err := webviewloader.GetAvailableCoreWebView2BrowserVersionString(globalApplication.options.Windows.WebviewBrowserPath) + if err != nil { + globalApplication.error("Error getting WebView2 version: " + err.Error()) + return + } + globalApplication.capabilities = capabilities.NewCapabilities(webview2version) + + disableFeatues := []string{} + + if !opts.EnableFraudulentWebsiteWarnings { + disableFeatues = append(disableFeatues, "msSmartScreenProtection") + } + + chromium.DataPath = globalApplication.options.Windows.WebviewUserDataPath + chromium.BrowserPath = globalApplication.options.Windows.WebviewBrowserPath + + if opts.WebviewGpuIsDisabled { + chromium.AdditionalBrowserArgs = append(chromium.AdditionalBrowserArgs, "--disable-gpu") + } + + if len(disableFeatues) > 0 { + arg := fmt.Sprintf("--disable-features=%s", strings.Join(disableFeatues, ",")) + chromium.AdditionalBrowserArgs = append(chromium.AdditionalBrowserArgs, arg) + } + + if opts.Permissions != nil { + for permission, state := range opts.Permissions { + chromium.SetPermission(edge.CoreWebView2PermissionKind(permission), + edge.CoreWebView2PermissionState(state)) + } + } + + chromium.MessageCallback = w.processMessage + chromium.MessageWithAdditionalObjectsCallback = w.processMessageWithAdditionalObjects + chromium.WebResourceRequestedCallback = w.processRequest + chromium.ContainsFullScreenElementChangedCallback = w.fullscreenChanged + chromium.NavigationCompletedCallback = w.navigationCompleted + chromium.AcceleratorKeyCallback = func(vkey uint) bool { + if w.processKeyBinding(vkey) { + return true + } + w32.PostMessage(w.hwnd, w32.WM_KEYDOWN, uintptr(vkey), 0) + return false + } + + chromium.Embed(w.hwnd) + + if chromium.HasCapability(edge.SwipeNavigation) { + err := chromium.PutIsSwipeNavigationEnabled(opts.EnableSwipeGestures) + if err != nil { + globalApplication.fatal(err.Error()) + } + } + + if chromium.HasCapability(edge.AllowExternalDrop) { + err := chromium.AllowExternalDrag(false) + if err != nil { + globalApplication.fatal(err.Error()) + } + } + if w.parent.options.EnableDragAndDrop { + w.dropTarget = w32.NewDropTarget() + w.dropTarget.OnDrop = func(files []string) { + w.parent.emit(events.Windows.WindowDragDrop) + windowDragAndDropBuffer <- &dragAndDropMessage{ + windowId: windowID, + filenames: files, + } + } + if opts.OnEnterEffect != 0 { + w.dropTarget.OnEnterEffect = convertEffect(opts.OnEnterEffect) + } + if opts.OnOverEffect != 0 { + w.dropTarget.OnOverEffect = convertEffect(opts.OnOverEffect) + } + w.dropTarget.OnEnter = func() { + w.parent.emit(events.Windows.WindowDragEnter) + } + w.dropTarget.OnLeave = func() { + w.parent.emit(events.Windows.WindowDragLeave) + } + w.dropTarget.OnOver = func() { + w.parent.emit(events.Windows.WindowDragOver) + } + // Enumerate all the child windows for this window and register them as drop targets + w32.EnumChildWindows(w.hwnd, func(hwnd w32.HWND, lparam w32.LPARAM) w32.LRESULT { + // Check if the window class is "Chrome_RenderWidgetHostHWND" + // If it is, then we register it as a drop target + //windowName := w32.GetClassName(hwnd) + //println(windowName) + //if windowName == "Chrome_RenderWidgetHostHWND" { + err := w32.RegisterDragDrop(hwnd, w.dropTarget) + if err != nil && err != syscall.Errno(w32.DRAGDROP_E_ALREADYREGISTERED) { + globalApplication.error("Error registering drag and drop: " + err.Error()) + } + //} + return 1 + }) + + } + + // We will get round to this + //if chromium.HasCapability(edge.AllowExternalDrop) { + // err := chromium.AllowExternalDrag(w.parent.options.EnableDragAndDrop) + // if err != nil { + // globalApplication.fatal(err.Error()) + // } + // if w.parent.options.EnableDragAndDrop { + // chromium.MessageWithAdditionalObjectsCallback = w.processMessageWithAdditionalObjects + // } + //} + + chromium.Resize() + settings, err := chromium.GetSettings() + if err != nil { + globalApplication.fatal(err.Error()) + } + err = settings.PutAreDefaultContextMenusEnabled(debugMode || !w.parent.options.DefaultContextMenuDisabled) + if err != nil { + globalApplication.fatal(err.Error()) + } + err = settings.PutAreDevToolsEnabled(debugMode || w.parent.options.DevToolsEnabled) + if err != nil { + globalApplication.fatal(err.Error()) + } + + if w.parent.options.Zoom > 0.0 { + chromium.PutZoomFactor(w.parent.options.Zoom) + } + err = settings.PutIsZoomControlEnabled(w.parent.options.ZoomControlEnabled) + if err != nil { + globalApplication.fatal(err.Error()) + } + + err = settings.PutIsStatusBarEnabled(false) + if err != nil { + globalApplication.fatal(err.Error()) + } + err = settings.PutAreBrowserAcceleratorKeysEnabled(false) + if err != nil { + globalApplication.fatal(err.Error()) + } + err = settings.PutIsSwipeNavigationEnabled(false) + if err != nil { + globalApplication.fatal(err.Error()) + } + + if debugMode && w.parent.options.OpenInspectorOnStartup { + chromium.OpenDevToolsWindow() + } + + // Set background colour + w.setBackgroundColour(w.parent.options.BackgroundColour) + + chromium.SetGlobalPermission(edge.CoreWebView2PermissionStateAllow) + chromium.AddWebResourceRequestedFilter("*", edge.COREWEBVIEW2_WEB_RESOURCE_CONTEXT_ALL) + + if w.parent.options.HTML != "" { + var script string + if w.parent.options.JS != "" { + script = w.parent.options.JS + } + if w.parent.options.CSS != "" { + script += fmt.Sprintf("; addEventListener(\"DOMContentLoaded\", (event) => { document.head.appendChild(document.createElement('style')).innerHTML=\"%s\"; });", strings.ReplaceAll(w.parent.options.CSS, `"`, `\"`)) + } + chromium.Init(script) + chromium.NavigateToString(w.parent.options.HTML) + } else { + var startURL = "http://wails.localhost" + if globalApplication.options.Assets.ExternalURL != "" { + // Parse the port + parsedURL, err := url.Parse(globalApplication.options.Assets.ExternalURL) + if err != nil { + globalApplication.fatal("Error parsing ExternalURL: " + err.Error()) + } + port := parsedURL.Port() + if port != "" { + startURL += ":" + port + } + } else { + if w.parent.options.URL != "" { + // parse the url + parsedURL, err := url.Parse(w.parent.options.URL) + if err != nil { + globalApplication.fatal("Error parsing URL: " + err.Error()) + } + if parsedURL.Scheme == "" { + startURL = path.Join(startURL, w.parent.options.URL) + // if the original URL had a trailing slash, add it back + if strings.HasSuffix(w.parent.options.URL, "/") { + startURL = startURL + "/" + } + } else { + startURL = w.parent.options.URL + } + } + } + chromium.Navigate(startURL) + } + +} + +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()) + } + if isFullscreen { + w.fullscreen() + } else { + w.unfullscreen() + } +} + +func convertEffect(effect DragEffect) w32.DWORD { + switch effect { + case DragEffectCopy: + return w32.DROPEFFECT_COPY + case DragEffectMove: + return w32.DROPEFFECT_MOVE + case DragEffectLink: + return w32.DROPEFFECT_LINK + default: + return w32.DROPEFFECT_NONE + } +} + +func (w *windowsWebviewWindow) flash(enabled bool) { + w32.FlashWindow(w.hwnd, enabled) +} + +func (w *windowsWebviewWindow) navigationCompleted(sender *edge.ICoreWebView2, args *edge.ICoreWebView2NavigationCompletedEventArgs) { + + // Emit DomReady Event + windowEvents <- &windowEvent{EventID: uint(events.Windows.WebViewNavigationCompleted), WindowID: w.parent.id} + + if w.hasStarted { + // NavigationCompleted is triggered for every Load. If an application uses reloads the Hide/Show will trigger + // a flickering of the window with every reload. So we only do this once for the first NavigationCompleted. + return + } + w.hasStarted = true + + // 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()) + } + err = w.chromium.Show() + if err != nil { + globalApplication.fatal(err.Error()) + } + + //f.mainWindow.hasBeenShown = true + +} + +func (w *windowsWebviewWindow) processKeyBinding(vkey uint) bool { + + globalApplication.debug("Processing key binding", "vkey", vkey) + + if len(w.parent.keyBindings) == 0 { + return false + } + // Get the keyboard state and convert to an accelerator + var keyState [256]byte + if !w32.GetKeyboardState(keyState[:]) { + globalApplication.error("Error getting keyboard state") + return false + } + + var acc accelerator + // Check if CTRL is pressed + if keyState[w32.VK_CONTROL]&0x80 != 0 { + acc.Modifiers = append(acc.Modifiers, ControlKey) + } + // Check if ALT is pressed + if keyState[w32.VK_MENU]&0x80 != 0 { + acc.Modifiers = append(acc.Modifiers, OptionOrAltKey) + } + // Check if SHIFT is pressed + if keyState[w32.VK_SHIFT]&0x80 != 0 { + acc.Modifiers = append(acc.Modifiers, ShiftKey) + } + // Check if WIN is pressed + if keyState[w32.VK_LWIN]&0x80 != 0 || keyState[w32.VK_RWIN]&0x80 != 0 { + acc.Modifiers = append(acc.Modifiers, SuperKey) + } + + if vkey != w32.VK_CONTROL && vkey != w32.VK_MENU && vkey != w32.VK_SHIFT && vkey != w32.VK_LWIN && vkey != w32.VK_RWIN { + // Convert the vkey to a string + accKey, ok := VirtualKeyCodes[vkey] + if !ok { + return false + } + acc.Key = accKey + } + + // Process the key binding + return w.parent.processKeyBinding(acc.String()) + +} + +func (w *windowsWebviewWindow) processMessageWithAdditionalObjects(message string, sender *edge.ICoreWebView2, args *edge.ICoreWebView2WebMessageReceivedEventArgs) { + if strings.HasPrefix(message, "FilesDropped") { + objs, err := args.GetAdditionalObjects() + if err != nil { + globalApplication.error(err.Error()) + return + } + + defer objs.Release() + + count, err := objs.GetCount() + if err != nil { + globalApplication.error(err.Error()) + return + } + + var filenames []string + 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()) + return + } + + file := (*edge.ICoreWebView2File)(unsafe.Pointer(_file)) + defer file.Release() + + filepath, err := file.GetPath() + if err != nil { + globalApplication.error("cannot get path for object at %d : %s", i, err.Error()) + return + } + + filenames = append(filenames, filepath) + } + + addDragAndDropMessage(w.parent.id, filenames) + return + } +} + +func ScaleWithDPI(pixels int, dpi uint) int { + return (pixels * int(dpi)) / 96 +} + +func ScaleToDefaultDPI(pixels int, dpi uint) int { + return (pixels * 96) / int(dpi) +} + +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)) + } + return result, err +} diff --git a/v3/pkg/application/webview_window_windows_devtools.go b/v3/pkg/application/webview_window_windows_devtools.go new file mode 100644 index 000000000..590d4115c --- /dev/null +++ b/v3/pkg/application/webview_window_windows_devtools.go @@ -0,0 +1,13 @@ +//go:build windows && !production + +package application + +import ( + "github.com/wailsapp/go-webview2/pkg/edge" +) + +func init() { + showDevTools = func(chromium *edge.Chromium) { + chromium.OpenDevToolsWindow() + } +} diff --git a/v3/pkg/application/window.go b/v3/pkg/application/window.go new file mode 100644 index 000000000..46a8420cc --- /dev/null +++ b/v3/pkg/application/window.go @@ -0,0 +1,78 @@ +package application + +import ( + "github.com/wailsapp/wails/v3/pkg/events" +) + +type Callback interface { + CallError(callID string, result string) + CallResponse(callID string, result string) + DialogError(dialogID string, result string) + DialogResponse(dialogID string, result string, isJSON bool) +} + +type Window interface { + Callback + AbsolutePosition() (int, int) + Center() + Close() + Destroy() + DisableSizeConstraints() + DispatchWailsEvent(event *WailsEvent) + EnableSizeConstraints() + Error(message string, args ...any) + ExecJS(callID, js string) + Focus() + ForceReload() + Fullscreen() Window + GetScreen() (*Screen, error) + GetZoom() float64 + HandleDragAndDropMessage(filenames []string) + HandleMessage(message string) + HandleWindowEvent(id uint) + Height() int + Hide() Window + ID() uint + Info(message string, args ...any) + IsFullscreen() bool + IsMaximised() bool + IsMinimised() bool + HandleKeyEvent(acceleratorString string) + Maximise() Window + Minimise() Window + Name() string + On(eventType events.WindowEventType, callback func(event *WindowEvent)) func() + OpenContextMenu(data *ContextMenuData) + RegisterContextMenu(name string, menu *Menu) + RelativePosition() (int, int) + Reload() + Resizable() bool + Restore() + Run() + SetAbsolutePosition(x, y int) + SetAlwaysOnTop(b bool) Window + SetBackgroundColour(colour RGBA) Window + SetFrameless(frameless bool) Window + SetFullscreenButtonEnabled(enabled bool) Window + SetHTML(html string) Window + SetMaxSize(maxWidth, maxHeight int) Window + SetMinSize(minWidth, minHeight int) Window + SetRelativePosition(x, y int) Window + SetResizable(b bool) Window + SetSize(width, height int) Window + SetTitle(title string) Window + SetURL(s string) Window + SetZoom(magnification float64) Window + Show() Window + Size() (width int, height int) + ToggleDevTools() + ToggleFullscreen() + UnFullscreen() + UnMaximise() + UnMinimise() + Width() int + Zoom() + ZoomIn() + ZoomOut() + ZoomReset() Window +} diff --git a/v3/pkg/events/README.md b/v3/pkg/events/README.md new file mode 100644 index 000000000..f9389f198 --- /dev/null +++ b/v3/pkg/events/README.md @@ -0,0 +1,14 @@ +# Events + +This package is used to generate the event management code and to allow quick addition of events. + +## Usage + +1. Add events to `events.txt` +2. Run `task generate:events` + +## Notes + +For events that you want to handle manually, add a `!` to the end of the event name and +add custom code into the appropriate platform. See [this PR](https://github.com/wailsapp/wails/pull/2991) +for an example of how to do this. \ No newline at end of file diff --git a/v3/pkg/events/defaults.go b/v3/pkg/events/defaults.go new file mode 100644 index 000000000..3b169ba8f --- /dev/null +++ b/v3/pkg/events/defaults.go @@ -0,0 +1,34 @@ +package events + +import "runtime" + +var defaultWindowEventMapping = map[string]map[WindowEventType]WindowEventType{ + "windows": { + Windows.WindowClose: Common.WindowClosing, + Windows.WindowInactive: Common.WindowLostFocus, + Windows.WindowClickActive: Common.WindowFocus, + Windows.WindowActive: Common.WindowFocus, + Windows.WindowMaximise: Common.WindowMaximise, + Windows.WindowMinimise: Common.WindowMinimise, + Windows.WindowRestore: Common.WindowRestore, + Windows.WindowUnMaximise: Common.WindowUnMaximise, + Windows.WindowUnMinimise: Common.WindowUnMinimise, + Windows.WindowFullscreen: Common.WindowFullscreen, + Windows.WindowUnFullscreen: Common.WindowUnFullscreen, + }, + "darwin": { + Mac.WindowDidResignKey: Common.WindowLostFocus, + Mac.WindowDidResignKey: Common.WindowLostFocus, + Mac.WindowDidBecomeKey: Common.WindowFocus, + Mac.WindowDidMiniaturize: Common.WindowMinimise, + Mac.WindowDidDeminiaturize: Common.WindowUnMinimise, + Mac.WindowDidEnterFullScreen: Common.WindowFullscreen, + Mac.WindowDidExitFullScreen: Common.WindowUnFullscreen, + }, + "linux": {}, +} + +func DefaultWindowEventMapping() map[WindowEventType]WindowEventType { + platform := runtime.GOOS + return defaultWindowEventMapping[platform] +} diff --git a/v3/pkg/events/events.go b/v3/pkg/events/events.go new file mode 100644 index 000000000..72162dfd3 --- /dev/null +++ b/v3/pkg/events/events.go @@ -0,0 +1,548 @@ +package events + +type ApplicationEventType uint +type WindowEventType uint + +var Common = newCommonEvents() + +type commonEvents struct { + ApplicationStarted ApplicationEventType + WindowMaximise WindowEventType + WindowUnMaximise WindowEventType + WindowFullscreen WindowEventType + WindowUnFullscreen WindowEventType + WindowRestore WindowEventType + WindowMinimise WindowEventType + WindowUnMinimise WindowEventType + WindowClosing WindowEventType + WindowZoom WindowEventType + WindowZoomIn WindowEventType + WindowZoomOut WindowEventType + WindowZoomReset WindowEventType + WindowFocus WindowEventType + WindowLostFocus WindowEventType + WindowShow WindowEventType + WindowHide WindowEventType + WindowDPIChanged WindowEventType + WindowFilesDropped WindowEventType + ThemeChanged ApplicationEventType +} + +func newCommonEvents() commonEvents { + return commonEvents{ + ApplicationStarted: 1173, + WindowMaximise: 1174, + WindowUnMaximise: 1175, + WindowFullscreen: 1176, + WindowUnFullscreen: 1177, + WindowRestore: 1178, + WindowMinimise: 1179, + WindowUnMinimise: 1180, + WindowClosing: 1181, + WindowZoom: 1182, + WindowZoomIn: 1183, + WindowZoomOut: 1184, + WindowZoomReset: 1185, + WindowFocus: 1186, + WindowLostFocus: 1187, + WindowShow: 1188, + WindowHide: 1189, + WindowDPIChanged: 1190, + WindowFilesDropped: 1191, + ThemeChanged: 1192, + } +} + +var Mac = newMacEvents() + +type macEvents struct { + ApplicationDidBecomeActive ApplicationEventType + ApplicationDidChangeBackingProperties ApplicationEventType + ApplicationDidChangeEffectiveAppearance ApplicationEventType + ApplicationDidChangeIcon ApplicationEventType + ApplicationDidChangeOcclusionState ApplicationEventType + ApplicationDidChangeScreenParameters ApplicationEventType + ApplicationDidChangeStatusBarFrame ApplicationEventType + ApplicationDidChangeStatusBarOrientation ApplicationEventType + ApplicationDidFinishLaunching ApplicationEventType + ApplicationDidHide ApplicationEventType + ApplicationDidResignActiveNotification ApplicationEventType + ApplicationDidUnhide ApplicationEventType + ApplicationDidUpdate ApplicationEventType + ApplicationWillBecomeActive ApplicationEventType + ApplicationWillFinishLaunching ApplicationEventType + ApplicationWillHide ApplicationEventType + ApplicationWillResignActive ApplicationEventType + ApplicationWillTerminate ApplicationEventType + ApplicationWillUnhide ApplicationEventType + ApplicationWillUpdate ApplicationEventType + ApplicationDidChangeTheme ApplicationEventType + ApplicationShouldHandleReopen ApplicationEventType + WindowDidBecomeKey WindowEventType + WindowDidBecomeMain WindowEventType + WindowDidBeginSheet WindowEventType + WindowDidChangeAlpha WindowEventType + WindowDidChangeBackingLocation WindowEventType + WindowDidChangeBackingProperties WindowEventType + WindowDidChangeCollectionBehavior WindowEventType + WindowDidChangeEffectiveAppearance WindowEventType + WindowDidChangeOcclusionState WindowEventType + WindowDidChangeOrderingMode WindowEventType + WindowDidChangeScreen WindowEventType + WindowDidChangeScreenParameters WindowEventType + WindowDidChangeScreenProfile WindowEventType + WindowDidChangeScreenSpace WindowEventType + WindowDidChangeScreenSpaceProperties WindowEventType + WindowDidChangeSharingType WindowEventType + WindowDidChangeSpace WindowEventType + WindowDidChangeSpaceOrderingMode WindowEventType + WindowDidChangeTitle WindowEventType + WindowDidChangeToolbar WindowEventType + WindowDidChangeVisibility WindowEventType + WindowDidDeminiaturize WindowEventType + WindowDidEndSheet WindowEventType + WindowDidEnterFullScreen WindowEventType + WindowDidEnterVersionBrowser WindowEventType + WindowDidExitFullScreen WindowEventType + WindowDidExitVersionBrowser WindowEventType + WindowDidExpose WindowEventType + WindowDidFocus WindowEventType + WindowDidMiniaturize WindowEventType + WindowDidMove WindowEventType + WindowDidOrderOffScreen WindowEventType + WindowDidOrderOnScreen WindowEventType + WindowDidResignKey WindowEventType + WindowDidResignMain WindowEventType + WindowDidResize WindowEventType + WindowDidUpdate WindowEventType + WindowDidUpdateAlpha WindowEventType + WindowDidUpdateCollectionBehavior WindowEventType + WindowDidUpdateCollectionProperties WindowEventType + WindowDidUpdateShadow WindowEventType + WindowDidUpdateTitle WindowEventType + WindowDidUpdateToolbar WindowEventType + WindowDidUpdateVisibility WindowEventType + WindowShouldClose WindowEventType + WindowWillBecomeKey WindowEventType + WindowWillBecomeMain WindowEventType + WindowWillBeginSheet WindowEventType + WindowWillChangeOrderingMode WindowEventType + WindowWillClose WindowEventType + WindowWillDeminiaturize WindowEventType + WindowWillEnterFullScreen WindowEventType + WindowWillEnterVersionBrowser WindowEventType + WindowWillExitFullScreen WindowEventType + WindowWillExitVersionBrowser WindowEventType + WindowWillFocus WindowEventType + WindowWillMiniaturize WindowEventType + WindowWillMove WindowEventType + WindowWillOrderOffScreen WindowEventType + WindowWillOrderOnScreen WindowEventType + WindowWillResignMain WindowEventType + WindowWillResize WindowEventType + WindowWillUnfocus WindowEventType + WindowWillUpdate WindowEventType + WindowWillUpdateAlpha WindowEventType + WindowWillUpdateCollectionBehavior WindowEventType + WindowWillUpdateCollectionProperties WindowEventType + WindowWillUpdateShadow WindowEventType + WindowWillUpdateTitle WindowEventType + WindowWillUpdateToolbar WindowEventType + WindowWillUpdateVisibility WindowEventType + WindowWillUseStandardFrame WindowEventType + MenuWillOpen ApplicationEventType + MenuDidOpen ApplicationEventType + MenuDidClose ApplicationEventType + MenuWillSendAction ApplicationEventType + MenuDidSendAction ApplicationEventType + MenuWillHighlightItem ApplicationEventType + MenuDidHighlightItem ApplicationEventType + MenuWillDisplayItem ApplicationEventType + MenuDidDisplayItem ApplicationEventType + MenuWillAddItem ApplicationEventType + MenuDidAddItem ApplicationEventType + MenuWillRemoveItem ApplicationEventType + MenuDidRemoveItem ApplicationEventType + MenuWillBeginTracking ApplicationEventType + MenuDidBeginTracking ApplicationEventType + MenuWillEndTracking ApplicationEventType + MenuDidEndTracking ApplicationEventType + MenuWillUpdate ApplicationEventType + MenuDidUpdate ApplicationEventType + MenuWillPopUp ApplicationEventType + MenuDidPopUp ApplicationEventType + MenuWillSendActionToItem ApplicationEventType + MenuDidSendActionToItem ApplicationEventType + WebViewDidStartProvisionalNavigation WindowEventType + WebViewDidReceiveServerRedirectForProvisionalNavigation WindowEventType + WebViewDidFinishNavigation WindowEventType + WebViewDidCommitNavigation WindowEventType + WindowFileDraggingEntered WindowEventType + WindowFileDraggingPerformed WindowEventType + WindowFileDraggingExited WindowEventType +} + +func newMacEvents() macEvents { + return macEvents{ + ApplicationDidBecomeActive: 1024, + ApplicationDidChangeBackingProperties: 1025, + ApplicationDidChangeEffectiveAppearance: 1026, + ApplicationDidChangeIcon: 1027, + ApplicationDidChangeOcclusionState: 1028, + ApplicationDidChangeScreenParameters: 1029, + ApplicationDidChangeStatusBarFrame: 1030, + ApplicationDidChangeStatusBarOrientation: 1031, + ApplicationDidFinishLaunching: 1032, + ApplicationDidHide: 1033, + ApplicationDidResignActiveNotification: 1034, + ApplicationDidUnhide: 1035, + ApplicationDidUpdate: 1036, + ApplicationWillBecomeActive: 1037, + ApplicationWillFinishLaunching: 1038, + ApplicationWillHide: 1039, + ApplicationWillResignActive: 1040, + ApplicationWillTerminate: 1041, + ApplicationWillUnhide: 1042, + ApplicationWillUpdate: 1043, + ApplicationDidChangeTheme: 1044, + ApplicationShouldHandleReopen: 1045, + WindowDidBecomeKey: 1046, + WindowDidBecomeMain: 1047, + WindowDidBeginSheet: 1048, + WindowDidChangeAlpha: 1049, + WindowDidChangeBackingLocation: 1050, + WindowDidChangeBackingProperties: 1051, + WindowDidChangeCollectionBehavior: 1052, + WindowDidChangeEffectiveAppearance: 1053, + WindowDidChangeOcclusionState: 1054, + WindowDidChangeOrderingMode: 1055, + WindowDidChangeScreen: 1056, + WindowDidChangeScreenParameters: 1057, + WindowDidChangeScreenProfile: 1058, + WindowDidChangeScreenSpace: 1059, + WindowDidChangeScreenSpaceProperties: 1060, + WindowDidChangeSharingType: 1061, + WindowDidChangeSpace: 1062, + WindowDidChangeSpaceOrderingMode: 1063, + WindowDidChangeTitle: 1064, + WindowDidChangeToolbar: 1065, + WindowDidChangeVisibility: 1066, + WindowDidDeminiaturize: 1067, + WindowDidEndSheet: 1068, + WindowDidEnterFullScreen: 1069, + WindowDidEnterVersionBrowser: 1070, + WindowDidExitFullScreen: 1071, + WindowDidExitVersionBrowser: 1072, + WindowDidExpose: 1073, + WindowDidFocus: 1074, + WindowDidMiniaturize: 1075, + WindowDidMove: 1076, + WindowDidOrderOffScreen: 1077, + WindowDidOrderOnScreen: 1078, + WindowDidResignKey: 1079, + WindowDidResignMain: 1080, + WindowDidResize: 1081, + WindowDidUpdate: 1082, + WindowDidUpdateAlpha: 1083, + WindowDidUpdateCollectionBehavior: 1084, + WindowDidUpdateCollectionProperties: 1085, + WindowDidUpdateShadow: 1086, + WindowDidUpdateTitle: 1087, + WindowDidUpdateToolbar: 1088, + WindowDidUpdateVisibility: 1089, + WindowShouldClose: 1090, + WindowWillBecomeKey: 1091, + WindowWillBecomeMain: 1092, + WindowWillBeginSheet: 1093, + WindowWillChangeOrderingMode: 1094, + WindowWillClose: 1095, + WindowWillDeminiaturize: 1096, + WindowWillEnterFullScreen: 1097, + WindowWillEnterVersionBrowser: 1098, + WindowWillExitFullScreen: 1099, + WindowWillExitVersionBrowser: 1100, + WindowWillFocus: 1101, + WindowWillMiniaturize: 1102, + WindowWillMove: 1103, + WindowWillOrderOffScreen: 1104, + WindowWillOrderOnScreen: 1105, + WindowWillResignMain: 1106, + WindowWillResize: 1107, + WindowWillUnfocus: 1108, + WindowWillUpdate: 1109, + WindowWillUpdateAlpha: 1110, + WindowWillUpdateCollectionBehavior: 1111, + WindowWillUpdateCollectionProperties: 1112, + WindowWillUpdateShadow: 1113, + WindowWillUpdateTitle: 1114, + WindowWillUpdateToolbar: 1115, + WindowWillUpdateVisibility: 1116, + WindowWillUseStandardFrame: 1117, + MenuWillOpen: 1118, + MenuDidOpen: 1119, + MenuDidClose: 1120, + MenuWillSendAction: 1121, + MenuDidSendAction: 1122, + MenuWillHighlightItem: 1123, + MenuDidHighlightItem: 1124, + MenuWillDisplayItem: 1125, + MenuDidDisplayItem: 1126, + MenuWillAddItem: 1127, + MenuDidAddItem: 1128, + MenuWillRemoveItem: 1129, + MenuDidRemoveItem: 1130, + MenuWillBeginTracking: 1131, + MenuDidBeginTracking: 1132, + MenuWillEndTracking: 1133, + MenuDidEndTracking: 1134, + MenuWillUpdate: 1135, + MenuDidUpdate: 1136, + MenuWillPopUp: 1137, + MenuDidPopUp: 1138, + MenuWillSendActionToItem: 1139, + MenuDidSendActionToItem: 1140, + WebViewDidStartProvisionalNavigation: 1141, + WebViewDidReceiveServerRedirectForProvisionalNavigation: 1142, + WebViewDidFinishNavigation: 1143, + WebViewDidCommitNavigation: 1144, + WindowFileDraggingEntered: 1145, + WindowFileDraggingPerformed: 1146, + WindowFileDraggingExited: 1147, + } +} + +var Windows = newWindowsEvents() + +type windowsEvents struct { + SystemThemeChanged ApplicationEventType + APMPowerStatusChange ApplicationEventType + APMSuspend ApplicationEventType + APMResumeAutomatic ApplicationEventType + APMResumeSuspend ApplicationEventType + APMPowerSettingChange ApplicationEventType + ApplicationStarted ApplicationEventType + WebViewNavigationCompleted WindowEventType + WindowInactive WindowEventType + WindowActive WindowEventType + WindowClickActive WindowEventType + WindowMaximise WindowEventType + WindowUnMaximise WindowEventType + WindowFullscreen WindowEventType + WindowUnFullscreen WindowEventType + WindowRestore WindowEventType + WindowMinimise WindowEventType + WindowUnMinimise WindowEventType + WindowClose WindowEventType + WindowSetFocus WindowEventType + WindowKillFocus WindowEventType + WindowDragDrop WindowEventType + WindowDragEnter WindowEventType + WindowDragLeave WindowEventType + WindowDragOver WindowEventType +} + +func newWindowsEvents() windowsEvents { + return windowsEvents{ + SystemThemeChanged: 1148, + APMPowerStatusChange: 1149, + APMSuspend: 1150, + APMResumeAutomatic: 1151, + APMResumeSuspend: 1152, + APMPowerSettingChange: 1153, + ApplicationStarted: 1154, + WebViewNavigationCompleted: 1155, + WindowInactive: 1156, + WindowActive: 1157, + WindowClickActive: 1158, + WindowMaximise: 1159, + WindowUnMaximise: 1160, + WindowFullscreen: 1161, + WindowUnFullscreen: 1162, + WindowRestore: 1163, + WindowMinimise: 1164, + WindowUnMinimise: 1165, + WindowClose: 1166, + WindowSetFocus: 1167, + WindowKillFocus: 1168, + WindowDragDrop: 1169, + WindowDragEnter: 1170, + WindowDragLeave: 1171, + WindowDragOver: 1172, + } +} + +func JSEvent(event uint) string { + return eventToJS[event] +} + +var eventToJS = map[uint]string{ + 1024: "mac:ApplicationDidBecomeActive", + 1025: "mac:ApplicationDidChangeBackingProperties", + 1026: "mac:ApplicationDidChangeEffectiveAppearance", + 1027: "mac:ApplicationDidChangeIcon", + 1028: "mac:ApplicationDidChangeOcclusionState", + 1029: "mac:ApplicationDidChangeScreenParameters", + 1030: "mac:ApplicationDidChangeStatusBarFrame", + 1031: "mac:ApplicationDidChangeStatusBarOrientation", + 1032: "mac:ApplicationDidFinishLaunching", + 1033: "mac:ApplicationDidHide", + 1034: "mac:ApplicationDidResignActiveNotification", + 1035: "mac:ApplicationDidUnhide", + 1036: "mac:ApplicationDidUpdate", + 1037: "mac:ApplicationWillBecomeActive", + 1038: "mac:ApplicationWillFinishLaunching", + 1039: "mac:ApplicationWillHide", + 1040: "mac:ApplicationWillResignActive", + 1041: "mac:ApplicationWillTerminate", + 1042: "mac:ApplicationWillUnhide", + 1043: "mac:ApplicationWillUpdate", + 1044: "mac:ApplicationDidChangeTheme!", + 1045: "mac:ApplicationShouldHandleReopen!", + 1046: "mac:WindowDidBecomeKey", + 1047: "mac:WindowDidBecomeMain", + 1048: "mac:WindowDidBeginSheet", + 1049: "mac:WindowDidChangeAlpha", + 1050: "mac:WindowDidChangeBackingLocation", + 1051: "mac:WindowDidChangeBackingProperties", + 1052: "mac:WindowDidChangeCollectionBehavior", + 1053: "mac:WindowDidChangeEffectiveAppearance", + 1054: "mac:WindowDidChangeOcclusionState", + 1055: "mac:WindowDidChangeOrderingMode", + 1056: "mac:WindowDidChangeScreen", + 1057: "mac:WindowDidChangeScreenParameters", + 1058: "mac:WindowDidChangeScreenProfile", + 1059: "mac:WindowDidChangeScreenSpace", + 1060: "mac:WindowDidChangeScreenSpaceProperties", + 1061: "mac:WindowDidChangeSharingType", + 1062: "mac:WindowDidChangeSpace", + 1063: "mac:WindowDidChangeSpaceOrderingMode", + 1064: "mac:WindowDidChangeTitle", + 1065: "mac:WindowDidChangeToolbar", + 1066: "mac:WindowDidChangeVisibility", + 1067: "mac:WindowDidDeminiaturize", + 1068: "mac:WindowDidEndSheet", + 1069: "mac:WindowDidEnterFullScreen", + 1070: "mac:WindowDidEnterVersionBrowser", + 1071: "mac:WindowDidExitFullScreen", + 1072: "mac:WindowDidExitVersionBrowser", + 1073: "mac:WindowDidExpose", + 1074: "mac:WindowDidFocus", + 1075: "mac:WindowDidMiniaturize", + 1076: "mac:WindowDidMove", + 1077: "mac:WindowDidOrderOffScreen", + 1078: "mac:WindowDidOrderOnScreen", + 1079: "mac:WindowDidResignKey", + 1080: "mac:WindowDidResignMain", + 1081: "mac:WindowDidResize", + 1082: "mac:WindowDidUpdate", + 1083: "mac:WindowDidUpdateAlpha", + 1084: "mac:WindowDidUpdateCollectionBehavior", + 1085: "mac:WindowDidUpdateCollectionProperties", + 1086: "mac:WindowDidUpdateShadow", + 1087: "mac:WindowDidUpdateTitle", + 1088: "mac:WindowDidUpdateToolbar", + 1089: "mac:WindowDidUpdateVisibility", + 1090: "mac:WindowShouldClose!", + 1091: "mac:WindowWillBecomeKey", + 1092: "mac:WindowWillBecomeMain", + 1093: "mac:WindowWillBeginSheet", + 1094: "mac:WindowWillChangeOrderingMode", + 1095: "mac:WindowWillClose", + 1096: "mac:WindowWillDeminiaturize", + 1097: "mac:WindowWillEnterFullScreen", + 1098: "mac:WindowWillEnterVersionBrowser", + 1099: "mac:WindowWillExitFullScreen", + 1100: "mac:WindowWillExitVersionBrowser", + 1101: "mac:WindowWillFocus", + 1102: "mac:WindowWillMiniaturize", + 1103: "mac:WindowWillMove", + 1104: "mac:WindowWillOrderOffScreen", + 1105: "mac:WindowWillOrderOnScreen", + 1106: "mac:WindowWillResignMain", + 1107: "mac:WindowWillResize", + 1108: "mac:WindowWillUnfocus", + 1109: "mac:WindowWillUpdate", + 1110: "mac:WindowWillUpdateAlpha", + 1111: "mac:WindowWillUpdateCollectionBehavior", + 1112: "mac:WindowWillUpdateCollectionProperties", + 1113: "mac:WindowWillUpdateShadow", + 1114: "mac:WindowWillUpdateTitle", + 1115: "mac:WindowWillUpdateToolbar", + 1116: "mac:WindowWillUpdateVisibility", + 1117: "mac:WindowWillUseStandardFrame", + 1118: "mac:MenuWillOpen", + 1119: "mac:MenuDidOpen", + 1120: "mac:MenuDidClose", + 1121: "mac:MenuWillSendAction", + 1122: "mac:MenuDidSendAction", + 1123: "mac:MenuWillHighlightItem", + 1124: "mac:MenuDidHighlightItem", + 1125: "mac:MenuWillDisplayItem", + 1126: "mac:MenuDidDisplayItem", + 1127: "mac:MenuWillAddItem", + 1128: "mac:MenuDidAddItem", + 1129: "mac:MenuWillRemoveItem", + 1130: "mac:MenuDidRemoveItem", + 1131: "mac:MenuWillBeginTracking", + 1132: "mac:MenuDidBeginTracking", + 1133: "mac:MenuWillEndTracking", + 1134: "mac:MenuDidEndTracking", + 1135: "mac:MenuWillUpdate", + 1136: "mac:MenuDidUpdate", + 1137: "mac:MenuWillPopUp", + 1138: "mac:MenuDidPopUp", + 1139: "mac:MenuWillSendActionToItem", + 1140: "mac:MenuDidSendActionToItem", + 1141: "mac:WebViewDidStartProvisionalNavigation", + 1142: "mac:WebViewDidReceiveServerRedirectForProvisionalNavigation", + 1143: "mac:WebViewDidFinishNavigation", + 1144: "mac:WebViewDidCommitNavigation", + 1145: "mac:WindowFileDraggingEntered", + 1146: "mac:WindowFileDraggingPerformed", + 1147: "mac:WindowFileDraggingExited", + 1148: "windows:SystemThemeChanged", + 1149: "windows:APMPowerStatusChange", + 1150: "windows:APMSuspend", + 1151: "windows:APMResumeAutomatic", + 1152: "windows:APMResumeSuspend", + 1153: "windows:APMPowerSettingChange", + 1154: "windows:ApplicationStarted", + 1155: "windows:WebViewNavigationCompleted", + 1156: "windows:WindowInactive", + 1157: "windows:WindowActive", + 1158: "windows:WindowClickActive", + 1159: "windows:WindowMaximise", + 1160: "windows:WindowUnMaximise", + 1161: "windows:WindowFullscreen", + 1162: "windows:WindowUnFullscreen", + 1163: "windows:WindowRestore", + 1164: "windows:WindowMinimise", + 1165: "windows:WindowUnMinimise", + 1166: "windows:WindowClose", + 1167: "windows:WindowSetFocus", + 1168: "windows:WindowKillFocus", + 1169: "windows:WindowDragDrop", + 1170: "windows:WindowDragEnter", + 1171: "windows:WindowDragLeave", + 1172: "windows:WindowDragOver", + 1173: "common:ApplicationStarted", + 1174: "common:WindowMaximise", + 1175: "common:WindowUnMaximise", + 1176: "common:WindowFullscreen", + 1177: "common:WindowUnFullscreen", + 1178: "common:WindowRestore", + 1179: "common:WindowMinimise", + 1180: "common:WindowUnMinimise", + 1181: "common:WindowClosing", + 1182: "common:WindowZoom", + 1183: "common:WindowZoomIn", + 1184: "common:WindowZoomOut", + 1185: "common:WindowZoomReset", + 1186: "common:WindowFocus", + 1187: "common:WindowLostFocus", + 1188: "common:WindowShow", + 1189: "common:WindowHide", + 1190: "common:WindowDPIChanged", + 1191: "common:WindowFilesDropped", + 1192: "common:ThemeChanged", +} diff --git a/v3/pkg/events/events.h b/v3/pkg/events/events.h new file mode 100644 index 000000000..3eb09f936 --- /dev/null +++ b/v3/pkg/events/events.h @@ -0,0 +1,137 @@ +//go:build darwin + +#ifndef _events_h +#define _events_h + +extern void processApplicationEvent(unsigned int, void* data); +extern void processWindowEvent(unsigned int, unsigned int); + +#define EventApplicationDidBecomeActive 1024 +#define EventApplicationDidChangeBackingProperties 1025 +#define EventApplicationDidChangeEffectiveAppearance 1026 +#define EventApplicationDidChangeIcon 1027 +#define EventApplicationDidChangeOcclusionState 1028 +#define EventApplicationDidChangeScreenParameters 1029 +#define EventApplicationDidChangeStatusBarFrame 1030 +#define EventApplicationDidChangeStatusBarOrientation 1031 +#define EventApplicationDidFinishLaunching 1032 +#define EventApplicationDidHide 1033 +#define EventApplicationDidResignActiveNotification 1034 +#define EventApplicationDidUnhide 1035 +#define EventApplicationDidUpdate 1036 +#define EventApplicationWillBecomeActive 1037 +#define EventApplicationWillFinishLaunching 1038 +#define EventApplicationWillHide 1039 +#define EventApplicationWillResignActive 1040 +#define EventApplicationWillTerminate 1041 +#define EventApplicationWillUnhide 1042 +#define EventApplicationWillUpdate 1043 +#define EventApplicationDidChangeTheme 1044 +#define EventApplicationShouldHandleReopen 1045 +#define EventWindowDidBecomeKey 1046 +#define EventWindowDidBecomeMain 1047 +#define EventWindowDidBeginSheet 1048 +#define EventWindowDidChangeAlpha 1049 +#define EventWindowDidChangeBackingLocation 1050 +#define EventWindowDidChangeBackingProperties 1051 +#define EventWindowDidChangeCollectionBehavior 1052 +#define EventWindowDidChangeEffectiveAppearance 1053 +#define EventWindowDidChangeOcclusionState 1054 +#define EventWindowDidChangeOrderingMode 1055 +#define EventWindowDidChangeScreen 1056 +#define EventWindowDidChangeScreenParameters 1057 +#define EventWindowDidChangeScreenProfile 1058 +#define EventWindowDidChangeScreenSpace 1059 +#define EventWindowDidChangeScreenSpaceProperties 1060 +#define EventWindowDidChangeSharingType 1061 +#define EventWindowDidChangeSpace 1062 +#define EventWindowDidChangeSpaceOrderingMode 1063 +#define EventWindowDidChangeTitle 1064 +#define EventWindowDidChangeToolbar 1065 +#define EventWindowDidChangeVisibility 1066 +#define EventWindowDidDeminiaturize 1067 +#define EventWindowDidEndSheet 1068 +#define EventWindowDidEnterFullScreen 1069 +#define EventWindowDidEnterVersionBrowser 1070 +#define EventWindowDidExitFullScreen 1071 +#define EventWindowDidExitVersionBrowser 1072 +#define EventWindowDidExpose 1073 +#define EventWindowDidFocus 1074 +#define EventWindowDidMiniaturize 1075 +#define EventWindowDidMove 1076 +#define EventWindowDidOrderOffScreen 1077 +#define EventWindowDidOrderOnScreen 1078 +#define EventWindowDidResignKey 1079 +#define EventWindowDidResignMain 1080 +#define EventWindowDidResize 1081 +#define EventWindowDidUpdate 1082 +#define EventWindowDidUpdateAlpha 1083 +#define EventWindowDidUpdateCollectionBehavior 1084 +#define EventWindowDidUpdateCollectionProperties 1085 +#define EventWindowDidUpdateShadow 1086 +#define EventWindowDidUpdateTitle 1087 +#define EventWindowDidUpdateToolbar 1088 +#define EventWindowDidUpdateVisibility 1089 +#define EventWindowShouldClose 1090 +#define EventWindowWillBecomeKey 1091 +#define EventWindowWillBecomeMain 1092 +#define EventWindowWillBeginSheet 1093 +#define EventWindowWillChangeOrderingMode 1094 +#define EventWindowWillClose 1095 +#define EventWindowWillDeminiaturize 1096 +#define EventWindowWillEnterFullScreen 1097 +#define EventWindowWillEnterVersionBrowser 1098 +#define EventWindowWillExitFullScreen 1099 +#define EventWindowWillExitVersionBrowser 1100 +#define EventWindowWillFocus 1101 +#define EventWindowWillMiniaturize 1102 +#define EventWindowWillMove 1103 +#define EventWindowWillOrderOffScreen 1104 +#define EventWindowWillOrderOnScreen 1105 +#define EventWindowWillResignMain 1106 +#define EventWindowWillResize 1107 +#define EventWindowWillUnfocus 1108 +#define EventWindowWillUpdate 1109 +#define EventWindowWillUpdateAlpha 1110 +#define EventWindowWillUpdateCollectionBehavior 1111 +#define EventWindowWillUpdateCollectionProperties 1112 +#define EventWindowWillUpdateShadow 1113 +#define EventWindowWillUpdateTitle 1114 +#define EventWindowWillUpdateToolbar 1115 +#define EventWindowWillUpdateVisibility 1116 +#define EventWindowWillUseStandardFrame 1117 +#define EventMenuWillOpen 1118 +#define EventMenuDidOpen 1119 +#define EventMenuDidClose 1120 +#define EventMenuWillSendAction 1121 +#define EventMenuDidSendAction 1122 +#define EventMenuWillHighlightItem 1123 +#define EventMenuDidHighlightItem 1124 +#define EventMenuWillDisplayItem 1125 +#define EventMenuDidDisplayItem 1126 +#define EventMenuWillAddItem 1127 +#define EventMenuDidAddItem 1128 +#define EventMenuWillRemoveItem 1129 +#define EventMenuDidRemoveItem 1130 +#define EventMenuWillBeginTracking 1131 +#define EventMenuDidBeginTracking 1132 +#define EventMenuWillEndTracking 1133 +#define EventMenuDidEndTracking 1134 +#define EventMenuWillUpdate 1135 +#define EventMenuDidUpdate 1136 +#define EventMenuWillPopUp 1137 +#define EventMenuDidPopUp 1138 +#define EventMenuWillSendActionToItem 1139 +#define EventMenuDidSendActionToItem 1140 +#define EventWebViewDidStartProvisionalNavigation 1141 +#define EventWebViewDidReceiveServerRedirectForProvisionalNavigation 1142 +#define EventWebViewDidFinishNavigation 1143 +#define EventWebViewDidCommitNavigation 1144 +#define EventWindowFileDraggingEntered 1145 +#define EventWindowFileDraggingPerformed 1146 +#define EventWindowFileDraggingExited 1147 + +#define MAX_EVENTS 1148 + + +#endif \ No newline at end of file diff --git a/v3/pkg/events/events.txt b/v3/pkg/events/events.txt new file mode 100644 index 000000000..73c52bff2 --- /dev/null +++ b/v3/pkg/events/events.txt @@ -0,0 +1,169 @@ +mac:ApplicationDidBecomeActive +mac:ApplicationDidChangeBackingProperties +mac:ApplicationDidChangeEffectiveAppearance +mac:ApplicationDidChangeIcon +mac:ApplicationDidChangeOcclusionState +mac:ApplicationDidChangeScreenParameters +mac:ApplicationDidChangeStatusBarFrame +mac:ApplicationDidChangeStatusBarOrientation +mac:ApplicationDidFinishLaunching +mac:ApplicationDidHide +mac:ApplicationDidResignActiveNotification +mac:ApplicationDidUnhide +mac:ApplicationDidUpdate +mac:ApplicationWillBecomeActive +mac:ApplicationWillFinishLaunching +mac:ApplicationWillHide +mac:ApplicationWillResignActive +mac:ApplicationWillTerminate +mac:ApplicationWillUnhide +mac:ApplicationWillUpdate +mac:ApplicationDidChangeTheme! +mac:ApplicationShouldHandleReopen! +mac:WindowDidBecomeKey +mac:WindowDidBecomeMain +mac:WindowDidBeginSheet +mac:WindowDidChangeAlpha +mac:WindowDidChangeBackingLocation +mac:WindowDidChangeBackingProperties +mac:WindowDidChangeCollectionBehavior +mac:WindowDidChangeEffectiveAppearance +mac:WindowDidChangeOcclusionState +mac:WindowDidChangeOrderingMode +mac:WindowDidChangeScreen +mac:WindowDidChangeScreenParameters +mac:WindowDidChangeScreenProfile +mac:WindowDidChangeScreenSpace +mac:WindowDidChangeScreenSpaceProperties +mac:WindowDidChangeSharingType +mac:WindowDidChangeSpace +mac:WindowDidChangeSpaceOrderingMode +mac:WindowDidChangeTitle +mac:WindowDidChangeToolbar +mac:WindowDidChangeVisibility +mac:WindowDidDeminiaturize +mac:WindowDidEndSheet +mac:WindowDidEnterFullScreen +mac:WindowDidEnterVersionBrowser +mac:WindowDidExitFullScreen +mac:WindowDidExitVersionBrowser +mac:WindowDidExpose +mac:WindowDidFocus +mac:WindowDidMiniaturize +mac:WindowDidMove +mac:WindowDidOrderOffScreen +mac:WindowDidOrderOnScreen +mac:WindowDidResignKey +mac:WindowDidResignMain +mac:WindowDidResize +mac:WindowDidUpdate +mac:WindowDidUpdateAlpha +mac:WindowDidUpdateCollectionBehavior +mac:WindowDidUpdateCollectionProperties +mac:WindowDidUpdateShadow +mac:WindowDidUpdateTitle +mac:WindowDidUpdateToolbar +mac:WindowDidUpdateVisibility +mac:WindowShouldClose! +mac:WindowWillBecomeKey +mac:WindowWillBecomeMain +mac:WindowWillBeginSheet +mac:WindowWillChangeOrderingMode +mac:WindowWillClose +mac:WindowWillDeminiaturize +mac:WindowWillEnterFullScreen +mac:WindowWillEnterVersionBrowser +mac:WindowWillExitFullScreen +mac:WindowWillExitVersionBrowser +mac:WindowWillFocus +mac:WindowWillMiniaturize +mac:WindowWillMove +mac:WindowWillOrderOffScreen +mac:WindowWillOrderOnScreen +mac:WindowWillResignMain +mac:WindowWillResize +mac:WindowWillUnfocus +mac:WindowWillUpdate +mac:WindowWillUpdateAlpha +mac:WindowWillUpdateCollectionBehavior +mac:WindowWillUpdateCollectionProperties +mac:WindowWillUpdateShadow +mac:WindowWillUpdateTitle +mac:WindowWillUpdateToolbar +mac:WindowWillUpdateVisibility +mac:WindowWillUseStandardFrame +mac:MenuWillOpen +mac:MenuDidOpen +mac:MenuDidClose +mac:MenuWillSendAction +mac:MenuDidSendAction +mac:MenuWillHighlightItem +mac:MenuDidHighlightItem +mac:MenuWillDisplayItem +mac:MenuDidDisplayItem +mac:MenuWillAddItem +mac:MenuDidAddItem +mac:MenuWillRemoveItem +mac:MenuDidRemoveItem +mac:MenuWillBeginTracking +mac:MenuDidBeginTracking +mac:MenuWillEndTracking +mac:MenuDidEndTracking +mac:MenuWillUpdate +mac:MenuDidUpdate +mac:MenuWillPopUp +mac:MenuDidPopUp +mac:MenuWillSendActionToItem +mac:MenuDidSendActionToItem +mac:WebViewDidStartProvisionalNavigation +mac:WebViewDidReceiveServerRedirectForProvisionalNavigation +mac:WebViewDidFinishNavigation +mac:WebViewDidCommitNavigation +mac:WindowFileDraggingEntered +mac:WindowFileDraggingPerformed +mac:WindowFileDraggingExited +windows:SystemThemeChanged +windows:APMPowerStatusChange +windows:APMSuspend +windows:APMResumeAutomatic +windows:APMResumeSuspend +windows:APMPowerSettingChange +windows:ApplicationStarted +windows:WebViewNavigationCompleted +windows:WindowInactive +windows:WindowActive +windows:WindowClickActive +windows:WindowMaximise +windows:WindowUnMaximise +windows:WindowFullscreen +windows:WindowUnFullscreen +windows:WindowRestore +windows:WindowMinimise +windows:WindowUnMinimise +windows:WindowClose +windows:WindowSetFocus +windows:WindowKillFocus +windows:WindowDragDrop +windows:WindowDragEnter +windows:WindowDragLeave +windows:WindowDragOver +common:ApplicationStarted +common:WindowMaximise +common:WindowUnMaximise +common:WindowFullscreen +common:WindowUnFullscreen +common:WindowRestore +common:WindowMinimise +common:WindowUnMinimise +common:WindowClosing +common:WindowZoom +common:WindowZoomIn +common:WindowZoomOut +common:WindowZoomReset +common:WindowFocus +common:WindowLostFocus +common:WindowShow +common:WindowHide +common:WindowDPIChanged +common:WindowFilesDropped +common:ThemeChanged diff --git a/v3/pkg/events/events_darwin.go b/v3/pkg/events/events_darwin.go new file mode 100644 index 000000000..7c3e3f2d5 --- /dev/null +++ b/v3/pkg/events/events_darwin.go @@ -0,0 +1,25 @@ +//go:build darwin + +package events + +/* +#cgo CFLAGS: -x objective-c +#cgo LDFLAGS: -framework Cocoa -mmacosx-version-min=10.13 + +#include "events_darwin.h" +#include +#include + +bool hasListener[MAX_EVENTS] = {false}; + +void registerListener(unsigned int event) { + hasListener[event] = true; +} + +bool hasListeners(unsigned int event) { + //return hasListener[event]; + return true; +} + +*/ +import "C" diff --git a/v3/pkg/events/events_darwin.h b/v3/pkg/events/events_darwin.h new file mode 100644 index 000000000..6150307a8 --- /dev/null +++ b/v3/pkg/events/events_darwin.h @@ -0,0 +1,137 @@ +//go:build darwin + +#ifndef _events_darwin_h +#define _events_darwin_h + +extern void processApplicationEvent(unsigned int, void* data); +extern void processWindowEvent(unsigned int, unsigned int); + +#define EventApplicationDidBecomeActive 1024 +#define EventApplicationDidChangeBackingProperties 1025 +#define EventApplicationDidChangeEffectiveAppearance 1026 +#define EventApplicationDidChangeIcon 1027 +#define EventApplicationDidChangeOcclusionState 1028 +#define EventApplicationDidChangeScreenParameters 1029 +#define EventApplicationDidChangeStatusBarFrame 1030 +#define EventApplicationDidChangeStatusBarOrientation 1031 +#define EventApplicationDidFinishLaunching 1032 +#define EventApplicationDidHide 1033 +#define EventApplicationDidResignActiveNotification 1034 +#define EventApplicationDidUnhide 1035 +#define EventApplicationDidUpdate 1036 +#define EventApplicationWillBecomeActive 1037 +#define EventApplicationWillFinishLaunching 1038 +#define EventApplicationWillHide 1039 +#define EventApplicationWillResignActive 1040 +#define EventApplicationWillTerminate 1041 +#define EventApplicationWillUnhide 1042 +#define EventApplicationWillUpdate 1043 +#define EventApplicationDidChangeTheme 1044 +#define EventApplicationShouldHandleReopen 1045 +#define EventWindowDidBecomeKey 1046 +#define EventWindowDidBecomeMain 1047 +#define EventWindowDidBeginSheet 1048 +#define EventWindowDidChangeAlpha 1049 +#define EventWindowDidChangeBackingLocation 1050 +#define EventWindowDidChangeBackingProperties 1051 +#define EventWindowDidChangeCollectionBehavior 1052 +#define EventWindowDidChangeEffectiveAppearance 1053 +#define EventWindowDidChangeOcclusionState 1054 +#define EventWindowDidChangeOrderingMode 1055 +#define EventWindowDidChangeScreen 1056 +#define EventWindowDidChangeScreenParameters 1057 +#define EventWindowDidChangeScreenProfile 1058 +#define EventWindowDidChangeScreenSpace 1059 +#define EventWindowDidChangeScreenSpaceProperties 1060 +#define EventWindowDidChangeSharingType 1061 +#define EventWindowDidChangeSpace 1062 +#define EventWindowDidChangeSpaceOrderingMode 1063 +#define EventWindowDidChangeTitle 1064 +#define EventWindowDidChangeToolbar 1065 +#define EventWindowDidChangeVisibility 1066 +#define EventWindowDidDeminiaturize 1067 +#define EventWindowDidEndSheet 1068 +#define EventWindowDidEnterFullScreen 1069 +#define EventWindowDidEnterVersionBrowser 1070 +#define EventWindowDidExitFullScreen 1071 +#define EventWindowDidExitVersionBrowser 1072 +#define EventWindowDidExpose 1073 +#define EventWindowDidFocus 1074 +#define EventWindowDidMiniaturize 1075 +#define EventWindowDidMove 1076 +#define EventWindowDidOrderOffScreen 1077 +#define EventWindowDidOrderOnScreen 1078 +#define EventWindowDidResignKey 1079 +#define EventWindowDidResignMain 1080 +#define EventWindowDidResize 1081 +#define EventWindowDidUpdate 1082 +#define EventWindowDidUpdateAlpha 1083 +#define EventWindowDidUpdateCollectionBehavior 1084 +#define EventWindowDidUpdateCollectionProperties 1085 +#define EventWindowDidUpdateShadow 1086 +#define EventWindowDidUpdateTitle 1087 +#define EventWindowDidUpdateToolbar 1088 +#define EventWindowDidUpdateVisibility 1089 +#define EventWindowShouldClose 1090 +#define EventWindowWillBecomeKey 1091 +#define EventWindowWillBecomeMain 1092 +#define EventWindowWillBeginSheet 1093 +#define EventWindowWillChangeOrderingMode 1094 +#define EventWindowWillClose 1095 +#define EventWindowWillDeminiaturize 1096 +#define EventWindowWillEnterFullScreen 1097 +#define EventWindowWillEnterVersionBrowser 1098 +#define EventWindowWillExitFullScreen 1099 +#define EventWindowWillExitVersionBrowser 1100 +#define EventWindowWillFocus 1101 +#define EventWindowWillMiniaturize 1102 +#define EventWindowWillMove 1103 +#define EventWindowWillOrderOffScreen 1104 +#define EventWindowWillOrderOnScreen 1105 +#define EventWindowWillResignMain 1106 +#define EventWindowWillResize 1107 +#define EventWindowWillUnfocus 1108 +#define EventWindowWillUpdate 1109 +#define EventWindowWillUpdateAlpha 1110 +#define EventWindowWillUpdateCollectionBehavior 1111 +#define EventWindowWillUpdateCollectionProperties 1112 +#define EventWindowWillUpdateShadow 1113 +#define EventWindowWillUpdateTitle 1114 +#define EventWindowWillUpdateToolbar 1115 +#define EventWindowWillUpdateVisibility 1116 +#define EventWindowWillUseStandardFrame 1117 +#define EventMenuWillOpen 1118 +#define EventMenuDidOpen 1119 +#define EventMenuDidClose 1120 +#define EventMenuWillSendAction 1121 +#define EventMenuDidSendAction 1122 +#define EventMenuWillHighlightItem 1123 +#define EventMenuDidHighlightItem 1124 +#define EventMenuWillDisplayItem 1125 +#define EventMenuDidDisplayItem 1126 +#define EventMenuWillAddItem 1127 +#define EventMenuDidAddItem 1128 +#define EventMenuWillRemoveItem 1129 +#define EventMenuDidRemoveItem 1130 +#define EventMenuWillBeginTracking 1131 +#define EventMenuDidBeginTracking 1132 +#define EventMenuWillEndTracking 1133 +#define EventMenuDidEndTracking 1134 +#define EventMenuWillUpdate 1135 +#define EventMenuDidUpdate 1136 +#define EventMenuWillPopUp 1137 +#define EventMenuDidPopUp 1138 +#define EventMenuWillSendActionToItem 1139 +#define EventMenuDidSendActionToItem 1140 +#define EventWebViewDidStartProvisionalNavigation 1141 +#define EventWebViewDidReceiveServerRedirectForProvisionalNavigation 1142 +#define EventWebViewDidFinishNavigation 1143 +#define EventWebViewDidCommitNavigation 1144 +#define EventWindowFileDraggingEntered 1145 +#define EventWindowFileDraggingPerformed 1146 +#define EventWindowFileDraggingExited 1147 + +#define MAX_EVENTS 1148 + + +#endif \ No newline at end of file diff --git a/v3/pkg/icons/ApplicationDarkMode-256.png b/v3/pkg/icons/ApplicationDarkMode-256.png new file mode 100644 index 000000000..68e5e7666 Binary files /dev/null and b/v3/pkg/icons/ApplicationDarkMode-256.png differ diff --git a/v3/pkg/icons/ApplicationLightMode-256.png b/v3/pkg/icons/ApplicationLightMode-256.png new file mode 100644 index 000000000..fe74e60ea Binary files /dev/null and b/v3/pkg/icons/ApplicationLightMode-256.png differ diff --git a/v3/pkg/icons/DefaultApplicationIcon.png b/v3/pkg/icons/DefaultApplicationIcon.png new file mode 100644 index 000000000..a6129a69f Binary files /dev/null and b/v3/pkg/icons/DefaultApplicationIcon.png differ diff --git a/v3/pkg/icons/DefaultMacTemplateIcon.png b/v3/pkg/icons/DefaultMacTemplateIcon.png new file mode 100644 index 000000000..ee8ad2352 Binary files /dev/null and b/v3/pkg/icons/DefaultMacTemplateIcon.png differ diff --git a/v3/pkg/icons/WailsLogoBlack.png b/v3/pkg/icons/WailsLogoBlack.png new file mode 100644 index 000000000..97269c4bb Binary files /dev/null and b/v3/pkg/icons/WailsLogoBlack.png differ diff --git a/v3/pkg/icons/WailsLogoBlackTransparent.png b/v3/pkg/icons/WailsLogoBlackTransparent.png new file mode 100644 index 000000000..2a767148d Binary files /dev/null and b/v3/pkg/icons/WailsLogoBlackTransparent.png differ diff --git a/v3/pkg/icons/WailsLogoWhite.png b/v3/pkg/icons/WailsLogoWhite.png new file mode 100644 index 000000000..c0e9582cd Binary files /dev/null and b/v3/pkg/icons/WailsLogoWhite.png differ diff --git a/v3/pkg/icons/WailsLogoWhiteTransparent.png b/v3/pkg/icons/WailsLogoWhiteTransparent.png new file mode 100644 index 000000000..e65c582ff Binary files /dev/null and b/v3/pkg/icons/WailsLogoWhiteTransparent.png differ diff --git a/v3/pkg/icons/icon.ico b/v3/pkg/icons/icon.ico new file mode 100644 index 000000000..bfa0690b7 Binary files /dev/null and b/v3/pkg/icons/icon.ico differ diff --git a/v3/pkg/icons/icons.go b/v3/pkg/icons/icons.go new file mode 100644 index 000000000..7434ba33c --- /dev/null +++ b/v3/pkg/icons/icons.go @@ -0,0 +1,33 @@ +package icons + +import _ "embed" + +//go:embed DefaultMacTemplateIcon.png +var SystrayMacTemplate []byte + +//go:embed systray-light.png +var SystrayLight []byte + +//go:embed icon.ico +var DefaultWindowsIcon []byte + +//go:embed systray-dark.png +var SystrayDark []byte + +//go:embed ApplicationDarkMode-256.png +var ApplicationDarkMode256 []byte + +//go:embed ApplicationLightMode-256.png +var ApplicationLightMode256 []byte + +//go:embed WailsLogoBlack.png +var WailsLogoBlack []byte + +//go:embed WailsLogoBlackTransparent.png +var WailsLogoBlackTransparent []byte + +//go:embed WailsLogoWhite.png +var WailsLogoWhite []byte + +//go:embed WailsLogoWhiteTransparent.png +var WailsLogoWhiteTransparent []byte diff --git a/v3/pkg/icons/systray-dark.png b/v3/pkg/icons/systray-dark.png new file mode 100644 index 000000000..6bfe5bd64 Binary files /dev/null and b/v3/pkg/icons/systray-dark.png differ diff --git a/v3/pkg/icons/systray-light.png b/v3/pkg/icons/systray-light.png new file mode 100644 index 000000000..a96c3b33c Binary files /dev/null and b/v3/pkg/icons/systray-light.png differ diff --git a/v3/pkg/mac/mac.go b/v3/pkg/mac/mac.go new file mode 100644 index 000000000..73e0258b0 --- /dev/null +++ b/v3/pkg/mac/mac.go @@ -0,0 +1,24 @@ +//go:build darwin + +// Package mac provides a set of functions to interact with the macOS platform. +package mac + +/* +#cgo CFLAGS: -x objective-c +#cgo LDFLAGS: -framework Foundation -framework ServiceManagement + +#import +#import + +// Get the bundle ID +char* getBundleID() { + NSString *bundleID = [[NSBundle mainBundle] bundleIdentifier]; + return (char*)[bundleID UTF8String]; +} +*/ +import "C" + +// GetBundleID returns the bundle ID of the application. +func GetBundleID() string { + return C.GoString(C.getBundleID()) +} diff --git a/v3/pkg/w32/clipboard.go b/v3/pkg/w32/clipboard.go new file mode 100644 index 000000000..89334c0a4 --- /dev/null +++ b/v3/pkg/w32/clipboard.go @@ -0,0 +1,143 @@ +//go:build windows + +/* + * Based on code originally from https://github.com/atotto/clipboard. Copyright (c) 2013 Ato Araki. All rights reserved. + */ + +package w32 + +import ( + "runtime" + "syscall" + "time" + "unsafe" +) + +const ( + cfUnicodetext = 13 + gmemMoveable = 0x0002 +) + +// waitOpenClipboard opens the clipboard, waiting for up to a second to do so. +func waitOpenClipboard() error { + started := time.Now() + limit := started.Add(time.Second) + var r uintptr + var err error + for time.Now().Before(limit) { + r, _, err = procOpenClipboard.Call(0) + if r != 0 { + return nil + } + time.Sleep(time.Millisecond) + } + return err +} + +func GetClipboardText() (string, error) { + // LockOSThread ensure that the whole method will keep executing on the same thread from begin to end (it actually locks the goroutine thread attribution). + // Otherwise if the goroutine switch thread during execution (which is a common practice), the OpenClipboard and CloseClipboard will happen on two different threads, and it will result in a clipboard deadlock. + runtime.LockOSThread() + defer runtime.UnlockOSThread() + if formatAvailable, _, err := procIsClipboardFormatAvailable.Call(cfUnicodetext); formatAvailable == 0 { + return "", err + } + err := waitOpenClipboard() + if err != nil { + return "", err + } + + h, _, err := procGetClipboardData.Call(cfUnicodetext) + if h == 0 { + _, _, _ = procCloseClipboard.Call() + return "", err + } + + l, _, err := kernelGlobalLock.Call(h) + if l == 0 { + _, _, _ = procCloseClipboard.Call() + return "", err + } + + text := syscall.UTF16ToString((*[1 << 20]uint16)(unsafe.Pointer(l))[:]) + + r, _, err := kernelGlobalUnlock.Call(h) + if r == 0 { + _, _, _ = procCloseClipboard.Call() + return "", err + } + + closed, _, err := procCloseClipboard.Call() + if closed == 0 { + return "", err + } + return text, nil +} + +func SetClipboardText(text string) error { + // LockOSThread ensure that the whole method will keep executing on the same thread from begin to end (it actually locks the goroutine thread attribution). + // Otherwise if the goroutine switch thread during execution (which is a common practice), the OpenClipboard and CloseClipboard will happen on two different threads, and it will result in a clipboard deadlock. + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + err := waitOpenClipboard() + if err != nil { + return err + } + + r, _, err := procEmptyClipboard.Call(0) + if r == 0 { + _, _, _ = procCloseClipboard.Call() + return err + } + + data, err := syscall.UTF16FromString(text) + if err != nil { + return err + } + + // "If the hMem parameter identifies a memory object, the object must have + // been allocated using the function with the GMEM_MOVEABLE flag." + h, _, err := kernelGlobalAlloc.Call(gmemMoveable, uintptr(len(data)*int(unsafe.Sizeof(data[0])))) + if h == 0 { + _, _, _ = procCloseClipboard.Call() + return err + } + defer func() { + if h != 0 { + kernelGlobalFree.Call(h) + } + }() + + l, _, err := kernelGlobalLock.Call(h) + if l == 0 { + _, _, _ = procCloseClipboard.Call() + return err + } + + r, _, err = kernelLstrcpy.Call(l, uintptr(unsafe.Pointer(&data[0]))) + if r == 0 { + _, _, _ = procCloseClipboard.Call() + return err + } + + r, _, err = kernelGlobalUnlock.Call(h) + if r == 0 { + if err.(syscall.Errno) != 0 { + _, _, _ = procCloseClipboard.Call() + return err + } + } + + r, _, err = procSetClipboardData.Call(cfUnicodetext, h) + if r == 0 { + _, _, _ = procCloseClipboard.Call() + return err + } + h = 0 // suppress deferred cleanup + closed, _, err := procCloseClipboard.Call() + if closed == 0 { + return err + } + return nil +} diff --git a/v3/pkg/w32/com.go b/v3/pkg/w32/com.go new file mode 100644 index 000000000..35b79e535 --- /dev/null +++ b/v3/pkg/w32/com.go @@ -0,0 +1,55 @@ +//go:build windows + +package w32 + +import ( + "golang.org/x/sys/windows" + "syscall" + "unsafe" +) + +// ComProc stores a COM procedure. +type ComProc uintptr + +// NewComProc creates a new COM proc from a Go function. +func NewComProc(fn interface{}) ComProc { + return ComProc(windows.NewCallback(fn)) +} + +type EventRegistrationToken struct { + value int64 +} + +// IUnknown +type IUnknown struct { + Vtbl *IUnknownVtbl +} + +type IUnknownVtbl struct { + QueryInterface ComProc + AddRef ComProc + Release ComProc +} + +func (i *IUnknownVtbl) CallRelease(this unsafe.Pointer) error { + _, _, err := i.Release.Call( + uintptr(this), + ) + if err != windows.ERROR_SUCCESS { + return err + } + return nil +} + +type IUnknownImpl interface { + QueryInterface(refiid, object uintptr) uintptr + AddRef() uintptr + Release() uintptr +} + +// Call calls a COM procedure. +// +//go:uintptrescapes +func (p ComProc) Call(a ...uintptr) (r1, r2 uintptr, lastErr error) { + return syscall.SyscallN(uintptr(p), a...) +} diff --git a/v3/pkg/w32/comctl32.go b/v3/pkg/w32/comctl32.go new file mode 100644 index 000000000..b66709f5f --- /dev/null +++ b/v3/pkg/w32/comctl32.go @@ -0,0 +1,112 @@ +//go:build windows + +/* + * Copyright (C) 2019 The Winc Authors. All Rights Reserved. + * Copyright (C) 2010-2012 The W32 Authors. All Rights Reserved. + */ + +package w32 + +import ( + "syscall" + "unsafe" +) + +var ( + modcomctl32 = syscall.NewLazyDLL("comctl32.dll") + + procInitCommonControlsEx = modcomctl32.NewProc("InitCommonControlsEx") + procImageList_Create = modcomctl32.NewProc("ImageList_Create") + procImageList_Destroy = modcomctl32.NewProc("ImageList_Destroy") + procImageList_GetImageCount = modcomctl32.NewProc("ImageList_GetImageCount") + procImageList_SetImageCount = modcomctl32.NewProc("ImageList_SetImageCount") + procImageList_Add = modcomctl32.NewProc("ImageList_Add") + procImageList_ReplaceIcon = modcomctl32.NewProc("ImageList_ReplaceIcon") + procImageList_Remove = modcomctl32.NewProc("ImageList_Remove") + procTrackMouseEvent = modcomctl32.NewProc("_TrackMouseEvent") +) + +func InitCommonControlsEx(lpInitCtrls *INITCOMMONCONTROLSEX) bool { + ret, _, _ := procInitCommonControlsEx.Call( + uintptr(unsafe.Pointer(lpInitCtrls))) + + return ret != 0 +} + +func ImageList_Create(cx, cy int, flags uint, cInitial, cGrow int) HIMAGELIST { + ret, _, _ := procImageList_Create.Call( + uintptr(cx), + uintptr(cy), + uintptr(flags), + uintptr(cInitial), + uintptr(cGrow)) + + if ret == 0 { + panic("Create image list failed") + } + + return HIMAGELIST(ret) +} + +func ImageList_Destroy(himl HIMAGELIST) bool { + ret, _, _ := procImageList_Destroy.Call( + uintptr(himl)) + + return ret != 0 +} + +func ImageList_GetImageCount(himl HIMAGELIST) int { + ret, _, _ := procImageList_GetImageCount.Call( + uintptr(himl)) + + return int(ret) +} + +func ImageList_SetImageCount(himl HIMAGELIST, uNewCount uint) bool { + ret, _, _ := procImageList_SetImageCount.Call( + uintptr(himl), + uintptr(uNewCount)) + + return ret != 0 +} + +func ImageList_Add(himl HIMAGELIST, hbmImage, hbmMask HBITMAP) int { + ret, _, _ := procImageList_Add.Call( + uintptr(himl), + uintptr(hbmImage), + uintptr(hbmMask)) + + return int(ret) +} + +func ImageList_ReplaceIcon(himl HIMAGELIST, i int, hicon HICON) int { + ret, _, _ := procImageList_ReplaceIcon.Call( + uintptr(himl), + uintptr(i), + uintptr(hicon)) + + return int(ret) +} + +func ImageList_AddIcon(himl HIMAGELIST, hicon HICON) int { + return ImageList_ReplaceIcon(himl, -1, hicon) +} + +func ImageList_Remove(himl HIMAGELIST, i int) bool { + ret, _, _ := procImageList_Remove.Call( + uintptr(himl), + uintptr(i)) + + return ret != 0 +} + +func ImageList_RemoveAll(himl HIMAGELIST) bool { + return ImageList_Remove(himl, -1) +} + +func TrackMouseEvent(tme *TRACKMOUSEEVENT) bool { + ret, _, _ := procTrackMouseEvent.Call( + uintptr(unsafe.Pointer(tme))) + + return ret != 0 +} diff --git a/v3/pkg/w32/comdlg32.go b/v3/pkg/w32/comdlg32.go new file mode 100644 index 000000000..d28922c33 --- /dev/null +++ b/v3/pkg/w32/comdlg32.go @@ -0,0 +1,40 @@ +//go:build windows + +/* + * Copyright (C) 2019 The Winc Authors. All Rights Reserved. + * Copyright (C) 2010-2012 The W32 Authors. All Rights Reserved. + */ +package w32 + +import ( + "syscall" + "unsafe" +) + +var ( + modcomdlg32 = syscall.NewLazyDLL("comdlg32.dll") + + procGetSaveFileName = modcomdlg32.NewProc("GetSaveFileNameW") + procGetOpenFileName = modcomdlg32.NewProc("GetOpenFileNameW") + procCommDlgExtendedError = modcomdlg32.NewProc("CommDlgExtendedError") +) + +func GetOpenFileName(ofn *OPENFILENAME) bool { + ret, _, _ := procGetOpenFileName.Call( + uintptr(unsafe.Pointer(ofn))) + + return ret != 0 +} + +func GetSaveFileName(ofn *OPENFILENAME) bool { + ret, _, _ := procGetSaveFileName.Call( + uintptr(unsafe.Pointer(ofn))) + + return ret != 0 +} + +func CommDlgExtendedError() uint { + ret, _, _ := procCommDlgExtendedError.Call() + + return uint(ret) +} diff --git a/v3/pkg/w32/constants.go b/v3/pkg/w32/constants.go new file mode 100644 index 000000000..f6cab85c8 --- /dev/null +++ b/v3/pkg/w32/constants.go @@ -0,0 +1,3621 @@ +//go:build windows + +/* + * Copyright (C) 2019 The Winc Authors. All Rights Reserved. + * Copyright (C) 2010-2012 The W32 Authors. All Rights Reserved. + */ +package w32 + +const ( + FALSE = 0 + TRUE = 1 +) + +const ( + NO_ERROR = 0 + ERROR_SUCCESS = 0 + ERROR_FILE_NOT_FOUND = 2 + ERROR_PATH_NOT_FOUND = 3 + ERROR_ACCESS_DENIED = 5 + ERROR_INVALID_HANDLE = 6 + ERROR_BAD_FORMAT = 11 + ERROR_INVALID_NAME = 123 + ERROR_MORE_DATA = 234 + ERROR_NO_MORE_ITEMS = 259 + ERROR_INVALID_SERVICE_CONTROL = 1052 + ERROR_SERVICE_REQUEST_TIMEOUT = 1053 + ERROR_SERVICE_NO_THREAD = 1054 + ERROR_SERVICE_DATABASE_LOCKED = 1055 + ERROR_SERVICE_ALREADY_RUNNING = 1056 + ERROR_SERVICE_DISABLED = 1058 + ERROR_SERVICE_DOES_NOT_EXIST = 1060 + ERROR_SERVICE_CANNOT_ACCEPT_CTRL = 1061 + ERROR_SERVICE_NOT_ACTIVE = 1062 + ERROR_DATABASE_DOES_NOT_EXIST = 1065 + ERROR_SERVICE_DEPENDENCY_FAIL = 1068 + ERROR_SERVICE_LOGON_FAILED = 1069 + ERROR_SERVICE_MARKED_FOR_DELETE = 1072 + ERROR_SERVICE_DEPENDENCY_DELETED = 1075 +) + +const ( + SE_ERR_FNF = 2 + SE_ERR_PNF = 3 + SE_ERR_ACCESSDENIED = 5 + SE_ERR_OOM = 8 + SE_ERR_DLLNOTFOUND = 32 + SE_ERR_SHARE = 26 + SE_ERR_ASSOCINCOMPLETE = 27 + SE_ERR_DDETIMEOUT = 28 + SE_ERR_DDEFAIL = 29 + SE_ERR_DDEBUSY = 30 + SE_ERR_NOASSOC = 31 +) + +const ( + EDS_ROTATEDMODE = 0x00000001 + EDS_RAWMODE = 0x00000002 + DMDO_DEFAULT = 0 + DMDO_90 = 1 + DMDO_180 = 2 + DMDO_270 = 3 +) + +const ( + CW_USEDEFAULT = ^0x7fffffff +) + +const ( + IMAGE_BITMAP = 0 + IMAGE_ICON = 1 + IMAGE_CURSOR = 2 + IMAGE_ENHMETAFILE = 3 +) + +// ShowWindow constants +const ( + SW_HIDE = 0 + SW_NORMAL = 1 + SW_SHOWNORMAL = 1 + SW_SHOWMINIMIZED = 2 + SW_MAXIMIZE = 3 + SW_SHOWMAXIMIZED = 3 + SW_SHOWNOACTIVATE = 4 + SW_SHOW = 5 + SW_MINIMIZE = 6 + SW_SHOWMINNOACTIVE = 7 + SW_SHOWNA = 8 + SW_RESTORE = 9 + SW_SHOWDEFAULT = 10 + SW_FORCEMINIMIZE = 11 +) + +// Window class styles +const ( + CS_VREDRAW = 0x00000001 + CS_HREDRAW = 0x00000002 + CS_KEYCVTWINDOW = 0x00000004 + CS_DBLCLKS = 0x00000008 + CS_OWNDC = 0x00000020 + CS_CLASSDC = 0x00000040 + CS_PARENTDC = 0x00000080 + CS_NOKEYCVT = 0x00000100 + CS_NOCLOSE = 0x00000200 + CS_SAVEBITS = 0x00000800 + CS_BYTEALIGNCLIENT = 0x00001000 + CS_BYTEALIGNWINDOW = 0x00002000 + CS_GLOBALCLASS = 0x00004000 + CS_IME = 0x00010000 + CS_DROPSHADOW = 0x00020000 +) + +// Predefined cursor constants +const ( + IDC_ARROW = 32512 + IDC_IBEAM = 32513 + IDC_WAIT = 32514 + IDC_CROSS = 32515 + IDC_UPARROW = 32516 + IDC_SIZENWSE = 32642 + IDC_SIZENESW = 32643 + IDC_SIZEWE = 32644 + IDC_SIZENS = 32645 + IDC_SIZEALL = 32646 + IDC_NO = 32648 + IDC_HAND = 32649 + IDC_APPSTARTING = 32650 + IDC_HELP = 32651 + IDC_ICON = 32641 + IDC_SIZE = 32640 +) + +// Predefined icon constants +const ( + IDI_APPLICATION = 32512 + IDI_HAND = 32513 + IDI_QUESTION = 32514 + IDI_EXCLAMATION = 32515 + IDI_ASTERISK = 32516 + IDI_WINLOGO = 32517 + IDI_WARNING = IDI_EXCLAMATION + IDI_ERROR = IDI_HAND + IDI_INFORMATION = IDI_ASTERISK +) + +// Button style constants +const ( + BS_3STATE = 5 + BS_AUTO3STATE = 6 + BS_AUTOCHECKBOX = 3 + BS_AUTORADIOBUTTON = 9 + BS_BITMAP = 128 + BS_BOTTOM = 0x800 + BS_CENTER = 0x300 + BS_CHECKBOX = 2 + BS_DEFPUSHBUTTON = 1 + BS_GROUPBOX = 7 + BS_ICON = 64 + BS_LEFT = 256 + BS_LEFTTEXT = 32 + BS_MULTILINE = 0x2000 + BS_NOTIFY = 0x4000 + BS_OWNERDRAW = 0xB + BS_PUSHBUTTON = 0 + BS_PUSHLIKE = 4096 + BS_RADIOBUTTON = 4 + BS_RIGHT = 512 + BS_RIGHTBUTTON = 32 + BS_TEXT = 0 + BS_TOP = 0x400 + BS_USERBUTTON = 8 + BS_VCENTER = 0xC00 + BS_FLAT = 0x8000 + BS_SPLITBUTTON = 0x000C // >= Vista + BS_DEFSPLITBUTTON = 0x000D // >= Vista +) + +// Button state constants +const ( + BST_CHECKED = 1 + BST_INDETERMINATE = 2 + BST_UNCHECKED = 0 + BST_FOCUS = 8 + BST_PUSHED = 4 +) + +// Predefined brushes constants +const ( + COLOR_3DDKSHADOW = 21 + COLOR_3DFACE = 15 + COLOR_3DHILIGHT = 20 + COLOR_3DHIGHLIGHT = 20 + COLOR_3DLIGHT = 22 + COLOR_BTNHILIGHT = 20 + COLOR_3DSHADOW = 16 + COLOR_ACTIVEBORDER = 10 + COLOR_ACTIVECAPTION = 2 + COLOR_APPWORKSPACE = 12 + COLOR_BACKGROUND = 1 + COLOR_DESKTOP = 1 + COLOR_BTNFACE = 15 + COLOR_BTNHIGHLIGHT = 20 + COLOR_BTNSHADOW = 16 + COLOR_BTNTEXT = 18 + COLOR_CAPTIONTEXT = 9 + COLOR_GRAYTEXT = 17 + COLOR_HIGHLIGHT = 13 + COLOR_HIGHLIGHTTEXT = 14 + COLOR_INACTIVEBORDER = 11 + COLOR_INACTIVECAPTION = 3 + COLOR_INACTIVECAPTIONTEXT = 19 + COLOR_INFOBK = 24 + COLOR_INFOTEXT = 23 + COLOR_MENU = 4 + COLOR_MENUTEXT = 7 + COLOR_SCROLLBAR = 0 + COLOR_WINDOW = 5 + COLOR_WINDOWFRAME = 6 + COLOR_WINDOWTEXT = 8 + COLOR_HOTLIGHT = 26 + COLOR_GRADIENTACTIVECAPTION = 27 + COLOR_GRADIENTINACTIVECAPTION = 28 +) + +// Button message constants +const ( + BM_CLICK = 245 + BM_GETCHECK = 240 + BM_GETIMAGE = 246 + BM_GETSTATE = 242 + BM_SETCHECK = 241 + BM_SETIMAGE = 247 + BM_SETSTATE = 243 + BM_SETSTYLE = 244 +) + +// Button notifications +const ( + BN_CLICKED = 0 + BN_PAINT = 1 + BN_HILITE = 2 + BN_PUSHED = BN_HILITE + BN_UNHILITE = 3 + BN_UNPUSHED = BN_UNHILITE + BN_DISABLE = 4 + BN_DOUBLECLICKED = 5 + BN_DBLCLK = BN_DOUBLECLICKED + BN_SETFOCUS = 6 + BN_KILLFOCUS = 7 +) + +// TrackPopupMenu[Ex] flags +const ( + TPM_CENTERALIGN = 0x0004 + TPM_LEFTALIGN = 0x0000 + TPM_RIGHTALIGN = 0x0008 + TPM_BOTTOMALIGN = 0x0020 + TPM_TOPALIGN = 0x0000 + TPM_VCENTERALIGN = 0x0010 + TPM_NONOTIFY = 0x0080 + TPM_RETURNCMD = 0x0100 + TPM_LEFTBUTTON = 0x0000 + TPM_RIGHTBUTTON = 0x0002 + TPM_HORNEGANIMATION = 0x0800 + TPM_HORPOSANIMATION = 0x0400 + TPM_NOANIMATION = 0x4000 + TPM_VERNEGANIMATION = 0x2000 + TPM_VERPOSANIMATION = 0x1000 + TPM_HORIZONTAL = 0x0000 + TPM_VERTICAL = 0x0040 +) + +// GetWindowLong and GetWindowLongPtr constants +const ( + GWL_EXSTYLE = -20 + GWL_STYLE = -16 + GWL_WNDPROC = -4 + GWLP_WNDPROC = -4 + GWL_HINSTANCE = -6 + GWLP_HINSTANCE = -6 + GWL_HWNDPARENT = -8 + GWLP_HWNDPARENT = -8 + GWL_ID = -12 + GWLP_ID = -12 + GWL_USERDATA = -21 + GWLP_USERDATA = -21 +) + +const ( + GW_HWNDFIRST = 0 + GW_HWNDLAST = 1 + GW_HWNDNEXT = 2 + GW_HWNDPREV = 3 + GW_OWNER = 4 + GW_CHILD = 5 + GW_ENABLEDPOPUP = 6 +) + +// Window style constants +const ( + WS_OVERLAPPED = 0x00000000 + WS_POPUP = 0x80000000 + WS_CHILD = 0x40000000 + WS_MINIMIZE = 0x20000000 + WS_VISIBLE = 0x10000000 + WS_DISABLED = 0x08000000 + WS_CLIPSIBLINGS = 0x04000000 + WS_CLIPCHILDREN = 0x02000000 + WS_MAXIMIZE = 0x01000000 + WS_CAPTION = 0x00C00000 + WS_BORDER = 0x00800000 + WS_DLGFRAME = 0x00400000 + WS_VSCROLL = 0x00200000 + WS_HSCROLL = 0x00100000 + WS_SYSMENU = 0x00080000 + WS_THICKFRAME = 0x00040000 + WS_GROUP = 0x00020000 + WS_TABSTOP = 0x00010000 + WS_MINIMIZEBOX = 0x00020000 + WS_MAXIMIZEBOX = 0x00010000 + WS_TILED = 0x00000000 + WS_ICONIC = 0x20000000 + WS_SIZEBOX = 0x00040000 + WS_OVERLAPPEDWINDOW = 0x00000000 | 0x00C00000 | 0x00080000 | 0x00040000 | 0x00020000 | 0x00010000 + WS_POPUPWINDOW = 0x80000000 | 0x00800000 | 0x00080000 + WS_CHILDWINDOW = 0x40000000 +) + +// Extended window style constants +const ( + WS_EX_DLGMODALFRAME = 0x00000001 + WS_EX_NOPARENTNOTIFY = 0x00000004 + WS_EX_TOPMOST = 0x00000008 + WS_EX_ACCEPTFILES = 0x00000010 + WS_EX_TRANSPARENT = 0x00000020 + WS_EX_MDICHILD = 0x00000040 + WS_EX_TOOLWINDOW = 0x00000080 + WS_EX_WINDOWEDGE = 0x00000100 + WS_EX_CLIENTEDGE = 0x00000200 + WS_EX_CONTEXTHELP = 0x00000400 + WS_EX_COMPOSITED = 0x02000000 + WS_EX_RIGHT = 0x00001000 + WS_EX_LEFT = 0x00000000 + WS_EX_RTLREADING = 0x00002000 + WS_EX_LTRREADING = 0x00000000 + WS_EX_LEFTSCROLLBAR = 0x00004000 + WS_EX_RIGHTSCROLLBAR = 0x00000000 + WS_EX_CONTROLPARENT = 0x00010000 + WS_EX_STATICEDGE = 0x00020000 + WS_EX_APPWINDOW = 0x00040000 + WS_EX_OVERLAPPEDWINDOW = 0x00000100 | 0x00000200 + WS_EX_PALETTEWINDOW = 0x00000100 | 0x00000080 | 0x00000008 + WS_EX_LAYERED = 0x00080000 + WS_EX_NOINHERITLAYOUT = 0x00100000 + WS_EX_NOREDIRECTIONBITMAP = 0x00200000 + WS_EX_LAYOUTRTL = 0x00400000 + WS_EX_NOACTIVATE = 0x08000000 +) + +// Window message constants +const ( + WM_APP = 32768 + WM_ACTIVATE = 6 + WM_ACTIVATEAPP = 28 + WM_AFXFIRST = 864 + WM_AFXLAST = 895 + WM_ASKCBFORMATNAME = 780 + WM_CANCELJOURNAL = 75 + WM_CANCELMODE = 31 + WM_CAPTURECHANGED = 533 + WM_CHANGECBCHAIN = 781 + WM_CHAR = 258 + WM_CHARTOITEM = 47 + WM_CHILDACTIVATE = 34 + WM_CLEAR = 771 + WM_CLOSE = 16 + WM_COMMAND = 273 + WM_COMMNOTIFY = 68 /* OBSOLETE */ + WM_COMPACTING = 65 + WM_COMPAREITEM = 57 + WM_CONTEXTMENU = 123 + WM_COPY = 769 + WM_COPYDATA = 74 + WM_CREATE = 1 + WM_CTLCOLORBTN = 309 + WM_CTLCOLORDLG = 310 + WM_CTLCOLOREDIT = 307 + WM_CTLCOLORLISTBOX = 308 + WM_CTLCOLORMSGBOX = 306 + WM_CTLCOLORSCROLLBAR = 311 + WM_CTLCOLORSTATIC = 312 + WM_CUT = 768 + WM_DEADCHAR = 259 + WM_DELETEITEM = 45 + WM_DESTROY = 2 + WM_DESTROYCLIPBOARD = 775 + WM_DEVICECHANGE = 537 + WM_DEVMODECHANGE = 27 + WM_DISPLAYCHANGE = 126 + WM_DRAWCLIPBOARD = 776 + WM_DRAWITEM = 43 + WM_DROPFILES = 563 + WM_ENABLE = 10 + WM_ENDSESSION = 22 + WM_ENTERIDLE = 289 + WM_ENTERMENULOOP = 529 + WM_ENTERSIZEMOVE = 561 + WM_ERASEBKGND = 20 + WM_EXITMENULOOP = 530 + WM_EXITSIZEMOVE = 562 + WM_FONTCHANGE = 29 + WM_GETDLGCODE = 135 + WM_GETFONT = 49 + WM_GETHOTKEY = 51 + WM_GETICON = 127 + WM_GETMINMAXINFO = 36 + WM_GETTEXT = 13 + WM_GETTEXTLENGTH = 14 + WM_HANDHELDFIRST = 856 + WM_HANDHELDLAST = 863 + WM_HELP = 83 + WM_HOTKEY = 786 + WM_HSCROLL = 276 + WM_HSCROLLCLIPBOARD = 782 + WM_ICONERASEBKGND = 39 + WM_INITDIALOG = 272 + WM_INITMENU = 278 + WM_INITMENUPOPUP = 279 + WM_INPUT = 0x00FF + WM_INPUTLANGCHANGE = 81 + WM_INPUTLANGCHANGEREQUEST = 80 + WM_KEYDOWN = 256 + WM_KEYUP = 257 + WM_KILLFOCUS = 8 + WM_MDIACTIVATE = 546 + WM_MDICASCADE = 551 + WM_MDICREATE = 544 + WM_MDIDESTROY = 545 + WM_MDIGETACTIVE = 553 + WM_MDIICONARRANGE = 552 + WM_MDIMAXIMIZE = 549 + WM_MDINEXT = 548 + WM_MDIREFRESHMENU = 564 + WM_MDIRESTORE = 547 + WM_MDISETMENU = 560 + WM_MDITILE = 550 + WM_MEASUREITEM = 44 + WM_GETOBJECT = 0x003D + WM_CHANGEUISTATE = 0x0127 + WM_UPDATEUISTATE = 0x0128 + WM_QUERYUISTATE = 0x0129 + WM_UNINITMENUPOPUP = 0x0125 + WM_MENURBUTTONUP = 290 + WM_MENUCOMMAND = 0x0126 + WM_MENUGETOBJECT = 0x0124 + WM_MENUDRAG = 0x0123 + WM_APPCOMMAND = 0x0319 + WM_MENUCHAR = 288 + WM_MENUSELECT = 287 + WM_MOVE = 3 + WM_MOVING = 534 + WM_NCACTIVATE = 134 + WM_NCCALCSIZE = 131 + WM_NCCREATE = 129 + WM_NCDESTROY = 130 + WM_NCHITTEST = 132 + WM_NCLBUTTONDBLCLK = 163 + WM_NCLBUTTONDOWN = 161 + WM_NCLBUTTONUP = 162 + WM_NCMBUTTONDBLCLK = 169 + WM_NCMBUTTONDOWN = 167 + WM_NCMBUTTONUP = 168 + WM_NCXBUTTONDOWN = 171 + WM_NCXBUTTONUP = 172 + WM_NCXBUTTONDBLCLK = 173 + WM_NCMOUSEHOVER = 0x02A0 + WM_NCMOUSELEAVE = 0x02A2 + WM_NCMOUSEMOVE = 160 + WM_NCPAINT = 133 + WM_NCRBUTTONDBLCLK = 166 + WM_NCRBUTTONDOWN = 164 + WM_NCRBUTTONUP = 165 + WM_NEXTDLGCTL = 40 + WM_NEXTMENU = 531 + WM_NOTIFY = 78 + WM_NOTIFYFORMAT = 85 + WM_NULL = 0 + WM_PAINT = 15 + WM_PAINTCLIPBOARD = 777 + WM_PAINTICON = 38 + WM_PALETTECHANGED = 785 + WM_PALETTEISCHANGING = 784 + WM_PARENTNOTIFY = 528 + WM_PASTE = 770 + WM_PENWINFIRST = 896 + WM_PENWINLAST = 911 + WM_POWER = 72 + WM_POWERBROADCAST = 536 + WM_PRINT = 791 + WM_PRINTCLIENT = 792 + WM_QUERYDRAGICON = 55 + WM_QUERYENDSESSION = 17 + WM_QUERYNEWPALETTE = 783 + WM_QUERYOPEN = 19 + WM_QUEUESYNC = 35 + WM_QUIT = 18 + WM_RENDERALLFORMATS = 774 + WM_RENDERFORMAT = 773 + WM_SETCURSOR = 32 + WM_SETFOCUS = 7 + WM_SETFONT = 48 + WM_SETHOTKEY = 50 + WM_SETICON = 128 + WM_SETREDRAW = 11 + WM_SETTEXT = 12 + WM_SETTINGCHANGE = 26 + WM_SHOWWINDOW = 24 + WM_SIZE = 5 + WM_SIZECLIPBOARD = 779 + WM_SIZING = 532 + WM_SPOOLERSTATUS = 42 + WM_STYLECHANGED = 125 + WM_STYLECHANGING = 124 + WM_SYSCHAR = 262 + WM_SYSCOLORCHANGE = 21 + WM_SYSCOMMAND = 274 + WM_SYSDEADCHAR = 263 + WM_SYSKEYDOWN = 260 + WM_SYSKEYUP = 261 + WM_TCARD = 82 + WM_THEMECHANGED = 794 + WM_TIMECHANGE = 30 + WM_TIMER = 275 + WM_UNDO = 772 + WM_USER = 1024 + WM_USERCHANGED = 84 + WM_VKEYTOITEM = 46 + WM_VSCROLL = 277 + WM_VSCROLLCLIPBOARD = 778 + WM_WINDOWPOSCHANGED = 71 + WM_WINDOWPOSCHANGING = 70 + WM_WININICHANGE = 26 + WM_KEYFIRST = 256 + WM_KEYLAST = 264 + WM_SYNCPAINT = 136 + WM_MOUSEACTIVATE = 33 + WM_MOUSEMOVE = 512 + WM_LBUTTONDOWN = 513 + WM_LBUTTONUP = 514 + WM_LBUTTONDBLCLK = 515 + WM_RBUTTONDOWN = 516 + WM_RBUTTONUP = 517 + WM_RBUTTONDBLCLK = 518 + WM_MBUTTONDOWN = 519 + WM_MBUTTONUP = 520 + WM_MBUTTONDBLCLK = 521 + WM_MOUSEWHEEL = 522 + WM_MOUSEHWHEEL = 526 + WM_MOUSEFIRST = 512 + WM_XBUTTONDOWN = 523 + WM_XBUTTONUP = 524 + WM_XBUTTONDBLCLK = 525 + WM_MOUSELAST = 525 + WM_MOUSEHOVER = 0x2A1 + WM_MOUSELEAVE = 0x2A3 + WM_CLIPBOARDUPDATE = 0x031D + WM_DPICHANGED = 0x02E0 +) + +// WM_ACTIVATE +const ( + WA_INACTIVE = 0 + WA_ACTIVE = 1 + WA_CLICKACTIVE = 2 +) + +const LF_FACESIZE = 32 + +// Font weight constants +const ( + FW_DONTCARE = 0 + FW_THIN = 100 + FW_EXTRALIGHT = 200 + FW_ULTRALIGHT = FW_EXTRALIGHT + FW_LIGHT = 300 + FW_NORMAL = 400 + FW_REGULAR = 400 + FW_MEDIUM = 500 + FW_SEMIBOLD = 600 + FW_DEMIBOLD = FW_SEMIBOLD + FW_BOLD = 700 + FW_EXTRABOLD = 800 + FW_ULTRABOLD = FW_EXTRABOLD + FW_HEAVY = 900 + FW_BLACK = FW_HEAVY +) + +// Charset constants +const ( + ANSI_CHARSET = 0 + DEFAULT_CHARSET = 1 + SYMBOL_CHARSET = 2 + SHIFTJIS_CHARSET = 128 + HANGEUL_CHARSET = 129 + HANGUL_CHARSET = 129 + GB2312_CHARSET = 134 + CHINESEBIG5_CHARSET = 136 + GREEK_CHARSET = 161 + TURKISH_CHARSET = 162 + HEBREW_CHARSET = 177 + ARABIC_CHARSET = 178 + BALTIC_CHARSET = 186 + RUSSIAN_CHARSET = 204 + THAI_CHARSET = 222 + EASTEUROPE_CHARSET = 238 + OEM_CHARSET = 255 + JOHAB_CHARSET = 130 + VIETNAMESE_CHARSET = 163 + MAC_CHARSET = 77 +) + +const ( + // PBT_APMPOWERSTATUSCHANGE - Power status has changed. + PBT_APMPOWERSTATUSCHANGE = 10 + + // PBT_APMRESUMEAUTOMATIC -Operation is resuming automatically from a low-power state. This message is sent every time the system resumes. + PBT_APMRESUMEAUTOMATIC = 18 + + // PBT_APMRESUMESUSPEND - Operation is resuming from a low-power state. This message is sent after PBT_APMRESUMEAUTOMATIC if the resume is triggered by user input, such as pressing a key. + PBT_APMRESUMESUSPEND = 7 + + // PBT_APMSUSPEND - System is suspending operation. + PBT_APMSUSPEND = 4 + + // PBT_POWERSETTINGCHANGE - A power setting change event has been received. + PBT_POWERSETTINGCHANGE = 32787 +) + +// Font output precision constants +const ( + OUT_DEFAULT_PRECIS = 0 + OUT_STRING_PRECIS = 1 + OUT_CHARACTER_PRECIS = 2 + OUT_STROKE_PRECIS = 3 + OUT_TT_PRECIS = 4 + OUT_DEVICE_PRECIS = 5 + OUT_RASTER_PRECIS = 6 + OUT_TT_ONLY_PRECIS = 7 + OUT_OUTLINE_PRECIS = 8 + OUT_PS_ONLY_PRECIS = 10 +) + +// Font clipping precision constants +const ( + CLIP_DEFAULT_PRECIS = 0 + CLIP_CHARACTER_PRECIS = 1 + CLIP_STROKE_PRECIS = 2 + CLIP_MASK = 15 + CLIP_LH_ANGLES = 16 + CLIP_TT_ALWAYS = 32 + CLIP_EMBEDDED = 128 +) + +// Font output quality constants +const ( + DEFAULT_QUALITY = 0 + DRAFT_QUALITY = 1 + PROOF_QUALITY = 2 + NONANTIALIASED_QUALITY = 3 + ANTIALIASED_QUALITY = 4 + CLEARTYPE_QUALITY = 5 +) + +// Font pitch constants +const ( + DEFAULT_PITCH = 0 + FIXED_PITCH = 1 + VARIABLE_PITCH = 2 +) + +// Font family constants +const ( + FF_DECORATIVE = 80 + FF_DONTCARE = 0 + FF_MODERN = 48 + FF_ROMAN = 16 + FF_SCRIPT = 64 + FF_SWISS = 32 +) + +// DeviceCapabilities capabilities +const ( + DC_FIELDS = 1 + DC_PAPERS = 2 + DC_PAPERSIZE = 3 + DC_MINEXTENT = 4 + DC_MAXEXTENT = 5 + DC_BINS = 6 + DC_DUPLEX = 7 + DC_SIZE = 8 + DC_EXTRA = 9 + DC_VERSION = 10 + DC_DRIVER = 11 + DC_BINNAMES = 12 + DC_ENUMRESOLUTIONS = 13 + DC_FILEDEPENDENCIES = 14 + DC_TRUETYPE = 15 + DC_PAPERNAMES = 16 + DC_ORIENTATION = 17 + DC_COPIES = 18 + DC_BINADJUST = 19 + DC_EMF_COMPLIANT = 20 + DC_DATATYPE_PRODUCED = 21 + DC_COLLATE = 22 + DC_MANUFACTURER = 23 + DC_MODEL = 24 + DC_PERSONALITY = 25 + DC_PRINTRATE = 26 + DC_PRINTRATEUNIT = 27 + DC_PRINTERMEM = 28 + DC_MEDIAREADY = 29 + DC_STAPLE = 30 + DC_PRINTRATEPPM = 31 + DC_COLORDEVICE = 32 + DC_NUP = 33 + DC_MEDIATYPENAMES = 34 + DC_MEDIATYPES = 35 +) + +// GetDeviceCaps index constants +const ( + DRIVERVERSION = 0 + TECHNOLOGY = 2 + HORZSIZE = 4 + VERTSIZE = 6 + HORZRES = 8 + VERTRES = 10 + LOGPIXELSX = 88 + LOGPIXELSY = 90 + BITSPIXEL = 12 + PLANES = 14 + NUMBRUSHES = 16 + NUMPENS = 18 + NUMFONTS = 22 + NUMCOLORS = 24 + NUMMARKERS = 20 + ASPECTX = 40 + ASPECTY = 42 + ASPECTXY = 44 + PDEVICESIZE = 26 + CLIPCAPS = 36 + SIZEPALETTE = 104 + NUMRESERVED = 106 + COLORRES = 108 + PHYSICALWIDTH = 110 + PHYSICALHEIGHT = 111 + PHYSICALOFFSETX = 112 + PHYSICALOFFSETY = 113 + SCALINGFACTORX = 114 + SCALINGFACTORY = 115 + VREFRESH = 116 + DESKTOPHORZRES = 118 + DESKTOPVERTRES = 117 + BLTALIGNMENT = 119 + SHADEBLENDCAPS = 120 + COLORMGMTCAPS = 121 + RASTERCAPS = 38 + CURVECAPS = 28 + LINECAPS = 30 + POLYGONALCAPS = 32 + TEXTCAPS = 34 +) + +// GetDeviceCaps TECHNOLOGY constants +const ( + DT_PLOTTER = 0 + DT_RASDISPLAY = 1 + DT_RASPRINTER = 2 + DT_RASCAMERA = 3 + DT_CHARSTREAM = 4 + DT_METAFILE = 5 + DT_DISPFILE = 6 +) + +// GetDeviceCaps SHADEBLENDCAPS constants +const ( + SB_NONE = 0x00 + SB_CONST_ALPHA = 0x01 + SB_PIXEL_ALPHA = 0x02 + SB_PREMULT_ALPHA = 0x04 + SB_GRAD_RECT = 0x10 + SB_GRAD_TRI = 0x20 +) + +// GetDeviceCaps COLORMGMTCAPS constants +const ( + CM_NONE = 0x00 + CM_DEVICE_ICM = 0x01 + CM_GAMMA_RAMP = 0x02 + CM_CMYK_COLOR = 0x04 +) + +// GetDeviceCaps RASTERCAPS constants +const ( + RC_BANDING = 2 + RC_BITBLT = 1 + RC_BITMAP64 = 8 + RC_DI_BITMAP = 128 + RC_DIBTODEV = 512 + RC_FLOODFILL = 4096 + RC_GDI20_OUTPUT = 16 + RC_PALETTE = 256 + RC_SCALING = 4 + RC_STRETCHBLT = 2048 + RC_STRETCHDIB = 8192 + RC_DEVBITS = 0x8000 + RC_OP_DX_OUTPUT = 0x4000 +) + +// GetDeviceCaps CURVECAPS constants +const ( + CC_NONE = 0 + CC_CIRCLES = 1 + CC_PIE = 2 + CC_CHORD = 4 + CC_ELLIPSES = 8 + CC_WIDE = 16 + CC_STYLED = 32 + CC_WIDESTYLED = 64 + CC_INTERIORS = 128 + CC_ROUNDRECT = 256 +) + +// GetDeviceCaps LINECAPS constants +const ( + LC_NONE = 0 + LC_POLYLINE = 2 + LC_MARKER = 4 + LC_POLYMARKER = 8 + LC_WIDE = 16 + LC_STYLED = 32 + LC_WIDESTYLED = 64 + LC_INTERIORS = 128 +) + +// GetDeviceCaps POLYGONALCAPS constants +const ( + PC_NONE = 0 + PC_POLYGON = 1 + PC_POLYPOLYGON = 256 + PC_PATHS = 512 + PC_RECTANGLE = 2 + PC_WINDPOLYGON = 4 + PC_SCANLINE = 8 + PC_TRAPEZOID = 4 + PC_WIDE = 16 + PC_STYLED = 32 + PC_WIDESTYLED = 64 + PC_INTERIORS = 128 +) + +// GetDeviceCaps TEXTCAPS constants +const ( + TC_OP_CHARACTER = 1 + TC_OP_STROKE = 2 + TC_CP_STROKE = 4 + TC_CR_90 = 8 + TC_CR_ANY = 16 + TC_SF_X_YINDEP = 32 + TC_SA_DOUBLE = 64 + TC_SA_INTEGER = 128 + TC_SA_CONTIN = 256 + TC_EA_DOUBLE = 512 + TC_IA_ABLE = 1024 + TC_UA_ABLE = 2048 + TC_SO_ABLE = 4096 + TC_RA_ABLE = 8192 + TC_VA_ABLE = 16384 + TC_RESERVED = 32768 + TC_SCROLLBLT = 65536 +) + +// Static control styles +const ( + SS_BITMAP = 14 + SS_BLACKFRAME = 7 + SS_BLACKRECT = 4 + SS_CENTER = 1 + SS_CENTERIMAGE = 512 + SS_EDITCONTROL = 0x2000 + SS_ENHMETAFILE = 15 + SS_ETCHEDFRAME = 18 + SS_ETCHEDHORZ = 16 + SS_ETCHEDVERT = 17 + SS_GRAYFRAME = 8 + SS_GRAYRECT = 5 + SS_ICON = 3 + SS_LEFT = 0 + SS_LEFTNOWORDWRAP = 0xc + SS_NOPREFIX = 128 + SS_NOTIFY = 256 + SS_OWNERDRAW = 0xd + SS_REALSIZECONTROL = 0x040 + SS_REALSIZEIMAGE = 0x800 + SS_RIGHT = 2 + SS_RIGHTJUST = 0x400 + SS_SIMPLE = 11 + SS_SUNKEN = 4096 + SS_WHITEFRAME = 9 + SS_WHITERECT = 6 + SS_USERITEM = 10 + SS_TYPEMASK = 0x0000001F + SS_ENDELLIPSIS = 0x00004000 + SS_PATHELLIPSIS = 0x00008000 + SS_WORDELLIPSIS = 0x0000C000 + SS_ELLIPSISMASK = 0x0000C000 +) + +const ( + FLASHW_STOP = 0 // Stop flashing. The system restores the window to its original state. + FLASHW_CAPTION = 1 // Flash the window caption. + FLASHW_TRAY = 2 // Flash the taskbar button. + FLASHW_ALL = 3 // Flash both the window caption and taskbar button. This is equivalent to setting the FLASHW_CAPTION | FLASHW_TRAY flags. + FLASHW_TIMER = 4 // Flash continuously, until the FLASHW_STOP flag is set. + FLASHW_TIMERNOFG = 12 // Flash continuously until the window comes to the foreground. +) + +// Edit styles +const ( + ES_LEFT = 0x0000 + ES_CENTER = 0x0001 + ES_RIGHT = 0x0002 + ES_MULTILINE = 0x0004 + ES_UPPERCASE = 0x0008 + ES_LOWERCASE = 0x0010 + ES_PASSWORD = 0x0020 + ES_AUTOVSCROLL = 0x0040 + ES_AUTOHSCROLL = 0x0080 + ES_NOHIDESEL = 0x0100 + ES_OEMCONVERT = 0x0400 + ES_READONLY = 0x0800 + ES_WANTRETURN = 0x1000 + ES_NUMBER = 0x2000 +) + +// Edit notifications +const ( + EN_SETFOCUS = 0x0100 + EN_KILLFOCUS = 0x0200 + EN_CHANGE = 0x0300 + EN_UPDATE = 0x0400 + EN_ERRSPACE = 0x0500 + EN_MAXTEXT = 0x0501 + EN_HSCROLL = 0x0601 + EN_VSCROLL = 0x0602 + EN_ALIGN_LTR_EC = 0x0700 + EN_ALIGN_RTL_EC = 0x0701 +) + +// Edit messages +const ( + EM_GETSEL = 0x00B0 + EM_SETSEL = 0x00B1 + EM_GETRECT = 0x00B2 + EM_SETRECT = 0x00B3 + EM_SETRECTNP = 0x00B4 + EM_SCROLL = 0x00B5 + EM_LINESCROLL = 0x00B6 + EM_SCROLLCARET = 0x00B7 + EM_GETMODIFY = 0x00B8 + EM_SETMODIFY = 0x00B9 + EM_GETLINECOUNT = 0x00BA + EM_LINEINDEX = 0x00BB + EM_SETHANDLE = 0x00BC + EM_GETHANDLE = 0x00BD + EM_GETTHUMB = 0x00BE + EM_LINELENGTH = 0x00C1 + EM_REPLACESEL = 0x00C2 + EM_GETLINE = 0x00C4 + EM_LIMITTEXT = 0x00C5 + EM_CANUNDO = 0x00C6 + EM_UNDO = 0x00C7 + EM_FMTLINES = 0x00C8 + EM_LINEFROMCHAR = 0x00C9 + EM_SETTABSTOPS = 0x00CB + EM_SETPASSWORDCHAR = 0x00CC + EM_EMPTYUNDOBUFFER = 0x00CD + EM_GETFIRSTVISIBLELINE = 0x00CE + EM_SETREADONLY = 0x00CF + EM_SETWORDBREAKPROC = 0x00D0 + EM_GETWORDBREAKPROC = 0x00D1 + EM_GETPASSWORDCHAR = 0x00D2 + EM_SETMARGINS = 0x00D3 + EM_GETMARGINS = 0x00D4 + EM_SETLIMITTEXT = EM_LIMITTEXT + EM_GETLIMITTEXT = 0x00D5 + EM_POSFROMCHAR = 0x00D6 + EM_CHARFROMPOS = 0x00D7 + EM_SETIMESTATUS = 0x00D8 + EM_GETIMESTATUS = 0x00D9 + EM_SETCUEBANNER = 0x1501 + EM_GETCUEBANNER = 0x1502 +) + +const ( + CCM_FIRST = 0x2000 + CCM_LAST = CCM_FIRST + 0x200 + CCM_SETBKCOLOR = 8193 + CCM_SETCOLORSCHEME = 8194 + CCM_GETCOLORSCHEME = 8195 + CCM_GETDROPTARGET = 8196 + CCM_SETUNICODEFORMAT = 8197 + CCM_GETUNICODEFORMAT = 8198 + CCM_SETVERSION = 0x2007 + CCM_GETVERSION = 0x2008 + CCM_SETNOTIFYWINDOW = 0x2009 + CCM_SETWINDOWTHEME = 0x200b + CCM_DPISCALE = 0x200c +) + +// Common controls styles +const ( + CCS_TOP = 1 + CCS_NOMOVEY = 2 + CCS_BOTTOM = 3 + CCS_NORESIZE = 4 + CCS_NOPARENTALIGN = 8 + CCS_ADJUSTABLE = 32 + CCS_NODIVIDER = 64 + CCS_VERT = 128 + CCS_LEFT = 129 + CCS_NOMOVEX = 130 + CCS_RIGHT = 131 +) + +// ProgressBar messages +const ( + PROGRESS_CLASS = "msctls_progress32" + PBM_SETPOS = WM_USER + 2 + PBM_DELTAPOS = WM_USER + 3 + PBM_SETSTEP = WM_USER + 4 + PBM_STEPIT = WM_USER + 5 + PBM_SETRANGE32 = 1030 + PBM_GETRANGE = 1031 + PBM_GETPOS = 1032 + PBM_SETBARCOLOR = 1033 + PBM_SETBKCOLOR = CCM_SETBKCOLOR + PBS_SMOOTH = 1 + PBS_VERTICAL = 4 +) + +// Trackbar messages and constants +const ( + TBS_AUTOTICKS = 1 + TBS_VERT = 2 + TBS_HORZ = 0 + TBS_TOP = 4 + TBS_BOTTOM = 0 + TBS_LEFT = 4 + TBS_RIGHT = 0 + TBS_BOTH = 8 + TBS_NOTICKS = 16 + TBS_ENABLESELRANGE = 32 + TBS_FIXEDLENGTH = 64 + TBS_NOTHUMB = 128 + TBS_TOOLTIPS = 0x0100 +) + +const ( + TBM_GETPOS = WM_USER + TBM_GETRANGEMIN = WM_USER + 1 + TBM_GETRANGEMAX = WM_USER + 2 + TBM_GETTIC = WM_USER + 3 + TBM_SETTIC = WM_USER + 4 + TBM_SETPOS = WM_USER + 5 + TBM_SETRANGE = WM_USER + 6 + TBM_SETRANGEMIN = WM_USER + 7 + TBM_SETRANGEMAX = WM_USER + 8 + TBM_CLEARTICS = WM_USER + 9 + TBM_SETSEL = WM_USER + 10 + TBM_SETSELSTART = WM_USER + 11 + TBM_SETSELEND = WM_USER + 12 + TBM_GETPTICS = WM_USER + 14 + TBM_GETTICPOS = WM_USER + 15 + TBM_GETNUMTICS = WM_USER + 16 + TBM_GETSELSTART = WM_USER + 17 + TBM_GETSELEND = WM_USER + 18 + TBM_CLEARSEL = WM_USER + 19 + TBM_SETTICFREQ = WM_USER + 20 + TBM_SETPAGESIZE = WM_USER + 21 + TBM_GETPAGESIZE = WM_USER + 22 + TBM_SETLINESIZE = WM_USER + 23 + TBM_GETLINESIZE = WM_USER + 24 + TBM_GETTHUMBRECT = WM_USER + 25 + TBM_GETCHANNELRECT = WM_USER + 26 + TBM_SETTHUMBLENGTH = WM_USER + 27 + TBM_GETTHUMBLENGTH = WM_USER + 28 + TBM_SETTOOLTIPS = WM_USER + 29 + TBM_GETTOOLTIPS = WM_USER + 30 + TBM_SETTIPSIDE = WM_USER + 31 + TBM_SETBUDDY = WM_USER + 32 + TBM_GETBUDDY = WM_USER + 33 +) + +const ( + TB_LINEUP = 0 + TB_LINEDOWN = 1 + TB_PAGEUP = 2 + TB_PAGEDOWN = 3 + TB_THUMBPOSITION = 4 + TB_THUMBTRACK = 5 + TB_TOP = 6 + TB_BOTTOM = 7 + TB_ENDTRACK = 8 +) + +// GetOpenFileName and GetSaveFileName extended flags +const ( + OFN_EX_NOPLACESBAR = 0x00000001 +) + +// GetOpenFileName and GetSaveFileName flags +const ( + OFN_ALLOWMULTISELECT = 0x00000200 + OFN_CREATEPROMPT = 0x00002000 + OFN_DONTADDTORECENT = 0x02000000 + OFN_ENABLEHOOK = 0x00000020 + OFN_ENABLEINCLUDENOTIFY = 0x00400000 + OFN_ENABLESIZING = 0x00800000 + OFN_ENABLETEMPLATE = 0x00000040 + OFN_ENABLETEMPLATEHANDLE = 0x00000080 + OFN_EXPLORER = 0x00080000 + OFN_EXTENSIONDIFFERENT = 0x00000400 + OFN_FILEMUSTEXIST = 0x00001000 + OFN_FORCESHOWHIDDEN = 0x10000000 + OFN_HIDEREADONLY = 0x00000004 + OFN_LONGNAMES = 0x00200000 + OFN_NOCHANGEDIR = 0x00000008 + OFN_NODEREFERENCELINKS = 0x00100000 + OFN_NOLONGNAMES = 0x00040000 + OFN_NONETWORKBUTTON = 0x00020000 + OFN_NOREADONLYRETURN = 0x00008000 + OFN_NOTESTFILECREATE = 0x00010000 + OFN_NOVALIDATE = 0x00000100 + OFN_OVERWRITEPROMPT = 0x00000002 + OFN_PATHMUSTEXIST = 0x00000800 + OFN_READONLY = 0x00000001 + OFN_SHAREAWARE = 0x00004000 + OFN_SHOWHELP = 0x00000010 +) + +// SHBrowseForFolder flags +const ( + BIF_RETURNONLYFSDIRS = 0x00000001 + BIF_DONTGOBELOWDOMAIN = 0x00000002 + BIF_STATUSTEXT = 0x00000004 + BIF_RETURNFSANCESTORS = 0x00000008 + BIF_EDITBOX = 0x00000010 + BIF_VALIDATE = 0x00000020 + BIF_NEWDIALOGSTYLE = 0x00000040 + BIF_BROWSEINCLUDEURLS = 0x00000080 + BIF_USENEWUI = BIF_EDITBOX | BIF_NEWDIALOGSTYLE + BIF_UAHINT = 0x00000100 + BIF_NONEWFOLDERBUTTON = 0x00000200 + BIF_NOTRANSLATETARGETS = 0x00000400 + BIF_BROWSEFORCOMPUTER = 0x00001000 + BIF_BROWSEFORPRINTER = 0x00002000 + BIF_BROWSEINCLUDEFILES = 0x00004000 + BIF_SHAREABLE = 0x00008000 + BIF_BROWSEFILEJUNCTIONS = 0x00010000 +) + +// MessageBox flags +const ( + MB_OK = 0x00000000 + MB_OKCANCEL = 0x00000001 + MB_ABORTRETRYIGNORE = 0x00000002 + MB_YESNOCANCEL = 0x00000003 + MB_YESNO = 0x00000004 + MB_RETRYCANCEL = 0x00000005 + MB_CANCELTRYCONTINUE = 0x00000006 + MB_ICONHAND = 0x00000010 + MB_ICONQUESTION = 0x00000020 + MB_ICONEXCLAMATION = 0x00000030 + MB_ICONASTERISK = 0x00000040 + MB_USERICON = 0x00000080 + MB_ICONWARNING = MB_ICONEXCLAMATION + MB_ICONERROR = MB_ICONHAND + MB_ICONINFORMATION = MB_ICONASTERISK + MB_ICONSTOP = MB_ICONHAND + MB_DEFBUTTON1 = 0x00000000 + MB_DEFBUTTON2 = 0x00000100 + MB_DEFBUTTON3 = 0x00000200 + MB_DEFBUTTON4 = 0x00000300 +) + +// COM +const ( + E_INVALIDARG = 0x80070057 + E_OUTOFMEMORY = 0x8007000E + E_UNEXPECTED = 0x8000FFFF +) + +const ( + S_OK = 0 + S_FALSE = 0x0001 + RPC_E_CHANGED_MODE = 0x80010106 +) + +// GetSystemMetrics constants +const ( + SM_CXSCREEN = 0 + SM_CYSCREEN = 1 + SM_CXVSCROLL = 2 + SM_CYHSCROLL = 3 + SM_CYCAPTION = 4 + SM_CXBORDER = 5 + SM_CYBORDER = 6 + SM_CXDLGFRAME = 7 + SM_CYDLGFRAME = 8 + SM_CYVTHUMB = 9 + SM_CXHTHUMB = 10 + SM_CXICON = 11 + SM_CYICON = 12 + SM_CXCURSOR = 13 + SM_CYCURSOR = 14 + SM_CYMENU = 15 + SM_CXFULLSCREEN = 16 + SM_CYFULLSCREEN = 17 + SM_CYKANJIWINDOW = 18 + SM_MOUSEPRESENT = 19 + SM_CYVSCROLL = 20 + SM_CXHSCROLL = 21 + SM_DEBUG = 22 + SM_SWAPBUTTON = 23 + SM_RESERVED1 = 24 + SM_RESERVED2 = 25 + SM_RESERVED3 = 26 + SM_RESERVED4 = 27 + SM_CXMIN = 28 + SM_CYMIN = 29 + SM_CXSIZE = 30 + SM_CYSIZE = 31 + SM_CXFRAME = 32 + SM_CYFRAME = 33 + SM_CXMINTRACK = 34 + SM_CYMINTRACK = 35 + SM_CXDOUBLECLK = 36 + SM_CYDOUBLECLK = 37 + SM_CXICONSPACING = 38 + SM_CYICONSPACING = 39 + SM_MENUDROPALIGNMENT = 40 + SM_PENWINDOWS = 41 + SM_DBCSENABLED = 42 + SM_CMOUSEBUTTONS = 43 + SM_CXFIXEDFRAME = SM_CXDLGFRAME + SM_CYFIXEDFRAME = SM_CYDLGFRAME + SM_CXSIZEFRAME = SM_CXFRAME + SM_CYSIZEFRAME = SM_CYFRAME + SM_SECURE = 44 + SM_CXEDGE = 45 + SM_CYEDGE = 46 + SM_CXMINSPACING = 47 + SM_CYMINSPACING = 48 + SM_CXSMICON = 49 + SM_CYSMICON = 50 + SM_CYSMCAPTION = 51 + SM_CXSMSIZE = 52 + SM_CYSMSIZE = 53 + SM_CXMENUSIZE = 54 + SM_CYMENUSIZE = 55 + SM_ARRANGE = 56 + SM_CXMINIMIZED = 57 + SM_CYMINIMIZED = 58 + SM_CXMAXTRACK = 59 + SM_CYMAXTRACK = 60 + SM_CXMAXIMIZED = 61 + SM_CYMAXIMIZED = 62 + SM_NETWORK = 63 + SM_CLEANBOOT = 67 + SM_CXDRAG = 68 + SM_CYDRAG = 69 + SM_SHOWSOUNDS = 70 + SM_CXMENUCHECK = 71 + SM_CYMENUCHECK = 72 + SM_SLOWMACHINE = 73 + SM_MIDEASTENABLED = 74 + SM_MOUSEWHEELPRESENT = 75 + SM_XVIRTUALSCREEN = 76 + SM_YVIRTUALSCREEN = 77 + SM_CXVIRTUALSCREEN = 78 + SM_CYVIRTUALSCREEN = 79 + SM_CMONITORS = 80 + SM_SAMEDISPLAYFORMAT = 81 + SM_IMMENABLED = 82 + SM_CXFOCUSBORDER = 83 + SM_CYFOCUSBORDER = 84 + SM_TABLETPC = 86 + SM_MEDIACENTER = 87 + SM_STARTER = 88 + SM_SERVERR2 = 89 + SM_CMETRICS = 91 + SM_REMOTESESSION = 0x1000 + SM_SHUTTINGDOWN = 0x2000 + SM_REMOTECONTROL = 0x2001 + SM_CARETBLINKINGENABLED = 0x2002 +) + +const ( + CLSCTX_INPROC_SERVER = 1 + CLSCTX_INPROC_HANDLER = 2 + CLSCTX_LOCAL_SERVER = 4 + CLSCTX_INPROC_SERVER16 = 8 + CLSCTX_REMOTE_SERVER = 16 + CLSCTX_ALL = CLSCTX_INPROC_SERVER | CLSCTX_INPROC_HANDLER | CLSCTX_LOCAL_SERVER + CLSCTX_INPROC = CLSCTX_INPROC_SERVER | CLSCTX_INPROC_HANDLER + CLSCTX_SERVER = CLSCTX_INPROC_SERVER | CLSCTX_LOCAL_SERVER | CLSCTX_REMOTE_SERVER +) + +const ( + COINIT_APARTMENTTHREADED = 0x2 + COINIT_MULTITHREADED = 0x0 + COINIT_DISABLE_OLE1DDE = 0x4 + COINIT_SPEED_OVER_MEMORY = 0x8 +) + +const ( + DISPATCH_METHOD = 1 + DISPATCH_PROPERTYGET = 2 + DISPATCH_PROPERTYPUT = 4 + DISPATCH_PROPERTYPUTREF = 8 +) + +const ( + CC_FASTCALL = iota + CC_CDECL + CC_MSCPASCAL + CC_PASCAL = CC_MSCPASCAL + CC_MACPASCAL + CC_STDCALL + CC_FPFASTCALL + CC_SYSCALL + CC_MPWCDECL + CC_MPWPASCAL + CC_MAX = CC_MPWPASCAL +) + +const ( + VT_EMPTY = 0x0 + VT_NULL = 0x1 + VT_I2 = 0x2 + VT_I4 = 0x3 + VT_R4 = 0x4 + VT_R8 = 0x5 + VT_CY = 0x6 + VT_DATE = 0x7 + VT_BSTR = 0x8 + VT_DISPATCH = 0x9 + VT_ERROR = 0xa + VT_BOOL = 0xb + VT_VARIANT = 0xc + VT_UNKNOWN = 0xd + VT_DECIMAL = 0xe + VT_I1 = 0x10 + VT_UI1 = 0x11 + VT_UI2 = 0x12 + VT_UI4 = 0x13 + VT_I8 = 0x14 + VT_UI8 = 0x15 + VT_INT = 0x16 + VT_UINT = 0x17 + VT_VOID = 0x18 + VT_HRESULT = 0x19 + VT_PTR = 0x1a + VT_SAFEARRAY = 0x1b + VT_CARRAY = 0x1c + VT_USERDEFINED = 0x1d + VT_LPSTR = 0x1e + VT_LPWSTR = 0x1f + VT_RECORD = 0x24 + VT_INT_PTR = 0x25 + VT_UINT_PTR = 0x26 + VT_FILETIME = 0x40 + VT_BLOB = 0x41 + VT_STREAM = 0x42 + VT_STORAGE = 0x43 + VT_STREAMED_OBJECT = 0x44 + VT_STORED_OBJECT = 0x45 + VT_BLOB_OBJECT = 0x46 + VT_CF = 0x47 + VT_CLSID = 0x48 + VT_BSTR_BLOB = 0xfff + VT_VECTOR = 0x1000 + VT_ARRAY = 0x2000 + VT_BYREF = 0x4000 + VT_RESERVED = 0x8000 + VT_ILLEGAL = 0xffff + VT_ILLEGALMASKED = 0xfff + VT_TYPEMASK = 0xfff +) + +const ( + DISPID_UNKNOWN = -1 + DISPID_VALUE = 0 + DISPID_PROPERTYPUT = -3 + DISPID_NEWENUM = -4 + DISPID_EVALUATE = -5 + DISPID_CONSTRUCTOR = -6 + DISPID_DESTRUCTOR = -7 + DISPID_COLLECT = -8 +) + +const ( + MONITOR_DEFAULTTONULL = 0x00000000 + MONITOR_DEFAULTTOPRIMARY = 0x00000001 + MONITOR_DEFAULTTONEAREST = 0x00000002 + + MONITORINFOF_PRIMARY = 0x00000001 +) + +const ( + CCHDEVICENAME = 32 + CCHFORMNAME = 32 +) + +const ( + IDOK = 1 + IDCANCEL = 2 + IDABORT = 3 + IDRETRY = 4 + IDIGNORE = 5 + IDYES = 6 + IDNO = 7 + IDCLOSE = 8 + IDHELP = 9 + IDTRYAGAIN = 10 + IDCONTINUE = 11 + IDTIMEOUT = 32000 +) + +// Generic WM_NOTIFY notification codes +const ( + NM_FIRST = 0 + NM_OUTOFMEMORY = NM_FIRST - 1 + NM_CLICK = NM_FIRST - 2 + NM_DBLCLK = NM_FIRST - 3 + NM_RETURN = NM_FIRST - 4 + NM_RCLICK = NM_FIRST - 5 + NM_RDBLCLK = NM_FIRST - 6 + NM_SETFOCUS = NM_FIRST - 7 + NM_KILLFOCUS = NM_FIRST - 8 + NM_CUSTOMDRAW = NM_FIRST - 12 + NM_HOVER = NM_FIRST - 13 + NM_NCHITTEST = NM_FIRST - 14 + NM_KEYDOWN = NM_FIRST - 15 + NM_RELEASEDCAPTURE = NM_FIRST - 16 + NM_SETCURSOR = NM_FIRST - 17 + NM_CHAR = NM_FIRST - 18 + NM_TOOLTIPSCREATED = NM_FIRST - 19 + NM_LAST = NM_FIRST - 99 +) + +// ListView messages +const ( + LVM_FIRST = 0x1000 + LVM_GETITEMCOUNT = LVM_FIRST + 4 + LVM_SETIMAGELIST = LVM_FIRST + 3 + LVM_GETIMAGELIST = LVM_FIRST + 2 + LVM_GETITEM = LVM_FIRST + 75 + LVM_SETITEM = LVM_FIRST + 76 + LVM_INSERTITEM = LVM_FIRST + 77 + LVM_DELETEITEM = LVM_FIRST + 8 + LVM_DELETEALLITEMS = LVM_FIRST + 9 + LVM_GETCALLBACKMASK = LVM_FIRST + 10 + LVM_SETCALLBACKMASK = LVM_FIRST + 11 + LVM_SETUNICODEFORMAT = CCM_SETUNICODEFORMAT + LVM_GETNEXTITEM = LVM_FIRST + 12 + LVM_FINDITEM = LVM_FIRST + 83 + LVM_GETITEMRECT = LVM_FIRST + 14 + LVM_GETSTRINGWIDTH = LVM_FIRST + 87 + LVM_HITTEST = LVM_FIRST + 18 + LVM_ENSUREVISIBLE = LVM_FIRST + 19 + LVM_SCROLL = LVM_FIRST + 20 + LVM_REDRAWITEMS = LVM_FIRST + 21 + LVM_ARRANGE = LVM_FIRST + 22 + LVM_EDITLABEL = LVM_FIRST + 118 + LVM_GETEDITCONTROL = LVM_FIRST + 24 + LVM_GETCOLUMN = LVM_FIRST + 95 + LVM_SETCOLUMN = LVM_FIRST + 96 + LVM_INSERTCOLUMN = LVM_FIRST + 97 + LVM_DELETECOLUMN = LVM_FIRST + 28 + LVM_GETCOLUMNWIDTH = LVM_FIRST + 29 + LVM_SETCOLUMNWIDTH = LVM_FIRST + 30 + LVM_GETHEADER = LVM_FIRST + 31 + LVM_CREATEDRAGIMAGE = LVM_FIRST + 33 + LVM_GETVIEWRECT = LVM_FIRST + 34 + LVM_GETTEXTCOLOR = LVM_FIRST + 35 + LVM_SETTEXTCOLOR = LVM_FIRST + 36 + LVM_GETTEXTBKCOLOR = LVM_FIRST + 37 + LVM_SETTEXTBKCOLOR = LVM_FIRST + 38 + LVM_GETTOPINDEX = LVM_FIRST + 39 + LVM_GETCOUNTPERPAGE = LVM_FIRST + 40 + LVM_GETORIGIN = LVM_FIRST + 41 + LVM_UPDATE = LVM_FIRST + 42 + LVM_SETITEMSTATE = LVM_FIRST + 43 + LVM_GETITEMSTATE = LVM_FIRST + 44 + LVM_GETITEMTEXT = LVM_FIRST + 115 + LVM_SETITEMTEXT = LVM_FIRST + 116 + LVM_SETITEMCOUNT = LVM_FIRST + 47 + LVM_SORTITEMS = LVM_FIRST + 48 + LVM_SETITEMPOSITION32 = LVM_FIRST + 49 + LVM_GETSELECTEDCOUNT = LVM_FIRST + 50 + LVM_GETITEMSPACING = LVM_FIRST + 51 + LVM_GETISEARCHSTRING = LVM_FIRST + 117 + LVM_SETICONSPACING = LVM_FIRST + 53 + LVM_SETEXTENDEDLISTVIEWSTYLE = LVM_FIRST + 54 + LVM_GETEXTENDEDLISTVIEWSTYLE = LVM_FIRST + 55 + LVM_GETSUBITEMRECT = LVM_FIRST + 56 + LVM_SUBITEMHITTEST = LVM_FIRST + 57 + LVM_SETCOLUMNORDERARRAY = LVM_FIRST + 58 + LVM_GETCOLUMNORDERARRAY = LVM_FIRST + 59 + LVM_SETHOTITEM = LVM_FIRST + 60 + LVM_GETHOTITEM = LVM_FIRST + 61 + LVM_SETHOTCURSOR = LVM_FIRST + 62 + LVM_GETHOTCURSOR = LVM_FIRST + 63 + LVM_APPROXIMATEVIEWRECT = LVM_FIRST + 64 + LVM_SETWORKAREAS = LVM_FIRST + 65 + LVM_GETWORKAREAS = LVM_FIRST + 70 + LVM_GETNUMBEROFWORKAREAS = LVM_FIRST + 73 + LVM_GETSELECTIONMARK = LVM_FIRST + 66 + LVM_SETSELECTIONMARK = LVM_FIRST + 67 + LVM_SETHOVERTIME = LVM_FIRST + 71 + LVM_GETHOVERTIME = LVM_FIRST + 72 + LVM_SETTOOLTIPS = LVM_FIRST + 74 + LVM_GETTOOLTIPS = LVM_FIRST + 78 + LVM_SORTITEMSEX = LVM_FIRST + 81 + LVM_SETBKIMAGE = LVM_FIRST + 138 + LVM_GETBKIMAGE = LVM_FIRST + 139 + LVM_SETSELECTEDCOLUMN = LVM_FIRST + 140 + LVM_SETVIEW = LVM_FIRST + 142 + LVM_GETVIEW = LVM_FIRST + 143 + LVM_INSERTGROUP = LVM_FIRST + 145 + LVM_SETGROUPINFO = LVM_FIRST + 147 + LVM_GETGROUPINFO = LVM_FIRST + 149 + LVM_REMOVEGROUP = LVM_FIRST + 150 + LVM_MOVEGROUP = LVM_FIRST + 151 + LVM_GETGROUPCOUNT = LVM_FIRST + 152 + LVM_GETGROUPINFOBYINDEX = LVM_FIRST + 153 + LVM_MOVEITEMTOGROUP = LVM_FIRST + 154 + LVM_GETGROUPRECT = LVM_FIRST + 98 + LVM_SETGROUPMETRICS = LVM_FIRST + 155 + LVM_GETGROUPMETRICS = LVM_FIRST + 156 + LVM_ENABLEGROUPVIEW = LVM_FIRST + 157 + LVM_SORTGROUPS = LVM_FIRST + 158 + LVM_INSERTGROUPSORTED = LVM_FIRST + 159 + LVM_REMOVEALLGROUPS = LVM_FIRST + 160 + LVM_HASGROUP = LVM_FIRST + 161 + LVM_GETGROUPSTATE = LVM_FIRST + 92 + LVM_GETFOCUSEDGROUP = LVM_FIRST + 93 + LVM_SETTILEVIEWINFO = LVM_FIRST + 162 + LVM_GETTILEVIEWINFO = LVM_FIRST + 163 + LVM_SETTILEINFO = LVM_FIRST + 164 + LVM_GETTILEINFO = LVM_FIRST + 165 + LVM_SETINSERTMARK = LVM_FIRST + 166 + LVM_GETINSERTMARK = LVM_FIRST + 167 + LVM_INSERTMARKHITTEST = LVM_FIRST + 168 + LVM_GETINSERTMARKRECT = LVM_FIRST + 169 + LVM_SETINSERTMARKCOLOR = LVM_FIRST + 170 + LVM_GETINSERTMARKCOLOR = LVM_FIRST + 171 + LVM_SETINFOTIP = LVM_FIRST + 173 + LVM_GETSELECTEDCOLUMN = LVM_FIRST + 174 + LVM_ISGROUPVIEWENABLED = LVM_FIRST + 175 + LVM_GETOUTLINECOLOR = LVM_FIRST + 176 + LVM_SETOUTLINECOLOR = LVM_FIRST + 177 + LVM_CANCELEDITLABEL = LVM_FIRST + 179 + LVM_MAPINDEXTOID = LVM_FIRST + 180 + LVM_MAPIDTOINDEX = LVM_FIRST + 181 + LVM_ISITEMVISIBLE = LVM_FIRST + 182 + LVM_GETNEXTITEMINDEX = LVM_FIRST + 211 +) + +// ListView notifications +const ( + LVN_FIRST = -100 + + LVN_ITEMCHANGING = LVN_FIRST - 0 + LVN_ITEMCHANGED = LVN_FIRST - 1 + LVN_INSERTITEM = LVN_FIRST - 2 + LVN_DELETEITEM = LVN_FIRST - 3 + LVN_DELETEALLITEMS = LVN_FIRST - 4 + LVN_BEGINLABELEDITA = LVN_FIRST - 5 + LVN_BEGINLABELEDITW = LVN_FIRST - 75 + LVN_ENDLABELEDITA = LVN_FIRST - 6 + LVN_ENDLABELEDITW = LVN_FIRST - 76 + LVN_COLUMNCLICK = LVN_FIRST - 8 + LVN_BEGINDRAG = LVN_FIRST - 9 + LVN_BEGINRDRAG = LVN_FIRST - 11 + LVN_ODCACHEHINT = LVN_FIRST - 13 + LVN_ODFINDITEMA = LVN_FIRST - 52 + LVN_ODFINDITEMW = LVN_FIRST - 79 + LVN_ITEMACTIVATE = LVN_FIRST - 14 + LVN_ODSTATECHANGED = LVN_FIRST - 15 + LVN_HOTTRACK = LVN_FIRST - 21 + LVN_GETDISPINFO = LVN_FIRST - 77 + LVN_SETDISPINFO = LVN_FIRST - 78 + LVN_KEYDOWN = LVN_FIRST - 55 + LVN_MARQUEEBEGIN = LVN_FIRST - 56 + LVN_GETINFOTIP = LVN_FIRST - 58 + LVN_INCREMENTALSEARCH = LVN_FIRST - 63 + LVN_BEGINSCROLL = LVN_FIRST - 80 + LVN_ENDSCROLL = LVN_FIRST - 81 +) + +const ( + LVSCW_AUTOSIZE = ^uintptr(0) + LVSCW_AUTOSIZE_USEHEADER = ^uintptr(1) +) + +// ListView LVNI constants +const ( + LVNI_ALL = 0 + LVNI_FOCUSED = 1 + LVNI_SELECTED = 2 + LVNI_CUT = 4 + LVNI_DROPHILITED = 8 + LVNI_ABOVE = 256 + LVNI_BELOW = 512 + LVNI_TOLEFT = 1024 + LVNI_TORIGHT = 2048 +) + +// ListView styles +const ( + LVS_ICON = 0x0000 + LVS_REPORT = 0x0001 + LVS_SMALLICON = 0x0002 + LVS_LIST = 0x0003 + LVS_TYPEMASK = 0x0003 + LVS_SINGLESEL = 0x0004 + LVS_SHOWSELALWAYS = 0x0008 + LVS_SORTASCENDING = 0x0010 + LVS_SORTDESCENDING = 0x0020 + LVS_SHAREIMAGELISTS = 0x0040 + LVS_NOLABELWRAP = 0x0080 + LVS_AUTOARRANGE = 0x0100 + LVS_EDITLABELS = 0x0200 + LVS_OWNERDATA = 0x1000 + LVS_NOSCROLL = 0x2000 + LVS_TYPESTYLEMASK = 0xfc00 + LVS_ALIGNTOP = 0x0000 + LVS_ALIGNLEFT = 0x0800 + LVS_ALIGNMASK = 0x0c00 + LVS_OWNERDRAWFIXED = 0x0400 + LVS_NOCOLUMNHEADER = 0x4000 + LVS_NOSORTHEADER = 0x8000 +) + +// ListView extended styles +const ( + LVS_EX_GRIDLINES = 0x00000001 + LVS_EX_SUBITEMIMAGES = 0x00000002 + LVS_EX_CHECKBOXES = 0x00000004 + LVS_EX_TRACKSELECT = 0x00000008 + LVS_EX_HEADERDRAGDROP = 0x00000010 + LVS_EX_FULLROWSELECT = 0x00000020 + LVS_EX_ONECLICKACTIVATE = 0x00000040 + LVS_EX_TWOCLICKACTIVATE = 0x00000080 + LVS_EX_FLATSB = 0x00000100 + LVS_EX_REGIONAL = 0x00000200 + LVS_EX_INFOTIP = 0x00000400 + LVS_EX_UNDERLINEHOT = 0x00000800 + LVS_EX_UNDERLINECOLD = 0x00001000 + LVS_EX_MULTIWORKAREAS = 0x00002000 + LVS_EX_LABELTIP = 0x00004000 + LVS_EX_BORDERSELECT = 0x00008000 + LVS_EX_DOUBLEBUFFER = 0x00010000 + LVS_EX_HIDELABELS = 0x00020000 + LVS_EX_SINGLEROW = 0x00040000 + LVS_EX_SNAPTOGRID = 0x00080000 + LVS_EX_SIMPLESELECT = 0x00100000 +) + +// ListView column flags +const ( + LVCF_FMT = 0x0001 + LVCF_WIDTH = 0x0002 + LVCF_TEXT = 0x0004 + LVCF_SUBITEM = 0x0008 + LVCF_IMAGE = 0x0010 + LVCF_ORDER = 0x0020 +) + +// ListView column format constants +const ( + LVCFMT_LEFT = 0x0000 + LVCFMT_RIGHT = 0x0001 + LVCFMT_CENTER = 0x0002 + LVCFMT_JUSTIFYMASK = 0x0003 + LVCFMT_IMAGE = 0x0800 + LVCFMT_BITMAP_ON_RIGHT = 0x1000 + LVCFMT_COL_HAS_IMAGES = 0x8000 +) + +// ListView item flags +const ( + LVIF_TEXT = 0x00000001 + LVIF_IMAGE = 0x00000002 + LVIF_PARAM = 0x00000004 + LVIF_STATE = 0x00000008 + LVIF_INDENT = 0x00000010 + LVIF_NORECOMPUTE = 0x00000800 + LVIF_GROUPID = 0x00000100 + LVIF_COLUMNS = 0x00000200 +) + +const LVFI_PARAM = 0x0001 + +// ListView item states +const ( + LVIS_FOCUSED = 1 + LVIS_SELECTED = 2 + LVIS_CUT = 4 + LVIS_DROPHILITED = 8 + LVIS_OVERLAYMASK = 0xF00 + LVIS_STATEIMAGEMASK = 0xF000 +) + +// ListView hit test constants +const ( + LVHT_NOWHERE = 0x00000001 + LVHT_ONITEMICON = 0x00000002 + LVHT_ONITEMLABEL = 0x00000004 + LVHT_ONITEMSTATEICON = 0x00000008 + LVHT_ONITEM = LVHT_ONITEMICON | LVHT_ONITEMLABEL | LVHT_ONITEMSTATEICON + + LVHT_ABOVE = 0x00000008 + LVHT_BELOW = 0x00000010 + LVHT_TORIGHT = 0x00000020 + LVHT_TOLEFT = 0x00000040 +) + +// ListView image list types +const ( + LVSIL_NORMAL = 0 + LVSIL_SMALL = 1 + LVSIL_STATE = 2 + LVSIL_GROUPHEADER = 3 +) + +// InitCommonControlsEx flags +const ( + ICC_LISTVIEW_CLASSES = 1 + ICC_TREEVIEW_CLASSES = 2 + ICC_BAR_CLASSES = 4 + ICC_TAB_CLASSES = 8 + ICC_UPDOWN_CLASS = 16 + ICC_PROGRESS_CLASS = 32 + ICC_HOTKEY_CLASS = 64 + ICC_ANIMATE_CLASS = 128 + ICC_WIN95_CLASSES = 255 + ICC_DATE_CLASSES = 256 + ICC_USEREX_CLASSES = 512 + ICC_COOL_CLASSES = 1024 + ICC_INTERNET_CLASSES = 2048 + ICC_PAGESCROLLER_CLASS = 4096 + ICC_NATIVEFNTCTL_CLASS = 8192 + INFOTIPSIZE = 1024 + ICC_STANDARD_CLASSES = 0x00004000 + ICC_LINK_CLASS = 0x00008000 +) + +// Dialog Codes +const ( + DLGC_WANTARROWS = 0x0001 + DLGC_WANTTAB = 0x0002 + DLGC_WANTALLKEYS = 0x0004 + DLGC_WANTMESSAGE = 0x0004 + DLGC_HASSETSEL = 0x0008 + DLGC_DEFPUSHBUTTON = 0x0010 + DLGC_UNDEFPUSHBUTTON = 0x0020 + DLGC_RADIOBUTTON = 0x0040 + DLGC_WANTCHARS = 0x0080 + DLGC_STATIC = 0x0100 + DLGC_BUTTON = 0x2000 +) + +// Get/SetWindowWord/Long offsets for use with WC_DIALOG windows +const ( + DWL_MSGRESULT = 0 + DWL_DLGPROC = 4 + DWL_USER = 8 +) + +// Registry predefined keys +const ( + HKEY_CLASSES_ROOT HKEY = 0x80000000 + HKEY_CURRENT_USER HKEY = 0x80000001 + HKEY_LOCAL_MACHINE HKEY = 0x80000002 + HKEY_USERS HKEY = 0x80000003 + HKEY_PERFORMANCE_DATA HKEY = 0x80000004 + HKEY_CURRENT_CONFIG HKEY = 0x80000005 + HKEY_DYN_DATA HKEY = 0x80000006 +) + +// Registry Key Security and Access Rights +const ( + KEY_ALL_ACCESS = 0xF003F + KEY_CREATE_SUB_KEY = 0x0004 + KEY_ENUMERATE_SUB_KEYS = 0x0008 + KEY_NOTIFY = 0x0010 + KEY_QUERY_VALUE = 0x0001 + KEY_SET_VALUE = 0x0002 + KEY_READ = 0x20019 + KEY_WRITE = 0x20006 +) + +const ( + NFR_ANSI = 1 + NFR_UNICODE = 2 + NF_QUERY = 3 + NF_REQUERY = 4 +) + +// Registry value types +const ( + RRF_RT_REG_NONE = 0x00000001 + RRF_RT_REG_SZ = 0x00000002 + RRF_RT_REG_EXPAND_SZ = 0x00000004 + RRF_RT_REG_BINARY = 0x00000008 + RRF_RT_REG_DWORD = 0x00000010 + RRF_RT_REG_MULTI_SZ = 0x00000020 + RRF_RT_REG_QWORD = 0x00000040 + RRF_RT_DWORD = RRF_RT_REG_BINARY | RRF_RT_REG_DWORD + RRF_RT_QWORD = RRF_RT_REG_BINARY | RRF_RT_REG_QWORD + RRF_RT_ANY = 0x0000ffff + RRF_NOEXPAND = 0x10000000 + RRF_ZEROONFAILURE = 0x20000000 + REG_PROCESS_APPKEY = 0x00000001 + REG_MUI_STRING_TRUNCATE = 0x00000001 +) + +// PeekMessage wRemoveMsg value +const ( + PM_NOREMOVE = 0x000 + PM_REMOVE = 0x001 + PM_NOYIELD = 0x002 +) + +// ImageList flags +const ( + ILC_MASK = 0x00000001 + ILC_COLOR = 0x00000000 + ILC_COLORDDB = 0x000000FE + ILC_COLOR4 = 0x00000004 + ILC_COLOR8 = 0x00000008 + ILC_COLOR16 = 0x00000010 + ILC_COLOR24 = 0x00000018 + ILC_COLOR32 = 0x00000020 + ILC_PALETTE = 0x00000800 + ILC_MIRROR = 0x00002000 + ILC_PERITEMMIRROR = 0x00008000 + ILC_ORIGINALSIZE = 0x00010000 + ILC_HIGHQUALITYSCALE = 0x00020000 +) + +// Keystroke Message Flags +const ( + KF_EXTENDED = 0x0100 + KF_DLGMODE = 0x0800 + KF_MENUMODE = 0x1000 + KF_ALTDOWN = 0x2000 + KF_REPEAT = 0x4000 + KF_UP = 0x8000 +) + +// Virtual-Key Codes +/* +const ( + VK_LBUTTON = 0x01 + VK_RBUTTON = 0x02 + VK_CANCEL = 0x03 + VK_MBUTTON = 0x04 + VK_XBUTTON1 = 0x05 + VK_XBUTTON2 = 0x06 + VK_BACK = 0x08 + VK_TAB = 0x09 + VK_CLEAR = 0x0C + VK_RETURN = 0x0D + VK_SHIFT = 0x10 + VK_CONTROL = 0x11 + VK_MENU = 0x12 + VK_PAUSE = 0x13 + VK_CAPITAL = 0x14 + VK_KANA = 0x15 + VK_HANGEUL = 0x15 + VK_HANGUL = 0x15 + VK_JUNJA = 0x17 + VK_FINAL = 0x18 + VK_HANJA = 0x19 + VK_KANJI = 0x19 + VK_ESCAPE = 0x1B + VK_CONVERT = 0x1C + VK_NONCONVERT = 0x1D + VK_ACCEPT = 0x1E + VK_MODECHANGE = 0x1F + VK_SPACE = 0x20 + VK_PRIOR = 0x21 + VK_NEXT = 0x22 + VK_END = 0x23 + VK_HOME = 0x24 + VK_LEFT = 0x25 + VK_UP = 0x26 + VK_RIGHT = 0x27 + VK_DOWN = 0x28 + VK_SELECT = 0x29 + VK_PRINT = 0x2A + VK_EXECUTE = 0x2B + VK_SNAPSHOT = 0x2C + VK_INSERT = 0x2D + VK_DELETE = 0x2E + VK_HELP = 0x2F + VK_LWIN = 0x5B + VK_RWIN = 0x5C + VK_APPS = 0x5D + VK_SLEEP = 0x5F + VK_NUMPAD0 = 0x60 + VK_NUMPAD1 = 0x61 + VK_NUMPAD2 = 0x62 + VK_NUMPAD3 = 0x63 + VK_NUMPAD4 = 0x64 + VK_NUMPAD5 = 0x65 + VK_NUMPAD6 = 0x66 + VK_NUMPAD7 = 0x67 + VK_NUMPAD8 = 0x68 + VK_NUMPAD9 = 0x69 + VK_MULTIPLY = 0x6A + VK_ADD = 0x6B + VK_SEPARATOR = 0x6C + VK_SUBTRACT = 0x6D + VK_DECIMAL = 0x6E + VK_DIVIDE = 0x6F + VK_F1 = 0x70 + VK_F2 = 0x71 + VK_F3 = 0x72 + VK_F4 = 0x73 + VK_F5 = 0x74 + VK_F6 = 0x75 + VK_F7 = 0x76 + VK_F8 = 0x77 + VK_F9 = 0x78 + VK_F10 = 0x79 + VK_F11 = 0x7A + VK_F12 = 0x7B + VK_F13 = 0x7C + VK_F14 = 0x7D + VK_F15 = 0x7E + VK_F16 = 0x7F + VK_F17 = 0x80 + VK_F18 = 0x81 + VK_F19 = 0x82 + VK_F20 = 0x83 + VK_F21 = 0x84 + VK_F22 = 0x85 + VK_F23 = 0x86 + VK_F24 = 0x87 + VK_NUMLOCK = 0x90 + VK_SCROLL = 0x91 + VK_OEM_NEC_EQUAL = 0x92 + VK_OEM_FJ_JISHO = 0x92 + VK_OEM_FJ_MASSHOU = 0x93 + VK_OEM_FJ_TOUROKU = 0x94 + VK_OEM_FJ_LOYA = 0x95 + VK_OEM_FJ_ROYA = 0x96 + VK_LSHIFT = 0xA0 + VK_RSHIFT = 0xA1 + VK_LCONTROL = 0xA2 + VK_RCONTROL = 0xA3 + VK_LMENU = 0xA4 + VK_RMENU = 0xA5 + VK_BROWSER_BACK = 0xA6 + VK_BROWSER_FORWARD = 0xA7 + VK_BROWSER_REFRESH = 0xA8 + VK_BROWSER_STOP = 0xA9 + VK_BROWSER_SEARCH = 0xAA + VK_BROWSER_FAVORITES = 0xAB + VK_BROWSER_HOME = 0xAC + VK_VOLUME_MUTE = 0xAD + VK_VOLUME_DOWN = 0xAE + VK_VOLUME_UP = 0xAF + VK_MEDIA_NEXT_TRACK = 0xB0 + VK_MEDIA_PREV_TRACK = 0xB1 + VK_MEDIA_STOP = 0xB2 + VK_MEDIA_PLAY_PAUSE = 0xB3 + VK_LAUNCH_MAIL = 0xB4 + VK_LAUNCH_MEDIA_SELECT = 0xB5 + VK_LAUNCH_APP1 = 0xB6 + VK_LAUNCH_APP2 = 0xB7 + VK_OEM_1 = 0xBA + VK_OEM_PLUS = 0xBB + VK_OEM_COMMA = 0xBC + VK_OEM_MINUS = 0xBD + VK_OEM_PERIOD = 0xBE + VK_OEM_2 = 0xBF + VK_OEM_3 = 0xC0 + VK_OEM_4 = 0xDB + VK_OEM_5 = 0xDC + VK_OEM_6 = 0xDD + VK_OEM_7 = 0xDE + VK_OEM_8 = 0xDF + VK_OEM_AX = 0xE1 + VK_OEM_102 = 0xE2 + VK_ICO_HELP = 0xE3 + VK_ICO_00 = 0xE4 + VK_PROCESSKEY = 0xE5 + VK_ICO_CLEAR = 0xE6 + VK_OEM_RESET = 0xE9 + VK_OEM_JUMP = 0xEA + VK_OEM_PA1 = 0xEB + VK_OEM_PA2 = 0xEC + VK_OEM_PA3 = 0xED + VK_OEM_WSCTRL = 0xEE + VK_OEM_CUSEL = 0xEF + VK_OEM_ATTN = 0xF0 + VK_OEM_FINISH = 0xF1 + VK_OEM_COPY = 0xF2 + VK_OEM_AUTO = 0xF3 + VK_OEM_ENLW = 0xF4 + VK_OEM_BACKTAB = 0xF5 + VK_ATTN = 0xF6 + VK_CRSEL = 0xF7 + VK_EXSEL = 0xF8 + VK_EREOF = 0xF9 + VK_PLAY = 0xFA + VK_ZOOM = 0xFB + VK_NONAME = 0xFC + VK_PA1 = 0xFD + VK_OEM_CLEAR = 0xFE +)*/ + +// Registry Value Types +const ( + REG_NONE = 0 + REG_SZ = 1 + REG_EXPAND_SZ = 2 + REG_BINARY = 3 + REG_DWORD = 4 + REG_DWORD_LITTLE_ENDIAN = 4 + REG_DWORD_BIG_ENDIAN = 5 + REG_LINK = 6 + REG_MULTI_SZ = 7 + REG_RESOURCE_LIST = 8 + REG_FULL_RESOURCE_DESCRIPTOR = 9 + REG_RESOURCE_REQUIREMENTS_LIST = 10 + REG_QWORD = 11 + REG_QWORD_LITTLE_ENDIAN = 11 +) + +// Tooltip styles +const ( + TTS_ALWAYSTIP = 0x01 + TTS_NOPREFIX = 0x02 + TTS_NOANIMATE = 0x10 + TTS_NOFADE = 0x20 + TTS_BALLOON = 0x40 + TTS_CLOSE = 0x80 + TTS_USEVISUALSTYLE = 0x100 +) + +// Tooltip messages +const ( + TTM_ACTIVATE = WM_USER + 1 + TTM_SETDELAYTIME = WM_USER + 3 + TTM_ADDTOOL = WM_USER + 50 + TTM_DELTOOL = WM_USER + 51 + TTM_NEWTOOLRECT = WM_USER + 52 + TTM_RELAYEVENT = WM_USER + 7 + TTM_GETTOOLINFO = WM_USER + 53 + TTM_SETTOOLINFO = WM_USER + 54 + TTM_HITTEST = WM_USER + 55 + TTM_GETTEXT = WM_USER + 56 + TTM_UPDATETIPTEXT = WM_USER + 57 + TTM_GETTOOLCOUNT = WM_USER + 13 + TTM_ENUMTOOLS = WM_USER + 58 + TTM_GETCURRENTTOOL = WM_USER + 59 + TTM_WINDOWFROMPOINT = WM_USER + 16 + TTM_TRACKACTIVATE = WM_USER + 17 + TTM_TRACKPOSITION = WM_USER + 18 + TTM_SETTIPBKCOLOR = WM_USER + 19 + TTM_SETTIPTEXTCOLOR = WM_USER + 20 + TTM_GETDELAYTIME = WM_USER + 21 + TTM_GETTIPBKCOLOR = WM_USER + 22 + TTM_GETTIPTEXTCOLOR = WM_USER + 23 + TTM_SETMAXTIPWIDTH = WM_USER + 24 + TTM_GETMAXTIPWIDTH = WM_USER + 25 + TTM_SETMARGIN = WM_USER + 26 + TTM_GETMARGIN = WM_USER + 27 + TTM_POP = WM_USER + 28 + TTM_UPDATE = WM_USER + 29 + TTM_GETBUBBLESIZE = WM_USER + 30 + TTM_ADJUSTRECT = WM_USER + 31 + TTM_SETTITLE = WM_USER + 33 + TTM_POPUP = WM_USER + 34 + TTM_GETTITLE = WM_USER + 35 +) + +// Tooltip icons +const ( + TTI_NONE = 0 + TTI_INFO = 1 + TTI_WARNING = 2 + TTI_ERROR = 3 + TTI_INFO_LARGE = 4 + TTI_WARNING_LARGE = 5 + TTI_ERROR_LARGE = 6 +) + +// Tooltip notifications +const ( + TTN_FIRST = -520 + TTN_LAST = -549 + TTN_GETDISPINFO = TTN_FIRST - 10 + TTN_SHOW = TTN_FIRST - 1 + TTN_POP = TTN_FIRST - 2 + TTN_LINKCLICK = TTN_FIRST - 3 + TTN_NEEDTEXT = TTN_GETDISPINFO +) + +const ( + TTF_IDISHWND = 0x0001 + TTF_CENTERTIP = 0x0002 + TTF_RTLREADING = 0x0004 + TTF_SUBCLASS = 0x0010 + TTF_TRACK = 0x0020 + TTF_ABSOLUTE = 0x0080 + TTF_TRANSPARENT = 0x0100 + TTF_PARSELINKS = 0x1000 + TTF_DI_SETITEM = 0x8000 +) + +const ( + SWP_NOSIZE = 0x0001 + SWP_NOMOVE = 0x0002 + SWP_NOZORDER = 0x0004 + SWP_NOREDRAW = 0x0008 + SWP_NOACTIVATE = 0x0010 + SWP_FRAMECHANGED = 0x0020 + SWP_SHOWWINDOW = 0x0040 + SWP_HIDEWINDOW = 0x0080 + SWP_NOCOPYBITS = 0x0100 + SWP_NOOWNERZORDER = 0x0200 + SWP_NOSENDCHANGING = 0x0400 + SWP_DRAWFRAME = SWP_FRAMECHANGED + SWP_NOREPOSITION = SWP_NOOWNERZORDER + SWP_DEFERERASE = 0x2000 + SWP_ASYNCWINDOWPOS = 0x4000 +) + +// Predefined window handles +const ( + HWND_BROADCAST = HWND(0xFFFF) + HWND_BOTTOM = HWND(1) + HWND_NOTOPMOST = ^HWND(1) // -2 + HWND_TOP = HWND(0) + HWND_TOPMOST = ^HWND(0) // -1 + HWND_DESKTOP = HWND(0) + HWND_MESSAGE = ^HWND(2) // -3 +) + +// Pen types +const ( + PS_COSMETIC = 0x00000000 + PS_GEOMETRIC = 0x00010000 + PS_TYPE_MASK = 0x000F0000 +) + +// Pen styles +const ( + PS_SOLID = 0 + PS_DASH = 1 + PS_DOT = 2 + PS_DASHDOT = 3 + PS_DASHDOTDOT = 4 + PS_NULL = 5 + PS_INSIDEFRAME = 6 + PS_USERSTYLE = 7 + PS_ALTERNATE = 8 + PS_STYLE_MASK = 0x0000000F +) + +// Pen cap types +const ( + PS_ENDCAP_ROUND = 0x00000000 + PS_ENDCAP_SQUARE = 0x00000100 + PS_ENDCAP_FLAT = 0x00000200 + PS_ENDCAP_MASK = 0x00000F00 +) + +// Pen join types +const ( + PS_JOIN_ROUND = 0x00000000 + PS_JOIN_BEVEL = 0x00001000 + PS_JOIN_MITER = 0x00002000 + PS_JOIN_MASK = 0x0000F000 +) + +// Hatch styles +const ( + HS_HORIZONTAL = 0 + HS_VERTICAL = 1 + HS_FDIAGONAL = 2 + HS_BDIAGONAL = 3 + HS_CROSS = 4 + HS_DIAGCROSS = 5 +) + +// Stock Logical Objects +const ( + WHITE_BRUSH = 0 + LTGRAY_BRUSH = 1 + GRAY_BRUSH = 2 + DKGRAY_BRUSH = 3 + BLACK_BRUSH = 4 + NULL_BRUSH = 5 + HOLLOW_BRUSH = NULL_BRUSH + WHITE_PEN = 6 + BLACK_PEN = 7 + NULL_PEN = 8 + OEM_FIXED_FONT = 10 + ANSI_FIXED_FONT = 11 + ANSI_VAR_FONT = 12 + SYSTEM_FONT = 13 + DEVICE_DEFAULT_FONT = 14 + DEFAULT_PALETTE = 15 + SYSTEM_FIXED_FONT = 16 + DEFAULT_GUI_FONT = 17 + DC_BRUSH = 18 + DC_PEN = 19 +) + +// Brush styles +const ( + BS_SOLID = 0 + BS_NULL = 1 + BS_HOLLOW = BS_NULL + BS_HATCHED = 2 + BS_PATTERN = 3 + BS_INDEXED = 4 + BS_DIBPATTERN = 5 + BS_DIBPATTERNPT = 6 + BS_PATTERN8X8 = 7 + BS_DIBPATTERN8X8 = 8 + BS_MONOPATTERN = 9 +) + +// TRACKMOUSEEVENT flags +const ( + TME_HOVER = 0x00000001 + TME_LEAVE = 0x00000002 + TME_NONCLIENT = 0x00000010 + TME_QUERY = 0x40000000 + TME_CANCEL = 0x80000000 + + HOVER_DEFAULT = 0xFFFFFFFF +) + +// WM_NCHITTEST and MOUSEHOOKSTRUCT Mouse Position Codes +const ( + HTERROR = -2 + HTTRANSPARENT = -1 + HTNOWHERE = 0 + HTCLIENT = 1 + HTCAPTION = 2 + HTSYSMENU = 3 + HTGROWBOX = 4 + HTSIZE = HTGROWBOX + HTMENU = 5 + HTHSCROLL = 6 + HTVSCROLL = 7 + HTMINBUTTON = 8 + HTMAXBUTTON = 9 + HTLEFT = 10 + HTRIGHT = 11 + HTTOP = 12 + HTTOPLEFT = 13 + HTTOPRIGHT = 14 + HTBOTTOM = 15 + HTBOTTOMLEFT = 16 + HTBOTTOMRIGHT = 17 + HTBORDER = 18 + HTREDUCE = HTMINBUTTON + HTZOOM = HTMAXBUTTON + HTSIZEFIRST = HTLEFT + HTSIZELAST = HTBOTTOMRIGHT + HTOBJECT = 19 + HTCLOSE = 20 + HTHELP = 21 +) + +// DrawText[Ex] format flags +const ( + DT_TOP = 0x00000000 + DT_LEFT = 0x00000000 + DT_CENTER = 0x00000001 + DT_RIGHT = 0x00000002 + DT_VCENTER = 0x00000004 + DT_BOTTOM = 0x00000008 + DT_WORDBREAK = 0x00000010 + DT_SINGLELINE = 0x00000020 + DT_EXPANDTABS = 0x00000040 + DT_TABSTOP = 0x00000080 + DT_NOCLIP = 0x00000100 + DT_EXTERNALLEADING = 0x00000200 + DT_CALCRECT = 0x00000400 + DT_NOPREFIX = 0x00000800 + DT_INTERNAL = 0x00001000 + DT_EDITCONTROL = 0x00002000 + DT_PATH_ELLIPSIS = 0x00004000 + DT_END_ELLIPSIS = 0x00008000 + DT_MODIFYSTRING = 0x00010000 + DT_RTLREADING = 0x00020000 + DT_WORD_ELLIPSIS = 0x00040000 + DT_NOFULLWIDTHCHARBREAK = 0x00080000 + DT_HIDEPREFIX = 0x00100000 + DT_PREFIXONLY = 0x00200000 +) + +const CLR_INVALID = 0xFFFFFFFF + +// Background Modes +const ( + TRANSPARENT = 1 + OPAQUE = 2 + BKMODE_LAST = 2 +) + +// Global Memory Flags +const ( + GMEM_FIXED = 0x0000 + GMEM_MOVEABLE = 0x0002 + GMEM_NOCOMPACT = 0x0010 + GMEM_NODISCARD = 0x0020 + GMEM_ZEROINIT = 0x0040 + GMEM_MODIFY = 0x0080 + GMEM_DISCARDABLE = 0x0100 + GMEM_NOT_BANKED = 0x1000 + GMEM_SHARE = 0x2000 + GMEM_DDESHARE = 0x2000 + GMEM_NOTIFY = 0x4000 + GMEM_LOWER = GMEM_NOT_BANKED + GMEM_VALID_FLAGS = 0x7F72 + GMEM_INVALID_HANDLE = 0x8000 + GHND = GMEM_MOVEABLE | GMEM_ZEROINIT + GPTR = GMEM_FIXED | GMEM_ZEROINIT +) + +// Ternary raster operations +const ( + SRCCOPY = 0x00CC0020 + SRCPAINT = 0x00EE0086 + SRCAND = 0x008800C6 + SRCINVERT = 0x00660046 + SRCERASE = 0x00440328 + NOTSRCCOPY = 0x00330008 + NOTSRCERASE = 0x001100A6 + MERGECOPY = 0x00C000CA + MERGEPAINT = 0x00BB0226 + PATCOPY = 0x00F00021 + PATPAINT = 0x00FB0A09 + PATINVERT = 0x005A0049 + DSTINVERT = 0x00550009 + BLACKNESS = 0x00000042 + WHITENESS = 0x00FF0062 + NOMIRRORBITMAP = 0x80000000 + CAPTUREBLT = 0x40000000 +) + +// Clipboard formats +const ( + CF_TEXT = 1 + CF_BITMAP = 2 + CF_METAFILEPICT = 3 + CF_SYLK = 4 + CF_DIF = 5 + CF_TIFF = 6 + CF_OEMTEXT = 7 + CF_DIB = 8 + CF_PALETTE = 9 + CF_PENDATA = 10 + CF_RIFF = 11 + CF_WAVE = 12 + CF_UNICODETEXT = 13 + CF_ENHMETAFILE = 14 + CF_HDROP = 15 + CF_LOCALE = 16 + CF_DIBV5 = 17 + CF_MAX = 18 + CF_OWNERDISPLAY = 0x0080 + CF_DSPTEXT = 0x0081 + CF_DSPBITMAP = 0x0082 + CF_DSPMETAFILEPICT = 0x0083 + CF_DSPENHMETAFILE = 0x008E + CF_PRIVATEFIRST = 0x0200 + CF_PRIVATELAST = 0x02FF + CF_GDIOBJFIRST = 0x0300 + CF_GDIOBJLAST = 0x03FF +) + +// Bitmap compression formats +const ( + BI_RGB = 0 + BI_RLE8 = 1 + BI_RLE4 = 2 + BI_BITFIELDS = 3 + BI_JPEG = 4 + BI_PNG = 5 +) + +// SetDIBitsToDevice fuColorUse +const ( + DIB_PAL_COLORS = 1 + DIB_RGB_COLORS = 0 +) + +const ( + STANDARD_RIGHTS_REQUIRED = 0x000F +) + +// Service Control Manager object specific access types +const ( + SC_MANAGER_CONNECT = 0x0001 + SC_MANAGER_CREATE_SERVICE = 0x0002 + SC_MANAGER_ENUMERATE_SERVICE = 0x0004 + SC_MANAGER_LOCK = 0x0008 + SC_MANAGER_QUERY_LOCK_STATUS = 0x0010 + SC_MANAGER_MODIFY_BOOT_CONFIG = 0x0020 + SC_MANAGER_ALL_ACCESS = STANDARD_RIGHTS_REQUIRED | SC_MANAGER_CONNECT | SC_MANAGER_CREATE_SERVICE | SC_MANAGER_ENUMERATE_SERVICE | SC_MANAGER_LOCK | SC_MANAGER_QUERY_LOCK_STATUS | SC_MANAGER_MODIFY_BOOT_CONFIG +) + +// Service Types (Bit Mask) +const ( + SERVICE_KERNEL_DRIVER = 0x00000001 + SERVICE_FILE_SYSTEM_DRIVER = 0x00000002 + SERVICE_ADAPTER = 0x00000004 + SERVICE_RECOGNIZER_DRIVER = 0x00000008 + SERVICE_DRIVER = SERVICE_KERNEL_DRIVER | SERVICE_FILE_SYSTEM_DRIVER | SERVICE_RECOGNIZER_DRIVER + SERVICE_WIN32_OWN_PROCESS = 0x00000010 + SERVICE_WIN32_SHARE_PROCESS = 0x00000020 + SERVICE_WIN32 = SERVICE_WIN32_OWN_PROCESS | SERVICE_WIN32_SHARE_PROCESS + SERVICE_INTERACTIVE_PROCESS = 0x00000100 + SERVICE_TYPE_ALL = SERVICE_WIN32 | SERVICE_ADAPTER | SERVICE_DRIVER | SERVICE_INTERACTIVE_PROCESS +) + +// Service State -- for CurrentState +const ( + SERVICE_STOPPED = 0x00000001 + SERVICE_START_PENDING = 0x00000002 + SERVICE_STOP_PENDING = 0x00000003 + SERVICE_RUNNING = 0x00000004 + SERVICE_CONTINUE_PENDING = 0x00000005 + SERVICE_PAUSE_PENDING = 0x00000006 + SERVICE_PAUSED = 0x00000007 +) + +// Controls Accepted (Bit Mask) +const ( + SERVICE_ACCEPT_STOP = 0x00000001 + SERVICE_ACCEPT_PAUSE_CONTINUE = 0x00000002 + SERVICE_ACCEPT_SHUTDOWN = 0x00000004 + SERVICE_ACCEPT_PARAMCHANGE = 0x00000008 + SERVICE_ACCEPT_NETBINDCHANGE = 0x00000010 + SERVICE_ACCEPT_HARDWAREPROFILECHANGE = 0x00000020 + SERVICE_ACCEPT_POWEREVENT = 0x00000040 + SERVICE_ACCEPT_SESSIONCHANGE = 0x00000080 + SERVICE_ACCEPT_PRESHUTDOWN = 0x00000100 + SERVICE_ACCEPT_TIMECHANGE = 0x00000200 + SERVICE_ACCEPT_TRIGGEREVENT = 0x00000400 +) + +// Service object specific access type +const ( + SERVICE_QUERY_CONFIG = 0x0001 + SERVICE_CHANGE_CONFIG = 0x0002 + SERVICE_QUERY_STATUS = 0x0004 + SERVICE_ENUMERATE_DEPENDENTS = 0x0008 + SERVICE_START = 0x0010 + SERVICE_STOP = 0x0020 + SERVICE_PAUSE_CONTINUE = 0x0040 + SERVICE_INTERROGATE = 0x0080 + SERVICE_USER_DEFINED_CONTROL = 0x0100 + + SERVICE_ALL_ACCESS = STANDARD_RIGHTS_REQUIRED | + SERVICE_QUERY_CONFIG | + SERVICE_CHANGE_CONFIG | + SERVICE_QUERY_STATUS | + SERVICE_ENUMERATE_DEPENDENTS | + SERVICE_START | + SERVICE_STOP | + SERVICE_PAUSE_CONTINUE | + SERVICE_INTERROGATE | + SERVICE_USER_DEFINED_CONTROL +) + +// MapVirtualKey maptypes +const ( + MAPVK_VK_TO_CHAR = 2 + MAPVK_VK_TO_VSC = 0 + MAPVK_VSC_TO_VK = 1 + MAPVK_VSC_TO_VK_EX = 3 +) + +// ReadEventLog Flags +const ( + EVENTLOG_SEEK_READ = 0x0002 + EVENTLOG_SEQUENTIAL_READ = 0x0001 + EVENTLOG_FORWARDS_READ = 0x0004 + EVENTLOG_BACKWARDS_READ = 0x0008 +) + +// CreateToolhelp32Snapshot flags +const ( + TH32CS_SNAPHEAPLIST = 0x00000001 + TH32CS_SNAPPROCESS = 0x00000002 + TH32CS_SNAPTHREAD = 0x00000004 + TH32CS_SNAPMODULE = 0x00000008 + TH32CS_SNAPMODULE32 = 0x00000010 + TH32CS_INHERIT = 0x80000000 + TH32CS_SNAPALL = TH32CS_SNAPHEAPLIST | TH32CS_SNAPMODULE | TH32CS_SNAPPROCESS | TH32CS_SNAPTHREAD +) + +const ( + MAX_MODULE_NAME32 = 255 + MAX_PATH = 260 +) + +const ( + NIM_ADD = 0x00000000 + NIM_MODIFY = 0x00000001 + NIM_DELETE = 0x00000002 + NIM_SETVERSION = 0x00000004 + + NIF_MESSAGE = 0x00000001 + NIF_ICON = 0x00000002 + NIF_TIP = 0x00000004 + NIF_STATE = 0x00000008 + NIF_INFO = 0x00000010 + + NIS_HIDDEN = 0x00000001 + + NIIF_NONE = 0x00000000 + NIIF_INFO = 0x00000001 + NIIF_WARNING = 0x00000002 + NIIF_ERROR = 0x00000003 + NIIF_USER = 0x00000004 + NIIF_NOSOUND = 0x00000010 + NIIF_LARGE_ICON = 0x00000020 + NIIF_RESPECT_QUIET_TIME = 0x00000080 + NIIF_ICON_MASK = 0x0000000F +) + +const ( + FOREGROUND_BLUE = 0x0001 + FOREGROUND_GREEN = 0x0002 + FOREGROUND_RED = 0x0004 + FOREGROUND_INTENSITY = 0x0008 + BACKGROUND_BLUE = 0x0010 + BACKGROUND_GREEN = 0x0020 + BACKGROUND_RED = 0x0040 + BACKGROUND_INTENSITY = 0x0080 + COMMON_LVB_LEADING_BYTE = 0x0100 + COMMON_LVB_TRAILING_BYTE = 0x0200 + COMMON_LVB_GRID_HORIZONTAL = 0x0400 + COMMON_LVB_GRID_LVERTICAL = 0x0800 + COMMON_LVB_GRID_RVERTICAL = 0x1000 + COMMON_LVB_REVERSE_VIDEO = 0x4000 + COMMON_LVB_UNDERSCORE = 0x8000 +) + +// Flags used by the DWM_BLURBEHIND structure to indicate +// which of its members contain valid information. +const ( + DWM_BB_ENABLE = 0x00000001 // A value for the fEnable member has been specified. + DWM_BB_BLURREGION = 0x00000002 // A value for the hRgnBlur member has been specified. + DWM_BB_TRANSITIONONMAXIMIZED = 0x00000004 // A value for the fTransitionOnMaximized member has been specified. +) + +// Flags used by the DwmEnableComposition function +// to change the state of Desktop Window Manager (DWM) composition. +const ( + DWM_EC_DISABLECOMPOSITION = 0 // Disable composition + DWM_EC_ENABLECOMPOSITION = 1 // Enable composition +) + +// enum-lite implementation for the following constant structure +type DWM_SHOWCONTACT int32 + +const ( + DWMSC_DOWN = 0x00000001 + DWMSC_UP = 0x00000002 + DWMSC_DRAG = 0x00000004 + DWMSC_HOLD = 0x00000008 + DWMSC_PENBARREL = 0x00000010 + DWMSC_NONE = 0x00000000 + DWMSC_ALL = 0xFFFFFFFF +) + +// enum-lite implementation for the following constant structure +type DWM_SOURCE_FRAME_SAMPLING int32 + +// TODO: need to verify this construction +// Flags used by the DwmSetPresentParameters function +// to specify the frame sampling type +const ( + DWM_SOURCE_FRAME_SAMPLING_POINT = iota + 1 + DWM_SOURCE_FRAME_SAMPLING_COVERAGE + DWM_SOURCE_FRAME_SAMPLING_LAST +) + +// Flags used by the DWM_THUMBNAIL_PROPERTIES structure to +// indicate which of its members contain valid information. +const ( + DWM_TNP_RECTDESTINATION = 0x00000001 // A value for the rcDestination member has been specified + DWM_TNP_RECTSOURCE = 0x00000002 // A value for the rcSource member has been specified + DWM_TNP_OPACITY = 0x00000004 // A value for the opacity member has been specified + DWM_TNP_VISIBLE = 0x00000008 // A value for the fVisible member has been specified + DWM_TNP_SOURCECLIENTAREAONLY = 0x00000010 // A value for the fSourceClientAreaOnly member has been specified +) + +// enum-lite implementation for the following constant structure +type DWMFLIP3DWINDOWPOLICY int32 + +// TODO: need to verify this construction +// Flags used by the DwmSetWindowAttribute function +// to specify the Flip3D window policy +const ( + DWMFLIP3D_DEFAULT = iota + 1 + DWMFLIP3D_EXCLUDEBELOW + DWMFLIP3D_EXCLUDEABOVE + DWMFLIP3D_LAST +) + +// enum-lite implementation for the following constant structure +type DWMNCRENDERINGPOLICY int32 + +// TODO: need to verify this construction +// Flags used by the DwmSetWindowAttribute function +// to specify the non-client area rendering policy +const ( + DWMNCRP_USEWINDOWSTYLE = iota + 1 + DWMNCRP_DISABLED + DWMNCRP_ENABLED + DWMNCRP_LAST +) + +// enum-lite implementation for the following constant structure +type DWMTRANSITION_OWNEDWINDOW_TARGET int32 + +const ( + DWMTRANSITION_OWNEDWINDOW_NULL = -1 + DWMTRANSITION_OWNEDWINDOW_REPOSITION = 0 +) + +// TODO: need to verify this construction +// Flags used by the DwmGetWindowAttribute and DwmSetWindowAttribute functions +// to specify window attributes for non-client rendering +const ( + DWMWA_NCRENDERING_ENABLED = iota + 1 + DWMWA_NCRENDERING_POLICY + DWMWA_TRANSITIONS_FORCEDISABLED + DWMWA_ALLOW_NCPAINT + DWMWA_CAPTION_BUTTON_BOUNDS + DWMWA_NONCLIENT_RTL_LAYOUT + DWMWA_FORCE_ICONIC_REPRESENTATION + DWMWA_FLIP3D_POLICY + DWMWA_EXTENDED_FRAME_BOUNDS + DWMWA_HAS_ICONIC_BITMAP + DWMWA_DISALLOW_PEEK + DWMWA_EXCLUDED_FROM_PEEK + DWMWA_CLOAK + DWMWA_CLOAKED + DWMWA_FREEZE_REPRESENTATION + DWMWA_LAST +) + +// enum-lite implementation for the following constant structure +type GESTURE_TYPE int32 + +// TODO: use iota? +// Identifies the gesture type +const ( + GT_PEN_TAP = 0 + GT_PEN_DOUBLETAP = 1 + GT_PEN_RIGHTTAP = 2 + GT_PEN_PRESSANDHOLD = 3 + GT_PEN_PRESSANDHOLDABORT = 4 + GT_TOUCH_TAP = 5 + GT_TOUCH_DOUBLETAP = 6 + GT_TOUCH_RIGHTTAP = 7 + GT_TOUCH_PRESSANDHOLD = 8 + GT_TOUCH_PRESSANDHOLDABORT = 9 + GT_TOUCH_PRESSANDTAP = 10 +) + +// Icons +const ( + ICON_SMALL = 0 + ICON_BIG = 1 + ICON_SMALL2 = 2 +) + +const ( + SIZE_RESTORED = 0 + SIZE_MINIMIZED = 1 + SIZE_MAXIMIZED = 2 + SIZE_MAXSHOW = 3 + SIZE_MAXHIDE = 4 +) + +// XButton values +const ( + XBUTTON1 = 1 + XBUTTON2 = 2 +) + +const ( + LR_LOADFROMFILE = 0x00000010 + LR_DEFAULTSIZE = 0x00000040 +) + +// Devmode +const ( + DM_SPECVERSION = 0x0401 + + DM_ORIENTATION = 0x00000001 + DM_PAPERSIZE = 0x00000002 + DM_PAPERLENGTH = 0x00000004 + DM_PAPERWIDTH = 0x00000008 + DM_SCALE = 0x00000010 + DM_POSITION = 0x00000020 + DM_NUP = 0x00000040 + DM_DISPLAYORIENTATION = 0x00000080 + DM_COPIES = 0x00000100 + DM_DEFAULTSOURCE = 0x00000200 + DM_PRINTQUALITY = 0x00000400 + DM_COLOR = 0x00000800 + DM_DUPLEX = 0x00001000 + DM_YRESOLUTION = 0x00002000 + DM_TTOPTION = 0x00004000 + DM_COLLATE = 0x00008000 + DM_FORMNAME = 0x00010000 + DM_LOGPIXELS = 0x00020000 + DM_BITSPERPEL = 0x00040000 + DM_PELSWIDTH = 0x00080000 + DM_PELSHEIGHT = 0x00100000 + DM_DISPLAYFLAGS = 0x00200000 + DM_DISPLAYFREQUENCY = 0x00400000 + DM_ICMMETHOD = 0x00800000 + DM_ICMINTENT = 0x01000000 + DM_MEDIATYPE = 0x02000000 + DM_DITHERTYPE = 0x04000000 + DM_PANNINGWIDTH = 0x08000000 + DM_PANNINGHEIGHT = 0x10000000 + DM_DISPLAYFIXEDOUTPUT = 0x20000000 +) + +// ChangeDisplaySettings +const ( + CDS_UPDATEREGISTRY = 0x00000001 + CDS_TEST = 0x00000002 + CDS_FULLSCREEN = 0x00000004 + CDS_GLOBAL = 0x00000008 + CDS_SET_PRIMARY = 0x00000010 + CDS_VIDEOPARAMETERS = 0x00000020 + CDS_RESET = 0x40000000 + CDS_NORESET = 0x10000000 + + DISP_CHANGE_SUCCESSFUL = 0 + DISP_CHANGE_RESTART = 1 + DISP_CHANGE_FAILED = -1 + DISP_CHANGE_BADMODE = -2 + DISP_CHANGE_NOTUPDATED = -3 + DISP_CHANGE_BADFLAGS = -4 + DISP_CHANGE_BADPARAM = -5 + DISP_CHANGE_BADDUALVIEW = -6 +) + +const ( + ENUM_CURRENT_SETTINGS = 0xFFFFFFFF + ENUM_REGISTRY_SETTINGS = 0xFFFFFFFE +) + +// PIXELFORMATDESCRIPTOR +const ( + PFD_TYPE_RGBA = 0 + PFD_TYPE_COLORINDEX = 1 + + PFD_MAIN_PLANE = 0 + PFD_OVERLAY_PLANE = 1 + PFD_UNDERLAY_PLANE = -1 + + PFD_DOUBLEBUFFER = 0x00000001 + PFD_STEREO = 0x00000002 + PFD_DRAW_TO_WINDOW = 0x00000004 + PFD_DRAW_TO_BITMAP = 0x00000008 + PFD_SUPPORT_GDI = 0x00000010 + PFD_SUPPORT_OPENGL = 0x00000020 + PFD_GENERIC_FORMAT = 0x00000040 + PFD_NEED_PALETTE = 0x00000080 + PFD_NEED_SYSTEM_PALETTE = 0x00000100 + PFD_SWAP_EXCHANGE = 0x00000200 + PFD_SWAP_COPY = 0x00000400 + PFD_SWAP_LAYER_BUFFERS = 0x00000800 + PFD_GENERIC_ACCELERATED = 0x00001000 + PFD_SUPPORT_DIRECTDRAW = 0x00002000 + PFD_DIRECT3D_ACCELERATED = 0x00004000 + PFD_SUPPORT_COMPOSITION = 0x00008000 + + PFD_DEPTH_DONTCARE = 0x20000000 + PFD_DOUBLEBUFFER_DONTCARE = 0x40000000 + PFD_STEREO_DONTCARE = 0x80000000 +) + +const ( + INPUT_MOUSE = 0 + INPUT_KEYBOARD = 1 + INPUT_HARDWARE = 2 +) + +const ( + MOUSEEVENTF_ABSOLUTE = 0x8000 + MOUSEEVENTF_HWHEEL = 0x01000 + MOUSEEVENTF_MOVE = 0x0001 + MOUSEEVENTF_MOVE_NOCOALESCE = 0x2000 + MOUSEEVENTF_LEFTDOWN = 0x0002 + MOUSEEVENTF_LEFTUP = 0x0004 + MOUSEEVENTF_RIGHTDOWN = 0x0008 + MOUSEEVENTF_RIGHTUP = 0x0010 + MOUSEEVENTF_MIDDLEDOWN = 0x0020 + MOUSEEVENTF_MIDDLEUP = 0x0040 + MOUSEEVENTF_VIRTUALDESK = 0x4000 + MOUSEEVENTF_WHEEL = 0x0800 + MOUSEEVENTF_XDOWN = 0x0080 + MOUSEEVENTF_XUP = 0x0100 +) + +// Windows Hooks (WH_*) +// http://msdn.microsoft.com/en-us/library/windows/desktop/ms644990(v=vs.85).aspx +const ( + WH_CALLWNDPROC = 4 + WH_CALLWNDPROCRET = 12 + WH_CBT = 5 + WH_DEBUG = 9 + WH_FOREGROUNDIDLE = 11 + WH_GETMESSAGE = 3 + WH_JOURNALPLAYBACK = 1 + WH_JOURNALRECORD = 0 + WH_KEYBOARD = 2 + WH_KEYBOARD_LL = 13 + WH_MOUSE = 7 + WH_MOUSE_LL = 14 + WH_MSGFILTER = -1 + WH_SHELL = 10 + WH_SYSMSGFILTER = 6 +) + +// ComboBox return values +const ( + CB_OKAY = 0 + CB_ERR = ^uintptr(0) // -1 + CB_ERRSPACE = ^uintptr(1) // -2 +) + +// ComboBox notifications +const ( + CBN_ERRSPACE = -1 + CBN_SELCHANGE = 1 + CBN_DBLCLK = 2 + CBN_SETFOCUS = 3 + CBN_KILLFOCUS = 4 + CBN_EDITCHANGE = 5 + CBN_EDITUPDATE = 6 + CBN_DROPDOWN = 7 + CBN_CLOSEUP = 8 + CBN_SELENDOK = 9 + CBN_SELENDCANCEL = 10 +) + +// ComboBox styles +const ( + CBS_SIMPLE = 0x0001 + CBS_DROPDOWN = 0x0002 + CBS_DROPDOWNLIST = 0x0003 + CBS_OWNERDRAWFIXED = 0x0010 + CBS_OWNERDRAWVARIABLE = 0x0020 + CBS_AUTOHSCROLL = 0x0040 + CBS_OEMCONVERT = 0x0080 + CBS_SORT = 0x0100 + CBS_HASSTRINGS = 0x0200 + CBS_NOINTEGRALHEIGHT = 0x0400 + CBS_DISABLENOSCROLL = 0x0800 + CBS_UPPERCASE = 0x2000 + CBS_LOWERCASE = 0x4000 +) + +// ComboBox messages +const ( + CB_GETEDITSEL = 0x0140 + CB_LIMITTEXT = 0x0141 + CB_SETEDITSEL = 0x0142 + CB_ADDSTRING = 0x0143 + CB_DELETESTRING = 0x0144 + CB_DIR = 0x0145 + CB_GETCOUNT = 0x0146 + CB_GETCURSEL = 0x0147 + CB_GETLBTEXT = 0x0148 + CB_GETLBTEXTLEN = 0x0149 + CB_INSERTSTRING = 0x014A + CB_RESETCONTENT = 0x014B + CB_FINDSTRING = 0x014C + CB_SELECTSTRING = 0x014D + CB_SETCURSEL = 0x014E + CB_SHOWDROPDOWN = 0x014F + CB_GETITEMDATA = 0x0150 + CB_SETITEMDATA = 0x0151 + CB_GETDROPPEDCONTROLRECT = 0x0152 + CB_SETITEMHEIGHT = 0x0153 + CB_GETITEMHEIGHT = 0x0154 + CB_SETEXTENDEDUI = 0x0155 + CB_GETEXTENDEDUI = 0x0156 + CB_GETDROPPEDSTATE = 0x0157 + CB_FINDSTRINGEXACT = 0x0158 + CB_SETLOCALE = 0x0159 + CB_GETLOCALE = 0x015A + CB_GETTOPINDEX = 0x015b + CB_SETTOPINDEX = 0x015c + CB_GETHORIZONTALEXTENT = 0x015d + CB_SETHORIZONTALEXTENT = 0x015e + CB_GETDROPPEDWIDTH = 0x015f + CB_SETDROPPEDWIDTH = 0x0160 + CB_INITSTORAGE = 0x0161 + CB_MULTIPLEADDSTRING = 0x0163 + CB_GETCOMBOBOXINFO = 0x0164 +) + +// TreeView styles +const ( + TVS_HASBUTTONS = 0x0001 + TVS_HASLINES = 0x0002 + TVS_LINESATROOT = 0x0004 + TVS_EDITLABELS = 0x0008 + TVS_DISABLEDRAGDROP = 0x0010 + TVS_SHOWSELALWAYS = 0x0020 + TVS_RTLREADING = 0x0040 + TVS_NOTOOLTIPS = 0x0080 + TVS_CHECKBOXES = 0x0100 + TVS_TRACKSELECT = 0x0200 + TVS_SINGLEEXPAND = 0x0400 + TVS_INFOTIP = 0x0800 + TVS_FULLROWSELECT = 0x1000 + TVS_NOSCROLL = 0x2000 + TVS_NONEVENHEIGHT = 0x4000 + TVS_NOHSCROLL = 0x8000 +) + +const ( + TVS_EX_NOSINGLECOLLAPSE = 0x0001 + TVS_EX_MULTISELECT = 0x0002 + TVS_EX_DOUBLEBUFFER = 0x0004 + TVS_EX_NOINDENTSTATE = 0x0008 + TVS_EX_RICHTOOLTIP = 0x0010 + TVS_EX_AUTOHSCROLL = 0x0020 + TVS_EX_FADEINOUTEXPANDOS = 0x0040 + TVS_EX_PARTIALCHECKBOXES = 0x0080 + TVS_EX_EXCLUSIONCHECKBOXES = 0x0100 + TVS_EX_DIMMEDCHECKBOXES = 0x0200 + TVS_EX_DRAWIMAGEASYNC = 0x0400 +) + +const ( + TVIF_TEXT = 0x0001 + TVIF_IMAGE = 0x0002 + TVIF_PARAM = 0x0004 + TVIF_STATE = 0x0008 + TVIF_HANDLE = 0x0010 + TVIF_SELECTEDIMAGE = 0x0020 + TVIF_CHILDREN = 0x0040 + TVIF_INTEGRAL = 0x0080 + TVIF_STATEEX = 0x0100 + TVIF_EXPANDEDIMAGE = 0x0200 +) + +const ( + TVIS_SELECTED = 0x0002 + TVIS_CUT = 0x0004 + TVIS_DROPHILITED = 0x0008 + TVIS_BOLD = 0x0010 + TVIS_EXPANDED = 0x0020 + TVIS_EXPANDEDONCE = 0x0040 + TVIS_EXPANDPARTIAL = 0x0080 + TVIS_OVERLAYMASK = 0x0F00 + TVIS_STATEIMAGEMASK = 0xF000 + TVIS_USERMASK = 0xF000 +) + +const ( + TVIS_EX_FLAT = 0x0001 + TVIS_EX_DISABLED = 0x0002 + TVIS_EX_ALL = 0x0002 +) + +const ( + TVI_ROOT = ^HTREEITEM(0xffff) + TVI_FIRST = ^HTREEITEM(0xfffe) + TVI_LAST = ^HTREEITEM(0xfffd) + TVI_SORT = ^HTREEITEM(0xfffc) +) + +// TVM_EXPAND action flags +const ( + TVE_COLLAPSE = 0x0001 + TVE_EXPAND = 0x0002 + TVE_TOGGLE = 0x0003 + TVE_EXPANDPARTIAL = 0x4000 + TVE_COLLAPSERESET = 0x8000 +) + +const ( + TVGN_CARET = 9 +) + +// TreeView messages +const ( + TV_FIRST = 0x1100 + + TVM_INSERTITEM = TV_FIRST + 50 + TVM_DELETEITEM = TV_FIRST + 1 + TVM_EXPAND = TV_FIRST + 2 + TVM_GETITEMRECT = TV_FIRST + 4 + TVM_GETCOUNT = TV_FIRST + 5 + TVM_GETINDENT = TV_FIRST + 6 + TVM_SETINDENT = TV_FIRST + 7 + TVM_GETIMAGELIST = TV_FIRST + 8 + TVM_SETIMAGELIST = TV_FIRST + 9 + TVM_GETNEXTITEM = TV_FIRST + 10 + TVM_SELECTITEM = TV_FIRST + 11 + TVM_GETITEM = TV_FIRST + 62 + TVM_SETITEM = TV_FIRST + 63 + TVM_EDITLABEL = TV_FIRST + 65 + TVM_GETEDITCONTROL = TV_FIRST + 15 + TVM_GETVISIBLECOUNT = TV_FIRST + 16 + TVM_HITTEST = TV_FIRST + 17 + TVM_CREATEDRAGIMAGE = TV_FIRST + 18 + TVM_SORTCHILDREN = TV_FIRST + 19 + TVM_ENSUREVISIBLE = TV_FIRST + 20 + TVM_SORTCHILDRENCB = TV_FIRST + 21 + TVM_ENDEDITLABELNOW = TV_FIRST + 22 + TVM_GETISEARCHSTRING = TV_FIRST + 64 + TVM_SETTOOLTIPS = TV_FIRST + 24 + TVM_GETTOOLTIPS = TV_FIRST + 25 + TVM_SETINSERTMARK = TV_FIRST + 26 + TVM_SETUNICODEFORMAT = CCM_SETUNICODEFORMAT + TVM_GETUNICODEFORMAT = CCM_GETUNICODEFORMAT + TVM_SETITEMHEIGHT = TV_FIRST + 27 + TVM_GETITEMHEIGHT = TV_FIRST + 28 + TVM_SETBKCOLOR = TV_FIRST + 29 + TVM_SETTEXTCOLOR = TV_FIRST + 30 + TVM_GETBKCOLOR = TV_FIRST + 31 + TVM_GETTEXTCOLOR = TV_FIRST + 32 + TVM_SETSCROLLTIME = TV_FIRST + 33 + TVM_GETSCROLLTIME = TV_FIRST + 34 + TVM_SETINSERTMARKCOLOR = TV_FIRST + 37 + TVM_GETINSERTMARKCOLOR = TV_FIRST + 38 + TVM_GETITEMSTATE = TV_FIRST + 39 + TVM_SETLINECOLOR = TV_FIRST + 40 + TVM_GETLINECOLOR = TV_FIRST + 41 + TVM_MAPACCIDTOHTREEITEM = TV_FIRST + 42 + TVM_MAPHTREEITEMTOACCID = TV_FIRST + 43 + TVM_SETEXTENDEDSTYLE = TV_FIRST + 44 + TVM_GETEXTENDEDSTYLE = TV_FIRST + 45 + TVM_SETAUTOSCROLLINFO = TV_FIRST + 59 +) + +// TreeView notifications +const ( + TVN_FIRST = ^uint32(399) + + TVN_SELCHANGING = TVN_FIRST - 50 + TVN_SELCHANGED = TVN_FIRST - 51 + TVN_GETDISPINFO = TVN_FIRST - 52 + TVN_ITEMEXPANDING = TVN_FIRST - 54 + TVN_ITEMEXPANDED = TVN_FIRST - 55 + TVN_BEGINDRAG = TVN_FIRST - 56 + TVN_BEGINRDRAG = TVN_FIRST - 57 + TVN_DELETEITEM = TVN_FIRST - 58 + TVN_BEGINLABELEDIT = TVN_FIRST - 59 + TVN_ENDLABELEDIT = TVN_FIRST - 60 + TVN_KEYDOWN = TVN_FIRST - 12 + TVN_GETINFOTIP = TVN_FIRST - 14 + TVN_SINGLEEXPAND = TVN_FIRST - 15 + TVN_ITEMCHANGING = TVN_FIRST - 17 + TVN_ITEMCHANGED = TVN_FIRST - 19 + TVN_ASYNCDRAW = TVN_FIRST - 20 +) + +// TreeView hit test constants +const ( + TVHT_NOWHERE = 1 + TVHT_ONITEMICON = 2 + TVHT_ONITEMLABEL = 4 + TVHT_ONITEM = TVHT_ONITEMICON | TVHT_ONITEMLABEL | TVHT_ONITEMSTATEICON + TVHT_ONITEMINDENT = 8 + TVHT_ONITEMBUTTON = 16 + TVHT_ONITEMRIGHT = 32 + TVHT_ONITEMSTATEICON = 64 + TVHT_ABOVE = 256 + TVHT_BELOW = 512 + TVHT_TORIGHT = 1024 + TVHT_TOLEFT = 2048 +) + +type HTREEITEM HANDLE + +type TVITEM struct { + Mask uint32 + HItem HTREEITEM + State uint32 + StateMask uint32 + PszText uintptr + CchTextMax int32 + IImage int32 + ISelectedImage int32 + CChildren int32 + LParam uintptr +} + +/*type TVITEMEX struct { + mask UINT + hItem HTREEITEM + state UINT + stateMask UINT + pszText LPWSTR + cchTextMax int + iImage int + iSelectedImage int + cChildren int + lParam LPARAM + iIntegral int + uStateEx UINT + hwnd HWND + iExpandedImage int +}*/ + +type TVINSERTSTRUCT struct { + HParent HTREEITEM + HInsertAfter HTREEITEM + Item TVITEM + // itemex TVITEMEX +} + +type NMTREEVIEW struct { + Hdr NMHDR + Action uint32 + ItemOld TVITEM + ItemNew TVITEM + PtDrag POINT +} + +type NMTVDISPINFO struct { + Hdr NMHDR + Item TVITEM +} + +type NMTVKEYDOWN struct { + Hdr NMHDR + WVKey uint16 + Flags uint32 +} + +type TVHITTESTINFO struct { + Pt POINT + Flags uint32 + HItem HTREEITEM +} + +// TabPage support + +const TCM_FIRST = 0x1300 +const TCN_FIRST = -550 + +const ( + TCS_SCROLLOPPOSITE = 0x0001 + TCS_BOTTOM = 0x0002 + TCS_RIGHT = 0x0002 + TCS_MULTISELECT = 0x0004 + TCS_FLATBUTTONS = 0x0008 + TCS_FORCEICONLEFT = 0x0010 + TCS_FORCELABELLEFT = 0x0020 + TCS_HOTTRACK = 0x0040 + TCS_VERTICAL = 0x0080 + TCS_TABS = 0x0000 + TCS_BUTTONS = 0x0100 + TCS_SINGLELINE = 0x0000 + TCS_MULTILINE = 0x0200 + TCS_RIGHTJUSTIFY = 0x0000 + TCS_FIXEDWIDTH = 0x0400 + TCS_RAGGEDRIGHT = 0x0800 + TCS_FOCUSONBUTTONDOWN = 0x1000 + TCS_OWNERDRAWFIXED = 0x2000 + TCS_TOOLTIPS = 0x4000 + TCS_FOCUSNEVER = 0x8000 +) + +const ( + TCS_EX_FLATSEPARATORS = 0x00000001 + TCS_EX_REGISTERDROP = 0x00000002 +) + +const ( + TCM_GETIMAGELIST = TCM_FIRST + 2 + TCM_SETIMAGELIST = TCM_FIRST + 3 + TCM_GETITEMCOUNT = TCM_FIRST + 4 + TCM_GETITEM = TCM_FIRST + 60 + TCM_SETITEM = TCM_FIRST + 61 + TCM_INSERTITEM = TCM_FIRST + 62 + TCM_DELETEITEM = TCM_FIRST + 8 + TCM_DELETEALLITEMS = TCM_FIRST + 9 + TCM_GETITEMRECT = TCM_FIRST + 10 + TCM_GETCURSEL = TCM_FIRST + 11 + TCM_SETCURSEL = TCM_FIRST + 12 + TCM_HITTEST = TCM_FIRST + 13 + TCM_SETITEMEXTRA = TCM_FIRST + 14 + TCM_ADJUSTRECT = TCM_FIRST + 40 + TCM_SETITEMSIZE = TCM_FIRST + 41 + TCM_REMOVEIMAGE = TCM_FIRST + 42 + TCM_SETPADDING = TCM_FIRST + 43 + TCM_GETROWCOUNT = TCM_FIRST + 44 + TCM_GETTOOLTIPS = TCM_FIRST + 45 + TCM_SETTOOLTIPS = TCM_FIRST + 46 + TCM_GETCURFOCUS = TCM_FIRST + 47 + TCM_SETCURFOCUS = TCM_FIRST + 48 + TCM_SETMINTABWIDTH = TCM_FIRST + 49 + TCM_DESELECTALL = TCM_FIRST + 50 + TCM_HIGHLIGHTITEM = TCM_FIRST + 51 + TCM_SETEXTENDEDSTYLE = TCM_FIRST + 52 + TCM_GETEXTENDEDSTYLE = TCM_FIRST + 53 + TCM_SETUNICODEFORMAT = CCM_SETUNICODEFORMAT + TCM_GETUNICODEFORMAT = CCM_GETUNICODEFORMAT +) + +const ( + TCIF_TEXT = 0x0001 + TCIF_IMAGE = 0x0002 + TCIF_RTLREADING = 0x0004 + TCIF_PARAM = 0x0008 + TCIF_STATE = 0x0010 +) + +const ( + TCIS_BUTTONPRESSED = 0x0001 + TCIS_HIGHLIGHTED = 0x0002 +) + +const ( + TCHT_NOWHERE = 0x0001 + TCHT_ONITEMICON = 0x0002 + TCHT_ONITEMLABEL = 0x0004 + TCHT_ONITEM = TCHT_ONITEMICON | TCHT_ONITEMLABEL +) + +const ( + TCN_KEYDOWN = TCN_FIRST - 0 + TCN_SELCHANGE = TCN_FIRST - 1 + TCN_SELCHANGING = TCN_FIRST - 2 + TCN_GETOBJECT = TCN_FIRST - 3 + TCN_FOCUSCHANGE = TCN_FIRST - 4 +) + +type TCITEMHEADER struct { + Mask uint32 + LpReserved1 uint32 + LpReserved2 uint32 + PszText *uint16 + CchTextMax int32 + IImage int32 +} + +type TCITEM struct { + Mask uint32 + DwState uint32 + DwStateMask uint32 + PszText *uint16 + CchTextMax int32 + IImage int32 + LParam uintptr +} + +type TCHITTESTINFO struct { + Pt POINT + flags uint32 +} + +type NMTCKEYDOWN struct { + Hdr NMHDR + WVKey uint16 + Flags uint32 +} + +// Menu support constants + +// Constants for MENUITEMINFO.fMask +const ( + MIIM_STATE = 1 + MIIM_ID = 2 + MIIM_SUBMENU = 4 + MIIM_CHECKMARKS = 8 + MIIM_TYPE = 16 + MIIM_DATA = 32 + MIIM_STRING = 64 + MIIM_BITMAP = 128 + MIIM_FTYPE = 256 +) + +// Constants for MENUITEMINFO.fType +const ( + MFT_BITMAP = 4 + MFT_MENUBARBREAK = 32 + MFT_MENUBREAK = 64 + MFT_OWNERDRAW = 256 + MFT_RADIOCHECK = 512 + MFT_RIGHTJUSTIFY = 0x4000 + MFT_SEPARATOR = 0x800 + MFT_RIGHTORDER = 0x2000 + MFT_STRING = 0 +) + +// Constants for MENUITEMINFO.fState +const ( + MFS_CHECKED = 8 + MFS_DEFAULT = 4096 + MFS_DISABLED = 3 + MFS_ENABLED = 0 + MFS_GRAYED = 3 + MFS_HILITE = 128 + MFS_UNCHECKED = 0 + MFS_UNHILITE = 0 +) + +// Constants for MENUITEMINFO.hbmp* +const ( + HBMMENU_CALLBACK = -1 + HBMMENU_SYSTEM = 1 + HBMMENU_MBAR_RESTORE = 2 + HBMMENU_MBAR_MINIMIZE = 3 + HBMMENU_MBAR_CLOSE = 5 + HBMMENU_MBAR_CLOSE_D = 6 + HBMMENU_MBAR_MINIMIZE_D = 7 + HBMMENU_POPUP_CLOSE = 8 + HBMMENU_POPUP_RESTORE = 9 + HBMMENU_POPUP_MAXIMIZE = 10 + HBMMENU_POPUP_MINIMIZE = 11 +) + +// MENUINFO mask constants +const ( + MIM_APPLYTOSUBMENUS = 0x80000000 + MIM_BACKGROUND = 0x00000002 + MIM_HELPID = 0x00000004 + MIM_MAXHEIGHT = 0x00000001 + MIM_MENUDATA = 0x00000008 + MIM_STYLE = 0x00000010 +) + +// MENUINFO style constants +const ( + MNS_AUTODISMISS = 0x10000000 + MNS_CHECKORBMP = 0x04000000 + MNS_DRAGDROP = 0x20000000 + MNS_MODELESS = 0x40000000 + MNS_NOCHECK = 0x80000000 + MNS_NOTIFYBYPOS = 0x08000000 +) + +const ( + MF_BYCOMMAND = 0x00000000 + MF_BYPOSITION = 0x00000400 + MF_BITMAP = 0x00000004 + MF_CHECKED = 0x00000008 + MF_DISABLED = 0x00000002 + MF_ENABLED = 0x00000000 + MF_GRAYED = 0x00000001 + MF_MENUBARBREAK = 0x00000020 + MF_MENUBREAK = 0x00000040 + MF_OWNERDRAW = 0x00000100 + MF_POPUP = 0x00000010 + MF_SEPARATOR = 0x00000800 + MF_STRING = 0x00000000 + MF_UNCHECKED = 0x00000000 +) + +type MENUITEMINFO struct { + CbSize uint32 + FMask uint32 + FType uint32 + FState uint32 + WID uint32 + HSubMenu HMENU + HbmpChecked HBITMAP + HbmpUnchecked HBITMAP + DwItemData uintptr + DwTypeData *uint16 + Cch uint32 + HbmpItem HBITMAP +} + +type MENUINFO struct { + CbSize uint32 + FMask uint32 + DwStyle uint32 + CyMax uint32 + HbrBack HBRUSH + DwContextHelpID uint32 + DwMenuData uintptr +} + +// UI state constants +const ( + UIS_SET = 1 + UIS_CLEAR = 2 + UIS_INITIALIZE = 3 +) + +// UI state constants +const ( + UISF_HIDEFOCUS = 0x1 + UISF_HIDEACCEL = 0x2 + UISF_ACTIVE = 0x4 +) + +// Virtual key codes +const ( + VK_LBUTTON = 1 + VK_RBUTTON = 2 + VK_CANCEL = 3 + VK_MBUTTON = 4 + VK_XBUTTON1 = 5 + VK_XBUTTON2 = 6 + VK_BACK = 8 + VK_TAB = 9 + VK_CLEAR = 12 + VK_RETURN = 13 + VK_SHIFT = 16 + VK_CONTROL = 17 + VK_MENU = 18 + VK_PAUSE = 19 + VK_CAPITAL = 20 + VK_KANA = 0x15 + VK_HANGEUL = 0x15 + VK_HANGUL = 0x15 + VK_JUNJA = 0x17 + VK_FINAL = 0x18 + VK_HANJA = 0x19 + VK_KANJI = 0x19 + VK_ESCAPE = 0x1B + VK_CONVERT = 0x1C + VK_NONCONVERT = 0x1D + VK_ACCEPT = 0x1E + VK_MODECHANGE = 0x1F + VK_SPACE = 32 + VK_PRIOR = 33 + VK_NEXT = 34 + VK_END = 35 + VK_HOME = 36 + VK_LEFT = 37 + VK_UP = 38 + VK_RIGHT = 39 + VK_DOWN = 40 + VK_SELECT = 41 + VK_PRINT = 42 + VK_EXECUTE = 43 + VK_SNAPSHOT = 44 + VK_INSERT = 45 + VK_DELETE = 46 + VK_HELP = 47 + VK_LWIN = 0x5B + VK_RWIN = 0x5C + VK_APPS = 0x5D + VK_SLEEP = 0x5F + VK_NUMPAD0 = 0x60 + VK_NUMPAD1 = 0x61 + VK_NUMPAD2 = 0x62 + VK_NUMPAD3 = 0x63 + VK_NUMPAD4 = 0x64 + VK_NUMPAD5 = 0x65 + VK_NUMPAD6 = 0x66 + VK_NUMPAD7 = 0x67 + VK_NUMPAD8 = 0x68 + VK_NUMPAD9 = 0x69 + VK_MULTIPLY = 0x6A + VK_ADD = 0x6B + VK_SEPARATOR = 0x6C + VK_SUBTRACT = 0x6D + VK_DECIMAL = 0x6E + VK_DIVIDE = 0x6F + VK_F1 = 0x70 + VK_F2 = 0x71 + VK_F3 = 0x72 + VK_F4 = 0x73 + VK_F5 = 0x74 + VK_F6 = 0x75 + VK_F7 = 0x76 + VK_F8 = 0x77 + VK_F9 = 0x78 + VK_F10 = 0x79 + VK_F11 = 0x7A + VK_F12 = 0x7B + VK_F13 = 0x7C + VK_F14 = 0x7D + VK_F15 = 0x7E + VK_F16 = 0x7F + VK_F17 = 0x80 + VK_F18 = 0x81 + VK_F19 = 0x82 + VK_F20 = 0x83 + VK_F21 = 0x84 + VK_F22 = 0x85 + VK_F23 = 0x86 + VK_F24 = 0x87 + VK_NUMLOCK = 0x90 + VK_SCROLL = 0x91 + VK_LSHIFT = 0xA0 + VK_RSHIFT = 0xA1 + VK_LCONTROL = 0xA2 + VK_RCONTROL = 0xA3 + VK_LMENU = 0xA4 + VK_RMENU = 0xA5 + VK_BROWSER_BACK = 0xA6 + VK_BROWSER_FORWARD = 0xA7 + VK_BROWSER_REFRESH = 0xA8 + VK_BROWSER_STOP = 0xA9 + VK_BROWSER_SEARCH = 0xAA + VK_BROWSER_FAVORITES = 0xAB + VK_BROWSER_HOME = 0xAC + VK_VOLUME_MUTE = 0xAD + VK_VOLUME_DOWN = 0xAE + VK_VOLUME_UP = 0xAF + VK_MEDIA_NEXT_TRACK = 0xB0 + VK_MEDIA_PREV_TRACK = 0xB1 + VK_MEDIA_STOP = 0xB2 + VK_MEDIA_PLAY_PAUSE = 0xB3 + VK_LAUNCH_MAIL = 0xB4 + VK_LAUNCH_MEDIA_SELECT = 0xB5 + VK_LAUNCH_APP1 = 0xB6 + VK_LAUNCH_APP2 = 0xB7 + VK_OEM_1 = 0xBA + VK_OEM_PLUS = 0xBB + VK_OEM_COMMA = 0xBC + VK_OEM_MINUS = 0xBD + VK_OEM_PERIOD = 0xBE + VK_OEM_2 = 0xBF + VK_OEM_3 = 0xC0 + VK_OEM_4 = 0xDB + VK_OEM_5 = 0xDC + VK_OEM_6 = 0xDD + VK_OEM_7 = 0xDE + VK_OEM_8 = 0xDF + VK_OEM_102 = 0xE2 + VK_PROCESSKEY = 0xE5 + VK_PACKET = 0xE7 + VK_ATTN = 0xF6 + VK_CRSEL = 0xF7 + VK_EXSEL = 0xF8 + VK_EREOF = 0xF9 + VK_PLAY = 0xFA + VK_ZOOM = 0xFB + VK_NONAME = 0xFC + VK_PA1 = 0xFD + VK_OEM_CLEAR = 0xFE +) + +// ScrollBar constants +const ( + SB_HORZ = 0 + SB_VERT = 1 + SB_CTL = 2 + SB_BOTH = 3 +) + +// ScrollBar commands +const ( + SB_LINEUP = 0 + SB_LINELEFT = 0 + SB_LINEDOWN = 1 + SB_LINERIGHT = 1 + SB_PAGEUP = 2 + SB_PAGELEFT = 2 + SB_PAGEDOWN = 3 + SB_PAGERIGHT = 3 + SB_THUMBPOSITION = 4 + SB_THUMBTRACK = 5 + SB_TOP = 6 + SB_LEFT = 6 + SB_BOTTOM = 7 + SB_RIGHT = 7 + SB_ENDSCROLL = 8 +) + +// [Get|Set]ScrollInfo mask constants +const ( + SIF_RANGE = 1 + SIF_PAGE = 2 + SIF_POS = 4 + SIF_DISABLENOSCROLL = 8 + SIF_TRACKPOS = 16 + SIF_ALL = SIF_RANGE + SIF_PAGE + SIF_POS + SIF_TRACKPOS +) + +const AC_SRC_OVER = 0 +const AC_SRC_ALPHA = 1 + +const ULW_COLORKEY = 1 +const ULW_ALPHA = 2 +const ULW_OPAQUE = 4 +const ULW_EX_NORESIZE = 8 diff --git a/v3/pkg/w32/consts.go b/v3/pkg/w32/consts.go new file mode 100644 index 000000000..b8cdf0358 --- /dev/null +++ b/v3/pkg/w32/consts.go @@ -0,0 +1,102 @@ +//go:build windows + +package w32 + +import ( + "fmt" + "golang.org/x/sys/windows" + "golang.org/x/sys/windows/registry" + "strconv" + "syscall" + "unsafe" +) + +var ( + modwingdi = syscall.NewLazyDLL("gdi32.dll") + procCreateSolidBrush = modwingdi.NewProc("CreateSolidBrush") +) +var ( + kernel32 = syscall.NewLazyDLL("kernel32") + kernelGlobalAlloc = kernel32.NewProc("GlobalAlloc") + kernelGlobalFree = kernel32.NewProc("GlobalFree") + kernelGlobalLock = kernel32.NewProc("GlobalLock") + kernelGlobalUnlock = kernel32.NewProc("GlobalUnlock") + kernelLstrcpy = kernel32.NewProc("lstrcpyW") +) +var ( + modBranding = syscall.NewLazyDLL("winbrand.dll") + brandingFormatString = modBranding.NewProc("BrandingFormatString") +) + +var windowsVersion, _ = GetWindowsVersionInfo() + +func IsWindowsVersionAtLeast(major, minor, buildNumber int) bool { + return windowsVersion.Major >= major && + windowsVersion.Minor >= minor && + windowsVersion.Build >= buildNumber +} + +type WindowsVersionInfo struct { + Major int + Minor int + Build int + DisplayVersion string +} + +func (w *WindowsVersionInfo) String() string { + return fmt.Sprintf("%d.%d.%d (%s)", w.Major, w.Minor, w.Build, w.DisplayVersion) +} + +func (w *WindowsVersionInfo) IsWindowsVersionAtLeast(major, minor, buildNumber int) bool { + return w.Major >= major && w.Minor >= minor && w.Build >= buildNumber +} + +func GetBranding() string { + windowsLong := MustStringToUTF16Ptr("%WINDOWS_LONG%\x00") + ret, _, _ := brandingFormatString.Call( + uintptr(unsafe.Pointer(windowsLong)), + ) + return windows.UTF16PtrToString((*uint16)(unsafe.Pointer(ret))) +} + +func GetWindowsVersionInfo() (*WindowsVersionInfo, error) { + key, err := registry.OpenKey(registry.LOCAL_MACHINE, `SOFTWARE\Microsoft\Windows NT\CurrentVersion`, registry.QUERY_VALUE) + if err != nil { + return nil, err + } + + return &WindowsVersionInfo{ + Major: regDWORDKeyAsInt(key, "CurrentMajorVersionNumber"), + Minor: regDWORDKeyAsInt(key, "CurrentMinorVersionNumber"), + Build: regStringKeyAsInt(key, "CurrentBuildNumber"), + DisplayVersion: regKeyAsString(key, "DisplayVersion"), + }, nil +} + +func regDWORDKeyAsInt(key registry.Key, name string) int { + result, _, err := key.GetIntegerValue(name) + if err != nil { + return -1 + } + return int(result) +} + +func regStringKeyAsInt(key registry.Key, name string) int { + resultStr, _, err := key.GetStringValue(name) + if err != nil { + return -1 + } + result, err := strconv.Atoi(resultStr) + if err != nil { + return -1 + } + return result +} + +func regKeyAsString(key registry.Key, name string) string { + resultStr, _, err := key.GetStringValue(name) + if err != nil { + return "" + } + return resultStr +} diff --git a/v3/pkg/w32/dialogs.go b/v3/pkg/w32/dialogs.go new file mode 100644 index 000000000..d327187c0 --- /dev/null +++ b/v3/pkg/w32/dialogs.go @@ -0,0 +1,28 @@ +//go:build windows + +package w32 + +import ( + "unsafe" +) + +func MessageBoxWithIcon(hwnd HWND, text *uint16, caption *uint16, iconID int, flags uint32) (int32, error) { + + params := MSGBOXPARAMS{ + cbSize: uint32(unsafe.Sizeof(MSGBOXPARAMS{})), + hwndOwner: hwnd, + hInstance: GetApplicationHandle(), + lpszText: text, + lpszCaption: caption, + dwStyle: flags, + lpszIcon: (*uint16)(unsafe.Pointer(uintptr(iconID))), + } + + r, _, err := procMessageBoxIndirect.Call( + uintptr(unsafe.Pointer(¶ms)), + ) + if r == 0 { + return 0, err + } + return int32(r), nil +} diff --git a/v3/pkg/w32/dwmapi.go b/v3/pkg/w32/dwmapi.go new file mode 100644 index 000000000..b25310db2 --- /dev/null +++ b/v3/pkg/w32/dwmapi.go @@ -0,0 +1,36 @@ +//go:build windows + +package w32 + +import ( + "syscall" + "unsafe" +) + +var ( + moddwmapi = syscall.NewLazyDLL("dwmapi.dll") + + procDwmSetWindowAttribute = moddwmapi.NewProc("DwmSetWindowAttribute") + procDwmExtendFrameIntoClientArea = moddwmapi.NewProc("DwmExtendFrameIntoClientArea") +) + +func DwmSetWindowAttribute(hwnd HWND, dwAttribute DWMWINDOWATTRIBUTE, pvAttribute unsafe.Pointer, cbAttribute uintptr) HRESULT { + ret, _, _ := procDwmSetWindowAttribute.Call( + hwnd, + uintptr(dwAttribute), + uintptr(pvAttribute), + cbAttribute) + return HRESULT(ret) +} + +func dwmExtendFrameIntoClientArea(hwnd uintptr, margins *MARGINS) error { + ret, _, _ := procDwmExtendFrameIntoClientArea.Call( + hwnd, + uintptr(unsafe.Pointer(margins))) + + if ret != 0 { + return syscall.GetLastError() + } + + return nil +} diff --git a/v3/pkg/w32/gdi32.go b/v3/pkg/w32/gdi32.go new file mode 100644 index 000000000..b4b9053e6 --- /dev/null +++ b/v3/pkg/w32/gdi32.go @@ -0,0 +1,526 @@ +//go:build windows + +/* + * Copyright (C) 2019 The Winc Authors. All Rights Reserved. + * Copyright (C) 2010-2012 The W32 Authors. All Rights Reserved. + */ +package w32 + +import ( + "syscall" + "unsafe" +) + +var ( + modgdi32 = syscall.NewLazyDLL("gdi32.dll") + + procGetDeviceCaps = modgdi32.NewProc("GetDeviceCaps") + procDeleteObject = modgdi32.NewProc("DeleteObject") + procCreateFontIndirect = modgdi32.NewProc("CreateFontIndirectW") + procAbortDoc = modgdi32.NewProc("AbortDoc") + procBitBlt = modgdi32.NewProc("BitBlt") + procPatBlt = modgdi32.NewProc("PatBlt") + procCloseEnhMetaFile = modgdi32.NewProc("CloseEnhMetaFile") + procCopyEnhMetaFile = modgdi32.NewProc("CopyEnhMetaFileW") + procCreateBrushIndirect = modgdi32.NewProc("CreateBrushIndirect") + procCreateCompatibleDC = modgdi32.NewProc("CreateCompatibleDC") + procCreateDC = modgdi32.NewProc("CreateDCW") + procCreateDIBSection = modgdi32.NewProc("CreateDIBSection") + procCreateEnhMetaFile = modgdi32.NewProc("CreateEnhMetaFileW") + procCreateIC = modgdi32.NewProc("CreateICW") + procDeleteDC = modgdi32.NewProc("DeleteDC") + procDeleteEnhMetaFile = modgdi32.NewProc("DeleteEnhMetaFile") + procEllipse = modgdi32.NewProc("Ellipse") + procEndDoc = modgdi32.NewProc("EndDoc") + procEndPage = modgdi32.NewProc("EndPage") + procExtCreatePen = modgdi32.NewProc("ExtCreatePen") + procGetEnhMetaFile = modgdi32.NewProc("GetEnhMetaFileW") + procGetEnhMetaFileHeader = modgdi32.NewProc("GetEnhMetaFileHeader") + procGetObject = modgdi32.NewProc("GetObjectW") + procGetStockObject = modgdi32.NewProc("GetStockObject") + procGetTextExtentExPoint = modgdi32.NewProc("GetTextExtentExPointW") + procGetTextExtentPoint32 = modgdi32.NewProc("GetTextExtentPoint32W") + procGetTextMetrics = modgdi32.NewProc("GetTextMetricsW") + procLineTo = modgdi32.NewProc("LineTo") + procMoveToEx = modgdi32.NewProc("MoveToEx") + procPlayEnhMetaFile = modgdi32.NewProc("PlayEnhMetaFile") + procRectangle = modgdi32.NewProc("Rectangle") + procResetDC = modgdi32.NewProc("ResetDCW") + procSelectObject = modgdi32.NewProc("SelectObject") + procSetBkMode = modgdi32.NewProc("SetBkMode") + procSetBrushOrgEx = modgdi32.NewProc("SetBrushOrgEx") + procSetStretchBltMode = modgdi32.NewProc("SetStretchBltMode") + procSetTextColor = modgdi32.NewProc("SetTextColor") + procSetBkColor = modgdi32.NewProc("SetBkColor") + procStartDoc = modgdi32.NewProc("StartDocW") + procStartPage = modgdi32.NewProc("StartPage") + procStretchBlt = modgdi32.NewProc("StretchBlt") + procSetDIBitsToDevice = modgdi32.NewProc("SetDIBitsToDevice") + procChoosePixelFormat = modgdi32.NewProc("ChoosePixelFormat") + procDescribePixelFormat = modgdi32.NewProc("DescribePixelFormat") + procGetEnhMetaFilePixelFormat = modgdi32.NewProc("GetEnhMetaFilePixelFormat") + procGetPixelFormat = modgdi32.NewProc("GetPixelFormat") + procSetPixelFormat = modgdi32.NewProc("SetPixelFormat") + procSwapBuffers = modgdi32.NewProc("SwapBuffers") +) + +func GetDeviceCaps(hdc HDC, index int) int { + ret, _, _ := procGetDeviceCaps.Call( + uintptr(hdc), + uintptr(index)) + + return int(ret) +} + +func DeleteObject(hObject HGDIOBJ) bool { + ret, _, _ := procDeleteObject.Call( + uintptr(hObject)) + + return ret != 0 +} + +func CreateFontIndirect(logFont *LOGFONT) HFONT { + ret, _, _ := procCreateFontIndirect.Call( + uintptr(unsafe.Pointer(logFont))) + + return HFONT(ret) +} + +func AbortDoc(hdc HDC) int { + ret, _, _ := procAbortDoc.Call( + uintptr(hdc)) + + return int(ret) +} + +func BitBlt(hdcDest HDC, nXDest, nYDest, nWidth, nHeight int, hdcSrc HDC, nXSrc, nYSrc int, dwRop uint) { + ret, _, _ := procBitBlt.Call( + uintptr(hdcDest), + uintptr(nXDest), + uintptr(nYDest), + uintptr(nWidth), + uintptr(nHeight), + uintptr(hdcSrc), + uintptr(nXSrc), + uintptr(nYSrc), + uintptr(dwRop)) + + if ret == 0 { + panic("BitBlt failed") + } +} + +func PatBlt(hdc HDC, nXLeft, nYLeft, nWidth, nHeight int, dwRop uint) { + ret, _, _ := procPatBlt.Call( + uintptr(hdc), + uintptr(nXLeft), + uintptr(nYLeft), + uintptr(nWidth), + uintptr(nHeight), + uintptr(dwRop)) + + if ret == 0 { + panic("PatBlt failed") + } +} + +func CloseEnhMetaFile(hdc HDC) HENHMETAFILE { + ret, _, _ := procCloseEnhMetaFile.Call( + uintptr(hdc)) + + return HENHMETAFILE(ret) +} + +func CopyEnhMetaFile(hemfSrc HENHMETAFILE, lpszFile *uint16) HENHMETAFILE { + ret, _, _ := procCopyEnhMetaFile.Call( + uintptr(hemfSrc), + uintptr(unsafe.Pointer(lpszFile))) + + return HENHMETAFILE(ret) +} + +func CreateBrushIndirect(lplb *LOGBRUSH) HBRUSH { + ret, _, _ := procCreateBrushIndirect.Call( + uintptr(unsafe.Pointer(lplb))) + + return HBRUSH(ret) +} + +func CreateCompatibleDC(hdc HDC) HDC { + ret, _, _ := procCreateCompatibleDC.Call( + uintptr(hdc)) + + if ret == 0 { + panic("Create compatible DC failed") + } + + return HDC(ret) +} + +func CreateDC(lpszDriver, lpszDevice, lpszOutput *uint16, lpInitData *DEVMODE) HDC { + ret, _, _ := procCreateDC.Call( + uintptr(unsafe.Pointer(lpszDriver)), + uintptr(unsafe.Pointer(lpszDevice)), + uintptr(unsafe.Pointer(lpszOutput)), + uintptr(unsafe.Pointer(lpInitData))) + + return HDC(ret) +} + +func CreateDIBSection(hdc HDC, pbmi *BITMAPINFO, iUsage uint, ppvBits *unsafe.Pointer, hSection HANDLE, dwOffset uint) HBITMAP { + ret, _, _ := procCreateDIBSection.Call( + uintptr(hdc), + uintptr(unsafe.Pointer(pbmi)), + uintptr(iUsage), + uintptr(unsafe.Pointer(ppvBits)), + uintptr(hSection), + uintptr(dwOffset)) + + return HBITMAP(ret) +} + +func CreateEnhMetaFile(hdcRef HDC, lpFilename *uint16, lpRect *RECT, lpDescription *uint16) HDC { + ret, _, _ := procCreateEnhMetaFile.Call( + uintptr(hdcRef), + uintptr(unsafe.Pointer(lpFilename)), + uintptr(unsafe.Pointer(lpRect)), + uintptr(unsafe.Pointer(lpDescription))) + + return HDC(ret) +} + +func CreateIC(lpszDriver, lpszDevice, lpszOutput *uint16, lpdvmInit *DEVMODE) HDC { + ret, _, _ := procCreateIC.Call( + uintptr(unsafe.Pointer(lpszDriver)), + uintptr(unsafe.Pointer(lpszDevice)), + uintptr(unsafe.Pointer(lpszOutput)), + uintptr(unsafe.Pointer(lpdvmInit))) + + return HDC(ret) +} + +func DeleteDC(hdc HDC) bool { + ret, _, _ := procDeleteDC.Call( + uintptr(hdc)) + + return ret != 0 +} + +func DeleteEnhMetaFile(hemf HENHMETAFILE) bool { + ret, _, _ := procDeleteEnhMetaFile.Call( + uintptr(hemf)) + + return ret != 0 +} + +func Ellipse(hdc HDC, nLeftRect, nTopRect, nRightRect, nBottomRect int32) bool { + ret, _, _ := procEllipse.Call( + uintptr(hdc), + uintptr(nLeftRect), + uintptr(nTopRect), + uintptr(nRightRect), + uintptr(nBottomRect)) + + return ret != 0 +} + +func EndDoc(hdc HDC) int { + ret, _, _ := procEndDoc.Call( + uintptr(hdc)) + + return int(ret) +} + +func EndPage(hdc HDC) int { + ret, _, _ := procEndPage.Call( + uintptr(hdc)) + + return int(ret) +} + +func ExtCreatePen(dwPenStyle, dwWidth uint, lplb *LOGBRUSH, dwStyleCount uint, lpStyle *uint) HPEN { + ret, _, _ := procExtCreatePen.Call( + uintptr(dwPenStyle), + uintptr(dwWidth), + uintptr(unsafe.Pointer(lplb)), + uintptr(dwStyleCount), + uintptr(unsafe.Pointer(lpStyle))) + + return HPEN(ret) +} + +func GetEnhMetaFile(lpszMetaFile *uint16) HENHMETAFILE { + ret, _, _ := procGetEnhMetaFile.Call( + uintptr(unsafe.Pointer(lpszMetaFile))) + + return HENHMETAFILE(ret) +} + +func GetEnhMetaFileHeader(hemf HENHMETAFILE, cbBuffer uint, lpemh *ENHMETAHEADER) uint { + ret, _, _ := procGetEnhMetaFileHeader.Call( + uintptr(hemf), + uintptr(cbBuffer), + uintptr(unsafe.Pointer(lpemh))) + + return uint(ret) +} + +func GetObject(hgdiobj HGDIOBJ, cbBuffer uintptr, lpvObject unsafe.Pointer) int { + ret, _, _ := procGetObject.Call( + uintptr(hgdiobj), + uintptr(cbBuffer), + uintptr(lpvObject)) + + return int(ret) +} + +func GetStockObject(fnObject int) HGDIOBJ { + ret, _, _ := procGetDeviceCaps.Call( + uintptr(fnObject)) + + return HGDIOBJ(ret) +} + +func GetTextExtentExPoint(hdc HDC, lpszStr *uint16, cchString, nMaxExtent int, lpnFit, alpDx *int, lpSize *SIZE) bool { + ret, _, _ := procGetTextExtentExPoint.Call( + uintptr(hdc), + uintptr(unsafe.Pointer(lpszStr)), + uintptr(cchString), + uintptr(nMaxExtent), + uintptr(unsafe.Pointer(lpnFit)), + uintptr(unsafe.Pointer(alpDx)), + uintptr(unsafe.Pointer(lpSize))) + + return ret != 0 +} + +func GetTextExtentPoint32(hdc HDC, lpString *uint16, c int, lpSize *SIZE) bool { + ret, _, _ := procGetTextExtentPoint32.Call( + uintptr(hdc), + uintptr(unsafe.Pointer(lpString)), + uintptr(c), + uintptr(unsafe.Pointer(lpSize))) + + return ret != 0 +} + +func GetTextMetrics(hdc HDC, lptm *TEXTMETRIC) bool { + ret, _, _ := procGetTextMetrics.Call( + uintptr(hdc), + uintptr(unsafe.Pointer(lptm))) + + return ret != 0 +} + +func LineTo(hdc HDC, nXEnd, nYEnd int32) bool { + ret, _, _ := procLineTo.Call( + uintptr(hdc), + uintptr(nXEnd), + uintptr(nYEnd)) + + return ret != 0 +} + +func MoveToEx(hdc HDC, x, y int, lpPoint *POINT) bool { + ret, _, _ := procMoveToEx.Call( + uintptr(hdc), + uintptr(x), + uintptr(y), + uintptr(unsafe.Pointer(lpPoint))) + + return ret != 0 +} + +func PlayEnhMetaFile(hdc HDC, hemf HENHMETAFILE, lpRect *RECT) bool { + ret, _, _ := procPlayEnhMetaFile.Call( + uintptr(hdc), + uintptr(hemf), + uintptr(unsafe.Pointer(lpRect))) + + return ret != 0 +} + +func Rectangle(hdc HDC, nLeftRect, nTopRect, nRightRect, nBottomRect int32) bool { + ret, _, _ := procRectangle.Call( + uintptr(hdc), + uintptr(nLeftRect), + uintptr(nTopRect), + uintptr(nRightRect), + uintptr(nBottomRect)) + + return ret != 0 +} + +func ResetDC(hdc HDC, lpInitData *DEVMODE) HDC { + ret, _, _ := procResetDC.Call( + uintptr(hdc), + uintptr(unsafe.Pointer(lpInitData))) + + return HDC(ret) +} + +func SelectObject(hdc HDC, hgdiobj HGDIOBJ) HGDIOBJ { + ret, _, _ := procSelectObject.Call( + uintptr(hdc), + uintptr(hgdiobj)) + + if ret == 0 { + panic("SelectObject failed") + } + + return HGDIOBJ(ret) +} + +func SetBkMode(hdc HDC, iBkMode int) int { + ret, _, _ := procSetBkMode.Call( + uintptr(hdc), + uintptr(iBkMode)) + + if ret == 0 { + panic("SetBkMode failed") + } + + return int(ret) +} + +func SetBrushOrgEx(hdc HDC, nXOrg, nYOrg int, lppt *POINT) bool { + ret, _, _ := procSetBrushOrgEx.Call( + uintptr(hdc), + uintptr(nXOrg), + uintptr(nYOrg), + uintptr(unsafe.Pointer(lppt))) + + return ret != 0 +} + +func SetStretchBltMode(hdc HDC, iStretchMode int) int { + ret, _, _ := procSetStretchBltMode.Call( + uintptr(hdc), + uintptr(iStretchMode)) + + return int(ret) +} + +func SetTextColor(hdc HDC, crColor COLORREF) COLORREF { + ret, _, _ := procSetTextColor.Call( + uintptr(hdc), + uintptr(crColor)) + + if ret == CLR_INVALID { + panic("SetTextColor failed") + } + + return COLORREF(ret) +} + +func SetBkColor(hdc HDC, crColor COLORREF) COLORREF { + ret, _, _ := procSetBkColor.Call( + uintptr(hdc), + uintptr(crColor)) + + if ret == CLR_INVALID { + panic("SetBkColor failed") + } + + return COLORREF(ret) +} + +func StartDoc(hdc HDC, lpdi *DOCINFO) int { + ret, _, _ := procStartDoc.Call( + uintptr(hdc), + uintptr(unsafe.Pointer(lpdi))) + + return int(ret) +} + +func StartPage(hdc HDC) int { + ret, _, _ := procStartPage.Call( + uintptr(hdc)) + + return int(ret) +} + +func StretchBlt(hdcDest HDC, nXOriginDest, nYOriginDest, nWidthDest, nHeightDest int, hdcSrc HDC, nXOriginSrc, nYOriginSrc, nWidthSrc, nHeightSrc int, dwRop uint) { + ret, _, _ := procStretchBlt.Call( + uintptr(hdcDest), + uintptr(nXOriginDest), + uintptr(nYOriginDest), + uintptr(nWidthDest), + uintptr(nHeightDest), + uintptr(hdcSrc), + uintptr(nXOriginSrc), + uintptr(nYOriginSrc), + uintptr(nWidthSrc), + uintptr(nHeightSrc), + uintptr(dwRop)) + + if ret == 0 { + panic("StretchBlt failed") + } +} + +func SetDIBitsToDevice(hdc HDC, xDest, yDest, dwWidth, dwHeight, xSrc, ySrc int, uStartScan, cScanLines uint, lpvBits []byte, lpbmi *BITMAPINFO, fuColorUse uint) int { + ret, _, _ := procSetDIBitsToDevice.Call( + uintptr(hdc), + uintptr(xDest), + uintptr(yDest), + uintptr(dwWidth), + uintptr(dwHeight), + uintptr(xSrc), + uintptr(ySrc), + uintptr(uStartScan), + uintptr(cScanLines), + uintptr(unsafe.Pointer(&lpvBits[0])), + uintptr(unsafe.Pointer(lpbmi)), + uintptr(fuColorUse)) + + return int(ret) +} + +func ChoosePixelFormat(hdc HDC, pfd *PIXELFORMATDESCRIPTOR) int { + ret, _, _ := procChoosePixelFormat.Call( + uintptr(hdc), + uintptr(unsafe.Pointer(pfd)), + ) + return int(ret) +} + +func DescribePixelFormat(hdc HDC, iPixelFormat int, nBytes uint, pfd *PIXELFORMATDESCRIPTOR) int { + ret, _, _ := procDescribePixelFormat.Call( + uintptr(hdc), + uintptr(iPixelFormat), + uintptr(nBytes), + uintptr(unsafe.Pointer(pfd)), + ) + return int(ret) +} + +func GetEnhMetaFilePixelFormat(hemf HENHMETAFILE, cbBuffer uint32, pfd *PIXELFORMATDESCRIPTOR) uint { + ret, _, _ := procGetEnhMetaFilePixelFormat.Call( + uintptr(hemf), + uintptr(cbBuffer), + uintptr(unsafe.Pointer(pfd)), + ) + return uint(ret) +} + +func GetPixelFormat(hdc HDC) int { + ret, _, _ := procGetPixelFormat.Call( + uintptr(hdc), + ) + return int(ret) +} + +func SetPixelFormat(hdc HDC, iPixelFormat int, pfd *PIXELFORMATDESCRIPTOR) bool { + ret, _, _ := procSetPixelFormat.Call( + uintptr(hdc), + uintptr(iPixelFormat), + uintptr(unsafe.Pointer(pfd)), + ) + return ret == TRUE +} + +func SwapBuffers(hdc HDC) bool { + ret, _, _ := procSwapBuffers.Call(uintptr(hdc)) + return ret == TRUE +} diff --git a/v3/pkg/w32/gdiplus.go b/v3/pkg/w32/gdiplus.go new file mode 100644 index 000000000..2591ed71b --- /dev/null +++ b/v3/pkg/w32/gdiplus.go @@ -0,0 +1,177 @@ +//go:build windows + +/* + * Copyright (C) 2019 The Winc Authors. All Rights Reserved. + * Copyright (C) 2010-2012 The W32 Authors. All Rights Reserved. + */ +package w32 + +import ( + "errors" + "fmt" + "syscall" + "unsafe" +) + +const ( + Ok = 0 + GenericError = 1 + InvalidParameter = 2 + OutOfMemory = 3 + ObjectBusy = 4 + InsufficientBuffer = 5 + NotImplemented = 6 + Win32Error = 7 + WrongState = 8 + Aborted = 9 + FileNotFound = 10 + ValueOverflow = 11 + AccessDenied = 12 + UnknownImageFormat = 13 + FontFamilyNotFound = 14 + FontStyleNotFound = 15 + NotTrueTypeFont = 16 + UnsupportedGdiplusVersion = 17 + GdiplusNotInitialized = 18 + PropertyNotFound = 19 + PropertyNotSupported = 20 + ProfileNotFound = 21 +) + +func GetGpStatus(s int32) string { + switch s { + case Ok: + return "Ok" + case GenericError: + return "GenericError" + case InvalidParameter: + return "InvalidParameter" + case OutOfMemory: + return "OutOfMemory" + case ObjectBusy: + return "ObjectBusy" + case InsufficientBuffer: + return "InsufficientBuffer" + case NotImplemented: + return "NotImplemented" + case Win32Error: + return "Win32Error" + case WrongState: + return "WrongState" + case Aborted: + return "Aborted" + case FileNotFound: + return "FileNotFound" + case ValueOverflow: + return "ValueOverflow" + case AccessDenied: + return "AccessDenied" + case UnknownImageFormat: + return "UnknownImageFormat" + case FontFamilyNotFound: + return "FontFamilyNotFound" + case FontStyleNotFound: + return "FontStyleNotFound" + case NotTrueTypeFont: + return "NotTrueTypeFont" + case UnsupportedGdiplusVersion: + return "UnsupportedGdiplusVersion" + case GdiplusNotInitialized: + return "GdiplusNotInitialized" + case PropertyNotFound: + return "PropertyNotFound" + case PropertyNotSupported: + return "PropertyNotSupported" + case ProfileNotFound: + return "ProfileNotFound" + } + return "Unknown Status Value" +} + +var ( + token uintptr + + modgdiplus = syscall.NewLazyDLL("gdiplus.dll") + + procGdipCreateBitmapFromFile = modgdiplus.NewProc("GdipCreateBitmapFromFile") + procGdipCreateBitmapFromHBITMAP = modgdiplus.NewProc("GdipCreateBitmapFromHBITMAP") + procGdipCreateHBITMAPFromBitmap = modgdiplus.NewProc("GdipCreateHBITMAPFromBitmap") + procGdipCreateBitmapFromResource = modgdiplus.NewProc("GdipCreateBitmapFromResource") + procGdipCreateBitmapFromStream = modgdiplus.NewProc("GdipCreateBitmapFromStream") + procGdipDisposeImage = modgdiplus.NewProc("GdipDisposeImage") + procGdiplusShutdown = modgdiplus.NewProc("GdiplusShutdown") + procGdiplusStartup = modgdiplus.NewProc("GdiplusStartup") +) + +func GdipCreateBitmapFromFile(filename string) (*uintptr, error) { + var bitmap *uintptr + ret, _, _ := procGdipCreateBitmapFromFile.Call( + uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(filename))), + uintptr(unsafe.Pointer(&bitmap))) + + if ret != Ok { + return nil, errors.New(fmt.Sprintf("GdipCreateBitmapFromFile failed with status '%s' for file '%s'", GetGpStatus(int32(ret)), filename)) + } + + return bitmap, nil +} + +func GdipCreateBitmapFromResource(instance HINSTANCE, resId *uint16) (*uintptr, error) { + var bitmap *uintptr + ret, _, _ := procGdipCreateBitmapFromResource.Call( + uintptr(instance), + uintptr(unsafe.Pointer(resId)), + uintptr(unsafe.Pointer(&bitmap))) + + if ret != Ok { + return nil, errors.New(fmt.Sprintf("GdiCreateBitmapFromResource failed with status '%s'", GetGpStatus(int32(ret)))) + } + + return bitmap, nil +} + +func GdipCreateBitmapFromStream(stream *IStream) (*uintptr, error) { + var bitmap *uintptr + ret, _, _ := procGdipCreateBitmapFromStream.Call( + uintptr(unsafe.Pointer(stream)), + uintptr(unsafe.Pointer(&bitmap))) + + if ret != Ok { + return nil, errors.New(fmt.Sprintf("GdipCreateBitmapFromStream failed with status '%s'", GetGpStatus(int32(ret)))) + } + + return bitmap, nil +} + +func GdipCreateHBITMAPFromBitmap(bitmap *uintptr, background uint32) (HBITMAP, error) { + var hbitmap HBITMAP + ret, _, _ := procGdipCreateHBITMAPFromBitmap.Call( + uintptr(unsafe.Pointer(bitmap)), + uintptr(unsafe.Pointer(&hbitmap)), + uintptr(background)) + + if ret != Ok { + return 0, errors.New(fmt.Sprintf("GdipCreateHBITMAPFromBitmap failed with status '%s'", GetGpStatus(int32(ret)))) + } + + return hbitmap, nil +} + +func GdipDisposeImage(image *uintptr) { + procGdipDisposeImage.Call(uintptr(unsafe.Pointer(image))) +} + +func GdiplusShutdown() { + procGdiplusShutdown.Call(token) +} + +func GdiplusStartup(input *GdiplusStartupInput, output *GdiplusStartupOutput) { + ret, _, _ := procGdiplusStartup.Call( + uintptr(unsafe.Pointer(&token)), + uintptr(unsafe.Pointer(input)), + uintptr(unsafe.Pointer(output))) + + if ret != Ok { + panic("GdiplusStartup failed with status " + GetGpStatus(int32(ret))) + } +} diff --git a/v3/pkg/w32/guid.go b/v3/pkg/w32/guid.go new file mode 100644 index 000000000..b6e557f01 --- /dev/null +++ b/v3/pkg/w32/guid.go @@ -0,0 +1,225 @@ +//go:build windows + +package w32 + +// This code has been adapted from: https://github.com/go-ole/go-ole + +/* + +The MIT License (MIT) + +Copyright © 2013-2017 Yasuhiro Matsumoto, + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the “Software”), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +*/ + +const hextable = "0123456789ABCDEF" +const emptyGUID = "{00000000-0000-0000-0000-000000000000}" + +// GUID is Windows API specific GUID type. +// +// This exists to match Windows GUID type for direct passing for COM. +// Format is in xxxxxxxx-xxxx-xxxx-xxxxxxxxxxxxxxxx. +type GUID struct { + Data1 uint32 + Data2 uint16 + Data3 uint16 + Data4 [8]byte +} + +// NewGUID converts the given string into a globally unique identifier that is +// compliant with the Windows API. +// +// The supplied string may be in any of these formats: +// +// XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +// XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX +// {XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX} +// +// The conversion of the supplied string is not case-sensitive. +func NewGUID(guid string) *GUID { + d := []byte(guid) + var d1, d2, d3, d4a, d4b []byte + + switch len(d) { + case 38: + if d[0] != '{' || d[37] != '}' { + return nil + } + d = d[1:37] + fallthrough + case 36: + if d[8] != '-' || d[13] != '-' || d[18] != '-' || d[23] != '-' { + return nil + } + d1 = d[0:8] + d2 = d[9:13] + d3 = d[14:18] + d4a = d[19:23] + d4b = d[24:36] + case 32: + d1 = d[0:8] + d2 = d[8:12] + d3 = d[12:16] + d4a = d[16:20] + d4b = d[20:32] + default: + return nil + } + + var g GUID + var ok1, ok2, ok3, ok4 bool + g.Data1, ok1 = decodeHexUint32(d1) + g.Data2, ok2 = decodeHexUint16(d2) + g.Data3, ok3 = decodeHexUint16(d3) + g.Data4, ok4 = decodeHexByte64(d4a, d4b) + if ok1 && ok2 && ok3 && ok4 { + return &g + } + return nil +} + +func decodeHexUint32(src []byte) (value uint32, ok bool) { + var b1, b2, b3, b4 byte + var ok1, ok2, ok3, ok4 bool + b1, ok1 = decodeHexByte(src[0], src[1]) + b2, ok2 = decodeHexByte(src[2], src[3]) + b3, ok3 = decodeHexByte(src[4], src[5]) + b4, ok4 = decodeHexByte(src[6], src[7]) + value = (uint32(b1) << 24) | (uint32(b2) << 16) | (uint32(b3) << 8) | uint32(b4) + ok = ok1 && ok2 && ok3 && ok4 + return +} + +func decodeHexUint16(src []byte) (value uint16, ok bool) { + var b1, b2 byte + var ok1, ok2 bool + b1, ok1 = decodeHexByte(src[0], src[1]) + b2, ok2 = decodeHexByte(src[2], src[3]) + value = (uint16(b1) << 8) | uint16(b2) + ok = ok1 && ok2 + return +} + +func decodeHexByte64(s1 []byte, s2 []byte) (value [8]byte, ok bool) { + var ok1, ok2, ok3, ok4, ok5, ok6, ok7, ok8 bool + value[0], ok1 = decodeHexByte(s1[0], s1[1]) + value[1], ok2 = decodeHexByte(s1[2], s1[3]) + value[2], ok3 = decodeHexByte(s2[0], s2[1]) + value[3], ok4 = decodeHexByte(s2[2], s2[3]) + value[4], ok5 = decodeHexByte(s2[4], s2[5]) + value[5], ok6 = decodeHexByte(s2[6], s2[7]) + value[6], ok7 = decodeHexByte(s2[8], s2[9]) + value[7], ok8 = decodeHexByte(s2[10], s2[11]) + ok = ok1 && ok2 && ok3 && ok4 && ok5 && ok6 && ok7 && ok8 + return +} + +func decodeHexByte(c1, c2 byte) (value byte, ok bool) { + var n1, n2 byte + var ok1, ok2 bool + n1, ok1 = decodeHexChar(c1) + n2, ok2 = decodeHexChar(c2) + value = (n1 << 4) | n2 + ok = ok1 && ok2 + return +} + +func decodeHexChar(c byte) (byte, bool) { + switch { + case '0' <= c && c <= '9': + return c - '0', true + case 'a' <= c && c <= 'f': + return c - 'a' + 10, true + case 'A' <= c && c <= 'F': + return c - 'A' + 10, true + } + + return 0, false +} + +// String converts the GUID to string form. It will adhere to this pattern: +// +// {XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX} +// +// If the GUID is nil, the string representation of an empty GUID is returned: +// +// {00000000-0000-0000-0000-000000000000} +func (guid *GUID) String() string { + if guid == nil { + return emptyGUID + } + + var c [38]byte + c[0] = '{' + putUint32Hex(c[1:9], guid.Data1) + c[9] = '-' + putUint16Hex(c[10:14], guid.Data2) + c[14] = '-' + putUint16Hex(c[15:19], guid.Data3) + c[19] = '-' + putByteHex(c[20:24], guid.Data4[0:2]) + c[24] = '-' + putByteHex(c[25:37], guid.Data4[2:8]) + c[37] = '}' + return string(c[:]) +} + +func putUint32Hex(b []byte, v uint32) { + b[0] = hextable[byte(v>>24)>>4] + b[1] = hextable[byte(v>>24)&0x0f] + b[2] = hextable[byte(v>>16)>>4] + b[3] = hextable[byte(v>>16)&0x0f] + b[4] = hextable[byte(v>>8)>>4] + b[5] = hextable[byte(v>>8)&0x0f] + b[6] = hextable[byte(v)>>4] + b[7] = hextable[byte(v)&0x0f] +} + +func putUint16Hex(b []byte, v uint16) { + b[0] = hextable[byte(v>>8)>>4] + b[1] = hextable[byte(v>>8)&0x0f] + b[2] = hextable[byte(v)>>4] + b[3] = hextable[byte(v)&0x0f] +} + +func putByteHex(dst, src []byte) { + for i := 0; i < len(src); i++ { + dst[i*2] = hextable[src[i]>>4] + dst[i*2+1] = hextable[src[i]&0x0f] + } +} + +// IsEqualGUID compares two GUID. +// +// Not constant time comparison. +func IsEqualGUID(guid1 *GUID, guid2 *GUID) bool { + return guid1.Data1 == guid2.Data1 && + guid1.Data2 == guid2.Data2 && + guid1.Data3 == guid2.Data3 && + guid1.Data4[0] == guid2.Data4[0] && + guid1.Data4[1] == guid2.Data4[1] && + guid1.Data4[2] == guid2.Data4[2] && + guid1.Data4[3] == guid2.Data4[3] && + guid1.Data4[4] == guid2.Data4[4] && + guid1.Data4[5] == guid2.Data4[5] && + guid1.Data4[6] == guid2.Data4[6] && + guid1.Data4[7] == guid2.Data4[7] +} diff --git a/v3/pkg/w32/icon.go b/v3/pkg/w32/icon.go new file mode 100644 index 000000000..009479323 --- /dev/null +++ b/v3/pkg/w32/icon.go @@ -0,0 +1,137 @@ +//go:build windows + +package w32 + +import ( + "bytes" + "fmt" + "image" + "image/draw" + "image/png" + "unsafe" +) + +func CreateIconFromResourceEx(presbits uintptr, dwResSize uint32, isIcon bool, version uint32, cxDesired int, cyDesired int, flags uint) (uintptr, error) { + icon := 0 + if isIcon { + icon = 1 + } + r, _, err := procCreateIconFromResourceEx.Call( + presbits, + uintptr(dwResSize), + uintptr(icon), + uintptr(version), + uintptr(cxDesired), + uintptr(cyDesired), + uintptr(flags), + ) + + if r == 0 { + return 0, err + } + return r, nil +} + +func isPNG(fileData []byte) bool { + if len(fileData) < 4 { + return false + } + return string(fileData[:4]) == "\x89PNG" +} + +func isICO(fileData []byte) bool { + if len(fileData) < 4 { + return false + } + return string(fileData[:4]) == "\x00\x00\x01\x00" +} + +// CreateSmallHIconFromImage creates a HICON from a PNG or ICO file +func CreateSmallHIconFromImage(fileData []byte) (HICON, error) { + if len(fileData) < 8 { + return 0, fmt.Errorf("invalid file format") + } + + if !isPNG(fileData) && !isICO(fileData) { + return 0, fmt.Errorf("unsupported file format") + } + iconWidth := GetSystemMetrics(SM_CXSMICON) + iconHeight := GetSystemMetrics(SM_CYSMICON) + icon, err := CreateIconFromResourceEx( + uintptr(unsafe.Pointer(&fileData[0])), + uint32(len(fileData)), + true, + 0x00030000, + iconWidth, + iconHeight, + LR_DEFAULTSIZE) + return HICON(icon), err +} + +// CreateLargeHIconFromImage creates a HICON from a PNG or ICO file +func CreateLargeHIconFromImage(fileData []byte) (HICON, error) { + if len(fileData) < 8 { + return 0, fmt.Errorf("invalid file format") + } + + if !isPNG(fileData) && !isICO(fileData) { + return 0, fmt.Errorf("unsupported file format") + } + iconWidth := GetSystemMetrics(SM_CXICON) + iconHeight := GetSystemMetrics(SM_CXICON) + icon, err := CreateIconFromResourceEx( + uintptr(unsafe.Pointer(&fileData[0])), + uint32(len(fileData)), + true, + 0x00030000, + iconWidth, + iconHeight, + LR_DEFAULTSIZE) + return HICON(icon), err +} + +func SetWindowIcon(hwnd HWND, icon HICON) { + SendMessage(hwnd, WM_SETICON, ICON_SMALL, uintptr(icon)) +} + +func pngToImage(data []byte) (*image.RGBA, error) { + img, err := png.Decode(bytes.NewReader(data)) + if err != nil { + return nil, err + } + + bounds := img.Bounds() + rgba := image.NewRGBA(bounds) + draw.Draw(rgba, bounds, img, bounds.Min, draw.Src) + return rgba, nil +} + +func SetMenuIcons(parentMenu HMENU, itemID int, unchecked []byte, checked []byte) error { + if unchecked == nil { + return fmt.Errorf("invalid unchecked bitmap") + } + var err error + var uncheckedIcon, checkedIcon HBITMAP + var uncheckedImage, checkedImage *image.RGBA + uncheckedImage, err = pngToImage(unchecked) + if err != nil { + return err + } + uncheckedIcon, err = CreateHBITMAPFromImage(uncheckedImage) + if err != nil { + return err + } + if checked != nil { + checkedImage, err = pngToImage(checked) + if err != nil { + return err + } + checkedIcon, err = CreateHBITMAPFromImage(checkedImage) + if err != nil { + return err + } + } else { + checkedIcon = uncheckedIcon + } + return SetMenuItemBitmaps(parentMenu, uint32(itemID), MF_BYCOMMAND, checkedIcon, uncheckedIcon) +} diff --git a/v3/pkg/w32/idataobject.go b/v3/pkg/w32/idataobject.go new file mode 100644 index 000000000..cf56d0934 --- /dev/null +++ b/v3/pkg/w32/idataobject.go @@ -0,0 +1,168 @@ +//go:build windows + +package w32 + +import ( + "golang.org/x/sys/windows" + "syscall" + "unsafe" +) + +type IDataObjectVtbl struct { + IUnknownVtbl + GetData ComProc + GetDataHere ComProc + QueryGetData ComProc + GetCanonicalFormatEtc ComProc + SetData ComProc + EnumFormatEtc ComProc + DAdvise ComProc +} + +type IDataObject struct { + Vtbl *IDataObjectVtbl +} + +func (i *IDataObject) AddRef() uintptr { + refCounter, _, _ := i.Vtbl.AddRef.Call(uintptr(unsafe.Pointer(i))) + return refCounter +} + +func (i *IDataObject) GetData(formatEtc *FORMATETC, medium *STGMEDIUM) error { + hr, _, err := i.Vtbl.GetData.Call( + uintptr(unsafe.Pointer(i)), + uintptr(unsafe.Pointer(formatEtc)), + uintptr(unsafe.Pointer(medium)), + ) + if windows.Handle(hr) != windows.S_OK { + return syscall.Errno(hr) + } + return err +} + +func (i *IDataObject) GetDataHere(formatEtc *FORMATETC, medium *STGMEDIUM) error { + hr, _, err := i.Vtbl.GetDataHere.Call( + uintptr(unsafe.Pointer(i)), + uintptr(unsafe.Pointer(formatEtc)), + uintptr(unsafe.Pointer(medium)), + ) + if windows.Handle(hr) != windows.S_OK { + return syscall.Errno(hr) + } + return err +} + +func (i *IDataObject) QueryGetData(formatEtc *FORMATETC) error { + hr, _, err := i.Vtbl.QueryGetData.Call( + uintptr(unsafe.Pointer(i)), + uintptr(unsafe.Pointer(formatEtc)), + ) + if windows.Handle(hr) != windows.S_OK { + return syscall.Errno(hr) + } + return err +} + +func (i *IDataObject) GetCanonicalFormatEtc(inputFormatEtc *FORMATETC, outputFormatEtc *FORMATETC) error { + hr, _, err := i.Vtbl.GetCanonicalFormatEtc.Call( + uintptr(unsafe.Pointer(i)), + uintptr(unsafe.Pointer(inputFormatEtc)), + uintptr(unsafe.Pointer(outputFormatEtc)), + ) + if windows.Handle(hr) != windows.S_OK { + return syscall.Errno(hr) + } + return err +} + +func (i *IDataObject) SetData(formatEtc *FORMATETC, medium *STGMEDIUM, release bool) error { + hr, _, err := i.Vtbl.SetData.Call( + uintptr(unsafe.Pointer(i)), + uintptr(unsafe.Pointer(formatEtc)), + uintptr(unsafe.Pointer(medium)), + uintptr(BoolToBOOL(release)), + ) + if windows.Handle(hr) != windows.S_OK { + return syscall.Errno(hr) + } + return err +} + +func (i *IDataObject) EnumFormatEtc(dwDirection uint32, enumFormatEtc **IEnumFORMATETC) error { + hr, _, err := i.Vtbl.EnumFormatEtc.Call( + uintptr(unsafe.Pointer(i)), + uintptr(dwDirection), + uintptr(unsafe.Pointer(enumFormatEtc)), + ) + if windows.Handle(hr) != windows.S_OK { + return syscall.Errno(hr) + } + return err +} + +func (i *IDataObject) DAdvise(formatEtc *FORMATETC, advf uint32, adviseSink *IAdviseSink, pdwConnection *uint32) error { + hr, _, err := i.Vtbl.DAdvise.Call( + uintptr(unsafe.Pointer(i)), + uintptr(unsafe.Pointer(formatEtc)), + uintptr(advf), + uintptr(unsafe.Pointer(adviseSink)), + uintptr(unsafe.Pointer(pdwConnection)), + ) + if windows.Handle(hr) != windows.S_OK { + return syscall.Errno(hr) + } + return err +} + +type DVTargetDevice struct { + TdSize uint32 + TdDriverNameOffset uint16 + TdDeviceNameOffset uint16 + TdPortNameOffset uint16 + TdExtDevmodeOffset uint16 + TdData [1]byte +} + +type FORMATETC struct { + CfFormat uint16 + Ptd *DVTargetDevice + DwAspect uint32 + Lindex int32 + Tymed Tymed +} + +type Tymed uint32 + +const ( + TYMED_HGLOBAL Tymed = 1 + TYMED_FILE Tymed = 2 + TYMED_ISTREAM Tymed = 4 + TYMED_ISTORAGE Tymed = 8 + TYMED_GDI Tymed = 16 + TYMED_MFPICT Tymed = 32 + TYMED_ENHMF Tymed = 64 + TYMED_NULL Tymed = 0 +) + +type STGMEDIUM struct { + Tymed Tymed + Union uintptr + PUnkForRelease IUnknownImpl +} + +func (s STGMEDIUM) FileName() string { + if s.Tymed != TYMED_FILE { + return "" + } + return windows.UTF16PtrToString((*uint16)(unsafe.Pointer(s.Union))) +} + +func (s STGMEDIUM) Release() { + if s.PUnkForRelease != nil { + s.PUnkForRelease.Release() + } +} + +type IEnumFORMATETC struct{} +type IAdviseSink struct{} +type IEnumStatData struct{} diff --git a/v3/pkg/w32/idispatch.go b/v3/pkg/w32/idispatch.go new file mode 100644 index 000000000..4f610f3ff --- /dev/null +++ b/v3/pkg/w32/idispatch.go @@ -0,0 +1,45 @@ +//go:build windows + +/* + * Copyright (C) 2019 The Winc Authors. All Rights Reserved. + * Copyright (C) 2010-2012 The W32 Authors. All Rights Reserved. + */ +package w32 + +import ( + "unsafe" +) + +type pIDispatchVtbl struct { + pQueryInterface uintptr + pAddRef uintptr + pRelease uintptr + pGetTypeInfoCount uintptr + pGetTypeInfo uintptr + pGetIDsOfNames uintptr + pInvoke uintptr +} + +type IDispatch struct { + lpVtbl *pIDispatchVtbl +} + +func (this *IDispatch) QueryInterface(id *GUID) *IDispatch { + return ComQueryInterface((*IUnknown)(unsafe.Pointer(this)), id) +} + +func (this *IDispatch) AddRef() int32 { + return ComAddRef((*IUnknown)(unsafe.Pointer(this))) +} + +func (this *IDispatch) Release() int32 { + return ComRelease((*IUnknown)(unsafe.Pointer(this))) +} + +func (this *IDispatch) GetIDsOfName(names []string) []int32 { + return ComGetIDsOfName(this, names) +} + +func (this *IDispatch) Invoke(dispid int32, dispatch int16, params ...interface{}) *VARIANT { + return ComInvoke(this, dispid, dispatch, params...) +} diff --git a/v3/pkg/w32/idroptarget.go b/v3/pkg/w32/idroptarget.go new file mode 100644 index 000000000..4c54ba66c --- /dev/null +++ b/v3/pkg/w32/idroptarget.go @@ -0,0 +1,134 @@ +//go:build windows + +package w32 + +import ( + "github.com/wailsapp/go-webview2/pkg/combridge" + "golang.org/x/sys/windows" +) + +var ( + DROPEFFECT_NONE DWORD = 0 + DROPEFFECT_COPY DWORD = 1 + DROPEFFECT_MOVE DWORD = 2 + DROPEFFECT_LINK DWORD = 4 +) + +const ( + DRAGDROP_E_ALREADYREGISTERED = 0x80040101 + DRAGDROP_E_INVALIDHWND = 0x80040102 +) + +func _NOP(_ uintptr) uintptr { + return uintptr(windows.S_FALSE) +} + +func init() { + combridge.RegisterVTable[combridge.IUnknown, iDropTarget]( + "{00000122-0000-0000-C000-000000000046}", + _iDropTargetDragEnter, + _iDropTargetDragOver, + _iDropTargetDragLeave, + _iDropTargetDrop, + ) +} + +func _iDropTargetDragEnter(this uintptr, dataObject *IDataObject, grfKeyState DWORD, point POINT, pdfEffect *DWORD) uintptr { + return combridge.Resolve[iDropTarget](this).DragEnter(dataObject, grfKeyState, point, pdfEffect) +} + +func _iDropTargetDragOver(this uintptr, grfKeyState DWORD, point POINT, pdfEffect *DWORD) uintptr { + return combridge.Resolve[iDropTarget](this).DragOver(grfKeyState, point, pdfEffect) +} + +func _iDropTargetDragLeave(this uintptr) uintptr { + return combridge.Resolve[iDropTarget](this).DragLeave() +} + +func _iDropTargetDrop(this uintptr, dataObject *IDataObject, grfKeyState DWORD, point POINT, pdfEffect *DWORD) uintptr { + return combridge.Resolve[iDropTarget](this).Drop(dataObject, grfKeyState, point, pdfEffect) +} + +type iDropTarget interface { + combridge.IUnknown + + DragEnter(dataObject *IDataObject, grfKeyState DWORD, point POINT, pdfEffect *DWORD) uintptr + DragOver(grfKeyState DWORD, point POINT, pdfEffect *DWORD) uintptr + DragLeave() uintptr + Drop(dataObject *IDataObject, grfKeyState DWORD, point POINT, pdfEffect *DWORD) uintptr +} + +var _ iDropTarget = &DropTarget{} + +type DropTarget struct { + combridge.IUnknownImpl + OnEnterEffect DWORD + OnOverEffect DWORD + OnEnter func() + OnLeave func() + OnOver func() + OnDrop func(filenames []string) +} + +func NewDropTarget() *DropTarget { + result := &DropTarget{ + OnEnterEffect: DROPEFFECT_COPY, + OnOverEffect: DROPEFFECT_COPY, + } + return result +} + +func (d *DropTarget) DragEnter(dataObject *IDataObject, grfKeyState DWORD, point POINT, pdfEffect *DWORD) uintptr { + *pdfEffect = d.OnEnterEffect + if d.OnEnter != nil { + d.OnEnter() + } + return uintptr(windows.S_OK) +} + +func (d *DropTarget) DragOver(grfKeyState DWORD, point POINT, pdfEffect *DWORD) uintptr { + *pdfEffect = d.OnOverEffect + if d.OnOver != nil { + d.OnOver() + } + return uintptr(windows.S_OK) +} + +func (d *DropTarget) DragLeave() uintptr { + if d.OnLeave != nil { + d.OnLeave() + } + return uintptr(windows.S_OK) +} + +func (d *DropTarget) Drop(dataObject *IDataObject, grfKeyState DWORD, point POINT, pdfEffect *DWORD) uintptr { + + if d.OnDrop == nil { + return uintptr(windows.S_OK) + } + + // Extract filenames from dataObject + var filenames []string + var formatETC = FORMATETC{ + CfFormat: CF_HDROP, + Tymed: TYMED_HGLOBAL, + } + + var stgMedium STGMEDIUM + + err := dataObject.GetData(&formatETC, &stgMedium) + if err != nil && err != windows.ERROR_SUCCESS { + return uintptr(windows.S_FALSE) + } + defer stgMedium.Release() + hDrop := stgMedium.Union + _, numFiles := DragQueryFile(hDrop, 0xFFFFFFFF) + for i := uint(0); i < numFiles; i++ { + filename, _ := DragQueryFile(hDrop, i) + filenames = append(filenames, filename) + } + + d.OnDrop(filenames) + + return uintptr(windows.S_OK) +} diff --git a/v3/pkg/w32/image.go b/v3/pkg/w32/image.go new file mode 100644 index 000000000..1c7520f36 --- /dev/null +++ b/v3/pkg/w32/image.go @@ -0,0 +1,55 @@ +//go:build windows + +package w32 + +import ( + "image" + "syscall" + "unsafe" +) + +func CreateHBITMAPFromImage(img *image.RGBA) (HBITMAP, error) { + bounds := img.Bounds() + width, height := bounds.Dx(), bounds.Dy() + + // Create a BITMAPINFO structure for the DIB + bmi := BITMAPINFO{ + BmiHeader: BITMAPINFOHEADER{ + BiSize: uint32(unsafe.Sizeof(BITMAPINFOHEADER{})), + BiWidth: int32(width), + BiHeight: int32(-height), // negative to indicate top-down bitmap + BiPlanes: 1, + BiBitCount: 32, + BiCompression: BI_RGB, + BiSizeImage: uint32(width * height * 4), // RGBA = 4 bytes + }, + } + + // Create the DIB section + var bits unsafe.Pointer + + hbmp := CreateDIBSection(0, &bmi, DIB_RGB_COLORS, &bits, 0, 0) + if hbmp == 0 { + return 0, syscall.GetLastError() + } + + // Copy the pixel data from the Go image to the DIB section + for y := 0; y < height; y++ { + for x := 0; x < width; x++ { + i := img.PixOffset(x, y) + r := img.Pix[i+0] + g := img.Pix[i+1] + b := img.Pix[i+2] + a := img.Pix[i+3] + + // Write the RGBA pixel data to the DIB section (BGR order) + offset := y*width*4 + x*4 + *((*uint8)(unsafe.Pointer(uintptr(bits) + uintptr(offset) + 0))) = b + *((*uint8)(unsafe.Pointer(uintptr(bits) + uintptr(offset) + 1))) = g + *((*uint8)(unsafe.Pointer(uintptr(bits) + uintptr(offset) + 2))) = r + *((*uint8)(unsafe.Pointer(uintptr(bits) + uintptr(offset) + 3))) = a + } + } + + return hbmp, nil +} diff --git a/v3/pkg/w32/istream.go b/v3/pkg/w32/istream.go new file mode 100644 index 000000000..a47fbbce1 --- /dev/null +++ b/v3/pkg/w32/istream.go @@ -0,0 +1,33 @@ +//go:build windows + +/* + * Copyright (C) 2019 Tad Vizbaras. All Rights Reserved. + * Copyright (C) 2010-2012 The W32 Authors. All Rights Reserved. + */ +package w32 + +import ( + "unsafe" +) + +type pIStreamVtbl struct { + pQueryInterface uintptr + pAddRef uintptr + pRelease uintptr +} + +type IStream struct { + lpVtbl *pIStreamVtbl +} + +func (this *IStream) QueryInterface(id *GUID) *IDispatch { + return ComQueryInterface((*IUnknown)(unsafe.Pointer(this)), id) +} + +func (this *IStream) AddRef() int32 { + return ComAddRef((*IUnknown)(unsafe.Pointer(this))) +} + +func (this *IStream) Release() int32 { + return ComRelease((*IUnknown)(unsafe.Pointer(this))) +} diff --git a/v3/pkg/w32/kernel32.go b/v3/pkg/w32/kernel32.go new file mode 100644 index 000000000..affd497cd --- /dev/null +++ b/v3/pkg/w32/kernel32.go @@ -0,0 +1,337 @@ +//go:build windows + +/* + * Copyright (C) 2019 Tad Vizbaras. All Rights Reserved. + * Copyright (C) 2010-2012 The W32 Authors. All Rights Reserved. + */ +package w32 + +import ( + "syscall" + "unsafe" +) + +var ( + modkernel32 = syscall.NewLazyDLL("kernel32.dll") + + procGetModuleHandle = modkernel32.NewProc("GetModuleHandleW") + procMulDiv = modkernel32.NewProc("MulDiv") + procGetConsoleWindow = modkernel32.NewProc("GetConsoleWindow") + procGetCurrentThread = modkernel32.NewProc("GetCurrentThread") + procGetCurrentThreadId = modkernel32.NewProc("GetCurrentThreadId") + procGetLogicalDrives = modkernel32.NewProc("GetLogicalDrives") + procGetLogicalDriveStrings = modkernel32.NewProc("GetLogicalDriveStringsW") + procGetUserDefaultLCID = modkernel32.NewProc("GetUserDefaultLCID") + procLstrlen = modkernel32.NewProc("lstrlenW") + procLstrcpy = modkernel32.NewProc("lstrcpyW") + procGlobalAlloc = modkernel32.NewProc("GlobalAlloc") + procGlobalFree = modkernel32.NewProc("GlobalFree") + procGlobalLock = modkernel32.NewProc("GlobalLock") + procGlobalUnlock = modkernel32.NewProc("GlobalUnlock") + procMoveMemory = modkernel32.NewProc("RtlMoveMemory") + procFindResource = modkernel32.NewProc("FindResourceW") + procSizeofResource = modkernel32.NewProc("SizeofResource") + procLockResource = modkernel32.NewProc("LockResource") + procLoadResource = modkernel32.NewProc("LoadResource") + procGetLastError = modkernel32.NewProc("GetLastError") + procOpenProcess = modkernel32.NewProc("OpenProcess") + procTerminateProcess = modkernel32.NewProc("TerminateProcess") + procCloseHandle = modkernel32.NewProc("CloseHandle") + procCreateToolhelp32Snapshot = modkernel32.NewProc("CreateToolhelp32Snapshot") + procModule32First = modkernel32.NewProc("Module32FirstW") + procModule32Next = modkernel32.NewProc("Module32NextW") + procGetSystemTimes = modkernel32.NewProc("GetSystemTimes") + procGetConsoleScreenBufferInfo = modkernel32.NewProc("GetConsoleScreenBufferInfo") + procSetConsoleTextAttribute = modkernel32.NewProc("SetConsoleTextAttribute") + procGetDiskFreeSpaceEx = modkernel32.NewProc("GetDiskFreeSpaceExW") + procGetProcessTimes = modkernel32.NewProc("GetProcessTimes") + procSetSystemTime = modkernel32.NewProc("SetSystemTime") + procGetSystemTime = modkernel32.NewProc("GetSystemTime") +) + +func GetModuleHandle(modulename string) HINSTANCE { + var mn uintptr + if modulename == "" { + mn = 0 + } else { + mn = uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(modulename))) + } + ret, _, _ := procGetModuleHandle.Call(mn) + return HINSTANCE(ret) +} + +func GetApplicationHandle() HINSTANCE { + ret, _, _ := procGetModuleHandle.Call(0) + return ret +} + +func MulDiv(number, numerator, denominator int) int { + ret, _, _ := procMulDiv.Call( + uintptr(number), + uintptr(numerator), + uintptr(denominator)) + + return int(ret) +} + +func GetConsoleWindow() HWND { + ret, _, _ := procGetConsoleWindow.Call() + + return HWND(ret) +} + +func GetCurrentThread() HANDLE { + ret, _, _ := procGetCurrentThread.Call() + + return HANDLE(ret) +} + +func GetCurrentThreadId() HANDLE { + ret, _, _ := procGetCurrentThreadId.Call() + + return HANDLE(ret) +} + +func GetLogicalDrives() uint32 { + ret, _, _ := procGetLogicalDrives.Call() + + return uint32(ret) +} + +func GetUserDefaultLCID() uint32 { + ret, _, _ := procGetUserDefaultLCID.Call() + + return uint32(ret) +} + +func Lstrlen(lpString *uint16) int { + ret, _, _ := procLstrlen.Call(uintptr(unsafe.Pointer(lpString))) + + return int(ret) +} + +func Lstrcpy(buf []uint16, lpString *uint16) { + procLstrcpy.Call( + uintptr(unsafe.Pointer(&buf[0])), + uintptr(unsafe.Pointer(lpString))) +} + +func GlobalAlloc(uFlags uint, dwBytes uint32) HGLOBAL { + ret, _, _ := procGlobalAlloc.Call( + uintptr(uFlags), + uintptr(dwBytes)) + + if ret == 0 { + panic("GlobalAlloc failed") + } + + return HGLOBAL(ret) +} + +func GlobalFree(hMem HGLOBAL) { + ret, _, _ := procGlobalFree.Call(uintptr(hMem)) + + if ret != 0 { + panic("GlobalFree failed") + } +} + +func GlobalLock(hMem HGLOBAL) unsafe.Pointer { + ret, _, _ := procGlobalLock.Call(uintptr(hMem)) + + if ret == 0 { + panic("GlobalLock failed") + } + + return unsafe.Pointer(ret) +} + +func GlobalUnlock(hMem HGLOBAL) bool { + ret, _, _ := procGlobalUnlock.Call(uintptr(hMem)) + + return ret != 0 +} + +func MoveMemory(destination, source unsafe.Pointer, length uint32) { + procMoveMemory.Call( + uintptr(unsafe.Pointer(destination)), + uintptr(source), + uintptr(length)) +} + +func FindResource(hModule HMODULE, lpName, lpType *uint16) (HRSRC, error) { + ret, _, _ := procFindResource.Call( + uintptr(hModule), + uintptr(unsafe.Pointer(lpName)), + uintptr(unsafe.Pointer(lpType))) + + if ret == 0 { + return 0, syscall.GetLastError() + } + + return HRSRC(ret), nil +} + +func SizeofResource(hModule HMODULE, hResInfo HRSRC) uint32 { + ret, _, _ := procSizeofResource.Call( + uintptr(hModule), + uintptr(hResInfo)) + + if ret == 0 { + panic("SizeofResource failed") + } + + return uint32(ret) +} + +func LockResource(hResData HGLOBAL) unsafe.Pointer { + ret, _, _ := procLockResource.Call(uintptr(hResData)) + + if ret == 0 { + panic("LockResource failed") + } + + return unsafe.Pointer(ret) +} + +func LoadResource(hModule HMODULE, hResInfo HRSRC) HGLOBAL { + ret, _, _ := procLoadResource.Call( + uintptr(hModule), + uintptr(hResInfo)) + + if ret == 0 { + panic("LoadResource failed") + } + + return HGLOBAL(ret) +} + +func GetLastError() uint32 { + ret, _, _ := procGetLastError.Call() + return uint32(ret) +} + +func OpenProcess(desiredAccess uint32, inheritHandle bool, processId uint32) HANDLE { + inherit := 0 + if inheritHandle { + inherit = 1 + } + + ret, _, _ := procOpenProcess.Call( + uintptr(desiredAccess), + uintptr(inherit), + uintptr(processId)) + return HANDLE(ret) +} + +func TerminateProcess(hProcess HANDLE, uExitCode uint) bool { + ret, _, _ := procTerminateProcess.Call( + uintptr(hProcess), + uintptr(uExitCode)) + return ret != 0 +} + +func CloseHandle(object HANDLE) bool { + ret, _, _ := procCloseHandle.Call( + uintptr(object)) + return ret != 0 +} + +func CreateToolhelp32Snapshot(flags, processId uint32) HANDLE { + ret, _, _ := procCreateToolhelp32Snapshot.Call( + uintptr(flags), + uintptr(processId)) + + if ret <= 0 { + return HANDLE(0) + } + + return HANDLE(ret) +} + +func Module32First(snapshot HANDLE, me *MODULEENTRY32) bool { + ret, _, _ := procModule32First.Call( + uintptr(snapshot), + uintptr(unsafe.Pointer(me))) + + return ret != 0 +} + +func Module32Next(snapshot HANDLE, me *MODULEENTRY32) bool { + ret, _, _ := procModule32Next.Call( + uintptr(snapshot), + uintptr(unsafe.Pointer(me))) + + return ret != 0 +} + +func GetSystemTimes(lpIdleTime, lpKernelTime, lpUserTime *FILETIME) bool { + ret, _, _ := procGetSystemTimes.Call( + uintptr(unsafe.Pointer(lpIdleTime)), + uintptr(unsafe.Pointer(lpKernelTime)), + uintptr(unsafe.Pointer(lpUserTime))) + + return ret != 0 +} + +func GetProcessTimes(hProcess HANDLE, lpCreationTime, lpExitTime, lpKernelTime, lpUserTime *FILETIME) bool { + ret, _, _ := procGetProcessTimes.Call( + uintptr(hProcess), + uintptr(unsafe.Pointer(lpCreationTime)), + uintptr(unsafe.Pointer(lpExitTime)), + uintptr(unsafe.Pointer(lpKernelTime)), + uintptr(unsafe.Pointer(lpUserTime))) + + return ret != 0 +} + +func GetConsoleScreenBufferInfo(hConsoleOutput HANDLE) *CONSOLE_SCREEN_BUFFER_INFO { + var csbi CONSOLE_SCREEN_BUFFER_INFO + ret, _, _ := procGetConsoleScreenBufferInfo.Call( + uintptr(hConsoleOutput), + uintptr(unsafe.Pointer(&csbi))) + if ret == 0 { + return nil + } + return &csbi +} + +func SetConsoleTextAttribute(hConsoleOutput HANDLE, wAttributes uint16) bool { + ret, _, _ := procSetConsoleTextAttribute.Call( + uintptr(hConsoleOutput), + uintptr(wAttributes)) + return ret != 0 +} + +func GetDiskFreeSpaceEx(dirName string) (r bool, + freeBytesAvailable, totalNumberOfBytes, totalNumberOfFreeBytes uint64) { + ret, _, _ := procGetDiskFreeSpaceEx.Call( + uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(dirName))), + uintptr(unsafe.Pointer(&freeBytesAvailable)), + uintptr(unsafe.Pointer(&totalNumberOfBytes)), + uintptr(unsafe.Pointer(&totalNumberOfFreeBytes))) + return ret != 0, + freeBytesAvailable, totalNumberOfBytes, totalNumberOfFreeBytes +} + +func GetSystemTime() *SYSTEMTIME { + var time SYSTEMTIME + procGetSystemTime.Call( + uintptr(unsafe.Pointer(&time))) + return &time +} + +func SetSystemTime(time *SYSTEMTIME) bool { + ret, _, _ := procSetSystemTime.Call( + uintptr(unsafe.Pointer(time))) + return ret != 0 +} + +func GetLogicalDriveStrings(nBufferLength uint32, lpBuffer *uint16) uint32 { + ret, _, _ := procGetLogicalDriveStrings.Call( + uintptr(nBufferLength), + uintptr(unsafe.Pointer(lpBuffer)), + 0) + + return uint32(ret) +} diff --git a/v3/pkg/w32/ole32.go b/v3/pkg/w32/ole32.go new file mode 100644 index 000000000..8e7c20d4a --- /dev/null +++ b/v3/pkg/w32/ole32.go @@ -0,0 +1,97 @@ +//go:build windows + +/* + * Copyright (C) 2019 Tad Vizbaras. All Rights Reserved. + * Copyright (C) 2010-2012 The W32 Authors. All Rights Reserved. + */ +package w32 + +import ( + "github.com/wailsapp/go-webview2/pkg/combridge" + "syscall" + "unsafe" +) + +var ( + modole32 = syscall.NewLazyDLL("ole32.dll") + + procCoInitializeEx = modole32.NewProc("CoInitializeEx") + procCoInitialize = modole32.NewProc("CoInitialize") + procOleInitialize = modole32.NewProc("OleInitialize") + procCoUninitialize = modole32.NewProc("CoUninitialize") + procCreateStreamOnHGlobal = modole32.NewProc("CreateStreamOnHGlobal") + procRegisterDragDrop = modole32.NewProc("RegisterDragDrop") + procRevokeDragDrop = modole32.NewProc("RevokeDragDrop") +) + +func CoInitializeEx(coInit uintptr) HRESULT { + ret, _, _ := procCoInitializeEx.Call( + 0, + coInit) + + switch uint32(ret) { + case E_INVALIDARG: + panic("CoInitializeEx failed with E_INVALIDARG") + case E_OUTOFMEMORY: + panic("CoInitializeEx failed with E_OUTOFMEMORY") + case E_UNEXPECTED: + panic("CoInitializeEx failed with E_UNEXPECTED") + } + + return HRESULT(ret) +} + +func CoInitialize() { + procCoInitialize.Call(0) +} + +func CoUninitialize() { + procCoUninitialize.Call() +} + +func CreateStreamOnHGlobal(hGlobal HGLOBAL, fDeleteOnRelease bool) *IStream { + stream := new(IStream) + ret, _, _ := procCreateStreamOnHGlobal.Call( + uintptr(hGlobal), + uintptr(BoolToBOOL(fDeleteOnRelease)), + uintptr(unsafe.Pointer(&stream))) + + switch uint32(ret) { + case E_INVALIDARG: + panic("CreateStreamOnHGlobal failed with E_INVALIDARG") + case E_OUTOFMEMORY: + panic("CreateStreamOnHGlobal failed with E_OUTOFMEMORY") + case E_UNEXPECTED: + panic("CreateStreamOnHGlobal failed with E_UNEXPECTED") + } + + return stream +} +func OleInitialise() { + procOleInitialize.Call() +} + +func RegisterDragDrop(hwnd HWND, dropTarget *DropTarget) error { + + dt := combridge.New[iDropTarget](dropTarget) + hr, _, _ := procRegisterDragDrop.Call( + hwnd, + dt.Ref(), + ) + + if hr != S_OK { + return syscall.Errno(hr) + } + return nil +} + +func RevokeDragDrop(hwnd HWND) error { + hr, _, _ := procRevokeDragDrop.Call( + hwnd, + ) + + if hr != S_OK { + return syscall.Errno(hr) + } + return nil +} diff --git a/v3/pkg/w32/oleaut32.go b/v3/pkg/w32/oleaut32.go new file mode 100644 index 000000000..0bb8ef7da --- /dev/null +++ b/v3/pkg/w32/oleaut32.go @@ -0,0 +1,50 @@ +//go:build windows + +/* + * Copyright (C) 2019 Tad Vizbaras. All Rights Reserved. + * Copyright (C) 2010-2012 The W32 Authors. All Rights Reserved. + */ +package w32 + +import ( + "syscall" + "unsafe" +) + +var ( + modoleaut32 = syscall.NewLazyDLL("oleaut32") + + procVariantInit = modoleaut32.NewProc("VariantInit") + procSysAllocString = modoleaut32.NewProc("SysAllocString") + procSysFreeString = modoleaut32.NewProc("SysFreeString") + procSysStringLen = modoleaut32.NewProc("SysStringLen") + procCreateDispTypeInfo = modoleaut32.NewProc("CreateDispTypeInfo") + procCreateStdDispatch = modoleaut32.NewProc("CreateStdDispatch") +) + +func VariantInit(v *VARIANT) { + hr, _, _ := procVariantInit.Call(uintptr(unsafe.Pointer(v))) + if hr != 0 { + panic("Invoke VariantInit error.") + } + return +} + +func SysAllocString(v string) (ss *int16) { + pss, _, _ := procSysAllocString.Call(uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(v)))) + ss = (*int16)(unsafe.Pointer(pss)) + return +} + +func SysFreeString(v *int16) { + hr, _, _ := procSysFreeString.Call(uintptr(unsafe.Pointer(v))) + if hr != 0 { + panic("Invoke SysFreeString error.") + } + return +} + +func SysStringLen(v *int16) uint { + l, _, _ := procSysStringLen.Call(uintptr(unsafe.Pointer(v))) + return uint(l) +} diff --git a/v3/pkg/w32/popupmenu.go b/v3/pkg/w32/popupmenu.go new file mode 100644 index 000000000..9604cc92d --- /dev/null +++ b/v3/pkg/w32/popupmenu.go @@ -0,0 +1,96 @@ +//go:build windows + +package w32 + +type Menu HMENU +type PopupMenu Menu + +func (m Menu) Destroy() bool { + ret, _, _ := procDestroyMenu.Call(uintptr(m)) + return ret != 0 +} + +func (p PopupMenu) Destroy() bool { + return Menu(p).Destroy() +} + +func (p PopupMenu) Track(hwnd HWND, flags uint32, x, y int32) bool { + return TrackPopupMenuEx( + HMENU(p), + flags, + x, + y, + hwnd, + nil) +} + +func RemoveMenu(m HMENU, pos, flags int) bool { + ret, _, _ := procRemoveMenu.Call( + uintptr(m), + uintptr(pos), + uintptr(flags)) + return ret != 0 +} + +func (p PopupMenu) Append(flags uint32, id uintptr, text string) bool { + return Menu(p).Append(flags, id, text) +} + +func (m Menu) Append(flags uint32, id uintptr, text string) bool { + return AppendMenu(HMENU(m), flags, id, MustStringToUTF16Ptr(text)) +} + +func (p PopupMenu) Check(id uintptr, checked bool) bool { + return Menu(p).Check(id, checked) +} + +func (m Menu) Check(id uintptr, check bool) bool { + var checkState uint = MF_UNCHECKED + if check { + checkState = MF_CHECKED + } + return CheckMenuItem(HMENU(m), id, checkState) != 0 +} + +func CheckRadio(m HMENU, startID int, endID int, selectedID int) bool { + ret, _, _ := procCheckMenuRadioItem.Call( + m, + uintptr(startID), + uintptr(endID), + uintptr(selectedID), + MF_BYCOMMAND) + return ret != 0 +} + +func (m Menu) CheckRadio(startID int, endID int, selectedID int) bool { + ret, _, _ := procCheckMenuRadioItem.Call( + uintptr(m), + uintptr(startID), + uintptr(endID), + uintptr(selectedID), + MF_BYCOMMAND) + return ret != 0 +} + +func CheckMenuItem(menu HMENU, id uintptr, flags uint) uint { + ret, _, _ := procCheckMenuItem.Call( + menu, + id, + uintptr(flags), + ) + return uint(ret) +} + +func (p PopupMenu) CheckRadio(startID, endID, selectedID int) bool { + return Menu(p).CheckRadio(startID, endID, selectedID) +} + +func NewMenu() HMENU { + ret, _, _ := procCreateMenu.Call() + return HMENU(ret) +} + +func NewPopupMenu() HMENU { + ret, _, _ := procCreatePopupMenu.Call() + return ret +} diff --git a/v3/pkg/w32/screen.go b/v3/pkg/w32/screen.go new file mode 100644 index 000000000..0b935e3b0 --- /dev/null +++ b/v3/pkg/w32/screen.go @@ -0,0 +1,153 @@ +//go:build windows + +package w32 + +import ( + "fmt" + "syscall" + "unsafe" +) + +type Screen struct { + MONITORINFOEX + Name string + IsPrimary bool + IsCurrent bool + Scale float32 + Rotation float32 +} + +type DISPLAY_DEVICE struct { + cb uint32 + DeviceName [32]uint16 + DeviceString [128]uint16 + StateFlags uint32 + DeviceID [128]uint16 + DeviceKey [128]uint16 +} + +func getMonitorName(deviceName string) (string, error) { + var device DISPLAY_DEVICE + device.cb = uint32(unsafe.Sizeof(device)) + i := uint32(0) + for { + res, _, _ := procEnumDisplayDevices.Call(uintptr(unsafe.Pointer(MustStringToUTF16Ptr(deviceName))), uintptr(i), uintptr(unsafe.Pointer(&device)), 0) + if res == 0 { + break + } + if device.StateFlags&0x1 != 0 { + return syscall.UTF16ToString(device.DeviceString[:]), nil + } + } + + return "", fmt.Errorf("monitor name not found for device: %s", deviceName) +} + +// I'm not convinced this works properly +func GetRotationForMonitor(displayName [32]uint16) (float32, error) { + var devMode DEVMODE + devMode.DmSize = uint16(unsafe.Sizeof(devMode)) + resp, _, _ := procEnumDisplaySettings.Call(uintptr(unsafe.Pointer(&displayName[0])), ENUM_CURRENT_SETTINGS, uintptr(unsafe.Pointer(&devMode))) + if resp == 0 { + return 0, fmt.Errorf("EnumDisplaySettings failed") + } + + if (devMode.DmFields & DM_DISPLAYORIENTATION) == 0 { + return 0, fmt.Errorf("DM_DISPLAYORIENTATION not set") + } + + switch devMode.DmOrientation { + case DMDO_DEFAULT: + return 0, nil + case DMDO_90: + return 90, nil + case DMDO_180: + return 180, nil + case DMDO_270: + return 270, nil + } + + return -1, nil +} + +func GetAllScreens() ([]*Screen, error) { + var monitorList []MONITORINFOEX + + enumFunc := func(hMonitor uintptr, hdc uintptr, lprcMonitor *RECT, lParam uintptr) uintptr { + monitor := MONITORINFOEX{ + MONITORINFO: MONITORINFO{ + CbSize: uint32(unsafe.Sizeof(MONITORINFOEX{})), + }, + SzDevice: [32]uint16{}, + } + ret, _, _ := procGetMonitorInfo.Call(hMonitor, uintptr(unsafe.Pointer(&monitor))) + if ret == 0 { + return 1 // Continue enumeration + } + + monitorList = append(monitorList, monitor) + return 1 // Continue enumeration + } + + ret, _, _ := procEnumDisplayMonitors.Call(0, 0, syscall.NewCallback(enumFunc), 0) + if ret == 0 { + return nil, fmt.Errorf("EnumDisplayMonitors failed") + } + + // Get the active screen + var pt POINT + ret, _, _ = procGetCursorPos.Call(uintptr(unsafe.Pointer(&pt))) + if ret == 0 { + return nil, fmt.Errorf("GetCursorPos failed") + } + + hMonitor, _, _ := procMonitorFromPoint.Call(uintptr(unsafe.Pointer(&pt)), MONITOR_DEFAULTTONEAREST) + if hMonitor == 0 { + return nil, fmt.Errorf("MonitorFromPoint failed") + } + + var monitorInfo MONITORINFO + monitorInfo.CbSize = uint32(unsafe.Sizeof(monitorInfo)) + ret, _, _ = procGetMonitorInfo.Call(hMonitor, uintptr(unsafe.Pointer(&monitorInfo))) + if ret == 0 { + return nil, fmt.Errorf("GetMonitorInfo failed") + } + + var result []*Screen + + // Iterate through the screens and set the active one + for _, monitor := range monitorList { + thisContainer := &Screen{ + MONITORINFOEX: monitor, + } + thisContainer.IsCurrent = equalRect(monitor.RcMonitor, monitorInfo.RcMonitor) + thisContainer.IsPrimary = monitor.DwFlags == MONITORINFOF_PRIMARY + name, err := getMonitorName(syscall.UTF16ToString(monitor.SzDevice[:])) + if err != nil { + name = "" + } + // Get DPI for monitor + var dpiX, dpiY uint + ret = GetDPIForMonitor(hMonitor, MDT_EFFECTIVE_DPI, &dpiX, &dpiY) + if ret != S_OK { + return nil, fmt.Errorf("GetDpiForMonitor failed") + } + // Convert to float32 + thisContainer.Scale = float32(dpiX) / 96.0 + + // Get rotation of monitor + rot, err := GetRotationForMonitor(monitor.SzDevice) + if err != nil { + rot = 0 + } + thisContainer.Rotation = rot + thisContainer.Name = name + result = append(result, thisContainer) + } + + return result, nil +} + +func equalRect(a RECT, b RECT) bool { + return a.Left == b.Left && a.Top == b.Top && a.Right == b.Right && a.Bottom == b.Bottom +} diff --git a/v3/pkg/w32/shcore.go b/v3/pkg/w32/shcore.go new file mode 100644 index 000000000..39e72b917 --- /dev/null +++ b/v3/pkg/w32/shcore.go @@ -0,0 +1,39 @@ +//go:build windows + +package w32 + +import ( + "syscall" + "unsafe" +) + +var ( + modshcore = syscall.NewLazyDLL("shcore.dll") + + procGetDpiForMonitor = modshcore.NewProc("GetDpiForMonitor") +) + +func HasGetDPIForMonitorFunc() bool { + err := procGetDpiForMonitor.Find() + return err == nil +} + +func GetDPIForMonitor(hmonitor HMONITOR, dpiType MONITOR_DPI_TYPE, dpiX *UINT, dpiY *UINT) uintptr { + ret, _, _ := procGetDpiForMonitor.Call( + hmonitor, + uintptr(dpiType), + uintptr(unsafe.Pointer(dpiX)), + uintptr(unsafe.Pointer(dpiY))) + + return ret +} + +func GetNotificationFlyoutBounds() (*RECT, error) { + var rect RECT + res, _, err := procSystemParametersInfo.Call(SPI_GETNOTIFYWINDOWRECT, 0, uintptr(unsafe.Pointer(&rect)), 0) + if res == 0 { + _ = err + return nil, err + } + return &rect, nil +} diff --git a/v3/pkg/w32/shell32.go b/v3/pkg/w32/shell32.go new file mode 100644 index 000000000..33cb72bc5 --- /dev/null +++ b/v3/pkg/w32/shell32.go @@ -0,0 +1,415 @@ +//go:build windows + +/* + * Copyright (C) 2019 Tad Vizbaras. All Rights Reserved. + * Copyright (C) 2010-2012 The W32 Authors. All Rights Reserved. + */ +package w32 + +import ( + "errors" + "fmt" + "golang.org/x/sys/windows" + "syscall" + "unsafe" +) + +type CSIDL uint32 + +const ( + CSIDL_DESKTOP = 0x00 + CSIDL_INTERNET = 0x01 + CSIDL_PROGRAMS = 0x02 + CSIDL_CONTROLS = 0x03 + CSIDL_PRINTERS = 0x04 + CSIDL_PERSONAL = 0x05 + CSIDL_FAVORITES = 0x06 + CSIDL_STARTUP = 0x07 + CSIDL_RECENT = 0x08 + CSIDL_SENDTO = 0x09 + CSIDL_BITBUCKET = 0x0A + CSIDL_STARTMENU = 0x0B + CSIDL_MYDOCUMENTS = 0x0C + CSIDL_MYMUSIC = 0x0D + CSIDL_MYVIDEO = 0x0E + CSIDL_DESKTOPDIRECTORY = 0x10 + CSIDL_DRIVES = 0x11 + CSIDL_NETWORK = 0x12 + CSIDL_NETHOOD = 0x13 + CSIDL_FONTS = 0x14 + CSIDL_TEMPLATES = 0x15 + CSIDL_COMMON_STARTMENU = 0x16 + CSIDL_COMMON_PROGRAMS = 0x17 + CSIDL_COMMON_STARTUP = 0x18 + CSIDL_COMMON_DESKTOPDIRECTORY = 0x19 + CSIDL_APPDATA = 0x1A + CSIDL_PRINTHOOD = 0x1B + CSIDL_LOCAL_APPDATA = 0x1C + CSIDL_ALTSTARTUP = 0x1D + CSIDL_COMMON_ALTSTARTUP = 0x1E + CSIDL_COMMON_FAVORITES = 0x1F + CSIDL_INTERNET_CACHE = 0x20 + CSIDL_COOKIES = 0x21 + CSIDL_HISTORY = 0x22 + CSIDL_COMMON_APPDATA = 0x23 + CSIDL_WINDOWS = 0x24 + CSIDL_SYSTEM = 0x25 + CSIDL_PROGRAM_FILES = 0x26 + CSIDL_MYPICTURES = 0x27 + CSIDL_PROFILE = 0x28 + CSIDL_SYSTEMX86 = 0x29 + CSIDL_PROGRAM_FILESX86 = 0x2A + CSIDL_PROGRAM_FILES_COMMON = 0x2B + CSIDL_PROGRAM_FILES_COMMONX86 = 0x2C + CSIDL_COMMON_TEMPLATES = 0x2D + CSIDL_COMMON_DOCUMENTS = 0x2E + CSIDL_COMMON_ADMINTOOLS = 0x2F + CSIDL_ADMINTOOLS = 0x30 + CSIDL_CONNECTIONS = 0x31 + CSIDL_COMMON_MUSIC = 0x35 + CSIDL_COMMON_PICTURES = 0x36 + CSIDL_COMMON_VIDEO = 0x37 + CSIDL_RESOURCES = 0x38 + CSIDL_RESOURCES_LOCALIZED = 0x39 + CSIDL_COMMON_OEM_LINKS = 0x3A + CSIDL_CDBURN_AREA = 0x3B + CSIDL_COMPUTERSNEARME = 0x3D + CSIDL_FLAG_CREATE = 0x8000 + CSIDL_FLAG_DONT_VERIFY = 0x4000 + CSIDL_FLAG_NO_ALIAS = 0x1000 + CSIDL_FLAG_PER_USER_INIT = 0x8000 + CSIDL_FLAG_MASK = 0xFF00 + + NOTIFYICON_VERSION = 4 +) + +var ( + FOLDERID_AccountPictures = NewGUID("{008CA0B1-55B4-4C56-B8A8-4DE4B299D3BE}") + FOLDERID_AddNewPrograms = NewGUID("{DE61D971-5EBC-4F02-A3A9-6C82895E5C04}") + FOLDERID_AdminTools = NewGUID("{724EF170-A42D-4FEF-9F26-B60E846FBA4F}") + FOLDERID_ApplicationShortcuts = NewGUID("{A3918781-E5F2-4890-B3D9-A7E54332328C}") + FOLDERID_AppsFolder = NewGUID("{1E87508D-89C2-42F0-8A7E-645A0F50CA58}") + FOLDERID_AppUpdates = NewGUID("{A305CE99-F527-492B-8B1A-7E76FA98D6E4}") + FOLDERID_CDBurning = NewGUID("{9E52AB10-F80D-49DF-ACB8-4330F5687855}") + FOLDERID_ChangeRemovePrograms = NewGUID("{DF7266AC-9274-4867-8D55-3BD661DE872D}") + FOLDERID_CommonAdminTools = NewGUID("{D0384E7D-BAC3-4797-8F14-CBA229B392B5}") + FOLDERID_CommonOEMLinks = NewGUID("{C1BAE2D0-10DF-4334-BEDD-7AA20B227A9D}") + FOLDERID_CommonPrograms = NewGUID("{0139D44E-6AFE-49F2-8690-3DAFCAE6FFB8}") + FOLDERID_CommonStartMenu = NewGUID("{A4115719-D62E-491D-AA7C-E74B8BE3B067}") + FOLDERID_CommonStartup = NewGUID("{82A5EA35-D9CD-47C5-9629-E15D2F714E6E}") + FOLDERID_CommonTemplates = NewGUID("{B94237E7-57AC-4347-9151-B08C6C32D1F7}") + FOLDERID_ComputerFolder = NewGUID("{0AC0837C-BBF8-452A-850D-79D08E667CA7}") + FOLDERID_ConflictFolder = NewGUID("{4BFEFB45-347D-4006-A5BE-AC0CB0567192}") + FOLDERID_ConnectionsFolder = NewGUID("{6F0CD92B-2E97-45D1-88FF-B0D186B8DEDD}") + FOLDERID_Contacts = NewGUID("{56784854-C6CB-462B-8169-88E350ACB882}") + FOLDERID_ControlPanelFolder = NewGUID("{82A74AEB-AEB4-465C-A014-D097EE346D63}") + FOLDERID_Cookies = NewGUID("{2B0F765D-C0E9-4171-908E-08A611B84FF6}") + FOLDERID_Desktop = NewGUID("{B4BFCC3A-DB2C-424C-B029-7FE99A87C641}") + FOLDERID_DeviceMetadataStore = NewGUID("{5CE4A5E9-E4EB-479D-B89F-130C02886155}") + FOLDERID_Documents = NewGUID("{FDD39AD0-238F-46AF-ADB4-6C85480369C7}") + FOLDERID_DocumentsLibrary = NewGUID("{7B0DB17D-9CD2-4A93-9733-46CC89022E7C}") + FOLDERID_Downloads = NewGUID("{374DE290-123F-4565-9164-39C4925E467B}") + FOLDERID_Favorites = NewGUID("{1777F761-68AD-4D8A-87BD-30B759FA33DD}") + FOLDERID_Fonts = NewGUID("{FD228CB7-AE11-4AE3-864C-16F3910AB8FE}") + FOLDERID_Games = NewGUID("{CAC52C1A-B53D-4EDC-92D7-6B2E8AC19434}") + FOLDERID_GameTasks = NewGUID("{054FAE61-4DD8-4787-80B6-090220C4B700}") + FOLDERID_History = NewGUID("{D9DC8A3B-B784-432E-A781-5A1130A75963}") + FOLDERID_HomeGroup = NewGUID("{52528A6B-B9E3-4ADD-B60D-588C2DBA842D}") + FOLDERID_HomeGroupCurrentUser = NewGUID("{9B74B6A3-0DFD-4F11-9E78-5F7800F2E772}") + FOLDERID_ImplicitAppShortcuts = NewGUID("{BCB5256F-79F6-4CEE-B725-DC34E402FD46}") + FOLDERID_InternetCache = NewGUID("{352481E8-33BE-4251-BA85-6007CAEDCF9D}") + FOLDERID_InternetFolder = NewGUID("{4D9F7874-4E0C-4904-967B-40B0D20C3E4B}") + FOLDERID_Libraries = NewGUID("{1B3EA5DC-B587-4786-B4EF-BD1DC332AEAE}") + FOLDERID_Links = NewGUID("{BFB9D5E0-C6A9-404C-B2B2-AE6DB6AF4968}") + FOLDERID_LocalAppData = NewGUID("{F1B32785-6FBA-4FCF-9D55-7B8E7F157091}") + FOLDERID_LocalAppDataLow = NewGUID("{A520A1A4-1780-4FF6-BD18-167343C5AF16}") + FOLDERID_LocalizedResourcesDir = NewGUID("{2A00375E-224C-49DE-B8D1-440DF7EF3DDC}") + FOLDERID_Music = NewGUID("{4BD8D571-6D19-48D3-BE97-422220080E43}") + FOLDERID_MusicLibrary = NewGUID("{2112AB0A-C86A-4FFE-A368-0DE96E47012E}") + FOLDERID_NetHood = NewGUID("{C5ABBF53-E17F-4121-8900-86626FC2C973}") + FOLDERID_NetworkFolder = NewGUID("{D20BEEC4-5CA8-4905-AE3B-BF251EA09B53}") + FOLDERID_OriginalImages = NewGUID("{2C36C0AA-5812-4B87-BFD0-4CD0DFB19B39}") + FOLDERID_PhotoAlbums = NewGUID("{69D2CF90-FC33-4FB7-9A0C-EBB0F0FCB43C}") + FOLDERID_Pictures = NewGUID("{33E28130-4E1E-4676-835A-98395C3BC3BB}") + FOLDERID_PicturesLibrary = NewGUID("{A990AE9F-A03B-4E80-94BC-9912D7504104}") + FOLDERID_Playlists = NewGUID("{DE92C1C7-837F-4F69-A3BB-86E631204A23}") + FOLDERID_PrintersFolder = NewGUID("{76FC4E2D-D6AD-4519-A663-37BD56068185}") + FOLDERID_PrintHood = NewGUID("{9274BD8D-CFD1-41C3-B35E-B13F55A758F4}") + FOLDERID_Profile = NewGUID("{5E6C858F-0E22-4760-9AFE-EA3317B67173}") + FOLDERID_ProgramData = NewGUID("{62AB5D82-FDC1-4DC3-A9DD-070D1D495D97}") + FOLDERID_ProgramFiles = NewGUID("{905E63B6-C1BF-494E-B29C-65B732D3D21A}") + FOLDERID_ProgramFilesCommon = NewGUID("{F7F1ED05-9F6D-47A2-AAAE-29D317C6F066}") + FOLDERID_ProgramFilesCommonX64 = NewGUID("{6365D5A7-0F0D-45E5-87F6-0DA56B6A4F7D}") + FOLDERID_ProgramFilesCommonX86 = NewGUID("{DE974D24-D9C6-4D3E-BF91-F4455120B917}") + FOLDERID_ProgramFilesX64 = NewGUID("{6D809377-6AF0-444B-8957-A3773F02200E}") + FOLDERID_ProgramFilesX86 = NewGUID("{7C5A40EF-A0FB-4BFC-874A-C0F2E0B9FA8E}") + FOLDERID_Programs = NewGUID("{A77F5D77-2E2B-44C3-A6A2-ABA601054A51}") + FOLDERID_Public = NewGUID("{DFDF76A2-C82A-4D63-906A-5644AC457385}") + FOLDERID_PublicDesktop = NewGUID("{C4AA340D-F20F-4863-AFEF-1F769F2BE730}") + FOLDERID_PublicDocuments = NewGUID("{ED4824AF-DCE4-45A8-81E2-FC7965083634}") + FOLDERID_PublicDownloads = NewGUID("{3D644C9B-1FB8-4F30-9B45-F670235F79C0}") + FOLDERID_PublicGameTasks = NewGUID("{DEBF2536-E1A8-4C59-B6A2-414586476AEA}") + FOLDERID_PublicLibraries = NewGUID("{48DAF80B-E6CF-4F4E-B800-0E69D84EE384}") + FOLDERID_PublicMusic = NewGUID("{3214FAB5-9757-4298-BB61-92A9DEAA44FF}") + FOLDERID_PublicPictures = NewGUID("{B6EBFB86-6907-413C-9AF7-4FC2ABF07CC5}") + FOLDERID_PublicRingtones = NewGUID("{E555AB60-153B-4D17-9F04-A5FE99FC15EC}") + FOLDERID_PublicUserTiles = NewGUID("{0482af6c-08f1-4c34-8c90-e17ec98b1e17}") + FOLDERID_PublicVideos = NewGUID("{2400183A-6185-49FB-A2D8-4A392A602BA3}") + FOLDERID_QuickLaunch = NewGUID("{52a4f021-7b75-48a9-9f6b-4b87a210bc8f}") + FOLDERID_Recent = NewGUID("{AE50C081-EBD2-438A-8655-8A092E34987A}") + FOLDERID_RecordedTVLibrary = NewGUID("{1A6FDBA2-F42D-4358-A798-B74D745926C5}") + FOLDERID_RecycleBinFolder = NewGUID("{B7534046-3ECB-4C18-BE4E-64CD4CB7D6AC}") + FOLDERID_ResourceDir = NewGUID("{8AD10C31-2ADB-4296-A8F7-E4701232C972}") + FOLDERID_Ringtones = NewGUID("{C870044B-F49E-4126-A9C3-B52A1FF411E8}") + FOLDERID_RoamingAppData = NewGUID("{3EB685DB-65F9-4CF6-A03A-E3EF65729F3D}") + FOLDERID_RoamingTiles = NewGUID("{AAA8D5A5-F1D6-4259-BAA8-78E7EF60835E}") + FOLDERID_SampleMusic = NewGUID("{B250C668-F57D-4EE1-A63C-290EE7D1AA1F}") + FOLDERID_SamplePictures = NewGUID("{C4900540-2379-4C75-844B-64E6FAF8716B}") + FOLDERID_SamplePlaylists = NewGUID("{15CA69B3-30EE-49C1-ACE1-6B5EC372AFB5}") + FOLDERID_SampleVideos = NewGUID("{859EAD94-2E85-48AD-A71A-0969CB56A6CD}") + FOLDERID_SavedGames = NewGUID("{4C5C32FF-BB9D-43B0-B5B4-2D72E54EAAA4}") + FOLDERID_SavedPictures = NewGUID("{3B193882-D3AD-4EAB-965A-69829D1FB59F}") + FOLDERID_SavedPicturesLibrary = NewGUID("{E25B5812-BE88-4BD9-94B0-29233477B6C3}") + FOLDERID_SavedSearches = NewGUID("{7D1D3A04-DEBB-4115-95CF-2F29DA2920DA}") + FOLDERID_SEARCH_CSC = NewGUID("{ee32e446-31ca-4aba-814f-a5ebd2fd6d5e}") + FOLDERID_SEARCH_MAPI = NewGUID("{98ec0e18-2098-4d44-8644-66979315a281}") + FOLDERID_SearchHome = NewGUID("{190337d1-b8ca-4121-a639-6d472d16972a}") + FOLDERID_SendTo = NewGUID("{8983036C-27C0-404B-8F08-102D10DCFD74}") + FOLDERID_SidebarDefaultParts = NewGUID("{7B396E54-9EC5-4300-BE0A-2482EBAE1A26}") + FOLDERID_SidebarParts = NewGUID("{A75D362E-50FC-4fb7-AC2C-A8BEAA314493}") + FOLDERID_SkyDrive = NewGUID("{A52BBA46-E9E1-435f-B3D9-28DAA648C0F6}") + FOLDERID_SkyDriveCameraRoll = NewGUID("{767E6811-49CB-4273-87C2-20F355E1085B}") + FOLDERID_SkyDriveDocuments = NewGUID("{24D89E24-2F19-4534-9DDE-6A6671FBB8FE}") + FOLDERID_SkyDriveMusic = NewGUID("{C3F2459E-80D6-45DC-BFEF-1F769F2BE730}") + FOLDERID_SkyDrivePictures = NewGUID("{339719B5-8C47-4894-94C2-D8F77ADD44A6}") + FOLDERID_StartMenu = NewGUID("{625B53C3-AB48-4EC1-BA1F-A1EF4146FC19}") + FOLDERID_Startup = NewGUID("{B97D20BB-F46A-4C97-BA10-5E3608430854}") + FOLDERID_SyncManagerFolder = NewGUID("{43668BF8-C14E-49B2-97C9-747784D784B7}") + FOLDERID_SyncResultsFolder = NewGUID("{289a9a43-be44-4057-a41b-587a76d7e7f9}") + FOLDERID_SyncSetupFolder = NewGUID("{0F214138-B1D3-4a90-BBA9-27CBC0C5389A}") + FOLDERID_System = NewGUID("{1AC14E77-02E7-4E5D-B744-2EB1AE5198B7}") + FOLDERID_SystemX86 = NewGUID("{D65231B0-B2F1-4857-A4CE-A8E7C6EA7D27}") + FOLDERID_Templates = NewGUID("{A63293E8-664E-48DB-A079-DF759E0509F7}") + FOLDERID_UserPinned = NewGUID("{9E3995AB-1F9C-4F13-B827-48B24B6C7174}") + FOLDERID_UserProfiles = NewGUID("{0762D272-C50A-4BB0-A382-697DCD729B80}") + FOLDERID_UserProgramFiles = NewGUID("{5CD7AEE2-2219-4A67-B85D-6C9CE15660CB}") + FOLDERID_UserProgramFilesCommon = NewGUID("{BCBD3057-CA5C-4622-B42D-BC56DB0AE516}") + FOLDERID_UsersFiles = NewGUID("{F3CE0F7C-4901-4ACC-8648-D5D44B04EF8F}") + FOLDERID_UsersLibraries = NewGUID("{A302545D-DEFF-464b-ABE8-61C8648D939B}") + FOLDERID_Videos = NewGUID("{18989B1D-99B5-455B-841C-AB7C74E4DDFC}") + FOLDERID_VideosLibrary = NewGUID("{491E922F-5643-4AF4-A7EB-4E7A138D8174}") + FOLDERID_Windows = NewGUID("{F38BF404-1D43-42F2-9305-67DE0B28FC23}") +) + +var ( + modshell32 = syscall.NewLazyDLL("shell32.dll") + + procSHBrowseForFolder = modshell32.NewProc("SHBrowseForFolderW") + procSHGetPathFromIDList = modshell32.NewProc("SHGetPathFromIDListW") + procDragAcceptFiles = modshell32.NewProc("DragAcceptFiles") + procDragQueryFile = modshell32.NewProc("DragQueryFileW") + procDragQueryPoint = modshell32.NewProc("DragQueryPoint") + procDragFinish = modshell32.NewProc("DragFinish") + procShellExecute = modshell32.NewProc("ShellExecuteW") + procExtractIcon = modshell32.NewProc("ExtractIconW") + procGetSpecialFolderPath = modshell32.NewProc("SHGetSpecialFolderPathW") + procShellNotifyIcon = modshell32.NewProc("Shell_NotifyIconW") + procShellNotifyIconGetRect = modshell32.NewProc("Shell_NotifyIconGetRect") + procSHGetKnownFolderPath = modshell32.NewProc("SHGetKnownFolderPath") + procSHAppBarMessage = modshell32.NewProc("SHAppBarMessage") +) + +type APPBARDATA struct { + CbSize uint32 + HWnd HWND + UCallbackMessage uint32 + UEdge uint32 + Rc RECT + LParam uintptr +} + +func ShellNotifyIcon(cmd uintptr, nid *NOTIFYICONDATA) bool { + ret, _, _ := procShellNotifyIcon.Call(cmd, uintptr(unsafe.Pointer(nid))) + return ret == 1 +} + +func SHBrowseForFolder(bi *BROWSEINFO) uintptr { + ret, _, _ := procSHBrowseForFolder.Call(uintptr(unsafe.Pointer(bi))) + + return ret +} + +func SHGetKnownFolderPath(rfid *GUID, dwFlags uint32, hToken HANDLE) (string, error) { + var path *uint16 + ret, _, _ := procSHGetKnownFolderPath.Call(uintptr(unsafe.Pointer(rfid)), uintptr(dwFlags), hToken, uintptr(unsafe.Pointer(path))) + if ret != uintptr(windows.S_OK) { + return "", fmt.Errorf("SHGetKnownFolderPath failed: %v", ret) + } + return windows.UTF16PtrToString(path), nil +} + +func SHGetPathFromIDList(idl uintptr) string { + buf := make([]uint16, 1024) + procSHGetPathFromIDList.Call( + idl, + uintptr(unsafe.Pointer(&buf[0]))) + + return syscall.UTF16ToString(buf) +} + +func DragAcceptFiles(hwnd HWND, accept bool) { + procDragAcceptFiles.Call( + hwnd, + uintptr(BoolToBOOL(accept))) +} + +func DragQueryFile(hDrop HDROP, iFile uint) (fileName string, fileCount uint) { + ret, _, _ := procDragQueryFile.Call( + hDrop, + uintptr(iFile), + 0, + 0) + + fileCount = uint(ret) + + if iFile != 0xFFFFFFFF { + buf := make([]uint16, fileCount+1) + + ret, _, _ := procDragQueryFile.Call( + hDrop, + uintptr(iFile), + uintptr(unsafe.Pointer(&buf[0])), + uintptr(fileCount+1)) + + if ret == 0 { + panic("Invoke DragQueryFile error.") + } + + fileName = syscall.UTF16ToString(buf) + } + + return +} + +func DragQueryPoint(hDrop HDROP) (x, y int, isClientArea bool) { + var pt POINT + ret, _, _ := procDragQueryPoint.Call( + uintptr(hDrop), + uintptr(unsafe.Pointer(&pt))) + + return int(pt.X), int(pt.Y), (ret == 1) +} + +func DragFinish(hDrop HDROP) { + procDragFinish.Call(uintptr(hDrop)) +} + +func ShellExecute(hwnd HWND, lpOperation, lpFile, lpParameters, lpDirectory string, nShowCmd int) error { + var op, param, directory uintptr + if len(lpOperation) != 0 { + op = uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(lpOperation))) + } + if len(lpParameters) != 0 { + param = uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(lpParameters))) + } + if len(lpDirectory) != 0 { + directory = uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(lpDirectory))) + } + + ret, _, _ := procShellExecute.Call( + uintptr(hwnd), + op, + uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(lpFile))), + param, + directory, + uintptr(nShowCmd)) + + errorMsg := "" + if ret != 0 && ret <= 32 { + switch int(ret) { + case ERROR_FILE_NOT_FOUND: + errorMsg = "The specified file was not found." + case ERROR_PATH_NOT_FOUND: + errorMsg = "The specified path was not found." + case ERROR_BAD_FORMAT: + errorMsg = "The .exe file is invalid (non-Win32 .exe or error in .exe image)." + case SE_ERR_ACCESSDENIED: + errorMsg = "The operating system denied access to the specified file." + case SE_ERR_ASSOCINCOMPLETE: + errorMsg = "The file name association is incomplete or invalid." + case SE_ERR_DDEBUSY: + errorMsg = "The DDE transaction could not be completed because other DDE transactions were being processed." + case SE_ERR_DDEFAIL: + errorMsg = "The DDE transaction failed." + case SE_ERR_DDETIMEOUT: + errorMsg = "The DDE transaction could not be completed because the request timed out." + case SE_ERR_DLLNOTFOUND: + errorMsg = "The specified DLL was not found." + case SE_ERR_NOASSOC: + errorMsg = "There is no application associated with the given file name extension. This error will also be returned if you attempt to print a file that is not printable." + case SE_ERR_OOM: + errorMsg = "There was not enough memory to complete the operation." + case SE_ERR_SHARE: + errorMsg = "A sharing violation occurred." + default: + errorMsg = fmt.Sprintf("Unknown error occurred with error code %v", ret) + } + } else { + return nil + } + + return errors.New(errorMsg) +} + +func ExtractIcon(lpszExeFileName string, nIconIndex int) HICON { + ret, _, _ := procExtractIcon.Call( + 0, + uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(lpszExeFileName))), + uintptr(nIconIndex)) + + return HICON(ret) +} + +func SHGetSpecialFolderPath(hwndOwner HWND, lpszPath *uint16, csidl CSIDL, fCreate bool) bool { + ret, _, _ := procGetSpecialFolderPath.Call( + uintptr(hwndOwner), + uintptr(unsafe.Pointer(lpszPath)), + uintptr(csidl), + uintptr(BoolToBOOL(fCreate)), + 0, + 0) + + return ret != 0 +} + +func GetSystrayBounds(hwnd HWND, uid uint32) (*RECT, error) { + var rect RECT + identifier := NOTIFYICONIDENTIFIER{ + CbSize: uint32(unsafe.Sizeof(NOTIFYICONIDENTIFIER{})), + HWnd: hwnd, + UId: uid, + } + ret, _, _ := procShellNotifyIconGetRect.Call( + uintptr(unsafe.Pointer(&identifier)), + uintptr(unsafe.Pointer(&rect))) + + if ret != S_OK { + return nil, syscall.GetLastError() + } + + return &rect, nil +} + +// GetTaskbarPosition returns the location of the taskbar. +func GetTaskbarPosition() *APPBARDATA { + var result APPBARDATA + result.CbSize = uint32(unsafe.Sizeof(APPBARDATA{})) + ret, _, _ := procSHAppBarMessage.Call( + ABM_GETTASKBARPOS, + uintptr(unsafe.Pointer(&result))) + if ret == 0 { + return nil + } + + return &result +} diff --git a/v3/pkg/w32/shlwapi.go b/v3/pkg/w32/shlwapi.go new file mode 100644 index 000000000..89d17ce6f --- /dev/null +++ b/v3/pkg/w32/shlwapi.go @@ -0,0 +1,26 @@ +//go:build windows + +package w32 + +import ( + "syscall" + "unsafe" +) + +var ( + modshlwapi = syscall.NewLazyDLL("shlwapi.dll") + + procSHCreateMemStream = modshlwapi.NewProc("SHCreateMemStream") +) + +func SHCreateMemStream(data []byte) (uintptr, error) { + ret, _, err := procSHCreateMemStream.Call( + uintptr(unsafe.Pointer(&data[0])), + uintptr(len(data)), + ) + if ret == 0 { + return 0, err + } + + return ret, nil +} diff --git a/v3/pkg/w32/theme.go b/v3/pkg/w32/theme.go new file mode 100644 index 000000000..40ebb69bd --- /dev/null +++ b/v3/pkg/w32/theme.go @@ -0,0 +1,112 @@ +//go:build windows + +package w32 + +import ( + "unsafe" + + "golang.org/x/sys/windows/registry" +) + +type DWMWINDOWATTRIBUTE int32 + +const DwmwaUseImmersiveDarkModeBefore20h1 DWMWINDOWATTRIBUTE = 19 +const DwmwaUseImmersiveDarkMode DWMWINDOWATTRIBUTE = 20 +const DwmwaBorderColor DWMWINDOWATTRIBUTE = 34 +const DwmwaCaptionColor DWMWINDOWATTRIBUTE = 35 +const DwmwaTextColor DWMWINDOWATTRIBUTE = 36 +const DwmwaSystemBackdropType DWMWINDOWATTRIBUTE = 38 + +const SPI_GETHIGHCONTRAST = 0x0042 +const HCF_HIGHCONTRASTON = 0x00000001 + +func dwmSetWindowAttribute(hwnd uintptr, dwAttribute DWMWINDOWATTRIBUTE, pvAttribute unsafe.Pointer, cbAttribute uintptr) { + ret, _, err := procDwmSetWindowAttribute.Call( + hwnd, + uintptr(dwAttribute), + uintptr(pvAttribute), + cbAttribute) + if ret != 0 { + _ = err + // println(err.Error()) + } +} + +func SupportsThemes() bool { + // We can't support Windows versions before 17763 + return IsWindowsVersionAtLeast(10, 0, 17763) +} + +func SupportsCustomThemes() bool { + return IsWindowsVersionAtLeast(10, 0, 17763) +} + +func SupportsBackdropTypes() bool { + return IsWindowsVersionAtLeast(10, 0, 22621) +} + +func SupportsImmersiveDarkMode() bool { + return IsWindowsVersionAtLeast(10, 0, 18985) +} + +func SetTheme(hwnd uintptr, useDarkMode bool) { + if SupportsThemes() { + attr := DwmwaUseImmersiveDarkModeBefore20h1 + if SupportsImmersiveDarkMode() { + attr = DwmwaUseImmersiveDarkMode + } + var winDark int32 + if useDarkMode { + winDark = 1 + } + dwmSetWindowAttribute(hwnd, attr, unsafe.Pointer(&winDark), unsafe.Sizeof(winDark)) + } +} + +func EnableTranslucency(hwnd uintptr, backdrop int32) { + dwmSetWindowAttribute(hwnd, DwmwaSystemBackdropType, unsafe.Pointer(&backdrop), unsafe.Sizeof(backdrop)) +} + +func SetTitleBarColour(hwnd uintptr, titleBarColour int32) { + dwmSetWindowAttribute(hwnd, DwmwaCaptionColor, unsafe.Pointer(&titleBarColour), unsafe.Sizeof(titleBarColour)) +} + +func SetTitleTextColour(hwnd uintptr, titleTextColour int32) { + dwmSetWindowAttribute(hwnd, DwmwaTextColor, unsafe.Pointer(&titleTextColour), unsafe.Sizeof(titleTextColour)) +} + +func SetBorderColour(hwnd uintptr, titleBorderColour int32) { + dwmSetWindowAttribute(hwnd, DwmwaBorderColor, unsafe.Pointer(&titleBorderColour), unsafe.Sizeof(titleBorderColour)) +} + +func IsCurrentlyDarkMode() bool { + key, err := registry.OpenKey(registry.CURRENT_USER, `SOFTWARE\Microsoft\Windows\CurrentVersion\Themes\Personalize`, registry.QUERY_VALUE) + if err != nil { + return false + } + defer key.Close() + + AppsUseLightTheme, _, err := key.GetIntegerValue("AppsUseLightTheme") + if err != nil { + return false + } + return AppsUseLightTheme == 0 +} + +type highContrast struct { + CbSize uint32 + DwFlags uint32 + LpszDefaultScheme *int16 +} + +func IsCurrentlyHighContrastMode() bool { + var result highContrast + result.CbSize = uint32(unsafe.Sizeof(result)) + res, _, err := procSystemParametersInfo.Call(SPI_GETHIGHCONTRAST, uintptr(result.CbSize), uintptr(unsafe.Pointer(&result)), 0) + if res == 0 { + _ = err + return false + } + r := result.DwFlags&HCF_HIGHCONTRASTON == HCF_HIGHCONTRASTON + return r +} diff --git a/v3/pkg/w32/toolbar.go b/v3/pkg/w32/toolbar.go new file mode 100644 index 000000000..ac9261fc4 --- /dev/null +++ b/v3/pkg/w32/toolbar.go @@ -0,0 +1,216 @@ +//go:build windows + +/* + * Copyright (C) 2019 Tad Vizbaras. All Rights Reserved. + * Copyright (C) 2010-2012 The W32 Authors. All Rights Reserved. + */ + +package w32 + +// ToolBar messages +const ( + TB_ENABLEBUTTON = WM_USER + 1 + TB_CHECKBUTTON = WM_USER + 2 + TB_PRESSBUTTON = WM_USER + 3 + TB_HIDEBUTTON = WM_USER + 4 + TB_INDETERMINATE = WM_USER + 5 + TB_MARKBUTTON = WM_USER + 6 + TB_ISBUTTONENABLED = WM_USER + 9 + TB_ISBUTTONCHECKED = WM_USER + 10 + TB_ISBUTTONPRESSED = WM_USER + 11 + TB_ISBUTTONHIDDEN = WM_USER + 12 + TB_ISBUTTONINDETERMINATE = WM_USER + 13 + TB_ISBUTTONHIGHLIGHTED = WM_USER + 14 + TB_SETSTATE = WM_USER + 17 + TB_GETSTATE = WM_USER + 18 + TB_ADDBITMAP = WM_USER + 19 + TB_DELETEBUTTON = WM_USER + 22 + TB_GETBUTTON = WM_USER + 23 + TB_BUTTONCOUNT = WM_USER + 24 + TB_COMMANDTOINDEX = WM_USER + 25 + TB_SAVERESTORE = WM_USER + 76 + TB_CUSTOMIZE = WM_USER + 27 + TB_ADDSTRING = WM_USER + 77 + TB_GETITEMRECT = WM_USER + 29 + TB_BUTTONSTRUCTSIZE = WM_USER + 30 + TB_SETBUTTONSIZE = WM_USER + 31 + TB_SETBITMAPSIZE = WM_USER + 32 + TB_AUTOSIZE = WM_USER + 33 + TB_GETTOOLTIPS = WM_USER + 35 + TB_SETTOOLTIPS = WM_USER + 36 + TB_SETPARENT = WM_USER + 37 + TB_SETROWS = WM_USER + 39 + TB_GETROWS = WM_USER + 40 + TB_GETBITMAPFLAGS = WM_USER + 41 + TB_SETCMDID = WM_USER + 42 + TB_CHANGEBITMAP = WM_USER + 43 + TB_GETBITMAP = WM_USER + 44 + TB_GETBUTTONTEXT = WM_USER + 75 + TB_REPLACEBITMAP = WM_USER + 46 + TB_GETBUTTONSIZE = WM_USER + 58 + TB_SETBUTTONWIDTH = WM_USER + 59 + TB_SETINDENT = WM_USER + 47 + TB_SETIMAGELIST = WM_USER + 48 + TB_GETIMAGELIST = WM_USER + 49 + TB_LOADIMAGES = WM_USER + 50 + TB_GETRECT = WM_USER + 51 + TB_SETHOTIMAGELIST = WM_USER + 52 + TB_GETHOTIMAGELIST = WM_USER + 53 + TB_SETDISABLEDIMAGELIST = WM_USER + 54 + TB_GETDISABLEDIMAGELIST = WM_USER + 55 + TB_SETSTYLE = WM_USER + 56 + TB_GETSTYLE = WM_USER + 57 + TB_SETMAXTEXTROWS = WM_USER + 60 + TB_GETTEXTROWS = WM_USER + 61 + TB_GETOBJECT = WM_USER + 62 + TB_GETBUTTONINFO = WM_USER + 63 + TB_SETBUTTONINFO = WM_USER + 64 + TB_INSERTBUTTON = WM_USER + 67 + TB_ADDBUTTONS = WM_USER + 68 + TB_HITTEST = WM_USER + 69 + TB_SETDRAWTEXTFLAGS = WM_USER + 70 + TB_GETHOTITEM = WM_USER + 71 + TB_SETHOTITEM = WM_USER + 72 + TB_SETANCHORHIGHLIGHT = WM_USER + 73 + TB_GETANCHORHIGHLIGHT = WM_USER + 74 + TB_GETINSERTMARK = WM_USER + 79 + TB_SETINSERTMARK = WM_USER + 80 + TB_INSERTMARKHITTEST = WM_USER + 81 + TB_MOVEBUTTON = WM_USER + 82 + TB_GETMAXSIZE = WM_USER + 83 + TB_SETEXTENDEDSTYLE = WM_USER + 84 + TB_GETEXTENDEDSTYLE = WM_USER + 85 + TB_GETPADDING = WM_USER + 86 + TB_SETPADDING = WM_USER + 87 + TB_SETINSERTMARKCOLOR = WM_USER + 88 + TB_GETINSERTMARKCOLOR = WM_USER + 89 + TB_MAPACCELERATOR = WM_USER + 90 + TB_GETSTRING = WM_USER + 91 + TB_SETCOLORSCHEME = CCM_SETCOLORSCHEME + TB_GETCOLORSCHEME = CCM_GETCOLORSCHEME + TB_SETUNICODEFORMAT = CCM_SETUNICODEFORMAT + TB_GETUNICODEFORMAT = CCM_GETUNICODEFORMAT +) + +// ToolBar notifications +const ( + TBN_FIRST = -700 + TBN_DROPDOWN = TBN_FIRST - 10 +) + +// TBN_DROPDOWN return codes +const ( + TBDDRET_DEFAULT = 0 + TBDDRET_NODEFAULT = 1 + TBDDRET_TREATPRESSED = 2 +) + +// ToolBar state constants +const ( + TBSTATE_CHECKED = 1 + TBSTATE_PRESSED = 2 + TBSTATE_ENABLED = 4 + TBSTATE_HIDDEN = 8 + TBSTATE_INDETERMINATE = 16 + TBSTATE_WRAP = 32 + TBSTATE_ELLIPSES = 0x40 + TBSTATE_MARKED = 0x0080 +) + +// ToolBar style constants +const ( + TBSTYLE_BUTTON = 0 + TBSTYLE_SEP = 1 + TBSTYLE_CHECK = 2 + TBSTYLE_GROUP = 4 + TBSTYLE_CHECKGROUP = TBSTYLE_GROUP | TBSTYLE_CHECK + TBSTYLE_DROPDOWN = 8 + TBSTYLE_AUTOSIZE = 16 + TBSTYLE_NOPREFIX = 32 + TBSTYLE_TOOLTIPS = 256 + TBSTYLE_WRAPABLE = 512 + TBSTYLE_ALTDRAG = 1024 + TBSTYLE_FLAT = 2048 + TBSTYLE_LIST = 4096 + TBSTYLE_CUSTOMERASE = 8192 + TBSTYLE_REGISTERDROP = 0x4000 + TBSTYLE_TRANSPARENT = 0x8000 +) + +// ToolBar extended style constants +const ( + TBSTYLE_EX_DRAWDDARROWS = 0x00000001 + TBSTYLE_EX_MIXEDBUTTONS = 8 + TBSTYLE_EX_HIDECLIPPEDBUTTONS = 16 + TBSTYLE_EX_DOUBLEBUFFER = 0x80 +) + +// ToolBar button style constants +const ( + BTNS_BUTTON = TBSTYLE_BUTTON + BTNS_SEP = TBSTYLE_SEP + BTNS_CHECK = TBSTYLE_CHECK + BTNS_GROUP = TBSTYLE_GROUP + BTNS_CHECKGROUP = TBSTYLE_CHECKGROUP + BTNS_DROPDOWN = TBSTYLE_DROPDOWN + BTNS_AUTOSIZE = TBSTYLE_AUTOSIZE + BTNS_NOPREFIX = TBSTYLE_NOPREFIX + BTNS_WHOLEDROPDOWN = 0x0080 + BTNS_SHOWTEXT = 0x0040 +) + +// TBBUTTONINFO mask flags +const ( + TBIF_IMAGE = 0x00000001 + TBIF_TEXT = 0x00000002 + TBIF_STATE = 0x00000004 + TBIF_STYLE = 0x00000008 + TBIF_LPARAM = 0x00000010 + TBIF_COMMAND = 0x00000020 + TBIF_SIZE = 0x00000040 + TBIF_BYINDEX = 0x80000000 +) + +type NMMOUSE struct { + Hdr NMHDR + DwItemSpec uintptr + DwItemData uintptr + Pt POINT + DwHitInfo uintptr +} + +type NMTOOLBAR struct { + Hdr NMHDR + IItem int32 + TbButton TBBUTTON + CchText int32 + PszText *uint16 + RcButton RECT +} + +type TBBUTTON struct { + IBitmap int32 + IdCommand int32 + FsState byte + FsStyle byte + //#ifdef _WIN64 + // BYTE bReserved[6] // padding for alignment + //#elif defined(_WIN32) + BReserved [2]byte // padding for alignment + //#endif + DwData uintptr + IString uintptr +} + +type TBBUTTONINFO struct { + CbSize uint32 + DwMask uint32 + IdCommand int32 + IImage int32 + FsState byte + FsStyle byte + Cx uint16 + LParam uintptr + PszText uintptr + CchText int32 +} diff --git a/v3/pkg/w32/typedef.go b/v3/pkg/w32/typedef.go new file mode 100644 index 000000000..433a375b3 --- /dev/null +++ b/v3/pkg/w32/typedef.go @@ -0,0 +1,1143 @@ +//go:build windows + +/* + * Copyright (C) 2019 Tad Vizbaras. All Rights Reserved. + * Copyright (C) 2010-2012 The W32 Authors. All Rights Reserved. + */ + +package w32 + +import ( + "fmt" + "golang.org/x/sys/windows" + "unsafe" +) + +// From MSDN: Windows Data Types +// http://msdn.microsoft.com/en-us/library/s3f49ktz.aspx +// http://msdn.microsoft.com/en-us/library/windows/desktop/aa383751.aspx +// ATOM WORD +// BOOL int32 +// BOOLEAN byte +// BYTE byte +// CCHAR int8 +// CHAR int8 +// COLORREF DWORD +// DWORD uint32 +// DWORDLONG ULONGLONG +// DWORD_PTR ULONG_PTR +// DWORD32 uint32 +// DWORD64 uint64 +// FLOAT float32 +// HACCEL HANDLE +// HALF_PTR struct{} // ??? +// HANDLE PVOID +// HBITMAP HANDLE +// HBRUSH HANDLE +// HCOLORSPACE HANDLE +// HCONV HANDLE +// HCONVLIST HANDLE +// HCURSOR HANDLE +// HDC HANDLE +// HDDEDATA HANDLE +// HDESK HANDLE +// HDROP HANDLE +// HDWP HANDLE +// HENHMETAFILE HANDLE +// HFILE HANDLE +// HFONT HANDLE +// HGDIOBJ HANDLE +// HGLOBAL HANDLE +// HHOOK HANDLE +// HICON HANDLE +// HINSTANCE HANDLE +// HKEY HANDLE +// HKL HANDLE +// HLOCAL HANDLE +// HMENU HANDLE +// HMETAFILE HANDLE +// HMODULE HANDLE +// HPALETTE HANDLE +// HPEN HANDLE +// HRESULT int32 +// HRGN HANDLE +// HSZ HANDLE +// HWINSTA HANDLE +// HWND HANDLE +// INT int32 +// INT_PTR uintptr +// INT8 int8 +// INT16 int16 +// INT32 int32 +// INT64 int64 +// LANGID WORD +// LCID DWORD +// LCTYPE DWORD +// LGRPID DWORD +// LONG int32 +// LONGLONG int64 +// LONG_PTR uintptr +// LONG32 int32 +// LONG64 int64 +// LPARAM LONG_PTR +// LPBOOL *BOOL +// LPBYTE *BYTE +// LPCOLORREF *COLORREF +// LPCSTR *int8 +// LPCTSTR LPCWSTR +// LPCVOID unsafe.Pointer +// LPCWSTR *WCHAR +// LPDWORD *DWORD +// LPHANDLE *HANDLE +// LPINT *INT +// LPLONG *LONG +// LPSTR *CHAR +// LPTSTR LPWSTR +// LPVOID unsafe.Pointer +// LPWORD *WORD +// LPWSTR *WCHAR +// LRESULT LONG_PTR +// PBOOL *BOOL +// PBOOLEAN *BOOLEAN +// PBYTE *BYTE +// PCHAR *CHAR +// PCSTR *CHAR +// PCTSTR PCWSTR +// PCWSTR *WCHAR +// PDWORD *DWORD +// PDWORDLONG *DWORDLONG +// PDWORD_PTR *DWORD_PTR +// PDWORD32 *DWORD32 +// PDWORD64 *DWORD64 +// PFLOAT *FLOAT +// PHALF_PTR *HALF_PTR +// PHANDLE *HANDLE +// PHKEY *HKEY +// PINT_PTR *INT_PTR +// PINT8 *INT8 +// PINT16 *INT16 +// PINT32 *INT32 +// PINT64 *INT64 +// PLCID *LCID +// PLONG *LONG +// PLONGLONG *LONGLONG +// PLONG_PTR *LONG_PTR +// PLONG32 *LONG32 +// PLONG64 *LONG64 +// POINTER_32 struct{} // ??? +// POINTER_64 struct{} // ??? +// POINTER_SIGNED uintptr +// POINTER_UNSIGNED uintptr +// PSHORT *SHORT +// PSIZE_T *SIZE_T +// PSSIZE_T *SSIZE_T +// PSTR *CHAR +// PTBYTE *TBYTE +// PTCHAR *TCHAR +// PTSTR PWSTR +// PUCHAR *UCHAR +// PUHALF_PTR *UHALF_PTR +// PUINT *UINT +// PUINT_PTR *UINT_PTR +// PUINT8 *UINT8 +// PUINT16 *UINT16 +// PUINT32 *UINT32 +// PUINT64 *UINT64 +// PULONG *ULONG +// PULONGLONG *ULONGLONG +// PULONG_PTR *ULONG_PTR +// PULONG32 *ULONG32 +// PULONG64 *ULONG64 +// PUSHORT *USHORT +// PVOID unsafe.Pointer +// PWCHAR *WCHAR +// PWORD *WORD +// PWSTR *WCHAR +// QWORD uint64 +// SC_HANDLE HANDLE +// SC_LOCK LPVOID +// SERVICE_STATUS_HANDLE HANDLE +// SHORT int16 +// SIZE_T ULONG_PTR +// SSIZE_T LONG_PTR +// TBYTE WCHAR +// TCHAR WCHAR +// UCHAR uint8 +// UHALF_PTR struct{} // ??? +// UINT uint32 +// UINT_PTR uintptr +// UINT8 uint8 +// UINT16 uint16 +// UINT32 uint32 +// UINT64 uint64 +// ULONG uint32 +// ULONGLONG uint64 +// ULONG_PTR uintptr +// ULONG32 uint32 +// ULONG64 uint64 +// USHORT uint16 +// USN LONGLONG +// WCHAR uint16 +// WORD uint16 +// WPARAM UINT_PTR +type ( + ATOM = uint16 + BOOL = int32 + COLORREF = uint32 + DWM_FRAME_COUNT = uint64 + WORD = uint16 + DWORD = uint32 + HACCEL = HANDLE + HANDLE = uintptr + HBITMAP = HANDLE + HBRUSH = HANDLE + HCURSOR = HANDLE + HDC = HANDLE + HDROP = HANDLE + HDWP = HANDLE + HENHMETAFILE = HANDLE + HFONT = HANDLE + HGDIOBJ = HANDLE + HGLOBAL = HANDLE + HGLRC = HANDLE + HHOOK = HANDLE + HICON = HANDLE + HIMAGELIST = HANDLE + HINSTANCE = HANDLE + HKEY = HANDLE + HKL = HANDLE + HMENU = HANDLE + HMODULE = HANDLE + HMONITOR = HANDLE + HPEN = HANDLE + HRESULT = int32 + HRGN = HANDLE + HRSRC = HANDLE + HTHUMBNAIL = HANDLE + HWND = HANDLE + LPARAM = uintptr + LPCVOID = unsafe.Pointer + LRESULT = uintptr + PVOID = unsafe.Pointer + QPC_TIME = uint64 + ULONG_PTR = uintptr + SIZE_T = ULONG_PTR + WPARAM = uintptr + UINT = uint +) + +// http://msdn.microsoft.com/en-us/library/windows/desktop/dd162805.aspx +type POINT struct { + X, Y int32 +} + +// http://msdn.microsoft.com/en-us/library/windows/desktop/dd162897.aspx +type RECT struct { + Left, Top, Right, Bottom int32 +} + +func (r *RECT) String() string { + return fmt.Sprintf("RECT (%p): Left: %d, Top: %d, Right: %d, Bottom: %d", r, r.Left, r.Top, r.Right, r.Bottom) +} + +// http://msdn.microsoft.com/en-us/library/windows/desktop/ms633577.aspx +type WNDCLASSEX struct { + Size uint32 + Style uint32 + WndProc uintptr + ClsExtra int32 + WndExtra int32 + Instance HINSTANCE + Icon HICON + Cursor HCURSOR + Background HBRUSH + MenuName *uint16 + ClassName *uint16 + IconSm HICON +} + +type TPMPARAMS struct { + CbSize uint32 + RcExclude RECT +} + +// http://msdn.microsoft.com/en-us/library/windows/desktop/ms644958.aspx +type MSG struct { + Hwnd HWND + Message uint32 + WParam uintptr + LParam uintptr + Time uint32 + Pt POINT +} + +// https://docs.microsoft.com/en-us/windows/win32/api/winuser/ns-winuser-minmaxinfo +type MINMAXINFO struct { + PtReserved POINT + PtMaxSize POINT + PtMaxPosition POINT + PtMinTrackSize POINT + PtMaxTrackSize POINT +} + +// http://msdn.microsoft.com/en-us/library/windows/desktop/dd145037.aspx +type LOGFONT struct { + Height int32 + Width int32 + Escapement int32 + Orientation int32 + Weight int32 + Italic byte + Underline byte + StrikeOut byte + CharSet byte + OutPrecision byte + ClipPrecision byte + Quality byte + PitchAndFamily byte + FaceName [LF_FACESIZE]uint16 +} + +// http://msdn.microsoft.com/en-us/library/windows/desktop/ms646839.aspx +type OPENFILENAME struct { + StructSize uint32 + Owner HWND + Instance HINSTANCE + Filter *uint16 + CustomFilter *uint16 + MaxCustomFilter uint32 + FilterIndex uint32 + File *uint16 + MaxFile uint32 + FileTitle *uint16 + MaxFileTitle uint32 + InitialDir *uint16 + Title *uint16 + Flags uint32 + FileOffset uint16 + FileExtension uint16 + DefExt *uint16 + CustData uintptr + FnHook uintptr + TemplateName *uint16 + PvReserved unsafe.Pointer + DwReserved uint32 + FlagsEx uint32 +} + +// http://msdn.microsoft.com/en-us/library/windows/desktop/bb773205.aspx +type BROWSEINFO struct { + Owner HWND + Root *uint16 + DisplayName *uint16 + Title *uint16 + Flags uint32 + CallbackFunc uintptr + LParam uintptr + Image int32 +} + +// http://msdn.microsoft.com/en-us/library/windows/desktop/ms221627.aspx +type VARIANT struct { + VT uint16 // 2 + WReserved1 uint16 // 4 + WReserved2 uint16 // 6 + WReserved3 uint16 // 8 + Val int64 // 16 +} + +// http://msdn.microsoft.com/en-us/library/windows/desktop/ms221416.aspx +type DISPPARAMS struct { + Rgvarg uintptr + RgdispidNamedArgs uintptr + CArgs uint32 + CNamedArgs uint32 +} + +// http://msdn.microsoft.com/en-us/library/windows/desktop/ms221133.aspx +type EXCEPINFO struct { + WCode uint16 + WReserved uint16 + BstrSource *uint16 + BstrDescription *uint16 + BstrHelpFile *uint16 + DwHelpContext uint32 + PvReserved uintptr + PfnDeferredFillIn uintptr + Scode int32 +} + +// http://msdn.microsoft.com/en-us/library/windows/desktop/dd145035.aspx +type LOGBRUSH struct { + LbStyle uint32 + LbColor COLORREF + LbHatch uintptr +} + +// http://msdn.microsoft.com/en-us/library/windows/desktop/dd183565.aspx +type DEVMODE struct { + DmDeviceName [CCHDEVICENAME]uint16 + DmSpecVersion uint16 + DmDriverVersion uint16 + DmSize uint16 + DmDriverExtra uint16 + DmFields uint32 + DmOrientation int16 + DmPaperSize int16 + DmPaperLength int16 + DmPaperWidth int16 + DmScale int16 + DmCopies int16 + DmDefaultSource int16 + DmPrintQuality int16 + DmColor int16 + DmDuplex int16 + DmYResolution int16 + DmTTOption int16 + DmCollate int16 + DmFormName [CCHFORMNAME]uint16 + DmLogPixels uint16 + DmBitsPerPel uint32 + DmPelsWidth uint32 + DmPelsHeight uint32 + DmDisplayFlags uint32 + DmDisplayFrequency uint32 + DmICMMethod uint32 + DmICMIntent uint32 + DmMediaType uint32 + DmDitherType uint32 + DmReserved1 uint32 + DmReserved2 uint32 + DmPanningWidth uint32 + DmPanningHeight uint32 +} + +// http://msdn.microsoft.com/en-us/library/windows/desktop/dd183376.aspx +type BITMAPINFOHEADER struct { + BiSize uint32 + BiWidth int32 + BiHeight int32 + BiPlanes uint16 + BiBitCount uint16 + BiCompression uint32 + BiSizeImage uint32 + BiXPelsPerMeter int32 + BiYPelsPerMeter int32 + BiClrUsed uint32 + BiClrImportant uint32 +} + +// http://msdn.microsoft.com/en-us/library/windows/desktop/dd162938.aspx +type RGBQUAD struct { + RgbBlue byte + RgbGreen byte + RgbRed byte + RgbReserved byte +} + +// http://msdn.microsoft.com/en-us/library/windows/desktop/dd183375.aspx +type BITMAPINFO struct { + BmiHeader BITMAPINFOHEADER + BmiColors *RGBQUAD +} + +// http://msdn.microsoft.com/en-us/library/windows/desktop/dd183371.aspx +type BITMAP struct { + BmType int32 + BmWidth int32 + BmHeight int32 + BmWidthBytes int32 + BmPlanes uint16 + BmBitsPixel uint16 + BmBits unsafe.Pointer +} + +type BLENDFUNCTION struct { + BlendOp byte + BlendFlags byte + SourceConstantAlpha byte + AlphaFormat byte +} + +// http://msdn.microsoft.com/en-us/library/windows/desktop/dd183567.aspx +type DIBSECTION struct { + DsBm BITMAP + DsBmih BITMAPINFOHEADER + DsBitfields [3]uint32 + DshSection HANDLE + DsOffset uint32 +} + +type MSGBOXPARAMS struct { + cbSize uint32 + hwndOwner HWND + hInstance HANDLE + lpszText *uint16 + lpszCaption *uint16 + dwStyle uint32 + lpszIcon *uint16 + dwContextHelp uintptr + lpfnMsgBoxCallback uintptr + dwLanguageId uint32 +} + +// http://msdn.microsoft.com/en-us/library/windows/desktop/dd162607.aspx +type ENHMETAHEADER struct { + IType uint32 + NSize uint32 + RclBounds RECT + RclFrame RECT + DSignature uint32 + NVersion uint32 + NBytes uint32 + NRecords uint32 + NHandles uint16 + SReserved uint16 + NDescription uint32 + OffDescription uint32 + NPalEntries uint32 + SzlDevice SIZE + SzlMillimeters SIZE + CbPixelFormat uint32 + OffPixelFormat uint32 + BOpenGL uint32 + SzlMicrometers SIZE +} + +// http://msdn.microsoft.com/en-us/library/windows/desktop/dd145106.aspx +type SIZE struct { + CX, CY int32 +} + +// http://msdn.microsoft.com/en-us/library/windows/desktop/dd145132.aspx +type TEXTMETRIC struct { + TmHeight int32 + TmAscent int32 + TmDescent int32 + TmInternalLeading int32 + TmExternalLeading int32 + TmAveCharWidth int32 + TmMaxCharWidth int32 + TmWeight int32 + TmOverhang int32 + TmDigitizedAspectX int32 + TmDigitizedAspectY int32 + TmFirstChar uint16 + TmLastChar uint16 + TmDefaultChar uint16 + TmBreakChar uint16 + TmItalic byte + TmUnderlined byte + TmStruckOut byte + TmPitchAndFamily byte + TmCharSet byte +} + +// http://msdn.microsoft.com/en-us/library/windows/desktop/dd183574.aspx +type DOCINFO struct { + CbSize int32 + LpszDocName *uint16 + LpszOutput *uint16 + LpszDatatype *uint16 + FwType uint32 +} + +// http://msdn.microsoft.com/en-us/library/windows/desktop/bb775514.aspx +type NMHDR struct { + HwndFrom HWND + IdFrom uintptr + Code uint32 +} + +// http://msdn.microsoft.com/en-us/library/windows/desktop/bb774743.aspx +type LVCOLUMN struct { + Mask uint32 + Fmt int32 + Cx int32 + PszText *uint16 + CchTextMax int32 + ISubItem int32 + IImage int32 + IOrder int32 +} + +// http://msdn.microsoft.com/en-us/library/windows/desktop/bb774760.aspx +type LVITEM struct { + Mask uint32 + IItem int32 + ISubItem int32 + State uint32 + StateMask uint32 + PszText *uint16 + CchTextMax int32 + IImage int32 + LParam uintptr + IIndent int32 + IGroupId int32 + CColumns uint32 + PuColumns uint32 +} + +type LVFINDINFO struct { + Flags uint32 + PszText *uint16 + LParam uintptr + Pt POINT + VkDirection uint32 +} + +// http://msdn.microsoft.com/en-us/library/windows/desktop/bb774754.aspx +type LVHITTESTINFO struct { + Pt POINT + Flags uint32 + IItem int32 + ISubItem int32 + IGroup int32 +} + +// http://msdn.microsoft.com/en-us/library/windows/desktop/bb774771.aspx +type NMITEMACTIVATE struct { + Hdr NMHDR + IItem int32 + ISubItem int32 + UNewState uint32 + UOldState uint32 + UChanged uint32 + PtAction POINT + LParam uintptr + UKeyFlags uint32 +} + +type NMLVKEYDOWN struct { + Hdr NMHDR + WVKey uint16 + Flags uint32 +} + +// http://msdn.microsoft.com/en-us/library/windows/desktop/bb774773.aspx +type NMLISTVIEW struct { + Hdr NMHDR + IItem int32 + ISubItem int32 + UNewState uint32 + UOldState uint32 + UChanged uint32 + PtAction POINT + LParam uintptr +} + +// http://msdn.microsoft.com/en-us/library/windows/desktop/bb774780.aspx +type NMLVDISPINFO struct { + Hdr NMHDR + Item LVITEM +} + +// http://msdn.microsoft.com/en-us/library/windows/desktop/bb775507.aspx +type INITCOMMONCONTROLSEX struct { + DwSize uint32 + DwICC uint32 +} + +// http://msdn.microsoft.com/en-us/library/windows/desktop/bb760256.aspx +type TOOLINFO struct { + CbSize uint32 + UFlags uint32 + Hwnd HWND + UId uintptr + Rect RECT + Hinst HINSTANCE + LpszText *uint16 + LParam uintptr + LpReserved unsafe.Pointer +} + +// http://msdn.microsoft.com/en-us/library/windows/desktop/ms645604.aspx +type TRACKMOUSEEVENT struct { + CbSize uint32 + DwFlags uint32 + HwndTrack HWND + DwHoverTime uint32 +} + +type NOTIFYICONDATA struct { + CbSize uint32 + HWnd HWND + UID uint32 + UFlags uint32 + UCallbackMessage uint32 + HIcon HICON + SzTip [128]uint16 + DwState uint32 + DwStateMask uint32 + SzInfo [256]uint16 + UVersion uint32 + SzInfoTitle [64]uint16 + DwInfoFlags uint32 + GuidItem windows.GUID + HBalloonIcon HICON +} + +const SPI_GETNOTIFYWINDOWRECT = 0x0040 + +// Taskbar constants +const ABM_GETTASKBARPOS = 0x00000005 +const ABM_GETSTATE = 0x00000004 +const ABM_GETAUTOHIDEBAR = 0x00000007 +const ABM_SETSTATE = 0x0000000a +const ABM_SETAUTOHIDEBAR = 0x00000008 +const ABM_WINDOWPOSCHANGED = 0x00000009 +const ABM_SETPOS = 0x00000003 + +const ABE_LEFT = 0 +const ABE_TOP = 1 +const ABE_RIGHT = 2 +const ABE_BOTTOM = 3 + +type NOTIFYICONIDENTIFIER struct { + CbSize uint32 + HWnd HWND + UId uint32 + GuidItem windows.GUID +} + +// http://msdn.microsoft.com/en-us/library/windows/desktop/ms534067.aspx +type GdiplusStartupInput struct { + GdiplusVersion uint32 + DebugEventCallback uintptr + SuppressBackgroundThread BOOL + SuppressExternalCodecs BOOL +} + +// http://msdn.microsoft.com/en-us/library/windows/desktop/ms534068.aspx +type GdiplusStartupOutput struct { + NotificationHook uintptr + NotificationUnhook uintptr +} + +// http://msdn.microsoft.com/en-us/library/windows/desktop/dd162768.aspx +type PAINTSTRUCT struct { + Hdc HDC + FErase BOOL + RcPaint RECT + FRestore BOOL + FIncUpdate BOOL + RgbReserved [32]byte +} + +// http://msdn.microsoft.com/en-us/library/windows/desktop/aa363646.aspx +type EVENTLOGRECORD struct { + Length uint32 + Reserved uint32 + RecordNumber uint32 + TimeGenerated uint32 + TimeWritten uint32 + EventID uint32 + EventType uint16 + NumStrings uint16 + EventCategory uint16 + ReservedFlags uint16 + ClosingRecordNumber uint32 + StringOffset uint32 + UserSidLength uint32 + UserSidOffset uint32 + DataLength uint32 + DataOffset uint32 +} + +// http://msdn.microsoft.com/en-us/library/windows/desktop/ms685996.aspx +type SERVICE_STATUS struct { + DwServiceType uint32 + DwCurrentState uint32 + DwControlsAccepted uint32 + DwWin32ExitCode uint32 + DwServiceSpecificExitCode uint32 + DwCheckPoint uint32 + DwWaitHint uint32 +} + +/* ------------------------- + Undocumented API +------------------------- */ + +type ACCENT_STATE DWORD + +const ( + ACCENT_DISABLED ACCENT_STATE = 0 + ACCENT_ENABLE_GRADIENT ACCENT_STATE = 1 + ACCENT_ENABLE_TRANSPARENTGRADIENT ACCENT_STATE = 2 + ACCENT_ENABLE_BLURBEHIND ACCENT_STATE = 3 + ACCENT_ENABLE_ACRYLICBLURBEHIND ACCENT_STATE = 4 // RS4 1803 + ACCENT_ENABLE_HOSTBACKDROP ACCENT_STATE = 5 // RS5 1809 + ACCENT_INVALID_STATE ACCENT_STATE = 6 +) + +type ACCENT_POLICY struct { + AccentState ACCENT_STATE + AccentFlags DWORD + GradientColor DWORD + AnimationId DWORD +} + +type WINDOWCOMPOSITIONATTRIBDATA struct { + Attrib WINDOWCOMPOSITIONATTRIB + PvData PVOID + CbData SIZE_T +} + +type WINDOWCOMPOSITIONATTRIB DWORD + +const ( + WCA_UNDEFINED WINDOWCOMPOSITIONATTRIB = 0 + WCA_NCRENDERING_ENABLED WINDOWCOMPOSITIONATTRIB = 1 + WCA_NCRENDERING_POLICY WINDOWCOMPOSITIONATTRIB = 2 + WCA_TRANSITIONS_FORCEDISABLED WINDOWCOMPOSITIONATTRIB = 3 + WCA_ALLOW_NCPAINT WINDOWCOMPOSITIONATTRIB = 4 + WCA_CAPTION_BUTTON_BOUNDS WINDOWCOMPOSITIONATTRIB = 5 + WCA_NONCLIENT_RTL_LAYOUT WINDOWCOMPOSITIONATTRIB = 6 + WCA_FORCE_ICONIC_REPRESENTATION WINDOWCOMPOSITIONATTRIB = 7 + WCA_EXTENDED_FRAME_BOUNDS WINDOWCOMPOSITIONATTRIB = 8 + WCA_HAS_ICONIC_BITMAP WINDOWCOMPOSITIONATTRIB = 9 + WCA_THEME_ATTRIBUTES WINDOWCOMPOSITIONATTRIB = 10 + WCA_NCRENDERING_EXILED WINDOWCOMPOSITIONATTRIB = 11 + WCA_NCADORNMENTINFO WINDOWCOMPOSITIONATTRIB = 12 + WCA_EXCLUDED_FROM_LIVEPREVIEW WINDOWCOMPOSITIONATTRIB = 13 + WCA_VIDEO_OVERLAY_ACTIVE WINDOWCOMPOSITIONATTRIB = 14 + WCA_FORCE_ACTIVEWINDOW_APPEARANCE WINDOWCOMPOSITIONATTRIB = 15 + WCA_DISALLOW_PEEK WINDOWCOMPOSITIONATTRIB = 16 + WCA_CLOAK WINDOWCOMPOSITIONATTRIB = 17 + WCA_CLOAKED WINDOWCOMPOSITIONATTRIB = 18 + WCA_ACCENT_POLICY WINDOWCOMPOSITIONATTRIB = 19 + WCA_FREEZE_REPRESENTATION WINDOWCOMPOSITIONATTRIB = 20 + WCA_EVER_UNCLOAKED WINDOWCOMPOSITIONATTRIB = 21 + WCA_VISUAL_OWNER WINDOWCOMPOSITIONATTRIB = 22 + WCA_HOLOGRAPHIC WINDOWCOMPOSITIONATTRIB = 23 + WCA_EXCLUDED_FROM_DDA WINDOWCOMPOSITIONATTRIB = 24 + WCA_PASSIVEUPDATEMODE WINDOWCOMPOSITIONATTRIB = 25 + WCA_USEDARKMODECOLORS WINDOWCOMPOSITIONATTRIB = 26 + WCA_CORNER_STYLE WINDOWCOMPOSITIONATTRIB = 27 + WCA_PART_COLOR WINDOWCOMPOSITIONATTRIB = 28 + WCA_DISABLE_MOVESIZE_FEEDBACK WINDOWCOMPOSITIONATTRIB = 29 + WCA_LAST WINDOWCOMPOSITIONATTRIB = 30 +) + +// ------------------------- + +// http://msdn.microsoft.com/en-us/library/windows/desktop/ms684225.aspx +type MODULEENTRY32 struct { + Size uint32 + ModuleID uint32 + ProcessID uint32 + GlblcntUsage uint32 + ProccntUsage uint32 + ModBaseAddr *uint8 + ModBaseSize uint32 + HModule HMODULE + SzModule [MAX_MODULE_NAME32 + 1]uint16 + SzExePath [MAX_PATH]uint16 +} + +// http://msdn.microsoft.com/en-us/library/windows/desktop/ms724284.aspx +type FILETIME struct { + DwLowDateTime uint32 + DwHighDateTime uint32 +} + +// http://msdn.microsoft.com/en-us/library/windows/desktop/ms682119.aspx +type COORD struct { + X, Y int16 +} + +// http://msdn.microsoft.com/en-us/library/windows/desktop/ms686311.aspx +type SMALL_RECT struct { + Left, Top, Right, Bottom int16 +} + +// http://msdn.microsoft.com/en-us/library/windows/desktop/ms682093.aspx +type CONSOLE_SCREEN_BUFFER_INFO struct { + DwSize COORD + DwCursorPosition COORD + WAttributes uint16 + SrWindow SMALL_RECT + DwMaximumWindowSize COORD +} + +// http://msdn.microsoft.com/en-us/library/windows/desktop/bb773244.aspx +type MARGINS struct { + CxLeftWidth, CxRightWidth, CyTopHeight, CyBottomHeight int32 +} + +// http://msdn.microsoft.com/en-us/library/windows/desktop/aa969500.aspx +type DWM_BLURBEHIND struct { + DwFlags uint32 + fEnable BOOL + hRgnBlur HRGN + fTransitionOnMaximized BOOL +} + +// http://msdn.microsoft.com/en-us/library/windows/desktop/aa969501.aspx +type DWM_PRESENT_PARAMETERS struct { + cbSize uint32 + fQueue BOOL + cRefreshStart DWM_FRAME_COUNT + cBuffer uint32 + fUseSourceRate BOOL + rateSource UNSIGNED_RATIO + cRefreshesPerFrame uint32 + eSampling DWM_SOURCE_FRAME_SAMPLING +} + +// http://msdn.microsoft.com/en-us/library/windows/desktop/aa969502.aspx +type DWM_THUMBNAIL_PROPERTIES struct { + dwFlags uint32 + rcDestination RECT + rcSource RECT + opacity byte + fVisible BOOL + fSourceClientAreaOnly BOOL +} + +// http://msdn.microsoft.com/en-us/library/windows/desktop/aa969503.aspx +type DWM_TIMING_INFO struct { + cbSize uint32 + rateRefresh UNSIGNED_RATIO + qpcRefreshPeriod QPC_TIME + rateCompose UNSIGNED_RATIO + qpcVBlank QPC_TIME + cRefresh DWM_FRAME_COUNT + cDXRefresh uint32 + qpcCompose QPC_TIME + cFrame DWM_FRAME_COUNT + cDXPresent uint32 + cRefreshFrame DWM_FRAME_COUNT + cFrameSubmitted DWM_FRAME_COUNT + cDXPresentSubmitted uint32 + cFrameConfirmed DWM_FRAME_COUNT + cDXPresentConfirmed uint32 + cRefreshConfirmed DWM_FRAME_COUNT + cDXRefreshConfirmed uint32 + cFramesLate DWM_FRAME_COUNT + cFramesOutstanding uint32 + cFrameDisplayed DWM_FRAME_COUNT + qpcFrameDisplayed QPC_TIME + cRefreshFrameDisplayed DWM_FRAME_COUNT + cFrameComplete DWM_FRAME_COUNT + qpcFrameComplete QPC_TIME + cFramePending DWM_FRAME_COUNT + qpcFramePending QPC_TIME + cFramesDisplayed DWM_FRAME_COUNT + cFramesComplete DWM_FRAME_COUNT + cFramesPending DWM_FRAME_COUNT + cFramesAvailable DWM_FRAME_COUNT + cFramesDropped DWM_FRAME_COUNT + cFramesMissed DWM_FRAME_COUNT + cRefreshNextDisplayed DWM_FRAME_COUNT + cRefreshNextPresented DWM_FRAME_COUNT + cRefreshesDisplayed DWM_FRAME_COUNT + cRefreshesPresented DWM_FRAME_COUNT + cRefreshStarted DWM_FRAME_COUNT + cPixelsReceived uint64 + cPixelsDrawn uint64 + cBuffersEmpty DWM_FRAME_COUNT +} + +// http://msdn.microsoft.com/en-us/library/windows/desktop/dd389402.aspx +type MilMatrix3x2D struct { + S_11, S_12, S_21, S_22 float64 + DX, DY float64 +} + +// http://msdn.microsoft.com/en-us/library/windows/desktop/aa969505.aspx +type UNSIGNED_RATIO struct { + uiNumerator uint32 + uiDenominator uint32 +} + +// http://msdn.microsoft.com/en-us/library/windows/desktop/ms632603.aspx +type CREATESTRUCT struct { + CreateParams uintptr + Instance HINSTANCE + Menu HMENU + Parent HWND + Cy, Cx int32 + Y, X int32 + Style int32 + Name *uint16 + Class *uint16 + dwExStyle uint32 +} + +// http://msdn.microsoft.com/en-us/library/windows/desktop/dd145065.aspx +type MONITORINFO struct { + CbSize uint32 + RcMonitor RECT + RcWork RECT + DwFlags uint32 +} + +type WINDOWINFO struct { + CbSize DWORD + RcWindow RECT + RcClient RECT + DwStyle DWORD + DwExStyle DWORD + DwWindowStatus DWORD + CxWindowBorders UINT + CyWindowBorders UINT + AtomWindowType ATOM + WCreatorVersion WORD +} + +type MONITOR_DPI_TYPE int32 + +const ( + MDT_EFFECTIVE_DPI MONITOR_DPI_TYPE = 0 + MDT_ANGULAR_DPI MONITOR_DPI_TYPE = 1 + MDT_RAW_DPI MONITOR_DPI_TYPE = 2 + MDT_DEFAULT MONITOR_DPI_TYPE = 0 +) + +func (w *WINDOWINFO) isStyle(style DWORD) bool { + return w.DwStyle&style == style +} + +func (w *WINDOWINFO) IsPopup() bool { + return w.isStyle(WS_POPUP) +} + +func (m *MONITORINFO) Dump() { + fmt.Printf("MONITORINFO (%p)\n", m) + fmt.Printf(" CbSize : %d\n", m.CbSize) + fmt.Printf(" RcMonitor: %s\n", &m.RcMonitor) + fmt.Printf(" RcWork : %s\n", &m.RcWork) + fmt.Printf(" DwFlags : %d\n", m.DwFlags) +} + +// http://msdn.microsoft.com/en-us/library/windows/desktop/dd145066.aspx +type MONITORINFOEX struct { + MONITORINFO + SzDevice [CCHDEVICENAME]uint16 +} + +// http://msdn.microsoft.com/en-us/library/windows/desktop/dd368826.aspx +type PIXELFORMATDESCRIPTOR struct { + Size uint16 + Version uint16 + DwFlags uint32 + IPixelType byte + ColorBits byte + RedBits, RedShift byte + GreenBits, GreenShift byte + BlueBits, BlueShift byte + AlphaBits, AlphaShift byte + AccumBits byte + AccumRedBits byte + AccumGreenBits byte + AccumBlueBits byte + AccumAlphaBits byte + DepthBits, StencilBits byte + AuxBuffers byte + ILayerType byte + Reserved byte + DwLayerMask uint32 + DwVisibleMask uint32 + DwDamageMask uint32 +} + +// http://msdn.microsoft.com/en-us/library/windows/desktop/ms646270(v=vs.85).aspx +type INPUT struct { + Type uint32 + Mi MOUSEINPUT + Ki KEYBDINPUT + Hi HARDWAREINPUT +} + +// http://msdn.microsoft.com/en-us/library/windows/desktop/ms646273(v=vs.85).aspx +type MOUSEINPUT struct { + Dx int32 + Dy int32 + MouseData uint32 + DwFlags uint32 + Time uint32 + DwExtraInfo uintptr +} + +// http://msdn.microsoft.com/en-us/library/windows/desktop/ms646271(v=vs.85).aspx +type KEYBDINPUT struct { + WVk uint16 + WScan uint16 + DwFlags uint32 + Time uint32 + DwExtraInfo uintptr +} + +// http://msdn.microsoft.com/en-us/library/windows/desktop/ms646269(v=vs.85).aspx +type HARDWAREINPUT struct { + UMsg uint32 + WParamL uint16 + WParamH uint16 +} + +type KbdInput struct { + typ uint32 + ki KEYBDINPUT +} + +type MouseInput struct { + typ uint32 + mi MOUSEINPUT +} + +type HardwareInput struct { + typ uint32 + hi HARDWAREINPUT +} + +// http://msdn.microsoft.com/en-us/library/windows/desktop/ms724950(v=vs.85).aspx +type SYSTEMTIME struct { + Year uint16 + Month uint16 + DayOfWeek uint16 + Day uint16 + Hour uint16 + Minute uint16 + Second uint16 + Milliseconds uint16 +} + +// http://msdn.microsoft.com/en-us/library/windows/desktop/ms644967(v=vs.85).aspx +type KBDLLHOOKSTRUCT struct { + VkCode DWORD + ScanCode DWORD + Flags DWORD + Time DWORD + DwExtraInfo ULONG_PTR +} + +type HOOKPROC func(int, WPARAM, LPARAM) LRESULT + +type WINDOWPLACEMENT struct { + Length uint32 + Flags uint32 + ShowCmd uint32 + PtMinPosition POINT + PtMaxPosition POINT + RcNormalPosition RECT +} + +type SCROLLINFO struct { + CbSize uint32 + FMask uint32 + NMin int32 + NMax int32 + NPage uint32 + NPos int32 + NTrackPos int32 +} + +type FLASHWINFO struct { + CbSize uint32 + Hwnd HWND + DwFlags DWORD + UCount uint32 + DwTimeout DWORD +} diff --git a/v3/pkg/w32/user32.go b/v3/pkg/w32/user32.go new file mode 100644 index 000000000..bf4575fbf --- /dev/null +++ b/v3/pkg/w32/user32.go @@ -0,0 +1,1395 @@ +//go:build windows + +/* + * Copyright (C) 2019 Tad Vizbaras. All Rights Reserved. + * Copyright (C) 2010-2012 The W32 Authors. All Rights Reserved. + */ + +package w32 + +import ( + "fmt" + "golang.org/x/sys/windows" + "runtime" + "syscall" + "unsafe" +) + +var ( + moduser32 = syscall.NewLazyDLL("user32.dll") + + procRegisterClassEx = moduser32.NewProc("RegisterClassExW") + procGetClassName = moduser32.NewProc("GetClassNameW") + procLoadIcon = moduser32.NewProc("LoadIconW") + procLoadCursor = moduser32.NewProc("LoadCursorW") + procShowWindow = moduser32.NewProc("ShowWindow") + procGetDesktopWindow = moduser32.NewProc("GetDesktopWindow") + procShowWindowAsync = moduser32.NewProc("ShowWindowAsync") + procUpdateWindow = moduser32.NewProc("UpdateWindow") + procCreateWindowEx = moduser32.NewProc("CreateWindowExW") + procAdjustWindowRect = moduser32.NewProc("AdjustWindowRect") + procAdjustWindowRectEx = moduser32.NewProc("AdjustWindowRectEx") + procDestroyWindow = moduser32.NewProc("DestroyWindow") + procDefWindowProc = moduser32.NewProc("DefWindowProcW") + procDefDlgProc = moduser32.NewProc("DefDlgProcW") + procPostQuitMessage = moduser32.NewProc("PostQuitMessage") + procGetMessage = moduser32.NewProc("GetMessageW") + procTranslateMessage = moduser32.NewProc("TranslateMessage") + procDispatchMessage = moduser32.NewProc("DispatchMessageW") + procSendMessage = moduser32.NewProc("SendMessageW") + procPostMessage = moduser32.NewProc("PostMessageW") + procWaitMessage = moduser32.NewProc("WaitMessage") + procSetWindowText = moduser32.NewProc("SetWindowTextW") + procGetWindowTextLength = moduser32.NewProc("GetWindowTextLengthW") + procGetWindowText = moduser32.NewProc("GetWindowTextW") + procGetWindowRect = moduser32.NewProc("GetWindowRect") + procGetWindowInfo = moduser32.NewProc("GetWindowInfo") + procGetWindow = moduser32.NewProc("GetWindow") + procSetWindowCompositionAttribute = moduser32.NewProc("SetWindowCompositionAttribute") + procMoveWindow = moduser32.NewProc("MoveWindow") + procScreenToClient = moduser32.NewProc("ScreenToClient") + procCallWindowProc = moduser32.NewProc("CallWindowProcW") + procSetWindowLong = moduser32.NewProc("SetWindowLongW") + procSetWindowLongPtr = moduser32.NewProc("SetWindowLongW") + procGetWindowLong = moduser32.NewProc("GetWindowLongW") + procGetWindowLongPtr = moduser32.NewProc("GetWindowLongW") + procEnableWindow = moduser32.NewProc("EnableWindow") + procIsWindowEnabled = moduser32.NewProc("IsWindowEnabled") + procIsWindowVisible = moduser32.NewProc("IsWindowVisible") + procSetFocus = moduser32.NewProc("SetFocus") + procGetFocus = moduser32.NewProc("GetFocus") + procSetActiveWindow = moduser32.NewProc("SetActiveWindow") + procSetForegroundWindow = moduser32.NewProc("SetForegroundWindow") + procBringWindowToTop = moduser32.NewProc("BringWindowToTop") + procInvalidateRect = moduser32.NewProc("InvalidateRect") + procGetClientRect = moduser32.NewProc("GetClientRect") + procGetDC = moduser32.NewProc("GetDC") + procReleaseDC = moduser32.NewProc("ReleaseDC") + procSetCapture = moduser32.NewProc("SetCapture") + procReleaseCapture = moduser32.NewProc("ReleaseCapture") + procGetWindowThreadProcessId = moduser32.NewProc("GetWindowThreadProcessId") + procMessageBox = moduser32.NewProc("MessageBoxW") + procMessageBoxIndirect = moduser32.NewProc("MessageBoxIndirectW") + procGetSystemMetrics = moduser32.NewProc("GetSystemMetrics") + procPostThreadMessageW = moduser32.NewProc("PostThreadMessageW") + procRegisterWindowMessageA = moduser32.NewProc("RegisterWindowMessageA") + procCopyRect = moduser32.NewProc("CopyRect") + procEqualRect = moduser32.NewProc("EqualRect") + procInflateRect = moduser32.NewProc("InflateRect") + procIntersectRect = moduser32.NewProc("IntersectRect") + procIsRectEmpty = moduser32.NewProc("IsRectEmpty") + procOffsetRect = moduser32.NewProc("OffsetRect") + procPtInRect = moduser32.NewProc("PtInRect") + procSetRect = moduser32.NewProc("SetRect") + procSetRectEmpty = moduser32.NewProc("SetRectEmpty") + procSubtractRect = moduser32.NewProc("SubtractRect") + procUnionRect = moduser32.NewProc("UnionRect") + procCreateDialogParam = moduser32.NewProc("CreateDialogParamW") + procDialogBoxParam = moduser32.NewProc("DialogBoxParamW") + procGetDlgItem = moduser32.NewProc("GetDlgItem") + procDrawIcon = moduser32.NewProc("DrawIcon") + procCreateMenu = moduser32.NewProc("CreateMenu") + procRemoveMenu = moduser32.NewProc("RemoveMenu") + procGetMenuItemPosition = moduser32.NewProc("GetMenuItemPosition") + procDestroyMenu = moduser32.NewProc("DestroyMenu") + procCreatePopupMenu = moduser32.NewProc("CreatePopupMenu") + procCheckMenuRadioItem = moduser32.NewProc("CheckMenuRadioItem") + procCreateIconFromResourceEx = moduser32.NewProc("CreateIconFromResourceEx") + procInsertMenuItem = moduser32.NewProc("InsertMenuItemW") + procCheckMenuItem = moduser32.NewProc("CheckMenuItem") + procClientToScreen = moduser32.NewProc("ClientToScreen") + procIsDialogMessage = moduser32.NewProc("IsDialogMessageW") + procIsWindow = moduser32.NewProc("IsWindow") + procEndDialog = moduser32.NewProc("EndDialog") + procPeekMessage = moduser32.NewProc("PeekMessageW") + procTranslateAccelerator = moduser32.NewProc("TranslateAcceleratorW") + procSetWindowPos = moduser32.NewProc("SetWindowPos") + procFillRect = moduser32.NewProc("FillRect") + procDrawText = moduser32.NewProc("DrawTextW") + procAddClipboardFormatListener = moduser32.NewProc("AddClipboardFormatListener") + procRemoveClipboardFormatListener = moduser32.NewProc("RemoveClipboardFormatListener") + procOpenClipboard = moduser32.NewProc("OpenClipboard") + procCloseClipboard = moduser32.NewProc("CloseClipboard") + procEnumClipboardFormats = moduser32.NewProc("EnumClipboardFormats") + procGetClipboardData = moduser32.NewProc("GetClipboardData") + procSetClipboardData = moduser32.NewProc("SetClipboardData") + procEmptyClipboard = moduser32.NewProc("EmptyClipboard") + procGetClipboardFormatName = moduser32.NewProc("GetClipboardFormatNameW") + procIsClipboardFormatAvailable = moduser32.NewProc("IsClipboardFormatAvailable") + procBeginPaint = moduser32.NewProc("BeginPaint") + procEndPaint = moduser32.NewProc("EndPaint") + procGetKeyboardState = moduser32.NewProc("GetKeyboardState") + procMapVirtualKey = moduser32.NewProc("MapVirtualKeyW") + procMapVirtualKeyEx = moduser32.NewProc("MapVirtualKeyExW") + procGetAsyncKeyState = moduser32.NewProc("GetAsyncKeyState") + procToAscii = moduser32.NewProc("ToAscii") + procSwapMouseButton = moduser32.NewProc("SwapMouseButton") + procGetCursorPos = moduser32.NewProc("GetCursorPos") + procSetCursorPos = moduser32.NewProc("SetCursorPos") + procSetCursor = moduser32.NewProc("SetCursor") + procCreateIcon = moduser32.NewProc("CreateIcon") + procDestroyIcon = moduser32.NewProc("DestroyIcon") + procMonitorFromPoint = moduser32.NewProc("MonitorFromPoint") + procMonitorFromRect = moduser32.NewProc("MonitorFromRect") + procMonitorFromWindow = moduser32.NewProc("MonitorFromWindow") + procGetMonitorInfo = moduser32.NewProc("GetMonitorInfoW") + procGetDpiForSystem = moduser32.NewProc("GetDpiForSystem") + procGetDpiForWindow = moduser32.NewProc("GetDpiForWindow") + procSetProcessDPIAware = moduser32.NewProc("SetProcessDPIAware") + procEnumDisplayMonitors = moduser32.NewProc("EnumDisplayMonitors") + procEnumDisplayDevices = moduser32.NewProc("EnumDisplayDevicesW") + procEnumDisplaySettings = moduser32.NewProc("EnumDisplaySettingsW") + procEnumDisplaySettingsEx = moduser32.NewProc("EnumDisplaySettingsExW") + procEnumWindows = moduser32.NewProc("EnumWindows") + procEnumChildWindows = moduser32.NewProc("EnumChildWindows") + procChangeDisplaySettingsEx = moduser32.NewProc("ChangeDisplaySettingsExW") + procSendInput = moduser32.NewProc("SendInput") + procSetWindowsHookEx = moduser32.NewProc("SetWindowsHookExW") + procUnhookWindowsHookEx = moduser32.NewProc("UnhookWindowsHookEx") + procCallNextHookEx = moduser32.NewProc("CallNextHookEx") + procGetForegroundWindow = moduser32.NewProc("GetForegroundWindow") + procUpdateLayeredWindow = moduser32.NewProc("UpdateLayeredWindow") + getDisplayConfig = moduser32.NewProc("GetDisplayConfigBufferSizes") + queryDisplayConfig = moduser32.NewProc("QueryDisplayConfig") + + procSystemParametersInfo = moduser32.NewProc("SystemParametersInfoW") + procSetClassLong = moduser32.NewProc("SetClassLongW") + procSetClassLongPtr = moduser32.NewProc("SetClassLongPtrW") + + procSetMenu = moduser32.NewProc("SetMenu") + procAppendMenu = moduser32.NewProc("AppendMenuW") + procSetMenuItemInfo = moduser32.NewProc("SetMenuItemInfoW") + procDrawMenuBar = moduser32.NewProc("DrawMenuBar") + procTrackPopupMenuEx = moduser32.NewProc("TrackPopupMenuEx") + procGetKeyState = moduser32.NewProc("GetKeyState") + procGetSysColorBrush = moduser32.NewProc("GetSysColorBrush") + + procGetWindowPlacement = moduser32.NewProc("GetWindowPlacement") + procSetWindowPlacement = moduser32.NewProc("SetWindowPlacement") + + procGetScrollInfo = moduser32.NewProc("GetScrollInfo") + procSetScrollInfo = moduser32.NewProc("SetScrollInfo") + + procFlashWindowEx = moduser32.NewProc("FlashWindowEx") + + procSetMenuItemBitmaps = moduser32.NewProc("SetMenuItemBitmaps") + + mainThread HANDLE +) + +func init() { + runtime.LockOSThread() + mainThread = GetCurrentThreadId() +} + +func GET_X_LPARAM(lp uintptr) int32 { + return int32(int16(LOWORD(uint32(lp)))) +} + +func GET_Y_LPARAM(lp uintptr) int32 { + return int32(int16(HIWORD(uint32(lp)))) +} + +func RegisterClassEx(wndClassEx *WNDCLASSEX) ATOM { + ret, _, _ := procRegisterClassEx.Call(uintptr(unsafe.Pointer(wndClassEx))) + return ATOM(ret) +} + +func SetMenuItemBitmaps(hMenu HMENU, uPosition, uFlags uint32, hBitmapUnchecked HBITMAP, hBitmapChecked HBITMAP) error { + ret, _, _ := procSetMenuItemBitmaps.Call( + hMenu, + uintptr(uPosition), + uintptr(uFlags), + hBitmapUnchecked, + hBitmapChecked) + + if ret == 0 { + return windows.GetLastError() + } + return nil +} + +func GetDesktopWindow() HWND { + ret, _, _ := procGetDesktopWindow.Call() + return ret +} + +func LoadIcon(instance HINSTANCE, iconName *uint16) HICON { + ret, _, _ := procLoadIcon.Call( + uintptr(instance), + uintptr(unsafe.Pointer(iconName))) + + return HICON(ret) +} + +func LoadIconWithResourceID(instance HINSTANCE, res uint16) HICON { + ret, _, _ := procLoadIcon.Call( + uintptr(instance), + uintptr(res)) + + return HICON(ret) +} + +func LoadCursor(instance HINSTANCE, cursorName *uint16) HCURSOR { + ret, _, _ := procLoadCursor.Call( + uintptr(instance), + uintptr(unsafe.Pointer(cursorName))) + + return HCURSOR(ret) +} + +func LoadCursorWithResourceID(instance HINSTANCE, res uint16) HCURSOR { + ret, _, _ := procLoadCursor.Call( + uintptr(instance), + uintptr(res)) + + return HCURSOR(ret) +} + +func MessageBoxIndirect(msgbox *MSGBOXPARAMS) int32 { + ret, _, _ := procMessageBoxIndirect.Call( + uintptr(unsafe.Pointer(msgbox))) + + return int32(ret) +} + +func ShowWindow(hwnd HWND, cmdshow int) bool { + ret, _, _ := procShowWindow.Call( + uintptr(hwnd), + uintptr(cmdshow)) + + return ret != 0 +} + +func ShowWindowAsync(hwnd HWND, cmdshow int) bool { + ret, _, _ := procShowWindowAsync.Call( + uintptr(hwnd), + uintptr(cmdshow)) + + return ret != 0 +} + +func UpdateWindow(hwnd HWND) bool { + ret, _, _ := procUpdateWindow.Call( + uintptr(hwnd)) + return ret != 0 +} + +func UpdateLayeredWindow(hwnd HWND, hdcDst HDC, pptDst *POINT, psize *SIZE, + hdcSrc HDC, pptSrc *POINT, crKey COLORREF, pblend *BLENDFUNCTION, dwFlags DWORD) bool { + ret, _, _ := procUpdateLayeredWindow.Call( + hwnd, + hdcDst, + uintptr(unsafe.Pointer(pptDst)), + uintptr(unsafe.Pointer(psize)), + hdcSrc, + uintptr(unsafe.Pointer(pptSrc)), + uintptr(crKey), + uintptr(unsafe.Pointer(pblend)), + uintptr(dwFlags)) + return ret != 0 +} + +func PostThreadMessage(threadID HANDLE, msg int, wp, lp uintptr) { + procPostThreadMessageW.Call(threadID, uintptr(msg), wp, lp) +} + +func RegisterWindowMessage(name *uint16) uint32 { + ret, _, _ := procRegisterWindowMessageA.Call( + uintptr(unsafe.Pointer(name))) + + return uint32(ret) +} + +func PostMainThreadMessage(msg uint32, wp, lp uintptr) bool { + ret, _, _ := procPostThreadMessageW.Call(mainThread, uintptr(msg), wp, lp) + return ret != 0 +} + +func CreateWindowEx(exStyle uint, className, windowName *uint16, + style uint, x, y, width, height int, parent HWND, menu HMENU, + instance HINSTANCE, param unsafe.Pointer) HWND { + ret, _, _ := procCreateWindowEx.Call( + uintptr(exStyle), + uintptr(unsafe.Pointer(className)), + uintptr(unsafe.Pointer(windowName)), + uintptr(style), + uintptr(x), + uintptr(y), + uintptr(width), + uintptr(height), + uintptr(parent), + uintptr(menu), + uintptr(instance), + uintptr(param)) + + return HWND(ret) +} + +func AdjustWindowRectEx(rect *RECT, style uint, menu bool, exStyle uint) bool { + ret, _, _ := procAdjustWindowRectEx.Call( + uintptr(unsafe.Pointer(rect)), + uintptr(style), + uintptr(BoolToBOOL(menu)), + uintptr(exStyle)) + + return ret != 0 +} + +func AdjustWindowRect(rect *RECT, style uint, menu bool) bool { + ret, _, _ := procAdjustWindowRect.Call( + uintptr(unsafe.Pointer(rect)), + uintptr(style), + uintptr(BoolToBOOL(menu))) + + return ret != 0 +} + +func DestroyWindow(hwnd HWND) bool { + ret, _, _ := procDestroyWindow.Call(hwnd) + return ret != 0 +} + +func HasGetDpiForWindowFunc() bool { + err := procGetDpiForWindow.Find() + return err == nil +} + +func GetDpiForWindow(hwnd HWND) UINT { + dpi, _, _ := procGetDpiForWindow.Call(hwnd) + return uint(dpi) +} + +func GetClassName(hwnd HWND) string { + var buf [256]uint16 + procGetClassName.Call( + uintptr(hwnd), + uintptr(unsafe.Pointer(&buf[0])), + uintptr(len(buf))) + + return syscall.UTF16ToString(buf[:]) +} + +func SetProcessDPIAware() error { + status, r, err := procSetProcessDPIAware.Call() + if status == 0 { + return fmt.Errorf("SetProcessDPIAware failed %d: %v %v", status, r, err) + } + return nil +} + +func GetForegroundWindow() HWND { + ret, _, _ := procGetForegroundWindow.Call() + return HWND(ret) +} + +func SetWindowCompositionAttribute(hwnd HWND, data *WINDOWCOMPOSITIONATTRIBDATA) bool { + if procSetWindowCompositionAttribute != nil { + ret, _, _ := procSetWindowCompositionAttribute.Call( + hwnd, + uintptr(unsafe.Pointer(data)), + ) + return ret != 0 + } + return false +} + +func DefWindowProc(hwnd HWND, msg uint32, wParam, lParam uintptr) uintptr { + ret, _, _ := procDefWindowProc.Call( + uintptr(hwnd), + uintptr(msg), + wParam, + lParam) + + return ret +} + +func DefDlgProc(hwnd HWND, msg uint32, wParam, lParam uintptr) uintptr { + ret, _, _ := procDefDlgProc.Call( + uintptr(hwnd), + uintptr(msg), + wParam, + lParam) + + return ret +} + +func PostQuitMessage(exitCode int) { + procPostQuitMessage.Call( + uintptr(exitCode)) +} + +func GetMessage(msg *MSG, hwnd HWND, msgFilterMin, msgFilterMax uint32) int { + ret, _, _ := procGetMessage.Call( + uintptr(unsafe.Pointer(msg)), + uintptr(hwnd), + uintptr(msgFilterMin), + uintptr(msgFilterMax)) + + return int(ret) +} + +func TranslateMessage(msg *MSG) bool { + ret, _, _ := procTranslateMessage.Call( + uintptr(unsafe.Pointer(msg))) + + return ret != 0 + +} + +func DispatchMessage(msg *MSG) uintptr { + ret, _, _ := procDispatchMessage.Call( + uintptr(unsafe.Pointer(msg))) + + return ret + +} + +func SendMessage(hwnd HWND, msg uint32, wParam, lParam uintptr) uintptr { + ret, _, _ := procSendMessage.Call( + uintptr(hwnd), + uintptr(msg), + wParam, + lParam) + + return ret +} + +func PostMessage(hwnd HWND, msg uint32, wParam, lParam uintptr) bool { + ret, _, _ := procPostMessage.Call( + uintptr(hwnd), + uintptr(msg), + wParam, + lParam) + + return ret != 0 +} + +func WaitMessage() bool { + ret, _, _ := procWaitMessage.Call() + return ret != 0 +} + +func SetWindowText(hwnd HWND, text string) { + procSetWindowText.Call( + uintptr(hwnd), + uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(text)))) +} + +func GetWindowTextLength(hwnd HWND) int { + ret, _, _ := procGetWindowTextLength.Call( + uintptr(hwnd)) + + return int(ret) +} + +func GetWindowInfo(hwnd HWND, info *WINDOWINFO) int { + ret, _, _ := procGetWindowInfo.Call( + hwnd, + uintptr(unsafe.Pointer(info)), + ) + return int(ret) +} + +func GetWindow(hwnd HWND, cmd uint32) HWND { + ret, _, _ := procGetWindow.Call( + hwnd, + uintptr(cmd), + ) + return HWND(ret) +} + +func GetWindowText(hwnd HWND) string { + textLen := GetWindowTextLength(hwnd) + 1 + + buf := make([]uint16, textLen) + procGetWindowText.Call( + uintptr(hwnd), + uintptr(unsafe.Pointer(&buf[0])), + uintptr(textLen)) + + return syscall.UTF16ToString(buf) +} + +func GetWindowRect(hwnd HWND) *RECT { + var rect RECT + procGetWindowRect.Call( + hwnd, + uintptr(unsafe.Pointer(&rect))) + + return &rect +} + +func MoveWindow(hwnd HWND, x, y, width, height int, repaint bool) bool { + ret, _, _ := procMoveWindow.Call( + uintptr(hwnd), + uintptr(x), + uintptr(y), + uintptr(width), + uintptr(height), + uintptr(BoolToBOOL(repaint))) + + return ret != 0 + +} + +func ScreenToClient(hwnd HWND, x, y int) (X, Y int, ok bool) { + pt := POINT{X: int32(x), Y: int32(y)} + ret, _, _ := procScreenToClient.Call( + uintptr(hwnd), + uintptr(unsafe.Pointer(&pt))) + + return int(pt.X), int(pt.Y), ret != 0 +} + +func CallWindowProc(preWndProc uintptr, hwnd HWND, msg uint32, wParam, lParam uintptr) uintptr { + ret, _, _ := procCallWindowProc.Call( + preWndProc, + uintptr(hwnd), + uintptr(msg), + wParam, + lParam) + + return ret +} + +func SetWindowLong(hwnd HWND, index int, value uint32) uint32 { + ret, _, _ := procSetWindowLong.Call( + uintptr(hwnd), + uintptr(index), + uintptr(value)) + + return uint32(ret) +} + +func SetWindowLongPtr(hwnd HWND, index int, value uintptr) uintptr { + ret, _, _ := procSetWindowLongPtr.Call( + uintptr(hwnd), + uintptr(index), + value) + + return ret +} + +func GetWindowLong(hwnd HWND, index int) int32 { + ret, _, _ := procGetWindowLong.Call( + uintptr(hwnd), + uintptr(index)) + + return int32(ret) +} + +func GetWindowLongPtr(hwnd HWND, index int) uintptr { + ret, _, _ := procGetWindowLongPtr.Call( + uintptr(hwnd), + uintptr(index)) + + return ret +} + +func EnableWindow(hwnd HWND, b bool) bool { + ret, _, _ := procEnableWindow.Call( + uintptr(hwnd), + uintptr(BoolToBOOL(b))) + return ret != 0 +} + +func IsWindowEnabled(hwnd HWND) bool { + ret, _, _ := procIsWindowEnabled.Call( + uintptr(hwnd)) + + return ret != 0 +} + +func IsWindowVisible(hwnd HWND) bool { + ret, _, _ := procIsWindowVisible.Call( + uintptr(hwnd)) + + return ret != 0 +} + +func SetFocus(hwnd HWND) HWND { + ret, _, _ := procSetFocus.Call( + uintptr(hwnd)) + + return HWND(ret) +} + +func SetActiveWindow(hwnd HWND) HWND { + ret, _, _ := procSetActiveWindow.Call( + uintptr(hwnd)) + + return HWND(ret) +} + +func BringWindowToTop(hwnd HWND) bool { + ret, _, _ := procBringWindowToTop.Call(uintptr(hwnd)) + return ret != 0 +} + +func SetForegroundWindow(hwnd HWND) HWND { + ret, _, _ := procSetForegroundWindow.Call( + uintptr(hwnd)) + + return HWND(ret) +} + +func GetFocus() HWND { + ret, _, _ := procGetFocus.Call() + return HWND(ret) +} + +func InvalidateRect(hwnd HWND, rect *RECT, erase bool) bool { + ret, _, _ := procInvalidateRect.Call( + uintptr(hwnd), + uintptr(unsafe.Pointer(rect)), + uintptr(BoolToBOOL(erase))) + + return ret != 0 +} + +func GetClientRect(hwnd HWND) *RECT { + var rect RECT + ret, _, _ := procGetClientRect.Call( + uintptr(hwnd), + uintptr(unsafe.Pointer(&rect))) + + if ret == 0 { + panic(fmt.Sprintf("GetClientRect(%d) failed", hwnd)) + } + + return &rect +} + +func GetDC(hwnd HWND) HDC { + ret, _, _ := procGetDC.Call( + uintptr(hwnd)) + + return HDC(ret) +} + +func ReleaseDC(hwnd HWND, hDC HDC) bool { + ret, _, _ := procReleaseDC.Call( + uintptr(hwnd), + uintptr(hDC)) + + return ret != 0 +} + +func SetCapture(hwnd HWND) HWND { + ret, _, _ := procSetCapture.Call( + uintptr(hwnd)) + + return HWND(ret) +} + +func ReleaseCapture() bool { + ret, _, _ := procReleaseCapture.Call() + + return ret != 0 +} + +func EnumWindows(enumFunc uintptr, lparam uintptr) bool { + ret, _, _ := procEnumWindows.Call( + enumFunc, + lparam) + + return ret != 0 +} + +func GetWindowThreadProcessId(hwnd HWND) (HANDLE, int) { + var processId int + ret, _, _ := procGetWindowThreadProcessId.Call( + uintptr(hwnd), + uintptr(unsafe.Pointer(&processId))) + + return HANDLE(ret), processId +} + +func MessageBox(hwnd HWND, title, caption string, flags uint) int { + ret, _, _ := procMessageBox.Call( + uintptr(hwnd), + uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(title))), + uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(caption))), + uintptr(flags)) + + return int(ret) +} + +func GetSystemMetrics(index int) int { + ret, _, _ := procGetSystemMetrics.Call( + uintptr(index)) + + return int(ret) +} + +func GetSysColorBrush(nIndex int) HBRUSH { + ret, _, _ := procGetSysColorBrush.Call(1, + uintptr(nIndex), + 0, + 0) + + return HBRUSH(ret) +} + +func CopyRect(dst, src *RECT) bool { + ret, _, _ := procCopyRect.Call( + uintptr(unsafe.Pointer(dst)), + uintptr(unsafe.Pointer(src))) + + return ret != 0 +} + +func EqualRect(rect1, rect2 *RECT) bool { + ret, _, _ := procEqualRect.Call( + uintptr(unsafe.Pointer(rect1)), + uintptr(unsafe.Pointer(rect2))) + + return ret != 0 +} + +func InflateRect(rect *RECT, dx, dy int) bool { + ret, _, _ := procInflateRect.Call( + uintptr(unsafe.Pointer(rect)), + uintptr(dx), + uintptr(dy)) + + return ret != 0 +} + +func IntersectRect(dst, src1, src2 *RECT) bool { + ret, _, _ := procIntersectRect.Call( + uintptr(unsafe.Pointer(dst)), + uintptr(unsafe.Pointer(src1)), + uintptr(unsafe.Pointer(src2))) + + return ret != 0 +} + +func IsRectEmpty(rect *RECT) bool { + ret, _, _ := procIsRectEmpty.Call( + uintptr(unsafe.Pointer(rect))) + + return ret != 0 +} + +func OffsetRect(rect *RECT, dx, dy int) bool { + ret, _, _ := procOffsetRect.Call( + uintptr(unsafe.Pointer(rect)), + uintptr(dx), + uintptr(dy)) + + return ret != 0 +} + +func PtInRect(rect *RECT, x, y int) bool { + pt := POINT{X: int32(x), Y: int32(y)} + ret, _, _ := procPtInRect.Call( + uintptr(unsafe.Pointer(rect)), + uintptr(unsafe.Pointer(&pt))) + + return ret != 0 +} + +func RectInRect(rect1, rect2 *RECT) bool { + return rect1.Left >= rect2.Left && rect1.Right <= rect2.Right && + rect1.Top >= rect2.Top && rect1.Bottom <= rect2.Bottom +} + +func SetRect(rect *RECT, left, top, right, bottom int) bool { + ret, _, _ := procSetRect.Call( + uintptr(unsafe.Pointer(rect)), + uintptr(left), + uintptr(top), + uintptr(right), + uintptr(bottom)) + + return ret != 0 +} + +func SetRectEmpty(rect *RECT) bool { + ret, _, _ := procSetRectEmpty.Call( + uintptr(unsafe.Pointer(rect))) + + return ret != 0 +} + +func SubtractRect(dst, src1, src2 *RECT) bool { + ret, _, _ := procSubtractRect.Call( + uintptr(unsafe.Pointer(dst)), + uintptr(unsafe.Pointer(src1)), + uintptr(unsafe.Pointer(src2))) + + return ret != 0 +} + +func UnionRect(dst, src1, src2 *RECT) bool { + ret, _, _ := procUnionRect.Call( + uintptr(unsafe.Pointer(dst)), + uintptr(unsafe.Pointer(src1)), + uintptr(unsafe.Pointer(src2))) + + return ret != 0 +} + +func CreateDialog(hInstance HINSTANCE, lpTemplate *uint16, hWndParent HWND, lpDialogProc uintptr) HWND { + ret, _, _ := procCreateDialogParam.Call( + uintptr(hInstance), + uintptr(unsafe.Pointer(lpTemplate)), + uintptr(hWndParent), + lpDialogProc, + 0) + + return HWND(ret) +} + +func DialogBox(hInstance HINSTANCE, lpTemplateName *uint16, hWndParent HWND, lpDialogProc uintptr) int { + ret, _, _ := procDialogBoxParam.Call( + uintptr(hInstance), + uintptr(unsafe.Pointer(lpTemplateName)), + uintptr(hWndParent), + lpDialogProc, + 0) + + return int(ret) +} + +func GetDlgItem(hDlg HWND, nIDDlgItem int) HWND { + ret, _, _ := procGetDlgItem.Call( + uintptr(unsafe.Pointer(hDlg)), + uintptr(nIDDlgItem)) + + return HWND(ret) +} + +func DrawIcon(hDC HDC, x, y int, hIcon HICON) bool { + ret, _, _ := procDrawIcon.Call( + uintptr(unsafe.Pointer(hDC)), + uintptr(x), + uintptr(y), + uintptr(unsafe.Pointer(hIcon))) + + return ret != 0 +} + +func CreateMenu() HMENU { + ret, _, _ := procCreateMenu.Call(0, + 0, + 0, + 0) + + return HMENU(ret) +} + +func SetMenu(hWnd HWND, hMenu HMENU) bool { + + ret, _, _ := procSetMenu.Call(hWnd, hMenu) + return ret != 0 +} + +func AppendMenu(hMenu HMENU, uFlags uint32, uIDNewItem uintptr, lpNewItem *uint16) bool { + ret, _, _ := procAppendMenu.Call( + hMenu, + uintptr(uFlags), + uIDNewItem, + uintptr(unsafe.Pointer(lpNewItem))) + + return ret != 0 +} + +// https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-checkmenuradioitem +func SelectRadioMenuItem(menuID uint16, startID uint16, endID uint16, hwnd HWND) bool { + ret, _, _ := procCheckMenuRadioItem.Call( + hwnd, + uintptr(startID), + uintptr(endID), + uintptr(menuID), + MF_BYCOMMAND) + return ret != 0 + +} + +func CreatePopupMenu() PopupMenu { + ret, _, _ := procCreatePopupMenu.Call(0, + 0, + 0, + 0) + + return PopupMenu(ret) +} + +func TrackPopupMenuEx(hMenu HMENU, fuFlags uint32, x, y int32, hWnd HWND, lptpm *TPMPARAMS) bool { + + ret, _, _ := procTrackPopupMenuEx.Call( + hMenu, + uintptr(fuFlags), + uintptr(x), + uintptr(y), + hWnd, + uintptr(unsafe.Pointer(lptpm))) + + return ret != 0 +} + +func DrawMenuBar(hWnd HWND) bool { + ret, _, _ := procDrawMenuBar.Call(hWnd, 0, 0) + return ret != 0 +} + +func InsertMenuItem(hMenu HMENU, uItem uint32, fByPosition bool, lpmii *MENUITEMINFO) bool { + ret, _, _ := procInsertMenuItem.Call( + hMenu, + uintptr(uItem), + uintptr(BoolToBOOL(fByPosition)), + uintptr(unsafe.Pointer(lpmii)), + 0, + 0) + + return ret != 0 +} + +func SetMenuItemInfo(hMenu HMENU, uItem uint32, fByPosition bool, lpmii *MENUITEMINFO) bool { + ret, _, _ := procSetMenuItemInfo.Call( + hMenu, + uintptr(uItem), + uintptr(BoolToBOOL(fByPosition)), + uintptr(unsafe.Pointer(lpmii)), + 0, + 0) + + return ret != 0 +} + +func ClientToScreen(hwnd HWND, x, y int) (int, int) { + pt := POINT{X: int32(x), Y: int32(y)} + + procClientToScreen.Call( + uintptr(hwnd), + uintptr(unsafe.Pointer(&pt))) + + return int(pt.X), int(pt.Y) +} + +func IsDialogMessage(hwnd HWND, msg *MSG) bool { + ret, _, _ := procIsDialogMessage.Call( + uintptr(hwnd), + uintptr(unsafe.Pointer(msg))) + + return ret != 0 +} + +func IsWindow(hwnd HWND) bool { + ret, _, _ := procIsWindow.Call( + uintptr(hwnd)) + + return ret != 0 +} + +func EndDialog(hwnd HWND, nResult uintptr) bool { + ret, _, _ := procEndDialog.Call( + uintptr(hwnd), + nResult) + + return ret != 0 +} + +func PeekMessage(lpMsg *MSG, hwnd HWND, wMsgFilterMin, wMsgFilterMax, wRemoveMsg uint32) bool { + ret, _, _ := procPeekMessage.Call( + uintptr(unsafe.Pointer(lpMsg)), + uintptr(hwnd), + uintptr(wMsgFilterMin), + uintptr(wMsgFilterMax), + uintptr(wRemoveMsg)) + + return ret != 0 +} + +func TranslateAccelerator(hwnd HWND, hAccTable HACCEL, lpMsg *MSG) bool { + ret, _, _ := procTranslateMessage.Call( + uintptr(hwnd), + uintptr(hAccTable), + uintptr(unsafe.Pointer(lpMsg))) + + return ret != 0 +} + +func SetWindowPos(hwnd, hWndInsertAfter HWND, x, y, cx, cy int, uFlags uint) bool { + ret, _, _ := procSetWindowPos.Call( + uintptr(hwnd), + uintptr(hWndInsertAfter), + uintptr(x), + uintptr(y), + uintptr(cx), + uintptr(cy), + uintptr(uFlags)) + + return ret != 0 +} + +func FillRect(hDC HDC, lprc *RECT, hbr HBRUSH) bool { + ret, _, _ := procFillRect.Call( + uintptr(hDC), + uintptr(unsafe.Pointer(lprc)), + uintptr(hbr)) + + return ret != 0 +} + +func DrawText(hDC HDC, text string, uCount int, lpRect *RECT, uFormat uint) int { + ret, _, _ := procDrawText.Call( + uintptr(hDC), + uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(text))), + uintptr(uCount), + uintptr(unsafe.Pointer(lpRect)), + uintptr(uFormat)) + + return int(ret) +} + +func AddClipboardFormatListener(hwnd HWND) bool { + ret, _, _ := procAddClipboardFormatListener.Call( + uintptr(hwnd)) + return ret != 0 +} + +func RemoveClipboardFormatListener(hwnd HWND) bool { + ret, _, _ := procRemoveClipboardFormatListener.Call( + uintptr(hwnd)) + return ret != 0 +} + +func OpenClipboard(hWndNewOwner HWND) bool { + ret, _, _ := procOpenClipboard.Call( + uintptr(hWndNewOwner)) + return ret != 0 +} + +func CloseClipboard() bool { + ret, _, _ := procCloseClipboard.Call() + return ret != 0 +} + +func EnumClipboardFormats(format uint) uint { + ret, _, _ := procEnumClipboardFormats.Call( + uintptr(format)) + return uint(ret) +} + +func GetClipboardData(uFormat uint) HANDLE { + ret, _, _ := procGetClipboardData.Call( + uintptr(uFormat)) + return HANDLE(ret) +} + +func SetClipboardData(uFormat uint, hMem HANDLE) HANDLE { + ret, _, _ := procSetClipboardData.Call( + uintptr(uFormat), + uintptr(hMem)) + return HANDLE(ret) +} + +func EmptyClipboard() bool { + ret, _, _ := procEmptyClipboard.Call() + return ret != 0 +} + +func GetClipboardFormatName(format uint) (string, bool) { + cchMaxCount := 255 + buf := make([]uint16, cchMaxCount) + ret, _, _ := procGetClipboardFormatName.Call( + uintptr(format), + uintptr(unsafe.Pointer(&buf[0])), + uintptr(cchMaxCount)) + + if ret > 0 { + return syscall.UTF16ToString(buf), true + } + + return "Requested format does not exist or is predefined", false +} + +func IsClipboardFormatAvailable(format uint) bool { + ret, _, _ := procIsClipboardFormatAvailable.Call(uintptr(format)) + return ret != 0 +} + +func BeginPaint(hwnd HWND, paint *PAINTSTRUCT) HDC { + ret, _, _ := procBeginPaint.Call( + uintptr(hwnd), + uintptr(unsafe.Pointer(paint))) + return HDC(ret) +} + +func EndPaint(hwnd HWND, paint *PAINTSTRUCT) { + procEndPaint.Call( + uintptr(hwnd), + uintptr(unsafe.Pointer(paint))) +} + +func GetKeyboardState(keyState []byte) bool { + if len(keyState) != 256 { + panic("keyState slice must have a size of 256 bytes") + } + ret, _, _ := procGetKeyboardState.Call(uintptr(unsafe.Pointer(&keyState[0]))) + return ret != 0 +} + +func MapVirtualKeyEx(uCode, uMapType uint, dwhkl HKL) uint { + ret, _, _ := procMapVirtualKeyEx.Call( + uintptr(uCode), + uintptr(uMapType), + uintptr(dwhkl)) + return uint(ret) +} + +func MapVirtualKey(uCode uint, uMapType uint) uint { + ret, _, _ := procMapVirtualKey.Call(uintptr(uCode), uintptr(uMapType)) + return uint(ret) +} + +func GetAsyncKeyState(vKey int) uint16 { + ret, _, _ := procGetAsyncKeyState.Call(uintptr(vKey)) + return uint16(ret) +} + +func ToAscii(uVirtKey, uScanCode uint, lpKeyState *byte, lpChar *uint16, uFlags uint) int { + ret, _, _ := procToAscii.Call( + uintptr(uVirtKey), + uintptr(uScanCode), + uintptr(unsafe.Pointer(lpKeyState)), + uintptr(unsafe.Pointer(lpChar)), + uintptr(uFlags)) + return int(ret) +} + +func SwapMouseButton(fSwap bool) bool { + ret, _, _ := procSwapMouseButton.Call( + uintptr(BoolToBOOL(fSwap))) + return ret != 0 +} + +func GetCursorPos() (x, y int, ok bool) { + pt := POINT{} + ret, _, _ := procGetCursorPos.Call(uintptr(unsafe.Pointer(&pt))) + return int(pt.X), int(pt.Y), ret != 0 +} + +func SetCursorPos(x, y int) bool { + ret, _, _ := procSetCursorPos.Call( + uintptr(x), + uintptr(y), + ) + return ret != 0 +} + +func SetCursor(cursor HCURSOR) HCURSOR { + ret, _, _ := procSetCursor.Call( + uintptr(cursor), + ) + return HCURSOR(ret) +} + +func CreateIcon(instance HINSTANCE, nWidth, nHeight int, cPlanes, cBitsPerPixel byte, ANDbits, XORbits *byte) HICON { + ret, _, _ := procCreateIcon.Call( + uintptr(instance), + uintptr(nWidth), + uintptr(nHeight), + uintptr(cPlanes), + uintptr(cBitsPerPixel), + uintptr(unsafe.Pointer(ANDbits)), + uintptr(unsafe.Pointer(XORbits)), + ) + return HICON(ret) +} + +func DestroyIcon(icon HICON) bool { + ret, _, _ := procDestroyIcon.Call( + uintptr(icon), + ) + return ret != 0 +} + +func MonitorFromPoint(x, y int, dwFlags uint32) HMONITOR { + ret, _, _ := procMonitorFromPoint.Call( + uintptr(x), + uintptr(y), + uintptr(dwFlags), + ) + return HMONITOR(ret) +} + +func MonitorFromRect(rc *RECT, dwFlags uint32) HMONITOR { + ret, _, _ := procMonitorFromRect.Call( + uintptr(unsafe.Pointer(rc)), + uintptr(dwFlags), + ) + return HMONITOR(ret) +} + +func MonitorFromWindow(hwnd HWND, dwFlags uint32) HMONITOR { + ret, _, _ := procMonitorFromWindow.Call( + uintptr(hwnd), + uintptr(dwFlags), + ) + return HMONITOR(ret) +} + +func GetMonitorInfo(hMonitor HMONITOR, lmpi *MONITORINFO) bool { + ret, _, _ := procGetMonitorInfo.Call( + uintptr(hMonitor), + uintptr(unsafe.Pointer(lmpi)), + ) + return ret != 0 +} + +func GetMonitorInfoEx(hMonitor HMONITOR, lmpi *MONITORINFOEX) bool { + ret, _, _ := procGetMonitorInfo.Call( + uintptr(hMonitor), + uintptr(unsafe.Pointer(lmpi)), + ) + return ret != 0 +} + +func EnumDisplayMonitors(hdc HDC, clip *RECT, fnEnum uintptr, dwData unsafe.Pointer) bool { + ret, _, _ := procEnumDisplayMonitors.Call( + hdc, + uintptr(unsafe.Pointer(clip)), + fnEnum, + uintptr(dwData), + ) + return ret != 0 +} + +func EnumDisplaySettingsEx(szDeviceName *uint16, iModeNum uint32, devMode *DEVMODE, dwFlags uint32) bool { + ret, _, _ := procEnumDisplaySettingsEx.Call( + uintptr(unsafe.Pointer(szDeviceName)), + uintptr(iModeNum), + uintptr(unsafe.Pointer(devMode)), + uintptr(dwFlags), + ) + return ret != 0 +} + +func ChangeDisplaySettingsEx(szDeviceName *uint16, devMode *DEVMODE, hwnd HWND, dwFlags uint32, lParam uintptr) int32 { + ret, _, _ := procChangeDisplaySettingsEx.Call( + uintptr(unsafe.Pointer(szDeviceName)), + uintptr(unsafe.Pointer(devMode)), + uintptr(hwnd), + uintptr(dwFlags), + lParam, + ) + return int32(ret) +} + +/* +func SendInput(inputs []INPUT) uint32 { + var validInputs []C.INPUT + + for _, oneInput := range inputs { + input := C.INPUT{_type: C.DWORD(oneInput.Type)} + + switch oneInput.Type { + case INPUT_MOUSE: + (*MouseInput)(unsafe.Pointer(&input)).mi = oneInput.Mi + case INPUT_KEYBOARD: + (*KbdInput)(unsafe.Pointer(&input)).ki = oneInput.Ki + case INPUT_HARDWARE: + (*HardwareInput)(unsafe.Pointer(&input)).hi = oneInput.Hi + default: + panic("unkown type") + } + + validInputs = append(validInputs, input) + } + + ret, _, _ := procSendInput.Call( + uintptr(len(validInputs)), + uintptr(unsafe.Pointer(&validInputs[0])), + uintptr(unsafe.Sizeof(C.INPUT{})), + ) + return uint32(ret) +}*/ + +func SetWindowsHookEx(idHook int, lpfn HOOKPROC, hMod HINSTANCE, dwThreadId DWORD) HHOOK { + ret, _, _ := procSetWindowsHookEx.Call( + uintptr(idHook), + uintptr(syscall.NewCallback(lpfn)), + uintptr(hMod), + uintptr(dwThreadId), + ) + return HHOOK(ret) +} + +func UnhookWindowsHookEx(hhk HHOOK) bool { + ret, _, _ := procUnhookWindowsHookEx.Call( + uintptr(hhk), + ) + return ret != 0 +} + +func CallNextHookEx(hhk HHOOK, nCode int, wParam WPARAM, lParam LPARAM) LRESULT { + ret, _, _ := procCallNextHookEx.Call( + uintptr(hhk), + uintptr(nCode), + uintptr(wParam), + uintptr(lParam), + ) + return LRESULT(ret) +} + +func GetKeyState(nVirtKey int32) int16 { + ret, _, _ := procGetKeyState.Call( + uintptr(nVirtKey), + 0, + 0) + + return int16(ret) +} + +func DestroyMenu(hMenu HMENU) bool { + ret, _, _ := procDestroyMenu.Call(1, + uintptr(hMenu), + 0, + 0) + + return ret != 0 +} + +func GetWindowPlacement(hWnd HWND, lpwndpl *WINDOWPLACEMENT) bool { + ret, _, _ := procGetWindowPlacement.Call( + uintptr(hWnd), + uintptr(unsafe.Pointer(lpwndpl)), + 0) + + return ret != 0 +} + +func SetWindowPlacement(hWnd HWND, lpwndpl *WINDOWPLACEMENT) bool { + ret, _, _ := procSetWindowPlacement.Call( + uintptr(hWnd), + uintptr(unsafe.Pointer(lpwndpl)), + 0) + + return ret != 0 +} + +func SetScrollInfo(hwnd HWND, fnBar int32, lpsi *SCROLLINFO, fRedraw bool) int32 { + ret, _, _ := procSetScrollInfo.Call( + hwnd, + uintptr(fnBar), + uintptr(unsafe.Pointer(lpsi)), + uintptr(BoolToBOOL(fRedraw)), + 0, + 0) + + return int32(ret) +} + +func GetScrollInfo(hwnd HWND, fnBar int32, lpsi *SCROLLINFO) bool { + ret, _, _ := procGetScrollInfo.Call( + hwnd, + uintptr(fnBar), + uintptr(unsafe.Pointer(lpsi))) + + return ret != 0 +} diff --git a/v3/pkg/w32/utils.go b/v3/pkg/w32/utils.go new file mode 100644 index 000000000..1f36480c0 --- /dev/null +++ b/v3/pkg/w32/utils.go @@ -0,0 +1,648 @@ +//go:build windows + +/* + * Copyright (C) 2019 Tad Vizbaras. All Rights Reserved. + * Copyright (C) 2010-2012 The W32 Authors. All Rights Reserved. + */ + +package w32 + +import ( + "fmt" + "os" + "runtime/debug" + "strconv" + "syscall" + "unicode/utf16" + "unsafe" +) + +func MustLoadLibrary(name string) uintptr { + lib, err := syscall.LoadLibrary(name) + if err != nil { + panic(err) + } + + return uintptr(lib) +} + +func MustGetProcAddress(lib uintptr, name string) uintptr { + addr, err := syscall.GetProcAddress(syscall.Handle(lib), name) + if err != nil { + panic(err) + } + + return uintptr(addr) +} + +func SUCCEEDED(hr HRESULT) bool { + return hr >= 0 +} + +func FAILED(hr HRESULT) bool { + return hr < 0 +} + +func LOWORD(dw uint32) uint16 { + return uint16(dw) +} + +func HIWORD(dw uint32) uint16 { + return uint16(dw >> 16 & 0xffff) +} + +func MAKELONG(lo, hi uint16) uint32 { + return uint32(uint32(lo) | ((uint32(hi)) << 16)) +} + +func BoolToBOOL(value bool) BOOL { + if value { + return 1 + } + + return 0 +} + +func UTF16PtrToString(cstr *uint16) string { + if cstr != nil { + us := make([]uint16, 0, 256) + for p := uintptr(unsafe.Pointer(cstr)); ; p += 2 { + u := *(*uint16)(unsafe.Pointer(p)) + if u == 0 { + return string(utf16.Decode(us)) + } + us = append(us, u) + } + } + + return "" +} + +func ComAddRef(unknown *IUnknown) int32 { + ret, _, _ := syscall.Syscall(uintptr(unknown.Vtbl.AddRef), 1, + uintptr(unsafe.Pointer(unknown)), + 0, + 0) + return int32(ret) +} + +func ComRelease(unknown *IUnknown) int32 { + ret, _, _ := syscall.Syscall(uintptr(unknown.Vtbl.Release), 1, + uintptr(unsafe.Pointer(unknown)), + 0, + 0) + return int32(ret) +} + +func ComQueryInterface(unknown *IUnknown, id *GUID) *IDispatch { + var disp *IDispatch + hr, _, _ := syscall.Syscall(uintptr(unknown.Vtbl.QueryInterface), 3, + uintptr(unsafe.Pointer(unknown)), + uintptr(unsafe.Pointer(id)), + uintptr(unsafe.Pointer(&disp))) + if hr != 0 { + panic("Invoke QieryInterface error.") + } + return disp +} + +func ComGetIDsOfName(disp *IDispatch, names []string) []int32 { + wnames := make([]*uint16, len(names)) + dispid := make([]int32, len(names)) + for i := 0; i < len(names); i++ { + wnames[i] = syscall.StringToUTF16Ptr(names[i]) + } + hr, _, _ := syscall.Syscall6(disp.lpVtbl.pGetIDsOfNames, 6, + uintptr(unsafe.Pointer(disp)), + uintptr(unsafe.Pointer(IID_NULL)), + uintptr(unsafe.Pointer(&wnames[0])), + uintptr(len(names)), + uintptr(GetUserDefaultLCID()), + uintptr(unsafe.Pointer(&dispid[0]))) + if hr != 0 { + panic("Invoke GetIDsOfName error.") + } + return dispid +} + +func ComInvoke(disp *IDispatch, dispid int32, dispatch int16, params ...interface{}) (result *VARIANT) { + var dispparams DISPPARAMS + + if dispatch&DISPATCH_PROPERTYPUT != 0 { + dispnames := [1]int32{DISPID_PROPERTYPUT} + dispparams.RgdispidNamedArgs = uintptr(unsafe.Pointer(&dispnames[0])) + dispparams.CNamedArgs = 1 + } + var vargs []VARIANT + if len(params) > 0 { + vargs = make([]VARIANT, len(params)) + for i, v := range params { + //n := len(params)-i-1 + n := len(params) - i - 1 + VariantInit(&vargs[n]) + switch v.(type) { + case bool: + if v.(bool) { + vargs[n] = VARIANT{VT_BOOL, 0, 0, 0, 0xffff} + } else { + vargs[n] = VARIANT{VT_BOOL, 0, 0, 0, 0} + } + case *bool: + vargs[n] = VARIANT{VT_BOOL | VT_BYREF, 0, 0, 0, int64(uintptr(unsafe.Pointer(v.(*bool))))} + case byte: + vargs[n] = VARIANT{VT_I1, 0, 0, 0, int64(v.(byte))} + case *byte: + vargs[n] = VARIANT{VT_I1 | VT_BYREF, 0, 0, 0, int64(uintptr(unsafe.Pointer(v.(*byte))))} + case int16: + vargs[n] = VARIANT{VT_I2, 0, 0, 0, int64(v.(int16))} + case *int16: + vargs[n] = VARIANT{VT_I2 | VT_BYREF, 0, 0, 0, int64(uintptr(unsafe.Pointer(v.(*int16))))} + case uint16: + vargs[n] = VARIANT{VT_UI2, 0, 0, 0, int64(v.(int16))} + case *uint16: + vargs[n] = VARIANT{VT_UI2 | VT_BYREF, 0, 0, 0, int64(uintptr(unsafe.Pointer(v.(*uint16))))} + case int, int32: + vargs[n] = VARIANT{VT_UI4, 0, 0, 0, int64(v.(int))} + case *int, *int32: + vargs[n] = VARIANT{VT_I4 | VT_BYREF, 0, 0, 0, int64(uintptr(unsafe.Pointer(v.(*int))))} + case uint, uint32: + vargs[n] = VARIANT{VT_UI4, 0, 0, 0, int64(v.(uint))} + case *uint, *uint32: + vargs[n] = VARIANT{VT_UI4 | VT_BYREF, 0, 0, 0, int64(uintptr(unsafe.Pointer(v.(*uint))))} + case int64: + vargs[n] = VARIANT{VT_I8, 0, 0, 0, v.(int64)} + case *int64: + vargs[n] = VARIANT{VT_I8 | VT_BYREF, 0, 0, 0, int64(uintptr(unsafe.Pointer(v.(*int64))))} + case uint64: + vargs[n] = VARIANT{VT_UI8, 0, 0, 0, int64(v.(uint64))} + case *uint64: + vargs[n] = VARIANT{VT_UI8 | VT_BYREF, 0, 0, 0, int64(uintptr(unsafe.Pointer(v.(*uint64))))} + case float32: + vargs[n] = VARIANT{VT_R4, 0, 0, 0, int64(v.(float32))} + case *float32: + vargs[n] = VARIANT{VT_R4 | VT_BYREF, 0, 0, 0, int64(uintptr(unsafe.Pointer(v.(*float32))))} + case float64: + vargs[n] = VARIANT{VT_R8, 0, 0, 0, int64(v.(float64))} + case *float64: + vargs[n] = VARIANT{VT_R8 | VT_BYREF, 0, 0, 0, int64(uintptr(unsafe.Pointer(v.(*float64))))} + case string: + vargs[n] = VARIANT{VT_BSTR, 0, 0, 0, int64(uintptr(unsafe.Pointer(SysAllocString(v.(string)))))} + case *string: + vargs[n] = VARIANT{VT_BSTR | VT_BYREF, 0, 0, 0, int64(uintptr(unsafe.Pointer(v.(*string))))} + case *IDispatch: + vargs[n] = VARIANT{VT_DISPATCH, 0, 0, 0, int64(uintptr(unsafe.Pointer(v.(*IDispatch))))} + case **IDispatch: + vargs[n] = VARIANT{VT_DISPATCH | VT_BYREF, 0, 0, 0, int64(uintptr(unsafe.Pointer(v.(**IDispatch))))} + case nil: + vargs[n] = VARIANT{VT_NULL, 0, 0, 0, 0} + case *VARIANT: + vargs[n] = VARIANT{VT_VARIANT | VT_BYREF, 0, 0, 0, int64(uintptr(unsafe.Pointer(v.(*VARIANT))))} + default: + panic("unknown type") + } + } + dispparams.Rgvarg = uintptr(unsafe.Pointer(&vargs[0])) + dispparams.CArgs = uint32(len(params)) + } + + var ret VARIANT + var excepInfo EXCEPINFO + VariantInit(&ret) + hr, _, _ := syscall.Syscall9(disp.lpVtbl.pInvoke, 8, + uintptr(unsafe.Pointer(disp)), + uintptr(dispid), + uintptr(unsafe.Pointer(IID_NULL)), + uintptr(GetUserDefaultLCID()), + uintptr(dispatch), + uintptr(unsafe.Pointer(&dispparams)), + uintptr(unsafe.Pointer(&ret)), + uintptr(unsafe.Pointer(&excepInfo)), + 0) + if hr != 0 { + if excepInfo.BstrDescription != nil { + bs := UTF16PtrToString(excepInfo.BstrDescription) + panic(bs) + } + } + for _, varg := range vargs { + if varg.VT == VT_BSTR && varg.Val != 0 { + SysFreeString(((*int16)(unsafe.Pointer(uintptr(varg.Val))))) + } + } + result = &ret + return +} + +func WMMessageToString(msg uintptr) string { + // Convert windows message to string + switch msg { + case WM_APP: + return "WM_APP" + case WM_ACTIVATE: + return "WM_ACTIVATE" + case WM_ACTIVATEAPP: + return "WM_ACTIVATEAPP" + case WM_AFXFIRST: + return "WM_AFXFIRST" + case WM_AFXLAST: + return "WM_AFXLAST" + case WM_ASKCBFORMATNAME: + return "WM_ASKCBFORMATNAME" + case WM_CANCELJOURNAL: + return "WM_CANCELJOURNAL" + case WM_CANCELMODE: + return "WM_CANCELMODE" + case WM_CAPTURECHANGED: + return "WM_CAPTURECHANGED" + case WM_CHANGECBCHAIN: + return "WM_CHANGECBCHAIN" + case WM_CHAR: + return "WM_CHAR" + case WM_CHARTOITEM: + return "WM_CHARTOITEM" + case WM_CHILDACTIVATE: + return "WM_CHILDACTIVATE" + case WM_CLEAR: + return "WM_CLEAR" + case WM_CLOSE: + return "WM_CLOSE" + case WM_COMMAND: + return "WM_COMMAND" + case WM_COMMNOTIFY /* OBSOLETE */ : + return "WM_COMMNOTIFY" + case WM_COMPACTING: + return "WM_COMPACTING" + case WM_COMPAREITEM: + return "WM_COMPAREITEM" + case WM_CONTEXTMENU: + return "WM_CONTEXTMENU" + case WM_COPY: + return "WM_COPY" + case WM_COPYDATA: + return "WM_COPYDATA" + case WM_CREATE: + return "WM_CREATE" + case WM_CTLCOLORBTN: + return "WM_CTLCOLORBTN" + case WM_CTLCOLORDLG: + return "WM_CTLCOLORDLG" + case WM_CTLCOLOREDIT: + return "WM_CTLCOLOREDIT" + case WM_CTLCOLORLISTBOX: + return "WM_CTLCOLORLISTBOX" + case WM_CTLCOLORMSGBOX: + return "WM_CTLCOLORMSGBOX" + case WM_CTLCOLORSCROLLBAR: + return "WM_CTLCOLORSCROLLBAR" + case WM_CTLCOLORSTATIC: + return "WM_CTLCOLORSTATIC" + case WM_CUT: + return "WM_CUT" + case WM_DEADCHAR: + return "WM_DEADCHAR" + case WM_DELETEITEM: + return "WM_DELETEITEM" + case WM_DESTROY: + return "WM_DESTROY" + case WM_DESTROYCLIPBOARD: + return "WM_DESTROYCLIPBOARD" + case WM_DEVICECHANGE: + return "WM_DEVICECHANGE" + case WM_DEVMODECHANGE: + return "WM_DEVMODECHANGE" + case WM_DISPLAYCHANGE: + return "WM_DISPLAYCHANGE" + case WM_DRAWCLIPBOARD: + return "WM_DRAWCLIPBOARD" + case WM_DRAWITEM: + return "WM_DRAWITEM" + case WM_DROPFILES: + return "WM_DROPFILES" + case WM_ENABLE: + return "WM_ENABLE" + case WM_ENDSESSION: + return "WM_ENDSESSION" + case WM_ENTERIDLE: + return "WM_ENTERIDLE" + case WM_ENTERMENULOOP: + return "WM_ENTERMENULOOP" + case WM_ENTERSIZEMOVE: + return "WM_ENTERSIZEMOVE" + case WM_ERASEBKGND: + return "WM_ERASEBKGND" + case WM_EXITMENULOOP: + return "WM_EXITMENULOOP" + case WM_EXITSIZEMOVE: + return "WM_EXITSIZEMOVE" + case WM_FONTCHANGE: + return "WM_FONTCHANGE" + case WM_GETDLGCODE: + return "WM_GETDLGCODE" + case WM_GETFONT: + return "WM_GETFONT" + case WM_GETHOTKEY: + return "WM_GETHOTKEY" + case WM_GETICON: + return "WM_GETICON" + case WM_GETMINMAXINFO: + return "WM_GETMINMAXINFO" + case WM_GETTEXT: + return "WM_GETTEXT" + case WM_GETTEXTLENGTH: + return "WM_GETTEXTLENGTH" + case WM_HANDHELDFIRST: + return "WM_HANDHELDFIRST" + case WM_HANDHELDLAST: + return "WM_HANDHELDLAST" + case WM_HELP: + return "WM_HELP" + case WM_HOTKEY: + return "WM_HOTKEY" + case WM_HSCROLL: + return "WM_HSCROLL" + case WM_HSCROLLCLIPBOARD: + return "WM_HSCROLLCLIPBOARD" + case WM_ICONERASEBKGND: + return "WM_ICONERASEBKGND" + case WM_INITDIALOG: + return "WM_INITDIALOG" + case WM_INITMENU: + return "WM_INITMENU" + case WM_INITMENUPOPUP: + return "WM_INITMENUPOPUP" + case WM_INPUT: + return "WM_INPUT" + case WM_INPUTLANGCHANGE: + return "WM_INPUTLANGCHANGE" + case WM_INPUTLANGCHANGEREQUEST: + return "WM_INPUTLANGCHANGEREQUEST" + case WM_KEYDOWN: + return "WM_KEYDOWN" + case WM_KEYUP: + return "WM_KEYUP" + case WM_KILLFOCUS: + return "WM_KILLFOCUS" + case WM_MDIACTIVATE: + return "WM_MDIACTIVATE" + case WM_MDICASCADE: + return "WM_MDICASCADE" + case WM_MDICREATE: + return "WM_MDICREATE" + case WM_MDIDESTROY: + return "WM_MDIDESTROY" + case WM_MDIGETACTIVE: + return "WM_MDIGETACTIVE" + case WM_MDIICONARRANGE: + return "WM_MDIICONARRANGE" + case WM_MDIMAXIMIZE: + return "WM_MDIMAXIMIZE" + case WM_MDINEXT: + return "WM_MDINEXT" + case WM_MDIREFRESHMENU: + return "WM_MDIREFRESHMENU" + case WM_MDIRESTORE: + return "WM_MDIRESTORE" + case WM_MDISETMENU: + return "WM_MDISETMENU" + case WM_MDITILE: + return "WM_MDITILE" + case WM_MEASUREITEM: + return "WM_MEASUREITEM" + case WM_GETOBJECT: + return "WM_GETOBJECT" + case WM_CHANGEUISTATE: + return "WM_CHANGEUISTATE" + case WM_UPDATEUISTATE: + return "WM_UPDATEUISTATE" + case WM_QUERYUISTATE: + return "WM_QUERYUISTATE" + case WM_UNINITMENUPOPUP: + return "WM_UNINITMENUPOPUP" + case WM_MENURBUTTONUP: + return "WM_MENURBUTTONUP" + case WM_MENUCOMMAND: + return "WM_MENUCOMMAND" + case WM_MENUGETOBJECT: + return "WM_MENUGETOBJECT" + case WM_MENUDRAG: + return "WM_MENUDRAG" + case WM_APPCOMMAND: + return "WM_APPCOMMAND" + case WM_MENUCHAR: + return "WM_MENUCHAR" + case WM_MENUSELECT: + return "WM_MENUSELECT" + case WM_MOVE: + return "WM_MOVE" + case WM_MOVING: + return "WM_MOVING" + case WM_NCACTIVATE: + return "WM_NCACTIVATE" + case WM_NCCALCSIZE: + return "WM_NCCALCSIZE" + case WM_NCCREATE: + return "WM_NCCREATE" + case WM_NCDESTROY: + return "WM_NCDESTROY" + case WM_NCHITTEST: + return "WM_NCHITTEST" + case WM_NCLBUTTONDBLCLK: + return "WM_NCLBUTTONDBLCLK" + case WM_NCLBUTTONDOWN: + return "WM_NCLBUTTONDOWN" + case WM_NCLBUTTONUP: + return "WM_NCLBUTTONUP" + case WM_NCMBUTTONDBLCLK: + return "WM_NCMBUTTONDBLCLK" + case WM_NCMBUTTONDOWN: + return "WM_NCMBUTTONDOWN" + case WM_NCMBUTTONUP: + return "WM_NCMBUTTONUP" + case WM_NCXBUTTONDOWN: + return "WM_NCXBUTTONDOWN" + case WM_NCXBUTTONUP: + return "WM_NCXBUTTONUP" + case WM_NCXBUTTONDBLCLK: + return "WM_NCXBUTTONDBLCLK" + case WM_NCMOUSEHOVER: + return "WM_NCMOUSEHOVER" + case WM_NCMOUSELEAVE: + return "WM_NCMOUSELEAVE" + case WM_NCMOUSEMOVE: + return "WM_NCMOUSEMOVE" + case WM_NCPAINT: + return "WM_NCPAINT" + case WM_NCRBUTTONDBLCLK: + return "WM_NCRBUTTONDBLCLK" + case WM_NCRBUTTONDOWN: + return "WM_NCRBUTTONDOWN" + case WM_NCRBUTTONUP: + return "WM_NCRBUTTONUP" + case WM_NEXTDLGCTL: + return "WM_NEXTDLGCTL" + case WM_NEXTMENU: + return "WM_NEXTMENU" + case WM_NOTIFY: + return "WM_NOTIFY" + case WM_NOTIFYFORMAT: + return "WM_NOTIFYFORMAT" + case WM_NULL: + return "WM_NULL" + case WM_PAINT: + return "WM_PAINT" + case WM_PAINTCLIPBOARD: + return "WM_PAINTCLIPBOARD" + case WM_PAINTICON: + return "WM_PAINTICON" + case WM_PALETTECHANGED: + return "WM_PALETTECHANGED" + case WM_PALETTEISCHANGING: + return "WM_PALETTEISCHANGING" + case WM_PARENTNOTIFY: + return "WM_PARENTNOTIFY" + case WM_PASTE: + return "WM_PASTE" + case WM_PENWINFIRST: + return "WM_PENWINFIRST" + case WM_PENWINLAST: + return "WM_PENWINLAST" + case WM_POWER: + return "WM_POWER" + case WM_PRINT: + return "WM_PRINT" + case WM_PRINTCLIENT: + return "WM_PRINTCLIENT" + case WM_QUERYDRAGICON: + return "WM_QUERYDRAGICON" + case WM_QUERYENDSESSION: + return "WM_QUERYENDSESSION" + case WM_QUERYNEWPALETTE: + return "WM_QUERYNEWPALETTE" + case WM_QUERYOPEN: + return "WM_QUERYOPEN" + case WM_QUEUESYNC: + return "WM_QUEUESYNC" + case WM_QUIT: + return "WM_QUIT" + case WM_RENDERALLFORMATS: + return "WM_RENDERALLFORMATS" + case WM_RENDERFORMAT: + return "WM_RENDERFORMAT" + case WM_SETCURSOR: + return "WM_SETCURSOR" + case WM_SETFOCUS: + return "WM_SETFOCUS" + case WM_SETFONT: + return "WM_SETFONT" + case WM_SETHOTKEY: + return "WM_SETHOTKEY" + case WM_SETICON: + return "WM_SETICON" + case WM_SETREDRAW: + return "WM_SETREDRAW" + case WM_SETTEXT: + return "WM_SETTEXT" + case WM_SETTINGCHANGE: + return "WM_SETTINGCHANGE" + case WM_SHOWWINDOW: + return "WM_SHOWWINDOW" + case WM_SIZE: + return "WM_SIZE" + case WM_SIZECLIPBOARD: + return "WM_SIZECLIPBOARD" + case WM_SIZING: + return "WM_SIZING" + case WM_SPOOLERSTATUS: + return "WM_SPOOLERSTATUS" + case WM_STYLECHANGED: + return "WM_STYLECHANGED" + case WM_STYLECHANGING: + return "WM_STYLECHANGING" + case WM_SYSCHAR: + return "WM_SYSCHAR" + case WM_SYSCOLORCHANGE: + return "WM_SYSCOLORCHANGE" + case WM_SYSCOMMAND: + return "WM_SYSCOMMAND" + case WM_SYSDEADCHAR: + return "WM_SYSDEADCHAR" + case WM_SYSKEYDOWN: + return "WM_SYSKEYDOWN" + case WM_SYSKEYUP: + return "WM_SYSKEYUP" + case WM_TCARD: + return "WM_TCARD" + case WM_THEMECHANGED: + return "WM_THEMECHANGED" + case WM_TIMECHANGE: + return "WM_TIMECHANGE" + case WM_TIMER: + return "WM_TIMER" + case WM_UNDO: + return "WM_UNDO" + case WM_USER: + return "WM_USER" + case WM_USERCHANGED: + return "WM_USERCHANGED" + case WM_VKEYTOITEM: + return "WM_VKEYTOITEM" + case WM_VSCROLL: + return "WM_VSCROLL" + case WM_VSCROLLCLIPBOARD: + return "WM_VSCROLLCLIPBOARD" + case WM_WINDOWPOSCHANGED: + return "WM_WINDOWPOSCHANGED" + case WM_WINDOWPOSCHANGING: + return "WM_WINDOWPOSCHANGING" + case WM_KEYLAST: + return "WM_KEYLAST" + case WM_SYNCPAINT: + return "WM_SYNCPAINT" + case WM_MOUSEACTIVATE: + return "WM_MOUSEACTIVATE" + case WM_MOUSEMOVE: + return "WM_MOUSEMOVE" + case WM_LBUTTONDOWN: + return "WM_LBUTTONDOWN" + case WM_LBUTTONUP: + return "WM_LBUTTONUP" + case WM_LBUTTONDBLCLK: + return "WM_LBUTTONDBLCLK" + case WM_RBUTTONDOWN: + return "WM_RBUTTONDOWN" + case WM_RBUTTONUP: + return "WM_RBUTTONUP" + case WM_RBUTTONDBLCLK: + return "WM_RBUTTONDBLCLK" + case WM_MBUTTONDOWN: + return "WM_MBUTTONDOWN" + case WM_MBUTTONUP: + return "WM_MBUTTONUP" + case WM_MBUTTONDBLCLK: + return "WM_MBUTTONDBLCLK" + case WM_MOUSEWHEEL: + return "WM_MOUSEWHEEL" + case WM_XBUTTONDOWN: + return "WM_XBUTTONDOWN" + case WM_XBUTTONUP: + return "WM_XBUTTONUP" + case WM_MOUSELAST: + return "WM_MOUSELAST" + case WM_MOUSEHOVER: + return "WM_MOUSEHOVER" + case WM_MOUSELEAVE: + return "WM_MOUSELEAVE" + case WM_CLIPBOARDUPDATE: + return "WM_CLIPBOARDUPDATE" + default: + return fmt.Sprintf("0x%08x", msg) + } +} + +func Fatal(message string) { + println("***************** FATAL ERROR ******************") + fmt.Println("Message: " + message) + fmt.Println("Last Error: " + strconv.Itoa(int(GetLastError()))) + fmt.Println("Stack: " + string(debug.Stack())) + os.Exit(1) +} diff --git a/v3/pkg/w32/uxtheme.go b/v3/pkg/w32/uxtheme.go new file mode 100644 index 000000000..51ec0035f --- /dev/null +++ b/v3/pkg/w32/uxtheme.go @@ -0,0 +1,152 @@ +//go:build windows + +/* + * Copyright (C) 2019 Tad Vizbaras. All Rights Reserved. + * Copyright (C) 2010-2012 The W32 Authors. All Rights Reserved. + */ + +package w32 + +import ( + "syscall" + "unsafe" +) + +// LISTVIEW parts +const ( + LVP_LISTITEM = 1 + LVP_LISTGROUP = 2 + LVP_LISTDETAIL = 3 + LVP_LISTSORTEDDETAIL = 4 + LVP_EMPTYTEXT = 5 + LVP_GROUPHEADER = 6 + LVP_GROUPHEADERLINE = 7 + LVP_EXPANDBUTTON = 8 + LVP_COLLAPSEBUTTON = 9 + LVP_COLUMNDETAIL = 10 +) + +// LVP_LISTITEM states +const ( + LISS_NORMAL = 1 + LISS_HOT = 2 + LISS_SELECTED = 3 + LISS_DISABLED = 4 + LISS_SELECTEDNOTFOCUS = 5 + LISS_HOTSELECTED = 6 +) + +// TREEVIEW parts +const ( + TVP_TREEITEM = 1 + TVP_GLYPH = 2 + TVP_BRANCH = 3 + TVP_HOTGLYPH = 4 +) + +// TVP_TREEITEM states +const ( + TREIS_NORMAL = 1 + TREIS_HOT = 2 + TREIS_SELECTED = 3 + TREIS_DISABLED = 4 + TREIS_SELECTEDNOTFOCUS = 5 + TREIS_HOTSELECTED = 6 +) + +type HTHEME HANDLE + +var ( + // Library + libuxtheme uintptr + + // Functions + closeThemeData uintptr + drawThemeBackground uintptr + drawThemeText uintptr + getThemeTextExtent uintptr + openThemeData uintptr + setWindowTheme uintptr +) + +func init() { + // Library + libuxtheme = MustLoadLibrary("uxtheme.dll") + + // Functions + closeThemeData = MustGetProcAddress(libuxtheme, "CloseThemeData") + drawThemeBackground = MustGetProcAddress(libuxtheme, "DrawThemeBackground") + drawThemeText = MustGetProcAddress(libuxtheme, "DrawThemeText") + getThemeTextExtent = MustGetProcAddress(libuxtheme, "GetThemeTextExtent") + openThemeData = MustGetProcAddress(libuxtheme, "OpenThemeData") + setWindowTheme = MustGetProcAddress(libuxtheme, "SetWindowTheme") +} + +func CloseThemeData(hTheme HTHEME) HRESULT { + ret, _, _ := syscall.Syscall(closeThemeData, 1, + uintptr(hTheme), + 0, + 0) + + return HRESULT(ret) +} + +func DrawThemeBackground(hTheme HTHEME, hdc HDC, iPartId, iStateId int32, pRect, pClipRect *RECT) HRESULT { + ret, _, _ := syscall.Syscall6(drawThemeBackground, 6, + uintptr(hTheme), + uintptr(hdc), + uintptr(iPartId), + uintptr(iStateId), + uintptr(unsafe.Pointer(pRect)), + uintptr(unsafe.Pointer(pClipRect))) + + return HRESULT(ret) +} + +func DrawThemeText(hTheme HTHEME, hdc HDC, iPartId, iStateId int32, pszText *uint16, iCharCount int32, dwTextFlags, dwTextFlags2 uint32, pRect *RECT) HRESULT { + ret, _, _ := syscall.Syscall9(drawThemeText, 9, + uintptr(hTheme), + uintptr(hdc), + uintptr(iPartId), + uintptr(iStateId), + uintptr(unsafe.Pointer(pszText)), + uintptr(iCharCount), + uintptr(dwTextFlags), + uintptr(dwTextFlags2), + uintptr(unsafe.Pointer(pRect))) + + return HRESULT(ret) +} + +func GetThemeTextExtent(hTheme HTHEME, hdc HDC, iPartId, iStateId int32, pszText *uint16, iCharCount int32, dwTextFlags uint32, pBoundingRect, pExtentRect *RECT) HRESULT { + ret, _, _ := syscall.Syscall9(getThemeTextExtent, 9, + uintptr(hTheme), + uintptr(hdc), + uintptr(iPartId), + uintptr(iStateId), + uintptr(unsafe.Pointer(pszText)), + uintptr(iCharCount), + uintptr(dwTextFlags), + uintptr(unsafe.Pointer(pBoundingRect)), + uintptr(unsafe.Pointer(pExtentRect))) + + return HRESULT(ret) +} + +func OpenThemeData(hwnd HWND, pszClassList *uint16) HTHEME { + ret, _, _ := syscall.Syscall(openThemeData, 2, + uintptr(hwnd), + uintptr(unsafe.Pointer(pszClassList)), + 0) + + return HTHEME(ret) +} + +func SetWindowTheme(hwnd HWND, pszSubAppName, pszSubIdList *uint16) HRESULT { + ret, _, _ := syscall.Syscall(setWindowTheme, 3, + uintptr(hwnd), + uintptr(unsafe.Pointer(pszSubAppName)), + uintptr(unsafe.Pointer(pszSubIdList))) + + return HRESULT(ret) +} diff --git a/v3/pkg/w32/vars.go b/v3/pkg/w32/vars.go new file mode 100644 index 000000000..cb69f9d19 --- /dev/null +++ b/v3/pkg/w32/vars.go @@ -0,0 +1,16 @@ +//go:build windows + +/* + * Copyright (C) 2019 Tad Vizbaras. All Rights Reserved. + * Copyright (C) 2010-2012 The W32 Authors. All Rights Reserved. + */ + +package w32 + +var ( + IID_NULL = &GUID{0x00000000, 0x0000, 0x0000, [8]byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}} + IID_IUnknown = &GUID{0x00000000, 0x0000, 0x0000, [8]byte{0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46}} + IID_IDispatch = &GUID{0x00020400, 0x0000, 0x0000, [8]byte{0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46}} + IID_IConnectionPointContainer = &GUID{0xB196B284, 0xBAB4, 0x101A, [8]byte{0xB6, 0x9C, 0x00, 0xAA, 0x00, 0x34, 0x1D, 0x07}} + IID_IConnectionPoint = &GUID{0xB196B286, 0xBAB4, 0x101A, [8]byte{0xB6, 0x9C, 0x00, 0xAA, 0x00, 0x34, 0x1D, 0x07}} +) diff --git a/v3/pkg/w32/window.go b/v3/pkg/w32/window.go new file mode 100644 index 000000000..545b42135 --- /dev/null +++ b/v3/pkg/w32/window.go @@ -0,0 +1,253 @@ +//go:build windows + +package w32 + +import ( + "fmt" + "github.com/samber/lo" + "log" + "strconv" + "strings" + "sync" + "syscall" + "unsafe" +) + +const ( + GCLP_HBRBACKGROUND int32 = -10 + GCLP_HICON int32 = -14 +) + +func ExtendFrameIntoClientArea(hwnd uintptr, extend bool) { + // -1: Adds the default frame styling (aero shadow and e.g. rounded corners on Windows 11) + // Also shows the caption buttons if transparent ant translucent but they don't work. + // 0: Adds the default frame styling but no aero shadow, does not show the caption buttons. + // 1: Adds the default frame styling (aero shadow and e.g. rounded corners on Windows 11) but no caption buttons + // are shown if transparent ant translucent. + var margins MARGINS + if extend { + margins = MARGINS{1, 1, 1, 1} // Only extend 1 pixel to have the default frame styling but no caption buttons + } + if err := dwmExtendFrameIntoClientArea(hwnd, &margins); err != nil { + log.Fatal(fmt.Errorf("DwmExtendFrameIntoClientArea failed: %s", err)) + } +} + +func IsVisible(hwnd uintptr) bool { + ret, _, _ := procIsWindowVisible.Call(hwnd) + return ret != 0 +} + +func IsWindowFullScreen(hwnd uintptr) bool { + wRect := GetWindowRect(hwnd) + m := MonitorFromWindow(hwnd, MONITOR_DEFAULTTOPRIMARY) + var mi MONITORINFO + mi.CbSize = uint32(unsafe.Sizeof(mi)) + if !GetMonitorInfo(m, &mi) { + return false + } + return wRect.Left == mi.RcMonitor.Left && + wRect.Top == mi.RcMonitor.Top && + wRect.Right == mi.RcMonitor.Right && + wRect.Bottom == mi.RcMonitor.Bottom +} + +func IsWindowMaximised(hwnd uintptr) bool { + style := uint32(getWindowLong(hwnd, GWL_STYLE)) + return style&WS_MAXIMIZE != 0 +} +func IsWindowMinimised(hwnd uintptr) bool { + style := uint32(getWindowLong(hwnd, GWL_STYLE)) + return style&WS_MINIMIZE != 0 +} + +func RestoreWindow(hwnd uintptr) { + showWindow(hwnd, SW_RESTORE) +} + +func ShowWindowMaximised(hwnd uintptr) { + showWindow(hwnd, SW_MAXIMIZE) +} +func ShowWindowMinimised(hwnd uintptr) { + showWindow(hwnd, SW_MINIMIZE) +} + +func SetApplicationIcon(hwnd uintptr, icon HICON) { + setClassLongPtr(hwnd, GCLP_HICON, icon) +} + +func SetBackgroundColour(hwnd uintptr, r, g, b uint8) { + col := uint32(r) | uint32(g)<<8 | uint32(b)<<16 + hbrush, _, _ := procCreateSolidBrush.Call(uintptr(col)) + setClassLongPtr(hwnd, GCLP_HBRBACKGROUND, hbrush) +} + +func IsWindowNormal(hwnd uintptr) bool { + return !IsWindowMaximised(hwnd) && !IsWindowMinimised(hwnd) && !IsWindowFullScreen(hwnd) +} + +func setClassLongPtr(hwnd uintptr, param int32, val uintptr) bool { + proc := procSetClassLongPtr + if strconv.IntSize == 32 { + /* + https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-setclasslongptrw + Note: To write code that is compatible with both 32-bit and 64-bit Windows, use SetClassLongPtr. + When compiling for 32-bit Windows, SetClassLongPtr is defined as a call to the SetClassLong function + + => We have to do this dynamically when directly calling the DLL procedures + */ + proc = procSetClassLong + } + + ret, _, _ := proc.Call( + hwnd, + uintptr(param), + val, + ) + return ret != 0 +} + +func getWindowLong(hwnd uintptr, index int) int32 { + ret, _, _ := procGetWindowLong.Call( + hwnd, + uintptr(index)) + + return int32(ret) +} + +func showWindow(hwnd uintptr, cmdshow int) bool { + ret, _, _ := procShowWindow.Call( + hwnd, + uintptr(cmdshow)) + return ret != 0 +} + +func stripNulls(str string) string { + // Split the string into substrings at each null character + substrings := strings.Split(str, "\x00") + + // Join the substrings back into a single string + strippedStr := strings.Join(substrings, "") + + return strippedStr +} + +func MustStringToUTF16Ptr(input string) *uint16 { + input = stripNulls(input) + result, err := syscall.UTF16PtrFromString(input) + if err != nil { + Fatal(err.Error()) + } + return result +} + +func MustStringToUTF16uintptr(input string) uintptr { + input = stripNulls(input) + ret := lo.Must(syscall.UTF16PtrFromString(input)) + return uintptr(unsafe.Pointer(ret)) +} + +func MustStringToUTF16(input string) []uint16 { + input = stripNulls(input) + return lo.Must(syscall.UTF16FromString(input)) +} + +func CenterWindow(hwnd HWND) { + windowInfo := getWindowInfo(hwnd) + frameless := windowInfo.IsPopup() + + info := GetMonitorInfoForWindow(hwnd) + workRect := info.RcWork + screenMiddleW := workRect.Left + (workRect.Right-workRect.Left)/2 + screenMiddleH := workRect.Top + (workRect.Bottom-workRect.Top)/2 + var winRect *RECT + if !frameless { + winRect = GetWindowRect(hwnd) + } else { + winRect = GetClientRect(hwnd) + } + winWidth := winRect.Right - winRect.Left + winHeight := winRect.Bottom - winRect.Top + windowX := screenMiddleW - (winWidth / 2) + windowY := screenMiddleH - (winHeight / 2) + SetWindowPos(hwnd, HWND_TOP, int(windowX), int(windowY), int(winWidth), int(winHeight), SWP_NOSIZE) +} + +func getWindowInfo(hwnd HWND) *WINDOWINFO { + var info WINDOWINFO + info.CbSize = uint32(unsafe.Sizeof(info)) + GetWindowInfo(hwnd, &info) + return &info +} + +func GetMonitorInfoForWindow(hwnd HWND) *MONITORINFO { + currentMonitor := MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST) + var info MONITORINFO + info.CbSize = uint32(unsafe.Sizeof(info)) + GetMonitorInfo(currentMonitor, &info) + return &info +} + +type WindowProc func(hwnd HWND, msg uint32, wParam, lParam uintptr) uintptr + +var windowClasses = make(map[string]HINSTANCE) +var windowClassesLock sync.Mutex + +func getWindowClass(name string) (HINSTANCE, bool) { + windowClassesLock.Lock() + defer windowClassesLock.Unlock() + result, exists := windowClasses[name] + return result, exists +} + +func setWindowClass(name string, instance HINSTANCE) { + windowClassesLock.Lock() + defer windowClassesLock.Unlock() + windowClasses[name] = instance +} + +func RegisterWindow(name string, proc WindowProc) (HINSTANCE, error) { + classInstance, exists := getWindowClass(name) + if exists { + return classInstance, nil + } + applicationInstance := GetModuleHandle("") + if applicationInstance == 0 { + return 0, fmt.Errorf("get module handle failed") + } + + var wc WNDCLASSEX + wc.Size = uint32(unsafe.Sizeof(wc)) + wc.WndProc = syscall.NewCallback(proc) + wc.Instance = applicationInstance + wc.Icon = LoadIconWithResourceID(0, uint16(IDI_APPLICATION)) + wc.Cursor = LoadCursorWithResourceID(0, uint16(IDC_ARROW)) + wc.Background = COLOR_BTNFACE + 1 + wc.ClassName = MustStringToUTF16Ptr(name) + + atom := RegisterClassEx(&wc) + if atom == 0 { + panic(syscall.GetLastError()) + } + + setWindowClass(name, applicationInstance) + + return applicationInstance, nil +} + +func FlashWindow(hwnd HWND, enabled bool) { + var flashInfo FLASHWINFO + flashInfo.CbSize = uint32(unsafe.Sizeof(flashInfo)) + flashInfo.Hwnd = hwnd + if enabled { + flashInfo.DwFlags = FLASHW_ALL | FLASHW_TIMERNOFG + } else { + flashInfo.DwFlags = FLASHW_STOP + } + _, _, _ = procFlashWindowEx.Call(uintptr(unsafe.Pointer(&flashInfo))) +} + +func EnumChildWindows(hwnd HWND, callback func(hwnd HWND, lparam LPARAM) LRESULT) LRESULT { + r, _, _ := procEnumChildWindows.Call(hwnd, syscall.NewCallback(callback), 0) + return r +} diff --git a/v3/plugins/experimental/server/README.md b/v3/plugins/experimental/server/README.md new file mode 100644 index 000000000..ddc488a73 --- /dev/null +++ b/v3/plugins/experimental/server/README.md @@ -0,0 +1,34 @@ +# Server Plugin + +This plugin provides a simple server for your Wails applications to make them accessible over the local network. +Bidirectional communication occurs over a websocket connection. + +## Installation + +Add the plugin to the `Plugins` option in the Applications options: + +```go +package main + +import ( + "github.com/wailsapp/wails/v3/pkg/application" + "github.com/wailsapp/wails/v3/plugins/server" +) + +func main() { + app := application.New(application.Options{ + // ... + Plugins: map[string]application.Plugin{ + "server": server.NewPlugin(&server.Config{ + Host: "0.0.0.0", + Port: 31115, + }), + }, + }) + +``` + + +## Support + +If you find a bug in this plugin, please raise a ticket on the Wails [Issue Tracker](https://github.com/wailsapp/wails/issues). diff --git a/v3/plugins/experimental/server/client.go b/v3/plugins/experimental/server/client.go new file mode 100644 index 000000000..38fdce201 --- /dev/null +++ b/v3/plugins/experimental/server/client.go @@ -0,0 +1,5 @@ +package server + +type Client struct { + Address string +} diff --git a/v3/plugins/experimental/server/client.js b/v3/plugins/experimental/server/client.js new file mode 100644 index 000000000..3673d6fe0 --- /dev/null +++ b/v3/plugins/experimental/server/client.js @@ -0,0 +1,8 @@ +(()=>{function _(){}var D=t=>t;function q(t){return t()}function et(){return Object.create(null)}function v(t){t.forEach(q)}function w(t){return typeof t=="function"}function L(t,e){return t!=t?e==e:t!==e||t&&typeof t=="object"||typeof t=="function"}function nt(t){return Object.keys(t).length===0}function it(t,...e){if(t==null)return _;let n=t.subscribe(...e);return n.unsubscribe?()=>n.unsubscribe():n}function rt(t,e,n){t.$$.on_destroy.push(it(e,n))}var ot=typeof window!="undefined",Et=ot?()=>window.performance.now():()=>Date.now(),P=ot?t=>requestAnimationFrame(t):_;var x=new Set;function st(t){x.forEach(e=>{e.c(t)||(x.delete(e),e.f())}),x.size!==0&&P(st)}function kt(t){let e;return x.size===0&&P(st),{promise:new Promise(n=>{x.add(e={c:t,f:n})}),abort(){x.delete(e)}}}var ct=!1;function Ot(){ct=!0}function It(){ct=!1}function jt(t,e){t.appendChild(e)}function lt(t,e,n){let i=K(t);if(!i.getElementById(e)){let r=T("style");r.id=e,r.textContent=n,ut(i,r)}}function K(t){if(!t)return document;let e=t.getRootNode?t.getRootNode():t.ownerDocument;return e&&e.host?e:t.ownerDocument}function At(t){let e=T("style");return ut(K(t),e),e.sheet}function ut(t,e){return jt(t.head||t,e),e.sheet}function R(t,e,n){t.insertBefore(e,n||null)}function k(t){t.parentNode&&t.parentNode.removeChild(t)}function T(t){return document.createElement(t)}function Dt(t){return document.createTextNode(t)}function at(){return Dt("")}function ft(t,e,n){n==null?t.removeAttribute(e):t.getAttribute(e)!==n&&t.setAttribute(e,n)}function Lt(t){return Array.from(t.childNodes)}function Tt(t,e,{bubbles:n=!1,cancelable:i=!1}={}){let r=document.createEvent("CustomEvent");return r.initCustomEvent(t,n,i,e),r}var J=new Map,B=0;function Jt(t){let e=5381,n=t.length;for(;n--;)e=(e<<5)-e^t.charCodeAt(n);return e>>>0}function Bt(t,e){let n={stylesheet:At(e),rules:{}};return J.set(t,n),n}function dt(t,e,n,i,r,c,l,s=0){let u=16.666/i,o=`{ +`;for(let b=0;b<=1;b+=u){let F=e+(n-e)*c(b);o+=b*100+`%{${l(F,1-F)}} +`}let p=o+`100% {${l(n,1-n)}} +}`,f=`__svelte_${Jt(p)}_${s}`,g=K(t),{stylesheet:a,rules:h}=J.get(g)||Bt(g,t);h[f]||(h[f]=!0,a.insertRule(`@keyframes ${f} ${p}`,a.cssRules.length));let m=t.style.animation||"";return t.style.animation=`${m?`${m}, `:""}${f} ${i}ms linear ${r}ms 1 both`,B+=1,f}function zt(t,e){let n=(t.style.animation||"").split(", "),i=n.filter(e?c=>c.indexOf(e)<0:c=>c.indexOf("__svelte")===-1),r=n.length-i.length;r&&(t.style.animation=i.join(", "),B-=r,B||Ht())}function Ht(){P(()=>{B||(J.forEach(t=>{let{ownerNode:e}=t.stylesheet;e&&k(e)}),J.clear())})}var V;function O(t){V=t}var $=[];var ht=[],M=[],_t=[],Nt=Promise.resolve(),W=!1;function Gt(){W||(W=!0,Nt.then(pt))}function S(t){M.push(t)}var U=new Set,C=0;function pt(){if(C!==0)return;let t=V;do{try{for(;C<$.length;){let e=$[C];C++,O(e),qt(e.$$)}}catch(e){throw $.length=0,C=0,e}for(O(null),$.length=0,C=0;ht.length;)ht.pop()();for(let e=0;et.indexOf(i)===-1?e.push(i):n.push(i)),n.forEach(i=>i()),M=e}var I;function Kt(){return I||(I=Promise.resolve(),I.then(()=>{I=null})),I}function X(t,e,n){t.dispatchEvent(Tt(`${e?"intro":"outro"}${n}`))}var z=new Set,y;function mt(){y={r:0,c:[],p:y}}function yt(){y.r||v(y.c),y=y.p}function j(t,e){t&&t.i&&(z.delete(t),t.i(e))}function Z(t,e,n,i){if(t&&t.o){if(z.has(t))return;z.add(t),y.c.push(()=>{z.delete(t),i&&(n&&t.d(1),i())}),t.o(e)}else i&&i()}var Rt={duration:0};function Q(t,e,n,i){let r={direction:"both"},c=e(t,n,r),l=i?0:1,s=null,u=null,o=null;function p(){o&&zt(t,o)}function f(a,h){let m=a.b-l;return h*=Math.abs(m),{a:l,b:a.b,d:m,duration:h,start:a.start,end:a.start+h,group:a.group}}function g(a){let{delay:h=0,duration:m=300,easing:b=D,tick:F=_,css:N}=c||Rt,G={start:Et()+h,b:a};a||(G.group=y,y.r+=1),s||u?u=G:(N&&(p(),o=dt(t,l,a,m,h,b,N)),a&&F(0,1),s=f(G,m),S(()=>X(t,a,"start")),kt(A=>{if(u&&A>u.start&&(s=f(u,m),u=null,X(t,s.b,"start"),N&&(p(),o=dt(t,l,s.b,s.duration,0,b,c.css))),s){if(A>=s.end)F(l=s.b,1-l),X(t,s.b,"end"),u||(s.b?p():--s.group.r||v(s.group.c)),s=null;else if(A>=s.start){let Ct=A-s.start;l=s.a+s.d*b(Ct/s.duration),F(l,1-l)}}return!!(s||u)}))}return{run(a){w(c)?Kt().then(()=>{c=c(r),g(a)}):g(a)},end(){p(),s=u=null}}}var ae=typeof window!="undefined"?window:typeof globalThis!="undefined"?globalThis:global;var Vt=["allowfullscreen","allowpaymentrequest","async","autofocus","autoplay","checked","controls","default","defer","disabled","formnovalidate","hidden","inert","ismap","loop","multiple","muted","nomodule","novalidate","open","playsinline","readonly","required","reversed","selected"],fe=new Set([...Vt]);function Wt(t,e,n,i){let{fragment:r,after_update:c}=t.$$;r&&r.m(e,n),i||S(()=>{let l=t.$$.on_mount.map(q).filter(w);t.$$.on_destroy?t.$$.on_destroy.push(...l):v(l),t.$$.on_mount=[]}),c.forEach(S)}function gt(t,e){let n=t.$$;n.fragment!==null&&(Pt(n.after_update),v(n.on_destroy),n.fragment&&n.fragment.d(e),n.on_destroy=n.fragment=null,n.ctx=[])}function Ut(t,e){t.$$.dirty[0]===-1&&($.push(t),Gt(),t.$$.dirty.fill(0)),t.$$.dirty[e/31|0]|=1<{let h=a.length?a[0]:g;return o.ctx&&r(o.ctx[f],o.ctx[f]=h)&&(!o.skip_bound&&o.bound[f]&&o.bound[f](h),p&&Ut(t,f)),g}):[],o.update(),p=!0,v(o.before_update),o.fragment=i?i(o.ctx):!1,e.target){if(e.hydrate){Ot();let f=Lt(e.target);o.fragment&&o.fragment.l(f),f.forEach(k)}else o.fragment&&o.fragment.c();e.intro&&j(t.$$.fragment),Wt(t,e.target,e.anchor,e.customElement),It(),pt()}O(u)}var Xt;typeof HTMLElement=="function"&&(Xt=class extends HTMLElement{constructor(){super();this.attachShadow({mode:"open"})}connectedCallback(){let{on_mount:t}=this.$$;this.$$.on_disconnect=t.map(q).filter(w);for(let e in this.$$.slotted)this.appendChild(this.$$.slotted[e])}attributeChangedCallback(t,e,n){this[t]=n}disconnectedCallback(){v(this.$$.on_disconnect)}$destroy(){gt(this,1),this.$destroy=_}$on(t,e){if(!w(e))return _;let n=this.$$.callbacks[t]||(this.$$.callbacks[t]=[]);return n.push(e),()=>{let i=n.indexOf(e);i!==-1&&n.splice(i,1)}}$set(t){this.$$set&&!nt(t)&&(this.$$.skip_bound=!0,this.$$set(t),this.$$.skip_bound=!1)}});var Y=class{$destroy(){gt(this,1),this.$destroy=_}$on(e,n){if(!w(n))return _;let i=this.$$.callbacks[e]||(this.$$.callbacks[e]=[]);return i.push(n),()=>{let r=i.indexOf(n);r!==-1&&i.splice(r,1)}}$set(e){this.$$set&&!nt(e)&&(this.$$.skip_bound=!0,this.$$set(e),this.$$.skip_bound=!1)}};var E=[];function vt(t,e=_){let n,i=new Set;function r(s){if(L(t,s)&&(t=s,n)){let u=!E.length;for(let o of i)o[1](),E.push(o,t);if(u){for(let o=0;o{i.delete(o),i.size===0&&n&&(n(),n=null)}}return{set:r,update:c,subscribe:l}}var H=vt(!1);function wt(){H.set(!0)}function Ft(){H.set(!1)}function tt(t,{delay:e=0,duration:n=400,easing:i=D}={}){let r=+getComputedStyle(t).opacity;return{delay:e,duration:n,easing:i,css:c=>`opacity: ${c*r}`}}function Zt(t){lt(t,"svelte-181h7z",`.wails-reconnect-overlay.svelte-181h7z{position:fixed;top:0;left:0;width:100%;height:100%;backdrop-filter:blur(2px) saturate(0%) contrast(50%) brightness(25%);z-index:999999 + }.wails-reconnect-overlay-content.svelte-181h7z{position:relative;top:50%;transform:translateY(-50%);margin:0;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEsAAAA7CAMAAAAEsocZAAAC91BMVEUAAACzQ0PjMjLkMjLZLS7XLS+vJCjkMjKlEx6uGyHjMDGiFx7GJyrAISjUKy3mMzPlMjLjMzOsGyDKJirkMjK6HyXmMjLgMDC6IiLcMjLULC3MJyrRKSy+IibmMzPmMjK7ISXlMjLIJimzHSLkMjKtGiHZLC7BIifgMDCpGSDFIivcLy+yHSKoGR+eFBzNKCvlMjKxHSPkMTKxHSLmMjLKJyq5ICXDJCe6ISXdLzDkMjLmMzPFJSm2HyTlMTLhMDGyHSKUEBmhFx24HyTCJCjHJijjMzOiFh7mMjJ6BhDaLDCuGyOKABjnMzPGJinJJiquHCGEChSmGB/pMzOiFh7VKy3OKCu1HiSvHCLjMTLMKCrBIyeICxWxHCLDIyjSKizBIyh+CBO9ISa6ISWDChS9Iie1HyXVLC7FJSrLKCrlMjLiMTGPDhicFRywGyKXFBuhFx1/BxO7IiXkMTGeFBx8BxLkMTGnGR/GJCi4ICWsGyGJDxXSLS2yGiHSKi3CJCfnMzPQKiyECRTKJiq6ISWUERq/Iye0HiPDJCjGJSm6ICaPDxiTEBrdLy+3HyXSKiy0HyOQEBi4ICWhFh1+CBO9IieODhfSKyzWLC2LDhh8BxHKKCq7ISWaFBzkMzPqNDTTLC3EJSiHDBacExyvGyO1HyTPKCy+IieoGSC7ISaVEhrMKCvQKyusGyG0HiKACBPIJSq/JCaABxR5BRLEJCnkMzPJJinEJimPDRZ2BRKqHx/jMjLnMzPgMDHULC3NKSvQKSzsNDTWLS7SKyy3HyTKJyrDJSjbLzDYLC6mGB/GJSnVLC61HiPLKCrHJSm/Iye8Iia6ICWzHSKxHCLaLi/PKSupGR+7ICXpMzPbLi/IJinJJSmsGyGrGiCkFx6PDheJCxaFChXBIyfAIieSDxmBCBPlMjLeLzDdLzC5HySMDRe+ISWvGyGcFBzSKSzPJyvMJyrEJCjDIyefFRyWERriMDHUKiy/ISaZExv0NjbwNTXuNDTrMzMI0c+yAAAAu3RSTlMAA8HR/gwGgAj+MEpGCsC+hGpjQjYnIxgWBfzx7urizMrFqqB1bF83KhsR/fz8+/r5+fXv7unZ1tC+t6mmopqKdW1nYVpVRjUeHhIQBPr59/b28/Hx8ODg3NvUw8O/vKeim5aNioiDgn1vZWNjX1xUU1JPTUVFPT08Mi4qJyIh/Pv7+/n4+Pf39fT08/Du7efn5uXj4uHa19XNwsG/vrq2tbSuramlnpyYkpGNiIZ+enRraGVjVVBKOzghdjzRsAAABJVJREFUWMPtllVQG1EYhTc0ASpoobS0FCulUHd3oUjd3d3d3d3d3d2b7CYhnkBCCHGDEIK7Vh56d0NpOgwkYfLQzvA9ZrLfnPvfc+8uVEst/yheBJup3Nya2MjU6pa/jWLZtxjXpZFtVB4uVNI6m5gIruNkVFebqIb5Ug2ym4TIEM/gtUOGbg613oBzjAzZFrZ+lXu/3TIiMXXS5M6HTvrNHeLpZLEh6suGNW9fzZ9zd/qVi2eOHygqi5cDE5GUrJocONgzyqo0UXNSUlKSEhMztFqtXq9vNxImAmS3g7Y6QlbjdBWVGW36jt4wDGTUXjUsafh5zJWRkdFuZGtWGnCRmg+HasiGMUClTTzW0ZuVgLlGDIPM4Lhi0IrVq+tv2hS21fNrSONQgpM9DsJ4t3fM9PkvJuKj2ZjrZwvILKvaSTgciUSirjt6dOfOpyd169bDb9rMOwF9Hj4OD100gY0YXYb299bjzMrqj9doNByJWlVXFB9DT5dmJuvy+cq83JyuS6ayEYSHulKL8dmFnBkrCeZlHKMrC5XRhXGCZB2Ty1fkleRQaMCFT2DBsEafzRFJu7/2MicbKynPhQUDLiZwMWLJZKNLzoLbJBYVcurSmbmn+rcyJ8vCMgmlmaW6gnwun/+3C96VpAUuET1ZgRR36r2xWlnYSnf3oKABA14uXDDvydxHs6cpTV1p3hlJ2rJCiUjIZCByItXg8sHJijuvT64CuMTABUYvb6NN1Jdp1PH7D7f3bo2eS5KvW4RJr7atWT5w4MBBg9zdBw9+37BS7QIoFS5WnIaj12dr1DEXFgdvr4fh4eFl+u/wz8uf3jjHic8s4DL2Dal0IANyUBeCRCcwOBJV26JsjSpGwHVuSai69jvqD+jr56OgtKy0zAAK5mLTVBKVKL5tNthGAR9JneJQ/bFsHNzy+U7IlCYROxtMpIjR0ceoQVnowracLLpAQWETqV361bPoFo3cEbz2zYLZM7t3HWXcxmiBOgttS1ycWkTXMWh4mGigdug9DFdttqCFgTN6nD0q1XEVSoCxEjyFCi2eNC6Z69MRVIImJ6JQSf5gcFVCuF+aDhCa1F6MJFDaiNBQAh2TMfWBjhmLsAxUjG/fmjs0qjJck8D0GPBcuUuZW1LS/tIsPzqmQt17PvZQknlwnf4tHDBc+7t5VV3QQCkdc+Ur8/hdrz0but0RCumWiYbiKmLJ7EVbRomj4Q7+y5wsaXvfTGFpQcHB7n2WbG4MGdniw2Tm8xl5Yhr7MrSYHQ3uampz10aWyHyuzxvqaW/6W4MjXAUD3QV2aw97ZxhGjxCohYf5TpTHMXU1BbsAuoFnkRygVieIGAbqiF7rrH4rfWpKJouBCtyHJF8ctEyGubBa+C6NsMYEUonJFITHZqWBxXUA12Dv76Tf/PgOBmeNiiLG1pcKo1HAq8jLpY4JU1yWEixVNaOgoRJAKBSZHTZTU+wJOMtUDZvlVITC6FTlksyrEBoPHXpxxbzdaqzigUtVDkJVIOtVQ9UEOR4VGUh/kHWq0edJ6CxnZ+eePXva2bnY/cF/I1RLLf8vvwDANdMSMegxcAAAAABJRU5ErkJggg==);background-repeat:no-repeat;background-position:center + }.wails-reconnect-overlay-loadingspinner.svelte-181h7z{pointer-events:none;width:2.5em;height:2.5em;border:.4em solid transparent;border-color:#f00 #eee0 #f00 #eee0;border-radius:50%;animation:svelte-181h7z-loadingspin 1s linear infinite;margin:auto;padding:2.5em + }@keyframes svelte-181h7z-loadingspin{100%{transform:rotate(360deg)}}`)}function xt(t){let e,n,i;return{c(){e=T("div"),e.innerHTML='
',ft(e,"class","wails-reconnect-overlay svelte-181h7z")},m(r,c){R(r,e,c),i=!0},i(r){i||(S(()=>{!i||(n||(n=Q(e,tt,{duration:300},!0)),n.run(1))}),i=!0)},o(r){n||(n=Q(e,tt,{duration:300},!1)),n.run(0),i=!1},d(r){r&&k(e),r&&n&&n.end()}}}function Qt(t){let e,n,i=t[0]&&xt(t);return{c(){i&&i.c(),e=at()},m(r,c){i&&i.m(r,c),R(r,e,c),n=!0},p(r,[c]){r[0]?i?c&1&&j(i,1):(i=xt(r),i.c(),j(i,1),i.m(e.parentNode,e)):i&&(mt(),Z(i,1,1,()=>{i=null}),yt())},i(r){n||(j(i),n=!0)},o(r){Z(i),n=!1},d(r){i&&i.d(r),r&&k(e)}}}function Yt(t,e,n){let i;return rt(t,H,r=>n(0,i=r)),[i]}var $t=class extends Y{constructor(e){super();bt(this,e,Yt,Qt,L,{},Zt)}},Mt=$t;var te={},d=null;function ee(t){let e=JSON.parse(t.data);_wails.callCallback(e.id,e.result,!0)}function ne(t){let e=JSON.parse(t.data);_wails.callErrorCallback(e.id,e.result)}function ie(t){let e=JSON.parse(t.data);_wails.dialogCallback(e.id,e.result,!0)}function re(t){let e=JSON.parse(t.data);_wails.dialogErrorCallback(e.id,e.result)}function oe(t){console.log("WailsEvent: "+t.data)}window.addEventListener("DOMContentLoaded",()=>{te.overlay=new Mt({target:document.body,anchor:document.querySelector("#wails-spinner")}),le()});window.onbeforeunload=function(){d&&(d.onclose=function(){},d.close(),d=null)};function se(t){Ft(),d.onclose=St}function St(t){this.readyState==EventSource.CONNECTING?wt():console.log(t)}function ce(){d==null&&(d=new EventSource("/server/events?clientId="+wails.clientId),d.onopen=se,d.onerror=St,d.addEventListener("cb",ee),d.addEventListener("cberror",ne),d.addEventListener("dlgcb",ie),d.addEventListener("dlgcberror",re),d.addEventListener("wailsevent",oe))}function le(){ce()}})(); +//# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsiaXBjL25vZGVfbW9kdWxlcy9zdmVsdGUvaW50ZXJuYWwvaW5kZXgubWpzIiwgImlwYy9ub2RlX21vZHVsZXMvc3ZlbHRlL3N0b3JlL2luZGV4Lm1qcyIsICJpcGMvc3RvcmUuanMiLCAiaXBjL25vZGVfbW9kdWxlcy9zdmVsdGUvdHJhbnNpdGlvbi9pbmRleC5tanMiLCAiaXBjL092ZXJsYXkuc3ZlbHRlIiwgImlwYy9tYWluLmpzIl0sCiAgInNvdXJjZXNDb250ZW50IjogWyJmdW5jdGlvbiBub29wKCkgeyB9XG5jb25zdCBpZGVudGl0eSA9IHggPT4geDtcbmZ1bmN0aW9uIGFzc2lnbih0YXIsIHNyYykge1xuICAgIC8vIEB0cy1pZ25vcmVcbiAgICBmb3IgKGNvbnN0IGsgaW4gc3JjKVxuICAgICAgICB0YXJba10gPSBzcmNba107XG4gICAgcmV0dXJuIHRhcjtcbn1cbi8vIEFkYXB0ZWQgZnJvbSBodHRwczovL2dpdGh1Yi5jb20vdGhlbi9pcy1wcm9taXNlL2Jsb2IvbWFzdGVyL2luZGV4LmpzXG4vLyBEaXN0cmlidXRlZCB1bmRlciBNSVQgTGljZW5zZSBodHRwczovL2dpdGh1Yi5jb20vdGhlbi9pcy1wcm9taXNlL2Jsb2IvbWFzdGVyL0xJQ0VOU0VcbmZ1bmN0aW9uIGlzX3Byb21pc2UodmFsdWUpIHtcbiAgICByZXR1cm4gISF2YWx1ZSAmJiAodHlwZW9mIHZhbHVlID09PSAnb2JqZWN0JyB8fCB0eXBlb2YgdmFsdWUgPT09ICdmdW5jdGlvbicpICYmIHR5cGVvZiB2YWx1ZS50aGVuID09PSAnZnVuY3Rpb24nO1xufVxuZnVuY3Rpb24gYWRkX2xvY2F0aW9uKGVsZW1lbnQsIGZpbGUsIGxpbmUsIGNvbHVtbiwgY2hhcikge1xuICAgIGVsZW1lbnQuX19zdmVsdGVfbWV0YSA9IHtcbiAgICAgICAgbG9jOiB7IGZpbGUsIGxpbmUsIGNvbHVtbiwgY2hhciB9XG4gICAgfTtcbn1cbmZ1bmN0aW9uIHJ1bihmbikge1xuICAgIHJldHVybiBmbigpO1xufVxuZnVuY3Rpb24gYmxhbmtfb2JqZWN0KCkge1xuICAgIHJldHVybiBPYmplY3QuY3JlYXRlKG51bGwpO1xufVxuZnVuY3Rpb24gcnVuX2FsbChmbnMpIHtcbiAgICBmbnMuZm9yRWFjaChydW4pO1xufVxuZnVuY3Rpb24gaXNfZnVuY3Rpb24odGhpbmcpIHtcbiAgICByZXR1cm4gdHlwZW9mIHRoaW5nID09PSAnZnVuY3Rpb24nO1xufVxuZnVuY3Rpb24gc2FmZV9ub3RfZXF1YWwoYSwgYikge1xuICAgIHJldHVybiBhICE9IGEgPyBiID09IGIgOiBhICE9PSBiIHx8ICgoYSAmJiB0eXBlb2YgYSA9PT0gJ29iamVjdCcpIHx8IHR5cGVvZiBhID09PSAnZnVuY3Rpb24nKTtcbn1cbmxldCBzcmNfdXJsX2VxdWFsX2FuY2hvcjtcbmZ1bmN0aW9uIHNyY191cmxfZXF1YWwoZWxlbWVudF9zcmMsIHVybCkge1xuICAgIGlmICghc3JjX3VybF9lcXVhbF9hbmNob3IpIHtcbiAgICAgICAgc3JjX3VybF9lcXVhbF9hbmNob3IgPSBkb2N1bWVudC5jcmVhdGVFbGVtZW50KCdhJyk7XG4gICAgfVxuICAgIHNyY191cmxfZXF1YWxfYW5jaG9yLmhyZWYgPSB1cmw7XG4gICAgcmV0dXJuIGVsZW1lbnRfc3JjID09PSBzcmNfdXJsX2VxdWFsX2FuY2hvci5ocmVmO1xufVxuZnVuY3Rpb24gbm90X2VxdWFsKGEsIGIpIHtcbiAgICByZXR1cm4gYSAhPSBhID8gYiA9PSBiIDogYSAhPT0gYjtcbn1cbmZ1bmN0aW9uIGlzX2VtcHR5KG9iaikge1xuICAgIHJldHVybiBPYmplY3Qua2V5cyhvYmopLmxlbmd0aCA9PT0gMDtcbn1cbmZ1bmN0aW9uIHZhbGlkYXRlX3N0b3JlKHN0b3JlLCBuYW1lKSB7XG4gICAgaWYgKHN0b3JlICE9IG51bGwgJiYgdHlwZW9mIHN0b3JlLnN1YnNjcmliZSAhPT0gJ2Z1bmN0aW9uJykge1xuICAgICAgICB0aHJvdyBuZXcgRXJyb3IoYCcke25hbWV9JyBpcyBub3QgYSBzdG9yZSB3aXRoIGEgJ3N1YnNjcmliZScgbWV0aG9kYCk7XG4gICAgfVxufVxuZnVuY3Rpb24gc3Vic2NyaWJlKHN0b3JlLCAuLi5jYWxsYmFja3MpIHtcbiAgICBpZiAoc3RvcmUgPT0gbnVsbCkge1xuICAgICAgICByZXR1cm4gbm9vcDtcbiAgICB9XG4gICAgY29uc3QgdW5zdWIgPSBzdG9yZS5zdWJzY3JpYmUoLi4uY2FsbGJhY2tzKTtcbiAgICByZXR1cm4gdW5zdWIudW5zdWJzY3JpYmUgPyAoKSA9PiB1bnN1Yi51bnN1YnNjcmliZSgpIDogdW5zdWI7XG59XG5mdW5jdGlvbiBnZXRfc3RvcmVfdmFsdWUoc3RvcmUpIHtcbiAgICBsZXQgdmFsdWU7XG4gICAgc3Vic2NyaWJlKHN0b3JlLCBfID0+IHZhbHVlID0gXykoKTtcbiAgICByZXR1cm4gdmFsdWU7XG59XG5mdW5jdGlvbiBjb21wb25lbnRfc3Vic2NyaWJlKGNvbXBvbmVudCwgc3RvcmUsIGNhbGxiYWNrKSB7XG4gICAgY29tcG9uZW50LiQkLm9uX2Rlc3Ryb3kucHVzaChzdWJzY3JpYmUoc3RvcmUsIGNhbGxiYWNrKSk7XG59XG5mdW5jdGlvbiBjcmVhdGVfc2xvdChkZWZpbml0aW9uLCBjdHgsICQkc2NvcGUsIGZuKSB7XG4gICAgaWYgKGRlZmluaXRpb24pIHtcbiAgICAgICAgY29uc3Qgc2xvdF9jdHggPSBnZXRfc2xvdF9jb250ZXh0KGRlZmluaXRpb24sIGN0eCwgJCRzY29wZSwgZm4pO1xuICAgICAgICByZXR1cm4gZGVmaW5pdGlvblswXShzbG90X2N0eCk7XG4gICAgfVxufVxuZnVuY3Rpb24gZ2V0X3Nsb3RfY29udGV4dChkZWZpbml0aW9uLCBjdHgsICQkc2NvcGUsIGZuKSB7XG4gICAgcmV0dXJuIGRlZmluaXRpb25bMV0gJiYgZm5cbiAgICAgICAgPyBhc3NpZ24oJCRzY29wZS5jdHguc2xpY2UoKSwgZGVmaW5pdGlvblsxXShmbihjdHgpKSlcbiAgICAgICAgOiAkJHNjb3BlLmN0eDtcbn1cbmZ1bmN0aW9uIGdldF9zbG90X2NoYW5nZXMoZGVmaW5pdGlvbiwgJCRzY29wZSwgZGlydHksIGZuKSB7XG4gICAgaWYgKGRlZmluaXRpb25bMl0gJiYgZm4pIHtcbiAgICAgICAgY29uc3QgbGV0cyA9IGRlZmluaXRpb25bMl0oZm4oZGlydHkpKTtcbiAgICAgICAgaWYgKCQkc2NvcGUuZGlydHkgPT09IHVuZGVmaW5lZCkge1xuICAgICAgICAgICAgcmV0dXJuIGxldHM7XG4gICAgICAgIH1cbiAgICAgICAgaWYgKHR5cGVvZiBsZXRzID09PSAnb2JqZWN0Jykge1xuICAgICAgICAgICAgY29uc3QgbWVyZ2VkID0gW107XG4gICAgICAgICAgICBjb25zdCBsZW4gPSBNYXRoLm1heCgkJHNjb3BlLmRpcnR5Lmxlbmd0aCwgbGV0cy5sZW5ndGgpO1xuICAgICAgICAgICAgZm9yIChsZXQgaSA9IDA7IGkgPCBsZW47IGkgKz0gMSkge1xuICAgICAgICAgICAgICAgIG1lcmdlZFtpXSA9ICQkc2NvcGUuZGlydHlbaV0gfCBsZXRzW2ldO1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgcmV0dXJuIG1lcmdlZDtcbiAgICAgICAgfVxuICAgICAgICByZXR1cm4gJCRzY29wZS5kaXJ0eSB8IGxldHM7XG4gICAgfVxuICAgIHJldHVybiAkJHNjb3BlLmRpcnR5O1xufVxuZnVuY3Rpb24gdXBkYXRlX3Nsb3RfYmFzZShzbG90LCBzbG90X2RlZmluaXRpb24sIGN0eCwgJCRzY29wZSwgc2xvdF9jaGFuZ2VzLCBnZXRfc2xvdF9jb250ZXh0X2ZuKSB7XG4gICAgaWYgKHNsb3RfY2hhbmdlcykge1xuICAgICAgICBjb25zdCBzbG90X2NvbnRleHQgPSBnZXRfc2xvdF9jb250ZXh0KHNsb3RfZGVmaW5pdGlvbiwgY3R4LCAkJHNjb3BlLCBnZXRfc2xvdF9jb250ZXh0X2ZuKTtcbiAgICAgICAgc2xvdC5wKHNsb3RfY29udGV4dCwgc2xvdF9jaGFuZ2VzKTtcbiAgICB9XG59XG5mdW5jdGlvbiB1cGRhdGVfc2xvdChzbG90LCBzbG90X2RlZmluaXRpb24sIGN0eCwgJCRzY29wZSwgZGlydHksIGdldF9zbG90X2NoYW5nZXNfZm4sIGdldF9zbG90X2NvbnRleHRfZm4pIHtcbiAgICBjb25zdCBzbG90X2NoYW5nZXMgPSBnZXRfc2xvdF9jaGFuZ2VzKHNsb3RfZGVmaW5pdGlvbiwgJCRzY29wZSwgZGlydHksIGdldF9zbG90X2NoYW5nZXNfZm4pO1xuICAgIHVwZGF0ZV9zbG90X2Jhc2Uoc2xvdCwgc2xvdF9kZWZpbml0aW9uLCBjdHgsICQkc2NvcGUsIHNsb3RfY2hhbmdlcywgZ2V0X3Nsb3RfY29udGV4dF9mbik7XG59XG5mdW5jdGlvbiBnZXRfYWxsX2RpcnR5X2Zyb21fc2NvcGUoJCRzY29wZSkge1xuICAgIGlmICgkJHNjb3BlLmN0eC5sZW5ndGggPiAzMikge1xuICAgICAgICBjb25zdCBkaXJ0eSA9IFtdO1xuICAgICAgICBjb25zdCBsZW5ndGggPSAkJHNjb3BlLmN0eC5sZW5ndGggLyAzMjtcbiAgICAgICAgZm9yIChsZXQgaSA9IDA7IGkgPCBsZW5ndGg7IGkrKykge1xuICAgICAgICAgICAgZGlydHlbaV0gPSAtMTtcbiAgICAgICAgfVxuICAgICAgICByZXR1cm4gZGlydHk7XG4gICAgfVxuICAgIHJldHVybiAtMTtcbn1cbmZ1bmN0aW9uIGV4Y2x1ZGVfaW50ZXJuYWxfcHJvcHMocHJvcHMpIHtcbiAgICBjb25zdCByZXN1bHQgPSB7fTtcbiAgICBmb3IgKGNvbnN0IGsgaW4gcHJvcHMpXG4gICAgICAgIGlmIChrWzBdICE9PSAnJCcpXG4gICAgICAgICAgICByZXN1bHRba10gPSBwcm9wc1trXTtcbiAgICByZXR1cm4gcmVzdWx0O1xufVxuZnVuY3Rpb24gY29tcHV0ZV9yZXN0X3Byb3BzKHByb3BzLCBrZXlzKSB7XG4gICAgY29uc3QgcmVzdCA9IHt9O1xuICAgIGtleXMgPSBuZXcgU2V0KGtleXMpO1xuICAgIGZvciAoY29uc3QgayBpbiBwcm9wcylcbiAgICAgICAgaWYgKCFrZXlzLmhhcyhrKSAmJiBrWzBdICE9PSAnJCcpXG4gICAgICAgICAgICByZXN0W2tdID0gcHJvcHNba107XG4gICAgcmV0dXJuIHJlc3Q7XG59XG5mdW5jdGlvbiBjb21wdXRlX3Nsb3RzKHNsb3RzKSB7XG4gICAgY29uc3QgcmVzdWx0ID0ge307XG4gICAgZm9yIChjb25zdCBrZXkgaW4gc2xvdHMpIHtcbiAgICAgICAgcmVzdWx0W2tleV0gPSB0cnVlO1xuICAgIH1cbiAgICByZXR1cm4gcmVzdWx0O1xufVxuZnVuY3Rpb24gb25jZShmbikge1xuICAgIGxldCByYW4gPSBmYWxzZTtcbiAgICByZXR1cm4gZnVuY3Rpb24gKC4uLmFyZ3MpIHtcbiAgICAgICAgaWYgKHJhbilcbiAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgcmFuID0gdHJ1ZTtcbiAgICAgICAgZm4uY2FsbCh0aGlzLCAuLi5hcmdzKTtcbiAgICB9O1xufVxuZnVuY3Rpb24gbnVsbF90b19lbXB0eSh2YWx1ZSkge1xuICAgIHJldHVybiB2YWx1ZSA9PSBudWxsID8gJycgOiB2YWx1ZTtcbn1cbmZ1bmN0aW9uIHNldF9zdG9yZV92YWx1ZShzdG9yZSwgcmV0LCB2YWx1ZSkge1xuICAgIHN0b3JlLnNldCh2YWx1ZSk7XG4gICAgcmV0dXJuIHJldDtcbn1cbmNvbnN0IGhhc19wcm9wID0gKG9iaiwgcHJvcCkgPT4gT2JqZWN0LnByb3RvdHlwZS5oYXNPd25Qcm9wZXJ0eS5jYWxsKG9iaiwgcHJvcCk7XG5mdW5jdGlvbiBhY3Rpb25fZGVzdHJveWVyKGFjdGlvbl9yZXN1bHQpIHtcbiAgICByZXR1cm4gYWN0aW9uX3Jlc3VsdCAmJiBpc19mdW5jdGlvbihhY3Rpb25fcmVzdWx0LmRlc3Ryb3kpID8gYWN0aW9uX3Jlc3VsdC5kZXN0cm95IDogbm9vcDtcbn1cbmZ1bmN0aW9uIHNwbGl0X2Nzc191bml0KHZhbHVlKSB7XG4gICAgY29uc3Qgc3BsaXQgPSB0eXBlb2YgdmFsdWUgPT09ICdzdHJpbmcnICYmIHZhbHVlLm1hdGNoKC9eXFxzKigtP1tcXGQuXSspKFteXFxzXSopXFxzKiQvKTtcbiAgICByZXR1cm4gc3BsaXQgPyBbcGFyc2VGbG9hdChzcGxpdFsxXSksIHNwbGl0WzJdIHx8ICdweCddIDogW3ZhbHVlLCAncHgnXTtcbn1cbmNvbnN0IGNvbnRlbnRlZGl0YWJsZV90cnV0aHlfdmFsdWVzID0gWycnLCB0cnVlLCAxLCAndHJ1ZScsICdjb250ZW50ZWRpdGFibGUnXTtcblxuY29uc3QgaXNfY2xpZW50ID0gdHlwZW9mIHdpbmRvdyAhPT0gJ3VuZGVmaW5lZCc7XG5sZXQgbm93ID0gaXNfY2xpZW50XG4gICAgPyAoKSA9PiB3aW5kb3cucGVyZm9ybWFuY2Uubm93KClcbiAgICA6ICgpID0+IERhdGUubm93KCk7XG5sZXQgcmFmID0gaXNfY2xpZW50ID8gY2IgPT4gcmVxdWVzdEFuaW1hdGlvbkZyYW1lKGNiKSA6IG5vb3A7XG4vLyB1c2VkIGludGVybmFsbHkgZm9yIHRlc3RpbmdcbmZ1bmN0aW9uIHNldF9ub3coZm4pIHtcbiAgICBub3cgPSBmbjtcbn1cbmZ1bmN0aW9uIHNldF9yYWYoZm4pIHtcbiAgICByYWYgPSBmbjtcbn1cblxuY29uc3QgdGFza3MgPSBuZXcgU2V0KCk7XG5mdW5jdGlvbiBydW5fdGFza3Mobm93KSB7XG4gICAgdGFza3MuZm9yRWFjaCh0YXNrID0+IHtcbiAgICAgICAgaWYgKCF0YXNrLmMobm93KSkge1xuICAgICAgICAgICAgdGFza3MuZGVsZXRlKHRhc2spO1xuICAgICAgICAgICAgdGFzay5mKCk7XG4gICAgICAgIH1cbiAgICB9KTtcbiAgICBpZiAodGFza3Muc2l6ZSAhPT0gMClcbiAgICAgICAgcmFmKHJ1bl90YXNrcyk7XG59XG4vKipcbiAqIEZvciB0ZXN0aW5nIHB1cnBvc2VzIG9ubHkhXG4gKi9cbmZ1bmN0aW9uIGNsZWFyX2xvb3BzKCkge1xuICAgIHRhc2tzLmNsZWFyKCk7XG59XG4vKipcbiAqIENyZWF0ZXMgYSBuZXcgdGFzayB0aGF0IHJ1bnMgb24gZWFjaCByYWYgZnJhbWVcbiAqIHVudGlsIGl0IHJldHVybnMgYSBmYWxzeSB2YWx1ZSBvciBpcyBhYm9ydGVkXG4gKi9cbmZ1bmN0aW9uIGxvb3AoY2FsbGJhY2spIHtcbiAgICBsZXQgdGFzaztcbiAgICBpZiAodGFza3Muc2l6ZSA9PT0gMClcbiAgICAgICAgcmFmKHJ1bl90YXNrcyk7XG4gICAgcmV0dXJuIHtcbiAgICAgICAgcHJvbWlzZTogbmV3IFByb21pc2UoZnVsZmlsbCA9PiB7XG4gICAgICAgICAgICB0YXNrcy5hZGQodGFzayA9IHsgYzogY2FsbGJhY2ssIGY6IGZ1bGZpbGwgfSk7XG4gICAgICAgIH0pLFxuICAgICAgICBhYm9ydCgpIHtcbiAgICAgICAgICAgIHRhc2tzLmRlbGV0ZSh0YXNrKTtcbiAgICAgICAgfVxuICAgIH07XG59XG5cbi8vIFRyYWNrIHdoaWNoIG5vZGVzIGFyZSBjbGFpbWVkIGR1cmluZyBoeWRyYXRpb24uIFVuY2xhaW1lZCBub2RlcyBjYW4gdGhlbiBiZSByZW1vdmVkIGZyb20gdGhlIERPTVxuLy8gYXQgdGhlIGVuZCBvZiBoeWRyYXRpb24gd2l0aG91dCB0b3VjaGluZyB0aGUgcmVtYWluaW5nIG5vZGVzLlxubGV0IGlzX2h5ZHJhdGluZyA9IGZhbHNlO1xuZnVuY3Rpb24gc3RhcnRfaHlkcmF0aW5nKCkge1xuICAgIGlzX2h5ZHJhdGluZyA9IHRydWU7XG59XG5mdW5jdGlvbiBlbmRfaHlkcmF0aW5nKCkge1xuICAgIGlzX2h5ZHJhdGluZyA9IGZhbHNlO1xufVxuZnVuY3Rpb24gdXBwZXJfYm91bmQobG93LCBoaWdoLCBrZXksIHZhbHVlKSB7XG4gICAgLy8gUmV0dXJuIGZpcnN0IGluZGV4IG9mIHZhbHVlIGxhcmdlciB0aGFuIGlucHV0IHZhbHVlIGluIHRoZSByYW5nZSBbbG93LCBoaWdoKVxuICAgIHdoaWxlIChsb3cgPCBoaWdoKSB7XG4gICAgICAgIGNvbnN0IG1pZCA9IGxvdyArICgoaGlnaCAtIGxvdykgPj4gMSk7XG4gICAgICAgIGlmIChrZXkobWlkKSA8PSB2YWx1ZSkge1xuICAgICAgICAgICAgbG93ID0gbWlkICsgMTtcbiAgICAgICAgfVxuICAgICAgICBlbHNlIHtcbiAgICAgICAgICAgIGhpZ2ggPSBtaWQ7XG4gICAgICAgIH1cbiAgICB9XG4gICAgcmV0dXJuIGxvdztcbn1cbmZ1bmN0aW9uIGluaXRfaHlkcmF0ZSh0YXJnZXQpIHtcbiAgICBpZiAodGFyZ2V0Lmh5ZHJhdGVfaW5pdClcbiAgICAgICAgcmV0dXJuO1xuICAgIHRhcmdldC5oeWRyYXRlX2luaXQgPSB0cnVlO1xuICAgIC8vIFdlIGtub3cgdGhhdCBhbGwgY2hpbGRyZW4gaGF2ZSBjbGFpbV9vcmRlciB2YWx1ZXMgc2luY2UgdGhlIHVuY2xhaW1lZCBoYXZlIGJlZW4gZGV0YWNoZWQgaWYgdGFyZ2V0IGlzIG5vdCA8aGVhZD5cbiAgICBsZXQgY2hpbGRyZW4gPSB0YXJnZXQuY2hpbGROb2RlcztcbiAgICAvLyBJZiB0YXJnZXQgaXMgPGhlYWQ+LCB0aGVyZSBtYXkgYmUgY2hpbGRyZW4gd2l0aG91dCBjbGFpbV9vcmRlclxuICAgIGlmICh0YXJnZXQubm9kZU5hbWUgPT09ICdIRUFEJykge1xuICAgICAgICBjb25zdCBteUNoaWxkcmVuID0gW107XG4gICAgICAgIGZvciAobGV0IGkgPSAwOyBpIDwgY2hpbGRyZW4ubGVuZ3RoOyBpKyspIHtcbiAgICAgICAgICAgIGNvbnN0IG5vZGUgPSBjaGlsZHJlbltpXTtcbiAgICAgICAgICAgIGlmIChub2RlLmNsYWltX29yZGVyICE9PSB1bmRlZmluZWQpIHtcbiAgICAgICAgICAgICAgICBteUNoaWxkcmVuLnB1c2gobm9kZSk7XG4gICAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICAgICAgY2hpbGRyZW4gPSBteUNoaWxkcmVuO1xuICAgIH1cbiAgICAvKlxuICAgICogUmVvcmRlciBjbGFpbWVkIGNoaWxkcmVuIG9wdGltYWxseS5cbiAgICAqIFdlIGNhbiByZW9yZGVyIGNsYWltZWQgY2hpbGRyZW4gb3B0aW1hbGx5IGJ5IGZpbmRpbmcgdGhlIGxvbmdlc3Qgc3Vic2VxdWVuY2Ugb2ZcbiAgICAqIG5vZGVzIHRoYXQgYXJlIGFscmVhZHkgY2xhaW1lZCBpbiBvcmRlciBhbmQgb25seSBtb3ZpbmcgdGhlIHJlc3QuIFRoZSBsb25nZXN0XG4gICAgKiBzdWJzZXF1ZW5jZSBvZiBub2RlcyB0aGF0IGFyZSBjbGFpbWVkIGluIG9yZGVyIGNhbiBiZSBmb3VuZCBieVxuICAgICogY29tcHV0aW5nIHRoZSBsb25nZXN0IGluY3JlYXNpbmcgc3Vic2VxdWVuY2Ugb2YgLmNsYWltX29yZGVyIHZhbHVlcy5cbiAgICAqXG4gICAgKiBUaGlzIGFsZ29yaXRobSBpcyBvcHRpbWFsIGluIGdlbmVyYXRpbmcgdGhlIGxlYXN0IGFtb3VudCBvZiByZW9yZGVyIG9wZXJhdGlvbnNcbiAgICAqIHBvc3NpYmxlLlxuICAgICpcbiAgICAqIFByb29mOlxuICAgICogV2Uga25vdyB0aGF0LCBnaXZlbiBhIHNldCBvZiByZW9yZGVyaW5nIG9wZXJhdGlvbnMsIHRoZSBub2RlcyB0aGF0IGRvIG5vdCBtb3ZlXG4gICAgKiBhbHdheXMgZm9ybSBhbiBpbmNyZWFzaW5nIHN1YnNlcXVlbmNlLCBzaW5jZSB0aGV5IGRvIG5vdCBtb3ZlIGFtb25nIGVhY2ggb3RoZXJcbiAgICAqIG1lYW5pbmcgdGhhdCB0aGV5IG11c3QgYmUgYWxyZWFkeSBvcmRlcmVkIGFtb25nIGVhY2ggb3RoZXIuIFRodXMsIHRoZSBtYXhpbWFsXG4gICAgKiBzZXQgb2Ygbm9kZXMgdGhhdCBkbyBub3QgbW92ZSBmb3JtIGEgbG9uZ2VzdCBpbmNyZWFzaW5nIHN1YnNlcXVlbmNlLlxuICAgICovXG4gICAgLy8gQ29tcHV0ZSBsb25nZXN0IGluY3JlYXNpbmcgc3Vic2VxdWVuY2VcbiAgICAvLyBtOiBzdWJzZXF1ZW5jZSBsZW5ndGggaiA9PiBpbmRleCBrIG9mIHNtYWxsZXN0IHZhbHVlIHRoYXQgZW5kcyBhbiBpbmNyZWFzaW5nIHN1YnNlcXVlbmNlIG9mIGxlbmd0aCBqXG4gICAgY29uc3QgbSA9IG5ldyBJbnQzMkFycmF5KGNoaWxkcmVuLmxlbmd0aCArIDEpO1xuICAgIC8vIFByZWRlY2Vzc29yIGluZGljZXMgKyAxXG4gICAgY29uc3QgcCA9IG5ldyBJbnQzMkFycmF5KGNoaWxkcmVuLmxlbmd0aCk7XG4gICAgbVswXSA9IC0xO1xuICAgIGxldCBsb25nZXN0ID0gMDtcbiAgICBmb3IgKGxldCBpID0gMDsgaSA8IGNoaWxkcmVuLmxlbmd0aDsgaSsrKSB7XG4gICAgICAgIGNvbnN0IGN1cnJlbnQgPSBjaGlsZHJlbltpXS5jbGFpbV9vcmRlcjtcbiAgICAgICAgLy8gRmluZCB0aGUgbGFyZ2VzdCBzdWJzZXF1ZW5jZSBsZW5ndGggc3VjaCB0aGF0IGl0IGVuZHMgaW4gYSB2YWx1ZSBsZXNzIHRoYW4gb3VyIGN1cnJlbnQgdmFsdWVcbiAgICAgICAgLy8gdXBwZXJfYm91bmQgcmV0dXJucyBmaXJzdCBncmVhdGVyIHZhbHVlLCBzbyB3ZSBzdWJ0cmFjdCBvbmVcbiAgICAgICAgLy8gd2l0aCBmYXN0IHBhdGggZm9yIHdoZW4gd2UgYXJlIG9uIHRoZSBjdXJyZW50IGxvbmdlc3Qgc3Vic2VxdWVuY2VcbiAgICAgICAgY29uc3Qgc2VxTGVuID0gKChsb25nZXN0ID4gMCAmJiBjaGlsZHJlblttW2xvbmdlc3RdXS5jbGFpbV9vcmRlciA8PSBjdXJyZW50KSA/IGxvbmdlc3QgKyAxIDogdXBwZXJfYm91bmQoMSwgbG9uZ2VzdCwgaWR4ID0+IGNoaWxkcmVuW21baWR4XV0uY2xhaW1fb3JkZXIsIGN1cnJlbnQpKSAtIDE7XG4gICAgICAgIHBbaV0gPSBtW3NlcUxlbl0gKyAxO1xuICAgICAgICBjb25zdCBuZXdMZW4gPSBzZXFMZW4gKyAxO1xuICAgICAgICAvLyBXZSBjYW4gZ3VhcmFudGVlIHRoYXQgY3VycmVudCBpcyB0aGUgc21hbGxlc3QgdmFsdWUuIE90aGVyd2lzZSwgd2Ugd291bGQgaGF2ZSBnZW5lcmF0ZWQgYSBsb25nZXIgc2VxdWVuY2UuXG4gICAgICAgIG1bbmV3TGVuXSA9IGk7XG4gICAgICAgIGxvbmdlc3QgPSBNYXRoLm1heChuZXdMZW4sIGxvbmdlc3QpO1xuICAgIH1cbiAgICAvLyBUaGUgbG9uZ2VzdCBpbmNyZWFzaW5nIHN1YnNlcXVlbmNlIG9mIG5vZGVzIChpbml0aWFsbHkgcmV2ZXJzZWQpXG4gICAgY29uc3QgbGlzID0gW107XG4gICAgLy8gVGhlIHJlc3Qgb2YgdGhlIG5vZGVzLCBub2RlcyB0aGF0IHdpbGwgYmUgbW92ZWRcbiAgICBjb25zdCB0b01vdmUgPSBbXTtcbiAgICBsZXQgbGFzdCA9IGNoaWxkcmVuLmxlbmd0aCAtIDE7XG4gICAgZm9yIChsZXQgY3VyID0gbVtsb25nZXN0XSArIDE7IGN1ciAhPSAwOyBjdXIgPSBwW2N1ciAtIDFdKSB7XG4gICAgICAgIGxpcy5wdXNoKGNoaWxkcmVuW2N1ciAtIDFdKTtcbiAgICAgICAgZm9yICg7IGxhc3QgPj0gY3VyOyBsYXN0LS0pIHtcbiAgICAgICAgICAgIHRvTW92ZS5wdXNoKGNoaWxkcmVuW2xhc3RdKTtcbiAgICAgICAgfVxuICAgICAgICBsYXN0LS07XG4gICAgfVxuICAgIGZvciAoOyBsYXN0ID49IDA7IGxhc3QtLSkge1xuICAgICAgICB0b01vdmUucHVzaChjaGlsZHJlbltsYXN0XSk7XG4gICAgfVxuICAgIGxpcy5yZXZlcnNlKCk7XG4gICAgLy8gV2Ugc29ydCB0aGUgbm9kZXMgYmVpbmcgbW92ZWQgdG8gZ3VhcmFudGVlIHRoYXQgdGhlaXIgaW5zZXJ0aW9uIG9yZGVyIG1hdGNoZXMgdGhlIGNsYWltIG9yZGVyXG4gICAgdG9Nb3ZlLnNvcnQoKGEsIGIpID0+IGEuY2xhaW1fb3JkZXIgLSBiLmNsYWltX29yZGVyKTtcbiAgICAvLyBGaW5hbGx5LCB3ZSBtb3ZlIHRoZSBub2Rlc1xuICAgIGZvciAobGV0IGkgPSAwLCBqID0gMDsgaSA8IHRvTW92ZS5sZW5ndGg7IGkrKykge1xuICAgICAgICB3aGlsZSAoaiA8IGxpcy5sZW5ndGggJiYgdG9Nb3ZlW2ldLmNsYWltX29yZGVyID49IGxpc1tqXS5jbGFpbV9vcmRlcikge1xuICAgICAgICAgICAgaisrO1xuICAgICAgICB9XG4gICAgICAgIGNvbnN0IGFuY2hvciA9IGogPCBsaXMubGVuZ3RoID8gbGlzW2pdIDogbnVsbDtcbiAgICAgICAgdGFyZ2V0Lmluc2VydEJlZm9yZSh0b01vdmVbaV0sIGFuY2hvcik7XG4gICAgfVxufVxuZnVuY3Rpb24gYXBwZW5kKHRhcmdldCwgbm9kZSkge1xuICAgIHRhcmdldC5hcHBlbmRDaGlsZChub2RlKTtcbn1cbmZ1bmN0aW9uIGFwcGVuZF9zdHlsZXModGFyZ2V0LCBzdHlsZV9zaGVldF9pZCwgc3R5bGVzKSB7XG4gICAgY29uc3QgYXBwZW5kX3N0eWxlc190byA9IGdldF9yb290X2Zvcl9zdHlsZSh0YXJnZXQpO1xuICAgIGlmICghYXBwZW5kX3N0eWxlc190by5nZXRFbGVtZW50QnlJZChzdHlsZV9zaGVldF9pZCkpIHtcbiAgICAgICAgY29uc3Qgc3R5bGUgPSBlbGVtZW50KCdzdHlsZScpO1xuICAgICAgICBzdHlsZS5pZCA9IHN0eWxlX3NoZWV0X2lkO1xuICAgICAgICBzdHlsZS50ZXh0Q29udGVudCA9IHN0eWxlcztcbiAgICAgICAgYXBwZW5kX3N0eWxlc2hlZXQoYXBwZW5kX3N0eWxlc190bywgc3R5bGUpO1xuICAgIH1cbn1cbmZ1bmN0aW9uIGdldF9yb290X2Zvcl9zdHlsZShub2RlKSB7XG4gICAgaWYgKCFub2RlKVxuICAgICAgICByZXR1cm4gZG9jdW1lbnQ7XG4gICAgY29uc3Qgcm9vdCA9IG5vZGUuZ2V0Um9vdE5vZGUgPyBub2RlLmdldFJvb3ROb2RlKCkgOiBub2RlLm93bmVyRG9jdW1lbnQ7XG4gICAgaWYgKHJvb3QgJiYgcm9vdC5ob3N0KSB7XG4gICAgICAgIHJldHVybiByb290O1xuICAgIH1cbiAgICByZXR1cm4gbm9kZS5vd25lckRvY3VtZW50O1xufVxuZnVuY3Rpb24gYXBwZW5kX2VtcHR5X3N0eWxlc2hlZXQobm9kZSkge1xuICAgIGNvbnN0IHN0eWxlX2VsZW1lbnQgPSBlbGVtZW50KCdzdHlsZScpO1xuICAgIGFwcGVuZF9zdHlsZXNoZWV0KGdldF9yb290X2Zvcl9zdHlsZShub2RlKSwgc3R5bGVfZWxlbWVudCk7XG4gICAgcmV0dXJuIHN0eWxlX2VsZW1lbnQuc2hlZXQ7XG59XG5mdW5jdGlvbiBhcHBlbmRfc3R5bGVzaGVldChub2RlLCBzdHlsZSkge1xuICAgIGFwcGVuZChub2RlLmhlYWQgfHwgbm9kZSwgc3R5bGUpO1xuICAgIHJldHVybiBzdHlsZS5zaGVldDtcbn1cbmZ1bmN0aW9uIGFwcGVuZF9oeWRyYXRpb24odGFyZ2V0LCBub2RlKSB7XG4gICAgaWYgKGlzX2h5ZHJhdGluZykge1xuICAgICAgICBpbml0X2h5ZHJhdGUodGFyZ2V0KTtcbiAgICAgICAgaWYgKCh0YXJnZXQuYWN0dWFsX2VuZF9jaGlsZCA9PT0gdW5kZWZpbmVkKSB8fCAoKHRhcmdldC5hY3R1YWxfZW5kX2NoaWxkICE9PSBudWxsKSAmJiAodGFyZ2V0LmFjdHVhbF9lbmRfY2hpbGQucGFyZW50Tm9kZSAhPT0gdGFyZ2V0KSkpIHtcbiAgICAgICAgICAgIHRhcmdldC5hY3R1YWxfZW5kX2NoaWxkID0gdGFyZ2V0LmZpcnN0Q2hpbGQ7XG4gICAgICAgIH1cbiAgICAgICAgLy8gU2tpcCBub2RlcyBvZiB1bmRlZmluZWQgb3JkZXJpbmdcbiAgICAgICAgd2hpbGUgKCh0YXJnZXQuYWN0dWFsX2VuZF9jaGlsZCAhPT0gbnVsbCkgJiYgKHRhcmdldC5hY3R1YWxfZW5kX2NoaWxkLmNsYWltX29yZGVyID09PSB1bmRlZmluZWQpKSB7XG4gICAgICAgICAgICB0YXJnZXQuYWN0dWFsX2VuZF9jaGlsZCA9IHRhcmdldC5hY3R1YWxfZW5kX2NoaWxkLm5leHRTaWJsaW5nO1xuICAgICAgICB9XG4gICAgICAgIGlmIChub2RlICE9PSB0YXJnZXQuYWN0dWFsX2VuZF9jaGlsZCkge1xuICAgICAgICAgICAgLy8gV2Ugb25seSBpbnNlcnQgaWYgdGhlIG9yZGVyaW5nIG9mIHRoaXMgbm9kZSBzaG91bGQgYmUgbW9kaWZpZWQgb3IgdGhlIHBhcmVudCBub2RlIGlzIG5vdCB0YXJnZXRcbiAgICAgICAgICAgIGlmIChub2RlLmNsYWltX29yZGVyICE9PSB1bmRlZmluZWQgfHwgbm9kZS5wYXJlbnROb2RlICE9PSB0YXJnZXQpIHtcbiAgICAgICAgICAgICAgICB0YXJnZXQuaW5zZXJ0QmVmb3JlKG5vZGUsIHRhcmdldC5hY3R1YWxfZW5kX2NoaWxkKTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgICBlbHNlIHtcbiAgICAgICAgICAgIHRhcmdldC5hY3R1YWxfZW5kX2NoaWxkID0gbm9kZS5uZXh0U2libGluZztcbiAgICAgICAgfVxuICAgIH1cbiAgICBlbHNlIGlmIChub2RlLnBhcmVudE5vZGUgIT09IHRhcmdldCB8fCBub2RlLm5leHRTaWJsaW5nICE9PSBudWxsKSB7XG4gICAgICAgIHRhcmdldC5hcHBlbmRDaGlsZChub2RlKTtcbiAgICB9XG59XG5mdW5jdGlvbiBpbnNlcnQodGFyZ2V0LCBub2RlLCBhbmNob3IpIHtcbiAgICB0YXJnZXQuaW5zZXJ0QmVmb3JlKG5vZGUsIGFuY2hvciB8fCBudWxsKTtcbn1cbmZ1bmN0aW9uIGluc2VydF9oeWRyYXRpb24odGFyZ2V0LCBub2RlLCBhbmNob3IpIHtcbiAgICBpZiAoaXNfaHlkcmF0aW5nICYmICFhbmNob3IpIHtcbiAgICAgICAgYXBwZW5kX2h5ZHJhdGlvbih0YXJnZXQsIG5vZGUpO1xuICAgIH1cbiAgICBlbHNlIGlmIChub2RlLnBhcmVudE5vZGUgIT09IHRhcmdldCB8fCBub2RlLm5leHRTaWJsaW5nICE9IGFuY2hvcikge1xuICAgICAgICB0YXJnZXQuaW5zZXJ0QmVmb3JlKG5vZGUsIGFuY2hvciB8fCBudWxsKTtcbiAgICB9XG59XG5mdW5jdGlvbiBkZXRhY2gobm9kZSkge1xuICAgIGlmIChub2RlLnBhcmVudE5vZGUpIHtcbiAgICAgICAgbm9kZS5wYXJlbnROb2RlLnJlbW92ZUNoaWxkKG5vZGUpO1xuICAgIH1cbn1cbmZ1bmN0aW9uIGRlc3Ryb3lfZWFjaChpdGVyYXRpb25zLCBkZXRhY2hpbmcpIHtcbiAgICBmb3IgKGxldCBpID0gMDsgaSA8IGl0ZXJhdGlvbnMubGVuZ3RoOyBpICs9IDEpIHtcbiAgICAgICAgaWYgKGl0ZXJhdGlvbnNbaV0pXG4gICAgICAgICAgICBpdGVyYXRpb25zW2ldLmQoZGV0YWNoaW5nKTtcbiAgICB9XG59XG5mdW5jdGlvbiBlbGVtZW50KG5hbWUpIHtcbiAgICByZXR1cm4gZG9jdW1lbnQuY3JlYXRlRWxlbWVudChuYW1lKTtcbn1cbmZ1bmN0aW9uIGVsZW1lbnRfaXMobmFtZSwgaXMpIHtcbiAgICByZXR1cm4gZG9jdW1lbnQuY3JlYXRlRWxlbWVudChuYW1lLCB7IGlzIH0pO1xufVxuZnVuY3Rpb24gb2JqZWN0X3dpdGhvdXRfcHJvcGVydGllcyhvYmosIGV4Y2x1ZGUpIHtcbiAgICBjb25zdCB0YXJnZXQgPSB7fTtcbiAgICBmb3IgKGNvbnN0IGsgaW4gb2JqKSB7XG4gICAgICAgIGlmIChoYXNfcHJvcChvYmosIGspXG4gICAgICAgICAgICAvLyBAdHMtaWdub3JlXG4gICAgICAgICAgICAmJiBleGNsdWRlLmluZGV4T2YoaykgPT09IC0xKSB7XG4gICAgICAgICAgICAvLyBAdHMtaWdub3JlXG4gICAgICAgICAgICB0YXJnZXRba10gPSBvYmpba107XG4gICAgICAgIH1cbiAgICB9XG4gICAgcmV0dXJuIHRhcmdldDtcbn1cbmZ1bmN0aW9uIHN2Z19lbGVtZW50KG5hbWUpIHtcbiAgICByZXR1cm4gZG9jdW1lbnQuY3JlYXRlRWxlbWVudE5TKCdodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZycsIG5hbWUpO1xufVxuZnVuY3Rpb24gdGV4dChkYXRhKSB7XG4gICAgcmV0dXJuIGRvY3VtZW50LmNyZWF0ZVRleHROb2RlKGRhdGEpO1xufVxuZnVuY3Rpb24gc3BhY2UoKSB7XG4gICAgcmV0dXJuIHRleHQoJyAnKTtcbn1cbmZ1bmN0aW9uIGVtcHR5KCkge1xuICAgIHJldHVybiB0ZXh0KCcnKTtcbn1cbmZ1bmN0aW9uIGNvbW1lbnQoY29udGVudCkge1xuICAgIHJldHVybiBkb2N1bWVudC5jcmVhdGVDb21tZW50KGNvbnRlbnQpO1xufVxuZnVuY3Rpb24gbGlzdGVuKG5vZGUsIGV2ZW50LCBoYW5kbGVyLCBvcHRpb25zKSB7XG4gICAgbm9kZS5hZGRFdmVudExpc3RlbmVyKGV2ZW50LCBoYW5kbGVyLCBvcHRpb25zKTtcbiAgICByZXR1cm4gKCkgPT4gbm9kZS5yZW1vdmVFdmVudExpc3RlbmVyKGV2ZW50LCBoYW5kbGVyLCBvcHRpb25zKTtcbn1cbmZ1bmN0aW9uIHByZXZlbnRfZGVmYXVsdChmbikge1xuICAgIHJldHVybiBmdW5jdGlvbiAoZXZlbnQpIHtcbiAgICAgICAgZXZlbnQucHJldmVudERlZmF1bHQoKTtcbiAgICAgICAgLy8gQHRzLWlnbm9yZVxuICAgICAgICByZXR1cm4gZm4uY2FsbCh0aGlzLCBldmVudCk7XG4gICAgfTtcbn1cbmZ1bmN0aW9uIHN0b3BfcHJvcGFnYXRpb24oZm4pIHtcbiAgICByZXR1cm4gZnVuY3Rpb24gKGV2ZW50KSB7XG4gICAgICAgIGV2ZW50LnN0b3BQcm9wYWdhdGlvbigpO1xuICAgICAgICAvLyBAdHMtaWdub3JlXG4gICAgICAgIHJldHVybiBmbi5jYWxsKHRoaXMsIGV2ZW50KTtcbiAgICB9O1xufVxuZnVuY3Rpb24gc3RvcF9pbW1lZGlhdGVfcHJvcGFnYXRpb24oZm4pIHtcbiAgICByZXR1cm4gZnVuY3Rpb24gKGV2ZW50KSB7XG4gICAgICAgIGV2ZW50LnN0b3BJbW1lZGlhdGVQcm9wYWdhdGlvbigpO1xuICAgICAgICAvLyBAdHMtaWdub3JlXG4gICAgICAgIHJldHVybiBmbi5jYWxsKHRoaXMsIGV2ZW50KTtcbiAgICB9O1xufVxuZnVuY3Rpb24gc2VsZihmbikge1xuICAgIHJldHVybiBmdW5jdGlvbiAoZXZlbnQpIHtcbiAgICAgICAgLy8gQHRzLWlnbm9yZVxuICAgICAgICBpZiAoZXZlbnQudGFyZ2V0ID09PSB0aGlzKVxuICAgICAgICAgICAgZm4uY2FsbCh0aGlzLCBldmVudCk7XG4gICAgfTtcbn1cbmZ1bmN0aW9uIHRydXN0ZWQoZm4pIHtcbiAgICByZXR1cm4gZnVuY3Rpb24gKGV2ZW50KSB7XG4gICAgICAgIC8vIEB0cy1pZ25vcmVcbiAgICAgICAgaWYgKGV2ZW50LmlzVHJ1c3RlZClcbiAgICAgICAgICAgIGZuLmNhbGwodGhpcywgZXZlbnQpO1xuICAgIH07XG59XG5mdW5jdGlvbiBhdHRyKG5vZGUsIGF0dHJpYnV0ZSwgdmFsdWUpIHtcbiAgICBpZiAodmFsdWUgPT0gbnVsbClcbiAgICAgICAgbm9kZS5yZW1vdmVBdHRyaWJ1dGUoYXR0cmlidXRlKTtcbiAgICBlbHNlIGlmIChub2RlLmdldEF0dHJpYnV0ZShhdHRyaWJ1dGUpICE9PSB2YWx1ZSlcbiAgICAgICAgbm9kZS5zZXRBdHRyaWJ1dGUoYXR0cmlidXRlLCB2YWx1ZSk7XG59XG5mdW5jdGlvbiBzZXRfYXR0cmlidXRlcyhub2RlLCBhdHRyaWJ1dGVzKSB7XG4gICAgLy8gQHRzLWlnbm9yZVxuICAgIGNvbnN0IGRlc2NyaXB0b3JzID0gT2JqZWN0LmdldE93blByb3BlcnR5RGVzY3JpcHRvcnMobm9kZS5fX3Byb3RvX18pO1xuICAgIGZvciAoY29uc3Qga2V5IGluIGF0dHJpYnV0ZXMpIHtcbiAgICAgICAgaWYgKGF0dHJpYnV0ZXNba2V5XSA9PSBudWxsKSB7XG4gICAgICAgICAgICBub2RlLnJlbW92ZUF0dHJpYnV0ZShrZXkpO1xuICAgICAgICB9XG4gICAgICAgIGVsc2UgaWYgKGtleSA9PT0gJ3N0eWxlJykge1xuICAgICAgICAgICAgbm9kZS5zdHlsZS5jc3NUZXh0ID0gYXR0cmlidXRlc1trZXldO1xuICAgICAgICB9XG4gICAgICAgIGVsc2UgaWYgKGtleSA9PT0gJ19fdmFsdWUnKSB7XG4gICAgICAgICAgICBub2RlLnZhbHVlID0gbm9kZVtrZXldID0gYXR0cmlidXRlc1trZXldO1xuICAgICAgICB9XG4gICAgICAgIGVsc2UgaWYgKGRlc2NyaXB0b3JzW2tleV0gJiYgZGVzY3JpcHRvcnNba2V5XS5zZXQpIHtcbiAgICAgICAgICAgIG5vZGVba2V5XSA9IGF0dHJpYnV0ZXNba2V5XTtcbiAgICAgICAgfVxuICAgICAgICBlbHNlIHtcbiAgICAgICAgICAgIGF0dHIobm9kZSwga2V5LCBhdHRyaWJ1dGVzW2tleV0pO1xuICAgICAgICB9XG4gICAgfVxufVxuZnVuY3Rpb24gc2V0X3N2Z19hdHRyaWJ1dGVzKG5vZGUsIGF0dHJpYnV0ZXMpIHtcbiAgICBmb3IgKGNvbnN0IGtleSBpbiBhdHRyaWJ1dGVzKSB7XG4gICAgICAgIGF0dHIobm9kZSwga2V5LCBhdHRyaWJ1dGVzW2tleV0pO1xuICAgIH1cbn1cbmZ1bmN0aW9uIHNldF9jdXN0b21fZWxlbWVudF9kYXRhX21hcChub2RlLCBkYXRhX21hcCkge1xuICAgIE9iamVjdC5rZXlzKGRhdGFfbWFwKS5mb3JFYWNoKChrZXkpID0+IHtcbiAgICAgICAgc2V0X2N1c3RvbV9lbGVtZW50X2RhdGEobm9kZSwga2V5LCBkYXRhX21hcFtrZXldKTtcbiAgICB9KTtcbn1cbmZ1bmN0aW9uIHNldF9jdXN0b21fZWxlbWVudF9kYXRhKG5vZGUsIHByb3AsIHZhbHVlKSB7XG4gICAgaWYgKHByb3AgaW4gbm9kZSkge1xuICAgICAgICBub2RlW3Byb3BdID0gdHlwZW9mIG5vZGVbcHJvcF0gPT09ICdib29sZWFuJyAmJiB2YWx1ZSA9PT0gJycgPyB0cnVlIDogdmFsdWU7XG4gICAgfVxuICAgIGVsc2Uge1xuICAgICAgICBhdHRyKG5vZGUsIHByb3AsIHZhbHVlKTtcbiAgICB9XG59XG5mdW5jdGlvbiBzZXRfZHluYW1pY19lbGVtZW50X2RhdGEodGFnKSB7XG4gICAgcmV0dXJuICgvLS8udGVzdCh0YWcpKSA/IHNldF9jdXN0b21fZWxlbWVudF9kYXRhX21hcCA6IHNldF9hdHRyaWJ1dGVzO1xufVxuZnVuY3Rpb24geGxpbmtfYXR0cihub2RlLCBhdHRyaWJ1dGUsIHZhbHVlKSB7XG4gICAgbm9kZS5zZXRBdHRyaWJ1dGVOUygnaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluaycsIGF0dHJpYnV0ZSwgdmFsdWUpO1xufVxuZnVuY3Rpb24gZ2V0X2JpbmRpbmdfZ3JvdXBfdmFsdWUoZ3JvdXAsIF9fdmFsdWUsIGNoZWNrZWQpIHtcbiAgICBjb25zdCB2YWx1ZSA9IG5ldyBTZXQoKTtcbiAgICBmb3IgKGxldCBpID0gMDsgaSA8IGdyb3VwLmxlbmd0aDsgaSArPSAxKSB7XG4gICAgICAgIGlmIChncm91cFtpXS5jaGVja2VkKVxuICAgICAgICAgICAgdmFsdWUuYWRkKGdyb3VwW2ldLl9fdmFsdWUpO1xuICAgIH1cbiAgICBpZiAoIWNoZWNrZWQpIHtcbiAgICAgICAgdmFsdWUuZGVsZXRlKF9fdmFsdWUpO1xuICAgIH1cbiAgICByZXR1cm4gQXJyYXkuZnJvbSh2YWx1ZSk7XG59XG5mdW5jdGlvbiBpbml0X2JpbmRpbmdfZ3JvdXAoZ3JvdXApIHtcbiAgICBsZXQgX2lucHV0cztcbiAgICByZXR1cm4ge1xuICAgICAgICAvKiBwdXNoICovIHAoLi4uaW5wdXRzKSB7XG4gICAgICAgICAgICBfaW5wdXRzID0gaW5wdXRzO1xuICAgICAgICAgICAgX2lucHV0cy5mb3JFYWNoKGlucHV0ID0+IGdyb3VwLnB1c2goaW5wdXQpKTtcbiAgICAgICAgfSxcbiAgICAgICAgLyogcmVtb3ZlICovIHIoKSB7XG4gICAgICAgICAgICBfaW5wdXRzLmZvckVhY2goaW5wdXQgPT4gZ3JvdXAuc3BsaWNlKGdyb3VwLmluZGV4T2YoaW5wdXQpLCAxKSk7XG4gICAgICAgIH1cbiAgICB9O1xufVxuZnVuY3Rpb24gaW5pdF9iaW5kaW5nX2dyb3VwX2R5bmFtaWMoZ3JvdXAsIGluZGV4ZXMpIHtcbiAgICBsZXQgX2dyb3VwID0gZ2V0X2JpbmRpbmdfZ3JvdXAoZ3JvdXApO1xuICAgIGxldCBfaW5wdXRzO1xuICAgIGZ1bmN0aW9uIGdldF9iaW5kaW5nX2dyb3VwKGdyb3VwKSB7XG4gICAgICAgIGZvciAobGV0IGkgPSAwOyBpIDwgaW5kZXhlcy5sZW5ndGg7IGkrKykge1xuICAgICAgICAgICAgZ3JvdXAgPSBncm91cFtpbmRleGVzW2ldXSA9IGdyb3VwW2luZGV4ZXNbaV1dIHx8IFtdO1xuICAgICAgICB9XG4gICAgICAgIHJldHVybiBncm91cDtcbiAgICB9XG4gICAgZnVuY3Rpb24gcHVzaCgpIHtcbiAgICAgICAgX2lucHV0cy5mb3JFYWNoKGlucHV0ID0+IF9ncm91cC5wdXNoKGlucHV0KSk7XG4gICAgfVxuICAgIGZ1bmN0aW9uIHJlbW92ZSgpIHtcbiAgICAgICAgX2lucHV0cy5mb3JFYWNoKGlucHV0ID0+IF9ncm91cC5zcGxpY2UoX2dyb3VwLmluZGV4T2YoaW5wdXQpLCAxKSk7XG4gICAgfVxuICAgIHJldHVybiB7XG4gICAgICAgIC8qIHVwZGF0ZSAqLyB1KG5ld19pbmRleGVzKSB7XG4gICAgICAgICAgICBpbmRleGVzID0gbmV3X2luZGV4ZXM7XG4gICAgICAgICAgICBjb25zdCBuZXdfZ3JvdXAgPSBnZXRfYmluZGluZ19ncm91cChncm91cCk7XG4gICAgICAgICAgICBpZiAobmV3X2dyb3VwICE9PSBfZ3JvdXApIHtcbiAgICAgICAgICAgICAgICByZW1vdmUoKTtcbiAgICAgICAgICAgICAgICBfZ3JvdXAgPSBuZXdfZ3JvdXA7XG4gICAgICAgICAgICAgICAgcHVzaCgpO1xuICAgICAgICAgICAgfVxuICAgICAgICB9LFxuICAgICAgICAvKiBwdXNoICovIHAoLi4uaW5wdXRzKSB7XG4gICAgICAgICAgICBfaW5wdXRzID0gaW5wdXRzO1xuICAgICAgICAgICAgcHVzaCgpO1xuICAgICAgICB9LFxuICAgICAgICAvKiByZW1vdmUgKi8gcjogcmVtb3ZlXG4gICAgfTtcbn1cbmZ1bmN0aW9uIHRvX251bWJlcih2YWx1ZSkge1xuICAgIHJldHVybiB2YWx1ZSA9PT0gJycgPyBudWxsIDogK3ZhbHVlO1xufVxuZnVuY3Rpb24gdGltZV9yYW5nZXNfdG9fYXJyYXkocmFuZ2VzKSB7XG4gICAgY29uc3QgYXJyYXkgPSBbXTtcbiAgICBmb3IgKGxldCBpID0gMDsgaSA8IHJhbmdlcy5sZW5ndGg7IGkgKz0gMSkge1xuICAgICAgICBhcnJheS5wdXNoKHsgc3RhcnQ6IHJhbmdlcy5zdGFydChpKSwgZW5kOiByYW5nZXMuZW5kKGkpIH0pO1xuICAgIH1cbiAgICByZXR1cm4gYXJyYXk7XG59XG5mdW5jdGlvbiBjaGlsZHJlbihlbGVtZW50KSB7XG4gICAgcmV0dXJuIEFycmF5LmZyb20oZWxlbWVudC5jaGlsZE5vZGVzKTtcbn1cbmZ1bmN0aW9uIGluaXRfY2xhaW1faW5mbyhub2Rlcykge1xuICAgIGlmIChub2Rlcy5jbGFpbV9pbmZvID09PSB1bmRlZmluZWQpIHtcbiAgICAgICAgbm9kZXMuY2xhaW1faW5mbyA9IHsgbGFzdF9pbmRleDogMCwgdG90YWxfY2xhaW1lZDogMCB9O1xuICAgIH1cbn1cbmZ1bmN0aW9uIGNsYWltX25vZGUobm9kZXMsIHByZWRpY2F0ZSwgcHJvY2Vzc05vZGUsIGNyZWF0ZU5vZGUsIGRvbnRVcGRhdGVMYXN0SW5kZXggPSBmYWxzZSkge1xuICAgIC8vIFRyeSB0byBmaW5kIG5vZGVzIGluIGFuIG9yZGVyIHN1Y2ggdGhhdCB3ZSBsZW5ndGhlbiB0aGUgbG9uZ2VzdCBpbmNyZWFzaW5nIHN1YnNlcXVlbmNlXG4gICAgaW5pdF9jbGFpbV9pbmZvKG5vZGVzKTtcbiAgICBjb25zdCByZXN1bHROb2RlID0gKCgpID0+IHtcbiAgICAgICAgLy8gV2UgZmlyc3QgdHJ5IHRvIGZpbmQgYW4gZWxlbWVudCBhZnRlciB0aGUgcHJldmlvdXMgb25lXG4gICAgICAgIGZvciAobGV0IGkgPSBub2Rlcy5jbGFpbV9pbmZvLmxhc3RfaW5kZXg7IGkgPCBub2Rlcy5sZW5ndGg7IGkrKykge1xuICAgICAgICAgICAgY29uc3Qgbm9kZSA9IG5vZGVzW2ldO1xuICAgICAgICAgICAgaWYgKHByZWRpY2F0ZShub2RlKSkge1xuICAgICAgICAgICAgICAgIGNvbnN0IHJlcGxhY2VtZW50ID0gcHJvY2Vzc05vZGUobm9kZSk7XG4gICAgICAgICAgICAgICAgaWYgKHJlcGxhY2VtZW50ID09PSB1bmRlZmluZWQpIHtcbiAgICAgICAgICAgICAgICAgICAgbm9kZXMuc3BsaWNlKGksIDEpO1xuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICBlbHNlIHtcbiAgICAgICAgICAgICAgICAgICAgbm9kZXNbaV0gPSByZXBsYWNlbWVudDtcbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgaWYgKCFkb250VXBkYXRlTGFzdEluZGV4KSB7XG4gICAgICAgICAgICAgICAgICAgIG5vZGVzLmNsYWltX2luZm8ubGFzdF9pbmRleCA9IGk7XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgIHJldHVybiBub2RlO1xuICAgICAgICAgICAgfVxuICAgICAgICB9XG4gICAgICAgIC8vIE90aGVyd2lzZSwgd2UgdHJ5IHRvIGZpbmQgb25lIGJlZm9yZVxuICAgICAgICAvLyBXZSBpdGVyYXRlIGluIHJldmVyc2Ugc28gdGhhdCB3ZSBkb24ndCBnbyB0b28gZmFyIGJhY2tcbiAgICAgICAgZm9yIChsZXQgaSA9IG5vZGVzLmNsYWltX2luZm8ubGFzdF9pbmRleCAtIDE7IGkgPj0gMDsgaS0tKSB7XG4gICAgICAgICAgICBjb25zdCBub2RlID0gbm9kZXNbaV07XG4gICAgICAgICAgICBpZiAocHJlZGljYXRlKG5vZGUpKSB7XG4gICAgICAgICAgICAgICAgY29uc3QgcmVwbGFjZW1lbnQgPSBwcm9jZXNzTm9kZShub2RlKTtcbiAgICAgICAgICAgICAgICBpZiAocmVwbGFjZW1lbnQgPT09IHVuZGVmaW5lZCkge1xuICAgICAgICAgICAgICAgICAgICBub2Rlcy5zcGxpY2UoaSwgMSk7XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgIGVsc2Uge1xuICAgICAgICAgICAgICAgICAgICBub2Rlc1tpXSA9IHJlcGxhY2VtZW50O1xuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICBpZiAoIWRvbnRVcGRhdGVMYXN0SW5kZXgpIHtcbiAgICAgICAgICAgICAgICAgICAgbm9kZXMuY2xhaW1faW5mby5sYXN0X2luZGV4ID0gaTtcbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgZWxzZSBpZiAocmVwbGFjZW1lbnQgPT09IHVuZGVmaW5lZCkge1xuICAgICAgICAgICAgICAgICAgICAvLyBTaW5jZSB3ZSBzcGxpY2VkIGJlZm9yZSB0aGUgbGFzdF9pbmRleCwgd2UgZGVjcmVhc2UgaXRcbiAgICAgICAgICAgICAgICAgICAgbm9kZXMuY2xhaW1faW5mby5sYXN0X2luZGV4LS07XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgIHJldHVybiBub2RlO1xuICAgICAgICAgICAgfVxuICAgICAgICB9XG4gICAgICAgIC8vIElmIHdlIGNhbid0IGZpbmQgYW55IG1hdGNoaW5nIG5vZGUsIHdlIGNyZWF0ZSBhIG5ldyBvbmVcbiAgICAgICAgcmV0dXJuIGNyZWF0ZU5vZGUoKTtcbiAgICB9KSgpO1xuICAgIHJlc3VsdE5vZGUuY2xhaW1fb3JkZXIgPSBub2Rlcy5jbGFpbV9pbmZvLnRvdGFsX2NsYWltZWQ7XG4gICAgbm9kZXMuY2xhaW1faW5mby50b3RhbF9jbGFpbWVkICs9IDE7XG4gICAgcmV0dXJuIHJlc3VsdE5vZGU7XG59XG5mdW5jdGlvbiBjbGFpbV9lbGVtZW50X2Jhc2Uobm9kZXMsIG5hbWUsIGF0dHJpYnV0ZXMsIGNyZWF0ZV9lbGVtZW50KSB7XG4gICAgcmV0dXJuIGNsYWltX25vZGUobm9kZXMsIChub2RlKSA9PiBub2RlLm5vZGVOYW1lID09PSBuYW1lLCAobm9kZSkgPT4ge1xuICAgICAgICBjb25zdCByZW1vdmUgPSBbXTtcbiAgICAgICAgZm9yIChsZXQgaiA9IDA7IGogPCBub2RlLmF0dHJpYnV0ZXMubGVuZ3RoOyBqKyspIHtcbiAgICAgICAgICAgIGNvbnN0IGF0dHJpYnV0ZSA9IG5vZGUuYXR0cmlidXRlc1tqXTtcbiAgICAgICAgICAgIGlmICghYXR0cmlidXRlc1thdHRyaWJ1dGUubmFtZV0pIHtcbiAgICAgICAgICAgICAgICByZW1vdmUucHVzaChhdHRyaWJ1dGUubmFtZSk7XG4gICAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICAgICAgcmVtb3ZlLmZvckVhY2godiA9PiBub2RlLnJlbW92ZUF0dHJpYnV0ZSh2KSk7XG4gICAgICAgIHJldHVybiB1bmRlZmluZWQ7XG4gICAgfSwgKCkgPT4gY3JlYXRlX2VsZW1lbnQobmFtZSkpO1xufVxuZnVuY3Rpb24gY2xhaW1fZWxlbWVudChub2RlcywgbmFtZSwgYXR0cmlidXRlcykge1xuICAgIHJldHVybiBjbGFpbV9lbGVtZW50X2Jhc2Uobm9kZXMsIG5hbWUsIGF0dHJpYnV0ZXMsIGVsZW1lbnQpO1xufVxuZnVuY3Rpb24gY2xhaW1fc3ZnX2VsZW1lbnQobm9kZXMsIG5hbWUsIGF0dHJpYnV0ZXMpIHtcbiAgICByZXR1cm4gY2xhaW1fZWxlbWVudF9iYXNlKG5vZGVzLCBuYW1lLCBhdHRyaWJ1dGVzLCBzdmdfZWxlbWVudCk7XG59XG5mdW5jdGlvbiBjbGFpbV90ZXh0KG5vZGVzLCBkYXRhKSB7XG4gICAgcmV0dXJuIGNsYWltX25vZGUobm9kZXMsIChub2RlKSA9PiBub2RlLm5vZGVUeXBlID09PSAzLCAobm9kZSkgPT4ge1xuICAgICAgICBjb25zdCBkYXRhU3RyID0gJycgKyBkYXRhO1xuICAgICAgICBpZiAobm9kZS5kYXRhLnN0YXJ0c1dpdGgoZGF0YVN0cikpIHtcbiAgICAgICAgICAgIGlmIChub2RlLmRhdGEubGVuZ3RoICE9PSBkYXRhU3RyLmxlbmd0aCkge1xuICAgICAgICAgICAgICAgIHJldHVybiBub2RlLnNwbGl0VGV4dChkYXRhU3RyLmxlbmd0aCk7XG4gICAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICAgICAgZWxzZSB7XG4gICAgICAgICAgICBub2RlLmRhdGEgPSBkYXRhU3RyO1xuICAgICAgICB9XG4gICAgfSwgKCkgPT4gdGV4dChkYXRhKSwgdHJ1ZSAvLyBUZXh0IG5vZGVzIHNob3VsZCBub3QgdXBkYXRlIGxhc3QgaW5kZXggc2luY2UgaXQgaXMgbGlrZWx5IG5vdCB3b3J0aCBpdCB0byBlbGltaW5hdGUgYW4gaW5jcmVhc2luZyBzdWJzZXF1ZW5jZSBvZiBhY3R1YWwgZWxlbWVudHNcbiAgICApO1xufVxuZnVuY3Rpb24gY2xhaW1fc3BhY2Uobm9kZXMpIHtcbiAgICByZXR1cm4gY2xhaW1fdGV4dChub2RlcywgJyAnKTtcbn1cbmZ1bmN0aW9uIGNsYWltX2NvbW1lbnQobm9kZXMsIGRhdGEpIHtcbiAgICByZXR1cm4gY2xhaW1fbm9kZShub2RlcywgKG5vZGUpID0+IG5vZGUubm9kZVR5cGUgPT09IDgsIChub2RlKSA9PiB7XG4gICAgICAgIG5vZGUuZGF0YSA9ICcnICsgZGF0YTtcbiAgICAgICAgcmV0dXJuIHVuZGVmaW5lZDtcbiAgICB9LCAoKSA9PiBjb21tZW50KGRhdGEpLCB0cnVlKTtcbn1cbmZ1bmN0aW9uIGZpbmRfY29tbWVudChub2RlcywgdGV4dCwgc3RhcnQpIHtcbiAgICBmb3IgKGxldCBpID0gc3RhcnQ7IGkgPCBub2Rlcy5sZW5ndGg7IGkgKz0gMSkge1xuICAgICAgICBjb25zdCBub2RlID0gbm9kZXNbaV07XG4gICAgICAgIGlmIChub2RlLm5vZGVUeXBlID09PSA4IC8qIGNvbW1lbnQgbm9kZSAqLyAmJiBub2RlLnRleHRDb250ZW50LnRyaW0oKSA9PT0gdGV4dCkge1xuICAgICAgICAgICAgcmV0dXJuIGk7XG4gICAgICAgIH1cbiAgICB9XG4gICAgcmV0dXJuIG5vZGVzLmxlbmd0aDtcbn1cbmZ1bmN0aW9uIGNsYWltX2h0bWxfdGFnKG5vZGVzLCBpc19zdmcpIHtcbiAgICAvLyBmaW5kIGh0bWwgb3BlbmluZyB0YWdcbiAgICBjb25zdCBzdGFydF9pbmRleCA9IGZpbmRfY29tbWVudChub2RlcywgJ0hUTUxfVEFHX1NUQVJUJywgMCk7XG4gICAgY29uc3QgZW5kX2luZGV4ID0gZmluZF9jb21tZW50KG5vZGVzLCAnSFRNTF9UQUdfRU5EJywgc3RhcnRfaW5kZXgpO1xuICAgIGlmIChzdGFydF9pbmRleCA9PT0gZW5kX2luZGV4KSB7XG4gICAgICAgIHJldHVybiBuZXcgSHRtbFRhZ0h5ZHJhdGlvbih1bmRlZmluZWQsIGlzX3N2Zyk7XG4gICAgfVxuICAgIGluaXRfY2xhaW1faW5mbyhub2Rlcyk7XG4gICAgY29uc3QgaHRtbF90YWdfbm9kZXMgPSBub2Rlcy5zcGxpY2Uoc3RhcnRfaW5kZXgsIGVuZF9pbmRleCAtIHN0YXJ0X2luZGV4ICsgMSk7XG4gICAgZGV0YWNoKGh0bWxfdGFnX25vZGVzWzBdKTtcbiAgICBkZXRhY2goaHRtbF90YWdfbm9kZXNbaHRtbF90YWdfbm9kZXMubGVuZ3RoIC0gMV0pO1xuICAgIGNvbnN0IGNsYWltZWRfbm9kZXMgPSBodG1sX3RhZ19ub2Rlcy5zbGljZSgxLCBodG1sX3RhZ19ub2Rlcy5sZW5ndGggLSAxKTtcbiAgICBmb3IgKGNvbnN0IG4gb2YgY2xhaW1lZF9ub2Rlcykge1xuICAgICAgICBuLmNsYWltX29yZGVyID0gbm9kZXMuY2xhaW1faW5mby50b3RhbF9jbGFpbWVkO1xuICAgICAgICBub2Rlcy5jbGFpbV9pbmZvLnRvdGFsX2NsYWltZWQgKz0gMTtcbiAgICB9XG4gICAgcmV0dXJuIG5ldyBIdG1sVGFnSHlkcmF0aW9uKGNsYWltZWRfbm9kZXMsIGlzX3N2Zyk7XG59XG5mdW5jdGlvbiBzZXRfZGF0YSh0ZXh0LCBkYXRhKSB7XG4gICAgZGF0YSA9ICcnICsgZGF0YTtcbiAgICBpZiAodGV4dC5kYXRhID09PSBkYXRhKVxuICAgICAgICByZXR1cm47XG4gICAgdGV4dC5kYXRhID0gZGF0YTtcbn1cbmZ1bmN0aW9uIHNldF9kYXRhX2NvbnRlbnRlZGl0YWJsZSh0ZXh0LCBkYXRhKSB7XG4gICAgZGF0YSA9ICcnICsgZGF0YTtcbiAgICBpZiAodGV4dC53aG9sZVRleHQgPT09IGRhdGEpXG4gICAgICAgIHJldHVybjtcbiAgICB0ZXh0LmRhdGEgPSBkYXRhO1xufVxuZnVuY3Rpb24gc2V0X2RhdGFfbWF5YmVfY29udGVudGVkaXRhYmxlKHRleHQsIGRhdGEsIGF0dHJfdmFsdWUpIHtcbiAgICBpZiAofmNvbnRlbnRlZGl0YWJsZV90cnV0aHlfdmFsdWVzLmluZGV4T2YoYXR0cl92YWx1ZSkpIHtcbiAgICAgICAgc2V0X2RhdGFfY29udGVudGVkaXRhYmxlKHRleHQsIGRhdGEpO1xuICAgIH1cbiAgICBlbHNlIHtcbiAgICAgICAgc2V0X2RhdGEodGV4dCwgZGF0YSk7XG4gICAgfVxufVxuZnVuY3Rpb24gc2V0X2lucHV0X3ZhbHVlKGlucHV0LCB2YWx1ZSkge1xuICAgIGlucHV0LnZhbHVlID0gdmFsdWUgPT0gbnVsbCA/ICcnIDogdmFsdWU7XG59XG5mdW5jdGlvbiBzZXRfaW5wdXRfdHlwZShpbnB1dCwgdHlwZSkge1xuICAgIHRyeSB7XG4gICAgICAgIGlucHV0LnR5cGUgPSB0eXBlO1xuICAgIH1cbiAgICBjYXRjaCAoZSkge1xuICAgICAgICAvLyBkbyBub3RoaW5nXG4gICAgfVxufVxuZnVuY3Rpb24gc2V0X3N0eWxlKG5vZGUsIGtleSwgdmFsdWUsIGltcG9ydGFudCkge1xuICAgIGlmICh2YWx1ZSA9PT0gbnVsbCkge1xuICAgICAgICBub2RlLnN0eWxlLnJlbW92ZVByb3BlcnR5KGtleSk7XG4gICAgfVxuICAgIGVsc2Uge1xuICAgICAgICBub2RlLnN0eWxlLnNldFByb3BlcnR5KGtleSwgdmFsdWUsIGltcG9ydGFudCA/ICdpbXBvcnRhbnQnIDogJycpO1xuICAgIH1cbn1cbmZ1bmN0aW9uIHNlbGVjdF9vcHRpb24oc2VsZWN0LCB2YWx1ZSwgbW91bnRpbmcpIHtcbiAgICBmb3IgKGxldCBpID0gMDsgaSA8IHNlbGVjdC5vcHRpb25zLmxlbmd0aDsgaSArPSAxKSB7XG4gICAgICAgIGNvbnN0IG9wdGlvbiA9IHNlbGVjdC5vcHRpb25zW2ldO1xuICAgICAgICBpZiAob3B0aW9uLl9fdmFsdWUgPT09IHZhbHVlKSB7XG4gICAgICAgICAgICBvcHRpb24uc2VsZWN0ZWQgPSB0cnVlO1xuICAgICAgICAgICAgcmV0dXJuO1xuICAgICAgICB9XG4gICAgfVxuICAgIGlmICghbW91bnRpbmcgfHwgdmFsdWUgIT09IHVuZGVmaW5lZCkge1xuICAgICAgICBzZWxlY3Quc2VsZWN0ZWRJbmRleCA9IC0xOyAvLyBubyBvcHRpb24gc2hvdWxkIGJlIHNlbGVjdGVkXG4gICAgfVxufVxuZnVuY3Rpb24gc2VsZWN0X29wdGlvbnMoc2VsZWN0LCB2YWx1ZSkge1xuICAgIGZvciAobGV0IGkgPSAwOyBpIDwgc2VsZWN0Lm9wdGlvbnMubGVuZ3RoOyBpICs9IDEpIHtcbiAgICAgICAgY29uc3Qgb3B0aW9uID0gc2VsZWN0Lm9wdGlvbnNbaV07XG4gICAgICAgIG9wdGlvbi5zZWxlY3RlZCA9IH52YWx1ZS5pbmRleE9mKG9wdGlvbi5fX3ZhbHVlKTtcbiAgICB9XG59XG5mdW5jdGlvbiBzZWxlY3RfdmFsdWUoc2VsZWN0KSB7XG4gICAgY29uc3Qgc2VsZWN0ZWRfb3B0aW9uID0gc2VsZWN0LnF1ZXJ5U2VsZWN0b3IoJzpjaGVja2VkJyk7XG4gICAgcmV0dXJuIHNlbGVjdGVkX29wdGlvbiAmJiBzZWxlY3RlZF9vcHRpb24uX192YWx1ZTtcbn1cbmZ1bmN0aW9uIHNlbGVjdF9tdWx0aXBsZV92YWx1ZShzZWxlY3QpIHtcbiAgICByZXR1cm4gW10ubWFwLmNhbGwoc2VsZWN0LnF1ZXJ5U2VsZWN0b3JBbGwoJzpjaGVja2VkJyksIG9wdGlvbiA9PiBvcHRpb24uX192YWx1ZSk7XG59XG4vLyB1bmZvcnR1bmF0ZWx5IHRoaXMgY2FuJ3QgYmUgYSBjb25zdGFudCBhcyB0aGF0IHdvdWxkbid0IGJlIHRyZWUtc2hha2VhYmxlXG4vLyBzbyB3ZSBjYWNoZSB0aGUgcmVzdWx0IGluc3RlYWRcbmxldCBjcm9zc29yaWdpbjtcbmZ1bmN0aW9uIGlzX2Nyb3Nzb3JpZ2luKCkge1xuICAgIGlmIChjcm9zc29yaWdpbiA9PT0gdW5kZWZpbmVkKSB7XG4gICAgICAgIGNyb3Nzb3JpZ2luID0gZmFsc2U7XG4gICAgICAgIHRyeSB7XG4gICAgICAgICAgICBpZiAodHlwZW9mIHdpbmRvdyAhPT0gJ3VuZGVmaW5lZCcgJiYgd2luZG93LnBhcmVudCkge1xuICAgICAgICAgICAgICAgIHZvaWQgd2luZG93LnBhcmVudC5kb2N1bWVudDtcbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgICBjYXRjaCAoZXJyb3IpIHtcbiAgICAgICAgICAgIGNyb3Nzb3JpZ2luID0gdHJ1ZTtcbiAgICAgICAgfVxuICAgIH1cbiAgICByZXR1cm4gY3Jvc3NvcmlnaW47XG59XG5mdW5jdGlvbiBhZGRfcmVzaXplX2xpc3RlbmVyKG5vZGUsIGZuKSB7XG4gICAgY29uc3QgY29tcHV0ZWRfc3R5bGUgPSBnZXRDb21wdXRlZFN0eWxlKG5vZGUpO1xuICAgIGlmIChjb21wdXRlZF9zdHlsZS5wb3NpdGlvbiA9PT0gJ3N0YXRpYycpIHtcbiAgICAgICAgbm9kZS5zdHlsZS5wb3NpdGlvbiA9ICdyZWxhdGl2ZSc7XG4gICAgfVxuICAgIGNvbnN0IGlmcmFtZSA9IGVsZW1lbnQoJ2lmcmFtZScpO1xuICAgIGlmcmFtZS5zZXRBdHRyaWJ1dGUoJ3N0eWxlJywgJ2Rpc3BsYXk6IGJsb2NrOyBwb3NpdGlvbjogYWJzb2x1dGU7IHRvcDogMDsgbGVmdDogMDsgd2lkdGg6IDEwMCU7IGhlaWdodDogMTAwJTsgJyArXG4gICAgICAgICdvdmVyZmxvdzogaGlkZGVuOyBib3JkZXI6IDA7IG9wYWNpdHk6IDA7IHBvaW50ZXItZXZlbnRzOiBub25lOyB6LWluZGV4OiAtMTsnKTtcbiAgICBpZnJhbWUuc2V0QXR0cmlidXRlKCdhcmlhLWhpZGRlbicsICd0cnVlJyk7XG4gICAgaWZyYW1lLnRhYkluZGV4ID0gLTE7XG4gICAgY29uc3QgY3Jvc3NvcmlnaW4gPSBpc19jcm9zc29yaWdpbigpO1xuICAgIGxldCB1bnN1YnNjcmliZTtcbiAgICBpZiAoY3Jvc3NvcmlnaW4pIHtcbiAgICAgICAgaWZyYW1lLnNyYyA9IFwiZGF0YTp0ZXh0L2h0bWwsPHNjcmlwdD5vbnJlc2l6ZT1mdW5jdGlvbigpe3BhcmVudC5wb3N0TWVzc2FnZSgwLCcqJyl9PC9zY3JpcHQ+XCI7XG4gICAgICAgIHVuc3Vic2NyaWJlID0gbGlzdGVuKHdpbmRvdywgJ21lc3NhZ2UnLCAoZXZlbnQpID0+IHtcbiAgICAgICAgICAgIGlmIChldmVudC5zb3VyY2UgPT09IGlmcmFtZS5jb250ZW50V2luZG93KVxuICAgICAgICAgICAgICAgIGZuKCk7XG4gICAgICAgIH0pO1xuICAgIH1cbiAgICBlbHNlIHtcbiAgICAgICAgaWZyYW1lLnNyYyA9ICdhYm91dDpibGFuayc7XG4gICAgICAgIGlmcmFtZS5vbmxvYWQgPSAoKSA9PiB7XG4gICAgICAgICAgICB1bnN1YnNjcmliZSA9IGxpc3RlbihpZnJhbWUuY29udGVudFdpbmRvdywgJ3Jlc2l6ZScsIGZuKTtcbiAgICAgICAgICAgIC8vIG1ha2Ugc3VyZSBhbiBpbml0aWFsIHJlc2l6ZSBldmVudCBpcyBmaXJlZCBfYWZ0ZXJfIHRoZSBpZnJhbWUgaXMgbG9hZGVkICh3aGljaCBpcyBhc3luY2hyb25vdXMpXG4gICAgICAgICAgICAvLyBzZWUgaHR0cHM6Ly9naXRodWIuY29tL3N2ZWx0ZWpzL3N2ZWx0ZS9pc3N1ZXMvNDIzM1xuICAgICAgICAgICAgZm4oKTtcbiAgICAgICAgfTtcbiAgICB9XG4gICAgYXBwZW5kKG5vZGUsIGlmcmFtZSk7XG4gICAgcmV0dXJuICgpID0+IHtcbiAgICAgICAgaWYgKGNyb3Nzb3JpZ2luKSB7XG4gICAgICAgICAgICB1bnN1YnNjcmliZSgpO1xuICAgICAgICB9XG4gICAgICAgIGVsc2UgaWYgKHVuc3Vic2NyaWJlICYmIGlmcmFtZS5jb250ZW50V2luZG93KSB7XG4gICAgICAgICAgICB1bnN1YnNjcmliZSgpO1xuICAgICAgICB9XG4gICAgICAgIGRldGFjaChpZnJhbWUpO1xuICAgIH07XG59XG5mdW5jdGlvbiB0b2dnbGVfY2xhc3MoZWxlbWVudCwgbmFtZSwgdG9nZ2xlKSB7XG4gICAgZWxlbWVudC5jbGFzc0xpc3RbdG9nZ2xlID8gJ2FkZCcgOiAncmVtb3ZlJ10obmFtZSk7XG59XG5mdW5jdGlvbiBjdXN0b21fZXZlbnQodHlwZSwgZGV0YWlsLCB7IGJ1YmJsZXMgPSBmYWxzZSwgY2FuY2VsYWJsZSA9IGZhbHNlIH0gPSB7fSkge1xuICAgIGNvbnN0IGUgPSBkb2N1bWVudC5jcmVhdGVFdmVudCgnQ3VzdG9tRXZlbnQnKTtcbiAgICBlLmluaXRDdXN0b21FdmVudCh0eXBlLCBidWJibGVzLCBjYW5jZWxhYmxlLCBkZXRhaWwpO1xuICAgIHJldHVybiBlO1xufVxuZnVuY3Rpb24gcXVlcnlfc2VsZWN0b3JfYWxsKHNlbGVjdG9yLCBwYXJlbnQgPSBkb2N1bWVudC5ib2R5KSB7XG4gICAgcmV0dXJuIEFycmF5LmZyb20ocGFyZW50LnF1ZXJ5U2VsZWN0b3JBbGwoc2VsZWN0b3IpKTtcbn1cbmZ1bmN0aW9uIGhlYWRfc2VsZWN0b3Iobm9kZUlkLCBoZWFkKSB7XG4gICAgY29uc3QgcmVzdWx0ID0gW107XG4gICAgbGV0IHN0YXJ0ZWQgPSAwO1xuICAgIGZvciAoY29uc3Qgbm9kZSBvZiBoZWFkLmNoaWxkTm9kZXMpIHtcbiAgICAgICAgaWYgKG5vZGUubm9kZVR5cGUgPT09IDggLyogY29tbWVudCBub2RlICovKSB7XG4gICAgICAgICAgICBjb25zdCBjb21tZW50ID0gbm9kZS50ZXh0Q29udGVudC50cmltKCk7XG4gICAgICAgICAgICBpZiAoY29tbWVudCA9PT0gYEhFQURfJHtub2RlSWR9X0VORGApIHtcbiAgICAgICAgICAgICAgICBzdGFydGVkIC09IDE7XG4gICAgICAgICAgICAgICAgcmVzdWx0LnB1c2gobm9kZSk7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICBlbHNlIGlmIChjb21tZW50ID09PSBgSEVBRF8ke25vZGVJZH1fU1RBUlRgKSB7XG4gICAgICAgICAgICAgICAgc3RhcnRlZCArPSAxO1xuICAgICAgICAgICAgICAgIHJlc3VsdC5wdXNoKG5vZGUpO1xuICAgICAgICAgICAgfVxuICAgICAgICB9XG4gICAgICAgIGVsc2UgaWYgKHN0YXJ0ZWQgPiAwKSB7XG4gICAgICAgICAgICByZXN1bHQucHVzaChub2RlKTtcbiAgICAgICAgfVxuICAgIH1cbiAgICByZXR1cm4gcmVzdWx0O1xufVxuY2xhc3MgSHRtbFRhZyB7XG4gICAgY29uc3RydWN0b3IoaXNfc3ZnID0gZmFsc2UpIHtcbiAgICAgICAgdGhpcy5pc19zdmcgPSBmYWxzZTtcbiAgICAgICAgdGhpcy5pc19zdmcgPSBpc19zdmc7XG4gICAgICAgIHRoaXMuZSA9IHRoaXMubiA9IG51bGw7XG4gICAgfVxuICAgIGMoaHRtbCkge1xuICAgICAgICB0aGlzLmgoaHRtbCk7XG4gICAgfVxuICAgIG0oaHRtbCwgdGFyZ2V0LCBhbmNob3IgPSBudWxsKSB7XG4gICAgICAgIGlmICghdGhpcy5lKSB7XG4gICAgICAgICAgICBpZiAodGhpcy5pc19zdmcpXG4gICAgICAgICAgICAgICAgdGhpcy5lID0gc3ZnX2VsZW1lbnQodGFyZ2V0Lm5vZGVOYW1lKTtcbiAgICAgICAgICAgIC8qKiAjNzM2NCAgdGFyZ2V0IGZvciA8dGVtcGxhdGU+IG1heSBiZSBwcm92aWRlZCBhcyAjZG9jdW1lbnQtZnJhZ21lbnQoMTEpICovXG4gICAgICAgICAgICBlbHNlXG4gICAgICAgICAgICAgICAgdGhpcy5lID0gZWxlbWVudCgodGFyZ2V0Lm5vZGVUeXBlID09PSAxMSA/ICdURU1QTEFURScgOiB0YXJnZXQubm9kZU5hbWUpKTtcbiAgICAgICAgICAgIHRoaXMudCA9IHRhcmdldC50YWdOYW1lICE9PSAnVEVNUExBVEUnID8gdGFyZ2V0IDogdGFyZ2V0LmNvbnRlbnQ7XG4gICAgICAgICAgICB0aGlzLmMoaHRtbCk7XG4gICAgICAgIH1cbiAgICAgICAgdGhpcy5pKGFuY2hvcik7XG4gICAgfVxuICAgIGgoaHRtbCkge1xuICAgICAgICB0aGlzLmUuaW5uZXJIVE1MID0gaHRtbDtcbiAgICAgICAgdGhpcy5uID0gQXJyYXkuZnJvbSh0aGlzLmUubm9kZU5hbWUgPT09ICdURU1QTEFURScgPyB0aGlzLmUuY29udGVudC5jaGlsZE5vZGVzIDogdGhpcy5lLmNoaWxkTm9kZXMpO1xuICAgIH1cbiAgICBpKGFuY2hvcikge1xuICAgICAgICBmb3IgKGxldCBpID0gMDsgaSA8IHRoaXMubi5sZW5ndGg7IGkgKz0gMSkge1xuICAgICAgICAgICAgaW5zZXJ0KHRoaXMudCwgdGhpcy5uW2ldLCBhbmNob3IpO1xuICAgICAgICB9XG4gICAgfVxuICAgIHAoaHRtbCkge1xuICAgICAgICB0aGlzLmQoKTtcbiAgICAgICAgdGhpcy5oKGh0bWwpO1xuICAgICAgICB0aGlzLmkodGhpcy5hKTtcbiAgICB9XG4gICAgZCgpIHtcbiAgICAgICAgdGhpcy5uLmZvckVhY2goZGV0YWNoKTtcbiAgICB9XG59XG5jbGFzcyBIdG1sVGFnSHlkcmF0aW9uIGV4dGVuZHMgSHRtbFRhZyB7XG4gICAgY29uc3RydWN0b3IoY2xhaW1lZF9ub2RlcywgaXNfc3ZnID0gZmFsc2UpIHtcbiAgICAgICAgc3VwZXIoaXNfc3ZnKTtcbiAgICAgICAgdGhpcy5lID0gdGhpcy5uID0gbnVsbDtcbiAgICAgICAgdGhpcy5sID0gY2xhaW1lZF9ub2RlcztcbiAgICB9XG4gICAgYyhodG1sKSB7XG4gICAgICAgIGlmICh0aGlzLmwpIHtcbiAgICAgICAgICAgIHRoaXMubiA9IHRoaXMubDtcbiAgICAgICAgfVxuICAgICAgICBlbHNlIHtcbiAgICAgICAgICAgIHN1cGVyLmMoaHRtbCk7XG4gICAgICAgIH1cbiAgICB9XG4gICAgaShhbmNob3IpIHtcbiAgICAgICAgZm9yIChsZXQgaSA9IDA7IGkgPCB0aGlzLm4ubGVuZ3RoOyBpICs9IDEpIHtcbiAgICAgICAgICAgIGluc2VydF9oeWRyYXRpb24odGhpcy50LCB0aGlzLm5baV0sIGFuY2hvcik7XG4gICAgICAgIH1cbiAgICB9XG59XG5mdW5jdGlvbiBhdHRyaWJ1dGVfdG9fb2JqZWN0KGF0dHJpYnV0ZXMpIHtcbiAgICBjb25zdCByZXN1bHQgPSB7fTtcbiAgICBmb3IgKGNvbnN0IGF0dHJpYnV0ZSBvZiBhdHRyaWJ1dGVzKSB7XG4gICAgICAgIHJlc3VsdFthdHRyaWJ1dGUubmFtZV0gPSBhdHRyaWJ1dGUudmFsdWU7XG4gICAgfVxuICAgIHJldHVybiByZXN1bHQ7XG59XG5mdW5jdGlvbiBnZXRfY3VzdG9tX2VsZW1lbnRzX3Nsb3RzKGVsZW1lbnQpIHtcbiAgICBjb25zdCByZXN1bHQgPSB7fTtcbiAgICBlbGVtZW50LmNoaWxkTm9kZXMuZm9yRWFjaCgobm9kZSkgPT4ge1xuICAgICAgICByZXN1bHRbbm9kZS5zbG90IHx8ICdkZWZhdWx0J10gPSB0cnVlO1xuICAgIH0pO1xuICAgIHJldHVybiByZXN1bHQ7XG59XG5mdW5jdGlvbiBjb25zdHJ1Y3Rfc3ZlbHRlX2NvbXBvbmVudChjb21wb25lbnQsIHByb3BzKSB7XG4gICAgcmV0dXJuIG5ldyBjb21wb25lbnQocHJvcHMpO1xufVxuXG4vLyB3ZSBuZWVkIHRvIHN0b3JlIHRoZSBpbmZvcm1hdGlvbiBmb3IgbXVsdGlwbGUgZG9jdW1lbnRzIGJlY2F1c2UgYSBTdmVsdGUgYXBwbGljYXRpb24gY291bGQgYWxzbyBjb250YWluIGlmcmFtZXNcbi8vIGh0dHBzOi8vZ2l0aHViLmNvbS9zdmVsdGVqcy9zdmVsdGUvaXNzdWVzLzM2MjRcbmNvbnN0IG1hbmFnZWRfc3R5bGVzID0gbmV3IE1hcCgpO1xubGV0IGFjdGl2ZSA9IDA7XG4vLyBodHRwczovL2dpdGh1Yi5jb20vZGFya3NreWFwcC9zdHJpbmctaGFzaC9ibG9iL21hc3Rlci9pbmRleC5qc1xuZnVuY3Rpb24gaGFzaChzdHIpIHtcbiAgICBsZXQgaGFzaCA9IDUzODE7XG4gICAgbGV0IGkgPSBzdHIubGVuZ3RoO1xuICAgIHdoaWxlIChpLS0pXG4gICAgICAgIGhhc2ggPSAoKGhhc2ggPDwgNSkgLSBoYXNoKSBeIHN0ci5jaGFyQ29kZUF0KGkpO1xuICAgIHJldHVybiBoYXNoID4+PiAwO1xufVxuZnVuY3Rpb24gY3JlYXRlX3N0eWxlX2luZm9ybWF0aW9uKGRvYywgbm9kZSkge1xuICAgIGNvbnN0IGluZm8gPSB7IHN0eWxlc2hlZXQ6IGFwcGVuZF9lbXB0eV9zdHlsZXNoZWV0KG5vZGUpLCBydWxlczoge30gfTtcbiAgICBtYW5hZ2VkX3N0eWxlcy5zZXQoZG9jLCBpbmZvKTtcbiAgICByZXR1cm4gaW5mbztcbn1cbmZ1bmN0aW9uIGNyZWF0ZV9ydWxlKG5vZGUsIGEsIGIsIGR1cmF0aW9uLCBkZWxheSwgZWFzZSwgZm4sIHVpZCA9IDApIHtcbiAgICBjb25zdCBzdGVwID0gMTYuNjY2IC8gZHVyYXRpb247XG4gICAgbGV0IGtleWZyYW1lcyA9ICd7XFxuJztcbiAgICBmb3IgKGxldCBwID0gMDsgcCA8PSAxOyBwICs9IHN0ZXApIHtcbiAgICAgICAgY29uc3QgdCA9IGEgKyAoYiAtIGEpICogZWFzZShwKTtcbiAgICAgICAga2V5ZnJhbWVzICs9IHAgKiAxMDAgKyBgJXske2ZuKHQsIDEgLSB0KX19XFxuYDtcbiAgICB9XG4gICAgY29uc3QgcnVsZSA9IGtleWZyYW1lcyArIGAxMDAlIHske2ZuKGIsIDEgLSBiKX19XFxufWA7XG4gICAgY29uc3QgbmFtZSA9IGBfX3N2ZWx0ZV8ke2hhc2gocnVsZSl9XyR7dWlkfWA7XG4gICAgY29uc3QgZG9jID0gZ2V0X3Jvb3RfZm9yX3N0eWxlKG5vZGUpO1xuICAgIGNvbnN0IHsgc3R5bGVzaGVldCwgcnVsZXMgfSA9IG1hbmFnZWRfc3R5bGVzLmdldChkb2MpIHx8IGNyZWF0ZV9zdHlsZV9pbmZvcm1hdGlvbihkb2MsIG5vZGUpO1xuICAgIGlmICghcnVsZXNbbmFtZV0pIHtcbiAgICAgICAgcnVsZXNbbmFtZV0gPSB0cnVlO1xuICAgICAgICBzdHlsZXNoZWV0Lmluc2VydFJ1bGUoYEBrZXlmcmFtZXMgJHtuYW1lfSAke3J1bGV9YCwgc3R5bGVzaGVldC5jc3NSdWxlcy5sZW5ndGgpO1xuICAgIH1cbiAgICBjb25zdCBhbmltYXRpb24gPSBub2RlLnN0eWxlLmFuaW1hdGlvbiB8fCAnJztcbiAgICBub2RlLnN0eWxlLmFuaW1hdGlvbiA9IGAke2FuaW1hdGlvbiA/IGAke2FuaW1hdGlvbn0sIGAgOiAnJ30ke25hbWV9ICR7ZHVyYXRpb259bXMgbGluZWFyICR7ZGVsYXl9bXMgMSBib3RoYDtcbiAgICBhY3RpdmUgKz0gMTtcbiAgICByZXR1cm4gbmFtZTtcbn1cbmZ1bmN0aW9uIGRlbGV0ZV9ydWxlKG5vZGUsIG5hbWUpIHtcbiAgICBjb25zdCBwcmV2aW91cyA9IChub2RlLnN0eWxlLmFuaW1hdGlvbiB8fCAnJykuc3BsaXQoJywgJyk7XG4gICAgY29uc3QgbmV4dCA9IHByZXZpb3VzLmZpbHRlcihuYW1lXG4gICAgICAgID8gYW5pbSA9PiBhbmltLmluZGV4T2YobmFtZSkgPCAwIC8vIHJlbW92ZSBzcGVjaWZpYyBhbmltYXRpb25cbiAgICAgICAgOiBhbmltID0+IGFuaW0uaW5kZXhPZignX19zdmVsdGUnKSA9PT0gLTEgLy8gcmVtb3ZlIGFsbCBTdmVsdGUgYW5pbWF0aW9uc1xuICAgICk7XG4gICAgY29uc3QgZGVsZXRlZCA9IHByZXZpb3VzLmxlbmd0aCAtIG5leHQubGVuZ3RoO1xuICAgIGlmIChkZWxldGVkKSB7XG4gICAgICAgIG5vZGUuc3R5bGUuYW5pbWF0aW9uID0gbmV4dC5qb2luKCcsICcpO1xuICAgICAgICBhY3RpdmUgLT0gZGVsZXRlZDtcbiAgICAgICAgaWYgKCFhY3RpdmUpXG4gICAgICAgICAgICBjbGVhcl9ydWxlcygpO1xuICAgIH1cbn1cbmZ1bmN0aW9uIGNsZWFyX3J1bGVzKCkge1xuICAgIHJhZigoKSA9PiB7XG4gICAgICAgIGlmIChhY3RpdmUpXG4gICAgICAgICAgICByZXR1cm47XG4gICAgICAgIG1hbmFnZWRfc3R5bGVzLmZvckVhY2goaW5mbyA9PiB7XG4gICAgICAgICAgICBjb25zdCB7IG93bmVyTm9kZSB9ID0gaW5mby5zdHlsZXNoZWV0O1xuICAgICAgICAgICAgLy8gdGhlcmUgaXMgbm8gb3duZXJOb2RlIGlmIGl0IHJ1bnMgb24ganNkb20uXG4gICAgICAgICAgICBpZiAob3duZXJOb2RlKVxuICAgICAgICAgICAgICAgIGRldGFjaChvd25lck5vZGUpO1xuICAgICAgICB9KTtcbiAgICAgICAgbWFuYWdlZF9zdHlsZXMuY2xlYXIoKTtcbiAgICB9KTtcbn1cblxuZnVuY3Rpb24gY3JlYXRlX2FuaW1hdGlvbihub2RlLCBmcm9tLCBmbiwgcGFyYW1zKSB7XG4gICAgaWYgKCFmcm9tKVxuICAgICAgICByZXR1cm4gbm9vcDtcbiAgICBjb25zdCB0byA9IG5vZGUuZ2V0Qm91bmRpbmdDbGllbnRSZWN0KCk7XG4gICAgaWYgKGZyb20ubGVmdCA9PT0gdG8ubGVmdCAmJiBmcm9tLnJpZ2h0ID09PSB0by5yaWdodCAmJiBmcm9tLnRvcCA9PT0gdG8udG9wICYmIGZyb20uYm90dG9tID09PSB0by5ib3R0b20pXG4gICAgICAgIHJldHVybiBub29wO1xuICAgIGNvbnN0IHsgZGVsYXkgPSAwLCBkdXJhdGlvbiA9IDMwMCwgZWFzaW5nID0gaWRlbnRpdHksIFxuICAgIC8vIEB0cy1pZ25vcmUgdG9kbzogc2hvdWxkIHRoaXMgYmUgc2VwYXJhdGVkIGZyb20gZGVzdHJ1Y3R1cmluZz8gT3Igc3RhcnQvZW5kIGFkZGVkIHRvIHB1YmxpYyBhcGkgYW5kIGRvY3VtZW50YXRpb24/XG4gICAgc3RhcnQ6IHN0YXJ0X3RpbWUgPSBub3coKSArIGRlbGF5LCBcbiAgICAvLyBAdHMtaWdub3JlIHRvZG86XG4gICAgZW5kID0gc3RhcnRfdGltZSArIGR1cmF0aW9uLCB0aWNrID0gbm9vcCwgY3NzIH0gPSBmbihub2RlLCB7IGZyb20sIHRvIH0sIHBhcmFtcyk7XG4gICAgbGV0IHJ1bm5pbmcgPSB0cnVlO1xuICAgIGxldCBzdGFydGVkID0gZmFsc2U7XG4gICAgbGV0IG5hbWU7XG4gICAgZnVuY3Rpb24gc3RhcnQoKSB7XG4gICAgICAgIGlmIChjc3MpIHtcbiAgICAgICAgICAgIG5hbWUgPSBjcmVhdGVfcnVsZShub2RlLCAwLCAxLCBkdXJhdGlvbiwgZGVsYXksIGVhc2luZywgY3NzKTtcbiAgICAgICAgfVxuICAgICAgICBpZiAoIWRlbGF5KSB7XG4gICAgICAgICAgICBzdGFydGVkID0gdHJ1ZTtcbiAgICAgICAgfVxuICAgIH1cbiAgICBmdW5jdGlvbiBzdG9wKCkge1xuICAgICAgICBpZiAoY3NzKVxuICAgICAgICAgICAgZGVsZXRlX3J1bGUobm9kZSwgbmFtZSk7XG4gICAgICAgIHJ1bm5pbmcgPSBmYWxzZTtcbiAgICB9XG4gICAgbG9vcChub3cgPT4ge1xuICAgICAgICBpZiAoIXN0YXJ0ZWQgJiYgbm93ID49IHN0YXJ0X3RpbWUpIHtcbiAgICAgICAgICAgIHN0YXJ0ZWQgPSB0cnVlO1xuICAgICAgICB9XG4gICAgICAgIGlmIChzdGFydGVkICYmIG5vdyA+PSBlbmQpIHtcbiAgICAgICAgICAgIHRpY2soMSwgMCk7XG4gICAgICAgICAgICBzdG9wKCk7XG4gICAgICAgIH1cbiAgICAgICAgaWYgKCFydW5uaW5nKSB7XG4gICAgICAgICAgICByZXR1cm4gZmFsc2U7XG4gICAgICAgIH1cbiAgICAgICAgaWYgKHN0YXJ0ZWQpIHtcbiAgICAgICAgICAgIGNvbnN0IHAgPSBub3cgLSBzdGFydF90aW1lO1xuICAgICAgICAgICAgY29uc3QgdCA9IDAgKyAxICogZWFzaW5nKHAgLyBkdXJhdGlvbik7XG4gICAgICAgICAgICB0aWNrKHQsIDEgLSB0KTtcbiAgICAgICAgfVxuICAgICAgICByZXR1cm4gdHJ1ZTtcbiAgICB9KTtcbiAgICBzdGFydCgpO1xuICAgIHRpY2soMCwgMSk7XG4gICAgcmV0dXJuIHN0b3A7XG59XG5mdW5jdGlvbiBmaXhfcG9zaXRpb24obm9kZSkge1xuICAgIGNvbnN0IHN0eWxlID0gZ2V0Q29tcHV0ZWRTdHlsZShub2RlKTtcbiAgICBpZiAoc3R5bGUucG9zaXRpb24gIT09ICdhYnNvbHV0ZScgJiYgc3R5bGUucG9zaXRpb24gIT09ICdmaXhlZCcpIHtcbiAgICAgICAgY29uc3QgeyB3aWR0aCwgaGVpZ2h0IH0gPSBzdHlsZTtcbiAgICAgICAgY29uc3QgYSA9IG5vZGUuZ2V0Qm91bmRpbmdDbGllbnRSZWN0KCk7XG4gICAgICAgIG5vZGUuc3R5bGUucG9zaXRpb24gPSAnYWJzb2x1dGUnO1xuICAgICAgICBub2RlLnN0eWxlLndpZHRoID0gd2lkdGg7XG4gICAgICAgIG5vZGUuc3R5bGUuaGVpZ2h0ID0gaGVpZ2h0O1xuICAgICAgICBhZGRfdHJhbnNmb3JtKG5vZGUsIGEpO1xuICAgIH1cbn1cbmZ1bmN0aW9uIGFkZF90cmFuc2Zvcm0obm9kZSwgYSkge1xuICAgIGNvbnN0IGIgPSBub2RlLmdldEJvdW5kaW5nQ2xpZW50UmVjdCgpO1xuICAgIGlmIChhLmxlZnQgIT09IGIubGVmdCB8fCBhLnRvcCAhPT0gYi50b3ApIHtcbiAgICAgICAgY29uc3Qgc3R5bGUgPSBnZXRDb21wdXRlZFN0eWxlKG5vZGUpO1xuICAgICAgICBjb25zdCB0cmFuc2Zvcm0gPSBzdHlsZS50cmFuc2Zvcm0gPT09ICdub25lJyA/ICcnIDogc3R5bGUudHJhbnNmb3JtO1xuICAgICAgICBub2RlLnN0eWxlLnRyYW5zZm9ybSA9IGAke3RyYW5zZm9ybX0gdHJhbnNsYXRlKCR7YS5sZWZ0IC0gYi5sZWZ0fXB4LCAke2EudG9wIC0gYi50b3B9cHgpYDtcbiAgICB9XG59XG5cbmxldCBjdXJyZW50X2NvbXBvbmVudDtcbmZ1bmN0aW9uIHNldF9jdXJyZW50X2NvbXBvbmVudChjb21wb25lbnQpIHtcbiAgICBjdXJyZW50X2NvbXBvbmVudCA9IGNvbXBvbmVudDtcbn1cbmZ1bmN0aW9uIGdldF9jdXJyZW50X2NvbXBvbmVudCgpIHtcbiAgICBpZiAoIWN1cnJlbnRfY29tcG9uZW50KVxuICAgICAgICB0aHJvdyBuZXcgRXJyb3IoJ0Z1bmN0aW9uIGNhbGxlZCBvdXRzaWRlIGNvbXBvbmVudCBpbml0aWFsaXphdGlvbicpO1xuICAgIHJldHVybiBjdXJyZW50X2NvbXBvbmVudDtcbn1cbi8qKlxuICogU2NoZWR1bGVzIGEgY2FsbGJhY2sgdG8gcnVuIGltbWVkaWF0ZWx5IGJlZm9yZSB0aGUgY29tcG9uZW50IGlzIHVwZGF0ZWQgYWZ0ZXIgYW55IHN0YXRlIGNoYW5nZS5cbiAqXG4gKiBUaGUgZmlyc3QgdGltZSB0aGUgY2FsbGJhY2sgcnVucyB3aWxsIGJlIGJlZm9yZSB0aGUgaW5pdGlhbCBgb25Nb3VudGBcbiAqXG4gKiBodHRwczovL3N2ZWx0ZS5kZXYvZG9jcyNydW4tdGltZS1zdmVsdGUtYmVmb3JldXBkYXRlXG4gKi9cbmZ1bmN0aW9uIGJlZm9yZVVwZGF0ZShmbikge1xuICAgIGdldF9jdXJyZW50X2NvbXBvbmVudCgpLiQkLmJlZm9yZV91cGRhdGUucHVzaChmbik7XG59XG4vKipcbiAqIFRoZSBgb25Nb3VudGAgZnVuY3Rpb24gc2NoZWR1bGVzIGEgY2FsbGJhY2sgdG8gcnVuIGFzIHNvb24gYXMgdGhlIGNvbXBvbmVudCBoYXMgYmVlbiBtb3VudGVkIHRvIHRoZSBET00uXG4gKiBJdCBtdXN0IGJlIGNhbGxlZCBkdXJpbmcgdGhlIGNvbXBvbmVudCdzIGluaXRpYWxpc2F0aW9uIChidXQgZG9lc24ndCBuZWVkIHRvIGxpdmUgKmluc2lkZSogdGhlIGNvbXBvbmVudDtcbiAqIGl0IGNhbiBiZSBjYWxsZWQgZnJvbSBhbiBleHRlcm5hbCBtb2R1bGUpLlxuICpcbiAqIGBvbk1vdW50YCBkb2VzIG5vdCBydW4gaW5zaWRlIGEgW3NlcnZlci1zaWRlIGNvbXBvbmVudF0oL2RvY3MjcnVuLXRpbWUtc2VydmVyLXNpZGUtY29tcG9uZW50LWFwaSkuXG4gKlxuICogaHR0cHM6Ly9zdmVsdGUuZGV2L2RvY3MjcnVuLXRpbWUtc3ZlbHRlLW9ubW91bnRcbiAqL1xuZnVuY3Rpb24gb25Nb3VudChmbikge1xuICAgIGdldF9jdXJyZW50X2NvbXBvbmVudCgpLiQkLm9uX21vdW50LnB1c2goZm4pO1xufVxuLyoqXG4gKiBTY2hlZHVsZXMgYSBjYWxsYmFjayB0byBydW4gaW1tZWRpYXRlbHkgYWZ0ZXIgdGhlIGNvbXBvbmVudCBoYXMgYmVlbiB1cGRhdGVkLlxuICpcbiAqIFRoZSBmaXJzdCB0aW1lIHRoZSBjYWxsYmFjayBydW5zIHdpbGwgYmUgYWZ0ZXIgdGhlIGluaXRpYWwgYG9uTW91bnRgXG4gKi9cbmZ1bmN0aW9uIGFmdGVyVXBkYXRlKGZuKSB7XG4gICAgZ2V0X2N1cnJlbnRfY29tcG9uZW50KCkuJCQuYWZ0ZXJfdXBkYXRlLnB1c2goZm4pO1xufVxuLyoqXG4gKiBTY2hlZHVsZXMgYSBjYWxsYmFjayB0byBydW4gaW1tZWRpYXRlbHkgYmVmb3JlIHRoZSBjb21wb25lbnQgaXMgdW5tb3VudGVkLlxuICpcbiAqIE91dCBvZiBgb25Nb3VudGAsIGBiZWZvcmVVcGRhdGVgLCBgYWZ0ZXJVcGRhdGVgIGFuZCBgb25EZXN0cm95YCwgdGhpcyBpcyB0aGVcbiAqIG9ubHkgb25lIHRoYXQgcnVucyBpbnNpZGUgYSBzZXJ2ZXItc2lkZSBjb21wb25lbnQuXG4gKlxuICogaHR0cHM6Ly9zdmVsdGUuZGV2L2RvY3MjcnVuLXRpbWUtc3ZlbHRlLW9uZGVzdHJveVxuICovXG5mdW5jdGlvbiBvbkRlc3Ryb3koZm4pIHtcbiAgICBnZXRfY3VycmVudF9jb21wb25lbnQoKS4kJC5vbl9kZXN0cm95LnB1c2goZm4pO1xufVxuLyoqXG4gKiBDcmVhdGVzIGFuIGV2ZW50IGRpc3BhdGNoZXIgdGhhdCBjYW4gYmUgdXNlZCB0byBkaXNwYXRjaCBbY29tcG9uZW50IGV2ZW50c10oL2RvY3MjdGVtcGxhdGUtc3ludGF4LWNvbXBvbmVudC1kaXJlY3RpdmVzLW9uLWV2ZW50bmFtZSkuXG4gKiBFdmVudCBkaXNwYXRjaGVycyBhcmUgZnVuY3Rpb25zIHRoYXQgY2FuIHRha2UgdHdvIGFyZ3VtZW50czogYG5hbWVgIGFuZCBgZGV0YWlsYC5cbiAqXG4gKiBDb21wb25lbnQgZXZlbnRzIGNyZWF0ZWQgd2l0aCBgY3JlYXRlRXZlbnREaXNwYXRjaGVyYCBjcmVhdGUgYVxuICogW0N1c3RvbUV2ZW50XShodHRwczovL2RldmVsb3Blci5tb3ppbGxhLm9yZy9lbi1VUy9kb2NzL1dlYi9BUEkvQ3VzdG9tRXZlbnQpLlxuICogVGhlc2UgZXZlbnRzIGRvIG5vdCBbYnViYmxlXShodHRwczovL2RldmVsb3Blci5tb3ppbGxhLm9yZy9lbi1VUy9kb2NzL0xlYXJuL0phdmFTY3JpcHQvQnVpbGRpbmdfYmxvY2tzL0V2ZW50cyNFdmVudF9idWJibGluZ19hbmRfY2FwdHVyZSkuXG4gKiBUaGUgYGRldGFpbGAgYXJndW1lbnQgY29ycmVzcG9uZHMgdG8gdGhlIFtDdXN0b21FdmVudC5kZXRhaWxdKGh0dHBzOi8vZGV2ZWxvcGVyLm1vemlsbGEub3JnL2VuLVVTL2RvY3MvV2ViL0FQSS9DdXN0b21FdmVudC9kZXRhaWwpXG4gKiBwcm9wZXJ0eSBhbmQgY2FuIGNvbnRhaW4gYW55IHR5cGUgb2YgZGF0YS5cbiAqXG4gKiBodHRwczovL3N2ZWx0ZS5kZXYvZG9jcyNydW4tdGltZS1zdmVsdGUtY3JlYXRlZXZlbnRkaXNwYXRjaGVyXG4gKi9cbmZ1bmN0aW9uIGNyZWF0ZUV2ZW50RGlzcGF0Y2hlcigpIHtcbiAgICBjb25zdCBjb21wb25lbnQgPSBnZXRfY3VycmVudF9jb21wb25lbnQoKTtcbiAgICByZXR1cm4gKHR5cGUsIGRldGFpbCwgeyBjYW5jZWxhYmxlID0gZmFsc2UgfSA9IHt9KSA9PiB7XG4gICAgICAgIGNvbnN0IGNhbGxiYWNrcyA9IGNvbXBvbmVudC4kJC5jYWxsYmFja3NbdHlwZV07XG4gICAgICAgIGlmIChjYWxsYmFja3MpIHtcbiAgICAgICAgICAgIC8vIFRPRE8gYXJlIHRoZXJlIHNpdHVhdGlvbnMgd2hlcmUgZXZlbnRzIGNvdWxkIGJlIGRpc3BhdGNoZWRcbiAgICAgICAgICAgIC8vIGluIGEgc2VydmVyIChub24tRE9NKSBlbnZpcm9ubWVudD9cbiAgICAgICAgICAgIGNvbnN0IGV2ZW50ID0gY3VzdG9tX2V2ZW50KHR5cGUsIGRldGFpbCwgeyBjYW5jZWxhYmxlIH0pO1xuICAgICAgICAgICAgY2FsbGJhY2tzLnNsaWNlKCkuZm9yRWFjaChmbiA9PiB7XG4gICAgICAgICAgICAgICAgZm4uY2FsbChjb21wb25lbnQsIGV2ZW50KTtcbiAgICAgICAgICAgIH0pO1xuICAgICAgICAgICAgcmV0dXJuICFldmVudC5kZWZhdWx0UHJldmVudGVkO1xuICAgICAgICB9XG4gICAgICAgIHJldHVybiB0cnVlO1xuICAgIH07XG59XG4vKipcbiAqIEFzc29jaWF0ZXMgYW4gYXJiaXRyYXJ5IGBjb250ZXh0YCBvYmplY3Qgd2l0aCB0aGUgY3VycmVudCBjb21wb25lbnQgYW5kIHRoZSBzcGVjaWZpZWQgYGtleWBcbiAqIGFuZCByZXR1cm5zIHRoYXQgb2JqZWN0LiBUaGUgY29udGV4dCBpcyB0aGVuIGF2YWlsYWJsZSB0byBjaGlsZHJlbiBvZiB0aGUgY29tcG9uZW50XG4gKiAoaW5jbHVkaW5nIHNsb3R0ZWQgY29udGVudCkgd2l0aCBgZ2V0Q29udGV4dGAuXG4gKlxuICogTGlrZSBsaWZlY3ljbGUgZnVuY3Rpb25zLCB0aGlzIG11c3QgYmUgY2FsbGVkIGR1cmluZyBjb21wb25lbnQgaW5pdGlhbGlzYXRpb24uXG4gKlxuICogaHR0cHM6Ly9zdmVsdGUuZGV2L2RvY3MjcnVuLXRpbWUtc3ZlbHRlLXNldGNvbnRleHRcbiAqL1xuZnVuY3Rpb24gc2V0Q29udGV4dChrZXksIGNvbnRleHQpIHtcbiAgICBnZXRfY3VycmVudF9jb21wb25lbnQoKS4kJC5jb250ZXh0LnNldChrZXksIGNvbnRleHQpO1xuICAgIHJldHVybiBjb250ZXh0O1xufVxuLyoqXG4gKiBSZXRyaWV2ZXMgdGhlIGNvbnRleHQgdGhhdCBiZWxvbmdzIHRvIHRoZSBjbG9zZXN0IHBhcmVudCBjb21wb25lbnQgd2l0aCB0aGUgc3BlY2lmaWVkIGBrZXlgLlxuICogTXVzdCBiZSBjYWxsZWQgZHVyaW5nIGNvbXBvbmVudCBpbml0aWFsaXNhdGlvbi5cbiAqXG4gKiBodHRwczovL3N2ZWx0ZS5kZXYvZG9jcyNydW4tdGltZS1zdmVsdGUtZ2V0Y29udGV4dFxuICovXG5mdW5jdGlvbiBnZXRDb250ZXh0KGtleSkge1xuICAgIHJldHVybiBnZXRfY3VycmVudF9jb21wb25lbnQoKS4kJC5jb250ZXh0LmdldChrZXkpO1xufVxuLyoqXG4gKiBSZXRyaWV2ZXMgdGhlIHdob2xlIGNvbnRleHQgbWFwIHRoYXQgYmVsb25ncyB0byB0aGUgY2xvc2VzdCBwYXJlbnQgY29tcG9uZW50LlxuICogTXVzdCBiZSBjYWxsZWQgZHVyaW5nIGNvbXBvbmVudCBpbml0aWFsaXNhdGlvbi4gVXNlZnVsLCBmb3IgZXhhbXBsZSwgaWYgeW91XG4gKiBwcm9ncmFtbWF0aWNhbGx5IGNyZWF0ZSBhIGNvbXBvbmVudCBhbmQgd2FudCB0byBwYXNzIHRoZSBleGlzdGluZyBjb250ZXh0IHRvIGl0LlxuICpcbiAqIGh0dHBzOi8vc3ZlbHRlLmRldi9kb2NzI3J1bi10aW1lLXN2ZWx0ZS1nZXRhbGxjb250ZXh0c1xuICovXG5mdW5jdGlvbiBnZXRBbGxDb250ZXh0cygpIHtcbiAgICByZXR1cm4gZ2V0X2N1cnJlbnRfY29tcG9uZW50KCkuJCQuY29udGV4dDtcbn1cbi8qKlxuICogQ2hlY2tzIHdoZXRoZXIgYSBnaXZlbiBga2V5YCBoYXMgYmVlbiBzZXQgaW4gdGhlIGNvbnRleHQgb2YgYSBwYXJlbnQgY29tcG9uZW50LlxuICogTXVzdCBiZSBjYWxsZWQgZHVyaW5nIGNvbXBvbmVudCBpbml0aWFsaXNhdGlvbi5cbiAqXG4gKiBodHRwczovL3N2ZWx0ZS5kZXYvZG9jcyNydW4tdGltZS1zdmVsdGUtaGFzY29udGV4dFxuICovXG5mdW5jdGlvbiBoYXNDb250ZXh0KGtleSkge1xuICAgIHJldHVybiBnZXRfY3VycmVudF9jb21wb25lbnQoKS4kJC5jb250ZXh0LmhhcyhrZXkpO1xufVxuLy8gVE9ETyBmaWd1cmUgb3V0IGlmIHdlIHN0aWxsIHdhbnQgdG8gc3VwcG9ydFxuLy8gc2hvcnRoYW5kIGV2ZW50cywgb3IgaWYgd2Ugd2FudCB0byBpbXBsZW1lbnRcbi8vIGEgcmVhbCBidWJibGluZyBtZWNoYW5pc21cbmZ1bmN0aW9uIGJ1YmJsZShjb21wb25lbnQsIGV2ZW50KSB7XG4gICAgY29uc3QgY2FsbGJhY2tzID0gY29tcG9uZW50LiQkLmNhbGxiYWNrc1tldmVudC50eXBlXTtcbiAgICBpZiAoY2FsbGJhY2tzKSB7XG4gICAgICAgIC8vIEB0cy1pZ25vcmVcbiAgICAgICAgY2FsbGJhY2tzLnNsaWNlKCkuZm9yRWFjaChmbiA9PiBmbi5jYWxsKHRoaXMsIGV2ZW50KSk7XG4gICAgfVxufVxuXG5jb25zdCBkaXJ0eV9jb21wb25lbnRzID0gW107XG5jb25zdCBpbnRyb3MgPSB7IGVuYWJsZWQ6IGZhbHNlIH07XG5jb25zdCBiaW5kaW5nX2NhbGxiYWNrcyA9IFtdO1xubGV0IHJlbmRlcl9jYWxsYmFja3MgPSBbXTtcbmNvbnN0IGZsdXNoX2NhbGxiYWNrcyA9IFtdO1xuY29uc3QgcmVzb2x2ZWRfcHJvbWlzZSA9IC8qIEBfX1BVUkVfXyAqLyBQcm9taXNlLnJlc29sdmUoKTtcbmxldCB1cGRhdGVfc2NoZWR1bGVkID0gZmFsc2U7XG5mdW5jdGlvbiBzY2hlZHVsZV91cGRhdGUoKSB7XG4gICAgaWYgKCF1cGRhdGVfc2NoZWR1bGVkKSB7XG4gICAgICAgIHVwZGF0ZV9zY2hlZHVsZWQgPSB0cnVlO1xuICAgICAgICByZXNvbHZlZF9wcm9taXNlLnRoZW4oZmx1c2gpO1xuICAgIH1cbn1cbmZ1bmN0aW9uIHRpY2soKSB7XG4gICAgc2NoZWR1bGVfdXBkYXRlKCk7XG4gICAgcmV0dXJuIHJlc29sdmVkX3Byb21pc2U7XG59XG5mdW5jdGlvbiBhZGRfcmVuZGVyX2NhbGxiYWNrKGZuKSB7XG4gICAgcmVuZGVyX2NhbGxiYWNrcy5wdXNoKGZuKTtcbn1cbmZ1bmN0aW9uIGFkZF9mbHVzaF9jYWxsYmFjayhmbikge1xuICAgIGZsdXNoX2NhbGxiYWNrcy5wdXNoKGZuKTtcbn1cbi8vIGZsdXNoKCkgY2FsbHMgY2FsbGJhY2tzIGluIHRoaXMgb3JkZXI6XG4vLyAxLiBBbGwgYmVmb3JlVXBkYXRlIGNhbGxiYWNrcywgaW4gb3JkZXI6IHBhcmVudHMgYmVmb3JlIGNoaWxkcmVuXG4vLyAyLiBBbGwgYmluZDp0aGlzIGNhbGxiYWNrcywgaW4gcmV2ZXJzZSBvcmRlcjogY2hpbGRyZW4gYmVmb3JlIHBhcmVudHMuXG4vLyAzLiBBbGwgYWZ0ZXJVcGRhdGUgY2FsbGJhY2tzLCBpbiBvcmRlcjogcGFyZW50cyBiZWZvcmUgY2hpbGRyZW4uIEVYQ0VQVFxuLy8gICAgZm9yIGFmdGVyVXBkYXRlcyBjYWxsZWQgZHVyaW5nIHRoZSBpbml0aWFsIG9uTW91bnQsIHdoaWNoIGFyZSBjYWxsZWQgaW5cbi8vICAgIHJldmVyc2Ugb3JkZXI6IGNoaWxkcmVuIGJlZm9yZSBwYXJlbnRzLlxuLy8gU2luY2UgY2FsbGJhY2tzIG1pZ2h0IHVwZGF0ZSBjb21wb25lbnQgdmFsdWVzLCB3aGljaCBjb3VsZCB0cmlnZ2VyIGFub3RoZXJcbi8vIGNhbGwgdG8gZmx1c2goKSwgdGhlIGZvbGxvd2luZyBzdGVwcyBndWFyZCBhZ2FpbnN0IHRoaXM6XG4vLyAxLiBEdXJpbmcgYmVmb3JlVXBkYXRlLCBhbnkgdXBkYXRlZCBjb21wb25lbnRzIHdpbGwgYmUgYWRkZWQgdG8gdGhlXG4vLyAgICBkaXJ0eV9jb21wb25lbnRzIGFycmF5IGFuZCB3aWxsIGNhdXNlIGEgcmVlbnRyYW50IGNhbGwgdG8gZmx1c2goKS4gQmVjYXVzZVxuLy8gICAgdGhlIGZsdXNoIGluZGV4IGlzIGtlcHQgb3V0c2lkZSB0aGUgZnVuY3Rpb24sIHRoZSByZWVudHJhbnQgY2FsbCB3aWxsIHBpY2tcbi8vICAgIHVwIHdoZXJlIHRoZSBlYXJsaWVyIGNhbGwgbGVmdCBvZmYgYW5kIGdvIHRocm91Z2ggYWxsIGRpcnR5IGNvbXBvbmVudHMuIFRoZVxuLy8gICAgY3VycmVudF9jb21wb25lbnQgdmFsdWUgaXMgc2F2ZWQgYW5kIHJlc3RvcmVkIHNvIHRoYXQgdGhlIHJlZW50cmFudCBjYWxsIHdpbGxcbi8vICAgIG5vdCBpbnRlcmZlcmUgd2l0aCB0aGUgXCJwYXJlbnRcIiBmbHVzaCgpIGNhbGwuXG4vLyAyLiBiaW5kOnRoaXMgY2FsbGJhY2tzIGNhbm5vdCB0cmlnZ2VyIG5ldyBmbHVzaCgpIGNhbGxzLlxuLy8gMy4gRHVyaW5nIGFmdGVyVXBkYXRlLCBhbnkgdXBkYXRlZCBjb21wb25lbnRzIHdpbGwgTk9UIGhhdmUgdGhlaXIgYWZ0ZXJVcGRhdGVcbi8vICAgIGNhbGxiYWNrIGNhbGxlZCBhIHNlY29uZCB0aW1lOyB0aGUgc2Vlbl9jYWxsYmFja3Mgc2V0LCBvdXRzaWRlIHRoZSBmbHVzaCgpXG4vLyAgICBmdW5jdGlvbiwgZ3VhcmFudGVlcyB0aGlzIGJlaGF2aW9yLlxuY29uc3Qgc2Vlbl9jYWxsYmFja3MgPSBuZXcgU2V0KCk7XG5sZXQgZmx1c2hpZHggPSAwOyAvLyBEbyAqbm90KiBtb3ZlIHRoaXMgaW5zaWRlIHRoZSBmbHVzaCgpIGZ1bmN0aW9uXG5mdW5jdGlvbiBmbHVzaCgpIHtcbiAgICAvLyBEbyBub3QgcmVlbnRlciBmbHVzaCB3aGlsZSBkaXJ0eSBjb21wb25lbnRzIGFyZSB1cGRhdGVkLCBhcyB0aGlzIGNhblxuICAgIC8vIHJlc3VsdCBpbiBhbiBpbmZpbml0ZSBsb29wLiBJbnN0ZWFkLCBsZXQgdGhlIGlubmVyIGZsdXNoIGhhbmRsZSBpdC5cbiAgICAvLyBSZWVudHJhbmN5IGlzIG9rIGFmdGVyd2FyZHMgZm9yIGJpbmRpbmdzIGV0Yy5cbiAgICBpZiAoZmx1c2hpZHggIT09IDApIHtcbiAgICAgICAgcmV0dXJuO1xuICAgIH1cbiAgICBjb25zdCBzYXZlZF9jb21wb25lbnQgPSBjdXJyZW50X2NvbXBvbmVudDtcbiAgICBkbyB7XG4gICAgICAgIC8vIGZpcnN0LCBjYWxsIGJlZm9yZVVwZGF0ZSBmdW5jdGlvbnNcbiAgICAgICAgLy8gYW5kIHVwZGF0ZSBjb21wb25lbnRzXG4gICAgICAgIHRyeSB7XG4gICAgICAgICAgICB3aGlsZSAoZmx1c2hpZHggPCBkaXJ0eV9jb21wb25lbnRzLmxlbmd0aCkge1xuICAgICAgICAgICAgICAgIGNvbnN0IGNvbXBvbmVudCA9IGRpcnR5X2NvbXBvbmVudHNbZmx1c2hpZHhdO1xuICAgICAgICAgICAgICAgIGZsdXNoaWR4Kys7XG4gICAgICAgICAgICAgICAgc2V0X2N1cnJlbnRfY29tcG9uZW50KGNvbXBvbmVudCk7XG4gICAgICAgICAgICAgICAgdXBkYXRlKGNvbXBvbmVudC4kJCk7XG4gICAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICAgICAgY2F0Y2ggKGUpIHtcbiAgICAgICAgICAgIC8vIHJlc2V0IGRpcnR5IHN0YXRlIHRvIG5vdCBlbmQgdXAgaW4gYSBkZWFkbG9ja2VkIHN0YXRlIGFuZCB0aGVuIHJldGhyb3dcbiAgICAgICAgICAgIGRpcnR5X2NvbXBvbmVudHMubGVuZ3RoID0gMDtcbiAgICAgICAgICAgIGZsdXNoaWR4ID0gMDtcbiAgICAgICAgICAgIHRocm93IGU7XG4gICAgICAgIH1cbiAgICAgICAgc2V0X2N1cnJlbnRfY29tcG9uZW50KG51bGwpO1xuICAgICAgICBkaXJ0eV9jb21wb25lbnRzLmxlbmd0aCA9IDA7XG4gICAgICAgIGZsdXNoaWR4ID0gMDtcbiAgICAgICAgd2hpbGUgKGJpbmRpbmdfY2FsbGJhY2tzLmxlbmd0aClcbiAgICAgICAgICAgIGJpbmRpbmdfY2FsbGJhY2tzLnBvcCgpKCk7XG4gICAgICAgIC8vIHRoZW4sIG9uY2UgY29tcG9uZW50cyBhcmUgdXBkYXRlZCwgY2FsbFxuICAgICAgICAvLyBhZnRlclVwZGF0ZSBmdW5jdGlvbnMuIFRoaXMgbWF5IGNhdXNlXG4gICAgICAgIC8vIHN1YnNlcXVlbnQgdXBkYXRlcy4uLlxuICAgICAgICBmb3IgKGxldCBpID0gMDsgaSA8IHJlbmRlcl9jYWxsYmFja3MubGVuZ3RoOyBpICs9IDEpIHtcbiAgICAgICAgICAgIGNvbnN0IGNhbGxiYWNrID0gcmVuZGVyX2NhbGxiYWNrc1tpXTtcbiAgICAgICAgICAgIGlmICghc2Vlbl9jYWxsYmFja3MuaGFzKGNhbGxiYWNrKSkge1xuICAgICAgICAgICAgICAgIC8vIC4uLnNvIGd1YXJkIGFnYWluc3QgaW5maW5pdGUgbG9vcHNcbiAgICAgICAgICAgICAgICBzZWVuX2NhbGxiYWNrcy5hZGQoY2FsbGJhY2spO1xuICAgICAgICAgICAgICAgIGNhbGxiYWNrKCk7XG4gICAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICAgICAgcmVuZGVyX2NhbGxiYWNrcy5sZW5ndGggPSAwO1xuICAgIH0gd2hpbGUgKGRpcnR5X2NvbXBvbmVudHMubGVuZ3RoKTtcbiAgICB3aGlsZSAoZmx1c2hfY2FsbGJhY2tzLmxlbmd0aCkge1xuICAgICAgICBmbHVzaF9jYWxsYmFja3MucG9wKCkoKTtcbiAgICB9XG4gICAgdXBkYXRlX3NjaGVkdWxlZCA9IGZhbHNlO1xuICAgIHNlZW5fY2FsbGJhY2tzLmNsZWFyKCk7XG4gICAgc2V0X2N1cnJlbnRfY29tcG9uZW50KHNhdmVkX2NvbXBvbmVudCk7XG59XG5mdW5jdGlvbiB1cGRhdGUoJCQpIHtcbiAgICBpZiAoJCQuZnJhZ21lbnQgIT09IG51bGwpIHtcbiAgICAgICAgJCQudXBkYXRlKCk7XG4gICAgICAgIHJ1bl9hbGwoJCQuYmVmb3JlX3VwZGF0ZSk7XG4gICAgICAgIGNvbnN0IGRpcnR5ID0gJCQuZGlydHk7XG4gICAgICAgICQkLmRpcnR5ID0gWy0xXTtcbiAgICAgICAgJCQuZnJhZ21lbnQgJiYgJCQuZnJhZ21lbnQucCgkJC5jdHgsIGRpcnR5KTtcbiAgICAgICAgJCQuYWZ0ZXJfdXBkYXRlLmZvckVhY2goYWRkX3JlbmRlcl9jYWxsYmFjayk7XG4gICAgfVxufVxuLyoqXG4gKiBVc2VmdWwgZm9yIGV4YW1wbGUgdG8gZXhlY3V0ZSByZW1haW5pbmcgYGFmdGVyVXBkYXRlYCBjYWxsYmFja3MgYmVmb3JlIGV4ZWN1dGluZyBgZGVzdHJveWAuXG4gKi9cbmZ1bmN0aW9uIGZsdXNoX3JlbmRlcl9jYWxsYmFja3MoZm5zKSB7XG4gICAgY29uc3QgZmlsdGVyZWQgPSBbXTtcbiAgICBjb25zdCB0YXJnZXRzID0gW107XG4gICAgcmVuZGVyX2NhbGxiYWNrcy5mb3JFYWNoKChjKSA9PiBmbnMuaW5kZXhPZihjKSA9PT0gLTEgPyBmaWx0ZXJlZC5wdXNoKGMpIDogdGFyZ2V0cy5wdXNoKGMpKTtcbiAgICB0YXJnZXRzLmZvckVhY2goKGMpID0+IGMoKSk7XG4gICAgcmVuZGVyX2NhbGxiYWNrcyA9IGZpbHRlcmVkO1xufVxuXG5sZXQgcHJvbWlzZTtcbmZ1bmN0aW9uIHdhaXQoKSB7XG4gICAgaWYgKCFwcm9taXNlKSB7XG4gICAgICAgIHByb21pc2UgPSBQcm9taXNlLnJlc29sdmUoKTtcbiAgICAgICAgcHJvbWlzZS50aGVuKCgpID0+IHtcbiAgICAgICAgICAgIHByb21pc2UgPSBudWxsO1xuICAgICAgICB9KTtcbiAgICB9XG4gICAgcmV0dXJuIHByb21pc2U7XG59XG5mdW5jdGlvbiBkaXNwYXRjaChub2RlLCBkaXJlY3Rpb24sIGtpbmQpIHtcbiAgICBub2RlLmRpc3BhdGNoRXZlbnQoY3VzdG9tX2V2ZW50KGAke2RpcmVjdGlvbiA/ICdpbnRybycgOiAnb3V0cm8nfSR7a2luZH1gKSk7XG59XG5jb25zdCBvdXRyb2luZyA9IG5ldyBTZXQoKTtcbmxldCBvdXRyb3M7XG5mdW5jdGlvbiBncm91cF9vdXRyb3MoKSB7XG4gICAgb3V0cm9zID0ge1xuICAgICAgICByOiAwLFxuICAgICAgICBjOiBbXSxcbiAgICAgICAgcDogb3V0cm9zIC8vIHBhcmVudCBncm91cFxuICAgIH07XG59XG5mdW5jdGlvbiBjaGVja19vdXRyb3MoKSB7XG4gICAgaWYgKCFvdXRyb3Mucikge1xuICAgICAgICBydW5fYWxsKG91dHJvcy5jKTtcbiAgICB9XG4gICAgb3V0cm9zID0gb3V0cm9zLnA7XG59XG5mdW5jdGlvbiB0cmFuc2l0aW9uX2luKGJsb2NrLCBsb2NhbCkge1xuICAgIGlmIChibG9jayAmJiBibG9jay5pKSB7XG4gICAgICAgIG91dHJvaW5nLmRlbGV0ZShibG9jayk7XG4gICAgICAgIGJsb2NrLmkobG9jYWwpO1xuICAgIH1cbn1cbmZ1bmN0aW9uIHRyYW5zaXRpb25fb3V0KGJsb2NrLCBsb2NhbCwgZGV0YWNoLCBjYWxsYmFjaykge1xuICAgIGlmIChibG9jayAmJiBibG9jay5vKSB7XG4gICAgICAgIGlmIChvdXRyb2luZy5oYXMoYmxvY2spKVxuICAgICAgICAgICAgcmV0dXJuO1xuICAgICAgICBvdXRyb2luZy5hZGQoYmxvY2spO1xuICAgICAgICBvdXRyb3MuYy5wdXNoKCgpID0+IHtcbiAgICAgICAgICAgIG91dHJvaW5nLmRlbGV0ZShibG9jayk7XG4gICAgICAgICAgICBpZiAoY2FsbGJhY2spIHtcbiAgICAgICAgICAgICAgICBpZiAoZGV0YWNoKVxuICAgICAgICAgICAgICAgICAgICBibG9jay5kKDEpO1xuICAgICAgICAgICAgICAgIGNhbGxiYWNrKCk7XG4gICAgICAgICAgICB9XG4gICAgICAgIH0pO1xuICAgICAgICBibG9jay5vKGxvY2FsKTtcbiAgICB9XG4gICAgZWxzZSBpZiAoY2FsbGJhY2spIHtcbiAgICAgICAgY2FsbGJhY2soKTtcbiAgICB9XG59XG5jb25zdCBudWxsX3RyYW5zaXRpb24gPSB7IGR1cmF0aW9uOiAwIH07XG5mdW5jdGlvbiBjcmVhdGVfaW5fdHJhbnNpdGlvbihub2RlLCBmbiwgcGFyYW1zKSB7XG4gICAgY29uc3Qgb3B0aW9ucyA9IHsgZGlyZWN0aW9uOiAnaW4nIH07XG4gICAgbGV0IGNvbmZpZyA9IGZuKG5vZGUsIHBhcmFtcywgb3B0aW9ucyk7XG4gICAgbGV0IHJ1bm5pbmcgPSBmYWxzZTtcbiAgICBsZXQgYW5pbWF0aW9uX25hbWU7XG4gICAgbGV0IHRhc2s7XG4gICAgbGV0IHVpZCA9IDA7XG4gICAgZnVuY3Rpb24gY2xlYW51cCgpIHtcbiAgICAgICAgaWYgKGFuaW1hdGlvbl9uYW1lKVxuICAgICAgICAgICAgZGVsZXRlX3J1bGUobm9kZSwgYW5pbWF0aW9uX25hbWUpO1xuICAgIH1cbiAgICBmdW5jdGlvbiBnbygpIHtcbiAgICAgICAgY29uc3QgeyBkZWxheSA9IDAsIGR1cmF0aW9uID0gMzAwLCBlYXNpbmcgPSBpZGVudGl0eSwgdGljayA9IG5vb3AsIGNzcyB9ID0gY29uZmlnIHx8IG51bGxfdHJhbnNpdGlvbjtcbiAgICAgICAgaWYgKGNzcylcbiAgICAgICAgICAgIGFuaW1hdGlvbl9uYW1lID0gY3JlYXRlX3J1bGUobm9kZSwgMCwgMSwgZHVyYXRpb24sIGRlbGF5LCBlYXNpbmcsIGNzcywgdWlkKyspO1xuICAgICAgICB0aWNrKDAsIDEpO1xuICAgICAgICBjb25zdCBzdGFydF90aW1lID0gbm93KCkgKyBkZWxheTtcbiAgICAgICAgY29uc3QgZW5kX3RpbWUgPSBzdGFydF90aW1lICsgZHVyYXRpb247XG4gICAgICAgIGlmICh0YXNrKVxuICAgICAgICAgICAgdGFzay5hYm9ydCgpO1xuICAgICAgICBydW5uaW5nID0gdHJ1ZTtcbiAgICAgICAgYWRkX3JlbmRlcl9jYWxsYmFjaygoKSA9PiBkaXNwYXRjaChub2RlLCB0cnVlLCAnc3RhcnQnKSk7XG4gICAgICAgIHRhc2sgPSBsb29wKG5vdyA9PiB7XG4gICAgICAgICAgICBpZiAocnVubmluZykge1xuICAgICAgICAgICAgICAgIGlmIChub3cgPj0gZW5kX3RpbWUpIHtcbiAgICAgICAgICAgICAgICAgICAgdGljaygxLCAwKTtcbiAgICAgICAgICAgICAgICAgICAgZGlzcGF0Y2gobm9kZSwgdHJ1ZSwgJ2VuZCcpO1xuICAgICAgICAgICAgICAgICAgICBjbGVhbnVwKCk7XG4gICAgICAgICAgICAgICAgICAgIHJldHVybiBydW5uaW5nID0gZmFsc2U7XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgIGlmIChub3cgPj0gc3RhcnRfdGltZSkge1xuICAgICAgICAgICAgICAgICAgICBjb25zdCB0ID0gZWFzaW5nKChub3cgLSBzdGFydF90aW1lKSAvIGR1cmF0aW9uKTtcbiAgICAgICAgICAgICAgICAgICAgdGljayh0LCAxIC0gdCk7XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgfVxuICAgICAgICAgICAgcmV0dXJuIHJ1bm5pbmc7XG4gICAgICAgIH0pO1xuICAgIH1cbiAgICBsZXQgc3RhcnRlZCA9IGZhbHNlO1xuICAgIHJldHVybiB7XG4gICAgICAgIHN0YXJ0KCkge1xuICAgICAgICAgICAgaWYgKHN0YXJ0ZWQpXG4gICAgICAgICAgICAgICAgcmV0dXJuO1xuICAgICAgICAgICAgc3RhcnRlZCA9IHRydWU7XG4gICAgICAgICAgICBkZWxldGVfcnVsZShub2RlKTtcbiAgICAgICAgICAgIGlmIChpc19mdW5jdGlvbihjb25maWcpKSB7XG4gICAgICAgICAgICAgICAgY29uZmlnID0gY29uZmlnKG9wdGlvbnMpO1xuICAgICAgICAgICAgICAgIHdhaXQoKS50aGVuKGdvKTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIGVsc2Uge1xuICAgICAgICAgICAgICAgIGdvKCk7XG4gICAgICAgICAgICB9XG4gICAgICAgIH0sXG4gICAgICAgIGludmFsaWRhdGUoKSB7XG4gICAgICAgICAgICBzdGFydGVkID0gZmFsc2U7XG4gICAgICAgIH0sXG4gICAgICAgIGVuZCgpIHtcbiAgICAgICAgICAgIGlmIChydW5uaW5nKSB7XG4gICAgICAgICAgICAgICAgY2xlYW51cCgpO1xuICAgICAgICAgICAgICAgIHJ1bm5pbmcgPSBmYWxzZTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgIH07XG59XG5mdW5jdGlvbiBjcmVhdGVfb3V0X3RyYW5zaXRpb24obm9kZSwgZm4sIHBhcmFtcykge1xuICAgIGNvbnN0IG9wdGlvbnMgPSB7IGRpcmVjdGlvbjogJ291dCcgfTtcbiAgICBsZXQgY29uZmlnID0gZm4obm9kZSwgcGFyYW1zLCBvcHRpb25zKTtcbiAgICBsZXQgcnVubmluZyA9IHRydWU7XG4gICAgbGV0IGFuaW1hdGlvbl9uYW1lO1xuICAgIGNvbnN0IGdyb3VwID0gb3V0cm9zO1xuICAgIGdyb3VwLnIgKz0gMTtcbiAgICBmdW5jdGlvbiBnbygpIHtcbiAgICAgICAgY29uc3QgeyBkZWxheSA9IDAsIGR1cmF0aW9uID0gMzAwLCBlYXNpbmcgPSBpZGVudGl0eSwgdGljayA9IG5vb3AsIGNzcyB9ID0gY29uZmlnIHx8IG51bGxfdHJhbnNpdGlvbjtcbiAgICAgICAgaWYgKGNzcylcbiAgICAgICAgICAgIGFuaW1hdGlvbl9uYW1lID0gY3JlYXRlX3J1bGUobm9kZSwgMSwgMCwgZHVyYXRpb24sIGRlbGF5LCBlYXNpbmcsIGNzcyk7XG4gICAgICAgIGNvbnN0IHN0YXJ0X3RpbWUgPSBub3coKSArIGRlbGF5O1xuICAgICAgICBjb25zdCBlbmRfdGltZSA9IHN0YXJ0X3RpbWUgKyBkdXJhdGlvbjtcbiAgICAgICAgYWRkX3JlbmRlcl9jYWxsYmFjaygoKSA9PiBkaXNwYXRjaChub2RlLCBmYWxzZSwgJ3N0YXJ0JykpO1xuICAgICAgICBsb29wKG5vdyA9PiB7XG4gICAgICAgICAgICBpZiAocnVubmluZykge1xuICAgICAgICAgICAgICAgIGlmIChub3cgPj0gZW5kX3RpbWUpIHtcbiAgICAgICAgICAgICAgICAgICAgdGljaygwLCAxKTtcbiAgICAgICAgICAgICAgICAgICAgZGlzcGF0Y2gobm9kZSwgZmFsc2UsICdlbmQnKTtcbiAgICAgICAgICAgICAgICAgICAgaWYgKCEtLWdyb3VwLnIpIHtcbiAgICAgICAgICAgICAgICAgICAgICAgIC8vIHRoaXMgd2lsbCByZXN1bHQgaW4gYGVuZCgpYCBiZWluZyBjYWxsZWQsXG4gICAgICAgICAgICAgICAgICAgICAgICAvLyBzbyB3ZSBkb24ndCBuZWVkIHRvIGNsZWFuIHVwIGhlcmVcbiAgICAgICAgICAgICAgICAgICAgICAgIHJ1bl9hbGwoZ3JvdXAuYyk7XG4gICAgICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICAgICAgcmV0dXJuIGZhbHNlO1xuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICBpZiAobm93ID49IHN0YXJ0X3RpbWUpIHtcbiAgICAgICAgICAgICAgICAgICAgY29uc3QgdCA9IGVhc2luZygobm93IC0gc3RhcnRfdGltZSkgLyBkdXJhdGlvbik7XG4gICAgICAgICAgICAgICAgICAgIHRpY2soMSAtIHQsIHQpO1xuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIHJldHVybiBydW5uaW5nO1xuICAgICAgICB9KTtcbiAgICB9XG4gICAgaWYgKGlzX2Z1bmN0aW9uKGNvbmZpZykpIHtcbiAgICAgICAgd2FpdCgpLnRoZW4oKCkgPT4ge1xuICAgICAgICAgICAgLy8gQHRzLWlnbm9yZVxuICAgICAgICAgICAgY29uZmlnID0gY29uZmlnKG9wdGlvbnMpO1xuICAgICAgICAgICAgZ28oKTtcbiAgICAgICAgfSk7XG4gICAgfVxuICAgIGVsc2Uge1xuICAgICAgICBnbygpO1xuICAgIH1cbiAgICByZXR1cm4ge1xuICAgICAgICBlbmQocmVzZXQpIHtcbiAgICAgICAgICAgIGlmIChyZXNldCAmJiBjb25maWcudGljaykge1xuICAgICAgICAgICAgICAgIGNvbmZpZy50aWNrKDEsIDApO1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgaWYgKHJ1bm5pbmcpIHtcbiAgICAgICAgICAgICAgICBpZiAoYW5pbWF0aW9uX25hbWUpXG4gICAgICAgICAgICAgICAgICAgIGRlbGV0ZV9ydWxlKG5vZGUsIGFuaW1hdGlvbl9uYW1lKTtcbiAgICAgICAgICAgICAgICBydW5uaW5nID0gZmFsc2U7XG4gICAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICB9O1xufVxuZnVuY3Rpb24gY3JlYXRlX2JpZGlyZWN0aW9uYWxfdHJhbnNpdGlvbihub2RlLCBmbiwgcGFyYW1zLCBpbnRybykge1xuICAgIGNvbnN0IG9wdGlvbnMgPSB7IGRpcmVjdGlvbjogJ2JvdGgnIH07XG4gICAgbGV0IGNvbmZpZyA9IGZuKG5vZGUsIHBhcmFtcywgb3B0aW9ucyk7XG4gICAgbGV0IHQgPSBpbnRybyA/IDAgOiAxO1xuICAgIGxldCBydW5uaW5nX3Byb2dyYW0gPSBudWxsO1xuICAgIGxldCBwZW5kaW5nX3Byb2dyYW0gPSBudWxsO1xuICAgIGxldCBhbmltYXRpb25fbmFtZSA9IG51bGw7XG4gICAgZnVuY3Rpb24gY2xlYXJfYW5pbWF0aW9uKCkge1xuICAgICAgICBpZiAoYW5pbWF0aW9uX25hbWUpXG4gICAgICAgICAgICBkZWxldGVfcnVsZShub2RlLCBhbmltYXRpb25fbmFtZSk7XG4gICAgfVxuICAgIGZ1bmN0aW9uIGluaXQocHJvZ3JhbSwgZHVyYXRpb24pIHtcbiAgICAgICAgY29uc3QgZCA9IChwcm9ncmFtLmIgLSB0KTtcbiAgICAgICAgZHVyYXRpb24gKj0gTWF0aC5hYnMoZCk7XG4gICAgICAgIHJldHVybiB7XG4gICAgICAgICAgICBhOiB0LFxuICAgICAgICAgICAgYjogcHJvZ3JhbS5iLFxuICAgICAgICAgICAgZCxcbiAgICAgICAgICAgIGR1cmF0aW9uLFxuICAgICAgICAgICAgc3RhcnQ6IHByb2dyYW0uc3RhcnQsXG4gICAgICAgICAgICBlbmQ6IHByb2dyYW0uc3RhcnQgKyBkdXJhdGlvbixcbiAgICAgICAgICAgIGdyb3VwOiBwcm9ncmFtLmdyb3VwXG4gICAgICAgIH07XG4gICAgfVxuICAgIGZ1bmN0aW9uIGdvKGIpIHtcbiAgICAgICAgY29uc3QgeyBkZWxheSA9IDAsIGR1cmF0aW9uID0gMzAwLCBlYXNpbmcgPSBpZGVudGl0eSwgdGljayA9IG5vb3AsIGNzcyB9ID0gY29uZmlnIHx8IG51bGxfdHJhbnNpdGlvbjtcbiAgICAgICAgY29uc3QgcHJvZ3JhbSA9IHtcbiAgICAgICAgICAgIHN0YXJ0OiBub3coKSArIGRlbGF5LFxuICAgICAgICAgICAgYlxuICAgICAgICB9O1xuICAgICAgICBpZiAoIWIpIHtcbiAgICAgICAgICAgIC8vIEB0cy1pZ25vcmUgdG9kbzogaW1wcm92ZSB0eXBpbmdzXG4gICAgICAgICAgICBwcm9ncmFtLmdyb3VwID0gb3V0cm9zO1xuICAgICAgICAgICAgb3V0cm9zLnIgKz0gMTtcbiAgICAgICAgfVxuICAgICAgICBpZiAocnVubmluZ19wcm9ncmFtIHx8IHBlbmRpbmdfcHJvZ3JhbSkge1xuICAgICAgICAgICAgcGVuZGluZ19wcm9ncmFtID0gcHJvZ3JhbTtcbiAgICAgICAgfVxuICAgICAgICBlbHNlIHtcbiAgICAgICAgICAgIC8vIGlmIHRoaXMgaXMgYW4gaW50cm8sIGFuZCB0aGVyZSdzIGEgZGVsYXksIHdlIG5lZWQgdG8gZG9cbiAgICAgICAgICAgIC8vIGFuIGluaXRpYWwgdGljayBhbmQvb3IgYXBwbHkgQ1NTIGFuaW1hdGlvbiBpbW1lZGlhdGVseVxuICAgICAgICAgICAgaWYgKGNzcykge1xuICAgICAgICAgICAgICAgIGNsZWFyX2FuaW1hdGlvbigpO1xuICAgICAgICAgICAgICAgIGFuaW1hdGlvbl9uYW1lID0gY3JlYXRlX3J1bGUobm9kZSwgdCwgYiwgZHVyYXRpb24sIGRlbGF5LCBlYXNpbmcsIGNzcyk7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICBpZiAoYilcbiAgICAgICAgICAgICAgICB0aWNrKDAsIDEpO1xuICAgICAgICAgICAgcnVubmluZ19wcm9ncmFtID0gaW5pdChwcm9ncmFtLCBkdXJhdGlvbik7XG4gICAgICAgICAgICBhZGRfcmVuZGVyX2NhbGxiYWNrKCgpID0+IGRpc3BhdGNoKG5vZGUsIGIsICdzdGFydCcpKTtcbiAgICAgICAgICAgIGxvb3Aobm93ID0+IHtcbiAgICAgICAgICAgICAgICBpZiAocGVuZGluZ19wcm9ncmFtICYmIG5vdyA+IHBlbmRpbmdfcHJvZ3JhbS5zdGFydCkge1xuICAgICAgICAgICAgICAgICAgICBydW5uaW5nX3Byb2dyYW0gPSBpbml0KHBlbmRpbmdfcHJvZ3JhbSwgZHVyYXRpb24pO1xuICAgICAgICAgICAgICAgICAgICBwZW5kaW5nX3Byb2dyYW0gPSBudWxsO1xuICAgICAgICAgICAgICAgICAgICBkaXNwYXRjaChub2RlLCBydW5uaW5nX3Byb2dyYW0uYiwgJ3N0YXJ0Jyk7XG4gICAgICAgICAgICAgICAgICAgIGlmIChjc3MpIHtcbiAgICAgICAgICAgICAgICAgICAgICAgIGNsZWFyX2FuaW1hdGlvbigpO1xuICAgICAgICAgICAgICAgICAgICAgICAgYW5pbWF0aW9uX25hbWUgPSBjcmVhdGVfcnVsZShub2RlLCB0LCBydW5uaW5nX3Byb2dyYW0uYiwgcnVubmluZ19wcm9ncmFtLmR1cmF0aW9uLCAwLCBlYXNpbmcsIGNvbmZpZy5jc3MpO1xuICAgICAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgIGlmIChydW5uaW5nX3Byb2dyYW0pIHtcbiAgICAgICAgICAgICAgICAgICAgaWYgKG5vdyA+PSBydW5uaW5nX3Byb2dyYW0uZW5kKSB7XG4gICAgICAgICAgICAgICAgICAgICAgICB0aWNrKHQgPSBydW5uaW5nX3Byb2dyYW0uYiwgMSAtIHQpO1xuICAgICAgICAgICAgICAgICAgICAgICAgZGlzcGF0Y2gobm9kZSwgcnVubmluZ19wcm9ncmFtLmIsICdlbmQnKTtcbiAgICAgICAgICAgICAgICAgICAgICAgIGlmICghcGVuZGluZ19wcm9ncmFtKSB7XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgLy8gd2UncmUgZG9uZVxuICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlmIChydW5uaW5nX3Byb2dyYW0uYikge1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAvLyBpbnRybyBcdTIwMTQgd2UgY2FuIHRpZHkgdXAgaW1tZWRpYXRlbHlcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY2xlYXJfYW5pbWF0aW9uKCk7XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgICAgICAgICAgICAgIGVsc2Uge1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAvLyBvdXRybyBcdTIwMTQgbmVlZHMgdG8gYmUgY29vcmRpbmF0ZWRcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaWYgKCEtLXJ1bm5pbmdfcHJvZ3JhbS5ncm91cC5yKVxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcnVuX2FsbChydW5uaW5nX3Byb2dyYW0uZ3JvdXAuYyk7XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgICAgICAgICAgcnVubmluZ19wcm9ncmFtID0gbnVsbDtcbiAgICAgICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgICAgICBlbHNlIGlmIChub3cgPj0gcnVubmluZ19wcm9ncmFtLnN0YXJ0KSB7XG4gICAgICAgICAgICAgICAgICAgICAgICBjb25zdCBwID0gbm93IC0gcnVubmluZ19wcm9ncmFtLnN0YXJ0O1xuICAgICAgICAgICAgICAgICAgICAgICAgdCA9IHJ1bm5pbmdfcHJvZ3JhbS5hICsgcnVubmluZ19wcm9ncmFtLmQgKiBlYXNpbmcocCAvIHJ1bm5pbmdfcHJvZ3JhbS5kdXJhdGlvbik7XG4gICAgICAgICAgICAgICAgICAgICAgICB0aWNrKHQsIDEgLSB0KTtcbiAgICAgICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICByZXR1cm4gISEocnVubmluZ19wcm9ncmFtIHx8IHBlbmRpbmdfcHJvZ3JhbSk7XG4gICAgICAgICAgICB9KTtcbiAgICAgICAgfVxuICAgIH1cbiAgICByZXR1cm4ge1xuICAgICAgICBydW4oYikge1xuICAgICAgICAgICAgaWYgKGlzX2Z1bmN0aW9uKGNvbmZpZykpIHtcbiAgICAgICAgICAgICAgICB3YWl0KCkudGhlbigoKSA9PiB7XG4gICAgICAgICAgICAgICAgICAgIC8vIEB0cy1pZ25vcmVcbiAgICAgICAgICAgICAgICAgICAgY29uZmlnID0gY29uZmlnKG9wdGlvbnMpO1xuICAgICAgICAgICAgICAgICAgICBnbyhiKTtcbiAgICAgICAgICAgICAgICB9KTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIGVsc2Uge1xuICAgICAgICAgICAgICAgIGdvKGIpO1xuICAgICAgICAgICAgfVxuICAgICAgICB9LFxuICAgICAgICBlbmQoKSB7XG4gICAgICAgICAgICBjbGVhcl9hbmltYXRpb24oKTtcbiAgICAgICAgICAgIHJ1bm5pbmdfcHJvZ3JhbSA9IHBlbmRpbmdfcHJvZ3JhbSA9IG51bGw7XG4gICAgICAgIH1cbiAgICB9O1xufVxuXG5mdW5jdGlvbiBoYW5kbGVfcHJvbWlzZShwcm9taXNlLCBpbmZvKSB7XG4gICAgY29uc3QgdG9rZW4gPSBpbmZvLnRva2VuID0ge307XG4gICAgZnVuY3Rpb24gdXBkYXRlKHR5cGUsIGluZGV4LCBrZXksIHZhbHVlKSB7XG4gICAgICAgIGlmIChpbmZvLnRva2VuICE9PSB0b2tlbilcbiAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgaW5mby5yZXNvbHZlZCA9IHZhbHVlO1xuICAgICAgICBsZXQgY2hpbGRfY3R4ID0gaW5mby5jdHg7XG4gICAgICAgIGlmIChrZXkgIT09IHVuZGVmaW5lZCkge1xuICAgICAgICAgICAgY2hpbGRfY3R4ID0gY2hpbGRfY3R4LnNsaWNlKCk7XG4gICAgICAgICAgICBjaGlsZF9jdHhba2V5XSA9IHZhbHVlO1xuICAgICAgICB9XG4gICAgICAgIGNvbnN0IGJsb2NrID0gdHlwZSAmJiAoaW5mby5jdXJyZW50ID0gdHlwZSkoY2hpbGRfY3R4KTtcbiAgICAgICAgbGV0IG5lZWRzX2ZsdXNoID0gZmFsc2U7XG4gICAgICAgIGlmIChpbmZvLmJsb2NrKSB7XG4gICAgICAgICAgICBpZiAoaW5mby5ibG9ja3MpIHtcbiAgICAgICAgICAgICAgICBpbmZvLmJsb2Nrcy5mb3JFYWNoKChibG9jaywgaSkgPT4ge1xuICAgICAgICAgICAgICAgICAgICBpZiAoaSAhPT0gaW5kZXggJiYgYmxvY2spIHtcbiAgICAgICAgICAgICAgICAgICAgICAgIGdyb3VwX291dHJvcygpO1xuICAgICAgICAgICAgICAgICAgICAgICAgdHJhbnNpdGlvbl9vdXQoYmxvY2ssIDEsIDEsICgpID0+IHtcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICBpZiAoaW5mby5ibG9ja3NbaV0gPT09IGJsb2NrKSB7XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGluZm8uYmxvY2tzW2ldID0gbnVsbDtcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgICAgICAgICB9KTtcbiAgICAgICAgICAgICAgICAgICAgICAgIGNoZWNrX291dHJvcygpO1xuICAgICAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgfSk7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICBlbHNlIHtcbiAgICAgICAgICAgICAgICBpbmZvLmJsb2NrLmQoMSk7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICBibG9jay5jKCk7XG4gICAgICAgICAgICB0cmFuc2l0aW9uX2luKGJsb2NrLCAxKTtcbiAgICAgICAgICAgIGJsb2NrLm0oaW5mby5tb3VudCgpLCBpbmZvLmFuY2hvcik7XG4gICAgICAgICAgICBuZWVkc19mbHVzaCA9IHRydWU7XG4gICAgICAgIH1cbiAgICAgICAgaW5mby5ibG9jayA9IGJsb2NrO1xuICAgICAgICBpZiAoaW5mby5ibG9ja3MpXG4gICAgICAgICAgICBpbmZvLmJsb2Nrc1tpbmRleF0gPSBibG9jaztcbiAgICAgICAgaWYgKG5lZWRzX2ZsdXNoKSB7XG4gICAgICAgICAgICBmbHVzaCgpO1xuICAgICAgICB9XG4gICAgfVxuICAgIGlmIChpc19wcm9taXNlKHByb21pc2UpKSB7XG4gICAgICAgIGNvbnN0IGN1cnJlbnRfY29tcG9uZW50ID0gZ2V0X2N1cnJlbnRfY29tcG9uZW50KCk7XG4gICAgICAgIHByb21pc2UudGhlbih2YWx1ZSA9PiB7XG4gICAgICAgICAgICBzZXRfY3VycmVudF9jb21wb25lbnQoY3VycmVudF9jb21wb25lbnQpO1xuICAgICAgICAgICAgdXBkYXRlKGluZm8udGhlbiwgMSwgaW5mby52YWx1ZSwgdmFsdWUpO1xuICAgICAgICAgICAgc2V0X2N1cnJlbnRfY29tcG9uZW50KG51bGwpO1xuICAgICAgICB9LCBlcnJvciA9PiB7XG4gICAgICAgICAgICBzZXRfY3VycmVudF9jb21wb25lbnQoY3VycmVudF9jb21wb25lbnQpO1xuICAgICAgICAgICAgdXBkYXRlKGluZm8uY2F0Y2gsIDIsIGluZm8uZXJyb3IsIGVycm9yKTtcbiAgICAgICAgICAgIHNldF9jdXJyZW50X2NvbXBvbmVudChudWxsKTtcbiAgICAgICAgICAgIGlmICghaW5mby5oYXNDYXRjaCkge1xuICAgICAgICAgICAgICAgIHRocm93IGVycm9yO1xuICAgICAgICAgICAgfVxuICAgICAgICB9KTtcbiAgICAgICAgLy8gaWYgd2UgcHJldmlvdXNseSBoYWQgYSB0aGVuL2NhdGNoIGJsb2NrLCBkZXN0cm95IGl0XG4gICAgICAgIGlmIChpbmZvLmN1cnJlbnQgIT09IGluZm8ucGVuZGluZykge1xuICAgICAgICAgICAgdXBkYXRlKGluZm8ucGVuZGluZywgMCk7XG4gICAgICAgICAgICByZXR1cm4gdHJ1ZTtcbiAgICAgICAgfVxuICAgIH1cbiAgICBlbHNlIHtcbiAgICAgICAgaWYgKGluZm8uY3VycmVudCAhPT0gaW5mby50aGVuKSB7XG4gICAgICAgICAgICB1cGRhdGUoaW5mby50aGVuLCAxLCBpbmZvLnZhbHVlLCBwcm9taXNlKTtcbiAgICAgICAgICAgIHJldHVybiB0cnVlO1xuICAgICAgICB9XG4gICAgICAgIGluZm8ucmVzb2x2ZWQgPSBwcm9taXNlO1xuICAgIH1cbn1cbmZ1bmN0aW9uIHVwZGF0ZV9hd2FpdF9ibG9ja19icmFuY2goaW5mbywgY3R4LCBkaXJ0eSkge1xuICAgIGNvbnN0IGNoaWxkX2N0eCA9IGN0eC5zbGljZSgpO1xuICAgIGNvbnN0IHsgcmVzb2x2ZWQgfSA9IGluZm87XG4gICAgaWYgKGluZm8uY3VycmVudCA9PT0gaW5mby50aGVuKSB7XG4gICAgICAgIGNoaWxkX2N0eFtpbmZvLnZhbHVlXSA9IHJlc29sdmVkO1xuICAgIH1cbiAgICBpZiAoaW5mby5jdXJyZW50ID09PSBpbmZvLmNhdGNoKSB7XG4gICAgICAgIGNoaWxkX2N0eFtpbmZvLmVycm9yXSA9IHJlc29sdmVkO1xuICAgIH1cbiAgICBpbmZvLmJsb2NrLnAoY2hpbGRfY3R4LCBkaXJ0eSk7XG59XG5cbmNvbnN0IGdsb2JhbHMgPSAodHlwZW9mIHdpbmRvdyAhPT0gJ3VuZGVmaW5lZCdcbiAgICA/IHdpbmRvd1xuICAgIDogdHlwZW9mIGdsb2JhbFRoaXMgIT09ICd1bmRlZmluZWQnXG4gICAgICAgID8gZ2xvYmFsVGhpc1xuICAgICAgICA6IGdsb2JhbCk7XG5cbmZ1bmN0aW9uIGRlc3Ryb3lfYmxvY2soYmxvY2ssIGxvb2t1cCkge1xuICAgIGJsb2NrLmQoMSk7XG4gICAgbG9va3VwLmRlbGV0ZShibG9jay5rZXkpO1xufVxuZnVuY3Rpb24gb3V0cm9fYW5kX2Rlc3Ryb3lfYmxvY2soYmxvY2ssIGxvb2t1cCkge1xuICAgIHRyYW5zaXRpb25fb3V0KGJsb2NrLCAxLCAxLCAoKSA9PiB7XG4gICAgICAgIGxvb2t1cC5kZWxldGUoYmxvY2sua2V5KTtcbiAgICB9KTtcbn1cbmZ1bmN0aW9uIGZpeF9hbmRfZGVzdHJveV9ibG9jayhibG9jaywgbG9va3VwKSB7XG4gICAgYmxvY2suZigpO1xuICAgIGRlc3Ryb3lfYmxvY2soYmxvY2ssIGxvb2t1cCk7XG59XG5mdW5jdGlvbiBmaXhfYW5kX291dHJvX2FuZF9kZXN0cm95X2Jsb2NrKGJsb2NrLCBsb29rdXApIHtcbiAgICBibG9jay5mKCk7XG4gICAgb3V0cm9fYW5kX2Rlc3Ryb3lfYmxvY2soYmxvY2ssIGxvb2t1cCk7XG59XG5mdW5jdGlvbiB1cGRhdGVfa2V5ZWRfZWFjaChvbGRfYmxvY2tzLCBkaXJ0eSwgZ2V0X2tleSwgZHluYW1pYywgY3R4LCBsaXN0LCBsb29rdXAsIG5vZGUsIGRlc3Ryb3ksIGNyZWF0ZV9lYWNoX2Jsb2NrLCBuZXh0LCBnZXRfY29udGV4dCkge1xuICAgIGxldCBvID0gb2xkX2Jsb2Nrcy5sZW5ndGg7XG4gICAgbGV0IG4gPSBsaXN0Lmxlbmd0aDtcbiAgICBsZXQgaSA9IG87XG4gICAgY29uc3Qgb2xkX2luZGV4ZXMgPSB7fTtcbiAgICB3aGlsZSAoaS0tKVxuICAgICAgICBvbGRfaW5kZXhlc1tvbGRfYmxvY2tzW2ldLmtleV0gPSBpO1xuICAgIGNvbnN0IG5ld19ibG9ja3MgPSBbXTtcbiAgICBjb25zdCBuZXdfbG9va3VwID0gbmV3IE1hcCgpO1xuICAgIGNvbnN0IGRlbHRhcyA9IG5ldyBNYXAoKTtcbiAgICBjb25zdCB1cGRhdGVzID0gW107XG4gICAgaSA9IG47XG4gICAgd2hpbGUgKGktLSkge1xuICAgICAgICBjb25zdCBjaGlsZF9jdHggPSBnZXRfY29udGV4dChjdHgsIGxpc3QsIGkpO1xuICAgICAgICBjb25zdCBrZXkgPSBnZXRfa2V5KGNoaWxkX2N0eCk7XG4gICAgICAgIGxldCBibG9jayA9IGxvb2t1cC5nZXQoa2V5KTtcbiAgICAgICAgaWYgKCFibG9jaykge1xuICAgICAgICAgICAgYmxvY2sgPSBjcmVhdGVfZWFjaF9ibG9jayhrZXksIGNoaWxkX2N0eCk7XG4gICAgICAgICAgICBibG9jay5jKCk7XG4gICAgICAgIH1cbiAgICAgICAgZWxzZSBpZiAoZHluYW1pYykge1xuICAgICAgICAgICAgLy8gZGVmZXIgdXBkYXRlcyB1bnRpbCBhbGwgdGhlIERPTSBzaHVmZmxpbmcgaXMgZG9uZVxuICAgICAgICAgICAgdXBkYXRlcy5wdXNoKCgpID0+IGJsb2NrLnAoY2hpbGRfY3R4LCBkaXJ0eSkpO1xuICAgICAgICB9XG4gICAgICAgIG5ld19sb29rdXAuc2V0KGtleSwgbmV3X2Jsb2Nrc1tpXSA9IGJsb2NrKTtcbiAgICAgICAgaWYgKGtleSBpbiBvbGRfaW5kZXhlcylcbiAgICAgICAgICAgIGRlbHRhcy5zZXQoa2V5LCBNYXRoLmFicyhpIC0gb2xkX2luZGV4ZXNba2V5XSkpO1xuICAgIH1cbiAgICBjb25zdCB3aWxsX21vdmUgPSBuZXcgU2V0KCk7XG4gICAgY29uc3QgZGlkX21vdmUgPSBuZXcgU2V0KCk7XG4gICAgZnVuY3Rpb24gaW5zZXJ0KGJsb2NrKSB7XG4gICAgICAgIHRyYW5zaXRpb25faW4oYmxvY2ssIDEpO1xuICAgICAgICBibG9jay5tKG5vZGUsIG5leHQpO1xuICAgICAgICBsb29rdXAuc2V0KGJsb2NrLmtleSwgYmxvY2spO1xuICAgICAgICBuZXh0ID0gYmxvY2suZmlyc3Q7XG4gICAgICAgIG4tLTtcbiAgICB9XG4gICAgd2hpbGUgKG8gJiYgbikge1xuICAgICAgICBjb25zdCBuZXdfYmxvY2sgPSBuZXdfYmxvY2tzW24gLSAxXTtcbiAgICAgICAgY29uc3Qgb2xkX2Jsb2NrID0gb2xkX2Jsb2Nrc1tvIC0gMV07XG4gICAgICAgIGNvbnN0IG5ld19rZXkgPSBuZXdfYmxvY2sua2V5O1xuICAgICAgICBjb25zdCBvbGRfa2V5ID0gb2xkX2Jsb2NrLmtleTtcbiAgICAgICAgaWYgKG5ld19ibG9jayA9PT0gb2xkX2Jsb2NrKSB7XG4gICAgICAgICAgICAvLyBkbyBub3RoaW5nXG4gICAgICAgICAgICBuZXh0ID0gbmV3X2Jsb2NrLmZpcnN0O1xuICAgICAgICAgICAgby0tO1xuICAgICAgICAgICAgbi0tO1xuICAgICAgICB9XG4gICAgICAgIGVsc2UgaWYgKCFuZXdfbG9va3VwLmhhcyhvbGRfa2V5KSkge1xuICAgICAgICAgICAgLy8gcmVtb3ZlIG9sZCBibG9ja1xuICAgICAgICAgICAgZGVzdHJveShvbGRfYmxvY2ssIGxvb2t1cCk7XG4gICAgICAgICAgICBvLS07XG4gICAgICAgIH1cbiAgICAgICAgZWxzZSBpZiAoIWxvb2t1cC5oYXMobmV3X2tleSkgfHwgd2lsbF9tb3ZlLmhhcyhuZXdfa2V5KSkge1xuICAgICAgICAgICAgaW5zZXJ0KG5ld19ibG9jayk7XG4gICAgICAgIH1cbiAgICAgICAgZWxzZSBpZiAoZGlkX21vdmUuaGFzKG9sZF9rZXkpKSB7XG4gICAgICAgICAgICBvLS07XG4gICAgICAgIH1cbiAgICAgICAgZWxzZSBpZiAoZGVsdGFzLmdldChuZXdfa2V5KSA+IGRlbHRhcy5nZXQob2xkX2tleSkpIHtcbiAgICAgICAgICAgIGRpZF9tb3ZlLmFkZChuZXdfa2V5KTtcbiAgICAgICAgICAgIGluc2VydChuZXdfYmxvY2spO1xuICAgICAgICB9XG4gICAgICAgIGVsc2Uge1xuICAgICAgICAgICAgd2lsbF9tb3ZlLmFkZChvbGRfa2V5KTtcbiAgICAgICAgICAgIG8tLTtcbiAgICAgICAgfVxuICAgIH1cbiAgICB3aGlsZSAoby0tKSB7XG4gICAgICAgIGNvbnN0IG9sZF9ibG9jayA9IG9sZF9ibG9ja3Nbb107XG4gICAgICAgIGlmICghbmV3X2xvb2t1cC5oYXMob2xkX2Jsb2NrLmtleSkpXG4gICAgICAgICAgICBkZXN0cm95KG9sZF9ibG9jaywgbG9va3VwKTtcbiAgICB9XG4gICAgd2hpbGUgKG4pXG4gICAgICAgIGluc2VydChuZXdfYmxvY2tzW24gLSAxXSk7XG4gICAgcnVuX2FsbCh1cGRhdGVzKTtcbiAgICByZXR1cm4gbmV3X2Jsb2Nrcztcbn1cbmZ1bmN0aW9uIHZhbGlkYXRlX2VhY2hfa2V5cyhjdHgsIGxpc3QsIGdldF9jb250ZXh0LCBnZXRfa2V5KSB7XG4gICAgY29uc3Qga2V5cyA9IG5ldyBTZXQoKTtcbiAgICBmb3IgKGxldCBpID0gMDsgaSA8IGxpc3QubGVuZ3RoOyBpKyspIHtcbiAgICAgICAgY29uc3Qga2V5ID0gZ2V0X2tleShnZXRfY29udGV4dChjdHgsIGxpc3QsIGkpKTtcbiAgICAgICAgaWYgKGtleXMuaGFzKGtleSkpIHtcbiAgICAgICAgICAgIHRocm93IG5ldyBFcnJvcignQ2Fubm90IGhhdmUgZHVwbGljYXRlIGtleXMgaW4gYSBrZXllZCBlYWNoJyk7XG4gICAgICAgIH1cbiAgICAgICAga2V5cy5hZGQoa2V5KTtcbiAgICB9XG59XG5cbmZ1bmN0aW9uIGdldF9zcHJlYWRfdXBkYXRlKGxldmVscywgdXBkYXRlcykge1xuICAgIGNvbnN0IHVwZGF0ZSA9IHt9O1xuICAgIGNvbnN0IHRvX251bGxfb3V0ID0ge307XG4gICAgY29uc3QgYWNjb3VudGVkX2ZvciA9IHsgJCRzY29wZTogMSB9O1xuICAgIGxldCBpID0gbGV2ZWxzLmxlbmd0aDtcbiAgICB3aGlsZSAoaS0tKSB7XG4gICAgICAgIGNvbnN0IG8gPSBsZXZlbHNbaV07XG4gICAgICAgIGNvbnN0IG4gPSB1cGRhdGVzW2ldO1xuICAgICAgICBpZiAobikge1xuICAgICAgICAgICAgZm9yIChjb25zdCBrZXkgaW4gbykge1xuICAgICAgICAgICAgICAgIGlmICghKGtleSBpbiBuKSlcbiAgICAgICAgICAgICAgICAgICAgdG9fbnVsbF9vdXRba2V5XSA9IDE7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICBmb3IgKGNvbnN0IGtleSBpbiBuKSB7XG4gICAgICAgICAgICAgICAgaWYgKCFhY2NvdW50ZWRfZm9yW2tleV0pIHtcbiAgICAgICAgICAgICAgICAgICAgdXBkYXRlW2tleV0gPSBuW2tleV07XG4gICAgICAgICAgICAgICAgICAgIGFjY291bnRlZF9mb3Jba2V5XSA9IDE7XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgfVxuICAgICAgICAgICAgbGV2ZWxzW2ldID0gbjtcbiAgICAgICAgfVxuICAgICAgICBlbHNlIHtcbiAgICAgICAgICAgIGZvciAoY29uc3Qga2V5IGluIG8pIHtcbiAgICAgICAgICAgICAgICBhY2NvdW50ZWRfZm9yW2tleV0gPSAxO1xuICAgICAgICAgICAgfVxuICAgICAgICB9XG4gICAgfVxuICAgIGZvciAoY29uc3Qga2V5IGluIHRvX251bGxfb3V0KSB7XG4gICAgICAgIGlmICghKGtleSBpbiB1cGRhdGUpKVxuICAgICAgICAgICAgdXBkYXRlW2tleV0gPSB1bmRlZmluZWQ7XG4gICAgfVxuICAgIHJldHVybiB1cGRhdGU7XG59XG5mdW5jdGlvbiBnZXRfc3ByZWFkX29iamVjdChzcHJlYWRfcHJvcHMpIHtcbiAgICByZXR1cm4gdHlwZW9mIHNwcmVhZF9wcm9wcyA9PT0gJ29iamVjdCcgJiYgc3ByZWFkX3Byb3BzICE9PSBudWxsID8gc3ByZWFkX3Byb3BzIDoge307XG59XG5cbmNvbnN0IF9ib29sZWFuX2F0dHJpYnV0ZXMgPSBbXG4gICAgJ2FsbG93ZnVsbHNjcmVlbicsXG4gICAgJ2FsbG93cGF5bWVudHJlcXVlc3QnLFxuICAgICdhc3luYycsXG4gICAgJ2F1dG9mb2N1cycsXG4gICAgJ2F1dG9wbGF5JyxcbiAgICAnY2hlY2tlZCcsXG4gICAgJ2NvbnRyb2xzJyxcbiAgICAnZGVmYXVsdCcsXG4gICAgJ2RlZmVyJyxcbiAgICAnZGlzYWJsZWQnLFxuICAgICdmb3Jtbm92YWxpZGF0ZScsXG4gICAgJ2hpZGRlbicsXG4gICAgJ2luZXJ0JyxcbiAgICAnaXNtYXAnLFxuICAgICdsb29wJyxcbiAgICAnbXVsdGlwbGUnLFxuICAgICdtdXRlZCcsXG4gICAgJ25vbW9kdWxlJyxcbiAgICAnbm92YWxpZGF0ZScsXG4gICAgJ29wZW4nLFxuICAgICdwbGF5c2lubGluZScsXG4gICAgJ3JlYWRvbmx5JyxcbiAgICAncmVxdWlyZWQnLFxuICAgICdyZXZlcnNlZCcsXG4gICAgJ3NlbGVjdGVkJ1xuXTtcbi8qKlxuICogTGlzdCBvZiBIVE1MIGJvb2xlYW4gYXR0cmlidXRlcyAoZS5nLiBgPGlucHV0IGRpc2FibGVkPmApLlxuICogU291cmNlOiBodHRwczovL2h0bWwuc3BlYy53aGF0d2cub3JnL211bHRpcGFnZS9pbmRpY2VzLmh0bWxcbiAqL1xuY29uc3QgYm9vbGVhbl9hdHRyaWJ1dGVzID0gbmV3IFNldChbLi4uX2Jvb2xlYW5fYXR0cmlidXRlc10pO1xuXG4vKiogcmVnZXggb2YgYWxsIGh0bWwgdm9pZCBlbGVtZW50IG5hbWVzICovXG5jb25zdCB2b2lkX2VsZW1lbnRfbmFtZXMgPSAvXig/OmFyZWF8YmFzZXxicnxjb2x8Y29tbWFuZHxlbWJlZHxocnxpbWd8aW5wdXR8a2V5Z2VufGxpbmt8bWV0YXxwYXJhbXxzb3VyY2V8dHJhY2t8d2JyKSQvO1xuZnVuY3Rpb24gaXNfdm9pZChuYW1lKSB7XG4gICAgcmV0dXJuIHZvaWRfZWxlbWVudF9uYW1lcy50ZXN0KG5hbWUpIHx8IG5hbWUudG9Mb3dlckNhc2UoKSA9PT0gJyFkb2N0eXBlJztcbn1cblxuY29uc3QgaW52YWxpZF9hdHRyaWJ1dGVfbmFtZV9jaGFyYWN0ZXIgPSAvW1xccydcIj4vPVxcdXtGREQwfS1cXHV7RkRFRn1cXHV7RkZGRX1cXHV7RkZGRn1cXHV7MUZGRkV9XFx1ezFGRkZGfVxcdXsyRkZGRX1cXHV7MkZGRkZ9XFx1ezNGRkZFfVxcdXszRkZGRn1cXHV7NEZGRkV9XFx1ezRGRkZGfVxcdXs1RkZGRX1cXHV7NUZGRkZ9XFx1ezZGRkZFfVxcdXs2RkZGRn1cXHV7N0ZGRkV9XFx1ezdGRkZGfVxcdXs4RkZGRX1cXHV7OEZGRkZ9XFx1ezlGRkZFfVxcdXs5RkZGRn1cXHV7QUZGRkV9XFx1e0FGRkZGfVxcdXtCRkZGRX1cXHV7QkZGRkZ9XFx1e0NGRkZFfVxcdXtDRkZGRn1cXHV7REZGRkV9XFx1e0RGRkZGfVxcdXtFRkZGRX1cXHV7RUZGRkZ9XFx1e0ZGRkZFfVxcdXtGRkZGRn1cXHV7MTBGRkZFfVxcdXsxMEZGRkZ9XS91O1xuLy8gaHR0cHM6Ly9odG1sLnNwZWMud2hhdHdnLm9yZy9tdWx0aXBhZ2Uvc3ludGF4Lmh0bWwjYXR0cmlidXRlcy0yXG4vLyBodHRwczovL2luZnJhLnNwZWMud2hhdHdnLm9yZy8jbm9uY2hhcmFjdGVyXG5mdW5jdGlvbiBzcHJlYWQoYXJncywgYXR0cnNfdG9fYWRkKSB7XG4gICAgY29uc3QgYXR0cmlidXRlcyA9IE9iamVjdC5hc3NpZ24oe30sIC4uLmFyZ3MpO1xuICAgIGlmIChhdHRyc190b19hZGQpIHtcbiAgICAgICAgY29uc3QgY2xhc3Nlc190b19hZGQgPSBhdHRyc190b19hZGQuY2xhc3NlcztcbiAgICAgICAgY29uc3Qgc3R5bGVzX3RvX2FkZCA9IGF0dHJzX3RvX2FkZC5zdHlsZXM7XG4gICAgICAgIGlmIChjbGFzc2VzX3RvX2FkZCkge1xuICAgICAgICAgICAgaWYgKGF0dHJpYnV0ZXMuY2xhc3MgPT0gbnVsbCkge1xuICAgICAgICAgICAgICAgIGF0dHJpYnV0ZXMuY2xhc3MgPSBjbGFzc2VzX3RvX2FkZDtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIGVsc2Uge1xuICAgICAgICAgICAgICAgIGF0dHJpYnV0ZXMuY2xhc3MgKz0gJyAnICsgY2xhc3Nlc190b19hZGQ7XG4gICAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICAgICAgaWYgKHN0eWxlc190b19hZGQpIHtcbiAgICAgICAgICAgIGlmIChhdHRyaWJ1dGVzLnN0eWxlID09IG51bGwpIHtcbiAgICAgICAgICAgICAgICBhdHRyaWJ1dGVzLnN0eWxlID0gc3R5bGVfb2JqZWN0X3RvX3N0cmluZyhzdHlsZXNfdG9fYWRkKTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIGVsc2Uge1xuICAgICAgICAgICAgICAgIGF0dHJpYnV0ZXMuc3R5bGUgPSBzdHlsZV9vYmplY3RfdG9fc3RyaW5nKG1lcmdlX3Nzcl9zdHlsZXMoYXR0cmlidXRlcy5zdHlsZSwgc3R5bGVzX3RvX2FkZCkpO1xuICAgICAgICAgICAgfVxuICAgICAgICB9XG4gICAgfVxuICAgIGxldCBzdHIgPSAnJztcbiAgICBPYmplY3Qua2V5cyhhdHRyaWJ1dGVzKS5mb3JFYWNoKG5hbWUgPT4ge1xuICAgICAgICBpZiAoaW52YWxpZF9hdHRyaWJ1dGVfbmFtZV9jaGFyYWN0ZXIudGVzdChuYW1lKSlcbiAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgY29uc3QgdmFsdWUgPSBhdHRyaWJ1dGVzW25hbWVdO1xuICAgICAgICBpZiAodmFsdWUgPT09IHRydWUpXG4gICAgICAgICAgICBzdHIgKz0gJyAnICsgbmFtZTtcbiAgICAgICAgZWxzZSBpZiAoYm9vbGVhbl9hdHRyaWJ1dGVzLmhhcyhuYW1lLnRvTG93ZXJDYXNlKCkpKSB7XG4gICAgICAgICAgICBpZiAodmFsdWUpXG4gICAgICAgICAgICAgICAgc3RyICs9ICcgJyArIG5hbWU7XG4gICAgICAgIH1cbiAgICAgICAgZWxzZSBpZiAodmFsdWUgIT0gbnVsbCkge1xuICAgICAgICAgICAgc3RyICs9IGAgJHtuYW1lfT1cIiR7dmFsdWV9XCJgO1xuICAgICAgICB9XG4gICAgfSk7XG4gICAgcmV0dXJuIHN0cjtcbn1cbmZ1bmN0aW9uIG1lcmdlX3Nzcl9zdHlsZXMoc3R5bGVfYXR0cmlidXRlLCBzdHlsZV9kaXJlY3RpdmUpIHtcbiAgICBjb25zdCBzdHlsZV9vYmplY3QgPSB7fTtcbiAgICBmb3IgKGNvbnN0IGluZGl2aWR1YWxfc3R5bGUgb2Ygc3R5bGVfYXR0cmlidXRlLnNwbGl0KCc7JykpIHtcbiAgICAgICAgY29uc3QgY29sb25faW5kZXggPSBpbmRpdmlkdWFsX3N0eWxlLmluZGV4T2YoJzonKTtcbiAgICAgICAgY29uc3QgbmFtZSA9IGluZGl2aWR1YWxfc3R5bGUuc2xpY2UoMCwgY29sb25faW5kZXgpLnRyaW0oKTtcbiAgICAgICAgY29uc3QgdmFsdWUgPSBpbmRpdmlkdWFsX3N0eWxlLnNsaWNlKGNvbG9uX2luZGV4ICsgMSkudHJpbSgpO1xuICAgICAgICBpZiAoIW5hbWUpXG4gICAgICAgICAgICBjb250aW51ZTtcbiAgICAgICAgc3R5bGVfb2JqZWN0W25hbWVdID0gdmFsdWU7XG4gICAgfVxuICAgIGZvciAoY29uc3QgbmFtZSBpbiBzdHlsZV9kaXJlY3RpdmUpIHtcbiAgICAgICAgY29uc3QgdmFsdWUgPSBzdHlsZV9kaXJlY3RpdmVbbmFtZV07XG4gICAgICAgIGlmICh2YWx1ZSkge1xuICAgICAgICAgICAgc3R5bGVfb2JqZWN0W25hbWVdID0gdmFsdWU7XG4gICAgICAgIH1cbiAgICAgICAgZWxzZSB7XG4gICAgICAgICAgICBkZWxldGUgc3R5bGVfb2JqZWN0W25hbWVdO1xuICAgICAgICB9XG4gICAgfVxuICAgIHJldHVybiBzdHlsZV9vYmplY3Q7XG59XG5jb25zdCBBVFRSX1JFR0VYID0gL1smXCJdL2c7XG5jb25zdCBDT05URU5UX1JFR0VYID0gL1smPF0vZztcbi8qKlxuICogTm90ZTogdGhpcyBtZXRob2QgaXMgcGVyZm9ybWFuY2Ugc2Vuc2l0aXZlIGFuZCBoYXMgYmVlbiBvcHRpbWl6ZWRcbiAqIGh0dHBzOi8vZ2l0aHViLmNvbS9zdmVsdGVqcy9zdmVsdGUvcHVsbC81NzAxXG4gKi9cbmZ1bmN0aW9uIGVzY2FwZSh2YWx1ZSwgaXNfYXR0ciA9IGZhbHNlKSB7XG4gICAgY29uc3Qgc3RyID0gU3RyaW5nKHZhbHVlKTtcbiAgICBjb25zdCBwYXR0ZXJuID0gaXNfYXR0ciA/IEFUVFJfUkVHRVggOiBDT05URU5UX1JFR0VYO1xuICAgIHBhdHRlcm4ubGFzdEluZGV4ID0gMDtcbiAgICBsZXQgZXNjYXBlZCA9ICcnO1xuICAgIGxldCBsYXN0ID0gMDtcbiAgICB3aGlsZSAocGF0dGVybi50ZXN0KHN0cikpIHtcbiAgICAgICAgY29uc3QgaSA9IHBhdHRlcm4ubGFzdEluZGV4IC0gMTtcbiAgICAgICAgY29uc3QgY2ggPSBzdHJbaV07XG4gICAgICAgIGVzY2FwZWQgKz0gc3RyLnN1YnN0cmluZyhsYXN0LCBpKSArIChjaCA9PT0gJyYnID8gJyZhbXA7JyA6IChjaCA9PT0gJ1wiJyA/ICcmcXVvdDsnIDogJyZsdDsnKSk7XG4gICAgICAgIGxhc3QgPSBpICsgMTtcbiAgICB9XG4gICAgcmV0dXJuIGVzY2FwZWQgKyBzdHIuc3Vic3RyaW5nKGxhc3QpO1xufVxuZnVuY3Rpb24gZXNjYXBlX2F0dHJpYnV0ZV92YWx1ZSh2YWx1ZSkge1xuICAgIC8vIGtlZXAgYm9vbGVhbnMsIG51bGwsIGFuZCB1bmRlZmluZWQgZm9yIHRoZSBzYWtlIG9mIGBzcHJlYWRgXG4gICAgY29uc3Qgc2hvdWxkX2VzY2FwZSA9IHR5cGVvZiB2YWx1ZSA9PT0gJ3N0cmluZycgfHwgKHZhbHVlICYmIHR5cGVvZiB2YWx1ZSA9PT0gJ29iamVjdCcpO1xuICAgIHJldHVybiBzaG91bGRfZXNjYXBlID8gZXNjYXBlKHZhbHVlLCB0cnVlKSA6IHZhbHVlO1xufVxuZnVuY3Rpb24gZXNjYXBlX29iamVjdChvYmopIHtcbiAgICBjb25zdCByZXN1bHQgPSB7fTtcbiAgICBmb3IgKGNvbnN0IGtleSBpbiBvYmopIHtcbiAgICAgICAgcmVzdWx0W2tleV0gPSBlc2NhcGVfYXR0cmlidXRlX3ZhbHVlKG9ialtrZXldKTtcbiAgICB9XG4gICAgcmV0dXJuIHJlc3VsdDtcbn1cbmZ1bmN0aW9uIGVhY2goaXRlbXMsIGZuKSB7XG4gICAgbGV0IHN0ciA9ICcnO1xuICAgIGZvciAobGV0IGkgPSAwOyBpIDwgaXRlbXMubGVuZ3RoOyBpICs9IDEpIHtcbiAgICAgICAgc3RyICs9IGZuKGl0ZW1zW2ldLCBpKTtcbiAgICB9XG4gICAgcmV0dXJuIHN0cjtcbn1cbmNvbnN0IG1pc3NpbmdfY29tcG9uZW50ID0ge1xuICAgICQkcmVuZGVyOiAoKSA9PiAnJ1xufTtcbmZ1bmN0aW9uIHZhbGlkYXRlX2NvbXBvbmVudChjb21wb25lbnQsIG5hbWUpIHtcbiAgICBpZiAoIWNvbXBvbmVudCB8fCAhY29tcG9uZW50LiQkcmVuZGVyKSB7XG4gICAgICAgIGlmIChuYW1lID09PSAnc3ZlbHRlOmNvbXBvbmVudCcpXG4gICAgICAgICAgICBuYW1lICs9ICcgdGhpcz17Li4ufSc7XG4gICAgICAgIHRocm93IG5ldyBFcnJvcihgPCR7bmFtZX0+IGlzIG5vdCBhIHZhbGlkIFNTUiBjb21wb25lbnQuIFlvdSBtYXkgbmVlZCB0byByZXZpZXcgeW91ciBidWlsZCBjb25maWcgdG8gZW5zdXJlIHRoYXQgZGVwZW5kZW5jaWVzIGFyZSBjb21waWxlZCwgcmF0aGVyIHRoYW4gaW1wb3J0ZWQgYXMgcHJlLWNvbXBpbGVkIG1vZHVsZXMuIE90aGVyd2lzZSB5b3UgbWF5IG5lZWQgdG8gZml4IGEgPCR7bmFtZX0+LmApO1xuICAgIH1cbiAgICByZXR1cm4gY29tcG9uZW50O1xufVxuZnVuY3Rpb24gZGVidWcoZmlsZSwgbGluZSwgY29sdW1uLCB2YWx1ZXMpIHtcbiAgICBjb25zb2xlLmxvZyhge0BkZWJ1Z30gJHtmaWxlID8gZmlsZSArICcgJyA6ICcnfSgke2xpbmV9OiR7Y29sdW1ufSlgKTsgLy8gZXNsaW50LWRpc2FibGUtbGluZSBuby1jb25zb2xlXG4gICAgY29uc29sZS5sb2codmFsdWVzKTsgLy8gZXNsaW50LWRpc2FibGUtbGluZSBuby1jb25zb2xlXG4gICAgcmV0dXJuICcnO1xufVxubGV0IG9uX2Rlc3Ryb3k7XG5mdW5jdGlvbiBjcmVhdGVfc3NyX2NvbXBvbmVudChmbikge1xuICAgIGZ1bmN0aW9uICQkcmVuZGVyKHJlc3VsdCwgcHJvcHMsIGJpbmRpbmdzLCBzbG90cywgY29udGV4dCkge1xuICAgICAgICBjb25zdCBwYXJlbnRfY29tcG9uZW50ID0gY3VycmVudF9jb21wb25lbnQ7XG4gICAgICAgIGNvbnN0ICQkID0ge1xuICAgICAgICAgICAgb25fZGVzdHJveSxcbiAgICAgICAgICAgIGNvbnRleHQ6IG5ldyBNYXAoY29udGV4dCB8fCAocGFyZW50X2NvbXBvbmVudCA/IHBhcmVudF9jb21wb25lbnQuJCQuY29udGV4dCA6IFtdKSksXG4gICAgICAgICAgICAvLyB0aGVzZSB3aWxsIGJlIGltbWVkaWF0ZWx5IGRpc2NhcmRlZFxuICAgICAgICAgICAgb25fbW91bnQ6IFtdLFxuICAgICAgICAgICAgYmVmb3JlX3VwZGF0ZTogW10sXG4gICAgICAgICAgICBhZnRlcl91cGRhdGU6IFtdLFxuICAgICAgICAgICAgY2FsbGJhY2tzOiBibGFua19vYmplY3QoKVxuICAgICAgICB9O1xuICAgICAgICBzZXRfY3VycmVudF9jb21wb25lbnQoeyAkJCB9KTtcbiAgICAgICAgY29uc3QgaHRtbCA9IGZuKHJlc3VsdCwgcHJvcHMsIGJpbmRpbmdzLCBzbG90cyk7XG4gICAgICAgIHNldF9jdXJyZW50X2NvbXBvbmVudChwYXJlbnRfY29tcG9uZW50KTtcbiAgICAgICAgcmV0dXJuIGh0bWw7XG4gICAgfVxuICAgIHJldHVybiB7XG4gICAgICAgIHJlbmRlcjogKHByb3BzID0ge30sIHsgJCRzbG90cyA9IHt9LCBjb250ZXh0ID0gbmV3IE1hcCgpIH0gPSB7fSkgPT4ge1xuICAgICAgICAgICAgb25fZGVzdHJveSA9IFtdO1xuICAgICAgICAgICAgY29uc3QgcmVzdWx0ID0geyB0aXRsZTogJycsIGhlYWQ6ICcnLCBjc3M6IG5ldyBTZXQoKSB9O1xuICAgICAgICAgICAgY29uc3QgaHRtbCA9ICQkcmVuZGVyKHJlc3VsdCwgcHJvcHMsIHt9LCAkJHNsb3RzLCBjb250ZXh0KTtcbiAgICAgICAgICAgIHJ1bl9hbGwob25fZGVzdHJveSk7XG4gICAgICAgICAgICByZXR1cm4ge1xuICAgICAgICAgICAgICAgIGh0bWwsXG4gICAgICAgICAgICAgICAgY3NzOiB7XG4gICAgICAgICAgICAgICAgICAgIGNvZGU6IEFycmF5LmZyb20ocmVzdWx0LmNzcykubWFwKGNzcyA9PiBjc3MuY29kZSkuam9pbignXFxuJyksXG4gICAgICAgICAgICAgICAgICAgIG1hcDogbnVsbCAvLyBUT0RPXG4gICAgICAgICAgICAgICAgfSxcbiAgICAgICAgICAgICAgICBoZWFkOiByZXN1bHQudGl0bGUgKyByZXN1bHQuaGVhZFxuICAgICAgICAgICAgfTtcbiAgICAgICAgfSxcbiAgICAgICAgJCRyZW5kZXJcbiAgICB9O1xufVxuZnVuY3Rpb24gYWRkX2F0dHJpYnV0ZShuYW1lLCB2YWx1ZSwgYm9vbGVhbikge1xuICAgIGlmICh2YWx1ZSA9PSBudWxsIHx8IChib29sZWFuICYmICF2YWx1ZSkpXG4gICAgICAgIHJldHVybiAnJztcbiAgICBjb25zdCBhc3NpZ25tZW50ID0gKGJvb2xlYW4gJiYgdmFsdWUgPT09IHRydWUpID8gJycgOiBgPVwiJHtlc2NhcGUodmFsdWUsIHRydWUpfVwiYDtcbiAgICByZXR1cm4gYCAke25hbWV9JHthc3NpZ25tZW50fWA7XG59XG5mdW5jdGlvbiBhZGRfY2xhc3NlcyhjbGFzc2VzKSB7XG4gICAgcmV0dXJuIGNsYXNzZXMgPyBgIGNsYXNzPVwiJHtjbGFzc2VzfVwiYCA6ICcnO1xufVxuZnVuY3Rpb24gc3R5bGVfb2JqZWN0X3RvX3N0cmluZyhzdHlsZV9vYmplY3QpIHtcbiAgICByZXR1cm4gT2JqZWN0LmtleXMoc3R5bGVfb2JqZWN0KVxuICAgICAgICAuZmlsdGVyKGtleSA9PiBzdHlsZV9vYmplY3Rba2V5XSlcbiAgICAgICAgLm1hcChrZXkgPT4gYCR7a2V5fTogJHtlc2NhcGVfYXR0cmlidXRlX3ZhbHVlKHN0eWxlX29iamVjdFtrZXldKX07YClcbiAgICAgICAgLmpvaW4oJyAnKTtcbn1cbmZ1bmN0aW9uIGFkZF9zdHlsZXMoc3R5bGVfb2JqZWN0KSB7XG4gICAgY29uc3Qgc3R5bGVzID0gc3R5bGVfb2JqZWN0X3RvX3N0cmluZyhzdHlsZV9vYmplY3QpO1xuICAgIHJldHVybiBzdHlsZXMgPyBgIHN0eWxlPVwiJHtzdHlsZXN9XCJgIDogJyc7XG59XG5cbmZ1bmN0aW9uIGJpbmQoY29tcG9uZW50LCBuYW1lLCBjYWxsYmFjaykge1xuICAgIGNvbnN0IGluZGV4ID0gY29tcG9uZW50LiQkLnByb3BzW25hbWVdO1xuICAgIGlmIChpbmRleCAhPT0gdW5kZWZpbmVkKSB7XG4gICAgICAgIGNvbXBvbmVudC4kJC5ib3VuZFtpbmRleF0gPSBjYWxsYmFjaztcbiAgICAgICAgY2FsbGJhY2soY29tcG9uZW50LiQkLmN0eFtpbmRleF0pO1xuICAgIH1cbn1cbmZ1bmN0aW9uIGNyZWF0ZV9jb21wb25lbnQoYmxvY2spIHtcbiAgICBibG9jayAmJiBibG9jay5jKCk7XG59XG5mdW5jdGlvbiBjbGFpbV9jb21wb25lbnQoYmxvY2ssIHBhcmVudF9ub2Rlcykge1xuICAgIGJsb2NrICYmIGJsb2NrLmwocGFyZW50X25vZGVzKTtcbn1cbmZ1bmN0aW9uIG1vdW50X2NvbXBvbmVudChjb21wb25lbnQsIHRhcmdldCwgYW5jaG9yLCBjdXN0b21FbGVtZW50KSB7XG4gICAgY29uc3QgeyBmcmFnbWVudCwgYWZ0ZXJfdXBkYXRlIH0gPSBjb21wb25lbnQuJCQ7XG4gICAgZnJhZ21lbnQgJiYgZnJhZ21lbnQubSh0YXJnZXQsIGFuY2hvcik7XG4gICAgaWYgKCFjdXN0b21FbGVtZW50KSB7XG4gICAgICAgIC8vIG9uTW91bnQgaGFwcGVucyBiZWZvcmUgdGhlIGluaXRpYWwgYWZ0ZXJVcGRhdGVcbiAgICAgICAgYWRkX3JlbmRlcl9jYWxsYmFjaygoKSA9PiB7XG4gICAgICAgICAgICBjb25zdCBuZXdfb25fZGVzdHJveSA9IGNvbXBvbmVudC4kJC5vbl9tb3VudC5tYXAocnVuKS5maWx0ZXIoaXNfZnVuY3Rpb24pO1xuICAgICAgICAgICAgLy8gaWYgdGhlIGNvbXBvbmVudCB3YXMgZGVzdHJveWVkIGltbWVkaWF0ZWx5XG4gICAgICAgICAgICAvLyBpdCB3aWxsIHVwZGF0ZSB0aGUgYCQkLm9uX2Rlc3Ryb3lgIHJlZmVyZW5jZSB0byBgbnVsbGAuXG4gICAgICAgICAgICAvLyB0aGUgZGVzdHJ1Y3R1cmVkIG9uX2Rlc3Ryb3kgbWF5IHN0aWxsIHJlZmVyZW5jZSB0byB0aGUgb2xkIGFycmF5XG4gICAgICAgICAgICBpZiAoY29tcG9uZW50LiQkLm9uX2Rlc3Ryb3kpIHtcbiAgICAgICAgICAgICAgICBjb21wb25lbnQuJCQub25fZGVzdHJveS5wdXNoKC4uLm5ld19vbl9kZXN0cm95KTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIGVsc2Uge1xuICAgICAgICAgICAgICAgIC8vIEVkZ2UgY2FzZSAtIGNvbXBvbmVudCB3YXMgZGVzdHJveWVkIGltbWVkaWF0ZWx5LFxuICAgICAgICAgICAgICAgIC8vIG1vc3QgbGlrZWx5IGFzIGEgcmVzdWx0IG9mIGEgYmluZGluZyBpbml0aWFsaXNpbmdcbiAgICAgICAgICAgICAgICBydW5fYWxsKG5ld19vbl9kZXN0cm95KTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIGNvbXBvbmVudC4kJC5vbl9tb3VudCA9IFtdO1xuICAgICAgICB9KTtcbiAgICB9XG4gICAgYWZ0ZXJfdXBkYXRlLmZvckVhY2goYWRkX3JlbmRlcl9jYWxsYmFjayk7XG59XG5mdW5jdGlvbiBkZXN0cm95X2NvbXBvbmVudChjb21wb25lbnQsIGRldGFjaGluZykge1xuICAgIGNvbnN0ICQkID0gY29tcG9uZW50LiQkO1xuICAgIGlmICgkJC5mcmFnbWVudCAhPT0gbnVsbCkge1xuICAgICAgICBmbHVzaF9yZW5kZXJfY2FsbGJhY2tzKCQkLmFmdGVyX3VwZGF0ZSk7XG4gICAgICAgIHJ1bl9hbGwoJCQub25fZGVzdHJveSk7XG4gICAgICAgICQkLmZyYWdtZW50ICYmICQkLmZyYWdtZW50LmQoZGV0YWNoaW5nKTtcbiAgICAgICAgLy8gVE9ETyBudWxsIG91dCBvdGhlciByZWZzLCBpbmNsdWRpbmcgY29tcG9uZW50LiQkIChidXQgbmVlZCB0b1xuICAgICAgICAvLyBwcmVzZXJ2ZSBmaW5hbCBzdGF0ZT8pXG4gICAgICAgICQkLm9uX2Rlc3Ryb3kgPSAkJC5mcmFnbWVudCA9IG51bGw7XG4gICAgICAgICQkLmN0eCA9IFtdO1xuICAgIH1cbn1cbmZ1bmN0aW9uIG1ha2VfZGlydHkoY29tcG9uZW50LCBpKSB7XG4gICAgaWYgKGNvbXBvbmVudC4kJC5kaXJ0eVswXSA9PT0gLTEpIHtcbiAgICAgICAgZGlydHlfY29tcG9uZW50cy5wdXNoKGNvbXBvbmVudCk7XG4gICAgICAgIHNjaGVkdWxlX3VwZGF0ZSgpO1xuICAgICAgICBjb21wb25lbnQuJCQuZGlydHkuZmlsbCgwKTtcbiAgICB9XG4gICAgY29tcG9uZW50LiQkLmRpcnR5WyhpIC8gMzEpIHwgMF0gfD0gKDEgPDwgKGkgJSAzMSkpO1xufVxuZnVuY3Rpb24gaW5pdChjb21wb25lbnQsIG9wdGlvbnMsIGluc3RhbmNlLCBjcmVhdGVfZnJhZ21lbnQsIG5vdF9lcXVhbCwgcHJvcHMsIGFwcGVuZF9zdHlsZXMsIGRpcnR5ID0gWy0xXSkge1xuICAgIGNvbnN0IHBhcmVudF9jb21wb25lbnQgPSBjdXJyZW50X2NvbXBvbmVudDtcbiAgICBzZXRfY3VycmVudF9jb21wb25lbnQoY29tcG9uZW50KTtcbiAgICBjb25zdCAkJCA9IGNvbXBvbmVudC4kJCA9IHtcbiAgICAgICAgZnJhZ21lbnQ6IG51bGwsXG4gICAgICAgIGN0eDogW10sXG4gICAgICAgIC8vIHN0YXRlXG4gICAgICAgIHByb3BzLFxuICAgICAgICB1cGRhdGU6IG5vb3AsXG4gICAgICAgIG5vdF9lcXVhbCxcbiAgICAgICAgYm91bmQ6IGJsYW5rX29iamVjdCgpLFxuICAgICAgICAvLyBsaWZlY3ljbGVcbiAgICAgICAgb25fbW91bnQ6IFtdLFxuICAgICAgICBvbl9kZXN0cm95OiBbXSxcbiAgICAgICAgb25fZGlzY29ubmVjdDogW10sXG4gICAgICAgIGJlZm9yZV91cGRhdGU6IFtdLFxuICAgICAgICBhZnRlcl91cGRhdGU6IFtdLFxuICAgICAgICBjb250ZXh0OiBuZXcgTWFwKG9wdGlvbnMuY29udGV4dCB8fCAocGFyZW50X2NvbXBvbmVudCA/IHBhcmVudF9jb21wb25lbnQuJCQuY29udGV4dCA6IFtdKSksXG4gICAgICAgIC8vIGV2ZXJ5dGhpbmcgZWxzZVxuICAgICAgICBjYWxsYmFja3M6IGJsYW5rX29iamVjdCgpLFxuICAgICAgICBkaXJ0eSxcbiAgICAgICAgc2tpcF9ib3VuZDogZmFsc2UsXG4gICAgICAgIHJvb3Q6IG9wdGlvbnMudGFyZ2V0IHx8IHBhcmVudF9jb21wb25lbnQuJCQucm9vdFxuICAgIH07XG4gICAgYXBwZW5kX3N0eWxlcyAmJiBhcHBlbmRfc3R5bGVzKCQkLnJvb3QpO1xuICAgIGxldCByZWFkeSA9IGZhbHNlO1xuICAgICQkLmN0eCA9IGluc3RhbmNlXG4gICAgICAgID8gaW5zdGFuY2UoY29tcG9uZW50LCBvcHRpb25zLnByb3BzIHx8IHt9LCAoaSwgcmV0LCAuLi5yZXN0KSA9PiB7XG4gICAgICAgICAgICBjb25zdCB2YWx1ZSA9IHJlc3QubGVuZ3RoID8gcmVzdFswXSA6IHJldDtcbiAgICAgICAgICAgIGlmICgkJC5jdHggJiYgbm90X2VxdWFsKCQkLmN0eFtpXSwgJCQuY3R4W2ldID0gdmFsdWUpKSB7XG4gICAgICAgICAgICAgICAgaWYgKCEkJC5za2lwX2JvdW5kICYmICQkLmJvdW5kW2ldKVxuICAgICAgICAgICAgICAgICAgICAkJC5ib3VuZFtpXSh2YWx1ZSk7XG4gICAgICAgICAgICAgICAgaWYgKHJlYWR5KVxuICAgICAgICAgICAgICAgICAgICBtYWtlX2RpcnR5KGNvbXBvbmVudCwgaSk7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICByZXR1cm4gcmV0O1xuICAgICAgICB9KVxuICAgICAgICA6IFtdO1xuICAgICQkLnVwZGF0ZSgpO1xuICAgIHJlYWR5ID0gdHJ1ZTtcbiAgICBydW5fYWxsKCQkLmJlZm9yZV91cGRhdGUpO1xuICAgIC8vIGBmYWxzZWAgYXMgYSBzcGVjaWFsIGNhc2Ugb2Ygbm8gRE9NIGNvbXBvbmVudFxuICAgICQkLmZyYWdtZW50ID0gY3JlYXRlX2ZyYWdtZW50ID8gY3JlYXRlX2ZyYWdtZW50KCQkLmN0eCkgOiBmYWxzZTtcbiAgICBpZiAob3B0aW9ucy50YXJnZXQpIHtcbiAgICAgICAgaWYgKG9wdGlvbnMuaHlkcmF0ZSkge1xuICAgICAgICAgICAgc3RhcnRfaHlkcmF0aW5nKCk7XG4gICAgICAgICAgICBjb25zdCBub2RlcyA9IGNoaWxkcmVuKG9wdGlvbnMudGFyZ2V0KTtcbiAgICAgICAgICAgIC8vIGVzbGludC1kaXNhYmxlLW5leHQtbGluZSBAdHlwZXNjcmlwdC1lc2xpbnQvbm8tbm9uLW51bGwtYXNzZXJ0aW9uXG4gICAgICAgICAgICAkJC5mcmFnbWVudCAmJiAkJC5mcmFnbWVudC5sKG5vZGVzKTtcbiAgICAgICAgICAgIG5vZGVzLmZvckVhY2goZGV0YWNoKTtcbiAgICAgICAgfVxuICAgICAgICBlbHNlIHtcbiAgICAgICAgICAgIC8vIGVzbGludC1kaXNhYmxlLW5leHQtbGluZSBAdHlwZXNjcmlwdC1lc2xpbnQvbm8tbm9uLW51bGwtYXNzZXJ0aW9uXG4gICAgICAgICAgICAkJC5mcmFnbWVudCAmJiAkJC5mcmFnbWVudC5jKCk7XG4gICAgICAgIH1cbiAgICAgICAgaWYgKG9wdGlvbnMuaW50cm8pXG4gICAgICAgICAgICB0cmFuc2l0aW9uX2luKGNvbXBvbmVudC4kJC5mcmFnbWVudCk7XG4gICAgICAgIG1vdW50X2NvbXBvbmVudChjb21wb25lbnQsIG9wdGlvbnMudGFyZ2V0LCBvcHRpb25zLmFuY2hvciwgb3B0aW9ucy5jdXN0b21FbGVtZW50KTtcbiAgICAgICAgZW5kX2h5ZHJhdGluZygpO1xuICAgICAgICBmbHVzaCgpO1xuICAgIH1cbiAgICBzZXRfY3VycmVudF9jb21wb25lbnQocGFyZW50X2NvbXBvbmVudCk7XG59XG5sZXQgU3ZlbHRlRWxlbWVudDtcbmlmICh0eXBlb2YgSFRNTEVsZW1lbnQgPT09ICdmdW5jdGlvbicpIHtcbiAgICBTdmVsdGVFbGVtZW50ID0gY2xhc3MgZXh0ZW5kcyBIVE1MRWxlbWVudCB7XG4gICAgICAgIGNvbnN0cnVjdG9yKCkge1xuICAgICAgICAgICAgc3VwZXIoKTtcbiAgICAgICAgICAgIHRoaXMuYXR0YWNoU2hhZG93KHsgbW9kZTogJ29wZW4nIH0pO1xuICAgICAgICB9XG4gICAgICAgIGNvbm5lY3RlZENhbGxiYWNrKCkge1xuICAgICAgICAgICAgY29uc3QgeyBvbl9tb3VudCB9ID0gdGhpcy4kJDtcbiAgICAgICAgICAgIHRoaXMuJCQub25fZGlzY29ubmVjdCA9IG9uX21vdW50Lm1hcChydW4pLmZpbHRlcihpc19mdW5jdGlvbik7XG4gICAgICAgICAgICAvLyBAdHMtaWdub3JlIHRvZG86IGltcHJvdmUgdHlwaW5nc1xuICAgICAgICAgICAgZm9yIChjb25zdCBrZXkgaW4gdGhpcy4kJC5zbG90dGVkKSB7XG4gICAgICAgICAgICAgICAgLy8gQHRzLWlnbm9yZSB0b2RvOiBpbXByb3ZlIHR5cGluZ3NcbiAgICAgICAgICAgICAgICB0aGlzLmFwcGVuZENoaWxkKHRoaXMuJCQuc2xvdHRlZFtrZXldKTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgICBhdHRyaWJ1dGVDaGFuZ2VkQ2FsbGJhY2soYXR0ciwgX29sZFZhbHVlLCBuZXdWYWx1ZSkge1xuICAgICAgICAgICAgdGhpc1thdHRyXSA9IG5ld1ZhbHVlO1xuICAgICAgICB9XG4gICAgICAgIGRpc2Nvbm5lY3RlZENhbGxiYWNrKCkge1xuICAgICAgICAgICAgcnVuX2FsbCh0aGlzLiQkLm9uX2Rpc2Nvbm5lY3QpO1xuICAgICAgICB9XG4gICAgICAgICRkZXN0cm95KCkge1xuICAgICAgICAgICAgZGVzdHJveV9jb21wb25lbnQodGhpcywgMSk7XG4gICAgICAgICAgICB0aGlzLiRkZXN0cm95ID0gbm9vcDtcbiAgICAgICAgfVxuICAgICAgICAkb24odHlwZSwgY2FsbGJhY2spIHtcbiAgICAgICAgICAgIC8vIFRPRE8gc2hvdWxkIHRoaXMgZGVsZWdhdGUgdG8gYWRkRXZlbnRMaXN0ZW5lcj9cbiAgICAgICAgICAgIGlmICghaXNfZnVuY3Rpb24oY2FsbGJhY2spKSB7XG4gICAgICAgICAgICAgICAgcmV0dXJuIG5vb3A7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICBjb25zdCBjYWxsYmFja3MgPSAodGhpcy4kJC5jYWxsYmFja3NbdHlwZV0gfHwgKHRoaXMuJCQuY2FsbGJhY2tzW3R5cGVdID0gW10pKTtcbiAgICAgICAgICAgIGNhbGxiYWNrcy5wdXNoKGNhbGxiYWNrKTtcbiAgICAgICAgICAgIHJldHVybiAoKSA9PiB7XG4gICAgICAgICAgICAgICAgY29uc3QgaW5kZXggPSBjYWxsYmFja3MuaW5kZXhPZihjYWxsYmFjayk7XG4gICAgICAgICAgICAgICAgaWYgKGluZGV4ICE9PSAtMSlcbiAgICAgICAgICAgICAgICAgICAgY2FsbGJhY2tzLnNwbGljZShpbmRleCwgMSk7XG4gICAgICAgICAgICB9O1xuICAgICAgICB9XG4gICAgICAgICRzZXQoJCRwcm9wcykge1xuICAgICAgICAgICAgaWYgKHRoaXMuJCRzZXQgJiYgIWlzX2VtcHR5KCQkcHJvcHMpKSB7XG4gICAgICAgICAgICAgICAgdGhpcy4kJC5za2lwX2JvdW5kID0gdHJ1ZTtcbiAgICAgICAgICAgICAgICB0aGlzLiQkc2V0KCQkcHJvcHMpO1xuICAgICAgICAgICAgICAgIHRoaXMuJCQuc2tpcF9ib3VuZCA9IGZhbHNlO1xuICAgICAgICAgICAgfVxuICAgICAgICB9XG4gICAgfTtcbn1cbi8qKlxuICogQmFzZSBjbGFzcyBmb3IgU3ZlbHRlIGNvbXBvbmVudHMuIFVzZWQgd2hlbiBkZXY9ZmFsc2UuXG4gKi9cbmNsYXNzIFN2ZWx0ZUNvbXBvbmVudCB7XG4gICAgJGRlc3Ryb3koKSB7XG4gICAgICAgIGRlc3Ryb3lfY29tcG9uZW50KHRoaXMsIDEpO1xuICAgICAgICB0aGlzLiRkZXN0cm95ID0gbm9vcDtcbiAgICB9XG4gICAgJG9uKHR5cGUsIGNhbGxiYWNrKSB7XG4gICAgICAgIGlmICghaXNfZnVuY3Rpb24oY2FsbGJhY2spKSB7XG4gICAgICAgICAgICByZXR1cm4gbm9vcDtcbiAgICAgICAgfVxuICAgICAgICBjb25zdCBjYWxsYmFja3MgPSAodGhpcy4kJC5jYWxsYmFja3NbdHlwZV0gfHwgKHRoaXMuJCQuY2FsbGJhY2tzW3R5cGVdID0gW10pKTtcbiAgICAgICAgY2FsbGJhY2tzLnB1c2goY2FsbGJhY2spO1xuICAgICAgICByZXR1cm4gKCkgPT4ge1xuICAgICAgICAgICAgY29uc3QgaW5kZXggPSBjYWxsYmFja3MuaW5kZXhPZihjYWxsYmFjayk7XG4gICAgICAgICAgICBpZiAoaW5kZXggIT09IC0xKVxuICAgICAgICAgICAgICAgIGNhbGxiYWNrcy5zcGxpY2UoaW5kZXgsIDEpO1xuICAgICAgICB9O1xuICAgIH1cbiAgICAkc2V0KCQkcHJvcHMpIHtcbiAgICAgICAgaWYgKHRoaXMuJCRzZXQgJiYgIWlzX2VtcHR5KCQkcHJvcHMpKSB7XG4gICAgICAgICAgICB0aGlzLiQkLnNraXBfYm91bmQgPSB0cnVlO1xuICAgICAgICAgICAgdGhpcy4kJHNldCgkJHByb3BzKTtcbiAgICAgICAgICAgIHRoaXMuJCQuc2tpcF9ib3VuZCA9IGZhbHNlO1xuICAgICAgICB9XG4gICAgfVxufVxuXG5mdW5jdGlvbiBkaXNwYXRjaF9kZXYodHlwZSwgZGV0YWlsKSB7XG4gICAgZG9jdW1lbnQuZGlzcGF0Y2hFdmVudChjdXN0b21fZXZlbnQodHlwZSwgT2JqZWN0LmFzc2lnbih7IHZlcnNpb246ICczLjU4LjAnIH0sIGRldGFpbCksIHsgYnViYmxlczogdHJ1ZSB9KSk7XG59XG5mdW5jdGlvbiBhcHBlbmRfZGV2KHRhcmdldCwgbm9kZSkge1xuICAgIGRpc3BhdGNoX2RldignU3ZlbHRlRE9NSW5zZXJ0JywgeyB0YXJnZXQsIG5vZGUgfSk7XG4gICAgYXBwZW5kKHRhcmdldCwgbm9kZSk7XG59XG5mdW5jdGlvbiBhcHBlbmRfaHlkcmF0aW9uX2Rldih0YXJnZXQsIG5vZGUpIHtcbiAgICBkaXNwYXRjaF9kZXYoJ1N2ZWx0ZURPTUluc2VydCcsIHsgdGFyZ2V0LCBub2RlIH0pO1xuICAgIGFwcGVuZF9oeWRyYXRpb24odGFyZ2V0LCBub2RlKTtcbn1cbmZ1bmN0aW9uIGluc2VydF9kZXYodGFyZ2V0LCBub2RlLCBhbmNob3IpIHtcbiAgICBkaXNwYXRjaF9kZXYoJ1N2ZWx0ZURPTUluc2VydCcsIHsgdGFyZ2V0LCBub2RlLCBhbmNob3IgfSk7XG4gICAgaW5zZXJ0KHRhcmdldCwgbm9kZSwgYW5jaG9yKTtcbn1cbmZ1bmN0aW9uIGluc2VydF9oeWRyYXRpb25fZGV2KHRhcmdldCwgbm9kZSwgYW5jaG9yKSB7XG4gICAgZGlzcGF0Y2hfZGV2KCdTdmVsdGVET01JbnNlcnQnLCB7IHRhcmdldCwgbm9kZSwgYW5jaG9yIH0pO1xuICAgIGluc2VydF9oeWRyYXRpb24odGFyZ2V0LCBub2RlLCBhbmNob3IpO1xufVxuZnVuY3Rpb24gZGV0YWNoX2Rldihub2RlKSB7XG4gICAgZGlzcGF0Y2hfZGV2KCdTdmVsdGVET01SZW1vdmUnLCB7IG5vZGUgfSk7XG4gICAgZGV0YWNoKG5vZGUpO1xufVxuZnVuY3Rpb24gZGV0YWNoX2JldHdlZW5fZGV2KGJlZm9yZSwgYWZ0ZXIpIHtcbiAgICB3aGlsZSAoYmVmb3JlLm5leHRTaWJsaW5nICYmIGJlZm9yZS5uZXh0U2libGluZyAhPT0gYWZ0ZXIpIHtcbiAgICAgICAgZGV0YWNoX2RldihiZWZvcmUubmV4dFNpYmxpbmcpO1xuICAgIH1cbn1cbmZ1bmN0aW9uIGRldGFjaF9iZWZvcmVfZGV2KGFmdGVyKSB7XG4gICAgd2hpbGUgKGFmdGVyLnByZXZpb3VzU2libGluZykge1xuICAgICAgICBkZXRhY2hfZGV2KGFmdGVyLnByZXZpb3VzU2libGluZyk7XG4gICAgfVxufVxuZnVuY3Rpb24gZGV0YWNoX2FmdGVyX2RldihiZWZvcmUpIHtcbiAgICB3aGlsZSAoYmVmb3JlLm5leHRTaWJsaW5nKSB7XG4gICAgICAgIGRldGFjaF9kZXYoYmVmb3JlLm5leHRTaWJsaW5nKTtcbiAgICB9XG59XG5mdW5jdGlvbiBsaXN0ZW5fZGV2KG5vZGUsIGV2ZW50LCBoYW5kbGVyLCBvcHRpb25zLCBoYXNfcHJldmVudF9kZWZhdWx0LCBoYXNfc3RvcF9wcm9wYWdhdGlvbiwgaGFzX3N0b3BfaW1tZWRpYXRlX3Byb3BhZ2F0aW9uKSB7XG4gICAgY29uc3QgbW9kaWZpZXJzID0gb3B0aW9ucyA9PT0gdHJ1ZSA/IFsnY2FwdHVyZSddIDogb3B0aW9ucyA/IEFycmF5LmZyb20oT2JqZWN0LmtleXMob3B0aW9ucykpIDogW107XG4gICAgaWYgKGhhc19wcmV2ZW50X2RlZmF1bHQpXG4gICAgICAgIG1vZGlmaWVycy5wdXNoKCdwcmV2ZW50RGVmYXVsdCcpO1xuICAgIGlmIChoYXNfc3RvcF9wcm9wYWdhdGlvbilcbiAgICAgICAgbW9kaWZpZXJzLnB1c2goJ3N0b3BQcm9wYWdhdGlvbicpO1xuICAgIGlmIChoYXNfc3RvcF9pbW1lZGlhdGVfcHJvcGFnYXRpb24pXG4gICAgICAgIG1vZGlmaWVycy5wdXNoKCdzdG9wSW1tZWRpYXRlUHJvcGFnYXRpb24nKTtcbiAgICBkaXNwYXRjaF9kZXYoJ1N2ZWx0ZURPTUFkZEV2ZW50TGlzdGVuZXInLCB7IG5vZGUsIGV2ZW50LCBoYW5kbGVyLCBtb2RpZmllcnMgfSk7XG4gICAgY29uc3QgZGlzcG9zZSA9IGxpc3Rlbihub2RlLCBldmVudCwgaGFuZGxlciwgb3B0aW9ucyk7XG4gICAgcmV0dXJuICgpID0+IHtcbiAgICAgICAgZGlzcGF0Y2hfZGV2KCdTdmVsdGVET01SZW1vdmVFdmVudExpc3RlbmVyJywgeyBub2RlLCBldmVudCwgaGFuZGxlciwgbW9kaWZpZXJzIH0pO1xuICAgICAgICBkaXNwb3NlKCk7XG4gICAgfTtcbn1cbmZ1bmN0aW9uIGF0dHJfZGV2KG5vZGUsIGF0dHJpYnV0ZSwgdmFsdWUpIHtcbiAgICBhdHRyKG5vZGUsIGF0dHJpYnV0ZSwgdmFsdWUpO1xuICAgIGlmICh2YWx1ZSA9PSBudWxsKVxuICAgICAgICBkaXNwYXRjaF9kZXYoJ1N2ZWx0ZURPTVJlbW92ZUF0dHJpYnV0ZScsIHsgbm9kZSwgYXR0cmlidXRlIH0pO1xuICAgIGVsc2VcbiAgICAgICAgZGlzcGF0Y2hfZGV2KCdTdmVsdGVET01TZXRBdHRyaWJ1dGUnLCB7IG5vZGUsIGF0dHJpYnV0ZSwgdmFsdWUgfSk7XG59XG5mdW5jdGlvbiBwcm9wX2Rldihub2RlLCBwcm9wZXJ0eSwgdmFsdWUpIHtcbiAgICBub2RlW3Byb3BlcnR5XSA9IHZhbHVlO1xuICAgIGRpc3BhdGNoX2RldignU3ZlbHRlRE9NU2V0UHJvcGVydHknLCB7IG5vZGUsIHByb3BlcnR5LCB2YWx1ZSB9KTtcbn1cbmZ1bmN0aW9uIGRhdGFzZXRfZGV2KG5vZGUsIHByb3BlcnR5LCB2YWx1ZSkge1xuICAgIG5vZGUuZGF0YXNldFtwcm9wZXJ0eV0gPSB2YWx1ZTtcbiAgICBkaXNwYXRjaF9kZXYoJ1N2ZWx0ZURPTVNldERhdGFzZXQnLCB7IG5vZGUsIHByb3BlcnR5LCB2YWx1ZSB9KTtcbn1cbmZ1bmN0aW9uIHNldF9kYXRhX2Rldih0ZXh0LCBkYXRhKSB7XG4gICAgZGF0YSA9ICcnICsgZGF0YTtcbiAgICBpZiAodGV4dC5kYXRhID09PSBkYXRhKVxuICAgICAgICByZXR1cm47XG4gICAgZGlzcGF0Y2hfZGV2KCdTdmVsdGVET01TZXREYXRhJywgeyBub2RlOiB0ZXh0LCBkYXRhIH0pO1xuICAgIHRleHQuZGF0YSA9IGRhdGE7XG59XG5mdW5jdGlvbiBzZXRfZGF0YV9jb250ZW50ZWRpdGFibGVfZGV2KHRleHQsIGRhdGEpIHtcbiAgICBkYXRhID0gJycgKyBkYXRhO1xuICAgIGlmICh0ZXh0Lndob2xlVGV4dCA9PT0gZGF0YSlcbiAgICAgICAgcmV0dXJuO1xuICAgIGRpc3BhdGNoX2RldignU3ZlbHRlRE9NU2V0RGF0YScsIHsgbm9kZTogdGV4dCwgZGF0YSB9KTtcbiAgICB0ZXh0LmRhdGEgPSBkYXRhO1xufVxuZnVuY3Rpb24gc2V0X2RhdGFfbWF5YmVfY29udGVudGVkaXRhYmxlX2Rldih0ZXh0LCBkYXRhLCBhdHRyX3ZhbHVlKSB7XG4gICAgaWYgKH5jb250ZW50ZWRpdGFibGVfdHJ1dGh5X3ZhbHVlcy5pbmRleE9mKGF0dHJfdmFsdWUpKSB7XG4gICAgICAgIHNldF9kYXRhX2NvbnRlbnRlZGl0YWJsZV9kZXYodGV4dCwgZGF0YSk7XG4gICAgfVxuICAgIGVsc2Uge1xuICAgICAgICBzZXRfZGF0YV9kZXYodGV4dCwgZGF0YSk7XG4gICAgfVxufVxuZnVuY3Rpb24gdmFsaWRhdGVfZWFjaF9hcmd1bWVudChhcmcpIHtcbiAgICBpZiAodHlwZW9mIGFyZyAhPT0gJ3N0cmluZycgJiYgIShhcmcgJiYgdHlwZW9mIGFyZyA9PT0gJ29iamVjdCcgJiYgJ2xlbmd0aCcgaW4gYXJnKSkge1xuICAgICAgICBsZXQgbXNnID0gJ3sjZWFjaH0gb25seSBpdGVyYXRlcyBvdmVyIGFycmF5LWxpa2Ugb2JqZWN0cy4nO1xuICAgICAgICBpZiAodHlwZW9mIFN5bWJvbCA9PT0gJ2Z1bmN0aW9uJyAmJiBhcmcgJiYgU3ltYm9sLml0ZXJhdG9yIGluIGFyZykge1xuICAgICAgICAgICAgbXNnICs9ICcgWW91IGNhbiB1c2UgYSBzcHJlYWQgdG8gY29udmVydCB0aGlzIGl0ZXJhYmxlIGludG8gYW4gYXJyYXkuJztcbiAgICAgICAgfVxuICAgICAgICB0aHJvdyBuZXcgRXJyb3IobXNnKTtcbiAgICB9XG59XG5mdW5jdGlvbiB2YWxpZGF0ZV9zbG90cyhuYW1lLCBzbG90LCBrZXlzKSB7XG4gICAgZm9yIChjb25zdCBzbG90X2tleSBvZiBPYmplY3Qua2V5cyhzbG90KSkge1xuICAgICAgICBpZiAoIX5rZXlzLmluZGV4T2Yoc2xvdF9rZXkpKSB7XG4gICAgICAgICAgICBjb25zb2xlLndhcm4oYDwke25hbWV9PiByZWNlaXZlZCBhbiB1bmV4cGVjdGVkIHNsb3QgXCIke3Nsb3Rfa2V5fVwiLmApO1xuICAgICAgICB9XG4gICAgfVxufVxuZnVuY3Rpb24gdmFsaWRhdGVfZHluYW1pY19lbGVtZW50KHRhZykge1xuICAgIGNvbnN0IGlzX3N0cmluZyA9IHR5cGVvZiB0YWcgPT09ICdzdHJpbmcnO1xuICAgIGlmICh0YWcgJiYgIWlzX3N0cmluZykge1xuICAgICAgICB0aHJvdyBuZXcgRXJyb3IoJzxzdmVsdGU6ZWxlbWVudD4gZXhwZWN0cyBcInRoaXNcIiBhdHRyaWJ1dGUgdG8gYmUgYSBzdHJpbmcuJyk7XG4gICAgfVxufVxuZnVuY3Rpb24gdmFsaWRhdGVfdm9pZF9keW5hbWljX2VsZW1lbnQodGFnKSB7XG4gICAgaWYgKHRhZyAmJiBpc192b2lkKHRhZykpIHtcbiAgICAgICAgY29uc29sZS53YXJuKGA8c3ZlbHRlOmVsZW1lbnQgdGhpcz1cIiR7dGFnfVwiPiBpcyBzZWxmLWNsb3NpbmcgYW5kIGNhbm5vdCBoYXZlIGNvbnRlbnQuYCk7XG4gICAgfVxufVxuZnVuY3Rpb24gY29uc3RydWN0X3N2ZWx0ZV9jb21wb25lbnRfZGV2KGNvbXBvbmVudCwgcHJvcHMpIHtcbiAgICBjb25zdCBlcnJvcl9tZXNzYWdlID0gJ3RoaXM9ey4uLn0gb2YgPHN2ZWx0ZTpjb21wb25lbnQ+IHNob3VsZCBzcGVjaWZ5IGEgU3ZlbHRlIGNvbXBvbmVudC4nO1xuICAgIHRyeSB7XG4gICAgICAgIGNvbnN0IGluc3RhbmNlID0gbmV3IGNvbXBvbmVudChwcm9wcyk7XG4gICAgICAgIGlmICghaW5zdGFuY2UuJCQgfHwgIWluc3RhbmNlLiRzZXQgfHwgIWluc3RhbmNlLiRvbiB8fCAhaW5zdGFuY2UuJGRlc3Ryb3kpIHtcbiAgICAgICAgICAgIHRocm93IG5ldyBFcnJvcihlcnJvcl9tZXNzYWdlKTtcbiAgICAgICAgfVxuICAgICAgICByZXR1cm4gaW5zdGFuY2U7XG4gICAgfVxuICAgIGNhdGNoIChlcnIpIHtcbiAgICAgICAgY29uc3QgeyBtZXNzYWdlIH0gPSBlcnI7XG4gICAgICAgIGlmICh0eXBlb2YgbWVzc2FnZSA9PT0gJ3N0cmluZycgJiYgbWVzc2FnZS5pbmRleE9mKCdpcyBub3QgYSBjb25zdHJ1Y3RvcicpICE9PSAtMSkge1xuICAgICAgICAgICAgdGhyb3cgbmV3IEVycm9yKGVycm9yX21lc3NhZ2UpO1xuICAgICAgICB9XG4gICAgICAgIGVsc2Uge1xuICAgICAgICAgICAgdGhyb3cgZXJyO1xuICAgICAgICB9XG4gICAgfVxufVxuLyoqXG4gKiBCYXNlIGNsYXNzIGZvciBTdmVsdGUgY29tcG9uZW50cyB3aXRoIHNvbWUgbWlub3IgZGV2LWVuaGFuY2VtZW50cy4gVXNlZCB3aGVuIGRldj10cnVlLlxuICovXG5jbGFzcyBTdmVsdGVDb21wb25lbnREZXYgZXh0ZW5kcyBTdmVsdGVDb21wb25lbnQge1xuICAgIGNvbnN0cnVjdG9yKG9wdGlvbnMpIHtcbiAgICAgICAgaWYgKCFvcHRpb25zIHx8ICghb3B0aW9ucy50YXJnZXQgJiYgIW9wdGlvbnMuJCRpbmxpbmUpKSB7XG4gICAgICAgICAgICB0aHJvdyBuZXcgRXJyb3IoXCIndGFyZ2V0JyBpcyBhIHJlcXVpcmVkIG9wdGlvblwiKTtcbiAgICAgICAgfVxuICAgICAgICBzdXBlcigpO1xuICAgIH1cbiAgICAkZGVzdHJveSgpIHtcbiAgICAgICAgc3VwZXIuJGRlc3Ryb3koKTtcbiAgICAgICAgdGhpcy4kZGVzdHJveSA9ICgpID0+IHtcbiAgICAgICAgICAgIGNvbnNvbGUud2FybignQ29tcG9uZW50IHdhcyBhbHJlYWR5IGRlc3Ryb3llZCcpOyAvLyBlc2xpbnQtZGlzYWJsZS1saW5lIG5vLWNvbnNvbGVcbiAgICAgICAgfTtcbiAgICB9XG4gICAgJGNhcHR1cmVfc3RhdGUoKSB7IH1cbiAgICAkaW5qZWN0X3N0YXRlKCkgeyB9XG59XG4vKipcbiAqIEJhc2UgY2xhc3MgdG8gY3JlYXRlIHN0cm9uZ2x5IHR5cGVkIFN2ZWx0ZSBjb21wb25lbnRzLlxuICogVGhpcyBvbmx5IGV4aXN0cyBmb3IgdHlwaW5nIHB1cnBvc2VzIGFuZCBzaG91bGQgYmUgdXNlZCBpbiBgLmQudHNgIGZpbGVzLlxuICpcbiAqICMjIyBFeGFtcGxlOlxuICpcbiAqIFlvdSBoYXZlIGNvbXBvbmVudCBsaWJyYXJ5IG9uIG5wbSBjYWxsZWQgYGNvbXBvbmVudC1saWJyYXJ5YCwgZnJvbSB3aGljaFxuICogeW91IGV4cG9ydCBhIGNvbXBvbmVudCBjYWxsZWQgYE15Q29tcG9uZW50YC4gRm9yIFN2ZWx0ZStUeXBlU2NyaXB0IHVzZXJzLFxuICogeW91IHdhbnQgdG8gcHJvdmlkZSB0eXBpbmdzLiBUaGVyZWZvcmUgeW91IGNyZWF0ZSBhIGBpbmRleC5kLnRzYDpcbiAqIGBgYHRzXG4gKiBpbXBvcnQgeyBTdmVsdGVDb21wb25lbnRUeXBlZCB9IGZyb20gXCJzdmVsdGVcIjtcbiAqIGV4cG9ydCBjbGFzcyBNeUNvbXBvbmVudCBleHRlbmRzIFN2ZWx0ZUNvbXBvbmVudFR5cGVkPHtmb286IHN0cmluZ30+IHt9XG4gKiBgYGBcbiAqIFR5cGluZyB0aGlzIG1ha2VzIGl0IHBvc3NpYmxlIGZvciBJREVzIGxpa2UgVlMgQ29kZSB3aXRoIHRoZSBTdmVsdGUgZXh0ZW5zaW9uXG4gKiB0byBwcm92aWRlIGludGVsbGlzZW5zZSBhbmQgdG8gdXNlIHRoZSBjb21wb25lbnQgbGlrZSB0aGlzIGluIGEgU3ZlbHRlIGZpbGVcbiAqIHdpdGggVHlwZVNjcmlwdDpcbiAqIGBgYHN2ZWx0ZVxuICogPHNjcmlwdCBsYW5nPVwidHNcIj5cbiAqIFx0aW1wb3J0IHsgTXlDb21wb25lbnQgfSBmcm9tIFwiY29tcG9uZW50LWxpYnJhcnlcIjtcbiAqIDwvc2NyaXB0PlxuICogPE15Q29tcG9uZW50IGZvbz17J2Jhcid9IC8+XG4gKiBgYGBcbiAqXG4gKiAjIyMjIFdoeSBub3QgbWFrZSB0aGlzIHBhcnQgb2YgYFN2ZWx0ZUNvbXBvbmVudChEZXYpYD9cbiAqIEJlY2F1c2VcbiAqIGBgYHRzXG4gKiBjbGFzcyBBU3ViY2xhc3NPZlN2ZWx0ZUNvbXBvbmVudCBleHRlbmRzIFN2ZWx0ZUNvbXBvbmVudDx7Zm9vOiBzdHJpbmd9PiB7fVxuICogY29uc3QgY29tcG9uZW50OiB0eXBlb2YgU3ZlbHRlQ29tcG9uZW50ID0gQVN1YmNsYXNzT2ZTdmVsdGVDb21wb25lbnQ7XG4gKiBgYGBcbiAqIHdpbGwgdGhyb3cgYSB0eXBlIGVycm9yLCBzbyB3ZSBuZWVkIHRvIHNlcGFyYXRlIHRoZSBtb3JlIHN0cmljdGx5IHR5cGVkIGNsYXNzLlxuICovXG5jbGFzcyBTdmVsdGVDb21wb25lbnRUeXBlZCBleHRlbmRzIFN2ZWx0ZUNvbXBvbmVudERldiB7XG4gICAgY29uc3RydWN0b3Iob3B0aW9ucykge1xuICAgICAgICBzdXBlcihvcHRpb25zKTtcbiAgICB9XG59XG5mdW5jdGlvbiBsb29wX2d1YXJkKHRpbWVvdXQpIHtcbiAgICBjb25zdCBzdGFydCA9IERhdGUubm93KCk7XG4gICAgcmV0dXJuICgpID0+IHtcbiAgICAgICAgaWYgKERhdGUubm93KCkgLSBzdGFydCA+IHRpbWVvdXQpIHtcbiAgICAgICAgICAgIHRocm93IG5ldyBFcnJvcignSW5maW5pdGUgbG9vcCBkZXRlY3RlZCcpO1xuICAgICAgICB9XG4gICAgfTtcbn1cblxuZXhwb3J0IHsgSHRtbFRhZywgSHRtbFRhZ0h5ZHJhdGlvbiwgU3ZlbHRlQ29tcG9uZW50LCBTdmVsdGVDb21wb25lbnREZXYsIFN2ZWx0ZUNvbXBvbmVudFR5cGVkLCBTdmVsdGVFbGVtZW50LCBhY3Rpb25fZGVzdHJveWVyLCBhZGRfYXR0cmlidXRlLCBhZGRfY2xhc3NlcywgYWRkX2ZsdXNoX2NhbGxiYWNrLCBhZGRfbG9jYXRpb24sIGFkZF9yZW5kZXJfY2FsbGJhY2ssIGFkZF9yZXNpemVfbGlzdGVuZXIsIGFkZF9zdHlsZXMsIGFkZF90cmFuc2Zvcm0sIGFmdGVyVXBkYXRlLCBhcHBlbmQsIGFwcGVuZF9kZXYsIGFwcGVuZF9lbXB0eV9zdHlsZXNoZWV0LCBhcHBlbmRfaHlkcmF0aW9uLCBhcHBlbmRfaHlkcmF0aW9uX2RldiwgYXBwZW5kX3N0eWxlcywgYXNzaWduLCBhdHRyLCBhdHRyX2RldiwgYXR0cmlidXRlX3RvX29iamVjdCwgYmVmb3JlVXBkYXRlLCBiaW5kLCBiaW5kaW5nX2NhbGxiYWNrcywgYmxhbmtfb2JqZWN0LCBidWJibGUsIGNoZWNrX291dHJvcywgY2hpbGRyZW4sIGNsYWltX2NvbW1lbnQsIGNsYWltX2NvbXBvbmVudCwgY2xhaW1fZWxlbWVudCwgY2xhaW1faHRtbF90YWcsIGNsYWltX3NwYWNlLCBjbGFpbV9zdmdfZWxlbWVudCwgY2xhaW1fdGV4dCwgY2xlYXJfbG9vcHMsIGNvbW1lbnQsIGNvbXBvbmVudF9zdWJzY3JpYmUsIGNvbXB1dGVfcmVzdF9wcm9wcywgY29tcHV0ZV9zbG90cywgY29uc3RydWN0X3N2ZWx0ZV9jb21wb25lbnQsIGNvbnN0cnVjdF9zdmVsdGVfY29tcG9uZW50X2RldiwgY29udGVudGVkaXRhYmxlX3RydXRoeV92YWx1ZXMsIGNyZWF0ZUV2ZW50RGlzcGF0Y2hlciwgY3JlYXRlX2FuaW1hdGlvbiwgY3JlYXRlX2JpZGlyZWN0aW9uYWxfdHJhbnNpdGlvbiwgY3JlYXRlX2NvbXBvbmVudCwgY3JlYXRlX2luX3RyYW5zaXRpb24sIGNyZWF0ZV9vdXRfdHJhbnNpdGlvbiwgY3JlYXRlX3Nsb3QsIGNyZWF0ZV9zc3JfY29tcG9uZW50LCBjdXJyZW50X2NvbXBvbmVudCwgY3VzdG9tX2V2ZW50LCBkYXRhc2V0X2RldiwgZGVidWcsIGRlc3Ryb3lfYmxvY2ssIGRlc3Ryb3lfY29tcG9uZW50LCBkZXN0cm95X2VhY2gsIGRldGFjaCwgZGV0YWNoX2FmdGVyX2RldiwgZGV0YWNoX2JlZm9yZV9kZXYsIGRldGFjaF9iZXR3ZWVuX2RldiwgZGV0YWNoX2RldiwgZGlydHlfY29tcG9uZW50cywgZGlzcGF0Y2hfZGV2LCBlYWNoLCBlbGVtZW50LCBlbGVtZW50X2lzLCBlbXB0eSwgZW5kX2h5ZHJhdGluZywgZXNjYXBlLCBlc2NhcGVfYXR0cmlidXRlX3ZhbHVlLCBlc2NhcGVfb2JqZWN0LCBleGNsdWRlX2ludGVybmFsX3Byb3BzLCBmaXhfYW5kX2Rlc3Ryb3lfYmxvY2ssIGZpeF9hbmRfb3V0cm9fYW5kX2Rlc3Ryb3lfYmxvY2ssIGZpeF9wb3NpdGlvbiwgZmx1c2gsIGZsdXNoX3JlbmRlcl9jYWxsYmFja3MsIGdldEFsbENvbnRleHRzLCBnZXRDb250ZXh0LCBnZXRfYWxsX2RpcnR5X2Zyb21fc2NvcGUsIGdldF9iaW5kaW5nX2dyb3VwX3ZhbHVlLCBnZXRfY3VycmVudF9jb21wb25lbnQsIGdldF9jdXN0b21fZWxlbWVudHNfc2xvdHMsIGdldF9yb290X2Zvcl9zdHlsZSwgZ2V0X3Nsb3RfY2hhbmdlcywgZ2V0X3NwcmVhZF9vYmplY3QsIGdldF9zcHJlYWRfdXBkYXRlLCBnZXRfc3RvcmVfdmFsdWUsIGdsb2JhbHMsIGdyb3VwX291dHJvcywgaGFuZGxlX3Byb21pc2UsIGhhc0NvbnRleHQsIGhhc19wcm9wLCBoZWFkX3NlbGVjdG9yLCBpZGVudGl0eSwgaW5pdCwgaW5pdF9iaW5kaW5nX2dyb3VwLCBpbml0X2JpbmRpbmdfZ3JvdXBfZHluYW1pYywgaW5zZXJ0LCBpbnNlcnRfZGV2LCBpbnNlcnRfaHlkcmF0aW9uLCBpbnNlcnRfaHlkcmF0aW9uX2RldiwgaW50cm9zLCBpbnZhbGlkX2F0dHJpYnV0ZV9uYW1lX2NoYXJhY3RlciwgaXNfY2xpZW50LCBpc19jcm9zc29yaWdpbiwgaXNfZW1wdHksIGlzX2Z1bmN0aW9uLCBpc19wcm9taXNlLCBpc192b2lkLCBsaXN0ZW4sIGxpc3Rlbl9kZXYsIGxvb3AsIGxvb3BfZ3VhcmQsIG1lcmdlX3Nzcl9zdHlsZXMsIG1pc3NpbmdfY29tcG9uZW50LCBtb3VudF9jb21wb25lbnQsIG5vb3AsIG5vdF9lcXVhbCwgbm93LCBudWxsX3RvX2VtcHR5LCBvYmplY3Rfd2l0aG91dF9wcm9wZXJ0aWVzLCBvbkRlc3Ryb3ksIG9uTW91bnQsIG9uY2UsIG91dHJvX2FuZF9kZXN0cm95X2Jsb2NrLCBwcmV2ZW50X2RlZmF1bHQsIHByb3BfZGV2LCBxdWVyeV9zZWxlY3Rvcl9hbGwsIHJhZiwgcnVuLCBydW5fYWxsLCBzYWZlX25vdF9lcXVhbCwgc2NoZWR1bGVfdXBkYXRlLCBzZWxlY3RfbXVsdGlwbGVfdmFsdWUsIHNlbGVjdF9vcHRpb24sIHNlbGVjdF9vcHRpb25zLCBzZWxlY3RfdmFsdWUsIHNlbGYsIHNldENvbnRleHQsIHNldF9hdHRyaWJ1dGVzLCBzZXRfY3VycmVudF9jb21wb25lbnQsIHNldF9jdXN0b21fZWxlbWVudF9kYXRhLCBzZXRfY3VzdG9tX2VsZW1lbnRfZGF0YV9tYXAsIHNldF9kYXRhLCBzZXRfZGF0YV9jb250ZW50ZWRpdGFibGUsIHNldF9kYXRhX2NvbnRlbnRlZGl0YWJsZV9kZXYsIHNldF9kYXRhX2Rldiwgc2V0X2RhdGFfbWF5YmVfY29udGVudGVkaXRhYmxlLCBzZXRfZGF0YV9tYXliZV9jb250ZW50ZWRpdGFibGVfZGV2LCBzZXRfZHluYW1pY19lbGVtZW50X2RhdGEsIHNldF9pbnB1dF90eXBlLCBzZXRfaW5wdXRfdmFsdWUsIHNldF9ub3csIHNldF9yYWYsIHNldF9zdG9yZV92YWx1ZSwgc2V0X3N0eWxlLCBzZXRfc3ZnX2F0dHJpYnV0ZXMsIHNwYWNlLCBzcGxpdF9jc3NfdW5pdCwgc3ByZWFkLCBzcmNfdXJsX2VxdWFsLCBzdGFydF9oeWRyYXRpbmcsIHN0b3BfaW1tZWRpYXRlX3Byb3BhZ2F0aW9uLCBzdG9wX3Byb3BhZ2F0aW9uLCBzdWJzY3JpYmUsIHN2Z19lbGVtZW50LCB0ZXh0LCB0aWNrLCB0aW1lX3Jhbmdlc190b19hcnJheSwgdG9fbnVtYmVyLCB0b2dnbGVfY2xhc3MsIHRyYW5zaXRpb25faW4sIHRyYW5zaXRpb25fb3V0LCB0cnVzdGVkLCB1cGRhdGVfYXdhaXRfYmxvY2tfYnJhbmNoLCB1cGRhdGVfa2V5ZWRfZWFjaCwgdXBkYXRlX3Nsb3QsIHVwZGF0ZV9zbG90X2Jhc2UsIHZhbGlkYXRlX2NvbXBvbmVudCwgdmFsaWRhdGVfZHluYW1pY19lbGVtZW50LCB2YWxpZGF0ZV9lYWNoX2FyZ3VtZW50LCB2YWxpZGF0ZV9lYWNoX2tleXMsIHZhbGlkYXRlX3Nsb3RzLCB2YWxpZGF0ZV9zdG9yZSwgdmFsaWRhdGVfdm9pZF9keW5hbWljX2VsZW1lbnQsIHhsaW5rX2F0dHIgfTtcbiIsICJpbXBvcnQgeyBub29wLCBzYWZlX25vdF9lcXVhbCwgc3Vic2NyaWJlLCBydW5fYWxsLCBpc19mdW5jdGlvbiB9IGZyb20gJy4uL2ludGVybmFsL2luZGV4Lm1qcyc7XG5leHBvcnQgeyBnZXRfc3RvcmVfdmFsdWUgYXMgZ2V0IH0gZnJvbSAnLi4vaW50ZXJuYWwvaW5kZXgubWpzJztcblxuY29uc3Qgc3Vic2NyaWJlcl9xdWV1ZSA9IFtdO1xuLyoqXG4gKiBDcmVhdGVzIGEgYFJlYWRhYmxlYCBzdG9yZSB0aGF0IGFsbG93cyByZWFkaW5nIGJ5IHN1YnNjcmlwdGlvbi5cbiAqIEBwYXJhbSB2YWx1ZSBpbml0aWFsIHZhbHVlXG4gKiBAcGFyYW0ge1N0YXJ0U3RvcE5vdGlmaWVyfXN0YXJ0IHN0YXJ0IGFuZCBzdG9wIG5vdGlmaWNhdGlvbnMgZm9yIHN1YnNjcmlwdGlvbnNcbiAqL1xuZnVuY3Rpb24gcmVhZGFibGUodmFsdWUsIHN0YXJ0KSB7XG4gICAgcmV0dXJuIHtcbiAgICAgICAgc3Vic2NyaWJlOiB3cml0YWJsZSh2YWx1ZSwgc3RhcnQpLnN1YnNjcmliZVxuICAgIH07XG59XG4vKipcbiAqIENyZWF0ZSBhIGBXcml0YWJsZWAgc3RvcmUgdGhhdCBhbGxvd3MgYm90aCB1cGRhdGluZyBhbmQgcmVhZGluZyBieSBzdWJzY3JpcHRpb24uXG4gKiBAcGFyYW0geyo9fXZhbHVlIGluaXRpYWwgdmFsdWVcbiAqIEBwYXJhbSB7U3RhcnRTdG9wTm90aWZpZXI9fXN0YXJ0IHN0YXJ0IGFuZCBzdG9wIG5vdGlmaWNhdGlvbnMgZm9yIHN1YnNjcmlwdGlvbnNcbiAqL1xuZnVuY3Rpb24gd3JpdGFibGUodmFsdWUsIHN0YXJ0ID0gbm9vcCkge1xuICAgIGxldCBzdG9wO1xuICAgIGNvbnN0IHN1YnNjcmliZXJzID0gbmV3IFNldCgpO1xuICAgIGZ1bmN0aW9uIHNldChuZXdfdmFsdWUpIHtcbiAgICAgICAgaWYgKHNhZmVfbm90X2VxdWFsKHZhbHVlLCBuZXdfdmFsdWUpKSB7XG4gICAgICAgICAgICB2YWx1ZSA9IG5ld192YWx1ZTtcbiAgICAgICAgICAgIGlmIChzdG9wKSB7IC8vIHN0b3JlIGlzIHJlYWR5XG4gICAgICAgICAgICAgICAgY29uc3QgcnVuX3F1ZXVlID0gIXN1YnNjcmliZXJfcXVldWUubGVuZ3RoO1xuICAgICAgICAgICAgICAgIGZvciAoY29uc3Qgc3Vic2NyaWJlciBvZiBzdWJzY3JpYmVycykge1xuICAgICAgICAgICAgICAgICAgICBzdWJzY3JpYmVyWzFdKCk7XG4gICAgICAgICAgICAgICAgICAgIHN1YnNjcmliZXJfcXVldWUucHVzaChzdWJzY3JpYmVyLCB2YWx1ZSk7XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgIGlmIChydW5fcXVldWUpIHtcbiAgICAgICAgICAgICAgICAgICAgZm9yIChsZXQgaSA9IDA7IGkgPCBzdWJzY3JpYmVyX3F1ZXVlLmxlbmd0aDsgaSArPSAyKSB7XG4gICAgICAgICAgICAgICAgICAgICAgICBzdWJzY3JpYmVyX3F1ZXVlW2ldWzBdKHN1YnNjcmliZXJfcXVldWVbaSArIDFdKTtcbiAgICAgICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgICAgICBzdWJzY3JpYmVyX3F1ZXVlLmxlbmd0aCA9IDA7XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgfVxuICAgICAgICB9XG4gICAgfVxuICAgIGZ1bmN0aW9uIHVwZGF0ZShmbikge1xuICAgICAgICBzZXQoZm4odmFsdWUpKTtcbiAgICB9XG4gICAgZnVuY3Rpb24gc3Vic2NyaWJlKHJ1biwgaW52YWxpZGF0ZSA9IG5vb3ApIHtcbiAgICAgICAgY29uc3Qgc3Vic2NyaWJlciA9IFtydW4sIGludmFsaWRhdGVdO1xuICAgICAgICBzdWJzY3JpYmVycy5hZGQoc3Vic2NyaWJlcik7XG4gICAgICAgIGlmIChzdWJzY3JpYmVycy5zaXplID09PSAxKSB7XG4gICAgICAgICAgICBzdG9wID0gc3RhcnQoc2V0KSB8fCBub29wO1xuICAgICAgICB9XG4gICAgICAgIHJ1bih2YWx1ZSk7XG4gICAgICAgIHJldHVybiAoKSA9PiB7XG4gICAgICAgICAgICBzdWJzY3JpYmVycy5kZWxldGUoc3Vic2NyaWJlcik7XG4gICAgICAgICAgICBpZiAoc3Vic2NyaWJlcnMuc2l6ZSA9PT0gMCAmJiBzdG9wKSB7XG4gICAgICAgICAgICAgICAgc3RvcCgpO1xuICAgICAgICAgICAgICAgIHN0b3AgPSBudWxsO1xuICAgICAgICAgICAgfVxuICAgICAgICB9O1xuICAgIH1cbiAgICByZXR1cm4geyBzZXQsIHVwZGF0ZSwgc3Vic2NyaWJlIH07XG59XG5mdW5jdGlvbiBkZXJpdmVkKHN0b3JlcywgZm4sIGluaXRpYWxfdmFsdWUpIHtcbiAgICBjb25zdCBzaW5nbGUgPSAhQXJyYXkuaXNBcnJheShzdG9yZXMpO1xuICAgIGNvbnN0IHN0b3Jlc19hcnJheSA9IHNpbmdsZVxuICAgICAgICA/IFtzdG9yZXNdXG4gICAgICAgIDogc3RvcmVzO1xuICAgIGNvbnN0IGF1dG8gPSBmbi5sZW5ndGggPCAyO1xuICAgIHJldHVybiByZWFkYWJsZShpbml0aWFsX3ZhbHVlLCAoc2V0KSA9PiB7XG4gICAgICAgIGxldCBzdGFydGVkID0gZmFsc2U7XG4gICAgICAgIGNvbnN0IHZhbHVlcyA9IFtdO1xuICAgICAgICBsZXQgcGVuZGluZyA9IDA7XG4gICAgICAgIGxldCBjbGVhbnVwID0gbm9vcDtcbiAgICAgICAgY29uc3Qgc3luYyA9ICgpID0+IHtcbiAgICAgICAgICAgIGlmIChwZW5kaW5nKSB7XG4gICAgICAgICAgICAgICAgcmV0dXJuO1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgY2xlYW51cCgpO1xuICAgICAgICAgICAgY29uc3QgcmVzdWx0ID0gZm4oc2luZ2xlID8gdmFsdWVzWzBdIDogdmFsdWVzLCBzZXQpO1xuICAgICAgICAgICAgaWYgKGF1dG8pIHtcbiAgICAgICAgICAgICAgICBzZXQocmVzdWx0KTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIGVsc2Uge1xuICAgICAgICAgICAgICAgIGNsZWFudXAgPSBpc19mdW5jdGlvbihyZXN1bHQpID8gcmVzdWx0IDogbm9vcDtcbiAgICAgICAgICAgIH1cbiAgICAgICAgfTtcbiAgICAgICAgY29uc3QgdW5zdWJzY3JpYmVycyA9IHN0b3Jlc19hcnJheS5tYXAoKHN0b3JlLCBpKSA9PiBzdWJzY3JpYmUoc3RvcmUsICh2YWx1ZSkgPT4ge1xuICAgICAgICAgICAgdmFsdWVzW2ldID0gdmFsdWU7XG4gICAgICAgICAgICBwZW5kaW5nICY9IH4oMSA8PCBpKTtcbiAgICAgICAgICAgIGlmIChzdGFydGVkKSB7XG4gICAgICAgICAgICAgICAgc3luYygpO1xuICAgICAgICAgICAgfVxuICAgICAgICB9LCAoKSA9PiB7XG4gICAgICAgICAgICBwZW5kaW5nIHw9ICgxIDw8IGkpO1xuICAgICAgICB9KSk7XG4gICAgICAgIHN0YXJ0ZWQgPSB0cnVlO1xuICAgICAgICBzeW5jKCk7XG4gICAgICAgIHJldHVybiBmdW5jdGlvbiBzdG9wKCkge1xuICAgICAgICAgICAgcnVuX2FsbCh1bnN1YnNjcmliZXJzKTtcbiAgICAgICAgICAgIGNsZWFudXAoKTtcbiAgICAgICAgICAgIC8vIFdlIG5lZWQgdG8gc2V0IHRoaXMgdG8gZmFsc2UgYmVjYXVzZSBjYWxsYmFja3MgY2FuIHN0aWxsIGhhcHBlbiBkZXNwaXRlIGhhdmluZyB1bnN1YnNjcmliZWQ6XG4gICAgICAgICAgICAvLyBDYWxsYmFja3MgbWlnaHQgYWxyZWFkeSBiZSBwbGFjZWQgaW4gdGhlIHF1ZXVlIHdoaWNoIGRvZXNuJ3Qga25vdyBpdCBzaG91bGQgbm8gbG9uZ2VyXG4gICAgICAgICAgICAvLyBpbnZva2UgdGhpcyBkZXJpdmVkIHN0b3JlLlxuICAgICAgICAgICAgc3RhcnRlZCA9IGZhbHNlO1xuICAgICAgICB9O1xuICAgIH0pO1xufVxuLyoqXG4gKiBUYWtlcyBhIHN0b3JlIGFuZCByZXR1cm5zIGEgbmV3IG9uZSBkZXJpdmVkIGZyb20gdGhlIG9sZCBvbmUgdGhhdCBpcyByZWFkYWJsZS5cbiAqXG4gKiBAcGFyYW0gc3RvcmUgLSBzdG9yZSB0byBtYWtlIHJlYWRvbmx5XG4gKi9cbmZ1bmN0aW9uIHJlYWRvbmx5KHN0b3JlKSB7XG4gICAgcmV0dXJuIHtcbiAgICAgICAgc3Vic2NyaWJlOiBzdG9yZS5zdWJzY3JpYmUuYmluZChzdG9yZSlcbiAgICB9O1xufVxuXG5leHBvcnQgeyBkZXJpdmVkLCByZWFkYWJsZSwgcmVhZG9ubHksIHdyaXRhYmxlIH07XG4iLCAiaW1wb3J0IHt3cml0YWJsZX0gZnJvbSAnc3ZlbHRlL3N0b3JlJztcblxuLyoqIE92ZXJsYXkgKi9cbmV4cG9ydCBjb25zdCBvdmVybGF5VmlzaWJsZSA9IHdyaXRhYmxlKGZhbHNlKTtcblxuZXhwb3J0IGZ1bmN0aW9uIHNob3dPdmVybGF5KCkge1xuICAgIG92ZXJsYXlWaXNpYmxlLnNldCh0cnVlKTtcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIGhpZGVPdmVybGF5KCkge1xuICAgIG92ZXJsYXlWaXNpYmxlLnNldChmYWxzZSk7XG59XG4iLCAiaW1wb3J0IHsgY3ViaWNJbk91dCwgbGluZWFyLCBjdWJpY091dCB9IGZyb20gJy4uL2Vhc2luZy9pbmRleC5tanMnO1xuaW1wb3J0IHsgc3BsaXRfY3NzX3VuaXQsIGlzX2Z1bmN0aW9uLCBhc3NpZ24gfSBmcm9tICcuLi9pbnRlcm5hbC9pbmRleC5tanMnO1xuXG4vKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqXHJcbkNvcHlyaWdodCAoYykgTWljcm9zb2Z0IENvcnBvcmF0aW9uLlxyXG5cclxuUGVybWlzc2lvbiB0byB1c2UsIGNvcHksIG1vZGlmeSwgYW5kL29yIGRpc3RyaWJ1dGUgdGhpcyBzb2Z0d2FyZSBmb3IgYW55XHJcbnB1cnBvc2Ugd2l0aCBvciB3aXRob3V0IGZlZSBpcyBoZXJlYnkgZ3JhbnRlZC5cclxuXHJcblRIRSBTT0ZUV0FSRSBJUyBQUk9WSURFRCBcIkFTIElTXCIgQU5EIFRIRSBBVVRIT1IgRElTQ0xBSU1TIEFMTCBXQVJSQU5USUVTIFdJVEhcclxuUkVHQVJEIFRPIFRISVMgU09GVFdBUkUgSU5DTFVESU5HIEFMTCBJTVBMSUVEIFdBUlJBTlRJRVMgT0YgTUVSQ0hBTlRBQklMSVRZXHJcbkFORCBGSVRORVNTLiBJTiBOTyBFVkVOVCBTSEFMTCBUSEUgQVVUSE9SIEJFIExJQUJMRSBGT1IgQU5ZIFNQRUNJQUwsIERJUkVDVCxcclxuSU5ESVJFQ1QsIE9SIENPTlNFUVVFTlRJQUwgREFNQUdFUyBPUiBBTlkgREFNQUdFUyBXSEFUU09FVkVSIFJFU1VMVElORyBGUk9NXHJcbkxPU1MgT0YgVVNFLCBEQVRBIE9SIFBST0ZJVFMsIFdIRVRIRVIgSU4gQU4gQUNUSU9OIE9GIENPTlRSQUNULCBORUdMSUdFTkNFIE9SXHJcbk9USEVSIFRPUlRJT1VTIEFDVElPTiwgQVJJU0lORyBPVVQgT0YgT1IgSU4gQ09OTkVDVElPTiBXSVRIIFRIRSBVU0UgT1JcclxuUEVSRk9STUFOQ0UgT0YgVEhJUyBTT0ZUV0FSRS5cclxuKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKiogKi9cclxuXHJcbmZ1bmN0aW9uIF9fcmVzdChzLCBlKSB7XHJcbiAgICB2YXIgdCA9IHt9O1xyXG4gICAgZm9yICh2YXIgcCBpbiBzKSBpZiAoT2JqZWN0LnByb3RvdHlwZS5oYXNPd25Qcm9wZXJ0eS5jYWxsKHMsIHApICYmIGUuaW5kZXhPZihwKSA8IDApXHJcbiAgICAgICAgdFtwXSA9IHNbcF07XHJcbiAgICBpZiAocyAhPSBudWxsICYmIHR5cGVvZiBPYmplY3QuZ2V0T3duUHJvcGVydHlTeW1ib2xzID09PSBcImZ1bmN0aW9uXCIpXHJcbiAgICAgICAgZm9yICh2YXIgaSA9IDAsIHAgPSBPYmplY3QuZ2V0T3duUHJvcGVydHlTeW1ib2xzKHMpOyBpIDwgcC5sZW5ndGg7IGkrKykge1xyXG4gICAgICAgICAgICBpZiAoZS5pbmRleE9mKHBbaV0pIDwgMCAmJiBPYmplY3QucHJvdG90eXBlLnByb3BlcnR5SXNFbnVtZXJhYmxlLmNhbGwocywgcFtpXSkpXHJcbiAgICAgICAgICAgICAgICB0W3BbaV1dID0gc1twW2ldXTtcclxuICAgICAgICB9XHJcbiAgICByZXR1cm4gdDtcclxufVxuXG5mdW5jdGlvbiBibHVyKG5vZGUsIHsgZGVsYXkgPSAwLCBkdXJhdGlvbiA9IDQwMCwgZWFzaW5nID0gY3ViaWNJbk91dCwgYW1vdW50ID0gNSwgb3BhY2l0eSA9IDAgfSA9IHt9KSB7XG4gICAgY29uc3Qgc3R5bGUgPSBnZXRDb21wdXRlZFN0eWxlKG5vZGUpO1xuICAgIGNvbnN0IHRhcmdldF9vcGFjaXR5ID0gK3N0eWxlLm9wYWNpdHk7XG4gICAgY29uc3QgZiA9IHN0eWxlLmZpbHRlciA9PT0gJ25vbmUnID8gJycgOiBzdHlsZS5maWx0ZXI7XG4gICAgY29uc3Qgb2QgPSB0YXJnZXRfb3BhY2l0eSAqICgxIC0gb3BhY2l0eSk7XG4gICAgY29uc3QgW3ZhbHVlLCB1bml0XSA9IHNwbGl0X2Nzc191bml0KGFtb3VudCk7XG4gICAgcmV0dXJuIHtcbiAgICAgICAgZGVsYXksXG4gICAgICAgIGR1cmF0aW9uLFxuICAgICAgICBlYXNpbmcsXG4gICAgICAgIGNzczogKF90LCB1KSA9PiBgb3BhY2l0eTogJHt0YXJnZXRfb3BhY2l0eSAtIChvZCAqIHUpfTsgZmlsdGVyOiAke2Z9IGJsdXIoJHt1ICogdmFsdWV9JHt1bml0fSk7YFxuICAgIH07XG59XG5mdW5jdGlvbiBmYWRlKG5vZGUsIHsgZGVsYXkgPSAwLCBkdXJhdGlvbiA9IDQwMCwgZWFzaW5nID0gbGluZWFyIH0gPSB7fSkge1xuICAgIGNvbnN0IG8gPSArZ2V0Q29tcHV0ZWRTdHlsZShub2RlKS5vcGFjaXR5O1xuICAgIHJldHVybiB7XG4gICAgICAgIGRlbGF5LFxuICAgICAgICBkdXJhdGlvbixcbiAgICAgICAgZWFzaW5nLFxuICAgICAgICBjc3M6IHQgPT4gYG9wYWNpdHk6ICR7dCAqIG99YFxuICAgIH07XG59XG5mdW5jdGlvbiBmbHkobm9kZSwgeyBkZWxheSA9IDAsIGR1cmF0aW9uID0gNDAwLCBlYXNpbmcgPSBjdWJpY091dCwgeCA9IDAsIHkgPSAwLCBvcGFjaXR5ID0gMCB9ID0ge30pIHtcbiAgICBjb25zdCBzdHlsZSA9IGdldENvbXB1dGVkU3R5bGUobm9kZSk7XG4gICAgY29uc3QgdGFyZ2V0X29wYWNpdHkgPSArc3R5bGUub3BhY2l0eTtcbiAgICBjb25zdCB0cmFuc2Zvcm0gPSBzdHlsZS50cmFuc2Zvcm0gPT09ICdub25lJyA/ICcnIDogc3R5bGUudHJhbnNmb3JtO1xuICAgIGNvbnN0IG9kID0gdGFyZ2V0X29wYWNpdHkgKiAoMSAtIG9wYWNpdHkpO1xuICAgIGNvbnN0IFt4VmFsdWUsIHhVbml0XSA9IHNwbGl0X2Nzc191bml0KHgpO1xuICAgIGNvbnN0IFt5VmFsdWUsIHlVbml0XSA9IHNwbGl0X2Nzc191bml0KHkpO1xuICAgIHJldHVybiB7XG4gICAgICAgIGRlbGF5LFxuICAgICAgICBkdXJhdGlvbixcbiAgICAgICAgZWFzaW5nLFxuICAgICAgICBjc3M6ICh0LCB1KSA9PiBgXG5cdFx0XHR0cmFuc2Zvcm06ICR7dHJhbnNmb3JtfSB0cmFuc2xhdGUoJHsoMSAtIHQpICogeFZhbHVlfSR7eFVuaXR9LCAkeygxIC0gdCkgKiB5VmFsdWV9JHt5VW5pdH0pO1xuXHRcdFx0b3BhY2l0eTogJHt0YXJnZXRfb3BhY2l0eSAtIChvZCAqIHUpfWBcbiAgICB9O1xufVxuZnVuY3Rpb24gc2xpZGUobm9kZSwgeyBkZWxheSA9IDAsIGR1cmF0aW9uID0gNDAwLCBlYXNpbmcgPSBjdWJpY091dCwgYXhpcyA9ICd5JyB9ID0ge30pIHtcbiAgICBjb25zdCBzdHlsZSA9IGdldENvbXB1dGVkU3R5bGUobm9kZSk7XG4gICAgY29uc3Qgb3BhY2l0eSA9ICtzdHlsZS5vcGFjaXR5O1xuICAgIGNvbnN0IHByaW1hcnlfcHJvcGVydHkgPSBheGlzID09PSAneScgPyAnaGVpZ2h0JyA6ICd3aWR0aCc7XG4gICAgY29uc3QgcHJpbWFyeV9wcm9wZXJ0eV92YWx1ZSA9IHBhcnNlRmxvYXQoc3R5bGVbcHJpbWFyeV9wcm9wZXJ0eV0pO1xuICAgIGNvbnN0IHNlY29uZGFyeV9wcm9wZXJ0aWVzID0gYXhpcyA9PT0gJ3knID8gWyd0b3AnLCAnYm90dG9tJ10gOiBbJ2xlZnQnLCAncmlnaHQnXTtcbiAgICBjb25zdCBjYXBpdGFsaXplZF9zZWNvbmRhcnlfcHJvcGVydGllcyA9IHNlY29uZGFyeV9wcm9wZXJ0aWVzLm1hcCgoZSkgPT4gYCR7ZVswXS50b1VwcGVyQ2FzZSgpfSR7ZS5zbGljZSgxKX1gKTtcbiAgICBjb25zdCBwYWRkaW5nX3N0YXJ0X3ZhbHVlID0gcGFyc2VGbG9hdChzdHlsZVtgcGFkZGluZyR7Y2FwaXRhbGl6ZWRfc2Vjb25kYXJ5X3Byb3BlcnRpZXNbMF19YF0pO1xuICAgIGNvbnN0IHBhZGRpbmdfZW5kX3ZhbHVlID0gcGFyc2VGbG9hdChzdHlsZVtgcGFkZGluZyR7Y2FwaXRhbGl6ZWRfc2Vjb25kYXJ5X3Byb3BlcnRpZXNbMV19YF0pO1xuICAgIGNvbnN0IG1hcmdpbl9zdGFydF92YWx1ZSA9IHBhcnNlRmxvYXQoc3R5bGVbYG1hcmdpbiR7Y2FwaXRhbGl6ZWRfc2Vjb25kYXJ5X3Byb3BlcnRpZXNbMF19YF0pO1xuICAgIGNvbnN0IG1hcmdpbl9lbmRfdmFsdWUgPSBwYXJzZUZsb2F0KHN0eWxlW2BtYXJnaW4ke2NhcGl0YWxpemVkX3NlY29uZGFyeV9wcm9wZXJ0aWVzWzFdfWBdKTtcbiAgICBjb25zdCBib3JkZXJfd2lkdGhfc3RhcnRfdmFsdWUgPSBwYXJzZUZsb2F0KHN0eWxlW2Bib3JkZXIke2NhcGl0YWxpemVkX3NlY29uZGFyeV9wcm9wZXJ0aWVzWzBdfVdpZHRoYF0pO1xuICAgIGNvbnN0IGJvcmRlcl93aWR0aF9lbmRfdmFsdWUgPSBwYXJzZUZsb2F0KHN0eWxlW2Bib3JkZXIke2NhcGl0YWxpemVkX3NlY29uZGFyeV9wcm9wZXJ0aWVzWzFdfVdpZHRoYF0pO1xuICAgIHJldHVybiB7XG4gICAgICAgIGRlbGF5LFxuICAgICAgICBkdXJhdGlvbixcbiAgICAgICAgZWFzaW5nLFxuICAgICAgICBjc3M6IHQgPT4gJ292ZXJmbG93OiBoaWRkZW47JyArXG4gICAgICAgICAgICBgb3BhY2l0eTogJHtNYXRoLm1pbih0ICogMjAsIDEpICogb3BhY2l0eX07YCArXG4gICAgICAgICAgICBgJHtwcmltYXJ5X3Byb3BlcnR5fTogJHt0ICogcHJpbWFyeV9wcm9wZXJ0eV92YWx1ZX1weDtgICtcbiAgICAgICAgICAgIGBwYWRkaW5nLSR7c2Vjb25kYXJ5X3Byb3BlcnRpZXNbMF19OiAke3QgKiBwYWRkaW5nX3N0YXJ0X3ZhbHVlfXB4O2AgK1xuICAgICAgICAgICAgYHBhZGRpbmctJHtzZWNvbmRhcnlfcHJvcGVydGllc1sxXX06ICR7dCAqIHBhZGRpbmdfZW5kX3ZhbHVlfXB4O2AgK1xuICAgICAgICAgICAgYG1hcmdpbi0ke3NlY29uZGFyeV9wcm9wZXJ0aWVzWzBdfTogJHt0ICogbWFyZ2luX3N0YXJ0X3ZhbHVlfXB4O2AgK1xuICAgICAgICAgICAgYG1hcmdpbi0ke3NlY29uZGFyeV9wcm9wZXJ0aWVzWzFdfTogJHt0ICogbWFyZ2luX2VuZF92YWx1ZX1weDtgICtcbiAgICAgICAgICAgIGBib3JkZXItJHtzZWNvbmRhcnlfcHJvcGVydGllc1swXX0td2lkdGg6ICR7dCAqIGJvcmRlcl93aWR0aF9zdGFydF92YWx1ZX1weDtgICtcbiAgICAgICAgICAgIGBib3JkZXItJHtzZWNvbmRhcnlfcHJvcGVydGllc1sxXX0td2lkdGg6ICR7dCAqIGJvcmRlcl93aWR0aF9lbmRfdmFsdWV9cHg7YFxuICAgIH07XG59XG5mdW5jdGlvbiBzY2FsZShub2RlLCB7IGRlbGF5ID0gMCwgZHVyYXRpb24gPSA0MDAsIGVhc2luZyA9IGN1YmljT3V0LCBzdGFydCA9IDAsIG9wYWNpdHkgPSAwIH0gPSB7fSkge1xuICAgIGNvbnN0IHN0eWxlID0gZ2V0Q29tcHV0ZWRTdHlsZShub2RlKTtcbiAgICBjb25zdCB0YXJnZXRfb3BhY2l0eSA9ICtzdHlsZS5vcGFjaXR5O1xuICAgIGNvbnN0IHRyYW5zZm9ybSA9IHN0eWxlLnRyYW5zZm9ybSA9PT0gJ25vbmUnID8gJycgOiBzdHlsZS50cmFuc2Zvcm07XG4gICAgY29uc3Qgc2QgPSAxIC0gc3RhcnQ7XG4gICAgY29uc3Qgb2QgPSB0YXJnZXRfb3BhY2l0eSAqICgxIC0gb3BhY2l0eSk7XG4gICAgcmV0dXJuIHtcbiAgICAgICAgZGVsYXksXG4gICAgICAgIGR1cmF0aW9uLFxuICAgICAgICBlYXNpbmcsXG4gICAgICAgIGNzczogKF90LCB1KSA9PiBgXG5cdFx0XHR0cmFuc2Zvcm06ICR7dHJhbnNmb3JtfSBzY2FsZSgkezEgLSAoc2QgKiB1KX0pO1xuXHRcdFx0b3BhY2l0eTogJHt0YXJnZXRfb3BhY2l0eSAtIChvZCAqIHUpfVxuXHRcdGBcbiAgICB9O1xufVxuZnVuY3Rpb24gZHJhdyhub2RlLCB7IGRlbGF5ID0gMCwgc3BlZWQsIGR1cmF0aW9uLCBlYXNpbmcgPSBjdWJpY0luT3V0IH0gPSB7fSkge1xuICAgIGxldCBsZW4gPSBub2RlLmdldFRvdGFsTGVuZ3RoKCk7XG4gICAgY29uc3Qgc3R5bGUgPSBnZXRDb21wdXRlZFN0eWxlKG5vZGUpO1xuICAgIGlmIChzdHlsZS5zdHJva2VMaW5lY2FwICE9PSAnYnV0dCcpIHtcbiAgICAgICAgbGVuICs9IHBhcnNlSW50KHN0eWxlLnN0cm9rZVdpZHRoKTtcbiAgICB9XG4gICAgaWYgKGR1cmF0aW9uID09PSB1bmRlZmluZWQpIHtcbiAgICAgICAgaWYgKHNwZWVkID09PSB1bmRlZmluZWQpIHtcbiAgICAgICAgICAgIGR1cmF0aW9uID0gODAwO1xuICAgICAgICB9XG4gICAgICAgIGVsc2Uge1xuICAgICAgICAgICAgZHVyYXRpb24gPSBsZW4gLyBzcGVlZDtcbiAgICAgICAgfVxuICAgIH1cbiAgICBlbHNlIGlmICh0eXBlb2YgZHVyYXRpb24gPT09ICdmdW5jdGlvbicpIHtcbiAgICAgICAgZHVyYXRpb24gPSBkdXJhdGlvbihsZW4pO1xuICAgIH1cbiAgICByZXR1cm4ge1xuICAgICAgICBkZWxheSxcbiAgICAgICAgZHVyYXRpb24sXG4gICAgICAgIGVhc2luZyxcbiAgICAgICAgY3NzOiAoXywgdSkgPT4gYFxuXHRcdFx0c3Ryb2tlLWRhc2hhcnJheTogJHtsZW59O1xuXHRcdFx0c3Ryb2tlLWRhc2hvZmZzZXQ6ICR7dSAqIGxlbn07XG5cdFx0YFxuICAgIH07XG59XG5mdW5jdGlvbiBjcm9zc2ZhZGUoX2EpIHtcbiAgICB2YXIgeyBmYWxsYmFjayB9ID0gX2EsIGRlZmF1bHRzID0gX19yZXN0KF9hLCBbXCJmYWxsYmFja1wiXSk7XG4gICAgY29uc3QgdG9fcmVjZWl2ZSA9IG5ldyBNYXAoKTtcbiAgICBjb25zdCB0b19zZW5kID0gbmV3IE1hcCgpO1xuICAgIGZ1bmN0aW9uIGNyb3NzZmFkZShmcm9tX25vZGUsIG5vZGUsIHBhcmFtcykge1xuICAgICAgICBjb25zdCB7IGRlbGF5ID0gMCwgZHVyYXRpb24gPSBkID0+IE1hdGguc3FydChkKSAqIDMwLCBlYXNpbmcgPSBjdWJpY091dCB9ID0gYXNzaWduKGFzc2lnbih7fSwgZGVmYXVsdHMpLCBwYXJhbXMpO1xuICAgICAgICBjb25zdCBmcm9tID0gZnJvbV9ub2RlLmdldEJvdW5kaW5nQ2xpZW50UmVjdCgpO1xuICAgICAgICBjb25zdCB0byA9IG5vZGUuZ2V0Qm91bmRpbmdDbGllbnRSZWN0KCk7XG4gICAgICAgIGNvbnN0IGR4ID0gZnJvbS5sZWZ0IC0gdG8ubGVmdDtcbiAgICAgICAgY29uc3QgZHkgPSBmcm9tLnRvcCAtIHRvLnRvcDtcbiAgICAgICAgY29uc3QgZHcgPSBmcm9tLndpZHRoIC8gdG8ud2lkdGg7XG4gICAgICAgIGNvbnN0IGRoID0gZnJvbS5oZWlnaHQgLyB0by5oZWlnaHQ7XG4gICAgICAgIGNvbnN0IGQgPSBNYXRoLnNxcnQoZHggKiBkeCArIGR5ICogZHkpO1xuICAgICAgICBjb25zdCBzdHlsZSA9IGdldENvbXB1dGVkU3R5bGUobm9kZSk7XG4gICAgICAgIGNvbnN0IHRyYW5zZm9ybSA9IHN0eWxlLnRyYW5zZm9ybSA9PT0gJ25vbmUnID8gJycgOiBzdHlsZS50cmFuc2Zvcm07XG4gICAgICAgIGNvbnN0IG9wYWNpdHkgPSArc3R5bGUub3BhY2l0eTtcbiAgICAgICAgcmV0dXJuIHtcbiAgICAgICAgICAgIGRlbGF5LFxuICAgICAgICAgICAgZHVyYXRpb246IGlzX2Z1bmN0aW9uKGR1cmF0aW9uKSA/IGR1cmF0aW9uKGQpIDogZHVyYXRpb24sXG4gICAgICAgICAgICBlYXNpbmcsXG4gICAgICAgICAgICBjc3M6ICh0LCB1KSA9PiBgXG5cdFx0XHRcdG9wYWNpdHk6ICR7dCAqIG9wYWNpdHl9O1xuXHRcdFx0XHR0cmFuc2Zvcm0tb3JpZ2luOiB0b3AgbGVmdDtcblx0XHRcdFx0dHJhbnNmb3JtOiAke3RyYW5zZm9ybX0gdHJhbnNsYXRlKCR7dSAqIGR4fXB4LCR7dSAqIGR5fXB4KSBzY2FsZSgke3QgKyAoMSAtIHQpICogZHd9LCAke3QgKyAoMSAtIHQpICogZGh9KTtcblx0XHRcdGBcbiAgICAgICAgfTtcbiAgICB9XG4gICAgZnVuY3Rpb24gdHJhbnNpdGlvbihpdGVtcywgY291bnRlcnBhcnRzLCBpbnRybykge1xuICAgICAgICByZXR1cm4gKG5vZGUsIHBhcmFtcykgPT4ge1xuICAgICAgICAgICAgaXRlbXMuc2V0KHBhcmFtcy5rZXksIG5vZGUpO1xuICAgICAgICAgICAgcmV0dXJuICgpID0+IHtcbiAgICAgICAgICAgICAgICBpZiAoY291bnRlcnBhcnRzLmhhcyhwYXJhbXMua2V5KSkge1xuICAgICAgICAgICAgICAgICAgICBjb25zdCBvdGhlcl9ub2RlID0gY291bnRlcnBhcnRzLmdldChwYXJhbXMua2V5KTtcbiAgICAgICAgICAgICAgICAgICAgY291bnRlcnBhcnRzLmRlbGV0ZShwYXJhbXMua2V5KTtcbiAgICAgICAgICAgICAgICAgICAgcmV0dXJuIGNyb3NzZmFkZShvdGhlcl9ub2RlLCBub2RlLCBwYXJhbXMpO1xuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICAvLyBpZiB0aGUgbm9kZSBpcyBkaXNhcHBlYXJpbmcgYWx0b2dldGhlclxuICAgICAgICAgICAgICAgIC8vIChpLmUuIHdhc24ndCBjbGFpbWVkIGJ5IHRoZSBvdGhlciBsaXN0KVxuICAgICAgICAgICAgICAgIC8vIHRoZW4gd2UgbmVlZCB0byBzdXBwbHkgYW4gb3V0cm9cbiAgICAgICAgICAgICAgICBpdGVtcy5kZWxldGUocGFyYW1zLmtleSk7XG4gICAgICAgICAgICAgICAgcmV0dXJuIGZhbGxiYWNrICYmIGZhbGxiYWNrKG5vZGUsIHBhcmFtcywgaW50cm8pO1xuICAgICAgICAgICAgfTtcbiAgICAgICAgfTtcbiAgICB9XG4gICAgcmV0dXJuIFtcbiAgICAgICAgdHJhbnNpdGlvbih0b19zZW5kLCB0b19yZWNlaXZlLCBmYWxzZSksXG4gICAgICAgIHRyYW5zaXRpb24odG9fcmVjZWl2ZSwgdG9fc2VuZCwgdHJ1ZSlcbiAgICBdO1xufVxuXG5leHBvcnQgeyBibHVyLCBjcm9zc2ZhZGUsIGRyYXcsIGZhZGUsIGZseSwgc2NhbGUsIHNsaWRlIH07XG4iLCAiPHNjcmlwdD5cblxuICAgIGltcG9ydCB7b3ZlcmxheVZpc2libGV9IGZyb20gJy4vc3RvcmUnXG4gICAgaW1wb3J0IHtmYWRlLH0gZnJvbSAnc3ZlbHRlL3RyYW5zaXRpb24nO1xuPC9zY3JpcHQ+XG5cbnsjaWYgJG92ZXJsYXlWaXNpYmxlIH1cbiAgICA8ZGl2IGNsYXNzPVwid2FpbHMtcmVjb25uZWN0LW92ZXJsYXlcIiB0cmFuc2l0aW9uOmZhZGU9XCJ7eyBkdXJhdGlvbjogMzAwIH19XCI+XG4gICAgICAgIDxkaXYgY2xhc3M9XCJ3YWlscy1yZWNvbm5lY3Qtb3ZlcmxheS1jb250ZW50XCI+XG4gICAgICAgICAgICA8ZGl2IGNsYXNzPVwid2FpbHMtcmVjb25uZWN0LW92ZXJsYXktbG9hZGluZ3NwaW5uZXJcIj48L2Rpdj5cbiAgICAgICAgPC9kaXY+XG4gICAgPC9kaXY+XG57L2lmfVxuXG48c3R5bGU+XG4gICAgLndhaWxzLXJlY29ubmVjdC1vdmVybGF5IHtcbiAgICAgICAgcG9zaXRpb246IGZpeGVkO1xuICAgICAgICB0b3A6IDA7XG4gICAgICAgIGxlZnQ6IDA7XG4gICAgICAgIHdpZHRoOiAxMDAlO1xuICAgICAgICBoZWlnaHQ6IDEwMCU7XG4gICAgICAgIGJhY2tkcm9wLWZpbHRlcjogYmx1cigycHgpIHNhdHVyYXRlKDAlKSBjb250cmFzdCg1MCUpIGJyaWdodG5lc3MoMjUlKTtcbiAgICAgICAgei1pbmRleDogOTk5OTk5XG4gICAgfVxuXG4gICAgLndhaWxzLXJlY29ubmVjdC1vdmVybGF5LWNvbnRlbnQge1xuICAgICAgICBwb3NpdGlvbjogcmVsYXRpdmU7XG4gICAgICAgIHRvcDogNTAlO1xuICAgICAgICB0cmFuc2Zvcm06IHRyYW5zbGF0ZVkoLTUwJSk7XG4gICAgICAgIG1hcmdpbjogMDtcbiAgICAgICAgYmFja2dyb3VuZC1pbWFnZTogdXJsKGRhdGE6aW1hZ2UvcG5nO2Jhc2U2NCxpVkJPUncwS0dnb0FBQUFOU1VoRVVnQUFBRXNBQUFBN0NBTUFBQUFFc29jWkFBQUM5MUJNVkVVQUFBQ3pRMFBqTWpMa01qTFpMUzdYTFMrdkpDamtNaktsRXg2dUd5SGpNREdpRng3R0p5ckFJU2pVS3kzbU16UGxNakxqTXpPc0d5REtKaXJrTWpLNkh5WG1NakxnTURDNklpTGNNakxVTEMzTUp5clJLU3krSWlibU16UG1Naks3SVNYbE1qTElKaW16SFNMa01qS3RHaUhaTEM3QklpZmdNRENwR1NERklpdmNMeSt5SFNLb0dSK2VGQnpOS0N2bE1qS3hIU1BrTVRLeEhTTG1NakxLSnlxNUlDWERKQ2U2SVNYZEx6RGtNakxtTXpQRkpTbTJIeVRsTVRMaE1ER3lIU0tVRUJtaEZ4MjRIeVRDSkNqSEppampNek9pRmg3bU1qSjZCaERhTERDdUd5T0tBQmpuTXpQR0ppbkpKaXF1SENHRUNoU21HQi9wTXpPaUZoN1ZLeTNPS0N1MUhpU3ZIQ0xqTVRMTUtDckJJeWVJQ3hXeEhDTERJeWpTS2l6Qkl5aCtDQk85SVNhNklTV0RDaFM5SWllMUh5WFZMQzdGSlNyTEtDcmxNakxpTVRHUERoaWNGUnl3R3lLWEZCdWhGeDEvQnhPN0lpWGtNVEdlRkJ4OEJ4TGtNVEduR1IvR0pDaTRJQ1dzR3lHSkR4WFNMUzJ5R2lIU0tpM0NKQ2ZuTXpQUUtpeUVDUlRLSmlxNklTV1VFUnEvSXllMEhpUERKQ2pHSlNtNklDYVBEeGlURUJyZEx5KzNIeVhTS2l5MEh5T1FFQmk0SUNXaEZoMStDQk85SWllT0RoZlNLeXpXTEMyTERoaDhCeEhLS0NxN0lTV2FGQnprTXpQcU5EVFRMQzNFSlNpSERCYWNFeHl2R3lPMUh5VFBLQ3krSWllb0dTQzdJU2FWRWhyTUtDdlFLeXVzR3lHMEhpS0FDQlBJSlNxL0pDYUFCeFI1QlJMRUpDbmtNelBKSmluRUppbVBEUloyQlJLcUh4L2pNakxuTXpQZ01ESFVMQzNOS1N2UUtTenNORFRXTFM3U0t5eTNIeVRLSnlyREpTamJMekRZTEM2bUdCL0dKU25WTEM2MUhpUExLQ3JISlNtL0l5ZThJaWE2SUNXekhTS3hIQ0xhTGkvUEtTdXBHUis3SUNYcE16UGJMaS9JSmluSkpTbXNHeUdyR2lDa0Z4NlBEaGVKQ3hhRkNoWEJJeWZBSWllU0R4bUJDQlBsTWpMZUx6RGRMekM1SHlTTURSZStJU1d2R3lHY0ZCelNLU3pQSnl2TUp5ckVKQ2pESXllZkZSeVdFUnJpTURIVUtpeS9JU2FaRXh2ME5qYndOVFh1TkRUck16TUkwYyt5QUFBQXUzUlNUbE1BQThIUi9nd0dnQWorTUVwR0NzQytoR3BqUWpZbkl4Z1dCZnp4N3VyaXpNckZxcUIxYkY4M0toc1IvZno4Ky9yNStmWHY3dW5aMXRDK3Q2bW1vcHFLZFcxbllWcFZSalVlSGhJUUJQcjU5L2IyOC9IeDhPRGczTnZVdzhPL3ZLZWltNWFOaW9pRGduMXZaV05qWDF4VVUxSlBUVVZGUFQwOE1pNHFKeUloL1B2NysvbjQrUGYzOWZUMDgvRHU3ZWZuNXVYajR1SGExOVhOd3NHL3ZycTJ0YlN1cmFtbG5weVlrcEdOaUlaK2VuUnJhR1ZqVlZCS096Z2hkanpSc0FBQUJKVkpSRUZVV01QdGxsVlFHMUVZaFRjMEFTcG9vYlMwRkN1bFVIZDNvVWpkM2QzZDNkM2QzZDJiN0NZaG5rQkNDSEdERUlLN1ZoNTZkME5wT2d3a1lmTFF6dkE5WnJMZm5QdmZjKzh1VkVzdC95aGVCSnVwM055YTJNalU2cGEvaldMWnR4alhwWkZ0VkI0dVZOSTZtNWdJcnVOa1ZGZWJxSWI1VWcyeW00VElFTS9ndFVPR2JnNjEzb0J6akF6WkZyWitsWHUvM1RJaU1YWFM1TTZIVHZyTkhlTHBaTEVoNnN1R05XOWZ6Wjl6ZC9xVmkyZU9IeWdxaTVjREU1R1VySm9jT05nenlxbzBVWE5TVWxLU0VoTXp0RnF0WHE5dk54SW1BbVMzZzdZNlFsYmpkQldWR1czNmp0NHdER1RVWGpVc2FmaDV6SldSa2RGdVpHdFdHbkNSbWcrSGFzaUdNVUNsVFR6VzBadVZnTGxHRElQTTRMaGkwSXJWcSt0djJoUzIxZk5yU09OUWdwTTlEc0o0dDNmTTlQa3ZKdUtqMlpqclp3dklMS3ZhU1RnY2lVU2lyanQ2ZE9mT3B5ZDE2OWJEYjlyTU93RjlIajRPRDEwMGdZMFlYWWIyOTlianpNcnFqOWRvTkJ5SldsVlhGQjlEVDVkbUp1dnkrY3E4M0p5dVM2YXlFWVNIdWxLTDhkbUZuQmtyQ2VabEhLTXJDNVhSaFhHQ1pCMlR5MWZrbGVSUWFNQ0ZUMkRCc0VhZnpSRkp1Ny8yTWljYkt5blBoUVVETGlad01XTEpaS05Mem9MYkpCWVZjdXJTbWJtbityY3lKOHZDTWdtbG1hVzZnbnd1bi8rM0M5NlZwQVV1RVQxWmdSUjM2cjJ4V2xuWVNuZjNvS0FCQTE0dVhERHZ5ZHhIczZjcFRWMXAzaGxKMnJKQ2lVaklaQ0J5SXRYZzhzSEppanV2VDY0Q3VNVEFCVVl2YjZOTjFKZHAxUEg3RDdmM2JvMmVTNUt2VzRSSnI3YXRXVDV3NE1CQmc5emRCdzkrMzdCUzdRSW9GUzVXbklhajEyZHIxREVYRmdkdnI0Zmg0ZUZsK3Uvd3o4dWYzampIaWM4czRETDJEYWwwSUFOeVVCZUNSQ2N3T0JKVjI2SnNqU3BHd0hWdVNhaTY5anZxRCtqcjU2T2d0S3kwekFBSzVtTFRWQktWS0w1dE50aEdBUjlKbmVKUS9iRnNITnp5K1U3SWxDWVJPeHRNcElqUjBjZW9RVm5vd3JhY0xMcEFRV0VUcVYzNjFiUG9GbzNjRWJ6MnpZTFpNN3QzSFdYY3htaUJPZ3R0UzF5Y1drVFhNV2g0bUdpZ2R1ZzlERmR0dHFDRmdUTjZuRDBxMVhFVlNvQ3hFanlGQ2kyZU5DNlo2OU1SVklJbUo2SlFTZjVnY0ZWQ3VGK2FEaENhMUY2TUpGRGFpTkJRQWgyVE1mV0JqaG1Mc0F4VWpHL2ZtanMwcWpKY2s4RDBHUEJjdVV1WlcxTFMvdElzUHpxbVF0MTdQdlpRa25sd25mNHRIREJjKzd0NVZWM1FRQ2tkYytVcjgvaGRyejBidXQwUkN1bVdpWWJpS21MSjdFVmJSb21qNFE3K3k1d3NhWHZmVEdGcFFjSEI3bjJXYkc0TUdkbml3MlRtOHhsNVlocjdNclNZSFEzdWFtcHoxMGFXeUh5dXp4dnFhVy82VzRNalhBVUQzUVYyYXc5N1p4aEdqeENvaFlmNVRwVEhNWFUxQmJzQXVvRm5rUnlnVmllSUdBYnFpRjdyckg0cmZXcEtKb3VCQ3R5SEpGOGN0RXlHdWJCYStDNk5zTVlFVW9uSkZJVEhacVdCeFhVQTEyRHY3NlRmL1BnT0JtZU5paUxHMXBjS28xSEFxOGpMcFk0SlUxeVdFaXhWTmFPZ29SSkFLQlNaSFRaVFUrd0pPTXRVRFp2bFZJVEM2RlRsa3N5ckVCb1BIWHB4eGJ6ZGFxemlnVXRWRGtKVklPdFZROVVFT1I0VkdVaC9rSFdxMGVkSjZDeG5aK2VlUFh2YTJiblkvY0YvSTFSTExmOHZ2d0RBTmRNU01lZ3hjQUFBQUFCSlJVNUVya0pnZ2c9PSk7XG4gICAgICAgIGJhY2tncm91bmQtcmVwZWF0OiBuby1yZXBlYXQ7XG4gICAgICAgIGJhY2tncm91bmQtcG9zaXRpb246IGNlbnRlclxuICAgIH1cblxuICAgIC53YWlscy1yZWNvbm5lY3Qtb3ZlcmxheS1sb2FkaW5nc3Bpbm5lciB7XG4gICAgICAgIHBvaW50ZXItZXZlbnRzOiBub25lO1xuICAgICAgICB3aWR0aDogMi41ZW07XG4gICAgICAgIGhlaWdodDogMi41ZW07XG4gICAgICAgIGJvcmRlcjogLjRlbSBzb2xpZCB0cmFuc3BhcmVudDtcbiAgICAgICAgYm9yZGVyLWNvbG9yOiAjZjAwICNlZWUwICNmMDAgI2VlZTA7XG4gICAgICAgIGJvcmRlci1yYWRpdXM6IDUwJTtcbiAgICAgICAgYW5pbWF0aW9uOiBsb2FkaW5nc3BpbiAxcyBsaW5lYXIgaW5maW5pdGU7XG4gICAgICAgIG1hcmdpbjogYXV0bztcbiAgICAgICAgcGFkZGluZzogMi41ZW1cbiAgICB9XG5cbiAgICBAa2V5ZnJhbWVzIGxvYWRpbmdzcGluIHtcbiAgICAgICAgMTAwJSB7XG4gICAgICAgICAgICB0cmFuc2Zvcm06IHJvdGF0ZSgzNjBkZWcpO1xuICAgICAgICB9XG4gICAgfVxuXG48L3N0eWxlPiIsICIvKlxuIF8gICAgICAgX18gICAgICBfIF9fXG58IHwgICAgIC8gL19fXyBfKF8pIC9fX19fXG58IHwgL3wgLyAvIF9fIGAvIC8gLyBfX18vXG58IHwvIHwvIC8gL18vIC8gLyAoX18gIClcbnxfXy98X18vXFxfXyxfL18vXy9fX19fL1xuVGhlIGVsZWN0cm9uIGFsdGVybmF0aXZlIGZvciBHb1xuKGMpIExlYSBBbnRob255IDIwMTktcHJlc2VudFxuKi9cbi8qIGpzaGludCBlc3ZlcnNpb246IDYgKi9cblxuaW1wb3J0IHtsb2d9IGZyb20gXCIuL2xvZ1wiO1xuaW1wb3J0IE92ZXJsYXkgZnJvbSBcIi4vT3ZlcmxheS5zdmVsdGVcIjtcbmltcG9ydCB7aGlkZU92ZXJsYXksIHNob3dPdmVybGF5fSBmcm9tIFwiLi9zdG9yZVwiO1xuaW1wb3J0IHsgbmFub2lkIH0gZnJvbSAnbmFub2lkL25vbi1zZWN1cmUnO1xuXG5sZXQgY29tcG9uZW50cyA9IHt9O1xubGV0IHNvdXJjZSA9IG51bGw7XG5cbmZ1bmN0aW9uIGhhbmRsZUNhbGxiYWNrKGUpIHtcbiAgICBjb25zdCBwYXlsb2FkID0gSlNPTi5wYXJzZShlLmRhdGEpO1xuICAgIF93YWlscy5jYWxsQ2FsbGJhY2socGF5bG9hZC5pZCxcbiAgICAgICAgICAgICAgICAgICAgICAgIHBheWxvYWQucmVzdWx0LFxuICAgICAgICAgICAgICAgICAgICAgICAgdHJ1ZSk7XG59XG5cbmZ1bmN0aW9uIGhhbmRsZUNhbGxiYWNrRXJyb3IoZSkge1xuICAgIGNvbnN0IHBheWxvYWQgPSBKU09OLnBhcnNlKGUuZGF0YSk7XG4gICAgX3dhaWxzLmNhbGxFcnJvckNhbGxiYWNrKHBheWxvYWQuaWQsIHBheWxvYWQucmVzdWx0KTtcbn1cblxuZnVuY3Rpb24gaGFuZGxlRGlhbG9nKGUpIHtcbiAgICBjb25zdCBwYXlsb2FkID0gSlNPTi5wYXJzZShlLmRhdGEpO1xuICAgIF93YWlscy5kaWFsb2dDYWxsYmFjayhwYXlsb2FkLmlkLFxuICAgICAgICAgICAgICAgICAgICAgICAgICBwYXlsb2FkLnJlc3VsdCxcbiAgICAgICAgICAgICAgICAgICAgICAgICAgdHJ1ZSk7XG59XG5cbmZ1bmN0aW9uIGhhbmRsZURpYWxvZ0Vycm9yKGUpIHtcbiAgICBjb25zdCBwYXlsb2FkID0gSlNPTi5wYXJzZShlLmRhdGEpO1xuICAgIF93YWlscy5kaWFsb2dFcnJvckNhbGxiYWNrKHBheWxvYWQuaWQsIHBheWxvYWQucmVzdWx0KTtcbn1cblxuZnVuY3Rpb24gaGFuZGxlV2FpbHNFdmVudChlKSB7XG4gICAgY29uc29sZS5sb2coXCJXYWlsc0V2ZW50OiBcIiArIGUuZGF0YSlcbn1cblxud2luZG93LmFkZEV2ZW50TGlzdGVuZXIoJ0RPTUNvbnRlbnRMb2FkZWQnLCAoKSA9PiB7XG4gICAgY29tcG9uZW50cy5vdmVybGF5ID0gbmV3IE92ZXJsYXkoe1xuICAgICAgICB0YXJnZXQ6IGRvY3VtZW50LmJvZHksXG4gICAgICAgIGFuY2hvcjogZG9jdW1lbnQucXVlcnlTZWxlY3RvcignI3dhaWxzLXNwaW5uZXInKSxcbiAgICB9KTtcbiAgICBjb25uZWN0KCk7XG59KTtcblxud2luZG93Lm9uYmVmb3JldW5sb2FkID0gZnVuY3Rpb24gKCkge1xuICAgIGlmIChzb3VyY2UpIHtcbiAgICAgICAgc291cmNlLm9uY2xvc2UgPSBmdW5jdGlvbiAoKSB7IH07XG4gICAgICAgIHNvdXJjZS5jbG9zZSgpO1xuICAgICAgICBzb3VyY2UgPSBudWxsO1xuICAgIH1cbn07XG5cbi8vIEhhbmRsZXMgc3NlIGNvbm5lY3Rpb25zXG5mdW5jdGlvbiBoYW5kbGVDb25uZWN0KGUpIHtcbiAgICBoaWRlT3ZlcmxheSgpO1xuICAgIHNvdXJjZS5vbmNsb3NlID0gaGFuZGxlRGlzY29ubmVjdDtcblxufVxuXG4vLyBIYW5kbGVzIFNTRSBkaXNjb25uZWN0c1xuLy8gRXZlbnRTb3VyY2Ugd2lsbCBhdHRlbXB0IHRvIHJlY29ubmVjdCBvbiBpdCdzIG93blxuZnVuY3Rpb24gaGFuZGxlRGlzY29ubmVjdChlKSB7XG4gICAgaWYgKHRoaXMucmVhZHlTdGF0ZSA9PSBFdmVudFNvdXJjZS5DT05ORUNUSU5HKSB7XG4gICAgICAgIHNob3dPdmVybGF5KCk7XG4gICAgfSBlbHNlIHtcbiAgICAgICAgY29uc29sZS5sb2coZSk7XG4gICAgfVxufVxuXG5mdW5jdGlvbiBfY29ubmVjdCgpIHtcbiAgICBpZiAoc291cmNlID09IG51bGwpIHtcbiAgICAgICAgc291cmNlID0gbmV3IEV2ZW50U291cmNlKFwiL3NlcnZlci9ldmVudHM/Y2xpZW50SWQ9XCIrd2FpbHMuY2xpZW50SWQpO1xuICAgICAgICBzb3VyY2Uub25vcGVuID0gaGFuZGxlQ29ubmVjdDtcbiAgICAgICAgc291cmNlLm9uZXJyb3IgPSBoYW5kbGVEaXNjb25uZWN0O1xuICAgICAgICBzb3VyY2UuYWRkRXZlbnRMaXN0ZW5lcignY2InLCBoYW5kbGVDYWxsYmFjayk7XG4gICAgICAgIHNvdXJjZS5hZGRFdmVudExpc3RlbmVyKCdjYmVycm9yJywgaGFuZGxlQ2FsbGJhY2tFcnJvcik7XG4gICAgICAgIHNvdXJjZS5hZGRFdmVudExpc3RlbmVyKCdkbGdjYicsIGhhbmRsZURpYWxvZyk7XG4gICAgICAgIHNvdXJjZS5hZGRFdmVudExpc3RlbmVyKCdkbGdjYmVycm9yJywgaGFuZGxlRGlhbG9nRXJyb3IpO1xuICAgICAgICBzb3VyY2UuYWRkRXZlbnRMaXN0ZW5lcignd2FpbHNldmVudCcsIGhhbmRsZVdhaWxzRXZlbnQpO1xuICAgIH1cbn1cblxuLy8gVHJ5IHRvIGNvbm5lY3QgdG8gdGhlIGJhY2tlbmQgZXZlcnkgLjVzXG5mdW5jdGlvbiBjb25uZWN0KCkge1xuICAgIF9jb25uZWN0KCk7XG59XG4iXSwKICAibWFwcGluZ3MiOiAiTUFBQSxZQUFnQixFQUNoQixHQUFNLEdBQVcsR0FBSyxFQWlCdEIsV0FBYSxFQUFJLENBQ2IsTUFBTyxLQUVYLGFBQXdCLENBQ3BCLE1BQU8sUUFBTyxPQUFPLE1BRXpCLFdBQWlCLEVBQUssQ0FDbEIsRUFBSSxRQUFRLEdBRWhCLFdBQXFCLEVBQU8sQ0FDeEIsTUFBTyxPQUFPLElBQVUsV0FFNUIsV0FBd0IsRUFBRyxFQUFHLENBQzFCLE1BQU8sSUFBSyxFQUFJLEdBQUssRUFBSSxJQUFNLEdBQU8sR0FBSyxNQUFPLElBQU0sVUFBYSxNQUFPLElBQU0sV0FhdEYsWUFBa0IsRUFBSyxDQUNuQixNQUFPLFFBQU8sS0FBSyxHQUFLLFNBQVcsRUFPdkMsWUFBbUIsS0FBVSxFQUFXLENBQ3BDLEdBQUksR0FBUyxLQUNULE1BQU8sR0FFWCxHQUFNLEdBQVEsRUFBTSxVQUFVLEdBQUcsR0FDakMsTUFBTyxHQUFNLFlBQWMsSUFBTSxFQUFNLGNBQWdCLEVBTzNELFlBQTZCLEVBQVcsRUFBTyxFQUFVLENBQ3JELEVBQVUsR0FBRyxXQUFXLEtBQUssR0FBVSxFQUFPLElBb0dsRCxHQUFNLElBQVksTUFBTyxTQUFXLFlBQ2hDLEdBQU0sR0FDSixJQUFNLE9BQU8sWUFBWSxNQUN6QixJQUFNLEtBQUssTUFDYixFQUFNLEdBQVksR0FBTSxzQkFBc0IsR0FBTSxFQVN4RCxHQUFNLEdBQVEsR0FBSSxLQUNsQixZQUFtQixFQUFLLENBQ3BCLEVBQU0sUUFBUSxHQUFRLENBQ2xCLEFBQUssRUFBSyxFQUFFLElBQ1IsR0FBTSxPQUFPLEdBQ2IsRUFBSyxPQUdULEVBQU0sT0FBUyxHQUNmLEVBQUksSUFZWixZQUFjLEVBQVUsQ0FDcEIsR0FBSSxHQUNKLE1BQUksR0FBTSxPQUFTLEdBQ2YsRUFBSSxJQUNELENBQ0gsUUFBUyxHQUFJLFNBQVEsR0FBVyxDQUM1QixFQUFNLElBQUksRUFBTyxDQUFFLEVBQUcsRUFBVSxFQUFHLE1BRXZDLE9BQVEsQ0FDSixFQUFNLE9BQU8sS0FPekIsR0FBSSxJQUFlLEdBQ25CLGFBQTJCLENBQ3ZCLEdBQWUsR0FFbkIsYUFBeUIsQ0FDckIsR0FBZSxHQThGbkIsWUFBZ0IsRUFBUSxFQUFNLENBQzFCLEVBQU8sWUFBWSxHQUV2QixZQUF1QixFQUFRLEVBQWdCLEVBQVEsQ0FDbkQsR0FBTSxHQUFtQixFQUFtQixHQUM1QyxHQUFJLENBQUMsRUFBaUIsZUFBZSxHQUFpQixDQUNsRCxHQUFNLEdBQVEsRUFBUSxTQUN0QixFQUFNLEdBQUssRUFDWCxFQUFNLFlBQWMsRUFDcEIsR0FBa0IsRUFBa0IsSUFHNUMsV0FBNEIsRUFBTSxDQUM5QixHQUFJLENBQUMsRUFDRCxNQUFPLFVBQ1gsR0FBTSxHQUFPLEVBQUssWUFBYyxFQUFLLGNBQWdCLEVBQUssY0FDMUQsTUFBSSxJQUFRLEVBQUssS0FDTixFQUVKLEVBQUssY0FFaEIsWUFBaUMsRUFBTSxDQUNuQyxHQUFNLEdBQWdCLEVBQVEsU0FDOUIsVUFBa0IsRUFBbUIsR0FBTyxHQUNyQyxFQUFjLE1BRXpCLFlBQTJCLEVBQU0sRUFBTyxDQUNwQyxVQUFPLEVBQUssTUFBUSxFQUFNLEdBQ25CLEVBQU0sTUEwQmpCLFdBQWdCLEVBQVEsRUFBTSxFQUFRLENBQ2xDLEVBQU8sYUFBYSxFQUFNLEdBQVUsTUFVeEMsV0FBZ0IsRUFBTSxDQUNsQixBQUFJLEVBQUssWUFDTCxFQUFLLFdBQVcsWUFBWSxHQVNwQyxXQUFpQixFQUFNLENBQ25CLE1BQU8sVUFBUyxjQUFjLEdBb0JsQyxZQUFjLEVBQU0sQ0FDaEIsTUFBTyxVQUFTLGVBQWUsR0FLbkMsYUFBaUIsQ0FDYixNQUFPLElBQUssSUE0Q2hCLFlBQWMsRUFBTSxFQUFXLEVBQU8sQ0FDbEMsQUFBSSxHQUFTLEtBQ1QsRUFBSyxnQkFBZ0IsR0FDaEIsRUFBSyxhQUFhLEtBQWUsR0FDdEMsRUFBSyxhQUFhLEVBQVcsR0FnSHJDLFlBQWtCLEVBQVMsQ0FDdkIsTUFBTyxPQUFNLEtBQUssRUFBUSxZQXlQOUIsWUFBc0IsRUFBTSxFQUFRLENBQUUsVUFBVSxHQUFPLGFBQWEsSUFBVSxHQUFJLENBQzlFLEdBQU0sR0FBSSxTQUFTLFlBQVksZUFDL0IsU0FBRSxnQkFBZ0IsRUFBTSxFQUFTLEVBQVksR0FDdEMsRUF5R1gsR0FBTSxHQUFpQixHQUFJLEtBQ3ZCLEVBQVMsRUFFYixZQUFjLEVBQUssQ0FDZixHQUFJLEdBQU8sS0FDUCxFQUFJLEVBQUksT0FDWixLQUFPLEtBQ0gsRUFBUyxJQUFRLEdBQUssRUFBUSxFQUFJLFdBQVcsR0FDakQsTUFBTyxLQUFTLEVBRXBCLFlBQWtDLEVBQUssRUFBTSxDQUN6QyxHQUFNLEdBQU8sQ0FBRSxXQUFZLEdBQXdCLEdBQU8sTUFBTyxJQUNqRSxTQUFlLElBQUksRUFBSyxHQUNqQixFQUVYLFlBQXFCLEVBQU0sRUFBRyxFQUFHLEVBQVUsRUFBTyxFQUFNLEVBQUksRUFBTSxFQUFHLENBQ2pFLEdBQU0sR0FBTyxPQUFTLEVBQ2xCLEVBQVk7QUFBQSxFQUNoQixPQUFTLEdBQUksRUFBRyxHQUFLLEVBQUcsR0FBSyxFQUFNLENBQy9CLEdBQU0sR0FBSSxFQUFLLEdBQUksR0FBSyxFQUFLLEdBQzdCLEdBQWEsRUFBSSxJQUFNLEtBQUssRUFBRyxFQUFHLEVBQUk7QUFBQSxFQUUxQyxHQUFNLEdBQU8sRUFBWSxTQUFTLEVBQUcsRUFBRyxFQUFJO0FBQUEsR0FDdEMsRUFBTyxZQUFZLEdBQUssTUFBUyxJQUNqQyxFQUFNLEVBQW1CLEdBQ3pCLENBQUUsYUFBWSxTQUFVLEVBQWUsSUFBSSxJQUFRLEdBQXlCLEVBQUssR0FDdkYsQUFBSyxFQUFNLElBQ1AsR0FBTSxHQUFRLEdBQ2QsRUFBVyxXQUFXLGNBQWMsS0FBUSxJQUFRLEVBQVcsU0FBUyxTQUU1RSxHQUFNLEdBQVksRUFBSyxNQUFNLFdBQWEsR0FDMUMsU0FBSyxNQUFNLFVBQVksR0FBRyxFQUFZLEdBQUcsTUFBZ0IsS0FBSyxLQUFRLGNBQXFCLGFBQzNGLEdBQVUsRUFDSCxFQUVYLFlBQXFCLEVBQU0sRUFBTSxDQUM3QixHQUFNLEdBQVksR0FBSyxNQUFNLFdBQWEsSUFBSSxNQUFNLE1BQzlDLEVBQU8sRUFBUyxPQUFPLEVBQ3ZCLEdBQVEsRUFBSyxRQUFRLEdBQVEsRUFDN0IsR0FBUSxFQUFLLFFBQVEsY0FBZ0IsSUFFckMsRUFBVSxFQUFTLE9BQVMsRUFBSyxPQUN2QyxBQUFJLEdBQ0EsR0FBSyxNQUFNLFVBQVksRUFBSyxLQUFLLE1BQ2pDLEdBQVUsRUFDTCxHQUNELE1BR1osYUFBdUIsQ0FDbkIsRUFBSSxJQUFNLENBQ04sQUFBSSxHQUVKLEdBQWUsUUFBUSxHQUFRLENBQzNCLEdBQU0sQ0FBRSxhQUFjLEVBQUssV0FFM0IsQUFBSSxHQUNBLEVBQU8sS0FFZixFQUFlLFdBeUV2QixHQUFJLEdBQ0osV0FBK0IsRUFBVyxDQUN0QyxFQUFvQixFQWdJeEIsR0FBTSxHQUFtQixHQUV6QixHQUFNLElBQW9CLEdBQ3RCLEVBQW1CLEdBQ2pCLEdBQWtCLEdBQ2xCLEdBQW1DLFFBQVEsVUFDN0MsRUFBbUIsR0FDdkIsYUFBMkIsQ0FDdkIsQUFBSyxHQUNELEdBQW1CLEdBQ25CLEdBQWlCLEtBQUssS0FPOUIsV0FBNkIsRUFBSSxDQUM3QixFQUFpQixLQUFLLEdBdUIxQixHQUFNLEdBQWlCLEdBQUksS0FDdkIsRUFBVyxFQUNmLGFBQWlCLENBSWIsR0FBSSxJQUFhLEVBQ2IsT0FFSixHQUFNLEdBQWtCLEVBQ3hCLEVBQUcsQ0FHQyxHQUFJLENBQ0EsS0FBTyxFQUFXLEVBQWlCLFFBQVEsQ0FDdkMsR0FBTSxHQUFZLEVBQWlCLEdBQ25DLElBQ0EsRUFBc0IsR0FDdEIsR0FBTyxFQUFVLFdBR2xCLEVBQVAsQ0FFSSxRQUFpQixPQUFTLEVBQzFCLEVBQVcsRUFDTCxFQUtWLElBSEEsRUFBc0IsTUFDdEIsRUFBaUIsT0FBUyxFQUMxQixFQUFXLEVBQ0osR0FBa0IsUUFDckIsR0FBa0IsUUFJdEIsT0FBUyxHQUFJLEVBQUcsRUFBSSxFQUFpQixPQUFRLEdBQUssRUFBRyxDQUNqRCxHQUFNLEdBQVcsRUFBaUIsR0FDbEMsQUFBSyxFQUFlLElBQUksSUFFcEIsR0FBZSxJQUFJLEdBQ25CLEtBR1IsRUFBaUIsT0FBUyxRQUNyQixFQUFpQixRQUMxQixLQUFPLEdBQWdCLFFBQ25CLEdBQWdCLFFBRXBCLEVBQW1CLEdBQ25CLEVBQWUsUUFDZixFQUFzQixHQUUxQixZQUFnQixFQUFJLENBQ2hCLEdBQUksRUFBRyxXQUFhLEtBQU0sQ0FDdEIsRUFBRyxTQUNILEVBQVEsRUFBRyxlQUNYLEdBQU0sR0FBUSxFQUFHLE1BQ2pCLEVBQUcsTUFBUSxDQUFDLElBQ1osRUFBRyxVQUFZLEVBQUcsU0FBUyxFQUFFLEVBQUcsSUFBSyxHQUNyQyxFQUFHLGFBQWEsUUFBUSxJQU1oQyxZQUFnQyxFQUFLLENBQ2pDLEdBQU0sR0FBVyxHQUNYLEVBQVUsR0FDaEIsRUFBaUIsUUFBUSxBQUFDLEdBQU0sRUFBSSxRQUFRLEtBQU8sR0FBSyxFQUFTLEtBQUssR0FBSyxFQUFRLEtBQUssSUFDeEYsRUFBUSxRQUFRLEFBQUMsR0FBTSxLQUN2QixFQUFtQixFQUd2QixHQUFJLEdBQ0osYUFBZ0IsQ0FDWixNQUFLLElBQ0QsR0FBVSxRQUFRLFVBQ2xCLEVBQVEsS0FBSyxJQUFNLENBQ2YsRUFBVSxRQUdYLEVBRVgsV0FBa0IsRUFBTSxFQUFXLEVBQU0sQ0FDckMsRUFBSyxjQUFjLEdBQWEsR0FBRyxFQUFZLFFBQVUsVUFBVSxNQUV2RSxHQUFNLEdBQVcsR0FBSSxLQUNqQixFQUNKLGFBQXdCLENBQ3BCLEVBQVMsQ0FDTCxFQUFHLEVBQ0gsRUFBRyxHQUNILEVBQUcsR0FHWCxhQUF3QixDQUNwQixBQUFLLEVBQU8sR0FDUixFQUFRLEVBQU8sR0FFbkIsRUFBUyxFQUFPLEVBRXBCLFdBQXVCLEVBQU8sRUFBTyxDQUNqQyxBQUFJLEdBQVMsRUFBTSxHQUNmLEdBQVMsT0FBTyxHQUNoQixFQUFNLEVBQUUsSUFHaEIsV0FBd0IsRUFBTyxFQUFPLEVBQVEsRUFBVSxDQUNwRCxHQUFJLEdBQVMsRUFBTSxFQUFHLENBQ2xCLEdBQUksRUFBUyxJQUFJLEdBQ2IsT0FDSixFQUFTLElBQUksR0FDYixFQUFPLEVBQUUsS0FBSyxJQUFNLENBQ2hCLEVBQVMsT0FBTyxHQUNaLEdBQ0ksSUFDQSxFQUFNLEVBQUUsR0FDWixPQUdSLEVBQU0sRUFBRSxPQUVQLEFBQUksSUFDTCxJQUdSLEdBQU0sSUFBa0IsQ0FBRSxTQUFVLEdBMEhwQyxXQUF5QyxFQUFNLEVBQUksRUFBUSxFQUFPLENBQzlELEdBQU0sR0FBVSxDQUFFLFVBQVcsUUFDekIsRUFBUyxFQUFHLEVBQU0sRUFBUSxHQUMxQixFQUFJLEVBQVEsRUFBSSxFQUNoQixFQUFrQixLQUNsQixFQUFrQixLQUNsQixFQUFpQixLQUNyQixZQUEyQixDQUN2QixBQUFJLEdBQ0EsR0FBWSxFQUFNLEdBRTFCLFdBQWMsRUFBUyxFQUFVLENBQzdCLEdBQU0sR0FBSyxFQUFRLEVBQUksRUFDdkIsVUFBWSxLQUFLLElBQUksR0FDZCxDQUNILEVBQUcsRUFDSCxFQUFHLEVBQVEsRUFDWCxJQUNBLFdBQ0EsTUFBTyxFQUFRLE1BQ2YsSUFBSyxFQUFRLE1BQVEsRUFDckIsTUFBTyxFQUFRLE9BR3ZCLFdBQVksRUFBRyxDQUNYLEdBQU0sQ0FBRSxRQUFRLEVBQUcsV0FBVyxJQUFLLFNBQVMsRUFBVSxPQUFPLEVBQU0sT0FBUSxHQUFVLEdBQy9FLEVBQVUsQ0FDWixNQUFPLEtBQVEsRUFDZixLQUVKLEFBQUssR0FFRCxHQUFRLE1BQVEsRUFDaEIsRUFBTyxHQUFLLEdBRWhCLEFBQUksR0FBbUIsRUFDbkIsRUFBa0IsRUFLZCxJQUNBLEtBQ0EsRUFBaUIsR0FBWSxFQUFNLEVBQUcsRUFBRyxFQUFVLEVBQU8sRUFBUSxJQUVsRSxHQUNBLEVBQUssRUFBRyxHQUNaLEVBQWtCLEVBQUssRUFBUyxHQUNoQyxFQUFvQixJQUFNLEVBQVMsRUFBTSxFQUFHLFVBQzVDLEdBQUssR0FBTyxDQVVSLEdBVEksR0FBbUIsRUFBTSxFQUFnQixPQUN6QyxHQUFrQixFQUFLLEVBQWlCLEdBQ3hDLEVBQWtCLEtBQ2xCLEVBQVMsRUFBTSxFQUFnQixFQUFHLFNBQzlCLEdBQ0EsS0FDQSxFQUFpQixHQUFZLEVBQU0sRUFBRyxFQUFnQixFQUFHLEVBQWdCLFNBQVUsRUFBRyxFQUFRLEVBQU8sT0FHekcsR0FDQSxHQUFJLEdBQU8sRUFBZ0IsSUFDdkIsRUFBSyxFQUFJLEVBQWdCLEVBQUcsRUFBSSxHQUNoQyxFQUFTLEVBQU0sRUFBZ0IsRUFBRyxPQUM3QixHQUVELENBQUksRUFBZ0IsRUFFaEIsSUFJSyxFQUFFLEVBQWdCLE1BQU0sR0FDekIsRUFBUSxFQUFnQixNQUFNLElBRzFDLEVBQWtCLGFBRWIsR0FBTyxFQUFnQixNQUFPLENBQ25DLEdBQU0sSUFBSSxFQUFNLEVBQWdCLE1BQ2hDLEVBQUksRUFBZ0IsRUFBSSxFQUFnQixFQUFJLEVBQU8sR0FBSSxFQUFnQixVQUN2RSxFQUFLLEVBQUcsRUFBSSxJQUdwQixNQUFPLENBQUMsQ0FBRSxJQUFtQixNQUl6QyxNQUFPLENBQ0gsSUFBSSxFQUFHLENBQ0gsQUFBSSxFQUFZLEdBQ1osS0FBTyxLQUFLLElBQU0sQ0FFZCxFQUFTLEVBQU8sR0FDaEIsRUFBRyxLQUlQLEVBQUcsSUFHWCxLQUFNLENBQ0YsSUFDQSxFQUFrQixFQUFrQixPQXVGaEQsR0FBTSxJQUFXLE1BQU8sU0FBVyxZQUM3QixPQUNBLE1BQU8sYUFBZSxZQUNsQixXQUNBLE9BaUpWLEdBQU0sSUFBc0IsQ0FDeEIsa0JBQ0Esc0JBQ0EsUUFDQSxZQUNBLFdBQ0EsVUFDQSxXQUNBLFVBQ0EsUUFDQSxXQUNBLGlCQUNBLFNBQ0EsUUFDQSxRQUNBLE9BQ0EsV0FDQSxRQUNBLFdBQ0EsYUFDQSxPQUNBLGNBQ0EsV0FDQSxXQUNBLFdBQ0EsWUFNRSxHQUFxQixHQUFJLEtBQUksQ0FBQyxHQUFHLEtBbU12QyxZQUF5QixFQUFXLEVBQVEsRUFBUSxFQUFlLENBQy9ELEdBQU0sQ0FBRSxXQUFVLGdCQUFpQixFQUFVLEdBQzdDLEdBQVksRUFBUyxFQUFFLEVBQVEsR0FDMUIsR0FFRCxFQUFvQixJQUFNLENBQ3RCLEdBQU0sR0FBaUIsRUFBVSxHQUFHLFNBQVMsSUFBSSxHQUFLLE9BQU8sR0FJN0QsQUFBSSxFQUFVLEdBQUcsV0FDYixFQUFVLEdBQUcsV0FBVyxLQUFLLEdBQUcsR0FLaEMsRUFBUSxHQUVaLEVBQVUsR0FBRyxTQUFXLEtBR2hDLEVBQWEsUUFBUSxHQUV6QixZQUEyQixFQUFXLEVBQVcsQ0FDN0MsR0FBTSxHQUFLLEVBQVUsR0FDckIsQUFBSSxFQUFHLFdBQWEsTUFDaEIsSUFBdUIsRUFBRyxjQUMxQixFQUFRLEVBQUcsWUFDWCxFQUFHLFVBQVksRUFBRyxTQUFTLEVBQUUsR0FHN0IsRUFBRyxXQUFhLEVBQUcsU0FBVyxLQUM5QixFQUFHLElBQU0sSUFHakIsWUFBb0IsRUFBVyxFQUFHLENBQzlCLEFBQUksRUFBVSxHQUFHLE1BQU0sS0FBTyxJQUMxQixHQUFpQixLQUFLLEdBQ3RCLEtBQ0EsRUFBVSxHQUFHLE1BQU0sS0FBSyxJQUU1QixFQUFVLEdBQUcsTUFBTyxFQUFJLEdBQU0sSUFBTyxHQUFNLEVBQUksR0FFbkQsWUFBYyxFQUFXLEVBQVMsRUFBVSxFQUFpQixFQUFXLEVBQU8sRUFBZSxFQUFRLENBQUMsSUFBSyxDQUN4RyxHQUFNLEdBQW1CLEVBQ3pCLEVBQXNCLEdBQ3RCLEdBQU0sR0FBSyxFQUFVLEdBQUssQ0FDdEIsU0FBVSxLQUNWLElBQUssR0FFTCxRQUNBLE9BQVEsRUFDUixZQUNBLE1BQU8sS0FFUCxTQUFVLEdBQ1YsV0FBWSxHQUNaLGNBQWUsR0FDZixjQUFlLEdBQ2YsYUFBYyxHQUNkLFFBQVMsR0FBSSxLQUFJLEVBQVEsU0FBWSxHQUFtQixFQUFpQixHQUFHLFFBQVUsS0FFdEYsVUFBVyxLQUNYLFFBQ0EsV0FBWSxHQUNaLEtBQU0sRUFBUSxRQUFVLEVBQWlCLEdBQUcsTUFFaEQsR0FBaUIsRUFBYyxFQUFHLE1BQ2xDLEdBQUksR0FBUSxHQWtCWixHQWpCQSxFQUFHLElBQU0sRUFDSCxFQUFTLEVBQVcsRUFBUSxPQUFTLEdBQUksQ0FBQyxFQUFHLEtBQVEsSUFBUyxDQUM1RCxHQUFNLEdBQVEsRUFBSyxPQUFTLEVBQUssR0FBSyxFQUN0QyxNQUFJLEdBQUcsS0FBTyxFQUFVLEVBQUcsSUFBSSxHQUFJLEVBQUcsSUFBSSxHQUFLLElBQ3ZDLEVBQUMsRUFBRyxZQUFjLEVBQUcsTUFBTSxJQUMzQixFQUFHLE1BQU0sR0FBRyxHQUNaLEdBQ0EsR0FBVyxFQUFXLElBRXZCLElBRVQsR0FDTixFQUFHLFNBQ0gsRUFBUSxHQUNSLEVBQVEsRUFBRyxlQUVYLEVBQUcsU0FBVyxFQUFrQixFQUFnQixFQUFHLEtBQU8sR0FDdEQsRUFBUSxPQUFRLENBQ2hCLEdBQUksRUFBUSxRQUFTLENBQ2pCLEtBQ0EsR0FBTSxHQUFRLEdBQVMsRUFBUSxRQUUvQixFQUFHLFVBQVksRUFBRyxTQUFTLEVBQUUsR0FDN0IsRUFBTSxRQUFRLE9BSWQsR0FBRyxVQUFZLEVBQUcsU0FBUyxJQUUvQixBQUFJLEVBQVEsT0FDUixFQUFjLEVBQVUsR0FBRyxVQUMvQixHQUFnQixFQUFXLEVBQVEsT0FBUSxFQUFRLE9BQVEsRUFBUSxlQUNuRSxLQUNBLEtBRUosRUFBc0IsR0FFMUIsR0FBSSxJQUNKLEFBQUksTUFBTyxjQUFnQixZQUN2QixJQUFnQixhQUFjLFlBQVksQ0FDdEMsYUFBYyxDQUNWLFFBQ0EsS0FBSyxhQUFhLENBQUUsS0FBTSxTQUU5QixtQkFBb0IsQ0FDaEIsR0FBTSxDQUFFLFlBQWEsS0FBSyxHQUMxQixLQUFLLEdBQUcsY0FBZ0IsRUFBUyxJQUFJLEdBQUssT0FBTyxHQUVqRCxPQUFXLEtBQU8sTUFBSyxHQUFHLFFBRXRCLEtBQUssWUFBWSxLQUFLLEdBQUcsUUFBUSxJQUd6Qyx5QkFBeUIsRUFBTSxFQUFXLEVBQVUsQ0FDaEQsS0FBSyxHQUFRLEVBRWpCLHNCQUF1QixDQUNuQixFQUFRLEtBQUssR0FBRyxlQUVwQixVQUFXLENBQ1AsR0FBa0IsS0FBTSxHQUN4QixLQUFLLFNBQVcsRUFFcEIsSUFBSSxFQUFNLEVBQVUsQ0FFaEIsR0FBSSxDQUFDLEVBQVksR0FDYixNQUFPLEdBRVgsR0FBTSxHQUFhLEtBQUssR0FBRyxVQUFVLElBQVUsTUFBSyxHQUFHLFVBQVUsR0FBUSxJQUN6RSxTQUFVLEtBQUssR0FDUixJQUFNLENBQ1QsR0FBTSxHQUFRLEVBQVUsUUFBUSxHQUNoQyxBQUFJLElBQVUsSUFDVixFQUFVLE9BQU8sRUFBTyxJQUdwQyxLQUFLLEVBQVMsQ0FDVixBQUFJLEtBQUssT0FBUyxDQUFDLEdBQVMsSUFDeEIsTUFBSyxHQUFHLFdBQWEsR0FDckIsS0FBSyxNQUFNLEdBQ1gsS0FBSyxHQUFHLFdBQWEsT0FRckMsV0FBc0IsQ0FDbEIsVUFBVyxDQUNQLEdBQWtCLEtBQU0sR0FDeEIsS0FBSyxTQUFXLEVBRXBCLElBQUksRUFBTSxFQUFVLENBQ2hCLEdBQUksQ0FBQyxFQUFZLEdBQ2IsTUFBTyxHQUVYLEdBQU0sR0FBYSxLQUFLLEdBQUcsVUFBVSxJQUFVLE1BQUssR0FBRyxVQUFVLEdBQVEsSUFDekUsU0FBVSxLQUFLLEdBQ1IsSUFBTSxDQUNULEdBQU0sR0FBUSxFQUFVLFFBQVEsR0FDaEMsQUFBSSxJQUFVLElBQ1YsRUFBVSxPQUFPLEVBQU8sSUFHcEMsS0FBSyxFQUFTLENBQ1YsQUFBSSxLQUFLLE9BQVMsQ0FBQyxHQUFTLElBQ3hCLE1BQUssR0FBRyxXQUFhLEdBQ3JCLEtBQUssTUFBTSxHQUNYLEtBQUssR0FBRyxXQUFhLE1DbHJFakMsR0FBTSxHQUFtQixHQWdCekIsWUFBa0IsRUFBTyxFQUFRLEVBQU0sQ0FDbkMsR0FBSSxHQUNFLEVBQWMsR0FBSSxLQUN4QixXQUFhLEVBQVcsQ0FDcEIsR0FBSSxFQUFlLEVBQU8sSUFDdEIsR0FBUSxFQUNKLEdBQU0sQ0FDTixHQUFNLEdBQVksQ0FBQyxFQUFpQixPQUNwQyxPQUFXLEtBQWMsR0FDckIsRUFBVyxLQUNYLEVBQWlCLEtBQUssRUFBWSxHQUV0QyxHQUFJLEVBQVcsQ0FDWCxPQUFTLEdBQUksRUFBRyxFQUFJLEVBQWlCLE9BQVEsR0FBSyxFQUM5QyxFQUFpQixHQUFHLEdBQUcsRUFBaUIsRUFBSSxJQUVoRCxFQUFpQixPQUFTLElBSzFDLFdBQWdCLEVBQUksQ0FDaEIsRUFBSSxFQUFHLElBRVgsV0FBbUIsRUFBSyxFQUFhLEVBQU0sQ0FDdkMsR0FBTSxHQUFhLENBQUMsRUFBSyxHQUN6QixTQUFZLElBQUksR0FDWixFQUFZLE9BQVMsR0FDckIsR0FBTyxFQUFNLElBQVEsR0FFekIsRUFBSSxHQUNHLElBQU0sQ0FDVCxFQUFZLE9BQU8sR0FDZixFQUFZLE9BQVMsR0FBSyxHQUMxQixLQUNBLEVBQU8sT0FJbkIsTUFBTyxDQUFFLE1BQUssU0FBUSxhQ3ZEbkIsR0FBTSxHQUFpQixHQUFTLElBRWhDLGFBQXVCLENBQzFCLEVBQWUsSUFBSSxJQUdoQixhQUF1QixDQUMxQixFQUFlLElBQUksSUNpQ3ZCLFlBQWMsRUFBTSxDQUFFLFFBQVEsRUFBRyxXQUFXLElBQUssU0FBUyxHQUFXLEdBQUksQ0FDckUsR0FBTSxHQUFJLENBQUMsaUJBQWlCLEdBQU0sUUFDbEMsTUFBTyxDQUNILFFBQ0EsV0FDQSxTQUNBLElBQUssR0FBSyxZQUFZLEVBQUk7OztpVkMxQzlCLEVBSU0sRUFBQSxFQUFBLDRDQUptRCxTQUFVLEtBQUcsMkNBQWIsU0FBVSxLQUFHLDBFQURyRSxFQUFlLElBQUEsR0FBQSwwRUFBZixFQUFlLHVTQ1VwQixHQUFJLElBQWEsR0FDYixFQUFTLEtBRWIsWUFBd0IsRUFBRyxDQUN2QixHQUFNLEdBQVUsS0FBSyxNQUFNLEVBQUUsTUFDN0IsT0FBTyxhQUFhLEVBQVEsR0FDUixFQUFRLE9BQ1IsSUFHeEIsWUFBNkIsRUFBRyxDQUM1QixHQUFNLEdBQVUsS0FBSyxNQUFNLEVBQUUsTUFDN0IsT0FBTyxrQkFBa0IsRUFBUSxHQUFJLEVBQVEsUUFHakQsWUFBc0IsRUFBRyxDQUNyQixHQUFNLEdBQVUsS0FBSyxNQUFNLEVBQUUsTUFDN0IsT0FBTyxlQUFlLEVBQVEsR0FDUixFQUFRLE9BQ1IsSUFHMUIsWUFBMkIsRUFBRyxDQUMxQixHQUFNLEdBQVUsS0FBSyxNQUFNLEVBQUUsTUFDN0IsT0FBTyxvQkFBb0IsRUFBUSxHQUFJLEVBQVEsUUFHbkQsWUFBMEIsRUFBRyxDQUN6QixRQUFRLElBQUksZUFBaUIsRUFBRSxNQUduQyxPQUFPLGlCQUFpQixtQkFBb0IsSUFBTSxDQUM5QyxHQUFXLFFBQVUsR0FBSSxJQUFRLENBQzdCLE9BQVEsU0FBUyxLQUNqQixPQUFRLFNBQVMsY0FBYyxvQkFFbkMsT0FHSixPQUFPLGVBQWlCLFVBQVksQ0FDaEMsQUFBSSxHQUNBLEdBQU8sUUFBVSxVQUFZLEdBQzdCLEVBQU8sUUFDUCxFQUFTLE9BS2pCLFlBQXVCLEVBQUcsQ0FDdEIsS0FDQSxFQUFPLFFBQVUsR0FNckIsWUFBMEIsRUFBRyxDQUN6QixBQUFJLEtBQUssWUFBYyxZQUFZLFdBQy9CLEtBRUEsUUFBUSxJQUFJLEdBSXBCLGFBQW9CLENBQ2hCLEFBQUksR0FBVSxNQUNWLEdBQVMsR0FBSSxhQUFZLDJCQUEyQixNQUFNLFVBQzFELEVBQU8sT0FBUyxHQUNoQixFQUFPLFFBQVUsR0FDakIsRUFBTyxpQkFBaUIsS0FBTSxJQUM5QixFQUFPLGlCQUFpQixVQUFXLElBQ25DLEVBQU8saUJBQWlCLFFBQVMsSUFDakMsRUFBTyxpQkFBaUIsYUFBYyxJQUN0QyxFQUFPLGlCQUFpQixhQUFjLEtBSzlDLGFBQW1CLENBQ2YiLAogICJuYW1lcyI6IFtdCn0K diff --git a/v3/plugins/experimental/server/emptywriter.go b/v3/plugins/experimental/server/emptywriter.go new file mode 100644 index 000000000..b24b8ebe4 --- /dev/null +++ b/v3/plugins/experimental/server/emptywriter.go @@ -0,0 +1,16 @@ +package server + +import "net/http" + +type Consumer struct{} + +func (c Consumer) Header() http.Header { + return http.Header{} +} + +func (c Consumer) Write(data []byte) (int, error) { + return len(data), nil +} + +func (c Consumer) WriteHeader(statusCode int) { +} diff --git a/v3/plugins/experimental/server/ipc/Overlay.svelte b/v3/plugins/experimental/server/ipc/Overlay.svelte new file mode 100644 index 000000000..dfe02b21b --- /dev/null +++ b/v3/plugins/experimental/server/ipc/Overlay.svelte @@ -0,0 +1,54 @@ + + +{#if $overlayVisible } +
+
+
+
+
+{/if} + + \ No newline at end of file diff --git a/v3/plugins/experimental/server/ipc/build.js b/v3/plugins/experimental/server/ipc/build.js new file mode 100644 index 000000000..4ed0c35d9 --- /dev/null +++ b/v3/plugins/experimental/server/ipc/build.js @@ -0,0 +1,15 @@ +/* jshint esversion: 8 */ +const esbuild = require("esbuild"); +const sveltePlugin = require("esbuild-svelte"); + +esbuild + .build({ + entryPoints: ["main.js"], + bundle: true, + minify: true, + outfile: "../client.js", + plugins: [sveltePlugin({compileOptions: {css: true}})], + logLevel: "info", + sourcemap: "inline", + }) + .catch(() => process.exit(1)); diff --git a/v3/plugins/experimental/server/ipc/log.js b/v3/plugins/experimental/server/ipc/log.js new file mode 100644 index 000000000..e128c97f0 --- /dev/null +++ b/v3/plugins/experimental/server/ipc/log.js @@ -0,0 +1,8 @@ +export function log(message) { + // eslint-disable-next-line + console.log( + '%c wails dev %c ' + message + ' ', + 'background: #aa0000; color: #fff; border-radius: 3px 0px 0px 3px; padding: 1px; font-size: 0.7rem', + 'background: #009900; color: #fff; border-radius: 0px 3px 3px 0px; padding: 1px; font-size: 0.7rem' + ); +} \ No newline at end of file diff --git a/v3/plugins/experimental/server/ipc/main.js b/v3/plugins/experimental/server/ipc/main.js new file mode 100644 index 000000000..12da4ab79 --- /dev/null +++ b/v3/plugins/experimental/server/ipc/main.js @@ -0,0 +1,97 @@ +/* + _ __ _ __ +| | / /___ _(_) /____ +| | /| / / __ `/ / / ___/ +| |/ |/ / /_/ / / (__ ) +|__/|__/\__,_/_/_/____/ +The electron alternative for Go +(c) Lea Anthony 2019-present +*/ +/* jshint esversion: 6 */ + +import {log} from "./log"; +import Overlay from "./Overlay.svelte"; +import {hideOverlay, showOverlay} from "./store"; +import { nanoid } from 'nanoid/non-secure'; + +let components = {}; +let source = null; + +function handleCallback(e) { + const payload = JSON.parse(e.data); + _wails.callCallback(payload.id, + payload.result, + true); +} + +function handleCallbackError(e) { + const payload = JSON.parse(e.data); + _wails.callErrorCallback(payload.id, payload.result); +} + +function handleDialog(e) { + const payload = JSON.parse(e.data); + _wails.dialogCallback(payload.id, + payload.result, + true); +} + +function handleDialogError(e) { + const payload = JSON.parse(e.data); + _wails.dialogErrorCallback(payload.id, payload.result); +} + +function handleWailsEvent(e) { + console.log("WailsEvent: " + e.data) +} + +window.addEventListener('DOMContentLoaded', () => { + components.overlay = new Overlay({ + target: document.body, + anchor: document.querySelector('#wails-spinner'), + }); + connect(); +}); + +window.onbeforeunload = function () { + if (source) { + source.onclose = function () { }; + source.close(); + source = null; + } +}; + +// Handles sse connections +function handleConnect(e) { + hideOverlay(); + source.onclose = handleDisconnect; + +} + +// Handles SSE disconnects +// EventSource will attempt to reconnect on it's own +function handleDisconnect(e) { + if (this.readyState == EventSource.CONNECTING) { + showOverlay(); + } else { + console.log(e); + } +} + +function _connect() { + if (source == null) { + source = new EventSource("/server/events?clientId="+wails.clientId); + source.onopen = handleConnect; + source.onerror = handleDisconnect; + source.addEventListener('cb', handleCallback); + source.addEventListener('cberror', handleCallbackError); + source.addEventListener('dlgcb', handleDialog); + source.addEventListener('dlgcberror', handleDialogError); + source.addEventListener('wailsevent', handleWailsEvent); + } +} + +// Try to connect to the backend every .5s +function connect() { + _connect(); +} diff --git a/v3/plugins/experimental/server/ipc/package-lock.json b/v3/plugins/experimental/server/ipc/package-lock.json new file mode 100644 index 000000000..19e507d78 --- /dev/null +++ b/v3/plugins/experimental/server/ipc/package-lock.json @@ -0,0 +1,1561 @@ +{ + "name": "ipc", + "version": "3.0.0", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "ipc", + "version": "3.0.0", + "license": "ISC", + "devDependencies": { + "esbuild": "^0.12.17", + "esbuild-svelte": "^0.5.6", + "nanoid": "^4.0.0", + "npm-run-all": "^4.1.5", + "svelte": "^3.55.1" + } + }, + "node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "node_modules/cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "dev": true, + "dependencies": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + }, + "engines": { + "node": ">=4.8" + } + }, + "node_modules/define-properties": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", + "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", + "dev": true, + "dependencies": { + "object-keys": "^1.0.12" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/es-abstract": { + "version": "1.18.5", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.5.tgz", + "integrity": "sha512-DDggyJLoS91CkJjgauM5c0yZMjiD1uK3KcaCeAmffGwZ+ODWzOkPN4QwRbsK5DOFf06fywmyLci3ZD8jLGhVYA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "get-intrinsic": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.2", + "internal-slot": "^1.0.3", + "is-callable": "^1.2.3", + "is-negative-zero": "^2.0.1", + "is-regex": "^1.1.3", + "is-string": "^1.0.6", + "object-inspect": "^1.11.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.2", + "string.prototype.trimend": "^1.0.4", + "string.prototype.trimstart": "^1.0.4", + "unbox-primitive": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, + "dependencies": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/esbuild": { + "version": "0.12.21", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.12.21.tgz", + "integrity": "sha512-7hyXbU3g94aREufI/5nls7Xcc+RGQeZWZApm6hoBaFvt2BPtpT4TjFMQ9Tb1jU8XyBGz00ShmiyflCogphMHFQ==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + } + }, + "node_modules/esbuild-svelte": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/esbuild-svelte/-/esbuild-svelte-0.5.6.tgz", + "integrity": "sha512-Bz8nU45FrT6sP/Tf3M2rQUuBGxnDSNSPZNIoYwSNt5H+wjSyo/t+zm94tgnOZsR6GgpDMbNQgo4jGbK0NLvEfw==", + "dev": true, + "dependencies": { + "svelte": "^3.42.6" + }, + "peerDependencies": { + "esbuild": ">=0.9.6" + } + }, + "node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "node_modules/get-intrinsic": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", + "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.8.tgz", + "integrity": "sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg==", + "dev": true + }, + "node_modules/has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/has-bigints": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.1.tgz", + "integrity": "sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/has-symbols": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz", + "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", + "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", + "dev": true, + "dependencies": { + "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hosted-git-info": { + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", + "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", + "dev": true + }, + "node_modules/internal-slot": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.3.tgz", + "integrity": "sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.1.0", + "has": "^1.0.3", + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", + "dev": true + }, + "node_modules/is-bigint": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", + "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "dev": true, + "dependencies": { + "has-bigints": "^1.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-boolean-object": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", + "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-callable": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.4.tgz", + "integrity": "sha512-nsuwtxZfMX67Oryl9LCQ+upnC0Z0BgpwntpS89m1H/TLF0zNfzfLMV/9Wa/6MZsj0acpEjAO0KF1xT6ZdLl95w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-core-module": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.6.0.tgz", + "integrity": "sha512-wShG8vs60jKfPWpF2KZRaAtvt3a20OAn7+IJ6hLPECpSABLcKtFKTTI4ZtH5QcBruBHlq+WsdHWyz0BCZW7svQ==", + "dev": true, + "dependencies": { + "has": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", + "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-negative-zero": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.1.tgz", + "integrity": "sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-number-object": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.6.tgz", + "integrity": "sha512-bEVOqiRcvo3zO1+G2lVMy+gkkEm9Yh7cDMRusKKu5ZJKPUYSJwICTKZrNKHA2EbSP0Tu0+6B/emsYNHZyn6K8g==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-regex": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", + "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-string": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", + "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", + "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", + "dev": true, + "dependencies": { + "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "node_modules/json-parse-better-errors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", + "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", + "dev": true + }, + "node_modules/load-json-file": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", + "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=", + "dev": true, + "dependencies": { + "graceful-fs": "^4.1.2", + "parse-json": "^4.0.0", + "pify": "^3.0.0", + "strip-bom": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/memorystream": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz", + "integrity": "sha1-htcJCzDORV1j+64S3aUaR93K+bI=", + "dev": true, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/nanoid": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-4.0.2.tgz", + "integrity": "sha512-7ZtY5KTCNheRGfEFxnedV5zFiORN1+Y1N6zvPTnHQd8ENUvfaDBeuJDZb2bN/oXwXxu3qkTXDzy57W5vAmDTBw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.js" + }, + "engines": { + "node": "^14 || ^16 || >=18" + } + }, + "node_modules/nice-try": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", + "dev": true + }, + "node_modules/normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "dev": true, + "dependencies": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "node_modules/npm-run-all": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/npm-run-all/-/npm-run-all-4.1.5.tgz", + "integrity": "sha512-Oo82gJDAVcaMdi3nuoKFavkIHBRVqQ1qvMb+9LHk/cF4P6B2m8aP04hGf7oL6wZ9BuGwX1onlLhpuoofSyoQDQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "chalk": "^2.4.1", + "cross-spawn": "^6.0.5", + "memorystream": "^0.3.1", + "minimatch": "^3.0.4", + "pidtree": "^0.3.0", + "read-pkg": "^3.0.0", + "shell-quote": "^1.6.1", + "string.prototype.padend": "^3.0.0" + }, + "bin": { + "npm-run-all": "bin/npm-run-all/index.js", + "run-p": "bin/run-p/index.js", + "run-s": "bin/run-s/index.js" + }, + "engines": { + "node": ">= 4" + } + }, + "node_modules/object-inspect": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.11.0.tgz", + "integrity": "sha512-jp7ikS6Sd3GxQfZJPyH3cjcbJF6GZPClgdV+EFygjFLQ5FmW/dRUnTd9PQ9k0JhoNDabWFbpF1yCdSWCC6gexg==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz", + "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3", + "has-symbols": "^1.0.1", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", + "dev": true, + "dependencies": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "node_modules/path-type": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", + "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", + "dev": true, + "dependencies": { + "pify": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/pidtree": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.3.1.tgz", + "integrity": "sha512-qQbW94hLHEqCg7nhby4yRC7G2+jYHY4Rguc2bjw7Uug4GIJuu1tvf2uHaZv5Q8zdt+WKJ6qK1FOI6amaWUo5FA==", + "dev": true, + "bin": { + "pidtree": "bin/pidtree.js" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/read-pkg": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", + "integrity": "sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=", + "dev": true, + "dependencies": { + "load-json-file": "^4.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/resolve": { + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz", + "integrity": "sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==", + "dev": true, + "dependencies": { + "is-core-module": "^2.2.0", + "path-parse": "^1.0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "dev": true, + "dependencies": { + "shebang-regex": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/shell-quote": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.7.3.tgz", + "integrity": "sha512-Vpfqwm4EnqGdlsBFNmHhxhElJYrdfcxPThu+ryKS5J8L/fhAwLazFZtq+S+TWZ9ANj2piSQLGj6NQg+lKPmxrw==", + "dev": true + }, + "node_modules/side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/spdx-correct": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", + "integrity": "sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==", + "dev": true, + "dependencies": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-exceptions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", + "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==", + "dev": true + }, + "node_modules/spdx-expression-parse": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "dev": true, + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-license-ids": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.10.tgz", + "integrity": "sha512-oie3/+gKf7QtpitB0LYLETe+k8SifzsX4KixvpOsbI6S0kRiRQ5MKOio8eMSAKQ17N06+wdEOXRiId+zOxo0hA==", + "dev": true + }, + "node_modules/string.prototype.padend": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/string.prototype.padend/-/string.prototype.padend-3.1.2.tgz", + "integrity": "sha512-/AQFLdYvePENU3W5rgurfWSMU6n+Ww8n/3cUt7E+vPBB/D7YDG8x+qjoFs4M/alR2bW7Qg6xMjVwWUOvuQ0XpQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.18.0-next.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz", + "integrity": "sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz", + "integrity": "sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/svelte": { + "version": "3.58.0", + "resolved": "https://registry.npmjs.org/svelte/-/svelte-3.58.0.tgz", + "integrity": "sha512-brIBNNB76mXFmU/Kerm4wFnkskBbluBDCjx/8TcpYRb298Yh2dztS2kQ6bhtjMcvUhd5ynClfwpz5h2gnzdQ1A==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/unbox-primitive": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.1.tgz", + "integrity": "sha512-tZU/3NqK3dA5gpE1KtyiJUrEB0lxnGkMFHptJ7q6ewdZ8s12QrODwNbhIJStmJkd1QDXa1NRA8aF2A1zk/Ypyw==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1", + "has-bigints": "^1.0.1", + "has-symbols": "^1.0.2", + "which-boxed-primitive": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "dev": true, + "dependencies": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, + "node_modules/which-boxed-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", + "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", + "dev": true, + "dependencies": { + "is-bigint": "^1.0.1", + "is-boolean-object": "^1.1.0", + "is-number-object": "^1.0.4", + "is-string": "^1.0.5", + "is-symbol": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + } + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "dev": true, + "requires": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "dev": true, + "requires": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "define-properties": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", + "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", + "dev": true, + "requires": { + "object-keys": "^1.0.12" + } + }, + "error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "requires": { + "is-arrayish": "^0.2.1" + } + }, + "es-abstract": { + "version": "1.18.5", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.5.tgz", + "integrity": "sha512-DDggyJLoS91CkJjgauM5c0yZMjiD1uK3KcaCeAmffGwZ+ODWzOkPN4QwRbsK5DOFf06fywmyLci3ZD8jLGhVYA==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "get-intrinsic": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.2", + "internal-slot": "^1.0.3", + "is-callable": "^1.2.3", + "is-negative-zero": "^2.0.1", + "is-regex": "^1.1.3", + "is-string": "^1.0.6", + "object-inspect": "^1.11.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.2", + "string.prototype.trimend": "^1.0.4", + "string.prototype.trimstart": "^1.0.4", + "unbox-primitive": "^1.0.1" + } + }, + "es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, + "requires": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + } + }, + "esbuild": { + "version": "0.12.21", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.12.21.tgz", + "integrity": "sha512-7hyXbU3g94aREufI/5nls7Xcc+RGQeZWZApm6hoBaFvt2BPtpT4TjFMQ9Tb1jU8XyBGz00ShmiyflCogphMHFQ==", + "dev": true + }, + "esbuild-svelte": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/esbuild-svelte/-/esbuild-svelte-0.5.6.tgz", + "integrity": "sha512-Bz8nU45FrT6sP/Tf3M2rQUuBGxnDSNSPZNIoYwSNt5H+wjSyo/t+zm94tgnOZsR6GgpDMbNQgo4jGbK0NLvEfw==", + "dev": true, + "requires": { + "svelte": "^3.42.6" + } + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "get-intrinsic": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", + "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", + "dev": true, + "requires": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1" + } + }, + "graceful-fs": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.8.tgz", + "integrity": "sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg==", + "dev": true + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-bigints": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.1.tgz", + "integrity": "sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA==", + "dev": true + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "has-symbols": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz", + "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==", + "dev": true + }, + "has-tostringtag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", + "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", + "dev": true, + "requires": { + "has-symbols": "^1.0.2" + } + }, + "hosted-git-info": { + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", + "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", + "dev": true + }, + "internal-slot": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.3.tgz", + "integrity": "sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA==", + "dev": true, + "requires": { + "get-intrinsic": "^1.1.0", + "has": "^1.0.3", + "side-channel": "^1.0.4" + } + }, + "is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", + "dev": true + }, + "is-bigint": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", + "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "dev": true, + "requires": { + "has-bigints": "^1.0.1" + } + }, + "is-boolean-object": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", + "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + } + }, + "is-callable": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.4.tgz", + "integrity": "sha512-nsuwtxZfMX67Oryl9LCQ+upnC0Z0BgpwntpS89m1H/TLF0zNfzfLMV/9Wa/6MZsj0acpEjAO0KF1xT6ZdLl95w==", + "dev": true + }, + "is-core-module": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.6.0.tgz", + "integrity": "sha512-wShG8vs60jKfPWpF2KZRaAtvt3a20OAn7+IJ6hLPECpSABLcKtFKTTI4ZtH5QcBruBHlq+WsdHWyz0BCZW7svQ==", + "dev": true, + "requires": { + "has": "^1.0.3" + } + }, + "is-date-object": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", + "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "dev": true, + "requires": { + "has-tostringtag": "^1.0.0" + } + }, + "is-negative-zero": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.1.tgz", + "integrity": "sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w==", + "dev": true + }, + "is-number-object": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.6.tgz", + "integrity": "sha512-bEVOqiRcvo3zO1+G2lVMy+gkkEm9Yh7cDMRusKKu5ZJKPUYSJwICTKZrNKHA2EbSP0Tu0+6B/emsYNHZyn6K8g==", + "dev": true, + "requires": { + "has-tostringtag": "^1.0.0" + } + }, + "is-regex": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", + "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + } + }, + "is-string": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", + "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "dev": true, + "requires": { + "has-tostringtag": "^1.0.0" + } + }, + "is-symbol": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", + "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", + "dev": true, + "requires": { + "has-symbols": "^1.0.2" + } + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "json-parse-better-errors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", + "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", + "dev": true + }, + "load-json-file": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", + "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "parse-json": "^4.0.0", + "pify": "^3.0.0", + "strip-bom": "^3.0.0" + } + }, + "memorystream": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz", + "integrity": "sha1-htcJCzDORV1j+64S3aUaR93K+bI=", + "dev": true + }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "nanoid": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-4.0.2.tgz", + "integrity": "sha512-7ZtY5KTCNheRGfEFxnedV5zFiORN1+Y1N6zvPTnHQd8ENUvfaDBeuJDZb2bN/oXwXxu3qkTXDzy57W5vAmDTBw==", + "dev": true + }, + "nice-try": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", + "dev": true + }, + "normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "dev": true, + "requires": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "npm-run-all": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/npm-run-all/-/npm-run-all-4.1.5.tgz", + "integrity": "sha512-Oo82gJDAVcaMdi3nuoKFavkIHBRVqQ1qvMb+9LHk/cF4P6B2m8aP04hGf7oL6wZ9BuGwX1onlLhpuoofSyoQDQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "chalk": "^2.4.1", + "cross-spawn": "^6.0.5", + "memorystream": "^0.3.1", + "minimatch": "^3.0.4", + "pidtree": "^0.3.0", + "read-pkg": "^3.0.0", + "shell-quote": "^1.6.1", + "string.prototype.padend": "^3.0.0" + } + }, + "object-inspect": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.11.0.tgz", + "integrity": "sha512-jp7ikS6Sd3GxQfZJPyH3cjcbJF6GZPClgdV+EFygjFLQ5FmW/dRUnTd9PQ9k0JhoNDabWFbpF1yCdSWCC6gexg==", + "dev": true + }, + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true + }, + "object.assign": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz", + "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3", + "has-symbols": "^1.0.1", + "object-keys": "^1.1.1" + } + }, + "parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", + "dev": true, + "requires": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + } + }, + "path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", + "dev": true + }, + "path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "path-type": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", + "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", + "dev": true, + "requires": { + "pify": "^3.0.0" + } + }, + "pidtree": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.3.1.tgz", + "integrity": "sha512-qQbW94hLHEqCg7nhby4yRC7G2+jYHY4Rguc2bjw7Uug4GIJuu1tvf2uHaZv5Q8zdt+WKJ6qK1FOI6amaWUo5FA==", + "dev": true + }, + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "dev": true + }, + "read-pkg": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", + "integrity": "sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=", + "dev": true, + "requires": { + "load-json-file": "^4.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^3.0.0" + } + }, + "resolve": { + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz", + "integrity": "sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==", + "dev": true, + "requires": { + "is-core-module": "^2.2.0", + "path-parse": "^1.0.6" + } + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + }, + "shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "dev": true, + "requires": { + "shebang-regex": "^1.0.0" + } + }, + "shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", + "dev": true + }, + "shell-quote": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.7.3.tgz", + "integrity": "sha512-Vpfqwm4EnqGdlsBFNmHhxhElJYrdfcxPThu+ryKS5J8L/fhAwLazFZtq+S+TWZ9ANj2piSQLGj6NQg+lKPmxrw==", + "dev": true + }, + "side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "dev": true, + "requires": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + } + }, + "spdx-correct": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", + "integrity": "sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==", + "dev": true, + "requires": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-exceptions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", + "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==", + "dev": true + }, + "spdx-expression-parse": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "dev": true, + "requires": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-license-ids": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.10.tgz", + "integrity": "sha512-oie3/+gKf7QtpitB0LYLETe+k8SifzsX4KixvpOsbI6S0kRiRQ5MKOio8eMSAKQ17N06+wdEOXRiId+zOxo0hA==", + "dev": true + }, + "string.prototype.padend": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/string.prototype.padend/-/string.prototype.padend-3.1.2.tgz", + "integrity": "sha512-/AQFLdYvePENU3W5rgurfWSMU6n+Ww8n/3cUt7E+vPBB/D7YDG8x+qjoFs4M/alR2bW7Qg6xMjVwWUOvuQ0XpQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.18.0-next.2" + } + }, + "string.prototype.trimend": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz", + "integrity": "sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3" + } + }, + "string.prototype.trimstart": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz", + "integrity": "sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3" + } + }, + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + }, + "svelte": { + "version": "3.58.0", + "resolved": "https://registry.npmjs.org/svelte/-/svelte-3.58.0.tgz", + "integrity": "sha512-brIBNNB76mXFmU/Kerm4wFnkskBbluBDCjx/8TcpYRb298Yh2dztS2kQ6bhtjMcvUhd5ynClfwpz5h2gnzdQ1A==", + "dev": true + }, + "unbox-primitive": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.1.tgz", + "integrity": "sha512-tZU/3NqK3dA5gpE1KtyiJUrEB0lxnGkMFHptJ7q6ewdZ8s12QrODwNbhIJStmJkd1QDXa1NRA8aF2A1zk/Ypyw==", + "dev": true, + "requires": { + "function-bind": "^1.1.1", + "has-bigints": "^1.0.1", + "has-symbols": "^1.0.2", + "which-boxed-primitive": "^1.0.2" + } + }, + "validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "dev": true, + "requires": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "which-boxed-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", + "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", + "dev": true, + "requires": { + "is-bigint": "^1.0.1", + "is-boolean-object": "^1.1.0", + "is-number-object": "^1.0.4", + "is-string": "^1.0.5", + "is-symbol": "^1.0.3" + } + } + } +} diff --git a/v3/plugins/experimental/server/ipc/package.json b/v3/plugins/experimental/server/ipc/package.json new file mode 100644 index 000000000..1486f7ab6 --- /dev/null +++ b/v3/plugins/experimental/server/ipc/package.json @@ -0,0 +1,19 @@ +{ + "name": "sse", + "version": "3.0.0", + "description": "Wails Server Plugin SSE", + "main": "main.js", + "scripts": { + "build": "run-p build:*", + "build:dev": "node build.js" + }, + "author": "Lea Anthony ", + "license": "ISC", + "devDependencies": { + "esbuild": "^0.12.17", + "esbuild-svelte": "^0.5.6", + "nanoid": "^4.0.0", + "npm-run-all": "^4.1.5", + "svelte": "^3.55.1" + } +} diff --git a/v3/plugins/experimental/server/ipc/store.js b/v3/plugins/experimental/server/ipc/store.js new file mode 100644 index 000000000..fc085570b --- /dev/null +++ b/v3/plugins/experimental/server/ipc/store.js @@ -0,0 +1,12 @@ +import {writable} from 'svelte/store'; + +/** Overlay */ +export const overlayVisible = writable(false); + +export function showOverlay() { + overlayVisible.set(true); +} + +export function hideOverlay() { + overlayVisible.set(false); +} diff --git a/v3/plugins/experimental/server/ipc_websocket.js b/v3/plugins/experimental/server/ipc_websocket.js new file mode 100644 index 000000000..812740d44 --- /dev/null +++ b/v3/plugins/experimental/server/ipc_websocket.js @@ -0,0 +1,8 @@ +(()=>{function _(){}var B=t=>t;function W(t){return t()}function ot(){return Object.create(null)}function w(t){t.forEach(W)}function v(t){return typeof t=="function"}function J(t,e){return t!=t?e==e:t!==e||t&&typeof t=="object"||typeof t=="function"}function st(t){return Object.keys(t).length===0}function ct(t,...e){if(t==null)return _;let n=t.subscribe(...e);return n.unsubscribe?()=>n.unsubscribe():n}function lt(t,e,n){t.$$.on_destroy.push(ct(e,n))}var ut=typeof window!="undefined",Lt=ut?()=>window.performance.now():()=>Date.now(),V=ut?t=>requestAnimationFrame(t):_;var x=new Set;function at(t){x.forEach(e=>{e.c(t)||(x.delete(e),e.f())}),x.size!==0&&V(at)}function Tt(t){let e;return x.size===0&&V(at),{promise:new Promise(n=>{x.add(e={c:t,f:n})}),abort(){x.delete(e)}}}var ft=!1;function Bt(){ft=!0}function Jt(){ft=!1}function zt(t,e){t.appendChild(e)}function dt(t,e,n){let i=U(t);if(!i.getElementById(e)){let r=z("style");r.id=e,r.textContent=n,ht(i,r)}}function U(t){if(!t)return document;let e=t.getRootNode?t.getRootNode():t.ownerDocument;return e&&e.host?e:t.ownerDocument}function Ht(t){let e=z("style");return ht(U(t),e),e.sheet}function ht(t,e){return zt(t.head||t,e),e.sheet}function X(t,e,n){t.insertBefore(e,n||null)}function I(t){t.parentNode&&t.parentNode.removeChild(t)}function z(t){return document.createElement(t)}function Nt(t){return document.createTextNode(t)}function _t(){return Nt("")}function pt(t,e,n){n==null?t.removeAttribute(e):t.getAttribute(e)!==n&&t.setAttribute(e,n)}function Pt(t){return Array.from(t.childNodes)}function Gt(t,e,{bubbles:n=!1,cancelable:i=!1}={}){let r=document.createEvent("CustomEvent");return r.initCustomEvent(t,n,i,e),r}var H=new Map,N=0;function qt(t){let e=5381,n=t.length;for(;n--;)e=(e<<5)-e^t.charCodeAt(n);return e>>>0}function Kt(t,e){let n={stylesheet:Ht(e),rules:{}};return H.set(t,n),n}function mt(t,e,n,i,r,c,l,s=0){let u=16.666/i,o=`{ +`;for(let b=0;b<=1;b+=u){let F=e+(n-e)*c(b);o+=b*100+`%{${l(F,1-F)}} +`}let p=o+`100% {${l(n,1-n)}} +}`,f=`__svelte_${qt(p)}_${s}`,g=U(t),{stylesheet:a,rules:h}=H.get(g)||Kt(g,t);h[f]||(h[f]=!0,a.insertRule(`@keyframes ${f} ${p}`,a.cssRules.length));let m=t.style.animation||"";return t.style.animation=`${m?`${m}, `:""}${f} ${i}ms linear ${r}ms 1 both`,N+=1,f}function Rt(t,e){let n=(t.style.animation||"").split(", "),i=n.filter(e?c=>c.indexOf(e)<0:c=>c.indexOf("__svelte")===-1),r=n.length-i.length;r&&(t.style.animation=i.join(", "),N-=r,N||Wt())}function Wt(){V(()=>{N||(H.forEach(t=>{let{ownerNode:e}=t.stylesheet;e&&I(e)}),H.clear())})}var Z;function O(t){Z=t}var $=[];var yt=[],M=[],gt=[],Vt=Promise.resolve(),Q=!1;function Ut(){Q||(Q=!0,Vt.then(bt))}function S(t){M.push(t)}var Y=new Set,C=0;function bt(){if(C!==0)return;let t=Z;do{try{for(;C<$.length;){let e=$[C];C++,O(e),Xt(e.$$)}}catch(e){throw $.length=0,C=0,e}for(O(null),$.length=0,C=0;yt.length;)yt.pop()();for(let e=0;et.indexOf(i)===-1?e.push(i):n.push(i)),n.forEach(i=>i()),M=e}var j;function Qt(){return j||(j=Promise.resolve(),j.then(()=>{j=null})),j}function tt(t,e,n){t.dispatchEvent(Gt(`${e?"intro":"outro"}${n}`))}var P=new Set,y;function wt(){y={r:0,c:[],p:y}}function vt(){y.r||w(y.c),y=y.p}function A(t,e){t&&t.i&&(P.delete(t),t.i(e))}function et(t,e,n,i){if(t&&t.o){if(P.has(t))return;P.add(t),y.c.push(()=>{P.delete(t),i&&(n&&t.d(1),i())}),t.o(e)}else i&&i()}var Yt={duration:0};function nt(t,e,n,i){let r={direction:"both"},c=e(t,n,r),l=i?0:1,s=null,u=null,o=null;function p(){o&&Rt(t,o)}function f(a,h){let m=a.b-l;return h*=Math.abs(m),{a:l,b:a.b,d:m,duration:h,start:a.start,end:a.start+h,group:a.group}}function g(a){let{delay:h=0,duration:m=300,easing:b=B,tick:F=_,css:K}=c||Yt,R={start:Lt()+h,b:a};a||(R.group=y,y.r+=1),s||u?u=R:(K&&(p(),o=mt(t,l,a,m,h,b,K)),a&&F(0,1),s=f(R,m),S(()=>tt(t,a,"start")),Tt(T=>{if(u&&T>u.start&&(s=f(u,m),u=null,tt(t,s.b,"start"),K&&(p(),o=mt(t,l,s.b,s.duration,0,b,c.css))),s){if(T>=s.end)F(l=s.b,1-l),tt(t,s.b,"end"),u||(s.b?p():--s.group.r||w(s.group.c)),s=null;else if(T>=s.start){let Dt=T-s.start;l=s.a+s.d*b(Dt/s.duration),F(l,1-l)}}return!!(s||u)}))}return{run(a){v(c)?Qt().then(()=>{c=c(r),g(a)}):g(a)},end(){p(),s=u=null}}}var pe=typeof window!="undefined"?window:typeof globalThis!="undefined"?globalThis:global;var te=["allowfullscreen","allowpaymentrequest","async","autofocus","autoplay","checked","controls","default","defer","disabled","formnovalidate","hidden","inert","ismap","loop","multiple","muted","nomodule","novalidate","open","playsinline","readonly","required","reversed","selected"],me=new Set([...te]);function ee(t,e,n,i){let{fragment:r,after_update:c}=t.$$;r&&r.m(e,n),i||S(()=>{let l=t.$$.on_mount.map(W).filter(v);t.$$.on_destroy?t.$$.on_destroy.push(...l):w(l),t.$$.on_mount=[]}),c.forEach(S)}function Ft(t,e){let n=t.$$;n.fragment!==null&&(Zt(n.after_update),w(n.on_destroy),n.fragment&&n.fragment.d(e),n.on_destroy=n.fragment=null,n.ctx=[])}function ne(t,e){t.$$.dirty[0]===-1&&($.push(t),Ut(),t.$$.dirty.fill(0)),t.$$.dirty[e/31|0]|=1<{let h=a.length?a[0]:g;return o.ctx&&r(o.ctx[f],o.ctx[f]=h)&&(!o.skip_bound&&o.bound[f]&&o.bound[f](h),p&&ne(t,f)),g}):[],o.update(),p=!0,w(o.before_update),o.fragment=i?i(o.ctx):!1,e.target){if(e.hydrate){Bt();let f=Pt(e.target);o.fragment&&o.fragment.l(f),f.forEach(I)}else o.fragment&&o.fragment.c();e.intro&&A(t.$$.fragment),ee(t,e.target,e.anchor,e.customElement),Jt(),bt()}O(u)}var ie;typeof HTMLElement=="function"&&(ie=class extends HTMLElement{constructor(){super();this.attachShadow({mode:"open"})}connectedCallback(){let{on_mount:t}=this.$$;this.$$.on_disconnect=t.map(W).filter(v);for(let e in this.$$.slotted)this.appendChild(this.$$.slotted[e])}attributeChangedCallback(t,e,n){this[t]=n}disconnectedCallback(){w(this.$$.on_disconnect)}$destroy(){Ft(this,1),this.$destroy=_}$on(t,e){if(!v(e))return _;let n=this.$$.callbacks[t]||(this.$$.callbacks[t]=[]);return n.push(e),()=>{let i=n.indexOf(e);i!==-1&&n.splice(i,1)}}$set(t){this.$$set&&!st(t)&&(this.$$.skip_bound=!0,this.$$set(t),this.$$.skip_bound=!1)}});var it=class{$destroy(){Ft(this,1),this.$destroy=_}$on(e,n){if(!v(n))return _;let i=this.$$.callbacks[e]||(this.$$.callbacks[e]=[]);return i.push(n),()=>{let r=i.indexOf(n);r!==-1&&i.splice(r,1)}}$set(e){this.$$set&&!st(e)&&(this.$$.skip_bound=!0,this.$$set(e),this.$$.skip_bound=!1)}};var k=[];function $t(t,e=_){let n,i=new Set;function r(s){if(J(t,s)&&(t=s,n)){let u=!k.length;for(let o of i)o[1](),k.push(o,t);if(u){for(let o=0;o{i.delete(o),i.size===0&&n&&(n(),n=null)}}return{set:r,update:c,subscribe:l}}var G=$t(!1);function Mt(){G.set(!0)}function St(){G.set(!1)}function rt(t,{delay:e=0,duration:n=400,easing:i=B}={}){let r=+getComputedStyle(t).opacity;return{delay:e,duration:n,easing:i,css:c=>`opacity: ${c*r}`}}function re(t){dt(t,"svelte-181h7z",`.wails-reconnect-overlay.svelte-181h7z{position:fixed;top:0;left:0;width:100%;height:100%;backdrop-filter:blur(2px) saturate(0%) contrast(50%) brightness(25%);z-index:999999 + }.wails-reconnect-overlay-content.svelte-181h7z{position:relative;top:50%;transform:translateY(-50%);margin:0;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEsAAAA7CAMAAAAEsocZAAAC91BMVEUAAACzQ0PjMjLkMjLZLS7XLS+vJCjkMjKlEx6uGyHjMDGiFx7GJyrAISjUKy3mMzPlMjLjMzOsGyDKJirkMjK6HyXmMjLgMDC6IiLcMjLULC3MJyrRKSy+IibmMzPmMjK7ISXlMjLIJimzHSLkMjKtGiHZLC7BIifgMDCpGSDFIivcLy+yHSKoGR+eFBzNKCvlMjKxHSPkMTKxHSLmMjLKJyq5ICXDJCe6ISXdLzDkMjLmMzPFJSm2HyTlMTLhMDGyHSKUEBmhFx24HyTCJCjHJijjMzOiFh7mMjJ6BhDaLDCuGyOKABjnMzPGJinJJiquHCGEChSmGB/pMzOiFh7VKy3OKCu1HiSvHCLjMTLMKCrBIyeICxWxHCLDIyjSKizBIyh+CBO9ISa6ISWDChS9Iie1HyXVLC7FJSrLKCrlMjLiMTGPDhicFRywGyKXFBuhFx1/BxO7IiXkMTGeFBx8BxLkMTGnGR/GJCi4ICWsGyGJDxXSLS2yGiHSKi3CJCfnMzPQKiyECRTKJiq6ISWUERq/Iye0HiPDJCjGJSm6ICaPDxiTEBrdLy+3HyXSKiy0HyOQEBi4ICWhFh1+CBO9IieODhfSKyzWLC2LDhh8BxHKKCq7ISWaFBzkMzPqNDTTLC3EJSiHDBacExyvGyO1HyTPKCy+IieoGSC7ISaVEhrMKCvQKyusGyG0HiKACBPIJSq/JCaABxR5BRLEJCnkMzPJJinEJimPDRZ2BRKqHx/jMjLnMzPgMDHULC3NKSvQKSzsNDTWLS7SKyy3HyTKJyrDJSjbLzDYLC6mGB/GJSnVLC61HiPLKCrHJSm/Iye8Iia6ICWzHSKxHCLaLi/PKSupGR+7ICXpMzPbLi/IJinJJSmsGyGrGiCkFx6PDheJCxaFChXBIyfAIieSDxmBCBPlMjLeLzDdLzC5HySMDRe+ISWvGyGcFBzSKSzPJyvMJyrEJCjDIyefFRyWERriMDHUKiy/ISaZExv0NjbwNTXuNDTrMzMI0c+yAAAAu3RSTlMAA8HR/gwGgAj+MEpGCsC+hGpjQjYnIxgWBfzx7urizMrFqqB1bF83KhsR/fz8+/r5+fXv7unZ1tC+t6mmopqKdW1nYVpVRjUeHhIQBPr59/b28/Hx8ODg3NvUw8O/vKeim5aNioiDgn1vZWNjX1xUU1JPTUVFPT08Mi4qJyIh/Pv7+/n4+Pf39fT08/Du7efn5uXj4uHa19XNwsG/vrq2tbSuramlnpyYkpGNiIZ+enRraGVjVVBKOzghdjzRsAAABJVJREFUWMPtllVQG1EYhTc0ASpoobS0FCulUHd3oUjd3d3d3d3d3d2b7CYhnkBCCHGDEIK7Vh56d0NpOgwkYfLQzvA9ZrLfnPvfc+8uVEst/yheBJup3Nya2MjU6pa/jWLZtxjXpZFtVB4uVNI6m5gIruNkVFebqIb5Ug2ym4TIEM/gtUOGbg613oBzjAzZFrZ+lXu/3TIiMXXS5M6HTvrNHeLpZLEh6suGNW9fzZ9zd/qVi2eOHygqi5cDE5GUrJocONgzyqo0UXNSUlKSEhMztFqtXq9vNxImAmS3g7Y6QlbjdBWVGW36jt4wDGTUXjUsafh5zJWRkdFuZGtWGnCRmg+HasiGMUClTTzW0ZuVgLlGDIPM4Lhi0IrVq+tv2hS21fNrSONQgpM9DsJ4t3fM9PkvJuKj2ZjrZwvILKvaSTgciUSirjt6dOfOpyd169bDb9rMOwF9Hj4OD100gY0YXYb299bjzMrqj9doNByJWlVXFB9DT5dmJuvy+cq83JyuS6ayEYSHulKL8dmFnBkrCeZlHKMrC5XRhXGCZB2Ty1fkleRQaMCFT2DBsEafzRFJu7/2MicbKynPhQUDLiZwMWLJZKNLzoLbJBYVcurSmbmn+rcyJ8vCMgmlmaW6gnwun/+3C96VpAUuET1ZgRR36r2xWlnYSnf3oKABA14uXDDvydxHs6cpTV1p3hlJ2rJCiUjIZCByItXg8sHJijuvT64CuMTABUYvb6NN1Jdp1PH7D7f3bo2eS5KvW4RJr7atWT5w4MBBg9zdBw9+37BS7QIoFS5WnIaj12dr1DEXFgdvr4fh4eFl+u/wz8uf3jjHic8s4DL2Dal0IANyUBeCRCcwOBJV26JsjSpGwHVuSai69jvqD+jr56OgtKy0zAAK5mLTVBKVKL5tNthGAR9JneJQ/bFsHNzy+U7IlCYROxtMpIjR0ceoQVnowracLLpAQWETqV361bPoFo3cEbz2zYLZM7t3HWXcxmiBOgttS1ycWkTXMWh4mGigdug9DFdttqCFgTN6nD0q1XEVSoCxEjyFCi2eNC6Z69MRVIImJ6JQSf5gcFVCuF+aDhCa1F6MJFDaiNBQAh2TMfWBjhmLsAxUjG/fmjs0qjJck8D0GPBcuUuZW1LS/tIsPzqmQt17PvZQknlwnf4tHDBc+7t5VV3QQCkdc+Ur8/hdrz0but0RCumWiYbiKmLJ7EVbRomj4Q7+y5wsaXvfTGFpQcHB7n2WbG4MGdniw2Tm8xl5Yhr7MrSYHQ3uampz10aWyHyuzxvqaW/6W4MjXAUD3QV2aw97ZxhGjxCohYf5TpTHMXU1BbsAuoFnkRygVieIGAbqiF7rrH4rfWpKJouBCtyHJF8ctEyGubBa+C6NsMYEUonJFITHZqWBxXUA12Dv76Tf/PgOBmeNiiLG1pcKo1HAq8jLpY4JU1yWEixVNaOgoRJAKBSZHTZTU+wJOMtUDZvlVITC6FTlksyrEBoPHXpxxbzdaqzigUtVDkJVIOtVQ9UEOR4VGUh/kHWq0edJ6CxnZ+eePXva2bnY/cF/I1RLLf8vvwDANdMSMegxcAAAAABJRU5ErkJggg==);background-repeat:no-repeat;background-position:center + }.wails-reconnect-overlay-loadingspinner.svelte-181h7z{pointer-events:none;width:2.5em;height:2.5em;border:.4em solid transparent;border-color:#f00 #eee0 #f00 #eee0;border-radius:50%;animation:svelte-181h7z-loadingspin 1s linear infinite;margin:auto;padding:2.5em + }@keyframes svelte-181h7z-loadingspin{100%{transform:rotate(360deg)}}`)}function Ct(t){let e,n,i;return{c(){e=z("div"),e.innerHTML='
',pt(e,"class","wails-reconnect-overlay svelte-181h7z")},m(r,c){X(r,e,c),i=!0},i(r){i||(S(()=>{!i||(n||(n=nt(e,rt,{duration:300},!0)),n.run(1))}),i=!0)},o(r){n||(n=nt(e,rt,{duration:300},!1)),n.run(0),i=!1},d(r){r&&I(e),r&&n&&n.end()}}}function oe(t){let e,n,i=t[0]&&Ct(t);return{c(){i&&i.c(),e=_t()},m(r,c){i&&i.m(r,c),X(r,e,c),n=!0},p(r,[c]){r[0]?i?c&1&&A(i,1):(i=Ct(r),i.c(),A(i,1),i.m(e.parentNode,e)):i&&(wt(),et(i,1,1,()=>{i=null}),vt())},i(r){n||(A(i),n=!0)},o(r){et(i),n=!1},d(r){i&&i.d(r),r&&I(e)}}}function se(t,e,n){let i;return lt(t,G,r=>n(0,i=r)),[i]}var kt=class extends it{constructor(e){super();xt(this,e,se,oe,J,{},re)}},Et=kt;var ce="useandom-26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict";var It=(t=21)=>{let e="",n=t;for(;n--;)e+=ce[Math.random()*64|0];return e};var le={},D=null,q=[],E=new Map;function ue(){let t;do t=It();while(E.has(t));return t}function L(t,e){let n=Object.assign({},e),i=ue();return n.method=t,n.time=Date.now(),n["call-id"]=i,window.WailsInvoke(JSON.stringify(n)),new Promise((r,c)=>{E.set(i,{resolve:r,reject:c})})}window.WailsInvoke=t=>{if(!D){console.log("Queueing: "+t),q.push(t);return}D(t)};window.addEventListener("DOMContentLoaded",()=>{le.overlay=new Et({target:document.body,anchor:document.querySelector("#wails-spinner")}),At()});var d=null,Ot;window.onbeforeunload=function(){d&&(d.onclose=function(){},d.close(),d=null)};function ae(){wails.Call=t=>L("call.Call",t),window.Emit=t=>L("events.Emit",t),wails.Events.Emit=t=>L("events.Emit",t),wails.Log=t=>L("Log",t),wails.Plugin=(t,e,...n)=>L("call.Call",{packageName:"wails-plugins",structName:t,methodName:e,args:n}),_wails.callCallback=(t,e,n)=>{let i=E.get(t);i&&(n?i.resolve(JSON.parse(e)):i.resolve(e),E.delete(t))},_wails.callErrorCallback=(t,e)=>{let n=E.get(t);n&&n.reject(e),E.delete(t)}}function fe(){St(),clearInterval(Ot),d.onclose=de,d.onmessage=he,D=t=>{d.send(t)};for(let t=0;t { + + }) + + // or + wails.Plugin("browser","OpenFile","/path/to/file") +``` + +## Support + +If you find a bug in this plugin, please raise a ticket on the Wails [Issue Tracker](https://github.com/wailsapp/wails/issues). diff --git a/v3/plugins/kvstore/kvstore.go b/v3/plugins/kvstore/kvstore.go new file mode 100644 index 000000000..50370ffba --- /dev/null +++ b/v3/plugins/kvstore/kvstore.go @@ -0,0 +1,163 @@ +package kvstore + +import ( + "encoding/json" + "github.com/wailsapp/wails/v3/pkg/application" + "io" + "os" + "sync" +) + +type KeyValueStore struct { + config *Config + filename string + data map[string]any + unsaved bool + lock sync.RWMutex +} + +func (kvs *KeyValueStore) InjectJS() string { + return "" +} + +type Config struct { + Filename string + AutoSave bool +} + +type Plugin struct{} + +func NewPlugin(config *Config) *KeyValueStore { + return &KeyValueStore{ + config: config, + data: make(map[string]any), + } +} + +// Shutdown will save the store to disk if there are unsaved changes. +func (kvs *KeyValueStore) Shutdown() { + if kvs.unsaved { + err := kvs.Save() + if err != nil { + application.Get().Logger.Error("Error saving store: " + err.Error()) + } + } +} + +// Name returns the name of the plugin. +func (kvs *KeyValueStore) Name() string { + return "github.com/wailsapp/wails/v3/plugins/kvstore" +} + +// Init is called when the plugin is loaded. It is passed the application.App +// instance. This is where you should do any setup. +func (kvs *KeyValueStore) Init() error { + err := kvs.open(kvs.config.Filename) + if err != nil { + return err + } + + return nil +} + +func (kvs *KeyValueStore) CallableByJS() []string { + return []string{ + "Set", + "Get", + "Save", + } +} + +func (p *Plugin) InjectJS() string { + return "" +} + +// ---------------- Plugin Methods ---------------- + +func (kvs *KeyValueStore) open(filename string) (err error) { + kvs.filename = filename + kvs.data = make(map[string]any) + + file, err := os.Open(filename) + if err != nil { + if os.IsNotExist(err) { + return nil + } + return err + } + defer func() { + err2 := file.Close() + if err2 != nil { + application.Get().Logger.Error("Key/Value Store Plugin Error:", "error", err.Error()) + if err == nil { + err = err2 + } + } + }() + + bytes, err := io.ReadAll(file) + if err != nil { + return err + } + + if len(bytes) > 0 { + if err := json.Unmarshal(bytes, &kvs.data); err != nil { + return err + } + } + + return nil +} + +// Save saves the store to disk +func (kvs *KeyValueStore) Save() error { + kvs.lock.Lock() + defer kvs.lock.Unlock() + + bytes, err := json.Marshal(kvs.data) + if err != nil { + return err + } + + err = os.WriteFile(kvs.filename, bytes, 0644) + if err != nil { + return err + } + + kvs.unsaved = false + + return nil +} + +// Get returns the value for the given key. If key is empty, the entire store is returned. +func (kvs *KeyValueStore) Get(key string) any { + kvs.lock.RLock() + defer kvs.lock.RUnlock() + + if key == "" { + return kvs.data + } + + return kvs.data[key] +} + +// Set sets the value for the given key. If AutoSave is true, the store is saved to disk. +func (kvs *KeyValueStore) Set(key string, value any) error { + kvs.lock.Lock() + if value == nil { + delete(kvs.data, key) + } else { + kvs.data[key] = value + } + kvs.lock.Unlock() + if kvs.config.AutoSave { + err := kvs.Save() + if err != nil { + return err + } + kvs.unsaved = false + } else { + kvs.unsaved = true + } + return nil +} diff --git a/v3/plugins/kvstore/plugin.js b/v3/plugins/kvstore/plugin.js new file mode 100644 index 000000000..5233081f6 --- /dev/null +++ b/v3/plugins/kvstore/plugin.js @@ -0,0 +1,31 @@ +// plugin.js +// This file should contain helper functions for the that can be used by the frontend. +// Below are examples of how to use JSDoc to define the Hashes struct and the exported functions. + +/** + * Get the value of a key. + * @param key {string} - The store key. + * @returns {Promise} - The value of the key. + */ +export function Get(key) { + return wails.CallByID(3322496224, key); +} + +/** + * Set the value of a key. + @param key {string} - The store key. + @param value {any} - The value to set. + * @returns {Promise} + */ +export function Set(key, value) { + return wails.CallByID(1207638860, key, value); +} + + +/** + * Save the database to disk. + * @returns {Promise} + */ +export function Save() { + return wails.CallByID(1377075201); +} diff --git a/v3/plugins/kvstore/plugin.yaml b/v3/plugins/kvstore/plugin.yaml new file mode 100644 index 000000000..2c2a80cc2 --- /dev/null +++ b/v3/plugins/kvstore/plugin.yaml @@ -0,0 +1,11 @@ +# This is the plugin definition file for the "kvstore" plugin. +--- +Name: kvstore +Description: A Simple Key/Value Store for Wails Applications +Author: Lea Anthony +Version: v1.0.0 +Website: https://wails.io +Repository: https://github.com/wailsapp/wails/v3/plugins/kvstore +License: MIT + + diff --git a/v3/plugins/log/README.md b/v3/plugins/log/README.md new file mode 100644 index 000000000..cd5f511e1 --- /dev/null +++ b/v3/plugins/log/README.md @@ -0,0 +1,51 @@ +# log Plugin + +This example plugin provides a way to generate hashes of strings. + +## Installation + +Add the plugin to the `Plugins` option in the Applications options: + +```go + Plugins: map[string]application.Plugin{ + "log": log.NewPlugin(), + }, +``` + +## Usage + +You can then call the methods from the frontend: + +```js + wails.Plugin("log","Debug","hello world") +``` + +### Methods + +- Trace +- Debug +- Info +- Warning +- Error +- Fatal +- SetLevel + +SetLevel takes an integer value from JS: + +```js + wails.Plugin("log","SetLevel",1) +``` + +Levels are: + + - Trace: 1 + - Debug: 2 + - Info: 3 + - Warning: 4 + - Error: 5 + - Fatal: 6 + +## Support + +If you find a bug in this plugin, please raise a ticket [here](https://github.com/plugin/repository). +Please do not contact the Wails team for support. \ No newline at end of file diff --git a/v3/plugins/log/plugin.go b/v3/plugins/log/plugin.go new file mode 100644 index 000000000..f9e180c34 --- /dev/null +++ b/v3/plugins/log/plugin.go @@ -0,0 +1,103 @@ +package log + +import ( + _ "embed" + "github.com/wailsapp/wails/v3/pkg/application" + "log/slog" +) + +//go:embed plugin.js +var pluginJS string + +// ---------------- Plugin Setup ---------------- +// This is the main plugin struct. It can be named anything you like. +// It must implement the application.Plugin interface. +// Both the Init() and Shutdown() methods are called synchronously when the app starts and stops. + +type Config struct { + // Logger is the logger to use. If not set, a default logger will be used. + Logger *slog.Logger + + // LogLevel defines the log level of the logger. + LogLevel slog.Level + + // Handles errors that occur when writing to the log + ErrorHandler func(err error) +} + +type Plugin struct { + config *Config + app *application.App + level slog.LevelVar +} + +func NewPluginWithConfig(config *Config) *Plugin { + if config.Logger == nil { + config.Logger = application.DefaultLogger(config.LogLevel) + } + + result := &Plugin{ + config: config, + } + result.level.Set(config.LogLevel) + return result +} + +func NewPlugin() *Plugin { + return NewPluginWithConfig(&Config{}) +} + +// Shutdown is called when the app is shutting down +// You can use this to clean up any resources you have allocated +func (p *Plugin) Shutdown() {} + +// Name returns the name of the plugin. +// You should use the go module format e.g. github.com/myuser/myplugin +func (p *Plugin) Name() string { + return "github.com/wailsapp/wails/v3/plugins/log" +} + +func (p *Plugin) Init() error { + return nil +} + +// CallableByJS returns a list of methods that can be called from the frontend +func (p *Plugin) CallableByJS() []string { + return []string{ + "Debug", + "Info", + "Warning", + "Error", + "SetLogLevel", + } +} + +func (p *Plugin) InjectJS() string { + return pluginJS +} + +// ---------------- Plugin Methods ---------------- +// Plugin methods are just normal Go methods. You can add as many as you like. +// The only requirement is that they are exported (start with a capital letter). +// You can also return any type that is JSON serializable. +// See https://golang.org/pkg/encoding/json/#Marshal for more information. + +func (p *Plugin) Debug(message string, args ...any) { + p.config.Logger.Debug(message, args...) +} + +func (p *Plugin) Info(message string, args ...any) { + p.config.Logger.Info(message, args...) +} + +func (p *Plugin) Warning(message string, args ...any) { + p.config.Logger.Warn(message, args...) +} + +func (p *Plugin) Error(message string, args ...any) { + p.config.Logger.Error(message, args...) +} + +func (p *Plugin) SetLogLevel(level slog.Level) { + p.level.Set(level) +} diff --git a/v3/plugins/log/plugin.js b/v3/plugins/log/plugin.js new file mode 100644 index 000000000..7fea212d6 --- /dev/null +++ b/v3/plugins/log/plugin.js @@ -0,0 +1,59 @@ +// plugin.js +// This file should contain helper functions for the that can be used by the frontend. +// Below are examples of how to use JSDoc to define the Hashes struct and the exported functions. + +/** + * Log at the Debug level. + * @param input {string} - The message in printf format. + * @param args {...any} - The arguments for the log message. + * @returns {Promise} + */ + +function Debug(input, ...args) { + return wails.CallByID(4111675027, input, ...args); +} + +/** + * Log at the Info level. + * @param input {string} - The message in printf format. + * @param args {...any} - The arguments for the log message. + * @returns {Promise} + */ +function Info(input, ...args) { + return wails.CallByID(2391172776, input, ...args); +} + +/** + * Log at the Warning level. + * @param input {string} - The message in printf format. + * @param args {...any} - The arguments for the log message. + * @returns {Promise} + */ +function Warning(input, ...args) { + return wails.CallByID(2762394760, input, ...args); +} + +/** + * Log at the Error level. + * @param input {string} - The message in printf format. + * @param args {...any} - The arguments for the log message. + * @returns {Promise} + */ +function Error(input, ...args) { + return wails.CallByID(878590242, input, ...args); +} + +const LevelDebug = -4 +const LevelInfo = 0 +const LevelWarn = 4 +const LevelError = 8 + + +/** + * Set Log level + * @param level {LogLevel} - The log level to set. + * @returns {Promise} + */ +function SetLogLevel(level) { + return wails.CallByID(2758810652, level); +} diff --git a/v3/plugins/log/plugin.yaml b/v3/plugins/log/plugin.yaml new file mode 100644 index 000000000..ea0cf5f67 --- /dev/null +++ b/v3/plugins/log/plugin.yaml @@ -0,0 +1,11 @@ +# This is the plugin definition file for the "log" plugin. +--- +Name: log +Description: A basic logger +Author: Lea Anthony +Version: v1.0.0 +Website: https://wails.io +Repository: https://github.com/wailsapp/wails/v3/plugins/log +License: MIT + + diff --git a/v3/plugins/oauth/README.md b/v3/plugins/oauth/README.md new file mode 100644 index 000000000..a06b7a1d9 --- /dev/null +++ b/v3/plugins/oauth/README.md @@ -0,0 +1,214 @@ +# oauth Plugin + +This plugin provides the ability to initiate an OAuth authentication flow with a wide range of OAuth providers: + + - Amazon + - Apple + - Auth0 + - AzureAD + - BattleNet + - Bitbucket + - Box + - Dailymotion + - Deezer + - DigitalOcean + - Discord + - Dropbox + - EveOnline + - Facebook + - Fitbit + - Gitea + - Gitlab + - Github + - Google + - GooglePlus + - Heroku + - Intercom + - Instagram + - Kakao + - LastFM + - LinkedIn + - Line + - Mastodon + - Meetup + - MicrosoftOnline + - Naver + - NextCloud + - Okta + - Onedrive + - OpenIDConnect + - Patreon + - PayPal + - SalesForce + - SeaTalk + - Shopify + - Slack + - SoundCloud + - Spotify + - Steam + - Strava + - Stripe + - TikTok + - Twitter + - TwitterV2 + - Typetalk + - Twitch + - Uber + - VK + - WeCom + - Wepay + - Xero + - Yahoo + - Yammer + - Yandex + - Zoom + +## Installation + +Add the plugin to the `Plugins` option in the Applications options. This example we are using the github provider: + +```go +package main + +import ( + "github.com/wailsapp/wails/v3/pkg/application" + "github.com/wailsapp/wails/v3/plugins/browser" +) + +func main() { + oAuthPlugin := oauth.NewPlugin(oauth.Config{ + Providers: []goth.Provider{ + github.New( + os.Getenv("clientkey"), + os.Getenv("secret"), + "http://localhost:9876/auth/github/callback", + "email", + "profile"), + }, + }) + + app := application.New(application.Options{ + // ... + Plugins: map[string]application.Plugin{ + "oauth": oAuthPlugin, + }, +}) +``` + +### Configuration + +The plugin takes a `Config` struct as a parameter. This struct has the following fields: + +```go +type Config struct { + + // Address to bind the temporary webserver to + // Defaults to localhost:9876 + Address string + + // SessionSecret is the secret used to encrypt the session store. + SessionSecret string + + // MaxAge is the maximum age of the session in seconds. + MaxAge int + + // Providers is a list of goth providers to use. + Providers []goth.Provider + + // WindowConfig is the configuration for the window that will be opened + // to perform the OAuth login. + WindowConfig *application.WebviewWindowOptions +} +``` + +If you don't specify a `WindowConfig`, the plugin will use the default window configuration: + +```go +&application.WebviewWindowOptions{ + Title: "OAuth Login", + Width: 600, + Height: 850, + Hidden: true, +} +``` + +## Usage + +### Go + +You can start the flow by calling one of the provider methods: + +```go +err := oAuthPlugin.Github() +``` + +In this example, we send an event from the frontend to start the process, so we listen for the event in the backend: + +```go +app.Events.On("github-login", func(e *application.WailsEvent) { + err := oAuthPlugin.Github() + if err != nil { + // process error + } +}) +``` + +### JavaScript + +You can start the flow by calling one of the provider methods: + +```javascript +await wails.Plugin("oauth","Github") +``` + +### Handling Success & Failure + +When the OAuth flow completes, the plugin will send one of 2 events: + + - `wails:oauth:success` - The OAuth flow completed successfully. The event data will contain the user information. + - `wails:oauth:error` - The OAuth flow failed. The event data will contain the error message. + +In Javascript, we can listen for these events like so: + +```javascript +window.wails.Events.On("wails:oauth:success", (event) => { + document.getElementById("main").style.display = "none"; + document.getElementById("name").innerText = event.data.Name; + document.getElementById("logo").src = event.data.AvatarURL; + document.body.style.backgroundColor = "#000"; + document.body.style.color = "#FFF"; +}); +``` + +If you want to handle them in Go, you can do so like this: + +```go +app.Events.On("wails:oauth:success", func(e *application.WailsEvent) { + // Do something with the user data +}) +``` + +Both these events are constants in the plugin: + +```go +const ( + Success = "wails:oauth:success" + Error = "wails:oauth:error" +) +``` + +There is a working example of GitHub auth in the `v3/examples/oauth` directory. + +## Logging Out + +To log out, you can call the relevant `Logout` method for the provider: + +```go + err := oAuthPlugin.GithubLogout() +``` + +On success, the plugin will send a `wails:oauth:loggedout` event. On failure, it will send a `wails:oauth:error` event. + +## Support + +If you find a bug in this plugin, please raise a ticket on the Wails [Issue Tracker](https://github.com/wailsapp/wails/issues). diff --git a/v3/plugins/oauth/plugin.go b/v3/plugins/oauth/plugin.go new file mode 100644 index 000000000..d8ad1e5f2 --- /dev/null +++ b/v3/plugins/oauth/plugin.go @@ -0,0 +1,835 @@ +package oauth + +import ( + "fmt" + "github.com/gorilla/pat" + "github.com/gorilla/sessions" + "github.com/markbates/goth" + "github.com/markbates/goth/gothic" + "github.com/wailsapp/wails/v3/pkg/application" + "net/http" + "time" +) + +// ---------------- Plugin Setup ---------------- +// This is the main plugin struct. It can be named anything you like. +// It must implement the application.Plugin interface. +// Both the Init() and Shutdown() methods are called synchronously when the app starts and stops. + +const ( + Success = "wails:oauth:success" + Error = "wails:oauth:error" + LoggedOut = "wails:oauth:loggedout" +) + +type Plugin struct { + config Config + server *http.Server + router *pat.Router +} + +type Config struct { + + // Address to bind the temporary webserver to + // Defaults to localhost:9876 + Address string + + // SessionSecret is the secret used to encrypt the session store. + SessionSecret string + + // MaxAge is the maximum age of the session in seconds. + MaxAge int + + // Providers is a list of goth providers to use. + Providers []goth.Provider + + // WindowConfig is the configuration for the window that will be opened + // to perform the OAuth login. + WindowConfig *application.WebviewWindowOptions +} + +func NewPlugin(config Config) *Plugin { + result := &Plugin{ + config: config, + } + if result.config.MaxAge == 0 { + result.config.MaxAge = 86400 * 30 // 30 days + } + if result.config.Address == "" { + result.config.Address = "localhost:9876" + } + if result.config.WindowConfig == nil { + result.config.WindowConfig = &application.WebviewWindowOptions{ + Title: "OAuth Login", + Width: 600, + Height: 850, + Hidden: true, + } + } + return result +} + +func (p *Plugin) Shutdown() { + if p.server != nil { + p.server.Close() + } +} + +func (p *Plugin) Name() string { + return "github.com/wailsapp/wails/v3/plugins/oauth" +} + +func (p *Plugin) Init() error { + + store := sessions.NewCookieStore([]byte(p.config.SessionSecret)) + store.MaxAge(p.config.MaxAge) + store.Options.Path = "/" + store.Options.HttpOnly = true + store.Options.Secure = false + + gothic.Store = store + goth.UseProviders(p.config.Providers...) + + return nil +} + +func (p *Plugin) CallableByJS() []string { + return []string{ + "Github", + "LogoutGithub", + "Amazon", + "LogoutAmazon", + "Apple", + "LogoutApple", + "Auth0", + "LogoutAuth0", + "AzureAD", + "LogoutAzureAD", + "BattleNet", + "LogoutBattleNet", + "Bitbucket", + "LogoutBitbucket", + "Box", + "LogoutBox", + "Dailymotion", + "LogoutDailymotion", + "Deezer", + "LogoutDeezer", + "DigitalOcean", + "LogoutDigitalOcean", + "Discord", + "LogoutDiscord", + "Dropbox", + "LogoutDropbox", + "EveOnline", + "LogoutEveOnline", + "Facebook", + "LogoutFacebook", + "Fitbit", + "LogoutFitbit", + "Gitea", + "LogoutGitea", + "Gitlab", + "LogoutGitlab", + "Google", + "LogoutGoogle", + "GooglePlus", + "LogoutGooglePlus", + "Heroku", + "LogoutHeroku", + "Intercom", + "LogoutIntercom", + "Instagram", + "LogoutInstagram", + "Kakao", + "LogoutKakao", + "LastFM", + "LogoutLastFM", + "LinkedIn", + "LogoutLinkedIn", + "Line", + "LogoutLine", + "Mastodon", + "LogoutMastodon", + "Meetup", + "LogoutMeetup", + "MicrosoftOnline", + "LogoutMicrosoftOnline", + "Naver", + "LogoutNaver", + "NextCloud", + "LogoutNextCloud", + "Okta", + "LogoutOkta", + "Onedrive", + "LogoutOnedrive", + "OpenIDConnect", + "LogoutOpenIDConnect", + "Patreon", + "LogoutPatreon", + "PayPal", + "LogoutPayPal", + "SalesForce", + "LogoutSalesForce", + "SeaTalk", + "LogoutSeaTalk", + "Shopify", + "LogoutShopify", + "Slack", + "LogoutSlack", + "SoundCloud", + "LogoutSoundCloud", + "Spotify", + "LogoutSpotify", + "Steam", + "LogoutSteam", + "Strava", + "LogoutStrava", + "Stripe", + "LogoutStripe", + "TikTok", + "LogoutTikTok", + "Twitter", + "LogoutTwitter", + "TwitterV2", + "LogoutTwitterV2", + "Typetalk", + "LogoutTypetalk", + "Twitch", + "LogoutTwitch", + "Uber", + "LogoutUber", + "VK", + "LogoutVK", + "WeCom", + "LogoutWeCom", + "Wepay", + "LogoutWepay", + "Xero", + "LogoutXero", + "Yahoo", + "LogoutYahoo", + "Yammer", + "LogoutYammer", + "Yandex", + "LogoutYandex", + "Zoom", + "LogoutZoom", + } +} + +func (p *Plugin) InjectJS() string { + return "" +} + +func (p *Plugin) start(provider string) error { + if p.server != nil { + return fmt.Errorf("server already processing request. Please wait for the current login to complete") + } + + router := pat.New() + router.Get("/auth/{provider}/callback", func(res http.ResponseWriter, req *http.Request) { + + event := &application.WailsEvent{Name: Success} + + user, err := gothic.CompleteUserAuth(res, req) + if err != nil { + event.Data = err.Error() + event.Name = Error + } else { + event.Data = user + } + application.Get().Events.Emit(event) + _ = p.server.Close() + p.server = nil + }) + + router.Get("/auth/{provider}", func(res http.ResponseWriter, req *http.Request) { + gothic.BeginAuthHandler(res, req) + }) + + p.server = &http.Server{ + Addr: p.config.Address, + Handler: router, + } + + go p.server.ListenAndServe() + + // Keep trying to connect until we succeed + var keepTrying = true + var connected = false + + go func() { + time.Sleep(3 * time.Second) + keepTrying = false + }() + + for keepTrying { + _, err := http.Get("http://" + p.config.Address) + if err == nil { + connected = true + break + } + } + + if !connected { + return fmt.Errorf("server failed to start") + } + + // create a window + p.config.WindowConfig.URL = "http://" + p.config.Address + "/auth/" + provider + window := application.Get().NewWebviewWindowWithOptions(*p.config.WindowConfig) + window.Show() + + application.Get().Events.On(Success, func(event *application.WailsEvent) { + window.Close() + }) + application.Get().Events.On(Error, func(event *application.WailsEvent) { + window.Close() + }) + + return nil +} + +func (p *Plugin) logout(provider string) error { + if p.server != nil { + return fmt.Errorf("server already processing request. Please wait for the current operation to complete") + } + + router := pat.New() + router.Get("/logout/{provider}", func(res http.ResponseWriter, req *http.Request) { + err := gothic.Logout(res, req) + event := &application.WailsEvent{Name: LoggedOut} + if err != nil { + event.Data = err.Error() + event.Name = Error + } + application.Get().Events.Emit(event) + _ = p.server.Close() + p.server = nil + }) + + p.server = &http.Server{ + Addr: p.config.Address, + Handler: router, + } + + go p.server.ListenAndServe() + + // Keep trying to connect until we succeed + var keepTrying = true + var connected = false + + go func() { + time.Sleep(3 * time.Second) + keepTrying = false + }() + + for keepTrying { + _, err := http.Get("http://" + p.config.Address) + if err == nil { + connected = true + break + } + } + + if !connected { + return fmt.Errorf("server failed to start") + } + + // create a window + p.config.WindowConfig.URL = "http://" + p.config.Address + "/logout/" + provider + window := application.Get().NewWebviewWindowWithOptions(*p.config.WindowConfig) + window.Show() + + application.Get().Events.On(LoggedOut, func(event *application.WailsEvent) { + window.Close() + }) + application.Get().Events.On(Error, func(event *application.WailsEvent) { + window.Close() + }) + + return nil +} + +// ---------------- Plugin Methods ---------------- + +func (p *Plugin) Amazon() error { + return p.start("amazon") +} + +func (p *Plugin) Apple() error { + return p.start("apple") +} + +func (p *Plugin) Auth0() error { + return p.start("auth0") +} + +func (p *Plugin) AzureAD() error { + return p.start("azuread") +} + +func (p *Plugin) BattleNet() error { + return p.start("battlenet") +} + +func (p *Plugin) Bitbucket() error { + return p.start("bitbucket") +} + +func (p *Plugin) Box() error { + return p.start("box") +} + +func (p *Plugin) Dailymotion() error { + return p.start("dailymotion") +} + +func (p *Plugin) Deezer() error { + return p.start("deezer") +} + +func (p *Plugin) DigitalOcean() error { + return p.start("digitalocean") +} + +func (p *Plugin) Discord() error { + return p.start("discord") +} + +func (p *Plugin) Dropbox() error { + return p.start("dropbox") +} + +func (p *Plugin) EveOnline() error { + return p.start("eveonline") +} + +func (p *Plugin) Facebook() error { + return p.start("facebook") +} + +func (p *Plugin) Fitbit() error { + return p.start("fitbit") +} + +func (p *Plugin) Gitea() error { + return p.start("gitea") +} + +func (p *Plugin) Gitlab() error { + return p.start("gitlab") +} + +func (p *Plugin) Github() error { + return p.start("github") +} + +func (p *Plugin) Google() error { + return p.start("google") +} + +func (p *Plugin) GooglePlus() error { + return p.start("gplus") +} + +func (p *Plugin) Heroku() error { + return p.start("heroku") +} + +func (p *Plugin) Intercom() error { + return p.start("intercom") +} + +func (p *Plugin) Instagram() error { + return p.start("instagram") +} + +func (p *Plugin) Kakao() error { + return p.start("kakao") +} + +func (p *Plugin) LastFM() error { + return p.start("lastfm") +} + +func (p *Plugin) LinkedIn() error { + return p.start("linkedin") +} + +func (p *Plugin) Line() error { + return p.start("line") +} + +func (p *Plugin) Mastodon() error { + return p.start("mastodon") +} + +func (p *Plugin) Meetup() error { + return p.start("meetup") +} + +func (p *Plugin) MicrosoftOnline() error { + return p.start("microsoftonline") +} + +func (p *Plugin) Naver() error { + return p.start("naver") +} + +func (p *Plugin) NextCloud() error { + return p.start("nextcloud") +} + +func (p *Plugin) Okta() error { + return p.start("okta") +} + +func (p *Plugin) Onedrive() error { + return p.start("onedrive") +} + +func (p *Plugin) OpenIDConnect() error { + return p.start("openid-connect") +} + +func (p *Plugin) Patreon() error { + return p.start("patreon") +} + +func (p *Plugin) PayPal() error { + return p.start("paypal") +} + +func (p *Plugin) SalesForce() error { + return p.start("salesforce") +} + +func (p *Plugin) SeaTalk() error { + return p.start("seatalk") +} + +func (p *Plugin) Shopify() error { + return p.start("shopify") +} + +func (p *Plugin) Slack() error { + return p.start("slack") +} + +func (p *Plugin) SoundCloud() error { + return p.start("soundcloud") +} + +func (p *Plugin) Spotify() error { + return p.start("spotify") +} + +func (p *Plugin) Steam() error { + return p.start("steam") +} + +func (p *Plugin) Strava() error { + return p.start("strava") +} + +func (p *Plugin) Stripe() error { + return p.start("stripe") +} + +func (p *Plugin) TikTok() error { + return p.start("tiktok") +} + +func (p *Plugin) Twitter() error { + return p.start("twitter") +} + +func (p *Plugin) TwitterV2() error { + return p.start("twitterv2") +} + +func (p *Plugin) Typetalk() error { + return p.start("typetalk") +} + +func (p *Plugin) Twitch() error { + return p.start("twitch") +} + +func (p *Plugin) Uber() error { + return p.start("uber") +} + +func (p *Plugin) VK() error { + return p.start("vk") +} + +func (p *Plugin) WeCom() error { + return p.start("wecom") +} + +func (p *Plugin) Wepay() error { + return p.start("wepay") +} + +func (p *Plugin) Xero() error { + return p.start("xero") +} + +func (p *Plugin) Yahoo() error { + return p.start("yahoo") +} + +func (p *Plugin) Yammer() error { + return p.start("yammer") +} + +func (p *Plugin) Yandex() error { + return p.start("yandex") +} + +func (p *Plugin) Zoom() error { + return p.start("zoom") +} + +func (p *Plugin) LogoutAmazon() error { + return p.logout("amazon") +} + +func (p *Plugin) LogoutApple() error { + return p.logout("apple") +} + +func (p *Plugin) LogoutAuth0() error { + return p.logout("auth0") +} + +func (p *Plugin) LogoutAzureAD() error { + return p.logout("azuread") +} + +func (p *Plugin) LogoutBattleNet() error { + return p.logout("battlenet") +} + +func (p *Plugin) LogoutBitbucket() error { + return p.logout("bitbucket") +} + +func (p *Plugin) LogoutBox() error { + return p.logout("box") +} + +func (p *Plugin) LogoutDailymotion() error { + return p.logout("dailymotion") +} + +func (p *Plugin) LogoutDeezer() error { + return p.logout("deezer") +} + +func (p *Plugin) LogoutDigitalOcean() error { + return p.logout("digitalocean") +} + +func (p *Plugin) LogoutDiscord() error { + return p.logout("discord") +} + +func (p *Plugin) LogoutDropbox() error { + return p.logout("dropbox") +} + +func (p *Plugin) LogoutEveOnline() error { + return p.logout("eveonline") +} + +func (p *Plugin) LogoutFacebook() error { + return p.logout("facebook") +} + +func (p *Plugin) LogoutFitbit() error { + return p.logout("fitbit") +} + +func (p *Plugin) LogoutGitea() error { + return p.logout("gitea") +} + +func (p *Plugin) LogoutGitlab() error { + return p.logout("gitlab") +} + +func (p *Plugin) LogoutGithub() error { + return p.logout("github") +} + +func (p *Plugin) LogoutGoogle() error { + return p.logout("google") +} + +func (p *Plugin) LogoutGooglePlus() error { + return p.logout("gplus") +} + +func (p *Plugin) LogoutHeroku() error { + return p.logout("heroku") +} + +func (p *Plugin) LogoutIntercom() error { + return p.logout("intercom") +} + +func (p *Plugin) LogoutInstagram() error { + return p.logout("instagram") +} + +func (p *Plugin) LogoutKakao() error { + return p.logout("kakao") +} + +func (p *Plugin) LogoutLastFM() error { + return p.logout("lastfm") +} + +func (p *Plugin) LogoutLinkedIn() error { + return p.logout("linkedin") +} + +func (p *Plugin) LogoutLine() error { + return p.logout("line") +} + +func (p *Plugin) LogoutMastodon() error { + return p.logout("mastodon") +} + +func (p *Plugin) LogoutMeetup() error { + return p.logout("meetup") +} + +func (p *Plugin) LogoutMicrosoftOnline() error { + return p.logout("microsoftonline") +} + +func (p *Plugin) LogoutNaver() error { + return p.logout("naver") +} + +func (p *Plugin) LogoutNextCloud() error { + return p.logout("nextcloud") +} + +func (p *Plugin) LogoutOkta() error { + return p.logout("okta") +} + +func (p *Plugin) LogoutOnedrive() error { + return p.logout("onedrive") +} + +func (p *Plugin) LogoutOpenIDConnect() error { + return p.logout("openid-connect") +} + +func (p *Plugin) LogoutPatreon() error { + return p.logout("patreon") +} + +func (p *Plugin) LogoutPayPal() error { + return p.logout("paypal") +} + +func (p *Plugin) LogoutSalesForce() error { + return p.logout("salesforce") +} + +func (p *Plugin) LogoutSeaTalk() error { + return p.logout("seatalk") +} + +func (p *Plugin) LogoutShopify() error { + return p.logout("shopify") +} + +func (p *Plugin) LogoutSlack() error { + return p.logout("slack") +} + +func (p *Plugin) LogoutSoundCloud() error { + return p.logout("soundcloud") +} + +func (p *Plugin) LogoutSpotify() error { + return p.logout("spotify") +} + +func (p *Plugin) LogoutSteam() error { + return p.logout("steam") +} + +func (p *Plugin) LogoutStrava() error { + return p.logout("strava") +} + +func (p *Plugin) LogoutStripe() error { + return p.logout("stripe") +} + +func (p *Plugin) LogoutTikTok() error { + return p.logout("tiktok") +} + +func (p *Plugin) LogoutTwitter() error { + return p.logout("twitter") +} + +func (p *Plugin) LogoutTwitterV2() error { + return p.logout("twitterv2") +} + +func (p *Plugin) LogoutTypetalk() error { + return p.logout("typetalk") +} + +func (p *Plugin) LogoutTwitch() error { + return p.logout("twitch") +} + +func (p *Plugin) LogoutUber() error { + return p.logout("uber") +} + +func (p *Plugin) LogoutVK() error { + return p.logout("vk") +} + +func (p *Plugin) LogoutWeCom() error { + return p.logout("wecom") +} + +func (p *Plugin) LogoutWepay() error { + return p.logout("wepay") +} + +func (p *Plugin) LogoutXero() error { + return p.logout("xero") +} + +func (p *Plugin) LogoutYahoo() error { + return p.logout("yahoo") +} + +func (p *Plugin) LogoutYammer() error { + return p.logout("yammer") +} + +func (p *Plugin) LogoutYandex() error { + return p.logout("yandex") +} + +func (p *Plugin) LogoutZoom() error { + return p.logout("zoom") +} diff --git a/v3/plugins/oauth/plugin.js b/v3/plugins/oauth/plugin.js new file mode 100644 index 000000000..4ca97e971 --- /dev/null +++ b/v3/plugins/oauth/plugin.js @@ -0,0 +1,608 @@ +// Calculate FNV hash of "auth.Github" to get the plugin ID +// https://www.fileformat.info/tool/hash.htm?text=auth.Github +// 4111675027 + + +function Amazon() { + return wails.CallByID(2331828509); +} + +function Apple() { + return wails.CallByID(3176757999); +} + +function Auth0() { + return wails.CallByID(2541844605); +} + +function AzureAD() { + return wails.CallByID(4136596197); +} + +function BattleNet() { + return wails.CallByID(1768856346); +} + +function Bitbucket() { + return wails.CallByID(526693694); +} + +function Box() { + return wails.CallByID(2583659906); +} + +function Dailymotion() { + return wails.CallByID(1431387676); +} + +function Deezer() { + return wails.CallByID(828114094); +} + +function DigitalOcean() { + return wails.CallByID(3019360447); +} + +function Discord() { + return wails.CallByID(655199039); +} + +function Dropbox() { + return wails.CallByID(3446570357); +} + +function EveOnline() { + return wails.CallByID(1615270010); +} + +function Facebook() { + return wails.CallByID(825207297); +} + +function Fitbit() { + return wails.CallByID(1290725671); +} + +function Gitea() { + return wails.CallByID(3921640321); +} + +function Github() { + return wails.CallByID(1424971906); +} + +function Gitlab() { + return wails.CallByID(387644474); +} + +function Google() { + return wails.CallByID(1712198358); +} + +function GooglePlus() { + return wails.CallByID(2550604010); +} + +function Heroku() { + return wails.CallByID(3655952201); +} + +function Instagram() { + return wails.CallByID(65944971); +} + +function Intercom() { + return wails.CallByID(10529210); +} + +function Kakao() { + return wails.CallByID(3034570310); +} + +function LastFM() { + return wails.CallByID(1802166992); +} + +function Line() { + return wails.CallByID(2789106871); +} + +function LinkedIn() { + return wails.CallByID(1277552879); +} + +function LogoutAmazon() { + return wails.CallByID(469077327); +} + +function LogoutApple() { + return wails.CallByID(3655868585); +} + +function LogoutAuth0() { + return wails.CallByID(18151695); +} + +function LogoutAzureAD() { + return wails.CallByID(2138080511); +} + +function LogoutBattleNet() { + return wails.CallByID(4280940652); +} + +function LogoutBitbucket() { + return wails.CallByID(3756772960); +} + +function LogoutBox() { + return wails.CallByID(2810174732); +} + +function LogoutDailymotion() { + return wails.CallByID(2060789018); +} + +function LogoutDeezer() { + return wails.CallByID(1779336988); +} + +function LogoutDigitalOcean() { + return wails.CallByID(2745195285); +} + +function LogoutDiscord() { + return wails.CallByID(1756238441); +} + +function LogoutDropbox() { + return wails.CallByID(1276092991); +} + +function LogoutEveOnline() { + return wails.CallByID(3427696132); +} + +function LogoutFacebook() { + return wails.CallByID(3289650391); +} + +function LogoutFitbit() { + return wails.CallByID(4026910725); +} + +function LogoutGitea() { + return wails.CallByID(289654943); +} + +function LogoutGithub() { + return wails.CallByID(1644797072); +} + +function LogoutGitlab() { + return wails.CallByID(654269224); +} + +function LogoutGoogle() { + return wails.CallByID(4020864940); +} + +function LogoutGooglePlus() { + return wails.CallByID(3868989020); +} + +function LogoutHeroku() { + return wails.CallByID(2618955859); +} + +function LogoutInstagram() { + return wails.CallByID(931376085); +} + +function LogoutIntercom() { + return wails.CallByID(4092924400); +} + +function LogoutKakao() { + return wails.CallByID(3794403964); +} + +function LogoutLastFM() { + return wails.CallByID(1376498158); +} + +function LogoutLine() { + return wails.CallByID(3582304913); +} + +function LogoutLinkedIn() { + return wails.CallByID(1607790273); +} + +function LogoutMastodon() { + return wails.CallByID(4210982224); +} + +function LogoutMeetup() { + return wails.CallByID(3715047585); +} + +function LogoutMicrosoftOnline() { + return wails.CallByID(97532862); +} + +function LogoutNaver() { + return wails.CallByID(2633825507); +} + +function LogoutNextCloud() { + return wails.CallByID(3261782247); +} + +function LogoutOkta() { + return wails.CallByID(624232214); +} + +function LogoutOnedrive() { + return wails.CallByID(814508603); +} + +function LogoutOpenIDConnect() { + return wails.CallByID(1020114184); +} + +function LogoutPatreon() { + return wails.CallByID(202386648); +} + +function LogoutPayPal() { + return wails.CallByID(1094363076); +} + +function LogoutSalesForce() { + return wails.CallByID(186208216); +} + +function LogoutSeaTalk() { + return wails.CallByID(967155452); +} + +function LogoutShopify() { + return wails.CallByID(710746663); +} + +function LogoutSlack() { + return wails.CallByID(2829130917); +} + +function LogoutSoundCloud() { + return wails.CallByID(1958837967); +} + +function LogoutSpotify() { + return wails.CallByID(3505564851); +} + +function LogoutSteam() { + return wails.CallByID(1111934849); +} + +function LogoutStrava() { + return wails.CallByID(1393113524); +} + +function LogoutStripe() { + return wails.CallByID(2886428130); +} + +function LogoutTikTok() { + return wails.CallByID(3971249709); +} + +function LogoutTwitch() { + return wails.CallByID(88308904); +} + +function LogoutTwitter() { + return wails.CallByID(1067400118); +} + +function LogoutTwitterV2() { + return wails.CallByID(2761439446); +} + +function LogoutTypetalk() { + return wails.CallByID(128226145); +} + +function LogoutUber() { + return wails.CallByID(3429923567); +} + +function LogoutVK() { + return wails.CallByID(1951646996); +} + +function LogoutWeCom() { + return wails.CallByID(1686099456); +} + +function LogoutWepay() { + return wails.CallByID(1261265379); +} + +function LogoutXero() { + return wails.CallByID(2890165735); +} + +function LogoutYahoo() { + return wails.CallByID(2054451877); +} + +function LogoutYammer() { + return wails.CallByID(407239716); +} + +function LogoutYandex() { + return wails.CallByID(2947524466); +} + +function LogoutZoom() { + return wails.CallByID(4146090020); +} + +function Mastodon() { + return wails.CallByID(3603981882); +} + +function Meetup() { + return wails.CallByID(1147762171); +} + +function MicrosoftOnline() { + return wails.CallByID(1805946880); +} + +function Naver() { + return wails.CallByID(749569909); +} + +function NextCloud() { + return wails.CallByID(1924196249); +} + +function Okta() { + return wails.CallByID(412847760); +} + +function Onedrive() { + return wails.CallByID(3973355685); +} + +function OpenIDConnect() { + return wails.CallByID(2729309874); +} + +function Patreon() { + return wails.CallByID(3723931538); +} + +function PayPal() { + return wails.CallByID(3284153782); +} + +function SalesForce() { + return wails.CallByID(1734014770); +} + +function SeaTalk() { + return wails.CallByID(3129463546); +} + +function Shopify() { + return wails.CallByID(1877652737); +} + +function Slack() { + return wails.CallByID(812719543); +} + +function SoundCloud() { + return wails.CallByID(3726943661); +} + +function Spotify() { + return wails.CallByID(256059429); +} + +function Steam() { + return wails.CallByID(4258843427); +} + +function Strava() { + return wails.CallByID(1874925690); +} + +function Stripe() { + return wails.CallByID(2661473260); +} + +function TikTok() { + return wails.CallByID(1284732707); +} + +function Twitch() { + return wails.CallByID(487656954); +} + +function Twitter() { + return wails.CallByID(3389126356); +} + +function TwitterV2() { + return wails.CallByID(4048286460); +} + +function Typetalk() { + return wails.CallByID(1880294983); +} + +function Uber() { + return wails.CallByID(2810476945); +} + +function VK() { + return wails.CallByID(1043499094); +} + +function WeCom() { + return wails.CallByID(2495227034); +} + +function Wepay() { + return wails.CallByID(3921566885); +} + +function Xero() { + return wails.CallByID(1174147097); +} + +function Yahoo() { + return wails.CallByID(4227069139); +} + +function Yammer() { + return wails.CallByID(2053813786); +} + +function Yandex() { + return wails.CallByID(1095025512); +} + +function Zoom() { + return wails.CallByID(391053986); +} + + +export default { + Github, + LogoutGithub, + Amazon, + LogoutAmazon, + Apple, + LogoutApple, + Auth0, + LogoutAuth0, + AzureAD, + LogoutAzureAD, + BattleNet, + LogoutBattleNet, + Bitbucket, + LogoutBitbucket, + Box, + LogoutBox, + Dailymotion, + LogoutDailymotion, + Deezer, + LogoutDeezer, + DigitalOcean, + LogoutDigitalOcean, + Discord, + LogoutDiscord, + Dropbox, + LogoutDropbox, + EveOnline, + LogoutEveOnline, + Facebook, + LogoutFacebook, + Fitbit, + LogoutFitbit, + Gitea, + LogoutGitea, + Gitlab, + LogoutGitlab, + Google, + LogoutGoogle, + GooglePlus, + LogoutGooglePlus, + Heroku, + LogoutHeroku, + Intercom, + LogoutIntercom, + Instagram, + LogoutInstagram, + Kakao, + LogoutKakao, + LastFM, + LogoutLastFM, + LinkedIn, + LogoutLinkedIn, + Line, + LogoutLine, + Mastodon, + LogoutMastodon, + Meetup, + LogoutMeetup, + MicrosoftOnline, + LogoutMicrosoftOnline, + Naver, + LogoutNaver, + NextCloud, + LogoutNextCloud, + Okta, + LogoutOkta, + Onedrive, + LogoutOnedrive, + OpenIDConnect, + LogoutOpenIDConnect, + Patreon, + LogoutPatreon, + PayPal, + LogoutPayPal, + SalesForce, + LogoutSalesForce, + SeaTalk, + LogoutSeaTalk, + Shopify, + LogoutShopify, + Slack, + LogoutSlack, + SoundCloud, + LogoutSoundCloud, + Spotify, + LogoutSpotify, + Steam, + LogoutSteam, + Strava, + LogoutStrava, + Stripe, + LogoutStripe, + TikTok, + LogoutTikTok, + Twitter, + LogoutTwitter, + TwitterV2, + LogoutTwitterV2, + Typetalk, + LogoutTypetalk, + Twitch, + LogoutTwitch, + Uber, + LogoutUber, + VK, + LogoutVK, + WeCom, + LogoutWeCom, + Wepay, + LogoutWepay, + Xero, + LogoutXero, + Yahoo, + LogoutYahoo, + Yammer, + LogoutYammer, + Yandex, + LogoutYandex, + Zoom, + LogoutZoom +}; diff --git a/v3/plugins/oauth/plugin.yaml b/v3/plugins/oauth/plugin.yaml new file mode 100644 index 000000000..c6932094e --- /dev/null +++ b/v3/plugins/oauth/plugin.yaml @@ -0,0 +1,11 @@ +# This is the plugin definition file for the "oauth" plugin. +--- +Name: oauth +Description: Provides oauth capabilities. +Author: Lea Anthony +Version: v1.0.0 +Website: https://wails.io +Repository: https://github.com/wailsapp/wails/v3/plugins/oauth +License: MIT + + diff --git a/v3/plugins/single_instance/README.md b/v3/plugins/single_instance/README.md new file mode 100644 index 000000000..87cb417ca --- /dev/null +++ b/v3/plugins/single_instance/README.md @@ -0,0 +1,31 @@ +# single-instance Plugin + +This plugin provides a way to prevent multiple launches of your application. + +## Installation + +Add the plugin to the `Plugins` option in the Applications options: + +```go + Plugins: map[string]application.Plugin{ + "single_instance": single_instance.NewPlugin(&single_instance.Config{ + // When true, the original app will be activated when a second instance is launched + ActivateAppOnSubsequentLaunch: true, + } + }, +``` + +## Usage + +This plugin prevents the launch of multiple copies of your application. +If you set `ActivateAppOnSubsequentLaunch` to true the original app will be activated when a second instance is launched. + +## Support + +If you find a bug in this plugin, please raise a ticket [here](https://github.com/plugin/repository). +Please do not contact the Wails team for support. + +## Credit + +This plugin contains modified code from the awesome [go-singleinstance](https://github.com/allan-simon/go-singleinstance) module (c) 2015 Allan Simon. +Original license file has been renamed `go-singleinstance.LICENSE` and is available [here](./singleinstance_LICENSE). diff --git a/v3/plugins/single_instance/go-singleinstance.LICENSE b/v3/plugins/single_instance/go-singleinstance.LICENSE new file mode 100644 index 000000000..67bb9ea50 --- /dev/null +++ b/v3/plugins/single_instance/go-singleinstance.LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2015 Allan Simon + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/v3/plugins/single_instance/lock.go b/v3/plugins/single_instance/lock.go new file mode 100644 index 000000000..ba2758769 --- /dev/null +++ b/v3/plugins/single_instance/lock.go @@ -0,0 +1,16 @@ +package single_instance + +import ( + "os" + "strconv" +) + +func GetLockFilePid(filename string) (pid int, err error) { + contents, err := os.ReadFile(filename) + if err != nil { + return + } + + pid, err = strconv.Atoi(string(contents)) + return +} diff --git a/v3/plugins/single_instance/lock_posix.go b/v3/plugins/single_instance/lock_posix.go new file mode 100644 index 000000000..06139d52b --- /dev/null +++ b/v3/plugins/single_instance/lock_posix.go @@ -0,0 +1,38 @@ +//go:build !windows + +package single_instance + +import ( + "os" + "strconv" + "syscall" +) + +// CreateLockFile tries to create a file with given name and acquire an +// exclusive lock on it. If the file already exists AND is still locked, it will +// fail. +func CreateLockFile(filename string, PID int) (*os.File, error) { + file, err := os.OpenFile(filename, os.O_WRONLY|os.O_CREATE, 0600) + if err != nil { + return nil, err + } + + err = syscall.Flock(int(file.Fd()), syscall.LOCK_EX|syscall.LOCK_NB) + if err != nil { + file.Close() + return nil, err + } + + // Write PID to lock file + contents := strconv.Itoa(PID) + if err := file.Truncate(0); err != nil { + file.Close() + return nil, err + } + if _, err := file.WriteString(contents); err != nil { + file.Close() + return nil, err + } + + return file, nil +} diff --git a/v3/plugins/single_instance/lock_windows.go b/v3/plugins/single_instance/lock_windows.go new file mode 100644 index 000000000..67aabe685 --- /dev/null +++ b/v3/plugins/single_instance/lock_windows.go @@ -0,0 +1,35 @@ +//go:build windows + +package single_instance + +import ( + "os" + "strconv" +) + +// CreateLockFile tries to create a file with given name and acquire an +// exclusive lock on it. If the file already exists AND is still locked, it will +// fail. +func CreateLockFile(filename string, PID int) (*os.File, error) { + if _, err := os.Stat(filename); err == nil { + // If the file exists, we first try to remove it + if err = os.Remove(filename); err != nil { + return nil, err + } + } else if !os.IsNotExist(err) { + return nil, err + } + + file, err := os.OpenFile(filename, os.O_CREATE|os.O_EXCL|os.O_WRONLY, 0600) + if err != nil { + return nil, err + } + + // Write PID to lock file + _, err = file.WriteString(strconv.Itoa(PID)) + if err != nil { + return nil, err + } + + return file, nil +} diff --git a/v3/plugins/single_instance/plugin.go b/v3/plugins/single_instance/plugin.go new file mode 100644 index 000000000..b3bad39f5 --- /dev/null +++ b/v3/plugins/single_instance/plugin.go @@ -0,0 +1,81 @@ +package single_instance + +import ( + "fmt" + "os" + "path/filepath" + + "github.com/wailsapp/wails/v3/pkg/application" +) + +type Config struct { + // Add any configuration options here + LockFileName string + LockFilePath string + ActivateAppOnSubsequentLaunch bool +} + +type Plugin struct { + config *Config + lockfile *os.File +} + +func (p *Plugin) CallableByJS() []string { + return []string{} +} + +func (p *Plugin) InjectJS() string { + return "" +} + +func NewPlugin(config *Config) *Plugin { + if config.LockFilePath == "" { + // Use the system default temp directory + config.LockFilePath = os.TempDir() + } + if config.LockFileName == "" { + // Use the executable name + config.LockFileName = filepath.Base(os.Args[0]) + ".lock" + } + return &Plugin{ + config: config, + } +} + +// Shutdown is called when the app is shutting down +func (p *Plugin) Shutdown() { + p.lockfile.Close() +} + +// Name returns the name of the plugin. +func (p *Plugin) Name() string { + return "github.com/wailsapp/wails/v3/plugins/single-instance" +} + +// Init is called when the app is starting up. You can use this to +// initialise any resources you need. You can also access the application +// instance via the app property. +func (p *Plugin) Init() error { + var err error + lockfileName := p.config.LockFilePath + "/" + p.config.LockFileName + p.lockfile, err = CreateLockFile(lockfileName, application.Get().GetPID()) + if err != nil { + if p.config.ActivateAppOnSubsequentLaunch { + pid, err := GetLockFilePid(lockfileName) + if err != nil { + return err + } + err = p.activeInstance(pid) + if err != nil { + return err + } + } + return fmt.Errorf("another instance of this application is already running") + } + return nil +} + +// Exported returns a list of exported methods that can be called from the frontend +func (p *Plugin) Exported() []string { + return []string{} +} diff --git a/v3/plugins/single_instance/plugin.yaml b/v3/plugins/single_instance/plugin.yaml new file mode 100644 index 000000000..49416bba6 --- /dev/null +++ b/v3/plugins/single_instance/plugin.yaml @@ -0,0 +1,11 @@ +# This is the plugin definition file for the "single-instance" plugin. +--- +Name: single-instance +Description: Allows only a single instance of your application to run +Author: Lea Anthony +Version: v1.0.0 +Website: https://wails.io +Repository: https://github.com/wailsapp/wails/v3/plugins/single-instance +License: MIT + + diff --git a/v3/plugins/single_instance/plugin_darwin.go b/v3/plugins/single_instance/plugin_darwin.go new file mode 100644 index 000000000..c4f47f715 --- /dev/null +++ b/v3/plugins/single_instance/plugin_darwin.go @@ -0,0 +1,24 @@ +//go:build darwin + +package single_instance + +/* +#cgo CFLAGS: -mmacosx-version-min=10.13 -x objective-c +#cgo LDFLAGS: -framework Cocoa -framework AppKit -mmacosx-version-min=10.13 + +#import + +void activateApplicationWithProcessID(int pid) { + NSRunningApplication *app = [NSRunningApplication runningApplicationWithProcessIdentifier:pid]; + if (app != nil) { + [app unhide]; + [app activateWithOptions:(NSApplicationActivateAllWindows | NSApplicationActivateIgnoringOtherApps)]; + } +} +*/ +import "C" + +func (p *Plugin) activeInstance(pid int) error { + C.activateApplicationWithProcessID(C.int(pid)) + return nil +} diff --git a/v3/plugins/single_instance/plugin_linux.go b/v3/plugins/single_instance/plugin_linux.go new file mode 100644 index 000000000..e8ce64f02 --- /dev/null +++ b/v3/plugins/single_instance/plugin_linux.go @@ -0,0 +1,29 @@ +//go:build linux + +package single_instance + +import ( + "os" + "os/signal" + "syscall" + + "github.com/wailsapp/wails/v3/pkg/application" +) + +func init() { + sigc := make(chan os.Signal, 1) + signal.Notify(sigc, + syscall.SIGUSR2, + ) + go func() { + for { + <-sigc + application.Get().Show() + } + }() +} + +func (p *Plugin) activeInstance(pid int) error { + syscall.Kill(pid, syscall.SIGUSR2) + return nil +} diff --git a/v3/plugins/single_instance/plugin_windows.go b/v3/plugins/single_instance/plugin_windows.go new file mode 100644 index 000000000..2b231e12a --- /dev/null +++ b/v3/plugins/single_instance/plugin_windows.go @@ -0,0 +1,34 @@ +//go:build windows + +package single_instance + +import ( + "github.com/wailsapp/wails/v3/pkg/w32" + "syscall" +) + +type enumWindowsCallback func(hwnd syscall.Handle, lParam uintptr) uintptr + +func enumWindowsProc(hwnd syscall.Handle, lParam uintptr) uintptr { + _, processID := w32.GetWindowThreadProcessId(uintptr(hwnd)) + targetProcessID := uint32(lParam) + if uint32(processID) == targetProcessID { + // Bring the window forward + w32.SetForegroundWindow(w32.HWND(hwnd)) + } + + // Continue enumeration + return 1 +} + +func (p *Plugin) activeInstance(pid int) error { + + // Get the window associated with the process ID. + targetProcessID := uint32(pid) // Replace with the desired process ID + + w32.EnumWindows( + syscall.NewCallback(enumWindowsCallback(enumWindowsProc)), + uintptr(targetProcessID), + ) + return nil +} diff --git a/v3/plugins/sqlite/plugin.go b/v3/plugins/sqlite/plugin.go new file mode 100644 index 000000000..e9feadc13 --- /dev/null +++ b/v3/plugins/sqlite/plugin.go @@ -0,0 +1,169 @@ +package sqlite + +import ( + "database/sql" + _ "embed" + "errors" + "fmt" + _ "modernc.org/sqlite" + "strings" +) + +//go:embed sqlite_close.js +var closejs string + +//go:embed sqlite_open.js +var openjs string + +//go:embed sqlite_execute_select.js +var executeselectjs string + +// ---------------- Plugin Setup ---------------- +// This is the main plugin struct. It can be named anything you like. +// It must implement the application.Plugin interface. +// Both the Init() and Shutdown() methods are called synchronously when the app starts and stops. + +type Config struct { + // Add any configuration options here + CanOpenFromJS bool + CanCloseFromJS bool + DBFile string +} + +type Plugin struct { + config *Config + conn *sql.DB + callableMethods []string + js string +} + +func NewPlugin(config *Config) *Plugin { + return &Plugin{ + config: config, + } +} + +// Shutdown is called when the app is shutting down +// You can use this to clean up any resources you have allocated +func (p *Plugin) Shutdown() { + if p.conn != nil { + p.conn.Close() + } +} + +// Name returns the name of the plugin. +// You should use the go module format e.g. github.com/myuser/myplugin +func (p *Plugin) Name() string { + return "github.com/wailsapp/wails/v3/plugins/sqlite" +} + +// Init is called when the app is starting up. You can use this to +// initialise any resources you need. +func (p *Plugin) Init() error { + p.callableMethods = []string{"Execute", "Select"} + p.js = executeselectjs + if p.config.CanOpenFromJS { + p.callableMethods = append(p.callableMethods, "Open") + p.js += openjs + } + if p.config.CanCloseFromJS { + p.callableMethods = append(p.callableMethods, "Close") + p.js += closejs + } + if p.config.DBFile == "" { + return errors.New(`no database file specified. Please set DBFile in the config to either a filename or use ":memory:" to use an in-memory database`) + } + _, err := p.Open(p.config.DBFile) + if err != nil { + return err + } + + p.js += fmt.Sprintf("\nwindow.sqlite = { %s };", strings.Join(p.callableMethods, ", ")) + return nil +} + +// CallableByJS returns a list of exported methods that can be called from the frontend +func (p *Plugin) CallableByJS() []string { + return p.callableMethods +} + +func (p *Plugin) InjectJS() string { + return p.js +} + +// ---------------- Plugin Methods ---------------- +// Plugin methods are just normal Go methods. You can add as many as you like. +// The only requirement is that they are exported (start with a capital letter). +// You can also return any type that is JSON serializable. +// Any methods that you want to be callable from the frontend must be returned by the +// Exported() method above. +// See https://golang.org/pkg/encoding/json/#Marshal for more information. + +func (p *Plugin) Open(dbPath string) (string, error) { + var err error + p.conn, err = sql.Open("sqlite", dbPath) + if err != nil { + return "", err + } + return "Database connection opened", nil +} + +func (p *Plugin) Execute(query string, args ...any) error { + if p.conn == nil { + return errors.New("no open database connection") + } + + _, err := p.conn.Exec(query, args...) + if err != nil { + return err + } + return nil +} + +func (p *Plugin) Select(query string, args ...any) ([]map[string]any, error) { + if p.conn == nil { + return nil, errors.New("no open database connection") + } + + rows, err := p.conn.Query(query, args...) + if err != nil { + return nil, err + } + defer rows.Close() + + columns, err := rows.Columns() + var results []map[string]any + for rows.Next() { + values := make([]any, len(columns)) + pointers := make([]any, len(columns)) + + for i := range values { + pointers[i] = &values[i] + } + + if err := rows.Scan(pointers...); err != nil { + return nil, err + } + + row := make(map[string]any, len(columns)) + for i, column := range columns { + row[column] = values[i] + } + results = append(results, row) + } + + return results, nil +} + +func (p *Plugin) Close() error { + if p.conn == nil { + return errors.New("no open database connection") + } + + err := p.conn.Close() + if err != nil { + return err + } + p.conn = nil + return nil +} diff --git a/v3/plugins/sqlite/plugin.yaml b/v3/plugins/sqlite/plugin.yaml new file mode 100644 index 000000000..08a3d6926 --- /dev/null +++ b/v3/plugins/sqlite/plugin.yaml @@ -0,0 +1,11 @@ +# This is the plugin definition file for the "sqlite" plugin. +--- +Name: sqlite +Description: Provides easy access to SQLite DBs +Author: Lea Anthony +Version: v1.0.0 +Website: https://wails.io/plugins/sqlite +Repository: https://github.com/wailsapp/wails/v3/plugins/sqlite +License: MIT + + diff --git a/v3/plugins/sqlite/sqlite_close.js b/v3/plugins/sqlite/sqlite_close.js new file mode 100644 index 000000000..579af1a07 --- /dev/null +++ b/v3/plugins/sqlite/sqlite_close.js @@ -0,0 +1,8 @@ + +/** + * Close a sqlite DB. + * @returns {Promise} + */ +function Close() { + return wails.CallByID(3998329564); +} diff --git a/v3/plugins/sqlite/sqlite_execute_select.js b/v3/plugins/sqlite/sqlite_execute_select.js new file mode 100644 index 000000000..ab0bcd931 --- /dev/null +++ b/v3/plugins/sqlite/sqlite_execute_select.js @@ -0,0 +1,20 @@ + +/** + * Execute a SQL statement. + * @param statement {string} - SQL statement to execute. + @param args {...any} - Arguments to pass to the statement. + * @returns {Promise} + */ +function Execute(statement, ...args) { + return wails.CallByID(2804887383, statement, ...args); +} + +/** + * Perform a select query. + * @param statement {string} - Select SQL statement. + * @param args {...any} - Arguments to pass to the statement. + * @returns {Promise} + */ +function Select(statement, ...args) { + return wails.CallByID(2209315040, statement, ...args); +} diff --git a/v3/plugins/sqlite/sqlite_open.js b/v3/plugins/sqlite/sqlite_open.js new file mode 100644 index 000000000..e7385bb8b --- /dev/null +++ b/v3/plugins/sqlite/sqlite_open.js @@ -0,0 +1,8 @@ +/** + * Open a sqlite DB. + * @param filename {string} - file to open. + * @returns {Promise} + */ +function Open(filename) { + return wails.CallByID(147348976, filename); +} diff --git a/v3/plugins/start_at_login/README.md b/v3/plugins/start_at_login/README.md new file mode 100644 index 000000000..b8291a214 --- /dev/null +++ b/v3/plugins/start_at_login/README.md @@ -0,0 +1,34 @@ +# start_at_login Plugin + +This example plugin provides a way to generate hashes of strings. + +## Installation + +Add the plugin to the `Plugins` option in the Applications options: + +```go + Plugins: map[string]application.Plugin{ + "start_at_login": start_at_login.NewPlugin(), + }, +``` + +## Usage + +You can then call the methods from the frontend: + +```js + wails.Plugin("start_at_login","StartAtLogin", true).then((result) => console.log(result)) + wails.Plugin("start_at_login","IsStartAtLogin").then((result) => console.log(result)) +``` + +To use this from Go, create a new instance of the plugin, then call the methods on that: + +```go + start_at_login := start_at_login.NewPlugin(Options) + start_at_login.StartAtLogin(true) +``` + +## Support + +If you find a bug in this plugin, please raise a ticket [here](https://github.com/plugin/repository). +Please do not contact the Wails team for support. \ No newline at end of file diff --git a/v3/plugins/start_at_login/plugin.go b/v3/plugins/start_at_login/plugin.go new file mode 100644 index 000000000..4f6a69d89 --- /dev/null +++ b/v3/plugins/start_at_login/plugin.go @@ -0,0 +1,48 @@ +package start_at_login + +type Plugin struct { + disabled bool + options Config +} + +type Config struct { + // RegistryKey is the key in the registry to use for storing the start at login setting. + // This defaults to the name of the executable + RegistryKey string +} + +func NewPlugin(options Config) *Plugin { + return &Plugin{ + options: options, + } +} + +// Shutdown is called when the app is shutting down +// You can use this to clean up any resources you have allocated +func (p *Plugin) Shutdown() {} + +// Name returns the name of the plugin. +// You should use the go module format e.g. github.com/myuser/myplugin +func (p *Plugin) Name() string { + return "github.com/wailsapp/wails/v3/plugins/start_at_login" +} + +func (p *Plugin) Init() error { + // OS specific initialiser + err := p.init() + if err != nil { + return err + } + return nil +} + +func (p *Plugin) CallableByJS() []string { + return []string{ + "StartAtLogin", + "IsStartAtLogin", + } +} + +func (p *Plugin) InjectJS() string { + return "" +} diff --git a/v3/plugins/start_at_login/plugin.yaml b/v3/plugins/start_at_login/plugin.yaml new file mode 100644 index 000000000..36e8c6b3a --- /dev/null +++ b/v3/plugins/start_at_login/plugin.yaml @@ -0,0 +1,11 @@ +# This is the plugin definition file for the "start_at_login" plugin. +--- +Name: start_at_login +Description: Plugin to control whether the application should start at login +Author: Lea Anthony +Version: v1.0.0 +Website: https://wails.io/plugins/start_at_login +Repository: https://github.com/wailsapp/wails/v3/plugins/start_at_login +License: MIT + + diff --git a/v3/plugins/start_at_login/plugin_darwin.go b/v3/plugins/start_at_login/plugin_darwin.go new file mode 100644 index 000000000..9d5d3e82b --- /dev/null +++ b/v3/plugins/start_at_login/plugin_darwin.go @@ -0,0 +1,77 @@ +//go:build darwin + +package start_at_login + +import ( + "fmt" + "github.com/pkg/errors" + "github.com/samber/lo" + "github.com/wailsapp/wails/v3/pkg/application" + "github.com/wailsapp/wails/v3/pkg/mac" + "os" + "os/exec" + "path/filepath" + "strings" +) + +func (p *Plugin) init() error { + bundleID := mac.GetBundleID() + if bundleID == "" { + application.Get().Logger.Warn("StartAtLogin Plugin: Application is not in bundle. StartAtLogin will not work.") + p.disabled = true + } + return nil +} + +func (p *Plugin) StartAtLogin(enabled bool) error { + if p.disabled { + return nil + } + exe, err := os.Executable() + if err != nil { + return errors.Wrap(err, "Error running os.Executable:") + } + binName := filepath.Base(exe) + if !strings.HasSuffix(exe, "/Contents/MacOS/"+binName) { + return fmt.Errorf("app needs to be running as package.app file to start at login") + } + appPath := strings.TrimSuffix(exe, "/Contents/MacOS/"+binName) + var command string + if enabled { + command = fmt.Sprintf("tell application \"System Events\" to make login item at end with properties {name: \"%s\",path:\"%s\", hidden:false}", binName, appPath) + } else { + command = fmt.Sprintf("tell application \"System Events\" to delete login item \"%s\"", binName) + } + + cmd := exec.Command("osascript", "-e", command) + _, err = cmd.CombinedOutput() + if err != nil { + return err + } + return nil +} + +func (p *Plugin) IsStartAtLogin() (bool, error) { + if p.disabled { + return false, nil + } + exe, err := os.Executable() + if err != nil { + return false, err + } + binName := filepath.Base(exe) + if !strings.HasSuffix(exe, "/Contents/MacOS/"+binName) { + return false, fmt.Errorf("app needs to be running as package.app file to start at login") + } + appPath := strings.TrimSuffix(exe, "/Contents/MacOS/"+binName) + appName := strings.TrimSuffix(filepath.Base(appPath), ".app") + cmd := exec.Command("osascript", "-e", `tell application "System Events" to get the name of every login item`) + results, err := cmd.CombinedOutput() + if err != nil { + return false, err + } + resultsString := strings.TrimSpace(string(results)) + startupApps := strings.Split(resultsString, ", ") + result := lo.Contains(startupApps, appName) + return result, nil +} diff --git a/v3/plugins/start_at_login/plugin_linux.go b/v3/plugins/start_at_login/plugin_linux.go new file mode 100644 index 000000000..341ef07aa --- /dev/null +++ b/v3/plugins/start_at_login/plugin_linux.go @@ -0,0 +1,84 @@ +//go:build linux + +package start_at_login + +import ( + "fmt" + "os" + "path/filepath" + "strings" + "text/template" +) + +type startTpl struct { + Name string + Cmd string + Enabled bool +} + +const tpl = ` +[Desktop Entry] +Name={{.Name}} +Comment=Autostart service for {{.Name}} +Type=Application +Exec={{.Cmd}} +{{if .Enabled}} +Hidden=true +X-GNOME-Autostart-enabled=true +{{end}} +` + +func (p *Plugin) init() error { + return nil +} + +func (p *Plugin) autoStartFileExists() bool { + if _, err := os.Stat(p.autostartFile()); err == nil { + return true + } + return false +} + +func (p *Plugin) StartAtLogin(enabled bool) error { + autostart := p.autostartFile() + if !enabled && p.autoStartFileExists() { + return os.Remove(autostart) + } else if enabled && !p.autoStartFileExists() { + p.createAutoStartFile(autostart, enabled) + } + + return nil +} + +func (p *Plugin) IsStartAtLogin() (bool, error) { + result := p.autoStartFileExists() + return result, nil +} + +func (p Plugin) autostartFile() string { + homedir, _ := os.UserHomeDir() + exe, _ := os.Executable() + name := filepath.Base(exe) + autostartFile := fmt.Sprintf("%s-autostart.desktop", name) + return strings.Join([]string{homedir, ".config", "autostart", autostartFile}, "/") +} + +func (p Plugin) createAutoStartFile(filename string, enabled bool) { + file, err := os.OpenFile(filename, os.O_RDWR|os.O_CREATE, 0600) + defer file.Close() + + tmpl, err := template.New("autostart").Parse(tpl) + if err != nil { + panic(err) + } + exe, _ := os.Executable() + input := startTpl{ + Name: filepath.Base(exe) , + Cmd: exe, + Enabled: enabled, + } + err = tmpl.Execute(file, input) + if err != nil { + panic(err) + } +} diff --git a/v3/plugins/start_at_login/plugin_window.go b/v3/plugins/start_at_login/plugin_window.go new file mode 100644 index 000000000..1d1545302 --- /dev/null +++ b/v3/plugins/start_at_login/plugin_window.go @@ -0,0 +1,88 @@ +//go:build windows + +package start_at_login + +import ( + "fmt" + "golang.org/x/sys/windows/registry" + "os" + "path/filepath" + "strings" +) + +func (p *Plugin) init() error { + // TBD + return nil +} + +func (p *Plugin) getRegistryKey() (string, string, error) { + exePath, err := os.Executable() + if err != nil { + return "", "", fmt.Errorf("failed to get executable path: %s", err) + } + + registryKey := p.options.RegistryKey + if p.options.RegistryKey == "" { + registryKey = strings.Split(filepath.Base(exePath), ".")[0] + } + + return registryKey, exePath, nil +} + +func openRegKey() (registry.Key, error) { + // Open the registry key + return registry.OpenKey(registry.CURRENT_USER, `Software\Microsoft\Windows\CurrentVersion\Run`, registry.ALL_ACCESS) +} + +func (p *Plugin) IsStartAtLogin() (bool, error) { + registryKey, exePath, err := p.getRegistryKey() + if err != nil { + return false, err + } + + key, err := openRegKey() + if err != nil { + return false, err + } + defer key.Close() + + // Get the registry value + value, _, err := key.GetStringValue(registryKey) + if err != nil { + return false, nil + } + return value == exePath, nil +} + +func (p *Plugin) StartAtLogin(enabled bool) error { + registryKey, exePath, err := p.getRegistryKey() + if err != nil { + return err + } + + if enabled { + // Open the registry key + key, err := openRegKey() + defer key.Close() + + // Set the registry value + err = key.SetStringValue(registryKey, exePath) + if err != nil { + return fmt.Errorf("failed to set registry value: %s", err) + } + } else { + // Remove registry key + key, err := openRegKey() + if err != nil { + return fmt.Errorf("failed to open registry key: %s", err) + } + defer key.Close() + + // Remove the registry value + err = key.DeleteValue(registryKey) + if err != nil { + return fmt.Errorf("failed to delete registry value: %s", err) + } + } + return nil +} diff --git a/v3/tasks/Taskfile.yml b/v3/tasks/Taskfile.yml new file mode 100644 index 000000000..a9a8505dd --- /dev/null +++ b/v3/tasks/Taskfile.yml @@ -0,0 +1,7 @@ +version: '3' + +tasks: + generate:events: + dir: ./events + cmds: + - go run generate.go \ No newline at end of file diff --git a/v3/tasks/contribs/main.go b/v3/tasks/contribs/main.go new file mode 100644 index 000000000..64fba1968 --- /dev/null +++ b/v3/tasks/contribs/main.go @@ -0,0 +1,38 @@ +package main + +import ( + "log" + "os/exec" + "strings" +) + +func main() { + cmd := exec.Command("npx", "all-contributors-cli", "check") + //cmd.Stdin = strings.NewReader("some input") + var out strings.Builder + cmd.Stdout = &out + err := cmd.Run() + missingSplit := strings.Split(out.String(), "\n") + if len(missingSplit) < 2 { + log.Fatal(out.String()) + } + missing := missingSplit[1] + missing = strings.TrimSpace(missing) + // Split on comma + for _, contrib := range strings.Split(missing, ",") { + // Trim whitespace + contrib = strings.TrimSpace(contrib) + if contrib == "dependabot[bot]" || contrib == "" { + continue + } + // Add contributor + cmd := exec.Command("npx", "all-contributors-cli", "add", contrib, "code") + err := cmd.Run() + if err != nil { + log.Fatal(err) + } + } + if err != nil { + log.Fatal(err) + } +} diff --git a/v3/tasks/events/generate.go b/v3/tasks/events/generate.go new file mode 100644 index 000000000..aade6d266 --- /dev/null +++ b/v3/tasks/events/generate.go @@ -0,0 +1,310 @@ +package main + +import ( + "bytes" + "os" + "strconv" + "strings" +) + +var eventsGo = `package events + +type ApplicationEventType uint +type WindowEventType uint + +var Common = newCommonEvents() + +type commonEvents struct { +$$COMMONEVENTSDECL} + +func newCommonEvents() commonEvents { + return commonEvents{ +$$COMMONEVENTSVALUES } +} + +var Mac = newMacEvents() + +type macEvents struct { +$$MACEVENTSDECL} + +func newMacEvents() macEvents { + return macEvents{ +$$MACEVENTSVALUES } +} + +var Windows = newWindowsEvents() + +type windowsEvents struct { +$$WINDOWSEVENTSDECL} + +func newWindowsEvents() windowsEvents { + return windowsEvents{ +$$WINDOWSEVENTSVALUES } +} + +func JSEvent(event uint) string { + return eventToJS[event] +} + +var eventToJS = map[uint]string{ +$$EVENTTOJS} + +` + +var eventsH = `//go:build darwin + +#ifndef _events_h +#define _events_h + +extern void processApplicationEvent(unsigned int, void* data); +extern void processWindowEvent(unsigned int, unsigned int); + +$$CHEADEREVENTS + +#endif` + +var eventsJS = ` +export const EventTypes = { + Windows: { +$$WINDOWSJSEVENTS }, + Mac: { +$$MACJSEVENTS }, + Common: { +$$COMMONJSEVENTS }, +}; +` + +func main() { + + eventNames, err := os.ReadFile("../../pkg/events/events.txt") + if err != nil { + panic(err) + } + + macEventsDecl := bytes.NewBufferString("") + macEventsValues := bytes.NewBufferString("") + cHeaderEvents := bytes.NewBufferString("") + windowDelegateEvents := bytes.NewBufferString("") + applicationDelegateEvents := bytes.NewBufferString("") + webviewDelegateEvents := bytes.NewBufferString("") + + windowsEventsDecl := bytes.NewBufferString("") + windowsEventsValues := bytes.NewBufferString("") + + commonEventsDecl := bytes.NewBufferString("") + commonEventsValues := bytes.NewBufferString("") + + macJSEvents := bytes.NewBufferString("") + windowsJSEvents := bytes.NewBufferString("") + commonJSEvents := bytes.NewBufferString("") + + eventToJS := bytes.NewBufferString("") + + var id int + var maxMacEvents int + var line []byte + // Loop over each line in the file + for id, line = range bytes.Split(eventNames, []byte{'\n'}) { + + // First 1024 is reserved + id = id + 1024 + + // Skip empty lines + if len(line) == 0 { + continue + } + + // split on the colon + split := bytes.Split(line, []byte{':'}) + platform := strings.TrimSpace(string(split[0])) + event := strings.TrimSpace(string(split[1])) + var ignoreEvent bool + if strings.HasSuffix(event, "!") { + event = event[:len(event)-1] + ignoreEvent = true + } + + // Title case the event name + eventTitle := string(bytes.ToUpper([]byte{event[0]})) + event[1:] + // delegate function name has a lowercase first character + delegateEventFunction := string(bytes.ToLower([]byte{event[0]})) + event[1:] + + // Add to buffer + switch platform { + case "mac": + eventType := "ApplicationEventType" + if strings.HasPrefix(event, "Window") { + eventType = "WindowEventType" + } + if strings.HasPrefix(event, "WebView") { + eventType = "WindowEventType" + } + macEventsDecl.WriteString("\t" + eventTitle + " " + eventType + "\n") + macEventsValues.WriteString("\t\t" + event + ": " + strconv.Itoa(id) + ",\n") + macJSEvents.WriteString("\t\t" + event + ": \"" + strings.TrimSpace(string(line)) + "\",\n") + cHeaderEvents.WriteString("#define Event" + eventTitle + " " + strconv.Itoa(id) + "\n") + eventToJS.WriteString("\t" + strconv.Itoa(id) + ": \"" + strings.TrimSpace(string(line)) + "\",\n") + maxMacEvents = id + if ignoreEvent { + continue + } + // Check if this is a window event + if strings.HasPrefix(event, "Window") { + windowDelegateEvents.WriteString(`- (void)` + delegateEventFunction + `:(NSNotification *)notification { + if( hasListeners(Event` + eventTitle + `) ) { + processWindowEvent(self.windowId, Event` + eventTitle + `); + } +} + +`) + } + // Check if this is a webview event + if strings.HasPrefix(event, "WebView") { + webViewFunction := strings.TrimPrefix(event, "WebView") + webViewFunction = string(bytes.ToLower([]byte{webViewFunction[0]})) + webViewFunction[1:] + webviewDelegateEvents.WriteString(`- (void)webView:(WKWebView *)webview ` + webViewFunction + `:(WKNavigation *)navigation { + if( hasListeners(Event` + eventTitle + `) ) { + processWindowEvent(self.windowId, Event` + eventTitle + `); + } +} + +`) + } + if strings.HasPrefix(event, "Application") { + applicationDelegateEvents.WriteString(`- (void)` + delegateEventFunction + `:(NSNotification *)notification { + if( hasListeners(Event` + eventTitle + `) ) { + processApplicationEvent(Event` + eventTitle + `, NULL); + } +} + +`) + } + case "common": + eventType := "ApplicationEventType" + if strings.HasPrefix(event, "Window") { + eventType = "WindowEventType" + } + if strings.HasPrefix(event, "WebView") { + eventType = "WindowEventType" + } + commonEventsDecl.WriteString("\t" + eventTitle + " " + eventType + "\n") + commonEventsValues.WriteString("\t\t" + event + ": " + strconv.Itoa(id) + ",\n") + commonJSEvents.WriteString("\t\t" + event + ": \"" + strings.TrimSpace(string(line)) + "\",\n") + eventToJS.WriteString("\t" + strconv.Itoa(id) + ": \"" + strings.TrimSpace(string(line)) + "\",\n") + case "windows": + eventType := "ApplicationEventType" + if strings.HasPrefix(event, "Window") { + eventType = "WindowEventType" + } + if strings.HasPrefix(event, "WebView") { + eventType = "WindowEventType" + } + windowsEventsDecl.WriteString("\t" + eventTitle + " " + eventType + "\n") + windowsEventsValues.WriteString("\t\t" + event + ": " + strconv.Itoa(id) + ",\n") + windowsJSEvents.WriteString("\t\t" + event + ": \"" + strings.TrimSpace(string(line)) + "\",\n") + eventToJS.WriteString("\t" + strconv.Itoa(id) + ": \"" + strings.TrimSpace(string(line)) + "\",\n") + } + } + + cHeaderEvents.WriteString("\n#define MAX_EVENTS " + strconv.Itoa(maxMacEvents+1) + "\n") + + // Save the eventsGo template substituting the values and decls + templateToWrite := strings.ReplaceAll(eventsGo, "$$MACEVENTSDECL", macEventsDecl.String()) + templateToWrite = strings.ReplaceAll(templateToWrite, "$$MACEVENTSVALUES", macEventsValues.String()) + templateToWrite = strings.ReplaceAll(templateToWrite, "$$WINDOWSEVENTSDECL", windowsEventsDecl.String()) + templateToWrite = strings.ReplaceAll(templateToWrite, "$$WINDOWSEVENTSVALUES", windowsEventsValues.String()) + templateToWrite = strings.ReplaceAll(templateToWrite, "$$COMMONEVENTSDECL", commonEventsDecl.String()) + templateToWrite = strings.ReplaceAll(templateToWrite, "$$COMMONEVENTSVALUES", commonEventsValues.String()) + templateToWrite = strings.ReplaceAll(templateToWrite, "$$EVENTTOJS", eventToJS.String()) + err = os.WriteFile("../../pkg/events/events.go", []byte(templateToWrite), 0644) + if err != nil { + panic(err) + } + + // Save the eventsJS template substituting the values and decls + templateToWrite = strings.ReplaceAll(eventsJS, "$$MACJSEVENTS", macJSEvents.String()) + templateToWrite = strings.ReplaceAll(templateToWrite, "$$WINDOWSJSEVENTS", windowsJSEvents.String()) + templateToWrite = strings.ReplaceAll(templateToWrite, "$$COMMONJSEVENTS", commonJSEvents.String()) + err = os.WriteFile("../../internal/runtime/desktop/api/event_types.js", []byte(templateToWrite), 0644) + if err != nil { + panic(err) + } + + // Save the eventsH template substituting the values and decls + templateToWrite = strings.ReplaceAll(eventsH, "$$CHEADEREVENTS", cHeaderEvents.String()) + err = os.WriteFile("../../pkg/events/events.h", []byte(templateToWrite), 0644) + if err != nil { + panic(err) + } + + // Load the window_delegate.m file + windowDelegate, err := os.ReadFile("../../pkg/application/webview_window_darwin.m") + if err != nil { + panic(err) + } + // iterate over the lines until we reach a line that says "// GENERATED EVENTS START" + // then we insert the events + // then we iterate until we reach a line that says "// GENERATED EVENTS END" + // then we write the file + var buffer bytes.Buffer + var inGeneratedEvents bool + for _, line := range bytes.Split(windowDelegate, []byte{'\n'}) { + if bytes.Contains(line, []byte("// GENERATED EVENTS START")) { + inGeneratedEvents = true + buffer.WriteString("// GENERATED EVENTS START\n") + buffer.WriteString(windowDelegateEvents.String()) + buffer.WriteString(webviewDelegateEvents.String()) + continue + } + if bytes.Contains(line, []byte("// GENERATED EVENTS END")) { + inGeneratedEvents = false + buffer.WriteString("// GENERATED EVENTS END\n") + continue + } + if !inGeneratedEvents { + if len(line) > 0 { + buffer.Write(line) + buffer.WriteString("\n") + } + } + } + err = os.WriteFile("../../pkg/application/webview_window_darwin.m", buffer.Bytes(), 0755) + if err != nil { + panic(err) + } + + // Load the app_delegate.m file + appDelegate, err := os.ReadFile("../../pkg/application/application_darwin_delegate.m") + if err != nil { + panic(err) + } + // iterate over the lines until we reach a line that says "// GENERATED EVENTS START" + // then we insert the events + // then we iterate until we reach a line that says "// GENERATED EVENTS END" + // then we write the file + buffer.Reset() + for _, line := range bytes.Split(appDelegate, []byte{'\n'}) { + if bytes.Contains(line, []byte("// GENERATED EVENTS START")) { + inGeneratedEvents = true + buffer.WriteString("// GENERATED EVENTS START\n") + buffer.WriteString(applicationDelegateEvents.String()) + continue + } + if bytes.Contains(line, []byte("// GENERATED EVENTS END")) { + inGeneratedEvents = false + buffer.WriteString("// GENERATED EVENTS END\n") + continue + } + if !inGeneratedEvents { + if len(line) > 0 { + buffer.Write(line) + buffer.WriteString("\n") + } + } + } + err = os.WriteFile("../../pkg/application/application_darwin_delegate.m", buffer.Bytes(), 0755) + if err != nil { + panic(err) + } + +} diff --git a/v3/tasks/sed/sed.go b/v3/tasks/sed/sed.go new file mode 100644 index 000000000..12e55f8d3 --- /dev/null +++ b/v3/tasks/sed/sed.go @@ -0,0 +1,68 @@ +package main + +import ( + "fmt" + "os" + "path/filepath" + "strings" + + "github.com/leaanthony/clir" + + "github.com/samber/lo" +) + +func main() { + app := clir.NewCli("sed", "A simple sed replacement", "v1") + app.NewSubCommandFunction("replace", "Replace a string in files", ReplaceInFiles) + err := app.Run() + if err != nil { + println(err.Error()) + os.Exit(1) + } +} + +type ReplaceInFilesOptions struct { + Dir string `name:"dir" help:"Directory to search in"` + OldString string `name:"old" description:"The string to replace"` + NewString string `name:"new" description:"The string to replace with"` + Extensions string `name:"ext" description:"The file extensions to process"` + Ignore string `name:"ignore" description:"The files to ignore"` +} + +func ReplaceInFiles(options *ReplaceInFilesOptions) error { + extensions := strings.Split(options.Extensions, ",") + ignore := strings.Split(options.Ignore, ",") + err := filepath.Walk(options.Dir, func(path string, info os.FileInfo, err error) error { + if info.IsDir() { + return nil + } + + ext := filepath.Ext(path) + if !lo.Contains(extensions, ext) { + println("Skipping", path) + return nil + } + filename := filepath.Base(path) + if lo.Contains(ignore, filename) { + println("Ignoring:", path) + return nil + } + + println("Processing file:", path) + + content, err := os.ReadFile(path) + if err != nil { + return err + } + + newContent := strings.Replace(string(content), options.OldString, options.NewString, -1) + + return os.WriteFile(path, []byte(newContent), info.Mode()) + }) + + if err != nil { + return fmt.Errorf("Error while replacing in files: %v", err) + } + + return nil +}