From 4f2788a2945195ef7239e783e44059004e9035df Mon Sep 17 00:00:00 2001 From: Lea Anthony Date: Sat, 10 Jul 2021 17:03:46 +1000 Subject: [PATCH] [windows] Fixes for radiobox sync --- v2/go.mod | 1 + v2/go.sum | 4 ++ v2/internal/ffenestri/ffenestri_windows.go | 13 ++-- v2/internal/ffenestri/windows_checkboxes.go | 24 ++++++++ .../ffenestri/windows_errorhandler_debug.go | 28 +++++++++ .../windows_errorhandler_production.go | 47 ++++++++++++++ v2/internal/ffenestri/windows_menu.go | 7 ++- v2/internal/ffenestri/windows_radiogroup.go | 61 ++++++++++++++++++- v2/internal/ffenestri/windows_win32.go | 18 +++--- v2/internal/menumanager/applicationmenu.go | 1 + v2/internal/menumanager/menumanager.go | 25 ++++++++ 11 files changed, 212 insertions(+), 17 deletions(-) create mode 100644 v2/internal/ffenestri/windows_errorhandler_debug.go create mode 100644 v2/internal/ffenestri/windows_errorhandler_production.go diff --git a/v2/go.mod b/v2/go.mod index db9b041e..86fc85da 100644 --- a/v2/go.mod +++ b/v2/go.mod @@ -33,6 +33,7 @@ require ( github.com/tidwall/sjson v1.1.7 github.com/wzshiming/ctc v1.2.3 github.com/xyproto/xpm v1.2.1 + github.com/ztrue/tracerr v0.3.0 golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b // indirect golang.org/x/mod v0.4.1 // indirect golang.org/x/net v0.0.0-20210326060303-6b1517762897 diff --git a/v2/go.sum b/v2/go.sum index a5f01a3c..0ac137ca 100644 --- a/v2/go.sum +++ b/v2/go.sum @@ -107,6 +107,8 @@ github.com/leaanthony/winicon v0.0.0-20200606125418-4419cea822a0 h1:FPGYnfxuuxqC github.com/leaanthony/winicon v0.0.0-20200606125418-4419cea822a0/go.mod h1:en5xhijl92aphrJdmRPlh4NI1L6wq3gEm0LpXAPghjU= github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y= github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= +github.com/logrusorgru/aurora v0.0.0-20181002194514-a7b3b318ed4e h1:9MlwzLdW7QSDrhDjFlsEYmxpFyIoXmYRon3dt0io31k= +github.com/logrusorgru/aurora v0.0.0-20181002194514-a7b3b318ed4e/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4= github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE= github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= @@ -166,6 +168,8 @@ github.com/xanzy/ssh-agent v0.3.0/go.mod h1:3s9xbODqPuuhK9JV1R321M/FlMZSBvE5aY6e github.com/xyproto/xpm v1.2.1 h1:trdvGjjWBsOOKzBBUPT6JvaIQM3acJEEYfbxN7M96wg= github.com/xyproto/xpm v1.2.1/go.mod h1:cMnesLsD0PBXLgjDfTDEaKr8XyTFsnP1QycSqRw7BiY= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/ztrue/tracerr v0.3.0 h1:lDi6EgEYhPYPnKcjsYzmWw4EkFEoA/gfe+I9Y5f+h6Y= +github.com/ztrue/tracerr v0.3.0/go.mod h1:qEalzze4VN9O8tnhBXScfCrmoJo10o8TN5ciKjm6Mww= golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= diff --git a/v2/internal/ffenestri/ffenestri_windows.go b/v2/internal/ffenestri/ffenestri_windows.go index 36ae93b8..473f7234 100644 --- a/v2/internal/ffenestri/ffenestri_windows.go +++ b/v2/internal/ffenestri/ffenestri_windows.go @@ -14,7 +14,7 @@ extern void DisableWindowIcon(struct Application* app); */ import "C" import ( - "log" + "os" "github.com/wailsapp/wails/v2/pkg/menu" ) @@ -105,7 +105,9 @@ been sent. func checkFatal(err error) { if err != nil { - log.Fatal(err) + globalRadioGroupCache.Dump() + globalRadioGroupMap.Dump() + os.Exit(1) } } @@ -150,7 +152,7 @@ func menuClicked(id uint32) { // Determine if the menu is set or not res, _, err := win32GetMenuState.Call(uintptr(menuItemDetails.parent), uintptr(id), uintptr(MF_BYCOMMAND)) if int(res) == -1 { - log.Fatal(err) + checkFatal(err) } flag := MF_CHECKED @@ -163,11 +165,12 @@ func menuClicked(id uint32) { menuItemDetails := getMenuCacheEntry(menuid) res, _, err = win32CheckMenuItem.Call(uintptr(menuItemDetails.parent), uintptr(menuid), uintptr(flag)) if int(res) == -1 { - log.Fatal(err) + checkFatal(err) } } case menu.RadioType: - selectRadioItemFromWailsMenuID(wailsMenuID, win32MenuID) + err := selectRadioItemFromWailsMenuID(wailsMenuID, win32MenuID) + checkFatal(err) } // Print the click error - it's not fatal diff --git a/v2/internal/ffenestri/windows_checkboxes.go b/v2/internal/ffenestri/windows_checkboxes.go index 92d28a8a..65e29566 100644 --- a/v2/internal/ffenestri/windows_checkboxes.go +++ b/v2/internal/ffenestri/windows_checkboxes.go @@ -3,8 +3,12 @@ package ffenestri import ( + "fmt" + "github.com/leaanthony/slicer" "github.com/wailsapp/wails/v2/internal/menumanager" + "os" "sync" + "text/tabwriter" ) /* --------------------------------------------------------------------------------- @@ -28,6 +32,26 @@ func NewCheckboxCache() *CheckboxCache { } } +func (c *CheckboxCache) Dump() { + // Start a new tabwriter + w := new(tabwriter.Writer) + w.Init(os.Stdout, 8, 8, 0, '\t', 0) + + println("---------------- Checkbox", c, "Dump ----------------") + for _, processedMenu := range c.cache { + println("Menu", processedMenu) + for wailsMenuItemID, win32menus := range processedMenu { + println(" WailsMenu: ", wailsMenuItemID) + menus := slicer.String() + for _, win32menu := range win32menus { + menus.Add(fmt.Sprintf("%v", win32menu)) + } + _, _ = fmt.Fprintf(w, "%s\t%s\n", wailsMenuItemID, menus.Join(", ")) + _ = w.Flush() + } + } +} + func (c *CheckboxCache) addToCheckboxCache(menu *menumanager.ProcessedMenu, item wailsMenuItemID, menuID win32MenuItemID) { // Get map for menu diff --git a/v2/internal/ffenestri/windows_errorhandler_debug.go b/v2/internal/ffenestri/windows_errorhandler_debug.go new file mode 100644 index 00000000..5de8758b --- /dev/null +++ b/v2/internal/ffenestri/windows_errorhandler_debug.go @@ -0,0 +1,28 @@ +//+build windows,debug + +package ffenestri + +import ( + "fmt" + "github.com/ztrue/tracerr" + "runtime" + "strings" +) + +func wall(err error, inputs ...interface{}) error { + if err == nil { + return nil + } + pc, _, _, _ := runtime.Caller(1) + funcName := runtime.FuncForPC(pc).Name() + splitName := strings.Split(funcName, ".") + message := "[" + splitName[len(splitName)-1] + "]" + if len(inputs) > 0 { + params := []string{} + for _, param := range inputs { + params = append(params, fmt.Sprintf("%v", param)) + } + message += "(" + strings.Join(params, " ") + ")" + } + return tracerr.Errorf(message) +} diff --git a/v2/internal/ffenestri/windows_errorhandler_production.go b/v2/internal/ffenestri/windows_errorhandler_production.go new file mode 100644 index 00000000..20c9dc9d --- /dev/null +++ b/v2/internal/ffenestri/windows_errorhandler_production.go @@ -0,0 +1,47 @@ +// +build windows,!debug + +package ffenestri + +import "C" +import ( + "fmt" + "golang.org/x/sys/windows" + "log" + "os" + "runtime" + "strings" + "syscall" +) + +func wall(err error, inputs ...interface{}) error { + if err == nil { + return nil + } + pc, _, _, _ := runtime.Caller(1) + funcName := runtime.FuncForPC(pc).Name() + splitName := strings.Split(funcName, ".") + message := "[" + splitName[len(splitName)-1] + "]" + if len(inputs) > 0 { + params := []string{} + for _, param := range inputs { + params = append(params, fmt.Sprintf("%v", param)) + } + message += "(" + strings.Join(params, " ") + ")" + } + + title, err := syscall.UTF16PtrFromString("Fatal Error") + if err != nil { + log.Fatal(err) + } + + text, err := syscall.UTF16PtrFromString("There has been a fatal error. Details:\n" + message) + if err != nil { + log.Fatal(err) + } + + var flags uint32 = windows.MB_ICONERROR | windows.MB_OK + + _, err = windows.MessageBox(0, text, title, flags|windows.MB_SYSTEMMODAL) + os.Exit(1) + return err +} diff --git a/v2/internal/ffenestri/windows_menu.go b/v2/internal/ffenestri/windows_menu.go index 97d5dbea..5cf81c3c 100644 --- a/v2/internal/ffenestri/windows_menu.go +++ b/v2/internal/ffenestri/windows_menu.go @@ -142,7 +142,10 @@ func (m *Menu) processRadioGroups() error { for _, win32MenuID := range m.initiallySelectedRadioItems { menuItemDetails := getMenuCacheEntry(win32MenuID) wailsMenuID := wailsMenuItemID(menuItemDetails.item.ID) - selectRadioItemFromWailsMenuID(wailsMenuID, win32MenuID) + err := selectRadioItemFromWailsMenuID(wailsMenuID, win32MenuID) + if err != nil { + return err + } } return nil @@ -156,6 +159,8 @@ func (m *Menu) Destroy() error { // Unload this menu's radio groups from the cache globalRadioGroupCache.removeMenuFromRadioBoxCache(m.wailsMenu.Menu) + globalRadioGroupMap.removeMenuFromRadioGroupMapping(m.wailsMenu.Menu) + // Delete menu return destroyWin32Menu(m.menu) } diff --git a/v2/internal/ffenestri/windows_radiogroup.go b/v2/internal/ffenestri/windows_radiogroup.go index b5cce8ee..3f3d349f 100644 --- a/v2/internal/ffenestri/windows_radiogroup.go +++ b/v2/internal/ffenestri/windows_radiogroup.go @@ -3,7 +3,11 @@ package ffenestri import ( + "fmt" + "github.com/leaanthony/slicer" + "os" "sync" + "text/tabwriter" "github.com/wailsapp/wails/v2/internal/menumanager" ) @@ -36,6 +40,26 @@ func NewRadioGroupCache() *RadioGroupCache { } } +func (c *RadioGroupCache) Dump() { + // Start a new tabwriter + w := new(tabwriter.Writer) + w.Init(os.Stdout, 8, 8, 0, '\t', 0) + + println("---------------- RadioGroupCache", c, "Dump ----------------") + for menu, processedMenu := range c.cache { + println("Menu", menu) + _, _ = fmt.Fprintf(w, "Wails ID \tWindows ID Pairs\n") + for wailsMenuItemID, radioGroupStartEnd := range processedMenu { + menus := slicer.String() + for _, se := range radioGroupStartEnd { + menus.Add(fmt.Sprintf("[%d -> %d]", se.startID, se.endID)) + } + _, _ = fmt.Fprintf(w, "%s\t%s\n", wailsMenuItemID, menus.Join(", ")) + _ = w.Flush() + } + } +} + func (c *RadioGroupCache) addToRadioGroupCache(menu *menumanager.ProcessedMenu, item wailsMenuItemID, radioGroupMaps []*radioGroupStartEnd) { c.mutex.Lock() @@ -87,6 +111,25 @@ func NewRadioGroupMap() *RadioGroupMap { } } +func (c *RadioGroupMap) Dump() { + // Start a new tabwriter + w := new(tabwriter.Writer) + w.Init(os.Stdout, 8, 8, 0, '\t', 0) + + println("---------------- RadioGroupMap", c, "Dump ----------------") + for _, processedMenu := range c.cache { + _, _ = fmt.Fprintf(w, "Menu\tWails ID \tWindows IDs\n") + for wailsMenuItemID, win32menus := range processedMenu { + menus := slicer.String() + for _, win32menu := range win32menus { + menus.Add(fmt.Sprintf("%v", win32menu)) + } + _, _ = fmt.Fprintf(w, "%p\t%s\t%s\n", processedMenu, wailsMenuItemID, menus.Join(", ")) + _ = w.Flush() + } + } +} + func (m *RadioGroupMap) addRadioGroupMapping(menu *menumanager.ProcessedMenu, item wailsMenuItemID, win32ID win32MenuItemID) { m.mutex.Lock() @@ -106,6 +149,12 @@ func (m *RadioGroupMap) addRadioGroupMapping(menu *menumanager.ProcessedMenu, it m.mutex.Unlock() } +func (m *RadioGroupMap) removeMenuFromRadioGroupMapping(menu *menumanager.ProcessedMenu) { + m.mutex.Lock() + delete(m.cache, menu) + m.mutex.Unlock() +} + func (m *RadioGroupMap) getRadioGroupMapping(wailsMenuID wailsMenuItemID) []win32MenuItemID { m.mutex.Lock() result := []win32MenuItemID{} @@ -119,7 +168,7 @@ func (m *RadioGroupMap) getRadioGroupMapping(wailsMenuID wailsMenuItemID) []win3 return result } -func selectRadioItemFromWailsMenuID(wailsMenuID wailsMenuItemID, win32MenuID win32MenuItemID) { +func selectRadioItemFromWailsMenuID(wailsMenuID wailsMenuItemID, win32MenuID win32MenuItemID) error { radioItemGroups := globalRadioGroupCache.getRadioGroupMappings(wailsMenuID) // Figure out offset into group var offset win32MenuItemID = 0 @@ -132,6 +181,14 @@ func selectRadioItemFromWailsMenuID(wailsMenuID wailsMenuItemID, win32MenuID win for _, radioItemGroup := range radioItemGroups { selectedMenuID := radioItemGroup.startID + offset menuItemDetails := getMenuCacheEntry(selectedMenuID) - selectRadioItem(selectedMenuID, radioItemGroup.startID, radioItemGroup.endID, menuItemDetails.parent) + if menuItemDetails != nil { + if menuItemDetails.parent != 0 { + err := selectRadioItem(selectedMenuID, radioItemGroup.startID, radioItemGroup.endID, menuItemDetails.parent) + if err != nil { + return err + } + } + } } + return nil } diff --git a/v2/internal/ffenestri/windows_win32.go b/v2/internal/ffenestri/windows_win32.go index b3cd1806..2f5763c1 100644 --- a/v2/internal/ffenestri/windows_win32.go +++ b/v2/internal/ffenestri/windows_win32.go @@ -3,7 +3,6 @@ package ffenestri import ( - "log" "unsafe" "github.com/wailsapp/wails/v2/internal/menumanager" @@ -45,15 +44,15 @@ const MF_BYPOSITION uint32 = 0x00000400 func createWin32Menu() (win32Menu, error) { res, _, err := win32CreateMenu.Call() if res == 0 { - return 0, err + return 0, wall(err) } return win32Menu(res), nil } func destroyWin32Menu(menu win32Menu) error { - res, _, err := win32CreateMenu.Call(uintptr(menu)) + res, _, err := win32DestroyMenu.Call(uintptr(menu)) if res == 0 { - return err + return wall(err, "Menu:", menu) } return nil } @@ -61,7 +60,7 @@ func destroyWin32Menu(menu win32Menu) error { func createWin32PopupMenu() (win32Menu, error) { res, _, err := win32CreatePopupMenu.Call() if res == 0 { - return 0, err + return 0, wall(err) } return win32Menu(res), nil } @@ -78,7 +77,7 @@ func appendWin32MenuItem(menu win32Menu, flags uintptr, submenuOrID uintptr, lab uintptr(unsafe.Pointer(menuText)), ) if res == 0 { - return err + return wall(err, "Menu", menu, "Flags", flags, "submenuOrID", submenuOrID, "label", label) } return nil } @@ -86,14 +85,15 @@ func appendWin32MenuItem(menu win32Menu, flags uintptr, submenuOrID uintptr, lab func setWindowMenu(window win32Window, menu win32Menu) error { res, _, err := win32SetMenu.Call(uintptr(window), uintptr(menu)) if res == 0 { - return err + return wall(err, "window", window, "menu", menu) } return nil } -func selectRadioItem(selectedMenuID, startMenuItemID, endMenuItemID win32MenuItemID, parent win32Menu) { +func selectRadioItem(selectedMenuID, startMenuItemID, endMenuItemID win32MenuItemID, parent win32Menu) error { res, _, err := win32CheckMenuRadioItem.Call(uintptr(parent), uintptr(startMenuItemID), uintptr(endMenuItemID), uintptr(selectedMenuID), uintptr(MF_BYCOMMAND)) if int(res) == 0 { - log.Fatal(err) + return wall(err, selectedMenuID, startMenuItemID, endMenuItemID, parent) } + return nil } diff --git a/v2/internal/menumanager/applicationmenu.go b/v2/internal/menumanager/applicationmenu.go index 3e6ba83e..424834e5 100644 --- a/v2/internal/menumanager/applicationmenu.go +++ b/v2/internal/menumanager/applicationmenu.go @@ -41,6 +41,7 @@ func (m *Manager) processApplicationMenu() error { // Process the menu m.processedApplicationMenu = NewWailsMenu(m.applicationMenuItemMap, m.applicationMenu) + m.processRadioGroups(m.processedApplicationMenu, m.applicationMenuItemMap) applicationMenuJSON, err := m.processedApplicationMenu.AsJSON() if err != nil { return err diff --git a/v2/internal/menumanager/menumanager.go b/v2/internal/menumanager/menumanager.go index 76fe9b49..61195326 100644 --- a/v2/internal/menumanager/menumanager.go +++ b/v2/internal/menumanager/menumanager.go @@ -22,6 +22,9 @@ type Manager struct { // Tray menu stores trayMenus map[string]*TrayMenu trayMenuPointers map[*menu.TrayMenu]string + + // Radio groups + radioGroups map[*menu.MenuItem][]*menu.MenuItem } func NewManager() *Manager { @@ -31,6 +34,7 @@ func NewManager() *Manager { contextMenuPointers: make(map[*menu.ContextMenu]string), trayMenus: make(map[string]*TrayMenu), trayMenuPointers: make(map[*menu.TrayMenu]string), + radioGroups: make(map[*menu.MenuItem][]*menu.MenuItem), } } @@ -73,6 +77,14 @@ func (m *Manager) ProcessClick(menuID string, data string, menuType string, pare menuItem.Checked = !menuItem.Checked } + if menuItem.Type == menu.RadioType { + println("Toggle radio") + // Get my radio group + for _, radioMenuItem := range m.radioGroups[menuItem] { + radioMenuItem.Checked = (radioMenuItem == menuItem) + } + } + if menuItem.Click == nil { // No callback return fmt.Errorf("No callback for menu '%s'", menuItem.Label) @@ -89,3 +101,16 @@ func (m *Manager) ProcessClick(menuID string, data string, menuType string, pare return nil } + +func (m *Manager) processRadioGroups(processedMenu *WailsMenu, itemMap *MenuItemMap) { + for _, group := range processedMenu.RadioGroups { + radioGroupMenuItems := []*menu.MenuItem{} + for _, member := range group.Members { + item := m.getMenuItemByID(itemMap, member) + radioGroupMenuItems = append(radioGroupMenuItems, item) + } + for _, radioGroupMenuItem := range radioGroupMenuItems { + m.radioGroups[radioGroupMenuItem] = radioGroupMenuItems + } + } +}