Skip to content

Commit d5a4697

Browse files
committed
[*] refactor: make portal cursor tracker as a new program
1 parent c82e017 commit d5a4697

File tree

22 files changed

+260
-74
lines changed

22 files changed

+260
-74
lines changed

.github/workflows/linux-portal.yml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ jobs:
2727

2828
- run: echo "start build..."
2929
- run: make desktop-build-release desktop-features=desktop-wayland-portal
30+
- run: make cursor-release
3031
- run: echo "end build..."
3132

3233
- name: Get the release version from the tag
@@ -49,6 +50,12 @@ jobs:
4950
cp $output ..
5051
echo "ASSET=$output" >> $GITHUB_ENV
5152
53+
output="$binary_name-cursor-${{ env.VERSION }}-x86_64-linux.tar.gz"
54+
cp -rf release/$binary_name-cursor $binary_name-cursor
55+
tar -zcf $output $binary_name-cursor
56+
cp $output ..
57+
echo "ASSET=$output" >> $GITHUB_ENV
58+
5259
output="$binary_name-portal-${{ env.VERSION }}-x86_64-linux.deb"
5360
cp -rf $binary_name.deb $output
5461
cp $output ..

CLAUDE.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,4 +102,5 @@ The recorder library provides the core screen recording functionality:
102102
1. For recorder development: `cargo test -p recorder -- --nocapture`
103103
2. For GUI changes: `make slint-viewer-desktop` for live preview
104104
3. For integration testing: `make test` to run all workspace tests
105-
4. Examples are in `lib/recorder/examples/` for testing specific functionality
105+
4. Examples are in `lib/recorder/examples/` for testing specific functionality
106+
- 不要使用模拟数据

Cargo.lock

Lines changed: 11 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
[workspace]
22
exclude = []
33
resolver = "3"
4-
members = ["wayshot", "tr-helper", "icon-helper", "lib/*"]
4+
members = ["wayshot", "wayshot-cursor", "tr-helper", "icon-helper", "lib/*"]
55

66
[workspace.package]
77
license = "MIT"

Makefile

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,12 @@ web-build-release:
5353
web-debug: web-build
5454
cd $(app-name) && python3 -m http.server -d web 8000
5555

56+
cursor-debug:
57+
$(run-env) cargo run --bin wayshot-cursor
58+
59+
cursor-release:
60+
cargo build --release --bin wayshot-cursor
61+
5662
tr:
5763
cargo run --bin tr-helper
5864

README.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,7 @@ This is a screen recording tool for `Linux` `Wayland`, which uses the `wlroots`
2828

2929
- Check program output log information: `RUST_LOG=debug wayshot`。Available log level:`debug`, `info`, `warn`, `error`
3030

31-
- When using the cursor tracking feature with the `Wayland xdg portal` version, you need to either add the current user to the `input` and `plugdev` groups.
32-
- `sudo usermod -aG input $USER` or `sudo usermod -aG plugdev $USER`
31+
- To use the cursor tracking feature with the `Wayland xdg portal` version, it needs to be used together with the `wayshot-cursor` program. The program can be downloaded from the Github page. The program must be run with administrator privileges: `sudo -E wayshot-cursor`. If you need to view logs, you can use: `RUST_LOG=debug sudo -E wayshot-cursor`. Available log levels: `debug`, `info`, `warn`, `error`
3332

3433
- Program version selection:
3534
- `portal` version: `Ubuntu` and `KDE`, etc.

README.zh-CN.md

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,7 @@
2828

2929
- 查看程序输出日志信息:`RUST_LOG=debug wayshot`。可选日志级别:`debug`, `info`, `warn`, `error`
3030

31-
- `Wayland xdg portal`版本使用光标追踪功能,需要将当前用户添加到`input``plugdev`组。
32-
- `sudo usermod -aG input $USER` or `sudo usermod -aG plugdev $USER`
31+
- `Wayland xdg portal`版本使用光标追踪功能,需要配合 `wayshot-curosr` 程序一起使用。程序可以到Github页面去下载。运行程序需要使用管理员权限:`sudo -E wayshot-cursor`。 如果需要查看日志可以使用:`RUST_LOG=debug sudo -E wayshot-cursor`。可选日志级别:`debug`, `info`, `warn`, `error`
3332

3433
- 程序版本选择版本:
3534
- `portal` 版本:`Ubuntu``KDE`
@@ -40,7 +39,6 @@
4039
sudo apt install libxcb-composite0-dev libasound2-dev libpipewire-0.3-dev \
4140
libx264-dev libx11-dev libxi-dev libxtst-dev libevdev-dev \
4241
qt6-base-dev qt6-tools-dev qt6-tools-dev-tools
43-
```
4442

4543
### 参考
4644
- [Slint Language Documentation](https://slint-ui.com/releases/1.0.0/docs/slint/)

lib/screen-capture-wayland-portal/Cargo.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ screen-capture.workspace = true
2626
derive_setters.workspace = true
2727
tokio = { workspace = true, features = ["full"] }
2828
serde = { workspace = true, features = ["derive"] }
29-
rdev = { workspace = true, features = ["unstable_grab"] }
3029

3130
[dev-dependencies]
3231
ctrlc.workspace = true

lib/screen-capture-wayland-portal/src/backend.rs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ impl PortalCapturer {
4949
}
5050
}
5151

52-
pub async fn open_portal<'a>(&self) -> Result<(ScreencastStream, OwnedFd)> {
52+
pub async fn open_portal(&self) -> Result<(ScreencastStream, OwnedFd)> {
5353
let proxy = Screencast::new().await?;
5454
let session = proxy.create_session().await?;
5555
proxy
@@ -257,7 +257,8 @@ impl PortalCapturer {
257257
spa::utils::Direction::Input,
258258
Some(node_id),
259259
pw::stream::StreamFlags::AUTOCONNECT | pw::stream::StreamFlags::MAP_BUFFERS,
260-
&mut [spa::pod::Pod::from_bytes(&self.init_pipewire_pod()).unwrap()],
260+
&mut [spa::pod::Pod::from_bytes(&self.init_pipewire_pod()?)
261+
.ok_or(Error::Other(format!("pod from_bytes failed")))?],
261262
)?;
262263

263264
log::info!("Portal connected stream sucessfully");
@@ -273,7 +274,7 @@ impl PortalCapturer {
273274
Ok(())
274275
}
275276

276-
fn init_pipewire_pod(&self) -> Vec<u8> {
277+
fn init_pipewire_pod(&self) -> Result<Vec<u8>> {
277278
let obj = pw::spa::pod::object!(
278279
pw::spa::utils::SpaTypes::ObjectParamFormat,
279280
pw::spa::param::ParamType::EnumFormat,
@@ -342,10 +343,10 @@ impl PortalCapturer {
342343
std::io::Cursor::new(Vec::new()),
343344
&pw::spa::pod::Value::Object(obj),
344345
)
345-
.unwrap()
346+
.map_err(|e| Error::Other(format!("PodSerializer failed: {e}")))?
346347
.0
347348
.into_inner();
348349

349-
values
350+
Ok(values)
350351
}
351352
}
Lines changed: 41 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,76 +1,69 @@
1-
use crate::Error;
2-
use rdev::{Event, EventType, grab};
1+
use crate::Result;
32
use screen_capture::{CursorPosition, MonitorCursorPositionConfig};
43
use std::{
5-
sync::atomic::{AtomicBool, AtomicU64, Ordering},
6-
time::Duration,
4+
sync::atomic::Ordering,
5+
{io::Read, os::unix::net::UnixStream, time::Duration},
76
};
87

9-
static CURSOR_GRAP_THREAD_RUNNING: AtomicBool = AtomicBool::new(false);
10-
static CURSOR_POSITION: AtomicU64 = AtomicU64::new(u64::MAX);
11-
128
pub fn monitor_cursor_position(
139
config: MonitorCursorPositionConfig,
1410
mut callback: impl FnMut(CursorPosition) + Send + 'static,
15-
) -> Result<(), Error> {
16-
if !CURSOR_GRAP_THREAD_RUNNING.load(Ordering::Relaxed) {
17-
std::thread::spawn(move || {
18-
CURSOR_GRAP_THREAD_RUNNING.store(true, Ordering::Relaxed);
19-
log::info!("start long run cursor grap thread...");
11+
) -> Result<()> {
12+
let socket_path = "/tmp/wayshot-cursor.sock";
2013

21-
let callback = move |event: Event| -> Option<Event> {
22-
if let EventType::MouseMove { x, y } = event.event_type {
23-
// log::debug!("cursor position: (x, y) = ({x}, {y})");
24-
let cur_pos = (((x as u64) << 32) & 0xffff_ffff_0000_0000) | (y as u64);
25-
CURSOR_POSITION.store(cur_pos, Ordering::Relaxed);
26-
}
14+
loop {
15+
if config.stop_sig.load(Ordering::Relaxed) {
16+
log::info!("exit monitor cursor thread...");
17+
break;
18+
}
2719

28-
Some(event)
29-
};
20+
match UnixStream::connect(socket_path) {
21+
Ok(mut stream) => {
22+
log::info!("Connected to server process");
3023

31-
// The grab function use the evdev library to intercept events,
32-
// so they will work with both X11 and Wayland In order for this to work,
33-
// the process running the listen or grab loop needs to either run as root (not recommended),
34-
// or run as a user who's a member of the input group (recommended) Note: on some distros,
35-
// the group name for evdev access is called plugdev, and on some systems, both groups can exist.
36-
// When in doubt, add your user to both groups if they exist.
37-
// commands: `sudo usermod -aG input $USER` or `sudo usermod -aG plugdev $USER`
38-
if let Err(e) = grab(callback) {
39-
log::warn!("cursor monitor failed: {e:?}");
24+
if let Err(e) = process_mouse_positions(&mut stream, &config, &mut callback) {
25+
log::warn!("process mouse positions failed: {e}");
26+
}
4027
}
28+
Err(e) => log::warn!("UnixStream connect `{socket_path}` failed: {e}"),
29+
}
4130

42-
CURSOR_GRAP_THREAD_RUNNING.store(false, Ordering::Relaxed);
43-
log::info!("exit long run cursor grap thread...");
44-
});
31+
std::thread::sleep(Duration::from_secs(3));
4532
}
4633

47-
let mut last_pos = CURSOR_POSITION.load(Ordering::Relaxed);
48-
loop {
49-
if config.stop_sig.load(Ordering::Relaxed) {
50-
log::info!("exit monitor cursor thread...");
51-
break;
52-
}
34+
Ok(())
35+
}
5336

54-
let cur_pos = CURSOR_POSITION.load(Ordering::Relaxed);
55-
if last_pos == cur_pos || cur_pos == u64::MAX {
56-
std::thread::sleep(Duration::from_millis(5));
57-
continue;
58-
};
37+
fn process_mouse_positions(
38+
stream: &mut UnixStream,
39+
config: &MonitorCursorPositionConfig,
40+
callback: &mut (impl FnMut(CursorPosition) + Send + 'static),
41+
) -> Result<()> {
42+
loop {
43+
let pos = receive_position(stream)?;
44+
let x = ((pos >> 32) & 0x0000_0000_ffff_ffff) as i32;
45+
let y = (pos & 0x0000_0000_ffff_ffff) as i32;
5946

60-
last_pos = cur_pos;
47+
log::debug!("Received mouse position: ({}, {})", x, y);
6148

6249
let position = CursorPosition {
63-
x: ((cur_pos >> 32) & 0x0000_0000_ffff_ffff) as i32,
64-
y: (cur_pos & 0x0000_0000_ffff_ffff) as i32,
50+
x,
51+
y,
6552
output_x: config.screen_info.position.x,
6653
output_y: config.screen_info.position.y,
6754
output_width: config.screen_info.logical_size.width,
6855
output_height: config.screen_info.logical_size.height,
6956
};
7057

7158
callback(position);
72-
}
7359

74-
Ok(())
60+
std::thread::sleep(Duration::from_millis(5));
61+
}
7562
}
7663

64+
fn receive_position(stream: &mut UnixStream) -> Result<u64> {
65+
let mut buffer = [0u8; 8];
66+
stream.read_exact(&mut buffer)?;
67+
let value = u64::from_ne_bytes(buffer);
68+
Ok(value)
69+
}

0 commit comments

Comments
 (0)