Standard library · Web and browser

gui/webview

A native window around HTML, and a whole desktop application layer on top of it.

import gui.webview

This package opens a native desktop window around HTML, and builds a whole desktop application on top of it, the kind you would build with Tauri or Electron, with no Rust and no second language. There are two levels in one package. The low level is a small window: you create it, show a page with SetHTML or Navigate, and Run keeps it open until the user closes it. The application level starts with Init and adds commands the page can call, events in both directions, packaged files, dialogs, notifications, the keychain, tray icons, menus, a single-instance lock and installers made by salam bundle.

The interface is HTML, CSS and JavaScript. By default it runs in the web view the operating system already has, like Tauri: WebView2 on Windows, WKWebView on macOS and WebKitGTK on Linux, so the program stays one small native executable. The engine is chosen when the program is built; see choosing the web engine for the Chromium option.

A page calls your Salam code with salam.invoke, which returns a promise. Pages can come from three places: files packed inside the executable, so nothing sits in a folder for users to edit; a remote site; or an HTML string. Use it for a small desktop tool, a viewer for data your program computed, or a native window around a web interface your program serves. For pages that run in the user's own browser, see dom; to generate the HTML, see template.

How data moves, and what does not work

  • Put the data in the page. The reliable way to give the page your data is to write it into the HTML you pass to SetHTML, as JSON inside a <script>.
  • Init installs a script for every page loaded later, before the page's own scripts run. Eval runs once, on whatever page is loaded at that moment.
  • Pages load in the background, so code sent with Eval right after SetHTML or Navigate can run before the new page exists. Use Init, or wait for OnLoad.
  • The page can talk back with OnMessage. For a whole application, with commands, events, packaged files and installers, use the application functions such as Init, Command and Open, described in the guides further down.
  • Create reports failure by returning a window that is already closed; check IsClosed() right after it, for example on a Linux server with no display.

What pages may do

  • Local pages may run every command. Remote sites get nothing until you name them with Allow, command by command if you like.
  • The bridge exists only in the top frame of an allowed origin, and every message carries a secret made for this run, so a frame or another site cannot call you. Answers carry the origin they belong to, so they never reach a page that replaced it.
  • A link to any other site opens in the system browser. On Linux the engine cannot tell a frame from the page, so a cross-origin frame there needs its origin in allow_navigation.
  • Files inside the executable are compressed and scrambled, which keeps them out of sight. They are not secret: anything a page can read, a determined user can extract. Keep real secrets on your server.

A window needs a desktop session, so the site only compiles these programs; run them on your own machine with salam run. On Linux install libgtk-3-dev and libwebkit2gtk-4.1-dev (WebKitGTK 4.1) first. Windows 10 and 11 already ship WebView2, and macOS ships WKWebView. Building for another system needs nothing extra: --target works from any machine.

Showing data your program computed

The simplest desktop program builds an HTML string and shows it. Everything the page needs, the style sheet included, can travel in that one string, so there are no files to ship next to the executable. HINT_FIXED keeps the window at the size you chose, which suits a small dialog like this order summary.

gui-webview-guide-report.salam
import gui.webview
import str

struct Item:
    pub name: str = ""
    pub qty: int = 0
    pub price: int = 0
end

func page(items: Vector<Item>): str:
    mut rows := ""
    mut total := 0
    each it in items:
        line := it.qty * it.price
        total += line
        rows = rows + "<tr><td>" + it.name + "</td><td>" + str.FromInt(it.qty) + "</td><td>" + str.FromInt(line) + "</td></tr>"
    end
    css := "body{font-family:sans-serif;margin:2em}td,th{padding:4px 12px;text-align:left}"
    ret "<style>" + css + "</style><h1>Order</h1><table><tr><th>Item</th><th>Qty</th><th>Sum</th></tr>" + rows + "</table><p>Total: " + str.FromInt(total) + "</p>"
end

func main:
    mut items := Vector {} as Vector<Item>
    items.push(Item { name = "Tea", qty = 2, price = 45 })
    items.push(Item { name = "Cake", qty = 1, price = 120 })
    mut win := webview.Create()
    win.SetTitle("Order")
    win.SetSize(480, 320, webview.HINT_FIXED)
    win.SetHTML(page(items))
    win.Run()
    win.Destroy()
end

The item names go into the HTML as they are. When the text comes from a user or a file, pass it through EscapeHTML from template first, or a name with a < in it breaks the page.

Handing structured data to the page

For anything bigger than a table, let JavaScript draw the page and give it the data as JSON. Marshal from encoding/json turns a vector of structs into a JSON array whose keys are the field names, and that text goes straight into a <script> in the page. The script runs as the page loads, so the data is there before anything is drawn.

gui-webview-guide-data.salam
import gui.webview
import encoding.json

struct Task:
    pub title: str = ""
    pub done: bool = false
end

func main:
    mut tasks := Vector {} as Vector<Task>
    tasks.push(Task { title = "Write the report", done = true })
    tasks.push(Task { title = "Send it to Sara" })
    tasks.push(Task { title = "Book the meeting room" })
    page := `<h2 id="count"></h2>
<ul id="list"></ul>
<script>
const tasks = ` + json.Marshal(tasks) + `;
const open = tasks.filter(t => !t.done).length;
document.getElementById("count").textContent = open + " of " + tasks.length + " still open";
for (const t of tasks) {
  const li = document.createElement("li");
  li.textContent = (t.done ? "[x] " : "[ ] ") + t.title;
  document.getElementById("list").appendChild(li);
}
</script>`
    mut win := webview.Create(false)
    win.SetTitle("Tasks")
    win.SetSize(400, 300, webview.HINT_NONE)
    win.SetHTML(page)
    win.Run()
    win.Destroy()
end

JSON is valid JavaScript, so no parsing step is needed. One caution: if a value could contain the text </script>, it would end the script early; replace </ with <\/ in the JSON first when the data comes from users.

Opening a site or a local server

Navigate loads an address instead of a string, so the window can show a remote site or a web interface your own program serves on 127.0.0.1. This one takes the address from the command line, so salam run webview-guide-launch.salam http://127.0.0.1:8000 wraps a local development server in a native window.

gui-webview-guide-launch.salam
import gui.webview
import os

func main:
    args := os.Args()
    mut url := "https://salamlang.github.io/Salam/"
    if args.len() > 1:
        url = args.get(1)
    end
    mut win := webview.Create()
    win.SetTitle(url)
    win.SetSize(1280, 800, webview.HINT_NONE)
    win.Navigate(url)
    win.Run()
    if win.IsClosed():
        println "window closed"
    end
    win.Destroy()
end

Run blocks until the user closes the window; the lines after it are where you save state and clean up. Call Destroy once at the end in every case. Terminate makes Run return at its next turn; on Linux and Windows that turn only comes with the next window event, such as a mouse move. On macOS it quits the whole application instead, so the lines after Run do not run there.

An application with a page and a command

While developing, assets_dir serves your folder directly, so a reload shows your edits. Without it the files inside the executable are used.

gui-webview-guide-start.salam
import gui.webview

func main:
    webview.Init(webview.Config { id = "com.example.notes", name = "Notes", assets_dir = "www" })
    webview.Command("greet", (c: webview.Call):
        c.ResolveStr("Hello " + c.Str("name"))
    end)
    webview.On("ready", (_data: str):
        webview.Emit("saved", `{"count":1}`)
    end)
    win := webview.Open(webview.WindowOptions { title = "Notes", width = 900, height = 600 })
    if win.IsClosed():
        println "could not open a window"
        ret 1
    end
    webview.Run()
end

The page calls it like any other async function:

const text = await salam.invoke("greet", { name: "Sara" });
const stop = salam.listen("saved", (data) => console.log(data));
salam.emit("ready", {});

A command normally runs on the UI thread, so keep it short. Register slow work with CommandAsync; the page stays responsive and Resolve is safe from the worker.

Commands, errors and slow work

Arguments arrive as JSON. Str, Int and Bool read one by name and Has tells you whether the page sent it. Finish every call exactly once: with one Resolve method, or with Reject, which makes the page's promise throw an error carrying your message. A second answer is ignored.

A Command runs on the UI thread, so a slow one freezes the window. Register it with CommandAsync and it runs on a worker, where Resolve is safe to call later. Every window call belongs on the UI thread, so a worker hands such work to Dispatch.

gui-webview-guide-commands.salam
import gui.webview
import encoding.json
import sync

func main:
    webview.Init(webview.Config { id = "com.example.notes", name = "Notes", assets_dir = "www" })
    webview.Command("add", (c: webview.Call):
        if not c.Has("a") or not c.Has("b"):
            c.Reject("a and b are required")
            ret
        end
        c.ResolveInt(c.Int("a") + c.Int("b"))
    end)
    webview.CommandAsync("report", (c: webview.Call):
        name := c.Str("name")
        sync.SleepMs(1500)
        c.Resolve(`{"name":` + `"` + json.Escape(name) + `"` + `,"pages":12}`)
    end)
    webview.On("save", (data: str):
        webview.Dispatch(():
            webview.Emit("saved", data)
        end)
    end)
    win := webview.Open(webview.WindowOptions { title = "Notes", width = 900, height = 600 })
    if win.IsClosed():
        println webview.LastError()
        ret 1
    end
    webview.Run()
end
try {
  const sum = await salam.invoke("add", { a: 40, b: 2 });
  const report = await salam.invoke("report", { name: "March" });
} catch (e) {
  console.log(e.message);
}

Windows, events and menus

Open can be called many times. Give each window a label and find it again with GetWindow. Emit on a window reaches only that window, and desktop.Emit reaches all of them. OnClose runs when the user closes a window: return false and the window stays, which is the place to ask about unsaved work.

gui-webview-guide-windows.salam
import gui.webview

func main:
    webview.Init(webview.Config { id = "com.example.notes", name = "Notes", assets_dir = "www" })
    main_win := webview.Open(webview.WindowOptions { label = "main", title = "Notes", width = 900, height = 600, min_width = 480, min_height = 320 })
    main_win.OnClose(():
        ret webview.Confirm("Notes", "Close the notes window?")
    end)
    main_win.OnResize((w: int, h: int):
        println "size", w, h
    end)
    webview.Command("open_help", (c: webview.Call):
        help := webview.Open(webview.WindowOptions { label = "help", title = "Help", width = 520, height = 420, html = "<h1>Help</h1>" })
        help.Emit("hello", `{"from":"main"}`)
        c.ResolveNull()
    end)
    webview.OnMenu((id: str):
        if id == "help":
            webview.GetWindow("main").Emit("menu", `"help"`)
        end
        if id == "quit":
            webview.Quit()
        end
    end)
    mut items := Vector {} as Vector<webview.MenuItem>
    items.push(webview.Item("help", "Help"))
    items.push(webview.Separator())
    items.push(webview.Item("quit", "Quit"))
    webview.SetMenu(main_win, items)
    webview.Run()
end

A menu is a list of items made with Item, Separator and Submenu. Every click arrives in OnMenu with the item's id, whichever menu it came from.

Dialogs, notifications, secrets and the tray

These calls use whatever the system provides, so the same program behaves natively on each system. File dialogs return an empty string when the user cancels. SecretSet and SecretGet keep a small value, such as a login token, in the keychain instead of a file in your data folder. SingleInstance returns false when the program is already running, and the running copy gets the new arguments in its callback.

gui-webview-guide-services.salam
import gui.webview

func main:
    webview.Init(webview.Config { id = "com.example.notes", name = "Notes", assets_dir = "www" })
    win := webview.Open(webview.WindowOptions { title = "Notes" })
    keep := webview.SingleInstance((args: Vector<str>):
        println "second start with", args.len(), "arguments"
        webview.GetWindow("main").Focus()
    end)
    if not keep:
        ret 0
    end
    webview.Command("choose_folder", (c: webview.Call):
        c.ResolveStr(webview.PickFolder("Choose a folder"))
    end)
    webview.Command("sign_in", (c: webview.Call):
        ok := webview.SecretSet("token", c.Str("token"))
        c.ResolveBool(ok)
    end)
    webview.Command("token", (c: webview.Call):
        c.ResolveStr(webview.SecretGet("token"))
    end)
    webview.Command("done", (c: webview.Call):
        webview.Notify("Notes", "Your export is ready")
        webview.RequestAttention(win)
        c.ResolveNull()
    end)
    webview.OnTrayClick(():
        win.Show()
        win.Focus()
    end)
    mut tray := Vector {} as Vector<webview.MenuItem>
    tray.push(webview.Item("quit", "Quit"))
    webview.SetTray("icon.png", "Notes", tray)
    webview.Run()
end

Keep in mind that a page can call a command only if it is allowed to. Local pages may call all of them. If you load a remote site, list the dangerous commands, such as choose_folder above, only for origins you trust.

A window around a website

Give url an address to wrap a site you already run, and use Allow to give that one site access to chosen commands. Your local error page can stay in the assets.

gui-webview-guide-remote.salam
import gui.webview

func main:
    webview.Init(webview.Config { id = "com.example.portal", name = "Portal" })
    mut allowed := Vector {} as Vector<str>
    allowed.push("pick_folder")
    webview.Allow("https://app.example.com", allowed)
    webview.Command("pick_folder", (c: webview.Call):
        c.ResolveStr(webview.PickFolder("Choose a folder"))
    end)
    win := webview.Open(webview.WindowOptions { title = "Portal", url = "https://app.example.com/" })
    win.OnCrash((): win.Navigate(webview.AssetURL("/error.html")) end)
    webview.Run()
end

Making installers

Describe the application in salam.toml and run salam bundle. The same command works from any system with --target, and the assets folder goes inside the application.

[app]
id = "com.example.notes"
name = "Notes"
version = "1.0.0"
assets = "www"
icon = "icon.png"
description = "Take notes"

$ salam bundle
$ salam bundle --target=x86_64-w64-windows-gnu
$ salam bundle --target=arm64-apple-macos

On Linux you get a tar archive and a deb package, on Windows a zip with a windowed executable, on macOS an .app. --format also offers rpm, AppImage, NSIS and dmg, which need rpmbuild, appimagetool, makensis and hdiutil. Code signing is up to you.

When something does not work

  • If Open gives a closed window, LastError says why. A missing WebKitGTK or WebView2 runtime shows up here.
  • A window that opens blank: set SALAM_WEBVIEW_TRACE to a file name and run again. Each line is one step: how the engine started, each navigation, resource and message, and every message the bridge refused. It also works in a program with no console.
  • Set debug = true in Config and call OpenDevTools on the window to use the developer tools of the engine.
  • A command that never answers leaves the page's promise pending forever. Make sure every path ends in a Resolve method or Reject.

Choosing the web engine

The engine is chosen when your program is built, not when it runs. By default a program uses the web view of the operating system: WebView2 on Windows, WKWebView on macOS and WebKitGTK on Linux. That is what keeps the executable small, and it is the engine every guide above assumes. Because the web view comes with the system, the same page can look slightly different from one system to another.

For the same engine everywhere you can build against Chromium Embedded Framework (CEF) instead. Build the program with the -DSALAM_WEBVIEW_CEF flag, or let salam bundle do it: set engine = "chromium" and runtime = "dir" in the [app] table of salam.toml, or pass --engine=chromium and --runtime=DIR (the SALAM_CEF_DIR environment variable works too). The runtime folder holds the built salam_webview_cef library, salam_cef_helper and the CEF runtime files (see std/gui/webview/native/CMakeLists.txt in the Salam repository for how to build them). It is copied next to the program, and only for tar on Linux and zip on Windows.

A program can ask which engine it got. Engine returns ENGINE_SYSTEM ("system") or ENGINE_CHROMIUM ("chromium"), EngineName returns a name for people to read (WebKitGTK, WebView2, WKWebView or Chromium Embedded Framework), and SupportsCommands says whether commands work.

gui-webview-guide-engine.salam
import gui.webview

func main:
    println "Engine:", webview.Engine()
    println "Name:", webview.EngineName()
    if webview.Engine() == webview.ENGINE_CHROMIUM:
        println "Chromium Embedded Framework"
    end
    if not webview.SupportsCommands():
        println "No commands or events with this engine yet"
    end
end

Be aware of the limits. With the Chromium engine today, windows open and pages load, but commands, events, custom schemes and the system services (dialogs, notifications, keychain, tray) are not available yet, and SupportsCommands() returns false. Full parity with the system engine is planned. If your program depends on salam.invoke, build it with the default engine for now, or check SupportsCommands() and fall back.

Types

Request

struct Request
A request a custom scheme received from the page.
NameTypeDescription
urlstr = ""The whole address.
schemestr = ""The scheme, such as app.
methodstr = "GET"The HTTP method, usually GET.
pathstr = "/"The path, starting with a slash.
querystr = ""The text after the question mark.
gui-webview-Window.salam
import gui.webview

func main:
    webview.SetApp("com.example.window", "Window")
    webview.RegisterScheme("app", (r: webview.Request):
        body := "<h1>" + r.path + "</h1>"
        mut res := webview.Response { mime = "text/html; charset=utf-8", data = body as void*, size = body.len() as i64 }
        res.Header("X-Example", r.method + r.query + r.url + r.scheme)
        ret res
    end)
    mut win := webview.Create(true, false)
    win.SetTitle("hello")
    win.SetSize(800, 600, webview.HINT_NONE)
    win.SetMinSize(400, 300)
    win.SetIcon("icon.png")
    win.SetContextMenu(false)
    win.Init("window.salam = { version: 1 };")
    win.OnMessage((text: str): println text end)
    win.OnNavigate((url: str): ret url != "https://blocked.example/" and webview.NavigationIsMainFrame() end)
    win.OnNewWindow((url: str): println url end)
    win.OnClose((): ret true end)
    win.OnLoad((url: str, done: bool): println url, done end)
    win.OnCrash((): println "crashed" end)
    win.OnResize((w: int, h: int): println w, h end)
    win.Center()
    win.Show()
    win.Focus()
    win.Hide()
    win.Minimize()
    win.Maximize()
    win.OpenDevTools()
    win.SetHTML("<h1>hello world</h1>")
    win.Eval("document.title = 'ready'")
    win.EvalLater("document.title = 'later'")
    println win.URL(), win.IsActive(), win.id, win.Native() != null, win.Content() != null
    webview.Dispatch((): println "on the UI thread" end)
    webview.OnCommand((id: int): println id end)
    webview.OnTrayMessage((event: int): println event end)
    webview.OnCopyData((_data: void*, size: int): println size end)
    webview.NameMessageWindow("example")
    println webview.MenuHost() != null, webview.SchemeOrigin("app"), webview.SchemeURL("app", "/x"), webview.PostScript()
    win.Run()
    webview.RunAll()
    webview.Quit()
    println webview.LastError()
    if not win.IsClosed():
        win.Terminate()
    end
    win.Destroy()
    println webview.HINT_FIXED
end

Response

struct Response
What a custom scheme answers with. The data is copied, so the handler keeps ownership.
NameTypeDescription
statusint = 200The HTTP status, 200 by default.
mimestr = "text/html"The content type.
datavoid*A pointer to the body.
sizei64 = 0The body size in bytes.
headersVectorExtra headers, as name and value pairs.

The example for this item is shown with the ones above: gui-webview-Window.salam

Header

func Header(name: str, value: str)
Adds one header.

The example for this item is shown with the ones above: gui-webview-Window.salam

WebView

struct WebView
A window with a browser inside it.
NameTypeDescription
idint = -1The number of this window inside the package.

The example for this item is shown with the ones above: gui-webview-Window.salam

Destroy

func Destroy()
Closes the window and frees the resources.

The example for this item is shown with the ones above: gui-webview-Window.salam

Run

func Run()
Runs the event loop until this window is closed or Terminate is called.

The example for this item is shown with the ones above: gui-webview-Window.salam

Terminate

func Terminate()
Asks Run to return. The window stays open until Destroy.

The example for this item is shown with the ones above: gui-webview-Window.salam

SetTitle

func SetTitle(title: str)
The window title.

The example for this item is shown with the ones above: gui-webview-Window.salam

SetSize

func SetSize(width: int, height: int, hints: int)
Resizes the window, with one of the HINT constants: HINT_FIXED stops the user from resizing it.

The example for this item is shown with the ones above: gui-webview-Window.salam

SetMinSize

func SetMinSize(width: int, height: int)
The smallest size the user can drag the window to.

The example for this item is shown with the ones above: gui-webview-Window.salam

SetIcon

func SetIcon(path: str)
The window icon, from a file: any image GTK reads on Linux, .ico on Windows, any image on macOS.

The example for this item is shown with the ones above: gui-webview-Window.salam

SetHTML

func SetHTML(html: str)
Shows the HTML content.

The example for this item is shown with the ones above: gui-webview-Window.salam

Navigate

func Navigate(url: str)
Opens the address.
gui-webview-Navigate.salam
import gui.webview

func main:
    mut win := webview.Create()
    win.SetTitle("the Salam language")
    win.SetSize(1024, 768, webview.HINT_FIXED)
    win.Navigate("https://salamlang.github.io/Salam/")
    win.Run()
    win.Destroy()
end

Eval

func Eval(js: str)
Runs JavaScript in the page loaded right now, without waiting for a result. Call it on the UI thread.

The example for this item is shown with the ones above: gui-webview-Window.salam

EvalLater

func EvalLater(js: str)
The same as Eval, but safe from any thread: the code runs on the UI thread soon after.

The example for this item is shown with the ones above: gui-webview-Window.salam

Init

func Init(js: str, all_frames: bool = false)
Installs JavaScript that runs at the start of every page loaded later, before the page's own scripts. Pass true to run it in frames too.

The example for this item is shown with the ones above: gui-webview-Window.salam

URL

func URL(): str
The address of the page now. The caller frees the string.

The example for this item is shown with the ones above: gui-webview-Window.salam

Show

func Show()
Shows the window and brings it forward.

The example for this item is shown with the ones above: gui-webview-Window.salam

Hide

func Hide()
Hides the window without closing it.

The example for this item is shown with the ones above: gui-webview-Window.salam

Focus

func Focus()
Brings the window forward.

The example for this item is shown with the ones above: gui-webview-Window.salam

Minimize

func Minimize()
Minimizes the window.

The example for this item is shown with the ones above: gui-webview-Window.salam

Maximize

func Maximize()
Maximizes the window.

The example for this item is shown with the ones above: gui-webview-Window.salam

Center

func Center()
Moves the window to the middle of the screen.

The example for this item is shown with the ones above: gui-webview-Window.salam

IsActive

func IsActive(): bool
The window has the keyboard focus.

The example for this item is shown with the ones above: gui-webview-Window.salam

Native

func Native(): void*
The window handle in the operating system: GtkWindow*, HWND or NSWindow*.

The example for this item is shown with the ones above: gui-webview-Window.salam

Content

func Content(): void*
Where native widgets go: a GtkBox on Linux, the window elsewhere.

The example for this item is shown with the ones above: gui-webview-Window.salam

OpenDevTools

func OpenDevTools()
Opens the developer tools.

The example for this item is shown with the ones above: gui-webview-Window.salam

SetContextMenu

func SetContextMenu(enabled: bool)
Turns the page's right-click menu on or off.

The example for this item is shown with the ones above: gui-webview-Window.salam

OnMessage

func OnMessage(handler: func(str))
Called with the text a page posts with the function from PostScript.

The example for this item is shown with the ones above: gui-webview-Window.salam

OnNavigate

func OnNavigate(handler: func(str): bool)
Called before a page loads. Return false to cancel it. On Linux frames come here too; see NavigationIsMainFrame.

The example for this item is shown with the ones above: gui-webview-Window.salam

OnNewWindow

func OnNewWindow(handler: func(str))
Called when a page asks for a new window. No window opens; the address is handed to you.

The example for this item is shown with the ones above: gui-webview-Window.salam

OnClose

func OnClose(handler: func(): bool)
Called when the user closes the window. Return false to keep it open.

The example for this item is shown with the ones above: gui-webview-Window.salam

OnLoad

func OnLoad(handler: func(str, bool))
Called when a page starts loading (false) and when it finishes (true).

The example for this item is shown with the ones above: gui-webview-Window.salam

OnCrash

func OnCrash(handler: func())
Called when the page process dies, for example out of memory.

The example for this item is shown with the ones above: gui-webview-Window.salam

OnResize

func OnResize(handler: func(int, int))
Called with the new size.

The example for this item is shown with the ones above: gui-webview-Window.salam

IsClosed

func IsClosed(): bool
The window has been closed.

The example for this item is shown with the ones above: gui-webview-Window.salam

Config

struct Config
Settings of the application. Pass it to Init.
NameTypeDescription
idstr = ""A reverse-domain identifier, such as com.example.notes. It names the data folder and scrambles the asset pack.
namestr = ""The name people see.
versionstr = ""The version text.
iconstr = ""An icon file for notifications.
schemestr = "app"The scheme local pages load from, app by default.
assets_dirstr = ""Serve pages from this folder. Handy while developing; when it is empty only the pack is used.
pack_pathstr = ""Read assets from this pack file. By default the pack inside the executable is used.
cspstr = ""A Content-Security-Policy header for local pages.
spabool = falseAnswer unknown paths with index.html, for single page apps.
debugbool = falseTurn on developer tools.
external_linksbool = trueOpen links to other sites in the system browser, on by default.
gui-webview-App.salam
import gui.webview

func main:
    webview.Init(webview.Config {
        id = "com.example.app", name = "App", version = "1.0", scheme = "app", assets_dir = "www", pack_path = "", csp = "default-src 'self'", spa = false, debug = false, external_links = true
    })
    if not webview.SingleInstance((args: Vector<str>): println args.len() end):
        ret
    end
    mut allowed := Vector {} as Vector<str>
    allowed.push("greet")
    webview.Allow("https://example.com", allowed)
    println webview.Allowed("https://example.com", "greet"), webview.OriginOf("https://example.com/a")
    println webview.SafeAssetPath("/a/"), webview.DataDir(), webview.AssetURL("/index.html"), webview.CurrentConfig().name
    println webview.PackDir("www", "app.pack", "com.example.app")
    webview.Command("greet", (c: webview.Call):
        println c.command, c.args, c.origin, c.window, c.Str("name"), c.Int("n"), c.Bool("b"), c.Has("x")
        c.ResolveStr("hello")
    end)
    webview.CommandAsync("slow", (c: webview.Call):
        c.Resolve(`{"ok":true}`)
        c.ResolveInt(1 as i64)
        c.ResolveBool(true)
        c.ResolveNull()
        c.Reject("no")
    end)
    webview.On("ready", (data: str): webview.Emit("hello", data) end)
    mut scripts := Vector {} as Vector<str>
    scripts.push("window.started = true")
    mut others := Vector {} as Vector<str>
    others.push("https://example.org")
    w := webview.Open(webview.WindowOptions {
        label = "main", title = "App", width = 800, height = 600, min_width = 400, min_height = 300, url = "", html = "", icon = "", resizable = true, center = true, visible = true, context_menu = true, scripts = scripts, allow_navigation = others
    })
    w.OnClose((): ret webview.Confirm("Close", "Close the window?") end)
    w.OnLoad((url: str, done: bool): println url, done end)
    w.OnCrash((): w.Navigate(webview.AssetURL("/error.html")) end)
    w.OnResize((x: int, y: int): println x, y end)
    w.SetTitle("App")
    w.SetSize(900, 700)
    w.Show()
    w.Focus()
    w.Hide()
    w.Minimize()
    w.Maximize()
    w.Eval("document.title")
    w.Emit("hello", "1")
    w.SetHTML("<h1>hello</h1>")
    w.OpenDevTools()
    println w.index, w.URL(), w.IsActive(), w.IsClosed(), w.Native() != null, webview.GetWindow("main").index
    webview.Dispatch((): println "ui thread" end)
    mut items := Vector {} as Vector<webview.MenuItem>
    items.push(webview.Item("open", "Open"))
    items.push(webview.Separator())
    items.push(webview.MenuItem { id = "quit", label = "Quit", separator = false })
    mut bar := Vector {} as Vector<webview.MenuItem>
    bar.push(webview.Submenu("File", items))
    webview.SetMenu(w, bar)
    webview.OnMenu((id: str): println id end)
    println webview.SetTray("icon.png", "App", items)
    webview.OnTrayClick((): w.Focus() end)
    webview.RemoveTray()
    println webview.PickFolder("Folder"), webview.PickFile("File", "Text", "*.txt"), webview.SaveFile("Save", "a.txt")
    webview.Message("Title", "Text")
    println webview.Notify("Title", "Text"), webview.OpenURL("https://example.com")
    webview.OnNotifyClick((): w.Focus() end)
    webview.RequestAttention(w)
    println webview.SecretSet("key", "value"), webview.SecretGet("key")
    webview.SecretDelete("key")
    println webview.LastError()
    webview.Run()
    webview.Quit()
    w.Close()
end

WindowOptions

struct WindowOptions
How to open a window.
NameTypeDescription
labelstr = "main"A name to find the window by, main by default.
titlestr = ""The title.
widthint = 1024The width in pixels.
heightint = 768The height in pixels.
min_widthint = 0The smallest width.
min_heightint = 0The smallest height.
urlstr = ""A remote site to load. Empty means index.html from the assets.
htmlstr = ""HTML to show directly instead of an address.
iconstr = ""The icon file.
resizablebool = trueThe user can resize the window.
centerbool = trueStart in the middle of the screen.
visiblebool = trueShow the window at once.
context_menubool = trueAllow the page right-click menu.
scriptsVectorJavaScript to install in every page and frame, before the page runs.
allow_navigationVectorOther origins this window may navigate to. They get no access to commands.

The example for this item is shown with the ones above: gui-webview-App.salam

Call

struct Call
One call from JavaScript. Finish it with one of the Resolve methods or Reject, on any thread. A second answer is ignored, and the answer frees the text in the call, so read what you need first.
NameTypeDescription
commandstr = ""The command name.
argsstr = "{}"The arguments, as JSON text.
originstr = ""The origin of the page that called.
windowint = -1The number of the window that called.

The example for this item is shown with the ones above: gui-webview-App.salam

Str

func Str(key: str): str
Reads a text argument.

The example for this item is shown with the ones above: gui-webview-App.salam

Int

func Int(key: str): int
Reads a whole number argument.

The example for this item is shown with the ones above: gui-webview-App.salam

Bool

func Bool(key: str): bool
Reads a true or false argument.

The example for this item is shown with the ones above: gui-webview-App.salam

Has

func Has(key: str): bool
The argument was sent.

The example for this item is shown with the ones above: gui-webview-App.salam

Resolve

func Resolve(raw_json: str)
Answers with JSON text.

The example for this item is shown with the ones above: gui-webview-App.salam

ResolveStr

func ResolveStr(value: str)
Answers with text.

The example for this item is shown with the ones above: gui-webview-App.salam

ResolveInt

func ResolveInt(value: i64)
Answers with a number.

The example for this item is shown with the ones above: gui-webview-App.salam

ResolveBool

func ResolveBool(value: bool)
Answers with true or false.

The example for this item is shown with the ones above: gui-webview-App.salam

ResolveNull

func ResolveNull()
Answers with null.

The example for this item is shown with the ones above: gui-webview-App.salam

Reject

func Reject(message: str)
Rejects the promise with this message.

The example for this item is shown with the ones above: gui-webview-App.salam

Window

struct Window
An open window.
NameTypeDescription
indexint = -1The number of the window.

The example for this item is shown with the ones above: gui-webview-App.salam

Navigate

func Navigate(url: str)
Opens an address.

The example for this item is shown with the ones above: gui-webview-App.salam

SetHTML

func SetHTML(html: str)
Shows HTML.

The example for this item is shown with the ones above: gui-webview-App.salam

Eval

func Eval(js: str)
Runs JavaScript in the page; safe from any thread.

The example for this item is shown with the ones above: gui-webview-App.salam

Emit

func Emit(event: str, raw_json: str = "null")
Sends an event to this window. The data is JSON text.

The example for this item is shown with the ones above: gui-webview-App.salam

SetTitle

func SetTitle(title: str)
The title.

The example for this item is shown with the ones above: gui-webview-App.salam

SetSize

func SetSize(width: int, height: int)
The size.

The example for this item is shown with the ones above: gui-webview-App.salam

Show

func Show()
Shows the window.

The example for this item is shown with the ones above: gui-webview-App.salam

Hide

func Hide()
Hides the window.

The example for this item is shown with the ones above: gui-webview-App.salam

Focus

func Focus()
Brings the window forward.

The example for this item is shown with the ones above: gui-webview-App.salam

Minimize

func Minimize()
Minimizes the window.

The example for this item is shown with the ones above: gui-webview-App.salam

Maximize

func Maximize()
Maximizes the window.

The example for this item is shown with the ones above: gui-webview-App.salam

Close

func Close()
Closes the window.

The example for this item is shown with the ones above: gui-webview-App.salam

URL

func URL(): str
The current address. The caller frees it.

The example for this item is shown with the ones above: gui-webview-App.salam

IsActive

func IsActive(): bool
The window has the keyboard focus.

The example for this item is shown with the ones above: gui-webview-App.salam

IsClosed

func IsClosed(): bool
The window is closed, or could not open.

The example for this item is shown with the ones above: gui-webview-App.salam

OpenDevTools

func OpenDevTools()
Opens the developer tools.

The example for this item is shown with the ones above: gui-webview-App.salam

OnClose

func OnClose(handler: func(): bool)
Called when the user closes the window. Return false to keep it open.

The example for this item is shown with the ones above: gui-webview-App.salam

OnLoad

func OnLoad(handler: func(str, bool))
Called when a page starts (false) and finishes (true) loading.

The example for this item is shown with the ones above: gui-webview-App.salam

OnCrash

func OnCrash(handler: func())
Called when the page process dies.

The example for this item is shown with the ones above: gui-webview-App.salam

OnResize

func OnResize(handler: func(int, int))
Called with the new size.

The example for this item is shown with the ones above: gui-webview-App.salam

Native

func Native(): void*
The handle in the operating system.

The example for this item is shown with the ones above: gui-webview-App.salam

struct MenuItem
One menu entry.
NameTypeDescription
idstr = ""What OnMenu receives.
labelstr = ""The text.
separatorbool = falseThis entry is a line.
childrenVectorThe entries of a submenu.

The example for this item is shown with the ones above: gui-webview-App.salam

Constants and variables

HINT_NONE

const HINT_NONE: i32 = 0
The user can resize the window.

The example for this item is shown with the ones above: gui-webview-Window.salam

HINT_FIXED

const HINT_FIXED: i32 = 1
The window size is fixed.

The example for this item is shown with the ones above: gui-webview-Window.salam

ENGINE_SYSTEM

const ENGINE_SYSTEM: str = "system"
The engine name "system": the web view of the operating system, the default.
gui-webview-Engine.salam
import gui.webview

func main:
    println "Engine:", webview.Engine()
    println "Name:", webview.EngineName()
    if webview.Engine() == webview.ENGINE_CHROMIUM:
        println "Chromium Embedded Framework"
    end
    if not webview.SupportsCommands():
        println "No commands or events with this engine yet"
    end
end

ENGINE_CHROMIUM

const ENGINE_CHROMIUM: str = "chromium"
The engine name "chromium": Chromium Embedded Framework, chosen at build time with -DSALAM_WEBVIEW_CEF.

The example for this item is shown with the ones above: gui-webview-Engine.salam

Functions

Dispatch

func Dispatch(work: func())
Runs a function on the UI thread, from any thread.

The example for this item is shown with the ones above: gui-webview-Window.salam

RunAll

func RunAll()
Runs the event loop until every window is closed or Quit is called.

The example for this item is shown with the ones above: gui-webview-Window.salam

Quit

func Quit()
Makes the running event loop return.

The example for this item is shown with the ones above: gui-webview-Window.salam

SetApp

func SetApp(id: str, name: str)
The application id and name. Call it before the first window.

The example for this item is shown with the ones above: gui-webview-Window.salam

SetDataDir

func SetDataDir(dir: str)
The folder for cookies and local storage. Call it before the first window.

The example for this item is shown with the ones above: gui-webview-Window.salam

RegisterScheme

func RegisterScheme(name: str, handler: func(Request): Response)
Serves a custom address scheme from your own function, so pages can load from memory.

The example for this item is shown with the ones above: gui-webview-Window.salam

SchemeURL

func SchemeURL(name: str, path: str): str
The address of a path in a scheme, for this operating system: app://localhost/x, or https://app.localhost/x on Windows.

The example for this item is shown with the ones above: gui-webview-Window.salam

SchemeOrigin

func SchemeOrigin(name: str): str
The origin of a scheme, for this operating system.

The example for this item is shown with the ones above: gui-webview-Window.salam

LastError

func LastError(): str
Why the last window could not open, or empty text.

The example for this item is shown with the ones above: gui-webview-Window.salam

Trace

func Trace(text: str)
Adds a line to the file named by the SALAM_WEBVIEW_TRACE environment variable, and does nothing when it is unset. The backends use it to record each startup step, navigation and message, so a window that stays blank can be diagnosed, including in an application with no console.

The example for this item is shown with the ones above: gui-webview-Window.salam

OnCommand

func OnCommand(handler: func(int))
Called with the number of a native menu item that was chosen.

The example for this item is shown with the ones above: gui-webview-Window.salam

OnTrayMessage

func OnTrayMessage(handler: func(int))
Windows only: called with tray icon events.

The example for this item is shown with the ones above: gui-webview-Window.salam

OnCopyData

func OnCopyData(handler: func(void*, int))
Windows only: called with WM_COPYDATA data sent to the message window.

The example for this item is shown with the ones above: gui-webview-Window.salam

func MenuHost(): void*
The object native menus report to: the message window on Windows, the application delegate on macOS.

The example for this item is shown with the ones above: gui-webview-Window.salam

NameMessageWindow

func NameMessageWindow(title: str)
Windows only: the title of the hidden message window, so another process can find it.

The example for this item is shown with the ones above: gui-webview-Window.salam

func NavigationIsMainFrame(): bool
Inside OnNavigate: whether the page itself is navigating. WebKitGTK cannot tell a frame from the page, so on Linux this reports a user gesture instead.

The example for this item is shown with the ones above: gui-webview-Window.salam

PostScript

func PostScript(): str
JavaScript for a function that posts a string to OnMessage. The caller frees it.

The example for this item is shown with the ones above: gui-webview-Window.salam

Create

func Create(debug: bool = false, visible: bool = true): WebView
Makes a new window. The first flag turns on developer tools, the second shows the window at once. On failure the window it returns is already closed.

The example for this item is shown with the ones above: gui-webview-Window.salam

Engine

func Engine(): str
The engine this program was built with, ENGINE_SYSTEM or ENGINE_CHROMIUM.

The example for this item is shown with the ones above: gui-webview-Engine.salam

EngineName

func EngineName(): str
The engine in words for people: WebKitGTK, WebView2, WKWebView or Chromium Embedded Framework.

The example for this item is shown with the ones above: gui-webview-Engine.salam

SupportsCommands

func SupportsCommands(): bool
Whether commands, events and custom schemes work with this engine. False with the Chromium engine for now.

The example for this item is shown with the ones above: gui-webview-Engine.salam

OriginOf

func OriginOf(url: str): str
The origin of an address, in the form browsers use.

The example for this item is shown with the ones above: gui-webview-App.salam

Allowed

func Allowed(origin: str, command: str): bool
Whether a page from this origin may run this command.

The example for this item is shown with the ones above: gui-webview-App.salam

PackDir

func PackDir(dir: str, out_path: str, key: str): int
Packs a folder into an asset file and returns how many files it holds, or -1.

The example for this item is shown with the ones above: gui-webview-App.salam

Init

func Init(config: Config)
Sets up the application. Call it first.

The example for this item is shown with the ones above: gui-webview-App.salam

CurrentConfig

func CurrentConfig(): Config
The settings in use.

The example for this item is shown with the ones above: gui-webview-App.salam

DataDir

func DataDir(): str
The folder for this application data, for the current user.

The example for this item is shown with the ones above: gui-webview-App.salam

Allow

func Allow(origin: str, commands: Vector<str>)
Lets a remote origin run these commands; a pattern may end in *. Call it before opening windows. Nothing remote can call anything by default.

The example for this item is shown with the ones above: gui-webview-App.salam

Command

func Command(name: str, handler: func(Call))
Registers a command that runs on the UI thread.

The example for this item is shown with the ones above: gui-webview-App.salam

CommandAsync

func CommandAsync(name: str, handler: func(Call))
Registers a command that runs on a worker thread, for slow work.

The example for this item is shown with the ones above: gui-webview-App.salam

On

func On(event: str, handler: func(str))
Called when a page emits this event. The data is JSON text.

The example for this item is shown with the ones above: gui-webview-App.salam

Emit

func Emit(event: str, raw_json: str = "null")
Sends an event to every window; safe from any thread.

The example for this item is shown with the ones above: gui-webview-App.salam

AssetURL

func AssetURL(path: str): str
The address of an asset on this operating system.

The example for this item is shown with the ones above: gui-webview-App.salam

Open

func Open(options: WindowOptions): Window
Opens a window. On failure it returns a closed one; check IsClosed.

The example for this item is shown with the ones above: gui-webview-App.salam

GetWindow

func GetWindow(label: str): Window
Finds a window by its label.

The example for this item is shown with the ones above: gui-webview-App.salam

Run

func Run()
Runs until every window closes or Quit is called.

The example for this item is shown with the ones above: gui-webview-App.salam

OpenURL

func OpenURL(url: str): bool
Opens an http, https, mailto or tel address in the system handler.

The example for this item is shown with the ones above: gui-webview-App.salam

PickFolder

func PickFolder(title: str = "Select a folder"): str
Asks the user for a folder. Empty text means cancelled.

The example for this item is shown with the ones above: gui-webview-App.salam

PickFile

func PickFile(title: str = "Open", filter_name: str = "", patterns: str = ""): str
Asks the user for a file. Patterns look like *.txt;*.md.

The example for this item is shown with the ones above: gui-webview-App.salam

SaveFile

func SaveFile(title: str = "Save", default_name: str = ""): str
Asks the user where to save.

The example for this item is shown with the ones above: gui-webview-App.salam

Message

func Message(title: str, text: str)
Shows a message.

The example for this item is shown with the ones above: gui-webview-App.salam

Confirm

func Confirm(title: str, text: str): bool
Asks a yes or no question.

The example for this item is shown with the ones above: gui-webview-App.salam

Notify

func Notify(title: str, body: str, sound: str = ""): bool
Shows a desktop notification, silently or with the sound default or alert. False when the system has no way to show it.

The example for this item is shown with the ones above: gui-webview-App.salam

OnNotifyClick

func OnNotifyClick(handler: func())
Called when the user clicks a notification, on Linux and Windows. On macOS a click brings the application forward by itself.

The example for this item is shown with the ones above: gui-webview-App.salam

RequestAttention

func RequestAttention(window: Window)
Flashes the taskbar entry or bounces the dock icon.

The example for this item is shown with the ones above: gui-webview-App.salam

SecretSet

func SecretSet(key: str, value: str): bool
Stores a secret in the system keychain, or in a private file when there is none.

The example for this item is shown with the ones above: gui-webview-App.salam

SecretGet

func SecretGet(key: str): str
Reads a secret. Empty text when there is none.

The example for this item is shown with the ones above: gui-webview-App.salam

SecretDelete

func SecretDelete(key: str)
Removes a secret.

The example for this item is shown with the ones above: gui-webview-App.salam

Item

func Item(id: str, label: str): MenuItem
Makes a menu entry.

The example for this item is shown with the ones above: gui-webview-App.salam

Separator

func Separator(): MenuItem
Makes a line between menu entries.

The example for this item is shown with the ones above: gui-webview-App.salam

func Submenu(label: str, items: Vector<MenuItem>): MenuItem
Makes a submenu.

The example for this item is shown with the ones above: gui-webview-App.salam

OnMenu

func OnMenu(handler: func(str))
Called with the id of the chosen menu entry.

The example for this item is shown with the ones above: gui-webview-App.salam

SetMenu

func SetMenu(window: Window, items: Vector<MenuItem>)
Gives a window a menu bar. On macOS the entries join the application menu.

The example for this item is shown with the ones above: gui-webview-App.salam

SetTray

func SetTray(icon: str, tooltip: str, items: Vector<MenuItem>): bool
Adds a tray icon with a menu.

The example for this item is shown with the ones above: gui-webview-App.salam

RemoveTray

func RemoveTray()
Removes the tray icon.

The example for this item is shown with the ones above: gui-webview-App.salam

OnTrayClick

func OnTrayClick(handler: func())
Called when the tray icon is clicked, where the system reports it.

The example for this item is shown with the ones above: gui-webview-App.salam

SingleInstance

func SingleInstance(on_second: func(Vector<str>)): bool
Keeps one running copy. True means this is the first copy; false means another one got your arguments and you should exit.

The example for this item is shown with the ones above: gui-webview-App.salam

SafeAssetPath

func SafeAssetPath(request_path: str): str
Turns a request path into a relative asset name, or empty text if it tries to leave the assets. The caller frees it.

The example for this item is shown with the ones above: gui-webview-App.salam