wallopino 0.1.1

A Rust library for attaching windows behind the Windows desktop icons.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
# Wallopino


**Wallopino** is a Windows-focused Rust library for turning an ordinary application window into an interactive desktop wallpaper.

Instead of forcing your application to implement a traditional wallpaper renderer, Wallopino works with an existing `HWND` and integrates it into the Windows desktop window hierarchy. This makes it possible to build **interactive live wallpapers, desktop visualizations, WebView-based backgrounds, animated scenes, and other desktop experiences** using the UI technology or rendering engine you already like.

> **Status:** `0.1.0` β€” early-stage / experimental API.

[![Rust](https://img.shields.io/badge/language-Rust-orange?logo=rust)](https://www.rust-lang.org/)
[![Windows](https://img.shields.io/badge/platform-Windows-0078D6?logo=windows)](https://www.microsoft.com/windows)
[![License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)

---

## Showcase


See Wallopino in action. Each example below is a normal application window transformed into an animated desktop wallpaper.

### πŸ¦€ Ferris


**Native Macroquad rendering with desktop attachment and interactive mouse input.**

![Ferris Demo](https://github.com/amir-frjn/wallopino/blob/main/assets/Ferris.gif)

---

### ⌨️ Keyboard


**Interactive WebView content built with Wry/Tao, attached to the desktop with forwarded mouse input.**

![Keyboard Demo](https://github.com/amir-frjn/wallopino/blob/main/assets/Keyboard.gif)

---

### πŸŒ† Neon Village


**A large animated WebView/Three.js-style scene running directly as an interactive desktop background.**

![Neon Village Demo](https://github.com/amir-frjn/wallopino/blob/main/assets/Neonvillage.gif)

---

### ✨ Strings


**An animated WebView experience attached to the Windows desktop.**

![Strings Demo](https://github.com/amir-frjn/wallopino/blob/main/assets/Strings.gif)

---

### πŸ•³οΈ Wormhole


**A WebView-based animated black-hole scene running behind the desktop icons.**

![Wormhole Demo](https://github.com/amir-frjn/wallopino/blob/main/assets/Wormhole.gif)


---

## Why Wallopino?


Windows desktop wallpaper is more than a single background image. The Shell maintains a hierarchy of windows for the desktop, icons, wallpaper surfaces, and other system UI.

Wallopino provides the low-level plumbing needed to work with that hierarchy:

* Attach an existing window behind the desktop icons.
* Work across multiple monitors.
* Normalize monitor coordinates for a virtual desktop.
* Watch the desktop hierarchy and repair wallpaper z-order changes.
* Capture global mouse and keyboard input.
* Forward captured input as normal Windows messages to the target window.
* Redirect forwarded events to a descendant window when frameworks such as WebView2 need it.
* Track mouse button state for frame-based applications.
* Clean up the wallpaper integration when the application shuts down.

The core idea is simple:

```text
Your application
      β”‚
      β”‚ creates a normal HWND
      β–Ό
   Wallopino
      β”‚
      β”œβ”€β”€ Attach HWND to Windows desktop layer
      β”‚
      β”œβ”€β”€ Keep desktop z-order stable
      β”‚
      └── Optionally forward mouse/keyboard input
      β–Ό
Interactive desktop wallpaper
```

---

## Features


### πŸ–₯️ Window-to-desktop attachment


Attach an existing window to the Windows desktop layer with:

```rust
let mut wallpaper = wallopino::AttachWindow::auto_attach(hwnd, true)?;
```

Wallopino discovers the relevant desktop windows and configures the target `HWND` accordingly.

### 🧱 Desktop hierarchy / z-order watching


Desktop window topology can change while Windows is running. Wallopino can start a background watcher to detect relevant changes and restore the wallpaper placement:

```rust
wallopino.start_watcher(std::time::Duration::from_millis(100))?;
```

You can stop the watcher explicitly with `stop_watcher()`.

### πŸ–±οΈ Global mouse input forwarding


`EventForwarder` can capture low-level mouse input and forward it to your wallpaper window:

```rust
let forwarder =
    wallopino::EventForwarder::new(hwnd, None, true, false)?;

let controller = forwarder.forward_events()?;
```

Supported mouse events include movement, left/right/middle button press/release, and wheel scrolling.

### ⌨️ Keyboard input forwarding


Keyboard forwarding can be enabled independently:

```rust
let forwarder =
    wallopino::EventForwarder::new(hwnd, None, false, true)?;

let controller = forwarder.forward_events()?;
```

Mouse and keyboard forwarding can also be enabled together.

### 🎯 Descendant-window targeting


Some UI frameworks do not process input on the top-level `HWND`. Wallopino can search the target's descendants for a specific window class and use that child as the forwarding destination:

```rust
let forwarder = wallopino::EventForwarder::new(
    hwnd,
    Some("Chrome_WidgetWin_1"),
    true,
    false,
)?;
```

This is particularly useful for WebView2/Wry-style applications.

### πŸ–₯️ Multi-monitor support


Wallopino can enumerate connected monitors and normalize their coordinates into a virtual-desktop coordinate system:

```rust
let monitors = wallpaper.enumerate_monitors()?;

for (index, monitor) in monitors.iter().enumerate() {
    println!(
        "Monitor {index}: ({}, {}) {}x{}",
        monitor.x,
        monitor.y,
        monitor.width,
        monitor.height
    );
}
```

It also exposes monitor work-area information through `MonitorInfo`.

### πŸ–±οΈ Frame-based mouse state


For render loops and game-style applications, `AttachWindow` can track the current and previous state of five mouse buttons:

```rust
wallpaper.update_mouse_state();

if wallpaper.is_mouse_button_pressed(0) {
    println!("Left click!");
}

if wallpaper.is_mouse_button_down(0) {
    println!("Left button is held.");
}

if wallpaper.is_mouse_button_released(0) {
    println!("Left button released.");
}
```

Button indices are:

| Index | Button |
| ----: | ------ |
|   `0` | Left   |
|   `1` | Right  |
|   `2` | Middle |
|   `3` | X1     |
|   `4` | X2     |

---

## Installation


Wallopino is currently developed as a Windows-specific crate.

### From GitHub


```toml
[dependencies]
wallopino = { git = "https://github.com/amir-frjn/wallopino" }
```

If your application directly uses Windows types such as `HWND`, add the `windows` crate as a direct dependency too:

```toml
[target.'cfg(windows)'.dependencies]
wallopino = { git = "https://github.com/amir-frjn/wallopino" }
windows = { version = "0.61", features = [
    "Win32_Foundation",
    "Win32_UI_WindowsAndMessaging",
] }
```

Wallopino currently uses the `windows` crate version `0.61` internally.

---

## Quick Start


The following example shows the basic Wallopino workflow.

```rust
use std::time::Duration;

use wallopino::AttachWindow;

fn attach_wallpaper(hwnd: isize) -> Result<(), Box<dyn std::error::Error>> {
    // Attach the existing window to the desktop.
    let mut wallpaper = AttachWindow::auto_attach(hwnd, true)?;

    // Keep the attachment alive when Windows changes desktop window topology.
    wallpaper.start_watcher(Duration::from_millis(100))?;

    Ok(())
}
```

The `hwnd` is the normal Windows handle of the window you want to use as the wallpaper.

For example, frameworks that expose a native Windows handle, such as `tao`, can provide the `HWND` for you.

---

## Interactive Wallpaper


Wallopino becomes especially interesting when the wallpaper is interactive.

A typical application has two independent pieces:

1. **Attach the window to the desktop**
2. **Forward input to that window when required**

```rust
use std::time::Duration;

let event_forwarder =
    wallopino::EventForwarder::new(hwnd, None, true, false)?;

let controller = event_forwarder.forward_events()?;

let mut wallpaper =
    wallopino::AttachWindow::auto_attach(hwnd, true)?;

wallpaper.start_watcher(Duration::from_millis(100))?;

// ... run your application ...

controller.pause();
controller.resume();

// Shut down the forwarding session when finished.
controller.exit()?;
```

The forwarding controller is thread-safe and can pause, resume, query, or terminate an active forwarding session.

---

## WebView / Wry Example


One of Wallopino's useful applications is combining it with a WebView.

The repository contains examples built with [`wry`](https://github.com/tauri-apps/wry) and [`tao`](https://github.com/tauri-apps/tao). The important part is still very small:

```rust
use std::time::Duration;
use tao::platform::windows::WindowExtWindows;

let hwnd = window.hwnd();

let event_forwarder =
    wallopino::EventForwarder::new(
        hwnd,
        Some("Chrome_WidgetWin_1"),
        true,
        false,
    )?;

event_forwarder.forward_events()?;

let mut wallpaper =
    wallopino::AttachWindow::auto_attach(hwnd, true)?;

wallpaper.start_watcher(Duration::from_millis(100))?;
```

For WebView-based windows, the top-level window may not be the element that actually processes mouse messages. The optional descendant class-name parameter lets Wallopino target an appropriate child window instead.

---

## Examples


The [`examples/`](examples) directory contains several complete demos.

| Example                                     | What it demonstrates                                                                       | Video                             |
| ------------------------------------------- | ------------------------------------------------------------------------------------------ | --------------------------------- |
| [`ferris.rs`]examples/ferris.rs           | Native rendering with Macroquad, desktop attachment, and interactive mouse input           | [▢️ Demo]assets/Ferris.mp4      |
| [`keyboard.rs`]examples/keyboard.rs       | Interactive WebView content with Wry/Tao, desktop attachment, and forwarded mouse input    | [▢️ Demo]assets/Keyboard.mp4    |
| [`neonvillage.rs`]examples/neonvillage.rs | A large WebView/Three.js-style animated scene running as an interactive desktop background | [▢️ Demo]assets/Neonvillage.mp4 |
| [`strings.rs`]examples/strings.rs         | A WebView animation attached to the desktop                                                | [▢️ Demo]assets/Strings.mp4     |
| [`wormhole.rs`]examples/wormhole.rs       | A WebView-based animated black-hole scene attached to the desktop                          | [▢️ Demo]assets/Wormhole.mp4    |

Run an example with:

```bash
cargo run --example ferris
```

or:

```bash
cargo run --example keyboard
```

The examples are intentionally useful as reference implementations: each one shows how a normal application window can be converted into a desktop background rather than building a dedicated wallpaper renderer from scratch.

---

## Architecture


At a high level, Wallopino is split into two responsibilities.

### `AttachWindow`


`AttachWindow` is responsible for the desktop side of the problem.

It can:

* discover the relevant Windows desktop windows,
* select a monitor or the virtual desktop,
* configure the target `HWND`,
* enumerate monitors,
* calculate desktop-relative mouse coordinates,
* maintain mouse button state,
* watch the wallpaper's z-order,
* stop the watcher,
* restore desktop state during cleanup.

The most convenient entry point is:

```rust
AttachWindow::auto_attach(hwnd, static_edge_mode)
```

For more control, the lower-level flow is:

```text
AttachWindow::initialize()
        β”‚
        β–Ό
enumerate_monitors()
        β”‚
        β–Ό
get_wallpaper_target(...)
        β”‚
        β–Ό
configure_wallpaper_window(...)
        β”‚
        β–Ό
start_watcher(...)
```

### `EventForwarder`


`EventForwarder` handles input.

Internally it uses low-level Windows hooks to capture selected input events, then forwards those events as Windows messages to the selected target window.

Its lifecycle looks like:

```text
EventForwarder::new(...)
        β”‚
        β–Ό
forward_events()
        β”‚
        β–Ό
ForwardingController
      /   |    \
     /    |     \
 pause  resume  exit
```

This separation is useful because not every wallpaper needs input forwarding. Static visualizations can use only `AttachWindow`, while interactive wallpapers can opt into `EventForwarder`.

---

## Input Events


Wallopino exposes an `Events` enum representing captured input:

```rust
use wallopino::Events;

let event = Events::LeftDown { x: 100, y: 200 };

println!("{event:?}");
```

Current event categories include:

### Mouse


* `Move`
* `LeftDown`
* `LeftUp`
* `RightDown`
* `RightUp`
* `MiddleDown`
* `MiddleUp`
* `Scroll`

### Keyboard


* `KeyDown`
* `KeyUp`

Mouse coordinates are converted from screen coordinates toward the target window's client coordinate system before the corresponding Windows message is posted.

---

## Controlling Input Forwarding


`ForwardingController` lets you manage a running forwarding session:

```rust
let controller = forwarder.forward_events()?;

// Temporarily stop forwarding.
controller.pause();

// Continue forwarding.
controller.resume();

// Check the current state.
if controller.is_forwarding() {
    println!("Forwarding is active.");
}

// Permanently terminate the forwarding session.
controller.exit()?;
```

Dropping the controller also signals the forwarding thread to terminate.

---

## Cleanup


When the wallpaper is no longer needed, you can explicitly clean up the attachment:

```rust
wallpaper.cleanup()?;
```

The watcher can also be stopped directly:

```rust
wallpaper.stop_watcher()?;
```

`AttachWindow` also cleans up its watcher when it is dropped.

---

## `static_edge_mode`


`auto_attach` accepts a second boolean parameter:

```rust
AttachWindow::auto_attach(hwnd, true)
```

This controls the library's static-edge attachment mode.

The examples use `true`, particularly for applications where keeping the wallpaper anchored to the desktop hierarchy is important.

---

## Platform Support


Wallopino is currently **Windows-only**.

The crate is designed around the Win32 desktop window hierarchy and uses the Windows API for:

* window discovery,
* desktop window management,
* monitor enumeration,
* input hooks,
* cursor state,
* z-order maintenance,
* desktop wallpaper restoration.

The project currently targets modern Windows desktop environments and is primarily developed/tested around Windows 10 and Windows 11.

---

## Requirements


* Windows
* Rust (edition 2024)
* A Windows desktop application that exposes a native `HWND`

Optional libraries such as `wry`, `tao`, or `macroquad` can be used alongside Wallopino depending on how you render your wallpaper.

---

## Use Cases


Wallopino can be used as a foundation for:

* Interactive live wallpapers
* WebView-based desktop backgrounds
* Three.js / Canvas visualizations
* Macroquad or other native rendering engines
* Animated desktop widgets
* Audio-reactive desktop scenes
* Experimental desktop environments
* Applications that need a normal window to live behind desktop icons

The important distinction is that **Wallopino does not dictate how your wallpaper is rendered**.

You can render it using native graphics, a game engine, WebView, HTML/CSS/JavaScript, or another window-based technologyβ€”as long as you have a window handle to attach.

---

## Project Layout


```text
wallopino/
β”œβ”€β”€ assets/
β”‚   β”œβ”€β”€ Ferris.mp4
β”‚   β”œβ”€β”€ Keyboard.mp4
β”‚   β”œβ”€β”€ Neonvillage.mp4
β”‚   β”œβ”€β”€ Strings.mp4
β”‚   └── Wormhole.mp4
β”œβ”€β”€ examples/
β”‚   β”œβ”€β”€ ferris.rs
β”‚   β”œβ”€β”€ keyboard.rs
β”‚   β”œβ”€β”€ neonvillage.rs
β”‚   β”œβ”€β”€ strings.rs
β”‚   └── wormhole.rs
β”œβ”€β”€ src/
β”‚   β”œβ”€β”€ platform/
β”‚   β”‚   └── windows/
β”‚   └── lib.rs
β”œβ”€β”€ Cargo.toml
β”œβ”€β”€ LICENSE
└── README.md
```

---

## Public API


The main items re-exported by the crate are:

```rust
wallopino::AttachWindow
wallopino::EventForwarder
wallopino::ForwardingController
wallopino::Events
wallopino::functions
```

For detailed API documentation, see the Rust documentation generated from the crate source.

---

## Safety


Wallopino interacts directly with Win32 APIs and therefore contains `unsafe` code internally.

Because the library operates on real Windows handles and global input hooks:

* pass valid window handles,
* ensure your target window remains alive while it is being used,
* stop forwarding sessions when they are no longer needed,
* be mindful that global input hooks affect the entire desktop session.

Wallopino does not try to hide the low-level nature of this functionality; it wraps the Windows-specific plumbing so the application can focus on the wallpaper itself.

---

## Contributing


Issues, experiments, improvements, and Windows-specific findings are welcome.

Because the project is still young, API design may evolve between releases.

For development:

```bash
git clone https://github.com/amir-frjn/wallopino.git
cd wallopino

cargo check
cargo test
cargo run --example ferris
```

---

## License


Wallopino is licensed under the [MIT License](LICENSE).

---

## Credits & Inspiration


Wallopino was created as a Rust-oriented exploration of the Windows desktop window hierarchy and the techniques required to build interactive desktop backgrounds.

Some example visuals are inspired by public CodePen projects; the example source files contain the relevant attribution and inspiration notes.

---

## Roadmap


Some directions that fit naturally with the project include:

* Better multi-monitor wallpaper management
* More robust desktop topology recovery
* Cleaner high-level APIs for wallpaper engines
* Improved event-target discovery for WebView frameworks
* More rendering-framework examples
* Better automated testing around Windows desktop state changes
* Expanded platform abstraction for future non-Windows implementations

---

## The idea in one line


> **Build your desktop experience as a normal window. Let Wallopino put it where the wallpaper belongs.**