diff --git a/ipc-scripts/pin-view.py b/ipc-scripts/pin-view.py new file mode 100755 index 0000000..6bf4667 --- /dev/null +++ b/ipc-scripts/pin-view.py @@ -0,0 +1,24 @@ +#!/usr/bin/python3 + +import os +import sys +from wayfire import WayfireSocket +from wayfire.extra.wpe import WPE + +if len(sys.argv) < 4: + print(f"Usage: {sys.argv[0]} [workspace_x: int], [workspace_y: int]") + exit(1) + +addr = os.getenv("WAYFIRE_SOCKET") + +sock = WayfireSocket(addr) +wpe = WPE(sock) + +for view in sock.list_views(): + if view["id"] == int(sys.argv[1]): + if len(sys.argv) == 4: + wpe.pin_view(int(sys.argv[1]), sys.argv[2], sys.argv[3].lower() == "true", None, None) + if len(sys.argv) == 5: + wpe.pin_view(int(sys.argv[1]), sys.argv[2], sys.argv[3].lower() == "true", int(sys.argv[4]), None) + elif len(sys.argv) == 6: + wpe.pin_view(int(sys.argv[1]), sys.argv[2], sys.argv[3].lower() == "true", int(sys.argv[4]), int(sys.argv[5])) diff --git a/ipc-scripts/unpin-view.py b/ipc-scripts/unpin-view.py new file mode 100755 index 0000000..6d91836 --- /dev/null +++ b/ipc-scripts/unpin-view.py @@ -0,0 +1,17 @@ +#!/usr/bin/python3 + +import os +import sys +from wayfire_socket import * + +if len(sys.argv) < 2: + print(f"Usage: {sys.argv[0]} ") + exit(1) + +addr = os.getenv('WAYFIRE_SOCKET') + +commands_sock = WayfireSocket(addr) + +for view in commands_sock.list_views(): + if view["id"] == int(sys.argv[1]): + commands_sock.unpin_view(int(sys.argv[1])) diff --git a/ipc-scripts/wayfire_socket.py b/ipc-scripts/wayfire_socket.py index 9d3933c..a79f25c 100644 --- a/ipc-scripts/wayfire_socket.py +++ b/ipc-scripts/wayfire_socket.py @@ -76,3 +76,19 @@ def ghost_view_toggle(self, view_id: int): message = get_msg_template("ghost/ghost_toggle") message["data"]["view-id"] = view_id return self.send_json(message) + + def pin_view(self, view_id: int, layer: str, ws_x: int, ws_y: int): + message = get_msg_template("pin-view/pin") + message["data"]["view-id"] = view_id + message["data"]["layer"] = layer + if ws_x != None: + message["data"]["x"] = ws_x + message["data"]["y"] = 0 + if ws_y != None: + message["data"]["y"] = ws_y + return self.send_json(message) + + def unpin_view(self, view_id: int): + message = get_msg_template("pin-view/unpin") + message["data"]["view-id"] = view_id + return self.send_json(message) diff --git a/metadata/meson.build b/metadata/meson.build index 60d6457..e2c7e9a 100644 --- a/metadata/meson.build +++ b/metadata/meson.build @@ -13,6 +13,7 @@ install_data('join-views.xml', install_dir: wayfire.get_variable(pkgconfig: 'met install_data('keycolor.xml', install_dir: wayfire.get_variable(pkgconfig: 'metadatadir')) install_data('mag.xml', install_dir: wayfire.get_variable(pkgconfig: 'metadatadir')) install_data('obs.xml', install_dir: wayfire.get_variable(pkgconfig: 'metadatadir')) +install_data('pin-view.xml', install_dir: wayfire.get_variable(pkgconfig: 'metadatadir')) install_data('showrepaint.xml', install_dir: wayfire.get_variable(pkgconfig: 'metadatadir')) install_data('view-shot.xml', install_dir: wayfire.get_variable(pkgconfig: 'metadatadir')) install_data('water.xml', install_dir: wayfire.get_variable(pkgconfig: 'metadatadir')) diff --git a/metadata/pin-view.xml b/metadata/pin-view.xml new file mode 100644 index 0000000..f9dc2fa --- /dev/null +++ b/metadata/pin-view.xml @@ -0,0 +1,8 @@ + + + + <_short>Pin View + <_long>Set a view layer, role and workspace + Window Management + + diff --git a/src/meson.build b/src/meson.build index a824dc8..d09b4b8 100644 --- a/src/meson.build +++ b/src/meson.build @@ -66,6 +66,9 @@ if json.found() obs = shared_module('obs', 'obs.cpp', dependencies: [wayfire, json], install: true, install_dir: join_paths(get_option('libdir'), 'wayfire')) +pin_view = shared_module('pin-view', 'pin-view.cpp', + dependencies: [wayfire, json], + install: true, install_dir: join_paths(get_option('libdir'), 'wayfire')) endif showrepaint = shared_module('showrepaint', 'showrepaint.cpp', diff --git a/src/pin-view.cpp b/src/pin-view.cpp new file mode 100644 index 0000000..2630cba --- /dev/null +++ b/src/pin-view.cpp @@ -0,0 +1,237 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2024 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 +#include +#include +#include +#include + +namespace wf +{ +namespace pin_view +{ +class pin_view_data : public wf::custom_data_t +{ + public: + wf::geometry_t geometry; + wf::point_t workspace; + wf::view_role_t role; +}; +class wayfire_pin_view : public wf::plugin_interface_t +{ + wf::shared_data::ref_ptr_t ipc_repo; + + public: + void init() override + { + ipc_repo->register_method("pin-view/pin", ipc_pin_view); + ipc_repo->register_method("pin-view/unpin", ipc_unpin_view); + } + + wf::ipc::method_callback ipc_pin_view = [=] (nlohmann::json data) -> nlohmann::json + { + WFJSON_EXPECT_FIELD(data, "view-id", number_unsigned); + WFJSON_EXPECT_FIELD(data, "layer", string); + WFJSON_EXPECT_FIELD(data, "resize", boolean); + /* workspace x,y */ + WFJSON_OPTIONAL_FIELD(data, "x", number_unsigned); + WFJSON_OPTIONAL_FIELD(data, "y", number_unsigned); + + auto view = wf::ipc::find_view_by_id(data["view-id"]); + if (view) + { + auto output = view->get_output(); + output->connect(&on_workspace_changed); + if (!view->get_data()) + { + pin_view_data pv_data; + pv_data.role = view->role; + + if (auto toplevel = toplevel_cast(view)) + { + pv_data.workspace = output->wset()->get_view_main_workspace(toplevel); + pv_data.geometry = toplevel->get_geometry(); + } + + view->store_data(std::make_unique(pv_data)); + } + + wf::scene::layer layer; + if (data["layer"] == "background") + { + layer = wf::scene::layer::BACKGROUND; + } else if (data["layer"] == "bottom") + { + layer = wf::scene::layer::BOTTOM; + } else if (data["layer"] == "workspace") + { + layer = wf::scene::layer::WORKSPACE; + } else if (data["layer"] == "top") + { + layer = wf::scene::layer::TOP; + } else if (data["layer"] == "unmanaged") + { + layer = wf::scene::layer::UNMANAGED; + } else if (data["layer"] == "overlay") + { + layer = wf::scene::layer::OVERLAY; + } else if (data["layer"] == "lock") + { + layer = wf::scene::layer::LOCK; + } else + { + layer = wf::scene::layer::TOP; + } + + bool resize = data["resize"]; + auto og = output->get_relative_geometry(); + int x = 0, y = 0; + if (data.contains("x")) + { + x = data["x"].get(); + if (data.contains("y")) + { + y = data["y"].get(); + } + + wf::point_t nws{x, y}; + if (auto toplevel = toplevel_cast(view)) + { + auto vg = toplevel->get_geometry(); + auto cws = output->wset()->get_view_main_workspace(toplevel); + toplevel->set_geometry(wf::geometry_t{(nws.x - cws.x) * og.width, + (nws.y - cws.y) * og.height, resize ? og.width : vg.width, + resize ? og.height : vg.height}); + } + } else + { + if (auto toplevel = toplevel_cast(view)) + { + auto vg = toplevel->get_geometry(); + wf::point_t nws = output->wset()->get_current_workspace(); + auto cws = output->wset()->get_view_main_workspace(toplevel); + toplevel->move(vg.x + (nws.x - cws.x) * og.width, vg.y + (nws.y - cws.y) * og.height); + if (resize) + { + toplevel->set_geometry(og); + } + } + + view->role = wf::VIEW_ROLE_DESKTOP_ENVIRONMENT; + wf::scene::readd_front(view->get_output()->node_for_layer(layer), view->get_root_node()); + return wf::ipc::json_ok(); + } + + wf::scene::readd_front(output->node_for_layer(layer), view->get_root_node()); + } else + { + return wf::ipc::json_error("Failed to find view with given id."); + } + + return wf::ipc::json_ok(); + }; + + wf::ipc::method_callback ipc_unpin_view = [=] (nlohmann::json data) -> nlohmann::json + { + WFJSON_EXPECT_FIELD(data, "view-id", number_unsigned); + + auto view = wf::ipc::find_view_by_id(data["view-id"]); + if (view && view->get_data()) + { + auto pvd = view->get_data(); + view->role = pvd->role; + wf::scene::readd_front(view->get_output()->wset()->get_node(), view->get_root_node()); + if (auto toplevel = toplevel_cast(view)) + { + auto output = view->get_output(); + auto og = output->get_relative_geometry(); + auto cws = output->wset()->get_view_main_workspace(toplevel); + auto vg = toplevel->get_geometry(); + toplevel->move(vg.x + (pvd->workspace.x - cws.x) * og.width, + vg.y + (pvd->workspace.y - cws.y) * og.height); + toplevel->set_geometry(pvd->geometry); + } + + view->release_data(); + } else + { + LOGE("Failed to find view with given id. Perhaps it is not pinned."); + return wf::ipc::json_error("Failed to find view with given id. Perhaps it is not pinned."); + } + + return wf::ipc::json_ok(); + }; + + wf::signal::connection_t on_workspace_changed = + [=] (wf::workspace_changed_signal *ev) + { + auto nws = ev->new_viewport; + auto output = ev->output; + auto og = output->get_relative_geometry(); + for (auto & view : wf::get_core().get_all_views()) + { + auto pvd = view->get_data(); + if (!pvd || (view->role != wf::VIEW_ROLE_DESKTOP_ENVIRONMENT)) + { + continue; + } + + if (auto toplevel = toplevel_cast(view)) + { + auto cws = output->wset()->get_view_main_workspace(toplevel); + auto vg = toplevel->get_geometry(); + toplevel->move(vg.x + (nws.x - cws.x) * og.width, vg.y + (nws.y - cws.y) * og.height); + } + } + }; + + void fini() override + { + for (auto & view : wf::get_core().get_all_views()) + { + if (view->get_data()) + { + view->release_data(); + } + } + + ipc_repo->unregister_method("pin-view/pin"); + ipc_repo->unregister_method("pin-view/unpin"); + on_workspace_changed.disconnect(); + } +}; +} +} + +DECLARE_WAYFIRE_PLUGIN(wf::pin_view::wayfire_pin_view);