Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions v3/examples/window/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -701,6 +701,14 @@ func main() {
w.OpenDevTools()
})
})

if runtime.GOOS == "windows" {
stateMenu.Add("Show SnapAssist").OnClick(func(ctx *application.Context) {
currentWindow(func(w *application.WebviewWindow) {
w.ShowSnapAssist()
})
})
}

if runtime.GOOS != "darwin" {
stateMenu.Add("Flash for 5s").OnClick(func(ctx *application.Context) {
Expand Down
11 changes: 11 additions & 0 deletions v3/pkg/application/webview_window.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ type (
selectAll()
redo()
showMenuBar()
showSnapAssist()
hideMenuBar()
toggleMenuBar()
setMenu(menu *Menu)
Expand Down Expand Up @@ -458,6 +459,7 @@ func (w *WebviewWindow) Show() Window {
return w
}


// Hide hides the window.
func (w *WebviewWindow) Hide() Window {
w.options.Hidden = true
Expand Down Expand Up @@ -1413,6 +1415,15 @@ func (w *WebviewWindow) ShowMenuBar() {
InvokeSync(w.impl.showMenuBar)
}

// ShowSnapAssist shows the Windows SnapAssist overlay for this window.
// This is a Windows-only feature and does nothing on other platforms.
func (w *WebviewWindow) ShowSnapAssist() {
if w.impl == nil || w.isDestroyed() {
return
}
InvokeSync(w.impl.showSnapAssist)
}

// HideMenuBar hides the menu bar for the window.
func (w *WebviewWindow) HideMenuBar() {
if w.impl == nil || w.isDestroyed() {
Expand Down
1 change: 1 addition & 0 deletions v3/pkg/application/webview_window_darwin.go
Original file line number Diff line number Diff line change
Expand Up @@ -1435,6 +1435,7 @@ func (w *macosWebviewWindow) redo() {
}

func (w *macosWebviewWindow) showMenuBar() {}
func (w *macosWebviewWindow) showSnapAssist() {}
func (w *macosWebviewWindow) hideMenuBar() {}
func (w *macosWebviewWindow) toggleMenuBar() {}
func (w *macosWebviewWindow) setMenu(_ *Menu) {}
1 change: 1 addition & 0 deletions v3/pkg/application/webview_window_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -425,5 +425,6 @@ func (w *linuxWebviewWindow) hide() {
}

func (w *linuxWebviewWindow) showMenuBar() {}
func (w *linuxWebviewWindow) showSnapAssist() {}
func (w *linuxWebviewWindow) hideMenuBar() {}
func (w *linuxWebviewWindow) toggleMenuBar() {}
65 changes: 65 additions & 0 deletions v3/pkg/application/webview_window_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -2276,8 +2276,73 @@ func (w *windowsWebviewWindow) showMenuBar() {
}
}

func (w *windowsWebviewWindow) showSnapAssist() {
// Ensure this window is focused first
w32.SetForegroundWindow(w.hwnd)

// Send Win+Z key combination to trigger SnapAssist
w.sendKeyCombo(0x5A, 0x5B) // Z key with Windows key
}

func (w *windowsWebviewWindow) hideMenuBar() {
if w.menu != nil {
w32.SetMenu(w.hwnd, 0)
}
}

// sendKeyCombo sends a key combination (key + modifier)
func (w *windowsWebviewWindow) sendKeyCombo(key, modifier uint16) {
// Create input array for key combination
inputs := make([]w32.INPUT, 4)

// Modifier key down (Windows key)
inputs[0] = w32.INPUT{
Type: 1, // INPUT_KEYBOARD
Ki: w32.KEYBDINPUT{
WVk: modifier,
WScan: 0,
DwFlags: 0,
Time: 0,
DwExtraInfo: 0,
},
}

// Main key down (Z key)
inputs[1] = w32.INPUT{
Type: 1, // INPUT_KEYBOARD
Ki: w32.KEYBDINPUT{
WVk: key,
WScan: 0,
DwFlags: 0,
Time: 0,
DwExtraInfo: 0,
},
}

// Main key up (Z key)
inputs[2] = w32.INPUT{
Type: 1, // INPUT_KEYBOARD
Ki: w32.KEYBDINPUT{
WVk: key,
WScan: 0,
DwFlags: 0x0002, // KEYEVENTF_KEYUP
Time: 0,
DwExtraInfo: 0,
},
}

// Modifier key up (Windows key)
inputs[3] = w32.INPUT{
Type: 1, // INPUT_KEYBOARD
Ki: w32.KEYBDINPUT{
WVk: modifier,
WScan: 0,
DwFlags: 0x0002, // KEYEVENTF_KEYUP
Time: 0,
DwExtraInfo: 0,
},
}

// Send the input events
w32.SendInput(len(inputs), unsafe.Pointer(&inputs[0]), int(unsafe.Sizeof(w32.INPUT{})))
}
1 change: 1 addition & 0 deletions v3/pkg/application/window.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ type Window interface {
SetZoom(magnification float64) Window
Show() Window
ShowMenuBar()
ShowSnapAssist()
Size() (width int, height int)
OpenDevTools()
ToggleFullscreen()
Expand Down
1 change: 1 addition & 0 deletions v3/pkg/application/window_manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -133,3 +133,4 @@ func (wm *WindowManager) GetAll() []Window {
}
return windows
}

8 changes: 8 additions & 0 deletions v3/pkg/application/window_manager_darwin.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
//go:build darwin

package application

// showSnapAssist is a no-op on macOS as SnapAssist is a Windows-only feature
func showSnapAssist(window *WebviewWindow) {
// SnapAssist is not available on macOS
}
8 changes: 8 additions & 0 deletions v3/pkg/application/window_manager_linux.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
//go:build linux

package application

// showSnapAssist is a no-op on Linux as SnapAssist is a Windows-only feature
func showSnapAssist(window *WebviewWindow) {
// SnapAssist is not available on Linux
}
95 changes: 95 additions & 0 deletions v3/pkg/application/window_manager_windows.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
//go:build windows

package application

import (
"time"
"unsafe"

"github.com/wailsapp/wails/v3/pkg/w32"
)

// showSnapAssist sends Win+Z key combination to trigger SnapAssist for the specified window
func showSnapAssist(window *WebviewWindow) {
// First, ensure the window is visible and focused to target SnapAssist correctly
window.Show() // Ensure window is visible
window.Restore() // Restore if minimized

// Get the native window handle and set it as foreground
if hwnd := w32.HWND(window.impl.nativeWindowHandle()); hwnd != 0 {
w32.SetForegroundWindow(hwnd)
// Small delay to ensure window focus is established
time.Sleep(50 * time.Millisecond)
}

// Send Win+Z key combination using a robotgo-style approach
sendKeyCombo(w32.VK_Z, w32.VK_LWIN)
}

// sendKeyCombo sends a key combination (key + modifiers)
func sendKeyCombo(key uint16, modifiers ...uint16) {
// Calculate total inputs needed (press and release for each key)
numKeys := len(modifiers) + 1
inputs := make([]w32.INPUT, numKeys*2)

inputIndex := 0

// Press all modifier keys first
for _, modifier := range modifiers {
inputs[inputIndex] = w32.INPUT{
Type: w32.INPUT_KEYBOARD,
Ki: w32.KEYBDINPUT{
WVk: modifier,
WScan: 0,
DwFlags: 0,
Time: 0,
DwExtraInfo: 0,
},
}
inputIndex++
}

// Press the main key
inputs[inputIndex] = w32.INPUT{
Type: w32.INPUT_KEYBOARD,
Ki: w32.KEYBDINPUT{
WVk: key,
WScan: 0,
DwFlags: 0,
Time: 0,
DwExtraInfo: 0,
},
}
inputIndex++

// Release the main key
inputs[inputIndex] = w32.INPUT{
Type: w32.INPUT_KEYBOARD,
Ki: w32.KEYBDINPUT{
WVk: key,
WScan: 0,
DwFlags: w32.KEYEVENTF_KEYUP,
Time: 0,
DwExtraInfo: 0,
},
}
inputIndex++

// Release all modifier keys in reverse order
for i := len(modifiers) - 1; i >= 0; i-- {
inputs[inputIndex] = w32.INPUT{
Type: w32.INPUT_KEYBOARD,
Ki: w32.KEYBDINPUT{
WVk: modifiers[i],
WScan: 0,
DwFlags: w32.KEYEVENTF_KEYUP,
Time: 0,
DwExtraInfo: 0,
},
}
inputIndex++
}

// Send all input events
w32.SendInput(len(inputs), unsafe.Pointer(&inputs[0]), int(unsafe.Sizeof(w32.INPUT{})))
}
Comment on lines +29 to +95

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Address code duplication between files.

There are now two different implementations of sendKeyCombo():

  1. This sophisticated version supporting multiple modifiers
  2. A simpler version in webview_window_windows.go (lines 2293-2348) supporting only one modifier

Consider consolidating to avoid duplication:

  • Remove the sendKeyCombo method from webview_window_windows.go
  • Update the showSnapAssist method in webview_window_windows.go to call this function
  • Or make this function available to both implementations

This would eliminate code duplication and ensure consistent behavior across the codebase.

🤖 Prompt for AI Agents
In v3/pkg/application/window_manager_windows.go lines 29 to 95, the sendKeyCombo
function supports multiple modifiers but duplicates a simpler version in
webview_window_windows.go lines 2293 to 2348. To fix this, remove the
sendKeyCombo function from webview_window_windows.go and update its
showSnapAssist method to call the sendKeyCombo function from
window_manager_windows.go instead. Make sendKeyCombo accessible to both files by
moving it to a shared package or making it a public function, ensuring
consistent behavior and eliminating duplication.

6 changes: 6 additions & 0 deletions v3/pkg/w32/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -1994,6 +1994,7 @@ const (
VK_INSERT = 0x2D
VK_DELETE = 0x2E
VK_HELP = 0x2F
VK_Z = 0x5A
VK_LWIN = 0x5B
VK_RWIN = 0x5C
VK_APPS = 0x5D
Expand Down Expand Up @@ -2900,6 +2901,10 @@ const (
INPUT_HARDWARE = 2
)

const (
KEYEVENTF_KEYUP = 0x0002
)

const (
MOUSEEVENTF_ABSOLUTE = 0x8000
MOUSEEVENTF_HWHEEL = 0x01000
Expand Down Expand Up @@ -3555,6 +3560,7 @@ const (
VK_INSERT = 45
VK_DELETE = 46
VK_HELP = 47
VK_Z = 0x5A
VK_LWIN = 0x5B
VK_RWIN = 0x5C
VK_APPS = 0x5D
Expand Down
30 changes: 5 additions & 25 deletions v3/pkg/w32/user32.go
Original file line number Diff line number Diff line change
Expand Up @@ -1333,34 +1333,14 @@ func ChangeDisplaySettingsEx(szDeviceName *uint16, devMode *DEVMODE, hwnd HWND,
return int32(ret)
}

/*
func SendInput(inputs []INPUT) uint32 {
var validInputs []C.INPUT

for _, oneInput := range inputs {
input := C.INPUT{_type: C.DWORD(oneInput.Type)}

switch oneInput.Type {
case INPUT_MOUSE:
(*MouseInput)(unsafe.Pointer(&input)).mi = oneInput.Mi
case INPUT_KEYBOARD:
(*KbdInput)(unsafe.Pointer(&input)).ki = oneInput.Ki
case INPUT_HARDWARE:
(*HardwareInput)(unsafe.Pointer(&input)).hi = oneInput.Hi
default:
panic("unkown type")
}

validInputs = append(validInputs, input)
}

func SendInput(nInputs int, pInputs unsafe.Pointer, cbSize int) uint32 {
ret, _, _ := procSendInput.Call(
uintptr(len(validInputs)),
uintptr(unsafe.Pointer(&validInputs[0])),
uintptr(unsafe.Sizeof(C.INPUT{})),
uintptr(nInputs),
uintptr(pInputs),
uintptr(cbSize),
)
return uint32(ret)
}*/
}

func SetWindowsHookEx(idHook int, lpfn HOOKPROC, hMod HINSTANCE, dwThreadId DWORD) HHOOK {
ret, _, _ := procSetWindowsHookEx.Call(
Expand Down