From ef2836b9139aeaf4c287c35ac7828445a619769c Mon Sep 17 00:00:00 2001 From: Ilia Bozhinov Date: Sat, 29 Jun 2024 16:26:17 +0200 Subject: [PATCH 1/5] ipc-scripts: update to use official wayfire python bindings --- ipc-scripts/cfg.py | 6 +- ipc-scripts/headless.py | 7 +- ipc-scripts/inactive-alpha.py | 11 +- ipc-scripts/input.py | 7 +- ipc-scripts/ipc-bindings.py | 19 ++- ipc-scripts/ipc-rules-demo.py | 23 ++- ipc-scripts/list-methods.py | 14 +- ipc-scripts/master-stack-tile-layout.py | 39 ++---- ipc-scripts/move-to-workspace.py | 44 ------ ipc-scripts/set-touch-state.py | 6 +- ipc-scripts/wayfire_socket.py | 179 ------------------------ 11 files changed, 42 insertions(+), 313 deletions(-) delete mode 100755 ipc-scripts/move-to-workspace.py delete mode 100644 ipc-scripts/wayfire_socket.py diff --git a/ipc-scripts/cfg.py b/ipc-scripts/cfg.py index 669b7cab8..97574469d 100644 --- a/ipc-scripts/cfg.py +++ b/ipc-scripts/cfg.py @@ -1,9 +1,7 @@ -import wayfire_socket as ws -import os +from wayfire.ipc import WayfireSocket import sys -addr = os.getenv('WAYFIRE_SOCKET') -sock = ws.WayfireSocket(addr) +sock = WayfireSocket() if sys.argv[1] == "get": opt = sock.get_option_value(sys.argv[2]) diff --git a/ipc-scripts/headless.py b/ipc-scripts/headless.py index 9a4697e65..a1b041e62 100644 --- a/ipc-scripts/headless.py +++ b/ipc-scripts/headless.py @@ -1,11 +1,8 @@ -import wayfire_socket as ws -import os +from wayfire.ipc import WayfireSocket import json import argparse -addr = os.getenv('WAYFIRE_SOCKET') -sock = ws.WayfireSocket(addr) - +sock = WayfireSocket() parser = argparse.ArgumentParser() subparsers = parser.add_subparsers(dest='action') diff --git a/ipc-scripts/inactive-alpha.py b/ipc-scripts/inactive-alpha.py index 364d1c359..ae3cc932b 100644 --- a/ipc-scripts/inactive-alpha.py +++ b/ipc-scripts/inactive-alpha.py @@ -2,17 +2,14 @@ # # This script demonstrates how Wayfire's IPC can be used to set the opacity of inactive views. -import os -from wayfire_socket import * +from wayfire.ipc import WayfireSocket -addr = os.getenv('WAYFIRE_SOCKET') -sock = WayfireSocket(addr) +sock = WayfireSocket() sock.watch(['view-focused']) last_focused_toplevel = -1 while True: - msg = sock.read_message() - # The view-mapped event is emitted when a new window has been opened. + msg = sock.read_next_event() if "event" in msg: print(msg["event"]) view = msg["view"] @@ -20,7 +17,7 @@ if last_focused_toplevel != new_focus: if last_focused_toplevel != -1 and new_focus != -1: try: - sock.set_view_alpha(last_focused_toplevel, 0.8) + sock.set_view_alpha(last_focused_toplevel, 0.6) except: print("Last focused toplevel was closed?") diff --git a/ipc-scripts/input.py b/ipc-scripts/input.py index c55a12691..10f85eb0f 100644 --- a/ipc-scripts/input.py +++ b/ipc-scripts/input.py @@ -1,5 +1,4 @@ -import wayfire_socket as ws -import os +from wayfire.ipc import WayfireSocket import argparse import tabulate @@ -22,9 +21,7 @@ print('Invalid usage, an input id >= 0 is required!') exit(-1) -addr = os.getenv('WAYFIRE_SOCKET') -sock = ws.WayfireSocket(addr) - +sock = WayfireSocket() devices = sock.list_input_devices() def find_device_id(name_or_id_or_type): diff --git a/ipc-scripts/ipc-bindings.py b/ipc-scripts/ipc-bindings.py index 750282248..a7c599108 100644 --- a/ipc-scripts/ipc-bindings.py +++ b/ipc-scripts/ipc-bindings.py @@ -3,29 +3,28 @@ # This script demonstrates how to use custom bindings to achieve more complex command sequences in Wayfire # The binding realised with this script is double pressing and releasing the left control button, which causes Expo to be activated. -import os import time -from wayfire_socket import * +from wayfire.ipc import WayfireSocket -addr = os.getenv('WAYFIRE_SOCKET') +sock = WayfireSocket() -sock = WayfireSocket(addr) - -response = sock.register_binding('', 'press', True) +response = sock.register_binding('KEY_D', 'press', True) +print(response) binding_id = response['binding-id'] last_release_time = 0 MAX_DELAY = 0.5 while True: - msg = sock.read_message() + msg = sock.read_next_event() + print(msg) if "event" in msg and msg["event"] == "command-binding": assert msg['binding-id'] == binding_id now = time.time() if now - last_release_time <= MAX_DELAY: - msg = get_msg_template("expo/toggle") - sock.send_json(msg) - + print("toggle") + sock.toggle_expo() last_release_time = now - 2 * MAX_DELAY # Prevent triple press else: + print("reset") last_release_time = now diff --git a/ipc-scripts/ipc-rules-demo.py b/ipc-scripts/ipc-rules-demo.py index 78b1bb5de..54b6f7fa7 100644 --- a/ipc-scripts/ipc-rules-demo.py +++ b/ipc-scripts/ipc-rules-demo.py @@ -9,25 +9,18 @@ # Lastly, this script can be run from a terminal for testing purposes, or started as an autostart entry. # It is safe to kill/restart the process at any point in time. -import os -from wayfire_socket import * +from wayfire.ipc import WayfireSocket -addr = os.getenv('WAYFIRE_SOCKET') - -# Important: we connect to Wayfire's IPC two times. The one socket is used for reading events (view-mapped, view-focused, etc). -# The other is used for sending commands and querying Wayfire. -# We could use the same socket, but this would complicate reading responses, as events and query responses would be mixed with just one socket. -events_sock = WayfireSocket(addr) -commands_sock = WayfireSocket(addr) -events_sock.watch(['view-mapped']) +sock = WayfireSocket() +sock.watch(['view-mapped']) while True: - msg = events_sock.read_message() + msg = sock.read_next_event() # The view-mapped event is emitted when a new window has been opened. if "event" in msg: view = msg["view"] if view["app-id"] == "gedit": - output_data = commands_sock.query_output(view["output"]) + output_data = sock.query_output(view["output"]) print(output_data) workarea = output_data["workarea"] @@ -37,7 +30,7 @@ w = workarea['width'] // 2 h = workarea['height'] // 2 - commands_sock.configure_view(view["id"], x, y, w, h) + sock.configure_view(view["id"], x, y, w, h) # sock.assign_slot(view["id"], "slot_br") - commands_sock.set_always_on_top(view["id"], True) - commands_sock.set_view_alpha(view["id"], 0.5) + sock.set_view_always_on_top(view["id"], True) + sock.set_view_alpha(view["id"], 0.5) diff --git a/ipc-scripts/list-methods.py b/ipc-scripts/list-methods.py index 1d7f3d7ad..db86f28a6 100644 --- a/ipc-scripts/list-methods.py +++ b/ipc-scripts/list-methods.py @@ -1,16 +1,10 @@ -import wayfire_socket as ws -import os +from wayfire.ipc import WayfireSocket import json -addr = os.getenv('WAYFIRE_SOCKET') -sock = ws.WayfireSocket(addr) +sock = WayfireSocket() -query = ws.get_msg_template('wayfire/configuration') -response = sock.send_json(query) print("Wayfire version:") -print(json.dumps(response, indent=4)) +print(json.dumps(sock.get_configuration(), indent=4)) -query = ws.get_msg_template('list-methods') -response = sock.send_json(query) print("Supported methods:") -print(json.dumps(response['methods'], indent=4)) +print(json.dumps(sock.list_methods(), indent=4)) diff --git a/ipc-scripts/master-stack-tile-layout.py b/ipc-scripts/master-stack-tile-layout.py index f94fce508..42df8d59e 100644 --- a/ipc-scripts/master-stack-tile-layout.py +++ b/ipc-scripts/master-stack-tile-layout.py @@ -3,31 +3,10 @@ # A simple script which demonstrates how simple-tile's IPC scripting capabilities can be used to achieve automatic tiling policies. # This script in particular listens for the view-mapped event and places new views in a master-stack layout: one view remains on the left, # and all other views are piled on top of each other vertically in the right column. -import os -from wayfire_socket import * +from wayfire.ipc import WayfireSocket -addr = os.getenv('WAYFIRE_SOCKET') - -events_sock = WayfireSocket(addr) -commands_sock = WayfireSocket(addr) -events_sock.watch(['view-mapped']) - -def get_tiling_layout(wset, x, y): - msg = get_msg_template('simple-tile/get-layout') - msg['data']['wset-index'] = wset - msg['data']['workspace'] = {} - msg['data']['workspace']['x'] = x - msg['data']['workspace']['y'] = y - return commands_sock.send_json(msg)['layout'] - -def set_tiling_layout(wset, x, y, layout): - msg = get_msg_template('simple-tile/set-layout') - msg['data']['wset-index'] = wset - msg['data']['workspace'] = {} - msg['data']['workspace']['x'] = x - msg['data']['workspace']['y'] = y - msg['data']['layout'] = layout - return commands_sock.send_json(msg) +sock = WayfireSocket() +sock.watch(['view-mapped']) def create_list_views(layout): if 'view-id' in layout: @@ -40,7 +19,7 @@ def create_list_views(layout): return list while True: - msg = events_sock.read_message() + msg = sock.read_next_event() # The view-mapped event is emitted when a new window has been opened. if "event" in msg: view = msg["view"] @@ -48,18 +27,18 @@ def create_list_views(layout): # Tile the new view appropriately # First, figure out current wset and workspace and existing layout - output = commands_sock.query_output(view["output-id"]) + output = sock.query_output(view["output-id"]) wset = output['wset-index'] wsx = output['workspace']['x'] wsy = output['workspace']['y'] - layout = get_tiling_layout(wset, wsx, wsy) + layout = sock.get_tiling_layout(wset, wsx, wsy) all_views = create_list_views(layout) desired_layout = {} if not all_views or (len(all_views) == 1 and all_views[0][0] == view["id"]): # Degenerate case 1: we have just our view desired_layout = { 'vertical-split': [ {'view-id': view["id"], 'weight': 1} ]} - set_tiling_layout(wset, wsx, wsy, desired_layout) + sock.set_tiling_layout(wset, wsx, wsy, desired_layout) continue main_view = all_views[0][0] @@ -74,7 +53,7 @@ def create_list_views(layout): if not stack_views_old: # Degenerate case 2: the new view is the first on the stack, set 2:1 ratio and place the new view on the right desired_layout = { 'vertical-split': [ {'view-id': main_view, 'weight': 2}, {'view-id': view["id"], 'weight': 1} ]} - set_tiling_layout(wset, wsx, wsy, desired_layout) + sock.set_tiling_layout(wset, wsx, wsy, desired_layout) continue stack = [{'view-id': v[0], 'weight': v[2]} for v in stack_views_old] @@ -87,4 +66,4 @@ def create_list_views(layout): ] } - set_tiling_layout(wset, wsx, wsy, desired_layout) + sock.set_tiling_layout(wset, wsx, wsy, desired_layout) diff --git a/ipc-scripts/move-to-workspace.py b/ipc-scripts/move-to-workspace.py deleted file mode 100755 index c17464e92..000000000 --- a/ipc-scripts/move-to-workspace.py +++ /dev/null @@ -1,44 +0,0 @@ -#!/usr/bin/python3 - -"""Example script that moves all the windows for a given app ID to the specified workspace. - - Usage: move-to-workspace " -""" - -import os -import sys - -from wayfire_socket import * - - -def move_to_workspace(s: WayfireSocket, view, ws_x: int, ws_y: int): - output = s.query_output(view["output-id"]) - xsize = output["workarea"]["width"] - ysize = output["workarea"]["height"] - cur_ws_x = output["workspace"]["x"] - cur_ws_y = output["workspace"]["y"] - - geometry = view["geometry"] - x = (geometry["x"] % xsize) + xsize * (ws_x - cur_ws_x) - y = (geometry["y"] % ysize) + ysize * (ws_y - cur_ws_y) - w = geometry["width"] - h = geometry["height"] - - s.configure_view(view["id"], x, y, w, h) - - -def move_all_to_workspace(app_id, x, y): - s = WayfireSocket(os.getenv("WAYFIRE_SOCKET")) - for view in s.list_views(): - if view["app-id"] != app_id: - continue - print("Moving %s(%d) to workspace (%d, %d)" % - (app_id, view["id"], x, y)) - move_to_workspace(s, view, x, y) - s.close() - - -if __name__ == "__main__": - if len(sys.argv) != 4: - sys.exit(sys.modules[__name__].__doc__) - move_all_to_workspace(sys.argv[1], int(sys.argv[2]), int(sys.argv[3])) diff --git a/ipc-scripts/set-touch-state.py b/ipc-scripts/set-touch-state.py index b744b2ada..793370fb8 100644 --- a/ipc-scripts/set-touch-state.py +++ b/ipc-scripts/set-touch-state.py @@ -1,5 +1,4 @@ -import wayfire_socket as ws -import os +from wayfire.ipc import WayfireSocket import sys # This is a small example of how to use the Wayfire socket to set the touchscreen state to on or off. @@ -11,8 +10,7 @@ exit(-1) state: bool = sys.argv[1] == 'enabled' -addr = os.getenv('WAYFIRE_SOCKET') -sock = ws.WayfireSocket(addr) +sock = WayfireSocket() devices = sock.list_input_devices() for dev in devices: diff --git a/ipc-scripts/wayfire_socket.py b/ipc-scripts/wayfire_socket.py deleted file mode 100644 index aee53e9af..000000000 --- a/ipc-scripts/wayfire_socket.py +++ /dev/null @@ -1,179 +0,0 @@ -import socket -import json as js -import select - -def get_msg_template(method: str): - # Create generic message template - message = {} - message["method"] = method - message["data"] = {} - return message - -def geometry_to_json(x: int, y: int, w: int, h: int): - geometry = {} - geometry["x"] = x - geometry["y"] = y - geometry["width"] = w - geometry["height"] = h - return geometry - -class WayfireSocket: - def __init__(self, socket_name): - self.client = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) - self.client.connect(socket_name) - - def read_exact(self, n): - response = bytes() - while n > 0: - read_this_time = self.client.recv(n) - if not read_this_time: - raise Exception("Failed to read anything from the socket!") - n -= len(read_this_time) - response += read_this_time - - return response - - def read_message_timeout(self, timeout: float): - ready = select.select([self.client], [], [], timeout) # Wait 5 seconds - if not ready[0]: - return None - return self.read_message() - - def read_message(self): - bs = self.read_exact(4) - rlen = int.from_bytes(bs, byteorder="little") - response_message = self.read_exact(rlen) - response = js.loads(response_message) - if "error" in response: - raise Exception(response["error"]) - return response - - def send_json(self, msg): - data = js.dumps(msg).encode('utf8') - header = len(data).to_bytes(4, byteorder="little") - self.client.send(header) - self.client.send(data) - return self.read_message() - - def close(self): - self.client.close() - - def watch(self, events = None): - message = get_msg_template("window-rules/events/watch") - if events: - message["data"]["events"] = events - return self.send_json(message) - - def register_binding(self, binding: str, - call_method = None, call_data = None, - command = None, - mode = None, - exec_always = False): - message = get_msg_template("command/register-binding") - message["data"]["binding"] = binding - message["data"]["exec-always"] = exec_always - if mode and mode != "press" and mode != "normal": - message["data"]["mode"] = mode - - if call_method is not None: - message["data"]["call-method"] = call_method - if call_data is not None: - message["data"]["call-data"] = call_data - if command is not None: - message["data"]["command"] = command - - return self.send_json(message) - - def unregister_binding(self, binding_id: int): - message = get_msg_template("command/unregister-binding") - message["data"]["binding-id"] = binding_id - return self.send_json(message) - - def clear_bindings(self): - message = get_msg_template("command/clear-bindings") - return self.send_json(message) - - def query_output(self, output_id: int): - message = get_msg_template("window-rules/output-info") - message["data"]["id"] = output_id - return self.send_json(message) - - def list_outputs(self): - return self.send_json(get_msg_template("window-rules/list-outputs")) - - def list_views(self): - return self.send_json(get_msg_template("window-rules/list-views")) - - def configure_view(self, view_id: int, x: int, y: int, w: int, h: int): - message = get_msg_template("window-rules/configure-view") - message["data"]["id"] = view_id - message["data"]["geometry"] = geometry_to_json(x, y, w, h) - return self.send_json(message) - - def assign_slot(self, view_id: int, slot: str): - message = get_msg_template("grid/" + slot) - message["data"]["view_id"] = view_id - return self.send_json(message) - - def set_focus(self, view_id: int): - message = get_msg_template("window-rules/focus-view") - message["data"]["id"] = view_id - return self.send_json(message) - - def set_always_on_top(self, view_id: int, always_on_top: bool): - message = get_msg_template("wm-actions/set-always-on-top") - message["data"]["view_id"] = view_id - message["data"]["state"] = always_on_top - return self.send_json(message) - - def set_view_alpha(self, view_id: int, alpha: float): - message = get_msg_template("wf/alpha/set-view-alpha") - message["data"] = {} - message["data"]["view-id"] = view_id - message["data"]["alpha"] = alpha - return self.send_json(message) - - def list_input_devices(self): - message = get_msg_template("input/list-devices") - return self.send_json(message) - - def configure_input_device(self, id, enabled: bool): - message = get_msg_template("input/configure-device") - message["data"]["id"] = id - message["data"]["enabled"] = enabled - return self.send_json(message) - - def create_headless_output(self, width, height): - message = get_msg_template("wayfire/create-headless-output") - message["data"]["width"] = width - message["data"]["height"] = height - return self.send_json(message) - - def destroy_headless_output(self, output_name=None, output_id=None): - assert output_name is not None or output_id is not None - message = get_msg_template("wayfire/destroy-headless-output") - if output_name is not None: - message['data']['output'] = output_name - else: - message['data']['output-id'] = output_id - - return self.send_json(message) - - def get_option_value(self, option): - message = get_msg_template("wayfire/get-config-option") - message["data"]["option"] = option - return self.send_json(message) - - def set_option_values(self, options): - sanitized_options = {} - for key, value in options.items(): - if '/' in key: - sanitized_options[key] = value - else: - for option_name, option_value in value.items(): - sanitized_options[key + "/" + option_name] = option_value - - message = get_msg_template("wayfire/set-config-options") - print(js.dumps(sanitized_options, indent=4)) - message["data"] = sanitized_options - return self.send_json(message) From 0cc21d788ae131abeb4463a0bca1de86aaeef4ed Mon Sep 17 00:00:00 2001 From: Ilia Bozhinov Date: Sat, 27 Jul 2024 09:18:30 +0200 Subject: [PATCH 2/5] decoration: include --- plugins/decor/deco-layout.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/decor/deco-layout.cpp b/plugins/decor/deco-layout.cpp index 24370bb0e..b008adea6 100644 --- a/plugins/decor/deco-layout.cpp +++ b/plugins/decor/deco-layout.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #define BUTTON_HEIGHT_PC 0.7 From b9200636cc914caf52346a4cbe9b1007e425e84c Mon Sep 17 00:00:00 2001 From: Ilia Bozhinov Date: Sat, 27 Jul 2024 10:54:30 +0200 Subject: [PATCH 3/5] move: fix snap slots when moving across outputs --- .../wayfire/plugins/common/input-grab.hpp | 24 ++++++++++++++++++- plugins/single_plugins/move.cpp | 8 +++++-- 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/plugins/common/wayfire/plugins/common/input-grab.hpp b/plugins/common/wayfire/plugins/common/input-grab.hpp index 4e8b8a099..42054831f 100644 --- a/plugins/common/wayfire/plugins/common/input-grab.hpp +++ b/plugins/common/wayfire/plugins/common/input-grab.hpp @@ -78,7 +78,7 @@ class grab_node_t : public node_t */ std::string stringify() const override { - return name + "-input-grab"; + return name + "-input-grab " + std::string(output ? output->to_string() : "null"); } keyboard_interaction_t& keyboard_interaction() override @@ -157,6 +157,28 @@ class input_grab_t wf::get_core().set_cursor("default"); } + void regrab_input() + { + const auto& check_focus = [&] (wf::scene::node_ptr focused) + { + return (focused == nullptr) || (focused == grab_node); + }; + + if ((wf::get_core().seat->get_active_node() == grab_node) && + check_focus(wf::get_core().get_cursor_focus()) && + check_focus(wf::get_core().get_touch_focus())) + { + return; + } + + if (output == wf::get_core().seat->get_active_output()) + { + wf::get_core().transfer_grab(grab_node); + } + + scene::update(wf::get_core().scene(), scene::update_flag::REFOCUS); + } + /** * Ungrab the input. */ diff --git a/plugins/single_plugins/move.cpp b/plugins/single_plugins/move.cpp index 1e7db994b..82067273c 100644 --- a/plugins/single_plugins/move.cpp +++ b/plugins/single_plugins/move.cpp @@ -69,6 +69,10 @@ class wayfire_move : public wf::per_output_plugin_instance_t, if (!output->is_plugin_active(grab_interface.name)) { grab_input(drag_helper->view); + } else + { + input_grab->regrab_input(); + update_slot(calc_slot(get_input_coords())); } } else { @@ -291,7 +295,7 @@ class wayfire_move : public wf::per_output_plugin_instance_t, } this->input_grab->grab_input(wf::scene::layer::OVERLAY); - slot.slot_id = wf::grid::SLOT_NONE; + update_slot(wf::grid::SLOT_NONE); return true; } @@ -343,7 +347,7 @@ class wayfire_move : public wf::per_output_plugin_instance_t, drag_helper->set_pending_drag(grab_position); drag_helper->start_drag(view, opts); drag_helper->handle_motion(get_global_input_coords()); - slot.slot_id = wf::grid::SLOT_NONE; + update_slot(wf::grid::SLOT_NONE); return true; } From f1f1296423944b1788aefb8b32733b4f477bf871 Mon Sep 17 00:00:00 2001 From: Ilia Bozhinov Date: Wed, 21 Aug 2024 12:07:15 +0200 Subject: [PATCH 4/5] drag-icon: make sure to use weak_ptr to the drag icon The drag icon object may go away, but the scenegraph nodes may outlive it. We need to make sure to handle this case. Fixes #2435 --- src/core/seat/drag-icon.cpp | 126 +++++++++++++++++++++--------------- src/core/seat/drag-icon.hpp | 10 ++- 2 files changed, 81 insertions(+), 55 deletions(-) diff --git a/src/core/seat/drag-icon.cpp b/src/core/seat/drag-icon.cpp index e0fbc2321..264a9ac5c 100644 --- a/src/core/seat/drag-icon.cpp +++ b/src/core/seat/drag-icon.cpp @@ -3,89 +3,97 @@ #include "wayfire/unstable/wlr-surface-node.hpp" #include "wayfire/core.hpp" #include "wayfire/geometry.hpp" -#include "wayfire/object.hpp" #include "wayfire/opengl.hpp" #include "wayfire/region.hpp" #include "wayfire/scene-operations.hpp" #include "wayfire/scene-render.hpp" #include "wayfire/scene.hpp" -#include "wayfire/debug.hpp" #include "../core-impl.hpp" #include "wayfire/signal-provider.hpp" #include -#include #include "seat-impl.hpp" namespace wf { namespace scene { -class dnd_icon_root_render_instance_t : public render_instance_t +class dnd_root_icon_root_node_t : public floating_inner_node_t { - std::vector children; - wf::drag_icon_t *icon; - damage_callback push_damage; - wf::signal::connection_t on_damage = - [=] (node_damage_signal *data) - { - push_damage(data->region); - }; - - public: - dnd_icon_root_render_instance_t(node_t *self, - wf::drag_icon_t *icon, damage_callback push_damage) + class dnd_icon_root_render_instance_t : public render_instance_t { - this->icon = icon; - this->push_damage = push_damage; - self->connect(&on_damage); - - auto transformed_push_damage = [icon, push_damage] (wf::region_t region) + std::vector children; + damage_callback push_damage; + wf::signal::connection_t on_damage = + [=] (node_damage_signal *data) { - region += icon->get_position(); - push_damage(region); + push_damage(data->region); }; - for (auto& ch : self->get_children()) + std::weak_ptr _self; + + public: + dnd_icon_root_render_instance_t(dnd_root_icon_root_node_t *self, damage_callback push_damage) { - if (ch->is_enabled()) + this->_self = std::dynamic_pointer_cast(self->shared_from_this()); + this->push_damage = push_damage; + self->connect(&on_damage); + + auto transformed_push_damage = [this] (wf::region_t region) + { + if (auto self = _self.lock()) + { + region += self->icon->get_position(); + this->push_damage(region); + } + }; + + for (auto& ch : self->get_children()) { - ch->gen_render_instances(children, transformed_push_damage); + if (ch->is_enabled()) + { + ch->gen_render_instances(children, transformed_push_damage); + } } } - } - - void schedule_instructions( - std::vector& instructions, - const wf::render_target_t& target, wf::region_t& damage) override - { - wf::render_target_t our_target = target.translated(-icon->get_position()); - damage += -icon->get_position(); - for (auto& ch : this->children) + void schedule_instructions( + std::vector& instructions, + const wf::render_target_t& target, wf::region_t& damage) override { - ch->schedule_instructions(instructions, our_target, damage); - } + auto self = _self.lock(); + if (!self) + { + return; + } - damage += icon->get_position(); - } + wf::render_target_t our_target = target.translated(-self->get_position()); - void render(const wf::render_target_t& target, - const wf::region_t& region) override - { - wf::dassert(false, "Rendering a drag icon root node?"); - } + damage += -self->get_position(); + for (auto& ch : this->children) + { + ch->schedule_instructions(instructions, our_target, damage); + } - void compute_visibility(wf::output_t *output, wf::region_t& visible) override - { - compute_visibility_from_list(children, output, visible, icon->get_position()); - } -}; + damage += self->get_position(); + } -class dnd_root_icon_root_node_t : public floating_inner_node_t -{ - wf::drag_icon_t *icon; + void render(const wf::render_target_t& target, + const wf::region_t& region) override + { + wf::dassert(false, "Rendering a drag icon root node?"); + } + + void compute_visibility(wf::output_t *output, wf::region_t& visible) override + { + if (auto self = _self.lock()) + { + compute_visibility_from_list(children, output, visible, self->get_position()); + } + } + }; public: + wf::drag_icon_t *icon; dnd_root_icon_root_node_t(drag_icon_t *icon) : floating_inner_node_t(false) { this->icon = icon; @@ -98,7 +106,7 @@ class dnd_root_icon_root_node_t : public floating_inner_node_t void gen_render_instances(std::vector& instances, damage_callback push_damage, wf::output_t *output) override { - instances.push_back(std::make_unique(this, icon, push_damage)); + instances.push_back(std::make_unique(this, push_damage)); } std::optional find_node_at(const wf::pointf_t& at) override @@ -116,6 +124,17 @@ class dnd_root_icon_root_node_t : public floating_inner_node_t { return "dnd-icon " + stringify_flags(); } + + wf::point_t get_position() + { + if (icon) + { + return icon->get_position(); + } else + { + return {0, 0}; + } + } }; } } @@ -164,6 +183,7 @@ wf::drag_icon_t::drag_icon_t(wlr_drag_icon *ic) : icon(ic) wf::drag_icon_t::~drag_icon_t() { + root_node->icon = nullptr; wf::scene::remove_child(root_node); } diff --git a/src/core/seat/drag-icon.hpp b/src/core/seat/drag-icon.hpp index d255de24e..ad890becb 100644 --- a/src/core/seat/drag-icon.hpp +++ b/src/core/seat/drag-icon.hpp @@ -2,10 +2,16 @@ #include #include "wayfire/geometry.hpp" -#include "wayfire/scene.hpp" +#include +#include namespace wf { +namespace scene +{ +class dnd_root_icon_root_node_t; +} + class drag_icon_t { public: @@ -22,6 +28,6 @@ class drag_icon_t wf::geometry_t last_box = {0, 0, 0, 0}; wf::point_t get_position(); - scene::floating_inner_ptr root_node; + std::shared_ptr root_node; }; } From a630d96f82e5a617c3c01a821d933ab0d085b7d4 Mon Sep 17 00:00:00 2001 From: Ilia Bozhinov Date: Wed, 21 Aug 2024 16:15:41 +0200 Subject: [PATCH 5/5] improve moving dialogs to other outputs --- .../plugins/common/move-drag-interface.hpp | 17 ++++--------- plugins/scale/scale-title-overlay.cpp | 24 +++++-------------- plugins/scale/scale.cpp | 23 ++++-------------- plugins/single_plugins/move.cpp | 11 +++++---- plugins/single_plugins/oswitch.cpp | 6 +++-- src/core/core.cpp | 15 ++++++++++++ src/view/xwayland/xwayland-toplevel-view.hpp | 7 +----- 7 files changed, 40 insertions(+), 63 deletions(-) diff --git a/plugins/common/wayfire/plugins/common/move-drag-interface.hpp b/plugins/common/wayfire/plugins/common/move-drag-interface.hpp index 995b7a900..fc187d014 100644 --- a/plugins/common/wayfire/plugins/common/move-drag-interface.hpp +++ b/plugins/common/wayfire/plugins/common/move-drag-interface.hpp @@ -11,6 +11,7 @@ #include "wayfire/scene.hpp" #include "wayfire/seat.hpp" #include "wayfire/signal-definitions.hpp" +#include "wayfire/view-helpers.hpp" #include #include #include @@ -279,16 +280,6 @@ struct dragged_view_t 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) { @@ -492,7 +483,7 @@ class core_drag_t : public signal::provider_t if (options.join_views) { - grab_view = get_toplevel(grab_view); + grab_view = find_topmost_parent(grab_view); } this->view = grab_view; @@ -556,7 +547,7 @@ class core_drag_t : public signal::provider_t if (options.join_views) { - view = get_toplevel(view); + view = find_topmost_parent(view); } auto bbox = view->get_transformed_node()->get_bounding_box() + @@ -775,7 +766,7 @@ 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); + auto parent = find_topmost_parent(ev->main_view); if (!parent->is_mapped()) { return; diff --git a/plugins/scale/scale-title-overlay.cpp b/plugins/scale/scale-title-overlay.cpp index 24001f812..6bdc37f56 100644 --- a/plugins/scale/scale-title-overlay.cpp +++ b/plugins/scale/scale-title-overlay.cpp @@ -7,6 +7,7 @@ #include "wayfire/plugins/scale-signal.hpp" #include "wayfire/scene-operations.hpp" #include "wayfire/signal-definitions.hpp" +#include "wayfire/view-helpers.hpp" #include "wayfire/view-transform.hpp" #include @@ -17,19 +18,6 @@ #include #include -/** - * Get the topmost parent of a view. - */ -static wayfire_toplevel_view find_toplevel_parent(wayfire_toplevel_view view) -{ - while (view->parent) - { - view = view->parent; - } - - return view; -} - /** * Class storing an overlay with a view's title, only stored for parent views. */ @@ -141,7 +129,7 @@ class title_overlay_node_t : public node_t wf::dimensions_t find_maximal_title_size() { wf::dimensions_t max_size = {0, 0}; - auto parent = find_toplevel_parent(view); + auto parent = find_topmost_parent(view); for (auto v : parent->enumerate_views()) { @@ -169,7 +157,7 @@ class title_overlay_node_t : public node_t return false; } - auto parent = find_toplevel_parent(view); + auto parent = find_topmost_parent(view); if ((this->parent.show_view_title_overlay == scale_show_title_t::title_overlay_t::MOUSE) && @@ -213,7 +201,7 @@ class title_overlay_node_t : public node_t * TODO: check if this wastes too high CPU power when views are being * animated and maybe redraw less frequently */ - auto& tex = get_overlay_texture(find_toplevel_parent(view)); + auto& tex = get_overlay_texture(find_topmost_parent(view)); if ((tex.overlay.tex.tex == (GLuint) - 1) || (output_scale != tex.par.output_scale) || (tex.overlay.tex.width > box.width * output_scale) || @@ -253,7 +241,7 @@ class title_overlay_node_t : public node_t wayfire_toplevel_view view_, position pos_, scale_show_title_t& parent_) : node_t(false), view(view_), parent(parent_), pos(pos_) { - auto parent = find_toplevel_parent(view); + auto parent = find_topmost_parent(view); auto& title = get_overlay_texture(parent); if (title.overlay.tex.tex != (GLuint) - 1) @@ -500,7 +488,7 @@ void scale_show_title_t::update_title_overlay_mouse() wayfire_toplevel_view v = scale_find_view_at(wf::get_core().get_cursor_position(), output); if (v) { - v = find_toplevel_parent(v); + v = wf::find_topmost_parent(v); if (v->role != wf::VIEW_ROLE_TOPLEVEL) { diff --git a/plugins/scale/scale.cpp b/plugins/scale/scale.cpp index 6ac3e7c85..de445d5bc 100644 --- a/plugins/scale/scale.cpp +++ b/plugins/scale/scale.cpp @@ -335,17 +335,6 @@ class wayfire_scale : public wf::per_output_plugin_instance_t, } } - /** Return the topmost parent */ - wayfire_toplevel_view get_top_parent(wayfire_toplevel_view view) - { - while (view && view->parent) - { - view = view->parent; - } - - return view; - } - /* Fade all views' alpha to inactive alpha except the * view argument */ void fade_out_all_except(wayfire_toplevel_view view) @@ -353,7 +342,7 @@ class wayfire_scale : public wf::per_output_plugin_instance_t, for (auto& e : scale_data) { auto v = e.first; - if (get_top_parent(v) == get_top_parent(view)) + if (wf::find_topmost_parent(v) == wf::find_topmost_parent(view)) { continue; } @@ -495,7 +484,7 @@ class wayfire_scale : public wf::per_output_plugin_instance_t, // Focus the view under the mouse current_focus_view = view; fade_out_all_except(view); - fade_in(get_top_parent(view)); + fade_in(wf::find_topmost_parent(view)); // End scale initial_focus_view.reset(); @@ -548,11 +537,7 @@ class wayfire_scale : public wf::per_output_plugin_instance_t, /* Get the workspace for the center point of the untransformed view geometry */ wf::point_t get_view_main_workspace(wayfire_toplevel_view view) { - while (view->parent) - { - view = view->parent; - } - + view = wf::find_topmost_parent(view); auto ws = output->wset()->get_current_workspace(); auto og = output->get_layout_geometry(); auto vg = view->get_geometry(); @@ -787,7 +772,7 @@ class wayfire_scale : public wf::per_output_plugin_instance_t, auto views = get_views(); return std::find( - views.begin(), views.end(), get_top_parent(view)) != views.end(); + views.begin(), views.end(), wf::find_topmost_parent(view)) != views.end(); } /* Convenience assignment function */ diff --git a/plugins/single_plugins/move.cpp b/plugins/single_plugins/move.cpp index 82067273c..8f726e1f4 100644 --- a/plugins/single_plugins/move.cpp +++ b/plugins/single_plugins/move.cpp @@ -262,12 +262,13 @@ class wayfire_move : public wf::per_output_plugin_instance_t, */ wayfire_toplevel_view get_target_view(wayfire_toplevel_view view) { - while (view && view->parent && join_views) + if (join_views) + { + return wf::find_topmost_parent(view); + } else { - view = view->parent; + return view; } - - return view; } bool can_move_view(wayfire_toplevel_view view) @@ -305,7 +306,7 @@ class wayfire_move : public wf::per_output_plugin_instance_t, auto target_output = wf::get_core().output_layout->get_output_at(grab_position.x, grab_position.y); if (target_output && (view->get_output() != target_output)) { - auto parent = wf::move_drag::get_toplevel(view); + auto parent = wf::find_topmost_parent(view); auto offset = wf::origin(parent->get_output()->get_layout_geometry()) + -wf::origin(target_output->get_layout_geometry()); diff --git a/plugins/single_plugins/oswitch.cpp b/plugins/single_plugins/oswitch.cpp index a8d6d8476..e0dfa682f 100644 --- a/plugins/single_plugins/oswitch.cpp +++ b/plugins/single_plugins/oswitch.cpp @@ -1,5 +1,6 @@ #include "wayfire/plugin.hpp" #include "wayfire/toplevel-view.hpp" +#include "wayfire/view-helpers.hpp" #include #include #include @@ -46,8 +47,9 @@ class wayfire_oswitch : public wf::plugin_interface_t void switch_to_output_with_window(wf::output_t *target_output) { auto current_output = wf::get_core().seat->get_active_output(); - auto view = wf::toplevel_cast(wf::get_active_view_for_output(current_output)); - LOGI("Found view ", view); + auto view = + wf::find_topmost_parent(wf::toplevel_cast(wf::get_active_view_for_output(current_output))); + if (view) { move_view_to_output(view, target_output, true); diff --git a/src/core/core.cpp b/src/core/core.cpp index 0d0f7d3b5..c60365343 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp @@ -522,6 +522,9 @@ void wf::move_view_to_output(wayfire_toplevel_view v, wf::output_t *new_output, wf::geometry_t old_output_g; wf::geometry_t new_output_g; + int delta_x = 0; + int delta_y = 0; + if (reconfigure) { edges = v->pending_tiled_edges(); @@ -533,6 +536,9 @@ void wf::move_view_to_output(wayfire_toplevel_view v, wf::output_t *new_output, auto ratio_y = (double)new_output_g.height / old_output_g.height; view_g.x *= ratio_x; view_g.y *= ratio_y; + + delta_x = view_g.x - v->get_pending_geometry().x; + delta_y = view_g.y - v->get_pending_geometry().y; } assert(new_output); @@ -556,6 +562,15 @@ void wf::move_view_to_output(wayfire_toplevel_view v, wf::output_t *new_output, auto new_g = wf::clamp(view_g, new_output->workarea->get_workarea()); v->set_geometry(new_g); } + + for (auto& dialog : v->enumerate_views()) + { + if ((dialog != v) && (delta_x || delta_y)) + { + dialog->move(dialog->get_pending_geometry().x + delta_x, + dialog->get_pending_geometry().y + delta_y); + } + } } emit_view_moved_to_wset(v, old_wset, new_output->wset()); diff --git a/src/view/xwayland/xwayland-toplevel-view.hpp b/src/view/xwayland/xwayland-toplevel-view.hpp index 32b67084b..a34ba6bd0 100644 --- a/src/view/xwayland/xwayland-toplevel-view.hpp +++ b/src/view/xwayland/xwayland-toplevel-view.hpp @@ -130,12 +130,7 @@ class wayfire_xwayland_view : public wf::toplevel_view_interface_t, public wayfi configure_geometry.x -= og.x; configure_geometry.y -= og.y; - wayfire_toplevel_view view = {this}; - while (view->parent) - { - view = view->parent; - } - + wayfire_toplevel_view view = wf::find_topmost_parent(wayfire_toplevel_view{this}); auto vg = view->get_pending_geometry(); // View workspace relative to current workspace