wayfire_rs/
ipc.rs

1use crate::models::{
2    InputDevice, Layout, MsgTemplate, OptionValueResponse, Output, View, ViewAlpha,
3    WayfireConfiguration, WorkspaceSet,
4};
5use serde_json::Value;
6use std::env;
7use std::error::Error;
8use std::io;
9use tokio::io::{AsyncReadExt, AsyncWriteExt};
10use tokio::net::UnixStream as TokioUnixStream;
11
12pub struct WayfireSocket {
13    client: TokioUnixStream,
14}
15
16impl WayfireSocket {
17    pub async fn connect() -> io::Result<Self> {
18        let socket_name =
19            env::var("WAYFIRE_SOCKET").expect("WAYFIRE_SOCKET environment variable not set");
20        let client = TokioUnixStream::connect(&socket_name).await?;
21        Ok(WayfireSocket { client })
22    }
23
24    pub async fn send_json(&mut self, msg: &MsgTemplate) -> io::Result<Value> {
25        let data = serde_json::to_vec(msg)?;
26        let header = (data.len() as u32).to_le_bytes();
27
28        self.client.write_all(&header).await?;
29        self.client.write_all(&data).await?;
30
31        self.read_message().await
32    }
33
34    pub async fn read_exact(&mut self, n: usize) -> io::Result<Vec<u8>> {
35        let mut buf = vec![0; n];
36        self.client.read_exact(&mut buf).await?;
37        Ok(buf)
38    }
39
40    pub async fn read_message(&mut self) -> io::Result<Value> {
41        let len_buf = self.read_exact(4).await?;
42        let len = u32::from_le_bytes(len_buf.try_into().unwrap()) as usize;
43
44        let response_buf = self.read_exact(len).await?;
45        let response: Value = serde_json::from_slice(&response_buf)?;
46
47        if response.get("error").is_some() {
48            eprintln!("Error: {:?}", response);
49        }
50
51        Ok(response)
52    }
53
54    pub async fn list_views(&mut self) -> io::Result<Vec<View>> {
55        let message = MsgTemplate {
56            method: "window-rules/list-views".to_string(),
57            data: None,
58        };
59
60        let response = self.send_json(&message).await?;
61        let views: Vec<View> = serde_json::from_value(response)?;
62
63        Ok(views)
64    }
65
66    pub async fn list_outputs(&mut self) -> io::Result<Vec<Output>> {
67        let message = MsgTemplate {
68            method: "window-rules/list-outputs".to_string(),
69            data: None,
70        };
71
72        let response = self.send_json(&message).await?;
73        let outputs: Vec<Output> = serde_json::from_value(response)?;
74
75        Ok(outputs)
76    }
77
78    pub async fn list_wsets(&mut self) -> io::Result<Vec<WorkspaceSet>> {
79        let message = MsgTemplate {
80            method: "window-rules/list-wsets".to_string(),
81            data: None,
82        };
83
84        let response = self.send_json(&message).await?;
85        let workspace_sets: Vec<WorkspaceSet> = serde_json::from_value(response)?;
86
87        Ok(workspace_sets)
88    }
89
90    pub async fn list_input_devices(&mut self) -> io::Result<Vec<InputDevice>> {
91        let message = MsgTemplate {
92            method: "input/list-devices".to_string(),
93            data: None,
94        };
95
96        let response = self.send_json(&message).await?;
97        let input_devices: Vec<InputDevice> = serde_json::from_value(response)?;
98
99        Ok(input_devices)
100    }
101
102    pub async fn get_configuration(&mut self) -> io::Result<WayfireConfiguration> {
103        let message = MsgTemplate {
104            method: "wayfire/configuration".to_string(),
105            data: None,
106        };
107
108        let response = self.send_json(&message).await?;
109        let configuration: WayfireConfiguration = serde_json::from_value(response)?;
110
111        Ok(configuration)
112    }
113
114    pub async fn get_option_value(&mut self, option: &str) -> io::Result<OptionValueResponse> {
115        let message = MsgTemplate {
116            method: "wayfire/get-config-option".to_string(),
117            data: Some(serde_json::json!({
118                "option": option
119            })),
120        };
121
122        let response = self.send_json(&message).await?;
123        let option_value_response: OptionValueResponse = serde_json::from_value(response)?;
124
125        Ok(option_value_response)
126    }
127
128    pub async fn get_output(&mut self, output_id: i64) -> io::Result<Output> {
129        let message = MsgTemplate {
130            method: "window-rules/output-info".to_string(),
131            data: Some(serde_json::json!({
132                "id": output_id
133            })),
134        };
135
136        let response = self.send_json(&message).await?;
137        let output: Output = serde_json::from_value(response)?;
138
139        Ok(output)
140    }
141
142    pub async fn get_view(&mut self, view_id: i64) -> io::Result<View> {
143        let message = MsgTemplate {
144            method: "window-rules/view-info".to_string(),
145            data: Some(serde_json::json!({
146                "id": view_id
147            })),
148        };
149
150        let response = self.send_json(&message).await?;
151
152        let info = response.get("info").ok_or_else(|| {
153            io::Error::new(io::ErrorKind::NotFound, "Missing 'info' field in response")
154        })?;
155
156        let view: View = serde_json::from_value(info.clone())?;
157
158        Ok(view)
159    }
160
161    pub async fn get_focused_view(&mut self) -> Result<View, Box<dyn Error>> {
162        let message = MsgTemplate {
163            method: "window-rules/get-focused-view".to_string(),
164            data: None,
165        };
166
167        let response = self.send_json(&message).await?;
168
169        let view_info = response.get("info").ok_or_else(|| {
170            io::Error::new(io::ErrorKind::NotFound, "Missing 'info' field in response")
171        })?;
172
173        let view: View = serde_json::from_value(view_info.clone())?;
174
175        Ok(view)
176    }
177    pub async fn get_focused_output(&mut self) -> Result<Output, Box<dyn Error>> {
178        let message = MsgTemplate {
179            method: "window-rules/get-focused-output".to_string(),
180            data: None,
181        };
182
183        let response = self.send_json(&message).await?;
184
185        let output_info = response.get("info").ok_or_else(|| {
186            io::Error::new(io::ErrorKind::NotFound, "Missing 'info' field in response")
187        })?;
188
189        let output: Output = serde_json::from_value(output_info.clone())?;
190
191        Ok(output)
192    }
193
194    pub async fn get_view_alpha(&mut self, view_id: i64) -> io::Result<ViewAlpha> {
195        let message = MsgTemplate {
196            method: "wf/alpha/get-view-alpha".to_string(),
197            data: Some(serde_json::json!({
198                "view-id": view_id
199            })),
200        };
201
202        let response = self.send_json(&message).await?;
203
204        let view_alpha: ViewAlpha = serde_json::from_value(response).map_err(|e| {
205            io::Error::new(
206                io::ErrorKind::InvalidData,
207                format!("Failed to parse response: {}", e),
208            )
209        })?;
210
211        Ok(view_alpha)
212    }
213
214    pub async fn set_view_alpha(&mut self, view_id: i64, alpha: f64) -> io::Result<Value> {
215        let message = MsgTemplate {
216            method: "wf/alpha/set-view-alpha".to_string(),
217            data: Some(serde_json::json!({
218                "view-id": view_id,
219                "alpha": alpha
220            })),
221        };
222
223        self.send_json(&message).await
224    }
225
226    pub async fn get_tiling_layout(&mut self, wset: i64, x: i64, y: i64) -> io::Result<Layout> {
227        let message = MsgTemplate {
228            method: "simple-tile/get-layout".to_string(),
229            data: Some(serde_json::json!({
230                "wset-index": wset,
231                "workspace": {
232                    "x": x,
233                    "y": y
234                }
235            })),
236        };
237
238        let response = self.send_json(&message).await?;
239
240        let layout_value = response.get("layout").ok_or_else(|| {
241            io::Error::new(
242                io::ErrorKind::InvalidData,
243                "Missing `layout` field in response",
244            )
245        })?;
246
247        let layout: Layout = serde_json::from_value(layout_value.clone())?;
248
249        Ok(layout)
250    }
251
252    pub async fn set_tiling_layout(
253        &mut self,
254        wset: i64,
255        x: i64,
256        y: i64,
257        layout: &Layout,
258    ) -> io::Result<Value> {
259        let message = MsgTemplate {
260            method: "simple-tile/set-layout".to_string(),
261            data: Some(serde_json::json!({
262                "wset-index": wset,
263                "workspace": {
264                    "x": x,
265                    "y": y
266                },
267                "layout": layout
268            })),
269        };
270
271        self.send_json(&message).await
272    }
273
274    pub async fn set_view_fullscreen(&mut self, view_id: i64, state: bool) -> io::Result<Value> {
275        let message = MsgTemplate {
276            method: "wm-actions/set-fullscreen".to_string(),
277            data: Some(serde_json::json!({
278                "view_id": view_id,
279                "state": state
280            })),
281        };
282
283        self.send_json(&message).await
284    }
285
286    pub async fn expo_toggle(&mut self) -> io::Result<Value> {
287        let message = MsgTemplate {
288            method: "expo/toggle".to_string(),
289            data: None,
290        };
291
292        self.send_json(&message).await
293    }
294
295    pub async fn scale_toggle(&mut self) -> io::Result<Value> {
296        let message = MsgTemplate {
297            method: "scale/toggle".to_string(),
298            data: None,
299        };
300
301        self.send_json(&message).await
302    }
303    pub async fn scale_toggle_all(&mut self) -> io::Result<Value> {
304        let message = MsgTemplate {
305            method: "expo/toggle_all".to_string(),
306            data: None,
307        };
308
309        self.send_json(&message).await
310    }
311
312    pub async fn cube_activate(&mut self) -> io::Result<Value> {
313        let message = MsgTemplate {
314            method: "cube/activate".to_string(),
315            data: None,
316        };
317
318        self.send_json(&message).await
319    }
320
321    pub async fn cube_rotate_left(&mut self) -> io::Result<Value> {
322        let message = MsgTemplate {
323            method: "cube/rotate_left".to_string(),
324            data: None,
325        };
326
327        self.send_json(&message).await
328    }
329
330    pub async fn cube_rotate_right(&mut self) -> io::Result<Value> {
331        let message = MsgTemplate {
332            method: "cube/rotate_right".to_string(),
333            data: None,
334        };
335
336        self.send_json(&message).await
337    }
338
339    pub async fn toggle_showdesktop(&mut self) -> io::Result<Value> {
340        let message = MsgTemplate {
341            method: "wm-actions/toggle_showdesktop".to_string(),
342            data: None,
343        };
344        self.send_json(&message).await
345    }
346
347    pub async fn set_view_sticky(&mut self, view_id: i64, state: bool) -> io::Result<Value> {
348        let message = MsgTemplate {
349            method: "wm-actions/set-sticky".to_string(),
350            data: Some(serde_json::json!({
351                "view_id": view_id,
352                "state": state,
353            })),
354        };
355        self.send_json(&message).await
356    }
357
358    pub async fn send_view_to_back(&mut self, view_id: i64, state: bool) -> io::Result<Value> {
359        let message = MsgTemplate {
360            method: "wm-actions/send-to-back".to_string(),
361            data: Some(serde_json::json!({
362                "view_id": view_id,
363                "state": state,
364            })),
365        };
366        self.send_json(&message).await
367    }
368
369    pub async fn set_view_minimized(&mut self, view_id: i64, state: bool) -> io::Result<Value> {
370        let message = MsgTemplate {
371            method: "wm-actions/set-minimized".to_string(),
372            data: Some(serde_json::json!({
373                "view_id": view_id,
374                "state": state,
375            })),
376        };
377        self.send_json(&message).await
378    }
379
380    pub async fn configure_input_device(&mut self, id: i64, enabled: bool) -> io::Result<Value> {
381        let message = MsgTemplate {
382            method: "input/configure-device".to_string(),
383            data: Some(serde_json::json!({
384                "id": id,
385                "enabled": enabled,
386            })),
387        };
388        self.send_json(&message).await
389    }
390
391    pub async fn close_view(&mut self, view_id: i64) -> io::Result<Value> {
392        let message = MsgTemplate {
393            method: "window-rules/close-view".to_string(),
394            data: Some(serde_json::json!({
395                "id": view_id,
396            })),
397        };
398        self.send_json(&message).await
399    }
400
401    pub async fn wset_info(&mut self, id: i64) -> io::Result<serde_json::Value> {
402        let message = MsgTemplate {
403            method: "window-rules/wset-info".to_string(),
404            data: Some(serde_json::json!({
405                "id": id,
406            })),
407        };
408
409        self.send_json(&message).await
410    }
411
412    pub async fn watch(&mut self, events: Option<Vec<String>>) -> io::Result<serde_json::Value> {
413        let mut data = serde_json::json!({});
414        if let Some(events) = events {
415            data["events"] = serde_json::json!(events);
416        }
417
418        let message = MsgTemplate {
419            method: "window-rules/events/watch".to_string(),
420            data: Some(data),
421        };
422
423        self.send_json(&message).await
424    }
425
426    pub async fn configure_view(
427        &mut self,
428        view_id: i64,
429        x: i64,
430        y: i64,
431        w: i64,
432        h: i64,
433        output_id: Option<i64>,
434    ) -> io::Result<serde_json::Value> {
435        let mut data = serde_json::json!({
436            "id": view_id,
437            "geometry": {
438                "x": x,
439                "y": y,
440                "width": w,
441                "height": h
442            }
443        });
444
445        if let Some(output_id) = output_id {
446            data["output_id"] = serde_json::json!(output_id);
447        }
448
449        let message = MsgTemplate {
450            method: "window-rules/configure-view".to_string(),
451            data: Some(data),
452        };
453
454        self.send_json(&message).await
455    }
456
457    pub async fn assign_slot(&mut self, view_id: i64, slot: &str) -> io::Result<serde_json::Value> {
458        let message = MsgTemplate {
459            method: format!("grid/{}", slot),
460            data: Some(serde_json::json!({
461                "view_id": view_id
462            })),
463        };
464
465        self.send_json(&message).await
466    }
467
468    pub async fn set_focus(&mut self, view_id: i64) -> io::Result<serde_json::Value> {
469        let message = MsgTemplate {
470            method: "window-rules/focus-view".to_string(),
471            data: Some(serde_json::json!({
472                "id": view_id
473            })),
474        };
475
476        self.send_json(&message).await
477    }
478
479    pub async fn set_workspace(
480        &mut self,
481        x: i64,
482        y: i64,
483        view_id: i64,
484        output_id: i64,
485    ) -> io::Result<Value> {
486        let message = MsgTemplate {
487            method: "vswitch/set-workspace".to_string(),
488            data: Some(serde_json::json!({
489                "x": x,
490                "y": y,
491                "output-id": output_id,
492                "view-id": view_id
493            })),
494        };
495
496        self.send_json(&message).await
497    }
498
499    pub async fn create_headless_output(&mut self, width: u32, height: u32) -> io::Result<Value> {
500        let message = MsgTemplate {
501            method: "wayfire/create-headless-output".to_string(),
502            data: Some(serde_json::json!({
503                "width": width,
504                "height": height
505            })),
506        };
507
508        self.send_json(&message).await
509    }
510
511    pub async fn destroy_headless_output(
512        &mut self,
513        output_name: Option<String>,
514        output_id: Option<i64>,
515    ) -> io::Result<Value> {
516        let mut data = serde_json::json!({});
517
518        if let Some(name) = output_name {
519            data["output"] = serde_json::Value::String(name);
520        } else if let Some(id) = output_id {
521            data["output-id"] = serde_json::Value::Number(serde_json::Number::from(id));
522        } else {
523            return Err(io::Error::new(
524                io::ErrorKind::InvalidInput,
525                "Either output_name or output_id must be provided",
526            ));
527        }
528
529        let message = MsgTemplate {
530            method: "wayfire/destroy-headless-output".to_string(),
531            data: Some(data),
532        };
533
534        self.send_json(&message).await
535    }
536}