yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
gui_commands.cc
Go to the documentation of this file.
2
3#include "absl/flags/declare.h"
4#include "absl/flags/flag.h"
5#include "absl/strings/numbers.h"
6#include "absl/strings/str_format.h"
7#include "absl/strings/str_join.h"
10#include "cli/util/hex_util.h"
11
12ABSL_DECLARE_FLAG(std::string, gui_server_address);
13ABSL_DECLARE_FLAG(bool, quiet);
14
15namespace yaze {
16namespace cli {
17namespace handlers {
18
20
21namespace {
22
24 const resources::ArgumentParser& parser) {
25 const bool has_target = parser.GetString("target").has_value();
26 const bool has_widget_key = parser.GetString("widget-key").has_value();
27 if (has_target == has_widget_key) {
28 return absl::InvalidArgumentError(
29 "Provide exactly one of --target or --widget-key");
30 }
31 return absl::OkStatus();
32}
33
35 const resources::ArgumentParser& parser) {
36 const bool has_condition = parser.GetString("condition").has_value();
37 const bool has_widget_key = parser.GetString("widget-key").has_value();
38 if (!has_condition && !has_widget_key) {
39 return absl::InvalidArgumentError(
40 "Provide at least one of --condition or --widget-key");
41 }
42 return absl::OkStatus();
43}
44
45absl::StatusOr<int> ReadIntArgOrDefault(const resources::ArgumentParser& parser,
46 const std::string& arg_name,
47 int default_value) {
48 if (!parser.GetString(arg_name).has_value()) {
49 return default_value;
50 }
51 return parser.GetInt(arg_name);
52}
53
55 resources::OutputFormatter& formatter) {
56 if (!result.resolved_widget_key.empty()) {
57 formatter.AddField("resolved_widget_key", result.resolved_widget_key);
58 }
59 if (!result.resolved_path.empty()) {
60 formatter.AddField("resolved_path", result.resolved_path);
61 }
62}
63
65 auto status = client->Connect();
66 if (!status.ok()) {
67 return absl::UnavailableError("Failed to connect to GUI server: " +
68 std::string(status.message()));
69 }
70 return absl::OkStatus();
71}
72
73} // namespace
74
76 const resources::ArgumentParser& parser) {
77 return ValidateExactlyOneTargetOrWidget(parser);
78}
79
81 const resources::ArgumentParser& parser) {
82 if (auto selector_status = ValidateExactlyOneTargetOrWidget(parser);
83 !selector_status.ok()) {
84 return selector_status;
85 }
86 if (!parser.GetString("text").has_value()) {
87 return absl::InvalidArgumentError("Missing required argument: --text");
88 }
89 return absl::OkStatus();
90}
91
93 const resources::ArgumentParser& parser) {
94 return ValidateConditionOrWidget(parser);
95}
96
98 const resources::ArgumentParser& parser) {
99 return ValidateConditionOrWidget(parser);
100}
101
103 Rom* rom, const resources::ArgumentParser& parser,
104 resources::OutputFormatter& formatter) {
105 auto tile_id_str = parser.GetString("tile").value();
106 auto x_str = parser.GetString("x").value();
107 auto y_str = parser.GetString("y").value();
108
109 int tile_id, x, y;
110 if (!ParseHexString(tile_id_str, &tile_id) || !absl::SimpleAtoi(x_str, &x) ||
111 !absl::SimpleAtoi(y_str, &y)) {
112 return absl::InvalidArgumentError("Invalid tile ID or coordinate format.");
113 }
114
115 CanvasAutomationClient client(absl::GetFlag(FLAGS_gui_server_address));
116 auto status = client.Connect();
117 if (!status.ok()) {
118 return absl::UnavailableError("Failed to connect to GUI server: " +
119 std::string(status.message()));
120 }
121
122 // Assume "overworld" canvas for now, or parse from args if needed
123 std::string canvas_id = "overworld";
124 status = client.SetTile(canvas_id, x, y, tile_id);
125
126 formatter.BeginObject("GUI Tile Placement");
127 formatter.AddField("tile_id", absl::StrFormat("0x%03X", tile_id));
128 formatter.AddField("x", x);
129 formatter.AddField("y", y);
130 if (status.ok()) {
131 formatter.AddField("status", "Success");
132 } else {
133 formatter.AddField("status", "Failed");
134 formatter.AddField("error", std::string(status.message()));
135 }
136 formatter.EndObject();
137
138 return status;
139}
140
142 Rom* rom, const resources::ArgumentParser& parser,
143 resources::OutputFormatter& formatter) {
144 auto target = parser.GetString("target").value_or("");
145 auto widget_key = parser.GetString("widget-key").value_or("");
146 auto click_type_str = parser.GetString("click-type").value_or("left");
147
148 ClickType click_type = ClickType::kLeft;
149 if (click_type_str == "right")
150 click_type = ClickType::kRight;
151 else if (click_type_str == "middle")
152 click_type = ClickType::kMiddle;
153 else if (click_type_str == "double")
154 click_type = ClickType::kDouble;
155
156 GuiAutomationClient client(absl::GetFlag(FLAGS_gui_server_address));
157 if (auto status = ConnectGuiClient(&client); !status.ok()) {
158 return status;
159 }
160
161 auto result = client.Click(target, click_type, widget_key);
162
163 formatter.BeginObject("GUI Click Action");
164 formatter.AddField("target", target);
165 formatter.AddField("widget_key", widget_key);
166 formatter.AddField("click_type", click_type_str);
167
168 if (result.ok()) {
169 formatter.AddField("status", result->success ? "Success" : "Failed");
170 AddSelectorResolutionFields(*result, formatter);
171 if (!result->success) {
172 formatter.AddField("error", result->message);
173 }
174 formatter.AddField("execution_time_ms",
175 static_cast<int>(result->execution_time.count()));
176 } else {
177 formatter.AddField("status", "Error");
178 formatter.AddField("error", std::string(result.status().message()));
179 }
180 formatter.EndObject();
181
182 return result.status();
183}
184
186 Rom* rom, const resources::ArgumentParser& parser,
187 resources::OutputFormatter& formatter) {
188 auto target = parser.GetString("target").value_or("");
189 auto widget_key = parser.GetString("widget-key").value_or("");
190 auto text = parser.GetString("text").value_or("");
191 const bool clear_first = parser.HasFlag("clear-first");
192
193 GuiAutomationClient client(absl::GetFlag(FLAGS_gui_server_address));
194 if (auto status = ConnectGuiClient(&client); !status.ok()) {
195 return status;
196 }
197
198 auto result = client.Type(target, text, clear_first, widget_key);
199
200 formatter.BeginObject("GUI Type Action");
201 formatter.AddField("target", target);
202 formatter.AddField("widget_key", widget_key);
203 formatter.AddField("clear_first", clear_first);
204
205 if (result.ok()) {
206 formatter.AddField("status", result->success ? "Success" : "Failed");
207 AddSelectorResolutionFields(*result, formatter);
208 if (!result->success) {
209 formatter.AddField("error", result->message);
210 }
211 formatter.AddField("execution_time_ms",
212 static_cast<int>(result->execution_time.count()));
213 } else {
214 formatter.AddField("status", "Error");
215 formatter.AddField("error", std::string(result.status().message()));
216 }
217 formatter.EndObject();
218
219 return result.status();
220}
221
223 Rom* rom, const resources::ArgumentParser& parser,
224 resources::OutputFormatter& formatter) {
225 auto condition = parser.GetString("condition").value_or("");
226 auto widget_key = parser.GetString("widget-key").value_or("");
227
228 auto timeout_or = ReadIntArgOrDefault(parser, "timeout-ms", 5000);
229 if (!timeout_or.ok()) {
230 return timeout_or.status();
231 }
232 auto poll_interval_or = ReadIntArgOrDefault(parser, "poll-interval-ms", 100);
233 if (!poll_interval_or.ok()) {
234 return poll_interval_or.status();
235 }
236 int timeout_ms = *timeout_or;
237 int poll_interval_ms = *poll_interval_or;
238 if (timeout_ms <= 0) {
239 return absl::InvalidArgumentError("--timeout-ms must be > 0");
240 }
241 if (poll_interval_ms <= 0) {
242 return absl::InvalidArgumentError("--poll-interval-ms must be > 0");
243 }
244
245 GuiAutomationClient client(absl::GetFlag(FLAGS_gui_server_address));
246 if (auto status = ConnectGuiClient(&client); !status.ok()) {
247 return status;
248 }
249
250 auto result =
251 client.Wait(condition, timeout_ms, poll_interval_ms, widget_key);
252
253 formatter.BeginObject("GUI Wait Action");
254 formatter.AddField("condition", condition);
255 formatter.AddField("widget_key", widget_key);
256 formatter.AddField("timeout_ms", timeout_ms);
257 formatter.AddField("poll_interval_ms", poll_interval_ms);
258
259 if (result.ok()) {
260 formatter.AddField("status", result->success ? "Success" : "Failed");
261 AddSelectorResolutionFields(*result, formatter);
262 if (!result->success) {
263 formatter.AddField("error", result->message);
264 }
265 formatter.AddField("elapsed_ms",
266 static_cast<int>(result->execution_time.count()));
267 } else {
268 formatter.AddField("status", "Error");
269 formatter.AddField("error", std::string(result.status().message()));
270 }
271 formatter.EndObject();
272
273 return result.status();
274}
275
277 Rom* rom, const resources::ArgumentParser& parser,
278 resources::OutputFormatter& formatter) {
279 auto condition = parser.GetString("condition").value_or("");
280 auto widget_key = parser.GetString("widget-key").value_or("");
281
282 GuiAutomationClient client(absl::GetFlag(FLAGS_gui_server_address));
283 if (auto status = ConnectGuiClient(&client); !status.ok()) {
284 return status;
285 }
286
287 auto result = client.Assert(condition, widget_key);
288
289 formatter.BeginObject("GUI Assert Action");
290 formatter.AddField("condition", condition);
291 formatter.AddField("widget_key", widget_key);
292
293 if (result.ok()) {
294 formatter.AddField("status", result->success ? "Success" : "Failed");
295 AddSelectorResolutionFields(*result, formatter);
296 if (!result->expected_value.empty()) {
297 formatter.AddField("expected_value", result->expected_value);
298 }
299 if (!result->actual_value.empty()) {
300 formatter.AddField("actual_value", result->actual_value);
301 }
302 if (!result->success) {
303 formatter.AddField("error", result->message);
304 }
305 } else {
306 formatter.AddField("status", "Error");
307 formatter.AddField("error", std::string(result.status().message()));
308 }
309 formatter.EndObject();
310
311 return result.status();
312}
313
315 Rom* rom, const resources::ArgumentParser& parser,
316 resources::OutputFormatter& formatter) {
317 auto window = parser.GetString("window").value_or("");
318 auto type_str = parser.GetString("type").value_or("all");
319
320 // Detect if we were called as 'summarize' to provide more compact output
321 bool is_summary = (GetName() == "gui-summarize-widgets");
322
323 GuiAutomationClient client(absl::GetFlag(FLAGS_gui_server_address));
324 if (auto status = ConnectGuiClient(&client); !status.ok()) {
325 return status;
326 }
327
329 query.window_filter = window;
331 query.include_invisible = false;
332
333 auto result = client.DiscoverWidgets(query);
334
335 formatter.BeginObject(is_summary ? "GUI Summary" : "Widget Discovery");
336 formatter.AddField("window_filter", window);
337
338 if (result.ok()) {
339 formatter.AddField("total_widgets", result->total_widgets);
340 formatter.AddField("status", "Success");
341
342 formatter.BeginArray("windows");
343 for (const auto& win : result->windows) {
344 if (!win.visible && is_summary)
345 continue;
346
347 formatter.BeginObject("window");
348 formatter.AddField("name", win.name);
349 formatter.AddField("visible", win.visible);
350
351 if (is_summary) {
352 std::vector<std::string> highlights;
353 for (const auto& w : win.widgets) {
354 if (w.type == "button" || w.type == "input" || w.type == "menu") {
355 highlights.push_back(absl::StrFormat("%s (%s)", w.label, w.type));
356 }
357 if (highlights.size() > 10)
358 break;
359 }
360 formatter.AddField("key_elements", absl::StrJoin(highlights, ", "));
361 } else {
362 formatter.BeginArray("widgets");
363 int count = 0;
364 for (const auto& widget : win.widgets) {
365 if (count++ > 50)
366 break;
367 formatter.AddArrayItem(absl::StrFormat("%s (%s) - %s", widget.label,
368 widget.type, widget.path));
369 }
370 formatter.EndArray();
371 }
372 formatter.EndObject();
373 }
374 formatter.EndArray();
375 } else {
376 formatter.AddField("status", "Error");
377 formatter.AddField("error", std::string(result.status().message()));
378 }
379 formatter.EndObject();
380
381 return result.status();
382}
383
385 Rom* rom, const resources::ArgumentParser& parser,
386 resources::OutputFormatter& formatter) {
387 auto region = parser.GetString("region").value_or("full");
388 auto image_format = parser.GetString("format").value_or("PNG");
389
390 GuiAutomationClient client(absl::GetFlag(FLAGS_gui_server_address));
391 if (auto status = ConnectGuiClient(&client); !status.ok()) {
392 return status;
393 }
394
395 auto result = client.Screenshot(region, image_format);
396
397 formatter.BeginObject("Screenshot Capture");
398 formatter.AddField("region", region);
399 formatter.AddField("image_format", image_format);
400
401 if (result.ok()) {
402 formatter.AddField("status", result->success ? "Success" : "Failed");
403 if (result->success) {
404 formatter.AddField("output_path", result->message);
405
406 // Also print a user-friendly message directly to stderr for visibility
407 if (!absl::GetFlag(FLAGS_quiet)) {
408 std::cerr << "\n📸 \033[1;32mScreenshot captured!\033[0m\n";
409 std::cerr << " Path: \033[1;34m" << result->message << "\033[0m\n\n";
410 }
411 } else {
412 formatter.AddField("error", result->message);
413 }
414 } else {
415 formatter.AddField("status", "Error");
416 formatter.AddField("error", std::string(result.status().message()));
417 }
418 formatter.EndObject();
419
420 return result.status();
421}
422
423} // namespace handlers
424} // namespace cli
425} // namespace yaze
The Rom class is used to load, save, and modify Rom data. This is a generic SNES ROM container and do...
Definition rom.h:28
absl::Status SetTile(const std::string &canvas_id, int x, int y, int tile_id)
Client for automating YAZE GUI through gRPC.
absl::StatusOr< AutomationResult > Type(const std::string &target, const std::string &text, bool clear_first=false, const std::string &widget_key="")
Type text into an input field.
absl::StatusOr< AutomationResult > Screenshot(const std::string &region="full", const std::string &format="PNG")
Capture a screenshot.
absl::Status Connect()
Connect to the test harness server.
absl::StatusOr< DiscoverWidgetsResult > DiscoverWidgets(const DiscoverWidgetsQuery &query)
absl::StatusOr< AutomationResult > Assert(const std::string &condition, const std::string &widget_key="")
Assert a GUI state condition.
absl::StatusOr< AutomationResult > Wait(const std::string &condition, int timeout_ms=5000, int poll_interval_ms=100, const std::string &widget_key="")
Wait for a condition to be met.
absl::StatusOr< AutomationResult > Click(const std::string &target, ClickType type=ClickType::kLeft, const std::string &widget_key="")
Click a GUI element.
absl::Status ValidateArgs(const resources::ArgumentParser &parser) override
Validate command arguments.
absl::Status Execute(Rom *rom, const resources::ArgumentParser &parser, resources::OutputFormatter &formatter) override
Execute the command business logic.
absl::Status ValidateArgs(const resources::ArgumentParser &parser) override
Validate command arguments.
absl::Status Execute(Rom *rom, const resources::ArgumentParser &parser, resources::OutputFormatter &formatter) override
Execute the command business logic.
absl::Status Execute(Rom *rom, const resources::ArgumentParser &parser, resources::OutputFormatter &formatter) override
Execute the command business logic.
std::string GetName() const
Get the command name.
absl::Status Execute(Rom *rom, const resources::ArgumentParser &parser, resources::OutputFormatter &formatter) override
Execute the command business logic.
absl::Status Execute(Rom *rom, const resources::ArgumentParser &parser, resources::OutputFormatter &formatter) override
Execute the command business logic.
absl::Status ValidateArgs(const resources::ArgumentParser &parser) override
Validate command arguments.
absl::Status Execute(Rom *rom, const resources::ArgumentParser &parser, resources::OutputFormatter &formatter) override
Execute the command business logic.
absl::Status Execute(Rom *rom, const resources::ArgumentParser &parser, resources::OutputFormatter &formatter) override
Execute the command business logic.
absl::Status ValidateArgs(const resources::ArgumentParser &parser) override
Validate command arguments.
Utility for parsing common CLI argument patterns.
std::optional< std::string > GetString(const std::string &name) const
Parse a named argument (e.g., –format=json or –format json)
bool HasFlag(const std::string &name) const
Check if a flag is present.
absl::StatusOr< int > GetInt(const std::string &name) const
Parse an integer argument (supports hex with 0x prefix)
Utility for consistent output formatting across commands.
void BeginArray(const std::string &key)
Begin an array.
void AddArrayItem(const std::string &item)
Add an item to current array.
void BeginObject(const std::string &title="")
Start a JSON object or text section.
void EndObject()
End a JSON object or text section.
void AddField(const std::string &key, const std::string &value)
Add a key-value pair.
ABSL_DECLARE_FLAG(std::string, gui_server_address)
void AddSelectorResolutionFields(const AutomationResult &result, resources::OutputFormatter &formatter)
absl::Status ValidateConditionOrWidget(const resources::ArgumentParser &parser)
absl::Status ConnectGuiClient(GuiAutomationClient *client)
absl::Status ValidateExactlyOneTargetOrWidget(const resources::ArgumentParser &parser)
absl::StatusOr< int > ReadIntArgOrDefault(const resources::ArgumentParser &parser, const std::string &arg_name, int default_value)
bool ParseHexString(absl::string_view str, int *out)
Definition hex_util.h:17
ClickType
Type of click action to perform.
Result of a GUI automation action.