mirror of
https://github.com/taigrr/wails.git
synced 2026-04-04 06:02:43 -07:00
Compare commits
1 Commits
v2.0.0-alp
...
feature/v2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
29715b2d57 |
4
.gitignore
vendored
4
.gitignore
vendored
@@ -27,7 +27,3 @@ v2/pkg/parser/testproject/frontend/wails
|
||||
v2/test/kitchensink/frontend/public
|
||||
v2/test/kitchensink/build/darwin/desktop/kitchensink
|
||||
v2/test/kitchensink/frontend/package.json.md5
|
||||
/v2/internal/ffenestri/windows/test/cmake-build-debug/
|
||||
!v2/internal/ffenestri/windows/x64/webview2.dll
|
||||
!v2/internal/ffenestri/windows/x64/WebView2Loader.dll
|
||||
.idea/
|
||||
|
||||
@@ -40,5 +40,4 @@ Wails is what it is because of the time and effort given by these great people.
|
||||
* [Balakrishna Prasad Ganne](https://github.com/aayush420)
|
||||
* [Charaf Rezrazi](https://github.com/Rezrazi)
|
||||
* [misitebao](https://github.com/misitebao)
|
||||
* [Elie Grenon](https://github.com/DrunkenPoney)
|
||||
* [Amaury Tobias Quiroz](https://github.com/amaury-tobias)
|
||||
* [Elie Grenon](https://github.com/DrunkenPoney)
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
echo "**** Checking if Wails passes unit tests ****"
|
||||
if ! go test ./lib/... ./runtime/... ./cmd/...
|
||||
if ! go test ./...
|
||||
then
|
||||
echo ""
|
||||
echo "ERROR: Unit tests failed!"
|
||||
|
||||
13
v2/.vscode/settings.json
vendored
Normal file
13
v2/.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"files.associations": {
|
||||
"ios": "c",
|
||||
"typeinfo": "c",
|
||||
"sstream": "c",
|
||||
"__functional_03": "c",
|
||||
"functional": "c",
|
||||
"__locale": "c",
|
||||
"locale": "c",
|
||||
"chrono": "c",
|
||||
"system_error": "c"
|
||||
}
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
# Build
|
||||
|
||||
The build command processes the Wails project and generates an application binary.
|
||||
|
||||
## Usage
|
||||
|
||||
`wails build <flags>`
|
||||
|
||||
### Flags
|
||||
|
||||
| Flag | Details | Default |
|
||||
| :------------- | :----------- | :------ |
|
||||
| -clean | Clean the bin directory before building | |
|
||||
| -compiler path/to/compiler | Use a different go compiler, eg go1.15beta1 | go |
|
||||
| -ldflags "custom ld flags" | Use given ldflags | |
|
||||
| -o path/to/binary | Compile to given path/filename | |
|
||||
| -k | Keep generated assets | |
|
||||
| -package | Create a platform specific package | |
|
||||
| -production | Compile in production mode: -ldflags="-w -s" + "-h windows" on Windows | |
|
||||
| -tags | Build tags to pass to Go compiler (quoted and space separated) | |
|
||||
| -upx | Compress final binary with UPX (if installed) | |
|
||||
| -upxflags "custom flags" | Flags to pass to upx | |
|
||||
| -v int | Verbosity level (0 - silent, 1 - default, 2 - verbose) | 1 |
|
||||
|
||||
## The Build Process
|
||||
|
||||
The build process is as follows:
|
||||
|
||||
- The flags are processed, and an Options struct built containing the build context.
|
||||
- The type of target is determined, and a custom build process is followed for target.
|
||||
|
||||
### Desktop Target
|
||||
|
||||
- The frontend dependencies are installed. The command is read from the project file `wails.json` under the key `frontend:install` and executed in the `frontend` directory. If this is not defined, it is ignored.
|
||||
- The frontend is then built. This command is read from the project file `wails.json` under the key `frontend:install` and executed in the `frontend` directory. If this is not defined, it is ignored.
|
||||
- The project directory is checked to see if the `build` directory exists. If not, it is created and default project assets are copied to it.
|
||||
- An asset bundle is then created by reading the `html` key from `wails.json` and loading the referenced file. This is then parsed, looking for local Javascript and CSS references. Those files are in turn loaded into memory, converted to C data and saved into the asset bundle located at `build/assets.h`, which also includes the original HTML.
|
||||
- The application icon is then processed: if there is no `build/appicon.png`, a default icon is copied. On Windows, an `app.ico` file is generated from this png. On Mac, `icons.icns` is generated.
|
||||
- If there are icons in the `build/tray` directory, these are processed, converted to C data and saved as `build/trayicons.h`, ready for the compilation step.
|
||||
- If there are icons in the `build/dialog` directory, these are processed, converted to C data and saved as `build/userdialogicons.h`, ready for the compilation step.
|
||||
- If the `-package` flag is given for a Windows target, the Windows assets in the `build/windows` directory are processed: manifest + icons compiled to a `.syso` file (deleted after compilation).
|
||||
- If we are building a universal binary for Mac, the application is compiled for both `arm64` and `amd64`. The `lipo` tool is then executed to create the universal binary.
|
||||
- If we are not building a universal binary for Mac, the application is built using `go build`, using build tags to indicate type of application and build mode (debug/production).
|
||||
- If the `-upx` flag was provided, `upx` is invoked to compress the binary. Custom flags may be provided using the `-upxflags` flag.
|
||||
- If the `package` flag is given for a non Windows target, the application is bundled for the platform. On Mac, this creates a `.app` with the processed icons, the `Info.plist` in `build/darwin` and the compiled binary.
|
||||
|
||||
### Server Target
|
||||
|
||||
TBD
|
||||
|
||||
### Hybrid Target
|
||||
|
||||
TBD
|
||||
|
||||
@@ -3,10 +3,8 @@ package build
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"runtime"
|
||||
"strings"
|
||||
"text/tabwriter"
|
||||
"time"
|
||||
|
||||
"github.com/leaanthony/clir"
|
||||
@@ -24,6 +22,10 @@ func AddBuildSubcommand(app *clir.Cli, w io.Writer) {
|
||||
|
||||
command := app.NewSubCommand("build", "Builds the application")
|
||||
|
||||
// Setup target type flag
|
||||
description := "Type of application to build. Valid types: " + validTargetTypes.Join(",")
|
||||
command.StringFlag("t", description, &outputType)
|
||||
|
||||
// Setup production flag
|
||||
production := false
|
||||
command.BoolFlag("production", "Build in production mode", &production)
|
||||
@@ -35,44 +37,28 @@ func AddBuildSubcommand(app *clir.Cli, w io.Writer) {
|
||||
compilerCommand := "go"
|
||||
command.StringFlag("compiler", "Use a different go compiler to build, eg go1.15beta1", &compilerCommand)
|
||||
|
||||
compress := false
|
||||
command.BoolFlag("upx", "Compress final binary with UPX (if installed)", &compress)
|
||||
|
||||
compressFlags := ""
|
||||
command.StringFlag("upxflags", "Flags to pass to upx", &compressFlags)
|
||||
|
||||
// Setup Platform flag
|
||||
platform := runtime.GOOS
|
||||
command.StringFlag("platform", "Platform to target", &platform)
|
||||
|
||||
// Verbosity
|
||||
verbosity := 1
|
||||
command.IntFlag("v", "Verbosity level (0 - silent, 1 - default, 2 - verbose)", &verbosity)
|
||||
// Quiet Build
|
||||
quiet := false
|
||||
command.BoolFlag("q", "Suppress output to console", &quiet)
|
||||
|
||||
// ldflags to pass to `go`
|
||||
ldflags := ""
|
||||
command.StringFlag("ldflags", "optional ldflags", &ldflags)
|
||||
|
||||
// tags to pass to `go`
|
||||
tags := ""
|
||||
command.StringFlag("tags", "tags to pass to Go compiler (quoted and space separated)", &tags)
|
||||
// Log to file
|
||||
logFile := ""
|
||||
command.StringFlag("l", "Log to file", &logFile)
|
||||
|
||||
// Retain assets
|
||||
keepAssets := false
|
||||
command.BoolFlag("k", "Keep generated assets", &keepAssets)
|
||||
|
||||
// Retain assets
|
||||
outputFilename := ""
|
||||
command.StringFlag("o", "Output filename", &outputFilename)
|
||||
|
||||
// Clean build directory
|
||||
cleanBuildDirectory := false
|
||||
command.BoolFlag("clean", "Clean the build directory before building", &cleanBuildDirectory)
|
||||
|
||||
command.Action(func() error {
|
||||
|
||||
quiet := verbosity == 0
|
||||
|
||||
// Create logger
|
||||
logger := clilogger.New(w)
|
||||
logger.Mute(quiet)
|
||||
@@ -86,95 +72,28 @@ func AddBuildSubcommand(app *clir.Cli, w io.Writer) {
|
||||
app.PrintBanner()
|
||||
}
|
||||
|
||||
task := fmt.Sprintf("Building %s Application", strings.Title(outputType))
|
||||
logger.Println(task)
|
||||
logger.Println(strings.Repeat("-", len(task)))
|
||||
|
||||
// Setup mode
|
||||
mode := build.Debug
|
||||
if production {
|
||||
mode = build.Production
|
||||
}
|
||||
|
||||
// Check platform
|
||||
validPlatformArch := slicer.String([]string{
|
||||
"darwin",
|
||||
"darwin/amd64",
|
||||
"darwin/arm64",
|
||||
"darwin/universal",
|
||||
"linux",
|
||||
//"linux/amd64",
|
||||
//"linux/arm-7",
|
||||
"windows",
|
||||
"windows/amd64",
|
||||
})
|
||||
if !validPlatformArch.Contains(platform) {
|
||||
return fmt.Errorf("platform %s is not supported", platform)
|
||||
}
|
||||
|
||||
if compress && platform == "darwin/universal" {
|
||||
println("Warning: compress flag unsupported for universal binaries. Ignoring.")
|
||||
compress = false
|
||||
}
|
||||
|
||||
// Tags
|
||||
userTags := []string{}
|
||||
for _, tag := range strings.Split(tags, " ") {
|
||||
thisTag := strings.TrimSpace(tag)
|
||||
if thisTag != "" {
|
||||
userTags = append(userTags, thisTag)
|
||||
}
|
||||
}
|
||||
|
||||
// Create BuildOptions
|
||||
buildOptions := &build.Options{
|
||||
Logger: logger,
|
||||
OutputType: outputType,
|
||||
OutputFile: outputFilename,
|
||||
CleanBuildDirectory: cleanBuildDirectory,
|
||||
Mode: mode,
|
||||
Pack: pack,
|
||||
LDFlags: ldflags,
|
||||
Compiler: compilerCommand,
|
||||
KeepAssets: keepAssets,
|
||||
Verbosity: verbosity,
|
||||
Compress: compress,
|
||||
CompressFlags: compressFlags,
|
||||
UserTags: userTags,
|
||||
Logger: logger,
|
||||
OutputType: outputType,
|
||||
Mode: mode,
|
||||
Pack: pack,
|
||||
Platform: platform,
|
||||
LDFlags: ldflags,
|
||||
Compiler: compilerCommand,
|
||||
KeepAssets: keepAssets,
|
||||
}
|
||||
|
||||
// Calculate platform and arch
|
||||
platformSplit := strings.Split(platform, "/")
|
||||
buildOptions.Platform = platformSplit[0]
|
||||
buildOptions.Arch = runtime.GOARCH
|
||||
if len(platformSplit) == 2 {
|
||||
buildOptions.Arch = platformSplit[1]
|
||||
}
|
||||
|
||||
// Start a new tabwriter
|
||||
w := new(tabwriter.Writer)
|
||||
w.Init(os.Stdout, 8, 8, 0, '\t', 0)
|
||||
|
||||
buildModeText := "debug"
|
||||
if production {
|
||||
buildModeText = "production"
|
||||
}
|
||||
|
||||
// Write out the system information
|
||||
fmt.Fprintf(w, "\n")
|
||||
fmt.Fprintf(w, "App Type: \t%s\n", buildOptions.OutputType)
|
||||
fmt.Fprintf(w, "Platform: \t%s\n", buildOptions.Platform)
|
||||
fmt.Fprintf(w, "Arch: \t%s\n", buildOptions.Arch)
|
||||
fmt.Fprintf(w, "Compiler: \t%s\n", buildOptions.Compiler)
|
||||
fmt.Fprintf(w, "Compress: \t%t\n", buildOptions.Compress)
|
||||
fmt.Fprintf(w, "Build Mode: \t%s\n", buildModeText)
|
||||
fmt.Fprintf(w, "Package: \t%t\n", buildOptions.Pack)
|
||||
fmt.Fprintf(w, "Clean Build Dir: \t%t\n", buildOptions.CleanBuildDirectory)
|
||||
fmt.Fprintf(w, "KeepAssets: \t%t\n", buildOptions.KeepAssets)
|
||||
fmt.Fprintf(w, "LDFlags: \t\"%s\"\n", buildOptions.LDFlags)
|
||||
fmt.Fprintf(w, "Tags: \t[%s]\n", strings.Join(buildOptions.UserTags, ","))
|
||||
if len(buildOptions.OutputFile) > 0 {
|
||||
fmt.Fprintf(w, "Output File: \t%s\n", buildOptions.OutputFile)
|
||||
}
|
||||
fmt.Fprintf(w, "\n")
|
||||
w.Flush()
|
||||
|
||||
return doBuild(buildOptions)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
# Dev
|
||||
|
||||
The dev command allows you to develop your application through a standard browser.
|
||||
|
||||
## Usage
|
||||
|
||||
`wails dev <flags>`
|
||||
|
||||
### Flags
|
||||
|
||||
| Flag | Details | Default |
|
||||
| :------------- | :----------- | :------ |
|
||||
| -compiler path/to/compiler | Use a different go compiler, eg go1.15beta1 | go |
|
||||
| -ldflags "custom ld flags" | Use given ldflags | |
|
||||
| -e list,of,extensions | File extensions to trigger rebuilds | go |
|
||||
| -w | Show warnings | false |
|
||||
| -v int | Verbosity level (0 - silent, 1 - default, 2 - verbose) | 1 |
|
||||
| -loglevel | Loglevel to pass to the application - Trace, Debug, Info, Warning, Error | Debug |
|
||||
|
||||
## How it works
|
||||
|
||||
The project is built using a special mode that starts a webserver and starts listening to port 34115. When the frontend project is run independently, so long as the JS is wrapped with the runtime method `ready`, then the frontend will connect to the backend code via websockets. The interface should be present in your browser, and you should be able to interact with the backend as you would in a desktop app.
|
||||
@@ -1,42 +1,30 @@
|
||||
package dev
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/wailsapp/wails/v2/internal/colour"
|
||||
"github.com/wailsapp/wails/v2/pkg/commands/build"
|
||||
|
||||
"github.com/wailsapp/wails/v2/internal/process"
|
||||
|
||||
"github.com/wzshiming/ctc"
|
||||
|
||||
"github.com/wailsapp/wails/v2/internal/fs"
|
||||
|
||||
"github.com/fsnotify/fsnotify"
|
||||
|
||||
"github.com/leaanthony/clir"
|
||||
"github.com/wailsapp/wails/v2/internal/fs"
|
||||
"github.com/wailsapp/wails/v2/internal/process"
|
||||
"github.com/wailsapp/wails/v2/pkg/clilogger"
|
||||
"github.com/wailsapp/wails/v2/pkg/commands/build"
|
||||
)
|
||||
|
||||
func LogGreen(message string, args ...interface{}) {
|
||||
text := fmt.Sprintf(message, args...)
|
||||
println(colour.Green(text))
|
||||
}
|
||||
|
||||
func LogRed(message string, args ...interface{}) {
|
||||
text := fmt.Sprintf(message, args...)
|
||||
println(colour.Red(text))
|
||||
}
|
||||
|
||||
func LogDarkYellow(message string, args ...interface{}) {
|
||||
text := fmt.Sprintf(message, args...)
|
||||
println(colour.DarkYellow(text))
|
||||
}
|
||||
|
||||
// AddSubcommand adds the `dev` command for the Wails application
|
||||
func AddSubcommand(app *clir.Cli, w io.Writer) error {
|
||||
|
||||
@@ -54,17 +42,6 @@ func AddSubcommand(app *clir.Cli, w io.Writer) error {
|
||||
extensions := "go"
|
||||
command.StringFlag("e", "Extensions to trigger rebuilds (comma separated) eg go,js,css,html", &extensions)
|
||||
|
||||
// extensions to trigger rebuilds
|
||||
showWarnings := false
|
||||
command.BoolFlag("w", "Show warnings", &showWarnings)
|
||||
|
||||
// Verbosity
|
||||
verbosity := 1
|
||||
command.IntFlag("v", "Verbosity level (0 - silent, 1 - default, 2 - verbose)", &verbosity)
|
||||
|
||||
loglevel := ""
|
||||
command.StringFlag("loglevel", "Loglevel to use - Trace, Debug, Info, Warning, Error", &loglevel)
|
||||
|
||||
command.Action(func() error {
|
||||
|
||||
// Create logger
|
||||
@@ -72,243 +49,264 @@ func AddSubcommand(app *clir.Cli, w io.Writer) error {
|
||||
app.PrintBanner()
|
||||
|
||||
// TODO: Check you are in a project directory
|
||||
|
||||
watcher, err := fsnotify.NewWatcher()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func(watcher *fsnotify.Watcher) {
|
||||
err := watcher.Close()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}(watcher)
|
||||
|
||||
var debugBinaryProcess *process.Process = nil
|
||||
var extensionsThatTriggerARebuild = strings.Split(extensions, ",")
|
||||
|
||||
// Setup signal handler
|
||||
quitChannel := make(chan os.Signal, 1)
|
||||
signal.Notify(quitChannel, os.Interrupt, os.Kill, syscall.SIGTERM)
|
||||
|
||||
debounceQuit := make(chan bool, 1)
|
||||
|
||||
var passthruArgs []string
|
||||
//if len(os.Args) > 2 {
|
||||
// passthruArgs = os.Args[2:]
|
||||
//}
|
||||
|
||||
// Do initial build
|
||||
logger.Println("Building application for development...")
|
||||
newProcess, appBinary, err := restartApp(logger, ldflags, compilerCommand, debugBinaryProcess, loglevel, passthruArgs, verbosity)
|
||||
if newProcess != nil {
|
||||
debugBinaryProcess = newProcess
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var newBinaryProcess *process.Process
|
||||
go debounce(100*time.Millisecond, watcher.Events, debounceQuit, func(event fsnotify.Event) {
|
||||
// logger.Println("event: %+v", event)
|
||||
|
||||
// Check for new directories
|
||||
if event.Op&fsnotify.Create == fsnotify.Create {
|
||||
// If this is a folder, add it to our watch list
|
||||
if fs.DirExists(event.Name) {
|
||||
if !strings.Contains(event.Name, "node_modules") {
|
||||
err := watcher.Add(event.Name)
|
||||
if err != nil {
|
||||
logger.Fatal("%s", err.Error())
|
||||
}
|
||||
LogGreen("[New Directory] Watching new directory: %s", event.Name)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Check for file writes
|
||||
if event.Op&fsnotify.Write == fsnotify.Write {
|
||||
|
||||
var rebuild bool = false
|
||||
|
||||
// Iterate all file patterns
|
||||
for _, pattern := range extensionsThatTriggerARebuild {
|
||||
if strings.HasSuffix(event.Name, pattern) {
|
||||
rebuild = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !rebuild {
|
||||
if showWarnings {
|
||||
LogDarkYellow("[File change] %s did not match extension list (%s)", event.Name, extensions)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
LogGreen("[Attempting rebuild] %s updated", event.Name)
|
||||
|
||||
// Do a rebuild
|
||||
|
||||
// Try and build the app
|
||||
newBinaryProcess, _, err = restartApp(logger, ldflags, compilerCommand, debugBinaryProcess, loglevel, passthruArgs, verbosity)
|
||||
if err != nil {
|
||||
fmt.Printf("Error during build: %s", err.Error())
|
||||
return
|
||||
}
|
||||
// If we have a new process, save it
|
||||
if newBinaryProcess != nil {
|
||||
debugBinaryProcess = newBinaryProcess
|
||||
}
|
||||
|
||||
}
|
||||
})
|
||||
|
||||
// Get project dir
|
||||
projectDir, err := os.Getwd()
|
||||
reloader, err := NewReloader(logger, extensionsThatTriggerARebuild, ldflags, compilerCommand)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Get all subdirectories
|
||||
dirs, err := fs.GetSubdirectories(projectDir)
|
||||
// Start
|
||||
err = reloader.Start()
|
||||
if err != nil {
|
||||
return err
|
||||
println("ERRRRRRRRRR: %+v", err)
|
||||
}
|
||||
|
||||
LogGreen("Watching (sub)/directory: %s", projectDir)
|
||||
logger.Println("\nDevelopment mode exited")
|
||||
|
||||
// Setup a watcher for non-node_modules directories
|
||||
dirs.Each(func(dir string) {
|
||||
if strings.Contains(dir, "node_modules") {
|
||||
return
|
||||
}
|
||||
// Ignore build directory
|
||||
if strings.HasPrefix(dir, filepath.Join(projectDir, "build")) {
|
||||
return
|
||||
}
|
||||
err = watcher.Add(dir)
|
||||
if err != nil {
|
||||
logger.Fatal(err.Error())
|
||||
}
|
||||
})
|
||||
|
||||
// Wait until we get a quit signal
|
||||
quit := false
|
||||
for quit == false {
|
||||
select {
|
||||
case <-quitChannel:
|
||||
LogGreen("\nCaught quit")
|
||||
// Notify debouncer to quit
|
||||
debounceQuit <- true
|
||||
quit = true
|
||||
}
|
||||
}
|
||||
|
||||
// Kill the current program if running
|
||||
if debugBinaryProcess != nil {
|
||||
err := debugBinaryProcess.Kill()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Remove dev binary
|
||||
err = os.Remove(appBinary)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
LogGreen("Development mode exited")
|
||||
|
||||
return nil
|
||||
return err
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Credit: https://drailing.net/2018/01/debounce-function-for-golang/
|
||||
func debounce(interval time.Duration, input chan fsnotify.Event, quitChannel chan bool, cb func(arg fsnotify.Event)) {
|
||||
var item fsnotify.Event
|
||||
timer := time.NewTimer(interval)
|
||||
exit:
|
||||
type Reloader struct {
|
||||
|
||||
// Main context
|
||||
ctx context.Context
|
||||
|
||||
// Signal context
|
||||
signalContext context.Context
|
||||
|
||||
// notify
|
||||
watcher *fsnotify.Watcher
|
||||
|
||||
// Logger
|
||||
logger *clilogger.CLILogger
|
||||
|
||||
// Extensions to listen for
|
||||
extensionsThatTriggerARebuild []string
|
||||
|
||||
// The binary we are running
|
||||
binary *process.Process
|
||||
|
||||
// options
|
||||
ldflags string
|
||||
compiler string
|
||||
}
|
||||
|
||||
func NewReloader(logger *clilogger.CLILogger, extensionsThatTriggerARebuild []string, ldFlags string, compiler string) (*Reloader, error) {
|
||||
var result Reloader
|
||||
|
||||
// Create context
|
||||
result.ctx = context.Background()
|
||||
|
||||
// Signal context (we don't need cancel)
|
||||
signalContext, _ := signal.NotifyContext(result.ctx, os.Interrupt, os.Kill, syscall.SIGTERM)
|
||||
result.signalContext = signalContext
|
||||
|
||||
// Create watcher
|
||||
watcher, err := fsnotify.NewWatcher()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result.watcher = watcher
|
||||
|
||||
// Logger
|
||||
result.logger = logger
|
||||
|
||||
// Extensions
|
||||
result.extensionsThatTriggerARebuild = extensionsThatTriggerARebuild
|
||||
|
||||
// Options
|
||||
result.ldflags = ldFlags
|
||||
result.compiler = compiler
|
||||
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
func (r *Reloader) Start() error {
|
||||
|
||||
err := r.rebuildBinary()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Get project dir
|
||||
dir, err := os.Getwd()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Get all subdirectories
|
||||
dirs, err := fs.GetSubdirectories(dir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Setup a watcher for non-node_modules directories
|
||||
r.logger.Println("Watching (sub)directories: %s", dir)
|
||||
|
||||
dirs.Each(func(dir string) {
|
||||
if strings.Contains(dir, "node_modules") {
|
||||
return
|
||||
}
|
||||
err = r.watcher.Add(dir)
|
||||
if err != nil {
|
||||
r.logger.Fatal(err.Error())
|
||||
}
|
||||
})
|
||||
|
||||
// Main loop
|
||||
for {
|
||||
select {
|
||||
case item = <-input:
|
||||
timer.Reset(interval)
|
||||
case <-timer.C:
|
||||
if item.Name != "" {
|
||||
cb(item)
|
||||
case <-r.signalContext.Done():
|
||||
if r.binary != nil {
|
||||
println("Binary is not nil - kill")
|
||||
return r.binary.Kill()
|
||||
}
|
||||
return nil
|
||||
case event := <-r.watcher.Events:
|
||||
err := r.processWatcherEvent(event)
|
||||
if err != nil {
|
||||
println("error from processWatcherEvent. Calling cancel()")
|
||||
println("Calling kill")
|
||||
return r.binary.Kill()
|
||||
}
|
||||
case <-quitChannel:
|
||||
break exit
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func restartApp(logger *clilogger.CLILogger, ldflags string, compilerCommand string, debugBinaryProcess *process.Process, loglevel string, passthruArgs []string, verbosity int) (*process.Process, string, error) {
|
||||
func (r *Reloader) processWatcherEvent(event fsnotify.Event) error {
|
||||
|
||||
appBinary, err := buildApp(logger, ldflags, compilerCommand, verbosity)
|
||||
println()
|
||||
if err != nil {
|
||||
LogRed("Build error - continuing to run current version")
|
||||
LogDarkYellow(err.Error())
|
||||
return nil, "", nil
|
||||
// Check for new directories
|
||||
if event.Op&fsnotify.Create == fsnotify.Create {
|
||||
// If this is a folder, add it to our watch list
|
||||
if fs.DirExists(event.Name) {
|
||||
if !strings.Contains(event.Name, "node_modules") {
|
||||
err := r.watcher.Add(event.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
r.logger.Println("Watching directory: %s", event.Name)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Kill existing binary if need be
|
||||
if debugBinaryProcess != nil {
|
||||
killError := debugBinaryProcess.Kill()
|
||||
// Check for file writes
|
||||
if event.Op&fsnotify.Write == fsnotify.Write {
|
||||
|
||||
if killError != nil {
|
||||
logger.Fatal("Unable to kill debug binary (PID: %d)!", debugBinaryProcess.PID())
|
||||
var rebuild bool
|
||||
|
||||
// Iterate all file patterns
|
||||
for _, pattern := range r.extensionsThatTriggerARebuild {
|
||||
if strings.HasSuffix(event.Name, pattern) {
|
||||
rebuild = true
|
||||
}
|
||||
}
|
||||
|
||||
debugBinaryProcess = nil
|
||||
if !rebuild {
|
||||
return nil
|
||||
}
|
||||
|
||||
r.logger.Println("\n%s[Build triggered] %s %s", ctc.ForegroundGreen|ctc.ForegroundBright, event.Name, ctc.Reset)
|
||||
|
||||
return r.rebuildBinary()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Reloader) rebuildBinary() error {
|
||||
|
||||
// rebuild binary
|
||||
binary, err := r.buildApp()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// TODO: Generate `backend.js`
|
||||
|
||||
// Start up new binary with correct args
|
||||
var args = []string{"-loglevel", loglevel}
|
||||
if len(passthruArgs) > 0 {
|
||||
args = append(args, passthruArgs...)
|
||||
// Kill current binary if running
|
||||
if r.binary != nil {
|
||||
err = r.binary.Kill()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
newProcess := process.NewProcess(logger, appBinary, args...)
|
||||
|
||||
newProcess := process.NewProcess(r.ctx, r.logger, binary)
|
||||
err = newProcess.Start()
|
||||
if err != nil {
|
||||
// Remove binary
|
||||
deleteError := fs.DeleteFile(appBinary)
|
||||
if deleteError != nil {
|
||||
logger.Fatal("Unable to delete app binary: " + appBinary)
|
||||
}
|
||||
logger.Fatal("Unable to start application: %s", err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
return newProcess, appBinary, nil
|
||||
// Ensure process runs correctly
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func buildApp(logger *clilogger.CLILogger, ldflags string, compilerCommand string, verbosity int) (string, error) {
|
||||
//func restartApp(logger *clilogger.CLILogger, outputType string, ldflags string, compilerCommand string, debugBinaryProcess *process.Process) (*process.Process, error) {
|
||||
//
|
||||
// appBinary, err := buildApp(logger, outputType, ldflags, compilerCommand)
|
||||
// println()
|
||||
// if err != nil {
|
||||
// logger.Fatal(err.Error())
|
||||
// return nil, errors.Wrap(err, "Build Failed:")
|
||||
// }
|
||||
// logger.Println("Build new binary: %s", appBinary)
|
||||
//
|
||||
// // Kill existing binary if need be
|
||||
// if debugBinaryProcess != nil {
|
||||
// killError := debugBinaryProcess.Kill()
|
||||
//
|
||||
// if killError != nil {
|
||||
// logger.Fatal("Unable to kill debug binary (PID: %d)!", debugBinaryProcess.PID())
|
||||
// }
|
||||
//
|
||||
// debugBinaryProcess = nil
|
||||
// }
|
||||
//
|
||||
// // TODO: Generate `backend.js`
|
||||
//
|
||||
// // Start up new binary
|
||||
// newProcess := process.NewProcess(logger, appBinary)
|
||||
// err = newProcess.Start()
|
||||
// if err != nil {
|
||||
// // Remove binary
|
||||
// deleteError := fs.DeleteFile(appBinary)
|
||||
// if deleteError != nil {
|
||||
// logger.Fatal("Unable to delete app binary: " + appBinary)
|
||||
// }
|
||||
// logger.Fatal("Unable to start application: %s", err.Error())
|
||||
// }
|
||||
//
|
||||
// // Check if port is open
|
||||
// timeout := time.Second
|
||||
// conn, err := net.DialTimeout("tcp", net.JoinHostPort("host", port), timeout)
|
||||
// if err != nil {
|
||||
// return
|
||||
// }
|
||||
// newProcess.Running
|
||||
// return newProcess, nil
|
||||
//}
|
||||
|
||||
// buildapp attempts to compile the application
|
||||
// It returns the path to the new binary or an error
|
||||
func (r *Reloader) buildApp() (string, error) {
|
||||
|
||||
// Create random output file
|
||||
outputFile := "wailsdev"
|
||||
if runtime.GOOS == "windows" {
|
||||
outputFile += ".exe"
|
||||
}
|
||||
outputFile := fmt.Sprintf("debug-%d", time.Now().Unix())
|
||||
|
||||
// Create BuildOptions
|
||||
buildOptions := &build.Options{
|
||||
Logger: logger,
|
||||
Logger: r.logger,
|
||||
OutputType: "dev",
|
||||
Mode: build.Debug,
|
||||
Pack: false,
|
||||
Platform: runtime.GOOS,
|
||||
LDFlags: ldflags,
|
||||
Compiler: compilerCommand,
|
||||
LDFlags: r.ldflags,
|
||||
Compiler: r.compiler,
|
||||
OutputFile: outputFile,
|
||||
IgnoreFrontend: true,
|
||||
Verbosity: verbosity,
|
||||
}
|
||||
|
||||
return build.Build(buildOptions)
|
||||
|
||||
@@ -3,6 +3,7 @@ package doctor
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"runtime"
|
||||
"strings"
|
||||
@@ -24,13 +25,11 @@ func AddSubcommand(app *clir.Cli, w io.Writer) error {
|
||||
logger := clilogger.New(w)
|
||||
|
||||
app.PrintBanner()
|
||||
|
||||
logger.Print("Scanning system - Please wait (this may take a long time)...")
|
||||
logger.Print("Scanning system - please wait...")
|
||||
|
||||
// Get system info
|
||||
info, err := system.GetInfo()
|
||||
if err != nil {
|
||||
logger.Println("Failed.")
|
||||
return err
|
||||
}
|
||||
logger.Println("Done.")
|
||||
@@ -40,22 +39,27 @@ func AddSubcommand(app *clir.Cli, w io.Writer) error {
|
||||
w.Init(os.Stdout, 8, 8, 0, '\t', 0)
|
||||
|
||||
// Write out the system information
|
||||
fmt.Fprintf(w, "\n")
|
||||
fmt.Fprintf(w, "System\n")
|
||||
fmt.Fprintf(w, "------\n")
|
||||
fmt.Fprintf(w, "%s\t%s\n", "OS:", info.OS.Name)
|
||||
fmt.Fprintf(w, "%s\t%s\n", "Version: ", info.OS.Version)
|
||||
fmt.Fprintf(w, "%s\t%s\n", "ID:", info.OS.ID)
|
||||
|
||||
// Exit early if PM not found
|
||||
if info.PM == nil {
|
||||
fmt.Fprintf(w, "\n%s\t%s", "Package Manager:", "Not Found")
|
||||
w.Flush()
|
||||
println()
|
||||
return nil
|
||||
}
|
||||
fmt.Fprintf(w, "%s\t%s\n", "Package Manager: ", info.PM.Name())
|
||||
|
||||
// Output Go Information
|
||||
fmt.Fprintf(w, "%s\t%s\n", "Go Version:", runtime.Version())
|
||||
fmt.Fprintf(w, "%s\t%s\n", "Platform:", runtime.GOOS)
|
||||
fmt.Fprintf(w, "%s\t%s\n", "Architecture:", runtime.GOARCH)
|
||||
|
||||
// Exit early if PM not found
|
||||
if info.PM != nil {
|
||||
fmt.Fprintf(w, "%s\t%s\n", "Package Manager: ", info.PM.Name())
|
||||
}
|
||||
|
||||
// Output Dependencies Status
|
||||
var dependenciesMissing = []string{}
|
||||
var externalPackages = []*packagemanager.Dependancy{}
|
||||
@@ -65,14 +69,12 @@ func AddSubcommand(app *clir.Cli, w io.Writer) error {
|
||||
fmt.Fprintf(w, "Dependency\tPackage Name\tStatus\tVersion\n")
|
||||
fmt.Fprintf(w, "----------\t------------\t------\t-------\n")
|
||||
|
||||
hasOptionalDependencies := false
|
||||
// Loop over dependencies
|
||||
for _, dependency := range info.Dependencies {
|
||||
|
||||
name := dependency.Name
|
||||
if dependency.Optional {
|
||||
name = "*" + name
|
||||
hasOptionalDependencies = true
|
||||
name += "*"
|
||||
}
|
||||
packageName := "Unknown"
|
||||
status := "Not Found"
|
||||
@@ -107,48 +109,44 @@ func AddSubcommand(app *clir.Cli, w io.Writer) error {
|
||||
|
||||
fmt.Fprintf(w, "%s\t%s\t%s\t%s\n", name, packageName, status, dependency.Version)
|
||||
}
|
||||
if hasOptionalDependencies {
|
||||
fmt.Fprintf(w, "\n")
|
||||
fmt.Fprintf(w, "* - Optional Dependency\n")
|
||||
}
|
||||
fmt.Fprintf(w, "\n")
|
||||
fmt.Fprintf(w, "* - Optional Dependency\n")
|
||||
w.Flush()
|
||||
logger.Println("")
|
||||
logger.Println("Diagnosis")
|
||||
logger.Println("---------")
|
||||
logger.Println("---------\n")
|
||||
|
||||
// Generate an appropriate diagnosis
|
||||
|
||||
if len(dependenciesMissing) == 0 && dependenciesAvailableRequired == 0 {
|
||||
logger.Println("Your system is ready for Wails development!")
|
||||
} else {
|
||||
logger.Println("Your system has missing dependencies!\n")
|
||||
}
|
||||
|
||||
if dependenciesAvailableRequired != 0 {
|
||||
logger.Println("Required package(s) installation details: \n" + info.Dependencies.InstallAllRequiredCommand())
|
||||
log.Println("Install required packages using: " + info.Dependencies.InstallAllRequiredCommand())
|
||||
}
|
||||
|
||||
if dependenciesAvailableOptional != 0 {
|
||||
logger.Println("Optional package(s) installation details: \n" + info.Dependencies.InstallAllOptionalCommand())
|
||||
log.Println("Install optional packages using: " + info.Dependencies.InstallAllOptionalCommand())
|
||||
}
|
||||
|
||||
if len(externalPackages) > 0 {
|
||||
for _, p := range externalPackages {
|
||||
if p.Optional {
|
||||
print("[Optional] ")
|
||||
}
|
||||
log.Println("Install " + p.Name + ": " + p.InstallCommand)
|
||||
}
|
||||
}
|
||||
//
|
||||
//if len(externalPackages) > 0 {
|
||||
// for _, p := range externalPackages {
|
||||
// if p.Optional {
|
||||
// print("[Optional] ")
|
||||
// }
|
||||
// logger.Println("Install " + p.Name + ": " + p.InstallCommand)
|
||||
// }
|
||||
//}
|
||||
|
||||
if len(dependenciesMissing) != 0 {
|
||||
// TODO: Check if apps are available locally and if so, adjust the diagnosis
|
||||
logger.Println("Fatal:")
|
||||
logger.Println("Required dependencies missing: " + strings.Join(dependenciesMissing, " "))
|
||||
logger.Println("Please read this article on how to resolve this: https://wails.app/guides/resolving-missing-packages")
|
||||
log.Println("Fatal:")
|
||||
log.Println("Required dependencies missing: " + strings.Join(dependenciesMissing, " "))
|
||||
log.Println("Please read this article on how to resolve this: https://wails.app/guides/resolving-missing-packages")
|
||||
}
|
||||
|
||||
logger.Println("")
|
||||
log.Println("")
|
||||
return nil
|
||||
})
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
"github.com/wailsapp/wails/v2/pkg/parser"
|
||||
)
|
||||
|
||||
// AddSubcommand adds the `generate` command for the Wails application
|
||||
// AddSubcommand adds the `dev` command for the Wails application
|
||||
func AddSubcommand(app *clir.Cli, w io.Writer) error {
|
||||
|
||||
command := app.NewSubCommand("generate", "Code Generation Tools")
|
||||
@@ -19,7 +19,7 @@ func AddSubcommand(app *clir.Cli, w io.Writer) error {
|
||||
|
||||
// Quiet Init
|
||||
quiet := false
|
||||
backendAPI.BoolFlag("q", "Suppress output to console", &quiet)
|
||||
backendAPI.BoolFlag("q", "Supress output to console", &quiet)
|
||||
|
||||
backendAPI.Action(func() error {
|
||||
|
||||
@@ -85,4 +85,7 @@ func logPackage(pkg *parser.Package, logger *clilogger.CLILogger) {
|
||||
}
|
||||
}
|
||||
logger.Println("")
|
||||
|
||||
// logger.Println(" Original Go Package Path:", pkg.Gopackage.PkgPath)
|
||||
// logger.Println(" Original Go Package Path:", pkg.Gopackage.PkgPath)
|
||||
}
|
||||
|
||||
@@ -2,15 +2,13 @@ package initialise
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/wailsapp/wails/v2/pkg/buildassets"
|
||||
"io"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/wailsapp/wails/v2/cmd/wails/internal/commands/initialise/templates"
|
||||
|
||||
"github.com/leaanthony/clir"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/wailsapp/wails/v2/internal/templates"
|
||||
"github.com/wailsapp/wails/v2/pkg/clilogger"
|
||||
"github.com/wailsapp/wails/v2/pkg/git"
|
||||
)
|
||||
@@ -41,7 +39,7 @@ func AddSubcommand(app *clir.Cli, w io.Writer) error {
|
||||
|
||||
// Quiet Init
|
||||
quiet := false
|
||||
command.BoolFlag("q", "Suppress output to console", &quiet)
|
||||
command.BoolFlag("q", "Supress output to console", &quiet)
|
||||
|
||||
initGit := false
|
||||
gitInstalled := git.IsInstalled()
|
||||
@@ -107,7 +105,10 @@ func AddSubcommand(app *clir.Cli, w io.Writer) error {
|
||||
}
|
||||
|
||||
// Try to discover author details from git config
|
||||
findAuthorDetails(options)
|
||||
err := findAuthorDetails(options)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return initProject(options)
|
||||
})
|
||||
@@ -127,12 +128,6 @@ func initProject(options *templates.Options) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// Install the default assets
|
||||
err = buildassets.Install(options.TargetDir, options.ProjectName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if options.InitGit {
|
||||
err = initGit(options)
|
||||
if err != nil {
|
||||
@@ -168,18 +163,20 @@ func initGit(options *templates.Options) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// findAuthorDetails tries to find the user's name and email
|
||||
// from gitconfig. If it finds them, it stores them in the project options
|
||||
func findAuthorDetails(options *templates.Options) {
|
||||
func findAuthorDetails(options *templates.Options) error {
|
||||
if git.IsInstalled() {
|
||||
name, err := git.Name()
|
||||
if err == nil {
|
||||
options.AuthorName = strings.TrimSpace(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
options.AuthorName = strings.TrimSpace(name)
|
||||
|
||||
email, err := git.Email()
|
||||
if err == nil {
|
||||
options.AuthorEmail = strings.TrimSpace(email)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
options.AuthorEmail = strings.TrimSpace(email)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
# README
|
||||
|
||||
## About
|
||||
|
||||
This template uses vanilla JS / HTML and CSS. [Rollup](https://rollupjs.org/guide/en/) is used for processing and bundling the files.
|
||||
|
||||
## Building
|
||||
|
||||
To build this project in debug mode, use `wails build`. For production, use `wails build -production`.
|
||||
To generate a platform native package, add the `-package` flag.
|
||||
|
||||
## Live Development
|
||||
|
||||
To run in live development mode, run `wails dev` in the project directory. In another terminal, go into the `frontend`
|
||||
directory and run `npm run dev`. The frontend dev server will run on http://localhost:5000. Connect to this
|
||||
in your browser and connect to your application.
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,27 +0,0 @@
|
||||
{
|
||||
"name": "vanilla",
|
||||
"version": "1.0.0",
|
||||
"description": "Vanilla Wails v2 template",
|
||||
"main": "src/main.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"build": "rollup -c",
|
||||
"dev": "rollup -c -w",
|
||||
"start": "sirv dist"
|
||||
},
|
||||
"author": "{{.AuthorName}}",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@rollup/plugin-commonjs": "^19.0.0",
|
||||
"@rollup/plugin-image": "^2.0.6",
|
||||
"@rollup/plugin-node-resolve": "^13.0.0",
|
||||
"@rollup/plugin-url": "^6.0.0",
|
||||
"@wails/runtime": "^1.3.19",
|
||||
"rollup": "^2.50.4",
|
||||
"rollup-plugin-copy": "^3.4.0",
|
||||
"rollup-plugin-livereload": "^2.0.0",
|
||||
"rollup-plugin-postcss": "^4.0.0",
|
||||
"rollup-plugin-terser": "^7.0.2",
|
||||
"sirv-cli": "^1.0.12"
|
||||
}
|
||||
}
|
||||
@@ -1,103 +0,0 @@
|
||||
import resolve from '@rollup/plugin-node-resolve';
|
||||
import commonjs from '@rollup/plugin-commonjs';
|
||||
import livereload from 'rollup-plugin-livereload';
|
||||
import { terser } from 'rollup-plugin-terser';
|
||||
import postcss from 'rollup-plugin-postcss';
|
||||
import image from '@rollup/plugin-image';
|
||||
import url from '@rollup/plugin-url';
|
||||
import copy from 'rollup-plugin-copy';
|
||||
|
||||
const production = !process.env.ROLLUP_WATCH;
|
||||
|
||||
export default {
|
||||
input: 'src/main.js',
|
||||
output: {
|
||||
sourcemap: true,
|
||||
format: 'iife',
|
||||
name: 'app',
|
||||
file: 'dist/main.js'
|
||||
},
|
||||
onwarn: handleRollupWarning,
|
||||
plugins: [
|
||||
|
||||
image(),
|
||||
|
||||
copy({
|
||||
targets: [
|
||||
{ src: 'src/index.html', dest: 'dist' },
|
||||
{ src: 'src/main.css', dest: 'dist' },
|
||||
]
|
||||
}),
|
||||
|
||||
// Embed binary files
|
||||
url({
|
||||
include: ['**/*.woff', '**/*.woff2'],
|
||||
limit: Infinity,
|
||||
}),
|
||||
|
||||
// If you have external dependencies installed from
|
||||
// npm, you'll most likely need these plugins. In
|
||||
// some cases you'll need additional configuration -
|
||||
// consult the documentation for details:
|
||||
// https://github.com/rollup/plugins/tree/master/packages/commonjs
|
||||
resolve({
|
||||
browser: true,
|
||||
}),
|
||||
commonjs(),
|
||||
|
||||
// PostCSS preprocessing
|
||||
postcss({
|
||||
extensions: ['.css', '.scss'],
|
||||
extract: true,
|
||||
minimize: false,
|
||||
use: [
|
||||
['sass', {
|
||||
includePaths: [
|
||||
'./src',
|
||||
'./node_modules'
|
||||
]
|
||||
}]
|
||||
],
|
||||
}),
|
||||
|
||||
// In dev mode, call `npm run start` once
|
||||
// the bundle has been generated
|
||||
!production && serve(),
|
||||
|
||||
// Watch the `public` directory and refresh the
|
||||
// browser on changes when not in production
|
||||
!production && livereload('dist'),
|
||||
|
||||
// If we're building for production (npm run build
|
||||
// instead of npm run dev), minify
|
||||
production && terser()
|
||||
],
|
||||
watch: {
|
||||
clearScreen: false
|
||||
}
|
||||
};
|
||||
|
||||
function handleRollupWarning(warning) {
|
||||
console.error('ERROR: ' + warning.toString());
|
||||
}
|
||||
|
||||
function serve() {
|
||||
let server;
|
||||
|
||||
function toExit() {
|
||||
if (server) server.kill(0);
|
||||
}
|
||||
|
||||
return {
|
||||
writeBundle() {
|
||||
if (server) return;
|
||||
server = require('child_process').spawn('npm', ['run', 'start', '--', '--dev'], {
|
||||
stdio: ['ignore', 'inherit', 'inherit'],
|
||||
shell: true
|
||||
});
|
||||
|
||||
process.on('SIGTERM', toExit);
|
||||
process.on('exit', toExit);
|
||||
}
|
||||
};
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
@@ -1,21 +0,0 @@
|
||||
|
||||
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 Basic.Greet(name)
|
||||
window.backend.main.Basic.Greet(name).then((result) => {
|
||||
// Update result with data back from Basic.Greet()
|
||||
document.getElementById("result").innerText = result;
|
||||
});
|
||||
};
|
||||
});
|
||||
@@ -1,39 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/wailsapp/wails/v2"
|
||||
"github.com/wailsapp/wails/v2/pkg/logger"
|
||||
"github.com/wailsapp/wails/v2/pkg/menu"
|
||||
"github.com/wailsapp/wails/v2/pkg/options"
|
||||
"github.com/wailsapp/wails/v2/pkg/options/mac"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
||||
// Create application with options
|
||||
app := NewBasic()
|
||||
|
||||
err := wails.Run(&options.App{
|
||||
Title: "{{.ProjectName}}",
|
||||
Width: 800,
|
||||
Height: 600,
|
||||
DisableResize: true,
|
||||
Mac: &mac.Options{
|
||||
WebviewIsTransparent: true,
|
||||
WindowBackgroundIsTranslucent: true,
|
||||
TitleBar: mac.TitleBarHiddenInset(),
|
||||
Menu: menu.DefaultMacMenu(),
|
||||
},
|
||||
LogLevel: logger.DEBUG,
|
||||
Startup: app.startup,
|
||||
Shutdown: app.shutdown,
|
||||
Bind: []interface{}{
|
||||
app,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
package templates
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/matryer/is"
|
||||
)
|
||||
|
||||
func TestList(t *testing.T) {
|
||||
|
||||
is2 := is.New(t)
|
||||
templates, err := List()
|
||||
is2.NoErr(err)
|
||||
|
||||
println("Found these templates:")
|
||||
for _, template := range templates {
|
||||
fmt.Printf("%+v\n", template)
|
||||
}
|
||||
}
|
||||
|
||||
func TestShortname(t *testing.T) {
|
||||
|
||||
is2 := is.New(t)
|
||||
|
||||
template, err := getTemplateByShortname("vanilla")
|
||||
is2.NoErr(err)
|
||||
|
||||
println("Found this template:")
|
||||
fmt.Printf("%+v\n", template)
|
||||
}
|
||||
|
||||
func TestInstall(t *testing.T) {
|
||||
|
||||
is2 := is.New(t)
|
||||
|
||||
options := &Options{
|
||||
ProjectName: "test",
|
||||
TemplateName: "vanilla",
|
||||
AuthorName: "Lea Anthony",
|
||||
AuthorEmail: "lea.anthony@gmail.com",
|
||||
}
|
||||
|
||||
err := Install(options)
|
||||
is2.NoErr(err)
|
||||
}
|
||||
@@ -18,14 +18,14 @@ import (
|
||||
func AddSubcommand(app *clir.Cli, w io.Writer, currentVersion string) error {
|
||||
|
||||
command := app.NewSubCommand("update", "Update the Wails CLI")
|
||||
command.LongDescription(`This command allows you to update your version of the Wails CLI.`)
|
||||
command.LongDescription(`This command allows you to update your version of Wails.`)
|
||||
|
||||
// Setup flags
|
||||
var prereleaseRequired bool
|
||||
command.BoolFlag("pre", "Update CLI to latest Prerelease", &prereleaseRequired)
|
||||
command.BoolFlag("pre", "Update to latest Prerelease", &prereleaseRequired)
|
||||
|
||||
var specificVersion string
|
||||
command.StringFlag("version", "Install a specific version (Overrides other flags) of the CLI", &specificVersion)
|
||||
command.StringFlag("version", "Install a specific version (Overrides other flags)", &specificVersion)
|
||||
|
||||
command.Action(func() error {
|
||||
|
||||
@@ -143,7 +143,7 @@ func updateToVersion(logger *clilogger.CLILogger, targetVersion *github.Semantic
|
||||
}
|
||||
|
||||
fmt.Println()
|
||||
logger.Print("Installing Wails CLI " + desiredVersion + "...")
|
||||
logger.Print("Installing Wails " + desiredVersion + "...")
|
||||
|
||||
// Run command in non module directory
|
||||
homeDir, err := os.UserHomeDir()
|
||||
@@ -158,7 +158,7 @@ func updateToVersion(logger *clilogger.CLILogger, targetVersion *github.Semantic
|
||||
return err
|
||||
}
|
||||
fmt.Println()
|
||||
logger.Println("Wails CLI updated to " + desiredVersion)
|
||||
logger.Println("Wails updated to " + desiredVersion)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/wailsapp/wails/v2/internal/colour"
|
||||
"github.com/wzshiming/ctc"
|
||||
|
||||
"github.com/wailsapp/wails/v2/cmd/wails/internal/commands/update"
|
||||
|
||||
@@ -22,15 +22,27 @@ func fatal(message string) {
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
func banner(_ *clir.Cli) string {
|
||||
return fmt.Sprintf("%s %s", colour.Yellow("Wails CLI"), colour.DarkRed(version))
|
||||
func col(colour ctc.Color, text string) string {
|
||||
return fmt.Sprintf("%s%s%s", colour, text, ctc.Reset)
|
||||
}
|
||||
|
||||
func Yellow(str string) string {
|
||||
return col(ctc.ForegroundBrightYellow, str)
|
||||
}
|
||||
|
||||
func Red(str string) string {
|
||||
return col(ctc.ForegroundBrightRed, str)
|
||||
}
|
||||
|
||||
func banner(cli *clir.Cli) string {
|
||||
return fmt.Sprintf("%s %s - Go/HTML Application Framework", Yellow("Wails"), Red(version))
|
||||
}
|
||||
|
||||
func main() {
|
||||
|
||||
var err error
|
||||
|
||||
app := clir.NewCli("Wails", "Go/HTML Appkit", version)
|
||||
app := clir.NewCli("Wails", "Go/HTML Application Framework", version)
|
||||
|
||||
app.SetBannerFunction(banner)
|
||||
|
||||
@@ -67,6 +79,5 @@ func main() {
|
||||
err = app.Run()
|
||||
if err != nil {
|
||||
println("\n\nERROR: " + err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
package main
|
||||
|
||||
var version = "v2.0.0-alpha.66"
|
||||
var version = "v2.0.0-alpha.26"
|
||||
|
||||
@@ -4,17 +4,15 @@ go 1.16
|
||||
|
||||
require (
|
||||
github.com/Masterminds/semver v1.5.0
|
||||
github.com/davecgh/go-spew v1.1.1
|
||||
github.com/fatih/structtag v1.2.0
|
||||
github.com/fsnotify/fsnotify v1.4.9
|
||||
github.com/gorilla/websocket v1.4.1
|
||||
github.com/imdario/mergo v0.3.11
|
||||
github.com/jackmordaunt/icns v1.0.0
|
||||
github.com/leaanthony/clir v1.0.4
|
||||
github.com/leaanthony/debme v1.2.0
|
||||
github.com/leaanthony/go-ansi-parser v1.0.1
|
||||
github.com/leaanthony/gosod v1.0.1
|
||||
github.com/leaanthony/gosod v0.0.4
|
||||
github.com/leaanthony/slicer v1.5.0
|
||||
github.com/leaanthony/winicon v0.0.0-20200606125418-4419cea822a0
|
||||
github.com/matryer/is v1.4.0
|
||||
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 // indirect
|
||||
github.com/olekukonko/tablewriter v0.0.4
|
||||
@@ -22,9 +20,10 @@ require (
|
||||
github.com/tdewolff/minify v2.3.6+incompatible
|
||||
github.com/tdewolff/parse v2.3.4+incompatible // indirect
|
||||
github.com/tdewolff/test v1.0.6 // indirect
|
||||
github.com/wzshiming/ctc v1.2.3
|
||||
github.com/wzshiming/ctc v1.2.3 // indirect
|
||||
github.com/xyproto/xpm v1.2.1
|
||||
golang.org/x/net v0.0.0-20200822124328-c89045814202
|
||||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a
|
||||
golang.org/x/sys v0.0.0-20200724161237-0e2f3a69832c
|
||||
golang.org/x/tools v0.0.0-20200902012652-d1954cc86c82
|
||||
nhooyr.io/websocket v1.8.6
|
||||
|
||||
16
v2/go.sum
16
v2/go.sum
@@ -1,6 +1,7 @@
|
||||
github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww=
|
||||
github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
|
||||
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/fatih/structtag v1.2.0 h1:/OdNE99OxoI/PqaW/SuSK9uxxT3f/tcSZgon/ssNSx4=
|
||||
github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94=
|
||||
@@ -41,17 +42,10 @@ github.com/klauspost/compress v1.10.3 h1:OP96hzwJVBIHYU52pVTI6CczrxPvrGfgqF9N5eT
|
||||
github.com/klauspost/compress v1.10.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
|
||||
github.com/leaanthony/clir v1.0.4 h1:Dov2y9zWJmZr7CjaCe86lKa4b5CSxskGAt2yBkoDyiU=
|
||||
github.com/leaanthony/clir v1.0.4/go.mod h1:k/RBkdkFl18xkkACMCLt09bhiZnrGORoxmomeMvDpE0=
|
||||
github.com/leaanthony/debme v1.1.1/go.mod h1:3V+sCm5tYAgQymvSOfYQ5Xx2JCr+OXiD9Jkw3otUjiA=
|
||||
github.com/leaanthony/debme v1.2.0 h1:i7JUQhuv7PtJ/7qV+SIa5QARTGBcojUZ+2eCiV9begU=
|
||||
github.com/leaanthony/debme v1.2.0/go.mod h1:3V+sCm5tYAgQymvSOfYQ5Xx2JCr+OXiD9Jkw3otUjiA=
|
||||
github.com/leaanthony/go-ansi-parser v1.0.1 h1:97v6c5kYppVsbScf4r/VZdXyQ21KQIfeQOk2DgKxGG4=
|
||||
github.com/leaanthony/go-ansi-parser v1.0.1/go.mod h1:7arTzgVI47srICYhvgUV4CGd063sGEeoSlych5yeSPM=
|
||||
github.com/leaanthony/gosod v1.0.1 h1:F+4c3DmEBfigi7oAswCV2RpQ+k4DcNbhuCZUGdBHacQ=
|
||||
github.com/leaanthony/gosod v1.0.1/go.mod h1:W8RyeSFBXu7RpIxPGEJfW4moSyGGEjlJMLV25wEbAdU=
|
||||
github.com/leaanthony/gosod v0.0.4 h1:v4hepo4IyL8E8c9qzDsvYcA0KGh7Npf8As74K5ibQpI=
|
||||
github.com/leaanthony/gosod v0.0.4/go.mod h1:nGMCb1PJfXwBDbOAike78jEYlpqge+xUKFf0iBKjKxU=
|
||||
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/leaanthony/winicon v0.0.0-20200606125418-4419cea822a0 h1:FPGYnfxuuxqCZhrGq8nKjthEcYHgHmFbyY953Xv9cNI=
|
||||
github.com/leaanthony/winicon v0.0.0-20200606125418-4419cea822a0/go.mod h1:en5xhijl92aphrJdmRPlh4NI1L6wq3gEm0LpXAPghjU=
|
||||
github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y=
|
||||
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
|
||||
github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE=
|
||||
@@ -94,8 +88,6 @@ github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9dec
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
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/image v0.0.0-20200430140353-33d19683fad8 h1:6WW6V3x1P/jokJBpRQYUJnMHRP6isStQwCozxnU7XQw=
|
||||
golang.org/x/image v0.0.0-20200430140353-33d19683fad8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
@@ -104,6 +96,8 @@ golang.org/x/net v0.0.0-20200822124328-c89045814202 h1:VvcQYSHwXgi7W+TpUR6A9g6Up
|
||||
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/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-20201207232520-09787c993a3a h1:DcqTD9SDLc+1P/r1EmRBwnVsrOwW+kk2vWf9n+1sGhs=
|
||||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
// +build !windows
|
||||
|
||||
package wails
|
||||
|
||||
func Init() error {
|
||||
return nil
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
package wails
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
func Init() error {
|
||||
status, r, err := syscall.NewLazyDLL("user32.dll").NewProc("SetProcessDPIAware").Call()
|
||||
if status == 0 {
|
||||
return fmt.Errorf("exit status %d: %v %v", status, r, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -20,10 +20,10 @@ func (a *App) Init() error {
|
||||
}
|
||||
|
||||
// Set log levels
|
||||
loglevel := flag.String("loglevel", "debug", "Loglevel to use - Trace, Debug, Info, Warning, Error")
|
||||
greeting := flag.String("loglevel", "debug", "Loglevel to use - Trace, Debug, Info, Warning, Error")
|
||||
flag.Parse()
|
||||
if len(*loglevel) > 0 {
|
||||
switch strings.ToLower(*loglevel) {
|
||||
if len(*greeting) > 0 {
|
||||
switch strings.ToLower(*greeting) {
|
||||
case "trace":
|
||||
a.logger.SetLogLevel(logger.TRACE)
|
||||
case "info":
|
||||
|
||||
@@ -35,7 +35,6 @@ type App struct {
|
||||
//binding *subsystem.Binding
|
||||
call *subsystem.Call
|
||||
menu *subsystem.Menu
|
||||
url *subsystem.URL
|
||||
dispatcher *messagedispatcher.Dispatcher
|
||||
|
||||
menuManager *menumanager.Manager
|
||||
@@ -118,6 +117,14 @@ func (a *App) Run() error {
|
||||
parentContext := context.WithValue(context.Background(), "waitgroup", &subsystemWaitGroup)
|
||||
ctx, cancel := context.WithCancel(parentContext)
|
||||
|
||||
// Setup signal handler
|
||||
signalsubsystem, err := signal.NewManager(ctx, cancel, a.servicebus, a.logger, a.shutdownCallback)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
a.signal = signalsubsystem
|
||||
a.signal.Start()
|
||||
|
||||
// Start the service bus
|
||||
a.servicebus.Debug()
|
||||
err = a.servicebus.Start()
|
||||
@@ -125,7 +132,7 @@ func (a *App) Run() error {
|
||||
return err
|
||||
}
|
||||
|
||||
runtimesubsystem, err := subsystem.NewRuntime(ctx, a.servicebus, a.logger, a.startupCallback)
|
||||
runtimesubsystem, err := subsystem.NewRuntime(ctx, a.servicebus, a.logger, a.startupCallback, a.shutdownCallback)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -161,19 +168,6 @@ func (a *App) Run() error {
|
||||
return err
|
||||
}
|
||||
|
||||
if a.options.Mac.URLHandlers != nil {
|
||||
// Start the url handler subsystem
|
||||
url, err := subsystem.NewURL(a.servicebus, a.logger, a.options.Mac.URLHandlers)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
a.url = url
|
||||
err = a.url.Start()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Start the eventing subsystem
|
||||
eventsubsystem, err := subsystem.NewEvent(ctx, a.servicebus, a.logger)
|
||||
if err != nil {
|
||||
@@ -213,14 +207,6 @@ func (a *App) Run() error {
|
||||
return err
|
||||
}
|
||||
|
||||
// Setup signal handler
|
||||
signalsubsystem, err := signal.NewManager(ctx, cancel, a.servicebus, a.logger)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
a.signal = signalsubsystem
|
||||
a.signal.Start()
|
||||
|
||||
err = a.window.Run(dispatcher, bindingDump, a.debug)
|
||||
a.logger.Trace("Ffenestri.Run() exited")
|
||||
if err != nil {
|
||||
@@ -245,10 +231,5 @@ func (a *App) Run() error {
|
||||
return err
|
||||
}
|
||||
|
||||
// Shutdown callback
|
||||
if a.shutdownCallback != nil {
|
||||
a.shutdownCallback()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -66,6 +66,7 @@ func CreateApp(appoptions *options.App) (*App, error) {
|
||||
|
||||
// Set up logger
|
||||
myLogger := logger.New(appoptions.Logger)
|
||||
myLogger.SetLogLevel(appoptions.LogLevel)
|
||||
|
||||
// Create the menu manager
|
||||
menuManager := menumanager.NewManager()
|
||||
@@ -118,6 +119,14 @@ func (a *App) Run() error {
|
||||
parentContext := context.WithValue(context.Background(), "waitgroup", &subsystemWaitGroup)
|
||||
ctx, cancel := context.WithCancel(parentContext)
|
||||
|
||||
// Setup signal handler
|
||||
signalsubsystem, err := signal.NewManager(ctx, cancel, a.servicebus, a.logger, a.shutdownCallback)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
a.signal = signalsubsystem
|
||||
a.signal.Start()
|
||||
|
||||
// Start the service bus
|
||||
a.servicebus.Debug()
|
||||
err = a.servicebus.Start()
|
||||
@@ -125,7 +134,7 @@ func (a *App) Run() error {
|
||||
return err
|
||||
}
|
||||
|
||||
runtimesubsystem, err := subsystem.NewRuntime(ctx, a.servicebus, a.logger, a.startupCallback)
|
||||
runtimesubsystem, err := subsystem.NewRuntime(ctx, a.servicebus, a.logger, a.startupCallback, a.shutdownCallback)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -203,14 +212,6 @@ func (a *App) Run() error {
|
||||
// Generate backend.js
|
||||
a.bindings.GenerateBackendJS()
|
||||
|
||||
// Setup signal handler
|
||||
signalsubsystem, err := signal.NewManager(ctx, cancel, a.servicebus, a.logger)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
a.signal = signalsubsystem
|
||||
a.signal.Start()
|
||||
|
||||
err = a.bridge.Run(dispatcher, a.menuManager, bindingDump, a.debug)
|
||||
a.logger.Trace("Bridge.Run() exited")
|
||||
if err != nil {
|
||||
@@ -235,10 +236,6 @@ func (a *App) Run() error {
|
||||
return err
|
||||
}
|
||||
|
||||
// Shutdown callback
|
||||
if a.shutdownCallback != nil {
|
||||
a.shutdownCallback()
|
||||
}
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
@@ -79,7 +79,7 @@ func (a *App) Run() error {
|
||||
// Default app options
|
||||
var port = 8080
|
||||
var ip = "localhost"
|
||||
var SuppressLogging = false
|
||||
var supressLogging = false
|
||||
var debugMode = false
|
||||
|
||||
// Create CLI
|
||||
@@ -89,7 +89,7 @@ func (a *App) Run() error {
|
||||
cli.IntFlag("p", "Port to serve on", &port)
|
||||
cli.StringFlag("i", "IP to serve on", &ip)
|
||||
cli.BoolFlag("d", "Debug mode", &debugMode)
|
||||
cli.BoolFlag("q", "Suppress logging", &SuppressLogging)
|
||||
cli.BoolFlag("q", "Supress logging", &supressLogging)
|
||||
|
||||
// Setup main action
|
||||
cli.Action(func() error {
|
||||
@@ -98,8 +98,8 @@ func (a *App) Run() error {
|
||||
a.webserver.SetPort(port)
|
||||
a.webserver.SetIP(ip)
|
||||
a.webserver.SetBindings(a.bindings)
|
||||
// Log information (if we aren't Suppressing it)
|
||||
if !SuppressLogging {
|
||||
// Log information (if we aren't supressing it)
|
||||
if !supressLogging {
|
||||
cli.PrintBanner()
|
||||
a.logger.Info("Running server at %s", a.webserver.URL())
|
||||
}
|
||||
|
||||
@@ -14,6 +14,30 @@ import (
|
||||
"github.com/leaanthony/slicer"
|
||||
)
|
||||
|
||||
const _comment = `
|
||||
|
||||
const backend = {
|
||||
main: {
|
||||
"xbarApp": {
|
||||
"GetCategories": () => {
|
||||
window.backend.main.xbarApp.GetCategories.call(arguments);
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {string} arg1
|
||||
*/
|
||||
"InstallPlugin": (arg1) => {
|
||||
window.backend.main.xbarApp.InstallPlugin.call(arguments);
|
||||
},
|
||||
"GetPlugins": () => {
|
||||
window.backend.main.xbarApp.GetPlugins.call(arguments);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default backend;`
|
||||
|
||||
//go:embed assets/package.json
|
||||
var packageJSON []byte
|
||||
|
||||
@@ -34,7 +58,7 @@ const backend = {`)
|
||||
sortedPackageNames.Add(packageName)
|
||||
}
|
||||
sortedPackageNames.Sort()
|
||||
sortedPackageNames.Each(func(packageName string) {
|
||||
for _, packageName := range sortedPackageNames.AsSlice() {
|
||||
packages := store[packageName]
|
||||
output.WriteString(fmt.Sprintf(" \"%s\": {", packageName))
|
||||
output.WriteString("\n")
|
||||
@@ -43,8 +67,7 @@ const backend = {`)
|
||||
sortedStructNames.Add(structName)
|
||||
}
|
||||
sortedStructNames.Sort()
|
||||
|
||||
sortedStructNames.Each(func(structName string) {
|
||||
for _, structName := range sortedStructNames.AsSlice() {
|
||||
structs := packages[structName]
|
||||
output.WriteString(fmt.Sprintf(" \"%s\": {", structName))
|
||||
output.WriteString("\n")
|
||||
@@ -55,7 +78,7 @@ const backend = {`)
|
||||
}
|
||||
sortedMethodNames.Sort()
|
||||
|
||||
sortedMethodNames.Each(func(methodName string) {
|
||||
for _, methodName := range sortedMethodNames.AsSlice() {
|
||||
methodDetails := structs[methodName]
|
||||
output.WriteString(" /**\n")
|
||||
output.WriteString(" * " + methodName + "\n")
|
||||
@@ -76,8 +99,6 @@ const backend = {`)
|
||||
}
|
||||
returnType += ">"
|
||||
returnTypeDetails = " - Go Type: " + methodDetails.Outputs[0].TypeName
|
||||
} else {
|
||||
returnType = "Promise<void>"
|
||||
}
|
||||
output.WriteString(" * @returns {" + returnType + "} " + returnTypeDetails + "\n")
|
||||
output.WriteString(" */\n")
|
||||
@@ -88,29 +109,25 @@ const backend = {`)
|
||||
output.WriteString("\n")
|
||||
output.WriteString(fmt.Sprintf(" },"))
|
||||
output.WriteString("\n")
|
||||
|
||||
})
|
||||
|
||||
}
|
||||
output.WriteString(fmt.Sprintf(" }"))
|
||||
output.WriteString("\n")
|
||||
})
|
||||
|
||||
}
|
||||
output.WriteString(fmt.Sprintf(" }\n"))
|
||||
output.WriteString("\n")
|
||||
})
|
||||
}
|
||||
|
||||
output.WriteString(`};
|
||||
export default backend;`)
|
||||
output.WriteString("\n")
|
||||
|
||||
// TODO: Make this configurable in wails.json
|
||||
dirname, err := fs.RelativeToCwd("frontend/src/backend")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
if !fs.DirExists(dirname) {
|
||||
err := fs.MkDirs(dirname)
|
||||
err := fs.Mkdir(dirname)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
@@ -144,8 +161,6 @@ func goTypeToJSDocType(input string) string {
|
||||
return "number"
|
||||
case input == "bool":
|
||||
return "boolean"
|
||||
case input == "[]byte":
|
||||
return "string"
|
||||
case strings.HasPrefix(input, "[]"):
|
||||
arrayType := goTypeToJSDocType(input[2:])
|
||||
return "Array.<" + arrayType + ">"
|
||||
|
||||
@@ -1,87 +0,0 @@
|
||||
package binding
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func Test_goTypeToJSDocType(t *testing.T) {
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "string",
|
||||
input: "string",
|
||||
want: "string",
|
||||
},
|
||||
{
|
||||
name: "error",
|
||||
input: "error",
|
||||
want: "Error",
|
||||
},
|
||||
{
|
||||
name: "int",
|
||||
input: "int",
|
||||
want: "number",
|
||||
},
|
||||
{
|
||||
name: "int32",
|
||||
input: "int32",
|
||||
want: "number",
|
||||
},
|
||||
{
|
||||
name: "uint",
|
||||
input: "uint",
|
||||
want: "number",
|
||||
},
|
||||
{
|
||||
name: "uint32",
|
||||
input: "uint32",
|
||||
want: "number",
|
||||
},
|
||||
{
|
||||
name: "float32",
|
||||
input: "float32",
|
||||
want: "number",
|
||||
},
|
||||
{
|
||||
name: "float64",
|
||||
input: "float64",
|
||||
want: "number",
|
||||
},
|
||||
{
|
||||
name: "bool",
|
||||
input: "bool",
|
||||
want: "boolean",
|
||||
},
|
||||
{
|
||||
name: "[]byte",
|
||||
input: "[]byte",
|
||||
want: "string",
|
||||
},
|
||||
{
|
||||
name: "[]int",
|
||||
input: "[]int",
|
||||
want: "Array.<number>",
|
||||
},
|
||||
{
|
||||
name: "[]bool",
|
||||
input: "[]bool",
|
||||
want: "Array.<boolean>",
|
||||
},
|
||||
{
|
||||
name: "anything else",
|
||||
input: "foo",
|
||||
want: "any",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := goTypeToJSDocType(tt.input); got != tt.want {
|
||||
t.Errorf("goTypeToJSDocType() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -11,10 +11,6 @@ type BridgeClient struct {
|
||||
messageCache chan string
|
||||
}
|
||||
|
||||
func (b BridgeClient) DeleteTrayMenuByID(id string) {
|
||||
b.session.sendMessage("TD" + id)
|
||||
}
|
||||
|
||||
func NewBridgeClient() *BridgeClient {
|
||||
return &BridgeClient{
|
||||
messageCache: make(chan string, 100),
|
||||
@@ -26,8 +22,9 @@ func (b BridgeClient) Quit() {
|
||||
}
|
||||
|
||||
func (b BridgeClient) NotifyEvent(message string) {
|
||||
b.session.sendMessage("n" + message)
|
||||
//b.session.sendMessage("n" + message)
|
||||
b.session.log.Info("NotifyEvent: %s", message)
|
||||
b.session.log.Info("NotifyEvent unsupported in Bridge mode")
|
||||
}
|
||||
|
||||
func (b BridgeClient) CallResult(message string) {
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -19,9 +19,6 @@ type DialogClient struct {
|
||||
log *logger.Logger
|
||||
}
|
||||
|
||||
func (d *DialogClient) DeleteTrayMenuByID(id string) {
|
||||
}
|
||||
|
||||
func NewDialogClient(log *logger.Logger) *DialogClient {
|
||||
return &DialogClient{
|
||||
log: log,
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -18,12 +18,6 @@ import (
|
||||
//go:embed darwin.js
|
||||
var darwinRuntime string
|
||||
|
||||
//go:embed windows.js
|
||||
var windowsRuntime string
|
||||
|
||||
//go:embed linux.js
|
||||
var linuxRuntime string
|
||||
|
||||
// session represents a single websocket session
|
||||
type session struct {
|
||||
bindings string
|
||||
@@ -90,10 +84,6 @@ func (s *session) start(firstSession bool) {
|
||||
switch runtime.GOOS {
|
||||
case "darwin":
|
||||
wailsRuntime = darwinRuntime
|
||||
case "windows":
|
||||
wailsRuntime = windowsRuntime
|
||||
case "linux":
|
||||
wailsRuntime = linuxRuntime
|
||||
default:
|
||||
log.Fatal("platform not supported")
|
||||
}
|
||||
@@ -102,10 +92,7 @@ func (s *session) start(firstSession bool) {
|
||||
s.log.Info(bindingsMessage)
|
||||
bootstrapMessage := bindingsMessage + wailsRuntime
|
||||
|
||||
err := s.sendMessage("b" + bootstrapMessage)
|
||||
if err != nil {
|
||||
s.log.Error(err.Error())
|
||||
}
|
||||
s.sendMessage("b" + bootstrapMessage)
|
||||
|
||||
// Send menus
|
||||
traymenus, err := s.menumanager.GetTrayMenus()
|
||||
@@ -114,10 +101,7 @@ func (s *session) start(firstSession bool) {
|
||||
}
|
||||
|
||||
for _, trayMenu := range traymenus {
|
||||
err := s.sendMessage("TS" + trayMenu)
|
||||
if err != nil {
|
||||
s.log.Error(err.Error())
|
||||
}
|
||||
s.sendMessage("TS" + trayMenu)
|
||||
}
|
||||
|
||||
for {
|
||||
@@ -146,10 +130,7 @@ func (s *session) start(firstSession bool) {
|
||||
|
||||
// Shutdown
|
||||
func (s *session) Shutdown() {
|
||||
err := s.conn.Close()
|
||||
if err != nil {
|
||||
s.log.Error(err.Error())
|
||||
}
|
||||
s.conn.Close()
|
||||
s.done = true
|
||||
s.log.Info("session %v exit", s.Identifier())
|
||||
}
|
||||
@@ -165,16 +146,10 @@ func (s *session) writePump() {
|
||||
s.Shutdown()
|
||||
return
|
||||
case msg, ok := <-s.writeChan:
|
||||
err := s.conn.SetWriteDeadline(time.Now().Add(1 * time.Second))
|
||||
if err != nil {
|
||||
s.log.Error(err.Error())
|
||||
}
|
||||
s.conn.SetWriteDeadline(time.Now().Add(1 * time.Second))
|
||||
if !ok {
|
||||
s.log.Debug("writeChan was closed!")
|
||||
err := s.conn.WriteMessage(websocket.CloseMessage, []byte{})
|
||||
if err != nil {
|
||||
s.log.Error(err.Error())
|
||||
}
|
||||
s.conn.WriteMessage(websocket.CloseMessage, []byte{})
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1,89 +0,0 @@
|
||||
package colour
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/wzshiming/ctc"
|
||||
)
|
||||
|
||||
func Col(col ctc.Color, text string) string {
|
||||
return fmt.Sprintf("%s%s%s", col, text, ctc.Reset)
|
||||
}
|
||||
|
||||
func Yellow(text string) string {
|
||||
return Col(ctc.ForegroundBrightYellow, text)
|
||||
}
|
||||
|
||||
func Red(text string) string {
|
||||
return Col(ctc.ForegroundBrightRed, text)
|
||||
}
|
||||
|
||||
func Blue(text string) string {
|
||||
return Col(ctc.ForegroundBrightBlue, text)
|
||||
}
|
||||
|
||||
func Green(text string) string {
|
||||
return Col(ctc.ForegroundBrightGreen, text)
|
||||
}
|
||||
|
||||
func Cyan(text string) string {
|
||||
return Col(ctc.ForegroundBrightCyan, text)
|
||||
}
|
||||
|
||||
func Magenta(text string) string {
|
||||
return Col(ctc.ForegroundBrightMagenta, text)
|
||||
}
|
||||
|
||||
func White(text string) string {
|
||||
return Col(ctc.ForegroundBrightWhite, text)
|
||||
}
|
||||
|
||||
func Black(text string) string {
|
||||
return Col(ctc.ForegroundBrightBlack, text)
|
||||
}
|
||||
|
||||
func DarkYellow(text string) string {
|
||||
return Col(ctc.ForegroundYellow, text)
|
||||
}
|
||||
|
||||
func DarkRed(text string) string {
|
||||
return Col(ctc.ForegroundRed, text)
|
||||
}
|
||||
|
||||
func DarkBlue(text string) string {
|
||||
return Col(ctc.ForegroundBlue, text)
|
||||
}
|
||||
|
||||
func DarkGreen(text string) string {
|
||||
return Col(ctc.ForegroundGreen, text)
|
||||
}
|
||||
|
||||
func DarkCyan(text string) string {
|
||||
return Col(ctc.ForegroundCyan, text)
|
||||
}
|
||||
|
||||
func DarkMagenta(text string) string {
|
||||
return Col(ctc.ForegroundMagenta, text)
|
||||
}
|
||||
|
||||
func DarkWhite(text string) string {
|
||||
return Col(ctc.ForegroundWhite, text)
|
||||
}
|
||||
|
||||
func DarkBlack(text string) string {
|
||||
return Col(ctc.ForegroundBlack, text)
|
||||
}
|
||||
|
||||
var rainbowCols = []func(string) string{Red, Yellow, Green, Cyan, Blue, Magenta}
|
||||
|
||||
func Rainbow(text string) string {
|
||||
var builder strings.Builder
|
||||
|
||||
for i := 0; i < len(text); i++ {
|
||||
fn := rainbowCols[i%len(rainbowCols)]
|
||||
builder.WriteString(fn(text[i : i+1]))
|
||||
}
|
||||
|
||||
return builder.String()
|
||||
}
|
||||
@@ -1,11 +1,5 @@
|
||||
# 3rd Party Licenses
|
||||
|
||||
## Webview
|
||||
Whilst not using the library directly, there is certainly some code that is inspired by or used from the webview library.
|
||||
|
||||
Homepage: https://github.com/webview/webview
|
||||
License: https://github.com/webview/webview/blob/master/LICENSE
|
||||
|
||||
## vec
|
||||
Homepage: https://github.com/rxi/vec
|
||||
License: https://github.com/rxi/vec/blob/master/LICENSE
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
// +build !windows
|
||||
|
||||
|
||||
//
|
||||
// Created by Lea Anthony on 6/1/21.
|
||||
//
|
||||
|
||||
@@ -5,6 +5,10 @@
|
||||
#ifndef COMMON_H
|
||||
#define COMMON_H
|
||||
|
||||
#define OBJC_OLD_DISPATCH_PROTOTYPES 1
|
||||
#include <objc/objc-runtime.h>
|
||||
#include <CoreGraphics/CoreGraphics.h>
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdarg.h>
|
||||
#include "string.h"
|
||||
|
||||
@@ -82,10 +82,10 @@ void ShowContextMenu(ContextMenuStore* store, id mainWindow, const char *context
|
||||
FREE_AND_SET(contextMenu->contextMenuData, contextMenuData);
|
||||
|
||||
// Grab the content view and show the menu
|
||||
id contentView = msg_reg(mainWindow, s("contentView"));
|
||||
id contentView = msg(mainWindow, s("contentView"));
|
||||
|
||||
// Get the triggering event
|
||||
id menuEvent = msg_reg(mainWindow, s("currentEvent"));
|
||||
id menuEvent = msg(mainWindow, s("currentEvent"));
|
||||
|
||||
if( contextMenu->nsmenu == NULL ) {
|
||||
// GetMenu creates the NSMenu
|
||||
@@ -93,7 +93,7 @@ void ShowContextMenu(ContextMenuStore* store, id mainWindow, const char *context
|
||||
}
|
||||
|
||||
// Show popup
|
||||
((id(*)(id, SEL, id, id, id))objc_msgSend)(c("NSMenu"), s("popUpContextMenu:withEvent:forView:"), contextMenu->nsmenu, menuEvent, contentView);
|
||||
msg(c("NSMenu"), s("popUpContextMenu:withEvent:forView:"), contextMenu->nsmenu, menuEvent, contentView);
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -1,64 +0,0 @@
|
||||
// Credit: https://gist.github.com/ysc3839/b08d2bff1c7dacde529bed1d37e85ccf
|
||||
#pragma once
|
||||
|
||||
typedef enum _WINDOWCOMPOSITIONATTRIB
|
||||
{
|
||||
WCA_UNDEFINED = 0,
|
||||
WCA_NCRENDERING_ENABLED = 1,
|
||||
WCA_NCRENDERING_POLICY = 2,
|
||||
WCA_TRANSITIONS_FORCEDISABLED = 3,
|
||||
WCA_ALLOW_NCPAINT = 4,
|
||||
WCA_CAPTION_BUTTON_BOUNDS = 5,
|
||||
WCA_NONCLIENT_RTL_LAYOUT = 6,
|
||||
WCA_FORCE_ICONIC_REPRESENTATION = 7,
|
||||
WCA_EXTENDED_FRAME_BOUNDS = 8,
|
||||
WCA_HAS_ICONIC_BITMAP = 9,
|
||||
WCA_THEME_ATTRIBUTES = 10,
|
||||
WCA_NCRENDERING_EXILED = 11,
|
||||
WCA_NCADORNMENTINFO = 12,
|
||||
WCA_EXCLUDED_FROM_LIVEPREVIEW = 13,
|
||||
WCA_VIDEO_OVERLAY_ACTIVE = 14,
|
||||
WCA_FORCE_ACTIVEWINDOW_APPEARANCE = 15,
|
||||
WCA_DISALLOW_PEEK = 16,
|
||||
WCA_CLOAK = 17,
|
||||
WCA_CLOAKED = 18,
|
||||
WCA_ACCENT_POLICY = 19,
|
||||
WCA_FREEZE_REPRESENTATION = 20,
|
||||
WCA_EVER_UNCLOAKED = 21,
|
||||
WCA_VISUAL_OWNER = 22,
|
||||
WCA_HOLOGRAPHIC = 23,
|
||||
WCA_EXCLUDED_FROM_DDA = 24,
|
||||
WCA_PASSIVEUPDATEMODE = 25,
|
||||
WCA_USEDARKMODECOLORS = 26,
|
||||
WCA_LAST = 27
|
||||
} WINDOWCOMPOSITIONATTRIB;
|
||||
|
||||
typedef struct _WINDOWCOMPOSITIONATTRIBDATA
|
||||
{
|
||||
WINDOWCOMPOSITIONATTRIB Attrib;
|
||||
PVOID pvData;
|
||||
SIZE_T cbData;
|
||||
} WINDOWCOMPOSITIONATTRIBDATA;
|
||||
|
||||
typedef enum _ACCENT_STATE
|
||||
{
|
||||
ACCENT_DISABLED = 0,
|
||||
ACCENT_ENABLE_GRADIENT = 1,
|
||||
ACCENT_ENABLE_TRANSPARENTGRADIENT = 2,
|
||||
ACCENT_ENABLE_BLURBEHIND = 3,
|
||||
ACCENT_ENABLE_ACRYLICBLURBEHIND = 4, // RS4 1803
|
||||
ACCENT_ENABLE_HOSTBACKDROP = 5, // RS5 1809
|
||||
ACCENT_INVALID_STATE = 6
|
||||
} ACCENT_STATE;
|
||||
|
||||
typedef struct _ACCENT_POLICY
|
||||
{
|
||||
ACCENT_STATE AccentState;
|
||||
DWORD AccentFlags;
|
||||
DWORD GradientColor;
|
||||
DWORD AnimationId;
|
||||
} ACCENT_POLICY;
|
||||
|
||||
typedef BOOL (WINAPI *pfnGetWindowCompositionAttribute)(HWND, WINDOWCOMPOSITIONATTRIBDATA*);
|
||||
|
||||
typedef BOOL (WINAPI *pfnSetWindowCompositionAttribute)(HWND, WINDOWCOMPOSITIONATTRIBDATA*);
|
||||
@@ -13,17 +13,17 @@ import (
|
||||
)
|
||||
|
||||
/*
|
||||
|
||||
#cgo linux CFLAGS: -DFFENESTRI_LINUX=1
|
||||
#cgo linux pkg-config: gtk+-3.0 webkit2gtk-4.0
|
||||
|
||||
#cgo darwin CFLAGS: -DFFENESTRI_DARWIN=1
|
||||
#cgo darwin LDFLAGS: -framework WebKit -lobjc
|
||||
|
||||
#cgo windows CXXFLAGS: -std=c++11
|
||||
#cgo windows,amd64 LDFLAGS: -L./windows/x64 -lwebview -lWebView2Loader -lgdi32 -lole32 -lShlwapi -luser32 -loleaut32
|
||||
|
||||
#include <stdlib.h>
|
||||
#include "ffenestri.h"
|
||||
|
||||
|
||||
*/
|
||||
import "C"
|
||||
|
||||
@@ -65,6 +65,14 @@ func NewApplicationWithConfig(config *options.App, logger *logger.Logger, menuMa
|
||||
}
|
||||
}
|
||||
|
||||
// NewApplication creates a new Application with the default config
|
||||
func NewApplication(logger *logger.Logger) *Application {
|
||||
return &Application{
|
||||
config: options.Default,
|
||||
logger: logger.CustomLogger("Ffenestri"),
|
||||
}
|
||||
}
|
||||
|
||||
func (a *Application) freeMemory() {
|
||||
for _, mem := range a.memory {
|
||||
// fmt.Printf("Freeing memory: %+v\n", mem)
|
||||
@@ -131,9 +139,9 @@ func (a *Application) Run(incomingDispatcher Dispatcher, bindings string, debug
|
||||
C.SetDebug(app, a.bool2Cint(debug))
|
||||
|
||||
// TODO: Move frameless to Linux options
|
||||
if a.config.Frameless {
|
||||
C.DisableFrame(a.app)
|
||||
}
|
||||
// if a.config.Frameless {
|
||||
// C.DisableFrame(a.app)
|
||||
// }
|
||||
|
||||
if a.config.RGBA != 0 {
|
||||
r, g, b, alpha := intToColour(a.config.RGBA)
|
||||
@@ -165,7 +173,7 @@ func (a *Application) Run(incomingDispatcher Dispatcher, bindings string, debug
|
||||
// Oh no! We couldn't initialise the application
|
||||
a.logger.Fatal("Cannot initialise Application.")
|
||||
}
|
||||
//println("\n\n\n\n\n\nhererererer\n\n\n\n")
|
||||
|
||||
a.freeMemory()
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
#ifndef __FFENESTRI_H__
|
||||
#define __FFENESTRI_H__
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <stdio.h>
|
||||
struct Application;
|
||||
|
||||
@@ -41,15 +37,8 @@ extern void DarkModeEnabled(struct Application*, char *callbackID);
|
||||
extern void SetApplicationMenu(struct Application*, const char *);
|
||||
extern void AddTrayMenu(struct Application*, const char *menuTrayJSON);
|
||||
extern void SetTrayMenu(struct Application*, const char *menuTrayJSON);
|
||||
extern void DeleteTrayMenuByID(struct Application*, const char *id);
|
||||
extern void UpdateTrayMenuLabel(struct Application*, const char* JSON);
|
||||
extern void AddContextMenu(struct Application*, char *contextMenuJSON);
|
||||
extern void UpdateContextMenu(struct Application*, char *contextMenuJSON);
|
||||
extern void WebviewIsTransparent(struct Application*);
|
||||
extern void WindowBackgroundIsTranslucent(struct Application*);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1,7 +1,13 @@
|
||||
package ffenestri
|
||||
|
||||
/*
|
||||
|
||||
#cgo linux CFLAGS: -DFFENESTRI_LINUX=1
|
||||
#cgo linux pkg-config: gtk+-3.0 webkit2gtk-4.0
|
||||
|
||||
#include <stdlib.h>
|
||||
#include "ffenestri.h"
|
||||
|
||||
*/
|
||||
import "C"
|
||||
|
||||
@@ -202,7 +208,3 @@ func (c *Client) UpdateTrayMenuLabel(JSON string) {
|
||||
func (c *Client) UpdateContextMenu(contextMenuJSON string) {
|
||||
C.UpdateContextMenu(c.app.app, c.app.string2CString(contextMenuJSON))
|
||||
}
|
||||
|
||||
func (c *Client) DeleteTrayMenuByID(id string) {
|
||||
C.DeleteTrayMenuByID(c.app.app, c.app.string2CString(id))
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -48,9 +48,6 @@ func (a *Application) processPlatformSettings() error {
|
||||
C.SetAppearance(a.app, a.string2CString(string(mac.Appearance)))
|
||||
}
|
||||
|
||||
// Set activation policy
|
||||
C.SetActivationPolicy(a.app, C.int(mac.ActivationPolicy))
|
||||
|
||||
// Check if the webview should be transparent
|
||||
if mac.WebviewIsTransparent {
|
||||
C.WebviewIsTransparent(a.app)
|
||||
@@ -90,10 +87,5 @@ func (a *Application) processPlatformSettings() error {
|
||||
}
|
||||
}
|
||||
|
||||
// Process URL Handlers
|
||||
if a.config.Mac.URLHandlers != nil {
|
||||
C.HasURLHandlers(a.app)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -11,51 +11,24 @@
|
||||
#include "hashmap.h"
|
||||
#include "stdlib.h"
|
||||
|
||||
typedef struct {
|
||||
long maj;
|
||||
long min;
|
||||
long patch;
|
||||
} OSVersion;
|
||||
|
||||
// Macros to make it slightly more sane
|
||||
#define msg objc_msgSend
|
||||
#define msg_reg ((id(*)(id, SEL))objc_msgSend)
|
||||
#define msg_id ((id(*)(id, SEL, id))objc_msgSend)
|
||||
#define msg_id_id ((id(*)(id, SEL, id, id))objc_msgSend)
|
||||
#define msg_bool ((id(*)(id, SEL, BOOL))objc_msgSend)
|
||||
#define msg_int ((id(*)(id, SEL, int))objc_msgSend)
|
||||
#define msg_uint ((id(*)(id, SEL, unsigned int))objc_msgSend)
|
||||
#define msg_float ((id(*)(id, SEL, float))objc_msgSend)
|
||||
#define kInternetEventClass 'GURL'
|
||||
#define kAEGetURL 'GURL'
|
||||
#define keyDirectObject '----'
|
||||
|
||||
#define c(str) (id)objc_getClass(str)
|
||||
#define s(str) sel_registerName(str)
|
||||
#define u(str) sel_getUid(str)
|
||||
#define str(input) ((id(*)(id, SEL, const char *))objc_msgSend)(c("NSString"), s("stringWithUTF8String:"), input)
|
||||
#define strunicode(input) ((id(*)(id, SEL, id, unsigned short))objc_msgSend)(c("NSString"), s("stringWithFormat:"), str("%C"), (unsigned short)input)
|
||||
#define cstr(input) (const char *)msg_reg(input, s("UTF8String"))
|
||||
#define url(input) msg_id(c("NSURL"), s("fileURLWithPath:"), str(input))
|
||||
#define ALLOC(classname) msg_reg(c(classname), s("alloc"))
|
||||
#define ALLOC_INIT(classname) msg_reg(msg_reg(c(classname), s("alloc")), s("init"))
|
||||
|
||||
#if defined (__aarch64__)
|
||||
#define GET_FRAME(receiver) ((CGRect(*)(id, SEL))objc_msgSend)(receiver, s("frame"))
|
||||
#define GET_BOUNDS(receiver) ((CGRect(*)(id, SEL))objc_msgSend)(receiver, s("bounds"))
|
||||
#define GET_OSVERSION(receiver) ((OSVersion(*)(id, SEL))objc_msgSend)(processInfo, s("operatingSystemVersion"));
|
||||
#endif
|
||||
|
||||
#if defined (__x86_64__)
|
||||
#define str(input) msg(c("NSString"), s("stringWithUTF8String:"), input)
|
||||
#define strunicode(input) msg(c("NSString"), s("stringWithFormat:"), str("%C"), (unsigned short)input)
|
||||
#define cstr(input) (const char *)msg(input, s("UTF8String"))
|
||||
#define url(input) msg(c("NSURL"), s("fileURLWithPath:"), str(input))
|
||||
#define ALLOC(classname) msg(c(classname), s("alloc"))
|
||||
#define ALLOC_INIT(classname) msg(msg(c(classname), s("alloc")), s("init"))
|
||||
#define GET_FRAME(receiver) ((CGRect(*)(id, SEL))objc_msgSend_stret)(receiver, s("frame"))
|
||||
#define GET_BOUNDS(receiver) ((CGRect(*)(id, SEL))objc_msgSend_stret)(receiver, s("bounds"))
|
||||
#define GET_OSVERSION(receiver) ((OSVersion(*)(id, SEL))objc_msgSend_stret)(processInfo, s("operatingSystemVersion"));
|
||||
#endif
|
||||
|
||||
#define GET_BACKINGSCALEFACTOR(receiver) ((CGFloat(*)(id, SEL))objc_msgSend)(receiver, s("backingScaleFactor"))
|
||||
#define GET_BACKINGSCALEFACTOR(receiver) ((CGFloat(*)(id, SEL))msg)(receiver, s("backingScaleFactor"))
|
||||
|
||||
#define ON_MAIN_THREAD(str) dispatch( ^{ str; } )
|
||||
#define MAIN_WINDOW_CALL(str) msg_reg(app->mainWindow, s((str)))
|
||||
#define MAIN_WINDOW_CALL(str) msg(app->mainWindow, s((str)))
|
||||
|
||||
#define NSBackingStoreBuffered 2
|
||||
|
||||
@@ -93,10 +66,6 @@ typedef struct {
|
||||
#define NSControlStateValueOff 0
|
||||
#define NSControlStateValueOn 1
|
||||
|
||||
#define NSApplicationActivationPolicyRegular 0
|
||||
#define NSApplicationActivationPolicyAccessory 1
|
||||
#define NSApplicationActivationPolicyProhibited 2
|
||||
|
||||
// Unbelievably, if the user swaps their button preference
|
||||
// then right buttons are reported as left buttons
|
||||
#define NSEventMaskLeftMouseDown 1 << 1
|
||||
@@ -125,8 +94,6 @@ typedef struct {
|
||||
#define NSAlertSecondButtonReturn 1001
|
||||
#define NSAlertThirdButtonReturn 1002
|
||||
|
||||
#define BrokenImage "iVBORw0KGgoAAAANSUhEUgAAABAAAAASCAMAAABl5a5YAAABj1BMVEWopan///+koqSWk5P9/v3///////////+AgACMiovz8/PB0fG9z+3i4+WysbGBfX1Erh80rACLiYqBxolEsDhHlDEbqQDDx+CNho7W1tj4+/bw+O3P5Mn4/f/W1tbK6sX////b2dn////////////8/Pz6+vro6Ojj4+P////G1PL////EzNydmp2cmZnd3eDF1PHs8v/o8P/Q3vrS3vfE0vCdmpqZkpr19/3N2vXI1vPH1fOgnqDg6frP3PbCytvHx8irqq6HhIZtuGtjnlZetU1Xs0NWskBNsi7w9v/d6P7w9P3S4Pzr8Pvl7PrY5PrU4PjQ3fjD1Ozo6Om30NjGzNi7ubm34K+UxKmbnaWXlJeUjpSPi4tppF1TtjxSsTf2+f7L2PTr7e3H2+3V7+q+0uXg4OPg4eLR1uG7z+Hg4ODGzODV2N7V1trP5dmxzs65vcfFxMWq0cKxxr+/vr+0s7apxbWaxrCv2qao05+dlp2Uuo2Dn4F8vIB6xnyAoHmAym9zqGpctENLryNFsgoblJpnAAAAKnRSTlP+hP7+5ZRmYgL+/f39/f39/f38/Pz8/Pv69+7j083My8GocnBPTTMWEgjxeITOAAABEklEQVQY0y3KZXuCYBiG4ceYuu7u3nyVAaKOMBBQ7O5Yd3f3fvheDnd9u8/jBkGwNxP6sjOWVQvY/ftrzfT6bd3yEhCnYZqiaYoKiwX/gXkFiHySTcUTLJMsZ9v8nQvgssWYOEKedKpcOO6CUXD5IlGEY5hLUbyDAAZ6HRf1bnkoavOsFQibg+Q4nuNYL+ON5PHD5nBaraRVyxnzGf6BJzUi2QQCQgMyk8tleL7dg1owpJ17D5IkvV100EingeOopPyo6vfAuXF+9hbDTknZCIaUoeK4efKwG4iT6xDewd7imGlid7gGwv37b6Oh9jwaTdOf/Tc1qH7UZVmuP6G5qZfBr9cAGNy4KiDd4tXIs7tS+QO9aUKvPAIKuQAAAABJRU5ErkJggg=="
|
||||
|
||||
struct Application;
|
||||
int releaseNSObject(void *const context, struct hashmap_element_s *const e);
|
||||
void TitlebarAppearsTransparent(struct Application* app);
|
||||
@@ -143,12 +110,6 @@ void SetTray(struct Application* app, const char *, const char *, const char *);
|
||||
//void SetContextMenus(struct Application* app, const char *);
|
||||
void AddTrayMenu(struct Application* app, const char *);
|
||||
|
||||
void SetActivationPolicy(struct Application* app, int policy);
|
||||
|
||||
void* lookupStringConstant(id constantName);
|
||||
|
||||
void HasURLHandlers(struct Application* app);
|
||||
|
||||
id createImageFromBase64Data(const char *data, bool isTemplateImage);
|
||||
|
||||
#endif
|
||||
@@ -2,7 +2,6 @@
|
||||
#ifndef __FFENESTRI_LINUX_H__
|
||||
#define __FFENESTRI_LINUX_H__
|
||||
|
||||
#include "common.h"
|
||||
#include "gtk/gtk.h"
|
||||
#include "webkit2/webkit2.h"
|
||||
#include <time.h>
|
||||
@@ -12,10 +11,9 @@
|
||||
#include <stdarg.h>
|
||||
|
||||
// References to assets
|
||||
extern const unsigned char *assets[];
|
||||
extern const unsigned char runtime;
|
||||
|
||||
#include "icon.h"
|
||||
#include "assets.h"
|
||||
extern const char *icon[];
|
||||
|
||||
// Constants
|
||||
#define PRIMARY_MOUSE_BUTTON 1
|
||||
@@ -25,6 +23,17 @@ extern const unsigned char runtime;
|
||||
// MAIN DEBUG FLAG
|
||||
int debug;
|
||||
|
||||
// Credit: https://stackoverflow.com/a/8465083
|
||||
char *concat(const char *s1, const char *s2)
|
||||
{
|
||||
const size_t len1 = strlen(s1);
|
||||
const size_t len2 = strlen(s2);
|
||||
char *result = malloc(len1 + len2 + 1);
|
||||
memcpy(result, s1, len1);
|
||||
memcpy(result + len1, s2, len2 + 1);
|
||||
return result;
|
||||
}
|
||||
|
||||
// Debug works like sprintf but mutes if the global debug flag is true
|
||||
// Credit: https://stackoverflow.com/a/20639708
|
||||
void Debug(char *message, ...)
|
||||
@@ -303,17 +312,17 @@ void SetMaxWindowSize(struct Application *app, int maxWidth, int maxHeight)
|
||||
}
|
||||
|
||||
// SetColour sets the colour of the webview to the given colour string
|
||||
void SetColour(struct Application *app, int red, int green, int blue, int alpha)
|
||||
int SetColour(struct Application *app, const char *colourString)
|
||||
{
|
||||
// GdkRGBA rgba;
|
||||
// rgba.
|
||||
// gboolean result = gdk_rgba_parse(&rgba, colourString);
|
||||
// if (result == FALSE)
|
||||
// {
|
||||
// return 0;
|
||||
// }
|
||||
// // Debug("Setting webview colour to: %s", colourString);
|
||||
// webkit_web_view_get_background_color((WebKitWebView *)(app->webView), &rgba);
|
||||
GdkRGBA rgba;
|
||||
gboolean result = gdk_rgba_parse(&rgba, colourString);
|
||||
if (result == FALSE)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
// Debug("Setting webview colour to: %s", colourString);
|
||||
webkit_web_view_get_background_color((WebKitWebView *)(app->webView), &rgba);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// DisableFrame disables the window frame
|
||||
@@ -514,11 +523,6 @@ static void load_finished_cb(WebKitWebView *webView,
|
||||
Debug("Binding Methods");
|
||||
syncEval(app, app->bindings);
|
||||
|
||||
// Setup IPC commands
|
||||
Debug("Setting up IPC methods");
|
||||
const char *invoke = "window.wailsInvoke=function(message){window.webkit.messageHandlers.external.postMessage(message);};window.wailsDrag=function(message){window.webkit.messageHandlers.windowDrag.postMessage(message);};window.wailsContextMenuMessage=function(message){window.webkit.messageHandlers.contextMenu.postMessage(message);};";
|
||||
syncEval(app, invoke);
|
||||
|
||||
// Runtime
|
||||
Debug("Setting up Wails runtime");
|
||||
syncEval(app, &runtime);
|
||||
@@ -820,7 +824,7 @@ void Show(struct Application *app) {
|
||||
|
||||
// maximiseInternal maximises the main window
|
||||
void maximiseInternal(struct Application *app) {
|
||||
gtk_window_maximize(GTK_WINDOW(app->mainWindow));
|
||||
gtk_window_maximize(GTK_WIDGET(app->mainWindow));
|
||||
}
|
||||
|
||||
// Maximise places the maximiseInternal method onto the main thread for execution
|
||||
@@ -837,7 +841,7 @@ void Maximise(struct Application *app) {
|
||||
|
||||
// unmaximiseInternal unmaximises the main window
|
||||
void unmaximiseInternal(struct Application *app) {
|
||||
gtk_window_unmaximize(GTK_WINDOW(app->mainWindow));
|
||||
gtk_window_unmaximize(GTK_WIDGET(app->mainWindow));
|
||||
}
|
||||
|
||||
// Unmaximise places the unmaximiseInternal method onto the main thread for execution
|
||||
@@ -853,21 +857,6 @@ void Unmaximise(struct Application *app) {
|
||||
}
|
||||
|
||||
|
||||
void DarkModeEnabled(struct Application* app, char *callbackID) {}
|
||||
void SetApplicationMenu(struct Application* app, const char *menuJSON) {}
|
||||
void AddTrayMenu(struct Application* app, const char *menuTrayJSON) {}
|
||||
void SetTrayMenu(struct Application* app, const char *menuTrayJSON) {}
|
||||
void DeleteTrayMenuByID(struct Application* app, const char *id) {}
|
||||
void UpdateTrayMenuLabel(struct Application* app, const char* JSON) {}
|
||||
void AddContextMenu(struct Application* app, char *contextMenuJSON) {}
|
||||
void UpdateContextMenu(struct Application* app, char *contextMenuJSON) {}
|
||||
void WebviewIsTransparent(struct Application* app) {}
|
||||
void WindowBackgroundIsTranslucent(struct Application* app) {}
|
||||
void OpenDialog(struct Application* app, char *callbackID, char *title, char *filters, char *defaultFilename, char *defaultDir, int allowFiles, int allowDirs, int allowMultiple, int showHiddenFiles, int canCreateDirectories, int resolvesAliases, int treatPackagesAsDirectories) {}
|
||||
void SaveDialog(struct Application* app, char *callbackID, char *title, char *filters, char *defaultFilename, char *defaultDir, int showHiddenFiles, int canCreateDirectories, int treatPackagesAsDirectories) {}
|
||||
void MessageDialog(struct Application* app, char *callbackID, char *type, char *title, char *message, char *icon, char *button1, char *button2, char *button3, char *button4, char *defaultButton, char *cancelButton) {}
|
||||
|
||||
|
||||
// minimiseInternal minimises the main window
|
||||
void minimiseInternal(struct Application *app) {
|
||||
gtk_window_iconify(app->mainWindow);
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
package ffenestri
|
||||
|
||||
/*
|
||||
#cgo linux CFLAGS: -DFFENESTRI_LINUX=1
|
||||
#cgo linux pkg-config: gtk+-3.0 webkit2gtk-4.0
|
||||
|
||||
|
||||
#include "ffenestri.h"
|
||||
#include "ffenestri_linux.h"
|
||||
|
||||
*/
|
||||
import "C"
|
||||
|
||||
func (a *Application) processPlatformSettings() error {
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
|
||||
#ifndef FFENESTRI_LINUX_H
|
||||
#define FFENESTRI_LINUX_H
|
||||
|
||||
|
||||
#endif
|
||||
@@ -1,622 +0,0 @@
|
||||
// Some code may be inspired by or directly used from Webview (c) zserge.
|
||||
// License included in README.md
|
||||
|
||||
#include "ffenestri_windows.h"
|
||||
#include "wv2ComHandler_windows.h"
|
||||
#include <functional>
|
||||
#include <atomic>
|
||||
#include <Shlwapi.h>
|
||||
#include <locale>
|
||||
#include <codecvt>
|
||||
#include "windows/WebView2.h"
|
||||
#include "effectstructs_windows.h"
|
||||
#include <Shlobj.h>
|
||||
|
||||
int debug = 0;
|
||||
DWORD mainThread;
|
||||
|
||||
// --- Assets
|
||||
extern const unsigned char runtime;
|
||||
extern const unsigned char *defaultDialogIcons[];
|
||||
|
||||
// dispatch will execute the given `func` pointer
|
||||
void dispatch(dispatchFunction func) {
|
||||
PostThreadMessage(mainThread, WM_APP, 0, (LPARAM) new dispatchFunction(func));
|
||||
}
|
||||
|
||||
LPWSTR cstrToLPWSTR(const char *cstr) {
|
||||
int wchars_num = MultiByteToWideChar( CP_UTF8 , 0 , cstr , -1, NULL , 0 );
|
||||
wchar_t* wstr = new wchar_t[wchars_num+1];
|
||||
MultiByteToWideChar( CP_UTF8 , 0 , cstr , -1, wstr , wchars_num );
|
||||
return wstr;
|
||||
}
|
||||
|
||||
// Credit: https://stackoverflow.com/a/9842450
|
||||
char* LPWSTRToCstr(LPWSTR input) {
|
||||
int length = WideCharToMultiByte(CP_UTF8, 0, input, -1, 0, 0, NULL, NULL);
|
||||
char* output = new char[length];
|
||||
WideCharToMultiByte(CP_UTF8, 0, input, -1, output , length, NULL, NULL);
|
||||
return output;
|
||||
}
|
||||
|
||||
struct Application *NewApplication(const char *title, int width, int height, int resizable, int devtools, int fullscreen, int startHidden, int logLevel, int hideWindowOnClose) {
|
||||
|
||||
// Create application
|
||||
struct Application *result = (struct Application*)malloc(sizeof(struct Application));
|
||||
|
||||
result->window = nullptr;
|
||||
result->webview = nullptr;
|
||||
result->webviewController = nullptr;
|
||||
|
||||
result->title = title;
|
||||
result->width = width;
|
||||
result->height = height;
|
||||
result->resizable = resizable;
|
||||
result->devtools = devtools;
|
||||
result->fullscreen = fullscreen;
|
||||
result->startHidden = startHidden;
|
||||
result->logLevel = logLevel;
|
||||
result->hideWindowOnClose = hideWindowOnClose;
|
||||
result->webviewIsTranparent = false;
|
||||
result->windowBackgroundIsTranslucent = false;
|
||||
|
||||
// Min/Max Width/Height
|
||||
result->minWidth = 0;
|
||||
result->minHeight = 0;
|
||||
result->maxWidth = 0;
|
||||
result->maxHeight = 0;
|
||||
|
||||
// Default colour
|
||||
result->backgroundColour.R = 255;
|
||||
result->backgroundColour.G = 255;
|
||||
result->backgroundColour.B = 255;
|
||||
result->backgroundColour.A = 255;
|
||||
|
||||
// Have a frame by default
|
||||
result->frame = 1;
|
||||
|
||||
// Capture Main Thread
|
||||
mainThread = GetCurrentThreadId();
|
||||
|
||||
// Startup url
|
||||
result->startupURL = nullptr;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void SetMinWindowSize(struct Application* app, int minWidth, int minHeight) {
|
||||
app->minWidth = (LONG)minWidth;
|
||||
app->minHeight = (LONG)minHeight;
|
||||
}
|
||||
|
||||
void SetMaxWindowSize(struct Application* app, int maxWidth, int maxHeight) {
|
||||
app->maxWidth = (LONG)maxWidth;
|
||||
app->maxHeight = (LONG)maxHeight;
|
||||
}
|
||||
|
||||
void SetBindings(struct Application *app, const char *bindings) {
|
||||
std::string temp = std::string("window.wailsbindings = \"") + std::string(bindings) + std::string("\";");
|
||||
app->bindings = new char[temp.length()+1];
|
||||
memcpy(app->bindings, temp.c_str(), temp.length()+1);
|
||||
}
|
||||
|
||||
void performShutdown(struct Application *app) {
|
||||
if( app->startupURL != nullptr ) {
|
||||
delete[] app->startupURL;
|
||||
}
|
||||
messageFromWindowCallback("WC");
|
||||
}
|
||||
|
||||
void enableTranslucentBackground(struct Application *app) {
|
||||
HMODULE hUser = GetModuleHandleA("user32.dll");
|
||||
if (hUser)
|
||||
{
|
||||
pfnSetWindowCompositionAttribute setWindowCompositionAttribute = (pfnSetWindowCompositionAttribute)GetProcAddress(hUser, "SetWindowCompositionAttribute");
|
||||
if (setWindowCompositionAttribute)
|
||||
{
|
||||
ACCENT_POLICY accent = { ACCENT_ENABLE_BLURBEHIND, 0, 0, 0 };
|
||||
WINDOWCOMPOSITIONATTRIBDATA data;
|
||||
data.Attrib = WCA_ACCENT_POLICY;
|
||||
data.pvData = &accent;
|
||||
data.cbData = sizeof(accent);
|
||||
setWindowCompositionAttribute(app->window, &data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
|
||||
|
||||
struct Application *app = (struct Application *)GetWindowLongPtr(hwnd, GWLP_USERDATA);
|
||||
|
||||
switch(msg) {
|
||||
|
||||
case WM_DESTROY: {
|
||||
DestroyApplication(app);
|
||||
break;
|
||||
}
|
||||
case WM_SIZE: {
|
||||
if( app->webviewController != nullptr) {
|
||||
RECT bounds;
|
||||
GetClientRect(app->window, &bounds);
|
||||
app->webviewController->put_Bounds(bounds);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case WM_GETMINMAXINFO: {
|
||||
// Exit early if this is called before the window is created.
|
||||
if ( app == NULL ) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// get pixel density
|
||||
HDC hDC = GetDC(NULL);
|
||||
double DPIScaleX = GetDeviceCaps(hDC, 88)/96.0;
|
||||
double DPIScaleY = GetDeviceCaps(hDC, 90)/96.0;
|
||||
ReleaseDC(NULL, hDC);
|
||||
|
||||
RECT rcClient, rcWind;
|
||||
POINT ptDiff;
|
||||
GetClientRect(hwnd, &rcClient);
|
||||
GetWindowRect(hwnd, &rcWind);
|
||||
|
||||
int widthExtra = (rcWind.right - rcWind.left) - rcClient.right;
|
||||
int heightExtra = (rcWind.bottom - rcWind.top) - rcClient.bottom;
|
||||
|
||||
LPMINMAXINFO mmi = (LPMINMAXINFO) lParam;
|
||||
if (app->minWidth > 0 && app->minHeight > 0) {
|
||||
mmi->ptMinTrackSize.x = app->minWidth * DPIScaleX + widthExtra;
|
||||
mmi->ptMinTrackSize.y = app->minHeight * DPIScaleY + heightExtra;
|
||||
}
|
||||
if (app->maxWidth > 0 && app->maxHeight > 0) {
|
||||
mmi->ptMaxSize.x = app->maxWidth * DPIScaleX + widthExtra;
|
||||
mmi->ptMaxSize.y = app->maxHeight * DPIScaleY + heightExtra;
|
||||
mmi->ptMaxTrackSize.x = app->maxWidth * DPIScaleX + widthExtra;
|
||||
mmi->ptMaxTrackSize.y = app->maxHeight * DPIScaleY + heightExtra;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
default:
|
||||
return DefWindowProc(hwnd, msg, wParam, lParam);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void init(struct Application *app, const char* js) {
|
||||
LPCWSTR wjs = cstrToLPWSTR(js);
|
||||
app->webview->AddScriptToExecuteOnDocumentCreated(wjs, nullptr);
|
||||
delete[] wjs;
|
||||
}
|
||||
|
||||
void execJS(struct Application* app, const char *script) {
|
||||
LPWSTR s = cstrToLPWSTR(script);
|
||||
app->webview->ExecuteScript(s, nullptr);
|
||||
delete[] s;
|
||||
}
|
||||
|
||||
void loadAssets(struct Application* app) {
|
||||
|
||||
// setup window.wailsInvoke
|
||||
std::string initialCode = std::string("window.wailsInvoke=function(m){window.chrome.webview.postMessage(m)};");
|
||||
|
||||
// Load bindings
|
||||
initialCode += std::string(app->bindings);
|
||||
delete[] app->bindings;
|
||||
|
||||
// Load runtime
|
||||
initialCode += std::string((const char*)&runtime);
|
||||
|
||||
int index = 1;
|
||||
while(1) {
|
||||
// Get next asset pointer
|
||||
const unsigned char *asset = assets[index];
|
||||
|
||||
// If we have no more assets, break
|
||||
if (asset == 0x00) {
|
||||
break;
|
||||
}
|
||||
|
||||
initialCode += std::string((const char*)asset);
|
||||
index++;
|
||||
};
|
||||
|
||||
// Disable context menu if not in debug mode
|
||||
if( debug != 1 ) {
|
||||
initialCode += std::string("wails._.DisableDefaultContextMenu();");
|
||||
}
|
||||
|
||||
initialCode += std::string("window.wailsInvoke('completed');");
|
||||
|
||||
// Keep a copy of the code
|
||||
app->initialCode = new char[initialCode.length()+1];
|
||||
memcpy(app->initialCode, initialCode.c_str(), initialCode.length()+1);
|
||||
|
||||
execJS(app, app->initialCode);
|
||||
|
||||
// Show app if we need to
|
||||
if( app->startHidden == false ) {
|
||||
Show(app);
|
||||
}
|
||||
}
|
||||
|
||||
// This is called when all our assets are loaded into the DOM
|
||||
void completed(struct Application* app) {
|
||||
delete[] app->initialCode;
|
||||
app->initialCode = nullptr;
|
||||
|
||||
if( app->startupURL == nullptr ) {
|
||||
messageFromWindowCallback("SS");
|
||||
return;
|
||||
}
|
||||
std::string readyMessage = std::string("SS") + std::string(app->startupURL);
|
||||
messageFromWindowCallback(readyMessage.c_str());
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
bool initWebView2(struct Application *app, int debugEnabled, messageCallback cb) {
|
||||
|
||||
debug = debugEnabled;
|
||||
|
||||
CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);
|
||||
|
||||
std::atomic_flag flag = ATOMIC_FLAG_INIT;
|
||||
flag.test_and_set();
|
||||
|
||||
// char currentExePath[MAX_PATH];
|
||||
// GetModuleFileNameA(NULL, currentExePath, MAX_PATH);
|
||||
// char *currentExeName = PathFindFileNameA(currentExePath);
|
||||
// std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>> wideCharConverter;
|
||||
// auto exeName = wideCharConverter.from_bytes(currentExeName);
|
||||
//
|
||||
// PWSTR path;
|
||||
// HRESULT appDataResult = SHGetFolderPathAndSubDir(app->window, CSIDL_LOCAL_APPDATA, nullptr, SHGFP_TYPE_CURRENT, exeName.c_str(), path);
|
||||
// if ( appDataResult == false ) {
|
||||
// path = nullptr;
|
||||
// }
|
||||
//
|
||||
|
||||
ICoreWebView2Controller *controller;
|
||||
ICoreWebView2* webview;
|
||||
|
||||
HRESULT res = CreateCoreWebView2EnvironmentWithOptions(
|
||||
nullptr, nullptr, nullptr,
|
||||
new wv2ComHandler(app, app->window, cb,
|
||||
[&](ICoreWebView2Controller *webviewController) {
|
||||
controller = webviewController;
|
||||
controller->get_CoreWebView2(&webview);
|
||||
webview->AddRef();
|
||||
flag.clear();
|
||||
}));
|
||||
if (res != S_OK) {
|
||||
CoUninitialize();
|
||||
return false;
|
||||
}
|
||||
|
||||
MSG msg = {};
|
||||
while (flag.test_and_set() && GetMessage(&msg, NULL, 0, 0)) {
|
||||
TranslateMessage(&msg);
|
||||
DispatchMessage(&msg);
|
||||
}
|
||||
app->webviewController = controller;
|
||||
app->webview = webview;
|
||||
// Resize WebView to fit the bounds of the parent window
|
||||
RECT bounds;
|
||||
GetClientRect(app->window, &bounds);
|
||||
app->webviewController->put_Bounds(bounds);
|
||||
|
||||
// Let the backend know we have initialised
|
||||
app->webview->AddScriptToExecuteOnDocumentCreated(L"window.chrome.webview.postMessage('initialised');", nullptr);
|
||||
// Load the HTML
|
||||
LPCWSTR html = (LPCWSTR) cstrToLPWSTR((char*)assets[0]);
|
||||
app->webview->Navigate(html);
|
||||
|
||||
messageFromWindowCallback("Ej{\"name\":\"wails:launched\",\"data\":[]}");
|
||||
return true;
|
||||
}
|
||||
|
||||
void initialCallback(std::string message) {
|
||||
printf("MESSAGE=%s\n", message);
|
||||
}
|
||||
|
||||
void Run(struct Application* app, int argc, char **argv) {
|
||||
|
||||
WNDCLASSEX wc;
|
||||
HINSTANCE hInstance = GetModuleHandle(NULL);
|
||||
ZeroMemory(&wc, sizeof(WNDCLASSEX));
|
||||
wc.cbSize = sizeof(WNDCLASSEX);
|
||||
wc.style = CS_HREDRAW | CS_VREDRAW;
|
||||
wc.hInstance = hInstance;
|
||||
wc.lpszClassName = (LPCWSTR)"ffenestri";
|
||||
wc.lpfnWndProc = WndProc;
|
||||
wc.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(100));
|
||||
wc.hIconSm = LoadIcon(hInstance, MAKEINTRESOURCE(100));
|
||||
|
||||
// TODO: Menu
|
||||
// wc.lpszMenuName = nullptr;
|
||||
|
||||
|
||||
// Process window resizable
|
||||
DWORD windowStyle = WS_OVERLAPPEDWINDOW;
|
||||
if (app->resizable == 0) {
|
||||
windowStyle &= ~WS_MAXIMIZEBOX;
|
||||
windowStyle &= ~WS_THICKFRAME;
|
||||
}
|
||||
if ( app->frame == 0 ) {
|
||||
windowStyle = WS_POPUP;
|
||||
}
|
||||
|
||||
RegisterClassEx(&wc);
|
||||
app->window = CreateWindow((LPCWSTR)"ffenestri", (LPCWSTR)"", windowStyle, CW_USEDEFAULT,
|
||||
CW_USEDEFAULT, app->width, app->height, NULL, NULL,
|
||||
hInstance, NULL);
|
||||
|
||||
// Private setTitle as we're on the main thread
|
||||
setTitle(app, app->title);
|
||||
|
||||
// Store application pointer in window handle
|
||||
SetWindowLongPtr(app->window, GWLP_USERDATA, (LONG_PTR)app);
|
||||
|
||||
// Process whether window should show by default
|
||||
int startVisibility = SW_SHOWNORMAL;
|
||||
if ( app->startHidden == 1 ) {
|
||||
startVisibility = SW_HIDE;
|
||||
}
|
||||
|
||||
// TODO: Make configurable
|
||||
// COREWEBVIEW2_COLOR wvColor;
|
||||
// wvColor.A = 255;
|
||||
// std::weak_ptr<ICoreWebView2Controller2> controller2 = app->webviewController->query<ICoreWebView2Controller2>();
|
||||
// controller2->put_DefaultBackgroundColor(wvColor);
|
||||
|
||||
if( app->windowBackgroundIsTranslucent ) {
|
||||
enableTranslucentBackground(app);
|
||||
}
|
||||
|
||||
// private center() as we are on main thread
|
||||
center(app);
|
||||
ShowWindow(app->window, startVisibility);
|
||||
UpdateWindow(app->window);
|
||||
SetFocus(app->window);
|
||||
|
||||
// Add webview2
|
||||
initWebView2(app, 1, initialCallback);
|
||||
|
||||
// Main event loop
|
||||
MSG msg;
|
||||
BOOL res;
|
||||
while ((res = GetMessage(&msg, NULL, 0, 0)) != -1) {
|
||||
if (msg.hwnd) {
|
||||
TranslateMessage(&msg);
|
||||
DispatchMessage(&msg);
|
||||
continue;
|
||||
}
|
||||
if (msg.message == WM_APP) {
|
||||
dispatchFunction *f = (dispatchFunction*) msg.lParam;
|
||||
(*f)();
|
||||
delete(f);
|
||||
} else if (msg.message == WM_QUIT) {
|
||||
performShutdown(app);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DestroyApplication(struct Application* app) {
|
||||
PostQuitMessage(0);
|
||||
}
|
||||
void SetDebug(struct Application* app, int flag) {
|
||||
debug = flag;
|
||||
}
|
||||
|
||||
void ExecJS(struct Application* app, const char *script) {
|
||||
ON_MAIN_THREAD(
|
||||
execJS(app, script);
|
||||
);
|
||||
}
|
||||
|
||||
void hide(struct Application* app) {
|
||||
ShowWindow(app->window, SW_HIDE);
|
||||
}
|
||||
|
||||
void Hide(struct Application* app) {
|
||||
ON_MAIN_THREAD(
|
||||
hide(app);
|
||||
);
|
||||
}
|
||||
|
||||
void show(struct Application* app) {
|
||||
ShowWindow(app->window, SW_SHOW);
|
||||
}
|
||||
|
||||
void Show(struct Application* app) {
|
||||
ON_MAIN_THREAD(
|
||||
show(app);
|
||||
);
|
||||
}
|
||||
|
||||
void center(struct Application* app) {
|
||||
|
||||
HMONITOR currentMonitor = MonitorFromWindow(app->window, MONITOR_DEFAULTTONEAREST);
|
||||
MONITORINFO info = {0};
|
||||
info.cbSize = sizeof(info);
|
||||
GetMonitorInfoA(currentMonitor, &info);
|
||||
RECT workRect = info.rcWork;
|
||||
LONG screenMiddleW = (workRect.right - workRect.left) / 2;
|
||||
LONG screenMiddleH = (workRect.bottom - workRect.top) / 2;
|
||||
RECT winRect;
|
||||
if (app->frame == 1) {
|
||||
GetWindowRect(app->window, &winRect);
|
||||
} else {
|
||||
GetClientRect(app->window, &winRect);
|
||||
}
|
||||
LONG winWidth = winRect.right - winRect.left;
|
||||
LONG winHeight = winRect.bottom - winRect.top;
|
||||
|
||||
LONG windowX = screenMiddleW - (winWidth / 2);
|
||||
LONG windowY = screenMiddleH - (winHeight / 2);
|
||||
|
||||
SetWindowPos(app->window, HWND_TOP, windowX, windowY, winWidth, winHeight, SWP_NOSIZE);
|
||||
}
|
||||
|
||||
void Center(struct Application* app) {
|
||||
ON_MAIN_THREAD(
|
||||
center(app);
|
||||
);
|
||||
}
|
||||
|
||||
UINT getWindowPlacement(struct Application* app) {
|
||||
WINDOWPLACEMENT lpwndpl;
|
||||
lpwndpl.length = sizeof(WINDOWPLACEMENT);
|
||||
BOOL result = GetWindowPlacement(app->window, &lpwndpl);
|
||||
if( result == 0 ) {
|
||||
// TODO: Work out what this call failing means
|
||||
return -1;
|
||||
}
|
||||
return lpwndpl.showCmd;
|
||||
}
|
||||
|
||||
int isMaximised(struct Application* app) {
|
||||
return getWindowPlacement(app) == SW_SHOWMAXIMIZED;
|
||||
}
|
||||
|
||||
void maximise(struct Application* app) {
|
||||
ShowWindow(app->window, SW_MAXIMIZE);
|
||||
}
|
||||
|
||||
void Maximise(struct Application* app) {
|
||||
ON_MAIN_THREAD(
|
||||
maximise(app);
|
||||
);
|
||||
}
|
||||
|
||||
void unmaximise(struct Application* app) {
|
||||
ShowWindow(app->window, SW_RESTORE);
|
||||
}
|
||||
|
||||
void Unmaximise(struct Application* app) {
|
||||
ON_MAIN_THREAD(
|
||||
unmaximise(app);
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
void ToggleMaximise(struct Application* app) {
|
||||
if(isMaximised(app)) {
|
||||
return Unmaximise(app);
|
||||
}
|
||||
return Maximise(app);
|
||||
}
|
||||
|
||||
int isMinimised(struct Application* app) {
|
||||
return getWindowPlacement(app) == SW_SHOWMINIMIZED;
|
||||
}
|
||||
|
||||
void minimise(struct Application* app) {
|
||||
ShowWindow(app->window, SW_MINIMIZE);
|
||||
}
|
||||
|
||||
void Minimise(struct Application* app) {
|
||||
ON_MAIN_THREAD(
|
||||
minimise(app);
|
||||
);
|
||||
}
|
||||
|
||||
void unminimise(struct Application* app) {
|
||||
ShowWindow(app->window, SW_RESTORE);
|
||||
}
|
||||
|
||||
void Unminimise(struct Application* app) {
|
||||
ON_MAIN_THREAD(
|
||||
unminimise(app);
|
||||
);
|
||||
}
|
||||
|
||||
void ToggleMinimise(struct Application* app) {
|
||||
if(isMinimised(app)) {
|
||||
return Unminimise(app);
|
||||
}
|
||||
return Minimise(app);
|
||||
}
|
||||
|
||||
void SetColour(struct Application* app, int red, int green, int blue, int alpha) {
|
||||
// TBD
|
||||
}
|
||||
|
||||
void SetSize(struct Application* app, int width, int height) {
|
||||
// TBD
|
||||
}
|
||||
|
||||
void setPosition(struct Application* app, int x, int y) {
|
||||
// TBD
|
||||
}
|
||||
|
||||
void SetPosition(struct Application* app, int x, int y) {
|
||||
// ON_MAIN_THREAD(
|
||||
// setPosition(app, x, y);
|
||||
// );
|
||||
}
|
||||
|
||||
void Quit(struct Application* app) {
|
||||
DestroyWindow(app->window);
|
||||
}
|
||||
|
||||
|
||||
// Credit: https://stackoverflow.com/a/6693107
|
||||
void setTitle(struct Application* app, const char *title) {
|
||||
LPCTSTR text = cstrToLPWSTR(title);
|
||||
SetWindowText(app->window, text);
|
||||
delete[] text;
|
||||
}
|
||||
|
||||
void SetTitle(struct Application* app, const char *title) {
|
||||
ON_MAIN_THREAD(
|
||||
setTitle(app, title);
|
||||
);
|
||||
}
|
||||
|
||||
void Fullscreen(struct Application* app) {
|
||||
}
|
||||
|
||||
void UnFullscreen(struct Application* app) {
|
||||
}
|
||||
|
||||
void ToggleFullscreen(struct Application* app) {
|
||||
}
|
||||
|
||||
void DisableFrame(struct Application* app) {
|
||||
app->frame = 0;
|
||||
}
|
||||
|
||||
// WebviewIsTransparent will make the webview transparent
|
||||
// revealing the window underneath
|
||||
void WebviewIsTransparent(struct Application *app) {
|
||||
app->webviewIsTranparent = true;
|
||||
}
|
||||
|
||||
void WindowBackgroundIsTranslucent(struct Application *app) {
|
||||
app->windowBackgroundIsTranslucent = true;
|
||||
}
|
||||
|
||||
|
||||
void OpenDialog(struct Application* app, char *callbackID, char *title, char *filters, char *defaultFilename, char *defaultDir, int allowFiles, int allowDirs, int allowMultiple, int showHiddenFiles, int canCreateDirectories, int resolvesAliases, int treatPackagesAsDirectories) {
|
||||
}
|
||||
void SaveDialog(struct Application* app, char *callbackID, char *title, char *filters, char *defaultFilename, char *defaultDir, int showHiddenFiles, int canCreateDirectories, int treatPackagesAsDirectories) {
|
||||
}
|
||||
void MessageDialog(struct Application* app, char *callbackID, char *type, char *title, char *message, char *icon, char *button1, char *button2, char *button3, char *button4, char *defaultButton, char *cancelButton) {
|
||||
}
|
||||
void DarkModeEnabled(struct Application* app, char *callbackID) {
|
||||
}
|
||||
void SetApplicationMenu(struct Application* app, const char *applicationMenuJSON) {
|
||||
}
|
||||
void AddTrayMenu(struct Application* app, const char *menuTrayJSON) {
|
||||
}
|
||||
void SetTrayMenu(struct Application* app, const char *menuTrayJSON) {
|
||||
}
|
||||
void DeleteTrayMenuByID(struct Application* app, const char *id) {
|
||||
}
|
||||
void UpdateTrayMenuLabel(struct Application* app, const char* JSON) {
|
||||
}
|
||||
void AddContextMenu(struct Application* app, char *contextMenuJSON) {
|
||||
}
|
||||
void UpdateContextMenu(struct Application* app, char *contextMenuJSON) {
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
package ffenestri
|
||||
|
||||
import "C"
|
||||
|
||||
/*
|
||||
|
||||
#cgo windows CXXFLAGS: -std=c++11
|
||||
#cgo windows,amd64 LDFLAGS: -L./windows/x64 -lwebview -lWebView2Loader -lgdi32 -lole32 -lShlwapi -luser32 -loleaut32
|
||||
|
||||
#include "ffenestri.h"
|
||||
|
||||
*/
|
||||
import "C"
|
||||
|
||||
func (a *Application) processPlatformSettings() error {
|
||||
|
||||
config := a.config.Windows
|
||||
if config == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Check if the webview should be transparent
|
||||
if config.WebviewIsTransparent {
|
||||
C.WebviewIsTransparent(a.app)
|
||||
}
|
||||
|
||||
if config.WindowBackgroundIsTranslucent {
|
||||
C.WindowBackgroundIsTranslucent(a.app)
|
||||
}
|
||||
|
||||
//// Process menu
|
||||
////applicationMenu := options.GetApplicationMenu(a.config)
|
||||
//applicationMenu := a.menuManager.GetApplicationMenuJSON()
|
||||
//if applicationMenu != "" {
|
||||
// C.SetApplicationMenu(a.app, a.string2CString(applicationMenu))
|
||||
//}
|
||||
//
|
||||
//// Process tray
|
||||
//trays, err := a.menuManager.GetTrayMenus()
|
||||
//if err != nil {
|
||||
// return err
|
||||
//}
|
||||
//if trays != nil {
|
||||
// for _, tray := range trays {
|
||||
// C.AddTrayMenu(a.app, a.string2CString(tray))
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//// Process context menus
|
||||
//contextMenus, err := a.menuManager.GetContextMenus()
|
||||
//if err != nil {
|
||||
// return err
|
||||
//}
|
||||
//if contextMenus != nil {
|
||||
// for _, contextMenu := range contextMenus {
|
||||
// C.AddContextMenu(a.app, a.string2CString(contextMenu))
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//// Process URL Handlers
|
||||
//if a.config.Mac.URLHandlers != nil {
|
||||
// C.HasURLHandlers(a.app)
|
||||
//}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,75 +0,0 @@
|
||||
|
||||
#ifndef _FFENESTRI_WINDOWS_H
|
||||
#define _FFENESTRI_WINDOWS_H
|
||||
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#define UNICODE 1
|
||||
|
||||
#include "ffenestri.h"
|
||||
#include <windows.h>
|
||||
#include <wingdi.h>
|
||||
#include <functional>
|
||||
#include <codecvt>
|
||||
#include "windows/WebView2.h"
|
||||
|
||||
#include "assets.h"
|
||||
|
||||
// TODO:
|
||||
//#include "userdialogicons.h"
|
||||
|
||||
|
||||
struct Application{
|
||||
// Window specific
|
||||
HWND window;
|
||||
ICoreWebView2 *webview;
|
||||
ICoreWebView2Controller* webviewController;
|
||||
|
||||
// Application
|
||||
const char *title;
|
||||
int width;
|
||||
int height;
|
||||
int resizable;
|
||||
int devtools;
|
||||
int fullscreen;
|
||||
int startHidden;
|
||||
int logLevel;
|
||||
int hideWindowOnClose;
|
||||
int minSizeSet;
|
||||
LONG minWidth;
|
||||
LONG minHeight;
|
||||
int maxSizeSet;
|
||||
LONG maxWidth;
|
||||
LONG maxHeight;
|
||||
int frame;
|
||||
char *startupURL;
|
||||
bool webviewIsTranparent;
|
||||
bool windowBackgroundIsTranslucent;
|
||||
COREWEBVIEW2_COLOR backgroundColour;
|
||||
|
||||
// placeholders
|
||||
char* bindings;
|
||||
char* initialCode;
|
||||
};
|
||||
|
||||
#define ON_MAIN_THREAD(code) dispatch( [=]{ code; } )
|
||||
|
||||
typedef std::function<void()> dispatchFunction;
|
||||
typedef std::function<void(const std::string)> messageCallback;
|
||||
typedef std::function<void(ICoreWebView2Controller *)> comHandlerCallback;
|
||||
|
||||
void center(struct Application*);
|
||||
void setTitle(struct Application* app, const char *title);
|
||||
char* LPWSTRToCstr(LPWSTR input);
|
||||
|
||||
// called when the DOM is ready
|
||||
void loadAssets(struct Application* app);
|
||||
|
||||
// called when the application assets have been loaded into the DOM
|
||||
void completed(struct Application* app);
|
||||
|
||||
// Callback
|
||||
extern "C" {
|
||||
void messageFromWindowCallback(const char *);
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -1,5 +1,3 @@
|
||||
// +build !windows
|
||||
|
||||
/*
|
||||
Copyright (C) 2011 Joseph A. Adams (joeyadams3.14159@gmail.com)
|
||||
All rights reserved.
|
||||
|
||||
@@ -90,7 +90,7 @@ void DeleteMenu(Menu *menu) {
|
||||
|
||||
// Free nsmenu if we have it
|
||||
if ( menu->menu != NULL ) {
|
||||
msg_reg(menu->menu, s("release"));
|
||||
msg(menu->menu, s("release"));
|
||||
}
|
||||
|
||||
free(menu);
|
||||
@@ -120,17 +120,17 @@ const char* createMenuClickedMessage(const char *menuItemID, const char *data, e
|
||||
|
||||
// Callback for text menu items
|
||||
void menuItemCallback(id self, SEL cmd, id sender) {
|
||||
MenuItemCallbackData *callbackData = (MenuItemCallbackData *)msg_reg(msg_reg(sender, s("representedObject")), s("pointerValue"));
|
||||
MenuItemCallbackData *callbackData = (MenuItemCallbackData *)msg(msg(sender, s("representedObject")), s("pointerValue"));
|
||||
const char *message;
|
||||
|
||||
// Update checkbox / radio item
|
||||
if( callbackData->menuItemType == Checkbox) {
|
||||
// Toggle state
|
||||
bool state = msg_reg(callbackData->menuItem, s("state"));
|
||||
msg_int(callbackData->menuItem, s("setState:"), (state? NSControlStateValueOff : NSControlStateValueOn));
|
||||
bool state = msg(callbackData->menuItem, s("state"));
|
||||
msg(callbackData->menuItem, s("setState:"), (state? NSControlStateValueOff : NSControlStateValueOn));
|
||||
} else if( callbackData->menuItemType == Radio ) {
|
||||
// Check the menu items' current state
|
||||
bool selected = (bool)msg_reg(callbackData->menuItem, s("state"));
|
||||
bool selected = msg(callbackData->menuItem, s("state"));
|
||||
|
||||
// If it's already selected, exit early
|
||||
if (selected) return;
|
||||
@@ -142,13 +142,13 @@ void menuItemCallback(id self, SEL cmd, id sender) {
|
||||
id thisMember = members[0];
|
||||
int count = 0;
|
||||
while(thisMember != NULL) {
|
||||
msg_int(thisMember, s("setState:"), NSControlStateValueOff);
|
||||
msg(thisMember, s("setState:"), NSControlStateValueOff);
|
||||
count = count + 1;
|
||||
thisMember = members[count];
|
||||
}
|
||||
|
||||
// check the selected menu item
|
||||
msg_int(callbackData->menuItem, s("setState:"), NSControlStateValueOn);
|
||||
msg(callbackData->menuItem, s("setState:"), NSControlStateValueOn);
|
||||
}
|
||||
|
||||
const char *menuID = callbackData->menuID;
|
||||
@@ -180,154 +180,151 @@ id processAcceleratorKey(const char *key) {
|
||||
return str("");
|
||||
}
|
||||
|
||||
if( STREQ(key, "backspace") ) {
|
||||
if( STREQ(key, "Backspace") ) {
|
||||
return strunicode(0x0008);
|
||||
}
|
||||
if( STREQ(key, "tab") ) {
|
||||
if( STREQ(key, "Tab") ) {
|
||||
return strunicode(0x0009);
|
||||
}
|
||||
if( STREQ(key, "return") ) {
|
||||
if( STREQ(key, "Return") ) {
|
||||
return strunicode(0x000d);
|
||||
}
|
||||
if( STREQ(key, "enter") ) {
|
||||
return strunicode(0x000d);
|
||||
}
|
||||
if( STREQ(key, "escape") ) {
|
||||
if( STREQ(key, "Escape") ) {
|
||||
return strunicode(0x001b);
|
||||
}
|
||||
if( STREQ(key, "left") ) {
|
||||
if( STREQ(key, "Left") ) {
|
||||
return strunicode(0x001c);
|
||||
}
|
||||
if( STREQ(key, "right") ) {
|
||||
if( STREQ(key, "Right") ) {
|
||||
return strunicode(0x001d);
|
||||
}
|
||||
if( STREQ(key, "up") ) {
|
||||
if( STREQ(key, "Up") ) {
|
||||
return strunicode(0x001e);
|
||||
}
|
||||
if( STREQ(key, "down") ) {
|
||||
if( STREQ(key, "Down") ) {
|
||||
return strunicode(0x001f);
|
||||
}
|
||||
if( STREQ(key, "space") ) {
|
||||
if( STREQ(key, "Space") ) {
|
||||
return strunicode(0x0020);
|
||||
}
|
||||
if( STREQ(key, "delete") ) {
|
||||
if( STREQ(key, "Delete") ) {
|
||||
return strunicode(0x007f);
|
||||
}
|
||||
if( STREQ(key, "home") ) {
|
||||
if( STREQ(key, "Home") ) {
|
||||
return strunicode(0x2196);
|
||||
}
|
||||
if( STREQ(key, "end") ) {
|
||||
if( STREQ(key, "End") ) {
|
||||
return strunicode(0x2198);
|
||||
}
|
||||
if( STREQ(key, "page up") ) {
|
||||
if( STREQ(key, "Page Up") ) {
|
||||
return strunicode(0x21de);
|
||||
}
|
||||
if( STREQ(key, "page down") ) {
|
||||
if( STREQ(key, "Page Down") ) {
|
||||
return strunicode(0x21df);
|
||||
}
|
||||
if( STREQ(key, "f1") ) {
|
||||
if( STREQ(key, "F1") ) {
|
||||
return strunicode(0xf704);
|
||||
}
|
||||
if( STREQ(key, "f2") ) {
|
||||
if( STREQ(key, "F2") ) {
|
||||
return strunicode(0xf705);
|
||||
}
|
||||
if( STREQ(key, "f3") ) {
|
||||
if( STREQ(key, "F3") ) {
|
||||
return strunicode(0xf706);
|
||||
}
|
||||
if( STREQ(key, "f4") ) {
|
||||
if( STREQ(key, "F4") ) {
|
||||
return strunicode(0xf707);
|
||||
}
|
||||
if( STREQ(key, "f5") ) {
|
||||
if( STREQ(key, "F5") ) {
|
||||
return strunicode(0xf708);
|
||||
}
|
||||
if( STREQ(key, "f6") ) {
|
||||
if( STREQ(key, "F6") ) {
|
||||
return strunicode(0xf709);
|
||||
}
|
||||
if( STREQ(key, "f7") ) {
|
||||
if( STREQ(key, "F7") ) {
|
||||
return strunicode(0xf70a);
|
||||
}
|
||||
if( STREQ(key, "f8") ) {
|
||||
if( STREQ(key, "F8") ) {
|
||||
return strunicode(0xf70b);
|
||||
}
|
||||
if( STREQ(key, "f9") ) {
|
||||
if( STREQ(key, "F9") ) {
|
||||
return strunicode(0xf70c);
|
||||
}
|
||||
if( STREQ(key, "f10") ) {
|
||||
if( STREQ(key, "F10") ) {
|
||||
return strunicode(0xf70d);
|
||||
}
|
||||
if( STREQ(key, "f11") ) {
|
||||
if( STREQ(key, "F11") ) {
|
||||
return strunicode(0xf70e);
|
||||
}
|
||||
if( STREQ(key, "f12") ) {
|
||||
if( STREQ(key, "F12") ) {
|
||||
return strunicode(0xf70f);
|
||||
}
|
||||
if( STREQ(key, "f13") ) {
|
||||
if( STREQ(key, "F13") ) {
|
||||
return strunicode(0xf710);
|
||||
}
|
||||
if( STREQ(key, "f14") ) {
|
||||
if( STREQ(key, "F14") ) {
|
||||
return strunicode(0xf711);
|
||||
}
|
||||
if( STREQ(key, "f15") ) {
|
||||
if( STREQ(key, "F15") ) {
|
||||
return strunicode(0xf712);
|
||||
}
|
||||
if( STREQ(key, "f16") ) {
|
||||
if( STREQ(key, "F16") ) {
|
||||
return strunicode(0xf713);
|
||||
}
|
||||
if( STREQ(key, "f17") ) {
|
||||
if( STREQ(key, "F17") ) {
|
||||
return strunicode(0xf714);
|
||||
}
|
||||
if( STREQ(key, "f18") ) {
|
||||
if( STREQ(key, "F18") ) {
|
||||
return strunicode(0xf715);
|
||||
}
|
||||
if( STREQ(key, "f19") ) {
|
||||
if( STREQ(key, "F19") ) {
|
||||
return strunicode(0xf716);
|
||||
}
|
||||
if( STREQ(key, "f20") ) {
|
||||
if( STREQ(key, "F20") ) {
|
||||
return strunicode(0xf717);
|
||||
}
|
||||
if( STREQ(key, "f21") ) {
|
||||
if( STREQ(key, "F21") ) {
|
||||
return strunicode(0xf718);
|
||||
}
|
||||
if( STREQ(key, "f22") ) {
|
||||
if( STREQ(key, "F22") ) {
|
||||
return strunicode(0xf719);
|
||||
}
|
||||
if( STREQ(key, "f23") ) {
|
||||
if( STREQ(key, "F23") ) {
|
||||
return strunicode(0xf71a);
|
||||
}
|
||||
if( STREQ(key, "f24") ) {
|
||||
if( STREQ(key, "F24") ) {
|
||||
return strunicode(0xf71b);
|
||||
}
|
||||
if( STREQ(key, "f25") ) {
|
||||
if( STREQ(key, "F25") ) {
|
||||
return strunicode(0xf71c);
|
||||
}
|
||||
if( STREQ(key, "f26") ) {
|
||||
if( STREQ(key, "F26") ) {
|
||||
return strunicode(0xf71d);
|
||||
}
|
||||
if( STREQ(key, "f27") ) {
|
||||
if( STREQ(key, "F27") ) {
|
||||
return strunicode(0xf71e);
|
||||
}
|
||||
if( STREQ(key, "f28") ) {
|
||||
if( STREQ(key, "F28") ) {
|
||||
return strunicode(0xf71f);
|
||||
}
|
||||
if( STREQ(key, "f29") ) {
|
||||
if( STREQ(key, "F29") ) {
|
||||
return strunicode(0xf720);
|
||||
}
|
||||
if( STREQ(key, "f30") ) {
|
||||
if( STREQ(key, "F30") ) {
|
||||
return strunicode(0xf721);
|
||||
}
|
||||
if( STREQ(key, "f31") ) {
|
||||
if( STREQ(key, "F31") ) {
|
||||
return strunicode(0xf722);
|
||||
}
|
||||
if( STREQ(key, "f32") ) {
|
||||
if( STREQ(key, "F32") ) {
|
||||
return strunicode(0xf723);
|
||||
}
|
||||
if( STREQ(key, "f33") ) {
|
||||
if( STREQ(key, "F33") ) {
|
||||
return strunicode(0xf724);
|
||||
}
|
||||
if( STREQ(key, "f34") ) {
|
||||
if( STREQ(key, "F34") ) {
|
||||
return strunicode(0xf725);
|
||||
}
|
||||
if( STREQ(key, "f35") ) {
|
||||
if( STREQ(key, "F35") ) {
|
||||
return strunicode(0xf726);
|
||||
}
|
||||
// if( STREQ(key, "Insert") ) {
|
||||
@@ -339,7 +336,7 @@ id processAcceleratorKey(const char *key) {
|
||||
// if( STREQ(key, "ScrollLock") ) {
|
||||
// return strunicode(0xf72f);
|
||||
// }
|
||||
if( STREQ(key, "numLock") ) {
|
||||
if( STREQ(key, "NumLock") ) {
|
||||
return strunicode(0xf739);
|
||||
}
|
||||
|
||||
@@ -348,61 +345,61 @@ id processAcceleratorKey(const char *key) {
|
||||
|
||||
|
||||
void addSeparator(id menu) {
|
||||
id item = msg_reg(c("NSMenuItem"), s("separatorItem"));
|
||||
msg_id(menu, s("addItem:"), item);
|
||||
id item = msg(c("NSMenuItem"), s("separatorItem"));
|
||||
msg(menu, s("addItem:"), item);
|
||||
}
|
||||
|
||||
id createMenuItemNoAutorelease( id title, const char *action, const char *key) {
|
||||
id item = ALLOC("NSMenuItem");
|
||||
((id(*)(id, SEL, id, SEL, id))objc_msgSend)(item, s("initWithTitle:action:keyEquivalent:"), title, s(action), str(key));
|
||||
msg(item, s("initWithTitle:action:keyEquivalent:"), title, s(action), str(key));
|
||||
return item;
|
||||
}
|
||||
|
||||
id createMenuItem(id title, const char *action, const char *key) {
|
||||
id item = ALLOC("NSMenuItem");
|
||||
((id(*)(id, SEL, id, SEL, id))objc_msgSend)(item, s("initWithTitle:action:keyEquivalent:"), title, s(action), str(key));
|
||||
msg_reg(item, s("autorelease"));
|
||||
msg(item, s("initWithTitle:action:keyEquivalent:"), title, s(action), str(key));
|
||||
msg(item, s("autorelease"));
|
||||
return item;
|
||||
}
|
||||
|
||||
id addMenuItem(id menu, const char *title, const char *action, const char *key, bool disabled) {
|
||||
id item = createMenuItem(str(title), action, key);
|
||||
msg_bool(item, s("setEnabled:"), !disabled);
|
||||
msg_id(menu, s("addItem:"), item);
|
||||
msg(item, s("setEnabled:"), !disabled);
|
||||
msg(menu, s("addItem:"), item);
|
||||
return item;
|
||||
}
|
||||
|
||||
id createMenu(id title) {
|
||||
id menu = ALLOC("NSMenu");
|
||||
msg_id(menu, s("initWithTitle:"), title);
|
||||
msg_bool(menu, s("setAutoenablesItems:"), NO);
|
||||
msg(menu, s("initWithTitle:"), title);
|
||||
msg(menu, s("setAutoenablesItems:"), NO);
|
||||
// msg(menu, s("autorelease"));
|
||||
return menu;
|
||||
}
|
||||
|
||||
void createDefaultAppMenu(id parentMenu) {
|
||||
// App Menu
|
||||
id appName = msg_reg(msg_reg(c("NSProcessInfo"), s("processInfo")), s("processName"));
|
||||
id appName = msg(msg(c("NSProcessInfo"), s("processInfo")), s("processName"));
|
||||
id appMenuItem = createMenuItemNoAutorelease(appName, NULL, "");
|
||||
id appMenu = createMenu(appName);
|
||||
|
||||
msg_id(appMenuItem, s("setSubmenu:"), appMenu);
|
||||
msg_id(parentMenu, s("addItem:"), appMenuItem);
|
||||
msg(appMenuItem, s("setSubmenu:"), appMenu);
|
||||
msg(parentMenu, s("addItem:"), appMenuItem);
|
||||
|
||||
id title = msg_id(str("Hide "), s("stringByAppendingString:"), appName);
|
||||
id title = msg(str("Hide "), s("stringByAppendingString:"), appName);
|
||||
id item = createMenuItem(title, "hide:", "h");
|
||||
msg_id(appMenu, s("addItem:"), item);
|
||||
msg(appMenu, s("addItem:"), item);
|
||||
|
||||
id hideOthers = addMenuItem(appMenu, "Hide Others", "hideOtherApplications:", "h", FALSE);
|
||||
msg_int(hideOthers, s("setKeyEquivalentModifierMask:"), (NSEventModifierFlagOption | NSEventModifierFlagCommand));
|
||||
msg(hideOthers, s("setKeyEquivalentModifierMask:"), (NSEventModifierFlagOption | NSEventModifierFlagCommand));
|
||||
|
||||
addMenuItem(appMenu, "Show All", "unhideAllApplications:", "", FALSE);
|
||||
|
||||
addSeparator(appMenu);
|
||||
|
||||
title = msg_id(str("Quit "), s("stringByAppendingString:"), appName);
|
||||
title = msg(str("Quit "), s("stringByAppendingString:"), appName);
|
||||
item = createMenuItem(title, "terminate:", "q");
|
||||
msg_id(appMenu, s("addItem:"), item);
|
||||
msg(appMenu, s("addItem:"), item);
|
||||
}
|
||||
|
||||
void createDefaultEditMenu(id parentMenu) {
|
||||
@@ -410,8 +407,8 @@ void createDefaultEditMenu(id parentMenu) {
|
||||
id editMenuItem = createMenuItemNoAutorelease(str("Edit"), NULL, "");
|
||||
id editMenu = createMenu(str("Edit"));
|
||||
|
||||
msg_id(editMenuItem, s("setSubmenu:"), editMenu);
|
||||
msg_id(parentMenu, s("addItem:"), editMenuItem);
|
||||
msg(editMenuItem, s("setSubmenu:"), editMenu);
|
||||
msg(parentMenu, s("addItem:"), editMenuItem);
|
||||
|
||||
addMenuItem(editMenu, "Undo", "undo:", "z", FALSE);
|
||||
addMenuItem(editMenu, "Redo", "redo:", "y", FALSE);
|
||||
@@ -439,7 +436,7 @@ void processMenuRole(Menu *menu, id parentMenu, JsonNode *item) {
|
||||
}
|
||||
if ( STREQ(roleName, "hideothers")) {
|
||||
id hideOthers = addMenuItem(parentMenu, "Hide Others", "hideOtherApplications:", "h", FALSE);
|
||||
msg_int(hideOthers, s("setKeyEquivalentModifierMask:"), (NSEventModifierFlagOption | NSEventModifierFlagCommand));
|
||||
msg(hideOthers, s("setKeyEquivalentModifierMask:"), (NSEventModifierFlagOption | NSEventModifierFlagCommand));
|
||||
return;
|
||||
}
|
||||
if ( STREQ(roleName, "unhide")) {
|
||||
@@ -476,7 +473,7 @@ void processMenuRole(Menu *menu, id parentMenu, JsonNode *item) {
|
||||
}
|
||||
if( STREQ(roleName, "pasteandmatchstyle")) {
|
||||
id pasteandmatchstyle = addMenuItem(parentMenu, "Paste and Match Style", "pasteandmatchstyle:", "v", FALSE);
|
||||
msg_int(pasteandmatchstyle, s("setKeyEquivalentModifierMask:"), (NSEventModifierFlagOption | NSEventModifierFlagShift | NSEventModifierFlagCommand));
|
||||
msg(pasteandmatchstyle, s("setKeyEquivalentModifierMask:"), (NSEventModifierFlagOption | NSEventModifierFlagShift | NSEventModifierFlagCommand));
|
||||
}
|
||||
if ( STREQ(roleName, "selectall")) {
|
||||
addMenuItem(parentMenu, "Select All", "selectAll:", "a", FALSE);
|
||||
@@ -511,21 +508,20 @@ unsigned long parseModifiers(const char **modifiers) {
|
||||
const char *thisModifier = modifiers[0];
|
||||
int count = 0;
|
||||
while( thisModifier != NULL ) {
|
||||
|
||||
// Determine flags
|
||||
if( STREQ(thisModifier, "cmdorctrl") ) {
|
||||
if( STREQ(thisModifier, "CmdOrCtrl") ) {
|
||||
result |= NSEventModifierFlagCommand;
|
||||
}
|
||||
if( STREQ(thisModifier, "optionoralt") ) {
|
||||
if( STREQ(thisModifier, "OptionOrAlt") ) {
|
||||
result |= NSEventModifierFlagOption;
|
||||
}
|
||||
if( STREQ(thisModifier, "shift") ) {
|
||||
if( STREQ(thisModifier, "Shift") ) {
|
||||
result |= NSEventModifierFlagShift;
|
||||
}
|
||||
if( STREQ(thisModifier, "super") ) {
|
||||
if( STREQ(thisModifier, "Super") ) {
|
||||
result |= NSEventModifierFlagCommand;
|
||||
}
|
||||
if( STREQ(thisModifier, "ctrl") ) {
|
||||
if( STREQ(thisModifier, "Control") ) {
|
||||
result |= NSEventModifierFlagControl;
|
||||
}
|
||||
count++;
|
||||
@@ -543,18 +539,18 @@ id processRadioMenuItem(Menu *menu, id parentmenu, const char *title, const char
|
||||
// Create a MenuItemCallbackData
|
||||
MenuItemCallbackData *callback = CreateMenuItemCallbackData(menu, item, menuid, Radio);
|
||||
|
||||
id wrappedId = msg_id(c("NSValue"), s("valueWithPointer:"), (id)callback);
|
||||
msg_id(item, s("setRepresentedObject:"), wrappedId);
|
||||
id wrappedId = msg(c("NSValue"), s("valueWithPointer:"), callback);
|
||||
msg(item, s("setRepresentedObject:"), wrappedId);
|
||||
|
||||
id key = processAcceleratorKey(acceleratorkey);
|
||||
|
||||
((id(*)(id, SEL, id, SEL, id))objc_msgSend)(item, s("initWithTitle:action:keyEquivalent:"), str(title), s("menuItemCallback:"), key);
|
||||
msg(item, s("initWithTitle:action:keyEquivalent:"), str(title), s("menuItemCallback:"), key);
|
||||
|
||||
msg_bool(item, s("setEnabled:"), !disabled);
|
||||
msg_reg(item, s("autorelease"));
|
||||
msg_int(item, s("setState:"), (checked ? NSControlStateValueOn : NSControlStateValueOff));
|
||||
msg(item, s("setEnabled:"), !disabled);
|
||||
msg(item, s("autorelease"));
|
||||
msg(item, s("setState:"), (checked ? NSControlStateValueOn : NSControlStateValueOff));
|
||||
|
||||
msg_id(parentmenu, s("addItem:"), item);
|
||||
msg(parentmenu, s("addItem:"), item);
|
||||
return item;
|
||||
|
||||
}
|
||||
@@ -569,136 +565,73 @@ id processCheckboxMenuItem(Menu *menu, id parentmenu, const char *title, const c
|
||||
// Create a MenuItemCallbackData
|
||||
MenuItemCallbackData *callback = CreateMenuItemCallbackData(menu, item, menuid, Checkbox);
|
||||
|
||||
id wrappedId = msg_id(c("NSValue"), s("valueWithPointer:"), (id)callback);
|
||||
msg_id(item, s("setRepresentedObject:"), wrappedId);
|
||||
((id(*)(id, SEL, id, SEL, id))objc_msgSend)(item, s("initWithTitle:action:keyEquivalent:"), str(title), s("menuItemCallback:"), str(key));
|
||||
msg_bool(item, s("setEnabled:"), !disabled);
|
||||
msg_reg(item, s("autorelease"));
|
||||
msg_int(item, s("setState:"), (checked ? NSControlStateValueOn : NSControlStateValueOff));
|
||||
msg_id(parentmenu, s("addItem:"), item);
|
||||
id wrappedId = msg(c("NSValue"), s("valueWithPointer:"), callback);
|
||||
msg(item, s("setRepresentedObject:"), wrappedId);
|
||||
msg(item, s("initWithTitle:action:keyEquivalent:"), str(title), s("menuItemCallback:"), str(key));
|
||||
msg(item, s("setEnabled:"), !disabled);
|
||||
msg(item, s("autorelease"));
|
||||
msg(item, s("setState:"), (checked ? NSControlStateValueOn : NSControlStateValueOff));
|
||||
msg(parentmenu, s("addItem:"), item);
|
||||
return item;
|
||||
}
|
||||
|
||||
// getColour returns the colour from a styledLabel based on the key
|
||||
const char* getColour(JsonNode *styledLabelEntry, const char* key) {
|
||||
JsonNode* colEntry = getJSONObject(styledLabelEntry, key);
|
||||
if( colEntry == NULL ) {
|
||||
return NULL;
|
||||
id processTextMenuItem(Menu *menu, id parentMenu, const char *title, const char *menuid, bool disabled, const char *acceleratorkey, const char **modifiers, const char* tooltip, const char* image, const char* fontName, int fontSize, const char* RGBA, bool templateImage) {
|
||||
id item = ALLOC("NSMenuItem");
|
||||
|
||||
// Create a MenuItemCallbackData
|
||||
MenuItemCallbackData *callback = CreateMenuItemCallbackData(menu, item, menuid, Text);
|
||||
|
||||
id wrappedId = msg(c("NSValue"), s("valueWithPointer:"), callback);
|
||||
msg(item, s("setRepresentedObject:"), wrappedId);
|
||||
|
||||
id key = processAcceleratorKey(acceleratorkey);
|
||||
msg(item, s("initWithTitle:action:keyEquivalent:"), str(title),
|
||||
s("menuItemCallback:"), key);
|
||||
|
||||
if( tooltip != NULL ) {
|
||||
msg(item, s("setToolTip:"), str(tooltip));
|
||||
}
|
||||
return getJSONString(colEntry, "hex");
|
||||
}
|
||||
|
||||
id createAttributedStringFromStyledLabel(JsonNode *styledLabel, const char* fontName, int fontSize) {
|
||||
// Process image
|
||||
if( image != NULL && strlen(image) > 0) {
|
||||
id data = ALLOC("NSData");
|
||||
id imageData = msg(data, s("initWithBase64EncodedString:options:"), str(image), 0);
|
||||
id nsimage = ALLOC("NSImage");
|
||||
msg(nsimage, s("initWithData:"), imageData);
|
||||
if( templateImage ) {
|
||||
msg(nsimage, s("template"), YES);
|
||||
}
|
||||
msg(item, s("setImage:"), nsimage);
|
||||
}
|
||||
|
||||
// Create result
|
||||
id attributedString = ALLOC_INIT("NSMutableAttributedString");
|
||||
msg_reg(attributedString, s("autorelease"));
|
||||
|
||||
// Create new Dictionary
|
||||
// Process Menu Item attributes
|
||||
id dictionary = ALLOC_INIT("NSMutableDictionary");
|
||||
msg_reg(dictionary, s("autorelease"));
|
||||
|
||||
// Use default font
|
||||
CGFloat fontSizeFloat = (CGFloat)fontSize;
|
||||
id font = ((id(*)(id, SEL, CGFloat))objc_msgSend)(c("NSFont"), s("menuBarFontOfSize:"), fontSizeFloat);
|
||||
|
||||
// Check user supplied font
|
||||
if( STR_HAS_CHARS(fontName) ) {
|
||||
id fontNameAsNSString = str(fontName);
|
||||
id userFont = ((id(*)(id, SEL, id, CGFloat))objc_msgSend)(c("NSFont"), s("fontWithName:size:"), fontNameAsNSString, fontSizeFloat);
|
||||
if( userFont != NULL ) {
|
||||
font = userFont;
|
||||
}
|
||||
}
|
||||
|
||||
id fan = lookupStringConstant(str("NSFontAttributeName"));
|
||||
id NSForegroundColorAttributeName = lookupStringConstant(str("NSForegroundColorAttributeName"));
|
||||
id NSBackgroundColorAttributeName = lookupStringConstant(str("NSBackgroundColorAttributeName"));
|
||||
|
||||
// Loop over styled text creating NSAttributedText and appending to result
|
||||
JsonNode *styledLabelEntry;
|
||||
json_foreach(styledLabelEntry, styledLabel) {
|
||||
|
||||
// Clear dictionary
|
||||
msg_reg(dictionary, s("removeAllObjects"));
|
||||
|
||||
// Add font to dictionary
|
||||
msg_id_id(dictionary, s("setObject:forKey:"), font, fan);
|
||||
|
||||
// Get Text
|
||||
const char* thisLabel = mustJSONString(styledLabelEntry, "Label");
|
||||
|
||||
// Get foreground colour
|
||||
const char *hexColour = getColour(styledLabelEntry, "FgCol");
|
||||
if( hexColour != NULL) {
|
||||
unsigned short r, g, b, a;
|
||||
|
||||
// white by default
|
||||
r = g = b = a = 255;
|
||||
int count = sscanf(hexColour, "#%02hx%02hx%02hx%02hx", &r, &g, &b, &a);
|
||||
if (count > 0) {
|
||||
id colour = ((id(*)(id, SEL, CGFloat, CGFloat, CGFloat, CGFloat))objc_msgSend)(c("NSColor"), s("colorWithCalibratedRed:green:blue:alpha:"),
|
||||
(CGFloat)r / (CGFloat)255.0,
|
||||
(CGFloat)g / (CGFloat)255.0,
|
||||
(CGFloat)b / (CGFloat)255.0,
|
||||
(CGFloat)a / (CGFloat)255.0);
|
||||
msg_id_id(dictionary, s("setObject:forKey:"), colour, NSForegroundColorAttributeName);
|
||||
}
|
||||
}
|
||||
|
||||
// Get background colour
|
||||
hexColour = getColour(styledLabelEntry, "BgCol");
|
||||
if( hexColour != NULL) {
|
||||
unsigned short r, g, b, a;
|
||||
|
||||
// white by default
|
||||
r = g = b = a = 255;
|
||||
int count = sscanf(hexColour, "#%02hx%02hx%02hx%02hx", &r, &g, &b, &a);
|
||||
if (count > 0) {
|
||||
id colour = ((id(*)(id, SEL, CGFloat, CGFloat, CGFloat, CGFloat))objc_msgSend)(c("NSColor"), s("colorWithCalibratedRed:green:blue:alpha:"),
|
||||
(CGFloat)r / (CGFloat)255.0,
|
||||
(CGFloat)g / (CGFloat)255.0,
|
||||
(CGFloat)b / (CGFloat)255.0,
|
||||
(CGFloat)a / (CGFloat)255.0);
|
||||
msg_id_id(dictionary, s("setObject:forKey:"), colour, NSForegroundColorAttributeName);
|
||||
}
|
||||
}
|
||||
|
||||
// Create AttributedText
|
||||
id thisString = ALLOC("NSMutableAttributedString");
|
||||
msg_reg(thisString, s("autorelease"));
|
||||
msg_id_id(thisString, s("initWithString:attributes:"), str(thisLabel), dictionary);
|
||||
|
||||
// Append text to result
|
||||
msg_id(attributedString, s("appendAttributedString:"), thisString);
|
||||
}
|
||||
|
||||
return attributedString;
|
||||
|
||||
}
|
||||
|
||||
|
||||
id createAttributedString(const char* title, const char* fontName, int fontSize, const char* RGBA) {
|
||||
|
||||
// Create new Dictionary
|
||||
id dictionary = ALLOC_INIT("NSMutableDictionary");
|
||||
// Process font
|
||||
id font;
|
||||
CGFloat fontSizeFloat = (CGFloat)fontSize;
|
||||
|
||||
// Use default font
|
||||
id font = ((id(*)(id, SEL, CGFloat))objc_msgSend)(c("NSFont"), s("menuBarFontOfSize:"), fontSizeFloat);
|
||||
|
||||
// Check user supplied font
|
||||
if( STR_HAS_CHARS(fontName) ) {
|
||||
id fontNameAsNSString = str(fontName);
|
||||
id userFont = ((id(*)(id, SEL, id, CGFloat))objc_msgSend)(c("NSFont"), s("fontWithName:size:"), fontNameAsNSString, fontSizeFloat);
|
||||
if( userFont != NULL ) {
|
||||
font = userFont;
|
||||
// Check if valid
|
||||
id fontNameAsNSString = str(fontName);
|
||||
id fontsOnSystem = msg(msg(c("NSFontManager"), s("sharedFontManager")), s("availableFonts"));
|
||||
bool valid = msg(fontsOnSystem, s("containsObject:"), fontNameAsNSString);
|
||||
if( valid ) {
|
||||
font = msg(c("NSFont"), s("fontWithName:size:"), fontNameAsNSString, fontSizeFloat);
|
||||
} else {
|
||||
bool supportsMonospacedDigitSystemFont = (bool) msg(c("NSFont"), s("respondsToSelector:"), s("monospacedDigitSystemFontOfSize:weight:"));
|
||||
if( supportsMonospacedDigitSystemFont ) {
|
||||
font = msg(c("NSFont"), s("monospacedDigitSystemFontOfSize:weight:"), fontSizeFloat, NSFontWeightRegular);
|
||||
} else {
|
||||
font = msg(c("NSFont"), s("menuFontOfSize:"), fontSizeFloat);
|
||||
}
|
||||
}
|
||||
|
||||
// Add font to dictionary
|
||||
id fan = lookupStringConstant(str("NSFontAttributeName"));
|
||||
msg_id_id(dictionary, s("setObject:forKey:"), font, fan);
|
||||
msg(dictionary, s("setObject:forKey:"), font, lookupStringConstant(str("NSFontAttributeName")));
|
||||
|
||||
// Add offset to dictionary
|
||||
id offset = msg(c("NSNumber"), s("numberWithFloat:"), 0.0);
|
||||
msg(dictionary, s("setObject:forKey:"), offset, lookupStringConstant(str("NSBaselineOffsetAttributeName")));
|
||||
|
||||
// RGBA
|
||||
if( RGBA != NULL && strlen(RGBA) > 0) {
|
||||
@@ -708,75 +641,32 @@ id createAttributedString(const char* title, const char* fontName, int fontSize,
|
||||
r = g = b = a = 255;
|
||||
int count = sscanf(RGBA, "#%02hx%02hx%02hx%02hx", &r, &g, &b, &a);
|
||||
if (count > 0) {
|
||||
id colour = ((id(*)(id, SEL, CGFloat, CGFloat, CGFloat, CGFloat))objc_msgSend)(c("NSColor"), s("colorWithCalibratedRed:green:blue:alpha:"),
|
||||
(CGFloat)r / (CGFloat)255.0,
|
||||
(CGFloat)g / (CGFloat)255.0,
|
||||
(CGFloat)b / (CGFloat)255.0,
|
||||
(CGFloat)a / (CGFloat)255.0);
|
||||
id NSForegroundColorAttributeName = lookupStringConstant(str("NSForegroundColorAttributeName"));
|
||||
msg_id_id(dictionary, s("setObject:forKey:"), colour, NSForegroundColorAttributeName);
|
||||
id colour = msg(c("NSColor"), s("colorWithCalibratedRed:green:blue:alpha:"),
|
||||
(float)r / 255.0,
|
||||
(float)g / 255.0,
|
||||
(float)b / 255.0,
|
||||
(float)a / 255.0);
|
||||
msg(dictionary, s("setObject:forKey:"), colour, lookupStringConstant(str("NSForegroundColorAttributeName")));
|
||||
msg(colour, s("release"));
|
||||
}
|
||||
}
|
||||
|
||||
id attributedString = ALLOC("NSMutableAttributedString");
|
||||
msg_id_id(attributedString, s("initWithString:attributes:"), str(title), dictionary);
|
||||
msg_reg(attributedString, s("autorelease"));
|
||||
msg_reg(dictionary, s("autorelease"));
|
||||
return attributedString;
|
||||
}
|
||||
msg(attributedString, s("initWithString:attributes:"), str(title), dictionary);
|
||||
msg(dictionary, s("release"));
|
||||
|
||||
id processTextMenuItem(Menu *menu, id parentMenu, const char *title, const char *menuid, bool disabled, const char *acceleratorkey, const char **modifiers, const char* tooltip, const char* image, const char* fontName, int fontSize, const char* RGBA, bool templateImage, bool alternate, JsonNode* styledLabel) {
|
||||
id item = ALLOC("NSMenuItem");
|
||||
msg(item, s("setAttributedTitle:"), attributedString);
|
||||
msg(attributedString, s("autorelease"));
|
||||
|
||||
// Create a MenuItemCallbackData
|
||||
MenuItemCallbackData *callback = CreateMenuItemCallbackData(menu, item, menuid, Text);
|
||||
|
||||
id wrappedId = msg_id(c("NSValue"), s("valueWithPointer:"), (id)callback);
|
||||
msg_id(item, s("setRepresentedObject:"), wrappedId);
|
||||
|
||||
if( !alternate ) {
|
||||
id key = processAcceleratorKey(acceleratorkey);
|
||||
((id(*)(id, SEL, id, SEL, id))objc_msgSend)(item, s("initWithTitle:action:keyEquivalent:"), str(title),
|
||||
s("menuItemCallback:"), key);
|
||||
} else {
|
||||
((id(*)(id, SEL, id, SEL, id))objc_msgSend)(item, s("initWithTitle:action:keyEquivalent:"), str(title), s("menuItemCallback:"), str(""));
|
||||
}
|
||||
|
||||
if( tooltip != NULL ) {
|
||||
msg_id(item, s("setToolTip:"), str(tooltip));
|
||||
}
|
||||
|
||||
// Process image
|
||||
if( image != NULL && strlen(image) > 0) {
|
||||
id nsimage = createImageFromBase64Data(image, templateImage);
|
||||
msg_id(item, s("setImage:"), nsimage);
|
||||
}
|
||||
|
||||
id attributedString = NULL;
|
||||
if( styledLabel != NULL) {
|
||||
attributedString = createAttributedStringFromStyledLabel(styledLabel, fontName, fontSize);
|
||||
} else {
|
||||
attributedString = createAttributedString(title, fontName, fontSize, RGBA);
|
||||
}
|
||||
msg_id(item, s("setAttributedTitle:"), attributedString);
|
||||
|
||||
//msg_id(item, s("setTitle:"), str(title));
|
||||
|
||||
msg_bool(item, s("setEnabled:"), !disabled);
|
||||
msg_reg(item, s("autorelease"));
|
||||
msg(item, s("setEnabled:"), !disabled);
|
||||
msg(item, s("autorelease"));
|
||||
|
||||
// Process modifiers
|
||||
if( modifiers != NULL && !alternate) {
|
||||
if( modifiers != NULL ) {
|
||||
unsigned long modifierFlags = parseModifiers(modifiers);
|
||||
((id(*)(id, SEL, unsigned long))objc_msgSend)(item, s("setKeyEquivalentModifierMask:"), modifierFlags);
|
||||
msg(item, s("setKeyEquivalentModifierMask:"), modifierFlags);
|
||||
}
|
||||
|
||||
// alternate
|
||||
if( alternate ) {
|
||||
msg_bool(item, s("setAlternate:"), true);
|
||||
msg_int(item, s("setKeyEquivalentModifierMask:"), NSEventModifierFlagOption);
|
||||
}
|
||||
msg_id(parentMenu, s("addItem:"), item);
|
||||
msg(parentMenu, s("addItem:"), item);
|
||||
|
||||
return item;
|
||||
}
|
||||
@@ -797,6 +687,38 @@ void processMenuItem(Menu *menu, id parentMenu, JsonNode *item) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if this is a submenu
|
||||
JsonNode *submenu = json_find_member(item, "SubMenu");
|
||||
if( submenu != NULL ) {
|
||||
// Get the label
|
||||
JsonNode *menuNameNode = json_find_member(item, "Label");
|
||||
const char *name = "";
|
||||
if ( menuNameNode != NULL) {
|
||||
name = menuNameNode->string_;
|
||||
}
|
||||
|
||||
id thisMenuItem = createMenuItemNoAutorelease(str(name), NULL, "");
|
||||
id thisMenu = createMenu(str(name));
|
||||
|
||||
msg(thisMenuItem, s("setSubmenu:"), thisMenu);
|
||||
msg(parentMenu, s("addItem:"), thisMenuItem);
|
||||
|
||||
JsonNode *submenuItems = json_find_member(submenu, "Items");
|
||||
// If we have no items, just return
|
||||
if ( submenuItems == NULL ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Loop over submenu items
|
||||
JsonNode *item;
|
||||
json_foreach(item, submenuItems) {
|
||||
// Get item label
|
||||
processMenuItem(menu, thisMenu, item);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// This is a user menu. Get the common data
|
||||
// Get the label
|
||||
const char *label = getJSONString(item, "Label");
|
||||
@@ -804,13 +726,6 @@ void processMenuItem(Menu *menu, id parentMenu, JsonNode *item) {
|
||||
label = "(empty)";
|
||||
}
|
||||
|
||||
// Check for a styled label
|
||||
JsonNode *styledLabel = getJSONObject(item, "StyledLabel");
|
||||
|
||||
// Is this an alternate menu item?
|
||||
bool alternate = false;
|
||||
getJSONBool(item, "MacAlternate", &alternate);
|
||||
|
||||
const char *menuid = getJSONString(item, "ID");
|
||||
if ( menuid == NULL) {
|
||||
menuid = "";
|
||||
@@ -831,7 +746,7 @@ void processMenuItem(Menu *menu, id parentMenu, JsonNode *item) {
|
||||
bool templateImage = false;
|
||||
getJSONBool(item, "MacTemplateImage", &templateImage);
|
||||
|
||||
int fontSize = 0;
|
||||
int fontSize = 12;
|
||||
getJSONInt(item, "FontSize", &fontSize);
|
||||
|
||||
// If we have an accelerator
|
||||
@@ -864,36 +779,9 @@ void processMenuItem(Menu *menu, id parentMenu, JsonNode *item) {
|
||||
// Get the Type
|
||||
JsonNode *type = json_find_member(item, "Type");
|
||||
if( type != NULL ) {
|
||||
if( STREQ(type->string_, "Text") || STREQ(type->string_, "Submenu")) {
|
||||
id thisMenuItem = processTextMenuItem(menu, parentMenu, label, menuid, disabled, acceleratorkey, modifiers, tooltip, image, fontName, fontSize, RGBA, templateImage, alternate, styledLabel);
|
||||
|
||||
// Check if this node has a submenu
|
||||
JsonNode *submenu = json_find_member(item, "SubMenu");
|
||||
if( submenu != NULL ) {
|
||||
// Get the label
|
||||
JsonNode *menuNameNode = json_find_member(item, "Label");
|
||||
const char *name = "";
|
||||
if ( menuNameNode != NULL) {
|
||||
name = menuNameNode->string_;
|
||||
}
|
||||
|
||||
id thisMenu = createMenu(str(name));
|
||||
|
||||
msg_id(thisMenuItem, s("setSubmenu:"), thisMenu);
|
||||
|
||||
JsonNode *submenuItems = json_find_member(submenu, "Items");
|
||||
// If we have no items, just return
|
||||
if ( submenuItems == NULL ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Loop over submenu items
|
||||
JsonNode *item;
|
||||
json_foreach(item, submenuItems) {
|
||||
// Get item label
|
||||
processMenuItem(menu, thisMenu, item);
|
||||
}
|
||||
}
|
||||
if( STREQ(type->string_, "Text")) {
|
||||
processTextMenuItem(menu, parentMenu, label, menuid, disabled, acceleratorkey, modifiers, tooltip, image, fontName, fontSize, RGBA, templateImage);
|
||||
}
|
||||
else if ( STREQ(type->string_, "Separator")) {
|
||||
addSeparator(parentMenu);
|
||||
@@ -912,6 +800,7 @@ void processMenuItem(Menu *menu, id parentMenu, JsonNode *item) {
|
||||
|
||||
processRadioMenuItem(menu, parentMenu, label, menuid, disabled, checked, "");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if ( modifiers != NULL ) {
|
||||
|
||||
@@ -105,13 +105,10 @@ id processRadioMenuItem(Menu *menu, id parentmenu, const char *title, const char
|
||||
|
||||
id processCheckboxMenuItem(Menu *menu, id parentmenu, const char *title, const char *menuid, bool disabled, bool checked, const char *key);
|
||||
|
||||
id processTextMenuItem(Menu *menu, id parentMenu, const char *title, const char *menuid, bool disabled, const char *acceleratorkey, const char **modifiers, const char* tooltip, const char* image, const char* fontName, int fontSize, const char* RGBA, bool templateImage, bool alternate, JsonNode* styledLabel);
|
||||
id processTextMenuItem(Menu *menu, id parentMenu, const char *title, const char *menuid, bool disabled, const char *acceleratorkey, const char **modifiers, const char* tooltip, const char* image, const char* fontName, int fontSize, const char* RGBA, bool templateImage);
|
||||
void processMenuItem(Menu *menu, id parentMenu, JsonNode *item);
|
||||
void processMenuData(Menu *menu, JsonNode *menuData);
|
||||
|
||||
void processRadioGroupJSON(Menu *menu, JsonNode *radioGroup);
|
||||
void processRadioGroupJSON(Menu *menu, JsonNode *radioGroup) ;
|
||||
id GetMenu(Menu *menu);
|
||||
id createAttributedString(const char* title, const char* fontName, int fontSize, const char* RGBA);
|
||||
id createAttributedStringFromStyledLabel(JsonNode *styledLabel, const char* fontName, int fontSize);
|
||||
|
||||
#endif //ASSETS_C_MENU_DARWIN_H
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -6,8 +6,6 @@
|
||||
#include "traymenu_darwin.h"
|
||||
#include "trayicons.h"
|
||||
|
||||
extern Class trayMenuDelegateClass;
|
||||
|
||||
// A cache for all our tray menu icons
|
||||
// Global because it's a singleton
|
||||
struct hashmap_s trayIconCache;
|
||||
@@ -31,25 +29,12 @@ TrayMenu* NewTrayMenu(const char* menuJSON) {
|
||||
|
||||
result->ID = mustJSONString(processedJSON, "ID");
|
||||
result->label = mustJSONString(processedJSON, "Label");
|
||||
result->icon = mustJSONString(processedJSON, "Image");
|
||||
result->fontName = getJSONString(processedJSON, "FontName");
|
||||
result->RGBA = getJSONString(processedJSON, "RGBA");
|
||||
getJSONBool(processedJSON, "MacTemplateImage", &result->templateImage);
|
||||
result->fontSize = 0;
|
||||
getJSONInt(processedJSON, "FontSize", &result->fontSize);
|
||||
result->tooltip = NULL;
|
||||
result->tooltip = getJSONString(processedJSON, "Tooltip");
|
||||
result->disabled = false;
|
||||
getJSONBool(processedJSON, "Disabled", &result->disabled);
|
||||
|
||||
result->styledLabel = getJSONObject(processedJSON, "StyledLabel");
|
||||
result->icon = mustJSONString(processedJSON, "Icon");
|
||||
JsonNode* processedMenu = mustJSONObject(processedJSON, "ProcessedMenu");
|
||||
|
||||
// Create the menu
|
||||
JsonNode* processedMenu = mustJSONObject(processedJSON, "ProcessedMenu");
|
||||
result->menu = NewMenu(processedMenu);
|
||||
|
||||
result->delegate = NULL;
|
||||
|
||||
// Init tray status bar item
|
||||
result->statusbaritem = NULL;
|
||||
|
||||
@@ -65,28 +50,15 @@ void DumpTrayMenu(TrayMenu* trayMenu) {
|
||||
}
|
||||
|
||||
|
||||
void UpdateTrayLabel(TrayMenu *trayMenu, const char *label, const char *fontName, int fontSize, const char *RGBA, const char *tooltip, bool disabled, JsonNode *styledLabel) {
|
||||
void UpdateTrayLabel(TrayMenu *trayMenu, const char *label) {
|
||||
|
||||
// Exit early if NULL
|
||||
if( trayMenu->label == NULL ) {
|
||||
return;
|
||||
}
|
||||
// Update button label
|
||||
id statusBarButton = msg_reg(trayMenu->statusbaritem, s("button"));
|
||||
id attributedString = NULL;
|
||||
if( styledLabel != NULL) {
|
||||
attributedString = createAttributedStringFromStyledLabel(styledLabel, fontName, fontSize);
|
||||
} else {
|
||||
attributedString = createAttributedString(label, fontName, fontSize, RGBA);
|
||||
}
|
||||
|
||||
if( tooltip != NULL ) {
|
||||
msg_id(statusBarButton, s("setToolTip:"), str(tooltip));
|
||||
}
|
||||
|
||||
msg_bool(statusBarButton, s("setEnabled:"), !disabled);
|
||||
|
||||
msg_id(statusBarButton, s("setAttributedTitle:"), attributedString);
|
||||
id statusBarButton = msg(trayMenu->statusbaritem, s("button"));
|
||||
msg(statusBarButton, s("setTitle:"), str(label));
|
||||
}
|
||||
|
||||
void UpdateTrayIcon(TrayMenu *trayMenu) {
|
||||
@@ -96,57 +68,44 @@ void UpdateTrayIcon(TrayMenu *trayMenu) {
|
||||
return;
|
||||
}
|
||||
|
||||
id statusBarButton = msg_reg(trayMenu->statusbaritem, s("button"));
|
||||
id statusBarButton = msg(trayMenu->statusbaritem, s("button"));
|
||||
|
||||
// Empty icon means remove it
|
||||
if( STREMPTY(trayMenu->icon) ) {
|
||||
// Remove image
|
||||
msg_id(statusBarButton, s("setImage:"), NULL);
|
||||
msg(statusBarButton, s("setImage:"), NULL);
|
||||
return;
|
||||
}
|
||||
|
||||
id trayImage = hashmap_get(&trayIconCache, trayMenu->icon, strlen(trayMenu->icon));
|
||||
|
||||
// If we don't have the image in the icon cache then assume it's base64 encoded image data
|
||||
if (trayImage == NULL) {
|
||||
trayImage = createImageFromBase64Data(trayMenu->icon, trayMenu->templateImage);
|
||||
}
|
||||
|
||||
msg_int(statusBarButton, s("setImagePosition:"), trayMenu->trayIconPosition);
|
||||
msg_id(statusBarButton, s("setImage:"), trayImage);
|
||||
|
||||
msg(statusBarButton, s("setImagePosition:"), trayMenu->trayIconPosition);
|
||||
msg(statusBarButton, s("setImage:"), trayImage);
|
||||
}
|
||||
|
||||
|
||||
|
||||
void ShowTrayMenu(TrayMenu* trayMenu) {
|
||||
|
||||
// Create a status bar item if we don't have one
|
||||
if( trayMenu->statusbaritem == NULL ) {
|
||||
id statusBar = msg_reg( c("NSStatusBar"), s("systemStatusBar") );
|
||||
trayMenu->statusbaritem = ((id(*)(id, SEL, CGFloat))objc_msgSend)(statusBar, s("statusItemWithLength:"), NSVariableStatusItemLength);
|
||||
msg_reg(trayMenu->statusbaritem, s("retain"));
|
||||
id statusBar = msg( c("NSStatusBar"), s("systemStatusBar") );
|
||||
trayMenu->statusbaritem = msg(statusBar, s("statusItemWithLength:"), NSVariableStatusItemLength);
|
||||
msg(trayMenu->statusbaritem, s("retain"));
|
||||
|
||||
}
|
||||
|
||||
id statusBarButton = msg_reg(trayMenu->statusbaritem, s("button"));
|
||||
msg_uint(statusBarButton, s("setImagePosition:"), trayMenu->trayIconPosition);
|
||||
id statusBarButton = msg(trayMenu->statusbaritem, s("button"));
|
||||
msg(statusBarButton, s("setImagePosition:"), trayMenu->trayIconPosition);
|
||||
|
||||
// Update the icon if needed
|
||||
UpdateTrayIcon(trayMenu);
|
||||
|
||||
// Update the label if needed
|
||||
UpdateTrayLabel(trayMenu, trayMenu->label, trayMenu->fontName, trayMenu->fontSize, trayMenu->RGBA, trayMenu->tooltip, trayMenu->disabled, trayMenu->styledLabel);
|
||||
UpdateTrayLabel(trayMenu, trayMenu->label);
|
||||
|
||||
// Update the menu
|
||||
id menu = GetMenu(trayMenu->menu);
|
||||
objc_setAssociatedObject(menu, "trayMenuID", str(trayMenu->ID), OBJC_ASSOCIATION_ASSIGN);
|
||||
|
||||
// Create delegate
|
||||
id trayMenuDelegate = msg_reg((id)trayMenuDelegateClass, s("new"));
|
||||
msg_id(menu, s("setDelegate:"), trayMenuDelegate);
|
||||
objc_setAssociatedObject(trayMenuDelegate, "menu", menu, OBJC_ASSOCIATION_ASSIGN);
|
||||
|
||||
// Create menu delegate
|
||||
trayMenu->delegate = trayMenuDelegate;
|
||||
|
||||
msg_id(trayMenu->statusbaritem, s("setMenu:"), menu);
|
||||
msg(trayMenu->statusbaritem, s("setMenu:"), menu);
|
||||
}
|
||||
|
||||
// UpdateTrayMenuInPlace receives 2 menus. The current menu gets
|
||||
@@ -168,7 +127,6 @@ void UpdateTrayMenuInPlace(TrayMenu* currentMenu, TrayMenu* newMenu) {
|
||||
// Copy the other data
|
||||
currentMenu->ID = newMenu->ID;
|
||||
currentMenu->label = newMenu->label;
|
||||
currentMenu->styledLabel = newMenu->styledLabel;
|
||||
currentMenu->trayIconPosition = newMenu->trayIconPosition;
|
||||
currentMenu->icon = newMenu->icon;
|
||||
|
||||
@@ -189,16 +147,12 @@ void DeleteTrayMenu(TrayMenu* trayMenu) {
|
||||
|
||||
// Free the status item
|
||||
if ( trayMenu->statusbaritem != NULL ) {
|
||||
id statusBar = msg_reg( c("NSStatusBar"), s("systemStatusBar") );
|
||||
msg_id(statusBar, s("removeStatusItem:"), trayMenu->statusbaritem);
|
||||
msg_reg(trayMenu->statusbaritem, s("release"));
|
||||
id statusBar = msg( c("NSStatusBar"), s("systemStatusBar") );
|
||||
msg(statusBar, s("removeStatusItem:"), trayMenu->statusbaritem);
|
||||
msg(trayMenu->statusbaritem, s("release"));
|
||||
trayMenu->statusbaritem = NULL;
|
||||
}
|
||||
|
||||
if ( trayMenu->delegate != NULL ) {
|
||||
msg_reg(trayMenu->delegate, s("release"));
|
||||
}
|
||||
|
||||
// Free the tray menu memory
|
||||
MEMFREE(trayMenu);
|
||||
}
|
||||
@@ -228,9 +182,9 @@ void LoadTrayIcons() {
|
||||
int length = atoi((const char *)lengthAsString);
|
||||
|
||||
// Create the icon and add to the hashmap
|
||||
id imageData = ((id(*)(id, SEL, id, int))objc_msgSend)(c("NSData"), s("dataWithBytes:length:"), (id)data, length);
|
||||
id imageData = msg(c("NSData"), s("dataWithBytes:length:"), data, length);
|
||||
id trayImage = ALLOC("NSImage");
|
||||
msg_id(trayImage, s("initWithData:"), imageData);
|
||||
msg(trayImage, s("initWithData:"), imageData);
|
||||
hashmap_put(&trayIconCache, (const char *)name, strlen((const char *)name), trayImage);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,26 +13,14 @@ typedef struct {
|
||||
const char *label;
|
||||
const char *icon;
|
||||
const char *ID;
|
||||
const char *tooltip;
|
||||
|
||||
bool templateImage;
|
||||
const char *fontName;
|
||||
int fontSize;
|
||||
const char *RGBA;
|
||||
|
||||
bool disabled;
|
||||
|
||||
Menu* menu;
|
||||
|
||||
id statusbaritem;
|
||||
unsigned int trayIconPosition;
|
||||
int trayIconPosition;
|
||||
|
||||
JsonNode* processedJSON;
|
||||
|
||||
JsonNode* styledLabel;
|
||||
|
||||
id delegate;
|
||||
|
||||
} TrayMenu;
|
||||
|
||||
TrayMenu* NewTrayMenu(const char *trayJSON);
|
||||
@@ -40,7 +28,7 @@ void DumpTrayMenu(TrayMenu* trayMenu);
|
||||
void ShowTrayMenu(TrayMenu* trayMenu);
|
||||
void UpdateTrayMenuInPlace(TrayMenu* currentMenu, TrayMenu* newMenu);
|
||||
void UpdateTrayIcon(TrayMenu *trayMenu);
|
||||
void UpdateTrayLabel(TrayMenu *trayMenu, const char *label, const char *fontName, int fontSize, const char *RGBA, const char *tooltip, bool disabled, JsonNode *styledLabel);
|
||||
void UpdateTrayLabel(TrayMenu *trayMenu, const char*);
|
||||
|
||||
void LoadTrayIcons();
|
||||
void UnloadTrayIcons();
|
||||
|
||||
@@ -16,11 +16,6 @@ TrayMenuStore* NewTrayMenuStore() {
|
||||
ABORT("[NewTrayMenuStore] Not enough memory to allocate trayMenuMap!");
|
||||
}
|
||||
|
||||
if (pthread_mutex_init(&result->lock, NULL) != 0) {
|
||||
printf("\n mutex init has failed\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -30,19 +25,15 @@ int dumpTrayMenu(void *const context, struct hashmap_element_s *const e) {
|
||||
}
|
||||
|
||||
void DumpTrayMenuStore(TrayMenuStore* store) {
|
||||
pthread_mutex_lock(&store->lock);
|
||||
hashmap_iterate_pairs(&store->trayMenuMap, dumpTrayMenu, NULL);
|
||||
pthread_mutex_unlock(&store->lock);
|
||||
}
|
||||
|
||||
void AddTrayMenuToStore(TrayMenuStore* store, const char* menuJSON) {
|
||||
|
||||
TrayMenu* newMenu = NewTrayMenu(menuJSON);
|
||||
|
||||
pthread_mutex_lock(&store->lock);
|
||||
//TODO: check if there is already an entry for this menu
|
||||
hashmap_put(&store->trayMenuMap, newMenu->ID, strlen(newMenu->ID), newMenu);
|
||||
pthread_mutex_unlock(&store->lock);
|
||||
|
||||
}
|
||||
|
||||
int showTrayMenu(void *const context, struct hashmap_element_s *const e) {
|
||||
@@ -52,13 +43,12 @@ int showTrayMenu(void *const context, struct hashmap_element_s *const e) {
|
||||
}
|
||||
|
||||
void ShowTrayMenusInStore(TrayMenuStore* store) {
|
||||
pthread_mutex_lock(&store->lock);
|
||||
if( hashmap_num_entries(&store->trayMenuMap) > 0 ) {
|
||||
hashmap_iterate_pairs(&store->trayMenuMap, showTrayMenu, NULL);
|
||||
}
|
||||
pthread_mutex_unlock(&store->lock);
|
||||
}
|
||||
|
||||
|
||||
int freeTrayMenu(void *const context, struct hashmap_element_s *const e) {
|
||||
DeleteTrayMenu(e->data);
|
||||
return -1;
|
||||
@@ -75,39 +65,22 @@ void DeleteTrayMenuStore(TrayMenuStore *store) {
|
||||
|
||||
// Destroy tray menu map
|
||||
hashmap_destroy(&store->trayMenuMap);
|
||||
|
||||
pthread_mutex_destroy(&store->lock);
|
||||
}
|
||||
|
||||
TrayMenu* GetTrayMenuFromStore(TrayMenuStore* store, const char* menuID) {
|
||||
// Get the current menu
|
||||
pthread_mutex_lock(&store->lock);
|
||||
TrayMenu* result = hashmap_get(&store->trayMenuMap, menuID, strlen(menuID));
|
||||
pthread_mutex_unlock(&store->lock);
|
||||
return result;
|
||||
return hashmap_get(&store->trayMenuMap, menuID, strlen(menuID));
|
||||
}
|
||||
|
||||
TrayMenu* MustGetTrayMenuFromStore(TrayMenuStore* store, const char* menuID) {
|
||||
// Get the current menu
|
||||
pthread_mutex_lock(&store->lock);
|
||||
TrayMenu* result = hashmap_get(&store->trayMenuMap, menuID, strlen(menuID));
|
||||
pthread_mutex_unlock(&store->lock);
|
||||
|
||||
if (result == NULL ) {
|
||||
ABORT("Unable to find TrayMenu with ID '%s' in the TrayMenuStore!", menuID);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void DeleteTrayMenuInStore(TrayMenuStore* store, const char* ID) {
|
||||
|
||||
TrayMenu *menu = MustGetTrayMenuFromStore(store, ID);
|
||||
pthread_mutex_lock(&store->lock);
|
||||
hashmap_remove(&store->trayMenuMap, ID, strlen(ID));
|
||||
pthread_mutex_unlock(&store->lock);
|
||||
DeleteTrayMenu(menu);
|
||||
}
|
||||
|
||||
void UpdateTrayMenuLabelInStore(TrayMenuStore* store, const char* JSON) {
|
||||
// Parse the JSON
|
||||
JsonNode *parsedUpdate = mustParseJSON(JSON);
|
||||
@@ -118,19 +91,7 @@ void UpdateTrayMenuLabelInStore(TrayMenuStore* store, const char* JSON) {
|
||||
|
||||
// Check we have this menu
|
||||
TrayMenu *menu = MustGetTrayMenuFromStore(store, ID);
|
||||
|
||||
const char *fontName = getJSONString(parsedUpdate, "FontName");
|
||||
const char *RGBA = getJSONString(parsedUpdate, "RGBA");
|
||||
int fontSize = 0;
|
||||
getJSONInt(parsedUpdate, "FontSize", &fontSize);
|
||||
const char *tooltip = getJSONString(parsedUpdate, "Tooltip");
|
||||
bool disabled = false;
|
||||
getJSONBool(parsedUpdate, "Disabled", &disabled);
|
||||
|
||||
JsonNode *styledLabel = getJSONObject(parsedUpdate, "StyledLabel");
|
||||
|
||||
UpdateTrayLabel(menu, Label, fontName, fontSize, RGBA, tooltip, disabled, styledLabel);
|
||||
|
||||
UpdateTrayLabel(menu, Label);
|
||||
|
||||
}
|
||||
|
||||
@@ -144,9 +105,7 @@ void UpdateTrayMenuInStore(TrayMenuStore* store, const char* menuJSON) {
|
||||
// If we don't have a menu, we create one
|
||||
if ( currentMenu == NULL ) {
|
||||
// Store the new menu
|
||||
pthread_mutex_lock(&store->lock);
|
||||
hashmap_put(&store->trayMenuMap, newMenu->ID, strlen(newMenu->ID), newMenu);
|
||||
pthread_mutex_unlock(&store->lock);
|
||||
|
||||
// Show it
|
||||
ShowTrayMenu(newMenu);
|
||||
@@ -157,9 +116,7 @@ void UpdateTrayMenuInStore(TrayMenuStore* store, const char* menuJSON) {
|
||||
// Save the status bar reference
|
||||
newMenu->statusbaritem = currentMenu->statusbaritem;
|
||||
|
||||
pthread_mutex_lock(&store->lock);
|
||||
hashmap_remove(&store->trayMenuMap, newMenu->ID, strlen(newMenu->ID));
|
||||
pthread_mutex_unlock(&store->lock);
|
||||
|
||||
// Delete the current menu
|
||||
DeleteMenu(currentMenu->menu);
|
||||
@@ -168,10 +125,9 @@ void UpdateTrayMenuInStore(TrayMenuStore* store, const char* menuJSON) {
|
||||
// Free the tray menu memory
|
||||
MEMFREE(currentMenu);
|
||||
|
||||
pthread_mutex_lock(&store->lock);
|
||||
hashmap_put(&store->trayMenuMap, newMenu->ID, strlen(newMenu->ID), newMenu);
|
||||
pthread_mutex_unlock(&store->lock);
|
||||
|
||||
// Show the updated menu
|
||||
ShowTrayMenu(newMenu);
|
||||
|
||||
}
|
||||
|
||||
@@ -5,10 +5,6 @@
|
||||
#ifndef TRAYMENUSTORE_DARWIN_H
|
||||
#define TRAYMENUSTORE_DARWIN_H
|
||||
|
||||
#include "traymenu_darwin.h"
|
||||
|
||||
#include <pthread.h>
|
||||
|
||||
typedef struct {
|
||||
|
||||
int dummy;
|
||||
@@ -17,8 +13,6 @@ typedef struct {
|
||||
// It maps tray IDs to TrayMenu*
|
||||
struct hashmap_s trayMenuMap;
|
||||
|
||||
pthread_mutex_t lock;
|
||||
|
||||
} TrayMenuStore;
|
||||
|
||||
TrayMenuStore* NewTrayMenuStore();
|
||||
@@ -28,9 +22,6 @@ void UpdateTrayMenuInStore(TrayMenuStore* store, const char* menuJSON);
|
||||
void ShowTrayMenusInStore(TrayMenuStore* store);
|
||||
void DeleteTrayMenuStore(TrayMenuStore* store);
|
||||
|
||||
TrayMenu* GetTrayMenuByID(TrayMenuStore* store, const char* menuID);
|
||||
|
||||
void UpdateTrayMenuLabelInStore(TrayMenuStore* store, const char* JSON);
|
||||
void DeleteTrayMenuInStore(TrayMenuStore* store, const char* id);
|
||||
|
||||
#endif //TRAYMENUSTORE_DARWIN_H
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
// +build !windows
|
||||
|
||||
/**
|
||||
/**
|
||||
* Copyright (c) 2014 rxi
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or modify it
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/**
|
||||
/**
|
||||
* Copyright (c) 2014 rxi
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or modify it
|
||||
|
||||
@@ -1,68 +0,0 @@
|
||||
|
||||
|
||||
/* this ALWAYS GENERATED file contains the definitions for the interfaces */
|
||||
|
||||
|
||||
/* File created by MIDL compiler version 8.01.0622 */
|
||||
/* @@MIDL_FILE_HEADING( ) */
|
||||
|
||||
|
||||
|
||||
/* verify that the <rpcndr.h> version is high enough to compile this file*/
|
||||
#ifndef __REQUIRED_RPCNDR_H_VERSION__
|
||||
#define __REQUIRED_RPCNDR_H_VERSION__ 500
|
||||
#endif
|
||||
|
||||
/* verify that the <rpcsal.h> version is high enough to compile this file*/
|
||||
#ifndef __REQUIRED_RPCSAL_H_VERSION__
|
||||
#define __REQUIRED_RPCSAL_H_VERSION__ 100
|
||||
#endif
|
||||
|
||||
#include "rpc.h"
|
||||
#include "rpcndr.h"
|
||||
|
||||
#ifndef __RPCNDR_H_VERSION__
|
||||
#error this stub requires an updated version of <rpcndr.h>
|
||||
#endif /* __RPCNDR_H_VERSION__ */
|
||||
|
||||
|
||||
#ifndef __eventtoken_h__
|
||||
#define __eventtoken_h__
|
||||
|
||||
#if defined(_MSC_VER) && (_MSC_VER >= 1020)
|
||||
#pragma once
|
||||
#endif
|
||||
|
||||
/* Forward Declarations */
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C"{
|
||||
#endif
|
||||
|
||||
|
||||
/* interface __MIDL_itf_eventtoken_0000_0000 */
|
||||
/* [local] */
|
||||
|
||||
// Microsoft Windows
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
#pragma once
|
||||
typedef struct EventRegistrationToken
|
||||
{
|
||||
__int64 value;
|
||||
} EventRegistrationToken;
|
||||
|
||||
|
||||
|
||||
extern RPC_IF_HANDLE __MIDL_itf_eventtoken_0000_0000_v0_0_c_ifspec;
|
||||
extern RPC_IF_HANDLE __MIDL_itf_eventtoken_0000_0000_v0_0_s_ifspec;
|
||||
|
||||
/* Additional Prototypes for ALL interfaces */
|
||||
|
||||
/* end of Additional Prototypes */
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
These files were generated using the scripts in the [webview](https://github.com/webview/webview) project and compressed using UPX.
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,6 +0,0 @@
|
||||
cmake_minimum_required(VERSION 3.19)
|
||||
project(test)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 14)
|
||||
|
||||
add_executable(test main.cpp)
|
||||
Binary file not shown.
Binary file not shown.
@@ -1,11 +0,0 @@
|
||||
// +build windows
|
||||
|
||||
package x64
|
||||
|
||||
import _ "embed"
|
||||
|
||||
//go:embed webview.dll
|
||||
var WebView2 []byte
|
||||
|
||||
//go:embed WebView2Loader.dll
|
||||
var WebView2Loader []byte
|
||||
@@ -1,9 +0,0 @@
|
||||
// +build !windows
|
||||
|
||||
// This is a stub to define the following even though they don't exist
|
||||
// on non-Windows systems. Note: This is not the right way to handle this.
|
||||
package x64
|
||||
|
||||
var WebView2 []byte
|
||||
|
||||
var WebView2Loader []byte
|
||||
@@ -1,94 +0,0 @@
|
||||
|
||||
#ifndef WV2COMHANDLER_H
|
||||
#define WV2COMHANDLER_H
|
||||
|
||||
#include "ffenestri_windows.h"
|
||||
#include "windows/WebView2.h"
|
||||
|
||||
#include <locale>
|
||||
#include <codecvt>
|
||||
|
||||
class wv2ComHandler
|
||||
: public ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler,
|
||||
public ICoreWebView2CreateCoreWebView2ControllerCompletedHandler,
|
||||
public ICoreWebView2WebMessageReceivedEventHandler,
|
||||
public ICoreWebView2PermissionRequestedEventHandler
|
||||
{
|
||||
|
||||
struct Application *app;
|
||||
HWND window;
|
||||
messageCallback mcb;
|
||||
comHandlerCallback cb;
|
||||
|
||||
public:
|
||||
wv2ComHandler(struct Application *app, HWND window, messageCallback mcb, comHandlerCallback cb) {
|
||||
this->app = app;
|
||||
this->window = window;
|
||||
this->mcb = mcb;
|
||||
this->cb = cb;
|
||||
}
|
||||
ULONG STDMETHODCALLTYPE AddRef() { return 1; }
|
||||
ULONG STDMETHODCALLTYPE Release() { return 1; }
|
||||
HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, LPVOID *ppv) {
|
||||
return S_OK;
|
||||
}
|
||||
HRESULT STDMETHODCALLTYPE Invoke(HRESULT res,
|
||||
ICoreWebView2Environment *env) {
|
||||
env->CreateCoreWebView2Controller(window, this);
|
||||
return S_OK;
|
||||
}
|
||||
HRESULT STDMETHODCALLTYPE Invoke(HRESULT res,
|
||||
ICoreWebView2Controller *controller) {
|
||||
controller->AddRef();
|
||||
|
||||
ICoreWebView2 *webview;
|
||||
::EventRegistrationToken token;
|
||||
controller->get_CoreWebView2(&webview);
|
||||
webview->add_WebMessageReceived(this, &token);
|
||||
webview->add_PermissionRequested(this, &token);
|
||||
|
||||
cb(controller);
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
// This is called when JS posts a message back to webkit
|
||||
HRESULT STDMETHODCALLTYPE Invoke(
|
||||
ICoreWebView2 *sender, ICoreWebView2WebMessageReceivedEventArgs *args) {
|
||||
LPWSTR message;
|
||||
args->TryGetWebMessageAsString(&message);
|
||||
if ( message == nullptr ) {
|
||||
return S_OK;
|
||||
}
|
||||
const char *m = LPWSTRToCstr(message);
|
||||
|
||||
// check for internal messages
|
||||
if (strcmp(m, "completed") == 0) {
|
||||
completed(app);
|
||||
return S_OK;
|
||||
}
|
||||
else if (strcmp(m, "initialised") == 0) {
|
||||
loadAssets(app);
|
||||
return S_OK;
|
||||
}
|
||||
else {
|
||||
messageFromWindowCallback(m);
|
||||
}
|
||||
delete[] m;
|
||||
return S_OK;
|
||||
}
|
||||
HRESULT STDMETHODCALLTYPE
|
||||
Invoke(ICoreWebView2 *sender,
|
||||
ICoreWebView2PermissionRequestedEventArgs *args) {
|
||||
printf("DDDDDDDDDDDD\n");
|
||||
|
||||
COREWEBVIEW2_PERMISSION_KIND kind;
|
||||
args->get_PermissionKind(&kind);
|
||||
if (kind == COREWEBVIEW2_PERMISSION_KIND_CLIPBOARD_READ) {
|
||||
args->put_State(COREWEBVIEW2_PERMISSION_STATE_ALLOW);
|
||||
}
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -40,7 +40,7 @@ func Mkdir(dirname string) error {
|
||||
// Returns error on failure
|
||||
func MkDirs(fullPath string, mode ...os.FileMode) error {
|
||||
var perms os.FileMode
|
||||
perms = 0755
|
||||
perms = 0700
|
||||
if len(mode) == 1 {
|
||||
perms = mode[0]
|
||||
}
|
||||
@@ -201,6 +201,10 @@ func GetSubdirectories(rootDir string) (*slicer.StringSlicer, error) {
|
||||
|
||||
func DirIsEmpty(dir string) (bool, error) {
|
||||
|
||||
if !DirExists(dir) {
|
||||
return false, fmt.Errorf("DirIsEmpty called with a non-existant directory: %s", dir)
|
||||
}
|
||||
|
||||
// CREDIT: https://stackoverflow.com/a/30708914/8325411
|
||||
f, err := os.Open(dir)
|
||||
if err != nil {
|
||||
@@ -239,7 +243,7 @@ func CopyDir(src string, dst string) (err error) {
|
||||
return fmt.Errorf("destination already exists")
|
||||
}
|
||||
|
||||
err = MkDirs(dst)
|
||||
err = os.MkdirAll(dst, si.Mode())
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -52,13 +52,21 @@ func (a *Asset) AsString() string {
|
||||
return a.Data
|
||||
}
|
||||
|
||||
func (a *Asset) minifiedData() (string, error) {
|
||||
// AsCHexData processes the asset data so it may be used by C
|
||||
func (a *Asset) AsCHexData() string {
|
||||
|
||||
// This will be our final string to hexify
|
||||
dataString := a.Data
|
||||
|
||||
switch a.Type {
|
||||
case AssetTypes.HTML:
|
||||
|
||||
// Escape HTML
|
||||
var re = regexp.MustCompile(`[\s]+`)
|
||||
result := re.ReplaceAllString(a.Data, ` `)
|
||||
var re = regexp.MustCompile(`\s{2,}`)
|
||||
result := re.ReplaceAllString(a.Data, ``)
|
||||
result = strings.ReplaceAll(result, "\n", "")
|
||||
result = strings.ReplaceAll(result, "\r\n", "")
|
||||
result = strings.ReplaceAll(result, "\n", "")
|
||||
|
||||
// Inject wailsloader code
|
||||
result = strings.Replace(result, `</body>`, `<script id='wailsloader'>window.wailsloader = { html: true, runtime: false, userjs: false, usercss: false };var self=document.querySelector('#wailsloader');self.parentNode.removeChild(self);</script></body>`, 1)
|
||||
@@ -67,7 +75,7 @@ func (a *Asset) minifiedData() (string, error) {
|
||||
urlString := strings.ReplaceAll(url.String(), "/", "%2f")
|
||||
|
||||
// Save Data uRI string
|
||||
return "data:text/html;charset=utf-8," + urlString, nil
|
||||
dataString = "data:text/html;charset=utf-8," + urlString
|
||||
|
||||
case AssetTypes.CSS:
|
||||
|
||||
@@ -83,28 +91,19 @@ func (a *Asset) minifiedData() (string, error) {
|
||||
result = strings.ReplaceAll(result, `'`, `\'`)
|
||||
result = strings.ReplaceAll(result, ` {`, `{`)
|
||||
result = strings.ReplaceAll(result, `: `, `:`)
|
||||
return fmt.Sprintf("window.wails._.InjectCSS(\"%s\");", result), nil
|
||||
dataString = fmt.Sprintf("window.wails._.InjectCSS(\"%s\");", result)
|
||||
|
||||
case AssetTypes.JS:
|
||||
m := minify.New()
|
||||
m.AddFunc("application/javascript", js.Minify)
|
||||
var err error
|
||||
result, err := m.String("application/javascript", a.Data+";")
|
||||
dataString, err = m.String("application/javascript", a.Data+";")
|
||||
if err != nil {
|
||||
return "", err
|
||||
log.Fatal(err)
|
||||
}
|
||||
return result, nil
|
||||
default:
|
||||
return "", fmt.Errorf("minification for asset type %s not implemented", a.Type)
|
||||
a.Data = dataString
|
||||
}
|
||||
}
|
||||
|
||||
// AsCHexData processes the asset data so it may be used by C
|
||||
func (a *Asset) AsCHexData() string {
|
||||
dataString, err := a.minifiedData()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
// Get byte data of the string
|
||||
bytes := *(*[]byte)(unsafe.Pointer(&dataString))
|
||||
|
||||
|
||||
@@ -1,53 +0,0 @@
|
||||
package html
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestAsset_minifiedData(t *testing.T) {
|
||||
type fields struct {
|
||||
Type string
|
||||
Path string
|
||||
Data string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
want string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "multi-line tag",
|
||||
fields: fields{
|
||||
Type: AssetTypes.HTML,
|
||||
Path: "foo.html",
|
||||
Data: "<link\n rel=\"stylesheet\"\n href=\"src/foo.css\"\n>\n",
|
||||
},
|
||||
want: "data:text/html;charset=utf-8,%3Clink%20rel=%22stylesheet%22%20href=%22src%2ffoo.css%22%20%3E%20",
|
||||
},
|
||||
{
|
||||
name: "multi-line tag no spaces",
|
||||
fields: fields{
|
||||
Type: AssetTypes.HTML,
|
||||
Path: "foo.html",
|
||||
Data: "<link\nrel=\"stylesheet\"\nhref=\"src/foo.css\"\n>\n",
|
||||
},
|
||||
want: "data:text/html;charset=utf-8,%3Clink%20rel=%22stylesheet%22%20href=%22src%2ffoo.css%22%20%3E%20",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
a := &Asset{
|
||||
Type: tt.fields.Type,
|
||||
Path: tt.fields.Path,
|
||||
Data: tt.fields.Data,
|
||||
}
|
||||
got, err := a.minifiedData()
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("Asset.minifiedData() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if got != tt.want {
|
||||
t.Errorf("Asset.minifiedData() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -84,7 +84,7 @@ func (a *AssetBundle) processHTML(htmldata string) error {
|
||||
}
|
||||
|
||||
//process the token according to the token type...
|
||||
if tokenType == html.StartTagToken || tokenType == html.SelfClosingTagToken {
|
||||
if tokenType == html.StartTagToken {
|
||||
//get the token
|
||||
token := tokenizer.Token()
|
||||
|
||||
@@ -112,9 +112,6 @@ func (a *AssetBundle) processHTML(htmldata string) error {
|
||||
if attr.Key == "as" && attr.Val == "script" {
|
||||
asset.Type = AssetTypes.JS
|
||||
}
|
||||
if attr.Key == "rel" && attr.Val == "modulepreload" {
|
||||
asset.Type = AssetTypes.JS
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure we don't include duplicates
|
||||
|
||||
@@ -1,73 +0,0 @@
|
||||
package html
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestNewAssetBundle(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
pathToHTML string
|
||||
wantAssets []string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "basic html",
|
||||
pathToHTML: "testdata/basic.html",
|
||||
wantAssets: []string{
|
||||
AssetTypes.HTML,
|
||||
AssetTypes.FAVICON,
|
||||
AssetTypes.JS,
|
||||
AssetTypes.CSS,
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "self closing tags",
|
||||
pathToHTML: "testdata/self_closing.html",
|
||||
wantAssets: []string{
|
||||
AssetTypes.HTML,
|
||||
AssetTypes.FAVICON,
|
||||
AssetTypes.JS,
|
||||
AssetTypes.CSS,
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "multi-line tags",
|
||||
pathToHTML: "testdata/self_closing.html",
|
||||
wantAssets: []string{
|
||||
AssetTypes.HTML,
|
||||
AssetTypes.FAVICON,
|
||||
AssetTypes.JS,
|
||||
AssetTypes.CSS,
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := NewAssetBundle(tt.pathToHTML)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("NewAssetBundle() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if len(got.assets) != len(tt.wantAssets) {
|
||||
t.Errorf("NewAssetBundle() len(assets) = %d, want %d",
|
||||
len(got.assets), len(tt.wantAssets))
|
||||
}
|
||||
|
||||
for i := range tt.wantAssets {
|
||||
if i >= len(got.assets) {
|
||||
t.Errorf("NewAssetBundle() missing assets[%d].Type = %s",
|
||||
i, tt.wantAssets[i])
|
||||
} else {
|
||||
if got.assets[i].Type != tt.wantAssets[i] {
|
||||
t.Errorf("NewAssetBundle() assets[%d].Type = %s, want %s",
|
||||
i, got.assets[i].Type, tt.wantAssets[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
14
v2/internal/html/testdata/basic.html
vendored
14
v2/internal/html/testdata/basic.html
vendored
@@ -1,14 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/src/favicon.svg"></link>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<script type="text/javascript" src="src/bundle.js"></script>
|
||||
<link rel="stylesheet" href="src/style.css"></link>
|
||||
<title>Vite App</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
</body>
|
||||
</html>
|
||||
17
v2/internal/html/testdata/multi_line.html
vendored
17
v2/internal/html/testdata/multi_line.html
vendored
@@ -1,17 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/src/favicon.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<script type="text/javascript" src="src/bundle.js"></script>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="src/style.css"
|
||||
/>
|
||||
<title>Vite App</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
</body>
|
||||
</html>
|
||||
14
v2/internal/html/testdata/self_closing.html
vendored
14
v2/internal/html/testdata/self_closing.html
vendored
@@ -1,14 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/src/favicon.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<script type="text/javascript" src="src/bundle.js"></script>
|
||||
<link rel="stylesheet" href="src/style.css" />
|
||||
<title>Vite App</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
</body>
|
||||
</html>
|
||||
1
v2/internal/html/testdata/src/bundle.js
vendored
1
v2/internal/html/testdata/src/bundle.js
vendored
@@ -1 +0,0 @@
|
||||
window.alert("I am JS!");
|
||||
8
v2/internal/html/testdata/src/favicon.svg
vendored
8
v2/internal/html/testdata/src/favicon.svg
vendored
@@ -1,8 +0,0 @@
|
||||
<svg version="1.1"
|
||||
baseProfile="full"
|
||||
width="300" height="200"
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
|
||||
<rect width="100%" height="100%" fill="blue" />
|
||||
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 172 B |
3
v2/internal/html/testdata/src/style.css
vendored
3
v2/internal/html/testdata/src/style.css
vendored
@@ -1,3 +0,0 @@
|
||||
.body {
|
||||
background: blue;
|
||||
}
|
||||
@@ -2,9 +2,6 @@ package menumanager
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strings"
|
||||
|
||||
"github.com/leaanthony/go-ansi-parser"
|
||||
|
||||
"github.com/wailsapp/wails/v2/pkg/menu"
|
||||
"github.com/wailsapp/wails/v2/pkg/menu/keys"
|
||||
@@ -40,29 +37,14 @@ type ProcessedMenuItem struct {
|
||||
// Image - base64 image data
|
||||
Image string `json:",omitempty"`
|
||||
MacTemplateImage bool `json:", omitempty"`
|
||||
MacAlternate bool `json:", omitempty"`
|
||||
|
||||
// Tooltip
|
||||
Tooltip string `json:",omitempty"`
|
||||
|
||||
// Styled label
|
||||
StyledLabel []*ansi.StyledText `json:",omitempty"`
|
||||
}
|
||||
|
||||
func NewProcessedMenuItem(menuItemMap *MenuItemMap, menuItem *menu.MenuItem) *ProcessedMenuItem {
|
||||
|
||||
ID := menuItemMap.menuItemToIDMap[menuItem]
|
||||
|
||||
// Parse ANSI text
|
||||
var styledLabel []*ansi.StyledText
|
||||
tempLabel := menuItem.Label
|
||||
if strings.Contains(tempLabel, "\033[") {
|
||||
parsedLabel, err := ansi.Parse(menuItem.Label)
|
||||
if err == nil {
|
||||
styledLabel = parsedLabel
|
||||
}
|
||||
}
|
||||
|
||||
result := &ProcessedMenuItem{
|
||||
ID: ID,
|
||||
Label: menuItem.Label,
|
||||
@@ -78,9 +60,7 @@ func NewProcessedMenuItem(menuItemMap *MenuItemMap, menuItem *menu.MenuItem) *Pr
|
||||
FontName: menuItem.FontName,
|
||||
Image: menuItem.Image,
|
||||
MacTemplateImage: menuItem.MacTemplateImage,
|
||||
MacAlternate: menuItem.MacAlternate,
|
||||
Tooltip: menuItem.Tooltip,
|
||||
StyledLabel: styledLabel,
|
||||
}
|
||||
|
||||
if menuItem.SubMenu != nil {
|
||||
|
||||
@@ -3,43 +3,29 @@ package menumanager
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/leaanthony/go-ansi-parser"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/wailsapp/wails/v2/pkg/menu"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var trayMenuID int
|
||||
var trayMenuIDMutex sync.Mutex
|
||||
|
||||
func generateTrayID() string {
|
||||
var idStr string
|
||||
trayMenuIDMutex.Lock()
|
||||
idStr = strconv.Itoa(trayMenuID)
|
||||
result := fmt.Sprintf("%d", trayMenuID)
|
||||
trayMenuID++
|
||||
trayMenuIDMutex.Unlock()
|
||||
return idStr
|
||||
return result
|
||||
}
|
||||
|
||||
type TrayMenu struct {
|
||||
ID string
|
||||
Label string
|
||||
FontSize int
|
||||
FontName string
|
||||
Disabled bool
|
||||
Tooltip string `json:",omitempty"`
|
||||
Image string
|
||||
MacTemplateImage bool
|
||||
RGBA string
|
||||
menuItemMap *MenuItemMap
|
||||
menu *menu.Menu
|
||||
ProcessedMenu *WailsMenu
|
||||
trayMenu *menu.TrayMenu
|
||||
StyledLabel []*ansi.StyledText `json:",omitempty"`
|
||||
ID string
|
||||
Label string
|
||||
Icon string
|
||||
menuItemMap *MenuItemMap
|
||||
menu *menu.Menu
|
||||
ProcessedMenu *WailsMenu
|
||||
}
|
||||
|
||||
func (t *TrayMenu) AsJSON() (string, error) {
|
||||
@@ -52,29 +38,11 @@ func (t *TrayMenu) AsJSON() (string, error) {
|
||||
|
||||
func NewTrayMenu(trayMenu *menu.TrayMenu) *TrayMenu {
|
||||
|
||||
// Parse ANSI text
|
||||
var styledLabel []*ansi.StyledText
|
||||
tempLabel := trayMenu.Label
|
||||
if strings.Contains(tempLabel, "\033[") {
|
||||
parsedLabel, err := ansi.Parse(tempLabel)
|
||||
if err == nil {
|
||||
styledLabel = parsedLabel
|
||||
}
|
||||
}
|
||||
|
||||
result := &TrayMenu{
|
||||
Label: trayMenu.Label,
|
||||
FontName: trayMenu.FontName,
|
||||
FontSize: trayMenu.FontSize,
|
||||
Disabled: trayMenu.Disabled,
|
||||
Tooltip: trayMenu.Tooltip,
|
||||
Image: trayMenu.Image,
|
||||
MacTemplateImage: trayMenu.MacTemplateImage,
|
||||
menu: trayMenu.Menu,
|
||||
RGBA: trayMenu.RGBA,
|
||||
menuItemMap: NewMenuItemMap(),
|
||||
trayMenu: trayMenu,
|
||||
StyledLabel: styledLabel,
|
||||
Label: trayMenu.Label,
|
||||
Icon: trayMenu.Icon,
|
||||
menu: trayMenu.Menu,
|
||||
menuItemMap: NewMenuItemMap(),
|
||||
}
|
||||
|
||||
result.menuItemMap.AddMenu(trayMenu.Menu)
|
||||
@@ -83,28 +51,6 @@ func NewTrayMenu(trayMenu *menu.TrayMenu) *TrayMenu {
|
||||
return result
|
||||
}
|
||||
|
||||
func (m *Manager) OnTrayMenuOpen(id string) {
|
||||
trayMenu, ok := m.trayMenus[id]
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
if trayMenu.trayMenu.OnOpen == nil {
|
||||
return
|
||||
}
|
||||
go trayMenu.trayMenu.OnOpen()
|
||||
}
|
||||
|
||||
func (m *Manager) OnTrayMenuClose(id string) {
|
||||
trayMenu, ok := m.trayMenus[id]
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
if trayMenu.trayMenu.OnClose == nil {
|
||||
return
|
||||
}
|
||||
go trayMenu.trayMenu.OnClose()
|
||||
}
|
||||
|
||||
func (m *Manager) AddTrayMenu(trayMenu *menu.TrayMenu) (string, error) {
|
||||
newTrayMenu := NewTrayMenu(trayMenu)
|
||||
|
||||
@@ -119,14 +65,6 @@ func (m *Manager) AddTrayMenu(trayMenu *menu.TrayMenu) (string, error) {
|
||||
return newTrayMenu.AsJSON()
|
||||
}
|
||||
|
||||
func (m *Manager) GetTrayID(trayMenu *menu.TrayMenu) (string, error) {
|
||||
trayID, exists := m.trayMenuPointers[trayMenu]
|
||||
if !exists {
|
||||
return "", fmt.Errorf("Unable to find menu ID for tray menu!")
|
||||
}
|
||||
return trayID, nil
|
||||
}
|
||||
|
||||
// SetTrayMenu updates or creates a menu
|
||||
func (m *Manager) SetTrayMenu(trayMenu *menu.TrayMenu) (string, error) {
|
||||
trayID, trayMenuKnown := m.trayMenuPointers[trayMenu]
|
||||
@@ -164,39 +102,13 @@ func (m *Manager) UpdateTrayMenuLabel(trayMenu *menu.TrayMenu) (string, error) {
|
||||
}
|
||||
|
||||
type LabelUpdate struct {
|
||||
ID string
|
||||
Label string `json:",omitempty"`
|
||||
FontName string `json:",omitempty"`
|
||||
FontSize int
|
||||
RGBA string `json:",omitempty"`
|
||||
Disabled bool
|
||||
Tooltip string `json:",omitempty"`
|
||||
Image string `json:",omitempty"`
|
||||
MacTemplateImage bool
|
||||
StyledLabel []*ansi.StyledText `json:",omitempty"`
|
||||
}
|
||||
|
||||
// Parse ANSI text
|
||||
var styledLabel []*ansi.StyledText
|
||||
tempLabel := trayMenu.Label
|
||||
if strings.Contains(tempLabel, "\033[") {
|
||||
parsedLabel, err := ansi.Parse(tempLabel)
|
||||
if err == nil {
|
||||
styledLabel = parsedLabel
|
||||
}
|
||||
ID string
|
||||
Label string
|
||||
}
|
||||
|
||||
update := &LabelUpdate{
|
||||
ID: trayID,
|
||||
Label: trayMenu.Label,
|
||||
FontName: trayMenu.FontName,
|
||||
FontSize: trayMenu.FontSize,
|
||||
Disabled: trayMenu.Disabled,
|
||||
Tooltip: trayMenu.Tooltip,
|
||||
Image: trayMenu.Image,
|
||||
MacTemplateImage: trayMenu.MacTemplateImage,
|
||||
RGBA: trayMenu.RGBA,
|
||||
StyledLabel: styledLabel,
|
||||
ID: trayID,
|
||||
Label: trayMenu.Label,
|
||||
}
|
||||
|
||||
data, err := json.Marshal(update)
|
||||
|
||||
@@ -37,7 +37,6 @@ type Client interface {
|
||||
SetTrayMenu(trayMenuJSON string)
|
||||
UpdateTrayMenuLabel(JSON string)
|
||||
UpdateContextMenu(contextMenuJSON string)
|
||||
DeleteTrayMenuByID(id string)
|
||||
}
|
||||
|
||||
// DispatchClient is what the frontends use to interface with the
|
||||
|
||||
@@ -32,14 +32,6 @@ func menuMessageParser(message string) (*parsedMessage, error) {
|
||||
callbackid := message[2:]
|
||||
topic = "menu:clicked"
|
||||
data = callbackid
|
||||
case 'o':
|
||||
callbackid := message[2:]
|
||||
topic = "menu:ontrayopen"
|
||||
data = callbackid
|
||||
case 'c':
|
||||
callbackid := message[2:]
|
||||
topic = "menu:ontrayclose"
|
||||
data = callbackid
|
||||
default:
|
||||
return nil, fmt.Errorf("invalid menu message: %s", message)
|
||||
}
|
||||
|
||||
@@ -21,14 +21,13 @@ var messageParsers = map[byte]func(string) (*parsedMessage, error){
|
||||
'M': menuMessageParser,
|
||||
'T': trayMessageParser,
|
||||
'X': contextMenusMessageParser,
|
||||
'U': urlMessageParser,
|
||||
}
|
||||
|
||||
// Parse will attempt to parse the given message
|
||||
func Parse(message string) (*parsedMessage, error) {
|
||||
|
||||
if len(message) == 0 {
|
||||
return nil, fmt.Errorf("MessageParser received blank message")
|
||||
return nil, fmt.Errorf("MessageParser received blank message");
|
||||
}
|
||||
|
||||
parseMethod := messageParsers[message[0]]
|
||||
|
||||
@@ -40,8 +40,7 @@ func systemMessageParser(message string) (*parsedMessage, error) {
|
||||
// This is our startup hook - the frontend is now ready
|
||||
case 'S':
|
||||
topic := "hooks:startup"
|
||||
startupURL := message[1:]
|
||||
responseMessage = &parsedMessage{Topic: topic, Data: startupURL}
|
||||
responseMessage = &parsedMessage{Topic: topic, Data: nil}
|
||||
default:
|
||||
return nil, fmt.Errorf("Invalid message to systemMessageParser()")
|
||||
}
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
package message
|
||||
|
||||
import "fmt"
|
||||
|
||||
// urlMessageParser does what it says on the tin!
|
||||
func urlMessageParser(message string) (*parsedMessage, error) {
|
||||
|
||||
// Sanity check: URL messages must be at least 2 bytes
|
||||
if len(message) < 2 {
|
||||
return nil, fmt.Errorf("log message was an invalid length")
|
||||
}
|
||||
|
||||
// Switch on the log type
|
||||
switch message[1] {
|
||||
case 'C':
|
||||
return &parsedMessage{Topic: "url:handler", Data: message[2:]}, nil
|
||||
default:
|
||||
return nil, fmt.Errorf("url message type '%c' invalid", message[1])
|
||||
}
|
||||
}
|
||||
@@ -527,17 +527,6 @@ func (d *Dispatcher) processMenuMessage(result *servicebus.Message) {
|
||||
for _, client := range d.clients {
|
||||
client.frontend.UpdateTrayMenuLabel(updatedTrayMenuLabel)
|
||||
}
|
||||
case "deletetraymenu":
|
||||
traymenuid, ok := result.Data().(string)
|
||||
if !ok {
|
||||
d.logger.Error("Invalid data for 'menufrontend:updatetraymenulabel' : %#v",
|
||||
result.Data())
|
||||
return
|
||||
}
|
||||
|
||||
for _, client := range d.clients {
|
||||
client.frontend.DeleteTrayMenuByID(traymenuid)
|
||||
}
|
||||
|
||||
default:
|
||||
d.logger.Error("Unknown menufrontend command: %s", command)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package process
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"os/exec"
|
||||
|
||||
@@ -9,18 +10,16 @@ import (
|
||||
|
||||
// Process defines a process that can be executed
|
||||
type Process struct {
|
||||
logger *clilogger.CLILogger
|
||||
cmd *exec.Cmd
|
||||
exitChannel chan bool
|
||||
Running bool
|
||||
logger *clilogger.CLILogger
|
||||
cmd *exec.Cmd
|
||||
running bool
|
||||
}
|
||||
|
||||
// NewProcess creates a new process struct
|
||||
func NewProcess(logger *clilogger.CLILogger, cmd string, args ...string) *Process {
|
||||
func NewProcess(ctx context.Context, logger *clilogger.CLILogger, cmd string, args ...string) *Process {
|
||||
result := &Process{
|
||||
logger: logger,
|
||||
cmd: exec.Command(cmd, args...),
|
||||
exitChannel: make(chan bool, 1),
|
||||
logger: logger,
|
||||
cmd: exec.CommandContext(ctx, cmd, args...),
|
||||
}
|
||||
result.cmd.Stdout = os.Stdout
|
||||
result.cmd.Stderr = os.Stderr
|
||||
@@ -35,37 +34,30 @@ func (p *Process) Start() error {
|
||||
return err
|
||||
}
|
||||
|
||||
p.Running = true
|
||||
p.running = true
|
||||
|
||||
go func(cmd *exec.Cmd, running *bool, logger *clilogger.CLILogger, exitChannel chan bool) {
|
||||
logger.Println("Starting process (PID: %d)", cmd.Process.Pid)
|
||||
_ = cmd.Wait()
|
||||
logger.Println("Exiting process (PID: %d)", cmd.Process.Pid)
|
||||
*running = false
|
||||
exitChannel <- true
|
||||
}(p.cmd, &p.Running, p.logger, p.exitChannel)
|
||||
go func(p *Process) {
|
||||
p.logger.Println("Starting process (PID: %d)", p.cmd.Process.Pid)
|
||||
err := p.cmd.Wait()
|
||||
if err != nil {
|
||||
p.running = false
|
||||
p.logger.Println("Process failed to run: %s", err.Error())
|
||||
return
|
||||
}
|
||||
p.logger.Println("Exiting process (PID: %d)", p.cmd.Process.Pid)
|
||||
}(p)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Kill the process
|
||||
func (p *Process) Kill() error {
|
||||
if !p.Running {
|
||||
return nil
|
||||
if p.running {
|
||||
println("Calling kill")
|
||||
p.running = false
|
||||
return p.cmd.Process.Kill()
|
||||
}
|
||||
err := p.cmd.Process.Kill()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = p.cmd.Process.Release()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Wait for command to exit properly
|
||||
<-p.exitChannel
|
||||
|
||||
return err
|
||||
return nil
|
||||
}
|
||||
|
||||
// PID returns the process PID
|
||||
|
||||
@@ -25,8 +25,8 @@ type Project struct {
|
||||
// The path to the project directory
|
||||
Path string
|
||||
|
||||
// Build directory
|
||||
BuildDir string `json:"builddir"`
|
||||
// Assets directory
|
||||
AssetsDir string `json:"assetsdir"`
|
||||
|
||||
// The output filename
|
||||
OutputFilename string `json:"outputfilename"`
|
||||
@@ -76,8 +76,8 @@ func Load(projectPath string) (*Project, error) {
|
||||
}
|
||||
|
||||
// Set default assets directory if none given
|
||||
if result.BuildDir == "" {
|
||||
result.BuildDir = filepath.Join(result.Path, "build")
|
||||
if result.AssetsDir == "" {
|
||||
result.AssetsDir = filepath.Join(result.Path, "assets")
|
||||
}
|
||||
|
||||
// Fix up OutputFilename
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
package assets
|
||||
|
||||
import _ "embed"
|
||||
|
||||
//go:embed desktop_darwin.js
|
||||
var desktopDarwinJS string
|
||||
|
||||
//go:embed desktop_windows.js
|
||||
var desktopWindowsJS string
|
||||
|
||||
//go:embed wails.js
|
||||
var wailsJS string
|
||||
File diff suppressed because one or more lines are too long
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user