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