1 | /******************************************************************** |
2 | KWin - the KDE window manager |
3 | This file is part of the KDE project. |
4 | |
5 | Copyright (C) 2013 Martin Gräßlin <mgraesslin@kde.org> |
6 | |
7 | This program is free software; you can redistribute it and/or modify |
8 | it under the terms of the GNU General Public License as published by |
9 | the Free Software Foundation; either version 2 of the License, or |
10 | (at your option) any later version. |
11 | |
12 | This program is distributed in the hope that it will be useful, |
13 | but WITHOUT ANY WARRANTY; without even the implied warranty of |
14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
15 | GNU General Public License for more details. |
16 | |
17 | You should have received a copy of the GNU General Public License |
18 | along with this program. If not, see <http://www.gnu.org/licenses/>. |
19 | *********************************************************************/ |
20 | #define WL_EGL_PLATFORM 1 |
21 | #include "egl_wayland_backend.h" |
22 | // kwin |
23 | #include "cursor.h" |
24 | #include "options.h" |
25 | // kwin libs |
26 | #include <kwinglplatform.h> |
27 | // KDE |
28 | #include <KDE/KDebug> |
29 | #include <KDE/KTemporaryFile> |
30 | // Qt |
31 | #include <QSocketNotifier> |
32 | // xcb |
33 | #include <xcb/xtest.h> |
34 | // Wayland |
35 | #include <wayland-client-protocol.h> |
36 | // system |
37 | #include <linux/input.h> |
38 | #include <unistd.h> |
39 | #include <sys/mman.h> |
40 | #include <sys/shm.h> |
41 | #include <sys/types.h> |
42 | |
43 | namespace KWin |
44 | { |
45 | |
46 | namespace Wayland |
47 | { |
48 | |
49 | /** |
50 | * Callback for announcing global objects in the registry |
51 | **/ |
52 | static void registryHandleGlobal(void *data, struct wl_registry *registry, |
53 | uint32_t name, const char *interface, uint32_t version) |
54 | { |
55 | Q_UNUSED(version) |
56 | WaylandBackend *d = reinterpret_cast<WaylandBackend*>(data); |
57 | |
58 | if (strcmp(interface, "wl_compositor" ) == 0) { |
59 | d->setCompositor(reinterpret_cast<wl_compositor*>(wl_registry_bind(registry, name, &wl_compositor_interface, 1))); |
60 | } else if (strcmp(interface, "wl_shell" ) == 0) { |
61 | d->setShell(reinterpret_cast<wl_shell *>(wl_registry_bind(registry, name, &wl_shell_interface, 1))); |
62 | } else if (strcmp(interface, "wl_seat" ) == 0) { |
63 | d->createSeat(name); |
64 | } else if (strcmp(interface, "wl_shm" ) == 0) { |
65 | d->createShm(name); |
66 | } |
67 | kDebug(1212) << "Wayland Interface: " << interface; |
68 | } |
69 | |
70 | /** |
71 | * Callback for removal of global objects in the registry |
72 | **/ |
73 | static void registryHandleGlobalRemove(void *data, struct wl_registry *registry, uint32_t name) |
74 | { |
75 | Q_UNUSED(data) |
76 | Q_UNUSED(registry) |
77 | Q_UNUSED(name) |
78 | // TODO: implement me |
79 | } |
80 | |
81 | /** |
82 | * Call back for ping from Wayland Shell. |
83 | **/ |
84 | static void handlePing(void *data, struct wl_shell_surface *shellSurface, uint32_t serial) |
85 | { |
86 | Q_UNUSED(shellSurface); |
87 | reinterpret_cast<WaylandBackend*>(data)->ping(serial); |
88 | } |
89 | |
90 | /** |
91 | * Callback for a configure request for a shell surface |
92 | **/ |
93 | static void handleConfigure(void *data, struct wl_shell_surface *shellSurface, uint32_t edges, int32_t width, int32_t height) |
94 | { |
95 | Q_UNUSED(shellSurface) |
96 | Q_UNUSED(edges) |
97 | WaylandBackend *display = reinterpret_cast<WaylandBackend*>(data); |
98 | wl_egl_window_resize(display->overlay(), width, height, 0, 0); |
99 | // TODO: this information should probably go into Screens |
100 | } |
101 | |
102 | /** |
103 | * Callback for popups - not needed, we don't have popups |
104 | **/ |
105 | static void handlePopupDone(void *data, struct wl_shell_surface *shellSurface) |
106 | { |
107 | Q_UNUSED(data) |
108 | Q_UNUSED(shellSurface) |
109 | } |
110 | |
111 | static void seatHandleCapabilities(void *data, wl_seat *seat, uint32_t capabilities) |
112 | { |
113 | WaylandSeat *s = reinterpret_cast<WaylandSeat*>(data); |
114 | if (seat != s->seat()) { |
115 | return; |
116 | } |
117 | s->changed(capabilities); |
118 | } |
119 | |
120 | static void pointerHandleEnter(void *data, wl_pointer *pointer, uint32_t serial, wl_surface *surface, |
121 | wl_fixed_t sx, wl_fixed_t sy) |
122 | { |
123 | Q_UNUSED(data) |
124 | Q_UNUSED(pointer) |
125 | Q_UNUSED(surface) |
126 | Q_UNUSED(sx) |
127 | Q_UNUSED(sy) |
128 | WaylandSeat *seat = reinterpret_cast<WaylandSeat*>(data); |
129 | seat->pointerEntered(serial); |
130 | } |
131 | |
132 | static void pointerHandleLeave(void *data, wl_pointer *pointer, uint32_t serial, wl_surface *surface) |
133 | { |
134 | Q_UNUSED(data) |
135 | Q_UNUSED(pointer) |
136 | Q_UNUSED(serial) |
137 | Q_UNUSED(surface) |
138 | } |
139 | |
140 | static void pointerHandleMotion(void *data, wl_pointer *pointer, uint32_t time, wl_fixed_t sx, wl_fixed_t sy) |
141 | { |
142 | Q_UNUSED(data) |
143 | Q_UNUSED(pointer) |
144 | Q_UNUSED(time) |
145 | xcb_test_fake_input(connection(), XCB_MOTION_NOTIFY, 0, XCB_TIME_CURRENT_TIME, XCB_WINDOW_NONE, |
146 | wl_fixed_to_int(sx), wl_fixed_to_int(sy), 0); |
147 | } |
148 | |
149 | static void pointerHandleButton(void *data, wl_pointer *pointer, uint32_t serial, uint32_t time, |
150 | uint32_t button, uint32_t state) |
151 | { |
152 | Q_UNUSED(data) |
153 | Q_UNUSED(pointer) |
154 | Q_UNUSED(serial) |
155 | Q_UNUSED(time) |
156 | uint8_t type = XCB_BUTTON_PRESS; |
157 | if (state == WL_POINTER_BUTTON_STATE_RELEASED) { |
158 | type = XCB_BUTTON_RELEASE; |
159 | } |
160 | // TODO: there must be a better way for mapping |
161 | uint8_t xButton = 0; |
162 | switch (button) { |
163 | case BTN_LEFT: |
164 | xButton = XCB_BUTTON_INDEX_1; |
165 | break; |
166 | case BTN_RIGHT: |
167 | xButton = XCB_BUTTON_INDEX_3; |
168 | break; |
169 | case BTN_MIDDLE: |
170 | xButton = XCB_BUTTON_INDEX_2; |
171 | break; |
172 | default: |
173 | // TODO: add more buttons |
174 | return; |
175 | } |
176 | xcb_test_fake_input(connection(), type, xButton, XCB_TIME_CURRENT_TIME, XCB_WINDOW_NONE, 0, 0, 0); |
177 | } |
178 | |
179 | static void pointerHandleAxis(void *data, wl_pointer *pointer, uint32_t time, uint32_t axis, wl_fixed_t value) |
180 | { |
181 | Q_UNUSED(data) |
182 | Q_UNUSED(pointer) |
183 | Q_UNUSED(time) |
184 | uint8_t xButton = 0; |
185 | const int delta = wl_fixed_to_int(value); |
186 | if (delta == 0) { |
187 | return; |
188 | } |
189 | switch (axis) { |
190 | case WL_POINTER_AXIS_VERTICAL_SCROLL: |
191 | xButton = delta > 0 ? XCB_BUTTON_INDEX_5 : XCB_BUTTON_INDEX_4; |
192 | break; |
193 | case WL_POINTER_AXIS_HORIZONTAL_SCROLL: |
194 | // no enum values defined for buttons larger than 5 |
195 | xButton = delta > 0 ? 7 : 6; |
196 | break; |
197 | default: |
198 | // doesn't exist |
199 | return; |
200 | } |
201 | for (int i = 0; i < qAbs(delta); ++i) { |
202 | xcb_test_fake_input(connection(), XCB_BUTTON_PRESS, xButton, XCB_TIME_CURRENT_TIME, XCB_WINDOW_NONE, 0, 0, 0); |
203 | xcb_test_fake_input(connection(), XCB_BUTTON_RELEASE, xButton, XCB_TIME_CURRENT_TIME, XCB_WINDOW_NONE, 0, 0, 0); |
204 | } |
205 | } |
206 | |
207 | static void keyboardHandleKeymap(void *data, wl_keyboard *keyboard, |
208 | uint32_t format, int fd, uint32_t size) |
209 | { |
210 | Q_UNUSED(data) |
211 | Q_UNUSED(keyboard) |
212 | Q_UNUSED(format) |
213 | Q_UNUSED(fd) |
214 | Q_UNUSED(size) |
215 | } |
216 | |
217 | static void keyboardHandleEnter(void *data, wl_keyboard *keyboard, |
218 | uint32_t serial, wl_surface *surface, |
219 | wl_array *keys) |
220 | { |
221 | Q_UNUSED(data) |
222 | Q_UNUSED(keyboard) |
223 | Q_UNUSED(serial) |
224 | Q_UNUSED(surface) |
225 | Q_UNUSED(keys) |
226 | } |
227 | |
228 | static void keyboardHandleLeave(void *data, wl_keyboard *keyboard, uint32_t serial, wl_surface *surface) |
229 | { |
230 | Q_UNUSED(data) |
231 | Q_UNUSED(keyboard) |
232 | Q_UNUSED(serial) |
233 | Q_UNUSED(surface) |
234 | } |
235 | |
236 | static void keyboardHandleKey(void *data, wl_keyboard *keyboard, uint32_t serial, uint32_t time, |
237 | uint32_t key, uint32_t state) |
238 | { |
239 | Q_UNUSED(data) |
240 | Q_UNUSED(keyboard) |
241 | Q_UNUSED(serial) |
242 | Q_UNUSED(time) |
243 | uint8_t type = XCB_KEY_PRESS; |
244 | if (state == WL_KEYBOARD_KEY_STATE_RELEASED) { |
245 | type = XCB_KEY_RELEASE; |
246 | } |
247 | xcb_test_fake_input(connection(), type, key + 8 /*magic*/, XCB_TIME_CURRENT_TIME, XCB_WINDOW_NONE, 0, 0, 0); |
248 | } |
249 | |
250 | static void keyboardHandleModifiers(void *data, wl_keyboard *keyboard, uint32_t serial, uint32_t modsDepressed, |
251 | uint32_t modsLatched, uint32_t modsLocked, uint32_t group) |
252 | { |
253 | Q_UNUSED(data) |
254 | Q_UNUSED(keyboard) |
255 | Q_UNUSED(serial) |
256 | Q_UNUSED(modsDepressed) |
257 | Q_UNUSED(modsLatched) |
258 | Q_UNUSED(modsLocked) |
259 | Q_UNUSED(group) |
260 | } |
261 | |
262 | // handlers |
263 | static const struct wl_registry_listener s_registryListener = { |
264 | registryHandleGlobal, |
265 | registryHandleGlobalRemove |
266 | }; |
267 | |
268 | static const struct wl_shell_surface_listener s_shellSurfaceListener = { |
269 | handlePing, |
270 | handleConfigure, |
271 | handlePopupDone |
272 | }; |
273 | |
274 | static const struct wl_pointer_listener s_pointerListener = { |
275 | pointerHandleEnter, |
276 | pointerHandleLeave, |
277 | pointerHandleMotion, |
278 | pointerHandleButton, |
279 | pointerHandleAxis |
280 | }; |
281 | |
282 | static const struct wl_keyboard_listener s_keyboardListener = { |
283 | keyboardHandleKeymap, |
284 | keyboardHandleEnter, |
285 | keyboardHandleLeave, |
286 | keyboardHandleKey, |
287 | keyboardHandleModifiers, |
288 | }; |
289 | |
290 | static const struct wl_seat_listener s_seatListener = { |
291 | seatHandleCapabilities |
292 | }; |
293 | |
294 | CursorData::CursorData(ShmPool *pool) |
295 | : m_cursor(NULL) |
296 | , m_valid(init(pool)) |
297 | { |
298 | } |
299 | |
300 | CursorData::~CursorData() |
301 | { |
302 | } |
303 | |
304 | bool CursorData::init(ShmPool *pool) |
305 | { |
306 | QScopedPointer<xcb_xfixes_get_cursor_image_reply_t, QScopedPointerPodDeleter> cursor( |
307 | xcb_xfixes_get_cursor_image_reply(connection(), |
308 | xcb_xfixes_get_cursor_image_unchecked(connection()), |
309 | NULL)); |
310 | if (cursor.isNull()) { |
311 | return false; |
312 | } |
313 | |
314 | QImage cursorImage((uchar *) xcb_xfixes_get_cursor_image_cursor_image(cursor.data()), cursor->width, cursor->height, |
315 | QImage::Format_ARGB32_Premultiplied); |
316 | if (cursorImage.isNull()) { |
317 | return false; |
318 | } |
319 | m_size = QSize(cursor->width, cursor->height); |
320 | |
321 | m_cursor = pool->createBuffer(cursorImage); |
322 | if (!m_cursor) { |
323 | kDebug(1212) << "Creating cursor buffer failed" ; |
324 | return false; |
325 | } |
326 | |
327 | m_hotSpot = QPoint(cursor->xhot, cursor->yhot); |
328 | return true; |
329 | } |
330 | |
331 | X11CursorTracker::X11CursorTracker(wl_pointer *pointer, WaylandBackend *backend, QObject* parent) |
332 | : QObject(parent) |
333 | , m_pointer(pointer) |
334 | , m_backend(backend) |
335 | , m_cursor(wl_compositor_create_surface(backend->compositor())) |
336 | , m_enteredSerial(0) |
337 | , m_lastX11Cursor(0) |
338 | { |
339 | Cursor::self()->startCursorTracking(); |
340 | connect(Cursor::self(), SIGNAL(cursorChanged(uint32_t)), SLOT(cursorChanged(uint32_t))); |
341 | } |
342 | |
343 | X11CursorTracker::~X11CursorTracker() |
344 | { |
345 | Cursor::self()->stopCursorTracking(); |
346 | if (m_cursor) { |
347 | wl_surface_destroy(m_cursor); |
348 | } |
349 | } |
350 | |
351 | void X11CursorTracker::cursorChanged(uint32_t serial) |
352 | { |
353 | if (m_lastX11Cursor == serial) { |
354 | // not changed; |
355 | return; |
356 | } |
357 | m_lastX11Cursor = serial; |
358 | QHash<uint32_t, CursorData>::iterator it = m_cursors.find(serial); |
359 | if (it != m_cursors.end()) { |
360 | installCursor(it.value()); |
361 | return; |
362 | } |
363 | ShmPool *pool = m_backend->shmPool(); |
364 | if (!pool) { |
365 | return; |
366 | } |
367 | CursorData cursor(pool); |
368 | if (cursor.isValid()) { |
369 | // TODO: discard unused cursors after some time? |
370 | m_cursors.insert(serial, cursor); |
371 | } |
372 | installCursor(cursor); |
373 | } |
374 | |
375 | void X11CursorTracker::installCursor(const CursorData& cursor) |
376 | { |
377 | wl_pointer_set_cursor(m_pointer, m_enteredSerial, m_cursor, cursor.hotSpot().x(), cursor.hotSpot().y()); |
378 | wl_surface_attach(m_cursor, cursor.cursor(), 0, 0); |
379 | wl_surface_damage(m_cursor, 0, 0, cursor.size().width(), cursor.size().height()); |
380 | wl_surface_commit(m_cursor); |
381 | } |
382 | |
383 | void X11CursorTracker::setEnteredSerial(uint32_t serial) |
384 | { |
385 | m_enteredSerial = serial; |
386 | } |
387 | |
388 | void X11CursorTracker::resetCursor() |
389 | { |
390 | QHash<uint32_t, CursorData>::iterator it = m_cursors.find(m_lastX11Cursor); |
391 | if (it != m_cursors.end()) { |
392 | installCursor(it.value()); |
393 | } |
394 | } |
395 | |
396 | ShmPool::ShmPool(wl_shm *shm) |
397 | : m_shm(shm) |
398 | , m_pool(NULL) |
399 | , m_poolData(NULL) |
400 | , m_size(1024 * 1024) // TODO: useful size? |
401 | , m_tmpFile(new KTemporaryFile()) |
402 | , m_valid(createPool()) |
403 | , m_offset(0) |
404 | { |
405 | } |
406 | |
407 | ShmPool::~ShmPool() |
408 | { |
409 | if (m_poolData) { |
410 | munmap(m_poolData, m_size); |
411 | } |
412 | if (m_pool) { |
413 | wl_shm_pool_destroy(m_pool); |
414 | } |
415 | if (m_shm) { |
416 | wl_shm_destroy(m_shm); |
417 | } |
418 | } |
419 | |
420 | bool ShmPool::createPool() |
421 | { |
422 | if (!m_tmpFile->open()) { |
423 | kDebug(1212) << "Could not open temporary file for Shm pool" ; |
424 | return false; |
425 | } |
426 | if (ftruncate(m_tmpFile->handle(), m_size) < 0) { |
427 | kDebug(1212) << "Could not set size for Shm pool file" ; |
428 | return false; |
429 | } |
430 | m_poolData = mmap(NULL, m_size, PROT_READ | PROT_WRITE, MAP_SHARED, m_tmpFile->handle(), 0); |
431 | m_pool = wl_shm_create_pool(m_shm, m_tmpFile->handle(), m_size); |
432 | |
433 | if (!m_poolData || !m_pool) { |
434 | kDebug(1212) << "Creating Shm pool failed" ; |
435 | return false; |
436 | } |
437 | m_tmpFile->close(); |
438 | return true; |
439 | } |
440 | |
441 | wl_buffer *ShmPool::createBuffer(const QImage& image) |
442 | { |
443 | if (image.isNull() || !m_valid) { |
444 | return NULL; |
445 | } |
446 | // TODO: test whether buffer needs resizing |
447 | wl_buffer *buffer = wl_shm_pool_create_buffer(m_pool, m_offset, image.width(), image.height(), |
448 | image.bytesPerLine(), WL_SHM_FORMAT_ARGB8888); |
449 | if (buffer) { |
450 | memcpy((char *)m_poolData + m_offset, image.bits(), image.byteCount()); |
451 | m_offset += image.byteCount(); |
452 | } |
453 | return buffer; |
454 | } |
455 | |
456 | WaylandSeat::WaylandSeat(wl_seat *seat, WaylandBackend *backend) |
457 | : m_seat(seat) |
458 | , m_pointer(NULL) |
459 | , m_keyboard(NULL) |
460 | , m_cursorTracker() |
461 | , m_backend(backend) |
462 | { |
463 | if (m_seat) { |
464 | wl_seat_add_listener(m_seat, &s_seatListener, this); |
465 | } |
466 | } |
467 | |
468 | WaylandSeat::~WaylandSeat() |
469 | { |
470 | destroyPointer(); |
471 | destroyKeyboard(); |
472 | if (m_seat) { |
473 | wl_seat_destroy(m_seat); |
474 | } |
475 | } |
476 | |
477 | void WaylandSeat::destroyPointer() |
478 | { |
479 | if (m_pointer) { |
480 | wl_pointer_destroy(m_pointer); |
481 | m_pointer = NULL; |
482 | m_cursorTracker.reset(); |
483 | } |
484 | } |
485 | |
486 | void WaylandSeat::destroyKeyboard() |
487 | { |
488 | if (m_keyboard) { |
489 | wl_keyboard_destroy(m_keyboard); |
490 | m_keyboard = NULL; |
491 | } |
492 | } |
493 | |
494 | void WaylandSeat::changed(uint32_t capabilities) |
495 | { |
496 | if ((capabilities & WL_SEAT_CAPABILITY_POINTER) && !m_pointer) { |
497 | m_pointer = wl_seat_get_pointer(m_seat); |
498 | wl_pointer_add_listener(m_pointer, &s_pointerListener, this); |
499 | m_cursorTracker.reset(new X11CursorTracker(m_pointer, m_backend)); |
500 | } else if (!(capabilities & WL_SEAT_CAPABILITY_POINTER)) { |
501 | destroyPointer(); |
502 | } |
503 | if ((capabilities & WL_SEAT_CAPABILITY_KEYBOARD)) { |
504 | m_keyboard = wl_seat_get_keyboard(m_seat); |
505 | wl_keyboard_add_listener(m_keyboard, &s_keyboardListener, this); |
506 | } else if (!(capabilities & WL_SEAT_CAPABILITY_KEYBOARD)) { |
507 | destroyKeyboard(); |
508 | } |
509 | } |
510 | |
511 | void WaylandSeat::pointerEntered(uint32_t serial) |
512 | { |
513 | if (m_cursorTracker.isNull()) { |
514 | return; |
515 | } |
516 | m_cursorTracker->setEnteredSerial(serial); |
517 | } |
518 | |
519 | void WaylandSeat::resetCursor() |
520 | { |
521 | if (!m_cursorTracker.isNull()) { |
522 | m_cursorTracker->resetCursor(); |
523 | } |
524 | } |
525 | |
526 | WaylandBackend::WaylandBackend() |
527 | : QObject(NULL) |
528 | , m_display(wl_display_connect(NULL)) |
529 | , m_registry(wl_display_get_registry(m_display)) |
530 | , m_compositor(NULL) |
531 | , m_shell(NULL) |
532 | , m_surface(NULL) |
533 | , m_overlay(NULL) |
534 | , m_shellSurface(NULL) |
535 | , m_seat() |
536 | , m_shm() |
537 | { |
538 | kDebug(1212) << "Created Wayland display" ; |
539 | // setup the registry |
540 | wl_registry_add_listener(m_registry, &s_registryListener, this); |
541 | wl_display_dispatch(m_display); |
542 | int fd = wl_display_get_fd(m_display); |
543 | QSocketNotifier *notifier = new QSocketNotifier(fd, QSocketNotifier::Read, this); |
544 | connect(notifier, SIGNAL(activated(int)), SLOT(readEvents())); |
545 | } |
546 | |
547 | WaylandBackend::~WaylandBackend() |
548 | { |
549 | if (m_overlay) { |
550 | wl_egl_window_destroy(m_overlay); |
551 | } |
552 | if (m_shellSurface) { |
553 | wl_shell_surface_destroy(m_shellSurface); |
554 | } |
555 | if (m_surface) { |
556 | wl_surface_destroy(m_surface); |
557 | } |
558 | if (m_shell) { |
559 | wl_shell_destroy(m_shell); |
560 | } |
561 | if (m_compositor) { |
562 | wl_compositor_destroy(m_compositor); |
563 | } |
564 | if (m_registry) { |
565 | wl_registry_destroy(m_registry); |
566 | } |
567 | if (m_display) { |
568 | wl_display_flush(m_display); |
569 | wl_display_disconnect(m_display); |
570 | } |
571 | kDebug(1212) << "Destroyed Wayland display" ; |
572 | } |
573 | |
574 | void WaylandBackend::readEvents() |
575 | { |
576 | // TODO: this still seems to block |
577 | wl_display_flush(m_display); |
578 | wl_display_dispatch(m_display); |
579 | } |
580 | |
581 | void WaylandBackend::createSeat(uint32_t name) |
582 | { |
583 | wl_seat *seat = reinterpret_cast<wl_seat*>(wl_registry_bind(m_registry, name, &wl_seat_interface, 1)); |
584 | m_seat.reset(new WaylandSeat(seat, this)); |
585 | } |
586 | |
587 | bool WaylandBackend::createSurface() |
588 | { |
589 | m_surface = wl_compositor_create_surface(m_compositor); |
590 | if (!m_surface) { |
591 | kError(1212) << "Creating Wayland Surface failed" ; |
592 | return false; |
593 | } |
594 | // map the surface as fullscreen |
595 | m_shellSurface = wl_shell_get_shell_surface(m_shell, m_surface); |
596 | wl_shell_surface_add_listener(m_shellSurface, &s_shellSurfaceListener, this); |
597 | |
598 | // TODO: do something better than displayWidth/displayHeight |
599 | m_overlay = wl_egl_window_create(m_surface, displayWidth(), displayHeight()); |
600 | if (!m_overlay) { |
601 | kError(1212) << "Creating Wayland Egl window failed" ; |
602 | return false; |
603 | } |
604 | wl_shell_surface_set_fullscreen(m_shellSurface, WL_SHELL_SURFACE_FULLSCREEN_METHOD_DEFAULT, 0, NULL); |
605 | |
606 | return true; |
607 | } |
608 | |
609 | void WaylandBackend::createShm(uint32_t name) |
610 | { |
611 | m_shm.reset(new ShmPool(reinterpret_cast<wl_shm *>(wl_registry_bind(m_registry, name, &wl_shm_interface, 1)))); |
612 | if (!m_shm->isValid()) { |
613 | m_shm.reset(); |
614 | } |
615 | } |
616 | |
617 | void WaylandBackend::ping(uint32_t serial) |
618 | { |
619 | wl_shell_surface_pong(m_shellSurface, serial); |
620 | if (!m_seat.isNull()) { |
621 | m_seat->resetCursor(); |
622 | } |
623 | } |
624 | |
625 | } |
626 | |
627 | EglWaylandBackend::EglWaylandBackend() |
628 | : OpenGLBackend() |
629 | , m_context(EGL_NO_CONTEXT) |
630 | , m_wayland(new Wayland::WaylandBackend) |
631 | { |
632 | kDebug(1212) << "Connected to Wayland display?" << (m_wayland->display() ? "yes" : "no" ); |
633 | if (!m_wayland->display()) { |
634 | setFailed("Could not connect to Wayland compositor" ); |
635 | return; |
636 | } |
637 | initializeEgl(); |
638 | init(); |
639 | // Egl is always direct rendering |
640 | setIsDirectRendering(true); |
641 | |
642 | kWarning(1212) << "Using Wayland rendering backend" ; |
643 | kWarning(1212) << "This is a highly experimental backend, do not use for productive usage!" ; |
644 | kWarning(1212) << "Please do not report any issues you might encounter when using this backend!" ; |
645 | } |
646 | |
647 | EglWaylandBackend::~EglWaylandBackend() |
648 | { |
649 | cleanupGL(); |
650 | checkGLError("Cleanup" ); |
651 | eglMakeCurrent(m_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); |
652 | eglDestroyContext(m_display, m_context); |
653 | eglDestroySurface(m_display, m_surface); |
654 | eglTerminate(m_display); |
655 | eglReleaseThread(); |
656 | } |
657 | |
658 | bool EglWaylandBackend::initializeEgl() |
659 | { |
660 | m_display = eglGetDisplay(m_wayland->display()); |
661 | if (m_display == EGL_NO_DISPLAY) |
662 | return false; |
663 | |
664 | EGLint major, minor; |
665 | if (eglInitialize(m_display, &major, &minor) == EGL_FALSE) |
666 | return false; |
667 | EGLint error = eglGetError(); |
668 | if (error != EGL_SUCCESS) { |
669 | kWarning(1212) << "Error during eglInitialize " << error; |
670 | return false; |
671 | } |
672 | kDebug(1212) << "Egl Initialize succeeded" ; |
673 | |
674 | #ifdef KWIN_HAVE_OPENGLES |
675 | eglBindAPI(EGL_OPENGL_ES_API); |
676 | #else |
677 | if (eglBindAPI(EGL_OPENGL_API) == EGL_FALSE) { |
678 | kError(1212) << "bind OpenGL API failed" ; |
679 | return false; |
680 | } |
681 | #endif |
682 | kDebug(1212) << "EGL version: " << major << "." << minor; |
683 | return true; |
684 | } |
685 | |
686 | void EglWaylandBackend::init() |
687 | { |
688 | if (!initRenderingContext()) { |
689 | setFailed("Could not initialize rendering context" ); |
690 | return; |
691 | } |
692 | |
693 | initEGL(); |
694 | GLPlatform *glPlatform = GLPlatform::instance(); |
695 | glPlatform->detect(EglPlatformInterface); |
696 | glPlatform->printResults(); |
697 | initGL(EglPlatformInterface); |
698 | } |
699 | |
700 | bool EglWaylandBackend::initRenderingContext() |
701 | { |
702 | initBufferConfigs(); |
703 | |
704 | #ifdef KWIN_HAVE_OPENGLES |
705 | const EGLint context_attribs[] = { |
706 | EGL_CONTEXT_CLIENT_VERSION, 2, |
707 | EGL_NONE |
708 | }; |
709 | |
710 | m_context = eglCreateContext(m_display, m_config, EGL_NO_CONTEXT, context_attribs); |
711 | #else |
712 | const EGLint context_attribs_31_core[] = { |
713 | EGL_CONTEXT_MAJOR_VERSION_KHR, 3, |
714 | EGL_CONTEXT_MINOR_VERSION_KHR, 1, |
715 | EGL_CONTEXT_FLAGS_KHR, EGL_CONTEXT_OPENGL_FORWARD_COMPATIBLE_BIT_KHR, |
716 | EGL_NONE |
717 | }; |
718 | |
719 | const EGLint context_attribs_legacy[] = { |
720 | EGL_NONE |
721 | }; |
722 | |
723 | const QByteArray eglExtensions = eglQueryString(m_display, EGL_EXTENSIONS); |
724 | const QList<QByteArray> extensions = eglExtensions.split(' '); |
725 | |
726 | // Try to create a 3.1 core context |
727 | if (options->glCoreProfile() && extensions.contains("EGL_KHR_create_context" )) |
728 | m_context = eglCreateContext(m_display, m_config, EGL_NO_CONTEXT, context_attribs_31_core); |
729 | |
730 | if (m_context == EGL_NO_CONTEXT) |
731 | m_context = eglCreateContext(m_display, m_config, EGL_NO_CONTEXT, context_attribs_legacy); |
732 | #endif |
733 | |
734 | if (m_context == EGL_NO_CONTEXT) { |
735 | kError(1212) << "Create Context failed" ; |
736 | return false; |
737 | } |
738 | |
739 | if (!m_wayland->createSurface()) { |
740 | return false; |
741 | } |
742 | |
743 | m_surface = eglCreateWindowSurface(m_display, m_config, m_wayland->overlay(), NULL); |
744 | if (m_surface == EGL_NO_SURFACE) { |
745 | kError(1212) << "Create Window Surface failed" ; |
746 | return false; |
747 | } |
748 | |
749 | return makeContextCurrent(); |
750 | } |
751 | |
752 | bool EglWaylandBackend::makeContextCurrent() |
753 | { |
754 | if (eglMakeCurrent(m_display, m_surface, m_surface, m_context) == EGL_FALSE) { |
755 | kError(1212) << "Make Context Current failed" ; |
756 | return false; |
757 | } |
758 | |
759 | EGLint error = eglGetError(); |
760 | if (error != EGL_SUCCESS) { |
761 | kWarning(1212) << "Error occurred while creating context " << error; |
762 | return false; |
763 | } |
764 | return true; |
765 | } |
766 | |
767 | bool EglWaylandBackend::initBufferConfigs() |
768 | { |
769 | const EGLint config_attribs[] = { |
770 | EGL_SURFACE_TYPE, EGL_WINDOW_BIT, |
771 | EGL_RED_SIZE, 1, |
772 | EGL_GREEN_SIZE, 1, |
773 | EGL_BLUE_SIZE, 1, |
774 | EGL_ALPHA_SIZE, 0, |
775 | #ifdef KWIN_HAVE_OPENGLES |
776 | EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, |
777 | #else |
778 | EGL_RENDERABLE_TYPE, EGL_OPENGL_BIT, |
779 | #endif |
780 | EGL_CONFIG_CAVEAT, EGL_NONE, |
781 | EGL_NONE, |
782 | }; |
783 | |
784 | EGLint count; |
785 | EGLConfig configs[1024]; |
786 | if (eglChooseConfig(m_display, config_attribs, configs, 1, &count) == EGL_FALSE) { |
787 | kError(1212) << "choose config failed" ; |
788 | return false; |
789 | } |
790 | if (count != 1) { |
791 | kError(1212) << "choose config did not return a config" << count; |
792 | return false; |
793 | } |
794 | m_config = configs[0]; |
795 | |
796 | return true; |
797 | } |
798 | |
799 | void EglWaylandBackend::present() |
800 | { |
801 | setLastDamage(QRegion()); |
802 | // need to dispatch pending events as eglSwapBuffers can block |
803 | wl_display_dispatch_pending(m_wayland->display()); |
804 | wl_display_flush(m_wayland->display()); |
805 | eglSwapBuffers(m_display, m_surface); |
806 | } |
807 | |
808 | void EglWaylandBackend::screenGeometryChanged(const QSize &size) |
809 | { |
810 | Q_UNUSED(size) |
811 | // no backend specific code needed |
812 | // TODO: base implementation in OpenGLBackend |
813 | } |
814 | |
815 | SceneOpenGL::TexturePrivate *EglWaylandBackend::createBackendTexture(SceneOpenGL::Texture *texture) |
816 | { |
817 | return new EglWaylandTexture(texture, this); |
818 | } |
819 | |
820 | QRegion EglWaylandBackend::prepareRenderingFrame() |
821 | { |
822 | if (!lastDamage().isEmpty()) |
823 | present(); |
824 | |
825 | eglWaitNative(EGL_CORE_NATIVE_ENGINE); |
826 | startRenderTimer(); |
827 | |
828 | return QRegion(); |
829 | } |
830 | |
831 | void EglWaylandBackend::endRenderingFrame(const QRegion &renderedRegion, const QRegion &damagedRegion) |
832 | { |
833 | setLastDamage(renderedRegion); |
834 | glFlush(); |
835 | } |
836 | |
837 | Shm *EglWaylandBackend::shm() |
838 | { |
839 | if (m_shm.isNull()) { |
840 | m_shm.reset(new Shm); |
841 | } |
842 | return m_shm.data(); |
843 | } |
844 | |
845 | /************************************************ |
846 | * EglTexture |
847 | ************************************************/ |
848 | |
849 | EglWaylandTexture::EglWaylandTexture(KWin::SceneOpenGL::Texture *texture, KWin::EglWaylandBackend *backend) |
850 | : SceneOpenGL::TexturePrivate() |
851 | , q(texture) |
852 | , m_backend(backend) |
853 | , m_referencedPixmap(XCB_PIXMAP_NONE) |
854 | { |
855 | m_target = GL_TEXTURE_2D; |
856 | } |
857 | |
858 | EglWaylandTexture::~EglWaylandTexture() |
859 | { |
860 | } |
861 | |
862 | OpenGLBackend *EglWaylandTexture::backend() |
863 | { |
864 | return m_backend; |
865 | } |
866 | |
867 | void EglWaylandTexture::findTarget() |
868 | { |
869 | if (m_target != GL_TEXTURE_2D) { |
870 | m_target = GL_TEXTURE_2D; |
871 | } |
872 | } |
873 | |
874 | bool EglWaylandTexture::loadTexture(const Pixmap &pix, const QSize &size, int depth) |
875 | { |
876 | // HACK: egl wayland platform doesn't support texture from X11 pixmap through the KHR_image_pixmap |
877 | // extension. To circumvent this problem we copy the pixmap content into a SHM image and from there |
878 | // to the OpenGL texture. This is a temporary solution. In future we won't need to get the content |
879 | // from X11 pixmaps. That's what we have XWayland for to get the content into a nice Wayland buffer. |
880 | Q_UNUSED(depth) |
881 | if (pix == XCB_PIXMAP_NONE) |
882 | return false; |
883 | m_referencedPixmap = pix; |
884 | |
885 | Shm *shm = m_backend->shm(); |
886 | if (!shm->isValid()) { |
887 | return false; |
888 | } |
889 | |
890 | xcb_shm_get_image_cookie_t cookie = xcb_shm_get_image_unchecked(connection(), pix, 0, 0, size.width(), |
891 | size.height(), ~0, XCB_IMAGE_FORMAT_Z_PIXMAP, shm->segment(), 0); |
892 | |
893 | glGenTextures(1, &m_texture); |
894 | q->setWrapMode(GL_CLAMP_TO_EDGE); |
895 | q->setFilter(GL_LINEAR); |
896 | q->bind(); |
897 | |
898 | ScopedCPointer<xcb_shm_get_image_reply_t> image(xcb_shm_get_image_reply(connection(), cookie, NULL)); |
899 | if (image.isNull()) { |
900 | return false; |
901 | } |
902 | |
903 | // TODO: other formats |
904 | #ifndef KWIN_HAVE_OPENGLES |
905 | glTexImage2D(m_target, 0, GL_RGBA8, size.width(), size.height(), 0, |
906 | GL_BGRA, GL_UNSIGNED_BYTE, shm->buffer()); |
907 | #endif |
908 | |
909 | q->unbind(); |
910 | checkGLError("load texture" ); |
911 | q->setYInverted(true); |
912 | m_size = size; |
913 | updateMatrix(); |
914 | return true; |
915 | } |
916 | |
917 | bool EglWaylandTexture::update(const QRegion &damage) |
918 | { |
919 | if (m_referencedPixmap == XCB_PIXMAP_NONE) { |
920 | return false; |
921 | } |
922 | |
923 | Shm *shm = m_backend->shm(); |
924 | if (!shm->isValid()) { |
925 | return false; |
926 | } |
927 | |
928 | // TODO: optimize by only updating the damaged areas |
929 | const QRect &damagedRect = damage.boundingRect(); |
930 | xcb_shm_get_image_cookie_t cookie = xcb_shm_get_image_unchecked(connection(), m_referencedPixmap, |
931 | damagedRect.x(), damagedRect.y(), damagedRect.width(), damagedRect.height(), |
932 | ~0, XCB_IMAGE_FORMAT_Z_PIXMAP, shm->segment(), 0); |
933 | |
934 | q->bind(); |
935 | |
936 | ScopedCPointer<xcb_shm_get_image_reply_t> image(xcb_shm_get_image_reply(connection(), cookie, NULL)); |
937 | if (image.isNull()) { |
938 | return false; |
939 | } |
940 | |
941 | // TODO: other formats |
942 | #ifndef KWIN_HAVE_OPENGLES |
943 | glTexSubImage2D(m_target, 0, damagedRect.x(), damagedRect.y(), damagedRect.width(), damagedRect.height(), GL_BGRA, GL_UNSIGNED_BYTE, shm->buffer()); |
944 | #endif |
945 | |
946 | q->unbind(); |
947 | checkGLError("update texture" ); |
948 | return true; |
949 | } |
950 | |
951 | Shm::Shm() |
952 | : m_shmId(-1) |
953 | , m_buffer(NULL) |
954 | , m_segment(XCB_NONE) |
955 | , m_valid(false) |
956 | { |
957 | m_valid = init(); |
958 | } |
959 | |
960 | Shm::~Shm() |
961 | { |
962 | if (m_valid) { |
963 | xcb_shm_detach(connection(), m_segment); |
964 | shmdt(m_buffer); |
965 | } |
966 | } |
967 | |
968 | bool Shm::init() |
969 | { |
970 | const xcb_query_extension_reply_t *ext = xcb_get_extension_data(connection(), &xcb_shm_id); |
971 | if (!ext || !ext->present) { |
972 | kDebug(1212) << "SHM extension not available" ; |
973 | return false; |
974 | } |
975 | ScopedCPointer<xcb_shm_query_version_reply_t> version(xcb_shm_query_version_reply(connection(), |
976 | xcb_shm_query_version_unchecked(connection()), NULL)); |
977 | if (version.isNull()) { |
978 | kDebug(1212) << "Failed to get SHM extension version information" ; |
979 | return false; |
980 | } |
981 | const int MAXSIZE = 4096 * 2048 * 4; // TODO check there are not larger windows |
982 | m_shmId = shmget(IPC_PRIVATE, MAXSIZE, IPC_CREAT | 0600); |
983 | if (m_shmId < 0) { |
984 | kDebug(1212) << "Failed to allocate SHM segment" ; |
985 | return false; |
986 | } |
987 | m_buffer = shmat(m_shmId, NULL, 0 /*read/write*/); |
988 | if (-1 == reinterpret_cast<long>(m_buffer)) { |
989 | kDebug(1212) << "Failed to attach SHM segment" ; |
990 | shmctl(m_shmId, IPC_RMID, NULL); |
991 | return false; |
992 | } |
993 | shmctl(m_shmId, IPC_RMID, NULL); |
994 | |
995 | m_segment = xcb_generate_id(connection()); |
996 | const xcb_void_cookie_t cookie = xcb_shm_attach_checked(connection(), m_segment, m_shmId, false); |
997 | ScopedCPointer<xcb_generic_error_t> error(xcb_request_check(connection(), cookie)); |
998 | if (!error.isNull()) { |
999 | kDebug(1212) << "xcb_shm_attach error: " << error->error_code; |
1000 | shmdt(m_buffer); |
1001 | return false; |
1002 | } |
1003 | |
1004 | return true; |
1005 | } |
1006 | |
1007 | } // namespace |
1008 | |