Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[linux] Add support for WebKit2GTK 2.36+ features #2151

Merged
merged 1 commit into from
Nov 30, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 13 additions & 13 deletions v2/internal/frontend/desktop/linux/frontend.go
Original file line number Diff line number Diff line change
Expand Up @@ -126,15 +126,17 @@ func (f *Frontend) WindowClose() {

func init() {
runtime.LockOSThread()
}

func NewFrontend(ctx context.Context, appoptions *options.App, myLogger *logger.Logger, appBindings *binding.Bindings, dispatcher frontend.Dispatcher) *Frontend {

// Set GDK_BACKEND=x11 if currently unset and XDG_SESSION_TYPE is unset, unspecified or x11 to prevent warnings
if os.Getenv("GDK_BACKEND") == "" && (os.Getenv("XDG_SESSION_TYPE") == "" || os.Getenv("XDG_SESSION_TYPE") == "unspecified" || os.Getenv("XDG_SESSION_TYPE") == "x11") {
_ = os.Setenv("GDK_BACKEND", "x11")
}

C.gtk_init(nil, nil)
}

func NewFrontend(ctx context.Context, appoptions *options.App, myLogger *logger.Logger, appBindings *binding.Bindings, dispatcher frontend.Dispatcher) *Frontend {

result := &Frontend{
frontendOptions: appoptions,
logger: myLogger,
Expand Down Expand Up @@ -171,8 +173,6 @@ func NewFrontend(ctx context.Context, appoptions *options.App, myLogger *logger.

go result.startMessageProcessor()

C.gtk_init(nil, nil)

var _debug = ctx.Value("debug")
if _debug != nil {
result.debug = _debug.(bool)
Expand Down Expand Up @@ -480,29 +480,29 @@ func (f *Frontend) processRequest(request unsafe.Pointer) {
uri := C.webkit_uri_scheme_request_get_uri(req)
goURI := C.GoString(uri)

// WebKitGTK stable < 2.36 API does not support request method, request headers and request.
// Apart from request bodies, this is only available beginning with 2.36: https://webkitgtk.org/reference/webkit2gtk/stable/WebKitURISchemeResponse.html
rw := &webKitResponseWriter{req: req}
defer rw.Close()

f.assets.ProcessHTTPRequest(
goURI,
rw,
func() (*http.Request, error) {
req, err := http.NewRequest(http.MethodGet, goURI, nil)
method := webkit_uri_scheme_request_get_http_method(req)
r, err := http.NewRequest(method, goURI, nil)
if err != nil {
return nil, err
}
r.Header = webkit_uri_scheme_request_get_http_headers(req)

if req.URL.Host != f.startURL.Host {
if req.Body != nil {
req.Body.Close()
if r.URL.Host != f.startURL.Host {
if r.Body != nil {
r.Body.Close()
}

return nil, fmt.Errorf("Expected host '%s' in request, but was '%s'", f.startURL.Host, req.URL.Host)
return nil, fmt.Errorf("Expected host '%s' in request, but was '%s'", f.startURL.Host, r.URL.Host)
}

return req, nil
return r, nil
})

}
29 changes: 29 additions & 0 deletions v2/internal/frontend/desktop/linux/webkit2.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
//go:build linux

package linux

/*
#cgo linux pkg-config: webkit2gtk-4.0
#include "webkit2/webkit2.h"
*/
import "C"
import (
"fmt"

"github.com/wailsapp/wails/v2/pkg/options"
"github.com/wailsapp/wails/v2/pkg/options/linux"
)

func validateWebKit2Version(options *options.App) {
if C.webkit_get_major_version() == 2 && C.webkit_get_minor_version() >= webkit2MinMinorVersion {
return
}

msg := linux.DefaultMessages()
if options.Linux != nil && options.Linux.Messages != nil {
msg = options.Linux.Messages
}

v := fmt.Sprintf("2.%d.0", webkit2MinMinorVersion)
showModalDialogAndExit("WebKit2GTK", fmt.Sprintf(msg.WebKit2GTKMinRequired, v))
}
73 changes: 73 additions & 0 deletions v2/internal/frontend/desktop/linux/webkit2_36.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
//go:build linux && webkit2_36

package linux

/*
#cgo linux pkg-config: gtk+-3.0 webkit2gtk-4.0 libsoup-2.4
Copy link
Member

@leaanthony leaanthony Nov 30, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is libsoup a new dependency that we need to add to our website and package manager code? It looks like it's already installed in Ubuntu 22.04 but might not be for others?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WebKit2GTK uses libsoup internally, so I think it should not be possible to have WebKit2GTK installed without this dependency. But maybe it's better to be on the safe side and add it.

I'll add that as soon as we have merged the CLI refactoring so it doesn't interfere with that. Are you fine with that?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah sure, though I suspect you are right: it's probably installed by webkit2gtk anyway so no need to specify it 👍


#include "gtk/gtk.h"
#include "webkit2/webkit2.h"
#include "libsoup/soup.h"
*/
import "C"

import (
"net/http"
"strings"
"unsafe"

"github.com/wailsapp/wails/v2/internal/frontend/assetserver"
)

const webkit2MinMinorVersion = 36

func webkit_uri_scheme_request_get_http_method(req *C.WebKitURISchemeRequest) string {
method := C.GoString(C.webkit_uri_scheme_request_get_http_method(req))
return strings.ToUpper(method)
}

func webkit_uri_scheme_request_get_http_headers(req *C.WebKitURISchemeRequest) http.Header {
hdrs := C.webkit_uri_scheme_request_get_http_headers(req)

var iter C.SoupMessageHeadersIter
C.soup_message_headers_iter_init(&iter, hdrs)

var name *C.char
var value *C.char

h := http.Header{}
for C.soup_message_headers_iter_next(&iter, &name, &value) != 0 {
h.Add(C.GoString(name), C.GoString(value))
}

return h
}

func webkit_uri_scheme_request_finish(req *C.WebKitURISchemeRequest, code int, header http.Header, stream *C.GInputStream, streamLength int64) error {
resp := C.webkit_uri_scheme_response_new(stream, C.gint64(streamLength))
defer C.g_object_unref(C.gpointer(resp))

cReason := C.CString(http.StatusText(code))
C.webkit_uri_scheme_response_set_status(resp, C.guint(code), cReason)
C.free(unsafe.Pointer(cReason))

cMimeType := C.CString(header.Get(assetserver.HeaderContentType))
C.webkit_uri_scheme_response_set_content_type(resp, cMimeType)
C.free(unsafe.Pointer(cMimeType))

hdrs := C.soup_message_headers_new(C.SOUP_MESSAGE_HEADERS_RESPONSE)
for name, values := range header {
cName := C.CString(name)
for _, value := range values {
cValue := C.CString(value)
C.soup_message_headers_append(hdrs, cName, cValue)
C.free(unsafe.Pointer(cValue))
}
C.free(unsafe.Pointer(cName))
}

C.webkit_uri_scheme_response_set_http_headers(resp, hdrs)

C.webkit_uri_scheme_request_finish_with_response(req, resp)
return nil
}
40 changes: 40 additions & 0 deletions v2/internal/frontend/desktop/linux/webkit2_legacy.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
//go:build linux && !webkit2_36

package linux

/*
#cgo linux pkg-config: gtk+-3.0 webkit2gtk-4.0

#include "gtk/gtk.h"
#include "webkit2/webkit2.h"
*/
import "C"

import (
"fmt"
"net/http"
"unsafe"

"github.com/wailsapp/wails/v2/internal/frontend/assetserver"
)

const webkit2MinMinorVersion = 0

func webkit_uri_scheme_request_get_http_method(_ *C.WebKitURISchemeRequest) string {
return http.MethodGet
}

func webkit_uri_scheme_request_get_http_headers(_ *C.WebKitURISchemeRequest) http.Header {
return http.Header{}
}

func webkit_uri_scheme_request_finish(req *C.WebKitURISchemeRequest, code int, header http.Header, stream *C.GInputStream, streamLength int64) error {
if code != http.StatusOK {
return fmt.Errorf("StatusCodes not supported: %d - %s", code, http.StatusText(code))
}

cMimeType := C.CString(header.Get(assetserver.HeaderContentType))
C.webkit_uri_scheme_request_finish(req, stream, C.gint64(streamLength), cMimeType)
C.free(unsafe.Pointer(cMimeType))
return nil
}
Original file line number Diff line number Diff line change
Expand Up @@ -55,37 +55,30 @@ func (rw *webKitResponseWriter) WriteHeader(code int) {
}
rw.wroteHeader = true

if code != http.StatusOK {
// WebKitGTK stable < 2.36 API does not support response headers and response statuscodes
rw.w = &nopCloser{io.Discard}
rw.finishWithError(http.StatusText(code), code)
return
contentLength := int64(-1)
if sLen := rw.Header().Get(assetserver.HeaderContentLength); sLen != "" {
if pLen, _ := strconv.ParseInt(sLen, 10, 64); pLen > 0 {
contentLength = pLen
}
}

// We can't use os.Pipe here, because that returns files with a finalizer for closing the FD. But the control over the
// read FD is given to the InputStream and will be closed there.
// Furthermore we especially don't want to have the FD_CLOEXEC
rFD, w, err := pipe()
if err != nil {
rw.wErr = fmt.Errorf("Unable opening pipe: %s", err)
rw.finishWithError(rw.wErr.Error(), http.StatusInternalServerError)
rw.finishWithError(http.StatusInternalServerError, fmt.Errorf("unable to open pipe: %s", err))
return
}
rw.w = w

cMimeType := C.CString(rw.Header().Get(assetserver.HeaderContentType))
defer C.free(unsafe.Pointer(cMimeType))
stream := C.g_unix_input_stream_new(C.int(rFD), gtkBool(true))
defer C.g_object_unref(C.gpointer(stream))

contentLength := int64(-1)
if sLen := rw.Header().Get(assetserver.HeaderContentLength); sLen != "" {
if pLen, _ := strconv.ParseInt(sLen, 10, 64); pLen > 0 {
contentLength = pLen
}
if err := webkit_uri_scheme_request_finish(rw.req, code, rw.Header(), stream, contentLength); err != nil {
rw.finishWithError(http.StatusInternalServerError, fmt.Errorf("unable to finish request: %s", err))
return
}

stream := C.g_unix_input_stream_new(C.int(rFD), gtkBool(true))
C.webkit_uri_scheme_request_finish(rw.req, stream, C.gint64(contentLength), cMimeType)
C.g_object_unref(C.gpointer(stream))
}

func (rw *webKitResponseWriter) Close() {
Expand All @@ -94,8 +87,14 @@ func (rw *webKitResponseWriter) Close() {
}
}

func (rw *webKitResponseWriter) finishWithError(message string, code int) {
msg := C.CString(http.StatusText(code))
func (rw *webKitResponseWriter) finishWithError(code int, err error) {
if rw.w != nil {
rw.w.Close()
rw.w = &nopCloser{io.Discard}
}
rw.wErr = err

msg := C.CString(err.Error())
gerr := C.g_error_new_literal(C.g_quark_from_string(msg), C.int(code), msg)
C.webkit_uri_scheme_request_finish_error(rw.req, gerr)
C.g_error_free(gerr)
Expand Down
18 changes: 18 additions & 0 deletions v2/internal/frontend/desktop/linux/window.go
Original file line number Diff line number Diff line change
Expand Up @@ -642,6 +642,7 @@ static void SetWindowTransparency(GtkWidget *widget)
*/
import "C"
import (
"log"
"strings"
"sync"
"unsafe"
Expand Down Expand Up @@ -679,6 +680,7 @@ func bool2Cint(value bool) C.int {
}

func NewWindow(appoptions *options.App, debug bool) *Window {
validateWebKit2Version(appoptions)

result := &Window{
appoptions: appoptions,
Expand Down Expand Up @@ -1034,3 +1036,19 @@ func (w *Window) ToggleMaximise() {
w.Maximise()
}
}

// showModalDialogAndExit shows a modal dialog and exits the app.
func showModalDialogAndExit(title, message string) {
go func() {
data := C.MessageDialogOptions{
title: C.CString(title),
message: C.CString(message),
messageType: C.int(1),
}

C.messageDialog(unsafe.Pointer(&data))
}()

<-messageDialogResult
log.Fatal(message)
}
13 changes: 13 additions & 0 deletions v2/pkg/options/linux/linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,17 @@ package linux
type Options struct {
Icon []byte
WindowIsTranslucent bool

// User messages that can be customised
Messages *Messages
}

type Messages struct {
WebKit2GTKMinRequired string
}

func DefaultMessages() *Messages {
return &Messages{
WebKit2GTKMinRequired: "This application requires at least WebKit2GTK %s to be installed.",
}
}
36 changes: 18 additions & 18 deletions website/docs/reference/options.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -249,24 +249,24 @@ dynamically with an `http.Handler` or hook into the request chain with an `asset

Not all features of an `http.Request` are currently supported, please see the following feature matrix:

| Feature | Win | Mac | Lin |
| ----------------------- | --- | --- | --- |
| GET | ✅ | ✅ | ✅ |
| POST | ✅ | ✅ | |
| PUT | ✅ | ✅ | |
| PATCH | ✅ | ✅ | |
| DELETE | ✅ | ✅ | |
| Request Headers | ✅ | ✅ | |
| Request Body | ✅ | ✅ | ❌ |
| Request Body Streaming | ❌ | ❌ | ❌ |
| Response StatusCodes | ✅ | ✅ | |
| Response Headers | ✅ | ✅ | |
| Response Body | ✅ | ✅ | ✅ |
| Response Body Streaming | ❌ | ❌ | ✅ |
| WebSockets | ❌ | ❌ | ❌ |

NOTE: Linux is currently very limited due to targeting a WebKit2GTK Version < 2.36.0. In the future some features will be
supported by the introduction of WebKit2GTK 2.36.0+ support.
| Feature | Win | Mac | Lin |
| ----------------------- | --- | --- | ------ |
| GET | ✅ | ✅ | ✅ |
| POST | ✅ | ✅ | ✅ [^1] |
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@leaanthony , @misitebao does anybody of you know if that footnotes do work with our docs?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your best bet it to open branches on the repo itself then PRs will auto-deploy so you can see :-)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah yeah, always forget about that 😄

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The answer is no
image

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm just seen your other PR and it seems footnotes are there for the next version docs: https://622b4565.wails.pages.dev/docs/next/reference/options#assetserver

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh my bad! I wondered why it looked like nothing had happened. The superscript 1 is super hard to click and it jumps to the bottom of the page. How about a * next to is with some text right under it or maybe another column with Linux (webkit 2.36+) in it?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry already merged 😔, but will open another PR to improve that.

| PUT | ✅ | ✅ | ✅ [^1] |
| PATCH | ✅ | ✅ | ✅ [^1] |
| DELETE | ✅ | ✅ | ✅ [^1] |
| Request Headers | ✅ | ✅ | ✅ [^1] |
| Request Body | ✅ | ✅ | ❌ |
| Request Body Streaming | ❌ | ❌ | ❌ |
| Response StatusCodes | ✅ | ✅ | ✅ [^1] |
| Response Headers | ✅ | ✅ | ✅ [^1] |
| Response Body | ✅ | ✅ | ✅ |
| Response Body Streaming | ❌ | ❌ | ✅ |
| WebSockets | ❌ | ❌ | ❌ |
| HTTP Redirects 30x | ✅ | ❌ | ❌ |

[^1]: This requires WebKit2GTK 2.36+ support and your app needs to be build with the build tag `webkit2_36` to activate support for this feature. This also bumps the minimum requirement of WebKit2GTK to 2.36 for your app.

Name: AssetServer<br/>
Type: `*assetserver.Options`
Expand Down
1 change: 1 addition & 0 deletions website/src/pages/changelog.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Add new property for the `wails.json` config file - `bindings`. More information on the new property can be found in the updated [schema](/schemas/config.v2.json). Properties `prefix` and `suffix` allow you to control the generated TypeScript entity name in the `model.ts` file. Added by @OlegGulevskyy in [PR](https://github.com/wailsapp/wails/pull/2101)
- The `WindowSetAlwaysOnTop` method is now exposed in the JS runtime. Fixed by @gotid in [PR](https://github.com/wailsapp/wails/pull/2128)
- The [AssetServer](/docs/reference/options#assetserver) now supports serving the index.html file when requesting a directory. Added by @stffabi in [PR](https://github.com/wailsapp/wails/pull/2110)
- Added support for WebKit2GTK 2.36+ on Linux. This brings additional features for the [AssetServer](/docs/reference/options#assetserver), like support for HTTP methods and Headers. The app must be compiled with the Go build tag `webkit2_36` to activate support for this features. This also bumps the minimum requirement of WebKit2GTK to 2.36 for your app. Fixed by @stffabi in this [PR](https://github.com/wailsapp/wails/pull/2151)

### Fixed
- The `noreload` flag in wails dev wasn't applied. Fixed by @stffabi in this [PR](https://github.com/wailsapp/wails/pull/2081)
Expand Down