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

[v3 windows] initial systray support

This commit is contained in:
Lea Anthony 2023-05-10 19:35:40 +10:00
parent b91468b6f2
commit 7fd627f169
No known key found for this signature in database
GPG Key ID: 33DAF7BB90A58405
8 changed files with 241 additions and 136 deletions

View File

@ -35,11 +35,13 @@ func main() {
myMenu := app.NewMenu()
myMenu.Add("Hello World!").OnClick(func(ctx *application.Context) {
app.InfoDialog().SetTitle("Hello World!").SetMessage("Hello World!").Show()
println("Hello World!")
// app.InfoDialog().SetTitle("Hello World!").SetMessage("Hello World!").Show()
})
subMenu := myMenu.AddSubmenu("Submenu")
subMenu.Add("Click me!").OnClick(func(ctx *application.Context) {
ctx.ClickedMenuItem().SetLabel("Clicked!")
println("Click me!")
// ctx.ClickedMenuItem().SetLabel("Clicked!")
})
myMenu.AddSeparator()
myMenu.Add("Quit").OnClick(func(ctx *application.Context) {

View File

@ -96,7 +96,7 @@ func (m *windowsApp) getCurrentWindowID() uint {
func (m *windowsApp) setApplicationMenu(menu *Menu) {
if menu == nil {
// Create a default menu for mac
// Create a default menu for windows
menu = defaultApplicationMenu()
}
menu.Update()

View File

@ -2,53 +2,112 @@
package application
import "unsafe"
import (
"github.com/wailsapp/wails/v3/pkg/w32"
)
type windowsMenu struct {
menu *Menu
menuImpl unsafe.Pointer
hWnd w32.HWND
hMenu w32.HMENU
currentMenuID int
menuMapping map[int]*MenuItem
checkboxItems []*Menu
}
func newMenuImpl(menu *Menu) *windowsMenu {
result := &windowsMenu{
menu: menu,
menu: menu,
menuMapping: make(map[int]*MenuItem),
}
return result
}
func (m *windowsMenu) update() {
//if m.menuImpl == nil {
// m.menuImpl = C.createNSMenu(C.CString(m.menu.label))
//} else {
// C.clearMenu(m.menuImpl)
//}
m.processMenu(m.menuImpl, m.menu)
func (w *windowsMenu) update() {
if w.hMenu != 0 {
w32.DestroyMenu(w.hMenu)
}
w.hMenu = w32.NewPopupMenu()
w.processMenu(w.hMenu, w.menu)
}
func (m *windowsMenu) processMenu(parent unsafe.Pointer, menu *Menu) {
//for _, item := range menu.items {
// switch item.itemType {
// case submenu:
// submenu := item.submenu
// nsSubmenu := C.createNSMenu(C.CString(item.label))
// m.processMenu(nsSubmenu, submenu)
// menuItem := newMenuItemImpl(item)
// item.impl = menuItem
// C.addMenuItem(parent, menuItem.nsMenuItem)
// C.setMenuItemSubmenu(menuItem.nsMenuItem, nsSubmenu)
// if item.role == ServicesMenu {
// C.addServicesMenu(nsSubmenu)
// }
// case text, checkbox, radio:
// menuItem := newMenuItemImpl(item)
// item.impl = menuItem
// C.addMenuItem(parent, menuItem.nsMenuItem)
// case separator:
// C.addMenuSeparator(parent)
// }
//
//}
func (w *windowsMenu) processMenu(parentMenu w32.HMENU, inputMenu *Menu) {
for _, item := range inputMenu.items {
if item.Hidden() {
continue
}
w.currentMenuID++
itemID := w.currentMenuID
w.menuMapping[itemID] = item
flags := uint32(w32.MF_STRING)
if item.disabled {
flags = flags | w32.MF_GRAYED
}
if item.checked {
flags = flags | w32.MF_CHECKED
}
if item.IsSeparator() {
flags = flags | w32.MF_SEPARATOR
}
//
//if item.IsCheckbox() {
// w.checkboxItems[item] = append(w.checkboxItems[item], itemID)
//}
//if item.IsRadio() {
// currentRadioGroup.Add(itemID, item)
//} else {
// if len(currentRadioGroup) > 0 {
// for _, radioMember := range currentRadioGroup {
// currentRadioGroup := currentRadioGroup
// p.radioGroups[radioMember.MenuItem] = append(p.radioGroups[radioMember.MenuItem], &currentRadioGroup)
// }
// currentRadioGroup = RadioGroup{}
// }
//}
if item.submenu != nil {
flags = flags | w32.MF_POPUP
newSubmenu := w32.CreateMenu()
w.processMenu(newSubmenu, item.submenu)
itemID = int(newSubmenu)
}
var menuText = w32.MustStringToUTF16Ptr(item.Label())
w32.AppendMenu(parentMenu, flags, uintptr(itemID), menuText)
}
}
func (w *windowsMenu) ShowAtCursor() {
invokeSync(func() {
x, y, ok := w32.GetCursorPos()
if !ok {
return
}
w.ShowAt(x, y)
})
}
func (w *windowsMenu) ShowAt(x int, y int) {
w.update()
w32.TrackPopupMenuEx(w.hMenu,
w32.TPM_LEFTALIGN,
int32(x),
int32(y),
w.hWnd,
nil)
w32.PostMessage(w.hWnd, w32.WM_NULL, 0, 0)
}
func (w *windowsMenu) ProcessCommand(cmdMsgID int) {
item := w.menuMapping[cmdMsgID]
if item == nil {
return
}
item.handleClick()
}
func defaultApplicationMenu() *Menu {

View File

@ -38,6 +38,7 @@ type menuItemImpl interface {
setDisabled(disabled bool)
setChecked(checked bool)
setAccelerator(accelerator *accelerator)
setHidden(hidden bool)
}
type MenuItem struct {
@ -46,6 +47,7 @@ type MenuItem struct {
tooltip string
disabled bool
checked bool
hidden bool
submenu *Menu
callback func(*Context)
itemType menuItemType
@ -257,10 +259,38 @@ func (m *MenuItem) SetChecked(checked bool) *MenuItem {
return m
}
func (m *MenuItem) SetHidden(hidden bool) *MenuItem {
m.hidden = hidden
if m.impl != nil {
m.impl.setHidden(m.hidden)
}
return m
}
func (m *MenuItem) Checked() bool {
return m.checked
}
func (m *MenuItem) IsSeparator() bool {
return m.itemType == separator
}
func (m *MenuItem) IsSubmenu() bool {
return m.itemType == submenu
}
func (m *MenuItem) IsCheckbox() bool {
return m.itemType == checkbox
}
func (m *MenuItem) IsRadio() bool {
return m.itemType == radio
}
func (m *MenuItem) Hidden() bool {
return m.hidden
}
func (m *MenuItem) OnClick(f func(*Context)) *MenuItem {
m.callback = f
return m

View File

@ -19,13 +19,14 @@ const (
type windowsSystemTray struct {
parent *SystemTray
menu *windowsMenu
// Platform specific implementation
uid uint32
hwnd w32.HWND
lightModeIcon w32.HICON
darkModeIcon w32.HICON
currentIcon w32.HICON
//menu *w32.PopupMenu
}
func (s *windowsSystemTray) setMenu(menu *Menu) {
@ -82,7 +83,9 @@ func (s *windowsSystemTray) run() {
}
s.uid = nid.UID
// TODO: Set Menu
if s.parent.menu != nil {
s.updateMenu()
}
// Set Default Callbacks
if s.parent.clickHandler == nil {
@ -92,7 +95,9 @@ func (s *windowsSystemTray) run() {
}
if s.parent.rightClickHandler == nil {
s.parent.rightClickHandler = func() {
//s.showMenu()
if s.menu != nil {
s.menu.ShowAtCursor()
}
}
}
@ -174,10 +179,6 @@ func newSystemTrayImpl(parent *SystemTray) systemTrayImpl {
}
}
func (s *windowsSystemTray) destroy() {
panic("implement me")
}
func (s *windowsSystemTray) wndProc(msg uint32, wParam, lParam uintptr) uintptr {
switch msg {
case WM_USER_SYSTRAY:
@ -211,20 +212,26 @@ func (s *windowsSystemTray) wndProc(msg uint32, wParam, lParam uintptr) uintptr
//println(w32.WMMessageToString(msg))
// TODO: Menu processing
//case w32.WM_COMMAND:
// cmdMsgID := int(wparam & 0xffff)
// switch cmdMsgID {
// default:
// p.menu.ProcessCommand(cmdMsgID)
// }
case w32.WM_COMMAND:
cmdMsgID := int(wParam & 0xffff)
switch cmdMsgID {
default:
s.menu.ProcessCommand(cmdMsgID)
}
default:
msg := int(wParam & 0xffff)
println(w32.WMMessageToString(uintptr(msg)))
//msg := int(wParam & 0xffff)
//println(w32.WMMessageToString(uintptr(msg)))
}
return w32.DefWindowProc(s.hwnd, msg, wParam, lParam)
}
func (s *windowsSystemTray) updateMenu() {
s.menu = newMenuImpl(s.parent.menu)
s.menu.hWnd = s.hwnd
s.menu.update()
}
// ---- Unsupported ----
func (s *windowsSystemTray) setLabel(_ string) {
@ -238,3 +245,6 @@ func (s *windowsSystemTray) setTemplateIcon(_ []byte) {
func (s *windowsSystemTray) setIconPosition(position int) {
// Unsupported - do nothing
}
func (s *windowsSystemTray) destroy() {
}

View File

@ -3360,8 +3360,20 @@ const (
)
const (
MF_BYCOMMAND = 0x00000000
MF_BYPOSITION = 0x00000400
MF_BYCOMMAND = 0x00000000
MF_BYPOSITION = 0x00000400
MF_BITMAP = 0x00000004
MF_CHECKED = 0x00000008
MF_DISABLED = 0x00000002
MF_ENABLED = 0x00000000
MF_GRAYED = 0x00000001
MF_MENUBARBREAK = 0x00000020
MF_MENUBREAK = 0x00000040
MF_OWNERDRAW = 0x00000100
MF_POPUP = 0x00000010
MF_SEPARATOR = 0x00000800
MF_STRING = 0x00000000
MF_UNCHECKED = 0x00000000
)
type MENUITEMINFO struct {

View File

@ -1,15 +1,8 @@
//go:build windows
package win32
package w32
type Menu HMENU
type PopupMenu Menu
func CreatePopupMenu() PopupMenu {
ret, _, _ := procCreatePopupMenu.Call(0, 0, 0, 0)
return PopupMenu(ret)
}
func (m Menu) Destroy() bool {
ret, _, _ := procDestroyMenu.Call(uintptr(m))
return ret != 0
@ -19,31 +12,22 @@ func (p PopupMenu) Destroy() bool {
return Menu(p).Destroy()
}
func (p PopupMenu) Track(flags uint, x, y int, wnd HWND) bool {
ret, _, _ := procTrackPopupMenu.Call(
uintptr(p),
uintptr(flags),
uintptr(x),
uintptr(y),
0,
uintptr(wnd),
0,
)
return ret != 0
func (p PopupMenu) Track(hwnd HWND, flags uint32, x, y int32) bool {
return TrackPopupMenuEx(
HMENU(p),
flags,
x,
y,
hwnd,
nil)
}
func (p PopupMenu) Append(flags uintptr, id uintptr, text string) bool {
func (p PopupMenu) Append(flags uint32, id uintptr, text string) bool {
return Menu(p).Append(flags, id, text)
}
func (m Menu) Append(flags uintptr, id uintptr, text string) bool {
ret, _, _ := procAppendMenuW.Call(
uintptr(m),
flags,
id,
MustStringToUTF16uintptr(text),
)
return ret != 0
func (m Menu) Append(flags uint32, id uintptr, text string) bool {
return AppendMenu(HMENU(m), flags, id, MustStringToUTF16Ptr(text))
}
func (p PopupMenu) Check(id uintptr, checked bool) bool {
@ -70,7 +54,7 @@ func (m Menu) CheckRadio(startID int, endID int, selectedID int) bool {
func CheckMenuItem(menu HMENU, id uintptr, flags uint) uint {
ret, _, _ := procCheckMenuItem.Call(
uintptr(menu),
menu,
id,
uintptr(flags),
)
@ -80,3 +64,13 @@ func CheckMenuItem(menu HMENU, id uintptr, flags uint) uint {
func (p PopupMenu) CheckRadio(startID, endID, selectedID int) bool {
return Menu(p).CheckRadio(startID, endID, selectedID)
}
func NewMenu() HMENU {
ret, _, _ := procCreateMenu.Call()
return HMENU(ret)
}
func NewPopupMenu() HMENU {
ret, _, _ := procCreatePopupMenu.Call()
return ret
}

View File

@ -21,6 +21,7 @@ var (
procLoadIcon = moduser32.NewProc("LoadIconW")
procLoadCursor = moduser32.NewProc("LoadCursorW")
procShowWindow = moduser32.NewProc("ShowWindow")
procGetDesktopWindow = moduser32.NewProc("GetDesktopWindow")
procShowWindowAsync = moduser32.NewProc("ShowWindowAsync")
procUpdateWindow = moduser32.NewProc("UpdateWindow")
procCreateWindowEx = moduser32.NewProc("CreateWindowExW")
@ -89,11 +90,8 @@ var (
procCreatePopupMenu = moduser32.NewProc("CreatePopupMenu")
procCheckMenuRadioItem = moduser32.NewProc("CheckMenuRadioItem")
procCreateIconFromResourceEx = moduser32.NewProc("CreateIconFromResourceEx")
//procSysColorBrush = moduser32.NewProc("GetSysColorBrush")
//procSetMenu = moduser32.NewProc("SetMenu")
//procDrawMenuBar = moduser32.NewProc("DrawMenuBar")
//procInsertMenuItem = moduser32.NewProc("InsertMenuItemW") // FIXIT:
procInsertMenuItem = moduser32.NewProc("InsertMenuItemW")
procCheckMenuItem = moduser32.NewProc("CheckMenuItem")
procClientToScreen = moduser32.NewProc("ClientToScreen")
procIsDialogMessage = moduser32.NewProc("IsDialogMessageW")
procIsWindow = moduser32.NewProc("IsWindow")
@ -146,20 +144,19 @@ var (
procSetClassLong = moduser32.NewProc("SetClassLongW")
procSetClassLongPtr = moduser32.NewProc("SetClassLongPtrW")
libuser32, _ = syscall.LoadLibrary("user32.dll")
insertMenuItem, _ = syscall.GetProcAddress(libuser32, "InsertMenuItemW")
setMenuItemInfo, _ = syscall.GetProcAddress(libuser32, "SetMenuItemInfoW")
setMenu, _ = syscall.GetProcAddress(libuser32, "SetMenu")
drawMenuBar, _ = syscall.GetProcAddress(libuser32, "DrawMenuBar")
trackPopupMenuEx, _ = syscall.GetProcAddress(libuser32, "TrackPopupMenuEx")
getKeyState, _ = syscall.GetProcAddress(libuser32, "GetKeyState")
getSysColorBrush, _ = syscall.GetProcAddress(libuser32, "GetSysColorBrush")
procSetMenu = moduser32.NewProc("SetMenu")
procAppendMenu = moduser32.NewProc("AppendMenuW")
procSetMenuItemInfo = moduser32.NewProc("SetMenuItemInfoW")
procDrawMenuBar = moduser32.NewProc("DrawMenuBar")
procTrackPopupMenuEx = moduser32.NewProc("TrackPopupMenuEx")
procGetKeyState = moduser32.NewProc("GetKeyState")
procGetSysColorBrush = moduser32.NewProc("GetSysColorBrush")
getWindowPlacement, _ = syscall.GetProcAddress(libuser32, "GetWindowPlacement")
setWindowPlacement, _ = syscall.GetProcAddress(libuser32, "SetWindowPlacement")
procGetWindowPlacement = moduser32.NewProc("GetWindowPlacement")
procSetWindowPlacement = moduser32.NewProc("SetWindowPlacement")
setScrollInfo, _ = syscall.GetProcAddress(libuser32, "SetScrollInfo")
getScrollInfo, _ = syscall.GetProcAddress(libuser32, "GetScrollInfo")
procGetScrollInfo = moduser32.NewProc("GetScrollInfo")
procSetScrollInfo = moduser32.NewProc("SetScrollInfo")
mainThread HANDLE
)
@ -182,6 +179,11 @@ func RegisterClassEx(wndClassEx *WNDCLASSEX) ATOM {
return ATOM(ret)
}
func GetDesktopWindow() HWND {
ret, _, _ := procGetDesktopWindow.Call()
return ret
}
func LoadIcon(instance HINSTANCE, iconName *uint16) HICON {
ret, _, _ := procLoadIcon.Call(
uintptr(instance),
@ -667,15 +669,7 @@ func GetSystemMetrics(index int) int {
}
func GetSysColorBrush(nIndex int) HBRUSH {
/*
ret, _, _ := procSysColorBrush.Call(1,
uintptr(nIndex),
0,
0)
return HBRUSH(ret)
*/
ret, _, _ := syscall.Syscall(getSysColorBrush, 1,
ret, _, _ := procGetSysColorBrush.Call(1,
uintptr(nIndex),
0,
0)
@ -828,10 +822,17 @@ func CreateMenu() HMENU {
}
func SetMenu(hWnd HWND, hMenu HMENU) bool {
ret, _, _ := syscall.Syscall(setMenu, 2,
uintptr(hWnd),
uintptr(hMenu),
0)
ret, _, _ := procSetMenu.Call(hWnd, hMenu)
return ret != 0
}
func AppendMenu(hMenu HMENU, uFlags uint32, uIDNewItem uintptr, lpNewItem *uint16) bool {
ret, _, _ := procAppendMenu.Call(
hMenu,
uintptr(uFlags),
uIDNewItem,
uintptr(unsafe.Pointer(lpNewItem)))
return ret != 0
}
@ -848,39 +849,36 @@ func SelectRadioMenuItem(menuID uint16, startID uint16, endID uint16, hwnd HWND)
}
func CreatePopupMenu() HMENU {
func CreatePopupMenu() PopupMenu {
ret, _, _ := procCreatePopupMenu.Call(0,
0,
0,
0)
return HMENU(ret)
return PopupMenu(ret)
}
func TrackPopupMenuEx(hMenu HMENU, fuFlags uint32, x, y int32, hWnd HWND, lptpm *TPMPARAMS) BOOL {
ret, _, _ := syscall.Syscall6(trackPopupMenuEx, 6,
uintptr(hMenu),
func TrackPopupMenuEx(hMenu HMENU, fuFlags uint32, x, y int32, hWnd HWND, lptpm *TPMPARAMS) bool {
ret, _, _ := procTrackPopupMenuEx.Call(
hMenu,
uintptr(fuFlags),
uintptr(x),
uintptr(y),
uintptr(hWnd),
hWnd,
uintptr(unsafe.Pointer(lptpm)))
return BOOL(ret)
}
func DrawMenuBar(hWnd HWND) bool {
ret, _, _ := syscall.Syscall(drawMenuBar, 1,
uintptr(hWnd),
0,
0)
return ret != 0
}
func DrawMenuBar(hWnd HWND) bool {
ret, _, _ := procDrawMenuBar.Call(hWnd, 0, 0)
return ret != 0
}
func InsertMenuItem(hMenu HMENU, uItem uint32, fByPosition bool, lpmii *MENUITEMINFO) bool {
ret, _, _ := syscall.Syscall6(insertMenuItem, 4,
uintptr(hMenu),
ret, _, _ := procInsertMenuItem.Call(
hMenu,
uintptr(uItem),
uintptr(BoolToBOOL(fByPosition)),
uintptr(unsafe.Pointer(lpmii)),
@ -891,8 +889,8 @@ func InsertMenuItem(hMenu HMENU, uItem uint32, fByPosition bool, lpmii *MENUITEM
}
func SetMenuItemInfo(hMenu HMENU, uItem uint32, fByPosition bool, lpmii *MENUITEMINFO) bool {
ret, _, _ := syscall.Syscall6(setMenuItemInfo, 4,
uintptr(hMenu),
ret, _, _ := procSetMenuItemInfo.Call(
hMenu,
uintptr(uItem),
uintptr(BoolToBOOL(fByPosition)),
uintptr(unsafe.Pointer(lpmii)),
@ -1273,7 +1271,7 @@ func CallNextHookEx(hhk HHOOK, nCode int, wParam WPARAM, lParam LPARAM) LRESULT
}
func GetKeyState(nVirtKey int32) int16 {
ret, _, _ := syscall.Syscall(getKeyState, 1,
ret, _, _ := procGetKeyState.Call(
uintptr(nVirtKey),
0,
0)
@ -1291,7 +1289,7 @@ func DestroyMenu(hMenu HMENU) bool {
}
func GetWindowPlacement(hWnd HWND, lpwndpl *WINDOWPLACEMENT) bool {
ret, _, _ := syscall.Syscall(getWindowPlacement, 2,
ret, _, _ := procGetWindowPlacement.Call(
uintptr(hWnd),
uintptr(unsafe.Pointer(lpwndpl)),
0)
@ -1300,7 +1298,7 @@ func GetWindowPlacement(hWnd HWND, lpwndpl *WINDOWPLACEMENT) bool {
}
func SetWindowPlacement(hWnd HWND, lpwndpl *WINDOWPLACEMENT) bool {
ret, _, _ := syscall.Syscall(setWindowPlacement, 2,
ret, _, _ := procSetWindowPlacement.Call(
uintptr(hWnd),
uintptr(unsafe.Pointer(lpwndpl)),
0)
@ -1309,7 +1307,7 @@ func SetWindowPlacement(hWnd HWND, lpwndpl *WINDOWPLACEMENT) bool {
}
func SetScrollInfo(hwnd HWND, fnBar int32, lpsi *SCROLLINFO, fRedraw bool) int32 {
ret, _, _ := syscall.Syscall6(setScrollInfo, 4,
ret, _, _ := procSetScrollInfo.Call(
hwnd,
uintptr(fnBar),
uintptr(unsafe.Pointer(lpsi)),
@ -1321,7 +1319,7 @@ func SetScrollInfo(hwnd HWND, fnBar int32, lpsi *SCROLLINFO, fRedraw bool) int32
}
func GetScrollInfo(hwnd HWND, fnBar int32, lpsi *SCROLLINFO) bool {
ret, _, _ := syscall.Syscall(getScrollInfo, 3,
ret, _, _ := procGetScrollInfo.Call(
hwnd,
uintptr(fnBar),
uintptr(unsafe.Pointer(lpsi)))