5
0
mirror of https://github.com/wailsapp/wails.git synced 2025-05-06 17:19:51 +08:00
wails/v3/pkg/application/application_windows.go
Lea Anthony 861ddea1b2
Add option to prevent application shutdown
This update allows the application shutdown to be cancelled. It implements the shouldQuit function, which can prevent the application from quitting if it returns false. Additional checks have been added to ensure cleanup and quit processes are performed only if required. The darwin delegate and linux and windows applications were modified to include this new functionality.
2024-01-17 20:45:30 +11:00

353 lines
9.1 KiB
Go

//go:build windows
package application
import (
"fmt"
"os"
"strconv"
"sync"
"syscall"
"unsafe"
"github.com/wailsapp/go-webview2/webviewloader"
"github.com/wailsapp/wails/v3/internal/operatingsystem"
"golang.org/x/sys/windows"
"github.com/wailsapp/wails/v3/pkg/events"
"github.com/wailsapp/wails/v3/pkg/w32"
"github.com/samber/lo"
)
var windowClassName = lo.Must(syscall.UTF16PtrFromString("WailsWebviewWindow"))
type windowsApp struct {
parent *App
windowClass w32.WNDCLASSEX
instance w32.HINSTANCE
windowMap map[w32.HWND]*windowsWebviewWindow
windowMapLock sync.RWMutex
systrayMap map[w32.HMENU]*windowsSystemTray
systrayMapLock sync.RWMutex
mainThreadID w32.HANDLE
mainThreadWindowHWND w32.HWND
// Windows hidden by application.Hide()
hiddenWindows []*windowsWebviewWindow
focusedWindow w32.HWND
// system theme
isCurrentlyDarkMode bool
currentWindowID uint
}
func (m *windowsApp) isDarkMode() bool {
return w32.IsCurrentlyDarkMode()
}
func (m *windowsApp) isOnMainThread() bool {
return m.mainThreadID == w32.GetCurrentThreadId()
}
func (m *windowsApp) GetFlags(options Options) map[string]any {
if options.Flags == nil {
options.Flags = make(map[string]any)
}
options.Flags["system"] = map[string]any{
"resizeHandleWidth": w32.GetSystemMetrics(w32.SM_CXSIZEFRAME),
"resizeHandleHeight": w32.GetSystemMetrics(w32.SM_CYSIZEFRAME),
}
return options.Flags
}
func (m *windowsApp) getWindowForHWND(hwnd w32.HWND) *windowsWebviewWindow {
m.windowMapLock.RLock()
defer m.windowMapLock.RUnlock()
return m.windowMap[hwnd]
}
func getNativeApplication() *windowsApp {
return globalApplication.impl.(*windowsApp)
}
func (m *windowsApp) getPrimaryScreen() (*Screen, error) {
screens, err := m.getScreens()
if err != nil {
return nil, err
}
for _, screen := range screens {
if screen.IsPrimary {
return screen, nil
}
}
return nil, fmt.Errorf("no primary screen found")
}
func (m *windowsApp) getScreens() ([]*Screen, error) {
allScreens, err := w32.GetAllScreens()
if err != nil {
return nil, err
}
// Convert result to []*Screen
screens := make([]*Screen, len(allScreens))
for id, screen := range allScreens {
x := int(screen.MONITORINFOEX.RcMonitor.Left)
y := int(screen.MONITORINFOEX.RcMonitor.Top)
right := int(screen.MONITORINFOEX.RcMonitor.Right)
bottom := int(screen.MONITORINFOEX.RcMonitor.Bottom)
width := right - x
height := bottom - y
screens[id] = &Screen{
ID: strconv.Itoa(id),
Name: windows.UTF16ToString(screen.MONITORINFOEX.SzDevice[:]),
X: x,
Y: y,
Size: Size{Width: width, Height: height},
Bounds: Rect{X: x, Y: y, Width: width, Height: height},
WorkArea: Rect{
X: int(screen.MONITORINFOEX.RcWork.Left),
Y: int(screen.MONITORINFOEX.RcWork.Top),
Width: int(screen.MONITORINFOEX.RcWork.Right - screen.MONITORINFOEX.RcWork.Left),
Height: int(screen.MONITORINFOEX.RcWork.Bottom - screen.MONITORINFOEX.RcWork.Top),
},
IsPrimary: screen.IsPrimary,
Scale: screen.Scale,
Rotation: 0,
}
}
return screens, nil
}
func (m *windowsApp) hide() {
// Get the current focussed window
m.focusedWindow = w32.GetForegroundWindow()
// Iterate over all windows and hide them if they aren't already hidden
for _, window := range m.windowMap {
if window.isVisible() {
// Add to hidden windows
m.hiddenWindows = append(m.hiddenWindows, window)
window.hide()
}
}
// Switch focus to the next application
hwndNext := w32.GetWindow(m.mainThreadWindowHWND, w32.GW_HWNDNEXT)
w32.SetForegroundWindow(hwndNext)
}
func (m *windowsApp) show() {
// Iterate over all windows and show them if they were previously hidden
for _, window := range m.hiddenWindows {
window.show()
}
// Show the foreground window
w32.SetForegroundWindow(m.focusedWindow)
}
func (m *windowsApp) on(_ uint) {
}
func (m *windowsApp) setIcon(_ []byte) {
}
func (m *windowsApp) name() string {
//appName := C.getAppName()
//defer C.free(unsafe.Pointer(appName))
//return C.GoString(appName)
return ""
}
func (m *windowsApp) getCurrentWindowID() uint {
return m.currentWindowID
}
func (m *windowsApp) setApplicationMenu(menu *Menu) {
if menu == nil {
// Create a default menu for windows
menu = defaultApplicationMenu()
}
menu.Update()
m.parent.ApplicationMenu = menu
}
func (m *windowsApp) run() error {
m.setupCommonEvents()
for eventID := range m.parent.applicationEventListeners {
m.on(eventID)
}
// Emit application started event
applicationEvents <- &Event{
Id: uint(events.Windows.ApplicationStarted),
ctx: blankApplicationEventContext,
}
_ = m.runMainLoop()
return nil
}
func (m *windowsApp) destroy() {
if !globalApplication.shouldQuit() {
return
}
globalApplication.cleanup()
// Post a quit message to the main thread
w32.PostQuitMessage(0)
}
func (m *windowsApp) init() {
// Register the window class
icon := w32.LoadIconWithResourceID(m.instance, w32.IDI_APPLICATION)
m.windowClass.Size = uint32(unsafe.Sizeof(m.windowClass))
m.windowClass.Style = w32.CS_HREDRAW | w32.CS_VREDRAW
m.windowClass.WndProc = syscall.NewCallback(m.wndProc)
m.windowClass.Instance = m.instance
m.windowClass.Background = w32.COLOR_BTNFACE + 1
m.windowClass.Icon = icon
m.windowClass.Cursor = w32.LoadCursorWithResourceID(0, w32.IDC_ARROW)
m.windowClass.ClassName = windowClassName
m.windowClass.MenuName = nil
m.windowClass.IconSm = icon
if ret := w32.RegisterClassEx(&m.windowClass); ret == 0 {
panic(syscall.GetLastError())
}
m.isCurrentlyDarkMode = w32.IsCurrentlyDarkMode()
}
func (m *windowsApp) wndProc(hwnd w32.HWND, msg uint32, wParam, lParam uintptr) uintptr {
// Handle the invoke callback
if msg == wmInvokeCallback {
m.invokeCallback(wParam, lParam)
return 0
}
// If the WndProcInterceptor is set in options, pass the message on
if m.parent.options.Windows.WndProcInterceptor != nil {
returnValue, shouldReturn := m.parent.options.Windows.WndProcInterceptor(hwnd, msg, wParam, lParam)
if shouldReturn {
return returnValue
}
}
switch msg {
case w32.WM_SETTINGCHANGE:
settingChanged := w32.UTF16PtrToString((*uint16)(unsafe.Pointer(lParam)))
if settingChanged == "ImmersiveColorSet" {
isDarkMode := w32.IsCurrentlyDarkMode()
if isDarkMode != m.isCurrentlyDarkMode {
eventContext := newApplicationEventContext()
eventContext.setIsDarkMode(isDarkMode)
applicationEvents <- &Event{
Id: uint(events.Windows.SystemThemeChanged),
ctx: eventContext,
}
m.isCurrentlyDarkMode = isDarkMode
}
}
return 0
case w32.WM_POWERBROADCAST:
switch wParam {
case w32.PBT_APMPOWERSTATUSCHANGE:
applicationEvents <- newApplicationEvent(events.Windows.APMPowerStatusChange)
case w32.PBT_APMSUSPEND:
applicationEvents <- newApplicationEvent(events.Windows.APMSuspend)
case w32.PBT_APMRESUMEAUTOMATIC:
applicationEvents <- newApplicationEvent(events.Windows.APMResumeAutomatic)
case w32.PBT_APMRESUMESUSPEND:
applicationEvents <- newApplicationEvent(events.Windows.APMResumeSuspend)
case w32.PBT_POWERSETTINGCHANGE:
applicationEvents <- newApplicationEvent(events.Windows.APMPowerSettingChange)
}
return 0
}
if window, ok := m.windowMap[hwnd]; ok {
return window.WndProc(msg, wParam, lParam)
}
m.systrayMapLock.Lock()
systray, ok := m.systrayMap[hwnd]
m.systrayMapLock.Unlock()
if ok {
return systray.wndProc(msg, wParam, lParam)
}
// Dispatch the message to the appropriate window
return w32.DefWindowProc(hwnd, msg, wParam, lParam)
}
func (m *windowsApp) registerWindow(result *windowsWebviewWindow) {
m.windowMapLock.Lock()
m.windowMap[result.hwnd] = result
m.windowMapLock.Unlock()
}
func (m *windowsApp) registerSystemTray(result *windowsSystemTray) {
m.systrayMapLock.Lock()
defer m.systrayMapLock.Unlock()
m.systrayMap[result.hwnd] = result
}
func (m *windowsApp) unregisterSystemTray(result *windowsSystemTray) {
m.systrayMapLock.Lock()
defer m.systrayMapLock.Unlock()
delete(m.systrayMap, result.hwnd)
}
func (m *windowsApp) unregisterWindow(w *windowsWebviewWindow) {
m.windowMapLock.Lock()
delete(m.windowMap, w.hwnd)
m.windowMapLock.Unlock()
// If this was the last window...
if len(m.windowMap) == 0 && !m.parent.options.Windows.DisableQuitOnLastWindowClosed {
w32.PostQuitMessage(0)
}
}
func newPlatformApp(app *App) *windowsApp {
err := w32.SetProcessDPIAware()
if err != nil {
globalApplication.fatal("Fatal error in application initialisation: ", err.Error())
os.Exit(1)
}
result := &windowsApp{
parent: app,
instance: w32.GetModuleHandle(""),
windowMap: make(map[w32.HWND]*windowsWebviewWindow),
systrayMap: make(map[w32.HWND]*windowsSystemTray),
}
result.init()
result.initMainLoop()
return result
}
func (a *App) logPlatformInfo() {
var args []any
args = append(args, "Go-WebView2Loader", webviewloader.UsingGoWebview2Loader)
webviewVersion, err := webviewloader.GetAvailableCoreWebView2BrowserVersionString(a.options.Windows.WebviewBrowserPath)
if err != nil {
args = append(args, "WebView2", "Error: "+err.Error())
} else {
args = append(args, "WebView2", webviewVersion)
}
osInfo, _ := operatingsystem.Info()
args = append(args, osInfo.AsLogSlice()...)
a.info("Platform Info:", args...)
}