diff --git a/CMakeLists.txt b/CMakeLists.txt index 247bf29..772391c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -77,6 +77,7 @@ add_library(miracle-wm-implementation src/scratchpad.h src/scratchpad.cpp src/compositor_state.h src/compositor_state.cpp src/math_helpers.h + src/command_controller.h ) add_executable(miracle-wm diff --git a/src/command_controller.h b/src/command_controller.h new file mode 100644 index 0000000..a899b51 --- /dev/null +++ b/src/command_controller.h @@ -0,0 +1,86 @@ +/** +Copyright (C) 2024 Matthew Kosarek + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +**/ + +#ifndef MIRACLE_WM_COMMAND_CONTROLLER_H +#define MIRACLE_WM_COMMAND_CONTROLLER_H + +#include "direction.h" +#include "output.h" +#include +#include + +namespace miracle +{ + +/// Abstract interface used to send commands to miracle. +class CommandController +{ +public: + virtual bool try_request_horizontal() = 0; + virtual bool try_request_vertical() = 0; + virtual bool try_toggle_layout(bool cycle_through_all) = 0; + virtual void try_toggle_resize_mode() = 0; + virtual bool try_resize(Direction direction, int pixels) = 0; + virtual bool try_set_size(std::optional const& width, std::optional const& height) = 0; + virtual bool try_move(Direction direction) = 0; + virtual bool try_move_by(Direction direction, int pixels) = 0; + virtual bool try_move_to(int x, int y) = 0; + virtual bool try_select(Direction direction) = 0; + virtual bool try_select_parent() = 0; + virtual bool try_select_child() = 0; + virtual bool try_select_floating() = 0; + virtual bool try_select_tiling() = 0; + virtual bool try_select_toggle() = 0; + virtual bool try_close_window() = 0; + virtual bool quit() = 0; + virtual bool try_toggle_fullscreen() = 0; + virtual bool select_workspace(int number, bool back_and_forth = true) = 0; + virtual bool select_workspace(std::string const& name, bool back_and_forth) = 0; + virtual bool next_workspace() = 0; + virtual bool prev_workspace() = 0; + virtual bool back_and_forth_workspace() = 0; + virtual bool next_workspace_on_output(Output const&) = 0; + virtual bool prev_workspace_on_output(Output const&) = 0; + virtual bool move_active_to_workspace(int number, bool back_and_forth = true) = 0; + virtual bool move_active_to_workspace_named(std::string const&, bool back_and_forth) = 0; + virtual bool move_active_to_next_workspace() = 0; + virtual bool move_active_to_prev_workspace() = 0; + virtual bool move_active_to_back_and_forth() = 0; + virtual bool move_to_scratchpad() = 0; + virtual bool show_scratchpad() = 0; + virtual bool toggle_floating() = 0; + virtual bool toggle_pinned_to_workspace() = 0; + virtual bool set_is_pinned(bool) = 0; + virtual bool toggle_tabbing() = 0; + virtual bool toggle_stacking() = 0; + virtual bool set_layout(LayoutScheme scheme) = 0; + virtual bool set_layout_default() = 0; + virtual void move_cursor_to_output(Output const&) = 0; + virtual bool try_select_next_output() = 0; + virtual bool try_select_prev_output() = 0; + virtual bool try_select_output(Direction direction) = 0; + virtual bool try_select_output(std::vector const& names) = 0; + virtual bool try_move_active_to_output(Direction direction) = 0; + virtual bool try_move_active_to_current() = 0; + virtual bool try_move_active_to_primary() = 0; + virtual bool try_move_active_to_nonprimary() = 0; + virtual bool try_move_active_to_next() = 0; + virtual bool try_move_active(std::vector const& names) = 0; +}; +} + +#endif // MIRACLE_WM_COMMAND_CONTROLLER_H diff --git a/src/compositor_state.h b/src/compositor_state.h index 1cbc6d7..622e1a8 100644 --- a/src/compositor_state.h +++ b/src/compositor_state.h @@ -51,6 +51,7 @@ public: mir::geometry::Point cursor_position; uint32_t modifiers = 0; bool has_clicked_floating_window = false; + std::vector> output_list; [[nodiscard]] std::shared_ptr active() const; diff --git a/src/ipc.cpp b/src/ipc.cpp index dda2a43..e8c0754 100644 --- a/src/ipc.cpp +++ b/src/ipc.cpp @@ -225,12 +225,10 @@ json mode_event_to_json(WindowManagerMode mode) Ipc::Ipc(miral::MirRunner& runner, miracle::WorkspaceManager& workspace_manager, Policy& policy, - std::shared_ptr const& queue, IpcCommandExecutor& executor, std::shared_ptr const& config) : workspace_manager { workspace_manager }, policy { policy }, - queue { queue }, executor { executor }, config { config } { @@ -528,14 +526,20 @@ void Ipc::handle_command(miracle::Ipc::IpcClient& client, uint32_t payload_lengt { mir::log_debug("Processing i3_command: %s", buf); auto result = parse_i3_command(buf); - if (result) + if (result.success) { const std::string msg = "[{\"success\": true}]"; send_reply(client, payload_type, msg); } else { - const std::string msg = "[{\"success\": false, \"parse_error\": true}]"; + json j = json::array(); + j.push_back({ + { "success", false }, + { "parse_error", result.parse_error }, + { "error", result.error }, + }); + const std::string msg = to_string(j); send_reply(client, payload_type, msg); } break; @@ -759,26 +763,9 @@ void Ipc::handle_writeable(miracle::Ipc::IpcClient& client) client.write_buffer_len = 0; } -bool Ipc::parse_i3_command(const char* command) +IpcValidationResult Ipc::parse_i3_command(const char* command) { - { - std::unique_lock lock(pending_commands_mutex); - IpcCommandParser parser(command); - pending_commands.push_back(parser.parse()); - } - - queue->enqueue(this, [&]() - { - size_t num_processed = 0; - { - std::shared_lock lock(pending_commands_mutex); - for (auto const& c : pending_commands) - executor.process(c); - num_processed = pending_commands.size(); - } - - std::unique_lock lock(pending_commands_mutex); - pending_commands.erase(pending_commands.begin(), pending_commands.begin() + num_processed); - }); - return true; + IpcCommandParser parser(command); + auto const pending_commands = parser.parse(); + return executor.process(pending_commands); } diff --git a/src/ipc.h b/src/ipc.h index 2036303..a2f800e 100644 --- a/src/ipc.h +++ b/src/ipc.h @@ -24,7 +24,6 @@ along with this program. If not, see . #include "workspace_manager.h" #include "workspace_observer.h" #include -#include #include #include #include @@ -84,7 +83,6 @@ public: Ipc(miral::MirRunner& runner, WorkspaceManager&, Policy& policy, - std::shared_ptr const&, IpcCommandExecutor&, std::shared_ptr const&); ~Ipc(); @@ -113,9 +111,6 @@ private: std::unique_ptr socket_handle; sockaddr_un* ipc_sockaddr = nullptr; std::vector clients; - std::vector pending_commands; - mutable std::shared_mutex pending_commands_mutex; - std::shared_ptr queue; IpcCommandExecutor& executor; std::shared_ptr config; @@ -124,7 +119,7 @@ private: void handle_command(IpcClient& client, uint32_t payload_length, IpcType payload_type); void send_reply(IpcClient& client, IpcType command_type, std::string const& payload); void handle_writeable(IpcClient& client); - bool parse_i3_command(const char* command); + IpcValidationResult parse_i3_command(const char* command); }; } diff --git a/src/ipc_command_executor.cpp b/src/ipc_command_executor.cpp index a1bb2de..80412e2 100644 --- a/src/ipc_command_executor.cpp +++ b/src/ipc_command_executor.cpp @@ -27,6 +27,7 @@ along with this program. If not, see . #include "window_helpers.h" #define MIR_LOG_COMPONENT "miracle" +#include #include #include @@ -100,7 +101,7 @@ protected: } IpcCommandExecutor::IpcCommandExecutor( - miracle::Policy& policy, + CommandController& policy, WorkspaceManager& workspace_manager, CompositorState const& state, AutoRestartingLauncher& launcher, @@ -113,49 +114,57 @@ IpcCommandExecutor::IpcCommandExecutor( { } -void IpcCommandExecutor::process(miracle::IpcParseResult const& command_list) +IpcValidationResult IpcCommandExecutor::process(miracle::IpcParseResult const& command_list) { + IpcValidationResult result; for (auto const& command : command_list.commands) { switch (command.type) { case IpcCommandType::exec: - process_exec(command, command_list); + result = process_exec(command, command_list); break; case IpcCommandType::split: - process_split(command, command_list); + result = process_split(command, command_list); break; case IpcCommandType::focus: - process_focus(command, command_list); + result = process_focus(command, command_list); break; case IpcCommandType::move: - process_move(command, command_list); + result = process_move(command, command_list); break; case IpcCommandType::sticky: - process_sticky(command, command_list); + result = process_sticky(command, command_list); break; case IpcCommandType::exit: policy.quit(); + result = {}; break; case IpcCommandType::input: - process_input(command, command_list); + result = process_input(command, command_list); break; case IpcCommandType::workspace: - process_workspace(command, command_list); + result = process_workspace(command, command_list); break; case IpcCommandType::layout: - process_layout(command, command_list); + result = process_layout(command, command_list); break; case IpcCommandType::scratchpad: - process_scratchpad(command, command_list); + result = process_scratchpad(command, command_list); break; case IpcCommandType::resize: - process_resize(command, command_list); + result = process_resize(command, command_list); break; default: + result = parse_error(std::format("Unsupported command type: %d", (int)command.type)); break; } + + if (!result.success) + return result; } + + return {}; } miral::Window IpcCommandExecutor::get_window_meeting_criteria(IpcParseResult const& command_list) @@ -176,23 +185,27 @@ miral::Window IpcCommandExecutor::get_window_meeting_criteria(IpcParseResult con return miral::Window {}; } -void IpcCommandExecutor::process_exec(miracle::IpcCommand const& command, miracle::IpcParseResult const& command_list) +IpcValidationResult IpcCommandExecutor::parse_error(std::string error) +{ + mir::log_error("Parse Error: %s", error.c_str()); + return { + .success = false, + .parse_error = true, + .error = std::move(error) + }; +} + +IpcValidationResult IpcCommandExecutor::process_exec(miracle::IpcCommand const& command, miracle::IpcParseResult const& command_list) { if (command.arguments.empty()) - { - mir::log_warning("process_exec: no arguments were supplied"); - return; - } + return parse_error("process_exec: no arguments were supplied"); bool no_startup_id = false; if (!command.options.empty() && command.options[0] == "--no-startup-id") no_startup_id = true; if (command.arguments.empty()) - { - mir::log_warning("process_exec: argument does not have a command to run"); - return; - } + return parse_error("process_exec: argument does not have a command to run"); std::string exec_cmd; for (auto const& arg : command.arguments) @@ -202,15 +215,13 @@ void IpcCommandExecutor::process_exec(miracle::IpcCommand const& command, miracl StartupApp app { exec_cmd, false, no_startup_id }; launcher.launch(app); + return {}; } -void IpcCommandExecutor::process_split(miracle::IpcCommand const& command, miracle::IpcParseResult const& command_list) +IpcValidationResult IpcCommandExecutor::process_split(miracle::IpcCommand const& command, miracle::IpcParseResult const& command_list) { if (command.arguments.empty()) - { - mir::log_warning("process_split: no arguments were supplied"); - return; - } + return parse_error("process_split: no arguments were supplied"); if (command.arguments.front() == "vertical") { @@ -226,27 +237,27 @@ void IpcCommandExecutor::process_split(miracle::IpcCommand const& command, mirac } else { - mir::log_warning("process_split: unknown argument %s", command.arguments.front().c_str()); - return; + return parse_error(std::format("process_split: unknown argument {}", command.arguments.front().c_str())); } + + return {}; } -void IpcCommandExecutor::process_focus(IpcCommand const& command, IpcParseResult const& command_list) +IpcValidationResult IpcCommandExecutor::process_focus(IpcCommand const& command, IpcParseResult const& command_list) { // https://i3wm.org/docs/userguide.html#_focusing_moving_containers if (command.arguments.empty()) { if (command_list.scope.empty()) { - mir::log_warning("Focus command expected scope but none was provided"); - return; + return parse_error("Focus command expected scope but none was provided"); } auto window = get_window_meeting_criteria(command_list); if (window) window_controller.select_active_window(window); - return; + return {}; } auto const& arg = command.arguments.front(); @@ -254,8 +265,7 @@ void IpcCommandExecutor::process_focus(IpcCommand const& command, IpcParseResult { if (command_list.scope.empty()) { - mir::log_warning("Focus 'workspace' command expected scope but none was provided"); - return; + return parse_error("Focus 'workspace' command expected scope but none was provided"); } auto window = get_window_meeting_criteria(command_list); @@ -279,13 +289,10 @@ void IpcCommandExecutor::process_focus(IpcCommand const& command, IpcParseResult { auto container = state.active(); if (!container) - return; + return parse_error("Active container does nto exist"); if (container->get_type() != ContainerType::leaf) - { - mir::log_warning("Cannot focus prev when a tiling window is not selected"); - return; - } + return parse_error("Cannot focus prev when a tiling window is not selected"); if (auto parent = Container::as_parent(container->get_parent().lock())) { @@ -301,13 +308,10 @@ void IpcCommandExecutor::process_focus(IpcCommand const& command, IpcParseResult { auto container = state.active(); if (!container) - return; + return parse_error("No container is selected"); if (container->get_type() != ContainerType::leaf) - { - mir::log_warning("Cannot focus prev when a tiling window is not selected"); - return; - } + return parse_error("Cannot focus prev when a tiling window is not selected"); if (auto parent = Container::as_parent(container->get_parent().lock())) { @@ -328,10 +332,7 @@ void IpcCommandExecutor::process_focus(IpcCommand const& command, IpcParseResult else if (arg == "output") { if (command.arguments.size() < 2) - { - mir::log_error("process_focus: 'focus output' must have more than two arguments"); - return; - } + return parse_error("process_focus: 'focus output' must have more than two arguments"); auto const& arg1 = command.arguments[1]; if (arg1 == "next") @@ -352,6 +353,8 @@ void IpcCommandExecutor::process_focus(IpcCommand const& command, IpcParseResult policy.try_select_output(names); } } + + return {}; } namespace @@ -385,21 +388,15 @@ bool parse_move_distance(std::vector const& arguments, int& index, } } -void IpcCommandExecutor::process_move(IpcCommand const& command, IpcParseResult const& command_list) +IpcValidationResult IpcCommandExecutor::process_move(IpcCommand const& command, IpcParseResult const& command_list) { - auto active_output = policy.get_active_output(); + auto const& active_output = state.active_output; if (!active_output) - { - mir::log_warning("process_move: output is not set"); - return; - } + return parse_error("process_move: output is not set"); // https://i3wm.org/docs/userguide.html#_focusing_moving_containers if (command.arguments.empty()) - { - mir::log_warning("process_move: move command expects arguments"); - return; - } + return parse_error("process_move: move command expects arguments"); int index = 0; auto const& arg0 = command.arguments[index++]; @@ -428,15 +425,12 @@ void IpcCommandExecutor::process_move(IpcCommand const& command, IpcParseResult else if (arg0 == "position") { if (command.arguments.size() < 2) - { - mir::log_error("process_move: move position expected a third argument"); - return; - } + return parse_error("process_move: move position expected a third argument"); auto const& arg1 = command.arguments[index++]; if (arg1 == "center") { - auto active = policy.get_state().active().get(); + auto active = state.active().get(); auto area = active_output->get_area(); float x = (float)area.size.width.as_int() / 2.f - (float)active->get_visible_area().size.width.as_int() / 2.f; float y = (float)area.size.height.as_int() / 2.f - (float)active->get_visible_area().size.height.as_int() / 2.f; @@ -444,7 +438,7 @@ void IpcCommandExecutor::process_move(IpcCommand const& command, IpcParseResult } else if (arg1 == "mouse") { - auto const& position = policy.get_cursor_position(); + auto const& position = state.cursor_position; policy.try_move_to((int)position.x.as_int(), (int)position.y.as_int()); } else @@ -453,39 +447,28 @@ void IpcCommandExecutor::process_move(IpcCommand const& command, IpcParseResult int move_distance_y; if (!parse_move_distance(command.arguments, index, total_size, move_distance_x)) - { - mir::log_error("process_move: move position : unable to parse x"); - return; - } + return parse_error("process_move: move position : unable to parse x"); if (!parse_move_distance(command.arguments, index, total_size, move_distance_y)) - { - mir::log_error("process_move: move position : unable to parse y"); - return; - } + return parse_error("process_move: move position : unable to parse y"); policy.try_move_to(move_distance_x, move_distance_y); } - return; + + return {}; } else if (arg0 == "absolute") { auto const& arg1 = command.arguments[index++]; auto const& arg2 = command.arguments[index++]; if (arg1 != "position") - { - mir::log_error("process_move: move [absolute] ... expected 'position' as the third argument"); - return; - } + return parse_error("process_move: move [absolute] ... expected 'position' as the third argument"); if (arg2 != "center") - { - mir::log_error("process_move: move absolute position ... expected 'center' as the third argument"); - return; - } + return parse_error("process_move: move absolute position ... expected 'center' as the third argument"); float x = 0, y = 0; - for (auto const& output : policy.get_output_list()) + for (auto const& output : state.output_list) { auto area = output->get_area(); float end_x = (float)area.size.width.as_int() + (float)area.top_left.x.as_int(); @@ -496,30 +479,24 @@ void IpcCommandExecutor::process_move(IpcCommand const& command, IpcParseResult y = end_y; } - auto active = policy.get_state().active(); + auto active = state.active(); float x_pos = x / 2.f - (float)active->get_visible_area().size.width.as_int() / 2.f; float y_pos = y / 2.f - (float)active->get_visible_area().size.height.as_int() / 2.f; policy.try_move_to((int)x_pos, (int)y_pos); - return; + return {}; } else if (arg0 == "window" || arg0 == "container") { auto const back_and_forth = std::find(command.options.begin(), command.options.end(), "--no-auto-back-and-forth") == command.options.end(); auto const& arg1 = command.arguments[index++]; if (arg1 != "to") - { - mir::log_error("process_move: expected 'to' after 'move window/container ...'"); - return; - } + return parse_error("process_move: expected 'to' after 'move window/container ...'"); auto const& arg2 = command.arguments[index++]; if (arg2 == "workspace") { if (command.arguments.size() <= 3) - { - mir::log_error("process_move: expected another argument after 'move container/window to output...'"); - return; - } + return parse_error("process_move: expected another argument after 'move container/window to output...'"); auto const& arg3 = command.arguments[index++]; int number = -1; @@ -527,17 +504,17 @@ void IpcCommandExecutor::process_move(IpcCommand const& command, IpcParseResult { // TODO: Do we need to care about the name here? policy.move_active_to_workspace(number, back_and_forth); - return; + return {}; } else if (arg3 == "next") { policy.move_active_to_next_workspace(); - return; + return {}; } else if (arg3 == "prev") { policy.move_active_to_prev_workspace(); - return; + return {}; } else if (arg3 == "current") { @@ -546,12 +523,12 @@ void IpcCommandExecutor::process_move(IpcCommand const& command, IpcParseResult else if (arg3 == "back_and_forth") { policy.move_active_to_back_and_forth(); - return; + return {}; } else { policy.move_active_to_workspace_named(arg3, back_and_forth); - return; + return {}; } } else if (arg2 == "output") @@ -559,7 +536,7 @@ void IpcCommandExecutor::process_move(IpcCommand const& command, IpcParseResult if (command.arguments.size() <= 3) { mir::log_error("process_move: expected another argument after 'move container/window to output...'"); - return; + return {}; } auto const& arg3 = command.arguments[index++]; @@ -589,7 +566,7 @@ void IpcCommandExecutor::process_move(IpcCommand const& command, IpcParseResult else if (arg0 == "scratchpad") { policy.move_to_scratchpad(); - return; + return {}; } if (direction < Direction::MAX) @@ -600,15 +577,14 @@ void IpcCommandExecutor::process_move(IpcCommand const& command, IpcParseResult else policy.try_move(direction); } + + return {}; } -void IpcCommandExecutor::process_sticky(IpcCommand const& command, IpcParseResult const& command_list) +IpcValidationResult IpcCommandExecutor::process_sticky(IpcCommand const& command, IpcParseResult const& command_list) { if (command.arguments.empty()) - { - mir::log_warning("process_sticky: expects arguments"); - return; - } + return parse_error("process_sticky: expects arguments"); auto const& arg0 = command.arguments[0]; if (arg0 == "enable") @@ -619,10 +595,11 @@ void IpcCommandExecutor::process_sticky(IpcCommand const& command, IpcParseResul policy.toggle_pinned_to_workspace(); else mir::log_warning("process_sticky: unknown arguments: %s", arg0.c_str()); + + return {}; } -// This command will be -void IpcCommandExecutor::process_input(IpcCommand const& command, IpcParseResult const& command_list) +IpcValidationResult IpcCommandExecutor::process_input(IpcCommand const& command, IpcParseResult const& command_list) { // Payloads appear in the following format: // [type:X, xkb_Y, Z] @@ -630,19 +607,13 @@ void IpcCommandExecutor::process_input(IpcCommand const& command, IpcParseResult // and Z is the value of that variable. Z may not be included at all, in which // case the variable is set to the default. if (command.arguments.size() < 2) - { - mir::log_warning("process_input: expects at least 2 arguments"); - return; - } + return parse_error("process_input: expects at least 2 arguments"); const char* const TYPE_PREFIX = "type:"; const size_t TYPE_PREFIX_LEN = strlen(TYPE_PREFIX); std::string_view type_str = command.arguments[0]; if (!type_str.starts_with("type:")) - { - mir::log_warning("process_input: 'type' string is misformatted: %s", command.arguments[0].c_str()); - return; - } + return parse_error(std::format("process_input: 'type' string is misformatted: {}", command.arguments[0].c_str())); std::string_view type = type_str.substr(TYPE_PREFIX_LEN); assert(type == "keyboard"); @@ -651,10 +622,7 @@ void IpcCommandExecutor::process_input(IpcCommand const& command, IpcParseResult const char* const XKB_PREFIX = "xkb_"; const size_t XKB_PREFIX_LEN = strlen(XKB_PREFIX); if (!xkb_str.starts_with(XKB_PREFIX)) - { - mir::log_warning("process_input: 'xkb' string is misformatted: %s", command.arguments[1].c_str()); - return; - } + return parse_error(std::format("process_input: 'xkb' string is misformatted: {}", command.arguments[1].c_str())); std::string_view xkb_variable_name = xkb_str.substr(XKB_PREFIX_LEN); assert(xkb_variable_name == "model" @@ -674,18 +642,16 @@ void IpcCommandExecutor::process_input(IpcCommand const& command, IpcParseResult } else { - mir::log_warning("process_input: > 3 arguments were provided but only <= 3 are expected"); - return; + return parse_error("process_input: > 3 arguments were provided but only <= 3 are expected"); } + + return {}; } -void IpcCommandExecutor::process_workspace(IpcCommand const& command, IpcParseResult const& command_list) +IpcValidationResult IpcCommandExecutor::process_workspace(IpcCommand const& command, IpcParseResult const& command_list) { if (command.arguments.empty()) - { - mir::log_error("process_workspace: no arguments provided"); - return; - } + return parse_error("process_workspace: no arguments provided"); std::string const& arg0 = command.arguments[0]; if (arg0 == "next") @@ -694,14 +660,14 @@ void IpcCommandExecutor::process_workspace(IpcCommand const& command, IpcParseRe policy.prev_workspace(); else if (arg0 == "next_on_output") { - if (auto const* output = policy.get_active_output()) + if (auto const& output = state.active_output) policy.next_workspace_on_output(*output); else mir::log_error("process_workspace: next_on_output has no output to go next on"); } else if (arg0 == "prev_on_output") { - if (auto const* output = policy.get_active_output()) + if (auto const& output = state.active_output) policy.prev_workspace_on_output(*output); else mir::log_error("process_workspace: prev_on_output has no output to go prev on"); @@ -722,7 +688,7 @@ void IpcCommandExecutor::process_workspace(IpcCommand const& command, IpcParseRe if (command.arguments.size() < 3) { policy.select_workspace(number, back_and_forth); - return; + return {}; } // We have "workspace number " @@ -735,9 +701,11 @@ void IpcCommandExecutor::process_workspace(IpcCommand const& command, IpcParseRe policy.select_workspace(*arg1, back_and_forth); } } + + return {}; } -void IpcCommandExecutor::process_layout(IpcCommand const& command, IpcParseResult const& command_list) +IpcValidationResult IpcCommandExecutor::process_layout(IpcCommand const& command, IpcParseResult const& command_list) { // https://i3wm.org/docs/userguide.html#manipulating_layout std::string const& arg0 = command.arguments[0]; @@ -754,10 +722,7 @@ void IpcCommandExecutor::process_layout(IpcCommand const& command, IpcParseResul else if (arg0 == "toggle") { if (command.arguments.size() == 1) - { - mir::log_error("process_layout: expected argument after 'layout toggle ...'"); - return; - } + return parse_error("process_layout: expected argument after 'layout toggle ...'"); if (command.arguments.size() == 2) { @@ -767,18 +732,15 @@ void IpcCommandExecutor::process_layout(IpcCommand const& command, IpcParseResul else if (arg1 == "all") policy.try_toggle_layout(true); else - mir::log_error("process_layout: expected split/all after 'layout toggle X'"); + return parse_error("process_layout: expected split/all after 'layout toggle X'"); - return; + return {}; } else { - auto container = policy.get_state().active(); + auto container = state.active(); if (!container) - { - mir::log_error("process_layout: container unavailable"); - return; - } + return parse_error("process_layout: container unavailable"); auto current_type = container->get_layout(); size_t index = 0; @@ -844,24 +806,21 @@ void IpcCommandExecutor::process_layout(IpcCommand const& command, IpcParseResul policy.set_layout(LayoutScheme::horizontal); } } + + return {}; } -void IpcCommandExecutor::process_scratchpad(IpcCommand const& command, IpcParseResult const& command_list) +IpcValidationResult IpcCommandExecutor::process_scratchpad(IpcCommand const& command, IpcParseResult const& command_list) { if (command.arguments.empty()) - { - mir::log_error("process_scratchpad: no arguments provided"); - return; - } + return parse_error("process_scratchpad: no arguments provided"); std::string const& arg0 = command.arguments[0]; if (arg0 != "show") - { - mir::log_error("process_scratchpad: all scratchpad commands must be 'scratchpad show'"); - return; - } + return parse_error("process_scratchpad: all scratchpad commands must be 'scratchpad show'"); policy.show_scratchpad(); + return {}; } namespace @@ -869,6 +828,7 @@ namespace struct ResizeAdjust { bool success = true; + std::string error; Direction direction = Direction::MAX; int first = 0; int second = 0; @@ -877,14 +837,11 @@ struct ResizeAdjust ResizeAdjust parse_resize(CompositorState const& state, ArgumentsIndexer& indexer, int multiplier) { if (!indexer.next()) - { - mir::log_error("process_resize: expected argument after 'resize grow'"); - return { false }; - } + return { .success = false, .error = "process_resize: expected argument after 'resize grow'" }; auto const& container = state.active(); if (!container) - return { .success = false }; + return { .success = false, .error = "No container is selcted" }; ResizeAdjust result; if (indexer.current() == "width" || indexer.current() == "horizontal") @@ -913,9 +870,7 @@ ResizeAdjust parse_resize(CompositorState const& state, ArgumentsIndexer& indexe } else { - mir::log_error("Unknown direction value: %s", indexer.current().c_str()); - result.success = false; - return result; + return { .success = false, .error = std::format("Unknown direction value: {}", indexer.current().c_str()) }; } int available_space = 0; @@ -932,19 +887,12 @@ ResizeAdjust parse_resize(CompositorState const& state, ArgumentsIndexer& indexe int first = 0; if (!indexer.parse_move_distance(available_space, first)) - { - result.success = false; - return result; - } + return { .success = false, .error = "cannot parse the first value" }; if (indexer.next()) { if (indexer.current() != "or") - { - mir::log_error("parse_resize: expected 'or'"); - result.success = false; - return result; - } + return { .success = false, .error = "expected 'or' after first value" }; } int second = 0; @@ -957,6 +905,7 @@ ResizeAdjust parse_resize(CompositorState const& state, ArgumentsIndexer& indexe struct SetResizeResult { bool success = true; + std::string error; std::optional width; std::optional height; }; @@ -965,21 +914,15 @@ SetResizeResult parse_set_resize(CompositorState const& state, ArgumentsIndexer& { auto const& container = state.active(); if (!container) - return { .success = false }; + return { .success = false, .error = "Container is not selected" }; SetResizeResult result; int width = 0, height = 0; if (!indexer.parse_move_distance(container->get_output()->get_area().size.width.as_value(), width)) - { - mir::log_error("parse_set_resize: invalid width"); - return { .success = false }; - } + return { .success = false, .error = "invalid width" }; if (!indexer.parse_move_distance(container->get_output()->get_area().size.height.as_value(), height)) - { - mir::log_error("parse_set_resize: invalid height"); - return { .success = false }; - } + return { .success = false, .error = "invalid height" }; if (width != 0) result.width = width; @@ -990,13 +933,10 @@ SetResizeResult parse_set_resize(CompositorState const& state, ArgumentsIndexer& } } -void IpcCommandExecutor::process_resize(IpcCommand const& command, IpcParseResult const& command_list) +IpcValidationResult IpcCommandExecutor::process_resize(IpcCommand const& command, IpcParseResult const& command_list) { if (command.arguments.empty()) - { - mir::log_error("process_resize: no arguments provided"); - return; - } + return parse_error("process_resize: no arguments provided"); ArgumentsIndexer indexer(command); auto const& arg0 = indexer.current(); @@ -1004,7 +944,7 @@ void IpcCommandExecutor::process_resize(IpcCommand const& command, IpcParseResul { auto adjust = parse_resize(state, indexer, 1); if (!adjust.success) - return; + return parse_error(adjust.error); policy.try_resize(adjust.direction, adjust.first); } @@ -1012,7 +952,7 @@ void IpcCommandExecutor::process_resize(IpcCommand const& command, IpcParseResul { auto adjust = parse_resize(state, indexer, -1); if (!adjust.success) - return; + return parse_error(adjust.error); policy.try_resize(adjust.direction, adjust.first); } @@ -1020,13 +960,12 @@ void IpcCommandExecutor::process_resize(IpcCommand const& command, IpcParseResul { auto result = parse_set_resize(state, indexer); if (!result.success) - return; + return parse_error(result.error); policy.try_set_size(result.width, result.height); } else - { - mir::log_error("process_resize: unexpected argument: %s", arg0.c_str()); - return; - } + return parse_error(std::format("process_resize: unexpected argument: {}", arg0.c_str())); + + return {}; } \ No newline at end of file diff --git a/src/ipc_command_executor.h b/src/ipc_command_executor.h index eda3d9b..d8af147 100644 --- a/src/ipc_command_executor.h +++ b/src/ipc_command_executor.h @@ -25,42 +25,51 @@ along with this program. If not, see . namespace miracle { -class Policy; +class CommandController; class WorkspaceManager; class AutoRestartingLauncher; class WindowController; +struct IpcValidationResult +{ + bool success = true; + bool parse_error = false; + std::string error; +}; + /// Processes all commands coming from i3 IPC. This class is mostly for organizational /// purposes, as a lot of logic is associated with processing these operations. class IpcCommandExecutor { public: IpcCommandExecutor( - Policy&, + CommandController&, WorkspaceManager&, CompositorState const&, AutoRestartingLauncher&, WindowController&); - void process(IpcParseResult const&); + IpcValidationResult process(IpcParseResult const&); private: - Policy& policy; + CommandController& policy; WorkspaceManager& workspace_manager; CompositorState const& state; AutoRestartingLauncher& launcher; WindowController& window_controller; miral::Window get_window_meeting_criteria(IpcParseResult const&); - void process_exec(IpcCommand const&, IpcParseResult const&); - void process_split(IpcCommand const&, IpcParseResult const&); - void process_focus(IpcCommand const&, IpcParseResult const&); - void process_move(IpcCommand const&, IpcParseResult const&); - void process_sticky(IpcCommand const&, IpcParseResult const&); - void process_input(IpcCommand const&, IpcParseResult const&); - void process_workspace(IpcCommand const&, IpcParseResult const&); - void process_layout(IpcCommand const&, IpcParseResult const&); - void process_scratchpad(IpcCommand const&, IpcParseResult const&); - void process_resize(IpcCommand const&, IpcParseResult const&); + IpcValidationResult process_exec(IpcCommand const&, IpcParseResult const&); + IpcValidationResult process_split(IpcCommand const&, IpcParseResult const&); + IpcValidationResult process_focus(IpcCommand const&, IpcParseResult const&); + IpcValidationResult process_move(IpcCommand const&, IpcParseResult const&); + IpcValidationResult process_sticky(IpcCommand const&, IpcParseResult const&); + IpcValidationResult process_input(IpcCommand const&, IpcParseResult const&); + IpcValidationResult process_workspace(IpcCommand const&, IpcParseResult const&); + IpcValidationResult process_layout(IpcCommand const&, IpcParseResult const&); + IpcValidationResult process_scratchpad(IpcCommand const&, IpcParseResult const&); + IpcValidationResult process_resize(IpcCommand const&, IpcParseResult const&); + + IpcValidationResult parse_error(std::string error); }; } // miracle diff --git a/src/policy.cpp b/src/policy.cpp index a568d48..90e22fa 100644 --- a/src/policy.cpp +++ b/src/policy.cpp @@ -110,7 +110,7 @@ Policy::Policy( window_controller(tools, animator, state), i3_command_executor(*this, workspace_manager, compositor_state, external_client_launcher, window_controller), surface_tracker { surface_tracker }, - ipc { std::make_shared(runner, workspace_manager, *this, server.the_main_loop(), i3_command_executor, config) }, + ipc { std::make_shared(runner, workspace_manager, *this, i3_command_executor, config) }, scratchpad_(window_controller, state), self { std::make_shared(*this) } { @@ -257,7 +257,7 @@ bool Policy::handle_pointer_event(MirPointerEvent const* event) state.cursor_position = { x, y }; // Select the output first - for (auto const& output : output_list) + for (auto const& output : state.output_list) { if (output->point_is_in_output(static_cast(x), static_cast(y))) { @@ -360,10 +360,10 @@ void Policy::advise_new_window(miral::WindowInfo const& window_info) { mir::log_warning("create_container: output unavailable"); auto window = window_info.window(); - if (!output_list.empty()) + if (!state.output_list.empty()) { // Our output is gone! Let's try to add it to a different output - output_list.front()->add_immediately(window); + state.output_list.front()->add_immediately(window); } else { @@ -505,7 +505,7 @@ void Policy::advise_output_create(miral::Output const& output) auto output_content = std::make_shared( output, workspace_manager, output.extents(), window_manager_tools, floating_window_manager, state, config, window_controller, animator); - output_list.push_back(output_content); + state.output_list.push_back(output_content); workspace_manager.request_first_available_workspace(output_content.get()); if (state.active_output == nullptr) { @@ -527,7 +527,7 @@ void Policy::advise_output_create(miral::Output const& output) void Policy::advise_output_update(miral::Output const& updated, miral::Output const& original) { - for (auto& output : output_list) + for (auto& output : state.output_list) { if (output->get_output().is_same_output(original)) { @@ -539,7 +539,7 @@ void Policy::advise_output_update(miral::Output const& updated, miral::Output co void Policy::advise_output_delete(miral::Output const& output) { - for (auto it = output_list.begin(); it != output_list.end(); it++) + for (auto it = state.output_list.begin(); it != state.output_list.end(); it++) { auto other_output = *it; if (other_output->get_output().is_same_output(output)) @@ -556,8 +556,8 @@ void Policy::advise_output_delete(miral::Output const& output) workspace_manager.delete_workspace(w); }; - output_list.erase(it); - if (output_list.empty()) + state.output_list.erase(it); + if (state.output_list.empty()) { // All nodes should become orphaned for (auto& window : other_output->collect_all_windows()) @@ -573,7 +573,7 @@ void Policy::advise_output_delete(miral::Output const& output) } else { - state.active_output = output_list.front(); + state.active_output = state.output_list.front(); state.active_output->set_is_active(true); for (auto& window : other_output->collect_all_windows()) { @@ -663,7 +663,7 @@ mir::geometry::Rectangle Policy::confirm_inherited_move( void Policy::advise_application_zone_create(miral::Zone const& application_zone) { - for (auto const& output : output_list) + for (auto const& output : state.output_list) { output->advise_application_zone_create(application_zone); } @@ -671,7 +671,7 @@ void Policy::advise_application_zone_create(miral::Zone const& application_zone) void Policy::advise_application_zone_update(miral::Zone const& updated, miral::Zone const& original) { - for (auto const& output : output_list) + for (auto const& output : state.output_list) { output->advise_application_zone_update(updated, original); } @@ -679,7 +679,7 @@ void Policy::advise_application_zone_update(miral::Zone const& updated, miral::Z void Policy::advise_application_zone_delete(miral::Zone const& application_zone) { - for (auto const& output : output_list) + for (auto const& output : state.output_list) { output->advise_application_zone_delete(application_zone); } @@ -1315,15 +1315,15 @@ void Policy::move_cursor_to_output(Output const& output) bool Policy::try_select_next_output() { - for (size_t i = 0; i < output_list.size(); i++) + for (size_t i = 0; i < state.output_list.size(); i++) { - if (output_list[i] == state.active_output) + if (state.output_list[i] == state.active_output) { size_t j = i + 1; - if (j == output_list.size()) + if (j == state.output_list.size()) j = 0; - move_cursor_to_output(*output_list[j]); + move_cursor_to_output(*state.output_list[j]); return true; } } @@ -1333,15 +1333,15 @@ bool Policy::try_select_next_output() bool Policy::try_select_prev_output() { - for (int i = output_list.size() - 1; i >= 0; i++) + for (int i = state.output_list.size() - 1; i >= 0; i++) { - if (output_list[i] == state.active_output) + if (state.output_list[i] == state.active_output) { size_t j = i - 1; if (j < 0) - j = output_list.size() - 1; + j = state.output_list.size() - 1; - move_cursor_to_output(*output_list[j]); + move_cursor_to_output(*state.output_list[j]); return true; } } @@ -1353,7 +1353,7 @@ std::shared_ptr const& Policy::_next_output_in_direction(Direction direc { auto const& active = state.active_output; auto const& active_area = active->get_area(); - for (auto const& output : output_list) + for (auto const& output : state.output_list) { if (output == state.active_output) continue; @@ -1432,7 +1432,7 @@ std::shared_ptr const& Policy::_next_output_in_list(std::vectorget_output().name() == names[next]) return output; @@ -1499,20 +1499,20 @@ bool Policy::try_move_active_to_current() bool Policy::try_move_active_to_primary() { - if (output_list.empty()) + if (state.output_list.empty()) return false; if (!can_move_container()) return false; - if (state.active()->get_output() == output_list[0].get()) + if (state.active()->get_output() == state.output_list[0].get()) return false; auto container = state.active(); container->get_output()->delete_container(container); state.unfocus(container); - output_list[0]->graft(container); + state.output_list[0]->graft(container); if (container->window().value()) window_controller.select_active_window(container->window().value()); return true; @@ -1521,20 +1521,20 @@ bool Policy::try_move_active_to_primary() bool Policy::try_move_active_to_nonprimary() { constexpr int MIN_SIZE_TO_HAVE_NONPRIMARY_OUTPUT = 2; - if (output_list.size() < MIN_SIZE_TO_HAVE_NONPRIMARY_OUTPUT) + if (state.output_list.size() < MIN_SIZE_TO_HAVE_NONPRIMARY_OUTPUT) return false; if (!can_move_container()) return false; - if (state.active_output != output_list[0]) + if (state.active_output != state.output_list[0]) return false; auto container = state.active(); container->get_output()->delete_container(container); state.unfocus(container); - output_list[1]->graft(container); + state.output_list[1]->graft(container); if (container->window().value()) window_controller.select_active_window(container->window().value()); return true; @@ -1545,16 +1545,16 @@ bool Policy::try_move_active_to_next() if (!can_move_container()) return false; - auto it = std::find(output_list.begin(), output_list.end(), state.active_output); - if (it == output_list.end()) + auto it = std::find(state.output_list.begin(), state.output_list.end(), state.active_output); + if (it == state.output_list.end()) { mir::log_error("Policy::try_move_active_to_next: cannot find active output in list"); return false; } it++; - if (it == output_list.end()) - it = output_list.begin(); + if (it == state.output_list.end()) + it = state.output_list.begin(); if (*it == state.active_output) return false; diff --git a/src/policy.h b/src/policy.h index 9fef196..533039b 100644 --- a/src/policy.h +++ b/src/policy.h @@ -20,6 +20,7 @@ along with this program. If not, see . #include "animator.h" #include "auto_restarting_launcher.h" +#include "command_controller.h" #include "compositor_state.h" #include "config.h" #include "ipc.h" @@ -49,7 +50,7 @@ class Container; class ContainerGroupContainer; class WindowToolsAccessor; -class Policy : public miral::WindowManagementPolicy +class Policy : public miral::WindowManagementPolicy, public CommandController { public: Policy( @@ -101,61 +102,61 @@ public: // Requests - bool try_request_horizontal(); - bool try_request_vertical(); - bool try_toggle_layout(bool cycle_through_all); - void try_toggle_resize_mode(); - bool try_resize(Direction direction, int pixels); - bool try_set_size(std::optional const& width, std::optional const& height); - bool try_move(Direction direction); - bool try_move_by(Direction direction, int pixels); - bool try_move_to(int x, int y); - bool try_select(Direction direction); - bool try_select_parent(); - bool try_select_child(); - bool try_select_floating(); - bool try_select_tiling(); - bool try_select_toggle(); - bool try_close_window(); - bool quit(); - bool try_toggle_fullscreen(); - bool select_workspace(int number, bool back_and_forth = true); - bool select_workspace(std::string const& name, bool back_and_forth); - bool next_workspace(); - bool prev_workspace(); - bool back_and_forth_workspace(); - bool next_workspace_on_output(Output const&); - bool prev_workspace_on_output(Output const&); - bool move_active_to_workspace(int number, bool back_and_forth = true); - bool move_active_to_workspace_named(std::string const&, bool back_and_forth); - bool move_active_to_next_workspace(); - bool move_active_to_prev_workspace(); - bool move_active_to_back_and_forth(); - bool move_to_scratchpad(); - bool show_scratchpad(); - bool toggle_floating(); - bool toggle_pinned_to_workspace(); - bool set_is_pinned(bool); - bool toggle_tabbing(); - bool toggle_stacking(); - bool set_layout(LayoutScheme scheme); - bool set_layout_default(); - void move_cursor_to_output(Output const&); - bool try_select_next_output(); - bool try_select_prev_output(); - bool try_select_output(Direction direction); - bool try_select_output(std::vector const& names); - bool try_move_active_to_output(Direction direction); - bool try_move_active_to_current(); - bool try_move_active_to_primary(); - bool try_move_active_to_nonprimary(); - bool try_move_active_to_next(); - bool try_move_active(std::vector const& names); + bool try_request_horizontal() override; + bool try_request_vertical() override; + bool try_toggle_layout(bool cycle_through_all) override; + void try_toggle_resize_mode() override; + bool try_resize(Direction direction, int pixels) override; + bool try_set_size(std::optional const& width, std::optional const& height) override; + bool try_move(Direction direction) override; + bool try_move_by(Direction direction, int pixels) override; + bool try_move_to(int x, int y) override; + bool try_select(Direction direction) override; + bool try_select_parent() override; + bool try_select_child() override; + bool try_select_floating() override; + bool try_select_tiling() override; + bool try_select_toggle() override; + bool try_close_window() override; + bool quit() override; + bool try_toggle_fullscreen() override; + bool select_workspace(int number, bool back_and_forth = true) override; + bool select_workspace(std::string const& name, bool back_and_forth) override; + bool next_workspace() override; + bool prev_workspace() override; + bool back_and_forth_workspace() override; + bool next_workspace_on_output(Output const&) override; + bool prev_workspace_on_output(Output const&) override; + bool move_active_to_workspace(int number, bool back_and_forth = true) override; + bool move_active_to_workspace_named(std::string const&, bool back_and_forth) override; + bool move_active_to_next_workspace() override; + bool move_active_to_prev_workspace() override; + bool move_active_to_back_and_forth() override; + bool move_to_scratchpad() override; + bool show_scratchpad() override; + bool toggle_floating() override; + bool toggle_pinned_to_workspace() override; + bool set_is_pinned(bool) override; + bool toggle_tabbing() override; + bool toggle_stacking() override; + bool set_layout(LayoutScheme scheme) override; + bool set_layout_default() override; + void move_cursor_to_output(Output const&) override; + bool try_select_next_output() override; + bool try_select_prev_output() override; + bool try_select_output(Direction direction) override; + bool try_select_output(std::vector const& names) override; + bool try_move_active_to_output(Direction direction) override; + bool try_move_active_to_current() override; + bool try_move_active_to_primary() override; + bool try_move_active_to_nonprimary() override; + bool try_move_active_to_next() override; + bool try_move_active(std::vector const& names) override; // Getters [[nodiscard]] Output const* get_active_output() const { return state.active_output.get(); } - [[nodiscard]] std::vector> const& get_output_list() const { return output_list; } + [[nodiscard]] std::vector> const& get_output_list() const { return state.output_list; } [[nodiscard]] geom::Point const& get_cursor_position() const { return state.cursor_position; } [[nodiscard]] CompositorState const& get_state() const { return state; } @@ -173,7 +174,6 @@ private: bool is_starting_ = true; CompositorState& state; - std::vector> output_list; AllocationHint pending_allocation; std::vector orphaned_window_list; miral::WindowManagerTools window_manager_tools;