├── LICENSE ├── README.md ├── ipc-scripts └── wf-info.py ├── meson.build ├── metadata ├── meson.build └── wf-info.xml ├── proto ├── meson.build └── wayfire-information.xml └── src ├── client ├── meson.build ├── qt │ ├── gui.py │ └── wf_info_base.py ├── wf-info.cpp └── wf-info.hpp ├── main.cpp ├── meson.build └── plugin ├── ipc-rules-common.hpp ├── wayfire-information.cpp └── wayfire-information.hpp /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2022 Scott Moreau 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # wf-info 2 | A simple wayfire plugin and program to get information from wayfire. 3 | 4 | ## Build 5 | 6 | meson build --prefix=/usr 7 | 8 | ninja -C build 9 | 10 | sudo ninja -C build install 11 | 12 | ## Runtime 13 | 14 | Enable Information Protocol plugin 15 | 16 | Run `wf-info` and click on a window, run `wf-info -l` to list information about all windows, or use `wf-info -i $id` where `$id` is the ID of the view about which you want info. An ID of -1 means the focused view. 17 | 18 | ## Examples 19 | 20 | ``` 21 | $ wf-info 22 | ========================= 23 | View ID: 1112 24 | Client PID: 1562086 25 | Output: DP-2(ID: 1) 26 | Workspace: 0,0 27 | App ID: python3 28 | Title: Wayfire Window Information 29 | Role: TOPLEVEL 30 | Geometry: 710,231 500x629 31 | Xwayland: false 32 | Focused: false 33 | ========================= 34 | ``` 35 | 36 | `python3 src/client/qt/gui.py` 37 | 38 | ![wf-info-gui](https://github.com/soreau/wf-info/assets/1450125/31d1e550-f145-4cec-a1ab-013b2c57844b) 39 | -------------------------------------------------------------------------------- /ipc-scripts/wf-info.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | import json 4 | from wayfire import WayfireSocket 5 | from wayfire.extra.wpe import WPE 6 | 7 | sock = WayfireSocket() 8 | wpe = WPE(sock) 9 | 10 | print(f"View Info:\n{json.dumps(wpe.get_view_info(), indent=2, ensure_ascii=False)}") 11 | -------------------------------------------------------------------------------- /meson.build: -------------------------------------------------------------------------------- 1 | project( 2 | 'wf-info', 3 | 'c', 4 | 'cpp', 5 | version: '0.7.0', 6 | license: 'MIT', 7 | meson_version: '>=0.51.0', 8 | default_options: [ 9 | 'cpp_std=c++17', 10 | 'c_std=c11', 11 | ], 12 | ) 13 | 14 | wayfire = dependency('wayfire') 15 | 16 | add_project_arguments(['-DWLR_USE_UNSTABLE'], language: ['cpp', 'c']) 17 | 18 | subdir('metadata') 19 | subdir('proto') 20 | subdir('src') 21 | -------------------------------------------------------------------------------- /metadata/meson.build: -------------------------------------------------------------------------------- 1 | install_data('wf-info.xml', install_dir: wayfire.get_variable(pkgconfig: 'metadatadir')) -------------------------------------------------------------------------------- /metadata/wf-info.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | <_short>Information Protocol 5 | <_long>Support for additional information about views and the desktop. 6 | Utility 7 | 8 | 9 | -------------------------------------------------------------------------------- /proto/meson.build: -------------------------------------------------------------------------------- 1 | wayland_protos = dependency('wayland-protocols') 2 | wayland_server = dependency('wayland-server') 3 | wayland_client = dependency('wayland-client') 4 | 5 | wl_protocol_dir = wayland_protos.get_pkgconfig_variable('pkgdatadir') 6 | 7 | wayland_scanner = find_program('wayland-scanner') 8 | 9 | wayland_scanner_server = generator( 10 | wayland_scanner, 11 | output: '@BASENAME@-server-protocol.h', 12 | arguments: ['server-header', '@INPUT@', '@OUTPUT@'], 13 | ) 14 | 15 | wayland_scanner_client = generator( 16 | wayland_scanner, 17 | output: '@BASENAME@-client-protocol.h', 18 | arguments: ['client-header', '@INPUT@', '@OUTPUT@'], 19 | ) 20 | 21 | wayland_scanner_code = generator( 22 | wayland_scanner, 23 | output: '@BASENAME@-protocol.c', 24 | arguments: ['private-code', '@INPUT@', '@OUTPUT@'], 25 | ) 26 | 27 | protocols = [ 28 | 'wayfire-information.xml' 29 | ] 30 | 31 | wl_server_protos_src = [] 32 | wl_server_protos_headers = [] 33 | 34 | foreach p : protocols 35 | xml = join_paths(p) 36 | wl_server_protos_headers += wayland_scanner_server.process(xml) 37 | wl_server_protos_src += wayland_scanner_code.process(xml) 38 | endforeach 39 | 40 | lib_wl_server_protos = static_library('wl_server_protos', wl_server_protos_src + wl_server_protos_headers, 41 | dependencies: [wayland_server]) # for the include directory 42 | 43 | wf_server_protos = declare_dependency( 44 | link_with: lib_wl_server_protos, 45 | sources: wl_server_protos_headers, 46 | ) 47 | 48 | wl_client_protos_src = [] 49 | wl_client_protos_headers = [] 50 | 51 | foreach p : protocols 52 | xml = join_paths(p) 53 | wl_client_protos_headers += wayland_scanner_client.process(xml) 54 | wl_client_protos_src += wayland_scanner_code.process(xml) 55 | endforeach 56 | 57 | lib_wl_client_protos = static_library('wl_client_protos', wl_client_protos_src + wl_client_protos_headers, 58 | dependencies: [wayland_client]) # for the include directory 59 | 60 | wf_client_protos = declare_dependency( 61 | link_with: lib_wl_client_protos, 62 | sources: wl_client_protos_headers, 63 | ) 64 | -------------------------------------------------------------------------------- /proto/wayfire-information.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | The MIT License (MIT) 6 | 7 | Copyright (c) 2022 Scott Moreau 8 | 9 | Permission is hereby granted, free of charge, to any person obtaining a copy 10 | of this software and associated documentation files (the "Software"), to deal 11 | in the Software without restriction, including without limitation the rights 12 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | copies of the Software, and to permit persons to whom the Software is 14 | furnished to do so, subject to the following conditions: 15 | 16 | The above copyright notice and this permission notice shall be included in all 17 | copies or substantial portions of the Software. 18 | 19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 25 | SOFTWARE. 26 | 27 | 28 | 29 | 30 | Interface that allows clients to get information from wayfire. 31 | 32 | 33 | 34 | 35 | Get information about the selected view. 36 | 37 | 38 | 39 | 40 | 41 | Get information about the view from id. 42 | 43 | 44 | 45 | 46 | 47 | 48 | Get information about all views. 49 | 50 | 51 | 52 | 53 | 54 | Provide client with information about a view. 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | Notify client that the complete list of views has been sent. 76 | 77 | 78 | 79 | 80 | -------------------------------------------------------------------------------- /src/client/meson.build: -------------------------------------------------------------------------------- 1 | executable('wf-info', ['wf-info.cpp'], 2 | dependencies: [wayland_client, wf_client_protos], 3 | install: true) -------------------------------------------------------------------------------- /src/client/qt/gui.py: -------------------------------------------------------------------------------- 1 | from PyQt5.QtWidgets import QApplication, QMainWindow, QLabel, QPushButton, QHBoxLayout, QVBoxLayout, QWidget 2 | from wf_info_base import WfInfoBase 3 | from pywayland.client import Display 4 | from PyQt5.QtCore import Qt 5 | import signal 6 | import sys 7 | 8 | class WfInfoApp(QMainWindow): 9 | def __init__(self, wf_info, clipboard): 10 | super().__init__() 11 | self.wf_info = wf_info 12 | self.clipboard = clipboard 13 | 14 | self.setWindowTitle("Wayfire Window Information") 15 | self.setGeometry(100, 100, 400, 500) 16 | 17 | self.central_widget = QWidget() 18 | self.setCentralWidget(self.central_widget) 19 | 20 | self.info_button = QPushButton("Click to get window information") 21 | 22 | self.layout = QVBoxLayout() 23 | 24 | self.layout.addWidget(self.info_button) 25 | 26 | self.central_widget.setLayout(self.layout) 27 | 28 | self.info_button.clicked.connect(self.get_info_text) 29 | def reset_layout(self, layout): 30 | if layout is not None: 31 | while layout.count(): 32 | item = layout.takeAt(0) 33 | widget = item.widget() 34 | if widget is not None: 35 | widget.setParent(None) 36 | else: 37 | self.reset_layout(item.layout()) 38 | def reset(self): 39 | self.reset_layout(self.layout) 40 | self.layout.addWidget(self.info_button) 41 | def copy_text(self, text): 42 | self.clipboard.setText(text) 43 | def handle_view_info(self, ok, view_id, client_pid, workspace_x, workspace_y, app_id, title, role, x, y, width, height, is_xwayland, focused, output, output_id): 44 | self.reset() 45 | labels = ["LABEL", "view_id", "client_pid", "workspace_x", "workspace_y", "app_id", "title", "role", "x", "y", "width", "height", "is_xwayland", "focused", "output", "output_id"] 46 | values = ["VALUE", f"{view_id}", f"{client_pid}", f"{workspace_x}", f"{workspace_y}", f"{app_id}", f"{title}", f"{role}", f"{x}", f"{y}", f"{width}", f"{height}", f"{is_xwayland}", f"{focused}", f"{output}", f"{output_id}"] 47 | hlayout = QHBoxLayout() 48 | label_layout = QVBoxLayout() 49 | i = 0 50 | for label in labels: 51 | i = i + 1 52 | if i == 1: 53 | label_label = QLabel(label) 54 | else: 55 | label_label = QLabel(label + ": ") 56 | label_label.setTextInteractionFlags(Qt.TextSelectableByMouse) 57 | label_layout.addWidget(label_label) 58 | hlayout.addLayout(label_layout) 59 | value_layout = QVBoxLayout() 60 | for value in values: 61 | value_label = QLabel(value) 62 | value_label.setTextInteractionFlags(Qt.TextSelectableByMouse) 63 | value_layout.addWidget(value_label) 64 | hlayout.addLayout(value_layout) 65 | copy_button_layout = QVBoxLayout() 66 | i = 0 67 | for value in values: 68 | i = i + 1 69 | if i == 1: 70 | reset_button = QPushButton("Reset") 71 | reset_button.clicked.connect(self.reset) 72 | copy_button_layout.addWidget(reset_button) 73 | continue 74 | copy_button = QPushButton("Copy Value") 75 | copy_button.clicked.connect(lambda s=self, v=value: self.copy_text(v)) 76 | copy_button_layout.addWidget(copy_button) 77 | hlayout.addLayout(copy_button_layout) 78 | 79 | self.layout.addLayout(hlayout) 80 | self.layout.addWidget(self.info_button) 81 | def get_info_text(self): 82 | self.wf_info["binding"].dispatcher["view_info"] = self.handle_view_info 83 | self.wf_info["binding"].view_info() 84 | self.wf_info["display"].dispatch(block=True) 85 | 86 | 87 | def handle_registry_global(wl_registry, id_num, iface_name, version): 88 | #print("global", id_num, iface_name) 89 | 90 | wf_info = wl_registry.user_data 91 | if iface_name == "wf_info_base": 92 | wf_info["enabled"] = True 93 | wf_info["binding"] = wl_registry.bind(id_num, WfInfoBase, 1) 94 | return 1 95 | 96 | def wf_info_create(): 97 | wf_info = {} 98 | 99 | # Make the display and get the registry 100 | wf_info["display"] = Display() 101 | wf_info["display"].connect() 102 | 103 | wf_info["registry"] = wf_info["display"].get_registry() 104 | wf_info["registry"].user_data = wf_info 105 | wf_info["registry"].dispatcher["global"] = handle_registry_global 106 | 107 | wf_info["enabled"] = False 108 | wf_info["display"].roundtrip() 109 | 110 | if not wf_info["enabled"]: 111 | print("Wayfire information protocol not advertised by compositor. Is wf-info plugin enabled?", file=sys.stderr) 112 | wf_info["display"].disconnect() 113 | exit(-1) 114 | 115 | return wf_info 116 | 117 | def main(): 118 | signal.signal(signal.SIGINT, signal.SIG_DFL) 119 | 120 | app = QApplication(sys.argv) 121 | clipboard = app.clipboard() 122 | 123 | wf_info = wf_info_create() 124 | window = WfInfoApp(wf_info, clipboard) 125 | 126 | window.show() 127 | 128 | ret = app.exec_() 129 | wf_info["display"].disconnect() 130 | sys.exit(ret) 131 | 132 | if __name__ == "__main__": 133 | main() 134 | -------------------------------------------------------------------------------- /src/client/qt/wf_info_base.py: -------------------------------------------------------------------------------- 1 | # This file has been autogenerated by the pywayland scanner 2 | 3 | # The MIT License (MIT) 4 | # 5 | # Copyright (c) 2022 Scott Moreau 6 | # 7 | # Permission is hereby granted, free of charge, to any person obtaining a copy 8 | # of this software and associated documentation files (the "Software"), to deal 9 | # in the Software without restriction, including without limitation the rights 10 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | # copies of the Software, and to permit persons to whom the Software is 12 | # furnished to do so, subject to the following conditions: 13 | # 14 | # The above copyright notice and this permission notice shall be included in all 15 | # copies or substantial portions of the Software. 16 | # 17 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | # SOFTWARE. 24 | 25 | from __future__ import annotations 26 | 27 | from pywayland.protocol_core import ( 28 | Argument, 29 | ArgumentType, 30 | Global, 31 | Interface, 32 | Proxy, 33 | Resource, 34 | ) 35 | 36 | 37 | class WfInfoBase(Interface): 38 | """Wayfire desktop communication 39 | 40 | Interface that allows clients to get information from wayfire. 41 | """ 42 | 43 | name = "wf_info_base" 44 | version = 1 45 | 46 | 47 | class WfInfoBaseProxy(Proxy[WfInfoBase]): 48 | interface = WfInfoBase 49 | 50 | @WfInfoBase.request() 51 | def view_info(self) -> None: 52 | """Get information about the selected view 53 | 54 | Get information about the selected view. 55 | """ 56 | self._marshal(0) 57 | 58 | @WfInfoBase.request( 59 | Argument(ArgumentType.Int), 60 | ) 61 | def view_info_id(self, view_id: int) -> None: 62 | """Get information about the view from id 63 | 64 | Get information about the view from id. 65 | 66 | :param view_id: 67 | view ID 68 | :type view_id: 69 | `ArgumentType.Int` 70 | """ 71 | self._marshal(1, view_id) 72 | 73 | @WfInfoBase.request() 74 | def view_info_list(self) -> None: 75 | """Get information from all views 76 | 77 | Get information about all views. 78 | """ 79 | self._marshal(2) 80 | 81 | 82 | class WfInfoBaseResource(Resource): 83 | interface = WfInfoBase 84 | 85 | @WfInfoBase.event( 86 | Argument(ArgumentType.Int), 87 | Argument(ArgumentType.Int), 88 | Argument(ArgumentType.Int), 89 | Argument(ArgumentType.Int), 90 | Argument(ArgumentType.String), 91 | Argument(ArgumentType.String), 92 | Argument(ArgumentType.String), 93 | Argument(ArgumentType.Int), 94 | Argument(ArgumentType.Int), 95 | Argument(ArgumentType.Int), 96 | Argument(ArgumentType.Int), 97 | Argument(ArgumentType.Int), 98 | Argument(ArgumentType.Int), 99 | Argument(ArgumentType.String), 100 | Argument(ArgumentType.Uint), 101 | ) 102 | def view_info(self, view_id: int, client_pid: int, workspace_x: int, workspace_y: int, app_id: str, title: str, role: str, x: int, y: int, width: int, height: int, is_xwayland: int, focused: int, output: str, output_id: int) -> None: 103 | """Export information about a view to a client 104 | 105 | Provide client with information about a view. 106 | 107 | :param view_id: 108 | view wayfire ID 109 | :type view_id: 110 | `ArgumentType.Int` 111 | :param client_pid: 112 | client PID 113 | :type client_pid: 114 | `ArgumentType.Int` 115 | :param workspace_x: 116 | view workspace x 117 | :type workspace_x: 118 | `ArgumentType.Int` 119 | :param workspace_y: 120 | view workspace y 121 | :type workspace_y: 122 | `ArgumentType.Int` 123 | :param app_id: 124 | view application ID 125 | :type app_id: 126 | `ArgumentType.String` 127 | :param title: 128 | view title 129 | :type title: 130 | `ArgumentType.String` 131 | :param role: 132 | view role 133 | :type role: 134 | `ArgumentType.String` 135 | :param x: 136 | view x position 137 | :type x: 138 | `ArgumentType.Int` 139 | :param y: 140 | view y position 141 | :type y: 142 | `ArgumentType.Int` 143 | :param width: 144 | view width 145 | :type width: 146 | `ArgumentType.Int` 147 | :param height: 148 | view height 149 | :type height: 150 | `ArgumentType.Int` 151 | :param is_xwayland: 152 | whether view is xwayland 153 | :type is_xwayland: 154 | `ArgumentType.Int` 155 | :param focused: 156 | whether view is focused 157 | :type focused: 158 | `ArgumentType.Int` 159 | :param output: 160 | Name of the view's output 161 | :type output: 162 | `ArgumentType.String` 163 | :param output_id: 164 | ID of the view's output 165 | :type output_id: 166 | `ArgumentType.Uint` 167 | """ 168 | self._post_event(0, view_id, client_pid, workspace_x, workspace_y, app_id, title, role, x, y, width, height, is_xwayland, focused, output, output_id) 169 | 170 | @WfInfoBase.event() 171 | def done(self) -> None: 172 | """Notify client that the complete list of views has been sent 173 | 174 | Notify client that the complete list of views has been sent. 175 | """ 176 | self._post_event(1) 177 | 178 | 179 | class WfInfoBaseGlobal(Global): 180 | interface = WfInfoBase 181 | 182 | 183 | WfInfoBase._gen_c() 184 | WfInfoBase.proxy_class = WfInfoBaseProxy 185 | WfInfoBase.resource_class = WfInfoBaseResource 186 | WfInfoBase.global_class = WfInfoBaseGlobal 187 | -------------------------------------------------------------------------------- /src/client/wf-info.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2022 Scott Moreau 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | 26 | #include 27 | #include 28 | #include 29 | #include 30 | 31 | #include "wf-info.hpp" 32 | 33 | static void registry_add(void *data, struct wl_registry *registry, 34 | uint32_t id, const char *interface, 35 | uint32_t version) 36 | { 37 | WfInfo *wfm = (WfInfo *) data; 38 | 39 | if (strcmp(interface, wf_info_base_interface.name) == 0) 40 | { 41 | wfm->wf_information_manager = (wf_info_base *) 42 | wl_registry_bind(registry, id, 43 | &wf_info_base_interface, 1); 44 | } 45 | } 46 | 47 | static void registry_remove(void *data, struct wl_registry *registry, 48 | uint32_t id) 49 | { 50 | exit(0); 51 | } 52 | 53 | static const struct wl_registry_listener registry_listener = { 54 | .global = registry_add, 55 | .global_remove = registry_remove, 56 | }; 57 | 58 | static void receive_view_info(void *data, 59 | struct wf_info_base *wf_info_base, 60 | const uint32_t view_id, 61 | const int client_pid, 62 | const int ws_x, 63 | const int ws_y, 64 | const char *app_id, 65 | const char *title, 66 | const char *role, 67 | const int x, 68 | const int y, 69 | const int width, 70 | const int height, 71 | const int xwayland, 72 | const int focused, 73 | const char * output_name, 74 | const uint32_t output_id) 75 | { 76 | std::cout << "=========================" << std::endl; 77 | std::cout << "View ID: " << view_id << std::endl; 78 | std::cout << "Client PID: " << client_pid << std::endl; 79 | std::cout << "Output: " << output_name << "(ID: " << output_id << ")" << std::endl; 80 | std::cout << "Workspace: " << ws_x << "," << ws_y << std::endl; 81 | std::cout << "App ID: " << app_id << std::endl; 82 | std::cout << "Title: " << title << std::endl; 83 | std::cout << "Role: " << role << std::endl; 84 | std::cout << "Geometry: " << x << "," << y << " " << width << "x" << height << std::endl; 85 | std::cout << "Xwayland: " << (xwayland ? "true" : "false") << std::endl; 86 | std::cout << "Focused: " << (focused ? "true" : "false") << std::endl; 87 | std::cout << "=========================" << std::endl; 88 | } 89 | 90 | static void done(void *data, 91 | struct wf_info_base *wf_info_base) 92 | { 93 | exit(0); 94 | } 95 | 96 | static struct wf_info_base_listener information_base_listener { 97 | .view_info = receive_view_info, 98 | .done = done, 99 | }; 100 | 101 | WfInfo::WfInfo(int argc, char *argv[]) 102 | { 103 | display = wl_display_connect(NULL); 104 | if (!display) 105 | { 106 | return; 107 | } 108 | 109 | wl_registry *registry = wl_display_get_registry(display); 110 | if (!registry) 111 | { 112 | return; 113 | } 114 | 115 | wl_registry_add_listener(registry, ®istry_listener, this); 116 | 117 | wf_information_manager = NULL; 118 | wl_display_roundtrip(display); 119 | wl_registry_destroy(registry); 120 | if (!wf_information_manager) 121 | { 122 | std::cout << "Wayfire information protocol not advertised by compositor. Is wf-info plugin enabled?" << std::endl; 123 | return; 124 | } 125 | 126 | wf_info_base_add_listener(wf_information_manager, 127 | &information_base_listener, this); 128 | 129 | struct option opts[] = { 130 | { "view-id", required_argument, NULL, 'i' }, 131 | { "all-views", no_argument, NULL, 'l' }, 132 | { 0, 0, NULL, 0 } 133 | }; 134 | 135 | std::vector view_ids; 136 | int c, i, list_all_views = 0; 137 | while((c = getopt_long(argc, argv, "i:l", opts, &i)) != -1) 138 | { 139 | switch(c) 140 | { 141 | case 'i': 142 | view_ids.push_back(atoi(optarg)); 143 | break; 144 | 145 | case 'l': 146 | list_all_views = 1; 147 | break; 148 | 149 | default: 150 | printf("Unsupported command line argument %s\n", optarg); 151 | } 152 | } 153 | 154 | for (auto view_id : view_ids) 155 | { 156 | wf_info_base_view_info_id(wf_information_manager, view_id); 157 | } 158 | 159 | if (list_all_views) 160 | { 161 | wf_info_base_view_info_list(wf_information_manager); 162 | } 163 | else if (view_ids.empty()) 164 | { 165 | wf_info_base_view_info(wf_information_manager); 166 | } 167 | 168 | while(1) 169 | wl_display_dispatch(display); 170 | 171 | wl_display_flush(display); 172 | wl_display_disconnect(display); 173 | } 174 | 175 | WfInfo::~WfInfo() 176 | { 177 | } 178 | 179 | int main(int argc, char *argv[]) 180 | { 181 | WfInfo(argc, argv); 182 | 183 | return 0; 184 | } 185 | -------------------------------------------------------------------------------- /src/client/wf-info.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2022 Scott Moreau 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | 26 | #pragma once 27 | 28 | #include "wayfire-information-client-protocol.h" 29 | 30 | class WfInfo 31 | { 32 | public: 33 | WfInfo(int argc, char *argv[]); 34 | ~WfInfo(); 35 | 36 | wl_display *display; 37 | wf_info_base *wf_information_manager; 38 | }; 39 | -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2023 Scott Moreau 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | 26 | #include 27 | #include 28 | #include 29 | 30 | #include "plugin/wayfire-information.hpp" 31 | 32 | class wf_info : public wf::plugin_interface_t, public wf::pointer_interaction_t 33 | { 34 | public: 35 | std::unique_ptr wayfire_information_ptr = nullptr; 36 | 37 | void init() 38 | { 39 | wayfire_information_ptr = std::make_unique(); 40 | wayfire_information_ptr->set_base_ptr(this); 41 | } 42 | 43 | void handle_pointer_button(const wlr_pointer_button_event& event) override 44 | { 45 | if (event.state == WL_POINTER_BUTTON_STATE_PRESSED) 46 | { 47 | wayfire_information_ptr->end_grab(); 48 | } 49 | } 50 | 51 | void fini() 52 | { 53 | wayfire_information_ptr.reset(); 54 | } 55 | }; 56 | 57 | DECLARE_WAYFIRE_PLUGIN(wf_info); 58 | -------------------------------------------------------------------------------- /src/meson.build: -------------------------------------------------------------------------------- 1 | sources = ['main.cpp', 'plugin/wayfire-information.cpp'] 2 | 3 | wf_info = shared_module('wf-info', sources, 4 | dependencies: [wayfire, wf_server_protos], 5 | install: true, install_dir: join_paths(get_option('libdir'), 'wayfire')) 6 | 7 | subdir('client') 8 | -------------------------------------------------------------------------------- /src/plugin/ipc-rules-common.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | static inline wf::json_t output_to_json(wf::output_t *o) 13 | { 14 | if (!o) 15 | { 16 | return wf::json_t::null(); 17 | } 18 | 19 | wf::json_t response; 20 | response["id"] = o->get_id(); 21 | response["name"] = o->to_string(); 22 | response["geometry"] = wf::ipc::geometry_to_json(o->get_layout_geometry()); 23 | response["workarea"] = wf::ipc::geometry_to_json(o->workarea->get_workarea()); 24 | response["wset-index"] = o->wset()->get_index(); 25 | response["workspace"]["x"] = o->wset()->get_current_workspace().x; 26 | response["workspace"]["y"] = o->wset()->get_current_workspace().y; 27 | response["workspace"]["grid_width"] = o->wset()->get_workspace_grid_size().width; 28 | response["workspace"]["grid_height"] = o->wset()->get_workspace_grid_size().height; 29 | return response; 30 | } 31 | 32 | static inline pid_t get_view_pid(wayfire_view view) 33 | { 34 | pid_t pid = -1; 35 | if (!view) 36 | { 37 | return pid; 38 | } 39 | 40 | #if WF_HAS_XWAYLAND 41 | wlr_surface *wlr_surface = view->get_wlr_surface(); 42 | if (wlr_surface && wlr_xwayland_surface_try_from_wlr_surface(wlr_surface)) 43 | { 44 | pid = wlr_xwayland_surface_try_from_wlr_surface(wlr_surface)->pid; 45 | } else 46 | #endif 47 | if (view && view->get_client()) 48 | { 49 | wl_client_get_credentials(view->get_client(), &pid, 0, 0); 50 | } 51 | 52 | return pid; // NOLINT 53 | } 54 | 55 | static inline wf::geometry_t get_view_base_geometry(wayfire_view view) 56 | { 57 | auto sroot = view->get_surface_root_node(); 58 | for (auto& ch : sroot->get_children()) 59 | { 60 | if (auto wlr_surf = dynamic_cast(ch.get())) 61 | { 62 | auto bbox = wlr_surf->get_bounding_box(); 63 | wf::pointf_t origin = sroot->to_global({0, 0}); 64 | bbox.x = origin.x; 65 | bbox.y = origin.y; 66 | return bbox; 67 | } 68 | } 69 | 70 | return sroot->get_bounding_box(); 71 | } 72 | 73 | static inline std::string role_to_string(enum wf::view_role_t role) 74 | { 75 | switch (role) 76 | { 77 | case wf::VIEW_ROLE_TOPLEVEL: 78 | return "toplevel"; 79 | 80 | case wf::VIEW_ROLE_UNMANAGED: 81 | return "unmanaged"; 82 | 83 | case wf::VIEW_ROLE_DESKTOP_ENVIRONMENT: 84 | return "desktop-environment"; 85 | 86 | default: 87 | return "unknown"; 88 | } 89 | } 90 | 91 | static inline std::string layer_to_string(std::optional layer) 92 | { 93 | if (!layer.has_value()) 94 | { 95 | return "none"; 96 | } 97 | 98 | switch (layer.value()) 99 | { 100 | case wf::scene::layer::BACKGROUND: 101 | return "background"; 102 | 103 | case wf::scene::layer::BOTTOM: 104 | return "bottom"; 105 | 106 | case wf::scene::layer::WORKSPACE: 107 | return "workspace"; 108 | 109 | case wf::scene::layer::TOP: 110 | return "top"; 111 | 112 | case wf::scene::layer::UNMANAGED: 113 | return "unmanaged"; 114 | 115 | case wf::scene::layer::OVERLAY: 116 | return "overlay"; 117 | 118 | case wf::scene::layer::LOCK: 119 | return "lock"; 120 | 121 | case wf::scene::layer::DWIDGET: 122 | return "dew"; 123 | 124 | default: 125 | break; 126 | } 127 | 128 | wf::dassert(false, "invalid layer!"); 129 | assert(false); // prevent compiler warning 130 | } 131 | 132 | static inline std::string get_view_type(wayfire_view view) 133 | { 134 | if (view->role == wf::VIEW_ROLE_TOPLEVEL) 135 | { 136 | return "toplevel"; 137 | } 138 | 139 | if (view->role == wf::VIEW_ROLE_UNMANAGED) 140 | { 141 | #if WF_HAS_XWAYLAND 142 | auto surf = view->get_wlr_surface(); 143 | if (surf && wlr_xwayland_surface_try_from_wlr_surface(surf)) 144 | { 145 | return "x-or"; 146 | } 147 | 148 | #endif 149 | 150 | return "unmanaged"; 151 | } 152 | 153 | auto layer = wf::get_view_layer(view); 154 | if ((layer == wf::scene::layer::BACKGROUND) || (layer == wf::scene::layer::BOTTOM)) 155 | { 156 | return "background"; 157 | } else if (layer == wf::scene::layer::TOP) 158 | { 159 | return "panel"; 160 | } else if (layer == wf::scene::layer::OVERLAY) 161 | { 162 | return "overlay"; 163 | } 164 | 165 | return "unknown"; 166 | } 167 | 168 | static inline wf::json_t view_to_json(wayfire_view view) 169 | { 170 | if (!view) 171 | { 172 | return wf::json_t::null(); 173 | } 174 | 175 | auto output = view->get_output(); 176 | wf::json_t description; 177 | description["id"] = view->get_id(); 178 | description["pid"] = get_view_pid(view); 179 | description["title"] = view->get_title(); 180 | description["app-id"] = view->get_app_id(); 181 | description["base-geometry"] = wf::ipc::geometry_to_json(get_view_base_geometry(view)); 182 | auto toplevel = wf::toplevel_cast(view); 183 | description["parent"] = toplevel && toplevel->parent ? (int)toplevel->parent->get_id() : -1; 184 | description["geometry"] = 185 | wf::ipc::geometry_to_json(toplevel ? toplevel->get_pending_geometry() : view->get_bounding_box()); 186 | description["bbox"] = wf::ipc::geometry_to_json(view->get_bounding_box()); 187 | description["output-id"] = view->get_output() ? view->get_output()->get_id() : -1; 188 | description["output-name"] = output ? output->to_string() : "null"; 189 | description["last-focus-timestamp"] = wf::get_focus_timestamp(view); 190 | description["role"] = role_to_string(view->role); 191 | description["mapped"] = view->is_mapped(); 192 | description["layer"] = layer_to_string(get_view_layer(view)); 193 | description["tiled-edges"] = toplevel ? toplevel->pending_tiled_edges() : 0; 194 | description["fullscreen"] = toplevel ? toplevel->pending_fullscreen() : false; 195 | description["minimized"] = toplevel ? toplevel->minimized : false; 196 | description["activated"] = toplevel ? toplevel->activated : false; 197 | description["sticky"] = toplevel ? toplevel->sticky : false; 198 | description["wset-index"] = toplevel && toplevel->get_wset() ? 199 | static_cast(toplevel->get_wset()->get_index()) : 200 | -1; 201 | description["min-size"] = wf::ipc::dimensions_to_json( 202 | toplevel ? toplevel->toplevel()->get_min_size() : wf::dimensions_t{0, 0}); 203 | description["max-size"] = wf::ipc::dimensions_to_json( 204 | toplevel ? toplevel->toplevel()->get_max_size() : wf::dimensions_t{0, 0}); 205 | description["focusable"] = view->is_focusable(); 206 | description["type"] = get_view_type(view); 207 | 208 | return description; 209 | } 210 | 211 | static inline wf::json_t wset_to_json(wf::workspace_set_t *wset) 212 | { 213 | if (!wset) 214 | { 215 | return wf::json_t::null(); 216 | } 217 | 218 | wf::json_t response; 219 | response["index"] = wset->get_index(); 220 | response["name"] = wset->to_string(); 221 | 222 | auto output = wset->get_attached_output(); 223 | response["output-id"] = output ? (int)output->get_id() : -1; 224 | response["output-name"] = output ? output->to_string() : ""; 225 | response["workspace"]["x"] = wset->get_current_workspace().x; 226 | response["workspace"]["y"] = wset->get_current_workspace().y; 227 | response["workspace"]["grid_width"] = wset->get_workspace_grid_size().width; 228 | response["workspace"]["grid_height"] = wset->get_workspace_grid_size().height; 229 | return response; 230 | } 231 | -------------------------------------------------------------------------------- /src/plugin/wayfire-information.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2023 Scott Moreau 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | 41 | #include "wayfire-information.hpp" 42 | #include "wayfire-information-server-protocol.h" 43 | 44 | extern "C" 45 | { 46 | #include 47 | } 48 | 49 | wf::plugin_activation_data_t grab_interface{ 50 | .name = "wf-info", 51 | .capabilities = wf::CAPABILITY_GRAB_INPUT, 52 | }; 53 | 54 | static void bind_manager(wl_client *client, void *data, 55 | uint32_t version, uint32_t id); 56 | 57 | void wayfire_information::send_view_info(wayfire_view view) 58 | { 59 | if (!view) 60 | { 61 | if (ipc_call) 62 | { 63 | ipc_response = wf::ipc::json_error("No view found"); 64 | ipc_call = false; 65 | } 66 | return; 67 | } 68 | if (ipc_call) 69 | { 70 | ipc_response = wf::ipc::json_ok(); 71 | ipc_response["info"] = view_to_json(view); 72 | ipc_call = false; 73 | return; 74 | } 75 | auto output = view->get_output(); 76 | if (!output) 77 | { 78 | return; 79 | } 80 | 81 | std::string role; 82 | switch (view->role) 83 | { 84 | case wf::VIEW_ROLE_TOPLEVEL: 85 | role = "TOPLEVEL"; 86 | break; 87 | case wf::VIEW_ROLE_UNMANAGED: 88 | role = "UNMANAGED"; 89 | break; 90 | case wf::VIEW_ROLE_DESKTOP_ENVIRONMENT: 91 | role = "DESKTOP_ENVIRONMENT"; 92 | break; 93 | default: 94 | role = "UNKNOWN"; 95 | break; 96 | } 97 | 98 | auto og = output->get_screen_size(); 99 | auto ws = output->wset()->get_current_workspace(); 100 | auto wm = wf::view_bounding_box_up_to(view); 101 | wf::point_t workspace = { 102 | ws.x + (int)std::floor((wm.x + wm.width / 2.0) / og.width), 103 | ws.y + (int)std::floor((wm.y + wm.height / 2.0) / og.height) 104 | }; 105 | 106 | auto toplevel = toplevel_cast(view); 107 | wf::geometry_t vg{0, 0, 0, 0}; 108 | if (toplevel) 109 | { 110 | vg = toplevel->get_geometry(); 111 | } 112 | 113 | pid_t pid = -1; 114 | wlr_surface *wlr_surface = view->get_wlr_surface(); 115 | int is_xwayland_surface = 0; 116 | #if WF_HAS_XWAYLAND 117 | is_xwayland_surface = wlr_surface && wlr_xwayland_surface_try_from_wlr_surface(wlr_surface); 118 | if (is_xwayland_surface) 119 | { 120 | pid = wlr_xwayland_surface_try_from_wlr_surface(wlr_surface)->pid; 121 | } else 122 | #endif 123 | { 124 | if (view->get_client()) 125 | { 126 | wl_client_get_credentials(view->get_client(), &pid, 0, 0); 127 | } 128 | } 129 | 130 | int focused = wf::get_active_view_for_output(output) == view; 131 | 132 | for (auto r : client_resources) 133 | { 134 | wf_info_base_send_view_info(r, view->get_id(), 135 | pid, 136 | workspace.x, 137 | workspace.y, 138 | view->get_app_id().c_str(), 139 | view->get_title().c_str(), 140 | role.c_str(), 141 | vg.x, 142 | vg.y, 143 | vg.width, 144 | vg.height, 145 | is_xwayland_surface, 146 | focused, 147 | output->to_string().c_str(), 148 | output->get_id()); 149 | } 150 | } 151 | 152 | void wayfire_information::deactivate() 153 | { 154 | for (auto& o : wf::get_core().output_layout->get_outputs()) 155 | { 156 | o->deactivate_plugin(&grab_interface); 157 | input_grabs[o]->ungrab_input(); 158 | input_grabs[o].reset(); 159 | } 160 | 161 | idle_set_cursor.run_once([this] () 162 | { 163 | wf::get_core().set_cursor("default"); 164 | send_view_info(wf::get_core().get_cursor_focus_view()); 165 | for (auto r : client_resources) 166 | { 167 | wf_info_base_send_done(r); 168 | } 169 | }); 170 | wl_call = false; 171 | } 172 | 173 | void wayfire_information::end_grab() 174 | { 175 | deactivate(); 176 | } 177 | 178 | void wayfire_information::set_base_ptr(wf::pointer_interaction_t *base) 179 | { 180 | this->base = base; 181 | } 182 | 183 | wayfire_information::wayfire_information() 184 | { 185 | manager = wl_global_create(wf::get_core().display, 186 | &wf_info_base_interface, 1, this, bind_manager); 187 | 188 | if (!manager) 189 | { 190 | LOGE("Failed to create wayfire_information interface"); 191 | return; 192 | } 193 | 194 | get_view_info_ipc = [=] (wf::json_t data) 195 | { 196 | if (ipc_call) 197 | { 198 | return wf::ipc::json_error("Another ipc grab is already active."); 199 | } 200 | for (auto& o : wf::get_core().output_layout->get_outputs()) 201 | { 202 | input_grabs[o] = std::make_unique (grab_interface.name, o, nullptr, base, nullptr); 203 | 204 | if (!o->activate_plugin(&grab_interface)) 205 | { 206 | continue; 207 | } 208 | 209 | input_grabs[o]->grab_input(wf::scene::layer::OVERLAY); 210 | } 211 | 212 | idle_set_cursor.run_once([=] () 213 | { 214 | wf::get_core().set_cursor("crosshair"); 215 | }); 216 | 217 | ipc_call = true; 218 | while (ipc_call) 219 | { 220 | wl_event_loop_dispatch(wf::get_core().ev_loop, 0); 221 | } 222 | 223 | return ipc_response; 224 | }; 225 | 226 | ipc_repo->register_method("wf-info/get_view_info", get_view_info_ipc); 227 | } 228 | 229 | wayfire_information::~wayfire_information() 230 | { 231 | wl_global_destroy(manager); 232 | 233 | for (auto& o : wf::get_core().output_layout->get_outputs()) 234 | { 235 | input_grabs[o].reset(); 236 | } 237 | } 238 | 239 | wayfire_view view_from_id(int32_t id) 240 | { 241 | if (id == -1) 242 | { 243 | return wf::get_active_view_for_output(wf::get_core().seat->get_active_output()); 244 | } 245 | 246 | for (auto& view : wf::get_core().get_all_views()) 247 | { 248 | if (int32_t(view->get_id()) == id) 249 | { 250 | return view; 251 | } 252 | } 253 | 254 | return nullptr; 255 | } 256 | 257 | static void get_view_info(struct wl_client *client, struct wl_resource *resource) 258 | { 259 | wayfire_information *wd = (wayfire_information*)wl_resource_get_user_data(resource); 260 | 261 | if (wd->wl_call) 262 | { 263 | return; 264 | } 265 | wd->wl_call = true; 266 | 267 | for (auto& o : wf::get_core().output_layout->get_outputs()) 268 | { 269 | wd->input_grabs[o] = std::make_unique (grab_interface.name, o, nullptr, wd->base, nullptr); 270 | 271 | if (!o->activate_plugin(&grab_interface)) 272 | { 273 | continue; 274 | } 275 | 276 | wd->input_grabs[o]->grab_input(wf::scene::layer::OVERLAY); 277 | } 278 | 279 | wd->idle_set_cursor.run_once([wd] () 280 | { 281 | wf::get_core().set_cursor("crosshair"); 282 | }); 283 | } 284 | 285 | static void send_view_info_from_id(struct wl_client *client, struct wl_resource *resource, int id) 286 | { 287 | wayfire_information *wd = (wayfire_information*)wl_resource_get_user_data(resource); 288 | 289 | auto view = view_from_id(id); 290 | 291 | if (!view) 292 | { 293 | return; 294 | } 295 | 296 | wd->send_view_info(view); 297 | 298 | for (auto r : wd->client_resources) 299 | { 300 | wf_info_base_send_done(r); 301 | } 302 | } 303 | 304 | static void send_all_views(struct wl_client *client, struct wl_resource *resource) 305 | { 306 | wayfire_information *wd = (wayfire_information*)wl_resource_get_user_data(resource); 307 | 308 | for (auto& view : wf::get_core().get_all_views()) 309 | { 310 | if (view->role != wf::VIEW_ROLE_TOPLEVEL && 311 | view->role != wf::VIEW_ROLE_DESKTOP_ENVIRONMENT) 312 | { 313 | continue; 314 | } 315 | wd->send_view_info(view); 316 | } 317 | 318 | for (auto r : wd->client_resources) 319 | { 320 | wf_info_base_send_done(r); 321 | } 322 | } 323 | 324 | static const struct wf_info_base_interface wayfire_information_impl = 325 | { 326 | .view_info = get_view_info, 327 | .view_info_id = send_view_info_from_id, 328 | .view_info_list = send_all_views, 329 | }; 330 | 331 | static void destroy_client(wl_resource *resource) 332 | { 333 | wayfire_information *wd = (wayfire_information*)wl_resource_get_user_data(resource); 334 | 335 | for (auto& r : wd->client_resources) 336 | { 337 | if (r == resource) 338 | { 339 | r = nullptr; 340 | } 341 | } 342 | wd->client_resources.erase(std::remove(wd->client_resources.begin(), 343 | wd->client_resources.end(), nullptr), wd->client_resources.end()); 344 | } 345 | 346 | static void bind_manager(wl_client *client, void *data, 347 | uint32_t version, uint32_t id) 348 | { 349 | wayfire_information *wd = (wayfire_information*)data; 350 | 351 | auto resource = 352 | wl_resource_create(client, &wf_info_base_interface, 1, id); 353 | wl_resource_set_implementation(resource, 354 | &wayfire_information_impl, data, destroy_client); 355 | wd->client_resources.push_back(resource); 356 | 357 | } 358 | -------------------------------------------------------------------------------- /src/plugin/wayfire-information.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2023 Scott Moreau 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | 26 | #pragma once 27 | 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include "ipc-rules-common.hpp" 33 | 34 | class wayfire_information 35 | { 36 | wl_global *manager; 37 | 38 | public: 39 | wf::pointer_interaction_t *base; 40 | std::vector client_resources; 41 | void send_view_info(wayfire_view view); 42 | void deactivate(); 43 | void set_base_ptr(wf::pointer_interaction_t *base); 44 | wf::wl_idle_call idle_set_cursor; 45 | std::map> input_grabs; 46 | bool ipc_call = false; 47 | bool wl_call = false; 48 | wf::json_t ipc_response; 49 | wf::ipc::method_callback get_view_info_ipc; 50 | wf::shared_data::ref_ptr_t ipc_repo; 51 | void end_grab(); 52 | wayfire_information(); 53 | ~wayfire_information(); 54 | }; 55 | --------------------------------------------------------------------------------