From 6a2af03c68033a9683de619b4ae6493a4f7254b7 Mon Sep 17 00:00:00 2001 From: Ken Moore Date: Thu, 13 Jul 2017 14:02:59 -0400 Subject: Large update to the window embedding systems - almost have the compositing up and running. --- src-qt5/core/libLumina/NativeEmbedWidget.cpp | 185 ++++++++++++++++++++++++++ src-qt5/core/libLumina/NativeEmbedWidget.h | 47 +++++++ src-qt5/core/libLumina/NativeWindow.cpp | 8 +- src-qt5/core/libLumina/NativeWindow.h | 1 + src-qt5/core/libLumina/NativeWindow.pri | 6 +- src-qt5/core/libLumina/NativeWindowSystem.cpp | 27 +--- src-qt5/core/libLumina/RootSubWindow.cpp | 33 +++-- src-qt5/core/libLumina/RootSubWindow.h | 4 +- 8 files changed, 263 insertions(+), 48 deletions(-) create mode 100644 src-qt5/core/libLumina/NativeEmbedWidget.cpp create mode 100644 src-qt5/core/libLumina/NativeEmbedWidget.h (limited to 'src-qt5/core') diff --git a/src-qt5/core/libLumina/NativeEmbedWidget.cpp b/src-qt5/core/libLumina/NativeEmbedWidget.cpp new file mode 100644 index 00000000..fd5939dd --- /dev/null +++ b/src-qt5/core/libLumina/NativeEmbedWidget.cpp @@ -0,0 +1,185 @@ +//=========================================== +// 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 +#include +#include + +#include +#include +#include +#include +#include + +//xcb_pixmap_t PIXBACK; //backend pixmap that compositing redirects to + +#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; } + qDebug() << "Sync Window Size:" << sz; + xcb_configure_window_value_list_t valList; + valList.x = 0; + valList.y = 0; + valList.width = sz.width(); + valList.height = sz.height(); + uint16_t mask = 0; + mask = mask | XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT; + mask = mask | XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y; + xcb_configure_window_aux(QX11Info::connection(), WIN->id(), mask, &valList); +} + +void NativeEmbedWidget::syncWidgetSize(QSize sz){ + this->resize(sz); +} + +void NativeEmbedWidget::hideWindow(){ + xcb_unmap_window(QX11Info::connection(), WIN->id()); +} + +void NativeEmbedWidget::showWindow(){ + xcb_map_window(QX11Info::connection(), WIN->id()); +} + +QImage NativeEmbedWidget::windowImage(QRect geom){ + //Need the graphics context of the window + /*xcb_gcontext_t gc = xcb_generate_id(QX11Info::connection()); + xcb_screen_t *screen = xcb_setup_roots_iterator(xcb_get_setup(QX11Info::connection())).data; + uint32_t values[1]; + values[0] = screen->black_pixel; + xcb_create_gc(QX11Info::connection(), + gc, + this->winId(), + XCB_GC_BACKGROUND, + values ); + xcb_pixmap_t pix = xcb_generate_id(QX11Info::connection()); + xcb_composite_name_window_pixmap(QX11Info::connection(), WIN->id(), pix); + //Now copy this pixmap onto widget + xcb_copy_area(QX11Info::connection(), pix, this->winId(), gc, geom.x(),geom.y(),geom.x(),geom.y(),geom.width(), geom.height()); + xcb_free_pixmap(QX11Info::connection(), pix); + return QImage();*/ + + /*xcb_put_image(QX11Info::connection(), XCB_IMAGE_FORMAT_Z_PIXMAP, pix, gc, sz.width(), sz.height(), 0, 0, */ + /*xcb_image_t *img = xcb_image_get(QX11Info::connection(), WIN->id(), geom.x(), geom.y(), geom.width(), geom.height(), 1, XCB_IMAGE_FORMAT_Z_PIXMAP); + if(img==0){ return QImage(); } + QImage image(geom.size(), QImage::Format_ARGB32); + image.loadFromData(img->data, img->size); + return image;*/ + +} + +// ============ +// PUBLIC +// ============ +NativeEmbedWidget::NativeEmbedWidget(QWidget *parent) : QWidget(parent){ + WIN = 0; //nothing embedded yet + this->setStyleSheet("background: transparent;"); //this widget should be fully-transparent to Qt itself (will paint on top of that) +} + +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 + //this->SelectInput(WIN->id(), true); //Notify of structure changes + //xcb_composite_redirect_window(QX11Info::connection(), WIN->id(), XCB_COMPOSITE_REDIRECT_MANUAL); //XCB_COMPOSITE_REDIRECT_[MANUAL/AUTOMATIC]); + + //xcb_composite_name_window_pixmap(QX11Info::connection(), WIN->id(), PIXBACK); + //Now map the window (will be a transparent child of the container) + //xcb_map_window(QX11Info::connection(), WIN->id()); + //xcb_map_window(QX11Info::connection(), this->winId()); + //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(update()) ); //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; +} + +bool NativeEmbedWidget::isEmbedded(){ + return (WIN!=0); +} + +// ============== +// PROTECTED +// ============== +void NativeEmbedWidget::resizeEvent(QResizeEvent *ev){ + if(WIN!=0){ syncWinSize(ev->size()); } //syncronize the window with the new widget size + QWidget::resizeEvent(ev); +} + +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){ + //QWidget::paintEvent(ev); //ensure all the Qt-compositing is done first + if(WIN==0){ QWidget::paintEvent(ev); return; } + //Need to paint the image from the window onto the widget as an overlay + QImage img = windowImage(ev->rect()); + if(!img.isNull()){ + QPainter P(this); + P.drawImage( ev->rect() , img, ev->rect(), 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 most of the time. + }else{ + QWidget::paintEvent(ev); + } +} diff --git a/src-qt5/core/libLumina/NativeEmbedWidget.h b/src-qt5/core/libLumina/NativeEmbedWidget.h new file mode 100644 index 00000000..6919249b --- /dev/null +++ b/src-qt5/core/libLumina/NativeEmbedWidget.h @@ -0,0 +1,47 @@ +//=========================================== +// Lumina-DE source code +// Copyright (c) 2017, Ken Moore +// Available under the 3-clause BSD license +// See the LICENSE file for full details +//=========================================== +// This is a container object for embedding a native window into a QWidget +// and maintaining a 1-to-1 mapping of sizing and other properties +// while also providing compositing effects between the two windows +//=========================================== +#ifndef _LUMINA_NATIVE_EMBED_WIDGET_H +#define _LUMINA_NATIVE_EMBED_WIDGET_H + +#include "NativeWindow.h" +#include +#include +#include +#include +#include + +class NativeEmbedWidget : public QWidget{ + Q_OBJECT +private: + NativeWindow *WIN; + + //Simplification functions + void syncWinSize(QSize); + void syncWidgetSize(QSize); + void hideWindow(); + void showWindow(); + QImage windowImage(QRect geom); + +public: + NativeEmbedWidget(QWidget *parent); + + bool embedWindow(NativeWindow *window); + bool detachWindow(); + bool isEmbedded(); //status of the embed + +protected: + void resizeEvent(QResizeEvent *ev); + void showEvent(QShowEvent *ev); + void hideEvent(QHideEvent *ev); + void paintEvent(QPaintEvent *ev); +}; + +#endif diff --git a/src-qt5/core/libLumina/NativeWindow.cpp b/src-qt5/core/libLumina/NativeWindow.cpp index 819661d5..ff7322f6 100644 --- a/src-qt5/core/libLumina/NativeWindow.cpp +++ b/src-qt5/core/libLumina/NativeWindow.cpp @@ -11,12 +11,10 @@ NativeWindow::NativeWindow(WId id) : QObject(){ winid = id; frameid = 0; dmgID = 0; - //WIN = QWindow::fromWinId(winid); } NativeWindow::~NativeWindow(){ hash.clear(); - //WIN->deleteLater(); //This class only deals with Native windows which were created outside the app - they need to be cleaned up outside the app too } void NativeWindow::addFrameWinID(WId fid){ @@ -43,10 +41,6 @@ unsigned int NativeWindow::damageId(){ return dmgID; } -/*QWindow* NativeWindow::window(){ - return WIN; -}*/ - QVariant NativeWindow::property(NativeWindow::Property prop){ if(hash.contains(prop)){ return hash.value(prop); } else if(prop == NativeWindow::RelatedWindows){ return QVariant::fromValue(relatedTo); } @@ -93,9 +87,11 @@ QRect NativeWindow::geometry(){ QRect geom( hash.value(NativeWindow::GlobalPos).toPoint(), hash.value(NativeWindow::Size).toSize() ); //Now adjust the window geom by the frame margins QList frame = hash.value(NativeWindow::FrameExtents).value< QList >(); //Left,Right,Top,Bottom + qDebug() << "Calculate Geometry:" << geom << frame; if(frame.length()==4){ geom = geom.adjusted( -frame[0], -frame[2], frame[1], frame[3] ); } + qDebug() << " - Total:" << geom; return geom; } // ==== PUBLIC SLOTS === diff --git a/src-qt5/core/libLumina/NativeWindow.h b/src-qt5/core/libLumina/NativeWindow.h index 62bb74b5..5fa194c9 100644 --- a/src-qt5/core/libLumina/NativeWindow.h +++ b/src-qt5/core/libLumina/NativeWindow.h @@ -95,6 +95,7 @@ signals: void RequestPropertiesChange(WId, QList, QList); void WindowClosed(WId); void WindowNotResponding(WId); //will be sent out if a window does not respond to a ping request + void VisualChanged(); //Action Requests (not automatically emitted - typically used to ask the WM to do something) //Note: "WId" should be the NativeWindow id() diff --git a/src-qt5/core/libLumina/NativeWindow.pri b/src-qt5/core/libLumina/NativeWindow.pri index 013291ff..c2ac0137 100644 --- a/src-qt5/core/libLumina/NativeWindow.pri +++ b/src-qt5/core/libLumina/NativeWindow.pri @@ -6,10 +6,12 @@ LIBS *= -lc -lxcb -lxcb-ewmh -lxcb-icccm -lxcb-image -lxcb-composite -lxcb-damag SOURCES *= $${PWD}/NativeWindow.cpp \ $${PWD}/NativeWindowSystem.cpp \ $${PWD}/NativeKeyToQt.cpp \ - $${PWD}/NativeEventFilter.cpp + $${PWD}/NativeEventFilter.cpp \ + $${PWD}/NativeEmbedWidget.cpp HEADERS *= $${PWD}/NativeWindow.h \ $${PWD}/NativeWindowSystem.h \ - $${PWD}/NativeEventFilter.h + $${PWD}/NativeEventFilter.h \ + $${PWD}/NativeEmbedWidget.h INCLUDEPATH *= $${PWD} diff --git a/src-qt5/core/libLumina/NativeWindowSystem.cpp b/src-qt5/core/libLumina/NativeWindowSystem.cpp index f208cbf1..bf827a1b 100644 --- a/src-qt5/core/libLumina/NativeWindowSystem.cpp +++ b/src-qt5/core/libLumina/NativeWindowSystem.cpp @@ -254,8 +254,9 @@ void NativeWindowSystem::stop(){ NativeWindow* NativeWindowSystem::findWindow(WId id, bool checkRelated){ //qDebug() << "Find Window:" << id; for(int i=0; iisRelatedTo(id)){ return NWindows[i]; } - else if(!checkRelated && id==NWindows[i]->id()){ return NWindows[i]; } + if(id==NWindows[i]->id() || id==NWindows[i]->frameId() ){ return NWindows[i]; } + //if(checkRelated && NWindows[i]->isRelatedTo(id)){ return NWindows[i]; } + //else if(!checkRelated && id==NWindows[i]->id()){ return NWindows[i]; } } //Check to see if this is a transient for some other window if(checkRelated){ @@ -703,13 +704,13 @@ void NativeWindowSystem::NewTrayWindowDetected(WId id){ void NativeWindowSystem::WindowCloseDetected(WId id){ NativeWindow *win = findWindow(id, false); - //qDebug() << "Got Window Closed" << id << win; + qDebug() << "Got Window Closed" << id << win; //qDebug() << "Old Window List:" << NWindows.length(); if(win!=0){ NWindows.removeAll(win); //RequestReparent(id, QX11Info::appRootWindow(), QPoint(0,0)); win->emit WindowClosed(id); - //qDebug() << "Visible Window Closed!!!"; + qDebug() << "Visible Window Closed!!!"; //win->deleteLater(); }else{ win = findTrayWindow(id); @@ -798,24 +799,8 @@ void NativeWindowSystem::NewMouseRelease(int buttoncode, WId win){ void NativeWindowSystem::CheckDamageID(WId win){ for(int i=0; idamageId() == win || NWindows[i]->id() == win || NWindows[i]->frameId()==win){ + NWindows[i]->emit VisualChanged(); //qDebug() << "Got DAMAGE Event"; - /*NativeWindow *WIN = NWindows[i]; - QSize sz = WIN->property(NativeWindow::Size).toSize(); - //Need the graphics context of the window - xcb_gcontext_t gc = xcb_generate_id(QX11Info::connection()); - xcb_screen_t *screen = xcb_setup_roots_iterator(xcb_get_setup(QX11Info::connection())).data; - uint32_t values[1]; - values[0] = screen->black_pixel; - xcb_create_gc(QX11Info::connection(), - gc, - WIN->frameId(), - XCB_GC_BACKGROUND, - values ); - xcb_pixmap_t pix = xcb_generate_id(QX11Info::connection()); - xcb_composite_name_window_pixmap(QX11Info::connection(), WIN->id(), pix); - //Now put this pixmap onto the frame window - xcb_copy_area(QX11Info::connection(), pix, WIN->frameId(), gc, 0,0,0,0,sz.width(), sz.height());*/ - /*xcb_put_image(QX11Info::connection(), XCB_IMAGE_FORMAT_Z_PIXMAP, pix, gc, sz.width(), sz.height(), 0, 0, */ return; } } diff --git a/src-qt5/core/libLumina/RootSubWindow.cpp b/src-qt5/core/libLumina/RootSubWindow.cpp index 92a85291..5ce3d149 100644 --- a/src-qt5/core/libLumina/RootSubWindow.cpp +++ b/src-qt5/core/libLumina/RootSubWindow.cpp @@ -26,10 +26,10 @@ RootSubWindow::RootSubWindow(QWidget *root, NativeWindow *win) : QFrame(root){ initWindowFrame(); //Hookup the signals/slots connect(WIN, SIGNAL(PropertiesChanged(QList, QList)), this, SLOT(propertiesChanged(QList, QList))); - WIN->addFrameWinID(WinWidget->winId()); - WIN->emit RequestReparent(WIN->id(), WinWidget->winId(), QPoint(0,0)); + WinWidget->embedWindow(WIN); qDebug() << "[NEW WINDOW]" << WIN->id() << WinWidget->winId() << this->winId(); LoadAllProperties(); + //this->show(); } RootSubWindow::~RootSubWindow(){ @@ -147,7 +147,7 @@ void RootSubWindow::initWindowFrame(){ otherB->setMenu(otherM); otherB->setPopupMode(QToolButton::InstantPopup); otherB->setAutoRaise(true); - WinWidget = new QWidget(this); + WinWidget = new NativeEmbedWidget(this); connect(closeB, SIGNAL(clicked()), this, SLOT(triggerClose()) ); connect(maxB, SIGNAL(clicked()), this, SLOT(toggleMaximize()) ); connect(minB, SIGNAL(clicked()), this, SLOT(toggleMinimize()) ); @@ -267,13 +267,13 @@ void RootSubWindow::propertiesChanged(QList props, QList //qDebug() << "RootSubWindow: Property Changed:" << props[i] << vals[i]; switch(props[i]){ case NativeWindow::Visible: - //qDebug() << "Got Visibility Change:" << vals[i]; + qDebug() << "Got Visibility Change:" << vals[i] << this->geometry(); if(vals[i].toBool()){ - WinWidget->setVisible(true); - animResetProp = WIN->geometry(); //show event - might not have the right geom yet + animResetProp = WIN->geometry(); //this->geometry(); anim->setPropertyName("geometry"); - anim->setStartValue( QRect(WIN->geometry().center(), QSize(0,0)) ); - anim->setEndValue(WIN->geometry()); + anim->setStartValue( QRect(animResetProp.toRect().center(), QSize(0,0)) ); + anim->setEndValue(animResetProp); + this->setGeometry( anim->startValue().toRect() ); //ensure the window is the initial geom before it becomes visible anim->start(); this->show(); }else{ @@ -283,7 +283,6 @@ void RootSubWindow::propertiesChanged(QList props, QList anim->setEndValue( QRect(this->geometry().center(), QSize(0,0) ) ); anim->start(); QTimer::singleShot(anim->duration(), this, SLOT(hide()) ); - //this->hide(); } break; case NativeWindow::Title: @@ -295,16 +294,16 @@ void RootSubWindow::propertiesChanged(QList props, QList else{ otherB->setIcon(vals[i].value()); } break; case NativeWindow::GlobalPos: - //qDebug() << "Got Global Pos:" << this->pos() << WinWidget->mapToGlobal(QPoint(0,0)) << WIN->geometry().topLeft() << vals[i].toPoint(); + qDebug() << "Got Global Pos:" << this->pos() << WinWidget->mapToGlobal(QPoint(0,0)) << WIN->geometry().topLeft() << vals[i].toPoint(); this->move( vals[i].toPoint() - (WinWidget->mapToGlobal(QPoint(0,0)) - this->pos()) ); //WIN->geometry().topLeft() ); break; case NativeWindow::Size: - if(WinWidget->size() != vals[i].toSize()){ + //if(WinWidget->size() != vals[i].toSize()){ qDebug() << "Got Widget Size Change:" << vals[i].toSize(); - WinWidget->resize(vals[i].toSize()); + //WinWidget->resize(vals[i].toSize()); this->resize( WIN->geometry().size() ); qDebug() << " - Size after change:" << WinWidget->size() << this->size() << WIN->geometry(); - } + //} break; case NativeWindow::MinSize: WinWidget->setMinimumSize(vals[i].toSize()); @@ -331,13 +330,13 @@ void RootSubWindow::animFinished(){ if(closing){ this->close(); return;} else if(anim->propertyName()=="geometry"){ if(!animResetProp.isNull()){ - qDebug() << "Animation Finished, Reset Geometry:" << animResetProp; + /*qDebug() << "Animation Finished, Reset Geometry:" << animResetProp; qDebug() << " - Starting Value:" << anim->startValue(); qDebug() << " - Ending Value:" << anim->endValue(); - qDebug() << " - Current Value:" << this->geometry(); + qDebug() << " - Current Value:" << this->geometry();*/ this->setGeometry( animResetProp.toRect() ); - WIN->requestProperty(NativeWindow::Size, WinWidget->size(), true); - WIN->setProperty(NativeWindow::GlobalPos, WinWidget->mapToGlobal(QPoint(0,0)),true ); + //WIN->requestProperty(NativeWindow::Size, WinWidget->size(), true); + //WIN->requestProperty(NativeWindow::GlobalPos, WinWidget->mapToGlobal(QPoint(0,0)),true ); } } animResetProp = QVariant(); //clear the variable diff --git a/src-qt5/core/libLumina/RootSubWindow.h b/src-qt5/core/libLumina/RootSubWindow.h index bd20291e..cef6f2ff 100644 --- a/src-qt5/core/libLumina/RootSubWindow.h +++ b/src-qt5/core/libLumina/RootSubWindow.h @@ -20,7 +20,7 @@ #include #include #include - +#include class RootSubWindow : public QFrame{ Q_OBJECT @@ -41,7 +41,7 @@ private: void setMouseCursor(ModState, bool override = false); //Update the mouse cursor based on state //Native window embed objects NativeWindow *WIN; - QWidget *WinWidget; + NativeEmbedWidget *WinWidget; bool closing; //Title bar objects QBoxLayout *titleBar, *mainLayout; -- cgit