diff options
author | Vaxry <[email protected]> | 2024-05-03 17:58:40 +0100 |
---|---|---|
committer | Vaxry <[email protected]> | 2024-05-03 18:08:04 +0100 |
commit | 8a2269272b6a9752cd48097e19c0bc0f3bdab191 (patch) | |
tree | e5381ccec7c7c09f946debc0ce5445cdca7a6fad | |
parent | d5bf15387ad3b4ee36cae7a3cd9f4a9c28790f2e (diff) | |
download | Hyprland-8a2269272b6a9752cd48097e19c0bc0f3bdab191.tar.gz Hyprland-8a2269272b6a9752cd48097e19c0bc0f3bdab191.zip |
output-management: move to new impl
-rwxr-xr-x | CMakeLists.txt | 3 | ||||
-rw-r--r-- | flake.lock | 6 | ||||
-rw-r--r-- | protocols/meson.build | 1 | ||||
-rw-r--r-- | protocols/wlr-output-management-unstable-v1.xml | 601 | ||||
-rw-r--r-- | src/Compositor.cpp | 8 | ||||
-rw-r--r-- | src/Compositor.hpp | 1 | ||||
-rw-r--r-- | src/config/ConfigManager.cpp | 7 | ||||
-rw-r--r-- | src/config/ConfigManager.hpp | 1 | ||||
-rw-r--r-- | src/events/Events.hpp | 4 | ||||
-rw-r--r-- | src/events/Misc.cpp | 10 | ||||
-rw-r--r-- | src/events/Monitors.cpp | 44 | ||||
-rw-r--r-- | src/helpers/Monitor.hpp | 1 | ||||
-rw-r--r-- | src/managers/ProtocolManager.cpp | 2 | ||||
-rw-r--r-- | src/protocols/OutputManagement.cpp | 597 | ||||
-rw-r--r-- | src/protocols/OutputManagement.hpp | 166 | ||||
-rw-r--r-- | src/render/Renderer.cpp | 72 | ||||
-rw-r--r-- | src/render/Renderer.hpp | 1 |
17 files changed, 1386 insertions, 139 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index f2939040..9775098b 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -109,7 +109,7 @@ pkg_check_modules(deps REQUIRED IMPORTED_TARGET wayland-server wayland-client wayland-cursor wayland-protocols cairo pango pangocairo pixman-1 libdrm libinput hwdata libseat libdisplay-info libliftoff libudev gbm - hyprwayland-scanner>=0.3.3 hyprlang>=0.3.2 hyprcursor>=0.1.7 + hyprwayland-scanner>=0.3.4 hyprlang>=0.3.2 hyprcursor>=0.1.7 ) file(GLOB_RECURSE SRCFILES CONFIGURE_DEPENDS "src/*.cpp") @@ -264,6 +264,7 @@ protocolNew("protocols/wlr-output-power-management-unstable-v1.xml" "wlr-output- protocolNew("protocols/virtual-keyboard-unstable-v1.xml" "virtual-keyboard-unstable-v1" true) protocolNew("protocols/wlr-virtual-pointer-unstable-v1.xml" "wlr-virtual-pointer-unstable-v1" true) protocolNew("protocols/input-method-unstable-v2.xml" "input-method-unstable-v2" true) +protocolNew("protocols/wlr-output-management-unstable-v1.xml" "wlr-output-management-unstable-v1" true) protocolNew("staging/tearing-control/tearing-control-v1.xml" "tearing-control-v1" false) protocolNew("staging/fractional-scale/fractional-scale-v1.xml" "fractional-scale-v1" false) protocolNew("unstable/xdg-output/xdg-output-unstable-v1.xml" "xdg-output-unstable-v1" false) @@ -82,11 +82,11 @@ ] }, "locked": { - "lastModified": 1714589749, - "narHash": "sha256-zzkb5vc4n/YI5KHrMie7oMZlgCKxs7zm/ybVXNL02Z0=", + "lastModified": 1714755542, + "narHash": "sha256-D0pg+ZRwrt4lavZ97Ca8clsgbPA3duLj8iEM7riaIFY=", "owner": "hyprwm", "repo": "hyprwayland-scanner", - "rev": "c8c2151c607a036ddfc790f5f70237ab984266aa", + "rev": "1270ebaa539e56d61b708c24b072b09cbbd3a828", "type": "github" }, "original": { diff --git a/protocols/meson.build b/protocols/meson.build index 38b973af..ce89e7f1 100644 --- a/protocols/meson.build +++ b/protocols/meson.build @@ -41,6 +41,7 @@ new_protocols = [ ['input-method-unstable-v2.xml'], ['virtual-keyboard-unstable-v1.xml'], ['wlr-virtual-pointer-unstable-v1.xml'], + ['wlr-output-management-unstable-v1.xml'], [wl_protocol_dir, 'staging/tearing-control/tearing-control-v1.xml'], [wl_protocol_dir, 'staging/fractional-scale/fractional-scale-v1.xml'], [wl_protocol_dir, 'unstable/xdg-output/xdg-output-unstable-v1.xml'], diff --git a/protocols/wlr-output-management-unstable-v1.xml b/protocols/wlr-output-management-unstable-v1.xml new file mode 100644 index 00000000..411e2f04 --- /dev/null +++ b/protocols/wlr-output-management-unstable-v1.xml @@ -0,0 +1,601 @@ +<?xml version="1.0" encoding="UTF-8"?> +<protocol name="wlr_output_management_unstable_v1"> + <copyright> + Copyright © 2019 Purism SPC + + 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. + </copyright> + + <description summary="protocol to configure output devices"> + This protocol exposes interfaces to obtain and modify output device + configuration. + + 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. + </description> + + <interface name="zwlr_output_manager_v1" version="4"> + <description summary="output device configuration manager"> + This interface is a manager that allows reading and writing the current + output device configuration. + + Output devices that display pixels (e.g. a physical monitor or a virtual + output in a window) are represented as heads. Heads cannot be created nor + destroyed by the client, but they can be enabled or disabled and their + properties can be changed. Each head may have one or more available modes. + + Whenever a head appears (e.g. a monitor is plugged in), it will be + advertised via the head event. Immediately after the output manager is + bound, all current heads are advertised. + + Whenever a head's properties change, the relevant wlr_output_head events + will be sent. Not all head properties will be sent: only properties that + have changed need to. + + Whenever a head disappears (e.g. a monitor is unplugged), a + wlr_output_head.finished event will be sent. + + After one or more heads appear, change or disappear, the done event will + be sent. It carries a serial which can be used in a create_configuration + request to update heads properties. + + The information obtained from this protocol should only be used for output + configuration purposes. This protocol is not designed to be a generic + output property advertisement protocol for regular clients. Instead, + protocols such as xdg-output should be used. + </description> + + <event name="head"> + <description summary="introduce a new head"> + This event introduces a new head. This happens whenever a new head + appears (e.g. a monitor is plugged in) or after the output manager is + bound. + </description> + <arg name="head" type="new_id" interface="zwlr_output_head_v1"/> + </event> + + <event name="done"> + <description summary="sent all information about current configuration"> + This event is sent after all information has been sent after binding to + the output manager object and after any subsequent changes. This applies + to child head and mode objects as well. In other words, this event is + sent whenever a head or mode is created or destroyed and whenever one of + their properties has been changed. Not all state is re-sent each time + the current configuration changes: only the actual changes are sent. + + This allows changes to the output configuration to be seen as atomic, + even if they happen via multiple events. + + A serial is sent to be used in a future create_configuration request. + </description> + <arg name="serial" type="uint" summary="current configuration serial"/> + </event> + + <request name="create_configuration"> + <description summary="create a new output configuration object"> + Create a new output configuration object. This allows to update head + properties. + </description> + <arg name="id" type="new_id" interface="zwlr_output_configuration_v1"/> + <arg name="serial" type="uint"/> + </request> + + <request name="stop"> + <description summary="stop sending events"> + Indicates the client no longer wishes to receive events for output + configuration changes. However the compositor may emit further events, + until the finished event is emitted. + + The client must not send any more requests after this one. + </description> + </request> + + <event name="finished" type="destructor"> + <description summary="the compositor has finished with the manager"> + This event indicates that the compositor is done sending manager events. + The compositor will destroy the object immediately after sending this + event, so it will become invalid and the client should release any + resources associated with it. + </description> + </event> + </interface> + + <interface name="zwlr_output_head_v1" version="4"> + <description summary="output device"> + A head is an output device. The difference between a wl_output object and + a head is that heads are advertised even if they are turned off. A head + object only advertises properties and cannot be used directly to change + them. + + A head has some read-only properties: modes, name, description and + physical_size. These cannot be changed by clients. + + Other properties can be updated via a wlr_output_configuration object. + + Properties sent via this interface are applied atomically via the + wlr_output_manager.done event. No guarantees are made regarding the order + in which properties are sent. + </description> + + <event name="name"> + <description summary="head name"> + This event describes the head name. + + The naming convention is compositor defined, but limited to alphanumeric + characters and dashes (-). Each name is unique among all wlr_output_head + objects, but if a wlr_output_head object is destroyed the same name may + be reused later. The names will also remain consistent across sessions + with the same hardware and software configuration. + + Examples of names include 'HDMI-A-1', 'WL-1', 'X11-1', etc. However, do + not assume that the name is a reflection of an underlying DRM + connector, X11 connection, etc. + + If the compositor implements the xdg-output protocol and this head is + enabled, the xdg_output.name event must report the same name. + + The name event is sent after a wlr_output_head object is created. This + event is only sent once per object, and the name does not change over + the lifetime of the wlr_output_head object. + </description> + <arg name="name" type="string"/> + </event> + + <event name="description"> + <description summary="head description"> + This event describes a human-readable description of the head. + + The description is a UTF-8 string with no convention defined for its + contents. Examples might include 'Foocorp 11" Display' or 'Virtual X11 + output via :1'. However, do not assume that the name is a reflection of + the make, model, serial of the underlying DRM connector or the display + name of the underlying X11 connection, etc. + + If the compositor implements xdg-output and this head is enabled, + the xdg_output.description must report the same description. + + The description event is sent after a wlr_output_head object is created. + This event is only sent once per object, and the description does not + change over the lifetime of the wlr_output_head object. + </description> + <arg name="description" type="string"/> + </event> + + <event name="physical_size"> + <description summary="head physical size"> + This event describes the physical size of the head. This event is only + sent if the head has a physical size (e.g. is not a projector or a + virtual device). + </description> + <arg name="width" type="int" summary="width in millimeters of the output"/> + <arg name="height" type="int" summary="height in millimeters of the output"/> + </event> + + <event name="mode"> + <description summary="introduce a mode"> + This event introduces a mode for this head. It is sent once per + supported mode. + </description> + <arg name="mode" type="new_id" interface="zwlr_output_mode_v1"/> + </event> + + <event name="enabled"> + <description summary="head is enabled or disabled"> + This event describes whether the head is enabled. A disabled head is not + mapped to a region of the global compositor space. + + When a head is disabled, some properties (current_mode, position, + transform and scale) are irrelevant. + </description> + <arg name="enabled" type="int" summary="zero if disabled, non-zero if enabled"/> + </event> + + <event name="current_mode"> + <description summary="current mode"> + This event describes the mode currently in use for this head. It is only + sent if the output is enabled. + </description> + <arg name="mode" type="object" interface="zwlr_output_mode_v1"/> + </event> + + <event name="position"> + <description summary="current position"> + This events describes the position of the head in the global compositor + space. It is only sent if the output is enabled. + </description> + <arg name="x" type="int" + summary="x position within the global compositor space"/> + <arg name="y" type="int" + summary="y position within the global compositor space"/> + </event> + + <event name="transform"> + <description summary="current transformation"> + This event describes the transformation currently applied to the head. + It is only sent if the output is enabled. + </description> + <arg name="transform" type="int" enum="wl_output.transform"/> + </event> + + <event name="scale"> + <description summary="current scale"> + This events describes the scale of the head in the global compositor + space. It is only sent if the output is enabled. + </description> + <arg name="scale" type="fixed"/> + </event> + + <event name="finished"> + <description summary="the head has disappeared"> + This event indicates that the head is no longer available. The head + object becomes inert. Clients should send a destroy request and release + any resources associated with it. + </description> + </event> + + <!-- Version 2 additions --> + + <event name="make" since="2"> + <description summary="head manufacturer"> + This event describes the manufacturer of the head. + + This must report the same make as the wl_output interface does in its + geometry event. + + Together with the model and serial_number events the purpose is to + allow clients to recognize heads from previous sessions and for example + load head-specific configurations back. + + It is not guaranteed this event will be ever sent. A reason for that + can be that the compositor does not have information about the make of + the head or the definition of a make is not sensible in the current + setup, for example in a virtual session. Clients can still try to + identify the head by available information from other events but should + be aware that there is an increased risk of false positives. + + It is not recommended to display the make string in UI to users. For + that the string provided by the description event should be preferred. + </description> + <arg name="make" type="string"/> + </event> + + <event name="model" since="2"> + <description summary="head model"> + This event describes the model of the head. + + This must report the same model as the wl_output interface does in its + geometry event. + + Together with the make and serial_number events the purpose is to + allow clients to recognize heads from previous sessions and for example + load head-specific configurations back. + + It is not guaranteed this event will be ever sent. A reason for that + can be that the compositor does not have information about the model of + the head or the definition of a model is not sensible in the current + setup, for example in a virtual session. Clients can still try to + identify the head by available information from other events but should + be aware that there is an increased risk of false positives. + + It is not recommended to display the model string in UI to users. For + that the string provided by the description event should be preferred. + </description> + <arg name="model" type="string"/> + </event> + + <event name="serial_number" since="2"> + <description summary="head serial number"> + This event describes the serial number of the head. + + Together with the make and model events the purpose is to allow clients + to recognize heads from previous sessions and for example load head- + specific configurations back. + + It is not guaranteed this event will be ever sent. A reason for that + can be that the compositor does not have information about the serial + number of the head or the definition of a serial number is not sensible + in the current setup. Clients can still try to identify the head by + available information from other events but should be aware that there + is an increased risk of false positives. + + It is not recommended to display the serial_number string in UI to + users. For that the string provided by the description event should be + preferred. + </description> + <arg name="serial_number" type="string"/> + </event> + + <!-- Version 3 additions --> + + <request name="release" type="destructor" since="3"> + <description summary="destroy the head object"> + This request indicates that the client will no longer use this head + object. + </description> + </request> + + <!-- Version 4 additions --> + + <enum name="adaptive_sync_state" since="4"> + <entry name="disabled" value="0" summary="adaptive sync is disabled"/> + <entry name="enabled" value="1" summary="adaptive sync is enabled"/> + </enum> + + <event name="adaptive_sync" since="4"> + <description summary="current adaptive sync state"> + This event describes whether adaptive sync is currently enabled for + the head or not. Adaptive sync is also known as Variable Refresh + Rate or VRR. + </description> + <arg name="state" type="uint" enum="adaptive_sync_state"/> + </event> + </interface> + + <interface name="zwlr_output_mode_v1" version="3"> + <description summary="output mode"> + This object describes an output mode. + + Some heads don't support output modes, in which case modes won't be + advertised. + + Properties sent via this interface are applied atomically via the + wlr_output_manager.done event. No guarantees are made regarding the order + in which properties are sent. + </description> + + <event name="size"> + <description summary="mode size"> + This event describes the mode size. The size is given in physical + hardware units of the output device. This is not necessarily the same as + the output size in the global compositor space. For instance, the output + may be scaled or transformed. + </description> + <arg name="width" type="int" summary="width of the mode in hardware units"/> + <arg name="height" type="int" summary="height of the mode in hardware units"/> + </event> + + <event name="refresh"> + <description summary="mode refresh rate"> + This event describes the mode's fixed vertical refresh rate. It is only + sent if the mode has a fixed refresh rate. + </description> + <arg name="refresh" type="int" summary="vertical refresh rate in mHz"/> + </event> + + <event name="preferred"> + <description summary="mode is preferred"> + This event advertises this mode as preferred. + </description> + </event> + + <event name="finished"> + <description summary="the mode has disappeared"> + This event indicates that the mode is no longer available. The mode + object becomes inert. Clients should send a destroy request and release + any resources associated with it. + </description> + </event> + + <!-- Version 3 additions --> + + <request name="release" type="destructor" since="3"> + <description summary="destroy the mode object"> + This request indicates that the client will no longer use this mode + object. + </description> + </request> + </interface> + + <interface name="zwlr_output_configuration_v1" version="4"> + <description summary="output configuration"> + This object is used by the client to describe a full output configuration. + + First, the client needs to setup the output configuration. Each head can + be either enabled (and configured) or disabled. It is a protocol error to + send two enable_head or disable_head requests with the same head. It is a + protocol error to omit a head in a configuration. + + Then, the client can apply or test the configuration. The compositor will + then reply with a succeeded, failed or cancelled event. Finally the client + should destroy the configuration object. + </description> + + <enum name="error"> + <entry name="already_configured_head" value="1" + summary="head has been configured twice"/> + <entry name="unconfigured_head" value="2" + summary="head has not been configured"/> + <entry name="already_used" value="3" + summary="request sent after configuration has been applied or tested"/> + </enum> + + <request name="enable_head"> + <description summary="enable and configure a head"> + Enable a head. This request creates a head configuration object that can + be used to change the head's properties. + </description> + <arg name="id" type="new_id" interface="zwlr_output_configuration_head_v1" + summary="a new object to configure the head"/> + <arg name="head" type="object" interface="zwlr_output_head_v1" + summary="the head to be enabled"/> + </request> + + <request name="disable_head"> + <description summary="disable a head"> + Disable a head. + </description> + <arg name="head" type="object" interface="zwlr_output_head_v1" + summary="the head to be disabled"/> + </request> + + <request name="apply"> + <description summary="apply the configuration"> + Apply the new output configuration. + + In case the configuration is successfully applied, there is no guarantee + that the new output state matches completely the requested + configuration. For instance, a compositor might round the scale if it + doesn't support fractional scaling. + + After this request has been sent, the compositor must respond with an + succeeded, failed or cancelled event. Sending a request that isn't the + destructor is a protocol error. + </description> + </request> + + <request name="test"> + <description summary="test the configuration"> + Test the new output configuration. The configuration won't be applied, + but will only be validated. + + Even if the compositor succeeds to test a configuration, applying it may + fail. + + After this request has been sent, the compositor must respond with an + succeeded, failed or cancelled event. Sending a request that isn't the + destructor is a protocol error. + </description> + </request> + + <event name="succeeded"> + <description summary="configuration changes succeeded"> + Sent after the compositor has successfully applied the changes or + tested them. + + Upon receiving this event, the client should destroy this object. + + If the current configuration has changed, events to describe the changes + will be sent followed by a wlr_output_manager.done event. + </description> + </event> + + <event name="failed"> + <description summary="configuration changes failed"> + Sent if the compositor rejects the changes or failed to apply them. The + compositor should revert any changes made by the apply request that + triggered this event. + + Upon receiving this event, the client should destroy this object. + </description> + </event> + + <event name="cancelled"> + <description summary="configuration has been cancelled"> + Sent if the compositor cancels the configuration because the state of an + output changed and the client has outdated information (e.g. after an + output has been hotplugged). + + The client can create a new configuration with a newer serial and try + again. + + Upon receiving this event, the client should destroy this object. + </description> + </event> + + <request name="destroy" type="destructor"> + <description summary="destroy the output configuration"> + Using this request a client can tell the compositor that it is not going + to use the configuration object anymore. Any changes to the outputs + that have not been applied will be discarded. + + This request also destroys wlr_output_configuration_head objects created + via this object. + </description> + </request> + </interface> + + <interface name="zwlr_output_configuration_head_v1" version="4"> + <description summary="head configuration"> + This object is used by the client to update a single head's configuration. + + It is a protocol error to set the same property twice. + </description> + + <enum name="error"> + <entry name="already_set" value="1" summary="property has already been set"/> + <entry name="invalid_mode" value="2" summary="mode doesn't belong to head"/> + <entry name="invalid_custom_mode" value="3" summary="mode is invalid"/> + <entry name="invalid_transform" value="4" summary="transform value outside enum"/> + <entry name="invalid_scale" value="5" summary="scale negative or zero"/> + <entry name="invalid_adaptive_sync_state" value="6" since="4" + summary="invalid enum value used in the set_adaptive_sync request"/> + </enum> + + <request name="set_mode"> + <description summary="set the mode"> + This request sets the head's mode. + </description> + <arg name="mode" type="object" interface="zwlr_output_mode_v1"/> + </request> + + <request name="set_custom_mode"> + <description summary="set a custom mode"> + This request assigns a custom mode to the head. The size is given in + physical hardware units of the output device. If set to zero, the + refresh rate is unspecified. + + It is a protocol error to set both a mode and a custom mode. + </description> + <arg name="width" type="int" summary="width of the mode in hardware units"/> + <arg name="height" type="int" summary="height of the mode in hardware units"/> + <arg name="refresh" type="int" summary="vertical refresh rate in mHz or zero"/> + </request> + + <request name="set_position"> + <description summary="set the position"> + This request sets the head's position in the global compositor space. + </description> + <arg name="x" type="int" summary="x position in the global compositor space"/> + <arg name="y" type="int" summary="y position in the global compositor space"/> + </request> + + <request name="set_transform"> + <description summary="set the transform"> + This request sets the head's transform. + </description> + <arg name="transform" type="int" enum="wl_output.transform"/> + </request> + + <request name="set_scale"> + <description summary="set the scale"> + This request sets the head's scale. + </description> + <arg name="scale" type="fixed"/> + </request> + + <!-- Version 4 additions --> + + <request name="set_adaptive_sync" since="4"> + <description summary="enable/disable adaptive sync"> + This request enables/disables adaptive sync. Adaptive sync is also + known as Variable Refresh Rate or VRR. + </description> + <arg name="state" type="uint" enum="zwlr_output_head_v1.adaptive_sync_state"/> + </request> + </interface> +</protocol> diff --git a/src/Compositor.cpp b/src/Compositor.cpp index ea88bec1..e28d86c7 100644 --- a/src/Compositor.cpp +++ b/src/Compositor.cpp @@ -243,8 +243,6 @@ void CCompositor::initServer() { m_sWLRServerDecoMgr = wlr_server_decoration_manager_create(m_sWLDisplay); wlr_server_decoration_manager_set_default_mode(m_sWLRServerDecoMgr, WLR_SERVER_DECORATION_MANAGER_MODE_SERVER); - m_sWLROutputMgr = wlr_output_manager_v1_create(m_sWLDisplay); - m_sWRLDRMLeaseMgr = wlr_drm_lease_v1_manager_create(m_sWLDisplay, m_sWLRBackend); if (!m_sWRLDRMLeaseMgr) { Debug::log(INFO, "Failed to create wlr_drm_lease_v1_manager"); @@ -300,9 +298,6 @@ void CCompositor::initAllSignals() { addWLSignal(&m_sSeat.seat->events.request_set_selection, &Events::listen_requestSetSel, &m_sSeat, "Seat"); addWLSignal(&m_sSeat.seat->events.request_set_primary_selection, &Events::listen_requestSetPrimarySel, &m_sSeat, "Seat"); addWLSignal(&m_sWLRLayerShell->events.new_surface, &Events::listen_newLayerSurface, m_sWLRLayerShell, "LayerShell"); - addWLSignal(&m_sWLROutputLayout->events.change, &Events::listen_change, m_sWLROutputLayout, "OutputLayout"); - addWLSignal(&m_sWLROutputMgr->events.apply, &Events::listen_outputMgrApply, m_sWLROutputMgr, "OutputMgr"); - addWLSignal(&m_sWLROutputMgr->events.test, &Events::listen_outputMgrTest, m_sWLROutputMgr, "OutputMgr"); addWLSignal(&m_sWLRRenderer->events.destroy, &Events::listen_RendererDestroy, m_sWLRRenderer, "WLRRenderer"); if (m_sWRLDRMLeaseMgr) @@ -340,9 +335,6 @@ void CCompositor::removeAllSignals() { removeWLSignal(&Events::listen_requestSetSel); removeWLSignal(&Events::listen_requestSetPrimarySel); removeWLSignal(&Events::listen_newLayerSurface); - removeWLSignal(&Events::listen_change); - removeWLSignal(&Events::listen_outputMgrApply); - removeWLSignal(&Events::listen_outputMgrTest); removeWLSignal(&Events::listen_RendererDestroy); if (m_sWRLDRMLeaseMgr) diff --git a/src/Compositor.hpp b/src/Compositor.hpp index 33b4a04e..f252d131 100644 --- a/src/Compositor.hpp +++ b/src/Compositor.hpp @@ -55,7 +55,6 @@ class CCompositor { wlr_layer_shell_v1* m_sWLRLayerShell; wlr_xdg_shell* m_sWLRXDGShell; wlr_cursor* m_sWLRCursor; - wlr_output_manager_v1* m_sWLROutputMgr; wlr_presentation* m_sWLRPresentation; wlr_egl* m_sWLREGL; int m_iDRMFD; diff --git a/src/config/ConfigManager.cpp b/src/config/ConfigManager.cpp index a2db6040..652a063c 100644 --- a/src/config/ConfigManager.cpp +++ b/src/config/ConfigManager.cpp @@ -16,6 +16,7 @@ #include <fstream> #include <iostream> #include <sstream> +#include <ranges> extern "C" char** environ; @@ -954,7 +955,7 @@ std::string CConfigManager::getDeviceString(const std::string& dev, const std::s } SMonitorRule CConfigManager::getMonitorRuleFor(const CMonitor& PMONITOR) { - for (auto& r : m_dMonitorRules) { + for (auto& r : m_dMonitorRules | std::views::reverse) { if (PMONITOR.matchesStaticSelector(r.name)) { return r; } @@ -1236,6 +1237,10 @@ void CConfigManager::dispatchExecOnce() { g_pCompositor->performUserChecks(); } +void CConfigManager::appendMonitorRule(const SMonitorRule& r) { + m_dMonitorRules.emplace_back(r); +} + void CConfigManager::performMonitorReload() { bool overAgain = false; diff --git a/src/config/ConfigManager.hpp b/src/config/ConfigManager.hpp index 2e723656..7fc132c6 100644 --- a/src/config/ConfigManager.hpp +++ b/src/config/ConfigManager.hpp @@ -127,6 +127,7 @@ class CConfigManager { void dispatchExecOnce(); void performMonitorReload(); + void appendMonitorRule(const SMonitorRule&); bool m_bWantsMonitorReload = false; bool m_bForceReload = false; bool m_bNoMonitorReload = false; diff --git a/src/events/Events.hpp b/src/events/Events.hpp index c7e98e65..5fa93c49 100644 --- a/src/events/Events.hpp +++ b/src/events/Events.hpp @@ -65,10 +65,6 @@ namespace Events { LISTENER(requestSetSel); LISTENER(requestSetPrimarySel); - // outputMgr - LISTENER(outputMgrApply); - LISTENER(outputMgrTest); - // Monitor part 2 the sequel DYNLISTENFUNC(monitorFrame); DYNLISTENFUNC(monitorDestroy); diff --git a/src/events/Misc.cpp b/src/events/Misc.cpp index 13dc85a3..a67a6753 100644 --- a/src/events/Misc.cpp +++ b/src/events/Misc.cpp @@ -16,16 +16,6 @@ // // // ------------------------------ // -void Events::listener_outputMgrApply(wl_listener* listener, void* data) { - const auto CONFIG = (wlr_output_configuration_v1*)data; - g_pHyprRenderer->outputMgrApplyTest(CONFIG, false); -} - -void Events::listener_outputMgrTest(wl_listener* listener, void* data) { - const auto CONFIG = (wlr_output_configuration_v1*)data; - g_pHyprRenderer->outputMgrApplyTest(CONFIG, true); -} - void Events::listener_leaseRequest(wl_listener* listener, void* data) { const auto REQUEST = (wlr_drm_lease_request_v1*)data; struct wlr_drm_lease_v1* lease = wlr_drm_lease_request_v1_grant(REQUEST); diff --git a/src/events/Monitors.cpp b/src/events/Monitors.cpp index 11b7a25c..69cc912d 100644 --- a/src/events/Monitors.cpp +++ b/src/events/Monitors.cpp @@ -16,50 +16,6 @@ // // // --------------------------------------------------------- // -void Events::listener_change(wl_listener* listener, void* data) { - // layout got changed, let's update monitors. - const auto CONFIG = wlr_output_configuration_v1_create(); - - if (!CONFIG) - return; - - for (auto& m : g_pCompositor->m_vRealMonitors) { - if (!m->output) - continue; - - if (g_pCompositor->m_pUnsafeOutput == m.get()) - continue; - - const auto CONFIGHEAD = wlr_output_configuration_head_v1_create(CONFIG, m->output); - - CBox BOX; - wlr_output_layout_get_box(g_pCompositor->m_sWLROutputLayout, m->output, BOX.pWlr()); - BOX.applyFromWlr(); - - //m->vecSize.x = BOX.width; - // m->vecSize.y = BOX.height; - m->vecPosition.x = BOX.x; - m->vecPosition.y = BOX.y; - - CONFIGHEAD->state.enabled = m->output->enabled; - CONFIGHEAD->state.mode = m->output->current_mode; - if (!m->output->current_mode) { - CONFIGHEAD->state.custom_mode = { - m->output->width, - m->output->height, - m->output->refresh, - }; - } - CONFIGHEAD->state.x = m->vecPosition.x; - CONFIGHEAD->state.y = m->vecPosition.y; - CONFIGHEAD->state.transform = m->transform; - CONFIGHEAD->state.scale = m->scale; - CONFIGHEAD->state.adaptive_sync_enabled = m->vrrActive; - } - - wlr_output_manager_v1_set_configuration(g_pCompositor->m_sWLROutputMgr, CONFIG); -} - static void checkDefaultCursorWarp(std::shared_ptr<CMonitor>* PNEWMONITORWRAP, std::string monitorName) { const auto PNEWMONITOR = PNEWMONITORWRAP->get(); diff --git a/src/helpers/Monitor.hpp b/src/helpers/Monitor.hpp index 81997514..a93d23af 100644 --- a/src/helpers/Monitor.hpp +++ b/src/helpers/Monitor.hpp @@ -96,6 +96,7 @@ class CMonitor { float xwaylandScale = 1.f; std::array<float, 9> projMatrix = {0}; std::optional<Vector2D> forceSize; + wlr_output_mode* currentMode = nullptr; bool dpmsStatus = true; bool vrrActive = false; // this can be TRUE even if VRR is not active in the case that this display does not support it. diff --git a/src/managers/ProtocolManager.cpp b/src/managers/ProtocolManager.cpp index 8ec2a35f..115e10f4 100644 --- a/src/managers/ProtocolManager.cpp +++ b/src/managers/ProtocolManager.cpp @@ -22,6 +22,7 @@ #include "../protocols/InputMethodV2.hpp" #include "../protocols/VirtualKeyboard.hpp" #include "../protocols/VirtualPointer.hpp" +#include "../protocols/OutputManagement.hpp" CProtocolManager::CProtocolManager() { @@ -47,6 +48,7 @@ CProtocolManager::CProtocolManager() { PROTO::ime = std::make_unique<CInputMethodV2Protocol>(&zwp_input_method_manager_v2_interface, 1, "IMEv2"); PROTO::virtualKeyboard = std::make_unique<CVirtualKeyboardProtocol>(&zwp_virtual_keyboard_manager_v1_interface, 1, "VirtualKeyboard"); PROTO::virtualPointer = std::make_unique<CVirtualPointerProtocol>(&zwlr_virtual_pointer_manager_v1_interface, 2, "VirtualPointer"); + PROTO::outputManagement = std::make_unique<COutputManagementProtocol>(&zwlr_output_manager_v1_interface, 4, "OutputManagement"); // Old protocol implementations. // TODO: rewrite them to use hyprwayland-scanner. diff --git a/src/protocols/OutputManagement.cpp b/src/protocols/OutputManagement.cpp new file mode 100644 index 00000000..18be636d --- /dev/null +++ b/src/protocols/OutputManagement.cpp @@ -0,0 +1,597 @@ +#include "OutputManagement.hpp" +#include <algorithm> +#include "../Compositor.hpp" + +#define LOGM PROTO::outputManagement->protoLog + +COutputManager::COutputManager(SP<CZwlrOutputManagerV1> resource_) : resource(resource_) { + if (!good()) + return; + + LOGM(LOG, "New OutputManager registered"); + + resource->setOnDestroy([this](CZwlrOutputManagerV1* r) { PROTO::outputManagement->destroyResource(this); }); + + resource->setStop([this](CZwlrOutputManagerV1* r) { stopped = true; }); + + resource->setCreateConfiguration([this](CZwlrOutputManagerV1* r, uint32_t id, uint32_t serial) { + LOGM(LOG, "Creating new configuration"); + + const auto RESOURCE = PROTO::outputManagement->m_vConfigurations.emplace_back( + std::make_shared<COutputConfiguration>(std::make_shared<CZwlrOutputConfigurationV1>(resource->client(), resource->version(), id), self.lock())); + + if (!RESOURCE->good()) { + resource->noMemory(); + PROTO::outputManagement->m_vConfigurations.pop_back(); + return; + } + }); + + // send all heads at start + for (auto& m : g_pCompositor->m_vRealMonitors) { + if (m.get() == g_pCompositor->m_pUnsafeOutput) + continue; + + LOGM(LOG, " | sending output head for {}", m->szName); + + makeAndSendNewHead(m.get()); + } + + sendDone(); +} + +bool COutputManager::good() { + return resource->resource(); +} + +void COutputManager::makeAndSendNewHead(CMonitor* pMonitor) { + if (stopped) + return; + + const auto RESOURCE = + PROTO::outputManagement->m_vHeads.emplace_back(std::make_shared<COutputHead>(std::make_shared<CZwlrOutputHeadV1>(resource->client(), resource->version(), 0), pMonitor)); + + if (!RESOURCE->good()) { + resource->noMemory(); + PROTO::outputManagement->m_vHeads.pop_back(); + return; + } + + heads.push_back(RESOURCE); + + resource->sendHead(RESOURCE->resource.get()); + RESOURCE->sendAllData(); +} + +void COutputManager::ensureMonitorSent(CMonitor* pMonitor) { + if (pMonitor == g_pCompositor->m_pUnsafeOutput) + return; + + for (auto& hw : heads) { + auto h = hw.lock(); + + if (!h) + continue; + + if (h->pMonitor == pMonitor) + return; + } + + makeAndSendNewHead(pMonitor); + + sendDone(); +} + +void COutputManager::sendDone() { + resource->sendDone(wl_display_next_serial(g_pCompositor->m_sWLDisplay)); +} + +COutputHead::COutputHead(SP<CZwlrOutputHeadV1> resource_, CMonitor* pMonitor_) : resource(resource_), pMonitor(pMonitor_) { + if (!good()) + return; + + resource->setRelease([this](CZwlrOutputHeadV1* r) { PROTO::outputManagement->destroyResource(this); }); + resource->setOnDestroy([this](CZwlrOutputHeadV1* r) { PROTO::outputManagement->destroyResource(this); }); + + listeners.monitorDestroy = pMonitor->events.destroy.registerListener([this](std::any d) { + resource->sendFinished(); + + for (auto& mw : modes) { + auto m = mw.lock(); + + if (!m) + continue; + + m->resource->sendFinished(); + } + + pMonitor = nullptr; + }); + + listeners.monitorModeChange = pMonitor->events.modeChanged.registerListener([this](std::any d) { updateMode(); }); +} + +bool COutputHead::good() { + return resource->resource(); +} + +void COutputHead::sendAllData() { + const auto VERSION = resource->version(); + + resource->sendName(pMonitor->szName.c_str()); + resource->sendDescription(pMonitor->szDescription.c_str()); + if (pMonitor->output->phys_width > 0 && pMonitor->output->phys_height > 0) + resource->sendPhysicalSize(pMonitor->output->phys_width, pMonitor->output->phys_height); + resource->sendEnabled(pMonitor->m_bEnabled); + + if (pMonitor->m_bEnabled) { + resource->sendPosition(pMonitor->vecPosition.x, pMonitor->vecPosition.y); + resource->sendTransform(pMonitor->transform); + resource->sendScale(wl_fixed_from_double(pMonitor->scale)); + } + + if (pMonitor->output->make && VERSION >= 2) + resource->sendMake(pMonitor->output->make); + if (pMonitor->output->model && VERSION >= 2) + resource->sendModel(pMonitor->output->model); + if (pMonitor->output->serial && VERSION >= 2) + resource->sendSerialNumber(pMonitor->output->serial); + + if (VERSION >= 4) + resource->sendAdaptiveSync(pMonitor->vrrActive ? ZWLR_OUTPUT_HEAD_V1_ADAPTIVE_SYNC_STATE_ENABLED : ZWLR_OUTPUT_HEAD_V1_ADAPTIVE_SYNC_STATE_DISABLED); + + // send all available modes + + if (modes.empty()) { + if (!wl_list_empty(&pMonitor->output->modes)) { + wlr_output_mode* mode; + + wl_list_for_each(mode, &pMonitor->output->modes, link) { + makeAndSendNewMode(mode); + } + } else + makeAndSendNewMode(nullptr); + } + + // send current mode + if (pMonitor->m_bEnabled) { + for (auto& mw : modes) { + auto m = mw.lock(); + + if (!m) + continue; + + if (m->mode == pMonitor->currentMode) { + if (m->mode) + LOGM(LOG, " | sending current mode for {}: {}x{}@{}", pMonitor->szName, m->mode->width, m->mode->height, m->mode->refresh); + else + LOGM(LOG, " | sending current mode for {}: null (fake)", pMonitor->szName); + resource->sendCurrentMode(m->resource->resource()); + break; + } + } + } +} + +void COutputHead::updateMode() { + resource->sendEnabled(pMonitor->m_bEnabled); + + if (pMonitor->m_bEnabled) { + resource->sendPosition(pMonitor->vecPosition.x, pMonitor->vecPosition.y); + resource->sendTransform(pMonitor->transform); + resource->sendScale(wl_fixed_from_double(pMonitor->scale)); + } + + if (resource->version() >= 4) + resource->sendAdaptiveSync(pMonitor->vrrActive ? ZWLR_OUTPUT_HEAD_V1_ADAPTIVE_SYNC_STATE_ENABLED : ZWLR_OUTPUT_HEAD_V1_ADAPTIVE_SYNC_STATE_DISABLED); + + if (pMonitor->m_bEnabled) { + for (auto& mw : modes) { + auto m = mw.lock(); + + if (!m) + continue; + + if (m->mode == pMonitor->currentMode) { + if (m->mode) + LOGM(LOG, " | sending current mode for {}: {}x{}@{}", pMonitor->szName, m->mode->width, m->mode->height, m->mode->refresh); + else + LOGM(LOG, " | sending current mode for {}: null (fake)", pMonitor->szName); + resource->sendCurrentMode(m->resource->resource()); + break; + } + } + } +} + +void COutputHead::makeAndSendNewMode(wlr_output_mode* mode) { + const auto RESOURCE = + PROTO::outputManagement->m_vModes.emplace_back(std::make_shared<COutputMode>(std::make_shared<CZwlrOutputModeV1>(resource->client(), resource->version(), 0), mode)); + + if (!RESOURCE->good()) { + resource->noMemory(); + PROTO::outputManagement->m_vModes.pop_back(); + return; + } + + modes.push_back(RESOURCE); + resource->sendMode(RESOURCE->resource.get()); + RESOURCE->sendAllData(); +} + +CMonitor* COutputHead::monitor() { + return pMonitor; +} + +COutputMode::COutputMode(SP<CZwlrOutputModeV1> resource_, wlr_output_mode* mode_) : resource(resource_), mode(mode_) { + if (!good()) + return; + + resource->setRelease([this](CZwlrOutputModeV1* r) { PROTO::outputManagement->destroyResource(this); }); + resource->setOnDestroy([this](CZwlrOutputModeV1* r) { PROTO::outputManagement->destroyResource(this); }); +} + +void COutputMode::sendAllData() { + if (!mode) + return; + + LOGM(LOG, " | sending mode {}x{}@{}mHz, pref: {}", mode->width, mode->height, mode->refresh, mode->preferred); + + resource->sendSize(mode->width, mode->height); + if (mode->refresh > 0) + resource->sendRefresh(mode->refresh); + if (mode->preferred) + resource->sendPreferred(); +} + +bool COutputMode::good() { + return resource->resource(); +} + +wlr_output_mode* COutputMode::getMode() { + return mode; +} + +COutputConfiguration::COutputConfiguration(SP<CZwlrOutputConfigurationV1> resource_, SP<COutputManager> owner_) : resource(resource_), owner(owner_) { + if (!good()) + return; + + resource->setDestroy([this](CZwlrOutputConfigurationV1* r) { PROTO::outputManagement->destroyResource(this); }); + resource->setOnDestroy([this](CZwlrOutputConfigurationV1* r) { PROTO::outputManagement->destroyResource(this); }); + + resource->setEnableHead([this](CZwlrOutputConfigurationV1* r, uint32_t id, wl_resource* outputHead) { + const auto HEAD = PROTO::outputManagement->headFromResource(outputHead); + + if (!HEAD) { + LOGM(ERR, "No head in setEnableHead??"); + return; + } + + const auto PMONITOR = HEAD->monitor(); + + if (!PMONITOR) { + LOGM(ERR, "No monitor in setEnableHead??"); + return; + } + + const auto RESOURCE = PROTO::outputManagement->m_vConfigurationHeads.emplace_back( + std::make_shared<COutputConfigurationHead>(std::make_shared<CZwlrOutputConfigurationHeadV1>(resource->client(), resource->version(), id), PMONITOR)); + + if (!RESOURCE->good()) { + resource->noMemory(); + PROTO::outputManagement->m_vConfigurationHeads.pop_back(); + return; + } + + heads.push_back(RESOURCE); + + LOGM(LOG, "enableHead on {}. For now, doing nothing. Waiting for apply().", PMONITOR->szName); + }); + + resource->setDisableHead([this](CZwlrOutputConfigurationV1* r, wl_resource* outputHead) { + const auto HEAD = PROTO::outputManagement->headFromResource(outputHead); + + if (!HEAD) { + LOGM(ERR, "No head in setDisableHead??"); + return; + } + + const auto PMONITOR = HEAD->monitor(); + + if (!PMONITOR) { + LOGM(ERR, "No monitor in setDisableHead??"); + return; + } + + LOGM(LOG, "disableHead on {}", PMONITOR->szName); + + PMONITOR->activeMonitorRule.disabled = true; + g_pHyprRenderer->applyMonitorRule(PMONITOR, &PMONITOR->activeMonitorRule, false); + }); + + resource->setTest([this](CZwlrOutputConfigurationV1* r) { + const auto SUCCESS = applyTestConfiguration(true); + + if (SUCCESS) + resource->sendSucceeded(); + else + resource->sendFailed(); + }); + + resource->setApply([this](CZwlrOutputConfigurationV1* r) { + const auto SUCCESS = applyTestConfiguration(false); + + if (SUCCESS) + resource->sendSucceeded(); + else + resource->sendFailed(); + + owner.lock()->sendDone(); + }); +} + +bool COutputConfiguration::good() { + return resource->resource(); +} + +bool COutputConfiguration::applyTestConfiguration(bool test) { + if (test) { + LOGM(WARN, "TODO: STUB: applyTestConfiguration for test not implemented, returning true."); + return true; + } + + LOGM(LOG, "Applying configuration"); + + for (auto& headw : heads) { + auto head = headw.lock(); + + if (!head) + continue; + + const auto PMONITOR = head->pMonitor; + + if (!PMONITOR) + continue; + + LOGM(LOG, "Applying config for monitor {}", PMONITOR->szName); + + SMonitorRule newRule = PMONITOR->activeMonitorRule; + newRule.name = PMONITOR->szName; + + if (head->committedProperties & COutputConfigurationHead::eCommittedProperties::OUTPUT_HEAD_COMMITTED_MODE) { + newRule.resolution = {head->state.mode.lock()->getMode()->width, head->state.mode.lock()->getMode()->height}; + newRule.refreshRate = head->state.mode.lock()->getMode()->refresh / 1000.F; + } else if (head->committedProperties & COutputConfigurationHead::eCommittedProperties::OUTPUT_HEAD_COMMITTED_CUSTOM_MODE) { + newRule.resolution = head->state.customMode.size; + newRule.refreshRate = head->state.customMode.refresh / 1000.F; + } + + if (head->committedProperties & COutputConfigurationHead::eCommittedProperties::OUTPUT_HEAD_COMMITTED_POSITION) + newRule.offset = head->state.position; + + if (head->committedProperties & COutputConfigurationHead::eCommittedProperties::OUTPUT_HEAD_COMMITTED_ADAPTIVE_SYNC) + newRule.vrr = head->state.adaptiveSync; + + if (head->committedProperties & COutputConfigurationHead::eCommittedProperties::OUTPUT_HEAD_COMMITTED_SCALE) + newRule.scale = head->state.scale; + + if (head->committedProperties & COutputConfigurationHead::eCommittedProperties::OUTPUT_HEAD_COMMITTED_TRANSFORM) + newRule.transform = head->state.transform; + + // reset properties for next set. + head->committedProperties = 0; + + g_pConfigManager->appendMonitorRule(newRule); + g_pConfigManager->m_bWantsMonitorReload = true; + } + + LOGM(LOG, "Applied configuration"); + + return true; +} + +COutputConfigurationHead::COutputConfigurationHead(SP<CZwlrOutputConfigurationHeadV1> resource_, CMonitor* pMonitor_) : resource(resource_), pMonitor(pMonitor_) { + if (!good()) + return; + + resource->setOnDestroy([this](CZwlrOutputConfigurationHeadV1* r) { PROTO::outputManagement->destroyResource(this); }); + + listeners.monitorDestroy = pMonitor->events.destroy.registerListener([this](std::any d) { pMonitor = nullptr; }); + + resource->setSetMode([this](CZwlrOutputConfigurationHeadV1* r, wl_resource* outputMode) { + const auto MODE = PROTO::outputManagement->modeFromResource(outputMode); + + if (!MODE || !MODE->getMode()) { + LOGM(ERR, "No mode in setMode??"); + return; + } + + if (!pMonitor) { + LOGM(ERR, "setMode on inert resource"); + return; + } + + if (committedProperties & OUTPUT_HEAD_COMMITTED_MODE) { + resource->error(ZWLR_OUTPUT_CONFIGURATION_HEAD_V1_ERROR_ALREADY_SET, "Property already set"); + return; + } + + committedProperties |= OUTPUT_HEAD_COMMITTED_MODE; + state.mode = MODE; + + LOGM(LOG, " | configHead for {}: set mode to {}x{}@{}", pMonitor->szName, MODE->getMode()->width, MODE->getMode()->height, MODE->getMode()->refresh); + }); + + resource->setSetCustomMode([this](CZwlrOutputConfigurationHeadV1* r, int32_t w, int32_t h, int32_t refresh) { + if (!pMonitor) { + LOGM(ERR, "setCustomMode on inert resource"); + return; + } + + if (committedProperties & OUTPUT_HEAD_COMMITTED_CUSTOM_MODE) { + resource->error(ZWLR_OUTPUT_CONFIGURATION_HEAD_V1_ERROR_ALREADY_SET, "Property already set"); + return; + } + + if (w <= 0 || h <= 0 || refresh <= 100) { + resource->error(ZWLR_OUTPUT_CONFIGURATION_HEAD_V1_ERROR_INVALID_CUSTOM_MODE, "Invalid mode"); + return; + } + + committedProperties |= OUTPUT_HEAD_COMMITTED_CUSTOM_MODE; + state.customMode = {{w, h}, (uint32_t)refresh}; + + LOGM(LOG, " | configHead for {}: set custom mode to {}x{}@{}", pMonitor->szName, w, h, refresh); + }); + + resource->setSetPosition([this](CZwlrOutputConfigurationHeadV1* r, int32_t x, int32_t y) { + if (!pMonitor) { + LOGM(ERR, "setMode on inert resource"); + return; + } + + if (committedProperties & OUTPUT_HEAD_COMMITTED_POSITION) { + resource->error(ZWLR_OUTPUT_CONFIGURATION_HEAD_V1_ERROR_ALREADY_SET, "Property already set"); + return; + } + + committedProperties |= OUTPUT_HEAD_COMMITTED_POSITION; + state.position = {x, y}; + + LOGM(LOG, " | configHead for {}: set pos to {}, {}", pMonitor->szName, x, y); + }); + + resource->setSetTransform([this](CZwlrOutputConfigurationHeadV1* r, int32_t transform) { + if (!pMonitor) { + LOGM(ERR, "setMode on inert resource"); + return; + } + + if (committedProperties & OUTPUT_HEAD_COMMITTED_TRANSFORM) { + resource->error(ZWLR_OUTPUT_CONFIGURATION_HEAD_V1_ERROR_ALREADY_SET, "Property already set"); + return; + } + + if (transform < 0 || transform > 7) { + resource->error(ZWLR_OUTPUT_CONFIGURATION_HEAD_V1_ERROR_INVALID_TRANSFORM, "Invalid transform"); + return; + } + + committedProperties |= OUTPUT_HEAD_COMMITTED_TRANSFORM; + state.transform = (wl_output_transform)transform; + + LOGM(LOG, " | configHead for {}: set transform to {}", pMonitor->szName, transform); + }); + + resource->setSetScale([this](CZwlrOutputConfigurationHeadV1* r, wl_fixed_t scale_) { + if (!pMonitor) { + LOGM(ERR, "setMode on inert resource"); + return; + } + + if (committedProperties & OUTPUT_HEAD_COMMITTED_SCALE) { + resource->error(ZWLR_OUTPUT_CONFIGURATION_HEAD_V1_ERROR_ALREADY_SET, "Property already set"); + return; + } + + double scale = wl_fixed_to_double(scale_); + + if (scale < 0.1 || scale > 10.0) { + resource->error(ZWLR_OUTPUT_CONFIGURATION_HEAD_V1_ERROR_INVALID_SCALE, "Invalid scale"); + return; + } + + committedProperties |= OUTPUT_HEAD_COMMITTED_SCALE; + state.scale = scale; + + LOGM(LOG, " | configHead for {}: set scale to {:.2f}", pMonitor->szName, scale); + }); + + resource->setSetAdaptiveSync([this](CZwlrOutputConfigurationHeadV1* r, uint32_t as) { + if (!pMonitor) { + LOGM(ERR, "setMode on inert resource"); + return; + } + + if (committedProperties & OUTPUT_HEAD_COMMITTED_ADAPTIVE_SYNC) { + resource->error(ZWLR_OUTPUT_CONFIGURATION_HEAD_V1_ERROR_ALREADY_SET, "Property already set"); + return; + } + + if (as > 1) { + resource->error(ZWLR_OUTPUT_CONFIGURATION_HEAD_V1_ERROR_INVALID_ADAPTIVE_SYNC_STATE, "Invalid adaptive sync state"); + return; + } + + committedProperties |= OUTPUT_HEAD_COMMITTED_ADAPTIVE_SYNC; + state.adaptiveSync = as; + + LOGM(LOG, " | configHead for {}: set adaptiveSync to {}", pMonitor->szName, as); + }); +} + +bool COutputConfigurationHead::good() { + return resource->resource(); +} + +COutputManagementProtocol::COutputManagementProtocol(const wl_interface* iface, const int& ver, const std::string& name) : IWaylandProtocol(iface, ver, name) { + static auto P = g_pHookSystem->hookDynamic("monitorLayoutChanged", [this](void* self, SCallbackInfo& info, std::any param) { this->updateAllOutputs(); }); +} + +void COutputManagementProtocol::bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id) { + const auto RESOURCE = m_vManagers.emplace_back(std::make_shared<COutputManager>(std::make_shared<CZwlrOutputManagerV1>(client, ver, id))); + + if (!RESOURCE->good()) { + wl_client_post_no_memory(client); + m_vManagers.pop_back(); + return; + } + + RESOURCE->self = RESOURCE; +} + +void COutputManagementProtocol::destroyResource(COutputManager* resource) { + std::erase_if(m_vManagers, [&](const auto& other) { return other.get() == resource; }); +} + +void COutputManagementProtocol::destroyResource(COutputHead* resource) { + std::erase_if(m_vHeads, [&](const auto& other) { return other.get() == resource; }); +} + +void COutputManagementProtocol::destroyResource(COutputMode* resource) { + std::erase_if(m_vModes, [&](const auto& other) { return other.get() == resource; }); +} + +void COutputManagementProtocol::destroyResource(COutputConfiguration* resource) { + std::erase_if(m_vConfigurations, [&](const auto& other) { return other.get() == resource; }); +} + +void COutputManagementProtocol::destroyResource(COutputConfigurationHead* resource) { + std::erase_if(m_vConfigurationHeads, [&](const auto& other) { return other.get() == resource; }); +} + +void COutputManagementProtocol::updateAllOutputs() { + for (auto& m : g_pCompositor->m_vRealMonitors) { + for (auto& mgr : m_vManagers) { + mgr->ensureMonitorSent(m.get()); + } + } +} + +SP<COutputHead> COutputManagementProtocol::headFromResource(wl_resource* r) { + for (auto& h : m_vHeads) { + if (h->resource->resource() == r) + return h; + } + + return nullptr; +} + +SP<COutputMode> COutputManagementProtocol::modeFromResource(wl_resource* r) { + for (auto& h : m_vModes) { + if (h->resource->resource() == r) + return h; + } + + return nullptr; +} diff --git a/src/protocols/OutputManagement.hpp b/src/protocols/OutputManagement.hpp new file mode 100644 index 00000000..12e324b7 --- /dev/null +++ b/src/protocols/OutputManagement.hpp @@ -0,0 +1,166 @@ +#pragma once + +#include <memory> +#include <vector> +#include <cstdint> +#include "WaylandProtocol.hpp" +#include "wlr-output-management-unstable-v1.hpp" +#include "../helpers/signal/Listener.hpp" + +class CMonitor; + +class COutputHead; +class COutputMode; + +class COutputManager { + public: + COutputManager(SP<CZwlrOutputManagerV1> resource_); + + bool good(); + void ensureMonitorSent(CMonitor* pMonitor); + void sendDone(); + + private: + SP<CZwlrOutputManagerV1> resource; + bool stopped = false; + + WP<COutputManager> self; + + std::vector<WP<COutputHead>> heads; + + void makeAndSendNewHead(CMonitor* pMonitor); + friend class COutputManagementProtocol; +}; + +class COutputMode { + public: + COutputMode(SP<CZwlrOutputModeV1> resource_, wlr_output_mode* mode_); + + bool good(); + wlr_output_mode* getMode(); + void sendAllData(); + + private: + SP<CZwlrOutputModeV1> resource; + wlr_output_mode* mode = nullptr; + + friend class COutputHead; + friend class COutputManagementProtocol; +}; + +class COutputHead { + public: + COutputHead(SP<CZwlrOutputHeadV1> resource_, CMonitor* pMonitor_); + + bool good(); + void sendAllData(); // this has to be separate as we need to send the head first, then set the data + void updateMode(); + CMonitor* monitor(); + + private: + SP<CZwlrOutputHeadV1> resource; + CMonitor* pMonitor = nullptr; + + void makeAndSendNewMode(wlr_output_mode* mode); + void sendCurrentMode(); + + std::vector<WP<COutputMode>> modes; + + struct { + CHyprSignalListener monitorDestroy; + CHyprSignalListener monitorModeChange; + } listeners; + + friend class COutputManager; + friend class COutputManagementProtocol; +}; + +class COutputConfigurationHead { + public: + COutputConfigurationHead(SP<CZwlrOutputConfigurationHeadV1> resource_, CMonitor* pMonitor_); + + bool good(); + + enum eCommittedProperties : uint32_t { + OUTPUT_HEAD_COMMITTED_MODE = (1 << 0), + OUTPUT_HEAD_COMMITTED_CUSTOM_MODE = (1 << 1), + OUTPUT_HEAD_COMMITTED_POSITION = (1 << 2), + OUTPUT_HEAD_COMMITTED_TRANSFORM = (1 << 3), + OUTPUT_HEAD_COMMITTED_SCALE = (1 << 4), + OUTPUT_HEAD_COMMITTED_ADAPTIVE_SYNC = (1 << 5), + }; + + uint32_t committedProperties = 0; + + struct { + WP<COutputMode> mode; + struct { + Vector2D size; + uint32_t refresh = 0; + } customMode; + Vector2D position; + wl_output_transform transform = WL_OUTPUT_TRANSFORM_NORMAL; + float scale = 1.F; + bool adaptiveSync = false; + } state; + + private: + SP<CZwlrOutputConfigurationHeadV1> resource; + CMonitor* pMonitor = nullptr; + + struct { + CHyprSignalListener monitorDestroy; + } listeners; + + friend class COutputConfiguration; +}; + +class COutputConfiguration { + public: + COutputConfiguration(SP<CZwlrOutputConfigurationV1> resource_, SP<COutputManager> owner_); + + bool good(); + + private: + SP<CZwlrOutputConfigurationV1> resource; + std::vector<WP<COutputConfigurationHead>> heads; + WP<COutputManager> owner; + + bool applyTestConfiguration(bool test); +}; + +class COutputManagementProtocol : public IWaylandProtocol { + public: + COutputManagementProtocol(const wl_interface* iface, const int& ver, const std::string& name); + + virtual void bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id); + + private: + void destroyResource(COutputManager* resource); + void destroyResource(COutputHead* resource); + void destroyResource(COutputMode* resource); + void destroyResource(COutputConfiguration* resource); + void destroyResource(COutputConfigurationHead* resource); + + void updateAllOutputs(); + + // + std::vector<SP<COutputManager>> m_vManagers; + std::vector<SP<COutputHead>> m_vHeads; + std::vector<SP<COutputMode>> m_vModes; + std::vector<SP<COutputConfiguration>> m_vConfigurations; + std::vector<SP<COutputConfigurationHead>> m_vConfigurationHeads; + + SP<COutputHead> headFromResource(wl_resource* r); + SP<COutputMode> modeFromResource(wl_resource* r); + + friend class COutputManager; + friend class COutputHead; + friend class COutputMode; + friend class COutputConfiguration; + friend class COutputConfigurationHead; +}; + +namespace PROTO { + inline UP<COutputManagementProtocol> outputManagement; +}; diff --git a/src/render/Renderer.cpp b/src/render/Renderer.cpp index 6c4c6a7b..7053adf7 100644 --- a/src/render/Renderer.cpp +++ b/src/render/Renderer.cpp @@ -1474,70 +1474,6 @@ void CHyprRenderer::setWindowScanoutMode(PHLWINDOW pWindow) { Debug::log(LOG, "Scanout mode ON set for {}", pWindow); } -void CHyprRenderer::outputMgrApplyTest(wlr_output_configuration_v1* config, bool test) { - wlr_output_configuration_head_v1* head; - bool ok = true; - - wl_list_for_each(head, &config->heads, link) { - - std::string commandForCfg = ""; - const auto OUTPUT = head->state.output; - - commandForCfg += std::string(OUTPUT->name) + ","; - - if (!head->state.enabled) { - commandForCfg += "disabled"; - if (!test) - g_pConfigManager->parseKeyword("monitor", commandForCfg); - continue; - } - - const auto PMONITOR = g_pCompositor->getRealMonitorFromOutput(OUTPUT); - RASSERT(PMONITOR, "nullptr monitor in outputMgrApplyTest"); - wlr_output_state_set_enabled(PMONITOR->state.wlr(), head->state.enabled); - - if (head->state.mode) - commandForCfg += - std::to_string(head->state.mode->width) + "x" + std::to_string(head->state.mode->height) + "@" + std::to_string(head->state.mode->refresh / 1000.f) + ","; - else - commandForCfg += std::to_string(head->state.custom_mode.width) + "x" + std::to_string(head->state.custom_mode.height) + "@" + - std::to_string(head->state.custom_mode.refresh / 1000.f) + ","; - - commandForCfg += std::to_string(head->state.x) + "x" + std::to_string(head->state.y) + "," + std::to_string(head->state.scale) + ",transform," + - std::to_string((int)head->state.transform); - - if (!test) { - g_pConfigManager->parseKeyword("monitor", commandForCfg); - wlr_output_state_set_adaptive_sync_enabled(PMONITOR->state.wlr(), head->state.adaptive_sync_enabled); - } - - ok = wlr_output_test_state(OUTPUT, PMONITOR->state.wlr()); - - if (!ok) - break; - } - - if (!test) { - g_pConfigManager->m_bWantsMonitorReload = true; // for monitor keywords - // if everything is disabled, performMonitorReload won't be called from renderMonitor - bool allDisabled = std::all_of(g_pCompositor->m_vMonitors.begin(), g_pCompositor->m_vMonitors.end(), - [](const auto m) { return !m->m_bEnabled || g_pCompositor->m_pUnsafeOutput == m.get(); }); - if (allDisabled) { - Debug::log(LOG, "OutputMgr apply: All monitors disabled; performing monitor reload."); - g_pConfigManager->performMonitorReload(); - } - } - - if (ok) - wlr_output_configuration_v1_send_succeeded(config); - else - wlr_output_configuration_v1_send_failed(config); - - wlr_output_configuration_v1_destroy(config); - - Debug::log(LOG, "OutputMgr Applied/Tested."); -} - // taken from Sway. // this is just too much of a spaghetti for me to understand static void applyExclusive(wlr_box& usableArea, uint32_t anchor, int32_t exclusive, int32_t marginTop, int32_t marginRight, int32_t marginBottom, int32_t marginLeft) { @@ -1939,6 +1875,7 @@ bool CHyprRenderer::applyMonitorRule(CMonitor* pMonitor, SMonitorRule* pMonitorR // Needed in case we are switching from a custom modeline to a standard mode pMonitor->customDrmMode = {}; + pMonitor->currentMode = nullptr; bool autoScale = false; if (RULE->scale > 0.1) { @@ -1981,6 +1918,7 @@ bool CHyprRenderer::applyMonitorRule(CMonitor* pMonitor, SMonitorRule* pMonitorR pMonitor->refreshRate = mode->refresh / 1000.f; pMonitor->vecSize = Vector2D(mode->width, mode->height); + pMonitor->currentMode = mode; break; } @@ -2010,6 +1948,7 @@ bool CHyprRenderer::applyMonitorRule(CMonitor* pMonitor, SMonitorRule* pMonitorR pMonitor->refreshRate = PREFERREDMODE->refresh / 1000.f; pMonitor->vecSize = Vector2D(PREFERREDMODE->width, PREFERREDMODE->height); + pMonitor->currentMode = PREFERREDMODE; } else { Debug::log(LOG, "Set a custom mode {:X0}@{:2f} (mode not found in monitor modes)", RULE->resolution, (float)RULE->refreshRate); } @@ -2118,6 +2057,7 @@ bool CHyprRenderer::applyMonitorRule(CMonitor* pMonitor, SMonitorRule* pMonitorR pMonitor->refreshRate = PREFERREDMODE->refresh / 1000.f; pMonitor->vecSize = Vector2D(PREFERREDMODE->width, PREFERREDMODE->height); + pMonitor->currentMode = PREFERREDMODE; } else { Debug::log(LOG, "Monitor {}: Applying highest mode {}x{}@{:2f}.", pMonitor->output->name, (int)currentWidth, (int)currentHeight, (int)currentRefresh / 1000.f); @@ -2148,6 +2088,7 @@ bool CHyprRenderer::applyMonitorRule(CMonitor* pMonitor, SMonitorRule* pMonitorR pMonitor->refreshRate = mode->refresh / 1000.f; pMonitor->vecSize = Vector2D(mode->width, mode->height); + pMonitor->currentMode = mode; break; } @@ -2158,6 +2099,7 @@ bool CHyprRenderer::applyMonitorRule(CMonitor* pMonitor, SMonitorRule* pMonitorR pMonitor->vecSize = Vector2D(PREFERREDMODE->width, PREFERREDMODE->height); pMonitor->refreshRate = PREFERREDMODE->refresh / 1000.f; + pMonitor->currentMode = PREFERREDMODE; Debug::log(LOG, "Setting preferred mode for {}", pMonitor->output->name); } @@ -2307,8 +2249,6 @@ bool CHyprRenderer::applyMonitorRule(CMonitor* pMonitor, SMonitorRule* pMonitorR pMonitor->events.modeChanged.emit(); - Events::listener_change(nullptr, nullptr); - return true; } diff --git a/src/render/Renderer.hpp b/src/render/Renderer.hpp index fcf3f20b..b93af0b0 100644 --- a/src/render/Renderer.hpp +++ b/src/render/Renderer.hpp @@ -43,7 +43,6 @@ class CHyprRenderer { CHyprRenderer(); void renderMonitor(CMonitor* pMonitor); - void outputMgrApplyTest(wlr_output_configuration_v1*, bool); void arrangeLayersForMonitor(const int&); void damageSurface(wlr_surface*, double, double, double scale = 1.0); void damageWindow(PHLWINDOW, bool forceFull = false); |