mirror of
https://github.com/wailsapp/wails.git
synced 2025-05-03 06:51:26 +08:00

* get dimensions working for linux * Cleaning up some GTK code I was getting the following errors due to some bad casts. Gdk-CRITICAL **: 18:58:51.943: gdk_monitor_get_geometry: assertion 'GDK_IS_MONITOR (monitor)' failed Gdk-CRITICAL **: 18:58:51.943: gdk_display_get_monitor_at_window: assertion 'GDK_IS_DISPLAY (display)' failed This commit fixes these errors * Adding Screen namespace along with linux implementation * moving ScreenGetAll into a more appropriate place * Fixing typescript definition mistake, documentation, ordering of functions, and formatting * add ScreenGetAll to more templates * moving screen into its own javascript file * fixing bug where screen objects are not returned from the runtime function * rebuilding frontend wrapper package * adding windows implementation of ScreenGetAll * adding screen get all implementation for darwin * reverting a change that is unrelated to the work on expose-dimensions * removing duplicate comparison * changing GetNthScreen in screen API on macos To use frame instead of visibleframe to keep into account the space the the dock takes up We want to include that space in the calculation in order to keep the sizes of screens consistent across platforms * Correcting screen jsdoc It used to say it returned a single screen object. Now it says that it returns an array of screen objects * Fixing typo in function name * reverting pointless spacing change * reverting pointless spacing change Co-authored-by: Lea Anthony <lea.anthony@gmail.com> Co-authored-by: shmuel.kamensky <shmuel.kamensky@shutterfly.com>
941 lines
25 KiB
Go
941 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 setBackgroundColour(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"
|
|
"sync"
|
|
"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.BackgroundColour
|
|
result.SetBackgroundColour(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
|
|
var wg sync.WaitGroup
|
|
wg.Add(1)
|
|
invokeOnMainThread(func() {
|
|
C.gtk_window_get_size(w.asGTKWindow(), &width, &height)
|
|
wg.Done()
|
|
})
|
|
wg.Wait()
|
|
return int(width), int(height)
|
|
}
|
|
|
|
func (w *Window) GetPosition() (int, int) {
|
|
var width, height C.int
|
|
var wg sync.WaitGroup
|
|
wg.Add(1)
|
|
invokeOnMainThread(func() {
|
|
C.gtk_window_get_position(w.asGTKWindow(), &width, &height)
|
|
wg.Done()
|
|
})
|
|
wg.Wait()
|
|
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) SetBackgroundColour(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.setBackgroundColour(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()
|
|
}
|
|
}
|