KWin
Loading...
Searching...
No Matches
x11_standalone_backend.cpp
Go to the documentation of this file.
1/*
2 KWin - the KDE window manager
3 This file is part of the KDE project.
4
5 SPDX-FileCopyrightText: 2016 Martin Gräßlin <mgraesslin@kde.org>
6
7 SPDX-License-Identifier: GPL-2.0-or-later
8*/
10
11#include "config-kwin.h"
12
13#include "atoms.h"
14#include "core/session.h"
16#include "x11_standalone_edge.h"
19#if HAVE_GLX
21#endif
22#if HAVE_X11_XINPUT
24#endif
25#include "core/renderloop.h"
26#include "keyboard_input.h"
27#include "options.h"
28#include "utils/c_ptr.h"
29#include "utils/edid.h"
30#include "utils/xcbutils.h"
31#include "window.h"
32#include "workspace.h"
40#include "xkb.h"
41
43
44#include <KConfigGroup>
45#include <KLocalizedString>
46
47#include <QOpenGLContext>
48#include <QThread>
49#include <private/qtx11extras_p.h>
50
51#include <span>
52
53namespace KWin
54{
55
57{
58public:
59 explicit XrandrEventFilter(X11StandaloneBackend *backend);
60
61 bool event(xcb_generic_event_t *event) override;
62
63private:
64 X11StandaloneBackend *m_backend;
65};
66
68 : X11EventFilter(Xcb::Extensions::self()->randrNotifyEvent())
69 , m_backend(backend)
70{
71}
72
73bool XrandrEventFilter::event(xcb_generic_event_t *event)
74{
75 Q_ASSERT((event->response_type & ~0x80) == Xcb::Extensions::self()->randrNotifyEvent());
76 // let's try to gather a few XRandR events, unlikely that there is just one
77 m_backend->scheduleUpdateOutputs();
78
79 // update default screen
80 auto *xrrEvent = reinterpret_cast<xcb_randr_screen_change_notify_event_t *>(event);
81 xcb_screen_t *screen = Xcb::defaultScreen();
82 if (xrrEvent->rotation & (XCB_RANDR_ROTATION_ROTATE_90 | XCB_RANDR_ROTATION_ROTATE_270)) {
83 screen->width_in_pixels = xrrEvent->height;
84 screen->height_in_pixels = xrrEvent->width;
85 screen->width_in_millimeters = xrrEvent->mheight;
86 screen->height_in_millimeters = xrrEvent->mwidth;
87 } else {
88 screen->width_in_pixels = xrrEvent->width;
89 screen->height_in_pixels = xrrEvent->height;
90 screen->width_in_millimeters = xrrEvent->mwidth;
91 screen->height_in_millimeters = xrrEvent->mheight;
92 }
93
94 return false;
95}
96
98 : OutputBackend(parent)
99 , m_updateOutputsTimer(std::make_unique<QTimer>())
100 , m_x11Display(QX11Info::display())
101 , m_renderLoop(std::make_unique<RenderLoop>(nullptr))
102{
103#if HAVE_X11_XINPUT
104 if (!qEnvironmentVariableIsSet("KWIN_NO_XI2")) {
105 m_xinputIntegration = std::make_unique<XInputIntegration>(m_x11Display, this);
106 m_xinputIntegration->init();
107 if (!m_xinputIntegration->hasXinput()) {
108 m_xinputIntegration.reset();
109 } else {
110 connect(kwinApp(), &Application::workspaceCreated, m_xinputIntegration.get(), &XInputIntegration::startListening);
111 }
112 }
113#endif
114
115 m_updateOutputsTimer->setSingleShot(true);
116 connect(m_updateOutputsTimer.get(), &QTimer::timeout, this, &X11StandaloneBackend::updateOutputs);
117
118 m_keyboard = std::make_unique<X11Keyboard>();
119}
120
122{
123 m_eglDisplay.reset();
125}
126
128{
129 return m_x11Display;
130}
131
132xcb_connection_t *X11StandaloneBackend::connection() const
133{
134 return kwinApp()->x11Connection();
135}
136
138{
139 return kwinApp()->x11RootWindow();
140}
141
143{
144 if (!QX11Info::isPlatformX11()) {
145 return false;
146 }
147 XRenderUtils::init(kwinApp()->x11Connection(), kwinApp()->x11RootWindow());
148 initOutputs();
149
150 if (Xcb::Extensions::self()->isRandrAvailable()) {
151 m_randrEventFilter = std::make_unique<XrandrEventFilter>(this);
152 }
153 connect(Cursors::self(), &Cursors::hiddenChanged, this, &X11StandaloneBackend::updateCursor);
154 return true;
155}
156
157std::unique_ptr<OpenGLBackend> X11StandaloneBackend::createOpenGLBackend()
158{
159 switch (options->glPlatformInterface()) {
160#if HAVE_GLX
162 if (hasGlx()) {
163 return std::make_unique<GlxBackend>(m_x11Display, this);
164 } else {
165 qCWarning(KWIN_X11STANDALONE) << "Glx not available, trying EGL instead.";
166 // no break, needs fall-through
167 Q_FALLTHROUGH();
168 }
169#endif
171 return std::make_unique<EglBackend>(m_x11Display, this);
172 default:
173 // no backend available
174 return nullptr;
175 }
176}
177
179{
180 if (!m_screenEdgesFilter) {
181 m_screenEdgesFilter = std::make_unique<ScreenEdgesFilter>();
182 }
183 return std::make_unique<WindowBasedEdge>(edges);
184}
185
187{
188#if HAVE_X11_XINPUT
189 auto c = std::make_unique<X11Cursor>(m_xinputIntegration != nullptr);
190 if (m_xinputIntegration) {
191 m_xinputIntegration->setCursor(c.get());
192 // we know we have xkb already
193 auto xkb = input()->keyboard()->xkb();
194 xkb->setConfig(kwinApp()->kxkbConfig());
195 xkb->reconfigure();
196 }
197 return c;
198#else
199 return std::make_unique<X11Cursor>(false);
200#endif
201}
202
203bool X11StandaloneBackend::hasGlx()
204{
205 return Xcb::Extensions::self()->hasGlx();
206}
207
209{
210 auto c = kwinApp()->x11Connection();
212 xcb_xfixes_get_cursor_image_reply(c,
213 xcb_xfixes_get_cursor_image_unchecked(c),
214 nullptr));
215 if (!cursor) {
216 return PlatformCursorImage();
217 }
218
219 QImage qcursorimg((uchar *)xcb_xfixes_get_cursor_image_cursor_image(cursor.get()), cursor->width, cursor->height,
220 QImage::Format_ARGB32_Premultiplied);
221 // deep copy of image as the data is going to be freed
222 return PlatformCursorImage(qcursorimg.copy(), QPoint(cursor->xhot, cursor->yhot));
223}
224
225void X11StandaloneBackend::updateCursor()
226{
227 if (Cursors::self()->isCursorHidden()) {
228 xcb_xfixes_hide_cursor(kwinApp()->x11Connection(), kwinApp()->x11RootWindow());
229 } else {
230 xcb_xfixes_show_cursor(kwinApp()->x11Connection(), kwinApp()->x11RootWindow());
231 }
232}
233
234void X11StandaloneBackend::startInteractiveWindowSelection(std::function<void(KWin::Window *)> callback, const QByteArray &cursorName)
235{
236 if (!m_windowSelector) {
237 m_windowSelector = std::make_unique<WindowSelector>();
238 }
239 m_windowSelector->start(callback, cursorName);
240}
241
242void X11StandaloneBackend::startInteractivePositionSelection(std::function<void(const QPointF &)> callback)
243{
244 if (!m_windowSelector) {
245 m_windowSelector = std::make_unique<WindowSelector>();
246 }
247 m_windowSelector->start(callback);
248}
249
250std::unique_ptr<OutlineVisual> X11StandaloneBackend::createOutline(Outline *outline)
251{
252 return std::make_unique<NonCompositedOutlineVisual>(outline);
253}
254
256{
257 new EffectsHandlerX11(compositor, scene);
258}
259
260QList<CompositingType> X11StandaloneBackend::supportedCompositors() const
261{
262 QList<CompositingType> compositors;
263#if HAVE_GLX
264 compositors << OpenGLCompositing;
265#endif
266 compositors << NoCompositing;
267 return compositors;
268}
269
271{
272 doUpdateOutputs<Xcb::RandR::ScreenResources>();
273 updateRefreshRate();
274}
275
277{
278 m_updateOutputsTimer->start();
279}
280
282{
283 doUpdateOutputs<Xcb::RandR::CurrentResources>();
284 updateRefreshRate();
285}
286
287template<typename T>
288void X11StandaloneBackend::doUpdateOutputs()
289{
290 QList<Output *> changed;
291 QList<Output *> added;
292 QList<Output *> removed = m_outputs;
293
294 if (Xcb::Extensions::self()->isRandrAvailable()) {
295 T resources(rootWindow());
296 if (!resources.isNull()) {
297
298 std::span crtcs(resources.crtcs(), resources->num_crtcs);
299 for (auto crtc : crtcs) {
300 Xcb::RandR::CrtcInfo info(crtc, resources->config_timestamp);
301
302 const QRect geometry = info.rect();
303 if (!geometry.isValid()) {
304 continue;
305 }
306
307 float refreshRate = -1.0f;
308
309 for (auto mode : std::span(resources.modes(), resources->num_modes)) {
310 if (info->mode == mode.id) {
311 if (mode.htotal != 0 && mode.vtotal != 0) { // BUG 313996
312 // refresh rate calculation - WTF was wikipedia 1998 when I needed it?
313 int dotclock = mode.dot_clock,
314 vtotal = mode.vtotal;
315 if (mode.mode_flags & XCB_RANDR_MODE_FLAG_INTERLACE) {
316 dotclock *= 2;
317 }
318 if (mode.mode_flags & XCB_RANDR_MODE_FLAG_DOUBLE_SCAN) {
319 vtotal *= 2;
320 }
321 refreshRate = dotclock / float(mode.htotal * vtotal);
322 }
323 break; // found mode
324 }
325 }
326
327 for (auto xcbOutput : std::span(info.outputs(), info->num_outputs)) {
328 Xcb::RandR::OutputInfo outputInfo(xcbOutput, resources->config_timestamp);
329 if (outputInfo->crtc != crtc) {
330 continue;
331 }
332
333 X11Output *output = findX11Output(outputInfo.name());
334 if (output) {
335 changed.append(output);
336 removed.removeOne(output);
337 } else {
338 output = new X11Output(this);
339 added.append(output);
340 }
341
342 // TODO: Perhaps the output has to save the inherited gamma ramp and
343 // restore it during tear down. Currently neither standalone x11 nor
344 // drm platform do this.
345 Xcb::RandR::CrtcGamma gamma(crtc);
346
347 output->setRenderLoop(m_renderLoop.get());
348 output->setCrtc(crtc);
349 output->setGammaRampSize(gamma.isNull() ? 0 : gamma->size);
350 auto it = std::find(crtcs.begin(), crtcs.end(), crtc);
351 int crtcIndex = std::distance(crtcs.begin(), it);
352 output->setXineramaNumber(crtcIndex);
353
354 QSize physicalSize(outputInfo->mm_width, outputInfo->mm_height);
355 switch (info->rotation) {
356 case XCB_RANDR_ROTATION_ROTATE_0:
357 case XCB_RANDR_ROTATION_ROTATE_180:
358 break;
359 case XCB_RANDR_ROTATION_ROTATE_90:
360 case XCB_RANDR_ROTATION_ROTATE_270:
361 physicalSize.transpose();
362 break;
363 case XCB_RANDR_ROTATION_REFLECT_X:
364 case XCB_RANDR_ROTATION_REFLECT_Y:
365 break;
366 }
367
368 X11Output::Information information{
369 .name = outputInfo.name(),
370 .physicalSize = physicalSize,
371 };
372
373 auto edidProperty = Xcb::RandR::OutputProperty(xcbOutput, atoms->edid, XCB_ATOM_INTEGER, 0, 100, false, false);
374 bool ok;
375 if (auto data = edidProperty.toByteArray(&ok); ok && !data.isEmpty()) {
376 if (auto edid = Edid(data, edidProperty.data()->num_items); edid.isValid()) {
377 information.manufacturer = edid.manufacturerString();
378 information.model = edid.monitorName();
379 information.serialNumber = edid.serialNumber();
380 information.edid = edid;
381 }
382 }
383
384 auto mode = std::make_shared<OutputMode>(geometry.size(), refreshRate * 1000);
385
386 X11Output::State state = output->m_state;
387 state.modes = {mode};
388 state.currentMode = mode;
389 state.position = geometry.topLeft();
390
391 output->setInformation(information);
392 output->setState(state);
393 break;
394 }
395 }
396 }
397 }
398
399 // The workspace handles having no outputs poorly. If the last output is about to be
400 // removed, create a dummy output to avoid crashing.
401 if (changed.isEmpty() && added.isEmpty()) {
402 auto dummyOutput = new X11PlaceholderOutput(this);
403 m_outputs << dummyOutput;
404 Q_EMIT outputAdded(dummyOutput);
405 dummyOutput->updateEnabled(true);
406 }
407
408 // Process new outputs. Note new outputs must be introduced before removing any other outputs.
409 for (Output *output : std::as_const(added)) {
410 m_outputs.append(output);
411 Q_EMIT outputAdded(output);
412 if (auto placeholderOutput = qobject_cast<X11PlaceholderOutput *>(output)) {
413 placeholderOutput->updateEnabled(true);
414 } else if (auto nativeOutput = qobject_cast<X11Output *>(output)) {
415 nativeOutput->updateEnabled(true);
416 }
417 }
418
419 // Outputs have to be removed last to avoid the case where there are no enabled outputs.
420 for (Output *output : std::as_const(removed)) {
421 m_outputs.removeOne(output);
422 if (auto placeholderOutput = qobject_cast<X11PlaceholderOutput *>(output)) {
423 placeholderOutput->updateEnabled(false);
424 } else if (auto nativeOutput = qobject_cast<X11Output *>(output)) {
425 nativeOutput->updateEnabled(false);
426 }
427 Q_EMIT outputRemoved(output);
428 output->unref();
429 }
430
431 // Make sure that the position of an output in m_outputs matches its xinerama index, there
432 // are X11 protocols that use xinerama indices to identify outputs.
433 std::sort(m_outputs.begin(), m_outputs.end(), [](const Output *a, const Output *b) {
434 const auto xa = qobject_cast<const X11Output *>(a);
435 if (!xa) {
436 return false;
437 }
438 const auto xb = qobject_cast<const X11Output *>(b);
439 if (!xb) {
440 return true;
441 }
442 return xa->xineramaNumber() < xb->xineramaNumber();
443 });
444
445 Q_EMIT outputsQueried();
446}
447
448X11Output *X11StandaloneBackend::findX11Output(const QString &name) const
449{
450 for (Output *output : m_outputs) {
451 if (output->name() == name) {
452 return qobject_cast<X11Output *>(output);
453 }
454 }
455 return nullptr;
456}
457
458Outputs X11StandaloneBackend::outputs() const
459{
460 return m_outputs;
461}
462
463X11Keyboard *X11StandaloneBackend::keyboard() const
464{
465 return m_keyboard.get();
466}
467
468RenderLoop *X11StandaloneBackend::renderLoop() const
469{
470 return m_renderLoop.get();
471}
472
473static bool refreshRate_compare(const Output *first, const Output *smallest)
474{
475 return first->refreshRate() < smallest->refreshRate();
476}
477
478static int currentRefreshRate()
479{
480 static const int refreshRate = qEnvironmentVariableIntValue("KWIN_X11_REFRESH_RATE");
481 if (refreshRate) {
482 return refreshRate;
483 }
484
485 const QList<Output *> outputs = kwinApp()->outputBackend()->outputs();
486 if (outputs.isEmpty()) {
487 return 60000;
488 }
489
490 static const QString syncDisplayDevice = qEnvironmentVariable("__GL_SYNC_DISPLAY_DEVICE");
491 if (!syncDisplayDevice.isEmpty()) {
492 for (const Output *output : outputs) {
493 if (output->name() == syncDisplayDevice) {
494 return output->refreshRate();
495 }
496 }
497 }
498
499 auto syncIt = std::min_element(outputs.begin(), outputs.end(), refreshRate_compare);
500 return (*syncIt)->refreshRate();
501}
502
503void X11StandaloneBackend::updateRefreshRate()
504{
505 int refreshRate = currentRefreshRate();
506 if (refreshRate <= 0) {
507 qCWarning(KWIN_X11STANDALONE) << "Bogus refresh rate" << refreshRate;
508 refreshRate = 60000;
509 }
510
511 m_renderLoop->setRefreshRate(refreshRate);
512}
513
514void X11StandaloneBackend::setEglDisplay(std::unique_ptr<EglDisplay> &&display)
515{
516 m_eglDisplay = std::move(display);
517}
518
519EglDisplay *X11StandaloneBackend::sceneEglDisplayObject() const
520{
521 return m_eglDisplay.get();
522}
523}
524
525#include "moc_x11_standalone_backend.cpp"
Xcb::Atom edid
Definition atoms.h:78
static Cursors * self()
Definition cursor.cpp:35
void hiddenChanged()
KeyboardInputRedirection * keyboard() const
Definition input.h:216
KWin::OpenGLPlatformInterface glPlatformInterface
Definition options.h:198
This class is used to show the outline of a given geometry.
Definition outline.h:38
void outputAdded(Output *output)
void outputRemoved(Output *output)
uint32_t refreshRate() const
Definition output.cpp:475
Class for controlling screen edges.
Definition screenedge.h:222
std::unique_ptr< OutlineVisual > createOutline(Outline *outline)
xcb_connection_t * connection() const
std::unique_ptr< Edge > createScreenEdge(ScreenEdges *parent)
std::unique_ptr< OpenGLBackend > createOpenGLBackend() override
X11StandaloneBackend(QObject *parent=nullptr)
void createEffectsHandler(Compositor *compositor, WorkspaceScene *scene)
PlatformCursorImage cursorImage() const
void startInteractiveWindowSelection(std::function< void(KWin::Window *)> callback, const QByteArray &cursorName=QByteArray())
std::unique_ptr< Cursor > createPlatformCursor()
void startInteractivePositionSelection(std::function< void(const QPointF &)> callback)
QList< CompositingType > supportedCompositors() const override
static Extensions * self()
Definition xcbutils.cpp:346
bool hasGlx() const
Definition xcbutils.h:1511
int randrNotifyEvent() const
Definition xcbutils.cpp:529
void setConfig(const KSharedConfigPtr &config)
Definition xkb.cpp:142
XrandrEventFilter(X11StandaloneBackend *backend)
bool event(xcb_generic_event_t *event) override
QList< KWayland::Client::Output * > outputs
void init(xcb_connection_t *connection, xcb_window_t rootWindow)
@ EglPlatformInterface
Definition globals.h:45
@ GlxPlatformInterface
Definition globals.h:44
@ NoCompositing
Definition globals.h:29
@ OpenGLCompositing
Definition globals.h:37
Options * options
Definition main.cpp:73
InputRedirection * input()
Definition input.h:549
KWIN_EXPORT Atoms * atoms
Definition main.cpp:74
std::unique_ptr< T, CDeleter > UniqueCPtr
Definition c_ptr.h:28
struct _XDisplay Display