diff options
author | Sasongko Bawono <sasongko262@gmail.com> | 2017-02-26 08:33:25 +0700 |
---|---|---|
committer | Sasongko Bawono <sasongko262@gmail.com> | 2017-02-26 08:33:25 +0700 |
commit | eb7d5a1270d17ecd16912008d8587cc543cafc77 (patch) | |
tree | 64be065a2e3fe58933bb5be6f30cc76a1af33f86 /src-qt5/core/libLumina | |
parent | modified DEPENDENCIES for Slackware (diff) | |
parent | Merge remote-tracking branch 'origin/master' (diff) | |
download | lumina-eb7d5a1270d17ecd16912008d8587cc543cafc77.tar.gz lumina-eb7d5a1270d17ecd16912008d8587cc543cafc77.tar.bz2 lumina-eb7d5a1270d17ecd16912008d8587cc543cafc77.zip |
Merge remote-tracking branch 'upstream/master'
Diffstat (limited to 'src-qt5/core/libLumina')
20 files changed, 987 insertions, 22 deletions
diff --git a/src-qt5/core/libLumina/ExternalProcess.h b/src-qt5/core/libLumina/ExternalProcess.h index 1325247f..8329c361 100644 --- a/src-qt5/core/libLumina/ExternalProcess.h +++ b/src-qt5/core/libLumina/ExternalProcess.h @@ -13,17 +13,40 @@ #include <QProcess> #include <QString> +#include <QTimer> +#include <QApplication> class ExternalProcess : public QProcess{ Q_OBJECT +private: + bool cursorRestored; + private slots: + void resetCursor(){ + if(!cursorRestored){ + QApplication::restoreOverrideCursor(); + cursorRestored = true; + } + } + void processStarting(){ + if(!cursorRestored){ + QApplication::setOverrideCursor( QCursor(Qt::WaitCursor) ); + QTimer::singleShot(15000, this, SLOT(resetCursor()) ); + } + } void processFinished(){ + if(!cursorRestored){ + QApplication::restoreOverrideCursor(); + cursorRestored = true; + } //Clean up this object this->deleteLater(); } + public: - ExternalProcess(QString logfile = "") : QProcess(){ + ExternalProcess(QString logfile = "", bool manageCursors = true) : QProcess(){ this->setProcessChannelMode(QProcess::MergedChannels); + cursorRestored = !manageCursors; if(logfile.isEmpty()){ this->setStandardOutputFile(QProcess::nullDevice()); }else{ @@ -32,6 +55,7 @@ public: //Setup the connection for automatic cleanup connect(this, SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(processFinished()) ); } + ~ExternalProcess(){ /*if(this->state() == QProcess::Running){ this->detach(); //about to close down the QProcess - detach the other program so it can continue functioning normally. diff --git a/src-qt5/core/libLumina/LuminaRandR-X11.cpp b/src-qt5/core/libLumina/LuminaRandR-X11.cpp new file mode 100644 index 00000000..85251b64 --- /dev/null +++ b/src-qt5/core/libLumina/LuminaRandR-X11.cpp @@ -0,0 +1,206 @@ +//=========================================== +// Lumina-DE source code +// Copyright (c) 2017, Ken Moore +// Available under the 3-clause BSD license +// See the LICENSE file for full details +//=========================================== +#include "LuminaRandR.h" + +#include "xcb/randr.h" +#include "xcb/xcb_atom.h" + +#include <QDebug> +#include <QX11Info> + +static QString atomToName(xcb_atom_t atom){ + xcb_get_atom_name_reply_t *nreply = xcb_get_atom_name_reply(QX11Info::connection(), xcb_get_atom_name_unchecked(QX11Info::connection(), atom), NULL); + QString name = QString::fromLocal8Bit(xcb_get_atom_name_name(nreply), xcb_get_atom_name_name_length(nreply)); + free(nreply); + return name; +}; + +//More efficient method for converting lots of atoms to strings +static QStringList atomsToNames(xcb_atom_t *atoms, unsigned int num){ + //qDebug() << "atomsToNames:" << num; + QList< xcb_get_atom_name_cookie_t > cookies; + //qDebug() << " - Get cookies"; + for(unsigned int i=0; i<num; i++){ cookies << xcb_get_atom_name_unchecked(QX11Info::connection(), atoms[i]); } + QStringList names; + //qDebug() << " - Get names"; + for(int i=0; i<cookies.length(); i++){ + xcb_get_atom_name_reply_t *nreply = xcb_get_atom_name_reply(QX11Info::connection(), cookies[i], NULL); + if(nreply==0){ continue; } + names << QString::fromLocal8Bit(xcb_get_atom_name_name(nreply), xcb_get_atom_name_name_length(nreply)); + free(nreply); + } + return names; +}; + +class OutputDevice::p_objects{ +public: + xcb_atom_t monitor_atom; + QList<xcb_randr_output_t> outputs; //the actual output devices used by the monitor + +}; + +//Global Listing of Devices +QList<OutputDevice> OutputDevice::availableMonitors(){ + QList<OutputDevice> list; + //Get the list of monitors + xcb_randr_get_monitors_cookie_t cookie = xcb_randr_get_monitors_unchecked(QX11Info::connection(), QX11Info::appRootWindow(), 1); + xcb_randr_get_monitors_reply_t *reply = xcb_randr_get_monitors_reply(QX11Info::connection(), cookie, NULL); + if(reply==0){ + qDebug() << "Could not get monitor list"; + return list; + } + xcb_randr_monitor_info_iterator_t iter = xcb_randr_get_monitors_monitors_iterator(reply); + qDebug() << "Number of Monitors:" << xcb_randr_get_monitors_monitors_length(reply); + while(iter.rem>0){ + qDebug() << "Found Monitor:"; + //qDebug() << " Index:" << iter.index << "Rem:" << iter.rem; + QString name = atomToName(iter.data->name); + /*xcb_get_atom_name_reply_t *nreply = xcb_get_atom_name_reply(QX11Info::connection(), xcb_get_atom_name_unchecked(QX11Info::connection(), iter.data->name), NULL); + QString name = QString::fromLocal8Bit(xcb_get_atom_name_name(nreply), xcb_get_atom_name_name_length(nreply)); + free(nreply);*/ + + qDebug() << " - Name:" << iter.data->name << name; + qDebug() << " - Primary:" << (iter.data->primary == 1); + qDebug() << " - Automatic:" << (iter.data->automatic == 1); + qDebug() << " - nOutput:" << iter.data->nOutput; + qDebug() << " - Geometry:" << QRect(iter.data->x, iter.data->y, iter.data->width, iter.data->height); + qDebug() << " - Physical Size (mm):" << iter.data->width_in_millimeters << "x" << iter.data->height_in_millimeters; + qDebug() << " - Number of outputs:" << xcb_randr_monitor_info_outputs_length(iter.data); + xcb_randr_monitor_info_next(&iter); + } + + //Free up any objects we are done with + free(reply); + //Return the list + return list; +} + +//FUNCTIONS (do not use directly - use the static list function instead) +OutputDevice::OutputDevice(){ + enabled = false; + p_obj = new p_objects(); + p_obj->monitor_atom = 0; +} + +OutputDevice::~OutputDevice(){ + +} + +//Modification +bool OutputDevice::setAsPrimary(){ + if(isPrimary){ return true; } + if( !p_obj->outputs.isEmpty() ){ + xcb_randr_set_output_primary (QX11Info::connection(), QX11Info::appRootWindow(), p_obj->outputs[0]); + isPrimary = true; + } + return isPrimary; +} + +bool OutputDevice::disable(){ + if(p_obj->monitor_atom!=0){ + xcb_randr_delete_monitor(QX11Info::connection(), QX11Info::appRootWindow(), p_obj->monitor_atom); + p_obj->monitor_atom = 0; + return true; + } + return false; +} + +void OutputDevice::enable(QRect geom){ + //if no geom provided, will add as the right-most screen at optimal resolution + if(p_obj->monitor_atom!=0){ return; } + qDebug() << "Enable Monitor:" << geom; + +} + +void OutputDevice::changeResolution(QSize){ + +} + +OutputDeviceList::OutputDeviceList(){ + xcb_randr_get_screen_resources_reply_t *reply = xcb_randr_get_screen_resources_reply(QX11Info::connection(), + xcb_randr_get_screen_resources_unchecked(QX11Info::connection(), QX11Info::appRootWindow()), + NULL); + int outputnum = xcb_randr_get_screen_resources_outputs_length(reply); + qDebug() << "Probing Screen Resources:"; + qDebug() << " - Number of Outputs:" << outputnum; + qDebug() << " - Number of CRTC's:" << xcb_randr_get_screen_resources_crtcs_length(reply); + int mode_len =xcb_randr_get_screen_resources_modes_length(reply); + qDebug() << " - Modes:" << mode_len; + for(int m=0; m<mode_len; m++){ + xcb_randr_mode_info_t mode = xcb_randr_get_screen_resources_modes(reply)[m]; + qDebug() << " -- Mode:" << mode.id; + qDebug() << " - Size:" << mode.width <<"x"<<mode.height; + } + //qDebug() << " -- " << atomsToNames( (xcb_atom_t*) xcb_randr_get_screen_resources_modes(reply), xcb_randr_get_screen_resources_modes_length(reply) ); + qDebug() << " - Names:" << xcb_randr_get_screen_resources_names_length(reply); + //qDebug() << " -- " << atomsToNames( (xcb_atom_t*) xcb_randr_get_screen_resources_names(reply), xcb_randr_get_screen_resources_names_length(reply)); + for(int i=0; i<outputnum; i++){ + xcb_randr_output_t output = xcb_randr_get_screen_resources_outputs(reply)[i]; + //Now display the info about this output + xcb_randr_get_output_info_reply_t *info = xcb_randr_get_output_info_reply(QX11Info::connection(), + xcb_randr_get_output_info_unchecked(QX11Info::connection(), output, QX11Info::appTime()), + NULL); + qDebug() << "==== Output Information #"+QString::number(i); + + //Modes + qDebug() << "Number of Modes:" << xcb_randr_get_output_info_modes_length(info); + + + //Clones + qDebug() << "Number of Clones:" << xcb_randr_get_output_info_clones_length(info); + + //Names + int name_len = xcb_randr_get_output_info_name_length(info); + qDebug() << "Names:" << atomsToNames( (xcb_atom_t*) xcb_randr_get_output_info_name(info), name_len); + for(int n=0; n<name_len; n++){ + QString name = atomToName( xcb_randr_get_output_info_name(info)[n] ); + qDebug() << " -- " << name; + } + + //Properties + xcb_randr_list_output_properties_reply_t *pinfo = xcb_randr_list_output_properties_reply(QX11Info::connection(), + xcb_randr_list_output_properties_unchecked(QX11Info::connection(), output), + NULL); + int pinfo_len = xcb_randr_list_output_properties_atoms_length(pinfo); + qDebug() << "Properties:" << pinfo_len; + for(int p=0; p<pinfo_len; p++){ + xcb_atom_t atom = xcb_randr_list_output_properties_atoms(pinfo)[p]; + //Property Name + QString name = atomToName(atom); + //Property Value + xcb_randr_query_output_property_reply_t *pvalue = xcb_randr_query_output_property_reply(QX11Info::connection(), + xcb_randr_query_output_property_unchecked(QX11Info::connection(), output, atom), + NULL); + QStringList values = atomsToNames ( (xcb_atom_t*) xcb_randr_query_output_property_valid_values(pvalue), xcb_randr_query_output_property_valid_values_length(pvalue) ); //need to read values + /*for(int v=0; v<xcb_randr_query_output_property_valid_values_length(pvalue); v++){ + //values << QString::number(xcb_randr_query_output_property_valid_values(pvalue)[v] ); + values << atomToName( xcb_randr_query_output_property_valid_values(pvalue)[v] ); + }*/ + free(pvalue); + qDebug() << " -- " << name << "=" << values; + + } + free(pinfo); + + free(info); + } + + free(reply); +} + +OutputDeviceList::~OutputDeviceList(){ + +} + +//Simplification functions for dealing with multiple monitors +void OutputDeviceList::setPrimaryMonitor(QString id){ + +} + +void OutputDeviceList::disableMonitor(QString id){ + +} diff --git a/src-qt5/core/libLumina/LuminaRandR.cpp b/src-qt5/core/libLumina/LuminaRandR.cpp deleted file mode 100644 index eefc5aa8..00000000 --- a/src-qt5/core/libLumina/LuminaRandR.cpp +++ /dev/null @@ -1 +0,0 @@ -#include "LuminaRandR.h" diff --git a/src-qt5/core/libLumina/LuminaRandR.h b/src-qt5/core/libLumina/LuminaRandR.h index 3cdff651..448c676d 100644 --- a/src-qt5/core/libLumina/LuminaRandR.h +++ b/src-qt5/core/libLumina/LuminaRandR.h @@ -1,35 +1,70 @@ //=========================================== // Lumina-DE source code -// Copyright (c) 2016, Ken Moore +// Copyright (c) 2017, Ken Moore // Available under the 3-clause BSD license // See the LICENSE file for full details //=========================================== // This class governs all the xcb/randr interactions // and provides simpler Qt-based functions for use elsewhere //=========================================== +#ifndef _LUMINA_LIBRARY_RANDR_MONITORS_H +#define _LUMINA_LIBRARY_RANDR_MONITORS_H //Qt includes #include <QSize> +#include <QString> +#include <QPoint> +#include <QRect> +#include <QList> -#include "xcb/randr.h" -class outputDevice{ +class OutputDevice{ public: QString id; //output ID bool enabled; + bool isPrimary; //Monitor Geometry QPoint geom; //geometry of monitor within session //Monitor Resolution QSize cRes; //current resolution of the monitor (could be different from geom.size() if panning is enabled) QList<QSize> availRes; //available resolutions supported by the monitor //Refresh Rate - int cHz; //current refresh rate - QList<int> availHz; //available refresh rates + //int cHz; //current refresh rate + //QList<int> availHz; //available refresh rates //Expand this later to include: // panning (current/possible) // rotation (current/possible) - //FUNCTIONS - - //Modification + //Global Listing of Devices + static QList<OutputDevice> availableMonitors(); + + //FUNCTIONS (do not use directly - use the static list function instead) + OutputDevice(); + ~OutputDevice(); + + //Modification + bool setAsPrimary(); + bool disable(); + void enable(QRect geom = QRect()); //if no geom provided, will add as the right-most screen at optimal resolution + void changeResolution(QSize); + + //Now define a simple public_objects class so that each implementation + // has a storage container for placing private objects as needed + class p_objects; + p_objects* p_obj; +}; + +class OutputDeviceList : public QList<OutputDevice>{ +public: + OutputDeviceList(); + ~OutputDeviceList(); + + //Simplification functions for dealing with multiple monitors + void setPrimaryMonitor(QString id); + void disableMonitor(QString id); + //void enableMonitor(QString id, + +private: + }; +#endif diff --git a/src-qt5/core/libLumina/LuminaRandR.pri b/src-qt5/core/libLumina/LuminaRandR.pri new file mode 100644 index 00000000..0812819f --- /dev/null +++ b/src-qt5/core/libLumina/LuminaRandR.pri @@ -0,0 +1,12 @@ +#include("$${PWD}/../../OS-detect.pri") + +QT *= x11extras + +#X11/XCB includes +LIBS *= -lxcb -lxcb-randr +SOURCES *= $${PWD}/LuminaRandR-X11.cpp + +#General API/Header +HEADERS *= $${PWD}/LuminaRandR.h + +INCLUDEPATH *= ${PWD} diff --git a/src-qt5/core/libLumina/LuminaX11.cpp b/src-qt5/core/libLumina/LuminaX11.cpp index a8016460..e9eb4b7c 100644 --- a/src-qt5/core/libLumina/LuminaX11.cpp +++ b/src-qt5/core/libLumina/LuminaX11.cpp @@ -1470,7 +1470,17 @@ void LXCB::WM_ICCCM_SetProtocols(WId win, LXCB::ICCCM_PROTOCOLS flags){ // _NET_SUPPORTED (Root) void LXCB::WM_Set_Root_Supported(){ //NET_WM standards (ICCCM implied - no standard way to list those) - xcb_atom_t list[] = {}; + xcb_atom_t list[] = {EWMH._NET_WM_NAME, + EWMH._NET_WM_ICON, + EWMH._NET_WM_ICON_NAME, + EWMH._NET_WM_DESKTOP, + /*_NET_WINDOW_TYPE (and all the various types)*/ + EWMH._NET_WM_WINDOW_TYPE, EWMH._NET_WM_WINDOW_TYPE_DESKTOP, EWMH._NET_WM_WINDOW_TYPE_DOCK, + EWMH._NET_WM_WINDOW_TYPE_TOOLBAR, EWMH._NET_WM_WINDOW_TYPE_MENU, EWMH._NET_WM_WINDOW_TYPE_UTILITY, + EWMH._NET_WM_WINDOW_TYPE_SPLASH, EWMH._NET_WM_WINDOW_TYPE_DIALOG, EWMH._NET_WM_WINDOW_TYPE_NORMAL, + EWMH._NET_WM_WINDOW_TYPE_DROPDOWN_MENU, EWMH._NET_WM_WINDOW_TYPE_POPUP_MENU, EWMH._NET_WM_WINDOW_TYPE_TOOLTIP, + EWMH._NET_WM_WINDOW_TYPE_NOTIFICATION, EWMH._NET_WM_WINDOW_TYPE_COMBO, EWMH._NET_WM_WINDOW_TYPE_DND, + }; xcb_ewmh_set_supported(&EWMH, QX11Info::appScreen(), 0,list); } @@ -1609,7 +1619,23 @@ WId LXCB::WM_Get_Active_Window(){ } void LXCB::WM_Set_Active_Window(WId win){ - xcb_ewmh_set_active_window(&EWMH, QX11Info::appScreen(), win); + xcb_ewmh_set_active_window(&EWMH, QX11Info::appScreen(), win); + //Also send the active window a message to take input focus + //Send the window a WM_TAKE_FOCUS message + if(atoms.isEmpty()){ createWMAtoms(); } //need these atoms + xcb_client_message_event_t event; + event.response_type = XCB_CLIENT_MESSAGE; + event.format = 32; + event.window = win; + event.type = ATOMS[atoms.indexOf("WM_PROTOCOLS")]; + event.data.data32[0] = ATOMS[atoms.indexOf("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()); } // _NET_WORKAREA diff --git a/src-qt5/core/libLumina/LuminaX11.h b/src-qt5/core/libLumina/LuminaX11.h index 2c741111..2f66ce06 100644 --- a/src-qt5/core/libLumina/LuminaX11.h +++ b/src-qt5/core/libLumina/LuminaX11.h @@ -23,7 +23,6 @@ #include <QObject> #include <QFlags> - #include <xcb/xcb_ewmh.h> //SYSTEM TRAY STANDARD DEFINITIONS @@ -67,6 +66,18 @@ public: || width_inc>=0 || height_inc>=0 || min_aspect_num>=0 || min_aspect_den>=0 || max_aspect_num>=0 || max_aspect_den>=0 \ || base_width>=0 || base_height>=0 || win_gravity>0 ); } + bool validMaxSize(){ + return (max_width>0 && max_width>=min_width) && (max_height>0 && max_height>=min_height); + } + bool validMinSize(){ + return (min_width>0 && min_height>0); + } + bool validBaseSize(){ + return (base_width>0 && base_height>0); + } + bool validSize(){ //only check this if the base sizes are invalid (this is the old spec and should not be used any more) + return (x>0 && y>0); + } }; //simple data structure for passing around the XRANDR information diff --git a/src-qt5/core/libLumina/LuminaXDG.cpp b/src-qt5/core/libLumina/LuminaXDG.cpp index d92285c5..38128fc7 100644 --- a/src-qt5/core/libLumina/LuminaXDG.cpp +++ b/src-qt5/core/libLumina/LuminaXDG.cpp @@ -127,9 +127,8 @@ void XDGDesktop::sync(){ else if(var=="Type" && insection){ if(val.toLower()=="application"){ type = XDGDesktop::APP; } else if(val.toLower()=="link"){ type = XDGDesktop::LINK; } - else if(val.toLower()=="dir"){ type = XDGDesktop::DIR; } + else if(val.toLower().startsWith("dir")){ type = XDGDesktop::DIR; } //older specs are "Dir", newer specs are "Directory" else{ type = XDGDesktop::BAD; } //Unknown type - //hasType = true; } } //end reading file file.clear(); //done with contents of file @@ -176,7 +175,7 @@ bool XDGDesktop::isValid(bool showAll){ //if(DEBUG && !ok){ qDebug() << " - Link with missing URL"; } break; case XDGDesktop::DIR: - ok = !path.isEmpty(); + ok = !path.isEmpty() && QFile::exists(path); //if(DEBUG && !ok){ qDebug() << " - Dir with missing path"; } break; default: diff --git a/src-qt5/core/libLumina/NativeWindow.cpp b/src-qt5/core/libLumina/NativeWindow.cpp new file mode 100644 index 00000000..bd42ecaa --- /dev/null +++ b/src-qt5/core/libLumina/NativeWindow.cpp @@ -0,0 +1,37 @@ +//=========================================== +// 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" + +// === PUBLIC === +NativeWindow::NativeWindow(WId id) : QObject(){ + winid = id; + WIN = QWindow::fromWinId(winid); +} + +NativeWindow::~NativeWindow(){ + hash.clear(); + //WIN->deleteLater(); //This class only deals with Native windows which were created outside the app - they need to be cleaned up outside the app too +} + +WId NativeWindow::id(){ + return winid; +} + +QWindow* NativeWindow::window(){ + return WIN; +} + +QVariant NativeWindow::property(NativeWindow::Property prop){ + if(hash.contains(prop)){ return hash.value(prop); } + return QVariant(); //null variant +} + +void NativeWindow::setProperty(NativeWindow::Property prop, QVariant val){ + if(prop == NativeWindow::None){ return; } + hash.insert(prop, val); + emit PropertyChanged(prop, val); +} diff --git a/src-qt5/core/libLumina/NativeWindow.h b/src-qt5/core/libLumina/NativeWindow.h new file mode 100644 index 00000000..59b955c3 --- /dev/null +++ b/src-qt5/core/libLumina/NativeWindow.h @@ -0,0 +1,81 @@ +//=========================================== +// 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, /*null*/ + MinSize, /*QSize*/ + MaxSize, /*QSize*/ + Size, /*QSize*/ + GlobalPos, /*QPoint*/ + Title, /*QString*/ + ShortTitle, /*QString*/ + Icon, /*QIcon*/ + Name, /*QString*/ + Workspace, /*int*/ + States, /*QList<NativeWindow::State> : Current state of the window */ + WinTypes, /*QList<NativeWindow::Type> : Current type of window (typically does not change)*/ + WinActions, /*QList<NativeWindow::Action> : Current actions that the window allows (Managed/set by the WM)*/ + Active, /*bool*/ + Visible /*bool*/ + }; + + + NativeWindow(WId id); + ~NativeWindow(); + + WId id(); + QWindow* window(); + + QVariant property(NativeWindow::Property); + void setProperty(NativeWindow::Property, QVariant); + +private: + QHash <NativeWindow::Property, QVariant> hash; + QWindow *WIN; + WId winid; + +signals: + //General Notifications + void PropertyChanged(NativeWindow::Property, QVariant); + void WindowClosed(WId); + + //Action Requests (not automatically emitted - typically used to ask the WM to do something) + //Note: "WId" should be the NativeWindow id() + void RequestActivate(WId); //Activate the window + void RequestClose(WId); //Close the window + void RequestSetVisible(WId, bool); //Minimize/restore visiblility + void RequestSetGeometry(WId, QRect); //Register the location/size of the window + void RequestSetFrameExtents(WId, QList<int>); //Register the size of the frame around the window [Left,Right, Top,Bottom] in pixels + + // System Tray Icon Embed/Unembed Requests + //void RequestEmbed(WId, QWidget*); + //void RequestUnEmbed(WId, QWidget*); +}; +#endif diff --git a/src-qt5/core/libLumina/NativeWindow.pri b/src-qt5/core/libLumina/NativeWindow.pri new file mode 100644 index 00000000..4a585a06 --- /dev/null +++ b/src-qt5/core/libLumina/NativeWindow.pri @@ -0,0 +1,8 @@ + +# Files +SOURCES *= $${PWD}/NativeWindow.cpp + +HEADERS *= $${PWD}/NativeWindow.h + +INCLUDEPATH *= ${PWD} + diff --git a/src-qt5/core/libLumina/NativeWindowSystem.cpp b/src-qt5/core/libLumina/NativeWindowSystem.cpp new file mode 100644 index 00000000..c8e6d483 --- /dev/null +++ b/src-qt5/core/libLumina/NativeWindowSystem.cpp @@ -0,0 +1,221 @@ +//=========================================== +// 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> + +//XCB Library functions +#include <xcb/xcb_ewmh.h> + +//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_REDIRECT | \ + 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) + +//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(){ + QStringList atoms; + atoms << "WM_TAKE_FOCUS" << "WM_DELETE_WINDOW" << "WM_PROTOCOLS" << "WM_CHANGE_STATE" << "_NET_SYSTEM_TRAY_OPCODE" << "_NET_SYSTEM_TRAY_ORIENTATION" << "_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){ + obj->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 (obj->ATOMS.keys.length() == atoms.length()); + } + + 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, \ + win, 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 startSystemTray{ + 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) + //TODO + } + +}; //end private objects class + + +//inline functions for setting up the internal objects + + +// === PUBLIC === +NativeWindowSystem::NativeWindowSystem() : QObject(){ + obj = 0; +} + +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 + + return true; +} + +void NativeWindowSystem::stop(){ + +} + +// === PRIVATE === +void NativeWindowSystem::UpdateWindowProperties(NativeWindow* win, QList< NativeWindow::Property > props){ + +} + +// === PUBLIC SLOTS === +//These are the slots which are only used by the desktop system itself or the NativeWindowEventFilter +void NativeWindowSystem::RegisterVirtualRoot(WId){ + +} + +//NativeWindowEventFilter interactions +void NativeWindowSystem::NewWindowDetected(WId){ + +} + +void NativeWindowSystem::WindowCloseDetected(WId){ + +} + +void NativeWindowSystem::WindowPropertyChanged(WId, NativeWindow::Property){ + +} + +void NativeWindowSystem::NewKeyPress(int keycode){ + +} + +void NativeWindowSystem::NewKeyRelease(int keycode){ + +} + +void NativeWindowSystem::NewMousePress(int buttoncode){ + +} + +void NativeWindowSystem::NewMouseRelease(int buttoncode){ + +} + +// === PRIVATE SLOTS === +//These are the slots which are built-in and automatically connected when a new NativeWindow is created +void NativeWindowSystem::RequestActivate(WId){ + +} +void NativeWindowSystem::RequestClose(WId){ + +} + +void NativeWindowSystem::RequestSetVisible(WId, bool){ + +} +void NativeWindowSystem::RequestSetGeometry(WId, QRect){ + +} +void NativeWindowSystem::RequestSetFrameExtents(WId, QList<int>){ + //[Left,Top,Right,Bottom] in pixels + +} diff --git a/src-qt5/core/libLumina/NativeWindowSystem.h b/src-qt5/core/libLumina/NativeWindowSystem.h new file mode 100644 index 00000000..ef169059 --- /dev/null +++ b/src-qt5/core/libLumina/NativeWindowSystem.h @@ -0,0 +1,92 @@ +//=========================================== +// 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" + +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){ + for(int i=0; i<NWindows.length(); i++){ + if(id==NWindows[i]->id()){ return NWindows[i]; } + } + } + + NativeWindow* findTrayWindow(WId id){ + for(int i=0; i<TWindows.length(); i++){ + if(id==TWindows[i]->id()){ return TWindows[i]; } + } + } + + //Now define a simple private_objects class so that each implementation + // has a storage container for placing private objects as needed + class p_objects; + p_objects* obj; + + // 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); + +public: + NativeWindowSystem(); + ~NativeWindowSystem(); + + //Overarching start/stop functions + bool start(); + void stop(); + + //General-purpose listing functions + QList<NativeWindow*> currentWindows(){ return NWindows; } + +public slots: + //These are the slots which are typically only used by the desktop system itself or the NativeWindowEventFilter + + //RootWindow interactions + void RegisterVirtualRoot(WId); + //void GoToWorkspace(int); + //void RegisterWorkspaces(QStringList); //Names of workspaces, in ascending order + //void RegisterKnownInteractions(); + + + //NativeWindowEventFilter interactions + void NewWindowDetected(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 NewKeyPress(int keycode); + void NewKeyRelease(int keycode); + void NewMousePress(int buttoncode); + void NewMouseRelease(int buttoncode); + void CheckDamageID(WId); + +private slots: + //These are the slots which are built-in and automatically connected when a new NativeWindow is created + void RequestActivate(WId); + void RequestClose(WId); + void RequestSetVisible(WId, bool); + void RequestSetGeometry(WId, QRect); + void RequestSetFrameExtents(WId, QList<int>); //[Left,Right,Top,Bottom] in pixels + +signals: + void NewWindowAvailable(NativeWindow*); + void NewInputEvent(); //a mouse or keypress was detected (lock-state independent); + void NewKeyPress(int); //only emitted if lockstate = false + void NewKeyRelease(int); //only emitted if lockstate = false + void NewMousePress(Qt::MouseButton); //only emitted if lockstate = false + +}; + +#endif diff --git a/src-qt5/core/libLumina/RootSubWindow.cpp b/src-qt5/core/libLumina/RootSubWindow.cpp new file mode 100644 index 00000000..7be89f48 --- /dev/null +++ b/src-qt5/core/libLumina/RootSubWindow.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 +//=========================================== +#include "RootSubWindow.h" +#include <QDebug> + +// === PUBLIC === +RootSubWindow::RootSubWindow(QMdiArea *root, NativeWindow *win) : QMdiSubWindow(0){ + this->setAttribute(Qt::WA_DeleteOnClose); + //Create the QWindow and QWidget containers for the window + WIN = win; + closing = false; + WinWidget = QWidget::createWindowContainer( WIN->window(), this); + this->setWidget(WinWidget); + LoadProperties( QList< NativeWindow::Property>() << NativeWindow::WindowFlags << NativeWindow::Title << NativeWindow::Icon \ + << NativeWindow::MinSize << NativeWindow::MaxSize << NativeWindow::Size ); + //Hookup the signals/slots + connect(this, SIGNAL(aboutToActivate()), this, SLOT(aboutToActivate()) ); + connect(WIN, SIGNAL(PropertyChanged(NativeWindow::Property, QVariant)), this, SLOT(propertyChanged(NativeWindow::Property, QVariant))); + //Now add this window to the root QMdiArea + root->addSubWindow(this); + //Make sure the visibily property only gets loaded after it is added to the root area + propertyChanged(NativeWindow::Visible, WIN->property(NativeWindow::Visible)); +} + +RootSubWindow::~RootSubWindow(){ + +} + +WId RootSubWindow::id(){ + return WIN->id(); +} + +// === PRIVATE === +void RootSubWindow::LoadProperties( QList< NativeWindow::Property> list){ + for(int i=0; i<list.length(); i++){ + propertyChanged( list[i], WIN->property(list[i]) ); + } +} + +// === PUBLIC SLOTS === +void RootSubWindow::clientClosed(){ + qDebug() << "Client Closed"; + closing = true; + this->close(); +} + +void RootSubWindow::clientHidden(){ + qDebug() << "Client Hidden"; + this->hide(); +} + +void RootSubWindow::clientShown(){ + qDebug() << "Client Shown"; + this->show(); +} + +// === PRIVATE SLOTS === +void RootSubWindow::aboutToActivate(){ + WIN->emit RequestActivate(WIN->id()); +} + +void RootSubWindow::propertyChanged(NativeWindow::Property prop, QVariant val){ + if(val.isNull()){ return; } //not the same as a default/empty value - the property has just not been set yet + qDebug() << "Set Window Property:" << prop << val; + switch(prop){ + case NativeWindow::Visible: + if(val.toBool()){ clientShown(); } + else{ clientHidden(); } + break; + case NativeWindow::Title: + this->setWindowTitle(val.toString()); + break; + case NativeWindow::Icon: + this->setWindowIcon(val.value< QIcon>()); + break; + case NativeWindow::Size: + this->resize(val.toSize()); + break; + case NativeWindow::MinSize: + this->setMinimumSize(val.toSize()); + break; + case NativeWindow::MaxSize: + this->setMaximumSize(val.toSize()); + break; + case NativeWindow::Active: + if(val.toBool()){ this->mdiArea()->setActiveSubWindow(this); } + break; + case NativeWindow::WindowFlags: + this->setWindowFlags( val.value< Qt::WindowFlags >() ); + break; + default: + qDebug() << "Window Property Unused:" << prop << val; + } +} + +// === PROTECTED === +void RootSubWindow::closeEvent(QCloseEvent *ev){ + if(!closing){ + //qDebug() << "Close Window By Button:" << WIN->id(); + ev->ignore(); + WIN->emit RequestClose(WIN->id()); + }else{ + QMdiSubWindow::closeEvent(ev); + } + +} diff --git a/src-qt5/core/libLumina/RootSubWindow.h b/src-qt5/core/libLumina/RootSubWindow.h new file mode 100644 index 00000000..c56f3c96 --- /dev/null +++ b/src-qt5/core/libLumina/RootSubWindow.h @@ -0,0 +1,51 @@ +//=========================================== +// Lumina Desktop source code +// Copyright (c) 2017, Ken Moore +// Available under the 3-clause BSD license +// See the LICENSE file for full details +//=========================================== +// This class embeds a native window +// within the RootWindow area +//=========================================== +#ifndef _LUMINA_ROOT_WINDOW_SUB_WINDOW_H +#define _LUMINA_ROOT_WINDOW_SUB_WINDOW_H + +#include <QMdiArea> +#include <QMdiSubWindow> +#include <QWindow> +#include <QWidget> +#include <QCloseEvent> + +#include <NativeWindow.h> + +class RootSubWindow : public QMdiSubWindow{ + Q_OBJECT +public: + RootSubWindow(QMdiArea *root, NativeWindow *win); + ~RootSubWindow(); + + WId id(); + +private: + NativeWindow *WIN; + QWidget *WinWidget; + bool closing; + + void LoadProperties( QList< NativeWindow::Property> list); + +public slots: + void clientClosed(); + +private slots: + void clientHidden(); + void clientShown(); + void aboutToActivate(); + void propertyChanged(NativeWindow::Property, QVariant); + + +protected: + void closeEvent(QCloseEvent*); + +}; + +#endif diff --git a/src-qt5/core/libLumina/RootWindow.cpp b/src-qt5/core/libLumina/RootWindow.cpp index aa5957b5..0758653b 100644 --- a/src-qt5/core/libLumina/RootWindow.cpp +++ b/src-qt5/core/libLumina/RootWindow.cpp @@ -11,7 +11,7 @@ #include <QDebug> // === PUBLIC === -RootWindow::RootWindow() : QWidget(0, Qt::Window | Qt::BypassWindowManagerHint | Qt::WindowStaysOnBottomHint){ +RootWindow::RootWindow() : QMdiArea(0){ //QWidget(0, Qt::Window | Qt::BypassWindowManagerHint | Qt::WindowStaysOnBottomHint){ qRegisterMetaType<WId>("WId"); autoResizeTimer = 0; } @@ -166,13 +166,32 @@ void RootWindow::ChangeWallpaper(QString id, RootWindow::ScaleType scale, QStrin } +void RootWindow::NewWindow(NativeWindow *win){ + RootSubWindow *subwin = 0; + for(int i=0; i<WINDOWS.length() && subwin==0; i++){ + if(WINDOWS[i]->id() == win->id()){ subwin = WINDOWS[i]; } + } + if(subwin==0){ + subwin = new RootSubWindow(this, win); + connect(win, SIGNAL(WindowClosed(WId)), this, SLOT(CloseWindow(WId)) ); + WINDOWS << subwin; + } + //subwin->show(); +} + +void RootWindow::CloseWindow(WId win){ + for(int i=0; i<WINDOWS.length(); i++){ + if(WINDOWS[i]->id() == win){ WINDOWS.takeAt(i)->clientClosed(); break; } + } +} + // === PRIVATE SLOTS === // === PROTECTED === void RootWindow::paintEvent(QPaintEvent *ev){ //qDebug() << "RootWindow: PaintEvent:" << ev->rect(); //<< QDateTime::currentDateTime()->toString(QDateTime::ShortDate); bool found = false; - QPainter painter(this); + QPainter painter(this->viewport()); for(int i=0; i<WALLPAPERS.length(); i++){ if(WALLPAPERS[i].area.intersects(ev->rect()) ){ found = true; diff --git a/src-qt5/core/libLumina/RootWindow.h b/src-qt5/core/libLumina/RootWindow.h index b371d239..5d3bc963 100644 --- a/src-qt5/core/libLumina/RootWindow.h +++ b/src-qt5/core/libLumina/RootWindow.h @@ -18,8 +18,12 @@ #include <QTimer> #include <QApplication> #include <QPaintEvent> +#include <QMdiArea> -class RootWindow : public QWidget{ +#include "RootSubWindow.h" +#include "NativeWindow.h" + +class RootWindow : public QMdiArea{ Q_OBJECT public: enum ScaleType{ SolidColor, Stretch, Full, Fit, Center, Tile, BottomLeft, BottomRight, BottomCenter, \ @@ -43,11 +47,17 @@ private: QList<screeninfo> WALLPAPERS; void updateScreenPixmap(screeninfo *info); //used for recalculating the wallpaper pixmap based on file/area/scale as needed + //Window Management + QList<RootSubWindow*> WINDOWS; + public slots: void ResizeRoot(); void ChangeWallpaper(QString id, RootWindow::ScaleType scale, QString file); //Note: for "SingleColor" scaling the "file" variable should be "rgb(R,G,B)" or "#hexcode" + void NewWindow(NativeWindow*); + void CloseWindow(WId); //automatically connected for any new native window + private slots: protected: diff --git a/src-qt5/core/libLumina/RootWindow.pri b/src-qt5/core/libLumina/RootWindow.pri index 7ef3efb0..35e0e770 100644 --- a/src-qt5/core/libLumina/RootWindow.pri +++ b/src-qt5/core/libLumina/RootWindow.pri @@ -1,9 +1,13 @@ # Files -SOURCES *= $${PWD}/RootWindow.cpp -HEADERS *= $${PWD}/RootWindow.h +SOURCES *= $${PWD}/RootWindow.cpp \ + $${PWD}/RootSubWindow.cpp + +HEADERS *= $${PWD}/RootWindow.h \ + $${PWD}/RootSubWindow.h INCLUDEPATH *= ${PWD} -# include LUtils and LuminaX11 +# include other library dependencies include(LUtils.pri) +include(NativeWindow.pri); diff --git a/src-qt5/core/libLumina/test/main.cpp b/src-qt5/core/libLumina/test/main.cpp new file mode 100644 index 00000000..309fb938 --- /dev/null +++ b/src-qt5/core/libLumina/test/main.cpp @@ -0,0 +1,13 @@ + +#include "../LuminaRandR.h" +#include <QDebug> +#include <QApplication> + +int main(int argc, char** argv){ + QApplication A(argc, argv); + qDebug() << "Starting monitor scan..."; + QList<OutputDevice> outputs = OutputDevice::availableMonitors(); + qDebug() << "Finished monitor Scan"; + OutputDeviceList(); + return 0; +} diff --git a/src-qt5/core/libLumina/test/test.pro b/src-qt5/core/libLumina/test/test.pro new file mode 100644 index 00000000..9674801b --- /dev/null +++ b/src-qt5/core/libLumina/test/test.pro @@ -0,0 +1,7 @@ +QT = core gui widgets + +TARGET = test + +SOURCES += main.cpp + +include(../LuminaRandR.pri) |