5
0
mirror of https://github.com/wailsapp/wails.git synced 2025-05-03 05:50:08 +08:00

[linux] basic windowing pt2

This commit is contained in:
Lea Anthony 2021-11-27 20:36:25 +11:00
parent abc078fb1b
commit 39ca977b18
No known key found for this signature in database
GPG Key ID: 33DAF7BB90A58405
6 changed files with 311 additions and 252 deletions

View File

@ -49,8 +49,6 @@ require (
nhooyr.io/websocket v1.8.6
)
require github.com/gotk3/gotk3 v0.6.1
require (
github.com/Microsoft/go-winio v0.4.16 // indirect
github.com/andybalholm/brotli v1.0.2 // indirect

View File

@ -82,8 +82,6 @@ github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvKCM=
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/gotk3/gotk3 v0.6.1 h1:GJ400a0ecEEWrzjBvzBzH+pB/esEMIGdB9zPSmBdoeo=
github.com/gotk3/gotk3 v0.6.1/go.mod h1:/hqFpkNa9T3JgNAE2fLvCdov7c5bw//FHNZrZ3Uv9/Q=
github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU=
github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
github.com/jackmordaunt/icns v1.0.0 h1:RYSxplerf/l/DUd09AHtITwckkv/mqjVv4DjYdPmAMQ=

View File

@ -3,20 +3,28 @@
package linux
/*
#cgo linux pkg-config: gtk+-3.0 webkit2gtk-4.0
#include "gtk/gtk.h"
*/
import "C"
import (
"bytes"
"context"
"encoding/json"
"log"
"strconv"
"text/template"
"github.com/wailsapp/wails/v2/internal/binding"
"github.com/wailsapp/wails/v2/internal/frontend"
"github.com/wailsapp/wails/v2/internal/frontend/assetserver"
"github.com/wailsapp/wails/v2/internal/logger"
"github.com/wailsapp/wails/v2/pkg/options"
"github.com/gotk3/gotk3/gtk"
"github.com/xyproto/xpm"
"image/png"
"log"
"os"
"strconv"
"text/template"
)
type Frontend struct {
@ -34,24 +42,45 @@ type Frontend struct {
// main window handle
mainWindow *Window
minWidth, minHeight, maxWidth, maxHeight int
//minWidth, minHeight, maxWidth, maxHeight int
bindings *binding.Bindings
dispatcher frontend.Dispatcher
servingFromDisk bool
}
func png2XPM(pngData []byte) string {
// Create a new XPM encoder
enc := xpm.NewEncoder("icon")
// Open the PNG file
m, err := png.Decode(bytes.NewReader(pngData))
if err != nil {
log.Fatal(err)
}
var buf bytes.Buffer
// Generate and output the XPM data
err = enc.Encode(&buf, m)
if err != nil {
log.Fatal(err)
}
return buf.String()
}
func NewFrontend(ctx context.Context, appoptions *options.App, myLogger *logger.Logger, appBindings *binding.Bindings, dispatcher frontend.Dispatcher) *Frontend {
// Set GDK_BACKEND=x11
os.Setenv("GDK_BACKEND", "x11")
result := &Frontend{
frontendOptions: appoptions,
logger: myLogger,
bindings: appBindings,
dispatcher: dispatcher,
ctx: ctx,
minHeight: appoptions.MinHeight,
minWidth: appoptions.MinWidth,
maxHeight: appoptions.MaxHeight,
maxWidth: appoptions.MaxWidth,
startURL: "file://wails/",
}
@ -72,7 +101,7 @@ func NewFrontend(ctx context.Context, appoptions *options.App, myLogger *logger.
// Check if we have been given a directory to serve assets from.
// If so, this means we are in dev mode and are serving assets off disk.
// We indicate this through the `servingFromDisk` flag to ensure requests
// aren't cached by WebView2 in dev mode
// aren't cached by webkit.
_assetdir := ctx.Value("assetdir")
if _assetdir != nil {
@ -85,8 +114,13 @@ func NewFrontend(ctx context.Context, appoptions *options.App, myLogger *logger.
}
result.assets = assets
// Initialise GTK
gtk.Init(nil)
C.gtk_init(nil, nil)
var _debug = ctx.Value("debug")
if _debug != nil {
result.debug = _debug.(bool)
}
result.mainWindow = NewWindow(appoptions, result.debug)
return result
}
@ -99,108 +133,83 @@ func (f *Frontend) Run(ctx context.Context) error {
f.ctx = context.WithValue(ctx, "frontend", f)
mainWindow := NewWindow(f.frontendOptions)
f.mainWindow = mainWindow
var _debug = ctx.Value("debug")
if _debug != nil {
f.debug = _debug.(bool)
}
//f.WindowCenter()
//f.setupChromium()
//
//gtkWindow.OnSize().Bind(func(arg *winc.Event) {
// f.chromium.Resize()
//})
//
//gtkWindow.OnClose().Bind(func(arg *winc.Event) {
// if f.frontendOptions.HideWindowOnClose {
// f.WindowHide()
// } else {
// f.Quit()
// }
//})
go func() {
if f.frontendOptions.OnStartup != nil {
f.frontendOptions.OnStartup(f.ctx)
}
}()
if f.frontendOptions.Fullscreen {
mainWindow.Fullscreen()
}
f.mainWindow.Run()
mainWindow.Run()
mainWindow.Close()
return nil
}
func (f *Frontend) WindowCenter() {
f.mainWindow.Center()
////dCenter()
}
func (f *Frontend) WindowSetPos(x, y int) {
f.mainWindow.SetPos(x, y)
////dSetPos(x, y)
}
func (f *Frontend) WindowGetPos() (int, int) {
return f.mainWindow.Pos()
////return dPos()
return 0, 0
}
func (f *Frontend) WindowSetSize(width, height int) {
f.mainWindow.SetSize(width, height)
////dSetSize(width, height)
}
func (f *Frontend) WindowGetSize() (int, int) {
return f.mainWindow.Size()
////return dSize()
return 0, 0
}
func (f *Frontend) WindowSetTitle(title string) {
f.mainWindow.SetText(title)
////dSetText(title)
}
func (f *Frontend) WindowFullscreen() {
f.mainWindow.SetMaxSize(0, 0)
f.mainWindow.SetMinSize(0, 0)
f.mainWindow.Fullscreen()
////dSetMaxSize(0, 0)
////dSetMinSize(0, 0)
////dFullscreen()
}
func (f *Frontend) WindowUnFullscreen() {
f.mainWindow.UnFullscreen()
f.mainWindow.SetMaxSize(f.maxWidth, f.maxHeight)
f.mainWindow.SetMinSize(f.minWidth, f.minHeight)
////dUnFullscreen()
////dSetMaxSize(f.maxWidth, f.maxHeight)
////dSetMinSize(f.minWidth, f.minHeight)
}
func (f *Frontend) WindowShow() {
f.mainWindow.Show()
////dShow()
}
func (f *Frontend) WindowHide() {
f.mainWindow.Hide()
////dHide()
}
func (f *Frontend) WindowMaximise() {
f.mainWindow.Maximise()
////dMaximise()
}
func (f *Frontend) WindowUnmaximise() {
f.mainWindow.UnMaximise()
////dUnMaximise()
}
func (f *Frontend) WindowMinimise() {
f.mainWindow.Minimise()
//dMinimise()
}
func (f *Frontend) WindowUnminimise() {
f.mainWindow.UnMinimise()
//dUnMinimise()
}
func (f *Frontend) WindowSetMinSize(width int, height int) {
f.minWidth = width
f.minHeight = height
f.mainWindow.SetMinSize(width, height)
//f.minWidth = width
////f.minHeight = height
//dSetMinSize(width, height)
}
func (f *Frontend) WindowSetMaxSize(width int, height int) {
f.maxWidth = width
f.maxHeight = height
f.mainWindow.SetMaxSize(width, height)
//f.maxWidth = width
////f.maxHeight = height
//dSetMaxSize(width, height)
}
func (f *Frontend) WindowSetRGBA(col *options.RGBA) {
@ -341,12 +350,12 @@ func (f *Frontend) Notify(name string, data ...interface{}) {
func (f *Frontend) processMessage(message string) {
if message == "drag" {
if !f.mainWindow.IsFullScreen() {
//if !f.mainWindow.IsFullScreen() {
err := f.startDrag()
if err != nil {
f.logger.Error(err.Error())
}
}
//}
return
}
result, err := f.dispatcher.ProcessMessage(message, f)

View File

@ -3,245 +3,295 @@
package linux
import (
"github.com/gotk3/gotk3/gdk"
"github.com/gotk3/gotk3/glib"
"github.com/gotk3/gotk3/gtk"
"github.com/wailsapp/wails/v2/pkg/menu"
"github.com/wailsapp/wails/v2/pkg/options"
"github.com/wailsapp/wails/v2/pkg/options/linux"
"log"
"math"
"sync"
)
/*
#cgo linux pkg-config: gtk+-3.0 webkit2gtk-4.0
type Window struct {
frontendOptions *options.App
applicationMenu *menu.Menu
m sync.Mutex
application *gtk.Application
gtkWindow *gtk.ApplicationWindow
//dispatchq []func()
#include "gtk/gtk.h"
#include <stdio.h>
#include <limits.h>
static GtkWidget* GTKWIDGET(void *pointer) {
return GTK_WIDGET(pointer);
}
func NewWindow(options *options.App) *Window {
result := new(Window)
result.frontendOptions = options
static GtkWindow* GTKWINDOW(void *pointer) {
return GTK_WINDOW(pointer);
}
var linuxOptions linux.Options
if options.Linux != nil {
linuxOptions = *options.Linux
static void SetMinSize(GtkWindow* window, int width, int height) {
GdkGeometry size;
size.min_height = height;
size.min_width = width;
gtk_window_set_geometry_hints(window, NULL, &size, GDK_HINT_MIN_SIZE);
}
static void SetMaxSize(GtkWindow* window, int width, int height) {
GdkGeometry size;
if( width == 0 ) {
width = INT_MAX;
}
appID := linuxOptions.AppID
if appID == "" {
appID = "io.wails"
if( height == 0 ) {
height = INT_MAX;
}
println("AppID =", appID)
size.max_height = height;
size.max_width = width;
gtk_window_set_geometry_hints(window, NULL, &size, GDK_HINT_MAX_SIZE);
}
application, err := gtk.ApplicationNew(appID, glib.APPLICATION_FLAGS_NONE)
if err != nil {
log.Fatal("Could not create application:", err)
GdkRectangle getCurrentMonitorGeometry(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);
// Get the geometry of the monitor
GdkRectangle result;
gdk_monitor_get_geometry (monitor,&result);
return result;
}
void SetPosition(GtkWindow *window, int x, int y) {
GdkRectangle monitorDimensions = getCurrentMonitorGeometry(window);
gtk_window_move(window, monitorDimensions.x + x, monitorDimensions.y + y);
}
void Center(GtkWindow *window)
{
// 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);
}
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 == GDK_WINDOW_STATE_FULLSCREEN;
}
*/
import "C"
import (
"github.com/wailsapp/wails/v2/pkg/options"
"unsafe"
)
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
}
func NewWindow(appoptions *options.App, debug bool) *Window {
result := &Window{
appoptions: appoptions,
debug: debug,
}
result.application = application
gtkWindow := C.gtk_window_new(C.GTK_WINDOW_TOPLEVEL)
C.g_object_ref_sink(C.gpointer(gtkWindow))
application.Connect("activate", func() {
result.gtkWindow = unsafe.Pointer(gtkWindow)
result.SetKeepAbove(appoptions.AlwaysOnTop)
result.SetResizable(!appoptions.DisableResize)
result.SetSize(appoptions.Width, appoptions.Height)
result.Center()
result.SetDecorated(!appoptions.Frameless)
result.SetTitle(appoptions.Title)
result.SetMinSize(appoptions.MinWidth, appoptions.MinHeight)
result.SetMaxSize(appoptions.MaxWidth, appoptions.MaxHeight)
window, err := gtk.ApplicationWindowNew(application)
if err != nil {
log.Fatal("Could not create application window:", err)
}
window.Connect("delete-event", func() {
if options.HideWindowOnClose {
result.gtkWindow.Hide()
return
}
result.gtkWindow.Close()
})
result.gtkWindow = window
window.SetTitle(options.Title)
window.SetDecorated(!options.Frameless)
window.SetDefaultSize(600, 300)
window.SetResizable(!options.DisableResize)
window.SetKeepAbove(options.AlwaysOnTop)
window.SetPosition(gtk.WIN_POS_CENTER)
if !options.StartHidden {
window.ShowAll()
}
})
//if appoptions.Linux != nil && appoptions.Linux.Icon != nil {
// xpmData := png2XPM(appoptions.Linux.Icon)
// xpm := C.CString(xpmData)
// defer C.free(unsafe.Pointer(xpm))
// appIcon := C.gdk_pixbuf_new_from_xpm_data(
// C.gtk_window_set_icon(result.asGTKWindow(), appIcon)
//}
//result.SetIsForm(true)
//
//var exStyle int
//if options.Windows != nil {
// exStyle = w32.WS_EX_CONTROLPARENT | w32.WS_EX_APPWINDOW
// if options.Windows.WindowIsTranslucent {
// exStyle |= w32.WS_EX_NOREDIRECTIONBITMAP
// }
//windowStartState := C.int(int(a.appoptions.WindowStartState))
//if a.appoptions.RGBA != nil {
// result.SetRGBA(a.appoptions.RGBA.R, a.appoptions.RGBA.G, a.appoptions.RGBA.B, a.appoptions.RGBA.A)
//}
//if options.AlwaysOnTop {
// exStyle |= w32.WS_EX_TOPMOST
//}
//
//var dwStyle = w32.WS_OVERLAPPEDWINDOW
//if options.Frameless {
// dwStyle = w32.WS_POPUP
//}
//
//winc.RegClassOnlyOnce("wailsWindow")
//result.SetHandle(winc.CreateWindow("wailsWindow", parent, uint(exStyle), uint(dwStyle)))
//result.SetParent(parent)
//
//loadIcon := true
//if options.Windows != nil && options.Windows.DisableWindowIcon == true {
// loadIcon = false
//}
//if loadIcon {
// if ico, err := winc.NewIconFromResource(winc.GetAppInstance(), uint16(winc.AppIconID)); err == nil {
// result.SetIcon(0, ico)
// }
//}
//
//result.SetSize(options.Width, options.Height)
//result.SetText(options.Title)
//if options.Frameless == false && !options.Fullscreen {
// result.EnableMaxButton(!options.DisableResize)
// result.EnableSizable(!options.DisableResize)
// result.SetMinSize(options.MinWidth, options.MinHeight)
// result.SetMaxSize(options.MaxWidth, options.MaxHeight)
//}
//
//if options.Windows != nil {
// if options.Windows.WindowIsTranslucent {
// result.SetTranslucentBackground()
// }
//
// if options.Windows.DisableWindowIcon {
// result.DisableIcon()
// }
//}
//
//// Dlg forces display of focus rectangles, as soon as the user starts to type.
//w32.SendMessage(result.Handle(), w32.WM_CHANGEUISTATE, w32.UIS_INITIALIZE, 0)
//winc.RegMsgHandler(result)
//
//result.SetFont(winc.DefaultFont)
//
//if options.Menu != nil {
// result.SetApplicationMenu(options.Menu)
//if a.appoptions.Menu != nil {
// result.SetApplicationMenu(a.appoptions.Menu)
//}
return result
}
func (w *Window) Run() {
w.application.Run(nil)
func (w *Window) asGTKWidget() *C.GtkWidget {
return C.GTKWIDGET(w.gtkWindow)
}
func (w *Window) Dispatch(f func()) {
glib.IdleAdd(f)
func (w *Window) asGTKWindow() *C.GtkWindow {
return C.GTKWINDOW(w.gtkWindow)
}
//func (w *Window) Dispatch(f func()) {
// glib.IdleAdd(f)
//}
//
func (w *Window) Fullscreen() {
w.gtkWindow.Fullscreen()
C.gtk_window_fullscreen(w.asGTKWindow())
}
func (w *Window) UnFullscreen() {
w.gtkWindow.Unfullscreen()
C.gtk_window_unfullscreen(w.asGTKWindow())
}
func (w *Window) Destroy() {
/*
for (gulong connection: {
impl_->deleteEventConnection,
impl_->focusInEventConnection,
impl_->focusOutEventConnection,
impl_->configureEventConnection
}) {
g_signal_handler_disconnect(impl_->gtkWindow, connection);
}
gtk_widget_destroy(GTK_WIDGET(impl_->gtkWindow));
*/
//TODO: Proper shutdown
C.g_object_unref(C.gpointer(w.gtkWindow))
C.gtk_widget_destroy(w.asGTKWidget())
}
func (w *Window) Close() {
w.application.Quit()
C.gtk_window_close(w.asGTKWindow())
}
func (w *Window) Center() {
w.gtkWindow.SetPosition(gtk.WIN_POS_CENTER)
C.Center(w.asGTKWindow())
}
func (w *Window) SetPos(x int, y int) {
display, err := w.gtkWindow.GetDisplay()
if err != nil {
w.gtkWindow.Move(x, y)
return
}
window, err := w.gtkWindow.GetWindow()
if err != nil {
w.gtkWindow.Move(x, y)
return
}
monitor, err := display.GetMonitorAtWindow(window)
if err != nil {
w.gtkWindow.Move(x, y)
return
}
geom := monitor.GetGeometry()
w.gtkWindow.Move(geom.GetX()+x, geom.GetY()+y)
}
func (w *Window) Pos() (int, int) {
return w.gtkWindow.GetPosition()
}
func (w *Window) SetSize(width int, height int) {
w.gtkWindow.SetDefaultSize(width, height)
cX := C.int(x)
cY := C.int(y)
C.gtk_window_move(w.asGTKWindow(), cX, cY)
}
func (w *Window) Size() (int, int) {
return w.gtkWindow.GetSize()
var width, height C.int
C.gtk_window_get_size(w.asGTKWindow(), &width, &height)
return int(width), int(height)
}
func (w *Window) SetText(title string) {
w.gtkWindow.SetTitle(title)
func (w *Window) Pos() (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) {
var geom gdk.Geometry
if maxWidth == 0 {
maxWidth = math.MaxInt
}
if maxHeight == 0 {
maxHeight = math.MaxInt
}
geom.SetMaxWidth(maxWidth)
geom.SetMaxHeight(maxHeight)
w.gtkWindow.SetGeometryHints(w.gtkWindow, geom, gdk.HINT_MAX_SIZE)
C.SetMaxSize(w.asGTKWindow(), C.int(maxWidth), C.int(maxHeight))
}
func (w *Window) SetMinSize(minWidth int, minHeight int) {
var geom gdk.Geometry
geom.SetMinWidth(minWidth)
geom.SetMinHeight(minHeight)
w.gtkWindow.SetGeometryHints(w.gtkWindow, geom, gdk.HINT_MIN_SIZE)
C.SetMinSize(w.asGTKWindow(), C.int(minWidth), C.int(minHeight))
}
func (w *Window) Show() {
w.gtkWindow.ShowAll()
C.gtk_widget_show(w.asGTKWidget())
}
func (w *Window) Hide() {
w.gtkWindow.Hide()
C.gtk_widget_hide(w.asGTKWidget())
}
func (w *Window) Maximise() {
w.gtkWindow.Maximize()
C.gtk_window_maximize(w.asGTKWindow())
}
func (w *Window) UnMaximise() {
w.gtkWindow.Unmaximize()
C.gtk_window_unmaximize(w.asGTKWindow())
}
func (w *Window) Minimise() {
w.gtkWindow.Iconify()
C.gtk_window_iconify(w.asGTKWindow())
}
func (w *Window) UnMinimise() {
w.gtkWindow.Present()
C.gtk_window_present(w.asGTKWindow())
}
func (w *Window) IsFullScreen() bool {
result := C.IsFullscreen(w.asGTKWidget())
if result == 1 {
return true
}
return false
}
func (w *Window) SetRGBA(r uint8, g uint8, b uint8, a uint8) {
//C.SetRGBA(w.context, C.int(r), C.int(g), C.int(b), C.int(a))
}
//func (w *Window) SetApplicationMenu(inMenu *menu.Menu) {
// //mainMenu := NewNSMenu(w.context, "")
// //processMenu(mainMenu, inMenu)
// //C.SetAsApplicationMenu(w.context, mainMenu.nsmenu)
//}
func (w *Window) UpdateApplicationMenu() {
//C.UpdateApplicationMenu(w.context)
}
func (w *Window) Run() {
C.gtk_widget_show_all(w.asGTKWidget())
//switch w.appoptions.WindowStartState {
//case options.Fullscreen:
// w.Fullscreen()
//case options.Minimised:
// w.Minimise()
//case options.Maximised:
// w.Maximise()
//}
//println("Fullscreen: ", w.IsFullScreen())
C.gtk_main()
}
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) {
cTitle := C.CString(title)
defer C.free(unsafe.Pointer(cTitle))
C.gtk_window_set_title(w.asGTKWindow(), cTitle)
}

View File

@ -215,6 +215,10 @@ func (b *BaseBuilder) CompileProject(options *Options) error {
commands.Add(`"all=-N -l"`)
}
if options.ForceBuild {
commands.Add("-a")
}
var tags slicer.StringSlicer
tags.Add(options.OutputType)
tags.AddSlice(options.UserTags)

View File

@ -2,6 +2,6 @@ package linux
// Options specific to Linux builds
type Options struct {
// AppID is the gtk application id string. Defaults to a random uuid.
AppID string
// Linux needs the icon embedded
Icon []byte
}