aboutsummaryrefslogtreecommitdiff
path: root/src-qt5/core/libLumina/NativeEmbedWidget.cpp
blob: ed0fd89ce325ab0a0705c3d48e790c2d272d5149 (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
//===========================================
//  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 <QDebug>

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

#define NORMAL_WIN_EVENT_MASK (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_EVENT_MASK_PROPERTY_CHANGE)

inline void registerClientEvents(WId id){
  uint32_t value_list[1] = {NORMAL_WIN_EVENT_MASK};
  xcb_change_window_attributes(QX11Info::connection(), id, XCB_CW_EVENT_MASK, value_list);
}

// ============
//      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
    const uint32_t valList[2] = {(uint32_t) sz.width(), (uint32_t) sz.height()};
    const uint32_t mask = 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(){
  xcb_unmap_window(QX11Info::connection(), WIN->id());
}

void NativeEmbedWidget::showWindow(){
  xcb_map_window(QX11Info::connection(), WIN->id());
  QTimer::singleShot(0,this, SLOT(repaintWindow()));
}

QImage NativeEmbedWidget::windowImage(QRect geom){
  //if(paused){ return QImage(); }
  //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){ return QImage(); }

  //Convert this pixmap into a QImage
  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){ 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
  xcb_image_destroy(ximg);

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

  return img;

}

// ============
//      PUBLIC
// ============
NativeEmbedWidget::NativeEmbedWidget(QWidget *parent) : QWidget(parent){
  WIN = 0; //nothing embedded yet
  paused = false;
  //this->setSizeIncrement(2,2);
}

bool NativeEmbedWidget::embedWindow(NativeWindow *window){
  WIN = window;
  //PIXBACK = xcb_generate_id(QX11Info::connection());
  xcb_reparent_window(QX11Info::connection(), WIN->id(), this->winId(), 0, 0);
  //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
  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); //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
  WIN->addFrameWinID(this->winId());
  connect(WIN, SIGNAL(VisualChanged()), this, SLOT(repaintWindow()) ); //make sure we repaint the widget on visual change

  registerClientEvents(WIN->id());
  registerClientEvents(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);
}

// ==============
//   PUBLIC SLOTS
// ==============
//Pause/resume
void NativeEmbedWidget::pause(){
  if(winImage.isNull()){ repaintWindow(); } //make sure we have one image already cached first
  paused = true;
}

void NativeEmbedWidget::resume(){
  paused = false;
  //syncWinSize();
  //showWindow();
  repaintWindow(); //update the cached image right away
}

void NativeEmbedWidget::resyncWindow(){
   if(WIN==0){ return; }
  /*return; //skip the stuff below (not working)
  QRect geom = WIN->geometry();
  //Send an artificial configureNotify event to the window with the global position/size included
  xcb_configure_notify_event_t event;
    event.x = geom.x() + this->pos().x();
    event.y = geom.y() + this->pos().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, (const char *) &event);
  */
  //Just jitter the window size by 1 pixel really quick so the window knows to update it's geometry
  QSize sz = this->size();
  uint32_t valList[2] = {(uint32_t) sz.width()-1, (uint32_t) sz.height()};
    uint32_t mask = XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT;
    xcb_configure_window(QX11Info::connection(), WIN->id(), mask, valList);
    xcb_flush(QX11Info::connection());
  valList[0] = (uint32_t) sz.width();
    xcb_configure_window(QX11Info::connection(), WIN->id(), mask, valList);
    xcb_flush(QX11Info::connection());
  syncWinSize();
  QTimer::singleShot(0, this, SLOT(repaintWindow()) );
}

void NativeEmbedWidget::repaintWindow(){
  //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();
}
// ==============
//      PROTECTED
// ==============
void NativeEmbedWidget::resizeEvent(QResizeEvent *ev){
  QWidget::resizeEvent(ev);
  if(WIN!=0){
    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){ QWidget::paintEvent(ev); return; }
  else if( winImage.isNull() ){ /*QTimer::singleShot(0, this, SLOT(repaintWindow()) );*/ return; }
  else if(paused){ return; }
  //else if(this->size()!=winSize){ QTimer::singleShot(0,this, SLOT(syncWinSize())); return; } //do not paint here - waiting to re-sync the sizes
  //else if(this->size() != winImage.size()){ QTimer::singleShot(0, this, SLOT(repaintWindow()) ); return; }
  //Need to paint the image from the window onto the widget as an overlay
  QRect geom = ev->rect(); //atomic updates
  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
    if( !QRect(QPoint(0,0),winImage.size()).contains(geom) ){ QTimer::singleShot(0,this, SLOT(repaintWindow()) );return; }
    QPainter P(this);
      P.setClipping(true);
      P.setClipRect(0,0,this->width(), this->height());
    //qDebug() << "Paint Embed Window:" << geom << winImage.size();
    if(winImage.size() == this->size()){
      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.
    }else{
      P.drawImage( geom , winImage);
    }
    //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.

}
bgstack15