diff options
author | Weblate <noreply@weblate.org> | 2017-09-20 13:42:32 +0000 |
---|---|---|
committer | Weblate <noreply@weblate.org> | 2017-09-20 13:42:32 +0000 |
commit | a4fd58c9aae62207b131a13cc13187970495bdb5 (patch) | |
tree | cdabc3a0816c41f6f2a6f5191f3e38f698595c13 /src-qt5/core/libLumina | |
parent | Translated using Weblate (Lithuanian) (diff) | |
parent | Streamline a bit more of the new Lumina2 window embed functionality. (diff) | |
download | lumina-a4fd58c9aae62207b131a13cc13187970495bdb5.tar.gz lumina-a4fd58c9aae62207b131a13cc13187970495bdb5.tar.bz2 lumina-a4fd58c9aae62207b131a13cc13187970495bdb5.zip |
Merge remote-tracking branch 'origin/master'
Diffstat (limited to 'src-qt5/core/libLumina')
28 files changed, 988 insertions, 306 deletions
diff --git a/src-qt5/core/libLumina/DesktopSettings.cpp b/src-qt5/core/libLumina/DesktopSettings.cpp index 8bda1ac5..f1c74bc5 100644 --- a/src-qt5/core/libLumina/DesktopSettings.cpp +++ b/src-qt5/core/libLumina/DesktopSettings.cpp @@ -56,11 +56,19 @@ void DesktopSettings::stop(){ } //Main Read/Write functions +QList< DesktopSettings::File > DesktopSettings::writableFiles(){ + QList< DesktopSettings::File > tmp; + if(runmode!=DesktopSettings::SystemFull){ tmp = filesForRunMode(runmode); } + return tmp; +} + QVariant DesktopSettings::value(DesktopSettings::File file, QString variable, QVariant defaultvalue){ if(!files.contains(file)){ return defaultvalue; } for(int i=0; i<files[file].length(); i++){ + //qDebug() << "Look for Settings value:" << variable << files[file]; if( settings.contains(files[file][i])){ //make sure this file is in the settings hash if(settings[files[file][i]]->contains(variable)){ //if this file does not have the variable - go to the next one + //qDebug() << " - Found Setting in File:" << files[file][i]; return settings[files[file][i]]->value(variable, defaultvalue); } } @@ -118,7 +126,7 @@ void DesktopSettings::parseSystemSettings(){ QString defMode = settings[path]->value("default_mode","fulluser").toString().toLower(); if(defMode=="fullsystem"){ runmode= DesktopSettings::SystemFull; } else if(defMode=="staticinterface"){ runmode = DesktopSettings::SystemInterface; } - + else{ runmode = DesktopSettings::UserFull; } //Now determine the runmode for this user struct passwd *pw = getpwuid(getuid()); if(pw!=0){ @@ -178,25 +186,29 @@ void DesktopSettings::locateFiles(){ systemdirs << QString(getenv("XDG_CONFIG_DIRS")).split(":") << QString(getenv("XDG_DATA_DIRS")).split(":"); //Load all the user-level files for this run mode QList< DesktopSettings::File > tmp; - if(runmode == DesktopSettings::UserFull){ tmp << DesktopSettings::Favorites << DesktopSettings::Environment << DesktopSettings::Session << DesktopSettings::Desktop << DesktopSettings::Keys << DesktopSettings::Theme; } - else if(runmode == DesktopSettings::SystemInterface){ tmp << DesktopSettings::Favorites << DesktopSettings::Environment << DesktopSettings::Session << DesktopSettings::Desktop << DesktopSettings::Keys << DesktopSettings::Theme; } - for(int i=0; i<tmp.length(); i++){ - QString path = userdir+rel_path(tmp[i]); - touchFile(path); - files.insert(tmp[i], QStringList() << path); - settings.insert(path, new QSettings(path, QSettings::IniFormat, this) ); - watcher->addPath(path); + if(runmode!=DesktopSettings::SystemFull){ + //Load the user-level files + tmp= filesForRunMode(runmode); + for(int i=0; i<tmp.length(); i++){ + QString path = userdir+rel_path(tmp[i]); + touchFile(path); + files.insert(tmp[i], QStringList() << path); + settings.insert(path, new QSettings(path, QSettings::IniFormat, this) ); + watcher->addPath(path); + } } //Now load all the system-level files - tmp.clear(); - tmp << DesktopSettings::Favorites << DesktopSettings::Environment << DesktopSettings::Session << DesktopSettings::Desktop << DesktopSettings::Keys << DesktopSettings::Theme; + tmp = filesForRunMode(DesktopSettings::SystemFull); for(int i=0; i<systemdirs.length(); i++){ if(systemdirs[i].endsWith("/xdg")){ systemdirs[i] = systemdirs[i].section("/",0,-2); } if( !QFile::exists(systemdirs[i]+"/lumina-desktop") ){ continue; } for(int j=0; j<tmp.length(); j++){ QString path = systemdirs[i]+rel_path(tmp[j]); if(QFile::exists(path)){ - files.insert(tmp[j], QStringList() << path); + QStringList filepaths; + if(files.contains(tmp[j])){ filepaths = files[tmp[j]]; } + filepaths << path; //add this file to the end of the list for this type of settings file + files.insert(tmp[j], filepaths); settings.insert(path, new QSettings(path, QSettings::IniFormat, this) ); watcher->addPath(path); } @@ -219,6 +231,17 @@ void DesktopSettings::touchFile(QString path){ } } +QList< DesktopSettings::File > DesktopSettings::filesForRunMode(RunMode mode){ + // Note that the "System" file is always ignored here - that is specially loaded + QList< DesktopSettings::File > tmp; + if(mode == DesktopSettings::UserFull || mode == DesktopSettings::SystemFull){ + tmp << DesktopSettings::Favorites << DesktopSettings::Environment << DesktopSettings::Session << DesktopSettings::Desktop << DesktopSettings::Panels << DesktopSettings::Plugins << DesktopSettings::Keys << DesktopSettings::ContextMenu << DesktopSettings::Animation << DesktopSettings::ScreenSaver << DesktopSettings::WM; + }else if(runmode == DesktopSettings::SystemInterface){ + tmp << DesktopSettings::Favorites << DesktopSettings::Environment << DesktopSettings::Session; + } + return tmp; +} + QString DesktopSettings::rel_path(DesktopSettings::File file){ QString name; switch(file){ @@ -236,10 +259,16 @@ QString DesktopSettings::rel_path(DesktopSettings::File file){ name="contextmenu"; break; case DesktopSettings::Keys: name="keys"; break; - case DesktopSettings::Theme: - name="theme"; break; case DesktopSettings::Animation: name="animations"; break; + case DesktopSettings::Panels: + name="panels"; break; + case DesktopSettings::Plugins: + name="plugins"; break; + case DesktopSettings::ScreenSaver: + name="screensaver"; break; + case DesktopSettings::WM: + name="windows"; break; } return FILEPREFIX+name+".conf"; } diff --git a/src-qt5/core/libLumina/DesktopSettings.h b/src-qt5/core/libLumina/DesktopSettings.h index dcb10bb6..efb3776e 100644 --- a/src-qt5/core/libLumina/DesktopSettings.h +++ b/src-qt5/core/libLumina/DesktopSettings.h @@ -25,7 +25,8 @@ class DesktopSettings : public QObject{ Q_OBJECT public: - enum File{ System, Favorites, Environment, Session, Desktop, ContextMenu, Keys, Theme, Animation }; + enum File{ System, Favorites, Environment, Session, Desktop, Panels, Plugins, ContextMenu, Keys, Animation, ScreenSaver, WM}; + //Changes to this enum need to be added to the "filesForRunMode()" and "rel_path()" functions as well DesktopSettings(QObject *parent = 0); ~DesktopSettings(); @@ -37,6 +38,7 @@ public: void stop(); //Main Read/Write functions + QList< DesktopSettings::File > writableFiles(); //return the list of all writable files QVariant value(DesktopSettings::File, QString variable, QVariant defaultvalue); bool setValue(DesktopSettings::File, QString variable, QVariant value); QStringList keys(DesktopSettings::File); //return a list of all variables which are available in this file @@ -51,9 +53,13 @@ private: QHash< DesktopSettings::File, QStringList > files; //location hash for where files are actually located on disk QHash< QString, QSettings*> settings; //location hash for the settings files themselves + //Functions void parseSystemSettings(); //run at start - determine the RunMode for this user/session void locateFiles(); //run at start - finds the locations of the various files (based on RunMode) void touchFile(QString path); //used to create an empty file so it can be watched for changes later + + //The two functions which define the public "File" enumeration (both need updates when the enum changes) + QList< DesktopSettings::File > filesForRunMode(RunMode mode); QString rel_path(DesktopSettings::File); //return the relative file path (starting with "/") private slots: diff --git a/src-qt5/core/libLumina/LDesktopUtils.cpp b/src-qt5/core/libLumina/LDesktopUtils.cpp index b79f777d..54e660e6 100644 --- a/src-qt5/core/libLumina/LDesktopUtils.cpp +++ b/src-qt5/core/libLumina/LDesktopUtils.cpp @@ -16,7 +16,7 @@ static QStringList fav; QString LDesktopUtils::LuminaDesktopVersion(){ - QString ver = "1.3.2"; + QString ver = "1.3.3"; #ifdef GIT_VERSION ver.append( QString(" (Git Revision: %1)").arg(GIT_VERSION) ); #endif @@ -143,6 +143,8 @@ void LDesktopUtils::upgradeFavorites(int){ //fromoldversionnumber void LDesktopUtils::LoadSystemDefaults(bool skipOS){ //Will create the Lumina configuration files based on the current system template (if any) qDebug() << "Loading System Defaults"; + bool skipmime = QFile::exists( QString(getenv("XDG_CONFIG_HOME"))+"/lumina-mimapps.list" ); + qDebug() << " - Skipping mimetype default apps" << skipmime; QStringList sysDefaults; if(!skipOS){ sysDefaults = LUtils::readFile(LOS::AppPrefix()+"etc/luminaDesktop.conf"); } if(sysDefaults.isEmpty() && !skipOS){ sysDefaults = LUtils::readFile(LOS::AppPrefix()+"etc/luminaDesktop.conf.dist"); } @@ -193,18 +195,18 @@ void LDesktopUtils::LoadSystemDefaults(bool skipOS){ if(var=="session_enablenumlock"){ sset = "EnableNumlock="+ istrue; } else if(var=="session_playloginaudio"){ sset = "PlayStartupAudio="+istrue; } else if(var=="session_playlogoutaudio"){ sset = "PlayLogoutAudio="+istrue; } - else if(var=="session_default_terminal"){ + else if(var=="session_default_terminal" && !skipmime){ LXDG::setDefaultAppForMime("application/terminal", val); //sset = "default-terminal="+val; - }else if(var=="session_default_filemanager"){ + }else if(var=="session_default_filemanager" && !skipmime){ LXDG::setDefaultAppForMime("inode/directory", val); //sset = "default-filemanager="+val; //loset = "directory="+val; - }else if(var=="session_default_webbrowser"){ + }else if(var=="session_default_webbrowser" && !skipmime){ //loset = "webbrowser="+val; LXDG::setDefaultAppForMime("x-scheme-handler/http", val); LXDG::setDefaultAppForMime("x-scheme-handler/https", val); - }else if(var=="session_default_email"){ + }else if(var=="session_default_email" && !skipmime){ LXDG::setDefaultAppForMime("application/email",val); //loset = "email="+val; } @@ -225,7 +227,7 @@ void LDesktopUtils::LoadSystemDefaults(bool skipOS){ // -- MIMETYPE DEFAULTS -- tmp = sysDefaults.filter("mime_default_"); - for(int i=0; i<tmp.length(); i++){ + for(int i=0; i<tmp.length() && !skipmime; i++){ if(tmp[i].startsWith("#") || !tmp[i].contains("=") ){ continue; } QString var = tmp[i].section("=",0,0).toLower().simplified(); QString val = tmp[i].section("=",1,1).section("#",0,0).simplified(); @@ -337,6 +339,16 @@ void LDesktopUtils::LoadSystemDefaults(bool skipOS){ else if(var=="favorites_remove"){ qDebug() << " - Removing:"; LDesktopUtils::removeFavorite(val); } } + tmp = sysDefaults.filter("desktoplinks_"); + QString desktopFolder = QDir::homePath()+"/Desktop/"; //need to make this translatable and dynamic later + for(int i=0; i<tmp.length(); i++){ + if(tmp[i].startsWith("#") || !tmp[i].contains("=") ){ continue; } + QString var = tmp[i].section("=",0,0).toLower().simplified(); + QString val = tmp[i].section("=",1,1).section("#",0,0).simplified(); + val = LUtils::AppToAbsolute(val); //turn any relative files into absolute + if(var=="desktoplinks_add" && QFile::exists(val) && !QFile::exists(desktopFolder+val.section("/",-1)) ){ QFile::link(val, desktopFolder+val.section("/",-1)); } + } + // -- QUICKLAUNCH -- tmp = sysDefaults.filter("quicklaunch_"); if(tmp.isEmpty()){ tmp = sysDefaults.filter("quicklaunch."); } @@ -409,6 +421,7 @@ void LDesktopUtils::LoadSystemDefaults(bool skipOS){ } } } + } //qDebug() << " - Final Theme Color:" << themesettings[1]; @@ -419,7 +432,16 @@ void LDesktopUtils::LoadSystemDefaults(bool skipOS){ dir.mkpath(setdir); } //Now save the settings files - if(setTheme){ LTHEME::setCurrentSettings( themesettings[0], themesettings[1], themesettings[2], themesettings[3], themesettings[4]); } + if(setTheme){ + LTHEME::setCurrentSettings( themesettings[0], themesettings[1], themesettings[2], themesettings[3], themesettings[4]); + QSettings themeset("lthemeengine","lthemeengine"); + themeset.setValue("Appearance/icon_theme",themesettings[2]); + //Quick hack for a "dark" theme/color to be uniform across the desktop/applications + if(themesettings[0].contains("DarkGlass") || themesettings[1].contains("Black")){ + themeset.setValue("Appearance/custom_palette", true); + themeset.setValue("Appearance/color_scheme_path", LOS::LuminaShare().section("/",0,-3)+"/lthemeengine/colors/darker.conf"); + } + } LUtils::writeFile(setdir+"/sessionsettings.conf", sesset, true); LUtils::writeFile(setdir+"/desktopsettings.conf", deskset, true); diff --git a/src-qt5/core/libLumina/LDesktopUtils.h b/src-qt5/core/libLumina/LDesktopUtils.h index b21bc78a..a9b44c67 100644 --- a/src-qt5/core/libLumina/LDesktopUtils.h +++ b/src-qt5/core/libLumina/LDesktopUtils.h @@ -30,7 +30,7 @@ public: static QString findQuickPluginFile(QString ID); static QStringList listQuickPlugins(); //List of valid ID's static QStringList infoQuickPlugin(QString ID); //Returns: [Name, Description, Icon] - + //Various functions for the favorites sub-system // Formatting Note: "<name>::::[dir/app/<mimetype>]::::<path>" // the <name> field might not be used for "app" flagged entries @@ -40,7 +40,7 @@ public: static bool addFavorite(QString path, QString name = ""); static void removeFavorite(QString path); static void upgradeFavorites(int fromoldversionnumber); - + //Load the default setup for the system static void LoadSystemDefaults(bool skipOS = false); static bool checkUserFiles(QString lastversion); //returns true if something changed diff --git a/src-qt5/core/libLumina/LIconCache.cpp b/src-qt5/core/libLumina/LIconCache.cpp index 70c360fb..84428546 100644 --- a/src-qt5/core/libLumina/LIconCache.cpp +++ b/src-qt5/core/libLumina/LIconCache.cpp @@ -184,6 +184,27 @@ void LIconCache::loadIcon(QLabel *label, QString icon, bool noThumb){ if(needload){ startReadFile(icon, idata.fullpath); } } +void LIconCache::loadIcon(QMenu *action, QString icon, bool noThumb){ + if(icon.isEmpty()){ return; } + if(isThemeIcon(icon)){ + action->setIcon( iconFromTheme(icon)); + return ; + } + //See if the icon has already been loaded into the HASH + bool needload = !HASH.contains(icon); + if(!needload){ + if(!noThumb && !HASH[icon].thumbnail.isNull()){ action->setIcon( HASH[icon].thumbnail ); return; } + else if(!HASH[icon].icon.isNull()){ action->setIcon( HASH[icon].icon ); return; } + } + //Need to load the icon + icon_data idata; + if(HASH.contains(icon)){ idata = HASH.value(icon); } + else { idata = createData(icon); } + idata.pendingMenus << QPointer<QMenu>(action); //save this button for later + HASH.insert(icon, idata); + if(needload){ startReadFile(icon, idata.fullpath); } +} + void LIconCache::clearIconTheme(){ //use when the icon theme changes to refresh all requested icons QStringList keys = HASH.keys(); @@ -282,6 +303,8 @@ void LIconCache::startReadFile(QString id, QString path){ idat.pendingLabels.clear(); for(int i=0; i<idat.pendingActions.length(); i++){ if(!idat.pendingActions[i].isNull()){ idat.pendingActions[i]->setIcon(idat.icon); } } idat.pendingActions.clear(); + for(int i=0; i<idat.pendingMenus.length(); i++){ if(!idat.pendingMenus[i].isNull()){ idat.pendingMenus[i]->setIcon(idat.icon); } } + idat.pendingMenus.clear(); //Now update the hash and let the world know it is available now HASH.insert(id, idat); this->emit IconAvailable(id); diff --git a/src-qt5/core/libLumina/LIconCache.h b/src-qt5/core/libLumina/LIconCache.h index 428ffcab..691d328c 100644 --- a/src-qt5/core/libLumina/LIconCache.h +++ b/src-qt5/core/libLumina/LIconCache.h @@ -4,7 +4,7 @@ // Available under the 3-clause BSD license // See the LICENSE file for full details //=========================================== -// This is a simple class for loading/serving icon files +// This is a simple class for loading/serving icon files // from the icon theme or local filesystem //=========================================== #include <QHash> @@ -26,6 +26,7 @@ struct icon_data{ QList<QPointer<QLabel> > pendingLabels; QList<QPointer<QAbstractButton> > pendingButtons; QList<QPointer<QAction> > pendingActions; + QList<QPointer<QMenu> > pendingMenus; QIcon icon; QIcon thumbnail; }; @@ -50,6 +51,7 @@ public: void loadIcon(QAbstractButton *button, QString icon, bool noThumb = false); void loadIcon(QLabel *label, QString icon, bool noThumb = false); void loadIcon(QAction *action, QString icon, bool noThumb = false); + void loadIcon(QMenu *action, QString icon, bool noThumb = false); QIcon loadIcon(QString icon, bool noThumb = false); //generic loading routine - does not background the loading of icons when not in the cache diff --git a/src-qt5/core/libLumina/LUtils.cpp b/src-qt5/core/libLumina/LUtils.cpp index fa0173dc..491778ca 100644 --- a/src-qt5/core/libLumina/LUtils.cpp +++ b/src-qt5/core/libLumina/LUtils.cpp @@ -26,7 +26,7 @@ inline QStringList ProcessRun(QString cmd, QStringList args){ if(args.isEmpty()){ proc.start(cmd, QIODevice::ReadOnly); }else{ - proc.start(cmd,args ,QIODevice::ReadOnly); + proc.start(cmd,args ,QIODevice::ReadOnly); } QString info; while(!proc.waitForFinished(1000)){ @@ -37,7 +37,7 @@ inline QStringList ProcessRun(QString cmd, QStringList args){ } out[0] = QString::number(proc.exitCode()); out[1] = info+QString(proc.readAllStandardOutput()); - return out; + return out; } //============= // LUtils Functions @@ -59,7 +59,6 @@ int LUtils::runCmd(QString cmd, QStringList args){ return ret;*/ QFuture<QStringList> future = QtConcurrent::run(ProcessRun, cmd, args); return future.result()[0].toInt(); //turn it back into an integer return code - } QStringList LUtils::getCmdOutput(QString cmd, QStringList args){ @@ -72,7 +71,7 @@ QStringList LUtils::getCmdOutput(QString cmd, QStringList args){ if(args.isEmpty()){ proc.start(cmd); }else{ - proc.start(cmd,args); + proc.start(cmd,args); } //if(!proc.waitForStarted(30000)){ return QStringList(); } //process never started - max wait of 30 seconds while(!proc.waitForFinished(300)){ @@ -118,7 +117,7 @@ bool LUtils::isValidBinary(QString& bin){ //Relative path: search for it on the current "PATH" settings QStringList paths = QString(qgetenv("PATH")).split(":"); for(int i=0; i<paths.length(); i++){ - if(QFile::exists(paths[i]+"/"+bin)){ bin = paths[i]+"/"+bin; break;} + if(QFile::exists(paths[i]+"/"+bin)){ bin = paths[i]+"/"+bin; break;} } } //bin should be the full path by now @@ -150,7 +149,7 @@ QSettings* LUtils::openSettings(QString org, QString name, QObject *parent){ }else{ return (new QSettings(filepath, QSettings::IniFormat, parent)); } - + } QStringList LUtils::systemApplicationDirs(){ @@ -164,7 +163,7 @@ QStringList LUtils::systemApplicationDirs(){ for(int i=0; i<appDirs.length(); i++){ if( QFile::exists(appDirs[i]+"/applications") ){ out << appDirs[i]+"/applications"; - //Also check any subdirs within this directory + //Also check any subdirs within this directory // (looking at you KDE - stick to the standards!!) out << LUtils::listSubDirectories(appDirs[i]+"/applications"); } @@ -197,7 +196,7 @@ QString LUtils::GenerateOpenTerminalExec(QString term, QString dirpath){ }else if(term=="konsole" || term == "qterminal"){ exec = term+" --workdir \""+dirpath+"\""; }else{ - //-e is the parameter for most of the terminal appliction to execute an external command. + //-e is the parameter for most of the terminal appliction to execute an external command. //In this case we start a shell in the selected directory //Need the user's shell first QString shell = QString(getenv("SHELL")); @@ -253,12 +252,18 @@ QString LUtils::AppToAbsolute(QString path){ return path; } +QStringList LUtils::videoExtensions() { + static QStringList vidExtensions; + vidExtensions << "avi" << "mkv" << "mp4" << "mov" << "webm" << "wmv"; + return vidExtensions; +} + QStringList LUtils::imageExtensions(bool wildcards){ //Note that all the image extensions are lowercase!! static QStringList imgExtensions; if(imgExtensions.isEmpty()){ QList<QByteArray> fmt = QImageReader::supportedImageFormats(); - for(int i=0; i<fmt.length(); i++){ + for(int i=0; i<fmt.length(); i++){ if(wildcards){ imgExtensions << "*."+QString::fromLocal8Bit(fmt[i]); } else{ imgExtensions << QString::fromLocal8Bit(fmt[i]); } } @@ -308,7 +313,7 @@ QStringList LUtils::imageExtensions(bool wildcards){ qDebug() << "Loading System Encoding:" << langEnc; } //Load current encoding for this locale - QTextCodec::setCodecForLocale( QTextCodec::codecForName(langEnc.toUtf8()) ); + QTextCodec::setCodecForLocale( QTextCodec::codecForName(langEnc.toUtf8()) ); return cTrans; } @@ -379,7 +384,7 @@ void LUtils::setLocaleEnv(QString lang, QString msg, QString time, QString num,Q else{ if(!ctype.contains(".")){ ctype.append(".UTF-8"); } setenv("LC_CTYPE",ctype.toUtf8(),1); - } + } } QString LUtils::currentLocale(){ diff --git a/src-qt5/core/libLumina/LUtils.h b/src-qt5/core/libLumina/LUtils.h index 4ad05ca1..a494d4da 100644 --- a/src-qt5/core/libLumina/LUtils.h +++ b/src-qt5/core/libLumina/LUtils.h @@ -55,7 +55,7 @@ public: //Create the exec string to open a terminal in a particular directory static QString GenerateOpenTerminalExec(QString term, QString dirpath); - + //List all the sub-directories of a parent dir (recursive) static QStringList listSubDirectories(QString dir, bool recursive = true); @@ -65,18 +65,19 @@ public: //Get the list of all file extensions which Qt can read (lowercase) static QStringList imageExtensions(bool wildcards = false); - + static QStringList videoExtensions(); + //Load a translation file for a Lumina Project static QTranslator* LoadTranslation(QApplication *app, QString appname, QString locale = "", QTranslator *cTrans = 0); //Other localization shortcuts static QStringList knownLocales(); //Note: This only lists locales known to Lumina (so the i18n files need to be installed) static void setLocaleEnv(QString lang, QString msg="", QString time="", QString num="" ,QString money="",QString collate="", QString ctype=""); static QString currentLocale(); - + //Number format conversions static double DisplaySizeToBytes(QString num); //Turn a display size (like 50M or 50KB) into a double for calculations (bytes) static QString BytesToDisplaySize(qint64 bytes); //convert into a readable size (like 50M or 50KB) - + static QString SecondsToDisplay(int secs); //convert into a readable time }; #endif diff --git a/src-qt5/core/libLumina/LuminaSingleApplication.cpp b/src-qt5/core/libLumina/LuminaSingleApplication.cpp index 86248666..6811d147 100644 --- a/src-qt5/core/libLumina/LuminaSingleApplication.cpp +++ b/src-qt5/core/libLumina/LuminaSingleApplication.cpp @@ -107,17 +107,17 @@ void LSingleApplication::PerformLockChecks(){ QLocalSocket socket(this); socket.connectToServer(cfile); socket.waitForConnected(); - if(!socket.isValid() || socket.state()!=QLocalSocket::ConnectedState){ + if(!socket.isValid() || socket.state()!=QLocalSocket::ConnectedState){ //error - could not forward info for some reason qDebug() << " - Could not connect to locking process: exiting..."; - exit(1); - } - - qDebug() << " - Forwarding inputs to locking process and closing down this instance..."; + exit(1); + } + + qDebug() << " - Forwarding inputs to locking process and closing down this instance..."; socket.write( inputlist.join("::::").toLocal8Bit() ); socket.waitForDisconnected(500); //max out at 1/2 second (only hits this if no inputs) } - + } //New messages detected diff --git a/src-qt5/core/libLumina/LuminaThemes.cpp b/src-qt5/core/libLumina/LuminaThemes.cpp index 85d43925..ddbc7b37 100644 --- a/src-qt5/core/libLumina/LuminaThemes.cpp +++ b/src-qt5/core/libLumina/LuminaThemes.cpp @@ -23,7 +23,7 @@ //#include "qxcbcursor.h" //needed to prod Qt to refresh the mouse cursor theme //#include <QCursor> -QStringList LTHEME::availableSystemThemes(){ +QStringList LTHEME::availableSystemThemes(){ //returns: [name::::path] for each item QDir dir(LOS::LuminaShare()+"themes"); QStringList list = dir.entryList(QStringList() <<"*.qss.template", QDir::Files, QDir::Name); @@ -31,7 +31,7 @@ QStringList LTHEME::availableSystemThemes(){ //Format the output entry [<name>::::<fullpath>] list[i] = list[i].section(".qss.",0,0)+"::::"+dir.absoluteFilePath(list[i]); } - return list; + return list; } QStringList LTHEME::availableLocalThemes(){ //returns: [name::::path] for each item @@ -52,7 +52,7 @@ QStringList LTHEME::availableSystemColors(){ //returns: [name::::path] for each //Format the output entry [<name>::::<fullpath>] list[i] = list[i].section(".qss.",0,0)+"::::"+dir.absoluteFilePath(list[i]); } - return list; + return list; } QStringList LTHEME::availableLocalColors(){ //returns: [name::::path] for each item @@ -62,7 +62,7 @@ QStringList LTHEME::availableLocalColors(){ //returns: [name::::path] for each //Format the output entry [<name>::::<fullpath>] list[i] = list[i].section(".qss.",0,0)+"::::"+dir.absoluteFilePath(list[i]); } - return list; + return list; } QStringList LTHEME::availableSystemIcons(){ //returns: [name] for each item @@ -72,7 +72,7 @@ QStringList LTHEME::availableSystemIcons(){ //returns: [name] for each item xdd << QString(getenv("XDG_DATA_DIRS")).split(":"); for(int i=0; i<xdd.length(); i++){ if(QFile::exists(xdd[i]+"/icons")){ - paths << xdd[i]+"/icons"; + paths << xdd[i]+"/icons"; } } //Now get all the icon themes in these directories @@ -83,8 +83,8 @@ QStringList LTHEME::availableSystemIcons(){ //returns: [name] for each item tmpthemes = dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot, QDir::Name); for(int j=0; j<tmpthemes.length(); j++){ if(tmpthemes[j].startsWith("default")){ continue; } - if(QFile::exists(dir.absoluteFilePath(tmpthemes[j]+"/index.theme")) || - QFile::exists(dir.absoluteFilePath(tmpthemes[j]+"/index.desktop")) ){ themes << tmpthemes[j]; } + if( (QFile::exists(dir.absoluteFilePath(tmpthemes[j]+"/index.theme")) || + QFile::exists(dir.absoluteFilePath(tmpthemes[j]+"/index.desktop")) ) ){ themes << tmpthemes[j]; } } } } @@ -92,21 +92,32 @@ QStringList LTHEME::availableSystemIcons(){ //returns: [name] for each item themes.sort(); return themes; } - + QStringList LTHEME::availableSystemCursors(){ //returns: [name] for each item - QStringList paths; paths << LOS::SysPrefix()+"lib/X11/icons/" << LOS::AppPrefix()+"lib/X11/icons/"; - QStringList out; + QStringList paths; + paths << QDir::homePath()+"/.icons"; + QStringList xdd = QString(getenv("XDG_DATA_HOME")).split(":"); + xdd << QString(getenv("XDG_DATA_DIRS")).split(":"); + for(int i=0; i<xdd.length(); i++){ + if(QFile::exists(xdd[i]+"/icons")){ + paths << xdd[i]+"/icons"; + } + } + //Now get all the icon themes in these directories + QStringList themes, tmpthemes; + QDir dir; for(int i=0; i<paths.length(); i++){ - if( !QFile::exists(paths[i]) ){ continue; } - QDir dir(paths[i]); - QStringList tmp = dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot, QDir::Name); - for(int j=0; j<tmp.length(); j++){ - if(QFile::exists(paths[i]+tmp[j]+"/cursors")){ - out << tmp[j]; //good theme - save it to the output list - } + if(dir.cd(paths[i])){ + tmpthemes = dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot, QDir::Name); + for(int j=0; j<tmpthemes.length(); j++){ + if(tmpthemes[j].startsWith("default")){ continue; } + if( QFile::exists(dir.absoluteFilePath(tmpthemes[j]+"/cursors")) ){ themes << tmpthemes[j]; } + } } } - return out; + themes.removeDuplicates(); + themes.sort(); + return themes; } //Save a new theme/color file @@ -119,7 +130,7 @@ bool LTHEME::saveLocalTheme(QString name, QStringList contents){ bool LTHEME::saveLocalColors(QString name, QStringList contents){ QString localdir = QString(getenv("XDG_CONFIG_HOME"))+"/lumina-desktop/colors/"; if(!QFile::exists(localdir)){ QDir dir; dir.mkpath(localdir); } - return LUtils::writeFile(localdir+name+".qss.colors", contents, true); + return LUtils::writeFile(localdir+name+".qss.colors", contents, true); } //Return the currently selected Theme/Colors/Icons @@ -129,21 +140,23 @@ QStringList LTHEME::currentSettings(){ //returns [theme path, colorspath, iconsn for(int i=0; i<settings.length(); i++){ if(settings[i].startsWith("THEMEFILE=")){ out[0] = settings[i].section("=",1,1).simplified(); } else if(settings[i].startsWith("COLORFILE=")){ out[1] = settings[i].section("=",1,1).simplified(); } - else if(settings[i].startsWith("ICONTHEME=")){ out[2] = settings[i].section("=",1,1).simplified(); } + //else if(settings[i].startsWith("ICONTHEME=")){ out[2] = settings[i].section("=",1,1).simplified(); } else if(settings[i].startsWith("FONTFAMILY=")){ out[3] = settings[i].section("=",1,1).simplified(); } else if(settings[i].startsWith("FONTSIZE=")){ out[4] = settings[i].section("=",1,1).simplified(); } } + QSettings engineset("lthemeengine","lthemeengine"); + out[2]=engineset.value("Appearance/icon_theme", "material-design-light").toString(); bool nofile = settings.isEmpty(); if(out[0].isEmpty() || !QFile::exists(out[0]) ){ out[0] = LOS::LuminaShare()+"themes/Lumina-default.qss.template"; } if(out[1].isEmpty() || !QFile::exists(out[1]) ){ out[1] = LOS::LuminaShare()+"colors/Lumina-Glass.qss.colors"; } if(out[3].isEmpty()){ out[3] = QFont().defaultFamily(); } - if(out[4].isEmpty()){ + if(out[4].isEmpty()){ int num = QFont().pointSize(); out[4] = QString::number(num)+"pt"; //Check point size first if(num<0){ num = QFont().pixelSize(); out[4] = QString::number(num)+"px";} //Now check pixel size if(num<0){ out[4] = "9pt"; } //Now hard-code a fallback (just in case) } if(nofile){ setCurrentSettings(out[0], out[1], out[2], out[3], out[4]); } - + return out; } @@ -167,8 +180,14 @@ QString LTHEME::currentCursor(){ //Change the current Theme/Colors/Icons bool LTHEME::setCurrentSettings(QString themepath, QString colorpath, QString iconname, QString font, QString fontsize){ - QIcon::setThemeName(iconname); - //Now save the theme settings file + //QIcon::setThemeName(iconname); + QSettings engineset("lthemeengine","lthemeengine"); + engineset.setValue("Appearance/icon_theme", iconname); + //engineset.setValue("Appearance/color_scheme_path", colorpath); //re-enable this once the color scheme has been synced with lthemeengine + //Need to add theme path saving here too later + + + //Now save the theme settings file QStringList contents; contents << "THEMEFILE="+themepath; contents << "COLORFILE="+colorpath; @@ -195,13 +214,13 @@ bool LTHEME::setCursorTheme(QString cursorname){ bool changed = false; QString newval = "Inherits="+cursorname; for(int i=0; i<info.length() && !changed; i++){ - if(info[i]=="[Icon Theme]"){ + if(info[i]=="[Icon Theme]"){ insection = true; - }else if( info[i].startsWith("[") && insection){ + }else if( info[i].startsWith("[") && insection){ //Section does not have the setting - add it - info.insert(i, newval); + info.insert(i, newval); changed =true; - }else if( info[i].startsWith("[") ){ + }else if( info[i].startsWith("[") ){ insection = false; }else if(insection && info[i].startsWith("Inherits=")){ info[i] = newval; //replace the current setting diff --git a/src-qt5/core/libLumina/LuminaXDG.cpp b/src-qt5/core/libLumina/LuminaXDG.cpp index 01b3305e..ab1000ab 100644 --- a/src-qt5/core/libLumina/LuminaXDG.cpp +++ b/src-qt5/core/libLumina/LuminaXDG.cpp @@ -44,7 +44,7 @@ void XDGDesktop::sync(){ //Get the current localization code type = XDGDesktop::APP; //assume this initially if we read the file properly QString lang = QLocale::system().name(); //lang code - QString slang = lang.section("_",0,0); //short lang code + QString slang = lang.section("_",0,0); //short lang code //Now start looping over the information XDGDesktopAction CDA; //current desktop action bool insection=false; @@ -53,14 +53,14 @@ void XDGDesktop::sync(){ QString line = file[i]; //if(filePath.contains("pcbsd")){ qDebug() << " - Check Line:" << line << inaction << insection; } //Check if this is the end of a section - if(line.startsWith("[") && inaction){ + if(line.startsWith("[") && inaction){ insection=false; inaction=false; //Add the current Action structure to the main desktop structure if appropriate if(!CDA.ID.isEmpty()){ actions << CDA; CDA = XDGDesktopAction(); } }else if(line.startsWith("[")){ insection=false; inaction = false; } //Now check if this is the beginning of a section if(line=="[Desktop Entry]"){ insection=true; continue; } - else if(line.startsWith("[Desktop Action ")){ + else if(line.startsWith("[Desktop Action ")){ //Grab the ID of the action out of the label CDA.ID = line.section("]",0,0).section("Desktop Action",1,1).simplified(); inaction = true; @@ -73,26 +73,26 @@ void XDGDesktop::sync(){ var = var.section("[",0,0).simplified(); //remove the localization QString val = line.section("=",1,50).simplified(); //------------------- - if(var=="Name"){ + if(var=="Name"){ if(insection){ - if(name.isEmpty() && loc.isEmpty()){ name = val; } - else if(name.isEmpty() && loc==slang){ name = val; } //short locale code - else if(loc == lang){ name = val; } + if(loc==slang){ name = val;} //short locale code + else if(loc==lang){ name = val;} + else if(name.isEmpty() && loc.isEmpty()){ /*qDebug() << "Empty" << val;*/ name = val; } }else if(inaction){ if(CDA.name.isEmpty() && loc.isEmpty()){ CDA.name = val; } else if(CDA.name.isEmpty() && loc==slang){ CDA.name = val; } //short locale code - else if(loc == lang){ CDA.name = val; } + else if(loc == lang){ CDA.name = val; } } //hasName = true; - }else if(var=="GenericName" && insection){ + }else if(var=="GenericName" && insection){ if(genericName.isEmpty() && loc.isEmpty()){ genericName = val; } else if(genericName.isEmpty() && loc==slang){ genericName = val; } //short locale code else if(loc == lang){ genericName = val; } - }else if(var=="Comment" && insection){ + }else if(var=="Comment" && insection){ if(comment.isEmpty() && loc.isEmpty()){ comment = val; } else if(comment.isEmpty() && loc==slang){ comment = val; } //short locale code else if(loc == lang){ comment = val; } - }else if(var=="Icon"){ + }else if(var=="Icon"){ if(insection){ if(icon.isEmpty() && loc.isEmpty()){ icon = val; } else if(icon.isEmpty() && loc==slang){ icon = val; } //short locale code @@ -107,7 +107,7 @@ void XDGDesktop::sync(){ else if(var=="Exec"){ if(insection && exec.isEmpty() ){ exec = val; } else if(inaction && CDA.exec.isEmpty() ){ CDA.exec = val; } - } + } else if( (var=="Path") && (path.isEmpty() ) && insection){ path = val; } else if(var=="NoDisplay" && !isHidden && insection){ isHidden = (val.toLower()=="true"); } else if(var=="Hidden" && !isHidden && insection){ isHidden = (val.toLower()=="true"); } @@ -117,7 +117,7 @@ void XDGDesktop::sync(){ else if(var=="Terminal" && insection){ useTerminal= (val.toLower()=="true"); } else if(var=="Actions" && insection){ actionList = val.split(";",QString::SkipEmptyParts); } else if(var=="MimeType" && insection){ mimeList = val.split(";",QString::SkipEmptyParts); } - else if(var=="Keywords" && insection){ + else if(var=="Keywords" && insection){ if(keyList.isEmpty() && loc.isEmpty()){ keyList = val.split(";",QString::SkipEmptyParts); } else if(loc == lang){ keyList = val.split(";",QString::SkipEmptyParts); } } @@ -136,7 +136,7 @@ void XDGDesktop::sync(){ file.clear(); //done with contents of file //If there are OnlyShowIn desktops listed, add them to the name if( !showInList.isEmpty() && !showInList.contains("Lumina", Qt::CaseInsensitive) ){ - name.append(" ("+showInList.join(", ")+")"); + name.append(" ("+showInList.join(", ")+")"); } //Quick fix for showing "wine" applications (which quite often don't list a category, or have other differences) if(catList.isEmpty() && filePath.contains("/wine/")){ @@ -164,7 +164,7 @@ bool XDGDesktop::isValid(bool showAll){ //if(DEBUG){ qDebug() << "[LXDG] Check File validity:" << dFile.name << dFile.filePath; } switch (type){ case XDGDesktop::BAD: - ok=false; + ok=false; //if(DEBUG){ qDebug() << " - Bad file type"; } break; case XDGDesktop::APP: @@ -182,7 +182,7 @@ bool XDGDesktop::isValid(bool showAll){ break; default: ok=false; - //if(DEBUG){ qDebug() << " - Unknown file type"; } + //if(DEBUG){ qDebug() << " - Unknown file type"; } } if(!showAll){ QString cdesk = getenv("XDG_CURRENT_DESKTOP"); @@ -206,7 +206,7 @@ QString XDGDesktop::getDesktopExec(QString ActionID){ } } } - + if(out.isEmpty()){ return ""; } else if(useTerminal){ //Get the currently default terminal @@ -222,7 +222,7 @@ QString XDGDesktop::getDesktopExec(QString ActionID){ } //Now perform any of the XDG flag substitutions as appropriate (9/2014 standards) if(out.contains("%i") && !icon.isEmpty() ){ out.replace("%i", "--icon \""+icon+"\""); } - if(out.contains("%c")){ + if(out.contains("%c")){ if(!name.isEmpty()){ out.replace("%c", "\""+name+"\""); } else if(!genericName.isEmpty()){ out.replace("%c", "\""+genericName+"\""); } else{ out.replace("%c", "\""+filePath.section("/",-1).section(".desktop",0,0)+"\""); } @@ -250,17 +250,17 @@ QString XDGDesktop::generateExec(QStringList inputfiles, QString ActionID){ } } //Now to the exec replacements as needed - if(exec.contains("%f")){ + if(exec.contains("%f")){ if(inputfiles.isEmpty()){ exec.replace("%f",""); } else{ exec.replace("%f", "\""+inputfiles.first()+"\""); } //Note: can only take one input - }else if(exec.contains("%F")){ + }else if(exec.contains("%F")){ if(inputfiles.isEmpty()){ exec.replace("%F",""); } else{ exec.replace("%F", "\""+inputfiles.join("\" \"")+"\""); } } - if(exec.contains("%u")){ + if(exec.contains("%u")){ if(inputfiles.isEmpty()){ exec.replace("%u",""); } else{ exec.replace("%u", "\""+inputfiles.first()+"\""); } //Note: can only take one input - }else if(exec.contains("%U")){ + }else if(exec.contains("%U")){ if(inputfiles.isEmpty()){ exec.replace("%U",""); } else{ exec.replace("%U", "\""+inputfiles.join("\" \"")+"\""); } } @@ -281,7 +281,7 @@ bool XDGDesktop::saveDesktopFile(bool merge){ info = LUtils::readFile(filePath); //set a couple flags based on the contents before we start iterating through // - determine if a translated field was changed (need to remove all the now-invalid translations) - bool clearName, clearComment, clearGName; + bool clearName, clearComment, clearGName; QString tmp = ""; if(!info.filter("Name=").isEmpty()){ tmp = info.filter("Name=").first().section("=",1,50); } clearName=(tmp!=name); @@ -294,13 +294,13 @@ bool XDGDesktop::saveDesktopFile(bool merge){ //Now start iterating through the file and changing fields as necessary bool insection = false; for(int i=0; i<info.length(); i++){ - if(info[i]=="[Desktop Entry]"){ - insection = true; + if(info[i]=="[Desktop Entry]"){ + insection = true; continue; - }else if(info[i].startsWith("[")){ + }else if(info[i].startsWith("[")){ if(insection){ insertloc = i; } //save this location for later insertions - insection = false; - continue; + insection = false; + continue; } if(!insection || info[i].isEmpty() || info[i].section("#",0,0).simplified().isEmpty()){ continue; } QString var = info[i].section("=",0,0); @@ -331,25 +331,25 @@ bool XDGDesktop::saveDesktopFile(bool merge){ else if(var=="OnlyShowIn"){ info[i] = var+"="+showInList.join(";"); showInList.clear(); } else if(var=="NotShowIn"){ info[i] = var+"="+notShowInList.join(";"); notShowInList.clear(); } else if(var=="URL"){ info[i] = var+"="+url; url.clear(); } - + // --BOOLIAN VALUES-- - else if(var=="Hidden"){ + else if(var=="Hidden"){ if(!autofile){ info.removeAt(i); i--; continue; } else{ info[i] = var+"="+(isHidden ? "true": "false"); isHidden=false;} - }else if(var=="NoDisplay"){ + }else if(var=="NoDisplay"){ if(autofile){ info.removeAt(i); i--; continue; } else{ info[i] = var+"="+(isHidden ? "true": "false"); isHidden=false;} - }else if(var=="Terminal"){ + }else if(var=="Terminal"){ info[i] = var+"="+(useTerminal ? "true": "false"); useTerminal=false; - }else if(var=="StartupNotify"){ + }else if(var=="StartupNotify"){ info[i] = var+"="+(startupNotify ? "true": "false"); startupNotify=false; } // Remove any lines that have been un-set or removed from the file if(info[i].section("=",1,50).simplified().isEmpty()){ info.removeAt(i); i--; } } - + }else{ - //Just write a new file and overwrite any old one + //Just write a new file and overwrite any old one // (pre-set some values here which are always required) info << "[Desktop Entry]"; info << "Version=1.0"; @@ -357,7 +357,7 @@ bool XDGDesktop::saveDesktopFile(bool merge){ else if(type==XDGDesktop::LINK){ info << "Type=Link"; } else if(type==XDGDesktop::DIR){ info << "Type=Dir"; } } - + if(insertloc<0){ insertloc = info.size(); }//put it at the end //Now add in any items that did not exist in the original file if( !exec.isEmpty() ){ info.insert(insertloc,"Exec="+exec); } @@ -379,7 +379,7 @@ bool XDGDesktop::saveDesktopFile(bool merge){ else if(isHidden){ info.insert(insertloc,"NoDisplay=true"); } if( useTerminal){ info.insert(insertloc,"Terminal=true"); } if( startupNotify ){ info.insert(insertloc,"StartupNotify=true"); } - + //Now save the file return LUtils::writeFile(filePath, info, true); } @@ -409,7 +409,7 @@ bool XDGDesktop::setAutoStarted(bool autostart){ } } //Make sure the user-autostart dir is specified, and clean the app structure as necessary - if( !filePath.startsWith(upath) && autostart){ + if( !filePath.startsWith(upath) && autostart){ //Some other non-override autostart file - set it up to open with lumina-open if(!filePath.endsWith(".desktop")){ exec = "lumina-open \""+filePath+"\""; @@ -444,6 +444,39 @@ bool XDGDesktop::setAutoStarted(bool autostart){ return saved; } +void XDGDesktop::addToMenu(QMenu *topmenu){ + if(!this->isValid()){ return; } + if(actions.isEmpty()){ + //Just a single entry point - no extra actions + QAction *act = new QAction(this->name, topmenu); + act->setIcon(LXDG::findIcon(this->icon, "")); + act->setToolTip(this->comment); + act->setWhatsThis(this->filePath); + topmenu->addAction(act); + }else{ + //This app has additional actions - make this a sub menu + // - first the main menu/action + QMenu *submenu = new QMenu(this->name, topmenu); + submenu->setIcon( LXDG::findIcon(this->icon,"") ); + //This is the normal behavior - not a special sub-action (although it needs to be at the top of the new menu) + QAction *act = new QAction(this->name, submenu); + act->setIcon(LXDG::findIcon(this->icon, "")); + act->setToolTip(this->comment); + act->setWhatsThis(this->filePath); + submenu->addAction(act); + //Now add entries for every sub-action listed + for(int sa=0; sa<this->actions.length(); sa++){ + QAction *sact = new QAction( this->actions[sa].name, this); + sact->setIcon(LXDG::findIcon( this->actions[sa].icon, this->icon)); + sact->setToolTip(this->comment); + sact->setWhatsThis("-action \""+this->actions[sa].ID+"\" \""+this->filePath+"\""); + submenu->addAction(sact); + } + topmenu->addMenu(submenu); + } +} + + //====XDGDesktopList Functions ==== XDGDesktopList::XDGDesktopList(QObject *parent, bool watchdirs) : QObject(parent){ synctimer = new QTimer(this); //interval set automatically based on changes/interactions @@ -462,6 +495,14 @@ XDGDesktopList::~XDGDesktopList(){ //nothing special to do here } +XDGDesktopList* XDGDesktopList::instance(){ + static XDGDesktopList *APPLIST = 0; + if(APPLIST==0){ + APPLIST = new XDGDesktopList(0, true); + } + return APPLIST; +} + void XDGDesktopList::watcherChanged(){ if(synctimer->isActive()){ synctimer->stop(); } synctimer->setInterval(1000); //1 second delay before check kicks off @@ -484,7 +525,7 @@ void XDGDesktopList::updateList(){ apps = dir.entryList(QStringList() << "*.desktop",QDir::Files, QDir::Name); for(int a=0; a<apps.length(); a++){ path = dir.absoluteFilePath(apps[a]); - if(files.contains(path) && (files.value(path)->lastRead>QFileInfo(path).lastModified()) ){ + if(files.contains(path) && (files.value(path)->lastRead>QFileInfo(path).lastModified()) ){ //Re-use previous data for this file (nothing changed) found << files[path]->name; //keep track of which files were already found }else{ @@ -503,7 +544,7 @@ void XDGDesktopList::updateList(){ } //end loop over apps } //end loop over appDirs //Save the extra info to the internal lists - if(!firstrun){ + if(!firstrun){ removedApps = oldkeys;//files which were removed newApps = newfiles; //files which were added } @@ -539,6 +580,52 @@ QList<XDGDesktop*> XDGDesktopList::apps(bool showAll, bool showHidden){ return out; } +XDGDesktop* XDGDesktopList::findAppFile(QString filename){ + QStringList keys = files.keys().filter(filename); + QString chk = filename.section("/",-1); + for(int i=0; i<keys.length(); i++){ + if(keys[i] == filename || keys[i].endsWith("/"+chk)){ return files[keys[i]]; } + } + //No matches + return 0; +} + +void XDGDesktopList::populateMenu(QMenu *topmenu, bool byCategory){ + topmenu->clear(); + if(byCategory){ + QHash<QString, QList<XDGDesktop*> > APPS = LXDG::sortDesktopCats( this->apps(false,false) ); + QStringList cats = APPS.keys(); + cats.sort(); //make sure they are alphabetical + for(int i=0; i<cats.length(); i++){ + //Make sure they are translated and have the right icons + QString name, icon; + if(cats[i]=="All"){continue; } //skip this listing for the menu + else if(cats[i] == "Multimedia"){ name = tr("Multimedia"); icon = "applications-multimedia"; } + else if(cats[i] == "Development"){ name = tr("Development"); icon = "applications-development"; } + else if(cats[i] == "Education"){ name = tr("Education"); icon = "applications-education"; } + else if(cats[i] == "Game"){ name = tr("Games"); icon = "applications-games"; } + else if(cats[i] == "Graphics"){ name = tr("Graphics"); icon = "applications-graphics"; } + else if(cats[i] == "Network"){ name = tr("Network"); icon = "applications-internet"; } + else if(cats[i] == "Office"){ name = tr("Office"); icon = "applications-office"; } + else if(cats[i] == "Science"){ name = tr("Science"); icon = "applications-science"; } + else if(cats[i] == "Settings"){ name = tr("Settings"); icon = "preferences-system"; } + else if(cats[i] == "System"){ name = tr("System"); icon = "applications-system"; } + else if(cats[i] == "Utility"){ name = tr("Utility"); icon = "applications-utilities"; } + else if(cats[i] == "Wine"){ name = tr("Wine"); icon = "wine"; } + else{ name = tr("Unsorted"); icon = "applications-other"; } + + QMenu *menu = new QMenu(name, topmenu); + menu->setIcon(LXDG::findIcon(icon,"")); + QList<XDGDesktop*> appL = APPS.value(cats[i]); + for( int a=0; a<appL.length(); a++){ appL[a]->addToMenu(menu); } + topmenu->addMenu(menu); + } //end loop over cats + }else{ + QList<XDGDesktop*> APPS = this->apps(false, false); + for(int i=0; i<APPS.length(); i++){ APPS[i]->addToMenu(topmenu); } + } +} + //==== LFileInfo Functions ==== //Need some extra information not usually available by a QFileInfo void LFileInfo::loadExtraInfo(){ @@ -617,7 +704,14 @@ XDGDesktop* LFileInfo::XDG(){ return desk; } -// -- Check if this is a readable image file (for thumbnail support) +// -- Check if this is a readable video file (for thumbnail support) +bool LFileInfo::isVideo(){ + if(!mime.startsWith("video/")){ return false; } + //Check the hardcoded list of known supported video formats to see if the thumbnail can be generated + return ( !LUtils::videoExtensions().filter(this->suffix().toLower()).isEmpty() ); +} + +// -- Check if this is a readable image file bool LFileInfo::isImage(){ if(!mime.startsWith("image/")){ return false; } //quick return for non-image files //Check the Qt subsystems to see if this image file can be read diff --git a/src-qt5/core/libLumina/LuminaXDG.h b/src-qt5/core/libLumina/LuminaXDG.h index cc250c7e..0f7e7c48 100644 --- a/src-qt5/core/libLumina/LuminaXDG.h +++ b/src-qt5/core/libLumina/LuminaXDG.h @@ -28,7 +28,8 @@ #include <QTextStream> #include <QDateTime> #include <QDebug> - +#include <QMenu> +#include <QAction> // ====================== // FreeDesktop Desktop Actions Framework (data structure) @@ -82,6 +83,9 @@ public: bool saveDesktopFile(bool merge = true); //This will use the "filePath" variable for where to save the file bool setAutoStarted(bool autostart = true); + + //Create a menu entry for this application + void addToMenu(QMenu*); }; // ======================== @@ -93,8 +97,13 @@ public: //Functions XDGDesktopList(QObject *parent = 0, bool watchdirs = false); ~XDGDesktopList(); + + static XDGDesktopList* instance(); + //Main Interface functions QList<XDGDesktop*> apps(bool showAll, bool showHidden); //showAll: include invalid files, showHidden: include NoShow/Hidden files + XDGDesktop* findAppFile(QString filename); + void populateMenu(QMenu *, bool byCategory = true); //Administration variables (not typically used directly) QDateTime lastCheck; @@ -150,6 +159,7 @@ public: //Other file type identification routines bool isImage(); //Is a readable image file (for thumbnail support) + bool isVideo(); //Is a readable video file (for thumbnail support) bool isAVFile(); //Is an audio/video file }; typedef QList<LFileInfo> LFileInfoList; diff --git a/src-qt5/core/libLumina/NativeEmbedWidget.cpp b/src-qt5/core/libLumina/NativeEmbedWidget.cpp index 487d1040..ff0f4734 100644 --- a/src-qt5/core/libLumina/NativeEmbedWidget.cpp +++ b/src-qt5/core/libLumina/NativeEmbedWidget.cpp @@ -8,31 +8,75 @@ #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 -#define NORMAL_WIN_EVENT_MASK (XCB_EVENT_MASK_BUTTON_PRESS | \ - XCB_EVENT_MASK_BUTTON_RELEASE | \ - XCB_EVENT_MASK_POINTER_MOTION | \ - XCB_EVENT_MASK_BUTTON_MOTION | \ - XCB_EVENT_MASK_EXPOSURE | \ - XCB_EVENT_MASK_STRUCTURE_NOTIFY | \ - XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT | \ - XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY | \ - XCB_EVENT_MASK_ENTER_WINDOW| \ - XCB_EVENT_MASK_PROPERTY_CHANGE) +/*inline 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; } -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); + 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); } // ============ @@ -40,12 +84,14 @@ inline void registerClientEvents(WId id){ // ============ //Simplification functions for the XCB/XLib interactions void NativeEmbedWidget::syncWinSize(QSize sz){ - if(WIN==0 ){ return; } + if(WIN==0){ return; } else if(!sz.isValid()){ sz = this->size(); } //use the current widget size //qDebug() << "Sync Window Size:" << sz; - if(sz == winSize){ return; } //no change - const uint32_t valList[2] = {(uint32_t) sz.width(), (uint32_t) sz.height()}; - const uint32_t mask = XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT; + //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 } @@ -56,47 +102,74 @@ void NativeEmbedWidget::syncWidgetSize(QSize 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()); - QTimer::singleShot(0,this, SLOT(repaintWindow())); + reregisterEvents(); + if(!DISABLE_COMPOSITING){ + QTimer::singleShot(0,this, SLOT(repaintWindow())); + } } QImage NativeEmbedWidget::windowImage(QRect geom){ - //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, 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 - xcb_image_destroy(ximg); + //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(); } - //Cleanup the XCB data structures - xcb_free_pixmap(QX11Info::connection(), pix); + //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); - return img; + //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; - //PIXBACK = xcb_generate_id(QX11Info::connection()); - xcb_reparent_window(QX11Info::connection(), WIN->id(), this->winId(), 0, 0); + //Now send the embed event to the app //qDebug() << " - send _XEMBED event"; /*xcb_client_message_event_t event; @@ -115,7 +188,7 @@ bool NativeEmbedWidget::embedWindow(NativeWindow *window){ //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); //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) @@ -126,19 +199,28 @@ bool NativeEmbedWidget::embedWindow(NativeWindow *window){ 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()); - connect(WIN, SIGNAL(VisualChanged()), this, SLOT(repaintWindow()) ); //make sure we repaint the widget on visual change - registerClientEvents(WIN->id()); - registerClientEvents(this->winId()); - qDebug() << "Events Registered:" << WIN->id() << this->winId(); + //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; + //WIN = 0; return true; } @@ -146,69 +228,94 @@ 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(winImage.isNull()){ repaintWindow(); } //make sure we have one image already cached first + 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(); - //showWindow(); - repaintWindow(); //update the cached image right away + //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; } - /*return; //skip the stuff below (not working) - QRect geom = WIN->geometry(); - //Send an artificial configureNotify event to the window with the global position/size included - xcb_configure_notify_event_t event; - event.x = geom.x() + this->pos().x(); - event.y = geom.y() + this->pos().y(); - event.width = this->width(); - event.height = this->height(); - event.border_width = 0; - event.above_sibling = XCB_NONE; - event.override_redirect = false; - event.window = WIN->id(); - event.event = WIN->id(); - event.response_type = XCB_CONFIGURE_NOTIFY; - xcb_send_event(QX11Info::connection(), false, WIN->id(), XCB_EVENT_MASK_STRUCTURE_NOTIFY | XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY, (const char *) &event); - */ - //Just jitter the window size by 1 pixel really quick so the window knows to update it's geometry - QSize sz = this->size(); - uint32_t valList[2] = {(uint32_t) sz.width()-1, (uint32_t) sz.height()}; - uint32_t mask = XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT; - xcb_configure_window(QX11Info::connection(), WIN->id(), mask, valList); - xcb_flush(QX11Info::connection()); - valList[0] = (uint32_t) sz.width(); - xcb_configure_window(QX11Info::connection(), WIN->id(), mask, valList); + //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()); - syncWinSize(); - QTimer::singleShot(10, this, SLOT(repaintWindow()) ); + 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; } + //if(DISABLE_COMPOSITING){ return; } //qDebug() << "Update Window Image:" << !paused; if(paused){ return; } - QImage tmp = windowImage( QRect(QPoint(0,0), this->size()) ); + /*QImage tmp = windowImage( QRect(QPoint(0,0), this->size()) ); if(!tmp.isNull()){ winImage = tmp; - }else{ qDebug() << "Got Null Image!!"; } - this->parentWidget()->update(); + }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){ + if(WIN!=0 && !paused){ syncWinSize(ev->size()); } //syncronize the window with the new widget size } @@ -224,30 +331,106 @@ void NativeEmbedWidget::hideEvent(QHideEvent *ev){ } void NativeEmbedWidget::paintEvent(QPaintEvent *ev){ - if(WIN==0 || DISABLE_COMPOSITING){ QWidget::paintEvent(ev); return; } - else if( winImage.isNull() ){ /*QTimer::singleShot(0, this, SLOT(repaintWindow()) );*/ return; } - else if(paused){ return; } - //else if(this->size()!=winSize){ QTimer::singleShot(0,this, SLOT(syncWinSize())); return; } //do not paint here - waiting to re-sync the sizes - //else if(this->size() != winImage.size()){ QTimer::singleShot(0, this, SLOT(repaintWindow()) ); return; } - //Need to paint the image from the window onto the widget as an overlay + if(WIN==0){ return; } QRect geom = ev->rect(); //atomic updates - geom.adjust(-10,-10,10,10); //add an additional few pixels in each direction to be painted - geom = geom.intersected(QRect(0,0,this->width(), this->height())); //ensure intersection with actual window - if( !QRect(QPoint(0,0),winImage.size()).contains(geom) ){ QTimer::singleShot(0,this, SLOT(repaintWindow()) );return; } + //qDebug() << "Paint Rect:" << geom; + //geom.adjust(-10,-10,10,10); //add an additional few pixels in each direction to be painted + //geom = geom.intersected(QRect(0,0,this->width(), this->height())); //ensure intersection with actual window + QImage img; + if(!paused){ img = windowImage(geom); } + else if(!winImage.isNull()){ + if(winImage.size() == this->size()){ img = winImage.copy(geom); } + else{ img = winImage.scaled(geom.size()); } //this is a fast transformation - might be slightly distorted + } + //Need to paint the image from the window onto the widget as an overlay + QPainter P(this); P.setClipping(true); P.setClipRect(0,0,this->width(), this->height()); + //if(DISABLE_COMPOSITING){ P.fillRect(geom, Qt::black); } //get weird effects when partial-compositing is enabled if you layer transparent window frames above other windows //qDebug() << "Paint Embed Window:" << geom << winImage.size(); - if(winImage.size() == this->size()){ - P.drawImage( geom , winImage, geom, Qt::NoOpaqueDetection); //1-to-1 mapping + //if(winImage.size() == this->size()){ + P.drawImage( geom , img, QRect(QPoint(0,0), img.size()), Qt::NoOpaqueDetection); //1-to-1 mapping //Note: Qt::NoOpaqueDetection Speeds up the paint by bypassing the checks to see if there are [semi-]transparent pixels // Since this is an embedded image - we fully expect there to be transparency all/most of the time. - }else{ - P.drawImage( geom , winImage); - } + // }else{ + //P.drawImage( geom , winImage); //auto-scale it to fit (transforming a static image while paused?) + // } //else{ QImage scaled = winImage.scaled(geom.size()); P.drawImage(geom, scaled); } //P.drawImage( geom , winImage, geom, Qt::NoOpaqueDetection); //1-to-1 mapping //Note: Qt::NoOpaqueDetection Speeds up the paint by bypassing the checks to see if there are [semi-]transparent pixels // Since this is an embedded image - we fully expect there to be transparency all/most of the time. } + +void NativeEmbedWidget::enterEvent(QEvent *ev){ + QWidget::enterEvent(ev); + //qDebug() << "Enter Embed Widget"; + //raiseWindow(); //this->grabMouse(); +} + +void NativeEmbedWidget::leaveEvent(QEvent *ev){ + QWidget::leaveEvent(ev); + /*qDebug() << "Leave Embed Widget"; + QPoint pt = QCursor::pos(); + QPoint relpt = this->parentWidget()->mapFromGlobal(pt); + qDebug() << " - Geom:" << this->geometry() << "Global pt:" << pt << "Relative pt:" << relpt; + if(!this->geometry().contains(relpt) ){ lowerWindow(); }*/ +} + +void NativeEmbedWidget::mouseMoveEvent(QMouseEvent *ev){ + QWidget::mouseMoveEvent(ev); + //Forward this event on to the window +} + +void NativeEmbedWidget::mousePressEvent(QMouseEvent *ev){ + QWidget::mousePressEvent(ev); + //Forward this event on to the window +} + +void NativeEmbedWidget::mouseReleaseEvent(QMouseEvent *ev){ + QWidget::mouseReleaseEvent(ev); + //Forward this event on to the window +} + +/*bool NativeEmbedWidget::nativeEvent(const QByteArray &eventType, void *message, long *result){ + if(eventType=="xcb_generic_event_t" && WIN!=0){ + //Convert to known event type (for X11 systems) + xcb_generic_event_t *ev = static_cast<xcb_generic_event_t *>(message); + //qDebug() << "Got Embed Window Event:" << xcb_event_get_label(ev->response_type & XCB_EVENT_RESPONSE_TYPE_MASK) << xcb_event_get_request_label(ev->response_type); + uint32_t mask = 0; + switch( ev->response_type & XCB_EVENT_RESPONSE_TYPE_MASK){ + case XCB_BUTTON_PRESS: + //This is a mouse button press + mask = XCB_EVENT_MASK_BUTTON_PRESS; + break; + case XCB_BUTTON_RELEASE: + //This is a mouse button release + //qDebug() << "Button Release Event"; + mask = XCB_EVENT_MASK_BUTTON_RELEASE; + break; + case XCB_MOTION_NOTIFY: + //This is a mouse movement event + mask = XCB_EVENT_MASK_POINTER_MOTION; + break; + case XCB_ENTER_NOTIFY: + //This is a mouse movement event when mouse goes over a new window + mask = XCB_EVENT_MASK_ENTER_WINDOW; + break; + case XCB_LEAVE_NOTIFY: + //This is a mouse movement event when mouse goes leaves a window + mask = XCB_EVENT_MASK_LEAVE_WINDOW; + break; + default: + mask = 0; + } + + //Now forward this event on to the embedded window + if(mask!=0){ + qDebug() << " - Got a mouse event"; + xcb_send_event(QX11Info::connection(), true, WIN->id(),mask, (char*) ev); + return true; + } + } + return false; +}*/ diff --git a/src-qt5/core/libLumina/NativeEmbedWidget.h b/src-qt5/core/libLumina/NativeEmbedWidget.h index 65e03c51..16bb46dc 100644 --- a/src-qt5/core/libLumina/NativeEmbedWidget.h +++ b/src-qt5/core/libLumina/NativeEmbedWidget.h @@ -18,6 +18,7 @@ #include <QShowEvent> #include <QHideEvent> #include <QPaintEvent> +#include <QMouseEvent> class NativeEmbedWidget : public QWidget{ Q_OBJECT @@ -25,7 +26,7 @@ private: NativeWindow *WIN; QSize winSize; QImage winImage; - bool paused; + bool paused, hasAlphaChannel; private slots: //Simplification functions @@ -35,6 +36,7 @@ private slots: void showWindow(); QImage windowImage(QRect geom); + void setWinUnpaused(); public: NativeEmbedWidget(QWidget *parent); @@ -42,22 +44,31 @@ public: 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/core/libLumina/NativeEventFilter.cpp b/src-qt5/core/libLumina/NativeEventFilter.cpp index 354dbe76..c13c1fc8 100644 --- a/src-qt5/core/libLumina/NativeEventFilter.cpp +++ b/src-qt5/core/libLumina/NativeEventFilter.cpp @@ -65,7 +65,7 @@ static xcb_ewmh_connection_t EWMH; 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; + //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; } @@ -76,11 +76,14 @@ inline void ParsePropertyEvent(xcb_property_notify_event_t *ev, NativeEventFilte 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; + //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; } } @@ -98,7 +101,7 @@ inline void ParseClientMessageEvent(xcb_client_message_event_t *ev, NativeEventF else if(ev->type==EWMH._NET_WM_STATE){ prop = NativeWindow::States; } if(prop!=NativeWindow::None){ - //if(DEBUG){ + //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); } diff --git a/src-qt5/core/libLumina/NativeEventFilter.h b/src-qt5/core/libLumina/NativeEventFilter.h index 2b184f99..a3be3ef1 100644 --- a/src-qt5/core/libLumina/NativeEventFilter.h +++ b/src-qt5/core/libLumina/NativeEventFilter.h @@ -34,6 +34,7 @@ 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); diff --git a/src-qt5/core/libLumina/NativeWindow.cpp b/src-qt5/core/libLumina/NativeWindow.cpp index 3c76ed00..02cc001e 100644 --- a/src-qt5/core/libLumina/NativeWindow.cpp +++ b/src-qt5/core/libLumina/NativeWindow.cpp @@ -106,6 +106,10 @@ QRect NativeWindow::geometry(){ return geom; } // ==== PUBLIC SLOTS === +void NativeWindow::toggleVisibility(){ + setProperty(NativeWindow::Visible, !property(NativeWindow::Visible).toBool() ); +} + void NativeWindow::requestClose(){ emit RequestClose(winid); } diff --git a/src-qt5/core/libLumina/NativeWindow.h b/src-qt5/core/libLumina/NativeWindow.h index d04815ce..67436259 100644 --- a/src-qt5/core/libLumina/NativeWindow.h +++ b/src-qt5/core/libLumina/NativeWindow.h @@ -78,6 +78,7 @@ public: 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); diff --git a/src-qt5/core/libLumina/NativeWindow.pri b/src-qt5/core/libLumina/NativeWindow.pri index c2ac0137..c906d6fd 100644 --- a/src-qt5/core/libLumina/NativeWindow.pri +++ b/src-qt5/core/libLumina/NativeWindow.pri @@ -2,6 +2,7 @@ # Files QT *= x11extras LIBS *= -lc -lxcb -lxcb-ewmh -lxcb-icccm -lxcb-image -lxcb-composite -lxcb-damage -lxcb-util -lxcb-keysyms -lXdamage +#QT *= -lxcb-render -lxcb-render-util SOURCES *= $${PWD}/NativeWindow.cpp \ $${PWD}/NativeWindowSystem.cpp \ diff --git a/src-qt5/core/libLumina/NativeWindowSystem.cpp b/src-qt5/core/libLumina/NativeWindowSystem.cpp index 71e95a0e..0ee65929 100644 --- a/src-qt5/core/libLumina/NativeWindowSystem.cpp +++ b/src-qt5/core/libLumina/NativeWindowSystem.cpp @@ -45,7 +45,6 @@ #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 | \ @@ -60,13 +59,45 @@ 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_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){ +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{ @@ -255,7 +286,7 @@ 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() ){ qDebug() << "Matched Frame:" << 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]; } } @@ -479,27 +510,27 @@ void NativeWindowSystem::ChangeWindowProperties(NativeWindow* win, QList< Native } 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; + /*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)){ + 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); + 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)){ @@ -524,10 +555,20 @@ void NativeWindowSystem::ChangeWindowProperties(NativeWindow* win, QList< Native 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() ){ - xcb_ewmh_set_active_window(&obj->EWMH, QX11Info::appScreen(), (win->frameId()==0 ?win->id() : win->frameId())); + //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; +/* xcb_client_message_event_t event; event.response_type = XCB_CLIENT_MESSAGE; event.format = 32; event.window = win->id(); @@ -540,6 +581,7 @@ void NativeWindowSystem::ChangeWindowProperties(NativeWindow* win, QList< Native 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()); +*/ } } @@ -553,6 +595,9 @@ void NativeWindowSystem::RegisterVirtualRoot(WId id){ 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(){ @@ -561,7 +606,7 @@ void NativeWindowSystem::setRoot_supportedActions(){ obj->EWMH._NET_WM_ICON, obj->EWMH._NET_WM_ICON_NAME, obj->EWMH._NET_WM_DESKTOP, - obj->ATOMS["_NET_WM_WINDOW_OPACITY"], + /*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, @@ -625,7 +670,7 @@ void NativeWindowSystem::setRoot_desktopWorkarea(QList<QRect> list){ } void NativeWindowSystem::setRoot_activeWindow(WId win){ - xcb_ewmh_set_active_window(&obj->EWMH, QX11Info::appScreen(), 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; @@ -640,7 +685,7 @@ void NativeWindowSystem::setRoot_activeWindow(WId win){ 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()); + xcb_flush(QX11Info::connection());*/ } int NativeWindowSystem::currentWorkspace(){ @@ -656,7 +701,7 @@ int NativeWindowSystem::currentWorkspace(){ //NativeWindowEventFilter interactions void NativeWindowSystem::NewWindowDetected(WId id){ //Make sure this can be managed first - if(findWindow(id, false) != 0){ qDebug() << "Window Already Managed!!!!"; findWindow(id,false)->setProperty(NativeWindow::Visible, true, true); return; } //already managed + 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 @@ -668,7 +713,7 @@ void NativeWindowSystem::NewWindowDetected(WId id){ registerClientEvents(win->id()); NWindows << win; UpdateWindowProperties(win, NativeWindow::allProperties()); - qDebug() << "New Window [& associated ID's]:" << win->id() << win->frameId() << win->property(NativeWindow::RelatedWindows); + 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)) ); @@ -733,6 +778,27 @@ void NativeWindowSystem::WindowPropertyChanged(WId id, NativeWindow::Property pr 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); + } } } diff --git a/src-qt5/core/libLumina/NativeWindowSystem.h b/src-qt5/core/libLumina/NativeWindowSystem.h index 97208c2f..b67ecc94 100644 --- a/src-qt5/core/libLumina/NativeWindowSystem.h +++ b/src-qt5/core/libLumina/NativeWindowSystem.h @@ -105,6 +105,7 @@ public slots: 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); diff --git a/src-qt5/core/libLumina/RootSubWindow-animations.cpp b/src-qt5/core/libLumina/RootSubWindow-animations.cpp index ac813e3a..efab20fe 100644 --- a/src-qt5/core/libLumina/RootSubWindow-animations.cpp +++ b/src-qt5/core/libLumina/RootSubWindow-animations.cpp @@ -11,16 +11,20 @@ QStringList RootSubWindow::validAnimations(NativeWindow::Property prop){ QStringList valid; if(prop == NativeWindow::Visible){ valid << "zoom" << "wipe-center-vertical" << "wipe-center-horizontal" << "shade-top" << "shade-right" << "shade-left" << "shade-bottom"; + }else if(prop == NativeWindow::Size){ + //Note: this is used for pretty much all geometry changes to the window where it is visible both before/after animation + valid << "direct"; } return valid; } void RootSubWindow::loadAnimation(QString name, NativeWindow::Property prop, QVariant nval){ + if(anim->state()==QAbstractAnimation::Running){ return; } //already running animResetProp.clear(); //Special case - random animation each time if(name=="random"){ QStringList valid = validAnimations(prop); - name = valid.at(qrand()%valid.length()); + if(!valid.isEmpty()){ name = valid.at(qrand()%valid.length()); } } //Now setup the animation if(prop == NativeWindow::Visible){ @@ -57,6 +61,7 @@ void RootSubWindow::loadAnimation(QString name, NativeWindow::Property prop, QVa } if(nval.toBool()){ this->setGeometry( anim->startValue().toRect() ); //ensure the window is the initial geom before it becomes visible + //QTimer::singleShot( anim->duration()+5, this, SLOT(activate()) ); }else{ QVariant tmp = anim->startValue(); anim->setStartValue(anim->endValue()); @@ -68,6 +73,19 @@ void RootSubWindow::loadAnimation(QString name, NativeWindow::Property prop, QVa anim->start(); this->show(); } //end of Visibility animation + else if(prop == NativeWindow::Size){ + //This is pretty much all geometry animations where the window is visible->visible + anim->setPropertyName("geometry"); + anim->setStartValue(this->geometry()); + anim->setEndValue(nval.toRect()); + /*if(name==""){ + // TO-DO modify the path from beginning->end somehow + }*/ + // Now start the animation + WinWidget->pause(); + anim->start(); + this->show(); + } } void RootSubWindow::animFinished(){ @@ -87,11 +105,12 @@ void RootSubWindow::animFinished(){ //qDebug() << "Sub Window geometry:" << clientg; WIN->setProperties(QList< NativeWindow::Property>() << NativeWindow::Size << NativeWindow::GlobalPos, QList<QVariant>() << clientg.size() << clientg.topLeft() ); - WinWidget->resyncWindow(); //also let the window know about the current geometry } } + WinWidget->resyncWindow(); //also let the window know about the current geometry } animResetProp = QVariant(); //clear the variable - WinWidget->resume(); - + //QTimer::singleShot(10, WinWidget, SLOT(resume()) ); + WinWidget->resume(); + emit windowAnimFinished(); } diff --git a/src-qt5/core/libLumina/RootSubWindow.cpp b/src-qt5/core/libLumina/RootSubWindow.cpp index 6341f923..5fb8ece4 100644 --- a/src-qt5/core/libLumina/RootSubWindow.cpp +++ b/src-qt5/core/libLumina/RootSubWindow.cpp @@ -10,6 +10,7 @@ #include <QVBoxLayout> #include <QHBoxLayout> #include <QTimer> +#include <QScreen> #define WIN_BORDER 5 @@ -56,7 +57,7 @@ RootSubWindow::ModState RootSubWindow::getStateAtPoint(QPoint pt, bool setoffset if(setoffset){ offset.setX(pt.x()); offset.setY(pt.y()); } //difference from top-left corner return ResizeTopLeft; }else if(pt.x() > (this->width()*4.0/5.0)){ - if(setoffset){ offset.setX(this->width()-pt.x()); offset.setY(pt.y()); } //difference from top-right corner + if(setoffset){ offset.setX(pt.x()-this->width()); offset.setY(pt.y()); } //difference from top-right corner return ResizeTopRight; }else{ if(setoffset){ offset.setX(0); offset.setY(pt.y()); } //difference from top edge (X does not matter) @@ -65,13 +66,13 @@ RootSubWindow::ModState RootSubWindow::getStateAtPoint(QPoint pt, bool setoffset }else if(pt.y() > (this->height()-WIN_BORDER) ){ //One of the bottom options if(pt.x() < this->width()/5){ - if(setoffset){ offset.setX(pt.x()); offset.setY(this->height()-pt.y()); } //difference from bottom-left corner + if(setoffset){ offset.setX(pt.x()); offset.setY(pt.y()-this->height()); } //difference from bottom-left corner return ResizeBottomLeft; }else if(pt.x() > (this->width()*4.0/5.0)){ - if(setoffset){ offset.setX(this->width()-pt.x()); offset.setY(this->height()-pt.y()); } //difference from bottom-right corner + if(setoffset){ offset.setX(pt.x()-this->width()); offset.setY(pt.y()-this->height()); } //difference from bottom-right corner return ResizeBottomRight; }else{ - if(setoffset){ offset.setX(0); offset.setY(this->height() - pt.y()); } //difference from bottom edge (X does not matter) + if(setoffset){ offset.setX(0); offset.setY(pt.y()-this->height()); } //difference from bottom edge (X does not matter) return ResizeBottom; } }else if(pt.x() < WIN_BORDER){ @@ -80,7 +81,7 @@ RootSubWindow::ModState RootSubWindow::getStateAtPoint(QPoint pt, bool setoffset if(setoffset){ offset.setX(pt.x()); offset.setY(pt.y()); } //difference from top-left corner return ResizeTopLeft; }else if(pt.y() > (this->height()*4.0/5.0)){ - if(setoffset){ offset.setX(pt.x()); offset.setY(this->height()-pt.y()); } //difference from bottom-left corner + if(setoffset){ offset.setX(pt.x()); offset.setY(pt.y()-this->height()); } //difference from bottom-left corner return ResizeBottomLeft; }else{ if(setoffset){ offset.setX(pt.x()); offset.setY(0); } //difference from left edge (Y does not matter) @@ -89,13 +90,13 @@ RootSubWindow::ModState RootSubWindow::getStateAtPoint(QPoint pt, bool setoffset }else if(pt.x() > (this->width()-WIN_BORDER) ){ //Right side options if(pt.y() < this->height()/5){ - if(setoffset){ offset.setX(this->width()-pt.x()); offset.setY(pt.y()); } //difference from top-right corner + if(setoffset){ offset.setX(pt.x()-this->width()); offset.setY(pt.y()); } //difference from top-right corner return ResizeTopRight; }else if(pt.y() > (this->height()*4.0/5.0)){ - if(setoffset){ offset.setX(this->width()-pt.x()); offset.setY(this->height()-pt.y()); } //difference from bottom-right corner + if(setoffset){ offset.setX(pt.x()-this->width()); offset.setY(pt.y()-this->height()); } //difference from bottom-right corner return ResizeBottomRight; }else{ - if(setoffset){ offset.setX(this->width()-pt.x()); offset.setY(0); } //difference from right edge (Y does not matter) + if(setoffset){ offset.setX(pt.x()-this->width()); offset.setY(0); } //difference from right edge (Y does not matter) return ResizeRight; } }else{ @@ -186,12 +187,14 @@ void RootSubWindow::initWindowFrame(){ titleBar->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); mainLayout->addWidget(titleBar); mainLayout->addWidget(WinWidget); + mainLayout->setAlignment(titleBar, Qt::AlignTop); //Setup the cursors for the buttons closeB->setCursor(Qt::ArrowCursor); minB->setCursor(Qt::ArrowCursor); maxB->setCursor(Qt::ArrowCursor); - otherM->setCursor(Qt::ArrowCursor); + otherB->setCursor(Qt::ArrowCursor); titleLabel->setCursor(Qt::ArrowCursor); + WinWidget->setCursor(Qt::ArrowCursor); //Now all the stylesheet options this->setObjectName("WindowFrame"); closeB->setObjectName("Button_Close"); @@ -277,12 +280,44 @@ void RootSubWindow::LoadAllProperties(){ //Button Actions - public so they can be tied to key shortcuts and stuff as well void RootSubWindow::toggleMinimize(){ - WIN->setProperty(NativeWindow::Visible, false); - QTimer::singleShot(2000, this, SLOT(toggleMaximize()) ); + WIN->toggleVisibility(); } void RootSubWindow::toggleMaximize(){ - WIN->setProperty(NativeWindow::Visible, true); + //Get the current screen that this window is on + QList<QScreen*> screens = QApplication::screens(); + QRect rect; + int primaryscreen = 0; //fallback value + for(int i=0; i<screens.length(); i++){ + QRect intersect = screens[i]->geometry().intersected(this->geometry()); + if( (intersect.width()-rect.width() + intersect.height()-rect.height()) > 0){ + rect = intersect; + primaryscreen = i; + } + } + //Now that we have the screen dimensions, lets check/change the window + rect = screens[primaryscreen]->availableGeometry(); + QList< NativeWindow::State > states = WIN->property(NativeWindow::States).value< QList< NativeWindow::State> >(); + if(rect == this->geometry() || states.contains(NativeWindow::S_MAX_VERT) || states.contains(NativeWindow::S_MAX_HORZ)){ + //Already maximized - try to restore it to the previous size/location + if(!lastMaxGeom.isNull()){ + rect = lastMaxGeom; + }else{ + // no last geometry - started out maximized? + // make it half the screen size and centered on the screen + QPoint center = rect.center(); + rect.setWidth( rect.width()/2 ); + rect.setHeight( rect.height()/2 ); + rect.moveTopLeft( center - QPoint(rect.width()/2, rect.height()/2) ); + } + lastMaxGeom = QRect(); //clear this saved geom + }else{ + //Not maximized yet - go ahead and make it so + lastMaxGeom = this->geometry(); //save this for later; + } + //qDebug() << "Toggle Maximize:" << this->geometry() << rect; + QString anim_type = DesktopSettings::instance()->value(DesktopSettings::Animation, "window/move", "random").toString(); + loadAnimation(anim_type, NativeWindow::Size, rect); } void RootSubWindow::triggerClose(){ @@ -290,11 +325,18 @@ void RootSubWindow::triggerClose(){ } void RootSubWindow::toggleSticky(){ - + QList< NativeWindow::State> states = WIN->property(NativeWindow::States).value< QList< NativeWindow::State > >(); + if(states.contains(NativeWindow::S_STICKY)){ + states.removeAll(NativeWindow::S_STICKY); + }else{ + states << NativeWindow::S_STICKY; + } + WIN->requestProperty(NativeWindow::States, QVariant::fromValue<QList <NativeWindow::State> >(states) ); } void RootSubWindow::activate(){ - WIN->requestProperty(NativeWindow::Active, true); + //WinWidget->raiseWindow(); + WIN->requestProperty(NativeWindow::Active, true, true); } //Mouse Interactivity @@ -309,13 +351,15 @@ void RootSubWindow::startMoving(){ activeState = Move; offset = this->mapFromGlobal(curpt); setMouseCursor(activeState, true); //this one is an override cursor - //WinWidget->pause(); - //Also need to capture the mouse + WinWidget->pause(); this->grabMouse(); } void RootSubWindow::startResizing(){ - + activeState = getStateAtPoint( this->mapFromGlobal(QCursor::pos()), true); //also have it set the offset variable + setMouseCursor(activeState, true); //this one is an override cursor + WinWidget->pause(); + this->grabMouse(); } // === PRIVATE SLOTS === @@ -325,9 +369,11 @@ void RootSubWindow::propertiesChanged(QList<NativeWindow::Property> props, QList //qDebug() << "RootSubWindow: Property Changed:" << props[i] << vals[i]; switch(props[i]){ case NativeWindow::Visible: - //qDebug() << "Got Visibility Change:" << vals[i] << this->geometry() << WIN->geometry(); - if(vals[i].toBool()){ loadAnimation( DesktopSettings::instance()->value(DesktopSettings::Animation, "window/appear", "random").toString(), NativeWindow::Visible, vals[i]); } - else{ loadAnimation( DesktopSettings::instance()->value(DesktopSettings::Animation, "window/disappear", "random").toString(), NativeWindow::Visible, vals[i]); } + if(!WinWidget->isPaused() && (this->isVisible()!=vals[i].toBool()) && activeState==Normal ){ + qDebug() << "Got Visibility Change:" << vals[i] << this->geometry() << WIN->geometry(); + if(vals[i].toBool()){ loadAnimation( DesktopSettings::instance()->value(DesktopSettings::Animation, "window/appear", "random").toString(), NativeWindow::Visible, vals[i]); } + else{ loadAnimation( DesktopSettings::instance()->value(DesktopSettings::Animation, "window/disappear", "random").toString(), NativeWindow::Visible, vals[i]); } + } break; case NativeWindow::Title: titleLabel->setText(vals[i].toString()); @@ -338,6 +384,10 @@ void RootSubWindow::propertiesChanged(QList<NativeWindow::Property> props, QList else{ otherB->setIcon(vals[i].value<QIcon>()); } break; case NativeWindow::GlobalPos: + if(vals[i].toPoint()!=QPoint(0,0)){ + WinWidget->resyncWindow(); + } + break; case NativeWindow::Size: //qDebug() << " - SIZE CHANGE"; if(WIN->property(NativeWindow::FrameExtents).isNull() && (i<props.indexOf(NativeWindow::FrameExtents)) ){ @@ -345,9 +395,11 @@ void RootSubWindow::propertiesChanged(QList<NativeWindow::Property> props, QList props << props.takeAt(i); vals << vals.takeAt(i); i--; - }else if(anim->state() != QPropertyAnimation::Running ){ - if(WIN->property(NativeWindow::Size).toSize() != WinWidget->size() && activeState==Normal ){ - this->setGeometry(WIN->geometry()); + }else if(!WinWidget->isPaused() && activeState==Normal){ + if(WIN->property(NativeWindow::Size).toSize() != WinWidget->size()){ + qDebug() << "Got Direct Geometry Change:" << WIN->geometry(); + this->setGeometry( QRect(this->geometry().topLeft(), WIN->geometry().size()) ); + WinWidget->resyncWindow(); } } break; @@ -370,7 +422,7 @@ void RootSubWindow::propertiesChanged(QList<NativeWindow::Property> props, QList WinWidget->setMaximumSize(vals[i].toSize()); break; case NativeWindow::Active: - //if(vals[i].toBool()){ WinWidget->setFocus(); } + if(vals[i].toBool()){ activate(); } //WinWidget->raiseWindow(); } break; /*case NativeWindow::FrameExtents: qDebug() << " - FRAME CHANGE"; @@ -382,6 +434,7 @@ void RootSubWindow::propertiesChanged(QList<NativeWindow::Property> props, QList mainLayout->setContentsMargins( vals[i].value< QList<int> >().at(0),vals[i].value< QList<int> >().at(2) - titleLabel->height(),vals[i].value< QList<int> >().at(1),vals[i].value< QList<int> >().at(3)); break;*/ case NativeWindow::WinTypes: + qDebug() << "Got Window Types:" << vals[i].value< QList<NativeWindow::Type> >(); enableFrame(vals[i].value< QList<NativeWindow::Type> >().contains(NativeWindow::T_NORMAL) ); break; default: @@ -394,27 +447,24 @@ void RootSubWindow::propertiesChanged(QList<NativeWindow::Property> props, QList void RootSubWindow::mousePressEvent(QMouseEvent *ev){ activate(); this->raise(); + QFrame::mousePressEvent(ev); //qDebug() << "Frame Mouse Press Event"; - offset.setX(0); offset.setY(0); if(activeState != Normal){ return; } // do nothing - already in a state of grabbed mouse - //this->activate(); - if(this->childAt(ev->pos())!=0){ - //Check for any non-left-click event and skip it - if(ev->button()!=Qt::LeftButton){ return; } - activeState = Move; - offset.setX(ev->pos().x()); offset.setY(ev->pos().y()); - }else{ - //Clicked on the frame somewhere - activeState = getStateAtPoint(ev->pos(), true); //also have it set the offset variable + offset.setX(0); offset.setY(0); + if(ev->button()==Qt::LeftButton){ + if(this->childAt(ev->pos())!=0){ + //Clicked on the titlebar + startMoving(); + }else{ + //Clicked on the frame somewhere + startResizing(); + } } - setMouseCursor(activeState, true); //this one is an override cursor - //if(activeState!=Normal){WinWidget->pause(); } - if(activeState!=Normal && activeState!=Move){WinWidget->pause(); } - QFrame::mousePressEvent(ev); + } void RootSubWindow::mouseMoveEvent(QMouseEvent *ev){ - activate(); //make sure this window is "Active" + QFrame::mouseMoveEvent(ev); if(activeState == Normal){ setMouseCursor( getStateAtPoint(ev->pos()) ); //just update the mouse cursor }else{ @@ -489,10 +539,15 @@ void RootSubWindow::mouseMoveEvent(QMouseEvent *ev){ break; } //if( (geom.width()%2==0 && geom.height()%2==0) || activeState==Move){ - this->setGeometry(geom); + //qDebug() << " Change Window:" << this->geometry() << geom; + if(activeState==Move){ this->setGeometry(geom); } + else{ + //qDebug() << " Change Window Dimensions:" << this->geometry() << geom; + //qDebug() << " - Mouse Pos:" << ev->globalPos() << ev->pos() << "Offset" << offset; + this->setGeometry(geom); + } //} } - QFrame::mouseMoveEvent(ev); } void RootSubWindow::mouseReleaseEvent(QMouseEvent *ev){ @@ -500,28 +555,37 @@ void RootSubWindow::mouseReleaseEvent(QMouseEvent *ev){ //qDebug() << "Frame Mouse Release Event"; QFrame::mouseReleaseEvent(ev); if( (activeState==Normal) && (titleBar->geometry().contains(ev->pos())) && (ev->button()==Qt::RightButton) ){ + //WinWidget->raiseWindow();//need to ensure the native window is always on top of this frame but under the menu otherM->popup(ev->globalPos()); return; } - if(activeState!=Normal){ WinWidget->resume(); } - if(activeState!=Normal && activeState!=Move){WinWidget->resume(); } - activeState = Normal; - QApplication::restoreOverrideCursor(); - setMouseCursor( getStateAtPoint(ev->pos()) ); + if(activeState!=Normal){ + if(WinWidget->isPaused()){ WinWidget->resume(); } + activeState = Normal; + QApplication::restoreOverrideCursor(); + setMouseCursor( getStateAtPoint(ev->pos()) ); + } if(QFrame::mouseGrabber() == this){ this->releaseMouse(); } + activate(); + //QTimer::singleShot(0, WinWidget, SLOT(raiseWindow()) ); } -void RootSubWindow::leaveEvent(QEvent *ev){ +/*void RootSubWindow::enterEvent(QEvent *ev){ + QFrame::enterEvent(ev); + WinWidget->raiseWindow(); +}*/ +/*void RootSubWindow::leaveEvent(QEvent *ev){ QFrame::leaveEvent(ev); if(activeState == Normal){ setMouseCursor(Normal); } -} + if(!QRect(QPoint(0,0),this->size()).contains( this->mapFromGlobal(QCursor::pos())) ){ WinWidget->lowerWindow(); } +}*/ void RootSubWindow::moveEvent(QMoveEvent *ev){ //qDebug() << "Got Move Event:" << ev->pos() << WinWidget->geometry(); QFrame::moveEvent(ev); - if(!closing && anim->state()!=QAbstractAnimation::Running){ + if(!closing && !WinWidget->isPaused()){ moveTimer->start(); } } diff --git a/src-qt5/core/libLumina/RootSubWindow.h b/src-qt5/core/libLumina/RootSubWindow.h index 0af77009..c1964724 100644 --- a/src-qt5/core/libLumina/RootSubWindow.h +++ b/src-qt5/core/libLumina/RootSubWindow.h @@ -55,7 +55,7 @@ private: QPropertyAnimation *anim; QVariant animResetProp; QTimer *moveTimer; - QRect lastGeom; //frame coordinates + QRect lastGeom, lastMaxGeom; //frame coordinates void initWindowFrame(); void enableFrame(bool); @@ -65,6 +65,10 @@ private: static QStringList validAnimations(NativeWindow::Property); public slots: + void giveMouseFocus(){ WinWidget->raiseWindow(); } + void removeMouseFocus(){ WinWidget->lowerWindow(); } + void giveKeyboardFocus(){ WIN->requestProperty(NativeWindow::Active, true, true); } + void clientClosed(); void LoadAllProperties(); @@ -91,11 +95,13 @@ protected: void mousePressEvent(QMouseEvent*); void mouseMoveEvent(QMouseEvent*); void mouseReleaseEvent(QMouseEvent*); - void leaveEvent(QEvent *ev); - + //void leaveEvent(QEvent *ev); + //void enterEvent(QEvent *ev); void moveEvent(QMoveEvent *ev); - +signals: + void windowMoved(RootSubWindow*); + void windowAnimFinished(); }; #endif diff --git a/src-qt5/core/libLumina/RootWindow-mgmt.cpp b/src-qt5/core/libLumina/RootWindow-mgmt.cpp index 00b3e336..24ea639b 100644 --- a/src-qt5/core/libLumina/RootWindow-mgmt.cpp +++ b/src-qt5/core/libLumina/RootWindow-mgmt.cpp @@ -7,13 +7,59 @@ #include "RootWindow.h" //Primary/private function -void RootWindow::arrangeWindows(RootSubWindow *primary, QString type){ +void RootWindow::arrangeWindows(RootSubWindow *primary, QString type, bool primaryonly){ + if(type.isEmpty()){ type = "center"; } if(primary==0){ //Get the currently active window and treat that as the primary - + for(int i=0; i<WINDOWS.length(); i++){ + if(WINDOWS[i]->nativeWindow()->property(NativeWindow::Active).toBool()){ primary = WINDOWS[i]; } + } + if(primary==0 && !WINDOWS.isEmpty()){ primary = WINDOWS[0]; } //just use the first one in the list + } + //Now get the current screen that the mouse cursor is over (and valid area) + QScreen *screen = screenUnderMouse(); + QRect desktopArea = screen->availableGeometry(); + //qDebug() << "Arrange Windows:" << primary->geometry() << type << primaryonly << desktopArea; + //Now start filtering out all the windows that need to be ignored + int wkspace = primary->nativeWindow()->property(NativeWindow::Workspace).toInt(); + QList<RootSubWindow*> winlist = WINDOWS; + for(int i=0; i<winlist.length(); i++){ + if(winlist[i]->nativeWindow()->property(NativeWindow::Workspace).toInt()!=wkspace + || !winlist[i]->nativeWindow()->property(NativeWindow::Visible).toBool() + || desktopArea.intersected(winlist[i]->geometry()).isNull() ){ + //window is outside of the desired area or invisible - ignore it + winlist.removeAt(i); + i--; + } } - //Now loop over the windows and arrange them as needed + if(!winlist.contains(primary)){ winlist << primary; } //could be doing this on a window right before it is shown + else if(primaryonly){ winlist.removeAll(primary); winlist << primary; } //move primary window to last + //QRegion used; + for(int i=0; i<winlist.length(); i++){ + if(primaryonly && winlist[i]!=primary){ continue; } //skip this window + //Now loop over the windows and arrange them as needed + QRect geom = winlist[i]->geometry(); + //verify that the window is contained by the desktop area + if(geom.width()>desktopArea.width()){ geom.setWidth(desktopArea.width()); } + if(geom.height()>desktopArea.height()){ geom.setHeight(desktopArea.height()); } + //Now apply the proper placement routine + if(type=="center"){ + QPoint ct = desktopArea.center(); + winlist[i]->setGeometry( ct.x()-(geom.width()/2), ct.y()-(geom.height()/2), geom.width(), geom.height()); + }else if(type=="snap"){ + + }else if(type=="single_max"){ + winlist[i]->setGeometry( desktopArea.x(), desktopArea.y(), desktopArea.width(), desktopArea.height()); + }else if(type=="under-mouse"){ + QPoint ct = QCursor::pos(); + geom = QRect(ct.x()-(geom.width()/2), ct.y()-(geom.height()/2), geom.width(), geom.height() ); + //Now verify that the top of the window is still contained within the desktop area + if(geom.y() < desktopArea.y() ){ geom.moveTop(desktopArea.y()); } + winlist[i]->setGeometry(geom); + } + //qDebug() << " - New Geometry:" << winlist[i]->geometry(); + } //end loop over winlist } // ================ @@ -21,13 +67,13 @@ void RootWindow::arrangeWindows(RootSubWindow *primary, QString type){ // ================ void RootWindow::ArrangeWindows(WId primary, QString type){ RootSubWindow* win = windowForId(primary); - if(type.isEmpty()){ type = ""; } //grab the default arrangement format + if(type.isEmpty()){ type = "center"; } //grab the default arrangement format arrangeWindows(win, type); } void RootWindow::TileWindows(WId primary, QString type){ RootSubWindow* win = windowForId(primary); - if(type.isEmpty()){ type = ""; } //grab the default arrangement format for tiling + if(type.isEmpty()){ type = "single_max"; } //grab the default arrangement format for tiling arrangeWindows(win, type); } @@ -45,5 +91,5 @@ void RootWindow::CheckWindowPosition(WId id, bool newwindow){ if(geom.height() < 20){ changed = true; geom.setHeight(100); } if(changed){ win->setGeometry(geom); } //Now run it through the window arrangement routine - ArrangeWindows(id); + arrangeWindows(win, newwindow ?"center" : "snap", true); } diff --git a/src-qt5/core/libLumina/RootWindow.cpp b/src-qt5/core/libLumina/RootWindow.cpp index 48c37c86..fdbc1eb8 100644 --- a/src-qt5/core/libLumina/RootWindow.cpp +++ b/src-qt5/core/libLumina/RootWindow.cpp @@ -10,12 +10,14 @@ #include <QScreen> #include <QDebug> -#define DEBUG 1 +#define DEBUG 0 // === PUBLIC === RootWindow::RootWindow() : QWidget(0, Qt::Window | Qt::BypassWindowManagerHint | Qt::WindowStaysOnBottomHint){ qRegisterMetaType<WId>("WId"); autoResizeTimer = 0; + lastActiveMouse = 0; + mouseFocusTimer = 0; this->setMouseTracking(true); } @@ -33,6 +35,12 @@ void RootWindow::start(){ connect(QApplication::desktop(), SIGNAL(resized(int)), autoResizeTimer, SLOT(start()) ); connect(QApplication::desktop(), SIGNAL(screenCountChanged(int)), autoResizeTimer, SLOT(start()) ); } + if(mouseFocusTimer==0){ + mouseFocusTimer = new QTimer(this); + mouseFocusTimer->setInterval(100); + connect(mouseFocusTimer, SIGNAL(timeout()), this, SLOT(checkMouseFocus()) ); + + } this->show(); ResizeRoot(); emit RegisterVirtualRoot(this->winId()); @@ -108,6 +116,16 @@ RootSubWindow* RootWindow::windowForId(WId id){ return tmp; } +QScreen* RootWindow::screenUnderMouse(){ + QPoint mpos = QCursor::pos(); + QList<QScreen*> scrns = QApplication::screens(); + for(int i=0; i<scrns.length(); i++){ + if(scrns[i]->geometry().contains(mpos)){ return scrns[i]; } + } + //Could not find an exact match - just return the first one + return scrns.first(); +} + // === PUBLIC SLOTS === void RootWindow::ResizeRoot(){ if(DEBUG){ qDebug() << "Resize Root..."; } @@ -182,6 +200,32 @@ void RootWindow::ChangeWallpaper(QString id, RootWindow::ScaleType scale, QStrin } +void RootWindow::checkMouseFocus(){ + QPoint cpos = QCursor::pos(); + if(lastCursorPos != cpos){ emit MouseMoved(); } + lastCursorPos = cpos; + QWidget *child = this->childAt(QCursor::pos()); + while(child!=0 && child->whatsThis()!="RootSubWindow"){ + child = child->parentWidget(); + if(child==this){ child = 0;} //end of the line + } + + if(child==lastActiveMouse){ return; } //nothing new to do + //Make sure the child is actually a RootSubWindow + if(lastActiveMouse!=0){ lastActiveMouse->removeMouseFocus(); lastActiveMouse = 0; } + if(child!=0){ + lastActiveMouse = static_cast<RootSubWindow*>(child); + + if(DesktopSettings::instance()->value(DesktopSettings::WM, "focusFollowsMouse", true).toBool()){ + lastActiveMouse->giveKeyboardFocus(); + if(DesktopSettings::instance()->value(DesktopSettings::WM, "raiseOnFocus", false).toBool()){ + lastActiveMouse->raise(); + } + } + lastActiveMouse->giveMouseFocus(); //always give mouse focus on mouseover + } +} + void RootWindow::NewWindow(NativeWindow *win){ RootSubWindow *subwin = 0; //qDebug() << "Got New Window:" << win->property(NativeWindow::Title); @@ -190,19 +234,27 @@ void RootWindow::NewWindow(NativeWindow *win){ } if(subwin==0){ subwin = new RootSubWindow(this, win); + subwin->setWhatsThis("RootSubWindow"); connect(win, SIGNAL(WindowClosed(WId)), this, SLOT(CloseWindow(WId)) ); + connect(subwin, SIGNAL(windowAnimFinished()), this, SLOT(checkMouseFocus()) ); WINDOWS << subwin; } CheckWindowPosition(win->id(), true); //first-time run - //win->setProperty(NativeWindow::Visible, true); + win->setProperty(NativeWindow::Visible, true); //win->requestProperty( NativeWindow::Active, true); - win->requestProperties(QList<NativeWindow::Property>() << NativeWindow::Visible << NativeWindow::Active, QList<QVariant>() << true << true); + //win->requestProperties(QList<NativeWindow::Property>() << NativeWindow::Visible << NativeWindow::Active, QList<QVariant>() << true << true, true); + if(!mouseFocusTimer->isActive()){ mouseFocusTimer->start(); } } void RootWindow::CloseWindow(WId win){ for(int i=0; i<WINDOWS.length(); i++){ - if(WINDOWS[i]->id() == win){ WINDOWS.takeAt(i)->clientClosed(); break; } + if(WINDOWS[i]->id() == win){ + if(lastActiveMouse==WINDOWS[i]){ lastActiveMouse = 0; } //no longer valid + WINDOWS.takeAt(i)->clientClosed(); + break; + } } + if(WINDOWS.isEmpty()){ mouseFocusTimer->stop(); } //no windows to look for } // === PRIVATE SLOTS === @@ -210,11 +262,11 @@ void RootWindow::CloseWindow(WId win){ // === PROTECTED === void RootWindow::paintEvent(QPaintEvent *ev){ //qDebug() << "RootWindow: PaintEvent:" << ev->rect(); //<< QDateTime::currentDateTime()->toString(QDateTime::ShortDate); - QWidget::paintEvent(ev); + //QWidget::paintEvent(ev); bool found = false; QPainter painter(this); QRect geom = ev->rect(); - geom.adjust(-10,-10,10,10); //give it a few more pixels in each direction to repaint (noticing some issues in Qt 5.7.1) + geom.adjust(-100,-100,100,100); //give it a few more pixels in each direction to repaint (noticing some issues in Qt 5.7.1) for(int i=0; i<WALLPAPERS.length(); i++){ if(WALLPAPERS[i].area.intersects(geom) ){ found = true; diff --git a/src-qt5/core/libLumina/RootWindow.h b/src-qt5/core/libLumina/RootWindow.h index 9b1334dc..c5cd44a0 100644 --- a/src-qt5/core/libLumina/RootWindow.h +++ b/src-qt5/core/libLumina/RootWindow.h @@ -18,6 +18,8 @@ #include <QTimer> #include <QApplication> #include <QPaintEvent> +#include <QScreen> +#include <QDebug> #include "RootSubWindow.h" @@ -43,7 +45,9 @@ private: ScaleType scale; QPixmap wallpaper; //Note: This pixmap will always be the same size as "area" }; - QTimer *autoResizeTimer; + QTimer *autoResizeTimer, *mouseFocusTimer; + RootSubWindow *lastActiveMouse; + QPoint lastCursorPos; QList<screeninfo> WALLPAPERS; void updateScreenPixmap(screeninfo *info); //used for recalculating the wallpaper pixmap based on file/area/scale as needed @@ -51,12 +55,16 @@ private: //Window Management QList<RootSubWindow*> WINDOWS; RootSubWindow* windowForId(WId id); - void arrangeWindows(RootSubWindow *primary = 0, QString type = ""); + void arrangeWindows(RootSubWindow *primary = 0, QString type = "", bool primaryonly = false); + + QScreen* screenUnderMouse(); + 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 checkMouseFocus(); void NewWindow(NativeWindow*); void CloseWindow(WId); //automatically connected for any new native window @@ -76,6 +84,8 @@ signals: void RootResized(QRect); void NewScreens(QStringList); // [screen_id_1, screen_id_2, etc..] void RemovedScreens(QStringList); // [screen_id_1, screen_id_2, etc..] + void WorkspaceChanged(int); + void MouseMoved(); }; diff --git a/src-qt5/core/libLumina/XDGMime.cpp b/src-qt5/core/libLumina/XDGMime.cpp index 3983f6b5..cbbeff75 100644 --- a/src-qt5/core/libLumina/XDGMime.cpp +++ b/src-qt5/core/libLumina/XDGMime.cpp @@ -12,6 +12,9 @@ static QStringList mimeglobs; static qint64 mimechecktime; QString XDGMime::fromFileName(QString filename){ + if(QFile::exists(filename) && QFileInfo(filename).isDir()){ + return "inode/directory"; + } //Convert a filename into a mimetype return findAppMimeForFile(filename.section("/",-1),false); } |