The nixpkgs patch is to clear out a conflict with a patch that's applied to plasma-workspace by nixpkgs
2101 lines
64 KiB
Diff
2101 lines
64 KiB
Diff
From 677241efbba5f2a725b247baf86a822127571225 Mon Sep 17 00:00:00 2001
|
|
From: Vlad Zahorodnii <vlad.zahorodnii@kde.org>
|
|
Date: Thu, 16 Feb 2023 10:16:17 +0200
|
|
Subject: [PATCH 1/2] wayland: Implement xx-pip-v1
|
|
|
|
The xx-pip-v1 protocol provides clients a way to create floating windows
|
|
with miniature contents, for example a video (a movie or a video call),
|
|
a map with directions, a timer countdown, etc.
|
|
|
|
These windows are placed in the overlay layer above all windows, including
|
|
fullscreen windows.
|
|
|
|
This is an experimental version of the protocol.
|
|
---
|
|
CMakeLists.txt | 4 +
|
|
src/CMakeLists.txt | 2 +
|
|
src/placement.cpp | 18 ++
|
|
src/placement.h | 1 +
|
|
src/wayland/CMakeLists.txt | 2 +
|
|
src/wayland/protocols/xx-pip-v1.xml | 305 +++++++++++++++++++++++++++
|
|
src/wayland/xdgshell.cpp | 2 +-
|
|
src/wayland/xdgshell_p.h | 2 +
|
|
src/wayland/xxpip_v1.cpp | 312 ++++++++++++++++++++++++++++
|
|
src/wayland/xxpip_v1.h | 159 ++++++++++++++
|
|
src/wayland_server.cpp | 7 +
|
|
src/window.cpp | 3 +
|
|
src/window.h | 6 +
|
|
src/xxpipv1integration.cpp | 43 ++++
|
|
src/xxpipv1integration.h | 28 +++
|
|
src/xxpipv1window.cpp | 179 ++++++++++++++++
|
|
src/xxpipv1window.h | 48 +++++
|
|
tests/CMakeLists.txt | 4 +
|
|
tests/pip/CMakeLists.txt | 22 ++
|
|
tests/pip/main.cpp | 19 ++
|
|
tests/pip/pip.cpp | 227 ++++++++++++++++++++
|
|
tests/pip/pip.h | 102 +++++++++
|
|
tests/pip/pipshellsurface.cpp | 156 ++++++++++++++
|
|
tests/pip/pipshellsurface.h | 70 +++++++
|
|
tests/pip/window.cpp | 19 ++
|
|
tests/pip/window.h | 22 ++
|
|
26 files changed, 1761 insertions(+), 1 deletion(-)
|
|
create mode 100644 src/wayland/protocols/xx-pip-v1.xml
|
|
create mode 100644 src/wayland/xxpip_v1.cpp
|
|
create mode 100644 src/wayland/xxpip_v1.h
|
|
create mode 100644 src/xxpipv1integration.cpp
|
|
create mode 100644 src/xxpipv1integration.h
|
|
create mode 100644 src/xxpipv1window.cpp
|
|
create mode 100644 src/xxpipv1window.h
|
|
create mode 100644 tests/pip/CMakeLists.txt
|
|
create mode 100644 tests/pip/main.cpp
|
|
create mode 100644 tests/pip/pip.cpp
|
|
create mode 100644 tests/pip/pip.h
|
|
create mode 100644 tests/pip/pipshellsurface.cpp
|
|
create mode 100644 tests/pip/pipshellsurface.h
|
|
create mode 100644 tests/pip/window.cpp
|
|
create mode 100644 tests/pip/window.h
|
|
|
|
diff --git a/CMakeLists.txt b/CMakeLists.txt
|
|
index 8d4d03b8459..6ac395557c8 100644
|
|
--- a/CMakeLists.txt
|
|
+++ b/CMakeLists.txt
|
|
@@ -84,6 +84,10 @@ endif()
|
|
|
|
if (BUILD_TESTING)
|
|
find_package(KPipeWire)
|
|
+
|
|
+ if (Qt6WaylandClient_VERSION VERSION_GREATER_EQUAL "6.10.0")
|
|
+ find_package(Qt6WaylandClientPrivate ${REQUIRED_QT_VERSION} REQUIRED NO_MODULE)
|
|
+ endif()
|
|
endif()
|
|
|
|
# required frameworks by Core
|
|
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
|
|
index 48fb847ca7c..978c98f7ff3 100644
|
|
--- a/src/CMakeLists.txt
|
|
+++ b/src/CMakeLists.txt
|
|
@@ -211,6 +211,8 @@ target_sources(kwin PRIVATE
|
|
xdgshellintegration.cpp
|
|
xdgshellwindow.cpp
|
|
xkb.cpp
|
|
+ xxpipv1integration.cpp
|
|
+ xxpipv1window.cpp
|
|
)
|
|
|
|
target_link_libraries(kwin
|
|
diff --git a/src/placement.cpp b/src/placement.cpp
|
|
index 6c984282e5e..8890da56159 100644
|
|
--- a/src/placement.cpp
|
|
+++ b/src/placement.cpp
|
|
@@ -48,6 +48,8 @@ std::optional<PlacementCommand> Placement::place(const Window *c, const QRectF &
|
|
return placeOnScreenDisplay(c, area.toRect());
|
|
} else if (c->isTransient() && c->surface()) {
|
|
return placeDialog(c, area.toRect(), options->placement());
|
|
+ } else if (c->isPictureInPicture()) {
|
|
+ return placePictureInPicture(c, area.toRect());
|
|
} else {
|
|
return place(c, area, options->placement());
|
|
}
|
|
@@ -403,6 +405,22 @@ std::optional<PlacementCommand> Placement::placeDialog(const Window *c, const QR
|
|
return placeOnMainWindow(c, area, nextPlacement);
|
|
}
|
|
|
|
+std::optional<PlacementCommand> Placement::placePictureInPicture(const Window *c, const QRect &area)
|
|
+{
|
|
+ Q_ASSERT(area.isValid());
|
|
+
|
|
+ const QSizeF size = c->size();
|
|
+ if (size.isEmpty()) {
|
|
+ return std::nullopt;
|
|
+ }
|
|
+
|
|
+ const qreal x = area.x() + area.width() - size.width();
|
|
+ const qreal y = area.y() + area.height() - size.height();
|
|
+
|
|
+ return QPointF(x, y);
|
|
+}
|
|
+
|
|
+
|
|
std::optional<PlacementCommand> Placement::placeUnderMouse(const Window *c, const QRect &area, PlacementPolicy /*next*/)
|
|
{
|
|
const QSizeF size = c->size();
|
|
diff --git a/src/placement.h b/src/placement.h
|
|
index 63be9df117f..e0da0f51ace 100644
|
|
--- a/src/placement.h
|
|
+++ b/src/placement.h
|
|
@@ -44,6 +44,7 @@ private:
|
|
std::optional<PlacementCommand> placeDialog(const Window *c, const QRect &area, PlacementPolicy next = PlacementUnknown);
|
|
std::optional<PlacementCommand> placeUtility(const Window *c, const QRect &area, PlacementPolicy next = PlacementUnknown);
|
|
std::optional<PlacementCommand> placeOnScreenDisplay(const Window *c, const QRect &area);
|
|
+ std::optional<PlacementCommand> placePictureInPicture(const Window *c, const QRect &area);
|
|
};
|
|
|
|
} // namespace
|
|
diff --git a/src/wayland/CMakeLists.txt b/src/wayland/CMakeLists.txt
|
|
index fde7fc50348..7261395ecf9 100644
|
|
--- a/src/wayland/CMakeLists.txt
|
|
+++ b/src/wayland/CMakeLists.txt
|
|
@@ -38,6 +38,7 @@ ecm_add_qtwayland_server_protocol_kde(WaylandProtocols_xml
|
|
${PROJECT_SOURCE_DIR}/src/wayland/protocols/drm.xml
|
|
${PROJECT_SOURCE_DIR}/src/wayland/protocols/frog-color-management-v1.xml
|
|
${PROJECT_SOURCE_DIR}/src/wayland/protocols/wlr-layer-shell-unstable-v1.xml
|
|
+ ${PROJECT_SOURCE_DIR}/src/wayland/protocols/xx-pip-v1.xml
|
|
${PROJECT_SOURCE_DIR}/src/wayland/protocols/xx-session-management-v1.xml
|
|
|
|
${WaylandProtocols_DATADIR}/stable/presentation-time/presentation-time.xml
|
|
@@ -174,6 +175,7 @@ target_sources(kwin PRIVATE
|
|
xdgtopleveltag_v1.cpp
|
|
xwaylandkeyboardgrab_v1.cpp
|
|
xwaylandshell_v1.cpp
|
|
+ xxpip_v1.cpp
|
|
)
|
|
|
|
install(FILES
|
|
diff --git a/src/wayland/protocols/xx-pip-v1.xml b/src/wayland/protocols/xx-pip-v1.xml
|
|
new file mode 100644
|
|
index 00000000000..c5eb7636ee9
|
|
--- /dev/null
|
|
+++ b/src/wayland/protocols/xx-pip-v1.xml
|
|
@@ -0,0 +1,305 @@
|
|
+<?xml version="1.0" encoding="UTF-8"?>
|
|
+<protocol name="xx_pip_v1">
|
|
+ <copyright>
|
|
+ Copyright © 2025 Vlad Zahorodnii
|
|
+
|
|
+ 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.
|
|
+ </copyright>
|
|
+
|
|
+ <interface name="xx_pip_shell_v1" version="1">
|
|
+ <description summary="create picture-in-picture surfaces">
|
|
+ The xx_pip_shell_v1 interface provides a way to create picture-in-picture
|
|
+ windows.
|
|
+
|
|
+ Use cases are for example playing a video in a separate floating window.
|
|
+
|
|
+ Warning! The protocol described in this file is currently in the testing
|
|
+ phase. Backward compatible changes may be added together with the
|
|
+ corresponding interface version bump. Backward incompatible changes can
|
|
+ only be done by creating a new major version of the extension.
|
|
+ </description>
|
|
+
|
|
+ <enum name="error">
|
|
+ <entry name="role" value="0" summary="given wl_surface has another role"/>
|
|
+ <entry name="already_constructed" value="1" summary="wl_surface has a buffer attached or committed"/>
|
|
+ </enum>
|
|
+
|
|
+ <request name="destroy" type="destructor">
|
|
+ <description summary="destroy xx_pip_shell_v1">
|
|
+ Destroy this xx_pip_shell_v1 object. Objects that have been created
|
|
+ through this instance are unaffected.
|
|
+ </description>
|
|
+ </request>
|
|
+
|
|
+ <request name="get_pip">
|
|
+ <description summary="create picture-in-picture surface from a surface">
|
|
+ This creates an xx_pip_v1 for the given xdg_surface and gives the
|
|
+ associated wl_surface the xx_pip_v1 role.
|
|
+
|
|
+ If the wl_surface already has a role assigned, a role protocol error
|
|
+ will be raised.
|
|
+
|
|
+ Creating a picture-in-picture 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
|
|
+ xx_pip_v1.configure event must also be treated as errors.
|
|
+
|
|
+ After creating an xx_pip_v1 object and setting it up, the client
|
|
+ must perform an initial commit without any buffer attached.
|
|
+ The compositor will reply with a xx_pip_v1.configure event.
|
|
+ The client must acknowledge it and is then allowed to attach a buffer
|
|
+ to map the surface.
|
|
+
|
|
+ The compositor may deny showing the picture-in-picture surface, in
|
|
+ which case it will send the closed event before the first configure
|
|
+ event.
|
|
+
|
|
+ See the documentation of xdg_surface for more details about what an
|
|
+ xdg_surface is and how it is used.
|
|
+ </description>
|
|
+ <arg name="id" type="new_id" interface="xx_pip_v1"/>
|
|
+ <arg name="xdg_surface" type="object" interface="xdg_surface"/>
|
|
+ </request>
|
|
+ </interface>
|
|
+
|
|
+ <interface name="xx_pip_v1" version="1">
|
|
+ <description summary="picture-in-picture surface">
|
|
+ This interface defines an xdg_surface role which represents a floating
|
|
+ window with some miniature contents, for example a video.
|
|
+
|
|
+ The picture-in-picture window will be placed above all other windows.
|
|
+ Compositor-specific policies may override or customize the behavior
|
|
+ and the placement of the xx_pip_v1. For example, the compositor may
|
|
+ choose to put the xx_pip_v1 in a screen corner, etc.
|
|
+
|
|
+ Unmapping an xx_pip_v1 means that the surface cannot be shown
|
|
+ by the compositor until it is explicitly mapped again.
|
|
+ All active operations (e.g., move, resize) are canceled and all
|
|
+ attributes (e.g. title, state, stacking, ...) are discarded for
|
|
+ an xx_pip_v1 surface when it is unmapped. The xx_pip_v1 returns to
|
|
+ the state it had right after xx_pip_shell_v1.get_pip. The client
|
|
+ can re-map the pip by perfoming a commit without any buffer
|
|
+ attached, waiting for a configure event and handling it as usual (see
|
|
+ xdg_surface description).
|
|
+
|
|
+ Attaching a null buffer to a picture-in-picture unmaps the surface.
|
|
+ </description>
|
|
+
|
|
+ <enum name="error">
|
|
+ <entry name="invalid_size" value="0" summary="invalid surface size provided"/>
|
|
+ <entry name="invalid_origin" value="1" summary="invalid origin"/>
|
|
+ <entry name="invalid_resize_edge" value="2" summary="invalid resize edge"/>
|
|
+ </enum>
|
|
+
|
|
+ <request name="destroy" type="destructor">
|
|
+ <description summary="destroy the xx_pip_v1">
|
|
+ This request destroys the role surface and unmaps the surface.
|
|
+ </description>
|
|
+ </request>
|
|
+
|
|
+ <request name="set_app_id">
|
|
+ <description summary="set application ID">
|
|
+ Set an application identifier for the surface.
|
|
+
|
|
+ The app ID identifies the general class of applications to which
|
|
+ the surface belongs. The compositor can use this to group multiple
|
|
+ surfaces together, or to determine how to launch a new application.
|
|
+
|
|
+ For D-Bus activatable applications, the app ID is used as the D-Bus
|
|
+ service name.
|
|
+
|
|
+ The compositor shell will try to group application surfaces together
|
|
+ by their app ID. As a best practice, it is suggested to select app
|
|
+ ID's that match the basename of the application's .desktop file.
|
|
+ For example, "org.freedesktop.FooViewer" where the .desktop file is
|
|
+ "org.freedesktop.FooViewer.desktop".
|
|
+
|
|
+ Like other properties, a set_app_id request can be sent after the
|
|
+ xx_pip_v1 has been mapped to update the property.
|
|
+
|
|
+ See the desktop-entry specification [0] for more details on
|
|
+ application identifiers and how they relate to well-known D-Bus
|
|
+ names and .desktop files.
|
|
+
|
|
+ [0] http://standards.freedesktop.org/desktop-entry-spec/
|
|
+ </description>
|
|
+ <arg name="app_id" type="string"/>
|
|
+ </request>
|
|
+
|
|
+ <request name="set_origin">
|
|
+ <description summary="set origin surface">
|
|
+ Set the origin surface for the picture-in-picture surface.
|
|
+
|
|
+ The origin surface is an optional property that specifies a surface
|
|
+ from which the picture-in-picture surface has been launched. If set,
|
|
+ the compositor may use this hint to play an animation when the
|
|
+ picture-in-picture surface is mapped or unmapped. For example, smoothly
|
|
+ move the surface from the origin to a screen corner.
|
|
+
|
|
+ If the specified origin surface is the same as the picture-in-picture
|
|
+ surface, the invalid_origin protocol error will be posted.
|
|
+
|
|
+ The origin surface is double-buffered state, see wl_surface.commit.
|
|
+ </description>
|
|
+ <arg name="origin" type="object" interface="wl_surface"/>
|
|
+ </request>
|
|
+
|
|
+ <request name="set_origin_rect">
|
|
+ <description summary="set origin rect">
|
|
+ Set the origin rect within the origin surface for the picture-in-picture
|
|
+ surface.
|
|
+
|
|
+ The origin rect is an optional property that specifies the launch
|
|
+ rectangle within the origin surface. The compositor may use this hint
|
|
+ to play an animation when the picture-in-picture surface is mapped or
|
|
+ unmapped. For example, smoothly move the surface from the origin rect
|
|
+ to a screen corner.
|
|
+
|
|
+ The origin rect is specified in the surface-local coordinate space.
|
|
+
|
|
+ The compositor ignores the parts of the origin rect that fall outside
|
|
+ of the origin surface.
|
|
+
|
|
+ The origin rect is double-buffered state, see wl_surface.commit.
|
|
+ </description>
|
|
+ <arg name="x" type="int"/>
|
|
+ <arg name="y" type="int"/>
|
|
+ <arg name="width" type="uint"/>
|
|
+ <arg name="height" type="uint"/>
|
|
+ </request>
|
|
+
|
|
+ <request name="move">
|
|
+ <description summary="start an interactive move">
|
|
+ Start an interactive, user-driven move of the surface.
|
|
+
|
|
+ This request must be used in response to some sort of user action
|
|
+ like a button press, key press, or touch down event. The passed
|
|
+ serial is used to determine the type of interactive move (touch,
|
|
+ pointer, etc).
|
|
+
|
|
+ The server may ignore move requests depending on the state of
|
|
+ the surface, or if the passed serial is no longer valid.
|
|
+
|
|
+ If triggered, the surface will lose the focus of the device
|
|
+ (wl_pointer, wl_touch, etc) used for the move. It is up to the
|
|
+ compositor to visually indicate that the move is taking place, such as
|
|
+ updating a pointer cursor, during the move. There is no guarantee
|
|
+ that the device focus will return when the move is completed.
|
|
+ </description>
|
|
+ <arg name="seat" type="object" interface="wl_seat" summary="the wl_seat of the user event"/>
|
|
+ <arg name="serial" type="uint" summary="the serial of the user event"/>
|
|
+ </request>
|
|
+
|
|
+ <enum name="resize_edge">
|
|
+ <description summary="edge values for resizing">
|
|
+ These values are used to indicate which edge of a surface
|
|
+ is being dragged in a resize operation.
|
|
+ </description>
|
|
+ <entry name="none" value="0"/>
|
|
+ <entry name="top" value="1"/>
|
|
+ <entry name="bottom" value="2"/>
|
|
+ <entry name="left" value="4"/>
|
|
+ <entry name="top_left" value="5"/>
|
|
+ <entry name="bottom_left" value="6"/>
|
|
+ <entry name="right" value="8"/>
|
|
+ <entry name="top_right" value="9"/>
|
|
+ <entry name="bottom_right" value="10"/>
|
|
+ </enum>
|
|
+
|
|
+ <request name="resize">
|
|
+ <description summary="start an interactive resize">
|
|
+ Start a user-driven, interactive resize of the surface.
|
|
+
|
|
+ This request must be used in response to some sort of user action
|
|
+ like a button press, key press, or touch down event. The passed
|
|
+ serial is used to determine the type of interactive resize (touch,
|
|
+ pointer, etc).
|
|
+
|
|
+ The server may ignore resize requests depending on the state of
|
|
+ the surface, or if the passed serial is no longer valid.
|
|
+
|
|
+ If triggered, the surface also will lose the focus of the device
|
|
+ (wl_pointer, wl_touch, etc) used for the resize. It is up to the
|
|
+ compositor to visually indicate that the resize is taking place,
|
|
+ such as updating a pointer cursor, during the resize. There is no
|
|
+ guarantee that the device focus will return when the resize is
|
|
+ completed.
|
|
+
|
|
+ The edges parameter specifies how the surface should be resized,
|
|
+ and is one of the values of the resize_edge enum. The compositor
|
|
+ may use this information to update the surface position for
|
|
+ example when dragging the top left corner. The compositor may also
|
|
+ use this information to adapt its behavior, e.g. choose an
|
|
+ appropriate cursor image.
|
|
+ </description>
|
|
+ <arg name="seat" type="object" interface="wl_seat" summary="the wl_seat of the user event"/>
|
|
+ <arg name="serial" type="uint" summary="the serial of the user event"/>
|
|
+ <arg name="edges" type="uint" enum="resize_edge" summary="which edge or corner is being dragged"/>
|
|
+ </request>
|
|
+
|
|
+ <event name="closed">
|
|
+ <description summary="picture-in-picture surface has been closed">
|
|
+ The closed event is sent by the compositor when the surface will
|
|
+ no longer be shown. Further changes to the surface will be ignored.
|
|
+ The client should destroy the resource after receiving this event.
|
|
+ </description>
|
|
+ </event>
|
|
+
|
|
+ <event name="configure_bounds">
|
|
+ <description summary="surface bounds">
|
|
+ The configure_bounds event may be sent prior to a xx_pip_v1.configure
|
|
+ event to communicate the bounds a surface size must be constrained to.
|
|
+
|
|
+ The passed width and height are in surface coordinate space.
|
|
+
|
|
+ If the surface width or the surface height is greater than the specified
|
|
+ surface size bounds, an invalid_size protocol error will be posted.
|
|
+
|
|
+ The surface bounds subject to compositor policies.
|
|
+
|
|
+ The bounds may change at any point, and in such a case, a new
|
|
+ xx_pip_v1.configure_bounds will be sent, followed by xx_pip_v1.configure and
|
|
+ xdg_surface.configure.
|
|
+ </description>
|
|
+ <arg name="width" type="int"/>
|
|
+ <arg name="height" type="int"/>
|
|
+ </event>
|
|
+
|
|
+ <event name="configure_size">
|
|
+ <description summary="suggest a surface change">
|
|
+ This configure event asks the client to resize its pip surface.
|
|
+ The configured state should not be applied immediately. See
|
|
+ xdg_surface.configure for details.
|
|
+
|
|
+ The width and height arguments specify a hint to the window
|
|
+ about how its surface should be resized in window geometry
|
|
+ coordinates. See set_window_geometry.
|
|
+
|
|
+ If the width or height arguments are zero, it means the client
|
|
+ should decide its own window dimension.
|
|
+
|
|
+ Clients must send an ack_configure in response to this event. See
|
|
+ xdg_surface.configure and xdg_surface.ack_configure for details.
|
|
+ </description>
|
|
+ <arg name="width" type="int"/>
|
|
+ <arg name="height" type="int"/>
|
|
+ </event>
|
|
+ </interface>
|
|
+</protocol>
|
|
diff --git a/src/wayland/xdgshell.cpp b/src/wayland/xdgshell.cpp
|
|
index 282678c0938..f1217e9d37a 100644
|
|
--- a/src/wayland/xdgshell.cpp
|
|
+++ b/src/wayland/xdgshell.cpp
|
|
@@ -186,7 +186,7 @@ void XdgSurfaceInterfacePrivate::xdg_surface_destroy_resource(Resource *resource
|
|
|
|
void XdgSurfaceInterfacePrivate::xdg_surface_destroy(Resource *resource)
|
|
{
|
|
- if (toplevel || popup) {
|
|
+ if (!toplevel.isNull() || !popup.isNull() || !pip.isNull()) {
|
|
qWarning() << "Tried to destroy xdg_surface before its role object";
|
|
}
|
|
wl_resource_destroy(resource->handle);
|
|
diff --git a/src/wayland/xdgshell_p.h b/src/wayland/xdgshell_p.h
|
|
index 46244b23523..e48dfd8d78d 100644
|
|
--- a/src/wayland/xdgshell_p.h
|
|
+++ b/src/wayland/xdgshell_p.h
|
|
@@ -16,6 +16,7 @@
|
|
namespace KWin
|
|
{
|
|
class XdgToplevelDecorationV1Interface;
|
|
+class XXPipV1Interface;
|
|
|
|
class XdgShellInterfacePrivate : public QtWaylandServer::xdg_wm_base
|
|
{
|
|
@@ -118,6 +119,7 @@ public:
|
|
XdgShellInterface *shell = nullptr;
|
|
QPointer<XdgToplevelInterface> toplevel;
|
|
QPointer<XdgPopupInterface> popup;
|
|
+ QPointer<XXPipV1Interface> pip;
|
|
QPointer<SurfaceInterface> surface;
|
|
QRect windowGeometry;
|
|
bool firstBufferAttached = false;
|
|
diff --git a/src/wayland/xxpip_v1.cpp b/src/wayland/xxpip_v1.cpp
|
|
new file mode 100644
|
|
index 00000000000..e0b1f36134f
|
|
--- /dev/null
|
|
+++ b/src/wayland/xxpip_v1.cpp
|
|
@@ -0,0 +1,312 @@
|
|
+/*
|
|
+ SPDX-FileCopyrightText: 2023 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
|
|
+
|
|
+ SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
|
|
+*/
|
|
+
|
|
+#include "wayland/xxpip_v1.h"
|
|
+#include "utils/resource.h"
|
|
+#include "wayland/display.h"
|
|
+#include "wayland/seat.h"
|
|
+#include "wayland/surface.h"
|
|
+#include "wayland/xdgshell_p.h"
|
|
+
|
|
+#include "qwayland-server-xx-pip-v1.h"
|
|
+
|
|
+namespace KWin
|
|
+{
|
|
+
|
|
+static const int s_version = 1;
|
|
+
|
|
+class XXPipShellV1InterfacePrivate : public QtWaylandServer::xx_pip_shell_v1
|
|
+{
|
|
+public:
|
|
+ explicit XXPipShellV1InterfacePrivate(XXPipShellV1Interface *q, Display *display);
|
|
+
|
|
+ XXPipShellV1Interface *q;
|
|
+ Display *display;
|
|
+
|
|
+protected:
|
|
+ void xx_pip_shell_v1_destroy(Resource *resource) override;
|
|
+ void xx_pip_shell_v1_get_pip(Resource *resource, uint32_t id, struct ::wl_resource *xdg_surface) override;
|
|
+};
|
|
+
|
|
+XXPipShellV1InterfacePrivate::XXPipShellV1InterfacePrivate(XXPipShellV1Interface *q, Display *display)
|
|
+ : QtWaylandServer::xx_pip_shell_v1(*display, s_version)
|
|
+ , q(q)
|
|
+ , display(display)
|
|
+{
|
|
+}
|
|
+
|
|
+void XXPipShellV1InterfacePrivate::xx_pip_shell_v1_destroy(Resource *resource)
|
|
+{
|
|
+ wl_resource_destroy(resource->handle);
|
|
+}
|
|
+
|
|
+void XXPipShellV1InterfacePrivate::xx_pip_shell_v1_get_pip(Resource *resource, uint32_t id, struct ::wl_resource *xdg_surface)
|
|
+{
|
|
+ XdgSurfaceInterface *xdgSurface = XdgSurfaceInterface::get(xdg_surface);
|
|
+
|
|
+ if (const SurfaceRole *role = xdgSurface->surface()->role()) {
|
|
+ if (role != XXPipV1Interface::role()) {
|
|
+ wl_resource_post_error(resource->handle, error_already_constructed, "the surface already has a role assigned %s", role->name().constData());
|
|
+ return;
|
|
+ }
|
|
+ } else {
|
|
+ xdgSurface->surface()->setRole(XXPipV1Interface::role());
|
|
+ }
|
|
+
|
|
+ wl_resource *pipResource = wl_resource_create(resource->client(), &xx_pip_v1_interface, resource->version(), id);
|
|
+ auto pip = new XXPipV1Interface(q, xdgSurface, pipResource);
|
|
+
|
|
+ Q_EMIT q->pipCreated(pip);
|
|
+}
|
|
+
|
|
+XXPipShellV1Interface::XXPipShellV1Interface(Display *display, QObject *parent)
|
|
+ : QObject(parent)
|
|
+ , d(std::make_unique<XXPipShellV1InterfacePrivate>(this, display))
|
|
+{
|
|
+}
|
|
+
|
|
+XXPipShellV1Interface::~XXPipShellV1Interface()
|
|
+{
|
|
+}
|
|
+
|
|
+Display *XXPipShellV1Interface::display() const
|
|
+{
|
|
+ return d->display;
|
|
+}
|
|
+
|
|
+class XXPipV1Commit : public SurfaceAttachedState<XXPipV1Commit>, public XdgSurfaceCommit
|
|
+{
|
|
+public:
|
|
+ QPointer<SurfaceInterface> origin;
|
|
+ QRect originRect;
|
|
+};
|
|
+
|
|
+class XXPipV1InterfacePrivate : public SurfaceExtension<XXPipV1InterfacePrivate, XXPipV1Commit>, public QtWaylandServer::xx_pip_v1
|
|
+{
|
|
+public:
|
|
+ XXPipV1InterfacePrivate(XXPipV1Interface *q, XXPipShellV1Interface *shell, XdgSurfaceInterface *xdgSurface);
|
|
+
|
|
+ void apply(XXPipV1Commit *comit);
|
|
+ void reset();
|
|
+
|
|
+ XXPipV1Interface *q;
|
|
+ XXPipShellV1Interface *shell;
|
|
+ XdgSurfaceInterface *xdgSurface;
|
|
+ QString applicationId;
|
|
+ QPointer<SurfaceInterface> origin;
|
|
+ QRect originRect;
|
|
+
|
|
+protected:
|
|
+ void xx_pip_v1_destroy_resource(Resource *resource) override;
|
|
+ void xx_pip_v1_destroy(Resource *resource) override;
|
|
+ void xx_pip_v1_set_app_id(Resource *resource, const QString &app_id) override;
|
|
+ void xx_pip_v1_set_origin(Resource *resource, struct ::wl_resource *origin) override;
|
|
+ void xx_pip_v1_set_origin_rect(Resource *resource, int32_t x, int32_t y, uint32_t width, uint32_t height) override;
|
|
+ void xx_pip_v1_move(Resource *resource, struct ::wl_resource *seat, uint32_t serial) override;
|
|
+ void xx_pip_v1_resize(Resource *resource, struct ::wl_resource *seat, uint32_t serial, uint32_t edges) override;
|
|
+};
|
|
+
|
|
+XXPipV1InterfacePrivate::XXPipV1InterfacePrivate(XXPipV1Interface *q, XXPipShellV1Interface *shell, XdgSurfaceInterface *xdgSurface)
|
|
+ : SurfaceExtension(xdgSurface->surface())
|
|
+ , q(q)
|
|
+ , shell(shell)
|
|
+ , xdgSurface(xdgSurface)
|
|
+{
|
|
+}
|
|
+
|
|
+void XXPipV1InterfacePrivate::apply(XXPipV1Commit *commit)
|
|
+{
|
|
+ auto xdgSurfacePrivate = XdgSurfaceInterfacePrivate::get(xdgSurface);
|
|
+ if (xdgSurfacePrivate->firstBufferAttached && !xdgSurfacePrivate->surface->buffer()) {
|
|
+ reset();
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ if (!commit->origin.isNull()) {
|
|
+ origin = commit->origin;
|
|
+ }
|
|
+ if (!commit->originRect.isNull()) {
|
|
+ originRect = commit->originRect;
|
|
+ }
|
|
+
|
|
+ xdgSurfacePrivate->apply(commit);
|
|
+
|
|
+ if (!xdgSurfacePrivate->isConfigured) {
|
|
+ Q_EMIT q->initializeRequested();
|
|
+ }
|
|
+}
|
|
+
|
|
+void XXPipV1InterfacePrivate::reset()
|
|
+{
|
|
+ auto xdgSurfacePrivate = XdgSurfaceInterfacePrivate::get(xdgSurface);
|
|
+ xdgSurfacePrivate->reset();
|
|
+
|
|
+ Q_EMIT q->resetOccurred();
|
|
+}
|
|
+
|
|
+void XXPipV1InterfacePrivate::xx_pip_v1_destroy_resource(Resource *resource)
|
|
+{
|
|
+ Q_EMIT q->aboutToBeDestroyed();
|
|
+ delete q;
|
|
+}
|
|
+
|
|
+void XXPipV1InterfacePrivate::xx_pip_v1_destroy(Resource *resource)
|
|
+{
|
|
+ wl_resource_destroy(resource->handle);
|
|
+}
|
|
+
|
|
+void XXPipV1InterfacePrivate::xx_pip_v1_set_app_id(Resource *resource, const QString &app_id)
|
|
+{
|
|
+ if (applicationId != app_id) {
|
|
+ applicationId = app_id;
|
|
+ Q_EMIT q->applicationIdChanged();
|
|
+ }
|
|
+}
|
|
+
|
|
+void XXPipV1InterfacePrivate::xx_pip_v1_set_origin(Resource *resource, struct ::wl_resource *origin_resource)
|
|
+{
|
|
+ SurfaceInterface *origin = SurfaceInterface::get(origin_resource);
|
|
+ if (origin == xdgSurface->surface()) {
|
|
+ wl_resource_post_error(resource->handle, error_invalid_origin, "pip surface cannot be its own origin");
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ pending->origin = origin;
|
|
+}
|
|
+
|
|
+void XXPipV1InterfacePrivate::xx_pip_v1_set_origin_rect(Resource *resource, int32_t x, int32_t y, uint32_t width, uint32_t height)
|
|
+{
|
|
+ pending->originRect = QRect(x, y, width, height);
|
|
+}
|
|
+
|
|
+void XXPipV1InterfacePrivate::xx_pip_v1_move(Resource *resource, struct ::wl_resource *seat_resource, uint32_t serial)
|
|
+{
|
|
+ auto xdgSurfacePrivate = XdgSurfaceInterfacePrivate::get(xdgSurface);
|
|
+ if (!xdgSurfacePrivate->isConfigured) {
|
|
+ wl_resource_post_error(resource->handle, QtWaylandServer::xdg_surface::error_not_constructed, "surface has not been configured yet");
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ Q_EMIT q->moveRequested(SeatInterface::get(seat_resource), serial);
|
|
+}
|
|
+
|
|
+void XXPipV1InterfacePrivate::xx_pip_v1_resize(Resource *resource, struct ::wl_resource *seat_resource, uint32_t serial, uint32_t edges)
|
|
+{
|
|
+ auto xdgSurfacePrivate = XdgSurfaceInterfacePrivate::get(xdgSurface);
|
|
+ if (!xdgSurfacePrivate->isConfigured) {
|
|
+ wl_resource_post_error(resource->handle, QtWaylandServer::xdg_surface::error_not_constructed, "surface has not been configured yet");
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ Gravity gravity;
|
|
+ switch (edges) {
|
|
+ case resize_edge_none:
|
|
+ gravity = Gravity::None;
|
|
+ break;
|
|
+ case resize_edge_top:
|
|
+ gravity = Gravity::Top;
|
|
+ break;
|
|
+ case resize_edge_bottom:
|
|
+ gravity = Gravity::Bottom;
|
|
+ break;
|
|
+ case resize_edge_left:
|
|
+ gravity = Gravity::Left;
|
|
+ break;
|
|
+ case resize_edge_top_left:
|
|
+ gravity = Gravity::TopLeft;
|
|
+ break;
|
|
+ case resize_edge_bottom_left:
|
|
+ gravity = Gravity::BottomLeft;
|
|
+ break;
|
|
+ case resize_edge_right:
|
|
+ gravity = Gravity::Right;
|
|
+ break;
|
|
+ case resize_edge_top_right:
|
|
+ gravity = Gravity::TopRight;
|
|
+ break;
|
|
+ case resize_edge_bottom_right:
|
|
+ gravity = Gravity::BottomRight;
|
|
+ break;
|
|
+ default:
|
|
+ wl_resource_post_error(resource->handle, error_invalid_resize_edge, "invalid resize edge");
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ Q_EMIT q->resizeRequested(SeatInterface::get(seat_resource), gravity, serial);
|
|
+}
|
|
+
|
|
+XXPipV1Interface::XXPipV1Interface(XXPipShellV1Interface *shell, XdgSurfaceInterface *xdgSurface, wl_resource *resource)
|
|
+ : d(std::make_unique<XXPipV1InterfacePrivate>(this, shell, xdgSurface))
|
|
+{
|
|
+ XdgSurfaceInterfacePrivate *surfacePrivate = XdgSurfaceInterfacePrivate::get(xdgSurface);
|
|
+ surfacePrivate->pip = this;
|
|
+ surfacePrivate->pending = d->pending;
|
|
+
|
|
+ d->init(resource);
|
|
+}
|
|
+
|
|
+XXPipV1Interface::~XXPipV1Interface()
|
|
+{
|
|
+}
|
|
+
|
|
+SurfaceRole *XXPipV1Interface::role()
|
|
+{
|
|
+ static SurfaceRole role(QByteArrayLiteral("xx_pip_v1"));
|
|
+ return &role;
|
|
+}
|
|
+
|
|
+bool XXPipV1Interface::isConfigured() const
|
|
+{
|
|
+ return d->xdgSurface->isConfigured();
|
|
+}
|
|
+
|
|
+XdgSurfaceInterface *XXPipV1Interface::xdgSurface() const
|
|
+{
|
|
+ return d->xdgSurface;
|
|
+}
|
|
+
|
|
+SurfaceInterface *XXPipV1Interface::surface() const
|
|
+{
|
|
+ return d->xdgSurface->surface();
|
|
+}
|
|
+
|
|
+QString XXPipV1Interface::applicationId() const
|
|
+{
|
|
+ return d->applicationId;
|
|
+}
|
|
+
|
|
+quint32 XXPipV1Interface::sendConfigureSize(const QSizeF &size)
|
|
+{
|
|
+ const quint32 serial = d->shell->display()->nextSerial();
|
|
+
|
|
+ d->send_configure_size(size.width(), size.height());
|
|
+
|
|
+ auto xdgSurfacePrivate = XdgSurfaceInterfacePrivate::get(xdgSurface());
|
|
+ xdgSurfacePrivate->send_configure(serial);
|
|
+ xdgSurfacePrivate->isConfigured = true;
|
|
+
|
|
+ return serial;
|
|
+}
|
|
+
|
|
+void XXPipV1Interface::sendClosed()
|
|
+{
|
|
+ d->send_closed();
|
|
+}
|
|
+
|
|
+void XXPipV1Interface::sendConfigureBounds(const QSizeF &size)
|
|
+{
|
|
+ d->send_configure_bounds(size.width(), size.height());
|
|
+}
|
|
+
|
|
+XXPipV1Interface *XXPipV1Interface::get(::wl_resource *resource)
|
|
+{
|
|
+ if (auto pipPrivate = resource_cast<XXPipV1InterfacePrivate *>(resource)) {
|
|
+ return pipPrivate->q;
|
|
+ }
|
|
+ return nullptr;
|
|
+}
|
|
+
|
|
+} // namespace KWin
|
|
diff --git a/src/wayland/xxpip_v1.h b/src/wayland/xxpip_v1.h
|
|
new file mode 100644
|
|
index 00000000000..c2737f2ec26
|
|
--- /dev/null
|
|
+++ b/src/wayland/xxpip_v1.h
|
|
@@ -0,0 +1,159 @@
|
|
+/*
|
|
+ SPDX-FileCopyrightText: 2023 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
|
|
+
|
|
+ SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
|
|
+*/
|
|
+
|
|
+#pragma once
|
|
+
|
|
+#include "kwin_export.h"
|
|
+
|
|
+#include <QObject>
|
|
+
|
|
+#include <memory>
|
|
+
|
|
+struct wl_resource;
|
|
+
|
|
+namespace KWin
|
|
+{
|
|
+
|
|
+class Display;
|
|
+class SeatInterface;
|
|
+class SurfaceInterface;
|
|
+class SurfaceRole;
|
|
+class XXPipV1Interface;
|
|
+class XXPipV1InterfacePrivate;
|
|
+class XdgSurfaceInterface;
|
|
+class XXPipShellV1InterfacePrivate;
|
|
+
|
|
+enum class Gravity;
|
|
+
|
|
+/**
|
|
+ * The XXPipShellV1Interface extension provides clients a way to create picture-in-picture
|
|
+ * surfaces.
|
|
+ */
|
|
+class KWIN_EXPORT XXPipShellV1Interface : public QObject
|
|
+{
|
|
+ Q_OBJECT
|
|
+
|
|
+public:
|
|
+ explicit XXPipShellV1Interface(Display *display, QObject *parent = nullptr);
|
|
+ ~XXPipShellV1Interface() override;
|
|
+
|
|
+ Display *display() const;
|
|
+
|
|
+Q_SIGNALS:
|
|
+ void pipCreated(XXPipV1Interface *pip);
|
|
+
|
|
+private:
|
|
+ std::unique_ptr<XXPipShellV1InterfacePrivate> d;
|
|
+};
|
|
+
|
|
+/**
|
|
+ * The XXPipV1Interface class represents a picture-in-picture surface.
|
|
+ *
|
|
+ * XXPipV1Interface corresponds to the Wayland interface \c xx_pip_v1.
|
|
+ */
|
|
+class KWIN_EXPORT XXPipV1Interface : public QObject
|
|
+{
|
|
+ Q_OBJECT
|
|
+
|
|
+public:
|
|
+ XXPipV1Interface(XXPipShellV1Interface *shell, XdgSurfaceInterface *xdgSurface, wl_resource *resource);
|
|
+ ~XXPipV1Interface() override;
|
|
+
|
|
+ static SurfaceRole *role();
|
|
+
|
|
+ /**
|
|
+ * Returns \c true if the popup has been configured; otherwise returns \c false.
|
|
+ */
|
|
+ bool isConfigured() const;
|
|
+
|
|
+ /**
|
|
+ * Returns the XdgSurfaceInterface associated with the XXPipV1Interface.
|
|
+ */
|
|
+ XdgSurfaceInterface *xdgSurface() const;
|
|
+
|
|
+ /**
|
|
+ * Returns the SurfaceInterface associated with the XXPipV1Interface.
|
|
+ */
|
|
+ SurfaceInterface *surface() const;
|
|
+
|
|
+ /**
|
|
+ * Returns the desktop file name of the pip surface.
|
|
+ */
|
|
+ QString applicationId() const;
|
|
+
|
|
+ /**
|
|
+ * Returns the surface from which the picture-in-picture surface has been launched, or \c null.
|
|
+ */
|
|
+ SurfaceInterface *origin() const;
|
|
+
|
|
+ /**
|
|
+ * Specifies the bounds within the origin surface from which the picture-in-picture surface has
|
|
+ * been launched.
|
|
+ */
|
|
+ QRect originRect() const;
|
|
+
|
|
+ /**
|
|
+ * Sends a configure event to the client. \a size specifies the new window geometry size. A size
|
|
+ * of zero means the client should decide its own window dimensions.
|
|
+ */
|
|
+ quint32 sendConfigureSize(const QSizeF &size);
|
|
+
|
|
+ /**
|
|
+ * Sends a close event to the client. The client may choose to ignore this request.
|
|
+ */
|
|
+ void sendClosed();
|
|
+
|
|
+ /**
|
|
+ * Sends an event to the client specifying the maximum bounds for the surface size. Must be
|
|
+ * called before sendConfigure().
|
|
+ */
|
|
+ void sendConfigureBounds(const QSizeF &size);
|
|
+
|
|
+ /**
|
|
+ * Returns the XXPipV1Interface for the specified wayland resource object \a resource.
|
|
+ */
|
|
+ static XXPipV1Interface *get(::wl_resource *resource);
|
|
+
|
|
+Q_SIGNALS:
|
|
+ /**
|
|
+ * This signal is emitted when the xx-pip-v1 is about to be destroyed.
|
|
+ */
|
|
+ void aboutToBeDestroyed();
|
|
+
|
|
+ /**
|
|
+ * This signal is emitted when the xx-pip-v1 has commited the initial state and wants to
|
|
+ * be configured. After initializing the pip surface, you must send a configure event.
|
|
+ */
|
|
+ void initializeRequested();
|
|
+
|
|
+ /**
|
|
+ * This signal is emitted when the pip surface has been unmapped and its state has been reset.
|
|
+ */
|
|
+ void resetOccurred();
|
|
+
|
|
+ /**
|
|
+ * This signal is emitted when the pip wants to be interactively moved. The \a seat and
|
|
+ * the \a serial indicate the user action in response to which this request has been issued.
|
|
+ */
|
|
+ void moveRequested(SeatInterface *seat, quint32 serial);
|
|
+
|
|
+ /**
|
|
+ * This signal is emitted when the pip wants to be interactively resized with
|
|
+ * the specified \a gravity. The \a seat and the \a serial indicate the user action
|
|
+ * in response to which this request has been issued.
|
|
+ */
|
|
+ void resizeRequested(SeatInterface *seat, Gravity anchor, quint32 serial);
|
|
+
|
|
+ /**
|
|
+ * This signal is emitted when the application id changes.
|
|
+ */
|
|
+ void applicationIdChanged();
|
|
+
|
|
+private:
|
|
+ std::unique_ptr<XXPipV1InterfacePrivate> d;
|
|
+};
|
|
+
|
|
+} // namespace KWin
|
|
diff --git a/src/wayland_server.cpp b/src/wayland_server.cpp
|
|
index 24f7fa71ebe..0d54680313a 100644
|
|
--- a/src/wayland_server.cpp
|
|
+++ b/src/wayland_server.cpp
|
|
@@ -91,6 +91,7 @@
|
|
#include "xdgactivationv1.h"
|
|
#include "xdgshellintegration.h"
|
|
#include "xdgshellwindow.h"
|
|
+#include "xxpipv1integration.h"
|
|
#if KWIN_BUILD_X11
|
|
#include "wayland/xwaylandkeyboardgrab_v1.h"
|
|
#include "wayland/xwaylandshell_v1.h"
|
|
@@ -581,6 +582,12 @@ void WaylandServer::initWorkspace()
|
|
connect(layerShellV1Integration, &LayerShellV1Integration::windowCreated,
|
|
this, &WaylandServer::registerWindow);
|
|
|
|
+ if (qEnvironmentVariableIntValue("KWIN_WAYLAND_SUPPORT_XX_PIP_V1") == 1) {
|
|
+ auto pipV1Integration = new XXPipV1Integration(this);
|
|
+ connect(pipV1Integration, &XXPipV1Integration::windowCreated,
|
|
+ this, &WaylandServer::registerWindow);
|
|
+ }
|
|
+
|
|
new KeyStateInterface(m_display, m_display);
|
|
|
|
VirtualDesktopManager::self()->setVirtualDesktopManagement(m_virtualDesktopManagement);
|
|
diff --git a/src/window.cpp b/src/window.cpp
|
|
index 591950ab74a..f5a4be143b8 100644
|
|
--- a/src/window.cpp
|
|
+++ b/src/window.cpp
|
|
@@ -563,6 +563,9 @@ Layer Window::belongsToLayer() const
|
|
if (isUnmanaged() || isInternal()) {
|
|
return OverlayLayer;
|
|
}
|
|
+ if (isPictureInPicture()) {
|
|
+ return OverlayLayer;
|
|
+ }
|
|
if (isLockScreen() && !waylandServer()) {
|
|
return OverlayLayer;
|
|
}
|
|
diff --git a/src/window.h b/src/window.h
|
|
index 57be2c6891e..9ae5758de85 100644
|
|
--- a/src/window.h
|
|
+++ b/src/window.h
|
|
@@ -781,6 +781,7 @@ public:
|
|
virtual bool isClient() const;
|
|
bool isDeleted() const;
|
|
virtual bool isUnmanaged() const;
|
|
+ virtual bool isPictureInPicture() const;
|
|
|
|
bool isLockScreenOverlay() const;
|
|
void setLockScreenOverlay(bool allowed);
|
|
@@ -2086,6 +2087,11 @@ inline bool Window::isInternal() const
|
|
return false;
|
|
}
|
|
|
|
+inline bool Window::isPictureInPicture() const
|
|
+{
|
|
+ return false;
|
|
+}
|
|
+
|
|
inline WindowItem *Window::windowItem() const
|
|
{
|
|
return m_windowItem.get();
|
|
diff --git a/src/xxpipv1integration.cpp b/src/xxpipv1integration.cpp
|
|
new file mode 100644
|
|
index 00000000000..b72f2cba140
|
|
--- /dev/null
|
|
+++ b/src/xxpipv1integration.cpp
|
|
@@ -0,0 +1,43 @@
|
|
+/*
|
|
+ SPDX-FileCopyrightText: 2023 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
|
|
+
|
|
+ SPDX-License-Identifier: GPL-2.0-or-later
|
|
+*/
|
|
+
|
|
+#include "xxpipv1integration.h"
|
|
+#include "wayland/xxpip_v1.h"
|
|
+#include "wayland_server.h"
|
|
+#include "workspace.h"
|
|
+#include "xxpipv1window.h"
|
|
+
|
|
+namespace KWin
|
|
+{
|
|
+
|
|
+XXPipV1Integration::XXPipV1Integration(QObject *parent)
|
|
+ : WaylandShellIntegration(parent)
|
|
+{
|
|
+ XXPipShellV1Interface *shell = new XXPipShellV1Interface(waylandServer()->display(), this);
|
|
+ connect(shell, &XXPipShellV1Interface::pipCreated,
|
|
+ this, &XXPipV1Integration::registerPipV1Surface);
|
|
+}
|
|
+
|
|
+void XXPipV1Integration::registerPipV1Surface(XXPipV1Interface *pip)
|
|
+{
|
|
+ createPipV1Window(pip);
|
|
+ connect(pip, &XXPipV1Interface::resetOccurred, this, [this, pip] {
|
|
+ createPipV1Window(pip);
|
|
+ });
|
|
+}
|
|
+
|
|
+void XXPipV1Integration::createPipV1Window(XXPipV1Interface *pip)
|
|
+{
|
|
+ if (!workspace()) {
|
|
+ qCWarning(KWIN_CORE, "An xx-pip-v1 surface has been created while the compositor "
|
|
+ "is still not fully initialized. That is a compositor bug!");
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ Q_EMIT windowCreated(new XXPipV1Window(pip));
|
|
+}
|
|
+
|
|
+} // namespace KWin
|
|
diff --git a/src/xxpipv1integration.h b/src/xxpipv1integration.h
|
|
new file mode 100644
|
|
index 00000000000..8d2be310fe5
|
|
--- /dev/null
|
|
+++ b/src/xxpipv1integration.h
|
|
@@ -0,0 +1,28 @@
|
|
+/*
|
|
+ SPDX-FileCopyrightText: 2023 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
|
|
+
|
|
+ SPDX-License-Identifier: GPL-2.0-or-later
|
|
+*/
|
|
+
|
|
+#pragma once
|
|
+
|
|
+#include "waylandshellintegration.h"
|
|
+
|
|
+namespace KWin
|
|
+{
|
|
+
|
|
+class XXPipV1Interface;
|
|
+
|
|
+class XXPipV1Integration : public WaylandShellIntegration
|
|
+{
|
|
+ Q_OBJECT
|
|
+
|
|
+public:
|
|
+ explicit XXPipV1Integration(QObject *parent = nullptr);
|
|
+
|
|
+private:
|
|
+ void registerPipV1Surface(XXPipV1Interface *pip);
|
|
+ void createPipV1Window(XXPipV1Interface *pip);
|
|
+};
|
|
+
|
|
+} // namespace KWin
|
|
diff --git a/src/xxpipv1window.cpp b/src/xxpipv1window.cpp
|
|
new file mode 100644
|
|
index 00000000000..0d6b6bafc8b
|
|
--- /dev/null
|
|
+++ b/src/xxpipv1window.cpp
|
|
@@ -0,0 +1,179 @@
|
|
+/*
|
|
+ SPDX-FileCopyrightText: 2023 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
|
|
+
|
|
+ SPDX-License-Identifier: GPL-2.0-or-later
|
|
+*/
|
|
+
|
|
+#include "xxpipv1window.h"
|
|
+#include "input.h"
|
|
+#include "wayland/seat.h"
|
|
+#include "wayland/surface.h"
|
|
+#include "wayland/tablet_v2.h"
|
|
+#include "wayland_server.h"
|
|
+#include "workspace.h"
|
|
+
|
|
+namespace KWin
|
|
+{
|
|
+
|
|
+XXPipV1Window::XXPipV1Window(XXPipV1Interface *shellSurface)
|
|
+ : XdgSurfaceWindow(shellSurface->xdgSurface())
|
|
+ , m_shellSurface(shellSurface)
|
|
+{
|
|
+ setOutput(workspace()->activeOutput());
|
|
+ setMoveResizeOutput(workspace()->activeOutput());
|
|
+ setOnAllDesktops(true);
|
|
+ setOnAllActivities(true);
|
|
+
|
|
+ connect(shellSurface, &XXPipV1Interface::initializeRequested,
|
|
+ this, &XXPipV1Window::initialize);
|
|
+ connect(shellSurface, &XXPipV1Interface::aboutToBeDestroyed,
|
|
+ this, &XXPipV1Window::destroyWindow);
|
|
+ connect(shellSurface, &XXPipV1Interface::moveRequested,
|
|
+ this, &XXPipV1Window::handleMoveRequested);
|
|
+ connect(shellSurface, &XXPipV1Interface::resizeRequested,
|
|
+ this, &XXPipV1Window::handleResizeRequested);
|
|
+ connect(shellSurface, &XXPipV1Interface::applicationIdChanged,
|
|
+ this, &XXPipV1Window::handleApplicationIdChanged);
|
|
+}
|
|
+
|
|
+void XXPipV1Window::initialize()
|
|
+{
|
|
+ scheduleConfigure();
|
|
+}
|
|
+
|
|
+bool XXPipV1Window::isPictureInPicture() const
|
|
+{
|
|
+ return true;
|
|
+}
|
|
+
|
|
+bool XXPipV1Window::isResizable() const
|
|
+{
|
|
+ return true;
|
|
+}
|
|
+
|
|
+bool XXPipV1Window::isMovable() const
|
|
+{
|
|
+ return true;
|
|
+}
|
|
+
|
|
+bool XXPipV1Window::isMovableAcrossScreens() const
|
|
+{
|
|
+ return true;
|
|
+}
|
|
+
|
|
+bool XXPipV1Window::isCloseable() const
|
|
+{
|
|
+ return true;
|
|
+}
|
|
+
|
|
+void XXPipV1Window::closeWindow()
|
|
+{
|
|
+ m_shellSurface->sendClosed();
|
|
+}
|
|
+
|
|
+bool XXPipV1Window::wantsInput() const
|
|
+{
|
|
+ return false;
|
|
+}
|
|
+
|
|
+bool XXPipV1Window::takeFocus()
|
|
+{
|
|
+ return false;
|
|
+}
|
|
+
|
|
+bool XXPipV1Window::acceptsFocus() const
|
|
+{
|
|
+ return false;
|
|
+}
|
|
+
|
|
+XdgSurfaceConfigure *XXPipV1Window::sendRoleConfigure() const
|
|
+{
|
|
+ surface()->setPreferredBufferScale(nextTargetScale());
|
|
+ surface()->setPreferredBufferTransform(preferredBufferTransform());
|
|
+ surface()->setPreferredColorDescription(preferredColorDescription());
|
|
+
|
|
+ const QRectF geometry = moveResizeGeometry();
|
|
+ if (geometry.isEmpty()) {
|
|
+ const QRectF workArea = workspace()->clientArea(PlacementArea, this, moveResizeOutput());
|
|
+ m_shellSurface->sendConfigureBounds(workArea.size() * 0.25);
|
|
+ }
|
|
+
|
|
+ XdgSurfaceConfigure *configureEvent = new XdgSurfaceConfigure();
|
|
+ configureEvent->bounds = moveResizeGeometry();
|
|
+ configureEvent->serial = m_shellSurface->sendConfigureSize(geometry.size());
|
|
+
|
|
+ return configureEvent;
|
|
+}
|
|
+
|
|
+void XXPipV1Window::handleRoleDestroyed()
|
|
+{
|
|
+ m_shellSurface->disconnect(this);
|
|
+
|
|
+ XdgSurfaceWindow::handleRoleDestroyed();
|
|
+}
|
|
+
|
|
+void XXPipV1Window::handleApplicationIdChanged()
|
|
+{
|
|
+ setResourceClass(resourceName(), m_shellSurface->applicationId());
|
|
+ setDesktopFileName(m_shellSurface->applicationId());
|
|
+}
|
|
+
|
|
+void XXPipV1Window::handleMoveRequested(SeatInterface *seat, quint32 serial)
|
|
+{
|
|
+ if (const auto anchor = input()->implicitGrabPositionBySerial(seat, serial)) {
|
|
+ performMousePressCommand(Options::MouseMove, *anchor);
|
|
+ }
|
|
+}
|
|
+
|
|
+void XXPipV1Window::handleResizeRequested(SeatInterface *seat, Gravity gravity, quint32 serial)
|
|
+{
|
|
+ const auto anchor = input()->implicitGrabPositionBySerial(seat, serial);
|
|
+ if (!anchor) {
|
|
+ return;
|
|
+ }
|
|
+ if (isInteractiveMoveResize()) {
|
|
+ finishInteractiveMoveResize(false);
|
|
+ }
|
|
+ setInteractiveMoveResizePointerButtonDown(true);
|
|
+ setInteractiveMoveResizeAnchor(*anchor);
|
|
+ setInteractiveMoveResizeModifiers(Qt::KeyboardModifiers());
|
|
+ setInteractiveMoveOffset(QPointF((anchor->x() - x()) / width(), (anchor->y() - y()) / height()));
|
|
+ setUnrestrictedInteractiveMoveResize(false);
|
|
+ setInteractiveMoveResizeGravity(gravity);
|
|
+ if (!startInteractiveMoveResize()) {
|
|
+ setInteractiveMoveResizePointerButtonDown(false);
|
|
+ }
|
|
+ updateCursor();
|
|
+}
|
|
+
|
|
+void XXPipV1Window::doSetNextTargetScale()
|
|
+{
|
|
+ if (isDeleted()) {
|
|
+ return;
|
|
+ }
|
|
+ if (m_shellSurface->isConfigured()) {
|
|
+ scheduleConfigure();
|
|
+ }
|
|
+}
|
|
+
|
|
+void XXPipV1Window::doSetPreferredBufferTransform()
|
|
+{
|
|
+ if (isDeleted()) {
|
|
+ return;
|
|
+ }
|
|
+ if (m_shellSurface->isConfigured()) {
|
|
+ scheduleConfigure();
|
|
+ }
|
|
+}
|
|
+
|
|
+void XXPipV1Window::doSetPreferredColorDescription()
|
|
+{
|
|
+ if (isDeleted()) {
|
|
+ return;
|
|
+ }
|
|
+ if (m_shellSurface->isConfigured()) {
|
|
+ scheduleConfigure();
|
|
+ }
|
|
+}
|
|
+
|
|
+} // namespace KWin
|
|
diff --git a/src/xxpipv1window.h b/src/xxpipv1window.h
|
|
new file mode 100644
|
|
index 00000000000..15873e3b25f
|
|
--- /dev/null
|
|
+++ b/src/xxpipv1window.h
|
|
@@ -0,0 +1,48 @@
|
|
+/*
|
|
+ SPDX-FileCopyrightText: 2023 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
|
|
+
|
|
+ SPDX-License-Identifier: GPL-2.0-or-later
|
|
+*/
|
|
+
|
|
+#pragma once
|
|
+
|
|
+#include "wayland/xxpip_v1.h"
|
|
+#include "xdgshellwindow.h"
|
|
+
|
|
+namespace KWin
|
|
+{
|
|
+
|
|
+class XXPipV1Window final : public XdgSurfaceWindow
|
|
+{
|
|
+ Q_OBJECT
|
|
+
|
|
+public:
|
|
+ explicit XXPipV1Window(XXPipV1Interface *shellSurface);
|
|
+
|
|
+ bool isPictureInPicture() const override;
|
|
+ bool isResizable() const override;
|
|
+ bool isMovable() const override;
|
|
+ bool isMovableAcrossScreens() const override;
|
|
+ bool isCloseable() const override;
|
|
+ void closeWindow() override;
|
|
+ bool wantsInput() const override;
|
|
+ bool takeFocus() override;
|
|
+
|
|
+protected:
|
|
+ bool acceptsFocus() const override;
|
|
+ XdgSurfaceConfigure *sendRoleConfigure() const override;
|
|
+ void handleRoleDestroyed() override;
|
|
+ void doSetNextTargetScale() override;
|
|
+ void doSetPreferredBufferTransform() override;
|
|
+ void doSetPreferredColorDescription() override;
|
|
+
|
|
+private:
|
|
+ void initialize();
|
|
+ void handleApplicationIdChanged();
|
|
+ void handleMoveRequested(SeatInterface *seat, quint32 serial);
|
|
+ void handleResizeRequested(SeatInterface *seat, Gravity gravity, quint32 serial);
|
|
+
|
|
+ XXPipV1Interface *m_shellSurface;
|
|
+};
|
|
+
|
|
+} // namespace KWin
|
|
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
|
|
index 134f5416975..824cd67083c 100644
|
|
--- a/tests/CMakeLists.txt
|
|
+++ b/tests/CMakeLists.txt
|
|
@@ -1,3 +1,7 @@
|
|
+if (Qt6_VERSION VERSION_GREATER_EQUAL "6.9.0")
|
|
+ add_subdirectory(pip)
|
|
+endif()
|
|
+
|
|
if(KWIN_BUILD_X11)
|
|
set(normalhintsbasesizetest_SRCS normalhintsbasesizetest.cpp)
|
|
add_executable(normalhintsbasesizetest ${normalhintsbasesizetest_SRCS})
|
|
diff --git a/tests/pip/CMakeLists.txt b/tests/pip/CMakeLists.txt
|
|
new file mode 100644
|
|
index 00000000000..9135a5ab072
|
|
--- /dev/null
|
|
+++ b/tests/pip/CMakeLists.txt
|
|
@@ -0,0 +1,22 @@
|
|
+add_executable(piptest)
|
|
+
|
|
+target_sources(piptest PRIVATE
|
|
+ main.cpp
|
|
+ pipshellsurface.cpp
|
|
+ pip.cpp
|
|
+ window.cpp
|
|
+)
|
|
+
|
|
+qt6_generate_wayland_protocol_client_sources(piptest
|
|
+ PRIVATE_CODE
|
|
+ FILES
|
|
+ ${PROJECT_SOURCE_DIR}/src/wayland/protocols/xx-pip-v1.xml
|
|
+ ${WaylandProtocols_DATADIR}/stable/xdg-shell/xdg-shell.xml
|
|
+ ${Wayland_DATADIR}/wayland.xml
|
|
+)
|
|
+
|
|
+target_link_libraries(piptest PRIVATE
|
|
+ Qt::Gui
|
|
+ Qt::WaylandClientPrivate
|
|
+ Qt::Widgets
|
|
+)
|
|
diff --git a/tests/pip/main.cpp b/tests/pip/main.cpp
|
|
new file mode 100644
|
|
index 00000000000..6d7c4f5b3b4
|
|
--- /dev/null
|
|
+++ b/tests/pip/main.cpp
|
|
@@ -0,0 +1,19 @@
|
|
+/*
|
|
+ SPDX-FileCopyrightText: 2025 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
|
|
+
|
|
+ SPDX-License-Identifier: GPL-2.0-or-later
|
|
+*/
|
|
+
|
|
+#include <QApplication>
|
|
+
|
|
+#include "window.h"
|
|
+
|
|
+int main(int argc, char **argv)
|
|
+{
|
|
+ QApplication app(argc, argv);
|
|
+
|
|
+ Window w;
|
|
+ w.show();
|
|
+
|
|
+ return app.exec();
|
|
+}
|
|
diff --git a/tests/pip/pip.cpp b/tests/pip/pip.cpp
|
|
new file mode 100644
|
|
index 00000000000..ed14f5f98d4
|
|
--- /dev/null
|
|
+++ b/tests/pip/pip.cpp
|
|
@@ -0,0 +1,227 @@
|
|
+/*
|
|
+ SPDX-FileCopyrightText: 2025 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
|
|
+
|
|
+ SPDX-License-Identifier: GPL-2.0-or-later
|
|
+*/
|
|
+
|
|
+#include "pip.h"
|
|
+#include "pipshellsurface.h"
|
|
+
|
|
+#include <QMouseEvent>
|
|
+#include <QPainter>
|
|
+
|
|
+PipPin::PipPin(QWidget *parent)
|
|
+ : QWidget(parent)
|
|
+{
|
|
+ resize(100, 50);
|
|
+}
|
|
+
|
|
+bool PipPin::isPinned() const
|
|
+{
|
|
+ return m_pinned;
|
|
+}
|
|
+
|
|
+void PipPin::setPinned(bool pinned)
|
|
+{
|
|
+ if (m_pinned != pinned) {
|
|
+ m_pinned = pinned;
|
|
+ update();
|
|
+ }
|
|
+}
|
|
+
|
|
+void PipPin::paintEvent(QPaintEvent *event)
|
|
+{
|
|
+ QPainter painter(this);
|
|
+ painter.setClipRegion(event->region());
|
|
+
|
|
+ if (m_hovered) {
|
|
+ painter.setOpacity(1.0);
|
|
+ } else {
|
|
+ painter.setOpacity(0.5);
|
|
+ }
|
|
+
|
|
+ painter.fillRect(rect(), Qt::black);
|
|
+ painter.setPen(Qt::white);
|
|
+ painter.drawText(rect(), Qt::AlignCenter, m_pinned ? QStringLiteral("Unpin") : QStringLiteral("Pin"));
|
|
+}
|
|
+
|
|
+void PipPin::mousePressEvent(QMouseEvent *event)
|
|
+{
|
|
+ if (event->button() == Qt::LeftButton) {
|
|
+ event->accept();
|
|
+ Q_EMIT clicked();
|
|
+ }
|
|
+}
|
|
+
|
|
+void PipPin::enterEvent(QEnterEvent *event)
|
|
+{
|
|
+ m_hovered = true;
|
|
+ update();
|
|
+}
|
|
+
|
|
+void PipPin::leaveEvent(QEvent *event)
|
|
+{
|
|
+ m_hovered = false;
|
|
+ update();
|
|
+}
|
|
+
|
|
+Media::Media(QWidget *parent)
|
|
+ : QWidget(parent)
|
|
+{
|
|
+ m_pip = std::make_unique<Pip>();
|
|
+ connect(m_pip.get(), &Pip::pinned, this, [this]() {
|
|
+ m_pin->setPinned(true);
|
|
+ });
|
|
+ connect(m_pip.get(), &Pip::unpinned, this, [this]() {
|
|
+ m_pin->setPinned(false);
|
|
+ });
|
|
+
|
|
+ m_pin = new PipPin(this);
|
|
+ connect(m_pin, &PipPin::clicked, this, [this]() {
|
|
+ if (m_pin->isPinned()) {
|
|
+ m_pip->show();
|
|
+ } else {
|
|
+ m_pip->hide();
|
|
+ }
|
|
+ });
|
|
+}
|
|
+
|
|
+void Media::paintEvent(QPaintEvent *event)
|
|
+{
|
|
+ QPainter painter(this);
|
|
+ painter.setClipRegion(event->region());
|
|
+ painter.fillRect(rect(), QColor(0, 0, 0, 128));
|
|
+}
|
|
+
|
|
+void Media::resizeEvent(QResizeEvent *event)
|
|
+{
|
|
+ m_pin->move(width() - m_pin->width() - 50, height() - m_pin->height() - 50);
|
|
+ m_pip->resize(width(), height());
|
|
+}
|
|
+
|
|
+PipResizeHandle::PipResizeHandle(Qt::Edges edges, QWidget *parent)
|
|
+ : QWidget(parent)
|
|
+ , m_edges(edges)
|
|
+{
|
|
+ switch (edges) {
|
|
+ case Qt::LeftEdge:
|
|
+ case Qt::RightEdge:
|
|
+ setCursor(Qt::SizeHorCursor);
|
|
+ break;
|
|
+ case Qt::TopEdge:
|
|
+ case Qt::BottomEdge:
|
|
+ setCursor(Qt::SizeVerCursor);
|
|
+ break;
|
|
+ case Qt::TopEdge | Qt::LeftEdge:
|
|
+ case Qt::BottomEdge | Qt::RightEdge:
|
|
+ setCursor(Qt::SizeFDiagCursor);
|
|
+ break;
|
|
+ case Qt::TopEdge | Qt::RightEdge:
|
|
+ case Qt::BottomEdge | Qt::LeftEdge:
|
|
+ setCursor(Qt::SizeBDiagCursor);
|
|
+ break;
|
|
+ default:
|
|
+ Q_UNREACHABLE();
|
|
+ }
|
|
+}
|
|
+
|
|
+void PipResizeHandle::enterEvent(QEnterEvent *event)
|
|
+{
|
|
+ m_hovered = true;
|
|
+ update();
|
|
+}
|
|
+
|
|
+void PipResizeHandle::leaveEvent(QEvent *event)
|
|
+{
|
|
+ m_hovered = false;
|
|
+ update();
|
|
+}
|
|
+
|
|
+void PipResizeHandle::mousePressEvent(QMouseEvent *event)
|
|
+{
|
|
+ if (event->button() == Qt::LeftButton) {
|
|
+ event->accept();
|
|
+ window()->windowHandle()->startSystemResize(m_edges);
|
|
+ }
|
|
+}
|
|
+
|
|
+void PipResizeHandle::paintEvent(QPaintEvent *event)
|
|
+{
|
|
+ QPainter painter(this);
|
|
+ painter.setClipRegion(event->region());
|
|
+ painter.fillRect(rect(), QColor(222, 137, 190, m_hovered ? 128 : 0));
|
|
+}
|
|
+
|
|
+Pip::Pip(QWidget *parent)
|
|
+ : QWidget(parent)
|
|
+{
|
|
+ m_closeButton = new QPushButton(this);
|
|
+ m_closeButton->setIcon(QIcon::fromTheme(QStringLiteral("dialog-close")));
|
|
+ m_closeButton->setText(QStringLiteral("Close"));
|
|
+ connect(m_closeButton, &QPushButton::clicked, this, &Pip::hide);
|
|
+
|
|
+ m_topLeftResizeHandle = new PipResizeHandle(Qt::TopEdge | Qt::LeftEdge, this);
|
|
+ m_topResizeHandle = new PipResizeHandle(Qt::TopEdge, this);
|
|
+ m_topRightResizeHandle = new PipResizeHandle(Qt::TopEdge | Qt::RightEdge, this);
|
|
+ m_rightResizeHandle = new PipResizeHandle(Qt::RightEdge, this);
|
|
+ m_bottomRightResizeHandle = new PipResizeHandle(Qt::BottomEdge | Qt::BottomEdge, this);
|
|
+ m_bottomResizeHandle = new PipResizeHandle(Qt::BottomEdge, this);
|
|
+ m_bottomLeftResizeHandle = new PipResizeHandle(Qt::BottomEdge | Qt::LeftEdge, this);
|
|
+ m_leftResizeHandle = new PipResizeHandle(Qt::LeftEdge, this);
|
|
+
|
|
+ winId();
|
|
+ PipShellIntegration::assignPipRole(windowHandle());
|
|
+}
|
|
+
|
|
+void Pip::layout()
|
|
+{
|
|
+ const int gridUnit = 5;
|
|
+ const int resizeZone = 2 * gridUnit;
|
|
+
|
|
+ m_topLeftResizeHandle->setGeometry(0, 0, resizeZone, resizeZone);
|
|
+ m_topResizeHandle->setGeometry(resizeZone, 0, width() - 2 * resizeZone, resizeZone);
|
|
+ m_topRightResizeHandle->setGeometry(width() - resizeZone, 0, resizeZone, resizeZone);
|
|
+ m_rightResizeHandle->setGeometry(width() - resizeZone, resizeZone, resizeZone, height() - 2 * resizeZone);
|
|
+ m_bottomRightResizeHandle->setGeometry(width() - resizeZone, height() - resizeZone, resizeZone, resizeZone);
|
|
+ m_bottomResizeHandle->setGeometry(resizeZone, height() - resizeZone, width() - 2 * resizeZone, resizeZone);
|
|
+ m_bottomLeftResizeHandle->setGeometry(0, height() - resizeZone, resizeZone, resizeZone);
|
|
+ m_leftResizeHandle->setGeometry(0, resizeZone, resizeZone, height() - 2 * resizeZone);
|
|
+
|
|
+ m_closeButton->move(width() - resizeZone - gridUnit - m_closeButton->width(), resizeZone + gridUnit);
|
|
+}
|
|
+
|
|
+void Pip::paintEvent(QPaintEvent *event)
|
|
+{
|
|
+ QPainter painter(this);
|
|
+ painter.setClipRegion(event->region());
|
|
+ painter.fillRect(rect(), QColor(64, 67, 78));
|
|
+}
|
|
+
|
|
+void Pip::resizeEvent(QResizeEvent *event)
|
|
+{
|
|
+ layout();
|
|
+}
|
|
+
|
|
+void Pip::mousePressEvent(QMouseEvent *event)
|
|
+{
|
|
+ switch (event->button()) {
|
|
+ case Qt::LeftButton:
|
|
+ event->accept();
|
|
+ windowHandle()->startSystemMove();
|
|
+ break;
|
|
+ default:
|
|
+ break;
|
|
+ }
|
|
+}
|
|
+
|
|
+void Pip::showEvent(QShowEvent *event)
|
|
+{
|
|
+ Q_EMIT unpinned();
|
|
+}
|
|
+
|
|
+void Pip::hideEvent(QHideEvent *event)
|
|
+{
|
|
+ Q_EMIT pinned();
|
|
+}
|
|
+
|
|
+#include "moc_pip.cpp"
|
|
diff --git a/tests/pip/pip.h b/tests/pip/pip.h
|
|
new file mode 100644
|
|
index 00000000000..2f6e4a87d86
|
|
--- /dev/null
|
|
+++ b/tests/pip/pip.h
|
|
@@ -0,0 +1,102 @@
|
|
+/*
|
|
+ SPDX-FileCopyrightText: 2025 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
|
|
+
|
|
+ SPDX-License-Identifier: GPL-2.0-or-later
|
|
+*/
|
|
+
|
|
+#pragma once
|
|
+
|
|
+#include <QPushButton>
|
|
+#include <QWidget>
|
|
+
|
|
+class PipResizeHandle : public QWidget
|
|
+{
|
|
+ Q_OBJECT
|
|
+
|
|
+public:
|
|
+ explicit PipResizeHandle(Qt::Edges edges, QWidget *parent = nullptr);
|
|
+
|
|
+protected:
|
|
+ void enterEvent(QEnterEvent *event) override;
|
|
+ void leaveEvent(QEvent *event) override;
|
|
+ void mousePressEvent(QMouseEvent *event) override;
|
|
+ void paintEvent(QPaintEvent *event) override;
|
|
+
|
|
+private:
|
|
+ Qt::Edges m_edges;
|
|
+ bool m_hovered = false;
|
|
+};
|
|
+
|
|
+class Pip : public QWidget
|
|
+{
|
|
+ Q_OBJECT
|
|
+
|
|
+public:
|
|
+ explicit Pip(QWidget *parent = nullptr);
|
|
+
|
|
+Q_SIGNALS:
|
|
+ void pinned();
|
|
+ void unpinned();
|
|
+
|
|
+protected:
|
|
+ void paintEvent(QPaintEvent *event) override;
|
|
+ void resizeEvent(QResizeEvent *event) override;
|
|
+ void mousePressEvent(QMouseEvent *event) override;
|
|
+ void showEvent(QShowEvent *event) override;
|
|
+ void hideEvent(QHideEvent *event) override;
|
|
+
|
|
+private:
|
|
+ void layout();
|
|
+
|
|
+ QPushButton *m_closeButton = nullptr;
|
|
+ PipResizeHandle *m_topLeftResizeHandle = nullptr;
|
|
+ PipResizeHandle *m_topResizeHandle = nullptr;
|
|
+ PipResizeHandle *m_topRightResizeHandle = nullptr;
|
|
+ PipResizeHandle *m_rightResizeHandle = nullptr;
|
|
+ PipResizeHandle *m_bottomRightResizeHandle = nullptr;
|
|
+ PipResizeHandle *m_bottomResizeHandle = nullptr;
|
|
+ PipResizeHandle *m_bottomLeftResizeHandle = nullptr;
|
|
+ PipResizeHandle *m_leftResizeHandle = nullptr;
|
|
+};
|
|
+
|
|
+class PipPin : public QWidget
|
|
+{
|
|
+ Q_OBJECT
|
|
+
|
|
+public:
|
|
+ explicit PipPin(QWidget *parent = nullptr);
|
|
+
|
|
+ bool isPinned() const;
|
|
+ void setPinned(bool pinned);
|
|
+
|
|
+Q_SIGNALS:
|
|
+ void clicked();
|
|
+
|
|
+protected:
|
|
+ void paintEvent(QPaintEvent *event) override;
|
|
+ void mousePressEvent(QMouseEvent *event) override;
|
|
+ void enterEvent(QEnterEvent *event) override;
|
|
+ void leaveEvent(QEvent *event) override;
|
|
+
|
|
+private:
|
|
+ bool m_pinned = true;
|
|
+ bool m_hovered = false;
|
|
+};
|
|
+
|
|
+class Media : public QWidget
|
|
+{
|
|
+ Q_OBJECT
|
|
+
|
|
+public:
|
|
+ explicit Media(QWidget *parent = nullptr);
|
|
+
|
|
+protected:
|
|
+ void paintEvent(QPaintEvent *event) override;
|
|
+ void resizeEvent(QResizeEvent *event) override;
|
|
+
|
|
+private:
|
|
+ void layout();
|
|
+
|
|
+ std::unique_ptr<Pip> m_pip;
|
|
+ PipPin *m_pin = nullptr;
|
|
+};
|
|
diff --git a/tests/pip/pipshellsurface.cpp b/tests/pip/pipshellsurface.cpp
|
|
new file mode 100644
|
|
index 00000000000..082a51a3c24
|
|
--- /dev/null
|
|
+++ b/tests/pip/pipshellsurface.cpp
|
|
@@ -0,0 +1,156 @@
|
|
+/*
|
|
+ SPDX-FileCopyrightText: 2025 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
|
|
+
|
|
+ SPDX-License-Identifier: GPL-2.0-or-later
|
|
+*/
|
|
+
|
|
+#include "pipshellsurface.h"
|
|
+
|
|
+#include <QtGui/qpa/qwindowsysteminterface.h>
|
|
+#include <QtWaylandClient/private/qwaylandinputdevice_p.h>
|
|
+
|
|
+XdgWmBase::XdgWmBase()
|
|
+ : QWaylandClientExtensionTemplate<XdgWmBase>(6)
|
|
+{
|
|
+ initialize();
|
|
+ if (!isActive()) {
|
|
+ qFatal("The xdg-shell protocol is unsupported by the compositor");
|
|
+ }
|
|
+}
|
|
+
|
|
+XXPipShell::XXPipShell()
|
|
+ : QWaylandClientExtensionTemplate<XXPipShell>(1)
|
|
+{
|
|
+ initialize();
|
|
+ if (!isActive()) {
|
|
+ qFatal("The xx-pip-v1 protocol is unsupported by the compositor");
|
|
+ }
|
|
+}
|
|
+
|
|
+void PipShellIntegration::assignPipRole(QWindow *window)
|
|
+{
|
|
+ window->create();
|
|
+
|
|
+ auto waylandWindow = dynamic_cast<QtWaylandClient::QWaylandWindow *>(window->handle());
|
|
+ if (!waylandWindow) {
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ static PipShellIntegration *shellIntegration = nullptr;
|
|
+ if (!shellIntegration) {
|
|
+ shellIntegration = new PipShellIntegration();
|
|
+ }
|
|
+
|
|
+ waylandWindow->setShellIntegration(shellIntegration);
|
|
+}
|
|
+
|
|
+PipShellIntegration::PipShellIntegration()
|
|
+ : m_xdgWmBase(std::make_unique<XdgWmBase>())
|
|
+ , m_xxPipShell(std::make_unique<XXPipShell>())
|
|
+{
|
|
+}
|
|
+
|
|
+bool PipShellIntegration::initialize(QtWaylandClient::QWaylandDisplay *display)
|
|
+{
|
|
+ return m_xdgWmBase->isInitialized() && m_xxPipShell->isInitialized();
|
|
+}
|
|
+
|
|
+QtWaylandClient::QWaylandShellSurface *PipShellIntegration::createShellSurface(QtWaylandClient::QWaylandWindow *window)
|
|
+{
|
|
+ ::xdg_surface *xdgSurface = m_xdgWmBase->get_xdg_surface(window->wlSurface());
|
|
+ ::xx_pip_v1 *xxPip = m_xxPipShell->get_pip(xdgSurface);
|
|
+ return new PipShellSurface(xdgSurface, xxPip, window);
|
|
+}
|
|
+
|
|
+PipShellSurface::PipShellSurface(::xdg_surface *xdgSurface, ::xx_pip_v1 *xxPip, QtWaylandClient::QWaylandWindow *window)
|
|
+ : QWaylandShellSurface(window)
|
|
+ , QtWayland::xdg_surface(xdgSurface)
|
|
+ , QtWayland::xx_pip_v1(xxPip)
|
|
+{
|
|
+}
|
|
+
|
|
+PipShellSurface::~PipShellSurface()
|
|
+{
|
|
+ xx_pip_v1::destroy();
|
|
+ xdg_surface::destroy();
|
|
+}
|
|
+
|
|
+bool PipShellSurface::isExposed() const
|
|
+{
|
|
+ return m_configured;
|
|
+}
|
|
+
|
|
+void PipShellSurface::applyConfigure()
|
|
+{
|
|
+ QSize size = window()->windowContentGeometry().size();
|
|
+ if (m_pendingSize.width() > 0) {
|
|
+ size.setWidth(m_pendingSize.width());
|
|
+ }
|
|
+ if (m_pendingSize.height() > 0) {
|
|
+ size.setHeight(m_pendingSize.height());
|
|
+ }
|
|
+
|
|
+ window()->resizeFromApplyConfigure(size);
|
|
+}
|
|
+
|
|
+void PipShellSurface::setWindowGeometry(const QRect &rect)
|
|
+{
|
|
+ if (window()->isExposed()) {
|
|
+ xdg_surface::set_window_geometry(rect.x(), rect.y(), rect.width(), rect.height());
|
|
+ }
|
|
+}
|
|
+
|
|
+bool PipShellSurface::move(QtWaylandClient::QWaylandInputDevice *inputDevice)
|
|
+{
|
|
+ if (!m_configured) {
|
|
+ return false;
|
|
+ }
|
|
+ xx_pip_v1::move(inputDevice->wl_seat(), inputDevice->serial());
|
|
+ return true;
|
|
+}
|
|
+
|
|
+bool PipShellSurface::resize(QtWaylandClient::QWaylandInputDevice *inputDevice, Qt::Edges edges)
|
|
+{
|
|
+ if (!m_configured) {
|
|
+ return false;
|
|
+ }
|
|
+
|
|
+ const resize_edge edge = static_cast<enum resize_edge>(
|
|
+ ((edges & Qt::TopEdge) ? resize_edge_top : 0)
|
|
+ | ((edges & Qt::BottomEdge) ? resize_edge_bottom : 0)
|
|
+ | ((edges & Qt::LeftEdge) ? resize_edge_left : 0)
|
|
+ | ((edges & Qt::RightEdge) ? resize_edge_right : 0));
|
|
+
|
|
+ xx_pip_v1::resize(inputDevice->wl_seat(), inputDevice->serial(), edge);
|
|
+ return true;
|
|
+}
|
|
+
|
|
+void PipShellSurface::xdg_surface_configure(uint32_t serial)
|
|
+{
|
|
+ xdg_surface::ack_configure(serial);
|
|
+
|
|
+ if (!m_configured) {
|
|
+ m_configured = true;
|
|
+ applyConfigure();
|
|
+ } else {
|
|
+ window()->applyConfigureWhenPossible();
|
|
+ }
|
|
+
|
|
+ window()->updateExposure();
|
|
+}
|
|
+
|
|
+void PipShellSurface::xx_pip_v1_configure_bounds(int32_t width, int32_t height)
|
|
+{
|
|
+}
|
|
+
|
|
+void PipShellSurface::xx_pip_v1_configure_size(int32_t width, int32_t height)
|
|
+{
|
|
+ m_pendingSize = QSize(width, height);
|
|
+}
|
|
+
|
|
+void PipShellSurface::xx_pip_v1_closed()
|
|
+{
|
|
+ QWindowSystemInterface::handleCloseEvent(window()->window());
|
|
+}
|
|
+
|
|
+#include "moc_pipshellsurface.cpp"
|
|
diff --git a/tests/pip/pipshellsurface.h b/tests/pip/pipshellsurface.h
|
|
new file mode 100644
|
|
index 00000000000..b02e83b3fc9
|
|
--- /dev/null
|
|
+++ b/tests/pip/pipshellsurface.h
|
|
@@ -0,0 +1,70 @@
|
|
+/*
|
|
+ SPDX-FileCopyrightText: 2025 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
|
|
+
|
|
+ SPDX-License-Identifier: GPL-2.0-or-later
|
|
+*/
|
|
+
|
|
+#pragma once
|
|
+
|
|
+#include <QtWaylandClient/QWaylandClientExtensionTemplate>
|
|
+#include <QtWaylandClient/private/qwaylandshellintegration_p.h>
|
|
+#include <QtWaylandClient/private/qwaylandshellsurface_p.h>
|
|
+#include <QtWaylandClient/private/qwaylandwindow_p.h>
|
|
+
|
|
+#include "qwayland-xdg-shell.h"
|
|
+#include "qwayland-xx-pip-v1.h"
|
|
+
|
|
+class XdgWmBase : public QWaylandClientExtensionTemplate<XdgWmBase>, public QtWayland::xdg_wm_base
|
|
+{
|
|
+ Q_OBJECT
|
|
+
|
|
+public:
|
|
+ XdgWmBase();
|
|
+};
|
|
+
|
|
+class XXPipShell : public QWaylandClientExtensionTemplate<XXPipShell>, public QtWayland::xx_pip_shell_v1
|
|
+{
|
|
+ Q_OBJECT
|
|
+
|
|
+public:
|
|
+ XXPipShell();
|
|
+};
|
|
+
|
|
+class PipShellIntegration : public QtWaylandClient::QWaylandShellIntegration
|
|
+{
|
|
+public:
|
|
+ PipShellIntegration();
|
|
+
|
|
+ bool initialize(QtWaylandClient::QWaylandDisplay *display) override;
|
|
+ QtWaylandClient::QWaylandShellSurface *createShellSurface(QtWaylandClient::QWaylandWindow *window) override;
|
|
+
|
|
+ static void assignPipRole(QWindow *window);
|
|
+
|
|
+private:
|
|
+ std::unique_ptr<XdgWmBase> m_xdgWmBase;
|
|
+ std::unique_ptr<XXPipShell> m_xxPipShell;
|
|
+};
|
|
+
|
|
+class PipShellSurface : public QtWaylandClient::QWaylandShellSurface, public QtWayland::xdg_surface, public QtWayland::xx_pip_v1
|
|
+{
|
|
+ Q_OBJECT
|
|
+
|
|
+public:
|
|
+ PipShellSurface(::xdg_surface *xdgSurface, ::xx_pip_v1 *xxPip, QtWaylandClient::QWaylandWindow *window);
|
|
+ ~PipShellSurface() override;
|
|
+
|
|
+ bool isExposed() const override;
|
|
+ void applyConfigure() override;
|
|
+ void setWindowGeometry(const QRect &rect) override;
|
|
+ bool move(QtWaylandClient::QWaylandInputDevice *inputDevice) override;
|
|
+ bool resize(QtWaylandClient::QWaylandInputDevice *inputDevice, Qt::Edges edges) override;
|
|
+
|
|
+private:
|
|
+ void xdg_surface_configure(uint32_t serial) override;
|
|
+ void xx_pip_v1_closed() override;
|
|
+ void xx_pip_v1_configure_bounds(int32_t width, int32_t height) override;
|
|
+ void xx_pip_v1_configure_size(int32_t width, int32_t height) override;
|
|
+
|
|
+ QSize m_pendingSize;
|
|
+ bool m_configured = false;
|
|
+};
|
|
diff --git a/tests/pip/window.cpp b/tests/pip/window.cpp
|
|
new file mode 100644
|
|
index 00000000000..2ac98bde845
|
|
--- /dev/null
|
|
+++ b/tests/pip/window.cpp
|
|
@@ -0,0 +1,19 @@
|
|
+/*
|
|
+ SPDX-FileCopyrightText: 2025 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
|
|
+
|
|
+ SPDX-License-Identifier: GPL-2.0-or-later
|
|
+*/
|
|
+
|
|
+#include "window.h"
|
|
+#include "pip.h"
|
|
+
|
|
+Window::Window(QWidget *parent)
|
|
+ : QWidget(parent)
|
|
+{
|
|
+ resize(800, 600);
|
|
+
|
|
+ m_media = new Media(this);
|
|
+ m_media->setGeometry(100, 100, 400, 300);
|
|
+}
|
|
+
|
|
+#include "moc_window.cpp"
|
|
diff --git a/tests/pip/window.h b/tests/pip/window.h
|
|
new file mode 100644
|
|
index 00000000000..bfd83e9d467
|
|
--- /dev/null
|
|
+++ b/tests/pip/window.h
|
|
@@ -0,0 +1,22 @@
|
|
+/*
|
|
+ SPDX-FileCopyrightText: 2025 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
|
|
+
|
|
+ SPDX-License-Identifier: GPL-2.0-or-later
|
|
+*/
|
|
+
|
|
+#pragma once
|
|
+
|
|
+#include <QWidget>
|
|
+
|
|
+class Media;
|
|
+
|
|
+class Window : public QWidget
|
|
+{
|
|
+ Q_OBJECT
|
|
+
|
|
+public:
|
|
+ explicit Window(QWidget *parent = nullptr);
|
|
+
|
|
+private:
|
|
+ Media *m_media = nullptr;
|
|
+};
|
|
--
|
|
GitLab
|
|
|
|
|
|
From cab39d37c8cb7d1c37d04a76cc7fa7af39fb7908 Mon Sep 17 00:00:00 2001
|
|
From: Vlad Zahorodnii <vlad.zahorodnii@kde.org>
|
|
Date: Thu, 12 Jun 2025 14:22:55 +0000
|
|
Subject: [PATCH 2/2] Apply 2 suggestion(s) to 1 file(s)
|
|
|
|
Co-authored-by: Xaver Hugl <xaver.hugl@kde.org>
|
|
---
|
|
src/wayland/xxpip_v1.cpp | 4 ++--
|
|
1 file changed, 2 insertions(+), 2 deletions(-)
|
|
|
|
diff --git a/src/wayland/xxpip_v1.cpp b/src/wayland/xxpip_v1.cpp
|
|
index e0b1f36134f..bc1b65d0212 100644
|
|
--- a/src/wayland/xxpip_v1.cpp
|
|
+++ b/src/wayland/xxpip_v1.cpp
|
|
@@ -282,7 +282,7 @@ quint32 XXPipV1Interface::sendConfigureSize(const QSizeF &size)
|
|
{
|
|
const quint32 serial = d->shell->display()->nextSerial();
|
|
|
|
- d->send_configure_size(size.width(), size.height());
|
|
+ d->send_configure_size(std::round(size.width()), std::round(size.height()));
|
|
|
|
auto xdgSurfacePrivate = XdgSurfaceInterfacePrivate::get(xdgSurface());
|
|
xdgSurfacePrivate->send_configure(serial);
|
|
@@ -298,7 +298,7 @@ void XXPipV1Interface::sendClosed()
|
|
|
|
void XXPipV1Interface::sendConfigureBounds(const QSizeF &size)
|
|
{
|
|
- d->send_configure_bounds(size.width(), size.height());
|
|
+ d->send_configure_bounds(std::round(size.width()), std::round(size.height()));
|
|
}
|
|
|
|
XXPipV1Interface *XXPipV1Interface::get(::wl_resource *resource)
|
|
--
|
|
GitLab
|
|
|