aboutsummaryrefslogtreecommitdiff
path: root/src-qt5/core/libLumina/NativeEmbedWidget.cpp
blob: 876c701d1abc57c430f9c8cda13d19acac0f727d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
//===========================================
//  Lumina-DE source code
//  Copyright (c) 2017, Ken Moore
//  Available under the 3-clause BSD license
//  See the LICENSE file for full details
//===========================================
#include "NativeEmbedWidget.h"

#include <QPainter>
#include <QX11Info>
#include <QApplication>
#include <QScreen>
#include <QDebug>

#include <xcb/xproto.h>
#include <xcb/xcb_aux.h>
#include <xcb/xcb_event.h>
#include <xcb/xcb_image.h>
//#include <xcb/render.h>
//#include <xcb/xcb_renderutil.h>
#include <xcb/composite.h>
#include <X11/extensions/Xdamage.h>

#define DISABLE_COMPOSITING true

/*inline xcb_render_pictformat_t get_pictformat(){
  static xcb_render_pictformat_t format = 0;
  if(format==0){
    xcb_render_query_pict_formats_reply_t *reply = xcb_render_query_pict_formats_reply( QX11Info::connection(), xcb_render_query_pict_formats(QX11Info::connection()), NULL);
    format = xcb_render_util_find_standard_format(reply, XCB_PICT_STANDARD_ARGB_32)->id;
    free(reply);
  }
  return format;
}


inline void renderWindowToWidget(WId id, QWidget *widget, bool hastransparency = true){
  //window and widget are assumed to be the same size
  //Pull the XCB pixmap out of the compositing layer
  xcb_pixmap_t pix = xcb_generate_id(QX11Info::connection());
    xcb_composite_name_window_pixmap(QX11Info::connection(), WIN->id(), pix);
  if(pix==0){ qDebug() << "Got blank pixmap!"; return; }

  xcb_render_picture_t pic_id = xcb_generate_id(QX11Info::connection());
  xcb_render_create_picture_aux(QX11Info::connection(), pic_id, pix, get_pictformat() , 0, NULL);
  //
  xcb_render_composite(QX11Info::connection(), hastransparency ? XCB_RENDER_PICT_OP_OVER : XCB_RENDER_PICT_OP_SRC, pic_id, XCB_RENDER_PICTURE_NONE, widget->x11RenderHandle(),
				0, 0, 0, 0, 0, 0, (uint16_t) widget->width(), (uint16_t) widget->height() );
}*/

#define CLIENT_EVENT_MASK (XCB_EVENT_MASK_PROPERTY_CHANGE |  \
                          XCB_EVENT_MASK_STRUCTURE_NOTIFY | \
                          XCB_EVENT_MASK_FOCUS_CHANGE | \
                          XCB_EVENT_MASK_POINTER_MOTION)

#define FRAME_EVENT_MASK (XCB_EVENT_MASK_BUTTON_PRESS | \
                          XCB_EVENT_MASK_BUTTON_RELEASE | \
                          XCB_EVENT_MASK_POINTER_MOTION | \
                          XCB_EVENT_MASK_EXPOSURE | \
                          XCB_EVENT_MASK_STRUCTURE_NOTIFY | \
                          XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT | \
                          XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY | \
                          XCB_EVENT_MASK_ENTER_WINDOW)

inline void registerClientEvents(WId id, bool client = true){
  uint32_t values[] = {XCB_NONE};
  values[0] = client ? CLIENT_EVENT_MASK : FRAME_EVENT_MASK ;
  /*{ (XCB_EVENT_MASK_PROPERTY_CHANGE
			| XCB_EVENT_MASK_BUTTON_PRESS
			| XCB_EVENT_MASK_BUTTON_RELEASE
 			| XCB_EVENT_MASK_POINTER_MOTION
			| XCB_EVENT_MASK_BUTTON_MOTION
			| XCB_EVENT_MASK_EXPOSURE
			| XCB_EVENT_MASK_STRUCTURE_NOTIFY
			| XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT
			| XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY
			| XCB_EVENT_MASK_ENTER_WINDOW)
			};*/
  xcb_change_window_attributes(QX11Info::connection(), id, XCB_CW_EVENT_MASK, values);
}

// ============
//      PRIVATE
// ============
//Simplification functions for the XCB/XLib interactions
void NativeEmbedWidget::syncWinSize(QSize sz){
  if(WIN==0){ return; }
  else if(!sz.isValid()){ sz = this->size(); } //use the current widget size
  //qDebug() << "Sync Window Size:" << sz;
  //if(sz == winSize){ return; } //no change
  QPoint pt(0,0);
   if(!DISABLE_COMPOSITING){ pt = this->mapToGlobal(QPoint(0,0)); }
    const uint32_t valList[4] = {(uint32_t) pt.x(), (uint32_t) pt.y(), (uint32_t) sz.width(), (uint32_t) sz.height()};
    const uint32_t mask = XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y | XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT;
    xcb_configure_window(QX11Info::connection(), WIN->id(), mask, valList);
  winSize = sz; //save this for checking later
}

void NativeEmbedWidget::syncWidgetSize(QSize sz){
  //qDebug() << "Sync Widget Size:" << sz;
  this->resize(sz);
}

void NativeEmbedWidget::hideWindow(){
  //qDebug() << "Hide Embed Window";
  xcb_unmap_window(QX11Info::connection(), WIN->id());
}

void NativeEmbedWidget::showWindow(){
  //qDebug() << "Show Embed Window";
  xcb_map_window(QX11Info::connection(), WIN->id());
  reregisterEvents();
  if(!DISABLE_COMPOSITING){
    QTimer::singleShot(0,this, SLOT(repaintWindow()));
  }
}

QImage NativeEmbedWidget::windowImage(QRect geom){
  if(DISABLE_COMPOSITING){
    QList<QScreen*> screens = static_cast<QApplication*>( QApplication::instance() )->screens();
    //for(int i=0; i<screens.length(); i++){
      //if(screens[i]->contains(this)){
    if(!screens.isEmpty()){
        return screens[0]->grabWindow(WIN->id(), geom.x(), geom.y(), geom.width(), geom.height()).toImage();
    }
      //}
    //}
    return QImage();
  }else{
    //Pull the XCB pixmap out of the compositing layer
    xcb_pixmap_t pix = xcb_generate_id(QX11Info::connection());
    xcb_composite_name_window_pixmap(QX11Info::connection(), WIN->id(), pix);
    if(pix==0){ qDebug() << "Got blank pixmap!"; return QImage(); }

    //Convert this pixmap into a QImage
    //xcb_image_t *ximg = xcb_image_get(QX11Info::connection(), pix, 0, 0, this->width(), this->height(), ~0, XCB_IMAGE_FORMAT_Z_PIXMAP);
    xcb_image_t *ximg = xcb_image_get(QX11Info::connection(), pix, geom.x(), geom.y(), geom.width(), geom.height(), ~0, XCB_IMAGE_FORMAT_Z_PIXMAP);
    if(ximg == 0){ qDebug() << "Got blank image!"; return QImage(); }
    QImage img(ximg->data, ximg->width, ximg->height, ximg->stride, QImage::Format_ARGB32_Premultiplied);
    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
    xcb_image_destroy(ximg);

    //Cleanup the XCB data structures
    xcb_free_pixmap(QX11Info::connection(), pix);

    return img;
  }
}
void NativeEmbedWidget::setWinUnpaused(){
  paused = false;
  winImage = QImage();
  if(!DISABLE_COMPOSITING){
    repaintWindow(); //update the cached image right away
  }else if(this->isVisible()){
    showWindow();
  }
}
// ============
//      PUBLIC
// ============
NativeEmbedWidget::NativeEmbedWidget(QWidget *parent) : QWidget(parent){
  WIN = 0; //nothing embedded yet
  paused = false;
  this->setMouseTracking(true);
  //this->setSizeIncrement(2,2);
}

bool NativeEmbedWidget::embedWindow(NativeWindow *window){
  WIN = window;

  //Now send the embed event to the app
  //qDebug() << " - send _XEMBED event";
  /*xcb_client_message_event_t event;
    event.response_type = XCB_CLIENT_MESSAGE;
    event.format = 32;
    event.window = WIN->id();
    event.type = obj->ATOMS["_XEMBED"]; //_XEMBED
    event.data.data32[0] = XCB_TIME_CURRENT_TIME; //CurrentTime;
    event.data.data32[1] = 0; //XEMBED_EMBEDDED_NOTIFY
    event.data.data32[2] = 0;
    event.data.data32[3] = this->winId(); //WID of the container
    event.data.data32[4] = 0;

    xcb_send_event(QX11Info::connection(), 0, WIN->id(),  XCB_EVENT_MASK_STRUCTURE_NOTIFY | XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT, (const char *) &event);
  */
  //Now setup any redirects and return
  if(!DISABLE_COMPOSITING){
    xcb_composite_redirect_window(QX11Info::connection(), WIN->id(), XCB_COMPOSITE_REDIRECT_MANUAL); //XCB_COMPOSITE_REDIRECT_[MANUAL/AUTOMATIC]);
    xcb_composite_redirect_subwindows(QX11Info::connection(), WIN->id(), XCB_COMPOSITE_REDIRECT_MANUAL); //AUTOMATIC); //XCB_COMPOSITE_REDIRECT_[MANUAL/AUTOMATIC]);

    //Now create/register the damage handler
    // -- XCB (Note: The XCB damage registration is completely broken at the moment - 9/15/15, Ken Moore)
    //  -- Retested 6/29/17 (no change) Ken Moore
    //xcb_damage_damage_t dmgID = xcb_generate_id(QX11Info::connection()); //This is a typedef for a 32-bit unsigned integer
    //xcb_damage_create(QX11Info::connection(), dmgID, WIN->id(), XCB_DAMAGE_REPORT_LEVEL_RAW_RECTANGLES);
    // -- XLib (Note: This is only used because the XCB routine above does not work - needs to be fixed upstream in XCB itself).
    Damage dmgID = XDamageCreate(QX11Info::display(), WIN->id(), XDamageReportRawRectangles);

    WIN->addDamageID( (uint) dmgID); //save this for later
    connect(WIN, SIGNAL(VisualChanged()), this, SLOT(repaintWindow()) ); //make sure we repaint the widget on visual change
  }else{
    xcb_reparent_window(QX11Info::connection(), WIN->id(), this->winId(), 0, 0);
    registerClientEvents(this->winId()); //child events get forwarded through the frame - watch this for changes too
    //Also use a partial-composite here - make sure the window pixmap is available even when the window is obscured
    xcb_composite_redirect_window(QX11Info::connection(), WIN->id(), XCB_COMPOSITE_REDIRECT_AUTOMATIC);
    //Also alert us when the window visual changes
    Damage dmgID = XDamageCreate(QX11Info::display(), WIN->id(), XDamageReportRawRectangles);

    WIN->addDamageID( (uint) dmgID); //save this for later
    connect(WIN, SIGNAL(VisualChanged()), this, SLOT(repaintWindow()) ); //make sure we repaint the widget on visual change
  }
  WIN->addFrameWinID(this->winId());
  registerClientEvents(WIN->id());
  //qDebug() << "Events Registered:" << WIN->id() << this->winId();
  return true;
}

bool NativeEmbedWidget::detachWindow(){
  xcb_reparent_window(QX11Info::connection(), WIN->id(), QX11Info::appRootWindow(), -1, -1);
  //WIN = 0;
  return true;
}

bool NativeEmbedWidget::isEmbedded(){
  return (WIN!=0);
}

void NativeEmbedWidget::raiseWindow(){
 if(DISABLE_COMPOSITING){ return; }
  uint32_t val = XCB_STACK_MODE_ABOVE;
  xcb_configure_window(QX11Info::connection(),  WIN->id(), XCB_CONFIG_WINDOW_STACK_MODE, &val);
}

void NativeEmbedWidget::lowerWindow(){
  if(DISABLE_COMPOSITING){ return; }
  uint32_t val = XCB_STACK_MODE_BELOW;
  xcb_configure_window(QX11Info::connection(),  WIN->id(), XCB_CONFIG_WINDOW_STACK_MODE, &val);
}

// ==============
//   PUBLIC SLOTS
// ==============
//Pause/resume
void NativeEmbedWidget::pause(){
  if(DISABLE_COMPOSITING){
    winImage = windowImage(QRect(QPoint(0,0), this->size()));
    hideWindow();
  }else{
    if(winImage.isNull()){ repaintWindow(); } //make sure we have one image already cached first
  }
  paused = true;
}

void NativeEmbedWidget::resume(){
  //paused = false;
  syncWinSize();
  if(DISABLE_COMPOSITING){
    //showWindow();
  }else{
    repaintWindow(); //update the cached image right away
  }
  QTimer::singleShot(10, this, SLOT(setWinUnpaused()) );
}

void NativeEmbedWidget::resyncWindow(){
   if(WIN==0){ return; }
  syncWinSize();
  if(DISABLE_COMPOSITING){
    // Specs say to send an artificial configure event to the window if the window was reparented into the frame
    QPoint loc = this->mapToGlobal( QPoint(0,0));
    //Send an artificial configureNotify event to the window with the global position/size included
    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
      event->x = loc.x();
      event->y = loc.y();
      event->width = this->width();
      event->height = this->height();
      event->border_width = 0;
      event->above_sibling = XCB_NONE;
      event->override_redirect = false;
      event->window = WIN->id();
      event->event = WIN->id();
      event->response_type = XCB_CONFIGURE_NOTIFY;
    xcb_send_event(QX11Info::connection(), false, WIN->id(), XCB_EVENT_MASK_STRUCTURE_NOTIFY | XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY, (char *) event);
    xcb_flush(QX11Info::connection());
    free(event);
  }else{
    //Window is floating invisibly - make sure it is in the right place
    //Make sure the window size is syncronized and visual up to date
    //syncWinSize();
    QTimer::singleShot(10, this, SLOT(repaintWindow()) );
  }

}

void NativeEmbedWidget::repaintWindow(){
  //if(DISABLE_COMPOSITING){ return; }
  //qDebug() << "Update Window Image:" << !paused;
  if(paused){ return; }
    /*QImage tmp = windowImage( QRect(QPoint(0,0), this->size()) );
    if(!tmp.isNull()){
      winImage = tmp;
    }else{ qDebug() << "Got Null Image!!"; }*/
    this->parentWidget()->update(); //visual changed - need to update the image on the widget
}

void NativeEmbedWidget::reregisterEvents(){
  if(WIN!=0){ registerClientEvents(WIN->id()); }
}

// ==============
//      PROTECTED
// ==============
void NativeEmbedWidget::resizeEvent(QResizeEvent *ev){
  QWidget::resizeEvent(ev);
  if(WIN!=0 && !paused){
    syncWinSize(ev->size());
  } //syncronize the window with the new widget size
}

void NativeEmbedWidget::showEvent(QShowEvent *ev){
  if(WIN!=0){ showWindow(); }
  QWidget::showEvent(ev);
}

void NativeEmbedWidget::hideEvent(QHideEvent *ev){
  if(WIN!=0){ hideWindow(); }
  QWidget::hideEvent(ev);
}

void NativeEmbedWidget::paintEvent(QPaintEvent *ev){
  if(WIN==0){ return; }
  QRect geom = ev->rect(); //atomic updates
  //qDebug() << "Paint Rect:" << geom;
  //geom.adjust(-10,-10,10,10); //add an additional few pixels in each direction to be painted
  //geom = geom.intersected(QRect(0,0,this->width(), this->height())); //ensure intersection with actual window
  QImage img;
  if(!paused){ img = windowImage(geom); }
  else if(!winImage.isNull()){
    if(winImage.size() == this->size()){ img = winImage.copy(geom); }
    else{ img = winImage.scaled(geom.size()); } //this is a fast transformation - might be slightly distorted
  }
  //Need to paint the image from the window onto the widget as an overlay

    QPainter P(this);
      P.setClipping(true);
      P.setClipRect(0,0,this->width(), this->height());
      if(DISABLE_COMPOSITING){ P.fillRect(geom, Qt::black); } //get weird effects when partial-compositing is enabled if you layer transparent window frames above other windows
    //qDebug() << "Paint Embed Window:" << geom << winImage.size();
    //if(winImage.size() == this->size()){
      P.drawImage( geom , img,  QRect(QPoint(0,0), img.size()), Qt::NoOpaqueDetection); //1-to-1 mapping
      //Note: Qt::NoOpaqueDetection Speeds up the paint by bypassing the checks to see if there are [semi-]transparent pixels
      //  Since this is an embedded image - we fully expect there to be transparency all/most of the time.
   // }else{
      //P.drawImage( geom , winImage); //auto-scale it to fit (transforming a static image while paused?)
   // }
    //else{ QImage scaled = winImage.scaled(geom.size()); P.drawImage(geom, scaled); }
    //P.drawImage( geom , winImage, geom, Qt::NoOpaqueDetection); //1-to-1 mapping
  //Note: Qt::NoOpaqueDetection Speeds up the paint by bypassing the checks to see if there are [semi-]transparent pixels
  //  Since this is an embedded image - we fully expect there to be transparency all/most of the time.

}

void NativeEmbedWidget::enterEvent(QEvent *ev){
  QWidget::enterEvent(ev);
  //qDebug() << "Enter Embed Widget";
  //raiseWindow(); //this->grabMouse();
}

void NativeEmbedWidget::leaveEvent(QEvent *ev){
  QWidget::leaveEvent(ev);
  /*qDebug() << "Leave Embed Widget";
  QPoint pt = QCursor::pos();
  QPoint relpt = this->parentWidget()->mapFromGlobal(pt);
  qDebug() << " - Geom:" << this->geometry() << "Global pt:" << pt << "Relative pt:" << relpt;
  if(!this->geometry().contains(relpt) ){ lowerWindow(); }*/
}

void NativeEmbedWidget::mouseMoveEvent(QMouseEvent *ev){
  QWidget::mouseMoveEvent(ev);
  //Forward this event on to the window
}

void NativeEmbedWidget::mousePressEvent(QMouseEvent *ev){
  QWidget::mousePressEvent(ev);
  //Forward this event on to the window
}

void NativeEmbedWidget::mouseReleaseEvent(QMouseEvent *ev){
  QWidget::mouseReleaseEvent(ev);
  //Forward this event on to the window
}

/*bool NativeEmbedWidget::nativeEvent(const QByteArray &eventType, void *message, long *result){
  if(eventType=="xcb_generic_event_t" && WIN!=0){
    //Convert to known event type (for X11 systems)
    xcb_generic_event_t *ev = static_cast<xcb_generic_event_t *>(message);
    //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);
    uint32_t mask = 0;
    switch( ev->response_type  & XCB_EVENT_RESPONSE_TYPE_MASK){
	    case XCB_BUTTON_PRESS:
		//This is a mouse button press
		mask = XCB_EVENT_MASK_BUTTON_PRESS;
		break;
	    case XCB_BUTTON_RELEASE:
		//This is a mouse button release
		//qDebug() << "Button Release Event";
		mask = XCB_EVENT_MASK_BUTTON_RELEASE;
		break;
	    case XCB_MOTION_NOTIFY:
		//This is a mouse movement event
		mask = XCB_EVENT_MASK_POINTER_MOTION;
	        break;
	    case XCB_ENTER_NOTIFY:
		//This is a mouse movement event when mouse goes over a new window
		mask = XCB_EVENT_MASK_ENTER_WINDOW;
	        break;
	    case XCB_LEAVE_NOTIFY:
		//This is a mouse movement event when mouse goes leaves a window
		mask = XCB_EVENT_MASK_LEAVE_WINDOW;
	        break;
	    default:
		mask = 0;
    }

    //Now forward this event on to the embedded window
    if(mask!=0){
      qDebug() << " - Got a mouse event";
      xcb_send_event(QX11Info::connection(), true, WIN->id(),mask, (char*) ev);
      return true;
    }
  }
  return false;
}*/
bgstack15