diff --git a/.air.toml b/.air.toml index c46b56f..d7ff5c6 100644 --- a/.air.toml +++ b/.air.toml @@ -5,7 +5,7 @@ tmp_dir = "tmp" [build] args_bin = [] bin = "tmp\\main.exe" - cmd = "go build -ldflags='-H windowsgui' -o ./tmp/main.exe ./example" + cmd = "go build -o ./tmp/main.exe ./example" delay = 1000 exclude_dir = ["assets", "tmp", "vendor", "testdata", "runtime/out", "node_modules", "dist"] exclude_file = [] diff --git a/example/index.html b/example/index.html index af6d2e8..f686e39 100644 --- a/example/index.html +++ b/example/index.html @@ -4,7 +4,8 @@ - + + \ No newline at end of file diff --git a/go.mod b/go.mod index ccab6aa..2d18392 100644 --- a/go.mod +++ b/go.mod @@ -2,4 +2,18 @@ module git.iwakura.rip/grng/tiramisu go 1.23.4 -require github.com/webview/webview_go v0.0.0-20240831120633-6173450d4dd6 // indirect +require ( + git.sr.ht/~jackmordaunt/go-toast v1.1.2 // indirect + github.com/electricbubble/go-toast v0.3.0 // indirect + github.com/esiqveland/notify v0.13.3 // indirect + github.com/gen2brain/beeep v0.11.1 // indirect + github.com/go-ole/go-ole v1.3.0 // indirect + github.com/godbus/dbus/v5 v5.1.0 // indirect + github.com/jackmordaunt/icns/v3 v3.0.1 // indirect + github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 // indirect + github.com/sergeymakinen/go-bmp v1.0.0 // indirect + github.com/sergeymakinen/go-ico v1.0.0-beta.0 // indirect + github.com/tadvi/systray v0.0.0-20190226123456-11a2b8fa57af // indirect + github.com/webview/webview_go v0.0.0-20240831120633-6173450d4dd6 // indirect + golang.org/x/sys v0.33.0 // indirect +) diff --git a/go.sum b/go.sum index 2421e19..1f8ff6c 100644 --- a/go.sum +++ b/go.sum @@ -1,2 +1,39 @@ +git.sr.ht/~jackmordaunt/go-toast v1.1.2 h1:/yrfI55LRt1M7H1vkaw+NaH1+L1CDxrqDltwm5euVuE= +git.sr.ht/~jackmordaunt/go-toast v1.1.2/go.mod h1:jA4OqHKTQ4AFBdwrSnwnskUIIS3HYzlJSgdzCKqfavo= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/electricbubble/go-toast v0.3.0 h1:e/PtFkpYNdLANI/utTXzoYQuMYG/N8oHT0Rwal4z/sI= +github.com/electricbubble/go-toast v0.3.0/go.mod h1:6k4ufXmV/AS32EugdLeIXa+2jv6oYbWzv2+UZhB49FI= +github.com/esiqveland/notify v0.13.3 h1:QCMw6o1n+6rl+oLUfg8P1IIDSFsDEb2WlXvVvIJbI/o= +github.com/esiqveland/notify v0.13.3/go.mod h1:hesw/IRYTO0x99u1JPweAl4+5mwXJibQVUcP0Iu5ORE= +github.com/gen2brain/beeep v0.11.1 h1:EbSIhrQZFDj1K2fzlMpAYlFOzV8YuNe721A58XcCTYI= +github.com/gen2brain/beeep v0.11.1/go.mod h1:jQVvuwnLuwOcdctHn/uyh8horSBNJ8uGb9Cn2W4tvoc= +github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= +github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= +github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= +github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/jackmordaunt/icns/v3 v3.0.1 h1:xxot6aNuGrU+lNgxz5I5H0qSeCjNKp8uTXB1j8D4S3o= +github.com/jackmordaunt/icns/v3 v3.0.1/go.mod h1:5sHL59nqTd2ynTnowxB/MDQFhKNqkK8X687uKNygaSQ= +github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ= +github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/sergeymakinen/go-bmp v1.0.0 h1:SdGTzp9WvCV0A1V0mBeaS7kQAwNLdVJbmHlqNWq0R+M= +github.com/sergeymakinen/go-bmp v1.0.0/go.mod h1:/mxlAQZRLxSvJFNIEGGLBE/m40f3ZnUifpgVDlcUIEY= +github.com/sergeymakinen/go-ico v1.0.0-beta.0 h1:m5qKH7uPKLdrygMWxbamVn+tl2HfiA3K6MFJw4GfZvQ= +github.com/sergeymakinen/go-ico v1.0.0-beta.0/go.mod h1:wQ47mTczswBO5F0NoDt7O0IXgnV4Xy3ojrroMQzyhUk= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/tadvi/systray v0.0.0-20190226123456-11a2b8fa57af h1:6yITBqGTE2lEeTPG04SN9W+iWHCRyHqlVYILiSXziwk= +github.com/tadvi/systray v0.0.0-20190226123456-11a2b8fa57af/go.mod h1:4F09kP5F+am0jAwlQLddpoMDM+iewkxxt6nxUQ5nq5o= github.com/webview/webview_go v0.0.0-20240831120633-6173450d4dd6 h1:VQpB2SpK88C6B5lPHTuSZKb2Qee1QWwiFlC5CKY4AW0= github.com/webview/webview_go v0.0.0-20240831120633-6173450d4dd6/go.mod h1:yE65LFCeWf4kyWD5re+h4XNvOHJEXOCOuJZ4v8l5sgk= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= +golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/runtime/index.d.ts b/runtime/index.d.ts index a6fe80f..5193826 100644 --- a/runtime/index.d.ts +++ b/runtime/index.d.ts @@ -1,14 +1,25 @@ declare global { interface Window { + // internal __TIRAMISU_INTERNAL_invoke: (name: string, ...args: any[]) => Promise; - __TIRAMISU_INTERNAL_readFile: (path: string) => Promise; - __TIRAMISU_INTERNAL_readDir: (path: string) => Promise; + + // filesystem + __TIRAMISU_FILESYSTEM_readFile: (path: string) => Promise; + __TIRAMISU_FILESYSTEM_readDir: (path: string) => Promise; + __TIRAMISU_INTERNAL_exists: (path: string) => Promise; + + // notifications + __TIRAMISU_NOTIFICATIONS_notify: (message: string, ico?: string) => Promise; tiramisu: { invoke: (name: string, ...args: any[]) => Promise; fs: { readFile(path: string): Promise; readDir(path: string): Promise; + exists(path: string): Promise; + }, + notifications: { + notify(message: string, ico?: string): Promise; } }; } diff --git a/runtime/preload.ts b/runtime/preload.ts index ec9dad9..dd3e5be 100644 --- a/runtime/preload.ts +++ b/runtime/preload.ts @@ -1,9 +1,13 @@ const tiramisu = { invoke: window.__TIRAMISU_INTERNAL_invoke, fs: { - readFile: window.__TIRAMISU_INTERNAL_readFile, - readDir: window.__TIRAMISU_INTERNAL_readDir - } + readFile: window.__TIRAMISU_FILESYSTEM_readFile, + readDir: window.__TIRAMISU_FILESYSTEM_readDir, + exists: window.__TIRAMISU_INTERNAL_exists, + }, + notifications: { + notify: window.__TIRAMISU_NOTIFICATIONS_notify, + }, } window.tiramisu = tiramisu \ No newline at end of file diff --git a/tiramisu.go b/tiramisu.go index a2c0173..3292cc5 100644 --- a/tiramisu.go +++ b/tiramisu.go @@ -2,9 +2,11 @@ package tiramisu import ( "embed" + "encoding/json" "fmt" "os" + toast "github.com/electricbubble/go-toast" wv "github.com/webview/webview_go" ) @@ -19,6 +21,7 @@ type TiramisuOptions struct { type FuncHandler func(args ...any) (any, error) type Tiramisu struct { + o TiramisuOptions w wv.WebView funcs map[string]FuncHandler } @@ -29,6 +32,7 @@ func New(o TiramisuOptions) *Tiramisu { w.SetTitle(o.Title) t := &Tiramisu{ + o: o, w: w, funcs: make(map[string]FuncHandler), } @@ -104,27 +108,25 @@ func (t *Tiramisu) loadJSRuntime() { } func (t *Tiramisu) loadGoRuntime() { + // internal t.bind("__TIRAMISU_INTERNAL_invoke", func(args ...any) (any, error) { - name, ok := args[0].(string) - if !ok { - return nil, fmt.Errorf("first argument must be a string, got %T", args[0]) + name, err := ArgAs[string](args, 0) + if err != nil { + return nil, err } + // pass through any extra args if len(args) == 1 { return t.invoke(name) } return t.invoke(name, args[1:]...) }) - t.bind("__TIRAMISU_INTERNAL_readFile", func(args ...any) (any, error) { - if len(args) != 1 { - return nil, fmt.Errorf("readFile expects exactly one argument, got %d", len(args)) + // filesystem + t.bind("__TIRAMISU_FILESYSTEM_readFile", func(args ...any) (any, error) { + filename, err := ArgAs[string](args, 0) + if err != nil { + return nil, err } - - filename, ok := args[0].(string) - if !ok { - return nil, fmt.Errorf("readFile expects a string argument, got %T", args[0]) - } - data, err := os.ReadFile(filename) if err != nil { return nil, fmt.Errorf("error reading file %s: %w", filename, err) @@ -132,25 +134,76 @@ func (t *Tiramisu) loadGoRuntime() { return string(data), nil }) - t.bind("__TIRAMISU_INTERNAL_readDir", func(args ...any) (any, error) { - if len(args) != 1 { - return nil, fmt.Errorf("readDir expects exactly one argument, got %d", len(args)) + t.bind("__TIRAMISU_FILESYSTEM_readDir", func(args ...any) (any, error) { + dirname, err := ArgAs[string](args, 0) + if err != nil { + return nil, err } - - dirname, ok := args[0].(string) - if !ok { - return nil, fmt.Errorf("readDir expects a string argument, got %T", args[0]) - } - - files, err := os.ReadDir(dirname) + entries, err := os.ReadDir(dirname) if err != nil { return nil, fmt.Errorf("error reading directory %s: %w", dirname, err) } - - var fileNames []string - for _, file := range files { - fileNames = append(fileNames, file.Name()) + var names []string + for _, e := range entries { + names = append(names, e.Name()) } - return fileNames, nil + return names, nil + }) + + t.bind("__TIRAMISU_INTERNAL_exists", func(args ...any) (any, error) { + path, err := ArgAs[string](args, 0) + if err != nil { + return nil, err + } + _, err = os.Stat(path) + if err != nil { + if os.IsNotExist(err) { + return false, nil + } + return nil, fmt.Errorf("error checking existence of %s: %w", path, err) + } + return true, nil + }) + + // notifications + t.bind("__TIRAMISU_NOTIFICATIONS_notify", func(args ...any) (any, error) { + msg, err := ArgAs[string](args, 0) + if err != nil { + return nil, err + } + if err := toast.Push(msg); err != nil { + return nil, fmt.Errorf("error sending notification: %w", err) + } + + return nil, nil }) } + +func Arg(args []any, i int) (any, error) { + if i < 0 || i >= len(args) { + return nil, fmt.Errorf("arg %d out of range [0..%d]", i, len(args)-1) + } + return args[i], nil +} + +func ArgAs[T any](args []any, i int) (T, error) { + var t T + + arg, err := Arg(args, i) + if err != nil { + return t, err + } + + if v, ok := arg.(T); ok { + return v, nil + } + + j, err := json.Marshal(arg) + if err != nil { + return t, fmt.Errorf("cannot marshal arg[%d]: %w", i, err) + } + if err := json.Unmarshal(j, &t); err != nil { + return t, fmt.Errorf("cannot unmarshal arg[%d] -> %T: %w", i, t, err) + } + return t, nil +}