webview
A tiny cross-platform webview library for C/C++/Golang to build modern cross-platform GUIs. Also, there are Rust bindings, Python bindings, Nim bindings, Haskell and C# bindings available.
It supports two-way JavaScript bindings (to call JavaScript from C/C++/Go and to call C/C++/Go from JavaScript).
It uses Cocoa/WebKit on macOS, gtk-webkit2 on Linux and MSHTML (IE10/11) on Windows.
Webview for Go developers
If you are interested in writing Webview apps in C/C++, skip to the next section.
Getting started
Install Webview library with go get
:
$ go get github.com/zserge/webview
Import the package and start using it:
package main
import "github.com/zserge/webview"
func main() {
webview.Open("Minimal webview example",
"https://en.m.wikipedia.org/wiki/Main_Page", 800, 600, true)
}
It is not recommended to use go run
(although it works perfectly fine on Linux). Use go build
instead:
$ go build -o webview-example && ./webview-example
$ mkdir -p example.app/Contents/MacOS
$ go build -o example.app/Contents/MacOS/example
$ open example.app
$ go build -ldflags="-H windowsgui" -o webview-example.exe
API
See godoc.
How to serve or inject the initial HTML/CSS/JavaScript into the webview?
First of all, you probably want to embed your assets (HTML/CSS/JavaScript) into the binary to have a standalone executable. Consider using go-bindata or any other similar tools.
Now there are two major approaches to deploy the content:
- Serve HTML/CSS/JS with an embedded HTTP server
- Injecting HTML/CSS/JS via the JavaScript binding API
To serve the content it is recommended to use ephemeral ports:
ln, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
log.Fatal(err)
}
defer ln.Close()
go func() {
log.Fatal(http.Serve(ln, nil))
}()
webview.Open("Hello", "http://"+ln.Addr().String(), 400, 300, false)
Injecting the content via JS bindings is a bit more complicated, but feels more solid and does not expose any additional open TCP ports.
Leave webview.Settings.URL
empty to start with bare minimal HTML5. It will open a webview with <div id="app"></div>
in it. Alternatively, use a data URI to inject custom HTML code (don't forget to URL-encode it):
const myHTML = `<!doctype html><html>....</html>`
w := webview.New(webview.Settings{
URL: `data:text/html,` + url.PathEscape(myHTML),
})
Keep your initial HTML short (a few kilobytes maximum).
Now you can inject more JavaScript once the webview becomes ready using webview.Eval()
. You can also inject CSS styles using JavaScript:
w.Dispatch(func() {
w.Eval(fmt.Sprintf(`(function(css){
var style = document.createElement('style');
var head = document.head || document.getElementsByTagName('head')[0];
style.setAttribute('type', 'text/css');
if (style.styleSheet) {
style.styleSheet.cssText = css;
} else {
style.appendChild(document.createTextNode(css));
}
head.appendChild(style);
})("%s")`, template.JSEscapeString(myStylesCSS)))
w.Eval(myJSFramework)
w.Eval(myAppJS)
})
This works fairly well across the platforms, see counter-go
example for more details about how make a webview app with no web server. It also demonstrates how to use ReactJS, VueJS or Picodom with webview.
How to communicate between native Go and web UI?
You already have seen how to use w.Eval()
to run JavaScript inside the webview. There is also a way to call Go code from JavaScript.
On the low level there is a special callback, webview.Settings.ExternalInvokeCallback
that receives a string argument. This string can be passed from JavaScript using window.external.invoke(someString)
.
This might seem very inconvenient, and that is why there is a dedicated webview.Bind()
API call. It binds an existing Go object (struct or struct pointer) and creates/injects JS API for it. Now you can call JS methods and they will result in calling native Go methods. Even more, if you modify the Go object - it can be automatically serialized to JSON and passed to the web UI to keep things in sync.
Please, see counter-go
example for more details about how to bind Go controllers to the web UI.
Debugging and development tips
If terminal output is unavailable (e.g. if you launch app bundle on MacOS or
GUI app on Windows) you may use webview.Debug()
and webview.Debugf()
to
print logs. On MacOS such logs will be printed via NSLog and can be seen in the
Console
app. On Windows they use OutputDebugString
and can be seen using
DebugView
app. On Linux logging is done to stderr and can be seen in the
terminal or redirected to a file.
To debug the web part of your app you may use webview.Settings.Debug
flag. It
enables the Web Inspector in WebKit and works on Linux and MacOS (use popup menu
to open the web inspector). On Windows there is no easy to way to enable
debugging, but you may include Firebug in your HTML code:
<script type="text/javascript" src="https://getfirebug.com/firebug-lite.js"></script>
Even though Firebug browser extension development has been stopped, Firebug
Lite is still available and just works.
Distributing webview apps
On Linux you get a standalone executable. It will depend on GTK3 and GtkWebkit2, so if you distribute your app in DEB or RPM format include those dependencies. An application icon can be specified by providing a .desktop
file.
On MacOS you are likely to ship an app bundle. Make the following directory structure and just zip it:
example.app
└── Contents
├── Info.plist
├── MacOS
| └── example
└── Resources
└── example.icns
Here, Info.plist
is a property list file and *.icns
is a special icon format. You may convert PNG to icns online.
On Windows you probably would like to have a custom icon for your executable. It can be done by providing a resource file, compiling it and linking with it. Typically, windres
utility is used to compile resources.
You may find some example build scripts for all three platforms here.
Also, if you want to cross-compile your webview app - use xgo.
Webview for C/C++ developers
Getting started
Download webview.h and include it in your C/C++ code:
#define WEBVIEW_IMPLEMENTATION
#include "webview.h"
#ifdef WIN32
int WINAPI WinMain(HINSTANCE hInt, HINSTANCE hPrevInst, LPSTR lpCmdLine,
int nCmdShow) {
#else
int main() {
#endif
webview("Minimal webview example",
"https://en.m.wikipedia.org/wiki/Main_Page", 800, 600, 1);
return 0;
}
Build it:
$ cc main.c -DWEBVIEW_GTK=1 `pkg-config --cflags --libs gtk+-3.0 webkit2gtk-4.0` -o webview-example
$ cc main.c -DWEBVIEW_COCOA=1 -x objective-c -framework Cocoa -framework WebKit -o webview-example
$ cc main.c -DWEBVIEW_WINAPI=1 -lole32 -lcomctl32 -loleaut32 -luuid -mwindows -o webview-example.exe
API
For the most simple use cases there is only one function:
int webview(const char *title, const char *url, int width, int height, int resizable);
The following URL schemes are supported:
http://
and https://
, no surprises here.file:///
can be useful if you want to unpack HTML/CSS assets to some
temporary directory and point a webview to open index.html from there.data:text/html,<html>...</html>
allows to pass short HTML data inline
without using a web server or polluting the file system. Further
modifications of the webview contents can be done via JavaScript bindings.
If have chosen a regular http URL scheme, you can use Mongoose or any other web server/framework you like.
If you want to have more control over the app lifecycle you can use the following functions:
struct webview webview = {
.title = title,
.url = url,
.width = w,
.height = h,
.debug = debug,
.resizable = resizable,
};
webview_init(&webview);
while (webview_loop(&webview, blocking) == 0);
webview_exit(&webview);
webview_set_title(&webview, "New title");
webview_terminate(&webview);
webview_debug("exited: %d\n", 1);
To evaluate arbitrary JavaScript code use the following C function:
webview_eval(&webview, "alert('hello, world');");
There is also a special callback (webview.external_invoke_cb
) that can be invoked from JavaScript:
void my_cb(struct webview *w, const char *arg) {
...
}
window.external.invoke('some arg');
window.external.invoke(JSON.stringify({fn: 'sum', x: 5, y: 3}));
Webview library is meant to be used from a single UI thread only. So if you
want to call webview_eval
or webview_terminate
from some background thread
- you have to use
webview_dispatch
to post some arbitrary function with some
context to be executed inside the main UI thread:
void render(struct webview *w, void *arg) {
webview_eval(w, ......);
}
webview_dispatch(w, render, some_arg);
You may find some C/C++ examples in this repo that demonstrate the API above.
Also, there is a more more advanced complete C++ app, Slide, that uses webview as a GUI. You may have a look how webview apps can be built, packaged and how automatic CI/CD can be set up.
Notes
Execution on OpenBSD requires wxallowed
mount(8) option.
FreeBSD is also supported, to install webkit2 run pkg install webkit2-gtk3
.
License
Code is distributed under MIT license, feel free to use it in your proprietary
projects as well.