1 //===========================================
2 // Lumina-DE source code
3 // Copyright (c) 2017, Ken Moore
4 // Available under the 3-clause BSD license
5 // See the LICENSE file for full details
6 //===========================================
7 #include "NativeEmbedWidget.h"
8
9 #include <QPainter>
10 #include <QX11Info>
11 #include <QApplication>
12 #include <QScreen>
13 #include <QDebug>
14
15 #include <xcb/xproto.h>
16 #include <xcb/xcb_aux.h>
17 #include <xcb/xcb_event.h>
18 #include <xcb/xcb_image.h>
19 //#include <xcb/render.h>
20 //#include <xcb/xcb_renderutil.h>
21 #include <xcb/composite.h>
22 #include <X11/extensions/Xdamage.h>
23
24 #define DISABLE_COMPOSITING true
25
26 /*inline xcb_render_pictformat_t get_pictformat(){
27 static xcb_render_pictformat_t format = 0;
28 if(format==0){
29 xcb_render_query_pict_formats_reply_t *reply = xcb_render_query_pict_formats_reply( QX11Info::connection(), xcb_render_query_pict_formats(QX11Info::connection()), NULL);
30 format = xcb_render_util_find_standard_format(reply, XCB_PICT_STANDARD_ARGB_32)->id;
31 free(reply);
32 }
33 return format;
34 }
35
36
37 inline void renderWindowToWidget(WId id, QWidget *widget, bool hastransparency = true){
38 //window and widget are assumed to be the same size
39 //Pull the XCB pixmap out of the compositing layer
40 xcb_pixmap_t pix = xcb_generate_id(QX11Info::connection());
41 xcb_composite_name_window_pixmap(QX11Info::connection(), WIN->id(), pix);
42 if(pix==0){ qDebug() << "Got blank pixmap!"; return; }
43
44 xcb_render_picture_t pic_id = xcb_generate_id(QX11Info::connection());
45 xcb_render_create_picture_aux(QX11Info::connection(), pic_id, pix, get_pictformat() , 0, NULL);
46 //
47 xcb_render_composite(QX11Info::connection(), hastransparency ? XCB_RENDER_PICT_OP_OVER : XCB_RENDER_PICT_OP_SRC, pic_id, XCB_RENDER_PICTURE_NONE, widget->x11RenderHandle(),
48 0, 0, 0, 0, 0, 0, (uint16_t) widget->width(), (uint16_t) widget->height() );
49 }*/
50
51 #define CLIENT_EVENT_MASK (XCB_EVENT_MASK_PROPERTY_CHANGE | \
52 XCB_EVENT_MASK_STRUCTURE_NOTIFY | \
53 XCB_EVENT_MASK_FOCUS_CHANGE | \
54 XCB_EVENT_MASK_POINTER_MOTION)
55
56 #define FRAME_EVENT_MASK (XCB_EVENT_MASK_BUTTON_PRESS | \
57 XCB_EVENT_MASK_BUTTON_RELEASE | \
58 XCB_EVENT_MASK_POINTER_MOTION | \
59 XCB_EVENT_MASK_EXPOSURE | \
60 XCB_EVENT_MASK_STRUCTURE_NOTIFY | \
61 XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT | \
62 XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY | \
63 XCB_EVENT_MASK_ENTER_WINDOW)
64
registerClientEvents(WId id,bool client=true)65 inline void registerClientEvents(WId id, bool client = true){
66 uint32_t values[] = {XCB_NONE};
67 values[0] = client ? CLIENT_EVENT_MASK : FRAME_EVENT_MASK ;
68 /*{ (XCB_EVENT_MASK_PROPERTY_CHANGE
69 | XCB_EVENT_MASK_BUTTON_PRESS
70 | XCB_EVENT_MASK_BUTTON_RELEASE
71 | XCB_EVENT_MASK_POINTER_MOTION
72 | XCB_EVENT_MASK_BUTTON_MOTION
73 | XCB_EVENT_MASK_EXPOSURE
74 | XCB_EVENT_MASK_STRUCTURE_NOTIFY
75 | XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT
76 | XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY
77 | XCB_EVENT_MASK_ENTER_WINDOW)
78 };*/
79 xcb_change_window_attributes(QX11Info::connection(), id, XCB_CW_EVENT_MASK, values);
80 }
81
82 // ============
83 // PRIVATE
84 // ============
85 //Simplification functions for the XCB/XLib interactions
syncWinSize(QSize sz)86 void NativeEmbedWidget::syncWinSize(QSize sz){
87 if(WIN==0){ return; }
88 else if(!sz.isValid()){ sz = this->size(); } //use the current widget size
89 //qDebug() << "Sync Window Size:" << sz;
90 //if(sz == winSize){ return; } //no change
91 QPoint pt(0,0);
92 if(!DISABLE_COMPOSITING){ pt = this->mapToGlobal(QPoint(0,0)); }
93 const uint32_t valList[4] = {(uint32_t) pt.x(), (uint32_t) pt.y(), (uint32_t) sz.width(), (uint32_t) sz.height()};
94 const uint32_t mask = XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y | XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT;
95 xcb_configure_window(QX11Info::connection(), WIN->id(), mask, valList);
96 winSize = sz; //save this for checking later
97 }
98
syncWidgetSize(QSize sz)99 void NativeEmbedWidget::syncWidgetSize(QSize sz){
100 //qDebug() << "Sync Widget Size:" << sz;
101 this->resize(sz);
102 }
103
hideWindow()104 void NativeEmbedWidget::hideWindow(){
105 //qDebug() << "Hide Embed Window";
106 xcb_unmap_window(QX11Info::connection(), WIN->id());
107 }
108
showWindow()109 void NativeEmbedWidget::showWindow(){
110 //qDebug() << "Show Embed Window";
111 xcb_map_window(QX11Info::connection(), WIN->id());
112 reregisterEvents();
113 if(!DISABLE_COMPOSITING){
114 QTimer::singleShot(0,this, SLOT(repaintWindow()));
115 }
116 }
117
windowImage(QRect geom)118 QImage NativeEmbedWidget::windowImage(QRect geom){
119 //if(DISABLE_COMPOSITING){
120 if(!this->isVisible()){ return QImage(); } //nothing to grab yet
121 QList<QScreen*> screens = static_cast<QApplication*>( QApplication::instance() )->screens();
122 //for(int i=0; i<screens.length(); i++){
123 //if(screens[i]->contains(this)){
124 if(!screens.isEmpty()){
125 return screens[0]->grabWindow(WIN->id(), geom.x(), geom.y(), geom.width(), geom.height()).toImage();
126 }
127 //}
128 //}
129 return QImage();
130 /*}else{
131 //Pull the XCB pixmap out of the compositing layer
132 xcb_pixmap_t pix = xcb_generate_id(QX11Info::connection());
133 xcb_composite_name_window_pixmap(QX11Info::connection(), WIN->id(), pix);
134 if(pix==0){ qDebug() << "Got blank pixmap!"; return QImage(); }
135
136 //Convert this pixmap into a QImage
137 //xcb_image_t *ximg = xcb_image_get(QX11Info::connection(), pix, 0, 0, this->width(), this->height(), ~0, XCB_IMAGE_FORMAT_Z_PIXMAP);
138 xcb_image_t *ximg = xcb_image_get(QX11Info::connection(), pix, geom.x(), geom.y(), geom.width(), geom.height(), ~0, XCB_IMAGE_FORMAT_Z_PIXMAP);
139 if(ximg == 0){ qDebug() << "Got blank image!"; return QImage(); }
140 QImage img(ximg->data, ximg->width, ximg->height, ximg->stride, QImage::Format_ARGB32_Premultiplied);
141 img = img.copy(); //detach this image from the XCB data structures before we clean them up, otherwise the QImage will try to clean it up a second time on window close and crash
142 xcb_image_destroy(ximg);
143
144 //Cleanup the XCB data structures
145 xcb_free_pixmap(QX11Info::connection(), pix);
146
147 return img;
148 }*/
149 }
setWinUnpaused()150 void NativeEmbedWidget::setWinUnpaused(){
151 paused = false;
152 winImage = QImage();
153 if(!DISABLE_COMPOSITING){
154 repaintWindow(); //update the cached image right away
155 }else if(this->isVisible()){
156 showWindow();
157 }
158 resyncWindow(); //make sure the window knows about the new location
159 }
160 // ============
161 // PUBLIC
162 // ============
NativeEmbedWidget(QWidget * parent)163 NativeEmbedWidget::NativeEmbedWidget(QWidget *parent) : QWidget(parent){
164 WIN = 0; //nothing embedded yet
165 paused = false;
166 this->setMouseTracking(true);
167 //this->setSizeIncrement(2,2);
168 }
169
embedWindow(NativeWindow * window)170 bool NativeEmbedWidget::embedWindow(NativeWindow *window){
171 WIN = window;
172
173 //Now send the embed event to the app
174 //qDebug() << " - send _XEMBED event";
175 /*xcb_client_message_event_t event;
176 event.response_type = XCB_CLIENT_MESSAGE;
177 event.format = 32;
178 event.window = WIN->id();
179 event.type = obj->ATOMS["_XEMBED"]; //_XEMBED
180 event.data.data32[0] = XCB_TIME_CURRENT_TIME; //CurrentTime;
181 event.data.data32[1] = 0; //XEMBED_EMBEDDED_NOTIFY
182 event.data.data32[2] = 0;
183 event.data.data32[3] = this->winId(); //WID of the container
184 event.data.data32[4] = 0;
185
186 xcb_send_event(QX11Info::connection(), 0, WIN->id(), XCB_EVENT_MASK_STRUCTURE_NOTIFY | XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT, (const char *) &event);
187 */
188 //Now setup any redirects and return
189 if(!DISABLE_COMPOSITING){
190 xcb_composite_redirect_window(QX11Info::connection(), WIN->id(), XCB_COMPOSITE_REDIRECT_MANUAL); //XCB_COMPOSITE_REDIRECT_[MANUAL/AUTOMATIC]);
191 xcb_composite_redirect_subwindows(QX11Info::connection(), WIN->id(), XCB_COMPOSITE_REDIRECT_MANUAL); //AUTOMATIC); //XCB_COMPOSITE_REDIRECT_[MANUAL/AUTOMATIC]);
192
193 //Now create/register the damage handler
194 // -- XCB (Note: The XCB damage registration is completely broken at the moment - 9/15/15, Ken Moore)
195 // -- Retested 6/29/17 (no change) Ken Moore
196 //xcb_damage_damage_t dmgID = xcb_generate_id(QX11Info::connection()); //This is a typedef for a 32-bit unsigned integer
197 //xcb_damage_create(QX11Info::connection(), dmgID, WIN->id(), XCB_DAMAGE_REPORT_LEVEL_RAW_RECTANGLES);
198 // -- XLib (Note: This is only used because the XCB routine above does not work - needs to be fixed upstream in XCB itself).
199 Damage dmgID = XDamageCreate(QX11Info::display(), WIN->id(), XDamageReportRawRectangles);
200
201 WIN->addDamageID( (uint) dmgID); //save this for later
202 connect(WIN, SIGNAL(VisualChanged()), this, SLOT(repaintWindow()) ); //make sure we repaint the widget on visual change
203 }else{
204 xcb_reparent_window(QX11Info::connection(), WIN->id(), this->winId(), 0, 0);
205 registerClientEvents(this->winId()); //child events get forwarded through the frame - watch this for changes too
206 //Also use a partial-composite here - make sure the window pixmap is available even when the window is obscured
207 xcb_composite_redirect_window(QX11Info::connection(), WIN->id(), XCB_COMPOSITE_REDIRECT_AUTOMATIC);
208 //xcb_composite_redirect_subwindows(QX11Info::connection(), WIN->id(), XCB_COMPOSITE_REDIRECT_MANUAL);
209 //Also alert us when the window visual changes
210 Damage dmgID = XDamageCreate(QX11Info::display(), WIN->id(), XDamageReportRawRectangles);
211
212 WIN->addDamageID( (uint) dmgID); //save this for later
213 connect(WIN, SIGNAL(VisualChanged()), this, SLOT(repaintWindow()) ); //make sure we repaint the widget on visual change
214 }
215 WIN->addFrameWinID(this->winId());
216 registerClientEvents(WIN->id());
217 //qDebug() << "Events Registered:" << WIN->id() << this->winId();
218 return true;
219 }
220
detachWindow()221 bool NativeEmbedWidget::detachWindow(){
222 xcb_reparent_window(QX11Info::connection(), WIN->id(), QX11Info::appRootWindow(), -1, -1);
223 //WIN = 0;
224 return true;
225 }
226
isEmbedded()227 bool NativeEmbedWidget::isEmbedded(){
228 return (WIN!=0);
229 }
230
raiseWindow()231 void NativeEmbedWidget::raiseWindow(){
232 if(DISABLE_COMPOSITING){ return; }
233 uint32_t val = XCB_STACK_MODE_ABOVE;
234 xcb_configure_window(QX11Info::connection(), WIN->id(), XCB_CONFIG_WINDOW_STACK_MODE, &val);
235 }
236
lowerWindow()237 void NativeEmbedWidget::lowerWindow(){
238 if(DISABLE_COMPOSITING){ return; }
239 uint32_t val = XCB_STACK_MODE_BELOW;
240 xcb_configure_window(QX11Info::connection(), WIN->id(), XCB_CONFIG_WINDOW_STACK_MODE, &val);
241 }
242
243 // ==============
244 // PUBLIC SLOTS
245 // ==============
246 //Pause/resume
pause()247 void NativeEmbedWidget::pause(){
248 if(DISABLE_COMPOSITING){
249 winImage = windowImage(QRect(QPoint(0,0), this->size()));
250 hideWindow();
251 }else{
252 if(winImage.isNull()){ repaintWindow(); } //make sure we have one image already cached first
253 }
254 paused = true;
255 }
256
resume()257 void NativeEmbedWidget::resume(){
258 //paused = false;
259 syncWinSize();
260 if(DISABLE_COMPOSITING){
261 //showWindow();
262 }else{
263 repaintWindow(); //update the cached image right away
264 }
265 QTimer::singleShot(10, this, SLOT(setWinUnpaused()) );
266 }
267
resyncWindow()268 void NativeEmbedWidget::resyncWindow(){
269 if(WIN==0){ return; }
270 //syncWinSize();
271 //if(DISABLE_COMPOSITING){
272 // Specs say to send an artificial configure event to the window if the window was reparented into the frame
273 QPoint loc = this->mapToGlobal( QPoint(0,0) );
274 //Send an artificial configureNotify event to the window with the global position/size included
275 xcb_configure_notify_event_t *event = (xcb_configure_notify_event_t*) calloc(32,1); //always 32-byes long, even if we don't need all of it
276 event->x = loc.x();
277 event->y = loc.y();
278 event->width = this->width();
279 event->height = this->height();
280 event->border_width = 0;
281 event->above_sibling = XCB_NONE;
282 event->override_redirect = false;
283 event->window = WIN->id();
284 event->event = WIN->id();
285 event->response_type = XCB_CONFIGURE_NOTIFY;
286 xcb_send_event(QX11Info::connection(), false, WIN->id(), XCB_EVENT_MASK_STRUCTURE_NOTIFY | XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY, (char *) event);
287 xcb_flush(QX11Info::connection());
288 free(event);
289 /*}else{
290 //Window is floating invisibly - make sure it is in the right place
291 //Make sure the window size is syncronized and visual up to date
292 //syncWinSize();
293 QTimer::singleShot(10, this, SLOT(repaintWindow()) );
294 }*/
295
296 }
297
repaintWindow()298 void NativeEmbedWidget::repaintWindow(){
299 //if(DISABLE_COMPOSITING){ return; }
300 //qDebug() << "Update Window Image:" << !paused;
301 if(paused){ return; }
302 /*QImage tmp = windowImage( QRect(QPoint(0,0), this->size()) );
303 if(!tmp.isNull()){
304 winImage = tmp;
305 }else{ qDebug() << "Got Null Image!!"; }*/
306 this->parentWidget()->update(); //visual changed - need to update the image on the widget
307 }
308
reregisterEvents()309 void NativeEmbedWidget::reregisterEvents(){
310 if(WIN!=0){ registerClientEvents(WIN->id()); }
311 }
312
313 // ==============
314 // PROTECTED
315 // ==============
resizeEvent(QResizeEvent * ev)316 void NativeEmbedWidget::resizeEvent(QResizeEvent *ev){
317 QWidget::resizeEvent(ev);
318 if(WIN!=0 && !paused){
319 syncWinSize(ev->size());
320 } //syncronize the window with the new widget size
321 }
322
showEvent(QShowEvent * ev)323 void NativeEmbedWidget::showEvent(QShowEvent *ev){
324 if(WIN!=0){ showWindow(); }
325 QWidget::showEvent(ev);
326 }
327
hideEvent(QHideEvent * ev)328 void NativeEmbedWidget::hideEvent(QHideEvent *ev){
329 if(WIN!=0){ hideWindow(); }
330 QWidget::hideEvent(ev);
331 }
332
paintEvent(QPaintEvent * ev)333 void NativeEmbedWidget::paintEvent(QPaintEvent *ev){
334 QPainter P(this);
335 P.setClipping(true);
336 P.setClipRect(0,0,this->width(), this->height());
337 P.fillRect(ev->rect(), Qt::transparent);
338 if(WIN==0){ return; }
339 QRect geom = ev->rect(); //atomic updates
340 //qDebug() << "Paint Rect:" << geom;
341 QImage img;
342 if(!paused){ img = windowImage(geom); }
343 else if(!winImage.isNull()){
344 if(winImage.size() == this->size()){ img = winImage.copy(geom); }
345 else{ img = winImage.scaled(geom.size()); } //this is a fast transformation - might be slightly distorted
346 }
347 //Need to paint the image from the window onto the widget as an overlay
348 P.drawImage( geom , img, QRect(QPoint(0,0), img.size()), Qt::NoOpaqueDetection); //1-to-1 mapping
349
350
351 }
352
enterEvent(QEvent * ev)353 void NativeEmbedWidget::enterEvent(QEvent *ev){
354 QWidget::enterEvent(ev);
355 //qDebug() << "Enter Embed Widget";
356 //raiseWindow(); //this->grabMouse();
357 }
358
leaveEvent(QEvent * ev)359 void NativeEmbedWidget::leaveEvent(QEvent *ev){
360 QWidget::leaveEvent(ev);
361 /*qDebug() << "Leave Embed Widget";
362 QPoint pt = QCursor::pos();
363 QPoint relpt = this->parentWidget()->mapFromGlobal(pt);
364 qDebug() << " - Geom:" << this->geometry() << "Global pt:" << pt << "Relative pt:" << relpt;
365 if(!this->geometry().contains(relpt) ){ lowerWindow(); }*/
366 }
367
mouseMoveEvent(QMouseEvent * ev)368 void NativeEmbedWidget::mouseMoveEvent(QMouseEvent *ev){
369 QWidget::mouseMoveEvent(ev);
370 //Forward this event on to the window
371 }
372
mousePressEvent(QMouseEvent * ev)373 void NativeEmbedWidget::mousePressEvent(QMouseEvent *ev){
374 QWidget::mousePressEvent(ev);
375 //Forward this event on to the window
376 }
377
mouseReleaseEvent(QMouseEvent * ev)378 void NativeEmbedWidget::mouseReleaseEvent(QMouseEvent *ev){
379 QWidget::mouseReleaseEvent(ev);
380 //Forward this event on to the window
381 }
382
383 /*bool NativeEmbedWidget::nativeEvent(const QByteArray &eventType, void *message, long *result){
384 if(eventType=="xcb_generic_event_t" && WIN!=0){
385 //Convert to known event type (for X11 systems)
386 xcb_generic_event_t *ev = static_cast<xcb_generic_event_t *>(message);
387 //qDebug() << "Got Embed Window Event:" << xcb_event_get_label(ev->response_type & XCB_EVENT_RESPONSE_TYPE_MASK) << xcb_event_get_request_label(ev->response_type);
388 uint32_t mask = 0;
389 switch( ev->response_type & XCB_EVENT_RESPONSE_TYPE_MASK){
390 case XCB_BUTTON_PRESS:
391 //This is a mouse button press
392 mask = XCB_EVENT_MASK_BUTTON_PRESS;
393 break;
394 case XCB_BUTTON_RELEASE:
395 //This is a mouse button release
396 //qDebug() << "Button Release Event";
397 mask = XCB_EVENT_MASK_BUTTON_RELEASE;
398 break;
399 case XCB_MOTION_NOTIFY:
400 //This is a mouse movement event
401 mask = XCB_EVENT_MASK_POINTER_MOTION;
402 break;
403 case XCB_ENTER_NOTIFY:
404 //This is a mouse movement event when mouse goes over a new window
405 mask = XCB_EVENT_MASK_ENTER_WINDOW;
406 break;
407 case XCB_LEAVE_NOTIFY:
408 //This is a mouse movement event when mouse goes leaves a window
409 mask = XCB_EVENT_MASK_LEAVE_WINDOW;
410 break;
411 default:
412 mask = 0;
413 }
414
415 //Now forward this event on to the embedded window
416 if(mask!=0){
417 qDebug() << " - Got a mouse event";
418 xcb_send_event(QX11Info::connection(), true, WIN->id(),mask, (char*) ev);
419 return true;
420 }
421 }
422 return false;
423 }*/
424