diff --git a/v3/STATUS.md b/v3/STATUS.md index 3b35a554..e600dce2 100644 --- a/v3/STATUS.md +++ b/v3/STATUS.md @@ -2,6 +2,15 @@ Status of features in v3. Incomplete - please add as you see fit. + +## Major Blockers + +- [ ] Dev Support - What will it look like? +- [ ] Translucency on Windows doesn't work right +- [ ] Events - cross-platform events, simplified event handling? +- [ ] Error handling - needs to be revised. Centralised system error handling? +- [ ] Documentation - needs to be written + ## Application Application interface methods @@ -14,7 +23,7 @@ Application interface methods | name() string | | | Y | | | getCurrentWindowID() uint | Y | | Y | | | showAboutDialog(name string, description string, icon []byte) | | | Y | | -| setIcon(icon []byte) | | | Y | | +| setIcon(icon []byte) | - | | Y | | | on(id uint) | | | Y | | | dispatchOnMainThread(fn func()) | Y | | Y | | | hide() | Y | | Y | | @@ -162,7 +171,7 @@ An 'X' indicates that the option is not supported by the platform. | Width | Y | | | | | Height | Y | | | | | AlwaysOnTop | Y | | | | -| URL | | | | | +| URL | Y | | | | | DisableResize | Y | | | | | Frameless | Y | | | | | MinWidth | Y | | | | @@ -173,9 +182,9 @@ An 'X' indicates that the option is not supported by the platform. | Mac | - | - | | | | BackgroundType | | | | Acrylic seems to work but the others don't | | BackgroundColour | Y | | | | -| HTML | | | | | -| JS | | | | | -| CSS | | | | | +| HTML | Y | | | | +| JS | Y | | | | +| CSS | Y | | | | | X | Y | | | | | Y | Y | | | | | HideOnClose | Y | | | | @@ -277,16 +286,21 @@ Built-in plugin support: | Start at login | | | Y | | | Server | | | | | + +TODO: + +- Ensure each plugin has a JS wrapper that can be injected into the window. + ## Packaging | | Windows | Linux | Mac | Notes | |-----------------|---------|-------|-----|-------| -| Icon Generation | | | Y | | -| Icon Embedding | | | Y | | +| Icon Generation | Y | | Y | | +| Icon Embedding | Y | | Y | | | Info.plist | - | | Y | | | NSIS Installer | | | - | | -| Mac bundle | | | Y | | -| Windows exe | | | - | | +| Mac bundle | - | | Y | | +| Windows exe | Y | | - | | ## Frameless Windows @@ -308,10 +322,11 @@ Built-in plugin support: | TitleBar | | Standard window decorations by default | | Appearance | DefaultAppearance | | | InvisibleTitleBarHeight | 0 | Creates an invisible title bar for frameless windows | +| DisableShadow | false | Disables the window drop shadow | ## Windows Specific -- [x] Translucency +- [ ] Translucency - [x] Custom Themes ### Windows Options @@ -348,3 +363,4 @@ Built-in plugin support: | window | | | | | windowjs | | | | | wml | | | | + diff --git a/v3/pkg/application/application.go b/v3/pkg/application/application.go index 7508afda..0fdda3a1 100644 --- a/v3/pkg/application/application.go +++ b/v3/pkg/application/application.go @@ -391,8 +391,8 @@ func (a *App) Run() error { // set the application menu if runtime.GOOS == "darwin" { a.impl.setApplicationMenu(a.ApplicationMenu) - a.impl.setIcon(a.options.Icon) } + a.impl.setIcon(a.options.Icon) err := a.impl.run() if err != nil { diff --git a/v3/pkg/application/application_windows.go b/v3/pkg/application/application_windows.go index 9a2bd1c8..1c32e54b 100644 --- a/v3/pkg/application/application_windows.go +++ b/v3/pkg/application/application_windows.go @@ -22,7 +22,8 @@ var windowClassName = lo.Must(syscall.UTF16PtrFromString("WailsWebviewWindow")) type windowsApp struct { parent *App - instance w32.HINSTANCE + windowClass w32.WNDCLASSEX + instance w32.HINSTANCE windowMap map[w32.HWND]*windowsWebviewWindow windowMapLock sync.RWMutex @@ -182,22 +183,20 @@ func (m *windowsApp) init() { icon := w32.LoadIconWithResourceID(m.instance, w32.IDI_APPLICATION) - var wc w32.WNDCLASSEX - wc.Size = uint32(unsafe.Sizeof(wc)) - wc.Style = w32.CS_HREDRAW | w32.CS_VREDRAW - wc.WndProc = syscall.NewCallback(m.wndProc) - wc.Instance = m.instance - wc.Background = w32.COLOR_BTNFACE + 1 - wc.Icon = icon - wc.Cursor = w32.LoadCursorWithResourceID(0, w32.IDC_ARROW) - wc.ClassName = windowClassName - wc.MenuName = nil - wc.IconSm = icon + 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(&wc); ret == 0 { + if ret := w32.RegisterClassEx(&m.windowClass); ret == 0 { panic(syscall.GetLastError()) } - m.isDarkMode = w32.IsCurrentlyDarkMode() } diff --git a/v3/pkg/application/systemtray_windows.go b/v3/pkg/application/systemtray_windows.go index 827081ff..16abd727 100644 --- a/v3/pkg/application/systemtray_windows.go +++ b/v3/pkg/application/systemtray_windows.go @@ -71,14 +71,14 @@ func (s *windowsSystemTray) run() { } if s.parent.icon != nil { - s.lightModeIcon = lo.Must(w32.CreateHIconFromImage(s.parent.icon)) + s.lightModeIcon = lo.Must(w32.CreateSmallHIconFromImage(s.parent.icon)) } else { - s.lightModeIcon = lo.Must(w32.CreateHIconFromImage(icons.SystrayLight)) + s.lightModeIcon = lo.Must(w32.CreateSmallHIconFromImage(icons.SystrayLight)) } if s.parent.darkModeIcon != nil { - s.darkModeIcon = lo.Must(w32.CreateHIconFromImage(s.parent.darkModeIcon)) + s.darkModeIcon = lo.Must(w32.CreateSmallHIconFromImage(s.parent.darkModeIcon)) } else { - s.darkModeIcon = lo.Must(w32.CreateHIconFromImage(icons.SystrayDark)) + s.darkModeIcon = lo.Must(w32.CreateSmallHIconFromImage(icons.SystrayDark)) } s.uid = nid.UID @@ -149,7 +149,7 @@ func (s *windowsSystemTray) newNotifyIconData() w32.NOTIFYICONDATA { func (s *windowsSystemTray) setIcon(icon []byte) { var err error - s.lightModeIcon, err = w32.CreateHIconFromImage(icon) + s.lightModeIcon, err = w32.CreateSmallHIconFromImage(icon) if err != nil { panic(syscall.GetLastError()) } @@ -161,7 +161,7 @@ func (s *windowsSystemTray) setIcon(icon []byte) { } func (s *windowsSystemTray) setDarkModeIcon(icon []byte) { var err error - s.darkModeIcon, err = w32.CreateHIconFromImage(icon) + s.darkModeIcon, err = w32.CreateSmallHIconFromImage(icon) if err != nil { panic(syscall.GetLastError()) } diff --git a/v3/pkg/icons/ApplicationDarkMode-256.png b/v3/pkg/icons/ApplicationDarkMode-256.png index 10959272..68e5e766 100644 Binary files a/v3/pkg/icons/ApplicationDarkMode-256.png and b/v3/pkg/icons/ApplicationDarkMode-256.png differ diff --git a/v3/pkg/icons/ApplicationLightMode-256.png b/v3/pkg/icons/ApplicationLightMode-256.png index 1652dfe9..fe74e60e 100644 Binary files a/v3/pkg/icons/ApplicationLightMode-256.png and b/v3/pkg/icons/ApplicationLightMode-256.png differ diff --git a/v3/pkg/icons/icon.ico b/v3/pkg/icons/icon.ico new file mode 100644 index 00000000..bfa0690b Binary files /dev/null and b/v3/pkg/icons/icon.ico differ diff --git a/v3/pkg/icons/icons.go b/v3/pkg/icons/icons.go index 4023df12..7434ba33 100644 --- a/v3/pkg/icons/icons.go +++ b/v3/pkg/icons/icons.go @@ -8,6 +8,9 @@ var SystrayMacTemplate []byte //go:embed systray-light.png var SystrayLight []byte +//go:embed icon.ico +var DefaultWindowsIcon []byte + //go:embed systray-dark.png var SystrayDark []byte diff --git a/v3/pkg/w32/icon.go b/v3/pkg/w32/icon.go index e84444e6..610dd3ca 100644 --- a/v3/pkg/w32/icon.go +++ b/v3/pkg/w32/icon.go @@ -42,8 +42,8 @@ func isICO(fileData []byte) bool { return string(fileData[:4]) == "\x00\x00\x01\x00" } -// CreateHIconFromImage creates a HICON from a PNG or ICO file -func CreateHIconFromImage(fileData []byte) (HICON, error) { +// CreateSmallHIconFromImage creates a HICON from a PNG or ICO file +func CreateSmallHIconFromImage(fileData []byte) (HICON, error) { if len(fileData) < 8 { return 0, fmt.Errorf("invalid file format") } @@ -63,3 +63,29 @@ func CreateHIconFromImage(fileData []byte) (HICON, error) { LR_DEFAULTSIZE) return HICON(icon), err } + +// CreateLargeHIconFromImage creates a HICON from a PNG or ICO file +func CreateLargeHIconFromImage(fileData []byte) (HICON, error) { + if len(fileData) < 8 { + return 0, fmt.Errorf("invalid file format") + } + + if !isPNG(fileData) && !isICO(fileData) { + return 0, fmt.Errorf("unsupported file format") + } + iconWidth := GetSystemMetrics(SM_CXICON) + iconHeight := GetSystemMetrics(SM_CXICON) + icon, err := CreateIconFromResourceEx( + uintptr(unsafe.Pointer(&fileData[0])), + uint32(len(fileData)), + true, + 0x00030000, + iconWidth, + iconHeight, + LR_DEFAULTSIZE) + return HICON(icon), err +} + +func SetWindowIcon(hwnd HWND, icon HICON) { + SendMessage(hwnd, WM_SETICON, ICON_SMALL, uintptr(icon)) +} diff --git a/v3/pkg/w32/window.go b/v3/pkg/w32/window.go index e4aaafdc..84fcda9e 100644 --- a/v3/pkg/w32/window.go +++ b/v3/pkg/w32/window.go @@ -14,6 +14,7 @@ import ( const ( GCLP_HBRBACKGROUND int32 = -10 + GCLP_HICON int32 = -14 ) func ExtendFrameIntoClientArea(hwnd uintptr, extend bool) { @@ -70,6 +71,10 @@ func ShowWindowMinimised(hwnd uintptr) { showWindow(hwnd, SW_MINIMIZE) } +func SetApplicationIcon(hwnd uintptr, icon HICON) { + setClassLongPtr(hwnd, GCLP_HICON, icon) +} + func SetBackgroundColour(hwnd uintptr, r, g, b uint8) { col := uint32(r) | uint32(g)<<8 | uint32(b)<<16 hbrush, _, _ := procCreateSolidBrush.Call(uintptr(col))