fresh/services/gpm/
client.rs1use super::ffi::{self, GpmConnect, GpmEventRaw, GpmLib};
4use super::types::{GpmButtons, GpmEvent, GpmModifiers};
5use std::io;
6use std::os::unix::io::RawFd;
7
8pub struct GpmClient {
10 fd: RawFd,
11 lib: &'static GpmLib,
12}
13
14impl GpmClient {
15 pub fn connect() -> io::Result<Option<Self>> {
21 tracing::debug!("GPM: Attempting to connect...");
22
23 let Some(lib) = ffi::get_gpm_lib() else {
25 tracing::debug!("GPM: libgpm not available on this system");
26 return Ok(None);
27 };
28 tracing::debug!("GPM: libgpm loaded successfully");
29
30 let is_console = Self::is_linux_console();
32 tracing::debug!("GPM: is_linux_console() = {}", is_console);
33 if !is_console {
34 tracing::debug!("GPM: Not a Linux console, skipping GPM");
35 return Ok(None);
36 }
37
38 let mut conn = GpmConnect {
40 event_mask: ffi::GPM_MOVE
42 | ffi::GPM_DRAG
43 | ffi::GPM_DOWN
44 | ffi::GPM_UP
45 | ffi::GPM_SINGLE
46 | ffi::GPM_DOUBLE
47 | ffi::GPM_TRIPLE,
48 default_mask: 0,
50 min_mod: 0,
52 max_mod: !0,
53 pid: 0, vc: 0, };
56
57 tracing::debug!("GPM: Calling Gpm_Open...");
58 let result = lib.open(&mut conn);
59 tracing::debug!("GPM: Gpm_Open returned {}", result);
60
61 match result {
62 -2 => {
63 tracing::debug!("GPM: Reports xterm mode (-2), using standard mouse protocol");
65 Ok(None)
66 }
67 -1 => {
68 let err = io::Error::last_os_error();
70 tracing::debug!("GPM: Connection failed (-1): {}", err);
71 Ok(None) }
73 fd if fd >= 0 => {
74 tracing::info!("GPM: Connected successfully, fd={}", fd);
75 lib.set_visible_pointer(true);
77 Ok(Some(Self { fd, lib }))
78 }
79 _ => {
80 tracing::warn!("GPM: Unexpected Gpm_Open return value: {}", result);
82 Ok(None)
83 }
84 }
85 }
86
87 pub fn fd(&self) -> RawFd {
89 self.fd
90 }
91
92 pub fn read_event(&self) -> io::Result<Option<GpmEvent>> {
94 let mut raw = GpmEventRaw {
95 buttons: 0,
96 modifiers: 0,
97 vc: 0,
98 dx: 0,
99 dy: 0,
100 x: 0,
101 y: 0,
102 event_type: 0,
103 clicks: 0,
104 margin: 0,
105 wdx: 0,
106 wdy: 0,
107 };
108
109 let result = self.lib.get_event(&mut raw);
110
111 match result {
112 1 => {
113 let event = GpmEvent {
115 buttons: GpmButtons(raw.buttons),
116 modifiers: GpmModifiers(raw.modifiers),
117 x: raw.x.saturating_sub(1),
119 y: raw.y.saturating_sub(1),
120 dx: raw.dx,
121 dy: raw.dy,
122 event_type: raw.event_type as u32,
123 clicks: raw.clicks,
124 wdx: raw.wdx,
125 wdy: raw.wdy,
126 };
127 tracing::trace!(
128 "GPM event: x={}, y={}, buttons={:?}, type=0x{:x}, wdy={}",
129 event.x,
130 event.y,
131 event.buttons.0,
132 event.event_type,
133 event.wdy
134 );
135 Ok(Some(event))
136 }
137 0 => {
138 Ok(None)
140 }
141 _ => {
142 Err(io::Error::last_os_error())
144 }
145 }
146 }
147
148 fn is_linux_console() -> bool {
150 use std::fs;
151 use std::io;
152
153 let is_tty = nix::unistd::isatty(io::stdin()).unwrap_or(false);
155 tracing::debug!("GPM: stdin isatty = {}", is_tty);
156 if !is_tty {
157 return false;
158 }
159
160 match fs::read_link("/proc/self/fd/0") {
163 Ok(tty_path) => {
164 let tty_str = tty_path.to_string_lossy();
165 tracing::debug!("GPM: stdin tty path = {}", tty_str);
166
167 if tty_str.starts_with("/dev/tty") && !tty_str.starts_with("/dev/ttyS") {
170 let suffix = &tty_str[8..];
172 tracing::debug!("GPM: tty suffix = '{}'", suffix);
173 if suffix.chars().all(|c| c.is_ascii_digit()) && !suffix.is_empty() {
174 tracing::debug!("GPM: Detected Linux console: {}", tty_str);
175 return true;
176 }
177 }
178 tracing::debug!(
179 "GPM: Not a Linux virtual console (tty path doesn't match pattern)"
180 );
181 false
182 }
183 Err(e) => {
184 tracing::debug!("GPM: Failed to read /proc/self/fd/0 link: {}", e);
185 false
186 }
187 }
188 }
189}
190
191impl Drop for GpmClient {
192 fn drop(&mut self) {
193 self.lib.close();
194 tracing::debug!("GPM connection closed");
195 }
196}