1use std::{any::type_name, io::BufRead};
11
12use anyhow::Result as AnyhowResult;
13use bevy::{
14 platform::collections::HashMap,
15 remote::{
16 builtin_methods::{
17 BrpObserveParams, BrpQuery, BrpQueryFilter, BrpQueryParams, BrpSpawnEntityParams,
18 BrpWriteMessageParams, ComponentSelector, BRP_OBSERVE_METHOD, BRP_QUERY_METHOD,
19 BRP_SPAWN_ENTITY_METHOD, BRP_WRITE_MESSAGE_METHOD,
20 },
21 http::{DEFAULT_ADDR, DEFAULT_PORT},
22 BrpRequest,
23 },
24 render::view::screenshot::{Screenshot, ScreenshotCaptured},
25 ui::{widget::Button, UiGlobalTransform},
26 window::{Window, WindowEvent},
27};
28
29fn main() -> AnyhowResult<()> {
30 let url = format!("http://{DEFAULT_ADDR}:{DEFAULT_PORT}/");
31
32 println!("Spawning Screenshot entity...");
36 let spawn_response = brp_request(
37 &url,
38 BRP_SPAWN_ENTITY_METHOD,
39 1,
40 &BrpSpawnEntityParams {
41 components: HashMap::from([(
42 type_name::<Screenshot>().to_string(),
43 serde_json::json!({"Window": "Primary"}),
44 )]),
45 },
46 )?;
47 let screenshot_entity = &spawn_response["result"]["entity"];
48
49 println!("Observing ScreenshotCaptured on entity {screenshot_entity}...");
50 let observe_response = ureq::post(&url).send_json(BrpRequest {
51 method: BRP_OBSERVE_METHOD.to_string(),
52 id: Some(serde_json::to_value(2)?),
53 params: Some(serde_json::to_value(BrpObserveParams {
54 event: type_name::<ScreenshotCaptured>().to_string(),
55 entity: Some(serde_json::from_value(screenshot_entity.clone())?),
56 })?),
57 })?;
58
59 println!("Waiting for screenshot capture...");
60 let reader = std::io::BufReader::new(observe_response.into_body().into_reader());
61 for line in reader.lines() {
62 let line = line?;
63 if let Some(json_str) = line.strip_prefix("data: ") {
64 let response: serde_json::Value = serde_json::from_str(json_str)?;
65 if let Some(error) = response.get("error") {
66 anyhow::bail!("Observe error: {error}");
67 }
68 if let Some(result) = response.get("result") {
69 let events = result.as_array().expect("Expected events array");
70 let event = &events[0];
71
72 let image_data = &event["image"];
73 let width = image_data["texture_descriptor"]["size"]["width"]
74 .as_u64()
75 .unwrap();
76 let height = image_data["texture_descriptor"]["size"]["height"]
77 .as_u64()
78 .unwrap();
79 println!("Screenshot captured! Image size: {width}x{height}");
80
81 let image: bevy::image::Image = serde_json::from_value(image_data.clone())?;
82 let dyn_img = image
83 .try_into_dynamic()
84 .expect("Failed to convert screenshot to dynamic image");
85 let path = "screenshot.png";
86 dyn_img.to_rgb8().save(path)?;
87 println!("Screenshot saved to {path}");
88 break;
89 }
90 }
91 }
92
93 println!("Querying for button entity...");
95 let button_query = brp_request(
96 &url,
97 BRP_QUERY_METHOD,
98 3,
99 &BrpQueryParams {
100 data: BrpQuery {
101 components: vec![type_name::<UiGlobalTransform>().to_string()],
102 option: ComponentSelector::default(),
103 has: Vec::default(),
104 },
105 strict: false,
106 filter: BrpQueryFilter {
107 with: vec![type_name::<Button>().to_string()],
108 without: Vec::default(),
109 },
110 },
111 )?;
112
113 let button_result = button_query["result"]
114 .as_array()
115 .expect("Expected result array");
116 let button = &button_result[0];
117
118 let transform = &button["components"][type_name::<UiGlobalTransform>()];
122 let transform_arr = transform.as_array().expect("Expected transform array");
123 let phys_x = transform_arr[4].as_f64().unwrap();
124 let phys_y = transform_arr[5].as_f64().unwrap();
125 println!("Found button at physical ({phys_x}, {phys_y})");
126
127 println!("Querying for window entity...");
129 let window_query = brp_request(
130 &url,
131 BRP_QUERY_METHOD,
132 4,
133 &BrpQueryParams {
134 data: BrpQuery {
135 components: vec![type_name::<Window>().to_string()],
136 option: ComponentSelector::default(),
137 has: Vec::default(),
138 },
139 strict: false,
140 filter: BrpQueryFilter::default(),
141 },
142 )?;
143
144 let window_result = window_query["result"]
145 .as_array()
146 .expect("Expected result array");
147 let window = &window_result[0];
148 let window_entity = &window["entity"];
149 let window_data = &window["components"][type_name::<Window>()];
150 let scale_factor = window_data["resolution"]["scale_factor"].as_f64().unwrap();
151 println!("Found window entity: {window_entity}, scale_factor: {scale_factor}");
152
153 let logical_x = phys_x / scale_factor;
155 let logical_y = phys_y / scale_factor;
156 println!("Clicking at logical position: ({logical_x}, {logical_y})");
157
158 println!("Sending CursorMoved message...");
161 brp_request(
162 &url,
163 BRP_WRITE_MESSAGE_METHOD,
164 5,
165 &BrpWriteMessageParams {
166 message: type_name::<WindowEvent>().to_string(),
167 value: Some(serde_json::json!({
168 "CursorMoved": {
169 "window": window_entity,
170 "position": [logical_x, logical_y],
171 "delta": null
172 }
173 })),
174 },
175 )?;
176
177 println!("Sending mouse press...");
180 brp_request(
181 &url,
182 BRP_WRITE_MESSAGE_METHOD,
183 6,
184 &BrpWriteMessageParams {
185 message: type_name::<WindowEvent>().to_string(),
186 value: Some(serde_json::json!({
187 "MouseButtonInput": {
188 "button": "Left",
189 "state": "Pressed",
190 "window": window_entity,
191 }
192 })),
193 },
194 )?;
195
196 println!("Sending mouse release...");
197 brp_request(
198 &url,
199 BRP_WRITE_MESSAGE_METHOD,
200 7,
201 &BrpWriteMessageParams {
202 message: type_name::<WindowEvent>().to_string(),
203 value: Some(serde_json::json!({
204 "MouseButtonInput": {
205 "button": "Left",
206 "state": "Released",
207 "window": window_entity,
208 }
209 })),
210 },
211 )?;
212
213 Ok(())
214}
215
216fn brp_request(
217 url: &str,
218 method: &str,
219 id: u32,
220 params: &impl serde::Serialize,
221) -> AnyhowResult<serde_json::Value> {
222 let req = BrpRequest {
223 method: method.to_string(),
224 id: Some(serde_json::to_value(id)?),
225 params: Some(serde_json::to_value(params)?),
226 };
227 Ok(ureq::post(url).send_json(req)?.body_mut().read_json()?)
228}