5
0
mirror of https://github.com/wailsapp/wails.git synced 2025-05-10 22:19:46 +08:00

[windows] Split out wndProc. Generate windows events, support per-window themes

This commit is contained in:
Lea Anthony 2023-04-29 12:14:12 +10:00 committed by Misite Bao
parent 7c63cee9e8
commit 178ea9c8c5
7 changed files with 242 additions and 17 deletions

View File

@ -136,6 +136,7 @@ var (
procSetWindowsHookEx = moduser32.NewProc("SetWindowsHookExW")
procUnhookWindowsHookEx = moduser32.NewProc("UnhookWindowsHookEx")
procCallNextHookEx = moduser32.NewProc("CallNextHookEx")
procGetForegroundWindow = moduser32.NewProc("GetForegroundWindow")
procSystemParametersInfo = moduser32.NewProc("SystemParametersInfoW")
procSetClassLong = moduser32.NewProc("SetClassLongW")
@ -301,6 +302,11 @@ func GetDpiForWindow(hwnd HWND) UINT {
return uint(dpi)
}
func GetForegroundWindow() HWND {
ret, _, _ := procGetForegroundWindow.Call()
return HWND(ret)
}
func SetWindowCompositionAttribute(hwnd HWND, data *WINDOWCOMPOSITIONATTRIBDATA) bool {
if procSetWindowCompositionAttribute != nil {
ret, _, _ := procSetWindowCompositionAttribute.Call(

View File

@ -3,6 +3,7 @@
package application
import (
"github.com/wailsapp/wails/v3/pkg/events"
"syscall"
"unsafe"
@ -17,8 +18,13 @@ type windowsApp struct {
instance w32.HINSTANCE
windowMap map[w32.HWND]*windowsWebviewWindow
mainThreadID w32.HANDLE
mainThreadWindowHWND w32.HWND
// system theme
isDarkMode bool
}
func (m *windowsApp) getPrimaryScreen() (*Screen, error) {
@ -111,28 +117,57 @@ func (m *windowsApp) init() {
if ret := w32.RegisterClassEx(&wc); ret == 0 {
panic(syscall.GetLastError())
}
m.isDarkMode = w32.IsCurrentlyDarkMode()
}
func (m *windowsApp) wndProc(hwnd w32.HWND, msg uint32, wParam, lParam uintptr) uintptr {
switch msg {
case w32.WM_SIZE:
// Handle the invoke callback
if msg == wmInvokeCallback {
m.invokeCallback(wParam, lParam)
return 0
case w32.WM_CLOSE:
w32.PostQuitMessage(0)
return 0
case wmInvokeCallback:
if hwnd == m.mainThreadWindowHWND {
m.invokeCallback(wParam, lParam)
return 0
}
}
switch msg {
case w32.WM_SETTINGCHANGE:
settingChanged := w32.UTF16PtrToString((*uint16)(unsafe.Pointer(lParam)))
if settingChanged == "ImmersiveColorSet" {
isDarkMode := w32.IsCurrentlyDarkMode()
if isDarkMode != m.isDarkMode {
applicationEvents <- uint(events.Windows.SystemThemeChanged)
m.isDarkMode = isDarkMode
}
}
return 0
}
if window, ok := m.windowMap[hwnd]; ok {
return window.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.windowMap[result.hwnd] = result
}
func (m *windowsApp) unregisterWindow(w *windowsWebviewWindow) {
delete(m.windowMap, w.hwnd)
// If this was the last window...
if len(m.windowMap) == 0 {
w32.PostQuitMessage(0)
}
}
func newPlatformApp(app *App) *windowsApp {
result := &windowsApp{
parent: app,
instance: w32.GetModuleHandle(""),
parent: app,
instance: w32.GetModuleHandle(""),
windowMap: make(map[w32.HWND]*windowsWebviewWindow),
}
result.init()

View File

@ -15,4 +15,36 @@ type WindowsWindow struct {
BackdropType BackdropType
// Disable the icon in the titlebar
DisableIcon bool
// Theme. Defaults to SystemDefault which will use whatever the system theme is. The application will follow system theme changes.
Theme Theme
// Custom colours for dark/light mode
CustomTheme *ThemeSettings
}
type Theme int
const (
// SystemDefault will use whatever the system theme is. The application will follow system theme changes.
SystemDefault Theme = 0
// Dark Mode
Dark Theme = 1
// Light Mode
Light Theme = 2
)
// ThemeSettings defines custom colours to use in dark or light mode.
// They may be set using the hex values: 0x00BBGGRR
type ThemeSettings struct {
DarkModeTitleBar int32
DarkModeTitleBarInactive int32
DarkModeTitleText int32
DarkModeTitleTextInactive int32
DarkModeBorder int32
DarkModeBorderInactive int32
LightModeTitleBar int32
LightModeTitleBarInactive int32
LightModeTitleText int32
LightModeTitleTextInactive int32
LightModeBorder int32
LightModeBorderInactive int32
}

View File

@ -6,6 +6,7 @@ import (
"errors"
"fmt"
"github.com/wailsapp/wails/v3/internal/w32"
"github.com/wailsapp/wails/v3/pkg/events"
"syscall"
"unsafe"
@ -107,6 +108,10 @@ func (w *windowsWebviewWindow) _run() {
panic("Unable to create window")
}
// Register the window with the application
windowsApp := globalApplication.impl.(*windowsApp)
windowsApp.registerWindow(w)
if options.DisableResize {
w.setResizable(false)
}
@ -129,12 +134,25 @@ func (w *windowsWebviewWindow) _run() {
case BackgroundTypeTranslucent:
w.setBackdropType(options.Windows.BackdropType)
}
w.setForeground()
// Process the theme
switch options.Windows.Theme {
case SystemDefault:
w.updateTheme(w32.IsCurrentlyDarkMode())
globalApplication.On(events.Windows.SystemThemeChanged, func() {
w.updateTheme(w32.IsCurrentlyDarkMode())
})
case Light:
w.updateTheme(false)
case Dark:
w.updateTheme(true)
}
if !options.Hidden {
w.show()
w.update()
}
w.setForeground()
}
func (w *windowsWebviewWindow) center() {
@ -365,7 +383,7 @@ func (w *windowsWebviewWindow) setBackdropType(backdropType BackdropType) {
w32.SetWindowCompositionAttribute(w.hwnd, &data)
} else {
backdropValue := backdropType
// We default to None, but in win32 None = 1 and Auto = 0
// We default to None, but in w32 None = 1 and Auto = 0
// So we check if the value given was Auto and set it to 0
if backdropType == Auto {
backdropValue = None
@ -393,6 +411,65 @@ func (w *windowsWebviewWindow) disableIcon() {
)
}
func (w *windowsWebviewWindow) updateTheme(isDarkMode bool) {
if w32.IsCurrentlyHighContrastMode() {
return
}
if !w32.SupportsThemes() {
return
}
w32.SetTheme(w.hwnd, isDarkMode)
// Custom theme processing
customTheme := w.parent.options.Windows.CustomTheme
// Custom theme
if w32.SupportsCustomThemes() && customTheme != nil {
if w.isActive() {
if isDarkMode {
w32.SetTitleBarColour(w.hwnd, customTheme.DarkModeTitleBar)
w32.SetTitleTextColour(w.hwnd, customTheme.DarkModeTitleText)
w32.SetBorderColour(w.hwnd, customTheme.DarkModeBorder)
} else {
w32.SetTitleBarColour(w.hwnd, customTheme.LightModeTitleBar)
w32.SetTitleTextColour(w.hwnd, customTheme.LightModeTitleText)
w32.SetBorderColour(w.hwnd, customTheme.LightModeBorder)
}
} else {
if isDarkMode {
w32.SetTitleBarColour(w.hwnd, customTheme.DarkModeTitleBarInactive)
w32.SetTitleTextColour(w.hwnd, customTheme.DarkModeTitleTextInactive)
w32.SetBorderColour(w.hwnd, customTheme.DarkModeBorderInactive)
} else {
w32.SetTitleBarColour(w.hwnd, customTheme.LightModeTitleBarInactive)
w32.SetTitleTextColour(w.hwnd, customTheme.LightModeTitleTextInactive)
w32.SetBorderColour(w.hwnd, customTheme.LightModeBorderInactive)
}
}
}
}
func (w *windowsWebviewWindow) isActive() bool {
return w32.GetForegroundWindow() == w.hwnd
}
func (w *windowsWebviewWindow) WndProc(msg uint32, wparam, lparam uintptr) uintptr {
switch msg {
case w32.WM_SIZE:
return 0
case w32.WM_CLOSE:
w32.PostMessage(w.hwnd, w32.WM_QUIT, 0, 0)
// Unregister the window with the application
windowsApp := globalApplication.impl.(*windowsApp)
windowsApp.unregisterWindow(w)
return 0
default:
return w32.DefWindowProc(w.hwnd, msg, wparam, lparam)
}
}
func NewIconFromResource(instance w32.HINSTANCE, resId uint16) (w32.HICON, error) {
var err error
var result w32.HICON

View File

@ -260,3 +260,15 @@ func newMacEvents() macEvents {
WindowFileDraggingExited: 1145,
}
}
var Windows = newWindowsEvents()
type windowsEvents struct {
SystemThemeChanged ApplicationEventType
}
func newWindowsEvents() windowsEvents {
return windowsEvents{
SystemThemeChanged: 1146,
}
}

View File

@ -120,4 +120,4 @@ mac:WebViewDidCommitNavigation
mac:WindowFileDraggingEntered
mac:WindowFileDraggingPerformed
mac:WindowFileDraggingExited
windows:SystemThemeChanged

View File

@ -25,6 +25,18 @@ func newMacEvents() macEvents {
return macEvents{
$$MACEVENTSVALUES }
}
var Windows = newWindowsEvents()
type windowsEvents struct {
$$WINDOWSEVENTSDECL}
func newWindowsEvents() windowsEvents {
return windowsEvents{
$$WINDOWSEVENTSVALUES }
}
`
var eventsH = `//go:build darwin
@ -53,7 +65,11 @@ func main() {
applicationDelegateEvents := bytes.NewBufferString("")
webviewDelegateEvents := bytes.NewBufferString("")
windowsEventsDecl := bytes.NewBufferString("")
windowsEventsValues := bytes.NewBufferString("")
var id int
var maxMacEvents int
var line []byte
// Loop over each line in the file
for id, line = range bytes.Split(eventNames, []byte{'\n'}) {
@ -94,6 +110,7 @@ func main() {
macEventsDecl.WriteString("\t" + eventTitle + " " + eventType + "\n")
macEventsValues.WriteString("\t\t" + event + ": " + strconv.Itoa(id) + ",\n")
cHeaderEvents.WriteString("#define Event" + eventTitle + " " + strconv.Itoa(id) + "\n")
maxMacEvents = id
if ignoreEvent {
continue
}
@ -128,15 +145,61 @@ func main() {
`)
}
case "windows":
eventType := "ApplicationEventType"
if strings.HasPrefix(event, "Window") {
eventType = "WindowEventType"
}
if strings.HasPrefix(event, "WebView") {
eventType = "WindowEventType"
}
windowsEventsDecl.WriteString("\t" + eventTitle + " " + eventType + "\n")
windowsEventsValues.WriteString("\t\t" + event + ": " + strconv.Itoa(id) + ",\n")
// cHeaderEvents.WriteString("#define Event" + eventTitle + " " + strconv.Itoa(id) + "\n")
// if ignoreEvent {
// continue
// }
// // Check if this is a window event
// if strings.HasPrefix(event, "Window") {
// windowDelegateEvents.WriteString(`- (void)` + delegateEventFunction + `:(NSNotification *)notification {
// if( hasListeners(Event` + eventTitle + `) ) {
// processWindowEvent(self.windowId, Event` + eventTitle + `);
// }
//}
//
//`)
// }
// // Check if this is a webview event
// if strings.HasPrefix(event, "WebView") {
// webViewFunction := strings.TrimPrefix(event, "WebView")
// webViewFunction = string(bytes.ToLower([]byte{webViewFunction[0]})) + webViewFunction[1:]
// webviewDelegateEvents.WriteString(`- (void)webView:(WKWebView *)webview ` + webViewFunction + `:(WKNavigation *)navigation {
// if( hasListeners(Event` + eventTitle + `) ) {
// processWindowEvent(self.windowId, Event` + eventTitle + `);
// }
//}
//
//`)
// }
// if strings.HasPrefix(event, "Application") {
// applicationDelegateEvents.WriteString(`- (void)` + delegateEventFunction + `:(NSNotification *)notification {
// if( hasListeners(Event` + eventTitle + `) ) {
// processApplicationEvent(Event` + eventTitle + `);
// }
//}
//
//`)
// }
}
}
cHeaderEvents.WriteString("\n#define MAX_EVENTS " + strconv.Itoa(id-1) + "\n")
cHeaderEvents.WriteString("\n#define MAX_EVENTS " + strconv.Itoa(maxMacEvents+1) + "\n")
// Save the eventsGo template substituting the values and decls
templateToWrite := strings.ReplaceAll(eventsGo, "$$MACEVENTSDECL", macEventsDecl.String())
templateToWrite = strings.ReplaceAll(templateToWrite, "$$MACEVENTSVALUES", macEventsValues.String())
templateToWrite = strings.ReplaceAll(templateToWrite, "$$WINDOWSEVENTSDECL", windowsEventsDecl.String())
templateToWrite = strings.ReplaceAll(templateToWrite, "$$WINDOWSEVENTSVALUES", windowsEventsValues.String())
err = os.WriteFile("../../pkg/events/events.go", []byte(templateToWrite), 0644)
if err != nil {
panic(err)