diff options
author | Weblate <noreply@weblate.org> | 2017-12-15 22:52:34 +0000 |
---|---|---|
committer | Weblate <noreply@weblate.org> | 2017-12-15 22:52:34 +0000 |
commit | 9155556b94b0fe25ef3d5398a7f70dcdc1841d69 (patch) | |
tree | ae5af06e4741c8767344be6d90a7307bcddb5b5d /src-qt5/src-cpp | |
parent | Translated using Weblate (Russian) (diff) | |
parent | Another minor networking fix. (diff) | |
download | lumina-9155556b94b0fe25ef3d5398a7f70dcdc1841d69.tar.gz lumina-9155556b94b0fe25ef3d5398a7f70dcdc1841d69.tar.bz2 lumina-9155556b94b0fe25ef3d5398a7f70dcdc1841d69.zip |
Merge remote-tracking branch 'origin/master'
Diffstat (limited to 'src-qt5/src-cpp')
-rw-r--r-- | src-qt5/src-cpp/NativeEmbedWidget.cpp | 423 | ||||
-rw-r--r-- | src-qt5/src-cpp/NativeEmbedWidget.h | 74 | ||||
-rw-r--r-- | src-qt5/src-cpp/NativeEventFilter.cpp | 300 | ||||
-rw-r--r-- | src-qt5/src-cpp/NativeEventFilter.h | 71 | ||||
-rw-r--r-- | src-qt5/src-cpp/NativeKeyToQt.cpp | 528 | ||||
-rw-r--r-- | src-qt5/src-cpp/NativeWindow.cpp | 123 | ||||
-rw-r--r-- | src-qt5/src-cpp/NativeWindow.h | 118 | ||||
-rw-r--r-- | src-qt5/src-cpp/NativeWindow.pri | 17 | ||||
-rw-r--r-- | src-qt5/src-cpp/NativeWindowSystem.cpp | 986 | ||||
-rw-r--r-- | src-qt5/src-cpp/NativeWindowSystem.h | 139 | ||||
-rw-r--r-- | src-qt5/src-cpp/framework-OSInterface-template.cpp | 150 | ||||
-rw-r--r-- | src-qt5/src-cpp/framework-OSInterface.h | 190 | ||||
-rw-r--r-- | src-qt5/src-cpp/framework-OSInterface.pri | 9 | ||||
-rw-r--r-- | src-qt5/src-cpp/framework-OSInterface_private.cpp | 110 | ||||
-rw-r--r-- | src-qt5/src-cpp/plugins-screensaver.cpp | 152 | ||||
-rw-r--r-- | src-qt5/src-cpp/plugins-screensaver.h | 50 | ||||
-rw-r--r-- | src-qt5/src-cpp/plugins-screensaver.pri | 4 | ||||
-rw-r--r-- | src-qt5/src-cpp/tests/main.cpp | 38 | ||||
-rw-r--r-- | src-qt5/src-cpp/tests/test.pro | 7 |
19 files changed, 3489 insertions, 0 deletions
diff --git a/src-qt5/src-cpp/NativeEmbedWidget.cpp b/src-qt5/src-cpp/NativeEmbedWidget.cpp new file mode 100644 index 00000000..57b6edde --- /dev/null +++ b/src-qt5/src-cpp/NativeEmbedWidget.cpp @@ -0,0 +1,423 @@ +//=========================================== +// 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){ + if(!this->isVisible()){ return QImage(); } //nothing to grab yet + 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(); + } + resyncWindow(); //make sure the window knows about the new location +} +// ============ +// 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); + //xcb_composite_redirect_subwindows(QX11Info::connection(), WIN->id(), XCB_COMPOSITE_REDIRECT_MANUAL); + //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){ + QPainter P(this); + P.setClipping(true); + P.setClipRect(0,0,this->width(), this->height()); + P.fillRect(ev->rect(), Qt::transparent); + if(WIN==0){ return; } + QRect geom = ev->rect(); //atomic updates + //qDebug() << "Paint Rect:" << geom; + 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 + P.drawImage( geom , img, QRect(QPoint(0,0), img.size()), Qt::NoOpaqueDetection); //1-to-1 mapping + + +} + +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; +}*/ diff --git a/src-qt5/src-cpp/NativeEmbedWidget.h b/src-qt5/src-cpp/NativeEmbedWidget.h new file mode 100644 index 00000000..16bb46dc --- /dev/null +++ b/src-qt5/src-cpp/NativeEmbedWidget.h @@ -0,0 +1,74 @@ +//=========================================== +// 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 <QWidget> +#include <QTimer> +#include <QResizeEvent> +#include <QShowEvent> +#include <QHideEvent> +#include <QPaintEvent> +#include <QMouseEvent> + +class NativeEmbedWidget : public QWidget{ + Q_OBJECT +private: + NativeWindow *WIN; + QSize winSize; + QImage winImage; + bool paused, hasAlphaChannel; + +private slots: + //Simplification functions + void syncWinSize(QSize sz = QSize()); + void syncWidgetSize(QSize sz); + void hideWindow(); + void showWindow(); + QImage windowImage(QRect geom); + + void setWinUnpaused(); + +public: + NativeEmbedWidget(QWidget *parent); + + bool embedWindow(NativeWindow *window); + bool detachWindow(); + bool isEmbedded(); //status of the embed + bool isPaused(){ return paused; } + +public slots: + void raiseWindow(); + void lowerWindow(); + + //Pause/resume + void pause(); + void resume(); + + void resyncWindow(); + void repaintWindow(); + void reregisterEvents(); + +protected: + void resizeEvent(QResizeEvent *ev); + void showEvent(QShowEvent *ev); + void hideEvent(QHideEvent *ev); + void paintEvent(QPaintEvent *ev); + void enterEvent(QEvent *ev); + void leaveEvent(QEvent *ev); + void mouseMoveEvent(QMouseEvent *ev); + void mousePressEvent(QMouseEvent *ev); + void mouseReleaseEvent(QMouseEvent *ev); + //bool nativeEvent(const QByteArray &eventType, void *message, long *result); +}; + +#endif diff --git a/src-qt5/src-cpp/NativeEventFilter.cpp b/src-qt5/src-cpp/NativeEventFilter.cpp new file mode 100644 index 00000000..c13c1fc8 --- /dev/null +++ b/src-qt5/src-cpp/NativeEventFilter.cpp @@ -0,0 +1,300 @@ +//=========================================== +// Lumina-desktop source code +// Copyright (c) 2015-2017, Ken Moore +// Available under the 3-clause BSD license +// See the LICENSE file for full details +//=========================================== +#include "NativeEventFilter.h" +#include <QCoreApplication> +#include <QDebug> + +//#include <xcb/xcb_aux.h> +//#include <xcb/damage.h> + +//================================================== +// NOTE: All the XCB interactions and atoms are accessed via: +// obj->XCB->EWMH.(atom name) +// obj->XCB->(do something) +//================================================== + +/* +List of XCB response types (since almost impossible to find good docs on XCB) +switch (xcb_generic_event_t*->response_type & ~0x80) +case values: +XCB_KEY_[PRESS | RELEASE] +XCB_BUTTON_[PRESS | RELEASE] +XCB_MOTION_NOTIFY +XCB_ENTER_NOTIFY +XCB_LEAVE_NOTIFY +XCB_FOCUS_[IN | OUT] +XCB_KEYMAP_NOTIFY +XCB_EXPOSE +XCB_GRAPHICS_EXPOSURE +XCB_VISIBILITY_NOTIFY +XCB_CREATE_NOTIFY +XCB_DESTROY_NOTIFY +XCB_UNMAP_NOTIFY +XCB_MAP_[NOTIFY | REQUEST] +XCB_REPARENT_NOTIFY +XCB_CONFIGURE_[NOTIFY | REQUEST] +XCB_GRAVITY_NOTIFY +XCB_RESIZE_REQUEST +XCB_CIRCULATE_[NOTIFY | REQUEST] +XCB_PROPERTY_NOTIFY +XCB_SELECTION_[CLEAR | REQUEST | NOTIFY] +XCB_COLORMAP_NOTIFY +XCB_CLIENT_MESSAGE +*/ + +//SYSTEM TRAY STANDARD DEFINITIONS +#define SYSTEM_TRAY_REQUEST_DOCK 0 +#define SYSTEM_TRAY_BEGIN_MESSAGE 1 +#define SYSTEM_TRAY_CANCEL_MESSAGE 2 + +//#include <LuminaX11.h> +#include <QX11Info> +#include <xcb/xcb_ewmh.h> +#include <xcb/xcb_keysyms.h> +#include <xcb/damage.h> + +#define DEBUG 0 + +//Special objects/variables for XCB parsing +static xcb_ewmh_connection_t EWMH; +//static LXCB *XCB = 0; +static xcb_atom_t _NET_SYSTEM_TRAY_OPCODE = 0; + +inline void ParsePropertyEvent(xcb_property_notify_event_t *ev, NativeEventFilter *obj){ + //qDebug() << "Got Property Event:" << ev->window << ev->atom; + NativeWindow::Property prop = NativeWindow::None; + //Now determine which properties are getting changed, and update the native window as appropriate + if(ev->atom == EWMH._NET_WM_NAME){ prop = NativeWindow::Title; } + else if(ev->atom == EWMH._NET_WM_ICON){ prop = NativeWindow::Icon; } + else if(ev->atom == EWMH._NET_WM_ICON_NAME){ prop = NativeWindow::ShortTitle; } + else if(ev->atom == EWMH._NET_WM_DESKTOP){ prop = NativeWindow::Workspace; } + else if(ev->atom == EWMH._NET_WM_WINDOW_TYPE ){ prop = NativeWindow::WinTypes; } + else if( ev->atom == EWMH._NET_WM_STATE){ prop = NativeWindow::States; } + //Send out the signal if necessary + if(prop!=NativeWindow::None){ + //if(DEBUG){ + //qDebug() << "Detected Property Change:" << ev->window << prop; + //} + obj->emit WindowPropertyChanged(ev->window, prop); + }else{ + //Quick re-check of the simple properties (nothing like the icon or other graphics) + obj->emit WindowPropertiesChanged(ev->window, QList<NativeWindow::Property>() << NativeWindow::Title + << NativeWindow::ShortTitle << NativeWindow::Workspace ); + //qDebug() << "Unknown Property Change:" << ev->window << ev->atom; + } +} + +inline void ParseClientMessageEvent(xcb_client_message_event_t *ev, NativeEventFilter *obj){ + NativeWindow::Property prop = NativeWindow::None; + QVariant val; + if(ev->type==EWMH._NET_WM_NAME){ prop = NativeWindow::Title; } + else if(ev->type==EWMH._NET_WM_ICON){ prop = NativeWindow::Icon; } + else if(ev->type==EWMH._NET_WM_ICON_NAME){ prop = NativeWindow::ShortTitle; } + else if(ev->type==EWMH._NET_WM_DESKTOP){ + prop = NativeWindow::Workspace; + val = QVariant( (int) ev->data.data32[0] ); + }else if(ev->type==EWMH._NET_WM_WINDOW_TYPE){ prop = NativeWindow::WinTypes; } + else if(ev->type==EWMH._NET_WM_STATE){ prop = NativeWindow::States; } + + if(prop!=NativeWindow::None){ + //if(DEBUG){ + qDebug() << "Detected Property Change Request:" << ev->window << prop; //} + if(val.isNull()){ obj->emit WindowPropertyChanged(ev->window, prop); } + else{ obj->emit RequestWindowPropertyChange(ev->window, prop, val); } + } + +} + + +//Constructor for the Event Filter wrapper +NativeEventFilter::NativeEventFilter() : QObject(){ + EF = new EventFilter(this); + if(EWMH.nb_screens <=0){ + xcb_intern_atom_cookie_t *cookie = xcb_ewmh_init_atoms(QX11Info::connection(), &EWMH); + if(!xcb_ewmh_init_atoms_replies(&EWMH, cookie, NULL) ){ + qDebug() << "Error with XCB atom initializations"; + } + } + if(_NET_SYSTEM_TRAY_OPCODE==0){ + //_NET_SYSTEM_TRAY_OPCODE + xcb_intern_atom_cookie_t cookie = xcb_intern_atom(QX11Info::connection(), 0, 23,"_NET_SYSTEM_TRAY_OPCODE"); + xcb_intern_atom_reply_t *r = xcb_intern_atom_reply(QX11Info::connection(), cookie, NULL); + if(r){ + _NET_SYSTEM_TRAY_OPCODE = r->atom; + free(r); + } + } +} + +void NativeEventFilter::start(){ + if(DEBUG){ qDebug() << " - Install event filter..."; } + QCoreApplication::instance()->installNativeEventFilter(EF); + if(DEBUG){ qDebug() << " - Run request check..."; } + +} + +void NativeEventFilter::stop(){ + QCoreApplication::instance()->installNativeEventFilter(0); +} + +//============================= +// EventFilter Class +//============================= + +//Constructor for the XCB event filter +EventFilter::EventFilter(NativeEventFilter *parent) : QAbstractNativeEventFilter(){ + obj = parent; +} + +//This function format taken directly from the Qt5.3 documentation +bool EventFilter::nativeEventFilter(const QByteArray &eventType, void *message, long *){ + //qDebug() << "New Event"; + if(eventType=="xcb_generic_event_t"){ + //Convert to known event type (for X11 systems) + xcb_generic_event_t *ev = static_cast<xcb_generic_event_t *>(message); + //Now parse the event and emit signals as necessary + switch( ev->response_type & ~0x80){ +//============================== +// INTERACTIVITY EVENTS +//============================== + case XCB_KEY_PRESS: + //This is a keyboard key press + //qDebug() << "Key Press Event" + obj->emit KeyPressed( ((xcb_key_press_event_t *) ev)->detail, ((xcb_key_press_event_t *) ev)->root ); + break; + case XCB_KEY_RELEASE: + //This is a keyboard key release + //qDebug() << "Key Release Event"; + obj->emit KeyReleased( ((xcb_key_release_event_t *) ev)->detail, ((xcb_key_release_event_t *) ev)->root ); + break; + case XCB_BUTTON_PRESS: + //This is a mouse button press + //qDebug() << "Button Press Event"; + obj->emit MousePressed( ((xcb_button_press_event_t *) ev)->detail, ((xcb_button_press_event_t *) ev)->root ); + break; + case XCB_BUTTON_RELEASE: + //This is a mouse button release + //qDebug() << "Button Release Event"; + obj->emit MouseReleased( ((xcb_button_release_event_t *) ev)->detail, ((xcb_button_release_event_t *) ev)->root ); + break; + case XCB_MOTION_NOTIFY: + //This is a mouse movement event + if(DEBUG){ qDebug() << "Motion Notify Event"; } + obj->emit MouseMovement(); + break; + case XCB_ENTER_NOTIFY: + //This is a mouse movement event when mouse goes over a new window + //qDebug() << "Enter Notify Event"; + obj->emit MouseEnterWindow( ((xcb_enter_notify_event_t *) ev)->root ); + break; + case XCB_LEAVE_NOTIFY: + //This is a mouse movement event when mouse goes leaves a window + //qDebug() << "Leave Notify Event"; + obj->emit MouseLeaveWindow( ((xcb_leave_notify_event_t *) ev)->root ); + break; +//============================== + case XCB_EXPOSE: + //qDebug() << "Expose Notify Event:"; + //qDebug() << " - Given Window:" << ((xcb_property_notify_event_t*)ev)->window; + break; +//============================== + case XCB_MAP_NOTIFY: + //qDebug() << "Window Map Event:" << ((xcb_map_notify_event_t *)ev)->window; + obj->emit WindowPropertyChanged( ((xcb_map_notify_event_t *)ev)->window, NativeWindow::Visible, true); + break; //This is just a notification that a window was mapped - nothing needs to change here + case XCB_MAP_REQUEST: + //qDebug() << "Window Map Request Event"; + obj->emit WindowCreated( ((xcb_map_request_event_t *) ev)->window ); + break; +//============================== + case XCB_CREATE_NOTIFY: + //qDebug() << "Window Create Event"; + break; +//============================== + case XCB_UNMAP_NOTIFY: + //qDebug() << "Window Unmap Event:" << ((xcb_unmap_notify_event_t *)ev)->window; + obj->emit WindowPropertyChanged( ((xcb_map_notify_event_t *)ev)->window, NativeWindow::Visible, false); + break; +//============================== + case XCB_DESTROY_NOTIFY: + //qDebug() << "Window Closed Event:" << ((xcb_destroy_notify_event_t *)ev)->window; + obj->emit WindowDestroyed( ((xcb_destroy_notify_event_t *) ev)->window ); + break; +//============================== + case XCB_FOCUS_IN: + //qDebug() << "Focus In Event:"; + break; +//============================== + case XCB_FOCUS_OUT: + //qDebug() << "Focus Out Event:"; + break; +//============================== + case XCB_PROPERTY_NOTIFY: + //qDebug() << "Property Notify Event:"; + ParsePropertyEvent((xcb_property_notify_event_t*)ev, obj); + break; +//============================== + case XCB_CLIENT_MESSAGE: + //qDebug() << "Client Message Event"; + //qDebug() << " - Given Window:" << ((xcb_client_message_event_t*)ev)->window; + if( ((xcb_client_message_event_t*)ev)->type == _NET_SYSTEM_TRAY_OPCODE && ((xcb_client_message_event_t*)ev)->format == 32){ + //data32[0] is timestamp, [1] is opcode, [2] is window handle + if(SYSTEM_TRAY_REQUEST_DOCK == ((xcb_client_message_event_t*)ev)->data.data32[1]){ + obj->emit TrayWindowCreated( ((xcb_client_message_event_t*)ev)->data.data32[2] ); + //addTrayApp( ((xcb_client_message_event_t*)ev)->data.data32[2] ); + } + //Ignore the System Tray messages at the moment + }else if(((xcb_client_message_event_t*)ev)->window != QX11Info::appRootWindow()){ + ParseClientMessageEvent((xcb_client_message_event_t*)ev, obj); + } + break; +//============================== + case XCB_CONFIGURE_NOTIFY: + //qDebug() << "Configure Notify Event"; + /*obj->emit WindowPropertiesChanged( ((xcb_configure_notify_event_t*)ev)->window, + QList<NativeWindow::Property>() << NativeWindow::GlobalPos << NativeWindow::Size, + QList<QVariant>() << QPoint(((xcb_configure_notify_event_t*)ev)->x, ((xcb_configure_notify_event_t*)ev)->y) << + QSize(((xcb_configure_notify_event_t*)ev)->width, ((xcb_configure_notify_event_t*)ev)->height) );*/ + obj->emit WindowPropertyChanged( ((xcb_configure_notify_event_t*)ev)->window, NativeWindow::Size, + QSize(((xcb_configure_notify_event_t*)ev)->width, ((xcb_configure_notify_event_t*)ev)->height) ); + break; +//============================== + case XCB_CONFIGURE_REQUEST: + //qDebug() << "Configure Request Event"; + obj->emit RequestWindowPropertiesChange( ((xcb_configure_request_event_t*)ev)->window, + QList<NativeWindow::Property>() << NativeWindow::GlobalPos << NativeWindow::Size, + QList<QVariant>() << QPoint(((xcb_configure_request_event_t*)ev)->x, ((xcb_configure_request_event_t*)ev)->y) << + QSize(((xcb_configure_request_event_t*)ev)->width, ((xcb_configure_request_event_t*)ev)->height) ); + break; +//============================== + case XCB_RESIZE_REQUEST: + //qDebug() << "Resize Request Event"; + obj->emit RequestWindowPropertyChange( ((xcb_resize_request_event_t*)ev)->window, + NativeWindow::Size, QSize(((xcb_resize_request_event_t*)ev)->width, ((xcb_resize_request_event_t*)ev)->height) ); + break; +//============================== + case XCB_SELECTION_CLEAR: + //qDebug() << "Selection Clear Event"; + break; +//============================== + case 85: //not sure what event this is - but it seems to come up very often (just hide the notice) + case 0: + case XCB_GE_GENERIC: + break; //generic event - don't do anything special + default: + //if( (ev->response_type & ~0x80)==TrayDmgID){ + obj->emit PossibleDamageEvent( ((xcb_damage_notify_event_t*)ev)->drawable ); + //checkDamageID( ((xcb_damage_notify_event_t*)ev)->drawable ); + //}else{ + //qDebug() << "Default Event:" << (ev->response_type & ~0x80); + //} +//============================== + } + } + return false; + //never stop event handling (this will not impact the X events themselves - just the internal Qt application) +} diff --git a/src-qt5/src-cpp/NativeEventFilter.h b/src-qt5/src-cpp/NativeEventFilter.h new file mode 100644 index 00000000..a3be3ef1 --- /dev/null +++ b/src-qt5/src-cpp/NativeEventFilter.h @@ -0,0 +1,71 @@ +//=========================================== +// Lumina-DE source code +// Copyright (c) 2012-2017, Ken Moore +// Available under the 3-clause BSD license +// See the LICENSE file for full details +//=========================================== +// This class provides the XCB event handling/registrations that are needed +//=========================================== +#ifndef _LUMINA_DESKTOP_NATIVE_EVENT_FILTER_H +#define _LUMINA_DESKTOP_NATIVE_EVENT_FILTER_H + +#include <QAbstractNativeEventFilter> +#include <QObject> +#include <QByteArray> + +#include "NativeWindow.h" + + +class NativeEventFilter : public QObject{ + Q_OBJECT +private: + QAbstractNativeEventFilter* EF; + WId WMFlag; //used to flag a running WM process + +public: + NativeEventFilter(); + ~NativeEventFilter(){} + + void start(); + void stop(); + +signals: + //Window Signals + void WindowCreated(WId); + void WindowDestroyed(WId); + void WindowPropertyChanged(WId, NativeWindow::Property); + void WindowPropertiesChanged(WId, QList<NativeWindow::Property>); + void WindowPropertyChanged(WId, NativeWindow::Property, QVariant); + void WindowPropertiesChanged(WId, QList<NativeWindow::Property>, QList<QVariant>); + void RequestWindowPropertyChange(WId, NativeWindow::Property, QVariant); + void RequestWindowPropertiesChange(WId, QList<NativeWindow::Property>, QList<QVariant>); + + //System Tray Signals + void TrayWindowCreated(WId); + void TrayWindowDestroyed(WId); + + //Miscellaneos Signals + void PossibleDamageEvent(WId); + + //Input Event Signals + void KeyPressed(int, WId); + void KeyReleased(int, WId); + void MousePressed(int, WId); + void MouseReleased(int, WId); + void MouseMovement(); + void MouseEnterWindow(WId); + void MouseLeaveWindow(WId); +}; + +class EventFilter : public QAbstractNativeEventFilter{ +public: + EventFilter(NativeEventFilter *parent); + ~EventFilter(){} + + virtual bool nativeEventFilter(const QByteArray &eventType, void *message, long *); + +private: + NativeEventFilter *obj; +}; + +#endif diff --git a/src-qt5/src-cpp/NativeKeyToQt.cpp b/src-qt5/src-cpp/NativeKeyToQt.cpp new file mode 100644 index 00000000..06056be7 --- /dev/null +++ b/src-qt5/src-cpp/NativeKeyToQt.cpp @@ -0,0 +1,528 @@ + +#include <NativeWindowSystem.h> + +#include <QKeySequence> +#include <QX11Info> + +// XCB/X11 Includes +#define XK_MISCELLANY +#define XK_XKB_KEYS +#define XK_LATIN1 +#define XK_LATIN2 +#define XK_LATIN3 +#define XK_LATIN4 +#define XK_LATIN8 +#define XK_LATIN9 +//NOTE: Look at the keysymdef.h file for additional define/characters which we may need later +#include <X11/keysymdef.h> +#include <xcb/xcb_keysyms.h> + + +//Small simplification functions +Qt::Key NativeWindowSystem::KeycodeToQt(int keycode){ + static xcb_key_symbols_t *SYM = 0; + if(SYM==0){ SYM = xcb_key_symbols_alloc(QX11Info::connection()); } + xcb_keysym_t symbol = xcb_key_symbols_get_keysym(SYM, keycode,0); + //not sure about the "column" input - we want raw keys though so ignore the "modified" key states (columns) for now + //qDebug() << "Try to convert keycode to Qt::Key:" << keycode << symbol; + //Now map this symbol to the appropriate Qt::Key enumeration + switch(symbol){ + //FUNCTION KEYS + case XK_F1: return Qt::Key_F1; + case XK_F2: return Qt::Key_F2; + case XK_F3: return Qt::Key_F3; + case XK_F4: return Qt::Key_F4; + case XK_F5: return Qt::Key_F5; + case XK_F6: return Qt::Key_F6; + case XK_F7: return Qt::Key_F7; + case XK_F8: return Qt::Key_F8; + case XK_F9: return Qt::Key_F9; + case XK_F10: return Qt::Key_F10; + case XK_F11: return Qt::Key_F11; + case XK_F12: return Qt::Key_F12; + case XK_F13: return Qt::Key_F13; + case XK_F14: return Qt::Key_F14; + case XK_F15: return Qt::Key_F15; + case XK_F16: return Qt::Key_F16; + case XK_F17: return Qt::Key_F17; + case XK_F18: return Qt::Key_F18; + case XK_F19: return Qt::Key_F19; + case XK_F20: return Qt::Key_F20; + case XK_F21: return Qt::Key_F21; + case XK_F22: return Qt::Key_F22; + case XK_F23: return Qt::Key_F23; + case XK_F24: return Qt::Key_F24; + case XK_F25: return Qt::Key_F25; + case XK_F26: return Qt::Key_F26; + case XK_F27: return Qt::Key_F27; + case XK_F28: return Qt::Key_F28; + case XK_F29: return Qt::Key_F29; + case XK_F30: return Qt::Key_F30; + case XK_F31: return Qt::Key_F31; + case XK_F32: return Qt::Key_F32; + case XK_F33: return Qt::Key_F33; + case XK_F34: return Qt::Key_F34; + case XK_F35: return Qt::Key_F35; + //Miscellaneous Keys + case XK_BackSpace: return Qt::Key_Backspace; + case XK_Delete: return Qt::Key_Delete; + //case XK_LineFeed: return Qt::Key_Backspace; + case XK_Clear: return Qt::Key_Clear; + case XK_Return: return Qt::Key_Return; + case XK_Pause: return Qt::Key_Pause; + case XK_Scroll_Lock: return Qt::Key_ScrollLock; + case XK_Sys_Req: return Qt::Key_SysReq; + case XK_Escape: return Qt::Key_Escape; + case XK_Select: return Qt::Key_Select; + case XK_Print: return Qt::Key_Print; + //case XK_Execute: return Qt::Key_Execute; + case XK_Insert: return Qt::Key_Insert; + case XK_Undo: return Qt::Key_Undo; + case XK_Redo: return Qt::Key_Redo; + case XK_Menu: return Qt::Key_Menu; + case XK_Find: return Qt::Key_Find; + case XK_Cancel: return Qt::Key_Cancel; + case XK_Help: return Qt::Key_Help; + //case XK_Break: return Qt::Key_Break; + //case XK_Mode_switch: return Qt::Key_Backspace; + //case XK_script_switch: return Qt::Key_Backspace; + case XK_Num_Lock: return Qt::Key_NumLock; + //Cursor Controls + case XK_Home: return Qt::Key_Home; + case XK_Left: return Qt::Key_Left; + case XK_Up: return Qt::Key_Up; + case XK_Right: return Qt::Key_Right; + case XK_Down: return Qt::Key_Down; + //case XK_Prior: return Qt::Key_Backspace; + case XK_Page_Up: return Qt::Key_PageUp; + case XK_Page_Down: return Qt::Key_PageDown; + //case XK_Next: return Qt::Key_Backspace; + case XK_End: return Qt::Key_End; + //case XK_Begin: return Qt::Key_Backspace; + // Keypad Functions and numbers + case XK_KP_Space: return Qt::Key_Space; + case XK_KP_Tab: return Qt::Key_Tab; + case XK_KP_Enter: return Qt::Key_Enter; + case XK_KP_F1: return Qt::Key_F1; + case XK_KP_F2: return Qt::Key_F2; + case XK_KP_F3: return Qt::Key_F3; + case XK_KP_F4: return Qt::Key_F4; + case XK_KP_Home: return Qt::Key_Home; + case XK_KP_Left: return Qt::Key_Left; + case XK_KP_Up: return Qt::Key_Up; + case XK_KP_Right: return Qt::Key_Right; + case XK_KP_Down: return Qt::Key_Down; + //case XK_KP_Prior: return Qt::Key_ + case XK_KP_Page_Up: return Qt::Key_PageUp; + //case XK_KP_Next: return Qt::Key_ + case XK_KP_Page_Down: return Qt::Key_PageDown; + case XK_KP_End: return Qt::Key_End; + //case XK_KP_Begin: return Qt::Key_ + case XK_KP_Insert: return Qt::Key_Insert; + case XK_KP_Delete: return Qt::Key_Delete; + case XK_KP_Equal: return Qt::Key_Equal; + case XK_KP_Multiply: return Qt::Key_Asterisk; + case XK_KP_Add: return Qt::Key_Plus; + case XK_KP_Separator: return Qt::Key_Comma; //X11 definitions say this is often comma + case XK_KP_Subtract: return Qt::Key_Minus; + case XK_KP_Decimal: return Qt::Key_Period; + case XK_KP_Divide: return Qt::Key_Slash; + case XK_KP_0: return Qt::Key_0; + case XK_KP_1: return Qt::Key_1; + case XK_KP_2: return Qt::Key_2; + case XK_KP_3: return Qt::Key_3; + case XK_KP_4: return Qt::Key_4; + case XK_KP_5: return Qt::Key_5; + case XK_KP_6: return Qt::Key_6; + case XK_KP_7: return Qt::Key_7; + case XK_KP_8: return Qt::Key_8; + case XK_KP_9: return Qt::Key_9; + // Modifier Keys + case XK_Shift_L: return Qt::Key_Shift; + case XK_Shift_R: return Qt::Key_Shift; + case XK_Control_L: return Qt::Key_Control; + case XK_Control_R: return Qt::Key_Control; + case XK_Caps_Lock: return Qt::Key_CapsLock; + //case XK_Shift_Lock: return Qt::Key_ShiftLock; + case XK_Meta_L: return Qt::Key_Meta; + case XK_Meta_R: return Qt::Key_Meta; + case XK_Alt_L: return Qt::Key_Alt; + case XK_Alt_R: return Qt::Key_Alt; + case XK_Super_L: return Qt::Key_Super_L; + case XK_Super_R: return Qt::Key_Super_R; + case XK_Hyper_L: return Qt::Key_Hyper_L; + case XK_Hyper_R: return Qt::Key_Hyper_R; + case XK_space: return Qt::Key_Space; + case XK_exclam: return Qt::Key_Exclam; + case XK_quotedbl: return Qt::Key_QuoteDbl; + case XK_numbersign: return Qt::Key_NumberSign; + case XK_dollar: return Qt::Key_Dollar; + case XK_percent: return Qt::Key_Percent; + case XK_ampersand: return Qt::Key_Ampersand; + case XK_apostrophe: return Qt::Key_Apostrophe; + case XK_parenleft: return Qt::Key_ParenLeft; + case XK_parenright: return Qt::Key_ParenRight; + case XK_asterisk: return Qt::Key_Asterisk; + case XK_plus: return Qt::Key_Plus; + case XK_comma: return Qt::Key_Comma; + case XK_minus: return Qt::Key_Minus; + case XK_period: return Qt::Key_Period; + case XK_slash: return Qt::Key_Slash; + case XK_0: return Qt::Key_0; + case XK_1: return Qt::Key_1; + case XK_2: return Qt::Key_2; + case XK_3: return Qt::Key_3; + case XK_4: return Qt::Key_4; + case XK_5: return Qt::Key_5; + case XK_6: return Qt::Key_6; + case XK_7: return Qt::Key_7; + case XK_8: return Qt::Key_8; + case XK_9: return Qt::Key_9; + case XK_colon: return Qt::Key_Colon; + case XK_semicolon: return Qt::Key_Semicolon; + case XK_less: return Qt::Key_Less; + case XK_equal: return Qt::Key_Equal; + case XK_greater: return Qt::Key_Greater; + case XK_question: return Qt::Key_Question; + case XK_at: return Qt::Key_At; + case XK_A: return Qt::Key_A; + case XK_B: return Qt::Key_B; + case XK_C: return Qt::Key_C; + case XK_D: return Qt::Key_D; + case XK_E: return Qt::Key_E; + case XK_F: return Qt::Key_F; + case XK_G: return Qt::Key_G; + case XK_H: return Qt::Key_H; + case XK_I: return Qt::Key_I; + case XK_J: return Qt::Key_J; + case XK_K: return Qt::Key_K; + case XK_L: return Qt::Key_L; + case XK_M: return Qt::Key_M; + case XK_N: return Qt::Key_N; + case XK_O: return Qt::Key_O; + case XK_P: return Qt::Key_P; + case XK_Q: return Qt::Key_Q; + case XK_R: return Qt::Key_R; + case XK_S: return Qt::Key_S; + case XK_T: return Qt::Key_T; + case XK_U: return Qt::Key_U; + case XK_V: return Qt::Key_V; + case XK_W: return Qt::Key_W; + case XK_X: return Qt::Key_X; + case XK_Y : return Qt::Key_Y; + case XK_Z: return Qt::Key_Z; + case XK_bracketleft: return Qt::Key_BracketLeft; + case XK_backslash: return Qt::Key_Backslash; + case XK_bracketright: return Qt::Key_BracketRight; + case XK_asciicircum: return Qt::Key_AsciiCircum; + case XK_underscore: return Qt::Key_Underscore; + case XK_grave: return Qt::Key_Agrave; + case XK_a: return Qt::Key_A; + case XK_b: return Qt::Key_B; + case XK_c: return Qt::Key_C; + case XK_d: return Qt::Key_D; + case XK_e: return Qt::Key_E; + case XK_f : return Qt::Key_F; + case XK_g: return Qt::Key_G; + case XK_h: return Qt::Key_H; + case XK_i: return Qt::Key_I; + case XK_j: return Qt::Key_J; + case XK_k: return Qt::Key_K; + case XK_l: return Qt::Key_L; + case XK_m: return Qt::Key_M; + case XK_n: return Qt::Key_N; + case XK_o: return Qt::Key_O; + case XK_p: return Qt::Key_P; + case XK_q: return Qt::Key_Q; + case XK_r: return Qt::Key_R; + case XK_s: return Qt::Key_S; + case XK_t : return Qt::Key_T; + case XK_u: return Qt::Key_U; + case XK_v: return Qt::Key_V; + case XK_w: return Qt::Key_W; + case XK_x: return Qt::Key_X; + case XK_y: return Qt::Key_Y; + case XK_z: return Qt::Key_Z; + case XK_braceleft: return Qt::Key_BraceLeft; + case XK_bar: return Qt::Key_Bar; + case XK_braceright: return Qt::Key_BraceRight; + case XK_asciitilde: return Qt::Key_AsciiTilde; + + case XK_nobreakspace: return Qt::Key_nobreakspace; + case XK_exclamdown: return Qt::Key_exclamdown; + case XK_cent: return Qt::Key_cent; + case XK_sterling: return Qt::Key_sterling; + case XK_currency: return Qt::Key_currency; + case XK_yen: return Qt::Key_yen; + case XK_brokenbar: return Qt::Key_brokenbar; + case XK_section: return Qt::Key_section; + case XK_diaeresis: return Qt::Key_diaeresis; + case XK_copyright: return Qt::Key_copyright; + case XK_ordfeminine: return Qt::Key_ordfeminine; + case XK_guillemotleft: return Qt::Key_guillemotleft; + case XK_notsign: return Qt::Key_notsign; + case XK_hyphen: return Qt::Key_hyphen; + case XK_registered: return Qt::Key_registered; + case XK_macron: return Qt::Key_macron; + case XK_degree: return Qt::Key_degree; + case XK_plusminus: return Qt::Key_plusminus; + case XK_twosuperior: return Qt::Key_twosuperior; + case XK_threesuperior: return Qt::Key_threesuperior; + case XK_acute: return Qt::Key_acute; + case XK_mu: return Qt::Key_mu; + case XK_paragraph: return Qt::Key_paragraph; + case XK_periodcentered: return Qt::Key_periodcentered; + case XK_cedilla: return Qt::Key_cedilla; + case XK_onesuperior: return Qt::Key_onesuperior; + case XK_masculine: return Qt::Key_masculine; + case XK_guillemotright: return Qt::Key_guillemotright; + case XK_onequarter: return Qt::Key_onequarter; + case XK_onehalf: return Qt::Key_onehalf; + case XK_threequarters: return Qt::Key_threequarters; + case XK_questiondown: return Qt::Key_questiondown; + case XK_Agrave: return Qt::Key_Agrave; + case XK_Aacute: return Qt::Key_Aacute; + case XK_Acircumflex: return Qt::Key_Acircumflex; + case XK_Atilde: return Qt::Key_Atilde; + case XK_Adiaeresis: return Qt::Key_Adiaeresis; + case XK_Aring: return Qt::Key_Aring; + case XK_AE: return Qt::Key_AE; + case XK_Ccedilla: return Qt::Key_Ccedilla; + case XK_Egrave: return Qt::Key_Egrave; + case XK_Eacute: return Qt::Key_Eacute; + case XK_Ecircumflex: return Qt::Key_Ecircumflex; + case XK_Ediaeresis: return Qt::Key_Ediaeresis; + case XK_Igrave: return Qt::Key_Igrave; + case XK_Iacute: return Qt::Key_Iacute; + case XK_Icircumflex: return Qt::Key_Icircumflex; + case XK_Idiaeresis: return Qt::Key_Idiaeresis; + case XK_ETH: return Qt::Key_ETH; + //case XK_Eth: return Qt::Key_Eth; + case XK_Ntilde: return Qt::Key_Ntilde; + case XK_Ograve: return Qt::Key_Ograve; + case XK_Oacute: return Qt::Key_Oacute; + case XK_Ocircumflex: return Qt::Key_Ocircumflex; + case XK_Otilde: return Qt::Key_Otilde; + case XK_Odiaeresis: return Qt::Key_Odiaeresis; + case XK_multiply: return Qt::Key_multiply; + //case XK_Oslash: return Qt::Key_AsciiTilde; + case XK_Ooblique: return Qt::Key_Ooblique; + case XK_Ugrave: return Qt::Key_Ugrave; + case XK_Uacute: return Qt::Key_Uacute; + case XK_Ucircumflex: return Qt::Key_Ucircumflex; + case XK_Udiaeresis: return Qt::Key_Udiaeresis; + case XK_Yacute: return Qt::Key_Yacute; + case XK_THORN: return Qt::Key_THORN; + //case XK_Thorn: return Qt::Key_AsciiTilde; + case XK_ssharp: return Qt::Key_ssharp; + /*case XK_agrave: return Qt::Key_AsciiTilde; + case XK_aacute: return Qt::Key_AsciiTilde; + case XK_acircumflex: return Qt::Key_AsciiTilde; + case XK_atilde: return Qt::Key_AsciiTilde; + case XK_adiaeresis: return Qt::Key_AsciiTilde; + case XK_aring: return Qt::Key_AsciiTilde; + case XK_ae: return Qt::Key_AsciiTilde; + case XK_ccedilla: return Qt::Key_AsciiTilde; + case XK_egrave: return Qt::Key_AsciiTilde; + case XK_eacute: return Qt::Key_AsciiTilde; + case XK_ecircumflex: return Qt::Key_AsciiTilde; + case XK_ediaeresis: return Qt::Key_AsciiTilde; + case XK_igrave: return Qt::Key_AsciiTilde; + case XK_iacute: return Qt::Key_AsciiTilde; + case XK_icircumflex: return Qt::Key_AsciiTilde; + case XK_idiaeresis: return Qt::Key_AsciiTilde; + case XK_eth: return Qt::Key_AsciiTilde; + case XK_ntilde: return Qt::Key_AsciiTilde; + case XK_ograve: return Qt::Key_AsciiTilde; + case XK_oacute: return Qt::Key_AsciiTilde; + case XK_ocircumflex: return Qt::Key_AsciiTilde; + case XK_otilde: return Qt::Key_AsciiTilde; + case XK_odiaeresis: return Qt::Key_AsciiTilde; + case XK_division: return Qt::Key_AsciiTilde; + case XK_oslash: return Qt::Key_AsciiTilde; + case XK_ooblique: return Qt::Key_AsciiTilde; + case XK_ugrave: return Qt::Key_AsciiTilde; + case XK_uacute: return Qt::Key_AsciiTilde; + case XK_ucircumflex: return Qt::Key_AsciiTilde; + case XK_udiaeresis: return Qt::Key_AsciiTilde; + case XK_yacute: return Qt::Key_AsciiTilde; + case XK_thorn: return Qt::Key_AsciiTilde; + case XK_ydiaeresis: return Qt::Key_AsciiTilde; + + case: XK_Agonek: return Qt::Key_AsciiTilde; + case XK_breve: return Qt::Key_AsciiTilde; + case XK_Lstroke: return Qt::Key_AsciiTilde; + case XK_Lcaron: return Qt::Key_AsciiTilde; + case XK_Sacute: return Qt::Key_AsciiTilde; + case XK_Scaron: return Qt::Key_AsciiTilde; + case XK_Scedilla: return Qt::Key_AsciiTilde; + case XK_Tcaron: return Qt::Key_AsciiTilde; + case XK_Zacute: return Qt::Key_AsciiTilde; + case XK_Zcaron: return Qt::Key_AsciiTilde; + case XK_Zabovedot: return Qt::Key_AsciiTilde; + case XK_aogonek: return Qt::Key_AsciiTilde; + case XK_ogonek: return Qt::Key_AsciiTilde; + case XK_lstroke: return Qt::Key_AsciiTilde; + case XK_lcaron: return Qt::Key_AsciiTilde; + case XK_sacute: return Qt::Key_AsciiTilde; + case XK_caron: return Qt::Key_AsciiTilde; + case XK_scaron: return Qt::Key_AsciiTilde; + case XK_scedilla: return Qt::Key_AsciiTilde; + case XK_tcaron: return Qt::Key_AsciiTilde; + case XK_zacute: return Qt::Key_AsciiTilde; + case XK_doubleacute: return Qt::Key_AsciiTilde; + case XK_zcaron: return Qt::Key_AsciiTilde; + case XK_zabovedot: return Qt::Key_AsciiTilde; + case XK_Racute: return Qt::Key_AsciiTilde; + case XK_Abreve: return Qt::Key_AsciiTilde; + case XK_Lacute: return Qt::Key_AsciiTilde; + case XK_Cacute: return Qt::Key_AsciiTilde; + case XK_Ccaron: return Qt::Key_AsciiTilde; + case XK_Eogonek: return Qt::Key_AsciiTilde; + case XK_Ecaron: return Qt::Key_AsciiTilde; + case XK_Dcaron: return Qt::Key_AsciiTilde; + case XK_Dstroke: return Qt::Key_AsciiTilde; + case XK_Nacute: return Qt::Key_AsciiTilde; + case XK_Ncaron: return Qt::Key_AsciiTilde; + case XK_Odoubleacute: return Qt::Key_AsciiTilde; + case XK_Rcaron: return Qt::Key_AsciiTilde; + case XK_Uring: return Qt::Key_AsciiTilde; + case XK_Udoubleacute: return Qt::Key_AsciiTilde; + case XK_Tcedilla: return Qt::Key_AsciiTilde; + case XK_racute: return Qt::Key_AsciiTilde; + case XK_abreve: return Qt::Key_AsciiTilde; + case XK_lacute: return Qt::Key_AsciiTilde; + case XK_cacute: return Qt::Key_AsciiTilde; + case XK_ccaron: return Qt::Key_AsciiTilde; + case XK_eogonek: return Qt::Key_AsciiTilde; + case XK_ecaron: return Qt::Key_AsciiTilde; + case XK_dcaron: return Qt::Key_AsciiTilde; + case XK_dstroke: return Qt::Key_AsciiTilde; + case XK_nacute: return Qt::Key_AsciiTilde; + case XK_ncaron: return Qt::Key_AsciiTilde; + case XK_odoubleacute: return Qt::Key_AsciiTilde; + case XK_rcaron: return Qt::Key_AsciiTilde; + case XK_uring: return Qt::Key_AsciiTilde; + case XK_udoubleacute: return Qt::Key_AsciiTilde; + case XK_tcedilla: return Qt::Key_AsciiTilde; + case XK_abovedot: return Qt::Key_AsciiTilde; + case XK_Hstroke: return Qt::Key_AsciiTilde; + case XK_Hcircumflex: return Qt::Key_AsciiTilde; + case XK_Iabovedot: return Qt::Key_AsciiTilde; + case XK_Gbreve: return Qt::Key_AsciiTilde; + case XK_Jcircumflex: return Qt::Key_AsciiTilde; + case XK_hstroke: return Qt::Key_AsciiTilde; + case XK_hcircumflex: return Qt::Key_AsciiTilde; + case XK_idotless: return Qt::Key_AsciiTilde; + case XK_gbreve: return Qt::Key_AsciiTilde; + case XK_jcircumflex: return Qt::Key_AsciiTilde; + case XK_Cabovedot: return Qt::Key_AsciiTilde; + case XK_Ccircumflex: return Qt::Key_AsciiTilde; + case XK_Gabovedot: return Qt::Key_AsciiTilde; + case XK_Gcircumflex: return Qt::Key_AsciiTilde; + case XK_Ubreve: return Qt::Key_AsciiTilde; + case XK_Scircumflex: return Qt::Key_AsciiTilde; + case XK_cabovedot: return Qt::Key_AsciiTilde; + case XK_ccircumflex: return Qt::Key_AsciiTilde; + case XK_gabovedot: return Qt::Key_AsciiTilde; + case XK_gcircumflex: return Qt::Key_AsciiTilde; + case XK_ubreve: return Qt::Key_AsciiTilde; + case XK_scircumflex: return Qt::Key_AsciiTilde; + case XK_kra: return Qt::Key_AsciiTilde; + case XK_kappa: return Qt::Key_AsciiTilde; + case XK_Rcedilla: return Qt::Key_AsciiTilde; + case XK_Itilde: return Qt::Key_AsciiTilde; + case XK_Lcedilla: return Qt::Key_AsciiTilde; + case XK_Emacron: return Qt::Key_AsciiTilde; + case XK_Gcedilla: return Qt::Key_AsciiTilde; + case XK_Tslash: return Qt::Key_AsciiTilde; + case XK_rcedilla: return Qt::Key_AsciiTilde; + case XK_itilde: return Qt::Key_AsciiTilde; + case XK_lcedilla: return Qt::Key_AsciiTilde; + case XK_emacron: return Qt::Key_AsciiTilde; + case XK_gcedilla: return Qt::Key_AsciiTilde; + case XK_tslash: return Qt::Key_AsciiTilde; + case XK_ENG: return Qt::Key_AsciiTilde; + case XK_eng: return Qt::Key_AsciiTilde; + case XK_Amacron: return Qt::Key_AsciiTilde; + case XK_Iogonek: return Qt::Key_AsciiTilde; + case XK_Eabovedot: return Qt::Key_AsciiTilde; + case XK_Imacron: return Qt::Key_AsciiTilde; + case XK_Ncedilla: return Qt::Key_AsciiTilde; + case XK_Omacron: return Qt::Key_AsciiTilde; + case XK_Kcedilla: return Qt::Key_AsciiTilde; + case XK_Uogonek: return Qt::Key_AsciiTilde; + case XK_Utilde: return Qt::Key_AsciiTilde; + case XK_Umacron: return Qt::Key_AsciiTilde; + case XK_amacron: return Qt::Key_AsciiTilde; + case XK_iogonek: return Qt::Key_AsciiTilde; + case XK_eabovedot: return Qt::Key_AsciiTilde; + case XK_imacron: return Qt::Key_AsciiTilde; + case XK_ncedilla: return Qt::Key_AsciiTilde; + case XK_omacron: return Qt::Key_AsciiTilde; + case XK_kcedilla: return Qt::Key_AsciiTilde; + case XK_uogonek: return Qt::Key_AsciiTilde; + case XK_utilde: return Qt::Key_AsciiTilde; + case XK_umacron: return Qt::Key_AsciiTilde; + case XK_Wcircumflex: return Qt::Key_AsciiTilde; + case XK_wcircumflex: return Qt::Key_AsciiTilde; + case XK_Ycircumflex: return Qt::Key_AsciiTilde; + case XK_ycircumflex: return Qt::Key_AsciiTilde; + case XK_Babovedot: return Qt::Key_AsciiTilde; + case XK_babovedot: return Qt::Key_AsciiTilde; + case XK_Dabovedot: return Qt::Key_AsciiTilde; + case XK_dabovedot: return Qt::Key_AsciiTilde; + case XK_Fabovedot: return Qt::Key_AsciiTilde; + case XK_fabovedot: return Qt::Key_AsciiTilde; + case XK_Mabovedot: return Qt::Key_AsciiTilde; + case XK_mabovedot: return Qt::Key_AsciiTilde; + case XK_Pabovedot: return Qt::Key_AsciiTilde; + case XK_pabovedot: return Qt::Key_AsciiTilde; + case XK_Sabovedot: return Qt::Key_AsciiTilde; + case XK_sabovedot: return Qt::Key_AsciiTilde; + case XK_Tabovedot: return Qt::Key_AsciiTilde; + case XK_tabovedot: return Qt::Key_AsciiTilde; + case XK_Wgrave: return Qt::Key_AsciiTilde; + case XK_wgrave: return Qt::Key_AsciiTilde; + case XK_Wacute: return Qt::Key_AsciiTilde; + case XK_wacute: return Qt::Key_AsciiTilde; + case XK_Wdiaeresis: return Qt::Key_AsciiTilde; + case XK_wdiaeresis: return Qt::Key_AsciiTilde; + case XK_Ygrave: return Qt::Key_AsciiTilde; + case XK_ygrave: return Qt::Key_AsciiTilde; + case XK_OE: return Qt::Key_AsciiTilde; + case XK_oe: return Qt::Key_AsciiTilde; + case XK_Ydiaeresis: return Qt::Key_AsciiTilde;*/ + default: + qDebug() << "Unknown Key"; + } + qDebug() << " -- Simple Qt Map:" << (Qt::Key)(symbol); + qDebug() << " -- Key Sequence Map:" << QKeySequence(symbol); + qDebug() << " - Not implemented yet"; + return Qt::Key_unknown; +} + +NativeWindowSystem::MouseButton NativeWindowSystem::MouseToQt(int keycode){ + switch(keycode){ + case 1: + return NativeWindowSystem::LeftButton; + case 3: + return NativeWindowSystem::RightButton; + case 2: + return NativeWindowSystem::MidButton; + case 4: + return NativeWindowSystem::WheelUp; + case 5: + return NativeWindowSystem::WheelDown; + case 6: + return NativeWindowSystem::WheelLeft; + case 7: + return NativeWindowSystem::WheelRight; + case 8: + return NativeWindowSystem::BackButton; //Not sure if this is correct yet (1/27/17) + case 9: + return NativeWindowSystem::ForwardButton; //Not sure if this is correct yet (1/27/17) + default: + return NativeWindowSystem::NoButton; + } +} diff --git a/src-qt5/src-cpp/NativeWindow.cpp b/src-qt5/src-cpp/NativeWindow.cpp new file mode 100644 index 00000000..02cc001e --- /dev/null +++ b/src-qt5/src-cpp/NativeWindow.cpp @@ -0,0 +1,123 @@ +//=========================================== +// Lumina-DE source code +// Copyright (c) 2017, Ken Moore +// Available under the 3-clause BSD license +// See the LICENSE file for full details +//=========================================== +#include "NativeWindow.h" + +#include <QDebug> + +// === PUBLIC === +NativeWindow::NativeWindow(WId id) : QObject(){ + winid = id; + frameid = 0; + dmgID = 0; +} + +NativeWindow::~NativeWindow(){ + hash.clear(); +} + +void NativeWindow::addFrameWinID(WId fid){ + frameid = fid; +} + +void NativeWindow::addDamageID(unsigned int dmg){ + dmgID = dmg; +} + +bool NativeWindow::isRelatedTo(WId tmp){ + return (relatedTo.contains(tmp) || winid == tmp || frameid == tmp); +} + +WId NativeWindow::id(){ + return winid; +} + +WId NativeWindow::frameId(){ + return frameid; +} + +unsigned int NativeWindow::damageId(){ + return dmgID; +} + +QVariant NativeWindow::property(NativeWindow::Property prop){ + if(hash.contains(prop)){ return hash.value(prop); } + else if(prop == NativeWindow::RelatedWindows){ return QVariant::fromValue(relatedTo); } + return QVariant(); //null variant +} + +void NativeWindow::setProperty(NativeWindow::Property prop, QVariant val, bool force){ + if(prop == NativeWindow::RelatedWindows){ relatedTo = val.value< QList<WId> >(); } + else if(prop == NativeWindow::None || (!force && hash.value(prop)==val)){ return; } + else{ hash.insert(prop, val); } + emit PropertiesChanged(QList<NativeWindow::Property>() << prop, QList<QVariant>() << val); +} + +void NativeWindow::setProperties(QList<NativeWindow::Property> props, QList<QVariant> vals, bool force){ + for(int i=0; i<props.length(); i++){ + if(i>=vals.length()){ props.removeAt(i); i--; continue; } //no corresponding value for this property + if(props[i] == NativeWindow::None || (!force && (hash.value(props[i]) == vals[i])) ){ props.removeAt(i); vals.removeAt(i); i--; continue; } //Invalid property or identical value + hash.insert(props[i], vals[i]); + } + emit PropertiesChanged(props, vals); +} + +void NativeWindow::requestProperty(NativeWindow::Property prop, QVariant val, bool force){ + if(prop == NativeWindow::None || prop == NativeWindow::RelatedWindows || (!force && hash.value(prop)==val) ){ return; } + emit RequestPropertiesChange(winid, QList<NativeWindow::Property>() << prop, QList<QVariant>() << val); +} + +void NativeWindow::requestProperties(QList<NativeWindow::Property> props, QList<QVariant> vals, bool force){ + //Verify/adjust inputs as needed + for(int i=0; i<props.length(); i++){ + if(i>=vals.length()){ props.removeAt(i); i--; continue; } //no corresponding value for this property + if(props[i] == NativeWindow::None || props[i] == NativeWindow::RelatedWindows || (!force && hash.value(props[i])==vals[i]) ){ props.removeAt(i); vals.removeAt(i); i--; continue; } //Invalid property or identical value + /*if( (props[i] == NativeWindow::Visible || props[i] == NativeWindow::Active) && frameid !=0){ + //These particular properties needs to change the frame - not the window itself + emit RequestPropertiesChange(frameid, QList<NativeWindow::Property>() << props[i], QList<QVariant>() << vals[i]); + props.removeAt(i); vals.removeAt(i); i--; + }*/ + } + emit RequestPropertiesChange(winid, props, vals); +} + +QRect NativeWindow::geometry(){ + //Calculate the "full" geometry of the window + frame (if any) + //Check that the size is between the min/max limitations + QSize size = hash.value(NativeWindow::Size).toSize(); + QSize min = hash.value(NativeWindow::MinSize).toSize(); + QSize max = hash.value(NativeWindow::MaxSize).toSize(); + if(min.isValid() && min.width() > size.width() ){ size.setWidth(min.width()); } + if(min.isValid() && min.height() > size.height()){ size.setHeight(min.height()); } + if(max.isValid() && max.width() < size.width() && max.width()>min.width()){ size.setWidth(max.width()); } + if(max.isValid() && max.height() < size.height() && max.height()>min.height()){ size.setHeight(max.height()); } + //Assemble the full geometry + QRect geom( hash.value(NativeWindow::GlobalPos).toPoint(), size ); + //Now adjust the window geom by the frame margins + QList<int> frame = hash.value(NativeWindow::FrameExtents).value< QList<int> >(); //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 === +void NativeWindow::toggleVisibility(){ + setProperty(NativeWindow::Visible, !property(NativeWindow::Visible).toBool() ); +} + +void NativeWindow::requestClose(){ + emit RequestClose(winid); +} + +void NativeWindow::requestKill(){ + emit RequestKill(winid); +} + +void NativeWindow::requestPing(){ + emit RequestPing(winid); +} diff --git a/src-qt5/src-cpp/NativeWindow.h b/src-qt5/src-cpp/NativeWindow.h new file mode 100644 index 00000000..67436259 --- /dev/null +++ b/src-qt5/src-cpp/NativeWindow.h @@ -0,0 +1,118 @@ +//=========================================== +// 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 setting/announcing changes +// in a native window's properties. +// The WM will usually run the "setProperty" function on this object, +// and any other classes/widgets which watch this window can act appropriatly after-the-fact +// Non-WM classes should use the "Request" signals to ask the WM to do something, and listen for changes later +//=========================================== +#ifndef _LUMINA_DESKTOP_NATIVE_WINDOW_H +#define _LUMINA_DESKTOP_NATIVE_WINDOW_H + +#include <QString> +#include <QRect> +#include <QSize> +#include <QObject> +#include <QWindow> +#include <QHash> +#include <QVariant> + +class NativeWindow : public QObject{ + Q_OBJECT +public: + enum State{ S_MODAL, S_STICKY, S_MAX_VERT, S_MAX_HORZ, S_SHADED, S_SKIP_TASKBAR, S_SKIP_PAGER, S_HIDDEN, S_FULLSCREEN, S_ABOVE, S_BELOW, S_ATTENTION }; + enum Type{T_DESKTOP, T_DOCK, T_TOOLBAR, T_MENU, T_UTILITY, T_SPLASH, T_DIALOG, T_DROPDOWN_MENU, T_POPUP_MENU, T_TOOLTIP, T_NOTIFICATION, T_COMBO, T_DND, T_NORMAL }; + enum Action {A_MOVE, A_RESIZE, A_MINIMIZE, A_SHADE, A_STICK, A_MAX_VERT, A_MAX_HORZ, A_FULLSCREEN, A_CHANGE_DESKTOP, A_CLOSE, A_ABOVE, A_BELOW}; + + enum Property{ /*QVariant Type*/ + None=0, /*null*/ + MinSize=1, /*QSize*/ + MaxSize=2, /*QSize*/ + Size=3, /*QSize*/ + GlobalPos=4, /*QPoint*/ + Title=5, /*QString*/ + ShortTitle=6, /*QString*/ + Icon=7, /*QIcon*/ + Name=8, /*QString*/ + Workspace=9, /*int*/ + States=10, /*QList<NativeWindow::State> : Current state of the window */ + WinTypes=11, /*QList<NativeWindow::Type> : Current type of window (typically does not change)*/ + WinActions=12, /*QList<NativeWindow::Action> : Current actions that the window allows (Managed/set by the WM)*/ + FrameExtents=13, /*QList<int> : [Left, Right, Top, Bottom] in pixels */ + RelatedWindows=14, /* QList<WId> - better to use the "isRelatedTo(WId)" function instead of reading this directly*/ + Active=15, /*bool*/ + Visible=16 /*bool*/ + }; + + static QList<NativeWindow::Property> allProperties(){ + //Return all the available properties (excluding "None" and "FrameExtents" (WM control only) ) + QList<NativeWindow::Property> props; + props << MinSize << MaxSize << Size << GlobalPos << Title << ShortTitle << Icon << Name << Workspace \ + << States << WinTypes << WinActions << RelatedWindows << Active << Visible; + return props; + }; + + NativeWindow(WId id); + ~NativeWindow(); + + void addFrameWinID(WId); + void addDamageID(unsigned int); + bool isRelatedTo(WId); + + WId id(); + WId frameId(); + unsigned int damageId(); + + //QWindow* window(); + + QVariant property(NativeWindow::Property); + void setProperty(NativeWindow::Property, QVariant, bool force = false); + void setProperties(QList<NativeWindow::Property>, QList<QVariant>, bool force = false); + void requestProperty(NativeWindow::Property, QVariant, bool force = false); + void requestProperties(QList<NativeWindow::Property>, QList<QVariant>, bool force = false); + + QRect geometry(); //this returns the "full" geometry of the window (window + frame) + +public slots: + void toggleVisibility(); + void requestClose(); //ask the app to close the window (may/not depending on activity) + void requestKill(); //ask the WM to kill the app associated with this window (harsh - only use if not responding) + void requestPing(); //ask the app if it is still active (a WindowNotResponding signal will get sent out if there is no reply); + +private: + QHash <NativeWindow::Property, QVariant> hash; + //QWindow *WIN; + WId winid, frameid; + QList<WId> relatedTo; + unsigned int dmgID; + +signals: + //General Notifications + void PropertiesChanged(QList<NativeWindow::Property>, QList<QVariant>); + void RequestPropertiesChange(WId, QList<NativeWindow::Property>, QList<QVariant>); + 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() + void RequestClose(WId); //Close the window + void RequestKill(WId); //Kill the window/app (usually from being unresponsive) + void RequestPing(WId); //Verify that the window is still active (such as not closing after a request + void RequestReparent(WId, WId, QPoint); //client window, frame window, relative origin point in frame + // System Tray Icon Embed/Unembed Requests + //void RequestEmbed(WId, QWidget*); + //void RequestUnEmbed(WId, QWidget*); +}; + +// Declare the enumerations as Qt MetaTypes +Q_DECLARE_METATYPE(NativeWindow::Type); +Q_DECLARE_METATYPE(NativeWindow::Action); +Q_DECLARE_METATYPE(NativeWindow::State); +Q_DECLARE_METATYPE(NativeWindow::Property); + +#endif diff --git a/src-qt5/src-cpp/NativeWindow.pri b/src-qt5/src-cpp/NativeWindow.pri new file mode 100644 index 00000000..c2ac0137 --- /dev/null +++ b/src-qt5/src-cpp/NativeWindow.pri @@ -0,0 +1,17 @@ + +# Files +QT *= x11extras +LIBS *= -lc -lxcb -lxcb-ewmh -lxcb-icccm -lxcb-image -lxcb-composite -lxcb-damage -lxcb-util -lxcb-keysyms -lXdamage + +SOURCES *= $${PWD}/NativeWindow.cpp \ + $${PWD}/NativeWindowSystem.cpp \ + $${PWD}/NativeKeyToQt.cpp \ + $${PWD}/NativeEventFilter.cpp \ + $${PWD}/NativeEmbedWidget.cpp + +HEADERS *= $${PWD}/NativeWindow.h \ + $${PWD}/NativeWindowSystem.h \ + $${PWD}/NativeEventFilter.h \ + $${PWD}/NativeEmbedWidget.h + +INCLUDEPATH *= $${PWD} diff --git a/src-qt5/src-cpp/NativeWindowSystem.cpp b/src-qt5/src-cpp/NativeWindowSystem.cpp new file mode 100644 index 00000000..e8e9655a --- /dev/null +++ b/src-qt5/src-cpp/NativeWindowSystem.cpp @@ -0,0 +1,986 @@ +//=========================================== +// 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 the XCB version of the NativeWindowSystem class, +// used for interacting with the X11 display system on BSD/Linux/Unix systems +//=========================================== +#include "NativeWindowSystem.h" + +//Additional Qt includes +#include <QX11Info> +#include <QDebug> +#include <QApplication> +#include <QScreen> +#include <QFont> +#include <QFontMetrics> +#include <QKeySequence> + + +//XCB Library includes +#include <xcb/xcb.h> +#include <xcb/xcb_atom.h> +#include <xcb/xproto.h> +#include <xcb/xcb_ewmh.h> +#include <xcb/xcb_icccm.h> +#include <xcb/xcb_image.h> +#include <xcb/xcb_aux.h> +#include <xcb/composite.h> +#include <xcb/damage.h> + +//XLib includes (XCB Damage lib does not appear to register for damage events properly) +#include <X11/extensions/Xdamage.h> + +//SYSTEM TRAY STANDARD DEFINITIONS +#define _NET_SYSTEM_TRAY_ORIENTATION_HORZ 0 +#define _NET_SYSTEM_TRAY_ORIENTATION_VERT 1 +#define SYSTEM_TRAY_REQUEST_DOCK 0 +#define SYSTEM_TRAY_BEGIN_MESSAGE 1 +#define SYSTEM_TRAY_CANCEL_MESSAGE 2 + +#define URGENCYHINT (1L << 8) //For window urgency detection + +#define ROOT_WIN_EVENT_MASK (XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT | \ + XCB_EVENT_MASK_BUTTON_PRESS | \ + XCB_EVENT_MASK_STRUCTURE_NOTIFY | \ + XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY | \ + XCB_EVENT_MASK_POINTER_MOTION | \ + XCB_EVENT_MASK_PROPERTY_CHANGE | \ + XCB_EVENT_MASK_FOCUS_CHANGE | \ + XCB_EVENT_MASK_ENTER_WINDOW) + +#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 | \ + XCB_EVENT_MASK_FOCUS_CHANGE) + +#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); +} + +/*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); +}*/ + +//Internal XCB private objects class +class NativeWindowSystem::p_objects{ +public: + xcb_ewmh_connection_t EWMH; //This is where all the screen info and atoms are located + QHash<QString, xcb_atom_t> ATOMS; + xcb_screen_t *root_screen; + xcb_window_t root_window, wm_window, tray_window; + + //Functions for setting up these objects as needed + bool init_ATOMS(){ + xcb_intern_atom_cookie_t *cookie = xcb_ewmh_init_atoms(QX11Info::connection(), &EWMH); + if(!xcb_ewmh_init_atoms_replies(&EWMH, cookie, NULL) ){ + qDebug() << "Error with XCB atom initializations"; + return false; + } + + QStringList atoms; + atoms << "WM_TAKE_FOCUS" << "WM_DELETE_WINDOW" << "WM_PROTOCOLS" << "_NET_WM_WINDOW_OPACITY" + << "WM_CHANGE_STATE" << "_NET_SYSTEM_TRAY_OPCODE" << "_NET_SYSTEM_TRAY_ORIENTATION" << "_XEMBED" + << "_NET_SYSTEM_TRAY_VISUAL" << QString("_NET_SYSTEM_TRAY_S%1").arg(QString::number(QX11Info::appScreen())); + //Create all the requests for the atoms + QList<xcb_intern_atom_reply_t*> reply; + for(int i=0; i<atoms.length(); i++){ + reply << xcb_intern_atom_reply(QX11Info::connection(), \ + xcb_intern_atom(QX11Info::connection(), 0, atoms[i].length(), atoms[i].toLocal8Bit()), NULL); + } + //Now evaluate all the requests and save the atoms + for(int i=0; i<reply.length(); i++){ //NOTE: this will always be the same length as the "atoms" list + if(reply[i]!=0){ + ATOMS.insert(atoms[i], reply[i]->atom); + free(reply[i]); //done with this reply + }else{ + //Invalid atom - could not be created + qDebug() << "Could not initialize XCB atom:" << atoms[i]; + } + } //loop over reply + return (ATOMS.keys().length() == atoms.length()); + } + + WId getTransientFor(WId win){ + xcb_get_property_cookie_t cookie = xcb_icccm_get_wm_transient_for_unchecked(QX11Info::connection(), win); + xcb_window_t trans; + if(1!= xcb_icccm_get_wm_transient_for_reply(QX11Info::connection(), cookie, &trans, NULL) ){ + return win; //error in fetching transient window ID (or none found) + }else{ + return trans; + } +} + + bool register_wm(){ + uint32_t value_list[1] = {ROOT_WIN_EVENT_MASK}; + xcb_generic_error_t *status = xcb_request_check( QX11Info::connection(), xcb_change_window_attributes_checked(QX11Info::connection(), root_window, XCB_CW_EVENT_MASK, value_list)); + if(status!=0){ return false; } + uint32_t params[] = {1}; + wm_window = xcb_generate_id(QX11Info::connection()); //need a new ID + xcb_create_window(QX11Info::connection(), root_screen->root_depth, \ + wm_window, root_window, -1, -1, 1, 1, 0, \ + XCB_WINDOW_CLASS_INPUT_OUTPUT, root_screen->root_visual, \ + XCB_CW_OVERRIDE_REDIRECT, params); + if(wm_window==0){ return false; } + //Set the _NET_SUPPORTING_WM property on the root window first + xcb_ewmh_set_supporting_wm_check(&EWMH, root_window, wm_window); + //Also set this property on the child window (pointing to itself) + xcb_ewmh_set_supporting_wm_check(&EWMH, wm_window, wm_window); + //Now also setup the root event mask on the wm_window + status = xcb_request_check( QX11Info::connection(), xcb_change_window_attributes_checked(QX11Info::connection(), wm_window, XCB_CW_EVENT_MASK, value_list)); + if(status!=0){ return false; } + return true; + } + + bool start_system_tray(){ + xcb_atom_t _NET_SYSTEM_TRAY_S = ATOMS.value( QString("_NET_SYSTEM_TRAY_S%1").arg(QString::number(QX11Info::appScreen())) ); + //Make sure that there is no other system tray running + xcb_get_selection_owner_reply_t *ownreply = xcb_get_selection_owner_reply(QX11Info::connection(), \ + xcb_get_selection_owner_unchecked(QX11Info::connection(), _NET_SYSTEM_TRAY_S), NULL); + if(ownreply == 0){ + qWarning() << " - Could not get owner selection reply"; + return false; + }else if(ownreply->owner != 0){ + free(ownreply); + qWarning() << " - An alternate system tray is currently in use"; + return false; + } + free(ownreply); + //Now create the window to use (just offscreen) + tray_window = xcb_generate_id(QX11Info::connection()); //need a new ID + uint32_t params[] = {1}; + xcb_create_window(QX11Info::connection(), root_screen->root_depth, \ + tray_window, root_screen->root, -1, -1, 1, 1, 0, \ + XCB_WINDOW_CLASS_INPUT_OUTPUT, root_screen->root_visual, \ + XCB_CW_OVERRIDE_REDIRECT, params); + //Now register this widget as the system tray + xcb_set_selection_owner(QX11Info::connection(), tray_window, _NET_SYSTEM_TRAY_S, XCB_CURRENT_TIME); + //Make sure that it was registered properly + ownreply = xcb_get_selection_owner_reply(QX11Info::connection(), \ + xcb_get_selection_owner_unchecked(QX11Info::connection(), _NET_SYSTEM_TRAY_S), NULL); + if(ownreply==0 || ownreply->owner != tray_window){ + if(ownreply!=0){ free(ownreply); } + qWarning() << " - Could not register the system tray"; + xcb_destroy_window(QX11Info::connection(), tray_window); + return false; + } + free(ownreply); //done with structure + //Now register the orientation of the system tray + uint32_t orient = _NET_SYSTEM_TRAY_ORIENTATION_HORZ; + xcb_change_property(QX11Info::connection(), XCB_PROP_MODE_REPLACE, tray_window, \ + ATOMS.value("_NET_SYSTEM_TRAY_ORIENTATION"), XCB_ATOM_CARDINAL, 32, 1, &orient); + + //Now set the visual ID for the system tray (same as the root window, but TrueColor) + xcb_visualtype_t *type = xcb_aux_find_visual_by_attrs(root_screen, XCB_VISUAL_CLASS_TRUE_COLOR, 32); + if(type!=0){ + xcb_change_property(QX11Info::connection(), XCB_PROP_MODE_REPLACE, tray_window, \ + ATOMS.value("_NET_SYSTEM_TRAY_VISUAL"), XCB_ATOM_VISUALID, 32, 1, &type->visual_id); + }else{ + qWarning() << " - Could not set TrueColor visual for system tray"; + } + + //Finally, send out an X event letting others know that the system tray is up and running + xcb_client_message_event_t event; + event.response_type = XCB_CLIENT_MESSAGE; + event.format = 32; + event.window = root_screen->root; + event.type = EWMH.MANAGER; //MANAGER atom + event.data.data32[0] = XCB_TIME_CURRENT_TIME; //CurrentTime; + event.data.data32[1] = _NET_SYSTEM_TRAY_S; //_NET_SYSTEM_TRAY_S atom + event.data.data32[2] = tray_window; + event.data.data32[3] = 0; + event.data.data32[4] = 0; + + xcb_send_event(QX11Info::connection(), 0, root_screen->root, XCB_EVENT_MASK_STRUCTURE_NOTIFY | XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT, (const char *) &event); + return true; + } + +}; //end private objects class + + +//inline functions for setting up the internal objects + + +// === PUBLIC === +NativeWindowSystem::NativeWindowSystem() : QObject(){ + obj = 0; + pingTimer = 0; + screenLocked = false; +} + +NativeWindowSystem::~NativeWindowSystem(){ + xcb_ewmh_connection_wipe(&(obj->EWMH)); + free(obj); +} + +//Overarching start/stop functions +bool NativeWindowSystem::start(){ + //Initialize the XCB/EWMH objects + if(obj==0){ + obj = new p_objects(); //instantiate the private objects + obj->wm_window = 0; + obj->tray_window = 0; + xcb_intern_atom_cookie_t *cookie = xcb_ewmh_init_atoms(QX11Info::connection(), &obj->EWMH); + if(!xcb_ewmh_init_atoms_replies(&obj->EWMH, cookie, NULL) ){ + qDebug() << "Error with XCB atom initializations"; + return false; + } + obj->root_screen = xcb_aux_get_screen(QX11Info::connection(), QX11Info::appScreen()); + obj->root_window = obj->root_screen->root; //simplification for later - minor duplication of memory (unsigned int) + //Initialize all the extra atoms that the EWMH object does not have + if( !obj->init_ATOMS() ){ return false; } + } //Done with private object init + bool ok = obj->register_wm(); + if(ok){ + setRoot_supportedActions(); + ok = obj->start_system_tray(); + }else{ + qWarning() << "Could not register the WM"; + } + return ok; +} + +void NativeWindowSystem::stop(){ + +} + +// === PRIVATE === +NativeWindow* NativeWindowSystem::findWindow(WId id, bool checkRelated){ + //qDebug() << "Find Window:" << id; + for(int i=0; i<NWindows.length(); i++){ + if(id==NWindows[i]->id() ){ return NWindows[i]; } + else if(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){ + //WId tid = obj->getTransientFor(id); + //if(tid!=id){ return findWindow(tid, checkRelated); } //call it recursively as needed + //qDebug() << " -- Could not find Window!"; + } + return 0; +} + +NativeWindow* NativeWindowSystem::findTrayWindow(WId id){ + for(int i=0; i<TWindows.length(); i++){ + if(TWindows[i]->isRelatedTo(id)){ return TWindows[i]; } + } + return 0; +} + +void NativeWindowSystem::UpdateWindowProperties(NativeWindow* win, QList< NativeWindow::Property > props){ + //Put the properties in logical groups as appropriate (some XCB calls return multiple properties) + if(props.contains(NativeWindow::Title)){ + //Try the EWMH standards first + // _NET_WM_NAME + QString name; + xcb_get_property_cookie_t cookie = xcb_ewmh_get_wm_name_unchecked(&obj->EWMH, win->id()); + if(cookie.sequence != 0){ + xcb_ewmh_get_utf8_strings_reply_t data; + if( 1 == xcb_ewmh_get_wm_name_reply(&obj->EWMH, cookie, &data, NULL) ){ + name = QString::fromUtf8(data.strings, data.strings_len); + } + } + if(name.isEmpty()){ + //_NET_WM_VISIBLE_NAME + xcb_get_property_cookie_t cookie = xcb_ewmh_get_wm_visible_name_unchecked(&obj->EWMH, win->id()); + if(cookie.sequence != 0){ + xcb_ewmh_get_utf8_strings_reply_t data; + if( 1 == xcb_ewmh_get_wm_visible_name_reply(&obj->EWMH, cookie, &data, NULL) ){ + name = QString::fromUtf8(data.strings, data.strings_len); + } + } + } + if(name.isEmpty()){ + //Now try the ICCCM standard + xcb_get_property_cookie_t cookie = xcb_icccm_get_wm_name_unchecked(QX11Info::connection(), win->id()); + xcb_icccm_get_text_property_reply_t reply; + if(1 == xcb_icccm_get_wm_name_reply(QX11Info::connection(), cookie, &reply, NULL) ){ + name = QString::fromLocal8Bit(reply.name, reply.name_len); + xcb_icccm_get_text_property_reply_wipe(&reply); + } + } + win->setProperty(NativeWindow::Title, name); + } //end TITLE property + + if(props.contains(NativeWindow::ShortTitle)){ + //Try the EWMH standards first + // _NET_WM_ICON_NAME + QString name; + xcb_get_property_cookie_t cookie = xcb_ewmh_get_wm_icon_name_unchecked(&obj->EWMH, win->id()); + if(cookie.sequence != 0){ + xcb_ewmh_get_utf8_strings_reply_t data; + if( 1 == xcb_ewmh_get_wm_icon_name_reply(&obj->EWMH, cookie, &data, NULL) ){ + name = QString::fromUtf8(data.strings, data.strings_len); + } + } + if(name.isEmpty()){ + //_NET_WM_VISIBLE_ICON_NAME + xcb_get_property_cookie_t cookie = xcb_ewmh_get_wm_visible_icon_name_unchecked(&obj->EWMH, win->id()); + if(cookie.sequence != 0){ + xcb_ewmh_get_utf8_strings_reply_t data; + if( 1 == xcb_ewmh_get_wm_visible_icon_name_reply(&obj->EWMH, cookie, &data, NULL) ){ + name = QString::fromUtf8(data.strings, data.strings_len); + } + } + } + if(name.isEmpty()){ + //Now try the ICCCM standard + xcb_get_property_cookie_t cookie = xcb_icccm_get_wm_icon_name_unchecked(QX11Info::connection(), win->id()); + xcb_icccm_get_text_property_reply_t reply; + if(1 == xcb_icccm_get_wm_icon_name_reply(QX11Info::connection(), cookie, &reply, NULL) ){ + name = QString::fromLocal8Bit(reply.name, reply.name_len); + xcb_icccm_get_text_property_reply_wipe(&reply); + } + } + win->setProperty(NativeWindow::ShortTitle, name); + } //end SHORTTITLE property + + if(props.contains(NativeWindow::Icon)){ + //See if this is a tray icon first (different routine - entire app window is the icon) + QIcon icon; + if(win == findTrayWindow(win->id())){ + //Tray Icon Window + QPixmap pix; + //Get the current QScreen (for XCB->Qt conversion) + QList<QScreen*> scrnlist = QApplication::screens(); + //Try to grab the given window directly with Qt + for(int i=0; i<scrnlist.length() && pix.isNull(); i++){ + pix = scrnlist[i]->grabWindow(win->id()); + } + icon.addPixmap(pix); + }else{ + //Standard window + //Fetch the _NET_WM_ICON for the window and return it as a QIcon + xcb_get_property_cookie_t cookie = xcb_ewmh_get_wm_icon_unchecked(&obj->EWMH, win->id()); + xcb_ewmh_get_wm_icon_reply_t reply; + if(1 == xcb_ewmh_get_wm_icon_reply(&obj->EWMH, cookie, &reply, NULL)){ + xcb_ewmh_wm_icon_iterator_t iter = xcb_ewmh_get_wm_icon_iterator(&reply); + //Just use the first + bool done =false; + while(!done){ + //Now convert the current data into a Qt image + // - first 2 elements are width and height (removed via XCB functions) + // - data in rows from left to right and top to bottom + QImage image(iter.width, iter.height, QImage::Format_ARGB32); //initial setup + uint* dat = iter.data; + //dat+=2; //remember the first 2 element offset + for(int i=0; i<image.byteCount()/4; ++i, ++dat){ + ((uint*)image.bits())[i] = *dat; + } + icon.addPixmap(QPixmap::fromImage(image)); //layer this pixmap onto the icon + //Now see if there are any more icons available + done = (iter.rem<1); //number of icons remaining + if(!done){ xcb_ewmh_get_wm_icon_next(&iter); } //get the next icon data + } + xcb_ewmh_get_wm_icon_reply_wipe(&reply); + } + } //end type of window + win->setProperty(NativeWindow::Icon, icon); + } //end ICON property + + if(props.contains(NativeWindow::MinSize) || props.contains(NativeWindow::MaxSize) + || props.contains(NativeWindow::Size) || props.contains(NativeWindow::GlobalPos) ){ + //Try the ICCCM "Normal Hints" structure first (newer spec?) + xcb_get_property_cookie_t cookie = xcb_icccm_get_wm_normal_hints_unchecked(QX11Info::connection(), win->id()); + xcb_size_hints_t reply; + bool ok = false; + if(1==xcb_icccm_get_wm_normal_hints_reply(QX11Info::connection(), cookie, &reply, NULL) ){ ok = true; } + else{ + //Could not find normal hints, try the older "size hints" instead + cookie = xcb_icccm_get_wm_size_hints_unchecked(QX11Info::connection(), win->id(), XCB_ATOM_WM_SIZE_HINTS); + if(1==xcb_icccm_get_wm_size_hints_reply(QX11Info::connection(), cookie, &reply, NULL) ){ ok = true; } + } + if(ok){ + bool initsize = win->property(NativeWindow::Size).isNull(); //initial window size + if( (reply.flags&XCB_ICCCM_SIZE_HINT_US_POSITION)==XCB_ICCCM_SIZE_HINT_US_POSITION ){ win->setProperty(NativeWindow::GlobalPos, QPoint(reply.x,reply.y)); } + if( (reply.flags&XCB_ICCCM_SIZE_HINT_US_SIZE)==XCB_ICCCM_SIZE_HINT_US_SIZE ){ win->setProperty(NativeWindow::Size, QSize(reply.width, reply.height)); } + if( (reply.flags&XCB_ICCCM_SIZE_HINT_P_POSITION)==XCB_ICCCM_SIZE_HINT_P_POSITION ){ win->setProperty(NativeWindow::GlobalPos, QPoint(reply.x,reply.y)); } + if( (reply.flags&XCB_ICCCM_SIZE_HINT_P_SIZE)==XCB_ICCCM_SIZE_HINT_P_SIZE ){ win->setProperty(NativeWindow::Size, QSize(reply.width, reply.height)); } + if( (reply.flags&XCB_ICCCM_SIZE_HINT_P_MIN_SIZE)==XCB_ICCCM_SIZE_HINT_P_MIN_SIZE ){ win->setProperty(NativeWindow::MinSize, QSize(reply.min_width, reply.min_height)); } + if( (reply.flags&XCB_ICCCM_SIZE_HINT_P_MAX_SIZE)==XCB_ICCCM_SIZE_HINT_P_MAX_SIZE ){ win->setProperty(NativeWindow::MaxSize, QSize(reply.max_width, reply.max_height)); } + if( (reply.flags&XCB_ICCCM_SIZE_HINT_BASE_SIZE)==XCB_ICCCM_SIZE_HINT_BASE_SIZE && initsize ){ win->setProperty(NativeWindow::Size, QSize(reply.base_width, reply.base_height)); } + //if( (reply.flags&XCB_ICCCM_SIZE_HINT_P_RESIZE_INC)==XCB_ICCCM_SIZE_HINT_P_RESIZE_INC ){ hints.width_inc=reply.width_inc; hints.height_inc=reply.height_inc; } + //if( (reply.flags&XCB_ICCCM_SIZE_HINT_P_ASPECT)==XCB_ICCCM_SIZE_HINT_P_ASPECT ){ hints.min_aspect_num=reply.min_aspect_num; hints.min_aspect_den=reply.min_aspect_den; hints.max_aspect_num=reply.max_aspect_num; hints.max_aspect_den=reply.max_aspect_den;} + //if( (reply.flags&XCB_ICCCM_SIZE_HINT_P_WIN_GRAVITY)==XCB_ICCCM_SIZE_HINT_P_WIN_GRAVITY ){ hints.win_gravity=reply.win_gravity; } + } + } //end of geometry properties + + if(props.contains(NativeWindow::Name)){ + //Put the app/class name here (much more static than the "Title" properties + xcb_get_property_cookie_t cookie = xcb_icccm_get_wm_class_unchecked(QX11Info::connection(), win->id()); + xcb_icccm_get_wm_class_reply_t reply; + if(1 == xcb_icccm_get_wm_class_reply(QX11Info::connection(), cookie, &reply, NULL) ){ + //Returns: "<instance name>::::<class name>" + win->setProperty(NativeWindow::Name, ( QString::fromLocal8Bit(reply.instance_name)+"::::"+QString::fromLocal8Bit(reply.class_name) )); + xcb_icccm_get_wm_class_reply_wipe(&reply); + } + } //end NAME property + + if(props.contains(NativeWindow::Workspace)){ + xcb_get_property_cookie_t cookie = xcb_ewmh_get_wm_desktop_unchecked(&obj->EWMH, win->id()); + uint32_t num = 0; + int wkspace = -1; + if(1==xcb_ewmh_get_wm_desktop_reply(&obj->EWMH, cookie, &num, NULL) ){ + if(num!=0xFFFFFFFF){ wkspace = num; } + }/*else{ + //Error in fetching property (not set?) + // - put it on the current screen + out = WM_Get_Current_Desktop(); + }*/ + win->setProperty(NativeWindow::Workspace, wkspace); + } + if(props.contains(NativeWindow::FrameExtents)){ + //Just assign default values to this - need to automate it later + //win->setProperty(NativeWindow::FrameExtents, QVariant::fromValue<QList<int> >(QList<int>() << 5 << 5 << 5+QFontMetrics(QFont()).height() << 5) ); + } + if(props.contains(NativeWindow::RelatedWindows)){ + WId orig = win->id(); + WId tid = obj->getTransientFor(orig); + QList<WId> list; + while(tid != orig){ + list << tid; + orig = tid; + tid = obj->getTransientFor(orig); + } + win->setProperty(NativeWindow::RelatedWindows, QVariant::fromValue(list)); + } + if(props.contains(NativeWindow::Visible)){ + xcb_get_window_attributes_reply_t *attr = xcb_get_window_attributes_reply(QX11Info::connection(), xcb_get_window_attributes(QX11Info::connection(), win->id()) , NULL); + if(attr != 0){ + win->setProperty(NativeWindow::Visible, attr->map_state == XCB_MAP_STATE_VIEWABLE); + free(attr); + } + } + if(props.contains(NativeWindow::WinTypes)){ + QList< NativeWindow::Type> types; + xcb_get_property_cookie_t cookie = xcb_ewmh_get_wm_window_type_unchecked(&obj->EWMH, win->id()); + xcb_ewmh_get_atoms_reply_t reply; + if(1==xcb_ewmh_get_wm_window_type_reply(&obj->EWMH, cookie, &reply, NULL) ){ + for(unsigned int i=0; i<reply.atoms_len; i++){ + if(reply.atoms[i]==obj->EWMH._NET_WM_WINDOW_TYPE_DESKTOP){ types << NativeWindow::T_DESKTOP; } + else if(reply.atoms[i]==obj->EWMH._NET_WM_WINDOW_TYPE_DOCK){ types << NativeWindow::T_DOCK; } + else if(reply.atoms[i]==obj->EWMH._NET_WM_WINDOW_TYPE_TOOLBAR){ types << NativeWindow::T_TOOLBAR; } + else if(reply.atoms[i]==obj->EWMH._NET_WM_WINDOW_TYPE_MENU){ types << NativeWindow::T_MENU; } + else if(reply.atoms[i]==obj->EWMH._NET_WM_WINDOW_TYPE_UTILITY){ types << NativeWindow::T_UTILITY; } + else if(reply.atoms[i]==obj->EWMH._NET_WM_WINDOW_TYPE_SPLASH){ types << NativeWindow::T_SPLASH; } + else if(reply.atoms[i]==obj->EWMH._NET_WM_WINDOW_TYPE_DIALOG){ types << NativeWindow::T_DIALOG; } + else if(reply.atoms[i]==obj->EWMH._NET_WM_WINDOW_TYPE_DROPDOWN_MENU){ types << NativeWindow::T_DROPDOWN_MENU; } + else if(reply.atoms[i]==obj->EWMH._NET_WM_WINDOW_TYPE_POPUP_MENU){ types << NativeWindow::T_POPUP_MENU; } + else if(reply.atoms[i]==obj->EWMH._NET_WM_WINDOW_TYPE_TOOLTIP){ types << NativeWindow::T_TOOLTIP; } + else if(reply.atoms[i]==obj->EWMH._NET_WM_WINDOW_TYPE_NOTIFICATION){ types << NativeWindow::T_NOTIFICATION; } + else if(reply.atoms[i]==obj->EWMH._NET_WM_WINDOW_TYPE_COMBO){ types << NativeWindow::T_COMBO; } + else if(reply.atoms[i]==obj->EWMH._NET_WM_WINDOW_TYPE_DND){ types << NativeWindow::T_DND; } + else if(reply.atoms[i]==obj->EWMH._NET_WM_WINDOW_TYPE_NORMAL){ types << NativeWindow::T_NORMAL; } + } + } + if(types.isEmpty()){ types << NativeWindow::T_NORMAL; } + win->setProperty(NativeWindow::WinTypes, QVariant::fromValue< QList<NativeWindow::Type> >(types) ); + } +} + +void NativeWindowSystem::ChangeWindowProperties(NativeWindow* win, QList< NativeWindow::Property > props, QList<QVariant> vals){ + if(props.length() == 0 || vals.length()!=props.length() || win ==0 ){ return; } + //qDebug() << "Change Window Properties:" << props << vals; + if(props.contains(NativeWindow::Title)){ + + } + if(props.contains(NativeWindow::ShortTitle)){ + + } + if(props.contains(NativeWindow::Icon)){ + + } + if(props.contains(NativeWindow::Size) || props.contains(NativeWindow::GlobalPos) ){ + /*xcb_configure_window_value_list_t valList; + //valList.x = 0; //Note that this is the relative position - should always be 0,0 relative to the embed widget + //valList.y = 0; + QSize sz = win->property(NativeWindow::Size).toSize(); + if(props.contains(NativeWindow::Size)){ + sz = vals[ props.indexOf(NativeWindow::Size) ] .toSize(); + } + valList.width = sz.width(); + valList.height = sz.height(); + if(props.contains(NativeWindow::GlobalPos)){ + QPoint pt = vals[ props.indexOf(NativeWindow::GlobalPos) ] .toPoint(); + valList.x = pt.x(); + valList.y = pt.y(); + }else{ + valList.x = win->property(NativeWindow::GlobalPos).toPoint().x(); + valList.y = win->property(NativeWindow::GlobalPos).toPoint().y(); + } + uint16_t mask = 0; + mask = mask | XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT | XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y; + //qDebug() << "Configure window Geometry:" << sz; + xcb_configure_window_aux(QX11Info::connection(), win->id(), mask, &valList);*/ + } + if(props.contains(NativeWindow::Name)){ + + } + if(props.contains(NativeWindow::Workspace)){ + int num = vals[ props.indexOf(NativeWindow::Workspace) ].toInt(); + xcb_ewmh_set_wm_desktop(&obj->EWMH, win->id(), (num<0 ? 0xFFFFFFFF : qAbs(num) ) ); + } + if(props.contains(NativeWindow::RelatedWindows)){ + + } + if(props.contains(NativeWindow::Visible)){ + //qDebug() << "Check Window Visibility:" << vals[ props.indexOf(NativeWindow::Visible) ]; + if( vals[ props.indexOf(NativeWindow::Visible) ].toBool() ){ + //qDebug() << " - Map it!"; + xcb_map_window(QX11Info::connection(), win->id()); + }else{ + //qDebug() << " - Unmap it!"; + xcb_unmap_window(QX11Info::connection(), win->id()); + } + } + if(props.contains(NativeWindow::Active)){ + //Only one window can be "Active" at a time - so only do anything if this window wants to be active + if(vals[props.indexOf(NativeWindow::Active)].toBool() ){ + //Lower the currently active window (invisible window) to the bottom of the stack + xcb_window_t cactive; + if( 1 == xcb_ewmh_get_active_window_reply( &obj->EWMH, + xcb_ewmh_get_active_window_unchecked(&obj->EWMH, QX11Info::appScreen()), + &cactive, NULL) ){ + uint32_t val = XCB_STACK_MODE_BELOW; + xcb_configure_window(QX11Info::connection(), cactive, XCB_CONFIG_WINDOW_STACK_MODE, &val); + } + + xcb_ewmh_set_active_window(&obj->EWMH, QX11Info::appScreen(), win->id() ); + //Also send the active window a message to take input focus + xcb_set_input_focus(QX11Info::connection(), XCB_INPUT_FOCUS_PARENT, win->id(), XCB_CURRENT_TIME); + //Send the window a WM_TAKE_FOCUS message +/* xcb_client_message_event_t event; + event.response_type = XCB_CLIENT_MESSAGE; + event.format = 32; + event.window = win->id(); + event.type = obj->ATOMS["WM_PROTOCOLS"]; + event.data.data32[0] = obj->ATOMS["WM_TAKE_FOCUS"]; + event.data.data32[1] = XCB_TIME_CURRENT_TIME; //CurrentTime; + event.data.data32[2] = 0; + event.data.data32[3] = 0; + 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); + xcb_flush(QX11Info::connection()); +*/ + } + } + +} + +// === PUBLIC SLOTS === +//These are the slots which are typically only used by the desktop system itself or the NativeEventFilter +void NativeWindowSystem::RegisterVirtualRoot(WId id){ + //Convert to XCB array + xcb_window_t array[1]; + array[0] = id; + //Set the property + xcb_ewmh_set_virtual_roots(&obj->EWMH, QX11Info::appScreen(), 1, array); + //Now also enable automatic compositing for children of this window + //xcb_composite_redirect_window(QX11Info::connection(), id, XCB_COMPOSITE_REDIRECT_AUTOMATIC); + //xcb_composite_redirect_subwindows(QX11Info::connection(), id, XCB_COMPOSITE_REDIRECT_AUTOMATIC); +} + +void NativeWindowSystem::setRoot_supportedActions(){ +//NET_WM standards (ICCCM implied - no standard way to list those) + xcb_atom_t list[] = {obj->EWMH._NET_WM_NAME, + obj->EWMH._NET_WM_ICON, + obj->EWMH._NET_WM_ICON_NAME, + obj->EWMH._NET_WM_DESKTOP, + /*obj->ATOMS["_NET_WM_WINDOW_OPACITY"],*/ + /*_NET_WINDOW_TYPE (and all the various types - 15 in total*/ + obj->EWMH._NET_WM_WINDOW_TYPE, obj->EWMH._NET_WM_WINDOW_TYPE_DESKTOP, obj->EWMH._NET_WM_WINDOW_TYPE_DOCK, + obj->EWMH._NET_WM_WINDOW_TYPE_TOOLBAR, obj->EWMH._NET_WM_WINDOW_TYPE_MENU, obj->EWMH._NET_WM_WINDOW_TYPE_UTILITY, + obj->EWMH._NET_WM_WINDOW_TYPE_SPLASH, obj->EWMH._NET_WM_WINDOW_TYPE_DIALOG, obj->EWMH._NET_WM_WINDOW_TYPE_NORMAL, + obj->EWMH._NET_WM_WINDOW_TYPE_DROPDOWN_MENU, obj->EWMH._NET_WM_WINDOW_TYPE_POPUP_MENU, obj->EWMH._NET_WM_WINDOW_TYPE_TOOLTIP, + obj->EWMH._NET_WM_WINDOW_TYPE_NOTIFICATION, obj->EWMH._NET_WM_WINDOW_TYPE_COMBO, obj->EWMH._NET_WM_WINDOW_TYPE_DND, + }; + xcb_ewmh_set_supported(&obj->EWMH, QX11Info::appScreen(), 20,list); +} + +void NativeWindowSystem::setRoot_numberOfWorkspaces(QStringList names){ + if(names.isEmpty()){ names << "one"; } + //First set the overall number of workspaces + xcb_ewmh_set_number_of_desktops(&obj->EWMH, QX11Info::appScreen(), names.length()); + //Now set the names for the workspaces + //EWMH LIBRARY BROKEN - appears to be a mismatch in the function header (looking for a single char array, instead of a list of char arrays) + // Ken Moore - 6/27/17 + /* + char *array[ names.length() ]; + for(int i=0; i<names.length(); i++){array[i] = names[i].toUtf8().data(); } //Convert to an array of char arrays + xcb_ewmh_set_desktop_names(&obj->EWMH, QX11Info::appScreen(), names.length(), array); + */ +} + +void NativeWindowSystem::setRoot_currentWorkspace(int num){ + xcb_ewmh_set_current_desktop(&obj->EWMH, QX11Info::appScreen(), num); +} + +void NativeWindowSystem::setRoot_clientList(QList<WId> list, bool stackorder){ + //convert the QList into a generic array + xcb_window_t array[list.length()]; + for(int i=0; i<list.length(); i++){ array[i] = list[i]; } + if(stackorder){ + xcb_ewmh_set_client_list_stacking(&obj->EWMH, QX11Info::appScreen(), list.length(), array); + }else{ + xcb_ewmh_set_client_list(&obj->EWMH, QX11Info::appScreen(), list.length(), array); + } +} + +void NativeWindowSystem::setRoot_desktopGeometry(QRect geom){ + //This one is a combo function + // This will set the "DESKTOP_VIEWPORT" property (point) + // as well as the "DESKTOP_GEOMETRY" property (size) + //Turn the QList into xcb_ewmh_coordinates_t* + xcb_ewmh_coordinates_t array[1]; + array[0].x=geom.x(); array[0].y=geom.y(); + //Now set the property + xcb_ewmh_set_desktop_viewport(&obj->EWMH, QX11Info::appScreen(), 1, array); + xcb_ewmh_set_desktop_geometry(&obj->EWMH, QX11Info::appScreen(), geom.width(), geom.height()); +} + +void NativeWindowSystem::setRoot_desktopWorkarea(QList<QRect> list){ + //Convert to the XCB/EWMH data structures + xcb_ewmh_geometry_t array[list.length()]; + for(int i=0; i<list.length(); i++){ + array[i].x = list[i].x(); array[i].y = list[i].y(); + array[i].width = list[i].width(); array[i].height = list[i].height(); + } + //Now set the property + xcb_ewmh_set_workarea(&obj->EWMH, QX11Info::appScreen(), list.length(), array); +} + +void NativeWindowSystem::setRoot_activeWindow(WId win){ + /*xcb_ewmh_set_active_window(&obj->EWMH, QX11Info::appScreen(), win); + //Also send the active window a message to take input focus + //Send the window a WM_TAKE_FOCUS message + xcb_client_message_event_t event; + event.response_type = XCB_CLIENT_MESSAGE; + event.format = 32; + event.window = win; + event.type = obj->ATOMS["WM_PROTOCOLS"]; + event.data.data32[0] = obj->ATOMS["WM_TAKE_FOCUS"]; + event.data.data32[1] = XCB_TIME_CURRENT_TIME; //CurrentTime; + event.data.data32[2] = 0; + event.data.data32[3] = 0; + event.data.data32[4] = 0; + + xcb_send_event(QX11Info::connection(), 0, win, XCB_EVENT_MASK_STRUCTURE_NOTIFY | XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT, (const char *) &event); + xcb_flush(QX11Info::connection());*/ +} + +int NativeWindowSystem::currentWorkspace(){ + xcb_get_property_cookie_t cookie = xcb_ewmh_get_current_desktop_unchecked(&obj->EWMH, QX11Info::appScreen()); + uint32_t num = 0; + if(1==xcb_ewmh_get_current_desktop_reply(&obj->EWMH, cookie, &num, NULL) ){ + return num; + }else{ + return 0; + } +} + +//NativeWindowEventFilter interactions +void NativeWindowSystem::NewWindowDetected(WId id){ + //Make sure this can be managed first + if(findWindow(id, false) != 0){ findWindow(id,false)->setProperty(NativeWindow::Visible, true, true); return; } //already managed + xcb_get_window_attributes_cookie_t cookie = xcb_get_window_attributes(QX11Info::connection(), id); + xcb_get_window_attributes_reply_t *attr = xcb_get_window_attributes_reply(QX11Info::connection(), cookie, NULL); + if(attr == 0){ return; } //could not get attributes of window + if(attr->override_redirect){ free(attr); return; } //window has override redirect set (do not manage) + free(attr); + //Now go ahead and create/populate the container for this window + NativeWindow *win = new NativeWindow(id); + //Register for events from this window + registerClientEvents(win->id()); + NWindows << win; + UpdateWindowProperties(win, NativeWindow::allProperties()); + qDebug() << "New Window [& associated ID's]:" << win->id() << win->property(NativeWindow::Name).toString(); + //Now setup the connections with this window + connect(win, SIGNAL(RequestClose(WId)), this, SLOT(RequestClose(WId)) ); + connect(win, SIGNAL(RequestKill(WId)), this, SLOT(RequestKill(WId)) ); + connect(win, SIGNAL(RequestPing(WId)), this, SLOT(RequestPing(WId)) ); + connect(win, SIGNAL(RequestReparent(WId, WId, QPoint)), this, SLOT(RequestReparent(WId, WId, QPoint)) ); + connect(win, SIGNAL(RequestPropertiesChange(WId, QList<NativeWindow::Property>, QList<QVariant>)), this, SLOT(RequestPropertiesChange(WId, QList<NativeWindow::Property>, QList<QVariant>)) ); + emit NewWindowAvailable(win); +} + +void NativeWindowSystem::NewTrayWindowDetected(WId id){ + //Make sure this can be managed first + if(findTrayWindow(id) != 0){ return; } //already managed + xcb_get_window_attributes_cookie_t cookie = xcb_get_window_attributes(QX11Info::connection(), id); + xcb_get_window_attributes_reply_t *attr = xcb_get_window_attributes_reply(QX11Info::connection(), cookie, NULL); + if(attr == 0){ return; } //could not get attributes of window + if(attr->override_redirect){ free(attr); return; } //window has override redirect set (do not manage) + free(attr); + //Register for events from this window + #define TRAY_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) + + uint32_t value_list[1] = {TRAY_WIN_EVENT_MASK}; + xcb_change_window_attributes(QX11Info::connection(), id, XCB_CW_EVENT_MASK, value_list); + //Now go ahead and create/populate the container for this window + NativeWindow *win = new NativeWindow(id); + TWindows << win; + UpdateWindowProperties(win, NativeWindow::allProperties()); + emit NewTrayWindowAvailable(win); +} + +void NativeWindowSystem::WindowCloseDetected(WId id){ + NativeWindow *win = findWindow(id, false); + //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!!!"; + //win->deleteLater(); + }else{ + win = findTrayWindow(id); + if(win!=0){ + TWindows.removeAll(win); + win->emit WindowClosed(id); + win->deleteLater(); + } + } + //qDebug() << " - Now:" << NWindows.length(); +} + +void NativeWindowSystem::WindowPropertyChanged(WId id, NativeWindow::Property prop){ + //NOTE: This is triggered by the NativeEventFilter - not by changes to the NativeWindow objects themselves + NativeWindow *win = findWindow(id, prop!=NativeWindow::Visible); + if(win==0){ win = findTrayWindow(id); } + if(win!=0){ + UpdateWindowProperties(win, QList<NativeWindow::Property>() << prop); + }else if(prop != 0){ + //Could not find the window for a specific property with an undefined value + // - update this property for all the windows just in case + for(int i=0; i<NWindows.length(); i++){ + UpdateWindowProperties( NWindows[i], QList<NativeWindow::Property>() << prop); + } + } +} + +void NativeWindowSystem::WindowPropertiesChanged(WId id, QList<NativeWindow::Property> props){ + //NOTE: This is triggered by the NativeEventFilter - not by changes to the NativeWindow objects themselves + NativeWindow *win = findWindow(id); + if(win==0){ win = findTrayWindow(id); } + if(win!=0){ + UpdateWindowProperties(win, props); + }else{ + //Could not find the window for a specific property with an undefined value + // - update this property for all the windows just in case + for(int i=0; i<NWindows.length(); i++){ + UpdateWindowProperties( NWindows[i], props); + } + } +} + +void NativeWindowSystem::WindowPropertyChanged(WId id, NativeWindow::Property prop, QVariant val){ + NativeWindow *win = findWindow(id,prop!=NativeWindow::Visible); + if(win==0){ win = findTrayWindow(id); } + if(win!=0){ + win->setProperty(prop, val); + } +} + +void NativeWindowSystem::WindowPropertiesChanged(WId id, QList<NativeWindow::Property> props, QList<QVariant> vals){ + NativeWindow *win = findWindow(id); + if(win==0){ win = findTrayWindow(id); } + if(win!=0){ + for(int i=0; i<props.length() && i<vals.length(); i++){ win->setProperty(props[i], vals[i]); } + } +} + +void NativeWindowSystem::RequestPropertyChange(WId id, NativeWindow::Property prop, QVariant val){ + //This is just a simplified version of the multiple-property function + RequestPropertiesChange(id, QList<NativeWindow::Property>() << prop, QList<QVariant>() << val); +} + +void NativeWindowSystem::RequestPropertiesChange(WId win, QList<NativeWindow::Property> props, QList<QVariant> vals){ + //Find the window object associated with this id + bool istraywin = false; //just in case we care later if it is a tray window or a regular window + NativeWindow *WIN = findWindow(win); + if(WIN==0){ istraywin = true; WIN = findTrayWindow(win); } + if(WIN==0){ return; } //invalid window ID - no longer available + //Now make any changes as needed + ChangeWindowProperties(WIN, props, vals); +} + +void NativeWindowSystem::GotPong(WId id){ + if(waitingForPong.contains(id)){ + waitingForPong.remove(id); + } + if(waitingForPong.isEmpty() && pingTimer!=0){ pingTimer->stop(); } +} + +void NativeWindowSystem::NewKeyPress(int keycode, WId win){ + emit NewInputEvent(); + if(screenLocked){ return; } + Qt::Key key = KeycodeToQt(keycode); + if(key!=Qt::Key_unknown){ emit KeyPressDetected(win, key); } +} + +void NativeWindowSystem::NewKeyRelease(int keycode, WId win){ + emit NewInputEvent(); + if(screenLocked){ return; } + Qt::Key key = KeycodeToQt(keycode); + if(key!=Qt::Key_unknown){ emit KeyReleaseDetected(win, key); } +} + +void NativeWindowSystem::NewMousePress(int buttoncode, WId win){ + emit NewInputEvent(); + if(screenLocked){ return; } + emit MousePressDetected(win, MouseToQt(buttoncode)); +} + +void NativeWindowSystem::NewMouseRelease(int buttoncode, WId win){ + emit NewInputEvent(); + if(screenLocked){ return; } + emit MouseReleaseDetected(win, MouseToQt(buttoncode)); +} + +void NativeWindowSystem::CheckDamageID(WId win){ + for(int i=0; i<NWindows.length(); i++){ + if(NWindows[i]->damageId() == win || NWindows[i]->id() == win || NWindows[i]->frameId()==win){ + NWindows[i]->emit VisualChanged(); + //qDebug() << "Got DAMAGE Event"; + return; + } + } + NativeWindow *WIN = findTrayWindow(win); + if(WIN!=0){ + UpdateWindowProperties(WIN, QList<NativeWindow::Property>() << NativeWindow::Icon); + } +} + +// === PRIVATE SLOTS === +//These are the slots which are built-in and automatically connected when a new NativeWindow is created + +void NativeWindowSystem::RequestClose(WId win){ + //Send the window a WM_DELETE_WINDOW message + xcb_client_message_event_t event; + event.response_type = XCB_CLIENT_MESSAGE; + event.format = 32; + event.window = win; + event.type = obj->ATOMS.value("WM_PROTOCOLS"); + event.data.data32[0] = obj->ATOMS.value("WM_DELETE_WINDOW"); + event.data.data32[1] = XCB_TIME_CURRENT_TIME; //CurrentTime; + event.data.data32[2] = 0; + event.data.data32[3] = 0; + event.data.data32[4] = 0; + + xcb_send_event(QX11Info::connection(), 0, win, XCB_EVENT_MASK_STRUCTURE_NOTIFY | XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT, (const char *) &event); + xcb_flush(QX11Info::connection()); +} + +void NativeWindowSystem::RequestKill(WId win){ + xcb_kill_client(QX11Info::connection(), win); +} + +void NativeWindowSystem::RequestPing(WId win){ + waitingForPong.insert(win, QDateTime::currentDateTime().addSecs(5) ); + xcb_ewmh_send_wm_ping(&obj->EWMH, win, XCB_CURRENT_TIME); + if(pingTimer==0){ + pingTimer = new QTimer(this); + pingTimer->setInterval(2000); //2seconds + connect(pingTimer, SIGNAL(timeout()), this, SLOT(checkPings()) ); + } + pingTimer->start(); +} + +void NativeWindowSystem::RequestReparent(WId win, WId container, QPoint relorigin){ + NativeWindow *WIN = findWindow(win); + if(WIN==0){ return; } //could not find corresponding window structure +//Reparent the window into the container + xcb_reparent_window(QX11Info::connection(), win, container, relorigin.x(), relorigin.y()); + //xcb_map_window(QX11Info::connection(), win); + + //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; + 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] = container; //WID of the container + event.data.data32[4] = 0; + + xcb_send_event(QX11Info::connection(), 0, win, XCB_EVENT_MASK_STRUCTURE_NOTIFY | XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT, (const char *) &event); + + //Now setup any redirects and return + //this->SelectInput(win, true); //Notify of structure changes + registerClientEvents(win); + //xcb_composite_redirect_window(QX11Info::connection(), win, XCB_COMPOSITE_REDIRECT_MANUAL); //XCB_COMPOSITE_REDIRECT_[MANUAL/AUTOMATIC]); + + //Now map the window (will be a transparent child of the container) + xcb_map_window(QX11Info::connection(), win); + xcb_map_window(QX11Info::connection(), container); + //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, 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, XDamageReportRawRectangles); + WIN->addDamageID( (uint) dmgID); //save this for later + //qDebug() << " - Done"; + //return ( (uint) dmgID ); +} +/* + xcb_reparent_window(QX11Info::connection(), client, parent, relorigin.x(), relorigin.y()); + + //Now ensure that we still get event for these windows + registerClientEvents(client); //make sure we re-do this after reparenting + registerClientEvents(parent); + xcb_map_window(QX11Info::connection(), parent); +}*/ diff --git a/src-qt5/src-cpp/NativeWindowSystem.h b/src-qt5/src-cpp/NativeWindowSystem.h new file mode 100644 index 00000000..b67ecc94 --- /dev/null +++ b/src-qt5/src-cpp/NativeWindowSystem.h @@ -0,0 +1,139 @@ +//=========================================== +// 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 Qt5/Lumina wrapper around native graphics system calls +// It is primarily designed around the creation/modification of instances of +// the "NativeWindow" class for passing information around +//=========================================== +#ifndef _LUMINA_NATIVE_WINDOW_SYSTEM_H +#define _LUMINA_NATIVE_WINDOW_SYSTEM_H + +#include "NativeWindow.h" +#include <QDateTime> +#include <QTimer> +#include <QDebug> + +class NativeWindowSystem : public QObject{ + Q_OBJECT +private: + QList<NativeWindow*> NWindows; + QList<NativeWindow*> TWindows; + + //Simplifications to find an already-created window object + NativeWindow* findWindow(WId id, bool checkRelated = true); + + NativeWindow* findTrayWindow(WId id); + + //Now define a simple private_objects class so that each implementation + // has a storage container for defining/placing private objects as needed + class p_objects; + p_objects* obj; + + //Internal timers/variables for managing pings + QTimer *pingTimer; + QHash<WId, QDateTime> waitingForPong; + void checkPings(){ + QDateTime cur = QDateTime::currentDateTime(); + QList<WId> waiting = waitingForPong.keys(); + for(int i=0; i<waiting.length(); i++){ + if(waitingForPong.value(waiting[i]) < cur){ + waitingForPong.remove(waiting[i]); //Timeout on this window + if(waitingForPong.isEmpty() && pingTimer!=0){ pingTimer->stop(); } + NativeWindow *win = findWindow(waiting[i]); + if(win==0){ win = findTrayWindow(waiting[i]); } + if(win!=0){ win->emit WindowNotResponding(waiting[i]); } + } + } + } + + // Since some properties may be easier to update in bulk + // let the native system interaction do them in whatever logical groups are best + void UpdateWindowProperties(NativeWindow* win, QList< NativeWindow::Property > props); + void ChangeWindowProperties(NativeWindow* win, QList< NativeWindow::Property > props, QList<QVariant> vals); + + //Generic private variables + bool screenLocked; + +public: + //enum Property{ None, CurrentWorkspace, Workspaces, VirtualRoots, WorkAreas }; + enum MouseButton{NoButton, LeftButton, RightButton, MidButton, BackButton, ForwardButton, TaskButton, WheelUp, WheelDown, WheelLeft, WheelRight}; + + NativeWindowSystem(); + ~NativeWindowSystem(); + + //Overarching start/stop functions + bool start(); + void stop(); + + //General-purpose listing functions + QList<NativeWindow*> currentWindows(){ return NWindows; } + QList<NativeWindow*> currentTrayWindows(){ return TWindows; } + + //Small simplification functions + static Qt::Key KeycodeToQt(int keycode); + static NativeWindowSystem::MouseButton MouseToQt(int button); + +public slots: + //These are the slots which are typically only used by the desktop system itself or the NativeWindowEventFilter + + //This is called by the lock screen to keep the NWS aware of the current status + // it is **NOT** the function to call for the user to actually lock the session (that is in the screensaver/lockscreen class) + void ScreenLockChanged(bool lock){ + screenLocked = lock; + } + + //Root Window property registrations + void RegisterVirtualRoot(WId); + void setRoot_supportedActions(); + void setRoot_numberOfWorkspaces(QStringList names); + void setRoot_currentWorkspace(int); + void setRoot_clientList(QList<WId>, bool stackorder = false); + void setRoot_desktopGeometry(QRect); + void setRoot_desktopWorkarea(QList<QRect>); + void setRoot_activeWindow(WId); + + // - Workspaces + int currentWorkspace(); + //void GoToWorkspace(int); + + + //NativeWindowEventFilter interactions + void NewWindowDetected(WId); //will automatically create the new NativeWindow object + void NewTrayWindowDetected(WId); //will automatically create the new NativeWindow object + void WindowCloseDetected(WId); //will update the lists and make changes if needed + void WindowPropertyChanged(WId, NativeWindow::Property); //will rescan the window and update the object as needed + void WindowPropertiesChanged(WId, QList<NativeWindow::Property>); + void WindowPropertyChanged(WId, NativeWindow::Property, QVariant); //will save that property/value to the right object + void WindowPropertiesChanged(WId, QList<NativeWindow::Property>, QList<QVariant>); + void RequestPropertyChange(WId, NativeWindow::Property, QVariant); + void RequestPropertiesChange(WId, QList<NativeWindow::Property>, QList<QVariant>); + void GotPong(WId); + + void NewKeyPress(int keycode, WId win = 0); + void NewKeyRelease(int keycode, WId win = 0); + void NewMousePress(int buttoncode, WId win = 0); + void NewMouseRelease(int buttoncode, WId win = 0); + void CheckDamageID(WId); + +private slots: + //These are the slots which are built-in and automatically connected when a new NativeWindow is created + void RequestClose(WId); + void RequestKill(WId); + void RequestPing(WId); + void RequestReparent(WId, WId, QPoint); //client, parent, relative origin point in parent + +signals: + void NewWindowAvailable(NativeWindow*); + void NewTrayWindowAvailable(NativeWindow*); + void NewInputEvent(); //a mouse or keypress was detected (lock-state independent); + void KeyPressDetected(WId, Qt::Key); //only emitted if lockstate = false + void KeyReleaseDetected(WId, Qt::Key); //only emitted if lockstate = false + void MousePressDetected(WId, NativeWindowSystem::MouseButton); //only emitted if lockstate = false + void MouseReleaseDetected(WId, NativeWindowSystem::MouseButton); //only emitted if lockstate = false + +}; + +#endif diff --git a/src-qt5/src-cpp/framework-OSInterface-template.cpp b/src-qt5/src-cpp/framework-OSInterface-template.cpp new file mode 100644 index 00000000..972e02e0 --- /dev/null +++ b/src-qt5/src-cpp/framework-OSInterface-template.cpp @@ -0,0 +1,150 @@ +//=========================================== +// Lumina desktop source code +// Copyright (c) 2017, Ken Moore +// Available under the 3-clause BSD license +// See the LICENSE file for full details +//=========================================== +#include <framework-OSInterface.h> +#include <QNetworkConfiguration> +#include <QNetworkInterface> + +//Start/stop interface watchers/notifications +void OSInterface::start(){ + setupMediaWatcher(); //will create/connect the filesystem watcher automatically + setupNetworkManager(); +} + +void OSInterface::stop(){ + if(isRunning()){ + watcher->deleteLater(); + watcher = 0; + } +} + +bool OSInterface::isRunning(){ return (watcher!=0); } //status of the object - whether it has been started yet + +// = Battery = +bool OSInterface::batteryAvailable(){ return false; } +float OSInterface::batteryCharge(){ return -1; } +bool OSInterface::batteryCharging(){ return false; } +double OSInterface::batterySecondsLeft(){ return -1; } + +// = Volume = +bool OSInterface::volumeAvailable(){ return false; } +int OSInterface::volume(){ return -1; } +void OSInterface::setVolume(int){} + +// = Network Information = +bool OSInterface::networkAvailable(){ + if(INFO.contains("netaccess/available")){ return INFO.value("netaccess/available").toBool(); } + return false; +} + +QString OSInterface::networkType(){ + if(INFO.contains("netaccess/type")){ return INFO.value("netaccess/type").toString(); } //"wifi", "wired", or "cell" + return ""; +} + +float OSInterface::networkStrength(){ return -1; } //percentage. ("wired" type should always be 100%) + +QString OSInterface::networkHostname(){ + return QHostInfo::localHostName(); +} + +QHostAddress OSInterface::networkAddress(){ + QString addr; + if(INFO.contains("netaccess/address")){ addr = INFO.value("netaccess/address").toString(); } + return QHostAddress(addr); +} +// = Network Modification = + +// = Media Shortcuts = +QStringList OSInterface::mediaDirectories(){ return QStringList() << "/media"; } //directory where XDG shortcuts are placed for interacting with media (local/remote) +QStringList OSInterface::mediaShortcuts(){ return autoHandledMediaFiles(); } //List of currently-available XDG shortcut file paths + +// = Updates = +bool OSInterface::updatesAvailable(){ return false; } +QString OSInterface::updateDetails(){ return QString(); } //Information about any available updates +bool OSInterface::updatesRunning(){ return false; } +QString OSInterface::updateLog(){ return QString(); } //Information about any currently-running update +bool OSInterface::updatesFinished(){ return false; } +QString OSInterface::updateResults(){ return QString(); } //Information about any finished update +void OSInterface::startUpdates(){} +bool OSInterface::updateOnlyOnReboot(){ return false; } //Should the startUpdates function be called only when rebooting the system? +QDateTime OSInterface::lastUpdate(){ return QDateTime(); } //The date/time of the previous updates +QString OSInterface::lastUpdateResults(){ return QString(); } //Information about the previously-finished update + +// = System Power = +bool OSInterface::canReboot(){ return false; } +void OSInterface::startReboot(){} +bool OSInterface::canShutdown(){ return false; } +void OSInterface::startShutdown(){} +bool OSInterface::canSuspend(){ return false; } +void OSInterface::startSuspend(){} + +// = Screen Brightness = +int OSInterface::brightness(){ return -1; } //percentage: 0-100 with -1 for errors +void OSInterface::setBrightness(int){} + +// = System Status Monitoring +QList<int> OSInterface::cpuPercentage(){ return QList<int>(); } // (one per CPU) percentage: 0-100 with empty list for errors +QStringList OSInterface::cpuTemperatures(){ return QStringList(); } // (one per CPU) Temperature of CPU ("50C" for example) +int OSInterface::memoryUsedPercentage(){ return -1; } //percentage: 0-100 with -1 for errors +QString OSInterface::memoryTotal(){ return QString(); } //human-readable form - does not tend to change within a session +QStringList OSInterface::diskIO(){ return QStringList(); } //Returns list of current read/write stats for each device +int OSInterface::fileSystemPercentage(QString dir){ return -1; } //percentage of capacity used: 0-100 with -1 for errors +QString OSInterface::fileSystemCapacity(QString dir){ return QString(); } //human-readable form - total capacity + +// = OS-Specific Utilities = +bool OSInterface::hasControlPanel(){ return false; } +QString OSInterface::controlPanelShortcut(){ return QString(); } //relative *.desktop shortcut name (Example: "some_utility.desktop") +bool OSInterface::hasAudioMixer(){ return false; } +QString OSInterface::audioMixerShortcut(){ return QString(); } //relative *.desktop shortcut name (Example: "some_utility.desktop") +bool OSInterface::hasAppStore(){ return false; } +QString OSInterface::appStoreShortcut(){ return QString(); } //relative *.desktop shortcut name (Example: "some_utility.desktop") + +//FileSystemWatcher slots +void OSInterface::watcherFileChanged(QString){} +void OSInterface::watcherDirChanged(QString dir){ + if(handleMediaDirChange(dir)){ return; } +} + +//IO Device slots +void OSInterface::iodeviceReadyRead(){} +void OSInterface::iodeviceAboutToClose(){} + +//NetworkAccessManager slots +void OSInterface::netAccessChanged(QNetworkAccessManager::NetworkAccessibility stat){ + INFO.insert("netaccess/available", stat== QNetworkAccessManager::Accessible); + //Update all the other network status info at the same time + QNetworkConfiguration active = netman->activeConfiguration(); + //Type of connection + QString type; + switch(active.bearerTypeFamily()){ + case QNetworkConfiguration::BearerEthernet: type="wired"; break; + case QNetworkConfiguration::BearerWLAN: type="wifi"; break; + case QNetworkConfiguration::Bearer2G: type="cell-2G"; break; + case QNetworkConfiguration::Bearer3G: type="cell-3G"; break; + case QNetworkConfiguration::Bearer4G: type="cell-4G"; break; + default: type=""; + } + INFO.insert("netaccess/type", type); + qDebug() << "Detected Device Status:" << active.identifier() << type << stat; + QNetworkInterface iface = QNetworkInterface::interfaceFromName(active.name()); + qDebug() << " - Configuration: Name:" << active.name() << active.bearerTypeName() << active.identifier(); + qDebug() << " - Interface: MAC Address:" << iface.hardwareAddress() << "Name:" << iface.name() << iface.humanReadableName() << iface.isValid(); + QList<QNetworkAddressEntry> addressList = iface.addressEntries(); + QStringList address; + //NOTE: There are often 2 addresses, IPv4 and IPv6 + for(int i=0; i<addressList.length(); i++){ + address << addressList[i].ip().toString(); + } + qDebug() << " - IP Address:" << address; + qDebug() << " - Hostname:" << networkHostname(); + INFO.insert("netaccess/address", address.join(", ")); + emit networkStatusChanged(); +} + +void OSInterface::netRequestFinished(QNetworkReply*){} +void OSInterface::netSslErrors(QNetworkReply*, const QList<QSslError>&){} +void OSInterface::timerUpdate(){} diff --git a/src-qt5/src-cpp/framework-OSInterface.h b/src-qt5/src-cpp/framework-OSInterface.h new file mode 100644 index 00000000..a173ad5a --- /dev/null +++ b/src-qt5/src-cpp/framework-OSInterface.h @@ -0,0 +1,190 @@ +//=========================================== +// Lumina desktop source code +// Copyright (c) 2017, Ken Moore +// Available under the 3-clause BSD license +// See the LICENSE file for full details +//=========================================== +// This is the main interface for any OS-specific system calls +// To port Lumina to a different operating system, just create a file +// called "OSInterface-<Operating System>.cpp" +//=========================================== +#ifndef _LUMINA_LIBRARY_OS_INTERFACE_H +#define _LUMINA_LIBRARY_OS_INTERFACE_H + +#include <QString> +#include <QStringList> +#include <QList> +#include <QObject> +#include <QVariant> +#include <QHash> +#include <QTimer> + +#include <QIODevice> +#include <QFileSystemWatcher> +#include <QNetworkAccessManager> +#include <QNetworkReply> +#include <QSslError> +#include <QHostInfo> +#include <QHostAddress> + +class OSInterface : public QObject{ + Q_OBJECT + // == QML ACCESSIBLE PROPERTIES == + //Battery + Q_PROPERTY( float batteryCharge READ batteryCharge NOTIFY batteryChargeChanged) + Q_PROPERTY( bool batteryCharging READ batteryCharging NOTIFY batteryChargingChanged) + Q_PROPERTY( double batterySecondsLeft READ batterySecondsLeft NOTIFY batterySecondsLeftChanged) + //Volume + Q_PROPERTY( int volume READ volume WRITE setVolume NOTIFY volumeChanged) + //Network + Q_PROPERTY( bool networkAvailable READ networkAvailable NOTIFY networkStatusChanged) + Q_PROPERTY( QString networkType READ networkType NOTIFY networkStatusChanged) + Q_PROPERTY( float networkStrength READ networkStrength NOTIFY networkStatusChanged) + Q_PROPERTY( QString networkHostname READ networkHostname NOTIFY networkStatusChanged) + Q_PROPERTY( QHostAddress networkAddress READ networkAddress NOTIFY networkStatusChanged) + //Media + Q_PROPERTY( QStringList mediaShortcuts READ mediaShortcuts NOTIFY mediaShortcutsChanged) + //Updates + Q_PROPERTY( bool updatesAvailable READ updatesAvailable NOTIFY updateStatusChanged) + Q_PROPERTY( bool updatesRunning READ updatesRunning NOTIFY updateStatusChanged) + Q_PROPERTY( bool updatesFinished READ updatesFinished NOTIFY updateStatusChanged) + //Power options + Q_PROPERTY( bool canReboot READ canReboot NOTIFY powerAvailableChanged) + Q_PROPERTY( bool canShutdown READ canShutdown NOTIFY powerAvailableChanged) + Q_PROPERTY( bool canSuspend READ canSuspend NOTIFY powerAvailableChanged) + //Brightness + Q_PROPERTY( int brightness READ brightness WRITE setBrightness NOTIFY brightnessChanged) + +public: + // ================ + // SEMI-VIRTUAL FUNCTIONS - NEED TO BE DEFINED IN THE OS-SPECIFIC FILES + // ================ + //Start/stop interface watchers/notifications + void start(); + void stop(); + bool isRunning(); //status of the object - whether it has been started yet + + // = Battery = + Q_INVOKABLE bool batteryAvailable(); + Q_INVOKABLE float batteryCharge(); + Q_INVOKABLE bool batteryCharging(); + Q_INVOKABLE double batterySecondsLeft(); + // = Volume = + Q_INVOKABLE bool volumeAvailable(); + Q_INVOKABLE int volume(); + Q_INVOKABLE void setVolume(int); + // = Network Information = + Q_INVOKABLE bool networkAvailable(); + Q_INVOKABLE QString networkType(); //"wifi", "wired", "cell", "cell-2G", "cell-3G", "cell-4G" + Q_INVOKABLE float networkStrength(); //percentage. ("wired" type should always be 100%) + Q_INVOKABLE QString networkHostname(); + Q_INVOKABLE QHostAddress networkAddress(); + // = Network Modification = + + // = Media Shortcuts = + Q_INVOKABLE QStringList mediaDirectories(); //directory where XDG shortcuts are placed for interacting with media (local/remote) + Q_INVOKABLE QStringList mediaShortcuts(); //List of currently-available XDG shortcut file paths + // = Updates = + Q_INVOKABLE bool updatesAvailable(); + Q_INVOKABLE QString updateDetails(); //Information about any available updates + Q_INVOKABLE bool updatesRunning(); + Q_INVOKABLE QString updateLog(); //Information about any currently-running update + Q_INVOKABLE bool updatesFinished(); + Q_INVOKABLE QString updateResults(); //Information about any finished update + Q_INVOKABLE void startUpdates(); + Q_INVOKABLE bool updateOnlyOnReboot(); //Should the startUpdates function be called only when rebooting the system? + Q_INVOKABLE QDateTime lastUpdate(); //The date/time of the previous updates + Q_INVOKABLE QString lastUpdateResults(); //Information about the previously-finished update + // = System Power = + Q_INVOKABLE bool canReboot(); + Q_INVOKABLE void startReboot(); + Q_INVOKABLE bool canShutdown(); + Q_INVOKABLE void startShutdown(); + Q_INVOKABLE bool canSuspend(); + Q_INVOKABLE void startSuspend(); + // = Screen Brightness = + Q_INVOKABLE int brightness(); //percentage: 0-100 with -1 for errors + Q_INVOKABLE void setBrightness(int); + // = System Status Monitoring + Q_INVOKABLE QList<int> cpuPercentage(); // (one per CPU) percentage: 0-100 with -1 for errors + Q_INVOKABLE QStringList cpuTemperatures(); // (one per CPU) Temperature of CPU ("50C" for example) + Q_INVOKABLE int memoryUsedPercentage(); //percentage: 0-100 with -1 for errors + Q_INVOKABLE QString memoryTotal(); //human-readable form - does not tend to change within a session + Q_INVOKABLE QStringList diskIO(); //Returns list of current read/write stats for each device + Q_INVOKABLE int fileSystemPercentage(QString dir); //percentage of capacity used: 0-100 with -1 for errors + Q_INVOKABLE QString fileSystemCapacity(QString dir); //human-readable form - total capacity + // = OS-Specific Utilities = + Q_INVOKABLE bool hasControlPanel(); + Q_INVOKABLE QString controlPanelShortcut(); //relative *.desktop shortcut name (Example: "some_utility.desktop") + Q_INVOKABLE bool hasAudioMixer(); + Q_INVOKABLE QString audioMixerShortcut(); //relative *.desktop shortcut name (Example: "some_utility.desktop") + Q_INVOKABLE bool hasAppStore(); + Q_INVOKABLE QString appStoreShortcut(); //relative *.desktop shortcut name (Example: "some_utility.desktop") + +private slots: + // ================ + // SEMI-VIRTUAL FUNCTIONS - NEED TO BE DEFINED IN THE OS-SPECIFIC FILES + // ================ + //FileSystemWatcher slots + void watcherFileChanged(QString); + void watcherDirChanged(QString); + //IO Device slots + void iodeviceReadyRead(); + void iodeviceAboutToClose(); + //NetworkAccessManager slots + void netAccessChanged(QNetworkAccessManager::NetworkAccessibility); + void netRequestFinished(QNetworkReply*); + void netSslErrors(QNetworkReply*, const QList<QSslError>&); + //Timer slots + void timerUpdate(); + +signals: + void batteryChargeChanged(); + void batteryChargingChanged(); + void batterySecondsLeftChanged(); + void volumeChanged(); + void networkStatusChanged(); + void mediaShortcutsChanged(); + void updateStatusChanged(); + void powerAvailableChanged(); + void brightnessChanged(); + +private: + //Internal persistant data storage, OS-specific usage implementation + QHash< QString, QVariant> INFO; + + // ============ + // Internal possibilities for watching the system (OS-Specific usage/implementation) + // ============ + //File System Watcher + QFileSystemWatcher *watcher; + //IO Device (QLocalSocket, QTcpConnection, QFile, etc) + QIODevice *iodevice; + //Network Access Manager (check network connectivity, etc) + QNetworkAccessManager *netman; + //Timer for regular probes/updates + QTimer *timer; + + // Internal implifications for connecting the various watcher objects to their respective slots + // (OS-agnostic - defined in the "OSInterface_private.cpp" file) + void connectWatcher(); //setup the internal connections *only* + void connectIodevice(); //setup the internal connections *only* + void connectNetman(); //setup the internal connections *only* + void connectTimer(); //setup the internal connections *only* + + // External Media Management (if system uses *.desktop shortcuts only) + void setupMediaWatcher(); + bool handleMediaDirChange(QString dir); //returns true if directory was handled + QStringList autoHandledMediaFiles(); + + // Qt-based NetworkAccessManager usage + void setupNetworkManager(); + +public: + OSInterface(QObject *parent = 0); + ~OSInterface(); + + static OSInterface* instance(); //Get the currently-active instance of this class (or make a new one) + +}; +#endif diff --git a/src-qt5/src-cpp/framework-OSInterface.pri b/src-qt5/src-cpp/framework-OSInterface.pri new file mode 100644 index 00000000..be705e44 --- /dev/null +++ b/src-qt5/src-cpp/framework-OSInterface.pri @@ -0,0 +1,9 @@ +QT *= core network + +HEADERS *= $${PWD}/framework-OSInterface.h +SOURCES *= $${PWD}/framework-OSInterface_private.cpp + +_os=template +SOURCES *= $${PWD}/framework-OSInterface-$${_os}.cpp + +INCLUDEPATH *= $${PWD} diff --git a/src-qt5/src-cpp/framework-OSInterface_private.cpp b/src-qt5/src-cpp/framework-OSInterface_private.cpp new file mode 100644 index 00000000..d15d9be8 --- /dev/null +++ b/src-qt5/src-cpp/framework-OSInterface_private.cpp @@ -0,0 +1,110 @@ +//=========================================== +// Lumina desktop source code +// Copyright (c) 2017, Ken Moore +// Available under the 3-clause BSD license +// See the LICENSE file for full details +//=========================================== +// Internal, OS-agnostic functionality for managing the object itself +//=========================================== +#include <framework-OSInterface.h> +#include <QFile> +#include <QDir> +#include <QVariant> + +OSInterface::OSInterface(QObject *parent) : QObject(parent){ + watcher = 0; + iodevice = 0; + netman = 0; +} + +OSInterface::~OSInterface(){ + if(watcher!=0){ + QStringList paths; paths << watcher->files() << watcher->directories(); + if(!paths.isEmpty()){ watcher->removePaths(paths); } + watcher->deleteLater(); + } + if(iodevice!=0){ + if(iodevice->isOpen()){ iodevice->close(); } + iodevice->deleteLater(); + } + if(netman!=0){ + netman->deleteLater(); + } +} + +OSInterface* OSInterface::instance(){ + static OSInterface* m_os_object = 0; + if(m_os_object==0){ + m_os_object = new OSInterface(); + } + return m_os_object; +} + +void OSInterface::connectWatcher(){ + if(watcher==0){ return; } + connect(watcher, SIGNAL(fileChanged(QString)), this, SLOT(watcherFileChanged(QString)) ); + connect(watcher, SIGNAL(directoryChanged(QString)), this, SLOT(watcherDirChanged(QString)) ); +} + +void OSInterface::connectIodevice(){ + if(iodevice==0){ return; } + connect(iodevice, SIGNAL(readyRead()), this, SLOT(iodeviceReadyRead()) ); +} + +void OSInterface::connectNetman(){ + if(netman==0){ return; } + connect(netman, SIGNAL(networkAccessibleChanged(QNetworkAccessManager::NetworkAccessibility)), this, SLOT(netAccessChanged(QNetworkAccessManager::NetworkAccessibility)) ); + connect(netman, SIGNAL(finished(QNetworkReply*)), this, SLOT(netRequestFinished(QNetworkReply*)) ); + connect(netman, SIGNAL(sslErrors(QNetworkReply*, const QList<QSslError>&)), this, SLOT(netSslErrors(QNetworkReply*, const QList<QSslError>&)) ); +} + +void OSInterface::connectTimer(){ + if(timer==0){ return; } + connect(timer, SIGNAL(timeout()), this, SLOT(timerUpdate()) ); +} + +// External Media Management (if system uses *.desktop shortcuts) +void OSInterface::setupMediaWatcher(){ + //Create/connect the watcher if needed + if(watcher == 0){ watcher = new QFileSystemWatcher(); connectWatcher(); } + QStringList dirs = this->mediaDirectories(); + if(dirs.isEmpty()){ return; } //nothing to do + //Make sure each directory is scanned **right now** (if it exists) + for(int i=0; i<dirs.length(); i++){ + if(QFile::exists(dirs[i])){ + handleMediaDirChange(dirs[i]); + } + } +} + +bool OSInterface::handleMediaDirChange(QString dir){ //returns true if directory was handled + if( !this->mediaDirectories().contains(dir) ){ return false; } //not a media directory + QDir qdir(dir); + QStringList files = qdir.entryList(QStringList() << "*.desktop", QDir::Files, QDir::Name); + for(int i=0; i<files.length(); i++){ files[i] = qdir.absoluteFilePath(files[i]); } + QString key = "media_files/"+dir; + if(files.isEmpty() && INFO.contains(key)){ INFO.remove(key); emit mediaShortcutsChanged(); } //no files for this directory at the moment + else{ INFO.insert("media_files/"+dir, files); emit mediaShortcutsChanged(); } //save these file paths for later + //Make sure the directory is still watched (sometimes the dir is removed/recreated on modification) + if(!watcher->directories().contains(dir)){ watcher->addPath(dir); } + return true; +} + +QStringList OSInterface::autoHandledMediaFiles(){ + QStringList files; + QStringList keys = INFO.keys().filter("media_files/"); + for(int i=0; i<keys.length(); i++){ + if(keys[i].startsWith("media_files/")){ files << INFO[keys[i]].toStringList(); } + } + return files; +} + +// Qt-based NetworkAccessManager usage +void OSInterface::setupNetworkManager(){ + if(netman==0){ + netman = new QNetworkAccessManager(this); + connectNetman(); + } + //Load the initial state of the network accessibility + netAccessChanged(netman->networkAccessible()); +} diff --git a/src-qt5/src-cpp/plugins-screensaver.cpp b/src-qt5/src-cpp/plugins-screensaver.cpp new file mode 100644 index 00000000..50796a52 --- /dev/null +++ b/src-qt5/src-cpp/plugins-screensaver.cpp @@ -0,0 +1,152 @@ +//=========================================== +// Lumina-desktop source code +// Copyright (c) 2017, Ken Moore +// Available under the 3-clause BSD license +// See the LICENSE file for full details +//=========================================== +#include "plugins-screensaver.h" +#include <QJsonDocument> +#include <QJsonArray> +#include <QFile> +#include <QDir> +#include <QDebug> + +//Relative directory to search along the XDG paths for screensavers +#define REL_DIR QString("/lumina-desktop/screensavers") + +// ============ +// SS PLUGIN +// ============ +SSPlugin::SSPlugin(){ + +} + +SSPlugin::~SSPlugin(){ + +} + +void SSPlugin::loadFile(QString path){ + data = QJsonObject(); + currentfile = path; + QFile file(path); + if(!file.exists() || !file.open(QIODevice::ReadOnly)){ return; } + data = QJsonDocument::fromJson(file.readAll()).object(); + //qDebug() << "Loaded ScreenSaver Data:" << currentfile << data; + file.close(); +} + +bool SSPlugin::isLoaded(){ + return !data.isEmpty(); +} + +bool SSPlugin::isValid(){ + if(data.isEmpty()){ return false; } + bool ok = data.contains("name") && data.contains("qml") && data.contains("description"); + if(ok){ + //go to the next name level and see if required sub-items exist + QJsonObject tmp = data.value("name").toObject(); + ok = tmp.contains("default"); + } + if(ok){ + //go to the next description level and see if required sub-items exist + QJsonObject tmp = data.value("description").toObject(); + ok = tmp.contains("default"); + } +if(ok){ + //go to the next qml level and see if required sub-items exist + QJsonObject tmp = data.value("qml").toObject(); + QStringList mustexist; + QString exec = tmp.value("exec").toString(); + if(exec.isEmpty() || !exec.endsWith(".qml")){ return false; } + mustexist << exec; + QJsonArray tmpA = data.value("additional_files").toArray(); + for(int i=0; i<tmpA.count(); i++){ mustexist << tmpA[i].toString(); } + QString reldir = currentfile.section("/",0,-2) + "/"; + //qDebug() << "Got MustExist:" << mustexist << reldir; + for(int i=0; i<mustexist.length() && ok; i++){ + if(mustexist[i].startsWith("/")){ ok = QFile::exists(mustexist[i]); } + else { ok = QFile::exists(reldir+mustexist[i]); } + } + } + return ok; +} + +QString SSPlugin::translatedName(){ + QJsonObject tmp = data.value("name").toObject(); + //Get the current locale + QString locale = getenv("LC_ALL"); + if(locale.isEmpty()){ locale = getenv("LC_MESSAGES"); } + if(locale.isEmpty()){ locale = getenv("LANG"); } + if(locale.isEmpty()){ locale = "default"; } + if(locale.contains(".")){ locale = locale.section(".",0,0); } //chop any charset code off the end + //Now find which localized string is available and return it + if(tmp.contains(locale)){ return tmp.value(locale).toString(); } + locale = locale.section("_",0,0); //full locale not found - look for shortened form + if(tmp.contains(locale)){ return tmp.value(locale).toString(); } + return tmp.value("default").toString(); //use the default version +} + +QString SSPlugin::translatedDescription(){ + QJsonObject tmp = data.value("description").toObject(); + //Get the current locale + QString locale = getenv("LC_ALL"); + if(locale.isEmpty()){ locale = getenv("LC_MESSAGES"); } + if(locale.isEmpty()){ locale = getenv("LANG"); } + if(locale.isEmpty()){ locale = "default"; } + if(locale.contains(".")){ locale = locale.section(".",0,0); } //chop any charset code off the end + //Now find which localized string is available and return it + if(tmp.contains(locale)){ return tmp.value(locale).toString(); } + locale = locale.section("_",0,0); //full locale not found - look for shortened form + if(tmp.contains(locale)){ return tmp.value(locale).toString(); } + return tmp.value("default").toString(); //use the default version +} + +QUrl SSPlugin::scriptURL(){ + QString exec = data.value("qml").toObject().value("exec").toString(); + //qDebug() << "got exec:" << exec << data; + if(!exec.startsWith("/")){ exec.prepend( currentfile.section("/",0,-2)+"/" ); } + return QUrl::fromLocalFile(exec); +} + +// =================== +// SS PLUGIN SYSTEM +// =================== +SSPlugin SSPluginSystem::findPlugin(QString name){ + //qDebug() << "FindPlugin:" << name; + SSPlugin SSP; + if(name.startsWith("/") && QFile::exists(name)){ SSP.loadFile(name); return SSP;} //absolute path give - just load that one + //Cleanup the input name and ensure it has the right suffix + name = name.section("/",-1); + if(!name.endsWith(".json")){ name.append(".json"); } + //Get the list of directories to search + QStringList dirs; + dirs << QString(getenv("XDG_DATA_HOME")) << QString(getenv("XDG_DATA_DIRS")).split(":"); + //Look for that file within these directories and return the first one found + for(int i=0; i<dirs.length(); i++){ + if(!QFile::exists(dirs[i]+REL_DIR+"/"+name)){ continue; } + SSP.loadFile(dirs[i]+REL_DIR+"/"+name); + if(SSP.isValid()){ break; } //got a good one - stop here + } + return SSP; +} + +QList<SSPlugin> SSPluginSystem::findAllPlugins(bool validonly){ + QList<SSPlugin> LIST; + //Get the list of directories to search + QStringList dirs; + dirs << QString(getenv("XDG_DATA_HOME")) << QString(getenv("XDG_DATA_DIRS")).split(":"); + //Look for that file within these directories and return the first one found + for(int i=0; i<dirs.length(); i++){ + if(!QFile::exists(dirs[i]+REL_DIR)){ continue; } + QDir dir(dirs[i]+REL_DIR); + QStringList files = dir.entryList(QStringList() << "*.json", QDir::Files, QDir::Name); + //qDebug() << "Found Files:" << files; + for(int j=0; j<files.length(); j++){ + SSPlugin tmp; + tmp.loadFile(dir.absoluteFilePath(files[j])); + //qDebug() << "Loaded File:" << files[j] << tmp.isValid(); + if(!validonly || tmp.isValid()){ LIST << tmp; } + } + } + return LIST; +} diff --git a/src-qt5/src-cpp/plugins-screensaver.h b/src-qt5/src-cpp/plugins-screensaver.h new file mode 100644 index 00000000..9a7e98f5 --- /dev/null +++ b/src-qt5/src-cpp/plugins-screensaver.h @@ -0,0 +1,50 @@ +//=========================================== +// Lumina-desktop source code +// Copyright (c) 2017, Ken Moore +// Available under the 3-clause BSD license +// See the LICENSE file for full details +//=========================================== +// This is a simple class for managing all the various desktop +// screensaver plugins that could be available +//=========================================== +// NOTE: +// This class has a heirarchy-based lookup system +// USER plugins > SYSTEM plugins +// XDG_DATA_HOME/lumina-desktop/screensavers > XDG_DATA_DIRS/lumina-desktop/screensavers +//=========================================== +#ifndef _LUMINA_DESKTOP_SCREENSAVER_PLUGINS_CLASS_H +#define _LUMINA_DESKTOP_SCREENSAVER_PLUGINS_CLASS_H + +#include <QJsonObject> +#include <QString> +#include <QUrl> +#include <QObject> + +class SSPlugin{ +private: + QString currentfile; + +public: + QJsonObject data; //Hazardous to manually modify + + SSPlugin(); + ~SSPlugin(); + + void loadFile(QString path); + bool isLoaded(); + + bool isValid(); + + QString translatedName(); + QString translatedDescription(); + QUrl scriptURL(); +}; + +class SSPluginSystem{ +public: + static SSPlugin findPlugin(QString name); + static QList<SSPlugin> findAllPlugins(bool validonly = true); + +}; + +#endif diff --git a/src-qt5/src-cpp/plugins-screensaver.pri b/src-qt5/src-cpp/plugins-screensaver.pri new file mode 100644 index 00000000..ad03f34c --- /dev/null +++ b/src-qt5/src-cpp/plugins-screensaver.pri @@ -0,0 +1,4 @@ +HEADERS *= $${PWD}/plugins-screensaver.h +SOURCES *= $${PWD}/plugins-screensaver.cpp + +INCLUDEPATH *= $${PWD} diff --git a/src-qt5/src-cpp/tests/main.cpp b/src-qt5/src-cpp/tests/main.cpp new file mode 100644 index 00000000..682c318a --- /dev/null +++ b/src-qt5/src-cpp/tests/main.cpp @@ -0,0 +1,38 @@ +#include <QDebug> +#include <QApplication> + +#include <framework-OSInterface.h> + +/* +class tester : public QObject{ + Q_OBJECT +public slots: + void finished(){ QApplication::exit(0); } + +public: + QTimer *timer; + + tester(){ + timer = new QTimer(this); + timer->setInterval(5000); + timer->setSingleShot(true); + connect(timer, SIGNAL(timeout()), this, SLOT(finished()) ); + } + +}; +*/ + +int main(int argc, char** argv){ + + QApplication A(argc,argv); + OSInterface OS; + OS.start(); + QTimer *timer = new QTimer(); + timer->setInterval(5000); + timer->setSingleShot(true); + QObject::connect(timer, SIGNAL(timeout()), &A, SLOT(quit()) ); + timer->start(); + int ret = A.exec(); + qDebug() << " - Finished"; + return ret; +} diff --git a/src-qt5/src-cpp/tests/test.pro b/src-qt5/src-cpp/tests/test.pro new file mode 100644 index 00000000..425e7de6 --- /dev/null +++ b/src-qt5/src-cpp/tests/test.pro @@ -0,0 +1,7 @@ +QT = core gui widgets + +include(../framework-OSInterface.pri) + +TARGET = test + +SOURCES += main.cpp |