pax_global_header00006660000000000000000000000064145743145760014532gustar00rootroot0000000000000052 comment=01726528ace8ed71216f23c3b0fb3344cda5a461 wayfire-0.8.1/000077500000000000000000000000001457431457600132065ustar00rootroot00000000000000wayfire-0.8.1/.editorconfig000066400000000000000000000002771457431457600156710ustar00rootroot00000000000000root = true [*] end_of_line = lf insert_final_newline = true charset = utf-8 indent_style = space indent_size = 4 [*.{build,xml,yaml}] indent_size = 2 [metadata/**.xml] indent_style = tab wayfire-0.8.1/.github/000077500000000000000000000000001457431457600145465ustar00rootroot00000000000000wayfire-0.8.1/.github/ISSUE_TEMPLATE/000077500000000000000000000000001457431457600167315ustar00rootroot00000000000000wayfire-0.8.1/.github/ISSUE_TEMPLATE/bug_report.md000066400000000000000000000012761457431457600214310ustar00rootroot00000000000000--- name: Bug report about: Bad behavior (crash, something doesn't work, etc.) title: '' labels: bug assignees: '' --- **Describe the bug** A clear and concise description of what the bug is. **To Reproduce** Steps to reproduce the behavior: 1. Enable plugin X 2. Do Y 3. Z happens **Expected behavior** A clear and concise description of what you expected to happen. **Screenshots or stacktrace** If applicable, add screenshots to help explain your problem. If it is a crash, attach the backtrace (or the whole log file), Wayfire will print it in the end of the log file or stdout. Backtrace with address sanitizer enabled (if possible): **Wayfire version** 0.5.0, git, package, something else? wayfire-0.8.1/.github/ISSUE_TEMPLATE/feature_request.md000066400000000000000000000003411457431457600224540ustar00rootroot00000000000000--- name: Feature request about: Suggest an idea for this project title: '' labels: enhancement assignees: '' --- Please describe what you want Wayfire to do. If applicable, give an example: User does X, Y, Wayfire does Z wayfire-0.8.1/.github/ISSUE_TEMPLATE/others.md000066400000000000000000000001651457431457600205610ustar00rootroot00000000000000--- name: Others about: Issues which are neither bugs nor feature requests title: '' labels: '' assignees: '' --- wayfire-0.8.1/.github/workflows/000077500000000000000000000000001457431457600166035ustar00rootroot00000000000000wayfire-0.8.1/.github/workflows/ci.yaml000066400000000000000000000052571457431457600200730ustar00rootroot00000000000000name: CI on: [push, pull_request] jobs: test_musl_gcc: name: "Test with GCC/musl/libstdc++/BFD on Alpine Linux" runs-on: ubuntu-latest container: alpine:edge steps: - run: apk --no-cache add git gcc g++ binutils pkgconf meson ninja musl-dev wayland-dev wayland-protocols libinput-dev libevdev-dev libxkbcommon-dev pixman-dev glm-dev libdrm-dev mesa-dev cairo-dev pango-dev eudev-dev libxml2-dev libseat-dev libxcb-dev xcb-util-wm-dev xwayland doctest doctest-dev cmake libdisplay-info-dev hwdata-dev - uses: actions/checkout@v1 - run: git config --global --add safe.directory /__w/wayfire/wayfire - run: git submodule sync --recursive && git submodule update --init --force --recursive - run: (cd subprojects/wlroots && meson build --prefix=/usr && ninja -C build install) - run: meson build -Dtests=enabled -Db_pch=true -Duse_system_wlroots=enabled - run: ninja -v -Cbuild - run: ninja -v -Cbuild test test_glibc_llvm: name: "Test with clang/glibc/libc++/lld on Arch Linux" runs-on: ubuntu-latest container: image: archlinux:latest steps: - run: sed -i 's/SigLevel = Required DatabaseOptional/SigLevel = Optional TrustAll/' /etc/pacman.conf - run: pacman --noconfirm --noprogressbar -Syyu - run: pacman --noconfirm --noprogressbar -Sy git clang lld libc++ pkgconf cmake meson ninja wayland wayland-protocols libinput libxkbcommon pixman glm libdrm libglvnd cairo pango systemd scdoc base-devel seatd hwdata libdisplay-info # Build Wayfire - uses: actions/checkout@v1 - run: git config --global --add safe.directory /__w/wayfire/wayfire - run: git submodule sync --recursive && git submodule update --init --force --recursive - run: (cd subprojects/wlroots && env CC=clang CXX=clang++ CXXFLAGS="-stdlib=libc++" LDFLAGS="-fuse-ld=lld -stdlib=libc++" meson build --prefix=/usr && ninja -C build install) - run: env CC=clang CXX=clang++ CXXFLAGS="-stdlib=libc++" LDFLAGS="-fuse-ld=lld -stdlib=libc++" meson build -Db_pch=true -Duse_system_wlroots=enabled --unity on - run: ninja -v -Cbuild - run: ninja -v -Cbuild test test_code_style: name: "Check code style with uncrustify" runs-on: ubuntu-latest steps: - run: sudo apt-get update - run: sudo apt-get install -y git cmake gcc make - uses: actions/checkout@v1 - run: git clone http://github.com/ammen99/uncrustify - run: cd uncrustify && mkdir build && cd build && cmake ../ && make && cd ../../ - run: git ls-files | grep "hpp$\|cpp$" | xargs ./uncrustify/build/uncrustify -c uncrustify.ini --no-backup --replace - run: git diff - run: git diff | diff - /dev/null &> /dev/null wayfire-0.8.1/.github/workflows/mandoc.yaml000066400000000000000000000006001457431457600207240ustar00rootroot00000000000000name: mandoc on: push: branches: - '*' pull_request: branches: - '*' jobs: pipeline: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - name: Install dependencies run: | sudo apt update sudo apt-get install -y mandoc - name: Check man pages run: | mandoc -T lint -W warning man/wayfire.1.in wayfire-0.8.1/.gitignore000066400000000000000000000017331457431457600152020ustar00rootroot00000000000000 # Created by https://www.toptal.com/developers/gitignore/api/c++,meson,ninja,linux # Edit at https://www.toptal.com/developers/gitignore?templates=c++,meson,ninja,linux ### C++ ### # Prerequisites *.d # Compiled Object files *.slo *.lo *.o *.obj # Precompiled Headers *.gch *.pch # Compiled Dynamic libraries *.so *.dylib *.dll # Fortran module files *.mod *.smod # Compiled Static libraries *.lai *.la *.a *.lib # Executables *.exe *.out *.app ### Linux ### *~ # temporary files which can be created if a process still has a handle open of a deleted file .fuse_hidden* # KDE directory preferences .directory # Linux trash folder which might appear on any partition or disk .Trash-* # .nfs files are created when an open file is removed but is still being accessed .nfs* ### Meson ### # subproject directories /subprojects/* !/subprojects/*.wrap ### Ninja ### .ninja_deps .ninja_log build/** # End of https://www.toptal.com/developers/gitignore/api/c++,meson,ninja,linuxwayfire-0.8.1/.gitmodules000066400000000000000000000007061457431457600153660ustar00rootroot00000000000000[submodule "subprojects/wf-config"] path = subprojects/wf-config url = https://github.com/WayfireWM/wf-config [submodule "subprojects/wlroots"] path = subprojects/wlroots url = https://gitlab.freedesktop.org/wlroots/wlroots.git [submodule "subprojects/wf-utils"] path = subprojects/wf-utils url = https://github.com/WayfireWM/wf-utils.git [submodule "subprojects/wf-touch"] path = subprojects/wf-touch url = https://github.com/WayfireWM/wf-touch wayfire-0.8.1/CONTRIBUTING.md000066400000000000000000000060351457431457600154430ustar00rootroot00000000000000# Contributing ## Documentation Updating the documentation is one of the easiest way to contribute to the project. The [wiki](https://github.com/WayfireWM/wayfire/wiki) is the primary location for documentation and is editable by everyone. Feel free to do minor changes or additions (for example, adding a missing option or adding a tip) without consulting anyone. Make contact with the maintainer(s) of the repository if you want to make big changes. ## Issues Opening quality issues is another good way to contribute. See the issue templates for information about what a good issue would look like. ## Pull requests Pull requests are welcome, be it a bug fix or a new feature. A lot of ideas are already on the GitHub issue tracker: - The label [good first issue](https://github.com/WayfireWM/wayfire/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22) indicates items which everyone with basic programming knowledge in C++ should be able to implement. - The label [easy](https://github.com/WayfireWM/wayfire/issues?q=is%3Aopen+is%3Aissue+label%3Aeasy) indicates items which do not require deep knowledge about the codebase, and whose solution is relatively simple. - The label [help wanted](https://github.com/WayfireWM/wayfire/issues?q=is%3Aopen+is%3Aissue+label%3A%22help+wanted%22) indicates items which require more hardware or knowledge than we have available, so external contributions are needed. - The label `low priority` indicates that something is unlikely to be implemented by @ammen99 anytime soon, but a PR would still be reviewed and merged. - The label `external-plugin` indicates that the feature can be implemented in a plugin which will not be included in the main repository. Some of the issues have milestones. These are used to check which features are planned by @ammen99 for the given release. However, milestones are not firmly set, PRs by anyone for any issue can be merged at any time. If you want to work on a feature or a bug fix which does not have an open issue, it would be best to open a new one or at least contact the maintainer(s) to make sure your changes will be accepted. The base repository is meant only for common functionality like Autostart, Expo, Vswitch, Scale or plugins which demonstrate particular core features like Cube, Fisheye, Extra-gestures, etc. In any case, feel free to ask questions if you do not understand a part of the code, or if you are unsure how a particular feature should be implemented. ### Code Formatting Please use [`uncrustify`](https://github.com/uncrustify/uncrustify) (version `>=0.71`) to automatically format the code before committing: ```sh $ git ls-files | grep "hpp$\|cpp$" | xargs uncrustify -c uncrustify.ini --no-backup ``` You can setup a [githook](https://git-scm.com/docs/githooks) to run this automatically before committing. ## Contacting the maintainer(s) The primary communication channels are Matrix (#wayfire:matrix.org) and IRC (#wayfire at Libera.chat). The two channels are bridged together. Use GitHub to ask questions only if you are unable to access IRC and Matrix. wayfire-0.8.1/LICENSE000066400000000000000000000020711457431457600142130ustar00rootroot00000000000000The MIT License (MIT) Copyright (c) 2018 Iliya Bozhinov 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. wayfire-0.8.1/README.md000066400000000000000000000131041457431457600144640ustar00rootroot00000000000000# [Wayfire] [Wayfire]: https://wayfire.org ![Version](https://img.shields.io/github/v/release/WayfireWM/wayfire) [![Matrix: #wayfire:matrix.org](https://img.shields.io/badge/matrix-%23wayfire%3Amatrix.org-blue)](https://matrix.to/#/#wayfire:matrix.org) [![IRC: #wayfire on Libera.chat](https://img.shields.io/badge/IRC-%23wayfire%20at%20libera.chat-green)](https://web.libera.chat/#wayfire) [![Discord](https://img.shields.io/discord/1144831589877043220?label=Discord)](https://discord.gg/5SWAxmBCUH) [![CI](https://github.com/WayfireWM/wayfire/workflows/CI/badge.svg)](https://github.com/WayfireWM/wayfire/actions) [![Packaging status](https://repology.org/badge/tiny-repos/wayfire.svg)](https://repology.org/project/wayfire/versions) [![License](https://img.shields.io/github/license/WayfireWM/wayfire)](LICENSE) ###### [Get started] | [Manual] | [Configuration] [Get started]: https://github.com/WayfireWM/wayfire/wiki/Tutorial [Manual]: https://github.com/WayfireWM/wayfire/wiki/General [Configuration]: https://github.com/WayfireWM/wayfire/wiki/Configuration Wayfire is a 3D [Wayland] compositor, inspired by [Compiz] and based on [wlroots]. It aims to create a customizable, extendable and lightweight environment without sacrificing its appearance. [![Wayfire demos](https://img.youtube.com/vi_webp/2PtNzxDsxYM/maxresdefault.webp)](https://youtube.com/playlist?list=PLb7YRKEhWEBUIoT-a29UoJW9mhfzjpNle "YouTube – Wayfire demos") [![YouTube Play Button](https://www.iconfinder.com/icons/317714/download/png/16)](https://youtube.com/playlist?list=PLb7YRKEhWEBUIoT-a29UoJW9mhfzjpNle) · [Wayfire demos](https://youtube.com/playlist?list=PLb7YRKEhWEBUIoT-a29UoJW9mhfzjpNle) [Wayland]: https://wayland.freedesktop.org [wlroots]: https://github.com/swaywm/wlroots [Compiz]: https://launchpad.net/compiz ## Dependencies ### Wayfire Dependencies These are the dependencies needed for building Wayfire. - [Cairo](https://cairographics.org) - [Pango](https://pango.gnome.org/) and PangoCairo - [FreeType](https://freetype.org) - [GLM](https://glm.g-truc.net) - [libdrm](https://dri.freedesktop.org/wiki/DRM/) - [libevdev](https://freedesktop.org/wiki/Software/libevdev/) - [libGL](https://mesa3d.org) - [libinput](https://freedesktop.org/wiki/Software/libinput/) - [libjpeg](https://libjpeg-turbo.org) - [libpng](http://libpng.org/pub/png/libpng.html) - [libxkbcommon](https://xkbcommon.org) - [libxml2](http://xmlsoft.org/) - [Pixman](https://pixman.org) - [pkg-config](https://freedesktop.org/wiki/Software/pkg-config/) - [Wayland](https://wayland.freedesktop.org) - [wayland-protocols](https://gitlab.freedesktop.org/wayland/wayland-protocols) - [wf-config](https://github.com/WayfireWM/wf-config) - [wlroots](https://github.com/swaywm/wlroots) ### wlroots Dependencies These are the dependencies needed for building wlroots, and should be installed before building it. They are relevant for cases when the system doesn't have a version of wlroots installed. #### DRM Backend (optional, but you most likely want this) - [libdisplay-info-dev](https://gitlab.freedesktop.org/emersion/libdisplay-info) - [hwdata-dev](https://github.com/vcrhonek/hwdata) #### Session Provider (optional, recommended) - [systemd](https://systemd.io/) **or** - [elogind](https://github.com/elogind/elogind) **or** - [seatd](https://git.sr.ht/~kennylevinsen/seatd) #### XWayland Support (optional) - [xcb](https://xcb.freedesktop.org/) - [xcb-composite](https://xorg.freedesktop.org/wiki/) - [xcb-render](https://xorg.freedesktop.org/wiki/) - [xcb-xfixes](https://xorg.freedesktop.org/wiki/) #### X11 Backend (optional) - [xcb](https://xcb.freedesktop.org/) - [x11-xcb](https://xcb.freedesktop.org/) - [xcb-xinput](https://xorg.freedesktop.org/wiki/) - [xcb-xfixes](https://xorg.freedesktop.org/wiki/) ## Installation The easiest way to install Wayfire, wf-shell and WCM to get a functional desktop is to use the [install scripts](https://github.com/WayfireWM/wf-install). Alternatively, you can build from source: ``` sh meson build ninja -C build sudo ninja -C build install ``` **Note**: `wf-config` and `wlroots` can be built as submodules, by specifying `-Duse_system_wfconfig=disabled` and `-Duse_system_wlroots=disabled` options to `meson`. This is the default if they are not present on your system. Installing [wf-shell](https://github.com/WayfireWM/wf-shell) is recommended for a complete experience. ###### Arch Linux [wayfire](https://aur.archlinux.org/packages/wayfire/) and [wayfire-git] are available in the [AUR]. ``` sh yay -S wayfire ``` [AUR]: https://aur.archlinux.org [wayfire-git]: https://aur.archlinux.org/packages/wayfire-git/ ###### Exherbo ``` sh cave resolve -x wayfire ``` ###### Fedora ``` sh dnf install wayfire ``` ###### FreeBSD Install the latest release and recommended addons with ``` sh pkg install wayfire wayfire-plugins-extra wf-shell wcm ``` ###### Gentoo Install the latest release with ```sh emerge --ask --verbose wayfire ``` and to use the live version ```sh emerge --ask --verbose "=gui-wm/wayfire-9999" ``` ###### NixOS Enable Wayfire in your NixOS configuration: ```nix programs.wayfire = { enable = true; plugins = with pkgs.wayfirePlugins; [ wcm wf-shell wayfire-plugins-extra ]; }; ``` ###### Ubuntu ``` apt install wayfire ``` ###### Void ``` sh xbps-install -S wayfire ``` ## Configuration Copy [`wayfire.ini`] to `~/.config/wayfire.ini`. Before running Wayfire, you may want to change the command to start a terminal. See the [Configuration] document for information on the options. [`wayfire.ini`]: wayfire.ini ## Running Run [`wayfire`][Manual] from a TTY, or via a Wayland-compatible login manager. wayfire-0.8.1/config.h.in000066400000000000000000000005751457431457600152400ustar00rootroot00000000000000#ifndef CONFIG_H #define CONFIG_H #define INSTALL_PREFIX "@INSTALL_PREFIX@" #define PLUGIN_PATH "@PLUGIN_PATH@" #define PLUGIN_XML_DIR "@PLUGIN_XML_DIR@" #define SYSCONFDIR "@SYSCONFDIR@" #define WF_DEFAULT_CONFIG_BACKEND "@DEFAULT_CONFIG_BACKEND@" #mesondefine BUILD_WITH_IMAGEIO #mesondefine USE_GLES32 #mesondefine WF_HAS_XWAYLAND #endif /* end of include guard: CONFIG_H */ wayfire-0.8.1/ipc-scripts/000077500000000000000000000000001457431457600154465ustar00rootroot00000000000000wayfire-0.8.1/ipc-scripts/inactive-alpha.py000066400000000000000000000017071457431457600207120ustar00rootroot00000000000000#!/usr/bin/python3 # # This script demonstrates how Wayfire's IPC can be used to set the opacity of inactive views. import os from wayfire_socket import * addr = os.getenv('WAYFIRE_SOCKET') sock = WayfireSocket(addr) 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. if "event" in msg: print(msg["event"]) view = msg["view"] new_focus = view["id"] if view and view["type"] == "toplevel" else -1 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) except: print("Last focused toplevel was closed?") if new_focus != -1: sock.set_view_alpha(new_focus, 1.0) last_focused_toplevel = new_focus wayfire-0.8.1/ipc-scripts/ipc-rules-demo.py000066400000000000000000000036641457431457600206560ustar00rootroot00000000000000#!/usr/bin/python3 # # This is a simple script which demonstrates how to use the Wayfire IPC socket with the wayfire_socket.py helper. # To use this, make sure that the ipc plugin is first in the plugin list, so that the WAYFIRE_SOCKET environment # variable propagates to all processes, including autostarted processes. # To use this script, the ipc-rules plugin should also be enabled, as it provides some of the basic events and commands # required for IPC interaction. # # 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 * 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']) while True: msg = events_sock.read_message() # 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"]) print(output_data) workarea = output_data["workarea"] # We want gedit to take a certain position (200,200) and a quarter of the output x = 200 y = 200 w = workarea['width'] // 2 h = workarea['height'] // 2 commands_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) wayfire-0.8.1/ipc-scripts/list-methods.py000066400000000000000000000006461457431457600204420ustar00rootroot00000000000000import wayfire_socket as ws import os import json addr = os.getenv('WAYFIRE_SOCKET') sock = ws.WayfireSocket(addr) query = ws.get_msg_template('wayfire/configuration') response = sock.send_json(query) print("Wayfire version:") print(json.dumps(response, indent=4)) query = ws.get_msg_template('list-methods') response = sock.send_json(query) print("Supported methods:") print(json.dumps(response['methods'], indent=4)) wayfire-0.8.1/ipc-scripts/move-to-workspace.py000077500000000000000000000024011457431457600214020ustar00rootroot00000000000000#!/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])) wayfire-0.8.1/ipc-scripts/set-touch-state.py000066400000000000000000000012571457431457600210560ustar00rootroot00000000000000import wayfire_socket as ws import os import sys # This is a small example of how to use the Wayfire socket to set the touchscreen state to on or off. # Can for example be used as a custom command in Xournalpp. # Usage: set-touch-state.py if len(sys.argv) != 2 or sys.argv[1] not in ['enabled', 'disabled']: print("Invalid usage, exactly one argument required, either 'enabled' or 'disabled'!") exit(-1) state: bool = sys.argv[1] == 'enabled' addr = os.getenv('WAYFIRE_SOCKET') sock = ws.WayfireSocket(addr) devices = sock.list_input_devices() for dev in devices: if dev['type'] == 'touch': print(sock.configure_input_device(dev['id'], state)) wayfire-0.8.1/ipc-scripts/wayfire_socket.py000066400000000000000000000067311457431457600210450ustar00rootroot00000000000000import socket import json as js 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(self): rlen = int.from_bytes(self.read_exact(4), 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 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_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) wayfire-0.8.1/man/000077500000000000000000000000001457431457600137615ustar00rootroot00000000000000wayfire-0.8.1/man/meson.build000066400000000000000000000002741457431457600161260ustar00rootroot00000000000000configure_file(input: 'wayfire.1.in', output: 'wayfire.1', configuration: conf_data) install_man(join_paths(meson.project_build_root(), 'man', 'wayfire.1')) wayfire-0.8.1/man/wayfire.1.in000066400000000000000000000051561457431457600161250ustar00rootroot00000000000000.Dd $Mdocdate: May 21 2023 $ .Dt WAYFIRE 1 .Os .Sh NAME .Nm wayfire .Nd modular and extensible wayland compositor .Sh SYNOPSIS .Nm wayfire .Op Fl c , -config Ar config_file .Op Fl B , -config-backend Ar config_backend .Op Fl d , -debug .Op Fl D , -damage-debug .Op Fl h , -help .Op Fl R , -damage-renderer .Op Fl v , -version .Sh DESCRIPTION .Nm is a wayland compositor focusing on modularity and extensibility by providing a small core compositor implementation with all major functionality being provided by plugins. The default plugins provide 3D effects similar to compiz, such as 3D cube, wobbly windows, blur, fish eye, etc. .Pp The optional flags are described as follows: .Pp .Bl -tag -width Ds -compact .It Fl c , -config Ar config_file .Pp Start .Nm with an alternative configuration file. The default configuration file is searched first in the .Ev ${WAYFIRE_CONFIG_FILE} environment variable, or paths .Pa ${XDG_CONFIG_HOME}/wayfire.ini , .Pa ${HOME}/.config/wayfire.ini . .Pp .It Fl B , -config-backend Ar config_backend .Pp Specify config backend to use. .Pp .It Fl d , -debug .Pp Enable debug logging. .Pp .It Fl D , -damage-debug .Pp Enable additional debug for damaged regions. .Pp .It Fl h , -help .Pp Print a short help message. .Pp .It Fl R , -damage-renderer .Pp Rerender damaged regions. .Pp .It Fl v , -version .Pp Print the version. .El .Sh ENVIRONMENT VARIABLES .Nm respects the following environment variables: .Pp .Bl -tag -width Ds -compact .It Ev WAYFIRE_CONFIG_FILE The config file to use. .Pp .It Ev WAYFIRE_PLUGIN_XML_PATH .Pp A string of paths, separated by : , in which to look for plugin configuration files. By default .Nm looks for configuration files in .Pa @PLUGIN_XML_DIR@ and .Pa ${XDG_DATA_HOME}/wayfire/metadata . .Pp .It Ev WAYFIRE_PLUGIN_PATH .Pp A string of paths, separated by : , in which to look for plugins. By default .Nm looks for plugins in .Pa @PLUGIN_PATH@ and .Pa ${XDG_DATA_HOME}/wayfire/plugins . .Pp .It Ev _WAYFIRE_SOCKET .Pp Socket override to use to communicate with a specific .Nm instance using the IPC. Useful incase of multiple running instances of .Nm . .El .Pp In addition, .Nm also sets the following environment variables: .Pp .Bl -tag -width Ds -compact .It Ev WAYFIRE_SOCKET .Pp Socket to use when communicating with .Nm . .Pp .It Ev WAYLAND_DISPLAY .Pp Wayland display currently in effect. .Pp .It Ev CONFIG_FILE_ENV .Pp Which config file is being used. .Pp .It Ev _JAVA_AWT_WM_NONREPARENTING .Pp Needed for java based windows and it is set to 1. .Pp .It Ev DISPLAY .It Ev XCURSOR_SIZE .It Ev XCURSOR_THEME .Pp Variables for use with legacy .Xr xwayland 1 windows. .El .Sh SEE ALSO .Xr xwayland 1 wayfire-0.8.1/meson.build000066400000000000000000000161261457431457600153560ustar00rootroot00000000000000project( 'wayfire', 'c', 'cpp', version: '0.8.1', license: 'MIT', meson_version: '>=0.63.0', default_options: [ 'cpp_std=c++17', 'c_std=c11', 'warning_level=2', 'werror=false', ], ) version = '"@0@"'.format(meson.project_version()) add_project_arguments('-DWAYFIRE_VERSION=@0@'.format(version), language: 'cpp') wayfire_api_inc = include_directories('src/api') wayland_server = dependency('wayland-server') wayland_client = dependency('wayland-client') wayland_cursor = dependency('wayland-cursor') wayland_protos = dependency('wayland-protocols', version: '>=1.12') cairo = dependency('cairo') pango = dependency('pango') pangocairo = dependency('pangocairo') drm = dependency('libdrm') egl = dependency('egl') glesv2 = dependency('glesv2') glm = dependency('glm') libinput = dependency('libinput', version: '>=1.7.0') pixman = dependency('pixman-1') threads = dependency('threads') xkbcommon = dependency('xkbcommon') libdl = meson.get_compiler('cpp').find_library('dl') # We're not to use system wlroots: So we'll use the subproject if get_option('use_system_wlroots').disabled() use_system_wlroots = false wlroots = subproject('wlroots', default_options : ['examples=false']).get_variable('wlroots') elif get_option('use_system_wlroots').enabled() use_system_wlroots = true wlroots = dependency('wlroots', version: ['>=0.17.0', '<0.18.0'], required: true) elif get_option('use_system_wlroots').auto() message( 'SEARCHING FOR WLROOTS' ) wlroots = dependency('wlroots', version: ['>=0.17.0', '<0.18.0'], required: false) use_system_wlroots = true if not wlroots.found() use_system_wlroots = false wlroots = subproject('wlroots', default_options : ['examples=false']).get_variable('wlroots') endif endif # We're not to use system wlroots: So we'll use the subproject if get_option('use_system_wfconfig').disabled() use_system_wfconfig = false wfconfig = subproject('wf-config').get_variable('wfconfig') elif get_option('use_system_wfconfig').enabled() use_system_wfconfig = true wfconfig = dependency('wf-config', version: ['>=0.8.0', '<0.9.0'], required: true) elif get_option('use_system_wfconfig').auto() wfconfig = dependency('wf-config', version: ['>=0.8.0', '<0.9.0'], required: false) use_system_wfconfig = true if not wfconfig.found() use_system_wfconfig = false wfconfig = subproject('wf-config').get_variable('wfconfig') endif endif wfutils = subproject('wf-utils').get_variable('wfutils') wftouch = subproject('wf-touch').get_variable('wftouch') needs_libinotify = ['freebsd', 'dragonfly'].contains(host_machine.system()) libinotify = dependency('libinotify', required: needs_libinotify) jpeg = dependency('libjpeg', required: false) png = dependency('libpng', required: false) # backtrace() is in a separate library on FreeBSD and Linux with musl backtrace = meson.get_compiler('cpp').find_library('execinfo', required: false) conf_data = configuration_data() conf_data.set('INSTALL_PREFIX', get_option('prefix')) conf_data.set('PLUGIN_PATH', join_paths(get_option('prefix'), get_option('libdir'), 'wayfire')) metadata_dir_suffix = 'share/wayfire/metadata' conf_data.set('PLUGIN_XML_DIR', join_paths(get_option('prefix'), metadata_dir_suffix)) sysconfdir = join_paths(get_option('prefix'), get_option('sysconfdir')) conf_data.set('SYSCONFDIR', sysconfdir) pkgdatadir = join_paths(get_option('prefix'), 'share', 'wayfire', 'protocols') if get_option('default_config_backend') == 'default' conf_data.set('DEFAULT_CONFIG_BACKEND', join_paths(conf_data.get('PLUGIN_PATH'), 'libdefault-config-backend.so')) else conf_data.set('DEFAULT_CONFIG_BACKEND', get_option('default_config_backend')) endif cpp = meson.get_compiler('cpp') # needed to dlopen() plugins # -E is for RTTI/dynamic_cast across plugins add_project_link_arguments(['-rdynamic', '-Wl,-E'], language: 'cpp') project_args = ['-DWLR_USE_UNSTABLE'] # Needed for dlclose to actually free plugin memory on gcc+glibc if cpp.has_argument('-fno-gnu-unique') project_args += '-fno-gnu-unique' endif add_project_arguments(project_args, language: ['cpp', 'c']) # Needed on some older compilers if cpp.has_link_argument('-lc++fs') add_project_link_arguments(['-lc++fs'], language: 'cpp') elif cpp.has_link_argument('-lc++experimental') add_project_link_arguments(['-lc++experimental'], language: 'cpp') elif cpp.has_link_argument('-lstdc++fs') add_project_link_arguments(['-lstdc++fs'], language: 'cpp') endif if get_option('enable_gles32') cpp.check_header('GLES3/gl32.h', dependencies: glesv2, required: true) conf_data.set('USE_GLES32', true) else conf_data.set('USE_GLES32', false) endif if png.found() and jpeg.found() conf_data.set('BUILD_WITH_IMAGEIO', true) else conf_data.set('BUILD_WITH_IMAGEIO', false) endif wayfire_conf_inc = include_directories(['.']) add_project_arguments(['-Wno-unused-parameter'], language: 'cpp') have_xwayland = false have_x11_backend = false if use_system_wlroots have_xwayland = cpp.get_define('WLR_HAS_XWAYLAND', prefix: '#include ', dependencies: wlroots) == '1' have_x11_backend = cpp.get_define('WLR_HAS_X11_BACKEND', prefix: '#include ', dependencies: wlroots) == '1' else have_xwayland = subproject('wlroots').get_variable('conf_data').get('WLR_HAS_XWAYLAND', false) == 1 have_x11_backend = subproject('wlroots').get_variable('conf_data').get('WLR_HAS_X11_BACKEND', false) == 1 endif if get_option('xwayland').enabled() and not have_xwayland error('Cannot enable Xwayland in wayfire: wlroots has been built without Xwayland support') endif if get_option('xwayland').enabled() have_xwayland = true elif get_option('xwayland').disabled() have_xwayland = false endif if have_xwayland xcb = dependency('xcb') conf_data.set('WF_HAS_XWAYLAND', 1) else xcb = declare_dependency() # dummy dep conf_data.set('WF_HAS_XWAYLAND', 0) endif if get_option('print_trace') print_trace = true else print_trace = false endif add_project_arguments(['-DWF_USE_CONFIG_H'], language: ['cpp', 'c']) configure_file(input: 'config.h.in', output: 'config.h', install: true, install_dir: join_paths('include', 'wayfire'), configuration: conf_data) subdir('proto') subdir('src') subdir('man') subdir('metadata') subdir('plugins') # Unit tests doctest = dependency('doctest', required: get_option('tests')) if doctest.found() subdir('test') endif install_data('wayfire.desktop', install_dir : join_paths(get_option('prefix'), 'share/wayland-sessions')) summary = [ '', '----------------', 'wayfire @0@'.format(meson.project_version()), '', 'system wfconfig: @0@'.format(use_system_wfconfig), ' system wlroots: @0@'.format(use_system_wlroots), ' xwayland: @0@'.format(have_xwayland), ' x11-backend: @0@'.format(have_x11_backend), ' imageio: @0@'.format(conf_data.get('BUILD_WITH_IMAGEIO')), ' gles32: @0@'.format(conf_data.get('USE_GLES32')), ' print trace: @0@'.format(print_trace), ' unit tests: @0@'.format(doctest.found()), '----------------', '' ] message('\n'.join(summary)) wayfire-0.8.1/meson_options.txt000066400000000000000000000016011457431457600166410ustar00rootroot00000000000000option('enable_gles32', type: 'boolean', value: true, description: 'Enable usage of GLES 3.2') option('use_system_wfconfig', type: 'feature', value: 'auto', description: 'Use the system-wide installation of wf-config') option('use_system_wlroots', type: 'feature', value: 'auto', description: 'Use the system-wide installation of wlroots') option('xwayland', type: 'feature', value: 'auto', description: 'Build with xwayland support. Requires wlroots also built with xwayland support') option('default_config_backend', type: 'string', value: 'default', description: 'Default configuration backend to use') option('print_trace', type: 'boolean', value: true, description: 'Print stack trace in debug logs (disables coredump)') option('tests', type: 'feature', value: 'auto', description: 'Enable unit tests') option('debug_ipc', type: 'boolean', value: 'true', description: 'Enable debugging IPC') wayfire-0.8.1/metadata/000077500000000000000000000000001457431457600147665ustar00rootroot00000000000000wayfire-0.8.1/metadata/alpha.xml000066400000000000000000000013341457431457600165760ustar00rootroot00000000000000 <_short>Alpha <_long>A plugin to change the opacity of windows. Desktop wayfire-0.8.1/metadata/animate.xml000066400000000000000000000102711457431457600171270ustar00rootroot00000000000000 <_short>Animate <_long>A plugin which provides animations when a window is opened or closed. Effects wayfire-0.8.1/metadata/autostart.xml000066400000000000000000000015611457431457600175410ustar00rootroot00000000000000 <_short>Autostart <_long>A plugin to run shell commands on startup. General <_short>Autostart <_long>Specifies the shell commands to run on startup. wayfire-0.8.1/metadata/blur.xml000066400000000000000000000074701457431457600164640ustar00rootroot00000000000000 <_short>Blur <_long>A plugin to blur windows. Effects wayfire-0.8.1/metadata/command.xml000066400000000000000000000044401457431457600171300ustar00rootroot00000000000000 <_short>Command <_long>A plugin to bind key combo (key, button, touch) to execute shell commands. General wayfire-0.8.1/metadata/core.xml000066400000000000000000000067211457431457600164460ustar00rootroot00000000000000 <_short>Core <_long>General options. General wayfire-0.8.1/metadata/cube.xml000066400000000000000000000073511457431457600164340ustar00rootroot00000000000000 <_short>Cube <_long>A plugin to show the current workspace row as a cube. Desktop <_short>Bindings <_long>Sets the cube bindings. wayfire-0.8.1/metadata/decoration.xml000066400000000000000000000030411457431457600176350ustar00rootroot00000000000000 <_short>Decoration <_long>Default decorations around XWayland windows. Effects wayfire-0.8.1/metadata/expo.xml000066400000000000000000000040231457431457600164620ustar00rootroot00000000000000 <_short>Expo <_long>A plugin to show an overview of all workspaces. Desktop wayfire-0.8.1/metadata/extra-gestures.xml000066400000000000000000000017301457431457600204730ustar00rootroot00000000000000 <_short>Extra Touchscreen Gestures <_long>A plugin which provides several touchscreen gestures. Desktop wayfire-0.8.1/metadata/fast-switcher.xml000066400000000000000000000016321457431457600202750ustar00rootroot00000000000000 <_short>Fast Switcher <_long>A plugin to switch active window. Window Management wayfire-0.8.1/metadata/fisheye.xml000066400000000000000000000014121457431457600171420ustar00rootroot00000000000000 <_short>Fisheye <_long>A plugin which provides fisheye effect. Effects wayfire-0.8.1/metadata/foreign-toplevel.xml000066400000000000000000000003731457431457600207740ustar00rootroot00000000000000 <_short>Foreign Toplevel Protocol <_long>An implementation of the wlr-foreign-toplevel-management-v1 protocol. Utility wayfire-0.8.1/metadata/grid.xml000066400000000000000000000067051457431457600164450ustar00rootroot00000000000000 <_short>Grid <_long>A plugin to position the windows in certain regions of the output. Window Management wayfire-0.8.1/metadata/gtk-shell.xml000066400000000000000000000003241457431457600174010ustar00rootroot00000000000000 <_short>GTK Shell Protocol <_long>An implementation of the gtk-shell protocol. Utility wayfire-0.8.1/metadata/idle.xml000066400000000000000000000042271457431457600164320ustar00rootroot00000000000000 <_short>Idle <_long>A plugin for idle settings, such as the screensaver and DPMS. Desktop wayfire-0.8.1/metadata/input-device.xml000066400000000000000000000003101457431457600200760ustar00rootroot00000000000000 General wayfire-0.8.1/metadata/input-method-v1.xml000066400000000000000000000004421457431457600204510ustar00rootroot00000000000000 <_short>Input-Method-V1 Protocol <_long>An implementation of the input-method-v1 protocol. Needed for proper functioning of input methods like Fcitx5. Utility wayfire-0.8.1/metadata/input.xml000066400000000000000000000212331457431457600166500ustar00rootroot00000000000000 <_short>Input <_long>Input configuration. General <_short>Keyboard <_long>Configure the keyboard. <_short>Mouse <_long>Configure the mouse. <_short>Touchpad <_long>Configure the touchpad. <_short>Tablet <_long>Configure graphic tablets. <_short>Cursor <_long>Configure the cursor. wayfire-0.8.1/metadata/invert.xml000066400000000000000000000011351457431457600170170ustar00rootroot00000000000000 <_short>Invert <_long>A plugin to invert the colors of the whole output. Accessibility wayfire-0.8.1/metadata/ipc-rules.xml000066400000000000000000000004251457431457600174140ustar00rootroot00000000000000 <_short>Window Rules IPC Interface <_long>Interface to allow an external program to implement window rules (requires the IPC plugin). Window Management wayfire-0.8.1/metadata/ipc.xml000066400000000000000000000003251457431457600162630ustar00rootroot00000000000000 <_short>IPC protocol <_long>Allow external programs to interact with Wayfire plugins. Utility wayfire-0.8.1/metadata/meson.build000066400000000000000000000063611457431457600171360ustar00rootroot00000000000000install_data('alpha.xml', install_dir: conf_data.get('PLUGIN_XML_DIR')) install_data('animate.xml', install_dir: conf_data.get('PLUGIN_XML_DIR')) install_data('autostart.xml', install_dir: conf_data.get('PLUGIN_XML_DIR')) install_data('blur.xml', install_dir: conf_data.get('PLUGIN_XML_DIR')) install_data('command.xml', install_dir: conf_data.get('PLUGIN_XML_DIR')) install_data('core.xml', install_dir: conf_data.get('PLUGIN_XML_DIR')) install_data('cube.xml', install_dir: conf_data.get('PLUGIN_XML_DIR')) install_data('decoration.xml', install_dir: conf_data.get('PLUGIN_XML_DIR')) install_data('expo.xml', install_dir: conf_data.get('PLUGIN_XML_DIR')) install_data('extra-gestures.xml', install_dir: conf_data.get('PLUGIN_XML_DIR')) install_data('fast-switcher.xml', install_dir: conf_data.get('PLUGIN_XML_DIR')) install_data('fisheye.xml', install_dir: conf_data.get('PLUGIN_XML_DIR')) install_data('foreign-toplevel.xml', install_dir: conf_data.get('PLUGIN_XML_DIR')) install_data('grid.xml', install_dir: conf_data.get('PLUGIN_XML_DIR')) install_data('gtk-shell.xml', install_dir: conf_data.get('PLUGIN_XML_DIR')) install_data('idle.xml', install_dir: conf_data.get('PLUGIN_XML_DIR')) install_data('input.xml', install_dir: conf_data.get('PLUGIN_XML_DIR')) install_data('input-device.xml', install_dir: conf_data.get('PLUGIN_XML_DIR')) install_data('input-method-v1.xml', install_dir: conf_data.get('PLUGIN_XML_DIR')) install_data('invert.xml', install_dir: conf_data.get('PLUGIN_XML_DIR')) install_data('ipc.xml', install_dir: conf_data.get('PLUGIN_XML_DIR')) install_data('ipc-rules.xml', install_dir: conf_data.get('PLUGIN_XML_DIR')) install_data('move.xml', install_dir: conf_data.get('PLUGIN_XML_DIR')) install_data('oswitch.xml', install_dir: conf_data.get('PLUGIN_XML_DIR')) install_data('output.xml', install_dir: conf_data.get('PLUGIN_XML_DIR')) install_data('place.xml', install_dir: conf_data.get('PLUGIN_XML_DIR')) install_data('preserve-output.xml', install_dir: conf_data.get('PLUGIN_XML_DIR')) install_data('resize.xml', install_dir: conf_data.get('PLUGIN_XML_DIR')) install_data('scale.xml', install_dir: conf_data.get('PLUGIN_XML_DIR')) install_data('simple-tile.xml', install_dir: conf_data.get('PLUGIN_XML_DIR')) install_data('switcher.xml', install_dir: conf_data.get('PLUGIN_XML_DIR')) install_data('vswipe.xml', install_dir: conf_data.get('PLUGIN_XML_DIR')) install_data('vswitch.xml', install_dir: conf_data.get('PLUGIN_XML_DIR')) install_data('window-rules.xml', install_dir: conf_data.get('PLUGIN_XML_DIR')) install_data('wm-actions.xml', install_dir: conf_data.get('PLUGIN_XML_DIR')) install_data('wobbly.xml', install_dir: conf_data.get('PLUGIN_XML_DIR')) install_data('workarounds.xml', install_dir: conf_data.get('PLUGIN_XML_DIR')) install_data('wrot.xml', install_dir: conf_data.get('PLUGIN_XML_DIR')) install_data('zoom.xml', install_dir: conf_data.get('PLUGIN_XML_DIR')) install_data('scale-title-filter.xml', install_dir: conf_data.get('PLUGIN_XML_DIR')) install_data('shortcuts-inhibit.xml', install_dir: conf_data.get('PLUGIN_XML_DIR')) install_data('wsets.xml', install_dir: conf_data.get('PLUGIN_XML_DIR')) install_data('wayfire-shell.xml', install_dir: conf_data.get('PLUGIN_XML_DIR')) install_data('xdg-activation.xml', install_dir: conf_data.get('PLUGIN_XML_DIR')) wayfire-0.8.1/metadata/move.xml000066400000000000000000000052441457431457600164630ustar00rootroot00000000000000 <_short>Move <_long>A plugin to move windows around by dragging them from any part (not just the title bar). Window Management wayfire-0.8.1/metadata/oswitch.xml000066400000000000000000000021531457431457600171710ustar00rootroot00000000000000 <_short>Output Switcher <_long>A plugin to change the focused output. Desktop wayfire-0.8.1/metadata/output.xml000066400000000000000000000012361457431457600170520ustar00rootroot00000000000000 General wayfire-0.8.1/metadata/place.xml000066400000000000000000000012571457431457600166010ustar00rootroot00000000000000 <_short>Place <_long>A plugin to position newly opened windows. Window Management wayfire-0.8.1/metadata/preserve-output.xml000066400000000000000000000011441457431457600207010ustar00rootroot00000000000000 <_short>Preserve Output <_long>A plugin to restore windows to their original position if outputs are temporarily disconnected. Window Management wayfire-0.8.1/metadata/resize.xml000066400000000000000000000013641457431457600170150ustar00rootroot00000000000000 <_short>Resize <_long>A plugin to resize windows by dragging them from any part (not just the edges). Window Management wayfire-0.8.1/metadata/scale-title-filter.xml000066400000000000000000000025241457431457600212040ustar00rootroot00000000000000 <_short>Scale Title Filter <_long>Filter the views shown by scale by typing. Utility wayfire-0.8.1/metadata/scale.xml000066400000000000000000000077361457431457600166140ustar00rootroot00000000000000 <_short>Scale <_long>Show all views on screen. Accessibility <_short>Behavior <_short>Appearance wayfire-0.8.1/metadata/shortcuts-inhibit.xml000066400000000000000000000016141457431457600211740ustar00rootroot00000000000000 <_short>Keyboard Shortcuts Inhibit Protocol <_long>An implementation of the keyboard-shortcuts-inhibit-v1 protocol. Utility wayfire-0.8.1/metadata/simple-tile.xml000066400000000000000000000072551457431457600177450ustar00rootroot00000000000000 <_short>Simple Tile <_long>A plugin which provides some tiling features, inspired by Sway. The plugin is meant to contain only the basics. Window Management wayfire-0.8.1/metadata/switcher.xml000066400000000000000000000022511457431457600173400ustar00rootroot00000000000000 <_short>Switcher <_long>A plugin to change active window with an animation. Window Management wayfire-0.8.1/metadata/vswipe.xml000066400000000000000000000043401457431457600170260ustar00rootroot00000000000000 <_short>Viewport Swipe <_long>A plugin to swipe workspaces in a grid. Desktop wayfire-0.8.1/metadata/vswitch.xml000066400000000000000000000124121457431457600171770ustar00rootroot00000000000000 <_short>Viewport Switcher <_long>A plugin to switch workspaces in a grid. Desktop wayfire-0.8.1/metadata/wayfire-shell.xml000066400000000000000000000006341457431457600202660ustar00rootroot00000000000000 <_short>Wayfire Shell Protocol <_long>An implementation of the wayfire-shell protocol. Utility wayfire-0.8.1/metadata/window-rules.xml000066400000000000000000000010551457431457600201500ustar00rootroot00000000000000 <_short>Window Rules <_long>A plugin to apply specific commands to windows by using various criteria. Window Management wayfire-0.8.1/metadata/wm-actions.xml000066400000000000000000000027711457431457600176000ustar00rootroot00000000000000 <_short>WM Actions <_long>A plugin which provides various window management functionalities. Window Management wayfire-0.8.1/metadata/wobbly.xml000066400000000000000000000012171457431457600170070ustar00rootroot00000000000000 <_short>Wobbly <_long>A plugin to get wobbly windows. Effects wayfire-0.8.1/metadata/workarounds.xml000066400000000000000000000055741457431457600201010ustar00rootroot00000000000000 <_short>Workarounds <_long>Some hacks that might be required to make things work. Utility wayfire-0.8.1/metadata/wrot.xml000066400000000000000000000032041457431457600165020ustar00rootroot00000000000000 <_short>Window Rotate <_long>A plugin to rotate windows with the mouse. Effects wayfire-0.8.1/metadata/wsets.xml000066400000000000000000000017361457431457600166640ustar00rootroot00000000000000 <_short>Workspace Sets <_long>A plugin which allows manipulation of workspace sets. Desktop wayfire-0.8.1/metadata/xdg-activation.xml000066400000000000000000000003461457431457600204340ustar00rootroot00000000000000 <_short>XDG Activation Protocol <_long>An implementation of the xdg-activation-v1 protocol. Utility wayfire-0.8.1/metadata/zoom.xml000066400000000000000000000022131457431457600164720ustar00rootroot00000000000000 <_short>Zoom <_long>A plugin to zoom in the desktop with the mouse. Accessibility wayfire-0.8.1/plugins/000077500000000000000000000000001457431457600146675ustar00rootroot00000000000000wayfire-0.8.1/plugins/animate/000077500000000000000000000000001457431457600163055ustar00rootroot00000000000000wayfire-0.8.1/plugins/animate/animate.cpp000066400000000000000000000337461457431457600204440ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include #include "animate.hpp" #include "system_fade.hpp" #include "basic_animations.hpp" #include "fire/fire.hpp" #include "unmapped-view-node.hpp" #include "wayfire/plugin.hpp" #include "wayfire/scene-operations.hpp" #include "wayfire/scene.hpp" #include "wayfire/signal-provider.hpp" #include "wayfire/view.hpp" #include void animation_base::init(wayfire_view, int, wf_animation_type) {} bool animation_base::step() { return false; } void animation_base::reverse() {} int animation_base::get_direction() { return 1; } animation_base::~animation_base() {} static constexpr const char *animate_custom_data_fire = "animation-hook-fire"; static constexpr const char *animate_custom_data_zoom = "animation-hook-zoom"; static constexpr const char *animate_custom_data_fade = "animation-hook-fade"; static constexpr const char *animate_custom_data_minimize = "animation-hook-minimize"; static constexpr int HIDDEN = 0; static constexpr int SHOWN = 1; /* Represents an animation running for a specific view * animation_t is which animation to use (i.e fire, zoom, etc). */ struct animation_hook_base : public wf::custom_data_t { virtual void stop_hook(bool) = 0; virtual void reverse(wf_animation_type) = 0; virtual int get_direction() = 0; animation_hook_base() = default; virtual ~animation_hook_base() = default; animation_hook_base(const animation_hook_base &) = default; animation_hook_base(animation_hook_base &&) = default; animation_hook_base& operator =(const animation_hook_base&) = default; animation_hook_base& operator =(animation_hook_base&&) = default; }; template struct animation_hook : public animation_hook_base { static_assert(std::is_base_of::value, "animation_type must be derived from animation_base!"); std::shared_ptr view; wf_animation_type type; std::string name; wf::output_t *current_output = nullptr; std::unique_ptr animation; std::shared_ptr unmapped_contents; void damage_whole_view() { view->damage(); if (unmapped_contents) { wf::scene::damage_node(unmapped_contents, unmapped_contents->get_bounding_box()); } } /* Update animation right before each frame */ wf::effect_hook_t update_animation_hook = [=] () { damage_whole_view(); bool result = animation->step(); damage_whole_view(); if (!result) { stop_hook(false); } }; /** * Switch the output the view is being animated on, and update the lastly * animated output in the global list. */ void set_output(wf::output_t *new_output) { if (current_output) { current_output->render->rem_effect(&update_animation_hook); } if (new_output) { new_output->render->add_effect(&update_animation_hook, wf::OUTPUT_EFFECT_PRE); } current_output = new_output; } wf::signal::connection_t on_set_output = [=] (auto) { set_output(view->get_output()); }; animation_hook(wayfire_view view, int duration, wf_animation_type type, std::string name) { this->type = type; this->view = view->shared_from_this(); this->name = name; animation = std::make_unique(); animation->init(view, duration, type); set_output(view->get_output()); /* Animation is driven by the output render cycle the view is on. * Thus, we need to keep in sync with the current output. */ view->connect(&on_set_output); wf::scene::set_node_enabled(view->get_root_node(), true); if (type == ANIMATION_TYPE_UNMAP) { set_unmapped_contents(); } } void stop_hook(bool detached) override { view->erase_data(name); } // When showing the final unmap animation, we show a ``fake'' node instead of the actual view contents, // because the underlying (sub)surfaces might be destroyed. // // The unmapped contents have to be visible iff the view is in an unmap animation. void set_unmapped_contents() { if (!unmapped_contents) { unmapped_contents = std::make_shared(view); auto parent = dynamic_cast( view->get_surface_root_node()->parent()); if (parent) { wf::scene::add_front( std::dynamic_pointer_cast(parent->shared_from_this()), unmapped_contents); } } } void unset_unmapped_contents() { if (unmapped_contents) { wf::scene::remove_child(unmapped_contents); unmapped_contents.reset(); } } void reverse(wf_animation_type type) override { if (type == ANIMATION_TYPE_UNMAP) { set_unmapped_contents(); } else { unset_unmapped_contents(); } this->type = type; if (animation) { animation->reverse(); } } int get_direction() override { return animation->get_direction(); } ~animation_hook() { /** * Order here is very important. * After doing unref() the view will be potentially destroyed. * Hence, we want to deinitialize everything else before that. */ set_output(nullptr); on_set_output.disconnect(); this->animation.reset(); unset_unmapped_contents(); wf::scene::set_node_enabled(view->get_root_node(), false); } animation_hook(const animation_hook &) = delete; animation_hook(animation_hook &&) = delete; animation_hook& operator =(const animation_hook&) = delete; animation_hook& operator =(animation_hook&&) = delete; }; static void cleanup_views_on_output(wf::output_t *output) { std::vector> all_views; for (auto& view : wf::get_core().get_all_views()) { all_views.push_back(view->shared_from_this()); } for (auto& view : all_views) { auto wo = view->get_output(); if ((wo != output) && output) { continue; } if (view->has_data(animate_custom_data_fire)) { view->get_data( animate_custom_data_fire)->stop_hook(true); } if (view->has_data(animate_custom_data_zoom)) { view->get_data( animate_custom_data_zoom)->stop_hook(true); } if (view->has_data(animate_custom_data_fade)) { view->get_data( animate_custom_data_fade)->stop_hook(true); } if (view->has_data(animate_custom_data_minimize)) { view->get_data( animate_custom_data_minimize)->stop_hook(true); } } } class wayfire_animation : public wf::plugin_interface_t, private wf::per_output_tracker_mixin_t<> { wf::option_wrapper_t open_animation{"animate/open_animation"}; wf::option_wrapper_t close_animation{"animate/close_animation"}; wf::option_wrapper_t default_duration{"animate/duration"}; wf::option_wrapper_t fade_duration{"animate/fade_duration"}; wf::option_wrapper_t zoom_duration{"animate/zoom_duration"}; wf::option_wrapper_t fire_duration{"animate/fire_duration"}; wf::option_wrapper_t startup_duration{"animate/startup_duration"}; wf::view_matcher_t animation_enabled_for{"animate/enabled_for"}; wf::view_matcher_t fade_enabled_for{"animate/fade_enabled_for"}; wf::view_matcher_t zoom_enabled_for{"animate/zoom_enabled_for"}; wf::view_matcher_t fire_enabled_for{"animate/fire_enabled_for"}; public: void init() override { init_output_tracking(); } void handle_new_output(wf::output_t *output) override { output->connect(&on_view_mapped); output->connect(&on_view_pre_unmap); output->connect(&on_render_start); output->connect(&on_minimize_request); } void handle_output_removed(wf::output_t *output) override { cleanup_views_on_output(output); } void fini() override { cleanup_views_on_output(nullptr); } struct view_animation_t { std::string animation_name; int duration; }; view_animation_t get_animation_for_view( wf::option_wrapper_t& anim_type, wayfire_view view) { /* Determine the animation for the given view. * Note that the matcher plugin might not have been loaded, so * we need to have a fallback algorithm */ if (fade_enabled_for.matches(view)) { return {"fade", fade_duration}; } if (zoom_enabled_for.matches(view)) { return {"zoom", zoom_duration}; } if (fire_enabled_for.matches(view)) { return {"fire", fire_duration}; } if (animation_enabled_for.matches(view)) { return {anim_type, default_duration}; } return {"none", 0}; } bool try_reverse(wayfire_view view, wf_animation_type type, std::string name, int visibility) { visibility = !visibility; if (view->has_data(name)) { auto data = view->get_data(name); if (data->get_direction() == visibility) { data->reverse(type); return true; } } return false; } template void set_animation(wayfire_view view, wf_animation_type type, int duration, std::string name) { name = "animation-hook-" + name; if (type == ANIMATION_TYPE_MAP) { if (try_reverse(view, type, name, SHOWN)) { return; } auto animation = get_animation_for_view(open_animation, view); view->store_data( std::make_unique>(view, duration, type, name), name); } else if (type == ANIMATION_TYPE_UNMAP) { if (try_reverse(view, type, name, HIDDEN)) { return; } auto animation = get_animation_for_view(close_animation, view); view->store_data( std::make_unique>(view, duration, type, name), name); } else if (type & MINIMIZE_STATE_ANIMATION) { if (view->has_data(animate_custom_data_minimize)) { view->get_data( animate_custom_data_minimize)->reverse(type); return; } view->store_data( std::make_unique>(view, duration, type, animate_custom_data_minimize), animate_custom_data_minimize); } } /* TODO: enhance - add more animations */ wf::signal::connection_t on_view_mapped = [=] (wf::view_mapped_signal *ev) { auto animation = get_animation_for_view(open_animation, ev->view); if (animation.animation_name == "fade") { set_animation(ev->view, ANIMATION_TYPE_MAP, animation.duration, animation.animation_name); } else if (animation.animation_name == "zoom") { set_animation(ev->view, ANIMATION_TYPE_MAP, animation.duration, animation.animation_name); } else if (animation.animation_name == "fire") { set_animation(ev->view, ANIMATION_TYPE_MAP, animation.duration, animation.animation_name); } }; wf::signal::connection_t on_view_pre_unmap = [=] (wf::view_pre_unmap_signal *ev) { auto animation = get_animation_for_view(close_animation, ev->view); if (animation.animation_name == "fade") { set_animation(ev->view, ANIMATION_TYPE_UNMAP, animation.duration, animation.animation_name); } else if (animation.animation_name == "zoom") { set_animation(ev->view, ANIMATION_TYPE_UNMAP, animation.duration, animation.animation_name); } else if (animation.animation_name == "fire") { set_animation(ev->view, ANIMATION_TYPE_UNMAP, animation.duration, animation.animation_name); } }; wf::signal::connection_t on_minimize_request = [=] (wf::view_minimize_request_signal *ev) { if (ev->state) { set_animation(ev->view, ANIMATION_TYPE_MINIMIZE, default_duration, "minimize"); } else { set_animation(ev->view, ANIMATION_TYPE_RESTORE, default_duration, "minimize"); } // ev->carried_out should remain false, so that core also does the automatic minimize/restore and // refocus }; wf::signal::connection_t on_render_start = [=] (wf::output_start_rendering_signal *ev) { new wf_system_fade(ev->output, startup_duration); }; }; DECLARE_WAYFIRE_PLUGIN(wayfire_animation); wayfire-0.8.1/plugins/animate/animate.hpp000066400000000000000000000022461457431457600204400ustar00rootroot00000000000000#ifndef ANIMATE_H_ #define ANIMATE_H_ #include #include #include #define HIDING_ANIMATION (1 << 0) #define SHOWING_ANIMATION (1 << 1) #define MAP_STATE_ANIMATION (1 << 2) #define MINIMIZE_STATE_ANIMATION (1 << 3) enum wf_animation_type { ANIMATION_TYPE_MAP = SHOWING_ANIMATION | MAP_STATE_ANIMATION, ANIMATION_TYPE_UNMAP = HIDING_ANIMATION | MAP_STATE_ANIMATION, ANIMATION_TYPE_MINIMIZE = HIDING_ANIMATION | MINIMIZE_STATE_ANIMATION, ANIMATION_TYPE_RESTORE = SHOWING_ANIMATION | MINIMIZE_STATE_ANIMATION, }; class animation_base { public: virtual void init(wayfire_view view, int duration, wf_animation_type type); virtual bool step(); /* return true if continue, false otherwise */ virtual void reverse(); /* reverse the animation */ virtual int get_direction(); animation_base() = default; virtual ~animation_base(); animation_base(const animation_base &) = default; animation_base(animation_base &&) = default; animation_base& operator =(const animation_base&) = default; animation_base& operator =(animation_base&&) = default; }; #endif wayfire-0.8.1/plugins/animate/basic_animations.hpp000066400000000000000000000113221457431457600223200ustar00rootroot00000000000000#include "animate.hpp" #include "wayfire/toplevel-view.hpp" #include #include #include #include #include class fade_animation : public animation_base { wayfire_view view; float start = 0, end = 1; wf::animation::simple_animation_t progression; std::string name; public: void init(wayfire_view view, int dur, wf_animation_type type) override { this->view = view; this->progression = wf::animation::simple_animation_t(wf::create_option(dur)); this->progression.animate(start, end); if (type & HIDING_ANIMATION) { this->progression.flip(); } name = "animation-fade-" + std::to_string(type); auto tr = std::make_shared(view); view->get_transformed_node()->add_transformer( tr, wf::TRANSFORMER_HIGHLEVEL, name); } bool step() override { auto transform = view->get_transformed_node() ->get_transformer(name); transform->alpha = this->progression; return progression.running(); } void reverse() override { this->progression.reverse(); } int get_direction() override { return this->progression.get_direction(); } ~fade_animation() { view->get_transformed_node()->rem_transformer(name); } }; using namespace wf::animation; class zoom_animation_t : public duration_t { public: using duration_t::duration_t; timed_transition_t alpha{*this}; timed_transition_t zoom{*this}; timed_transition_t offset_x{*this}; timed_transition_t offset_y{*this}; }; class zoom_animation : public animation_base { wayfire_view view; zoom_animation_t progression; std::string name; public: void init(wayfire_view view, int dur, wf_animation_type type) override { this->view = view; this->progression = zoom_animation_t(wf::create_option(dur)); this->progression.alpha = wf::animation::timed_transition_t( this->progression, 0, 1); this->progression.zoom = wf::animation::timed_transition_t( this->progression, 1. / 3, 1); this->progression.offset_x = wf::animation::timed_transition_t( this->progression, 0, 0); this->progression.offset_y = wf::animation::timed_transition_t( this->progression, 0, 0); this->progression.start(); if (type & MINIMIZE_STATE_ANIMATION) { auto toplevel = wf::toplevel_cast(view); wf::dassert(toplevel != nullptr, "We cannot minimize non-toplevel views!"); auto hint = toplevel->get_minimize_hint(); if ((hint.width > 0) && (hint.height > 0)) { int hint_cx = hint.x + hint.width / 2; int hint_cy = hint.y + hint.height / 2; auto bbox = toplevel->get_geometry(); int view_cx = bbox.x + bbox.width / 2; int view_cy = bbox.y + bbox.height / 2; progression.offset_x.set(1.0 * hint_cx - view_cx, 0); progression.offset_y.set(1.0 * hint_cy - view_cy, 0); if ((bbox.width > 0) && (bbox.height > 0)) { double scale_x = 1.0 * hint.width / bbox.width; double scale_y = 1.0 * hint.height / bbox.height; progression.zoom.set(std::min(scale_x, scale_y), 1); } } } if (type & HIDING_ANIMATION) { progression.alpha.flip(); progression.zoom.flip(); progression.offset_x.flip(); progression.offset_y.flip(); } name = "animation-zoom-" + std::to_string(type); auto tr = std::make_shared(view); view->get_transformed_node()->add_transformer( tr, wf::TRANSFORMER_HIGHLEVEL, name); } bool step() override { auto our_transform = view->get_transformed_node() ->get_transformer(name); float c = this->progression.zoom; our_transform->alpha = this->progression.alpha; our_transform->scale_x = c; our_transform->scale_y = c; our_transform->translation_x = this->progression.offset_x; our_transform->translation_y = this->progression.offset_y; return this->progression.running(); } void reverse() override { this->progression.reverse(); } ~zoom_animation() { view->get_transformed_node()->rem_transformer(name); } }; wayfire-0.8.1/plugins/animate/fire/000077500000000000000000000000001457431457600172325ustar00rootroot00000000000000wayfire-0.8.1/plugins/animate/fire/fire.cpp000066400000000000000000000200501457431457600206600ustar00rootroot00000000000000#include "fire.hpp" #include "particle.hpp" #include "wayfire/debug.hpp" #include "wayfire/geometry.hpp" #include "wayfire/opengl.hpp" #include "wayfire/scene-render.hpp" #include "wayfire/scene.hpp" #include "wayfire/view-transform.hpp" #include #include #include #include #include static wf::option_wrapper_t fire_particles{"animate/fire_particles"}; static wf::option_wrapper_t fire_particle_size{"animate/fire_particle_size"}; static wf::option_wrapper_t random_fire_color{"animate/random_fire_color"}; static wf::option_wrapper_t fire_color{"animate/fire_color"}; // generate a random float between s and e static float random(float s, float e) { double r = 1.0 * (std::rand() % RAND_MAX) / (RAND_MAX - 1); return (s * r + (1 - r) * e); } static int particle_count_for_width(int width) { int particles = fire_particles; return particles * std::min(width / 400.0, 3.5); } class fire_node_t : public wf::scene::floating_inner_node_t { public: std::unique_ptr ps; fire_node_t() : floating_inner_node_t(false) { ps = std::make_unique(1); ps->set_initer( [=] (Particle& p) { init_particle_with_node(p, get_children_bounding_box(), progress_line); }); } static void init_particle_with_node(Particle& p, wf::geometry_t bounding_box, double progress) { p.life = 1; p.fade = random(0.1, 0.6); wf::color_t color_setting = fire_color; float r; float g; float b; if (!random_fire_color) { // The calculation here makes the variation lower at darker values float randomize_amount_r = (color_setting.r * 0.857) / 2; float randomize_amount_g = (color_setting.g * 0.857) / 2; float randomize_amount_b = (color_setting.b * 0.857) / 2; r = random(color_setting.r - randomize_amount_r, std::min(color_setting.r + randomize_amount_r, 1.0)); g = random(color_setting.g - randomize_amount_g, std::min(color_setting.g + randomize_amount_g, 1.0)); b = random(color_setting.b - randomize_amount_b, std::min(color_setting.b + randomize_amount_b, 1.0)); } else { r = random(0, 1); g = random(0, 1); b = random(0, 1); r = 2 * pow(r, 16); g = 2 * pow(g, 16); b = 2 * pow(b, 16); } p.color = {r, g, b, 1}; const double cur_pos = bounding_box.height * progress; p.pos = {random(0, bounding_box.width), random(cur_pos - 10, cur_pos + 10)}; p.start_pos = p.pos; p.speed = {random(-10, 10), random(-25, 5)}; p.g = {-1, -3}; double size = fire_particle_size; p.base_radius = p.radius = random(size * 0.8, size * 1.2); } std::string stringify() const override { return "fire"; } void gen_render_instances( std::vector& instances, wf::scene::damage_callback push_damage, wf::output_t *output = nullptr) override; wf::geometry_t get_bounding_box() override { static constexpr int left_border = 200; static constexpr int right_border = 200; static constexpr int top_border = 200; static constexpr int bottom_border = 200; auto view = get_children_bounding_box(); view.x -= left_border; view.y -= top_border; view.width += left_border + right_border; view.height += top_border + bottom_border; return view; } float progress_line; void set_progress_line(float line) { progress_line = line; } }; class fire_render_instance_t : public wf::scene::render_instance_t { fire_node_t *self; public: fire_render_instance_t(fire_node_t *self, wf::scene::damage_callback push_damage, wf::output_t *output) { this->self = self; auto child_damage = [=] (const wf::region_t& damage) { push_damage(damage | self->get_bounding_box()); }; for (auto& ch : self->get_children()) { if (ch->is_enabled()) { ch->gen_render_instances(children, child_damage, output); } } } void schedule_instructions( std::vector& instructions, const wf::render_target_t& target, wf::region_t& damage) override { if (children.empty()) { return; } // Step 2: we render ourselves auto bbox = self->get_bounding_box(); instructions.push_back(wf::scene::render_instruction_t{ .instance = this, .target = target, .damage = damage & bbox, }); // Step 1: render the view below normally, however, make sure it doesn't // render above the progress line bbox = self->get_children_bounding_box(); bbox.height *= self->progress_line; auto child_damage = damage & bbox; for (auto& ch : children) { ch->schedule_instructions(instructions, target, child_damage); } } void render(const wf::render_target_t& target_fb, const wf::region_t& region) override { OpenGL::render_begin(target_fb); auto bbox = self->get_children_bounding_box(); auto translate = glm::translate(glm::mat4(1.0), {bbox.x, bbox.y, 0}); for (auto& box : region) { target_fb.logic_scissor(wlr_box_from_pixman_box(box)); self->ps->render(target_fb.get_orthographic_projection() * translate); } OpenGL::render_end(); } void presentation_feedback(wf::output_t *output) override { for (auto& ch : children) { ch->presentation_feedback(output); } } void compute_visibility(wf::output_t *output, wf::region_t& visible) override { for (auto& ch : this->children) { ch->compute_visibility(output, visible); } } private: std::vector children; }; void fire_node_t::gen_render_instances( std::vector& instances, wf::scene::damage_callback push_damage, wf::output_t *output) { instances.push_back(std::make_unique( this, push_damage, output)); } static float fire_duration_mod_for_height(int height) { return std::min(height / 400.0, 3.0); } void FireAnimation::init(wayfire_view view, int dur, wf_animation_type type) { this->view = view; auto bbox = view->get_transformed_node()->get_bounding_box(); int msec = dur * fire_duration_mod_for_height(bbox.height); this->progression = wf::animation::simple_animation_t(wf::create_option( msec), wf::animation::smoothing::linear); this->progression.animate(0, 1); if (type & HIDING_ANIMATION) { this->progression.flip(); } name = "animation-fire-" + std::to_string(type); auto tr = std::make_shared(); view->get_transformed_node()->add_transformer( tr, wf::TRANSFORMER_HIGHLEVEL + 1, name); } bool FireAnimation::step() { auto transformer = view->get_transformed_node() ->get_transformer(name); transformer->set_progress_line(this->progression); if (this->progression.running()) { transformer->ps->spawn(transformer->ps->size() / 10); } transformer->ps->update(); transformer->ps->resize(particle_count_for_width( transformer->get_children_bounding_box().width)); return this->progression.running() || transformer->ps->statistic(); } void FireAnimation::reverse() { this->progression.reverse(); } FireAnimation::~FireAnimation() { view->get_transformed_node()->rem_transformer(name); } wayfire-0.8.1/plugins/animate/fire/fire.hpp000066400000000000000000000013201457431457600206640ustar00rootroot00000000000000#ifndef FIRE_ANIMATION_HPP #define FIRE_ANIMATION_HPP #include #include #include #include "../animate.hpp" class FireTransformer; struct ParticleSystem; class FireAnimation : public animation_base { std::string name; // the name of the transformer in the view's table wayfire_view view; wf::animation::simple_animation_t progression; public: ~FireAnimation(); void init(wayfire_view view, int duration, wf_animation_type type) override; bool step() override; /* return true if continue, false otherwise */ void reverse() override; /* reverse the animation */ }; #endif /* end of include guard: FIRE_ANIMATION_HPP */ wayfire-0.8.1/plugins/animate/fire/particle.cpp000066400000000000000000000121161457431457600215420ustar00rootroot00000000000000#include "particle.hpp" #include "shaders.hpp" #include #include void Particle::update(float time) { if (life <= 0) // ignore { return; } const float slowdown = 0.8; pos += speed * 0.2f * slowdown; speed += g * 0.3f * slowdown; if (life != 0) { color.a /= life; } life -= fade * 0.3 * slowdown; radius = base_radius * std::pow(life, 0.5); color.a *= life; if (start_pos.x < pos.x) { g.x = -1; } else { g.x = 1; } if (life <= 0) { /* move outside */ pos = {-10000, -10000}; } } ParticleSystem::ParticleSystem(int particles) { resize(particles); last_update_msec = wf::get_current_time(); create_program(); particles_alive.store(0); } void ParticleSystem::set_initer(ParticleIniter init) { this->pinit_func = init; } ParticleSystem::~ParticleSystem() { OpenGL::render_begin(); program.free_resources(); OpenGL::render_end(); } int ParticleSystem::spawn(int num) { // TODO: multithread this int spawned = 0; for (size_t i = 0; i < ps.size() && spawned < num; i++) { if (ps[i].life <= 0) { pinit_func(ps[i]); ++spawned; ++particles_alive; } } return spawned; } void ParticleSystem::resize(int num) { if (num == (int)ps.size()) { return; } // TODO: multithread this for (int i = num; i < (int)ps.size(); i++) { if (ps[i].life >= 0) { --particles_alive; } } ps.resize(num); color.resize(color_per_particle * num); dark_color.resize(color_per_particle * num); radius.resize(radius_per_particle * num); center.resize(center_per_particle * num); } int ParticleSystem::size() { return ps.size(); } void ParticleSystem::update_worker(float time, int start, int end) { end = std::min(end, (int)ps.size()); for (int i = start; i < end; ++i) { if (ps[i].life <= 0) { continue; } // printf("%d\n", i); ps[i].update(time); if (ps[i].life <= 0) { --particles_alive; } for (int j = 0; j < 4; j++) // maybe use memcpy? { color[4 * i + j] = ps[i].color[j]; dark_color[4 * i + j] = ps[i].color[j] * 0.5; } // printf("center %d gets %f", 2 * i, ps[i].pos[0]); center[2 * i] = ps[i].pos[0]; center[2 * i + 1] = ps[i].pos[1]; radius[i] = ps[i].radius; } } void ParticleSystem::exec_worker_threads(std::function spawn_worker) { // return spawn_worker(0, ps.size()); const int num_threads = std::thread::hardware_concurrency(); const int worker_load = (ps.size() + num_threads - 1) / num_threads; std::thread workers[num_threads]; for (int i = 0; i < num_threads; i++) { int thread_start = i * worker_load; int thread_end = (i + 1) * worker_load; thread_end = std::min(thread_end, (int)ps.size()); workers[i] = std::thread([=] () { spawn_worker(thread_start, thread_end); }); } for (auto& w : workers) { w.join(); } } void ParticleSystem::update() { // FIXME: don't hardcode 60FPS float time = (wf::get_current_time() - last_update_msec) / 16.0; last_update_msec = wf::get_current_time(); exec_worker_threads([=] (int start, int end) { update_worker(time, start, end); }); } int ParticleSystem::statistic() { return particles_alive; } void ParticleSystem::create_program() { /* Just load the proper context, viewport doesn't matter */ OpenGL::render_begin(); program.set_simple(OpenGL::compile_program(particle_vert_source, particle_frag_source)); OpenGL::render_end(); } void ParticleSystem::render(glm::mat4 matrix) { program.use(wf::TEXTURE_TYPE_RGBA); static float vertex_data[] = { -1, -1, 1, -1, 1, 1, -1, 1 }; program.attrib_pointer("position", 2, 0, vertex_data); program.attrib_divisor("position", 0); program.attrib_pointer("radius", 1, 0, radius.data()); program.attrib_divisor("radius", 1); program.attrib_pointer("center", 2, 0, center.data()); program.attrib_divisor("center", 1); // matrix program.uniformMatrix4f("matrix", matrix); /* Darken the background */ program.attrib_pointer("color", 4, 0, dark_color.data()); program.attrib_divisor("color", 1); GL_CALL(glEnable(GL_BLEND)); GL_CALL(glBlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_ALPHA)); program.uniform1f("smoothing", 0.7); // TODO: optimize shaders for this case GL_CALL(glDrawArraysInstanced(GL_TRIANGLE_FAN, 0, 4, ps.size())); // particle color program.attrib_pointer("color", 4, 0, color.data()); GL_CALL(glBlendFunc(GL_SRC_ALPHA, GL_ONE)); program.uniform1f("smoothing", 0.5); GL_CALL(glDrawArraysInstanced(GL_TRIANGLE_FAN, 0, 4, ps.size())); GL_CALL(glDisable(GL_BLEND)); GL_CALL(glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA)); program.deactivate(); } wayfire-0.8.1/plugins/animate/fire/particle.hpp000066400000000000000000000046201457431457600215500ustar00rootroot00000000000000#ifndef ANIMATION_FIRE_PARTICLE_HPP #define ANIMATION_FIRE_PARTICLE_HPP #include #include #include #include struct Particle { float life = -1; float fade; float radius, base_radius; glm::vec2 pos{0.0, 0.0}, speed{0.0, 0.0}, g{0.0, 0.0}; glm::vec2 start_pos; glm::vec4 color{1.0, 1.0, 1.0, 1.0}; /* update the given particle * time is the percentage of the frame which has elapsed * must be thread-safe */ void update(float time); }; /* a function to initialize a particle */ using ParticleIniter = std::function; class ParticleSystem { public: /* the user of this class has to set up a proper GL context * before creating the ParticleSystem */ ParticleSystem(int num_part); ~ParticleSystem(); void set_initer(ParticleIniter init); ParticleSystem(const ParticleSystem &) = delete; ParticleSystem(ParticleSystem &&) = delete; ParticleSystem& operator =(const ParticleSystem&) = delete; ParticleSystem& operator =(ParticleSystem&&) = delete; /* spawn at most num new particles. * returns the number of actually spawned particles */ int spawn(int num); /* change the maximal number of particles * Warning: This might kill a lot of particles */ void resize(int num); // return the maximal number of particles int size(); /* update all particles */ void update(); // number of particles alive int statistic(); /* render particles, each will be multiplied by matrix * The user of this class has to set up the same GL context that was * used during the creation of the particle system */ void render(glm::mat4 matrix); private: ParticleSystem() = delete; ParticleIniter pinit_func = [] (auto) {}; uint32_t last_update_msec; std::atomic particles_alive; std::vector ps; static constexpr int color_per_particle = 4; std::vector color, dark_color; static constexpr int radius_per_particle = 1; std::vector radius; static constexpr int center_per_particle = 2; std::vector center; OpenGL::program_t program; void exec_worker_threads(std::function spawn_worker); void update_worker(float time, int start, int end); void create_program(); }; #endif /* end of include guard: ANIMATION_FIRE_PARTICLE_HPP */ wayfire-0.8.1/plugins/animate/fire/shaders.hpp000066400000000000000000000020471457431457600213770ustar00rootroot00000000000000#ifndef PARTICLE_ANIMATION_SHADER #define PARTICLE_ANIMATION_SHADER static const char *particle_vert_source = R"( #version 100 attribute mediump float radius; attribute mediump vec2 position; attribute mediump vec2 center; attribute mediump vec4 color; uniform mat4 matrix; varying mediump vec2 uv; varying mediump vec4 out_color; varying mediump float R; void main() { uv = position * radius; gl_Position = matrix * vec4(center.x + uv.x * 0.75, center.y + uv.y, 0.0, 1.0); R = radius; out_color = color; } )"; static const char *particle_frag_source = R"( #version 100 varying mediump vec2 uv; varying mediump vec4 out_color; varying mediump float R; uniform mediump float smoothing; void main() { mediump float len = length(uv); if (len >= R) { gl_FragColor = vec4(0.0, 0.0, 0.0, 0.0); } else { mediump float factor = 1.0 - len / R; factor = pow(factor, smoothing); gl_FragColor = factor * out_color; } } )"; #endif /* end of include guard: PARTICLE_ANIMATION_SHADER */ wayfire-0.8.1/plugins/animate/meson.build000066400000000000000000000006721457431457600204540ustar00rootroot00000000000000animiate = shared_module('animate', ['animate.cpp', 'fire/particle.cpp', 'fire/fire.cpp'], include_directories: [wayfire_api_inc, wayfire_conf_inc], dependencies: [wlroots, pixman, wfconfig], install: true, install_dir: join_paths(get_option('libdir'), 'wayfire')) wayfire-0.8.1/plugins/animate/shaders/000077500000000000000000000000001457431457600177365ustar00rootroot00000000000000wayfire-0.8.1/plugins/animate/shaders/frag.glsl000066400000000000000000000006541457431457600215450ustar00rootroot00000000000000#version 310 es in mediump vec4 out_color; in mediump vec2 pos; out mediump vec4 fragColor; layout(location = 4) uniform highp float radii; void main() { mediump float dist_center = sqrt(pos.x * pos.x + pos.y * pos.y); mediump float factor = (radii - dist_center) / radii; if (factor < 0.0) factor = 0.0; mediump float factor2 = factor * factor; fragColor = vec4(out_color.xyz, out_color.w * factor2); } wayfire-0.8.1/plugins/animate/shaders/vertex.glsl000066400000000000000000000006071457431457600221410ustar00rootroot00000000000000#version 310 es layout(location = 0) in mediump vec2 position; layout(location = 1) in mediump vec2 center; layout(location = 2) in mediump vec4 color; layout(location = 3) uniform mediump vec2 global_offset; out mediump vec4 out_color; out mediump vec2 pos; void main() { gl_Position = vec4 (position + center + global_offset, 0.0, 1.0); out_color = color; pos = position; } wayfire-0.8.1/plugins/animate/system_fade.hpp000066400000000000000000000030501457431457600213170ustar00rootroot00000000000000#ifndef SYSTEM_FADE_HPP #define SYSTEM_FADE_HPP #include #include #include #include #include "animate.hpp" /* animates wake from suspend/startup by fading in the whole output */ class wf_system_fade { wf::animation::simple_animation_t progression; wf::output_t *output; wf::effect_hook_t damage_hook, render_hook; public: wf_system_fade(wf::output_t *out, int dur) : progression(wf::create_option(dur)), output(out) { damage_hook = [=] () { output->render->damage_whole(); }; render_hook = [=] () { render(); }; output->render->add_effect(&damage_hook, wf::OUTPUT_EFFECT_PRE); output->render->add_effect(&render_hook, wf::OUTPUT_EFFECT_OVERLAY); output->render->set_redraw_always(); this->progression.animate(1, 0); } void render() { wf::color_t color{0, 0, 0, this->progression}; auto fb = output->render->get_target_framebuffer(); auto geometry = output->get_relative_geometry(); OpenGL::render_begin(fb); OpenGL::render_rectangle(geometry, color, fb.get_orthographic_projection()); OpenGL::render_end(); if (!progression.running()) { finish(); } } void finish() { output->render->rem_effect(&damage_hook); output->render->rem_effect(&render_hook); output->render->set_redraw_always(false); delete this; } }; #endif wayfire-0.8.1/plugins/animate/unmapped-view-node.hpp000066400000000000000000000031321457431457600225210ustar00rootroot00000000000000#pragma once #include "wayfire/geometry.hpp" #include "wayfire/opengl.hpp" #include "wayfire/region.hpp" #include "wayfire/scene-render.hpp" #include "wayfire/scene.hpp" #include namespace wf { class unmapped_view_snapshot_node : public wf::scene::node_t { wf::render_target_t snapshot; wf::geometry_t bbox; public: unmapped_view_snapshot_node(wayfire_view view) : node_t(false) { view->take_snapshot(snapshot); bbox = view->get_surface_root_node()->get_bounding_box(); } ~unmapped_view_snapshot_node() { OpenGL::render_begin(); snapshot.release(); OpenGL::render_end(); } wf::geometry_t get_bounding_box() override { return bbox; } void gen_render_instances(std::vector& instances, scene::damage_callback push_damage, wf::output_t *shown_on) override { instances.push_back(std::make_unique(this, push_damage, shown_on)); } private: class rinstance_t : public wf::scene::simple_render_instance_t { public: using simple_render_instance_t::simple_render_instance_t; void render(const wf::render_target_t& target, const wf::region_t& region) { OpenGL::render_begin(target); for (auto& box : region) { target.logic_scissor(wlr_box_from_pixman_box(box)); OpenGL::render_texture(self->snapshot.tex, target, self->get_bounding_box()); } OpenGL::render_end(); } }; }; } wayfire-0.8.1/plugins/blur/000077500000000000000000000000001457431457600156335ustar00rootroot00000000000000wayfire-0.8.1/plugins/blur/blur-base.cpp000066400000000000000000000240621457431457600202170ustar00rootroot00000000000000#include "blur.hpp" #include "wayfire/core.hpp" #include "wayfire/geometry.hpp" #include "wayfire/region.hpp" #include "wayfire/scene-render.hpp" #include #include #include #include static const char *blur_blend_vertex_shader = R"( #version 100 attribute mediump vec2 position; attribute mediump vec2 uv_in; varying mediump vec2 uvpos[2]; uniform mat4 mvp; uniform mat4 background_uv_matrix; void main() { gl_Position = mvp * vec4(position, 0.0, 1.0); uvpos[0] = uv_in; uvpos[1] = vec4(background_uv_matrix * vec4(uv_in - 0.5, 0.0, 1.0)).xy + 0.5; })"; static const char *blur_blend_fragment_shader = R"( #version 100 @builtin_ext@ precision mediump float; @builtin@ uniform float sat; uniform sampler2D bg_texture; varying mediump vec2 uvpos[2]; vec3 saturation(vec3 rgb, float adjustment) { // Algorithm from Chapter 16 of OpenGL Shading Language const vec3 w = vec3(0.2125, 0.7154, 0.0721); vec3 intensity = vec3(dot(rgb, w)); return mix(intensity, rgb, adjustment); } void main() { vec4 bp = texture2D(bg_texture, uvpos[1]); bp = vec4(saturation(bp.rgb, sat), bp.a); vec4 wp = get_pixel(uvpos[0]); vec4 c = clamp(4.0 * wp.a, 0.0, 1.0) * bp; gl_FragColor = wp + (1.0 - wp.a) * c; })"; wf_blur_base::wf_blur_base(std::string name) { this->algorithm_name = name; this->saturation_opt.load_option("blur/saturation"); this->offset_opt.load_option("blur/" + algorithm_name + "_offset"); this->degrade_opt.load_option("blur/" + algorithm_name + "_degrade"); this->iterations_opt.load_option("blur/" + algorithm_name + "_iterations"); this->options_changed = [=] () { wf::scene::damage_node(wf::get_core().scene(), wf::get_core().scene()->get_bounding_box()); }; this->saturation_opt.set_callback(options_changed); this->offset_opt.set_callback(options_changed); this->degrade_opt.set_callback(options_changed); this->iterations_opt.set_callback(options_changed); OpenGL::render_begin(); blend_program.compile(blur_blend_vertex_shader, blur_blend_fragment_shader); OpenGL::render_end(); } wf_blur_base::~wf_blur_base() { OpenGL::render_begin(); fb[0].release(); fb[1].release(); program[0].free_resources(); program[1].free_resources(); blend_program.free_resources(); OpenGL::render_end(); } int wf_blur_base::calculate_blur_radius() { return offset_opt * degrade_opt * std::max(1, (int)iterations_opt); } void wf_blur_base::render_iteration(wf::region_t blur_region, wf::framebuffer_t& in, wf::framebuffer_t& out, int width, int height) { /* Special case for small regions where we can't really blur, because we * simply have too few pixels */ width = std::max(width, 1); height = std::max(height, 1); out.allocate(width, height); out.bind(); GL_CALL(glBindTexture(GL_TEXTURE_2D, in.tex)); for (auto& b : blur_region) { out.scissor(wlr_box_from_pixman_box(b)); GL_CALL(glDrawArrays(GL_TRIANGLE_FAN, 0, 4)); } } /** @return Smallest integer >= x which is divisible by mod */ static int round_up(int x, int mod) { return mod * int((x + mod - 1) / mod); } /** * Calculate the smallest box which contains @box and whose x, y, width, height * are divisible by @degrade, and clamp that box to @bounds. */ static wf::geometry_t sanitize(wf::geometry_t box, int degrade, wf::geometry_t bounds) { wf::geometry_t out_box; out_box.x = degrade * int(box.x / degrade); out_box.y = degrade * int(box.y / degrade); out_box.width = round_up(box.width, degrade); out_box.height = round_up(box.height, degrade); if (out_box.x + out_box.width < box.x + box.width) { out_box.width += degrade; } if (out_box.y + out_box.height < box.y + box.height) { out_box.height += degrade; } return wf::clamp(out_box, bounds); } wlr_box wf_blur_base::copy_region(wf::framebuffer_t& result, const wf::render_target_t& source, const wf::region_t& region) { auto subbox = source.framebuffer_box_from_geometry_box( wlr_box_from_pixman_box(region.get_extents())); auto source_box = source.framebuffer_box_from_geometry_box(source.geometry); // Make sure that the box is aligned properly for degrading, otherwise, // we get a flickering subbox = sanitize(subbox, degrade_opt, source_box); int degraded_width = subbox.width / degrade_opt; int degraded_height = subbox.height / degrade_opt; OpenGL::render_begin(source); result.allocate(degraded_width, degraded_height); GL_CALL(glBindFramebuffer(GL_READ_FRAMEBUFFER, source.fb)); GL_CALL(glBindFramebuffer(GL_DRAW_FRAMEBUFFER, result.fb)); GL_CALL(glBlitFramebuffer( subbox.x, source.viewport_height - subbox.y - subbox.height, subbox.x + subbox.width, source.viewport_height - subbox.y, 0, 0, degraded_width, degraded_height, GL_COLOR_BUFFER_BIT, GL_LINEAR)); OpenGL::render_end(); return subbox; } void wf_blur_base::prepare_blur(const wf::render_target_t& target_fb, const wf::region_t& damage) { if (damage.empty()) { return; } int degrade = degrade_opt; auto damage_box = copy_region(fb[0], target_fb, damage); /* As an optimization, we create a region that blur can use * to perform minimal rendering required to blur. We start * by translating the input damage region */ wf::region_t blur_damage; for (auto b : damage) { blur_damage |= target_fb.framebuffer_box_from_geometry_box( wlr_box_from_pixman_box(b)); } /* Scale and translate the region */ blur_damage += -wf::point_t{damage_box.x, damage_box.y}; blur_damage *= 1.0 / degrade; int r = blur_fb0(blur_damage, fb[0].viewport_width, fb[0].viewport_height); /* Make sure the result is always fb[0], because that's what is used in render() * */ if (r != 0) { std::swap(fb[0], fb[1]); } prepared_geometry = damage_box; } static wf::pointf_t get_center(wf::geometry_t g) { return {g.x + g.width / 2.0, g.y + g.height / 2.0}; } void wf_blur_base::render(wf::texture_t src_tex, wlr_box src_box, const wf::region_t& damage, const wf::render_target_t& background_source_fb, const wf::render_target_t& target_fb) { OpenGL::render_begin(target_fb); blend_program.use(src_tex.type); /* Use shader and enable vertex and texcoord data */ static const float vertex_data_uv[] = { 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f, }; const float vertex_data_pos[] = { 1.0f * src_box.x, 1.0f * src_box.y + src_box.height, 1.0f * src_box.x + src_box.width, 1.0f * src_box.y + src_box.height, 1.0f * src_box.x + src_box.width, 1.0f * src_box.y, 1.0f * src_box.x, 1.0f * src_box.y, }; blend_program.attrib_pointer("position", 2, 0, vertex_data_pos); blend_program.attrib_pointer("uv_in", 2, 0, vertex_data_uv); // The blurred background is contained in a framebuffer with dimensions equal to the projected damage. // We need to calculate a mapping between the uv coordinates of the view (which may be bigger than the // damage) and the uv coordinates used for sampling the blurred background. // How it works: // 1. translate UV coordinates to (-0.5, -0.5) ~ (0.5, 0.5) range // 2. apply inverse framebuffer transform (needed because on rotated outputs, the framebuffer box includes // rotation). // 3. Scale to match the view size // 4. Translate to match the view auto view_box = background_source_fb.framebuffer_box_from_geometry_box(src_box); // Projected view auto blurred_box = prepared_geometry; // prepared_geometry is the projected damage bounding box glm::mat4 fb_fix = target_fb.transform; const auto scale_x = 1.0 * view_box.width / blurred_box.width; const auto scale_y = 1.0 * view_box.height / blurred_box.height; glm::mat4 scale = glm::scale(glm::mat4(1.0), glm::vec3{scale_x, scale_y, 1.0}); const wf::pointf_t center_view = get_center(view_box); const wf::pointf_t center_prepared = get_center(blurred_box); const auto translate_x = 1.0 * (center_view.x - center_prepared.x) / view_box.width; const auto translate_y = -1.0 * (center_view.y - center_prepared.y) / view_box.height; glm::mat4 fix_center = glm::translate(glm::mat4(1.0), glm::vec3{translate_x, translate_y, 0.0}); glm::mat4 composite = scale * fix_center * fb_fix; blend_program.uniformMatrix4f("background_uv_matrix", composite); /* Blend blurred background with window texture src_tex */ blend_program.uniformMatrix4f("mvp", target_fb.get_orthographic_projection()); /* XXX: core should give us the number of texture units used */ blend_program.uniform1i("bg_texture", 1); blend_program.uniform1f("sat", saturation_opt); blend_program.set_active_texture(src_tex); GL_CALL(glActiveTexture(GL_TEXTURE0 + 1)); GL_CALL(glBindTexture(GL_TEXTURE_2D, fb[0].tex)); /* Render it to target_fb */ target_fb.bind(); for (const auto& box : damage) { target_fb.logic_scissor(wlr_box_from_pixman_box(box)); GL_CALL(glDrawArrays(GL_TRIANGLE_FAN, 0, 4)); } /* * Disable stuff * GL_CALL(glActiveTexture(GL_TEXTURE0 + 1)); */ GL_CALL(glBindTexture(GL_TEXTURE_2D, 0)); GL_CALL(glActiveTexture(GL_TEXTURE0)); GL_CALL(glBindTexture(GL_TEXTURE_2D, 0)); blend_program.deactivate(); OpenGL::render_end(); } std::unique_ptr create_blur_from_name(std::string algorithm_name) { if (algorithm_name == "box") { return create_box_blur(); } if (algorithm_name == "bokeh") { return create_bokeh_blur(); } if (algorithm_name == "kawase") { return create_kawase_blur(); } if (algorithm_name == "gaussian") { return create_gaussian_blur(); } LOGE("Unrecognized blur algorithm %s. Using default kawase blur.", algorithm_name.c_str()); return create_kawase_blur(); } wayfire-0.8.1/plugins/blur/blur.cpp000066400000000000000000000266471457431457600173220ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include #include #include #include #include #include "blur.hpp" #include "wayfire/core.hpp" #include "wayfire/debug.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/signal-provider.hpp" using blur_algorithm_provider = std::function()>; static int calculate_damage_padding(const wf::render_target_t& target, int blur_radius) { float scale = target.scale; if (target.subbuffer) { const float subbox_scale_x = 1.0 * target.subbuffer->width / target.geometry.width; const float subbox_scale_y = 1.0 * target.subbuffer->height / target.geometry.height; scale *= std::max(subbox_scale_x, subbox_scale_y); } return std::ceil(blur_radius / scale); } namespace wf { namespace scene { class blur_node_t : public floating_inner_node_t { public: blur_algorithm_provider provider; blur_node_t(blur_algorithm_provider provider) : floating_inner_node_t(false) { this->provider = provider; } std::string stringify() const override { return "blur"; } void gen_render_instances(std::vector& instances, damage_callback push_damage, wf::output_t *shown_on) override; }; class blur_render_instance_t : public transformer_render_instance_t { wf::framebuffer_t saved_pixels; wf::region_t saved_pixels_region; public: using transformer_render_instance_t::transformer_render_instance_t; ~blur_render_instance_t() { OpenGL::render_begin(); saved_pixels.release(); OpenGL::render_end(); } bool is_fully_opaque(wf::region_t damage) { if (self->get_children().size() == 1) { if (auto opaque = dynamic_cast(self->get_children().front().get())) { return (damage ^ opaque->get_opaque_region()).empty(); } } return false; } wf::region_t calculate_translucent_damage(const wf::render_target_t& target, wf::region_t damage) { if (self->get_children().size() == 1) { if (auto opaque = dynamic_cast(self->get_children().front().get())) { const int padding = calculate_damage_padding(target, self->provider()->calculate_blur_radius()); auto opaque_region = opaque->get_opaque_region(); opaque_region.expand_edges(-padding); wf::region_t translucent_region = damage ^ opaque_region; return translucent_region; } } return damage; } void schedule_instructions(std::vector& instructions, const wf::render_target_t& target, wf::region_t& damage) override { const int padding = calculate_damage_padding(target, self->provider()->calculate_blur_radius()); auto bbox = self->get_bounding_box(); // In order to render a part of the blurred background, we need to sample // from area which is larger than the damaged area. However, the edges // of the expanded area suffer from the same problem (e.g. the blurred // background has artifacts). The solution to this is to expand the // damage and keep a copy of the pixels where we redraw, but wouldn't // have redrawn if not for blur. After that, we copy those old areas // back to the destination framebuffer, giving the illusion that they // were never damaged. auto padded_region = damage & bbox; if (is_fully_opaque(padded_region & target.geometry)) { // If there are no regions to blur, we can directly render them. for (auto& ch : this->children) { ch->schedule_instructions(instructions, target, damage); } return; } padded_region.expand_edges(padding); padded_region &= bbox; // Don't forget to keep expanded damage within the bounds of the render // target, otherwise we may be sampling from outside of it (undefined // contents). padded_region &= target.geometry; // Actual region which will be repainted by this render instance. wf::region_t we_repaint = padded_region; saved_pixels_region = target.framebuffer_region_from_geometry_region(padded_region) ^ target.framebuffer_region_from_geometry_region(damage); // Nodes below should re-render the padded areas so that we can sample from them damage |= padded_region; OpenGL::render_begin(); saved_pixels.allocate(target.viewport_width, target.viewport_height); saved_pixels.bind(); GL_CALL(glBindFramebuffer(GL_READ_FRAMEBUFFER, target.fb)); /* Copy pixels in padded_region from target_fb to saved_pixels. */ for (const auto& box : saved_pixels_region) { GL_CALL(glBlitFramebuffer( box.x1, target.viewport_height - box.y2, box.x2, target.viewport_height - box.y1, box.x1, box.y1, box.x2, box.y2, GL_COLOR_BUFFER_BIT, GL_LINEAR)); } OpenGL::render_end(); instructions.push_back(render_instruction_t{ .instance = this, .target = target, .damage = we_repaint, }); } void render(const wf::render_target_t& target, const wf::region_t& damage) override { auto tex = get_texture(target.scale); auto bounding_box = self->get_bounding_box(); if (!damage.empty()) { auto translucent_damage = calculate_translucent_damage(target, damage); self->provider()->prepare_blur(target, translucent_damage); self->provider()->render(tex, bounding_box, damage, target, target); } OpenGL::render_begin(target); // Setup framebuffer I/O. target_fb contains the frame // rendered with expanded damage and artifacts on the edges. // saved_pixels has the the padded region of pixels to overwrite the // artifacts that blurring has left behind. GL_CALL(glBindFramebuffer(GL_READ_FRAMEBUFFER, saved_pixels.fb)); /* Copy pixels back from saved_pixels to target_fb. */ for (const auto& box : saved_pixels_region) { GL_CALL(glBlitFramebuffer( box.x1, box.y1, box.x2, box.y2, box.x1, target.viewport_height - box.y2, box.x2, target.viewport_height - box.y1, GL_COLOR_BUFFER_BIT, GL_LINEAR)); } /* Reset stuff */ saved_pixels_region.clear(); OpenGL::render_end(); } direct_scanout try_scanout(wf::output_t *output) override { // Enable direct scanout if it is possible return scene::try_scanout_from_list(children, output); } }; void blur_node_t::gen_render_instances(std::vector& instances, damage_callback push_damage, wf::output_t *shown_on) { auto uptr = std::make_unique(this, push_damage, shown_on); if (uptr->has_instances()) { instances.push_back(std::move(uptr)); } } } } class wayfire_blur : public wf::plugin_interface_t { // Before doing a render pass, expand the damage by the blur radius. // This is needed, because when blurring, the pixels that changed // affect a larger area than the really damaged region, e.g. the region // that comes from client damage. wf::signal::connection_t on_render_pass_begin = [=] (wf::scene::render_pass_begin_signal *ev) { if (!provider) { return; } const int padding = calculate_damage_padding(ev->target, provider()->calculate_blur_radius()); ev->damage.expand_edges(padding); ev->damage &= ev->target.geometry; }; public: blur_algorithm_provider provider; wf::button_callback button_toggle; wf::signal::connection_t on_view_mapped = [=] (wf::view_mapped_signal *ev) { if (blur_by_default.matches(ev->view)) { add_transformer(ev->view); } }; wf::view_matcher_t blur_by_default{"blur/blur_by_default"}; wf::option_wrapper_t method_opt{"blur/method"}; wf::option_wrapper_t toggle_button{"blur/toggle"}; wf::config::option_base_t::updated_callback_t blur_method_changed; std::unique_ptr blur_algorithm; void add_transformer(wayfire_view view) { auto tmanager = view->get_transformed_node(); if (tmanager->get_transformer()) { return; } auto provider = [=] () { return blur_algorithm.get(); }; auto node = std::make_shared(provider); tmanager->add_transformer(node, wf::TRANSFORMER_BLUR); } void pop_transformer(wayfire_view view) { auto tmanager = view->get_transformed_node(); tmanager->rem_transformer(); } void remove_transformers() { for (auto& view : wf::get_core().get_all_views()) { pop_transformer(view); } } public: void init() override { wf::get_core().connect(&on_render_pass_begin); blur_method_changed = [=] () { blur_algorithm = create_blur_from_name(method_opt); wf::scene::damage_node(wf::get_core().scene(), wf::get_core().scene()->get_bounding_box()); }; /* Create initial blur algorithm */ blur_method_changed(); method_opt.set_callback(blur_method_changed); /* Toggles the blur state of the view the user clicked on */ button_toggle = [=] (auto) { auto view = wf::get_core().get_cursor_focus_view(); if (!view) { return false; } if (view->get_transformed_node()->get_transformer()) { pop_transformer(view); } else { add_transformer(view); } return true; }; wf::get_core().bindings->add_button(toggle_button, &button_toggle); provider = [=] () { return this->blur_algorithm.get(); }; wf::get_core().connect(&on_view_mapped); for (auto& view : wf::get_core().get_all_views()) { if (blur_by_default.matches(view)) { add_transformer(view); } } } void fini() override { remove_transformers(); wf::get_core().bindings->rem_binding(&button_toggle); /* Call blur algorithm destructor */ blur_algorithm = nullptr; } }; DECLARE_WAYFIRE_PLUGIN(wayfire_blur); wayfire-0.8.1/plugins/blur/blur.hpp000066400000000000000000000174671457431457600173270ustar00rootroot00000000000000#pragma once #include "wayfire/geometry.hpp" #include #include #include #include /* The MIT License (MIT) * * Copyright (c) 2018 Ilia Bozhinov * Copyright (c) 2018 Scott Moreau * * The design of blur takes extra consideration due to the fact that * the results of blurred pixels rely on surrounding pixel values. * This means that when damage happens for only part of the scene (1), * blurring this area can result to artifacts because of sampling * beyond the edges of the area. To work around this issue, wayfire * issues two signals - workspace-stream-pre and workspace-stream-post. * workspace-stream-pre gives plugins an opportunity to pad the rects * of the damage region (2) and save a snap-shot of the padded area from * the buffer containing the last frame. This will be used to redraw * the area that will contain artifacts after rendering. This is ok * because this area is outside of the original damage area, so the * pixels won't be changing in this region of the scene. pre_render is * called with the padded damage region as an argument (2). The padded * damage extents (3) are used for blitting from the framebuffer, which * contains the scene rendered up until the view for which pre_render * is called. The padded damage extents rect is blurred with artifacts * in pre_render, after which it is then alpha blended with the window * and rendered to the framebuffer. Finally, workspace-stream-post * allows a chance to redraw the padded area with the saved pixels, * before swapping buffers. As long as the padding is enough to cover * the maximum sample offset that the shader uses, there should be a * seamless experience. * * 1) * ................................................................. * | | * | | * | .................................. | * | | |.. | * | | | | | * | | Damage region | | | * | | | | | * | | | | | * | | | | | * | | | | | * | ```|```````````````````````````````| | * | ````````````````````````````````` | * | | * | | * ````````````````````````````````````````````````````````````````` * * 2) * ................................................................. * | | * | ...................................... | * | | .................................. |..<-- Padding | * | | | |.. | | * | | | | | | | * | | | Padded | | | | * | | | Damage region | | | | * | | | | | | | * | | | | | | | * | | | | | | | * | | | | | | | * | | ```|```````````````````````````````| | | * | ```| ````````````````````````````````` |<-- Padding | * | ````````````````````````````````````` | * ````````````````````````````````````````````````````````````````` * * 3) * ................................................................. * | | * | x1| |x2 | * | y1__ ...................................... . | * | | |.. | * | | | | * | | ^ | | * | | | | * | | <- Padded extents -> | | * | | | | * | | v | | * | | | | * | y2__ ```| | | * | ` ````````````````````````````````````` | * | | * ````````````````````````````````````````````````````````````````` */ class wf_blur_base { protected: /* used to store temporary results in blur algorithms, cleaned up in base * destructor */ wf::framebuffer_t fb[2]; wf::geometry_t prepared_geometry; /* the program created by the given algorithm, cleaned up in base destructor */ OpenGL::program_t program[2]; /* the program used by wf_blur_base to combine the blurred, unblurred and * view texture */ OpenGL::program_t blend_program; /* used to get individual algorithm options from config * should be set by the constructor */ std::string algorithm_name; wf::option_wrapper_t saturation_opt; wf::option_wrapper_t offset_opt; wf::option_wrapper_t degrade_opt, iterations_opt; wf::config::option_base_t::updated_callback_t options_changed; /* renders the in texture to the out framebuffer. * assumes a properly bound and initialized GL program */ void render_iteration(wf::region_t blur_region, wf::framebuffer_t& in, wf::framebuffer_t& out, int width, int height); /* copy the source pixels from region, storing into result * returns the result geometry, in framebuffer coords */ wlr_box copy_region(wf::framebuffer_t& result, const wf::render_target_t& source, const wf::region_t& region); /* blur fb[0] * width and height are the scaled dimensions of the buffer * returns the index of the fb where the result is stored (0 or 1) */ virtual int blur_fb0(const wf::region_t& blur_region, int width, int height) = 0; public: wf_blur_base(std::string name); virtual ~wf_blur_base(); virtual int calculate_blur_radius(); /** * Calculate the blurred background region. * * @param target_fb A render target containing the background to be blurred. * @param damage The region to be blurred. */ void prepare_blur(const wf::render_target_t& target_fb, const wf::region_t& damage); /** * Render a view with a blended background as prepared from @prepare_blur. * * @param src_tex The texture of the view to render. * @param src_box The geometry of the view in framebuffer logical coordinates. * @param damage The region to repaint, in logical coordinates. * @param background_source_fb The framebuffer used to prepare the background blur. * @param target_fb The target to draw to. */ void render(wf::texture_t src_tex, wlr_box src_box, const wf::region_t& damage, const wf::render_target_t& background_source_fb, const wf::render_target_t& target_fb); }; std::unique_ptr create_box_blur(); std::unique_ptr create_bokeh_blur(); std::unique_ptr create_kawase_blur(); std::unique_ptr create_gaussian_blur(); std::unique_ptr create_blur_from_name(std::string algorithm_name); wayfire-0.8.1/plugins/blur/bokeh.cpp000066400000000000000000000053361457431457600174360ustar00rootroot00000000000000#include "blur.hpp" static const char *bokeh_vertex_shader = R"( #version 100 attribute mediump vec2 position; varying mediump vec2 uv; void main() { gl_Position = vec4(position.xy, 0.0, 1.0); uv = (position.xy + vec2(1.0, 1.0)) / 2.0; } )"; static const char *bokeh_fragment_shader = R"( #version 100 precision mediump float; uniform float offset; uniform int iterations; uniform vec2 halfpixel; uniform int mode; uniform sampler2D bg_texture; varying mediump vec2 uv; #define GOLDEN_ANGLE 2.39996 mat2 rot = mat2(cos(GOLDEN_ANGLE), sin(GOLDEN_ANGLE), -sin(GOLDEN_ANGLE), cos(GOLDEN_ANGLE)); void main() { float radius = offset; vec4 acc = vec4(0), div = acc; float r = 1.0; vec2 vangle = vec2(radius / sqrt(float(iterations)), radius / sqrt(float(iterations))); for (int j = 0; j < iterations; j++) { r += 1.0 / r; vangle = rot * vangle; vec4 col = texture2D(bg_texture, uv + (r - 1.0) * vangle * halfpixel * 2.0); vec4 bokeh = pow(col, vec4(4.0)); acc += col * bokeh; div += bokeh; } if (iterations == 0) gl_FragColor = texture2D(bg_texture, uv); else gl_FragColor = acc / div; } )"; class wf_bokeh_blur : public wf_blur_base { public: wf_bokeh_blur() : wf_blur_base("bokeh") { OpenGL::render_begin(); program[0].set_simple(OpenGL::compile_program(bokeh_vertex_shader, bokeh_fragment_shader)); OpenGL::render_end(); } int blur_fb0(const wf::region_t& blur_region, int width, int height) override { int iterations = iterations_opt; float offset = offset_opt; static const float vertexData[] = { -1.0f, -1.0f, 1.0f, -1.0f, 1.0f, 1.0f, -1.0f, 1.0f }; OpenGL::render_begin(); /* Upload data to shader */ program[0].use(wf::TEXTURE_TYPE_RGBA); program[0].uniform2f("halfpixel", 0.5f / width, 0.5f / height); program[0].uniform1f("offset", offset); program[0].uniform1i("iterations", iterations); program[0].attrib_pointer("position", 2, 0, vertexData); GL_CALL(glDisable(GL_BLEND)); render_iteration(blur_region, fb[0], fb[1], width, height); /* Reset gl state */ GL_CALL(glEnable(GL_BLEND)); GL_CALL(glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA)); program[0].deactivate(); GL_CALL(glBindTexture(GL_TEXTURE_2D, 0)); OpenGL::render_end(); return 1; } int calculate_blur_radius() override { return 5 * wf_blur_base::offset_opt*wf_blur_base::degrade_opt; } }; std::unique_ptr create_bokeh_blur() { return std::make_unique(); } wayfire-0.8.1/plugins/blur/box.cpp000066400000000000000000000071221457431457600171310ustar00rootroot00000000000000#include "blur.hpp" static const char *box_vertex_shader = R"( #version 100 attribute mediump vec2 position; uniform vec2 size; uniform float offset; varying highp vec2 blurcoord[5]; void main() { gl_Position = vec4(position.xy, 0.0, 1.0); vec2 texcoord = (position.xy + vec2(1.0, 1.0)) / 2.0; blurcoord[0] = texcoord; blurcoord[1] = texcoord + vec2(1.5 * offset) / size; blurcoord[2] = texcoord - vec2(1.5 * offset) / size; blurcoord[3] = texcoord + vec2(3.5 * offset) / size; blurcoord[4] = texcoord - vec2(3.5 * offset) / size; } )"; static const char *box_fragment_shader_horz = R"( #version 100 precision mediump float; uniform sampler2D bg_texture; uniform int mode; varying highp vec2 blurcoord[5]; void main() { vec2 uv = blurcoord[0]; vec4 bp = vec4(0.0); for(int i = 0; i < 5; i++) { vec2 uv = vec2(blurcoord[i].x, uv.y); bp += texture2D(bg_texture, uv); } gl_FragColor = bp / 5.0; } )"; static const char *box_fragment_shader_vert = R"( #version 100 precision mediump float; uniform sampler2D bg_texture; uniform int mode; varying highp vec2 blurcoord[5]; void main() { vec2 uv = blurcoord[0]; vec4 bp = vec4(0.0); for(int i = 0; i < 5; i++) { vec2 uv = vec2(uv.x, blurcoord[i].y); bp += texture2D(bg_texture, uv); } gl_FragColor = bp / 5.0; } )"; class wf_box_blur : public wf_blur_base { public: void get_id_locations(int i) {} wf_box_blur() : wf_blur_base("box") { OpenGL::render_begin(); program[0].set_simple(OpenGL::compile_program( box_vertex_shader, box_fragment_shader_horz)); program[1].set_simple(OpenGL::compile_program( box_vertex_shader, box_fragment_shader_vert)); OpenGL::render_end(); } void upload_data(int i, int width, int height) { float offset = offset_opt; static const float vertexData[] = { -1.0f, -1.0f, 1.0f, -1.0f, 1.0f, 1.0f, -1.0f, 1.0f }; program[i].use(wf::TEXTURE_TYPE_RGBA); program[i].uniform2f("size", width, height); program[i].uniform1f("offset", offset); program[i].attrib_pointer("position", 2, 0, vertexData); } void blur(const wf::region_t& blur_region, int i, int width, int height) { program[i].use(wf::TEXTURE_TYPE_RGBA); render_iteration(blur_region, fb[i], fb[!i], width, height); } int blur_fb0(const wf::region_t& blur_region, int width, int height) override { int i, iterations = iterations_opt; OpenGL::render_begin(); GL_CALL(glDisable(GL_BLEND)); /* Enable our shader and pass some data to it. The shader * does box blur on the background texture in two passes, * one horizontal and one vertical */ upload_data(0, width, height); upload_data(1, width, height); for (i = 0; i < iterations; i++) { /* Blur horizontally */ blur(blur_region, 0, width, height); /* Blur vertically */ blur(blur_region, 1, width, height); } /* Reset gl state */ GL_CALL(glEnable(GL_BLEND)); GL_CALL(glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA)); program[0].deactivate(); GL_CALL(glBindTexture(GL_TEXTURE_2D, 0)); OpenGL::render_end(); return 0; } int calculate_blur_radius() override { return 4 * wf_blur_base::calculate_blur_radius(); } }; std::unique_ptr create_box_blur() { return std::make_unique(); } wayfire-0.8.1/plugins/blur/gaussian.cpp000066400000000000000000000100571457431457600201540ustar00rootroot00000000000000#include "blur.hpp" static const char *gaussian_vertex_shader = R"( #version 100 attribute mediump vec2 position; uniform vec2 size; uniform float offset; varying highp vec2 blurcoord[5]; void main() { gl_Position = vec4(position.xy, 0.0, 1.0); vec2 texcoord = (position.xy + vec2(1.0, 1.0)) / 2.0; blurcoord[0] = texcoord; blurcoord[1] = texcoord + vec2(1.5 * offset) / size; blurcoord[2] = texcoord - vec2(1.5 * offset) / size; blurcoord[3] = texcoord + vec2(3.5 * offset) / size; blurcoord[4] = texcoord - vec2(3.5 * offset) / size; } )"; static const char *gaussian_fragment_shader_horz = R"( #version 100 precision mediump float; uniform sampler2D bg_texture; uniform int mode; varying highp vec2 blurcoord[5]; void main() { vec2 uv = blurcoord[0]; vec4 bp = vec4(0.0); bp += texture2D(bg_texture, vec2(blurcoord[0].x, uv.y)) * 0.204164; bp += texture2D(bg_texture, vec2(blurcoord[1].x, uv.y)) * 0.304005; bp += texture2D(bg_texture, vec2(blurcoord[2].x, uv.y)) * 0.304005; bp += texture2D(bg_texture, vec2(blurcoord[3].x, uv.y)) * 0.093913; bp += texture2D(bg_texture, vec2(blurcoord[4].x, uv.y)) * 0.093913; gl_FragColor = bp; })"; static const char *gaussian_fragment_shader_vert = R"( #version 100 precision mediump float; uniform sampler2D bg_texture; uniform int mode; varying highp vec2 blurcoord[5]; void main() { vec2 uv = blurcoord[0]; vec4 bp = vec4(0.0); bp += texture2D(bg_texture, vec2(uv.x, blurcoord[0].y)) * 0.204164; bp += texture2D(bg_texture, vec2(uv.x, blurcoord[1].y)) * 0.304005; bp += texture2D(bg_texture, vec2(uv.x, blurcoord[2].y)) * 0.304005; bp += texture2D(bg_texture, vec2(uv.x, blurcoord[3].y)) * 0.093913; bp += texture2D(bg_texture, vec2(uv.x, blurcoord[4].y)) * 0.093913; gl_FragColor = bp; })"; class wf_gaussian_blur : public wf_blur_base { public: wf_gaussian_blur() : wf_blur_base("gaussian") { OpenGL::render_begin(); program[0].set_simple(OpenGL::compile_program( gaussian_vertex_shader, gaussian_fragment_shader_horz)); program[1].set_simple(OpenGL::compile_program( gaussian_vertex_shader, gaussian_fragment_shader_vert)); OpenGL::render_end(); } void upload_data(int i, int width, int height) { float offset = offset_opt; static const float vertexData[] = { -1.0f, -1.0f, 1.0f, -1.0f, 1.0f, 1.0f, -1.0f, 1.0f }; program[i].use(wf::TEXTURE_TYPE_RGBA); program[i].uniform2f("size", width, height); program[i].uniform1f("offset", offset); program[i].attrib_pointer("position", 2, 0, vertexData); } void blur(const wf::region_t& blur_region, int i, int width, int height) { program[i].use(wf::TEXTURE_TYPE_RGBA); render_iteration(blur_region, fb[i], fb[!i], width, height); } int blur_fb0(const wf::region_t& blur_region, int width, int height) override { int i, iterations = iterations_opt; OpenGL::render_begin(); GL_CALL(glDisable(GL_BLEND)); /* Enable our shader and pass some data to it. The shader * does gaussian blur on the background texture in two passes, * one horizontal and one vertical */ upload_data(0, width, height); upload_data(1, width, height); for (i = 0; i < iterations; i++) { /* Blur horizontally */ blur(blur_region, 0, width, height); /* Blur vertically */ blur(blur_region, 1, width, height); } /* Reset gl state */ GL_CALL(glEnable(GL_BLEND)); GL_CALL(glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA)); GL_CALL(glBindTexture(GL_TEXTURE_2D, 0)); program[1].deactivate(); OpenGL::render_end(); return 0; } int calculate_blur_radius() override { return 4 * wf_blur_base::calculate_blur_radius(); } }; std::unique_ptr create_gaussian_blur() { return std::make_unique(); } wayfire-0.8.1/plugins/blur/kawase.cpp000066400000000000000000000106621457431457600176170ustar00rootroot00000000000000#include "blur.hpp" static const char *kawase_vertex_shader = R"( #version 100 attribute mediump vec2 position; varying mediump vec2 uv; void main() { gl_Position = vec4(position.xy, 0.0, 1.0); uv = (position.xy + vec2(1.0, 1.0)) / 2.0; })"; static const char *kawase_fragment_shader_down = R"( #version 100 precision mediump float; uniform float offset; uniform vec2 halfpixel; uniform sampler2D bg_texture; varying mediump vec2 uv; void main() { vec4 sum = texture2D(bg_texture, uv) * 4.0; sum += texture2D(bg_texture, uv - halfpixel.xy * offset); sum += texture2D(bg_texture, uv + halfpixel.xy * offset); sum += texture2D(bg_texture, uv + vec2(halfpixel.x, -halfpixel.y) * offset); sum += texture2D(bg_texture, uv - vec2(halfpixel.x, -halfpixel.y) * offset); gl_FragColor = sum / 8.0; })"; static const char *kawase_fragment_shader_up = R"( #version 100 precision mediump float; uniform float offset; uniform vec2 halfpixel; uniform sampler2D bg_texture; varying mediump vec2 uv; void main() { vec4 sum = texture2D(bg_texture, uv + vec2(-halfpixel.x * 2.0, 0.0) * offset); sum += texture2D(bg_texture, uv + vec2(-halfpixel.x, halfpixel.y) * offset) * 2.0; sum += texture2D(bg_texture, uv + vec2(0.0, halfpixel.y * 2.0) * offset); sum += texture2D(bg_texture, uv + vec2(halfpixel.x, halfpixel.y) * offset) * 2.0; sum += texture2D(bg_texture, uv + vec2(halfpixel.x * 2.0, 0.0) * offset); sum += texture2D(bg_texture, uv + vec2(halfpixel.x, -halfpixel.y) * offset) * 2.0; sum += texture2D(bg_texture, uv + vec2(0.0, -halfpixel.y * 2.0) * offset); sum += texture2D(bg_texture, uv + vec2(-halfpixel.x, -halfpixel.y) * offset) * 2.0; gl_FragColor = sum / 12.0; })"; class wf_kawase_blur : public wf_blur_base { public: wf_kawase_blur() : wf_blur_base("kawase") { OpenGL::render_begin(); program[0].set_simple(OpenGL::compile_program(kawase_vertex_shader, kawase_fragment_shader_down)); program[1].set_simple(OpenGL::compile_program(kawase_vertex_shader, kawase_fragment_shader_up)); OpenGL::render_end(); } int blur_fb0(const wf::region_t& blur_region, int width, int height) override { int iterations = iterations_opt; float offset = offset_opt; int sampleWidth, sampleHeight; /* Upload data to shader */ static const float vertexData[] = { -1.0f, -1.0f, 1.0f, -1.0f, 1.0f, 1.0f, -1.0f, 1.0f }; OpenGL::render_begin(); program[0].use(wf::TEXTURE_TYPE_RGBA); /* Downsample */ program[0].attrib_pointer("position", 2, 0, vertexData); /* Disable blending, because we may have transparent background, which * we want to render on uncleared framebuffer */ GL_CALL(glDisable(GL_BLEND)); program[0].uniform1f("offset", offset); for (int i = 0; i < iterations; i++) { sampleWidth = width / (1 << i); sampleHeight = height / (1 << i); auto region = blur_region * (1.0 / (1 << i)); program[0].uniform2f("halfpixel", 0.5f / sampleWidth, 0.5f / sampleHeight); render_iteration(region, fb[i % 2], fb[1 - i % 2], sampleWidth, sampleHeight); } program[0].deactivate(); /* Upsample */ program[1].use(wf::TEXTURE_TYPE_RGBA); program[1].attrib_pointer("position", 2, 0, vertexData); program[1].uniform1f("offset", offset); for (int i = iterations - 1; i >= 0; i--) { sampleWidth = width / (1 << i); sampleHeight = height / (1 << i); auto region = blur_region * (1.0 / (1 << i)); program[1].uniform2f("halfpixel", 0.5f / sampleWidth, 0.5f / sampleHeight); render_iteration(region, fb[1 - i % 2], fb[i % 2], sampleWidth, sampleHeight); } /* Reset gl state */ GL_CALL(glEnable(GL_BLEND)); GL_CALL(glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA)); program[1].deactivate(); GL_CALL(glBindTexture(GL_TEXTURE_2D, 0)); OpenGL::render_end(); return 0; } int calculate_blur_radius() override { return pow(2, iterations_opt + 1) * offset_opt * degrade_opt; } }; std::unique_ptr create_kawase_blur() { return std::make_unique(); } wayfire-0.8.1/plugins/blur/meson.build000066400000000000000000000011561457431457600200000ustar00rootroot00000000000000blur_base = shared_library('wayfire-blur-base', ['blur-base.cpp', 'box.cpp', 'gaussian.cpp', 'kawase.cpp', 'bokeh.cpp'], include_directories: [wayfire_api_inc, wayfire_conf_inc], dependencies: [wlroots, pixman, wfconfig], override_options: ['b_lundef=false'], install: true) install_headers(['blur.hpp'], subdir: 'wayfire/plugins/blur') blur = shared_module('blur', ['blur.cpp'], link_with: blur_base, include_directories: [wayfire_api_inc, wayfire_conf_inc], dependencies: [wlroots, pixman, wfconfig], install: true, install_dir: join_paths(get_option('libdir'), 'wayfire')) wayfire-0.8.1/plugins/common/000077500000000000000000000000001457431457600161575ustar00rootroot00000000000000wayfire-0.8.1/plugins/common/meson.build000066400000000000000000000001571457431457600203240ustar00rootroot00000000000000plugins_common_inc = include_directories('.') install_subdir('wayfire', install_dir: get_option('includedir')) wayfire-0.8.1/plugins/common/wayfire/000077500000000000000000000000001457431457600176255ustar00rootroot00000000000000wayfire-0.8.1/plugins/common/wayfire/plugins/000077500000000000000000000000001457431457600213065ustar00rootroot00000000000000wayfire-0.8.1/plugins/common/wayfire/plugins/common/000077500000000000000000000000001457431457600225765ustar00rootroot00000000000000wayfire-0.8.1/plugins/common/wayfire/plugins/common/cairo-util.hpp000066400000000000000000000243541457431457600253670ustar00rootroot00000000000000#pragma once #include "wayfire/geometry.hpp" #include #include #include #include #include #include namespace wf { struct simple_texture_t; } /** * Upload the data from the cairo surface to the OpenGL texture. * * @param surface The source cairo surface. * @param buffer The buffer to upload data to. */ static void cairo_surface_upload_to_texture( cairo_surface_t *surface, wf::simple_texture_t& buffer) { buffer.width = cairo_image_surface_get_width(surface); buffer.height = cairo_image_surface_get_height(surface); if (buffer.tex == (GLuint) - 1) { GL_CALL(glGenTextures(1, &buffer.tex)); } auto src = cairo_image_surface_get_data(surface); GL_CALL(glBindTexture(GL_TEXTURE_2D, buffer.tex)); GL_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)); GL_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)); GL_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_R, GL_BLUE)); GL_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_B, GL_RED)); GL_CALL(glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, buffer.width, buffer.height, 0, GL_RGBA, GL_UNSIGNED_BYTE, src)); } namespace wf { /** * Simple wrapper around rendering text with Cairo. This object can be * kept around to avoid reallocation of the cairo surface and OpenGL * texture on repeated renders. */ struct cairo_text_t { wf::simple_texture_t tex; /* parameters used for rendering */ struct params { /* font size */ int font_size = 12; /* color for background rectangle (only used if bg_rect == true) */ wf::color_t bg_color; /* text color */ wf::color_t text_color; /* scale everything by this amount */ float output_scale = 1.f; /* crop result to this size (if nonzero); * note that this is multiplied by output_scale */ wf::dimensions_t max_size{0, 0}; /* draw a rectangle in the background with bg_color */ bool bg_rect = true; /* round the corners of the background rectangle */ bool rounded_rect = true; /* if true, the resulting surface will be cropped to the * minimum size necessary to fit the text; otherwise, the * resulting surface might be bigger than necessary and the * text is centered in it */ bool exact_size = false; params() {} params(int font_size_, const wf::color_t& bg_color_, const wf::color_t& text_color_, float output_scale_ = 1.f, const wf::dimensions_t& max_size_ = {0, 0}, bool bg_rect_ = true, bool exact_size_ = false) : font_size(font_size_), bg_color(bg_color_), text_color(text_color_), output_scale(output_scale_), max_size(max_size_), bg_rect(bg_rect_), exact_size(exact_size_) {} }; /** * Render the given text in the texture tex. * * @param text text to render * @param par parameters for rendering * * @return The size needed to render in scaled coordinates. If this is larger * than the size of tex, it means the result was cropped (due to the constraint * given in par.max_size). If it is smaller, than the result is centered along * that dimension. */ wf::dimensions_t render_text(const std::string& text, const params& par) { if (!cr) { /* create with default size */ cairo_create_surface(); } PangoFontDescription *font_desc; PangoLayout *layout; PangoRectangle extents; /* TODO: font properties could be made parameters! */ font_desc = pango_font_description_from_string("sans-serif bold"); pango_font_description_set_absolute_size(font_desc, par.font_size * par.output_scale * PANGO_SCALE); layout = pango_cairo_create_layout(cr); pango_layout_set_font_description(layout, font_desc); pango_layout_set_text(layout, text.c_str(), text.size()); pango_layout_get_extents(layout, NULL, &extents); double xpad = par.bg_rect ? 10.0 * par.output_scale : 0.0; double ypad = par.bg_rect ? 0.2 * ((float)extents.height / PANGO_SCALE) : 0.0; int w = (int)((float)extents.width / PANGO_SCALE + 2 * xpad); int h = (int)((float)extents.height / PANGO_SCALE + 2 * ypad); wf::dimensions_t ret = {w, h}; if (par.max_size.width && (w > par.max_size.width * par.output_scale)) { w = (int)std::floor(par.max_size.width * par.output_scale); } if (par.max_size.height && (h > par.max_size.height * par.output_scale)) { h = (int)std::floor(par.max_size.height * par.output_scale); } if ((w != surface_size.width) || (h != surface_size.height)) { if (par.exact_size || (w > surface_size.width) || (h > surface_size.height)) { surface_size.width = w; surface_size.height = h; cairo_create_surface(); } } cairo_set_operator(cr, CAIRO_OPERATOR_CLEAR); cairo_paint(cr); int x = (surface_size.width - w) / 2; int y = (surface_size.height - h) / 2; if (par.bg_rect) { int min_r = (int)(20 * par.output_scale); int r = par.rounded_rect ? (h > min_r ? min_r : (h - 2) / 2) : 0; cairo_move_to(cr, x + r, y); cairo_line_to(cr, x + w - r, y); if (par.rounded_rect) { cairo_curve_to(cr, x + w, y, x + w, y, x + w, y + r); } cairo_line_to(cr, x + w, y + h - r); if (par.rounded_rect) { cairo_curve_to(cr, x + w, y + h, x + w, y + h, x + w - r, y + h); } cairo_line_to(cr, x + r, y + h); if (par.rounded_rect) { cairo_curve_to(cr, x, y + h, x, y + h, x, y + h - r); } cairo_line_to(cr, x, y + r); if (par.rounded_rect) { cairo_curve_to(cr, x, y, x, y, x + r, y); } cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE); cairo_set_source_rgba(cr, par.bg_color.r, par.bg_color.g, par.bg_color.b, par.bg_color.a); cairo_fill(cr); } x += xpad; y += ypad; cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE); cairo_move_to(cr, x - (float)extents.x / PANGO_SCALE, y); cairo_set_source_rgba(cr, par.text_color.r, par.text_color.g, par.text_color.b, par.text_color.a); pango_cairo_show_layout(cr, layout); pango_font_description_free(font_desc); g_object_unref(layout); cairo_surface_flush(surface); OpenGL::render_begin(); cairo_surface_upload_to_texture(surface, tex); OpenGL::render_end(); return ret; } /** * Standalone function version to render text to an OpenGL texture */ static wf::dimensions_t cairo_render_text_to_texture(const std::string& text, const wf::cairo_text_t::params& par, wf::simple_texture_t& tex) { wf::cairo_text_t ct; /* note: we "borrow" the texture from what was supplied (if any) */ ct.tex.tex = tex.tex; auto ret = ct.render_text(text, par); if (tex.tex == (GLuint) - 1) { tex.tex = ct.tex.tex; } tex.width = ct.tex.width; tex.height = ct.tex.height; ct.tex.tex = -1; return ret; } cairo_text_t() = default; ~cairo_text_t() { cairo_free(); } cairo_text_t(const cairo_text_t &) = delete; cairo_text_t& operator =(const cairo_text_t&) = delete; cairo_text_t(cairo_text_t && o) noexcept : tex(std::move(o.tex)), cr(o.cr), surface(o.surface), surface_size(o.surface_size) { o.cr = nullptr; o.surface = nullptr; } cairo_text_t& operator =(cairo_text_t&& o) noexcept { if (&o == this) { return *this; } cairo_free(); tex = std::move(o.tex); cr = o.cr; surface = o.surface; surface_size = o.surface_size; o.cr = nullptr; o.surface = nullptr; return *this; } /** * Calculate the height of text rendered with a given font size. * * @param font_size Desired font size. * @param bg_rect Whether a background rectangle should be taken into account. * * @returns Required height of the surface. */ static unsigned int measure_height(int font_size, bool bg_rect = true) { cairo_text_t dummy; dummy.surface_size.width = 1; dummy.surface_size.height = 1; dummy.cairo_create_surface(); cairo_font_extents_t font_extents; /* TODO: font properties could be made parameters! */ cairo_select_font_face(dummy.cr, "sans-serif", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_BOLD); cairo_set_font_size(dummy.cr, font_size); cairo_font_extents(dummy.cr, &font_extents); double ypad = bg_rect ? 0.2 * (font_extents.ascent + font_extents.descent) : 0.0; unsigned int h = (unsigned int)std::ceil(font_extents.ascent + font_extents.descent + 2 * ypad); return h; } wf::dimensions_t get_size() const { return surface_size; } protected: /* cairo context and surface for the text */ cairo_t *cr = nullptr; cairo_surface_t *surface = nullptr; /* current width and height of the above surface */ wf::dimensions_t surface_size = {400, 100}; void cairo_free() { if (cr) { cairo_destroy(cr); } if (surface) { cairo_surface_destroy(surface); } cr = nullptr; surface = nullptr; } void cairo_create_surface() { cairo_free(); surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, surface_size.width, surface_size.height); cr = cairo_create(surface); } }; } wayfire-0.8.1/plugins/common/wayfire/plugins/common/geometry-animation.hpp000066400000000000000000000030161457431457600271170ustar00rootroot00000000000000#pragma once #include #include #include namespace wf { using namespace wf::animation; class geometry_animation_t : public duration_t { public: using duration_t::duration_t; timed_transition_t x{*this}; timed_transition_t y{*this}; timed_transition_t width{*this}; timed_transition_t height{*this}; void set_start(wf::geometry_t geometry) { copy_fields(geometry, &timed_transition_t::start); } void set_end(wf::geometry_t geometry) { copy_fields(geometry, &timed_transition_t::end); } operator wf::geometry_t() const { return {(int)x, (int)y, (int)width, (int)height}; } protected: void copy_fields(wf::geometry_t geometry, double timed_transition_t::*member) { this->x.*member = geometry.x; this->y.*member = geometry.y; this->width.*member = geometry.width; this->height.*member = geometry.height; } }; /** Interpolate the geometry between a and b with alpha (in [0..1]), i.e a * * (1-alpha) + b * alpha */ static inline wf::geometry_t interpolate(wf::geometry_t a, wf::geometry_t b, double alpha) { const auto& interp = [=] (int32_t wf::geometry_t::*member) -> int32_t { return std::round((1 - alpha) * a.*member + alpha * b.*member); }; return { interp(&wf::geometry_t::x), interp(&wf::geometry_t::y), interp(&wf::geometry_t::width), interp(&wf::geometry_t::height) }; } } wayfire-0.8.1/plugins/common/wayfire/plugins/common/input-grab.hpp000066400000000000000000000106431457431457600253630ustar00rootroot00000000000000#pragma once #include "wayfire/core.hpp" #include "wayfire/output.hpp" #include "wayfire/scene-input.hpp" #include "wayfire/scene-operations.hpp" #include "wayfire/scene.hpp" #include #include #include #include namespace wf { namespace scene { /** * A scene node which can be used to implement input grab on a particular output. */ class grab_node_t : public node_t { std::string name; wf::output_t *output; keyboard_interaction_t *keyboard = nullptr; pointer_interaction_t *pointer = nullptr; touch_interaction_t *touch = nullptr; node_flags_bitmask_t m_flags = 0; public: grab_node_t(std::string name, wf::output_t *output, keyboard_interaction_t *keyboard = NULL, pointer_interaction_t *pointer = NULL, touch_interaction_t *touch = NULL) : node_t(false), name(name), output(output), keyboard(keyboard), pointer(pointer), touch(touch) {} node_flags_bitmask_t flags() const override { return node_t::flags() | m_flags; } void set_additional_flags(node_flags_bitmask_t add_flags) { this->m_flags = add_flags; } std::optional find_node_at(const wf::pointf_t& at) override { if (output->get_layout_geometry() & at) { input_node_t result; result.node = this; result.local_coords = to_local(at); return result; } return {}; } wf::keyboard_focus_node_t keyboard_refocus(wf::output_t *output) override { if (output != this->output) { return wf::keyboard_focus_node_t{}; } return wf::keyboard_focus_node_t{ .node = this, .importance = focus_importance::REGULAR, .allow_focus_below = false, }; } /** * Get a textual representation of the node, used for debugging purposes. * For example, see wf::dump_scene(). * The representation should therefore not contain any newline characters. */ std::string stringify() const override { return name + "-input-grab"; } keyboard_interaction_t& keyboard_interaction() override { return keyboard ? *keyboard : node_t::keyboard_interaction(); } pointer_interaction_t& pointer_interaction() override { return pointer ? *pointer : node_t::pointer_interaction(); } touch_interaction_t& touch_interaction() override { return touch ? *touch : node_t::touch_interaction(); } }; } /** * A helper class for managing input grabs on an output. */ class input_grab_t { wf::output_t *output; std::shared_ptr grab_node; public: input_grab_t(std::string name, wf::output_t *output, keyboard_interaction_t *keyboard = NULL, pointer_interaction_t *pointer = NULL, touch_interaction_t *touch = NULL) { this->output = output; grab_node = std::make_shared(name, output, keyboard, pointer, touch); } /** * Set/unset the RAW_INPUT flag on the grab node. */ void set_wants_raw_input(bool wants_raw) { grab_node->set_additional_flags(wants_raw ? (uint64_t)wf::scene::node_flags::RAW_INPUT : 0); } bool is_grabbed() const { return grab_node->parent() != nullptr; } /** * Grab input from all layers from background to @layer_below. */ void grab_input(wf::scene::layer layer_below) { wf::dassert(grab_node->parent() == nullptr, "Trying to grab twice!"); auto& root = wf::get_core().scene(); auto children = root->get_children(); auto idx = std::find(children.begin(), children.end(), root->layers[(int)layer_below]); wf::dassert(idx != children.end(), "Could not find node for a layer: " + std::to_string((int)layer_below)); children.insert(idx, grab_node); root->set_children_list(children); wf::get_core().transfer_grab(grab_node); scene::update(root, scene::update_flag::CHILDREN_LIST | scene::update_flag::REFOCUS); // Set cursor to default. wf::get_core().set_cursor("default"); } /** * Ungrab the input. */ void ungrab_input() { if (grab_node->parent()) { wf::scene::remove_child(grab_node, scene::update_flag::REFOCUS); } } }; } wayfire-0.8.1/plugins/common/wayfire/plugins/common/key-repeat.hpp000066400000000000000000000016651457431457600253650ustar00rootroot00000000000000#pragma once #include #include namespace wf { struct key_repeat_t { wf::option_wrapper_t delay{"input/kb_repeat_delay"}; wf::option_wrapper_t rate{"input/kb_repeat_rate"}; wf::wl_timer timer_delay; wf::wl_timer timer_rate; using callback_t = std::function; key_repeat_t() {} key_repeat_t(uint32_t key, callback_t handler) { set_callback(key, handler); } void set_callback(uint32_t key, callback_t handler) { disconnect(); timer_delay.set_timeout(delay, [=] () { timer_rate.set_timeout(1000 / rate, [=] () { // handle can determine if key should be repeated return handler(key); }); }); } void disconnect() { timer_delay.disconnect(); timer_rate.disconnect(); } }; } wayfire-0.8.1/plugins/common/wayfire/plugins/common/move-drag-interface.hpp000066400000000000000000000612351457431457600271350ustar00rootroot00000000000000#pragma once #include "wayfire/core.hpp" #include "wayfire/debug.hpp" #include "wayfire/geometry.hpp" #include "wayfire/opengl.hpp" #include "wayfire/region.hpp" #include "wayfire/scene-input.hpp" #include "wayfire/scene-operations.hpp" #include "wayfire/scene-render.hpp" #include "wayfire/scene.hpp" #include "wayfire/seat.hpp" #include "wayfire/signal-definitions.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace wf { /** * A collection of classes and interfaces which can be used by plugins which * support dragging views to move them. * * A plugin using these APIs would get support for: * * - Moving views on the same output, following the pointer or touch position. * - Holding views in place until a certain threshold is reached * - Wobbly windows (if enabled) * - Move the view freely between different outputs with different plugins active * on them, as long as all of these plugins support this interface. * - Show smooth transitions of the moving view when moving between different * outputs. * * A plugin using these APIs is expected to: * - Grab input on its respective output and forward any events to the core_drag_t * singleton. * - Have activated itself with CAPABILITY_MANAGE_COMPOSITOR * - Connect to and handle the signals described below. */ namespace move_drag { /** * name: focus-output * on: core_drag_t * when: Emitted output whenever the output where the drag happens changes, * including when the drag begins. */ struct drag_focus_output_signal { /** The output which was focused up to now, might be null. */ wf::output_t *previous_focus_output; /** The output which was focused now. */ wf::output_t *focus_output; }; /** * name: snap-off * on: core_drag_t * when: Emitted if snap-off is enabled and the view was moved more than the * threshold. */ struct snap_off_signal { /** The output which is focused now. */ wf::output_t *focus_output; }; /** * name: done * on: core_drag_t * when: Emitted after the drag operation has ended, and if the view is unmapped * while being dragged. */ struct drag_done_signal { /** The output where the view was dropped. */ wf::output_t *focused_output; /** Whether join-views was enabled for this drag. */ bool join_views; struct view_t { /** Dragged view. */ wayfire_toplevel_view view; /** * The position relative to the view where the grab was. * See scale_around_grab_t::relative_grab */ wf::pointf_t relative_grab; }; /** All views which were dragged. */ std::vector all_views; /** The main view which was dragged. */ wayfire_toplevel_view main_view; /** * The position of the input when the view was dropped. * In output-layout coordinates. */ wf::point_t grab_position; }; /** * Find the geometry of a view, if it has size @size, it is grabbed at point @grab, * and the grab is at position @relative relative to the view. */ inline static wf::geometry_t find_geometry_around( wf::dimensions_t size, wf::point_t grab, wf::pointf_t relative) { return wf::geometry_t{ grab.x - (int)std::floor(relative.x * size.width), grab.y - (int)std::floor(relative.y * size.height), size.width, size.height, }; } /** * Find the position of grab relative to the view. * Example: returns [0.5, 0.5] if the grab is the midpoint of the view. */ inline static wf::pointf_t find_relative_grab( wf::geometry_t view, wf::point_t grab) { return wf::pointf_t{ 1.0 * (grab.x - view.x) / view.width, 1.0 * (grab.y - view.y) / view.height, }; } /** * A transformer used while dragging. * * It is primarily used to scale the view is a plugin needs it, and also to keep it * centered around the `grab_position`. */ class scale_around_grab_t : public wf::scene::floating_inner_node_t { public: /** * Factor for scaling down the view. * A factor 2.0 means that the view will have half of its width and height. */ wf::animation::simple_animation_t scale_factor{wf::create_option(300)}; /** * A place relative to the view, where it is grabbed. * * Coordinates are [0, 1]. A grab at (0.5, 0.5) means that the view is grabbed * at its center. */ wf::pointf_t relative_grab; /** * The position where the grab appears on the outputs, in output-layout * coordinates. */ wf::point_t grab_position; scale_around_grab_t() : floating_inner_node_t(false) {} std::string stringify() const override { return "move-drag"; } wf::pointf_t scale_around_grab(wf::pointf_t point, double factor) { auto bbox = get_children_bounding_box(); auto gx = bbox.x + bbox.width * relative_grab.x; auto gy = bbox.y + bbox.height * relative_grab.y; return { (point.x - gx) * factor + gx, (point.y - gy) * factor + gy, }; } wf::pointf_t to_local(const wf::pointf_t& point) override { return scale_around_grab(point, scale_factor); } wf::pointf_t to_global(const wf::pointf_t& point) override { return scale_around_grab(point, 1.0 / scale_factor); } wf::geometry_t get_bounding_box() override { auto bbox = get_children_bounding_box(); int w = std::floor(bbox.width / scale_factor); int h = std::floor(bbox.height / scale_factor); return find_geometry_around({w, h}, grab_position, relative_grab); } class render_instance_t : public scene::transformer_render_instance_t { public: using transformer_render_instance_t::transformer_render_instance_t; void transform_damage_region(wf::region_t& region) override { region |= self->get_bounding_box(); } void render(const wf::render_target_t& target, const wf::region_t& region) override { auto bbox = self->get_bounding_box(); auto tex = this->get_texture(target.scale); OpenGL::render_begin(target); for (auto& rect : region) { target.logic_scissor(wlr_box_from_pixman_box(rect)); OpenGL::render_texture(tex, target, bbox); } OpenGL::render_end(); } }; void gen_render_instances(std::vector& instances, scene::damage_callback push_damage, wf::output_t *shown_on) override { instances.push_back(std::make_unique(this, push_damage, shown_on)); } }; static const std::string move_drag_transformer = "move-drag-transformer"; /** * Represents a view which is being dragged. * Multiple views exist only if join_views is set to true. */ struct dragged_view_t { // The view being dragged wayfire_toplevel_view view; // Its transformer std::shared_ptr transformer; // The last bounding box used for damage. // This is needed in case the view resizes or something like that, in which // case we don't have access to the previous bbox. wf::geometry_t last_bbox; }; inline wayfire_toplevel_view get_toplevel(wayfire_toplevel_view view) { while (view->parent) { view = view->parent; } return view; } inline std::vector get_target_views(wayfire_toplevel_view grabbed, bool join_views) { std::vector r = {grabbed}; if (join_views) { r = grabbed->enumerate_views(); } return r; } // A node to render the dragged views in global coordinates. // The assumption is that all nodes have a view transformer which transforms them to global (not output-local) // coordinates and thus we just need to schedule them for rendering. class dragged_view_node_t : public wf::scene::node_t { std::vector views; public: dragged_view_node_t(std::vector views) : node_t(false) { this->views = views; } std::string stringify() const override { return "move-drag-view " + stringify_flags(); } void gen_render_instances(std::vector& instances, scene::damage_callback push_damage, wf::output_t *output = nullptr) override { instances.push_back(std::make_unique(this, push_damage, output)); } wf::geometry_t get_bounding_box() override { wf::region_t bounding; for (auto& view : views) { // Note: bbox will be in output layout coordinates now, since this is // how the transformer works auto bbox = view.view->get_transformed_node()->get_bounding_box(); bounding |= bbox; } return wlr_box_from_pixman_box(bounding.get_extents()); } class dragged_view_render_instance_t : public wf::scene::render_instance_t { wf::geometry_t last_bbox = {0, 0, 0, 0}; wf::scene::damage_callback push_damage; std::vector children; wf::signal::connection_t on_node_damage = [=] (scene::node_damage_signal *data) { push_damage(data->region); }; public: dragged_view_render_instance_t(dragged_view_node_t *self, wf::scene::damage_callback push_damage, wf::output_t *shown_on) { auto push_damage_child = [=] (wf::region_t child_damage) { push_damage(last_bbox); last_bbox = self->get_bounding_box(); push_damage(last_bbox); }; for (auto& view : self->views) { auto node = view.view->get_transformed_node(); node->gen_render_instances(children, push_damage_child, shown_on); } } void schedule_instructions(std::vector& instructions, const wf::render_target_t& target, wf::region_t& damage) override { for (auto& inst : children) { inst->schedule_instructions(instructions, target, damage); } } void presentation_feedback(wf::output_t *output) override { for (auto& instance : children) { instance->presentation_feedback(output); } } void compute_visibility(wf::output_t *output, wf::region_t& visible) override { for (auto& instance : children) { const int BIG_NUMBER = 1e5; wf::region_t big_region = wf::geometry_t{-BIG_NUMBER, -BIG_NUMBER, 2 * BIG_NUMBER, 2 * BIG_NUMBER}; instance->compute_visibility(output, big_region); } } }; }; struct drag_options_t { /** * Whether to enable snap off, that is, hold the view in place until * a certain threshold is reached. */ bool enable_snap_off = false; /** * If snap-off is enabled, the amount of pixels to wait for motion until * snap-off is triggered. */ int snap_off_threshold = 0; /** * Join views together, i.e move main window and dialogues together. */ bool join_views = false; double initial_scale = 1.0; }; /** * An object for storing global move drag data (i.e shared between all outputs). * * Intended for use via wf::shared_data::ref_ptr_t. */ class core_drag_t : public signal::provider_t { /** * Rebuild the wobbly model after a change in the scaling, so that the wobbly * model does not try to animate the scaling change itself. */ void rebuild_wobbly(wayfire_toplevel_view view, wf::point_t grab, wf::pointf_t relative) { auto dim = wf::dimensions(wf::view_bounding_box_up_to(view, "wobbly")); modify_wobbly(view, find_geometry_around(dim, grab, relative)); } public: std::optional tentative_grab_position; /** * A button has been pressed which might start a drag action. */ template void set_pending_drag(const Point& current_position) { this->tentative_grab_position = {(int)current_position.x, (int)current_position.y}; } /** * Check whether a motion event makes a sufficient drag so that the drag operation may start at all. * * Note that in some cases this functionality is not used at all, if the action for example was triggered * by a binding. */ bool should_start_pending_drag(wf::point_t current_position) { if (!tentative_grab_position.has_value()) { return false; } return distance_to_grab_origin(current_position) > 5; } /** * Start the actual dragging operation. Note: this should be called **after** set_pending_drag(). * * @param grab_view The view which is being dragged. * @param grab_position The position of the input, in output-layout coordinates. * @param relative The position of the grab_position relative to view. */ void start_drag(wayfire_toplevel_view grab_view, wf::pointf_t relative, const drag_options_t& options) { wf::dassert(tentative_grab_position.has_value(), "First, the drag operation should be set as pending!"); auto bbox = wf::view_bounding_box_up_to(grab_view, "wobbly"); wf::point_t rel_grab_pos = { int(bbox.x + relative.x * bbox.width), int(bbox.y + relative.y * bbox.height), }; if (options.join_views) { grab_view = get_toplevel(grab_view); } this->view = grab_view; this->params = options; wf::get_core().default_wm->set_view_grabbed(view, true); auto target_views = get_target_views(grab_view, options.join_views); for (auto& v : target_views) { dragged_view_t dragged; dragged.view = v; // Setup view transform auto tr = std::make_shared(); dragged.transformer = {tr}; tr->relative_grab = find_relative_grab( wf::view_bounding_box_up_to(v, "wobbly"), rel_grab_pos); tr->grab_position = *tentative_grab_position; tr->scale_factor.animate(options.initial_scale, options.initial_scale); v->get_transformed_node()->add_transformer( tr, wf::TRANSFORMER_HIGHLEVEL - 1); // Hide the view, we will render it as an overlay wf::scene::set_node_enabled(v->get_transformed_node(), false); v->damage(); // Make sure that wobbly has the correct geometry from the start! rebuild_wobbly(v, *tentative_grab_position, dragged.transformer->relative_grab); // TODO: make this configurable! start_wobbly_rel(v, dragged.transformer->relative_grab); this->all_views.push_back(dragged); v->connect(&on_view_unmap); } // Setup overlay hooks render_node = std::make_shared(all_views); wf::scene::add_front(wf::get_core().scene(), render_node); wf::get_core().set_cursor("grabbing"); // Set up snap-off if (params.enable_snap_off) { for (auto& v : all_views) { set_tiled_wobbly(v.view, true); } view_held_in_place = true; } } void start_drag(wayfire_toplevel_view view, const drag_options_t& options) { wf::dassert(tentative_grab_position.has_value(), "First, the drag operation should be set as pending!"); if (options.join_views) { view = get_toplevel(view); } auto bbox = view->get_transformed_node()->get_bounding_box() + wf::origin(view->get_output()->get_layout_geometry()); start_drag(view, find_relative_grab(bbox, *tentative_grab_position), options); } void handle_motion(wf::point_t to) { if (view_held_in_place) { if (distance_to_grab_origin(to) >= (double)params.snap_off_threshold) { view_held_in_place = false; for (auto& v : all_views) { set_tiled_wobbly(v.view, false); } snap_off_signal data; data.focus_output = current_output; emit(&data); } } // Update wobbly independently of the grab position. // This is because while held in place, wobbly is anchored to its edges // so we can still move the grabbed point without moving the view. for (auto& v : all_views) { move_wobbly(v.view, to.x, to.y); if (!view_held_in_place) { v.view->get_transformed_node()->begin_transform_update(); v.transformer->grab_position = to; v.view->get_transformed_node()->end_transform_update(); } } update_current_output(to); } double distance_to_grab_origin(wf::point_t to) const { return abs(to - *tentative_grab_position); } void handle_input_released() { if (!view || all_views.empty()) { this->tentative_grab_position = {}; // Input already released => don't do anything return; } // Store data for the drag done signal drag_done_signal data; data.grab_position = all_views.front().transformer->grab_position; for (auto& v : all_views) { data.all_views.push_back( {v.view, v.transformer->relative_grab}); } data.main_view = this->view; data.focused_output = current_output; data.join_views = params.join_views; // Remove overlay hooks and damage outputs BEFORE popping the transformer wf::scene::remove_child(render_node); render_node = nullptr; for (auto& v : all_views) { auto grab_position = v.transformer->grab_position; auto rel_pos = v.transformer->relative_grab; // Restore view to where it was before wf::scene::set_node_enabled(v.view->get_transformed_node(), true); v.view->get_transformed_node()->rem_transformer(); // Reset wobbly and leave it in output-LOCAL coordinates end_wobbly(v.view); // Important! If the view scale was not 1.0, the wobbly model needs to be // updated with the new size. Since this is an artificial resize, we need // to make sure that the resize happens smoothly. rebuild_wobbly(v.view, grab_position, rel_pos); // Put wobbly back in output-local space, the plugins will take it from // here. translate_wobbly(v.view, -wf::origin(v.view->get_output()->get_layout_geometry())); } // Reset our state wf::get_core().default_wm->set_view_grabbed(view, false); view = nullptr; all_views.clear(); current_output = nullptr; wf::get_core().set_cursor("default"); // Lastly, let the plugins handle what happens on drag end. emit(&data); view_held_in_place = false; on_view_unmap.disconnect(); this->tentative_grab_position = {}; } void set_scale(double new_scale) { for (auto& view : all_views) { view.transformer->scale_factor.animate(new_scale); } } bool is_view_held_in_place() { return view_held_in_place; } // View currently being moved. wayfire_toplevel_view view; // Output where the action is happening. wf::output_t *current_output = NULL; private: // All views being dragged, more than one in case of join_views. std::vector all_views; // Current parameters drag_options_t params; // View is held in place, waiting for snap-off bool view_held_in_place = false; std::shared_ptr render_node; void update_current_output(wf::point_t grab) { wf::pointf_t origin = {1.0 * grab.x, 1.0 * grab.y}; auto output = wf::get_core().output_layout->get_output_coords_at(origin, origin); if (output != current_output) { if (current_output) { current_output->render->rem_effect(&on_pre_frame); } drag_focus_output_signal data; data.previous_focus_output = current_output; current_output = output; data.focus_output = output; wf::get_core().seat->focus_output(output); emit(&data); if (output) { current_output->render->add_effect(&on_pre_frame, OUTPUT_EFFECT_PRE); } } } wf::effect_hook_t on_pre_frame = [=] () { for (auto& v : this->all_views) { if (v.transformer->scale_factor.running()) { v.view->damage(); } } }; wf::signal::connection_t on_view_unmap = [=] (auto *ev) { handle_input_released(); }; }; /** * Move the view to the target output and put it at the coordinates of the grab. * Also take into account view's fullscreen and tiled state. * * Unmapped views are ignored. */ inline void adjust_view_on_output(drag_done_signal *ev) { // Any one of the views that are being dragged. // They are all part of the same view tree. auto parent = get_toplevel(ev->main_view); if (!parent->is_mapped()) { return; } if (parent->get_output() != ev->focused_output) { move_view_to_output(parent, ev->focused_output, false); } // Calculate the position we're leaving the view on auto output_delta = -wf::origin(ev->focused_output->get_layout_geometry()); auto grab = ev->grab_position + output_delta; auto output_geometry = ev->focused_output->get_relative_geometry(); auto current_ws = ev->focused_output->wset()->get_current_workspace(); wf::point_t target_ws{ (int)std::floor(1.0 * grab.x / output_geometry.width), (int)std::floor(1.0 * grab.y / output_geometry.height), }; target_ws = target_ws + current_ws; auto gsize = ev->focused_output->wset()->get_workspace_grid_size(); target_ws.x = wf::clamp(target_ws.x, 0, gsize.width - 1); target_ws.y = wf::clamp(target_ws.y, 0, gsize.height - 1); // view to focus at the end of drag auto focus_view = ev->main_view; for (auto& v : ev->all_views) { if (!v.view->is_mapped()) { // Maybe some dialog got unmapped continue; } auto bbox = wf::view_bounding_box_up_to(v.view, "wobbly"); auto wm = v.view->get_geometry(); wf::point_t wm_offset = wf::origin(wm) + -wf::origin(bbox); bbox = wf::move_drag::find_geometry_around( wf::dimensions(bbox), grab, v.relative_grab); wf::point_t target = wf::origin(bbox) + wm_offset; v.view->move(target.x, target.y); if (v.view->pending_fullscreen()) { wf::get_core().default_wm->fullscreen_request(v.view, ev->focused_output, true, target_ws); } else if (v.view->pending_tiled_edges()) { wf::get_core().default_wm->tile_request(v.view, v.view->pending_tiled_edges(), target_ws); } // check focus timestamp and select the last focused view to (re)focus if (get_focus_timestamp(v.view) > get_focus_timestamp(focus_view)) { focus_view = v.view; } } // Ensure that every view is visible on parent's main workspace for (auto& v : parent->enumerate_views()) { ev->focused_output->wset()->move_to_workspace(v, target_ws); } wf::get_core().default_wm->focus_raise_view(focus_view); } /** * Adjust the view's state after snap-off. */ inline void adjust_view_on_snap_off(wayfire_toplevel_view view) { if (view->pending_tiled_edges() && !view->pending_fullscreen()) { wf::get_core().default_wm->tile_request(view, 0); } } } } wayfire-0.8.1/plugins/common/wayfire/plugins/common/preview-indication.hpp000066400000000000000000000110001457431457600270770ustar00rootroot00000000000000#include #include #include #include #include "geometry-animation.hpp" #include "wayfire/scene-operations.hpp" #include "wayfire/view.hpp" #include #include namespace wf { using namespace wf::animation; class preview_indication_animation_t : public geometry_animation_t { public: using geometry_animation_t::geometry_animation_t; timed_transition_t alpha{*this}; }; /** * A view which can be used to show previews for different actions on the * screen, for ex. when snapping a view */ class preview_indication_t : public std::enable_shared_from_this { wf::effect_hook_t pre_paint; wf::output_t *output; preview_indication_animation_t animation; bool should_close = false; /* Default colors */ const wf::option_wrapper_t base_color; const wf::option_wrapper_t base_border; const wf::option_wrapper_t base_border_w; std::shared_ptr _self_reference; public: std::shared_ptr view; /** * Create a new indication preview on the indicated output. * * @param start_geometry The geometry the preview should have, relative to * the output */ preview_indication_t(wf::geometry_t start_geometry, wf::output_t *output, const std::string& prefix) : animation(wf::create_option(200)), base_color(prefix + "/preview_base_color"), base_border(prefix + "/preview_base_border"), base_border_w(prefix + "/preview_border_width") { animation.set_start(start_geometry); animation.set_end(start_geometry); animation.alpha.set(0, 1); this->output = output; pre_paint = [=] () { update_animation(); }; output->render->add_effect(&pre_paint, wf::OUTPUT_EFFECT_PRE); view = color_rect_view_t::create(VIEW_ROLE_DESKTOP_ENVIRONMENT, output, wf::scene::layer::TOP); view->set_color(base_color); view->set_border_color(base_border); view->set_border(base_border_w); } /** A convenience wrapper around the full version */ preview_indication_t(wf::point_t start, wf::output_t *output, const std::string & prefix) : preview_indication_t(wf::geometry_t{start.x, start.y, 1, 1}, output, prefix) {} /** * Animate the preview to the given target geometry and alpha. * * @param close Whether the view should be closed when the target is * reached. */ void set_target_geometry(wf::geometry_t target, float alpha, bool close = false) { animation.x.restart_with_end(target.x); animation.y.restart_with_end(target.y); animation.width.restart_with_end(target.width); animation.height.restart_with_end(target.height); animation.alpha.restart_with_end(alpha); animation.start(); this->should_close = close; if (should_close) { // Take a reference until we finally close the view _self_reference = shared_from_this(); } } /** * A wrapper around set_target_geometry(wf::geometry_t, double, bool) */ void set_target_geometry(wf::point_t point, double alpha, bool should_close = false) { return set_target_geometry({point.x, point.y, 1, 1}, alpha, should_close); } virtual ~preview_indication_t() { if (this->output) { this->output->render->rem_effect(&pre_paint); } } protected: /** Update the current state */ void update_animation() { wf::geometry_t current = animation; if (current != view->get_geometry()) { view->set_geometry(current); } double alpha = animation.alpha; auto cur_color = view->get_color(); auto cur_border_color = view->get_border_color(); if (base_color.value().a * alpha != cur_color.a) { cur_color.a = alpha * base_color.value().a; cur_border_color.a = alpha * base_border.value().a; view->set_color(cur_color); view->set_border_color(cur_border_color); } /* The end of unmap animation, just exit */ if (!animation.running() && should_close) { view->close(); view->damage(); _self_reference.reset(); } } }; } wayfire-0.8.1/plugins/common/wayfire/plugins/common/shared-core-data.hpp000066400000000000000000000036361457431457600264220ustar00rootroot00000000000000#pragma once #include #include namespace wf { /** * The purpose of shared is to allow multiple plugins or plugin instances to * have shared global custom data. * * While this is already possible if the shared data is stored as custom data on * `wf::get_core()`, the classes here provide convenient wrappers for managing * the lifetime of the shared data by utilizing RAII. */ namespace shared_data { namespace detail { /** Implementation detail: the actual data stored in core. */ template class shared_data_t : public wf::custom_data_t { public: T data; int32_t use_count = 0; }; } /** * A pointer to shared data which holds a reference to it (similar to * std::shared_ptr). Once the last reference is destroyed, data will be freed * from core. */ template class ref_ptr_t { public: ref_ptr_t() { update_use_count(+1); this->data = &wf::get_core().get_data_safe>()->data; } ref_ptr_t(const ref_ptr_t& other) { this->data = other.data; update_use_count(+1); } ref_ptr_t& operator =(const ref_ptr_t& other) { this->data = other.data; update_use_count(+1); } ref_ptr_t(ref_ptr_t&& other) = default; ref_ptr_t& operator =(ref_ptr_t&& other) = default; ~ref_ptr_t() { update_use_count(-1); } T *get() { return data; } T*operator ->() { return data; } private: // Update the use count, and delete data if necessary. void update_use_count(int32_t delta) { auto instance = wf::get_core().get_data_safe>(); instance->use_count += delta; if (instance->use_count <= 0) { wf::get_core().erase_data>(); } } // Pointer to the global data T *data; }; } } wayfire-0.8.1/plugins/common/wayfire/plugins/common/simple-texture.hpp000066400000000000000000000023231457431457600262760ustar00rootroot00000000000000#pragma once #include namespace wf { struct simple_texture_t { GLuint tex = -1; int width = 0; int height = 0; /** * Destroy the GL texture. * This will call OpenGL::render_begin()/end() internally. */ void release() { if (this->tex == (GLuint) - 1) { return; } OpenGL::render_begin(); GL_CALL(glDeleteTextures(1, &tex)); OpenGL::render_end(); this->tex = -1; } simple_texture_t() = default; /** Auto-release the texture when the object is destroyed */ ~simple_texture_t() { release(); } simple_texture_t(const simple_texture_t &) = delete; simple_texture_t& operator =(const simple_texture_t&) = delete; simple_texture_t(simple_texture_t && o) noexcept : tex(o.tex), width(o.width), height(o.height) { o.tex = (GLuint) - 1; } simple_texture_t& operator =(simple_texture_t&& o) noexcept { if (&o == this) { return *this; } release(); tex = o.tex; width = o.width; height = o.height; o.tex = (GLuint) - 1; return *this; } }; } wayfire-0.8.1/plugins/common/wayfire/plugins/common/util.hpp000066400000000000000000000065741457431457600243000ustar00rootroot00000000000000// A collection of small utility functions that plugins use. // FIXME: consider splitting into multiple files as util functions accumulate. #pragma once #include "wayfire/core.hpp" #include "wayfire/geometry.hpp" #include "wayfire/scene.hpp" #include "wayfire/view.hpp" #include "wayfire/output.hpp" #include "wayfire/toplevel-view.hpp" #include "wayfire/workspace-set.hpp" #include namespace wf { inline uint64_t get_focus_timestamp(wayfire_view view) { const auto& node = view->get_surface_root_node(); return node->keyboard_interaction().last_focus_timestamp; } template inline std::shared_ptr ensure_view_transformer( wayfire_view view, int z_order, TransformerArgs... args) { auto trmanager = view->get_transformed_node(); auto transformer = trmanager->get_transformer(); if (!transformer) { transformer = std::make_shared(args...); trmanager->add_transformer(transformer, z_order); } return transformer; } template inline std::shared_ptr ensure_named_transformer( wayfire_view view, int z_order, std::string name, TransformerArgs... args) { auto trmanager = view->get_transformed_node(); auto transformer = trmanager->get_transformer(name); if (!transformer) { transformer = std::make_shared(args...); trmanager->add_transformer(transformer, z_order, name); } return transformer; } template inline wf::geometry_t view_bounding_box_up_to(wayfire_view view, std::string name = typeid(Transformer).name()) { auto transformer = view->get_transformed_node()->get_transformer(name); if (transformer) { return transformer->get_children_bounding_box(); } else { return view->get_transformed_node()->get_bounding_box(); } } /** * Find the topmost view on the given coordinates on the given output, bypassing any overlays / input grabs. */ inline wayfire_toplevel_view find_output_view_at(wf::output_t *output, const wf::pointf_t& coords) { for (auto& output_node : wf::collect_output_nodes(wf::get_core().scene(), output)) { auto as_output = std::dynamic_pointer_cast(output_node); if (!as_output || (as_output->get_output() != output) || !as_output->is_enabled()) { continue; } // We start the search directly from the output node's children. This is because the output nodes // usually reject all queries outside of their current visible geometry, but we want to be able to // query views from all workspaces, not just the current (and the only visible) one. for (auto& ch : output_node->get_children()) { if (!ch->is_enabled()) { continue; } auto isec = ch->find_node_at(coords); auto node = isec ? isec->node.get() : nullptr; if (auto view = wf::toplevel_cast(wf::node_to_view(node))) { if (view->get_wset() == output->wset()) { return view; } } if (node) { return nullptr; } } } return nullptr; } } wayfire-0.8.1/plugins/common/wayfire/plugins/common/workspace-wall.hpp000066400000000000000000000501721457431457600262470ustar00rootroot00000000000000#pragma once #include "wayfire/workspace-set.hpp" // IWYU pragma: keep #include #include #include "wayfire/core.hpp" #include "wayfire/geometry.hpp" #include "wayfire/opengl.hpp" #include "wayfire/region.hpp" #include "wayfire/scene-input.hpp" #include "wayfire/scene-operations.hpp" #include "wayfire/scene-render.hpp" #include "wayfire/scene.hpp" #include "wayfire/signal-provider.hpp" #include "wayfire/workspace-stream.hpp" #include "wayfire/output.hpp" namespace wf { /** * When the workspace wall is rendered via a render hook, the frame event * is emitted on each frame. * * The target framebuffer is passed as signal data. */ struct wall_frame_event_t { const wf::render_target_t& target; wall_frame_event_t(const wf::render_target_t& t) : target(t) {} }; /** * A helper class to render workspaces arranged in a grid. */ class workspace_wall_t : public wf::signal::provider_t { public: /** * Create a new workspace wall on the given output. */ workspace_wall_t(wf::output_t *_output) : output(_output) { this->viewport = get_wall_rectangle(); } ~workspace_wall_t() { stop_output_renderer(false); } /** * Set the color of the background outside of workspaces. * * @param color The new background color. */ void set_background_color(const wf::color_t& color) { this->background_color = color; } /** * Set the size of the gap between adjacent workspaces, both horizontally * and vertically. * * @param size The new gap size, in pixels. */ void set_gap_size(int size) { this->gap_size = size; } /** * Set which part of the workspace wall to render. * * If the output has effective resolution WxH and the gap size is G, then a * workspace with coordinates (i, j) has geometry * {i * (W + G), j * (H + G), W, H}. * * All other regions are painted with the background color. * * @param viewport_geometry The part of the workspace wall to render. */ void set_viewport(const wf::geometry_t& viewport_geometry) { this->viewport = viewport_geometry; if (render_node) { scene::damage_node(this->render_node, this->render_node->get_bounding_box()); } } /** * Render the selected viewport on the framebuffer. * * @param fb The framebuffer to render on. * @param geometry The rectangle in fb to draw to, in the same coordinate * system as the framebuffer's geometry. */ void render_wall(const wf::render_target_t& fb, const wf::region_t& damage) { wall_frame_event_t data{fb}; this->emit(&data); } /** * Register a render hook and paint the whole output as a desktop wall * with the set parameters. */ void start_output_renderer() { wf::dassert(render_node == nullptr, "Starting workspace-wall twice?"); render_node = std::make_shared(this); scene::add_front(wf::get_core().scene(), render_node); } /** * Stop repainting the whole output. * * @param reset_viewport If true, the viewport will be reset to {0, 0, 0, 0} * and thus all workspace streams will be stopped. */ void stop_output_renderer(bool reset_viewport) { if (!render_node) { return; } scene::remove_child(render_node); render_node = nullptr; if (reset_viewport) { set_viewport({0, 0, 0, 0}); } } /** * Calculate the geometry of a particular workspace, as described in * set_viewport(). * * @param ws The workspace whose geometry is to be computed. */ wf::geometry_t get_workspace_rectangle(const wf::point_t& ws) const { auto size = this->output->get_screen_size(); return { ws.x * (size.width + gap_size), ws.y * (size.height + gap_size), size.width, size.height }; } /** * Calculate the whole workspace wall region, including gaps around it. */ wf::geometry_t get_wall_rectangle() const { auto size = this->output->get_screen_size(); auto workspace_size = this->output->wset()->get_workspace_grid_size(); return { -gap_size, -gap_size, workspace_size.width * (size.width + gap_size) + gap_size, workspace_size.height * (size.height + gap_size) + gap_size }; } /** * Get/set the dimming factor for a given workspace. */ void set_ws_dim(const wf::point_t& ws, float value) { render_colors[{ws.x, ws.y}] = value; if (render_node) { scene::damage_node(render_node, render_node->get_bounding_box()); } } protected: wf::output_t *output; wf::color_t background_color = {0, 0, 0, 0}; int gap_size = 0; wf::geometry_t viewport = {0, 0, 0, 0}; std::map, float> render_colors; float get_color_for_workspace(wf::point_t ws) { auto it = render_colors.find({ws.x, ws.y}); if (it == render_colors.end()) { return 1.0; } return it->second; } /** * Get a list of workspaces visible in the viewport. */ std::vector get_visible_workspaces(wf::geometry_t viewport) const { std::vector visible; auto wsize = output->wset()->get_workspace_grid_size(); for (int i = 0; i < wsize.width; i++) { for (int j = 0; j < wsize.height; j++) { if (viewport & get_workspace_rectangle({i, j})) { visible.push_back({i, j}); } } } return visible; } protected: template using per_workspace_map_t = std::map>; class workspace_wall_node_t : public scene::node_t { class wwall_render_instance_t : public scene::render_instance_t { workspace_wall_node_t *self; per_workspace_map_t> instances; scene::damage_callback push_damage; wf::signal::connection_t on_wall_damage = [=] (scene::node_damage_signal *ev) { push_damage(ev->region); }; wf::geometry_t get_workspace_rect(wf::point_t ws) { auto output_size = self->wall->output->get_screen_size(); return { .x = ws.x * (output_size.width + self->wall->gap_size), .y = ws.y * (output_size.height + self->wall->gap_size), .width = output_size.width, .height = output_size.height, }; } public: wwall_render_instance_t(workspace_wall_node_t *self, scene::damage_callback push_damage) { this->self = self; this->push_damage = push_damage; self->connect(&on_wall_damage); for (int i = 0; i < (int)self->workspaces.size(); i++) { for (int j = 0; j < (int)self->workspaces[i].size(); j++) { auto push_damage_child = [=] (const wf::region_t& damage) { // Store the damage because we'll have to update the buffers self->aux_buffer_damage[i][j] |= damage; wf::region_t our_damage; for (auto& rect : damage) { wf::geometry_t box = wlr_box_from_pixman_box(rect); box = box + wf::origin(get_workspace_rect({i, j})); auto A = self->wall->viewport; auto B = self->get_bounding_box(); our_damage |= scale_box(A, B, box); } // Also damage the 'screen' after transforming damage push_damage(our_damage); }; self->workspaces[i][j]->gen_render_instances(instances[i][j], push_damage_child, self->wall->output); } } } static int damage_sum_area(const wf::region_t& damage) { int sum = 0; for (const auto& rect : damage) { sum += (rect.y2 - rect.y1) * (rect.x2 - rect.x1); } return sum; } bool consider_rescale_workspace_buffer(int i, int j, wf::region_t& visible_damage) { // In general, when rendering the auxilliary buffers for each workspace, we can render the // workspace thumbnails in a lower resolution, because at the end they are shown scaled. // This helps with performance and uses less GPU power. // // However, the situation is tricky because during the Expo animation the optimal render // scale constantly changes. Thus, in some cases it is actually far from optimal to rescale // on every frame - it is often better to just keep the buffers from the old scale. // // Nonetheless, we need to make sure to rescale when this makes sense, and to avoid visual // artifacts. auto bbox = self->workspaces[i][j]->get_bounding_box(); const float render_scale = std::max( 1.0 * bbox.width / self->wall->viewport.width, 1.0 * bbox.height / self->wall->viewport.height); const float current_scale = self->aux_buffer_current_scale[i][j]; // Avoid keeping a low resolution if we are going up in the scale (for example, expo exit // animation) and we're close to the 1.0 scale. Otherwise, we risk popping artifacts as we // suddenly switch from low to high resolution. const bool rescale_magnification = (render_scale > 0.5) && (render_scale > current_scale * 1.1); // In general, it is worth changing the buffer scale if we have a lot of damage to the old // buffer, so that for ex. a full re-scale is actually cheaper than repaiting the old buffer. // This could easily happen for example if we have a video player during Expo start animation. const int repaint_cost_current_scale = damage_sum_area(visible_damage) * (current_scale * current_scale); const int repaint_rescale_cost = (bbox.width * bbox.height) * (render_scale * render_scale); if ((repaint_cost_current_scale > repaint_rescale_cost) || rescale_magnification) { self->aux_buffer_current_scale[i][j] = render_scale; self->aux_buffers[i][j].subbuffer = wf::geometry_t{ 0, 0, int(std::ceil(render_scale * self->aux_buffers[i][j].viewport_width)), int(std::ceil(render_scale * self->aux_buffers[i][j].viewport_height)), }; self->aux_buffer_damage[i][j] |= self->workspaces[i][j]->get_bounding_box(); return true; } return false; } void schedule_instructions( std::vector& instructions, const wf::render_target_t& target, wf::region_t& damage) override { // Update workspaces in a render pass for (int i = 0; i < (int)self->workspaces.size(); i++) { for (int j = 0; j < (int)self->workspaces[i].size(); j++) { const auto ws_bbox = self->wall->get_workspace_rectangle({i, j}); const auto visible_box = geometry_intersection(self->wall->viewport, ws_bbox) - wf::origin(ws_bbox); wf::region_t visible_damage = self->aux_buffer_damage[i][j] & visible_box; if (consider_rescale_workspace_buffer(i, j, visible_damage)) { visible_damage |= visible_box; } if (!visible_damage.empty()) { scene::render_pass_params_t params; params.instances = &instances[i][j]; params.damage = std::move(visible_damage); params.reference_output = self->wall->output; params.target = self->aux_buffers[i][j]; scene::run_render_pass(params, scene::RPASS_EMIT_SIGNALS); self->aux_buffer_damage[i][j] ^= visible_damage; } } } // Render the wall instructions.push_back(scene::render_instruction_t{ .instance = this, .target = target, .damage = damage & self->get_bounding_box(), }); damage ^= self->get_bounding_box(); } static gl_geometry scale_fbox(wf::geometry_t A, wf::geometry_t B, wf::geometry_t box) { const float px = 1.0 * (box.x - A.x) / A.width; const float py = 1.0 * (box.y - A.y) / A.height; const float px2 = 1.0 * (box.x + box.width - A.x) / A.width; const float py2 = 1.0 * (box.y + box.height - A.y) / A.height; return gl_geometry{ B.x + B.width * px, B.y + B.height * py, B.x + B.width * px2, B.y + B.height * py2, }; } void render(const wf::render_target_t& target, const wf::region_t& region) override { OpenGL::render_begin(target); for (auto& box : region) { target.logic_scissor(wlr_box_from_pixman_box(box)); OpenGL::clear(self->wall->background_color); for (int i = 0; i < (int)self->workspaces.size(); i++) { for (int j = 0; j < (int)self->workspaces[i].size(); j++) { auto box = get_workspace_rect({i, j}); auto A = self->wall->viewport; auto B = self->get_bounding_box(); gl_geometry render_geometry = scale_fbox(A, B, box); auto& buffer = self->aux_buffers[i][j]; float dim = self->wall->get_color_for_workspace({i, j}); const glm::vec4 color = glm::vec4(dim, dim, dim, 1.0); if (!buffer.subbuffer.has_value()) { OpenGL::render_transformed_texture({buffer.tex}, render_geometry, {}, target.get_orthographic_projection(), color); } else { // The 0.999f come from trying to avoid floating-point artifacts const gl_geometry tex_geometry = { 0.0f, 1.0f - 0.999f * buffer.subbuffer->height / buffer.viewport_height, 0.999f * buffer.subbuffer->width / buffer.viewport_width, 1.0f, }; OpenGL::render_transformed_texture({buffer.tex}, render_geometry, tex_geometry, target.get_orthographic_projection(), color, OpenGL::TEXTURE_USE_TEX_GEOMETRY); } } } } OpenGL::render_end(); self->wall->render_wall(target, region); } void compute_visibility(wf::output_t *output, wf::region_t& visible) override { for (int i = 0; i < (int)self->workspaces.size(); i++) { for (int j = 0; j < (int)self->workspaces[i].size(); j++) { wf::region_t ws_region = self->workspaces[i][j]->get_bounding_box(); for (auto& ch : this->instances[i][j]) { ch->compute_visibility(output, ws_region); } } } } }; public: workspace_wall_node_t(workspace_wall_t *wall) : node_t(false) { this->wall = wall; auto [w, h] = wall->output->wset()->get_workspace_grid_size(); workspaces.resize(w); for (int i = 0; i < w; i++) { for (int j = 0; j < h; j++) { auto node = std::make_shared( wall->output, wf::point_t{i, j}); workspaces[i].push_back(node); aux_buffers[i][j].geometry = workspaces[i][j]->get_bounding_box(); aux_buffers[i][j].scale = wall->output->handle->scale; aux_buffers[i][j].wl_transform = WL_OUTPUT_TRANSFORM_NORMAL; aux_buffers[i][j].transform = get_output_matrix_from_transform( aux_buffers[i][j].wl_transform); auto size = aux_buffers[i][j].framebuffer_box_from_geometry_box(aux_buffers[i][j].geometry); OpenGL::render_begin(); aux_buffers[i][j].allocate(size.width, size.height); OpenGL::render_end(); aux_buffer_damage[i][j] |= aux_buffers[i][j].geometry; aux_buffer_current_scale[i][j] = 1.0; } } } ~workspace_wall_node_t() { OpenGL::render_begin(); for (auto& [_, buffers] : aux_buffers) { for (auto& [_, buffer] : buffers) { buffer.release(); } } OpenGL::render_end(); } virtual void gen_render_instances( std::vector& instances, scene::damage_callback push_damage, wf::output_t *shown_on) override { if (shown_on != this->wall->output) { return; } instances.push_back(std::make_unique( this, push_damage)); } std::string stringify() const override { return "workspace-wall " + stringify_flags(); } wf::geometry_t get_bounding_box() override { return wall->output->get_layout_geometry(); } private: workspace_wall_t *wall; std::vector>> workspaces; // Buffers keeping the contents of almost-static workspaces per_workspace_map_t aux_buffers; // Damage accumulated for those buffers per_workspace_map_t aux_buffer_damage; // Current rendering scale for the workspace per_workspace_map_t aux_buffer_current_scale; }; std::shared_ptr render_node; }; } wayfire-0.8.1/plugins/cube/000077500000000000000000000000001457431457600156055ustar00rootroot00000000000000wayfire-0.8.1/plugins/cube/cube-background.hpp000066400000000000000000000005721457431457600213550ustar00rootroot00000000000000#ifndef WF_CUBE_BACKGROUND_HPP #define WF_CUBE_BACKGROUND_HPP #include #include "cube.hpp" class wf_cube_background_base { public: virtual void render_frame(const wf::render_target_t& fb, wf_cube_animation_attribs& attribs) = 0; virtual ~wf_cube_background_base() = default; }; #endif /* end of include guard: WF_CUBE_BACKGROUND_HPP */ wayfire-0.8.1/plugins/cube/cube-control-signal.hpp000066400000000000000000000011711457431457600221650ustar00rootroot00000000000000#ifndef CUBE_CONTROL_SIGNAL #define CUBE_CONTROL_SIGNAL #include /* A private signal, currently shared by idle & cube * * It is used to rotate the cube from the idle plugin as a screensaver. */ /* Rotate cube to given angle and zoom level */ struct cube_control_signal { double angle; // cube rotation in radians double zoom; // 1.0 means 100%; increase value to zoom double ease; // for cube deformation; range 0.0-1.0 bool last_frame; // ends cube animation if true bool carried_out; // false if cube is disabled }; #endif /* end of include guard: CUBE_CONTROL_SIGNAL */ wayfire-0.8.1/plugins/cube/cube.cpp000066400000000000000000000643201457431457600172340ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include #include #include #include "plugins/ipc/ipc-activator.hpp" #include #include #include "cube.hpp" #include "simple-background.hpp" #include "skydome.hpp" #include "cubemap.hpp" #include "cube-control-signal.hpp" #include "wayfire/region.hpp" #include "wayfire/scene-render.hpp" #include "wayfire/scene.hpp" #include "wayfire/signal-definitions.hpp" #define Z_OFFSET_NEAR 0.89567f #define Z_OFFSET_FAR 2.00000f #define ZOOM_MAX 10.0f #define ZOOM_MIN 0.1f #ifdef USE_GLES32 #include #endif #include "shaders.tpp" #include "shaders-3-2.tpp" class wayfire_cube : public wf::per_output_plugin_instance_t, public wf::pointer_interaction_t { class cube_render_node_t : public wf::scene::node_t { class cube_render_instance_t : public wf::scene::render_instance_t { cube_render_node_t *self; wf::scene::damage_callback push_damage; std::vector> ws_instances; std::vector ws_damage; std::vector framebuffers; wf::signal::connection_t on_cube_damage = [=] (wf::scene::node_damage_signal *ev) { push_damage(ev->region); }; public: cube_render_instance_t(cube_render_node_t *self, wf::scene::damage_callback push_damage) { this->self = self; this->push_damage = push_damage; self->connect(&on_cube_damage); ws_damage.resize(self->workspaces.size()); framebuffers.resize(self->workspaces.size()); ws_instances.resize(self->workspaces.size()); for (int i = 0; i < (int)self->workspaces.size(); i++) { auto push_damage_child = [=] (const wf::region_t& damage) { ws_damage[i] |= damage; push_damage(self->get_bounding_box()); }; self->workspaces[i]->gen_render_instances(ws_instances[i], push_damage_child, self->cube->output); ws_damage[i] |= self->workspaces[i]->get_bounding_box(); } } ~cube_render_instance_t() { OpenGL::render_begin(); for (auto& buf : framebuffers) { buf.release(); } OpenGL::render_end(); } void schedule_instructions( std::vector& instructions, const wf::render_target_t& target, wf::region_t& damage) override { instructions.push_back(wf::scene::render_instruction_t{ .instance = this, .target = target, .damage = damage & self->get_bounding_box(), }); auto bbox = self->get_bounding_box(); damage ^= bbox; } void render(const wf::render_target_t& target, const wf::region_t& region, const std::any& tag) override { for (int i = 0; i < (int)ws_instances.size(); i++) { framebuffers[i].geometry = self->workspaces[i]->get_bounding_box(); framebuffers[i].scale = self->cube->output->handle->scale; framebuffers[i].wl_transform = WL_OUTPUT_TRANSFORM_NORMAL; framebuffers[i].transform = get_output_matrix_from_transform( framebuffers[i].wl_transform); auto size = framebuffers[i].framebuffer_box_from_geometry_box(framebuffers[i].geometry); OpenGL::render_begin(); framebuffers[i].allocate(size.width, size.height); OpenGL::render_end(); wf::scene::render_pass_params_t params; params.instances = &ws_instances[i]; params.damage = ws_damage[i]; params.reference_output = self->cube->output; params.target = framebuffers[i]; wf::scene::run_render_pass(params, wf::scene::RPASS_CLEAR_BACKGROUND | wf::scene::RPASS_EMIT_SIGNALS); ws_damage[i].clear(); } self->cube->render(target.translated(-wf::origin(self->get_bounding_box())), framebuffers); } void compute_visibility(wf::output_t *output, wf::region_t& visible) override { for (int i = 0; i < (int)self->workspaces.size(); i++) { wf::region_t ws_region = self->workspaces[i]->get_bounding_box(); for (auto& ch : this->ws_instances[i]) { ch->compute_visibility(output, ws_region); } } } }; public: cube_render_node_t(wayfire_cube *cube) : node_t(false) { this->cube = cube; auto w = cube->output->wset()->get_workspace_grid_size().width; auto y = cube->output->wset()->get_current_workspace().y; for (int i = 0; i < w; i++) { auto node = std::make_shared(cube->output, wf::point_t{i, y}); workspaces.push_back(node); } } virtual void gen_render_instances( std::vector& instances, wf::scene::damage_callback push_damage, wf::output_t *shown_on) { if (shown_on != this->cube->output) { return; } instances.push_back(std::make_unique( this, push_damage)); } wf::geometry_t get_bounding_box() { return cube->output->get_layout_geometry(); } private: std::vector> workspaces; wayfire_cube *cube; }; std::unique_ptr input_grab; std::shared_ptr render_node; wf::option_wrapper_t XVelocity{"cube/speed_spin_horiz"}, YVelocity{"cube/speed_spin_vert"}, ZVelocity{"cube/speed_zoom"}; wf::option_wrapper_t zoom_opt{"cube/zoom"}; /* the Z camera distance so that (-1, 1) is mapped to the whole screen * for the given FOV */ float identity_z_offset; OpenGL::program_t program; wf_cube_animation_attribs animation; wf::option_wrapper_t use_light{"cube/light"}; wf::option_wrapper_t use_deform{"cube/deform"}; std::string last_background_mode; std::unique_ptr background; wf::option_wrapper_t background_mode{"cube/background_mode"}; void reload_background() { if (last_background_mode == (std::string)background_mode) { return; } last_background_mode = background_mode; if (last_background_mode == "simple") { background = std::make_unique(); } else if (last_background_mode == "skydome") { background = std::make_unique(output); } else if (last_background_mode == "cubemap") { background = std::make_unique(); } else { LOGE("cube: Unrecognized background mode %s. Using default \"simple\"", last_background_mode.c_str()); background = std::make_unique(); } } bool tessellation_support; int get_num_faces() { return output->wset()->get_workspace_grid_size().width; } wf::plugin_activation_data_t grab_interface{ .name = "cube", .capabilities = wf::CAPABILITY_MANAGE_COMPOSITOR, .cancel = [=] () { deactivate(); }, }; public: void init() override { input_grab = std::make_unique("cube", output, nullptr, this, nullptr); input_grab->set_wants_raw_input(true); animation.cube_animation.offset_y.set(0, 0); animation.cube_animation.offset_z.set(0, 0); animation.cube_animation.rotation.set(0, 0); animation.cube_animation.zoom.set(1, 1); animation.cube_animation.ease_deformation.set(0, 0); animation.cube_animation.start(); reload_background(); output->connect(&on_cube_control); OpenGL::render_begin(); load_program(); OpenGL::render_end(); } void handle_pointer_button(const wlr_pointer_button_event& event) override { if (event.state == WLR_BUTTON_RELEASED) { input_ungrabbed(); } } void handle_pointer_axis(const wlr_pointer_axis_event& event) override { if (event.orientation == WLR_AXIS_ORIENTATION_VERTICAL) { pointer_scrolled(event.delta); } } void load_program() { #ifdef USE_GLES32 std::string ext_string(reinterpret_cast(glGetString(GL_EXTENSIONS))); tessellation_support = ext_string.find(std::string("GL_EXT_tessellation_shader")) != std::string::npos; #else tessellation_support = false; #endif if (!tessellation_support) { program.set_simple(OpenGL::compile_program(cube_vertex_2_0, cube_fragment_2_0)); } else { #ifdef USE_GLES32 auto id = GL_CALL(glCreateProgram()); GLuint vss, fss, tcs, tes, gss; vss = OpenGL::compile_shader(cube_vertex_3_2, GL_VERTEX_SHADER); fss = OpenGL::compile_shader(cube_fragment_3_2, GL_FRAGMENT_SHADER); tcs = OpenGL::compile_shader(cube_tcs_3_2, GL_TESS_CONTROL_SHADER); tes = OpenGL::compile_shader(cube_tes_3_2, GL_TESS_EVALUATION_SHADER); gss = OpenGL::compile_shader(cube_geometry_3_2, GL_GEOMETRY_SHADER); GL_CALL(glAttachShader(id, vss)); GL_CALL(glAttachShader(id, tcs)); GL_CALL(glAttachShader(id, tes)); GL_CALL(glAttachShader(id, gss)); GL_CALL(glAttachShader(id, fss)); GL_CALL(glLinkProgram(id)); GL_CALL(glUseProgram(id)); GL_CALL(glDeleteShader(vss)); GL_CALL(glDeleteShader(fss)); GL_CALL(glDeleteShader(tcs)); GL_CALL(glDeleteShader(tes)); GL_CALL(glDeleteShader(gss)); program.set_simple(id); #endif } animation.projection = glm::perspective(45.0f, 1.f, 0.1f, 100.f); } wf::signal::connection_t on_cube_control = [=] (cube_control_signal *d) { rotate_and_zoom_cube(d->angle, d->zoom, d->ease, d->last_frame); d->carried_out = true; }; void rotate_and_zoom_cube(double angle, double zoom, double ease, bool last_frame) { if (last_frame) { deactivate(); return; } if (!activate()) { return; } float offset_z = identity_z_offset + Z_OFFSET_NEAR; animation.cube_animation.rotation.set(angle, angle); animation.cube_animation.zoom.set(zoom, zoom); animation.cube_animation.ease_deformation.set(ease, ease); animation.cube_animation.offset_y.set(0, 0); animation.cube_animation.offset_z.set(offset_z, offset_z); animation.cube_animation.start(); update_view_matrix(); output->render->schedule_redraw(); } /* Tries to initialize renderer, activate plugin, etc. */ bool activate() { if (output->is_plugin_active(grab_interface.name)) { return true; } if (!output->activate_plugin(&grab_interface)) { return false; } wf::get_core().connect(&on_motion_event); render_node = std::make_shared(this); wf::scene::add_front(wf::get_core().scene(), render_node); output->render->add_effect(&pre_hook, wf::OUTPUT_EFFECT_PRE); wf::get_core().hide_cursor(); input_grab->grab_input(wf::scene::layer::OVERLAY); auto wsize = output->wset()->get_workspace_grid_size(); animation.side_angle = 2 * M_PI / float(wsize.width); identity_z_offset = 0.5 / std::tan(animation.side_angle / 2); if (wsize.width == 1) { // tan(M_PI) is 0, so identity_z_offset is invalid identity_z_offset = 0.0f; } reload_background(); animation.cube_animation.offset_z.set(identity_z_offset + Z_OFFSET_NEAR, identity_z_offset + Z_OFFSET_NEAR); return true; } int calculate_viewport_dx_from_rotation() { float dx = -animation.cube_animation.rotation / animation.side_angle; return std::floor(dx + 0.5); } /* Disable custom rendering and deactivate plugin */ void deactivate() { if (!output->is_plugin_active(grab_interface.name)) { return; } wf::scene::remove_child(render_node); render_node = nullptr; output->render->rem_effect(&pre_hook); input_grab->ungrab_input(); output->deactivate_plugin(&grab_interface); wf::get_core().unhide_cursor(); on_motion_event.disconnect(); /* Figure out how much we have rotated and switch workspace */ int size = get_num_faces(); int dvx = calculate_viewport_dx_from_rotation(); auto cws = output->wset()->get_current_workspace(); int nvx = (cws.x + (dvx % size) + size) % size; output->wset()->set_workspace({nvx, cws.y}); /* We are finished with rotation, make sure the next time cube is used * it is properly reset */ animation.cube_animation.rotation.set(0, 0); } /* Sets attributes target to such values that the cube effect isn't visible, * i.e towards the starting(or ending) position * * It doesn't change rotation because that is different in different cases - * for example when moved by the keyboard or with a button grab */ void reset_attribs() { animation.cube_animation.zoom.restart_with_end(1.0); animation.cube_animation.offset_z.restart_with_end( identity_z_offset + Z_OFFSET_NEAR); animation.cube_animation.offset_y.restart_with_end(0); animation.cube_animation.ease_deformation.restart_with_end(0); } /* Start moving to a workspace to the left/right using the keyboard */ bool move_vp(int dir) { if (!activate()) { return false; } /* After the rotation is done, we want to exit cube and focus the target * workspace */ animation.in_exit = true; /* Set up rotation target to the next workspace in the given direction, * and reset other attribs */ reset_attribs(); animation.cube_animation.rotation.restart_with_end( animation.cube_animation.rotation.end - dir * animation.side_angle); animation.cube_animation.start(); update_view_matrix(); output->render->schedule_redraw(); return true; } /* Initiate with an button grab. */ bool input_grabbed() { if (!activate()) { return false; } /* Rotations, offset_y and zoom stay as they are now, as they have been * grabbed. * offset_z changes to the default one. * * We also need to make sure the cube gets deformed */ animation.in_exit = false; float current_rotation = animation.cube_animation.rotation; float current_offset_y = animation.cube_animation.offset_y; float current_zoom = animation.cube_animation.zoom; animation.cube_animation.rotation.set(current_rotation, current_rotation); animation.cube_animation.offset_y.set(current_offset_y, current_offset_y); animation.cube_animation.offset_z.restart_with_end( zoom_opt + identity_z_offset + Z_OFFSET_NEAR); animation.cube_animation.zoom.set(current_zoom, current_zoom); animation.cube_animation.ease_deformation.restart_with_end(1); animation.cube_animation.start(); update_view_matrix(); output->render->schedule_redraw(); // Let the button go to the input grab return false; } /* Mouse grab was released */ void input_ungrabbed() { animation.in_exit = true; /* Rotate cube so that selected workspace aligns with the output */ float current_rotation = animation.cube_animation.rotation; int dvx = calculate_viewport_dx_from_rotation(); animation.cube_animation.rotation.set(current_rotation, -dvx * animation.side_angle); /* And reset other attributes, again to align the workspace with the output * */ reset_attribs(); animation.cube_animation.start(); update_view_matrix(); output->render->schedule_redraw(); } /* Update the view matrix used in the next frame */ void update_view_matrix() { auto zoom_translate = glm::translate(glm::mat4(1.f), glm::vec3(0.f, 0.f, -animation.cube_animation.offset_z)); auto rotation = glm::rotate(glm::mat4(1.0), (float)animation.cube_animation.offset_y, glm::vec3(1., 0., 0.)); auto view = glm::lookAt(glm::vec3(0., 0., 0.), glm::vec3(0., 0., -animation.cube_animation.offset_z), glm::vec3(0., 1., 0.)); animation.view = zoom_translate * rotation * view; } glm::mat4 calculate_vp_matrix(const wf::render_target_t& dest) { float zoom_factor = animation.cube_animation.zoom; auto scale_matrix = glm::scale(glm::mat4(1.0), glm::vec3(1. / zoom_factor, 1. / zoom_factor, 1. / zoom_factor)); return dest.transform * animation.projection * animation.view * scale_matrix; } /* Calculate the base model matrix for the i-th side of the cube */ glm::mat4 calculate_model_matrix(int i) { const float angle = i * animation.side_angle + animation.cube_animation.rotation; auto rotation = glm::rotate(glm::mat4(1.0), angle, glm::vec3(0, 1, 0)); double additional_z = 0.0; // Special case: 2 faces // In this case, we need to make sure that the two faces are just // slightly moved away from each other, to avoid artifacts which can // happen if both sides are touching. if (get_num_faces() == 2) { additional_z = 1e-3; } auto translation = glm::translate(glm::mat4(1.0), glm::vec3(0, 0, identity_z_offset + additional_z)); return rotation * translation; } /* Render the sides of the cube, using the given culling mode - cw or ccw */ void render_cube(GLuint front_face, glm::mat4 fb_transform, const std::vector& buffers) { GL_CALL(glFrontFace(front_face)); static const GLuint indexData[] = {0, 1, 2, 0, 2, 3}; auto cws = output->wset()->get_current_workspace(); for (int i = 0; i < get_num_faces(); i++) { int index = (cws.x + i) % get_num_faces(); GL_CALL(glBindTexture(GL_TEXTURE_2D, buffers[index].tex)); auto model = calculate_model_matrix(i); program.uniformMatrix4f("model", model); if (tessellation_support) { #ifdef USE_GLES32 GL_CALL(glDrawElements(GL_PATCHES, 6, GL_UNSIGNED_INT, &indexData)); #endif } else { GL_CALL(glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, &indexData)); } } } void render(const wf::render_target_t& dest, const std::vector& buffers) { if (program.get_program_id(wf::TEXTURE_TYPE_RGBA) == 0) { load_program(); } OpenGL::render_begin(dest); GL_CALL(glClear(GL_DEPTH_BUFFER_BIT)); OpenGL::render_end(); background->render_frame(dest, animation); auto vp = calculate_vp_matrix(dest); OpenGL::render_begin(dest); program.use(wf::TEXTURE_TYPE_RGBA); GL_CALL(glEnable(GL_DEPTH_TEST)); GL_CALL(glDepthFunc(GL_LESS)); static GLfloat vertexData[] = { -0.5, 0.5, 0.5, 0.5, 0.5, -0.5, -0.5, -0.5 }; static GLfloat coordData[] = { 0.0f, 1.0f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 0.0f }; program.attrib_pointer("position", 2, 0, vertexData); program.attrib_pointer("uvPosition", 2, 0, coordData); program.uniformMatrix4f("VP", vp); if (tessellation_support) { program.uniform1i("deform", use_deform); program.uniform1i("light", use_light); program.uniform1f("ease", animation.cube_animation.ease_deformation); } /* We render the cube in two stages, based on winding. * By using two stages, we ensure that we first render the cube sides * that are on the back, and then we render those at the front, so we * don't have to use depth testing and we also can support alpha cube. */ GL_CALL(glEnable(GL_CULL_FACE)); render_cube(GL_CCW, dest.transform, buffers); render_cube(GL_CW, dest.transform, buffers); GL_CALL(glDisable(GL_CULL_FACE)); GL_CALL(glDisable(GL_DEPTH_TEST)); program.deactivate(); OpenGL::render_end(); } wf::effect_hook_t pre_hook = [=] () { update_view_matrix(); wf::scene::damage_node(render_node, render_node->get_bounding_box()); if (animation.cube_animation.running()) { output->render->schedule_redraw(); } else if (animation.in_exit) { deactivate(); } }; wf::signal::connection_t> on_motion_event = [=] (wf::input_event_signal *ev) { pointer_moved(ev->event); ev->event->delta_x = 0; ev->event->delta_y = 0; ev->event->unaccel_dx = 0; ev->event->unaccel_dy = 0; }; void pointer_moved(wlr_pointer_motion_event *ev) { if (animation.in_exit) { return; } double xdiff = ev->delta_x; double ydiff = ev->delta_y; animation.cube_animation.zoom.restart_with_end( animation.cube_animation.zoom.end); double current_off_y = animation.cube_animation.offset_y; double off_y = current_off_y + ydiff * YVelocity; off_y = wf::clamp(off_y, -1.5, 1.5); animation.cube_animation.offset_y.set(current_off_y, off_y); animation.cube_animation.offset_z.restart_with_end( animation.cube_animation.offset_z.end); float current_rotation = animation.cube_animation.rotation; animation.cube_animation.rotation.restart_with_end( current_rotation + xdiff * XVelocity); animation.cube_animation.ease_deformation.restart_with_end( animation.cube_animation.ease_deformation.end); animation.cube_animation.start(); output->render->schedule_redraw(); } void pointer_scrolled(double amount) { if (animation.in_exit) { return; } animation.cube_animation.offset_y.restart_with_end( animation.cube_animation.offset_y.end); animation.cube_animation.offset_z.restart_with_end( animation.cube_animation.offset_z.end); animation.cube_animation.rotation.restart_with_end( animation.cube_animation.rotation.end); animation.cube_animation.ease_deformation.restart_with_end( animation.cube_animation.ease_deformation.end); float target_zoom = animation.cube_animation.zoom; float start_zoom = target_zoom; target_zoom += std::min(std::pow(target_zoom, 1.5f), ZOOM_MAX) * amount * ZVelocity; target_zoom = std::min(std::max(target_zoom, ZOOM_MIN), ZOOM_MAX); animation.cube_animation.zoom.set(start_zoom, target_zoom); animation.cube_animation.start(); output->render->schedule_redraw(); } void fini() override { if (output->is_plugin_active(grab_interface.name)) { deactivate(); } OpenGL::render_begin(); program.free_resources(); OpenGL::render_end(); } }; class wayfire_cube_global : public wf::plugin_interface_t, public wf::per_output_tracker_mixin_t { wf::ipc_activator_t rotate_left{"cube/rotate_left"}; wf::ipc_activator_t rotate_right{"cube/rotate_right"}; wf::ipc_activator_t activate{"cube/activate"}; public: void init() override { this->init_output_tracking(); rotate_left.set_handler(rotate_left_cb); rotate_right.set_handler(rotate_right_cb); activate.set_handler(activate_cb); } void fini() override { this->fini_output_tracking(); } wf::ipc_activator_t::handler_t rotate_left_cb = [=] (wf::output_t *output, wayfire_view) { return this->output_instance[output]->move_vp(-1); }; wf::ipc_activator_t::handler_t rotate_right_cb = [=] (wf::output_t *output, wayfire_view) { return this->output_instance[output]->move_vp(+1); }; wf::ipc_activator_t::handler_t activate_cb = [=] (wf::output_t *output, wayfire_view) { return this->output_instance[output]->input_grabbed(); }; }; DECLARE_WAYFIRE_PLUGIN(wayfire_cube_global); wayfire-0.8.1/plugins/cube/cube.hpp000066400000000000000000000015371457431457600172420ustar00rootroot00000000000000#ifndef WF_CUBE_HPP #define WF_CUBE_HPP #include #include #include #include #include #define TEX_ERROR_FLAG_COLOR 0, 1, 0, 1 using namespace wf::animation; class cube_animation_t : public duration_t { public: using duration_t::duration_t; timed_transition_t offset_y{*this}; timed_transition_t offset_z{*this}; timed_transition_t rotation{*this}; timed_transition_t zoom{*this}; timed_transition_t ease_deformation{*this}; }; struct wf_cube_animation_attribs { wf::option_wrapper_t animation_duration{"cube/initial_animation"}; cube_animation_t cube_animation{animation_duration}; glm::mat4 projection, view; float side_angle; bool in_exit; }; #endif /* end of include guard: WF_CUBE_HPP */ wayfire-0.8.1/plugins/cube/cubemap-shaders.tpp000066400000000000000000000006651457431457600214040ustar00rootroot00000000000000static const char* cubemap_vertex = R"(#version 100 attribute mediump vec3 position; varying highp vec3 direction; uniform mat4 cubeMapMatrix; void main() { gl_Position = cubeMapMatrix * vec4(position, 1.0); direction = position; })"; static const char* cubemap_fragment = R"(#version 100 varying highp vec3 direction; uniform samplerCube smp; void main() { gl_FragColor = vec4(textureCube(smp, direction).xyz, 1); })"; wayfire-0.8.1/plugins/cube/cubemap.cpp000066400000000000000000000107171457431457600177330ustar00rootroot00000000000000#include "cubemap.hpp" #include #include #include #include #include "cubemap-shaders.tpp" wf_cube_background_cubemap::wf_cube_background_cubemap() { create_program(); reload_texture(); } wf_cube_background_cubemap::~wf_cube_background_cubemap() { OpenGL::render_begin(); program.free_resources(); GL_CALL(glDeleteTextures(1, &tex)); GL_CALL(glDeleteBuffers(1, &vbo_cube_vertices)); GL_CALL(glDeleteBuffers(1, &ibo_cube_indices)); OpenGL::render_end(); } void wf_cube_background_cubemap::create_program() { OpenGL::render_begin(); program.set_simple( OpenGL::compile_program(cubemap_vertex, cubemap_fragment)); OpenGL::render_end(); } void wf_cube_background_cubemap::reload_texture() { if (!last_background_image.compare(background_image)) { return; } last_background_image = background_image; OpenGL::render_begin(); if (tex == (uint32_t)-1) { GL_CALL(glGenTextures(1, &tex)); GL_CALL(glGenBuffers(1, &vbo_cube_vertices)); GL_CALL(glGenBuffers(1, &ibo_cube_indices)); } GL_CALL(glBindTexture(GL_TEXTURE_CUBE_MAP, tex)); if (!image_io::load_from_file(last_background_image, GL_TEXTURE_CUBE_MAP)) { LOGE("Failed to load cubemap background image from \"%s\".", last_background_image.c_str()); GL_CALL(glDeleteTextures(1, &tex)); GL_CALL(glDeleteBuffers(1, &vbo_cube_vertices)); GL_CALL(glDeleteBuffers(1, &ibo_cube_indices)); tex = -1; } if (tex != (uint32_t)-1) { GL_CALL(glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR)); GL_CALL(glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR)); GL_CALL(glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE)); GL_CALL(glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE)); GL_CALL(glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE)); } GL_CALL(glBindTexture(GL_TEXTURE_CUBE_MAP, 0)); OpenGL::render_end(); } void wf_cube_background_cubemap::render_frame(const wf::render_target_t& fb, wf_cube_animation_attribs& attribs) { reload_texture(); OpenGL::render_begin(fb); if (tex == (uint32_t)-1) { GL_CALL(glClearColor(TEX_ERROR_FLAG_COLOR)); GL_CALL(glClear(GL_COLOR_BUFFER_BIT)); OpenGL::render_end(); return; } program.use(wf::TEXTURE_TYPE_RGBA); GL_CALL(glDepthMask(GL_FALSE)); GL_CALL(glBindTexture(GL_TEXTURE_CUBE_MAP, tex)); GLfloat cube_vertices[] = { -1.0, 1.0, 1.0, -1.0, -1.0, 1.0, 1.0, -1.0, 1.0, 1.0, 1.0, 1.0, -1.0, 1.0, -1.0, -1.0, -1.0, -1.0, 1.0, -1.0, -1.0, 1.0, 1.0, -1.0, }; GLushort cube_indices[] = { 3, 7, 6, // right 3, 6, 2, // right 4, 0, 1, // left 4, 1, 5, // left 4, 7, 3, // top 4, 3, 0, // top 1, 2, 6, // bottom 1, 6, 5, // bottom 0, 3, 2, // front 0, 2, 1, // front 7, 4, 5, // back 7, 5, 6, // back }; glBindBuffer(GL_ARRAY_BUFFER, vbo_cube_vertices); glBufferData(GL_ARRAY_BUFFER, sizeof(cube_vertices), cube_vertices, GL_STATIC_DRAW); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo_cube_indices); glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(cube_indices), cube_indices, GL_STATIC_DRAW); GLint vertex = glGetAttribLocation(program.get_program_id( wf::TEXTURE_TYPE_RGBA), "position"); glEnableVertexAttribArray(vertex); glVertexAttribPointer(vertex, 3, GL_FLOAT, GL_FALSE, 0, 0); auto model = glm::rotate(glm::mat4(1.0), float(attribs.cube_animation.rotation), glm::vec3(0, 1, 0)); glm::vec3 look_at{0., (double)-attribs.cube_animation.offset_y, (double)attribs.cube_animation.offset_z}; auto view = glm::lookAt(glm::vec3(0., 0., 0.), look_at, glm::vec3(0., 1., 0.)); auto vp = fb.transform * attribs.projection * view; model = vp * model; program.uniformMatrix4f("cubeMapMatrix", model); glDrawElements(GL_TRIANGLES, 12 * 3, GL_UNSIGNED_SHORT, 0); program.deactivate(); GL_CALL(glDepthMask(GL_TRUE)); glBindBuffer(GL_ARRAY_BUFFER, 0); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); OpenGL::render_end(); } wayfire-0.8.1/plugins/cube/cubemap.hpp000066400000000000000000000013031457431457600177270ustar00rootroot00000000000000#ifndef WF_CUBE_CUBEMAP_HPP #define WF_CUBE_CUBEMAP_HPP #include "cube-background.hpp" class wf_cube_background_cubemap : public wf_cube_background_base { public: wf_cube_background_cubemap(); virtual void render_frame(const wf::render_target_t& fb, wf_cube_animation_attribs& attribs) override; ~wf_cube_background_cubemap(); private: void reload_texture(); void create_program(); OpenGL::program_t program; GLuint tex = -1; GLuint vbo_cube_vertices; GLuint ibo_cube_indices; std::string last_background_image; wf::option_wrapper_t background_image{"cube/cubemap_image"}; }; #endif /* end of include guard: WF_CUBE_CUBEMAP_HPP */ wayfire-0.8.1/plugins/cube/meson.build000066400000000000000000000006751457431457600177570ustar00rootroot00000000000000animiate = shared_module('cube', ['cube.cpp', 'cubemap.cpp', 'skydome.cpp', 'simple-background.cpp'], include_directories: [wayfire_api_inc, wayfire_conf_inc, plugins_common_inc, ipc_include_dirs], dependencies: [wlroots, pixman, wfconfig, json], install: true, install_dir: join_paths(get_option('libdir'), 'wayfire')) wayfire-0.8.1/plugins/cube/shaders-3-2.tpp000066400000000000000000000067511457431457600202730ustar00rootroot00000000000000static const char *cube_vertex_3_2 = R"(#version 320 es in vec3 position; in vec2 uvPosition; out vec2 uvpos; out vec3 vPos; void main() { vPos = position; uvpos = uvPosition; })"; static const char *cube_tcs_3_2 = R"(#version 320 es layout(vertices = 3) out; in vec2 uvpos[]; in vec3 vPos[]; out vec3 tcPosition[]; out vec2 uv[]; #define ID gl_InvocationID uniform int deform; uniform int light; void main() { tcPosition[ID] = vPos[ID]; uv[ID] = uvpos[ID]; if(ID == 0){ /* deformation requires tessellation and lighting even higher degree to make lighting smoother */ float tessLevel = 1.0f; if(deform > 0) tessLevel = 30.0f; if(light > 0) tessLevel = 50.0f; gl_TessLevelInner[0] = tessLevel; gl_TessLevelOuter[0] = tessLevel; gl_TessLevelOuter[1] = tessLevel; gl_TessLevelOuter[2] = tessLevel; } })"; static const char *cube_tes_3_2 = R"(#version 320 es layout(triangles) in; in vec3 tcPosition[]; in vec2 uv[]; out vec2 tesuv; out vec3 tePosition; uniform mat4 model; uniform mat4 VP; uniform int deform; uniform float ease; vec2 interpolate2D(vec2 v0, vec2 v1, vec2 v2) { return vec2(gl_TessCoord.x) * v0 + vec2(gl_TessCoord.y) * v1 + vec2(gl_TessCoord.z) * v2; } vec3 interpolate3D(vec3 v0, vec3 v1, vec3 v2) { return vec3(gl_TessCoord.x) * v0 + vec3(gl_TessCoord.y) * v1 + vec3(gl_TessCoord.z) * v2; } vec3 tp; void main() { tesuv = interpolate2D(uv[0], uv[1], uv[2]); tp = interpolate3D(tcPosition[0], tcPosition[1], tcPosition[2]); tp = (model * vec4(tp, 1.0)).xyz; if(deform > 0) { float r = 0.5; float d = distance(tp.xz, vec2(0, 0)); float scale = 1.0; if(deform == 1) scale = r / d; else scale = d / r; scale = pow(scale, ease); tp = vec3(tp[0] * scale, tp[1], tp[2] * scale); } tePosition = tp; gl_Position = VP * vec4 (tp, 1.0); })"; static const char* cube_geometry_3_2 = R"(#version 320 es layout(triangles) in; layout(triangle_strip, max_vertices = 3) out; in vec2 tesuv[3]; in vec3 tePosition[3]; uniform int light; out vec2 guv; out vec3 colorFactor; #define AL 0.3 // ambient lighting #define DL (1.0-AL) // diffuse lighting void main() { const vec3 lightSource = vec3(0, 0, 2); const vec3 lightNormal = normalize(lightSource); if(light == 1) { vec3 A = tePosition[2] - tePosition[0]; vec3 B = tePosition[1] - tePosition[0]; vec3 N = normalize(cross(A, B)); vec3 center = (tePosition[0] + tePosition[1] + tePosition[2]) / 3.0; float d = distance(center, lightSource); float ambient_coeff = pow(clamp(2.0 / d, 0.0, 1.0), 10.0); float value = clamp(pow(abs(dot(N, lightNormal)), 1.5), 0.0, 1.0); float df = AL * ambient_coeff + DL * value; colorFactor = vec3(df, df, df); } else colorFactor = vec3(1.0, 1.0, 1.0); gl_Position = gl_in[0].gl_Position; guv = tesuv[0]; EmitVertex(); gl_Position = gl_in[1].gl_Position; guv = tesuv[1]; EmitVertex(); gl_Position = gl_in[2].gl_Position; guv = tesuv[2]; EmitVertex(); })"; static const char *cube_fragment_3_2 = R"(#version 320 es in highp vec2 guv; in highp vec3 colorFactor; layout(location = 0) out mediump vec4 outColor; uniform sampler2D smp; void main() { outColor = vec4(texture(smp, guv).xyz * colorFactor, 1.0); })"; wayfire-0.8.1/plugins/cube/shaders.tpp000066400000000000000000000007321457431457600177650ustar00rootroot00000000000000#pragma once static const char* cube_vertex_2_0 = R"(#version 100 attribute mediump vec3 position; attribute highp vec2 uvPosition; varying highp vec2 uvpos; uniform mat4 VP; uniform mat4 model; void main() { gl_Position = VP * model * vec4(position, 1.0); uvpos = uvPosition; })"; static const char* cube_fragment_2_0 = R"(#version 100 varying highp vec2 uvpos; uniform sampler2D smp; void main() { gl_FragColor = vec4(texture2D(smp, uvpos).xyz, 1); })"; wayfire-0.8.1/plugins/cube/simple-background.cpp000066400000000000000000000005331457431457600217200ustar00rootroot00000000000000#include #include "simple-background.hpp" wf_cube_simple_background::wf_cube_simple_background() {} void wf_cube_simple_background::render_frame(const wf::render_target_t& fb, wf_cube_animation_attribs&) { OpenGL::render_begin(fb); OpenGL::clear(background_color, GL_COLOR_BUFFER_BIT); OpenGL::render_end(); } wayfire-0.8.1/plugins/cube/simple-background.hpp000066400000000000000000000007371457431457600217330ustar00rootroot00000000000000#ifndef WF_CUBE_SIMPLE_BACKGROUND_HPP #define WF_CUBE_SIMPLE_BACKGROUND_HPP #include "cube-background.hpp" class wf_cube_simple_background : public wf_cube_background_base { wf::option_wrapper_t background_color{"cube/background"}; public: wf_cube_simple_background(); virtual void render_frame(const wf::render_target_t& fb, wf_cube_animation_attribs& attribs) override; }; #endif /* end of include guard: WF_CUBE_SIMPLE_BACKGROUND_HPP */ wayfire-0.8.1/plugins/cube/skydome.cpp000066400000000000000000000113671457431457600177740ustar00rootroot00000000000000#include "skydome.hpp" #include #include #include #include #include #include "shaders.tpp" #define SKYDOME_GRID_WIDTH 128 #define SKYDOME_GRID_HEIGHT 128 wf_cube_background_skydome::wf_cube_background_skydome(wf::output_t *output) { this->output = output; load_program(); reload_texture(); } wf_cube_background_skydome::~wf_cube_background_skydome() { OpenGL::render_begin(); program.free_resources(); if (tex != (GLuint) - 1) { GL_CALL(glDeleteTextures(1, &tex)); } OpenGL::render_end(); } void wf_cube_background_skydome::load_program() { OpenGL::render_begin(); program.set_simple(OpenGL::compile_program(cube_vertex_2_0, cube_fragment_2_0)); OpenGL::render_end(); } void wf_cube_background_skydome::reload_texture() { if (!last_background_image.compare(background_image)) { return; } last_background_image = background_image; OpenGL::render_begin(); if (tex == (uint32_t)-1) { GL_CALL(glGenTextures(1, &tex)); } GL_CALL(glBindTexture(GL_TEXTURE_2D, tex)); if (image_io::load_from_file(last_background_image, GL_TEXTURE_2D)) { GL_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)); GL_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)); GL_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE)); GL_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE)); } else { LOGE("Failed to load skydome image from \"%s\".", last_background_image.c_str()); GL_CALL(glDeleteTextures(1, &tex)); tex = -1; } GL_CALL(glBindTexture(GL_TEXTURE_2D, 0)); OpenGL::render_end(); } void wf_cube_background_skydome::fill_vertices() { if (mirror_opt == last_mirror) { return; } last_mirror = mirror_opt; float scale = 75.0; int gw = SKYDOME_GRID_WIDTH + 1; int gh = SKYDOME_GRID_HEIGHT; vertices.clear(); indices.clear(); coords.clear(); for (int i = 1; i < gh; i++) { for (int j = 0; j < gw; j++) { float theta = ((2 * M_PI) / (gw - 1)) * j; float phi = (M_PI / gh) * i; vertices.push_back(cos(theta) * sin(phi) * scale); vertices.push_back(cos(phi) * scale); vertices.push_back(sin(theta) * sin(phi) * scale); if (last_mirror == 0) { coords.push_back((float)j / (gw - 1)); coords.push_back((float)(i - 1) / (gh - 2)); } else { float u = ((float)j / (gw - 1)) * 2.0; coords.push_back(u - ((u > 1.0) ? (2.0 * (u - 1.0)) : 0)); coords.push_back((float)(i - 1) / (gh - 2)); } } } for (int i = 1; i < gh - 1; i++) { for (int j = 0; j < gw - 1; j++) { indices.push_back((i - 1) * gw + j); indices.push_back((i - 1) * gw + j + gw); indices.push_back((i - 1) * gw + j + 1); indices.push_back((i - 1) * gw + j + 1); indices.push_back((i - 1) * gw + j + gw); indices.push_back((i - 1) * gw + j + gw + 1); } } } void wf_cube_background_skydome::render_frame(const wf::render_target_t& fb, wf_cube_animation_attribs& attribs) { fill_vertices(); reload_texture(); if (tex == (uint32_t)-1) { GL_CALL(glClearColor(TEX_ERROR_FLAG_COLOR)); GL_CALL(glClear(GL_COLOR_BUFFER_BIT)); return; } OpenGL::render_begin(fb); program.use(wf::TEXTURE_TYPE_RGBA); auto rotation = glm::rotate(glm::mat4(1.0), (float)(attribs.cube_animation.offset_y * 0.5), glm::vec3(1., 0., 0.)); auto view = glm::lookAt(glm::vec3(0., 0., 0.), glm::vec3(0., 0., -attribs.cube_animation.offset_z), glm::vec3(0., 1., 0.)); auto vp = fb.transform * attribs.projection * view * rotation; program.uniformMatrix4f("VP", vp); program.attrib_pointer("position", 3, 0, vertices.data()); program.attrib_pointer("uvPosition", 2, 0, coords.data()); auto cws = output->wset()->get_current_workspace(); auto model = glm::rotate(glm::mat4(1.0), float(attribs.cube_animation.rotation) - cws.x * attribs.side_angle, glm::vec3(0, 1, 0)); program.uniformMatrix4f("model", model); GL_CALL(glActiveTexture(GL_TEXTURE0)); GL_CALL(glBindTexture(GL_TEXTURE_2D, tex)); GL_CALL(glDrawElements(GL_TRIANGLES, 6 * SKYDOME_GRID_WIDTH * (SKYDOME_GRID_HEIGHT - 2), GL_UNSIGNED_INT, indices.data())); program.deactivate(); OpenGL::render_end(); } wayfire-0.8.1/plugins/cube/skydome.hpp000066400000000000000000000017401457431457600177730ustar00rootroot00000000000000#ifndef WF_CUBE_BACKGROUND_SKYDOME #define WF_CUBE_BACKGROUND_SKYDOME #include "cube-background.hpp" #include "wayfire/output.hpp" #include class wf_cube_background_skydome : public wf_cube_background_base { public: wf_cube_background_skydome(wf::output_t *output); virtual void render_frame(const wf::render_target_t& fb, wf_cube_animation_attribs& attribs) override; virtual ~wf_cube_background_skydome(); private: wf::output_t *output; void load_program(); void fill_vertices(); void reload_texture(); OpenGL::program_t program; GLuint tex = -1; std::vector vertices; std::vector coords; std::vector indices; std::string last_background_image; int last_mirror = -1; wf::option_wrapper_t background_image{"cube/skydome_texture"}; wf::option_wrapper_t mirror_opt{"cube/skydome_mirror"}; }; #endif /* end of include guard: WF_CUBE_BACKGROUND_SKYDOME */ wayfire-0.8.1/plugins/decor/000077500000000000000000000000001457431457600157635ustar00rootroot00000000000000wayfire-0.8.1/plugins/decor/deco-button.cpp000066400000000000000000000045441457431457600207210ustar00rootroot00000000000000#include "deco-button.hpp" #include "deco-theme.hpp" #include #include #define HOVERED 1.0 #define NORMAL 0.0 #define PRESSED -0.7 namespace wf { namespace decor { button_t::button_t(const decoration_theme_t& t, std::function damage) : theme(t), damage_callback(damage) {} void button_t::set_button_type(button_type_t type) { this->type = type; this->hover.animate(0, 0); update_texture(); add_idle_damage(); } button_type_t button_t::get_button_type() const { return this->type; } void button_t::set_hover(bool is_hovered) { this->is_hovered = is_hovered; if (!this->is_pressed) { if (is_hovered) { this->hover.animate(HOVERED); } else { this->hover.animate(NORMAL); } } add_idle_damage(); } /** * Set whether the button is pressed or not. * Affects appearance. */ void button_t::set_pressed(bool is_pressed) { this->is_pressed = is_pressed; if (is_pressed) { this->hover.animate(PRESSED); } else { this->hover.animate(is_hovered ? HOVERED : NORMAL); } add_idle_damage(); } void button_t::render(const wf::render_target_t& fb, wf::geometry_t geometry, wf::geometry_t scissor) { OpenGL::render_begin(fb); fb.logic_scissor(scissor); OpenGL::render_texture(button_texture.tex, fb, geometry, {1, 1, 1, 1}, OpenGL::TEXTURE_TRANSFORM_INVERT_Y); OpenGL::render_end(); if (this->hover.running()) { add_idle_damage(); } } void button_t::update_texture() { /** * We render at 100% resolution * When uploading the texture, this gets scaled * to 70% of the titlebar height. Thus we will have * a very crisp image */ decoration_theme_t::button_state_t state = { .width = 1.0 * theme.get_title_height(), .height = 1.0 * theme.get_title_height(), .border = 1.0, .hover_progress = hover, }; auto surface = theme.get_button_surface(type, state); OpenGL::render_begin(); cairo_surface_upload_to_texture(surface, this->button_texture); OpenGL::render_end(); cairo_surface_destroy(surface); } void button_t::add_idle_damage() { this->idle_damage.run_once([=] () { this->damage_callback(); update_texture(); }); } } } wayfire-0.8.1/plugins/decor/deco-button.hpp000066400000000000000000000053321457431457600207220ustar00rootroot00000000000000#pragma once #include #include #include #include #include #include #include #include #include namespace wf { namespace decor { class decoration_theme_t; enum button_type_t { BUTTON_CLOSE = 1 << 0, BUTTON_TOGGLE_MAXIMIZE = 1 << 1, BUTTON_MINIMIZE = 1 << 2, }; class button_t { public: /** * Create a new button with the given theme. * @param theme The theme to use. * @param damage_callback A callback to execute when the button needs a * repaint. Damage won't be reported while render() is being called. */ button_t(const decoration_theme_t& theme, std::function damage_callback); ~button_t() = default; button_t(const button_t &) = delete; button_t(button_t &&) = delete; button_t& operator =(const button_t&) = delete; button_t& operator =(button_t&&) = delete; /** * Set the type of the button. This will affect the displayed icon and * potentially other appearance like colors. */ void set_button_type(button_type_t type); /** @return The type of the button */ button_type_t get_button_type() const; /** * Set the button hover state. * Affects appearance. */ void set_hover(bool is_hovered); /** * Set whether the button is pressed or not. * Affects appearance. */ void set_pressed(bool is_pressed); /** * Render the button on the given framebuffer at the given coordinates. * Precondition: set_button_type() has been called, otherwise result is no-op * * @param buffer The target framebuffer * @param geometry The geometry of the button, in logical coordinates * @param scissor The scissor rectangle to render. */ void render(const wf::render_target_t& buffer, wf::geometry_t geometry, wf::geometry_t scissor); private: const decoration_theme_t& theme; /* Whether the button needs repaint */ button_type_t type; wf::simple_texture_t button_texture; /* Whether the button is currently being hovered */ bool is_hovered = false; /* Whether the button is currently being held */ bool is_pressed = false; /* The shade of button background to use. */ wf::animation::simple_animation_t hover{wf::create_option(100)}; std::function damage_callback; wf::wl_idle_call idle_damage; /** Damage button the next time the main loop goes idle */ void add_idle_damage(); /** * Redraw the button surface and store it as a texture */ void update_texture(); }; } } wayfire-0.8.1/plugins/decor/deco-layout.cpp000066400000000000000000000242171457431457600207220ustar00rootroot00000000000000#include "deco-layout.hpp" #include "deco-theme.hpp" #include #include #include #include #define BUTTON_HEIGHT_PC 0.7 namespace wf { namespace decor { /** * Represents an area of the decoration which reacts to input events. */ decoration_area_t::decoration_area_t(decoration_area_type_t type, wf::geometry_t g) { this->type = type; this->geometry = g; assert(type != DECORATION_AREA_BUTTON); } /** * Initialize a new decoration area holding a button */ decoration_area_t::decoration_area_t(wf::geometry_t g, std::function damage_callback, const decoration_theme_t& theme) { this->type = DECORATION_AREA_BUTTON; this->geometry = g; this->button = std::make_unique(theme, std::bind(damage_callback, g)); } wf::geometry_t decoration_area_t::get_geometry() const { return geometry; } button_t& decoration_area_t::as_button() { assert(button); return *button; } decoration_area_type_t decoration_area_t::get_type() const { return type; } decoration_layout_t::decoration_layout_t(const decoration_theme_t& th, std::function callback) : titlebar_size(th.get_title_height()), border_size(th.get_border_size()), /** * This is necessary. Otherwise, we will draw an * overly huge button. 70% of the titlebar height * is a decent size. (Equals 21 px by default) */ button_width(titlebar_size * BUTTON_HEIGHT_PC), button_height(titlebar_size * BUTTON_HEIGHT_PC), button_padding((titlebar_size - button_height) / 2), theme(th), damage_callback(callback) {} wf::geometry_t decoration_layout_t::create_buttons(int width, int) { std::stringstream stream((std::string)button_order); std::vector buttons; std::string button_name; while (stream >> button_name) { if ((button_name == "minimize") && (theme.button_flags & BUTTON_MINIMIZE)) { buttons.push_back(BUTTON_MINIMIZE); } if ((button_name == "maximize") && (theme.button_flags & BUTTON_TOGGLE_MAXIMIZE)) { buttons.push_back(BUTTON_TOGGLE_MAXIMIZE); } if ((button_name == "close") && (theme.button_flags & BUTTON_CLOSE)) { buttons.push_back(BUTTON_CLOSE); } } int per_button = 2 * button_padding + button_width; wf::geometry_t button_geometry = { width - border_size + button_padding, /* 1 more padding initially */ button_padding + border_size, button_width, button_height, }; for (auto type : wf::reverse(buttons)) { button_geometry.x -= per_button; this->layout_areas.push_back(std::make_unique( button_geometry, damage_callback, theme)); this->layout_areas.back()->as_button().set_button_type(type); } int total_width = -button_padding + buttons.size() * per_button; return { button_geometry.x, border_size, total_width, titlebar_size }; } /** Regenerate layout using the new size */ void decoration_layout_t::resize(int width, int height) { this->layout_areas.clear(); if (this->titlebar_size > 0) { auto button_geometry_expanded = create_buttons(width, height); /* Padding around the button, allows move */ this->layout_areas.push_back(std::make_unique( DECORATION_AREA_MOVE, button_geometry_expanded)); /* Titlebar dragging area (for move) */ wf::geometry_t title_geometry = { border_size, border_size, /* Up to the button, but subtract the padding to the left of the * title and the padding between title and button */ button_geometry_expanded.x - border_size, titlebar_size, }; this->layout_areas.push_back(std::make_unique( DECORATION_AREA_TITLE, title_geometry)); } /* Resizing edges - left */ wf::geometry_t border_geometry = {0, 0, border_size, height}; this->layout_areas.push_back(std::make_unique( DECORATION_AREA_RESIZE_LEFT, border_geometry)); /* Resizing edges - right */ border_geometry = {width - border_size, 0, border_size, height}; this->layout_areas.push_back(std::make_unique( DECORATION_AREA_RESIZE_RIGHT, border_geometry)); /* Resizing edges - top */ border_geometry = {0, 0, width, border_size}; this->layout_areas.push_back(std::make_unique( DECORATION_AREA_RESIZE_TOP, border_geometry)); /* Resizing edges - bottom */ border_geometry = {0, height - border_size, width, border_size}; this->layout_areas.push_back(std::make_unique( DECORATION_AREA_RESIZE_BOTTOM, border_geometry)); } /** * @return The decoration areas which need to be rendered, in top to bottom * order. */ std::vector> decoration_layout_t::get_renderable_areas() { std::vector> renderable; for (auto& area : layout_areas) { if (area->get_type() & DECORATION_AREA_RENDERABLE_BIT) { renderable.push_back({area}); } } return renderable; } wf::region_t decoration_layout_t::calculate_region() const { wf::region_t r{}; for (auto& area : layout_areas) { auto g = area->get_geometry(); if ((g.width > 0) && (g.height > 0)) { r |= g; } } return r; } void decoration_layout_t::unset_hover(wf::point_t position) { auto area = find_area_at(position); if (area && (area->get_type() == DECORATION_AREA_BUTTON)) { area->as_button().set_hover(false); } } /** Handle motion event to (x, y) relative to the decoration */ decoration_layout_t::action_response_t decoration_layout_t::handle_motion( int x, int y) { auto previous_area = find_area_at(current_input); auto current_area = find_area_at({x, y}); if (previous_area == current_area) { if (is_grabbed && current_area && (current_area->get_type() & DECORATION_AREA_MOVE_BIT)) { is_grabbed = false; return {DECORATION_ACTION_MOVE, 0}; } } else { unset_hover(current_input); if (current_area && (current_area->get_type() == DECORATION_AREA_BUTTON)) { current_area->as_button().set_hover(true); } } this->current_input = {x, y}; update_cursor(); return {DECORATION_ACTION_NONE, 0}; } /** * Handle press or release event. * @param pressed Whether the event is a press(true) or release(false) * event. * @return The action which needs to be carried out in response to this * event. * */ decoration_layout_t::action_response_t decoration_layout_t::handle_press_event( bool pressed) { if (pressed) { auto area = find_area_at(current_input); if (area && (area->get_type() & DECORATION_AREA_MOVE_BIT)) { if (timer.is_connected()) { double_click_at_release = true; } else { timer.set_timeout(300, [] () { return false; }); } } if (area && (area->get_type() & DECORATION_AREA_RESIZE_BIT)) { return {DECORATION_ACTION_RESIZE, calculate_resize_edges()}; } if (area && (area->get_type() == DECORATION_AREA_BUTTON)) { area->as_button().set_pressed(true); } is_grabbed = true; grab_origin = current_input; } if (!pressed && double_click_at_release) { double_click_at_release = false; return {DECORATION_ACTION_TOGGLE_MAXIMIZE, 0}; } else if (!pressed && is_grabbed) { is_grabbed = false; auto begin_area = find_area_at(grab_origin); auto end_area = find_area_at(current_input); if (begin_area && (begin_area->get_type() == DECORATION_AREA_BUTTON)) { begin_area->as_button().set_pressed(false); if (end_area && (begin_area == end_area)) { switch (begin_area->as_button().get_button_type()) { case BUTTON_CLOSE: return {DECORATION_ACTION_CLOSE, 0}; case BUTTON_TOGGLE_MAXIMIZE: return {DECORATION_ACTION_TOGGLE_MAXIMIZE, 0}; case BUTTON_MINIMIZE: return {DECORATION_ACTION_MINIMIZE, 0}; default: break; } } } } return {DECORATION_ACTION_NONE, 0}; } /** * Find the layout area at the given coordinates, if any * @return The layout area or null on failure */ nonstd::observer_ptr decoration_layout_t::find_area_at( wf::point_t point) { for (auto& area : this->layout_areas) { if (area->get_geometry() & point) { return {area}; } } return nullptr; } /** Calculate resize edges based on @current_input */ uint32_t decoration_layout_t::calculate_resize_edges() const { uint32_t edges = 0; for (auto& area : layout_areas) { if (area->get_geometry() & this->current_input) { if (area->get_type() & DECORATION_AREA_RESIZE_BIT) { edges |= (area->get_type() & ~DECORATION_AREA_RESIZE_BIT); } } } return edges; } /** Update the cursor based on @current_input */ void decoration_layout_t::update_cursor() const { uint32_t edges = calculate_resize_edges(); auto cursor_name = edges > 0 ? wlr_xcursor_get_resize_name((wlr_edges)edges) : "default"; wf::get_core().set_cursor(cursor_name); } void decoration_layout_t::handle_focus_lost() { if (is_grabbed) { this->is_grabbed = false; auto area = find_area_at(grab_origin); if (area && (area->get_type() == DECORATION_AREA_BUTTON)) { area->as_button().set_pressed(false); } } this->unset_hover(current_input); } } } wayfire-0.8.1/plugins/decor/deco-layout.hpp000066400000000000000000000126001457431457600207200ustar00rootroot00000000000000#pragma once #include #include #include "deco-button.hpp" namespace wf { namespace decor { static constexpr uint32_t DECORATION_AREA_RENDERABLE_BIT = (1 << 16); static constexpr uint32_t DECORATION_AREA_RESIZE_BIT = (1 << 17); static constexpr uint32_t DECORATION_AREA_MOVE_BIT = (1 << 18); /** Different types of areas around the decoration */ enum decoration_area_type_t { DECORATION_AREA_MOVE = DECORATION_AREA_MOVE_BIT, DECORATION_AREA_TITLE = DECORATION_AREA_MOVE_BIT | DECORATION_AREA_RENDERABLE_BIT, DECORATION_AREA_BUTTON = DECORATION_AREA_RENDERABLE_BIT, DECORATION_AREA_RESIZE_LEFT = WLR_EDGE_LEFT | DECORATION_AREA_RESIZE_BIT, DECORATION_AREA_RESIZE_RIGHT = WLR_EDGE_RIGHT | DECORATION_AREA_RESIZE_BIT, DECORATION_AREA_RESIZE_TOP = WLR_EDGE_TOP | DECORATION_AREA_RESIZE_BIT, DECORATION_AREA_RESIZE_BOTTOM = WLR_EDGE_BOTTOM | DECORATION_AREA_RESIZE_BIT, }; /** * Represents an area of the decoration which reacts to input events. */ struct decoration_area_t { public: /** * Initialize a new decoration area with the given type and geometry */ decoration_area_t(decoration_area_type_t type, wf::geometry_t g); /** * Initialize a new decoration area holding a button. * * @param g The geometry of the button. * @param damage_callback Callback to execute when button needs repaint. * @param theme The theme to use for the button. */ decoration_area_t(wf::geometry_t g, std::function damage_callback, const decoration_theme_t& theme); /** @return The geometry of the decoration area, relative to the layout */ wf::geometry_t get_geometry() const; /** @return The area's button, if the area is a button. Otherwise UB */ button_t& as_button(); /** @return The type of the decoration area */ decoration_area_type_t get_type() const; private: decoration_area_type_t type; wf::geometry_t geometry; /* For buttons only */ std::unique_ptr button; }; /** * Action which needs to be taken in response to an input event */ enum decoration_layout_action_t { DECORATION_ACTION_NONE = 0, /* Drag actions */ DECORATION_ACTION_MOVE = 1, DECORATION_ACTION_RESIZE = 2, /* Button actions */ DECORATION_ACTION_CLOSE = 3, DECORATION_ACTION_TOGGLE_MAXIMIZE = 4, DECORATION_ACTION_MINIMIZE = 5, }; class decoration_theme_t; /** * Manages the layout of the decorations, i.e positioning of the title, * buttons, etc. * * Also dispatches the input events to the appropriate place. */ class decoration_layout_t { public: /** * Create a new decoration layout for the given theme. * When the theme changes, the decoration layout needs to be created again. * * @param damage_callback The function to be called when a part of the * layout needs a repaint. */ decoration_layout_t(const decoration_theme_t& theme, std::function damage_callback); /** Regenerate layout using the new size */ void resize(int width, int height); /** * @return The decoration areas which need to be rendered, in top to bottom * order. */ std::vector> get_renderable_areas(); /** @return The combined region of all layout areas */ wf::region_t calculate_region() const; struct action_response_t { decoration_layout_action_t action; /* For resizing action, determine the edges for resize request */ uint32_t edges; }; /** Handle motion event to (x, y) relative to the decoration */ action_response_t handle_motion(int x, int y); /** * Handle press or release event. * @param pressed Whether the event is a press(true) or release(false) * event. * @return The action which needs to be carried out in response to this * event. */ action_response_t handle_press_event(bool pressed = true); /** * Handle focus lost event. */ void handle_focus_lost(); private: const int titlebar_size; const int border_size; const int button_width; const int button_height; const int button_padding; const decoration_theme_t& theme; std::function damage_callback; std::vector> layout_areas; bool is_grabbed = false; /* Position where the grab has started */ wf::point_t grab_origin; /* Last position of the input */ wf::point_t current_input; /* double-click timer */ wf::wl_timer timer; bool double_click_at_release = false; /** Create buttons in the layout, and return their total geometry */ wf::geometry_t create_buttons(int width, int height); /** Calculate resize edges based on @current_input */ uint32_t calculate_resize_edges() const; /** Update the cursor based on @current_input */ void update_cursor() const; /** * Find the layout area at the given coordinates, if any * @return The layout area or null on failure */ nonstd::observer_ptr find_area_at(wf::point_t point); /** Unset hover state of hovered button at @position, if any */ void unset_hover(wf::point_t position); wf::option_wrapper_t button_order{"decoration/button_order"}; }; } } wayfire-0.8.1/plugins/decor/deco-subsurface.cpp000066400000000000000000000271031457431457600215440ustar00rootroot00000000000000#include "wayfire/geometry.hpp" #include "wayfire/scene-input.hpp" #include "wayfire/scene-operations.hpp" #include "wayfire/scene-render.hpp" #include "wayfire/scene.hpp" #include "wayfire/signal-provider.hpp" #include "wayfire/toplevel.hpp" #include #define GLM_FORCE_RADIANS #include #include #include #include #include #include #include #include #include #include "deco-subsurface.hpp" #include "deco-layout.hpp" #include "deco-theme.hpp" #include #include #include class simple_decoration_node_t : public wf::scene::node_t, public wf::pointer_interaction_t, public wf::touch_interaction_t { std::weak_ptr _view; wf::signal::connection_t title_set = [=] (wf::view_title_changed_signal *ev) { if (auto view = _view.lock()) { view->damage(); } }; void update_title(int width, int height, double scale) { if (auto view = _view.lock()) { int target_width = width * scale; int target_height = height * scale; if ((title_texture.tex.width != target_width) || (title_texture.tex.height != target_height) || (title_texture.current_text != view->get_title())) { auto surface = theme.render_text(view->get_title(), target_width, target_height); cairo_surface_upload_to_texture(surface, title_texture.tex); cairo_surface_destroy(surface); title_texture.current_text = view->get_title(); } } } struct { wf::simple_texture_t tex; std::string current_text = ""; } title_texture; public: wf::decor::decoration_theme_t theme; wf::decor::decoration_layout_t layout; wf::region_t cached_region; wf::dimensions_t size; int current_thickness; int current_titlebar; simple_decoration_node_t(wayfire_toplevel_view view) : node_t(false), theme{}, layout{theme, [=] (wlr_box box) { wf::scene::damage_node(shared_from_this(), box + get_offset()); }} { this->_view = view->weak_from_this(); view->connect(&title_set); if (view->parent) { theme.set_buttons(wf::decor::button_type_t(wf::decor::BUTTON_TOGGLE_MAXIMIZE | wf::decor::BUTTON_CLOSE)); } else { theme.set_buttons(wf::decor::button_type_t(wf::decor::BUTTON_MINIMIZE | wf::decor::BUTTON_TOGGLE_MAXIMIZE | wf::decor::BUTTON_CLOSE)); } // make sure to hide frame if the view is fullscreen update_decoration_size(); } wf::point_t get_offset() { return {-current_thickness, -current_titlebar}; } void render_title(const wf::render_target_t& fb, wf::geometry_t geometry) { update_title(geometry.width, geometry.height, fb.scale); OpenGL::render_texture(title_texture.tex.tex, fb, geometry, glm::vec4(1.0f), OpenGL::TEXTURE_TRANSFORM_INVERT_Y); } void render_scissor_box(const wf::render_target_t& fb, wf::point_t origin, const wlr_box& scissor) { /* Clear background */ wlr_box geometry{origin.x, origin.y, size.width, size.height}; bool activated = false; if (auto view = _view.lock()) { activated = view->activated; } theme.render_background(fb, geometry, scissor, activated); /* Draw title & buttons */ auto renderables = layout.get_renderable_areas(); for (auto item : renderables) { if (item->get_type() == wf::decor::DECORATION_AREA_TITLE) { OpenGL::render_begin(fb); fb.logic_scissor(scissor); render_title(fb, item->get_geometry() + origin); OpenGL::render_end(); } else // button { item->as_button().render(fb, item->get_geometry() + origin, scissor); } } } std::optional find_node_at(const wf::pointf_t& at) override { wf::pointf_t local = at - wf::pointf_t{get_offset()}; if (cached_region.contains_pointf(local)) { return wf::scene::input_node_t{ .node = this, .local_coords = local, }; } return {}; } pointer_interaction_t& pointer_interaction() override { return *this; } touch_interaction_t& touch_interaction() override { return *this; } class decoration_render_instance_t : public wf::scene::render_instance_t { simple_decoration_node_t *self; wf::scene::damage_callback push_damage; wf::signal::connection_t on_surface_damage = [=] (wf::scene::node_damage_signal *data) { push_damage(data->region); }; public: decoration_render_instance_t(simple_decoration_node_t *self, wf::scene::damage_callback push_damage) { this->self = self; this->push_damage = push_damage; self->connect(&on_surface_damage); } void schedule_instructions(std::vector& instructions, const wf::render_target_t& target, wf::region_t& damage) override { auto our_region = self->cached_region + self->get_offset(); wf::region_t our_damage = damage & our_region; if (!our_damage.empty()) { instructions.push_back(wf::scene::render_instruction_t{ .instance = this, .target = target, .damage = std::move(our_damage), }); } } void render(const wf::render_target_t& target, const wf::region_t& region) override { for (const auto& box : region) { self->render_scissor_box(target, self->get_offset(), wlr_box_from_pixman_box(box)); } } }; void gen_render_instances(std::vector& instances, wf::scene::damage_callback push_damage, wf::output_t *output = nullptr) override { instances.push_back(std::make_unique(this, push_damage)); } wf::geometry_t get_bounding_box() override { return wf::construct_box(get_offset(), size); } /* wf::compositor_surface_t implementation */ void handle_pointer_enter(wf::pointf_t point) override { point -= wf::pointf_t{get_offset()}; layout.handle_motion(point.x, point.y); } void handle_pointer_leave() override { layout.handle_focus_lost(); } void handle_pointer_motion(wf::pointf_t to, uint32_t) override { to -= wf::pointf_t{get_offset()}; handle_action(layout.handle_motion(to.x, to.y)); } void handle_pointer_button(const wlr_pointer_button_event& ev) override { if (ev.button != BTN_LEFT) { return; } handle_action(layout.handle_press_event(ev.state == WLR_BUTTON_PRESSED)); } void handle_action(wf::decor::decoration_layout_t::action_response_t action) { if (auto view = _view.lock()) { switch (action.action) { case wf::decor::DECORATION_ACTION_MOVE: return wf::get_core().default_wm->move_request(view); case wf::decor::DECORATION_ACTION_RESIZE: return wf::get_core().default_wm->resize_request(view, action.edges); case wf::decor::DECORATION_ACTION_CLOSE: return view->close(); case wf::decor::DECORATION_ACTION_TOGGLE_MAXIMIZE: if (view->pending_tiled_edges()) { return wf::get_core().default_wm->tile_request(view, 0); } else { return wf::get_core().default_wm->tile_request(view, wf::TILED_EDGES_ALL); } break; case wf::decor::DECORATION_ACTION_MINIMIZE: return wf::get_core().default_wm->minimize_request(view, true); break; default: break; } } } void handle_touch_down(uint32_t time_ms, int finger_id, wf::pointf_t position) override { handle_touch_motion(time_ms, finger_id, position); handle_action(layout.handle_press_event()); } void handle_touch_up(uint32_t time_ms, int finger_id, wf::pointf_t lift_off_position) override { handle_action(layout.handle_press_event(false)); layout.handle_focus_lost(); } void handle_touch_motion(uint32_t time_ms, int finger_id, wf::pointf_t position) override { position -= wf::pointf_t{get_offset()}; layout.handle_motion(position.x, position.y); } void resize(wf::dimensions_t dims) { if (auto view = _view.lock()) { view->damage(); size = dims; layout.resize(size.width, size.height); if (!view->toplevel()->current().fullscreen) { this->cached_region = layout.calculate_region(); } view->damage(); } } void update_decoration_size() { bool fullscreen = _view.lock()->toplevel()->current().fullscreen; if (fullscreen) { current_thickness = 0; current_titlebar = 0; this->cached_region.clear(); } else { current_thickness = theme.get_border_size(); current_titlebar = theme.get_title_height() + theme.get_border_size(); this->cached_region = layout.calculate_region(); } } }; wf::simple_decorator_t::simple_decorator_t(wayfire_toplevel_view view) { this->view = view; deco = std::make_shared(view); deco->resize(wf::dimensions(view->get_pending_geometry())); wf::scene::add_back(view->get_surface_root_node(), deco); view->connect(&on_view_activated); view->connect(&on_view_geometry_changed); view->connect(&on_view_fullscreen); on_view_activated = [this] (auto) { wf::scene::damage_node(deco, deco->get_bounding_box()); }; on_view_geometry_changed = [this] (auto) { deco->resize(wf::dimensions(this->view->get_geometry())); }; on_view_fullscreen = [this] (auto) { deco->update_decoration_size(); if (!this->view->toplevel()->current().fullscreen) { deco->resize(wf::dimensions(this->view->get_geometry())); } }; } wf::simple_decorator_t::~simple_decorator_t() { wf::scene::remove_child(deco); } wf::decoration_margins_t wf::simple_decorator_t::get_margins(const wf::toplevel_state_t& state) { if (state.fullscreen) { return {0, 0, 0, 0}; } const int thickness = deco->theme.get_border_size(); const int titlebar = deco->theme.get_title_height() + deco->theme.get_border_size(); return wf::decoration_margins_t{ .left = thickness, .right = thickness, .bottom = thickness, .top = titlebar, }; } wayfire-0.8.1/plugins/decor/deco-subsurface.hpp000066400000000000000000000016501457431457600215500ustar00rootroot00000000000000#ifndef DECO_SUBSURFACE_HPP #define DECO_SUBSURFACE_HPP #include "wayfire/object.hpp" #include "wayfire/toplevel.hpp" #include #include class simple_decoration_node_t; namespace wf { /** * A decorator object attached as custom data to a toplevel object. */ class simple_decorator_t : public wf::custom_data_t { wayfire_toplevel_view view; std::shared_ptr deco; wf::signal::connection_t on_view_activated; wf::signal::connection_t on_view_geometry_changed; wf::signal::connection_t on_view_fullscreen; public: simple_decorator_t(wayfire_toplevel_view view); ~simple_decorator_t(); wf::decoration_margins_t get_margins(const wf::toplevel_state_t& state); }; } #endif /* end of include guard: DECO_SUBSURFACE_HPP */ wayfire-0.8.1/plugins/decor/deco-theme.cpp000066400000000000000000000137401457431457600205060ustar00rootroot00000000000000#include "deco-theme.hpp" #include #include #include #include namespace wf { namespace decor { /** Create a new theme with the default parameters */ decoration_theme_t::decoration_theme_t() {} /** @return The available height for displaying the title */ int decoration_theme_t::get_title_height() const { return title_height; } /** @return The available border for resizing */ int decoration_theme_t::get_border_size() const { return border_size; } /** @return The available border for resizing */ void decoration_theme_t::set_buttons(button_type_t flags) { button_flags = flags; } /** * Fill the given rectangle with the background color(s). * * @param fb The target framebuffer, must have been bound already * @param rectangle The rectangle to redraw. * @param scissor The GL scissor rectangle to use. * @param active Whether to use active or inactive colors */ void decoration_theme_t::render_background(const wf::render_target_t& fb, wf::geometry_t rectangle, const wf::geometry_t& scissor, bool active) const { wf::color_t color = active ? active_color : inactive_color; OpenGL::render_begin(fb); fb.logic_scissor(scissor); OpenGL::render_rectangle(rectangle, color, fb.get_orthographic_projection()); OpenGL::render_end(); } /** * Render the given text on a cairo_surface_t with the given size. * The caller is responsible for freeing the memory afterwards. */ cairo_surface_t*decoration_theme_t::render_text(std::string text, int width, int height) const { const auto format = CAIRO_FORMAT_ARGB32; auto surface = cairo_image_surface_create(format, width, height); if (height == 0) { return surface; } auto cr = cairo_create(surface); const float font_scale = 0.8; const float font_size = height * font_scale; PangoFontDescription *font_desc; PangoLayout *layout; // render text font_desc = pango_font_description_from_string(((std::string)font).c_str()); pango_font_description_set_absolute_size(font_desc, font_size * PANGO_SCALE); layout = pango_cairo_create_layout(cr); pango_layout_set_font_description(layout, font_desc); pango_layout_set_text(layout, text.c_str(), text.size()); cairo_set_source_rgba(cr, 1, 1, 1, 1); pango_cairo_show_layout(cr, layout); pango_font_description_free(font_desc); g_object_unref(layout); cairo_destroy(cr); return surface; } cairo_surface_t*decoration_theme_t::get_button_surface(button_type_t button, const button_state_t& state) const { cairo_surface_t *button_surface = cairo_image_surface_create( CAIRO_FORMAT_ARGB32, state.width, state.height); auto cr = cairo_create(button_surface); cairo_set_antialias(cr, CAIRO_ANTIALIAS_BEST); /* Clear the button background */ cairo_set_operator(cr, CAIRO_OPERATOR_CLEAR); cairo_set_source_rgba(cr, 0, 0, 0, 0); cairo_rectangle(cr, 0, 0, state.width, state.height); cairo_fill(cr); cairo_set_operator(cr, CAIRO_OPERATOR_OVER); /** A gray that looks good on light and dark themes */ color_t base = {0.60, 0.60, 0.63, 0.36}; /** * We just need the alpha component. * r == g == b == 0.0 will be directly set */ double line = 0.27; double hover = 0.27; /** Coloured base on hover/press. Don't compare float to 0 */ if (fabs(state.hover_progress) > 1e-3) { switch (button) { case BUTTON_CLOSE: base = {242.0 / 255.0, 80.0 / 255.0, 86.0 / 255.0, 0.63}; break; case BUTTON_TOGGLE_MAXIMIZE: base = {57.0 / 255.0, 234.0 / 255.0, 73.0 / 255.0, 0.63}; break; case BUTTON_MINIMIZE: base = {250.0 / 255.0, 198.0 / 255.0, 54.0 / 255.0, 0.63}; break; default: assert(false); } line *= 2.0; } /** Draw the base */ cairo_set_source_rgba(cr, base.r + 0.0 * state.hover_progress, base.g + 0.0 * state.hover_progress, base.b + 0.0 * state.hover_progress, base.a + hover * state.hover_progress); cairo_arc(cr, state.width / 2, state.height / 2, state.width / 2, 0, 2 * M_PI); cairo_fill(cr); /** Draw the border */ cairo_set_line_width(cr, state.border); cairo_set_source_rgba(cr, 0.00, 0.00, 0.00, line); // This renders great on my screen (110 dpi 1376x768 lcd screen) // How this would appear on a Hi-DPI screen is questionable double r = state.width / 2 - 0.5 * state.border; cairo_arc(cr, state.width / 2, state.height / 2, r, 0, 2 * M_PI); cairo_stroke(cr); /** Draw the icon */ cairo_set_source_rgba(cr, 0.00, 0.00, 0.00, line / 2); cairo_set_antialias(cr, CAIRO_ANTIALIAS_NONE); switch (button) { case BUTTON_CLOSE: cairo_set_line_width(cr, 1.5 * state.border); cairo_move_to(cr, 1.0 * state.width / 4.0, 1.0 * state.height / 4.0); cairo_line_to(cr, 3.0 * state.width / 4.0, 3.0 * state.height / 4.0); // '\' part of x cairo_move_to(cr, 3.0 * state.width / 4.0, 1.0 * state.height / 4.0); cairo_line_to(cr, 1.0 * state.width / 4.0, 3.0 * state.height / 4.0); // '/' part of x cairo_stroke(cr); break; case BUTTON_TOGGLE_MAXIMIZE: cairo_set_line_width(cr, 1.5 * state.border); cairo_rectangle( cr, // Context state.width / 4.0, state.height / 4.0, // (x, y) state.width / 2.0, state.height / 2.0 // w x h ); cairo_stroke(cr); break; case BUTTON_MINIMIZE: cairo_set_line_width(cr, 1.75 * state.border); cairo_move_to(cr, 1.0 * state.width / 4.0, state.height / 2.0); cairo_line_to(cr, 3.0 * state.width / 4.0, state.height / 2.0); cairo_stroke(cr); break; default: assert(false); } cairo_fill(cr); cairo_destroy(cr); return button_surface; } } } wayfire-0.8.1/plugins/decor/deco-theme.hpp000066400000000000000000000045661457431457600205210ustar00rootroot00000000000000#pragma once #include #include "deco-button.hpp" namespace wf { namespace decor { /** * A class which manages the outlook of decorations. * It is responsible for determining the background colors, sizes, etc. */ class decoration_theme_t { public: /** Create a new theme with the default parameters */ decoration_theme_t(); /** @return The available height for displaying the title */ int get_title_height() const; /** @return The available border for resizing */ int get_border_size() const; /** Set the flags for buttons */ void set_buttons(button_type_t flags); button_type_t button_flags; /** * Fill the given rectangle with the background color(s). * * @param fb The target framebuffer, must have been bound already. * @param rectangle The rectangle to redraw. * @param scissor The GL scissor rectangle to use. * @param active Whether to use active or inactive colors */ void render_background(const wf::render_target_t& fb, wf::geometry_t rectangle, const wf::geometry_t& scissor, bool active) const; /** * Render the given text on a cairo_surface_t with the given size. * The caller is responsible for freeing the memory afterwards. */ cairo_surface_t *render_text(std::string text, int width, int height) const; struct button_state_t { /** Button width */ double width; /** Button height */ double height; /** Button outline size */ double border; /** Progress of button hover, in range [-1, 1]. * Negative numbers are usually used for pressed state. */ double hover_progress; }; /** * Get the icon for the given button. * The caller is responsible for freeing the memory afterwards. * * @param button The button type. * @param state The button state. */ cairo_surface_t *get_button_surface(button_type_t button, const button_state_t& state) const; private: wf::option_wrapper_t font{"decoration/font"}; wf::option_wrapper_t title_height{"decoration/title_height"}; wf::option_wrapper_t border_size{"decoration/border_size"}; wf::option_wrapper_t active_color{"decoration/active_color"}; wf::option_wrapper_t inactive_color{"decoration/inactive_color"}; }; } } wayfire-0.8.1/plugins/decor/decoration.cpp000066400000000000000000000122231457431457600206160ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include "deco-subsurface.hpp" #include "wayfire/core.hpp" #include "wayfire/plugin.hpp" #include "wayfire/signal-provider.hpp" #include "wayfire/toplevel-view.hpp" #include "wayfire/toplevel.hpp" class wayfire_decoration : public wf::plugin_interface_t { wf::view_matcher_t ignore_views{"decoration/ignore_views"}; wf::signal::connection_t on_new_tx = [=] (wf::txn::new_transaction_signal *ev) { // For each transaction, we need to consider what happens with participating views for (const auto& obj : ev->tx->get_objects()) { if (auto toplevel = std::dynamic_pointer_cast(obj)) { // First check whether the toplevel already has decoration // In that case, we should just set the correct margins if (auto deco = toplevel->get_data()) { toplevel->pending().margins = deco->get_margins(toplevel->pending()); continue; } // Second case: the view is already mapped, or the transaction does not map it. // The view is not being decorated, so nothing to do here. if (toplevel->current().mapped || !toplevel->pending().mapped) { continue; } // Third case: the transaction will map the toplevel. auto view = wf::find_view_for_toplevel(toplevel); wf::dassert(view != nullptr, "Mapping a toplevel means there must be a corresponding view!"); if (should_decorate_view(view)) { adjust_new_decorations(view); } } } }; wf::signal::connection_t on_decoration_state_changed = [=] (wf::view_decoration_state_updated_signal *ev) { update_view_decoration(ev->view); }; // allows criteria containing maximized or floating check wf::signal::connection_t on_view_tiled = [=] (wf::view_tiled_signal *ev) { update_view_decoration(ev->view); }; public: void init() override { wf::get_core().connect(&on_decoration_state_changed); wf::get_core().tx_manager->connect(&on_new_tx); wf::get_core().connect(&on_view_tiled); for (auto& view : wf::get_core().get_all_views()) { update_view_decoration(view); } } void fini() override { for (auto view : wf::get_core().get_all_views()) { if (auto toplevel = wf::toplevel_cast(view)) { remove_decoration(toplevel); wf::get_core().tx_manager->schedule_object(toplevel->toplevel()); } } } /** * Uses view_matcher_t to match whether the given view needs to be * ignored for decoration * * @param view The view to match * @return Whether the given view should be decorated? */ bool ignore_decoration_of_view(wayfire_view view) { return ignore_views.matches(view); } bool should_decorate_view(wayfire_toplevel_view view) { return view->should_be_decorated() && !ignore_decoration_of_view(view); } void adjust_new_decorations(wayfire_toplevel_view view) { auto toplevel = view->toplevel(); toplevel->store_data(std::make_unique(view)); auto deco = toplevel->get_data(); auto& pending = toplevel->pending(); pending.margins = deco->get_margins(pending); if (!pending.fullscreen && !pending.tiled_edges) { pending.geometry = wf::expand_geometry_by_margins(pending.geometry, pending.margins); if (view->get_output()) { pending.geometry = wf::clamp(pending.geometry, view->get_output()->workarea->get_workarea()); } } } void remove_decoration(wayfire_toplevel_view view) { view->toplevel()->erase_data(); auto& pending = view->toplevel()->pending(); if (!pending.fullscreen && !pending.tiled_edges) { pending.geometry = wf::shrink_geometry_by_margins(pending.geometry, pending.margins); } pending.margins = {0, 0, 0, 0}; } void update_view_decoration(wayfire_view view) { if (auto toplevel = wf::toplevel_cast(view)) { if (should_decorate_view(toplevel)) { adjust_new_decorations(toplevel); } else { remove_decoration(toplevel); } wf::get_core().tx_manager->schedule_object(toplevel->toplevel()); } } }; DECLARE_WAYFIRE_PLUGIN(wayfire_decoration); wayfire-0.8.1/plugins/decor/meson.build000066400000000000000000000006151457431457600201270ustar00rootroot00000000000000decoration = shared_module('decoration', ['decoration.cpp', 'deco-subsurface.cpp', 'deco-button.cpp', 'deco-layout.cpp', 'deco-theme.cpp'], include_directories: [wayfire_api_inc, wayfire_conf_inc, plugins_common_inc], dependencies: [wlroots, pixman, wf_protos, wfconfig, cairo, pango, pangocairo], install: true, install_dir: join_paths(get_option('libdir'), 'wayfire')) wayfire-0.8.1/plugins/grid/000077500000000000000000000000001457431457600156145ustar00rootroot00000000000000wayfire-0.8.1/plugins/grid/grid.cpp000066400000000000000000000173701457431457600172550ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include #include #include "wayfire/plugin.hpp" #include "wayfire/signal-definitions.hpp" #include #include "wayfire/plugins/grid.hpp" #include "wayfire/plugins/crossfade.hpp" #include #include #include #include "plugins/ipc/ipc-activator.hpp" #include "wayfire/signal-provider.hpp" const std::string grid_view_id = "grid-view"; class wf_grid_slot_data : public wf::custom_data_t { public: int slot; }; nonstd::observer_ptr ensure_grid_view(wayfire_toplevel_view view) { if (!view->has_data()) { wf::option_wrapper_t animation_type{"grid/type"}; wf::option_wrapper_t duration{"grid/duration"}; wf::grid::grid_animation_t::type_t type = wf::grid::grid_animation_t::NONE; if (animation_type.value() == "crossfade") { type = wf::grid::grid_animation_t::CROSSFADE; } else if (animation_type.value() == "wobbly") { type = wf::grid::grid_animation_t::WOBBLY; } view->store_data( std::make_unique(view, type, duration)); } return view->get_data(); } class wayfire_grid : public wf::plugin_interface_t, public wf::per_output_tracker_mixin_t<> { std::vector slots = {"unused", "bl", "b", "br", "l", "c", "r", "tl", "t", "tr"}; wf::ipc_activator_t bindings[10]; wf::ipc_activator_t restore{"grid/restore"}; wf::plugin_activation_data_t grab_interface{ .name = "grid", .capabilities = wf::CAPABILITY_MANAGE_DESKTOP, }; wf::ipc_activator_t::handler_t handle_restore = [=] (wf::output_t *wo, wayfire_view view) { if (!wo->can_activate_plugin(&grab_interface)) { return false; } auto toplevel = toplevel_cast(view); if (!view) { return false; } wf::get_core().default_wm->tile_request(toplevel, 0); return true; }; public: void init() override { init_output_tracking(); restore.set_handler(handle_restore); for (int i = 1; i < 10; i++) { bindings[i].load_from_xml_option("grid/slot_" + slots[i]); bindings[i].set_handler([=] (wf::output_t *wo, wayfire_view view) { if (!wo->can_activate_plugin(wf::CAPABILITY_MANAGE_DESKTOP)) { return false; } if (auto toplevel = toplevel_cast(view)) { handle_slot(toplevel, i); return true; } return false; }); } } void handle_new_output(wf::output_t *output) override { output->connect(&on_workarea_changed); output->connect(&on_maximize_signal); output->connect(&on_fullscreen_signal); output->connect(&on_tiled); } void handle_output_removed(wf::output_t *output) override { // no-op } void fini() override { fini_output_tracking(); } bool can_adjust_view(wayfire_toplevel_view view) { uint32_t req_actions = wf::VIEW_ALLOW_MOVE | wf::VIEW_ALLOW_RESIZE; return (view->get_allowed_actions() & req_actions) == req_actions; } void handle_slot(wayfire_toplevel_view view, int slot, wf::point_t delta = {0, 0}) { if (!can_adjust_view(view)) { return; } view->get_data_safe()->slot = slot; auto slot_geometry = wf::grid::get_slot_dimensions(view->get_output(), slot) + delta; ensure_grid_view(view)->adjust_target_geometry( slot_geometry, wf::grid::get_tiled_edges_for_slot(slot)); } wf::signal::connection_t on_workarea_changed = [=] (wf::workarea_changed_signal *ev) { for (auto& view : ev->output->wset()->get_views(wf::WSET_MAPPED_ONLY)) { auto data = view->get_data_safe(); /* Detect if the view was maximized outside of the grid plugin */ auto wm = view->get_pending_geometry(); if (view->pending_tiled_edges() && (wm.width == ev->old_workarea.width) && (wm.height == ev->old_workarea.height)) { data->slot = wf::grid::SLOT_CENTER; } if (!data->slot) { continue; } /* Workarea changed, and we have a view which is tiled into some slot. * We need to make sure it remains in its slot. So we calculate the * viewport of the view, and tile it there */ auto output_geometry = ev->output->get_relative_geometry(); int vx = std::floor(1.0 * wm.x / output_geometry.width); int vy = std::floor(1.0 * wm.y / output_geometry.height); handle_slot(view, data->slot, {vx *output_geometry.width, vy * output_geometry.height}); } }; wf::geometry_t adjust_for_workspace(std::shared_ptr wset, wf::geometry_t geometry, wf::point_t workspace) { auto delta_ws = workspace - wset->get_current_workspace(); auto scr_size = wset->get_last_output_geometry().value(); geometry.x += delta_ws.x * scr_size.width; geometry.y += delta_ws.y * scr_size.height; return geometry; } wf::signal::connection_t on_maximize_signal = [=] (wf::view_tile_request_signal *data) { if (data->carried_out || (data->desired_size.width <= 0) || !data->view->get_output() || !data->view->get_wset() || !can_adjust_view(data->view)) { return; } data->carried_out = true; uint32_t slot = wf::grid::get_slot_from_tiled_edges(data->edges); if (slot > 0) { data->desired_size = wf::grid::get_slot_dimensions(data->view->get_output(), slot); } data->view->get_data_safe()->slot = slot; ensure_grid_view(data->view)->adjust_target_geometry( adjust_for_workspace(data->view->get_wset(), data->desired_size, data->workspace), wf::grid::get_tiled_edges_for_slot(slot)); }; wf::signal::connection_t on_fullscreen_signal = [=] (wf::view_fullscreen_request_signal *data) { static const std::string fs_data_name = "grid-saved-fs"; if (data->carried_out || (data->desired_size.width <= 0) || !data->view->get_output() || !data->view->get_wset() || !can_adjust_view(data->view)) { return; } data->carried_out = true; ensure_grid_view(data->view)->adjust_target_geometry( adjust_for_workspace(data->view->get_wset(), data->desired_size, data->workspace), -1); }; wf::signal::connection_t on_tiled = [=] (wf::view_tiled_signal *ev) { if (!ev->view->has_data()) { return; } auto data = ev->view->get_data_safe(); if (ev->new_edges != wf::grid::get_tiled_edges_for_slot(data->slot)) { ev->view->erase_data(); } }; }; DECLARE_WAYFIRE_PLUGIN(wayfire_grid); wayfire-0.8.1/plugins/grid/meson.build000066400000000000000000000007021457431457600177550ustar00rootroot00000000000000grid_inc = include_directories('.') all_include_dirs = [wayfire_api_inc, wayfire_conf_inc, plugins_common_inc, wobbly_inc, grid_inc] all_deps = [wlroots, pixman, wfconfig, wftouch, cairo, json] shared_module('grid', ['grid.cpp'], include_directories: all_include_dirs, dependencies: all_deps, install: true, install_dir: conf_data.get('PLUGIN_PATH')) install_subdir('wayfire', install_dir: get_option('includedir')) wayfire-0.8.1/plugins/grid/wayfire/000077500000000000000000000000001457431457600172625ustar00rootroot00000000000000wayfire-0.8.1/plugins/grid/wayfire/plugins/000077500000000000000000000000001457431457600207435ustar00rootroot00000000000000wayfire-0.8.1/plugins/grid/wayfire/plugins/crossfade.hpp000066400000000000000000000231211457431457600234240ustar00rootroot00000000000000#pragma once #include #include "wayfire/core.hpp" #include "wayfire/geometry.hpp" #include "wayfire/opengl.hpp" #include "wayfire/region.hpp" #include "wayfire/scene-render.hpp" #include "wayfire/scene.hpp" #include "wayfire/signal-definitions.hpp" #include "wayfire/signal-provider.hpp" #include #include #include #include #include #include #include #include #include #include #include namespace wf { namespace grid { /** * A transformer used for a simple crossfade + scale animation. * * It fades out the scaled contents from original_buffer, and fades in the * current contents of the view, based on the alpha value in the transformer. */ class crossfade_node_t : public scene::view_2d_transformer_t { public: wayfire_view view; // The contents of the view before the change. wf::render_target_t original_buffer; public: wf::geometry_t displayed_geometry; double overlay_alpha; crossfade_node_t(wayfire_toplevel_view view) : view_2d_transformer_t(view) { displayed_geometry = view->get_geometry(); this->view = view; auto root_node = view->get_surface_root_node(); const wf::geometry_t bbox = root_node->get_bounding_box(); original_buffer.geometry = view->get_geometry(); original_buffer.scale = view->get_output()->handle->scale; OpenGL::render_begin(); auto w = original_buffer.scale * original_buffer.geometry.width; auto h = original_buffer.scale * original_buffer.geometry.height; original_buffer.allocate(w, h); OpenGL::render_end(); std::vector instances; root_node->gen_render_instances(instances, [] (auto) {}, view->get_output()); scene::render_pass_params_t params; params.background_color = {0, 0, 0, 0}; params.damage = bbox; params.target = original_buffer; params.instances = &instances; scene::run_render_pass(params, scene::RPASS_CLEAR_BACKGROUND); } ~crossfade_node_t() { OpenGL::render_begin(); original_buffer.release(); OpenGL::render_end(); } std::string stringify() const override { return "crossfade"; } void gen_render_instances(std::vector& instances, scene::damage_callback push_damage, wf::output_t *shown_on) override; }; class crossfade_render_instance_t : public scene::render_instance_t { crossfade_node_t *self; wf::signal::connection_t on_damage; public: crossfade_render_instance_t(crossfade_node_t *self, scene::damage_callback push_damage) { this->self = self; scene::damage_callback push_damage_child = [=] (const wf::region_t&) { // XXX: we could attempt to calculate a meaningful damage, but // we update on each frame anyway so .. push_damage(self->get_bounding_box()); }; on_damage = [=] (auto) { push_damage(self->get_bounding_box()); }; self->connect(&on_damage); } void schedule_instructions( std::vector& instructions, const wf::render_target_t& target, wf::region_t& damage) override { instructions.push_back(wf::scene::render_instruction_t{ .instance = this, .target = target, .damage = damage & self->get_bounding_box(), }); } void render(const wf::render_target_t& target, const wf::region_t& region) override { double ra; const double N = 2; if (self->overlay_alpha < 0.5) { ra = std::pow(self->overlay_alpha * 2, 1.0 / N) / 2.0; } else { ra = std::pow((self->overlay_alpha - 0.5) * 2, N) / 2.0 + 0.5; } OpenGL::render_begin(target); for (auto& box : region) { target.logic_scissor(wlr_box_from_pixman_box(box)); OpenGL::render_texture({self->original_buffer.tex}, target, self->displayed_geometry, glm::vec4{1.0f, 1.0f, 1.0f, 1.0 - ra}); } OpenGL::render_end(); } }; inline void crossfade_node_t::gen_render_instances( std::vector& instances, scene::damage_callback push_damage, wf::output_t *shown_on) { // Step 2: render overlay (instances are sorted front-to-back) instances.push_back( std::make_unique(this, push_damage)); // Step 1: render the scaled view scene::view_2d_transformer_t::gen_render_instances( instances, push_damage, shown_on); } /** * A class used for crossfade/wobbly animation of a change in a view's geometry. */ class grid_animation_t : public wf::custom_data_t { public: enum type_t { CROSSFADE, WOBBLY, NONE, }; /** * Create an animation object for the given view. * * @param type Indicates which animation method to use. * @param duration Indicates the duration of the animation (only for crossfade) */ grid_animation_t(wayfire_toplevel_view view, type_t type, wf::option_sptr_t duration) { this->view = view; this->output = view->get_output(); this->type = type; this->animation = wf::geometry_animation_t{duration}; output->render->add_effect(&pre_hook, wf::OUTPUT_EFFECT_PRE); output->connect(&on_disappear); } /** * Set the view geometry and start animating towards that target using the * animation type. * * @param geometry The target geometry. * @param target_edges The tiled edges the view should have at the end of the * animation. If target_edges are -1, then the tiled edges of the view will * not be changed. */ void adjust_target_geometry(wf::geometry_t geometry, int32_t target_edges, wf::txn::transaction_uptr& tx) { // Apply the desired attributes to the view const auto& set_state = [&] () { if (target_edges >= 0) { wf::get_core().default_wm->update_last_windowed_geometry(view); view->toplevel()->pending().fullscreen = false; view->toplevel()->pending().tiled_edges = target_edges; } view->toplevel()->pending().geometry = geometry; tx->add_object(view->toplevel()); }; if (type != CROSSFADE) { /* Order is important here: first we set the view geometry, and * after that we set the snap request. Otherwise the wobbly plugin * will think the view actually moved */ set_state(); if (type == WOBBLY) { activate_wobbly(view); } return destroy(); } // Crossfade animation original = view->get_geometry(); animation.set_start(original); animation.set_end(geometry); animation.start(); // Add crossfade transformer ensure_view_transformer( view, wf::TRANSFORMER_2D, view); // Start the transition set_state(); } void adjust_target_geometry(wf::geometry_t geometry, int32_t target_edges) { auto tx = wf::txn::transaction_t::create(); adjust_target_geometry(geometry, target_edges, tx); wf::get_core().tx_manager->schedule_transaction(std::move(tx)); } ~grid_animation_t() { view->get_transformed_node()->rem_transformer(); output->render->rem_effect(&pre_hook); } grid_animation_t(const grid_animation_t &) = delete; grid_animation_t(grid_animation_t &&) = delete; grid_animation_t& operator =(const grid_animation_t&) = delete; grid_animation_t& operator =(grid_animation_t&&) = delete; protected: wf::effect_hook_t pre_hook = [=] () { if (!animation.running()) { return destroy(); } if (view->get_geometry() != original) { original = view->get_geometry(); animation.set_end(original); } auto tr = view->get_transformed_node()->get_transformer(); view->get_transformed_node()->begin_transform_update(); tr->displayed_geometry = animation; auto geometry = view->get_geometry(); tr->scale_x = animation.width / geometry.width; tr->scale_y = animation.height / geometry.height; tr->translation_x = (animation.x + animation.width / 2) - (geometry.x + geometry.width / 2.0); tr->translation_y = (animation.y + animation.height / 2) - (geometry.y + geometry.height / 2.0); tr->overlay_alpha = animation.progress(); view->get_transformed_node()->end_transform_update(); }; void destroy() { view->erase_data(); } wf::geometry_t original; wayfire_toplevel_view view; wf::output_t *output; wf::signal::connection_t on_disappear = [=] (view_disappeared_signal *ev) { if (ev->view == view) { destroy(); } }; wf::geometry_animation_t animation; type_t type; }; } } wayfire-0.8.1/plugins/grid/wayfire/plugins/grid.hpp000066400000000000000000000032211457431457600223770ustar00rootroot00000000000000#pragma once #include #include #include namespace wf { namespace grid { /** * The slot where a view can be placed with grid. * BL = bottom-left, TR = top-right, etc. */ enum slot_t { SLOT_NONE = 0, SLOT_BL = 1, SLOT_BOTTOM = 2, SLOT_BR = 3, SLOT_LEFT = 4, SLOT_CENTER = 5, SLOT_RIGHT = 6, SLOT_TL = 7, SLOT_TOP = 8, SLOT_TR = 9, }; /* * 7 8 9 * 4 5 6 * 1 2 3 */ inline uint32_t get_tiled_edges_for_slot(uint32_t slot) { if (slot == 0) { return 0; } uint32_t edges = wf::TILED_EDGES_ALL; if (slot % 3 == 0) { edges &= ~WLR_EDGE_LEFT; } if (slot % 3 == 1) { edges &= ~WLR_EDGE_RIGHT; } if (slot <= 3) { edges &= ~WLR_EDGE_TOP; } if (slot >= 7) { edges &= ~WLR_EDGE_BOTTOM; } return edges; } inline uint32_t get_slot_from_tiled_edges(uint32_t edges) { for (int slot = 0; slot <= 9; slot++) { if (get_tiled_edges_for_slot(slot) == edges) { return slot; } } return 0; } /* * 7 8 9 * 4 5 6 * 1 2 3 * */ inline wf::geometry_t get_slot_dimensions(wf::output_t *output, int n) { auto area = output->workarea->get_workarea(); int w2 = area.width / 2; int h2 = area.height / 2; if (n % 3 == 1) { area.width = w2; } if (n % 3 == 0) { area.width = w2, area.x += w2; } if (n >= 7) { area.height = h2; } else if (n <= 3) { area.height = h2, area.y += h2; } return area; } } } wayfire-0.8.1/plugins/ipc/000077500000000000000000000000001457431457600154425ustar00rootroot00000000000000wayfire-0.8.1/plugins/ipc/demo-ipc.cpp000066400000000000000000000106701457431457600176470ustar00rootroot00000000000000#include #include #include #include #include #include #include "ipc-helpers.hpp" #include "ipc-method-repository.hpp" #include "wayfire/core.hpp" #include "wayfire/plugins/common/shared-core-data.hpp" #include "wayfire/signal-definitions.hpp" #include "wayfire/signal-provider.hpp" class wayfire_demo_ipc : public wf::plugin_interface_t { public: void init() override { method_repository->register_method("demo-ipc/watch", on_client_watch); method_repository->register_method("demo-ipc/view-info", get_view_info); method_repository->register_method("demo-ipc/output-info", get_output_info); method_repository->register_method("demo-ipc/view-set-geometry", set_view_geometry); method_repository->connect(&on_client_disconnected); wf::get_core().connect(&on_view_mapped); } void fini() override { method_repository->unregister_method("demo-ipc/watch"); method_repository->unregister_method("demo-ipc/view-info"); method_repository->unregister_method("demo-ipc/output-info"); method_repository->unregister_method("demo-ipc/view-set-geometry"); } wf::ipc::method_callback_full on_client_watch = [=] (nlohmann::json data, wf::ipc::client_interface_t *client) { clients.insert(client); return wf::ipc::json_ok(); }; wf::ipc::method_callback get_view_info = [=] (nlohmann::json data) { WFJSON_EXPECT_FIELD(data, "id", number_integer); for (auto view : wf::get_core().get_all_views()) { if (view->get_id() == data["id"]) { auto response = wf::ipc::json_ok(); response["info"] = view_to_json(view); return response; } } return wf::ipc::json_error("no such view"); }; wf::ipc::method_callback get_output_info = [=] (nlohmann::json data) { WFJSON_EXPECT_FIELD(data, "id", number_integer); auto wo = wf::ipc::find_output_by_id(data["id"]); if (!wo) { return wf::ipc::json_error("output not found"); } auto response = wf::ipc::json_ok(); response["info"]["name"] = wo->to_string(); response["info"]["geometry"] = wf::ipc::geometry_to_json(wo->get_layout_geometry()); return response; }; wf::ipc::method_callback set_view_geometry = [=] (nlohmann::json data) { WFJSON_EXPECT_FIELD(data, "id", number_integer); WFJSON_EXPECT_FIELD(data, "geometry", object); if (auto view = wf::ipc::find_view_by_id(data["id"])) { if (auto geometry = wf::ipc::geometry_from_json(data["geometry"])) { if (auto toplevel = toplevel_cast(view)) { toplevel->set_geometry(geometry.value()); return wf::ipc::json_ok(); } return wf::ipc::json_error("view is not toplevel"); } return wf::ipc::json_error("geometry incorrect"); } return wf::ipc::json_error("view not found"); }; private: wf::shared_data::ref_ptr_t method_repository; std::set clients; wf::signal::connection_t on_client_disconnected = [=] (wf::ipc::client_disconnected_signal *ev) { clients.erase(ev->client); }; wf::signal::connection_t on_view_mapped = [=] (wf::view_mapped_signal *ev) { nlohmann::json event; event["event"] = "view-mapped"; event["view"] = view_to_json(ev->view); for (auto& client : clients) { client->send_json(event); } }; nlohmann::json view_to_json(wayfire_view view) { nlohmann::json description; description["id"] = view->get_id(); description["app-id"] = view->get_app_id(); description["title"] = view->get_title(); auto toplevel = wf::toplevel_cast(view); description["geometry"] = wf::ipc::geometry_to_json(toplevel ? toplevel->get_geometry() : view->get_bounding_box()); description["output"] = view->get_output() ? view->get_output()->get_id() : -1; return description; } }; DECLARE_WAYFIRE_PLUGIN(wayfire_demo_ipc); wayfire-0.8.1/plugins/ipc/ipc-activator.hpp000066400000000000000000000063331457431457600207250ustar00rootroot00000000000000#pragma once #include #include #include "ipc-helpers.hpp" #include "ipc-method-repository.hpp" #include "wayfire/bindings.hpp" #include "wayfire/core.hpp" #include "wayfire/option-wrapper.hpp" #include "wayfire/output.hpp" #include "wayfire/plugins/common/shared-core-data.hpp" #include "wayfire/seat.hpp" namespace wf { /** * The IPC activator class is a helper class which combines an IPC method with a normal activator binding. */ class ipc_activator_t { public: ipc_activator_t() {} ipc_activator_t(std::string name) { load_from_xml_option(name); } void load_from_xml_option(std::string name) { activator.load_option(name); wf::get_core().bindings->add_activator(activator, &activator_cb); repo->register_method(name, ipc_cb); this->name = name; } ~ipc_activator_t() { wf::get_core().bindings->rem_binding(&activator_cb); repo->unregister_method(name); } /** * The handler is given over an optional output and a view to execute the action for. * Note that the output is always set (if not explicitly given, then it is set to the currently focused * output), however the view might be nullptr if not indicated in the IPC call or in the case of * activators, no suitable view could be found for the cursor/keyboard focus. */ using handler_t = std::function; void set_handler(handler_t hnd) { this->hnd = hnd; } private: wf::option_wrapper_t activator; shared_data::ref_ptr_t repo; std::string name; handler_t hnd; activator_callback activator_cb = [=] (const wf::activator_data_t& data) -> bool { if (hnd) { return hnd(choose_output(), choose_view(data.source)); } return false; }; ipc::method_callback ipc_cb = [=] (const nlohmann::json& data) { WFJSON_OPTIONAL_FIELD(data, "output_id", number_integer); WFJSON_OPTIONAL_FIELD(data, "view_id", number_integer); wf::output_t *wo = wf::get_core().seat->get_active_output(); if (data.contains("output_id")) { wo = ipc::find_output_by_id(data["output_id"]); if (!wo) { return ipc::json_error("output id not found!"); } } wayfire_view view; if (data.contains("view_id")) { view = ipc::find_view_by_id(data["view_id"]); if (!view) { return ipc::json_error("view id not found!"); } } if (hnd) { hnd(wo, view); } return ipc::json_ok(); }; wf::output_t *choose_output() { return wf::get_core().seat->get_active_output(); } wayfire_view choose_view(wf::activator_source_t source) { wayfire_view view; if (source == wf::activator_source_t::BUTTONBINDING) { view = wf::get_core().get_cursor_focus_view(); } else { view = wf::get_core().seat->get_active_view(); } return view; } }; } wayfire-0.8.1/plugins/ipc/ipc-helpers.hpp000066400000000000000000000051631457431457600203730ustar00rootroot00000000000000#pragma once #include "wayfire/geometry.hpp" #include #include #include #include #include #include #include namespace wf { namespace ipc { inline wayfire_view find_view_by_id(uint32_t id) { for (auto view : wf::get_core().get_all_views()) { if (view->get_id() == id) { return view; } } return nullptr; } inline wf::output_t *find_output_by_id(int32_t id) { for (auto wo : wf::get_core().output_layout->get_outputs()) { if ((int)wo->get_id() == id) { return wo; } } return nullptr; } inline wf::workspace_set_t *find_workspace_set_by_index(int32_t index) { for (auto wset : wf::workspace_set_t::get_all()) { if ((int)wset->get_index() == index) { return wset.get(); } } return nullptr; } inline nlohmann::json geometry_to_json(wf::geometry_t g) { nlohmann::json j; j["x"] = g.x; j["y"] = g.y; j["width"] = g.width; j["height"] = g.height; return j; } inline std::optional geometry_from_json(const nlohmann::json& j) { #define CHECK(field, type) (j.contains(field) && j[field].is_number_ ## type()) if (!CHECK("x", integer) || !CHECK("y", integer) || !CHECK("width", unsigned) || !CHECK("height", unsigned)) { return {}; } #undef CHECK return wf::geometry_t{ .x = j["x"], .y = j["y"], .width = j["width"], .height = j["height"], }; } inline nlohmann::json point_to_json(wf::point_t p) { nlohmann::json j; j["x"] = p.x; j["y"] = p.y; return j; } inline std::optional point_from_json(const nlohmann::json& j) { #define CHECK(field, type) (j.contains(field) && j[field].is_number_ ## type()) if (!CHECK("x", integer) || !CHECK("y", integer)) { return {}; } #undef CHECK return wf::point_t{ .x = j["x"], .y = j["y"], }; } inline nlohmann::json dimensions_to_json(wf::dimensions_t d) { nlohmann::json j; j["width"] = d.width; j["height"] = d.height; return j; } inline std::optional dimensions_from_json(const nlohmann::json& j) { #define CHECK(field, type) (j.contains(field) && j[field].is_number_ ## type()) if (!CHECK("width", integer) || !CHECK("height", integer)) { return {}; } #undef CHECK return wf::dimensions_t{ .width = j["width"], .height = j["height"], }; } } } wayfire-0.8.1/plugins/ipc/ipc-method-repository.hpp000066400000000000000000000076731457431457600224360ustar00rootroot00000000000000#pragma once #include #include #include #include "wayfire/signal-provider.hpp" namespace wf { namespace ipc { /** * A client_interface_t represents a client which has connected to the IPC socket. * It can be used by plugins to send back data to a specific client. */ class client_interface_t { public: virtual void send_json(nlohmann::json json) = 0; }; /** * A signal emitted on the ipc method repository when a client disconnects. */ struct client_disconnected_signal { client_interface_t *client; }; /** * An IPC method has a name and a callback. The callback is a simple function which takes a json object which * contains the method's parameters and returns the result of the operation. */ using method_callback = std::function; /** * Same as @method_callback, but also supports getting information about the connected ipc client. */ using method_callback_full = std::function; /** * The IPC method repository keeps track of all registered IPC methods. It can be used even without the IPC * plugin itself, as it facilitates inter-plugin calls similarly to signals. * * The method_repository_t is a singleton and is accessed by creating a shared_data::ref_ptr_t to it. */ class method_repository_t : public wf::signal::provider_t { public: /** * Register a new method to the method repository. If the method already exists, the old handler will be * overwritten. */ void register_method(std::string method, method_callback_full handler) { this->methods[method] = handler; } /** * Register a new method to the method repository. If the method already exists, the old handler will be * overwritten. */ void register_method(std::string method, method_callback handler) { this->methods[method] = [handler] (const nlohmann::json& data, client_interface_t*) { return handler(data); }; } /** * Remove the last registered handler for the given method. */ void unregister_method(std::string method) { this->methods.erase(method); } /** * Call an IPC method with the given name and given parameters. * If the method was not registered, a JSON object containing an error will be returned. */ nlohmann::json call_method(std::string method, nlohmann::json data, client_interface_t *client = nullptr) { if (this->methods.count(method)) { return this->methods[method](std::move(data), client); } return { {"error", "No such method found!"} }; } method_repository_t() { register_method("list-methods", [this] (auto) { nlohmann::json response; response["methods"] = nlohmann::json::array(); for (auto& [method, _] : methods) { response["methods"].push_back(method); } return response; }); } private: std::map methods; }; // A few helper definitions for IPC method implementations. inline nlohmann::json json_ok() { return nlohmann::json{ {"result", "ok"} }; } inline nlohmann::json json_error(std::string msg) { return nlohmann::json{ {"error", std::string(msg)} }; } #define WFJSON_EXPECT_FIELD(data, field, type) \ if (!data.count(field)) \ { \ return wf::ipc::json_error("Missing \"" field "\""); \ } \ else if (!data[field].is_ ## type()) \ { \ return wf::ipc::json_error("Field \"" field "\" does not have the correct type " #type); \ } #define WFJSON_OPTIONAL_FIELD(data, field, type) \ if (data.count(field) && !data[field].is_ ## type()) \ { \ return wf::ipc::json_error("Field \"" + std::string(field) + \ "\" does not have the correct type " #type); \ } } } wayfire-0.8.1/plugins/ipc/ipc.cpp000066400000000000000000000172001457431457600167210ustar00rootroot00000000000000#include "ipc.hpp" #include "wayfire/plugins/common/shared-core-data.hpp" #include #include #include #include #include #include #include #include /** * Handle WL_EVENT_READABLE on the socket. * Indicates a new connection. */ int wl_loop_handle_ipc_fd_connection(int, uint32_t, void *data) { (*((std::function*)data))(); return 0; } wf::ipc::server_t::server_t() { accept_new_client = [=] () { do_accept_new_client(); }; } void wf::ipc::server_t::init(std::string socket_path) { this->fd = setup_socket(socket_path.c_str()); if (fd == -1) { LOGE("Failed to create debug IPC socket!"); return; } listen(fd, 3); source = wl_event_loop_add_fd(wl_display_get_event_loop(wf::get_core().display), fd, WL_EVENT_READABLE, wl_loop_handle_ipc_fd_connection, &accept_new_client); } wf::ipc::server_t::~server_t() { if (fd != -1) { close(fd); unlink(saddr.sun_path); wl_event_source_remove(source); } } int wf::ipc::server_t::setup_socket(const char *address) { int fd = socket(AF_UNIX, SOCK_STREAM, 0); if (fd == -1) { return -1; } if (fcntl(fd, F_SETFD, FD_CLOEXEC) == -1) { return -1; } if (fcntl(fd, F_SETFL, O_NONBLOCK) == -1) { return -1; } // Ensure no old instance left after a crash or similar unlink(address); saddr.sun_family = AF_UNIX; strncpy(saddr.sun_path, address, sizeof(saddr.sun_path) - 1); int r = bind(fd, (sockaddr*)&saddr, sizeof(saddr)); if (r != 0) { LOGE("Failed to bind debug IPC socket at address ", address, " !"); // TODO: shutdown socket? return -1; } return fd; } void wf::ipc::server_t::do_accept_new_client() { // Heavily inspired by Sway int cfd = accept(this->fd, NULL, NULL); if (cfd == -1) { LOGW("Error accepting client connection"); return; } int flags; if (((flags = fcntl(cfd, F_GETFD)) == -1) || (fcntl(cfd, F_SETFD, flags | FD_CLOEXEC) == -1)) { LOGE("Failed setting CLOEXEC"); close(cfd); return; } if (((flags = fcntl(cfd, F_GETFL)) == -1) || (fcntl(cfd, F_SETFL, flags | O_NONBLOCK) == -1)) { LOGE("Failed setting NONBLOCK"); close(cfd); return; } this->clients.push_back(std::make_unique(this, cfd)); } void wf::ipc::server_t::client_disappeared(client_t *client) { LOGD("Removing IPC client ", client); client_disconnected_signal ev; ev.client = client; method_repository->emit(&ev); auto it = std::remove_if(clients.begin(), clients.end(), [&] (const auto& cl) { return cl.get() == client; }); clients.erase(it, clients.end()); } void wf::ipc::server_t::handle_incoming_message( client_t *client, nlohmann::json message) { client->send_json(method_repository->call_method(message["method"], message["data"], client)); } /* --------------------------- Per-client code ------------------------------*/ int wl_loop_handle_ipc_client_fd_event(int, uint32_t mask, void *data) { (*((std::function*)data))(mask); return 0; } static constexpr int MAX_MESSAGE_LEN = (1 << 20); static constexpr int HEADER_LEN = 4; wf::ipc::client_t::client_t(server_t *ipc, int fd) { LOGD("New IPC client, fd ", fd); this->fd = fd; this->ipc = ipc; auto ev_loop = wf::get_core().ev_loop; source = wl_event_loop_add_fd(ev_loop, fd, WL_EVENT_READABLE, wl_loop_handle_ipc_client_fd_event, &this->handle_fd_activity); // +1 for null byte at the end buffer.resize(MAX_MESSAGE_LEN + 1); this->handle_fd_activity = [=] (uint32_t event_mask) { handle_fd_incoming(event_mask); }; } // -1 error, 0 success, 1 try again later int wf::ipc::client_t::read_up_to(int n, int *available) { int need = n - current_buffer_valid; int want = std::min(need, *available); while (want > 0) { int r = read(fd, buffer.data() + current_buffer_valid, want); if (r <= 0) { LOGI("Read: EOF or error (%d) %s\n", r, strerror(errno)); return -1; } want -= r; *available -= r; current_buffer_valid += r; } if (current_buffer_valid < n) { // didn't read all n letters return 1; } return 0; } void wf::ipc::client_t::handle_fd_incoming(uint32_t event_mask) { if (event_mask & (WL_EVENT_ERROR | WL_EVENT_HANGUP)) { ipc->client_disappeared(this); // this no longer exists return; } int available = 0; if (ioctl(this->fd, FIONREAD, &available) != 0) { LOGE("Failed to inspect message buffer!"); ipc->client_disappeared(this); return; } while (available > 0) { if (current_buffer_valid < HEADER_LEN) { if (read_up_to(HEADER_LEN, &available) < 0) { ipc->client_disappeared(this); return; } continue; } const uint32_t len = *((uint32_t*)buffer.data()); if (len > MAX_MESSAGE_LEN - HEADER_LEN) { LOGE("Client tried to pass too long a message!"); ipc->client_disappeared(this); return; } const int next_target = HEADER_LEN + len; int r = read_up_to(next_target, &available); if (r < 0) { ipc->client_disappeared(this); return; } if (r > 0) { // Try again continue; } // Finally, received the message, make sure we have a terminating NULL byte buffer[current_buffer_valid] = '\0'; char *str = buffer.data() + HEADER_LEN; auto message = nlohmann::json::parse(str, nullptr, false); if (message.is_discarded()) { LOGE("Client's message could not be parsed: ", str); ipc->client_disappeared(this); return; } if (!message.contains("method")) { LOGE("Client's message does not contain a method to be called!"); ipc->client_disappeared(this); return; } ipc->handle_incoming_message(this, std::move(message)); // Reset for next message current_buffer_valid = 0; } } wf::ipc::client_t::~client_t() { wl_event_source_remove(source); shutdown(fd, SHUT_RDWR); close(this->fd); } static bool write_exact(int fd, char *buf, int n) { while (n > 0) { int w = write(fd, buf, n); if (w <= 0) { return false; } n -= w; } return true; } void wf::ipc::client_t::send_json(nlohmann::json json) { std::string buffer = json.dump(); uint32_t len = buffer.length(); write_exact(fd, (char*)&len, 4); write_exact(fd, buffer.data(), len); } namespace wf { class ipc_plugin_t : public wf::plugin_interface_t { private: shared_data::ref_ptr_t server; public: void init() override { char *pre_socket = getenv("_WAYFIRE_SOCKET"); const auto& dname = wf::get_core().wayland_display; std::string socket = pre_socket ?: "/tmp/wayfire-" + dname + ".socket"; setenv("WAYFIRE_SOCKET", socket.c_str(), 1); server->init(socket); } bool is_unloadable() override { return false; } int get_order_hint() const override { return INT_MIN; } }; } // namespace wf DECLARE_WAYFIRE_PLUGIN(wf::ipc_plugin_t); wayfire-0.8.1/plugins/ipc/ipc.hpp000066400000000000000000000032071457431457600167300ustar00rootroot00000000000000#pragma once #include #include #include #include #include #include "ipc-method-repository.hpp" namespace wf { namespace ipc { /** * Represents a single connected client to the IPC socket. */ class server_t; class client_t : public client_interface_t { public: client_t(server_t *server, int client_fd); ~client_t(); void send_json(nlohmann::json json) override; private: int fd; wl_event_source *source; server_t *ipc; int current_buffer_valid = 0; std::vector buffer; int read_up_to(int n, int *available); /** Handle incoming data on the socket */ std::function handle_fd_activity; void handle_fd_incoming(uint32_t); }; /** * The IPC server is a singleton object accessed via shared_data::ref_ptr_t. * It represents the IPC socket used for communication with clients. */ class server_t { public: server_t(); void init(std::string socket_path); ~server_t(); private: friend class client_t; wf::shared_data::ref_ptr_t method_repository; void handle_incoming_message(client_t *client, nlohmann::json message); void client_disappeared(client_t *client); int fd = -1; /** * Setup a socket at the given address, and set it as CLOEXEC and non-blocking. */ int setup_socket(const char *address); sockaddr_un saddr; wl_event_source *source; std::vector> clients; std::function accept_new_client; void do_accept_new_client(); }; } } wayfire-0.8.1/plugins/ipc/meson.build000066400000000000000000000017511457431457600176100ustar00rootroot00000000000000evdev = dependency('libevdev') ipc_include_dirs = include_directories('.') ipc = shared_module('ipc', ['ipc.cpp'], include_directories: [wayfire_api_inc, wayfire_conf_inc, plugins_common_inc], dependencies: [wlroots, pixman, wfconfig, wftouch, json, evdev], install: true, install_dir: conf_data.get('PLUGIN_PATH')) stipc = shared_module('stipc', ['stipc.cpp'], include_directories: [wayfire_api_inc, wayfire_conf_inc, plugins_common_inc], dependencies: [wlroots, pixman, wfconfig, wftouch, json, evdev], install: true, install_dir: conf_data.get('PLUGIN_PATH')) demoipc = shared_module('demo-ipc', ['demo-ipc.cpp'], include_directories: [wayfire_api_inc, wayfire_conf_inc, plugins_common_inc], dependencies: [wlroots, pixman, wfconfig, wftouch, json, evdev], install: true, install_dir: conf_data.get('PLUGIN_PATH')) install_headers(['ipc-method-repository.hpp', 'ipc-helpers.hpp', 'ipc-activator.hpp'], subdir: 'wayfire/plugins/ipc') wayfire-0.8.1/plugins/ipc/stipc.cpp000066400000000000000000000477141457431457600173050ustar00rootroot00000000000000#include "ipc-method-repository.hpp" #include "wayfire/plugin.hpp" #include "wayfire/plugins/common/shared-core-data.hpp" #include "wayfire/toplevel-view.hpp" #include "wayfire/util.hpp" #include #include #include #include #include #include "src/view/view-impl.hpp" #include #define WAYFIRE_PLUGIN #include #include extern "C" { #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include } #include #include static void locate_wayland_backend(wlr_backend *backend, void *data) { if (wlr_backend_is_wl(backend)) { wlr_backend **result = (wlr_backend**)data; *result = backend; } } namespace wf { static const struct wlr_pointer_impl pointer_impl = { .name = "stipc-pointer", }; static void led_update(wlr_keyboard *keyboard, uint32_t leds) {} static const struct wlr_keyboard_impl keyboard_impl = { .name = "stipc-keyboard", .led_update = led_update, }; static const struct wlr_touch_impl touch_impl = { .name = "stipc-touch-device", }; static const struct wlr_tablet_impl tablet_impl = { .name = "stipc-tablet", }; static const struct wlr_tablet_pad_impl tablet_pad_impl = { .name = "stipc-tablet-pad", }; static void init_wlr_tool(wlr_tablet_tool *tablet_tool) { std::memset(tablet_tool, 0, sizeof(*tablet_tool)); tablet_tool->type = WLR_TABLET_TOOL_TYPE_PEN; tablet_tool->pressure = true; wl_signal_init(&tablet_tool->events.destroy); } class headless_input_backend_t { public: wlr_backend *backend; wlr_pointer pointer; wlr_keyboard keyboard; wlr_touch touch; wlr_tablet tablet; wlr_tablet_tool tablet_tool; wlr_tablet_pad tablet_pad; headless_input_backend_t() { auto& core = wf::get_core(); backend = wlr_headless_backend_create(core.display); wlr_multi_backend_add(core.backend, backend); wlr_pointer_init(&pointer, &pointer_impl, "stipc_pointer"); wlr_keyboard_init(&keyboard, &keyboard_impl, "stipc_keyboard"); wlr_touch_init(&touch, &touch_impl, "stipc_touch"); wlr_tablet_init(&tablet, &tablet_impl, "stipc_tablet_tool"); wlr_tablet_pad_init(&tablet_pad, &tablet_pad_impl, "stipc_tablet_pad"); init_wlr_tool(&tablet_tool); wl_signal_emit_mutable(&backend->events.new_input, &pointer.base); wl_signal_emit_mutable(&backend->events.new_input, &keyboard.base); wl_signal_emit_mutable(&backend->events.new_input, &touch.base); wl_signal_emit_mutable(&backend->events.new_input, &tablet.base); wl_signal_emit_mutable(&backend->events.new_input, &tablet_pad.base); if (core.get_current_state() == compositor_state_t::RUNNING) { wlr_backend_start(backend); } wl_signal_emit_mutable(&tablet_pad.events.attach_tablet, &tablet_tool); } ~headless_input_backend_t() { auto& core = wf::get_core(); wlr_pointer_finish(&pointer); wlr_keyboard_finish(&keyboard); wlr_touch_finish(&touch); wlr_tablet_finish(&tablet); wlr_tablet_pad_finish(&tablet_pad); wlr_multi_backend_remove(core.backend, backend); wlr_backend_destroy(backend); } void do_key(uint32_t key, wl_keyboard_key_state state) { wlr_keyboard_key_event ev; ev.keycode = key; ev.state = state; ev.update_state = true; ev.time_msec = get_current_time(); wlr_keyboard_notify_key(&keyboard, &ev); } void do_button(uint32_t button, wlr_button_state state) { wlr_pointer_button_event ev; ev.pointer = &pointer; ev.button = button; ev.state = state; ev.time_msec = get_current_time(); wl_signal_emit(&pointer.events.button, &ev); wl_signal_emit(&pointer.events.frame, NULL); } void do_motion(double x, double y) { auto cursor = wf::get_core().get_cursor_position(); wlr_pointer_motion_event ev; ev.pointer = &pointer; ev.time_msec = get_current_time(); ev.delta_x = ev.unaccel_dx = x - cursor.x; ev.delta_y = ev.unaccel_dy = y - cursor.y; wl_signal_emit(&pointer.events.motion, &ev); wl_signal_emit(&pointer.events.frame, NULL); } void convert_xy_to_relative(double *x, double *y) { auto layout = wf::get_core().output_layout->get_handle(); wlr_box box; wlr_output_layout_get_box(layout, NULL, &box); *x = 1.0 * (*x - box.x) / box.width; *y = 1.0 * (*y - box.y) / box.height; } void do_touch(int finger, double x, double y) { convert_xy_to_relative(&x, &y); if (!wf::get_core().get_touch_state().fingers.count(finger)) { wlr_touch_down_event ev; ev.touch = &touch; ev.time_msec = get_current_time(); ev.x = x; ev.y = y; ev.touch_id = finger; wl_signal_emit(&touch.events.down, &ev); } else { wlr_touch_motion_event ev; ev.touch = &touch; ev.time_msec = get_current_time(); ev.x = x; ev.y = y; ev.touch_id = finger; wl_signal_emit(&touch.events.motion, &ev); } wl_signal_emit(&touch.events.frame, NULL); } void do_touch_release(int finger) { wlr_touch_up_event ev; ev.touch = &touch; ev.time_msec = get_current_time(); ev.touch_id = finger; wl_signal_emit(&touch.events.up, &ev); wl_signal_emit(&touch.events.frame, NULL); } void do_tablet_proximity(bool prox_in, double x, double y) { convert_xy_to_relative(&x, &y); wlr_tablet_tool_proximity_event ev; ev.tablet = &tablet; ev.tool = &tablet_tool; ev.state = prox_in ? WLR_TABLET_TOOL_PROXIMITY_IN : WLR_TABLET_TOOL_PROXIMITY_OUT; ev.time_msec = get_current_time(); ev.x = x; ev.y = y; wl_signal_emit(&tablet.events.proximity, &ev); } void do_tablet_tip(bool tip_down, double x, double y) { convert_xy_to_relative(&x, &y); wlr_tablet_tool_tip_event ev; ev.tablet = &tablet; ev.tool = &tablet_tool; ev.state = tip_down ? WLR_TABLET_TOOL_TIP_DOWN : WLR_TABLET_TOOL_TIP_UP; ev.time_msec = get_current_time(); ev.x = x; ev.y = y; wl_signal_emit(&tablet.events.tip, &ev); } void do_tablet_button(uint32_t button, bool down) { wlr_tablet_tool_button_event ev; ev.tablet = &tablet; ev.tool = &tablet_tool; ev.button = button; ev.state = down ? WLR_BUTTON_PRESSED : WLR_BUTTON_RELEASED; ev.time_msec = get_current_time(); wl_signal_emit(&tablet.events.button, &ev); } void do_tablet_axis(double x, double y, double pressure) { convert_xy_to_relative(&x, &y); wlr_tablet_tool_axis_event ev; ev.tablet = &tablet; ev.tool = &tablet_tool; ev.time_msec = get_current_time(); ev.pressure = pressure; ev.x = x; ev.y = y; ev.updated_axes = WLR_TABLET_TOOL_AXIS_X | WLR_TABLET_TOOL_AXIS_Y | WLR_TABLET_TOOL_AXIS_PRESSURE; wl_signal_emit(&tablet.events.axis, &ev); } void do_tablet_pad_button(uint32_t button, bool state) { wlr_tablet_pad_button_event ev; ev.group = 0; ev.button = button; ev.state = state ? WLR_BUTTON_PRESSED : WLR_BUTTON_RELEASED; ev.mode = 0; ev.time_msec = get_current_time(); wl_signal_emit(&tablet_pad.events.button, &ev); } headless_input_backend_t(const headless_input_backend_t&) = delete; headless_input_backend_t(headless_input_backend_t&&) = delete; headless_input_backend_t& operator =(const headless_input_backend_t&) = delete; headless_input_backend_t& operator =(headless_input_backend_t&&) = delete; }; class stipc_plugin_t : public wf::plugin_interface_t { wf::shared_data::ref_ptr_t method_repository; public: void init() override { input = std::make_unique(); method_repository->register_method("stipc/create_wayland_output", create_wayland_output); method_repository->register_method("stipc/destroy_wayland_output", destroy_wayland_output); method_repository->register_method("stipc/feed_key", feed_key); method_repository->register_method("stipc/feed_button", feed_button); method_repository->register_method("stipc/move_cursor", move_cursor); method_repository->register_method("stipc/run", run); method_repository->register_method("stipc/ping", ping); method_repository->register_method("stipc/get_display", get_display); method_repository->register_method("stipc/layout_views", layout_views); method_repository->register_method("stipc/touch", do_touch); method_repository->register_method("stipc/touch_release", do_touch_release); method_repository->register_method("stipc/tablet/tool_proximity", do_tool_proximity); method_repository->register_method("stipc/tablet/tool_button", do_tool_button); method_repository->register_method("stipc/tablet/tool_axis", do_tool_axis); method_repository->register_method("stipc/tablet/tool_tip", do_tool_tip); method_repository->register_method("stipc/tablet/pad_button", do_pad_button); method_repository->register_method("stipc/delay_next_tx", delay_next_tx); method_repository->register_method("stipc/get_xwayland_pid", get_xwayland_pid); method_repository->register_method("stipc/get_xwayland_display", get_xwayland_display); } bool is_unloadable() override { return false; } ipc::method_callback layout_views = [] (nlohmann::json data) { auto views = wf::get_core().get_all_views(); WFJSON_EXPECT_FIELD(data, "views", array); for (auto v : data["views"]) { WFJSON_EXPECT_FIELD(v, "id", number); WFJSON_EXPECT_FIELD(v, "x", number); WFJSON_EXPECT_FIELD(v, "y", number); WFJSON_EXPECT_FIELD(v, "width", number); WFJSON_EXPECT_FIELD(v, "height", number); auto it = std::find_if(views.begin(), views.end(), [&] (auto& view) { return view->get_id() == v["id"]; }); if (it == views.end()) { return wf::ipc::json_error("Could not find view with id " + std::to_string((int)v["id"])); } auto toplevel = toplevel_cast(*it); if (!toplevel) { return wf::ipc::json_error("View is not toplevel view id " + std::to_string((int)v["id"])); } if (v.contains("output")) { WFJSON_EXPECT_FIELD(v, "output", string); auto wo = wf::get_core().output_layout->find_output(v["output"]); if (!wo) { return wf::ipc::json_error("Unknown output " + (std::string)v["output"]); } move_view_to_output(toplevel, wo, false); } wf::geometry_t g{v["x"], v["y"], v["width"], v["height"]}; toplevel->set_geometry(g); } return wf::ipc::json_ok(); }; ipc::method_callback create_wayland_output = [] (nlohmann::json) { auto backend = wf::get_core().backend; wlr_backend *wayland_backend = NULL; wlr_multi_for_each_backend(backend, locate_wayland_backend, &wayland_backend); if (!wayland_backend) { return wf::ipc::json_error("Wayfire is not running in nested wayland mode!"); } wlr_wl_output_create(wayland_backend); return wf::ipc::json_ok(); }; ipc::method_callback destroy_wayland_output = [] (nlohmann::json data) { WFJSON_EXPECT_FIELD(data, "output", string); auto output = wf::get_core().output_layout->find_output(data["output"]); if (!output) { return wf::ipc::json_error("Could not find output: \"" + (std::string)data["output"] + "\""); } wlr_output_destroy(output->handle); return wf::ipc::json_ok(); }; struct key_t { bool modifier; int code; }; std::variant parse_key(nlohmann::json data) { if (!data.count("combo") || !data["combo"].is_string()) { return std::string("Missing or wrong json type for `combo`!"); } std::string combo = data["combo"]; if (combo.size() < 4) { return std::string("Missing or wrong json type for `combo`!"); } // Check super modifier bool modifier = false; if (combo.substr(0, 2) == "S-") { modifier = true; combo = combo.substr(2); } int key = libevdev_event_code_from_name(EV_KEY, combo.c_str()); if (key == -1) { return std::string("Failed to parse combo \"" + combo + "\""); } return key_t{modifier, key}; } ipc::method_callback feed_key = [=] (nlohmann::json data) { WFJSON_EXPECT_FIELD(data, "key", string); WFJSON_EXPECT_FIELD(data, "state", boolean); std::string key = data["key"]; int keycode = libevdev_event_code_from_name(EV_KEY, key.c_str()); if (keycode == -1) { return wf::ipc::json_error("Failed to parse evdev key \"" + key + "\""); } if (data["state"]) { input->do_key(keycode, WL_KEYBOARD_KEY_STATE_PRESSED); } else { input->do_key(keycode, WL_KEYBOARD_KEY_STATE_RELEASED); } return wf::ipc::json_ok(); }; ipc::method_callback feed_button = [=] (nlohmann::json data) { auto result = parse_key(data); auto button = std::get_if(&result); if (!button) { return wf::ipc::json_error(std::get(result)); } if (!data.count("mode") || !data["mode"].is_string()) { return wf::ipc::json_error("No mode specified"); } auto mode = data["mode"]; if ((mode == "press") || (mode == "full")) { if (button->modifier) { input->do_key(KEY_LEFTMETA, WL_KEYBOARD_KEY_STATE_PRESSED); } input->do_button(button->code, WLR_BUTTON_PRESSED); } if ((mode == "release") || (mode == "full")) { input->do_button(button->code, WLR_BUTTON_RELEASED); if (button->modifier) { input->do_key(KEY_LEFTMETA, WL_KEYBOARD_KEY_STATE_RELEASED); } } return wf::ipc::json_ok(); }; ipc::method_callback move_cursor = [=] (nlohmann::json data) { if (!data.count("x") || !data.count("y") || !data["x"].is_number() || !data["y"].is_number()) { return wf::ipc::json_error("Move cursor needs double x/y arguments"); } double x = data["x"]; double y = data["y"]; input->do_motion(x, y); return wf::ipc::json_ok(); }; ipc::method_callback do_touch = [=] (nlohmann::json data) { WFJSON_EXPECT_FIELD(data, "finger", number_integer); WFJSON_EXPECT_FIELD(data, "x", number); WFJSON_EXPECT_FIELD(data, "y", number); input->do_touch(data["finger"], data["x"], data["y"]); return wf::ipc::json_ok(); }; ipc::method_callback do_touch_release = [=] (nlohmann::json data) { WFJSON_EXPECT_FIELD(data, "finger", number_integer); input->do_touch_release(data["finger"]); return wf::ipc::json_ok(); }; ipc::method_callback run = [=] (nlohmann::json data) { if (!data.count("cmd") || !data["cmd"].is_string()) { return wf::ipc::json_error("run command needs a cmd to run"); } auto response = wf::ipc::json_ok(); response["pid"] = wf::get_core().run(data["cmd"]); return response; }; ipc::method_callback ping = [=] (nlohmann::json data) { return wf::ipc::json_ok(); }; ipc::method_callback get_display = [=] (nlohmann::json data) { nlohmann::json dpy; dpy["wayland"] = wf::get_core().wayland_display; dpy["xwayland"] = wf::get_core().get_xwayland_display(); return dpy; }; ipc::method_callback do_tool_proximity = [=] (nlohmann::json data) { WFJSON_EXPECT_FIELD(data, "proximity_in", boolean); WFJSON_EXPECT_FIELD(data, "x", number); WFJSON_EXPECT_FIELD(data, "y", number); input->do_tablet_proximity(data["proximity_in"], data["x"], data["y"]); return wf::ipc::json_ok(); }; ipc::method_callback do_tool_button = [=] (nlohmann::json data) { WFJSON_EXPECT_FIELD(data, "button", number_integer); WFJSON_EXPECT_FIELD(data, "state", boolean); input->do_tablet_button(data["button"], data["state"]); return wf::ipc::json_ok(); }; ipc::method_callback do_tool_axis = [=] (nlohmann::json data) { WFJSON_EXPECT_FIELD(data, "x", number); WFJSON_EXPECT_FIELD(data, "y", number); WFJSON_EXPECT_FIELD(data, "pressure", number); input->do_tablet_axis(data["x"], data["y"], data["pressure"]); return wf::ipc::json_ok(); }; ipc::method_callback do_tool_tip = [=] (nlohmann::json data) { WFJSON_EXPECT_FIELD(data, "x", number); WFJSON_EXPECT_FIELD(data, "y", number); WFJSON_EXPECT_FIELD(data, "state", boolean); input->do_tablet_tip(data["state"], data["x"], data["y"]); return wf::ipc::json_ok(); }; ipc::method_callback do_pad_button = [=] (nlohmann::json data) { WFJSON_EXPECT_FIELD(data, "button", number_integer); WFJSON_EXPECT_FIELD(data, "state", boolean); input->do_tablet_pad_button(data["button"], data["state"]); return wf::ipc::json_ok(); }; class never_ready_object : public wf::txn::transaction_object_t { public: void commit() override {} void apply() override {} std::string stringify() const override { return "force-timeout"; } }; wf::signal::connection_t on_new_tx = [=] (wf::txn::new_transaction_signal *ev) { ev->tx->add_object(std::make_shared()); on_new_tx.disconnect(); }; ipc::method_callback delay_next_tx = [=] (nlohmann::json) { wf::get_core().tx_manager->connect(&on_new_tx); return wf::ipc::json_ok(); }; ipc::method_callback get_xwayland_pid = [=] (nlohmann::json) { auto response = wf::ipc::json_ok(); response["pid"] = wf::xwayland_get_pid(); return response; }; ipc::method_callback get_xwayland_display = [=] (nlohmann::json) { auto response = wf::ipc::json_ok(); response["display"] = wf::xwayland_get_display(); return response; }; std::unique_ptr input; }; } DECLARE_WAYFIRE_PLUGIN(wf::stipc_plugin_t); wayfire-0.8.1/plugins/meson.build000066400000000000000000000007371457431457600170400ustar00rootroot00000000000000subdir('common') system_json = dependency('nlohmann_json', required: false) if system_json.found() json = system_json else json = subproject('json').get_variable('nlohmann_json_dep') endif if get_option('debug_ipc') subdir('ipc') endif subdir('protocols') subdir('vswitch') subdir('wobbly') subdir('grid') subdir('decor') subdir('animate') subdir('cube') subdir('window-rules') subdir('blur') subdir('tile') subdir('wm-actions') subdir('scale') subdir('single_plugins') wayfire-0.8.1/plugins/protocols/000077500000000000000000000000001457431457600167135ustar00rootroot00000000000000wayfire-0.8.1/plugins/protocols/foreign-toplevel.cpp000066400000000000000000000231301457431457600226770ustar00rootroot00000000000000#include "wayfire/core.hpp" #include "wayfire/signal-definitions.hpp" #include #include "wayfire/view.hpp" #include #include #include #include #include #include "gtk-shell.hpp" #include "config.h" class wayfire_foreign_toplevel; using foreign_toplevel_map_type = std::map>; class wayfire_foreign_toplevel { wayfire_toplevel_view view; wlr_foreign_toplevel_handle_v1 *handle; foreign_toplevel_map_type *view_to_toplevel; public: wayfire_foreign_toplevel(wayfire_toplevel_view view, wlr_foreign_toplevel_handle_v1 *handle, foreign_toplevel_map_type *view_to_toplevel) { this->view = view; this->handle = handle; this->view_to_toplevel = view_to_toplevel; init_request_handlers(); toplevel_handle_v1_close_request.connect(&handle->events.request_close); toplevel_handle_v1_maximize_request.connect(&handle->events.request_maximize); toplevel_handle_v1_minimize_request.connect(&handle->events.request_minimize); toplevel_handle_v1_activate_request.connect(&handle->events.request_activate); toplevel_handle_v1_fullscreen_request.connect(&handle->events.request_fullscreen); toplevel_handle_v1_set_rectangle_request.connect(&handle->events.set_rectangle); toplevel_send_title(); toplevel_send_app_id(); toplevel_send_state(); toplevel_update_output(view->get_output(), true); view->connect(&on_title_changed); view->connect(&on_app_id_changed); view->connect(&on_set_output); view->connect(&on_tiled); view->connect(&on_minimized); view->connect(&on_fullscreen); view->connect(&on_activated); view->connect(&on_parent_changed); } ~wayfire_foreign_toplevel() { toplevel_handle_v1_close_request.disconnect(); toplevel_handle_v1_maximize_request.disconnect(); toplevel_handle_v1_minimize_request.disconnect(); toplevel_handle_v1_activate_request.disconnect(); toplevel_handle_v1_fullscreen_request.disconnect(); toplevel_handle_v1_set_rectangle_request.disconnect(); wlr_foreign_toplevel_handle_v1_destroy(handle); } private: void toplevel_send_title() { wlr_foreign_toplevel_handle_v1_set_title(handle, view->get_title().c_str()); } void toplevel_send_app_id() { std::string app_id; auto default_app_id = view->get_app_id(); gtk_shell_app_id_query_signal ev; ev.view = view; wf::get_core().emit(&ev); std::string app_id_mode = wf::option_wrapper_t("workarounds/app_id_mode"); if ((app_id_mode == "gtk-shell") && (ev.app_id.length() > 0)) { app_id = ev.app_id; } else if (app_id_mode == "full") { #if WF_HAS_XWAYLAND if (wlr_xwayland_surface *xw_surface = wlr_xwayland_surface_try_from_wlr_surface(view->get_wlr_surface())) { ev.app_id = nonull(xw_surface->instance); } #endif app_id = default_app_id + " " + ev.app_id; } else { app_id = default_app_id; } wlr_foreign_toplevel_handle_v1_set_app_id(handle, app_id.c_str()); } void toplevel_send_state() { wlr_foreign_toplevel_handle_v1_set_maximized(handle, view->pending_tiled_edges() == wf::TILED_EDGES_ALL); wlr_foreign_toplevel_handle_v1_set_activated(handle, view->activated); wlr_foreign_toplevel_handle_v1_set_minimized(handle, view->minimized); wlr_foreign_toplevel_handle_v1_set_fullscreen(handle, view->pending_fullscreen()); /* update parent as well */ auto it = view_to_toplevel->find(view->parent); if (it == view_to_toplevel->end()) { wlr_foreign_toplevel_handle_v1_set_parent(handle, nullptr); } else { wlr_foreign_toplevel_handle_v1_set_parent(handle, it->second->handle); } } void toplevel_update_output(wf::output_t *output, bool enter) { if (output && enter) { wlr_foreign_toplevel_handle_v1_output_enter(handle, output->handle); } if (output && !enter) { wlr_foreign_toplevel_handle_v1_output_leave(handle, output->handle); } } wf::signal::connection_t on_title_changed = [=] (auto) { toplevel_send_title(); }; wf::signal::connection_t on_app_id_changed = [=] (auto) { toplevel_send_app_id(); }; wf::signal::connection_t on_set_output = [=] (wf::view_set_output_signal *ev) { toplevel_update_output(ev->output, false); toplevel_update_output(view->get_output(), true); }; wf::signal::connection_t on_minimized = [=] (auto) { toplevel_send_state(); }; wf::signal::connection_t on_fullscreen = [=] (auto) { toplevel_send_state(); }; wf::signal::connection_t on_tiled = [=] (auto) { toplevel_send_state(); }; wf::signal::connection_t on_activated = [=] (auto) { toplevel_send_state(); }; wf::signal::connection_t on_parent_changed = [=] (auto) { toplevel_send_state(); }; wf::wl_listener_wrapper toplevel_handle_v1_maximize_request; wf::wl_listener_wrapper toplevel_handle_v1_activate_request; wf::wl_listener_wrapper toplevel_handle_v1_minimize_request; wf::wl_listener_wrapper toplevel_handle_v1_set_rectangle_request; wf::wl_listener_wrapper toplevel_handle_v1_fullscreen_request; wf::wl_listener_wrapper toplevel_handle_v1_close_request; void init_request_handlers() { toplevel_handle_v1_maximize_request.set_callback([&] (void *data) { auto ev = static_cast(data); wf::get_core().default_wm->tile_request(view, ev->maximized ? wf::TILED_EDGES_ALL : 0); }); toplevel_handle_v1_minimize_request.set_callback([&] (void *data) { auto ev = static_cast(data); wf::get_core().default_wm->minimize_request(view, ev->minimized); }); toplevel_handle_v1_activate_request.set_callback([&] (auto) { wf::get_core().default_wm->focus_request(view); }); toplevel_handle_v1_close_request.set_callback([&] (auto) { view->close(); }); toplevel_handle_v1_set_rectangle_request.set_callback([&] (void *data) { auto ev = static_cast(data); auto surface = wf::wl_surface_to_wayfire_view(ev->surface->resource); if (!surface) { LOGE("Setting minimize hint to unknown surface. Wayfire currently" "supports only setting hints relative to views."); return; } handle_minimize_hint(view.get(), surface.get(), {ev->x, ev->y, ev->width, ev->height}); }); toplevel_handle_v1_fullscreen_request.set_callback([&] ( void *data) { auto ev = static_cast(data); auto wo = wf::get_core().output_layout->find_output(ev->output); wf::get_core().default_wm->fullscreen_request(view, wo, ev->fullscreen); }); } void handle_minimize_hint(wf::toplevel_view_interface_t *view, wf::view_interface_t *relative_to, wlr_box hint) { if (relative_to->get_output() != view->get_output()) { LOGE("Minimize hint set to surface on a different output, " "problems might arise"); /* TODO: translate coordinates in case minimize hint is on another output */ } wf::pointf_t relative = relative_to->get_surface_root_node()->to_global({0, 0}); hint.x += relative.x; hint.y += relative.y; view->set_minimize_hint(hint); } }; class wayfire_foreign_toplevel_protocol_impl : public wf::plugin_interface_t { public: void init() override { toplevel_manager = wlr_foreign_toplevel_manager_v1_create(wf::get_core().display); wf::get_core().connect(&on_view_mapped); wf::get_core().connect(&on_view_unmapped); } void fini() override {} bool is_unloadable() override { return false; } private: wf::signal::connection_t on_view_mapped = [=] (wf::view_mapped_signal *ev) { if (auto toplevel = wf::toplevel_cast(ev->view)) { auto handle = wlr_foreign_toplevel_handle_v1_create(toplevel_manager); handle_for_view[toplevel] = std::make_unique(toplevel, handle, &handle_for_view); } }; wf::signal::connection_t on_view_unmapped = [=] (wf::view_unmapped_signal *ev) { handle_for_view.erase(toplevel_cast(ev->view)); }; wlr_foreign_toplevel_manager_v1 *toplevel_manager; std::map> handle_for_view; }; DECLARE_WAYFIRE_PLUGIN(wayfire_foreign_toplevel_protocol_impl); wayfire-0.8.1/plugins/protocols/gtk-shell.cpp000066400000000000000000000267621457431457600213260ustar00rootroot00000000000000#include "gtk-shell-protocol.h" #include "wayfire/core.hpp" #include "wayfire/object.hpp" #include #include #include #include #include #include #include #include #include #include "gtk-shell.hpp" #define GTK_SHELL_VERSION 3 class wf_gtk_shell : public wf::custom_data_t { public: std::map surface_app_id; }; struct wf_gtk_surface { wl_resource *resource; wl_resource *wl_surface; wf::wl_listener_wrapper on_configure; wf::wl_listener_wrapper on_destroy; }; /** * In gnome-shell/mutter/meta windows/views keep track of the properties * specified as arguments here. * Currently only the app_id is implemented / required. */ static void handle_gtk_surface_set_dbus_properties(wl_client *client, wl_resource *resource, const char *application_id, const char *app_menu_path, const char *menubar_path, const char *window_object_path, const char *application_object_path, const char *unique_bus_name) { auto surface = static_cast(wl_resource_get_user_data(resource)); if (application_id) { wf::get_core().get_data_safe()->surface_app_id[surface->wl_surface] = application_id; } } /** * Modal dialogs may be handled differently than non-modal dialogs. * It is a hint that this should be attached to the parent surface. * In gnome this does not affect input-focus. * This function sets the modal hint. */ static void handle_gtk_surface_set_modal(wl_client *client, wl_resource *resource) { auto surface = static_cast(wl_resource_get_user_data(resource)); wayfire_view view = wf::wl_surface_to_wayfire_view(surface->wl_surface); if (view) { view->store_data(std::make_unique(), "gtk-shell-modal"); } } /** * Modal dialogs may be handled differently than non-modal dialogs. * It is a hint that this should be attached to the parent surface. * In gnome this does not affect input-focus. * This function removes the modal hint. */ static void handle_gtk_surface_unset_modal(wl_client *client, wl_resource *resource) { auto surface = static_cast(wl_resource_get_user_data(resource)); wayfire_view view = wf::wl_surface_to_wayfire_view(surface->wl_surface); if (view) { view->erase_data("gtk-shell-modal"); } } /** * The surface requests focus, for example single instance applications like * gnome-control-center, gnome-clocks, dconf-editor are single instance and if * they are already running and launched again, this will request that they get * focused. * This function is superseded by handle_gtk_surface_request_focus a newer * equivalelent * used by gtk-applications now. This function is for compatibility reasons. */ static void handle_gtk_surface_present(wl_client *client, wl_resource *resource, uint32_t time) { auto surface = static_cast(wl_resource_get_user_data(resource)); wayfire_toplevel_view view = toplevel_cast(wf::wl_surface_to_wayfire_view(surface->wl_surface)); if (view) { wf::get_core().default_wm->focus_request(view, true); } } /** * The surface requests focus, for example single instance applications like * gnome-control-center, gnome-clocks, dconf-editor are single instance and if * they are already running and launched again, this will request that they get * focused. */ static void handle_gtk_surface_request_focus(struct wl_client *client, struct wl_resource *resource, const char *startup_id) { auto surface = static_cast(wl_resource_get_user_data(resource)); wayfire_toplevel_view view = toplevel_cast(wf::wl_surface_to_wayfire_view(surface->wl_surface)); if (view) { wf::get_core().default_wm->focus_request(view, true); } } /** * Helper function used by send_gtk_surface_configure * and send_gtk_surface_configure_edges */ static void append_to_array(wl_array *array, uint32_t value) { uint32_t *tmp; tmp = (uint32_t*)wl_array_add(array, sizeof(*tmp)); *tmp = value; } /** * Tells the client about the window state in more detail than xdg_surface. * This currently only includes which edges are tiled. */ static void send_gtk_surface_configure(wf_gtk_surface *surface, wayfire_toplevel_view view) { int version = wl_resource_get_version(surface->resource); wl_array states; wl_array_init(&states); if (view->pending_tiled_edges()) { append_to_array(&states, GTK_SURFACE1_STATE_TILED); } if ((version >= GTK_SURFACE1_STATE_TILED_TOP_SINCE_VERSION) && (view->pending_tiled_edges() & WLR_EDGE_TOP)) { append_to_array(&states, GTK_SURFACE1_STATE_TILED_TOP); } if ((version >= GTK_SURFACE1_STATE_TILED_RIGHT_SINCE_VERSION) && (view->pending_tiled_edges() & WLR_EDGE_RIGHT)) { append_to_array(&states, GTK_SURFACE1_STATE_TILED_RIGHT); } if ((version >= GTK_SURFACE1_STATE_TILED_BOTTOM_SINCE_VERSION) && (view->pending_tiled_edges() & WLR_EDGE_BOTTOM)) { append_to_array(&states, GTK_SURFACE1_STATE_TILED_BOTTOM); } if ((version >= GTK_SURFACE1_STATE_TILED_LEFT_SINCE_VERSION) && (view->pending_tiled_edges() & WLR_EDGE_LEFT)) { append_to_array(&states, GTK_SURFACE1_STATE_TILED_LEFT); } gtk_surface1_send_configure(surface->resource, &states); wl_array_release(&states); } /** * Tells gtk which edges should be resizable. */ static void send_gtk_surface_configure_edges(wf_gtk_surface *surface, wayfire_toplevel_view view) { wl_array edges; wl_array_init(&edges); if (!view->pending_tiled_edges()) { append_to_array(&edges, GTK_SURFACE1_EDGE_CONSTRAINT_RESIZABLE_TOP); append_to_array(&edges, GTK_SURFACE1_EDGE_CONSTRAINT_RESIZABLE_RIGHT); append_to_array(&edges, GTK_SURFACE1_EDGE_CONSTRAINT_RESIZABLE_BOTTOM); append_to_array(&edges, GTK_SURFACE1_EDGE_CONSTRAINT_RESIZABLE_LEFT); } gtk_surface1_send_configure_edges(surface->resource, &edges); wl_array_release(&edges); } /** * Augments xdg_surface's configure with additional gtk-specific information. */ static void handle_xdg_surface_on_configure(wf_gtk_surface *surface) { wayfire_toplevel_view view = wf::toplevel_cast(wf::wl_surface_to_wayfire_view(surface->wl_surface)); if (view) { send_gtk_surface_configure(surface, view); if (wl_resource_get_version(surface->resource) >= GTK_SURFACE1_CONFIGURE_EDGES_SINCE_VERSION) { send_gtk_surface_configure_edges(surface, view); } } } /** * Prevents a race condition where the xdg_surface is destroyed before * the gtk_surface's resource and the gtk_surface's destructor tries to * disconnect these signals which causes a use-after-free */ static void handle_xdg_surface_on_destroy(wf_gtk_surface *surface) { surface->on_configure.disconnect(); surface->on_destroy.disconnect(); } /** * Destroys the gtk_surface object. */ static void handle_gtk_surface_destroy(wl_resource *resource) { auto surface = static_cast(wl_resource_get_user_data(resource)); delete surface; } /** * Supported functions of the gtk_surface_interface implementation */ const struct gtk_surface1_interface gtk_surface1_impl = { .set_dbus_properties = handle_gtk_surface_set_dbus_properties, .set_modal = handle_gtk_surface_set_modal, .unset_modal = handle_gtk_surface_unset_modal, .present = handle_gtk_surface_present, .request_focus = handle_gtk_surface_request_focus, }; /** * Initializes a gtk_surface object and passes it to the client. */ static void handle_gtk_shell_get_gtk_surface(wl_client *client, wl_resource *resource, uint32_t id, wl_resource *surface) { wf_gtk_surface *gtk_surface = new wf_gtk_surface; gtk_surface->resource = wl_resource_create(client, >k_surface1_interface, wl_resource_get_version(resource), id); gtk_surface->wl_surface = surface; wl_resource_set_implementation(gtk_surface->resource, >k_surface1_impl, gtk_surface, handle_gtk_surface_destroy); wlr_surface *wlr_surface = wlr_surface_from_resource(surface); if (wlr_xdg_surface *xdg_surface = wlr_xdg_surface_try_from_wlr_surface(wlr_surface)) { gtk_surface->on_configure.set_callback([=] (void*) { handle_xdg_surface_on_configure(gtk_surface); }); gtk_surface->on_configure.connect(&xdg_surface->events.configure); gtk_surface->on_destroy.set_callback([=] (void*) { handle_xdg_surface_on_destroy(gtk_surface); }); gtk_surface->on_destroy.connect(&xdg_surface->events.destroy); } } /** * Supplements the request_focus() and present() * to prevent focus stealing if user interaction happened * between the time application was called and request_focus was received. * Not implemented. */ static void handle_gtk_shell_notify_launch(wl_client *client, wl_resource *resource, const char *startup_id) {} /** * A view could use this to receive notification when the surface is ready. * Gets the DESKTOP_STARTUP_ID from environment and unsets this env var afterwards * so any child processes don't inherit it. * Not implemented. */ static void handle_gtk_shell_set_startup_id(wl_client *client, wl_resource *resource, const char *startup_id) {} /** * A view could use this to invoke the system bell, be it aural, visual or none at * all. */ static void handle_gtk_shell_system_bell(wl_client *client, wl_resource *resource, wl_resource *surface) { wf::view_system_bell_signal data; if (surface) { auto gtk_surface = static_cast(wl_resource_get_user_data(surface)); data.view = wf::wl_surface_to_wayfire_view(gtk_surface->wl_surface); } wf::get_core().emit(&data); } /** * Supported functions of the gtk_shell_interface implementation */ static const struct gtk_shell1_interface gtk_shell1_impl = { .get_gtk_surface = handle_gtk_shell_get_gtk_surface, .set_startup_id = handle_gtk_shell_set_startup_id, .system_bell = handle_gtk_shell_system_bell, .notify_launch = handle_gtk_shell_notify_launch, }; /** * Destroy the gtk_shell object. * gtk_shell exists as long as the compositor runs. */ static void handle_gtk_shell1_destroy(wl_resource *resource) {} /** * Binds the gtk_shell to wayland. */ void bind_gtk_shell1(wl_client *client, void *data, uint32_t version, uint32_t id) { auto resource = wl_resource_create(client, >k_shell1_interface, GTK_SHELL_VERSION, id); wl_resource_set_implementation(resource, >k_shell1_impl, data, handle_gtk_shell1_destroy); } class wayfire_gtk_shell_impl : public wf::plugin_interface_t { public: void init() override { auto display = wf::get_core().display; wl_global_create(display, >k_shell1_interface, GTK_SHELL_VERSION, NULL, bind_gtk_shell1); wf::get_core().connect(&on_app_id_query); } bool is_unloadable() override { return false; } wf::signal::connection_t on_app_id_query = [=] (gtk_shell_app_id_query_signal *ev) { if (auto surface = ev->view->get_wlr_surface()) { auto shell = wf::get_core().get_data_safe(); ev->app_id = shell->surface_app_id[surface->resource]; } }; }; DECLARE_WAYFIRE_PLUGIN(wayfire_gtk_shell_impl); wayfire-0.8.1/plugins/protocols/gtk-shell.hpp000066400000000000000000000004531457431457600213200ustar00rootroot00000000000000#pragma once #include /** * A signal to query the gtk_shell plugin about the gtk-shell-specific app_id of the given view. */ struct gtk_shell_app_id_query_signal { wayfire_view view; // Set by the gtk-shell plugin in response to the signal std::string app_id; }; wayfire-0.8.1/plugins/protocols/input-method-v1.cpp000066400000000000000000000623201457431457600223630ustar00rootroot00000000000000#include #include #include #include #include #include #include "input-method-unstable-v1-protocol.h" #include "wayfire/option-wrapper.hpp" #include "wayfire/nonstd/wlroots-full.hpp" #include "wayfire/nonstd/wlroots.hpp" #include "wayfire/debug.hpp" #include "wayfire/signal-definitions.hpp" #include "wayfire/seat.hpp" class wayfire_im_v1_text_input_v3 { public: wayfire_im_v1_text_input_v3(wlr_text_input_v3 *text_input) { this->text_input = text_input; this->client = wl_resource_get_client(text_input->resource); on_enable.connect(&text_input->events.enable); on_disable.connect(&text_input->events.disable); on_destroy.connect(&text_input->events.destroy); on_commit.connect(&text_input->events.commit); } void set_focus_surface(wlr_surface *surface) { wl_client *next_client = surface ? wl_resource_get_client(surface->resource) : nullptr; if (current_focus) { if (!next_client || (next_client != client) || (surface != current_focus)) { LOGC(IM, "Leave text input ti=", text_input); wlr_text_input_v3_send_leave(text_input); current_focus = nullptr; } } if ((next_client == client) && (surface != current_focus)) { LOGC(IM, "Enter text input ti=", text_input, " surface=", surface); wlr_text_input_v3_send_enter(text_input, surface); current_focus = surface; } } wlr_text_input_v3 *text_input = NULL; wl_client *client = NULL; wlr_surface *current_focus = NULL; wf::wl_listener_wrapper on_enable; wf::wl_listener_wrapper on_disable; wf::wl_listener_wrapper on_destroy; wf::wl_listener_wrapper on_commit; }; class wayfire_input_method_v1_context { public: wayfire_input_method_v1_context(wlr_text_input_v3 *text_input, wl_resource *current_im, const struct zwp_input_method_context_v1_interface *context_impl) { this->text_input = text_input; this->current_im = current_im; context = wl_resource_create(wl_resource_get_client(current_im), &zwp_input_method_context_v1_interface, 1, 0); wl_resource_set_implementation(context, context_impl, this, handle_ctx_destruct_final); zwp_input_method_v1_send_activate(current_im, context); } static void handle_ctx_destruct_final(wl_resource *resource) { auto context = static_cast(wl_resource_get_user_data(resource)); if (context) { context->deactivate(true); } } void handle_text_input_commit() { zwp_input_method_context_v1_send_content_type(context, text_input->current.content_type.hint, text_input->current.content_type.purpose); zwp_input_method_context_v1_send_surrounding_text(context, text_input->current.surrounding.text ?: "", text_input->current.surrounding.cursor, text_input->current.surrounding.anchor); zwp_input_method_context_v1_send_commit_state(context, ctx_serial++); } void deactivate(bool im_killed = false) { this->text_input = NULL; wl_resource_set_user_data(context, NULL); if (im_killed) { // Remove keys which core still thinks are pressed down physically: they will be sent as release // events to the client at a later point. for (auto& hw_pressed : wf::get_core().seat->get_pressed_keys()) { if (currently_pressed_keys_client.count(hw_pressed)) { currently_pressed_keys_client.erase( currently_pressed_keys_client.find(hw_pressed)); } } // For the other keys (where we potentially swallowed the release event, but the IM did not // respond yet with a release), release those keys. for (auto& key : currently_pressed_keys_client) { wlr_seat_keyboard_notify_key(this->text_input->seat, wf::get_current_time(), key, WL_KEYBOARD_KEY_STATE_RELEASED); } currently_pressed_keys_client.clear(); if (active_grab_keyboard) { wl_resource_set_user_data(active_grab_keyboard, NULL); } return; } zwp_input_method_v1_send_deactivate(current_im, context); if (active_grab_keyboard) { for (auto& key : currently_pressed_keys_im) { wl_keyboard_send_key(active_grab_keyboard, vkbd_serial++, wf::get_current_time(), key, WL_KEYBOARD_KEY_STATE_RELEASED); } currently_pressed_keys_im.clear(); wl_resource_destroy(active_grab_keyboard); } } void handle_im_key(uint32_t time, uint32_t key, uint32_t state) { wlr_seat_keyboard_notify_key(this->text_input->seat, time, key, state); update_pressed_keys(currently_pressed_keys_client, key, state); } void handle_im_modifiers(uint32_t serial, uint32_t mods_depressed, uint32_t mods_latched, uint32_t mods_locked, uint32_t group) { wlr_keyboard_modifiers mods{ .depressed = mods_depressed, .latched = mods_latched, .locked = mods_locked, .group = group }; wlr_seat_keyboard_notify_modifiers(this->text_input->seat, &mods); } void grab_keyboard(wl_client *client, uint32_t id) { this->active_grab_keyboard = wl_resource_create(client, &wl_keyboard_interface, 1, id); wl_resource_set_implementation(active_grab_keyboard, NULL, this, unbind_keyboard); wf::get_core().connect(&on_keyboard_key); wf::get_core().connect(&on_keyboard_modifiers); } static void unbind_keyboard(wl_resource *keyboard) { auto self = static_cast(wl_resource_get_user_data(keyboard)); if (!self) { return; } self->active_grab_keyboard = NULL; self->last_sent_keymap_keyboard = NULL; self->on_keyboard_key.disconnect(); self->on_keyboard_modifiers.disconnect(); self->currently_pressed_keys_im.clear(); } void update_pressed_keys(std::multiset& set, uint32_t key, uint32_t state) { if (state == WL_KEYBOARD_KEY_STATE_PRESSED) { set.insert(key); } else if (set.count(key)) { set.erase(set.find(key)); } } wf::signal::connection_t> on_keyboard_key = [=] (wf::pre_client_input_event_signal *ev) { if (active_grab_keyboard && !ev->carried_out) { check_send_keymap(wlr_keyboard_from_input_device(ev->device)); ev->carried_out = true; wl_keyboard_send_key(active_grab_keyboard, vkbd_serial++, ev->event->time_msec, ev->event->keycode, ev->event->state); // Keep track of pressed keys so that we can release all of them at the end. // Otherwise the IM gets stuck thinking that some modifiers are pressed, etc. update_pressed_keys(currently_pressed_keys_im, ev->event->keycode, ev->event->state); } }; wf::signal::connection_t> on_keyboard_modifiers = [=] (wf::input_event_signal *ev) { if (active_grab_keyboard) { auto kbd = wlr_keyboard_from_input_device(ev->device); check_send_keymap(kbd); wl_keyboard_send_modifiers(active_grab_keyboard, vkbd_serial++, kbd->modifiers.depressed, kbd->modifiers.latched, kbd->modifiers.locked, kbd->modifiers.group); } }; void check_send_keymap(wlr_keyboard *current_kbd) { if (current_kbd == last_sent_keymap_keyboard) { return; } last_sent_keymap_keyboard = current_kbd; if (current_kbd->keymap != NULL) { wl_keyboard_send_keymap(active_grab_keyboard, WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1, current_kbd->keymap_fd, current_kbd->keymap_size); } else { int fd = open("/dev/null", O_RDONLY | O_CLOEXEC); wl_keyboard_send_keymap(active_grab_keyboard, WL_KEYBOARD_KEYMAP_FORMAT_NO_KEYMAP, fd, 0); close(fd); } // Send new modifiers wl_keyboard_send_modifiers(active_grab_keyboard, vkbd_serial++, current_kbd->modifiers.depressed, current_kbd->modifiers.latched, current_kbd->modifiers.locked, current_kbd->modifiers.group); } std::multiset currently_pressed_keys_im; std::multiset currently_pressed_keys_client; wlr_keyboard *last_sent_keymap_keyboard = NULL; wl_resource *active_grab_keyboard = NULL; int32_t cursor = 0; uint32_t ctx_serial = 0; uint32_t vkbd_serial = 0; wl_resource *current_im = NULL; wl_resource *context = NULL; // NULL if inactive wlr_text_input_v3 *text_input = NULL; }; void handle_im_context_destroy(wl_client *client, wl_resource *resource) { wl_resource_destroy(resource); } void handle_im_context_commit_string(wl_client *client, wl_resource *resource, uint32_t serial, const char *text) { auto context = static_cast(wl_resource_get_user_data(resource)); if (context && context->text_input) { wlr_text_input_v3_send_commit_string(context->text_input, text); wlr_text_input_v3_send_done(context->text_input); } } void handle_im_context_preedit_string(wl_client *client, wl_resource *resource, uint32_t serial, const char *text, const char *commit) { auto context = static_cast(wl_resource_get_user_data(resource)); if (context && context->text_input) { int begin = std::min((int)strlen(text), context->cursor); int end = std::min((int)strlen(text), context->cursor); // Send null preedit_string if it's empty. // // This makes GTK to emit preedit-end signals so that vte paints its // cursor. The preedit_string from input-method-v1 can't be null. auto preedit_string = strlen(text) > 0 ? text : nullptr; wlr_text_input_v3_send_preedit_string(context->text_input, preedit_string, begin, end); wlr_text_input_v3_send_done(context->text_input); } } void handle_im_context_preedit_styling(wl_client *client, wl_resource *resource, uint32_t index, uint32_t length, uint32_t style) { // Nothing to do } void handle_im_context_preedit_cursor(wl_client *client, wl_resource *resource, int32_t index) { auto context = static_cast(wl_resource_get_user_data(resource)); if (context && context->text_input) { context->cursor = index; } } void handle_im_context_delete_surrounding_text(wl_client *client, wl_resource *resource, int32_t index, uint32_t length) { auto context = static_cast(wl_resource_get_user_data(resource)); if (context && context->text_input) { if ((index > 0) || (index + (int32_t)length < 0)) { // Ignore overflows return; } wlr_text_input_v3_send_delete_surrounding_text(context->text_input, -index, -index + length); wlr_text_input_v3_send_done(context->text_input); } } void handle_im_context_cursor_position(wl_client *client, wl_resource *resource, int32_t index, int32_t anchor) {} void handle_im_context_modifiers_map(wl_client *client, wl_resource *resource, struct wl_array *map) {} void handle_im_context_keysym(wl_client *client, wl_resource *resource, uint32_t serial, uint32_t time, uint32_t sym, uint32_t state, uint32_t modifiers) {} void handle_im_context_grab_keyboard(wl_client *client, wl_resource *resource, uint32_t keyboard_id) { auto context = static_cast(wl_resource_get_user_data(resource)); if (context) { context->grab_keyboard(client, keyboard_id); } else { // Create a dummy resource to avoid Wayland protocol errors. // But, we have already moved on from this context, so we won't send any events. auto resource = wl_resource_create(client, &wl_keyboard_interface, 1, keyboard_id); wl_resource_set_implementation(resource, NULL, NULL, NULL); } } void handle_im_context_key(wl_client*, wl_resource *resource, uint32_t, uint32_t time, uint32_t key, uint32_t state) { auto context = static_cast(wl_resource_get_user_data(resource)); if (context) { context->handle_im_key(time, key, state); } } void handle_im_context_modifiers(wl_client *client, wl_resource *resource, uint32_t serial, uint32_t mods_depressed, uint32_t mods_latched, uint32_t mods_locked, uint32_t group) { auto context = static_cast(wl_resource_get_user_data(resource)); if (context) { context->handle_im_modifiers(serial, mods_depressed, mods_latched, mods_locked, group); } } void handle_im_context_language(wl_client *client, wl_resource *resource, uint32_t serial, const char *language) {} void handle_im_context_text_direction(wl_client *client, wl_resource *resource, uint32_t serial, uint32_t direction) {} static const struct zwp_input_method_context_v1_interface context_implementation = { .destroy = handle_im_context_destroy, .commit_string = handle_im_context_commit_string, .preedit_string = handle_im_context_preedit_string, .preedit_styling = handle_im_context_preedit_styling, .preedit_cursor = handle_im_context_preedit_cursor, .delete_surrounding_text = handle_im_context_delete_surrounding_text, .cursor_position = handle_im_context_cursor_position, .modifiers_map = handle_im_context_modifiers_map, .keysym = handle_im_context_keysym, .grab_keyboard = handle_im_context_grab_keyboard, .key = handle_im_context_key, .modifiers = handle_im_context_modifiers, .language = handle_im_context_language, .text_direction = handle_im_context_text_direction }; void handle_input_panel_get_input_panel_surface(wl_client *client, wl_resource *resource, uint32_t id, struct wl_resource *surface); static const struct zwp_input_panel_v1_interface panel_implementation = { .get_input_panel_surface = handle_input_panel_get_input_panel_surface }; void handle_input_panel_surface_set_toplevel(wl_client *client, wl_resource *resource, struct wl_resource *output, uint32_t position); void handle_input_panel_surface_set_overlay_panel(wl_client *client, wl_resource *resource); static const struct zwp_input_panel_surface_v1_interface panel_surface_implementation = { .set_toplevel = handle_input_panel_surface_set_toplevel, .set_overlay_panel = handle_input_panel_surface_set_overlay_panel }; class wayfire_input_method_v1_panel_surface { public: wayfire_input_method_v1_panel_surface(wl_client *client, uint32_t id, wf::text_input_v3_im_relay_interface_t *relay, wlr_surface *surface) { LOGC(IM, "Input method panel surface created."); resource = wl_resource_create(client, &zwp_input_panel_surface_v1_interface, 1, id); wl_resource_set_implementation(resource, &panel_surface_implementation, this, handle_destroy); this->surface = surface; this->relay = relay; on_surface_commit.set_callback([=] (void*) { if (wlr_surface_has_buffer(surface) && !surface->mapped) { wlr_surface_map(surface); } else if (!wlr_surface_has_buffer(surface) && surface->mapped) { wlr_surface_unmap(surface); } }); on_surface_commit.connect(&surface->events.commit); // Initial commit, maybe already ok? on_surface_commit.emit(NULL); on_surface_destroy.set_callback([=] (void*) { if (surface->mapped) { wlr_surface_unmap(surface); } on_surface_destroy.disconnect(); on_surface_commit.disconnect(); }); on_surface_destroy.connect(&surface->events.destroy); } void set_overlay_panel() { LOGC(IM, "Input method panel surface set to overlay."); popup = wf::text_input_v3_popup::create(relay, surface); if (surface->mapped) { popup->map(); } } ~wayfire_input_method_v1_panel_surface() { if (popup && popup->is_mapped()) { popup->unmap(); } } private: wl_resource *resource; wlr_surface *surface; wf::text_input_v3_im_relay_interface_t *relay; std::shared_ptr popup = nullptr; wf::wl_listener_wrapper on_surface_commit; wf::wl_listener_wrapper on_surface_destroy; static void handle_destroy(wl_resource *destroy) { auto panel = (wayfire_input_method_v1_panel_surface*)wl_resource_get_user_data(destroy); delete panel; } }; void handle_input_panel_get_input_panel_surface(wl_client *client, wl_resource *resource, uint32_t id, struct wl_resource *surface) { new wayfire_input_method_v1_panel_surface(client, id, (wf::text_input_v3_im_relay_interface_t*)wl_resource_get_user_data(resource), (wlr_surface*)wl_resource_get_user_data(surface)); } void handle_input_panel_surface_set_toplevel(wl_client *client, wl_resource *resource, wl_resource *output, uint32_t position) { LOGE("The set toplevel request is not supported by the IM-v1 implementation!"); } void handle_input_panel_surface_set_overlay_panel(wl_client *client, wl_resource *resource) { auto panel = (wayfire_input_method_v1_panel_surface*)wl_resource_get_user_data(resource); if (panel) { panel->set_overlay_panel(); } } class wayfire_input_method_v1 : public wf::plugin_interface_t, public wf::text_input_v3_im_relay_interface_t { public: void init() override { if (enable_input_method_v2) { LOGE("Enabling both input-method-v2 and input-method-v1 is a bad idea!"); return; } wf::get_core().protocols.text_input = wlr_text_input_manager_v3_create(wf::get_core().display); input_method_manager = wl_global_create(wf::get_core().display, &zwp_input_method_v1_interface, 1, this, handle_bind_im_v1); input_panel_manager = wl_global_create(wf::get_core().display, &zwp_input_panel_v1_interface, 1, this, handle_bind_im_panel_v1); on_text_input_v3_created.connect(&wf::get_core().protocols.text_input->events.text_input); on_text_input_v3_created.set_callback([&] (void *data) { handle_text_input_v3_created(static_cast(data)); }); wf::get_core().connect(&on_keyboard_focus_changed); } void fini() override { if (input_method_manager) { wl_global_destroy(input_method_manager); } } bool is_unloadable() override { return false; } wf::signal::connection_t on_keyboard_focus_changed = [=] (wf::keyboard_focus_changed_signal *ev) { auto view = wf::node_to_view(ev->new_focus); auto surf = view ? view->get_wlr_surface() : nullptr; if (last_focus_surface != surf) { reset_current_im_context(); last_focus_surface = surf; for (auto& [_, im] : im_text_inputs) { im->set_focus_surface(last_focus_surface); } } }; // Handlers for text-input-v3 private: void handle_text_input_v3_created(wlr_text_input_v3 *input) { im_text_inputs[input] = std::make_unique(input); im_text_inputs[input]->on_enable.set_callback([=] (void *data) { handle_text_input_v3_enable(input); }); im_text_inputs[input]->on_disable.set_callback([=] (void *data) { handle_text_input_v3_disable(input); }); im_text_inputs[input]->on_destroy.set_callback([=] (void *data) { handle_text_input_v3_destroyed(input); }); im_text_inputs[input]->on_commit.set_callback([=] (void *data) { handle_text_input_v3_commit(input); }); im_text_inputs[input]->set_focus_surface(last_focus_surface); } void handle_text_input_v3_destroyed(wlr_text_input_v3 *input) { handle_text_input_v3_disable(input); im_text_inputs.erase(input); } void handle_text_input_v3_commit(wlr_text_input_v3 *input) { if (current_im_context && (current_im_context->text_input == input)) { current_im_context->handle_text_input_commit(); } } void handle_text_input_v3_enable(wlr_text_input_v3 *input) { if (!current_im) { LOGC(IM, "No IM currently connected: ignoring enable request."); return; } if (!last_focus_surface || (im_text_inputs[input]->current_focus != last_focus_surface)) { LOGC(IM, "Ignoring enable request for text input ", input, ": stale request"); return; } if (current_im_context) { LOGC(IM, "Text input activated while old context is still around?"); return; } LOGC(IM, "Enabling IM context for ", input); current_im_context = std::make_unique( input, current_im, &context_implementation); } void handle_text_input_v3_disable(wlr_text_input_v3 *input) { if (!current_im_context || (current_im_context->text_input != input)) { return; } reset_current_im_context(); } void reset_current_im_context(bool im_killed = false) { if (!current_im_context) { return; } LOGC(IM, "Disabling IM context for ", current_im_context->text_input); current_im_context->deactivate(im_killed); current_im_context.reset(); } wlr_text_input_v3 *find_focused_text_input_v3() override { return current_im_context ? current_im_context->text_input : nullptr; } // Implementation of input-method-v1 private: void bind_input_method_manager(wl_client *client, uint32_t id) { wl_resource *resource = wl_resource_create(client, &zwp_input_method_v1_interface, 1, id); if (current_im) { LOGE("Trying to bind to input-method-v1 while another input method is active is not supported!"); wl_resource_post_error(resource, WL_DISPLAY_ERROR_INVALID_OBJECT, "Input method already bound"); return; } LOGC(IM, "Input method bound"); wl_resource_set_implementation(resource, NULL, this, handle_destroy_im); current_im = resource; for (auto& [_, im] : im_text_inputs) { if (im->text_input->current_enabled) { handle_text_input_v3_enable(im->text_input); } } } static void handle_bind_im_v1(wl_client *client, void *data, uint32_t version, uint32_t id) { ((wayfire_input_method_v1*)data)->bind_input_method_manager(client, id); } static void handle_destroy_im(wl_resource *resource) { LOGC(IM, "Input method unbound"); auto data = wl_resource_get_user_data(resource); ((wayfire_input_method_v1*)data)->reset_current_im_context(true); ((wayfire_input_method_v1*)data)->current_im = nullptr; } // input-method-panel impl private: void bind_input_method_panel(wl_client *client, uint32_t id) { LOGC(IM, "Input method panel interface bound"); wl_resource *resource = wl_resource_create(client, &zwp_input_panel_v1_interface, 1, id); wl_resource_set_implementation(resource, &panel_implementation, dynamic_cast(this), handle_destroy_im_panel); } static void handle_bind_im_panel_v1(wl_client *client, void *data, uint32_t version, uint32_t id) { ((wayfire_input_method_v1*)data)->bind_input_method_panel(client, id); } static void handle_destroy_im_panel(wl_resource *resource) { LOGC(IM, "Input method panel interface unbound"); } private: wf::option_wrapper_t enable_input_method_v2{"workarounds/enable_input_method_v2"}; wl_global *input_method_manager = NULL; wl_global *input_panel_manager = NULL; wl_resource *current_im = NULL; wf::wl_listener_wrapper on_text_input_v3_created; wlr_surface *last_focus_surface = NULL; std::unique_ptr current_im_context = NULL; std::map> im_text_inputs; }; DECLARE_WAYFIRE_PLUGIN(wayfire_input_method_v1); wayfire-0.8.1/plugins/protocols/meson.build000066400000000000000000000007601457431457600210600ustar00rootroot00000000000000protocol_plugins = [ 'foreign-toplevel', 'gtk-shell', 'wayfire-shell', 'xdg-activation', 'shortcuts-inhibit', 'input-method-v1' ] all_include_dirs = [wayfire_api_inc, wayfire_conf_inc, plugins_common_inc] all_deps = [wlroots, pixman, wfconfig, wf_protos, json] foreach plugin : protocol_plugins shared_module(plugin, plugin + '.cpp', include_directories: all_include_dirs, dependencies: all_deps, install: true, install_dir: conf_data.get('PLUGIN_PATH')) endforeach wayfire-0.8.1/plugins/protocols/shortcuts-inhibit.cpp000066400000000000000000000133631457431457600231070ustar00rootroot00000000000000#include "wayfire/core.hpp" #include "wayfire/option-wrapper.hpp" #include "wayfire/plugins/common/shared-core-data.hpp" #include "wayfire/signal-definitions.hpp" #include "wayfire/signal-provider.hpp" #include "wayfire/util.hpp" #include "wayfire/seat.hpp" #include "wayfire/view.hpp" #include "wayfire/matcher.hpp" #include "wayfire/bindings-repository.hpp" #include #include #include class wayfire_shortcuts_inhibit : public wf::plugin_interface_t { public: void init() override { inhibit_manager = wlr_keyboard_shortcuts_inhibit_v1_create(wf::get_core().display); keyboard_inhibit_new.set_callback([&] (void *data) { auto wlr_inhibitor = (struct wlr_keyboard_shortcuts_inhibitor_v1*)data; if (inhibitors.count(wlr_inhibitor->surface)) { LOGE("Duplicate inhibitors for one surface not supported!"); return; } inhibitors[wlr_inhibitor->surface] = std::make_unique(); auto& inhibitor = inhibitors[wlr_inhibitor->surface]; inhibitor->inhibitor = wlr_inhibitor; inhibitor->on_destroy.set_callback([=] (auto) { deactivate_for_surface(wlr_inhibitor->surface); this->inhibitors.erase(wlr_inhibitor->surface); }); inhibitor->on_destroy.connect(&wlr_inhibitor->events.destroy); check_inhibit(wf::get_core().seat->get_active_node()); }); keyboard_inhibit_new.connect(&inhibit_manager->events.new_inhibitor); wf::get_core().connect(&on_kb_focus_change); wf::get_core().connect(&on_view_mapped); wf::get_core().connect(&on_key_press); } void fini() override {} void check_inhibit(wf::scene::node_ptr focus) { auto focus_view = focus ? wf::node_to_view(focus) : nullptr; wlr_surface *new_focus = focus_view ? focus_view->get_keyboard_focus_surface() : nullptr; if (!inhibitors.count(new_focus)) { new_focus = nullptr; } if (new_focus == last_focus) { return; } deactivate_for_surface(last_focus); if (ignore_views.matches(focus_view)) { return; } activate_for_surface(new_focus); } bool is_unloadable() override { return false; } private: wf::view_matcher_t ignore_views{"shortcuts-inhibit/ignore_views"}; wlr_keyboard_shortcuts_inhibit_manager_v1 *inhibit_manager; wf::wl_listener_wrapper keyboard_inhibit_new; wf::view_matcher_t inhibit_by_default{"shortcuts-inhibit/inhibit_by_default"}; struct inhibitor_t { bool active = false; wlr_keyboard_shortcuts_inhibitor_v1 *inhibitor; wf::wl_listener_wrapper on_destroy; }; std::map> inhibitors; wlr_surface *last_focus = nullptr; void activate_for_surface(wlr_surface *surface) { if (!surface) { return; } auto& inhibitor = inhibitors[surface]; if (!inhibitor->active) { LOGD("Activating inhibitor for surface ", surface); wf::get_core().bindings->set_enabled(false); if (inhibitor->inhibitor) { wlr_keyboard_shortcuts_inhibitor_v1_activate(inhibitor->inhibitor); } inhibitor->active = true; } last_focus = surface; } void deactivate_for_surface(wlr_surface *surface) { if (!surface) { return; } auto& inhibitor = inhibitors[surface]; if (inhibitor->active) { LOGD("Deactivating inhibitor for surface ", surface); wf::get_core().bindings->set_enabled(true); if (inhibitor->inhibitor) { wlr_keyboard_shortcuts_inhibitor_v1_deactivate(inhibitor->inhibitor); } inhibitor->active = false; } last_focus = nullptr; } wf::signal::connection_t on_kb_focus_change = [=] (wf::keyboard_focus_changed_signal *ev) { check_inhibit(ev->new_focus); }; wf::signal::connection_t on_view_mapped = [=] (wf::view_mapped_signal *ev) { if (inhibit_by_default.matches(ev->view) && ev->view->get_keyboard_focus_surface()) { auto surface = ev->view->get_keyboard_focus_surface(); inhibitors[surface] = std::make_unique(); auto& inhibitor = inhibitors[surface]; inhibitor->inhibitor = nullptr; inhibitor->on_destroy.set_callback([this, surface] (auto) { deactivate_for_surface(surface); this->inhibitors.erase(surface); }); inhibitor->on_destroy.connect(&surface->events.destroy); check_inhibit(wf::get_core().seat->get_active_node()); } }; wf::option_wrapper_t break_grab_key{"shortcuts-inhibit/break_grab"}; wf::signal::connection_t> on_key_press = [=] (wf::input_event_signal *ev) { auto break_key = break_grab_key.value(); if ((ev->event->state == WL_KEYBOARD_KEY_STATE_PRESSED) && (wf::get_core().seat->get_keyboard_modifiers() == break_key.get_modifiers()) && (ev->event->keycode == break_key.get_key())) { LOGD("Force-break active inhibitor"); deactivate_for_surface(last_focus); } }; }; DECLARE_WAYFIRE_PLUGIN(wayfire_shortcuts_inhibit); wayfire-0.8.1/plugins/protocols/wayfire-shell.cpp000066400000000000000000000350151457431457600221760ustar00rootroot00000000000000/** * Implementation of the wayfire-shell-unstable-v2 protocol */ #include #include #include #include #include #include "wayfire/output.hpp" #include "wayfire/core.hpp" #include "wayfire/output-layout.hpp" #include "wayfire/render-manager.hpp" #include "wayfire-shell-unstable-v2-protocol.h" #include "wayfire/signal-definitions.hpp" #include "plugins/ipc/ipc-activator.hpp" /* ----------------------------- wfs_hotspot -------------------------------- */ static void handle_hotspot_destroy(wl_resource *resource); /** * Represents a zwf_shell_hotspot_v2. * Lifetime is managed by the resource. */ class wfs_hotspot { private: wf::geometry_t hotspot_geometry; bool hotspot_triggered = false; wf::wl_idle_call idle_check_input; wf::wl_timer timer; uint32_t timeout_ms; wl_resource *hotspot_resource; wf::signal::connection_t> on_tablet_axis = [=] (wf::post_input_event_signal *ev) { idle_check_input.run_once([=] () { auto gcf = wf::get_core().get_cursor_position(); wf::point_t gc{(int)gcf.x, (int)gcf.y}; process_input_motion(gc); }); }; wf::signal::connection_t> on_motion_event = [=] (auto) { idle_check_input.run_once([=] () { auto gcf = wf::get_core().get_cursor_position(); wf::point_t gc{(int)gcf.x, (int)gcf.y}; process_input_motion(gc); }); }; wf::signal::connection_t> on_touch_motion = [=] (auto) { idle_check_input.run_once([=] () { auto gcf = wf::get_core().get_touch_position(0); wf::point_t gc{(int)gcf.x, (int)gcf.y}; process_input_motion(gc); }); }; wf::signal::connection_t on_output_removed; void process_input_motion(wf::point_t gc) { if (!(hotspot_geometry & gc)) { if (hotspot_triggered) { zwf_hotspot_v2_send_leave(hotspot_resource); } /* Cursor outside of the hotspot */ hotspot_triggered = false; timer.disconnect(); return; } if (hotspot_triggered) { /* Hotspot was already triggered, wait for the next time the cursor * enters the hotspot area to trigger again */ return; } if (!timer.is_connected()) { timer.set_timeout(timeout_ms, [=] () { hotspot_triggered = true; zwf_hotspot_v2_send_enter(hotspot_resource); }); } } wf::geometry_t calculate_hotspot_geometry(wf::output_t *output, uint32_t edge_mask, uint32_t distance) const { wf::geometry_t slot = output->get_layout_geometry(); if (edge_mask & ZWF_OUTPUT_V2_HOTSPOT_EDGE_TOP) { slot.height = distance; } else if (edge_mask & ZWF_OUTPUT_V2_HOTSPOT_EDGE_BOTTOM) { slot.y += slot.height - distance; slot.height = distance; } if (edge_mask & ZWF_OUTPUT_V2_HOTSPOT_EDGE_LEFT) { slot.width = distance; } else if (edge_mask & ZWF_OUTPUT_V2_HOTSPOT_EDGE_RIGHT) { slot.x += slot.width - distance; slot.width = distance; } return slot; } wfs_hotspot(const wfs_hotspot &) = delete; wfs_hotspot(wfs_hotspot &&) = delete; wfs_hotspot& operator =(const wfs_hotspot&) = delete; wfs_hotspot& operator =(wfs_hotspot&&) = delete; public: /** * Create a new hotspot. * It is guaranteedd that edge_mask contains at most 2 non-opposing edges. */ wfs_hotspot(wf::output_t *output, uint32_t edge_mask, uint32_t distance, uint32_t timeout, wl_client *client, uint32_t id) { this->timeout_ms = timeout; this->hotspot_geometry = calculate_hotspot_geometry(output, edge_mask, distance); hotspot_resource = wl_resource_create(client, &zwf_hotspot_v2_interface, 1, id); wl_resource_set_implementation(hotspot_resource, NULL, this, handle_hotspot_destroy); // setup output destroy listener on_output_removed.set_callback([this, output] (wf::output_removed_signal *ev) { if (ev->output == output) { /* Make hotspot inactive by setting the region to empty */ hotspot_geometry = {0, 0, 0, 0}; process_input_motion({0, 0}); } }); wf::get_core().connect(&on_motion_event); wf::get_core().connect(&on_touch_motion); wf::get_core().connect(&on_tablet_axis); wf::get_core().output_layout->connect(&on_output_removed); } ~wfs_hotspot() = default; }; static void handle_hotspot_destroy(wl_resource *resource) { auto *hotspot = (wfs_hotspot*)wl_resource_get_user_data(resource); delete hotspot; wl_resource_set_user_data(resource, nullptr); } /* ------------------------------ wfs_output -------------------------------- */ static void handle_output_destroy(wl_resource *resource); static void handle_zwf_output_inhibit_output(wl_client*, wl_resource *resource); static void handle_zwf_output_inhibit_output_done(wl_client*, wl_resource *resource); static void handle_zwf_output_create_hotspot(wl_client*, wl_resource *resource, uint32_t hotspot, uint32_t threshold, uint32_t timeout, uint32_t id); static struct zwf_output_v2_interface zwf_output_impl = { .inhibit_output = handle_zwf_output_inhibit_output, .inhibit_output_done = handle_zwf_output_inhibit_output_done, .create_hotspot = handle_zwf_output_create_hotspot, }; /** * A signal emitted on the wayfire output where the menu should be toggled. */ struct wayfire_shell_toggle_menu_signal {}; /** * Represents a zwf_output_v2. * Lifetime is managed by the wl_resource */ class wfs_output { uint32_t num_inhibits = 0; wl_resource *shell_resource; wl_resource *resource; wf::output_t *output; void disconnect_from_output() { wf::get_core().output_layout->disconnect(&on_output_removed); on_fullscreen_layer_focused.disconnect(); } wf::signal::connection_t on_output_removed = [=] (wf::output_removed_signal *ev) { if (ev->output == this->output) { disconnect_from_output(); this->output = nullptr; } }; wf::signal::connection_t on_fullscreen_layer_focused = [=] (wf::fullscreen_layer_focused_signal *ev) { if (ev->has_promoted) { zwf_output_v2_send_enter_fullscreen(resource); } else { zwf_output_v2_send_leave_fullscreen(resource); } }; wf::signal::connection_t on_toggle_menu = [=] (auto) { if (wl_resource_get_version(shell_resource) < ZWF_OUTPUT_V2_TOGGLE_MENU_SINCE_VERSION) { return; } zwf_output_v2_send_toggle_menu(resource); }; public: wfs_output(wf::output_t *output, wl_resource *shell_resource, wl_client *client, int id) { this->output = output; this->shell_resource = shell_resource; resource = wl_resource_create(client, &zwf_output_v2_interface, std::min(wl_resource_get_version(shell_resource), 2), id); wl_resource_set_implementation(resource, &zwf_output_impl, this, handle_output_destroy); output->connect(&on_fullscreen_layer_focused); output->connect(&on_toggle_menu); wf::get_core().output_layout->connect(&on_output_removed); } ~wfs_output() { if (!this->output) { /* The wayfire output was destroyed. Gracefully do nothing */ return; } disconnect_from_output(); /* Remove any remaining inhibits, otherwise the compositor will never * be "unlocked" */ while (num_inhibits > 0) { this->output->render->add_inhibit(false); --num_inhibits; } } wfs_output(const wfs_output &) = delete; wfs_output(wfs_output &&) = delete; wfs_output& operator =(const wfs_output&) = delete; wfs_output& operator =(wfs_output&&) = delete; void inhibit_output() { ++this->num_inhibits; if (this->output) { this->output->render->add_inhibit(true); } } void inhibit_output_done() { if (this->num_inhibits == 0) { wl_resource_post_no_memory(resource); return; } --this->num_inhibits; if (this->output) { this->output->render->add_inhibit(false); } } void create_hotspot(uint32_t hotspot, uint32_t threshold, uint32_t timeout, uint32_t id) { if (!this->output) { // It can happen that the client requests a hotspot immediately after an output is destroyed - // this is an inherent race condition because the compositor and client are not in sync. // // In this case, we create a dummy hotspot resource to avoid Wayland protocol errors. auto resource = wl_resource_create( wl_resource_get_client(this->resource), &zwf_hotspot_v2_interface, 1, id); wl_resource_set_implementation(resource, NULL, NULL, NULL); return; } // will be auto-deleted when the resource is destroyed by the client new wfs_hotspot(this->output, hotspot, threshold, timeout, wl_resource_get_client(this->resource), id); } }; static void handle_zwf_output_inhibit_output(wl_client*, wl_resource *resource) { auto output = (wfs_output*)wl_resource_get_user_data(resource); output->inhibit_output(); } static void handle_zwf_output_inhibit_output_done( wl_client*, wl_resource *resource) { auto output = (wfs_output*)wl_resource_get_user_data(resource); output->inhibit_output_done(); } static void handle_zwf_output_create_hotspot(wl_client*, wl_resource *resource, uint32_t hotspot, uint32_t threshold, uint32_t timeout, uint32_t id) { auto output = (wfs_output*)wl_resource_get_user_data(resource); output->create_hotspot(hotspot, threshold, timeout, id); } static void handle_output_destroy(wl_resource *resource) { auto *output = (wfs_output*)wl_resource_get_user_data(resource); delete output; wl_resource_set_user_data(resource, nullptr); } /* ------------------------------ wfs_surface ------------------------------- */ static void handle_surface_destroy(wl_resource *resource); static void handle_zwf_surface_interactive_move(wl_client*, wl_resource *resource); static struct zwf_surface_v2_interface zwf_surface_impl = { .interactive_move = handle_zwf_surface_interactive_move, }; /** * Represents a zwf_surface_v2. * Lifetime is managed by the wl_resource */ class wfs_surface { wl_resource *resource; wayfire_view view; wf::signal::connection_t on_unmap = [=] (auto) { view = nullptr; }; public: wfs_surface(wayfire_view view, wl_client *client, int id) { this->view = view; resource = wl_resource_create(client, &zwf_surface_v2_interface, 1, id); wl_resource_set_implementation(resource, &zwf_surface_impl, this, handle_surface_destroy); view->connect(&on_unmap); } ~wfs_surface() = default; void interactive_move() { LOGE("Interactive move no longer supported!"); } }; static void handle_zwf_surface_interactive_move(wl_client*, wl_resource *resource) { auto surface = (wfs_surface*)wl_resource_get_user_data(resource); surface->interactive_move(); } static void handle_surface_destroy(wl_resource *resource) { auto surface = (wfs_surface*)wl_resource_get_user_data(resource); delete surface; wl_resource_set_user_data(resource, nullptr); } static void zwf_shell_manager_get_wf_output(wl_client *client, wl_resource *resource, wl_resource *output, uint32_t id) { auto wlr_out = (wlr_output*)wl_resource_get_user_data(output); auto wo = wf::get_core().output_layout->find_output(wlr_out); if (wo) { // will be deleted when the resource is destroyed new wfs_output(wo, resource, client, id); } } static void zwf_shell_manager_get_wf_surface(wl_client *client, wl_resource *resource, wl_resource *surface, uint32_t id) { auto view = wf::wl_surface_to_wayfire_view(surface); if (view) { /* Will be freed when the resource is destroyed */ new wfs_surface(view, client, id); } } const struct zwf_shell_manager_v2_interface zwf_shell_manager_v2_impl = { zwf_shell_manager_get_wf_output, zwf_shell_manager_get_wf_surface, }; void bind_zwf_shell_manager(wl_client *client, void *data, uint32_t version, uint32_t id) { auto resource = wl_resource_create(client, &zwf_shell_manager_v2_interface, version, id); wl_resource_set_implementation(resource, &zwf_shell_manager_v2_impl, NULL, NULL); } struct wayfire_shell { wl_global *shell_manager; }; wayfire_shell *wayfire_shell_create(wl_display *display) { wayfire_shell *ws = new wayfire_shell; ws->shell_manager = wl_global_create(display, &zwf_shell_manager_v2_interface, 2, NULL, bind_zwf_shell_manager); if (ws->shell_manager == NULL) { LOGE("Failed to create wayfire_shell interface"); delete ws; return NULL; } return ws; } class wayfire_shell_protocol_impl : public wf::plugin_interface_t { wf::ipc_activator_t toggle_menu{"wayfire-shell/toggle_menu"}; wf::ipc_activator_t::handler_t toggle_menu_cb = [=] (wf::output_t *toggle_menu_output, wayfire_view) { wayfire_shell_toggle_menu_signal toggle_menu; toggle_menu_output->emit(&toggle_menu); return true; }; public: void init() override { wf_shell = wayfire_shell_create(wf::get_core().display); toggle_menu.set_handler(toggle_menu_cb); } void fini() override { wl_global_destroy(wf_shell->shell_manager); delete wf_shell; } private: wayfire_shell *wf_shell; }; DECLARE_WAYFIRE_PLUGIN(wayfire_shell_protocol_impl); wayfire-0.8.1/plugins/protocols/xdg-activation.cpp000066400000000000000000000033661457431457600223500ustar00rootroot00000000000000#include "wayfire/core.hpp" #include "wayfire/signal-definitions.hpp" #include "wayfire/view.hpp" #include #include #include #include #include #include #include "config.h" class wayfire_xdg_activation_protocol_impl : public wf::plugin_interface_t { public: void init() override { xdg_activation = wlr_xdg_activation_v1_create(wf::get_core().display); xdg_activation_request_activate.notify = xdg_activation_handle_request_activate; wl_signal_add(&xdg_activation->events.request_activate, &xdg_activation_request_activate); } void fini() override {} bool is_unloadable() override { return false; } private: static void xdg_activation_handle_request_activate(struct wl_listener *listener, void *data) { auto event = static_cast(data); wayfire_view view = wf::wl_surface_to_wayfire_view(event->surface->resource); if (!view) { LOGE("Could not get view"); return; } auto toplevel = wf::toplevel_cast(view); if (!toplevel) { LOGE("Could not get toplevel view"); return; } if (!event->token->seat) { LOGI("Denying focus request, seat wasn't supplied"); return; } LOGI("Activating view"); wf::get_core().default_wm->focus_request(toplevel); } struct wlr_xdg_activation_v1 *xdg_activation; struct wl_listener xdg_activation_request_activate; }; DECLARE_WAYFIRE_PLUGIN(wayfire_xdg_activation_protocol_impl); wayfire-0.8.1/plugins/scale/000077500000000000000000000000001457431457600157565ustar00rootroot00000000000000wayfire-0.8.1/plugins/scale/meson.build000066400000000000000000000013231457431457600201170ustar00rootroot00000000000000all_include_dirs = [wayfire_api_inc, wayfire_conf_inc, plugins_common_inc, vswitch_inc, wobbly_inc, include_directories('.')] all_deps = [wlroots, pixman, wfconfig, wftouch, cairo, pango, pangocairo, json] shared_module('scale', ['scale.cpp', 'scale-title-overlay.cpp'], include_directories: all_include_dirs, dependencies: all_deps, install: true, install_dir: conf_data.get('PLUGIN_PATH')) shared_module('scale-title-filter', 'scale-title-filter.cpp', include_directories: all_include_dirs, dependencies: all_deps, install: true, install_dir: conf_data.get('PLUGIN_PATH')) install_headers(['wayfire/plugins/scale-signal.hpp'], subdir: 'wayfire/plugins') wayfire-0.8.1/plugins/scale/scale-title-filter.cpp000066400000000000000000000310141457431457600221520ustar00rootroot00000000000000#include "wayfire/plugins/common/shared-core-data.hpp" #include "wayfire/util.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include class scale_title_filter; /** * Class storing the filter text, shared among all outputs */ struct scale_title_filter_text { std::string title_filter; /* since title filter is utf-8, here we store the length of each * character when adding them so backspace will work properly */ std::vector char_len; /* Individual plugins running on each output -- this is used to update them * when the shared filter text changes. */ std::vector output_instances; void add_instance(scale_title_filter *ptr) { output_instances.push_back(ptr); } void rem_instance(scale_title_filter *ptr) { auto it = std::remove(output_instances.begin(), output_instances.end(), ptr); output_instances.erase(it, output_instances.end()); } /** * Add any character corresponding to the given keycode to the filter. * * Updates the overlays and filter on all outputs if necessary. */ void add_key(struct xkb_state *xkb_state, xkb_keycode_t keycode); /** * Remove the last character from the overlay. * * Updates the overlays and filter on all outputs if necessary. */ void rem_char(); /** * Check if scale has ended on all outputs and clears the filter in this case. */ void check_scale_end(); /** * Clear the current filter text. Does not update output-specific instances. */ void clear() { title_filter.clear(); char_len.clear(); } }; class scale_title_filter : public wf::per_output_plugin_instance_t { wf::option_wrapper_t case_sensitive{"scale-title-filter/case_sensitive"}; wf::option_wrapper_t share_filter{"scale-title-filter/share_filter"}; scale_title_filter_text local_filter; wf::shared_data::ref_ptr_t global_filter; inline void fix_case(std::string& string) { if (case_sensitive) { return; } auto transform = [] (unsigned char c) -> unsigned char { if (std::isspace(c)) { return ' '; } return (c <= 127) ? (unsigned char)std::tolower(c) : c; }; std::transform(string.begin(), string.end(), string.begin(), transform); } bool should_show_view(wayfire_view view) { auto filter = get_active_filter().title_filter; if (filter.empty()) { return true; } auto title = view->get_title(); auto app_id = view->get_app_id(); fix_case(title); fix_case(app_id); fix_case(filter); return (title.find(filter) != std::string::npos) || (app_id.find(filter) != std::string::npos); } scale_title_filter_text& get_active_filter() { return share_filter ? *global_filter.get() : local_filter; } public: bool scale_running = false; scale_title_filter() { local_filter.add_instance(this); } void init() override { global_filter->add_instance(this); share_filter.set_callback(shared_option_changed); output->connect(&view_filter); output->connect(&scale_end); } void fini() override { do_end_scale(); global_filter->rem_instance(this); } wf::signal::connection_t view_filter = [=] (scale_filter_signal *ev) { if (!scale_running) { wf::get_core().connect(&scale_key); scale_running = true; update_overlay(); } scale_filter_views(ev, [this] (wayfire_toplevel_view v) { return !should_show_view(v); }); }; std::map> keys; wf::key_repeat_t::callback_t handle_key_repeat = [=] (uint32_t raw_keycode) { auto seat = wf::get_core().get_current_seat(); auto keyboard = wlr_seat_get_keyboard(seat); if (!keyboard) { return false; /* should not happen */ } auto xkb_state = keyboard->xkb_state; xkb_keycode_t keycode = raw_keycode + 8; xkb_keysym_t keysym = xkb_state_key_get_one_sym(xkb_state, keycode); auto& filter = get_active_filter(); if (keysym == XKB_KEY_BackSpace) { filter.rem_char(); } else { filter.add_key(xkb_state, keycode); } return true; }; wf::wl_idle_call idle_update_filter; void update_filter() { // Delay updating the filter in case the last key causes scale to exit. idle_update_filter.run_once([&] { if (scale_running) { scale_update_signal ev; output->emit(&ev); update_overlay(); } }); } wf::signal::connection_t> scale_key = [this] (wf::input_event_signal *ev) { if (ev->event->state == WL_KEYBOARD_KEY_STATE_RELEASED) { keys.erase(ev->event->keycode); return; } if ((ev->event->keycode == KEY_ESC) || (ev->event->keycode == KEY_ENTER)) { return; } if (output != wf::get_core().seat->get_active_output()) { return; } keys[ev->event->keycode] = std::make_unique(ev->event->keycode, handle_key_repeat); handle_key_repeat(ev->event->keycode); }; wf::signal::connection_t scale_end = [=] (scale_end_signal *ev) { do_end_scale(); }; void do_end_scale() { scale_key.disconnect(); keys.clear(); clear_overlay(); scale_running = false; get_active_filter().check_scale_end(); } wf::config::option_base_t::updated_callback_t shared_option_changed = [this] () { if (scale_running) { /* clear the filter that is not used anymore */ auto& filter = get_active_filter(); filter.clear(); scale_update_signal ev; output->emit(&ev); update_overlay(); } }; protected: /* * Text overlay with the current filter */ wf::cairo_text_t filter_overlay; wf::dimensions_t overlay_size; float output_scale = 1.0f; /* render function */ wf::effect_hook_t render_hook = [=] () { render(); }; /* flag to indicate if render_hook is active */ bool render_active = false; wf::option_wrapper_t bg_color{"scale-title-filter/bg_color"}; wf::option_wrapper_t text_color{"scale-title-filter/text_color"}; wf::option_wrapper_t show_overlay{"scale-title-filter/overlay"}; wf::option_wrapper_t font_size{"scale-title-filter/font_size"}; static wf::dimensions_t min(const wf::dimensions_t& x, const wf::dimensions_t& y) { return {std::min(x.width, y.width), std::min(x.height, y.height)}; } static wf::dimensions_t max(const wf::dimensions_t& x, const wf::dimensions_t& y) { return {std::max(x.width, y.width), std::max(x.height, y.height)}; } void update_overlay() { const auto& filter = get_active_filter().title_filter; if (!show_overlay || filter.empty()) { /* remove any overlay */ clear_overlay(); return; } auto dim = output->get_screen_size(); auto new_size = filter_overlay.render_text(filter, wf::cairo_text_t::params(font_size, bg_color, text_color, output_scale, dim)); if (!render_active) { output->render->add_effect(&render_hook, wf::OUTPUT_EFFECT_OVERLAY); render_active = true; } auto surface_size = min(new_size, {filter_overlay.tex.width, filter_overlay.tex.height}); auto damage = max(surface_size, overlay_size); output->render->damage({ dim.width / 2 - (int)(damage.width / output_scale / 2), dim.height / 2 - (int)(damage.height / output_scale / 2), (int)(damage.width / output_scale), (int)(damage.height / output_scale) }); overlay_size = surface_size; } /* render the current content of the overlay texture */ void render() { auto out_fb = output->render->get_target_framebuffer(); auto dim = output->get_screen_size(); if (output_scale != out_fb.scale) { output_scale = out_fb.scale; update_overlay(); } const wf::simple_texture_t& tex = filter_overlay.tex; if (tex.tex == (GLuint) - 1) { return; } wf::geometry_t geometry{ dim.width / 2 - (int)(overlay_size.width / output_scale / 2), dim.height / 2 - (int)(overlay_size.height / output_scale / 2), (int)(overlay_size.width / output_scale), (int)(overlay_size.height / output_scale) }; gl_geometry gl_geom{(float)geometry.x, (float)geometry.y, (float)(geometry.x + geometry.width), (float)(geometry.y + geometry.height)}; float tex_wr = (float)overlay_size.width / (float)tex.width; float tex_hr = (float)overlay_size.height / (float)tex.height; gl_geometry tex_geom{0.5f - tex_wr / 2.f, 0.5f - tex_hr / 2.f, 0.5f + tex_wr / 2.f, 0.5f + tex_hr / 2.f}; auto damage = output->render->get_scheduled_damage() & geometry; auto ortho = out_fb.get_orthographic_projection(); OpenGL::render_begin(out_fb); for (auto& box : damage) { out_fb.logic_scissor(wlr_box_from_pixman_box(box)); OpenGL::render_transformed_texture(tex.tex, gl_geom, tex_geom, ortho, glm::vec4(1.f), OpenGL::TEXTURE_TRANSFORM_INVERT_Y | OpenGL::TEXTURE_USE_TEX_GEOMETRY); } OpenGL::render_end(); } /* clear everything rendered by this plugin and deactivate rendering */ void clear_overlay() { if (render_active) { output->render->rem_effect(&render_hook); auto dim = output->get_screen_size(); int surface_width = filter_overlay.tex.width; int surface_height = filter_overlay.tex.height; output->render->damage({ dim.width / 2 - (int)(surface_width / output_scale / 2), dim.height / 2 - (int)(surface_height / output_scale / 2), (int)(surface_width / output_scale), (int)(surface_height / output_scale) }); render_active = false; } } }; void scale_title_filter_text::add_key(struct xkb_state *xkb_state, xkb_keycode_t keycode) { /* taken from libxkbcommon guide */ int size = xkb_state_key_get_utf8(xkb_state, keycode, nullptr, 0); if (size <= 0) { return; } std::string tmp(size, 0); xkb_state_key_get_utf8(xkb_state, keycode, tmp.data(), size + 1); char_len.push_back(size); title_filter += tmp; for (auto p : output_instances) { p->update_filter(); } } void scale_title_filter_text::rem_char() { if (!title_filter.empty()) { int len = char_len.back(); char_len.pop_back(); title_filter.resize(title_filter.length() - len); } else { return; } for (auto p : output_instances) { p->update_filter(); } } void scale_title_filter_text::check_scale_end() { bool scale_running = false; for (auto p : output_instances) { if (p->scale_running) { scale_running = true; break; } } if (!scale_running) { clear(); } } DECLARE_WAYFIRE_PLUGIN(wf::per_output_plugin_t); wayfire-0.8.1/plugins/scale/scale-title-overlay.cpp000066400000000000000000000340011457431457600223450ustar00rootroot00000000000000#include "scale.hpp" #include "scale-title-overlay.hpp" #include "wayfire/core.hpp" #include "wayfire/debug.hpp" #include "wayfire/geometry.hpp" #include "wayfire/output.hpp" #include "wayfire/plugins/common/util.hpp" #include "wayfire/plugins/scale-signal.hpp" #include "wayfire/render-manager.hpp" #include "wayfire/scene-operations.hpp" #include "wayfire/signal-definitions.hpp" #include "wayfire/view-transform.hpp" #include #include #include #include #include #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. */ struct view_title_texture_t : public wf::custom_data_t { wayfire_toplevel_view view; wf::cairo_text_t overlay; wf::cairo_text_t::params par; bool overflow = false; wayfire_toplevel_view dialog; /* the texture should be rendered on top of this dialog */ /** * Render the overlay text in our texture, cropping it to the size by * the given box. */ void update_overlay_texture(wf::dimensions_t dim) { par.max_size = dim; update_overlay_texture(); } void update_overlay_texture() { auto res = overlay.render_text(view->get_title(), par); overflow = res.width > overlay.tex.width; } wf::signal::connection_t view_changed_title = [=] (wf::view_title_changed_signal *ev) { if (overlay.tex.tex != (GLuint) - 1) { update_overlay_texture(); } }; view_title_texture_t(wayfire_toplevel_view v, int font_size, const wf::color_t& bg_color, const wf::color_t& text_color, float output_scale) : view(v) { par.font_size = font_size; par.bg_color = bg_color; par.text_color = text_color; par.exact_size = true; par.output_scale = output_scale; view->connect(&view_changed_title); } }; namespace wf { namespace scene { class title_overlay_node_t : public node_t { public: enum class position { TOP, CENTER, BOTTOM, }; /* save the transformed view, since we need it in the destructor */ wayfire_toplevel_view view; /* the position on the screen we currently render to */ wf::geometry_t geometry{0, 0, 0, 0}; scale_show_title_t& parent; unsigned int text_height; /* set in the constructor, should not change */ position pos = position::CENTER; /* Whether we are currently rendering the overlay by this transformer. * Set in the pre-render hook and used in the render function. */ bool overlay_shown = false; private: /** * Gets the overlay texture stored with the given view. */ view_title_texture_t& get_overlay_texture(wayfire_toplevel_view view) { auto data = view->get_data(); if (!data) { auto new_data = new view_title_texture_t(view, parent.title_font_size, parent.bg_color, parent.text_color, parent.output->handle->scale); view->store_data(std::unique_ptr( new_data)); return *new_data; } return *data.get(); } wf::geometry_t get_scaled_bbox(wayfire_toplevel_view v) { auto tr = v->get_transformed_node()-> get_transformer("scale"); if (tr) { auto wm_geometry = v->get_geometry(); return get_bbox_for_node(tr, wm_geometry); } return v->get_bounding_box(); } wf::dimensions_t find_maximal_title_size() { wf::dimensions_t max_size = {0, 0}; auto parent = find_toplevel_parent(view); for (auto v : parent->enumerate_views()) { if (!v->get_transformed_node()->is_enabled()) { continue; } auto bbox = get_scaled_bbox(v); max_size.width = std::max(max_size.width, bbox.width); max_size.height = std::max(max_size.height, bbox.height); } return max_size; } /** * Check if this view should display an overlay. */ bool should_have_overlay() { if (this->parent.show_view_title_overlay == scale_show_title_t::title_overlay_t::NEVER) { return false; } auto parent = find_toplevel_parent(view); if ((this->parent.show_view_title_overlay == scale_show_title_t::title_overlay_t::MOUSE) && (this->parent.last_title_overlay != parent)) { return false; } while (!parent->children.empty()) { parent = parent->children[0]; } return view == parent; } wf::effect_hook_t pre_render = [=] () -> void { if (!should_have_overlay()) { overlay_shown = false; return; } overlay_shown = true; auto box = find_maximal_title_size(); auto output_scale = parent.output->handle->scale; /** * regenerate the overlay texture in the following cases: * 1. Output's scale changed * 2. The overlay does not fit anymore * 3. The overlay previously did not fit, but there is more space now * 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)); if ((tex.overlay.tex.tex == (GLuint) - 1) || (output_scale != tex.par.output_scale) || (tex.overlay.tex.width > box.width * output_scale) || (tex.overflow && (tex.overlay.tex.width < std::floor(box.width * output_scale)))) { this->do_push_damage(get_bounding_box()); tex.par.output_scale = output_scale; tex.update_overlay_texture({box.width, box.height}); } geometry.width = tex.overlay.tex.width / output_scale; geometry.height = tex.overlay.tex.height / output_scale; auto bbox = get_scaled_bbox(view); geometry.x = bbox.x + bbox.width / 2 - geometry.width / 2; switch (pos) { case position::TOP: geometry.y = bbox.y; break; case position::CENTER: geometry.y = bbox.y + bbox.height / 2 - geometry.height / 2; break; case position::BOTTOM: geometry.y = bbox.y + bbox.height - geometry.height / 2; break; } this->do_push_damage(get_bounding_box()); }; wf::output_t *output; public: title_overlay_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& title = get_overlay_texture(parent); if (title.overlay.tex.tex != (GLuint) - 1) { text_height = (unsigned int)std::ceil( title.overlay.tex.height / title.par.output_scale); } else { text_height = wf::cairo_text_t::measure_height(title.par.font_size, true); } this->output = view->get_output(); output->render->add_effect(&pre_render, OUTPUT_EFFECT_PRE); } ~title_overlay_node_t() { output->render->rem_effect(&pre_render); view->erase_data(); } void gen_render_instances( std::vector& instances, damage_callback push_damage, wf::output_t *output) override; void do_push_damage(wf::region_t updated_region) { node_damage_signal ev; ev.region = updated_region; this->emit(&ev); } std::string stringify() const override { return "scale-title-overlay"; } wf::geometry_t get_bounding_box() override { return geometry; } }; class title_overlay_render_instance_t : public render_instance_t { wf::signal::connection_t on_node_damaged = [=] (node_damage_signal *ev) { push_to_parent(ev->region); }; title_overlay_node_t *self; damage_callback push_to_parent; public: title_overlay_render_instance_t(title_overlay_node_t *self, damage_callback push_dmg) { this->self = self; this->push_to_parent = push_dmg; self->connect(&on_node_damaged); } void schedule_instructions( std::vector& instructions, const wf::render_target_t& target, wf::region_t& damage) { if (!self->overlay_shown || !self->view->has_data()) { return; } // We want to render ourselves only, the node does not have children instructions.push_back(render_instruction_t{ .instance = this, .target = target, .damage = damage & self->get_bounding_box(), }); } void render(const wf::render_target_t& target, const wf::region_t& region) { auto& title = *self->view->get_data(); auto tr = self->view->get_transformed_node() ->get_transformer("scale"); GLuint tex = title.overlay.tex.tex; if (tex == (GLuint) - 1) { /* this should not happen */ return; } auto ortho = target.get_orthographic_projection(); OpenGL::render_begin(target); for (const auto& box : region) { target.logic_scissor(wlr_box_from_pixman_box(box)); OpenGL::render_transformed_texture(tex, self->geometry, ortho, {1.0f, 1.0f, 1.0f, tr->alpha}, OpenGL::TEXTURE_TRANSFORM_INVERT_Y); } OpenGL::render_end(); } }; void title_overlay_node_t::gen_render_instances( std::vector& instances, damage_callback push_damage, wf::output_t *output) { instances.push_back(std::make_unique( this, push_damage)); } } } scale_show_title_t::scale_show_title_t() : view_filter{[this] (auto) { update_title_overlay_opt(); }}, scale_end{[this] (auto) { show_view_title_overlay = title_overlay_t::NEVER; last_title_overlay = nullptr; post_absolute_motion.disconnect(); post_motion.disconnect(); } }, add_title_overlay{[this] (scale_transformer_added_signal *signal) { const std::string& opt = show_view_title_overlay_opt; if (opt == "never") { /* TODO: support changing this option while scale is running! */ return; } using namespace wf::scene; const std::string& pos_opt = title_position; title_overlay_node_t::position pos = title_overlay_node_t::position::CENTER; if (pos_opt == "top") { pos = title_overlay_node_t::position::TOP; } else if (pos_opt == "bottom") { pos = title_overlay_node_t::position::BOTTOM; } auto tr = signal->view->get_transformed_node()->get_transformer("scale"); auto parent = std::dynamic_pointer_cast( tr->parent()->shared_from_this()); auto node = std::make_shared(signal->view, pos, *this); wf::scene::add_front(parent, node); } }, rem_title_overlay{[] (scale_transformer_removed_signal *signal) { using namespace wf::scene; node_t *tr = signal->view->get_transformed_node()->get_transformer("scale").get(); while (tr) { for (auto& ch : tr->get_children()) { if (dynamic_cast(ch.get())) { remove_child(ch); break; } } tr = tr->parent(); } } }, post_motion{[=] (auto) { update_title_overlay_mouse(); } }, post_absolute_motion{[=] (auto) { update_title_overlay_mouse(); } } {} void scale_show_title_t::init(wf::output_t *output) { this->output = output; output->connect(&view_filter); output->connect(&add_title_overlay); output->connect(&rem_title_overlay); output->connect(&scale_end); } void scale_show_title_t::fini() { post_motion.disconnect(); post_absolute_motion.disconnect(); } void scale_show_title_t::update_title_overlay_opt() { const std::string& tmp = show_view_title_overlay_opt; if (tmp == "all") { show_view_title_overlay = title_overlay_t::ALL; } else if (tmp == "mouse") { show_view_title_overlay = title_overlay_t::MOUSE; } else { show_view_title_overlay = title_overlay_t::NEVER; } if (show_view_title_overlay == title_overlay_t::MOUSE) { update_title_overlay_mouse(); post_absolute_motion.disconnect(); post_motion.disconnect(); wf::get_core().connect(&post_absolute_motion); wf::get_core().connect(&post_motion); } } 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); if (v->role != wf::VIEW_ROLE_TOPLEVEL) { v = nullptr; } } if (v != last_title_overlay) { if (last_title_overlay) { last_title_overlay->damage(); } last_title_overlay = v; if (v) { v->damage(); } } } wayfire-0.8.1/plugins/scale/scale-title-overlay.hpp000066400000000000000000000033771457431457600223660ustar00rootroot00000000000000#pragma once #include "wayfire/signal-definitions.hpp" #include "wayfire/signal-provider.hpp" #include #include #include #include namespace wf { namespace scene { class title_overlay_node_t; } } class scale_show_title_t { protected: /* Overlays for showing the title of each view */ wf::option_wrapper_t bg_color{"scale/bg_color"}; wf::option_wrapper_t text_color{"scale/text_color"}; wf::option_wrapper_t show_view_title_overlay_opt{ "scale/title_overlay"}; wf::option_wrapper_t title_font_size{"scale/title_font_size"}; wf::option_wrapper_t title_position{"scale/title_position"}; wf::output_t *output; public: scale_show_title_t(); void init(wf::output_t *output); void fini(); protected: /* signals */ wf::signal::connection_t view_filter; wf::signal::connection_t scale_end; wf::signal::connection_t add_title_overlay; wf::signal::connection_t rem_title_overlay; wf::signal::connection_t> post_motion; wf::signal::connection_t> post_absolute_motion; enum class title_overlay_t { NEVER, MOUSE, ALL, }; friend class wf::scene::title_overlay_node_t; title_overlay_t show_view_title_overlay; /* only used if title overlay is set to follow the mouse */ wayfire_view last_title_overlay = nullptr; void update_title_overlay_opt(); void update_title_overlay_mouse(); }; wayfire-0.8.1/plugins/scale/scale.cpp000066400000000000000000001354661457431457600175700ustar00rootroot00000000000000/** * Original code by: Scott Moreau, Daniel Kondor */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "plugins/ipc/ipc-activator.hpp" #include "scale.hpp" #include "scale-title-overlay.hpp" #include "wayfire/core.hpp" #include "wayfire/debug.hpp" #include "wayfire/plugin.hpp" #include "wayfire/plugins/common/util.hpp" #include "wayfire/scene-input.hpp" #include "wayfire/scene.hpp" #include "wayfire/signal-provider.hpp" #include "wayfire/toplevel-view.hpp" #include "wayfire/view.hpp" using namespace wf::animation; class scale_animation_t : public duration_t { public: using duration_t::duration_t; timed_transition_t scale_x{*this}; timed_transition_t scale_y{*this}; timed_transition_t translation_x{*this}; timed_transition_t translation_y{*this}; }; struct wf_scale_animation_attribs { wf::option_wrapper_t duration{"scale/duration"}; scale_animation_t scale_animation{duration}; }; struct view_scale_data { int row, col; std::shared_ptr transformer; wf::animation::simple_animation_t fade_animation; wf_scale_animation_attribs animation; enum class view_visibility_t { VISIBLE, /* view is shown in position determined by layout_slots() */ HIDING, /* view is in the process of hiding (due to filters) */ HIDDEN, /* view is hidden by a filter (with set_visible(false)) */ }; view_visibility_t visibility = view_visibility_t::VISIBLE; bool was_minimized = false; /* flag to indicate if this view was originally minimized */ }; /** * Scale has the following hard coded bindings are as follows: * KEY_ENTER: * - Ends scale, switching to the workspace of the focused view * KEY_ESC: * - Ends scale, switching to the workspace where scale was started, * and focuses the initially active view * KEY_UP: * KEY_DOWN: * KEY_LEFT: * KEY_RIGHT: * - When scale is active, change focus of the views * * BTN_LEFT: * - Ends scale, switching to the workspace of the surface clicked * BTN_MIDDLE: * - If middle_click_close is true, closes the view clicked */ class wayfire_scale : public wf::per_output_plugin_instance_t, public wf::keyboard_interaction_t, public wf::pointer_interaction_t, public wf::touch_interaction_t { /* helper class for optionally showing title overlays */ scale_show_title_t show_title; std::vector current_row_sizes; wf::point_t initial_workspace; bool hook_set; /* View that was active before scale began. */ wayfire_toplevel_view initial_focus_view; /* View that has active focus. */ wayfire_toplevel_view current_focus_view; // View over which the last input press happened, might become dangling wayfire_toplevel_view last_selected_view; std::map scale_data; wf::option_wrapper_t spacing{"scale/spacing"}; wf::option_wrapper_t outer_margin{"scale/outer_margin"}; wf::option_wrapper_t middle_click_close{"scale/middle_click_close"}; wf::option_wrapper_t inactive_alpha{"scale/inactive_alpha"}; wf::option_wrapper_t minimized_alpha{"scale/minimized_alpha"}; wf::option_wrapper_t allow_scale_zoom{"scale/allow_zoom"}; wf::option_wrapper_t include_minimized{"scale/include_minimized"}; /* maximum scale -- 1.0 means we will not "zoom in" on a view */ const double max_scale_factor = 1.0; /* maximum scale for child views (relative to their parents) * zero means unconstrained, 1.0 means child cannot be scaled * "larger" than the parent */ const double max_scale_child = 1.0; /* true if the currently running scale should include views from * all workspaces */ bool all_workspaces; std::unique_ptr workspace_bindings; wf::shared_data::ref_ptr_t drag_helper; std::unique_ptr grab; wf::plugin_activation_data_t grab_interface{ .name = "scale", .capabilities = wf::CAPABILITY_MANAGE_DESKTOP | wf::CAPABILITY_GRAB_INPUT, .cancel = [=] () { finalize(); }, }; public: bool active = false; void init() override { hook_set = false; grab = std::make_unique("scale", output, this, this, this); allow_scale_zoom.set_callback(allow_scale_zoom_option_changed); setup_workspace_switching(); drag_helper->connect(&on_drag_output_focus); drag_helper->connect(&on_drag_done); drag_helper->connect(&on_drag_snap_off); show_title.init(output); output->connect(&update_cb); } void setup_workspace_switching() { workspace_bindings = std::make_unique(output); workspace_bindings->setup([&] (wf::point_t delta, wayfire_toplevel_view view, bool only_view) { if (!output->is_plugin_active(grab_interface.name)) { return false; } if (delta == wf::point_t{0, 0}) { // Consume input event return true; } if (only_view) { // For now, scale does not let you move views between workspaces return false; } auto ws = output->wset()->get_current_workspace() + delta; // vswitch picks the top view, we want the focused one std::vector fixed_views; if (view && !all_workspaces) { fixed_views.push_back(current_focus_view); } output->wset()->request_workspace(ws, fixed_views); return true; }); } /* Add a transformer that will be used to scale the view */ bool add_transformer(wayfire_toplevel_view view) { if (view->get_transformed_node()->get_transformer("scale")) { return false; } auto tr = std::make_shared(view); scale_data[view].transformer = tr; view->get_transformed_node()->add_transformer(tr, wf::TRANSFORMER_2D, "scale"); /* Handle potentially minimized views by making them visible, * however, they start out as fully transparent. */ if (view->minimized) { tr->alpha = 0.0; wf::scene::set_node_enabled(view->get_root_node(), true); scale_data[view].was_minimized = true; } /* Transformers are added only once when scale is activated so * this is a good place to connect the geometry-changed handler */ view->connect(&view_geometry_changed); set_tiled_wobbly(view, true); /* signal that a transformer was added to this view */ scale_transformer_added_signal data; data.view = view; output->emit(&data); return true; } /* Remove the scale transformer from the view */ void pop_transformer(wayfire_toplevel_view view) { /* signal that a transformer was added to this view */ scale_transformer_removed_signal data; data.view = view; output->emit(&data); view->get_transformed_node()->rem_transformer("scale"); set_tiled_wobbly(view, false); } /* Remove scale transformers from all views */ void remove_transformers() { for (auto& e : scale_data) { for (auto& toplevel : e.first->enumerate_views(false)) { pop_transformer(toplevel); } if (e.second.was_minimized) { wf::scene::set_node_enabled(e.first->get_root_node(), false); } if (e.second.visibility == view_scale_data::view_visibility_t::HIDDEN) { wf::scene::set_node_enabled(e.first->get_transformed_node(), true); } e.second.visibility = view_scale_data::view_visibility_t::VISIBLE; } } /* Check whether views exist on other workspaces */ bool all_same_as_current_workspace_views() { return get_all_workspace_views().size() == get_current_workspace_views().size(); } /* Activate scale, switch activator modes and deactivate */ bool handle_toggle(bool want_all_workspaces) { if (active && (all_same_as_current_workspace_views() || (want_all_workspaces == this->all_workspaces))) { deactivate(); return true; } this->all_workspaces = want_all_workspaces; if (active) { switch_scale_modes(); return true; } else { return activate(); } } wf::signal::connection_t update_cb = [=] (scale_update_signal *ev) { if (active) { layout_slots(get_views()); output->render->schedule_redraw(); } }; void handle_pointer_button( const wlr_pointer_button_event& event) override { process_input(event.button, event.state, wf::get_core().get_cursor_position()); } void handle_touch_down(uint32_t, int finger_id, wf::pointf_t pos) override { if (finger_id == 0) { process_input(BTN_LEFT, WLR_BUTTON_PRESSED, pos); } } void handle_touch_up(uint32_t, int finger_id, wf::pointf_t lift_off_position) override { if (finger_id == 0) { process_input(BTN_LEFT, WLR_BUTTON_RELEASED, lift_off_position); } } void handle_touch_motion(uint32_t time, int finger_id, wf::pointf_t position) override { if (finger_id == 0) { handle_pointer_motion(position, time); } } /** 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) { for (auto& e : scale_data) { auto v = e.first; if (get_top_parent(v) == get_top_parent(view)) { continue; } if (e.second.visibility != view_scale_data::view_visibility_t::VISIBLE) { continue; } fade_out(v); } } /* Fade in view alpha */ void fade_in(wayfire_toplevel_view view) { if (!view || !scale_data.count(view)) { return; } set_hook(); auto alpha = scale_data[view].transformer->alpha; scale_data[view].fade_animation.animate(alpha, 1); if (view->children.size()) { fade_in(view->children.front()); } } /* Fade out view alpha */ void fade_out(wayfire_toplevel_view view) { if (!view) { return; } set_hook(); for (auto v : view->enumerate_views(false)) { // Could happen if we have a never-mapped child view if (!scale_data.count(v)) { continue; } auto alpha = scale_data[v].transformer->alpha; double target_alpha = (v->minimized) ? minimized_alpha : inactive_alpha; scale_data[v].fade_animation.animate(alpha, target_alpha); } } /* Switch to the workspace for the untransformed view geometry */ void select_view(wayfire_toplevel_view view) { if (!view) { return; } auto ws = get_view_main_workspace(view); output->wset()->request_workspace(ws); } /* Updates current and initial view focus variables accordingly */ void check_focus_view(wayfire_toplevel_view view) { if (view == current_focus_view) { current_focus_view = toplevel_cast(wf::get_active_view_for_output(output)); } if (view == initial_focus_view) { initial_focus_view = nullptr; } } /* Remove transformer from view and remove view from the scale_data map */ void remove_view(wayfire_toplevel_view view) { if (!view) { return; } if (scale_data.at(view).was_minimized) { wf::scene::set_node_enabled(view->get_root_node(), false); } for (auto v : view->enumerate_views(false)) { check_focus_view(v); pop_transformer(v); scale_data.erase(v); } } /* Process button event */ void process_input(uint32_t button, uint32_t state, wf::pointf_t input_position) { if (!active) { return; } if (state == WLR_BUTTON_PRESSED) { auto view = scale_find_view_at(input_position, output); if (view && should_scale_view(view)) { // Mark the view as the target of the next input release operation last_selected_view = view; } else { last_selected_view = nullptr; } drag_helper->set_pending_drag(input_position); return; } drag_helper->handle_input_released(); auto view = scale_find_view_at(input_position, output); if (!view || (last_selected_view != view)) { last_selected_view = nullptr; // Operation was cancelled, for ex. dragged outside of the view return; } // Reset last_selected_view, because it is no longer held last_selected_view = nullptr; switch (button) { case BTN_LEFT: // Focus the view under the mouse current_focus_view = view; fade_out_all_except(view); fade_in(get_top_parent(view)); // End scale initial_focus_view = nullptr; deactivate(); break; case BTN_MIDDLE: // Check kill the view if (middle_click_close) { view->close(); } break; default: break; } } void handle_pointer_motion(wf::pointf_t to_f, uint32_t time) override { wf::point_t to{(int)std::round(to_f.x), (int)std::round(to_f.y)}; if (!drag_helper->view && last_selected_view && drag_helper->should_start_pending_drag(to)) { wf::move_drag::drag_options_t opts; opts.join_views = true; opts.enable_snap_off = true; opts.snap_off_threshold = 200; // We want to receive raw inputs (e.g. no fake pointer releases) in case the view is moved to // another output. grab->set_wants_raw_input(true); drag_helper->start_drag(last_selected_view, opts); drag_helper->handle_motion(to); } else if (drag_helper->view) { drag_helper->handle_motion(to); if (last_selected_view) { const double threshold = 20.0; if (drag_helper->distance_to_grab_origin(to) > threshold) { last_selected_view = nullptr; } } } } /* 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; } auto ws = output->wset()->get_current_workspace(); auto og = output->get_layout_geometry(); auto vg = view->get_geometry(); auto center = wf::point_t{vg.x + vg.width / 2, vg.y + vg.height / 2}; return wf::point_t{ ws.x + (int)std::floor((double)center.x / og.width), ws.y + (int)std::floor((double)center.y / og.height)}; } /* Given row and column, return a view at this position in the scale grid, * or the first scaled view if none is found */ wayfire_toplevel_view find_view_in_grid(int row, int col) { for (auto& view : scale_data) { if ((view.first->parent == nullptr) && (view.second.visibility == view_scale_data::view_visibility_t::VISIBLE) && ((view.second.row == row) && (view.second.col == col))) { return view.first; } } return get_views().front(); } /* Process key event */ void handle_keyboard_key(wf::seat_t*, wlr_keyboard_key_event ev) override { auto view = toplevel_cast(wf::get_active_view_for_output(output)); if (!view) { view = current_focus_view; if (view) { fade_out_all_except(view); fade_in(view); wf::get_core().default_wm->focus_raise_view(view); return; } } else if (!scale_data.count(view)) { return; } int cur_row = view ? scale_data[view].row : 0; int cur_col = view ? scale_data[view].col : 0; int next_row = cur_row; int next_col = cur_col; if ((ev.state != WLR_KEY_PRESSED) || wf::get_core().seat->get_keyboard_modifiers()) { return; } switch (ev.keycode) { case KEY_UP: next_row--; break; case KEY_DOWN: next_row++; break; case KEY_LEFT: next_col--; break; case KEY_RIGHT: next_col++; break; case KEY_ENTER: deactivate(); select_view(current_focus_view); wf::get_core().default_wm->focus_raise_view(view); return; case KEY_ESC: deactivate(); output->wset()->request_workspace(initial_workspace); wf::get_core().default_wm->focus_raise_view(initial_focus_view); initial_focus_view = nullptr; return; default: return; } if (!view) { return; } if (!current_row_sizes.empty()) { next_row = (next_row + current_row_sizes.size()) % current_row_sizes.size(); if (cur_row != next_row) { /* when moving to and from the last row, the number of columns * may be different, so this bit figures out which view we * should switch focus to */ float p = 1.0 * cur_col / current_row_sizes[cur_row]; next_col = p * current_row_sizes[next_row]; } else { next_col = (next_col + current_row_sizes[cur_row]) % current_row_sizes[cur_row]; } } else { next_row = cur_row; next_col = cur_col; } view = find_view_in_grid(next_row, next_col); if (view && (current_focus_view != view)) { fade_out_all_except(view); fade_in(view); current_focus_view = view; // Update activated state wf::get_core().seat->focus_view(view); } } /* Assign the transformer values to the view transformers */ void transform_views() { for (auto& e : scale_data) { auto view = e.first; auto& view_data = e.second; if (!view || !view_data.transformer) { continue; } if (view_data.fade_animation.running() || view_data.animation.scale_animation.running()) { view->get_transformed_node()->begin_transform_update(); view_data.transformer->scale_x = view_data.animation.scale_animation.scale_x; view_data.transformer->scale_y = view_data.animation.scale_animation.scale_y; view_data.transformer->translation_x = view_data.animation.scale_animation.translation_x; view_data.transformer->translation_y = view_data.animation.scale_animation.translation_y; view_data.transformer->alpha = view_data.fade_animation; if ((view_data.visibility == view_scale_data::view_visibility_t::HIDING) && !view_data.fade_animation.running()) { view_data.visibility = view_scale_data::view_visibility_t::HIDDEN; wf::scene::set_node_enabled(view->get_transformed_node(), false); } view->get_transformed_node()->end_transform_update(); } } } /* Returns a list of views for all workspaces */ std::vector get_all_workspace_views() { return output->wset()->get_views( (include_minimized ? 0 : wf::WSET_EXCLUDE_MINIMIZED) | wf::WSET_MAPPED_ONLY); } /* Returns a list of views for the current workspace */ std::vector get_current_workspace_views() { std::vector views; for (auto& view : get_all_workspace_views()) { auto vg = view->get_geometry(); auto og = output->get_relative_geometry(); wf::region_t wr{og}; wf::point_t center{vg.x + vg.width / 2, vg.y + vg.height / 2}; if (wr.contains_point(center)) { views.push_back(view); } } return views; } /* Returns a list of views to be scaled */ std::vector get_views() { std::vector views; if (all_workspaces) { views = get_all_workspace_views(); } else { views = get_current_workspace_views(); } return views; } /** * @return true if the view is to be scaled. */ bool should_scale_view(wayfire_toplevel_view view) { auto views = get_views(); return std::find( views.begin(), views.end(), get_top_parent(view)) != views.end(); } /* Convenience assignment function */ void setup_view_transform(view_scale_data& view_data, double scale_x, double scale_y, double translation_x, double translation_y, double target_alpha) { view_data.animation.scale_animation.scale_x.set( view_data.transformer->scale_x, scale_x); view_data.animation.scale_animation.scale_y.set( view_data.transformer->scale_y, scale_y); view_data.animation.scale_animation.translation_x.set( view_data.transformer->translation_x, translation_x); view_data.animation.scale_animation.translation_y.set( view_data.transformer->translation_y, translation_y); view_data.animation.scale_animation.start(); view_data.fade_animation = wf::animation::simple_animation_t( wf::option_wrapper_t{"scale/duration"}); view_data.fade_animation.animate(view_data.transformer->alpha, target_alpha); } static bool view_compare_x(const wayfire_toplevel_view& a, const wayfire_toplevel_view& b) { auto vg_a = a->get_geometry(); std::vector a_coords = {vg_a.x, vg_a.width, vg_a.y, vg_a.height}; auto vg_b = b->get_geometry(); std::vector b_coords = {vg_b.x, vg_b.width, vg_b.y, vg_b.height}; return a_coords < b_coords; } static bool view_compare_y(const wayfire_toplevel_view& a, const wayfire_toplevel_view& b) { auto vg_a = a->get_geometry(); std::vector a_coords = {vg_a.y, vg_a.height, vg_a.x, vg_a.width}; auto vg_b = b->get_geometry(); std::vector b_coords = {vg_b.y, vg_b.height, vg_b.x, vg_b.width}; return a_coords < b_coords; } std::vector> view_sort( std::vector& views) { std::vector> view_grid; // First ensure a consistent sorting of all views using a persistent // identifier before sorting by geometry. // This is so that if two views have exactly the same geometry, // they will always appear in the same order in the output list. std::sort(views.begin(), views.end(), [] (auto a, auto b) { return a.get() < b.get(); }); std::stable_sort(views.begin(), views.end(), view_compare_y); int rows = sqrt(views.size() + 1); int views_per_row = (int)std::ceil((double)views.size() / rows); size_t n = views.size(); for (size_t i = 0; i < n; i += views_per_row) { size_t j = std::min(i + views_per_row, n); view_grid.emplace_back(views.begin() + i, views.begin() + j); std::stable_sort(view_grid.back().begin(), view_grid.back().end(), view_compare_x); } return view_grid; } /* Filter the views to be arranged by layout_slots() */ void filter_views(std::vector& views) { std::vector filtered_views; scale_filter_signal signal(views, filtered_views); output->emit(&signal); /* update hidden views -- ensure that they and their children have a * transformer and are in scale_data */ for (auto view : filtered_views) { for (auto v : view->enumerate_views(false)) { add_transformer(v); auto& view_data = scale_data[v]; if (view_data.visibility == view_scale_data::view_visibility_t::VISIBLE) { view_data.visibility = view_scale_data::view_visibility_t::HIDING; setup_view_transform(view_data, 1, 1, 0, 0, 0); } if (v == current_focus_view) { current_focus_view = nullptr; } } } if (!current_focus_view) { std::sort(views.begin(), views.end(), [=] (wayfire_toplevel_view a, wayfire_toplevel_view b) { if (a->minimized != b->minimized) { /* avoid focusing minimized views if possible, so * sort them after non-minimized ones */ return b->minimized; } return wf::get_focus_timestamp(a) > wf::get_focus_timestamp(b); }); current_focus_view = views.empty() ? nullptr : views.front(); wf::get_core().default_wm->focus_raise_view(current_focus_view); } } /* Compute target scale layout geometry for all the view transformers * and start animating. Initial code borrowed from the compiz scale * plugin algorithm */ void layout_slots(std::vector views) { if (!views.size()) { if (!all_workspaces && active) { deactivate(); } return; } filter_views(views); auto workarea = output->workarea->get_workarea(); workarea.x += outer_margin; workarea.y += outer_margin; workarea.width -= outer_margin * 2; workarea.height -= outer_margin * 2; auto sorted_rows = view_sort(views); size_t cnt_rows = sorted_rows.size(); const double scaled_height = std::max((double) (workarea.height - (cnt_rows + 1) * spacing) / cnt_rows, 1.0); current_row_sizes.clear(); for (size_t i = 0; i < cnt_rows; i++) { size_t cnt_cols = sorted_rows[i].size(); current_row_sizes.push_back(cnt_cols); const double scaled_width = std::max((double) (workarea.width - (cnt_cols + 1) * spacing) / cnt_cols, 1.0); for (size_t j = 0; j < cnt_cols; j++) { double x = workarea.x + spacing + (spacing + scaled_width) * j; double y = workarea.y + spacing + (spacing + scaled_height) * i; auto view = sorted_rows[i][j]; // Calculate current transformation of the view, in order to // ensure that new views in the view tree start directly at the // correct position double main_view_dx = 0; double main_view_dy = 0; double main_view_scale = 1.0; if (scale_data.count(view)) { main_view_dx = scale_data[view].transformer->translation_x; main_view_dy = scale_data[view].transformer->translation_y; main_view_scale = scale_data[view].transformer->scale_x; } // Calculate target alpha for this view and its children double target_alpha = 1.0; if (view != current_focus_view) { target_alpha = (view->minimized) ? minimized_alpha : inactive_alpha; } // Helper function to calculate the desired scale for a view const auto& calculate_scale = [=] (wf::dimensions_t vg) { double w = std::max(1.0, scaled_width); double h = std::max(1.0, scaled_height); const double scale = std::min(w / vg.width, h / vg.height); if (!allow_scale_zoom) { return std::min(scale, max_scale_factor); } return scale; }; add_transformer(view); auto geom = view->get_geometry(); double view_scale = calculate_scale({geom.width, geom.height}); for (auto& child : view->enumerate_views(false)) { // Ensure a transformer for the view, and make sure that // new views in the view tree start off with the correct // attributes set. auto new_child = add_transformer(child); auto& child_data = scale_data[child]; if (new_child) { child_data.transformer->translation_x = main_view_dx; child_data.transformer->translation_y = main_view_dy; child_data.transformer->scale_x = main_view_scale; child_data.transformer->scale_y = main_view_scale; } if (child_data.visibility == view_scale_data::view_visibility_t::HIDDEN) { wf::scene::set_node_enabled( child->get_transformed_node(), true); } child_data.visibility = view_scale_data::view_visibility_t::VISIBLE; child_data.row = i; child_data.col = j; if (!active) { // On exit, we just animate towards normal state setup_view_transform(child_data, 1, 1, 0, 0, 1); continue; } auto vg = child->get_geometry(); wf::pointf_t center = {vg.x + vg.width / 2.0, vg.y + vg.height / 2.0}; // Take padding into account double scale = calculate_scale({vg.width, vg.height}); // Ensure child is not scaled more than parent if (!allow_scale_zoom && (child != view) && (max_scale_child > 0.0)) { scale = std::min(max_scale_child * view_scale, scale); } // Target geometry is centered around the center slot const double dx = x - center.x + scaled_width / 2.0; const double dy = y - center.y + scaled_height / 2.0; setup_view_transform(child_data, scale, scale, dx, dy, target_alpha); } } } set_hook(); transform_views(); } /* Called when adding or removing a group of views to be scaled, * in this case between views on all workspaces and views on the * current workspace */ void switch_scale_modes() { if (!output->is_plugin_active(grab_interface.name)) { return; } if (all_workspaces) { layout_slots(get_views()); return; } bool rearrange = false; for (auto& e : scale_data) { if (!should_scale_view(e.first)) { setup_view_transform(e.second, 1, 1, 0, 0, 1); rearrange = true; } } if (rearrange) { layout_slots(get_views()); } } /* Toggle between restricting maximum scale to 100% or allowing it * to become the greater. This is particularly noticeable when * scaling a single view or a view with child views. */ wf::config::option_base_t::updated_callback_t allow_scale_zoom_option_changed = [=] () { if (!output->is_plugin_active(grab_interface.name)) { return; } layout_slots(get_views()); }; void handle_new_view(wayfire_toplevel_view view) { if (!should_scale_view(view)) { return; } layout_slots(get_views()); } wf::signal::connection_t on_view_mapped = [=] (wf::view_mapped_signal *ev) { if (auto toplevel = wf::toplevel_cast(ev->view)) { handle_new_view(toplevel); } }; void handle_view_disappeared(wayfire_toplevel_view view) { if (scale_data.count(get_top_parent(view)) != 0) { /* Note: this signal can be emitted both when a view is removed from an output * (closed or moved to another output) and when it is minimized. We use * should_scale_view() to distinguish between the two cases. This only matters * if we show minimized views. */ if (include_minimized && view->minimized && should_scale_view(view)) { if (!scale_data.at(view).was_minimized) { scale_data.at(view).was_minimized = true; wf::scene::set_node_enabled(view->get_root_node(), true); } fade_out(view); } else { remove_view(view); if (scale_data.empty()) { finalize(); } if (!view->parent) { layout_slots(get_views()); } } } } /* Destroyed view or view moved to another output */ wf::signal::connection_t view_disappeared = [=] (wf::view_disappeared_signal *ev) { if (auto toplevel = toplevel_cast(ev->view)) { handle_view_disappeared(toplevel); } }; /* Workspace changed */ wf::signal::connection_t workspace_changed = [=] (wf::workspace_changed_signal *ev) { if (current_focus_view) { wf::get_core().default_wm->focus_raise_view(current_focus_view); } layout_slots(get_views()); }; wf::signal::connection_t workarea_changed = [=] (wf::workarea_changed_signal *ev) { layout_slots(get_views()); }; /* View geometry changed. Also called when workspace changes */ wf::signal::connection_t view_geometry_changed = [=] (wf::view_geometry_changed_signal *ev) { auto views = get_views(); if (!views.size()) { deactivate(); return; } layout_slots(std::move(views)); }; /* View minimized */ wf::signal::connection_t view_minimized = [=] (wf::view_minimized_signal *ev) { // Handle view restoration, view minimization is handled by disappeared already. if (!ev->view->minimized) { layout_slots(get_views()); } }; /* View unmapped */ wf::signal::connection_t view_unmapped = [=] (wf::view_unmapped_signal *ev) { if (auto toplevel = wf::toplevel_cast(ev->view)) { check_focus_view(toplevel); } }; /* Our own refocus that uses untransformed coordinates */ void refocus() { if (current_focus_view) { wf::get_core().default_wm->focus_raise_view(current_focus_view); select_view(current_focus_view); return; } wayfire_toplevel_view next_focus = nullptr; auto views = get_current_workspace_views(); for (auto v : views) { if (v->is_mapped() && v->get_keyboard_focus_surface()) { next_focus = v; break; } } wf::get_core().default_wm->focus_raise_view(current_focus_view); } /* Returns true if any scale animation is running */ bool animation_running() { for (auto& e : scale_data) { if (e.second.fade_animation.running() || e.second.animation.scale_animation.running()) { return true; } } return false; } /* Assign transform values to the actual transformer */ wf::effect_hook_t pre_hook = [=] () { transform_views(); }; /* Keep rendering until all animation has finished */ wf::effect_hook_t post_hook = [=] () { bool running = animation_running(); if (running) { output->render->schedule_redraw(); } if (active || running) { return; } finalize(); }; bool can_handle_drag() { return output->is_plugin_active(this->grab_interface.name); } wf::signal::connection_t on_drag_output_focus = [=] (wf::move_drag::drag_focus_output_signal *ev) { if ((ev->focus_output == output) && can_handle_drag()) { grab->set_wants_raw_input(true); drag_helper->set_scale(1.0); } }; wf::signal::connection_t on_drag_done = [=] (wf::move_drag::drag_done_signal *ev) { if ((ev->focused_output == output) && can_handle_drag() && !drag_helper->is_view_held_in_place()) { if (ev->main_view->get_output() == ev->focused_output) { // View left on the same output, don't do anything for (auto& v : ev->all_views) { set_tiled_wobbly(v.view, true); } layout_slots(get_views()); return; } wf::move_drag::adjust_view_on_output(ev); } grab->set_wants_raw_input(false); }; wf::signal::connection_t on_drag_snap_off = [=] (auto) { last_selected_view = nullptr; }; /* Activate and start scale animation */ bool activate() { if (active) { return false; } if (!output->activate_plugin(&grab_interface)) { return false; } auto views = get_views(); if (views.empty()) { output->deactivate_plugin(&grab_interface); return false; } initial_workspace = output->wset()->get_current_workspace(); initial_focus_view = toplevel_cast(wf::get_active_view_for_output(output)); current_focus_view = initial_focus_view ?: views.front(); // Make sure no leftover events from the activation binding // trigger an action in scale last_selected_view = nullptr; grab->grab_input(wf::scene::layer::WORKSPACE); if (current_focus_view != wf::get_core().seat->get_active_view()) { wf::get_core().default_wm->focus_raise_view(current_focus_view); } active = true; layout_slots(get_views()); output->connect(&on_view_mapped); output->connect(&workspace_changed); output->connect(&workarea_changed); output->connect(&view_disappeared); output->connect(&view_minimized); output->connect(&view_unmapped); fade_out_all_except(current_focus_view); fade_in(current_focus_view); return true; } /* Deactivate and start unscale animation */ void deactivate() { active = false; set_hook(); on_view_mapped.disconnect(); view_unmapped.disconnect(); view_minimized.disconnect(); workspace_changed.disconnect(); workarea_changed.disconnect(); view_geometry_changed.disconnect(); grab->ungrab_input(); output->deactivate_plugin(&grab_interface); for (auto& e : scale_data) { if (e.first->minimized && (e.first != current_focus_view)) { e.second.visibility = view_scale_data::view_visibility_t::HIDING; setup_view_transform(e.second, 1, 1, 0, 0, 0); } else { fade_in(e.first); setup_view_transform(e.second, 1, 1, 0, 0, 1); if (e.second.visibility == view_scale_data::view_visibility_t::HIDDEN) { wf::scene::set_node_enabled(e.first->get_transformed_node(), true); } e.second.visibility = view_scale_data::view_visibility_t::VISIBLE; } } refocus(); scale_end_signal signal; output->emit(&signal); } /* Completely end scale, including animation */ void finalize() { if (active) { /* only emit the signal if deactivate() was not called before */ scale_end_signal signal; output->emit(&signal); if (drag_helper->view) { drag_helper->handle_input_released(); } } active = false; unset_hook(); remove_transformers(); scale_data.clear(); grab->ungrab_input(); on_view_mapped.disconnect(); view_unmapped.disconnect(); view_disappeared.disconnect(); view_minimized.disconnect(); workspace_changed.disconnect(); workarea_changed.disconnect(); view_geometry_changed.disconnect(); output->deactivate_plugin(&grab_interface); wf::scene::update(wf::get_core().scene(), wf::scene::update_flag::INPUT_STATE); } /* Utility hook setter */ void set_hook() { if (hook_set) { return; } output->render->add_effect(&post_hook, wf::OUTPUT_EFFECT_POST); output->render->add_effect(&pre_hook, wf::OUTPUT_EFFECT_PRE); output->render->schedule_redraw(); hook_set = true; } /* Utility hook unsetter */ void unset_hook() { if (!hook_set) { return; } output->render->rem_effect(&post_hook); output->render->rem_effect(&pre_hook); hook_set = false; } void fini() override { finalize(); show_title.fini(); } }; class wayfire_scale_global : public wf::plugin_interface_t, public wf::per_output_tracker_mixin_t { wf::ipc_activator_t toggle_ws{"scale/toggle"}; wf::ipc_activator_t toggle_all{"scale/toggle_all"}; public: void init() override { this->init_output_tracking(); toggle_ws.set_handler(toggle_cb); toggle_all.set_handler(toggle_all_cb); } void fini() override { this->fini_output_tracking(); } void handle_new_output(wf::output_t *output) override { per_output_tracker_mixin_t::handle_new_output(output); output->connect(&on_view_set_output); } void handle_output_removed(wf::output_t *output) override { per_output_tracker_mixin_t::handle_output_removed(output); output->disconnect(&on_view_set_output); } wf::signal::connection_t on_view_set_output = [=] (wf::view_set_output_signal *ev) { if (auto toplevel = wf::toplevel_cast(ev->view)) { auto old_output = ev->output; if (old_output && output_instance.count(old_output)) { this->output_instance[old_output]->handle_view_disappeared(toplevel); } auto new_output = ev->view->get_output(); if (new_output && output_instance.count(new_output) && output_instance[new_output]->active) { this->output_instance[ev->view->get_output()]->handle_new_view(toplevel); } } }; wf::ipc_activator_t::handler_t toggle_cb = [=] (wf::output_t *output, wayfire_view) { if (this->output_instance[output]->handle_toggle(false)) { output->render->schedule_redraw(); return true; } return false; }; wf::ipc_activator_t::handler_t toggle_all_cb = [=] (wf::output_t *output, wayfire_view) { if (this->output_instance[output]->handle_toggle(true)) { output->render->schedule_redraw(); return true; } return false; }; }; DECLARE_WAYFIRE_PLUGIN(wayfire_scale_global); wayfire-0.8.1/plugins/scale/scale.hpp000066400000000000000000000007551457431457600175650ustar00rootroot00000000000000#pragma once #include "wayfire/debug.hpp" #include "wayfire/toplevel-view.hpp" #include "wayfire/plugins/common/util.hpp" #include #include #include #include inline wayfire_toplevel_view scale_find_view_at(wf::pointf_t at, wf::output_t *output) { auto offset = wf::origin(output->get_layout_geometry()); at.x -= offset.x; at.y -= offset.y; return wf::find_output_view_at(output, at); } wayfire-0.8.1/plugins/scale/wayfire/000077500000000000000000000000001457431457600174245ustar00rootroot00000000000000wayfire-0.8.1/plugins/scale/wayfire/plugins/000077500000000000000000000000001457431457600211055ustar00rootroot00000000000000wayfire-0.8.1/plugins/scale/wayfire/plugins/scale-signal.hpp000066400000000000000000000057471457431457600241750ustar00rootroot00000000000000/* definition of filters for scale plugin and activator */ #ifndef SCALE_SIGNALS_H #define SCALE_SIGNALS_H #include #include #include #include /** * name: scale-filter * on: output * when: This signal is sent from the scale plugin whenever it is updating the * list of views to display, with the list of views to be displayed in * views_shown. Plugins can move views to views_hidden to request them not to * be displayed by scale. * * Note: it is an error to remove a view from views_shown without adding it to * views_hidden; this will result in views rendered in wrong locations. * * If multiple plugins are connected to this signal, they are called in the * order defined by the logic in signal_provider_t; plugins should not depend * on being called in a predictable order. Specifically, plugins should not * expect views_hidden to be empty (and should not call clear() on it). It is OK * for a plugin to move a view from views_hidden to views_shown, but this will * likely not have predictable results. */ struct scale_filter_signal { std::vector& views_shown; std::vector& views_hidden; scale_filter_signal(std::vector& shown, std::vector& hidden) : views_shown(shown), views_hidden(hidden) {} }; /* Convenience function for processing a list of views if the plugin wants to * filter based on a simple predicate. The predicate should return true for * views to be hidden. */ template void scale_filter_views(scale_filter_signal *signal, pred&& p) { auto it = std::remove_if(signal->views_shown.begin(), signal->views_shown.end(), [signal, &p] (wayfire_toplevel_view v) { bool r = p(v); if (r) { signal->views_hidden.push_back(v); } return r; }); signal->views_shown.erase(it, signal->views_shown.end()); } /** * name: scale-end * on: output * when: When scale ended / is deactivated. A plugin performing filtering can * connect to this signal to reset itself if filtering is not supposed to * happen at the next activation of scale. * argument: unused */ struct scale_end_signal {}; /** * name: scale-update * on: output * when: A plugin can emit this signal to request scale to be updated. This is * intended for plugins that filter the scaled views to request an update when * the filter is changed. It is a no-op if scale is not currently running. * argument: unused */ struct scale_update_signal {}; /** * name: scale-transformer-added * on: output * when: This signal is emitted when scale adds a transformer to a view, so * plugins extending its functionality can add their overlays to it. * argument: pointer to the newly added transformer */ struct scale_transformer_added_signal { wayfire_toplevel_view view; }; struct scale_transformer_removed_signal { wayfire_toplevel_view view; }; #endif wayfire-0.8.1/plugins/single_plugins/000077500000000000000000000000001457431457600177115ustar00rootroot00000000000000wayfire-0.8.1/plugins/single_plugins/alpha.cpp000066400000000000000000000127631457431457600215130ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2018 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 "wayfire/plugins/common/shared-core-data.hpp" #include "wayfire/view-helpers.hpp" #include "wayfire/view-transform.hpp" #include "wayfire/workspace-set.hpp" #include "plugins/ipc/ipc-helpers.hpp" #include "plugins/ipc/ipc-method-repository.hpp" class wayfire_alpha : public wf::plugin_interface_t { wf::option_wrapper_t modifier{"alpha/modifier"}; wf::option_wrapper_t min_value{"alpha/min_value"}; wf::plugin_activation_data_t grab_interface{ .name = "alpha", .capabilities = wf::CAPABILITY_MANAGE_DESKTOP, }; wf::shared_data::ref_ptr_t ipc_repo; public: void init() override { min_value.set_callback(min_value_changed); wf::get_core().bindings->add_axis(modifier, &axis_cb); ipc_repo->register_method("wf/alpha/set-view-alpha", ipc_set_view_alpha); } wf::ipc::method_callback ipc_set_view_alpha = [=] (nlohmann::json data) -> nlohmann::json { WFJSON_EXPECT_FIELD(data, "view-id", number_unsigned); WFJSON_EXPECT_FIELD(data, "alpha", number); auto view = wf::ipc::find_view_by_id(data["view-id"]); if (view && view->is_mapped()) { auto tr = ensure_transformer(view); adjust_alpha(view, tr, data["alpha"]); } else { return wf::ipc::json_error("Failed to find view with given id. Maybe it was closed?"); } return wf::ipc::json_ok(); }; std::shared_ptr ensure_transformer(wayfire_view view) { auto tmgr = view->get_transformed_node(); if (!tmgr->get_transformer("alpha")) { auto node = std::make_shared(view); tmgr->add_transformer(node, wf::TRANSFORMER_2D, "alpha"); } return tmgr->get_transformer("alpha"); } void adjust_alpha(wayfire_view view, std::shared_ptr tr, float alpha) { tr->alpha = alpha; if (alpha == 1.0) { return view->get_transformed_node()->rem_transformer("alpha"); } else { view->damage(); } } void update_alpha(wayfire_view view, float delta) { auto transformer = ensure_transformer(view); float desired_value = std::clamp(transformer->alpha - delta * 0.003, (double)min_value, 1.0); adjust_alpha(view, transformer, desired_value); } wf::axis_callback axis_cb = [=] (wlr_pointer_axis_event *ev) { auto gc = wf::get_core().get_cursor_position(); auto current_output = wf::get_core().output_layout->get_output_coords_at(gc, gc); if (!current_output || !current_output->can_activate_plugin(&grab_interface)) { return false; } auto view = wf::get_core().get_cursor_focus_view(); if (!view) { return false; } auto layer = wf::get_view_layer(view).value_or(wf::scene::layer::BACKGROUND); if (layer == wf::scene::layer::BACKGROUND) { return false; } if (ev->orientation == WLR_AXIS_ORIENTATION_VERTICAL) { update_alpha(view, ev->delta); return true; } return false; }; wf::config::option_base_t::updated_callback_t min_value_changed = [=] () { for (auto& view : wf::get_core().get_all_views()) { auto tmgr = view->get_transformed_node(); auto transformer = tmgr->get_transformer("alpha"); if (transformer && (transformer->alpha < min_value)) { transformer->alpha = min_value; view->damage(); } } }; void fini() override { for (auto& view : wf::get_core().get_all_views()) { view->get_transformed_node()->rem_transformer("alpha"); } wf::get_core().bindings->rem_binding(&axis_cb); ipc_repo->unregister_method("wf/alpha/set-view-alpha"); } }; DECLARE_WAYFIRE_PLUGIN(wayfire_alpha); wayfire-0.8.1/plugins/single_plugins/autostart.cpp000066400000000000000000000031631457431457600224460ustar00rootroot00000000000000#include #include #include #include #include class wayfire_autostart : public wf::plugin_interface_t { wf::option_wrapper_t autostart_wf_shell{"autostart/autostart_wf_shell"}; wf::option_wrapper_t> autostart_entries{"autostart/autostart"}; public: void init() override { /* Run only once, at startup */ auto section = wf::get_core().config.get_section("autostart"); bool panel_manually_started = false; bool background_manually_started = false; for (const auto& [name, command] : autostart_entries.value()) { // Because we accept any option names, we should ignore regular // options if (name == "autostart_wf_shell") { continue; } wf::get_core().run(command); if (command.find("wf-panel") != std::string::npos) { panel_manually_started = true; } if (command.find("wf-background") != std::string::npos) { background_manually_started = true; } } if (autostart_wf_shell && !panel_manually_started) { wf::get_core().run("wf-panel"); } if (autostart_wf_shell && !background_manually_started) { wf::get_core().run("wf-background"); } } bool is_unloadable() override { return false; } }; DECLARE_WAYFIRE_PLUGIN(wayfire_autostart); wayfire-0.8.1/plugins/single_plugins/command.cpp000066400000000000000000000217051457431457600220400ustar00rootroot00000000000000#include "wayfire/signal-provider.hpp" #include #include #include #include #include #include #include /* Initial repeat delay passed */ static int repeat_delay_timeout_handler(void *callback) { (*reinterpret_cast*>(callback))(); return 1; // disconnect } /* Between each repeat */ static int repeat_once_handler(void *callback) { (*reinterpret_cast*>(callback))(); return 1; // continue timer } /* Provides a way to bind specific commands to activator bindings. * * It supports 4 modes: * * 1. Regular bindings * 2. Repeatable bindings - for example, if the user binds a keybinding, then * after a specific delay the command begins to be executed repeatedly, until * the user released the key. In the config file, repeatable bindings have the * prefix repeatable_ * 3. Always bindings - bindings that can be executed even if a plugin is already * active, or if the screen is locked. They have a prefix always_ * 4. Release bindings - Execute a command when a binding is released. Useful for * Push-To-Talk. They have a prefix release_ * */ class wayfire_command : public wf::per_output_plugin_instance_t { std::vector bindings; struct { uint32_t pressed_button = 0; uint32_t pressed_key = 0; std::string repeat_command; } repeat; wl_event_source *repeat_source = NULL, *repeat_delay_source = NULL; enum binding_mode { BINDING_NORMAL, BINDING_REPEAT, BINDING_ALWAYS, BINDING_RELEASE, }; bool on_binding(std::string command, binding_mode mode, const wf::activator_data_t& data) { /* We already have a repeatable command, do not accept further bindings */ if (repeat.pressed_key || repeat.pressed_button) { return false; } uint32_t act_flags = 0; if (mode == BINDING_ALWAYS) { act_flags |= wf::PLUGIN_ACTIVATION_IGNORE_INHIBIT; } if (!output->activate_plugin(&grab_interface, act_flags)) { return false; } if (mode == BINDING_RELEASE) { repeat.repeat_command = command; if (data.source == wf::activator_source_t::KEYBINDING) { repeat.pressed_key = data.activation_data; wf::get_core().connect(&on_key_event_release); } else { repeat.pressed_button = data.activation_data; wf::get_core().connect(&on_button_event_release); } return true; } else { wf::get_core().run(command.c_str()); } /* No repeat necessary in any of those cases */ if ((mode != BINDING_REPEAT) || (data.source == wf::activator_source_t::GESTURE) || (data.activation_data == 0)) { output->deactivate_plugin(&grab_interface); return true; } repeat.repeat_command = command; if (data.source == wf::activator_source_t::KEYBINDING) { repeat.pressed_key = data.activation_data; } else { repeat.pressed_button = data.activation_data; } repeat_delay_source = wl_event_loop_add_timer(wf::get_core().ev_loop, repeat_delay_timeout_handler, &on_repeat_delay_timeout); wl_event_source_timer_update(repeat_delay_source, wf::option_wrapper_t("input/kb_repeat_delay")); wf::get_core().connect(&on_button_event); wf::get_core().connect(&on_key_event); return true; } std::function on_repeat_delay_timeout = [=] () { repeat_delay_source = NULL; repeat_source = wl_event_loop_add_timer(wf::get_core().ev_loop, repeat_once_handler, &on_repeat_once); on_repeat_once(); }; std::function on_repeat_once = [=] () { uint32_t repeat_rate = wf::option_wrapper_t("input/kb_repeat_rate"); if ((repeat_rate <= 0) || (repeat_rate > 1000)) { return reset_repeat(); } wl_event_source_timer_update(repeat_source, 1000 / repeat_rate); wf::get_core().run(repeat.repeat_command.c_str()); }; void reset_repeat() { if (repeat_delay_source) { wl_event_source_remove(repeat_delay_source); repeat_delay_source = NULL; } if (repeat_source) { wl_event_source_remove(repeat_source); repeat_source = NULL; } repeat.pressed_key = repeat.pressed_button = 0; output->deactivate_plugin(&grab_interface); on_button_event.disconnect(); on_key_event.disconnect(); } wf::signal::connection_t> on_button_event = [=] (wf::input_event_signal *ev) { if ((ev->event->button == repeat.pressed_button) && (ev->event->state == WLR_BUTTON_RELEASED)) { reset_repeat(); } }; wf::signal::connection_t> on_key_event = [=] (wf::input_event_signal *ev) { if ((ev->event->keycode == repeat.pressed_key) && (ev->event->state == WLR_KEY_RELEASED)) { reset_repeat(); } }; wf::signal::connection_t> on_key_event_release = [=] (wf::input_event_signal *ev) { if ((ev->event->keycode == repeat.pressed_key) && (ev->event->state == WLR_KEY_RELEASED)) { wf::get_core().run(repeat.repeat_command.c_str()); repeat.pressed_key = repeat.pressed_button = 0; output->deactivate_plugin(&grab_interface); on_key_event_release.disconnect(); } }; wf::signal::connection_t> on_button_event_release = [=] (wf::input_event_signal *ev) { if ((ev->event->button == repeat.pressed_button) && (ev->event->state == WLR_BUTTON_RELEASED)) { wf::get_core().run(repeat.repeat_command.c_str()); repeat.pressed_key = repeat.pressed_button = 0; output->deactivate_plugin(&grab_interface); on_button_event_release.disconnect(); } }; public: wf::option_wrapper_t> regular_bindings{"command/bindings"}; wf::option_wrapper_t> repeat_bindings{ "command/repeatable_bindings" }; wf::option_wrapper_t> always_bindings{ "command/always_bindings" }; wf::option_wrapper_t> release_bindings{ "command/release_bindings" }; std::function setup_bindings_from_config = [=] () { clear_bindings(); using namespace std::placeholders; auto regular = regular_bindings.value(); auto repeatable = repeat_bindings.value(); auto always = always_bindings.value(); auto release = release_bindings.value(); bindings.resize( regular.size() + repeatable.size() + always.size() + release.size()); size_t i = 0; const auto& push_bindings = [&] (wf::config::compound_list_t& list, binding_mode mode) { for (const auto& [_, cmd, activator] : list) { bindings[i] = std::bind(std::mem_fn(&wayfire_command::on_binding), this, cmd, mode, _1); output->add_activator(wf::create_option(activator), &bindings[i]); ++i; } }; push_bindings(regular, BINDING_NORMAL); push_bindings(repeatable, BINDING_REPEAT); push_bindings(always, BINDING_ALWAYS); push_bindings(release, BINDING_RELEASE); }; void clear_bindings() { for (auto& binding : bindings) { output->rem_binding(&binding); } bindings.clear(); } wf::signal::connection_t on_reload_config = [=] (auto) { setup_bindings_from_config(); }; wf::plugin_activation_data_t grab_interface = { .name = "command", .capabilities = wf::CAPABILITY_GRAB_INPUT, }; void init() { using namespace std::placeholders; setup_bindings_from_config(); wf::get_core().connect(&on_reload_config); } void fini() { clear_bindings(); } }; DECLARE_WAYFIRE_PLUGIN(wf::per_output_plugin_t); wayfire-0.8.1/plugins/single_plugins/compositor-view-test.cpp000066400000000000000000000037001457431457600245400ustar00rootroot00000000000000#include "wayfire/compositor-view.hpp" #include "wayfire/render-manager.hpp" #include "wayfire/output.hpp" #include "wayfire/core.hpp" #include "wayfire/debug.hpp" extern "C" { #define static #include #include #include #undef static } class test_view : public wayfire_compositor_view_t, public wayfire_compositor_interactive_view { public: virtual void _wlr_render_box(const wf::framebuffer_t& fb, int x, int y, const wlr_box& scissor) { wlr_box g{x, y, geometry.width, geometry.height}; geometry = fb.damage_box_from_geometry_box(g); float projection[9]; wlr_matrix_projection(projection, fb.viewport_width, fb.viewport_height, (wl_output_transform)fb.wl_transform); float matrix[9]; wlr_matrix_project_box(matrix, &g, WL_OUTPUT_TRANSFORM_NORMAL, 0, projection); OpenGL::render_begin(fb); auto sbox = scissor; wlr_renderer_scissor(wf::get_core().renderer, &sbox); float color[] = {1.0f, 0.0, 1.0f, 1.0f}; wlr_render_quad_with_matrix(wf::get_core().renderer, color, matrix); OpenGL::render_end(); } virtual bool accepts_input(int sx, int sy) { return 0 <= sx && sx < geometry.width && 0 <= sy && sy < geometry.height; } }; class wayfire_cvtest : public wayfire_plugin_t { wf::key_callback binding; public: void init(wayfire_config *config) { binding = [=] (uint32_t) {test();}; output->add_key(new_static_option(" KEY_T"), &binding); } void test() { auto cv = new wayfire_mirror_view_t(output->get_top_view()); auto v = std::unique_ptr{cv}; wf::get_core().add_view(std::move(v)); cv->map(); } }; extern "C" { wayfire_plugin_t *newInstance() { return new wayfire_cvtest; } } wayfire-0.8.1/plugins/single_plugins/expo.cpp000066400000000000000000000550411457431457600213750ustar00rootroot00000000000000#include "wayfire/plugins/common/input-grab.hpp" #include "wayfire/plugins/common/util.hpp" #include "plugins/ipc/ipc-activator.hpp" #include "wayfire/render-manager.hpp" #include "wayfire/scene-input.hpp" #include "wayfire/scene.hpp" #include "wayfire/signal-definitions.hpp" #include "wayfire/view.hpp" #include #include #include #include #include #include #include #include #include #include #include /* TODO: this file should be included in some header maybe(plugin.hpp) */ #include class wayfire_expo : public wf::per_output_plugin_instance_t, public wf::keyboard_interaction_t, public wf::pointer_interaction_t, public wf::touch_interaction_t { private: wf::point_t convert_workspace_index_to_coords(int index) { index--; // compensate for indexing from 0 auto wsize = output->wset()->get_workspace_grid_size(); int x = index % wsize.width; int y = index / wsize.width; return wf::point_t{x, y}; } wf::option_wrapper_t background_color{"expo/background"}; wf::option_wrapper_t zoom_duration{"expo/duration"}; wf::option_wrapper_t delimiter_offset{"expo/offset"}; wf::option_wrapper_t keyboard_interaction{"expo/keyboard_interaction"}; wf::option_wrapper_t inactive_brightness{"expo/inactive_brightness"}; wf::option_wrapper_t transition_length{"expo/transition_length"}; wf::geometry_animation_t zoom_animation{zoom_duration}; wf::option_wrapper_t move_enable_snap_off{"move/enable_snap_off"}; wf::option_wrapper_t move_snap_off_threshold{"move/snap_off_threshold"}; wf::option_wrapper_t move_join_views{"move/join_views"}; wf::shared_data::ref_ptr_t drag_helper; wf::option_wrapper_t> workspace_bindings{"expo/workspace_bindings"}; std::vector keyboard_select_cbs; std::vector> keyboard_select_options; struct { bool active = false; bool button_pressed = false; bool zoom_in = false; bool accepting_input = false; } state; wf::point_t target_ws, initial_ws; std::unique_ptr wall; wf::key_repeat_t key_repeat; uint32_t key_pressed = 0; /* fade animations for each workspace */ std::vector> ws_fade; std::unique_ptr input_grab; public: void setup_workspace_bindings_from_config() { for (const auto& [workspace, binding] : workspace_bindings.value()) { int workspace_index = atoi(workspace.c_str()); auto wsize = output->wset()->get_workspace_grid_size(); if ((workspace_index > (wsize.width * wsize.height)) || (workspace_index < 1)) { continue; } wf::point_t target = convert_workspace_index_to_coords(workspace_index); keyboard_select_options.push_back(wf::create_option(binding)); keyboard_select_cbs.push_back([=] (auto) { if (!state.active) { return false; } else { if (!zoom_animation.running() || state.zoom_in) { if (target_ws != target) { shade_workspace(target_ws, true); target_ws = target; shade_workspace(target_ws, false); } deactivate(); } } return true; }); } } wf::plugin_activation_data_t grab_interface = { .name = "expo", .capabilities = wf::CAPABILITY_MANAGE_COMPOSITOR, .cancel = [=] () { finalize_and_exit(); }, }; void init() override { input_grab = std::make_unique("expo", output, this, this, this); setup_workspace_bindings_from_config(); wall = std::make_unique(this->output); drag_helper->connect(&on_drag_output_focus); drag_helper->connect(&on_drag_snap_off); drag_helper->connect(&on_drag_done); resize_ws_fade(); output->connect(&on_workspace_grid_changed); } bool handle_toggle() { if (!state.active) { return activate(); } else if (!zoom_animation.running() || state.zoom_in) { deactivate(); } return true; } void handle_pointer_button(const wlr_pointer_button_event& event) override { if (event.button != BTN_LEFT) { return; } auto gc = output->get_cursor_position(); handle_input_press(gc.x, gc.y, event.state); } void handle_pointer_motion(wf::pointf_t pointer_position, uint32_t time_ms) override { handle_input_move({(int)pointer_position.x, (int)pointer_position.y}); } void handle_keyboard_key(wf::seat_t*, wlr_keyboard_key_event event) override { if (event.state == WLR_KEY_PRESSED) { if (should_handle_key()) { handle_key_pressed(event.keycode); } } else { if (event.keycode == key_pressed) { key_repeat.disconnect(); key_pressed = 0; } } } void handle_touch_down(uint32_t time_ms, int finger_id, wf::pointf_t position) override { if (finger_id > 0) { return; } auto og = output->get_layout_geometry(); handle_input_press(position.x - og.x, position.y - og.y, WLR_BUTTON_PRESSED); } void handle_touch_up(uint32_t time_ms, int finger_id, wf::pointf_t lift_off_position) override { if (finger_id > 0) { return; } handle_input_press(0, 0, WLR_BUTTON_RELEASED); } void handle_touch_motion(uint32_t time_ms, int finger_id, wf::pointf_t position) override { if (finger_id > 0) // we handle just the first finger { return; } handle_input_move({(int)position.x, (int)position.y}); } bool can_handle_drag() { return output->is_plugin_active(grab_interface.name); } wf::signal::connection_t on_drag_output_focus = [=] (wf::move_drag::drag_focus_output_signal *ev) { if ((ev->focus_output == output) && can_handle_drag()) { state.button_pressed = true; auto [vw, vh] = output->wset()->get_workspace_grid_size(); drag_helper->set_scale(std::max(vw, vh)); input_grab->set_wants_raw_input(true); } }; wf::signal::connection_t on_drag_snap_off = [=] (wf::move_drag::snap_off_signal *ev) { if ((ev->focus_output == output) && can_handle_drag()) { wf::move_drag::adjust_view_on_snap_off(drag_helper->view); } }; wf::signal::connection_t on_drag_done = [=] (wf::move_drag::drag_done_signal *ev) { if ((ev->focused_output == output) && can_handle_drag() && !drag_helper->is_view_held_in_place()) { bool same_output = ev->main_view->get_output() == output; auto offset = wf::origin(output->get_layout_geometry()); auto local = input_coordinates_to_output_local_coordinates( ev->grab_position + -offset); for (auto& v : wf::move_drag::get_target_views(ev->main_view, ev->join_views)) { translate_wobbly(v, local - (ev->grab_position - offset)); } ev->grab_position = local + offset; wf::move_drag::adjust_view_on_output(ev); if (same_output && (move_started_ws != offscreen_point)) { wf::view_change_workspace_signal data; data.view = ev->main_view; data.from = move_started_ws; data.to = target_ws; output->emit(&data); } move_started_ws = offscreen_point; } input_grab->set_wants_raw_input(false); this->state.button_pressed = false; }; bool activate() { if (!output->activate_plugin(&grab_interface)) { return false; } input_grab->grab_input(wf::scene::layer::OVERLAY); state.active = true; state.button_pressed = false; state.accepting_input = true; start_zoom(true); wall->start_output_renderer(); output->render->add_effect(&pre_frame, wf::OUTPUT_EFFECT_PRE); output->render->schedule_redraw(); auto cws = output->wset()->get_current_workspace(); initial_ws = target_ws = cws; for (size_t i = 0; i < keyboard_select_cbs.size(); i++) { output->add_activator(keyboard_select_options[i], &keyboard_select_cbs[i]); } highlight_active_workspace(); return true; } void start_zoom(bool zoom_in) { wall->set_background_color(background_color); wall->set_gap_size(this->delimiter_offset); if (zoom_in) { zoom_animation.set_start(wall->get_workspace_rectangle( output->wset()->get_current_workspace())); /* Make sure workspaces are centered */ auto wsize = output->wset()->get_workspace_grid_size(); auto size = output->get_screen_size(); const int maxdim = std::max(wsize.width, wsize.height); const int gap = this->delimiter_offset; const int fullw = (gap + size.width) * maxdim + gap; const int fullh = (gap + size.height) * maxdim + gap; auto rectangle = wall->get_wall_rectangle(); rectangle.x -= (fullw - rectangle.width) / 2; rectangle.y -= (fullh - rectangle.height) / 2; rectangle.width = fullw; rectangle.height = fullh; zoom_animation.set_end(rectangle); } else { zoom_animation.set_start(zoom_animation); zoom_animation.set_end( wall->get_workspace_rectangle(target_ws)); } state.zoom_in = zoom_in; zoom_animation.start(); wall->set_viewport(zoom_animation); } void deactivate() { state.accepting_input = false; start_zoom(false); output->wset()->set_workspace(target_ws); for (size_t i = 0; i < keyboard_select_cbs.size(); i++) { output->rem_binding(&keyboard_select_cbs[i]); } } wf::geometry_t get_grid_geometry() { auto wsize = output->wset()->get_workspace_grid_size(); auto full_g = output->get_layout_geometry(); wf::geometry_t grid; grid.x = grid.y = 0; grid.width = full_g.width * wsize.width; grid.height = full_g.height * wsize.height; return grid; } /** * Handle an input press event. * * @param x, y The position of the event in output-local coordinates. */ void handle_input_press(int32_t x, int32_t y, uint32_t state) { if (zoom_animation.running() || !this->state.active) { return; } if ((state == WLR_BUTTON_RELEASED) && !this->drag_helper->view) { this->state.button_pressed = false; deactivate(); } else if (state == WLR_BUTTON_RELEASED) { this->state.button_pressed = false; this->drag_helper->handle_input_released(); } else { this->state.button_pressed = true; drag_helper->set_pending_drag(wf::get_core().get_cursor_position()); update_target_workspace(x, y); } } void start_moving(wayfire_toplevel_view view, wf::point_t local_grab) { if (!(view->get_allowed_actions() & (wf::VIEW_ALLOW_WS_CHANGE | wf::VIEW_ALLOW_MOVE))) { return; } auto ws_coords = input_coordinates_to_output_local_coordinates(local_grab); auto bbox = wf::view_bounding_box_up_to(view, "wobbly"); view->damage(); // Make sure that the view is in output-local coordinates! translate_wobbly(view, local_grab - ws_coords); auto [vw, vh] = output->wset()->get_workspace_grid_size(); wf::move_drag::drag_options_t opts; opts.initial_scale = std::max(vw, vh); opts.enable_snap_off = move_enable_snap_off && (view->pending_fullscreen() || view->pending_tiled_edges()); opts.snap_off_threshold = move_snap_off_threshold; opts.join_views = move_join_views; drag_helper->start_drag(view, wf::move_drag::find_relative_grab(bbox, ws_coords), opts); move_started_ws = target_ws; input_grab->set_wants_raw_input(true); } const wf::point_t offscreen_point = {-10, -10}; void handle_input_move(wf::point_t to) { if (!state.button_pressed) { return; } auto local = to - wf::origin(output->get_layout_geometry()); if (drag_helper->view) { drag_helper->handle_motion(to); update_target_workspace(local.x, local.y); return; } if (!drag_helper->should_start_pending_drag(to) || zoom_animation.running()) { /* Ignore small movements */ return; } auto local_grab = *drag_helper->tentative_grab_position - wf::origin(output->get_layout_geometry()); if (auto view = find_view_at_coordinates(local_grab.x, local_grab.y)) { start_moving(view, local_grab); drag_helper->handle_motion(to); } /* As input coordinates are always positive, this will ensure that any * subsequent motion events while grabbed are allowed */ update_target_workspace(local.x, local.y); } /** * Helper to determine if keyboard presses should be handled */ bool should_handle_key() { return state.accepting_input && keyboard_interaction && !state.button_pressed; } void handle_key_pressed(uint32_t key) { wf::point_t old_target = target_ws; switch (key) { case KEY_ENTER: deactivate(); return; case KEY_ESC: target_ws = initial_ws; shade_workspace(old_target, true); shade_workspace(target_ws, false); deactivate(); return; case KEY_UP: case KEY_K: target_ws.y -= 1; break; case KEY_DOWN: case KEY_J: target_ws.y += 1; break; case KEY_RIGHT: case KEY_L: target_ws.x += 1; break; case KEY_LEFT: case KEY_H: target_ws.x -= 1; break; default: return; } /* this part is only reached if one of the arrow keys is pressed */ if (key != key_pressed) { // update key repeat callbacks // (note: this will disconnect any previous callback) key_repeat.set_callback(key, [this] (uint32_t key) { if (!should_handle_key()) { // disconnect if key events should no longer be handled key_pressed = 0; return false; } handle_key_pressed(key); return true; // repeat }); key_pressed = key; } // ensure that the new target is valid (use wrap-around) auto dim = output->wset()->get_workspace_grid_size(); target_ws.x = (target_ws.x + dim.width) % dim.width; target_ws.y = (target_ws.y + dim.height) % dim.height; shade_workspace(old_target, true); shade_workspace(target_ws, false); } /** * shade all but the selected workspace instantly (without animation) */ void highlight_active_workspace() { auto dim = output->wset()->get_workspace_grid_size(); for (int x = 0; x < dim.width; x++) { for (int y = 0; y < dim.height; y++) { if ((x == target_ws.x) && (y == target_ws.y)) { wall->set_ws_dim({x, y}, 1.0); } else { wall->set_ws_dim({x, y}, inactive_brightness); } } } } /** * start an animation for shading the given workspace */ void shade_workspace(const wf::point_t& ws, bool shaded) { double target = shaded ? inactive_brightness : 1.0; auto& anim = ws_fade.at(ws.x).at(ws.y); if (anim.running()) { anim.animate(target); } else { anim.animate(shaded ? 1.0 : inactive_brightness, target); } output->render->schedule_redraw(); } wf::point_t move_started_ws = offscreen_point; /** * Find the coordinate of the given point from output-local coordinates * to coordinates relative to the first workspace (i.e (0,0)) */ void input_coordinates_to_global_coordinates(int & sx, int & sy) { auto og = output->get_layout_geometry(); auto wsize = output->wset()->get_workspace_grid_size(); float max = std::max(wsize.width, wsize.height); float grid_start_x = og.width * (max - wsize.width) / float(max) / 2; float grid_start_y = og.height * (max - wsize.height) / float(max) / 2; sx -= grid_start_x; sy -= grid_start_y; sx *= max; sy *= max; } /** * Find the coordinate of the given point from output-local coordinates * to output-workspace-local coordinates */ wf::point_t input_coordinates_to_output_local_coordinates(wf::point_t ip) { input_coordinates_to_global_coordinates(ip.x, ip.y); auto cws = output->wset()->get_current_workspace(); auto og = output->get_relative_geometry(); /* Translate coordinates into output-local coordinate system, * relative to the current workspace */ return { ip.x - cws.x * og.width, ip.y - cws.y * og.height, }; } wayfire_toplevel_view find_view_at_coordinates(int gx, int gy) { auto local = input_coordinates_to_output_local_coordinates({gx, gy}); wf::pointf_t localf = {1.0 * local.x, 1.0 * local.y}; return wf::find_output_view_at(output, localf); } void update_target_workspace(int x, int y) { auto og = output->get_layout_geometry(); input_coordinates_to_global_coordinates(x, y); auto grid = get_grid_geometry(); if (!(grid & wf::point_t{x, y})) { return; } int tmpx = x / og.width; int tmpy = y / og.height; if ((tmpx != target_ws.x) || (tmpy != target_ws.y)) { shade_workspace(target_ws, true); target_ws = {tmpx, tmpy}; shade_workspace(target_ws, false); } } wf::effect_hook_t pre_frame = [=] () { if (zoom_animation.running()) { wall->set_viewport(zoom_animation); } else if (!state.zoom_in) { finalize_and_exit(); return; } auto size = this->output->wset()->get_workspace_grid_size(); for (int x = 0; x < size.width; x++) { for (int y = 0; y < size.height; y++) { auto& anim = ws_fade.at(x).at(y); if (anim.running()) { wall->set_ws_dim({x, y}, anim); } } } }; void resize_ws_fade() { auto size = this->output->wset()->get_workspace_grid_size(); ws_fade.resize(size.width); for (auto& v : ws_fade) { size_t h = size.height; if (v.size() > h) { v.resize(h); } else { while (v.size() < h) { v.emplace_back(transition_length); } } } } wf::signal::connection_t on_workspace_grid_changed = [=] (auto) { resize_ws_fade(); // check that the target and initial workspaces are still in the grid auto size = this->output->wset()->get_workspace_grid_size(); initial_ws.x = std::min(initial_ws.x, size.width - 1); initial_ws.y = std::min(initial_ws.y, size.height - 1); if ((target_ws.x >= size.width) || (target_ws.y >= size.height)) { target_ws.x = std::min(target_ws.x, size.width - 1); target_ws.y = std::min(target_ws.y, size.height - 1); highlight_active_workspace(); } }; void finalize_and_exit() { state.active = false; if (drag_helper->view) { drag_helper->handle_input_released(); } output->deactivate_plugin(&grab_interface); input_grab->ungrab_input(); wall->stop_output_renderer(true); output->render->rem_effect(&pre_frame); key_repeat.disconnect(); key_pressed = 0; } void fini() override { if (state.active) { finalize_and_exit(); } } }; class wayfire_expo_global : public wf::plugin_interface_t, public wf::per_output_tracker_mixin_t { wf::ipc_activator_t toggle_binding{"expo/toggle"}; public: void init() override { this->init_output_tracking(); toggle_binding.set_handler(toggle_cb); } void fini() override { this->fini_output_tracking(); } wf::ipc_activator_t::handler_t toggle_cb = [=] (wf::output_t *output, wayfire_view) { return this->output_instance[output]->handle_toggle(); }; }; DECLARE_WAYFIRE_PLUGIN(wayfire_expo_global); wayfire-0.8.1/plugins/single_plugins/extra-gestures.cpp000066400000000000000000000103751457431457600234050ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include #include namespace wf { using namespace touch; class extra_gestures_plugin_t : public per_output_plugin_instance_t { std::unique_ptr touch_and_hold_move; std::unique_ptr tap_to_close; wf::option_wrapper_t move_fingers{"extra-gestures/move_fingers"}; wf::option_wrapper_t move_delay{"extra-gestures/move_delay"}; wf::option_wrapper_t close_fingers{"extra-gestures/close_fingers"}; wf::plugin_activation_data_t grab_interface = { .capabilities = CAPABILITY_MANAGE_COMPOSITOR, }; public: void init() override { build_touch_and_hold_move(); move_fingers.set_callback([=] () { build_touch_and_hold_move(); }); move_delay.set_callback([=] () { build_touch_and_hold_move(); }); wf::get_core().add_touch_gesture({touch_and_hold_move}); build_tap_to_close(); close_fingers.set_callback([=] () { build_tap_to_close(); }); wf::get_core().add_touch_gesture({tap_to_close}); } /** * Run an action on the view under the touch points, if the touch points * are on the current output and the view is toplevel. */ void execute_view_action(std::function action) { auto& core = wf::get_core(); auto state = core.get_touch_state(); auto center_touch_point = state.get_center().current; wf::pointf_t center = {center_touch_point.x, center_touch_point.y}; if (core.output_layout->get_output_at(center.x, center.y) != this->output) { return; } /** Make sure we don't interfere with already activated plugins */ if (!output->can_activate_plugin(&this->grab_interface)) { return; } auto view = core.get_view_at({center.x, center.y}); if (view && (view->role == VIEW_ROLE_TOPLEVEL)) { action(view); } } void build_touch_and_hold_move() { if (touch_and_hold_move) { wf::get_core().rem_touch_gesture({touch_and_hold_move}); } auto touch_down = std::make_unique(move_fingers, true); touch_down->set_move_tolerance(50); touch_down->set_duration(100); auto hold = std::make_unique(move_delay); hold->set_move_tolerance(100); std::vector> actions; actions.emplace_back(std::move(touch_down)); actions.emplace_back(std::move(hold)); touch_and_hold_move = std::make_unique(std::move(actions), [=] () { execute_view_action([] (wayfire_view view) { if (auto toplevel = wf::toplevel_cast(view)) { wf::get_core().default_wm->move_request(toplevel); } }); }); } void build_tap_to_close() { if (tap_to_close) { wf::get_core().rem_touch_gesture({tap_to_close}); } auto touch_down = std::make_unique(close_fingers, true); touch_down->set_move_tolerance(50); touch_down->set_duration(150); auto touch_up = std::make_unique(close_fingers, false); touch_up->set_move_tolerance(50); touch_up->set_duration(150); std::vector> actions; actions.emplace_back(std::move(touch_down)); actions.emplace_back(std::move(touch_up)); tap_to_close = std::make_unique(std::move(actions), [=] () { execute_view_action([] (wayfire_view view) { view->close(); }); }); } void fini() override { wf::get_core().rem_touch_gesture({touch_and_hold_move}); wf::get_core().rem_touch_gesture({tap_to_close}); } }; } DECLARE_WAYFIRE_PLUGIN(wf::per_output_plugin_t); wayfire-0.8.1/plugins/single_plugins/fast-switcher.cpp000066400000000000000000000147761457431457600232170ustar00rootroot00000000000000#include "wayfire/core.hpp" #include "wayfire/plugins/common/input-grab.hpp" #include "wayfire/plugins/common/util.hpp" #include "wayfire/scene-input.hpp" #include "wayfire/view-helpers.hpp" #include #include #include #include #include #include #include #include #include /* * This plugin provides abilities to switch between views. * It works similarly to the alt-esc binding in Windows or GNOME */ class wayfire_fast_switcher : public wf::per_output_plugin_instance_t, public wf::keyboard_interaction_t { wf::option_wrapper_t activate_key{"fast-switcher/activate"}; wf::option_wrapper_t activate_key_backward{ "fast-switcher/activate_backward"}; wf::option_wrapper_t inactive_alpha{"fast-switcher/inactive_alpha"}; std::vector views; // all views on current viewport size_t current_view_index = 0; // the modifiers which were used to activate switcher uint32_t activating_modifiers = 0; bool active = false; std::unique_ptr input_grab; wf::plugin_activation_data_t grab_interface = { .name = "fast-switcher", .capabilities = wf::CAPABILITY_MANAGE_COMPOSITOR, }; public: void init() override { output->add_key(activate_key, &fast_switch); output->add_key(activate_key_backward, &fast_switch_backward); input_grab = std::make_unique("fast-switch", output, this, nullptr, nullptr); grab_interface.cancel = [=] () { switch_terminate(); }; } void handle_keyboard_key(wf::seat_t*, wlr_keyboard_key_event event) override { auto mod = wf::get_core().seat->modifier_from_keycode(event.keycode); if ((event.state == WLR_KEY_RELEASED) && (mod & activating_modifiers)) { switch_terminate(); } } void view_chosen(int i, bool reorder_only) { /* No view available */ if (!((0 <= i) && (i < (int)views.size()))) { return; } current_view_index = i; set_view_highlighted(views[i], true); for (int i = (int)views.size() - 1; i >= 0; i--) { wf::view_bring_to_front(views[i]); } if (reorder_only) { wf::view_bring_to_front(views[i]); } else { wf::get_core().default_wm->focus_raise_view(views[i]); } } wf::signal::connection_t cleanup_view = [=] (wf::view_disappeared_signal *ev) { size_t i = 0; for (; i < views.size() && views[i] != ev->view; i++) {} if (i == views.size()) { return; } views.erase(views.begin() + i); if (views.empty()) { switch_terminate(); return; } if (i <= current_view_index) { int new_index = (current_view_index + views.size() - 1) % views.size(); view_chosen(new_index, true); } }; const std::string transformer_name = "fast-switcher"; void set_view_alpha(wayfire_view view, float alpha) { auto tr = wf::ensure_named_transformer( view, wf::TRANSFORMER_2D, transformer_name, view); view->get_transformed_node()->begin_transform_update(); tr->alpha = alpha; view->get_transformed_node()->end_transform_update(); } void set_view_highlighted(wayfire_toplevel_view view, bool selected) { // set alpha and decoration to indicate selected view view->set_activated(selected); // changes decoration focus state double alpha = selected ? 1.0 : inactive_alpha; set_view_alpha(view, alpha); } void update_views() { views = output->wset()->get_views( wf::WSET_CURRENT_WORKSPACE | wf::WSET_MAPPED_ONLY | wf::WSET_EXCLUDE_MINIMIZED); std::sort(views.begin(), views.end(), [] (wayfire_toplevel_view& a, wayfire_toplevel_view& b) { return wf::get_focus_timestamp(a) > wf::get_focus_timestamp(b); }); } bool do_switch(bool forward) { if (active) { switch_next(forward); return true; } if (!output->activate_plugin(&grab_interface)) { return false; } update_views(); if (views.size() < 1) { output->deactivate_plugin(&grab_interface); return false; } current_view_index = 0; active = true; /* Set all to semi-transparent */ for (auto view : views) { set_view_highlighted(view, false); } input_grab->grab_input(wf::scene::layer::OVERLAY); activating_modifiers = wf::get_core().seat->get_keyboard_modifiers(); switch_next(forward); output->connect(&cleanup_view); return true; } wf::key_callback fast_switch = [=] (auto) { return do_switch(true); }; wf::key_callback fast_switch_backward = [=] (auto) { return do_switch(false); }; void switch_terminate() { // May modify alpha view_chosen(current_view_index, false); // Ungrab after selecting the correct view, so that it gets the focus directly input_grab->ungrab_input(); output->deactivate_plugin(&grab_interface); // Remove transformers after modifying alpha for (auto view : views) { view->get_transformed_node()->rem_transformer(transformer_name); } active = false; cleanup_view.disconnect(); } void switch_next(bool forward) { set_view_highlighted(views[current_view_index], false); // deselect last view int index = current_view_index; if (forward) { index = (index + 1) % views.size(); } else { index = index ? index - 1 : views.size() - 1; } view_chosen(index, true); } void fini() override { if (active) { switch_terminate(); } output->rem_binding(&fast_switch); output->rem_binding(&fast_switch_backward); } }; DECLARE_WAYFIRE_PLUGIN(wf::per_output_plugin_t); wayfire-0.8.1/plugins/single_plugins/fisheye.cpp000066400000000000000000000137631457431457600220630ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2018 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 static const char *vertex_shader = R"( #version 100 attribute mediump vec2 position; void main() { gl_Position = vec4(position.xy, 0.0, 1.0); } )"; static const char *fragment_shader = R"( #version 100 precision mediump float; uniform vec2 u_resolution; uniform vec2 u_mouse; uniform float u_radius; uniform float u_zoom; uniform sampler2D u_texture; const float PI = 3.1415926535; void main() { float radius = u_radius; float zoom = u_zoom; float pw = 1.0 / u_resolution.x; float ph = 1.0 / u_resolution.y; vec4 p0 = vec4(u_mouse.x, u_resolution.y - u_mouse.y, 1.0 / radius, 0.0); vec4 p1 = vec4(pw, ph, PI / radius, (zoom - 1.0) * zoom); vec4 p2 = vec4(0, 0, -PI / 2.0, 0.0); vec4 t0, t1, t2, t3; vec3 tc = vec3(1.0, 0.0, 0.0); vec2 uv = vec2(gl_FragCoord.x, gl_FragCoord.y); t1 = p0.xyww - vec4(uv, 0.0, 0.0); t2.x = t2.y = t2.z = t2.w = 1.0 / sqrt(dot(t1.xyz, t1.xyz)); t0 = t2 - p0; t3.x = t3.y = t3.z = t3.w = 1.0 / t2.x; t3 = t3 * p1.z + p2.z; t3.x = t3.y = t3.z = t3.w = cos(t3.x); t3 = t3 * p1.w; t1 = t2 * t1; t1 = t1 * t3 + vec4(uv, 0.0, 0.0); if (t0.z < 0.0) { t1.x = uv.x; t1.y = uv.y; } t1 = t1 * p1 + p2; tc = texture2D(u_texture, t1.xy).rgb; gl_FragColor = vec4(tc, 1.0); } )"; class wayfire_fisheye : public wf::per_output_plugin_instance_t { wf::animation::simple_animation_t progression{wf::create_option(300)}; float target_zoom; bool active, hook_set; wf::option_wrapper_t radius{"fisheye/radius"}; wf::option_wrapper_t zoom{"fisheye/zoom"}; OpenGL::program_t program; wf::plugin_activation_data_t grab_interface = { .name = "fisheye", .capabilities = 0, }; public: void init() override { hook_set = active = false; output->add_activator(wf::option_wrapper_t{"fisheye/toggle"}, &toggle_cb); target_zoom = zoom; zoom.set_callback([=] () { if (active) { this->progression.animate(zoom); } }); OpenGL::render_begin(); program.set_simple( OpenGL::compile_program(vertex_shader, fragment_shader)); OpenGL::render_end(); } wf::activator_callback toggle_cb = [=] (auto) { if (!output->can_activate_plugin(&grab_interface)) { return false; } if (active) { active = false; progression.animate(0); } else { active = true; progression.animate(zoom); if (!hook_set) { hook_set = true; output->render->add_post(&render_hook); output->render->set_redraw_always(); } } return true; }; wf::post_hook_t render_hook = [=] (const wf::framebuffer_t& source, const wf::framebuffer_t& dest) { auto oc = output->get_cursor_position(); wlr_box box = {(int)oc.x, (int)oc.y, 1, 1}; box = output->render->get_target_framebuffer(). framebuffer_box_from_geometry_box(box); oc.x = box.x; oc.y = box.y; static const float vertexData[] = { -1.0f, -1.0f, 1.0f, -1.0f, 1.0f, 1.0f, -1.0f, 1.0f }; OpenGL::render_begin(dest); program.use(wf::TEXTURE_TYPE_RGBA); GL_CALL(glBindTexture(GL_TEXTURE_2D, source.tex)); GL_CALL(glActiveTexture(GL_TEXTURE0)); program.uniform2f("u_mouse", oc.x, oc.y); program.uniform2f("u_resolution", dest.viewport_width, dest.viewport_height); program.uniform1f("u_radius", radius); program.uniform1f("u_zoom", progression); program.attrib_pointer("position", 2, 0, vertexData); GL_CALL(glDrawArrays(GL_TRIANGLE_FAN, 0, 4)); GL_CALL(glBindTexture(GL_TEXTURE_2D, 0)); program.deactivate(); OpenGL::render_end(); if (!active && !progression.running()) { finalize(); } }; void finalize() { output->render->rem_post(&render_hook); output->render->set_redraw_always(false); hook_set = false; } void fini() override { if (hook_set) { finalize(); } OpenGL::render_begin(); program.free_resources(); OpenGL::render_end(); output->rem_binding(&toggle_cb); } }; DECLARE_WAYFIRE_PLUGIN(wf::per_output_plugin_t); wayfire-0.8.1/plugins/single_plugins/idle.cpp000066400000000000000000000261231457431457600213360ustar00rootroot00000000000000#include "wayfire/per-output-plugin.hpp" #include "wayfire/plugins/common/shared-core-data.hpp" #include "wayfire/render-manager.hpp" #include "wayfire/output.hpp" #include "wayfire/core.hpp" #include "wayfire/output-layout.hpp" #include "wayfire/workspace-set.hpp" #include "wayfire/signal-definitions.hpp" #include "wayfire/seat.hpp" #include "../cube/cube-control-signal.hpp" #include #include #include #include #include #define CUBE_ZOOM_BASE 1.0 enum cube_screensaver_state { CUBE_SCREENSAVER_DISABLED, CUBE_SCREENSAVER_RUNNING, CUBE_SCREENSAVER_STOPPING, }; using namespace wf::animation; class screensaver_animation_t : public duration_t { public: using duration_t::duration_t; timed_transition_t rot{*this}; timed_transition_t zoom{*this}; timed_transition_t ease{*this}; }; class wayfire_idle { wf::option_wrapper_t dpms_timeout{"idle/dpms_timeout"}; bool is_idle = false; public: wf::signal::connection_t on_seat_activity; std::optional hotkey_inhibitor; wf::wl_timer timeout_dpms; wayfire_idle() { dpms_timeout.set_callback([=] () { create_dpms_timeout(); }); on_seat_activity = [=] (void*) { create_dpms_timeout(); }; create_dpms_timeout(); wf::get_core().connect(&on_seat_activity); } void create_dpms_timeout() { if (dpms_timeout <= 0) { timeout_dpms.disconnect(); return; } if (!timeout_dpms.is_connected() && is_idle) { is_idle = false; set_state(wf::OUTPUT_IMAGE_SOURCE_DPMS, wf::OUTPUT_IMAGE_SOURCE_SELF); return; } timeout_dpms.disconnect(); timeout_dpms.set_timeout(1000 * dpms_timeout, [=] () { is_idle = true; set_state(wf::OUTPUT_IMAGE_SOURCE_SELF, wf::OUTPUT_IMAGE_SOURCE_DPMS); }); } ~wayfire_idle() { timeout_dpms.disconnect(); wf::get_core().disconnect(&on_seat_activity); } /* Change all outputs with state from to state to */ void set_state(wf::output_image_source_t from, wf::output_image_source_t to) { auto config = wf::get_core().output_layout->get_current_configuration(); for (auto& entry : config) { if (entry.second.source == from) { entry.second.source = to; } } wf::get_core().output_layout->apply_configuration(config); } }; class wayfire_idle_plugin : public wf::per_output_plugin_instance_t { double rotation = 0.0; wf::option_wrapper_t zoom_speed{"idle/cube_zoom_speed"}; screensaver_animation_t screensaver_animation{zoom_speed}; wf::option_wrapper_t screensaver_timeout{"idle/screensaver_timeout"}; wf::option_wrapper_t cube_rotate_speed{"idle/cube_rotate_speed"}; wf::option_wrapper_t cube_max_zoom{"idle/cube_max_zoom"}; wf::option_wrapper_t disable_on_fullscreen{"idle/disable_on_fullscreen"}; wf::option_wrapper_t disable_initially{"idle/disable_initially"}; std::optional fullscreen_inhibitor; bool has_fullscreen = false; cube_screensaver_state state = CUBE_SCREENSAVER_DISABLED; bool hook_set = false; bool output_inhibited = false; uint32_t last_time; wf::wl_timer timeout_screensaver; wf::signal::connection_t on_seat_activity; wf::shared_data::ref_ptr_t global_idle; wf::activator_callback toggle = [=] (auto) { if (global_idle->hotkey_inhibitor.has_value()) { global_idle->hotkey_inhibitor.reset(); } else { global_idle->hotkey_inhibitor.emplace(); } return true; }; wf::signal::connection_t fullscreen_state_changed = [=] (wf::fullscreen_layer_focused_signal *ev) { this->has_fullscreen = ev->has_promoted; update_fullscreen(); }; wf::signal::connection_t inhibit_changed = [=] (wf::idle_inhibit_changed_signal *ev) { if (!ev) { return; } if (ev->inhibit) { wf::get_core().disconnect(&global_idle->on_seat_activity); wf::get_core().disconnect(&on_seat_activity); global_idle->timeout_dpms.disconnect(); timeout_screensaver.disconnect(); } else { wf::get_core().connect(&global_idle->on_seat_activity); wf::get_core().connect(&on_seat_activity); global_idle->create_dpms_timeout(); create_screensaver_timeout(); } }; wf::config::option_base_t::updated_callback_t disable_on_fullscreen_changed = [=] () { update_fullscreen(); }; void update_fullscreen() { bool want = disable_on_fullscreen && has_fullscreen; if (want && !fullscreen_inhibitor.has_value()) { fullscreen_inhibitor.emplace(); } if (!want && fullscreen_inhibitor.has_value()) { fullscreen_inhibitor.reset(); } } wf::plugin_activation_data_t grab_interface = { .name = "idle", .capabilities = 0, }; public: void init() override { if (disable_initially) { global_idle->hotkey_inhibitor.emplace(); } output->add_activator(wf::option_wrapper_t{"idle/toggle"}, &toggle); output->connect(&fullscreen_state_changed); disable_on_fullscreen.set_callback(disable_on_fullscreen_changed); if (auto toplevel = toplevel_cast(wf::get_active_view_for_output(output))) { /* Currently, the fullscreen count would always be 0 or 1, * since fullscreen-layer-focused is only emitted on changes between 0 * and 1 **/ has_fullscreen = toplevel->pending_fullscreen(); } update_fullscreen(); screensaver_timeout.set_callback([=] () { create_screensaver_timeout(); }); create_screensaver_timeout(); on_seat_activity = [=] (void*) { create_screensaver_timeout(); }; wf::get_core().connect(&on_seat_activity); wf::get_core().connect(&inhibit_changed); } void create_screensaver_timeout() { if (screensaver_timeout <= 0) { timeout_screensaver.disconnect(); return; } if (!timeout_screensaver.is_connected() && output_inhibited) { uninhibit_output(); return; } if (!timeout_screensaver.is_connected() && (state == CUBE_SCREENSAVER_RUNNING)) { stop_screensaver(); return; } timeout_screensaver.disconnect(); timeout_screensaver.set_timeout(1000 * screensaver_timeout, [=] () { start_screensaver(); }); } void inhibit_output() { if (output_inhibited) { return; } if (hook_set) { output->render->rem_effect(&screensaver_frame); hook_set = false; } output->render->add_inhibit(true); output->render->damage_whole(); state = CUBE_SCREENSAVER_DISABLED; output_inhibited = true; } void uninhibit_output() { if (!output_inhibited) { return; } output->render->add_inhibit(false); output->render->damage_whole(); output_inhibited = false; } void screensaver_terminate() { cube_control_signal data; data.angle = 0.0; data.zoom = CUBE_ZOOM_BASE; data.ease = 0.0; data.last_frame = true; data.carried_out = false; output->emit(&data); if (hook_set) { output->render->rem_effect(&screensaver_frame); hook_set = false; } if (state == CUBE_SCREENSAVER_DISABLED) { uninhibit_output(); } state = CUBE_SCREENSAVER_DISABLED; } wf::effect_hook_t screensaver_frame = [=] () { cube_control_signal data; uint32_t current = wf::get_current_time(); uint32_t elapsed = current - last_time; last_time = current; if ((state == CUBE_SCREENSAVER_STOPPING) && !screensaver_animation.running()) { screensaver_terminate(); return; } if (state == CUBE_SCREENSAVER_STOPPING) { rotation = screensaver_animation.rot; } else { rotation += (cube_rotate_speed / 5000.0) * elapsed; } if (rotation > M_PI * 2) { rotation -= M_PI * 2; } data.angle = rotation; data.zoom = screensaver_animation.zoom; data.ease = screensaver_animation.ease; data.last_frame = false; data.carried_out = false; output->emit(&data); if (!data.carried_out) { screensaver_terminate(); return; } if (state == CUBE_SCREENSAVER_STOPPING) { wf::get_core().seat->notify_activity(); } }; void start_screensaver() { cube_control_signal data; data.angle = 0.0; data.zoom = CUBE_ZOOM_BASE; data.ease = 0.0; data.last_frame = false; data.carried_out = false; output->emit(&data); if (data.carried_out) { if (!hook_set) { output->render->add_effect( &screensaver_frame, wf::OUTPUT_EFFECT_PRE); hook_set = true; } } else if (state == CUBE_SCREENSAVER_DISABLED) { inhibit_output(); return; } state = CUBE_SCREENSAVER_RUNNING; rotation = 0.0; screensaver_animation.zoom.set(CUBE_ZOOM_BASE, cube_max_zoom); screensaver_animation.ease.set(0.0, 1.0); screensaver_animation.start(); last_time = wf::get_current_time(); } void stop_screensaver() { if (state == CUBE_SCREENSAVER_DISABLED) { uninhibit_output(); return; } state = CUBE_SCREENSAVER_STOPPING; double end = rotation > M_PI ? M_PI * 2 : 0.0; screensaver_animation.rot.set(rotation, end); screensaver_animation.zoom.restart_with_end(CUBE_ZOOM_BASE); screensaver_animation.ease.restart_with_end(0.0); screensaver_animation.start(); } void fini() override { wf::get_core().disconnect(&on_seat_activity); wf::get_core().disconnect(&inhibit_changed); timeout_screensaver.disconnect(); output->rem_binding(&toggle); } }; DECLARE_WAYFIRE_PLUGIN(wf::per_output_plugin_t); wayfire-0.8.1/plugins/single_plugins/invert.cpp000066400000000000000000000067041457431457600217330ustar00rootroot00000000000000#include #include #include #include static const char *vertex_shader = R"( #version 100 attribute mediump vec2 position; attribute highp vec2 uvPosition; varying highp vec2 uvpos; void main() { gl_Position = vec4(position.xy, 0.0, 1.0); uvpos = uvPosition; } )"; static const char *fragment_shader = R"( #version 100 varying highp vec2 uvpos; uniform sampler2D smp; uniform bool preserve_hue; void main() { mediump vec4 tex = texture2D(smp, uvpos); if (preserve_hue) { mediump float hue = tex.a - min(tex.r, min(tex.g, tex.b)) - max(tex.r, max(tex.g, tex.b)); gl_FragColor = hue + tex; } else { gl_FragColor = vec4(1.0 - tex.r, 1.0 - tex.g, 1.0 - tex.b, 1.0); } } )"; class wayfire_invert_screen : public wf::per_output_plugin_instance_t { wf::post_hook_t hook; wf::activator_callback toggle_cb; wf::option_wrapper_t preserve_hue{"invert/preserve_hue"}; bool active = false; OpenGL::program_t program; wf::plugin_activation_data_t grab_interface = { .name = "invert", .capabilities = 0, }; public: void init() override { wf::option_wrapper_t toggle_key{"invert/toggle"}; hook = [=] (const wf::framebuffer_t& source, const wf::framebuffer_t& destination) { render(source, destination); }; toggle_cb = [=] (auto) { if (!output->can_activate_plugin(&grab_interface)) { return false; } if (active) { output->render->rem_post(&hook); } else { output->render->add_post(&hook); } active = !active; return true; }; OpenGL::render_begin(); program.set_simple( OpenGL::compile_program(vertex_shader, fragment_shader)); OpenGL::render_end(); output->add_activator(toggle_key, &toggle_cb); } void render(const wf::framebuffer_t& source, const wf::framebuffer_t& destination) { static const float vertexData[] = { -1.0f, -1.0f, 1.0f, -1.0f, 1.0f, 1.0f, -1.0f, 1.0f }; static const float coordData[] = { 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f }; OpenGL::render_begin(destination); program.use(wf::TEXTURE_TYPE_RGBA); GL_CALL(glBindTexture(GL_TEXTURE_2D, source.tex)); GL_CALL(glActiveTexture(GL_TEXTURE0)); program.attrib_pointer("position", 2, 0, vertexData); program.attrib_pointer("uvPosition", 2, 0, coordData); program.uniform1i("preserve_hue", preserve_hue); GL_CALL(glDisable(GL_BLEND)); GL_CALL(glDrawArrays(GL_TRIANGLE_FAN, 0, 4)); GL_CALL(glEnable(GL_BLEND)); GL_CALL(glBindTexture(GL_TEXTURE_2D, 0)); program.deactivate(); OpenGL::render_end(); } void fini() override { if (active) { output->render->rem_post(&hook); } OpenGL::render_begin(); program.free_resources(); OpenGL::render_end(); output->rem_binding(&toggle_cb); } }; DECLARE_WAYFIRE_PLUGIN(wf::per_output_plugin_t); wayfire-0.8.1/plugins/single_plugins/ipc-rules.cpp000066400000000000000000000544711457431457600223330ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include "plugins/ipc/ipc-helpers.hpp" #include "plugins/ipc/ipc-method-repository.hpp" #include "wayfire/core.hpp" #include "wayfire/plugins/common/util.hpp" #include "wayfire/unstable/wlr-surface-node.hpp" #include "wayfire/plugins/common/shared-core-data.hpp" #include "wayfire/signal-definitions.hpp" #include "wayfire/signal-provider.hpp" #include "wayfire/view-helpers.hpp" #include "wayfire/window-manager.hpp" #include "wayfire/workarea.hpp" #include "config.h" #include #include static std::string role_to_string(enum wf::view_role_t role) { switch (role) { case wf::VIEW_ROLE_TOPLEVEL: return "toplevel"; case wf::VIEW_ROLE_UNMANAGED: return "unmanaged"; case wf::VIEW_ROLE_DESKTOP_ENVIRONMENT: return "desktop-environment"; default: return "unknown"; } } static std::string layer_to_string(std::optional layer) { if (!layer.has_value()) { return "none"; } switch (layer.value()) { case wf::scene::layer::BACKGROUND: return "background"; case wf::scene::layer::BOTTOM: return "bottom"; case wf::scene::layer::WORKSPACE: return "workspace"; case wf::scene::layer::TOP: return "top"; case wf::scene::layer::UNMANAGED: return "unmanaged"; case wf::scene::layer::OVERLAY: return "lock"; case wf::scene::layer::DWIDGET: return "dew"; default: break; } wf::dassert(false, "invalid layer!"); assert(false); // prevent compiler warning } static std::string wlr_input_device_type_to_string(wlr_input_device_type type) { switch (type) { case WLR_INPUT_DEVICE_KEYBOARD: return "keyboard"; case WLR_INPUT_DEVICE_POINTER: return "pointer"; case WLR_INPUT_DEVICE_TOUCH: return "touch"; case WLR_INPUT_DEVICE_TABLET_TOOL: return "tablet_tool"; case WLR_INPUT_DEVICE_TABLET_PAD: return "tablet_pad"; case WLR_INPUT_DEVICE_SWITCH: return "switch"; default: return "unknown"; } } static wf::geometry_t get_view_base_geometry(wayfire_view view) { auto sroot = view->get_surface_root_node(); for (auto& ch : sroot->get_children()) { if (auto wlr_surf = dynamic_cast(ch.get())) { auto bbox = wlr_surf->get_bounding_box(); wf::pointf_t origin = sroot->to_global({0, 0}); bbox.x = origin.x; bbox.y = origin.y; return bbox; } } return sroot->get_bounding_box(); } class ipc_rules_t : public wf::plugin_interface_t, public wf::per_output_tracker_mixin_t<> { public: void init() override { method_repository->register_method("wayfire/configuration", get_wayfire_configuration_info); method_repository->register_method("input/list-devices", list_input_devices); method_repository->register_method("input/configure-device", configure_input_device); method_repository->register_method("window-rules/events/watch", on_client_watch); method_repository->register_method("window-rules/list-views", list_views); method_repository->register_method("window-rules/list-outputs", list_outputs); method_repository->register_method("window-rules/list-wsets", list_wsets); method_repository->register_method("window-rules/view-info", get_view_info); method_repository->register_method("window-rules/output-info", get_output_info); method_repository->register_method("window-rules/wset-info", get_wset_info); method_repository->register_method("window-rules/configure-view", configure_view); method_repository->register_method("window-rules/focus-view", focus_view); method_repository->register_method("window-rules/get-focused-view", get_focused_view); method_repository->register_method("window-rules/close-view", close_view); method_repository->connect(&on_client_disconnected); wf::get_core().connect(&on_view_mapped); wf::get_core().connect(&on_view_unmapped); wf::get_core().connect(&on_kbfocus_changed); wf::get_core().connect(&on_title_changed); wf::get_core().connect(&on_app_id_changed); wf::get_core().connect(&on_plugin_activation_changed); init_output_tracking(); } void fini() override { method_repository->unregister_method("wayfire/configuration"); method_repository->unregister_method("input/list-devices"); method_repository->unregister_method("input/configure-device"); method_repository->unregister_method("window-rules/events/watch"); method_repository->unregister_method("window-rules/list-views"); method_repository->unregister_method("window-rules/list-outputs"); method_repository->unregister_method("window-rules/list-wsets"); method_repository->unregister_method("window-rules/view-info"); method_repository->unregister_method("window-rules/output-info"); method_repository->unregister_method("window-rules/wset-info"); method_repository->unregister_method("window-rules/configure-view"); method_repository->unregister_method("window-rules/focus-view"); method_repository->unregister_method("window-rules/get-focused-view"); method_repository->unregister_method("window-rules/close-view"); fini_output_tracking(); } void handle_new_output(wf::output_t *output) override { output->connect(&_tiled); output->connect(&_minimized); output->connect(&_fullscreened); output->connect(&on_wset_changed); output->connect(&on_wset_workspace_changed); nlohmann::json data; data["event"] = "output-added"; data["output"] = output_to_json(output); send_event_to_subscribes(data, data["event"]); } void handle_output_removed(wf::output_t *output) override { nlohmann::json data; data["event"] = "output-removed"; data["output"] = output_to_json(output); send_event_to_subscribes(data, data["event"]); } wf::ipc::method_callback get_wayfire_configuration_info = [=] (nlohmann::json) { nlohmann::json response; response["api-version"] = WAYFIRE_API_ABI_VERSION; response["plugin-path"] = PLUGIN_PATH; response["plugin-xml-dir"] = PLUGIN_XML_DIR; response["xwayland-support"] = WF_HAS_XWAYLAND; response["build-commit"] = wf::version::git_commit; response["build-branch"] = wf::version::git_branch; return response; }; wf::ipc::method_callback list_views = [=] (nlohmann::json) { auto response = nlohmann::json::array(); for (auto& view : wf::get_core().get_all_views()) { nlohmann::json v = view_to_json(view); response.push_back(v); } return response; }; wf::ipc::method_callback get_view_info = [=] (nlohmann::json data) { WFJSON_EXPECT_FIELD(data, "id", number_integer); if (auto view = wf::ipc::find_view_by_id(data["id"])) { auto response = wf::ipc::json_ok(); response["info"] = view_to_json(view); return response; } return wf::ipc::json_error("no such view"); }; wf::ipc::method_callback get_focused_view = [=] (nlohmann::json data) { if (auto view = wf::get_core().seat->get_active_view()) { auto response = wf::ipc::json_ok(); response["info"] = view_to_json(view); return response; } else { auto response = wf::ipc::json_ok(); response["info"] = nullptr; return response; } }; wf::ipc::method_callback focus_view = [=] (nlohmann::json data) { WFJSON_EXPECT_FIELD(data, "id", number_integer); if (auto view = wf::ipc::find_view_by_id(data["id"])) { auto response = wf::ipc::json_ok(); auto toplevel = wf::toplevel_cast(view); if (!toplevel) { return wf::ipc::json_error("view is not toplevel"); } wf::get_core().default_wm->focus_request(toplevel); return response; } return wf::ipc::json_error("no such view"); }; wf::ipc::method_callback close_view = [=] (nlohmann::json data) { WFJSON_EXPECT_FIELD(data, "id", number_integer); if (auto view = wf::ipc::find_view_by_id(data["id"])) { auto response = wf::ipc::json_ok(); view->close(); return response; } return wf::ipc::json_error("no such view"); }; nlohmann::json output_to_json(wf::output_t *o) { nlohmann::json response; response["id"] = o->get_id(); response["name"] = o->to_string(); response["geometry"] = wf::ipc::geometry_to_json(o->get_layout_geometry()); response["workarea"] = wf::ipc::geometry_to_json(o->workarea->get_workarea()); response["wset-index"] = o->wset()->get_index(); response["workspace"]["x"] = o->wset()->get_current_workspace().x; response["workspace"]["y"] = o->wset()->get_current_workspace().y; response["workspace"]["grid_width"] = o->wset()->get_workspace_grid_size().width; response["workspace"]["grid_height"] = o->wset()->get_workspace_grid_size().height; return response; } wf::ipc::method_callback list_outputs = [=] (nlohmann::json) { auto response = nlohmann::json::array(); for (auto& output : wf::get_core().output_layout->get_outputs()) { response.push_back(output_to_json(output)); } return response; }; wf::ipc::method_callback get_output_info = [=] (nlohmann::json data) { WFJSON_EXPECT_FIELD(data, "id", number_integer); auto wo = wf::ipc::find_output_by_id(data["id"]); if (!wo) { return wf::ipc::json_error("output not found"); } auto response = output_to_json(wo); return response; }; wf::ipc::method_callback configure_view = [=] (nlohmann::json data) { WFJSON_EXPECT_FIELD(data, "id", number_integer); WFJSON_OPTIONAL_FIELD(data, "output_id", number_integer); WFJSON_OPTIONAL_FIELD(data, "geometry", object); auto view = wf::ipc::find_view_by_id(data["id"]); if (!view) { return wf::ipc::json_error("view not found"); } auto toplevel = wf::toplevel_cast(view); if (!toplevel) { return wf::ipc::json_error("view is not toplevel"); } if (data.contains("output_id")) { auto wo = wf::ipc::find_output_by_id(data["output_id"]); if (!wo) { return wf::ipc::json_error("output not found"); } wf::move_view_to_output(toplevel, wo, !data.contains("geometry")); } if (data.contains("geometry")) { auto geometry = wf::ipc::geometry_from_json(data["geometry"]); if (!geometry) { return wf::ipc::json_error("invalid geometry"); } toplevel->set_geometry(*geometry); } return wf::ipc::json_ok(); }; nlohmann::json wset_to_json(wf::workspace_set_t *wset) { nlohmann::json response; response["index"] = wset->get_index(); response["name"] = wset->to_string(); auto output = wset->get_attached_output(); response["output-id"] = output ? (int)output->get_id() : -1; response["output-name"] = output ? output->to_string() : ""; response["workspace"]["x"] = wset->get_current_workspace().x; response["workspace"]["y"] = wset->get_current_workspace().y; response["workspace"]["grid_width"] = wset->get_workspace_grid_size().width; response["workspace"]["grid_height"] = wset->get_workspace_grid_size().height; return response; } wf::ipc::method_callback list_wsets = [=] (nlohmann::json) { auto response = nlohmann::json::array(); for (auto& workspace_set : wf::workspace_set_t::get_all()) { response.push_back(wset_to_json(workspace_set.get())); } return response; }; wf::ipc::method_callback get_wset_info = [=] (nlohmann::json data) { WFJSON_EXPECT_FIELD(data, "id", number_integer); auto ws = wf::ipc::find_workspace_set_by_index(data["id"]); if (!ws) { return wf::ipc::json_error("workspace set not found"); } auto response = wset_to_json(ws); return response; }; private: wf::shared_data::ref_ptr_t method_repository; // Track a list of clients which have requested watch std::map> clients; wf::ipc::method_callback_full on_client_watch = [=] (nlohmann::json data, wf::ipc::client_interface_t *client) { static constexpr const char *EVENTS = "events"; WFJSON_OPTIONAL_FIELD(data, EVENTS, array); std::set subscribed_to; if (data.contains(EVENTS)) { for (auto& sub : data[EVENTS]) { if (!sub.is_string()) { return wf::ipc::json_error("Event list contains non-string entries!"); } subscribed_to.insert((std::string)sub); } } clients[client] = subscribed_to; return wf::ipc::json_ok(); }; wf::signal::connection_t on_client_disconnected = [=] (wf::ipc::client_disconnected_signal *ev) { clients.erase(ev->client); }; void send_view_to_subscribes(wayfire_view view, std::string event_name) { nlohmann::json event; event["event"] = event_name; event["view"] = view_to_json(view); send_event_to_subscribes(event, event_name); } void send_event_to_subscribes(const nlohmann::json& data, const std::string& event_name) { for (auto& [client, events] : clients) { if (events.empty() || events.count(event_name)) { client->send_json(data); } } } wf::signal::connection_t on_view_mapped = [=] (wf::view_mapped_signal *ev) { send_view_to_subscribes(ev->view, "view-mapped"); }; wf::signal::connection_t on_view_unmapped = [=] (wf::view_unmapped_signal *ev) { send_view_to_subscribes(ev->view, "view-unmapped"); }; wf::signal::connection_t on_kbfocus_changed = [=] (wf::keyboard_focus_changed_signal *ev) { send_view_to_subscribes(wf::node_to_view(ev->new_focus), "view-focused"); }; // Maximized rule handler. wf::signal::connection_t _tiled = [=] (wf::view_tiled_signal *ev) { send_view_to_subscribes(ev->view, "view-tiled"); }; // Minimized rule handler. wf::signal::connection_t _minimized = [=] (wf::view_minimized_signal *ev) { send_view_to_subscribes(ev->view, "view-minimized"); }; // Fullscreened rule handler. wf::signal::connection_t _fullscreened = [=] (wf::view_fullscreen_signal *ev) { send_view_to_subscribes(ev->view, "view-fullscreen"); }; wf::signal::connection_t on_title_changed = [=] (wf::view_title_changed_signal *ev) { send_view_to_subscribes(ev->view, "view-title-changed"); }; wf::signal::connection_t on_app_id_changed = [=] (wf::view_app_id_changed_signal *ev) { send_view_to_subscribes(ev->view, "view-app-id-changed"); }; wf::signal::connection_t on_plugin_activation_changed = [=] (wf::output_plugin_activated_changed_signal *ev) { nlohmann::json data; data["event"] = "plugin-activation-state-changed"; data["plugin"] = ev->plugin_name; data["state"] = ev->activated; data["output"] = ev->output ? (int)ev->output->get_id() : -1; send_event_to_subscribes(data, data["event"]); }; wf::signal::connection_t on_wset_changed = [=] (wf::workspace_set_changed_signal *ev) { nlohmann::json data; data["event"] = "output-wset-changed"; data["new-wset"] = ev->new_wset ? (int)ev->new_wset->get_id() : -1; data["output"] = ev->output ? (int)ev->output->get_id() : -1; send_event_to_subscribes(data, data["event"]); }; wf::signal::connection_t on_wset_workspace_changed = [=] (wf::workspace_changed_signal *ev) { nlohmann::json data; data["event"] = "wset-workspace-changed"; data["previous-workspace"] = wf::ipc::point_to_json(ev->old_viewport); data["new-workspace"] = wf::ipc::point_to_json(ev->new_viewport); data["output"] = ev->output ? (int)ev->output->get_id() : -1; data["wset"] = (ev->output && ev->output->wset()) ? (int)ev->output->wset()->get_id() : -1; send_event_to_subscribes(data, data["event"]); }; std::string get_view_type(wayfire_view view) { if (view->role == wf::VIEW_ROLE_TOPLEVEL) { return "toplevel"; } if (view->role == wf::VIEW_ROLE_UNMANAGED) { #if WF_HAS_XWAYLAND auto surf = view->get_wlr_surface(); if (surf && wlr_xwayland_surface_try_from_wlr_surface(surf)) { return "x-or"; } #endif return "unmanaged"; } auto layer = wf::get_view_layer(view); if ((layer == wf::scene::layer::BACKGROUND) || (layer == wf::scene::layer::BOTTOM)) { return "background"; } else if (layer == wf::scene::layer::TOP) { return "panel"; } else if (layer == wf::scene::layer::OVERLAY) { return "overlay"; } return "unknown"; } nlohmann::json view_to_json(wayfire_view view) { if (!view) { return nullptr; } auto output = view->get_output(); nlohmann::json description; description["id"] = view->get_id(); description["pid"] = get_view_pid(view); description["title"] = view->get_title(); description["app-id"] = view->get_app_id(); description["base-geometry"] = wf::ipc::geometry_to_json(get_view_base_geometry(view)); auto toplevel = wf::toplevel_cast(view); description["parent"] = toplevel && toplevel->parent ? (int)toplevel->parent->get_id() : -1; description["geometry"] = wf::ipc::geometry_to_json(toplevel ? toplevel->get_pending_geometry() : view->get_bounding_box()); description["bbox"] = wf::ipc::geometry_to_json(view->get_bounding_box()); description["output-id"] = view->get_output() ? view->get_output()->get_id() : -1; description["output-name"] = output ? output->to_string() : "null"; description["last-focus-timestamp"] = wf::get_focus_timestamp(view); description["role"] = role_to_string(view->role); description["mapped"] = view->is_mapped(); description["layer"] = layer_to_string(get_view_layer(view)); description["tiled-edges"] = toplevel ? toplevel->pending_tiled_edges() : 0; description["fullscreen"] = toplevel ? toplevel->pending_fullscreen() : false; description["minimized"] = toplevel ? toplevel->minimized : false; description["activated"] = toplevel ? toplevel->activated : false; description["sticky"] = toplevel ? toplevel->sticky : false; description["wset-index"] = toplevel && toplevel->get_wset() ? toplevel->get_wset()->get_index() : -1; description["focusable"] = view->is_focusable(); description["type"] = get_view_type(view); return description; } wf::ipc::method_callback list_input_devices = [&] (const nlohmann::json&) { auto response = nlohmann::json::array(); for (auto& device : wf::get_core().get_input_devices()) { nlohmann::json d; d["id"] = (intptr_t)device->get_wlr_handle(); d["name"] = nonull(device->get_wlr_handle()->name); d["vendor"] = device->get_wlr_handle()->vendor; d["product"] = device->get_wlr_handle()->product; d["type"] = wlr_input_device_type_to_string(device->get_wlr_handle()->type); d["enabled"] = device->is_enabled(); response.push_back(d); } return response; }; wf::ipc::method_callback configure_input_device = [&] (const nlohmann::json& data) { WFJSON_EXPECT_FIELD(data, "id", number_unsigned); WFJSON_EXPECT_FIELD(data, "enabled", boolean); for (auto& device : wf::get_core().get_input_devices()) { if ((intptr_t)device->get_wlr_handle() == data["id"]) { device->set_enabled(data["enabled"]); return wf::ipc::json_ok(); } } return wf::ipc::json_error("Unknown input device!"); }; pid_t get_view_pid(wayfire_view view) { pid_t pid = -1; if (!view) { return pid; } #if WF_HAS_XWAYLAND wlr_surface *wlr_surface = view->get_wlr_surface(); if (wlr_surface && wlr_xwayland_surface_try_from_wlr_surface(wlr_surface)) { pid = wlr_xwayland_surface_try_from_wlr_surface(wlr_surface)->pid; } else #endif if (view && view->get_client()) { wl_client_get_credentials(view->get_client(), &pid, 0, 0); } return pid; // NOLINT } }; DECLARE_WAYFIRE_PLUGIN(ipc_rules_t); wayfire-0.8.1/plugins/single_plugins/meson.build000066400000000000000000000012211457431457600220470ustar00rootroot00000000000000plugins = [ 'move', 'resize', 'command', 'autostart', 'vswipe', 'wrot', 'expo', 'switcher', 'fast-switcher', 'oswitch', 'place', 'invert', 'fisheye', 'zoom', 'alpha', 'idle', 'extra-gestures', 'preserve-output', 'wsets', 'ipc-rules' ] all_include_dirs = [wayfire_api_inc, wayfire_conf_inc, plugins_common_inc, vswitch_inc, wobbly_inc, grid_inc] all_deps = [wlroots, pixman, wfconfig, wftouch, cairo, pango, pangocairo, json] foreach plugin : plugins shared_module(plugin, plugin + '.cpp', include_directories: all_include_dirs, dependencies: all_deps, install: true, install_dir: conf_data.get('PLUGIN_PATH')) endforeach wayfire-0.8.1/plugins/single_plugins/move.cpp000066400000000000000000000434761457431457600214010ustar00rootroot00000000000000#include "wayfire/debug.hpp" #include "wayfire/geometry.hpp" #include "wayfire/plugins/common/input-grab.hpp" #include "wayfire/scene-input.hpp" #include "wayfire/signal-provider.hpp" #include "wayfire/view-helpers.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include class wayfire_move : public wf::per_output_plugin_instance_t, public wf::pointer_interaction_t, public wf::touch_interaction_t { wf::button_callback activate_binding; wf::option_wrapper_t enable_snap{"move/enable_snap"}; wf::option_wrapper_t join_views{"move/join_views"}; wf::option_wrapper_t snap_threshold{"move/snap_threshold"}; wf::option_wrapper_t quarter_snap_threshold{"move/quarter_snap_threshold"}; wf::option_wrapper_t workspace_switch_after{"move/workspace_switch_after"}; wf::option_wrapper_t activate_button{"move/activate"}; wf::option_wrapper_t move_enable_snap_off{"move/enable_snap_off"}; wf::option_wrapper_t move_snap_off_threshold{"move/snap_off_threshold"}; struct { std::shared_ptr preview; wf::grid::slot_t slot_id = wf::grid::SLOT_NONE; } slot; wf::wl_timer workspace_switch_timer; wf::shared_data::ref_ptr_t drag_helper; bool can_handle_drag() { bool yes = output->can_activate_plugin(&grab_interface, wf::PLUGIN_ACTIVATE_ALLOW_MULTIPLE); return yes; } wf::signal::connection_t on_drag_output_focus = [=] (wf::move_drag::drag_focus_output_signal *ev) { if ((ev->focus_output == output) && can_handle_drag()) { drag_helper->set_scale(1.0); if (!output->is_plugin_active(grab_interface.name)) { grab_input(drag_helper->view); } } else { update_slot(wf::grid::SLOT_NONE); } }; wf::signal::connection_t on_drag_snap_off = [=] (wf::move_drag::snap_off_signal *ev) { if ((ev->focus_output == output) && can_handle_drag()) { wf::move_drag::adjust_view_on_snap_off(drag_helper->view); } }; wf::signal::connection_t on_drag_done = [=] (wf::move_drag::drag_done_signal *ev) { if ((ev->focused_output == output) && can_handle_drag() && !drag_helper->is_view_held_in_place()) { // Mark the last windowed geometry (which is the geometry before the view was grabbed: grabs work // not by moving the view, but by translating it with a transformer. Therefore, the view geometry // is still the geometry before the drag. wf::get_core().default_wm->update_last_windowed_geometry(ev->main_view); // Artificially continue the grab for a little bit more, so that the last windowed geometry does // not get overwritten. wf::get_core().default_wm->set_view_grabbed(ev->main_view, true); wf::move_drag::adjust_view_on_output(ev); if (enable_snap && (slot.slot_id != wf::grid::SLOT_NONE)) { wf::get_core().default_wm->tile_request(ev->main_view, wf::grid::get_tiled_edges_for_slot(slot.slot_id)); /* Update slot, will hide the preview as well */ update_slot(wf::grid::SLOT_NONE); } wf::get_core().default_wm->set_view_grabbed(ev->main_view, false); wf::view_change_workspace_signal data; data.view = ev->main_view; data.to = output->wset()->get_current_workspace(); data.old_workspace_valid = false; output->emit(&data); } deactivate(); }; // We listen for raw pointer button events independently of the active/inactive state of move. // We need this to determine the grab point for client-initiated move (i.e. when the user clicks and drags // the titlebar). Usually, there is a bit of delay in the signal, for example, GTK tells the compositor to // start interactive move after the pointer has moved ~5 pixels (but it can be much worse for programmed // tests). So, here we store the mouse position for every button press, and use that in client-initiated // move. // // We do the same for touch events. wf::point_t last_input_press_position = {0, 0}; wf::signal::connection_t> on_raw_pointer_button = [=] (wf::input_event_signal *ev) { if (ev->event->state == WLR_BUTTON_PRESSED) { last_input_press_position = get_global_input_coords(); } }; wf::signal::connection_t> on_raw_touch_down = [=] (wf::input_event_signal *ev) { if (ev->event->touch_id == 0) { last_input_press_position = get_global_input_coords(); } }; std::unique_ptr input_grab; wf::plugin_activation_data_t grab_interface = { .name = "move", .capabilities = wf::CAPABILITY_GRAB_INPUT | wf::CAPABILITY_MANAGE_DESKTOP, }; public: void init() override { wf::get_core().connect(&on_raw_pointer_button); wf::get_core().connect(&on_raw_touch_down); input_grab = std::make_unique("move", output, nullptr, this, this); input_grab->set_wants_raw_input(true); activate_binding = [=] (auto) { auto view = toplevel_cast(wf::get_core().get_cursor_focus_view()); if (view && (view->role != wf::VIEW_ROLE_DESKTOP_ENVIRONMENT)) { initiate(view, get_global_input_coords()); } // Even if we initiated, we want the button press to go to the grab node return false; }; output->add_button(activate_button, &activate_binding); using namespace std::placeholders; grab_interface.cancel = [=] () { input_pressed(WLR_BUTTON_RELEASED); }; output->connect(&move_request); drag_helper->connect(&on_drag_output_focus); drag_helper->connect(&on_drag_snap_off); drag_helper->connect(&on_drag_done); } void handle_pointer_button(const wlr_pointer_button_event& event) override { if (event.state != WLR_BUTTON_RELEASED) { return; } drag_helper->handle_input_released(); return; } void handle_pointer_motion(wf::pointf_t pointer_position, uint32_t time_ms) override { handle_input_motion(); } void handle_touch_up(uint32_t time_ms, int finger_id, wf::pointf_t lift_off_position) override { if (wf::get_core().get_touch_state().fingers.empty()) { input_pressed(WLR_BUTTON_RELEASED); } } void handle_touch_motion(uint32_t time_ms, int finger_id, wf::pointf_t position) override { handle_input_motion(); } wf::signal::connection_t move_request = [=] (wf::view_move_request_signal *ev) { initiate(ev->view, last_input_press_position); }; /** * Calculate plugin activation flags for the view. * * Activation flags ignore input inhibitors if the view is in the desktop * widget layer (i.e OSKs) */ uint32_t get_act_flags(wayfire_toplevel_view view) { auto view_layer = wf::get_view_layer(view).value_or(wf::scene::layer::WORKSPACE); /* Allow moving an on-screen keyboard while screen is locked */ bool ignore_inhibit = view_layer == wf::scene::layer::DWIDGET; uint32_t act_flags = 0; if (ignore_inhibit) { act_flags |= wf::PLUGIN_ACTIVATION_IGNORE_INHIBIT; } return act_flags; } /** * Calculate the view which is the actual target of this move operation. * * Usually, this is the view itself or its topmost parent if the join_views * option is set. */ wayfire_toplevel_view get_target_view(wayfire_toplevel_view view) { while (view && view->parent && join_views) { view = view->parent; } return view; } bool can_move_view(wayfire_toplevel_view view) { if (!view || !view->is_mapped() || !(view->get_allowed_actions() & wf::VIEW_ALLOW_MOVE)) { return false; } view = get_target_view(view); return output->can_activate_plugin(&grab_interface, get_act_flags(view)); } bool grab_input(wayfire_toplevel_view view) { view = view ?: drag_helper->view; if (!view) { return false; } if (!output->activate_plugin(&grab_interface, get_act_flags(view))) { return false; } this->input_grab->grab_input(wf::scene::layer::OVERLAY); slot.slot_id = wf::grid::SLOT_NONE; return true; } bool initiate(wayfire_toplevel_view view, wf::point_t grab_position) { // First, make sure that the view is on the output the input is. 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 offset = wf::origin(view->get_output()->get_layout_geometry()) + -wf::origin(target_output->get_layout_geometry()); move_view_to_output(view, target_output, false); view->move(view->get_geometry().x + offset.x, view->get_geometry().y + offset.y); // On the new output wf::get_core().default_wm->move_request(view); return false; } wayfire_toplevel_view grabbed_view = view; view = get_target_view(view); if (!can_move_view(view)) { return false; } if (!grab_input(view)) { return false; } wf::move_drag::drag_options_t opts; opts.enable_snap_off = move_enable_snap_off && (view->pending_fullscreen() || view->pending_tiled_edges()); opts.snap_off_threshold = move_snap_off_threshold; opts.join_views = join_views; if (join_views) { // ensure that the originally grabbed view will be focused wf::get_core().seat->focus_view(grabbed_view); } 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; return true; } void deactivate() { input_grab->ungrab_input(); output->deactivate_plugin(&grab_interface); } void input_pressed(uint32_t state) { if (state != WLR_BUTTON_RELEASED) { return; } drag_helper->handle_input_released(); } /* Calculate the slot to which the view would be snapped if the input * is released at output-local coordinates (x, y) */ wf::grid::slot_t calc_slot(wf::point_t point) { auto g = output->workarea->get_workarea(); if (!(output->get_relative_geometry() & point)) { return wf::grid::SLOT_NONE; } int threshold = snap_threshold; bool is_left = point.x - g.x <= threshold; bool is_right = g.x + g.width - point.x <= threshold; bool is_top = point.y - g.y < threshold; bool is_bottom = g.x + g.height - point.y < threshold; bool is_far_left = point.x - g.x <= quarter_snap_threshold; bool is_far_right = g.x + g.width - point.x <= quarter_snap_threshold; bool is_far_top = point.y - g.y < quarter_snap_threshold; bool is_far_bottom = g.x + g.height - point.y < quarter_snap_threshold; wf::grid::slot_t slot = wf::grid::SLOT_NONE; if ((is_left && is_far_top) || (is_far_left && is_top)) { slot = wf::grid::SLOT_TL; } else if ((is_right && is_far_top) || (is_far_right && is_top)) { slot = wf::grid::SLOT_TR; } else if ((is_right && is_far_bottom) || (is_far_right && is_bottom)) { slot = wf::grid::SLOT_BR; } else if ((is_left && is_far_bottom) || (is_far_left && is_bottom)) { slot = wf::grid::SLOT_BL; } else if (is_right) { slot = wf::grid::SLOT_RIGHT; } else if (is_left) { slot = wf::grid::SLOT_LEFT; } else if (is_top) { // Maximize when dragging to the top slot = wf::grid::SLOT_CENTER; } else if (is_bottom) { slot = wf::grid::SLOT_BOTTOM; } return slot; } void update_workspace_switch_timeout(wf::grid::slot_t slot_id) { if ((workspace_switch_after == -1) || (slot_id == wf::grid::SLOT_NONE)) { workspace_switch_timer.disconnect(); return; } int dx = 0, dy = 0; if (slot_id >= 7) { dy = -1; } if (slot_id <= 3) { dy = 1; } if (slot_id % 3 == 1) { dx = -1; } if (slot_id % 3 == 0) { dx = 1; } if ((dx == 0) && (dy == 0)) { workspace_switch_timer.disconnect(); return; } wf::point_t cws = output->wset()->get_current_workspace(); wf::point_t tws = {cws.x + dx, cws.y + dy}; wf::dimensions_t ws_dim = output->wset()->get_workspace_grid_size(); wf::geometry_t possible = { 0, 0, ws_dim.width, ws_dim.height }; /* Outside of workspace grid */ if (!(possible & tws)) { workspace_switch_timer.disconnect(); return; } workspace_switch_timer.set_timeout(workspace_switch_after, [this, tws] () { output->wset()->request_workspace(tws); }); } void update_slot(wf::grid::slot_t new_slot_id) { /* No changes in the slot, just return */ if (slot.slot_id == new_slot_id) { return; } /* Destroy previous preview */ if (slot.preview) { auto input = get_input_coords(); slot.preview->set_target_geometry({input.x, input.y, 1, 1}, 0, true); slot.preview = nullptr; } slot.slot_id = new_slot_id; /* Show a preview overlay */ if (new_slot_id) { wf::geometry_t slot_geometry = wf::grid::get_slot_dimensions(output, new_slot_id); /* Unknown slot geometry, can't show a preview */ if ((slot_geometry.width <= 0) || (slot_geometry.height <= 0)) { return; } auto input = get_input_coords(); slot.preview = std::make_shared( wf::geometry_t{input.x, input.y, 1, 1}, output, "move"); slot.preview->set_target_geometry(slot_geometry, 1); } update_workspace_switch_timeout(new_slot_id); } /* Returns the currently used input coordinates in global compositor space */ wf::point_t get_global_input_coords() { wf::pointf_t input; if (wf::get_core().get_touch_state().fingers.empty()) { input = wf::get_core().get_cursor_position(); } else { auto center = wf::get_core().get_touch_state().get_center().current; input = {center.x, center.y}; } return {(int)input.x, (int)input.y}; } /* Returns the currently used input coordinates in output-local space */ wf::point_t get_input_coords() { auto og = output->get_layout_geometry(); auto coords = get_global_input_coords() - wf::point_t{og.x, og.y}; return coords; } bool is_snap_enabled() { if (!enable_snap || !drag_helper->view || drag_helper->is_view_held_in_place()) { return false; } // Make sure that fullscreen views are not tiled. // We allow movement of fullscreen views but they should always // retain their fullscreen state (but they can be moved to other // workspaces). Unsetting the fullscreen state can break some // Xwayland games. if (drag_helper->view->pending_fullscreen()) { return false; } if (drag_helper->view->role == wf::VIEW_ROLE_DESKTOP_ENVIRONMENT) { return false; } return true; } void handle_input_motion() { drag_helper->handle_motion(get_global_input_coords()); if (is_snap_enabled()) { update_slot(calc_slot(get_input_coords())); } } void fini() override { if (input_grab->is_grabbed()) { input_pressed(WLR_BUTTON_RELEASED); } output->rem_binding(&activate_binding); } }; DECLARE_WAYFIRE_PLUGIN(wf::per_output_plugin_t); wayfire-0.8.1/plugins/single_plugins/oswitch.cpp000066400000000000000000000072531457431457600221040ustar00rootroot00000000000000#include "wayfire/plugin.hpp" #include "wayfire/toplevel-view.hpp" #include #include #include #include #include #include class wayfire_oswitch : public wf::plugin_interface_t { wf::wl_idle_call idle_switch_output; wf::output_t *get_output_relative(int step) { /* get the target output n steps after current output * if current output's index is i, and if there're n monitors * then return the (i + step) mod n th monitor */ auto current_output = wf::get_core().seat->get_active_output(); auto os = wf::get_core().output_layout->get_outputs(); auto it = std::find(os.begin(), os.end(), current_output); if (it == os.end()) { LOGI("Current output not found in output list"); return current_output; } int size = os.size(); int current_index = it - os.begin(); int target_index = ((current_index + step) % size + size) % size; return os[target_index]; } void switch_to_output(wf::output_t *target_output) { /* when we switch the output, the oswitch keybinding * may be activated for the next output, which we don't want, * so we postpone the switch */ idle_switch_output.run_once([=] () { wf::get_core().seat->focus_output(target_output); target_output->ensure_pointer(true); }); } 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); if (view) { move_view_to_output(view, target_output, true); } switch_to_output(target_output); } wf::activator_callback next_output = [=] (auto) { auto target_output = get_output_relative(1); switch_to_output(target_output); return true; }; wf::activator_callback next_output_with_window = [=] (auto) { auto target_output = get_output_relative(1); switch_to_output_with_window(target_output); return true; }; wf::activator_callback prev_output = [=] (auto) { auto target_output = get_output_relative(-1); switch_to_output(target_output); return true; }; wf::activator_callback prev_output_with_window = [=] (auto) { auto target_output = get_output_relative(-1); switch_to_output_with_window(target_output); return true; }; public: void init() { auto& bindings = wf::get_core().bindings; bindings->add_activator(wf::option_wrapper_t{"oswitch/next_output"}, &next_output); bindings->add_activator(wf::option_wrapper_t{"oswitch/next_output_with_win"}, &next_output_with_window); bindings->add_activator(wf::option_wrapper_t{"oswitch/prev_output"}, &prev_output); bindings->add_activator(wf::option_wrapper_t{"oswitch/prev_output_with_win"}, &prev_output_with_window); } void fini() { auto& bindings = wf::get_core().bindings; bindings->rem_binding(&next_output); bindings->rem_binding(&next_output_with_window); bindings->rem_binding(&prev_output); bindings->rem_binding(&prev_output_with_window); idle_switch_output.disconnect(); } }; DECLARE_WAYFIRE_PLUGIN(wayfire_oswitch); wayfire-0.8.1/plugins/single_plugins/place.cpp000066400000000000000000000072301457431457600215030ustar00rootroot00000000000000#include "wayfire/signal-provider.hpp" #include "wayfire/toplevel-view.hpp" #include #include #include #include #include #include class wayfire_place_window : public wf::per_output_plugin_instance_t { wf::signal::connection_t on_view_mapped = [=] (wf::view_mapped_signal *ev) { auto toplevel = wf::toplevel_cast(ev->view); if (!toplevel || toplevel->parent || toplevel->pending_fullscreen() || toplevel->pending_tiled_edges() || ev->is_positioned) { return; } ev->is_positioned = true; auto workarea = output->workarea->get_workarea(); std::string mode = placement_mode; if (mode == "cascade") { cascade(toplevel, workarea); } else if (mode == "maximize") { maximize(toplevel, workarea); } else if (mode == "random") { random(toplevel, workarea); } else { center(toplevel, workarea); } }; wf::signal::connection_t workarea_changed_cb = [=] (auto) { auto workarea = output->workarea->get_workarea(); if ((cascade_x < workarea.x) || (cascade_x > workarea.x + workarea.width)) { cascade_x = workarea.x; } if ((cascade_y < workarea.y) || (cascade_y > workarea.y + workarea.height)) { cascade_y = workarea.y; } }; wf::option_wrapper_t placement_mode{"place/mode"}; int cascade_x, cascade_y; public: void init() override { auto workarea = output->workarea->get_workarea(); cascade_x = workarea.x; cascade_y = workarea.y; output->connect(&workarea_changed_cb); output->connect(&on_view_mapped); } void cascade(wayfire_toplevel_view & view, wf::geometry_t workarea) { wf::geometry_t window = view->get_pending_geometry(); if ((cascade_x + window.width > workarea.x + workarea.width) || (cascade_y + window.height > workarea.y + workarea.height)) { cascade_x = workarea.x; cascade_y = workarea.y; } view->move(cascade_x, cascade_y); cascade_x += workarea.width * .03; cascade_y += workarea.height * .03; } void random(wayfire_toplevel_view & view, wf::geometry_t workarea) { wf::geometry_t window = view->get_pending_geometry(); wf::geometry_t area; int pos_x, pos_y; area.x = workarea.x; area.y = workarea.y; area.width = workarea.width - window.width; area.height = workarea.height - window.height; if ((area.width <= 0) || (area.height <= 0)) { center(view, workarea); return; } pos_x = rand() % area.width + area.x; pos_y = rand() % area.height + area.y; view->move(pos_x, pos_y); } void center(wayfire_toplevel_view & view, wf::geometry_t workarea) { wf::geometry_t window = view->get_pending_geometry(); window.x = workarea.x + (workarea.width / 2) - (window.width / 2); window.y = workarea.y + (workarea.height / 2) - (window.height / 2); view->move(window.x, window.y); } void maximize(wayfire_toplevel_view & view, wf::geometry_t workarea) { wf::get_core().default_wm->tile_request(view, wf::TILED_EDGES_ALL); } }; DECLARE_WAYFIRE_PLUGIN(wf::per_output_plugin_t); wayfire-0.8.1/plugins/single_plugins/preserve-output.cpp000066400000000000000000000101141457431457600236030ustar00rootroot00000000000000#include "wayfire/core.hpp" #include "wayfire/plugin.hpp" #include #include #include #include #include #include #include namespace wf { namespace preserve_output { static std::string make_output_identifier(wf::output_t *output) { std::string identifier = ""; identifier += nonull(output->handle->make); identifier += "|"; identifier += nonull(output->handle->model); identifier += "|"; identifier += nonull(output->handle->serial); return identifier; } struct per_output_state_t { std::shared_ptr workspace_set; std::chrono::time_point destroy_timestamp; bool was_focused = false; }; class preserve_output_t : public wf::plugin_interface_t { wf::option_wrapper_t last_output_focus_timeout{"preserve-output/last_output_focus_timeout"}; std::map saved_outputs; bool focused_output_expired(const per_output_state_t& state) const { using namespace std::chrono; const auto now = steady_clock::now(); const auto elapsed_since_focus = duration_cast(now - state.destroy_timestamp).count(); return elapsed_since_focus > last_output_focus_timeout; } void save_output(wf::output_t *output) { auto ident = make_output_identifier(output); auto& data = saved_outputs[ident]; data.was_focused = (output == wf::get_core().seat->get_active_output()); data.destroy_timestamp = std::chrono::steady_clock::now(); data.workspace_set = output->wset(); LOGD("Saving workspace set ", data.workspace_set->get_index(), " from output ", output->to_string(), " with identifier ", ident); // Set a dummy workspace set with no views at all. output->set_workspace_set(wf::workspace_set_t::create()); // Detach workspace set from its old output data.workspace_set->attach_to_output(nullptr); } void try_restore_output(wf::output_t *output) { std::string ident = make_output_identifier(output); if (!saved_outputs.count(ident)) { LOGD("No saved identifier for ", output->to_string()); return; } auto& data = saved_outputs[ident]; auto new_output = data.workspace_set->get_attached_output(); if (new_output && (new_output->wset() == data.workspace_set)) { // The wset was moved to a different output => We should leave it where it is LOGD("Saved workspace for ", output->to_string(), " has been remapped to another output."); return; } LOGD("Restoring workspace set ", data.workspace_set->get_index(), " to output ", output->to_string()); output->set_workspace_set(data.workspace_set); if (data.was_focused && !focused_output_expired(data)) { wf::get_core().seat->focus_output(output); } saved_outputs.erase(ident); } wf::signal::connection_t output_pre_remove = [=] (output_pre_remove_signal *ev) { if (wlr_output_is_headless(ev->output->handle)) { // For example, NOOP-1 return; } if (wf::get_core().get_current_state() == compositor_state_t::RUNNING) { LOGD("Received pre-remove event: ", ev->output->to_string()); save_output(ev->output); } }; wf::signal::connection_t on_new_output = [=] (output_added_signal *ev) { if (wlr_output_is_headless(ev->output->handle)) { // For example, NOOP-1 return; } try_restore_output(ev->output); }; public: void init() override { wf::get_core().output_layout->connect(&on_new_output); wf::get_core().output_layout->connect(&output_pre_remove); } }; } } DECLARE_WAYFIRE_PLUGIN(wf::preserve_output::preserve_output_t); wayfire-0.8.1/plugins/single_plugins/resize.cpp000066400000000000000000000234611457431457600217240ustar00rootroot00000000000000#include "wayfire/geometry.hpp" #include "wayfire/plugins/common/input-grab.hpp" #include "wayfire/scene-input.hpp" #include "wayfire/txn/transaction-manager.hpp" #include #include #include #include #include #include #include #include #include #include #include #include class wayfire_resize : public wf::per_output_plugin_instance_t, public wf::pointer_interaction_t, public wf::touch_interaction_t { wf::signal::connection_t on_resize_request = [=] (wf::view_resize_request_signal *request) { if (!request->view) { return; } auto touch = wf::get_core().get_touch_position(0); if (!std::isnan(touch.x) && !std::isnan(touch.y)) { is_using_touch = true; } else { is_using_touch = false; } was_client_request = true; preserve_aspect = false; initiate(request->view, request->edges); }; wf::signal::connection_t on_view_disappeared = [=] (wf::view_disappeared_signal *ev) { if (ev->view == view) { view = nullptr; input_pressed(WLR_BUTTON_RELEASED); } }; wf::button_callback activate_binding; wf::button_callback activate_binding_preserve_aspect; wayfire_toplevel_view view; bool was_client_request, is_using_touch; bool preserve_aspect = false; wf::point_t grab_start; wf::geometry_t grabbed_geometry; uint32_t edges; wf::option_wrapper_t button{"resize/activate"}; wf::option_wrapper_t button_preserve_aspect{ "resize/activate_preserve_aspect"}; std::unique_ptr input_grab; wf::plugin_activation_data_t grab_interface = { .name = "resize", .capabilities = wf::CAPABILITY_GRAB_INPUT | wf::CAPABILITY_MANAGE_DESKTOP, }; public: void init() override { input_grab = std::make_unique("resize", output, nullptr, this, this); activate_binding = [=] (auto) { return activate(false); }; activate_binding_preserve_aspect = [=] (auto) { return activate(true); }; output->add_button(button, &activate_binding); output->add_button(button_preserve_aspect, &activate_binding_preserve_aspect); grab_interface.cancel = [=] () { input_pressed(WLR_BUTTON_RELEASED); }; output->connect(&on_resize_request); output->connect(&on_view_disappeared); } bool activate(bool should_preserve_aspect) { auto view = toplevel_cast(wf::get_core().get_cursor_focus_view()); if (view) { is_using_touch = false; was_client_request = false; preserve_aspect = should_preserve_aspect; initiate(view); } return false; } void handle_pointer_button(const wlr_pointer_button_event& event) override { if ((event.state == WLR_BUTTON_RELEASED) && was_client_request && (event.button == BTN_LEFT)) { return input_pressed(event.state); } if ((event.button != wf::buttonbinding_t(button).get_button()) && (event.button != wf::buttonbinding_t(button_preserve_aspect).get_button())) { return; } input_pressed(event.state); } void handle_pointer_motion(wf::pointf_t pointer_position, uint32_t time_ms) override { input_motion(); } void handle_touch_up(uint32_t time_ms, int finger_id, wf::pointf_t lift_off_position) override { if (finger_id == 0) { input_pressed(WLR_BUTTON_RELEASED); } } void handle_touch_motion(uint32_t time_ms, int finger_id, wf::pointf_t position) override { if (finger_id == 0) { input_motion(); } } /* Returns the currently used input coordinates in global compositor space */ wf::point_t get_global_input_coords() { wf::pointf_t input; if (is_using_touch) { input = wf::get_core().get_touch_position(0); } else { input = wf::get_core().get_cursor_position(); } return {(int)input.x, (int)input.y}; } /* Returns the currently used input coordinates in output-local space */ wf::point_t get_input_coords() { auto og = output->get_layout_geometry(); return get_global_input_coords() - wf::point_t{og.x, og.y}; } /* Calculate resize edges, grab starts at (sx, sy), view's geometry is vg */ uint32_t calculate_edges(wf::geometry_t vg, int sx, int sy) { int view_x = sx - vg.x; int view_y = sy - vg.y; uint32_t edges = 0; if (view_x < vg.width / 2) { edges |= WLR_EDGE_LEFT; } else { edges |= WLR_EDGE_RIGHT; } if (view_y < vg.height / 2) { edges |= WLR_EDGE_TOP; } else { edges |= WLR_EDGE_BOTTOM; } return edges; } bool initiate(wayfire_toplevel_view view, uint32_t forced_edges = 0) { if (!view || (view->role == wf::VIEW_ROLE_DESKTOP_ENVIRONMENT) || !view->is_mapped() || view->pending_fullscreen()) { return false; } this->edges = forced_edges ?: calculate_edges(view->get_bounding_box(), get_input_coords().x, get_input_coords().y); if ((edges == 0) || !(view->get_allowed_actions() & wf::VIEW_ALLOW_RESIZE)) { return false; } if (!output->activate_plugin(&grab_interface)) { return false; } input_grab->set_wants_raw_input(true); input_grab->grab_input(wf::scene::layer::OVERLAY); grab_start = get_input_coords(); grabbed_geometry = view->get_geometry(); if (view->pending_tiled_edges()) { view->toplevel()->pending().tiled_edges = 0; } this->view = view; auto og = view->get_bounding_box(); int anchor_x = og.x; int anchor_y = og.y; if (edges & WLR_EDGE_LEFT) { anchor_x += og.width; } if (edges & WLR_EDGE_TOP) { anchor_y += og.height; } start_wobbly(view, anchor_x, anchor_y); wf::get_core().set_cursor(wlr_xcursor_get_resize_name((wlr_edges)edges)); return true; } void input_pressed(uint32_t state) { if (state != WLR_BUTTON_RELEASED) { return; } input_grab->ungrab_input(); output->deactivate_plugin(&grab_interface); if (view) { end_wobbly(view); wf::view_change_workspace_signal workspace_may_changed; workspace_may_changed.view = this->view; workspace_may_changed.to = output->wset()->get_current_workspace(); workspace_may_changed.old_workspace_valid = false; output->emit(&workspace_may_changed); } } // Convert resize edges to gravity uint32_t calculate_gravity() { uint32_t gravity = 0; if (edges & WLR_EDGE_LEFT) { gravity |= WLR_EDGE_RIGHT; } if (edges & WLR_EDGE_RIGHT) { gravity |= WLR_EDGE_LEFT; } if (edges & WLR_EDGE_TOP) { gravity |= WLR_EDGE_BOTTOM; } if (edges & WLR_EDGE_BOTTOM) { gravity |= WLR_EDGE_TOP; } return gravity; } void input_motion() { auto input = get_input_coords(); int dx = input.x - grab_start.x; int dy = input.y - grab_start.y; wf::geometry_t desired = grabbed_geometry; double ratio; if (preserve_aspect) { ratio = (double)desired.width / desired.height; } if (edges & WLR_EDGE_LEFT) { desired.x += dx; desired.width -= dx; } else if (edges & WLR_EDGE_RIGHT) { desired.width += dx; } if (edges & WLR_EDGE_TOP) { desired.y += dy; desired.height -= dy; } else if (edges & WLR_EDGE_BOTTOM) { desired.height += dy; } if (preserve_aspect) { auto bbox = desired; desired.width = std::min(std::max(bbox.width, 1), (int)(bbox.height * ratio)); desired.height = std::min(std::max(bbox.height, 1), (int)(bbox.width / ratio)); if (edges & WLR_EDGE_LEFT) { desired.x += bbox.width - desired.width; } if (edges & WLR_EDGE_TOP) { desired.y += bbox.height - desired.height; } } else { desired.width = std::max(desired.width, 1); desired.height = std::max(desired.height, 1); } view->toplevel()->pending().gravity = calculate_gravity(); view->toplevel()->pending().geometry = desired; wf::get_core().tx_manager->schedule_object(view->toplevel()); } void fini() override { if (input_grab->is_grabbed()) { input_pressed(WLR_BUTTON_RELEASED); } output->rem_binding(&activate_binding); output->rem_binding(&activate_binding_preserve_aspect); } }; DECLARE_WAYFIRE_PLUGIN(wf::per_output_plugin_t); wayfire-0.8.1/plugins/single_plugins/switcher.cpp000066400000000000000000000655671457431457600222700ustar00rootroot00000000000000#include "wayfire/object.hpp" #include "wayfire/plugins/common/input-grab.hpp" #include "wayfire/scene-input.hpp" #include "wayfire/scene-operations.hpp" #include "wayfire/scene-render.hpp" #include "wayfire/scene.hpp" #include "wayfire/view-helpers.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include constexpr const char *switcher_transformer = "switcher-3d"; constexpr const char *switcher_transformer_background = "switcher-3d"; constexpr float background_dim_factor = 0.6; using namespace wf::animation; class SwitcherPaintAttribs { public: SwitcherPaintAttribs(const duration_t& duration) : scale_x(duration, 1, 1), scale_y(duration, 1, 1), off_x(duration, 0, 0), off_y(duration, 0, 0), off_z(duration, 0, 0), rotation(duration, 0, 0), alpha(duration, 1, 1) {} timed_transition_t scale_x, scale_y; timed_transition_t off_x, off_y, off_z; timed_transition_t rotation, alpha; }; enum SwitcherViewPosition { SWITCHER_POSITION_LEFT = 0, SWITCHER_POSITION_CENTER = 1, SWITCHER_POSITION_RIGHT = 2, }; static constexpr bool view_expired(int view_position) { return view_position < SWITCHER_POSITION_LEFT || view_position > SWITCHER_POSITION_RIGHT; } struct SwitcherView { wayfire_toplevel_view view; SwitcherPaintAttribs attribs; int position; SwitcherView(duration_t& duration) : attribs(duration) {} /* Make animation start values the current progress of duration */ void refresh_start() { for_each([] (timed_transition_t& t) { t.restart_same_end(); }); } void to_end() { for_each([] (timed_transition_t& t) { t.set(t.end, t.end); }); } private: void for_each(std::function call) { call(attribs.off_x); call(attribs.off_y); call(attribs.off_z); call(attribs.scale_x); call(attribs.scale_y); call(attribs.alpha); call(attribs.rotation); } }; class WayfireSwitcher : public wf::per_output_plugin_instance_t, public wf::keyboard_interaction_t { wf::option_wrapper_t view_thumbnail_scale{ "switcher/view_thumbnail_scale"}; wf::option_wrapper_t speed{"switcher/speed"}; wf::option_wrapper_t view_thumbnail_rotation{ "switcher/view_thumbnail_rotation"}; duration_t duration{speed}; duration_t background_dim_duration{speed}; timed_transition_t background_dim{background_dim_duration}; std::unique_ptr input_grab; /* If a view comes before another in this list, it is on top of it */ std::vector views; // the modifiers which were used to activate switcher uint32_t activating_modifiers = 0; bool active = false; class switcher_render_node_t : public wf::scene::node_t { class switcher_render_instance_t : public wf::scene::render_instance_t { switcher_render_node_t *self; wf::scene::damage_callback push_damage; wf::signal::connection_t on_switcher_damage = [=] (wf::scene::node_damage_signal *ev) { push_damage(ev->region); }; public: switcher_render_instance_t(switcher_render_node_t *self, wf::scene::damage_callback push_damage) { this->self = self; this->push_damage = push_damage; self->connect(&on_switcher_damage); } void schedule_instructions( std::vector& instructions, const wf::render_target_t& target, wf::region_t& damage) override { instructions.push_back(wf::scene::render_instruction_t{ .instance = this, .target = target, .damage = damage & self->get_bounding_box(), }); // Don't render anything below auto bbox = self->get_bounding_box(); damage ^= bbox; } void render(const wf::render_target_t& target, const wf::region_t& region, const std::any& tag) override { self->switcher->render(target.translated(-wf::origin(self->get_bounding_box()))); } }; public: switcher_render_node_t(WayfireSwitcher *switcher) : node_t(false) { this->switcher = switcher; } virtual void gen_render_instances( std::vector& instances, wf::scene::damage_callback push_damage, wf::output_t *shown_on) { if (shown_on != this->switcher->output) { return; } instances.push_back(std::make_unique(this, push_damage)); } wf::geometry_t get_bounding_box() { return switcher->output->get_layout_geometry(); } private: WayfireSwitcher *switcher; }; std::shared_ptr render_node; wf::plugin_activation_data_t grab_interface = { .name = "switcher", .capabilities = wf::CAPABILITY_MANAGE_COMPOSITOR, }; public: void init() override { output->add_key( wf::option_wrapper_t{"switcher/next_view"}, &next_view_binding); output->add_key( wf::option_wrapper_t{"switcher/prev_view"}, &prev_view_binding); output->connect(&view_disappeared); input_grab = std::make_unique("switcher", output, this, nullptr, nullptr); grab_interface.cancel = [=] () {deinit_switcher();}; } void handle_keyboard_key(wf::seat_t*, wlr_keyboard_key_event event) override { auto mod = wf::get_core().seat->modifier_from_keycode(event.keycode); if ((event.state == WLR_KEY_RELEASED) && (mod & activating_modifiers)) { handle_done(); } } wf::key_callback next_view_binding = [=] (auto) { handle_switch_request(-1); return false; }; wf::key_callback prev_view_binding = [=] (auto) { handle_switch_request(1); return false; }; wf::effect_hook_t pre_hook = [=] () { dim_background(background_dim); wf::scene::damage_node(render_node, render_node->get_bounding_box()); if (!duration.running()) { cleanup_expired(); if (!active) { deinit_switcher(); } } }; wf::signal::connection_t view_disappeared = [=] (wf::view_disappeared_signal *ev) { if (auto toplevel = toplevel_cast(ev->view)) { handle_view_removed(toplevel); } }; void handle_view_removed(wayfire_toplevel_view view) { // not running at all, don't care if (!output->is_plugin_active(grab_interface.name)) { return; } bool need_action = false; for (auto& sv : views) { need_action |= (sv.view == view); } // don't do anything if we're not using this view if (!need_action) { return; } if (active) { arrange(); } else { cleanup_views([=] (SwitcherView& sv) { return sv.view == view; }); } } bool handle_switch_request(int dir) { if (get_workspace_views().empty()) { return false; } /* If we haven't grabbed, then we haven't setup anything */ if (!output->is_plugin_active(grab_interface.name)) { if (!init_switcher()) { return false; } } /* Maybe we're still animating the exit animation from a previous * switcher activation? */ if (!active) { active = true; input_grab->grab_input(wf::scene::layer::OVERLAY); focus_next(dir); arrange(); activating_modifiers = wf::get_core().seat->get_keyboard_modifiers(); } else { next_view(dir); } return true; } /* When switcher is done and starts animating towards end */ void handle_done() { cleanup_expired(); dearrange(); input_grab->ungrab_input(); } /* Sets up basic hooks needed while switcher works and/or displays animations. * Also lower any fullscreen views that are active */ bool init_switcher() { if (!output->activate_plugin(&grab_interface)) { return false; } output->render->add_effect(&pre_hook, wf::OUTPUT_EFFECT_PRE); render_node = std::make_shared(this); wf::scene::add_front(wf::get_core().scene(), render_node); return true; } /* The reverse of init_switcher */ void deinit_switcher() { output->deactivate_plugin(&grab_interface); output->render->rem_effect(&pre_hook); wf::scene::remove_child(render_node); render_node = nullptr; for (auto& view : output->wset()->get_views()) { if (view->has_data("switcher-minimized-showed")) { view->erase_data("switcher-minimized-showed"); wf::scene::set_node_enabled(view->get_root_node(), false); } view->get_transformed_node()->rem_transformer(switcher_transformer); view->get_transformed_node()->rem_transformer( switcher_transformer_background); } views.clear(); wf::scene::update(wf::get_core().scene(), wf::scene::update_flag::INPUT_STATE); } /* offset from the left or from the right */ float get_center_offset() { return output->get_relative_geometry().width / 3.0; } /* get the scale for non-focused views */ float get_back_scale() { return 0.66; } /* offset in Z-direction for non-focused views */ float get_z_offset() { return -1.0; } /* Move view animation target to the left * @param dir -1 for left, 1 for right */ void move(SwitcherView& sv, int dir) { sv.attribs.off_x.restart_with_end( sv.attribs.off_x.end + get_center_offset() * dir); sv.attribs.off_y.restart_same_end(); float z_sign = 0; if (sv.position == SWITCHER_POSITION_CENTER) { // Move from center to either left or right, so backwards z_sign = 1; } else if (view_expired(sv.position + dir)) { // Expires, don't move z_sign = 0; } else { // Not from center, doesn't expire -> comes to the center z_sign = -1; } sv.attribs.off_z.restart_with_end( sv.attribs.off_z.end + get_z_offset() * z_sign); /* scale views that aren't in the center */ sv.attribs.scale_x.restart_with_end( sv.attribs.scale_x.end * std::pow(get_back_scale(), z_sign)); sv.attribs.scale_y.restart_with_end( sv.attribs.scale_y.end * std::pow(get_back_scale(), z_sign)); float radians_rotation = -((float)view_thumbnail_rotation * (M_PI / 180.0)); sv.attribs.rotation.restart_with_end( sv.attribs.rotation.end + radians_rotation * dir); sv.position += dir; sv.attribs.alpha.restart_with_end( view_expired(sv.position) ? 0.3 : 1.0); } /* Calculate how much a view should be scaled to fit into the slots */ float calculate_scaling_factor(const wf::geometry_t& bbox) const { /* Each view should not be more than this percentage of the * width/height of the output */ constexpr float screen_percentage = 0.45; auto og = output->get_relative_geometry(); float max_width = og.width * screen_percentage; float max_height = og.height * screen_percentage; float needed_exact = std::min(max_width / bbox.width, max_height / bbox.height); // don't scale down if the view is already small enough return std::min(needed_exact, 1.0f) * view_thumbnail_scale; } /* Calculate alpha for the view when switcher is inactive. */ float get_view_normal_alpha(wayfire_toplevel_view view) { /* Usually views are visible, but if they were minimized, * and we aren't restoring the view, it has target alpha 0.0 */ if (view->minimized && (views.empty() || (view != views[0].view))) { return 0.0; } return 1.0; } /* Move untransformed view to the center */ void arrange_center_view(SwitcherView& sv) { auto og = output->get_relative_geometry(); auto bbox = wf::view_bounding_box_up_to(sv.view, switcher_transformer); float dx = (og.width / 2.0 - bbox.width / 2.0) - bbox.x; float dy = bbox.y - (og.height / 2.0 - bbox.height / 2.0); sv.attribs.off_x.set(0, dx); sv.attribs.off_y.set(0, dy); float scale = calculate_scaling_factor(bbox); sv.attribs.scale_x.set(1, scale); sv.attribs.scale_y.set(1, scale); sv.attribs.alpha.set(get_view_normal_alpha(sv.view), 1.0); } /* Position the view, starting from untransformed position */ void arrange_view(SwitcherView& sv, int position) { arrange_center_view(sv); if (position == SWITCHER_POSITION_CENTER) { /* view already centered */ } else { move(sv, position - SWITCHER_POSITION_CENTER); } } // returns a list of mapped views std::vector get_workspace_views() const { return output->wset()->get_views(wf::WSET_MAPPED_ONLY | wf::WSET_CURRENT_WORKSPACE); } /* Change the current focus to the next or the previous view */ void focus_next(int dir) { auto ws_views = get_workspace_views(); /* Change the focused view and rearrange views so that focused is on top */ int size = ws_views.size(); // calculate focus index & focus it int focused_view_index = (size + dir) % size; auto focused_view = ws_views[focused_view_index]; wf::view_bring_to_front(focused_view); } /* Create the initial arrangement on the screen * Also changes the focus to the next or the last view, depending on dir */ void arrange() { // clear views in case that deinit() hasn't been run views.clear(); duration.start(); background_dim.set(1, background_dim_factor); background_dim_duration.start(); auto ws_views = get_workspace_views(); for (auto v : ws_views) { views.push_back(create_switcher_view(v)); } std::sort(views.begin(), views.end(), [] (SwitcherView& a, SwitcherView& b) { return wf::get_focus_timestamp(a.view) > wf::get_focus_timestamp(b.view); }); /* Add a copy of the unfocused view if we have just 2 */ if (ws_views.size() == 2) { views.push_back(create_switcher_view(ws_views.back())); } arrange_view(views[0], SWITCHER_POSITION_CENTER); /* If we have just 1 view, don't do anything else */ if (ws_views.size() > 1) { arrange_view(views.back(), SWITCHER_POSITION_LEFT); } for (int i = 1; i < (int)views.size() - 1; i++) { arrange_view(views[i], SWITCHER_POSITION_RIGHT); } // We want the next view to be focused right off the bat // But we want it to be animated. handle_switch_request(-1); } void dearrange() { /* When we have just 2 views on the workspace, we have 2 copies * of the unfocused view. When dearranging those copies, they overlap. * If the view is translucent, this means that the view gets darker than * it really is. * To circumvent this, we just fade out one of the copies */ wayfire_toplevel_view fading_view = nullptr; if (count_different_active_views() == 2) { fading_view = get_unfocused_view(); } for (auto& sv : views) { sv.attribs.off_x.restart_with_end(0); sv.attribs.off_y.restart_with_end(0); sv.attribs.off_z.restart_with_end(0); sv.attribs.scale_x.restart_with_end(1.0); sv.attribs.scale_y.restart_with_end(1.0); sv.attribs.rotation.restart_with_end(0); sv.attribs.alpha.restart_with_end(get_view_normal_alpha(sv.view)); if (sv.view == fading_view) { sv.attribs.alpha.end = 0.0; // make sure we don't fade out the other unfocused view instance as // well fading_view = nullptr; } } background_dim.restart_with_end(1); background_dim_duration.start(); duration.start(); active = false; /* Potentially restore view[0] if it was maximized */ if (views.size()) { wf::get_core().default_wm->focus_raise_view(views[0].view); } } std::vector get_background_views() const { return wf::collect_views_from_output(output, {wf::scene::layer::BACKGROUND, wf::scene::layer::BOTTOM}); } std::vector get_overlay_views() const { return wf::collect_views_from_output(output, {wf::scene::layer::TOP, wf::scene::layer::OVERLAY, wf::scene::layer::DWIDGET}); } void dim_background(float dim) { for (auto view : get_background_views()) { if (dim == 1.0) { view->get_transformed_node()->rem_transformer( switcher_transformer_background); } else { auto tr = wf::ensure_named_transformer( view, wf::TRANSFORMER_3D, switcher_transformer_background, view); tr->color[0] = tr->color[1] = tr->color[2] = dim; } } } SwitcherView create_switcher_view(wayfire_toplevel_view view) { /* we add a view transform if there isn't any. * * Note that a view might be visible on more than 1 place, so damage * tracking doesn't work reliably. To circumvent this, we simply damage * the whole output */ if (!view->get_transformed_node()->get_transformer(switcher_transformer)) { if (view->minimized) { wf::scene::set_node_enabled(view->get_root_node(), true); view->store_data(std::make_unique(), "switcher-minimized-showed"); } view->get_transformed_node()->add_transformer( std::make_shared(view), wf::TRANSFORMER_3D, switcher_transformer); } SwitcherView sw{duration}; sw.view = view; sw.position = SWITCHER_POSITION_CENTER; return sw; } void render_view_scene(wayfire_view view, const wf::render_target_t& buffer) { std::vector instances; view->get_transformed_node()->gen_render_instances(instances, [] (auto) {}); wf::scene::render_pass_params_t params; params.instances = &instances; params.damage = view->get_transformed_node()->get_bounding_box(); params.reference_output = this->output; params.target = buffer; wf::scene::run_render_pass(params, 0); } void render_view(const SwitcherView& sv, const wf::render_target_t& buffer) { auto transform = sv.view->get_transformed_node() ->get_transformer(switcher_transformer); assert(transform); transform->translation = glm::translate(glm::mat4(1.0), {(double)sv.attribs.off_x, (double)sv.attribs.off_y, (double)sv.attribs.off_z}); transform->scaling = glm::scale(glm::mat4(1.0), {(double)sv.attribs.scale_x, (double)sv.attribs.scale_y, 1.0}); transform->rotation = glm::rotate(glm::mat4(1.0), (float)sv.attribs.rotation, {0.0, 1.0, 0.0}); transform->color[3] = sv.attribs.alpha; render_view_scene(sv.view, buffer); } void render(const wf::render_target_t& fb) { OpenGL::render_begin(fb); OpenGL::clear({0, 0, 0, 1}); OpenGL::render_end(); for (auto view : get_background_views()) { render_view_scene(view, fb); } /* Render in the reverse order because we don't use depth testing */ for (auto& view : wf::reverse(views)) { render_view(view, fb); } for (auto view : get_overlay_views()) { render_view_scene(view, fb); } } /* delete all views matching the given criteria, skipping the first "start" views * */ void cleanup_views(std::function criteria) { auto it = views.begin(); while (it != views.end()) { if (criteria(*it)) { it = views.erase(it); } else { ++it; } } } /* Removes all expired views from the list */ void cleanup_expired() { cleanup_views([=] (SwitcherView& sv) { return view_expired(sv.position); }); } /* sort views according to their Z-order */ void rebuild_view_list() { std::stable_sort(views.begin(), views.end(), [] (const SwitcherView& a, const SwitcherView& b) { enum category { FOCUSED = 0, UNFOCUSED = 1, EXPIRED = 2, }; auto view_category = [] (const SwitcherView& sv) { if (sv.position == SWITCHER_POSITION_CENTER) { return FOCUSED; } if (view_expired(sv.position)) { return EXPIRED; } return UNFOCUSED; }; category aCat = view_category(a), bCat = view_category(b); if (aCat == bCat) { return a.position < b.position; } else { return aCat < bCat; } }); } void next_view(int dir) { cleanup_expired(); if (count_different_active_views() <= 1) { return; } /* Count of views in the left/right slots */ int count_right = 0; int count_left = 0; /* Move the topmost view from the center and the left/right group, * depending on the direction*/ int to_move = (1 << SWITCHER_POSITION_CENTER) | (1 << (1 - dir)); for (auto& sv : views) { if (!view_expired(sv.position) && ((1 << sv.position) & to_move)) { to_move ^= (1 << sv.position); // only the topmost one move(sv, dir); } else if (!view_expired(sv.position)) { /* Make sure animations start from where we are now */ sv.refresh_start(); } count_left += (sv.position == SWITCHER_POSITION_LEFT); count_right += (sv.position == SWITCHER_POSITION_RIGHT); } /* Create a new view on the missing slot, but if both are missing, * show just the centered view */ if (bool(count_left) ^ bool(count_right)) { const int empty_slot = 1 - dir; fill_empty_slot(empty_slot); } rebuild_view_list(); wf::view_bring_to_front(views.front().view); duration.start(); } int count_different_active_views() { std::set active_views; for (auto& sv : views) { active_views.insert(sv.view); } return active_views.size(); } /* Move the last view in the given slot so that it becomes invalid */ wayfire_toplevel_view invalidate_last_in_slot(int slot) { for (int i = views.size() - 1; i >= 0; i--) { if (views[i].position == slot) { move(views[i], slot - 1); return views[i].view; } } return nullptr; } /* Returns the non-focused view in the case where there is only 1 view */ wayfire_toplevel_view get_unfocused_view() { for (auto& sv : views) { if (!view_expired(sv.position) && (sv.position != SWITCHER_POSITION_CENTER)) { return sv.view; } } return nullptr; } void fill_empty_slot(const int empty_slot) { const int full_slot = 2 - empty_slot; /* We have an empty slot. We invalidate the bottom-most view in the * opposite slot, and create a new view with the same content to * fill in the empty slot */ auto view_to_create = invalidate_last_in_slot(full_slot); /* special case: we have just 2 views * in this case, the "new" view should not be the same as the * invalidated view(because this view is focused now), but the * one which isn't focused */ if (count_different_active_views() == 2) { view_to_create = get_unfocused_view(); } assert(view_to_create); auto sv = create_switcher_view(view_to_create); arrange_view(sv, empty_slot); /* directly show it on the target position */ sv.to_end(); sv.attribs.alpha.set(0, 1); views.push_back(std::move(sv)); } void fini() override { if (output->is_plugin_active(grab_interface.name)) { deinit_switcher(); } output->rem_binding(&next_view_binding); output->rem_binding(&prev_view_binding); } }; DECLARE_WAYFIRE_PLUGIN(wf::per_output_plugin_t); wayfire-0.8.1/plugins/single_plugins/vswipe-processing.hpp000066400000000000000000000045141457431457600241150ustar00rootroot00000000000000#include #include static inline double vswipe_process_delta(const double delta, const double accumulated_dx, const int vx, const int vw, const double speed_cap = 0.5, const bool free_movement = false) { // The slowdown below must be applied differently for going out of bounds. double sdx_offset = free_movement ? std::copysign(0, accumulated_dx) : accumulated_dx; if (vx - accumulated_dx < 0.0) { sdx_offset = (accumulated_dx - std::floor(accumulated_dx)) + 1.0; } if (vx - accumulated_dx > vw - 1.0) { sdx_offset = (accumulated_dx - std::ceil(accumulated_dx)) - 1.0; } // To achieve a "rubberband" resistance effect when going too far, ease-in // of the whole swiped distance is used as a slowdown factor for the current // delta. const double ease = 1.0 - std::pow(std::abs(sdx_offset) - 0.025, 4.0); // If we're moving further in the limit direction, slow down all the way // to extremely slow, but reversing the direction should be easier. const double slowdown = wf::clamp(ease, std::signbit(delta) == std::signbit(sdx_offset) ? 0.005 : 0.2, 1.0); return wf::clamp(delta, -speed_cap, speed_cap) * slowdown; } static inline int vswipe_finish_target(const double accumulated_dx, const int vx, const int vw, const double last_deltas = 0, const double move_threshold = 0.35, const double fast_threshold = 24, const bool free_movement = false) { int target_dx = 0; if (accumulated_dx > 0) { target_dx = std::floor(accumulated_dx); if ((accumulated_dx - target_dx > move_threshold) || ((!free_movement || !target_dx) && (last_deltas > fast_threshold))) { ++target_dx; } if (vx - target_dx < 0) { target_dx = vx; } } else if (accumulated_dx < 0) { target_dx = std::ceil(accumulated_dx); if ((accumulated_dx - target_dx < -move_threshold) || ((!free_movement || !target_dx) && (last_deltas < -fast_threshold))) { --target_dx; } if (vx - target_dx > vw - 1) { target_dx = vx - vw + 1; } } if (!free_movement) { target_dx = wf::clamp(target_dx, -1, 1); } return target_dx; } wayfire-0.8.1/plugins/single_plugins/vswipe.cpp000066400000000000000000000277331457431457600217460ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "vswipe-processing.hpp" #include "wayfire/plugins/common/input-grab.hpp" #include "wayfire/signal-provider.hpp" using namespace wf::animation; class vswipe_smoothing_t : public duration_t { public: using duration_t::duration_t; timed_transition_t dx{*this}; timed_transition_t dy{*this}; }; static inline wf::geometry_t interpolate(wf::geometry_t a, wf::geometry_t b, double xalpha, double yalpha) { const auto& interp = [=] (int32_t wf::geometry_t::*member, double alpha) -> int32_t { return std::round((1 - alpha) * a.*member + alpha * b.*member); }; return { interp(&wf::geometry_t::x, xalpha), interp(&wf::geometry_t::y, yalpha), interp(&wf::geometry_t::width, xalpha), interp(&wf::geometry_t::height, yalpha) }; } class vswipe : public wf::per_output_plugin_instance_t { private: enum swipe_direction_t { HORIZONTAL = 1, VERTICAL = 2, DIAGONAL = HORIZONTAL | VERTICAL, UNKNOWN = 0, }; struct { bool swiping = false; bool animating = false; swipe_direction_t direction; wf::pointf_t initial_deltas; wf::pointf_t delta_sum; wf::pointf_t delta_prev; wf::pointf_t delta_last; int vx = 0; int vy = 0; int vw = 0; int vh = 0; } state; std::unique_ptr wall; wf::option_wrapper_t enable_horizontal{"vswipe/enable_horizontal"}; wf::option_wrapper_t enable_vertical{"vswipe/enable_vertical"}; wf::option_wrapper_t enable_free_movement{"vswipe/enable_free_movement"}; wf::option_wrapper_t smooth_transition{"vswipe/enable_smooth_transition"}; wf::option_wrapper_t background_color{"vswipe/background"}; wf::option_wrapper_t animation_duration{"vswipe/duration"}; vswipe_smoothing_t smooth_delta{animation_duration}; wf::option_wrapper_t fingers{"vswipe/fingers"}; wf::option_wrapper_t gap{"vswipe/gap"}; wf::option_wrapper_t threshold{"vswipe/threshold"}; wf::option_wrapper_t delta_threshold{"vswipe/delta_threshold"}; wf::option_wrapper_t speed_factor{"vswipe/speed_factor"}; wf::option_wrapper_t speed_cap{"vswipe/speed_cap"}; std::unique_ptr input_grab; wf::plugin_activation_data_t grab_interface = { .name = "vswipe", .capabilities = wf::CAPABILITY_MANAGE_COMPOSITOR, .cancel = [=] () { finalize_and_exit(); }, }; public: void init() override { input_grab = std::make_unique("vswipe", output); wf::get_core().connect(&on_swipe_begin); wf::get_core().connect(&on_swipe_update); wf::get_core().connect(&on_swipe_end); wall = std::make_unique(output); wall->connect(&this->on_frame); } wf::effect_hook_t post_frame = [=] () { if (!smooth_delta.running() && !state.swiping) { finalize_and_exit(); return; } output->render->schedule_redraw(); output->render->damage_whole(); }; wf::signal::connection_t on_frame = [=] (wf::wall_frame_event_t *ev) { wf::point_t current_workspace = {state.vx, state.vy}; int dx = 0, dy = 0; if (state.direction & HORIZONTAL) { dx = 1; } if (state.direction & VERTICAL) { dy = 1; } wf::point_t next_ws = {current_workspace.x + dx, current_workspace.y + dy}; auto g1 = wall->get_workspace_rectangle(current_workspace); auto g2 = wall->get_workspace_rectangle(next_ws); wall->set_viewport(interpolate(g1, g2, -smooth_delta.dx, -smooth_delta.dy)); }; template using event = wf::input_event_signal; wf::signal::connection_t> on_swipe_begin = [=] (event *ev) { if (!enable_horizontal && !enable_vertical) { return; } if (output->is_plugin_active(grab_interface.name)) { return; } if (static_cast(ev->event->fingers) != fingers) { return; } // Plugins are per output, swipes are global, so we need to handle // the swipe only when the cursor is on *our* (plugin instance's) output if (!(output->get_relative_geometry() & output->get_cursor_position())) { return; } state.swiping = true; state.direction = UNKNOWN; state.initial_deltas = {0.0, 0.0}; smooth_delta.dx.set(0, 0); smooth_delta.dy.set(0, 0); state.delta_last = {0, 0}; state.delta_prev = {0, 0}; state.delta_sum = {0, 0}; // We switch the actual workspace before the finishing animation, // so the rendering of the animation cannot dynamically query current // workspace again, so it's stored here auto grid = output->wset()->get_workspace_grid_size(); auto ws = output->wset()->get_current_workspace(); state.vw = grid.width; state.vh = grid.height; state.vx = ws.x; state.vy = ws.y; }; void start_swipe(swipe_direction_t direction) { assert(direction != UNKNOWN); state.direction = direction; if (!output->activate_plugin(&grab_interface)) { return; } input_grab->grab_input(wf::scene::layer::OVERLAY); wf::get_core().seat->focus_output(output); auto ws = output->wset()->get_current_workspace(); wall->set_background_color(background_color); wall->set_gap_size(gap); wall->set_viewport(wall->get_workspace_rectangle(ws)); wall->start_output_renderer(); output->render->add_effect(&post_frame, wf::OUTPUT_EFFECT_POST); } // XXX: how to determine this?? static constexpr double initial_direction_threshold = 0.05; static constexpr double secondary_direction_threshold = 0.3; static constexpr double diagonal_threshold = 1.73; // tan(30deg) bool is_diagonal(wf::pointf_t deltas) { /* Diagonal movement is possible if the slope is not too steep * and we have moved enough */ double slope = deltas.x / deltas.y; bool diagonal = wf::clamp(slope, 1.0 / diagonal_threshold, diagonal_threshold) == slope; diagonal &= (deltas.x * deltas.x + deltas.y * deltas.y) >= initial_direction_threshold * initial_direction_threshold; return diagonal; } swipe_direction_t calculate_direction(wf::pointf_t deltas) { auto grid = output->wset()->get_workspace_grid_size(); bool horizontal = deltas.x > initial_direction_threshold; bool vertical = deltas.y > initial_direction_threshold; horizontal &= deltas.x > deltas.y; vertical &= deltas.y > deltas.x; if (is_diagonal(deltas) && enable_free_movement) { return DIAGONAL; } else if (horizontal && (grid.width > 1) && enable_horizontal) { return HORIZONTAL; } else if (vertical && (grid.height > 1) && enable_vertical) { return VERTICAL; } return UNKNOWN; } wf::signal::connection_t> on_swipe_update = [&] (event *ev) { if (!state.swiping) { return; } state.delta_sum.x += ev->event->dx / speed_factor; state.delta_sum.y += ev->event->dy / speed_factor; if (state.direction == UNKNOWN) { state.initial_deltas.x += std::abs(ev->event->dx / speed_factor); state.initial_deltas.y += std::abs(ev->event->dy / speed_factor); state.direction = calculate_direction(state.initial_deltas); if (state.direction == UNKNOWN) { return; } start_swipe(state.direction); } else if ((state.direction != DIAGONAL) && enable_free_movement) { /* Consider promoting to diagonal movement */ double other = (state.direction == HORIZONTAL ? state.delta_sum.y : state.delta_sum.x); if (std::abs(other) > secondary_direction_threshold) { state.direction = DIAGONAL; } } const double cap = speed_cap; state.delta_prev = state.delta_last; double current_delta_processed; const auto& process_delta = [&] (double delta, wf::timed_transition_t& total_delta, int ws, int ws_max) { current_delta_processed = vswipe_process_delta(delta / speed_factor, total_delta, ws, ws_max, cap, enable_free_movement); double new_delta_end = total_delta.end + current_delta_processed; double new_delta_start = smooth_transition ? total_delta : new_delta_end; total_delta.set(new_delta_start, new_delta_end); }; if (state.direction & HORIZONTAL) { process_delta(ev->event->dx, smooth_delta.dx, state.vx, state.vw); } if (state.direction & VERTICAL) { process_delta(ev->event->dy, smooth_delta.dy, state.vy, state.vh); } state.delta_last = {ev->event->dx, ev->event->dy}; smooth_delta.start(); }; wf::signal::connection_t> on_swipe_end = [=] (auto) { if (!state.swiping || !output->is_plugin_active(grab_interface.name)) { state.swiping = false; return; } state.swiping = false; const double move_threshold = wf::clamp((double)threshold, 0.0, 1.0); const double fast_threshold = wf::clamp((double)delta_threshold, 0.0, 1000.0); wf::point_t target_delta = {0, 0}; wf::point_t target_workspace = {state.vx, state.vy}; if (state.direction & HORIZONTAL) { target_delta.x = vswipe_finish_target(smooth_delta.dx.end, state.vx, state.vw, state.delta_prev.x + state.delta_last.x, move_threshold, fast_threshold, enable_free_movement); target_workspace.x -= target_delta.x; } if (state.direction & VERTICAL) { target_delta.y = vswipe_finish_target(smooth_delta.dy.end, state.vy, state.vh, state.delta_prev.y + state.delta_last.y, move_threshold, fast_threshold, enable_free_movement); target_workspace.y -= target_delta.y; } smooth_delta.dx.restart_with_end(target_delta.x); smooth_delta.dy.restart_with_end(target_delta.y); smooth_delta.start(); output->wset()->set_workspace(target_workspace); state.animating = true; }; void finalize_and_exit() { state.swiping = false; input_grab->ungrab_input(); output->deactivate_plugin(&grab_interface); wall->stop_output_renderer(true); output->render->rem_effect(&post_frame); state.animating = false; } void fini() override { if (state.swiping) { finalize_and_exit(); } } }; DECLARE_WAYFIRE_PLUGIN(wf::per_output_plugin_t); wayfire-0.8.1/plugins/single_plugins/wrot.cpp000066400000000000000000000212071457431457600214120ustar00rootroot00000000000000#include #include "wayfire/debug.hpp" #include "wayfire/plugins/common/input-grab.hpp" #include "wayfire/scene-input.hpp" #include "wayfire/view.hpp" #include "wayfire/view-transform.hpp" #include "wayfire/output.hpp" #include "wayfire/core.hpp" #include #include #include #include #include #include static const char *transformer_3d = "wrot-3d"; static const char *transformer_2d = "wrot-2d"; static double cross(double x1, double y1, double x2, double y2) // cross product { return x1 * y2 - x2 * y1; } static double vlen(double x1, double y1) // length of vector centered at the origin { return std::sqrt(x1 * x1 + y1 * y1); } enum class mode { NONE, ROT_2D, ROT_3D, }; class wf_wrot : public wf::per_output_plugin_instance_t, public wf::pointer_interaction_t { wf::button_callback call; wf::option_wrapper_t reset_radius{"wrot/reset_radius"}; wf::option_wrapper_t sensitivity{"wrot/sensitivity"}; wf::option_wrapper_t invert{"wrot/invert"}; wf::pointf_t last_position; wayfire_toplevel_view current_view = nullptr; std::unique_ptr input_grab; mode current_mode = mode::NONE; void reset_all() { for (auto v : wf::get_core().get_all_views()) { v->get_transformed_node()->rem_transformer(transformer_2d); v->get_transformed_node()->rem_transformer(transformer_3d); } } wf::button_callback call_3d = [this] (auto) { if (current_mode != mode::NONE) { return false; } if (!output->activate_plugin(&grab_interface)) { return false; } current_view = toplevel_cast(wf::get_core().get_cursor_focus_view()); if (!current_view || (current_view->role != wf::VIEW_ROLE_TOPLEVEL)) { output->deactivate_plugin(&grab_interface); return false; } wf::get_core().default_wm->focus_raise_view(current_view); current_view->connect(¤t_view_unmapped); input_grab->grab_input(wf::scene::layer::OVERLAY); last_position = output->get_cursor_position(); current_mode = mode::ROT_3D; return false; // pass btn press to grab node }; wf::key_callback reset = [this] (auto) { reset_all(); return true; }; wf::key_callback reset_one = [this] (auto) { auto view = wf::get_active_view_for_output(output); if (view) { view->get_transformed_node()->rem_transformer(transformer_2d); view->get_transformed_node()->rem_transformer(transformer_3d); } return true; }; wf::signal::connection_t current_view_unmapped = [this] (auto) { if (input_grab->is_grabbed()) { current_view = nullptr; input_released(); } }; void motion_2d(int x, int y) { auto tr = wf::ensure_named_transformer( current_view, wf::TRANSFORMER_2D, transformer_2d, current_view); current_view->get_transformed_node()->begin_transform_update(); auto g = current_view->get_geometry(); double cx = g.x + g.width / 2.0; double cy = g.y + g.height / 2.0; double x1 = last_position.x - cx, y1 = last_position.y - cy; double x2 = x - cx, y2 = y - cy; if (vlen(x2, y2) <= reset_radius) { current_view->get_transformed_node()->end_transform_update(); current_view->get_transformed_node()->rem_transformer(transformer_2d); return; } /* cross(a, b) = |a| * |b| * sin(a, b) */ tr->angle -= std::asin(cross(x1, y1, x2, y2) / vlen(x1, y1) / vlen(x2, y2)); current_view->get_transformed_node()->end_transform_update(); last_position = {1.0 * x, 1.0 * y}; } void motion_3d(int x, int y) { if ((x == last_position.x) && (y == last_position.y)) { return; } auto tr = wf::ensure_named_transformer( current_view, wf::TRANSFORMER_3D, transformer_3d, current_view); current_view->get_transformed_node()->begin_transform_update(); float dx = x - last_position.x; float dy = y - last_position.y; float ascale = glm::radians(sensitivity / 60.0f); float dir = invert ? -1.f : 1.f; tr->rotation = glm::rotate(tr->rotation, vlen(dx, dy) * ascale, {dir *dy, dir * dx, 0}); current_view->get_transformed_node()->end_transform_update(); last_position = {(double)x, (double)y}; } wf::plugin_activation_data_t grab_interface = { .name = "wrot", .capabilities = wf::CAPABILITY_GRAB_INPUT, }; public: void init() override { input_grab = std::make_unique("wrot", output, nullptr, this, nullptr); call = [=] (auto) { if (current_mode != mode::NONE) { return false; } if (!output->activate_plugin(&grab_interface)) { return false; } current_view = toplevel_cast(wf::get_core().get_cursor_focus_view()); if (!current_view || (current_view->role != wf::VIEW_ROLE_TOPLEVEL)) { output->deactivate_plugin(&grab_interface); return false; } wf::get_core().default_wm->focus_raise_view(current_view); current_view->connect(¤t_view_unmapped); input_grab->grab_input(wf::scene::layer::OVERLAY); last_position = output->get_cursor_position(); current_mode = mode::ROT_2D; return false; // pass btn press to the grab node }; output->add_button(wf::option_wrapper_t("wrot/activate"), &call); output->add_button(wf::option_wrapper_t("wrot/activate-3d"), &call_3d); output->add_key(wf::option_wrapper_t{"wrot/reset"}, &reset); output->add_key(wf::option_wrapper_t{"wrot/reset-one"}, &reset_one); grab_interface.cancel = [=] () { if (input_grab->is_grabbed()) { input_released(); } }; } void handle_pointer_button(const wlr_pointer_button_event& event) override { if (event.state == WLR_BUTTON_RELEASED) { input_released(); } } void handle_pointer_motion(wf::pointf_t pointer_position, uint32_t time_ms) override { if (current_mode == mode::ROT_2D) { motion_2d(pointer_position.x, pointer_position.y); } else if (current_mode == mode::ROT_3D) { motion_3d(pointer_position.x, pointer_position.y); } } void input_released() { input_grab->ungrab_input(); output->deactivate_plugin(&grab_interface); current_view_unmapped.disconnect(); if ((current_mode == mode::ROT_3D) && current_view) { auto tr = current_view->get_transformed_node() ->get_transformer(transformer_3d); if (tr) { /* check if the view was rotated to perpendicular to the screen * and move it a bit more, so it will not get "stuck" that way */ glm::vec4 n{0, 0, 1.f, 0}; glm::vec4 x = tr->rotation * n; auto dot = glm::dot(n, x); if (std::abs(dot) < 0.05) { /* rotate 2.5 degrees around an axis perpendicular to x */ current_view->get_transformed_node()->begin_transform_update(); tr->rotation = glm::rotate(tr->rotation, glm::radians( dot < 0 ? -2.5f : 2.5f), {x.y, -x.x, 0}); current_view->get_transformed_node()->end_transform_update(); } } } current_mode = mode::NONE; } void fini() override { if (input_grab->is_grabbed()) { input_released(); } reset_all(); output->rem_binding(&call); output->rem_binding(&call_3d); output->rem_binding(&reset); output->rem_binding(&reset_one); } }; DECLARE_WAYFIRE_PLUGIN(wf::per_output_plugin_t); wayfire-0.8.1/plugins/single_plugins/wsets.cpp000066400000000000000000000246121457431457600215670ustar00rootroot00000000000000#include "wayfire/bindings.hpp" #include "wayfire/geometry.hpp" #include "wayfire/object.hpp" #include "wayfire/seat.hpp" #include "wayfire/option-wrapper.hpp" #include "wayfire/scene-operations.hpp" #include "wayfire/signal-provider.hpp" #include "wayfire/util.hpp" #include "wayfire/view.hpp" #include "plugins/ipc/ipc-helpers.hpp" #include "plugins/ipc/ipc-method-repository.hpp" #include "wayfire/plugins/common/shared-core-data.hpp" #include #include #include #include #include #include #include #include #include #include #include class wset_output_overlay_t : public wf::scene::node_t { class render_instance_t : public wf::scene::simple_render_instance_t { public: using simple_render_instance_t::simple_render_instance_t; void render(const wf::render_target_t& target, const wf::region_t& region) { OpenGL::render_begin(target); auto g = self->get_bounding_box(); for (auto box : region) { target.logic_scissor(wlr_box_from_pixman_box(box)); OpenGL::render_texture(self->cr_text.tex.tex, target, g, glm::vec4(1.0f), OpenGL::TEXTURE_TRANSFORM_INVERT_Y); } OpenGL::render_end(); } }; wf::cairo_text_t cr_text; public: wset_output_overlay_t() : node_t(false) {} void gen_render_instances(std::vector& instances, wf::scene::damage_callback push_damage, wf::output_t *output) override { instances.push_back(std::make_unique(this, push_damage, output)); } wf::geometry_t get_bounding_box() override { return wf::construct_box({10, 10}, cr_text.get_size()); } void set_text(std::string text) { wf::cairo_text_t::params params; params.text_color = wf::color_t{0.9, 0.9, 0.9, 1}; params.bg_color = wf::color_t{0.1, 0.1, 0.1, 0.9}; params.font_size = 32; params.rounded_rect = true; params.bg_rect = true; params.max_size = wf::dimensions(get_bounding_box()); cr_text.render_text(text, params); wf::scene::damage_node(this->shared_from_this(), get_bounding_box()); } }; class wayfire_wsets_plugin_t : public wf::plugin_interface_t { public: void init() override { method_repository->register_method("wsets/set-output-wset", set_output_wset); setup_bindings(); wf::get_core().output_layout->connect(&on_new_output); for (auto& wo : wf::get_core().output_layout->get_outputs()) { available_sets[wo->wset()->get_index()] = wo->wset(); } } void fini() override { method_repository->unregister_method("wsets/set-output-wset"); for (auto& binding : select_callback) { wf::get_core().bindings->rem_binding(&binding); } for (auto& binding : send_callback) { wf::get_core().bindings->rem_binding(&binding); } } private: wf::shared_data::ref_ptr_t method_repository; wf::option_wrapper_t> workspace_bindings{"wsets/wsets_bindings"}; wf::option_wrapper_t> send_to_bindings{"wsets/send_window_bindings"}; wf::option_wrapper_t label_duration{"wsets/label_duration"}; std::list select_callback; std::list send_callback; std::map> available_sets; wf::ipc::method_callback set_output_wset = [=] (nlohmann::json data) { WFJSON_EXPECT_FIELD(data, "output-id", number_integer); WFJSON_EXPECT_FIELD(data, "wset-index", number_integer); auto o = wf::ipc::find_output_by_id(data["output-id"]); if (!o) { return wf::ipc::json_error("output not found"); } select_workspace(data["wset-index"], o); return wf::ipc::json_ok(); }; void setup_bindings() { for (const auto& [workspace, binding] : workspace_bindings.value()) { int index = wf::option_type::from_string(workspace.c_str()).value_or(-1); if (index < 0) { LOGE("[WSETS] Invalid workspace set ", index, " in configuration!"); continue; } select_callback.push_back([=] (auto) { auto wo = wf::get_core().seat->get_active_output(); if (!wo->can_activate_plugin(wf::CAPABILITY_MANAGE_COMPOSITOR)) { return false; } select_workspace(index); return true; }); wf::get_core().bindings->add_activator(wf::create_option(binding), &select_callback.back()); } for (const auto& [workspace, binding] : send_to_bindings.value()) { int index = wf::option_type::from_string(workspace.c_str()).value_or(-1); if (index < 0) { LOGE("[WSETS] Invalid workspace set ", index, " in configuration!"); continue; } send_callback.push_back([=] (auto) { auto wo = wf::get_core().seat->get_active_output(); if (!wo->can_activate_plugin(wf::CAPABILITY_MANAGE_COMPOSITOR)) { return false; } send_window_to(index); return true; }); wf::get_core().bindings->add_activator(wf::create_option(binding), &send_callback.back()); } } struct output_overlay_data_t : public wf::custom_data_t { std::shared_ptr node; wf::wl_timer timer; ~output_overlay_data_t() { wf::scene::damage_node(node, node->get_bounding_box()); wf::scene::remove_child(node); timer.disconnect(); } }; void cleanup_wsets() { auto it = available_sets.begin(); while (it != available_sets.end()) { auto wset = it->second; if (wset->get_views().empty() && (!wset->get_attached_output() || (wset->get_attached_output()->wset() != wset))) { it = available_sets.erase(it); } else { ++it; } } } void show_workspace_set_overlay(wf::output_t *wo) { auto overlay = wo->get_data_safe(); if (!overlay->node) { overlay->node = std::make_shared(); } overlay->node->set_text("Workspace set " + std::to_string(wo->wset()->get_index())); wf::scene::readd_front(wo->node_for_layer(wf::scene::layer::DWIDGET), overlay->node); wf::scene::damage_node(overlay->node, overlay->node->get_bounding_box()); overlay->timer.set_timeout(label_duration, [wo] () { wo->erase_data(); }); } /** * Find the workspace set with the given index, or create a new one if it does not exist already. * In addition, take a reference to it. */ void locate_or_create_wset(uint64_t index) { if (available_sets.count(index)) { return; } auto all_wsets = wf::workspace_set_t::get_all(); auto it = std::find_if(all_wsets.begin(), all_wsets.end(), [&] (auto wset) { return wset->get_index() == index; }); if (it == all_wsets.end()) { available_sets[index] = wf::workspace_set_t::create(index); } else { available_sets[index] = (*it)->shared_from_this(); } } void select_workspace(int index, wf::output_t *wo = wf::get_core().seat->get_active_output()) { if (!wo) { return; } locate_or_create_wset(index); if (wo->wset() != available_sets[index]) { LOGC(WSET, "Output ", wo->to_string(), " selecting workspace set id=", index); if (auto old_output = available_sets[index]->get_attached_output()) { if (old_output->wset() == available_sets[index]) { // Create new empty wset for the output old_output->set_workspace_set(wf::workspace_set_t::create()); available_sets[old_output->wset()->get_index()] = old_output->wset(); show_workspace_set_overlay(old_output); } } wo->set_workspace_set(available_sets[index]); } // We want to show the overlay even if we remain on the same workspace set show_workspace_set_overlay(wo); cleanup_wsets(); } void send_window_to(int index) { auto wo = wf::get_core().seat->get_active_output(); if (!wo) { return; } auto view = toplevel_cast(wf::get_active_view_for_output(wo)); if (!view) { return; } locate_or_create_wset(index); auto target_wset = available_sets[index]; const auto& old_wset = view->get_wset(); old_wset->remove_view(view); wf::scene::remove_child(view->get_root_node()); wf::emit_view_pre_moved_to_wset_pre(view, old_wset, target_wset); if (view->get_output() != target_wset->get_attached_output()) { view->set_output(target_wset->get_attached_output()); } wf::scene::readd_front(target_wset->get_node(), view->get_root_node()); target_wset->add_view(view); wf::emit_view_moved_to_wset(view, old_wset, target_wset); wf::get_core().seat->refocus(); } wf::signal::connection_t on_new_output = [=] (wf::output_added_signal *ev) { available_sets[ev->output->wset()->get_index()] = ev->output->wset(); }; }; DECLARE_WAYFIRE_PLUGIN(wayfire_wsets_plugin_t); wayfire-0.8.1/plugins/single_plugins/zoom.cpp000066400000000000000000000074371457431457600214140ustar00rootroot00000000000000#include #include #include #include #include class wayfire_zoom_screen : public wf::per_output_plugin_instance_t { enum class interpolation_method_t { LINEAR = 0, NEAREST = 1, }; wf::option_wrapper_t modifier{"zoom/modifier"}; wf::option_wrapper_t speed{"zoom/speed"}; wf::option_wrapper_t smoothing_duration{"zoom/smoothing_duration"}; wf::option_wrapper_t interpolation_method{"zoom/interpolation_method"}; wf::animation::simple_animation_t progression{smoothing_duration}; bool hook_set = false; wf::plugin_activation_data_t grab_interface = { .name = "zoom", .capabilities = 0, }; public: void init() override { progression.set(1, 1); output->add_axis(modifier, &axis); } void update_zoom_target(float delta) { float target = progression.end; target -= target * delta * speed; target = wf::clamp(target, 1.0f, 50.0f); if (target != progression.end) { progression.animate(target); if (!hook_set) { hook_set = true; output->render->add_post(&render_hook); output->render->set_redraw_always(); } } } wf::axis_callback axis = [=] (wlr_pointer_axis_event *ev) { if (!output->can_activate_plugin(&grab_interface)) { return false; } if (ev->orientation != WLR_AXIS_ORIENTATION_VERTICAL) { return false; } update_zoom_target(ev->delta); return true; }; wf::post_hook_t render_hook = [=] (const wf::framebuffer_t& source, const wf::framebuffer_t& destination) { auto w = destination.viewport_width; auto h = destination.viewport_height; auto oc = output->get_cursor_position(); double x, y; wlr_box b = output->get_relative_geometry(); wlr_box_closest_point(&b, oc.x, oc.y, &x, &y); /* get rotation & scale */ wlr_box box = {int(x), int(y), 1, 1}; box = output->render->get_target_framebuffer(). framebuffer_box_from_geometry_box(box); x = box.x; y = h - box.y; const float scale = (progression - 1) / progression; // The target width and height are truncated here so that `x1+tw` and // `x1` round to GLint in tandem for glBlitFramebuffer(). This keeps the // aspect ratio constant while panning around. const GLint tw = w / progression, th = h / progression; const float x1 = x * scale; const float y1 = y * scale; const GLenum interpolation = (interpolation_method == (int)interpolation_method_t::NEAREST) ? GL_NEAREST : GL_LINEAR; OpenGL::render_begin(source); GL_CALL(glBindFramebuffer(GL_READ_FRAMEBUFFER, source.fb)); GL_CALL(glBindFramebuffer(GL_DRAW_FRAMEBUFFER, destination.fb)); GL_CALL(glBlitFramebuffer(x1, y1, x1 + tw, y1 + th, 0, 0, w, h, GL_COLOR_BUFFER_BIT, interpolation)); OpenGL::render_end(); if (!progression.running() && (progression - 1 <= 0.01)) { unset_hook(); } }; void unset_hook() { output->render->set_redraw_always(false); output->render->rem_post(&render_hook); hook_set = false; } void fini() override { if (hook_set) { output->render->rem_post(&render_hook); } output->rem_binding(&axis); } }; DECLARE_WAYFIRE_PLUGIN(wf::per_output_plugin_t); wayfire-0.8.1/plugins/tile/000077500000000000000000000000001457431457600156245ustar00rootroot00000000000000wayfire-0.8.1/plugins/tile/meson.build000066400000000000000000000005361457431457600177720ustar00rootroot00000000000000tile = shared_module('simple-tile', ['tile-plugin.cpp', 'tree.cpp', 'tree-controller.cpp'], include_directories: [wayfire_api_inc, wayfire_conf_inc, plugins_common_inc, grid_inc, wobbly_inc], dependencies: [wlroots, pixman, wfconfig], install: true, install_dir: join_paths(get_option('libdir'), 'wayfire')) wayfire-0.8.1/plugins/tile/tile-plugin.cpp000066400000000000000000000564561457431457600206010ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include #include #include #include "tree-controller.hpp" #include "tree.hpp" #include "wayfire/debug.hpp" #include "wayfire/geometry.hpp" #include "wayfire/object.hpp" #include "wayfire/option-wrapper.hpp" #include "wayfire/plugin.hpp" #include "wayfire/plugins/common/input-grab.hpp" #include "wayfire/scene-input.hpp" #include "wayfire/scene-operations.hpp" #include "wayfire/scene.hpp" #include "wayfire/signal-provider.hpp" #include "wayfire/toplevel-view.hpp" #include "wayfire/view-helpers.hpp" #include "wayfire/txn/transaction-manager.hpp" #include "wayfire/view.hpp" struct autocommit_transaction_t { public: wf::txn::transaction_uptr tx; autocommit_transaction_t() { tx = wf::txn::transaction_t::create(); } ~autocommit_transaction_t() { if (!tx->get_objects().empty()) { wf::get_core().tx_manager->schedule_transaction(std::move(tx)); } } }; namespace wf { static bool can_tile_view(wayfire_toplevel_view view) { if (view->parent) { return false; } return true; } /** * When a view is moved from one output to the other, we want to keep its tiled * status. To achieve this, we do the following: * * 1. In view-pre-moved-to-output handler, we set view_auto_tile_t custom data. * 2. In detach handler, we just remove the view as usual. * 3. We now know we will receive attach as next event. * Check for view_auto_tile_t, and tile the view again. */ class view_auto_tile_t : public wf::custom_data_t {}; class tile_workspace_set_data_t : public wf::custom_data_t { public: std::vector>> roots; std::vector> tiled_sublayer; const wf::tile::split_direction_t default_split = wf::tile::SPLIT_VERTICAL; wf::option_wrapper_t inner_gaps{"simple-tile/inner_gap_size"}; wf::option_wrapper_t outer_horiz_gaps{"simple-tile/outer_horiz_gap_size"}; wf::option_wrapper_t outer_vert_gaps{"simple-tile/outer_vert_gap_size"}; tile_workspace_set_data_t(std::shared_ptr wset) { this->wset = wset; wset->connect(&on_wset_attached); wset->connect(&on_workspace_grid_changed); resize_roots(wset->get_workspace_grid_size()); if (wset->get_attached_output()) { wset->get_attached_output()->connect(&on_workarea_changed); } inner_gaps.set_callback(update_gaps); outer_horiz_gaps.set_callback(update_gaps); outer_vert_gaps.set_callback(update_gaps); } wf::signal::connection_t on_workarea_changed = [=] (auto) { update_root_size(); }; wf::signal::connection_t on_wset_attached = [=] (auto) { on_workarea_changed.disconnect(); if (wset.lock()->get_attached_output()) { wset.lock()->get_attached_output()->connect(&on_workarea_changed); update_root_size(); } }; wf::signal::connection_t on_workspace_grid_changed = [=] (auto) { wf::dassert(!wset.expired(), "wset should not expire, ever!"); resize_roots(wset.lock()->get_workspace_grid_size()); }; void resize_roots(wf::dimensions_t wsize) { for (size_t i = 0; i < tiled_sublayer.size(); i++) { for (size_t j = 0; j < tiled_sublayer[i].size(); j++) { if (wset.lock()->is_workspace_valid({(int)i, (int)j})) { destroy_sublayer(tiled_sublayer[i][j]); } } } roots.resize(wsize.width); tiled_sublayer.resize(wsize.width); for (int i = 0; i < wsize.width; i++) { roots[i].resize(wsize.height); tiled_sublayer[i].resize(wsize.height); for (int j = 0; j < wsize.height; j++) { roots[i][j] = std::make_unique(default_split); tiled_sublayer[i][j] = std::make_shared(false); wf::scene::add_front(wset.lock()->get_node(), tiled_sublayer[i][j]); } } update_root_size(); update_gaps(); } void update_root_size() { auto wo = wset.lock()->get_attached_output(); wf::geometry_t workarea = wo ? wo->workarea->get_workarea() : tile::default_output_resolution; wf::geometry_t output_geometry = wset.lock()->get_last_output_geometry().value_or(tile::default_output_resolution); auto wsize = wset.lock()->get_workspace_grid_size(); for (int i = 0; i < wsize.width; i++) { for (int j = 0; j < wsize.height; j++) { /* Set size */ auto vp_geometry = workarea; vp_geometry.x += i * output_geometry.width; vp_geometry.y += j * output_geometry.height; autocommit_transaction_t tx; roots[i][j]->set_geometry(vp_geometry, tx.tx); } } } void destroy_sublayer(wf::scene::floating_inner_ptr sublayer) { // Transfer views to the top auto root = wset.lock()->get_node(); auto children = root->get_children(); auto sublayer_children = sublayer->get_children(); sublayer->set_children_list({}); children.insert(children.end(), sublayer_children.begin(), sublayer_children.end()); root->set_children_list(children); wf::scene::update(root, wf::scene::update_flag::CHILDREN_LIST); wf::scene::remove_child(sublayer); } std::function update_gaps = [=] () { tile::gap_size_t gaps = { .left = outer_horiz_gaps, .right = outer_horiz_gaps, .top = outer_vert_gaps, .bottom = outer_vert_gaps, .internal = inner_gaps, }; for (auto& col : roots) { for (auto& root : col) { autocommit_transaction_t tx; root->set_gaps(gaps, tx.tx); root->set_geometry(root->geometry, tx.tx); } } }; void flatten_roots() { for (auto& col : roots) { for (auto& root : col) { autocommit_transaction_t tx; tile::flatten_tree(root, tx.tx); } } } static tile_workspace_set_data_t& get(std::shared_ptr set) { if (!set->has_data()) { set->store_data(std::make_unique(set)); } return *set->get_data(); } static tile_workspace_set_data_t& get(wf::output_t *output) { return get(output->wset()); } static std::unique_ptr& get_current_root(wf::output_t *output) { auto set = output->wset(); auto vp = set->get_current_workspace(); auto& data = get(output); return data.roots[vp.x][vp.y]; } static scene::floating_inner_ptr get_current_sublayer(wf::output_t *output) { auto set = output->wset(); auto vp = set->get_current_workspace(); auto& data = get(output); return data.tiled_sublayer[vp.x][vp.y]; } std::weak_ptr wset; void attach_view(wayfire_toplevel_view view, wf::point_t vp = {-1, -1}) { view->set_allowed_actions(VIEW_ALLOW_WS_CHANGE); if (vp == wf::point_t{-1, -1}) { vp = wset.lock()->get_current_workspace(); } auto view_node = std::make_unique(view); { autocommit_transaction_t tx; roots[vp.x][vp.y]->as_split_node()->add_child(std::move(view_node), tx.tx); } auto node = view->get_root_node(); wf::scene::readd_front(tiled_sublayer[vp.x][vp.y], node); view_bring_to_front(view); consider_exit_fullscreen(view); } /** Remove the given view from its tiling container */ void detach_view(nonstd::observer_ptr view, bool reinsert = true) { auto wview = view->view; wview->set_allowed_actions(VIEW_ALLOW_ALL); { autocommit_transaction_t tx; view->parent->remove_child(view, tx.tx); } /* View node is invalid now */ flatten_roots(); if (wview->pending_fullscreen() && wview->is_mapped()) { wf::get_core().default_wm->fullscreen_request(wview, nullptr, false); } /* Remove from special sublayer */ if (reinsert) { wf::scene::readd_front(wview->get_output()->wset()->get_node(), wview->get_root_node()); } } /** * Consider unfullscreening all fullscreen views because a new view has been focused or attached to the * tiling tree. */ void consider_exit_fullscreen(wayfire_toplevel_view view) { if (tile::view_node_t::get_node(view) && !view->pending_fullscreen()) { auto vp = this->wset.lock()->get_current_workspace(); for_each_view(roots[vp.x][vp.y], [&] (wayfire_toplevel_view view) { if (view->pending_fullscreen()) { set_view_fullscreen(view, false); } }); } } void set_view_fullscreen(wayfire_toplevel_view view, bool fullscreen) { /* Set fullscreen, and trigger resizing of the views (which will commit the view) */ view->toplevel()->pending().fullscreen = fullscreen; update_root_size(); } }; class tile_output_plugin_t : public wf::pointer_interaction_t, public wf::custom_data_t { private: wf::view_matcher_t tile_by_default{"simple-tile/tile_by_default"}; wf::option_wrapper_t keep_fullscreen_on_adjacent{"simple-tile/keep_fullscreen_on_adjacent"}; wf::option_wrapper_t button_move{"simple-tile/button_move"}; wf::option_wrapper_t button_resize{"simple-tile/button_resize"}; wf::option_wrapper_t key_toggle_tile{"simple-tile/key_toggle"}; wf::option_wrapper_t key_focus_left{"simple-tile/key_focus_left"}; wf::option_wrapper_t key_focus_right{"simple-tile/key_focus_right"}; wf::option_wrapper_t key_focus_above{"simple-tile/key_focus_above"}; wf::option_wrapper_t key_focus_below{"simple-tile/key_focus_below"}; wf::output_t *output; public: std::unique_ptr input_grab; static std::unique_ptr get_default_controller() { return std::make_unique(); } std::unique_ptr controller = get_default_controller(); /** * Translate coordinates from output-local coordinates to the coordinate * system of the tiling trees, depending on the current workspace */ wf::point_t get_global_input_coordinates() { wf::pointf_t local = output->get_cursor_position(); auto vp = output->wset()->get_current_workspace(); auto size = output->get_screen_size(); local.x += size.width * vp.x; local.y += size.height * vp.y; return {(int)local.x, (int)local.y}; } /** Check whether we currently have a fullscreen tiled view */ bool has_fullscreen_view() { int count_fullscreen = 0; for_each_view(tile_workspace_set_data_t::get_current_root(output), [&] (wayfire_toplevel_view view) { count_fullscreen += view->pending_fullscreen(); }); return count_fullscreen > 0; } /** Check whether the current pointer focus is tiled view */ bool has_tiled_focus() { auto focus = wf::get_core().get_cursor_focus_view(); return focus && tile::view_node_t::get_node(focus); } template void start_controller() { /* No action possible in this case */ if (has_fullscreen_view() || !has_tiled_focus()) { return; } if (!output->activate_plugin(&grab_interface)) { return; } input_grab->grab_input(wf::scene::layer::OVERLAY); controller = std::make_unique(tile_workspace_set_data_t::get_current_root(output), get_global_input_coordinates()); } void stop_controller(bool force_stop) { if (!output->is_plugin_active(grab_interface.name)) { return; } input_grab->ungrab_input(); // Deactivate plugin, so that others can react to the events output->deactivate_plugin(&grab_interface); if (!force_stop) { controller->input_released(); } controller = get_default_controller(); } bool tile_window_by_default(wayfire_toplevel_view view) { return tile_by_default.matches(view) && can_tile_view(view); } void attach_view(wayfire_toplevel_view view, wf::point_t vp = {-1, -1}) { if (!view->get_wset()) { return; } stop_controller(true); tile_workspace_set_data_t::get(view->get_wset()).attach_view(view, vp); } void detach_view(nonstd::observer_ptr view, bool reinsert = true) { stop_controller(true); tile_workspace_set_data_t::get(view->view->get_wset()).detach_view(view, reinsert); } wf::signal::connection_t on_view_mapped = [=] (view_mapped_signal *ev) { if (auto toplevel = toplevel_cast(ev->view)) { if (tile_window_by_default(toplevel)) { attach_view(toplevel); } } }; wf::signal::connection_t on_view_unmapped = [=] (wf::view_unmapped_signal *ev) { stop_controller(true); auto node = wf::tile::view_node_t::get_node(ev->view); if (node) { detach_view(node); } }; wf::signal::connection_t on_tile_request = [=] (view_tile_request_signal *ev) { if (ev->carried_out || !tile::view_node_t::get_node(ev->view)) { return; } // we ignore those requests because we manage the tiled state manually ev->carried_out = true; }; wf::signal::connection_t on_fullscreen_request = [=] (view_fullscreen_request_signal *ev) { if (ev->carried_out || !tile::view_node_t::get_node(ev->view)) { return; } ev->carried_out = true; tile_workspace_set_data_t::get(ev->view->get_wset()).set_view_fullscreen(ev->view, ev->state); }; void change_view_workspace(wayfire_toplevel_view view, wf::point_t vp = {-1, -1}) { auto existing_node = wf::tile::view_node_t::get_node(view); if (existing_node) { detach_view(existing_node); attach_view(view, vp); } } wf::signal::connection_t on_view_change_workspace = [=] (view_change_workspace_signal *ev) { if (ev->old_workspace_valid) { change_view_workspace(ev->view, ev->to); } }; wf::signal::connection_t on_view_minimized = [=] (view_minimized_signal *ev) { auto existing_node = wf::tile::view_node_t::get_node(ev->view); if (ev->view->minimized && existing_node) { detach_view(existing_node); } if (!ev->view->minimized && tile_window_by_default(ev->view)) { attach_view(ev->view); } }; /** * Execute the given function on the focused view iff we can activate the * tiling plugin, there is a focused view and the focused view is a tiled * view * * @param need_tiled Whether the view needs to be tiled */ bool conditioned_view_execute(bool need_tiled, std::function func) { auto view = wf::get_core().seat->get_active_view(); if (!toplevel_cast(view) || (view->get_output() != output)) { return false; } if (need_tiled && !tile::view_node_t::get_node(view)) { return false; } if (output->can_activate_plugin(&grab_interface)) { func(toplevel_cast(view)); return true; } return false; } wf::key_callback on_toggle_tiled_state = [=] (auto) { return conditioned_view_execute(false, [=] (wayfire_toplevel_view view) { auto existing_node = tile::view_node_t::get_node(view); if (existing_node) { detach_view(existing_node); wf::get_core().default_wm->tile_request(view, 0); } else { attach_view(view); } }); }; bool focus_adjacent(tile::split_insertion_t direction) { return conditioned_view_execute(true, [=] (wayfire_toplevel_view view) { auto adjacent = tile::find_first_view_in_direction( tile::view_node_t::get_node(view), direction); bool was_fullscreen = view->pending_fullscreen(); if (adjacent) { /* This will lower the fullscreen status of the view */ view_bring_to_front(adjacent->view); wf::get_core().seat->focus_view(adjacent->view); if (was_fullscreen && keep_fullscreen_on_adjacent) { wf::get_core().default_wm->fullscreen_request(adjacent->view, output, true); } } }); } wf::key_callback on_focus_adjacent = [=] (wf::keybinding_t binding) { if (binding == key_focus_left) { return focus_adjacent(tile::INSERT_LEFT); } if (binding == key_focus_right) { return focus_adjacent(tile::INSERT_RIGHT); } if (binding == key_focus_above) { return focus_adjacent(tile::INSERT_ABOVE); } if (binding == key_focus_below) { return focus_adjacent(tile::INSERT_BELOW); } return false; }; wf::button_callback on_move_view = [=] (auto) { start_controller(); return false; // pass button to the grab node }; wf::button_callback on_resize_view = [=] (auto) { start_controller(); return false; // pass button to the grab node }; void handle_pointer_button(const wlr_pointer_button_event& event) override { if (event.state == WLR_BUTTON_RELEASED) { stop_controller(false); } } void handle_pointer_motion(wf::pointf_t pointer_position, uint32_t time_ms) override { controller->input_motion(get_global_input_coordinates()); } void setup_callbacks() { output->add_button(button_move, &on_move_view); output->add_button(button_resize, &on_resize_view); output->add_key(key_toggle_tile, &on_toggle_tiled_state); output->add_key(key_focus_left, &on_focus_adjacent); output->add_key(key_focus_right, &on_focus_adjacent); output->add_key(key_focus_above, &on_focus_adjacent); output->add_key(key_focus_below, &on_focus_adjacent); } wf::plugin_activation_data_t grab_interface = { .name = "simple-tile", .capabilities = CAPABILITY_MANAGE_COMPOSITOR, }; public: tile_output_plugin_t(wf::output_t *wo) { this->output = wo; input_grab = std::make_unique("simple-tile", output, nullptr, this, nullptr); output->connect(&on_view_mapped); output->connect(&on_view_unmapped); output->connect(&on_tile_request); output->connect(&on_fullscreen_request); output->connect(&on_view_change_workspace); output->connect(&on_view_minimized); setup_callbacks(); } ~tile_output_plugin_t() { output->rem_binding(&on_move_view); output->rem_binding(&on_resize_view); output->rem_binding(&on_toggle_tiled_state); output->rem_binding(&on_focus_adjacent); } }; class tile_plugin_t : public wf::plugin_interface_t, wf::per_output_tracker_mixin_t<> { public: void init() override { init_output_tracking(); wf::get_core().connect(&on_view_pre_moved_to_wset); wf::get_core().connect(&on_view_moved_to_wset); wf::get_core().connect(&on_focus_changed); } void fini() override { fini_output_tracking(); for (auto wset : workspace_set_t::get_all()) { wset->erase_data(); } for (auto wo : wf::get_core().output_layout->get_outputs()) { wo->erase_data(); } } void stop_controller(std::shared_ptr wset) { if (auto wo = wset->get_attached_output()) { auto tile = wo->get_data(); if (tile) { tile->stop_controller(true); } } } wf::signal::connection_t on_view_pre_moved_to_wset = [=] (view_pre_moved_to_wset_signal *ev) { auto node = wf::tile::view_node_t::get_node(ev->view); if (node) { ev->view->store_data(std::make_unique()); if (ev->old_wset) { stop_controller(ev->old_wset); tile_workspace_set_data_t::get(ev->old_wset).detach_view(node); } } }; wf::signal::connection_t on_focus_changed = [=] (keyboard_focus_changed_signal *ev) { if (auto toplevel = toplevel_cast(wf::node_to_view(ev->new_focus))) { if (toplevel->get_wset()) { tile_workspace_set_data_t::get(toplevel->get_wset()).consider_exit_fullscreen(toplevel); } } }; wf::signal::connection_t on_view_moved_to_wset = [=] (view_moved_to_wset_signal *ev) { if (ev->view->has_data() && ev->new_wset) { stop_controller(ev->new_wset); tile_workspace_set_data_t::get(ev->new_wset).attach_view(ev->view); } }; void handle_new_output(wf::output_t *output) override { output->store_data(std::make_unique(output)); } void handle_output_removed(wf::output_t *output) override { output->erase_data(); } }; } DECLARE_WAYFIRE_PLUGIN(wf::tile_plugin_t); wayfire-0.8.1/plugins/tile/tree-controller.cpp000066400000000000000000000361001457431457600214500ustar00rootroot00000000000000#include "tree-controller.hpp" #include #include #include #include #include #include #include #include #include #include namespace wf { namespace tile { void for_each_view(nonstd::observer_ptr root, std::function callback) { if (root->as_view_node()) { callback(root->as_view_node()->view); return; } for (auto& child : root->children) { for_each_view(child, callback); } } /** * Calculate which view node is at the given position * * Returns null if no view nodes are present. */ nonstd::observer_ptr find_view_at( nonstd::observer_ptr root, wf::point_t input) { if (root->as_view_node()) { return root->as_view_node(); } for (auto& child : root->children) { if (child->geometry & input) { return find_view_at({child}, input); } } /* Children probably empty? */ return nullptr; } /** * Calculate the position of the split that needs to be created if a view is * dropped at @input over @node * * @param sensitivity What percentage of the view is "active", i.e the threshold * for INSERT_NONE */ static split_insertion_t calculate_insert_type( nonstd::observer_ptr node, wf::point_t input, double sensitivity) { auto window = node->geometry; if (!(window & input)) { return INSERT_NONE; } /* * Calculate how much to the left, right, top and bottom of the window * our input is, then filter through the sensitivity. * * In the end, take the edge which is closest to input. */ std::vector> edges; double px = 1.0 * (input.x - window.x) / window.width; double py = 1.0 * (input.y - window.y) / window.height; edges.push_back({px, INSERT_LEFT}); edges.push_back({py, INSERT_ABOVE}); edges.push_back({1.0 - px, INSERT_RIGHT}); edges.push_back({1.0 - py, INSERT_BELOW}); /* Remove edges that are too far away */ auto it = std::remove_if(edges.begin(), edges.end(), [sensitivity] (auto pair) { return pair.first > sensitivity; }); edges.erase(it, edges.end()); if (edges.empty()) { return INSERT_SWAP; } /* Return the closest edge */ return std::min_element(edges.begin(), edges.end())->second; } /* By default, 1/3rd of the view can be dropped into */ static constexpr double SPLIT_PREVIEW_PERCENTAGE = 1.0 / 3.0; /** * Calculate the position of the split that needs to be created if a view is * dropped at @input over @node */ split_insertion_t calculate_insert_type( nonstd::observer_ptr node, wf::point_t input) { return calculate_insert_type(node, input, SPLIT_PREVIEW_PERCENTAGE); } /** * Calculate the bounds of the split preview */ wf::geometry_t calculate_split_preview(nonstd::observer_ptr over, split_insertion_t split_type) { auto preview = over->geometry; switch (split_type) { case INSERT_RIGHT: preview.x += preview.width * (1 - SPLIT_PREVIEW_PERCENTAGE); // fallthrough case INSERT_LEFT: preview.width = preview.width * SPLIT_PREVIEW_PERCENTAGE; break; case INSERT_BELOW: preview.y += preview.height * (1 - SPLIT_PREVIEW_PERCENTAGE); // fallthrough case INSERT_ABOVE: preview.height = preview.height * SPLIT_PREVIEW_PERCENTAGE; break; default: break; // nothing to do } return preview; } nonstd::observer_ptr find_first_view_in_direction( nonstd::observer_ptr from, split_insertion_t direction) { auto window = from->geometry; /* Since nodes are arranged tightly into a grid, we can just find the * proper edge and find the view there */ wf::point_t point; switch (direction) { case INSERT_ABOVE: point = { window.x + window.width / 2, window.y - 1, }; break; case INSERT_BELOW: point = { window.x + window.width / 2, window.y + window.height, }; break; case INSERT_LEFT: point = { window.x - 1, window.y + window.height / 2, }; break; case INSERT_RIGHT: point = { window.x + window.width, window.y + window.height / 2, }; break; default: assert(false); } auto root = from; while (root->parent) { root = root->parent; } return find_view_at(root, point); } /* ------------------------ move_view_controller_t -------------------------- */ move_view_controller_t::move_view_controller_t( std::unique_ptr& uroot, wf::point_t grab) : root(uroot) { this->grabbed_view = find_view_at(root, grab); if (this->grabbed_view) { this->output = this->grabbed_view->view->get_output(); this->current_input = grab; } } move_view_controller_t::~move_view_controller_t() { if (this->preview) { this->preview->set_target_geometry( get_wset_local_coordinates(output->wset(), current_input), 0.0, true); } } nonstd::observer_ptr move_view_controller_t::check_drop_destination( wf::point_t input) { auto dropped_at = find_view_at(this->root, this->current_input); if (!dropped_at || (dropped_at == this->grabbed_view)) { return nullptr; } return dropped_at; } void move_view_controller_t::ensure_preview(wf::point_t start) { if (this->preview) { return; } preview = std::make_shared(start, output, "simple-tile"); } void move_view_controller_t::input_motion(wf::point_t input) { if (!this->grabbed_view) { return; } this->current_input = input; auto view = check_drop_destination(input); if (!view) { /* No view, no preview */ if (this->preview) { preview->set_target_geometry(get_wset_local_coordinates(output->wset(), input), 0.0); } return; } auto split = calculate_insert_type(view, input); ensure_preview(get_wset_local_coordinates(output->wset(), input)); auto preview_geometry = calculate_split_preview(view, split); preview_geometry = get_wset_local_coordinates(output->wset(), preview_geometry); this->preview->set_target_geometry(preview_geometry, 1.0); } /** * Find the index of the view in its parent list */ static int find_idx(nonstd::observer_ptr view) { auto& children = view->parent->children; auto it = std::find_if(children.begin(), children.end(), [=] (auto& node) { return node.get() == view.get(); }); return it - children.begin(); } void move_view_controller_t::input_released() { auto dropped_at = check_drop_destination(this->current_input); if (!this->grabbed_view || !dropped_at) { return; } auto split = calculate_insert_type(dropped_at, current_input); if (split == INSERT_NONE) { return; } auto tx = wf::txn::transaction_t::create(); if (split == INSERT_SWAP) { std::swap(grabbed_view->geometry, dropped_at->geometry); auto p1 = grabbed_view->parent; auto p2 = dropped_at->parent; grabbed_view->parent = p2; dropped_at->parent = p1; auto it1 = std::find_if(p1->children.begin(), p1->children.end(), [&] (const auto& ptr) { return ptr.get() == grabbed_view.get(); }); auto it2 = std::find_if(p2->children.begin(), p2->children.end(), [&] (const auto& ptr) { return ptr.get() == dropped_at.get(); }); std::swap(*it1, *it2); p1->set_geometry(p1->geometry, tx); p2->set_geometry(p2->geometry, tx); return; } auto split_type = (split == INSERT_LEFT || split == INSERT_RIGHT) ? SPLIT_VERTICAL : SPLIT_HORIZONTAL; if (dropped_at->parent->get_split_direction() == split_type) { /* We can simply add the dragged view as a sibling of the target view */ auto view = grabbed_view->parent->remove_child(grabbed_view, tx); int idx = find_idx(dropped_at); if ((split == INSERT_RIGHT) || (split == INSERT_BELOW)) { ++idx; } dropped_at->parent->add_child(std::move(view), tx, idx); } else { /* Case 2: we need a new split just for the dropped on and the dragged * views */ auto new_split = std::make_unique(split_type); /* The size will be autodetermined by the tree structure, but we set * some valid size here to avoid UB */ new_split->set_geometry(dropped_at->geometry, tx); /* Find the position of the dropped view and its parent */ int idx = find_idx(dropped_at); auto dropped_parent = dropped_at->parent; /* Remove both views */ auto dropped_view = dropped_at->parent->remove_child(dropped_at, tx); auto dragged_view = grabbed_view->parent->remove_child(grabbed_view, tx); if ((split == INSERT_ABOVE) || (split == INSERT_LEFT)) { new_split->add_child(std::move(dragged_view), tx); new_split->add_child(std::move(dropped_view), tx); } else { new_split->add_child(std::move(dropped_view), tx); new_split->add_child(std::move(dragged_view), tx); } /* Put them in place */ dropped_parent->add_child(std::move(new_split), tx, idx); } /* Clean up tree structure */ flatten_tree(this->root, tx); wf::get_core().tx_manager->schedule_transaction(std::move(tx)); } wf::geometry_t eval(nonstd::observer_ptr node) { return node ? node->geometry : wf::geometry_t{0, 0, 0, 0}; } /* ----------------------- resize tile controller --------------------------- */ resize_view_controller_t::resize_view_controller_t( std::unique_ptr& uroot, wf::point_t grab) : root(uroot) { this->grabbed_view = find_view_at(root, grab); this->last_point = grab; if (this->grabbed_view) { this->resizing_edges = calculate_resizing_edges(grab); horizontal_pair = this->find_resizing_pair(true); vertical_pair = this->find_resizing_pair(false); } } resize_view_controller_t::~resize_view_controller_t() {} uint32_t resize_view_controller_t::calculate_resizing_edges(wf::point_t grab) { uint32_t result_edges = 0; auto window = this->grabbed_view->geometry; assert(window & grab); if (grab.x < window.x + window.width / 2) { result_edges |= WLR_EDGE_LEFT; } else { result_edges |= WLR_EDGE_RIGHT; } if (grab.y < window.y + window.height / 2) { result_edges |= WLR_EDGE_TOP; } else { result_edges |= WLR_EDGE_BOTTOM; } return result_edges; } resize_view_controller_t::resizing_pair_t resize_view_controller_t::find_resizing_pair(bool horiz) { split_insertion_t direction; /* Calculate the direction in which we are looking for the resizing pair */ if (horiz) { if (this->resizing_edges & WLR_EDGE_TOP) { direction = INSERT_ABOVE; } else { direction = INSERT_BELOW; } } else { if (this->resizing_edges & WLR_EDGE_LEFT) { direction = INSERT_LEFT; } else { direction = INSERT_RIGHT; } } /* Find a view in the resizing direction, then look for the least common * ancestor(LCA) of the grabbed view and the found view. * * Then the resizing pair is a pair of children of the LCA */ auto pair_view = find_first_view_in_direction(this->grabbed_view, direction); if (!pair_view) // no pair { return {nullptr, grabbed_view}; } /* Calculate all ancestors of the grabbed view */ std::set> grabbed_view_ancestors; nonstd::observer_ptr ancestor = grabbed_view; while (ancestor) { grabbed_view_ancestors.insert(ancestor); ancestor = ancestor->parent; } /* Find the LCA: this is the first ancestor of the pair_view which is also * an ancestor of the grabbed view */ nonstd::observer_ptr lca = pair_view; /* The child of lca we came from the second time */ nonstd::observer_ptr lca_successor = nullptr; while (lca && !grabbed_view_ancestors.count({lca})) { lca_successor = lca; lca = lca->parent; } /* In the "worst" case, the root of the tree is an LCA. * Also, an LCA is a split because it is an ancestor of two different * view nodes */ assert(lca && lca->children.size()); resizing_pair_t result_pair; for (auto& child : lca->children) { if (grabbed_view_ancestors.count({child})) { result_pair.first = {child}; break; } } result_pair.second = lca_successor; /* Make sure the first node in the resizing pair is always to the * left or above of the second one */ if ((direction == INSERT_LEFT) || (direction == INSERT_ABOVE)) { std::swap(result_pair.first, result_pair.second); } return result_pair; } void resize_view_controller_t::adjust_geometry(int32_t& x1, int32_t& len1, int32_t& x2, int32_t& len2, int32_t delta) { /* * On the line: * * x1 (x1+len1)=x2 x2+len2-1 * ._______________.___________________. */ constexpr int MIN_SIZE = 50; int maxPositive = std::max(0, len2 - MIN_SIZE); int maxNegative = std::max(0, len1 - MIN_SIZE); /* Make sure we don't shrink one dimension too much */ delta = clamp(delta, -maxNegative, maxPositive); /* Adjust sizes */ len1 += delta; x2 += delta; len2 -= delta; } void resize_view_controller_t::input_motion(wf::point_t input) { if (!this->grabbed_view) { return; } auto tx = wf::txn::transaction_t::create(); if (horizontal_pair.first && horizontal_pair.second) { int dy = input.y - last_point.y; auto g1 = horizontal_pair.first->geometry; auto g2 = horizontal_pair.second->geometry; adjust_geometry(g1.y, g1.height, g2.y, g2.height, dy); horizontal_pair.first->set_geometry(g1, tx); horizontal_pair.second->set_geometry(g2, tx); } if (vertical_pair.first && vertical_pair.second) { int dx = input.x - last_point.x; auto g1 = vertical_pair.first->geometry; auto g2 = vertical_pair.second->geometry; adjust_geometry(g1.x, g1.width, g2.x, g2.width, dx); vertical_pair.first->set_geometry(g1, tx); vertical_pair.second->set_geometry(g2, tx); } wf::get_core().tx_manager->schedule_transaction(std::move(tx)); this->last_point = input; } } } wayfire-0.8.1/plugins/tile/tree-controller.hpp000066400000000000000000000117061457431457600214620ustar00rootroot00000000000000#ifndef WF_TILE_PLUGIN_TREE_CONTROLLER_HPP #define WF_TILE_PLUGIN_TREE_CONTROLLER_HPP #include "tree.hpp" #include /* Contains functions which are related to manipulating the tiling tree */ namespace wf { class preview_indication_t; namespace tile { /** * Run callback for each view in the tree */ void for_each_view(nonstd::observer_ptr root, std::function callback); enum split_insertion_t { /** Insert is invalid */ INSERT_NONE = 0, /** Insert above the view */ INSERT_ABOVE = 1, /** Insert below the view */ INSERT_BELOW = 2, /** Insert to the left of the view */ INSERT_LEFT = 3, /** Insert to the right of the view */ INSERT_RIGHT = 4, /** Insert by swapping with the source view */ INSERT_SWAP = 5, }; /** * Find the first view in the indicated direction */ nonstd::observer_ptr find_first_view_in_direction( nonstd::observer_ptr from, split_insertion_t direction); /** * Represents the current mode in which the tile plugin is. * * Invariant: while a controller is active, the tree structure shouldn't change, * except for changes by the controller itself. * * If such an external event happens, then controller will be destroyed. */ class tile_controller_t { public: virtual ~tile_controller_t() = default; /** Called when the input is moved */ virtual void input_motion(wf::point_t input) {} /** * Called when the input is released or the controller should stop * Note that a controller may be deleted without receiving input_released(), * in which case it should simply stop operation. */ virtual void input_released() {} }; /** * Represents the moving view action, i.e dragging a window to change its * position in the grid */ class move_view_controller_t : public tile_controller_t { public: /** * Start the drag-to-reorder action. * * @param root The root of the tiling tree which is currently being * manipulated * @param Where the grab has started */ move_view_controller_t(std::unique_ptr& root, wf::point_t grab); ~move_view_controller_t(); void input_motion(wf::point_t input) override; void input_released() override; protected: std::unique_ptr& root; nonstd::observer_ptr grabbed_view; wf::output_t *output; wf::point_t current_input; std::shared_ptr preview; /** * Create preview if it doesn't exist * * @param now The position of the input now. Used only if the preview * needs to be created. */ void ensure_preview(wf::point_t now); /** * Return the node under the input which is suitable for dropping on. */ nonstd::observer_ptr check_drop_destination(wf::point_t input); }; class resize_view_controller_t : public tile_controller_t { public: /** * Start the drag-to-resize action. * * @param root The root of the tiling tree which is currently being * manipulated * @param Where the grab has started */ resize_view_controller_t(std::unique_ptr& root, wf::point_t grab); ~resize_view_controller_t(); void input_motion(wf::point_t input) override; protected: std::unique_ptr& root; /** Last input event location */ wf::point_t last_point; /** Edges of the grabbed view that we're resizing */ uint32_t resizing_edges; /** Calculate the resizing edges for the grabbing view. */ uint32_t calculate_resizing_edges(wf::point_t point); /** The view we are resizing */ nonstd::observer_ptr grabbed_view; /* * A resizing pair of nodes is a pair of nodes we need to resize * The first one is always to the left/above the second one. */ using resizing_pair_t = std::pair, nonstd::observer_ptr>; /** The horizontally-aligned pair we're resizing */ resizing_pair_t horizontal_pair; /** The vertically-aligned pair we're resizing */ resizing_pair_t vertical_pair; /* * Find a resizing pair in the given direction. * * The resizing pair depends on the currently grabbed view and the * resizing edges. */ resizing_pair_t find_resizing_pair(bool horizontal); /** * Adjust the given positions and sizes while resizing. * * @param x1 The start of the first geometry * @param len1 The dimension of the first geometry * @param x2 The start of the second geometry, should be x1 + len1 * @param len2 The length of the second geometry * * @param delta How much change to apply */ void adjust_geometry(int32_t& x1, int32_t& len1, int32_t& x2, int32_t& len2, int32_t delta); }; } } #endif /* end of include guard: WF_TILE_PLUGIN_TREE_CONTROLLER_HPP */ wayfire-0.8.1/plugins/tile/tree.cpp000066400000000000000000000357251457431457600173030ustar00rootroot00000000000000#include "tree.hpp" #include "wayfire/core.hpp" #include "wayfire/geometry.hpp" #include "wayfire/toplevel-view.hpp" #include #include #include #include #include #include #include #include #include #include #include namespace wf { namespace tile { void tree_node_t::set_geometry(wf::geometry_t geometry, wf::txn::transaction_uptr&) { this->geometry = geometry; } nonstd::observer_ptr tree_node_t::as_split_node() { return nonstd::make_observer(dynamic_cast(this)); } nonstd::observer_ptr tree_node_t::as_view_node() { return nonstd::make_observer(dynamic_cast(this)); } wf::point_t get_wset_local_coordinates(std::shared_ptr wset, wf::point_t p) { auto vp = wset->get_current_workspace(); auto size = wset->get_last_output_geometry().value_or(default_output_resolution); p.x -= vp.x * size.width; p.y -= vp.y * size.height; return p; } wf::geometry_t get_wset_local_coordinates(std::shared_ptr wset, wf::geometry_t g) { auto new_tl = get_wset_local_coordinates(wset, wf::point_t{g.x, g.y}); g.x = new_tl.x; g.y = new_tl.y; return g; } /* ---------------------- split_node_t implementation ----------------------- */ wf::geometry_t split_node_t::get_child_geometry( int32_t child_pos, int32_t child_size) { wf::geometry_t child_geometry = this->geometry; switch (get_split_direction()) { case SPLIT_HORIZONTAL: child_geometry.y += child_pos; child_geometry.height = child_size; break; case SPLIT_VERTICAL: child_geometry.x += child_pos; child_geometry.width = child_size; break; } return child_geometry; } int32_t split_node_t::calculate_splittable(wf::geometry_t available) const { switch (get_split_direction()) { case SPLIT_HORIZONTAL: return available.height; case SPLIT_VERTICAL: return available.width; } return -1; } int32_t split_node_t::calculate_splittable() const { return calculate_splittable(this->geometry); } void split_node_t::recalculate_children(wf::geometry_t available, wf::txn::transaction_uptr& tx) { if (this->children.empty()) { return; } double old_child_sum = 0.0; for (auto& child : this->children) { old_child_sum += calculate_splittable(child->geometry); } int32_t total_splittable = calculate_splittable(available); /* Sum of children sizes up to now */ double up_to_now = 0.0; auto progress = [=] (double current) { return (current / old_child_sum) * total_splittable; }; set_gaps(this->gaps, tx); /* For each child, assign its percentage of the whole. */ for (auto& child : this->children) { /* Calculate child_start/end every time using the percentage from the * beginning. This way we avoid rounding errors causing empty spaces */ int32_t child_start = progress(up_to_now); up_to_now += calculate_splittable(child->geometry); int32_t child_end = progress(up_to_now); /* Set new size */ int32_t child_size = child_end - child_start; child->set_geometry(get_child_geometry(child_start, child_size), tx); } } void split_node_t::add_child(std::unique_ptr child, wf::txn::transaction_uptr& tx, int index) { /* * Strategy: * Calculate the size of the new child relative to the old children, so * that proportions are right. After that, rescale all nodes. */ int num_children = this->children.size(); /* Calculate where the new child should be, in current proportions */ int size_new_child; if (num_children > 0) { size_new_child = (calculate_splittable() + num_children - 1) / num_children; } else { size_new_child = calculate_splittable(); } if ((index == -1) || (index > num_children)) { index = num_children; } /* Add child to the list */ child->parent = {this}; // Set size of the child to make sure it gets properly recalculated later child->geometry = get_child_geometry(0, size_new_child); this->children.emplace(this->children.begin() + index, std::move(child)); set_gaps(this->gaps, tx); /* Recalculate geometry */ recalculate_children(geometry, tx); } std::unique_ptr split_node_t::remove_child( nonstd::observer_ptr child, wf::txn::transaction_uptr& tx) { /* Remove child */ std::unique_ptr result; auto it = this->children.begin(); while (it != this->children.end()) { if (it->get() == child.get()) { result = std::move(*it); it = this->children.erase(it); } else { ++it; } } /* Remaining children have the full geometry */ recalculate_children(this->geometry, tx); result->parent = nullptr; return result; } void split_node_t::set_geometry(wf::geometry_t geometry, wf::txn::transaction_uptr& tx) { tree_node_t::set_geometry(geometry, tx); recalculate_children(geometry, tx); } void split_node_t::set_gaps(const gap_size_t& gaps, wf::txn::transaction_uptr& tx) { this->gaps = gaps; for (const auto& child : this->children) { gap_size_t child_gaps = gaps; /* See which edges are modified by this split */ int32_t *first_edge, *second_edge; switch (this->split_direction) { case SPLIT_HORIZONTAL: first_edge = &child_gaps.top; second_edge = &child_gaps.bottom; break; case SPLIT_VERTICAL: first_edge = &child_gaps.left; second_edge = &child_gaps.right; break; default: assert(false); } /* Override internal edges */ if (child != this->children.front()) { *first_edge = gaps.internal; } if (child != this->children.back()) { *second_edge = gaps.internal; } child->set_gaps(child_gaps, tx); } } split_direction_t split_node_t::get_split_direction() const { return this->split_direction; } split_node_t::split_node_t(split_direction_t dir) { this->split_direction = dir; this->geometry = {0, 0, 0, 0}; } /* -------------------- view_node_t implementation -------------------------- */ struct view_node_custom_data_t : public custom_data_t { nonstd::observer_ptr ptr; view_node_custom_data_t(view_node_t *node) { ptr = nonstd::make_observer(node); } }; /** * A simple transformer to scale and translate the view in such a way that * its displayed wm geometry region is a specified box on the screen */ static const std::string scale_transformer_name = "simple-tile-scale-transformer"; struct view_node_t::scale_transformer_t : public wf::scene::view_2d_transformer_t { wf::geometry_t box; scale_transformer_t(wayfire_toplevel_view view, wf::geometry_t box) : wf::scene::view_2d_transformer_t(view) { set_box(box); } void set_box(wf::geometry_t box) { assert(box.width > 0 && box.height > 0); this->view->damage(); auto current = toplevel_cast(this->view)->get_geometry(); if ((current.width <= 0) || (current.height <= 0)) { /* view possibly unmapped?? */ return; } double scale_horiz = 1.0 * box.width / current.width; double scale_vert = 1.0 * box.height / current.height; /* Position of top-left corner after scaling */ double scaled_x = current.x + (current.width / 2.0 * (1 - scale_horiz)); double scaled_y = current.y + (current.height / 2.0 * (1 - scale_vert)); this->scale_x = scale_horiz; this->scale_y = scale_vert; this->translation_x = box.x - scaled_x; this->translation_y = box.y - scaled_y; } }; /** * A class for animating the view, emits a signal when the animation is over. */ class tile_view_animation_t : public wf::grid::grid_animation_t { public: using wf::grid::grid_animation_t::grid_animation_t; ~tile_view_animation_t() { // The grid animation does this too, however, we want to remove the // transformer so that we can enforce the correct geometry from the // start. view->get_transformed_node()->rem_transformer(); tile_adjust_transformer_signal ev; view->emit(&ev); } tile_view_animation_t(const tile_view_animation_t &) = delete; tile_view_animation_t(tile_view_animation_t &&) = delete; tile_view_animation_t& operator =(const tile_view_animation_t&) = delete; tile_view_animation_t& operator =(tile_view_animation_t&&) = delete; }; view_node_t::view_node_t(wayfire_toplevel_view view) { this->view = view; LOGI("We store data??"); view->store_data(std::make_unique(this)); this->on_geometry_changed.set_callback([=] (auto) { update_transformer(); }); on_adjust_transformer.set_callback([=] (auto) { update_transformer(); }); view->connect(&on_geometry_changed); view->connect(&on_adjust_transformer); } view_node_t::~view_node_t() { view->get_transformed_node()->rem_transformer(scale_transformer_name); view->erase_data(); } void view_node_t::set_gaps(const gap_size_t& size, wf::txn::transaction_uptr& tx) { if ((this->gaps.top != size.top) || (this->gaps.bottom != size.bottom) || (this->gaps.left != size.left) || (this->gaps.right != size.right)) { this->gaps = size; } } wf::geometry_t view_node_t::calculate_target_geometry() { /* Calculate view geometry in coordinates local to the active workspace, * because tree coordinates are kept in workspace-agnostic coordinates. */ auto wset = view->get_wset(); auto local_geometry = get_wset_local_coordinates(wset, geometry); local_geometry.x += gaps.left; local_geometry.y += gaps.top; local_geometry.width -= gaps.left + gaps.right; local_geometry.height -= gaps.top + gaps.bottom; auto size = wset->get_last_output_geometry().value_or(default_output_resolution); /* If view is maximized, we want to use the full available geometry */ if (view->pending_fullscreen()) { auto vp = wset->get_current_workspace(); int view_vp_x = std::floor(1.0 * geometry.x / size.width); int view_vp_y = std::floor(1.0 * geometry.y / size.height); local_geometry = { (view_vp_x - vp.x) * size.width, (view_vp_y - vp.y) * size.height, size.width, size.height, }; } if (view->sticky) { local_geometry.x = (local_geometry.x % size.width + size.width) % size.width; local_geometry.y = (local_geometry.y % size.height + size.height) % size.height; } return local_geometry; } bool view_node_t::needs_crossfade() { if (animation_duration == 0) { return false; } if (view->has_data()) { return true; } if (view->get_output()->is_plugin_active("simple-tile")) { // Disable animations while controllers are active return false; } return true; } static nonstd::observer_ptr ensure_animation( wayfire_toplevel_view view, wf::option_sptr_t duration) { if (!view->has_data()) { const auto type = wf::grid::grid_animation_t::CROSSFADE; view->store_data( std::make_unique(view, type, duration)); } return view->get_data(); } void view_node_t::set_geometry(wf::geometry_t geometry, wf::txn::transaction_uptr& tx) { tree_node_t::set_geometry(geometry, tx); if (!view->is_mapped()) { return; } wf::get_core().default_wm->update_last_windowed_geometry(view); view->toplevel()->pending().tiled_edges = TILED_EDGES_ALL; tx->add_object(view->toplevel()); auto target = calculate_target_geometry(); if (this->needs_crossfade() && (target != view->get_geometry())) { view->get_transformed_node()->rem_transformer(scale_transformer_name); ensure_animation(view, animation_duration) ->adjust_target_geometry(target, -1, tx); } else { view->toplevel()->pending().geometry = target; tx->add_object(view->toplevel()); } } void view_node_t::update_transformer() { auto target_geometry = calculate_target_geometry(); if ((target_geometry.width <= 0) || (target_geometry.height <= 0)) { return; } if (view->has_data()) { // Still animating return; } auto wm = view->get_geometry(); if (wm != target_geometry) { auto tr = ensure_named_transformer(view, wf::TRANSFORMER_2D, scale_transformer_name, view, target_geometry); tr->set_box(target_geometry); } else { view->get_transformed_node()->rem_transformer(scale_transformer_name); } } nonstd::observer_ptr view_node_t::get_node(wayfire_view view) { if (!view->has_data()) { return nullptr; } return view->get_data()->ptr; } /* ----------------- Generic tree operations implementation ----------------- */ void flatten_tree(std::unique_ptr& root, txn::transaction_uptr& tx) { /* Cannot flatten a view node */ if (root->as_view_node()) { return; } /* No flattening required on this level */ if (root->children.size() >= 2) { for (auto& child : root->children) { flatten_tree(child, tx); } return; } /* Only the real root of the tree can have no children */ assert(!root->parent || root->children.size()); if (root->children.empty()) { return; } nonstd::observer_ptr child_ptr = {root->children.front()}; /* A single view child => cannot make it root */ if (child_ptr->as_view_node()) { if (!root->parent) { return; } } /* Rewire the tree, skipping the current root */ auto child = root->as_split_node()->remove_child(child_ptr, tx); child->parent = root->parent; root = std::move(child); // overwrite root with the child } nonstd::observer_ptr get_root( nonstd::observer_ptr node) { if (!node->parent) { return {dynamic_cast(node.get())}; } return get_root(node->parent); } } } wayfire-0.8.1/plugins/tile/tree.hpp000066400000000000000000000157661457431457600173130ustar00rootroot00000000000000#ifndef WF_TILE_PLUGIN_TREE #define WF_TILE_PLUGIN_TREE #include "wayfire/signal-definitions.hpp" #include "wayfire/workspace-set.hpp" #include #include #include namespace wf { namespace tile { /** * A tree node represents a logical container of views in the tiled part of * a workspace. * * There are two types of nodes: * 1. View tree nodes, i.e leaves, they contain a single view * 2. Split tree nodes, they contain at least 1 child view. */ struct split_node_t; struct view_node_t; struct gap_size_t { /* Gap on the left side */ int32_t left = 0; /* Gap on the right side */ int32_t right = 0; /* Gap on the top side */ int32_t top = 0; /* Gap on the bottom side */ int32_t bottom = 0; /* Gap for internal splits */ int32_t internal = 0; }; struct tree_node_t { /** The node parent, or nullptr if this is the root node */ nonstd::observer_ptr parent; /** The children of the node */ std::vector> children; /** The geometry occupied by the node */ wf::geometry_t geometry; /** Set the geometry available for the node and its subnodes. */ virtual void set_geometry(wf::geometry_t geometry, wf::txn::transaction_uptr& tx); /** Set the gaps for the node and subnodes. */ virtual void set_gaps(const gap_size_t& gaps, wf::txn::transaction_uptr& tx) = 0; virtual ~tree_node_t() {} /** Simply dynamic cast this to a split_node_t */ nonstd::observer_ptr as_split_node(); /** Simply dynamic cast this to a view_node_t */ nonstd::observer_ptr as_view_node(); protected: /* Gaps */ gap_size_t gaps; }; /** * A node which contains a split can be split either horizontally or vertically */ enum split_direction_t { SPLIT_HORIZONTAL = 0, SPLIT_VERTICAL = 1, }; /* * Represents a node in the tree which contains at 1 one child node */ struct split_node_t : public tree_node_t { /** * Add the given child to the list of children. * * The new child will get resized so that its area is at most 1/(N+1) of the * total node area, where N is the number of children before adding the new * child. * * @param index The index at which to insert the new child, or -1 for * adding to the end of the child list. */ void add_child(std::unique_ptr child, wf::txn::transaction_uptr& tx, int index = -1); /** * Remove a child from the node, and return its unique_ptr */ std::unique_ptr remove_child( nonstd::observer_ptr child, wf::txn::transaction_uptr& tx); /** * Set the total geometry available to the node. This will recursively * resize the children nodes, so that they fit inside the new geometry and * have a size proportional to their old size. */ void set_geometry(wf::geometry_t geometry, wf::txn::transaction_uptr& tx) override; /** * Set the gaps for the subnodes. The internal gap will override * the corresponding edges for each child. */ void set_gaps(const gap_size_t& gaps, wf::txn::transaction_uptr& tx) override; split_node_t(split_direction_t direction); split_direction_t get_split_direction() const; private: split_direction_t split_direction; /** * Resize the children so that they fit inside the given * available_geometry. */ void recalculate_children(wf::geometry_t available_geometry, wf::txn::transaction_uptr& tx); /** * Calculate the geometry of a child if it has child_size as one * dimension. Whether this is width/height depends on the node split type. * * @param child_pos The position from which the child starts, relative to * the node itself * * @return The geometry of the child, in global coordinates */ wf::geometry_t get_child_geometry(int32_t child_pos, int32_t child_size); /** Return the size of the node in the dimension in which the split happens */ int32_t calculate_splittable() const; /** Return the size of the geometry in the dimension in which the split * happens */ int32_t calculate_splittable(wf::geometry_t geometry) const; }; struct tile_adjust_transformer_signal {}; /** * Represents a leaf in the tree, contains a single view */ struct view_node_t : public tree_node_t { view_node_t(wayfire_toplevel_view view); ~view_node_t(); wayfire_toplevel_view view; /** * Set the geometry of the node and the contained view. * * Note that the resulting view geometry will not always be equal to the * geometry of the node. For example, a fullscreen view will always have * the geometry of the whole output. */ void set_geometry(wf::geometry_t geometry, wf::txn::transaction_uptr& tx) override; /** * Set the gaps for non-fullscreen mode. * The gap sizes will be subtracted from all edges of the view's geometry. */ void set_gaps(const gap_size_t& gaps, wf::txn::transaction_uptr& tx) override; /* Return the tree node corresponding to the view, or nullptr if none */ static nonstd::observer_ptr get_node(wayfire_view view); private: struct scale_transformer_t; nonstd::observer_ptr transformer; wf::signal::connection_t on_geometry_changed; wf::signal::connection_t on_adjust_transformer; wf::option_wrapper_t animation_duration{"simple-tile/animation_duration"}; /** * Check whether the crossfade animation should be enabled for the view * currently. */ bool needs_crossfade(); wf::geometry_t calculate_target_geometry(); void update_transformer(); }; /** * Flatten the tree as much as possible, i.e remove nodes with only one * split-node child. * * The only exception is "the root", which will always be a split node. * * Note: this will potentially invalidate pointers to the tree and modify * the given parameter. */ void flatten_tree(std::unique_ptr& root, wf::txn::transaction_uptr& tx); /** * Get the root of the tree which node is part of */ nonstd::observer_ptr get_root(nonstd::observer_ptr node); /** * Transform coordinates from the tiling trees coordinate system to wset-local coordinates. */ wf::geometry_t get_wset_local_coordinates(std::shared_ptr wset, wf::geometry_t g); wf::point_t get_wset_local_coordinates(std::shared_ptr wset, wf::point_t g); // Since wsets may not have been attached to any output yet, they may not have a native 'resolution'. // In this case, we use a default resolution of 1920x1080 in order to layout views. This resolution will be // automatically adjusted once the wset is added to an output. static constexpr wf::geometry_t default_output_resolution = {0, 0, 1920, 1080}; } } #endif /* end of include guard: WF_TILE_PLUGIN_TREE */ wayfire-0.8.1/plugins/vswitch/000077500000000000000000000000001457431457600163565ustar00rootroot00000000000000wayfire-0.8.1/plugins/vswitch/meson.build000066400000000000000000000006441457431457600205240ustar00rootroot00000000000000vswitch_inc = include_directories('.') vswitch = shared_module('vswitch', ['vswitch.cpp'], include_directories: [wayfire_api_inc, wayfire_conf_inc, plugins_common_inc, vswitch_inc, ipc_include_dirs], dependencies: [wlroots, pixman, wfconfig, json], install: true, install_dir: join_paths(get_option('libdir'), 'wayfire')) install_headers(['wayfire/plugins/vswitch.hpp'], subdir: 'wayfire/plugins') wayfire-0.8.1/plugins/vswitch/vswitch.cpp000066400000000000000000000201101457431457600205430ustar00rootroot00000000000000#include "plugins/ipc/ipc-helpers.hpp" #include "wayfire/core.hpp" #include #include #include #include #include #include "plugins/ipc/ipc-method-repository.hpp" #include "wayfire/plugins/common/shared-core-data.hpp" class vswitch : public wf::per_output_plugin_instance_t { private: /** * Adapter around the general algorithm, so that our own stop function is * called. */ class vswitch_basic_plugin : public wf::vswitch::workspace_switch_t { public: vswitch_basic_plugin(wf::output_t *output, std::function on_done) : workspace_switch_t(output) { this->on_done = on_done; } void stop_switch(bool normal_exit) override { workspace_switch_t::stop_switch(normal_exit); on_done(); } private: std::function on_done; }; std::unique_ptr algorithm; std::unique_ptr bindings; // Capabilities which are always required for vswitch, for now wall needs // a custom renderer. static constexpr uint32_t base_caps = wf::CAPABILITY_CUSTOM_RENDERER; wf::plugin_activation_data_t grab_interface = { .name = "vswitch", .cancel = [=] () { algorithm->stop_switch(false); }, }; public: void init() { output->connect(&on_set_workspace_request); output->connect(&on_grabbed_view_disappear); algorithm = std::make_unique(output, [=] () { output->deactivate_plugin(&grab_interface); }); bindings = std::make_unique(output); bindings->setup([this] (wf::point_t delta, wayfire_toplevel_view view, bool only_view) { // Do not switch workspace with sticky view, they are on all // workspaces anyway if (view && view->sticky) { view = nullptr; } if (this->set_capabilities(wf::CAPABILITY_MANAGE_DESKTOP)) { if (delta == wf::point_t{0, 0}) { // Consume input event return true; } if (only_view && view) { auto size = output->get_screen_size(); for (auto& v : view->enumerate_views(false)) { auto origin = wf::origin(v->get_pending_geometry()); v->move(origin.x + delta.x * size.width, origin.y + delta.y * size.height); } wf::view_change_workspace_signal data; data.view = view; data.from = output->wset()->get_current_workspace(); data.to = data.from + delta; output->emit(&data); wf::get_core().seat->refocus(); return true; } return add_direction(delta, view); } else { return false; } }); } inline bool is_active() { return output->is_plugin_active(grab_interface.name); } inline bool can_activate() { return is_active() || output->can_activate_plugin(&grab_interface); } /** * Check if we can switch the plugin capabilities. * This makes sense only if the plugin is already active, otherwise, * the operation can succeed. * * @param caps The additional capabilities required, aside from the * base caps. */ bool set_capabilities(uint32_t caps) { uint32_t total_caps = caps | base_caps; if (!is_active()) { this->grab_interface.capabilities = total_caps; return true; } // already have everything needed if ((grab_interface.capabilities & total_caps) == total_caps) { // note: do not downgrade, in case total_caps are a subset of // current_caps return true; } // check for only the additional caps if (output->can_activate_plugin(caps)) { grab_interface.capabilities = total_caps; return true; } else { return false; } } bool add_direction(wf::point_t delta, wayfire_view view = nullptr) { if (!is_active() && !start_switch()) { return false; } if (view && (view->role != wf::VIEW_ROLE_TOPLEVEL)) { view = nullptr; } algorithm->set_overlay_view(toplevel_cast(view)); algorithm->set_target_workspace( output->wset()->get_current_workspace() + delta); return true; } wf::signal::connection_t on_grabbed_view_disappear = [=] (wf::view_disappeared_signal *ev) { if (ev->view == algorithm->get_overlay_view()) { algorithm->set_overlay_view(nullptr); } }; wf::signal::connection_t on_set_workspace_request = [=] (wf::workspace_change_request_signal *ev) { if (ev->old_viewport == ev->new_viewport) { // nothing to do ev->carried_out = true; return; } if (is_active()) { ev->carried_out = add_direction(ev->new_viewport - ev->old_viewport); } else { if (this->set_capabilities(0)) { if (ev->fixed_views.size() > 2) { LOGE("NOT IMPLEMENTED: ", "changing workspace with more than 1 fixed view"); } ev->carried_out = add_direction(ev->new_viewport - ev->old_viewport, ev->fixed_views.empty() ? nullptr : ev->fixed_views[0]); } } }; bool start_switch() { if (!output->activate_plugin(&grab_interface)) { return false; } algorithm->start_switch(); return true; } void fini() { if (is_active()) { algorithm->stop_switch(false); } bindings->tear_down(); } }; class wf_vswitch_global_plugin_t : public wf::per_output_plugin_t { wf::shared_data::ref_ptr_t ipc_repo; public: void init() override { per_output_plugin_t::init(); ipc_repo->register_method("vswitch/set-workspace", request_workspace); } void fini() override { per_output_plugin_t::fini(); ipc_repo->unregister_method("vswitch/set-workspace"); } wf::ipc::method_callback request_workspace = [=] (const nlohmann::json& data) { WFJSON_EXPECT_FIELD(data, "x", number_unsigned); WFJSON_EXPECT_FIELD(data, "y", number_unsigned); WFJSON_EXPECT_FIELD(data, "output-id", number_unsigned); WFJSON_OPTIONAL_FIELD(data, "view-id", number_unsigned); auto wo = wf::ipc::find_output_by_id(data["output-id"]); if (!wo) { return wf::ipc::json_error("Invalid output!"); } auto grid_size = wo->wset()->get_workspace_grid_size(); if ((data["x"] >= grid_size.width) || (data["y"] >= grid_size.height)) { return wf::ipc::json_error("Workspace coordinates are too big!"); } std::vector switch_with_views; if (data.contains("view-id")) { auto view = toplevel_cast(wf::ipc::find_view_by_id(data["view-id"])); if (!view) { return wf::ipc::json_error("Invalid view or view not toplevel!"); } switch_with_views.push_back(view); } wo->wset()->request_workspace({data["x"], data["y"]}, switch_with_views); return wf::ipc::json_ok(); }; }; DECLARE_WAYFIRE_PLUGIN(wf_vswitch_global_plugin_t); wayfire-0.8.1/plugins/vswitch/wayfire/000077500000000000000000000000001457431457600200245ustar00rootroot00000000000000wayfire-0.8.1/plugins/vswitch/wayfire/plugins/000077500000000000000000000000001457431457600215055ustar00rootroot00000000000000wayfire-0.8.1/plugins/vswitch/wayfire/plugins/vswitch.hpp000066400000000000000000000443731457431457600237200ustar00rootroot00000000000000#pragma once #include "wayfire/scene-input.hpp" #include "wayfire/scene-operations.hpp" #include "wayfire/seat.hpp" #include "wayfire/core.hpp" #include "wayfire/render-manager.hpp" #include "wayfire/scene-render.hpp" #include "wayfire/scene.hpp" #include "wayfire/toplevel-view.hpp" #include "wayfire/unstable/translation-node.hpp" #include "wayfire/view-helpers.hpp" #include #include #include #include #include #include #include #include #include #include namespace wf { namespace vswitch { using namespace animation; class workspace_animation_t : public duration_t { public: using duration_t::duration_t; timed_transition_t dx{*this}; timed_transition_t dy{*this}; }; /** * A simple scenegraph node which draws a view at a fixed position and as an overlay over the workspace wall. */ class vswitch_overlay_node_t : public wf::scene::node_t { std::weak_ptr _view; public: vswitch_overlay_node_t(wayfire_toplevel_view view) : node_t(true) { _view = view->weak_from_this(); } // Since we do not grab focus via a grab node, route focus to the view being rendered as an overlay wf::keyboard_focus_node_t keyboard_refocus(wf::output_t *output) { if (auto view = _view.lock()) { return view->get_transformed_node()->keyboard_refocus(output); } return wf::keyboard_focus_node_t{}; } virtual void gen_render_instances(std::vector& instances, scene::damage_callback push_damage, wf::output_t *output = nullptr) { if (auto view = _view.lock()) { view->get_transformed_node()->gen_render_instances(instances, push_damage, output); } } virtual wf::geometry_t get_bounding_box() { if (auto view = _view.lock()) { return view->get_transformed_node()->get_bounding_box(); } return {0, 0, 0, 0}; } }; /** * Represents the action of switching workspaces with the vswitch algorithm. * * The workspace is actually switched at the end of the animation */ class workspace_switch_t { public: /** * Initialize the workspace switch process. * * @param output The output the workspace switch happens on. */ workspace_switch_t(output_t *output) { this->output = output; wall = std::make_unique(output); animation = workspace_animation_t{ wf::option_wrapper_t{"vswitch/duration"} }; } /** * Initialize switching animation. * At this point, the calling plugin needs to have the custom renderer * ability set. */ virtual void start_switch() { /* Setup wall */ wall->set_gap_size(gap); wall->set_viewport(wall->get_workspace_rectangle( output->wset()->get_current_workspace())); wall->set_background_color(background_color); wall->start_output_renderer(); if (overlay_view_node) { wf::scene::readd_front(wf::get_core().scene(), overlay_view_node); } output->render->add_effect(&post_render, OUTPUT_EFFECT_POST); running = true; /* Setup animation */ animation.dx.set(0, 0); animation.dy.set(0, 0); animation.start(); } /** * Start workspace switch animation towards the given workspace, * and set that workspace as current. * * @param workspace The new target workspace. */ virtual void set_target_workspace(point_t workspace) { point_t cws = output->wset()->get_current_workspace(); animation.dx.set(animation.dx + cws.x - workspace.x, 0); animation.dy.set(animation.dy + cws.y - workspace.y, 0); animation.start(); std::vector fixed_views; if (overlay_view) { fixed_views.push_back(overlay_view); } output->wset()->set_workspace(workspace, fixed_views); } /** * Set the overlay view. It will be hidden from the normal workspace layers * and shown on top of the workspace wall. The overlay view's position is * not animated together with the workspace transition, but its alpha is. * * Note: if the view disappears, the caller is responsible for resetting the * overlay view. * * @param view The desired overlay view, or NULL if the overlay view needs * to be unset. */ virtual void set_overlay_view(wayfire_toplevel_view view) { if (this->overlay_view == view) { /* Nothing to do */ return; } /* Reset old view */ if (this->overlay_view) { wf::scene::set_node_enabled(overlay_view->get_transformed_node(), true); overlay_view->get_transformed_node()->rem_transformer( vswitch_view_transformer_name); wf::scene::remove_child(overlay_view_node); overlay_view_node.reset(); } /* Set new view */ this->overlay_view = view; if (view) { view->get_transformed_node()->add_transformer( std::make_shared(view), wf::TRANSFORMER_2D, vswitch_view_transformer_name); wf::scene::set_node_enabled(view->get_transformed_node(), false); // Render as an overlay, but make sure it is translated to the local output auto vswitch_overlay = std::make_shared(view); overlay_view_node = std::make_shared(); overlay_view_node->set_children_list({vswitch_overlay}); overlay_view_node->set_offset(origin(output->get_layout_geometry())); wf::scene::add_front(wf::get_core().scene(), overlay_view_node); } } /** @return the current overlay view, might be NULL. */ virtual wayfire_view get_overlay_view() { return this->overlay_view; } /** * Called automatically when the workspace switch animation is done. * By default, this stops the animation. * * @param normal_exit Whether the operation has ended because of animation * running out, in which case the workspace and the overlay view are * adjusted, and otherwise not. */ virtual void stop_switch(bool normal_exit) { if (normal_exit) { auto old_ws = output->wset()->get_current_workspace(); adjust_overlay_view_switch_done(old_ws); } wall->stop_output_renderer(true); output->render->rem_effect(&post_render); running = false; } virtual bool is_running() const { return running; } virtual ~workspace_switch_t() {} protected: option_wrapper_t gap{"vswitch/gap"}; option_wrapper_t background_color{"vswitch/background"}; workspace_animation_t animation; output_t *output; std::unique_ptr wall; const std::string vswitch_view_transformer_name = "vswitch-transformer"; wayfire_toplevel_view overlay_view; std::shared_ptr overlay_view_node; bool running = false; void update_overlay_fb() { if (!overlay_view) { return; } double progress = animation.progress(); auto tmanager = overlay_view->get_transformed_node(); auto tr = tmanager->get_transformer( vswitch_view_transformer_name); static constexpr double smoothing_in = 0.4; static constexpr double smoothing_out = 0.2; static constexpr double smoothing_amount = 0.5; tmanager->begin_transform_update(); if (progress <= smoothing_in) { tr->alpha = 1.0 - (smoothing_amount / smoothing_in) * progress; } else if (progress >= 1.0 - smoothing_out) { tr->alpha = 1.0 - (smoothing_amount / smoothing_out) * (1.0 - progress); } else { tr->alpha = smoothing_amount; } tmanager->end_transform_update(); } wf::effect_hook_t post_render = [=] () { auto start = wall->get_workspace_rectangle( output->wset()->get_current_workspace()); auto size = output->get_screen_size(); geometry_t viewport = { (int)std::round(animation.dx * (size.width + gap) + start.x), (int)std::round(animation.dy * (size.height + gap) + start.y), start.width, start.height, }; wall->set_viewport(viewport); update_overlay_fb(); output->render->damage_whole(); output->render->schedule_redraw(); if (!animation.running()) { stop_switch(true); } }; /** * Emit the view-change-workspace signal from the old workspace to the current * workspace and unset the view. */ virtual void adjust_overlay_view_switch_done(wf::point_t old_workspace) { if (!overlay_view) { return; } wf::view_change_workspace_signal data; data.view = overlay_view; data.from = old_workspace; data.to = output->wset()->get_current_workspace(); output->emit(&data); set_overlay_view(nullptr); wf::get_core().seat->refocus(); } }; /** * A simple class to register the vswitch bindings and get a custom callback called. */ class control_bindings_t { public: /** * Create a vswitch binding instance for the given output. * * The bindings will not be automatically connected. */ control_bindings_t(wf::output_t *output) { this->output = output; workspace_bindings.set_callback(on_cfg_reload); workspace_bindings_win.set_callback(on_cfg_reload); bindings_win.set_callback(on_cfg_reload); } virtual ~control_bindings_t() { tear_down(); } control_bindings_t(const control_bindings_t &) = delete; control_bindings_t(control_bindings_t &&) = delete; control_bindings_t& operator =(const control_bindings_t&) = delete; control_bindings_t& operator =(control_bindings_t&&) = delete; /** * A binding callback for vswitch. * * @param delta The difference between current and target workspace. * @param view The view to be moved together with the switch, or nullptr. * @param window_only Move only the view to the given workspace. It is * guaranteed that @view will not be nullptr if this is true. */ using binding_callback_t = std::function; /** * Connect bindings on the output. * * @param callback The callback to execute on each binding */ void setup(binding_callback_t callback) { tear_down(); this->user_cb = callback; // Setup a new binding on the output. // // @param name The binding name // @param dx, dy The delta from the current workspace // @param only Whether to grab the current view // @param only Whether to move the view without changing workspaces /* *INDENT-OFF* */ // Uncrustify has problems with this macro #define SETUP(name, dx, dy, view, only) \ wf::option_wrapper_t binding_##name { \ "vswitch/"#name}; \ activator_cbs.push_back(std::make_unique()); \ *activator_cbs.back() = [=] (const wf::activator_data_t&) \ {\ return handle_dir({dx, dy}, view, only, callback); \ };\ output->add_activator(binding_##name, activator_cbs.back().get()); // Setup bindings for the 4 directions (left/right/up/down) #define SETUP4(prefix, view, only) \ SETUP(prefix ## _left, -1, 0, view, only) \ SETUP(prefix ## _right, 1, 0, view, only) \ SETUP(prefix ## _up, 0, -1, view, only) \ SETUP(prefix ## _down, 0, 1, view, only) SETUP4(binding, nullptr, false); SETUP4(with_win, get_target_view(), false); SETUP4(send_win, get_target_view(), true); #undef SETUP4 #undef SETUP /* *INDENT-ON* */ // Setup the bindings for switching back to the last workspace for the output /* *INDENT-OFF* */ // Uncrustify has problems with this macro #define SETUP_LAST(name, view, only) \ wf::option_wrapper_t binding_##name { \ "vswitch/"#name}; \ activator_cbs.push_back(std::make_unique()); \ *activator_cbs.back() = [=] (const wf::activator_data_t&) \ {\ return handle_dir(-get_last_dir(), view, only, callback); \ };\ output->add_activator(binding_##name, activator_cbs.back().get()); SETUP_LAST(binding_last, nullptr, false); SETUP_LAST(with_win_last, get_target_view(), false); SETUP_LAST(send_win_last, get_target_view(), true); #undef SETUP_LAST /* *INDENT-ON* */ // Setup a binding for going directly to a workspace identified by a name const auto& setup_direct_binding = [=] (wf::activatorbinding_t binding, std::string workspace_name, bool grab_view, bool only_view) { auto ws_nr = wf::option_type::from_string(workspace_name); if (!ws_nr) { LOGE("Invalid vswitch binding, no such workspace ", workspace_name); return; } int nr = ws_nr.value(); --nr; activator_cbs.push_back(std::make_unique()); *activator_cbs.back() = [=] (const wf::activator_data_t&) { auto [wsize, hsize] = output->wset()->get_workspace_grid_size(); // Calculate target x,y each time. // Workspace grid size might change at runtime, so we cannot // compute them at initialization time wf::point_t target{ nr % wsize, nr / wsize, }; wf::point_t current = output->wset()->get_current_workspace(); auto view = (grab_view ? get_target_view() : nullptr); return handle_dir(target - current, view, only_view, callback); }; output->add_activator(wf::create_option(binding), activator_cbs.back().get()); }; for (auto& [name, act] : workspace_bindings.value()) { setup_direct_binding(act, name, false, false); } for (auto& [name, act] : workspace_bindings_win.value()) { setup_direct_binding(act, name, true, false); } for (auto& [name, act] : bindings_win.value()) { setup_direct_binding(act, name, true, true); } } /** * Disconnect the bindings. */ void tear_down() { for (auto& cb : activator_cbs) { output->rem_binding(cb.get()); } activator_cbs.clear(); } protected: binding_callback_t user_cb; std::vector> activator_cbs; wf::point_t last_dir = {0, 0}; wf::wl_idle_call idle_reload; wf::config::option_base_t::updated_callback_t on_cfg_reload = [=] () { // Aggregate multiple updates together idle_reload.run_once([=] () { // Reload only if the plugin has already setup bindings once, // otherwise, we do not have any callbacks to register. if (user_cb) { setup(user_cb); } }); }; wf::option_wrapper_t> workspace_bindings{"vswitch/workspace_bindings"}; wf::option_wrapper_t> workspace_bindings_win{"vswitch/workspace_bindings_win"}; wf::option_wrapper_t> bindings_win{"vswitch/bindings_win"}; wf::option_wrapper_t wraparound{"vswitch/wraparound"}; wf::output_t *output; /** Find the view to switch workspace with */ virtual wayfire_toplevel_view get_target_view() { auto view = find_topmost_parent(toplevel_cast(wf::get_core().seat->get_active_view())); if (!view || (view->role != wf::VIEW_ROLE_TOPLEVEL)) { return nullptr; } return view; } virtual wf::point_t get_last_dir() { return this->last_dir; } /** * Handle binding in the given direction. The next workspace will be * determined by the current workspace, target direction and wraparound * mode. */ virtual bool handle_dir(wf::point_t dir, wayfire_toplevel_view view, bool window_only, binding_callback_t callback) { if (!view && window_only) { // Maybe there is no view, in any case, no need to do anything return false; } auto ws = output->wset()->get_current_workspace(); auto target_ws = ws + dir; if (!output->wset()->is_workspace_valid(target_ws)) { if (wraparound) { auto grid_size = output->wset()->get_workspace_grid_size(); target_ws.x = (target_ws.x + grid_size.width) % grid_size.width; target_ws.y = (target_ws.y + grid_size.height) % grid_size.height; } else { target_ws = ws; } } // Remember the direction we are moving now so that we can potentially // move back. Only remember when we are actually changing the workspace // and not just move a view around. if (!window_only) { if (target_ws != ws) { this->last_dir = target_ws - ws; } } return callback(target_ws - ws, view, window_only); } }; } } wayfire-0.8.1/plugins/window-rules/000077500000000000000000000000001457431457600173265ustar00rootroot00000000000000wayfire-0.8.1/plugins/window-rules/lambda-rules-registration.hpp000066400000000000000000000154711457431457600251270ustar00rootroot00000000000000#ifndef LAMBDARULESREGISTRATION_HPP #define LAMBDARULESREGISTRATION_HPP #include #include #include #include #include "wayfire/core.hpp" #include "wayfire/object.hpp" #include "wayfire/nonstd/observer_ptr.h" #include "wayfire/parser/lambda_rule_parser.hpp" #include "wayfire/rule/lambda_rule.hpp" #include "wayfire/util/log.hpp" #include "wayfire/view.hpp" class wayfire_window_rules_t; namespace wf { struct lambda_rule_registration_t; using map_type = std::map>; using lambda_reg_t = std::function; /** * @brief The lambda_rule_registration_t struct represents registration information * for a single lambda rule. * * To make a registration, create one of these structures in a shared_ptr, fill in * the appropriate values and * register it on the lambda_rules_registrations_t singleton instance. * * At minimum, the rule string and if_lambda need to be set. * * The rule text defines the condition that will will be matched by window-rules. If * the condition described * in the rule text evaluates to true (using access_interface to determine the * current values of variables), * the if_lambda function will be executed. If the condition evaluates to false, the * else_lambda (if not * nullptr) will be executed. */ struct lambda_rule_registration_t { public: /** * @brief rule This is the rule text. * * @note The registering plugin is supposed to set this value before registering. */ std::string rule; /** * @brief if_lambda This is the lambda method to be executed if the specified * condition holds. * * @note The registering plugin is supposed to set this value before registering. */ wf::lambda_reg_t if_lambda; /** * @brief else_lambda This is the lambda method to be executed if the specified * condition does not hold. * * @note The registering plugin is supposed to set this value before registering. * @note In most cases this should be left blank. * * @attention: Be very careful with this lambda because it will be executed on * the signal for each view * that did NOT match the condition. */ wf::lambda_reg_t else_lambda; /** * @brief access_interface Access interface to be used when evaluating the rule. * * @note If this is left blank (nullptr), the standard view_access_interface_t * instance will be used. */ std::shared_ptr access_interface; private: /** * @brief rule_instance Pointer to the parsed rule object. * * @attention You should not set this. Leave it at nullptr, the registration * process will fill in this * variable. Window rules can then use this cached rule instance on * each signal occurrence. */ std::shared_ptr rule_instance; // Friendship for window rules to be able to execute the rules. friend class ::wayfire_window_rules_t; // Friendship for the rules registrations to be able to modify the rule set. friend class lambda_rules_registrations_t; }; /** * @brief The lambda_rules_registrations_t class is a helper class for easy * registration and unregistration of * lambda rules for the window rules plugin. * * This class is a singleton and can only be used via the getInstance() method. * * The instance is stored in wf::core. The getInstance() method will fetch from * wf:core, and lazy-init if the * instance is not yet present. */ class lambda_rules_registrations_t : public custom_data_t { public: /** * @brief getInstance Static accessor for the singleton. * * @return Observer pointer to the singleton instance, fetched from wf::core. */ static nonstd::observer_ptr get_instance() { auto instance = get_core().get_data(); if (instance == nullptr) { get_core().store_data(std::unique_ptr( new lambda_rules_registrations_t())); instance = get_core().get_data(); if (instance == nullptr) { LOGE("Window lambda rules: Lazy-init of lambda registrations failed."); } else { LOGD( "Window lambda rules: Lazy-init of lambda registrations succeeded."); } } return instance; } /** * @brief registerLambdaRule Registers a lambda rule with its associated key. * * This method will return error result if the key is not unique or the * registration struct is incomplete. * * @param[in] key Unique key for the registration. * @param[in] registration The registration structure. * * @return True in case of error, false if ok. */ bool register_lambda_rule(std::string key, std::shared_ptr registration) { if (_registrations.find(key) != _registrations.end()) { return true; // Error, key already exists. } if (registration->if_lambda == nullptr) { return true; // Error, no if lambda specified. } registration->rule_instance = lambda_rule_parser_t().parse( registration->rule, nullptr, nullptr); if (registration->rule_instance == nullptr) { return true; // Error, failed to parse rule. } _registrations.emplace(key, registration); return false; } /** * @brief unregisterLambdaRule Unregisters a lambda rule with its associated key. * * Has no effect if no rule is registered with this key. * * @param[in] key Unique key for the registration. */ void unregister_lambda_rule(std::string key) { _registrations.erase(key); } /** * @brief rules Gets the boundaries of the rules map as a tuple of cbegin() and * cend() const_iterators. * * @return Boundaries of the rules map. */ std::tuple rules() { return std::tuple(_registrations.cbegin(), _registrations.cend()); } private: /** * @brief lambda_rules_registrations_t Constructor, private to enforce singleton * design pattern. */ lambda_rules_registrations_t() = default; /** * @brief _registrations The map holding all the current registrations. */ map_type _registrations; // Necessary for window-rules to manage the lifetime of the object uint32_t window_rule_instances = 0; friend class ::wayfire_window_rules_t; }; } // End namespace wf. #endif // LAMBDARULESREGISTRATION_HPP wayfire-0.8.1/plugins/window-rules/meson.build000066400000000000000000000007531457431457600214750ustar00rootroot00000000000000window_rules = shared_module('window-rules', ['window-rules.cpp', 'view-action-interface.cpp'], include_directories: [wayfire_api_inc, wayfire_conf_inc, grid_inc, plugins_common_inc], dependencies: [wlroots, pixman, wfconfig, wfutils], install: true, install_dir: join_paths(get_option('libdir'), 'wayfire') ) wayfire-0.8.1/plugins/window-rules/view-action-interface.cpp000066400000000000000000000331201457431457600242140ustar00rootroot00000000000000#include "view-action-interface.hpp" #include "wayfire/core.hpp" #include "wayfire/output.hpp" #include "wayfire/toplevel-view.hpp" #include "wayfire/view.hpp" #include "wayfire/plugins/grid.hpp" #include "wayfire/util/log.hpp" #include "wayfire/view-transform.hpp" #include "wayfire/output-layout.hpp" #include "../wm-actions/wm-actions-signals.hpp" #include #include #include #include #include #include namespace wf { view_action_interface_t::~view_action_interface_t() {} bool view_action_interface_t::execute(const std::string & name, const std::vector & args) { const auto& execute_set_alpha = [&] { auto alpha = _validate_alpha(args); if (std::get<0>(alpha)) { _set_alpha(std::get<1>(alpha)); } }; if (!_view) { // We have a non-toplevel view. We can only adjust alpha for it ... if ((name != "set") || (args.size() != 2) || (wf::get_string(args[0]) != "alpha")) { LOGW("The only allowed action for non-toplevel views is", " set alpha , matched ", _nontoplevel); return true; } execute_set_alpha(); return false; } if (name == "set") { auto id = wf::get_string(args.at(0)); if (id == "sticky") { _make_sticky(); return false; } else if (id == "always_on_top") { _always_on_top(); return false; } if ((args.size() < 2) || (wf::is_string(args.at(0)) == false)) { LOGE( "View action interface: Set execution requires at least 2 arguments, the first of which should be an identifier."); return true; } if (id == "alpha") { execute_set_alpha(); } else if (id == "geometry") { auto geometry = _validate_geometry(args); if (std::get<0>(geometry)) { _set_geometry(std::get<1>(geometry), std::get<2>(geometry), std::get<3>(geometry), std::get<4>(geometry)); } } else if (id == "geometry_ppt") { auto geometry = _validate_geometry(args); if (std::get<0>(geometry)) { _set_geometry_ppt(std::get<1>(geometry), std::get<2>(geometry), std::get<3>(geometry), std::get<4>(geometry)); } } else { LOGE("View action interface: Unsupported set operation to identifier ", id); return true; } return false; } else if (name == "maximize") { _maximize(); return false; } else if (name == "unmaximize") { _unmaximize(); return false; } else if (name == "minimize") { _minimize(); return false; } else if (name == "unminimize") { _unminimize(); return false; } else if (name == "snap") { if ((args.size() < 1) || (wf::is_string(args.at(0)) == false)) { LOGE( "View action interface: Snap execution requires 1 string as argument."); return true; } auto output = _view->get_output(); if (output == nullptr) { LOGE("View action interface: Output associated with view was null."); return true; } auto location = wf::get_string(args.at(0)); grid::slot_t slot; if (location == "top") { slot = grid::SLOT_TOP; } else if (location == "top_right") { slot = grid::SLOT_TR; } else if (location == "right") { slot = grid::SLOT_RIGHT; } else if (location == "bottom_right") { slot = grid::SLOT_BR; } else if (location == "bottom") { slot = grid::SLOT_BOTTOM; } else if (location == "bottom_left") { slot = grid::SLOT_BL; } else if (location == "left") { slot = grid::SLOT_LEFT; } else if (location == "top_left") { slot = grid::SLOT_TL; } else if (location == "center") { slot = grid::SLOT_CENTER; } else { LOGE( "View action interface: Incorrect string literal for snap location: ", location, "."); return true; } LOGI("View action interface: Snap to ", location, "."); wf::get_core().default_wm->tile_request(_view, grid::get_tiled_edges_for_slot(slot)); return false; } else if (name == "start_on_output") { if ((args.size() < 1) || (wf::is_string(args.at(0)) == false)) { LOGE( "View action interface: Start on output execution requires 1 string as argument."); return true; } auto name = wf::get_string(args.at(0)); _start_on_output(name); return false; } else if (name == "move") { auto position = _validate_position(args); if (std::get<0>(position)) { _move(std::get<1>(position), std::get<2>(position)); return false; } LOGE("View action interface: invalid arguments for move"); return true; } else if (name == "resize") { auto size = _validate_size(args); if (std::get<0>(size)) { _resize(std::get<1>(size), std::get<2>(size)); return false; } LOGE("View action interface: invalid arguments for resize"); return true; } else if (name == "assign_workspace") { auto [ok, ws] = _validate_ws(args); if (ok) { _assign_ws(ws); return false; } return true; } LOGE("View action interface: Unsupported action execution requested. Name: ", name, "."); return true; } void view_action_interface_t::set_view(wayfire_view view) { _nontoplevel = view; _view = toplevel_cast(view); } void view_action_interface_t::_maximize() { wf::get_core().default_wm->tile_request(_view, wf::TILED_EDGES_ALL); } void view_action_interface_t::_unmaximize() { wf::get_core().default_wm->tile_request(_view, 0); } void view_action_interface_t::_minimize() { wf::get_core().default_wm->minimize_request(_view, true); } void view_action_interface_t::_unminimize() { wf::get_core().default_wm->minimize_request(_view, false); } void view_action_interface_t::_make_sticky() { _view->set_sticky(1); } void view_action_interface_t::_always_on_top() { wf::wm_actions_set_above_state_signal data; auto output = _view->get_output(); if (!output) { return; } data.view = _view; data.above = true; output->emit(&data); } std::tuple view_action_interface_t::_expect_float( const std::vector & args, std::size_t position) { if ((args.size() > position) && (wf::is_float(args.at(position)))) { return {true, wf::get_float(args.at(position))}; } return {false, 0.0f}; } std::tuple view_action_interface_t::_expect_double( const std::vector & args, std::size_t position) { if ((args.size() > position) && (wf::is_double(args.at(position)))) { return {true, wf::get_double(args.at(position))}; } return {false, 0.0}; } std::tuple view_action_interface_t::_expect_int( const std::vector & args, std::size_t position) { if ((args.size() > position) && (wf::is_int(args.at(position)))) { return {true, wf::get_int(args.at(position))}; } return {false, 0}; } std::tuple view_action_interface_t::_validate_alpha( const std::vector & args) { auto arg_float = _expect_float(args, 1); if (std::get<0>(arg_float)) { return arg_float; } else { auto arg_double = _expect_double(args, 1); if (std::get<0>(arg_double)) { return {true, static_cast(std::get<1>(arg_double))}; } } LOGE( "View action interface: Invalid arguments. Expected 'set alpha [float|double]."); return {false, 1.0f}; } std::tuple view_action_interface_t::_validate_geometry( const std::vector & args) { auto arg_x = _expect_int(args, 1); auto arg_y = _expect_int(args, 2); auto arg_w = _expect_int(args, 3); auto arg_h = _expect_int(args, 4); if (std::get<0>(arg_x) && std::get<0>(arg_y) && std::get<0>(arg_w) && std::get<0>(arg_h)) { return {true, std::get<1>(arg_x), std::get<1>(arg_y), std::get<1>(arg_w), std::get<1>(arg_h)}; } LOGE( "View action interface: Invalid arguments. Expected 'set geometry int int int int."); return {false, 0, 0, 0, 0}; } std::tuple view_action_interface_t::_validate_position( const std::vector & args) { auto arg_x = _expect_int(args, 0); auto arg_y = _expect_int(args, 1); if (std::get<0>(arg_x) && std::get<0>(arg_y)) { return {true, std::get<1>(arg_x), std::get<1>(arg_y)}; } LOGE("View action interface: Invalid arguments. Expected 'move int int."); return {false, 0, 0}; } std::tuple view_action_interface_t::_validate_size( const std::vector & args) { auto arg_w = _expect_int(args, 0); auto arg_h = _expect_int(args, 1); if (std::get<0>(arg_w) && std::get<0>(arg_h)) { return {true, std::get<1>(arg_w), std::get<1>(arg_h)}; } LOGE("View action interface: Invalid arguments. Expected 'resize int int."); return {false, 0, 0}; } void view_action_interface_t::_set_alpha(float alpha) { alpha = std::clamp(alpha, 0.1f, 1.0f); // Apply view transformer if needed and set alpha. auto tr = wf::ensure_named_transformer( _nontoplevel, wf::TRANSFORMER_2D, "alpha", _nontoplevel); if (fabs(tr->alpha - alpha) > FLT_EPSILON) { tr->alpha = alpha; _nontoplevel->damage(); LOGI("View action interface: Alpha set to ", alpha, "."); } } void view_action_interface_t::_set_geometry(int x, int y, int w, int h) { _resize(w, h); _move(x, y); } void view_action_interface_t::_set_geometry_ppt(int x, int y, int w, int h) { auto output = _view->get_output(); if (!output) { return; } auto og = output->get_relative_geometry(); x = std::clamp(x, 0, 100); y = std::clamp(y, 0, 100); w = std::clamp(w, 0, 100); h = std::clamp(h, 0, 100); x = og.width * x / 100; y = og.height * y / 100; w = og.width * w / 100; h = og.height * h / 100; _resize(w, h); _move(x, y); } void view_action_interface_t::_start_on_output(std::string name) { auto output = wf::get_core().output_layout->find_output(name); if (!output) { return; } if (_view->get_output() == output) { return; } move_view_to_output(_view, output, true); } std::tuple view_action_interface_t::_validate_ws( const std::vector& args) { if (!this->_view->get_output()) { return {false, {}}; } if (args.size() != 2) { LOGE("Invalid workspace identifier, expected "); } auto [ok1, x] = _expect_int(args, 0); auto [ok2, y] = _expect_int(args, 1); if (!ok1 || !ok2) { LOGE("Workspace coordinates should be integers!"); return {false, {}}; } auto wsize = _view->get_output()->wset()->get_workspace_grid_size(); if (((0 <= x) && (x < wsize.width)) && ((0 <= y) && (y < wsize.height))) { return {true, {x, y}}; } LOGE("Workspace coordinates out of bounds!"); return {false, {}}; } wf::geometry_t view_action_interface_t::_get_workspace_grid_geometry( wf::output_t *output) const { auto vsize = output->wset()->get_workspace_grid_size(); auto vp = output->wset()->get_current_workspace(); auto res = output->get_screen_size(); return wf::geometry_t{ -vp.x * res.width, -vp.y * res.height, vsize.width * res.width, vsize.height * res.height, }; } void view_action_interface_t::_move(int x, int y) { // Clamp x and y to sane values. Do not allow to move outside of workspace grid. auto output = _view->get_output(); if (output != nullptr) { auto grid = this->_get_workspace_grid_geometry(output); auto view_geometry = _view->get_pending_geometry(); view_geometry.x = x; view_geometry.y = y; view_geometry = wf::clamp(view_geometry, grid); _view->move(view_geometry.x, view_geometry.y); } } void view_action_interface_t::_resize(int w, int h) { // Clamp w and h to sane values. Do not allow to get bigger then output. Do not // allow to get smaller then 40x30. auto output = _view->get_output(); if (output != nullptr) { auto dimensions = output->get_screen_size(); w = std::clamp(w, 40, dimensions.width); h = std::clamp(h, 30, dimensions.height); _view->resize(w, h); } } void view_action_interface_t::_assign_ws(wf::point_t point) { auto output = _view->get_output(); auto delta = point - output->wset()->get_current_workspace(); auto size = output->get_screen_size(); auto wm = _view->get_pending_geometry(); _view->move(wm.x + delta.x * size.width, wm.y + delta.y * size.height); } } // End namespace wf. wayfire-0.8.1/plugins/window-rules/view-action-interface.hpp000066400000000000000000000035761457431457600242350ustar00rootroot00000000000000#ifndef VIEW_ACTION_INTERFACE_HPP #define VIEW_ACTION_INTERFACE_HPP #include "wayfire/action/action_interface.hpp" #include "wayfire/view.hpp" #include #include #include namespace wf { class view_action_interface_t : public action_interface_t { public: virtual ~view_action_interface_t() override; virtual bool execute(const std::string & name, const std::vector & args) override; void set_view(wayfire_view view); private: void _maximize(); void _unmaximize(); void _minimize(); void _unminimize(); void _make_sticky(); void _always_on_top(); std::tuple _expect_float(const std::vector & args, std::size_t position); std::tuple _expect_double(const std::vector & args, std::size_t position); std::tuple _expect_int(const std::vector & args, std::size_t position); std::tuple _validate_alpha(const std::vector & args); std::tuple _validate_geometry( const std::vector & args); std::tuple _validate_position( const std::vector & args); std::tuple _validate_size(const std::vector & args); std::tuple _validate_ws(const std::vector& args); void _set_alpha(float alpha); void _set_geometry(int x, int y, int w, int h); void _set_geometry_ppt(int x, int y, int w, int h); void _start_on_output(std::string output); void _move(int x, int y); void _resize(int w, int h); void _assign_ws(wf::point_t point); wf::geometry_t _get_workspace_grid_geometry(wf::output_t *output) const; wayfire_toplevel_view _view; wayfire_view _nontoplevel; }; } // End namespace wf. #endif // VIEW_ACTION_INTERFACE_HPP wayfire-0.8.1/plugins/window-rules/window-rules.cpp000066400000000000000000000133631457431457600224770ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "lambda-rules-registration.hpp" #include "view-action-interface.hpp" #include "wayfire/signal-provider.hpp" class wayfire_window_rules_t : public wf::per_output_plugin_instance_t { public: void init() override; void fini() override; void apply(const std::string & signal, wayfire_view view); private: void setup_rules_from_config(); wf::lexer_t _lexer; // Created rule handler. wf::signal::connection_t on_view_mapped = [=] (wf::view_mapped_signal *ev) { apply("created", ev->view); }; // Maximized rule handler. wf::signal::connection_t _tiled = [=] (wf::view_tiled_signal *ev) { apply("maximized", ev->view); apply("unmaximized", ev->view); }; // Minimized rule handler. wf::signal::connection_t _minimized = [=] (wf::view_minimized_signal *ev) { apply("minimized", ev->view); }; // Fullscreened rule handler. wf::signal::connection_t _fullscreened = [=] (wf::view_fullscreen_signal *ev) { apply("fullscreened", ev->view); }; // Auto-reload on changes to config file wf::signal::connection_t _reload_config = [=] (wf::reload_config_signal *ev) { setup_rules_from_config(); }; std::vector> _rules; wf::view_access_interface_t _access_interface; wf::view_action_interface_t _action_interface; nonstd::observer_ptr _lambda_registrations; }; void wayfire_window_rules_t::init() { // Get the lambda rules registrations. _lambda_registrations = wf::lambda_rules_registrations_t::get_instance(); _lambda_registrations->window_rule_instances++; setup_rules_from_config(); output->connect(&on_view_mapped); output->connect(&_tiled); output->connect(&_minimized); output->connect(&_fullscreened); wf::get_core().connect(&_reload_config); } void wayfire_window_rules_t::fini() { _lambda_registrations->window_rule_instances--; if (_lambda_registrations->window_rule_instances == 0) { wf::get_core().erase_data(); } } void wayfire_window_rules_t::apply(const std::string & signal, wayfire_view view) { if (view == nullptr) { return; } auto toplevel = toplevel_cast(view); if ((signal == "maximized") && (!toplevel || (toplevel->pending_tiled_edges() != wf::TILED_EDGES_ALL))) { return; } if ((signal == "unmaximized") && (!toplevel || (toplevel->pending_tiled_edges() == wf::TILED_EDGES_ALL))) { return; } for (const auto & rule : _rules) { _access_interface.set_view(view); _action_interface.set_view(view); auto error = rule->apply(signal, _access_interface, _action_interface); if (error) { LOGE("Window-rules: Error while executing rule on ", signal, " signal."); } } auto bounds = _lambda_registrations->rules(); auto begin = std::get<0>(bounds); auto end = std::get<1>(bounds); while (begin != end) { auto registration = std::get<1>(*begin); bool error = false; // Assume we will use the view access interface. _access_interface.set_view(view); wf::access_interface_t & access_iface = _access_interface; // If a custom access interface is set in the regoistration, use this one. if (registration->access_interface != nullptr) { access_iface = *registration->access_interface; } // Load if lambda wrapper. if (registration->if_lambda != nullptr) { registration->rule_instance->setIfLambda( [registration, signal, view] () -> bool { return registration->if_lambda(signal, view); }); } // Load else lambda wrapper. if (registration->else_lambda) { registration->rule_instance->setElseLambda( [registration, signal, view] () -> bool { return registration->else_lambda(signal, view); }); } // Run the lambda rule. error = registration->rule_instance->apply(signal, _access_interface); // Unload wrappers. registration->rule_instance->setIfLambda(nullptr); registration->rule_instance->setElseLambda(nullptr); if (error) { LOGE("Window-rules: Error while executing rule on signal: ", signal, ", rule text:", registration->rule); } ++begin; } } void wayfire_window_rules_t::setup_rules_from_config() { _rules.clear(); wf::option_wrapper_t> rule_list_option{"window-rules/rules"}; auto rule_list = rule_list_option.value(); for (const auto& [name, rule_str] : rule_list) { LOGD("Registering ", rule_str); _lexer.reset(rule_str); auto rule = wf::rule_parser_t().parse(_lexer); if (rule != nullptr) { _rules.push_back(rule); } } } DECLARE_WAYFIRE_PLUGIN(wf::per_output_plugin_t); wayfire-0.8.1/plugins/wm-actions/000077500000000000000000000000001457431457600167505ustar00rootroot00000000000000wayfire-0.8.1/plugins/wm-actions/meson.build000066400000000000000000000007301457431457600211120ustar00rootroot00000000000000wm_actions = shared_module('wm-actions', ['wm-actions.cpp'], include_directories: [wayfire_api_inc, wayfire_conf_inc, plugins_common_inc], dependencies: [wlroots, pixman, wfconfig, json], install: true, install_dir: join_paths(get_option('libdir'), 'wayfire')) install_headers( [ 'wm-actions-signals.hpp' ], subdir: 'wayfire/plugins' ) wayfire-0.8.1/plugins/wm-actions/wm-actions-signals.hpp000066400000000000000000000012561457431457600232040ustar00rootroot00000000000000#pragma once #include namespace wf { /** * on: output * when: Emitted whenever some entity requests that the view's above state * is supposed to change. * arguments: above: whether or not to set above state */ struct wm_actions_set_above_state_signal { wayfire_view view; /** The requested above state. If this is true, the view will be * added to the always-above layer. If it is false, the view will * be placed in the 'normal' workspace layer. */ bool above; }; /** * on: output * when: Emitted whenever a views above layer has been changed. */ struct wm_actions_above_changed_signal { wayfire_view view; }; } wayfire-0.8.1/plugins/wm-actions/wm-actions.cpp000066400000000000000000000374611457431457600215500ustar00rootroot00000000000000#include #include #include #include #include #include "plugins/common/wayfire/plugins/common/shared-core-data.hpp" #include "plugins/ipc/ipc-method-repository.hpp" #include "plugins/ipc/ipc-helpers.hpp" #include "plugins/ipc/ipc-activator.hpp" #include "wayfire/core.hpp" #include "wayfire/plugin.hpp" #include "wayfire/scene-operations.hpp" #include "wayfire/scene.hpp" #include "wayfire/signal-definitions.hpp" #include "wayfire/signal-provider.hpp" #include "wayfire/toplevel-view.hpp" #include "wayfire/window-manager.hpp" #include "wayfire/seat.hpp" #include "wm-actions-signals.hpp" class always_on_top_root_node_t : public wf::scene::output_node_t { public: using output_node_t::output_node_t; std::string stringify() const override { return "always-on-top for output " + get_output()->to_string() + " " + stringify_flags(); } }; class wayfire_wm_actions_output_t : public wf::per_output_plugin_instance_t { wf::scene::floating_inner_ptr always_above; bool showdesktop_active = false; wf::option_wrapper_t minimize{ "wm-actions/minimize"}; wf::option_wrapper_t toggle_maximize{ "wm-actions/toggle_maximize"}; wf::option_wrapper_t toggle_above{ "wm-actions/toggle_always_on_top"}; wf::option_wrapper_t toggle_fullscreen{ "wm-actions/toggle_fullscreen"}; wf::option_wrapper_t toggle_sticky{ "wm-actions/toggle_sticky"}; wf::option_wrapper_t send_to_back{ "wm-actions/send_to_back"}; wf::plugin_activation_data_t grab_interface = { .name = "wm-actions", .capabilities = 0, }; public: bool set_keep_above_state(wayfire_view view, bool above) { if (!view || !output->can_activate_plugin(&grab_interface)) { return false; } if (above) { wf::scene::readd_front(always_above, view->get_root_node()); view->store_data(std::make_unique(), "wm-actions-above"); } else { wf::scene::readd_front(output->wset()->get_node(), view->get_root_node()); if (view->has_data("wm-actions-above")) { view->erase_data("wm-actions-above"); } } wf::wm_actions_above_changed_signal data; data.view = view; output->emit(&data); return true; } /** * Find the selected toplevel view, or nullptr if the selected view is not * toplevel. */ wayfire_toplevel_view choose_view(wf::activator_source_t source) { wayfire_view view; if (source == wf::activator_source_t::BUTTONBINDING) { view = wf::get_core().get_cursor_focus_view(); } else { view = wf::get_core().seat->get_active_view(); } return wf::toplevel_cast(view); } /** * Calling a specific view / specific keep_above action via signal */ wf::signal::connection_t on_set_above_state_signal = [=] (wf::wm_actions_set_above_state_signal *signal) { if (!set_keep_above_state(signal->view, signal->above)) { LOG(wf::log::LOG_LEVEL_DEBUG, "view above action failed via signal."); } }; /** * Ensures views marked as above are still above if their output changes. */ wf::signal::connection_t on_view_output_changed = [=] (wf::view_moved_to_wset_signal *signal) { if (!signal->new_wset || (signal->new_wset->get_attached_output() != output)) { return; } auto view = signal->view; if (!view) { return; } if (view->has_data("wm-actions-above")) { wf::scene::readd_front(always_above, view->get_root_node()); } }; /** * Ensures views marked as above are still above if they are minimized and * unminimized. */ wf::signal::connection_t on_view_minimized = [=] (wf::view_minimized_signal *ev) { if (ev->view->get_output() != output) { return; } if (ev->view->has_data("wm-actions-above") && !ev->view->minimized) { wf::scene::readd_front(always_above, ev->view->get_root_node()); } }; void check_disable_showdesktop(wayfire_view view) { if ((view->role != wf::VIEW_ROLE_TOPLEVEL) || !view->is_mapped()) { return; } disable_showdesktop(); } /** * Disables show desktop if the workspace is changed or any view is attached, * mapped or unminimized. */ wf::signal::connection_t view_set_output = [=] (wf::view_set_output_signal *ev) { check_disable_showdesktop(ev->view); }; wf::signal::connection_t on_view_mapped = [=] (wf::view_mapped_signal *ev) { check_disable_showdesktop(ev->view); }; wf::signal::connection_t workspace_changed = [=] (wf::workspace_changed_signal *ev) { disable_showdesktop(); }; wf::signal::connection_t view_minimized = [=] (wf::view_minimized_signal *ev) { if ((ev->view->role != wf::VIEW_ROLE_TOPLEVEL) || !ev->view->is_mapped()) { return; } if (!ev->view->minimized) { disable_showdesktop(); } }; /** * Execute for_view on the selected view, if available. */ bool execute_for_selected_view(wf::activator_source_t source, std::function for_view) { auto view = choose_view(source); if (!view || !output->can_activate_plugin(&grab_interface)) { return false; } return for_view(view); } /** * The default activator bindings. */ wf::activator_callback on_toggle_above = [=] (auto ev) -> bool { auto view = choose_view(ev.source); if (view) { return set_keep_above_state(view, !view->has_data("wm-actions-above")); } else { return false; } }; wf::activator_callback on_minimize = [=] (auto ev) -> bool { return execute_for_selected_view(ev.source, [] (wayfire_toplevel_view view) { wf::get_core().default_wm->minimize_request(view, !view->minimized); return true; }); }; wf::activator_callback on_toggle_maximize = [=] (auto ev) -> bool { return execute_for_selected_view(ev.source, [] (wayfire_toplevel_view view) { wf::get_core().default_wm->tile_request(view, view->pending_tiled_edges() == wf::TILED_EDGES_ALL ? 0 : wf::TILED_EDGES_ALL); return true; }); }; wf::activator_callback on_toggle_fullscreen = [=] (auto ev) -> bool { return execute_for_selected_view(ev.source, [] (wayfire_toplevel_view view) { wf::get_core().default_wm->fullscreen_request(view, view->get_output(), !view->pending_fullscreen()); return true; }); }; wf::activator_callback on_toggle_sticky = [=] (auto ev) -> bool { return execute_for_selected_view(ev.source, [] (wayfire_toplevel_view view) { view->set_sticky(view->sticky ^ 1); return true; }); }; bool on_toggle_showdesktop() { showdesktop_active = !showdesktop_active; if (showdesktop_active) { for (auto& view : output->wset()->get_views()) { if (!view->minimized) { wf::get_core().default_wm->minimize_request(view, true); view->store_data(std::make_unique(), "wm-actions-showdesktop"); } } output->connect(&view_set_output); output->connect(&workspace_changed); output->connect(&view_minimized); output->connect(&on_view_mapped); return true; } disable_showdesktop(); return true; } void do_send_to_back(wayfire_view view) { auto view_root = view->get_root_node(); if (auto parent = dynamic_cast(view_root->parent())) { auto parent_children = parent->get_children(); parent_children.erase( std::remove(parent_children.begin(), parent_children.end(), view_root), parent_children.end()); parent_children.push_back(view_root); parent->set_children_list(parent_children); wf::scene::update(parent->shared_from_this(), wf::scene::update_flag::CHILDREN_LIST); } } wf::activator_callback on_send_to_back = [=] (auto ev) -> bool { return execute_for_selected_view(ev.source, [this] (wayfire_view view) { auto views = view->get_output()->wset()->get_views( wf::WSET_CURRENT_WORKSPACE | wf::WSET_MAPPED_ONLY | wf::WSET_EXCLUDE_MINIMIZED | wf::WSET_SORT_STACKING); wayfire_view bottom_view = views[views.size() - 1]; if (view != bottom_view) { do_send_to_back(view); // Change focus to the last focused view on this workspace // Update the list after restacking. views = view->get_output()->wset()->get_views( wf::WSET_CURRENT_WORKSPACE | wf::WSET_MAPPED_ONLY | wf::WSET_EXCLUDE_MINIMIZED | wf::WSET_SORT_STACKING); wf::get_core().seat->focus_view(views[0]); } return true; }); }; void disable_showdesktop() { view_set_output.disconnect(); workspace_changed.disconnect(); view_minimized.disconnect(); auto views = output->wset()->get_views(wf::WSET_SORT_STACKING); for (auto& view : wf::reverse(views)) { if (view->has_data("wm-actions-showdesktop")) { view->erase_data("wm-actions-showdesktop"); wf::get_core().default_wm->minimize_request(view, false); } } showdesktop_active = false; } public: void init() override { always_above = std::make_shared(output); wf::scene::add_front(wf::get_core().scene()->layers[(int)wf::scene::layer::WORKSPACE], always_above); output->add_activator(minimize, &on_minimize); output->add_activator(toggle_maximize, &on_toggle_maximize); output->add_activator(toggle_above, &on_toggle_above); output->add_activator(toggle_fullscreen, &on_toggle_fullscreen); output->add_activator(toggle_sticky, &on_toggle_sticky); output->add_activator(send_to_back, &on_send_to_back); output->connect(&on_set_above_state_signal); output->connect(&on_view_minimized); wf::get_core().connect(&on_view_output_changed); } void fini() override { for (auto view : output->wset()->get_views()) { if (view->has_data("wm-actions-above")) { set_keep_above_state(view, false); } } wf::scene::remove_child(always_above); output->rem_binding(&on_minimize); output->rem_binding(&on_toggle_maximize); output->rem_binding(&on_toggle_above); output->rem_binding(&on_toggle_fullscreen); output->rem_binding(&on_toggle_sticky); output->rem_binding(&on_send_to_back); } }; class wayfire_wm_actions_t : public wf::plugin_interface_t, public wf::per_output_tracker_mixin_t { wf::shared_data::ref_ptr_t ipc_repo; wf::ipc_activator_t toggle_showdesktop{"wm-actions/toggle_showdesktop"}; public: void init() override { init_output_tracking(); ipc_repo->register_method("wm-actions/set-minimized", ipc_minimize); ipc_repo->register_method("wm-actions/set-always-on-top", ipc_set_always_on_top); ipc_repo->register_method("wm-actions/set-fullscreen", ipc_set_fullscreen); ipc_repo->register_method("wm-actions/set-sticky", ipc_set_sticky); ipc_repo->register_method("wm-actions/send-to-back", ipc_send_to_back); toggle_showdesktop.set_handler(on_toggle_showdesktop); } void fini() override { fini_output_tracking(); ipc_repo->unregister_method("wm-actions/set-minimized"); ipc_repo->unregister_method("wm-actions/set-always-on-top"); ipc_repo->unregister_method("wm-actions/set-fullscreen"); ipc_repo->unregister_method("wm-actions/set-sticky"); ipc_repo->unregister_method("wm-actions/send-to-back"); } nlohmann::json execute_for_view(const nlohmann::json& params, std::function view_op) { WFJSON_EXPECT_FIELD(params, "view_id", number_integer); WFJSON_EXPECT_FIELD(params, "state", boolean); wayfire_toplevel_view view = toplevel_cast(wf::ipc::find_view_by_id(params["view_id"])); if (!view) { return wf::ipc::json_error("toplevel view id not found!"); } bool state = params["state"]; view_op(view, state); return wf::ipc::json_ok(); } wf::ipc::method_callback ipc_minimize = [=] (const nlohmann::json& js) { return execute_for_view(js, [=] (wayfire_toplevel_view view, bool state) { wf::get_core().default_wm->minimize_request(view, state); }); }; wf::ipc::method_callback ipc_maximize = [=] (const nlohmann::json& js) { return execute_for_view(js, [=] (wayfire_toplevel_view view, bool state) { wf::get_core().default_wm->tile_request(view, state ? wf::TILED_EDGES_ALL : 0); }); }; wf::ipc::method_callback ipc_set_always_on_top = [=] (const nlohmann::json& js) { return execute_for_view(js, [=] (wayfire_toplevel_view view, bool state) { if (!view->get_output()) { view->store_data(std::make_unique(), "wm-actions-above"); return; } output_instance[view->get_output()]->set_keep_above_state(view, state); }); }; wf::ipc::method_callback ipc_set_fullscreen = [=] (const nlohmann::json& js) { return execute_for_view(js, [=] (wayfire_toplevel_view view, bool state) { wf::get_core().default_wm->fullscreen_request(view, nullptr, state); }); }; wf::ipc::method_callback ipc_set_sticky = [=] (const nlohmann::json& js) { return execute_for_view(js, [=] (wayfire_toplevel_view view, bool state) { view->set_sticky(state); }); }; wf::ipc::method_callback ipc_send_to_back = [=] (const nlohmann::json& js) { return execute_for_view(js, [=] (wayfire_toplevel_view view, bool state) { if (!view->get_output()) { return; } output_instance[view->get_output()]->do_send_to_back(view); }); }; wf::ipc_activator_t::handler_t on_toggle_showdesktop = [=] (wf::output_t *output, wayfire_view) { return this->output_instance[output]->on_toggle_showdesktop(); }; }; DECLARE_WAYFIRE_PLUGIN(wayfire_wm_actions_t); wayfire-0.8.1/plugins/wobbly/000077500000000000000000000000001457431457600161655ustar00rootroot00000000000000wayfire-0.8.1/plugins/wobbly/meson.build000066400000000000000000000007711457431457600203340ustar00rootroot00000000000000wobbly = shared_module('wobbly', ['wobbly.cpp', 'wobbly.c'], include_directories: [wayfire_api_inc, wayfire_conf_inc, plugins_common_inc], dependencies: [wlroots, pixman, wfconfig], install: true, install_dir: join_paths(get_option('libdir'), 'wayfire')) wobbly_inc = include_directories('.') install_headers(['wayfire/plugins/wobbly/wobbly-signal.hpp'], subdir: 'wayfire/plugins/wobbly') wayfire-0.8.1/plugins/wobbly/wayfire/000077500000000000000000000000001457431457600176335ustar00rootroot00000000000000wayfire-0.8.1/plugins/wobbly/wayfire/plugins/000077500000000000000000000000001457431457600213145ustar00rootroot00000000000000wayfire-0.8.1/plugins/wobbly/wayfire/plugins/wobbly/000077500000000000000000000000001457431457600226125ustar00rootroot00000000000000wayfire-0.8.1/plugins/wobbly/wayfire/plugins/wobbly/wobbly-signal.hpp000066400000000000000000000071551457431457600261040ustar00rootroot00000000000000#pragma once #include #include enum wobbly_event { WOBBLY_EVENT_GRAB = (1 << 0), WOBBLY_EVENT_MOVE = (1 << 1), WOBBLY_EVENT_END = (1 << 2), WOBBLY_EVENT_ACTIVATE = (1 << 3), WOBBLY_EVENT_TRANSLATE = (1 << 4), WOBBLY_EVENT_FORCE_TILE = (1 << 5), WOBBLY_EVENT_UNTILE = (1 << 6), WOBBLY_EVENT_SCALE = (1 << 7), }; /** * on: core * when: This signal is used to control(start/stop/update) the wobbly state * for a view. Note that plugins usually would use the helper functions below, * instead of emitting this signal directly. */ struct wobbly_signal { wayfire_toplevel_view view; wobbly_event events; /** * For EVENT_GRAB and EVENT_MOVE: the coordinates of the grab * For EVENT_TRANSLATE: the amount of translation */ wf::point_t pos; /** * For EVENT_SCALE: the new size of the base surface. */ wf::geometry_t geometry; }; /** * Start wobblying when the view is being grabbed, for ex. when moving it */ inline void start_wobbly(wayfire_toplevel_view view, int grab_x, int grab_y) { wobbly_signal sig; sig.view = view; sig.events = WOBBLY_EVENT_GRAB; sig.pos = {grab_x, grab_y}; wf::get_core().emit(&sig); } /** * Start wobblying when the view is being grabbed, for ex. when moving it. * The position is relative to the view, i.e [0.5, 0.5] is the midpoint. */ inline void start_wobbly_rel(wayfire_toplevel_view view, wf::pointf_t rel_grab) { wobbly_signal sig; sig.view = view; sig.events = WOBBLY_EVENT_GRAB; auto bbox = view->get_bounding_box(); sig.pos.x = bbox.x + rel_grab.x * bbox.width; sig.pos.y = bbox.y + rel_grab.y * bbox.height; wf::get_core().emit(&sig); } /** * Release the wobbly grab */ inline void end_wobbly(wayfire_toplevel_view view) { wobbly_signal sig; sig.view = view; sig.events = WOBBLY_EVENT_END; wf::get_core().emit(&sig); } /** * Indicate that the grab has moved (i.e cursor moved, touch moved, etc.) */ inline void move_wobbly(wayfire_toplevel_view view, int grab_x, int grab_y) { wobbly_signal sig; sig.view = view; sig.events = WOBBLY_EVENT_MOVE; sig.pos = {grab_x, grab_y}; wf::get_core().emit(&sig); } /** * Temporarily activate wobbly on the view. * This is useful when animating some transition like fullscreening, tiling, etc. */ inline void activate_wobbly(wayfire_toplevel_view view) { if (!view->get_transformed_node()->get_transformer("wobbly")) { wobbly_signal sig; sig.view = view; sig.events = WOBBLY_EVENT_ACTIVATE; wf::get_core().emit(&sig); } } /** * Translate the wobbly model (and its grab point, if any). */ inline void translate_wobbly(wayfire_toplevel_view view, wf::point_t delta) { wobbly_signal sig; sig.view = view; sig.events = WOBBLY_EVENT_TRANSLATE; sig.pos = delta; wf::get_core().emit(&sig); } /** * Set the wobbly model forcibly (un)tiled. * This means that its four corners will be held in place, until the model is * untiled. */ inline void set_tiled_wobbly(wayfire_toplevel_view view, bool tiled) { wobbly_signal sig; sig.view = view; sig.events = tiled ? WOBBLY_EVENT_FORCE_TILE : WOBBLY_EVENT_UNTILE; wf::get_core().emit(&sig); } /** * Change the wobbly model geometry, without re-activating the springs. */ inline void modify_wobbly(wayfire_toplevel_view view, wf::geometry_t target) { wobbly_signal sig; sig.view = view; sig.events = WOBBLY_EVENT_SCALE; sig.geometry = target; wf::get_core().emit(&sig); } wayfire-0.8.1/plugins/wobbly/wobbly.c000066400000000000000000000517741457431457600176450ustar00rootroot00000000000000/* * Copyright © 2005 Novell, Inc. * Copyright © 2014 Scott Moreau * * Permission to use, copy, modify, distribute, and sell this software * and its documentation for any purpose is hereby granted without * fee, provided that the above copyright notice appear in all copies * and that both that copyright notice and this permission notice * appear in supporting documentation, and that the name of * Novell, Inc. not be used in advertising or publicity pertaining to * distribution of the software without specific, written prior permission. * Novell, Inc. makes no representations about the suitability of this * software for any purpose. It is provided "as is" without express or * implied warranty. * * NOVELL, INC. DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN * NO EVENT SHALL NOVELL, INC. BE LIABLE FOR ANY SPECIAL, INDIRECT OR * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION * WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * * Author: David Reveman * Scott Moreau */ /* * Spring model implemented by Kristian Hogsberg. */ #include #include #include #include #include #include "wobbly.h" #define GRID_WIDTH 4 #define GRID_HEIGHT 4 #define MODEL_MAX_SPRINGS (GRID_WIDTH * GRID_HEIGHT * 2) typedef struct _xy_pair { float x, y; } Point, Vector; typedef struct _Edge { float next, prev; float start; float end; float attract; float velocity; } Edge; typedef struct _Object { Vector force; Point position; Vector velocity; float theta; int immobile; Edge vertEdge; Edge horzEdge; } Object; typedef struct _Spring { Object *a; Object *b; Vector offset; } Spring; typedef struct _Model { Object *objects; int numObjects; Spring springs[MODEL_MAX_SPRINGS]; int numSprings; Object *anchorObject; float steps; Point topLeft; Point bottomRight; } Model; typedef struct _WobblyWindow { Model *model; int wobbly; int grabbed; int velocity; int grab_dx; int grab_dy; unsigned int state; } WobblyWindow; #define WobblyInitial (1L << 0) #define WobblyForce (1L << 1) #define WobblyVelocity (1L << 2) static void objectInit(Object *object, float positionX, float positionY, float velocityX, float velocityY) { object->force.x = 0; object->force.y = 0; object->position.x = positionX; object->position.y = positionY; object->velocity.x = velocityX; object->velocity.y = velocityY; object->theta = 0; object->immobile = 0; object->vertEdge.next = 0.0f; object->horzEdge.next = 0.0f; } static void springInit(Spring *spring, Object *a, Object *b, float offsetX, float offsetY) { spring->a = a; spring->b = b; spring->offset.x = offsetX; spring->offset.y = offsetY; } static void modelCalcBounds(Model *model) { int i; model->topLeft.x = SHRT_MAX; model->topLeft.y = SHRT_MAX; model->bottomRight.x = SHRT_MIN; model->bottomRight.y = SHRT_MIN; for (i = 0; i < model->numObjects; i++) { if (model->objects[i].position.x < model->topLeft.x) model->topLeft.x = model->objects[i].position.x; else if (model->objects[i].position.x > model->bottomRight.x) model->bottomRight.x = model->objects[i].position.x; if (model->objects[i].position.y < model->topLeft.y) model->topLeft.y = model->objects[i].position.y; else if (model->objects[i].position.y > model->bottomRight.y) model->bottomRight.y = model->objects[i].position.y; } } static void modelAddSpring(Model *model, Object *a, Object *b, float offsetX, float offsetY) { Spring *spring; spring = &model->springs[model->numSprings]; model->numSprings++; springInit (spring, a, b, offsetX, offsetY); } static void modelSetMiddleAnchor(Model *model, int x, int y, int width, int height) { float gx, gy; gx = ((GRID_WIDTH - 1) / 2 * width) / (float) (GRID_WIDTH - 1); gy = ((GRID_HEIGHT - 1) / 2 * height) / (float) (GRID_HEIGHT - 1); if (model->anchorObject) model->anchorObject->immobile = 0; model->anchorObject = &model->objects[GRID_WIDTH * ((GRID_HEIGHT-1)/2) + (GRID_WIDTH-1)/ 2]; model->anchorObject->position.x = x + gx; model->anchorObject->position.y = y + gy; model->anchorObject->immobile = 1; } static void modelSetTopAnchor(Model *model, int x, int y, int width) { float gx; gx = ((GRID_WIDTH - 1) / 2 * width) / (float) (GRID_WIDTH - 1); if (model->anchorObject) model->anchorObject->immobile = 0; model->anchorObject = &model->objects[(GRID_WIDTH-1)/ 2]; model->anchorObject->position.x = x + gx; model->anchorObject->position.y = y; model->anchorObject->immobile = 1; } static void modelInitObjects(Model *model, int x, int y, int width, int height) { int gridX, gridY, i = 0; float gw, gh; gw = GRID_WIDTH - 1; gh = GRID_HEIGHT - 1; for (gridY = 0; gridY < GRID_HEIGHT; gridY++) { for (gridX = 0; gridX < GRID_WIDTH; gridX++) { objectInit (&model->objects[i], x + (gridX * width) / gw, y + (gridY * height) / gh, 0, 0); i++; } } if (!model->anchorObject) modelSetMiddleAnchor (model, x, y, width, height); } static void modelInitSprings(Model *model, int width, int height) { int gridX, gridY, i = 0; float hpad, vpad; model->numSprings = 0; hpad = ((float) width) / (GRID_WIDTH - 1); vpad = ((float) height) / (GRID_HEIGHT - 1); for (gridY = 0; gridY < GRID_HEIGHT; gridY++) { for (gridX = 0; gridX < GRID_WIDTH; gridX++) { if (gridX > 0) { modelAddSpring (model, &model->objects[i - 1], &model->objects[i], hpad, 0); } if (gridY > 0) { modelAddSpring (model, &model->objects[i - GRID_WIDTH], &model->objects[i], 0, vpad); } i++; } } } static Model * createModel(int x, int y, int width, int height) { Model *model; model = malloc(sizeof(Model)); if (!model) return 0; model->numObjects = GRID_WIDTH * GRID_HEIGHT; model->objects = malloc (sizeof (Object) * model->numObjects); if (!model->objects) { free (model); return 0; } model->anchorObject = 0; model->numSprings = 0; model->steps = 0; modelInitObjects (model, x, y, width, height); modelInitSprings (model, width, height); modelCalcBounds (model); return model; } static void objectApplyForce(Object *object, float fx, float fy) { object->force.x += fx; object->force.y += fy; } static void springExertForces(Spring *spring, float k) { Vector da, db; Vector a, b; a = spring->a->position; b = spring->b->position; da.x = 0.5f * (b.x - a.x - spring->offset.x); da.y = 0.5f * (b.y - a.y - spring->offset.y); db.x = 0.5f * (a.x - b.x + spring->offset.x); db.y = 0.5f * (a.y - b.y + spring->offset.y); objectApplyForce (spring->a, k * da.x, k * da.y); objectApplyForce (spring->b, k * db.x, k * db.y); } static float modelStepObject(Object *object, float friction, float *force) { object->theta += 0.05f; if (object->immobile) { object->velocity.x = 0.0f; object->velocity.y = 0.0f; object->force.x = 0.0f; object->force.y = 0.0f; *force = 0.0f; return 0.0f; } else { object->force.x -= friction * object->velocity.x; object->force.y -= friction * object->velocity.y; object->velocity.x += object->force.x / WOBBLY_MASS; object->velocity.y += object->force.y / WOBBLY_MASS; object->position.x += object->velocity.x; object->position.y += object->velocity.y; *force = fabs(object->force.x) + fabs(object->force.y); object->force.x = 0.0f; object->force.y = 0.0f; return fabs(object->velocity.x) + fabs(object->velocity.y); } } static int modelStep(Model *model, float friction, float k, float time) { int i, j, steps, wobbly = 0; float velocitySum = 0.0f; float force, forceSum = 0.0f; model->steps += time / 15.0f; steps = floor (model->steps); model->steps -= steps; if (!steps) return 1; for (j = 0; j < steps; j++) { for (i = 0; i < model->numSprings; i++) springExertForces (&model->springs[i], k); for (i = 0; i < model->numObjects; i++) { velocitySum += modelStepObject(&model->objects[i], friction, &force); forceSum += force; } } modelCalcBounds (model); if (velocitySum > 0.5f) wobbly |= WobblyVelocity; if (forceSum > 20.0f) wobbly |= WobblyForce; return wobbly; } static void bezierPatchEvaluate (Model *model, float u, float v, float *patchX, float *patchY) { float coeffsU[4], coeffsV[4]; float x, y; int i, j; coeffsU[0] = (1 - u) * (1 - u) * (1 - u); coeffsU[1] = 3 * u * (1 - u) * (1 - u); coeffsU[2] = 3 * u * u * (1 - u); coeffsU[3] = u * u * u; coeffsV[0] = (1 - v) * (1 - v) * (1 - v); coeffsV[1] = 3 * v * (1 - v) * (1 - v); coeffsV[2] = 3 * v * v * (1 - v); coeffsV[3] = v * v * v; x = y = 0.0f; for (i = 0; i < 4; i++) { for (j = 0; j < 4; j++) { x += coeffsU[i] * coeffsV[j] * model->objects[j * GRID_WIDTH + i].position.x; y += coeffsU[i] * coeffsV[j] * model->objects[j * GRID_HEIGHT + i].position.y; } } *patchX = x; *patchY = y; } static int wobblyEnsureModel(struct wobbly_surface *surface) { WobblyWindow *ww = surface->ww; if (!ww->model) { ww->model = createModel(surface->x, surface->y, surface->width, surface->height); if (!ww->model) return 0; } return 1; } static float objectDistance(Object *object, float x, float y) { float dx, dy; dx = object->position.x - x; dy = object->position.y - y; return sqrt(dx * dx + dy * dy); } static Object *modelFindNearestObject(Model *model, float x, float y) { Object *object = &model->objects[0]; float distance, minDistance = 0.0; int i; for (i = 0; i < model->numObjects; i++) { distance = objectDistance(&model->objects[i], x, y); if (i == 0 || distance < minDistance) { minDistance = distance; object = &model->objects[i]; } } return object; } static void modelAdjustCorners(Model *model, int x, int y, int width, int height, int make_immobile) { Object *o; o = &model->objects[0]; o->position.x = x; o->position.y = y; o->immobile = make_immobile; o = &model->objects[GRID_WIDTH - 1]; o->position.x = x + width; o->position.y = y; o->immobile = make_immobile; o = &model->objects[GRID_WIDTH * (GRID_HEIGHT - 1)]; o->position.x = x; o->position.y = y + height; o->immobile = make_immobile; o = &model->objects[model->numObjects - 1]; o->position.x = x + width; o->position.y = y + height; o->immobile = make_immobile; if (!model->anchorObject) model->anchorObject = &model->objects[0]; } static int modelRemoveEdgeAnchors(Model *model) { int result = 0; Object *o; o = &model->objects[0]; if (o != model->anchorObject) { result |= o->immobile; o->immobile = 0; } o = &model->objects[GRID_WIDTH - 1]; if (o != model->anchorObject) { result |= o->immobile; o->immobile = 0; } o = &model->objects[GRID_WIDTH * (GRID_HEIGHT - 1)]; if (o != model->anchorObject) { result |= o->immobile; o->immobile = 0; } o = &model->objects[model->numObjects - 1]; if (o != model->anchorObject) { result |= o->immobile; o->immobile = 0; } return result; } void wobbly_prepare_paint(struct wobbly_surface *surface, int msSinceLastPaint) { WobblyWindow *ww = surface->ww; float friction, springK; friction = wobbly_settings_get_friction(); springK = wobbly_settings_get_spring_k(); if (ww->wobbly) { if (ww->wobbly & (WobblyInitial | WobblyVelocity | WobblyForce)) { ww->wobbly = modelStep(ww->model, friction, springK, (ww->wobbly & WobblyVelocity) ? msSinceLastPaint : 16); if (ww->wobbly) { modelCalcBounds(ww->model); } else { surface->x = ww->model->topLeft.x; surface->y = ww->model->topLeft.y; surface->synced = 1; } } } } void wobbly_done_paint(struct wobbly_surface *surface) { WobblyWindow *ww = (WobblyWindow*)surface->ww; if (ww->wobbly) { surface->x = ww->model->topLeft.x; surface->y = ww->model->topLeft.y; } } void wobbly_add_geometry(struct wobbly_surface *surface) { WobblyWindow *ww = surface->ww; float width, height; float deformedX, deformedY; int x, y, iw, ih; float cell_w, cell_h; GLfloat *v, *uv; if (ww->wobbly) { width = surface->width; height = surface->height; cell_w = width / surface->x_cells; cell_h = height / surface->y_cells; iw = surface->x_cells + 1; ih = surface->y_cells + 1; v = realloc(surface->v, sizeof(GLfloat) * 2 * iw * ih); uv = realloc(surface->uv, sizeof(GLfloat) * 2 * iw * ih); surface->v = v; surface->uv = uv; for (y = 0; y < ih; y++) { for (x = 0; x < iw; x++) { bezierPatchEvaluate(ww->model, (x * cell_w) / width, (y * cell_h) / height, &deformedX, &deformedY); *v++ = deformedX; *v++ = deformedY; *uv++ = (x * cell_w) / width; *uv++ = 1.0 - ((y * cell_h) / height); } } } } void wobbly_resize(struct wobbly_surface *surface, int width, int height) { WobblyWindow *ww = surface->ww; surface->synced = 0; ww->wobbly |= WobblyInitial; if (ww->model) modelInitSprings(ww->model, width, height); ww->grab_dx = (ww->grab_dx * width) / surface->width; ww->grab_dy = (ww->grab_dy * height) / surface->height; surface->width = width; surface->height = height; } void wobbly_move_notify(struct wobbly_surface *surface, int x, int y) { WobblyWindow *ww = surface->ww; if (ww->grabbed) { ww->model->anchorObject->position.x = x + ww->grab_dx; ww->model->anchorObject->position.y = y + ww->grab_dy; ww->wobbly |= WobblyInitial; surface->synced = 0; } } void wobbly_slight_wobble(struct wobbly_surface *surface) { WobblyWindow *ww = surface->ww; if (wobblyEnsureModel(surface)) { Object *centerObj; Spring *s; int i; centerObj = modelFindNearestObject(ww->model, surface->x + surface->width / 2, surface->y + surface->height / 2); for (i = 0; i < ww->model->numSprings; i++) { s = &ww->model->springs[i]; if (s->a == centerObj) { s->b->velocity.x -= s->offset.x * 0.05f; s->b->velocity.y -= s->offset.y * 0.05f; } else if (s->b == centerObj) { s->a->velocity.x += s->offset.x * 0.05f; s->a->velocity.y += s->offset.y * 0.05f; } } ww->wobbly |= WobblyInitial; } } void wobbly_set_top_anchor(struct wobbly_surface *surface, int x, int y, int w, int h) { (void)h; WobblyWindow *ww = surface->ww; if (wobblyEnsureModel(surface)) { modelSetTopAnchor(ww->model, x, y, w); } } void wobbly_grab_notify(struct wobbly_surface *surface, int x, int y) { WobblyWindow *ww = surface->ww; if (wobblyEnsureModel(surface)) { Spring *s; int i; if (ww->model->anchorObject) ww->model->anchorObject->immobile = 0; ww->model->anchorObject = modelFindNearestObject(ww->model, x, y); ww->model->anchorObject->immobile = 1; ww->grab_dx = ww->model->anchorObject->position.x - x; ww->grab_dy = ww->model->anchorObject->position.y - y; ww->grabbed = 1; for (i = 0; i < ww->model->numSprings; i++) { s = &ww->model->springs[i]; if (s->a == ww->model->anchorObject) { s->b->velocity.x -= s->offset.x * 0.05f; s->b->velocity.y -= s->offset.y * 0.05f; } else if (s->b == ww->model->anchorObject) { s->a->velocity.x += s->offset.x * 0.05f; s->a->velocity.y += s->offset.y * 0.05f; } } ww->wobbly |= WobblyInitial; } } void wobbly_ungrab_notify(struct wobbly_surface *surface) { WobblyWindow *ww = surface->ww; if (ww->grabbed) { if (ww->model) { if (ww->model->anchorObject) ww->model->anchorObject->immobile = 0; ww->model->anchorObject = NULL; ww->wobbly |= WobblyInitial; } surface->synced = 0; ww->grabbed = 0; } } int wobbly_init(struct wobbly_surface *surface) { WobblyWindow *ww; ww = malloc(sizeof (WobblyWindow)); if (!ww) return 0; ww->model = 0; ww->wobbly = 0; ww->grabbed = 0; ww->state = 0; surface->ww = ww; if(!wobblyEnsureModel(surface)) { free(ww); return 0; } return 1; } void wobbly_fini(struct wobbly_surface *surface) { WobblyWindow *ww = surface->ww; if (ww->model) { free(ww->model->objects); free(ww->model); free(surface->v); } free (ww); } void wobbly_force_geometry(struct wobbly_surface *surface, int x, int y, int w, int h) { WobblyWindow *ww = surface->ww; if (wobblyEnsureModel(surface)) { if (!ww->grabbed && ww->model->anchorObject) { ww->model->anchorObject->immobile = 0; ww->model->anchorObject = NULL; } surface->x = x; surface->y = y; surface->width = w; surface->height = h; surface->synced = 0; modelInitSprings(ww->model, w, h); modelAdjustCorners(ww->model, x, y, w, h, 1); ww->wobbly |= WobblyInitial; } } void wobbly_unenforce_geometry(struct wobbly_surface *surface) { WobblyWindow *ww = surface->ww; if (wobblyEnsureModel(surface)) { if (modelRemoveEdgeAnchors(ww->model)) { if (!ww->model->anchorObject || !ww->model->anchorObject->immobile) { modelSetMiddleAnchor(ww->model, surface->x, surface->y, surface->width, surface->height); } modelInitSprings(ww->model, surface->width, surface->height); } ww->wobbly |= WobblyInitial; } } void wobbly_translate(struct wobbly_surface *surface, int dx, int dy) { WobblyWindow *ww = surface->ww; if (wobblyEnsureModel(surface)) { for (int i = 0; i < ww->model->numObjects; i++) { ww->model->objects[i].position.x += dx; ww->model->objects[i].position.y += dy; } ww->model->topLeft.x += dx; ww->model->topLeft.y += dy; ww->model->bottomRight.x += dx; ww->model->bottomRight.y += dy; } } static void scale(float origin, float *x, double scale) { *x = (*x - origin) * scale + origin; } void wobbly_scale(struct wobbly_surface *surface, double dx, double dy) { WobblyWindow *ww = surface->ww; if (wobblyEnsureModel(surface)) { for (int i = 0; i < ww->model->numObjects; i++) { scale(surface->x, &ww->model->objects[i].position.x, dx); scale(surface->y, &ww->model->objects[i].position.y, dy); } scale(surface->x, &ww->model->topLeft.x, dx); scale(surface->y, &ww->model->topLeft.y, dy); scale(surface->x, &ww->model->bottomRight.x, dx); scale(surface->y, &ww->model->bottomRight.y, dy); } } struct wobbly_rect wobbly_boundingbox(struct wobbly_surface *surface) { WobblyWindow *ww = surface->ww; struct wobbly_rect result; memset(&result, 0, sizeof(result)); if (ww->model) { result.tlx = ww->model->topLeft.x; result.tly = ww->model->topLeft.y; result.brx = ww->model->bottomRight.x; result.bry = ww->model->bottomRight.y; } return result; } wayfire-0.8.1/plugins/wobbly/wobbly.cpp000066400000000000000000000664061457431457600202030ustar00rootroot00000000000000#include "wayfire/debug.hpp" #include "wayfire/opengl.hpp" #include "wayfire/region.hpp" #include #include #include #include #include #include #include #include extern "C" { #include "wobbly.h" } #include "wayfire/plugins/wobbly/wobbly-signal.hpp" namespace wobbly_graphics { namespace { const char *vertex_source = R"( #version 100 attribute mediump vec2 position; attribute mediump vec2 uvPosition; varying highp vec2 uvpos; uniform mat4 MVP; void main() { gl_Position = MVP * vec4(position.xy, 0.0, 1.0); uvpos = uvPosition; } )"; const char *frag_source = R"( #version 100 @builtin_ext@ varying highp vec2 uvpos; @builtin@ void main() { gl_FragColor = get_pixel(uvpos); } )"; } /** * Enumerate the needed triangles for rendering the model */ void prepare_geometry(wobbly_surface *model, wf::geometry_t src_box, std::vector& vert, std::vector& uv) { float x = src_box.x, y = src_box.y, w = src_box.width, h = src_box.height; std::vector idx; int per_row = model->x_cells + 1; for (int j = 0; j < model->y_cells; j++) { for (int i = 0; i < model->x_cells; i++) { idx.push_back(i * per_row + j); idx.push_back((i + 1) * per_row + j + 1); idx.push_back(i * per_row + j + 1); idx.push_back(i * per_row + j); idx.push_back((i + 1) * per_row + j); idx.push_back((i + 1) * per_row + j + 1); } } if (!model->v || !model->uv) { for (auto id : idx) { float tile_w = w / model->x_cells; float tile_h = h / model->y_cells; int i = id / per_row; int j = id % per_row; vert.push_back(i * tile_w + x); vert.push_back(j * tile_h + y); uv.push_back(1.0f * i / model->x_cells); uv.push_back(1.0f - 1.0f * j / model->y_cells); } } else { for (auto i : idx) { vert.push_back(model->v[2 * i]); vert.push_back(model->v[2 * i + 1]); uv.push_back(model->uv[2 * i]); uv.push_back(model->uv[2 * i + 1]); } } } /* Requires bound opengl context */ void render_triangles(OpenGL::program_t *program, wf::texture_t tex, glm::mat4 mat, float *pos, float *uv, int cnt) { program->use(tex.type); program->set_active_texture(tex); program->attrib_pointer("position", 2, 0, pos); program->attrib_pointer("uvPosition", 2, 0, uv); program->uniformMatrix4f("MVP", mat); GL_CALL(glEnable(GL_BLEND)); GL_CALL(glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA)); GL_CALL(glDrawArrays(GL_TRIANGLES, 0, 3 * cnt)); GL_CALL(glDisable(GL_BLEND)); program->deactivate(); } } namespace wobbly_settings { wf::option_wrapper_t friction{"wobbly/friction"}; wf::option_wrapper_t spring_k{"wobbly/spring_k"}; wf::option_wrapper_t resolution{"wobbly/grid_resolution"}; } extern "C" { double wobbly_settings_get_friction() { return wf::clamp((double)wobbly_settings::friction, MINIMAL_FRICTION, MAXIMAL_FRICTION); } double wobbly_settings_get_spring_k() { return wf::clamp((double)wobbly_settings::spring_k, MINIMAL_SPRING_K, MAXIMAL_SPRING_K); } } namespace wf { using wobbly_model_t = std::unique_ptr; static const std::string wobbly_transformer_name = "wobbly"; /** * Different states in which wobbly can be */ enum ewobbly_state_t { WOBBLY_STATE_FLOATING = 0, WOBBLY_STATE_FREE = 1, WOBBLY_STATE_GRABBED = 2, WOBBLY_STATE_TILED = 3, WOBBLY_STATE_TILED_GRABBED = 4, }; /** * Interface representing the wobbly state. */ class iwobbly_state_t { public: virtual ~iwobbly_state_t() = default; iwobbly_state_t(const iwobbly_state_t &) = delete; iwobbly_state_t(iwobbly_state_t &&) = delete; iwobbly_state_t& operator =(const iwobbly_state_t&) = delete; iwobbly_state_t& operator =(iwobbly_state_t&&) = delete; /** Called when the state has been updated. */ virtual void handle_state_update_done() {} /** Called when a grab starts */ virtual void handle_grab_start(wf::point_t grab, bool takeover) {} /** Called when the wobbly grab is moved. */ virtual void handle_grab_move(wf::point_t grab) {} /** Query the last grab point */ virtual wf::point_t get_grab_position() const { return {0, 0}; } /** * Called when the wobbly grab is ended. * @param release_grab Whether to remove the grabbed object in the model. */ virtual void handle_grab_end(bool release_grab) {} /** Called when the next frame is being prepared */ virtual void handle_frame() { this->bounding_box = wf::view_bounding_box_up_to(view, "wobbly"); } /** Called when the view wm geometry changes */ virtual void handle_wm_geometry(const wf::geometry_t& old_wm_geometry) {} /** Called when the workspace is changed. */ virtual void handle_workspace_change(wf::point_t old, wf::point_t cur) {} /** @return true if the wobbly animation is done. */ virtual bool is_wobbly_done() const { return model->synced; } /** @return the current state of wobbly */ virtual ewobbly_state_t get_wobbly_state() const = 0; /** * This isn't really meant to be used standalone, only subclasses should * be instantiated. */ iwobbly_state_t(const wobbly_model_t& m, wayfire_toplevel_view v) : view(v), model(m) { bounding_box = {model->x, model->y, model->width, model->height}; } /** * Translate the model by the given offset */ virtual void translate_model(int dx, int dy) { wobbly_translate(model.get(), dx, dy); wobbly_add_geometry(model.get()); bounding_box.x += dx; bounding_box.y += dy; model->x += dx; model->y += dy; } virtual void update_base_geometry(wf::geometry_t base) { wobbly_scale(model.get(), 1.0 * base.width / bounding_box.width, 1.0 * base.height / bounding_box.height); wobbly_translate(model.get(), base.x - bounding_box.x, base.y - bounding_box.y); wobbly_resize(model.get(), base.width, base.height); this->bounding_box = base; model->x = base.x; model->y = base.y; model->width = base.width; model->height = base.height; } protected: wayfire_toplevel_view view; const wobbly_model_t& model; wf::geometry_t bounding_box; }; /** * Determines the behavior of the wobbly model when the view is grabbed, * for ex. when moving or resizing. */ class wobbly_state_grabbed_t : public iwobbly_state_t { public: using iwobbly_state_t::iwobbly_state_t; virtual void handle_grab_start(wf::point_t grab, bool takeover) override { this->last_grab = {(int)grab.x, (int)grab.y}; if (!takeover) { wobbly_grab_notify(model.get(), last_grab.x, last_grab.y); } } virtual wf::point_t get_grab_position() const override { return this->last_grab; } /** @return the current state of wobbly */ virtual ewobbly_state_t get_wobbly_state() const override { return WOBBLY_STATE_GRABBED; } virtual void handle_grab_end(bool release_grab) override { if (release_grab) { wobbly_ungrab_notify(model.get()); } } virtual void translate_model(int dx, int dy) override { iwobbly_state_t::translate_model(dx, dy); this->last_grab.x += dx; this->last_grab.y += dy; } void handle_frame() override { auto old_bbox = bounding_box; iwobbly_state_t::handle_frame(); if (wf::dimensions(old_bbox) != wf::dimensions(bounding_box)) { /* Directly accept new size, but keep position, * because it is managed by the grab. */ wobbly_resize(model.get(), bounding_box.width, bounding_box.height); } } protected: wf::point_t last_grab; void handle_grab_move(wf::point_t grab) override { wobbly_move_notify(model.get(), grab.x, grab.y); this->last_grab = {(int)grab.x, (int)grab.y}; } bool is_wobbly_done() const override { return false; } }; static void wobbly_tiled_state_handle_frame(const wobbly_model_t& model, const wf::geometry_t& old_bbox, const wf::geometry_t& new_bbox) { if (new_bbox != old_bbox) { /* Bounding box (excluding the wobbly transformer) changed, this * means the view got resized/moved by something outside of wobbly. * Adjust the geometry. */ wobbly_force_geometry(model.get(), new_bbox.x, new_bbox.y, new_bbox.width, new_bbox.height); } } /** * Determines the behavior of the wobbly model when the view is tiled or * fullscreen, i.e the view keeps its geometry where it was put. */ class wobbly_state_tiled_t : public iwobbly_state_t { public: using iwobbly_state_t::iwobbly_state_t; void handle_state_update_done() override { wobbly_force_geometry(model.get(), bounding_box.x, bounding_box.y, bounding_box.width, bounding_box.height); } void handle_frame() override { auto old_bbox = bounding_box; iwobbly_state_t::handle_frame(); wobbly_tiled_state_handle_frame(model, old_bbox, bounding_box); } virtual ~wobbly_state_tiled_t() { wobbly_unenforce_geometry(model.get()); } wobbly_state_tiled_t(const wobbly_state_tiled_t &) = delete; wobbly_state_tiled_t(wobbly_state_tiled_t &&) = delete; wobbly_state_tiled_t& operator =(const wobbly_state_tiled_t&) = delete; wobbly_state_tiled_t& operator =(wobbly_state_tiled_t&&) = delete; ewobbly_state_t get_wobbly_state() const override { return WOBBLY_STATE_TILED; } }; /** * Determines the behavior of the wobbly model when the view is tiled or * fullscreen, i.e the view keeps its geometry where it was put, and it has * an active grab at the same time. * * This is basically a combination of tiled and grabbed. */ class wobbly_state_tiled_grabbed_t : public wobbly_state_grabbed_t { public: using wobbly_state_grabbed_t::wobbly_state_grabbed_t; void handle_state_update_done() override { wobbly_force_geometry(model.get(), bounding_box.x, bounding_box.y, bounding_box.width, bounding_box.height); } void handle_frame() override { auto old_bbox = bounding_box; iwobbly_state_t::handle_frame(); wobbly_tiled_state_handle_frame(model, old_bbox, bounding_box); } virtual ~wobbly_state_tiled_grabbed_t() { wobbly_unenforce_geometry(model.get()); } wobbly_state_tiled_grabbed_t(const wobbly_state_tiled_grabbed_t &) = delete; wobbly_state_tiled_grabbed_t(wobbly_state_tiled_grabbed_t &&) = delete; wobbly_state_tiled_grabbed_t& operator =(const wobbly_state_tiled_grabbed_t&) = delete; wobbly_state_tiled_grabbed_t& operator =( wobbly_state_tiled_grabbed_t&&) = delete; ewobbly_state_t get_wobbly_state() const override { return WOBBLY_STATE_TILED_GRABBED; } }; /** * Determines the behavior of the wobbly model when the view is wobblying freely * without being grabbed or tiled. In this state, the model dictates the * position of the view. */ class wobbly_state_floating_t : public iwobbly_state_t { public: using iwobbly_state_t::iwobbly_state_t; protected: bool is_wobbly_done() const override { if (!model->synced) { return false; } /* Synchronize view position with the model */ auto new_bbox = view->get_transformed_node()->get_transformer("wobbly") ->get_children_bounding_box(); auto wm = view->get_geometry(); int target_x = model->x + wm.x - new_bbox.x; int target_y = model->y + wm.y - new_bbox.y; if ((target_x != wm.x) || (target_y != wm.y)) { view->move(model->x + wm.x - new_bbox.x, model->y + wm.y - new_bbox.y); } return true; } void handle_frame() override { auto new_bbox = view->get_transformed_node()->get_transformer("wobbly") ->get_children_bounding_box(); update_base_geometry(new_bbox); } void handle_wm_geometry(const wf::geometry_t& old_wm) override { update_base_geometry(wf::view_bounding_box_up_to(view, "wobbly")); } void handle_workspace_change(wf::point_t old, wf::point_t cur) override { auto size = view->get_output()->get_screen_size(); auto delta = old - cur; translate_model(delta.x * size.width, delta.y * size.height); } ewobbly_state_t get_wobbly_state() const override { return WOBBLY_STATE_FLOATING; } }; /** * Determines the behavior of wobbly when the view is not grabbed or tiled, * but the model should keep the true origin of the view. */ class wobbly_state_free_t : public iwobbly_state_t { public: using iwobbly_state_t::iwobbly_state_t; protected: void handle_frame() override { auto old_bbox = bounding_box; iwobbly_state_t::handle_frame(); if (wf::dimensions(old_bbox) != wf::dimensions(bounding_box)) { wobbly_set_top_anchor(model.get(), bounding_box.x, bounding_box.y, bounding_box.width, bounding_box.height); wobbly_resize(model.get(), bounding_box.width, bounding_box.height); } } void handle_workspace_change(wf::point_t old, wf::point_t cur) override { auto size = view->get_output()->get_screen_size(); auto delta = old - cur; wobbly_translate(model.get(), delta.x * size.width, delta.y * size.height); } ewobbly_state_t get_wobbly_state() const override { return WOBBLY_STATE_FREE; } }; } class wobbly_transformer_node_t : public wf::scene::floating_inner_node_t { public: wobbly_transformer_node_t(wayfire_toplevel_view view, OpenGL::program_t *wobbly_prog) : floating_inner_node_t(false) { this->view = view; this->wobbly_program = wobbly_prog; init_model(); last_frame = wf::get_current_time(); view->get_output()->connect(&on_workspace_changed); view->connect(&on_view_unmap); view->connect(&on_view_tiled); view->connect(&on_view_fullscreen); view->connect(&view_output_changed); view->connect(&on_view_geometry_changed); /* Set to free state initially but then look for the correct state */ this->state = std::make_unique(model, view); update_wobbly_state(false, {0, 0}, false); } ~wobbly_transformer_node_t() { state = nullptr; wobbly_fini(model.get()); } std::string stringify() const override { return "wobbly"; } wf::geometry_t get_bounding_box() override { auto box = wobbly_boundingbox(model.get()); wlr_box result; result.x = box.tlx; result.y = box.tly; result.width = std::ceil(box.brx - box.tlx); result.height = std::ceil(box.bry - box.tly); return result; } void gen_render_instances( std::vector& instances, wf::scene::damage_callback push_damage, wf::output_t *shown_on) override; std::unique_ptr model; void destroy_self() { view->get_transformed_node()->rem_transformer("wobbly"); } OpenGL::program_t *wobbly_program; private: wayfire_toplevel_view view; wf::signal::connection_t on_view_unmap = [=] (wf::view_unmapped_signal*) { destroy_self(); }; wf::signal::connection_t on_view_tiled = [=] (wf::view_tiled_signal *ev) { update_wobbly_state(false, {0, 0}, false); }; wf::signal::connection_t on_view_fullscreen = [=] (wf::view_fullscreen_signal *ev) { update_wobbly_state(false, {0, 0}, false); }; wf::signal::connection_t on_view_geometry_changed = [=] (wf::view_geometry_changed_signal *ev) { state->handle_wm_geometry(ev->old_geometry); }; wf::signal::connection_t on_workspace_changed = [=] (wf::workspace_changed_signal *ev) { state->handle_workspace_change(ev->old_viewport, ev->new_viewport); }; wf::signal::connection_t view_output_changed = [=] (wf::view_set_output_signal *ev) { /* Wobbly is active only when there's already been an output */ wf::dassert(ev->output != nullptr, "wobbly cannot be active on nullptr output!"); if (!view->get_output()) { // Destructor won't be able to disconnect bc view output is invalid return destroy_self(); } /* Translate wobbly when its output changes */ auto old_geometry = ev->output->get_layout_geometry(); auto new_geometry = view->get_output()->get_layout_geometry(); state->translate_model(old_geometry.x - new_geometry.x, old_geometry.y - new_geometry.y); on_workspace_changed.disconnect(); view->get_output()->connect(&on_workspace_changed); }; std::unique_ptr state; uint32_t last_frame; bool force_tile = false; void init_model() { model = std::make_unique(); auto g = view->get_bounding_box(); model->x = g.x; model->y = g.y; model->width = g.width; model->height = g.height; model->grabbed = 0; model->synced = 1; model->x_cells = wobbly_settings::resolution; model->y_cells = wobbly_settings::resolution; model->v = NULL; model->uv = NULL; wobbly_init(model.get()); } public: void update_model() { view->damage(); /* It is possible that the wobbly state needs to adjust view geometry. * We do not want it to get feedback from itself */ on_view_geometry_changed.disconnect(); state->handle_frame(); view->connect(&on_view_geometry_changed); /* Update all the wobbly model */ auto now = wf::get_current_time(); if (now > last_frame) { view->get_transformed_node()->begin_transform_update(); wobbly_prepare_paint(model.get(), now - last_frame); /* Update wobbly geometry */ last_frame = now; wobbly_add_geometry(model.get()); wobbly_done_paint(model.get()); view->get_transformed_node()->end_transform_update(); } if (state->is_wobbly_done()) { destroy_self(); } } /** * Update the current wobbly state based on: * 1. View state (tiled & fullscreen) * 2. Current wobbly state (grabbed or not) * 3. Whether we are starting a grab, or ending a grab. * * @param start_grab Whether to start a new grab at @grab * @param grab The position of the starting grab. * @param end_grab Whether to end an existing grab. */ void update_wobbly_state(bool start_grab, wf::point_t grab, bool end_grab) { bool was_grabbed = (state->get_wobbly_state() == wf::WOBBLY_STATE_GRABBED || state->get_wobbly_state() == wf::WOBBLY_STATE_TILED_GRABBED); bool grabbed = (start_grab || was_grabbed) && !end_grab; bool tiled = false; if (grabbed) { // If the view is grabbed, the grabbing plugin says whether to tile // or not tiled = force_tile; } else { tiled = (force_tile || view->pending_tiled_edges()) || view->pending_fullscreen(); } uint32_t next_state_mask = 0; if (tiled && grabbed) { next_state_mask = wf::WOBBLY_STATE_TILED_GRABBED; } else if (tiled) { next_state_mask = wf::WOBBLY_STATE_TILED; } else if (grabbed) { next_state_mask = wf::WOBBLY_STATE_GRABBED; } else if (was_grabbed || (state->get_wobbly_state() == wf::WOBBLY_STATE_FLOATING)) { /* If previously grabbed, we can let the view float freely */ next_state_mask = wf::WOBBLY_STATE_FLOATING; } else { /* Otherwise, we need to keep the position */ next_state_mask = wf::WOBBLY_STATE_FREE; } if (next_state_mask == state->get_wobbly_state()) { return; } std::unique_ptr next_state; switch (next_state_mask) { case wf::WOBBLY_STATE_FREE: next_state = std::make_unique< wf::wobbly_state_free_t>(model, view); break; case wf::WOBBLY_STATE_FLOATING: next_state = std::make_unique< wf::wobbly_state_floating_t>(model, view); break; case wf::WOBBLY_STATE_TILED: next_state = std::make_unique< wf::wobbly_state_tiled_t>(model, view); break; case wf::WOBBLY_STATE_GRABBED: next_state = std::make_unique< wf::wobbly_state_grabbed_t>(model, view); break; case wf::WOBBLY_STATE_TILED_GRABBED: next_state = std::make_unique< wf::wobbly_state_tiled_grabbed_t>(model, view); break; default: /* Not reached except by a bug */ assert(false); } if (was_grabbed) { this->state->handle_grab_end(end_grab); } if (grabbed) { if (was_grabbed) { grab = this->state->get_grab_position(); } next_state->handle_grab_start(grab, was_grabbed); } /* New state has been set up */ this->state = std::move(next_state); this->state->handle_state_update_done(); } public: void start_grab(wf::point_t grab) { update_wobbly_state(true, grab, false); } void move(wf::point_t point) { state->handle_grab_move(point); } void translate(wf::point_t delta) { state->translate_model(delta.x, delta.y); } void end_grab() { update_wobbly_state(false, {0, 0}, true); } void wobble() { wobbly_slight_wobble(model.get()); model->synced = 0; } void update_base_geometry(wf::geometry_t g) { state->update_base_geometry(g); } void set_force_tile(bool force_tile) { this->force_tile = force_tile; update_wobbly_state(false, {0, 0}, false); } }; class wobbly_render_instance_t : public wf::scene::transformer_render_instance_t { wf::output_t *wo = nullptr; wf::effect_hook_t pre_hook; public: wobbly_render_instance_t(wobbly_transformer_node_t *self, wf::scene::damage_callback push_damage, wf::output_t *shown_on) : transformer_render_instance_t(self, push_damage, shown_on) { if (shown_on) { wo = shown_on; pre_hook = [=] () { self->update_model(); }; wo->render->add_effect(&pre_hook, wf::OUTPUT_EFFECT_PRE); } } ~wobbly_render_instance_t() { if (wo) { wo->render->rem_effect(&pre_hook); } } void transform_damage_region(wf::region_t& damage) override { damage |= self->get_bounding_box(); } void render(const wf::render_target_t& target_fb, const wf::region_t& damage) override { std::vector vert, uv; auto subbox = self->get_children_bounding_box(); wobbly_graphics::prepare_geometry(self->model.get(), subbox, vert, uv); auto tex = get_texture(target_fb.scale); OpenGL::render_begin(target_fb); for (auto& box : damage) { target_fb.logic_scissor(wlr_box_from_pixman_box(box)); wobbly_graphics::render_triangles(self->wobbly_program, tex, target_fb.get_orthographic_projection(), vert.data(), uv.data(), self->model->x_cells * self->model->y_cells * 2); } OpenGL::render_end(); } }; void wobbly_transformer_node_t::gen_render_instances( std::vector& instances, wf::scene::damage_callback push_damage, wf::output_t *shown_on) { instances.push_back(std::make_unique( this, push_damage, shown_on)); } class wayfire_wobbly : public wf::plugin_interface_t { wf::signal::connection_t wobbly_changed = [=] (wobbly_signal *ev) { adjust_wobbly(ev); }; public: void init() override { wf::get_core().connect(&wobbly_changed); OpenGL::render_begin(); program.compile(wobbly_graphics::vertex_source, wobbly_graphics::frag_source); OpenGL::render_end(); } void adjust_wobbly(wobbly_signal *data) { auto tr_manager = data->view->get_transformed_node(); if ((data->events & (WOBBLY_EVENT_GRAB | WOBBLY_EVENT_ACTIVATE)) && !tr_manager->get_transformer("wobbly")) { tr_manager->add_transformer( std::make_shared(data->view, &program), wf::TRANSFORMER_HIGHLEVEL, "wobbly"); } auto wobbly = tr_manager->get_transformer("wobbly"); if (!wobbly) { return; } if (data->events & WOBBLY_EVENT_ACTIVATE) { wobbly->wobble(); } if (data->events & WOBBLY_EVENT_GRAB) { wobbly->start_grab(data->pos); } if (data->events & WOBBLY_EVENT_MOVE) { wobbly->move(data->pos); } if (data->events & WOBBLY_EVENT_TRANSLATE) { wobbly->translate(data->pos); } if (data->events & WOBBLY_EVENT_END) { wobbly->end_grab(); } if (data->events & WOBBLY_EVENT_FORCE_TILE) { wobbly->set_force_tile(true); } if (data->events & WOBBLY_EVENT_UNTILE) { wobbly->set_force_tile(false); } if (data->events & WOBBLY_EVENT_SCALE) { wobbly->update_base_geometry(data->geometry); } } void fini() override { for (auto& view : wf::get_core().get_all_views()) { auto wobbly = view->get_transformed_node()->get_transformer(); if (wobbly) { wobbly->destroy_self(); } } OpenGL::render_begin(); program.free_resources(); OpenGL::render_end(); } private: OpenGL::program_t program; }; DECLARE_WAYFIRE_PLUGIN(wayfire_wobbly); wayfire-0.8.1/plugins/wobbly/wobbly.h000066400000000000000000000034371457431457600176430ustar00rootroot00000000000000/************************************************************************** * * Copyright 2014 Scott Moreau * All Rights Reserved. * **************************************************************************/ #include #include #define MINIMAL_FRICTION 0.1 #define MAXIMAL_FRICTION 10.0 #define MINIMAL_SPRING_K 0.1 #define MAXIMAL_SPRING_K 10.0 #define WOBBLY_MASS 15.0 double wobbly_settings_get_friction(); double wobbly_settings_get_spring_k(); struct wobbly_surface { void *ww; int x, y, width, height; int x_cells, y_cells; int grabbed, synced; int vertex_count; GLfloat *v, *uv; }; struct wobbly_rect { float tlx, tly; float brx, bry; }; int wobbly_init(struct wobbly_surface *surface); void wobbly_fini(struct wobbly_surface *surface); void wobbly_set_top_anchor(struct wobbly_surface *surface, int x, int y, int w, int h); void wobbly_grab_notify(struct wobbly_surface *surface, int x, int y); void wobbly_slight_wobble(struct wobbly_surface *surface); void wobbly_ungrab_notify(struct wobbly_surface *surface); void wobbly_scale(struct wobbly_surface *surface, double dx, double dy); void wobbly_resize(struct wobbly_surface *surface, int width, int height); void wobbly_move_notify(struct wobbly_surface *surface, int x, int y); void wobbly_prepare_paint(struct wobbly_surface *surface, int msSinceLastPaint); void wobbly_done_paint(struct wobbly_surface *surface); void wobbly_add_geometry(struct wobbly_surface *surface); struct wobbly_rect wobbly_boundingbox(struct wobbly_surface *surface); void wobbly_force_geometry(struct wobbly_surface *surface, int x, int y, int w, int h); void wobbly_unenforce_geometry(struct wobbly_surface *surface); void wobbly_translate(struct wobbly_surface *surface, int dx, int dy); wayfire-0.8.1/proto/000077500000000000000000000000001457431457600143515ustar00rootroot00000000000000wayfire-0.8.1/proto/gtk-shell.xml000066400000000000000000000052431457431457600167710ustar00rootroot00000000000000 gtk_shell is a protocol extension providing additional features for clients implementing it. wayfire-0.8.1/proto/meson.build000066400000000000000000000040021457431457600165070ustar00rootroot00000000000000wl_protocol_dir = wayland_protos.get_variable(pkgconfig: 'pkgdatadir') wayland_scanner = find_program('wayland-scanner', native: true) wayland_scanner_server = generator( wayland_scanner, output: '@BASENAME@-protocol.h', arguments: ['server-header', '@INPUT@', '@OUTPUT@'], ) wayland_scanner_code = generator( wayland_scanner, output: '@BASENAME@-protocol.c', arguments: ['private-code', '@INPUT@', '@OUTPUT@'], ) wayland_scanner_client = generator( wayland_scanner, output: '@BASENAME@-client-protocol.h', arguments: ['client-header', '@INPUT@', '@OUTPUT@'], ) server_protocols = [ [wl_protocol_dir, 'stable/xdg-shell/xdg-shell.xml'], [wl_protocol_dir, 'unstable/linux-dmabuf/linux-dmabuf-unstable-v1.xml'], [wl_protocol_dir, 'unstable/xdg-shell/xdg-shell-unstable-v6.xml'], [wl_protocol_dir, 'unstable/xdg-output/xdg-output-unstable-v1.xml'], [wl_protocol_dir, 'unstable/pointer-constraints/pointer-constraints-unstable-v1.xml'], [wl_protocol_dir, 'unstable/relative-pointer/relative-pointer-unstable-v1.xml'], [wl_protocol_dir, 'unstable/tablet/tablet-unstable-v2.xml'], [wl_protocol_dir, 'unstable/keyboard-shortcuts-inhibit/keyboard-shortcuts-inhibit-unstable-v1.xml'], [wl_protocol_dir, 'unstable/input-method/input-method-unstable-v1.xml'], 'wayfire-shell-unstable-v2.xml', 'gtk-shell.xml', 'wlr-layer-shell-unstable-v1.xml', 'wlr-output-power-management-unstable-v1.xml' ] wl_protos_src = [] wl_protos_headers = [] foreach p : server_protocols xml = join_paths(p) wl_protos_src += wayland_scanner_code.process(xml) wl_protos_headers += wayland_scanner_server.process(xml) endforeach lib_wl_protos = static_library('wl_protos', wl_protos_src + wl_protos_headers, dependencies: [wayland_client]) # for the include directory wf_protos = declare_dependency( link_with: lib_wl_protos, sources: wl_protos_headers, ) # Install wayfire-shell protocol, so that other projects can find it install_data('wayfire-shell-unstable-v2.xml', install_dir: join_paths(pkgdatadir, 'unstable')) wayfire-0.8.1/proto/wayfire-shell-unstable-v2.xml000066400000000000000000000113661457431457600220150ustar00rootroot00000000000000 This protocol provides additional events and requests for special DE clients like panels, docks, etc. It is meant as an addition for protocols like wlr-layer-shell. Represents a single output. Each output is managed independently from the others. Emitted when a window gets fullscreened on the given output. In this mode, windows in the TOP layer are not visible. There will be no two consecutive enter_fullscreen calls, i.e. if fullscreen mode is entered it will be exited before going into this mode again. Emitted when the output is no longer in fullscreen mode. Each leave_fullscreen has a corresponding enter_fullscreen before it. Request the compositor to not render the output, so the output usually is cleared to black color. To enable output rendering again, call inhibit_output_done. Stop inhibiting the output. This must be called as many times as inhibit_output was called to actually uninhibit rendering. The inhibit/inhibit_done requests can be called multiple times, even from different apps, so don't assume that a call to inhibit_done would always mean actually starting the rendering process. A hotspot on the output is an edge or a corner region of the output where the mouse or touch point has been residing for a given amount of time. The hotspot can be used for example for autohiding panels, where the panel is shown when the input hovers on the edge of the output for a specific amount of time. Tells the menu to toggle open or close. Emitted using an activator binding. Means that the mouse and/or touch finger was inside the indicated hotspot for the given amount of time. Emitted at most once for each entry of the input inside the hotspot. This event indicates that the mouse or touch point has left the hotspot area. Emitted only once after each enter. wayfire-0.8.1/proto/wlr-layer-shell-unstable-v1.xml000066400000000000000000000440361457431457600222640ustar00rootroot00000000000000 Copyright © 2017 Drew DeVault Permission to use, copy, modify, distribute, and sell this software and its documentation for any purpose is hereby granted without fee, provided that the above copyright notice appear in all copies and that both that copyright notice and this permission notice appear in supporting documentation, and that the name of the copyright holders not be used in advertising or publicity pertaining to distribution of the software without specific, written prior permission. The copyright holders make no representations about the suitability of this software for any purpose. It is provided "as is" without express or implied warranty. THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. Clients can use this interface to assign the surface_layer role to wl_surfaces. Such surfaces are assigned to a "layer" of the output and rendered with a defined z-depth respective to each other. They may also be anchored to the edges and corners of a screen and specify input handling semantics. This interface should be suitable for the implementation of many desktop shell components, and a broad number of other applications that interact with the desktop. Create a layer surface for an existing surface. This assigns the role of layer_surface, or raises a protocol error if another role is already assigned. Creating a layer surface from a wl_surface which has a buffer attached or committed is a client error, and any attempts by a client to attach or manipulate a buffer prior to the first layer_surface.configure call must also be treated as errors. After creating a layer_surface object and setting it up, the client must perform an initial commit without any buffer attached. The compositor will reply with a layer_surface.configure event. The client must acknowledge it and is then allowed to attach a buffer to map the surface. You may pass NULL for output to allow the compositor to decide which output to use. Generally this will be the one that the user most recently interacted with. Clients can specify a namespace that defines the purpose of the layer surface. These values indicate which layers a surface can be rendered in. They are ordered by z depth, bottom-most first. Traditional shell surfaces will typically be rendered between the bottom and top layers. Fullscreen shell surfaces are typically rendered at the top layer. Multiple surfaces can share a single layer, and ordering within a single layer is undefined. This request indicates that the client will not use the layer_shell object any more. Objects that have been created through this instance are not affected. An interface that may be implemented by a wl_surface, for surfaces that are designed to be rendered as a layer of a stacked desktop-like environment. Layer surface state (layer, size, anchor, exclusive zone, margin, interactivity) is double-buffered, and will be applied at the time wl_surface.commit of the corresponding wl_surface is called. Attaching a null buffer to a layer surface unmaps it. Unmapping a layer_surface means that the surface cannot be shown by the compositor until it is explicitly mapped again. The layer_surface returns to the state it had right after layer_shell.get_layer_surface. The client can re-map the surface by performing a commit without any buffer attached, waiting for a configure event and handling it as usual. Sets the size of the surface in surface-local coordinates. The compositor will display the surface centered with respect to its anchors. If you pass 0 for either value, the compositor will assign it and inform you of the assignment in the configure event. You must set your anchor to opposite edges in the dimensions you omit; not doing so is a protocol error. Both values are 0 by default. Size is double-buffered, see wl_surface.commit. Requests that the compositor anchor the surface to the specified edges and corners. If two orthogonal edges are specified (e.g. 'top' and 'left'), then the anchor point will be the intersection of the edges (e.g. the top left corner of the output); otherwise the anchor point will be centered on that edge, or in the center if none is specified. Anchor is double-buffered, see wl_surface.commit. Requests that the compositor avoids occluding an area with other surfaces. The compositor's use of this information is implementation-dependent - do not assume that this region will not actually be occluded. A positive value is only meaningful if the surface is anchored to one edge or an edge and both perpendicular edges. If the surface is not anchored, anchored to only two perpendicular edges (a corner), anchored to only two parallel edges or anchored to all edges, a positive value will be treated the same as zero. A positive zone is the distance from the edge in surface-local coordinates to consider exclusive. Surfaces that do not wish to have an exclusive zone may instead specify how they should interact with surfaces that do. If set to zero, the surface indicates that it would like to be moved to avoid occluding surfaces with a positive exclusive zone. If set to -1, the surface indicates that it would not like to be moved to accommodate for other surfaces, and the compositor should extend it all the way to the edges it is anchored to. For example, a panel might set its exclusive zone to 10, so that maximized shell surfaces are not shown on top of it. A notification might set its exclusive zone to 0, so that it is moved to avoid occluding the panel, but shell surfaces are shown underneath it. A wallpaper or lock screen might set their exclusive zone to -1, so that they stretch below or over the panel. The default value is 0. Exclusive zone is double-buffered, see wl_surface.commit. Requests that the surface be placed some distance away from the anchor point on the output, in surface-local coordinates. Setting this value for edges you are not anchored to has no effect. The exclusive zone includes the margin. Margin is double-buffered, see wl_surface.commit. Types of keyboard interaction possible for layer shell surfaces. The rationale for this is twofold: (1) some applications are not interested in keyboard events and not allowing them to be focused can improve the desktop experience; (2) some applications will want to take exclusive keyboard focus. This value indicates that this surface is not interested in keyboard events and the compositor should never assign it the keyboard focus. This is the default value, set for newly created layer shell surfaces. This is useful for e.g. desktop widgets that display information or only have interaction with non-keyboard input devices. Request exclusive keyboard focus if this surface is above the shell surface layer. For the top and overlay layers, the seat will always give exclusive keyboard focus to the top-most layer which has keyboard interactivity set to exclusive. If this layer contains multiple surfaces with keyboard interactivity set to exclusive, the compositor determines the one receiving keyboard events in an implementation- defined manner. In this case, no guarantee is made when this surface will receive keyboard focus (if ever). For the bottom and background layers, the compositor is allowed to use normal focus semantics. This setting is mainly intended for applications that need to ensure they receive all keyboard events, such as a lock screen or a password prompt. This requests the compositor to allow this surface to be focused and unfocused by the user in an implementation-defined manner. The user should be able to unfocus this surface even regardless of the layer it is on. Typically, the compositor will want to use its normal mechanism to manage keyboard focus between layer shell surfaces with this setting and regular toplevels on the desktop layer (e.g. click to focus). Nevertheless, it is possible for a compositor to require a special interaction to focus or unfocus layer shell surfaces (e.g. requiring a click even if focus follows the mouse normally, or providing a keybinding to switch focus between layers). This setting is mainly intended for desktop shell components (e.g. panels) that allow keyboard interaction. Using this option can allow implementing a desktop shell that can be fully usable without the mouse. Set how keyboard events are delivered to this surface. By default, layer shell surfaces do not receive keyboard events; this request can be used to change this. This setting is inherited by child surfaces set by the get_popup request. Layer surfaces receive pointer, touch, and tablet events normally. If you do not want to receive them, set the input region on your surface to an empty region. Keyboard interactivity is double-buffered, see wl_surface.commit. This assigns an xdg_popup's parent to this layer_surface. This popup should have been created via xdg_surface::get_popup with the parent set to NULL, and this request must be invoked before committing the popup's initial state. See the documentation of xdg_popup for more details about what an xdg_popup is and how it is used. When a configure event is received, if a client commits the surface in response to the configure event, then the client must make an ack_configure request sometime before the commit request, passing along the serial of the configure event. If the client receives multiple configure events before it can respond to one, it only has to ack the last configure event. A client is not required to commit immediately after sending an ack_configure request - it may even ack_configure several times before its next surface commit. A client may send multiple ack_configure requests before committing, but only the last request sent before a commit indicates which configure event the client really is responding to. This request destroys the layer surface. The configure event asks the client to resize its surface. Clients should arrange their surface for the new states, and then send an ack_configure request with the serial sent in this configure event at some point before committing the new surface. The client is free to dismiss all but the last configure event it received. The width and height arguments specify the size of the window in surface-local coordinates. The size is a hint, in the sense that the client is free to ignore it if it doesn't resize, pick a smaller size (to satisfy aspect ratio or resize in steps of NxM pixels). If the client picks a smaller size and is anchored to two opposite anchors (e.g. 'top' and 'bottom'), the surface will be centered on this axis. If the width or height arguments are zero, it means the client should decide its own window dimension. The closed event is sent by the compositor when the surface will no longer be shown. The output may have been destroyed or the user may have asked for it to be removed. Further changes to the surface will be ignored. The client should destroy the resource after receiving this event, and create a new surface if they so choose. Change the layer that the surface is rendered on. Layer is double-buffered, see wl_surface.commit. wayfire-0.8.1/proto/wlr-output-power-management-unstable-v1.xml000066400000000000000000000127331457431457600246460ustar00rootroot00000000000000 Copyright © 2019 Purism SPC 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 (including the next paragraph) 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. This protocol allows clients to control power management modes of outputs that are currently part of the compositor space. The intent is to allow special clients like desktop shells to power down outputs when the system is idle. To modify outputs not currently part of the compositor space see wlr-output-management. Warning! The protocol described in this file is experimental and backward incompatible changes may be made. Backward compatible changes may be added together with the corresponding interface version bump. Backward incompatible changes are done by bumping the version number in the protocol and interface names and resetting the interface version. Once the protocol is to be declared stable, the 'z' prefix and the version number in the protocol and interface names are removed and the interface version number is reset. This interface is a manager that allows creating per-output power management mode controls. Create a output power management mode control that can be used to adjust the power management mode for a given output. All objects created by the manager will still remain valid, until their appropriate destroy request has been called. This object offers requests to set the power management mode of an output. Set an output's power save mode to the given mode. The mode change is effective immediately. If the output does not support the given mode a failed event is sent. Report the power management mode change of an output. The mode event is sent after an output changed its power management mode. The reason can be a client using set_mode or the compositor deciding to change an output's mode. This event is also sent immediately when the object is created so the client is informed about the current power management mode. This event indicates that the output power management mode control is no longer valid. This can happen for a number of reasons, including: - The output doesn't support power management - Another client already has exclusive power management mode control for this output - The output disappeared Upon receiving this event, the client should destroy this object. Destroys the output power management mode control object. wayfire-0.8.1/src/000077500000000000000000000000001457431457600137755ustar00rootroot00000000000000wayfire-0.8.1/src/api/000077500000000000000000000000001457431457600145465ustar00rootroot00000000000000wayfire-0.8.1/src/api/wayfire/000077500000000000000000000000001457431457600162145ustar00rootroot00000000000000wayfire-0.8.1/src/api/wayfire/bindings-repository.hpp000066400000000000000000000045141457431457600227430ustar00rootroot00000000000000#pragma once #include "wayfire/geometry.hpp" #include #include #include #include #include namespace wf { /** * bindings_repository_t is responsible for managing a list of all bindings in * Wayfire, and for calling these bindings on the corresponding events. */ class bindings_repository_t { public: bindings_repository_t(); ~bindings_repository_t(); /** * Plugins can use the add_* functions to register their own bindings. * * @param key/axis/button/activator The corresponding values are used to know when to trigger this * binding. The values that the shared pointer points to may be modified, in which case core will use * the latest value stored there. * * @param cb The plugin callback to be executed when the binding is triggered. Can be used to unregister * the binding. */ void add_key(option_sptr_t key, wf::key_callback *cb); void add_axis(option_sptr_t axis, wf::axis_callback *cb); void add_button(option_sptr_t button, wf::button_callback *cb); void add_activator(option_sptr_t activator, wf::activator_callback *cb); /** * Handle a keybinding pressed by the user. * * @param pressed The keybinding which was triggered. * @param mod_binding_key The modifier which triggered the binding, if any. * * @return true if any of the matching registered bindings consume the event. */ bool handle_key(const wf::keybinding_t& pressed, uint32_t mod_binding_key); /** Handle an axis event. */ bool handle_axis(uint32_t modifiers, wlr_pointer_axis_event *ev); /** * Handle a buttonbinding pressed by the user. * @return true if any of the matching registered bindings consume the event. */ bool handle_button(const wf::buttonbinding_t& pressed); /** Handle a gesture from the user. */ void handle_gesture(const wf::touchgesture_t& gesture); /** Erase binding of any type by callback */ void rem_binding(void *callback); /** * Enable or disable the repository. The state is reference-counted and starts at 1 (enabled). */ void set_enabled(bool enabled); struct impl; std::unique_ptr priv; }; } wayfire-0.8.1/src/api/wayfire/bindings.hpp000066400000000000000000000052431457431457600205260ustar00rootroot00000000000000#ifndef WF_BINDINGS_HPP #define WF_BINDINGS_HPP #include #include #include #include namespace wf { struct touchgesture_t; /** * A callback for key bindings. * Receives as a parameter the key combination which activated it. * * The returned value indicates whether the key event has been consumed, in which * case it will not be sent to clients (but may still be received by other plugins). */ using key_callback = std::function; /** * A callback for button bindings. * Receives as a parameter the button combination which activated it. * * The returned value indicates whether the button event has been consumed, in * which case it will not be sent to clients (but may still be received by other * plugins). */ using button_callback = std::function; /** * A callback for axis bindings. * Receives as a parameter an axis event from wlroots. * * The returned value indicates whether the event has been consumed, in which * case it will not be sent to clients (but may still be received by other * plugins). */ using axis_callback = std::function; /** * Describes the possible event sources that can activate an activator binding. */ enum class activator_source_t { /** Binding activated by a keybinding. */ KEYBINDING, /** Binding activated by a modifier keybinding. */ MODIFIERBINDING, /** Binding activated by a button binding. */ BUTTONBINDING, /** Binding activated by a touchscreen gesture. */ GESTURE, /** Binding activated by a hotspot. */ HOTSPOT, /** Binding was activated by another plugin. */ PLUGIN, /** Binding was activated by another plugin with custom data */ PLUGIN_WITH_DATA, }; /** * Data sent to activator bindings when they are activated. * Includes information from the activating event source. * * Note: some plugins might support extended activator data, i.e they might * accept a subclass of activator_data_t when source is PLUGIN_WITH_DATA. */ struct activator_data_t { /** The activating source type */ activator_source_t source; /** * Additional data from the event source which activates the activator. * * - The key which was pressed for KEYBINDING * - The modifier which was released for MODIFIERBINDING * - The button pressed for BUTTONBINDING * - The hotspot edges for HOTSPOT * - undefined otherwise */ uint32_t activation_data; }; using activator_callback = std::function; } #endif /* end of include guard: WF_BINDINGS_HPP */ wayfire-0.8.1/src/api/wayfire/compositor-view.hpp000066400000000000000000000044301457431457600220740ustar00rootroot00000000000000#ifndef COMPOSITOR_VIEW_HPP #define COMPOSITOR_VIEW_HPP #include "wayfire/geometry.hpp" #include "wayfire/view.hpp" #include namespace wf { /** * color_rect_view_t represents another common type of compositor view - a * view which is simply a colored rectangle with a border. */ class color_rect_view_t : public wf::view_interface_t { protected: wf::color_t _color; wf::color_t _border_color; int border; wf::geometry_t geometry; bool _is_mapped; class color_rect_node_t; /** * Create a colored rect view. The map signal is not fired by default. * The creator of the colored view should also add it to the desired layer. */ color_rect_view_t(); friend class wf::tracking_allocator_t; public: /** * Create and initialize a new color rect view. * The view will be automatically mapped, and if specified, put on the given output and layer. */ static std::shared_ptr create(view_role_t role, wf::output_t *start_output = nullptr, std::optional layer = {}); /** * Emit the unmap signal and then drop the internal reference. */ virtual void close() override; /** Set the view color. Color's alpha is not premultiplied */ virtual void set_color(wf::color_t color); /** Set the view border color. Color's alpha is not premultiplied */ virtual void set_border_color(wf::color_t border); /** Set the border width. */ virtual void set_border(int width); /** Get the view color. Color's alpha is not premultiplied */ wf::color_t get_color() { return _color; } /** Get the view border color. Color's alpha is not premultiplied */ wf::color_t get_border_color() { return _border_color; } /** Get the border width. */ int get_border() { return border; } /** Set the view geometry. */ virtual void set_geometry(wf::geometry_t geometry); virtual wf::geometry_t get_geometry(); /* required for view_interface_t */ virtual bool is_mapped() const override; virtual wlr_surface *get_keyboard_focus_surface() override; virtual bool is_focusable() const override; }; } #endif /* end of include guard: COMPOSITOR_VIEW_HPP */ wayfire-0.8.1/src/api/wayfire/config-backend.hpp000066400000000000000000000043261457431457600215640ustar00rootroot00000000000000#pragma once #include #include #include namespace wf { /** * A base class for configuration backend plugins. * * A configuration backend plugin is loaded immediately after creating a * wayland display and initializing the logging infrastructure. Because of * this, the configuration backend plugins are not allowed to use any * Wayfire APIs, but can use wf-config. * * The job of a configuration backend plugin is to populate and update the * configuration options used in the rest of the code. */ class config_backend_t { public: /** * Initialize the config backend and do the initial loading of config options. * The config backend must follow the same option types as described in the XML * files. * * @param display The wayland display used by Wayfire. * @param config A reference to the config manager which needs to be * populated. * @param cmd_config_file The configuration file specified on the command line. */ virtual void init(wl_display *display, config::config_manager_t& config, const std::string& cmd_config_file) = 0; /** * Find the output section for a given output. * * The returned section must be a valid output object as * described in output.xml */ virtual std::shared_ptr get_output_section( wlr_output *output); /** * Find the output section for a given input device. * * The returned section must be a valid output object as * described in input-device.xml */ virtual std::shared_ptr get_input_device_section( wlr_input_device *device); virtual ~config_backend_t() = default; protected: /** A helper to read the XML directories that Wayfire looks at */ virtual std::vector get_xml_dirs() const; }; } /** * A macro to declare the necessary functions, given the backend class name. */ #define DECLARE_WAYFIRE_CONFIG_BACKEND(PluginClass) \ extern "C" \ { \ wf::config_backend_t*newInstance() { return new PluginClass; } \ uint32_t getWayfireVersion() { return WAYFIRE_API_ABI_VERSION; } \ } wayfire-0.8.1/src/api/wayfire/core.hpp000066400000000000000000000224741457431457600176660ustar00rootroot00000000000000#ifndef CORE_HPP #define CORE_HPP #include "wayfire/object.hpp" #include "wayfire/scene-input.hpp" #include #include #include #include #include #include #include #include #include #include #include #include namespace wf { class view_interface_t; class toplevel_view_interface_t; class window_manager_t; namespace scene { class root_node_t; } namespace txn { class transaction_manager_t; } namespace touch { class gesture_t; struct gesture_state_t; } } using wayfire_view = nonstd::observer_ptr; using wayfire_toplevel_view = nonstd::observer_ptr; namespace wf { class output_t; class output_layout_t; class input_device_t; class bindings_repository_t; class seat_t; /** Describes the state of the compositor */ enum class compositor_state_t { /** Not started */ UNKNOWN, /** * The compositor core has finished initializing. * Now the wlroots backends are being started, which results in * adding of new input and output devices, as well as starting the * plugins on each output. */ START_BACKEND, /** * The compositor has loaded the initial devices and plugins and is * running the main loop. */ RUNNING, /** * The compositor has stopped the main loop and is shutting down. */ SHUTDOWN, }; class compositor_core_t : public wf::object_base_t, public signal::provider_t { public: /** * The current configuration used by Wayfire */ wf::config::config_manager_t config; /** * Command line arguments. */ int argc; char **argv; /** * The wayland display and its event loop */ wl_display *display; wl_event_loop *ev_loop; /** * The current wlr backend in use. The only case where another backend is * used is when there are no outputs added, in which case a noop backend is * used instead of this one */ wlr_backend *backend; wlr_session *session; wlr_renderer *renderer; wlr_allocator *allocator; std::unique_ptr config_backend; std::unique_ptr output_layout; std::unique_ptr bindings; std::unique_ptr seat; std::unique_ptr tx_manager; std::unique_ptr default_wm; /** * Various protocols supported by wlroots */ struct { wlr_data_device_manager *data_device; wlr_data_control_manager_v1 *data_control; wlr_gamma_control_manager_v1 *gamma_v1; wlr_screencopy_manager_v1 *screencopy; wlr_export_dmabuf_manager_v1 *export_dmabuf; wlr_server_decoration_manager *decorator_manager; wlr_xdg_decoration_manager_v1 *xdg_decorator; wlr_xdg_output_manager_v1 *output_manager; wlr_virtual_keyboard_manager_v1 *vkbd_manager; wlr_virtual_pointer_manager_v1 *vptr_manager; wlr_input_inhibit_manager *input_inhibit; wlr_idle_notifier_v1 *idle_notifier; wlr_idle_inhibit_manager_v1 *idle_inhibit; wlr_pointer_gestures_v1 *pointer_gestures; wlr_relative_pointer_manager_v1 *relative_pointer; wlr_pointer_constraints_v1 *pointer_constraints; wlr_tablet_manager_v2 *tablet_v2; wlr_input_method_manager_v2 *input_method = NULL; wlr_text_input_manager_v3 *text_input = NULL; wlr_presentation *presentation; wlr_primary_selection_v1_device_manager *primary_selection_v1; wlr_viewporter *viewporter; wlr_drm_lease_v1_manager *drm_v1; wlr_xdg_foreign_registry *foreign_registry; wlr_xdg_foreign_v1 *foreign_v1; wlr_xdg_foreign_v2 *foreign_v2; } protocols; std::string to_string() const { return "wayfire-core"; } /** * @return the current seat. For now, Wayfire supports only a single seat, * which means get_current_seat() will always return the same (and only) seat. */ virtual wlr_seat *get_current_seat() = 0; /** Set the cursor to the given name from the cursor theme, if available */ virtual void set_cursor(std::string name) = 0; /** * Decrements the hide ref counter and unhides the cursor if it becomes 0. * */ virtual void unhide_cursor() = 0; /** * Hides the cursor and increments the hide ref counter. * */ virtual void hide_cursor() = 0; /** * Move the cursor to a specific position. * @param position the new position for the cursor, in global coordinates. */ virtual void warp_cursor(wf::pointf_t position) = 0; /** * Break any grabs on pointer, touch and tablet input. * Then, transfer input focus to the given node in a grab mode. * Note that when transferring a grab, synthetic button release/etc. events are sent to the old pointer * and touch focus nodes (except if they are not RAW_INPUT nodes). * * The grab node, if it is not RAW_INPUT, will also not receive button release events for buttons pressed * before it grabbed the input, if it does not have the RAW_INPUT flag. * * @param node The node which should receive the grabbed input. */ virtual void transfer_grab(wf::scene::node_ptr node) = 0; /** no such coordinate will ever realistically be used for input */ static constexpr double invalid_coordinate = std::numeric_limits::quiet_NaN(); /** * @return The current cursor position in global coordinates or * {invalid_coordinate, invalid_coordinate} if no cursor. */ virtual wf::pointf_t get_cursor_position() = 0; /** * @deprecated, use get_touch_state() instead * * @return The current position of the given touch point, or * {invalid_coordinate,invalid_coordinate} if it is not found. */ virtual wf::pointf_t get_touch_position(int id) = 0; /** * @return The current state of all touch points. */ virtual const wf::touch::gesture_state_t& get_touch_state() = 0; /** * @return The surface which has the cursor focus, or null if none. */ virtual wf::scene::node_ptr get_cursor_focus() = 0; /** * @return The surface which has touch focus, or null if none. */ virtual wf::scene::node_ptr get_touch_focus() = 0; /** @return The view whose surface is cursor focus */ wayfire_view get_cursor_focus_view(); /** @return The view whose surface is touch focus */ wayfire_view get_touch_focus_view(); /** * @return The view whose surface is under the given global coordinates, * or null if none */ wayfire_view get_view_at(wf::pointf_t point); /** * @return A list of all currently attached input devices. */ virtual std::vector> get_input_devices() = 0; /** * @return the wlr_cursor used for the input devices */ virtual wlr_cursor *get_wlr_cursor() = 0; /** * Register a new touchscreen gesture. */ virtual void add_touch_gesture( nonstd::observer_ptr gesture) = 0; /** * Unregister a touchscreen gesture. */ virtual void rem_touch_gesture( nonstd::observer_ptr gesture) = 0; /** * @deprecated. Use tracking_allocator_t::get_all() * * @return A list of all views core manages, regardless of their output, * properties, etc. */ std::vector get_all_views(); /** The wayland socket name of Wayfire */ std::string wayland_display; /** * Return the xwayland display name. * * @return The xwayland display name, or empty string if xwayland is not * available. */ virtual std::string get_xwayland_display() = 0; /** * Execute the given command in a POSIX shell. (/bin/sh) * * This also sets some environment variables for the new process, including * correct WAYLAND_DISPLAY and DISPLAY. * * @return The PID of the started client, or -1 on failure. */ virtual pid_t run(std::string command) = 0; /** * @return The current state of the compositor. */ virtual compositor_state_t get_current_state() = 0; /** * Shut down the whole compositor. * * Stops event loops, destroys outputs, views, etc. */ virtual void shutdown() = 0; /** * Get the root node of Wayfire's scenegraph. */ virtual const std::shared_ptr& scene() = 0; /** * Returns a reference to the only core instance. */ static compositor_core_t& get(); protected: compositor_core_t(); virtual ~compositor_core_t(); }; /** * Change the view's output to new_output. If the reconfigure flag is * set, it will adjust the view geometry for the new output and clamp * it to the output geometry so it is at an expected size and position. */ void move_view_to_output(wayfire_toplevel_view v, wf::output_t *new_output, bool reconfigure); /** * Simply a convenience function to call wf::compositor_core_t::get() */ compositor_core_t& get_core(); } #endif // CORE_HPP wayfire-0.8.1/src/api/wayfire/dassert.hpp000066400000000000000000000014361457431457600203760ustar00rootroot00000000000000#pragma once #include #include namespace wf { /** * Print the current stacktrace at runtime. * * @param fast_mode If fast_mode is true, the stacktrace will be generated * using the fastest possible method. However, this means that not all * information will be printed (for ex., line numbers may be missing). */ void print_trace(bool fast_mode); /** * Assert that the condition is true. * Optionally print a message. * Print backtrace when the assertion fails and exit. */ inline void dassert(bool condition, std::string message = "") { if (!condition) { LOGE(message); print_trace(false); std::exit(0); } } } #define DASSERT(condition) \ wf::dassert(condition, "Assertion failed at " __FILE__ ":" __LINE__) wayfire-0.8.1/src/api/wayfire/debug.hpp000066400000000000000000000040111457431457600200070ustar00rootroot00000000000000#ifndef DEBUG_HPP #define DEBUG_HPP #ifndef WAYFIRE_PLUGIN #include "config.h" #endif #define nonull(x) ((x) ? (x) : ("nil")) #include #include #include #include #include namespace wf { /** * Dump a scenegraph to the log. */ void dump_scene(scene::node_ptr root = wf::get_core().scene()); /** * Information about the version that Wayfire was built with. * Made available at runtime. */ namespace version { extern std::string git_commit; extern std::string git_branch; } namespace log { /** * A list of available logging categories. * Logging categories need to be manually enabled. */ enum class logging_category : size_t { // Transactions - general TXN = 0, // Transactions - individual objects TXNI = 1, // Views - events VIEWS = 2, // Wlroots messages WLR = 3, // Direct scanout SCANOUT = 4, // Pointer events POINTER = 5, // Workspace set events WSET = 6, // Keyboard-related events KBD = 7, // Xwayland-related events XWL = 8, // Layer-shell-related events LSHELL = 9, // Input-Method-related events IM = 10, TOTAL, }; extern std::bitset<(size_t)logging_category::TOTAL> enabled_categories; } } #define LOGC(CAT, ...) \ if (wf::log::enabled_categories[(size_t)wf::log::logging_category::CAT]) \ { \ LOGD("[", #CAT, "] ", __VA_ARGS__); \ } /* ------------------- Miscallaneous helpers for debugging ------------------ */ #include #include #include std::ostream& operator <<(std::ostream& out, const glm::mat4& mat); wf::pointf_t operator *(const glm::mat4& m, const wf::pointf_t& p); wf::pointf_t operator *(const glm::mat4& m, const wf::point_t& p); namespace wf { class view_interface_t; } using wayfire_view = nonstd::observer_ptr; namespace wf { std::ostream& operator <<(std::ostream& out, wayfire_view view); } #endif wayfire-0.8.1/src/api/wayfire/geometry.hpp000066400000000000000000000073611457431457600205670ustar00rootroot00000000000000#ifndef WF_GEOMETRY_HPP #define WF_GEOMETRY_HPP #include #include namespace wf { struct point_t { int x, y; }; struct pointf_t { double x, y; pointf_t() : x(0), y(0) {} pointf_t(double _x, double _y) : x(_x), y(_y) {} explicit pointf_t(const point_t& pt) : x(pt.x), y(pt.y) {} pointf_t operator +(const pointf_t& other) const { return pointf_t{x + other.x, y + other.y}; } pointf_t operator -(const pointf_t& other) const { return pointf_t{x - other.x, y - other.y}; } pointf_t& operator +=(const pointf_t& other) { x += other.x; y += other.y; return *this; } pointf_t& operator -=(const pointf_t& other) { x -= other.x; y -= other.y; return *this; } pointf_t operator -() const { return pointf_t{-x, -y}; } }; struct dimensions_t { int32_t width; int32_t height; }; using geometry_t = wlr_box; point_t origin(const geometry_t& geometry); dimensions_t dimensions(const geometry_t& geometry); geometry_t construct_box( const wf::point_t& origin, const wf::dimensions_t& dimensions); /* Returns the intersection of the two boxes, if the boxes don't intersect, * the resulting geometry has undefined (x,y) and width == height == 0 */ geometry_t geometry_intersection(const geometry_t& r1, const geometry_t& r2); std::ostream& operator <<(std::ostream& stream, const wf::point_t& point); std::ostream& operator <<(std::ostream& stream, const wf::pointf_t& pointf); std::ostream& operator <<(std::ostream& stream, const wf::dimensions_t& dims); bool operator ==(const wf::dimensions_t& a, const wf::dimensions_t& b); bool operator !=(const wf::dimensions_t& a, const wf::dimensions_t& b); bool operator ==(const wf::point_t& a, const wf::point_t& b); bool operator !=(const wf::point_t& a, const wf::point_t& b); wf::point_t operator +(const wf::point_t& a, const wf::point_t& b); wf::point_t operator -(const wf::point_t& a, const wf::point_t& b); wf::point_t operator -(const wf::point_t& a); /** Return the closest valume to @value which is in [@min, @max] */ template T clamp(T value, T min, T max) { return std::min(std::max(value, min), max); } /** * Return the closest geometry to window which is completely inside the output. * The returned geometry might be smaller, but never bigger than window. */ geometry_t clamp(geometry_t window, geometry_t output); // Transform a subbox from coordinate space A to coordinate space B. // The returned subbox will occupy the same relative part of @B as // @box occupies in @A. wf::geometry_t scale_box(wf::geometry_t A, wf::geometry_t B, wf::geometry_t box); } bool operator ==(const wf::geometry_t& a, const wf::geometry_t& b); bool operator !=(const wf::geometry_t& a, const wf::geometry_t& b); wf::point_t operator +(const wf::point_t& a, const wf::geometry_t& b); wf::geometry_t operator +(const wf::geometry_t & a, const wf::point_t& b); wf::geometry_t operator -(const wf::geometry_t & a, const wf::point_t& b); /** Scale the box */ wf::geometry_t operator *(const wf::geometry_t& box, double scale); /* @return The length of the given vector */ double abs(const wf::point_t & p); /* Returns true if point is inside rect */ bool operator &(const wf::geometry_t& rect, const wf::point_t& point); /* Returns true if point is inside rect */ bool operator &(const wf::geometry_t& rect, const wf::pointf_t& point); /* Returns true if the two geometries have a common point */ bool operator &(const wf::geometry_t& r1, const wf::geometry_t& r2); /* Make geometry and point printable */ std::ostream& operator <<(std::ostream& stream, const wf::geometry_t& geometry); #endif /* end of include guard: WF_GEOMETRY_HPP */ wayfire-0.8.1/src/api/wayfire/idle.hpp000066400000000000000000000011401457431457600176360ustar00rootroot00000000000000#pragma once namespace wf { /** * Dummy non-copyable type that increments the global inhibitor count when created, * and decrements when destroyed. These changes influence wlroots idle enablement. */ class idle_inhibitor_t { public: idle_inhibitor_t(); ~idle_inhibitor_t(); idle_inhibitor_t(const idle_inhibitor_t &) = delete; idle_inhibitor_t(idle_inhibitor_t &&) = delete; idle_inhibitor_t& operator =(const idle_inhibitor_t&) = delete; idle_inhibitor_t& operator =(idle_inhibitor_t&&) = delete; private: static unsigned int inhibitors; void notify_update(); }; } wayfire-0.8.1/src/api/wayfire/img.hpp000066400000000000000000000013331457431457600175010ustar00rootroot00000000000000#ifndef IMG_HPP_ #define IMG_HPP_ #include #include namespace image_io { /* Load the image from the given file, binding it to the given GL texture target * Bind the texture before you call this function * Guaranteed: doesn't change any GL state except pixel packing */ bool load_from_file(std::string name, GLuint target); /* Function that saves the given pixels(in rgba format) to a (currently) png file */ void write_to_file(std::string name, uint8_t *pixels, int w, int h, std::string type, bool invert = false); void write_to_file(std::string name, wf::framebuffer_t buffer); /* Initializes all backends, called at startup */ void init(); } #endif /* end of include guard: IMG_HPP_ */ wayfire-0.8.1/src/api/wayfire/input-device.hpp000066400000000000000000000014371457431457600213260ustar00rootroot00000000000000#ifndef WF_INPUT_DEVICE_HPP #define WF_INPUT_DEVICE_HPP #include namespace wf { class input_device_t { public: /** * General comment * @return The represented wlr_input_device */ wlr_input_device *get_wlr_handle(); /** * @param enabled Whether the compositor should handle input events from * the device * @return true if the device state was successfully changed */ bool set_enabled(bool enabled = true); /** * @return true if the compositor should receive events from the device */ bool is_enabled(); virtual ~input_device_t() = default; protected: wlr_input_device *handle; input_device_t(wlr_input_device *handle); }; } #endif /* end of include guard: WF_INPUT_DEVICE_HPP */ wayfire-0.8.1/src/api/wayfire/matcher.hpp000066400000000000000000000031401457431457600203460ustar00rootroot00000000000000#pragma once #include #include namespace wf { /** * view_matcher_t provides a way to match certain views based on conditions. * The conditions are represented as string options. * * For information about the syntax or the possible conditions, see * wf::view_condition_interface_t and wf::condition_parser_t. */ class view_matcher_t { public: /** * Create a new matcher from the given option. * * Whenever the option value changes, the condition will be updated. * * @param option The option where the condition is encoded. */ view_matcher_t(std::shared_ptr> option); /** * Create a new matcher from the given option name. * The option will be loaded from core. * * @throws a std::runtime_error if the option is not found, or if the option * is not a string. */ view_matcher_t(const std::string& option_name); view_matcher_t(const view_matcher_t &) = delete; view_matcher_t(view_matcher_t &&) = default; view_matcher_t& operator =(const view_matcher_t&) = delete; view_matcher_t& operator =(view_matcher_t&&) = default; /** * Set the condition option after initialization. */ void set_from_option(std::shared_ptr> option); /** * @return True if the view matches the condition specified, false otherwise. */ bool matches(wayfire_view view); /** Destructor */ ~view_matcher_t(); private: view_matcher_t(); class impl; std::unique_ptr priv; }; } wayfire-0.8.1/src/api/wayfire/nonstd/000077500000000000000000000000001457431457600175215ustar00rootroot00000000000000wayfire-0.8.1/src/api/wayfire/nonstd/observer_ptr.h000066400000000000000000000156711457431457600224200ustar00rootroot00000000000000// Copyright 2015, 2016 by Martin Moene // // nonstd::observer_ptr<> is a C++98 onward implementation for std::observer_ptr as of C++17. // // This code is licensed under the MIT License (MIT). // #pragma once #ifndef NONSTD_OBSERVER_PTR_H_INCLUDED #define NONSTD_OBSERVER_PTR_H_INCLUDED #include #include #include #define observer_ptr_VERSION "0.2.0" // Configuration: #ifndef nop_FEATURE_ALLOW_IMPLICIT_CONVERSION # define nop_FEATURE_ALLOW_IMPLICIT_CONVERSION 0 #endif #ifndef nop_CONFIG_CONFIRMS_COMPILATION_ERRORS # define nop_CONFIG_CONFIRMS_COMPILATION_ERRORS 0 #endif // Compiler detection: #define nop_CPP11_OR_GREATER ( __cplusplus >= 201103L ) #define nop_CPP14_OR_GREATER ( __cplusplus >= 201402L ) // half-open range [lo..hi): #define nop_BETWEEN( v, lo, hi ) ( lo <= v && v < hi ) #if defined(_MSC_VER) # define nop_COMPILER_MSVC_VERSION (_MSC_VER / 100 - 5 - (_MSC_VER < 1900)) #else # define nop_COMPILER_MSVC_VERSION 0 # define nop_COMPILER_NON_MSVC 1 #endif // Presence of C++ language features: #if nop_CPP11_OR_GREATER # define nop_HAVE_CONSTEXPR_11 1 #endif #if nop_CPP14_OR_GREATER # define nop_HAVE_CONSTEXPR_14 1 #endif #if nop_CPP11_OR_GREATER || nop_COMPILER_MSVC_VERSION >= 14 # define nop_HAVE_EXPLICIT_CONVERSION 1 #endif #if nop_CPP11_OR_GREATER || nop_COMPILER_MSVC_VERSION >= 14 # define nop_HAVE_NOEXCEPT 1 #endif #if nop_CPP11_OR_GREATER || nop_COMPILER_MSVC_VERSION >= 10 # define nop_HAVE_NULLPTR 1 #endif #if defined( __GNUC__ ) # define nop_HAVE_TYPEOF 1 #endif // Presence of C++ library features: // For the rest, consider VC12, VC14 as C++11 for observer_ptr<>: #if nop_COMPILER_MSVC_VERSION >= 12 # undef nop_CPP11_OR_GREATER # define nop_CPP11_OR_GREATER 1 #endif #if nop_CPP11_OR_GREATER || nop_COMPILER_MSVC_VERSION >= 11 # define nop_HAVE_STD_DECAY 1 # define nop_HAVE_STD_DECLVAL 1 #endif // C++ feature usage: #if nop_HAVE_CONSTEXPR_11 # define nop_constexpr constexpr #else # define nop_constexpr /*nothing*/ #endif #if nop_HAVE_CONSTEXPR_14 # define nop_constexpr14 constexpr #else # define nop_constexpr14 /*nothing*/ #endif #if nop_HAVE_EXPLICIT_CONVERSION # define nop_explicit explicit #else # define nop_explicit /*nothing*/ #endif #if nop_HAVE_NOEXCEPT # define nop_noexcept noexcept #else # define nop_noexcept /*nothing*/ #endif #if nop_HAVE_NULLPTR # define nop_NULLPTR nullptr #else # define nop_NULLPTR NULL #endif // common_type: #if nop_HAVE_STD_DECAY && nop_HAVE_STD_DECLVAL # include // std::decay # include // std::declval # define nop_HAVE_OWN_COMMON_TYPE 1 # define nop_HAVE_OWN_COMMON_TYPE_STD 1 #elif nop_HAVE_TYPEOF # define nop_HAVE_OWN_COMMON_TYPE 1 # define nop_HAVE_OWN_COMMON_TYPE_TYPEOF 1 #endif namespace nonstd { template< class W > class observer_ptr { public: typedef W element_type; typedef W * pointer; typedef W & reference; nop_constexpr14 observer_ptr() nop_noexcept : ptr( nop_NULLPTR ) {} #if nop_HAVE_NULLPTR nop_constexpr14 observer_ptr( std::nullptr_t ) nop_noexcept : ptr( nullptr ) {} #endif nop_constexpr14 observer_ptr( pointer p ) nop_noexcept : ptr(p) {} template< class W2 > nop_constexpr14 observer_ptr(observer_ptr other ) nop_noexcept : ptr( other.get() ) {} template< class W2 > nop_constexpr14 observer_ptr(const std::unique_ptr& other) : ptr(other.get()) {} template< class W2 > nop_constexpr14 observer_ptr(const std::shared_ptr& other) : ptr(other.get()) {} nop_constexpr14 pointer get() const nop_noexcept { return ptr; } nop_constexpr14 reference operator*() const { return assert( ptr != nop_NULLPTR ), *ptr; } nop_constexpr14 pointer operator->() const nop_noexcept { return ptr; } #if nop_HAVE_EXPLICIT_CONVERSION nop_constexpr14 explicit operator bool() const nop_noexcept { return ptr != nop_NULLPTR; } nop_constexpr14 explicit operator pointer() const nop_noexcept { return ptr; } #elif nop_FEATURE_ALLOW_IMPLICIT_CONVERSION nop_constexpr14 operator pointer() const nop_noexcept { return ptr; } #else typedef void (observer_ptr::*safe_bool)() const; void this_type_does_not_support_comparisons() const {} nop_constexpr14 operator safe_bool() const nop_noexcept { return ptr != nop_NULLPTR ? &observer_ptr::this_type_does_not_support_comparisons : 0; } #endif nop_constexpr14 pointer release() nop_noexcept { pointer p( ptr ); reset(); return p; } nop_constexpr14 void reset( pointer p = nop_NULLPTR ) nop_noexcept { ptr = p; } nop_constexpr14 void swap( observer_ptr & other ) nop_noexcept { using std::swap; swap(ptr, other.ptr); } private: pointer ptr; }; // specialized algorithms: template< class W > void swap( observer_ptr & p1, observer_ptr & p2 ) nop_noexcept { p1.swap( p2 ); } template< class W > observer_ptr make_observer( W * p ) nop_noexcept { return observer_ptr( p ); } template< class W1, class W2 > bool operator==( observer_ptr p1, observer_ptr p2 ) { return p1.get() == p2.get(); } template< class W1, class W2 > bool operator!=( observer_ptr p1, observer_ptr p2 ) { return !( p1 == p2 ); } #if nop_HAVE_NULLPTR template< class W > bool operator==( observer_ptr p, std::nullptr_t ) nop_noexcept { return !p; } template< class W > bool operator==( std::nullptr_t, observer_ptr p ) nop_noexcept { return !p; } template< class W > bool operator!=( observer_ptr p, std::nullptr_t ) nop_noexcept { return (bool)p; } template< class W > bool operator!=( std::nullptr_t, observer_ptr p ) nop_noexcept { return (bool)p; } #endif namespace detail { template< class T, class U > #if nop_HAVE_OWN_COMMON_TYPE_STD struct common_type { typedef typename std::decay< decltype(true ? std::declval() : std::declval()) >::type type; }; #elif nop_HAVE_OWN_COMMON_TYPE_TYPEOF struct common_type { typedef __typeof__( true ? T() : U() ) type; }; #else // fall back struct common_type { typedef T type; }; #endif } // namespace detail template< class W1, class W2 > bool operator<( observer_ptr p1, observer_ptr p2 ) { // return std::less()( p1.get(), p2.get() ); // where W3 is the composite pointer type (C++14 5) of W1* and W2*. return std::less< typename detail::common_type::type >()( p1.get(), p2.get() ); } template< class W1, class W2 > bool operator>( observer_ptr p1, observer_ptr p2 ) { return p2 < p1; } template< class W1, class W2 > bool operator<=( observer_ptr p1, observer_ptr p2 ) { return !( p2 < p1 ); } template< class W1, class W2 > bool operator>=( observer_ptr p1, observer_ptr p2 ) { return !( p1 < p2 ); } } // namespace nonstd // #undef ... #endif // NONSTD_OBSERVER_PTR_H_INCLUDED // end of file wayfire-0.8.1/src/api/wayfire/nonstd/reverse.hpp000066400000000000000000000011011457431457600216760ustar00rootroot00000000000000#ifndef WF_REVERSE_HPP #define WF_REVERSE_HPP #include /* from https://stackoverflow.com/questions/8542591/c11-reverse-range-based-for-loop * */ namespace wf { template struct reversion_wrapper { T& iterable; }; template auto begin(reversion_wrapper w) { return std::rbegin(w.iterable); } template auto end(reversion_wrapper w) { return std::rend(w.iterable); } template reversion_wrapper reverse(T&& iterable) { return {iterable}; } } #endif /* end of include guard: WF_REVERSE_HPP */ wayfire-0.8.1/src/api/wayfire/nonstd/safe-list.hpp000066400000000000000000000105301457431457600221200ustar00rootroot00000000000000#ifndef WF_SAFE_LIST_HPP #define WF_SAFE_LIST_HPP #include #include #include #include #include #include #include #include #include #include "reverse.hpp" namespace wf { /** * The safe list is a trimmed down version of std::list. * * It supports adding an item to the end of a list, iterating over it and erasing elements. * The important advantage is that elements can be callbacks, which may be executed during the iteration, * and the callbacks can then add or remove elements from the list safely. * * The typical usage of safe list is for bindings and signal handlers. */ template class safe_list_t { static_assert(std::is_move_constructible_v>, "T must be moveable!"); public: safe_list_t() {} T& back() { auto it = list.rbegin(); assert((it != list.rend()) && "back() on an empty list!"); while (!((*it).has_value())) { ++it; assert((it != list.rend()) && "back() on an empty list!"); } return **it; } size_t size() const { if (!is_dirty) { return list.size(); } return std::count_if(list.begin(), list.end(), [] (const auto& elem) { return elem.has_value(); }); } /* Push back by copying */ void push_back(T value) { list.push_back({std::move(value)}); } /* Call func for each non-erased element of the list */ void for_each(std::function func) { _start_iter(); // Important: make sure we do not iterate over additional values in the list which are added // afterwards. size_t size = list.size(); for (size_t i = 0; i < size; i++) { if (list[i]) { func(*list[i]); } } _stop_iter(); } /* Call func for each non-erased element of the list in reversed order */ void for_each_reverse(std::function func) { _start_iter(); for (size_t i = list.size(); i > 0; i--) { if (list[i - 1]) { func(*list[i - 1]); } } _stop_iter(); } /* Safely remove all elements equal to value */ void remove_all(const T& value) { remove_if([=] (const T& el) { return el == value; }); } /* Remove all elements from the list */ void clear() { remove_if([] (const T&) { return true; }); } /* Remove all elements satisfying a given condition. * This function resets their pointers and scheduling a cleanup operation */ void remove_if(std::function predicate) { _start_iter(); const size_t size = list.size(); for (size_t i = 0; i < size; i++) { if (list[i] && predicate(*list[i])) { /* First reset the element in the list, and then free resources */ auto value = std::move(list[i]); list[i].reset(); is_dirty = true; // Call destructor value.reset(); } } _stop_iter(); _try_cleanup(); } private: /** * A vector containing the values of the list. * To make sure we can iterate over the list and erase any elements from it during iteration, the 'erase' * operation simply resets the optional value in the list. * * After all iterations are done, the list is 'cleaned up', that is, empty elements are removed from it. */ std::vector> list; int iteration_counter = 0; bool is_dirty = false; /* Remove all invalidated elements in the list */ void _try_cleanup() { if ((iteration_counter > 0) || !is_dirty) { // There is an active iteration. return; } auto it = std::remove_if(list.begin(), list.end(), [&] (const std::optional& elem) { return !elem.has_value(); }); list.erase(it, list.end()); is_dirty = false; } void _start_iter() { ++iteration_counter; } void _stop_iter() { --iteration_counter; _try_cleanup(); } }; } #endif /* end of include guard: WF_SAFE_LIST_HPP */ wayfire-0.8.1/src/api/wayfire/nonstd/tracking-allocator.hpp000066400000000000000000000041731457431457600240170ustar00rootroot00000000000000#pragma once #include #include #include #include #include namespace wf { /** * The destruct signal is emitted directly before an object is freed. * Emitted on objects managed with the tracking allocator which support signals. */ template struct destruct_signal { T *object; }; /** * The tracking allocator is a factory singleton for allocating objects of a certain type. * The objects are allocated via shared pointers, and the tracking allocator keeps a list of all allocated * objects, accessible by plugins. */ template class tracking_allocator_t { public: /** * Get the single global instance of the tracking allocator. */ static tracking_allocator_t& get() { static tracking_allocator_t allocator; return allocator; } template std::shared_ptr allocate(Args... args) { static_assert(std::is_base_of_v); auto ptr = std::shared_ptr( new ConcreteObjectType(std::forward(args)...), std::bind(&tracking_allocator_t::deallocate_object, this, std::placeholders::_1)); allocated_objects.push_back(ptr.get()); return ptr; } const std::vector>& get_all() { return allocated_objects; } private: std::vector> allocated_objects; void deallocate_object(ObjectType *obj) { if constexpr (std::is_base_of_v) { destruct_signal event; event.object = obj; obj->emit(&event); } auto it = std::find(allocated_objects.begin(), allocated_objects.end(), nonstd::observer_ptr{obj}); wf::dassert(it != allocated_objects.end(), "Object is not allocated?"); allocated_objects.erase(it); delete obj; } }; } wayfire-0.8.1/src/api/wayfire/nonstd/wlroots-full.hpp000066400000000000000000000105271457431457600227100ustar00rootroot00000000000000#pragma once /** * This file is used to put all wlroots headers needed in the Wayfire * (not only API) in an extern "C" block because wlroots headers are not * always compatible with C++. * * Note that some wlroots headers require generated protocol header files. * There are disabled unless the protocol header file is present. */ #include // WF_USE_CONFIG_H is set only when building Wayfire itself, external plugins // need to use #ifdef WF_USE_CONFIG_H #include #else #include #endif extern "C" { // Rendering #define static #include #include #include #include #include #include #include #undef static #include #include #include #include #include #include #include #include // Shells #if __has_include() #include #include #endif #include #include #include #include // layer-shell needs the protocol file, so we cannot expose it here #if __has_include() #define namespace namespace_t #include #undef namespace #endif #if WF_HAS_XWAYLAND // We need to rename class to class_t for the xwayland definitions. // However, it indirectly includes pthread.h which uses 'class' in the // C++ meaning, so we should include pthread before overriding 'class'. #if __has_include() #include #endif #define class class_t #define static #include #undef static #undef class #endif #include #include #include // Backends #include #define static #include #include #if WLR_HAS_X11_BACKEND #include #endif #include #undef static #include #include #include #include // Output management #include #include #if __has_include() #include #endif #include #include #include // Input #include #if __has_include() #include #endif #include #if __has_include() #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define delete delete_ #include #undef delete #include #include #include #include #include // Activation plugin #include } wayfire-0.8.1/src/api/wayfire/nonstd/wlroots.hpp000066400000000000000000000040761457431457600217520ustar00rootroot00000000000000#pragma once #ifndef WLR_USE_UNSTABLE #define WLR_USE_UNSTABLE #endif /** * This file is used to put all wlroots headers needed in the Wayfire API * in an extern "C" block because wlroots headers are not always compatible * with C++. */ extern "C" { struct wlr_backend; struct wlr_renderer; struct wlr_seat; struct wlr_cursor; struct wlr_data_device_manager; struct wlr_data_control_manager_v1; struct wlr_gamma_control_manager_v1; struct wlr_xdg_output_manager_v1; struct wlr_export_dmabuf_manager_v1; struct wlr_server_decoration_manager; struct wlr_input_inhibit_manager; struct wlr_idle_inhibit_manager_v1; struct wlr_xdg_decoration_manager_v1; struct wlr_virtual_keyboard_manager_v1; struct wlr_virtual_pointer_manager_v1; struct wlr_idle_notifier_v1; struct wlr_screencopy_manager_v1; struct wlr_foreign_toplevel_manager_v1; struct wlr_pointer_gestures_v1; struct wlr_relative_pointer_manager_v1; struct wlr_pointer_constraints_v1; struct wlr_tablet_manager_v2; struct wlr_input_method_manager_v2; struct wlr_text_input_manager_v3; struct wlr_presentation; struct wlr_primary_selection_v1_device_manager; struct wlr_drm_lease_v1_manager; struct wlr_xdg_foreign_v1; struct wlr_xdg_foreign_v2; struct wlr_xdg_foreign_registry; struct wlr_pointer_axis_event; struct wlr_pointer_motion_event; struct wlr_output_layout; struct wlr_surface; struct wlr_texture; struct wlr_viewporter; #include #include #include #include #define static #include #undef static #include #include #include #include static constexpr uint32_t WLR_KEY_PRESSED = WL_KEYBOARD_KEY_STATE_PRESSED; static constexpr uint32_t WLR_KEY_RELEASED = WL_KEYBOARD_KEY_STATE_RELEASED; struct mwlr_keyboard_modifiers_event { uint32_t time_msec; }; } wayfire-0.8.1/src/api/wayfire/object.hpp000066400000000000000000000073771457431457600202110ustar00rootroot00000000000000#ifndef OBJECT_HPP #define OBJECT_HPP #include #include #include #include namespace wf { /** * Subclasses of custom_data_t can be stored inside an object_base_t */ class custom_data_t { public: custom_data_t() = default; virtual ~custom_data_t() = default; custom_data_t(custom_data_t&& other) = default; custom_data_t(const custom_data_t& other) = default; custom_data_t& operator =(custom_data_t&& other) = default; custom_data_t& operator =(const custom_data_t& other) = default; }; /** * A base class for "objects". Objects provide signals and ways for plugins to * store custom data about the object. */ class object_base_t { public: /** Get a human-readable description of the object */ std::string to_string() const; /** Get the ID of the object. Each object has a unique ID */ uint32_t get_id() const; /** * Retrieve custom data stored with the given name. If no such data exists, * then it is created with the default constructor. * * REQUIRES a default constructor * If your type doesn't have one, use store_data + get_data */ template nonstd::observer_ptr get_data_safe( std::string name = typeid(T).name()) { auto data = get_data(name); if (data) { return data; } else { store_data(std::make_unique(), name); return get_data(name); } } /* Retrieve custom data stored with the given name. If no such * data exists, NULL is returned */ template nonstd::observer_ptr get_data( std::string name = typeid(T).name()) { return nonstd::make_observer(dynamic_cast(_fetch_data(name))); } /* Assigns the given data to the given name */ template void store_data(std::unique_ptr stored_data, std::string name = typeid(T).name()) { _store_data(std::move(stored_data), name); } /* Returns true if there is saved data under the given name */ template bool has_data() { return has_data(typeid(T).name()); } /** @return true if there is saved data with the given name */ bool has_data(std::string name); /** Remove the saved data under the given name */ void erase_data(std::string name); /** Remove the saved data for the type T */ template void erase_data() { erase_data(typeid(T).name()); } /* Erase the saved data from the store and return the pointer */ template std::unique_ptr release_data( std::string name = typeid(T).name()) { if (!has_data(name)) { return {nullptr}; } auto stored = _fetch_erase(name); return std::unique_ptr(dynamic_cast(stored)); } virtual ~object_base_t(); object_base_t(const object_base_t &) = delete; object_base_t(object_base_t &&) = delete; object_base_t& operator =(const object_base_t&) = delete; object_base_t& operator =(object_base_t&&) = delete; protected: object_base_t(); /** Clear all stored data. */ void _clear_data(); private: /** Just get the data under the given name, or nullptr, if it does not exist */ custom_data_t *_fetch_data(std::string name); /** Get the data under the given name, and release the pointer, deleting * the entry in the map */ custom_data_t *_fetch_erase(std::string name); /** Store the given data under the given name */ void _store_data(std::unique_ptr data, std::string name); class obase_impl; std::unique_ptr obase_priv; }; } #endif /* end of include guard: OBJECT_HPP */ wayfire-0.8.1/src/api/wayfire/opengl.hpp000066400000000000000000000347401457431457600202210ustar00rootroot00000000000000#ifndef WF_OPENGL_HPP #define WF_OPENGL_HPP #include "wayfire/region.hpp" #include #include #include #include #include #define GLM_FORCE_RADIANS #include #include void gl_call(const char*, uint32_t, const char*); #ifndef __STRING # define __STRING(x) #x #endif /* * recommended to use this to make OpenGL calls, since it offers easier debugging * This macro is taken from WLC source code */ #define GL_CALL(x) x;gl_call(__PRETTY_FUNCTION__, __LINE__, __STRING(x)) struct gl_geometry { float x1, y1, x2, y2; }; namespace wf { /** * A simple class for managing framebuffers. * It can handle allocation and deallocation (not done automatically) of * framebuffers and their backing textures. */ struct framebuffer_t { GLuint tex = -1, fb = -1; int32_t viewport_width = 0, viewport_height = 0; /* The functions below assume they are called between * OpenGL::render_begin() and OpenGL::render_end() */ /* will invalidate texture contents if width or height changes. * If tex and/or fb haven't been set, it creates them * Return true if texture was created/invalidated */ bool allocate(int width, int height); /* Make the framebuffer current, and adjust viewport to its size */ void bind() const; /* Set the GL scissor to the given box, after inverting it to match GL * coordinate space */ void scissor(wlr_box box) const; /* Will destroy the texture and framebuffer * Warning: will destroy tex/fb even if they have been allocated outside of * allocate() */ void release(); /* Reset the framebuffer, WITHOUT freeing resources. * There is no need to call reset() after release() */ void reset(); }; /** * A render target contains a framebuffer and information on how to map * coordinates from the logical coordinate space (output-local coordinates, etc.) * to framebuffer coordinates. * * A render target may or not cover the full framebuffer. */ struct render_target_t : public framebuffer_t { // Describes the logical coordinates of the render area, in whatever // coordinate system the render target needs. wf::geometry_t geometry = {0, 0, 0, 0}; wl_output_transform wl_transform = WL_OUTPUT_TRANSFORM_NORMAL; // The scale of a framebuffer is a hint at how bigger the actual framebuffer // is compared to the logical geometry. It is useful for plugins utilizing // auxiliary buffers in logical coordinates, so that they know they should // render with higher resolution and still get a crisp image on the screen. float scale = 1.0; // If set, the subbuffer indicates a subrectangle of the framebuffer which // is used instead of the full buffer. In that case, the logical @geometry // is mapped only to that subrectangle and not to the full framebuffer. // Note: (0,0) is top-left for subbuffer. std::optional subbuffer; /* Transform contains output rotation, and possibly * other framebuffer transformations, if has_nonstandard_transform is set */ glm::mat4 transform = glm::mat4(1.0); /** * Get a render target which is the same as this, but whose geometry is * translated by @offset. */ render_target_t translated(wf::point_t offset) const; /** * Get the geometry of the given box after projecting it onto the framebuffer. * In the values returned, (0,0) is top-left. * * The resulting geometry is affected by the framebuffer geometry, scale and * transform. */ wlr_box framebuffer_box_from_geometry_box(wlr_box box) const; /** * Get the geometry of the given region after projecting it onto the framebuffer. This is the same as * iterating over the rects in the region and transforming them with framebuffer_box_from_geometry_box. */ wf::region_t framebuffer_region_from_geometry_region(const wf::region_t& region) const; /* Returns a matrix which contains an orthographic projection from "geometry" * coordinates to the framebuffer coordinates. */ glm::mat4 get_orthographic_projection() const; /* Returns a matrix which contains an orthographic projection from OpenGL [-1, 1] coordinates * coordinates to the framebuffer coordinates (includes rotation, subbuffer, etc). */ glm::mat4 gl_to_framebuffer() const; /** * Set the scissor region to the given box. * * In contrast to framebuffer_t::scissor(), this method takes its argument * as a box with "logical" coordinates, not raw framebuffer coordinates. * * @param box The scissor box, in the same coordinate system as the * framebuffer's geometry. */ void logic_scissor(wlr_box box) const; }; } namespace wf { /** Represents the different types(formats) of textures in Wayfire. */ enum texture_type_t { /* Regular OpenGL texture with 4 channels */ TEXTURE_TYPE_RGBA = 0, /* Regular OpenGL texture with 4 channels, but alpha channel should be * discarded. */ TEXTURE_TYPE_RGBX = 1, /** An EGLImage, it has been shared via dmabuf */ TEXTURE_TYPE_EXTERNAL = 2, /* Invalid */ TEXTURE_TYPE_ALL = 3, }; struct texture_t { /* Texture type */ texture_type_t type = TEXTURE_TYPE_RGBA; /* Texture target */ GLenum target = GL_TEXTURE_2D; /* Actual texture ID */ GLuint tex_id; /** Invert Y? */ bool invert_y = false; /** Has viewport? */ bool has_viewport = false; /** * Part of the texture which is used for rendering. * Valid only if has_viewport is true. */ gl_geometry viewport_box; /* tex_id will be initialized later */ texture_t(); /** Initialize a non-inverted RGBA texture with the given texture id */ texture_t(GLuint tex); /** Initialize a texture with the attributes of the wlr texture */ explicit texture_t(wlr_texture*, std::optional viewport = {}); }; } namespace OpenGL { /* "Begin" rendering to the given framebuffer and the given viewport. * All rendering operations should happen between render_begin and render_end, * because * that's the only time we're guaranteed we have a valid GLES context * * The other functions below assume they are called between render_begin() * and render_end() */ void render_begin(); // use if you just want to bind GL context but won't draw void render_begin(const wf::framebuffer_t& fb); void render_begin(int32_t viewport_width, int32_t viewport_height, uint32_t fb); /* Call this to indicate an end of the rendering. * Resets bound framebuffer and scissor box. * render_end() must be called for each render_begin() */ void render_end(); /* Clear the currently bound framebuffer with the given color */ void clear(wf::color_t color, uint32_t mask = GL_COLOR_BUFFER_BIT); enum rendering_flags_t { /* Invert the texture's X axis when sampling */ TEXTURE_TRANSFORM_INVERT_X = (1 << 0), /* Invert the texture's Y axis when sampling */ TEXTURE_TRANSFORM_INVERT_Y = (1 << 1), /* Use a subrectangle of the texture to render */ TEXTURE_USE_TEX_GEOMETRY = (1 << 2), /* * Enable an optimized, "cached" mode. * * The user first calls a render_texture variant with this bit set. * The default GL program will be called, uniforms uploaded, etc. * After that, draw_cached() may be used for each damaged rectangle. * In the end, clear_cache() is called. * * This allows re-use of uniform values for different damage rectangles. */ RENDER_FLAG_CACHED = (1 << 3), }; /** * Render a textured quad using the built-in shaders. * * @param texture The texture to render. * @param g The initial coordinates of the quad. * @param texg A rectangle containing the subtexture of @texture to render. * To enable rendering a subtexture, use * TEXTURE_USE_TEX_GEOMETRY. Texture coordinates are in the * usual coordinate system [0,1]x[0,1]. x1/y1 describe the * lower-left corner, and x2/y2 - the upper-right corner. * @param transform The matrix transformation to apply to the quad. * @param color A color multiplier for each channel of the texture. * @param bits A bitwise OR of texture_rendering_flags_t. */ void render_transformed_texture(wf::texture_t texture, const gl_geometry& g, const gl_geometry& texg, glm::mat4 transform = glm::mat4(1.0), glm::vec4 color = glm::vec4(1.f), uint32_t bits = 0); /** * Render a textured quad using the built-in shaders. * * @param texture The texture to render. * @param geometry The initial coordinates of the quad. * @param transform The matrix transformation to apply to the quad. * @param color A color multiplier for each channel of the texture. * @param bits A bitwise OR of texture_rendering_flags_t. In this variant, * TEX_GEOMETRY flag is ignored. */ void render_transformed_texture(wf::texture_t texture, const wf::geometry_t& geometry, glm::mat4 transform = glm::mat4(1.0), glm::vec4 color = glm::vec4(1.f), uint32_t bits = 0); /** * Render a textured quad on the given framebuffer. * * @param texture The texture to render. * @param fb The framebuffer to render onto. * It should have been already bound. * @param geometry The geometry of the quad to render, in the same coordinate * system as the framebuffer geometry. * @param color A color multiplier for each channel of the texture. * @param bits A bitwise OR of texture_rendering_flags_t. In this variant, * TEX_GEOMETRY flag is ignored. */ void render_texture(wf::texture_t texture, const wf::render_target_t& framebuffer, const wf::geometry_t& geometry, glm::vec4 color = glm::vec4(1.f), uint32_t bits = 0); /** * Render the textured rectangle again. * * See RENDER_FLAG_CACHED for detailed explanation. */ void draw_cached(); /** * Clear the cached state. * * See RENDER_FLAG_CACHED for detailed explanation. */ void clear_cached(); /* Compiles the given shader source */ GLuint compile_shader(std::string source, GLuint type); /** * Create an OpenGL program from the given shader sources. * * @param vertex_source The source code of the vertex shader. * @param frag_source The source code of the fragment shader. */ GLuint compile_program(std::string vertex_source, std::string frag_source); /** * Render a colored rectangle using OpenGL. * * @param box The rectangle geometry. * @param color The color of the rectangle. * @param matrix The matrix to transform the rectangle with. */ void render_rectangle(wf::geometry_t box, wf::color_t color, glm::mat4 matrix); /** * An OpenGL program for rendering texture_t. * It contains multiple programs for the different texture types. * * All of the program_t's functions should only be used inside a rendering * block guarded by render_begin/end() */ class program_t { public: program_t(); /* Does nothing */ ~program_t(); program_t(const program_t &) = delete; program_t(program_t &&) = default; program_t& operator =(const program_t&) = delete; program_t& operator =(program_t&&) = default; /** * Compile the program consisting of @vertex_source and @fragment_source. * * Fragment source should contain two special symbols`@builtin@` and * `@builtin_ext@`.They will be replaced by the definitions needed for each * texture type, and will also provide a function `get_pixel(vec2)` to get * the texture pixel at the given position. `@builtin_ext@` has to be put * directly after the OpenGL version declaration, but there are no * restrictions about where to place `@builtin@`. * * The following identifiers should not be defined in the user source: * _wayfire_texture, _wayfire_uv_scale, _wayfire_y_base, get_pixel */ void compile(const std::string& vertex_source, const std::string& fragment_source); /** * Create a simple program * It will support only the given type. */ void set_simple(GLuint program_id, wf::texture_type_t type = wf::TEXTURE_TYPE_RGBA); /** Deletes the underlying OpenGL programs */ void free_resources(); /** * Call glUseProgram with the appropriate program for the given texture type. * Raises a runtime exception if the type is not supported by the * view_program_t . */ void use(wf::texture_type_t type); /** @return The program ID for the given texture type, or 0 on failure */ int get_program_id(wf::texture_type_t type); /** Set the given uniform for the currently used program. */ void uniform1i(const std::string& name, int value); /** Set the given uniform for the currently used program. */ void uniform1f(const std::string& name, float value); /** Set the given uniform for the currently used program. */ void uniform2f(const std::string& name, float x, float y); /** Set the given uniform for the currently used program. */ void uniform3f(const std::string& name, float x, float y, float z); /** Set the given uniform for the currently used program. */ void uniform4f(const std::string& name, const glm::vec4& value); /** Set the given uniform for the currently used program. */ void uniformMatrix4f(const std::string& name, const glm::mat4& value); /* * Set the attribute pointer and active the attribute. * * @param attrib The name of the attrib array. * @param size, stride, ptr, type The same as the corresponding arguments of * glVertexAttribPointer() */ void attrib_pointer(const std::string& attrib, int size, int stride, const void *ptr, GLenum type = GL_FLOAT); /* * Set the attrib divisor. Analogous to glVertexAttribDivisor(). * * @param attrib The name of the attribute. * @param divisor The divisor value. */ void attrib_divisor(const std::string& attrib, int divisor); /** * Set the active texture, and modify the builtin Y-inversion uniforms. * Will not work with custom programs. */ void set_active_texture(const wf::texture_t& texture); /** * Deactivate the vertex attributes activated by attrib_pointer and * attrib_divisor, and reset the active OpenGL program. */ void deactivate(); private: class impl; std::unique_ptr priv; }; } /* utils */ glm::mat4 get_output_matrix_from_transform(wl_output_transform transform); #endif // WF_OPENGL_HPP wayfire-0.8.1/src/api/wayfire/option-wrapper.hpp000066400000000000000000000013701457431457600217140ustar00rootroot00000000000000#pragma once #include #include #include namespace wf { /** * A simple wrapper around a config option. */ template class option_wrapper_t : public base_option_wrapper_t { public: /** * Initialize the option wrapper and directly load the given option. */ option_wrapper_t(const std::string& option_name) : wf::base_option_wrapper_t() { this->load_option(option_name); } option_wrapper_t() : wf::base_option_wrapper_t() {} protected: std::shared_ptr load_raw_option(const std::string& name) { return wf::get_core().config.get_option(name); } }; } wayfire-0.8.1/src/api/wayfire/output-layout.hpp000066400000000000000000000107711457431457600216060ustar00rootroot00000000000000#ifndef OUTPUT_LAYOUT_HPP #define OUTPUT_LAYOUT_HPP #include #include #include #include #include #include #include "geometry.hpp" #include "wayfire/object.hpp" #include "wayfire/signal-provider.hpp" #include "wayfire/util.hpp" #define RENDER_BIT_DEPTH_DEFAULT 8 namespace wf { class output_t; /** Represents the source of pixels for this output */ enum output_image_source_t { OUTPUT_IMAGE_SOURCE_INVALID = 0x0, /** Output renders itself */ OUTPUT_IMAGE_SOURCE_SELF = 0x1, /** Output is turned off */ OUTPUT_IMAGE_SOURCE_NONE = 0x2, /** Output is in DPMS state */ OUTPUT_IMAGE_SOURCE_DPMS = 0x3, /** Output is in mirroring state */ OUTPUT_IMAGE_SOURCE_MIRROR = 0x4, }; /** Represents the current state of an output as the output layout sees it */ struct output_state_t { /* The current source of the output. * * If source is none, then the values below don't have a meaning. * If source is mirror, then only mirror_from and mode have a meaning */ output_image_source_t source = OUTPUT_IMAGE_SOURCE_INVALID; /** Position for the output */ wf::output_config::position_t position; /** Only width, height and refresh fields are used. */ wlr_output_mode mode; /* The transform of the output */ wl_output_transform transform = WL_OUTPUT_TRANSFORM_NORMAL; /* The scale of the output */ double scale = 1.0; /* Whether or not adaptive sync is enabled */ bool vrr = false; /* Output format bit depth */ int depth = RENDER_BIT_DEPTH_DEFAULT; /* Output to take the image from. Valid only if source is mirror */ std::string mirror_from; bool operator ==(const output_state_t& other) const; }; /** An output configuration is simply a list of each output with its state */ using output_configuration_t = std::map; /* output_layout_t is responsible for managing outputs and their attributes - * mode, scale, position, transform. */ class output_layout_t : public wf::signal::provider_t { public: output_layout_t(wlr_backend *backend); ~output_layout_t(); /** * @return the underlying wlr_output_layout */ wlr_output_layout *get_handle(); /** * @return the output at the given coordinates, or null if no such output */ wf::output_t *get_output_at(int x, int y); /** * Get the output closest to the given origin and the closest coordinates * to origin which lie inside the output. * * @param origin The start coordinates * @param closest The closest point to origin inside the returned output * @return the output at the given coordinates */ wf::output_t *get_output_coords_at(wf::pointf_t origin, wf::pointf_t& closest); /** * @return the number of the active outputs in the output layout */ size_t get_num_outputs(); /** * @return a list of the active outputs in the output layout */ std::vector get_outputs(); /** * @return the "next" output in the layout. It is guaranteed that starting * with any output in the layout, and successively calling this function * will iterate over all outputs */ wf::output_t *get_next_output(wf::output_t *output); /** * @return the output_t associated with the wlr_output, or null if the * output isn't found */ wf::output_t *find_output(wlr_output *output); wf::output_t *find_output(std::string name); /** * @return the current output configuration. This contains ALL outputs, * not just the ones in the actual layout (so disabled ones are included * as well) */ output_configuration_t get_current_configuration(); /** * Apply the given configuration. It must contain exactly the outputs * returned by get_current_configuration() - no more and no less. * * Failure to apply the configuration on any output will reset all * outputs to their previous state. * * @param configuration The output configuration to be applied * @param test_only If true, this will only simulate applying * the configuration, without actually changing anything * * @return true on successful application, false otherwise */ bool apply_configuration(const output_configuration_t& configuration, bool test_only = false); class impl; std::unique_ptr pimpl; }; } #endif /* end of include guard: OUTPUT_LAYOUT_HPP */ wayfire-0.8.1/src/api/wayfire/output.hpp000066400000000000000000000152511457431457600202710ustar00rootroot00000000000000#ifndef OUTPUT_HPP #define OUTPUT_HPP #include "wayfire/geometry.hpp" #include "wayfire/object.hpp" #include "wayfire/bindings.hpp" #include "wayfire/scene-input.hpp" #include "wayfire/scene.hpp" #include "wayfire/signal-provider.hpp" #include #include #include namespace wf { class view_interface_t; } using wayfire_view = nonstd::observer_ptr; namespace wf { class render_manager; class workspace_set_t; class output_workarea_manager_t; struct plugin_activation_data_t; /** * Flags which can be passed to wf::output_t::activate_plugin() and * wf::output_t::can_activate_plugin(). */ enum plugin_activation_flags_t { /** * Activate the plugin even if input is inhibited, for ex. even when a * lockscreen is active. */ PLUGIN_ACTIVATION_IGNORE_INHIBIT = (1 << 0), /* * Allow the same plugin to be activated multiple times. * The plugin will also have to be deactivated as many times as it has been * activated. */ PLUGIN_ACTIVATE_ALLOW_MULTIPLE = (1 << 1), }; class output_t : public wf::object_base_t, public wf::signal::provider_t { public: /** * The wlr_output that this output represents */ wlr_output *handle; /** * The render manager of this output */ std::unique_ptr render; /** * The manager of the workspace area for this output. */ std::unique_ptr workarea; /** * Get the current workspace set of the output. */ virtual std::shared_ptr wset() = 0; /** * Set the current workspace set. * * The old workspace set will become invisible (that is, necessary scenegraph nodes will be disabled), but * it will remain attached to the output. */ virtual void set_workspace_set(std::shared_ptr wset) = 0; /** * Get a textual representation of the output */ std::string to_string() const; /** * Get the logical resolution of the output, i.e if an output has mode * 3860x2160, scale 2 and transform 90, then get_screen_size will report * that it has logical resolution of 1080x1920 */ virtual wf::dimensions_t get_screen_size() const = 0; /** * Same as get_screen_size() but returns a wf::geometry_t with x,y = 0 */ wf::geometry_t get_relative_geometry() const; /** * Returns the output geometry as the output layout sees it. This is * typically the same as get_relative_geometry() but with meaningful x and y */ wf::geometry_t get_layout_geometry() const; /** * Moves the pointer so that it is inside the output * * @param center If set to true, the pointer will be centered on the * output, regardless of whether it was inside before. */ void ensure_pointer(bool center = false) const; /** * Gets the cursor position relative to the output */ wf::pointf_t get_cursor_position() const; virtual std::shared_ptr node_for_layer( wf::scene::layer layer) const = 0; /** * Checks if a plugin can activate. This may not succeed if a plugin * with the same abilities is already active or if input is inhibited. * * @param flags A bitwise OR of plugin_activation_flags_t. * * @return true if the plugin is able to be activated, false otherwise. */ virtual bool can_activate_plugin(wf::plugin_activation_data_t *owner, uint32_t flags = 0) = 0; /** * Same as can_activate_plugin(plugin_grab_interface_uptr), but checks for * any plugin with the given capabilities. * * @param caps The capabilities to check. * @param flags A bitwise OR of plugin_activation_flags_t. */ virtual bool can_activate_plugin(uint32_t caps, uint32_t flags = 0) = 0; /** * Activates a plugin. Note that this may not succeed, if a plugin with the * same abilities is already active. However the same plugin might be * activated twice. * * @param flags A bitwise OR of plugin_activation_flags_t. * * @return true if the plugin was successfully activated, false otherwise. */ virtual bool activate_plugin(wf::plugin_activation_data_t *owner, uint32_t flags = 0) = 0; /** * Deactivates a plugin once, i.e if the plugin was activated more than * once, only one activation is removed. * * @return true if the plugin remains activated, false otherwise. */ virtual bool deactivate_plugin(wf::plugin_activation_data_t *owner) = 0; /** * Send cancel to all active plugins, * see plugin_grab_interface_t::callbacks.cancel */ virtual void cancel_active_plugins() = 0; /** * @return true if a grab interface with the given name is activated, false * otherwise. */ virtual bool is_plugin_active(std::string owner_name) const = 0; /** * Switch the workspace so that view becomes visible. * @return true if workspace switch really occurred */ bool ensure_visible(wayfire_view view); /** * the add_* functions are used by plugins to register bindings. They pass * a wf::option_t, which means that core will always use the latest binding * which is in the option. * * Adding a binding happens on a per-output basis. If a plugin registers * bindings on each output, it will receive for ex. a keybinding only on * the currently focused one. * * @return The wf::binding_t which can be used to unregister the binding. */ virtual void add_key(option_sptr_t key, wf::key_callback*) = 0; virtual void add_axis(option_sptr_t axis, wf::axis_callback*) = 0; virtual void add_button(option_sptr_t button, wf::button_callback*) = 0; virtual void add_activator(option_sptr_t activator, wf::activator_callback*) = 0; /** * Remove all bindings which have the given callback, regardless of the type. */ virtual void rem_binding(void *callback) = 0; virtual ~output_t(); protected: /* outputs are instantiated internally by core */ output_t(); }; /** * Find the active view on the given output. It is the same as wf::get_core().seat->get_active_view() if the * output is currently focused, otherwise NULL. */ wayfire_view get_active_view_for_output(wf::output_t *output); /** * Collect all nodes which belong to an output from the scenegraph. */ std::vector> collect_output_nodes( wf::scene::node_ptr root, wf::output_t *output); } #endif /* end of include guard: OUTPUT_HPP */ wayfire-0.8.1/src/api/wayfire/per-output-plugin.hpp000066400000000000000000000047561457431457600223610ustar00rootroot00000000000000#include "wayfire/object.hpp" #include "wayfire/signal-definitions.hpp" #include "wayfire/signal-provider.hpp" #include #include #include namespace wf { /** * A base class for plugins which want to have an instance per output. */ class per_output_plugin_instance_t { public: // Should be set before initializing the plugin instance. Usually done by per_output_tracker_mixin_t. wf::output_t *output = nullptr; virtual void init() {} virtual void fini() {} virtual ~per_output_plugin_instance_t() = default; }; /** * A mixin class which can be used to setup per-output-instance tracking. */ template class per_output_tracker_mixin_t { public: virtual ~per_output_tracker_mixin_t() = default; void init_output_tracking() { auto& ol = wf::get_core().output_layout; ol->connect(&on_output_added); ol->connect(&on_output_removed); for (auto wo : ol->get_outputs()) { handle_new_output(wo); } } void fini_output_tracking() { on_output_added.disconnect(); on_output_removed.disconnect(); for (auto& [output, inst] : output_instance) { inst->fini(); } output_instance.clear(); } protected: std::map> output_instance; wf::signal::connection_t on_output_added = [=] (output_added_signal *ev) { handle_new_output(ev->output); }; wf::signal::connection_t on_output_removed = [=] (output_pre_remove_signal *ev) { handle_output_removed(ev->output); }; virtual void handle_new_output(wf::output_t *output) { auto inst = std::make_unique(); inst->output = output; auto ptr = inst.get(); output_instance[output] = std::move(inst); ptr->init(); } virtual void handle_output_removed(wf::output_t *output) { output_instance[output]->fini(); output_instance.erase(output); } }; template class per_output_plugin_t : public wf::plugin_interface_t, public per_output_tracker_mixin_t { public: void init() override { this->init_output_tracking(); } void fini() override { this->fini_output_tracking(); } }; } wayfire-0.8.1/src/api/wayfire/plugin.hpp000066400000000000000000000103141457431457600202220ustar00rootroot00000000000000#ifndef PLUGIN_H #define PLUGIN_H #include #include #include "wayfire/util.hpp" #include "wayfire/bindings.hpp" #include class wayfire_config; namespace wf { /** * Plugins can set their capabilities to indicate what kind of plugin they are. * At any point, only one plugin with a given capability can be active on its * output (although multiple plugins with the same capability can be loaded). */ enum plugin_capabilities_t { /** The plugin grabs input. * Required in order to use plugin_grab_interface_t::grab() */ CAPABILITY_GRAB_INPUT = 1 << 0, /** The plugin uses custom renderer */ CAPABILITY_CUSTOM_RENDERER = 1 << 1, /** The plugin manages the whole desktop, for ex. switches workspaces. */ CAPABILITY_MANAGE_DESKTOP = 1 << 2, /* Compound capabilities */ /** The plugin manages the whole compositor state */ CAPABILITY_MANAGE_COMPOSITOR = CAPABILITY_GRAB_INPUT | CAPABILITY_MANAGE_DESKTOP | CAPABILITY_CUSTOM_RENDERER, }; /** * Plugins use the plugin activation data to indicate that they are active on a particular output. * The information is used to avoid conflicts between plugins with the same capabilities. */ struct plugin_activation_data_t { // The name of the plugin. Used mostly for debugging purposes. std::string name = ""; // The plugin capabilities. A bitmask of the values specified above uint32_t capabilities = 0; /** * Each plugin might be deactivated forcefully, for example when the desktop is locked. Plugins should * honor this signal and exit their grabs/renderers immediately. Note: this is sent only to active * plugins. */ std::function cancel = [] () {}; }; class plugin_interface_t { public: /** * The init method is the entry of the plugin. In the init() method, the * plugin should register all bindings it provides, connect to signals, etc. */ virtual void init() = 0; /** * The fini method is called when a plugin is unloaded. It should clean up * all global state it has set (for ex. signal callbacks, bindings, ...), * because the plugin will be freed after this. */ virtual void fini(); /** * A plugin can request that it is never unloaded, even if it is removed * from the config's plugin list. * * Note that unloading a plugin is sometimes unavoidable, for ex. when the * output the plugin is running on is destroyed. So non-unloadable plugins * should still provide proper fini() methods. */ virtual bool is_unloadable() { return true; } /** * When Wayfire starts, plugins are first sorted according to their order_hint before being initialized. * * The initialization order can be important for plugins which provide basic services like IPC and should * therefore be loaded and initialized first. * * The lower the order_hint, the earlier the plugin will be loaded. * Plugins with equal order hints will be loaded according to the order in the `core/plugins` option. */ virtual int get_order_hint() const { return 0; } virtual ~plugin_interface_t() = default; }; } /** * Each plugin must provide a function which instantiates the plugin's class * and returns the instance. * * This function must have the name newInstance() and should be declared with * extern "C" so that the loader can find it. */ using wayfire_plugin_load_func = wf::plugin_interface_t * (*)(); /** The version of Wayfire's API/ABI */ constexpr uint32_t WAYFIRE_API_ABI_VERSION = 2024'03'11; /** * Each plugin must also provide a function which returns the Wayfire API/ABI * that it was compiled with. * * This function must have the name getWayfireVersion() and should be declared * with extern "C" so that the loader can find it. */ using wayfire_plugin_version_func = uint32_t (*)(); /** * A macro to declare the necessary functions, given the plugin class name */ #define DECLARE_WAYFIRE_PLUGIN(PluginClass) \ extern "C" \ { \ wf::plugin_interface_t*newInstance() { return new PluginClass; } \ uint32_t getWayfireVersion() { return WAYFIRE_API_ABI_VERSION; } \ } #endif wayfire-0.8.1/src/api/wayfire/region.hpp000066400000000000000000000044051457431457600202130ustar00rootroot00000000000000#pragma once #include #include "wayfire/geometry.hpp" /* ---------------------- pixman utility functions -------------------------- */ namespace wf { struct region_t { region_t(); /* Makes a copy of the given region */ region_t(pixman_region32_t *damage); region_t(const wlr_box& box); ~region_t(); region_t(const region_t& other); region_t(region_t&& other); region_t& operator =(const region_t& other); region_t& operator =(region_t&& other); bool empty() const; void clear(); void expand_edges(int amount); pixman_box32_t get_extents() const; bool contains_point(const point_t& point) const; bool contains_pointf(const pointf_t& point) const; /* Translate the region */ region_t operator +(const point_t& vector) const; region_t& operator +=(const point_t& vector); region_t operator -(const point_t& vector) const; region_t& operator -=(const point_t& vector); region_t operator *(float scale) const; region_t& operator *=(float scale); /* Region intersection */ region_t operator &(const wlr_box& box) const; region_t operator &(const region_t& other) const; region_t& operator &=(const wlr_box& box); region_t& operator &=(const region_t& other); /* Region union */ region_t operator |(const wlr_box& other) const; region_t operator |(const region_t& other) const; region_t& operator |=(const wlr_box& other); region_t& operator |=(const region_t& other); /* Subtract the box/region from the current region */ region_t operator ^(const wlr_box& box) const; region_t operator ^(const region_t& other) const; region_t& operator ^=(const wlr_box& box); region_t& operator ^=(const region_t& other); pixman_region32_t *to_pixman(); const pixman_box32_t *begin() const; const pixman_box32_t *end() const; private: pixman_region32_t _region; /* Returns a const-casted pixman_region32_t*, useful in const operators * where we use this->_region as only source for calculations, but pixman * won't let us pass a const pixman_region32_t* */ pixman_region32_t *unconst() const; }; } wlr_box wlr_box_from_pixman_box(const pixman_box32_t& box); pixman_box32_t pixman_box_from_wlr_box(const wlr_box& box); wayfire-0.8.1/src/api/wayfire/render-manager.hpp000066400000000000000000000135061457431457600216210ustar00rootroot00000000000000#pragma once #include #include #include namespace wf { /* Effect hooks provide the plugins with a way to execute custom code * at certain parts of the repaint cycle */ using effect_hook_t = std::function; enum output_effect_type_t { /* Pre hooks are called before starting to repaint the output */ OUTPUT_EFFECT_PRE = 0, /** * Damage hooks are called before attaching the renderer to the output. * They are useful if the output damage needs to be modified, whereas * plugins that simply need to update their animation should use PRE hooks. */ OUTPUT_EFFECT_DAMAGE = 1, /* Overlay hooks are called right after repainting the output, but * before post hooks and before swapping buffers */ OUTPUT_EFFECT_OVERLAY = 2, /* Post hooks are called after the buffers have been swapped */ OUTPUT_EFFECT_POST = 3, /* Invalid type for a hook, used internally */ OUTPUT_EFFECT_TOTAL = 4, }; /** Post hooks are called just before swapping buffers. In contrast to * render hooks, post hooks operate on the whole output image, i.e they * are suitable for different postprocessing effects. * * When using post hooks, the output first gets rendered to a framebuffer, * which can then pass through multiple post hooks. The last hook then will * draw to the output's framebuffer. * * @param source Indicates the source buffer of the hook, which contains * the output image up to this moment. * * @param destination Indicates where the processed image should be stored. */ using post_hook_t = std::function; /** * The frame-done signal is emitted on an output when the frame has been completed (regardless of whether new * content was painted or not). */ struct frame_done_signal {}; /** Render manager * * Each output has a render manager, which is responsible for all rendering * operations that happen on it, and also for damage tracking. */ class render_manager { public: /** Create a render manager for the given output. Plugins do not need * to manually create render managers, as one is created for each output * automatically */ render_manager(output_t *o); ~render_manager(); /** * Rendering an output is done on demand, that is, when the output is * damaged. Some plugins however need to redraw the output as often as * possible, for ex. when displaying some kind of animation. * * auto_redraw() provides the plugins to temporarily request redrawing * of the output regardless of damage. * * @param always - Whether to always redraw, regardless of damage. Call * set_redraw_always(false) once for each set_redraw_always(true). */ void set_redraw_always(bool always = true); /** * Schedule a frame for the output. Note that if there is no damage for * the next frame, nothing will be redrawn */ void schedule_redraw(); /** * Inhibit rendering to the output. An inhibited output will show a * fully black image. Used mainly for compositor fade in/out on startup. */ void add_inhibit(bool add); /** * Add a new effect hook. * @param hook The hook callback * @param type The type of the effect hook */ void add_effect(effect_hook_t *hook, output_effect_type_t type); /** * Remove an added effect hook. No-op if the hook wasn't really added. * @param hook The hook callback to be removed */ void rem_effect(effect_hook_t *hook); /** * Add a new post hook. * * @param hook The hook callback */ void add_post(post_hook_t *hook); /** * Remove a post hook. No-op if hook isn't active. * * @param hook The hook to be removed. */ void rem_post(post_hook_t *hook); /** * @return The damaged region on the current output for the current * frame that is used when swapping buffers. This function should * only be called from overlay or postprocessing effect callbacks. * Otherwise it will return an empty region. */ wf::region_t get_swap_damage(); /** * @return The damaged region on the current output for the current * frame. Note that a larger region might actually be repainted due to * double buffering. */ wf::region_t get_scheduled_damage(); /** * Damage all workspaces of the output. Should not be used inside render * hooks, view transformers, etc. */ void damage_whole(); /** * Same as damage_whole() but the output will actually be damaged on the * next time the event loop goes idle. This is safe to use inside render * hooks, transformers, etc. */ void damage_whole_idle(); /** * Same as damage_whole(), but damages only a part of the output. * * @param box The output box to be damaged, in output-local coordinates. * @param repaint Whether to automatically schedule an output repaint. */ void damage(const wlr_box& box, bool repaint = true); /** * Same as damage_whole(), but damages only a part of the output. * * @param region The output region to be damaged, in output-local coordinates. * @param repaint Whether to automatically schedule an output repaint. */ void damage(const wf::region_t& region, bool repaint = true); /** * @return A box in output-local coordinates containing the given * workspace of the output (returned value depends on current workspace). */ wlr_box get_ws_box(wf::point_t ws) const; /** * @return The framebuffer on which all rendering operations except post * effects happen. */ wf::render_target_t get_target_framebuffer() const; private: class impl; std::unique_ptr pimpl; }; } wayfire-0.8.1/src/api/wayfire/scene-input.hpp000066400000000000000000000145371457431457600211710ustar00rootroot00000000000000#pragma once #include #include #include #include #include namespace wf { class seat_t; namespace scene { class node_t; using node_ptr = std::shared_ptr; } /** * When refocusing on a particular output, there may be multiple nodes * which can receive keyboard focus. While usually the most recently focused * node is chosen, there are cases where this is not the desired behavior, for ex. * nodes which have keyboard grabs. In order to accommodate for these cases, * the focus_importance enum provides a way for nodes to indicate in what cases * they should receive keyboard focus. */ enum class focus_importance { // No focus at all. NONE = 0, // Node may accept focus, but further nodes should override it if sensible. LOW = 1, // Regularly focused node (typically regular views). REGULAR = 2, // Highest priority. Nodes which request focus like this usually do not // get their requests overridden. HIGH = 3, }; struct keyboard_focus_node_t { scene::node_t *node = nullptr; focus_importance importance = focus_importance::NONE; // Whether nodes below this node are allowed to get focus, no matter their // focus importance. bool allow_focus_below = true; /** * True iff: * 1. The other node has a higher focus importance * 2. Or, the other node has the same importance but a newer * last_focus_timestamp. */ bool operator <(const keyboard_focus_node_t& other) const; }; /** * An interface for scene nodes which interact with the keyboard. * * Note that by default, nodes do not receive keyboard input. Nodes which wish * to do so need to have node_flags::ACTIVE_KEYBOARD set. */ class keyboard_interaction_t { public: /** * Handle a keyboard enter event. * This means that the node is now focused. */ virtual void handle_keyboard_enter(wf::seat_t *seat) {} /** * Handle a keyboard leave event. * The node is no longer focused. */ virtual void handle_keyboard_leave(wf::seat_t *seat) {} /** * Handle a keyboard key event. * * These are received only after the node has received keyboard focus and * before it loses it. * * @return What should happen with the further processing of the event. */ virtual void handle_keyboard_key(wf::seat_t *seat, wlr_keyboard_key_event event) {} keyboard_interaction_t() = default; virtual ~keyboard_interaction_t() {} /** * The last time(nanoseconds since epoch) when the node was focused. * Updated automatically by core. */ uint64_t last_focus_timestamp = 0; }; /** * An interface for scene nodes which interact with pointer input. * * As opposed to keyboard input, all nodes are eligible for receiving pointer * and input. As a result, every node may receive motion, button, etc. events. * Nodes which do not wish to process events may simply not accept input at * any point (as the default accepts_input implementation does). */ class pointer_interaction_t { protected: pointer_interaction_t(const pointer_interaction_t&) = delete; pointer_interaction_t(pointer_interaction_t&&) = delete; pointer_interaction_t& operator =(const pointer_interaction_t&) = delete; pointer_interaction_t& operator =(pointer_interaction_t&&) = delete; public: pointer_interaction_t() = default; virtual ~pointer_interaction_t() = default; /** * The pointer entered the node and thus the node gains pointer focus. */ virtual void handle_pointer_enter(wf::pointf_t position) {} /** * Notify a node that it no longer has pointer focus. * This event is always sent after a corresponding pointer_enter event. */ virtual void handle_pointer_leave() {} /** * Handle a button press or release event. * * When a node consumes a button event, core starts an *implicit grab* for it. This has the effect that * all subsequent input events are forwarded to that node, until all buttons are released. Thus, a node is * guaranteed to always receive matching press and release events, except when it explicitly opts out via * the RAW_INPUT node flag. * * @param pointer_position The position where the pointer is currently at. * @param button The wlr event describing the event. */ virtual void handle_pointer_button( const wlr_pointer_button_event& event) {} /** * The user moved the pointer. * * @param pointer_position The new position of the pointer. * @param time_ms The time reported by the device when the event happened. */ virtual void handle_pointer_motion(wf::pointf_t pointer_position, uint32_t time_ms) {} /** * The user scrolled. * * @param pointer_position The position where the pointer is currently at. * @param event A structure describing the event. */ virtual void handle_pointer_axis(const wlr_pointer_axis_event& event) {} }; /** * An interface for scene nodes which interact with touch input. */ class touch_interaction_t { public: touch_interaction_t() = default; virtual ~touch_interaction_t() = default; /** * The user pressed down with a finger on the node. * * @param finger_id The id of the finger pressed down (first is 0, then 1, * 2, ..). Note that it is possible that the finger 0 is pressed down on * another node, then the current node may start receiving touch down * events beginning with finger 1, 2, ... * * @param position The coordinates of the finger. */ virtual void handle_touch_down(uint32_t time_ms, int finger_id, wf::pointf_t position) {} /** * The user lifted their finger off the node. * * @param finger_id The id of the finger being lifted. It is guaranteed that * the finger will have been pressed on the node before. * @param lift_off_position The last position the finger had before the * lift off. */ virtual void handle_touch_up(uint32_t time_ms, int finger_id, wf::pointf_t lift_off_position) {} /** * The user moved their finger without lifting it off. */ virtual void handle_touch_motion(uint32_t time_ms, int finger_id, wf::pointf_t position) {} }; } wayfire-0.8.1/src/api/wayfire/scene-operations.hpp000066400000000000000000000042531457431457600222070ustar00rootroot00000000000000#pragma once #include "wayfire/scene-input.hpp" #include #include // This header contains implementations of simple scenegraph related functionality // used in many places throughout the codebase. namespace wf { namespace scene { /** * Remove a child node from a parent node and update the parent. */ inline void remove_child(node_ptr child, uint32_t add_flags = 0) { if (!child->parent()) { return; } auto parent = dynamic_cast(child->parent()); wf::dassert(parent, "Removing a child from a non-floating container!"); auto children = parent->get_children(); children.erase(std::remove(children.begin(), children.end(), child), children.end()); parent->set_children_list(children); update(parent->shared_from_this(), update_flag::CHILDREN_LIST | add_flags); } inline void add_front(floating_inner_ptr parent, node_ptr child) { auto children = parent->get_children(); children.insert(children.begin(), child); parent->set_children_list(children); update(parent, update_flag::CHILDREN_LIST); } inline void readd_front(floating_inner_ptr parent, node_ptr child) { remove_child(child); add_front(parent, child); } inline void add_back(floating_inner_ptr parent, node_ptr child) { auto children = parent->get_children(); children.push_back(child); parent->set_children_list(children); update(parent, update_flag::CHILDREN_LIST); } inline void readd_back(floating_inner_ptr parent, node_ptr child) { remove_child(child); add_back(parent, child); } inline bool raise_to_front(node_ptr child) { auto dyn_parent = dynamic_cast(child->parent()); wf::dassert(dyn_parent, "Raise to front in a non-floating container!"); auto children = dyn_parent->get_children(); if (children.front() == child) { return false; } children.erase(std::remove(children.begin(), children.end(), child), children.end()); children.insert(children.begin(), child); dyn_parent->set_children_list(children); update(dyn_parent->shared_from_this(), update_flag::CHILDREN_LIST); return true; } } } wayfire-0.8.1/src/api/wayfire/scene-render.hpp000066400000000000000000000237001457431457600213010ustar00rootroot00000000000000#pragma once #include "wayfire/nonstd/observer_ptr.h" #include #include #include #include #include #include #include #include namespace wf { class output_t; namespace scene { class node_t; using node_ptr = std::shared_ptr; class render_instance_t; /** * Describes the result of trying to do direct scanout of a render instance on * an output. */ enum class direct_scanout { /** * The node cannot be directly scanned out on the output, but does not occlude * any node below it which may be scanned out directly. */ SKIP, /** * The node cannot be directly scanned out on the output, but covers a part * of the output, thus makes direct scanout impossible. */ OCCLUSION, /** * The node was successfully scanned out. */ SUCCESS, }; /** * A single rendering call in a render pass. */ struct render_instruction_t { render_instance_t *instance = NULL; wf::render_target_t target; wf::region_t damage; std::any data = {}; }; /** * When (parts) of the scenegraph have to be rendered, they have to be * 'instantiated' first. The instantiation of a (sub)tree of the scenegraph * is a tree of render instances, called a render tree. The purpose of the * render trees is to enable damage tracking (each render instance has its own * damage), while allowing arbitrary transformations in the scenegraph (e.g. a * render instance does not need to export information about how it transforms * its children). Due to this design, render trees have to be regenerated every * time the relevant portion of the scenegraph changes. * * Actually painting a render tree (called render pass) is a process involving * three steps: * * 1. Calculate the damage accumulated from the render tree. * 2. A front-to-back iteration through the render tree, so that every node * calculates the parts of the destination buffer it should actually repaint. * 3. A final back-to-front iteration where the actual rendering happens. */ class render_instance_t { public: virtual ~render_instance_t() = default; /** * Handle the front-to-back iteration (2.) from a render pass. * Each instance should add the render instructions (calls to * render_instance_t::render()) for itself and its children. * * @param instructions A list of render instructions to be executed. * Instructions are evaluated in the reverse order they are pushed * (e.g. from instructions.rbegin() to instructions.rend()). * @param damage The damaged region of the node, in node-local coordinates. * Nodes may subtract from the damage, to prevent rendering below opaque * regions, or expand it for certain special effects like blur. * @param fb The target framebuffer to render the node and its children. * Note that some nodes may cause their children to be rendered to * auxiliary buffers. */ virtual void schedule_instructions( std::vector& instructions, const wf::render_target_t& target, wf::region_t& damage) = 0; /** * Render the node on the given render target and the given damage region. * The node should not paint outside of @region. * All coordinates are to be given in the node's parent coordinate system. * * Note: render() should not be called outside of a render pass. * * @param target The render target to render the node to, as calculated in * @schedule_instructions. * @param region The region to repaint, as calculated in * @schedule_instructions. */ virtual void render(const wf::render_target_t& target, const wf::region_t& region) {} /** * Render instances may also pass custom data to their render callbacks. * However, since few of them do this, it is enough to override the version * without custom data. */ virtual void render(const wf::render_target_t& target, const wf::region_t& region, const std::any& custom_data) { render(target, region); } /** * Notify the render instance that it has been presented on an output. * Note that a render instance may get multiple presentation_feedback calls * for the same rendered frame. */ virtual void presentation_feedback(wf::output_t *output) {} /** * Attempt direct scanout on the given output. * * Direct scanout is an optimization where a buffer from a node is directly * attached as the front buffer of an output. This is possible in a single * case, namely when the topmost node with visible contents on an output * covers it perfectly. * * @return The result of the attempt, see @direct_scanout. */ virtual direct_scanout try_scanout(wf::output_t *output) { // By default, we report an occlusion, e.g. scanout is not possible, // neither for this node, nor for nodes below. return direct_scanout::OCCLUSION; } /** * Compute the render instance's visible region on the given output. * * The visible region can be used for things like determining when to send frame done events to * wlr_surfaces and to ignore damage to invisible parts of a render instance. */ virtual void compute_visibility(wf::output_t *output, wf::region_t& visible) {} }; using render_instance_uptr = std::unique_ptr; using damage_callback = std::function; /** * A signal emitted when a part of the node is damaged. * on: the node itself. */ struct node_damage_signal { wf::region_t region; }; /** * A helper function to emit the damage signal on a node. */ template inline void damage_node(NodePtr node, wf::region_t damage) { node_damage_signal data; data.region = damage; node->emit(&data); } /** * Signal that a render pass starts. * emitted on: core. */ struct render_pass_begin_signal { render_pass_begin_signal(wf::region_t& damage, wf::render_target_t target) : damage(damage), target(target) {} /** * The initial damage for this render pass. * Plugins may expand it further. */ wf::region_t& damage; /** * The target buffer for rendering. */ wf::render_target_t target; }; /** * Signal that is emitted once a render pass ends. * emitted on: core. */ struct render_pass_end_signal { wf::render_target_t target; }; enum render_pass_flags { /** * Do not emit render-pass-{begin, end} signals. */ RPASS_EMIT_SIGNALS = (1 << 0), /** * Do not clear the background areas. */ RPASS_CLEAR_BACKGROUND = (1 << 1), }; /** * A struct containing the information necessary to execute a render pass. */ struct render_pass_params_t { /** The instances which are to be rendered in this render pass. */ std::vector *instances; /** The rendering target. */ render_target_t target; /** The total damage accumulated from the instances since the last repaint. */ region_t damage; /** * The background color visible below all instances, if * RPASS_CLEAR_BACKGROUND is specified. */ color_t background_color; /** * The output the instances were rendered, used for sending presentation * feedback. */ output_t *reference_output = nullptr; }; /** * A helper function to execute a render pass. * * The render pass goes as described below: * * 1. Emit render-pass-begin. * 2. Render instructions are generated from the given instances. * 3. Any remaining background areas are painted in @background_color. * 4. Render instructions are executed back-to-forth. * 5. Emit render-pass-end. * * By specifying @flags, steps 1, 3, and 5 can be disabled. * * @return The full damage which was rendered on the screen. It may be more (or * less) than @accumulated_damage because plugins are allowed to modify the * damage in render-pass-begin. */ wf::region_t run_render_pass( const render_pass_params_t& params, uint32_t flags); /** * A helper function for direct scanout implementations. * It tries to forward the direct scanout request to the first render instance * in the given list, and returns the first non-SKIP result, or SKIP, if no * instance interacts with direct scanout. */ direct_scanout try_scanout_from_list( const std::vector& instances, wf::output_t *scanout); /** * A helper function for compute_visibility implementations. It applies an offset to the damage and reverts it * afterwards. It also calls compute_visibility for the children instances. */ void compute_visibility_from_list(const std::vector& instances, wf::output_t *output, wf::region_t& region, const wf::point_t& offset); /** * A helper class for easier implementation of render instances. * It automatically schedules instruction for the current node and tracks damage from the main node. */ template class simple_render_instance_t : public render_instance_t { public: simple_render_instance_t(Node *self, damage_callback push_damage, wf::output_t *output) { this->self = self; this->push_damage = push_damage; this->output = output; self->connect(&on_self_damage); } void schedule_instructions(std::vector& instructions, const wf::render_target_t& target, wf::region_t& damage) override { instructions.push_back(render_instruction_t{ .instance = this, .target = target, .damage = damage & self->get_bounding_box(), }); } protected: Node *self; wf::signal::connection_t on_self_damage = [=] (scene::node_damage_signal *ev) { push_damage(ev->region); }; damage_callback push_damage; wf::output_t *output; }; } } wayfire-0.8.1/src/api/wayfire/scene.hpp000066400000000000000000000412231457431457600200240ustar00rootroot00000000000000#pragma once #include "wayfire/opengl.hpp" #include #include #include #include #include #include #include #include #include #include namespace wf { class output_t; class output_layout_t; /** * Contains definitions of the common elements used in Wayfire's scenegraph. * * The scenegraph is a complete representation of the current rendering and input * state of Wayfire. The basic nodes forms a tree where every node is responsible * for managing its children's state. * * The rough structure of the scenegraph is as follows: * * Level 1: the root node, which is a simple container of other nodes. * Level 2: a list of layer nodes, which represent different types of content, * ordered in increasing stacking order (i.e. first layer is the bottommost). * Level 3: in each layer, there is a special output node for each currently * enabled output. By default, this node's bounding box is limited to the * extents of the output, so that no nodes overlap multiple outputs. * Level 4 and beyond: These levels typically contain views and group of views, * or special effects (particle systems and the like). * * Each level may contain additional nodes added by plugins (or by core in the * case of DnD views). The scenegraph generally allows full flexibility here, * but the aforementioned nodes are always available and used by most plugins * to ensure the most compatibility. * * The most common operations that a plugin needs to execute on the scenegraph * are reordering elements (and thus changing the stack order) and potentially * moving them between layers and outputs. In addition, the scenegraph can be * used in some more advanced cases: * * - The scenegraph may be used to implement what used to be custom renderers * prior to Wayfire 0.8.0, i.e. override the default output of a single * workspace covering the whole output. The preferred way to do that is to * disable the output nodes in each layer and add a custom node in one of the * layers which does the custom rendering and covers the whole output. * * - A similar 'trick' can be used for grabbing all input on a particular output * and is the preferred way to do what input grabs used to do prior to Wayfire * 0.8.0. To emulate a grab, create an input-only scene node and place it above * every other node. Thus it will always be selected for input on the output it * is visible on. * * - Always-on-top views are simply nodes which are placed above the workspace * set of each output. * * Regarding coordinate systems: each node possesses a coordinate system. Some * nodes (for example, nodes which simply group other nodes together) share the * coordinate system of their parent node. Other nodes (for example transformers) * are responsible for converting between the coordinate system of their children * and the coordinate system of their parent. */ namespace scene { class node_t; using node_ptr = std::shared_ptr; using node_weak_ptr = std::weak_ptr; /** * Describes the current state of a node. */ enum class node_flags : int { /** * If set, the node should be ignored by visitors and any plugins iterating * over the scenegraph. Such nodes (and their children) do not wish to receive * pointer, keyboard, etc. events and do not wish to be displayed. * * Note that plugins might still force those nodes to receive input and be * rendered by calling the corresponding methods directly. */ DISABLED = (1 << 0), /** * If set, the node indicates that it wishes to receive raw input events, that is, it may receive * unmatched pointer press/release events, unmatched touch up/down events, etc. */ RAW_INPUT = (1 << 1), }; using node_flags_bitmask_t = uint64_t; /** * Used as a result of an intersection of the scenegraph with the user input. */ struct input_node_t { nonstd::observer_ptr node; // The coordinates of the user input in surface-local coordinates. wf::pointf_t local_coords; }; /** * The base class for all nodes in the scenegraph. */ class node_t : public std::enable_shared_from_this, public wf::signal::provider_t { public: /** * Create a new no-op node. * Plugins very rarely need this, instead, subclasses of node_t should be * instantiated. */ node_t(bool is_structure); virtual ~node_t(); /** * Find the input node at the given position. * By default, the node will try to pass input to its children. * * @param at The point at which the query is made. It is always in the node's * coordinate system (e.g. resulting from the parent's to_local() function). */ virtual std::optional find_node_at(const wf::pointf_t& at); /** * Figure out which node should receive keyboard focus on the given output. * * Typically, the focus is set directly via core::set_active_node(). However, * in some cases we need to re-elect a node to focus (for example if the * focused node is destroyed). In these cases, the keyboard_refocus() method * on a node is called. It should return the desired focus node. * * By default, a node tries to focus one of its focusable children with the * highest focus_importance. In case of a tie, the node with the highest * last_focus_timestamp is selected. */ virtual wf::keyboard_focus_node_t keyboard_refocus(wf::output_t *output); /** * Convert a point from the coordinate system the node resides in, to the * coordinate system of its children. * * By default, the node's children share the coordinate system of their parent, * that is, `to_local(x) == x`. */ virtual wf::pointf_t to_local(const wf::pointf_t& point); /** * Convert a point from the coordinate system of the node's children to * the coordinate system the node resides in. Typically, this is the inverse * operation of to_local, e.g. `to_global(to_local(x)) == x`. * * By default, the node's children share the coordinate system of their parent, * that is, `to_global(x) == x`. */ virtual wf::pointf_t to_global(const wf::pointf_t& point); /** * Get a textual representation of the node, used for debugging purposes. * For example, see wf::dump_scene(). * The representation should therefore not contain any newline characters. */ virtual std::string stringify() const; /** * Get the current flags of the node. */ virtual node_flags_bitmask_t flags() const { return enabled_counter > 0 ? 0 : (int)node_flags::DISABLED; } /** * Get the keyboard interaction interface of this node. * By default, a no-op. */ virtual keyboard_interaction_t& keyboard_interaction() { static keyboard_interaction_t noop; return noop; } virtual pointer_interaction_t& pointer_interaction() { static pointer_interaction_t noop; return noop; } virtual touch_interaction_t& touch_interaction() { static touch_interaction_t noop; return noop; } /** * Generate render instances for this node and its children. * See the @render_instance_t interface for more details. * * The default implementation just generates render instances from its * children. * * @param instances A vector of render instances to add to. The instances * are sorted from the foremost (or topmost) to the last (bottom-most). * @param push_damage A callback used to report damage on the new render * instance. * @param output An optional parameter describing which output the render * instances will be shown on. It can be used to avoid generating instances * on outputs where the node should not be shown. However, this should be * conservatively approximated - it is fine to generate more render * instances than necessary, but not less. */ virtual void gen_render_instances( std::vector& instances, damage_callback push_damage, wf::output_t *output = nullptr); /** * Get a bounding box of the node in the node's parent coordinate system. * * The bounding box is a rectangular region in which the node and its * children are fully contained. * * The default implementation ignores the node itself and simply returns * the same result as @get_children_bounding_box. Nodes may override this * function if they want to apply a transform to their children, or if the * nodes themselves have visible elements that should be included in the * bounding box. */ virtual wf::geometry_t get_bounding_box(); /** * Get the bounding box of the node's children, in the coordinate system of * the node. * * In contrast to @get_bounding_box, this does not include the node itself, * and does not apply any transformations which may be implemented by the * node. It is simply the bounding box of the bounding boxes of the children * as reported by their get_bounding_box() method. */ wf::geometry_t get_children_bounding_box(); /** * Structure nodes are special nodes which core usually creates when Wayfire * is started (e.g. layer and output nodes). These nodes should not be * reordered or removed from the scenegraph. */ bool is_structure_node() const { return _is_structure; } /** * Get the parent of the current node in the scene graph. */ node_t *parent() const { return _parent; } /** * A helper function to get the status of the DISABLED flag. */ inline bool is_enabled() const { return !(flags() & (int)node_flags::DISABLED); } /** * A helper function to get the status of the RAW_INPUT flag. */ inline bool wants_raw_input() const { return (flags() & (int)node_flags::RAW_INPUT); } /** * Increase or decrease the enabled counter. A non-positive counter causes * the DISABLED flag to be set. * * By default, a node is created with an enabled counter equal to 1. */ void set_enabled(bool is_enabled); /** * Obtain an immutable list of the node's children. * Use set_children_list() of floating_inner_node_t to modify the children, * if the node supports that. */ const std::vector& get_children() const { return children; } public: node_t(const node_t&) = delete; node_t(node_t&&) = delete; node_t& operator =(const node_t&) = delete; node_t& operator =(node_t&&) = delete; protected: bool _is_structure; int enabled_counter = 1; node_t *_parent = nullptr; friend class surface_root_node_t; friend class floating_inner_node_t; // A helper functions for stringify() implementations, serializes the flags() // to a string, e.g. node with KEYBOARD and USER_INPUT -> '(ku)' std::string stringify_flags() const; /** * A list of children nodes sorted from top to bottom. * * Note on special `structure` nodes: These nodes are typically present in * the normal list of children, but also accessible via a specialized pointer * in their parent's class. */ std::vector> children; void set_children_unchecked(std::vector new_list); }; /** * Inner nodes where plugins can add their own nodes and whose children can be * reordered freely. However, special care needs to be taken to avoid reordering * the special `structure` nodes. */ class floating_inner_node_t : public node_t { public: using node_t::node_t; ~floating_inner_node_t(); /** * Exchange the list of children of this node. * A typical usage (for example, bringing a node to the top): * 1. list = get_children() * 2. list.erase(target_node) * 3. list.insert(list.begin(), target_node) * 4. set_children_list(list) * * The set_children_list function also performs checks on the structure * nodes present in the inner node. If they were changed, the change is * rejected and false is returned. In all other cases, the list of * children is updated, and each child's parent is set to this node. */ bool set_children_list(std::vector new_list); }; using floating_inner_ptr = std::shared_ptr; /** * A Level 3 node which represents each output in each layer. * * Each output's children reside in a coordinate system offsetted by the output's * position in the output layout, e.g. each output has a position 0,0 in its * coordinate system. */ class output_node_t : public floating_inner_node_t { public: output_node_t(wf::output_t *output); std::string stringify() const override; wf::pointf_t to_local(const wf::pointf_t& point) override; wf::pointf_t to_global(const wf::pointf_t& point) override; /** * The output's render instance simply adjusts damage, rendering, etc. to * account for the output's position in the output layout. */ void gen_render_instances( std::vector& instances, damage_callback push_damage, wf::output_t *output) override; wf::geometry_t get_bounding_box() override; std::optional find_node_at(const wf::pointf_t& at) override; /** * Get the output this node is responsible for. */ wf::output_t *get_output() const { return output; } /** * The limit region of an output. * It defines the region of the output layout that this output occupies. * The output will not render anything outside of its limit region, and will * not find any intersections via find_node_at. */ std::optional limit_region; private: wf::output_t *output; }; /** * A list of all layers in the root node. */ enum class layer : size_t { BACKGROUND = 0, BOTTOM = 1, WORKSPACE = 2, TOP = 3, UNMANAGED = 4, OVERLAY = 5, // For compatibility with workspace-manager, to be removed DWIDGET = 6, /** Not a real layer, but a placeholder for the number of layers. */ ALL_LAYERS, }; /** * A list of bitmask flags which indicate what parts of the node state have * changed. The information is useful when updating the scenegraph's state * with wf::scene::update(). */ namespace update_flag { enum update_flag { /** * The list of the node's children changed. */ CHILDREN_LIST = (1 << 0), /** * The node's enabled or disabled state changed. */ ENABLED = (1 << 1), /** * The node's input state changed, that is, the result of find_node_at() * may have changed. Typically, this is triggered when a surface is mapped, * unmapped or moved. */ INPUT_STATE = (1 << 2), /** * The node's geometry changed. Changes include not just the bounding box * of the view, but also things like opaque regions. */ GEOMETRY = (1 << 3), /** * A keyboard refocus might be necessary (for example, node removed, keyboard input state changed, etc.). */ REFOCUS = (1 << 4), }; } /** * A signal that the root node has been updated. * * on: scenegraph's root * when: Emitted when an update sequence finishes at the scenegraph's root. */ struct root_node_update_signal { uint32_t flags; }; /** * The root (Level 1) node of the whole scenegraph. */ class root_node_t final : public floating_inner_node_t { public: root_node_t(); virtual ~root_node_t(); std::string stringify() const override; /** * An ordered list of all layers' nodes. */ std::shared_ptr layers[(size_t)layer::ALL_LAYERS]; struct priv_t; std::unique_ptr priv; }; /** * Increase or decrease the node's enabled counter (node_t::set_enabled()) and * also trigger a scenegraph update if necessary. */ void set_node_enabled(wf::scene::node_ptr node, bool enabled); /** * Trigger an update of the scenegraph's state. * * When any state of the node changes, this function should be called with a * bitmask list of flags that indicates which parts of the node's state changed. * * After updating the concrete node's state, the change is propagated to parent * nodes all the way up to the scenegraph's root. * * @param changed_node The node whose state changed. * @param flags A bit mask consisting of flags defined in the @update_flag enum. */ void update(node_ptr changed_node, uint32_t flags); } } // namespace wf wayfire-0.8.1/src/api/wayfire/seat.hpp000066400000000000000000000064641457431457600176730ustar00rootroot00000000000000#pragma once #include #include #include #include #include namespace wf { /** * A seat represents a group of input devices (mouse, keyboard, etc.) which logically belong together. * Each seat has its own keyboard, touch, pointer and tablet focus. * Currently, Wayfire supports only a single seat. */ class seat_t { public: // A pointer to the wlroots seat wlr_seat *const seat; wlr_seat*const seat; /** * Get the xkb_state of the currently active keyboard. * Note: may be null if there is no keyboard connected to the seat. */ xkb_state *get_xkb_state(); /** * Get a list of all currently pressed keys. */ std::vector get_pressed_keys(); /** * Get a bitmask of the pressed modifiers on the current keyboard. * The returned value is a bitmask of WLR_MODIFIER_*. */ uint32_t get_keyboard_modifiers(); /** * Figure out whether the given keycode is a modifier on the current keyboard's keymap. * If yes, return the modifier as a WLR_MODIFIER_* bitmask, otherwise, return 0. */ uint32_t modifier_from_keycode(uint32_t keycode); /** * Try to focus the given scenegraph node. This may not work if another node requests a higher focus * importance. * * Note that the focus_view function should be used for view nodes, as focusing views typically involves * more operations. Calling this function does not change the active view, even if the newly focused node * is a view node! * * The new_focus' last focus timestamp will be updated. */ void set_active_node(wf::scene::node_ptr node); /** * Get the node which has current keyboard focus. */ wf::scene::node_ptr get_active_node(); /** * Try to focus the given view. This may not work if another view or a node requests a higher focus * importance. */ void focus_view(wayfire_view v); /** * Get the view which is currently marked as active. In general, this is the last view for which * @focus_view() was called, or for ex. when refocusing after a view disappears, the next view which * received focus. * * Usually, the active view has keyboard focus as well. In some cases (for ex. grabs), however, another * node might have the actual keyboard focus. */ wayfire_view get_active_view() const; /** * Get the last focus timestamp which was given out by a set_active_node request. */ uint64_t get_last_focus_timestamp() const; /** * Trigger a refocus operation. * See scene::node_t::keyboard_refocus() for details. */ void refocus(); /** * Focus the given output. The currently focused output is used to determine * which plugins receive various events (including bindings) */ void focus_output(wf::output_t *o); /** * Get the currently focused "active" output */ wf::output_t *get_active_output(); /** * Notify clients and plugins of input activity on the seat */ void notify_activity(); /** * Create and initialize a new seat. */ seat_t(wl_display *display, std::string name); ~seat_t(); struct impl; std::unique_ptr priv; }; } wayfire-0.8.1/src/api/wayfire/signal-definitions.hpp000066400000000000000000000464351457431457600225270ustar00rootroot00000000000000#ifndef SIGNAL_DEFINITIONS_HPP #define SIGNAL_DEFINITIONS_HPP #include "wayfire/view.hpp" #include "wayfire/output.hpp" /** * Documentation of signals emitted from core components. * Each signal documentation follows the following scheme: * * name: The base name of the signal * on: Which components the plugin is emitted on. Prefixes are specified * in (), i.e test(view-) means that the signal is emitted on component * test with prefix 'view-'. * when: Description of when the signal is emitted. * argument: What the signal data represents when there is no dedicated * signal data struct. */ namespace wf { /* ----------------------------------------------------------------------------/ * Core signals * -------------------------------------------------------------------------- */ /** * on: core * when: Emitted when the wlroots backend has been started. */ struct core_backend_started_signal {}; /** * on: core * when: Emitted when the Wayfire initialization has been completed and the main loop is about to start. */ struct core_startup_finished_signal {}; /** * on: core * when: Right before the shutdown sequence starts. */ struct core_shutdown_signal {}; class input_device_t; /** * on: core * when: Whenever a new input device is added. */ struct input_device_added_signal { nonstd::observer_ptr device; }; /** * on: core * when: Whenever an input device is removed. */ struct input_device_removed_signal { nonstd::observer_ptr device; }; /** * on: core * when: When the corresponding switch device state changes. */ struct switch_signal { /** The switch device */ nonstd::observer_ptr device; /** On or off */ bool state; }; /** * Describes the various ways in which core should handle an input event. */ enum class input_event_processing_mode_t { /** * Core should process this event for input grabs, bindings and eventually * forward it to a client surface. */ FULL, /** * Core should process this event for input grabs and bindings, but not send * the event to the client. */ NO_CLIENT, /** * Core should not process this event at all. */ IGNORE, }; /** * Emitted for the following events: * pointer_motion, pointer_motion_absolute, pointer_button, pointer_axis, * pointer_swipe_begin, pointer_swipe_update, pointer_swipe_end, pointer_pinch_begin, pointer_pinch_update, * pointer_pinch_end, pointer_hold_begin, pointer_hold_end, * keyboard_key, * touch_down, touch_up, touch_motion, * tablet_proximity, tablet_axis, tablet_button, tablet_tip * * on: core * when: The input event signals are sent from core whenever a new input from an * input device arrives. The events are sent before any processing is done, * and they are independent of plugin input grabs and other wayfire input * mechanisms. * * The event data can be modified by plugins, and then the modified event * will be used instead. However plugins which modify the event must ensure * that subsequent events are adjusted accordingly as well. * * example: The pointer_motion event is emitted with data of type * input_event_signal */ template struct input_event_signal { /* The event as it has arrived from wlroots */ wlr_event_t *event; /** * Describes how core should handle this event. * * This is currently supported for only a subset of signals, namely: * * pointer_button, keyboard_key, touch_down */ input_event_processing_mode_t mode = input_event_processing_mode_t::FULL; /** * The wlr device which triggered the event. May be NULL for events which are merged together for ex. via * wlr-cursor. */ wlr_input_device *device; }; /** * Same as @input_event_signal, but emitted after bindings have been handled and before the event is sent * to the client (if at all). * * Note: currently only keyboard_key events support this signal. * TODO: add the event for the rest of the event types. */ template struct pre_client_input_event_signal { wlr_event_t *event; wlr_input_device *device; /** Last opportunity for plugins to influence the processing of this event. */ bool carried_out = false; /** The node which will receive the event. May be NULL. */ wf::scene::node_ptr focus_node; }; /** * Same as @input_event_signal, but emitted after the event has been handled. */ template struct post_input_event_signal { wlr_event_t *event; wlr_input_device *device; }; /** * on: core * when: When the config file is reloaded */ struct reload_config_signal {}; /** * on: core * when: Keyboard focus is changed (may change to nullptr). */ struct keyboard_focus_changed_signal { wf::scene::node_ptr new_focus; }; /** * on: core * when: Seat activity has happened after being idle. */ struct seat_activity_signal {}; /** * on: core * when: idle inhibit changed. */ struct idle_inhibit_changed_signal { bool inhibit; }; /* ----------------------------------------------------------------------------/ * Output signals * -------------------------------------------------------------------------- */ /** Base class for all output signals. */ /** * on: output-layout * when: Each time a new output is added. */ struct output_added_signal { wf::output_t *output; }; /** * on: output, output-layout(output-) * when: Emitted just before starting the destruction procedure for an output. */ struct output_pre_remove_signal { wf::output_t *output; }; /** * on: output-layout * when: Each time a new output is added. */ struct output_removed_signal { wf::output_t *output; }; enum output_config_field_t { /** Output source changed */ OUTPUT_SOURCE_CHANGE = (1 << 0), /** Output mode changed */ OUTPUT_MODE_CHANGE = (1 << 1), /** Output scale changed */ OUTPUT_SCALE_CHANGE = (1 << 2), /** Output transform changed */ OUTPUT_TRANSFORM_CHANGE = (1 << 3), /** Output position changed */ OUTPUT_POSITION_CHANGE = (1 << 4), }; struct output_state_t; /** * on: output-layout * when: Each time the configuration of the output layout changes. */ struct output_layout_configuration_changed_signal {}; /** * on: output * when: Each time the output's source, mode, scale, transform and/or position changes. */ struct output_configuration_changed_signal { wf::output_t *output; output_configuration_changed_signal(const wf::output_state_t& st) : state(st) {} /** * Which output attributes actually changed. * A bitwise OR of output_config_field_t. */ uint32_t changed_fields; /** * The new state of the output. */ const wf::output_state_t& state; }; /** * on: output, core(output-) * when: Immediately after the output becomes focused. */ struct output_gain_focus_signal { wf::output_t *output; }; /** * on: output, core * when: When an output activates or deactivates. Note that not all plugin actions are reflected with this * signal. A plugin activates on an output usually if it (temporarily) changes the way Wayfire works like * Expo, Scale, Vswitch. One-shot actions like command or wsets do not send this signal. */ struct output_plugin_activated_changed_signal { // The output on which the plugin was activated. May be NULL if the plugin works globally. wf::output_t *output; // The name of the plugin. std::string plugin_name; // Whether the plugin was activated (true) or deactivated (false). bool activated; }; /* ----------------------------------------------------------------------------/ * Output rendering signals (see also wayfire/workspace-stream.hpp) * -------------------------------------------------------------------------- */ /** * on: output * when: Whenever the output is ready to start rendering. This can happen * either on output creation or whenever all inhibits in wayfire-shell have * been removed. */ struct output_start_rendering_signal { wf::output_t *output; }; /* ----------------------------------------------------------------------------/ * Output workspace signals * -------------------------------------------------------------------------- */ /** * on: output * when: Whenever the current workspace set of the output changes. */ struct workspace_set_changed_signal { std::shared_ptr new_wset; wf::output_t *output; }; /** * on: workspace set, output * when: Whenever the current workspace on the output has changed. */ struct workspace_changed_signal { /** Previously focused workspace */ wf::point_t old_viewport; /** Workspace that is to be focused or became focused */ wf::point_t new_viewport; /** The output this is happening on */ wf::output_t *output; }; /** * on: output * when: Whenever a workspace change is requested by core or by a plugin. * This can be used by plugins who wish to handle workspace changing * themselves, for ex. if animating the transition. */ struct workspace_change_request_signal { /** Previously focused workspace */ wf::point_t old_viewport; /** Workspace that is to be focused or became focused */ wf::point_t new_viewport; /** The output this is happening on */ wf::output_t *output; /** Whether the request has already been handled. */ bool carried_out; /** * A list of views whose geometry should remain stationary. * The caller is responsible for ensuring that this doesn't move the views * outside of the visible area. * * Note that the views might still be moved if a previous workspace change * request is being serviced. */ std::vector fixed_views; }; /** * on: workspace set * when: Whenever the workspace grid size changes. */ struct workspace_grid_changed_signal { /** The grid size before the change. */ wf::dimensions_t old_grid_size; /** The grid size after the change. */ wf::dimensions_t new_grid_size; }; /** * on: output * when: Whenever the available workarea changes. */ struct workarea_changed_signal { wf::output_t *output; wf::geometry_t old_workarea; wf::geometry_t new_workarea; }; /** * on: output * when: Whenever a fullscreen view is promoted on top of the other layers. */ struct fullscreen_layer_focused_signal { bool has_promoted; }; /* ----------------------------------------------------------------------------/ * View signals * -------------------------------------------------------------------------- */ /** * on: view, output, core * when: After the view becomes mapped. This signal must also be emitted from all compositor views. */ struct view_mapped_signal { wayfire_view view; /* Indicates whether the position already has its initial position */ bool is_positioned = false; }; /** * on: view, output, core * when: Immediately before unmapping a mapped view. The signal may not be * emitted from all views, but it is necessary for unmap animations to work. */ struct view_pre_unmap_signal { wayfire_view view; }; /** * name: unmapped * on: view, output, core * when: After a previously mapped view becomes unmapped. This must be emitted * for all views. */ struct view_unmapped_signal { wayfire_view view; }; /** * on: view, new output, core * when: Immediately after the view's output changes. Note that child views may still be on the old output. */ struct view_set_output_signal { wayfire_view view; // The previous output of the view. wf::output_t *output; }; /* ----------------------------------------------------------------------------/ * View state signals * -------------------------------------------------------------------------- */ /** * on: view * when: After the view's parent changes. */ struct view_parent_changed_signal { wayfire_toplevel_view view; }; /** * on: view, output(view-) * when: After the view's minimized state changes. */ struct view_minimized_signal { wayfire_toplevel_view view; }; /** * on: output * when: Emitted whenever some entity requests that the view's minimized state * changes. If no plugin is available to service the request, it is carried * out by core. See view_interface_t::minimize_request() */ struct view_minimize_request_signal { wayfire_toplevel_view view; /** true is minimized, false is restored */ bool state; /** * Whether some plugin will service the minimization request, in which * case other plugins and core should ignore the request. */ bool carried_out = false; }; /** * on: view * when: After the view's activated state changes. */ struct view_activated_state_signal { wayfire_toplevel_view view; }; /** * on: view, output(view-) * when: After the view's tiled edges change. */ struct view_tiled_signal { wayfire_toplevel_view view; /** Previously tiled edges */ uint32_t old_edges; /** Currently tiled edges */ uint32_t new_edges; }; /** * on: output * when: Emitted whenever some entity requests that the view's tiled edges * change. If no plugin is available to service the request, it is carried * out by core. See view_interface_t::tile_request() */ struct view_tile_request_signal { wayfire_toplevel_view view; /** The desired edges */ uint32_t edges; /** * The geometry the view should have. This is for example the last geometry * a view had before being tiled. The given geometry is only a hint by core * and plugins may override it. It may also be undefined (0,0 0x0). */ wf::geometry_t desired_size; /** * The target workspace of the operation. */ wf::point_t workspace; /** * Whether some plugin will service the tile request, in which case other * plugins and core should ignore the request. */ bool carried_out = false; }; /** * on: view, output(view-) * when: After the view's fullscreen state changes. */ struct view_fullscreen_signal { wayfire_toplevel_view view; bool state; }; /** * on: output * when: Emitted whenever some entity requests that the view's fullscreen state * change. If no plugin is available to service the request, it is carried * out by core. See view_interface_t::fullscreen_request() */ struct view_fullscreen_request_signal { wayfire_toplevel_view view; /** The desired fullscreen state */ bool state; /** * Whether some plugin will service the fullscreen request, in which case * other plugins and core should ignore the request. */ bool carried_out = false; /** * The geometry the view should have. This is for example the last geometry * a view had before being fullscreened. The given geometry is only a hint * by core and plugins may override it. It may also be undefined (0,0 0x0). */ wf::geometry_t desired_size; /** * The target workspace of the operation. */ wf::point_t workspace; }; /** * on: view, core * when: Emitted whenever some entity (typically a panel) wants to focus the view. */ struct view_focus_request_signal { wayfire_view view; /** Set to true if core and other plugins should not handle this request. */ bool carried_out = false; /** Set to true if the request comes from the view client itself */ bool self_request; }; /** * name: set-sticky * on: view, output(view-) * when: Whenever the view's sticky state changes. */ struct view_set_sticky_signal { wayfire_toplevel_view view; }; /** * on: view, core * when: After the view's title has changed. */ struct view_title_changed_signal { wayfire_view view; }; /** * on: view, core * when: After the view's app-id has changed. */ struct view_app_id_changed_signal { wayfire_view view; }; /** * on: output, core * when: To show a menu with window related actions. */ struct view_show_window_menu_signal { wayfire_view view; /** The position as requested by the client, in surface coordinates */ wf::point_t relative_position; }; /** * on: view, output(view-), core(view-) * when: Whenever the view's wm geometry changes. */ struct view_geometry_changed_signal { wayfire_toplevel_view view; /** The old wm geometry */ wf::geometry_t old_geometry; }; /** * on: output * when: Whenever the view's workspace changes. (Every plugin changing the * view's workspace should emit this signal). */ struct view_change_workspace_signal { wayfire_toplevel_view view; wf::point_t from, to; /** * Indicates whether the old workspace is known. * If false, then the `from` field should be ignored. */ bool old_workspace_valid = true; }; /** * on: view, core * when: Whenever the value of view::should_be_decorated() changes. */ struct view_decoration_state_updated_signal { wayfire_toplevel_view view; }; /** * on: view * when: Whenever the client fails to respond to a ping request within * the expected time(10 seconds). */ struct view_ping_timeout_signal { wayfire_view view; }; /* ----------------------------------------------------------------------------/ * View <-> output signals * -------------------------------------------------------------------------- */ /** * on: core * when: Immediately before the view is moved to a different workspace set. * view-moved-to-set is emitted afterwards. */ struct view_pre_moved_to_wset_signal { /* The view being moved */ wayfire_toplevel_view view; /* The old wset the view was on, may be NULL. */ std::shared_ptr old_wset; /* The new wset the view is being moved to, may be NULL. */ std::shared_ptr new_wset; }; /** * on: core * when: After the view has been moved to a new wset. */ struct view_moved_to_wset_signal { /* The view being moved */ wayfire_toplevel_view view; /* The old wset the view was on, may be NULL. */ std::shared_ptr old_wset; /* The new wset the view is being moved to, may be NULL. */ std::shared_ptr new_wset; }; /** * on: output * when: This signal is a combination of the unmapped, minimized and set-output signals. In the latter case, * the signal is emitted on the view's previous output. The meaning of this signal is that the view is no * longer available for focus, interaction with the user, etc. on the output where it used to be. */ struct view_disappeared_signal { wayfire_view view; }; /** * on: output * when: Whenever an interactive move is requested on the view. See also * view_interface_t::move_request() */ struct view_move_request_signal { wayfire_toplevel_view view; }; /** * on: output * when: Whenever an interactive resize is requested on the view. See also * view_interface_t::resize_request() */ struct view_resize_request_signal { wayfire_toplevel_view view; /** The requested resize edges */ uint32_t edges; }; /** * on: view and core(view-) * when: the client indicates the views hints have changed (example urgency hint). */ struct view_hints_changed_signal { wayfire_view view; bool demands_attention = false; }; /** * on: core * when: Whenever a client wants to invoke the system bell if such is available. * Note the system bell may or may not be tied to a particular view, so the * signal may be emitted with a nullptr view. */ struct view_system_bell_signal { wayfire_view view; }; } #endif wayfire-0.8.1/src/api/wayfire/signal-provider.hpp000066400000000000000000000125121457431457600220330ustar00rootroot00000000000000#pragma once #include #include #include #include #include #include #include namespace wf { namespace signal { class provider_t; /** * A base class for all connection_t, needed to store list of connections in a * type-safe way. */ class connection_base_t { public: connection_base_t(const connection_base_t&) = delete; connection_base_t(connection_base_t&&) = delete; connection_base_t& operator =(const connection_base_t&) = delete; connection_base_t& operator =(connection_base_t&&) = delete; /** * Automatically disconnects from every connected provider. */ virtual ~connection_base_t() { disconnect(); } bool is_connected() const { return !connected_to.empty(); } /** Disconnect from all connected signal providers */ void disconnect(); protected: connection_base_t() {} // Allow provider to deregister itself friend class provider_t; std::unordered_set connected_to; }; /** * A connection to a signal on an object. * Uses RAII to automatically disconnect the signal when it goes out of scope. */ template class connection_t final : public connection_base_t { public: using callback = std::function; /** Initialize an empty signal connection */ connection_t() {} /** Automatically disconnects from all providers */ virtual ~connection_t() {} template using convertible_to_callback_t = std::enable_if_t, void>; /** Initialize a signal connection with the given callback */ template> connection_t(const T& callback) : connection_t() { set_callback(callback); } /** Initialize a signal connection with the given callback */ template> connection_t& operator =(const T& callback) { set_callback(callback); return *this; } template connection_t(std::function& callback) : connection_t() { set_callback(callback); } /** Set the signal callback or override the existing signal callback. */ void set_callback(callback cb) { this->current_callback = cb; } /** Call the stored callback with the given data. */ void emit(SignalType *data) { if (current_callback) { current_callback(data); } } private: // Non-copyable and non-movable, as that would require updating/duplicating // the signal handler. But this is usually not what users of this API want. // Also provider_t holds pointers to this object. connection_t(const connection_t&) = delete; connection_t(connection_t&&) = delete; connection_t& operator =(const connection_t&) = delete; connection_t& operator =(connection_t&&) = delete; callback current_callback; }; class provider_t { public: /** * Signals are designed to be useful for C++ plugins, however, they are * generally quite difficult to bind in other languages. * To avoid this problem, signal::provider_t also provides C-friendlier * callback support. * * The order of arguments is: (this_pointer, signal_name, data_pointer) */ using c_api_callback = std::function; /** Register a connection to be called when the given signal is emitted. */ template void connect(connection_t *callback) { typed_connections[index()].push_back(callback); callback->connected_to.insert(this); } /** Unregister a connection. */ void disconnect(connection_base_t *callback) { callback->connected_to.erase(this); for (auto& [id, connected] : typed_connections) { connected.remove_all(callback); } } /** Emit the given signal. */ template void emit(SignalType *data) { auto& conns = typed_connections[std::type_index(typeid(SignalType))]; conns.for_each([&] (connection_base_t *tc) { auto real_type = dynamic_cast*>(tc); assert(real_type); real_type->emit(data); }); } provider_t() {} ~provider_t() { for (auto& [id, connected] : typed_connections) { connected.for_each([&] (connection_base_t *base) { base->connected_to.erase(this); }); } } // Non-movable, non-copyable: connection_t keeps reference to this object. // Unclear what happens if this object is duplicated, and plugins usually // don't want this either. provider_t(const provider_t& other) = delete; provider_t& operator =(const provider_t& other) = delete; provider_t(provider_t&& other) = delete; provider_t& operator =(provider_t&& other) = delete; private: template static inline std::type_index index() { return std::type_index(typeid(SignalType)); } std::unordered_map> typed_connections; }; } } wayfire-0.8.1/src/api/wayfire/toplevel-view.hpp000066400000000000000000000152211457431457600215300ustar00rootroot00000000000000#pragma once #include "wayfire/nonstd/observer_ptr.h" #include #include namespace wf { class toplevel_t; class toplevel_view_interface_t; class window_manager_t; } using wayfire_toplevel_view = nonstd::observer_ptr; namespace wf { /** * A list of standard actions which may be allowed on a view. */ enum view_allowed_actions_t { // None of the actions below are allowed. VIEW_ALLOW_NONE = 0, // It is allowed to move the view anywhere on the screen. VIEW_ALLOW_MOVE = (1 << 0), // It is allowed to resize the view arbitrarily. VIEW_ALLOW_RESIZE = (1 << 1), // It is allowed to move the view to another workspace. VIEW_ALLOW_WS_CHANGE = (1 << 2), // All of the actions above are allowed. VIEW_ALLOW_ALL = VIEW_ALLOW_MOVE | VIEW_ALLOW_RESIZE | VIEW_ALLOW_WS_CHANGE, }; /** * A bitmask consisting of all tiled edges. * This corresponds to a maximized state. */ constexpr uint32_t TILED_EDGES_ALL = WLR_EDGE_TOP | WLR_EDGE_BOTTOM | WLR_EDGE_LEFT | WLR_EDGE_RIGHT; /** * Toplevel views are a subtype of views which have an associated toplevel object. As such, they may be moved, * resized, etc. freely by plugins and have many additional operations when compared to other view types. */ class toplevel_view_interface_t : public virtual wf::view_interface_t { public: /** * Get the toplevel object associated with the view. */ const std::shared_ptr& toplevel() const; /** * The toplevel parent of the view, for ex. the main view of a file chooser * dialogue. */ wayfire_toplevel_view parent = nullptr; /** * A list of the children views (typically dialogs). */ std::vector children; /** * Set the toplevel parent of the view, and adjust the children's list of * the parent. */ void set_toplevel_parent(wayfire_toplevel_view parent); /** * Generate a list of all views in the view's tree. * This includes the view itself, its @children and so on. * * @param mapped_only Whether to include only mapped views. * * @return A list of all views in the view's tree. This includes the view * itself, its @children and so on. */ std::vector enumerate_views(bool mapped_only = true); /** * A wrapper function for updating the toplevel's position. * Equivalent to setting the pending coordinates of the toplevel and committing it in a new transaction. */ void move(int x, int y); /** * A wrapper function for updating the toplevel's dimensions. * Equivalent to setting the pending dimensions of the toplevel and committing it in a new transaction. */ void resize(int w, int h); /** * A wrapper function for updating the toplevel's geometry. * Equivalent to setting the pending geometry of the toplevel and committing it in a new transaction. */ void set_geometry(wf::geometry_t g); /** * Request that the view resizes to its native size. */ virtual void request_native_size(); /** Whether the view is in activated state, usually you want to use either * set_activated() or focus_request() */ bool activated = false; /** Whether the view is in minimized state, usually you want to use either * set_minimized() or minimize_request() */ bool minimized = false; /** Whether the view is sticky. If a view is sticky it will not be affected * by changes of the current workspace. */ bool sticky = false; /** Set the minimized state of the view. */ virtual void set_minimized(bool minimized); /** Set the view's activated state. */ virtual void set_activated(bool active); /** Set the view's sticky state. */ virtual void set_sticky(bool sticky); inline uint32_t pending_tiled_edges() const { return toplevel()->pending().tiled_edges; } inline bool pending_fullscreen() const { return toplevel()->pending().fullscreen; } inline wf::geometry_t get_geometry() const { return toplevel()->current().geometry; } inline wf::geometry_t get_pending_geometry() const { return toplevel()->pending().geometry; } /** * Get the allowed actions for this view. By default, all actions are allowed, but plugins may disable * individual actions. * * The allowed actions are a bitmask of @view_allowed_actions_t. */ uint32_t get_allowed_actions() const; /** * Set the allowed actions for the view. * * @param actions The allowed actions, a bitmask of @view_allowed_actions_t. */ void set_allowed_actions(uint32_t actions) const; /** * Get the minimize target for this view, i.e when displaying a minimize * animation, where the animation's target should be. Defaults to {0,0,0,0}. * * @return the minimize target */ virtual wlr_box get_minimize_hint(); /** * Sets the minimize target for this view, i.e when displaying a minimize * animation, where the animation's target should be. * @param hint The new minimize target rectangle, in output-local coordinates. */ virtual void set_minimize_hint(wlr_box hint); /** @return true if the view needs decorations */ virtual bool should_be_decorated(); /** * Set the view's output. * * If the new output is different from the previous, the view will be * removed from the layer it was on the old output. */ virtual void set_output(wf::output_t *new_output) override; /** * Get the workspace set the view is attached to, if any. */ std::shared_ptr get_wset(); virtual ~toplevel_view_interface_t(); std::shared_ptr shared_from_this() { return std::dynamic_pointer_cast(view_interface_t::shared_from_this()); } std::weak_ptr weak_from_this() { return shared_from_this(); } protected: /** * Set the toplevel implementation used by this view. * This function is useful for view implementations only. */ void set_toplevel(std::shared_ptr toplevel); }; inline wayfire_toplevel_view toplevel_cast(wayfire_view view) { return dynamic_cast(view.get()); } // Find the view which has the given toplevel, if such a view exists. // The view might not exist if it was destroyed, but a plugin holds on to a stale toplevel pointer. wayfire_toplevel_view find_view_for_toplevel(std::shared_ptr toplevel); } wayfire-0.8.1/src/api/wayfire/toplevel.hpp000066400000000000000000000114631457431457600205640ustar00rootroot00000000000000#pragma once #include #include "wayfire/geometry.hpp" #include "wayfire/object.hpp" #include namespace wf { /** * Describes the size of the decoration frame around a toplevel. */ struct decoration_margins_t { int left; int right; int bottom; int top; }; struct toplevel_state_t { /** * Mapped toplevel objects are ready to be presented to the user and can interact with input. * Unmapped toplevels usually are not displayed and do not interact with any plugins until they are mapped * at a later point in time. */ bool mapped = false; /** * The geometry of the toplevel, as seen by the 'window manager'. This includes for example decorations, * but excludes shadows or subsurfaces sticking out of the main surface. */ wf::geometry_t geometry = {100, 100, 0, 0}; /** * A bitmask of WLR_EDGE_* values. Indicates the edge or corner of the toplevel which should stay immobile * if the client resizes in a way not indicated by the compositor. * * The default gravity is the top-left corner, which stays immobile if the client for example resizes * itself or does not obey a resize request sent by the compositor. */ uint32_t gravity = WLR_EDGE_LEFT | WLR_EDGE_TOP; /** * The tiled edges of the toplevel. * Tiled edges are edges of the toplevel that are aligned to other objects (output edge, other toplevels, * etc.). Clients usually draw no shadows, rounded corners and similar decorations on tiled edges. * * Usually, when all edges are tiled, the toplevel is considered maximized. */ uint32_t tiled_edges = 0; /** * The fullscreen state of the view. Fullscreen clients are typically shown above panels and take up the * full size of their primary output. */ bool fullscreen = false; /** * The size of the server-side decorations around the view. * * Note that the margin values should be updated by decoration plugins before the toplevel state is * committed, for example during the new_transaction_signal. As a result, the pending margins are not * always meaningful for plugins, and they should avoid reading these values as they likely will not be * finalized before the view is actually committed. */ decoration_margins_t margins = {0, 0, 0, 0}; }; /** * Toplevels are a kind of views which can be moved, resized and whose state can change (fullscreen, tiled, * etc). Most of the toplevel's attributes are double-buffered and are changed via transactions. */ class toplevel_t : public wf::txn::transaction_object_t, public wf::object_base_t { public: /** * The current state of the toplevel, as was last committed by the client. The main surface's buffers * contents correspond to the current state. */ const toplevel_state_t& current() { return _current; } /** * The committed state of the toplevel, that is, the state which the compositor has requested from the * client. This state may be different than the current state in case the client has not committed in * response to the compositor's request. */ const toplevel_state_t& committed() { return _committed; } /** * The pending state of a toplevel. It may be changed by plugins. The pending state, however, will not be * applied until the toplevel is committed as a part of a transaction. */ toplevel_state_t& pending() { return _pending; } protected: toplevel_state_t _current; toplevel_state_t _pending; toplevel_state_t _committed; std::optional last_windowed_geometry; }; // Helper functions when working with toplevel state inline wf::dimensions_t expand_dimensions_by_margins(wf::dimensions_t dim, const decoration_margins_t& margins) { dim.width += margins.left + margins.right; dim.height += margins.top + margins.bottom; return dim; } inline wf::dimensions_t shrink_dimensions_by_margins(wf::dimensions_t dim, const decoration_margins_t& margins) { dim.width -= margins.left + margins.right; dim.height -= margins.top + margins.bottom; return dim; } inline wf::geometry_t expand_geometry_by_margins(wf::geometry_t geometry, const decoration_margins_t& margins) { geometry.x -= margins.left; geometry.y -= margins.top; geometry.width += margins.left + margins.right; geometry.height += margins.top + margins.bottom; return geometry; } inline wf::geometry_t shrink_geometry_by_margins(wf::geometry_t geometry, const decoration_margins_t& margins) { geometry.x += margins.left; geometry.y += margins.top; geometry.width -= margins.left + margins.right; geometry.height -= margins.top + margins.bottom; return geometry; } } wayfire-0.8.1/src/api/wayfire/txn/000077500000000000000000000000001457431457600170255ustar00rootroot00000000000000wayfire-0.8.1/src/api/wayfire/txn/transaction-manager.hpp000066400000000000000000000045741457431457600235050ustar00rootroot00000000000000#pragma once #include "wayfire/signal-provider.hpp" #include "wayfire/txn/transaction-object.hpp" #include namespace wf { namespace txn { /* * The transaction manager keeps track of all committed and pending transactions and ensures that there is at * most one committed transaction for a given object. * * In order to do ensure correct ordering of transactions, it keeps a list of pending transactions. The first * transaction is committed as soon as there are no committed transactions with the same objects. In addition, * any new transactions which are not immediately committed but work with the same objects are coalesced * together. For example, if there are two transactions, one for objects A, for objects B,C and a third * transaction for objects A,B comes in, then all these three are merged together. This merging is done to * avoid sending excessive configure events to clients - for example during an interactive resize. */ class transaction_manager_t : public signal::provider_t { public: transaction_manager_t(); ~transaction_manager_t(); /** * Add a new transaction to the list of scheduled transactions. The transaction might be merged with * other transactions which came before or after it, according to the coalescing schema described above. * * Note that a transaction will never be started immediately. Instead, it will be started on the next idle * event of the event loop. */ void schedule_transaction(transaction_uptr tx); /** * A convenience function to create a transaction for a single object and schedule it via * schedule_transaction(). */ void schedule_object(transaction_object_sptr object); /** * Check whether there is a pending transaction for the given object. */ bool is_object_pending(transaction_object_sptr object) const; /** * Check whether there is a committed transaction for the given object. */ bool is_object_committed(transaction_object_sptr object) const; struct impl; std::unique_ptr priv; }; /** * The new-transaction signal is emitted before a new transaction is added to the transaction manager (e.g. * at the beginning of schedule_transaction()). The transaction may be merged into another transaction before * it is actually executed. */ struct new_transaction_signal { transaction_t *tx; }; } } wayfire-0.8.1/src/api/wayfire/txn/transaction-object.hpp000066400000000000000000000042611457431457600233320ustar00rootroot00000000000000#pragma once #include namespace wf { namespace txn { /** * A transaction object participates in the transactions system. * * Transaction objects usually have double-buffered state, which may not be applicable immediately, that is, * when a state change is requested, it takes some time until the changes can be applied. Sometimes, multiple * such objects are updated together in a single transaction, in which case the changes are to be seen as * atomic across all participating objects. * * The typical example of transaction objects are toplevels, where changing for example the size of the * toplevel requires cooperation from the client, and therefore cannot be done instantaneously. * * When speaking about transaction objects, they usually have three different types of state: current, * committed and pending. Current state is what the object currently is configured as, committed state is a * state which will soon be current (e.g. changes are underway), and pending are changes which have been * planned for the future, but execution has not started yet. */ class transaction_object_t : public signal::provider_t { public: /** * Get a string representing the transaction object. Used for debugging purposes. */ virtual std::string stringify() const; /** * Make the pending state committed. * This function is called when a transaction is committed. */ virtual void commit() = 0; /** * Make the committed state current. * This function is called when all transaction objects in a transaction are ready to apply the committed * state. */ virtual void apply() = 0; virtual ~transaction_object_t() = default; }; using transaction_object_sptr = std::shared_ptr; /** * A signal emitted on a transaction_object_t to indicate that it is ready to be applied. */ struct object_ready_signal { transaction_object_t *self; }; /** * Emit the object-ready signal on the given object. */ inline void emit_object_ready(wf::txn::transaction_object_t *obj) { wf::txn::object_ready_signal data_ready; data_ready.self = obj; obj->emit(&data_ready); return; } } } wayfire-0.8.1/src/api/wayfire/txn/transaction.hpp000066400000000000000000000045051457431457600220670ustar00rootroot00000000000000#pragma once #include "wayfire/signal-provider.hpp" #include "wayfire/util.hpp" #include namespace wf { namespace txn { /** * A transaction contains one or more transaction objects whose state should be applied atomically, that is, * changes to the objects should be applied only after all the objects are ready to apply the changes. */ class transaction_t : public signal::provider_t { public: using timer_setter_t = std::function::callback_t)>; /** * Create a new transaction. * * @param timeout The timeout for the transaction in milliseconds after it is committed. * -1 means that core should pick a default timeout. */ static std::unique_ptr create(int64_t timeout = -1); /** * Create a new empty transaction. * * @param timer A function used to set timeout callbacks at runtime. * @param timeout The maximal duration, in milliseconds, to wait for transaction objects to become ready. * When the timeout is reached, all committed state is applied. */ transaction_t(uint64_t timeout, timer_setter_t timer_setter); /** * Add a new object to the transaction. If the object was already part of it, this is no-op. */ void add_object(transaction_object_sptr object); /** * Get a list of all the objects currently part of the transaction. */ const std::vector& get_objects() const; /** * Commit the transaction, that is, commit the pending state of all participating objects. * As soon as all objects are ready or the transaction times out, the state will be applied. */ void commit(); virtual ~transaction_t() = default; private: std::vector objects; int count_ready_objects = 0; uint64_t timeout; timer_setter_t timer_setter; void apply(bool did_timeout); wf::signal::connection_t on_object_ready; }; using transaction_uptr = std::unique_ptr; /** * A signal emitted on a transaction as soon as it has been applied. */ struct transaction_applied_signal { transaction_t *self; // Set to true if the transaction timed out and the desired object state may not have been achieved. bool timed_out; }; } } wayfire-0.8.1/src/api/wayfire/unstable/000077500000000000000000000000001457431457600200315ustar00rootroot00000000000000wayfire-0.8.1/src/api/wayfire/unstable/translation-node.hpp000066400000000000000000000037231457431457600240300ustar00rootroot00000000000000#pragma once #include #include namespace wf { namespace scene { /** * A node which simply applies an offset to its children. */ class translation_node_t : public wf::scene::floating_inner_node_t { public: translation_node_t(bool is_structure = false); /** * Set the offset the node applies to its children. * Note that damage is not automatically applied. */ void set_offset(wf::point_t offset); /** * Get the current offset (set via @set_offset). Default offset is {0, 0}. */ wf::point_t get_offset() const; public: // Implementation of node_t interface wf::pointf_t to_local(const wf::pointf_t& point) override; wf::pointf_t to_global(const wf::pointf_t& point) override; std::string stringify() const override; void gen_render_instances(std::vector& instances, scene::damage_callback damage, wf::output_t *output) override; wf::geometry_t get_bounding_box() override; protected: wf::point_t offset = {0, 0}; }; class translation_node_instance_t : public render_instance_t { protected: std::vector children; damage_callback push_damage; translation_node_t *self; wf::signal::connection_t on_node_damage; public: translation_node_instance_t(translation_node_t *self, damage_callback push_damage, wf::output_t *shown_on); // Implementation of render_instance_t void schedule_instructions(std::vector& instructions, const wf::render_target_t& target, wf::region_t& damage) override; void render(const wf::render_target_t& target, const wf::region_t& region) override; void presentation_feedback(wf::output_t *output) override; wf::scene::direct_scanout try_scanout(wf::output_t *output) override; void compute_visibility(wf::output_t *output, wf::region_t& visible) override; }; } } wayfire-0.8.1/src/api/wayfire/unstable/wlr-surface-node.hpp000066400000000000000000000054551457431457600237300ustar00rootroot00000000000000#pragma once #include "wayfire/geometry.hpp" #include "wayfire/util.hpp" #include "wayfire/view-transform.hpp" #include #include namespace wf { namespace scene { struct surface_state_t { // The surface state struct keeps a lock on the wlr_buffer, so that it is always valid as long as the // state is current. wlr_buffer *current_buffer = nullptr; wlr_texture *texture; // The texture of the wlr_client_buffer wf::region_t accumulated_damage; wf::dimensions_t size = {0, 0}; std::optional src_viewport; // Read the current surface state, get a lock on the current surface buffer (releasing any old locks), // and accumulate damage. void merge_state(wlr_surface *surface); surface_state_t() = default; // Releases the lock on the current_buffer, if one is held. ~surface_state_t(); surface_state_t(const surface_state_t& other) = delete; surface_state_t& operator =(const surface_state_t& other) = delete; surface_state_t(surface_state_t&& other); surface_state_t& operator =(surface_state_t&& other); }; /** * An implementation of node_t for wlr_surfaces. * * The node typically does not have children and displays a single surface. It is assumed that the surface is * positioned at (0, 0), which means this node usually should be put with a parent node which manages the * position in the scenegraph. */ class wlr_surface_node_t : public node_t, public zero_copy_texturable_node_t { public: /** * @param autocommit Whether the surface should automatically apply new surface state on surface commit, * or it should wait until it is manually applied. */ wlr_surface_node_t(wlr_surface *surface, bool autocommit); std::optional find_node_at(const wf::pointf_t& at) override; std::string stringify() const override; pointer_interaction_t& pointer_interaction() override; touch_interaction_t& touch_interaction() override; void gen_render_instances(std::vector& instances, damage_callback damage, wf::output_t *output) override; wf::geometry_t get_bounding_box() override; std::optional to_texture() const override; wlr_surface *get_surface() const; void apply_state(surface_state_t&& state); void send_frame_done(bool delay_until_vblank); private: std::unique_ptr ptr_interaction; std::unique_ptr tch_interaction; wlr_surface *surface; std::map visibility; class wlr_surface_render_instance_t; wf::wl_listener_wrapper on_surface_destroyed; wf::wl_listener_wrapper on_surface_commit; const bool autocommit; surface_state_t current_state; void apply_current_surface_state(); }; } } wayfire-0.8.1/src/api/wayfire/unstable/wlr-text-input-v3-popup.hpp000066400000000000000000000031201457431457600251500ustar00rootroot00000000000000#pragma once #include #include #include #include namespace wf { class text_input_v3_im_relay_interface_t { public: virtual wlr_text_input_v3 *find_focused_text_input_v3() = 0; }; /** * A view implementation which presents an input-method popup-like surface relative to a text-input-v3 cursor. */ class text_input_v3_popup : public wf::view_interface_t { public: text_input_v3_im_relay_interface_t *relay = nullptr; wlr_surface *surface = nullptr; text_input_v3_popup(text_input_v3_im_relay_interface_t *relay, wlr_surface *surface); static std::shared_ptr create(text_input_v3_im_relay_interface_t*, wlr_surface*); bool is_mapped() const override; std::string get_app_id() override; std::string get_title() override; wf::geometry_t get_geometry(); void map(); void unmap(); void update_geometry(); ~text_input_v3_popup(); private: wf::geometry_t geometry{0, 0, 0, 0}; std::shared_ptr main_surface; std::shared_ptr surface_root_node; virtual wlr_surface *get_keyboard_focus_surface() override { return nullptr; } wf::wl_listener_wrapper on_map; wf::wl_listener_wrapper on_unmap; wf::wl_listener_wrapper on_commit; wf::wl_listener_wrapper on_surface_destroy; public: // This is only a convenience wrapper for the users of this class. wf::wl_listener_wrapper on_destroy; }; } wayfire-0.8.1/src/api/wayfire/unstable/wlr-view-events.hpp000066400000000000000000000041711457431457600236230ustar00rootroot00000000000000#pragma once #if __has_include() #include #else #include "config.h" #endif #include #include namespace wf { /** * A signal emitted whenever a new xdg_surface object was created on the wlroots side. * By using this signal, plugins may indicate to core that they want to override the view implementation for * the given surface. */ struct new_xdg_surface_signal { wlr_xdg_surface *surface; /** * If a plugin sets this to false, then that plugin is responsible for allocating a view and the * corresponding nodes for the xdg_surface. Core will not handle the xdg_surface any further. */ bool use_default_implementation = true; }; #if WF_HAS_XWAYLAND /** * A signal emitted whenever a new wlr_xwayland_surface object was created on the wlroots side. * By using this signal, plugins may indicate to core that they want to override the view implementation for * the given surface. */ struct new_xwayland_surface_signal { wlr_xwayland_surface *surface; /** * If a plugin sets this to false, then that plugin is responsible for allocating a view and the * corresponding nodes for the xwayland_surface. Core will not handle the xwayland_surface any further. */ bool use_default_implementation = true; }; #endif /** * A signal emitted on core when a view with the default wayfire implementation is about to be mapped. * Plugins can take a look at the view and decide to overwrite its implementation. */ struct view_pre_map_signal { /** * The view which will be mapped after this signal, if plugins do not override it. */ wf::view_interface_t *view; /** * The wlr-surface of the view. */ wlr_surface *surface; /** * Plugins can set this to override the view implementation. If they do so, the view will not be mapped, * and instead the default controller and view implementation for the view will be destroyed after the * signal. Plugins are then free to provide a view implementation themselves. */ bool override_implementation = false; }; } wayfire-0.8.1/src/api/wayfire/unstable/wlr-view-keyboard-interaction.hpp000066400000000000000000000027701457431457600264370ustar00rootroot00000000000000#pragma once #include "wayfire/signal-definitions.hpp" #include "wayfire/signal-provider.hpp" #include #include #include #include #include #include namespace wf { /** * An implementation of keyboard_interaction_t for wlr_surface-based views. */ class wlr_view_keyboard_interaction_t : public wf::keyboard_interaction_t { std::weak_ptr view; public: wlr_view_keyboard_interaction_t(wayfire_view _view) { this->view = _view->weak_from_this(); } void handle_keyboard_enter(wf::seat_t *seat) override { if (auto ptr = view.lock()) { if (ptr->get_wlr_surface()) { auto pressed_keys = seat->get_pressed_keys(); auto kbd = wlr_seat_get_keyboard(seat->seat); wlr_seat_keyboard_notify_enter(seat->seat, ptr->get_wlr_surface(), pressed_keys.data(), pressed_keys.size(), kbd ? &kbd->modifiers : NULL); } } } void handle_keyboard_leave(wf::seat_t *seat) override { if (auto ptr = view.lock()) { wlr_seat_keyboard_notify_clear_focus(seat->seat); } } void handle_keyboard_key(wf::seat_t *seat, wlr_keyboard_key_event event) override { wlr_seat_keyboard_notify_key(seat->seat, event.time_msec, event.keycode, event.state); } }; } wayfire-0.8.1/src/api/wayfire/unstable/xdg-toplevel-base.hpp000066400000000000000000000026411457431457600240670ustar00rootroot00000000000000#pragma once #include #include #include namespace wf { /** * A base class for xdg_toplevel-based views which implements the view_interface_t (but not toplevel_view_t, * see @xdg_toplevel_view_t for the full implementation). */ class xdg_toplevel_view_base_t : public virtual wf::view_interface_t { public: xdg_toplevel_view_base_t(wlr_xdg_toplevel *toplevel, bool autocommit); virtual ~xdg_toplevel_view_base_t(); void close() override; void ping() override; wlr_surface *get_keyboard_focus_surface() override; bool is_focusable() const override; std::string get_app_id() override; std::string get_title() override; bool is_mapped() const override; /** Set the view state to mapped. */ virtual void map(); /** Set the view state to unmapped. */ virtual void unmap(); protected: wlr_xdg_toplevel *xdg_toplevel; std::string app_id; std::string title; virtual void destroy(); std::shared_ptr main_surface; wf::wl_listener_wrapper on_destroy; wf::wl_listener_wrapper on_new_popup; wf::wl_listener_wrapper on_set_title; wf::wl_listener_wrapper on_set_app_id; wf::wl_listener_wrapper on_ping_timeout; void handle_title_changed(std::string new_title); void handle_app_id_changed(std::string new_app_id); }; } wayfire-0.8.1/src/api/wayfire/unstable/xwl-toplevel-base.hpp000066400000000000000000000027511457431457600241210ustar00rootroot00000000000000#pragma once #if __has_include() #include #else #include "config.h" #endif #include #include #include namespace wf { #if WF_HAS_XWAYLAND /** * A base class for views which base on a wlr_xwayland surface. * Contains the implementation of view_interface_t functions used in them. */ class xwayland_view_base_t : public virtual wf::view_interface_t { public: xwayland_view_base_t(wlr_xwayland_surface *xww); virtual ~xwayland_view_base_t(); virtual void do_map(wlr_surface *surface, bool autocommit, bool emit_map = true); virtual void do_unmap(); virtual void destroy(); void ping() override; void close() override; bool is_mapped() const override; std::string get_app_id() override; std::string get_title() override; wlr_surface *get_keyboard_focus_surface() override; bool is_focusable() const override; protected: std::string title, app_id; wlr_xwayland_surface *xw; bool kb_focus_enabled = true; /** Used by view implementations when the app id changes */ void handle_app_id_changed(std::string new_app_id); /** Used by view implementations when the title changes */ void handle_title_changed(std::string new_title); wf::wl_listener_wrapper on_destroy, on_set_title, on_set_app_id, on_ping_timeout; std::shared_ptr main_surface; }; #endif } wayfire-0.8.1/src/api/wayfire/util.hpp000066400000000000000000000102261457431457600177030ustar00rootroot00000000000000#ifndef WF_UTIL_HPP #define WF_UTIL_HPP #include #include #include namespace wf { /** Convert timespect to milliseconds. */ int64_t timespec_to_msec(const timespec& ts); /** Returns current time in msec, using CLOCK_MONOTONIC as a base */ int64_t get_current_time(); /** * A wrapper around wl_listener compatible with C++11 std::functions */ struct wl_listener_wrapper { using callback_t = std::function; wl_listener_wrapper(); ~wl_listener_wrapper(); wl_listener_wrapper(const wl_listener_wrapper &) = delete; wl_listener_wrapper(wl_listener_wrapper &&) = delete; wl_listener_wrapper& operator =(const wl_listener_wrapper&) = delete; wl_listener_wrapper& operator =(wl_listener_wrapper&&) = delete; /** Set the callback to be used when the signal is fired. Can be called * multiple times to update it */ void set_callback(callback_t call); /** Connect this callback to a signal. Calling this on an already * connected listener will have no effect. * @return true if connection was successful */ bool connect(wl_signal *signal); /** Disconnect from the wl_signal. No-op if not connected */ void disconnect(); /** @return true if connected to a wl_signal */ bool is_connected() const; /** Call the stored callback. No-op if no callback was specified */ void emit(void *data); struct wrapper { wl_listener listener; wl_listener_wrapper *self; }; private: callback_t call; wrapper _wrap; }; /** * A wrapper for adding idle callbacks to the event loop */ class wl_idle_call { public: using callback_t = std::function; /* Initialize an empty idle call. */ wl_idle_call(); /** Will disconnect if connected */ ~wl_idle_call(); // Non-movable since wayland holds pointers to this object. wl_idle_call(const wl_idle_call &) = delete; wl_idle_call(wl_idle_call &&) = delete; wl_idle_call& operator =(const wl_idle_call&) = delete; wl_idle_call& operator =(wl_idle_call&&) = delete; /** Set the callback. This will disconnect the wl_idle_call if it is * connected */ void set_callback(callback_t call); /** Run the passed callback the next time the loop goes idle. No effect * if already waiting for idleness, or if the callback hasn't been set. */ void run_once(); /* Same as calling set_callback + run_once */ void run_once(callback_t call); /** Stop waiting for idle, no-op if not connected */ void disconnect(); /** @return true if the event source is active */ bool is_connected() const; /** execute the callback now. do not use manually! */ void execute(); static wl_event_loop *loop; private: callback_t call; wl_event_source *source = NULL; }; /** * A wrapper for wl_event_loop_add_timer / wl_event_loop_timer_update * * @param repeatable If repeatable is true, then the callback's return value indicates whether to repeat the * timer or not. In those cases, it is not possible to destroy the timer from within the callback. */ template class wl_timer { public: // If @repeatable is true, then the return value indicates whether the timer should repeat after the same // amount of time. using callback_t = std::function()>; wl_timer() = default; /** Disconnects the timer if connected */ ~wl_timer(); // Non-movable since wayland holds pointers to this object. wl_timer(const wl_timer &) = delete; wl_timer(wl_timer &&) = delete; wl_timer& operator =(const wl_timer&) = delete; wl_timer& operator =(wl_timer&&) = delete; /** Execute call after a timeout of timeout_ms */ void set_timeout(uint32_t timeout_ms, callback_t call); /** If a timeout has been registered, but not fired yet, remove the * timeout. Otherwise no-op */ void disconnect(); /** @return true if the event source is active */ bool is_connected(); private: wl_event_source *source = NULL; uint32_t timeout = -1; std::function execute; }; } #endif /* end of include guard: WF_UTIL_HPP */ wayfire-0.8.1/src/api/wayfire/view-access-interface.hpp000066400000000000000000000031401457431457600230720ustar00rootroot00000000000000#pragma once #include "wayfire/condition/access_interface.hpp" #include "wayfire/view.hpp" #include #include namespace wf { /** * @brief The view_access_interface_t class is a view specific implementation of * access_interface_t. * * Refer to the docs of access_interface_t for more information. * * The following properties are supported: * * format: * property -> type (comment) * * "app_id" -> std::string * "title" -> std::string * "role" -> std::string * "fullscreen" -> bool * "activated" -> bool * "minimized" -> bool * "tiled-left" -> bool * "tiled-right" -> bool * "tiled-top" -> bool * "tiled-bottom" -> bool * "maximized" -> bool * "floating" -> bool * "type" -> std::string (This will return a type string like the matcher plugin did) */ class view_access_interface_t : public access_interface_t { public: /** * @brief view_access_interface_t Default constructor. */ view_access_interface_t(); /** * @brief view_access_interface_t Constructor that immediately assigns a view. * * @param[in] view The view to assign. */ view_access_interface_t(wayfire_view view); // Inherits docs. virtual ~view_access_interface_t() override; // Inherits docs. virtual variant_t get(const std::string & identifier, bool & error) override; /** * @brief set_view Setter for the view to interrogate. * * @param[in] view The view to assign. */ void set_view(wayfire_view view); private: /** * @brief _view The view to interrogate. */ wayfire_view _view; }; } // End namespace wf. wayfire-0.8.1/src/api/wayfire/view-helpers.hpp000066400000000000000000000035631457431457600213460ustar00rootroot00000000000000#pragma once #include "toplevel.hpp" #include "wayfire/scene-input.hpp" #include "wayfire/scene.hpp" #include #include // This file contains helper functions which are helpful when working with views. Most of the operations are // simply wrappers around more low-level functionality provided by views, scenegraph, etc. namespace wf { /** * Find the scenegraph layer that the view is currently in. */ std::optional get_view_layer(wayfire_view view); /** * Reorder the nodes on the path from the view to the scenegraph root so that the view is as high in the * stacking order as possible. * * Also damages the affected nodes. */ void view_bring_to_front(wayfire_view view); /** * Iterate over all scenegraph nodes in the given scenegraph subtree and collect all enabled view nodes. * The nodes returned are in front-to-back order. */ std::vector collect_views_from_scenegraph(wf::scene::node_ptr root); /** * Collect all views from the scenegraph nodes of the output for the given layers. */ std::vector collect_views_from_output( wf::output_t *output, std::initializer_list layers); /** * Find the topmost parent in the chain of views. */ wayfire_view find_topmost_parent(wayfire_view v); wayfire_toplevel_view find_topmost_parent(wayfire_toplevel_view v); /** * A few simple functions which help in view implementations. */ namespace view_implementation { void emit_toplevel_state_change_signals(wayfire_toplevel_view view, const wf::toplevel_state_t& old_state); void emit_view_map_signal(wayfire_view view, bool has_position); void emit_ping_timeout_signal(wayfire_view view); void emit_geometry_changed_signal(wayfire_toplevel_view view, wf::geometry_t old_geometry); void emit_title_changed_signal(wayfire_view view); void emit_app_id_changed_signal(wayfire_view view); } } wayfire-0.8.1/src/api/wayfire/view-transform.hpp000066400000000000000000000322431457431457600217140ustar00rootroot00000000000000#ifndef VIEW_TRANSFORM_HPP #define VIEW_TRANSFORM_HPP #include "wayfire/debug.hpp" #include "wayfire/geometry.hpp" #include "wayfire/region.hpp" #include "wayfire/scene-render.hpp" #include "wayfire/scene.hpp" #include #include namespace wf { namespace scene { class zero_copy_texturable_node_t { public: virtual ~zero_copy_texturable_node_t() = default; /** * Get a texture from the node without copying. * Note that this operation might fail for non-trivial transformers. */ virtual std::optional to_texture() const { return {}; } }; class opaque_region_node_t { public: virtual ~opaque_region_node_t() = default; /** * Get the opaque region of the node in its parent's coordinate system (same as get_bounding_box()). */ virtual wf::region_t get_opaque_region() const { return {}; } }; /** * A helper class for implementing transformer nodes. * Transformer nodes usually operate on views and implement special effects, like * for example rotating a view, blurring the background, etc. * * To allow arbitrary combinations of transformers, the different transformers are * arranged so that they build a chain where each transformer is the child of the * previous transformer, and the child of the last transformer is the view's * surface root node. For the actual composition of effects, every transformer * first renders its children (with the transformation which comes from the next * transformers in the chain) to a temporary buffer and then renders the temporary * buffer with the node's own transform applied. * * @param NodeType the concrete type of the node this instance belongs to, must be * a subclass of node_t. */ template class transformer_render_instance_t : public render_instance_t { protected: // A pointer to the transformer node this render instance belongs to. NodeType *self; // A list of render instances of the next transformer or the view itself. std::vector children; // A temporary buffer to render children to. wf::render_target_t inner_content; // Damage from the children, which is the region of @inner_content that // should be repainted on the next frame to have a valid copy of the // children's current content. wf::region_t cached_damage; /** * Get a texture which contains the contents of the children nodes. * If the node has a single child which supports zero-copy texture generation * via @to_texture, that method is preferred to avoid unnecessary copies. * * Otherwise, the children are rendered to an auxiliary buffer (@inner_content), * whose texture is returned. * * @param scale The scale to use when generating the texture. The scale * indicates how much bigger the temporary buffer should be than its logical * size. */ wf::texture_t get_texture(float scale) { // Optimization: if we have a single child (usually the surface root node) // and we can directly convert it to texture, we don't need a full render // pass. if (self->get_children().size() == 1) { auto child = self->get_children().front().get(); if (auto zcopy = dynamic_cast(child)) { if (auto tex = zcopy->to_texture()) { if (inner_content.fb != (uint) - 1) { // Release the inner_content buffer, because we are on // the zero-copy path and we do not need an auxiliary // buffer to render to. OpenGL::render_begin(); inner_content.release(); OpenGL::render_end(); } return *tex; } } } auto bbox = self->get_children_bounding_box(); int target_width = scale * bbox.width; int target_height = scale * bbox.height; OpenGL::render_begin(); inner_content.scale = scale; if (inner_content.allocate(target_width, target_height)) { cached_damage |= bbox; } inner_content.geometry = bbox; OpenGL::render_end(); render_pass_params_t params; params.instances = &children; params.target = inner_content; params.damage = cached_damage; params.background_color = {0.0f, 0.0f, 0.0f, 0.0f}; scene::run_render_pass(params, RPASS_CLEAR_BACKGROUND); cached_damage.clear(); return wf::texture_t{inner_content.tex}; } void presentation_feedback(wf::output_t *output) override { for (auto& ch : children) { ch->presentation_feedback(output); } } virtual void transform_damage_region(wf::region_t& damage) {} public: transformer_render_instance_t(NodeType *self, damage_callback push_damage, wf::output_t *shown_on) { static_assert(std::is_base_of_v, "transformer_render_instance_t should be instantiated with a " "subclass of node_t!"); this->self = self; auto push_damage_child = [=] (wf::region_t region) { this->cached_damage |= region; transform_damage_region(region); push_damage(region); }; this->cached_damage |= self->get_children_bounding_box(); for (auto& ch : self->get_children()) { ch->gen_render_instances(children, push_damage_child, shown_on); } } ~transformer_render_instance_t() { OpenGL::render_begin(); inner_content.release(); OpenGL::render_end(); } void schedule_instructions( std::vector& instructions, const wf::render_target_t& target, wf::region_t& damage) override { if (!damage.empty()) { auto our_damage = damage & self->get_bounding_box(); instructions.push_back(wf::scene::render_instruction_t{ .instance = this, .target = target, .damage = std::move(our_damage), }); } } void render(const wf::render_target_t& target, const wf::region_t& damage) override { wf::dassert(false, "Rendering not implemented for view transformer?"); } direct_scanout try_scanout(wf::output_t *output) override { // By default, disable direct scanout return direct_scanout::OCCLUSION; } bool has_instances() { return !children.empty(); } void compute_visibility(wf::output_t *output, wf::region_t& visible) override { if (!(visible & self->get_bounding_box()).empty()) { // By default, we are not sure how the visibility region is affected, so we take a simple 0-or-1 // approach: if anything of the bounding box is visible, we assume the whole view is visible, and // we do not subtract anything from the visibility region of the nodes below. wf::region_t copy = self->get_children_bounding_box(); for (auto& ch : this->children) { ch->compute_visibility(output, copy); } } } }; /** * A floating inner node which contains a chain of view transformers and a view * surface root node at the bottom of the chain. Its interface can be used to * add and sort view transformers. */ class transform_manager_node_t : public wf::scene::floating_inner_node_t { public: transform_manager_node_t() : floating_inner_node_t(false) {} /** * Marks a section of the code which updates one or more transformers added to this transform manager. * Doing so will ensure that the proper damage is propagated upwards in the scenegraph. */ void begin_transform_update(); /** * Marks the end of a section of the code which updates one or more transformers added to this transform * manager. Doing so will ensure that the proper damage is propagated upwards in the scenegraph. */ void end_transform_update(); /** * Add a new transformer in the transformer chain. * * @param transformer The transformer to be added. * @param z_order The order of this transformer relative to other transformers. * Smaller values indicate that the transformer should be applied before * others, see @transformer_z_order_t. * @param name A string which can be used as an ID for easily getting a * transformer node or removing it. */ template void add_transformer(std::shared_ptr transformer, int z_order, std::string name = typeid(ConcreteTransformer).name()) { _add_transformer(transformer, z_order, name); } template void rem_transformer(std::shared_ptr transformer) { _rem_transformer(transformer); } template void rem_transformer(std::string name = typeid(ConcreteTransformer).name()) { _rem_transformer(get_transformer(name)); } /** * Find a transformer with the given name and type. */ template std::shared_ptr get_transformer( std::string name = typeid(ConcreteTransformer).name()) { for (auto& tr : transformers) { if (tr.name == name) { return std::dynamic_pointer_cast(tr.node); } } return nullptr; } std::string stringify() const override { return "view-transform-root"; } private: struct added_transformer_t { wf::scene::floating_inner_ptr node; int z_order; std::string name; }; std::vector transformers; void _add_transformer(wf::scene::floating_inner_ptr transformer, int z_order, std::string name); void _rem_transformer(wf::scene::floating_inner_ptr transformer); }; /** * A simple transformer which supports 2D transformations on a view. */ class view_2d_transformer_t : public scene::floating_inner_node_t { public: float scale_x = 1.0f; float scale_y = 1.0f; float translation_x = 0.0f; float translation_y = 0.0f; // An angle in radians indicating how much the view should be rotated // around its center counter-clockwise. float angle = 0.0f; // A multiplier for the view's opacity. // Note that if the view was not opaque to begin with, setting alpha=1.0 // does not make it opaque. float alpha = 1.0f; view_2d_transformer_t(wayfire_view view); wf::pointf_t to_local(const wf::pointf_t& point) override; wf::pointf_t to_global(const wf::pointf_t& point) override; std::string stringify() const override; wf::geometry_t get_bounding_box() override; void gen_render_instances(std::vector& instances, damage_callback push_damage, wf::output_t *shown_on) override; wayfire_view view; }; /** * A simple transformer which supports 3D transformations on a view. */ class view_3d_transformer_t : public scene::floating_inner_node_t { protected: wayfire_view view; public: glm::mat4 view_proj{1.0}, translation{1.0}, rotation{1.0}, scaling{1.0}; glm::vec4 color{1, 1, 1, 1}; glm::mat4 calculate_total_transform(); public: view_3d_transformer_t(wayfire_view view); wf::pointf_t to_local(const wf::pointf_t& point) override; wf::pointf_t to_global(const wf::pointf_t& point) override; std::string stringify() const override; wf::geometry_t get_bounding_box() override; void gen_render_instances(std::vector& instances, damage_callback push_damage, wf::output_t *shown_on) override; static const float fov; // PI / 8 static glm::mat4 default_view_matrix(); static glm::mat4 default_proj_matrix(); }; } /** * When adding multiple transformers to a view, the relative order of these * transform nodes to each other matters. The transformer_z_order_t enum contains * a few common values used by transformers from core. Note that plugins may use * any integer as a Z order for a transformer. */ enum transformer_z_order_t { // Simple 2D transforms applied to the base surface. Used for things like // scaling, simple 2D rotation. TRANSFORMER_2D = 1, TRANSFORMER_3D = 2, // Highlevel transformation which is usually at the top of the stack. // Used for things like wobbly and fire animation. TRANSFORMER_HIGHLEVEL = 500, // The highest level of view transforms, used by blur. TRANSFORMER_BLUR = 1000, }; // Calculate a bounding box after applying the node transformation to @box, // assuming an affine transformation applied by the node. wf::geometry_t get_bbox_for_node(scene::node_ptr node, wf::geometry_t box); } #endif /* end of include guard: VIEW_TRANSFORM_HPP */ wayfire-0.8.1/src/api/wayfire/view.hpp000066400000000000000000000167651457431457600177160ustar00rootroot00000000000000#ifndef VIEW_HPP #define VIEW_HPP #include #include #include #include #include "wayfire/nonstd/tracking-allocator.hpp" #include "wayfire/object.hpp" #include "wayfire/geometry.hpp" #include "wayfire/view-transform.hpp" #include #include #include namespace wf { class view_interface_t; class workspace_set_t; struct render_target_t; } using wayfire_view = nonstd::observer_ptr; namespace wf { class output_t; namespace scene { class view_node_t; } /* abstraction for desktop-apis, no real need for plugins * This is a base class to all "drawables" - desktop views, subsurfaces, popups */ enum view_role_t { /** Regular views which can be moved around. */ VIEW_ROLE_TOPLEVEL, /** Views which position is fixed externally, for ex. Xwayland OR views */ VIEW_ROLE_UNMANAGED, /** * Views which are part of the desktop environment, for example panels, * background views, etc. */ VIEW_ROLE_DESKTOP_ENVIRONMENT, }; /** * The view_interface_t represents a window shown to the user. It includes panels, backgrounds, notifications, * and toplevels (which derive from the subclass toplevel_view_interface_t). * * Views should be allocated via the helper allocator tracking_allocator_t: * ``` * auto& alloc = tracking_allocator_t::get(); * alloc.allocate(arguments) * ``` * * This ensures that all plugins can query a list of all available views at any given time. */ class view_interface_t : public wf::signal::provider_t, public wf::object_base_t, public std::enable_shared_from_this { public: /** * Get the root of the view tree. This is the node which contains the view * and all of its child views. * * Usually, the tree root node has at least the transformed_node as its child, * and the tree root nodes of child views. */ const scene::floating_inner_ptr& get_root_node() const; /** * Get the root of the view itself, including its main surface, subsurfaces * and transformers, but not dialogs. */ const std::shared_ptr& get_transformed_node() const; /** * Get the node which contains the main view (+subsurfaces) only. */ const scene::floating_inner_ptr& get_surface_root_node() const; /** The current view role. */ view_role_t role = VIEW_ROLE_TOPLEVEL; /** Set the view role */ virtual void set_role(view_role_t new_role); /** Get a textual identifier for this view. */ std::string to_string() const; /** Wrap the view into a nonstd::observer_ptr<> */ wayfire_view self(); /** * Set the view's output. * * If the new output is different from the previous, the view will be * removed from the layer it was on the old output. */ virtual void set_output(wf::output_t *new_output); /** * Get the view's main output. */ virtual wf::output_t *get_output(); /** Request that the view closes. */ virtual void close(); /** * Ping the view's client. * If the ping request times out, `ping-timeout` event will be emitted. */ virtual void ping(); /** * @return The bounding box of the view, which includes all (sub)surfaces, * menus, etc. after applying the view transformations. */ virtual wlr_box get_bounding_box(); /** * @return the wlr_surface which should receive focus when focusing this * view. Views which aren't backed by a wlr_surface should implement the * compositor_view interface. * * In case no focus surface is available, or the view should not be focused, * nullptr should be returned. */ virtual wlr_surface *get_keyboard_focus_surface() = 0; /** * Check whether the surface is focusable. Note the actual ability to give * keyboard focus while the surface is mapped is determined by the keyboard * focus surface or the compositor_view implementation. * * This is meant for plugins like matcher, which need to check whether the * view is focusable at any point of the view life-cycle. */ virtual bool is_focusable() const; /** Damage the whole view and add the damage to its output */ virtual void damage(); /** @return the app-id of the view */ virtual std::string get_app_id() { return ""; } /** @return the title of the view */ virtual std::string get_title() { return ""; } /** @return true if the view has active transformers */ bool has_transformer(); /** * A snapshot of the view is a copy of the view's contents into a framebuffer. */ virtual void take_snapshot(wf::render_target_t& target); /** * @return the wl_client associated with this surface, or null if the * surface doesn't have a backing wlr_surface. */ wl_client *get_client(); /** * @return the wlr_surface associated with this surface, or null if no * the surface doesn't have a backing wlr_surface. */ virtual wlr_surface *get_wlr_surface(); virtual bool is_mapped() const { return false; } virtual ~view_interface_t(); class view_priv_impl; std::unique_ptr priv; template static std::shared_ptr create(Args... args) { static_assert(std::is_base_of_v, "view_interface_t::create can be used only when T is a view type!"); auto view = tracking_allocator_t::get().allocate(args...); view->base_initialization(); return view; } protected: view_interface_t(); void set_surface_root_node(scene::floating_inner_ptr surface_root_node); /** * Initialize the base implementation of a view, for example the node hierarchy. */ void base_initialization(); /** * Emit the view map signal. It indicates that a view has been mapped, i.e. * plugins can now "work" with it. Note that not all views will emit the map * event. */ virtual void emit_view_map(); /** * Emit the view unmap signal. It indicates that the view is in the process of * being destroyed. Most plugins should stop any actions they have on the view. */ virtual void emit_view_unmap(); /** * Emit the view pre-unmap signal. It is emitted right before the view * destruction start. At this moment a plugin can still take a snapshot of the * view. Note that not all views emit the pre-unmap signal, however the unmap * signal is mandatory for all views. */ virtual void emit_view_pre_unmap(); }; wayfire_view wl_surface_to_wayfire_view(wl_resource *surface); /** * Find a view this node belongs to. * May return NULL if @node is NULL or it is not a child of a view node. */ wayfire_view node_to_view(wf::scene::node_ptr node); wayfire_view node_to_view(wf::scene::node_t *node); /** * A base class for nodes which are to be identified as views. * Used by @node_to_view in order to figure out whether a given node is a view or not. */ class view_node_tag_t { public: view_node_tag_t(wayfire_view view) { this->view = view; } virtual ~view_node_tag_t() = default; wayfire_view get_view() const { return view; } private: wayfire_view view; }; } #endif wayfire-0.8.1/src/api/wayfire/window-manager.hpp000066400000000000000000000061651457431457600216540ustar00rootroot00000000000000#pragma once #include "wayfire/core.hpp" #include "wayfire/geometry.hpp" namespace wf { /** * An interface which describes basic window management operations on toplevels. */ class window_manager_t { public: virtual ~window_manager_t() = default; /** * Update the remembered last windowed geometry. * * When a view is being tiled or fullscreened, we usually want to remember its size and position so that * it can be restored to that geometry after unfullscreening/untiling. window-manager implementations keep * track of this when a plugin calls update_last_windowed_geometry(). */ virtual void update_last_windowed_geometry(wayfire_toplevel_view view); /** * Get the stored last_windowed_geometry, if it was stored at all. */ virtual std::optional get_last_windowed_geometry(wayfire_toplevel_view view); /** * Mark the view as (un)grabbed. * While a view is grabbed, its last windowed geometry will not be updated. */ virtual void set_view_grabbed(wayfire_toplevel_view view, bool grabbed); /** * Request that an interactive move starts for the given view. */ virtual void move_request(wayfire_toplevel_view view); /** * Request that an interactive resize starts for the given view. */ virtual void resize_request(wayfire_toplevel_view view, uint32_t edges = 0); /** * Try to focus the view and its output. * This will first emit a focus_request signal for the view, and if it is not handled by any plugin, the * default focus actions will be taken (i.e @focus_raise_view(allow_switch_ws=true) will be called). */ virtual void focus_request(wayfire_view view, bool self_request = false); /** * Focus the view and take any actions necessary to make it visible: * - Unminimize minized views * - Switch to the view's workspace, if @allow_switch_ws is set. * - Raise the view to the top of the stack. */ virtual void focus_raise_view(wayfire_view view, bool allow_switch_ws = false); /** Request that the view is (un)minimized */ virtual void minimize_request(wayfire_toplevel_view view, bool minimized); /** * Request that the view is (un)tiled on the given workspace of its primary output. * * Note: by default, any tiled edges means that the view gets the full workarea. * * @param ws If no workspace is provided, the view will be tiled on the current workspace. Otherwise, * the view will be tiled on the provided workspace. */ virtual void tile_request(wayfire_toplevel_view view, uint32_t tiled_edges, std::optional ws = {}); /** * Request that the view is (un)fullscreened on the given workspace of its primary output. * * @param ws If no workspace is provided, the view will be fullscreened or restored to the current * workspace of its primary output. Otherwise, the operation will be done for the given workspace. */ virtual void fullscreen_request(wayfire_toplevel_view view, wf::output_t *output, bool state, std::optional ws = {}); }; } wayfire-0.8.1/src/api/wayfire/workarea.hpp000066400000000000000000000044701457431457600205450ustar00rootroot00000000000000#pragma once #include #include #include namespace wf { /** * Each output has a workarea manager which keeps track of the available workarea on that output. The * available area is typically the full output area minus any space reserved for panels, bars, etc. */ class output_workarea_manager_t { public: /** * Special clients like panels can reserve place from an edge of the output. * It is used when calculating the dimensions of maximized/tiled windows and * others. The remaining space (which isn't reserved for panels) is called * the workarea. */ enum anchored_edge { ANCHORED_EDGE_TOP = 0, ANCHORED_EDGE_BOTTOM = 1, ANCHORED_EDGE_LEFT = 2, ANCHORED_EDGE_RIGHT = 3, }; struct anchored_area { // The edge from which to reserve area. anchored_edge edge; // Amount of space to reserve. int reserved_size; // The reflowed callback is optional and when present, is called every time the anchored areas are // reflowed (e.g. anchored areas are recalculated). The passed geometry is the available workarea // before the view's own request was considered. That means, for the first anchored area in the // workarea manager, the geometry will be the full output's geometry. For each subsequent anchored // area, the size of the previous anchored areas is excluded from the passed available workarea. std::function reflowed; }; /** * Add a reserved area. The actual recalculation must be manually * triggered by calling reflow_reserved_areas() */ void add_reserved_area(anchored_area *area); /** * Remove a reserved area. The actual recalculation must be manually * triggered by calling reflow_reserved_areas() */ void remove_reserved_area(anchored_area *area); /** * Recalculate reserved area for each anchored area */ void reflow_reserved_areas(); /** * @return The free space of the output after reserving the space for panels */ wf::geometry_t get_workarea(); output_workarea_manager_t(wf::output_t *output); ~output_workarea_manager_t(); private: struct impl; std::unique_ptr priv; }; } wayfire-0.8.1/src/api/wayfire/workspace-set.hpp000066400000000000000000000216421457431457600215210ustar00rootroot00000000000000#ifndef WORKSPACE_MANAGER_HPP #define WORKSPACE_MANAGER_HPP #include "wayfire/geometry.hpp" #include "wayfire/object.hpp" #include "wayfire/output.hpp" #include "wayfire/signal-provider.hpp" #include #include #include #include #include namespace wf { class workspace_set_t; /** * on: workspace set * when: Whenever the workspace set is attached to a new (including nullptr) output. */ struct workspace_set_attached_signal { wf::workspace_set_t *set; wf::output_t *old_output; }; /** * A set of flags that can be ORed and used as flags for the workspace set's get_view() function. */ enum wset_view_flags { // Include mapped views only. WSET_MAPPED_ONLY = (1 << 0), // Exclude minimized views, they are included by default. WSET_EXCLUDE_MINIMIZED = (1 << 1), // Views on the current workspace only, a shorthand for requesting the current workspace and supplying it // as the second filter of get_views(). WSET_CURRENT_WORKSPACE = (1 << 2), // Sort the resulting array in the same order as the scenegraph nodes of the corresponding views. // Views not attached to the scenegraph (wf::get_core().scene()) are not included in the answer. // This operation may be slow, so it should not be used on hot paths. WSET_SORT_STACKING = (1 << 3), }; /** * Workspace manager is responsible for managing the layers, the workspaces and * the views in them. There is one workspace manager per output. * * In the default workspace_manager implementation, there is one set of layers * per output. Each layer is infinite and covers all workspaces. * * Each output also has a set of workspaces, arranged in a 2D grid. A view may * overlap multiple workspaces. */ class workspace_set_t : public wf::signal::provider_t, public wf::object_base_t, public std::enable_shared_from_this { public: /** * Create a new empty workspace set. By default, the workspace set uses the core/{vwidth,vheight} options * to determine the workspace grid dimensions and is not attached to any outputs. * * When first created, the workspace set is invisible. It may become visible when it is set as the current * workspace set on an output. * * @param index The index of the new workspace set. It will be used if available, otherwise, a the lowest * available index will be selected (starting from 1). */ static std::shared_ptr create(int64_t index = -1); ~workspace_set_t(); /** * Generate a list of all workspace sets currently allocated. */ static std::vector> get_all(); /** * Get the index of the workspace set. The index is assigned on creation and always the lowest unused * index is assigned to the new set. */ uint64_t get_index() const; /** * Attach the workspace set to the given output. * Note that this does not automatically make the workspace set visible on the output, it also needs to be * set as the current workspace set on it. * * @param output The new output to attach to, or nullptr. */ void attach_to_output(wf::output_t *output); /** * Get the currently attached output, or null. */ wf::output_t *get_attached_output(); /** * Get the output geometry of the last attached output. */ std::optional get_last_output_geometry(); /** * Get the scenegraph node belonging to the workspace set. * Each workspace set has one scenegraph node which is put in the workspace layer and contains most of * the views from the workspace set. It is nonetheless possible to add views which are placed elsewhere * in the scenegraph (for example, on a different layer). */ scene::floating_inner_ptr get_node() const; /** * Add the given view to the workspace set. * Until the view is removed, it will be counted as part of the workspace set. * This means that it will be moved when the workspace changes, and it will be part of the view list * returned by @get_views(). * * The workspace set is also responsible for associating the view with an output, in case the workspace * set is moved to a different output. * * Note that adding a view to the workspace set does not automatically add the view to the scenegraph. * The stacking order, layer information, etc. is all determined by the scenegraph and managed separately * from the workspace set, which serves an organizational purpose. * * Note 2: Special care should be taken when adding views that are not part of the default scenegraph * node of the workspace set (i.e. @get_node()). Plugins adding these views have to ensure that the views * are disabled if the workspace set is not active on any output. */ void add_view(wayfire_toplevel_view view); /** * Remove the view from the workspace set. * Note that the view will remain associated with the last output the workspace set was on. */ void remove_view(wayfire_toplevel_view view); /** * Get a list of all views currently in the workspace set. * * Note that the list is not sorted by default (use WSET_SORT_STACKING if sorting is needed), and may * contain views from different scenegraph layers. * * @param flags A bit mask of wset_view_flags. See the individual enum values for detailed description. * @param workspace An optional workspace to filter views for (i.e. only views from that workspace will be * included in the return value. WSET_CURRENT_WORKSPACE takes higher precedence than this value if * specified. */ std::vector get_views(uint32_t flags = 0, std::optional workspace = {}); /** * Get the main workspace for a view. * The main workspace is the one which contains the view's center. * * If the center is on an invalid workspace, the closest workspace will be returned. */ wf::point_t get_view_main_workspace(wayfire_toplevel_view view); /** * Check if the given view is visible on the given workspace */ bool view_visible_on(wayfire_toplevel_view view, wf::point_t ws); /** * Ensure that the view's wm_geometry is visible on the workspace ws. This * involves moving the view as appropriate. */ void move_to_workspace(wayfire_toplevel_view view, wf::point_t ws); /** * Directly change the active workspace. * * @param ws The new active workspace. * @param fixed_views Views which do not change their workspace relative * to the current workspace (together with their child views). Note that it * may result in views getting offscreen if they are not visible on the * current workspace. */ void set_workspace(wf::point_t ws, const std::vector& fixed_views = {}); /** * Switch to the given workspace. * If possible, use a plugin which provides animation. * * @param ws The new active workspace. * @param fixed_views Views which do not change their workspace relative * to the current workspace (together with their child views). See also * workspace-change-request-signal. */ void request_workspace(wf::point_t ws, const std::vector& fixed_views = {}); /** * @return The given workspace */ wf::point_t get_current_workspace(); /** * @return The number of workspace columns and rows */ wf::dimensions_t get_workspace_grid_size(); /** * Set the workspace grid size for this output. * * Once a plugin calls this, the number of workspaces will no longer be * updated according to the config file. */ void set_workspace_grid_size(wf::dimensions_t grid_size); /** * @return Whether the given workspace is valid */ bool is_workspace_valid(wf::point_t ws); private: struct impl; std::unique_ptr pimpl; friend class output_impl_t; friend class wf::tracking_allocator_t; workspace_set_t(int64_t index = -1); /** * Change the visibility of the workspace set. On each output, only one workspace set will be visible * (the current workspace set). When a workspace set is invisible, views in it will be disabled in the * scenegraph. */ void set_visible(bool visible); }; // A helper function to emit view-pre-moved-to-wset void emit_view_pre_moved_to_wset_pre(wayfire_toplevel_view view, std::shared_ptr old_wset, std::shared_ptr new_wset); // A helper function to emit view-moved-to-wset void emit_view_moved_to_wset(wayfire_toplevel_view view, std::shared_ptr old_wset, std::shared_ptr new_wset); } #endif /* end of include guard: WORKSPACE_MANAGER_HPP */ wayfire-0.8.1/src/api/wayfire/workspace-stream.hpp000066400000000000000000000022111457431457600222100ustar00rootroot00000000000000#pragma once #include "wayfire/scene-render.hpp" #include "wayfire/scene.hpp" #include "wayfire/signal-provider.hpp" #include #include #include namespace wf { /** * A workspace stream is a special node which displays a workspace of an output. */ class workspace_stream_node_t : public scene::node_t { public: workspace_stream_node_t(wf::output_t *output, wf::point_t workspace); // The color of the background of the workspace stream. // If not set, the default background color (specified in the config file) // of Wayfire is used. std::optional background; wf::output_t*const output; const wf::point_t ws; // node_t implementation public: std::string stringify() const override; void gen_render_instances(std::vector& instances, scene::damage_callback push_damage, wf::output_t *output) override; // The bounding box of a workspace stream is // (0, 0, output_width, output_height). wf::geometry_t get_bounding_box() override; class workspace_stream_instance_t; private: }; } wayfire-0.8.1/src/core/000077500000000000000000000000001457431457600147255ustar00rootroot00000000000000wayfire-0.8.1/src/core/core-impl.hpp000066400000000000000000000056331457431457600173340ustar00rootroot00000000000000#ifndef WF_CORE_CORE_IMPL_HPP #define WF_CORE_CORE_IMPL_HPP #include "core/plugin-loader.hpp" #include "wayfire/core.hpp" #include "wayfire/scene-input.hpp" #include "wayfire/scene.hpp" #include "wayfire/util.hpp" #include namespace wf { class seat_t; class input_manager_t; class input_method_relay; class compositor_core_impl_t : public compositor_core_t { public: wlr_egl *egl; wlr_compositor *compositor; std::unique_ptr input; std::unique_ptr im_relay; std::unique_ptr plugin_mgr; /** * Initialize the compositor core. * Called only by main(). */ virtual void init(); /** * Finish initialization of core after the backend has started. * Called only by main(). */ virtual void post_init(); void fini(); static compositor_core_impl_t& get(); static compositor_core_impl_t& allocate_core(); static void deallocate_core(); wlr_seat *get_current_seat() override; void warp_cursor(wf::pointf_t pos) override; void transfer_grab(wf::scene::node_ptr node) override; void set_cursor(std::string name) override; void unhide_cursor() override; void hide_cursor() override; wf::pointf_t get_cursor_position() override; wf::pointf_t get_touch_position(int id) override; const wf::touch::gesture_state_t& get_touch_state() override; wf::scene::node_ptr get_cursor_focus() override; wf::scene::node_ptr get_touch_focus() override; void add_touch_gesture( nonstd::observer_ptr gesture) override; void rem_touch_gesture( nonstd::observer_ptr gesture) override; std::vector> get_input_devices() override; virtual wlr_cursor *get_wlr_cursor() override; std::string get_xwayland_display() override; pid_t run(std::string command) override; void shutdown() override; compositor_state_t get_current_state() override; const std::shared_ptr& scene() final; compositor_core_impl_t(); virtual ~compositor_core_impl_t(); protected: wf::wl_listener_wrapper vkbd_created; wf::wl_listener_wrapper vptr_created; wf::wl_listener_wrapper input_inhibit_activated; wf::wl_listener_wrapper input_inhibit_deactivated; wf::wl_listener_wrapper pointer_constraint_added; wf::wl_listener_wrapper idle_inhibitor_created; wf::wl_listener_wrapper drm_lease_request; std::shared_ptr scene_root; compositor_state_t state = compositor_state_t::UNKNOWN; private: wf::option_wrapper_t discard_command_output; static std::unique_ptr static_core; }; compositor_core_impl_t& get_core_impl(); void priv_output_layout_fini(wf::output_layout_t *layout); } #endif /* end of include guard: WF_CORE_CORE_IMPL_HPP */ wayfire-0.8.1/src/core/core.cpp000066400000000000000000000422411457431457600163640ustar00rootroot00000000000000/* Needed for pipe2 */ #ifndef _GNU_SOURCE #define _GNU_SOURCE #include "wayfire/core.hpp" #endif #include #include "wayfire/scene.hpp" #include #include "wayfire/scene-operations.hpp" #include "wayfire/txn/transaction-manager.hpp" #include "wayfire/bindings-repository.hpp" #include "wayfire/util.hpp" #include #include "plugin-loader.hpp" #include "seat/tablet.hpp" #include "wayfire/touch/touch.hpp" #include "wayfire/view.hpp" #include #include #include #include #include #include #include #include #include #include #include #include "view/surface-impl.hpp" #include "wayfire/scene-input.hpp" #include "opengl-priv.hpp" #include "seat/input-manager.hpp" #include "seat/input-method-relay.hpp" #include "seat/touch.hpp" #include "seat/pointer.hpp" #include "seat/cursor.hpp" #include "../view/view-impl.hpp" #include "main.hpp" #include #include "core-impl.hpp" struct wf_pointer_constraint { wf::wl_listener_wrapper on_destroy; wf_pointer_constraint(wlr_pointer_constraint_v1 *constraint) { // set correct constraint auto& lpointer = wf::get_core_impl().seat->priv->lpointer; auto focus = lpointer->get_focus(); if (focus) { wf::node_recheck_constraints_signal data; focus->emit(&data); } } }; struct wlr_idle_inhibitor_t : public wf::idle_inhibitor_t { wf::wl_listener_wrapper on_destroy; wlr_idle_inhibitor_t(wlr_idle_inhibitor_v1 *wlri) { on_destroy.set_callback([&] (void*) { delete this; }); on_destroy.connect(&wlri->events.destroy); } }; void wf::compositor_core_impl_t::init() { this->scene_root = std::make_shared(); this->tx_manager = std::make_unique(); this->default_wm = std::make_unique(); wlr_renderer_init_wl_display(renderer, display); /* Order here is important: * 1. init_desktop_apis() must come after wlr_compositor_create(), * since Xwayland initialization depends on the compositor * 2. input depends on output-layout * 3. weston toy clients expect xdg-shell before wl_seat, i.e * init_desktop_apis() should come before input. * 4. GTK expects primary selection early. */ compositor = wlr_compositor_create(display, 6, renderer); /* Needed for subsurfaces */ wlr_subcompositor_create(display); protocols.data_device = wlr_data_device_manager_create(display); protocols.primary_selection_v1 = wlr_primary_selection_v1_device_manager_create(display); protocols.data_control = wlr_data_control_manager_v1_create(display); output_layout = std::make_unique(backend); init_desktop_apis(); /* Somehow GTK requires the tablet_v2 to be advertised pretty early */ protocols.tablet_v2 = wlr_tablet_v2_create(display); input = std::make_unique(); seat = std::make_unique(display, "default"); protocols.screencopy = wlr_screencopy_manager_v1_create(display); protocols.gamma_v1 = wlr_gamma_control_manager_v1_create(display); protocols.export_dmabuf = wlr_export_dmabuf_manager_v1_create(display); protocols.output_manager = wlr_xdg_output_manager_v1_create(display, output_layout->get_handle()); protocols.drm_v1 = wlr_drm_lease_v1_manager_create(display, backend); drm_lease_request.set_callback([&] (void *data) { auto req = static_cast(data); struct wlr_drm_lease_v1 *lease = wlr_drm_lease_request_v1_grant(req); if (!lease) { wlr_drm_lease_request_v1_reject(req); } }); if (protocols.drm_v1) { drm_lease_request.connect(&protocols.drm_v1->events.request); } else { LOGE("Failed to create wlr_drm_lease_device_v1; VR will not be available!"); } /* input-inhibit setup */ protocols.input_inhibit = wlr_input_inhibit_manager_create(display); input_inhibit_activated.set_callback([&] (void*) { input->set_exclusive_focus(protocols.input_inhibit->active_client); }); input_inhibit_activated.connect(&protocols.input_inhibit->events.activate); input_inhibit_deactivated.set_callback([&] (void*) { input->set_exclusive_focus(nullptr); }); input_inhibit_deactivated.connect(&protocols.input_inhibit->events.deactivate); /* idle-inhibit setup */ protocols.idle_notifier = wlr_idle_notifier_v1_create(display); protocols.idle_inhibit = wlr_idle_inhibit_v1_create(display); idle_inhibitor_created.set_callback([&] (void *data) { auto wlri = static_cast(data); /* will be freed by the destroy request */ new wlr_idle_inhibitor_t(wlri); }); idle_inhibitor_created.connect(&protocols.idle_inhibit->events.new_inhibitor); /* decoration_manager setup */ protocols.decorator_manager = wlr_server_decoration_manager_create(display); protocols.xdg_decorator = wlr_xdg_decoration_manager_v1_create(display); init_xdg_decoration_handlers(); protocols.vkbd_manager = wlr_virtual_keyboard_manager_v1_create(display); vkbd_created.set_callback([&] (void *data) { auto kbd = (wlr_virtual_keyboard_v1*)data; input->handle_new_input(&kbd->keyboard.base); }); vkbd_created.connect(&protocols.vkbd_manager->events.new_virtual_keyboard); protocols.vptr_manager = wlr_virtual_pointer_manager_v1_create(display); vptr_created.set_callback([&] (void *data) { auto event = (wlr_virtual_pointer_v1_new_pointer_event*)data; auto ptr = event->new_pointer; input->handle_new_input(&ptr->pointer.base); }); vptr_created.connect(&protocols.vptr_manager->events.new_virtual_pointer); protocols.pointer_gestures = wlr_pointer_gestures_v1_create(display); protocols.relative_pointer = wlr_relative_pointer_manager_v1_create(display); protocols.pointer_constraints = wlr_pointer_constraints_v1_create(display); pointer_constraint_added.set_callback([&] (void *data) { // will delete itself when the constraint is destroyed new wf_pointer_constraint((wlr_pointer_constraint_v1*)data); }); pointer_constraint_added.connect( &protocols.pointer_constraints->events.new_constraint); wf::option_wrapper_t enable_input_method_v2{"workarounds/enable_input_method_v2"}; if (enable_input_method_v2) { protocols.input_method = wlr_input_method_manager_v2_create(display); protocols.text_input = wlr_text_input_manager_v3_create(display); } im_relay = std::make_unique(); protocols.presentation = wlr_presentation_create(display, backend); protocols.viewporter = wlr_viewporter_create(display); protocols.foreign_registry = wlr_xdg_foreign_registry_create(display); protocols.foreign_v1 = wlr_xdg_foreign_v1_create(display, protocols.foreign_registry); protocols.foreign_v2 = wlr_xdg_foreign_v2_create(display, protocols.foreign_registry); wlr_fractional_scale_manager_v1_create(display, 1); wlr_single_pixel_buffer_manager_v1_create(display); this->bindings = std::make_unique(); image_io::init(); OpenGL::init(); this->state = compositor_state_t::START_BACKEND; } void wf::compositor_core_impl_t::post_init() { discard_command_output.load_option("workarounds/discard_command_output"); core_backend_started_signal backend_started_ev; this->emit(&backend_started_ev); this->state = compositor_state_t::RUNNING; plugin_mgr = std::make_unique(); // Move pointer to the middle of the leftmost, topmost output wf::pointf_t p; wf::output_t *wo = wf::get_core().output_layout->get_output_coords_at({FLT_MIN, FLT_MIN}, p); // Output might be noop but guaranteed to not be null wo->ensure_pointer(true); seat->focus_output(wo); // Refresh device mappings when we have all outputs and devices input->refresh_device_mappings(); // Start processing cursor events seat->priv->cursor->setup_listeners(); core_startup_finished_signal startup_ev; this->emit(&startup_ev); } void wf::compositor_core_impl_t::shutdown() { // We might get multiple signals in some scenarios. Shutdown only on the first instance. if (this->state != compositor_state_t::SHUTDOWN) { wl_display_terminate(wf::get_core().display); } } void wf::compositor_core_impl_t::fini() { this->state = compositor_state_t::SHUTDOWN; core_shutdown_signal ev; this->emit(&ev); LOGI("Unloading plugins..."); plugin_mgr.reset(); LOGI("Stopping clients..."); wl_display_destroy_clients(static_core->display); LOGI("Freeing resources..."); default_wm.reset(); bindings.reset(); scene_root.reset(); // General core stuff im_relay.reset(); seat.reset(); input.reset(); priv_output_layout_fini(output_layout.get()); output_layout.reset(); tx_manager.reset(); OpenGL::fini(); wl_display_destroy(static_core->display); } wf::compositor_state_t wf::compositor_core_impl_t::get_current_state() { return this->state; } wlr_seat*wf::compositor_core_impl_t::get_current_seat() { return seat->seat; } void wf::compositor_core_impl_t::set_cursor(std::string name) { seat->priv->cursor->set_cursor(name); } void wf::compositor_core_impl_t::unhide_cursor() { seat->priv->cursor->unhide_cursor(); } void wf::compositor_core_impl_t::hide_cursor() { seat->priv->cursor->hide_cursor(); } void wf::compositor_core_impl_t::warp_cursor(wf::pointf_t pos) { seat->priv->cursor->warp_cursor(pos); seat->priv->lpointer->update_cursor_position(get_current_time()); } void wf::compositor_core_impl_t::transfer_grab(wf::scene::node_ptr node) { seat->priv->transfer_grab(node); seat->priv->lpointer->transfer_grab(node); seat->priv->touch->transfer_grab(node); for (auto dev : this->get_input_devices()) { if (auto tablet = dynamic_cast(dev.get())) { for (auto& tool : tablet->tools_list) { tool->reset_grab(); } } } } wf::pointf_t wf::compositor_core_impl_t::get_cursor_position() { if (seat->priv->cursor) { return seat->priv->cursor->get_cursor_position(); } else { return {invalid_coordinate, invalid_coordinate}; } } wf::pointf_t wf::compositor_core_impl_t::get_touch_position(int id) { const auto& state = seat->priv->touch->get_state(); auto it = state.fingers.find(id); if (it != state.fingers.end()) { return {it->second.current.x, it->second.current.y}; } return {invalid_coordinate, invalid_coordinate}; } const wf::touch::gesture_state_t& wf::compositor_core_impl_t::get_touch_state() { return seat->priv->touch->get_state(); } wf::scene::node_ptr wf::compositor_core_impl_t::get_cursor_focus() { return seat->priv->lpointer->get_focus(); } wayfire_view wf::compositor_core_t::get_cursor_focus_view() { return node_to_view(get_cursor_focus()); } wayfire_view wf::compositor_core_t::get_view_at(wf::pointf_t point) { auto isec = scene()->find_node_at(point); return isec ? node_to_view(isec->node->shared_from_this()) : nullptr; } wf::scene::node_ptr wf::compositor_core_impl_t::get_touch_focus() { return seat->priv->touch->get_focus(); } wayfire_view wf::compositor_core_t::get_touch_focus_view() { return node_to_view(get_touch_focus()); } void wf::compositor_core_impl_t::add_touch_gesture( nonstd::observer_ptr gesture) { seat->priv->touch->add_touch_gesture(gesture); } void wf::compositor_core_impl_t::rem_touch_gesture( nonstd::observer_ptr gesture) { seat->priv->touch->rem_touch_gesture(gesture); } std::vector> wf::compositor_core_impl_t::get_input_devices() { std::vector> list; for (auto& dev : input->input_devices) { list.push_back(nonstd::make_observer(dev.get())); } return list; } wlr_cursor*wf::compositor_core_impl_t::get_wlr_cursor() { return seat->priv->cursor->cursor; } std::vector wf::compositor_core_t::get_all_views() { return wf::tracking_allocator_t::get().get_all(); } pid_t wf::compositor_core_impl_t::run(std::string command) { static constexpr size_t READ_END = 0; static constexpr size_t WRITE_END = 1; pid_t pid; int pipe_fd[2]; pipe2(pipe_fd, O_CLOEXEC); /* The following is a "hack" for disowning the child processes, * otherwise they will simply stay as zombie processes */ pid = fork(); if (!pid) { pid = fork(); if (!pid) { close(pipe_fd[READ_END]); close(pipe_fd[WRITE_END]); setenv("_JAVA_AWT_WM_NONREPARENTING", "1", 1); setenv("WAYLAND_DISPLAY", wayland_display.c_str(), 1); #if WF_HAS_XWAYLAND if (!xwayland_get_display().empty()) { setenv("DISPLAY", xwayland_get_display().c_str(), 1); } #endif if (discard_command_output) { int dev_null = open("/dev/null", O_WRONLY); dup2(dev_null, 1); dup2(dev_null, 2); close(dev_null); } _exit(execl("/bin/sh", "/bin/sh", "-c", command.c_str(), NULL)); } else { close(pipe_fd[READ_END]); write(pipe_fd[WRITE_END], (void*)(&pid), sizeof(pid)); close(pipe_fd[WRITE_END]); _exit(0); } } else { close(pipe_fd[WRITE_END]); int status; waitpid(pid, &status, 0); pid_t child_pid; read(pipe_fd[READ_END], &child_pid, sizeof(child_pid)); close(pipe_fd[READ_END]); return child_pid; } } std::string wf::compositor_core_impl_t::get_xwayland_display() { return xwayland_get_display(); } void wf::move_view_to_output(wayfire_toplevel_view v, wf::output_t *new_output, bool reconfigure) { auto old_output = v->get_output(); auto old_wset = v->get_wset(); emit_view_pre_moved_to_wset_pre(v, old_wset, new_output->wset()); uint32_t edges; bool fullscreen; wf::geometry_t view_g; wf::geometry_t old_output_g; wf::geometry_t new_output_g; if (reconfigure) { edges = v->pending_tiled_edges(); fullscreen = v->pending_fullscreen(); view_g = v->get_pending_geometry(); old_output_g = old_output->get_relative_geometry(); new_output_g = new_output->get_relative_geometry(); auto ratio_x = (double)new_output_g.width / old_output_g.width; auto ratio_y = (double)new_output_g.height / old_output_g.height; view_g.x *= ratio_x; view_g.y *= ratio_y; } assert(new_output); if (old_output) { old_output->wset()->remove_view(v); wf::scene::remove_child(v->get_root_node()); } wf::scene::add_front(new_output->wset()->get_node(), v->get_root_node()); new_output->wset()->add_view(v); if (new_output == wf::get_core().seat->get_active_output()) { wf::get_core().seat->focus_view(v); } if (reconfigure) { if (fullscreen) { wf::get_core().default_wm->fullscreen_request(v, new_output, true); } else if (edges) { wf::get_core().default_wm->tile_request(v, edges); } else { auto new_g = wf::clamp(view_g, new_output->workarea->get_workarea()); v->set_geometry(new_g); } } emit_view_moved_to_wset(v, old_wset, new_output->wset()); } const std::shared_ptr& wf::compositor_core_impl_t::scene() { return scene_root; } wf::compositor_core_t::compositor_core_t() {} wf::compositor_core_t::~compositor_core_t() {} wf::compositor_core_impl_t::compositor_core_impl_t() {} wf::compositor_core_impl_t::~compositor_core_impl_t() { input.reset(); output_layout.reset(); } wf::compositor_core_t& wf::compositor_core_t::get() { return wf::compositor_core_impl_t::get(); } wf::compositor_core_t& wf::get_core() { return wf::compositor_core_t::get(); } wf::compositor_core_impl_t& wf::get_core_impl() { return wf::compositor_core_impl_t::get(); } wf::compositor_core_impl_t& wf::compositor_core_impl_t::allocate_core() { wf::dassert(!static_core, "Core already allocated"); static_core = std::make_unique(); return *static_core; } void wf::compositor_core_impl_t::deallocate_core() { static_core->fini(); static_core.reset(); } wf::compositor_core_impl_t& wf::compositor_core_impl_t::get() { return *static_core; } std::unique_ptr wf::compositor_core_impl_t::static_core; // TODO: move this to a better location wf_runtime_config runtime_config; wayfire-0.8.1/src/core/gldebug.hpp000066400000000000000000000044361457431457600170560ustar00rootroot00000000000000#include #include const char *getStrSrc(GLenum src) { if (src == GL_DEBUG_SOURCE_API) { return "API"; } if (src == GL_DEBUG_SOURCE_WINDOW_SYSTEM) { return "WINDOW_SYSTEM"; } if (src == GL_DEBUG_SOURCE_SHADER_COMPILER) { return "SHADER_COMPILER"; } if (src == GL_DEBUG_SOURCE_THIRD_PARTY) { return "THIRD_PARTYB"; } if (src == GL_DEBUG_SOURCE_APPLICATION) { return "APPLICATIONB"; } if (src == GL_DEBUG_SOURCE_OTHER) { return "OTHER"; } else { return "UNKNOWN"; } } const char *getStrType(GLenum type) { if (type == GL_DEBUG_TYPE_ERROR) { return "ERROR"; } if (type == GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR) { return "DEPRECATED_BEHAVIOR"; } if (type == GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR) { return "UNDEFINED_BEHAVIOR"; } if (type == GL_DEBUG_TYPE_PORTABILITY) { return "PORTABILITY"; } if (type == GL_DEBUG_TYPE_PERFORMANCE) { return "PERFORMANCE"; } if (type == GL_DEBUG_TYPE_OTHER) { return "OTHER"; } return "UNKNOWN"; } const char *getStrSeverity(GLenum severity) { if (severity == GL_DEBUG_SEVERITY_HIGH) { return "HIGH"; } if (severity == GL_DEBUG_SEVERITY_MEDIUM) { return "MEDIUM"; } if (severity == GL_DEBUG_SEVERITY_LOW) { return "LOW"; } if (severity == GL_DEBUG_SEVERITY_NOTIFICATION) { return "NOTIFICATION"; } return "UNKNOWN"; } void errorHandler(GLenum src, GLenum type, GLuint id, GLenum severity, GLsizei len, const GLchar *msg, const void *dummy) { // ignore notifications if (severity == GL_DEBUG_SEVERITY_NOTIFICATION) { return; } LOGI( "_______________________________________________\n", "Source: ", getStrSrc(src), "\n", "Type: ", getStrType(type), "\n", "Severity: ", getStrSeverity(severity), "\n", "Msg: ", msg, "\n", "_______________________________________________\n"); } void enable_gl_synchronous_debug() { glEnable(GL_DEBUG_OUTPUT); glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS); glDebugMessageCallback(errorHandler, 0); } wayfire-0.8.1/src/core/idle.cpp000066400000000000000000000014421457431457600163470ustar00rootroot00000000000000#include #include #include "core/seat/input-manager.hpp" #include "core-impl.hpp" unsigned int wf::idle_inhibitor_t::inhibitors = 0; void wf::idle_inhibitor_t::notify_update() { /* NOTE: inhibited -> NOT enabled */ wlr_idle_notifier_v1_set_inhibited(wf::get_core().protocols.idle_notifier, inhibitors != 0); wf::idle_inhibit_changed_signal data; data.inhibit = (inhibitors != 0); wf::get_core().emit(&data); } wf::idle_inhibitor_t::idle_inhibitor_t() { LOGD("creating idle inhibitor ", this, " previous count: ", inhibitors); inhibitors++; notify_update(); } wf::idle_inhibitor_t::~idle_inhibitor_t() { LOGD("destroying idle inhibitor ", this, " previous count: ", inhibitors); inhibitors--; notify_update(); } wayfire-0.8.1/src/core/img.cpp000066400000000000000000000241251457431457600162110ustar00rootroot00000000000000#include #include #include "wayfire/img.hpp" #include "wayfire/opengl.hpp" #include #ifdef BUILD_WITH_IMAGEIO #include #include #include #endif #include #include #include #include #include #include #define TEXTURE_LOAD_ERROR 0 namespace image_io { using Loader = std::function; using Writer = std::function; namespace { std::unordered_map loaders; std::unordered_map writers; } bool load_data_as_cubemap(unsigned char *data, int width, int height, int channels) { width /= 4; height /= 3; int x, y, t; if (width != height) { LOGE("cubemap width / 4(", width, ") != height / 3(", height, ")"); return false; } /* * CUBEMAP IMAGE FORMAT * * 0 1 2 3 * _____________________ * 0 | X | T | X | X | * |____|____|____|____| * 1 | R | F | L | BA | * |____|____|____|____| * 2 | X | BO | X | X | * |____|____|____|____| * * WIDTH / 4 == HEIGHT / 3 * * X : UNUSED * T: TOP * R: RIGHT * F: FRONT * L: LEFT * BA: BACK * BO: BOTTOM * */ for (t = GL_TEXTURE_CUBE_MAP_POSITIVE_X; t < GL_TEXTURE_CUBE_MAP_POSITIVE_X + 6; t++) { switch (t) { case GL_TEXTURE_CUBE_MAP_POSITIVE_X: x = 2, y = 1; break; case GL_TEXTURE_CUBE_MAP_NEGATIVE_X: x = 0, y = 1; break; case GL_TEXTURE_CUBE_MAP_POSITIVE_Y: x = 1, y = 0; break; case GL_TEXTURE_CUBE_MAP_NEGATIVE_Y: x = 1, y = 2; break; case GL_TEXTURE_CUBE_MAP_POSITIVE_Z: x = 1, y = 1; break; case GL_TEXTURE_CUBE_MAP_NEGATIVE_Z: x = 3, y = 1; break; default: return false; break; } auto format = (channels == 4 ? GL_RGBA : GL_RGB); GL_CALL(glPixelStorei(GL_UNPACK_ROW_LENGTH, width * 4)); GL_CALL(glPixelStorei(GL_UNPACK_SKIP_ROWS, y * height)); GL_CALL(glPixelStorei(GL_UNPACK_SKIP_PIXELS, x * width)); GL_CALL(glTexImage2D(t, 0, format, width, height, 0, format, GL_UNSIGNED_BYTE, data)); } GL_CALL(glPixelStorei(GL_UNPACK_ROW_LENGTH, 0)); GL_CALL(glPixelStorei(GL_UNPACK_SKIP_ROWS, 0)); GL_CALL(glPixelStorei(GL_UNPACK_SKIP_PIXELS, 0)); return true; } #ifdef BUILD_WITH_IMAGEIO /* All backend functions are taken from the internet. * If you want to be credited, contact me */ bool texture_from_png(const char *filename, GLuint target) { FILE *fp = fopen(filename, "rb"); int width, height; png_byte color_type; png_byte bit_depth; png_bytep *row_pointers; png_structp png = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); if (!png) { fclose(fp); return false; } png_infop infos = png_create_info_struct(png); if (!infos) { fclose(fp); return false; } if (setjmp(png_jmpbuf(png))) { fclose(fp); return false; } png_init_io(png, fp); png_read_info(png, infos); width = png_get_image_width(png, infos); height = png_get_image_height(png, infos); color_type = png_get_color_type(png, infos); bit_depth = png_get_bit_depth(png, infos); if (bit_depth == 16) { png_set_strip_16(png); } if (color_type == PNG_COLOR_TYPE_PALETTE) { png_set_palette_to_rgb(png); } // PNG_COLOR_TYPE_GRAY_ALPHA is always 8 or 16bit depth. if ((color_type == PNG_COLOR_TYPE_GRAY) && (bit_depth < 8)) { png_set_expand_gray_1_2_4_to_8(png); } if (png_get_valid(png, infos, PNG_INFO_tRNS)) { png_set_tRNS_to_alpha(png); } // These color_type don't have an alpha channel then fill it with 0xff. if ((color_type == PNG_COLOR_TYPE_RGB) || (color_type == PNG_COLOR_TYPE_GRAY) || (color_type == PNG_COLOR_TYPE_PALETTE)) { png_set_filler(png, 0xFF, PNG_FILLER_AFTER); } if ((color_type == PNG_COLOR_TYPE_GRAY) || (color_type == PNG_COLOR_TYPE_GRAY_ALPHA)) { png_set_gray_to_rgb(png); } png_read_update_info(png, infos); row_pointers = new png_bytep[height]; png_byte *data = new png_byte[height * png_get_rowbytes(png, infos)]; for (int i = 0; i < height; i++) { row_pointers[i] = data + i * png_get_rowbytes(png, infos); } png_read_image(png, row_pointers); if (target == GL_TEXTURE_CUBE_MAP) { if (!load_data_as_cubemap(data, width, height, png_get_channels(png, infos))) { png_destroy_read_struct(&png, &infos, NULL); delete[] row_pointers; delete[] data; fclose(fp); return false; } } else if (target == GL_TEXTURE_2D) { GL_CALL(glTexImage2D(target, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, (GLvoid*)data)); } png_destroy_read_struct(&png, &infos, NULL); delete[] row_pointers; delete[] data; fclose(fp); return true; } void texture_to_png(const char *name, uint8_t *pixels, int w, int h, bool invert) { png_structp png = png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr); if (!png) { return; } png_infop infot = png_create_info_struct(png); if (!infot) { png_destroy_write_struct(&png, &infot); return; } FILE *fp = fopen(name, "wb"); if (!fp) { png_destroy_write_struct(&png, &infot); return; } png_init_io(png, fp); png_set_IHDR(png, infot, w, h, 8 /* depth */, PNG_COLOR_TYPE_RGBA, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE); png_colorp palette = (png_colorp)png_malloc(png, PNG_MAX_PALETTE_LENGTH * sizeof(png_color)); if (!palette) { fclose(fp); png_destroy_write_struct(&png, &infot); return; } png_set_PLTE(png, infot, palette, PNG_MAX_PALETTE_LENGTH); png_write_info(png, infot); png_set_packing(png); png_bytepp rows = (png_bytepp)png_malloc(png, h * sizeof(png_bytep)); for (int i = 0; i < h; ++i) { if (invert) { rows[i] = (png_bytep)(pixels + (h - i - 1) * w * 4); } else { rows[i] = (png_bytep)(pixels + i * w * 4); } } png_write_image(png, rows); png_write_end(png, infot); png_free(png, palette); png_destroy_write_struct(&png, &infot); fclose(fp); png_free(png, rows); } bool texture_from_jpeg(const char *FileName, GLuint target) { unsigned long data_size; unsigned char *rowptr[1]; unsigned char *jdata; struct jpeg_decompress_struct infot; struct jpeg_error_mgr err; std::FILE *file = fopen(FileName, "rb"); infot.err = jpeg_std_error(&err); jpeg_create_decompress(&infot); if (!file) { LOGE("failed to read JPEG file ", FileName); return false; } jpeg_stdio_src(&infot, file); jpeg_read_header(&infot, TRUE); jpeg_start_decompress(&infot); data_size = infot.output_width * infot.output_height * 3; jdata = new unsigned char [data_size]; while (infot.output_scanline < infot.output_height) { rowptr[0] = (unsigned char*)jdata + 3 * infot.output_width * infot.output_scanline; jpeg_read_scanlines(&infot, rowptr, 1); } jpeg_finish_decompress(&infot); GLint width = infot.output_width; GLint height = infot.output_height; if (target == GL_TEXTURE_CUBE_MAP) { if (!load_data_as_cubemap(jdata, width, height, 3)) { fclose(file); delete[] jdata; return false; } } else if (target == GL_TEXTURE_2D) { GL_CALL(glTexImage2D(target, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, jdata)); } fclose(file); delete[] jdata; return true; } #endif bool load_from_file(std::string name, GLuint target) { if (access(name.c_str(), F_OK) == -1) { if (!name.empty()) { LOGE(__func__, "() cannot access ", name); } return false; } int len = name.length(); if ((len < 4) || (name[len - 4] != '.')) { LOGE( "load_from_file() called with file without extension or with invalid extension!"); return false; } auto ext = name.substr(len - 3, 3); for (int i = 0; i < 3; i++) { ext[i] = std::tolower(ext[i]); } auto it = loaders.find(ext); if (it == loaders.end()) { LOGE("load_from_file() called with unsupported extension ", ext); return false; } else { return it->second(name.c_str(), target); } } void write_to_file(std::string name, uint8_t *pixels, int w, int h, std::string type, bool invert) { auto it = writers.find(type); if (it == writers.end()) { LOGE("unsupported image_writer backend"); } else { it->second(name.c_str(), pixels, w, h, invert); } } void write_to_file(std::string name, wf::framebuffer_t fb) { std::vector buffer(fb.viewport_width * fb.viewport_height * 4); OpenGL::render_begin(); GL_CALL(glBindFramebuffer(GL_READ_FRAMEBUFFER, fb.fb)); GL_CALL(glReadPixels(0, 0, fb.viewport_width, fb.viewport_height, GL_RGBA, GL_UNSIGNED_BYTE, buffer.data())); OpenGL::render_end(); write_to_file(name, (uint8_t*)buffer.data(), fb.viewport_width, fb.viewport_height, "png", false); } void init() { LOGD("init ImageIO"); #ifdef BUILD_WITH_IMAGEIO loaders["png"] = Loader(texture_from_png); loaders["jpg"] = Loader(texture_from_jpeg); writers["png"] = Writer(texture_to_png); #endif } } wayfire-0.8.1/src/core/matcher.cpp000066400000000000000000000060001457431457600170500ustar00rootroot00000000000000#include #include #include #include #include #include #include class wf::view_matcher_t::impl { public: std::shared_ptr> option; wf::lexer_t lexer; wf::condition_parser_t parser; std::shared_ptr condition; bool try_parse(const std::string& value, const std::string& opt_name) { lexer.reset(value); try { condition = parser.parse(lexer); return true; } catch (std::runtime_error& error) { LOGE("Failed to parse condition ", value, " from option ", opt_name); LOGE("Reason for the failure: ", error.what()); condition.reset(); } return false; } wf::config::option_base_t::updated_callback_t update_condition = [=] () { if (!try_parse(option->get_value(), option->get_name())) { if (option->get_value() != option->get_default_value()) { try_parse(option->get_default_value(), option->get_name() + "(default)"); } } }; void connect_updated_handler() { if (this->option) { this->option->add_updated_handler(&update_condition); } } void disconnect_updated_handler() { if (this->option) { this->option->rem_updated_handler(&update_condition); } } void set_option(std::shared_ptr> option) { disconnect_updated_handler(); this->option = option; if (option) { connect_updated_handler(); update_condition(); } } impl() = default; ~impl() { disconnect_updated_handler(); } impl(const impl &) = delete; impl(impl &&) = delete; impl& operator =(const impl&) = delete; impl& operator =(impl&&) = delete; }; wf::view_matcher_t::view_matcher_t() { this->priv = std::make_unique(); } wf::view_matcher_t::view_matcher_t( std::shared_ptr> option) : view_matcher_t() { this->priv->set_option(option); } wf::view_matcher_t::view_matcher_t(const std::string& option_name) : view_matcher_t() { wf::option_wrapper_t option{option_name}; this->set_from_option(option); } void wf::view_matcher_t::set_from_option( std::shared_ptr> option) { this->priv->set_option(option); } bool wf::view_matcher_t::matches(wayfire_view view) { if (this->priv->condition) { bool ignored = false; wf::view_access_interface_t access_interface{view}; return this->priv->condition->evaluate(access_interface, ignored); } return false; } wf::view_matcher_t::~view_matcher_t() = default; wayfire-0.8.1/src/core/object.cpp000066400000000000000000000033111457431457600166750ustar00rootroot00000000000000#include "wayfire/object.hpp" #include "wayfire/nonstd/safe-list.hpp" #include #include #include void wf::signal::connection_base_t::disconnect() { auto connected_copy = this->connected_to; for (auto& x : connected_copy) { x->disconnect(this); } } class wf::object_base_t::obase_impl { public: std::unordered_map> data; uint32_t object_id; }; wf::object_base_t::object_base_t() { this->obase_priv = std::make_unique(); static uint32_t global_id = 0; obase_priv->object_id = global_id++; } wf::object_base_t::~object_base_t() = default; std::string wf::object_base_t::to_string() const { return std::to_string(get_id()); } uint32_t wf::object_base_t::get_id() const { return obase_priv->object_id; } bool wf::object_base_t::has_data(std::string name) { return obase_priv->data[name] != nullptr; } void wf::object_base_t::erase_data(std::string name) { auto data = std::move(obase_priv->data[name]); obase_priv->data.erase(name); data.reset(); } wf::custom_data_t*wf::object_base_t::_fetch_data(std::string name) { auto it = obase_priv->data.find(name); if (it == obase_priv->data.end()) { return nullptr; } return it->second.get(); } wf::custom_data_t*wf::object_base_t::_fetch_erase(std::string name) { auto data = obase_priv->data[name].release(); erase_data(name); return data; } void wf::object_base_t::_store_data(std::unique_ptr data, std::string name) { obase_priv->data[name] = std::move(data); } void wf::object_base_t::_clear_data() { obase_priv->data.clear(); } wayfire-0.8.1/src/core/opengl-priv.hpp000066400000000000000000000010061457431457600176750ustar00rootroot00000000000000#ifndef WF_OPENGL_PRIV_HPP #define WF_OPENGL_PRIV_HPP #include #include namespace OpenGL { /** Initialize OpenGL helper functions */ void init(); /** Destroy the default GL program and resources */ void fini(); /** Indicate we have started repainting the given output */ void bind_output(wf::output_t *output, uint32_t fb); /** Indicate the output frame has been finished */ void unbind_output(wf::output_t *output); } #endif /* end of include guard: WF_OPENGL_PRIV_HPP */ wayfire-0.8.1/src/core/opengl.cpp000066400000000000000000000532171457431457600167250ustar00rootroot00000000000000#include #include #include "opengl-priv.hpp" #include "wayfire/geometry.hpp" #include "wayfire/output.hpp" #include "core-impl.hpp" #include "config.h" #include #include #include #include "shaders.tpp" #include "wayfire/region.hpp" const char *gl_error_string(const GLenum err) { switch (err) { case GL_INVALID_ENUM: return "GL_INVALID_ENUM"; case GL_INVALID_VALUE: return "GL_INVALID_VALUE"; case GL_INVALID_OPERATION: return "GL_INVALID_OPERATION"; case GL_OUT_OF_MEMORY: return "GL_OUT_OF_MEMORY"; } return "UNKNOWN GL ERROR"; } static bool disable_gl_call = false; void gl_call(const char *func, uint32_t line, const char *glfunc) { GLenum err; if (disable_gl_call || ((err = glGetError()) == GL_NO_ERROR)) { return; } LOGE("gles2: function ", glfunc, " in ", func, " line ", line, ": ", gl_error_string(err)); } namespace OpenGL { /* * Different Context is kept for each output * Each of the following functions uses the currently bound context */ program_t program, color_program; GLuint compile_shader(std::string source, GLuint type) { GLuint shader = GL_CALL(glCreateShader(type)); const char *c_src = source.c_str(); GL_CALL(glShaderSource(shader, 1, &c_src, NULL)); int s; #define LENGTH 1024 * 128 char b1[LENGTH]; GL_CALL(glCompileShader(shader)); GL_CALL(glGetShaderiv(shader, GL_COMPILE_STATUS, &s)); GL_CALL(glGetShaderInfoLog(shader, LENGTH, NULL, b1)); if (s == GL_FALSE) { LOGE("Failed to load shader:\n", source, "\nCompiler output:\n", b1); return -1; } return shader; } /* Create a very simple gl program from the given shader sources */ GLuint compile_program(std::string vertex_source, std::string frag_source) { auto vertex_shader = compile_shader(vertex_source, GL_VERTEX_SHADER); auto fragment_shader = compile_shader(frag_source, GL_FRAGMENT_SHADER); auto result_program = GL_CALL(glCreateProgram()); GL_CALL(glAttachShader(result_program, vertex_shader)); GL_CALL(glAttachShader(result_program, fragment_shader)); GL_CALL(glLinkProgram(result_program)); int s = GL_FALSE; #define LENGTH 1024 * 128 char log[LENGTH]; GL_CALL(glGetProgramiv(result_program, GL_LINK_STATUS, &s)); GL_CALL(glGetProgramInfoLog(result_program, LENGTH, NULL, log)); if (s == GL_FALSE) { LOGE("Failed to link vertex shader:\n", vertex_source, "\nFragment shader:\n", frag_source, "\nLinker output:\n", log); GL_CALL(glDeleteProgram(result_program)); } /* won't be really deleted until program is deleted as well */ GL_CALL(glDeleteShader(vertex_shader)); GL_CALL(glDeleteShader(fragment_shader)); return (s == GL_FALSE) ? 0 : result_program; } void init() { render_begin(); // enable_gl_synchronous_debug() program.compile(default_vertex_shader_source, default_fragment_shader_source); color_program.set_simple(compile_program(default_vertex_shader_source, color_rect_fragment_source)); render_end(); } void fini() { render_begin(); program.free_resources(); color_program.free_resources(); render_end(); } namespace { wf::output_t *current_output = NULL; uint32_t current_output_fb = 0; } void bind_output(wf::output_t *output, uint32_t fb) { current_output = output; current_output_fb = fb; } void unbind_output(wf::output_t *output) { current_output = NULL; current_output_fb = 0; } std::vector vertexData; std::vector coordData; void render_transformed_texture(wf::texture_t tex, const gl_geometry& g, const gl_geometry& texg, glm::mat4 model, glm::vec4 color, uint32_t bits) { // We don't expect any errors from us! disable_gl_call = true; program.use(tex.type); vertexData = { g.x1, g.y2, g.x2, g.y2, g.x2, g.y1, g.x1, g.y1, }; gl_geometry final_texg = (bits & TEXTURE_USE_TEX_GEOMETRY) ? texg : gl_geometry{0.0f, 0.0f, 1.0f, 1.0f}; if (bits & TEXTURE_TRANSFORM_INVERT_Y) { final_texg.y1 = 1.0 - final_texg.y1; final_texg.y2 = 1.0 - final_texg.y2; } if (bits & TEXTURE_TRANSFORM_INVERT_X) { final_texg.x1 = 1.0 - final_texg.x1; final_texg.x2 = 1.0 - final_texg.x2; } coordData = { final_texg.x1, final_texg.y1, final_texg.x2, final_texg.y1, final_texg.x2, final_texg.y2, final_texg.x1, final_texg.y2, }; program.set_active_texture(tex); program.attrib_pointer("position", 2, 0, vertexData.data()); program.attrib_pointer("uvPosition", 2, 0, coordData.data()); program.uniformMatrix4f("MVP", model); program.uniform4f("color", color); GL_CALL(glEnable(GL_BLEND)); GL_CALL(glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA)); if (bits & RENDER_FLAG_CACHED) { return; } draw_cached(); clear_cached(); } void draw_cached() { GL_CALL(glDrawArrays(GL_TRIANGLE_FAN, 0, 4)); } void clear_cached() { disable_gl_call = false; program.deactivate(); } void render_transformed_texture(wf::texture_t texture, const wf::geometry_t& geometry, glm::mat4 transform, glm::vec4 color, uint32_t bits) { bits &= ~TEXTURE_USE_TEX_GEOMETRY; gl_geometry gg; gg.x1 = geometry.x; gg.y1 = geometry.y; gg.x2 = gg.x1 + geometry.width; gg.y2 = gg.y1 + geometry.height; render_transformed_texture(texture, gg, {}, transform, color, bits); } void render_texture(wf::texture_t texture, const wf::render_target_t& framebuffer, const wf::geometry_t& geometry, glm::vec4 color, uint32_t bits) { render_transformed_texture(texture, geometry, framebuffer.get_orthographic_projection(), color, bits); } void render_rectangle(wf::geometry_t geometry, wf::color_t color, glm::mat4 matrix) { color_program.use(wf::TEXTURE_TYPE_RGBA); float x = geometry.x, y = geometry.y, w = geometry.width, h = geometry.height; GLfloat vertexData[] = { x, y + h, x + w, y + h, x + w, y, x, y, }; color_program.attrib_pointer("position", 2, 0, vertexData); color_program.uniformMatrix4f("MVP", matrix); color_program.uniform4f("color", {color.r, color.g, color.b, color.a}); GL_CALL(glEnable(GL_BLEND)); GL_CALL(glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA)); GL_CALL(glDrawArrays(GL_TRIANGLE_FAN, 0, 4)); color_program.deactivate(); } static bool egl_make_current(struct wlr_egl *egl) { if (!eglMakeCurrent(wlr_egl_get_display(egl), EGL_NO_SURFACE, EGL_NO_SURFACE, wlr_egl_get_context(egl))) { LOGE("eglMakeCurrent failed"); return false; } return true; } static bool egl_is_current(struct wlr_egl *egl) { return eglGetCurrentContext() == wlr_egl_get_context(egl); } void render_begin() { if (!egl_is_current(wf::get_core_impl().egl)) { egl_make_current(wf::get_core_impl().egl); } GL_CALL(glEnable(GL_BLEND)); GL_CALL(glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA)); } void render_begin(const wf::framebuffer_t& fb) { render_begin(); fb.bind(); } void render_begin(int32_t width, int32_t height, uint32_t fb) { render_begin(); GL_CALL(glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fb)); GL_CALL(glViewport(0, 0, width, height)); } void clear(wf::color_t col, uint32_t mask) { GL_CALL(glClearColor(col.r, col.g, col.b, col.a)); GL_CALL(glClear(mask)); } void render_end() { GL_CALL(glBindFramebuffer(GL_FRAMEBUFFER, current_output_fb)); GL_CALL(glDisable(GL_SCISSOR_TEST)); } } static std::string framebuffer_status_to_str( GLuint status) { switch (status) { case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT: return "incomplete attachment"; case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT: return "missing attachment"; case GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS: return "incomplete dimensions"; case GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE: return "incomplete multisample"; default: return "unknown"; } } bool wf::framebuffer_t::allocate(int width, int height) { bool first_allocate = false; if (fb == (uint32_t)-1) { first_allocate = true; GL_CALL(glGenFramebuffers(1, &fb)); } if (tex == (uint32_t)-1) { first_allocate = true; GL_CALL(glGenTextures(1, &tex)); GL_CALL(glBindTexture(GL_TEXTURE_2D, tex)); GL_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE)); GL_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE)); GL_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)); GL_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)); } bool is_resize = false; /* Special case: fb = 0. This occurs in the default workspace streams, we don't * resize anything */ if (fb != OpenGL::current_output_fb) { if (first_allocate || (width != viewport_width) || (height != viewport_height)) { is_resize = true; GL_CALL(glBindTexture(GL_TEXTURE_2D, tex)); GL_CALL(glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0)); } } if (first_allocate) { GL_CALL(glBindFramebuffer(GL_FRAMEBUFFER, fb)); GL_CALL(glBindTexture(GL_TEXTURE_2D, tex)); GL_CALL(glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex, 0)); auto status = GL_CALL(glCheckFramebufferStatus(GL_FRAMEBUFFER)); if (status != GL_FRAMEBUFFER_COMPLETE) { LOGE("Failed to initialize framebuffer: ", framebuffer_status_to_str(status)); return false; } } viewport_width = width; viewport_height = height; GL_CALL(glBindTexture(GL_TEXTURE_2D, 0)); GL_CALL(glBindFramebuffer(GL_FRAMEBUFFER, OpenGL::current_output_fb)); return is_resize || first_allocate; } void wf::framebuffer_t::bind() const { GL_CALL(glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fb)); GL_CALL(glViewport(0, 0, viewport_width, viewport_height)); } void wf::framebuffer_t::scissor(wlr_box box) const { GL_CALL(glEnable(GL_SCISSOR_TEST)); GL_CALL(glScissor(box.x, viewport_height - box.y - box.height, box.width, box.height)); } void wf::framebuffer_t::release() { if ((fb != uint32_t(-1)) && (fb != 0)) { GL_CALL(glDeleteFramebuffers(1, &fb)); } if ((tex != uint32_t(-1)) && ((fb != 0) || (tex != 0))) { GL_CALL(glDeleteTextures(1, &tex)); } reset(); } void wf::framebuffer_t::reset() { fb = -1; tex = -1; viewport_width = viewport_height = 0; } wlr_box wf::render_target_t::framebuffer_box_from_geometry_box(wlr_box box) const { /* Step 1: Make relative to the framebuffer */ box.x -= this->geometry.x; box.y -= this->geometry.y; /* Step 2: Apply scale to box */ wlr_box scaled = box * scale; /* Step 3: rotate */ int width = viewport_width, height = viewport_height; if (wl_transform & 1) { std::swap(width, height); } wlr_box result; wl_output_transform transform = wlr_output_transform_invert((wl_output_transform)wl_transform); wlr_box_transform(&result, &scaled, transform, width, height); if (subbuffer) { result = scale_box({0, 0, viewport_width, viewport_height}, subbuffer.value(), result); } return result; } wf::region_t wf::render_target_t::framebuffer_region_from_geometry_region(const wf::region_t& region) const { wf::region_t result; for (const auto& rect : region) { result |= framebuffer_box_from_geometry_box(wlr_box_from_pixman_box(rect)); } return result; } glm::mat4 wf::render_target_t::get_orthographic_projection() const { auto ortho = glm::ortho(1.0f * geometry.x, 1.0f * geometry.x + 1.0f * geometry.width, 1.0f * geometry.y + 1.0f * geometry.height, 1.0f * geometry.y); return gl_to_framebuffer() * ortho; } glm::mat4 wf::render_target_t::gl_to_framebuffer() const { if (subbuffer) { auto sub = subbuffer.value(); float scale_x = 1.0 * sub.width / viewport_width; float scale_y = 1.0 * sub.height / viewport_height; // Translation is calculated between the midpoint of the whole buffer // and the midpoint of the subbuffer, then scaled to NDC. float half_w = viewport_width / 2.0; float half_h = viewport_height / 2.0; float translate_x = ((sub.x + sub.width / 2.0) - half_w) / half_w; float translate_y = (half_h - sub.y - sub.height / 2.0) / half_h; glm::mat4 scale = glm::scale(glm::mat4(1.0), glm::vec3(scale_x, scale_y, 1.0)); glm::mat4 translate = glm::translate(glm::mat4(1.0), glm::vec3(translate_x, translate_y, 0.0)); return translate * scale * this->transform; } return this->transform; } void wf::render_target_t::logic_scissor(wlr_box box) const { scissor(framebuffer_box_from_geometry_box(box)); } wf::render_target_t wf::render_target_t::translated(wf::point_t offset) const { render_target_t copy = *this; copy.geometry = copy.geometry + offset; return copy; } /* look up the actual values of wl_output_transform enum * All _flipped transforms have values (regular_transform + 4) */ glm::mat4 get_output_matrix_from_transform(wl_output_transform transform) { glm::mat4 scale = glm::mat4(1.0); if (transform >= 4) { scale = glm::scale(scale, {-1, 1, 1}); } /* remove the third bit if it's set */ uint32_t rotation = transform & (~4); glm::mat4 rotation_matrix(1.0); if (rotation == WL_OUTPUT_TRANSFORM_90) { rotation_matrix = glm::rotate(rotation_matrix, glm::radians(90.0f), {0, 0, 1}); } if (rotation == WL_OUTPUT_TRANSFORM_180) { rotation_matrix = glm::rotate(rotation_matrix, glm::radians( 180.0f), {0, 0, 1}); } if (rotation == WL_OUTPUT_TRANSFORM_270) { rotation_matrix = glm::rotate(rotation_matrix, glm::radians( 270.0f), {0, 0, 1}); } return rotation_matrix * scale; } namespace wf { wf::texture_t::texture_t() {} wf::texture_t::texture_t(GLuint tex) { this->tex_id = tex; } wf::texture_t::texture_t(wlr_texture *texture, std::optional viewport) { assert(wlr_texture_is_gles2(texture)); wlr_gles2_texture_attribs attribs; wlr_gles2_texture_get_attribs(texture, &attribs); /* Wayfire works in inverted Y while wlroots doesn't, so we do invert here */ this->invert_y = true; this->target = attribs.target; this->tex_id = attribs.tex; if (this->target == GL_TEXTURE_2D) { this->type = attribs.has_alpha ? wf::TEXTURE_TYPE_RGBA : wf::TEXTURE_TYPE_RGBX; } else { this->type = wf::TEXTURE_TYPE_EXTERNAL; } if (viewport) { this->has_viewport = true; auto width = texture->width; auto height = texture->height; viewport_box.x1 = viewport->x / width; viewport_box.x2 = (viewport->x + viewport->width) / width; viewport_box.y1 = 1.0 - (viewport->y + viewport->height) / height; viewport_box.y2 = 1.0 - (viewport->y) / height; } } } // namespace wf namespace OpenGL { class program_t::impl { public: std::set active_attrs; std::set active_attrs_divisors; int active_program_idx = 0; int id[wf::TEXTURE_TYPE_ALL]; std::unordered_map uniforms[wf::TEXTURE_TYPE_ALL]; /** Find the uniform location for the currently bound program */ int find_uniform_loc(const std::string& name) { auto it = uniforms[active_program_idx].find(name); if (it != uniforms[active_program_idx].end()) { return it->second; } uniforms[active_program_idx][name] = GL_CALL(glGetUniformLocation(id[active_program_idx], name.c_str())); if (uniforms[active_program_idx][name] == -1) { LOGE("Uniform ", name, " not found in program"); } return uniforms[active_program_idx][name]; } std::map attribs[wf::TEXTURE_TYPE_ALL]; /** Find the attrib location for the currently bound program */ int find_attrib_loc(const std::string& name) { auto it = attribs[active_program_idx].find(name); if (it != attribs[active_program_idx].end()) { return it->second; } attribs[active_program_idx][name] = GL_CALL(glGetAttribLocation(id[active_program_idx], name.c_str())); return attribs[active_program_idx][name]; } }; program_t::program_t() { this->priv = std::make_unique(); for (int i = 0; i < wf::TEXTURE_TYPE_ALL; i++) { this->priv->id[i] = 0; } } void program_t::set_simple(GLuint program_id, wf::texture_type_t type) { free_resources(); assert(type < wf::TEXTURE_TYPE_ALL); this->priv->id[type] = program_id; } program_t::~program_t() {} static std::string replace_builtin_with(const std::string& source, const std::string& builtin, const std::string& with) { size_t pos = source.find(builtin); if (pos == std::string::npos) { return source; } return source.substr(0, pos) + with + source.substr(pos + builtin.length()); } static const std::string builtin = "@builtin@"; static const std::string builtin_ext = "@builtin_ext@"; struct texture_type_builtins { std::string builtin; std::string builtin_ext; }; std::map builtins = { {wf::TEXTURE_TYPE_RGBA, {builtin_rgba_source, ""}}, {wf::TEXTURE_TYPE_RGBX, {builtin_rgbx_source, ""}}, {wf::TEXTURE_TYPE_EXTERNAL, {builtin_external_source, builtin_ext_external_source}}, }; void program_t::compile(const std::string& vertex_source, const std::string& fragment_source) { free_resources(); for (const auto& program_type : builtins) { auto fragment = replace_builtin_with(fragment_source, builtin, program_type.second.builtin); fragment = replace_builtin_with(fragment, builtin_ext, program_type.second.builtin_ext); this->priv->id[program_type.first] = compile_program(vertex_source, fragment); } } void program_t::free_resources() { for (int i = 0; i < wf::TEXTURE_TYPE_ALL; i++) { if (this->priv->id[i]) { GL_CALL(glDeleteProgram(priv->id[i])); this->priv->id[i] = 0; } } } void program_t::use(wf::texture_type_t type) { if (priv->id[type] == 0) { throw std::runtime_error("program_t has no program for type " + std::to_string(type)); } GL_CALL(glUseProgram(priv->id[type])); priv->active_program_idx = type; } int program_t::get_program_id(wf::texture_type_t type) { return priv->id[type]; } void program_t::uniform1i(const std::string& name, int value) { int loc = priv->find_uniform_loc(name); GL_CALL(glUniform1i(loc, value)); } void program_t::uniform1f(const std::string& name, float value) { int loc = priv->find_uniform_loc(name); GL_CALL(glUniform1f(loc, value)); } void program_t::uniform2f(const std::string& name, float x, float y) { int loc = priv->find_uniform_loc(name); GL_CALL(glUniform2f(loc, x, y)); } void program_t::uniform3f(const std::string& name, float x, float y, float z) { int loc = priv->find_uniform_loc(name); GL_CALL(glUniform3f(loc, x, y, z)); } void program_t::uniform4f(const std::string& name, const glm::vec4& value) { int loc = priv->find_uniform_loc(name); GL_CALL(glUniform4f(loc, value.r, value.g, value.b, value.a)); } void program_t::uniformMatrix4f(const std::string& name, const glm::mat4& value) { int loc = priv->find_uniform_loc(name); GL_CALL(glUniformMatrix4fv(loc, 1, GL_FALSE, &value[0][0])); } void program_t::attrib_pointer(const std::string& attrib, int size, int stride, const void *ptr, GLenum type) { int loc = priv->find_attrib_loc(attrib); priv->active_attrs.insert(loc); GL_CALL(glEnableVertexAttribArray(loc)); GL_CALL(glVertexAttribPointer(loc, size, type, GL_FALSE, stride, ptr)); } void program_t::attrib_divisor(const std::string& attrib, int divisor) { int loc = priv->find_attrib_loc(attrib); priv->active_attrs_divisors.insert(loc); GL_CALL(glVertexAttribDivisor(loc, divisor)); } void program_t::set_active_texture(const wf::texture_t& texture) { GL_CALL(glActiveTexture(GL_TEXTURE0)); GL_CALL(glBindTexture(texture.target, texture.tex_id)); GL_CALL(glTexParameteri(texture.target, GL_TEXTURE_MIN_FILTER, GL_LINEAR)); glm::vec2 base{0.0f, 0.0f}; glm::vec2 scale{1.0f, 1.0f}; if (texture.has_viewport) { scale.x = texture.viewport_box.x2 - texture.viewport_box.x1; scale.y = texture.viewport_box.y2 - texture.viewport_box.y1; base.x = texture.viewport_box.x1; base.y = texture.viewport_box.y1; } if (texture.invert_y) { scale.y *= -1; base.y = 1.0 - base.y; } uniform2f("_wayfire_uv_base", base.x, base.y); uniform2f("_wayfire_uv_scale", scale.x, scale.y); } void program_t::deactivate() { for (int loc : priv->active_attrs_divisors) { GL_CALL(glVertexAttribDivisor(loc, 0)); } for (int loc : priv->active_attrs) { GL_CALL(glDisableVertexAttribArray(loc)); } priv->active_attrs_divisors.clear(); priv->active_attrs.clear(); GL_CALL(glUseProgram(0)); } } wayfire-0.8.1/src/core/output-layout.cpp000066400000000000000000001521651457431457600203160ustar00rootroot00000000000000#include "wayfire/output.hpp" #include "wayfire/core.hpp" #include "wayfire/output-layout.hpp" #include "wayfire/view.hpp" #include "wayfire/workspace-set.hpp" #include "wayfire/render-manager.hpp" #include "wayfire/signal-definitions.hpp" #include "wayfire/util.hpp" #include "../output/output-impl.hpp" #include #include #include #include #include #include #include #include static void*const WF_NOOP_OUTPUT_MAGIC = (void*)0x1234; static wl_output_transform get_transform_from_string(std::string transform) { if (transform == "normal") { return WL_OUTPUT_TRANSFORM_NORMAL; } else if (transform == "90") { return WL_OUTPUT_TRANSFORM_90; } else if (transform == "180") { return WL_OUTPUT_TRANSFORM_180; } else if (transform == "270") { return WL_OUTPUT_TRANSFORM_270; } else if (transform == "flipped") { return WL_OUTPUT_TRANSFORM_FLIPPED; } else if (transform == "180_flipped") { return WL_OUTPUT_TRANSFORM_FLIPPED_180; } else if (transform == "90_flipped") { return WL_OUTPUT_TRANSFORM_FLIPPED_90; } else if (transform == "270_flipped") { return WL_OUTPUT_TRANSFORM_FLIPPED_270; } LOGE("Bad output transform in config: ", transform); return WL_OUTPUT_TRANSFORM_NORMAL; } wlr_output_mode *find_matching_mode(wlr_output *output, const wlr_output_mode& reference) { wlr_output_mode *mode; wlr_output_mode *best = NULL; wl_list_for_each(mode, &output->modes, link) { if ((mode->width == reference.width) && (mode->height == reference.height)) { if (mode->refresh == reference.refresh) { return mode; } if (!best || (best->refresh < mode->refresh)) { best = mode; } } } return best; } // from rootston static bool parse_modeline(const char *modeline, drmModeModeInfo & mode) { char hsync[16]; char vsync[16]; char interlace[16]; interlace[0] = '\0'; float fclock; std::memset(&mode, 0, sizeof(mode)); mode.type = DRM_MODE_TYPE_USERDEF; if (sscanf(modeline, "%f %hd %hd %hd %hd %hd %hd %hd %hd %15s %15s %15s", &fclock, &mode.hdisplay, &mode.hsync_start, &mode.hsync_end, &mode.htotal, &mode.vdisplay, &mode.vsync_start, &mode.vsync_end, &mode.vtotal, hsync, vsync, interlace) < 11) { return false; } mode.clock = fclock * 1000; mode.vrefresh = mode.clock * 1000.0 * 1000.0 / mode.htotal / mode.vtotal; if (strcasecmp(hsync, "+hsync") == 0) { mode.flags |= DRM_MODE_FLAG_PHSYNC; } else if (strcasecmp(hsync, "-hsync") == 0) { mode.flags |= DRM_MODE_FLAG_NHSYNC; } else { return false; } if (strcasecmp(vsync, "+vsync") == 0) { mode.flags |= DRM_MODE_FLAG_PVSYNC; } else if (strcasecmp(vsync, "-vsync") == 0) { mode.flags |= DRM_MODE_FLAG_NVSYNC; } else { return false; } if (strcasecmp(interlace, "interlace") == 0) { mode.flags |= DRM_MODE_FLAG_INTERLACE; } snprintf(mode.name, sizeof(mode.name), "%dx%d@%d", mode.hdisplay, mode.vdisplay, mode.vrefresh / 1000); return true; } namespace wf { void transfer_views(wf::output_t *from, wf::output_t *to) { assert(from); LOGI("transfer views from ", from->handle->name, " -> ", to ? to->handle->name : "null"); // Step 1: move views from the current workspace set to the other output if (to) { /* If we aren't moving to another output, then there is no need to * enumerate views either */ auto views = from->wset()->get_views(WSET_SORT_STACKING); for (auto& view : views) { move_view_to_output(view, to, true); } } // Step 2: Ensure none of the remaining views have an invalid output. // Note that all views in workspace sets will have their output reassigned automatically by the // workspace-set impl. std::vector> non_ws_views; for (auto& view : wf::get_core().get_all_views()) { if ((view->get_output() == from) && (!toplevel_cast(view) || !toplevel_cast(view)->get_wset())) { // Take a ref, so that the view doesn't get destroyed while we're doing operations on the views non_ws_views.push_back(view->shared_from_this()); } } for (auto& view : non_ws_views) { if (view->role == VIEW_ROLE_DESKTOP_ENVIRONMENT) { // typically: layer-shell views that should be closed, since they are tied to a single output view->close(); view->set_output(nullptr); } else { // typically: xwayland OR views view->set_output(to); } } } bool output_state_t::operator ==(const output_state_t& other) const { if (source == OUTPUT_IMAGE_SOURCE_NONE) { return other.source == OUTPUT_IMAGE_SOURCE_NONE; } if (source == OUTPUT_IMAGE_SOURCE_MIRROR) { return other.source == OUTPUT_IMAGE_SOURCE_MIRROR && mirror_from == other.mirror_from; } bool eq = true; eq &= source == other.source; eq &= position == other.position; eq &= (mode.width == other.mode.width); eq &= (mode.height == other.mode.height); eq &= (mode.refresh == other.mode.refresh); eq &= (transform == other.transform); eq &= (scale == other.scale); eq &= (vrr == other.vrr); eq &= (depth == other.depth); return eq; } inline bool is_shutting_down() { return wf::get_core().get_current_state() == compositor_state_t::SHUTDOWN; } static const char *get_format_name(uint32_t format) { switch (format) { case DRM_FORMAT_XRGB2101010: return "DRM_FORMAT_XRGB2101010"; case DRM_FORMAT_XBGR2101010: return "DRM_FORMAT_XBGR2101010"; case DRM_FORMAT_XRGB8888: return "DRM_FORMAT_XRGB8888"; case DRM_FORMAT_INVALID: default: return "DRM_FORMAT_INVALID"; } } /** Represents a single output in the output layout */ struct output_layout_output_t { wlr_output *handle; output_state_t current_state; bool is_externally_managed = false; bool is_nested_compositor = false; bool inhibited = false; std::map> formats_for_depth; int current_bit_depth = RENDER_BIT_DEPTH_DEFAULT; std::unique_ptr output; wl_listener_wrapper on_destroy, on_commit; std::shared_ptr config_section; wf::option_wrapper_t mode_opt; wf::option_wrapper_t position_opt; wf::option_wrapper_t scale_opt; wf::option_wrapper_t transform_opt; wf::option_wrapper_t vrr_opt; wf::option_wrapper_t depth_opt; wf::option_wrapper_t use_ext_config{ "workarounds/use_external_output_configuration"}; void initialize_config_options() { config_section = wf::get_core().config_backend->get_output_section(handle); auto name = config_section->get_name(); mode_opt.load_option(name + "/mode"); position_opt.load_option(name + "/position"); scale_opt.load_option(name + "/scale"); transform_opt.load_option(name + "/transform"); vrr_opt.load_option(name + "/vrr"); depth_opt.load_option(name + "/depth"); } output_layout_output_t(wlr_output *handle) { this->handle = handle; on_destroy.connect(&handle->events.destroy); initialize_config_options(); is_nested_compositor = wlr_output_is_wl(handle); #if WLR_HAS_X11_BACKEND is_nested_compositor |= wlr_output_is_x11(handle); #endif if (is_nested_compositor) { /* Nested backends can be resized by the user. We need to handle * these cases */ on_commit.set_callback([=] (void *data) { wlr_output_event_commit *ev = static_cast(data); if (ev->state->committed & WLR_OUTPUT_STATE_MODE) { handle_mode_changed(); } }); on_commit.connect(&handle->events.commit); } formats_for_depth[8] = {DRM_FORMAT_XRGB8888}; formats_for_depth[10] = { DRM_FORMAT_XRGB2101010, DRM_FORMAT_XBGR2101010, DRM_FORMAT_XRGB8888, }; } /** * Update the current configuration based on the mode set by the * backend. */ void handle_mode_changed() { auto& lmanager = wf::get_core().output_layout; auto config = lmanager->get_current_configuration(); if (config.count(handle) && (config[handle].source == OUTPUT_IMAGE_SOURCE_SELF)) { if (output && (output->get_screen_size() != get_effective_size())) { /* mode changed. Apply new configuration. */ current_state.mode.width = handle->width; current_state.mode.height = handle->height; current_state.mode.refresh = handle->refresh; this->output->set_effective_size(get_effective_size()); this->output->render->damage_whole(); emit_configuration_changed(wf::OUTPUT_MODE_CHANGE); // Emit the output-layout-configuration-changed signal as well, which is usually emitted for // all changed outputs together in output-layout::apply_state(). However, resizing nested // backends does not use apply_state(), hence we have to emit the signal manually. output_layout_configuration_changed_signal ev; wf::get_core().output_layout->emit(&ev); } } } wlr_output_mode select_default_mode() { wlr_output_mode *mode; wl_list_for_each(mode, &handle->modes, link) { if (mode->preferred) { return *mode; } } /* Couldn't find a preferred mode. Just return the last, which is * usually also the "largest" */ wl_list_for_each_reverse(mode, &handle->modes, link) return *mode; /* Finally, if there isn't any mode (for ex. wayland backend), * try the wlr_output resolution, falling back to 1200x720 * if width or height is <= 0 */ wlr_output_mode default_mode; auto width = handle->width > 0 ? handle->width : 1200; auto height = handle->height > 0 ? handle->height : 720; auto refresh = handle->refresh > 0 ? handle->refresh : 0; default_mode.width = width; default_mode.height = height; default_mode.refresh = refresh; return default_mode; } /* Returns true if mode setting for the given output can succeed */ bool is_mode_supported(const wlr_output_mode& query) { /* DRM doesn't support setting a custom mode, so any supported mode * must be found in the mode list */ if (wlr_output_is_drm(handle)) { wlr_output_mode *mode; wl_list_for_each(mode, &handle->modes, link) { if ((mode->width == query.width) && (mode->height == query.height)) { return true; } } return false; } /* X11 and Wayland backends support setting custom modes */ return true; } /** * Determine whether the state in the config file should be ignored. */ bool should_ignore_config_state() { if (is_externally_managed && use_ext_config) { wf::output_config::mode_t mode = mode_opt; if (mode.get_type() == output_config::MODE_MIRROR) { // Special case: output mirroring // It is not supported directly supported by wlr-output-management // Thus, if the config file says to mirror an output, we do use that // information. return false; } return true; } return false; } /** * Load the state the output is configured with. * This is typically the config file, but in case of daemons like kanshi this * might be the external configuration. */ output_state_t load_configured_state() { // Ensure custom modes from the config are enabled // Also make sure to refresh them even if the output is externally // managed. refresh_custom_modes(); if (should_ignore_config_state()) { // Current state is what was requested by the client. return this->current_state; } output_state_t state; state.position = position_opt; wf::output_config::mode_t mode = mode_opt; wlr_output_mode tmp; LOGI("loaded mode ", ((wf::option_sptr_t)mode_opt)->get_value_str()); switch (mode.get_type()) { case output_config::MODE_AUTO: state.mode = select_default_mode(); state.source = OUTPUT_IMAGE_SOURCE_SELF; break; // fallthrough case output_config::MODE_RESOLUTION: tmp.width = mode.get_width(); tmp.height = mode.get_height(); tmp.refresh = mode.get_refresh(); state.mode = (is_mode_supported(tmp) ? tmp : select_default_mode()); state.source = OUTPUT_IMAGE_SOURCE_SELF; break; case output_config::MODE_OFF: state.source = OUTPUT_IMAGE_SOURCE_NONE; return state; case output_config::MODE_MIRROR: state.source = OUTPUT_IMAGE_SOURCE_MIRROR; state.mode = select_default_mode(); state.mirror_from = mode.get_mirror_from(); break; } state.scale = scale_opt; state.transform = get_transform_from_string(transform_opt); state.vrr = vrr_opt; state.depth = depth_opt; return state; } void ensure_wayfire_output(const wf::dimensions_t& effective_size) { if (this->output) { this->output->set_effective_size(effective_size); return; } this->output = std::make_unique(handle, effective_size); auto wo = output.get(); /* Focus the first output, but do not change the focus on subsequently * added outputs. We also change the focus if the noop output was * focused */ wlr_output *focused = get_core().seat->get_active_output() ? get_core().seat->get_active_output()->handle : nullptr; if (!focused || (focused->data == WF_NOOP_OUTPUT_MAGIC)) { get_core().seat->focus_output(wo); } output_added_signal data; data.output = wo; get_core().output_layout->emit(&data); } void destroy_wayfire_output() { if (!this->output) { return; } LOGE("disabling output: ", output->handle->name); auto wo = output.get(); output_pre_remove_signal data; data.output = wo; wo->emit(&data); get_core().output_layout->emit(&data); wo->cancel_active_plugins(); bool shutdown = is_shutting_down(); if ((get_core().seat->get_active_output() == wo) && !shutdown) { get_core().seat->focus_output( get_core().output_layout->get_next_output(wo)); } else if (shutdown) { get_core().seat->focus_output(nullptr); } /* It doesn't make sense to transfer to another output if we're * going to shut down the compositor */ transfer_views(wo, shutdown ? nullptr : get_core().seat->get_active_output()); wf::output_removed_signal data2; data2.output = wo; get_core().output_layout->emit(&data2); this->output = nullptr; } std::unordered_set added_custom_modes; void add_custom_mode(std::string modeline) { if (added_custom_modes.count(modeline)) { return; } added_custom_modes.insert(modeline); drmModeModeInfo *mode = new drmModeModeInfo; if (!parse_modeline(modeline.c_str(), *mode)) { LOGE("invalid modeline ", modeline, " in config file"); return; } LOGD("output ", handle->name, ": adding custom mode ", mode->name); if (wlr_output_is_drm(handle)) { wlr_drm_connector_add_mode(handle, mode); } } void refresh_custom_modes() { auto section = wf::get_core().config_backend->get_output_section(handle); static const std::string custom_mode_prefix = "custom_mode"; for (auto& opt : section->get_registered_options()) { if (custom_mode_prefix == opt->get_name().substr(0, custom_mode_prefix.length())) { add_custom_mode(opt->get_value_str()); } } } /** Check whether the given state can be applied */ bool test_state(const output_state_t& state) { return true; } /** Change the output mode */ void apply_mode(const wlr_output_mode& mode) { if (handle->current_mode) { /* Do not modeset if nothing changed */ if ((handle->current_mode->width == mode.width) && (handle->current_mode->height == mode.height) && (handle->current_mode->refresh == mode.refresh) && ((handle->adaptive_sync_status == WLR_OUTPUT_ADAPTIVE_SYNC_ENABLED) == current_state.vrr) && (current_bit_depth == current_state.depth)) { /* Commit the enabling of the output */ wlr_output_commit(handle); return; } } refresh_custom_modes(); auto built_in = find_matching_mode(handle, mode); if (built_in) { wlr_output_set_mode(handle, built_in); } else { LOGI("Couldn't find matching mode ", mode.width, "x", mode.height, "@", mode.refresh / 1000.0, " for output ", handle->name, ". Trying to use custom mode", "(might not work)"); wlr_output_set_custom_mode(handle, mode.width, mode.height, mode.refresh); } wlr_output_commit(handle); const bool adaptive_sync_enabled = (handle->adaptive_sync_status == WLR_OUTPUT_ADAPTIVE_SYNC_ENABLED); if (adaptive_sync_enabled != current_state.vrr) { wlr_output_enable_adaptive_sync(handle, current_state.vrr); if (wlr_output_test(handle)) { wlr_output_commit(handle); LOGD("Changed adaptive sync on output: ", handle->name, " to ", current_state.vrr); } else { LOGE("Failed to change adaptive sync on output: ", handle->name); wlr_output_rollback(handle); } } if (current_state.depth != current_bit_depth) { for (auto fmt : formats_for_depth[current_state.depth]) { wlr_output_set_render_format(handle, fmt); if (wlr_output_test(handle)) { wlr_output_commit(handle); current_bit_depth = current_state.depth; LOGD("Set output format to ", get_format_name(fmt), " on output ", handle->name); break; } LOGD("Failed to set output format ", get_format_name(fmt), " on output ", handle->name); } } } /* Mirroring implementation */ wl_listener_wrapper on_mirrored_frame; wl_listener_wrapper on_frame; wlr_output *locked_cursors_on = NULL; /** Render the output using texture as source */ void render_output(wlr_texture *texture) { auto renderer = get_core().renderer; wlr_output_attach_render(handle, NULL); wlr_renderer_begin(renderer, handle->width, handle->height); wf::texture_t tex{texture}; OpenGL::render_transformed_texture(tex, {-1, -1, 2, 2}); wlr_renderer_end(renderer); wlr_output_commit(handle); } /* Load output contents and render them */ wlr_buffer *source_back_buffer = NULL; void handle_frame() { auto wo = get_core().output_layout->find_output( current_state.mirror_from); if (!wo) { LOGE("Cannot find mirrored output ", current_state.mirror_from, " for output ", handle->name); return; } if (source_back_buffer == NULL) { LOGE("Got empty buffer on ", wo->handle->name); return; } auto texture = wlr_texture_from_buffer(get_core().renderer, source_back_buffer); if (!texture) { LOGE("Failed to export texture to dmabuf!"); return; } render_output(texture); wlr_texture_destroy(texture); } void set_enabled(bool enabled) { wlr_output_enable(handle, enabled); if (!enabled) { wlr_output_commit(handle); } } void setup_mirror() { /* Check if we can mirror */ auto wo = get_core().output_layout->find_output( current_state.mirror_from); bool mirror_active = (wo != nullptr); if (wo) { auto config = get_core().output_layout->get_current_configuration(); auto& wo_state = config[wo->handle]; if (wo_state.source & OUTPUT_IMAGE_SOURCE_NONE) { mirror_active = false; } } if (!mirror_active) { /* If we mirror from a DPMS or an OFF output, we should turn * off this output as well */ set_enabled(false); LOGI(handle->name, ": Cannot mirror from output ", current_state.mirror_from, ". Disabling output."); return; } /* Force software cursors on the mirrored from output. * This ensures that they will be copied when reading pixels * from the main plane */ wlr_output_lock_software_cursors(wo->handle, true); locked_cursors_on = wo->handle; wlr_output_schedule_frame(handle); on_mirrored_frame.set_callback([=] (void *data) { auto ev = (wlr_output_event_commit*)data; if (!ev || !ev->state || !ev->state->buffer) { return; } if (ev->state->buffer) { if (source_back_buffer) { wlr_buffer_unlock(source_back_buffer); } source_back_buffer = ev->state->buffer; wlr_buffer_lock(ev->state->buffer); } /* The mirrored output was repainted, schedule repaint * for us as well */ wlr_output_schedule_frame(handle); }); on_mirrored_frame.connect(&wo->handle->events.commit); on_frame.set_callback([=] (void*) { handle_frame(); }); on_frame.connect(&handle->events.frame); } void teardown_mirror() { if (locked_cursors_on) { wlr_output_lock_software_cursors(locked_cursors_on, false); locked_cursors_on = NULL; } if (source_back_buffer) { wlr_buffer_unlock(source_back_buffer); source_back_buffer = NULL; } on_mirrored_frame.disconnect(); on_frame.disconnect(); } wf::dimensions_t get_effective_size() { wf::dimensions_t effective_size; wlr_output_effective_resolution(handle, &effective_size.width, &effective_size.height); return effective_size; } /** * Send the output-configuration-changed signal. */ void emit_configuration_changed(uint32_t changed_fields) { if ((handle->data != WF_NOOP_OUTPUT_MAGIC) && changed_fields) { wf::output_configuration_changed_signal data{current_state}; data.output = output.get(); data.changed_fields = changed_fields; output->emit(&data); } } /** Apply the given state to the output, ignoring position. * * This won't have any effect if the output state can't be applied, * i.e if test_state(state) == false */ void apply_state(const output_state_t& state) { if (!test_state(state)) { return; } uint32_t changed_fields = 0; if (this->current_state.source != state.source) { changed_fields |= wf::OUTPUT_SOURCE_CHANGE; } if ((this->current_state.mode.width != state.mode.width) || (this->current_state.mode.height != state.mode.height) || (this->current_state.mode.refresh != state.mode.refresh)) { changed_fields |= wf::OUTPUT_MODE_CHANGE; } if (this->current_state.scale != state.scale) { changed_fields |= wf::OUTPUT_SCALE_CHANGE; } if (this->current_state.transform != state.transform) { changed_fields |= wf::OUTPUT_TRANSFORM_CHANGE; } if (!(this->current_state.position == state.position)) { changed_fields |= wf::OUTPUT_POSITION_CHANGE; } this->current_state = state; /* Even if output will remain mirrored, we can tear it down and set * up again, in case the output to mirror from changed */ teardown_mirror(); if (state.source == OUTPUT_IMAGE_SOURCE_NONE) { /* output is OFF */ destroy_wayfire_output(); set_enabled(false); return; } set_enabled(!(state.source & OUTPUT_IMAGE_SOURCE_NONE)); apply_mode(state.mode); if (state.source & OUTPUT_IMAGE_SOURCE_SELF) { if (handle->transform != state.transform) { wlr_output_set_transform(handle, state.transform); } if (handle->scale != state.scale) { wlr_output_set_scale(handle, state.scale); } wlr_output_commit(handle); ensure_wayfire_output(get_effective_size()); output->render->damage_whole(); emit_configuration_changed(changed_fields); } else /* state.source == OUTPUT_IMAGE_SOURCE_MIRROR */ { destroy_wayfire_output(); setup_mirror(); } } }; class output_layout_t::impl { std::map> outputs; wlr_output_layout *output_layout; wlr_output_manager_v1 *output_manager; wlr_output_power_manager_v1 *output_pw_manager; wl_listener_wrapper on_new_output; wl_listener_wrapper on_output_manager_test; wl_listener_wrapper on_output_manager_apply; wl_listener_wrapper on_output_power_mode_set; wl_listener_wrapper on_backend_destroy; wl_idle_call idle_update_configuration; wl_timer timer_remove_noop; wlr_backend *noop_backend; /* Wayfire generally assumes that an enabled output is always available. * However, when switching connectors or something it might happen that * temporarily no output is available. For those cases, we create a * virtual output with the noop backend. */ std::unique_ptr noop_output; wf::signal::connection_t on_config_reload = [=] (wf::reload_config_signal *ev) { reconfigure_from_config(); }; wf::signal::connection_t on_backend_started = [=] (core_backend_started_signal *ev) { // We need to ensure that at any given time we have at least one // output while core is running. // // Thus we need to make sure the noop output is available if nothing // else is at startup. if (get_outputs().empty()) { ensure_noop_output(); } }; void deinit_noop() { /* Disconnect timer, since otherwise it will be destroyed * after the wayland display is. */ this->timer_remove_noop.disconnect(); if (noop_output) { noop_output->destroy_wayfire_output(); noop_output.reset(); } } public: impl(wlr_backend *backend) { on_new_output.set_callback([=] (void *data) { add_output((wlr_output*)data); }); on_new_output.connect(&backend->events.new_output); // We destroy the noop output when the renderer is destroyed. // This is needed because the noop output uses the same wlr_egl // as the real outputs. on_backend_destroy.set_callback([=] (auto) { deinit_noop(); }); on_backend_destroy.connect(&wf::get_core().renderer->events.destroy); output_layout = wlr_output_layout_create(); get_core().connect(&on_config_reload); noop_backend = wlr_headless_backend_create(get_core().display); wlr_backend_start(noop_backend); get_core().connect(&on_backend_started); output_manager = wlr_output_manager_v1_create(get_core().display); on_output_manager_test.set_callback([=] (void *data) { apply_wlr_configuration((wlr_output_configuration_v1*)data, true); }); on_output_manager_apply.set_callback([=] (void *data) { apply_wlr_configuration((wlr_output_configuration_v1*)data, false); }); on_output_manager_test.connect(&output_manager->events.test); on_output_manager_apply.connect(&output_manager->events.apply); output_pw_manager = wlr_output_power_manager_v1_create(get_core().display); on_output_power_mode_set.set_callback([=] (void *data) { set_power_mode((wlr_output_power_v1_set_mode_event*)data); }); on_output_power_mode_set.connect(&output_pw_manager->events.set_mode); } void fini() { // Destroy outputs first this->outputs.clear(); noop_output.reset(); // Disconnect all signals on_new_output.disconnect(); on_output_manager_test.disconnect(); on_output_manager_apply.disconnect(); on_output_power_mode_set.disconnect(); on_backend_destroy.disconnect(); wlr_backend_destroy(noop_backend); wlr_output_layout_destroy(output_layout); } impl(const impl &) = delete; impl(impl &&) = delete; impl& operator =(const impl&) = delete; impl& operator =(impl&&) = delete; output_configuration_t output_configuration_from_wlr_configuration( wlr_output_configuration_v1 *configuration) { output_configuration_t result; wlr_output_configuration_head_v1 *head; wl_list_for_each(head, &configuration->heads, link) { if (!this->outputs.count(head->state.output)) { LOGE("Output configuration request contains unknown", " output, probably a compositor bug!"); continue; } auto& handle = head->state.output; auto& state = result[handle]; if (!head->state.enabled) { state.source = OUTPUT_IMAGE_SOURCE_NONE; continue; } state.source = OUTPUT_IMAGE_SOURCE_SELF; if (head->state.mode) { state.mode = *head->state.mode; } else { state.mode.width = head->state.custom_mode.width; state.mode.height = head->state.custom_mode.height; state.mode.refresh = head->state.custom_mode.refresh; } state.position = {head->state.x, head->state.y}; state.scale = head->state.scale; state.transform = head->state.transform; state.vrr = head->state.adaptive_sync_enabled; if ((handle->pending.render_format == DRM_FORMAT_XRGB2101010) || (handle->pending.render_format == DRM_FORMAT_XBGR2101010)) { state.depth = 10; } else { state.depth = 8; } } return result; } void apply_wlr_configuration( wlr_output_configuration_v1 *wlr_configuration, bool test_only) { auto configuration = output_configuration_from_wlr_configuration(wlr_configuration); if (apply_configuration(configuration, test_only)) { // Notify outputs that they have external configuration for (auto& [wo, _] : configuration) { this->outputs[wo]->is_externally_managed = true; } wlr_output_configuration_v1_send_succeeded(wlr_configuration); } else { wlr_output_configuration_v1_send_failed(wlr_configuration); } } void ensure_noop_output() { LOGI("new output: NOOP-1"); if (!noop_output) { auto handle = wlr_headless_add_output(noop_backend, 1280, 720); handle->data = WF_NOOP_OUTPUT_MAGIC; strcpy(handle->name, "NOOP-1"); if (!wlr_output_init_render(handle, get_core().allocator, get_core().renderer)) { LOGE("failed to init wlr render for noop output!"); // XXX: can we even recover from this?? std::exit(0); } noop_output = std::make_unique(handle); } /* Make sure that the noop output is up and running even before the * next reconfiguration. This is needed because if we are removing * an output, we might get into a situation where the last physical * output has already been removed but we are yet to add the noop one */ noop_output->apply_state(noop_output->load_configured_state()); wlr_output_layout_add_auto(output_layout, noop_output->handle); timer_remove_noop.disconnect(); } void remove_noop_output() { if (!noop_output) { return; } if (noop_output->current_state.source == OUTPUT_IMAGE_SOURCE_NONE) { return; } LOGI("remove output: NOOP-1"); output_state_t state; state.source = OUTPUT_IMAGE_SOURCE_NONE; noop_output->apply_state(state); wlr_output_layout_remove(output_layout, noop_output->handle); // Trigger repositioning of all outputs apply_configuration(get_current_configuration()); } void add_output(wlr_output *output) { LOGI("new output: ", output->name, " (\"", output->make, " ", output->model, " ", output->serial, "\")"); if (output->non_desktop) { LOGD("Non-desktop output ", output->name, " found"); if (get_core().protocols.drm_v1) { LOGD("Drm lease offered to ", output->name); wlr_drm_lease_v1_manager_offer_output(get_core().protocols.drm_v1, output); } return; } if (!wlr_output_init_render(output, get_core().allocator, get_core().renderer)) { LOGE("failed to init wlr render for output ", output->name); return; } auto lo = new output_layout_output_t(output); outputs[output] = std::unique_ptr(lo); lo->on_destroy.set_callback([output, this] (void*) { remove_output(output); }); reconfigure_from_config(); } void remove_output(wlr_output *to_remove) { auto active_outputs = get_outputs(); LOGI("remove output: ", to_remove->name); /* Unset mode, plus destroy the wayfire output */ auto configuration = get_current_configuration(); configuration[to_remove].source = OUTPUT_IMAGE_SOURCE_NONE; apply_configuration(configuration); outputs.erase(to_remove); /* If no physical outputs, then at least the noop output */ assert(get_outputs().size() || is_shutting_down()); } /* Get the current configuration of all outputs */ output_configuration_t get_current_configuration() { output_configuration_t configuration; for (auto& entry : this->outputs) { configuration[entry.first] = entry.second->current_state; } return configuration; } /** Load config from file, test and apply */ void reconfigure_from_config() { // Load desired configuration from config file output_configuration_t configuration; for (auto& [output, layout_output] : this->outputs) { configuration[output] = layout_output->load_configured_state(); } if (configuration != get_current_configuration()) { if (test_configuration(configuration)) { apply_configuration(configuration); } } } /** * Calculate the output layout geometry for the state. * The state represents a non-automatically positioned enabled output. */ wf::geometry_t calculate_geometry_from_state( const output_state_t& state) const { wf::geometry_t geometry = { state.position.get_x(), state.position.get_y(), (int32_t)(state.mode.width / state.scale), (int32_t)(state.mode.height / state.scale), }; if (state.transform & 1) { std::swap(geometry.width, geometry.height); } return geometry; } /** @return A list of geometries of fixed position outputs. */ std::vector calculate_fixed_geometries( const output_configuration_t& config) { std::vector geometries; for (auto& entry : config) { if (!(entry.second.source & OUTPUT_IMAGE_SOURCE_SELF) || entry.second.position.is_automatic_position()) { continue; } geometries.push_back(calculate_geometry_from_state(entry.second)); } return geometries; } /** @return true if there are overlapping outputs */ bool test_overlapping_outputs(const output_configuration_t& config) { auto geometries = calculate_fixed_geometries(config); for (size_t i = 0; i < geometries.size(); i++) { for (size_t j = i + 1; j < geometries.size(); j++) { if (geometries[i] & geometries[j]) { return true; } } } return false; } /** @return true if all outputs are disabled. */ bool test_all_disabled_outputs(const output_configuration_t& config) { int count_enabled = 0; for (auto& entry : config) { if (entry.second.source & OUTPUT_IMAGE_SOURCE_SELF) { ++count_enabled; } } return count_enabled == 0; } /* @return true if rectangles have a common interior or border point. */ bool rectangles_touching(const wf::geometry_t& a, const wf::geometry_t& b) { return !(a.x + a.width < b.x || a.y + a.height < b.y || b.x + b.width < a.x || b.y + b.height < a.y); } /** @return true if fixed position outputs do not form a continuous space */ bool test_disjoint_outputs(const output_configuration_t& config) { auto geometries = calculate_fixed_geometries(config); if (geometries.empty()) { /* Not disjoint */ return false; } /* Create graph with a vertex for each rectangle. * Configuration is disjoint iff the graph has more than one component */ std::vector> graph(geometries.size()); for (size_t i = 0; i < geometries.size(); i++) { for (size_t j = i + 1; j < geometries.size(); j++) { if (rectangles_touching(geometries[i], geometries[j])) { graph[i].push_back(j); graph[j].push_back(i); } } } /* Do a depth-first-search */ std::vector visited(geometries.size(), 0); std::function dfs; dfs = [&] (int u) { if (visited[u] == 1) { return; } visited[u] = 1; for (int v : graph[u]) { dfs(v); } }; dfs(0); // If we have a zero somewhere it means the vertex was not reached return *std::min_element(visited.begin(), visited.end()) == 0; } /** Check whether the given configuration can be applied */ bool test_configuration(const output_configuration_t& config) { if (config.size() != this->outputs.size()) { return false; } bool ok = true; for (auto& entry : config) { if (this->outputs.count(entry.first) == 0) { return false; } ok &= this->outputs[entry.first]->test_state(entry.second); } /* Check overlapping outputs */ if (test_overlapping_outputs(config)) { LOGE("Overlapping outputs in the output configuration, ", "unexpected behavior might occur"); } if (test_all_disabled_outputs(config)) { LOGW("All wayfire outputs have been disabled!"); } if (test_disjoint_outputs(config)) { LOGW("Wayfire outputs have been configured with gaps between them, ", "pointer will not be movable between them. Note this might ", "be ok before all outputs are connected."); } return ok; } /** Apply the given configuration. Config MUST be a valid configuration */ void apply_configuration(const output_configuration_t& config) { /* The order in which we enable and disable outputs is important. * Firstly, on some systems where there aren't enough CRTCs, we can * only enable a subset of all outputs at once. This means we should * first try to disable as many outputs as possible, and only then * start enabling new ones. * * Secondly, we need to check when we need to enable noop output - * which is exactly when all currently enabled outputs are going to * be disabled */ /* Number of outputs that were enabled and continue to be enabled */ int count_remaining_enabled = 0; auto active_outputs = get_outputs(); for (auto& wo : active_outputs) { auto it = config.find(wo->handle); if ((it != config.end()) && (it->second.source & OUTPUT_IMAGE_SOURCE_SELF)) { ++count_remaining_enabled; } } bool turning_off_all_active = !active_outputs.empty() && count_remaining_enabled == 0; if (turning_off_all_active && !is_shutting_down()) { /* If we aren't shutting down, and we will turn off all the * currently enabled outputs, we'll need the noop output, as a * temporary output to store views in, until a real output is * enabled again */ ensure_noop_output(); } /* First: disable all outputs that need disabling */ for (auto& entry : config) { auto& handle = entry.first; auto& state = entry.second; auto& lo = this->outputs[handle]; if (!(state.source & OUTPUT_IMAGE_SOURCE_SELF)) { /* First shut down the output, move its views, etc. while it * is still in the output layout and its global is active. * * This is needed so that clients can receive * wl_surface.leave events for the to be destroyed output */ lo->apply_state(state); wlr_output_layout_remove(output_layout, handle); } } /* Second: enable outputs with fixed positions. */ int count_enabled = 0; for (auto& entry : config) { auto& handle = entry.first; auto& state = entry.second; auto& lo = this->outputs[handle]; if (state.source & OUTPUT_IMAGE_SOURCE_SELF && !entry.second.position.is_automatic_position()) { ++count_enabled; wlr_output_layout_add(output_layout, handle, state.position.get_x(), state.position.get_y()); lo->apply_state(state); } } /* * Third: enable dynamically positioned outputs. * Since outputs with fixed positions were already added, we know * that the outputs here will not be moved after they are added to * the output_layout. */ for (auto& entry : config) { auto& handle = entry.first; auto& lo = this->outputs[handle]; auto state = entry.second; if (state.source & OUTPUT_IMAGE_SOURCE_SELF && entry.second.position.is_automatic_position()) { ++count_enabled; wlr_output_layout_add_auto(output_layout, handle); lo->apply_state(state); } } /* Fourth: enable mirrored outputs */ for (auto& entry : config) { auto& handle = entry.first; auto& state = entry.second; auto& lo = this->outputs[handle]; if (state.source == OUTPUT_IMAGE_SOURCE_MIRROR) { lo->apply_state(state); wlr_output_layout_remove(output_layout, handle); } } /* Fifth: emit configuration-changed again for dynamically-positioned outputs, because their position * might have changed. */ for (auto& entry : config) { auto& handle = entry.first; auto& state = entry.second; auto& lo = this->outputs[handle]; if (state.source & OUTPUT_IMAGE_SOURCE_SELF && entry.second.position.is_automatic_position()) { lo->emit_configuration_changed(wf::OUTPUT_POSITION_CHANGE); } } wf::output_layout_configuration_changed_signal ev; get_core().output_layout->emit(&ev); if (count_enabled > 0) { /* Make sure to remove the noop output if it is no longer needed. * NB: Libwayland has a bug when a global is created and * immediately destroyed, as clients don't have enough time * to bind it. That's why we don't destroy noop immediately, * but only after a timeout */ timer_remove_noop.set_timeout(1000, [=] () { remove_noop_output(); }); } idle_update_configuration.run_once([=] () { send_wlr_configuration(); }); } void send_wlr_configuration() { auto wlr_configuration = wlr_output_configuration_v1_create(); for (auto& output : outputs) { auto head = wlr_output_configuration_head_v1_create( wlr_configuration, output.first); wlr_box box; wlr_output_layout_get_box(output_layout, output.first, &box); if (wlr_box_empty(&box)) { head->state.x = 0; head->state.y = 0; } else { head->state.x = box.x; head->state.y = box.y; } } wlr_output_manager_v1_set_configuration(output_manager, wlr_configuration); } void set_power_mode(wlr_output_power_v1_set_mode_event *ev) { LOGD("output: ", ev->output->name, " power mode: ", ev->mode); auto config = get_current_configuration(); if (!config.count(ev->output)) { return; } const bool wants_dpms = (ev->mode == ZWLR_OUTPUT_POWER_V1_MODE_OFF); config[ev->output].source = (wants_dpms ? OUTPUT_IMAGE_SOURCE_DPMS : OUTPUT_IMAGE_SOURCE_SELF); apply_configuration(config); auto& wo = outputs[ev->output]; if (wo->inhibited != wants_dpms) { wo->inhibited = wants_dpms; wo->output->render->add_inhibit(wants_dpms); } wo->output->render->damage_whole(); } /* Public API functions */ wlr_output_layout *get_handle() { return output_layout; } size_t get_num_outputs() { return get_outputs().size(); } wf::output_t *find_output(wlr_output *output) { if (outputs.count(output)) { return outputs[output]->output.get(); } if (noop_output && (noop_output->handle == output)) { return noop_output->output.get(); } return nullptr; } wf::output_t *find_output(std::string name) { for (auto& entry : outputs) { if (entry.first->name == name) { return entry.second->output.get(); } } if (noop_output && (noop_output->handle->name == name)) { return noop_output->output.get(); } return nullptr; } std::vector get_outputs() { std::vector result; for (auto& entry : outputs) { if (entry.second->current_state.source & OUTPUT_IMAGE_SOURCE_SELF) { result.push_back(entry.second->output.get()); } } if (noop_output && noop_output->output) { result.push_back(noop_output->output.get()); } return result; } wf::output_t *get_next_output(wf::output_t *output) { auto os = get_outputs(); auto it = std::find(os.begin(), os.end(), output); if ((it == os.end()) || (std::next(it) == os.end())) { return os[0]; } else { return *(++it); } } wf::output_t *get_output_coords_at(const wf::pointf_t& origin, wf::pointf_t& closest) { wlr_output_layout_closest_point(output_layout, NULL, origin.x, origin.y, &closest.x, &closest.y); auto handle = wlr_output_layout_output_at(output_layout, closest.x, closest.y); assert(handle || is_shutting_down()); if (!handle) { return nullptr; } if (noop_output && (handle == noop_output->handle)) { return noop_output->output.get(); } else { return outputs[handle]->output.get(); } } wf::output_t *get_output_at(int x, int y) { wf::pointf_t dummy; return get_output_coords_at({1.0 * x, 1.0 * y}, dummy); } bool apply_configuration(const output_configuration_t& configuration, bool test_only) { bool ok = test_configuration(configuration); if (ok && !test_only) { apply_configuration(configuration); } return ok; } }; /* Just pass to the PIMPL */ output_layout_t::output_layout_t(wlr_backend *b) : pimpl(new impl(b)) {} output_layout_t::~output_layout_t() = default; wlr_output_layout*output_layout_t::get_handle() { return pimpl->get_handle(); } wf::output_t*output_layout_t::get_output_at(int x, int y) { return pimpl->get_output_at(x, y); } wf::output_t*output_layout_t::get_output_coords_at(wf::pointf_t origin, wf::pointf_t& closest) { return pimpl->get_output_coords_at(origin, closest); } size_t output_layout_t::get_num_outputs() { return pimpl->get_num_outputs(); } std::vector output_layout_t::get_outputs() { return pimpl->get_outputs(); } wf::output_t*output_layout_t::get_next_output(wf::output_t *output) { return pimpl->get_next_output(output); } wf::output_t*output_layout_t::find_output(wlr_output *output) { return pimpl->find_output(output); } wf::output_t*output_layout_t::find_output(std::string name) { return pimpl->find_output(name); } output_configuration_t output_layout_t::get_current_configuration() { return pimpl->get_current_configuration(); } bool output_layout_t::apply_configuration( const output_configuration_t& configuration, bool test_only) { return pimpl->apply_configuration(configuration, test_only); } void priv_output_layout_fini(wf::output_layout_t *layout) { layout->pimpl->fini(); } } wayfire-0.8.1/src/core/plugin-loader.cpp000066400000000000000000000211671457431457600202020ustar00rootroot00000000000000#include #include #include #include #include #include "plugin-loader.hpp" #include "../core/wm.hpp" #include "wayfire/plugin.hpp" #include wf::plugin_manager_t::plugin_manager_t() { this->plugins_opt.load_option("core/plugins"); this->enable_so_unloading.load_option("workarounds/enable_so_unloading"); reload_dynamic_plugins(); load_static_plugins(); this->plugins_opt.set_callback([=] () { /* reload when config reload has finished */ idle_reload_plugins.run_once([&] () { reload_dynamic_plugins(); }); }); } void wf::plugin_manager_t::deinit_plugins(bool unloadable) { for (auto& [name, plugin] : loaded_plugins) { if (!plugin.instance) { continue; } if (plugin.instance->is_unloadable() == unloadable) { destroy_plugin(plugin); } } } wf::plugin_manager_t::~plugin_manager_t() { /* First remove unloadable plugins, then others */ deinit_plugins(true); deinit_plugins(false); loaded_plugins.clear(); } void wf::plugin_manager_t::destroy_plugin(wf::loaded_plugin_t& p) { LOGD("Unloading plugin ", p.so_path); p.instance->fini(); p.instance.reset(); /* dlopen()/dlclose() do reference counting, so we should close the plugin * as many times as we opened it. * * We also need to close the handle after deallocating the plugin, otherwise * we unload its destructor before calling it. * * Note however, that dlclose() is merely a "statement of intent" as per * POSIX[1]: * - On glibc[2], this decreases the reference count and potentially unloads * the binary. * - On musl-libc[3] this is a noop. * * [1]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/dlclose.html * [2]: https://man7.org/linux/man-pages/man3/dlclose.3.html * [3]: * https://wiki.musl-libc.org/functional-differences-from-glibc.html#Unloading-libraries * */ if (p.so_handle && enable_so_unloading) { dlclose(p.so_handle); } } std::pair wf::get_new_instance_handle(const std::string& path) { // RTLD_GLOBAL is required for RTTI/dynamic_cast across plugins void *handle = dlopen(path.c_str(), RTLD_NOW | RTLD_GLOBAL); if (handle == NULL) { LOGE("error loading plugin: ", dlerror()); return {nullptr, nullptr}; } /* Check plugin version */ auto version_func_ptr = dlsym(handle, "getWayfireVersion"); if (version_func_ptr == NULL) { LOGE(path, ": missing getWayfireVersion()", path.c_str()); dlclose(handle); return {nullptr, nullptr}; } auto version_func = union_cast(version_func_ptr); int32_t plugin_abi_version = version_func(); if (version_func() != WAYFIRE_API_ABI_VERSION) { LOGE(path, ": API/ABI version mismatch: Wayfire is ", WAYFIRE_API_ABI_VERSION, ", plugin built with ", plugin_abi_version); dlclose(handle); return {nullptr, nullptr}; } auto new_instance_func_ptr = dlsym(handle, "newInstance"); if (new_instance_func_ptr == NULL) { LOGE(path, ": missing newInstance(). ", dlerror()); dlclose(handle); return {nullptr, nullptr}; } LOGD("Loaded plugin ", path.c_str()); return {handle, new_instance_func_ptr}; } std::optional wf::plugin_manager_t::load_plugin_from_file(std::string path) { auto [handle, new_instance_func_ptr] = wf::get_new_instance_handle(path); if (new_instance_func_ptr) { auto new_instance_func = union_cast(new_instance_func_ptr); loaded_plugin_t lp; lp.instance = std::unique_ptr(new_instance_func()); lp.so_handle = handle; lp.so_path = path; return lp; } return {}; } void wf::plugin_manager_t::reload_dynamic_plugins() { std::string plugin_list = plugins_opt; if (plugin_list == "none") { LOGE("No plugins specified in the config file, or config file is " "missing. In this state the compositor is nearly unusable, please " "ensure your configuration file is set up properly."); } std::stringstream stream(plugin_list); std::vector next_plugins; std::vector plugin_paths = wf::get_plugin_paths(); std::string plugin_name; while (stream >> plugin_name) { if (plugin_name.size()) { auto plugin_path = wf::get_plugin_path_for_name(plugin_paths, plugin_name); if (plugin_path) { auto already_loaded = std::find(next_plugins.begin(), next_plugins.end(), plugin_path.value()); if (already_loaded != next_plugins.end()) { LOGE(plugin_name, " plugin found in the plugin list more than once, skipping"); continue; } next_plugins.push_back(plugin_path.value()); } else { LOGE("Failed to load plugin \"", plugin_name, "\". ", "Make sure it is installed in ", PLUGIN_PATH, " or in $WAYFIRE_PLUGIN_PATH."); } } } /* erase plugins that have been removed from the config */ auto it = loaded_plugins.begin(); while (it != loaded_plugins.end()) { /* skip built-in(static) plugins */ if (it->first.size() && (it->first[0] == '_')) { ++it; continue; } if ((std::find(next_plugins.begin(), next_plugins.end(), it->first) == next_plugins.end()) && it->second.instance->is_unloadable()) { LOGD("unload plugin ", it->first.c_str()); destroy_plugin(it->second); it = loaded_plugins.erase(it); } else { ++it; } } /* load new plugins */ std::vector> pending_initialize; for (auto plugin : next_plugins) { if (loaded_plugins.count(plugin)) { continue; } std::optional ptr = load_plugin_from_file(plugin); if (ptr) { pending_initialize.emplace_back(plugin, std::move(*ptr)); } } std::stable_sort(pending_initialize.begin(), pending_initialize.end(), [] (const auto& a, const auto& b) { return a.second.instance->get_order_hint() < b.second.instance->get_order_hint(); }); for (auto& [plugin, ptr] : pending_initialize) { ptr.instance->init(); loaded_plugins[plugin] = std::move(ptr); } } template static wf::loaded_plugin_t create_plugin(std::string name) { wf::loaded_plugin_t lp; lp.instance = std::make_unique(); lp.so_handle = nullptr; lp.so_path = name; lp.instance->init(); return lp; } void wf::plugin_manager_t::load_static_plugins() { loaded_plugins["_exit"] = create_plugin>("_exit"); loaded_plugins["_focus"] = create_plugin("_focus"); loaded_plugins["_close"] = create_plugin>("_close"); } std::vector wf::get_plugin_paths() { std::vector plugin_prefixes; if (char *plugin_path = getenv("WAYFIRE_PLUGIN_PATH")) { std::stringstream ss(plugin_path); std::string entry; while (std::getline(ss, entry, ':')) { plugin_prefixes.push_back(entry); } } // also add XDG specific paths std::string xdg_data_dir; char *c_xdg_data_dir = std::getenv("XDG_DATA_HOME"); char *c_user_home = std::getenv("HOME"); if (c_xdg_data_dir != NULL) { xdg_data_dir = c_xdg_data_dir; } else if (c_user_home != NULL) { xdg_data_dir = (std::string)c_user_home + "/.local/share/"; } if (xdg_data_dir != "") { plugin_prefixes.push_back(xdg_data_dir + "/wayfire/plugins"); } plugin_prefixes.push_back(PLUGIN_PATH); return plugin_prefixes; } std::optional wf::get_plugin_path_for_name( std::vector plugin_paths, std::string plugin_name) { if (plugin_name.at(0) == '/') { return plugin_name; } for (std::filesystem::path plugin_prefix : plugin_paths) { auto plugin_path = plugin_prefix / ("lib" + plugin_name + ".so"); if (std::filesystem::exists(plugin_path)) { return plugin_path; } } return {}; } wayfire-0.8.1/src/core/plugin-loader.hpp000066400000000000000000000041041457431457600201770ustar00rootroot00000000000000#pragma once #include #include #include "wayfire/plugin.hpp" #include "config.h" #include "wayfire/util.hpp" #include namespace wf { struct loaded_plugin_t { // A pointer to the plugin std::unique_ptr instance; // A handle returned by dlopen(). void *so_handle; // A path to the .so file of the plugin. std::string so_path; }; struct plugin_manager_t { plugin_manager_t(); ~plugin_manager_t(); void reload_dynamic_plugins(); wf::wl_idle_call idle_reload_plugins; private: wf::option_wrapper_t plugins_opt; wf::option_wrapper_t enable_so_unloading; std::unordered_map loaded_plugins; void deinit_plugins(bool unloadable); std::optional load_plugin_from_file(std::string path); void load_static_plugins(); void destroy_plugin(loaded_plugin_t& plugin); }; /** Helper functions */ template B union_cast(A object) { union { A x; B y; } helper; helper.x = object; return helper.y; } /** * Open a plugin file and check the file for version errors. * * On success, return the handle from dlopen() and the pointer to the * newInstance of the plugin. * * @return (dlopen() handle, newInstance pointer) */ std::pair get_new_instance_handle(const std::string& path); /** * List the locations where wayfire's plugins are installed. * This function takes care of env variable WAYFIRE_PLUGIN_PATH, * as well as the default location. */ std::vector get_plugin_paths(); /** * Search each path specified in @param plugin_paths for a plugin named @param * plugin_name * @param plugin_paths A list of locations where wayfire plugins are installed * @param plugin_name The plugin to be searched. If @param plugin_name is an * absolute path, then it is returned without modification. */ std::optional get_plugin_path_for_name( std::vector plugin_paths, std::string plugin_name); } wayfire-0.8.1/src/core/plugin.cpp000066400000000000000000000040201457431457600167230ustar00rootroot00000000000000#include "core-impl.hpp" #include "wayfire/debug.hpp" #include "wayfire/output.hpp" #include "seat/input-manager.hpp" #include "wayfire/signal-definitions.hpp" #include #include void wf::plugin_interface_t::fini() {} namespace wf { /** Implementation of default config backend functions. */ std::shared_ptr wf::config_backend_t::get_output_section( wlr_output *output) { std::string name = output->name; name = "output:" + name; auto& config = wf::get_core().config; if (!config.get_section(name)) { config.merge_section( config.get_section("output")->clone_with_name(name)); } return config.get_section(name); } std::shared_ptr wf::config_backend_t::get_input_device_section( wlr_input_device *device) { std::string name = nonull(device->name); name = "input-device:" + name; auto& config = wf::get_core().config; if (!config.get_section(name)) { config.merge_section( config.get_section("input-device")->clone_with_name(name)); } return config.get_section(name); } std::vector wf::config_backend_t::get_xml_dirs() const { std::vector xmldirs; if (char *plugin_xml_path = getenv("WAYFIRE_PLUGIN_XML_PATH")) { std::stringstream ss(plugin_xml_path); std::string entry; while (std::getline(ss, entry, ':')) { xmldirs.push_back(entry); } } // also add XDG specific paths std::string xdg_data_dir; char *c_xdg_data_dir = std::getenv("XDG_DATA_HOME"); char *c_user_home = std::getenv("HOME"); if (c_xdg_data_dir != NULL) { xdg_data_dir = c_xdg_data_dir; } else if (c_user_home != NULL) { xdg_data_dir = (std::string)c_user_home + "/.local/share/"; } if (xdg_data_dir != "") { xmldirs.push_back(xdg_data_dir + "/wayfire/metadata"); } xmldirs.push_back(PLUGIN_XML_DIR); return xmldirs; } } wayfire-0.8.1/src/core/scene-priv.hpp000066400000000000000000000001601457431457600175060ustar00rootroot00000000000000#pragma once #include namespace wf { namespace scene { struct root_node_t::priv_t {}; } } wayfire-0.8.1/src/core/scene.cpp000066400000000000000000000321541457431457600165330ustar00rootroot00000000000000#include #include #include #include #include #include #include #include "scene-priv.hpp" #include "wayfire/debug.hpp" #include "wayfire/geometry.hpp" #include "wayfire/opengl.hpp" #include "wayfire/region.hpp" #include "wayfire/scene-input.hpp" #include "wayfire/scene-render.hpp" #include "wayfire/signal-provider.hpp" #include "wayfire/util.hpp" #include namespace wf { bool keyboard_focus_node_t::operator <(const keyboard_focus_node_t& other) const { if (this->importance == other.importance) { if (this->node && other.node) { auto ts_self = node->keyboard_interaction().last_focus_timestamp; auto ts_other = other.node->keyboard_interaction().last_focus_timestamp; return ts_self < ts_other; } if (!this->node && !other.node) { return false; } // Prefer to focus with node return !this->node; } return this->importance < other.importance; } namespace scene { // ---------------------------------- node_t ----------------------------------- node_t::~node_t() {} node_t::node_t(bool is_structure) { this->_is_structure = is_structure; } void node_t::set_enabled(bool is_active) { enabled_counter += (is_active ? 1 : -1); } std::string node_t::stringify_flags() const { std::string fl = ""; if (flags() & ((int)node_flags::DISABLED)) { fl += "d"; } if (flags() & ((int)node_flags::RAW_INPUT)) { fl += "R"; } return "(" + fl + ")"; } std::optional node_t::find_node_at(const wf::pointf_t& at) { auto local = this->to_local(at); for (auto& node : get_children()) { if (!node->is_enabled()) { continue; } auto child_node = node->find_node_at(local); if (child_node.has_value()) { return child_node; } } return {}; } wf::keyboard_focus_node_t node_t::keyboard_refocus(wf::output_t *output) { wf::keyboard_focus_node_t result; for (auto& ch : this->get_children()) { if (!ch->is_enabled()) { continue; } auto ch_focus = ch->keyboard_refocus(output); result = std::max(result, ch_focus); if (!ch_focus.allow_focus_below) { result.allow_focus_below = false; break; } } return result; } bool floating_inner_node_t::set_children_list(std::vector new_list) { set_children_unchecked(std::move(new_list)); return true; } void node_t::set_children_unchecked(std::vector new_list) { node_damage_signal data; data.region |= get_bounding_box(); for (auto& node : this->children) { node->_parent = nullptr; } for (auto& node : new_list) { wf::dassert(node->parent() == nullptr, "Adding a child node twice!"); node->_parent = this; } this->children = std::move(new_list); data.region |= get_bounding_box(); this->emit(&data); } static int get_layer_index(const wf::scene::node_t *node) { using namespace wf::scene; if (auto root = dynamic_cast(node->parent())) { for (int layer = 0; layer < (int)layer::ALL_LAYERS; layer++) { if (root->layers[layer].get() == node) { return layer; } } } return -1; } std::string node_t::stringify() const { std::string description = "node "; int layer_idx = get_layer_index(this); if (layer_idx >= 0) { static constexpr const char *layer_names[] = { "background", "bottom", "workspace", "top", "unmanaged", "overlay", "dwidget" }; static_assert((sizeof(layer_names) / sizeof(layer_names[0])) == (size_t)layer::ALL_LAYERS); description = "layer_"; description += layer_names[layer_idx]; } return description + " " + stringify_flags(); } wf::pointf_t node_t::to_local(const wf::pointf_t& point) { return point; } wf::pointf_t node_t::to_global(const wf::pointf_t& point) { return point; } // Just listen for damage from the node and push it upwards class default_render_instance_t : public render_instance_t { protected: damage_callback push_damage; wf::signal::connection_t on_main_node_damaged = [=] (node_damage_signal *data) { push_damage(data->region); }; public: default_render_instance_t(node_t *self, damage_callback callback) { this->push_damage = callback; self->connect(&on_main_node_damaged); } void schedule_instructions(std::vector& instructions, const wf::render_target_t& target, wf::region_t& damage) override { // nothing to render here } void render(const wf::render_target_t& target, const wf::region_t& region) override { wf::dassert(false, "Rendering an inner node?"); } direct_scanout try_scanout(wf::output_t *output) override { // Nodes without actual visual content do not prevent further nodes // from being scanned out. return direct_scanout::SKIP; } }; void node_t::gen_render_instances(std::vector & instances, damage_callback push_damage, wf::output_t *output) { // Add self for damage tracking instances.push_back( std::make_unique(this, push_damage)); // Add children as a flat list to avoid multiple indirections for (auto& ch : this->children) { if (ch->is_enabled()) { ch->gen_render_instances(instances, push_damage, output); } } } wf::geometry_t node_t::get_children_bounding_box() { if (children.empty()) { return {0, 0, 0, 0}; } int min_x = std::numeric_limits::max(); int min_y = std::numeric_limits::max(); int max_x = std::numeric_limits::min(); int max_y = std::numeric_limits::min(); for (auto& ch : children) { auto bbox = ch->get_bounding_box(); min_x = std::min(min_x, bbox.x); min_y = std::min(min_y, bbox.y); max_x = std::max(max_x, bbox.x + bbox.width); max_y = std::max(max_y, bbox.y + bbox.height); } return {min_x, min_y, max_x - min_x, max_y - min_y}; } wf::geometry_t node_t::get_bounding_box() { return get_children_bounding_box(); } // ------------------------------ output_node_t -------------------------------- output_node_t::output_node_t(wf::output_t *output) : floating_inner_node_t(true) { this->output = output; } std::string output_node_t::stringify() const { return "output " + this->output->to_string() + " " + stringify_flags(); } wf::pointf_t output_node_t::to_local(const wf::pointf_t& point) { auto offset = wf::origin(output->get_layout_geometry()); return {point.x - offset.x, point.y - offset.y}; } wf::pointf_t output_node_t::to_global(const wf::pointf_t& point) { auto offset = wf::origin(output->get_layout_geometry()); return {point.x + offset.x, point.y + offset.y}; } std::optional output_node_t::find_node_at(const wf::pointf_t& at) { if (limit_region && !(*limit_region & at)) { return {}; } return node_t::find_node_at(at); } class output_render_instance_t : public default_render_instance_t { wf::output_t *output; output_node_t *self; std::vector children; public: output_render_instance_t(output_node_t *self, damage_callback callback, wf::output_t *output, wf::output_t *shown_on) : default_render_instance_t(self, transform_damage(callback)) { this->self = self; this->output = output; // Children are stored as a sublist, because we need to translate every // time between global and output-local geometry. for (auto& child : self->get_children()) { if (child->is_enabled()) { child->gen_render_instances(children, transform_damage(callback), shown_on); } } } damage_callback transform_damage(damage_callback child_damage) { return [=] (const wf::region_t& damage) { child_damage(damage + wf::origin(output->get_layout_geometry())); }; } void schedule_instructions(std::vector& instructions, const wf::render_target_t& target, wf::region_t& damage) override { if (self->limit_region) { wf::region_t our_damage = damage & *self->limit_region; our_damage &= target.geometry; if (!our_damage.empty()) { _schedule_instructions(instructions, target, our_damage); // Inside limit region, damage is defined as the children // decided. damage ^= *self->limit_region; damage |= our_damage & *self->limit_region; } } else { _schedule_instructions(instructions, target, damage); } } void _schedule_instructions(std::vector& instructions, const wf::render_target_t& target, wf::region_t& damage) { // In principle, we just have to schedule the children. // However, we need to adjust the target's geometry and the damage to // fit with the coordinate system of the output. auto offset = wf::origin(output->get_layout_geometry()); wf::render_target_t new_target = target.translated(-offset); damage += -offset; for (auto& ch : children) { ch->schedule_instructions(instructions, new_target, damage); } damage += offset; } direct_scanout try_scanout(wf::output_t *scanout) override { if ((scanout != this->output) && this->self->limit_region) { // Can't scanout on a different output because it is outside // of the limit region return direct_scanout::SKIP; } for (auto& ch : children) { auto res = ch->try_scanout(scanout); if (res != direct_scanout::SKIP) { return res; } } return direct_scanout::SKIP; } void compute_visibility(wf::output_t *output, wf::region_t& visible) override { auto offset = wf::origin(output->get_layout_geometry()); compute_visibility_from_list(children, output, visible, offset); } }; void output_node_t::gen_render_instances( std::vector & instances, damage_callback push_damage, wf::output_t *shown_on) { if (this->limit_region && shown_on && (shown_on != this->output)) { // If the limit region is set and we are limiting the generation of // instances to a particular region (typically an output), make sure we // generate instances only if the output will be visible. return; } instances.push_back( std::make_unique(this, push_damage, output, shown_on)); } wf::geometry_t output_node_t::get_bounding_box() { const auto bbox = node_t::get_bounding_box(); return bbox + wf::origin(output->get_layout_geometry()); } // ------------------------------ root_node_t ---------------------------------- root_node_t::root_node_t() : floating_inner_node_t(true) { std::vector children; this->priv = std::make_unique(); for (int i = (int)layer::ALL_LAYERS - 1; i >= 0; i--) { layers[i] = std::make_shared(true); children.push_back(layers[i]); } set_children_unchecked(children); } root_node_t::~root_node_t() {} std::string root_node_t::stringify() const { return "root " + stringify_flags(); } // ---------------------- generic scenegraph functions ------------------------- void set_node_enabled(wf::scene::node_ptr node, bool enabled) { bool was_enabled = node->is_enabled(); node->set_enabled(enabled); if (was_enabled != node->is_enabled()) { if (node->parent()) { node_damage_signal ev; ev.region = node->get_bounding_box(); node->parent()->emit(&ev); } update(node, update_flag::ENABLED); } } void update(node_ptr changed_node, uint32_t flags) { if ((flags & update_flag::CHILDREN_LIST) || (flags & update_flag::ENABLED) || (flags & update_flag::GEOMETRY)) { flags |= update_flag::INPUT_STATE; } if (changed_node == wf::get_core().scene()) { root_node_update_signal data; data.flags = flags; wf::get_core().scene()->emit(&data); return; } if (changed_node->parent()) { update(changed_node->parent()->shared_from_this(), flags); } } floating_inner_node_t::~floating_inner_node_t() { for (auto& node : this->children) { node->_parent = nullptr; } } } // namespace scene } wayfire-0.8.1/src/core/seat/000077500000000000000000000000001457431457600156615ustar00rootroot00000000000000wayfire-0.8.1/src/core/seat/bindings-repository-impl.hpp000066400000000000000000000021751457431457600233500ustar00rootroot00000000000000#pragma once #include "wayfire/bindings-repository.hpp" #include "hotspot-manager.hpp" #include "wayfire/signal-definitions.hpp" #include struct wf::bindings_repository_t::impl { /** * Recreate hotspots. * * The action will take place on the next idle. */ void recreate_hotspots() { this->idle_recreate_hotspots.run_once([=] () { if (enabled > 0) { hotspot_mgr.update_hotspots(activators); } else { hotspot_mgr.update_hotspots({}); } }); } binding_container_t keys; binding_container_t axes; binding_container_t buttons; binding_container_t activators; hotspot_manager_t hotspot_mgr; wf::signal::connection_t on_config_reload = [=] (wf::reload_config_signal *ev) { recreate_hotspots(); }; wf::wl_idle_call idle_recreate_hotspots; int enabled = 1; }; wayfire-0.8.1/src/core/seat/bindings-repository.cpp000066400000000000000000000147251457431457600224100ustar00rootroot00000000000000#include #include #include "bindings-repository-impl.hpp" wf::bindings_repository_t::bindings_repository_t() { priv = std::make_unique(); wf::get_core().connect(&priv->on_config_reload); } template static void push_binding(wf::binding_container_t& bindings, wf::option_sptr_t