From 337d9ca717d9a2a691daa231d1d879aec45b5863 Mon Sep 17 00:00:00 2001 From: andrew pliatsikas Date: Sun, 25 Feb 2024 05:31:30 +0000 Subject: [PATCH 1/2] Initial commit of remote-view --- src/remote-view/ipc-activator.hpp | 125 ++ src/remote-view/ipc-helpers.hpp | 132 ++ src/remote-view/ipc-method-repository.hpp | 147 ++ src/remote-view/meson.build | 5 + .../remote-view-workspace-wall.hpp | 472 +++++++ .../remoteview-move-drag-interface.hpp | 794 +++++++++++ src/remote-view/remoteview.cpp | 1205 +++++++++++++++++ 7 files changed, 2880 insertions(+) create mode 100644 src/remote-view/ipc-activator.hpp create mode 100644 src/remote-view/ipc-helpers.hpp create mode 100644 src/remote-view/ipc-method-repository.hpp create mode 100644 src/remote-view/meson.build create mode 100644 src/remote-view/remote-view-workspace-wall.hpp create mode 100644 src/remote-view/remoteview-move-drag-interface.hpp create mode 100644 src/remote-view/remoteview.cpp diff --git a/src/remote-view/ipc-activator.hpp b/src/remote-view/ipc-activator.hpp new file mode 100644 index 0000000..555aa9b --- /dev/null +++ b/src/remote-view/ipc-activator.hpp @@ -0,0 +1,125 @@ +#pragma once + +#include +#include +#include "ipc-helpers.hpp" +#include "ipc-method-repository.hpp" +#include "wayfire/bindings.hpp" +#include "wayfire/core.hpp" +#include "wayfire/option-wrapper.hpp" +#include "wayfire/output.hpp" +#include "wayfire/plugins/common/shared-core-data.hpp" +#include "wayfire/seat.hpp" + +namespace wf +{ +/** + * The IPC activator class is a helper class which combines an IPC method with a normal activator binding. + */ + +class ipc_activator_t +{ + public: + ipc_activator_t() + {} + + ipc_activator_t(std::string name) + { + load_from_xml_option(name); + } + + void load_from_xml_option(std::string name) + { + activator.load_option(name); + wf::get_core().bindings->add_activator(activator, &activator_cb); + repo->register_method(name, ipc_cb); + this->name = name; + } + + ~ipc_activator_t() + { + wf::get_core().bindings->rem_binding(&activator_cb); + repo->unregister_method(name); + } + + /** + * The handler is given over an optional output and a view to execute the action for. + * Note that the output is always set (if not explicitly given, then it is set to the currently focused + * output), however the view might be nullptr if not indicated in the IPC call or in the case of + * activators, no suitable view could be found for the cursor/keyboard focus. + */ + using handler_t = std::function; + void set_handler(handler_t hnd) + { + this->hnd = hnd; + } + + private: + wf::option_wrapper_t activator; + shared_data::ref_ptr_t repo; + std::string name; + handler_t hnd; + + activator_callback activator_cb = [=] (const wf::activator_data_t& data) -> bool + { + if (hnd) + { + return hnd(choose_output(), choose_view(data.source)); + } + + return false; + }; + + ipc::method_callback ipc_cb = [=] (const nlohmann::json& data) + { + WFJSON_OPTIONAL_FIELD(data, "output_id", number_integer); + WFJSON_OPTIONAL_FIELD(data, "view_id", number_integer); + + wf::output_t *wo = wf::get_core().seat->get_active_output(); + if (data.contains("output_id")) + { + wo = ipc::find_output_by_id(data["output_id"]); + if (!wo) + { + return ipc::json_error("output id not found!"); + } + } + + wayfire_view view; + if (data.contains("view_id")) + { + view = ipc::find_view_by_id(data["view_id"]); + if (!view) + { + return ipc::json_error("view id not found!"); + } + } + + if (hnd) + { + hnd(wo, view); + } + + return ipc::json_ok(); + }; + + wf::output_t *choose_output() + { + return wf::get_core().seat->get_active_output(); + } + + wayfire_view choose_view(wf::activator_source_t source) + { + wayfire_view view; + if (source == wf::activator_source_t::BUTTONBINDING) + { + view = wf::get_core().get_cursor_focus_view(); + } else + { + view = wf::get_core().seat->get_active_view(); + } + + return view; + } +}; +} diff --git a/src/remote-view/ipc-helpers.hpp b/src/remote-view/ipc-helpers.hpp new file mode 100644 index 0000000..c6cd481 --- /dev/null +++ b/src/remote-view/ipc-helpers.hpp @@ -0,0 +1,132 @@ +#pragma once + +#include "wayfire/geometry.hpp" +#include +#include +#include +#include +#include +#include +#include + +namespace wf +{ +namespace ipc +{ +inline wayfire_view find_view_by_id(uint32_t id) +{ + for (auto view : wf::get_core().get_all_views()) + { + if (view->get_id() == id) + { + return view; + } + } + + return nullptr; +} + +inline wf::output_t *find_output_by_id(int32_t id) +{ + for (auto wo : wf::get_core().output_layout->get_outputs()) + { + if ((int)wo->get_id() == id) + { + return wo; + } + } + + return nullptr; +} + +inline wf::workspace_set_t *find_workspace_set_by_index(int32_t index) +{ + for (auto wset : wf::workspace_set_t::get_all()) + { + if ((int)wset->get_index() == index) + { + return wset.get(); + } + } + + return nullptr; +} + +inline nlohmann::json geometry_to_json(wf::geometry_t g) +{ + nlohmann::json j; + j["x"] = g.x; + j["y"] = g.y; + j["width"] = g.width; + j["height"] = g.height; + return j; +} + +inline std::optional geometry_from_json(const nlohmann::json& j) +{ +#define CHECK(field, type) (j.contains(field) && j[field].is_number_ ## type()) + if (!CHECK("x", integer) || !CHECK("y", integer) || + !CHECK("width", unsigned) || !CHECK("height", unsigned)) + { + return {}; + } + +#undef CHECK + + return wf::geometry_t{ + .x = j["x"], + .y = j["y"], + .width = j["width"], + .height = j["height"], + }; +} + +inline nlohmann::json point_to_json(wf::point_t p) +{ + nlohmann::json j; + j["x"] = p.x; + j["y"] = p.y; + return j; +} + +inline std::optional point_from_json(const nlohmann::json& j) +{ +#define CHECK(field, type) (j.contains(field) && j[field].is_number_ ## type()) + if (!CHECK("x", integer) || !CHECK("y", integer)) + { + return {}; + } + +#undef CHECK + + return wf::point_t{ + .x = j["x"], + .y = j["y"], + }; +} + +inline nlohmann::json dimensions_to_json(wf::dimensions_t d) +{ + nlohmann::json j; + j["width"] = d.width; + j["height"] = d.height; + return j; +} + +inline std::optional dimensions_from_json(const nlohmann::json& j) +{ +#define CHECK(field, type) (j.contains(field) && j[field].is_number_ ## type()) + if (!CHECK("width", integer) || !CHECK("height", integer)) + { + return {}; + } + +#undef CHECK + + return wf::dimensions_t{ + .width = j["width"], + .height = j["height"], + }; +} +} +} diff --git a/src/remote-view/ipc-method-repository.hpp b/src/remote-view/ipc-method-repository.hpp new file mode 100644 index 0000000..005ad55 --- /dev/null +++ b/src/remote-view/ipc-method-repository.hpp @@ -0,0 +1,147 @@ +#pragma once + +#include +#include +#include +#include "wayfire/signal-provider.hpp" + +namespace wf +{ +namespace ipc +{ +/** + * A client_interface_t represents a client which has connected to the IPC socket. + * It can be used by plugins to send back data to a specific client. + */ +class client_interface_t +{ + public: + virtual void send_json(nlohmann::json json) = 0; +}; + +/** + * A signal emitted on the ipc method repository when a client disconnects. + */ +struct client_disconnected_signal +{ + client_interface_t *client; +}; + +/** + * An IPC method has a name and a callback. The callback is a simple function which takes a json object which + * contains the method's parameters and returns the result of the operation. + */ +using method_callback = std::function; + +/** + * Same as @method_callback, but also supports getting information about the connected ipc client. + */ +using method_callback_full = std::function; + +/** + * The IPC method repository keeps track of all registered IPC methods. It can be used even without the IPC + * plugin itself, as it facilitates inter-plugin calls similarly to signals. + * + * The method_repository_t is a singleton and is accessed by creating a shared_data::ref_ptr_t to it. + */ +class method_repository_t : public wf::signal::provider_t +{ + public: + /** + * Register a new method to the method repository. If the method already exists, the old handler will be + * overwritten. + */ + void register_method(std::string method, method_callback_full handler) + { + this->methods[method] = handler; + } + + /** + * Register a new method to the method repository. If the method already exists, the old handler will be + * overwritten. + */ + void register_method(std::string method, method_callback handler) + { + this->methods[method] = [handler] (const nlohmann::json& data, client_interface_t*) + { + return handler(data); + }; + } + + /** + * Remove the last registered handler for the given method. + */ + void unregister_method(std::string method) + { + this->methods.erase(method); + } + + /** + * Call an IPC method with the given name and given parameters. + * If the method was not registered, a JSON object containing an error will be returned. + */ + nlohmann::json call_method(std::string method, nlohmann::json data, + client_interface_t *client = nullptr) + { + if (this->methods.count(method)) + { + return this->methods[method](std::move(data), client); + } + + return { + {"error", "No such method found!"} + }; + } + + method_repository_t() + { + register_method("list-methods", [this] (auto) + { + nlohmann::json response; + response["methods"] = nlohmann::json::array(); + for (auto& [method, _] : methods) + { + response["methods"].push_back(method); + } + + return response; + }); + } + + private: + std::map methods; +}; + +// A few helper definitions for IPC method implementations. +inline nlohmann::json json_ok() +{ + return nlohmann::json{ + {"result", "ok"} + }; +} + +inline nlohmann::json json_error(std::string msg) +{ + return nlohmann::json{ + {"error", std::string(msg)} + }; +} + +#define WFJSON_EXPECT_FIELD(data, field, type) \ + if (!data.count(field)) \ + { \ + return wf::ipc::json_error("Missing \"" field "\""); \ + } \ + else if (!data[field].is_ ## type()) \ + { \ + return wf::ipc::json_error("Field \"" field "\" does not have the correct type " #type); \ + } + +#define WFJSON_OPTIONAL_FIELD(data, field, type) \ + if (data.count(field) && !data[field].is_ ## type()) \ + { \ + return wf::ipc::json_error("Field \"" + std::string(field) + \ + "\" does not have the correct type " #type); \ + } +} +} diff --git a/src/remote-view/meson.build b/src/remote-view/meson.build new file mode 100644 index 0000000..a6d7af6 --- /dev/null +++ b/src/remote-view/meson.build @@ -0,0 +1,5 @@ + +remote_view = shared_module('remoteview', 'remoteview.cpp', + dependencies: [wayfire], + install: true, install_dir: join_paths(get_option('libdir'), 'wayfire')) + diff --git a/src/remote-view/remote-view-workspace-wall.hpp b/src/remote-view/remote-view-workspace-wall.hpp new file mode 100644 index 0000000..46d7a5f --- /dev/null +++ b/src/remote-view/remote-view-workspace-wall.hpp @@ -0,0 +1,472 @@ +#pragma once + +#include +#include +#include +#include +#include "wayfire/core.hpp" +#include "wayfire/debug.hpp" +#include "wayfire/geometry.hpp" +#include "wayfire/opengl.hpp" +#include "wayfire/region.hpp" +#include "wayfire/render-manager.hpp" +#include "wayfire/scene-input.hpp" +#include "wayfire/scene-operations.hpp" +#include "wayfire/scene-render.hpp" +#include "wayfire/scene.hpp" +#include "wayfire/signal-definitions.hpp" +#include "wayfire/signal-provider.hpp" +#include "wayfire/workspace-stream.hpp" +#include "wayfire/workspace-set.hpp" + +int workspaceX_pos=0; + +namespace wf +{ + wf::geometry_t add_offset_to_target(const wf::geometry_t& target, int offset_x, int offset_y) + { + wf::geometry_t result = target; + result.x += offset_x; + result.y += offset_y; + return result; + } + + wf::region_t add_offset_to_workspace_rect(const wf::region_t& damage, + int offset_x, int offset_y) + { + wf::region_t adjusted_damage; + + for (auto& rect : damage) + { + wf::geometry_t adjusted_box = { + .x = rect.x1 + offset_x, + .y = rect.y1 + offset_y, + .width = rect.x2 - rect.x1, + .height = rect.y2 - rect.y1, + }; + + adjusted_damage |= adjusted_box; + } + + return adjusted_damage; + } + + struct wall_frame_event_t + { + const wf::render_target_t& target; + wall_frame_event_t(const wf::render_target_t& t) : target(t) + {} + }; + + class remoteview_workspace_wall : public wf::signal::provider_t + { + public: + remoteview_workspace_wall(wf::output_t *_output) : output(_output) + { + this->viewport = get_wall_rectangle(); + } + + ~remoteview_workspace_wall() + { + stop_output_renderer(false); + } + + void set_background_color(const wf::color_t& color) + { + this->background_color = color; + } + + void set_gap_size(int size) + { + this->gap_size = size; + } + + + + void set_viewport(const wf::geometry_t& viewport_geometry) + { + this->viewport = viewport_geometry; + if (render_node) + { + scene::damage_node(this->render_node, + this->render_node->get_bounding_box()); + } + } + + void render_wall(const wf::render_target_t& fb, const wf::region_t& damage) + { + wall_frame_event_t data{fb}; + this->emit(&data); + } + + void start_output_renderer() + { + wf::dassert(render_node == nullptr, "Starting workspace-wall twice?"); + render_node = std::make_shared(this); + scene::add_front(wf::get_core().scene(), render_node); + } + + void stop_output_renderer(bool reset_viewport) + { + if (!render_node) + { + return; + } + + scene::remove_child(render_node); + render_node = nullptr; + + if (reset_viewport) + { + set_viewport({0, 0, 0, 0}); + } + } + + wf::geometry_t get_workspace_rectangle(const wf::point_t& ws) const + { + auto size = this->output->get_screen_size(); + + return { + ws.x * (size.width + gap_size), + ws.y * (size.height + gap_size), + size.width, + size.height + }; + } + + wf::geometry_t get_wall_rectangle() const + { + auto size = this->output->get_screen_size(); + auto workspace_size = this->output->wset()->get_workspace_grid_size(); + + return { + -gap_size, + -gap_size, + workspace_size.width * (size.width + gap_size) + gap_size, + workspace_size.height * (size.height + gap_size) + gap_size + }; + } + + void set_ws_dim(const wf::point_t& ws, float value) + { + render_colors[{ws.x, ws.y}] = value; + if (render_node) + { + scene::damage_node(render_node, render_node->get_bounding_box()); + } + } + + protected: + bool transparent_background = true; + wf::output_t *output; + + wf::color_t background_color = {0, 0, 0, 0}; + int gap_size = 0; + + wf::geometry_t viewport = {0, 0, 0, 0}; + + std::map, float> render_colors; + + float get_color_for_workspace(wf::point_t ws) + { + auto it = render_colors.find({ws.x, ws.y}); + if (it == render_colors.end()) + { + return 1.0; + } + + return it->second; + } + + std::vector get_visible_workspaces(wf::geometry_t viewport) const + { + std::vector visible; + auto wsize = output->wset()->get_workspace_grid_size(); + for (int i = 0; i < wsize.width; i++) + { + for (int j = 0; j < wsize.height; j++) + { + if (viewport & get_workspace_rectangle({i, j})) + { + visible.push_back({i, j}); + } + } + } + + return visible; + } + + protected: + class workspace_wall_node_second_t : public scene::node_t + { + class wwall_render_instance_t : public scene::render_instance_t + { + workspace_wall_node_second_t *self; + + std::vector>> + instances; + + scene::damage_callback push_damage; + wf::signal::connection_t on_wall_damage = + [=] (scene::node_damage_signal *ev) + { + push_damage(ev->region); + }; + + wf::geometry_t get_workspace_rect(wf::point_t ws) + { + auto output_size = self->wall->output->get_screen_size(); + return { + .x = ws.x * (output_size.width + self->wall->gap_size), + .y = ws.y * (output_size.height + self->wall->gap_size), + .width = output_size.width, + .height = output_size.height, + }; + }; + + public: + wwall_render_instance_t(workspace_wall_node_second_t *self, + scene::damage_callback push_damage) + { + this->self = self; + this->push_damage = push_damage; + self->connect(&on_wall_damage); + + instances.resize(self->workspaces.size()); + for (int i = 0; i < 1; i++) + { + instances[i].resize(self->workspaces[i].size()); + for (int j = 0; j < (int)self->workspaces[i].size(); j++) + { + auto push_damage_child = [=] (const wf::region_t& damage) + { + wf::region_t our_damage; + for (auto& rect : damage) + { + wf::geometry_t box = wlr_box_from_pixman_box(rect); + box = box + wf::origin(get_workspace_rect({i, j})); + auto A = self->wall->viewport; + auto B = self->get_bounding_box(); + our_damage |= scale_box(A, B, box); + } + + push_damage(our_damage); + }; + +//self->workspaces[1][j] + +//printf("cws = %s\n",cws ); + self->workspaces[workspaceX_pos][j]->gen_render_instances(instances[i][j], + push_damage_child, self->wall->output); + } + } + } + + using render_tag = std::tuple; + static constexpr int TAG_BACKGROUND = 0; + static constexpr int TAG_WS_DIM = 1; + static constexpr int FRAME_EV = 2; + + void schedule_instructions( + std::vector& instructions, + const wf::render_target_t& target, wf::region_t& damage) override + { + instructions.push_back(scene::render_instruction_t{ + .instance = this, + .target = target, + .damage = wf::region_t{}, + .data = render_tag{FRAME_EV, 0.0}, + }); + + // Scale damage to be in the workspace's coordinate system + + wf::geometry_t sample_workspace = get_workspace_rect({0, 0}); + + wf::region_t workspaces_damage; + for (auto& rect : damage) + { + auto box = wlr_box_from_pixman_box(rect); + wf::geometry_t A = self->get_bounding_box(); + wf::geometry_t B = self->wall->viewport; + + // Adjust the box by subtracting 300 from the x-coordinate + box.x -= sample_workspace.width/2; + // box.width -= 300; + + workspaces_damage |= scale_box(A, B, box); + } + + + + + + for (int i = 0; i < 1; i++) + { + for (int j = 0; j < (int)self->workspaces[i].size(); j++) + { + // Compute render target: a subbuffer of the target buffer + // which corresponds to the region occupied by the + // workspace. + wf::render_target_t our_target = target; + our_target.geometry = + self->workspaces[i][j]->get_bounding_box(); + + wf::geometry_t workspace_rect = get_workspace_rect({i, j}); + wf::geometry_t relative_to_viewport; + + relative_to_viewport= scale_box( + self->wall->viewport, target.geometry,{ workspace_rect.x - workspace_rect.width/2,workspace_rect.y,workspace_rect.width,workspace_rect.height} /*workspace_rect*/); + + relative_to_viewport = add_offset_to_target(relative_to_viewport, workspace_rect.width/2, 0); + + our_target.subbuffer = target.framebuffer_box_from_geometry_box(relative_to_viewport); + + wf::geometry_t workspace_rect2; + workspace_rect2.x = workspace_rect.x - workspace_rect.width/2 ; + workspace_rect2.y = workspace_rect.y; + workspace_rect2.width = workspace_rect.width; + workspace_rect2.height = workspace_rect.height; + + // Take the damage for the workspace in workspace-local coordindates, as the workspace + // stream node expects. + wf::region_t our_damage = workspaces_damage & workspace_rect2; + // Adjust the damage here with - workspace_rect.width/2 + // Assuming our_damage is in a coordinate system compatible with the offset + + workspaces_damage ^= our_damage; + our_damage += -wf::origin(workspace_rect2); + + + + // Dim workspaces at the end (the first instruction pushed is executed last) + instructions.push_back(scene::render_instruction_t{ + .instance = this, + .target = our_target, + .damage = our_damage , + .data = render_tag{TAG_WS_DIM, + self->wall->get_color_for_workspace({i, j})}, + }); + + // Render the workspace contents first + for (auto& ch : instances[i][j]) + { + ch->schedule_instructions(instructions, our_target, our_damage); + } + } + } + + /* auto bbox = self->get_bounding_box(); + + instructions.push_back(scene::render_instruction_t{ + .instance = this, + .target = target, + .damage = damage & self->get_bounding_box(), + .data = render_tag{TAG_BACKGROUND, 0.0}, + }); + + damage ^= bbox; +*/ } + + + void render(const wf::render_target_t& target, + const wf::region_t& region, const std::any& any_tag) override + { + auto [tag, dim] = std::any_cast(any_tag); + + if (tag == TAG_BACKGROUND) + { + OpenGL::render_begin(target); + for (auto& box : region) + { + target.logic_scissor(wlr_box_from_pixman_box(box)); + OpenGL::clear(self->wall->background_color); + } + + OpenGL::render_end(); + } + else if (tag == FRAME_EV) + { + self->wall->render_wall(target, region); + } + else + { + auto fb_region = target.framebuffer_region_from_geometry_region(region); + + OpenGL::render_begin(target); + for (auto& dmg_rect : fb_region) + { + target.scissor(wlr_box_from_pixman_box(dmg_rect)); + const float a = 1.0 - dim; + + OpenGL::render_rectangle(target.geometry, {0, 0, 0, a}, + target.get_orthographic_projection()); + } + + OpenGL::render_end(); + } + } + + void compute_visibility(wf::output_t *output, wf::region_t& visible) override + { + for (int i = 0; i < 1; i++) + { + for (int j = 0; j < (int)self->workspaces[i].size(); j++) + { + wf::region_t ws_region = self->workspaces[i][j]->get_bounding_box(); + for (auto& ch : this->instances[i][j]) + { + ch->compute_visibility(output, ws_region); + } + } + } + } + }; + + public: + workspace_wall_node_second_t(remoteview_workspace_wall *wall) : node_t(false) + { + this->wall = wall; + auto [w, h] = wall->output->wset()->get_workspace_grid_size(); + workspaces.resize(w); + for (int i = 0; i < w; i++) + { + for (int j = 0; j < h; j++) + { + auto node = std::make_shared( + wall->output, wf::point_t{i, j}); + workspaces[i].push_back(node); + } + } + } + + virtual void gen_render_instances( + std::vector& instances, + scene::damage_callback push_damage, wf::output_t *shown_on) override + { + if (shown_on != this->wall->output) + { + return; + } + + instances.push_back(std::make_unique( + this, push_damage)); + } + + std::string stringify() const override + { + return "workspace-wall " + stringify_flags(); + } + + wf::geometry_t get_bounding_box() override + { + return wall->output->get_layout_geometry(); + } + + private: + remoteview_workspace_wall *wall; + std::vector>> workspaces; + }; + + std::shared_ptr render_node; + }; +} diff --git a/src/remote-view/remoteview-move-drag-interface.hpp b/src/remote-view/remoteview-move-drag-interface.hpp new file mode 100644 index 0000000..0e56fa4 --- /dev/null +++ b/src/remote-view/remoteview-move-drag-interface.hpp @@ -0,0 +1,794 @@ +#pragma once + +#include "wayfire/core.hpp" +#include "wayfire/debug.hpp" +#include "wayfire/geometry.hpp" +#include "wayfire/opengl.hpp" +#include "wayfire/region.hpp" +#include "wayfire/scene-input.hpp" +#include "wayfire/scene-operations.hpp" +#include "wayfire/scene-render.hpp" +#include "wayfire/scene.hpp" +#include "wayfire/seat.hpp" +#include "wayfire/signal-definitions.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +namespace wf +{ +/** + * A collection of classes and interfaces which can be used by plugins which + * support dragging views to move them. + * + * A plugin using these APIs would get support for: + * + * - Moving views on the same output, following the pointer or touch position. + * - Holding views in place until a certain threshold is reached + * - Wobbly windows (if enabled) + * - Move the view freely between different outputs with different plugins active + * on them, as long as all of these plugins support this interface. + * - Show smooth transitions of the moving view when moving between different + * outputs. + * + * A plugin using these APIs is expected to: + * - Grab input on its respective output and forward any events to the core_drag_t + * singleton. + * - Have activated itself with CAPABILITY_MANAGE_COMPOSITOR + * - Connect to and handle the signals described below. + */ +namespace remoteview_move_drag +{ +/** + * name: focus-output + * on: core_drag_t + * when: Emitted output whenever the output where the drag happens changes, + * including when the drag begins. + */ +struct drag_focus_output_signal +{ + /** The output which was focused up to now, might be null. */ + wf::output_t *previous_focus_output; + /** The output which was focused now. */ + wf::output_t *focus_output; +}; + +/** + * name: snap-off + * on: core_drag_t + * when: Emitted if snap-off is enabled and the view was moved more than the + * threshold. + */ +struct snap_off_signal +{ + /** The output which is focused now. */ + wf::output_t *focus_output; +}; + +/** + * name: done + * on: core_drag_t + * when: Emitted after the drag operation has ended, and if the view is unmapped + * while being dragged. + */ +struct drag_done_signal +{ + /** The output where the view was dropped. */ + wf::output_t *focused_output; + + /** Whether join-views was enabled for this drag. */ + bool join_views; + + struct view_t + { + /** Dragged view. */ + wayfire_toplevel_view view; + + /** + * The position relative to the view where the grab was. + * See scale_around_grab_t::relative_grab + */ + wf::pointf_t relative_grab; + }; + + /** All views which were dragged. */ + std::vector all_views; + + /** The main view which was dragged. */ + wayfire_toplevel_view main_view; + + /** + * The position of the input when the view was dropped. + * In output-layout coordinates. + */ + wf::point_t grab_position; +}; + +/** + * Find the geometry of a view, if it has size @size, it is grabbed at point @grab, + * and the grab is at position @relative relative to the view. + */ +inline static wf::geometry_t find_geometry_around( + wf::dimensions_t size, wf::point_t grab, wf::pointf_t relative) +{ + return wf::geometry_t{ + grab.x - (int)std::floor(relative.x * size.width), + grab.y - (int)std::floor(relative.y * size.height), + size.width, + size.height, + }; +} + +/** + * Find the position of grab relative to the view. + * Example: returns [0.5, 0.5] if the grab is the midpoint of the view. + */ +inline static wf::pointf_t find_relative_grab( + wf::geometry_t view, wf::point_t grab) +{ + return wf::pointf_t{ + 1.0 * (grab.x - view.x) / view.width, + 1.0 * (grab.y - view.y) / view.height, + }; +} + +/** + * A transformer used while dragging. + * + * It is primarily used to scale the view is a plugin needs it, and also to keep it + * centered around the `grab_position`. + */ +class scale_around_grab_t : public wf::scene::floating_inner_node_t +{ + public: + /** + * Factor for scaling down the view. + * A factor 2.0 means that the view will have half of its width and height. + */ + wf::animation::simple_animation_t scale_factor{wf::create_option(300)}; + + /** + * A place relative to the view, where it is grabbed. + * + * Coordinates are [0, 1]. A grab at (0.5, 0.5) means that the view is grabbed + * at its center. + */ + wf::pointf_t relative_grab; + + /** + * The position where the grab appears on the outputs, in output-layout + * coordinates. + */ + wf::point_t grab_position; + + scale_around_grab_t() : floating_inner_node_t(false) + {} + + std::string stringify() const override + { + return "move-drag"; + } + + wf::pointf_t scale_around_grab(wf::pointf_t point, double factor) + { + auto bbox = get_children_bounding_box(); + auto gx = bbox.x + bbox.width * relative_grab.x; + auto gy = bbox.y + bbox.height * relative_grab.y; + + return { + (point.x - gx) * factor + gx, + (point.y - gy) * factor + gy, + }; + } + + wf::pointf_t to_local(const wf::pointf_t& point) override + { + return scale_around_grab(point, scale_factor); + } + + wf::pointf_t to_global(const wf::pointf_t& point) override + { + return scale_around_grab(point, 1.0 / scale_factor); + } + + wf::geometry_t get_bounding_box() override + { + auto bbox = get_children_bounding_box(); + int w = std::floor(bbox.width / scale_factor); + int h = std::floor(bbox.height / scale_factor); + return find_geometry_around({w, h}, grab_position, relative_grab); + } + + class render_instance_t : + public scene::transformer_render_instance_t + { + public: + using transformer_render_instance_t::transformer_render_instance_t; + + void transform_damage_region(wf::region_t& region) override + { + region |= self->get_bounding_box(); + } + + void render(const wf::render_target_t& target, + const wf::region_t& region) override + { + auto bbox = self->get_bounding_box(); + auto tex = this->get_texture(target.scale); + + OpenGL::render_begin(target); + for (auto& rect : region) + { + target.logic_scissor(wlr_box_from_pixman_box(rect)); + OpenGL::render_texture(tex, target, bbox); + } + + OpenGL::render_end(); + } + }; + + void gen_render_instances(std::vector& instances, + scene::damage_callback push_damage, wf::output_t *shown_on) override + { + instances.push_back(std::make_unique(this, + push_damage, shown_on)); + } +}; + +static const std::string move_drag_transformer = "move-drag-transformer"; + +/** + * Represents a view which is being dragged. + * Multiple views exist only if join_views is set to true. + */ +struct dragged_view_t +{ + // The view being dragged + wayfire_toplevel_view view; + + // Its transformer + std::shared_ptr transformer; + + // The last bounding box used for damage. + // This is needed in case the view resizes or something like that, in which + // case we don't have access to the previous bbox. + wf::geometry_t last_bbox; +}; + +inline wayfire_toplevel_view get_toplevel(wayfire_toplevel_view view) +{ + while (view->parent) + { + view = view->parent; + } + + return view; +} + +inline std::vector get_target_views(wayfire_toplevel_view grabbed, + bool join_views) +{ + std::vector r = {grabbed}; + if (join_views) + { + r = grabbed->enumerate_views(); + } + + return r; +} + +// A node to render the dragged views in global coordinates. +// The assumption is that all nodes have a view transformer which transforms them to global (not output-local) +// coordinates and thus we just need to schedule them for rendering. +class dragged_view_node_t : public wf::scene::node_t +{ + std::vector views; + + public: + dragged_view_node_t(std::vector views) : node_t(false) + { + this->views = views; + } + + std::string stringify() const override + { + return "move-drag-view " + stringify_flags(); + } + + void gen_render_instances(std::vector& instances, + scene::damage_callback push_damage, wf::output_t *output = nullptr) override + { + instances.push_back(std::make_unique(this, push_damage, output)); + } + + wf::geometry_t get_bounding_box() override + { + wf::region_t bounding; + for (auto& view : views) + { + // Note: bbox will be in output layout coordinates now, since this is + // how the transformer works + auto bbox = view.view->get_transformed_node()->get_bounding_box(); + bounding |= bbox; + } + + return wlr_box_from_pixman_box(bounding.get_extents()); + } + + class dragged_view_render_instance_t : public wf::scene::render_instance_t + { + wf::geometry_t last_bbox = {0, 0, 0, 0}; + wf::scene::damage_callback push_damage; + std::vector children; + wf::signal::connection_t on_node_damage = + [=] (scene::node_damage_signal *data) + { + push_damage(data->region); + }; + + public: + dragged_view_render_instance_t(dragged_view_node_t *self, wf::scene::damage_callback push_damage, + wf::output_t *shown_on) + { + auto push_damage_child = [=] (wf::region_t child_damage) + { + push_damage(last_bbox); + last_bbox = self->get_bounding_box(); + push_damage(last_bbox); + }; + + for (auto& view : self->views) + { + auto node = view.view->get_transformed_node(); + node->gen_render_instances(children, push_damage_child, shown_on); + } + } + + void schedule_instructions(std::vector& instructions, + const wf::render_target_t& target, wf::region_t& damage) override + { + for (auto& inst : children) + { + inst->schedule_instructions(instructions, target, damage); + } + } + + void presentation_feedback(wf::output_t *output) override + { + for (auto& instance : children) + { + instance->presentation_feedback(output); + } + } + + void compute_visibility(wf::output_t *output, wf::region_t& visible) override + { + for (auto& instance : children) + { + const int BIG_NUMBER = 1e5; + wf::region_t big_region = + wf::geometry_t{-BIG_NUMBER, -BIG_NUMBER, 2 * BIG_NUMBER, 2 * BIG_NUMBER}; + instance->compute_visibility(output, big_region); + } + } + }; +}; + +struct drag_options_t +{ + /** + * Whether to enable snap off, that is, hold the view in place until + * a certain threshold is reached. + */ + bool enable_snap_off = false; + + /** + * If snap-off is enabled, the amount of pixels to wait for motion until + * snap-off is triggered. + */ + int snap_off_threshold = 0; + + /** + * Join views together, i.e move main window and dialogues together. + */ + bool join_views = false; + + double initial_scale = 1.0; +}; + +/** + * An object for storing global move drag data (i.e shared between all outputs). + * + * Intended for use via wf::shared_data::ref_ptr_t. + */ +class core_drag_t : public signal::provider_t +{ + /** + * Rebuild the wobbly model after a change in the scaling, so that the wobbly + * model does not try to animate the scaling change itself. + */ + void rebuild_wobbly(wayfire_toplevel_view view, wf::point_t grab, wf::pointf_t relative) + { + auto dim = wf::dimensions(wf::view_bounding_box_up_to(view, "wobbly")); + modify_wobbly(view, find_geometry_around(dim, grab, relative)); + } + + public: + /** + * Start drag. + * + * @param grab_view The view which is being dragged. + * @param grab_position The position of the input, in output-layout coordinates. + * @param relative The position of the grab_position relative to view. + */ + void start_drag(wayfire_toplevel_view grab_view, wf::point_t grab_position, + wf::pointf_t relative, + const drag_options_t& options) + { + auto bbox = wf::view_bounding_box_up_to(grab_view, "wobbly"); + wf::point_t rel_grab_pos = { + int(bbox.x + relative.x * bbox.width), + int(bbox.y + relative.y * bbox.height), + }; + + if (options.join_views) + { + grab_view = get_toplevel(grab_view); + } + + this->view = grab_view; + this->params = options; + wf::get_core().default_wm->set_view_grabbed(view, true); + + auto target_views = get_target_views(grab_view, options.join_views); + for (auto& v : target_views) + { + dragged_view_t dragged; + dragged.view = v; + + // Setup view transform + + auto tr = std::make_shared(); + dragged.transformer = {tr}; + + tr->relative_grab = find_relative_grab( + wf::view_bounding_box_up_to(v, "wobbly"), rel_grab_pos); + tr->grab_position = grab_position; + tr->scale_factor.animate(options.initial_scale, options.initial_scale); + v->get_transformed_node()->add_transformer( + tr, wf::TRANSFORMER_HIGHLEVEL - 1); + + // Hide the view, we will render it as an overlay + // wf::scene::set_node_enabled(v->get_transformed_node(), false); + + + v->damage(); + + // Make sure that wobbly has the correct geometry from the start! + rebuild_wobbly(v, grab_position, dragged.transformer->relative_grab); + + // TODO: make this configurable! + start_wobbly_rel(v, dragged.transformer->relative_grab); + + this->all_views.push_back(dragged); + v->connect(&on_view_unmap); + } + + // Setup overlay hooks + render_node = std::make_shared(all_views); + wf::scene::add_front(wf::get_core().scene(), render_node); + wf::get_core().set_cursor("grabbing"); + + // Set up snap-off + if (params.enable_snap_off) + { + for (auto& v : all_views) + { + set_tiled_wobbly(v.view, true); + } + + grab_origin = grab_position; + view_held_in_place = true; + } + } + + void start_drag(wayfire_toplevel_view view, wf::point_t grab_position, const drag_options_t& options) + { + if (options.join_views) + { + view = get_toplevel(view); + } + + auto bbox = view->get_transformed_node()->get_bounding_box() + + wf::origin(view->get_output()->get_layout_geometry()); + start_drag(view, grab_position, find_relative_grab(bbox, grab_position), options); + } + + void handle_motion(wf::point_t to) + { + if (view_held_in_place) + { + if (distance_to_grab_origin(to) >= (double)params.snap_off_threshold) + { + view_held_in_place = false; + for (auto& v : all_views) + { + set_tiled_wobbly(v.view, false); + } + + snap_off_signal data; + data.focus_output = current_output; + emit(&data); + } + } + + // Update wobbly independently of the grab position. + // This is because while held in place, wobbly is anchored to its edges + // so we can still move the grabbed point without moving the view. + for (auto& v : all_views) + { + move_wobbly(v.view, to.x, to.y); + if (!view_held_in_place) + { + v.view->get_transformed_node()->begin_transform_update(); + v.transformer->grab_position = to; + v.view->get_transformed_node()->end_transform_update(); + } + } + + update_current_output(to); + } + + double distance_to_grab_origin(wf::point_t to) const + { + auto offset = to - grab_origin; + const int dst_sq = offset.x * offset.x + offset.y * offset.y; + return std::sqrt(dst_sq); + } + + void handle_input_released() + { + if (!view || all_views.empty()) + { + // Input already released => don't do anything + return; + } + + // Store data for the drag done signal + drag_done_signal data; + data.grab_position = all_views.front().transformer->grab_position; + for (auto& v : all_views) + { + data.all_views.push_back( + {v.view, v.transformer->relative_grab}); + } + + data.main_view = this->view; + data.focused_output = current_output; + data.join_views = params.join_views; + + // Remove overlay hooks and damage outputs BEFORE popping the transformer + wf::scene::remove_child(render_node); + render_node = nullptr; + + for (auto& v : all_views) + { + auto grab_position = v.transformer->grab_position; + auto rel_pos = v.transformer->relative_grab; + + // Restore view to where it was before + wf::scene::set_node_enabled(v.view->get_transformed_node(), true); + v.view->get_transformed_node()->rem_transformer(); + + // Reset wobbly and leave it in output-LOCAL coordinates + end_wobbly(v.view); + + // Important! If the view scale was not 1.0, the wobbly model needs to be + // updated with the new size. Since this is an artificial resize, we need + // to make sure that the resize happens smoothly. + rebuild_wobbly(v.view, grab_position, rel_pos); + + // Put wobbly back in output-local space, the plugins will take it from + // here. + translate_wobbly(v.view, + -wf::origin(v.view->get_output()->get_layout_geometry())); + } + + // Reset our state + wf::get_core().default_wm->set_view_grabbed(view, false); + view = nullptr; + all_views.clear(); + current_output = nullptr; + wf::get_core().set_cursor("default"); + + // Lastly, let the plugins handle what happens on drag end. + emit(&data); + view_held_in_place = false; + on_view_unmap.disconnect(); + } + + void set_scale(double new_scale) + { + for (auto& view : all_views) + { + view.transformer->scale_factor.animate(new_scale); + } + } + + bool is_view_held_in_place() + { + return view_held_in_place; + } + + // View currently being moved. + wayfire_toplevel_view view; + + // Output where the action is happening. + wf::output_t *current_output = NULL; + + private: + // All views being dragged, more than one in case of join_views. + std::vector all_views; + + // Current parameters + drag_options_t params; + + // Grab origin, used for snap-off + wf::point_t grab_origin; + + // View is held in place, waiting for snap-off + bool view_held_in_place = false; + + std::shared_ptr render_node; + + void update_current_output(wf::point_t grab) + { + wf::pointf_t origin = {1.0 * grab.x, 1.0 * grab.y}; + auto output = wf::get_core().output_layout->get_output_coords_at(origin, origin); + + if (output != current_output) + { + if (current_output) + { + current_output->render->rem_effect(&on_pre_frame); + } + + drag_focus_output_signal data; + data.previous_focus_output = current_output; + + current_output = output; + data.focus_output = output; + wf::get_core().seat->focus_output(output); + emit(&data); + + if (output) + { + current_output->render->add_effect(&on_pre_frame, OUTPUT_EFFECT_PRE); + } + } + } + + wf::effect_hook_t on_pre_frame = [=] () + { + for (auto& v : this->all_views) + { + if (v.transformer->scale_factor.running()) + { + v.view->damage(); + } + } + }; + + wf::signal::connection_t on_view_unmap = [=] (auto *ev) + { + handle_input_released(); + }; +}; + +/** + * Move the view to the target output and put it at the coordinates of the grab. + * Also take into account view's fullscreen and tiled state. + * + * Unmapped views are ignored. + */ +inline void adjust_view_on_output(drag_done_signal *ev) +{ + // Any one of the views that are being dragged. + // They are all part of the same view tree. + auto parent = get_toplevel(ev->main_view); + if (!parent->is_mapped()) + { + return; + } + + if (parent->get_output() != ev->focused_output) + { + move_view_to_output(parent, ev->focused_output, false); + } + + // Calculate the position we're leaving the view on + auto output_delta = -wf::origin(ev->focused_output->get_layout_geometry()); + auto grab = ev->grab_position + output_delta; + + auto output_geometry = ev->focused_output->get_relative_geometry(); + auto current_ws = ev->focused_output->wset()->get_current_workspace(); + wf::point_t target_ws{ + (int)std::floor(1.0 * grab.x / output_geometry.width), + (int)std::floor(1.0 * grab.y / output_geometry.height), + }; + target_ws = target_ws + current_ws; + + auto gsize = ev->focused_output->wset()->get_workspace_grid_size(); + target_ws.x = wf::clamp(target_ws.x, 0, gsize.width - 1); + target_ws.y = wf::clamp(target_ws.y, 0, gsize.height - 1); + + // view to focus at the end of drag + auto focus_view = ev->main_view; + + for (auto& v : ev->all_views) + { + if (!v.view->is_mapped()) + { + // Maybe some dialog got unmapped + continue; + } + + auto bbox = wf::view_bounding_box_up_to(v.view, "wobbly"); + auto wm = v.view->get_geometry(); + + wf::point_t wm_offset = wf::origin(wm) + -wf::origin(bbox); + bbox = wf::remoteview_move_drag::find_geometry_around( + wf::dimensions(bbox), grab, v.relative_grab); + + wf::point_t target = wf::origin(bbox) + wm_offset; + v.view->move(target.x, target.y); + if (v.view->pending_fullscreen()) + { + wf::get_core().default_wm->fullscreen_request(v.view, ev->focused_output, true, target_ws); + } else if (v.view->pending_tiled_edges()) + { + wf::get_core().default_wm->tile_request(v.view, v.view->pending_tiled_edges(), target_ws); + } + + // check focus timestamp and select the last focused view to (re)focus + if (get_focus_timestamp(v.view) > get_focus_timestamp(focus_view)) + { + focus_view = v.view; + } + } + + // Ensure that every view is visible on parent's main workspace + for (auto& v : parent->enumerate_views()) + { + ev->focused_output->wset()->move_to_workspace(v, target_ws); + } + + wf::get_core().default_wm->focus_raise_view(focus_view); +} + +/** + * Adjust the view's state after snap-off. + */ +inline void adjust_view_on_snap_off(wayfire_toplevel_view view) +{ + if (view->pending_tiled_edges() && !view->pending_fullscreen()) + { + wf::get_core().default_wm->tile_request(view, 0); + } +} +} +} diff --git a/src/remote-view/remoteview.cpp b/src/remote-view/remoteview.cpp new file mode 100644 index 0000000..a95a52a --- /dev/null +++ b/src/remote-view/remoteview.cpp @@ -0,0 +1,1205 @@ +/* +The MIT License (MIT) + +Copyright (c) 2018 Iliya Bozhinov +Copyright (c) 2023 Andrew Pliatsikas +Copyright (c) 2023 Scott Moreau + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE.*/ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "remoteview-move-drag-interface.hpp" +#include "remote-view-workspace-wall.hpp" +#include + +#include "ipc-activator.hpp" +#include "wayfire/plugins/common/input-grab.hpp" +#include "wayfire/plugins/common/util.hpp" +#include "wayfire/render-manager.hpp" +#include "wayfire/scene-input.hpp" +#include "wayfire/scene.hpp" +#include "wayfire/signal-definitions.hpp" +#include "wayfire/view.hpp" + +/* TODO: this file should be included in some header maybe(plugin.hpp) */ +#include + + + +bool dragging_window = false; +bool grab_check = false; +bool main_workspace = false; +int animation = 1; + + +class wayfire_remoteview : public wf::per_output_plugin_instance_t, + public wf::keyboard_interaction_t, + public wf::pointer_interaction_t, + public wf::touch_interaction_t { + private: + + wf::option_wrapper_t vwidth_opt{"core/vwidth"}; + // Function to print cursor position + void CursorPos(const wf::pointf_t& cursor_position) { + // printf("CursorPos \n"); + auto size = output->get_screen_size(); + + auto wsize = output->wset()->get_workspace_grid_size(); + auto workspaces_horizontal = wsize.width; + int deskstopsY = wsize.height; + + if (cursor_position.x > size.width - size.width / deskstopsY) { + // printf("dock detected \n"); + + if (grab_check == false) { + output->activate_plugin(&grab_interface); + input_grab->ungrab_input(); + input_grab = std::make_unique("remoteview", output, this, + this, this); + input_grab->grab_input(wf::scene::layer::WORKSPACE); + state.active = true; + // state.button_pressed = false; + state.accepting_input = true; + main_workspace = false; + grab_check = true; + } + } else if (cursor_position.x <= size.width - size.width / deskstopsY) { + // printf("desktop detected \n"); + + if (grab_check == false) { + input_grab->ungrab_input(); + main_workspace = false; + output->deactivate_plugin(&grab_interface); + state.active = true; + // state.button_pressed = false; + state.accepting_input = true; + // this->state.button_pressed = false; + } + } + } + + wf::point_t convert_workspace_index_to_coords(int index) { + printf("convert_workspace_index_to_coords \n"); + index--; // compensate for indexing from 0 + auto wsize = output->wset()->get_workspace_grid_size(); + int x = index % wsize.width; + int y = index / wsize.width; + + return wf::point_t{x, y}; + } + + wf::option_wrapper_t background_color{"remoteview/background"}; + wf::option_wrapper_t zoom_duration{"remoteview/duration"}; + wf::option_wrapper_t delimiter_offset{"remoteview/offset"}; + wf::option_wrapper_t keyboard_interaction{"remoteview/keyboard_interaction"}; + wf::option_wrapper_t inactive_brightness{"remoteview/inactive_brightness"}; + wf::option_wrapper_t transition_length{"remoteview/transition_length"}; + wf::geometry_animation_t zoom_animation{zoom_duration}; + + wf::option_wrapper_t move_enable_snap_off{"move/enable_snap_off"}; + wf::option_wrapper_t move_snap_off_threshold{"move/snap_off_threshold"}; + wf::option_wrapper_t move_join_views{"move/join_views"}; + + wf::shared_data::ref_ptr_t drag_helper; + + wf::option_wrapper_t> + workspace_bindings{"remoteview/workspace_bindings"}; + + std::vector keyboard_select_cbs; + std::vector> + keyboard_select_options; + + struct { + bool active = false; + bool button_pressed = false; + bool zoom_in = false; + bool accepting_input = false; + } state; + + wf::point_t target_ws, initial_ws; + std::unique_ptr wall; + + wf::key_repeat_t key_repeat; + uint32_t key_pressed = 0; + + /* fade animations for each workspace */ + std::vector> ws_fade; + std::unique_ptr input_grab; + + public: + // this function reads workspace-related key bindings from a configuration, + // sets up corresponding callbacks, and associates them with the plugin's + // behavior, particularly related to workspace switching and + // activation/deactivation. + + void setup_workspace_bindings_from_config() { + printf("setup_workspace_bindings_from_config \n"); + for (const auto& [workspace, binding] : workspace_bindings.value()) { + int workspace_index = atoi(workspace.c_str()); + auto wsize = output->wset()->get_workspace_grid_size(); + if ((workspace_index > (wsize.width * wsize.height)) || + (workspace_index < 1)) { + continue; + } + + wf::point_t target = convert_workspace_index_to_coords(workspace_index); + + keyboard_select_options.push_back(wf::create_option(binding)); + keyboard_select_cbs.push_back([=](auto) { + if (!state.active) { + return false; + } else { + if (!zoom_animation.running() || state.zoom_in) { + if (target_ws != target) { + shade_workspace(target_ws, true); + target_ws = target; + shade_workspace(target_ws, false); + } + + deactivate(); + } + } + + return true; + }); + } + } + + wf::plugin_activation_data_t grab_interface = { + .name = "remoteview", + .capabilities = wf::CAPABILITY_MANAGE_COMPOSITOR, + .cancel = [=]() { finalize_and_exit(); }, + }; + + // the init function initializes input grabbing, sets up workspace-related key + // bindings, initializes a workspace wall, connects signal handlers for drag + // events, performs some initializations related to workspace resizing, and + // connects a signal related to workspace grid changes. + + void init() override { + + + + printf(" init \n"); + input_grab = + std::make_unique("remoteview", output, this, this, this); + + setup_workspace_bindings_from_config(); + wall = std::make_unique(this->output); + + drag_helper->connect(&on_drag_output_focus); + drag_helper->connect(&on_drag_snap_off); + + drag_helper->connect(&on_drag_done); + + resize_ws_fade(); + output->connect(&on_workspace_grid_changed); + } + bool handle_toggle() { + if (!state.active) { + return activate(); + } else if (!zoom_animation.running() || state.zoom_in) { + deactivate(); + } + + return true; + } +#//for mouse + void handle_pointer_button(const wlr_pointer_button_event& event) override { + if (event.button != BTN_LEFT) { + return; + } + + auto gc = output->get_cursor_position(); + handle_input_press(gc.x, gc.y, event.state); + } + + //int xdesktops; + void handle_pointer_motion(wf::pointf_t pointer_position, + uint32_t time_ms) override { + auto size = output->get_screen_size(); + + auto wsize = output->wset()->get_workspace_grid_size(); + auto workspaces_horizontal = wsize.width; + int deskstopsY = wsize.height; + if ((int)pointer_position.x <= size.width - size.width / deskstopsY && + dragging_window == false) + + { + input_grab->ungrab_input(); + // input_grab->grab_input(wf::scene::layer::WORKSPACE); + // input_grab->set_wants_raw_input(true); + + // output->deactivate_plugin(&grab_interface);// + grab_check = false; + + } else if ((int)pointer_position.x > size.width - size.width / deskstopsY && + dragging_window == false) { + // input_grab->grab_input(wf::scene::layer::OVERLAY); + // state.active = true; + // state.button_pressed = false; + // state.accepting_input = true; + grab_check = false; + + } + handle_input_move({(int)pointer_position.x, (int)pointer_position.y}); + } + // for keyboard + void handle_keyboard_key(wf::seat_t*, wlr_keyboard_key_event event) + override + { + if (event.state == WLR_KEY_PRESSED) + { + if (should_handle_key()) + { + handle_key_pressed(event.keycode); + } + } else + { + if (event.keycode == key_pressed) + { + key_repeat.disconnect(); + key_pressed = 0; + } + } + } + // for touch screens + void handle_touch_down(uint32_t time_ms, int finger_id, + wf::pointf_t position) override { + if (finger_id > 0) { + return; + } + + auto og = output->get_layout_geometry(); + handle_input_press(position.x - og.x, position.y - og.y, + WLR_BUTTON_PRESSED); + } + + void handle_touch_up(uint32_t time_ms, int finger_id, + wf::pointf_t lift_off_position) override { + if (finger_id > 0) { + return; + } + + handle_input_press(0, 0, WLR_BUTTON_RELEASED); + } + + void handle_touch_motion(uint32_t time_ms, int finger_id, + wf::pointf_t position) override { + if (finger_id > 0) // we handle just the first finger + { + return; + } + + handle_input_move({(int)position.x, (int)position.y}); + } + + bool can_handle_drag() { + return output->is_plugin_active(grab_interface.name); + } + + // In summary, this code appears to handle the snap-off signal during a move + // drag operation. + //"Snapping off" typically refers to the action of detaching a view from its + //current position and allowing it to be freely moved or attached to a + //different location. + // Wrapper function to handle wf::remoteview_move_drag::drag_focus_output_signal + // Add a new signal that matches the correct type + + // Connect adjust_view_on_output_from_focus to on_drag_output_focus + wf::signal::connection_t + on_drag_output_focus = [=](wf::remoteview_move_drag::drag_focus_output_signal* ev) { + if ((ev->focus_output == output) && can_handle_drag()) { + state.button_pressed = true; + auto [vw, vh] = output->wset()->get_workspace_grid_size(); + drag_helper->set_scale(std::max(vw, vh)); + input_grab->set_wants_raw_input(true); + } + + dragging_window = true; + }; + + // This code appears to handle the completion of a move drag operation, + // including wobbly view translation and workspace change signal emission + wf::signal::connection_t on_drag_snap_off = + [=](wf::remoteview_move_drag::snap_off_signal* ev) { + if ((ev->focus_output == output) && can_handle_drag()) { + wf::remoteview_move_drag::adjust_view_on_snap_off(drag_helper->view); + } + + dragging_window = false; + }; + + wf::signal::connection_t on_drag_done = + [=](wf::remoteview_move_drag::drag_done_signal* ev) { + // Code executed when the move drag operation is done + dragging_window = false; + // Check conditions to handle the drag + if ((ev->focused_output == output) && can_handle_drag() && + !drag_helper->is_view_held_in_place()) { + // Check if the dragged view is on the same output + bool same_output = ev->main_view->get_output() == output; + + // Calculate offset and local coordinates + auto offset = wf::origin(output->get_layout_geometry()); + auto local = input_coordinates_to_output_local_coordinates( + ev->grab_position + -offset); + + // Translate wobbly views + for (auto& v : + wf::remoteview_move_drag::get_target_views(ev->main_view, ev->join_views)) { + translate_wobbly(v, local - (ev->grab_position - offset)); + } + + // Adjust grab position and view on the output + if (main_workspace == false) { + ev->grab_position = + local + + offset; // for the position of small window if switch off will + // make window move to smae desktop but local cords + } + + wf::remoteview_move_drag::adjust_view_on_output( + ev); // for the end of drag window + + // Emit view_change_workspace_signal if the view moved to a different + // workspace or If the dragged view is on the same output and has + // moved to a different workspace, it emits a + // view_change_workspace_signal signal. + if (same_output && (move_started_ws != offscreen_point)) { + wf::view_change_workspace_signal data; + data.view = ev->main_view; + data.from = move_started_ws; + data.to = target_ws; + output->emit(&data); + } + + // Reset move_started_ws + move_started_ws = offscreen_point; + } + + // Reset input grab and button pressed state + input_grab->set_wants_raw_input(false); + this->state.button_pressed = false; + }; + + bool activate() { + + wf::workspace_set_t *workspaceSet = output->wset().get(); + + auto wsize = output->wset()->get_workspace_grid_size(); + +if (wsize.width > wsize.height) + { +wf::dimensions_t newGridSize{wsize.height, wsize.height}; +workspaceSet->set_workspace_grid_size(newGridSize); + + } + + printf(" bool activate \n"); + + if (!output->activate_plugin(&grab_interface)) { + return false; + } + + input_grab->grab_input(wf::scene::layer::OVERLAY); + state.active = true; + state.button_pressed = false; + state.accepting_input = true; + start_zoom(true); + + auto cws = output->wset()->get_current_workspace(); + initial_ws = target_ws = cws; + + wall->start_output_renderer(); + output->render->add_effect(&pre_frame, wf::OUTPUT_EFFECT_PRE); + + output->render->schedule_redraw(); + + // auto cws = output->wset()->get_current_workspace(); + // initial_ws = target_ws = cws; + + for (size_t i = 0; i < keyboard_select_cbs.size(); i++) { + output->add_activator(keyboard_select_options[i], + &keyboard_select_cbs[i]); + } + + highlight_active_workspace(); + + return true; + } + + void start_zoom(bool zoom_in) { + wall->set_background_color(background_color); + wall->set_gap_size(this->delimiter_offset); + // float zoom_factor = zoom_in ? 3.5 : 0.5; + + if (animation == 0) { + if (zoom_in) { + zoom_animation.set_start(wall->get_workspace_rectangle( + output->wset()->get_current_workspace())); + + + // Make sure workspaces are centered + auto wsize = output->wset()->get_workspace_grid_size(); + auto size = output->get_screen_size(); + const int maxdim = std::max(wsize.width, wsize.height); + // const int gap = this->delimiter_offset; + const int gap = 0; + const int fullw = (gap + size.width) * maxdim + gap; + const int fullh = (gap + size.height) * maxdim + gap; + + auto rectangle = wall->get_wall_rectangle(); + rectangle.x -= (fullw - rectangle.width) / 2; + rectangle.y -= (fullh - rectangle.height) / 2; + rectangle.width = fullw; + rectangle.height = fullh; + zoom_animation.set_end(rectangle); // u need this + } else { + // zoom_animation.set_start(zoom_animation); //andy note + zoom_animation.set_end(wall->get_workspace_rectangle(target_ws)); + + // zoom_animation.set_end(wall->get_workspace_rectangle(initial_ws)); + // //set this for no sliding of desktop + } + } else if (animation == 1) + + { + if (zoom_in) { + // Make sure workspaces are centered + auto wsize = output->wset()->get_workspace_grid_size(); + auto workspaces_horizontal = wsize.width; + int deskstopsY = wsize.height; + int deskstopsX = wsize.width; + auto size = output->get_screen_size(); + const int maxdim = std::max(wsize.width, wsize.height); + // const int gap = this->delimiter_offset; + const int gap = 0; + const int fullw = (gap + size.width) * deskstopsY + gap; + const int fullh = (gap + size.height) * deskstopsY + gap; + + + auto rectangle = wall->get_wall_rectangle(); + rectangle.x -= ((fullw - rectangle.width + ((rectangle.width)*(deskstopsX-1)/deskstopsX ) ) / 2) + size.width; + rectangle.y -= (fullh - rectangle.height) / 2; + rectangle.width = fullw; + rectangle.height = fullh; + + zoom_animation.set_start(rectangle); + + auto rectangle2 = wall->get_wall_rectangle(); + rectangle2.x -= ((fullw - rectangle2.width + ((rectangle2.width)*(deskstopsX-1)/deskstopsX) ) / 2); + rectangle2.y -= (fullh - rectangle2.height) / 2; + rectangle2.width = fullw; + rectangle2.height = fullh; + + zoom_animation.set_end(rectangle2); // u need this + } else { + // Make sure workspaces are centered + auto wsize = output->wset()->get_workspace_grid_size(); + auto workspaces_horizontal = wsize.width; + int deskstopsY = wsize.height; + int deskstopsX = wsize.width; + + auto size = output->get_screen_size(); + const int maxdim = std::max(wsize.width, wsize.height); + // const int gap = this->delimiter_offset; + const int gap = 0; + const int fullw = (gap + size.width) * deskstopsY + gap; + const int fullh = (gap + size.height) * deskstopsY + gap; + + auto rectangle = wall->get_wall_rectangle(); + rectangle.x -= ((fullw - rectangle.width + ((rectangle.width)*(deskstopsX-1)/deskstopsX ) ) / 2); + rectangle.y -= (fullh - rectangle.height) / 2; + rectangle.width = fullw; + rectangle.height = fullh; + + zoom_animation.set_start(rectangle); + // zoom_animation.set_start(zoom_animation); + zoom_animation.set_end(wall->get_workspace_rectangle(target_ws)); + // zoom_animation.set_end(wf::geometry_t{rectangle.x, rectangle.y, + // rectangle.width, rectangle.height}); + + // zoom_animation.set_end(wall->get_workspace_rectangle(initial_ws)); + // //set this for no sliding of desktop + } + } + state.zoom_in = zoom_in; // u need this + zoom_animation.start(); // for sliding animation + wall->set_viewport( + zoom_animation); // andy note set desktop sliding animation + } + + void finish_zoom(bool zoom_in) { + wall->set_background_color(background_color); + wall->set_gap_size(this->delimiter_offset); + // float zoom_factor = zoom_in ? 3.5 : 0.5; + + if (animation == 0) { + if (zoom_in) { + zoom_animation.set_start(wall->get_workspace_rectangle( + output->wset()->get_current_workspace())); + + + + + // Make sure workspaces are centered + auto wsize = output->wset()->get_workspace_grid_size(); + auto size = output->get_screen_size(); + auto workspaces_horizontal = wsize.width; + int deskstopsY = wsize.height; + int deskstopsX = wsize.width; + const int maxdim = std::max(wsize.width, wsize.height); + // const int gap = this->delimiter_offset; + const int gap = 0; + const int fullw = (gap + size.width) * maxdim + gap; + const int fullh = (gap + size.height) * maxdim + gap; + + auto rectangle = wall->get_wall_rectangle(); + rectangle.x -= ((fullw - rectangle.width + ((rectangle.width)*(deskstopsX-1)/deskstopsX ) ) / 2); + rectangle.y -= (fullh - rectangle.height) / 2; + rectangle.width = fullw; + rectangle.height = fullh; + zoom_animation.set_end(rectangle); // u need this + } else { + // zoom_animation.set_start(zoom_animation); //andy note + zoom_animation.set_end(wall->get_workspace_rectangle(target_ws)); + + // zoom_animation.set_end(wall->get_workspace_rectangle(initial_ws)); + // //set this for no sliding of desktop + } + } else if (animation == 1) + + { + if (zoom_in) { + // Make sure workspaces are centered + auto wsize = output->wset()->get_workspace_grid_size(); + auto workspaces_horizontal = wsize.width; + int deskstopsY = wsize.height; + int deskstopsX = wsize.width; + auto size = output->get_screen_size(); + const int maxdim = std::max(wsize.width, wsize.height); + // const int gap = this->delimiter_offset; + const int gap = 0; + const int fullw = (gap + size.width) * deskstopsY + gap; + const int fullh = (gap + size.height) * deskstopsY + gap; + + auto rectangle = wall->get_wall_rectangle(); + rectangle.x -= ((fullw - rectangle.width) / 2) + size.width+ wsize.width + ((size.width+ wsize.width) *((deskstopsX-1) )/2) ; +// rectangle.x -= ((fullw - rectangle.width + (((rectangle.width/deskstopsX)*(deskstopsX-1)) )) )/ 2 + size.width+ wsize.width; + rectangle.y -= (fullh - rectangle.height) / 2; + rectangle.width = fullw; + rectangle.height = fullh; + + + zoom_animation.set_start(rectangle); + + auto rectangle2 = wall->get_wall_rectangle(); + rectangle2.x -= ((fullw - rectangle2.width) / 2)+ ((size.width+ wsize.width) *((deskstopsX-1) )/2) ; +// rectangle2.x -= ((fullw - rectangle2.width + ((rectangle.width/deskstopsX)*(deskstopsX-1))) )/ 2; + rectangle2.y -= (fullh - rectangle2.height) / 2; + rectangle2.width = fullw; + rectangle2.height = fullh; + + + + zoom_animation.set_end(rectangle2); // u need this + } else { + // Make sure workspaces are centered + auto wsize = output->wset()->get_workspace_grid_size(); + auto workspaces_horizontal = wsize.width; + int deskstopsY = wsize.height; + int deskstopsX = wsize.width; + auto size = output->get_screen_size(); + const int maxdim = std::max(wsize.width, wsize.height); + // const int gap = this->delimiter_offset; + const int gap = 0; + const int fullw = (gap + size.width) * deskstopsY + gap; + const int fullh = (gap + size.height) * deskstopsY + gap; + + auto rectangle = wall->get_wall_rectangle(); + rectangle.x -= ((fullw - rectangle.width) / 2)+ ((size.width+ wsize.width) *((deskstopsX-1) )/2) ; + // rectangle.x -= ((fullw - rectangle.width+ ((rectangle.width/deskstopsX)*(deskstopsX-1))) )/ 2; + rectangle.y -= (fullh - rectangle.height) / 2; + rectangle.width = fullw; + rectangle.height = fullh; + + auto rectangle2 = wall->get_wall_rectangle(); + rectangle2.x -= ((fullw - rectangle2.width) / 2) + size.width+ wsize.width + ((size.width+ wsize.width) *((deskstopsX-1) )/2) ; + + // rectangle2.x -= ((fullw - rectangle2.width+ (((rectangle.width/deskstopsX)*(deskstopsX-1))))) / 2+ size.width+ wsize.width; + rectangle2.y -= (fullh - rectangle2.height) / 2; + rectangle2.width = fullw; + rectangle2.height = fullh; + + zoom_animation.set_start(rectangle); + // zoom_animation.set_start(zoom_animation); //andy note + zoom_animation.set_end(rectangle2); + // zoom_animation.set_end(wf::geometry_t{rectangle.x, rectangle.y, + // rectangle.width, rectangle.height}); + + // zoom_animation.set_end(wall->get_workspace_rectangle(initial_ws)); + // //set this for no sliding of desktop + } + } + state.zoom_in = zoom_in; // u need this + zoom_animation.start(); // for sliding animation + wall->set_viewport( + zoom_animation); // andy note set desktop sliding animation + } + + void deactivate() { + printf("deactivate\n"); + + if (main_workspace == false && target_ws != initial_ws) { + state.accepting_input = false; + start_zoom(false); + } else if (main_workspace == true && dragging_window == false && + target_ws == initial_ws) { + state.accepting_input = true; + start_zoom(true); + } else { + finish_zoom(false); + } + + for (size_t i = 0; i < keyboard_select_cbs.size(); i++) { + output->rem_binding(&keyboard_select_cbs[i]); + } + } + + wf::geometry_t get_grid_geometry() { + auto wsize = output->wset()->get_workspace_grid_size(); + auto full_g = output->get_layout_geometry(); + + auto workspaces_horizontal = wsize.width; + int deskstopsY = wsize.height; + int deskstopsX = wsize.width; + + wf::geometry_t grid; + grid.x = grid.y = 0; + grid.width = full_g.width * wsize.width; + grid.height = full_g.height * wsize.height; + + // printf("grid.width %d grid.height %d",grid.width, grid.height ); + // printf("full_g.width %d full_g.height %d",full_g.width, full_g.height + // ); + return grid; + } + + wf::point_t input_grab_origin; + /** + * Handle an input press event. + * + * @param x, y The position of the event in output-local coordinates. + */ + void handle_input_press(int32_t x, int32_t y, uint32_t state) { + if (zoom_animation.running() || !this->state.active) { + return; + } + + if ((state == WLR_BUTTON_RELEASED) && !this->drag_helper->view) { + this->state.button_pressed = false; + deactivate(); + } else if (state == WLR_BUTTON_RELEASED) { + this->state.button_pressed = false; + this->drag_helper->handle_input_released(); + + } else { + this->state.button_pressed = true; + + input_grab_origin = {x, y}; + update_target_workspace(x, y); + } + } + // The start_moving function you provided is designed to initiate the movement + // of a toplevel view, which typically represents a window in a graphical + // desktop environment. Toplevel views are the highest level of the view + // hierarchy and usually correspond to individual application windows. + + void start_moving(wayfire_toplevel_view view, wf::point_t grab) { + if (!(view->get_allowed_actions() & + (wf::VIEW_ALLOW_WS_CHANGE | wf::VIEW_ALLOW_MOVE))) { + return; + } + + auto ws_coords = input_coordinates_to_output_local_coordinates(grab); + auto bbox = wf::view_bounding_box_up_to(view, "wobbly"); + + view->damage(); + // Make sure that the view is in output-local coordinates! + translate_wobbly(view, grab - ws_coords); + + auto [vw, vh] = output->wset()->get_workspace_grid_size(); + wf::remoteview_move_drag::drag_options_t opts; + + opts.initial_scale = std::max(vw, vh); + + opts.enable_snap_off = + move_enable_snap_off && + (view->pending_fullscreen() || view->pending_tiled_edges()); + opts.snap_off_threshold = move_snap_off_threshold; + opts.join_views = move_join_views; + + auto output_offset = wf::origin(output->get_layout_geometry()); + + drag_helper->start_drag(view, grab + output_offset, + wf::remoteview_move_drag::find_relative_grab(bbox, ws_coords), + opts); // andy note disbale this to get to desktop + // movement of windows nnormally + move_started_ws = target_ws; + input_grab->set_wants_raw_input(true); + } + // this is whre it drag a window about + const wf::point_t offscreen_point = {-10, -10}; + void handle_input_move(wf::point_t to) { + if (!state.button_pressed) { + /* + if (abs(local - input_grab_origin) < 5) + { + Ignore small movements + return; + } + */ + if (dragging_window == true || main_workspace == true) { + auto local = to - wf::origin(output->get_layout_geometry()); + + if (drag_helper->view) { // + drag_helper->handle_motion(to); + } + + LOGI("Motion is ", to, " ", input_grab_origin); + + bool first_click = (input_grab_origin != offscreen_point); + if (!zoom_animation.running()) { + auto view = find_view_at_coordinates(input_grab_origin.x, + input_grab_origin.y); + if (view) { + start_moving(view, input_grab_origin); + drag_helper->handle_motion(to); + } + } + /* As input coordinates are always positive, this will ensure that any + * subsequent motion events while grabbed are allowed */ + input_grab_origin = offscreen_point; + update_target_workspace(local.x, local.y); + } + + return; + } + + dragging_window = true; + + auto local = to - wf::origin(output->get_layout_geometry()); + + if (drag_helper->view) { + drag_helper->handle_motion(to); + } + + LOGI("Motion is ", to, " ", input_grab_origin); + + if (abs(local - input_grab_origin) < 5) { + /* Ignore small movements */ + return; + } + + bool first_click = (input_grab_origin != offscreen_point); + if (!zoom_animation.running() && first_click) { + auto view = + find_view_at_coordinates(input_grab_origin.x, input_grab_origin.y); + if (view) { + start_moving(view, input_grab_origin); + drag_helper->handle_motion(to); + } + } + + /* As input coordinates are always positive, this will ensure that any + * subsequent motion events while grabbed are allowed */ + input_grab_origin = offscreen_point; + update_target_workspace(local.x, local.y); + } + + /** + * Helper to determine if keyboard presses should be handled + */ + bool should_handle_key() { + return state.accepting_input && keyboard_interaction && + !state.button_pressed; + } + + void handle_key_pressed(uint32_t key) + { + wf::point_t old_target = target_ws; + + switch (key) + { + case KEY_ENTER: + deactivate(); + return; + + case KEY_ESC: + target_ws = initial_ws; + shade_workspace(old_target, true); + shade_workspace(target_ws, false); + deactivate(); + return; + + case KEY_UP: + case KEY_K: + target_ws.y -= 1; + break; + + case KEY_DOWN: + case KEY_J: + target_ws.y += 1; + break; +/* + case KEY_RIGHT: + case KEY_L: + target_ws.x += ; + break; + + case KEY_LEFT: + case KEY_H: + target_ws.x -= 1; + break; +*/ + default: + return; + } + + // this part is only reached if one of the arrow keys is pressed + if (key != key_pressed) + { + // update key repeat callbacks + // (note: this will disconnect any previous callback) + key_repeat.set_callback(key, [this] (uint32_t key) + { + if (!should_handle_key()) + { + // disconnect if key events should no longer be handled + key_pressed = 0; + return false; + } + + handle_key_pressed(key); + return true; // repeat + }); + + key_pressed = key; + } + + // ensure that the new target is valid (use wrap-around) + auto dim = output->wset()->get_workspace_grid_size(); + target_ws.x = (target_ws.x + dim.width) % dim.width; + target_ws.y = (target_ws.y + dim.height) % dim.height; + + shade_workspace(old_target, true); + shade_workspace(target_ws, false); + } + + /** + * shade all but the selected workspace instantly (without animation) + */ + void highlight_active_workspace() { + auto dim = output->wset()->get_workspace_grid_size(); + for (int x = 0; x < dim.width; x++) { + for (int y = 0; y < dim.height; y++) { + if ((x == target_ws.x) && (y == target_ws.y)) { + wall->set_ws_dim({x, y}, 1.0); + } else { + wall->set_ws_dim({x, y}, inactive_brightness); + } + } + } + } + + /** + * start an animation for shading the given workspace + */ + void shade_workspace(const wf::point_t& ws, bool shaded) { + double target = shaded ? inactive_brightness : 1.0; + auto& anim = ws_fade.at(ws.x).at(ws.y); + + if (anim.running()) { + anim.animate(target); + } else { + anim.animate(shaded ? 1.0 : inactive_brightness, target); + } + + output->render->schedule_redraw(); + } + + wf::point_t move_started_ws = offscreen_point; + wf::option_wrapper_t vheight_opt{"core/vheight"}; + /** + * Find the coordinate of the given point from output-local coordinates + * to coordinates relative to the first workspace (i.e (0,0)) + */ + void input_coordinates_to_global_coordinates(int& sx, int& sy) { + auto og = output->get_layout_geometry(); + auto size = output->get_screen_size(); + auto wsize = output->wset()->get_workspace_grid_size(); + auto workspaces_horizontal = wsize.width; + auto workspaces_vertical = wsize.height; + + int deskstopsY = wsize.height; + int deskstopsX = wsize.width; + + // auto wsize = output->wset()->get_workspace_grid_size(); + float max = std::max(wsize.width, wsize.height); +//((size.width+ wsize.width) *((deskstopsX-1) )/2) + // float grid_start_x = (og.width * (max - wsize.width) / float(max) / 2)+ + // (size.width/2) - 180; workspace_rect.width/numberofdesktopinYdirection/2 + float grid_start_x = (og.width * (max - wsize.width + ((wsize.width/deskstopsX)*(deskstopsX-1)) ) / float(max) / 2) + size.width / 2 - (size.width / deskstopsY / 2 ); + float grid_start_y = og.height * (max - wsize.height) / float(max) / 2; + + sx -= grid_start_x; + sy -= grid_start_y; + + sx *= max; + sy *= max; + + // printf("grid %d", windows); + } + + /** + * Find the coordinate of the given point from output-local coordinates + * to output-workspace-local coordinates + */ + + // In summary, the function takes an input point in output-local coordinates, + // converts it to global coordinates, and then translates it into the + // output-workspace-local coordinate system relative to the current workspace. + // The final result is the coordinate of the given point in the + // output-workspace-local + // coordinate system. + wf::point_t input_coordinates_to_output_local_coordinates(wf::point_t ip) { + input_coordinates_to_global_coordinates(ip.x, ip.y); + + auto cws = output->wset()->get_current_workspace(); + auto og = output->get_relative_geometry(); + + /* Translate coordinates into output-local coordinate system, + * relative to the current workspace */ + return { + ip.x - cws.x * og.width, + ip.y - cws.y * og.height, + }; + } + + wayfire_toplevel_view find_view_at_coordinates(int gx, int gy) { + auto local = input_coordinates_to_output_local_coordinates({gx, gy}); + wf::pointf_t localf = {1.0 * local.x, 1.0 * local.y}; + + // Print the local coordinates + // std::cout << "Local Coordinates: x = " << localf.x << ", y = " << + // localf.y << std::endl; + printf("localf.x = %f,localf.y =%f/n", localf.x, localf.y); + + return wf::find_output_view_at(output, localf); + } + + void update_target_workspace(int x, int y) { + auto og = output->get_layout_geometry(); + + input_coordinates_to_global_coordinates(x, y); + + auto size = output->get_screen_size(); + auto wsize = output->wset()->get_workspace_grid_size(); + auto workspaces_horizontal = wsize.width; + auto workspaces_vertical = wsize.height; + int deskstopsY = wsize.height; + float max = std::max(wsize.width, wsize.height); + float grid_start_x = (og.width * (max - wsize.width) / float(max) / 2) + + size.width / 2 - (size.width / deskstopsY / 2); + + // wf::pointf_t cursor_position = wf::get_core().get_cursor_position(); + + if (x >= 0) { + main_workspace = false; + auto [vw, vh] = output->wset()->get_workspace_grid_size(); + drag_helper->set_scale(std::max(vw, vh)); + input_grab->set_wants_raw_input(true); + + auto grid = get_grid_geometry(); + if (!(grid & wf::point_t{x, y})) { + return; + } + + int tmpx = x / og.width; + int tmpy = y / og.height; + if ((tmpx != target_ws.x) || (tmpy != target_ws.y)) { + shade_workspace(target_ws, true); + + target_ws = {tmpx, tmpy}; + shade_workspace(target_ws, false); + } + + } else if (x < 0) { + + main_workspace = true; + int tmpx = x / og.width; + int tmpy = y / og.height; + // if ((tmpx != target_ws.x) || (tmpy != target_ws.y)) + { + // target_ws = initial_ws; + shade_workspace(target_ws, true); + target_ws = initial_ws; + + shade_workspace(target_ws, false); + } + + auto [vw, vh] = output->wset()->get_workspace_grid_size(); + drag_helper->set_scale(std::max(vw / deskstopsY, vh / deskstopsY)); + input_grab->set_wants_raw_input(true); + // input_grab->ungrab_input(); + + // drag_helper->handle_input_released(); + } + } + wf::effect_hook_t pre_frame = [=]() { + + auto cws = output->wset()->get_current_workspace(); + + workspaceX_pos= cws.x; + + output->render->damage_whole(); + // Get the cursor position + wf::pointf_t cursor_position = wf::get_core().get_cursor_position(); + + // Call the printCursorPos function + CursorPos(cursor_position); + + if (zoom_animation.running()) { + wall->set_viewport(zoom_animation); + } else if (!state.zoom_in) { + wall->set_viewport(zoom_animation); + finalize_and_exit(); + return; + } + + auto size = this->output->wset()->get_workspace_grid_size(); + for (int x = 0; x < size.width; x++) { + for (int y = 0; y < size.height; y++) { + auto& anim = ws_fade.at(x).at(y); + if (anim.running()) { + wall->set_ws_dim({x, y}, anim); + } + } + } + }; + + void resize_ws_fade() { + auto size = this->output->wset()->get_workspace_grid_size(); + ws_fade.resize(size.width); + for (auto& v : ws_fade) { + size_t h = size.height; + if (v.size() > h) { + v.resize(h); + } else { + while (v.size() < h) { + v.emplace_back(transition_length); + } + } + } + } + + wf::signal::connection_t + on_workspace_grid_changed = [=](auto) { + resize_ws_fade(); + + // check that the target and initial workspaces are still in the grid + auto size = this->output->wset()->get_workspace_grid_size(); + initial_ws.x = std::min(initial_ws.x, size.width - 1); + initial_ws.y = std::min(initial_ws.y, size.height - 1); + + if ((target_ws.x >= size.width) || (target_ws.y >= size.height)) { + target_ws.x = std::min(target_ws.x, size.width - 1); + target_ws.y = std::min(target_ws.y, size.height - 1); + highlight_active_workspace(); + } + }; + + void finalize_and_exit() { + state.active = false; + if (drag_helper->view) { + drag_helper->handle_input_released(); + } + + +if (target_ws == initial_ws) +{ + auto cws = output->wset()->get_current_workspace(); + output->wset()->set_workspace({cws.x,cws.y}); + + +}else{ + output->wset()->set_workspace( + {workspaceX_pos,target_ws.y}); // andy note change desktop after zoom +} + output->deactivate_plugin(&grab_interface); // + input_grab->ungrab_input(); + wall->stop_output_renderer(true); // + output->render->rem_effect(&pre_frame); + key_repeat.disconnect(); + key_pressed = 0; + + for (size_t i = 0; i < keyboard_select_cbs.size(); i++) + { + output->add_activator(keyboard_select_options[i], + &keyboard_select_cbs[i]); + } + + highlight_active_workspace(); + } + + void fini() override { + if (state.active) { + finalize_and_exit(); + } + } +}; + +class wayfire_remoteview_global + : public wf::plugin_interface_t, + public wf::per_output_tracker_mixin_t { + wf::ipc_activator_t toggle_binding{"remoteview/toggle"}; + + public: + void init() override { + this->init_output_tracking(); + toggle_binding.set_handler(toggle_cb); + } + + void fini() override { this->fini_output_tracking(); } + + wf::ipc_activator_t::handler_t toggle_cb = [=](wf::output_t* output, + wayfire_view) { + return this->output_instance[output]->handle_toggle(); + }; +}; + +DECLARE_WAYFIRE_PLUGIN(wayfire_remoteview_global); From 5a5b4b49c726f1f1ecd7facdfb2a06927e0f9e56 Mon Sep 17 00:00:00 2001 From: andrew pliatsikas Date: Sun, 25 Feb 2024 05:50:15 +0000 Subject: [PATCH 2/2] Initial commit of remote-view --- src/remote-view/remoteview.cpp | 33 +++++++++++++++------------------ 1 file changed, 15 insertions(+), 18 deletions(-) diff --git a/src/remote-view/remoteview.cpp b/src/remote-view/remoteview.cpp index a95a52a..313c6b2 100644 --- a/src/remote-view/remoteview.cpp +++ b/src/remote-view/remoteview.cpp @@ -150,13 +150,12 @@ class wayfire_remoteview : public wf::per_output_plugin_instance_t, std::unique_ptr input_grab; public: - // this function reads workspace-related key bindings from a configuration, + // reads workspace-related key bindings from a configuration, // sets up corresponding callbacks, and associates them with the plugin's - // behavior, particularly related to workspace switching and - // activation/deactivation. + // behavior void setup_workspace_bindings_from_config() { - printf("setup_workspace_bindings_from_config \n"); + // printf("setup_workspace_bindings_from_config \n"); for (const auto& [workspace, binding] : workspace_bindings.value()) { int workspace_index = atoi(workspace.c_str()); auto wsize = output->wset()->get_workspace_grid_size(); @@ -321,8 +320,7 @@ class wayfire_remoteview : public wf::per_output_plugin_instance_t, return output->is_plugin_active(grab_interface.name); } - // In summary, this code appears to handle the snap-off signal during a move - // drag operation. + // handle the snap-off signal during a move drag operation. //"Snapping off" typically refers to the action of detaching a view from its //current position and allowing it to be freely moved or attached to a //different location. @@ -342,7 +340,7 @@ class wayfire_remoteview : public wf::per_output_plugin_instance_t, dragging_window = true; }; - // This code appears to handle the completion of a move drag operation, + // handle the completion of a move drag operation, // including wobbly view translation and workspace change signal emission wf::signal::connection_t on_drag_snap_off = [=](wf::remoteview_move_drag::snap_off_signal* ev) { @@ -479,7 +477,7 @@ workspaceSet->set_workspace_grid_size(newGridSize); rectangle.height = fullh; zoom_animation.set_end(rectangle); // u need this } else { - // zoom_animation.set_start(zoom_animation); //andy note + // zoom_animation.set_start(zoom_animation); // zoom_animation.set_end(wall->get_workspace_rectangle(target_ws)); // zoom_animation.set_end(wall->get_workspace_rectangle(initial_ws)); @@ -550,7 +548,7 @@ workspaceSet->set_workspace_grid_size(newGridSize); state.zoom_in = zoom_in; // u need this zoom_animation.start(); // for sliding animation wall->set_viewport( - zoom_animation); // andy note set desktop sliding animation + zoom_animation); // set desktop sliding animation } void finish_zoom(bool zoom_in) { @@ -585,7 +583,7 @@ workspaceSet->set_workspace_grid_size(newGridSize); rectangle.height = fullh; zoom_animation.set_end(rectangle); // u need this } else { - // zoom_animation.set_start(zoom_animation); //andy note + // zoom_animation.set_start(zoom_animation); // zoom_animation.set_end(wall->get_workspace_rectangle(target_ws)); // zoom_animation.set_end(wall->get_workspace_rectangle(initial_ws)); @@ -656,7 +654,7 @@ workspaceSet->set_workspace_grid_size(newGridSize); rectangle2.height = fullh; zoom_animation.set_start(rectangle); - // zoom_animation.set_start(zoom_animation); //andy note + // zoom_animation.set_start(zoom_animation); // zoom_animation.set_end(rectangle2); // zoom_animation.set_end(wf::geometry_t{rectangle.x, rectangle.y, // rectangle.width, rectangle.height}); @@ -668,7 +666,7 @@ workspaceSet->set_workspace_grid_size(newGridSize); state.zoom_in = zoom_in; // u need this zoom_animation.start(); // for sliding animation wall->set_viewport( - zoom_animation); // andy note set desktop sliding animation + zoom_animation); // set desktop sliding animation } void deactivate() { @@ -734,8 +732,7 @@ workspaceSet->set_workspace_grid_size(newGridSize); update_target_workspace(x, y); } } - // The start_moving function you provided is designed to initiate the movement - // of a toplevel view, which typically represents a window in a graphical + // initiate the movement of a toplevel view, which typically represents a window in a graphical // desktop environment. Toplevel views are the highest level of the view // hierarchy and usually correspond to individual application windows. @@ -767,12 +764,12 @@ workspaceSet->set_workspace_grid_size(newGridSize); drag_helper->start_drag(view, grab + output_offset, wf::remoteview_move_drag::find_relative_grab(bbox, ws_coords), - opts); // andy note disbale this to get to desktop + opts); // disbale this to get to desktop // movement of windows nnormally move_started_ws = target_ws; input_grab->set_wants_raw_input(true); } - // this is whre it drag a window about + // this is where it drags a window about const wf::point_t offscreen_point = {-10, -10}; void handle_input_move(wf::point_t to) { if (!state.button_pressed) { @@ -990,7 +987,7 @@ workspaceSet->set_workspace_grid_size(newGridSize); * to output-workspace-local coordinates */ - // In summary, the function takes an input point in output-local coordinates, + // takes an input point in output-local coordinates, // converts it to global coordinates, and then translates it into the // output-workspace-local coordinate system relative to the current workspace. // The final result is the coordinate of the given point in the @@ -1158,7 +1155,7 @@ if (target_ws == initial_ws) }else{ output->wset()->set_workspace( - {workspaceX_pos,target_ws.y}); // andy note change desktop after zoom + {workspaceX_pos,target_ws.y}); // change desktop after zoom } output->deactivate_plugin(&grab_interface); // input_grab->ungrab_input();