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
//! Query handlers for IPC commands.
//!
//! This module contains methods that extract query logic from the dispatch
//! match arms into dedicated methods. Each method returns a [`SocketResponse`]
//! with JSON data.
use crate::ipc::message::SocketResponse;
use super::types::FlowWM;
impl FlowWM {
/// Return all tracked windows as JSON.
///
/// The viewport offset from the virtual layout is included so clients
/// can determine which windows are currently visible on screen.
pub(super) fn query_windows_all(&self) -> SocketResponse {
let viewport_offset = self.active_scrolling().virtual_layout().viewport_offset;
SocketResponse::Data {
payload: self.registry.to_json_value(viewport_offset),
}
}
/// Return the virtual layout structure as JSON.
///
/// The virtual layout represents the logical structure on an infinite
/// horizontal canvas. Columns store pixel widths (`width_px`), not pixel
/// positions (the projection layer derives positions via prefix-sum).
pub(super) fn query_layout_virtual(&self) -> SocketResponse {
let vl = self.active_scrolling().virtual_layout();
let columns_json: Vec<serde_json::Value> = vl
.columns
.iter()
.enumerate()
.map(|(i, col)| {
let rows: Vec<serde_json::Value> = col
.rows
.iter()
.map(|row| serde_json::json!({ "window_id": row.window_id.0, "height_px": row.height }))
.collect();
serde_json::json!({
"index": i,
"width_px": col.width_px,
"rows": rows,
})
})
.collect();
SocketResponse::Data {
payload: serde_json::json!({
"viewport_offset": vl.viewport_offset,
"column_count": vl.columns.len(),
"window_count": vl.window_count(),
"columns": columns_json,
}),
}
}
/// Return the actual layout (projected coordinates) as JSON.
///
/// The actual layout contains pixel-level coordinates for each window
/// after projection and padding have been applied.
pub(super) fn query_layout_actual(&self) -> SocketResponse {
let vl = self.active_scrolling().virtual_layout();
let al = self.active_scrolling().actual_layout();
let entries_json: Vec<serde_json::Value> = al
.entries
.iter()
.map(|e| {
serde_json::json!({
"window_id": e.window_id.0,
"rect": {
"x": e.rect.x,
"y": e.rect.y,
"width": e.rect.width,
"height": e.rect.height,
},
})
})
.collect();
SocketResponse::Data {
payload: serde_json::json!({
"viewport_offset": vl.viewport_offset,
"entry_count": al.entries.len(),
"entries": entries_json,
}),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
/// Verify that query methods exist and have the correct return type.
///
/// Since FlowWM construction requires admin rights (for named pipe)
/// and a real Windows desktop, we can't do full unit tests here.
/// The query methods are tested indirectly via integration tests in tests/cli/.
#[test]
fn query_methods_exist_and_have_correct_return_type() {
// This test verifies that the methods exist and return SocketResponse.
// We can't call them directly without a FlowWM instance.
// The actual behavior is tested via integration tests.
// query_windows_all returns SocketResponse
let _type_check: fn(&FlowWM) -> SocketResponse = FlowWM::query_windows_all;
// query_layout_virtual returns SocketResponse
let _type_check: fn(&FlowWM) -> SocketResponse = FlowWM::query_layout_virtual;
// query_layout_actual returns SocketResponse
let _type_check: fn(&FlowWM) -> SocketResponse = FlowWM::query_layout_actual;
}
/// Positive: verify that SocketResponse::Data is constructible with JSON.
///
/// This ensures that the query methods can return the correct type.
#[test]
fn socket_response_data_constructible_with_json() {
let payload = serde_json::json!({
"viewport_offset": 0,
"column_count": 0,
"window_count": 0,
"columns": []
});
let response = SocketResponse::Data { payload };
match response {
SocketResponse::Data { payload } => {
assert!(payload.is_object());
assert_eq!(payload["viewport_offset"], 0);
}
_ => panic!("should be Data variant"),
}
}
/// Positive: verify that JSON serialization works for query payloads.
#[test]
fn query_payload_serializes_to_json() {
let payload = serde_json::json!({
"test_key": "test_value",
"number": 42
});
let json_str = serde_json::to_string(&payload);
assert!(json_str.is_ok());
assert!(json_str.unwrap().contains("test_value"));
}
/// Positive: verify that virtual layout JSON structure is valid.
#[test]
fn virtual_layout_json_structure_is_valid() {
// Create a minimal virtual layout structure using a JSON array
let columns_json = serde_json::json!([]);
let window_count: usize = 0;
let json = serde_json::json!({
"viewport_offset": 0,
"column_count": columns_json.as_array().unwrap().len(),
"window_count": window_count,
"columns": columns_json
});
assert_eq!(json["viewport_offset"], 0);
assert_eq!(json["column_count"], 0);
assert_eq!(json["window_count"], 0);
assert!(json["columns"].as_array().unwrap().is_empty());
}
/// Positive: verify that actual layout JSON structure is valid.
#[test]
fn actual_layout_json_structure_is_valid() {
// Create a minimal actual layout structure using a JSON array
let entries_json = serde_json::json!([]);
let json = serde_json::json!({
"viewport_offset": 0,
"entry_count": entries_json.as_array().unwrap().len(),
"entries": entries_json
});
assert_eq!(json["viewport_offset"], 0);
assert_eq!(json["entry_count"], 0);
assert!(json["entries"].as_array().unwrap().is_empty());
}
}