diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 69f5c42..fd1d086 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -36,6 +36,7 @@ elseif(APPLE) else() list(APPEND _src qwindowkit_linux.h + qwindowkit_linux.cpp ) endif() @@ -66,6 +67,15 @@ else() contexts/qtwindowcontext_p.h contexts/qtwindowcontext.cpp ) + + if(LINUX) + list(APPEND _src + contexts/linuxx11context_p.h + contexts/linuxx11context.cpp + contexts/linuxwaylandcontext_p.h + contexts/linuxwaylandcontext.cpp + ) + endif() endif() endif() diff --git a/src/core/contexts/abstractwindowcontext.cpp b/src/core/contexts/abstractwindowcontext.cpp index 324dd4e..086edf4 100644 --- a/src/core/contexts/abstractwindowcontext.cpp +++ b/src/core/contexts/abstractwindowcontext.cpp @@ -1,5 +1,5 @@ -// Copyright (C) 2023-2024 Stdware Collections (https://www.github.com/stdware) // Copyright (C) 2021-2023 wangwenx190 (Yuhang Zhao) +// Copyright (C) 2023-2024 Stdware Collections (https://www.github.com/stdware) // SPDX-License-Identifier: Apache-2.0 #include "abstractwindowcontext_p.h" @@ -126,7 +126,7 @@ namespace QWK { return false; } - for (auto item : m_hitTestVisibleItems) { + for (auto &&item : std::as_const(m_hitTestVisibleItems)) { if (item && m_delegate->isVisible(item) && m_delegate->mapGeometryToScene(item).contains(pos)) { return false; diff --git a/src/core/contexts/abstractwindowcontext_p.h b/src/core/contexts/abstractwindowcontext_p.h index dc18f37..057b16b 100644 --- a/src/core/contexts/abstractwindowcontext_p.h +++ b/src/core/contexts/abstractwindowcontext_p.h @@ -1,5 +1,5 @@ -// Copyright (C) 2023-2024 Stdware Collections (https://www.github.com/stdware) // Copyright (C) 2021-2023 wangwenx190 (Yuhang Zhao) +// Copyright (C) 2023-2024 Stdware Collections (https://www.github.com/stdware) // SPDX-License-Identifier: Apache-2.0 #ifndef ABSTRACTWINDOWCONTEXT_P_H diff --git a/src/core/contexts/linuxwaylandcontext.cpp b/src/core/contexts/linuxwaylandcontext.cpp new file mode 100644 index 0000000..16f1251 --- /dev/null +++ b/src/core/contexts/linuxwaylandcontext.cpp @@ -0,0 +1,65 @@ +// Copyright (C) 2021-2023 wangwenx190 (Yuhang Zhao) +// Copyright (C) 2023-2024 Stdware Collections (https://www.github.com/stdware) +// Copyright (C) 2025-2027 Wing-summer (wingsummer) +// SPDX-License-Identifier: Apache-2.0 + +#include "linuxwaylandcontext_p.h" + +#include "qwindowkit_linux.h" + +#include + +namespace QWK { + static inline void xdg_toplevel_show_window_menu(struct xdg_toplevel *xdg_toplevel, + struct wl_seat *seat, uint32_t serial, + int32_t x, int32_t y) { + constexpr auto XDG_TOPLEVEL_SHOW_WINDOW_MENU = 4; + const auto &api = QWK::Private::waylandAPI(); + Q_ASSERT(api.isValid()); + api.wl_proxy_marshal_flags( + reinterpret_cast(xdg_toplevel), XDG_TOPLEVEL_SHOW_WINDOW_MENU, + nullptr, api.wl_proxy_get_version(reinterpret_cast(xdg_toplevel)), 0, + seat, serial, x, y); + } + + LinuxWaylandContext::LinuxWaylandContext() : QtWindowContext() { + } + + LinuxWaylandContext::~LinuxWaylandContext() = default; + + QString LinuxWaylandContext::key() const { + return QStringLiteral("wayland"); + } + + void LinuxWaylandContext::virtual_hook(int id, void *data) { + if (id == ShowSystemMenuHook) { + auto *waylandApp = qApp->nativeInterface(); + if (!waylandApp) { + return; + } + uint serial = waylandApp->lastInputSerial(); + wl_seat *seat = waylandApp->lastInputSeat(); + if (serial == 0 || !seat) { + return; + } + + auto toplevel = static_cast( + QGuiApplication::platformNativeInterface()->nativeResourceForWindow( + "xdg_toplevel", m_windowHandle)); + if (!toplevel) { + return; + } + auto pos = static_cast(data); + xdg_toplevel_show_window_menu(toplevel, seat, serial, pos->x(), pos->y()); + + wl_display *d = waylandApp->display(); + if (d) { + const auto &api = QWK::Private::waylandAPI(); + Q_ASSERT(api.isValid()); + api.wl_display_flush(d); + } + } else { + QtWindowContext::virtual_hook(id, data); + } + } +} diff --git a/src/core/contexts/linuxwaylandcontext_p.h b/src/core/contexts/linuxwaylandcontext_p.h new file mode 100644 index 0000000..063222f --- /dev/null +++ b/src/core/contexts/linuxwaylandcontext_p.h @@ -0,0 +1,36 @@ +// Copyright (C) 2021-2023 wangwenx190 (Yuhang Zhao) +// Copyright (C) 2023-2024 Stdware Collections (https://www.github.com/stdware) +// Copyright (C) 2025-2027 Wing-summer (wingsummer) +// SPDX-License-Identifier: Apache-2.0 + + +#ifndef LINUXWAYLANDCONTEXT_P_H +#define LINUXWAYLANDCONTEXT_P_H + +// +// W A R N I N G !!! +// ----------------- +// +// This file is not part of the QWindowKit API. It is used purely as an +// implementation detail. This header file may change from version to +// version without notice, or may even be removed. +// + + +#include "qtwindowcontext_p.h" + +namespace QWK { + + class LinuxWaylandContext : public QtWindowContext { + Q_OBJECT + public: + LinuxWaylandContext(); + ~LinuxWaylandContext() override; + + QString key() const override; + void virtual_hook(int id, void *data) override; + }; + +} + +#endif // LINUXWAYLANDCONTEXT_P_H diff --git a/src/core/contexts/linuxx11context.cpp b/src/core/contexts/linuxx11context.cpp new file mode 100644 index 0000000..2c409ad --- /dev/null +++ b/src/core/contexts/linuxx11context.cpp @@ -0,0 +1,191 @@ +// Copyright (C) 2021-2023 wangwenx190 (Yuhang Zhao) +// Copyright (C) 2023-2024 Stdware Collections (https://www.github.com/stdware) +// Copyright (C) 2025-2027 Wing-summer (wingsummer) +// SPDX-License-Identifier: Apache-2.0 + + +#include "linuxx11context_p.h" + +#include "qwindowkit_linux.h" + +// copy from X11 library and simplify it +typedef struct _XExtData XExtData; +struct ScreenFormat; +struct Depth; +struct Visual; +typedef XID Colormap; +typedef struct { + XExtData *ext_data; /* hook for extension to hang data */ + struct _XDisplay *display; /* back pointer to display structure */ + Window root; /* Root window id. */ + int width, height; /* width and height of screen */ + int mwidth, mheight; /* width and height of in millimeters */ + int ndepths; /* number of depths possible */ + Depth *depths; /* list of allowable depths on the screen */ + int root_depth; /* bits per pixel */ + Visual *root_visual; /* root visual */ + + // typedef struct _XGC + // #ifdef XLIB_ILLEGAL_ACCESS + // { + // XExtData *ext_data; /* hook for extension to hang data */ + // GContext gid; /* protocol ID for graphics context */ + // /* there is more to this structure, but it is private to Xlib */ + // } + // #endif + // *GC; + void * /*GC*/ default_gc; /* GC for the root root visual */ + + Colormap cmap; /* default color map */ + unsigned long white_pixel; + unsigned long black_pixel; /* White and Black pixel values */ + int max_maps, min_maps; /* max and min color maps */ + int backing_store; /* Never, WhenMapped, Always */ + Bool save_unders; + long root_input_mask; /* initial root input mask */ +} Screen; +typedef char *XPointer; +typedef struct { + XExtData *ext_data; /* hook for extension to hang data */ + struct _XPrivate *private1; + int fd; /* Network socket. */ + int private2; + int proto_major_version; /* major version of server's X protocol */ + int proto_minor_version; /* minor version of servers X protocol */ + char *vendor; /* vendor of the server hardware */ + XID private3; + XID private4; + XID private5; + int private6; + XID (*resource_alloc)(/* allocator function */ + struct _XDisplay *); + int byte_order; /* screen byte order, LSBFirst, MSBFirst */ + int bitmap_unit; /* padding and data requirements */ + int bitmap_pad; /* padding requirements on bitmaps */ + int bitmap_bit_order; /* LeastSignificant or MostSignificant */ + int nformats; /* number of pixmap formats in list */ + ScreenFormat *pixmap_format; /* pixmap format list */ + int private8; + int release; /* release of the server */ + struct _XPrivate *private9, *private10; + int qlen; /* Length of input event queue */ + unsigned long last_request_read; /* seq number of last event read */ + unsigned long request; /* sequence number of last request. */ + XPointer private11; + XPointer private12; + XPointer private13; + XPointer private14; + unsigned max_request_size; /* maximum number 32 bit words in request*/ + struct _XrmHashBucketRec *db; + int (*private15)(struct _XDisplay *); + char *display_name; /* "host:display" string used on this connect*/ + int default_screen; /* default screen for operations */ + int nscreens; /* number of screens on this server*/ + Screen *screens; /* pointer to list of screens */ + unsigned long motion_buffer; /* size of motion buffer */ + unsigned long private16; + int min_keycode; /* minimum defined keycode */ + int max_keycode; /* maximum defined keycode */ + XPointer private17; + XPointer private18; + int private19; + char *xdefaults; /* contents of defaults from server */ + /* there is more to this structure, but it is private to Xlib */ +} *_XPrivDisplay; + +typedef struct { + int type; + unsigned long serial; /* # of last request processed by server */ + Bool send_event; /* true if this came from a SendEvent request */ + Display *display; /* Display the event was read from */ + Window window; + Atom message_type; + int format; + union { + char b[20]; + short s[10]; + long l[5]; + } data; +} XClientMessageEvent; + +/* + * this union is defined so Xlib can always use the same sized + * event structure internally, to avoid memory fragmentation. + */ +union _XEvent { + // delete so many members but size unchanged because of pad + XClientMessageEvent xclient; + long pad[24]; +}; + +#define ScreenOfDisplay(dpy, scr) (&((_XPrivDisplay) (dpy))->screens[scr]) +#define DefaultScreen(dpy) (((_XPrivDisplay) (dpy))->default_screen) +#define DefaultRootWindow(dpy) (ScreenOfDisplay(dpy, DefaultScreen(dpy))->root) + +namespace QWK { + + LinuxX11Context::LinuxX11Context() : QtWindowContext() { + } + + LinuxX11Context::~LinuxX11Context() = default; + + QString LinuxX11Context::key() const { + return QStringLiteral("xcb"); + } + + void LinuxX11Context::virtual_hook(int id, void *data) { + if (id == ShowSystemMenuHook) { + auto *x11app = qApp->nativeInterface(); + if (!x11app) { + return; + } + + auto display = x11app->display(); + if (!display) { + return; + } + + const auto &api = QWK::Private::x11API(); + Q_ASSERT(api.isValid()); + + // some marcos to constexpr in X11 + constexpr auto None = 0L; + constexpr auto ClientMessage = 33; + constexpr auto False = 0; + constexpr auto Button3 = 3; + constexpr auto SubstructureNotifyMask = 1L << 19; + constexpr auto SubstructureRedirectMask = 1L << 20; + + // use window id (XID) + auto xwin = static_cast(m_windowId); + Atom atom = api.XInternAtom(display, "_GTK_SHOW_WINDOW_MENU", False); + if (atom == None) + return; // WM might not support this atom + auto pos = static_cast(data); + XEvent ev{}; + ev.xclient.type = ClientMessage; + ev.xclient.window = xwin; + ev.xclient.message_type = atom; + + // The format member is set to 8, 16, or 32 + // and specifies whether the data should be viewed as + // a list of bytes, shorts, or longs - typeof(xclient.data). + ev.xclient.format = 32; + + qreal dpr = m_windowHandle->devicePixelRatio(); + int root_x = qRound(pos->x() * dpr); + int root_y = qRound(pos->y() * dpr); + + ev.xclient.data.l[0] = Button3; // right button + ev.xclient.data.l[1] = root_x; + ev.xclient.data.l[2] = root_y; + + Window root = DefaultRootWindow(display); + api.XSendEvent(display, root, False, SubstructureRedirectMask | SubstructureNotifyMask, + &ev); + api.XFlush(display); + } else { + QtWindowContext::virtual_hook(id, data); + } + } +} diff --git a/src/core/contexts/linuxx11context_p.h b/src/core/contexts/linuxx11context_p.h new file mode 100644 index 0000000..2b17623 --- /dev/null +++ b/src/core/contexts/linuxx11context_p.h @@ -0,0 +1,34 @@ +// Copyright (C) 2021-2023 wangwenx190 (Yuhang Zhao) +// Copyright (C) 2023-2024 Stdware Collections (https://www.github.com/stdware) +// Copyright (C) 2025-2027 Wing-summer (wingsummer) +// SPDX-License-Identifier: Apache-2.0 + +#ifndef LINUXX11CONTEXT_P_H +#define LINUXX11CONTEXT_P_H + +// +// W A R N I N G !!! +// ----------------- +// +// This file is not part of the QWindowKit API. It is used purely as an +// implementation detail. This header file may change from version to +// version without notice, or may even be removed. +// + +#include "qtwindowcontext_p.h" + +namespace QWK { + + class LinuxX11Context : public QtWindowContext { + Q_OBJECT + public: + LinuxX11Context(); + ~LinuxX11Context() override; + + QString key() const override; + void virtual_hook(int id, void *data) override; + }; + +} + +#endif // LINUXX11CONTEXT_P_H diff --git a/src/core/contexts/qtwindowcontext.cpp b/src/core/contexts/qtwindowcontext.cpp index a476211..a400235 100644 --- a/src/core/contexts/qtwindowcontext.cpp +++ b/src/core/contexts/qtwindowcontext.cpp @@ -1,5 +1,5 @@ -// Copyright (C) 2023-2024 Stdware Collections (https://www.github.com/stdware) // Copyright (C) 2021-2023 wangwenx190 (Yuhang Zhao) +// Copyright (C) 2023-2024 Stdware Collections (https://www.github.com/stdware) // SPDX-License-Identifier: Apache-2.0 #include "qtwindowcontext_p.h" @@ -151,7 +151,9 @@ namespace QWK { break; } case Qt::RightButton: { - m_context->showSystemMenu(globalPos); + if (inTitleBar) { + m_context->showSystemMenu(globalPos); + } break; } default: diff --git a/src/core/contexts/qtwindowcontext_p.h b/src/core/contexts/qtwindowcontext_p.h index 1625ea1..9d315d5 100644 --- a/src/core/contexts/qtwindowcontext_p.h +++ b/src/core/contexts/qtwindowcontext_p.h @@ -1,5 +1,5 @@ -// Copyright (C) 2023-2024 Stdware Collections (https://www.github.com/stdware) // Copyright (C) 2021-2023 wangwenx190 (Yuhang Zhao) +// Copyright (C) 2023-2024 Stdware Collections (https://www.github.com/stdware) // SPDX-License-Identifier: Apache-2.0 #ifndef QTWINDOWCONTEXT_P_H diff --git a/src/core/qwindowkit_linux.cpp b/src/core/qwindowkit_linux.cpp new file mode 100644 index 0000000..6a76e96 --- /dev/null +++ b/src/core/qwindowkit_linux.cpp @@ -0,0 +1,80 @@ +// Copyright (C) 2021-2023 wangwenx190 (Yuhang Zhao) +// Copyright (C) 2023-2024 Stdware Collections (https://www.github.com/stdware) +// Copyright (C) 2025-2027 Wing-summer (wingsummer) +// SPDX-License-Identifier: Apache-2.0 + +#include "qwindowkit_linux.h" + +namespace QWK { + namespace Private { + + bool isX11Platform() { + static const bool isX11 = QGuiApplication::platformName().startsWith( + QStringLiteral("xcb"), Qt::CaseInsensitive); + return isX11; + } + + bool isWaylandPlatform() { + static const bool isWayland = QGuiApplication::platformName().startsWith( + QStringLiteral("wayland"), Qt::CaseInsensitive); + return isWayland; + } + + const LinuxWaylandAPI &waylandAPI() { + static LinuxWaylandAPI api; + static bool guard = true; + if (guard && isWaylandPlatform()) { + QLibrary waylib(QStringLiteral("libwayland-client.so")); + bool loaded = false; + if (waylib.load()) { + loaded = true; + } else { + waylib.setFileName(QStringLiteral("libwayland-client.so.0")); + if (waylib.load()) { + loaded = true; + } + } + + if (loaded) { + api.wl_display_flush = reinterpret_cast( + waylib.resolve("wl_display_flush")); + api.wl_proxy_marshal_flags = + reinterpret_cast( + waylib.resolve("wl_proxy_marshal_flags")); + api.wl_proxy_get_version = + reinterpret_cast( + waylib.resolve("wl_proxy_get_version")); + } + } + guard = false; + return api; + } + + const LinuxX11API &x11API() { + static LinuxX11API api; + static bool guard = true; + if (guard && isX11Platform()) { + QString libName = QStringLiteral( +#if defined(__CYGWIN__) + "libX11-6.so" +#elif defined(__OpenBSD__) || defined(__NetBSD__) + "libX11.so" +#else + "libX11.so.6" +#endif + ); + QLibrary x11lib(libName); + if (x11lib.load()) { + api.XInternAtom = + reinterpret_cast(x11lib.resolve("XInternAtom")); + api.XSendEvent = + reinterpret_cast(x11lib.resolve("XSendEvent")); + api.XFlush = reinterpret_cast(x11lib.resolve("XFlush")); + } + } + guard = false; + return api; + } + + } +} diff --git a/src/core/qwindowkit_linux.h b/src/core/qwindowkit_linux.h index 17e0106..14c0cd6 100644 --- a/src/core/qwindowkit_linux.h +++ b/src/core/qwindowkit_linux.h @@ -1,9 +1,85 @@ -// Copyright (C) 2023-2024 Stdware Collections (https://www.github.com/stdware) // Copyright (C) 2021-2023 wangwenx190 (Yuhang Zhao) +// Copyright (C) 2023-2024 Stdware Collections (https://www.github.com/stdware) +// Copyright (C) 2025-2027 Wing-summer (wingsummer) // SPDX-License-Identifier: Apache-2.0 #ifndef QWINDOWKIT_LINUX_H #define QWINDOWKIT_LINUX_H +// +// W A R N I N G !!! +// ----------------- +// +// This file is not part of the QWindowKit API. It is used purely as an +// implementation detail. This header file may change from version to +// version without notice, or may even be removed. +// + +#include "qguiapplication_platform.h" + +#include +#include +#include + +// some declarations about x11 +using Atom = unsigned long; +using Bool = int; +using XID = unsigned long; +using Window = XID; + +union _XEvent; +using XEvent = union _XEvent; + +// for wayland +struct wl_proxy; + +namespace QWK { + namespace Private { + struct LinuxX11API { + LinuxX11API() = default; + Q_DISABLE_COPY(LinuxX11API) + + using XInternAtomFn = Atom (*)(Display *, const char *, Bool); + using XSendEventFn = int (*)(Display *, Window, Bool, long, XEvent *); + using XFlushFn = int (*)(Display *); + + XInternAtomFn XInternAtom = nullptr; + XSendEventFn XSendEvent = nullptr; + XFlushFn XFlush = nullptr; + + inline bool isValid() const { + return XInternAtom && XSendEvent && XFlush; + } + }; + + struct LinuxWaylandAPI { + LinuxWaylandAPI() = default; + Q_DISABLE_COPY(LinuxWaylandAPI) + + using wl_display_flush_fn = int (*)(struct wl_display *); + using wl_proxy_marshal_flags_fn = void (*)(struct wl_proxy *, uint32_t, + const struct wl_interface *, uint32_t, + uint32_t, ...); + using wl_proxy_get_version_fn = int (*)(struct wl_proxy *); + + wl_display_flush_fn wl_display_flush = nullptr; + wl_proxy_marshal_flags_fn wl_proxy_marshal_flags = nullptr; + wl_proxy_get_version_fn wl_proxy_get_version = nullptr; + + inline bool isValid() const { + return wl_display_flush && wl_proxy_marshal_flags && wl_proxy_get_version; + } + }; + + + bool isWaylandPlatform(); + + bool isX11Platform(); + + const LinuxX11API &x11API(); + + const LinuxWaylandAPI &waylandAPI(); + } +} #endif // QWINDOWKIT_LINUX_H diff --git a/src/core/windowagentbase.cpp b/src/core/windowagentbase.cpp index 6b68cee..b7b7cc4 100644 --- a/src/core/windowagentbase.cpp +++ b/src/core/windowagentbase.cpp @@ -3,6 +3,7 @@ // SPDX-License-Identifier: Apache-2.0 #include "windowagentbase.h" +#include "qwindowkit_linux.h" #include "windowagentbase_p.h" #include @@ -15,6 +16,8 @@ # include "cocoawindowcontext_p.h" #else # include "qtwindowcontext_p.h" +# include "linuxwaylandcontext_p.h" +# include "linuxx11context_p.h" #endif Q_LOGGING_CATEGORY(qWindowKitLog, "qwindowkit") @@ -56,6 +59,16 @@ namespace QWK { #elif defined(Q_OS_MAC) && !QWINDOWKIT_CONFIG(ENABLE_QT_WINDOW_CONTEXT) return new CocoaWindowContext(); #else + if (QWK::Private::isX11Platform()) { + if (QWK::Private::x11API().isValid()) { + return new LinuxX11Context(); + } + } else if (QWK::Private::isWaylandPlatform()) { + if (QWK::Private::waylandAPI().isValid()) { + return new LinuxWaylandContext(); + } + } + // fallback to QtWindowContext return new QtWindowContext(); #endif }