Skip to content
Draft
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
79 changes: 79 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -142,3 +142,82 @@ For example: `bd create --help` shows `--parent`, `--deps`, `--assignee`, etc.
- Do NOT clutter repo root with planning documents

For more details, see README.md and QUICKSTART.md.

## Event System

### Adding New Events

**IMPORTANT**: Events are code-generated. Do NOT manually edit the generated event files.

#### Event Files (Generated - DO NOT EDIT)

These files are automatically generated from `v3/pkg/events/events.txt`:
- `v3/pkg/events/events.go` - Go event types and constants
- `v3/pkg/events/events_darwin.h` - macOS C header defines
- `v3/pkg/events/events_linux.h` - Linux C header defines
- `v3/pkg/events/events_ios.h` - iOS C header defines
- `v3/internal/runtime/desktop/@wailsio/runtime/src/event_types.ts` - TypeScript event types

#### How to Add New Events

1. **Edit the source file**: `v3/pkg/events/events.txt`
- Format: `platform:EventName`
- Platforms: `common`, `mac`, `windows`, `linux`, `ios`
- Add `!` suffix to prevent auto-generating delegate methods (e.g., `mac:WindowDidZoom!`)

2. **Run the generator**:
```bash
cd v3/tasks/events
go run generate.go
go fmt ../../pkg/events/events.go
```

Or use the task runner:
```bash
cd v3/internal/runtime
task generate:events
```

3. **Rebuild the JavaScript runtime** (if needed):
```bash
cd v3/internal/runtime
task build
```

#### Event Naming Conventions

- `common:` - Cross-platform events (emitted on all platforms)
- `mac:` - macOS-specific events
- `windows:` - Windows-specific events
- `linux:` - Linux-specific events
- `ios:` - iOS-specific events

#### Example: Adding a New Common Event

```
# In v3/pkg/events/events.txt, add:
common:WindowMouseEnter
common:WindowMouseLeave
```

Then run the generator to create:
- Go: `events.Common.WindowMouseEnter`, `events.Common.WindowMouseLeave`
- TypeScript: `"common:WindowMouseEnter"`, `"common:WindowMouseLeave"`

#### Generated File Headers

All generated files include the standard Wails header with both English and Welsh comments:
```
/*
_ __ _ __
| | / /___ _(_) /____
| | /| / / __ `/ / / ___/
| |/ |/ / /_/ / / (__ )
|__/|__/\__,_/_/_/____/
The electron alternative for Go
(c) Lea Anthony 2019-present
*/

// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
```
2 changes: 2 additions & 0 deletions v3/UNRELEASED_CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ After processing, the content will be moved to the main changelog and this file

## Added
<!-- New features, capabilities, or enhancements -->
- Add `WindowMouseEnter` and `WindowMouseLeave` events for detecting when the mouse enters or leaves a window (#4887)
- Add `FocusOnMouseEnter` window option to automatically focus windows on mouse hover (useful for tray popup windows)

## Changed
<!-- Changes in existing functionality -->
Expand Down
52 changes: 52 additions & 0 deletions v3/examples/mouse-hover/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# Mouse Hover Example

This example demonstrates the `WindowMouseEnter` and `WindowMouseLeave` events, which are fired when the mouse cursor enters or leaves a window.

## Features Demonstrated

1. **Mouse Enter/Leave Events**: The example shows how to listen for `events.Common.WindowMouseEnter` and `events.Common.WindowMouseLeave` events on windows.

2. **FocusOnMouseEnter Option**: The secondary window demonstrates the `FocusOnMouseEnter` option, which automatically focuses the window when the mouse enters it. This is particularly useful for tray popup windows where you want the user to be able to interact with the window immediately without an initial click to focus.

## Use Cases

- **Tray Icon Popup Windows**: When a user hovers over a tray icon and a popup window appears, enabling `FocusOnMouseEnter` allows immediate interaction with the window contents.
- **Tooltip-style Windows**: For custom tooltip or hover card implementations that need to receive keyboard input.
- **Dashboard Widgets**: For multi-window applications where you want hover-to-focus behavior.

## Running the Example

```bash
cd v3/examples/mouse-hover
go run .
```

## Code Highlights

### Listening for Mouse Events

```go
window.OnWindowEvent(events.Common.WindowMouseEnter, func(e *application.WindowEvent) {
app.Logger.Info("Mouse entered window!")
})

window.OnWindowEvent(events.Common.WindowMouseLeave, func(e *application.WindowEvent) {
app.Logger.Info("Mouse left window!")
})
```

### Auto-Focus on Mouse Enter

```go
window := app.Window.NewWithOptions(application.WebviewWindowOptions{
Title: "Auto-Focus Window",
FocusOnMouseEnter: true, // Window will focus when mouse enters
})
```

## Platform Support

Mouse enter/leave events work on all platforms:
- **Windows**: Uses `WM_MOUSEMOVE` with `TrackMouseEvent` and `WM_MOUSELEAVE`
- **macOS**: Uses `NSTrackingArea` with `NSTrackingActiveAlways` for tracking even when unfocused
- **Linux**: Uses GTK's `enter-notify-event` and `leave-notify-event` signals
113 changes: 113 additions & 0 deletions v3/examples/mouse-hover/assets/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Mouse Hover Demo</title>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
text-align: center;
color: white;
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
margin: 0;
padding: 20px;
min-height: calc(100vh - 40px);
user-select: none;
-webkit-user-select: none;
}
h1 {
color: #00d9ff;
margin-bottom: 10px;
}
.info {
background: rgba(255, 255, 255, 0.1);
border-radius: 10px;
padding: 15px;
margin: 15px auto;
max-width: 500px;
}
.status {
font-size: 1.2em;
padding: 10px;
border-radius: 5px;
margin: 10px 0;
transition: all 0.3s ease;
}
.status.inside {
background: rgba(0, 217, 255, 0.3);
border: 2px solid #00d9ff;
}
.status.outside {
background: rgba(255, 100, 100, 0.3);
border: 2px solid #ff6464;
}
#eventLog {
text-align: left;
background: rgba(0, 0, 0, 0.3);
border-radius: 5px;
padding: 10px;
margin-top: 15px;
max-height: 150px;
overflow-y: auto;
font-family: monospace;
font-size: 0.9em;
}
.log-entry {
padding: 2px 0;
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
}
.log-entry:last-child {
border-bottom: none;
}
.enter { color: #00ff88; }
.leave { color: #ff8800; }
</style>
</head>
<body>
<h1>Mouse Hover Events Demo</h1>

<div class="info">
<p>This demo shows the <code>WindowMouseEnter</code> and <code>WindowMouseLeave</code> events.</p>
<p>Move your mouse in and out of the windows to see the events fire.</p>
<p>Check the terminal for Go-side event logs.</p>
</div>

<div id="status" class="status outside">
Mouse is outside this window
</div>

<div id="eventLog">
<div class="log-entry">Event log:</div>
</div>

<script type="module">
import * as wails from "/wails/runtime.js";

const statusEl = document.getElementById('status');
const logEl = document.getElementById('eventLog');

function addLogEntry(message, className) {
const entry = document.createElement('div');
entry.className = 'log-entry ' + className;
entry.textContent = new Date().toLocaleTimeString() + ' - ' + message;
logEl.appendChild(entry);
logEl.scrollTop = logEl.scrollHeight;
}

// Listen for mouse enter event using the event string directly
wails.Events.On("common:WindowMouseEnter", function() {
statusEl.textContent = 'Mouse is INSIDE this window';
statusEl.className = 'status inside';
addLogEntry('WindowMouseEnter', 'enter');
});

// Listen for mouse leave event
wails.Events.On("common:WindowMouseLeave", function() {
statusEl.textContent = 'Mouse is outside this window';
statusEl.className = 'status outside';
addLogEntry('WindowMouseLeave', 'leave');
});
</script>

</body>
</html>
68 changes: 68 additions & 0 deletions v3/examples/mouse-hover/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package main

import (
"embed"
"log"

"github.com/wailsapp/wails/v3/pkg/application"
"github.com/wailsapp/wails/v3/pkg/events"
)

//go:embed assets
var assets embed.FS

func main() {

app := application.New(application.Options{
Name: "Mouse Hover Demo",
Description: "A demo of WindowMouseEnter and WindowMouseLeave events",
Assets: application.AssetOptions{
Handler: application.BundledAssetFileServer(assets),
},
Mac: application.MacOptions{
ApplicationShouldTerminateAfterLastWindowClosed: true,
},
})

// Create a main window that demonstrates mouse enter/leave events
mainWindow := app.Window.NewWithOptions(application.WebviewWindowOptions{
Title: "Mouse Hover Demo - Main Window",
Width: 600,
Height: 400,
})

// Log mouse enter/leave events for the main window
mainWindow.OnWindowEvent(events.Common.WindowMouseEnter, func(e *application.WindowEvent) {
app.Logger.Info("Main Window: Mouse entered!")
})

mainWindow.OnWindowEvent(events.Common.WindowMouseLeave, func(e *application.WindowEvent) {
app.Logger.Info("Main Window: Mouse left!")
})

// Create a secondary "popup-like" window that auto-focuses on mouse enter
// This is useful for tray popup windows where you want immediate interaction
popupWindow := app.Window.NewWithOptions(application.WebviewWindowOptions{
Title: "Auto-Focus Window (hover to focus)",
Width: 300,
Height: 200,
X: 650,
Y: 100,
FocusOnMouseEnter: true, // Automatically focus when mouse enters
})

// Log mouse enter/leave events for the popup window
popupWindow.OnWindowEvent(events.Common.WindowMouseEnter, func(e *application.WindowEvent) {
app.Logger.Info("Popup Window: Mouse entered! (window auto-focused)")
})

popupWindow.OnWindowEvent(events.Common.WindowMouseLeave, func(e *application.WindowEvent) {
app.Logger.Info("Popup Window: Mouse left!")
})

err := app.Run()

if err != nil {
log.Fatal(err.Error())
}
}
6 changes: 4 additions & 2 deletions v3/internal/assetserver/bundledassets/runtime.debug.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion v3/internal/assetserver/bundledassets/runtime.js

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -256,5 +256,7 @@ export const Types = Object.freeze({
WindowZoomIn: "common:WindowZoomIn",
WindowZoomOut: "common:WindowZoomOut",
WindowZoomReset: "common:WindowZoomReset",
WindowMouseEnter: "common:WindowMouseEnter",
WindowMouseLeave: "common:WindowMouseLeave",
}),
});
22 changes: 22 additions & 0 deletions v3/pkg/application/linux_cgo.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ extern void emit(WindowEvent* data);
extern gboolean handleConfigureEvent(GtkWidget*, GdkEventConfigure*, uintptr_t);
extern gboolean handleDeleteEvent(GtkWidget*, GdkEvent*, uintptr_t);
extern gboolean handleFocusEvent(GtkWidget*, GdkEvent*, uintptr_t);
extern gboolean handleEnterLeaveEvent(GtkWidget*, GdkEventCrossing*, uintptr_t);
extern void handleLoadChanged(WebKitWebView*, WebKitLoadEvent, uintptr_t);
void handleClick(void*);
extern gboolean onButtonEvent(GtkWidget *widget, GdkEventButton *event, uintptr_t user_data);
Expand Down Expand Up @@ -1633,6 +1634,21 @@ func handleFocusEvent(widget *C.GtkWidget, event *C.GdkEvent, data C.uintptr_t)
return C.gboolean(0)
}

//export handleEnterLeaveEvent
func handleEnterLeaveEvent(widget *C.GtkWidget, event *C.GdkEventCrossing, data C.uintptr_t) C.gboolean {
// Filter out enter/leave events caused by grabs or inferior windows
// Only emit events for actual mouse enter/leave of the window
if event.detail == C.GDK_NOTIFY_INFERIOR {
return C.gboolean(0)
}
if event._type == C.GDK_ENTER_NOTIFY {
processWindowEvent(C.uint(data), C.uint(events.Common.WindowMouseEnter))
} else if event._type == C.GDK_LEAVE_NOTIFY {
processWindowEvent(C.uint(data), C.uint(events.Common.WindowMouseLeave))
}
return C.gboolean(0)
}

//export handleLoadChanged
func handleLoadChanged(webview *C.WebKitWebView, event C.WebKitLoadEvent, data C.uintptr_t) {
switch event {
Expand Down Expand Up @@ -1666,6 +1682,12 @@ func (w *linuxWebviewWindow) setupSignalHandlers(emit func(e events.WindowEventT
C.signal_connect(wv, c.String("button-press-event"), C.onButtonEvent, winID)
C.signal_connect(wv, c.String("button-release-event"), C.onButtonEvent, winID)
C.signal_connect(wv, c.String("key-press-event"), C.onKeyPressEvent, winID)

// Mouse enter/leave events for the window
// Add event masks to receive enter/leave notifications even when unfocused
C.gtk_widget_add_events((*C.GtkWidget)(unsafe.Pointer(w.window)), C.GDK_ENTER_NOTIFY_MASK|C.GDK_LEAVE_NOTIFY_MASK)
C.signal_connect(unsafe.Pointer(w.window), c.String("enter-notify-event"), C.handleEnterLeaveEvent, winID)
C.signal_connect(unsafe.Pointer(w.window), c.String("leave-notify-event"), C.handleEnterLeaveEvent, winID)
}

func getMouseButtons() (bool, bool, bool) {
Expand Down
7 changes: 7 additions & 0 deletions v3/pkg/application/webview_window.go
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,13 @@ func NewWindow(options WebviewWindowOptions) *WebviewWindow {
result.keyBindings = processKeyBindingOptions(result.options.KeyBindings)
}

// Handle FocusOnMouseEnter option
if result.options.FocusOnMouseEnter {
result.OnWindowEvent(events.Common.WindowMouseEnter, func(event *WindowEvent) {
result.Focus()
})
}

return result
}

Expand Down
Loading
Loading