mirror of
https://github.com/wailsapp/wails.git
synced 2025-05-02 09:00:38 +08:00

Make sure no pointers to the stack are passed to g_idle_add, because at the time the callback gets executed on the main thread, the pointer might be invalid. Go might have reused the stack or grown the stack and the pointer is invalid. The concept used on Windows has been ported to Linux.
928 lines
25 KiB
Go
928 lines
25 KiB
Go
//go:build linux
|
|
// +build linux
|
|
|
|
package linux
|
|
|
|
/*
|
|
#cgo linux pkg-config: gtk+-3.0 webkit2gtk-4.0
|
|
|
|
#include <JavaScriptCore/JavaScript.h>
|
|
#include "gtk/gtk.h"
|
|
#include "webkit2/webkit2.h"
|
|
#include <stdio.h>
|
|
#include <limits.h>
|
|
#include "stdint.h"
|
|
|
|
|
|
void ExecuteOnMainThread(void* f, gpointer jscallback) {
|
|
g_idle_add((GSourceFunc)f, (gpointer)jscallback);
|
|
}
|
|
|
|
static GtkWidget* GTKWIDGET(void *pointer) {
|
|
return GTK_WIDGET(pointer);
|
|
}
|
|
|
|
static GtkWindow* GTKWINDOW(void *pointer) {
|
|
return GTK_WINDOW(pointer);
|
|
}
|
|
|
|
static GtkContainer* GTKCONTAINER(void *pointer) {
|
|
return GTK_CONTAINER(pointer);
|
|
}
|
|
|
|
static GtkBox* GTKBOX(void *pointer) {
|
|
return GTK_BOX(pointer);
|
|
}
|
|
|
|
static void SetMinMaxSize(GtkWindow* window, int min_width, int min_height, int max_width, int max_height) {
|
|
GdkGeometry size;
|
|
size.min_width = size.min_height = size.max_width = size.max_height = 0;
|
|
int flags = GDK_HINT_MAX_SIZE | GDK_HINT_MIN_SIZE;
|
|
size.max_height = (max_height == 0 ? INT_MAX : max_height);
|
|
size.max_width = (max_width == 0 ? INT_MAX : max_width);
|
|
size.min_height = min_height;
|
|
size.min_width = min_width;
|
|
gtk_window_set_geometry_hints(window, NULL, &size, flags);
|
|
}
|
|
|
|
GdkMonitor* getCurrentMonitor(GtkWindow *window) {
|
|
// Get the monitor that the window is currently on
|
|
GdkDisplay *display = gtk_widget_get_display(GTK_WIDGET(window));
|
|
GdkWindow *gdk_window = gtk_widget_get_window(GTK_WIDGET(window));
|
|
GdkMonitor *monitor = gdk_display_get_monitor_at_window(display, gdk_window);
|
|
|
|
return GDK_MONITOR(monitor);
|
|
}
|
|
|
|
GdkRectangle getCurrentMonitorGeometry(GtkWindow *window) {
|
|
GdkMonitor *monitor = getCurrentMonitor(window);
|
|
|
|
// Get the geometry of the monitor
|
|
GdkRectangle result;
|
|
gdk_monitor_get_geometry (monitor,&result);
|
|
return result;
|
|
}
|
|
|
|
int getCurrentMonitorScaleFactor(GtkWindow *window) {
|
|
GdkMonitor *monitor = getCurrentMonitor(window);
|
|
|
|
return gdk_monitor_get_scale_factor(monitor);
|
|
}
|
|
|
|
gboolean Center(gpointer data) {
|
|
GtkWindow *window = (GtkWindow*)data;
|
|
|
|
// Get the geometry of the monitor
|
|
GdkRectangle m = getCurrentMonitorGeometry(window);
|
|
|
|
// Get the window width/height
|
|
int windowWidth, windowHeight;
|
|
gtk_window_get_size(window, &windowWidth, &windowHeight);
|
|
|
|
int newX = ((m.width - windowWidth) / 2) + m.x;
|
|
int newY = ((m.height - windowHeight) / 2) + m.y;
|
|
|
|
// Place the window at the center of the monitor
|
|
gtk_window_move(window, newX, newY);
|
|
|
|
return G_SOURCE_REMOVE;
|
|
}
|
|
|
|
int IsFullscreen(GtkWidget *widget) {
|
|
GdkWindow *gdkwindow = gtk_widget_get_window(widget);
|
|
GdkWindowState state = gdk_window_get_state(GDK_WINDOW(gdkwindow));
|
|
return state & GDK_WINDOW_STATE_FULLSCREEN;
|
|
}
|
|
|
|
int IsMaximised(GtkWidget *widget) {
|
|
GdkWindow *gdkwindow = gtk_widget_get_window(widget);
|
|
GdkWindowState state = gdk_window_get_state(GDK_WINDOW(gdkwindow));
|
|
return state & GDK_WINDOW_STATE_MAXIMIZED;
|
|
}
|
|
|
|
|
|
extern void processMessage(char*);
|
|
|
|
static void sendMessageToBackend(WebKitUserContentManager *contentManager,
|
|
WebKitJavascriptResult *result,
|
|
void* data)
|
|
{
|
|
#if WEBKIT_MAJOR_VERSION >= 2 && WEBKIT_MINOR_VERSION >= 22
|
|
JSCValue *value = webkit_javascript_result_get_js_value(result);
|
|
char *message = jsc_value_to_string(value);
|
|
#else
|
|
JSGlobalContextRef context = webkit_javascript_result_get_global_context(result);
|
|
JSValueRef value = webkit_javascript_result_get_value(result);
|
|
JSStringRef js = JSValueToStringCopy(context, value, NULL);
|
|
size_t messageSize = JSStringGetMaximumUTF8CStringSize(js);
|
|
char *message = g_new(char, messageSize);
|
|
JSStringGetUTF8CString(js, message, messageSize);
|
|
JSStringRelease(js);
|
|
#endif
|
|
processMessage(message);
|
|
g_free(message);
|
|
}
|
|
|
|
static void webviewLoadChanged(WebKitWebView *web_view, WebKitLoadEvent load_event, gpointer data)
|
|
{
|
|
if (load_event == WEBKIT_LOAD_FINISHED) {
|
|
processMessage("DomReady");
|
|
}
|
|
}
|
|
|
|
ulong setupInvokeSignal(void* contentManager) {
|
|
return g_signal_connect((WebKitUserContentManager*)contentManager, "script-message-received::external", G_CALLBACK(sendMessageToBackend), NULL);
|
|
}
|
|
|
|
// These are the x,y & time of the last mouse down event
|
|
// It's used for window dragging
|
|
float xroot = 0.0f;
|
|
float yroot = 0.0f;
|
|
int dragTime = -1;
|
|
bool contextMenuDisabled = false;
|
|
|
|
gboolean buttonPress(GtkWidget *widget, GdkEventButton *event, void* dummy)
|
|
{
|
|
if( event == NULL ) {
|
|
xroot = yroot = 0.0f;
|
|
dragTime = -1;
|
|
return FALSE;
|
|
}
|
|
|
|
if( event->button == 3 && contextMenuDisabled ) {
|
|
return TRUE;
|
|
}
|
|
|
|
if (event->type == GDK_BUTTON_PRESS && event->button == 1)
|
|
{
|
|
xroot = event->x_root;
|
|
yroot = event->y_root;
|
|
dragTime = event->time;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
gboolean buttonRelease(GtkWidget *widget, GdkEventButton *event, void* dummy)
|
|
{
|
|
if (event == NULL || (event->type == GDK_BUTTON_RELEASE && event->button == 1))
|
|
{
|
|
xroot = yroot = 0.0f;
|
|
dragTime = -1;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
void connectButtons(void* webview) {
|
|
g_signal_connect(WEBKIT_WEB_VIEW(webview), "button-press-event", G_CALLBACK(buttonPress), NULL);
|
|
g_signal_connect(WEBKIT_WEB_VIEW(webview), "button-release-event", G_CALLBACK(buttonRelease), NULL);
|
|
}
|
|
|
|
extern void processURLRequest(void *request);
|
|
|
|
// This is called when the close button on the window is pressed
|
|
gboolean close_button_pressed(GtkWidget *widget, GdkEvent *event, void* data)
|
|
{
|
|
processMessage("Q");
|
|
// since we handle the close in processMessage tell GTK to not invoke additional handlers - see:
|
|
// https://docs.gtk.org/gtk3/signal.Widget.delete-event.html
|
|
return TRUE;
|
|
}
|
|
|
|
GtkWidget* setupWebview(void* contentManager, GtkWindow* window, int hideWindowOnClose) {
|
|
GtkWidget* webview = webkit_web_view_new_with_user_content_manager((WebKitUserContentManager*)contentManager);
|
|
//gtk_container_add(GTK_CONTAINER(window), webview);
|
|
WebKitWebContext *context = webkit_web_context_get_default();
|
|
webkit_web_context_register_uri_scheme(context, "wails", (WebKitURISchemeRequestCallback)processURLRequest, NULL, NULL);
|
|
g_signal_connect(G_OBJECT(webview), "load-changed", G_CALLBACK(webviewLoadChanged), NULL);
|
|
if (hideWindowOnClose) {
|
|
g_signal_connect(GTK_WIDGET(window), "delete-event", G_CALLBACK(gtk_widget_hide_on_delete), NULL);
|
|
} else {
|
|
g_signal_connect(GTK_WIDGET(window), "delete-event", G_CALLBACK(close_button_pressed), NULL);
|
|
}
|
|
|
|
WebKitSettings *settings = webkit_web_view_get_settings(WEBKIT_WEB_VIEW(webview));
|
|
webkit_settings_set_user_agent_with_application_details(settings, "wails.io", "");
|
|
return webview;
|
|
}
|
|
|
|
void devtoolsEnabled(void* webview, int enabled) {
|
|
WebKitSettings *settings = webkit_web_view_get_settings(WEBKIT_WEB_VIEW(webview));
|
|
gboolean genabled = enabled == 1 ? true : false;
|
|
webkit_settings_set_enable_developer_extras(settings, genabled);
|
|
}
|
|
|
|
void loadIndex(void* webview, char* url) {
|
|
webkit_web_view_load_uri(WEBKIT_WEB_VIEW(webview), url);
|
|
}
|
|
|
|
typedef struct DragOptions {
|
|
void *webview;
|
|
GtkWindow* mainwindow;
|
|
} DragOptions;
|
|
|
|
static gboolean startDrag(gpointer data) {
|
|
DragOptions* options = (DragOptions*)data;
|
|
|
|
// Ignore non-toplevel widgets
|
|
GtkWidget *window = gtk_widget_get_toplevel(GTK_WIDGET(options->webview));
|
|
if (!GTK_IS_WINDOW(window)) {
|
|
free(data);
|
|
return G_SOURCE_REMOVE;
|
|
}
|
|
|
|
gtk_window_begin_move_drag(options->mainwindow, 1, xroot, yroot, dragTime);
|
|
free(data);
|
|
|
|
return G_SOURCE_REMOVE;
|
|
}
|
|
|
|
static void StartDrag(void *webview, GtkWindow* mainwindow) {
|
|
DragOptions* data = malloc(sizeof(DragOptions));
|
|
data->webview = webview;
|
|
data->mainwindow = mainwindow;
|
|
ExecuteOnMainThread(startDrag, (gpointer)data);
|
|
}
|
|
|
|
typedef struct JSCallback {
|
|
void* webview;
|
|
char* script;
|
|
} JSCallback;
|
|
|
|
void executeJS(void *data) {
|
|
struct JSCallback *js = data;
|
|
webkit_web_view_run_javascript(js->webview, js->script, NULL, NULL, NULL);
|
|
free(js->script);
|
|
}
|
|
|
|
void extern processMessageDialogResult(char*);
|
|
|
|
typedef struct MessageDialogOptions {
|
|
void* window;
|
|
char* title;
|
|
char* message;
|
|
int messageType;
|
|
} MessageDialogOptions;
|
|
|
|
void messageDialog(void *data) {
|
|
GtkDialogFlags flags;
|
|
GtkMessageType messageType;
|
|
MessageDialogOptions *options = (MessageDialogOptions*) data;
|
|
if( options->messageType == 0 ) {
|
|
messageType = GTK_MESSAGE_INFO;
|
|
flags = GTK_BUTTONS_OK;
|
|
} else if( options->messageType == 1 ) {
|
|
messageType = GTK_MESSAGE_ERROR;
|
|
flags = GTK_BUTTONS_OK;
|
|
} else if( options->messageType == 2 ) {
|
|
messageType = GTK_MESSAGE_QUESTION;
|
|
flags = GTK_BUTTONS_YES_NO;
|
|
} else {
|
|
messageType = GTK_MESSAGE_WARNING;
|
|
flags = GTK_BUTTONS_OK;
|
|
}
|
|
|
|
GtkWidget *dialog;
|
|
dialog = gtk_message_dialog_new(GTK_WINDOW(options->window),
|
|
GTK_DIALOG_DESTROY_WITH_PARENT,
|
|
messageType,
|
|
flags,
|
|
options->message, NULL);
|
|
gtk_window_set_title(GTK_WINDOW(dialog), options->title);
|
|
GtkResponseType result = gtk_dialog_run(GTK_DIALOG(dialog));
|
|
if ( result == GTK_RESPONSE_YES ) {
|
|
processMessageDialogResult("Yes");
|
|
} else if ( result == GTK_RESPONSE_NO ) {
|
|
processMessageDialogResult("No");
|
|
} else if ( result == GTK_RESPONSE_OK ) {
|
|
processMessageDialogResult("OK");
|
|
} else if ( result == GTK_RESPONSE_CANCEL ) {
|
|
processMessageDialogResult("Cancel");
|
|
} else {
|
|
processMessageDialogResult("");
|
|
}
|
|
|
|
gtk_widget_destroy(dialog);
|
|
free(options->title);
|
|
free(options->message);
|
|
}
|
|
|
|
void extern processOpenFileResult(void*);
|
|
|
|
typedef struct OpenFileDialogOptions {
|
|
GtkWindow* window;
|
|
char* title;
|
|
char* defaultFilename;
|
|
char* defaultDirectory;
|
|
int createDirectories;
|
|
int multipleFiles;
|
|
int showHiddenFiles;
|
|
GtkFileChooserAction action;
|
|
GtkFileFilter** filters;
|
|
} OpenFileDialogOptions;
|
|
|
|
GtkFileFilter** allocFileFilterArray(size_t ln) {
|
|
return (GtkFileFilter**) malloc(ln * sizeof(GtkFileFilter*));
|
|
}
|
|
|
|
void freeFileFilterArray(GtkFileFilter** filters) {
|
|
free(filters);
|
|
}
|
|
|
|
void opendialog(void *data) {
|
|
struct OpenFileDialogOptions *options = data;
|
|
char *label = "_Open";
|
|
if (options->action == GTK_FILE_CHOOSER_ACTION_SAVE) {
|
|
label = "_Save";
|
|
}
|
|
GtkWidget *dlgWidget = gtk_file_chooser_dialog_new(options->title, options->window, options->action,
|
|
"_Cancel", GTK_RESPONSE_CANCEL,
|
|
label, GTK_RESPONSE_ACCEPT,
|
|
NULL);
|
|
|
|
GtkFileChooser *fc = GTK_FILE_CHOOSER(dlgWidget);
|
|
// filters
|
|
if (options->filters != 0) {
|
|
int index = 0;
|
|
GtkFileFilter* thisFilter;
|
|
while(options->filters[index] != NULL) {
|
|
thisFilter = options->filters[index];
|
|
gtk_file_chooser_add_filter(fc, thisFilter);
|
|
index++;
|
|
}
|
|
}
|
|
|
|
gtk_file_chooser_set_local_only(fc, FALSE);
|
|
|
|
if (options->multipleFiles == 1) {
|
|
gtk_file_chooser_set_select_multiple(fc, TRUE);
|
|
}
|
|
gtk_file_chooser_set_do_overwrite_confirmation(fc, TRUE);
|
|
if (options->createDirectories == 1) {
|
|
gtk_file_chooser_set_create_folders(fc, TRUE);
|
|
}
|
|
if (options->showHiddenFiles == 1) {
|
|
gtk_file_chooser_set_show_hidden(fc, TRUE);
|
|
}
|
|
|
|
if (options->defaultDirectory != NULL) {
|
|
gtk_file_chooser_set_current_folder (fc, options->defaultDirectory);
|
|
free(options->defaultDirectory);
|
|
}
|
|
|
|
if (options->action == GTK_FILE_CHOOSER_ACTION_SAVE) {
|
|
if (options->defaultFilename != NULL) {
|
|
gtk_file_chooser_set_current_name(fc, options->defaultFilename);
|
|
free(options->defaultFilename);
|
|
}
|
|
}
|
|
|
|
gint response = gtk_dialog_run(GTK_DIALOG(dlgWidget));
|
|
|
|
// Max 1024 files to select
|
|
char** result = calloc(1024, sizeof(char*));
|
|
int resultIndex = 0;
|
|
|
|
if (response == GTK_RESPONSE_ACCEPT) {
|
|
GSList* filenames = gtk_file_chooser_get_filenames(fc);
|
|
GSList *iter = filenames;
|
|
while(iter) {
|
|
result[resultIndex++] = (char *)iter->data;
|
|
iter = g_slist_next(iter);
|
|
if (resultIndex == 1024) {
|
|
break;
|
|
}
|
|
}
|
|
processOpenFileResult(result);
|
|
iter = filenames;
|
|
while(iter) {
|
|
g_free(iter->data);
|
|
iter = g_slist_next(iter);
|
|
}
|
|
} else {
|
|
processOpenFileResult(result);
|
|
}
|
|
free(result);
|
|
|
|
// Release filters
|
|
if (options->filters != NULL) {
|
|
int index = 0;
|
|
GtkFileFilter* thisFilter;
|
|
while(options->filters[index] != 0) {
|
|
thisFilter = options->filters[index];
|
|
g_object_unref(thisFilter);
|
|
index++;
|
|
}
|
|
freeFileFilterArray(options->filters);
|
|
}
|
|
gtk_widget_destroy(dlgWidget);
|
|
free(options->title);
|
|
}
|
|
|
|
GtkFileFilter* newFileFilter() {
|
|
GtkFileFilter* result = gtk_file_filter_new();
|
|
g_object_ref(result);
|
|
return result;
|
|
}
|
|
|
|
typedef struct RGBAOptions {
|
|
uint8_t r;
|
|
uint8_t g;
|
|
uint8_t b;
|
|
uint8_t a;
|
|
void *webview;
|
|
} RGBAOptions;
|
|
|
|
void setRGBA(void* data) {
|
|
RGBAOptions* options = (RGBAOptions*)data;
|
|
GdkRGBA colour = {options->r / 255.0, options->g / 255.0, options->b / 255.0, options->a / 255.0};
|
|
webkit_web_view_set_background_color(WEBKIT_WEB_VIEW(options->webview), &colour);
|
|
}
|
|
|
|
typedef struct SetTitleArgs {
|
|
GtkWindow* window;
|
|
char* title;
|
|
} SetTitleArgs;
|
|
|
|
gboolean setTitle(gpointer data) {
|
|
SetTitleArgs* args = (SetTitleArgs*)data;
|
|
gtk_window_set_title(args->window, args->title);
|
|
free((void*)args->title);
|
|
free((void*)data);
|
|
|
|
return G_SOURCE_REMOVE;
|
|
}
|
|
|
|
void SetTitle(GtkWindow* window, char* title) {
|
|
SetTitleArgs* args = malloc(sizeof(SetTitleArgs));
|
|
args->window = window;
|
|
args->title = title;
|
|
ExecuteOnMainThread(setTitle, (gpointer)args);
|
|
}
|
|
|
|
typedef struct SetPositionArgs {
|
|
int x;
|
|
int y;
|
|
void* window;
|
|
} SetPositionArgs;
|
|
|
|
gboolean setPosition(gpointer data) {
|
|
SetPositionArgs* args = (SetPositionArgs*)data;
|
|
gtk_window_move((GtkWindow*)args->window, args->x, args->y);
|
|
free(args);
|
|
|
|
return G_SOURCE_REMOVE;
|
|
}
|
|
|
|
void SetPosition(void* window, int x, int y) {
|
|
GdkRectangle monitorDimensions = getCurrentMonitorGeometry(window);
|
|
SetPositionArgs* args = malloc(sizeof(SetPositionArgs));
|
|
args->window = window;
|
|
args->x = monitorDimensions.x + x;
|
|
args->y = monitorDimensions.y + y;
|
|
ExecuteOnMainThread(setPosition, (gpointer)args);
|
|
}
|
|
|
|
gboolean Show(gpointer data) {
|
|
gtk_widget_show((GtkWidget*)data);
|
|
|
|
return G_SOURCE_REMOVE;
|
|
}
|
|
|
|
gboolean Hide(gpointer data) {
|
|
gtk_widget_hide((GtkWidget*)data);
|
|
|
|
return G_SOURCE_REMOVE;
|
|
}
|
|
|
|
gboolean Maximise(gpointer data) {
|
|
gtk_window_maximize((GtkWindow*)data);
|
|
|
|
return G_SOURCE_REMOVE;
|
|
}
|
|
|
|
gboolean UnMaximise(gpointer data) {
|
|
gtk_window_unmaximize((GtkWindow*)data);
|
|
|
|
return G_SOURCE_REMOVE;
|
|
}
|
|
|
|
gboolean Minimise(gpointer data) {
|
|
gtk_window_iconify((GtkWindow*)data);
|
|
|
|
return G_SOURCE_REMOVE;
|
|
}
|
|
|
|
gboolean UnMinimise(gpointer data) {
|
|
gtk_window_present((GtkWindow*)data);
|
|
|
|
return G_SOURCE_REMOVE;
|
|
}
|
|
|
|
gboolean Fullscreen(gpointer data) {
|
|
GtkWindow* window = (GtkWindow*)data;
|
|
|
|
// Get the geometry of the monitor.
|
|
GdkRectangle m = getCurrentMonitorGeometry(window);
|
|
int scale = getCurrentMonitorScaleFactor(window);
|
|
SetMinMaxSize(window, 0, 0, m.width * scale, m.height * scale);
|
|
|
|
gtk_window_fullscreen(window);
|
|
|
|
return G_SOURCE_REMOVE;
|
|
}
|
|
|
|
gboolean UnFullscreen(gpointer data) {
|
|
gtk_window_unfullscreen((GtkWindow*)data);
|
|
|
|
return G_SOURCE_REMOVE;
|
|
}
|
|
|
|
bool disableContextMenu(GtkWindow* window) {
|
|
return TRUE;
|
|
}
|
|
|
|
void DisableContextMenu(void* webview) {
|
|
contextMenuDisabled = TRUE;
|
|
g_signal_connect(WEBKIT_WEB_VIEW(webview), "context-menu", G_CALLBACK(disableContextMenu), NULL);
|
|
}
|
|
|
|
void SetWindowIcon(GtkWindow* window, const guchar* buf, gsize len) {
|
|
GdkPixbufLoader* loader = gdk_pixbuf_loader_new();
|
|
if (!loader) {
|
|
return;
|
|
}
|
|
if (gdk_pixbuf_loader_write(loader, buf, len, NULL) && gdk_pixbuf_loader_close(loader, NULL)) {
|
|
GdkPixbuf* pixbuf = gdk_pixbuf_loader_get_pixbuf(loader);
|
|
if (pixbuf) {
|
|
gtk_window_set_icon(window, pixbuf);
|
|
}
|
|
}
|
|
g_object_unref(loader);
|
|
}
|
|
|
|
*/
|
|
import "C"
|
|
import (
|
|
"strings"
|
|
"unsafe"
|
|
|
|
"github.com/wailsapp/wails/v2/internal/frontend"
|
|
"github.com/wailsapp/wails/v2/pkg/menu"
|
|
"github.com/wailsapp/wails/v2/pkg/options"
|
|
)
|
|
|
|
func gtkBool(input bool) C.gboolean {
|
|
if input {
|
|
return C.gboolean(1)
|
|
}
|
|
return C.gboolean(0)
|
|
}
|
|
|
|
type Window struct {
|
|
appoptions *options.App
|
|
debug bool
|
|
gtkWindow unsafe.Pointer
|
|
contentManager unsafe.Pointer
|
|
webview unsafe.Pointer
|
|
applicationMenu *menu.Menu
|
|
menubar *C.GtkWidget
|
|
vbox *C.GtkWidget
|
|
accels *C.GtkAccelGroup
|
|
minWidth, minHeight, maxWidth, maxHeight int
|
|
}
|
|
|
|
func bool2Cint(value bool) C.int {
|
|
if value {
|
|
return C.int(1)
|
|
}
|
|
return C.int(0)
|
|
}
|
|
|
|
func NewWindow(appoptions *options.App, debug bool) *Window {
|
|
|
|
result := &Window{
|
|
appoptions: appoptions,
|
|
debug: debug,
|
|
minHeight: appoptions.MinHeight,
|
|
minWidth: appoptions.MinWidth,
|
|
maxHeight: appoptions.MaxHeight,
|
|
maxWidth: appoptions.MaxWidth,
|
|
}
|
|
|
|
gtkWindow := C.gtk_window_new(C.GTK_WINDOW_TOPLEVEL)
|
|
C.g_object_ref_sink(C.gpointer(gtkWindow))
|
|
result.gtkWindow = unsafe.Pointer(gtkWindow)
|
|
|
|
result.vbox = C.gtk_box_new(C.GTK_ORIENTATION_VERTICAL, 0)
|
|
C.gtk_container_add(result.asGTKContainer(), result.vbox)
|
|
|
|
result.contentManager = unsafe.Pointer(C.webkit_user_content_manager_new())
|
|
external := C.CString("external")
|
|
defer C.free(unsafe.Pointer(external))
|
|
C.webkit_user_content_manager_register_script_message_handler(result.cWebKitUserContentManager(), external)
|
|
C.setupInvokeSignal(result.contentManager)
|
|
|
|
webview := C.setupWebview(result.contentManager, result.asGTKWindow(), bool2Cint(appoptions.HideWindowOnClose))
|
|
result.webview = unsafe.Pointer(webview)
|
|
buttonPressedName := C.CString("button-press-event")
|
|
defer C.free(unsafe.Pointer(buttonPressedName))
|
|
C.connectButtons(unsafe.Pointer(webview))
|
|
|
|
if debug {
|
|
C.devtoolsEnabled(unsafe.Pointer(webview), C.int(1))
|
|
} else {
|
|
C.DisableContextMenu(unsafe.Pointer(webview))
|
|
}
|
|
|
|
// Set background colour
|
|
RGBA := appoptions.RGBA
|
|
result.SetRGBA(RGBA.R, RGBA.G, RGBA.B, RGBA.A)
|
|
|
|
// Setup window
|
|
result.SetKeepAbove(appoptions.AlwaysOnTop)
|
|
result.SetResizable(!appoptions.DisableResize)
|
|
result.SetSize(appoptions.Width, appoptions.Height)
|
|
result.SetDecorated(!appoptions.Frameless)
|
|
result.SetTitle(appoptions.Title)
|
|
result.SetMinSize(appoptions.MinWidth, appoptions.MinHeight)
|
|
result.SetMaxSize(appoptions.MaxWidth, appoptions.MaxHeight)
|
|
if appoptions.Linux != nil {
|
|
if appoptions.Linux.Icon != nil {
|
|
result.SetWindowIcon(appoptions.Linux.Icon)
|
|
}
|
|
}
|
|
|
|
// Menu
|
|
result.SetApplicationMenu(appoptions.Menu)
|
|
|
|
return result
|
|
}
|
|
|
|
func (w *Window) asGTKWidget() *C.GtkWidget {
|
|
return C.GTKWIDGET(w.gtkWindow)
|
|
}
|
|
|
|
func (w *Window) asGTKWindow() *C.GtkWindow {
|
|
return C.GTKWINDOW(w.gtkWindow)
|
|
}
|
|
|
|
func (w *Window) asGTKContainer() *C.GtkContainer {
|
|
return C.GTKCONTAINER(w.gtkWindow)
|
|
}
|
|
|
|
func (w *Window) cWebKitUserContentManager() *C.WebKitUserContentManager {
|
|
return (*C.WebKitUserContentManager)(w.contentManager)
|
|
}
|
|
|
|
func (w *Window) Fullscreen() {
|
|
C.ExecuteOnMainThread(C.Fullscreen, C.gpointer(w.asGTKWindow()))
|
|
}
|
|
|
|
func (w *Window) UnFullscreen() {
|
|
if !w.IsFullScreen() {
|
|
return
|
|
}
|
|
C.ExecuteOnMainThread(C.UnFullscreen, C.gpointer(w.asGTKWindow()))
|
|
w.SetMinSize(w.minWidth, w.minHeight)
|
|
w.SetMaxSize(w.maxWidth, w.maxHeight)
|
|
}
|
|
|
|
func (w *Window) Destroy() {
|
|
C.gtk_widget_destroy(w.asGTKWidget())
|
|
C.g_object_unref(C.gpointer(w.gtkWindow))
|
|
}
|
|
|
|
func (w *Window) Close() {
|
|
C.gtk_window_close(w.asGTKWindow())
|
|
}
|
|
|
|
func (w *Window) Center() {
|
|
C.ExecuteOnMainThread(C.Center, C.gpointer(w.asGTKWindow()))
|
|
}
|
|
|
|
func (w *Window) SetPosition(x int, y int) {
|
|
C.SetPosition(unsafe.Pointer(w.asGTKWindow()), C.int(x), C.int(y))
|
|
}
|
|
|
|
func (w *Window) Size() (int, int) {
|
|
var width, height C.int
|
|
C.gtk_window_get_size(w.asGTKWindow(), &width, &height)
|
|
return int(width), int(height)
|
|
}
|
|
|
|
func (w *Window) GetPosition() (int, int) {
|
|
var width, height C.int
|
|
C.gtk_window_get_position(w.asGTKWindow(), &width, &height)
|
|
return int(width), int(height)
|
|
}
|
|
|
|
func (w *Window) SetMaxSize(maxWidth int, maxHeight int) {
|
|
w.maxHeight = maxHeight
|
|
w.maxWidth = maxWidth
|
|
C.SetMinMaxSize(w.asGTKWindow(), C.int(w.minWidth), C.int(w.minHeight), C.int(w.maxWidth), C.int(w.maxHeight))
|
|
}
|
|
|
|
func (w *Window) SetMinSize(minWidth int, minHeight int) {
|
|
w.minHeight = minHeight
|
|
w.minWidth = minWidth
|
|
C.SetMinMaxSize(w.asGTKWindow(), C.int(w.minWidth), C.int(w.minHeight), C.int(w.maxWidth), C.int(w.maxHeight))
|
|
}
|
|
|
|
func (w *Window) Show() {
|
|
C.ExecuteOnMainThread(C.Show, C.gpointer(w.asGTKWindow()))
|
|
}
|
|
|
|
func (w *Window) Hide() {
|
|
C.ExecuteOnMainThread(C.Hide, C.gpointer(w.asGTKWindow()))
|
|
}
|
|
|
|
func (w *Window) Maximise() {
|
|
C.ExecuteOnMainThread(C.Maximise, C.gpointer(w.asGTKWindow()))
|
|
}
|
|
|
|
func (w *Window) UnMaximise() {
|
|
C.ExecuteOnMainThread(C.UnMaximise, C.gpointer(w.asGTKWindow()))
|
|
}
|
|
|
|
func (w *Window) Minimise() {
|
|
C.ExecuteOnMainThread(C.Minimise, C.gpointer(w.asGTKWindow()))
|
|
}
|
|
|
|
func (w *Window) UnMinimise() {
|
|
C.ExecuteOnMainThread(C.UnMinimise, C.gpointer(w.asGTKWindow()))
|
|
}
|
|
|
|
func (w *Window) IsFullScreen() bool {
|
|
result := C.IsFullscreen(w.asGTKWidget())
|
|
if result != 0 {
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (w *Window) IsMaximised() bool {
|
|
result := C.IsMaximised(w.asGTKWidget())
|
|
return result > 0
|
|
}
|
|
|
|
func (w *Window) SetRGBA(r uint8, g uint8, b uint8, a uint8) {
|
|
data := C.RGBAOptions{
|
|
r: C.uchar(r),
|
|
g: C.uchar(g),
|
|
b: C.uchar(b),
|
|
a: C.uchar(a),
|
|
webview: w.webview,
|
|
}
|
|
invokeOnMainThread(func() { C.setRGBA(unsafe.Pointer(&data)) })
|
|
|
|
}
|
|
|
|
func (w *Window) SetWindowIcon(icon []byte) {
|
|
if len(icon) == 0 {
|
|
return
|
|
}
|
|
C.SetWindowIcon(w.asGTKWindow(), (*C.guchar)(&icon[0]), (C.gsize)(len(icon)))
|
|
}
|
|
|
|
func (w *Window) Run(url string) {
|
|
if w.menubar != nil {
|
|
C.gtk_box_pack_start(C.GTKBOX(unsafe.Pointer(w.vbox)), w.menubar, 0, 0, 0)
|
|
}
|
|
C.gtk_box_pack_start(C.GTKBOX(unsafe.Pointer(w.vbox)), C.GTKWIDGET(w.webview), 1, 1, 0)
|
|
_url := C.CString(url)
|
|
C.loadIndex(w.webview, _url)
|
|
defer C.free(unsafe.Pointer(_url))
|
|
C.gtk_widget_show_all(w.asGTKWidget())
|
|
w.Center()
|
|
switch w.appoptions.WindowStartState {
|
|
case options.Fullscreen:
|
|
w.Fullscreen()
|
|
case options.Minimised:
|
|
w.Minimise()
|
|
case options.Maximised:
|
|
w.Maximise()
|
|
}
|
|
|
|
C.gtk_main()
|
|
w.Destroy()
|
|
}
|
|
|
|
func (w *Window) SetKeepAbove(top bool) {
|
|
C.gtk_window_set_keep_above(w.asGTKWindow(), gtkBool(top))
|
|
}
|
|
|
|
func (w *Window) SetResizable(resizable bool) {
|
|
C.gtk_window_set_resizable(w.asGTKWindow(), gtkBool(resizable))
|
|
}
|
|
|
|
func (w *Window) SetSize(width int, height int) {
|
|
C.gtk_window_resize(w.asGTKWindow(), C.gint(width), C.gint(height))
|
|
}
|
|
|
|
func (w *Window) SetDecorated(frameless bool) {
|
|
C.gtk_window_set_decorated(w.asGTKWindow(), gtkBool(frameless))
|
|
}
|
|
|
|
func (w *Window) SetTitle(title string) {
|
|
C.SetTitle(w.asGTKWindow(), C.CString(title))
|
|
}
|
|
|
|
func (w *Window) ExecJS(js string) {
|
|
jscallback := C.JSCallback{
|
|
webview: w.webview,
|
|
script: C.CString(js),
|
|
}
|
|
invokeOnMainThread(func() { C.executeJS(unsafe.Pointer(&jscallback)) })
|
|
}
|
|
|
|
func (w *Window) StartDrag() {
|
|
C.StartDrag(w.webview, w.asGTKWindow())
|
|
}
|
|
|
|
func (w *Window) Quit() {
|
|
C.gtk_main_quit()
|
|
}
|
|
|
|
func (w *Window) OpenFileDialog(dialogOptions frontend.OpenDialogOptions, multipleFiles int, action C.GtkFileChooserAction) {
|
|
|
|
data := C.OpenFileDialogOptions{
|
|
window: w.asGTKWindow(),
|
|
title: C.CString(dialogOptions.Title),
|
|
multipleFiles: C.int(multipleFiles),
|
|
action: action,
|
|
}
|
|
|
|
if len(dialogOptions.Filters) > 0 {
|
|
// Create filter array
|
|
mem := NewCalloc()
|
|
arraySize := len(dialogOptions.Filters) + 1
|
|
data.filters = C.allocFileFilterArray((C.size_t)(arraySize))
|
|
filters := unsafe.Slice((**C.struct__GtkFileFilter)(unsafe.Pointer(data.filters)), arraySize)
|
|
for index, filter := range dialogOptions.Filters {
|
|
thisFilter := C.gtk_file_filter_new()
|
|
C.g_object_ref(C.gpointer(thisFilter))
|
|
if filter.DisplayName != "" {
|
|
cName := mem.String(filter.DisplayName)
|
|
C.gtk_file_filter_set_name(thisFilter, cName)
|
|
}
|
|
if filter.Pattern != "" {
|
|
for _, thisPattern := range strings.Split(filter.Pattern, ";") {
|
|
cThisPattern := mem.String(thisPattern)
|
|
C.gtk_file_filter_add_pattern(thisFilter, cThisPattern)
|
|
}
|
|
}
|
|
// Add filter to array
|
|
filters[index] = thisFilter
|
|
}
|
|
mem.Free()
|
|
filters[arraySize-1] = nil
|
|
}
|
|
|
|
if dialogOptions.CanCreateDirectories {
|
|
data.createDirectories = C.int(1)
|
|
}
|
|
|
|
if dialogOptions.ShowHiddenFiles {
|
|
data.showHiddenFiles = C.int(1)
|
|
}
|
|
|
|
if dialogOptions.DefaultFilename != "" {
|
|
data.defaultFilename = C.CString(dialogOptions.DefaultFilename)
|
|
}
|
|
|
|
if dialogOptions.DefaultDirectory != "" {
|
|
data.defaultDirectory = C.CString(dialogOptions.DefaultDirectory)
|
|
}
|
|
|
|
invokeOnMainThread(func() { C.opendialog(unsafe.Pointer(&data)) })
|
|
}
|
|
|
|
func (w *Window) MessageDialog(dialogOptions frontend.MessageDialogOptions) {
|
|
|
|
data := C.MessageDialogOptions{
|
|
window: w.gtkWindow,
|
|
title: C.CString(dialogOptions.Title),
|
|
message: C.CString(dialogOptions.Message),
|
|
}
|
|
switch dialogOptions.Type {
|
|
case frontend.InfoDialog:
|
|
data.messageType = C.int(0)
|
|
case frontend.ErrorDialog:
|
|
data.messageType = C.int(1)
|
|
case frontend.QuestionDialog:
|
|
data.messageType = C.int(2)
|
|
case frontend.WarningDialog:
|
|
data.messageType = C.int(3)
|
|
}
|
|
invokeOnMainThread(func() { C.messageDialog(unsafe.Pointer(&data)) })
|
|
}
|
|
|
|
func (w *Window) ToggleMaximise() {
|
|
if w.IsMaximised() {
|
|
w.UnMaximise()
|
|
} else {
|
|
w.Maximise()
|
|
}
|
|
}
|