mirror of
https://github.com/wailsapp/wails.git
synced 2026-03-14 14:45:49 +01:00
* fix(v3): add nil check to Focus() to prevent SIGSEGV after Hide The Focus() method was missing a nil/destroyed check on w.impl, causing a SIGSEGV when Focus() is called on a window that has been hidden and potentially destroyed (e.g., when clicking the dock icon after hiding the window on macOS). This aligns Focus() with Show() and Hide() which already have proper guards against nil/destroyed window implementations. Fixes #4890 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix(v3/linux): use g_bytes_new instead of g_bytes_new_static for Go slices g_bytes_new_static expects truly static data that is never freed or moved, but Go's garbage collector can move or free slice memory at any time. This causes SIGSEGV crashes in the GTK event loop when the GC runs and frees memory that GTK still references. The fix replaces g_bytes_new_static with g_bytes_new, which copies the data to C-owned memory that is safe from Go's GC. Also fixes memory leak in setIcon() by adding proper g_bytes_unref and g_object_unref calls for the GBytes and stream objects. Changes: - linux_cgo.go: Replace g_bytes_new_static with g_bytes_new (4 places) - linux_cgo.go: Add cleanup in setIcon() for gbytes and stream - linux_purego.go: Add gBytesNew binding and use it instead of static Fixes #4864 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix(linux): add empty slice guards to prevent panic Adds guards to check for empty slices before taking &slice[0] which would panic on empty input. Affects setIcon(), menuItemAddProperties(), menuItemSetBitmap(), and runQuestionDialog() in both CGO and purego. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> --------- Co-authored-by: ddmoney420 <ddmoney420@users.noreply.github.com> Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com> Co-authored-by: Lea Anthony <lea.anthony@gmail.com>
1275 lines
41 KiB
Go
1275 lines
41 KiB
Go
//go:build linux && purego
|
|
|
|
package application
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"unsafe"
|
|
|
|
"github.com/ebitengine/purego"
|
|
"github.com/wailsapp/wails/v3/internal/assetserver/webview"
|
|
"github.com/wailsapp/wails/v3/pkg/events"
|
|
)
|
|
|
|
type windowPointer uintptr
|
|
type identifier uint
|
|
type pointer uintptr
|
|
|
|
// type GSList uintptr
|
|
type GSList struct {
|
|
data pointer
|
|
next *GSList
|
|
}
|
|
|
|
type GSListPointer *GSList
|
|
|
|
const (
|
|
nilPointer pointer = 0
|
|
)
|
|
|
|
const (
|
|
GSourceRemove int = 0
|
|
|
|
// https://gitlab.gnome.org/GNOME/gtk/-/blob/gtk-3-24/gdk/gdkwindow.h#L121
|
|
GdkHintMinSize = 1 << 1
|
|
GdkHintMaxSize = 1 << 2
|
|
// https://gitlab.gnome.org/GNOME/gtk/-/blob/gtk-3-24/gdk/gdkevents.h#L512
|
|
GdkWindowStateIconified = 1 << 1
|
|
GdkWindowStateMaximized = 1 << 2
|
|
GdkWindowStateFullscreen = 1 << 4
|
|
|
|
// https://gitlab.gnome.org/GNOME/gtk/-/blob/gtk-3-24/gtk/gtkmessagedialog.h#L87
|
|
GtkButtonsNone int = 0
|
|
GtkButtonsOk = 1
|
|
GtkButtonsClose = 2
|
|
GtkButtonsCancel = 3
|
|
GtkButtonsYesNo = 4
|
|
GtkButtonsOkCancel = 5
|
|
|
|
// https://gitlab.gnome.org/GNOME/gtk/-/blob/gtk-3-24/gtk/gtkdialog.h#L36
|
|
GtkDialogModal = 1 << 0
|
|
GtkDialogDestroyWithParent = 1 << 1
|
|
GtkDialogUseHeaderBar = 1 << 2 // actions in header bar instead of action area
|
|
|
|
GtkOrientationVertical = 1
|
|
|
|
// enum GtkMessageType
|
|
GtkMessageInfo = 0
|
|
GtkMessageWarning = 1
|
|
GtkMessageQuestion = 2
|
|
GtkMessageError = 3
|
|
)
|
|
|
|
type GdkGeometry struct {
|
|
minWidth int32
|
|
minHeight int32
|
|
maxWidth int32
|
|
maxHeight int32
|
|
baseWidth int32
|
|
baseHeight int32
|
|
widthInc int32
|
|
heightInc int32
|
|
padding int32
|
|
minAspect float64
|
|
maxAspect float64
|
|
GdkGravity int32
|
|
}
|
|
|
|
var (
|
|
nilRadioGroup GSListPointer = nil
|
|
gtkSignalHandlers map[pointer]uint = map[pointer]uint{}
|
|
gtkSignalToMenuItem map[pointer]*MenuItem = map[pointer]*MenuItem{}
|
|
mainThreadId uint64
|
|
)
|
|
|
|
const (
|
|
// TODO: map distro => so filename - with fallback?
|
|
gtk3 = "libgtk-3.so.0"
|
|
gtk4 = "libgtk-4.so.1"
|
|
webkit4 = "libwebkit2gtk-4.1.so.0"
|
|
)
|
|
|
|
var (
|
|
gtk uintptr
|
|
gtkVersion int
|
|
webkit uintptr
|
|
|
|
// function references
|
|
gApplicationHold func(pointer)
|
|
gApplicationQuit func(pointer)
|
|
gApplicationName func() string
|
|
gApplicationRelease func(pointer)
|
|
gApplicationRun func(pointer, int, []string) int
|
|
gBytesNew func(uintptr, int) uintptr
|
|
gBytesNewStatic func(uintptr, int) uintptr
|
|
gBytesUnref func(uintptr)
|
|
gFree func(pointer)
|
|
gIdleAdd func(uintptr)
|
|
gListFree func(*GList)
|
|
gObjectRefSink func(pointer)
|
|
gObjectUnref func(pointer)
|
|
gSignalConnectData func(pointer, string, uintptr, pointer, bool, int) int
|
|
gSignalConnectObject func(pointer, string, pointer, pointer, int) uint
|
|
gSignalHandlerBlock func(pointer, uint)
|
|
gSignalHandlerUnblock func(pointer, uint)
|
|
gThreadSelf func() uint64
|
|
|
|
// gdk functions
|
|
gdkDisplayGetMonitor func(pointer, int) pointer
|
|
gdkDisplayGetMonitorAtWindow func(pointer, pointer) pointer
|
|
gdkDisplayGetNMonitors func(pointer) int
|
|
gdkMonitorGetGeometry func(pointer, pointer) pointer
|
|
gdkMonitorGetScaleFactor func(pointer) int
|
|
gdkMonitorIsPrimary func(pointer) int
|
|
gdkPixbufNewFromBytes func(uintptr, int, int, int, int, int, int) pointer
|
|
gdkRgbaParse func(pointer, string) bool
|
|
gdkScreenGetRgbaVisual func(pointer) pointer
|
|
gdkScreenIsComposited func(pointer) int
|
|
gdkWindowGetState func(pointer) int
|
|
gdkWindowGetDisplay func(pointer) pointer
|
|
|
|
// gtk functions
|
|
gtkApplicationNew func(string, uint) pointer
|
|
gtkApplicationGetActiveWindow func(pointer) pointer
|
|
gtkApplicationGetWindows func(pointer) *GList
|
|
gtkApplicationWindowNew func(pointer) pointer
|
|
gtkBoxNew func(int, int) pointer
|
|
gtkBoxPackStart func(pointer, pointer, int, int, int)
|
|
gtkCheckMenuItemGetActive func(pointer) int
|
|
gtkCheckMenuItemNewWithLabel func(string) pointer
|
|
gtkCheckMenuItemSetActive func(pointer, int)
|
|
gtkContainerAdd func(pointer, pointer)
|
|
gtkContainerGetChildren func(pointer) *GList
|
|
gtkContainerRemove func(pointer, pointer)
|
|
gtkCSSProviderLoadFromData func(pointer, string, int, pointer)
|
|
gtkCSSProviderNew func() pointer
|
|
gtkDialogAddButton func(pointer, string, int)
|
|
gtkDialogGetContentArea func(pointer) pointer
|
|
gtkDialogRun func(pointer) int
|
|
gtkDialogSetDefaultResponse func(pointer, int)
|
|
gtkDragDestSet func(pointer, uint, pointer, uint, uint)
|
|
gtkFileChooserAddFilter func(pointer, pointer)
|
|
gtkFileChooserDialogNew func(string, pointer, int, string, int, string, int, pointer) pointer
|
|
gtkFileChooserGetFilenames func(pointer) *GSList
|
|
gtkFileChooserSetAction func(pointer, int)
|
|
gtkFileChooserSetCreateFolders func(pointer, bool)
|
|
gtkFileChooserSetCurrentFolder func(pointer, string)
|
|
gtkFileChooserSetCurrentName func(pointer, string)
|
|
gtkFileChooserSetSelectMultiple func(pointer, bool)
|
|
gtkFileChooserSetShowHidden func(pointer, bool)
|
|
gtkFileFilterAddPattern func(pointer, string)
|
|
gtkFileFilterNew func() pointer
|
|
gtkFileFilterSetName func(pointer, string)
|
|
gtkImageNewFromPixbuf func(pointer) pointer
|
|
gtkMenuBarNew func() pointer
|
|
gtkMenuItemNewWithLabel func(string) pointer
|
|
gtkMenuItemSetLabel func(pointer, string)
|
|
gtkMenuItemSetSubmenu func(pointer, pointer)
|
|
gtkMenuNew func() pointer
|
|
gtkMenuShellAppend func(pointer, pointer)
|
|
gtkMessageDialogNew func(pointer, int, int, int, string) pointer
|
|
gtkRadioMenuItemGetGroup func(pointer) GSListPointer
|
|
gtkRadioMenuItemNewWithLabel func(GSListPointer, string) pointer
|
|
gtkSeparatorMenuItemNew func() pointer
|
|
gtkStyleContextAddProvider func(pointer, pointer, int)
|
|
gtkTargetEntryFree func(pointer)
|
|
gtkTargetEntryNew func(string, int, uint) pointer
|
|
gtkWidgetDestroy func(pointer)
|
|
gtkWidgetGetDisplay func(pointer) pointer
|
|
gtkWidgetGetScreen func(pointer) pointer
|
|
gtkWidgetGetStyleContext func(pointer) pointer
|
|
gtkWidgetGetWindow func(pointer) pointer
|
|
gtkWidgetHide func(pointer)
|
|
gtkWidgetIsVisible func(pointer) bool
|
|
gtkWidgetRealize func(pointer)
|
|
gtkWidgetShow func(pointer)
|
|
gtkWidgetShowAll func(pointer)
|
|
gtkWidgetSetAppPaintable func(pointer, int)
|
|
gtkWidgetSetName func(pointer, string)
|
|
gtkWidgetSetSensitive func(pointer, int)
|
|
gtkWidgetSetToolTipText func(pointer, string)
|
|
gtkWidgetSetVisual func(pointer, pointer)
|
|
gtkWindowClose func(pointer)
|
|
gtkWindowFullScreen func(pointer)
|
|
gtkWindowGetPosition func(pointer, *int, *int) bool
|
|
gtkWindowGetSize func(pointer, *int, *int)
|
|
gtkWindowHasToplevelFocus func(pointer) int
|
|
gtkWindowKeepAbove func(pointer, bool)
|
|
gtkWindowMaximize func(pointer)
|
|
gtkWindowMinimize func(pointer)
|
|
gtkWindowMove func(pointer, int, int)
|
|
gtkWindowPresent func(pointer)
|
|
gtkWindowResize func(pointer, int, int)
|
|
gtkWindowSetDecorated func(pointer, int)
|
|
gtkWindowSetGeometryHints func(pointer, pointer, pointer, int)
|
|
gtkWindowSetKeepAbove func(pointer, bool)
|
|
gtkWindowSetResizable func(pointer, bool)
|
|
gtkWindowSetTitle func(pointer, string)
|
|
gtkWindowUnfullscreen func(pointer)
|
|
gtkWindowUnmaximize func(pointer)
|
|
|
|
// webkit
|
|
webkitNewWithUserContentManager func(pointer) pointer
|
|
webkitRegisterUriScheme func(pointer, string, pointer, int, int)
|
|
webkitSettingsGetEnableDeveloperExtras func(pointer) bool
|
|
webkitSettingsSetHardwareAccelerationPolicy func(pointer, int)
|
|
webkitSettingsSetEnableDeveloperExtras func(pointer, bool)
|
|
webkitSettingsSetUserAgentWithApplicationDetails func(pointer, string, string)
|
|
webkitUserContentManagerNew func() pointer
|
|
webkitUserContentManagerRegisterScriptMessageHandler func(pointer, string)
|
|
webkitWebContextGetDefault func() pointer
|
|
webkitWebViewEvaluateJS func(pointer, string, int, pointer, string, pointer, pointer, pointer)
|
|
webkitWebViewGetSettings func(pointer) pointer
|
|
webkitWebViewGetZoom func(pointer) float64
|
|
webkitWebViewLoadAlternateHTML func(pointer, string, string, *string)
|
|
webkitWebViewLoadUri func(pointer, string)
|
|
webkitWebViewSetBackgroundColor func(pointer, pointer)
|
|
webkitWebViewSetSettings func(pointer, pointer)
|
|
webkitWebViewSetZoomLevel func(pointer, float64)
|
|
)
|
|
|
|
func init() {
|
|
// needed for GTK4 to function
|
|
_ = os.Setenv("GDK_BACKEND", "x11")
|
|
var err error
|
|
|
|
// gtk, err = purego.Dlopen(gtk4, purego.RTLD_NOW|purego.RTLD_GLOBAL)
|
|
// if err == nil {
|
|
// version = 4
|
|
// return
|
|
// }
|
|
|
|
// log.Println("Failed to open GTK4: Falling back to GTK3")
|
|
|
|
gtk, err = purego.Dlopen(gtk3, purego.RTLD_NOW|purego.RTLD_GLOBAL)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
gtkVersion = 3
|
|
|
|
webkit, err = purego.Dlopen(webkit4, purego.RTLD_NOW|purego.RTLD_GLOBAL)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
// Function registration
|
|
// GLib
|
|
purego.RegisterLibFunc(&gApplicationHold, gtk, "g_application_hold")
|
|
purego.RegisterLibFunc(&gApplicationName, gtk, "g_get_application_name")
|
|
purego.RegisterLibFunc(&gApplicationQuit, gtk, "g_application_quit")
|
|
purego.RegisterLibFunc(&gApplicationRelease, gtk, "g_application_release")
|
|
purego.RegisterLibFunc(&gApplicationRun, gtk, "g_application_run")
|
|
purego.RegisterLibFunc(&gBytesNew, gtk, "g_bytes_new")
|
|
purego.RegisterLibFunc(&gBytesNewStatic, gtk, "g_bytes_new_static")
|
|
purego.RegisterLibFunc(&gBytesUnref, gtk, "g_bytes_unref")
|
|
purego.RegisterLibFunc(&gFree, gtk, "g_free")
|
|
purego.RegisterLibFunc(&gIdleAdd, gtk, "g_idle_add")
|
|
purego.RegisterLibFunc(&gListFree, gtk, "g_list_free")
|
|
purego.RegisterLibFunc(&gObjectRefSink, gtk, "g_object_ref_sink")
|
|
purego.RegisterLibFunc(&gObjectUnref, gtk, "g_object_unref")
|
|
purego.RegisterLibFunc(&gSignalConnectData, gtk, "g_signal_connect_data")
|
|
purego.RegisterLibFunc(&gSignalConnectObject, gtk, "g_signal_connect_object")
|
|
purego.RegisterLibFunc(&gSignalHandlerBlock, gtk, "g_signal_handler_block")
|
|
purego.RegisterLibFunc(&gSignalHandlerUnblock, gtk, "g_signal_handler_unblock")
|
|
purego.RegisterLibFunc(&gThreadSelf, gtk, "g_thread_self")
|
|
|
|
// GDK
|
|
purego.RegisterLibFunc(&gdkDisplayGetMonitor, gtk, "gdk_display_get_monitor")
|
|
purego.RegisterLibFunc(&gdkDisplayGetMonitorAtWindow, gtk, "gdk_display_get_monitor_at_window")
|
|
purego.RegisterLibFunc(&gdkDisplayGetNMonitors, gtk, "gdk_display_get_n_monitors")
|
|
purego.RegisterLibFunc(&gdkMonitorGetGeometry, gtk, "gdk_monitor_get_geometry")
|
|
purego.RegisterLibFunc(&gdkMonitorGetScaleFactor, gtk, "gdk_monitor_get_scale_factor")
|
|
purego.RegisterLibFunc(&gdkMonitorIsPrimary, gtk, "gdk_monitor_is_primary")
|
|
purego.RegisterLibFunc(&gdkPixbufNewFromBytes, gtk, "gdk_pixbuf_new_from_bytes")
|
|
purego.RegisterLibFunc(&gdkRgbaParse, gtk, "gdk_rgba_parse")
|
|
purego.RegisterLibFunc(&gdkScreenGetRgbaVisual, gtk, "gdk_screen_get_rgba_visual")
|
|
purego.RegisterLibFunc(&gdkScreenIsComposited, gtk, "gdk_screen_is_composited")
|
|
purego.RegisterLibFunc(&gdkWindowGetDisplay, gtk, "gdk_window_get_display")
|
|
purego.RegisterLibFunc(&gdkWindowGetState, gtk, "gdk_window_get_state")
|
|
|
|
// GTK3
|
|
purego.RegisterLibFunc(>kApplicationNew, gtk, "gtk_application_new")
|
|
purego.RegisterLibFunc(>kApplicationGetActiveWindow, gtk, "gtk_application_get_active_window")
|
|
purego.RegisterLibFunc(>kApplicationGetWindows, gtk, "gtk_application_get_windows")
|
|
purego.RegisterLibFunc(>kApplicationWindowNew, gtk, "gtk_application_window_new")
|
|
purego.RegisterLibFunc(>kBoxNew, gtk, "gtk_box_new")
|
|
purego.RegisterLibFunc(>kBoxPackStart, gtk, "gtk_box_pack_start")
|
|
purego.RegisterLibFunc(>kCheckMenuItemGetActive, gtk, "gtk_check_menu_item_get_active")
|
|
purego.RegisterLibFunc(>kCheckMenuItemNewWithLabel, gtk, "gtk_check_menu_item_new_with_label")
|
|
purego.RegisterLibFunc(>kCheckMenuItemSetActive, gtk, "gtk_check_menu_item_set_active")
|
|
purego.RegisterLibFunc(>kContainerAdd, gtk, "gtk_container_add")
|
|
purego.RegisterLibFunc(>kContainerGetChildren, gtk, "gtk_container_get_children")
|
|
purego.RegisterLibFunc(>kContainerRemove, gtk, "gtk_container_remove")
|
|
purego.RegisterLibFunc(>kCSSProviderLoadFromData, gtk, "gtk_css_provider_load_from_data")
|
|
purego.RegisterLibFunc(>kDialogAddButton, gtk, "gtk_dialog_add_button")
|
|
purego.RegisterLibFunc(>kDialogGetContentArea, gtk, "gtk_dialog_get_content_area")
|
|
purego.RegisterLibFunc(>kDialogRun, gtk, "gtk_dialog_run")
|
|
purego.RegisterLibFunc(>kDialogSetDefaultResponse, gtk, "gtk_dialog_set_default_response")
|
|
purego.RegisterLibFunc(>kDragDestSet, gtk, "gtk_drag_dest_set")
|
|
purego.RegisterLibFunc(>kFileChooserAddFilter, gtk, "gtk_file_chooser_add_filter")
|
|
purego.RegisterLibFunc(>kFileChooserDialogNew, gtk, "gtk_file_chooser_dialog_new")
|
|
purego.RegisterLibFunc(>kFileChooserGetFilenames, gtk, "gtk_file_chooser_get_filenames")
|
|
purego.RegisterLibFunc(>kFileChooserSetAction, gtk, "gtk_file_chooser_set_action")
|
|
purego.RegisterLibFunc(>kFileChooserSetCreateFolders, gtk, "gtk_file_chooser_set_create_folders")
|
|
purego.RegisterLibFunc(>kFileChooserSetCurrentFolder, gtk, "gtk_file_chooser_set_current_folder")
|
|
purego.RegisterLibFunc(>kFileChooserSetCurrentName, gtk, "gtk_file_chooser_set_current_name")
|
|
purego.RegisterLibFunc(>kFileChooserSetSelectMultiple, gtk, "gtk_file_chooser_set_select_multiple")
|
|
purego.RegisterLibFunc(>kFileChooserSetShowHidden, gtk, "gtk_file_chooser_set_show_hidden")
|
|
purego.RegisterLibFunc(>kFileFilterAddPattern, gtk, "gtk_file_filter_add_pattern")
|
|
purego.RegisterLibFunc(>kFileFilterNew, gtk, "gtk_file_filter_new")
|
|
purego.RegisterLibFunc(>kFileFilterSetName, gtk, "gtk_file_filter_set_name")
|
|
purego.RegisterLibFunc(>kImageNewFromPixbuf, gtk, "gtk_image_new_from_pixbuf")
|
|
purego.RegisterLibFunc(>kMenuItemSetLabel, gtk, "gtk_menu_item_set_label")
|
|
purego.RegisterLibFunc(>kMenuBarNew, gtk, "gtk_menu_bar_new")
|
|
purego.RegisterLibFunc(>kMenuItemNewWithLabel, gtk, "gtk_menu_item_new_with_label")
|
|
purego.RegisterLibFunc(>kMenuItemSetSubmenu, gtk, "gtk_menu_item_set_submenu")
|
|
purego.RegisterLibFunc(>kMenuNew, gtk, "gtk_menu_new")
|
|
purego.RegisterLibFunc(>kMenuShellAppend, gtk, "gtk_menu_shell_append")
|
|
purego.RegisterLibFunc(>kMessageDialogNew, gtk, "gtk_message_dialog_new")
|
|
purego.RegisterLibFunc(>kRadioMenuItemGetGroup, gtk, "gtk_radio_menu_item_get_group")
|
|
purego.RegisterLibFunc(>kRadioMenuItemNewWithLabel, gtk, "gtk_radio_menu_item_new_with_label")
|
|
purego.RegisterLibFunc(>kSeparatorMenuItemNew, gtk, "gtk_separator_menu_item_new")
|
|
purego.RegisterLibFunc(>kStyleContextAddProvider, gtk, "gtk_style_context_add_provider")
|
|
purego.RegisterLibFunc(>kTargetEntryFree, gtk, "gtk_target_entry_free")
|
|
purego.RegisterLibFunc(>kTargetEntryNew, gtk, "gtk_target_entry_new")
|
|
purego.RegisterLibFunc(>kWidgetDestroy, gtk, "gtk_widget_destroy")
|
|
purego.RegisterLibFunc(>kWidgetGetDisplay, gtk, "gtk_widget_get_display")
|
|
purego.RegisterLibFunc(>kWidgetGetScreen, gtk, "gtk_widget_get_screen")
|
|
purego.RegisterLibFunc(>kWidgetGetStyleContext, gtk, "gtk_widget_get_style_context")
|
|
purego.RegisterLibFunc(>kWidgetGetWindow, gtk, "gtk_widget_get_window")
|
|
purego.RegisterLibFunc(>kWidgetHide, gtk, "gtk_widget_hide")
|
|
purego.RegisterLibFunc(>kWidgetIsVisible, gtk, "gtk_widget_is_visible")
|
|
purego.RegisterLibFunc(>kWidgetRealize, gtk, "gtk_widget_realize")
|
|
purego.RegisterLibFunc(>kWidgetSetAppPaintable, gtk, "gtk_widget_set_app_paintable")
|
|
purego.RegisterLibFunc(>kWidgetSetName, gtk, "gtk_widget_set_name")
|
|
purego.RegisterLibFunc(>kWidgetSetSensitive, gtk, "gtk_widget_set_sensitive")
|
|
purego.RegisterLibFunc(>kWidgetSetToolTipText, gtk, "gtk_widget_set_tooltip_text")
|
|
purego.RegisterLibFunc(>kWidgetSetVisual, gtk, "gtk_widget_set_visual")
|
|
purego.RegisterLibFunc(>kWidgetShow, gtk, "gtk_widget_show")
|
|
purego.RegisterLibFunc(>kWidgetShowAll, gtk, "gtk_widget_show_all")
|
|
purego.RegisterLibFunc(>kWindowFullScreen, gtk, "gtk_window_fullscreen")
|
|
purego.RegisterLibFunc(>kWindowClose, gtk, "gtk_window_close")
|
|
purego.RegisterLibFunc(>kWindowGetPosition, gtk, "gtk_window_get_position")
|
|
purego.RegisterLibFunc(>kWindowGetSize, gtk, "gtk_window_get_size")
|
|
purego.RegisterLibFunc(>kWindowMaximize, gtk, "gtk_window_maximize")
|
|
purego.RegisterLibFunc(>kWindowMove, gtk, "gtk_window_move")
|
|
purego.RegisterLibFunc(>kWindowPresent, gtk, "gtk_window_present")
|
|
//purego.RegisterLibFunc(>kWindowPresent, gtk, "gtk_window_unminimize") // gtk4
|
|
purego.RegisterLibFunc(>kWindowHasToplevelFocus, gtk, "gtk_window_has_toplevel_focus")
|
|
purego.RegisterLibFunc(>kWindowMinimize, gtk, "gtk_window_iconify") // gtk3
|
|
// purego.RegisterLibFunc(>kWindowMinimize, gtk, "gtk_window_minimize") // gtk4
|
|
purego.RegisterLibFunc(>kWindowResize, gtk, "gtk_window_resize")
|
|
purego.RegisterLibFunc(>kWindowSetGeometryHints, gtk, "gtk_window_set_geometry_hints")
|
|
purego.RegisterLibFunc(>kWindowSetDecorated, gtk, "gtk_window_set_decorated")
|
|
purego.RegisterLibFunc(>kWindowKeepAbove, gtk, "gtk_window_set_keep_above")
|
|
purego.RegisterLibFunc(>kWindowSetResizable, gtk, "gtk_window_set_resizable")
|
|
purego.RegisterLibFunc(>kWindowSetTitle, gtk, "gtk_window_set_title")
|
|
purego.RegisterLibFunc(>kWindowUnfullscreen, gtk, "gtk_window_unfullscreen")
|
|
purego.RegisterLibFunc(>kWindowUnmaximize, gtk, "gtk_window_unmaximize")
|
|
|
|
// webkit
|
|
purego.RegisterLibFunc(&webkitNewWithUserContentManager, webkit, "webkit_web_view_new_with_user_content_manager")
|
|
purego.RegisterLibFunc(&webkitRegisterUriScheme, webkit, "webkit_web_context_register_uri_scheme")
|
|
purego.RegisterLibFunc(&webkitSettingsGetEnableDeveloperExtras, webkit, "webkit_settings_get_enable_developer_extras")
|
|
purego.RegisterLibFunc(&webkitSettingsSetEnableDeveloperExtras, webkit, "webkit_settings_set_enable_developer_extras")
|
|
purego.RegisterLibFunc(&webkitSettingsSetHardwareAccelerationPolicy, webkit, "webkit_settings_set_hardware_acceleration_policy")
|
|
purego.RegisterLibFunc(&webkitSettingsSetUserAgentWithApplicationDetails, webkit, "webkit_settings_set_user_agent_with_application_details")
|
|
purego.RegisterLibFunc(&webkitUserContentManagerNew, webkit, "webkit_user_content_manager_new")
|
|
purego.RegisterLibFunc(&webkitUserContentManagerRegisterScriptMessageHandler, webkit, "webkit_user_content_manager_register_script_message_handler")
|
|
purego.RegisterLibFunc(&webkitWebContextGetDefault, webkit, "webkit_web_context_get_default")
|
|
purego.RegisterLibFunc(&webkitWebViewEvaluateJS, webkit, "webkit_web_view_evaluate_javascript")
|
|
purego.RegisterLibFunc(&webkitWebViewGetSettings, webkit, "webkit_web_view_get_settings")
|
|
purego.RegisterLibFunc(&webkitWebViewGetZoom, webkit, "webkit_web_view_get_zoom_level")
|
|
purego.RegisterLibFunc(&webkitWebViewLoadAlternateHTML, webkit, "webkit_web_view_load_alternate_html")
|
|
purego.RegisterLibFunc(&webkitWebViewLoadUri, webkit, "webkit_web_view_load_uri")
|
|
purego.RegisterLibFunc(&webkitWebViewSetBackgroundColor, webkit, "webkit_web_view_set_background_color")
|
|
purego.RegisterLibFunc(&webkitWebViewSetSettings, webkit, "webkit_web_view_set_settings")
|
|
purego.RegisterLibFunc(&webkitWebViewSetZoomLevel, webkit, "webkit_web_view_set_zoom_level")
|
|
}
|
|
|
|
// mainthread stuff
|
|
func dispatchOnMainThread(id uint) {
|
|
gIdleAdd(purego.NewCallback(func(pointer) int {
|
|
executeOnMainThread(id)
|
|
return GSourceRemove
|
|
}))
|
|
}
|
|
|
|
// implementation below
|
|
func appName() string {
|
|
return gApplicationName()
|
|
}
|
|
|
|
func appNew(name string) pointer {
|
|
// Use NON_UNIQUE to allow multiple instances of the application to run
|
|
// This matches the behavior of gtk_init/gtk_main used in v2
|
|
// G_APPLICATION_NON_UNIQUE = (1 << 5) = 32
|
|
GApplicationNonUnique := uint(32)
|
|
|
|
// Name is already sanitized by sanitizeAppName() in application_linux.go
|
|
identifier := fmt.Sprintf("org.wails.%s", name)
|
|
|
|
return pointer(gtkApplicationNew(identifier, GApplicationNonUnique))
|
|
}
|
|
|
|
func appRun(application pointer) error {
|
|
mainThreadId = gThreadSelf()
|
|
fmt.Println("linux_purego: appRun threadID", mainThreadId)
|
|
|
|
app := pointer(application)
|
|
activate := func() {
|
|
// TODO: Do we care?
|
|
fmt.Println("linux.activated!")
|
|
gApplicationHold(app) // allow running without a window
|
|
}
|
|
gSignalConnectData(
|
|
application,
|
|
"activate",
|
|
purego.NewCallback(activate),
|
|
app,
|
|
false,
|
|
0)
|
|
|
|
status := gApplicationRun(app, 0, nil)
|
|
gApplicationRelease(app)
|
|
gObjectUnref(app)
|
|
|
|
var err error
|
|
if status != 0 {
|
|
err = fmt.Errorf("exit code: %d", status)
|
|
}
|
|
return err
|
|
}
|
|
|
|
func appDestroy(application pointer) {
|
|
gApplicationQuit(pointer(application))
|
|
}
|
|
|
|
func getCurrentWindowID(application pointer, windows map[windowPointer]uint) uint {
|
|
// TODO: Add extra metadata to window and use it!
|
|
window := gtkApplicationGetActiveWindow(pointer(application))
|
|
identifier, ok := windows[windowPointer(window)]
|
|
if ok {
|
|
return identifier
|
|
}
|
|
// FIXME: Should we panic here if not found?
|
|
return uint(1)
|
|
}
|
|
|
|
type GList struct {
|
|
data pointer
|
|
next *GList
|
|
prev *GList
|
|
}
|
|
|
|
func getWindows(application pointer) []pointer {
|
|
result := []pointer{}
|
|
windows := gtkApplicationGetWindows(pointer(application))
|
|
// FIXME: Need to make a struct here to deal with response data
|
|
for {
|
|
result = append(result, pointer(windows.data))
|
|
windows = windows.next
|
|
if windows == nil {
|
|
return result
|
|
}
|
|
}
|
|
}
|
|
|
|
func hideAllWindows(application pointer) {
|
|
for _, window := range getWindows(application) {
|
|
gtkWidgetHide(window)
|
|
}
|
|
}
|
|
|
|
func showAllWindows(application pointer) {
|
|
for _, window := range getWindows(application) {
|
|
gtkWidgetShowAll(window)
|
|
}
|
|
}
|
|
|
|
// Menu
|
|
func menuAddSeparator(menu *Menu) {
|
|
gtkMenuShellAppend(
|
|
pointer((menu.impl).(*linuxMenu).native),
|
|
gtkSeparatorMenuItemNew())
|
|
}
|
|
|
|
func menuAppend(parent *Menu, menu *MenuItem) {
|
|
// TODO: override this with the GTK4 version if needed - possibly rename to imply it's an alias
|
|
gtkMenuShellAppend(
|
|
pointer((parent.impl).(*linuxMenu).native),
|
|
pointer((menu.impl).(*linuxMenuItem).native))
|
|
|
|
/* gtk4
|
|
C.gtk_menu_item_set_submenu(
|
|
(*C.struct__GtkMenuItem)((menu.impl).(*linuxMenuItem).native),
|
|
(*C.struct__GtkWidget)((parent.impl).(*linuxMenu).native),
|
|
)
|
|
*/
|
|
}
|
|
|
|
func menuBarNew() pointer {
|
|
return pointer(gtkMenuBarNew())
|
|
}
|
|
|
|
func menuNew() pointer {
|
|
return pointer(gtkMenuNew())
|
|
}
|
|
|
|
func menuSetSubmenu(item *MenuItem, menu *Menu) {
|
|
// FIXME: How is this different than `menuAppend` above?
|
|
gtkMenuItemSetSubmenu(
|
|
pointer((item.impl).(*linuxMenuItem).native),
|
|
pointer((menu.impl).(*linuxMenu).native))
|
|
}
|
|
|
|
func menuGetRadioGroup(item *linuxMenuItem) *GSList {
|
|
return (*GSList)(gtkRadioMenuItemGetGroup(pointer(item.native)))
|
|
}
|
|
|
|
func menuClear(menu *Menu) {
|
|
menuShell := pointer((menu.impl).(*linuxMenu).native)
|
|
children := gtkContainerGetChildren(menuShell)
|
|
if children != nil {
|
|
// Save the original pointer to free later
|
|
originalList := children
|
|
// Iterate through all children and remove them
|
|
for children != nil {
|
|
child := children.data
|
|
if child != nilPointer {
|
|
gtkContainerRemove(menuShell, child)
|
|
}
|
|
children = children.next
|
|
}
|
|
gListFree(originalList)
|
|
}
|
|
}
|
|
|
|
func attachMenuHandler(item *MenuItem) {
|
|
handleClick := func() {
|
|
item := item
|
|
switch item.itemType {
|
|
case text, checkbox:
|
|
menuItemClicked <- item.id
|
|
case radio:
|
|
menuItem := (item.impl).(*linuxMenuItem)
|
|
if menuItem.isChecked() {
|
|
menuItemClicked <- item.id
|
|
}
|
|
}
|
|
}
|
|
|
|
impl := (item.impl).(*linuxMenuItem)
|
|
widget := impl.native
|
|
flags := 0
|
|
handlerId := gSignalConnectObject(
|
|
pointer(widget),
|
|
"activate",
|
|
pointer(purego.NewCallback(handleClick)),
|
|
pointer(widget),
|
|
flags)
|
|
impl.handlerId = uint(handlerId)
|
|
}
|
|
|
|
// menuItem
|
|
func menuItemChecked(widget pointer) bool {
|
|
if gtkCheckMenuItemGetActive(widget) == 1 {
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
func menuItemNew(label string) pointer {
|
|
return pointer(gtkMenuItemNewWithLabel(label))
|
|
}
|
|
|
|
func menuCheckItemNew(label string) pointer {
|
|
return pointer(gtkCheckMenuItemNewWithLabel(label))
|
|
}
|
|
|
|
func menuItemSetChecked(widget pointer, checked bool) {
|
|
value := 0
|
|
if checked {
|
|
value = 1
|
|
}
|
|
gtkCheckMenuItemSetActive(pointer(widget), value)
|
|
}
|
|
|
|
func menuItemSetDisabled(widget pointer, disabled bool) {
|
|
value := 1
|
|
if disabled {
|
|
value = 0
|
|
}
|
|
gtkWidgetSetSensitive(widget, value)
|
|
}
|
|
|
|
func menuItemSetLabel(widget pointer, label string) {
|
|
gtkMenuItemSetLabel(
|
|
pointer(widget),
|
|
label)
|
|
}
|
|
|
|
func menuItemSetToolTip(widget pointer, tooltip string) {
|
|
gtkWidgetSetToolTipText(
|
|
pointer(widget),
|
|
tooltip)
|
|
}
|
|
|
|
func menuItemSignalBlock(widget pointer, handlerId uint, block bool) {
|
|
if block {
|
|
gSignalHandlerBlock(widget, handlerId)
|
|
} else {
|
|
gSignalHandlerUnblock(widget, handlerId)
|
|
}
|
|
}
|
|
|
|
func menuRadioItemNew(group GSListPointer, label string) pointer {
|
|
return pointer(gtkRadioMenuItemNewWithLabel(group, label))
|
|
}
|
|
|
|
// screen related
|
|
|
|
func getScreenByIndex(display pointer, index int) *Screen {
|
|
monitor := gdkDisplayGetMonitor(display, index)
|
|
// TODO: Do we need to update Screen to contain current info?
|
|
// currentMonitor := C.gdk_display_get_monitor_at_window(display, window)
|
|
|
|
geometry := struct {
|
|
x int32
|
|
y int32
|
|
width int32
|
|
height int32
|
|
}{}
|
|
result := pointer(unsafe.Pointer(&geometry))
|
|
gdkMonitorGetGeometry(monitor, result)
|
|
|
|
primary := false
|
|
if gdkMonitorIsPrimary(monitor) == 1 {
|
|
primary = true
|
|
}
|
|
|
|
return &Screen{
|
|
IsPrimary: primary,
|
|
ScaleFactor: 1.0,
|
|
X: int(geometry.x),
|
|
Y: int(geometry.y),
|
|
Size: Size{
|
|
Height: int(geometry.height),
|
|
Width: int(geometry.width),
|
|
},
|
|
}
|
|
}
|
|
|
|
func getScreens(app pointer) ([]*Screen, error) {
|
|
var screens []*Screen
|
|
window := gtkApplicationGetActiveWindow(app)
|
|
display := gdkWindowGetDisplay(window)
|
|
count := gdkDisplayGetNMonitors(display)
|
|
for i := 0; i < int(count); i++ {
|
|
screens = append(screens, getScreenByIndex(display, i))
|
|
}
|
|
return screens, nil
|
|
}
|
|
|
|
// widgets
|
|
func widgetSetSensitive(widget pointer, enabled bool) {
|
|
value := 0
|
|
if enabled {
|
|
value = 1
|
|
}
|
|
gtkWidgetSetSensitive(widget, value)
|
|
}
|
|
|
|
func widgetSetVisible(widget pointer, hidden bool) {
|
|
if hidden {
|
|
gtkWidgetHide(widget)
|
|
} else {
|
|
gtkWidgetShow(widget)
|
|
}
|
|
}
|
|
|
|
// window related functions
|
|
func windowClose(window pointer) {
|
|
gtkWindowClose(window)
|
|
}
|
|
|
|
func windowEnableDND(id uint, webview pointer) {
|
|
targetentry := gtkTargetEntryNew("text/uri-list", 0, id)
|
|
defer gtkTargetEntryFree(targetentry)
|
|
|
|
GtkDestDefaultDrop := uint(0)
|
|
GdkActionCopy := uint(0) //?
|
|
gtkDragDestSet(webview, GtkDestDefaultDrop, targetentry, 1, GdkActionCopy)
|
|
|
|
// FIXME: enable and process
|
|
/* gSignalConnectData(webview,
|
|
"drag-data-received",
|
|
purego.NewCallback(onDragNDrop),
|
|
0,
|
|
false,
|
|
0)*/
|
|
}
|
|
|
|
func windowExecJS(webview pointer, js string) {
|
|
webkitWebViewEvaluateJS(
|
|
webview,
|
|
js,
|
|
len(js),
|
|
0,
|
|
"",
|
|
0,
|
|
0,
|
|
0)
|
|
}
|
|
|
|
func windowDestroy(window pointer) {
|
|
// Should this truly 'destroy' ?
|
|
gtkWindowClose(window)
|
|
}
|
|
|
|
func windowFullscreen(window pointer) {
|
|
gtkWindowFullScreen(window)
|
|
}
|
|
|
|
func windowGetPosition(window pointer) (int, int) {
|
|
var x, y int
|
|
gtkWindowGetPosition(window, &x, &y)
|
|
return x, y
|
|
}
|
|
|
|
func windowGetCurrentMonitor(window pointer) pointer {
|
|
// Get the monitor that the window is currently on
|
|
display := gtkWidgetGetDisplay(window)
|
|
window = gtkWidgetGetWindow(window)
|
|
if window == 0 {
|
|
return 0
|
|
}
|
|
return gdkDisplayGetMonitorAtWindow(display, window)
|
|
}
|
|
|
|
func windowGetCurrentMonitorGeometry(window pointer) (x int, y int, width int, height int, scaleFactor int) {
|
|
monitor := windowGetCurrentMonitor(window)
|
|
if monitor == 0 {
|
|
return -1, -1, -1, -1, 1
|
|
}
|
|
|
|
result := struct {
|
|
x int32
|
|
y int32
|
|
width int32
|
|
height int32
|
|
}{}
|
|
gdkMonitorGetGeometry(monitor, pointer(unsafe.Pointer(&result)))
|
|
return int(result.x), int(result.y), int(result.width), int(result.height), gdkMonitorGetScaleFactor(monitor)
|
|
}
|
|
|
|
func windowGetRelativePosition(window pointer) (int, int) {
|
|
absX, absY := windowGetPosition(window)
|
|
x, y, _, _, _ := windowGetCurrentMonitorGeometry(window)
|
|
|
|
relX := absX - x
|
|
relY := absY - y
|
|
|
|
// TODO: Scale based on DPI
|
|
return relX, relY
|
|
}
|
|
|
|
func windowGetSize(window pointer) (int, int) {
|
|
// TODO: dispatchOnMainThread?
|
|
var width, height int
|
|
gtkWindowGetSize(window, &width, &height)
|
|
return width, height
|
|
}
|
|
|
|
func windowGetPosition(window pointer) (int, int) {
|
|
// TODO: dispatchOnMainThread?
|
|
var x, y int
|
|
gtkWindowGetPosition(window, &x, &y)
|
|
return x, y
|
|
}
|
|
|
|
func windowHide(window pointer) {
|
|
gtkWidgetHide(window)
|
|
}
|
|
|
|
func windowIsFocused(window pointer) bool {
|
|
return gtkWindowHasToplevelFocus(window) == 1
|
|
}
|
|
|
|
func windowIsFullscreen(window pointer) bool {
|
|
gdkwindow := gtkWidgetGetWindow(window)
|
|
state := gdkWindowGetState(gdkwindow)
|
|
return state&GdkWindowStateFullscreen > 0
|
|
}
|
|
|
|
func windowIsMaximized(window pointer) bool {
|
|
gdkwindow := gtkWidgetGetWindow(window)
|
|
state := gdkWindowGetState(gdkwindow)
|
|
return state&GdkWindowStateMaximized > 0 && state&GdkWindowStateFullscreen == 0
|
|
}
|
|
|
|
func windowIsMinimized(window pointer) bool {
|
|
gdkwindow := gtkWidgetGetWindow(window)
|
|
state := gdkWindowGetState(gdkwindow)
|
|
return state&GdkWindowStateIconified > 0
|
|
}
|
|
|
|
func windowIsVisible(window pointer) bool {
|
|
// TODO: validate this works.. (used a `bool` in the registration)
|
|
return gtkWidgetIsVisible(window)
|
|
}
|
|
|
|
func windowMaximize(window pointer) {
|
|
gtkWindowMaximize(window)
|
|
}
|
|
|
|
func windowMinimize(window pointer) {
|
|
gtkWindowMinimize(window)
|
|
}
|
|
|
|
func windowNew(application pointer, menu pointer, windowId uint, gpuPolicy int) (pointer, pointer, pointer) {
|
|
window := gtkApplicationWindowNew(application)
|
|
gObjectRefSink(window)
|
|
webview := windowNewWebview(windowId, gpuPolicy)
|
|
vbox := gtkBoxNew(GtkOrientationVertical, 0)
|
|
gtkContainerAdd(window, vbox)
|
|
gtkWidgetSetName(vbox, "webview-box")
|
|
|
|
if menu != 0 {
|
|
gtkBoxPackStart(vbox, menu, 0, 0, 0)
|
|
}
|
|
gtkBoxPackStart(vbox, webview, 1, 1, 0)
|
|
return pointer(window), pointer(webview), pointer(vbox)
|
|
}
|
|
|
|
func windowNewWebview(parentId uint, gpuPolicy int) pointer {
|
|
manager := webkitUserContentManagerNew()
|
|
webkitUserContentManagerRegisterScriptMessageHandler(manager, "external")
|
|
wv := webkitNewWithUserContentManager(manager)
|
|
if !registered {
|
|
webkitRegisterUriScheme(
|
|
webkitWebContextGetDefault(),
|
|
"wails",
|
|
pointer(purego.NewCallback(func(request uintptr) {
|
|
webviewRequests <- &webViewAssetRequest{
|
|
Request: webview.NewRequest(request),
|
|
windowId: parentId,
|
|
windowName: func() string {
|
|
if window, ok := globalApplication.Window.GetByID(parentId); ok {
|
|
return window.Name()
|
|
}
|
|
return ""
|
|
}(),
|
|
}
|
|
})),
|
|
0,
|
|
0,
|
|
)
|
|
registered = true
|
|
}
|
|
|
|
settings := webkitWebViewGetSettings(wv)
|
|
webkitSettingsSetUserAgentWithApplicationDetails(
|
|
settings,
|
|
"wails.io",
|
|
"")
|
|
webkitSettingsSetHardwareAccelerationPolicy(settings, gpuPolicy)
|
|
webkitWebViewSetSettings(wv, settings)
|
|
return wv
|
|
}
|
|
|
|
func windowPresent(window pointer) {
|
|
gtkWindowPresent(pointer(window))
|
|
}
|
|
|
|
func windowReload(webview pointer, address string) {
|
|
webkitWebViewLoadUri(pointer(webview), address)
|
|
}
|
|
|
|
func windowResize(window pointer, width, height int) {
|
|
gtkWindowResize(window, width, height)
|
|
}
|
|
|
|
func windowShow(window pointer) {
|
|
// Realize the window first to ensure it has a valid GdkWindow.
|
|
// This prevents crashes on Wayland when appmenu-gtk-module tries to
|
|
// set DBus properties for global menu integration before the window
|
|
// is fully realized. See: https://github.com/wailsapp/wails/issues/4769
|
|
gtkWidgetRealize(pointer(window))
|
|
gtkWidgetShowAll(pointer(window))
|
|
}
|
|
|
|
func windowSetBackgroundColour(vbox, webview pointer, colour RGBA) {
|
|
const GtkStyleProviderPriorityUser = 800
|
|
|
|
// FIXME: Use a struct!
|
|
rgba := make([]byte, 4*8) // C.sizeof_GdkRGBA == 32
|
|
rgbaPointer := pointer(unsafe.Pointer(&rgba[0]))
|
|
if !gdkRgbaParse(
|
|
rgbaPointer,
|
|
fmt.Sprintf("rgba(%v,%v,%v,%v)",
|
|
colour.Red,
|
|
colour.Green,
|
|
colour.Blue,
|
|
float32(colour.Alpha)/255.0,
|
|
)) {
|
|
return
|
|
}
|
|
webkitWebViewSetBackgroundColor(pointer(webview), rgbaPointer)
|
|
|
|
colour.Alpha = 255
|
|
css := fmt.Sprintf("#webview-box {background-color: rgba(%d, %d, %d, %1.1f);}", colour.Red, colour.Green, colour.Blue, float32(colour.Alpha)/255.0)
|
|
provider := gtkCSSProviderNew()
|
|
defer gObjectUnref(provider)
|
|
gtkStyleContextAddProvider(
|
|
gtkWidgetGetStyleContext(vbox),
|
|
provider,
|
|
GtkStyleProviderPriorityUser,
|
|
)
|
|
gtkCSSProviderLoadFromData(provider, css, -1, 0)
|
|
}
|
|
|
|
func windowSetGeometryHints(window pointer, minWidth, minHeight, maxWidth, maxHeight int) {
|
|
size := GdkGeometry{
|
|
minWidth: int32(minWidth),
|
|
minHeight: int32(minHeight),
|
|
maxWidth: int32(maxWidth),
|
|
maxHeight: int32(maxHeight),
|
|
}
|
|
gtkWindowSetGeometryHints(
|
|
pointer(window),
|
|
pointer(0),
|
|
pointer(unsafe.Pointer(&size)),
|
|
GdkHintMinSize|GdkHintMaxSize)
|
|
}
|
|
|
|
func windowSetFrameless(window pointer, frameless bool) {
|
|
decorated := 1
|
|
if frameless {
|
|
decorated = 0
|
|
}
|
|
gtkWindowSetDecorated(pointer(window), decorated)
|
|
|
|
// TODO: Deal with transparency for the titlebar if possible when !frameless
|
|
// Perhaps we just make it undecorated and add a menu bar inside?
|
|
}
|
|
|
|
// TODO: confirm this is working properly
|
|
func windowSetHTML(webview pointer, html string) {
|
|
webkitWebViewLoadAlternateHTML(webview, html, "wails://", nil)
|
|
}
|
|
|
|
func windowSetKeepAbove(window pointer, alwaysOnTop bool) {
|
|
gtkWindowKeepAbove(window, alwaysOnTop)
|
|
}
|
|
|
|
func windowSetResizable(window pointer, resizable bool) {
|
|
// FIXME: Does this work?
|
|
gtkWindowSetResizable(
|
|
pointer(window),
|
|
resizable,
|
|
)
|
|
}
|
|
|
|
func windowSetTitle(window pointer, title string) {
|
|
gtkWindowSetTitle(pointer(window), title)
|
|
}
|
|
|
|
func windowSetTransparent(window pointer) {
|
|
screen := gtkWidgetGetScreen(pointer(window))
|
|
visual := gdkScreenGetRgbaVisual(screen)
|
|
if visual == 0 {
|
|
return
|
|
}
|
|
if gdkScreenIsComposited(screen) == 1 {
|
|
gtkWidgetSetAppPaintable(pointer(window), 1)
|
|
gtkWidgetSetVisual(pointer(window), visual)
|
|
}
|
|
}
|
|
|
|
func windowSetURL(webview pointer, uri string) {
|
|
webkitWebViewLoadUri(webview, uri)
|
|
}
|
|
|
|
func windowSetupSignalHandlers(windowId uint, window, webview pointer, emit func(e events.WindowEventType)) {
|
|
handleDelete := purego.NewCallback(func(pointer) {
|
|
emit(events.Common.WindowClosing)
|
|
})
|
|
gSignalConnectData(window, "delete-event", handleDelete, 0, false, 0)
|
|
|
|
/*
|
|
event = C.CString("load-changed")
|
|
defer C.free(unsafe.Pointer(event))
|
|
C.signal_connect(webview, event, C.webviewLoadChanged, unsafe.Pointer(&w.parent.id))
|
|
*/
|
|
|
|
// TODO: Handle mouse button / drag events
|
|
/* id := C.uint(windowId)
|
|
event = C.CString("button-press-event")
|
|
C.signal_connect((*C.GtkWidget)(unsafe.Pointer(webview)), event, C.onButtonEvent, unsafe.Pointer(&id))
|
|
C.free(unsafe.Pointer(event))
|
|
event = C.CString("button-release-event")
|
|
defer C.free(unsafe.Pointer(event))
|
|
C.signal_connect((*C.GtkWidget)(unsafe.Pointer(webview)), event, onButtonEvent, unsafe.Pointer(&id))
|
|
*/
|
|
}
|
|
|
|
func windowOpenDevTools(webview pointer) {
|
|
settings := webkitWebViewGetSettings(pointer(webview))
|
|
webkitSettingsSetEnableDeveloperExtras(
|
|
settings,
|
|
!webkitSettingsGetEnableDeveloperExtras(settings))
|
|
}
|
|
|
|
func windowUnfullscreen(window pointer) {
|
|
gtkWindowUnfullscreen(window)
|
|
}
|
|
|
|
func windowUnmaximize(window pointer) {
|
|
gtkWindowUnmaximize(window)
|
|
}
|
|
|
|
func windowZoom(webview pointer) float64 {
|
|
return webkitWebViewGetZoom(webview)
|
|
}
|
|
|
|
func windowZoomIn(webview pointer) {
|
|
ZoomInFactor := 1.10
|
|
windowZoomSet(webview, windowZoom(webview)*ZoomInFactor)
|
|
}
|
|
func windowZoomOut(webview pointer) {
|
|
ZoomOutFactor := -1.10
|
|
windowZoomSet(webview, windowZoom(webview)*ZoomOutFactor)
|
|
}
|
|
|
|
func windowZoomSet(webview pointer, zoom float64) {
|
|
if zoom < 1.0 { // 1.0 is the smallest allowable
|
|
zoom = 1.0
|
|
}
|
|
webkitWebViewSetZoomLevel(webview, zoom)
|
|
}
|
|
|
|
func windowMove(window pointer, x, y int) {
|
|
gtkWindowMove(window, x, y)
|
|
}
|
|
|
|
func runChooserDialog(window pointer, allowMultiple, createFolders, showHidden bool, currentFolder, title string, action int, acceptLabel string, filters []FileFilter, currentName string) ([]string, error) {
|
|
GtkResponseCancel := 0
|
|
GtkResponseAccept := 1
|
|
|
|
fc := gtkFileChooserDialogNew(
|
|
title,
|
|
window,
|
|
action,
|
|
"_Cancel",
|
|
GtkResponseCancel,
|
|
acceptLabel,
|
|
GtkResponseAccept,
|
|
0)
|
|
|
|
gtkFileChooserSetAction(fc, action)
|
|
|
|
gtkFilters := []pointer{}
|
|
for _, filter := range filters {
|
|
f := gtkFileFilterNew()
|
|
gtkFileFilterSetName(f, filter.DisplayName)
|
|
gtkFileFilterAddPattern(f, filter.Pattern)
|
|
gtkFileChooserAddFilter(fc, f)
|
|
gtkFilters = append(gtkFilters, f)
|
|
}
|
|
gtkFileChooserSetSelectMultiple(fc, allowMultiple)
|
|
gtkFileChooserSetCreateFolders(fc, createFolders)
|
|
gtkFileChooserSetShowHidden(fc, showHidden)
|
|
|
|
if currentFolder != "" {
|
|
gtkFileChooserSetCurrentFolder(fc, currentFolder)
|
|
}
|
|
|
|
// Set the current name for save dialogs to pre-populate the filename
|
|
const GtkFileChooserActionSave = 1
|
|
if currentName != "" && action == GtkFileChooserActionSave {
|
|
gtkFileChooserSetCurrentName(fc, currentName)
|
|
}
|
|
|
|
buildStringAndFree := func(s pointer) string {
|
|
bytes := []byte{}
|
|
p := unsafe.Pointer(s)
|
|
for {
|
|
val := *(*byte)(p)
|
|
if val == 0 { // this is the null terminator
|
|
break
|
|
}
|
|
bytes = append(bytes, val)
|
|
p = unsafe.Add(p, 1)
|
|
}
|
|
gFree(s) // so we don't have to iterate a second time
|
|
return string(bytes)
|
|
}
|
|
|
|
response := gtkDialogRun(fc)
|
|
selections := []string{}
|
|
if response == GtkResponseAccept {
|
|
filenames := gtkFileChooserGetFilenames(fc)
|
|
iter := filenames
|
|
count := 0
|
|
for {
|
|
selections = append(selections, buildStringAndFree(iter.data))
|
|
iter = iter.next
|
|
if iter == nil || count == 1024 {
|
|
break
|
|
}
|
|
count++
|
|
}
|
|
}
|
|
defer gtkWidgetDestroy(fc)
|
|
return selections, nil
|
|
}
|
|
|
|
// dialog related
|
|
func runOpenFileDialog(dialog *OpenFileDialogStruct) ([]string, error) {
|
|
const GtkFileChooserActionOpen = 0
|
|
const GtkFileChooserActionSelectFolder = 2
|
|
|
|
var action int
|
|
|
|
if dialog.canChooseDirectories {
|
|
action = GtkFileChooserActionSelectFolder
|
|
} else {
|
|
action = GtkFileChooserActionOpen
|
|
}
|
|
|
|
window := pointer(0)
|
|
if dialog.window != nil {
|
|
nativeWindow := dialog.window.NativeWindow()
|
|
if nativeWindow != nil {
|
|
window = pointer(uintptr(nativeWindow))
|
|
}
|
|
}
|
|
|
|
buttonText := dialog.buttonText
|
|
if buttonText == "" {
|
|
buttonText = "_Open"
|
|
}
|
|
|
|
return runChooserDialog(
|
|
window,
|
|
dialog.allowsMultipleSelection,
|
|
dialog.canCreateDirectories,
|
|
dialog.showHiddenFiles,
|
|
dialog.directory,
|
|
dialog.title,
|
|
GtkFileChooserActionOpen,
|
|
buttonText,
|
|
dialog.filters,
|
|
"")
|
|
}
|
|
|
|
func runQuestionDialog(parent pointer, options *MessageDialog) int {
|
|
dType, ok := map[DialogType]int{
|
|
InfoDialogType: GtkMessageInfo,
|
|
WarningDialogType: GtkMessageWarning,
|
|
QuestionDialogType: GtkMessageQuestion,
|
|
}[options.DialogType]
|
|
if !ok {
|
|
// FIXME: Add logging here!
|
|
dType = GtkMessageInfo
|
|
}
|
|
buttonMask := GtkButtonsOk
|
|
if len(options.Buttons) > 0 {
|
|
buttonMask = GtkButtonsNone
|
|
}
|
|
|
|
dialog := gtkMessageDialogNew(
|
|
pointer(parent),
|
|
GtkDialogModal|GtkDialogDestroyWithParent,
|
|
dType,
|
|
buttonMask,
|
|
options.Message)
|
|
|
|
if options.Title != "" {
|
|
gtkWindowSetTitle(dialog, options.Title)
|
|
}
|
|
|
|
GdkColorspaceRGB := 0
|
|
|
|
if img, err := pngToImage(options.Icon); err == nil && len(img.Pix) > 0 {
|
|
// Use gBytesNew instead of gBytesNewStatic because Go memory can be
|
|
// moved or freed by the GC. gBytesNew copies the data to C-owned memory.
|
|
gbytes := gBytesNew(uintptr(unsafe.Pointer(&img.Pix[0])), len(img.Pix))
|
|
|
|
defer gBytesUnref(gbytes)
|
|
pixBuf := gdkPixbufNewFromBytes(
|
|
gbytes,
|
|
GdkColorspaceRGB,
|
|
1, // has_alpha
|
|
8,
|
|
img.Bounds().Dx(),
|
|
img.Bounds().Dy(),
|
|
img.Stride,
|
|
)
|
|
image := gtkImageNewFromPixbuf(pixBuf)
|
|
widgetSetVisible(image, false)
|
|
contentArea := gtkDialogGetContentArea(dialog)
|
|
gtkContainerAdd(contentArea, image)
|
|
}
|
|
|
|
for i, button := range options.Buttons {
|
|
gtkDialogAddButton(
|
|
dialog,
|
|
button.Label,
|
|
i,
|
|
)
|
|
if button.IsDefault {
|
|
gtkDialogSetDefaultResponse(dialog, i)
|
|
}
|
|
}
|
|
defer gtkWidgetDestroy(dialog)
|
|
return gtkDialogRun(dialog)
|
|
}
|
|
|
|
func runSaveFileDialog(dialog *SaveFileDialogStruct) (string, error) {
|
|
const GtkFileChooserActionSave = 1
|
|
|
|
window := pointer(0)
|
|
buttonText := dialog.buttonText
|
|
if buttonText == "" {
|
|
buttonText = "_Save"
|
|
}
|
|
results, err := runChooserDialog(
|
|
window,
|
|
false, // multiple selection
|
|
dialog.canCreateDirectories,
|
|
dialog.showHiddenFiles,
|
|
dialog.directory,
|
|
dialog.title,
|
|
GtkFileChooserActionSave,
|
|
buttonText,
|
|
dialog.filters,
|
|
dialog.filename)
|
|
|
|
if err != nil || len(results) == 0 {
|
|
return "", err
|
|
}
|
|
|
|
return results[0], nil
|
|
}
|
|
|
|
func isOnMainThread() bool {
|
|
return mainThreadId == gThreadSelf()
|
|
}
|
|
|
|
// linuxWebviewWindow show/hide methods for purego implementation
|
|
func (w *linuxWebviewWindow) windowShow() {
|
|
if w.window == 0 {
|
|
return
|
|
}
|
|
windowShow(w.window)
|
|
}
|
|
|
|
func (w *linuxWebviewWindow) windowHide() {
|
|
if w.window == 0 {
|
|
return
|
|
}
|
|
windowHide(w.window)
|
|
}
|