Skip to main content

computer_use/
input_session.rs

1//! Keeps mouse buttons and held keys down across calls in one process.
2//!
3//! `serve` holds one [`InputSession`] for the whole stdin loop, so a
4//! `mouse_down` in one request stays down until a later `mouse_up` or until
5//! serve exits. A one-shot command does not open a session: `dispatch`
6//! releases anything still held when it returns. A batch opens a nested
7//! session so its steps share the hold. The release runs when the last
8//! session on this thread drops.
9
10use std::cell::Cell;
11
12use crate::backend::DesktopBackend;
13
14std::thread_local! {
15    static DEPTH: Cell<usize> = const { Cell::new(0) };
16}
17
18/// True while at least one [`InputSession`] is alive on this thread.
19pub fn session_open() -> bool {
20    DEPTH.with(|depth| depth.get() > 0)
21}
22
23/// Guard that releases held buttons and keys when the last one drops.
24pub struct InputSession<'a> {
25    backend: &'a dyn DesktopBackend,
26}
27
28impl<'a> InputSession<'a> {
29    /// Increment the session depth.
30    ///
31    /// The last drop calls [`DesktopBackend::release_held`].
32    pub fn open(backend: &'a dyn DesktopBackend) -> Self {
33        DEPTH.with(|depth| depth.set(depth.get().saturating_add(1)));
34        Self { backend }
35    }
36}
37
38impl Drop for InputSession<'_> {
39    fn drop(&mut self) {
40        let release = DEPTH.with(|depth| {
41            let next = depth.get().saturating_sub(1);
42            depth.set(next);
43            next == 0
44        });
45        if release {
46            let _ = self.backend.release_held();
47        }
48    }
49}