diff options
author | Ken Moore <moorekou@gmail.com> | 2016-04-25 13:08:12 -0400 |
---|---|---|
committer | Ken Moore <moorekou@gmail.com> | 2016-04-25 13:08:12 -0400 |
commit | ed5ecf7ea7a482b4649e66ecb35fbc60af680684 (patch) | |
tree | acc0fa17d228259e847f55c678db9fb0a9b50f0c /src-qt5/core/libLumina | |
parent | Merge branch 'master' of github.com:pcbsd/lumina (diff) | |
download | lumina-ed5ecf7ea7a482b4649e66ecb35fbc60af680684.tar.gz lumina-ed5ecf7ea7a482b4649e66ecb35fbc60af680684.tar.bz2 lumina-ed5ecf7ea7a482b4649e66ecb35fbc60af680684.zip |
Rearrange the Lumina source tree quite a bit:
Now the utilites are arranged by category (core, core-utils, desktop-utils), so all the -utils may be excluded by a package system (or turned into separate packages) as needed.
Diffstat (limited to 'src-qt5/core/libLumina')
35 files changed, 8979 insertions, 0 deletions
diff --git a/src-qt5/core/libLumina/LuminaOS-Debian.cpp b/src-qt5/core/libLumina/LuminaOS-Debian.cpp new file mode 100644 index 00000000..75aad108 --- /dev/null +++ b/src-qt5/core/libLumina/LuminaOS-Debian.cpp @@ -0,0 +1,300 @@ +//=========================================== +// Lumina-DE source code +// Copyright (c) 2014, Ken Moore +// Available under the 3-clause BSD license +// See the LICENSE file for full details +//=========================================== +#ifdef __linux__ +#include <QDebug> +#include "LuminaOS.h" +#include <unistd.h> +#include <stdio.h> // Needed for BUFSIZ + +//can't read xbrightness settings - assume invalid until set +static int screenbrightness = -1; + +QString LOS::OSName(){ return "Debian GNU/Linux"; } + +//OS-specific prefix(s) +// NOTE: PREFIX, L_ETCDIR, L_SHAREDIR are defined in the OS-detect.pri project file and passed in +QString LOS::LuminaShare(){ return (L_SHAREDIR+"/Lumina-DE/"); } //Install dir for Lumina share files +QString LOS::AppPrefix(){ return "/usr/"; } //Prefix for applications +QString LOS::SysPrefix(){ return "/"; } //Prefix for system + +//OS-specific application shortcuts (*.desktop files) +QString LOS::ControlPanelShortcut(){ return ""; } //system control panel +QString LOS::AppStoreShortcut(){ return LOS::AppPrefix() + "/share/applications/synaptic.desktop"; } //graphical app/pkg manager + +// ==== ExternalDevicePaths() ==== +QStringList LOS::ExternalDevicePaths(){ + /* Returns: QStringList[<type>::::<filesystem>::::<path>] + Note: <type> = [USB, HDRIVE, DVD, SDCARD, UNKNOWN] + df is much better for this than mount, because it skips + all non-physical devices (like bind-mounts from schroot) + so they are not listed in lumina-fm */ + QStringList devs = LUtils::getCmdOutput("df --output=source,fstype,target"); + //Now check the output + for(int i=0; i<devs.length(); i++){ + if(devs[i].startsWith("/dev/")){ + devs[i] = devs[i].simplified(); + QString type = devs[i].section(" on ",0,0); + type.remove("/dev/"); + //Determine the type of hardware device based on the dev node + if(type.startsWith("sd") || type.startsWith("nvme")){ type = "HDRIVE"; } + else if(type.startsWith("sr")){ type="DVD"; } + else if(type.contains("mapper")){ type="LVM"; } + else{ type = "UNKNOWN"; } + //Now put the device in the proper output format + devs[i] = type + "::::" + devs[i].section(" ",1,1) + "::::" + devs[i].section(" ",2,2); + }else{ + //invalid device - remove it from the list + devs.removeAt(i); + i--; + } + } + return devs; +} + +//Read screen brightness information +int LOS::ScreenBrightness(){ + //Returns: Screen Brightness as a percentage (0-100, with -1 for errors) + if(screenbrightness==-1){ + if(QFile::exists(QDir::homePath()+"/.lumina/.currentxbrightness")){ + int val = LUtils::readFile(QDir::homePath()+"/.lumina/.currentxbrightness").join("").simplified().toInt(); + screenbrightness = val; + } + } + return screenbrightness; + +} + +//Set screen brightness +void LOS::setScreenBrightness(int percent){ + //ensure bounds + if(percent<0){percent=0;} + else if(percent>100){ percent=100; } + // float pf = percent/100.0; //convert to a decimel + //Run the command + QString cmd = "xbacklight -set %1"; + // cmd = cmd.arg( QString::number( int(65535*pf) ) ); + cmd = cmd.arg( QString::number( percent ) ); + int ret = LUtils::runCmd(cmd); + //Save the result for later + if(ret!=0){ screenbrightness = -1; } + else{ screenbrightness = percent; } + LUtils::writeFile(QDir::homePath()+"/.lumina/.currentxbrightness", QStringList() << QString::number(screenbrightness), true); + +} + +//Read the current volume +int LOS::audioVolume(){ //Returns: audio volume as a percentage (0-100, with -1 for errors) +QString info = LUtils::getCmdOutput("amixer get Master").join("").simplified();; + int out = -1; + int start_position, end_position; + QString current_volume; + if(!info.isEmpty()){ + start_position = info.indexOf("["); + start_position++; + end_position = info.indexOf("%"); + current_volume = info.mid(start_position, end_position - start_position); + out = current_volume.toInt(); + } + return out; + +} + +//Set the current volume +void LOS::setAudioVolume(int percent){ + if(percent<0){percent=0;} + else if(percent>100){percent=100;} + // QString info = "amixer -c 0 sset Master,0 " + QString::number(percent) + "%"; + QString info = "amixer set Master " + QString::number(percent) + "%"; + if(!info.isEmpty()){ + //Run Command + LUtils::runCmd(info); + } + +} + +//Change the current volume a set amount (+ or -) +void LOS::changeAudioVolume(int percentdiff){ + int old_volume = audioVolume(); + int new_volume = old_volume + percentdiff; + if (new_volume < 0) + new_volume = 0; + if (new_volume > 100) + new_volume = 100; + qDebug() << "Setting new volume to: " << new_volume; + setAudioVolume(new_volume); +} + +//Check if a graphical audio mixer is installed +bool LOS::hasMixerUtility(){ + return QFile::exists(LOS::AppPrefix() + "bin/pavucontrol"); +} + +//Launch the graphical audio mixer utility +void LOS::startMixerUtility(){ + QProcess::startDetached(LOS::AppPrefix() + "bin/pavucontrol"); +} + +//Check for user system permission (shutdown/restart) +bool LOS::userHasShutdownAccess(){ + return QProcess::startDetached("dbus-send --system --print-reply=literal \ + --type=method_call --dest=org.freedesktop.login1 \ + /org/freedesktop/login1 org.freedesktop.login1.Manager.CanPowerOff"); +} + +//Check for whether the system is safe to power off (no updates being perfomed) +bool LOS::systemPerformingUpdates(){ + return false; //Not implemented yet +} + +//System Shutdown +void LOS::systemShutdown(){ //start poweroff sequence + QProcess::startDetached("dbus-send --system --print-reply \ + --dest=org.freedesktop.login1 /org/freedesktop/login1 \ + org.freedesktop.login1.Manager.PowerOff boolean:true"); +} + +//System Restart +void LOS::systemRestart(){ //start reboot sequence + QProcess::startDetached("dbus-send --system --print-reply \ + --dest=org.freedesktop.login1 /org/freedesktop/login1 \ + org.freedesktop.login1.Manager.Reboot boolean:true"); +} + +//Check for suspend support +bool LOS::systemCanSuspend(){ + return QProcess::startDetached("dbus-send --system --print-reply=literal \ + --type=method_call --dest=org.freedesktop.login1 \ + /org/freedesktop/login1 org.freedesktop.login1.Manager.CanSuspend"); +} + +//Put the system into the suspend state +void LOS::systemSuspend(){ + QProcess::startDetached("dbus-send --system --print-reply \ + --dest=org.freedesktop.login1 /org/freedesktop/login1 \ + org.freedesktop.login1.Manager.Suspend boolean:true"); +} + +//Battery Availability +bool LOS::hasBattery(){ + QString my_status = LUtils::getCmdOutput("acpi -b").join(""); + bool no_battery = my_status.contains("No support"); + if (no_battery) return false; + return true; +} + +//Battery Charge Level +int LOS::batteryCharge(){ //Returns: percent charge (0-100), anything outside that range is counted as an error + QString my_status = LUtils::getCmdOutput("acpi -b").join(""); + int my_start = my_status.indexOf("%"); + // get the number right before the % sign + int my_end = my_start; + my_start--; + while ( (my_status[my_start] != ' ') && (my_start > 0) ) + my_start--; + my_start++; + int my_charge = my_status.mid(my_start, my_end - my_start).toInt(); + if ( (my_charge < 0) || (my_charge > 100) ) return -1; + return my_charge; +} + +//Battery Charging State +// Many possible values are returned if the laptop is plugged in +// these include "Unknown, Full and No support. +// However, it seems just one status is returned when running +// on battery and that is "Discharging". So if the value we get +// is NOT Discharging then we assume the battery is charging. +bool LOS::batteryIsCharging(){ + QString my_status = LUtils::getCmdOutput("acpi -b").join(""); + bool discharging = my_status.contains("Discharging"); + if (discharging) return false; + return true; +} + +//Battery Time Remaining +int LOS::batterySecondsLeft(){ //Returns: estimated number of seconds remaining + return 0; //not implemented yet for Linux +} + +//File Checksums +QStringList LOS::Checksums(QStringList filepaths){ //Return: checksum of the input file + QStringList info = LUtils::getCmdOutput("md5sum \""+filepaths.join("\" \"")+"\""); + for(int i=0; i<info.length(); i++){ + // first: md5sum: = error ; second: there's always one empty entry generated by getCmdOutput + if( info[i].startsWith("md5sum:") || info[i].isEmpty()){ info.removeAt(i); i--; } + else{ + //Strip out the extra information + info[i] = info[i].section(" ",0,0); + } + } + return info; +} + +//file system capacity +QString LOS::FileSystemCapacity(QString dir) { //Return: percentage capacity as give by the df command + QStringList mountInfo = LUtils::getCmdOutput("df -h \"" + dir + "\""); + QString::SectionFlag skipEmpty = QString::SectionSkipEmpty; + //output: 200G of 400G available on /mount/point + QString capacity = mountInfo[1].section(" ",3,3, skipEmpty) + " of " + mountInfo[1].section(" ",1,1, skipEmpty) + " available on " + mountInfo[1].section(" ",5,5, skipEmpty); + return capacity; +} + +QStringList LOS::CPUTemperatures(){ //Returns: List containing the temperature of any CPU's ("50C" for example) + QStringList temp = LUtils::getCmdOutput("acpi -t").filter("degrees"); + for(int i=0; i<temp.length(); i++){ + if(temp[i].startsWith("Thermal")){ + temp[i] = temp[i].section(" ", 4, 6); + }else{ + temp.removeAt(i); i--; + } + } + if(temp.isEmpty()) { + temp << "Not available"; + } + return temp; +} + +int LOS::CPUUsagePercent(){ //Returns: Overall percentage of the amount of CPU cycles in use (-1 for errors) + QStringList info = LUtils::getCmdOutput("top -bn1").filter("Cpu(s)"); + if(info.isEmpty()){ return -1; } + QString idle = info.first().section(" ", 7, 7, QString::SectionSkipEmpty); + if(idle.isEmpty()){ return -1; } + else{ + return (100 - idle.toDouble()); + } +} + +int LOS::MemoryUsagePercent(){ + QStringList mem = LUtils::getCmdOutput("top -bn1").filter("Mem :"); + if(mem.isEmpty()){ return -1; } + double fB = 0; //Free Bytes + double uB = 0; //Used Bytes + fB = mem.first().section(" ", 5, 5, QString::SectionSkipEmpty).toDouble(); + uB = mem.first().section(" ", 7, 7, QString::SectionSkipEmpty).toDouble(); + double per = (uB/(fB+uB)) * 100.0; + return qRound(per); +} + +QStringList LOS::DiskUsage(){ //Returns: List of current read/write stats for each device + QStringList info = LUtils::getCmdOutput("iostat -dx -N"); + if(info.length()<3){ return QStringList(); } //nothing from command + QStringList labs = info[1].split(" ",QString::SkipEmptyParts); + QStringList out; + QString fmt = "%1: %2 %3"; + for(int i=2; i<info.length(); i++){ //skip the first two lines, just labels + info[i].replace("\t"," "); + if(info[i].startsWith("Device:")){ labs = info[i].split(" ", QString::SkipEmptyParts); }//the labels for each column + else{ + QStringList data = info[i].split(" ",QString::SkipEmptyParts); //data[0] is always the device + if(data.length()>2 && labs.length()>2){ + out << fmt.arg(data[0], data[3]+" "+labs[3], data[4]+" "+labs[4]); + } + } + } + + return out; +} +#endif diff --git a/src-qt5/core/libLumina/LuminaOS-DragonFly.cpp b/src-qt5/core/libLumina/LuminaOS-DragonFly.cpp new file mode 100644 index 00000000..b98a36ee --- /dev/null +++ b/src-qt5/core/libLumina/LuminaOS-DragonFly.cpp @@ -0,0 +1,355 @@ +//=========================================== +// Lumina-DE source code +// Copyright (c) 2014, Ken Moore +// Available under the 3-clause BSD license +// See the LICENSE file for full details +//=========================================== +#ifdef __DragonFly__ +#include "LuminaOS.h" +#include <unistd.h> +#include <sys/types.h> +#include <sys/sysctl.h> +#include <sys/sensors.h> + +//can't read xbrightness settings - assume invalid until set +static int screenbrightness = -1; +static int audiovolume = -1; + +static bool get_sysctlbyname_int(const char *name, int *res) { + int r = 0; + size_t len = sizeof(r); + if (sysctlbyname(name, &r, &len, NULL, 0) == 0) { + *res = r; + return true; + } + return false; +} + +#if 0 +static bool get_sysctlbyname_qstr(const char *name, QString &str) { + size_t len = 0; + sysctlbyname(name, NULL, &len, NULL, 0); + if (len > 0) { + void *buf = malloc(len); + if (buf) { + int res = sysctlbyname(name, buf, &len, NULL, 0); + if (res == 0) { + str = QString((char*) buf); + } + free(buf); + return (res == 0); + } + } + return false; +} +#endif + +// returns -1 on error. +static int get_sysctlbyname_int(const char *name) { + int res = -1; + if (get_sysctlbyname_int(name, &res)) { + return res; + } + return -1; +} + +static bool get_sysctlbyname_uint(const char *name, unsigned int *res) { + unsigned int r = 0; + size_t len = sizeof(r); + if (sysctlbyname(name, &r, &len, NULL, 0) == 0) { + *res = r; + return true; + } + return false; +} + +QString LOS::OSName(){ return "DragonFly BSD"; } + +//OS-specific prefix(s) +// NOTE: PREFIX, L_ETCDIR, L_SHAREDIR are defined in the OS-detect.pri project file and passed in +QString LOS::LuminaShare(){ return (L_SHAREDIR+"/Lumina-DE/"); } //Install dir for Lumina share files +QString LOS::AppPrefix(){ return "/usr/local/"; } //Prefix for applications +QString LOS::SysPrefix(){ return "/usr/"; } //Prefix for system + +//OS-specific application shortcuts (*.desktop files) +QString LOS::ControlPanelShortcut(){ return ""; } //system control panel +QString LOS::AppStoreShortcut(){ return ""; } //graphical app/pkg manager + +// ==== ExternalDevicePaths() ==== +QStringList LOS::ExternalDevicePaths(){ + //Returns: QStringList[<type>::::<filesystem>::::<path>] + //Note: <type> = [USB, HDRIVE, DVD, SDCARD, UNKNOWN] + QStringList devs = LUtils::getCmdOutput("mount"); + //Now check the output + for(int i=0; i<devs.length(); i++){ + if(devs[i].startsWith("/dev/")){ + QString type = devs[i].section(" on ",0,0); + type.remove("/dev/"); + //Determine the type of hardware device based on the dev node + if(type.startsWith("da")){ type = "USB"; } + else if(type.startsWith("ada")){ type = "HDRIVE"; } + else if(type.startsWith("mmsd")){ type = "SDCARD"; } + else if(type.startsWith("cd")||type.startsWith("acd")){ type="DVD"; } + else{ type = "UNKNOWN"; } + //Now put the device in the proper output format + devs[i] = type+"::::"+devs[i].section("(",1,1).section(",",0,0)+"::::"+devs[i].section(" on ",1,50).section("(",0,0).simplified(); + }else{ + //invalid device - remove it from the list + devs.removeAt(i); + i--; + } + } + return devs; +} + +//Read screen brightness information +int LOS::ScreenBrightness(){ + //Returns: Screen Brightness as a percentage (0-100, with -1 for errors) + if(screenbrightness==-1){ + if(QFile::exists(QDir::homePath()+"/.lumina/.currentxbrightness")){ + int val = LUtils::readFile(QDir::homePath()+"/.lumina/.currentxbrightness").join("").simplified().toInt(); + screenbrightness = val; + } + } + //If it gets to this point, then we have a valid (but new) installation + if(screenbrightness<0){ screenbrightness = 100; } //default value for systems + + return screenbrightness; +} + +//Set screen brightness +void LOS::setScreenBrightness(int percent){ + if(percent == -1){ return; } //This is usually an invalid value passed directly to the setter + //ensure bounds + if(percent<0){percent=0;} + else if(percent>100){ percent=100; } + //Run the command(s) + bool success = false; + float pf = percent/100.0; //convert to a decimel + //Run the command + QString cmd = "xbrightness %1"; + cmd = cmd.arg( QString::number( int(65535*pf) ) ); + success = (0 == LUtils::runCmd(cmd) ); + //Save the result for later + if(!success){ screenbrightness = -1; } + else{ screenbrightness = percent; } + LUtils::writeFile(QDir::homePath()+"/.lumina/.currentxbrightness", QStringList() << QString::number(screenbrightness), true); +} + +//Read the current volume +int LOS::audioVolume(){ //Returns: audio volume as a percentage (0-100, with -1 for errors) + int out = audiovolume; + if(out < 0){ + //First time session check: Load the last setting for this user + QString info = LUtils::readFile(QDir::homePath()+"/.lumina/.currentvolume").join(""); + if(!info.isEmpty()){ + out = info.simplified().toInt(); + audiovolume = out; //unset this internal flag + return out; + } + } + + //probe the system for the current volume (other utils could be changing it) + QString info = LUtils::getCmdOutput("mixer -S vol").join(":").simplified(); //ignores any other lines + if(!info.isEmpty()){ + int L = info.section(":",1,1).toInt(); + int R = info.section(":",2,2).toInt(); + if(L>R){ out = L; } + else{ out = R; } + if(out != audiovolume){ + //Volume changed by other utility: adjust the saved value as well + LUtils::writeFile(QDir::homePath()+"/.lumina/.currentvolume", QStringList() << QString::number(out), true); + } + audiovolume = out; + } + + return out; +} + +//Set the current volume +void LOS::setAudioVolume(int percent){ + if(percent<0){percent=0;} + else if(percent>100){percent=100;} + QString info = LUtils::getCmdOutput("mixer -S vol").join(":").simplified(); //ignores any other lines + if(!info.isEmpty()){ + int L = info.section(":",1,1).toInt(); + int R = info.section(":",2,2).toInt(); + int diff = L-R; + if((percent == L) && (L==R)){ return; } //already set to that volume + if(diff<0){ R=percent; L=percent+diff; } //R Greater + else{ L=percent; R=percent-diff; } //L Greater or equal + //Check bounds + if(L<0){L=0;}else if(L>100){L=100;} + if(R<0){R=0;}else if(R>100){R=100;} + //Run Command + audiovolume = percent; //save for checking later + LUtils::runCmd("mixer vol "+QString::number(L)+":"+QString::number(R)); + LUtils::writeFile(QDir::homePath()+"/.lumina/.currentvolume", QStringList() << QString::number(percent), true); + } +} + +//Change the current volume a set amount (+ or -) +void LOS::changeAudioVolume(int percentdiff){ + QString info = LUtils::getCmdOutput("mixer -S vol").join(":").simplified(); //ignores any other lines + if(!info.isEmpty()){ + int L = info.section(":",1,1).toInt() + percentdiff; + int R = info.section(":",2,2).toInt() + percentdiff; + //Check bounds + if(L<0){L=0;}else if(L>100){L=100;} + if(R<0){R=0;}else if(R>100){R=100;} + //Run Command + LUtils::runCmd("mixer vol "+QString::number(L)+":"+QString::number(R)); + } +} + +//Check if a graphical audio mixer is installed +bool LOS::hasMixerUtility(){ + return false; //not implemented yet for DragonFly +} + +//Launch the graphical audio mixer utility +void LOS::startMixerUtility(){ + //Not implemented yet for DragonFly +} + +//Check for user system permission (shutdown/restart) +bool LOS::userHasShutdownAccess(){ + return true; //not implemented yet +} + +//Check for whether the system is safe to power off (no updates being perfomed) +bool LOS::systemPerformingUpdates(){ + return false; //Not implemented yet +} + +//System Shutdown +void LOS::systemShutdown(){ //start poweroff sequence + QProcess::startDetached("shutdown -p now"); +} + +//System Restart +void LOS::systemRestart(){ //start reboot sequence + QProcess::startDetached("shutdown -r now"); +} + +//Check for suspend support +bool LOS::systemCanSuspend(){ + return false; +} + +//Put the system into the suspend state +void LOS::systemSuspend(){ + +} + +//Battery Availability +bool LOS::hasBattery(){ + return (get_sysctlbyname_int("hw.acpi.battery.units") >= 1); +} + +//Battery Charge Level +int LOS::batteryCharge(){ //Returns: percent charge (0-100), anything outside that range is counted as an error + int charge = get_sysctlbyname_int("hw.acpi.battery.life"); + if(charge > 100){ charge = -1; } //invalid charge + return charge; +} + +//Battery Charging State +bool LOS::batteryIsCharging(){ + return (get_sysctlbyname_int("hw.acpi.battery.state") == 0); +} + +//Battery Time Remaining +int LOS::batterySecondsLeft(){ //Returns: estimated number of seconds remaining + int time = get_sysctlbyname_int("hw.acpi.battery.time"); + if (time > 0) { + // time is in minutes + time *= 60; + } + return time; +} + +//File Checksums +QStringList LOS::Checksums(QStringList filepaths){ //Return: checksum of the input file + QStringList info = LUtils::getCmdOutput("md5 \""+filepaths.join("\" \"")+"\""); + for(int i=0; i<info.length(); i++){ + if( !info[i].contains(" = ") ){ info.removeAt(i); i--; } + else{ + //Strip out the extra information + info[i] = info[i].section(" = ",1,1); + } + } + return info; +} + +//file system capacity +QString LOS::FileSystemCapacity(QString dir) { //Return: percentage capacity as give by the df command + QStringList mountInfo = LUtils::getCmdOutput("df \"" + dir+"\""); + QString::SectionFlag skipEmpty = QString::SectionSkipEmpty; + //we take the 5th word on the 2 line + QString capacity = mountInfo[1].section(" ",4,4, skipEmpty); + return capacity; +} + +static float sensor_value_to_degC(int64_t value) { + return (value - 273150000) / 1000000.0; +} + +//Returns: List containing the temperature of any CPU's ("50C" for example) +QStringList LOS::CPUTemperatures(){ + QStringList temps; + + int mib[5]; + mib[0] = CTL_HW; + mib[1] = HW_SENSORS; + + for (int dev=0; dev < MAXSENSORDEVICES; ++dev) { + struct sensordev sensordev; + size_t sdlen = sizeof(sensordev); + + mib[2] = dev; + if (sysctl(mib, 3, &sensordev, &sdlen, NULL, 0) == -1) { + continue; + } + mib[3] = SENSOR_TEMP; + for (int numt=0; numt < sensordev.maxnumt[SENSOR_TEMP]; ++numt) { + mib[4] = numt; + struct sensor sensor; + size_t slen = sizeof(sensor); + if (sysctl(mib, 5, &sensor, &slen, NULL, 0) == -1) { + continue; + } + + // XXX: Filter out non-cpu temperatures + + int degC = (int)sensor_value_to_degC(sensor.value); + temps << QString::number(degC) + "C" + "(" + QString(sensordev.xname) + ")"; + } + } + + return temps; +} + +int LOS::CPUUsagePercent(){ //Returns: Overall percentage of the amount of CPU cycles in use (-1 for errors) + return -1; //not implemented yet +} + +int LOS::MemoryUsagePercent(){ + //SYSCTL: vm.stats.vm.v_<something>_count + unsigned int v_page_count = 0; + unsigned int v_wire_count = 0; + unsigned int v_active_count = 0; + + if (!get_sysctlbyname_uint("vm.stats.vm.v_page_count", &v_page_count)) return -1; + if (!get_sysctlbyname_uint("vm.stats.vm.v_wire_count", &v_wire_count)) return -1; + if (!get_sysctlbyname_uint("vm.stats.vm.v_active_count", &v_active_count)) return -1; + + //List output: [total, wired, active] + double perc = 100.0 * ((long)v_wire_count+(long)v_active_count)/((double)v_page_count); + return qRound(perc); +} + +QStringList LOS::DiskUsage(){ //Returns: List of current read/write stats for each device + return QStringList(); //not implemented yet +} +#endif diff --git a/src-qt5/core/libLumina/LuminaOS-FreeBSD.cpp b/src-qt5/core/libLumina/LuminaOS-FreeBSD.cpp new file mode 100644 index 00000000..72abf0eb --- /dev/null +++ b/src-qt5/core/libLumina/LuminaOS-FreeBSD.cpp @@ -0,0 +1,362 @@ +//=========================================== +// Lumina-DE source code +// Copyright (c) 2014-2015, Ken Moore +// Available under the 3-clause BSD license +// See the LICENSE file for full details +//=========================================== +#ifdef __FreeBSD__ +#include "LuminaOS.h" +#include <unistd.h> +#include <sys/types.h> +#include <sys/sysctl.h> + +#include <QDebug> +//can't read xbrightness settings - assume invalid until set +static int screenbrightness = -1; +static int audiovolume = -1; + +QString LOS::OSName(){ return "FreeBSD"; } + +//OS-specific prefix(s) +// NOTE: PREFIX, L_ETCDIR, L_SHAREDIR are defined in the OS-detect.pri project file and passed in +QString LOS::LuminaShare(){ return (L_SHAREDIR+"/Lumina-DE/"); } //Install dir for Lumina share files +QString LOS::AppPrefix(){ return "/usr/local/"; } //Prefix for applications +QString LOS::SysPrefix(){ return "/usr/"; } //Prefix for system + +//OS-specific application shortcuts (*.desktop files) +QString LOS::ControlPanelShortcut(){ return "/usr/local/share/applications/pccontrol.desktop"; } //system control panel +QString LOS::AppStoreShortcut(){ return "/usr/local/share/applications/softmanager.desktop"; } //graphical app/pkg manager + +// ==== ExternalDevicePaths() ==== +QStringList LOS::ExternalDevicePaths(){ + //Returns: QStringList[<type>::::<filesystem>::::<path>] + //Note: <type> = [USB, HDRIVE, DVD, SDCARD, UNKNOWN] + QStringList devs = LUtils::getCmdOutput("mount"); + //Now check the output + for(int i=0; i<devs.length(); i++){ + if(devs[i].startsWith("/dev/")){ + devs[i].replace("\t"," "); + QString type = devs[i].section(" on ",0,0); + type.remove("/dev/"); + //Determine the type of hardware device based on the dev node + if(type.startsWith("da")){ type = "USB"; } + else if(type.startsWith("ada")){ type = "HDRIVE"; } + else if(type.startsWith("mmsd")){ type = "SDCARD"; } + else if(type.startsWith("cd")||type.startsWith("acd")){ type="DVD"; } + else{ type = "UNKNOWN"; } + //Now put the device in the proper output format + devs[i] = type+"::::"+devs[i].section("(",1,1).section(",",0,0)+"::::"+devs[i].section(" on ",1,50).section("(",0,0).simplified(); + }else{ + //invalid device - remove it from the list + devs.removeAt(i); + i--; + } + } + return devs; +} + +//Read screen brightness information +int LOS::ScreenBrightness(){ + //Returns: Screen Brightness as a percentage (0-100, with -1 for errors) + //Make sure we are not running in VirtualBox (does not work in a VM) + QStringList info = LUtils::getCmdOutput("pciconf -lv"); + if( !info.filter("VirtualBox", Qt::CaseInsensitive).isEmpty() ){ return -1; } + else if( !LUtils::isValidBinary("xbrightness") ){ return -1; } //incomplete install + //Now perform the standard brightness checks + if(screenbrightness==-1){ //memory value + if(QFile::exists(QDir::homePath()+"/.lumina/.currentxbrightness")){ //saved file value + int val = LUtils::readFile(QDir::homePath()+"/.lumina/.currentxbrightness").join("").simplified().toInt(); + screenbrightness = val; + } + } + //If it gets to this point, then we have a valid (but new) installation + if(screenbrightness<0){ screenbrightness = 100; } //default value for systems + + return screenbrightness; +} + +//Set screen brightness +void LOS::setScreenBrightness(int percent){ + if(percent == -1){ return; } //This is usually an invalid value passed directly to the setter + //ensure bounds + if(percent<0){percent=0;} + else if(percent>100){ percent=100; } + //Run the command(s) + bool success = false; + // - try hardware setting first (PC-BSD || or intel_backlight) + if( LUtils::isValidBinary("pc-sysconfig") ){ + //Use PC-BSD tool (direct sysctl control) + QString ret = LUtils::getCmdOutput("pc-sysconfig", QStringList() <<"setscreenbrightness "+QString::number(percent)).join(""); + success = ret.toLower().contains("success"); + qDebug() << "Set hardware brightness:" << percent << success; + } + if( !success && LUtils::isValidBinary("intel_backlight")){ + //Use the intel_backlight utility (only for Intel mobo/hardware?) + if(0== LUtils::runCmd("intel_backlight", QStringList() <<QString::number(percent)) ){ + //This utility does not report success/failure - run it again to get the current value and ensure it was set properly + success = (percent == LUtils::getCmdOutput("intel_backlight").join("").section("%",0,0).section(":",1,1).simplified().toInt() ); + } + } + // - if hardware brightness does not work, use software brightness + if(!success){ + QString cmd = "xbrightness %1"; + float pf = percent/100.0; //convert to a decimel + cmd = cmd.arg( QString::number( int(65535*pf) ) ); + success = (0 == LUtils::runCmd(cmd) ); + } + //Save the result for later + if(!success){ screenbrightness = -1; } + else{ screenbrightness = percent; } + LUtils::writeFile(QDir::homePath()+"/.lumina/.currentxbrightness", QStringList() << QString::number(screenbrightness), true); +} + +//Read the current volume +int LOS::audioVolume(){ //Returns: audio volume as a percentage (0-100, with -1 for errors) + int out = audiovolume; + if(out < 0){ + //First time session check: Load the last setting for this user + QString info = LUtils::readFile(QDir::homePath()+"/.lumina/.currentvolume").join(""); + if(!info.isEmpty()){ + out = info.simplified().toInt(); + audiovolume = out; //unset this internal flag + return out; + } + } + + //probe the system for the current volume (other utils could be changing it) + QString info = LUtils::getCmdOutput("mixer -S vol").join(":").simplified(); //ignores any other lines + if(!info.isEmpty()){ + int L = info.section(":",1,1).toInt(); + int R = info.section(":",2,2).toInt(); + if(L>R){ out = L; } + else{ out = R; } + if(out != audiovolume){ + //Volume changed by other utility: adjust the saved value as well + LUtils::writeFile(QDir::homePath()+"/.lumina/.currentvolume", QStringList() << QString::number(out), true); + } + audiovolume = out; + } + + return out; +} + +//Set the current volume +void LOS::setAudioVolume(int percent){ + if(percent<0){percent=0;} + else if(percent>100){percent=100;} + QString info = LUtils::getCmdOutput("mixer -S vol").join(":").simplified(); //ignores any other lines + if(!info.isEmpty()){ + int L = info.section(":",1,1).toInt(); + int R = info.section(":",2,2).toInt(); + int diff = L-R; + if((percent == L) && (L==R)){ return; } //already set to that volume + if(diff<0){ R=percent; L=percent+diff; } //R Greater + else{ L=percent; R=percent-diff; } //L Greater or equal + //Check bounds + if(L<0){L=0;}else if(L>100){L=100;} + if(R<0){R=0;}else if(R>100){R=100;} + //Run Command + audiovolume = percent; //save for checking later + LUtils::runCmd("mixer vol "+QString::number(L)+":"+QString::number(R)); + LUtils::writeFile(QDir::homePath()+"/.lumina/.currentvolume", QStringList() << QString::number(percent), true); + } +} + +//Change the current volume a set amount (+ or -) +void LOS::changeAudioVolume(int percentdiff){ + QString info = LUtils::getCmdOutput("mixer -S vol").join(":").simplified(); //ignores any other lines + if(!info.isEmpty()){ + int L = info.section(":",1,1).toInt() + percentdiff; + int R = info.section(":",2,2).toInt() + percentdiff; + //Check bounds + if(L<0){L=0;}else if(L>100){L=100;} + if(R<0){R=0;}else if(R>100){R=100;} + //Run Command + LUtils::runCmd("mixer vol "+QString::number(L)+":"+QString::number(R)); + } +} + +//Check if a graphical audio mixer is installed +bool LOS::hasMixerUtility(){ + return QFile::exists("/usr/local/bin/pc-mixer"); +} + +//Launch the graphical audio mixer utility +void LOS::startMixerUtility(){ + QProcess::startDetached("pc-mixer -notray"); +} + +//Check for user system permission (shutdown/restart) +bool LOS::userHasShutdownAccess(){ + //User needs to be a part of the operator group to be able to run the shutdown command + QStringList groups = LUtils::getCmdOutput("id -Gn").join(" ").split(" "); + return groups.contains("operator"); +} + +bool LOS::systemPerformingUpdates(){ + return (QProcess::execute("pgrep -F /tmp/.updateInProgress")==0); //this is 0 if updating right now +} + +//System Shutdown +void LOS::systemShutdown(){ //start poweroff sequence + QProcess::startDetached("shutdown -p now"); +} + +//System Restart +void LOS::systemRestart(){ //start reboot sequence + QProcess::startDetached("shutdown -r now"); +} + +//Check for suspend support +bool LOS::systemCanSuspend(){ + //This will only function on PC-BSD + //(permissions issues on standard FreeBSD unless setup a special way) + bool ok = QFile::exists("/usr/local/bin/pc-sysconfig"); + if(ok){ + ok = LUtils::getCmdOutput("pc-sysconfig systemcansuspend").join("").toLower().contains("true"); + } + return ok; +} + +//Put the system into the suspend state +void LOS::systemSuspend(){ + QProcess::startDetached("pc-sysconfig suspendsystem"); +} + +//Battery Availability +bool LOS::hasBattery(){ + int val = LUtils::getCmdOutput("apm -l").join("").toInt(); + return (val >= 0 && val <= 100); +} + +//Battery Charge Level +int LOS::batteryCharge(){ //Returns: percent charge (0-100), anything outside that range is counted as an error + int charge = LUtils::getCmdOutput("apm -l").join("").toInt(); + if(charge > 100){ charge = -1; } //invalid charge + return charge; +} + +//Battery Charging State +bool LOS::batteryIsCharging(){ + return (LUtils::getCmdOutput("apm -a").join("").simplified() == "1"); +} + +//Battery Time Remaining +int LOS::batterySecondsLeft(){ //Returns: estimated number of seconds remaining + return LUtils::getCmdOutput("apm -t").join("").toInt(); +} + +//File Checksums +QStringList LOS::Checksums(QStringList filepaths){ //Return: checksum of the input file + QStringList info = LUtils::getCmdOutput("md5 \""+filepaths.join("\" \"")+"\""); + for(int i=0; i<info.length(); i++){ + if( !info[i].contains(" = ") ){ info.removeAt(i); i--; } + else{ + //Strip out the extra information + info[i] = info[i].section(" = ",1,1); + } + } + return info; +} + +//file system capacity +QString LOS::FileSystemCapacity(QString dir) { //Return: percentage capacity as give by the df command + QStringList mountInfo = LUtils::getCmdOutput("df \"" + dir+"\""); + QString::SectionFlag skipEmpty = QString::SectionSkipEmpty; + //we take the 5th word on the 2 line + QString capacity = mountInfo[1].section(" ",4,4, skipEmpty); + return capacity; +} + +QStringList LOS::CPUTemperatures(){ //Returns: List containing the temperature of any CPU's ("50C" for example) + static QStringList vars = QStringList(); + QStringList temps; + if(vars.isEmpty()){ temps = LUtils::getCmdOutput("sysctl -ai").filter(".temperature:"); } + else{ temps = LUtils::getCmdOutput("sysctl "+vars.join(" ")); vars.clear(); } + + temps.sort(); + for(int i=0; i<temps.length(); i++){ + if(temps[i].contains(".acpi.") || temps[i].contains(".cpu")){ + vars << temps[i].section(":",0,0); //save this variable for later checks + temps[i] = temps[i].section(":",1,5).simplified(); //only pull out the value, not the variable + }else{ + //non CPU temperature - skip it + temps.removeAt(i); i--; + } + } + /*}else{ + //Already have the known variables - use the library call directly (much faster) + for(int i=0; i<vars.length(); i++){ + float result[1000]; + size_t len = sizeof(result); + if(0 != sysctlbyname(vars[i].toLocal8Bit(), result, &len, NULL, 0)){ continue; } //error returned + //result[len] = '\0'; //make sure to null-terminate the output + QString res; + for(int r=0; r<((int) len); r++){ res.append(QString::number(result[r])); } + temps << res; + qDebug() << "Temp Result:" << vars[i] << res << result << len; + } + }*/ + return temps; +} + +int LOS::CPUUsagePercent(){ //Returns: Overall percentage of the amount of CPU cycles in use (-1 for errors) + //Calculate the percentage based on the kernel information directly - no extra utilities + QStringList result = LUtils::getCmdOutput("sysctl -n kern.cp_times").join("").split(" "); + static QStringList last = QStringList(); + if(last.isEmpty()){ last = result; return 0; } //need two ticks before it works properly + + double tot = 0; + int cpnum = 0; + for(int i=4; i<result.length(); i+=5){ + //The values come in blocks of 5 per CPU: [user,nice,system,interrupt,idle] + cpnum++; //the number of CPU's accounted for (to average out at the end) + //qDebug() <<"CPU:" << cpnum; + long sum = 0; + //Adjust/all the data to correspond to diffs from the previous check + for(int j=0; j<5; j++){ + QString tmp = result[i-j]; + result[i-j] = QString::number(result[i-j].toLong()-last[i-j].toLong()); //need the difference between last run and this one + sum += result[i-j].toLong(); + last[i-j] = tmp; //make sure to keep the original value around for the next run + } + //Calculate the percentage used for this CPU (100% - IDLE%) + tot += 100.0L - ( (100.0L*result[i].toLong())/sum ); //remember IDLE is the last of the five values per CPU + } + return qRound(tot/cpnum); + +} + +int LOS::MemoryUsagePercent(){ + //SYSCTL: vm.stats.vm.v_<something>_count + QStringList info = LUtils::getCmdOutput("sysctl -n vm.stats.vm.v_page_count vm.stats.vm.v_wire_count vm.stats.vm.v_active_count"); + if(info.length()<3){ return -1; } //error in fetching information + //List output: [total, wired, active] + double perc = 100.0* (info[1].toLong()+info[2].toLong())/(info[0].toDouble()); + return qRound(perc); +} + +QStringList LOS::DiskUsage(){ //Returns: List of current read/write stats for each device + QStringList info = LUtils::getCmdOutput("iostat -dx -c 2 -w 0.1 -t IDE -t SCSI -t da"); + //Note: This returns the statistics *twice*: the first set is average for entire system + // - the second set is the actual average stats over that 0.1 second + if(info.length()<6){ return QStringList(); } //nothing from command + QStringList labs = info[1].split(" ",QString::SkipEmptyParts); + QStringList out; + QString fmt = "%1: %2 %3"; + for(int i=(info.length()/2)+2; i<info.length(); i++){ //skip the first data entry , just labels + info[i].replace("\t"," "); + if(i==1){ labs = info[i].split(" ", QString::SkipEmptyParts); }//the labels for each column + else{ + QStringList data = info[i].split(" ",QString::SkipEmptyParts); //data[0] is always the device + //qDebug() << "Data Line:" << data; + if(data.length()>2 && labs.length()>2){ + out << fmt.arg(data[0], data[1]+" "+labs[1], data[2]+" "+labs[2]); + } + } + } + + return out; +} + +#endif diff --git a/src-qt5/core/libLumina/LuminaOS-Gentoo.cpp b/src-qt5/core/libLumina/LuminaOS-Gentoo.cpp new file mode 100644 index 00000000..e3d5fe56 --- /dev/null +++ b/src-qt5/core/libLumina/LuminaOS-Gentoo.cpp @@ -0,0 +1,300 @@ +//=========================================== +// Lumina-DE source code +// Copyright (c) 2016, Ken Moore +// Available under the 3-clause BSD license +// See the LICENSE file for full details +//=========================================== +#ifdef __linux__ +#include <QDebug> +#include "LuminaOS.h" +#include <unistd.h> +#include <stdio.h> // Needed for BUFSIZ + +//can't read xbrightness settings - assume invalid until set +static int screenbrightness = -1; + +QString LOS::OSName(){ return "Gentoo Linux"; } + +//OS-specific prefix(s) +// NOTE: PREFIX, L_ETCDIR, L_SHAREDIR are defined in the OS-detect.pri project file and passed in +QString LOS::LuminaShare(){ return (L_SHAREDIR+"/Lumina-DE/"); } //Install dir for Lumina share files +QString LOS::AppPrefix(){ return "/usr/"; } //Prefix for applications +QString LOS::SysPrefix(){ return "/"; } //Prefix for system + +//OS-specific application shortcuts (*.desktop files) +QString LOS::ControlPanelShortcut(){ return ""; } //system control panel +QString LOS::AppStoreShortcut(){ return LOS::AppPrefix() + "/share/applications/porthole.desktop"; } //graphical app/pkg manager + +// ==== ExternalDevicePaths() ==== +QStringList LOS::ExternalDevicePaths(){ + /* Returns: QStringList[<type>::::<filesystem>::::<path>] + Note: <type> = [USB, HDRIVE, DVD, SDCARD, UNKNOWN] + df is much better for this than mount, because it skips + all non-physical devices (like bind-mounts from schroot) + so they are not listed in lumina-fm */ + QStringList devs = LUtils::getCmdOutput("df --output=source,fstype,target"); + //Now check the output + for(int i=0; i<devs.length(); i++){ + if(devs[i].startsWith("/dev/")){ + devs[i] = devs[i].simplified(); + QString type = devs[i].section(" on ",0,0); + type.remove("/dev/"); + //Determine the type of hardware device based on the dev node + if(type.startsWith("sd") || type.startsWith("nvme")){ type = "HDRIVE"; } + else if(type.startsWith("sr")){ type="DVD"; } + else if(type.contains("mapper")){ type="LVM"; } + else{ type = "UNKNOWN"; } + //Now put the device in the proper output format + devs[i] = type + "::::" + devs[i].section(" ",1,1) + "::::" + devs[i].section(" ",2,2); + }else{ + //invalid device - remove it from the list + devs.removeAt(i); + i--; + } + } + return devs; +} + +//Read screen brightness information +int LOS::ScreenBrightness(){ + //Returns: Screen Brightness as a percentage (0-100, with -1 for errors) + if(screenbrightness==-1){ + if(QFile::exists(QDir::homePath()+"/.lumina/.currentxbrightness")){ + int val = LUtils::readFile(QDir::homePath()+"/.lumina/.currentxbrightness").join("").simplified().toInt(); + screenbrightness = val; + } + } + return screenbrightness; + +} + +//Set screen brightness +void LOS::setScreenBrightness(int percent){ + //ensure bounds + if(percent<0){percent=0;} + else if(percent>100){ percent=100; } + // float pf = percent/100.0; //convert to a decimel + //Run the command + QString cmd = "xbacklight -set %1"; + // cmd = cmd.arg( QString::number( int(65535*pf) ) ); + cmd = cmd.arg( QString::number( percent ) ); + int ret = LUtils::runCmd(cmd); + //Save the result for later + if(ret!=0){ screenbrightness = -1; } + else{ screenbrightness = percent; } + LUtils::writeFile(QDir::homePath()+"/.lumina/.currentxbrightness", QStringList() << QString::number(screenbrightness), true); + +} + +//Read the current volume +int LOS::audioVolume(){ //Returns: audio volume as a percentage (0-100, with -1 for errors) +QString info = LUtils::getCmdOutput("amixer get Master").join("").simplified();; + int out = -1; + int start_position, end_position; + QString current_volume; + if(!info.isEmpty()){ + start_position = info.indexOf("["); + start_position++; + end_position = info.indexOf("%"); + current_volume = info.mid(start_position, end_position - start_position); + out = current_volume.toInt(); + } + return out; + +} + +//Set the current volume +void LOS::setAudioVolume(int percent){ + if(percent<0){percent=0;} + else if(percent>100){percent=100;} + // QString info = "amixer -c 0 sset Master,0 " + QString::number(percent) + "%"; + QString info = "amixer set Master " + QString::number(percent) + "%"; + if(!info.isEmpty()){ + //Run Command + LUtils::runCmd(info); + } + +} + +//Change the current volume a set amount (+ or -) +void LOS::changeAudioVolume(int percentdiff){ + int old_volume = audioVolume(); + int new_volume = old_volume + percentdiff; + if (new_volume < 0) + new_volume = 0; + if (new_volume > 100) + new_volume = 100; + qDebug() << "Setting new volume to: " << new_volume; + setAudioVolume(new_volume); +} + +//Check if a graphical audio mixer is installed +bool LOS::hasMixerUtility(){ + return QFile::exists(LOS::AppPrefix() + "bin/pavucontrol"); +} + +//Launch the graphical audio mixer utility +void LOS::startMixerUtility(){ + QProcess::startDetached(LOS::AppPrefix() + "bin/pavucontrol"); +} + +//Check for user system permission (shutdown/restart) +bool LOS::userHasShutdownAccess(){ + return QProcess::startDetached("dbus-send --system --print-reply=literal \ + --type=method_call --dest=org.freedesktop.login1 \ + /org/freedesktop/login1 org.freedesktop.login1.Manager.CanPowerOff"); +} + +//Check for whether the system is safe to power off (no updates being perfomed) +bool LOS::systemPerformingUpdates(){ + return false; //Not implemented yet +} + +//System Shutdown +void LOS::systemShutdown(){ //start poweroff sequence + QProcess::startDetached("dbus-send --system --print-reply \ + --dest=org.freedesktop.login1 /org/freedesktop/login1 \ + org.freedesktop.login1.Manager.PowerOff boolean:true"); +} + +//System Restart +void LOS::systemRestart(){ //start reboot sequence + QProcess::startDetached("dbus-send --system --print-reply \ + --dest=org.freedesktop.login1 /org/freedesktop/login1 \ + org.freedesktop.login1.Manager.Reboot boolean:true"); +} + +//Check for suspend support +bool LOS::systemCanSuspend(){ + return QProcess::startDetached("dbus-send --system --print-reply=literal \ + --type=method_call --dest=org.freedesktop.login1 \ + /org/freedesktop/login1 org.freedesktop.login1.Manager.CanSuspend"); +} + +//Put the system into the suspend state +void LOS::systemSuspend(){ + QProcess::startDetached("dbus-send --system --print-reply \ + --dest=org.freedesktop.login1 /org/freedesktop/login1 \ + org.freedesktop.login1.Manager.Suspend boolean:true"); +} + +//Battery Availability +bool LOS::hasBattery(){ + QString my_status = LUtils::getCmdOutput("acpi -b").join(""); + bool no_battery = my_status.contains("No support"); + if (no_battery) return false; + return true; +} + +//Battery Charge Level +int LOS::batteryCharge(){ //Returns: percent charge (0-100), anything outside that range is counted as an error + QString my_status = LUtils::getCmdOutput("acpi -b").join(""); + int my_start = my_status.indexOf("%"); + // get the number right before the % sign + int my_end = my_start; + my_start--; + while ( (my_status[my_start] != ' ') && (my_start > 0) ) + my_start--; + my_start++; + int my_charge = my_status.mid(my_start, my_end - my_start).toInt(); + if ( (my_charge < 0) || (my_charge > 100) ) return -1; + return my_charge; +} + +//Battery Charging State +// Many possible values are returned if the laptop is plugged in +// these include "Unknown, Full and No support. +// However, it seems just one status is returned when running +// on battery and that is "Discharging". So if the value we get +// is NOT Discharging then we assume the battery is charging. +bool LOS::batteryIsCharging(){ + QString my_status = LUtils::getCmdOutput("acpi -b").join(""); + bool discharging = my_status.contains("Discharging"); + if (discharging) return false; + return true; +} + +//Battery Time Remaining +int LOS::batterySecondsLeft(){ //Returns: estimated number of seconds remaining + return 0; //not implemented yet for Linux +} + +//File Checksums +QStringList LOS::Checksums(QStringList filepaths){ //Return: checksum of the input file + QStringList info = LUtils::getCmdOutput("md5sum \""+filepaths.join("\" \"")+"\""); + for(int i=0; i<info.length(); i++){ + // first: md5sum: = error ; second: there's always one empty entry generated by getCmdOutput + if( info[i].startsWith("md5sum:") || info[i].isEmpty()){ info.removeAt(i); i--; } + else{ + //Strip out the extra information + info[i] = info[i].section(" ",0,0); + } + } + return info; +} + +//file system capacity +QString LOS::FileSystemCapacity(QString dir) { //Return: percentage capacity as give by the df command + QStringList mountInfo = LUtils::getCmdOutput("df -h \"" + dir + "\""); + QString::SectionFlag skipEmpty = QString::SectionSkipEmpty; + //output: 200G of 400G available on /mount/point + QString capacity = mountInfo[1].section(" ",3,3, skipEmpty) + " of " + mountInfo[1].section(" ",1,1, skipEmpty) + " available on " + mountInfo[1].section(" ",5,5, skipEmpty); + return capacity; +} + +QStringList LOS::CPUTemperatures(){ //Returns: List containing the temperature of any CPU's ("50C" for example) + QStringList temp = LUtils::getCmdOutput("acpi -t").filter("degrees"); + for(int i=0; i<temp.length(); i++){ + if(temp[i].startsWith("Thermal")){ + temp[i] = temp[i].section(" ", 4, 6); + }else{ + temp.removeAt(i); i--; + } + } + if(temp.isEmpty()) { + temp << "Not available"; + } + return temp; +} + +int LOS::CPUUsagePercent(){ //Returns: Overall percentage of the amount of CPU cycles in use (-1 for errors) + QStringList info = LUtils::getCmdOutput("top -bn1").filter("Cpu(s)"); + if(info.isEmpty()){ return -1; } + QString idle = info.first().section(" ", 7, 7, QString::SectionSkipEmpty); + if(idle.isEmpty()){ return -1; } + else{ + return (100 - idle.toDouble()); + } +} + +int LOS::MemoryUsagePercent(){ + QStringList mem = LUtils::getCmdOutput("top -bn1").filter("Mem :"); + if(mem.isEmpty()){ return -1; } + double fB = 0; //Free Bytes + double uB = 0; //Used Bytes + fB = mem.first().section(" ", 5, 5, QString::SectionSkipEmpty).toDouble(); + uB = mem.first().section(" ", 7, 7, QString::SectionSkipEmpty).toDouble(); + double per = (uB/(fB+uB)) * 100.0; + return qRound(per); +} + +QStringList LOS::DiskUsage(){ //Returns: List of current read/write stats for each device + QStringList info = LUtils::getCmdOutput("iostat -dx -N"); + if(info.length()<3){ return QStringList(); } //nothing from command + QStringList labs = info[1].split(" ",QString::SkipEmptyParts); + QStringList out; + QString fmt = "%1: %2 %3"; + for(int i=2; i<info.length(); i++){ //skip the first two lines, just labels + info[i].replace("\t"," "); + if(info[i].startsWith("Device:")){ labs = info[i].split(" ", QString::SkipEmptyParts); }//the labels for each column + else{ + QStringList data = info[i].split(" ",QString::SkipEmptyParts); //data[0] is always the device + if(data.length()>2 && labs.length()>2){ + out << fmt.arg(data[0], data[3]+" "+labs[3], data[4]+" "+labs[4]); + } + } + } + + return out; +} +#endif diff --git a/src-qt5/core/libLumina/LuminaOS-Linux.cpp b/src-qt5/core/libLumina/LuminaOS-Linux.cpp new file mode 100644 index 00000000..5939c9d1 --- /dev/null +++ b/src-qt5/core/libLumina/LuminaOS-Linux.cpp @@ -0,0 +1,245 @@ +//=========================================== +// Lumina-DE source code +// Copyright (c) 2014, Ken Moore +// Available under the 3-clause BSD license +// See the LICENSE file for full details +//=========================================== +#ifdef __linux__ +#include <QDebug> +#include "LuminaOS.h" +#include <unistd.h> +#include <stdio.h> // Needed for BUFSIZ + +//can't read xbrightness settings - assume invalid until set +static int screenbrightness = -1; + +QString LOS::OSName(){ return "Linux"; } + +//OS-specific prefix(s) +// NOTE: PREFIX, L_ETCDIR, L_SHAREDIR are defined in the OS-detect.pri project file and passed in +QString LOS::LuminaShare(){ return (L_SHAREDIR+"/Lumina-DE/"); } //Install dir for Lumina share files +QString LOS::AppPrefix(){ return "/usr/"; } //Prefix for applications +QString LOS::SysPrefix(){ return "/usr/"; } //Prefix for system + +//OS-specific application shortcuts (*.desktop files) +QString LOS::ControlPanelShortcut(){ return ""; } //system control panel +QString LOS::AppStoreShortcut(){ return ""; } //graphical app/pkg manager + +// ==== ExternalDevicePaths() ==== +QStringList LOS::ExternalDevicePaths(){ + //Returns: QStringList[<type>::::<filesystem>::::<path>] + //Note: <type> = [USB, HDRIVE, DVD, SDCARD, UNKNOWN] + QStringList devs = LUtils::getCmdOutput("mount"); + //Now check the output + for(int i=0; i<devs.length(); i++){ + if(devs[i].startsWith("/dev/")){ + devs[i] = devs[i].simplified(); + QString type = devs[i].section(" ",0,0); + type.remove("/dev/"); + //Determine the type of hardware device based on the dev node + if(type.startsWith("sd") || type.startsWith("nvme")){ type = "HDRIVE"; } + else if(type.startsWith("sr")){ type="DVD"; } + else if(type.contains("mapper")){ type="LVM"; } + else{ type = "UNKNOWN"; } + //Now put the device in the proper output format + devs[i] = type+"::::"+devs[i].section(" ",4,4)+"::::"+devs[i].section(" ",2,2); + }else{ + //invalid device - remove it from the list + devs.removeAt(i); + i--; + } + } + return devs; +} + +//Read screen brightness information +int LOS::ScreenBrightness(){ + //Returns: Screen Brightness as a percentage (0-100, with -1 for errors) + if(screenbrightness==-1){ + if(QFile::exists(QDir::homePath()+"/.lumina/.currentxbrightness")){ + int val = LUtils::readFile(QDir::homePath()+"/.lumina/.currentxbrightness").join("").simplified().toInt(); + screenbrightness = val; + } + } + return screenbrightness; + +} + +//Set screen brightness +void LOS::setScreenBrightness(int percent){ + //ensure bounds + if(percent<0){percent=0;} + else if(percent>100){ percent=100; } + // float pf = percent/100.0; //convert to a decimel + //Run the command + QString cmd = "xbacklight -set %1"; + // cmd = cmd.arg( QString::number( int(65535*pf) ) ); + cmd = cmd.arg( QString::number( percent ) ); + int ret = LUtils::runCmd(cmd); + //Save the result for later + if(ret!=0){ screenbrightness = -1; } + else{ screenbrightness = percent; } + LUtils::writeFile(QDir::homePath()+"/.lumina/.currentxbrightness", QStringList() << QString::number(screenbrightness), true); + +} + +//Read the current volume +int LOS::audioVolume(){ //Returns: audio volume as a percentage (0-100, with -1 for errors) +QString info = LUtils::getCmdOutput("amixer get Master").join("").simplified();; + int out = -1; + int start_position, end_position; + QString current_volume; + if(!info.isEmpty()){ + start_position = info.indexOf("["); + start_position++; + end_position = info.indexOf("%"); + current_volume = info.mid(start_position, end_position - start_position); + out = current_volume.toInt(); + } + return out; + + +} + +//Set the current volume +void LOS::setAudioVolume(int percent){ + if(percent<0){percent=0;} + else if(percent>100){percent=100;} + QString info = "amixer set Master " + QString::number(percent) + "%"; + //Run Command + LUtils::runCmd(info); +} + +//Change the current volume a set amount (+ or -) +void LOS::changeAudioVolume(int percentdiff){ + int old_volume = audioVolume(); + int new_volume = old_volume + percentdiff; + if (new_volume < 0) + new_volume = 0; + if (new_volume > 100) + new_volume = 100; + qDebug() << "Setting new volume to: " << new_volume; + setAudioVolume(new_volume); +} + +//Check if a graphical audio mixer is installed +bool LOS::hasMixerUtility(){ + return QFile::exists(LOS::AppPrefix() + "bin/pavucontrol"); +} + +//Launch the graphical audio mixer utility +void LOS::startMixerUtility(){ + QProcess::startDetached(LOS::AppPrefix() + "bin/pavucontrol"); +} + +//Check for user system permission (shutdown/restart) +bool LOS::userHasShutdownAccess(){ + return true; //not implemented yet +} + +//Check for whether the system is safe to power off (no updates being perfomed) +bool LOS::systemPerformingUpdates(){ + return false; //Not implemented yet +} + +//System Shutdown +void LOS::systemShutdown(){ //start poweroff sequence + QProcess::startDetached("shutdown -P -h now"); +} + +//System Restart +void LOS::systemRestart(){ //start reboot sequence + QProcess::startDetached("shutdown -r now"); +} + +//Check for suspend support +bool LOS::systemCanSuspend(){ + return false; +} + +//Put the system into the suspend state +void LOS::systemSuspend(){ + +} + +//Battery Availability +bool LOS::hasBattery(){ + QString my_status = LUtils::getCmdOutput("acpi -b").join(""); + bool no_battery = my_status.contains("No support"); + if (no_battery) return false; + return true; +} + +//Battery Charge Level +int LOS::batteryCharge(){ //Returns: percent charge (0-100), anything outside that range is counted as an error + QString my_status = LUtils::getCmdOutput("acpi -b").join(""); + int my_start = my_status.indexOf("%"); + // get the number right before the % sign + int my_end = my_start; + my_start--; + while ( (my_status[my_start] != ' ') && (my_start > 0) ) + my_start--; + my_start++; + int my_charge = my_status.mid(my_start, my_end - my_start).toInt(); + if ( (my_charge < 0) || (my_charge > 100) ) return -1; + return my_charge; +} + +//Battery Charging State +// Many possible values are returned if the laptop is plugged in +// these include "Unknown, Full and No support. +// However, it seems just one status is returned when running +// on battery and that is "Discharging". So if the value we get +// is NOT Discharging then we assume the battery is charging. +bool LOS::batteryIsCharging(){ + QString my_status = LUtils::getCmdOutput("acpi -b").join(""); + bool discharging = my_status.contains("Discharging"); + if (discharging) return false; + return true; +} + +//Battery Time Remaining +int LOS::batterySecondsLeft(){ //Returns: estimated number of seconds remaining + return 0; //not implemented yet for Linux +} + +//File Checksums +QStringList LOS::Checksums(QStringList filepaths){ //Return: checksum of the input file + QStringList info = LUtils::getCmdOutput("md5sum \""+filepaths.join("\" \"")+"\""); + for(int i=0; i<info.length(); i++){ + // first: md5sum: = error ; second: there's always one empty entry generated by getCmdOutput + if( info[i].startsWith("md5sum:") || info[i].isEmpty()){ info.removeAt(i); i--; } + else{ + //Strip out the extra information + info[i] = info[i].section(" ",0,0); + } + } + return info; +} + +//file system capacity +QString LOS::FileSystemCapacity(QString dir) { //Return: percentage capacity as give by the df command + QStringList mountInfo = LUtils::getCmdOutput("df \"" + dir+"\""); + QString::SectionFlag skipEmpty = QString::SectionSkipEmpty; + //we take the 5th word on the 2 line + QString capacity = mountInfo[1].section(" ",4,4, skipEmpty) + " used"; + return capacity; +} + +QStringList LOS::CPUTemperatures(){ //Returns: List containing the temperature of any CPU's ("50C" for example) + return QStringList(); //not implemented yet +} + +int LOS::CPUUsagePercent(){ //Returns: Overall percentage of the amount of CPU cycles in use (-1 for errors) + return -1; //not implemented yet +} + +int LOS::MemoryUsagePercent(){ + return -1; //not implemented yet +} + +QStringList LOS::DiskUsage(){ //Returns: List of current read/write stats for each device + return QStringList(); //not implemented yet +} + +#endif diff --git a/src-qt5/core/libLumina/LuminaOS-NetBSD.cpp b/src-qt5/core/libLumina/LuminaOS-NetBSD.cpp new file mode 100644 index 00000000..866ccc5c --- /dev/null +++ b/src-qt5/core/libLumina/LuminaOS-NetBSD.cpp @@ -0,0 +1,169 @@ +//=========================================== +// Lumina-DE source code +// Copyright (c) 2014, Ken Moore +// Available under the 3-clause BSD license +// See the LICENSE file for full details +//=========================================== +#ifdef __NetBSD__ +#include "LuminaOS.h" +#include <unistd.h> +#include <stdio.h> // Needed for BUFSIZ + +QString LOS::OSName(){ return "NetBSD"; } + +//OS-specific prefix(s) +// NOTE: PREFIX, L_ETCDIR, L_SHAREDIR are defined in the OS-detect.pri project file and passed in +QString LOS::LuminaShare(){ return (L_SHAREDIR+"/Lumina-DE/"); } //Install dir for Lumina share files +QString LOS::AppPrefix(){ return "/usr/local/"; } //Prefix for applications +QString LOS::SysPrefix(){ return "/usr/"; } //Prefix for system + +//OS-specific application shortcuts (*.desktop files) +QString LOS::ControlPanelShortcut(){ return ""; } //system control panel +QString LOS::AppStoreShortcut(){ return ""; } //graphical app/pkg manager + +// ==== ExternalDevicePaths() ==== +QStringList LOS::ExternalDevicePaths(){ + //Returns: QStringList[<type>::::<filesystem>::::<path>] + //Note: <type> = [USB, HDRIVE, DVD, SDCARD, UNKNOWN] + + //Not implemented yet + return QStringList(); +} + +//Read screen brightness information +int LOS::ScreenBrightness(){ + //Returns: Screen Brightness as a percentage (0-100, with -1 for errors) + return -1; //not implemented yet +} + +//Set screen brightness +void LOS::setScreenBrightness(int percent){ + //not implemented yet +} + +//Read the current volume +int LOS::audioVolume(){ + //Returns: audio volume as a percentage (0-100, with -1 for errors) + return -1; //Not implemented yet +} + +//Set the current volume +void LOS::setAudioVolume(int percent){ + //not implemented yet +} + +//Change the current volume a set amount (+ or -) +void LOS::changeAudioVolume(int percentdiff){ + //not implemented yet +} + +//Check if a graphical audio mixer is installed +bool LOS::hasMixerUtility(){ + return false; //not implemented yet +} + +//Launch the graphical audio mixer utility +void LOS::startMixerUtility(){ + //not implemented yet +} + +//Check for user system permission (shutdown/restart) +bool LOS::userHasShutdownAccess(){ + //User needs to be a part of the operator group to be able to run the shutdown command + QStringList groups = LUtils::getCmdOutput("id -Gn").join(" ").split(" "); + return groups.contains("operator"); +} + +//Check for whether the system is safe to power off (no updates being perfomed) +bool LOS::systemPerformingUpdates(){ + return false; //Not implemented yet +} + +//System Shutdown +void LOS::systemShutdown(){ //start poweroff sequence + QProcess::startDetached("shutdown -p now"); +} + +//System Restart +void LOS::systemRestart(){ //start reboot sequence + QProcess::startDetached("shutdown -r now"); +} + +//Check for suspend support +bool LOS::systemCanSuspend(){ + return false; +} + +//Put the system into the suspend state +void LOS::systemSuspend(){ + +} + +//Battery Availability +bool LOS::hasBattery(){ + return false; //not implemented yet +} + +//Battery Charge Level +int LOS::batteryCharge(){ //Returns: percent charge (0-100), anything outside that range is counted as an error + return -1; //not implemented yet +} + +//Battery Charging State +bool LOS::batteryIsCharging(){ + return false; //not implemented yet +} + +//Battery Time Remaining +int LOS::batterySecondsLeft(){ //Returns: estimated number of seconds remaining + return 0; //not implemented yet +} + +//File Checksums +QStringList LOS::Checksums(QStringList filepaths){ //Return: checksum of the input file + //on NetBSD md5(1) has the following layout + //$ md5 DESCR Makefile + //MD5 (DESCR) = 6aaa128cf0466792be6f6d15e4589dfb + //MD5 (Makefile) = 4787f3145bba2a3cc393cdd812c5d18b + + QStringList info = LUtils::getCmdOutput("md5 \""+filepaths.join("\" \"")+"\""); + for(int i=0; i<info.length(); i++){ + if( !info[i].contains(" = ") ){ info.removeAt(i); i--; } + else{ + //Strip out the extra information + info[i] = info[i].section(" = ",1,1); + } + } + return info; +} + +//file system capacity +QString LOS::FileSystemCapacity(QString dir) { //Return: percentage capacity as give by the df command + // on NetBSD, df has the following layout: + // $ df /home + // Filesystem 512-blocks Used Avail %Cap Mounted on + // /dev/cgd0a 39711132 37140376 585200 98% /home + + QStringList mountInfo = LUtils::getCmdOutput("df \"" + dir+"\""); + QString::SectionFlag skipEmpty = QString::SectionSkipEmpty; + //we take the 5th word on the 2 line + QString capacity = mountInfo[1].section(" ",4,4, skipEmpty); + return capacity; +} + +QStringList LOS::CPUTemperatures(){ //Returns: List containing the temperature of any CPU's ("50C" for example) + return QStringList(); //not implemented yet +} + +int LOS::CPUUsagePercent(){ //Returns: Overall percentage of the amount of CPU cycles in use (-1 for errors) + return -1; //not implemented yet +} + +int LOS::MemoryUsagePercent(){ + return -1; //not implemented yet +} + +QStringList LOS::DiskUsage(){ //Returns: List of current read/write stats for each device + return QStringList(); //not implemented yet +} +#endif diff --git a/src-qt5/core/libLumina/LuminaOS-OpenBSD.cpp b/src-qt5/core/libLumina/LuminaOS-OpenBSD.cpp new file mode 100644 index 00000000..c0fdafd4 --- /dev/null +++ b/src-qt5/core/libLumina/LuminaOS-OpenBSD.cpp @@ -0,0 +1,257 @@ +//=========================================== +// Lumina-DE source code +// Copyright (c) 2014, Ken Moore +// Copyright (c) 2014, Antoine Jacoutot <ajacoutot@openbsd.org> +// Available under the 3-clause BSD license +// See the LICENSE file for full details +//=========================================== +#ifdef __OpenBSD__ +#include "LuminaOS.h" +#include <unistd.h> + +//can't read xbrightness settings - assume invalid until set +static int screenbrightness = -1; + +QString LOS::OSName(){ return "OpenBSD"; } + +//OS-specific prefix(s) +// NOTE: PREFIX, L_ETCDIR, L_SHAREDIR are defined in the OS-detect.pri project file and passed in +QString LOS::LuminaShare(){ return (L_SHAREDIR+"/Lumina-DE/"); } //Install dir for Lumina share files +QString LOS::AppPrefix(){ return "/usr/local/"; } //Prefix for applications +QString LOS::SysPrefix(){ return "/usr/"; } //Prefix for system + +//OS-specific application shortcuts (*.desktop files) +QString LOS::ControlPanelShortcut(){ return ""; } //system control panel +QString LOS::AppStoreShortcut(){ return ""; } //graphical app/pkg manager + +// ==== ExternalDevicePaths() ==== +QStringList LOS::ExternalDevicePaths(){ + //Returns: QStringList[<type>::::<filesystem>::::<path>] + //Note: <type> = [USB, HDRIVE, DVD, SDCARD, UNKNOWN] + QStringList devs = LUtils::getCmdOutput("mount"); + //Now check the output + for(int i=0; i<devs.length(); i++){ + QString type = devs[i].section(" ",0,0); + type.remove("/dev/"); + //Determine the type of hardware device based on the dev node + if(type.startsWith("sd")||type.startsWith("wd")){ type = "HDRIVE"; } + else if(type.startsWith("cd")){ type="DVD"; } + else{ type = "UNKNOWN"; } + //Now put the device in the proper output format + QString fs = devs[i].section(" ", 4, 4); + QString path = devs[i].section(" ",2, 2); + if (!fs.isEmpty() ) { //we not found a filesystem, most probably this is an invalid row + devs[i] = type+"::::"+fs+"::::"+path; + } + else { + devs.removeAt(i); + i--; + } + } + return devs; +} + +//Read screen brightness information +int LOS::ScreenBrightness(){ + //Returns: Screen Brightness as a percentage (0-100, with -1 for errors) + //Make sure we are not running in a VM (does not work) + QStringList info = LUtils::getCmdOutput("sysctl -n hw.product"); + if( !info.filter(QRegExp("VirtualBox|KVM")).isEmpty() ){ return -1; } + //Now perform the standard brightness checks + if(screenbrightness==-1){ + if(QFile::exists(QDir::homePath()+"/.lumina/.currentxbrightness")){ + int val = LUtils::readFile(QDir::homePath()+"/.lumina/.currentxbrightness").join("").simplified().toInt(); + screenbrightness = val; + } + } + return screenbrightness; +} + +//Set screen brightness +void LOS::setScreenBrightness(int percent){ + //ensure bounds + if(percent<0){percent=0;} + else if(percent>100){ percent=100; } + //Run the command + QString cmd = "xbacklight -time 0 -steps 1 -set %1"; + cmd = cmd.arg( QString::number(percent) ); + int ret = LUtils::runCmd(cmd); + //Save the result for later + if(ret!=0){ screenbrightness = -1; } + else{ screenbrightness = percent; } + LUtils::writeFile(QDir::homePath()+"/.lumina/.currentxbrightness", QStringList() << QString::number(screenbrightness), true); +} + +//Read the current volume +int LOS::audioVolume(){ //Returns: audio volume as a percentage (0-100, with -1 for errors) + QString info = LUtils::getCmdOutput("mixerctl -n outputs.master").join(",").simplified(); //ignores any other lines + int out = -1; + if(!info.isEmpty()){ + int L = info.section(",",0,0).toInt(); + int R = info.section(",",1,1).toInt(); + L = (L*100)/255; //percent + R = (R*100)/255; //percent + if(L>R){ out = L; } + else{ out = R; } + } + return out; +} + +//Set the current volume +void LOS::setAudioVolume(int percent){ + if(percent<0){percent=0;} + else if(percent>100){percent=100;} + QString info = LUtils::getCmdOutput("mixerctl -n outputs.master").join(",").simplified(); //ignores any other lines + if(!info.isEmpty()){ + int L = info.section(",",0,0).toInt(); + int R = info.section(",",1,1).toInt(); + L = (L*100)/255; //percent + R = (R*100)/255; //percent + int diff = L-R; + if(diff<0){ R=percent; L=percent+diff; } //R Greater + else{ L=percent; R=percent-diff; } //L Greater or equal + //Check bounds + if(L<0){L=0;}else if(L>100){L=100;} + if(R<0){R=0;}else if(R>100){R=100;} + //Run Command + L = (L*255)/100; //0-255 + R = (R*255)/100; //0-255 + LUtils::runCmd("mixerctl -q outputs.master="+QString::number(L)+","+QString::number(R)); + } +} + +//Change the current volume a set amount (+ or -) +void LOS::changeAudioVolume(int percentdiff){ + QString info = LUtils::getCmdOutput("mixerctl -n outputs.master").join(",").simplified(); //ignores any other lines + if(!info.isEmpty()){ + int L = info.section(",",0,0).toInt(); + int R = info.section(",",1,1).toInt(); + L = (L*100)/255; //percent + R = (R*100)/255; //percent + L = L + percentdiff; + R = R + percentdiff; + //Check bounds + if(L<0){L=0;}else if(L>100){L=100;} + if(R<0){R=0;}else if(R>100){R=100;} + //Run Command + L = (L*255)/100; //0-255 + R = (R*255)/100; //0-255 + LUtils::runCmd("mixerctl -q outputs.master="+QString::number(L)+","+QString::number(R)); + } +} + +//Check if a graphical audio mixer is installed +bool LOS::hasMixerUtility(){ + return false; //not implemented yet for OpenBSD +} + +//Launch the graphical audio mixer utility +void LOS::startMixerUtility(){ + //Not implemented yet for OpenBSD +} + +//Check for user system permission (shutdown/restart) +bool LOS::userHasShutdownAccess(){ + //User needs to be a part of the operator group to be able to run the shutdown command + QStringList groups = LUtils::getCmdOutput("id -Gn").join(" ").split(" "); + return groups.contains("operator"); +} + +//Check for whether the system is safe to power off (no updates being perfomed) +bool LOS::systemPerformingUpdates(){ + return false; //Not implemented yet +} + +//System Shutdown +void LOS::systemShutdown(){ //start poweroff sequence + QProcess::startDetached("shutdown -p now"); +} + +//System Restart +void LOS::systemRestart(){ //start reboot sequence + QProcess::startDetached("shutdown -r now"); +} + +//Check for suspend support +bool LOS::systemCanSuspend(){ + return false; +} + +//Put the system into the suspend state +void LOS::systemSuspend(){ + +} + +//Battery Availability +bool LOS::hasBattery(){ + int val = LUtils::getCmdOutput("apm -b").join("").toInt(); + return (val < 4); +} + +//Battery Charge Level +int LOS::batteryCharge(){ //Returns: percent charge (0-100), anything outside that range is counted as an error + int charge = LUtils::getCmdOutput("apm -l").join("").toInt(); + if(charge > 100){ charge = -1; } //invalid charge + return charge; +} + +//Battery Charging State +bool LOS::batteryIsCharging(){ + return (LUtils::getCmdOutput("apm -a").join("").simplified() == "1"); +} + +//Battery Time Remaining +int LOS::batterySecondsLeft(){ //Returns: estimated number of seconds remaining + int min = LUtils::getCmdOutput("apm -m").join("").toInt(); + return (min * 60); +} + +//File Checksums +QStringList LOS::Checksums(QStringList filepaths){ //Return: checksum of the input file + //on OpenBSD md5 has the following layout + //>md5 LuminaThemes.o LuminaUtils.o + //MD5 (LuminaThemes.o) = 50006505d9d7e54e5154eeb095555055 + //MD5 (LuminaUtils.o) = d490878ee8866e55e5af571b98b4d448 + + QStringList info = LUtils::getCmdOutput("md5 \""+filepaths.join("\" \"")+"\""); + for(int i=0; i<info.length(); i++){ + if( !info[i].contains(" = ") ){ info.removeAt(i); i--; } + else{ + //Strip out the extra information + info[i] = info[i].section(" = ",1,1); + } + } + return info; +} + +//file system capacity +QString LOS::FileSystemCapacity(QString dir) { + // on OpenBSD, df has the following layout: + //>df /home/wi + //>Filesystem 512-blocks Used Avail Capacity Mounted on + //>/dev/sd2l 14334588 739900 12877960 5% /home + + QStringList mountInfo = LUtils::getCmdOutput("df \"" + dir+"\""); + QString::SectionFlag skipEmpty = QString::SectionSkipEmpty; + //we take the 5th word on the 2 line + QString capacity = mountInfo[1].section(" ",4,4, skipEmpty); + return capacity; +} + +QStringList LOS::CPUTemperatures(){ //Returns: List containing the temperature of any CPU's ("50C" for example) + return QStringList(); //not implemented yet +} + +int LOS::CPUUsagePercent(){ //Returns: Overall percentage of the amount of CPU cycles in use (-1 for errors) + return -1; //not implemented yet +} + +int LOS::MemoryUsagePercent(){ + return -1; //not implemented yet +} + +QStringList LOS::DiskUsage(){ //Returns: List of current read/write stats for each device + return QStringList(); //not implemented yet +} + +#endif diff --git a/src-qt5/core/libLumina/LuminaOS-kFreeBSD.cpp b/src-qt5/core/libLumina/LuminaOS-kFreeBSD.cpp new file mode 100644 index 00000000..4fe62686 --- /dev/null +++ b/src-qt5/core/libLumina/LuminaOS-kFreeBSD.cpp @@ -0,0 +1,198 @@ +//=========================================== +// Lumina-DE source code +// Copyright (c) 2014, Ken Moore +// Available under the 3-clause BSD license +// See the LICENSE file for full details +//=========================================== +#ifdef __FreeBSD_kernel__ +#ifndef __FreeBSD__ +// The above two checks should make sure that we are on a +// operating system using the FreeBSD kernel without actually being +// on FreeBSD. That probably means Debian's kFreeBSD port. +#include <QDebug> +#include "LuminaOS.h" +#include <unistd.h> +#include <stdio.h> // Needed for BUFSIZ + +//can't read xbrightness settings - assume invalid until set +static int screenbrightness = -1; + +QString LOS::OSName(){ return "Debian GNU/kFreeBSD"; } + +//OS-specific prefix(s) +// NOTE: PREFIX, L_ETCDIR, L_SHAREDIR are defined in the OS-detect.pri project file and passed in +QString LOS::LuminaShare(){ return (L_SHAREDIR+"/Lumina-DE/"); } //Install dir for Lumina share files +QString LOS::AppPrefix(){ return "/usr/"; } //Prefix for applications +QString LOS::SysPrefix(){ return "/usr/"; } //Prefix for system + +//OS-specific application shortcuts (*.desktop files) +QString LOS::ControlPanelShortcut(){ return ""; } //system control panel +QString LOS::AppStoreShortcut(){ return ""; } //graphical app/pkg manager + +// ==== ExternalDevicePaths() ==== +QStringList LOS::ExternalDevicePaths(){ + //Returns: QStringList[<type>::::<filesystem>::::<path>] + //Note: <type> = [USB, HDRIVE, DVD, SDCARD, UNKNOWN] + QStringList devs = LUtils::getCmdOutput("mount"); + //Now check the output + for(int i=0; i<devs.length(); i++){ + if(devs[i].startsWith("/dev/")){ + QString type = devs[i].section(" on ",0,0); + type.remove("/dev/"); + //Determine the type of hardware device based on the dev node + if(type.startsWith("da")){ type = "USB"; } + else if(type.startsWith("ada")){ type = "HDRIVE"; } + else if(type.startsWith("mmsd")){ type = "SDCARD"; } + else if(type.startsWith("cd")||type.startsWith("acd")){ type="DVD"; } + else{ type = "UNKNOWN"; } + //Now put the device in the proper output format + devs[i] = type+"::::"+devs[i].section("(",1,1).section(",",0,0)+"::::"+devs[i].section(" on ",1,50).section("(",0,0).simplified(); + }else{ + //invalid device - remove it from the list + devs.removeAt(i); + i--; + } + } + return devs; +} + +//Read screen brightness information +int LOS::ScreenBrightness(){ + //Returns: Screen Brightness as a percentage (0-100, with -1 for errors) + if(screenbrightness==-1){ + if(QFile::exists(QDir::homePath()+"/.lumina/.currentxbrightness")){ + int val = LUtils::readFile(QDir::homePath()+"/.lumina/.currentxbrightness").join("").simplified().toInt(); + screenbrightness = val; + } + } + return screenbrightness; + +} + +//Set screen brightness +void LOS::setScreenBrightness(int percent){ + //ensure bounds + if(percent<0){percent=0;} + else if(percent>100){ percent=100; } + // float pf = percent/100.0; //convert to a decimel + //Run the command + QString cmd = "xbacklight -set %1"; + // cmd = cmd.arg( QString::number( int(65535*pf) ) ); + cmd = cmd.arg( QString::number( percent ) ); + int ret = LUtils::runCmd(cmd); + //Save the result for later + if(ret!=0){ screenbrightness = -1; } + else{ screenbrightness = percent; } + LUtils::writeFile(QDir::homePath()+"/.lumina/.currentxbrightness", QStringList() << QString::number(screenbrightness), true); + +} + +//Read the current volume +int LOS::audioVolume(){ //Returns: audio volume as a percentage (0-100, with -1 for errors) + return -1; // not available on kFreeBSD yet +} + +//Set the current volume +void LOS::setAudioVolume(int percent){ + return; +} + +//Change the current volume a set amount (+ or -) +void LOS::changeAudioVolume(int percentdiff){ + int old_volume = audioVolume(); + int new_volume = old_volume + percentdiff; + if (new_volume < 0) + new_volume = 0; + if (new_volume > 100) + new_volume = 100; + qDebug() << "Setting new volume to: " << new_volume; + setAudioVolume(new_volume); +} + +//Check if a graphical audio mixer is installed +bool LOS::hasMixerUtility(){ + return QFile::exists("/usr/bin/pavucontrol"); +} + +//Launch the graphical audio mixer utility +void LOS::startMixerUtility(){ + QProcess::startDetached("/usr/bin/pavucontrol"); +} + +//Check for user system permission (shutdown/restart) +bool LOS::userHasShutdownAccess(){ + return true; //not implemented yet +} + +//Check for whether the system is safe to power off (no updates being perfomed) +bool LOS::systemPerformingUpdates(){ + return false; //Not implemented yet +} + +//System Shutdown +void LOS::systemShutdown(){ //start poweroff sequence + QProcess::startDetached("shutdown -h now"); +} + +//System Restart +void LOS::systemRestart(){ //start reboot sequence + QProcess::startDetached("shutdown -r now"); +} + +//Check for suspend support +bool LOS::systemCanSuspend(){ + return false; +} + +//Put the system into the suspend state +void LOS::systemSuspend(){ + +} + +//Battery Availability +bool LOS::hasBattery(){ + return false; +} + +//Battery Charge Level +int LOS::batteryCharge(){ //Returns: percent charge (0-100), anything outside that range is counted as an error + return -1; +} + +//Battery Charging State +bool LOS::batteryIsCharging(){ + return false; +} + +//Battery Time Remaining +int LOS::batterySecondsLeft(){ //Returns: estimated number of seconds remaining + return 0; //not implemented yet for Linux +} + +//File Checksums +QStringList LOS::Checksums(QStringList filepaths){ //Return: checksum of the input file + return QStringList(); +} + +//file system capacity +QString LOS::FileSystemCapacity(QString dir) { //Return: percentage capacity as give by the df command + return QString(); +} + +QStringList LOS::CPUTemperatures(){ //Returns: List containing the temperature of any CPU's ("50C" for example) + return QStringList(); //not implemented yet +} + +int LOS::CPUUsagePercent(){ //Returns: Overall percentage of the amount of CPU cycles in use (-1 for errors) + return -1; //not implemented yet +} + +int LOS::MemoryUsagePercent(){ + return -1; //not implemented yet +} + +QStringList LOS::DiskUsage(){ //Returns: List of current read/write stats for each device + return QStringList(); //not implemented yet +} +#endif +#endif diff --git a/src-qt5/core/libLumina/LuminaOS-template.cpp b/src-qt5/core/libLumina/LuminaOS-template.cpp new file mode 100644 index 00000000..5969bf3a --- /dev/null +++ b/src-qt5/core/libLumina/LuminaOS-template.cpp @@ -0,0 +1,145 @@ +//=========================================== +// Lumina-DE source code +// Copyright (c) 2014, Ken Moore +// Available under the 3-clause BSD license +// See the LICENSE file for full details +//=========================================== +#ifdef __OSNAME__ +#include "LuminaOS.h" +#include <unistd.h> +#include <stdio.h> // Needed for BUFSIZ + +QString LOS::OSName(){ return "Sample"; } + +//OS-specific prefix(s) +// NOTE: PREFIX, L_ETCDIR, L_SHAREDIR are defined in the OS-detect.pri project file and passed in +QString LOS::LuminaShare(){ return (L_SHAREDIR+"/Lumina-DE/"); } //Install dir for Lumina share files +QString LOS::AppPrefix(){ return "/usr/local/"; } //Prefix for applications +QString LOS::SysPrefix(){ return "/usr/"; } //Prefix for system + +//OS-specific application shortcuts (*.desktop files) +QString LOS::ControlPanelShortcut(){ return ""; } //system control panel +QString LOS::AppStoreShortcut(){ return ""; } //graphical app/pkg manager + +// ==== ExternalDevicePaths() ==== +QStringList LOS::ExternalDevicePaths(){ + //Returns: QStringList[<type>::::<filesystem>::::<path>] + //Note: <type> = [USB, HDRIVE, DVD, SDCARD, UNKNOWN] + + //Not implemented yet + return QStringList(); +} + +//Read screen brightness information +int LOS::ScreenBrightness(){ + //Returns: Screen Brightness as a percentage (0-100, with -1 for errors) + return -1; //not implemented yet +} + +//Set screen brightness +void LOS::setScreenBrightness(int percent){ + //not implemented yet +} + +//Read the current volume +int LOS::audioVolume(){ + //Returns: audio volume as a percentage (0-100, with -1 for errors) + return -1; //Not implemented yet +} + +//Set the current volume +void LOS::setAudioVolume(int percent){ + //not implemented yet +} + +//Change the current volume a set amount (+ or -) +void LOS::changeAudioVolume(int percentdiff){ + //not implemented yet +} + +//Check if a graphical audio mixer is installed +bool LOS::hasMixerUtility(){ + return false; //not implemented yet +} + +//Launch the graphical audio mixer utility +void LOS::startMixerUtility(){ + //not implemented yet +} + +//Check for user system permission (shutdown/restart) +bool LOS::userHasShutdownAccess(){ + return false; //not implemented yet +} + +//Check for whether the system is safe to power off (no updates being perfomed) +bool LOS::systemPerformingUpdates(){ + return false; //Not implemented yet +} + +//System Shutdown +void LOS::systemShutdown(){ //start poweroff sequence + QProcess::startDetached("shutdown -p now"); +} + +//System Restart +void LOS::systemRestart(){ //start reboot sequence + QProcess::startDetached("shutdown -r now"); +} + +//Check for suspend support +bool LOS::systemCanSuspend(){ + return false; +} + +//Put the system into the suspend state +void LOS::systemSuspend(){ + +} + +//Battery Availability +bool LOS::hasBattery(){ + return false; //not implemented yet +} + +//Battery Charge Level +int LOS::batteryCharge(){ //Returns: percent charge (0-100), anything outside that range is counted as an error + return -1; //not implemented yet +} + +//Battery Charging State +bool LOS::batteryIsCharging(){ + return false; //not implemented yet +} + +//Battery Time Remaining +int LOS::batterySecondsLeft(){ //Returns: estimated number of seconds remaining + return 0; //not implemented yet +} + +//File Checksums +QStringList LOS::Checksums(QStringList filepaths){ //Return: checksum of the input file + return QStringList(); +} + +//file system capacity +QString LOS::FileSystemCapacity(QString dir) { //Return: percentage capacity as give by the df command + return QString(); +} + +QStringList LOS::CPUTemperatures(){ //Returns: List containing the temperature of any CPU's ("50C" for example) + return QStringList(); //not implemented yet +} + +int LOS::CPUUsagePercent(){ //Returns: Overall percentage of the amount of CPU cycles in use (-1 for errors) + return -1; //not implemented yet +} + +int LOS::MemoryUsagePercent(){ + return -1; //not implemented yet +} + +QStringList LOS::DiskUsage(){ //Returns: List of current read/write stats for each device + return QStringList(); //not implemented yet +} +#endif diff --git a/src-qt5/core/libLumina/LuminaOS.h b/src-qt5/core/libLumina/LuminaOS.h new file mode 100644 index 00000000..c305277a --- /dev/null +++ b/src-qt5/core/libLumina/LuminaOS.h @@ -0,0 +1,93 @@ +//=========================================== +// Lumina-DE source code +// Copyright (c) 2014-15, Ken Moore +// Available under the 3-clause BSD license +// See the LICENSE file for full details +//=========================================== +// This is the main interface for any OS-specific system calls +// To port Lumina to a different operating system, just create a file +// called "LuminaOS-<Operating System>.cpp", and use that file in +// the project (libLumina.pro) instead of LuminaOS-FreeBSD.cpp +//=========================================== +#ifndef _LUMINA_LIBRARY_OS_H +#define _LUMINA_LIBRARY_OS_H + +#include <QString> +#include <QStringList> +#include <QProcess> +#include <QDir> +#include <QObject> + +#include "LuminaUtils.h" + +class LOS{ +public: + //Return the name of the OS being used + static QString OSName(); + + //OS-specific prefix(s) + static QString LuminaShare(); //Install dir for Lumina share files + static QString AppPrefix(); //Prefix for applications (/usr/local/ on FreeBSD) + static QString SysPrefix(); //Prefix for system (/usr/ on FreeBSD) + + //OS-specific application shortcuts (*.desktop files) + static QString ControlPanelShortcut(); + static QString AppStoreShortcut(); + + //Scan for mounted external devices + static QStringList ExternalDevicePaths(); //Returns: QStringList[<type>::::<filesystem>::::<path>] + //Note: <type> = [USB, HDRIVE, DVD, SDCARD, UNKNOWN] + + //Read screen brightness information + static int ScreenBrightness(); //Returns: Screen Brightness as a percentage (0-100, with -1 for errors) + //Set screen brightness + static void setScreenBrightness(int percent); + + //Read the current volume + static int audioVolume(); //Returns: audio volume as a percentage (0-100, with -1 for errors) + //Set the current volume + static void setAudioVolume(int percent); + //Modify the current volume by a set amount (+ or -) + static void changeAudioVolume(int percentdiff); + + //Check if a graphical audio mixer is installed + static bool hasMixerUtility(); + //Launch the graphical audio mixer utility + static void startMixerUtility(); + + //Check for user system permission (shutdown/restart) + static bool userHasShutdownAccess(); + static bool systemPerformingUpdates(); + //System Shutdown + static void systemShutdown(); //start poweroff sequence + //System Restart + static void systemRestart(); //start reboot sequence + //Check for suspend support + static bool systemCanSuspend(); + //Put the system into the suspend state + static void systemSuspend(); + + + //Battery Availability + static bool hasBattery(); + //Battery Charge Level + static int batteryCharge(); //Returns: percent charge (0-100), anything outside that range is counted as an error + //Battery Charging State + static bool batteryIsCharging(); + //Battery Time Remaining + static int batterySecondsLeft(); //Returns: estimated number of seconds remaining + + //Get the checksum for a file + static QStringList Checksums(QStringList filepaths); //Return: checksum of each input file (same order) + + //Get the filesystem capacity + static QString FileSystemCapacity(QString dir) ; //Return: percentage capacity as give by the df command + + //System CPU Information + static QStringList CPUTemperatures(); //Returns: List containing the temperature of any CPU's ("50C" for example) + static int CPUUsagePercent(); //Returns: Overall percentage of the amount of CPU cycles in use (-1 for errors) + static int MemoryUsagePercent(); //Returns: Overall percentage of the amount of available memory in use (-1 for errors) + static QStringList DiskUsage(); //Returns: List of current read/write stats for each device +}; + +#endif diff --git a/src-qt5/core/libLumina/LuminaSingleApplication.cpp b/src-qt5/core/libLumina/LuminaSingleApplication.cpp new file mode 100644 index 00000000..30507b5e --- /dev/null +++ b/src-qt5/core/libLumina/LuminaSingleApplication.cpp @@ -0,0 +1,137 @@ +//=========================================== +// Lumina-DE source code +// Copyright (c) 2014, Ken Moore +// Available under the 3-clause BSD license +// See the LICENSE file for full details +//=========================================== +#include "LuminaSingleApplication.h" +#include <QDir> +#include <QFile> +#include <QLocalSocket> +#include <QDebug> +#include <QX11Info> + +#include <unistd.h> //for getlogin() + +LSingleApplication::LSingleApplication(int &argc, char **argv, QString appname) : QApplication(argc, argv){ + //Load the proper translation systems + if(appname!="lumina-desktop"){ cTrans = LUtils::LoadTranslation(this, appname); }//save the translator for later + //Initialize a couple convenience internal variables + cfile = QDir::tempPath()+"/.LSingleApp-%1-%2-%3"; + QString username = QString(getlogin()); + //For locking the process use the official process name - not the user input (no masking) + appname = this->applicationName(); + cfile = cfile.arg( username, appname, QString::number(QX11Info::appScreen()) ); + lockfile = new QLockFile(cfile+"-lock"); + lockfile->setStaleLockTime(0); //long-lived processes + for(int i=1; i<argc; i++){ + QString path = QString::fromLocal8Bit(argv[i]); + //do few quick conversions for relative paths and such as necessary + // (Remember: this is only used for secondary processes, not the primary) + if(path=="."){ + //Insert the current working directory instead + path = QDir::currentPath(); + }else{ + if(!path.startsWith("/") && !path.startsWith("-") ){ path.prepend(QDir::currentPath()+"/"); } + } + inputlist << path; + } + isActive = isBypass = false; + lserver = 0; + //Now check for the manual CLI flag to bypass single-instance forwarding (if necessary) + if(inputlist.contains("-new-instance")){ + isBypass = true; + inputlist.removeAll("-new-instance"); + } + PerformLockChecks(); +} + +LSingleApplication::~LSingleApplication(){ + if(lserver != 0 && lockfile->isLocked() ){ + //currently locked instance: remove the lock now + lserver->close(); + QLocalServer::removeServer(cfile); + lockfile->unlock(); + } +} + +bool LSingleApplication::isPrimaryProcess(){ + return (isActive || isBypass); +} + +void LSingleApplication::PerformLockChecks(){ + bool primary = lockfile->tryLock(); + //qDebug() << "Try Lock: " << primary; + if(!primary){ + //Pre-existing lock - check it for validity + QString appname, hostname; + qint64 pid; + lockfile->getLockInfo(&pid, &hostname, &appname); //PID already exists if it gets this far, ignore hostname + //qDebug() << " - Lock Info:" << pid << hostname << appname; + if( appname!=this->applicationName() || !QFile::exists(cfile) ){ + //Some other process has the same PID or the server does not exist - stale lock + qDebug() << " - Cleaning stale single-instance lock:"; + if(lockfile->removeStaleLockFile() ){ + if(QFile::exists(cfile)){ QLocalServer::removeServer(cfile); } //also remove stale socket/server file + }else{ + qDebug() << " -- Could not remove lock file"; + } + //Now re-try to create the lock + primary = lockfile->tryLock(); + //qDebug() << " - Try Lock Again:" << primary; + } + } + if(primary || !QFile::exists(cfile) ){ + //Create the server socket + //qDebug() << "Create Local Server"; + if(QFile::exists(cfile)){ QLocalServer::removeServer(cfile); } //stale socket/server file + lserver = new QLocalServer(this); + connect(lserver, SIGNAL(newConnection()), this, SLOT(newInputsAvailable()) ); + if( lserver->listen(cfile) ){ + qDebug() << " - Created new single-instance lock"; + lserver->setSocketOptions(QLocalServer::UserAccessOption); + //qDebug() << " - Success"; + isActive = true; + }else{ + qDebug() << " - WARNING: Could not create single-instance framework"; + qDebug() << " - Falling back on standard application startup"; + lockfile->unlock(); + isActive = true; + } + + }else if(!isBypass){ + //forward the current inputs to the locked process for processing and exit + //Check the connection to the local server first + qDebug() << "Single-instance lock found"; + QLocalSocket socket(this); + socket.connectToServer(cfile); + socket.waitForConnected(); + 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..."; + socket.write( inputlist.join("::::").toLocal8Bit() ); + socket.waitForDisconnected(500); //max out at 1/2 second (only hits this if no inputs) + } + +} + +//New messages detected +void LSingleApplication::newInputsAvailable(){ + while(lserver->hasPendingConnections()){ + QLocalSocket *sock = lserver->nextPendingConnection(); + QByteArray bytes; + sock->waitForReadyRead(); + while(sock->bytesAvailable() > 0){ //if(sock->waitForReadyRead()){ + //qDebug() << "Info Available"; + bytes.append( sock->readAll() ); + } + sock->disconnectFromServer(); + QStringList inputs = QString::fromLocal8Bit(bytes).split("::::"); + //qDebug() << " - New Inputs Detected:" << inputs; + emit InputsAvailable(inputs); + } +}
\ No newline at end of file diff --git a/src-qt5/core/libLumina/LuminaSingleApplication.h b/src-qt5/core/libLumina/LuminaSingleApplication.h new file mode 100644 index 00000000..725d8e40 --- /dev/null +++ b/src-qt5/core/libLumina/LuminaSingleApplication.h @@ -0,0 +1,59 @@ +//=========================================== +// Lumina-DE source code +// Copyright (c) 2014, Ken Moore +// Available under the 3-clause BSD license +// See the LICENSE file for full details +//=========================================== +// This is the general class for a single-instance application +//=========================================== +//EXAMPLE USAGE in main.cpp: +// +// LSingleApplication app(argc, argv); +// if( !app.isPrimaryProcess() ){ +// return 0; +// } +// QMainWindow w; //or whatever the main window class is +// connect(app, SIGNAL(InputsAvailable(QStringList)), w, SLOT(<some slot>)); //for interactive apps - optional +// app.exec(); +//=========================================== +#ifndef _LUMINA_LIBRARY_SINGLE_APPLICATION_H +#define _LUMINA_LIBRARY_SINGLE_APPLICATION_H + +#include <QString> +#include <QStringList> +#include <QLocalServer> +#include <QLockFile> +#include <QApplication> + +#include <LuminaUtils.h> + +//NOTE: This application type will automatically load the proper translation file(s) +// if the application name is set properly +class LSingleApplication : public QApplication{ + Q_OBJECT +public: + LSingleApplication(int &argc, char **argv, QString appname); + ~LSingleApplication(); + + bool isPrimaryProcess(); + + QStringList inputlist; //in case the app wants access to modified inputs (relative path fixes and such) + +private: + bool isActive, isBypass; + QLockFile *lockfile; + QLocalServer *lserver; + QString cfile; + QTranslator *cTrans; //current translation + + void PerformLockChecks(); + +private slots: + void newInputsAvailable(); //internally used to detect a message from an alternate instance + +signals: + void InputsAvailable(QStringList); + +}; + +#endif diff --git a/src-qt5/core/libLumina/LuminaThemes.cpp b/src-qt5/core/libLumina/LuminaThemes.cpp new file mode 100644 index 00000000..415b3acf --- /dev/null +++ b/src-qt5/core/libLumina/LuminaThemes.cpp @@ -0,0 +1,503 @@ +//=========================================== +// Lumina-DE source code +// Copyright (c) 2014-2015, Ken Moore +// Available under the 3-clause BSD license +// See the LICENSE file for full details +//=========================================== +#include "LuminaThemes.h" + +#include "LuminaUtils.h" +#include "LuminaOS.h" +#include <QIcon> +#include <QFont> +#include <QDebug> +#include <QObject> +#include <QPainter> +#include <QPen> + +#include <unistd.h> + +//Stuff necesary for Qt Cursor Reloads +//#include "qxcbcursor.h" //needed to prod Qt to refresh the mouse cursor theme +//#include <QCursor> + +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); + for(int i=0; i<list.length(); i++){ + //Format the output entry [<name>::::<fullpath>] + list[i] = list[i].section(".qss.",0,0)+"::::"+dir.absoluteFilePath(list[i]); + } + return list; +} + +QStringList LTHEME::availableLocalThemes(){ //returns: [name::::path] for each item + QDir dir(QDir::homePath()+"/.lumina/themes"); + QStringList list = dir.entryList(QStringList() <<"*.qss.template", QDir::Files, QDir::Name); + for(int i=0; i<list.length(); i++){ + //Format the output entry [<name>::::<fullpath>] + list[i] = list[i].section(".qss.",0,0)+"::::"+dir.absoluteFilePath(list[i]); + } + return list; +} + +QStringList LTHEME::availableSystemColors(){ //returns: [name::::path] for each item + //returns: [name::::path] for each item + QDir dir(LOS::LuminaShare()+"colors"); + QStringList list = dir.entryList(QStringList() <<"*.qss.colors", QDir::Files, QDir::Name); + for(int i=0; i<list.length(); i++){ + //Format the output entry [<name>::::<fullpath>] + list[i] = list[i].section(".qss.",0,0)+"::::"+dir.absoluteFilePath(list[i]); + } + return list; +} + +QStringList LTHEME::availableLocalColors(){ //returns: [name::::path] for each item + QDir dir(QDir::homePath()+"/.lumina/colors"); + QStringList list = dir.entryList(QStringList() <<"*.qss.colors", QDir::Files, QDir::Name); + for(int i=0; i<list.length(); i++){ + //Format the output entry [<name>::::<fullpath>] + list[i] = list[i].section(".qss.",0,0)+"::::"+dir.absoluteFilePath(list[i]); + } + return list; +} + +QStringList LTHEME::availableSystemIcons(){ //returns: [name] for each item + 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(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]+"/index.theme")) ){ themes << tmpthemes[j]; } + } + } + } + themes.removeDuplicates(); + 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; + 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 + } + } + } + return out; +} + +//Save a new theme/color file +bool LTHEME::saveLocalTheme(QString name, QStringList contents){ + QString localdir = QDir::homePath()+"/.lumina/themes/"; + if(!QFile::exists(localdir)){ QDir dir; dir.mkpath(localdir); } + return LUtils::writeFile(localdir+name+".qss.template", contents, true); +} + +bool LTHEME::saveLocalColors(QString name, QStringList contents){ + QString localdir = QDir::homePath()+"/.lumina/colors/"; + if(!QFile::exists(localdir)){ QDir dir; dir.mkpath(localdir); } + return LUtils::writeFile(localdir+name+".qss.colors", contents, true); +} + +//Return the currently selected Theme/Colors/Icons +QStringList LTHEME::currentSettings(){ //returns [theme path, colorspath, iconsname, font, fontsize] + QStringList out; out << "" << "" << "" << "" << ""; + QStringList settings = LUtils::readFile(QDir::homePath()+"/.lumina/themesettings.cfg"); + 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("FONTFAMILY=")){ out[3] = settings[i].section("=",1,1).simplified(); } + else if(settings[i].startsWith("FONTSIZE=")){ out[4] = settings[i].section("=",1,1).simplified(); } + } + 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()){ + 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; +} + +//Return the currently-selected Cursor theme +QString LTHEME::currentCursor(){ + //qDebug() << "Reading Current Cursor Theme:"; + QStringList info = LUtils::readFile(QDir::homePath()+"/.icons/default/index.theme"); + if(info.isEmpty()){ return ""; } + QString cursor; + bool insection = false; + for(int i=0; i<info.length(); i++){ + if(info[i]=="[Icon Theme]"){ insection = true; continue;} + else if(insection && info[i].startsWith("Inherits=")){ + cursor = info[i].section("=",1,1).simplified(); + break; + } + } + //qDebug() << " - found theme:" << cursor; + return cursor; +} + + //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 + QStringList contents; + contents << "THEMEFILE="+themepath; + contents << "COLORFILE="+colorpath; + contents << "ICONTHEME="+iconname; + contents << "FONTFAMILY="+font; + contents << "FONTSIZE="+fontsize; + bool ok = LUtils::writeFile(QDir::homePath()+"/.lumina/themesettings.cfg", contents, true); + + return ok; +} + +//Change the current Cursor Theme +bool LTHEME::setCursorTheme(QString cursorname){ +//qDebug() << "Set Cursor Theme:" << cursorname; + QStringList info = LUtils::readFile(QDir::homePath()+"/.icons/default/index.theme"); + bool insection = false; + bool changed = false; + QString newval = "Inherits="+cursorname; + for(int i=0; i<info.length() && !changed; i++){ + if(info[i]=="[Icon Theme]"){ + insection = true; + }else if( info[i].startsWith("[") && insection){ + //Section does not have the setting - add it + info.insert(i, newval); + changed =true; + }else if( info[i].startsWith("[") ){ + insection = false; + }else if(insection && info[i].startsWith("Inherits=")){ + info[i] = newval; //replace the current setting + changed = true; + } + } //end loop over file contents + if(!changed){ //Could not change the file contents for some reason + if(insection){ info << newval; } //end of file while in the section + else{ info << "[Icon Theme]" << newval; } //entire section missing from file + } + //Now save the file + //qDebug() << "Done saving the cursor:" << info; + return LUtils::writeFile(QDir::homePath()+"/.icons/default/index.theme", info, true); +} + + //Return the complete stylesheet for a given theme/colors +QString LTHEME::assembleStyleSheet(QString themepath, QString colorpath, QString font, QString fontsize){ + QString stylesheet = LUtils::readFile(themepath).join("\n"); + QStringList colors = LUtils::readFile(colorpath); + //qDebug() << "Found Theme:" << themepath << stylesheet; + //qDebug() << "Found Colors:" << colorpath << colors; + QStringList systhemes = availableSystemThemes(); + QStringList locthemes = availableLocalThemes(); + //Now do any inheritance between themes + int start = stylesheet.indexOf("INHERITS="); + while(start>=0){ + QString line = stylesheet.mid(start, stylesheet.indexOf("\n",start)-start); //only get this line + QString inherit = line.section("=",1,1); + QString rStyle; //replacement stylesheet + if(!locthemes.filter(inherit+"::::").isEmpty()){ + rStyle = LUtils::readFile(locthemes.filter(inherit+"::::").first().section("::::",1,1)).join("\n"); + }else if(!systhemes.filter(inherit+"::::").isEmpty()){ + rStyle = LUtils::readFile(systhemes.filter(inherit+"::::").first().section("::::",1,1)).join("\n"); + } + stylesheet.replace(line, rStyle); + //Now look for the next one + start = stylesheet.indexOf("INHERITS="); + } + //Now perform the color replacements + for(int i=0; i<colors.length(); i++){ + if(colors[i].isEmpty() || colors[i].startsWith("#")){ continue; } + else if(colors[i].startsWith("PRIMARYCOLOR=")){ stylesheet = stylesheet.replace("%%PRIMARYCOLOR%%", colors[i].section("=",1,1).simplified()); } + else if(colors[i].startsWith("SECONDARYCOLOR=")){ stylesheet = stylesheet.replace("%%SECONDARYCOLOR%%", colors[i].section("=",1,1).simplified()); } + else if(colors[i].startsWith("HIGHLIGHTCOLOR=")){ stylesheet = stylesheet.replace("%%HIGHLIGHTCOLOR%%", colors[i].section("=",1,1).simplified()); } + else if(colors[i].startsWith("ACCENTCOLOR=")){ stylesheet = stylesheet.replace("%%ACCENTCOLOR%%", colors[i].section("=",1,1).simplified()); } + else if(colors[i].startsWith("PRIMARYDISABLECOLOR=")){ stylesheet = stylesheet.replace("%%PRIMARYDISABLECOLOR%%", colors[i].section("=",1,1).simplified()); } + else if(colors[i].startsWith("SECONDARYDISABLECOLOR=")){ stylesheet = stylesheet.replace("%%SECONDARYDISABLECOLOR%%", colors[i].section("=",1,1).simplified()); } + else if(colors[i].startsWith("HIGHLIGHTDISABLECOLOR=")){ stylesheet = stylesheet.replace("%%HIGHLIGHTDISABLECOLOR%%", colors[i].section("=",1,1).simplified()); } + else if(colors[i].startsWith("ACCENTDISABLECOLOR=")){ stylesheet = stylesheet.replace("%%ACCENTDISABLECOLOR%%", colors[i].section("=",1,1).simplified()); } + else if(colors[i].startsWith("BASECOLOR=")){ stylesheet = stylesheet.replace("%%BASECOLOR%%", colors[i].section("=",1,1).simplified()); } + else if(colors[i].startsWith("ALTBASECOLOR=")){ stylesheet = stylesheet.replace("%%ALTBASECOLOR%%", colors[i].section("=",1,1).simplified()); } + else if(colors[i].startsWith("TEXTCOLOR=")){ stylesheet = stylesheet.replace("%%TEXTCOLOR%%", colors[i].section("=",1,1).simplified()); } + else if(colors[i].startsWith("TEXTDISABLECOLOR=")){ stylesheet = stylesheet.replace("%%TEXTDISABLECOLOR%%", colors[i].section("=",1,1).simplified()); } + else if(colors[i].startsWith("TEXTHIGHLIGHTCOLOR=")){ stylesheet = stylesheet.replace("%%TEXTHIGHLIGHTCOLOR%%", colors[i].section("=",1,1).simplified()); } + } + stylesheet = stylesheet.replace("%%FONT%%", "\""+font+"\""); + stylesheet = stylesheet.replace("%%FONTSIZE%%", fontsize); + //qDebug() << "Assembled Style Sheet:\n" << stylesheet; + return stylesheet; +} +// Extra information about a cursor theme +QStringList LTHEME::cursorInformation(QString name){ + //returns: [Name, Comment, Sample Image File] + QStringList out; out << "" << "" << ""; //ensure consistent output structure + QStringList paths; paths << LOS::SysPrefix()+"lib/X11/icons/" << LOS::AppPrefix()+"lib/X11/icons/"; + for(int i=0; i<paths.length(); i++){ + if(QFile::exists(paths[i]+name)){ + if(QFile::exists(paths[i]+name+"/cursors/arrow")){ out[2] = paths[i]+name+"/cursors/arrow"; } + QStringList info = LUtils::readFile(paths[i]+name+"/index.theme"); + for(int j=info.indexOf("[Icon Theme]"); j<info.length(); j++){ + if(j<0){continue; } //just in case the index function errors out + if(info[j].startsWith("Name") && info[j].contains("=")){ out[0] = info[j].section("=",1,1).simplified(); } + else if(info[j].startsWith("Comment") && info[j].contains("=")){ out[1] = info[j].section("=",1,1).simplified(); } + } + break; //found the cursor + } + } + return out; +} + +QStringList LTHEME::CustomEnvSettings(){ //view all the key=value settings + static QStringList info = QStringList(); + static QDateTime lastcheck = QDateTime(); + if(lastcheck.isNull() || lastcheck < QFileInfo(QDir::homePath()+"/.lumina/envsettings.conf").lastModified()){ + lastcheck = QDateTime::currentDateTime(); + info = LUtils::readFile(QDir::homePath()+"/.lumina/envsettings.conf"); + } + return info; +} + +void LTHEME::LoadCustomEnvSettings(){ + //will push the custom settings into the environment (recommended before loading the initial QApplication) + QStringList info = LTHEME::CustomEnvSettings(); + if(info.isEmpty()){ + //Ensure the file exists, and create it otherwise; + if(!QFile::exists(QDir::homePath()+"/.lumina/envsettings.conf")){ + LUtils::writeFile(QDir::homePath()+"/.lumina/envsettings.conf", QStringList() << "", true); + } + } + for(int i=0; i<info.length(); i++){ + if(info[i].isEmpty()){ continue; } + if(info[i].section("=",1,100).isEmpty()){ + unsetenv(info[i].section("=",0,0).toLocal8Bit()); + }else{ + setenv(info[i].section("=",0,0).toLocal8Bit(), info[i].section("=",1,100).simplified().toLocal8Bit(), 1); + } + } + +} + +bool LTHEME::setCustomEnvSetting(QString var, QString val){ + //variable/value pair (use an empty val to clear it) + QStringList info = LTHEME::CustomEnvSettings(); + bool changed = false; + if(!info.filter(var+"=").isEmpty()){ + for(int i=0; i<info.length(); i++){ + //Make sure this is an exact variable match + if(!info[i].startsWith(var+"=")){ continue; } + //Found it - replace this line + info[i] = var+"="+val; + changed = true; + } + } + if(!changed){ info << var+"="+val; } + return LUtils::writeFile(QDir::homePath()+"/.lumina/envsettings.conf", info, true); +} + +QString LTHEME::readCustomEnvSetting(QString var){ + QStringList info = LTHEME::CustomEnvSettings().filter(var+"="); + for(int i=0; i<info.length(); i++){ + if(info[i].startsWith(var+"=")){ + return info[i].section("=",1,100).simplified(); + } + } + //If it gets here, no setting found for that variable + return ""; +} + +// ========================= +// LuminaThemeStyle +// ========================= +/*LuminaThemeStyle::LuminaThemeStyle() : QProxyStyle(){ + this->update(); +} + +LuminaThemeStyle::~LuminaThemeStyle(){ + +} + +//Function to update the style (for use by the theme engine) +void LuminaThemeStyle::update(){ + darkfont = true; //make this dynamic later +}*/ + +//Subclassed functions +//void LuminaThemeStyle::drawItemText(QPainter *painter, const QRect &rect, int alignment, const QPalette &palette, bool enabled, const QString &text, QPalette::ColorRole textRole) const{ + /*QFont cfont = painter->font(); + cfont.setHintingPreference(QFont::PreferFullHinting); + QFont outfont = cfont; + outfont.setStretch(101); + outfont.setLetterSpacing(QFont::PercentageSpacing, 99); + //Paint the background outline + if(darkfont){ painter->setPen(QPen(Qt::white)); } + else{ painter->setPen(QPen(Qt::black)); } + painter->setFont(outfont); + //QRect outline = QRect(rect.left()+2, rect.top()+2, rect.right()+2, rect.bottom()+2); + painter->drawText(rect, text); + + //Paint the text itself (Make this respect the "enabled" flag later) + painter->setFont(cfont); + if(darkfont){ painter->setPen(QPen(Qt::black)); } + else{ painter->setPen(QPen(Qt::white)); } + painter->drawText(rect, text);*/ + + /*QFont font = painter->font(); + QFont cfont = font; //save for later + if(font.pixelSize()>0){ font.setPixelSize( font.pixelSize()-4); } + else{ font.setPointSize(font.pointSize()-1); } + painter->setFont(font); + //Create the path + QPainterPath path; + //path.setFillRule(Qt::WindingFill); + path.addText(rect.left(), rect.center().y()+(painter->fontMetrics().xHeight()/2), painter->font(), text); + //Now set the border/fill colors + QPen pen; + pen.setWidth(2); + if(darkfont){ + pen.setColor(Qt::white); + painter->fillPath(path,Qt::black); + }else{ + pen.setColor(Qt::black); + painter->fillPath(path,Qt::white); + } + painter->setPen(pen); + painter->drawPath(path); + painter->setFont(cfont); //reset back to original font*/ + +//} + + +//================== +// THEME ENGINE CLASS +//================== +LuminaThemeEngine::LuminaThemeEngine(QApplication *app){ + application=app; //save this pointer for later + //style = new LuminaThemeStyle(); + //Set the application-wide style + //application->setStyle( style ); + + lastcheck = QDateTime::currentDateTime(); // + // Now load the theme stylesheet + QStringList current = LTHEME::currentSettings(); + theme = current[0]; colors=current[1]; icons=current[2]; font=current[3]; fontsize=current[4]; + cursors = LTHEME::currentCursor(); + application->setStyleSheet( LTHEME::assembleStyleSheet(theme, colors, font, fontsize) ); + //Make sure to prefer font antialiasing on the application + /*QFont tmp = application->font(); + tmp.setStyleStrategy(QFont::PreferOutline); + tmp.setFamily(font); + tmp.setHintingPreference(QFont::PreferFullHinting); + if(fontsize.endsWith("pt")){ tmp.setPointSize(fontsize.section("pt",0,0).toInt()); } + else if(fontsize.endsWith("px")){ tmp.setPixelSize(fontsize.section("px",0,0).toInt()); } + application->setFont(tmp);*/ + QIcon::setThemeName(icons); //make sure this sets set within this environment + syncTimer = new QTimer(this); + syncTimer->setSingleShot(true); + syncTimer->setInterval(500); //wait 1/2 second before re-loading the files + if(cursors.isEmpty()){ + LTHEME::setCursorTheme("default"); //X11 fallback (always installed?) + cursors = "default"; + } + + //setenv("XCURSOR_THEME", cursors.toLocal8Bit(),1); + watcher = new QFileSystemWatcher(this); + watcher->addPath( QDir::homePath()+"/.lumina/envsettings.conf" ); + watcher->addPath( QDir::homePath()+"/.lumina/themesettings.cfg" ); + watcher->addPaths( QStringList() << theme << colors << QDir::homePath()+"/.icons/default/index.theme" ); //also watch these files for changes + connect(watcher, SIGNAL(fileChanged(QString)), this, SLOT(watcherChange(QString)) ); + connect(syncTimer, SIGNAL(timeout()), this, SLOT(reloadFiles()) ); +} + +LuminaThemeEngine::~LuminaThemeEngine(){ + +} + +void LuminaThemeEngine::refresh(){ + QTimer::singleShot(100,this, SLOT(reloadFiles()) ); +} + +void LuminaThemeEngine::watcherChange(QString file){ + if(syncTimer->isActive()){ syncTimer->stop(); } + syncTimer->start(); + if(!watcher->files().contains(file)){ watcher->addPath(file); } +} + +void LuminaThemeEngine::reloadFiles(){ + //Check the Theme file/settings + if(lastcheck < QFileInfo(QDir::homePath()+"/.lumina/themesettings.cfg").lastModified().addSecs(1) ){ + QStringList current = LTHEME::currentSettings(); + application->setStyleSheet( LTHEME::assembleStyleSheet(current[0], current[1], current[3], current[4]) ); + if(icons!=current[2]){ + QIcon::setThemeName(current[2]); //make sure this sets set within this environment + emit updateIcons(); + } + //save the settings for comparison later + theme = current[0]; colors=current[1]; icons=current[2]; + + if(font!=current[3] || fontsize!=current[4]){ + font=current[3]; fontsize=current[4]; + QFont tmp = application->font(); + tmp.setStyleStrategy(QFont::PreferAntialias); + tmp.setFamily(font); + if(fontsize.endsWith("pt")){ tmp.setPointSize(fontsize.section("pt",0,0).toInt()); } + else if(fontsize.endsWith("px")){ tmp.setPixelSize(fontsize.section("px",0,0).toInt()); } + application->setFont(tmp); + } + } + //Check the Cursor file/settings + if(lastcheck < QFileInfo(QDir::homePath()+"/.icons/default/index.theme").lastModified()){ + QString ccurs = LTHEME::currentCursor(); + if(cursors != ccurs){ + emit updateCursors(); + //Might be something we can do automatically here as well - since we have the QApplication handy + // - Note: setting/unsetting an override cursor does not update the current cursor bitmap + // Qt created a background database/hash/mapping of the theme pixmaps on startup + // So Qt itself needs to be prodded to update that mapping + /*QXcbCursor::cursorThemePropertyChanged( \ + new QXcbVirtualDesktop(QX11Info::connection(), application->screen()->handle(), QX11Info::appScreen()), + ccurs.toData(), QVariant("Inherits"), NULL);*/ + //QCursorData::cleanup(); + //QCursorData::initialize(); + //setenv("XCURSOR_THEME", ccurs.toLocal8Bit(),1); + } + cursors = ccurs; + } + + + //Environment Changes + if( lastcheck < QFileInfo(QDir::homePath()+"/.lumina/envsettings.conf").lastModified()){ + LTHEME::LoadCustomEnvSettings(); + emit EnvChanged(); + } + lastcheck = QDateTime::currentDateTime(); + + //Now update the watched files to ensure nothing is missed + watcher->removePaths( QStringList() << theme << colors << QDir::homePath()+"/.icons/default/index.theme" << QDir::homePath()+"/.lumina/envsettings.conf"); + watcher->addPaths( QStringList() << theme << colors << QDir::homePath()+"/.icons/default/index.theme" << QDir::homePath()+"/.lumina/envsettings.conf"); +} + diff --git a/src-qt5/core/libLumina/LuminaThemes.h b/src-qt5/core/libLumina/LuminaThemes.h new file mode 100644 index 00000000..f0b8c2b6 --- /dev/null +++ b/src-qt5/core/libLumina/LuminaThemes.h @@ -0,0 +1,115 @@ +//=========================================== +// Lumina-DE source code +// Copyright (c) 2014-2015, Ken Moore +// Available under the 3-clause BSD license +// See the LICENSE file for full details +//=========================================== +// This class governs all the stylesheet usage and interactions +// for the Lumina utilities to provide a consistant theme for the system +//=========================================== +#ifndef _LUMINA_LIBRARY_THEMES_H +#define _LUMINA_LIBRARY_THEMES_H + +#include <QApplication> +#include <QObject> +#include <QFileSystemWatcher> +#include <QString> +#include <QFile> +#include <QDir> +#include <QTimer> +#include <QDateTime> +#include <QStyle> +#include <QProxyStyle> + +class LTHEME{ +public: + //Read the Themes/Colors/Icons that are available on the system + static QStringList availableSystemThemes();//returns: [name::::path] for each item + static QStringList availableLocalThemes(); //returns: [name::::path] for each item + static QStringList availableSystemColors(); //returns: [name::::path] for each item + static QStringList availableLocalColors(); //returns: [name::::path] for each item + static QStringList availableSystemIcons(); //returns: [name] for each item + static QStringList availableSystemCursors(); //returns: [name] for each item + + //Save a new theme/color file + static bool saveLocalTheme(QString name, QStringList contents); + static bool saveLocalColors(QString name, QStringList contents); + + //Return the currently selected Theme/Colors/Icons + static QStringList currentSettings(); //returns [theme path, colorspath, iconsname, font, fontsize] + static QString currentCursor(); //returns: current cursor theme name + + //Change the current Theme/Colors/Icons + static bool setCurrentSettings(QString themepath, QString colorpath, QString iconname, QString font, QString fontsize); + static bool setCursorTheme(QString cursorname); + + //Return the complete stylesheet for a given theme/colors + static QString assembleStyleSheet(QString themepath, QString colorpath, QString font, QString fontsize); + + //Additional info for a cursor theme + static QStringList cursorInformation(QString name); //returns: [Name, Comment, Sample Image File] + + //Environment settings + static QStringList CustomEnvSettings(); //view all the key=value settings + static void LoadCustomEnvSettings(); //will push the custom settings into the environment (recommended before loading the initial QApplication) + static bool setCustomEnvSetting(QString var, QString val); //variable/value pair (use an empty val to clear it) + static QString readCustomEnvSetting(QString var); + +}; + +// Qt Style override to allow custom themeing/colors +/*class LuminaThemeStyle : public QProxyStyle{ + Q_OBJECT +private: + bool darkfont; + +public: + LuminaThemeStyle(); + ~LuminaThemeStyle(); + + //Function to update the style (for use by the theme engine) + void update(); + //Subclassed functions + void drawItemText(QPainter*, const QRect&, int, const QPalette&, bool, const QString&, QPalette::ColorRole) const; + +};*/ + +//Simple class to setup a utility to use the Lumina theme +//-----Example usage in "main.cpp" ------------------------------- +// LTHEME::LoadCustomEnvSettings(); +// QApplication a(argc,argv); +// LuminaThemeEngine themes(&a) +//------------------------------------------------------------------------------------ +// Note: If you also use LuminaXDG::findIcons() in the application and you want +// to dynamically update those icons - connect to the updateIcons() signal +//------------------------------------------------------------------------------------- +// QMainWindow w; //(or whatever the main app window is) +// QObject::connect(themes,SIGNAL(updateIcons()), &w, SLOT(updateIcons()) ); +//------------------------------------------------------------------------------------ +class LuminaThemeEngine : public QObject{ + Q_OBJECT +public: + LuminaThemeEngine(QApplication *app); + ~LuminaThemeEngine(); + + void refresh(); + +private: + QApplication *application; + QFileSystemWatcher *watcher; + QString theme,colors,icons, font, fontsize, cursors; //current settings + QTimer *syncTimer; + QDateTime lastcheck; + //LuminaThemeStyle *style; + +private slots: + void watcherChange(QString); + void reloadFiles(); + +signals: + void updateIcons(); //Icon theme changed + void updateCursors(); //Cursor theme changed + void EnvChanged(); //Some environment variable(s) changed +}; + +#endif diff --git a/src-qt5/core/libLumina/LuminaUtils.cpp b/src-qt5/core/libLumina/LuminaUtils.cpp new file mode 100644 index 00000000..e25596f8 --- /dev/null +++ b/src-qt5/core/libLumina/LuminaUtils.cpp @@ -0,0 +1,877 @@ +//=========================================== +// Lumina-DE source code +// Copyright (c) 2013-2015, Ken Moore +// Available under the 3-clause BSD license +// See the LICENSE file for full details +//=========================================== +#include "LuminaUtils.h" + +#include <QString> +#include <QFile> +#include <QStringList> +#include <QObject> +#include <QTextCodec> +#include <QDebug> +#include <QDesktopWidget> +#include <QImageReader> +#include <QRegExp> +#include <QFuture> +#include <QtConcurrent> + +#include <LuminaOS.h> +#include <LuminaThemes.h> +#include <LuminaXDG.h> + +static QStringList fav; + +inline QStringList ProcessRun(QString cmd, QStringList args){ + //Assemble outputs + QStringList out; out << "1" << ""; //error code, string output + QProcess proc; + QProcessEnvironment env = QProcessEnvironment::systemEnvironment(); + env.insert("LANG", "C"); + env.insert("LC_MESSAGES", "C"); + proc.setProcessEnvironment(env); + proc.setProcessChannelMode(QProcess::MergedChannels); + if(args.isEmpty()){ + proc.start(cmd); + }else{ + proc.start(cmd,args); + } + while(!proc.waitForFinished(500)){ + if(proc.state() == QProcess::NotRunning){ break; } //somehow missed the finished signal + } + out[0] = QString::number(proc.exitCode()); + out[1] = QString(proc.readAllStandardOutput()); + return out; +} +//============= +// LUtils Functions +//============= +QString LUtils::LuminaDesktopVersion(){ + QString ver = "0.9.0-devel"; + #ifdef GIT_VERSION + ver.append( QString(" (Git Revision: %1)").arg(GIT_VERSION) ); + #endif + return ver; +} + +QString LUtils::LuminaDesktopBuildDate(){ + #ifdef BUILD_DATE + return BUILD_DATE; + #endif + return ""; +} + +int LUtils::runCmd(QString cmd, QStringList args){ + /*QProcess proc; + proc.setProcessChannelMode(QProcess::MergedChannels); + if(args.isEmpty()){ + proc.start(cmd); + }else{ + proc.start(cmd, args); + } + //if(!proc.waitForStarted(30000)){ return 1; } //process never started - max wait of 30 seconds + while(!proc.waitForFinished(300)){ + if(proc.state() == QProcess::NotRunning){ break; } //somehow missed the finished signal + QCoreApplication::processEvents(); + } + int ret = proc.exitCode(); + 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){ + /*QProcess proc; + QProcessEnvironment env = QProcessEnvironment::systemEnvironment(); + env.insert("LANG", "C"); + env.insert("LC_MESSAGES", "C"); + proc.setProcessEnvironment(env); + proc.setProcessChannelMode(QProcess::MergedChannels); + if(args.isEmpty()){ + proc.start(cmd); + }else{ + proc.start(cmd,args); + } + //if(!proc.waitForStarted(30000)){ return QStringList(); } //process never started - max wait of 30 seconds + while(!proc.waitForFinished(300)){ + if(proc.state() == QProcess::NotRunning){ break; } //somehow missed the finished signal + QCoreApplication::processEvents(); + } + QStringList out = QString(proc.readAllStandardOutput()).split("\n"); + return out;*/ + QFuture<QStringList> future = QtConcurrent::run(ProcessRun, cmd, args); + return future.result()[1].split("\n"); //Split the return message into lines +} + +QStringList LUtils::readFile(QString filepath){ + QStringList out; + QFile file(filepath); + if(file.open(QIODevice::Text | QIODevice::ReadOnly)){ + QTextStream in(&file); + while(!in.atEnd()){ + out << in.readLine(); + } + file.close(); + } + return out; +} + +bool LUtils::writeFile(QString filepath, QStringList contents, bool overwrite){ + QFile file(filepath); + if(file.exists() && !overwrite){ return false; } + bool ok = false; + if(contents.isEmpty()){ contents << "\n"; } + if( file.open(QIODevice::WriteOnly | QIODevice::Truncate) ){ + QTextStream out(&file); + out << contents.join("\n"); + if(!contents.last().isEmpty()){ out << "\n"; } //always end with a new line + file.close(); + ok = true; + } + return ok; +} + +bool LUtils::isValidBinary(QString& bin){ + if(!bin.startsWith("/")){ + //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;} + } + } + //bin should be the full path by now + if(!bin.startsWith("/")){ return false; } + QFileInfo info(bin); + bool good = (info.exists() && info.isExecutable()); + if(good){ bin = info.absoluteFilePath(); } + return good; +} + +QString LUtils::GenerateOpenTerminalExec(QString term, QString dirpath){ + //Check the input terminal application (default/fallback - determined by calling application) + //if(!LUtils::isValidBinary(term)){ + if(term.endsWith(".desktop")){ + //Pull the binary name out of the shortcut + bool ok = false; + XDGDesktop DF = LXDG::loadDesktopFile(term,ok); + if(!ok){ term = "xterm"; } + else{ term= DF.exec.section(" ",0,0); } //only take the binary name - not any other flags + }else{ + term = "xterm"; //fallback + } + //} + //Now create the calling command for the designated terminal + // NOTE: While the "-e" routine is supposed to be universal, many terminals do not properly use it + // so add some special/known terminals here as necessary + QString exec; + qWarning() << " - Reached terminal initialization" << term; + if(term=="mate-terminal" || term=="lxterminal" || term=="gnome-terminal"){ + exec = term+" --working-directory=\""+dirpath+"\""; + }else if(term=="xfce4-terminal"){ + exec = term+" --default-working-directory=\""+dirpath+"\""; + }else if(term=="konsole"){ + exec = term+" --workdir \""+dirpath+"\""; + }else{ + //-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")); + if(!LUtils::isValidBinary(shell)){ shell = "/bin/sh"; } //universal fallback for a shell + exec = term + " -e \"cd " + dirpath + " && " + shell + " \" "; + } + qDebug() << exec; + return exec; +} + +QStringList LUtils::listSubDirectories(QString dir, bool recursive){ + //This is a recursive method for returning the full paths of all subdirectories (if recursive flag is enabled) + QDir maindir(dir); + QStringList out; + QStringList subs = maindir.entryList(QDir::NoDotAndDotDot | QDir::Dirs, QDir::Name); + for(int i=0; i<subs.length(); i++){ + out << maindir.absoluteFilePath(subs[i]); + if(recursive){ + out << LUtils::listSubDirectories(maindir.absoluteFilePath(subs[i]), recursive); + } + } + return out; +} + +QString LUtils::PathToAbsolute(QString path){ + //Convert an input path to an absolute path (this does not check existance ot anything) + if(path.startsWith("/")){ return path; } //already an absolute path + if(path.startsWith("~")){ path.replace(0,1,QDir::homePath()); } + if(!path.startsWith("/")){ + //Must be a relative path + if(path.startsWith("./")){ path = path.remove(2); } + path.prepend( QDir::currentPath()+"/"); + } + return path; +} + +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++){ + if(wildcards){ imgExtensions << "*."+QString::fromLocal8Bit(fmt[i]); } + else{ imgExtensions << QString::fromLocal8Bit(fmt[i]); } + } + } + return imgExtensions; +} + + QTranslator* LUtils::LoadTranslation(QApplication *app, QString appname, QString locale, QTranslator *cTrans){ + //Get the current localization + QString langEnc = "UTF-8"; //default value + QString langCode = locale; //provided locale + if(langCode.isEmpty()){ langCode = getenv("LC_ALL"); } + if(langCode.isEmpty()){ langCode = getenv("LANG"); } + if(langCode.isEmpty()){ langCode = "en_US.UTF-8"; } //default to US english + //See if the encoding is included and strip it out as necessary + if(langCode.contains(".")){ + langEnc = langCode.section(".",-1); + langCode = langCode.section(".",0,0); + } + //Now verify the encoding for the locale + if(langCode =="C" || langCode=="POSIX" || langCode.isEmpty()){ + langEnc = "System"; //use the Qt system encoding + } + if(app !=0){ + qDebug() << "Loading Locale:" << appname << langCode << langEnc; + //If an existing translator was provided, remove it first (will be replaced) + if(cTrans!=0){ app->removeTranslator(cTrans); } + //Setup the translator + cTrans = new QTranslator(); + //Use the shortened locale code if specific code does not have a corresponding file + if(!QFile::exists(LOS::LuminaShare()+"i18n/"+appname+"_" + langCode + ".qm") && langCode!="en_US" ){ + langCode.truncate( langCode.indexOf("_") ); + } + if( cTrans->load( appname+QString("_") + langCode, LOS::LuminaShare()+"i18n/" ) ){ + app->installTranslator( cTrans ); + }else{ + //Translator could not be loaded for some reason + cTrans = 0; + if(langCode!="en_US"){ + qWarning() << " - Could not load Locale:" << langCode; + } + } + }else{ + //Only going to set the encoding since no application given + qDebug() << "Loading System Encoding:" << langEnc; + } + //Load current encoding for this locale + QTextCodec::setCodecForLocale( QTextCodec::codecForName(langEnc.toUtf8()) ); + return cTrans; +} + +QStringList LUtils::knownLocales(){ + QDir i18n = QDir(LOS::LuminaShare()+"i18n"); + if( !i18n.exists() ){ return QStringList(); } + QStringList files = i18n.entryList(QStringList() << "lumina-desktop_*.qm", QDir::Files, QDir::Name); + if(files.isEmpty()){ return QStringList(); } + //Now strip off the filename and just leave the locale tag + for(int i=0; i<files.length(); i++){ + files[i].chop(3); //remove the ".qm" on the end + files[i] = files[i].section("_",1,50).simplified(); + } + files << "en_US"; //default locale + files.sort(); + return files; +} + +void LUtils::setLocaleEnv(QString lang, QString msg, QString time, QString num,QString money,QString collate, QString ctype){ + //Adjust the current locale environment variables + bool all = false; + if(msg.isEmpty() && time.isEmpty() && num.isEmpty() && money.isEmpty() && collate.isEmpty() && ctype.isEmpty() ){ + if(lang.isEmpty()){ return; } //nothing to do - no changes requested + all = true; //set everything to the "lang" value + } + //If no lang given, but others are given, then use the current setting + if(lang.isEmpty()){ lang = getenv("LC_ALL"); } + if(lang.isEmpty()){ lang = getenv("LANG"); } + if(lang.isEmpty()){ lang = "en_US"; } + //Now go through and set/unset the environment variables + // - LANG & LC_ALL + if(!lang.contains(".")){ lang.append(".UTF-8"); } + setenv("LANG",lang.toUtf8() ,1); //overwrite setting (this is always required as the fallback) + if(all){ setenv("LC_ALL",lang.toUtf8() ,1); } + else{ unsetenv("LC_ALL"); } //make sure the custom settings are used + // - LC_MESSAGES + if(msg.isEmpty()){ unsetenv("LC_MESSAGES"); } + else{ + if(!msg.contains(".")){ msg.append(".UTF-8"); } + setenv("LC_MESSAGES",msg.toUtf8(),1); + } + // - LC_TIME + if(time.isEmpty()){ unsetenv("LC_TIME"); } + else{ + if(!time.contains(".")){ time.append(".UTF-8"); } + setenv("LC_TIME",time.toUtf8(),1); + } + // - LC_NUMERIC + if(num.isEmpty()){ unsetenv("LC_NUMERIC"); } + else{ + if(!num.contains(".")){ num.append(".UTF-8"); } + setenv("LC_NUMERIC",num.toUtf8(),1); + } + // - LC_MONETARY + if(money.isEmpty()){ unsetenv("LC_MONETARY"); } + else{ + if(!money.contains(".")){ money.append(".UTF-8"); } + setenv("LC_MONETARY",money.toUtf8(),1); + } + // - LC_COLLATE + if(collate.isEmpty()){ unsetenv("LC_COLLATE"); } + else{ + if(!collate.contains(".")){ collate.append(".UTF-8"); } + setenv("LC_COLLATE",collate.toUtf8(),1); + } + // - LC_CTYPE + if(ctype.isEmpty()){ unsetenv("LC_CTYPE"); } + else{ + if(!ctype.contains(".")){ ctype.append(".UTF-8"); } + setenv("LC_CTYPE",ctype.toUtf8(),1); + } +} + +QString LUtils::currentLocale(){ + QString curr = getenv("LC_ALL");// = QLocale::system(); + if(curr.isEmpty()){ curr = getenv("LANG"); } + if(curr.isEmpty()){ curr = "en_US"; } + curr = curr.section(".",0,0); //remove any encodings off the end + return curr; +} + +double LUtils::DisplaySizeToBytes(QString num){ + //qDebug() << "Convert Num to Bytes:" << num; + num = num.toLower().simplified(); + num = num.remove(" "); + if(num.isEmpty()){ return 0.0; } + if(num.endsWith("b")){ num.chop(1); } //remove the "bytes" marker (if there is one) + QString lab = "b"; + if(!num[num.size()-1].isNumber()){ + lab = num.right(1); num.chop(1); + } + double N = num.toDouble(); + QStringList labs; labs <<"b"<<"k"<<"m"<<"g"<<"t"<<"p"; //go up to petabytes for now + for(int i=0; i<labs.length(); i++){ + if(lab==labs[i]){ break; }//already at the right units - break out + N = N*1024.0; //Move to the next unit of measurement + } + //qDebug() << " - Done:" << QString::number(N) << lab << num; + return N; +} + +QString LUtils::BytesToDisplaySize(qint64 ibytes){ + static QStringList labs = QStringList(); + if(labs.isEmpty()){ labs << "B" << "K" << "M" << "G" << "T" << "P"; } + //Now get the dominant unit + int c=0; + double bytes = ibytes; //need to keep decimel places for calculations + while(bytes>=1000 && c<labs.length() ){ + bytes = bytes/1024; + c++; + } //labs[c] is the unit + //Bytes are now + //Now format the number (up to 3 digits, not including decimel places) + QString num; + if(bytes>=100){ + //No decimel places + num = QString::number(qRound(bytes)); + }else if(bytes>=10){ + //need 1 decimel place + num = QString::number( (qRound(bytes*10)/10.0) ); + }else if(bytes>1){ + //need 2 decimel places + num = QString::number( (qRound(bytes*100)/100.0) ); + }else{ + //Fully decimel (3 places) + num = "0."+QString::number(qRound(bytes*1000)); + } + return (num+labs[c]); +} + +QString LUtils::SecondsToDisplay(int secs){ + if(secs < 0){ return "??"; } + QString rem; //remaining + if(secs > 3600){ + int hours = secs/3600; + rem.append( QString::number(hours)+"h "); + secs = secs - (hours*3600); + } + if(secs > 60){ + int min = secs/60; + rem.append( QString::number(min)+"m "); + secs = secs - (min*60); + } + if(secs > 0){ + rem.append( QString::number(secs)+"s"); + }else{ + rem.append( "0s" ); + } + return rem; +} + +//Various function for finding valid QtQuick plugins on the system +bool LUtils::validQuickPlugin(QString ID){ + return ( !LUtils::findQuickPluginFile(ID).isEmpty() ); +} + +QString LUtils::findQuickPluginFile(QString ID){ + if(ID.startsWith("quick-")){ ID = ID.section("-",1,50); } //just in case + //Give preference to any user-supplied plugins (overwrites for system plugins) + QString path = QDir::homePath()+"/.lumina/quickplugins/quick-"+ID+".qml"; + if( QFile::exists(path) ){return path; } + path = LOS::LuminaShare()+"quickplugins/quick-"+ID+".qml"; + if( QFile::exists(path) ){return path; } + return ""; //could not be found +} + +QStringList LUtils::listQuickPlugins(){ + QDir dir(QDir::homePath()+"/.lumina/quickplugins"); + QStringList files = dir.entryList(QStringList() << "quick-*.qml", QDir::Files | QDir::NoDotAndDotDot, QDir::Name); + dir.cd(LOS::LuminaShare()+"quickplugins"); + files << dir.entryList(QStringList() << "quick-*.qml", QDir::Files | QDir::NoDotAndDotDot, QDir::Name); + for(int i=0; i<files.length(); i++){ + files[i] = files[i].section("quick-",1,100).section(".qml",0,0); //just grab the ID out of the middle of the filename + } + files.removeDuplicates(); + //qDebug() << "Found Quick Plugins:" << files; + return files; +} + +QStringList LUtils::infoQuickPlugin(QString ID){ //Returns: [Name, Description, Icon] + //qDebug() << "Find Quick Info:" << ID; + QString path = findQuickPluginFile(ID); + //qDebug() << " - path:" << path; + if(path.isEmpty()){ return QStringList(); } //invalid ID + QStringList contents = LUtils::readFile(path); + if(contents.isEmpty()){ return QStringList(); } //invalid file (unreadable) + contents = contents.filter("//").filter("=").filter("Plugin"); //now just grab the comments + //qDebug() << " - Filtered Contents:" << contents; + QStringList info; info << "" << "" << ""; + for(int i=0; i<contents.length(); i++){ + if(contents[i].contains("Plugin-Name=")){ info[0] = contents[i].section("Plugin-Name=",1,1).simplified(); } + else if(contents[i].contains("Plugin-Description=")){ info[1] = contents[i].section("Plugin-Description=",1,1).simplified(); } + else if(contents[i].contains("Plugin-Icon=")){ info[2] = contents[i].section("Plugin-Icon=",1,1).simplified(); } + } + if(info[0].isEmpty()){ info[0]=ID; } + if(info[2].isEmpty()){ info[2]="preferences-plugin"; } + //qDebug() << " - info:" << info; + return info; +} + +QStringList LUtils::listFavorites(){ + static QDateTime lastRead; + QDateTime cur = QDateTime::currentDateTime(); + if(lastRead.isNull() || lastRead<QFileInfo(QDir::homePath()+"/.lumina/favorites/fav.list").lastModified()){ + fav = LUtils::readFile(QDir::homePath()+"/.lumina/favorites/fav.list"); + fav.removeAll(""); //remove any empty lines + lastRead = cur; + if(fav.isEmpty()){ + //Make sure the favorites dir exists, and create it if necessary + QDir dir(QDir::homePath()+"/.lumina/favorites"); + if(!dir.exists()){ dir.mkpath(QDir::homePath()+"/.lumina/favorites"); } + } + } + + return fav; +} + +bool LUtils::saveFavorites(QStringList list){ + bool ok = LUtils::writeFile(QDir::homePath()+"/.lumina/favorites/fav.list", list, true); + if(ok){ fav = list; } //also save internally in case of rapid write/read of the file + return ok; +} + +bool LUtils::isFavorite(QString path){ + QStringList fav = LUtils::listFavorites(); + for(int i=0; i<fav.length(); i++){ + if(fav[i].endsWith("::::"+path)){ return true; } + } + return false; +} + +bool LUtils::addFavorite(QString path, QString name){ + //Generate the type of favorite this is + QFileInfo info(path); + QString type; + if(info.isDir()){ type="dir"; } + else if(info.suffix()=="desktop"){ type="app"; } + else{ type = LXDG::findAppMimeForFile(path); } + //Assign a name if none given + if(name.isEmpty()){ name = info.fileName(); } + //Now add it to the list + QStringList favs = LUtils::listFavorites(); + bool found = false; + for(int i=0; i<favs.length(); i++){ + if(favs[i].endsWith("::::"+path)){ favs[i] = name+"::::"+type+"::::"+path; } + } + if(!found){ favs << name+"::::"+type+"::::"+path; } + return LUtils::saveFavorites(favs); +} + +void LUtils::removeFavorite(QString path){ + QStringList fav = LUtils::listFavorites(); + bool changed = false; + for(int i=0; i<fav.length(); i++){ + if(fav[i].endsWith("::::"+path)){ fav.removeAt(i); i--; changed=true;} + } + if(changed){ LUtils::saveFavorites(fav); } +} + +void LUtils::upgradeFavorites(int fromoldversionnumber){ + if(fromoldversionnumber <= 8004){ // < pre-0.8.4>, sym-links in the ~/.lumina/favorites dir} + //Include 0.8.4-devel versions in this upgrade (need to distinguish b/w devel and release versions later somehow) + QDir favdir(QDir::homePath()+"/.lumina/favorites"); + QFileInfoList symlinks = favdir.entryInfoList(QDir::Files | QDir::Dirs | QDir::System | QDir::NoDotAndDotDot); + QStringList favfile = LUtils::listFavorites(); //just in case some already exist + bool newentry = false; + for(int i=0; i<symlinks.length(); i++){ + if(!symlinks[i].isSymLink()){ continue; } //not a symlink + QString path = symlinks[i].symLinkTarget(); + QString name = symlinks[i].fileName(); //just use the name of the symlink from the old system + QString type; + if(symlinks[i].isDir()){ type = "dir"; } + else if(name.endsWith(".desktop")){ type = "app"; } + else{ type = LXDG::findAppMimeForFile(path); } + //Put the line into the file + favfile << name+"::::"+type+"::::"+path; + //Now remove the symlink - obsolete format + QFile::remove(symlinks[i].absoluteFilePath()); + newentry = true; + } + if(newentry){ + LUtils::saveFavorites(favfile); + } + } //end check for version <= 0.8.4 + +} + +void LUtils::LoadSystemDefaults(bool skipOS){ + //Will create the Lumina configuration files based on the current system template (if any) + qDebug() << "Loading System Defaults"; + 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"); } + if(sysDefaults.isEmpty() && !skipOS) { sysDefaults = LUtils::readFile(LOS::SysPrefix()+"etc/luminaDesktop.conf"); } + if(sysDefaults.isEmpty() && !skipOS){ sysDefaults = LUtils::readFile(LOS::SysPrefix()+"etc/luminaDesktop.conf.dist"); } + if(sysDefaults.isEmpty() && !skipOS) { sysDefaults = LUtils::readFile(L_ETCDIR+"/luminaDesktop.conf"); } + if(sysDefaults.isEmpty() && !skipOS){ sysDefaults = LUtils::readFile(L_ETCDIR+"/luminaDesktop.conf.dist"); } + if(sysDefaults.isEmpty()){ sysDefaults = LUtils::readFile(LOS::LuminaShare()+"luminaDesktop.conf"); } + //Find the number of the left-most desktop screen + QString screen = "0"; + QDesktopWidget *desk =QApplication::desktop(); + QRect screenGeom; + for(int i=0; i<desk->screenCount(); i++){ + if(desk->screenGeometry(i).x()==0){ + screen = QString::number(i); + screenGeom = desk->screenGeometry(i); + break; + } + } + //Now setup the default "desktopsettings.conf" and "sessionsettings.conf" files + QStringList deskset, sesset, lopenset; + + // -- SESSION SETTINGS -- + QStringList tmp = sysDefaults.filter("session_"); + if(tmp.isEmpty()){ tmp = sysDefaults.filter("session."); }//for backwards compat + sesset << "[General]"; //everything is in this section + sesset << "DesktopVersion="+LUtils::LuminaDesktopVersion(); + 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(); + if(val.isEmpty()){ continue; } + QString istrue = (val.toLower()=="true") ? "true": "false"; + //Change in 0.8.5 - use "_" instead of "." within variables names - need backwards compat for a little while + if(var.contains(".")){ var.replace(".","_"); } + //Now parse the variable and put the value in the proper file + + //Special handling for values which need to exist first + if(var.endsWith("_ifexists") ){ + var = var.remove("_ifexists"); //remove this flag from the variable + //Check if the value exists (absolute path only) + if(!QFile::exists(val)){ continue; } //skip this line - value/file does not exist + } + //Parse/save the value + QString loset, sset; //temporary strings + 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"){ sset = "default-terminal="+val; } + else if(var=="session_default_filemanager"){ + sset = "default-filemanager="+val; + loset = "directory="+val; + } + else if(var=="session_default_webbrowser"){ loset = "webbrowser="+val; } + else if(var=="session_default_email"){ loset = "email="+val; } + //Put the line into the file (overwriting any previous assignment as necessary) + if(!loset.isEmpty()){ + int index = lopenset.indexOf(QRegExp(loset.section("=",0,0)+"=*", Qt::CaseSensitive, QRegExp::Wildcard)); + qDebug() << "loset line:" << loset << index << lopenset; + if(index<0){ lopenset << loset; } //new line + else{ lopenset[index] = loset; } //overwrite the other line + } + if(!sset.isEmpty()){ + int index = sesset.indexOf(QRegExp(sset.section("=",0,0)+"=*", Qt::CaseSensitive, QRegExp::Wildcard)); + if(index<0){ sesset << sset; } //new line + else{ sesset[index] = sset; } //overwrite the other line + } + } + if(!lopenset.isEmpty()){ lopenset.prepend("[default]"); } //the session options exist within this set + + // -- DESKTOP SETTINGS -- + //(only works for the primary desktop at the moment) + tmp = sysDefaults.filter("desktop_"); + if(tmp.isEmpty()){ tmp = sysDefaults.filter("desktop."); }//for backwards compat + if(!tmp.isEmpty()){deskset << "[desktop-"+screen+"]"; } + 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(); + if(val.isEmpty()){ continue; } + QString istrue = (val.toLower()=="true") ? "true": "false"; + //Change in 0.8.5 - use "_" instead of "." within variables names - need backwards compat for a little while + if(var.contains(".")){ var.replace(".","_"); } + //Now parse the variable and put the value in the proper file + if(var=="desktop_visiblepanels"){ deskset << "panels="+val; } + else if(var=="desktop_backgroundfiles"){ deskset << "background\\filelist="+val; } + else if(var=="desktop_backgroundrotateminutes"){ deskset << "background\\minutesToChange="+val; } + else if(var=="desktop_plugins"){ deskset << "pluginlist="+val; } + else if(var=="desktop_generate_icons"){ deskset << "generateDesktopIcons="+istrue; } + } + if(!tmp.isEmpty()){ deskset << ""; } //space between sections + + // -- PANEL SETTINGS -- + //(only works for the primary desktop at the moment) + for(int i=1; i<11; i++){ + QString panvar = "panel"+QString::number(i); + tmp = sysDefaults.filter(panvar); + if(!tmp.isEmpty()){deskset << "[panel"+screen+"."+QString::number(i-1)+"]"; } + 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(); + if(val.isEmpty()){ continue; } + QString istrue = (val.toLower()=="true") ? "true": "false"; + //Change in 0.8.5 - use "_" instead of "." within variables names - need backwards compat for a little while + if(var.contains(".")){ var.replace(".","_"); } + //Now parse the variable and put the value in the proper file + if(var==(panvar+"_pixelsize")){ + //qDebug() << "Panel Size:" << val; + if(val.contains("%")){ + QString last = val.section("%",1,1).toLower(); //last character + val = val.section("%",0,0); + if(last=="h"){ val = QString::number( qRound(screenGeom.height()*val.toDouble())/100 ); }//adjust value to a percentage of the height of the screen + else if(last=="w"){ val = QString::number( qRound(screenGeom.width()*val.toDouble())/100 ); }//adjust value to a percentage of the width of the screen + } + //qDebug() << " -- Adjusted:" << val; + deskset << "height="+val; + } + else if(var==(panvar+"_autohide")){ deskset << "hidepanel="+istrue; } + else if(var==(panvar+"_location")){ deskset << "location="+val.toLower(); } + else if(var==(panvar+"_plugins")){ deskset << "pluginlist="+val; } + else if(var==(panvar+"_pinlocation")){ deskset << "pinLocation="+val.toLower(); } + else if(var==(panvar+"_edgepercent")){ deskset << "lengthPercent="+val; } + } + if(!tmp.isEmpty()){ deskset << ""; } //space between sections + } + + // -- MENU settings -- + tmp = sysDefaults.filter("menu_"); + if(tmp.isEmpty()){ tmp = sysDefaults.filter("menu."); } //backwards compat + if(!tmp.isEmpty()){deskset << "[menu]"; } + for(int i=0; i<tmp.length(); i++){ + if(tmp[i].startsWith("#") || !tmp[i].contains("=") ){ continue; } + QString var = tmp[i].section("=",0,0).simplified(); + QString val = tmp[i].section("=",1,1).section("#",0,0).toLower().simplified(); + if(val.isEmpty()){ continue; } + //Change in 0.8.5 - use "_" instead of "." within variables names - need backwards compat for a little while + if(var.contains(".")){ var.replace(".","_"); } + //Now parse the variable and put the value in the proper file + if(var=="menu_plugins"){ deskset << "itemlist="+val; } + } + if(!tmp.isEmpty()){ deskset << ""; } //space between sections + + // -- FAVORITES -- + tmp = sysDefaults.filter("favorites_"); + if(tmp.isEmpty()){ tmp = sysDefaults.filter("favorites."); } + 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(); + //Change in 0.8.5 - use "_" instead of "." within variables names - need backwards compat for a little while + if(var.contains(".")){ var.replace(".","_"); } + //Now parse the variable and put the value in the proper file + qDebug() << "Favorite entry:" << var << val; + if(var=="favorites_add_ifexists" && QFile::exists(val)){ qDebug() << " - Exists/Adding:"; LUtils::addFavorite(val); } + else if(var=="favorites_add"){ qDebug() << " - Adding:"; LUtils::addFavorite(val); } + else if(var=="favorites_remove"){ qDebug() << " - Removing:"; LUtils::removeFavorite(val); } + } + + //Now do any theme settings + QStringList themesettings = LTHEME::currentSettings(); + //List: [theme path, colorspath, iconsname, font, fontsize] + //qDebug() << "Current Theme Color:" << themesettings[1]; + tmp = sysDefaults.filter("theme_"); + if(tmp.isEmpty()){ tmp = sysDefaults.filter("theme."); } + bool setTheme = !tmp.isEmpty(); + 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(); + if(val.isEmpty()){ continue; } + //Change in 0.8.5 - use "_" instead of "." within variables names - need backwards compat for a little while + if(var.contains(".")){ var.replace(".","_"); } + //Now parse the variable and put the value in the proper file + if(var=="theme_themefile"){ themesettings[0] = val; } + else if(var=="theme_colorfile"){ themesettings[1] = val; } + else if(var=="theme_iconset"){ themesettings[2] = val; } + else if(var=="theme_font"){ themesettings[3] = val; } + else if(var=="theme_fontsize"){ + if(val.endsWith("%")){ val = QString::number( (screenGeom.height()*val.section("%",0,0).toDouble())/100 )+"px"; } + themesettings[4] = val; + } + } + //qDebug() << " - Now Color:" << themesettings[1] << setTheme; + + //Now double check that the custom theme/color files exist and reset it will the full path as necessary + if(setTheme){ + QStringList systhemes = LTHEME::availableSystemThemes(); + QStringList syscolors = LTHEME::availableSystemColors(); + //theme file + //qDebug() << "Detected Themes/colors:" << systhemes << syscolors; + if( !themesettings[0].startsWith("/") || !QFile::exists(themesettings[0]) || !themesettings[0].endsWith(".qss.template")){ + themesettings[0] = themesettings[0].section(".qss",0,0).simplified(); + for(int i=0; i<systhemes.length(); i++){ + if(systhemes[i].startsWith(themesettings[0]+"::::",Qt::CaseInsensitive)){ + themesettings[0] = systhemes[i].section("::::",1,1); //Replace with the full path + break; + } + } + } + //color file + if( !themesettings[1].startsWith("/") || !QFile::exists(themesettings[1]) || !themesettings[1].endsWith(".qss.colors") ){ + //Remove any extra/invalid extension + themesettings[1] = themesettings[1].section(".qss",0,0).simplified(); + for(int i=0; i<syscolors.length(); i++){ + if(syscolors[i].startsWith(themesettings[1]+"::::",Qt::CaseInsensitive)){ + themesettings[1] = syscolors[i].section("::::",1,1); //Replace with the full path + break; + } + } + } + } + //qDebug() << " - Final Theme Color:" << themesettings[1]; + + //Ensure that the settings directory exists + QString setdir = QDir::homePath()+"/.lumina/LuminaDE"; + if(!QFile::exists(setdir)){ + QDir dir; + dir.mkpath(setdir); + } + //Now save the settings files + if(setTheme){ LTHEME::setCurrentSettings( themesettings[0], themesettings[1], themesettings[2], themesettings[3], themesettings[4]); } + LUtils::writeFile(setdir+"/sessionsettings.conf", sesset, true); + LUtils::writeFile(setdir+"/desktopsettings.conf", deskset, true); + LUtils::writeFile(setdir+"/lumina-open.conf", lopenset, true); +} + +// ======================= +// RESIZEMENU CLASS +// ======================= +ResizeMenu::ResizeMenu(QWidget *parent) : QMenu(parent){ + this->setContentsMargins(1,1,1,1); + this->setMouseTracking(true); + resizeSide = NONE; + cAct = new QWidgetAction(this); + contents = 0; + connect(this, SIGNAL(aboutToShow()), this, SLOT(clearFlags()) ); + connect(this, SIGNAL(aboutToHide()), this, SLOT(clearFlags()) ); + connect(cAct, SIGNAL(hovered()), this, SLOT(actionHovered()) ); +} + +ResizeMenu::~ResizeMenu(){ + +} + +void ResizeMenu::setContents(QWidget *con){ + this->clear(); + cAct->setDefaultWidget(con); + this->addAction(cAct); + contents = con; //save for later + contents->setCursor(Qt::ArrowCursor); +} + +void ResizeMenu::mouseMoveEvent(QMouseEvent *ev){ + QRect geom = this->geometry(); + //Note: The exact position does not matter as much as the size + // since the window will be moved again the next time it is shown + // The "-2" in the sizing below accounts for the menu margins + QPoint gpos = this->mapToGlobal(ev->pos()); + switch(resizeSide){ + case TOP: + if(gpos.y() >= geom.bottom()-1){ break; } + geom.setTop(gpos.y()); + this->setGeometry(geom); + if(contents!=0){ contents->setFixedSize(QSize(geom.width()-2, geom.height()-2)); } + break; + case BOTTOM: + if(gpos.y() <= geom.top()+1){ break; } + geom.setBottom( gpos.y()); + this->setGeometry(geom); + if(contents!=0){ contents->setFixedSize(QSize(geom.width()-2, geom.height()-2)); } + break; + case LEFT: + if(gpos.x() >= geom.right()-1){ break; } + geom.setLeft(gpos.x()); + this->setGeometry(geom); + if(contents!=0){ contents->setFixedSize(QSize(geom.width()-2, geom.height()-2)); } + break; + case RIGHT: + if(gpos.x() <= geom.left()+1){ break; } + geom.setRight(gpos.x()); + this->setGeometry(geom); + if(contents!=0){ contents->setFixedSize(QSize(geom.width()-2, geom.height()-2)); } + break; + default: //NONE + //qDebug() << " - Mouse At:" << ev->pos(); + //Just adjust the mouse cursor which is shown + if(ev->pos().x()<=1 && ev->pos().x() >= -1){ this->setCursor(Qt::SizeHorCursor); } + else if(ev->pos().x() >= this->width()-1 && ev->pos().x() <= this->width()+1){ this->setCursor(Qt::SizeHorCursor); } + else if(ev->pos().y()<=1 && ev->pos().y() >= -1){ this->setCursor(Qt::SizeVerCursor); } + else if(ev->pos().y() >= this->height()-1 && ev->pos().y() <= this->height()+1){ this->setCursor(Qt::SizeVerCursor); } + else{ this->setCursor(Qt::ArrowCursor); } + } + QMenu::mouseMoveEvent(ev); //do normal processing as well +} + +void ResizeMenu::mousePressEvent(QMouseEvent *ev){ + bool used = false; + if(ev->buttons().testFlag(Qt::LeftButton) && resizeSide==NONE){ + //qDebug() << "Mouse Press Event:" << ev->pos() << resizeSide; + if(ev->pos().x()<=1 && ev->pos().x() >= -1){resizeSide = LEFT; used = true;} + else if(ev->pos().x() >= this->width()-1 && ev->pos().x() <= this->width()+1){ resizeSide = RIGHT; used = true;} + else if(ev->pos().y()<=1 && ev->pos().y() >= -1){ resizeSide = TOP; used = true; } + else if(ev->pos().y() >= this->height()-1 && ev->pos().y() <= this->height()+1){ resizeSide = BOTTOM; used = true; } + } + if(used){ ev->accept(); } + else{ QMenu::mousePressEvent(ev); } //do normal processing +} + +void ResizeMenu::mouseReleaseEvent(QMouseEvent *ev){ + if(ev->button() == Qt::LeftButton && resizeSide!=NONE ){ + //qDebug() << "Mouse Release Event:" << ev->pos() << resizeSide; + resizeSide = NONE; + emit MenuResized(contents->size()); + ev->accept(); + }else{ + QMenu::mouseReleaseEvent(ev); //do normal processing + } +} diff --git a/src-qt5/core/libLumina/LuminaUtils.h b/src-qt5/core/libLumina/LuminaUtils.h new file mode 100644 index 00000000..f0e4b88b --- /dev/null +++ b/src-qt5/core/libLumina/LuminaUtils.h @@ -0,0 +1,128 @@ +//=========================================== +// Lumina-DE source code +// Copyright (c) 2012-2015, Ken Moore +// Available under the 3-clause BSD license +// See the LICENSE file for full details +//=========================================== +#ifndef _LUMINA_LIBRARY_UTILS_H +#define _LUMINA_LIBRARY_UTILS_H + +#include <QCoreApplication> +#include <QProcess> +#include <QTextStream> +#include <QFile> +#include <QDir> +#include <QString> +#include <QStringList> +#include <QFile> +#include <QFileInfo> +#include <QObject> +#include <QTranslator> +//#include <QApplication> +#include <QMenu> +#include <QMouseEvent> +#include <QSize> +#include <QWidgetAction> + +class LUtils{ +public: + //Get the current version/build of the Lumina desktop + static QString LuminaDesktopVersion(); + static QString LuminaDesktopBuildDate(); + + //Run an external command and return the exit code + static int runCmd(QString cmd, QStringList args = QStringList()); + //Run an external command and return any text output (one line per entry) + static QStringList getCmdOutput(QString cmd, QStringList args = QStringList()); + + //Read a text file + static QStringList readFile(QString filepath); + //Write a text file + static bool writeFile(QString filepath, QStringList contents, bool overwrite=false); + + //Check whether a file/path is a valid binary + static bool isValidBinary(QString& bin); //full path or name only + static bool isValidBinary(const char *bin){ + QString bins(bin); + return isValidBinary(bins); //overload for a "junk" binary variable input + } + + //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); + + //Convert an input file/dir path to an absolute file path + static QString PathToAbsolute(QString path); + + //Get the list of all file extensions which Qt can read (lowercase) + static QStringList imageExtensions(bool wildcards = false); + + //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 + + //Various function for finding valid QtQuick plugins on the system + static bool validQuickPlugin(QString ID); + 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 + static QStringList listFavorites(); + static bool saveFavorites(QStringList); + static bool isFavorite(QString path); + 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); + +}; + +//Special subclass for a menu which the user can grab the edges and resize as necessary +// Note: Make sure that you don't set 0pixel contents margins on this menu +// - it needs at least 1 pixel margins for the user to be able to grab it +class ResizeMenu : public QMenu{ + Q_OBJECT +public: + ResizeMenu(QWidget *parent = 0); + virtual ~ResizeMenu(); + + void setContents(QWidget *con); + +private: + enum SideFlag{NONE, TOP, BOTTOM, LEFT, RIGHT}; + SideFlag resizeSide; + QWidget *contents; + QWidgetAction *cAct; + +private slots: + void clearFlags(){ + resizeSide=NONE; + } + +protected: + virtual void mouseMoveEvent(QMouseEvent *ev); + virtual void mousePressEvent(QMouseEvent *ev); + virtual void mouseReleaseEvent(QMouseEvent *ev); + +signals: + void MenuResized(QSize); //Emitted when the menu is manually resized by the user + +}; + +#endif diff --git a/src-qt5/core/libLumina/LuminaX11.cpp b/src-qt5/core/libLumina/LuminaX11.cpp new file mode 100644 index 00000000..5d1ac6f3 --- /dev/null +++ b/src-qt5/core/libLumina/LuminaX11.cpp @@ -0,0 +1,2172 @@ +//=========================================== +// Lumina-DE source code +// Copyright (c) 2014-2015, Ken Moore +// Available under the 3-clause BSD license +// See the LICENSE file for full details +//=========================================== +#include "LuminaX11.h" + +#include <QString> +#include <QByteArray> +#include <QFile> +#include <QObject> +#include <QImage> +#include <QApplication> +#include <QDesktopWidget> +#include <QScreen> + + + +//XCB Library includes +#include <xcb/xcb.h> +#include <xcb/xcb_atom.h> +#include <xcb/xproto.h> +#include <xcb/xcb_ewmh.h> +#include <xcb/xcb_icccm.h> +#include <xcb/xcb_image.h> +#include <xcb/xcb_aux.h> +#include <xcb/composite.h> +#include <xcb/damage.h> + +//XLib includes +#include <X11/extensions/Xdamage.h> + +#define DEBUG 0 + +//=============================== +//=============================== +// XCB LIBRARY FUNCTIONS +//=============================== +//=============================== +LXCB::LXCB(){ + xcb_intern_atom_cookie_t *cookie = xcb_ewmh_init_atoms(QX11Info::connection(), &EWMH); + if(!xcb_ewmh_init_atoms_replies(&EWMH, cookie, NULL) ){ + qDebug() << "Error with XCB atom initializations"; + }else{ + qDebug() << "Number of XCB screens:" << EWMH.nb_screens; + } +} +LXCB::~LXCB(){ + xcb_ewmh_connection_wipe(&EWMH); +} + +// private function +void LXCB::createWMAtoms(){ + ATOMS.clear(); + atoms.clear(); + //List the atoms needed by some WM functions + atoms << "WM_TAKE_FOCUS" << "WM_DELETE_WINDOW" << "WM_PROTOCOLS"; //WM_PROTOCOLS + + //Create all the requests for the atoms + QList<xcb_intern_atom_reply_t*> reply; + for(int i=0; i<atoms.length(); i++){ + reply << xcb_intern_atom_reply(QX11Info::connection(), \ + xcb_intern_atom(QX11Info::connection(), 0, atoms[i].length(), atoms[i].toLocal8Bit()), NULL); + } + //Now evaluate all the requests and save the atoms + for(int i=0; i<reply.length(); i++){ + if(reply[i]!=0){ + ATOMS << reply[i]->atom; + free(reply[i]); //done with this reply + }else{ + //Invalid atom - could not be created + atoms.removeAt(i); + reply.removeAt(i); + i--; + } + } + + + +} + +// === WindowList() === +QList<WId> LXCB::WindowList(bool rawlist){ + if(DEBUG){ qDebug() << "XCB: WindowList()" << rawlist; } + QList<WId> output; + //qDebug() << "Get Client list cookie"; + xcb_get_property_cookie_t cookie = xcb_ewmh_get_client_list_unchecked( &EWMH, 0); + xcb_ewmh_get_windows_reply_t winlist; + //qDebug() << "Get client list"; + if( 1 == xcb_ewmh_get_client_list_reply( &EWMH, cookie, &winlist, NULL) ){ + //qDebug() << " - Loop over items"; + unsigned int wkspace = CurrentWorkspace(); + for(unsigned int i=0; i<winlist.windows_len; i++){ + //Filter out the Lumina Desktop windows + if(WindowClass(winlist.windows[i]) == "Lumina Desktop Environment"){ continue; } + //Also filter out windows not on the active workspace + else if( (WindowWorkspace(winlist.windows[i])!=wkspace) && !rawlist ){ continue; } + else{ + output << winlist.windows[i]; + } + } + } + return output; +} + +// === CurrentWorkspace() === +unsigned int LXCB::CurrentWorkspace(){ + if(DEBUG){ qDebug() << "XCB: CurrentWorkspace()"; } + //qDebug() << "Get Current Workspace"; + xcb_get_property_cookie_t cookie = xcb_ewmh_get_current_desktop_unchecked(&EWMH, 0); + uint32_t wkspace = 0; + xcb_ewmh_get_current_desktop_reply(&EWMH, cookie, &wkspace, NULL); + //qDebug() << " - done:" << wkspace; + return wkspace; +} + +unsigned int LXCB::NumberOfWorkspaces(){ + xcb_get_property_cookie_t cookie = xcb_ewmh_get_number_of_desktops_unchecked(&EWMH, 0); + uint32_t number; + if(1==xcb_ewmh_get_number_of_desktops_reply(&EWMH, cookie, &number, NULL) ){ + return number; + }else{ + return 0; //unable to get this property + } +} + +// === ActiveWindow() === +WId LXCB::ActiveWindow(){ + if(DEBUG){ qDebug() << "XCB: ActiveWindow()"; } + xcb_get_property_cookie_t cookie = xcb_ewmh_get_active_window_unchecked(&EWMH, 0); + xcb_window_t actwin; + if(1 == xcb_ewmh_get_active_window_reply(&EWMH, cookie, &actwin, NULL) ){ + return actwin; + }else{ + return 0; //invalid ID/failure + } +} + +// === CheckDisableXinerama() === +bool LXCB::CheckDisableXinerama(){ + //returns true if Xinerama was initially set but now disabled + return false; + // TO-DO - not complete yet + + /*xcb_query_extension_cookie_t cookie = xcb_query_extension_unchecked(QX11Info::connection(), 8, "Xinerama"); + xcb_query_extension_reply_t *reply = xcb_query_extension_reply(QX11Info::connection(), cookie, NULL); + + if(reply!=0){ + + free(reply); + } + */ +} + +// === RegisterVirtualRoots() === +void LXCB::RegisterVirtualRoots(QList<WId> roots){ + if(DEBUG){ qDebug() << "XCB: RegisterVirtualRoots()"; } + //First convert the QList into the proper format + xcb_window_t *list = new xcb_window_t[ roots.length() ]; + for(int i=0; i<roots.length(); i++){ + list[i] = roots[i]; //move from the QList to the array + } + //Now set the property + xcb_ewmh_set_virtual_roots(&EWMH, 0, roots.length(), list); + //Now delete the temporary array from memory + delete [] list; +} + +// ===== SetCurrentWorkspace() ===== +void LXCB::SetCurrentWorkspace(int number){ + //Need to send a client message event for the window so the WM picks it up + xcb_client_message_event_t event; + event.response_type = XCB_CLIENT_MESSAGE; + event.format = 32; + event.window = QX11Info::appRootWindow(); + event.type = EWMH._NET_CURRENT_DESKTOP; + event.data.data32[0] = number; //set to enabled + event.data.data32[1] = XCB_TIME_CURRENT_TIME; + event.data.data32[2] = 0; + event.data.data32[3] = 0; + event.data.data32[4] = 0; + + xcb_send_event(QX11Info::connection(), 0, QX11Info::appRootWindow(), XCB_EVENT_MASK_STRUCTURE_NOTIFY | XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT, (const char *) &event); + + //EWMH function (does not seem to be recognized by Fluxbox) + xcb_ewmh_request_change_showing_desktop(&EWMH, QX11Info::appScreen(), number); +} + +// === WindowClass() === +QString LXCB::WindowClass(WId win){ + if(DEBUG){ qDebug() << "XCB: WindowClass()" << win; } + QString out; + if(win==0){ return ""; } + xcb_get_property_cookie_t cookie = xcb_icccm_get_wm_class_unchecked(QX11Info::connection(), win); + if(cookie.sequence == 0){ return out; } + xcb_icccm_get_wm_class_reply_t value; + if( 1== xcb_icccm_get_wm_class_reply( QX11Info::connection(), cookie, &value, NULL) ){ + out = QString::fromUtf8(value.class_name); + xcb_icccm_get_wm_class_reply_wipe(&value); + } + return out; +} + +// === WindowWorkspace() === +unsigned int LXCB::WindowWorkspace(WId win){ + if(DEBUG){ qDebug() << "XCB: WindowWorkspace()" << win; } + //qDebug() << "Get Window Workspace"; + if(win==0){ return 0; } + uint32_t wkspace = 0; + xcb_get_property_cookie_t scookie = xcb_ewmh_get_wm_state_unchecked(&EWMH, win); + xcb_get_property_cookie_t cookie = xcb_ewmh_get_wm_desktop_unchecked(&EWMH, win); + if(cookie.sequence == 0){ return wkspace; } + xcb_ewmh_get_wm_desktop_reply(&EWMH, cookie, &wkspace, NULL); + xcb_ewmh_get_atoms_reply_t reply; + if(1==xcb_ewmh_get_wm_state_reply(&EWMH,scookie, &reply, NULL)){ + //Also check if this window is "sticky", in which case return the current workspace (on all of them) + for(unsigned int i=0; i<reply.atoms_len; i++){ + if(reply.atoms[i]==EWMH._NET_WM_STATE_STICKY){ wkspace = LXCB::CurrentWorkspace(); break; } + } + } + //qDebug() << " - done: " << wkspace; + return wkspace; +} + +// === WindowGeometry() === +QRect LXCB::WindowGeometry(WId win, bool includeFrame){ + if(DEBUG){ qDebug() << "XCB: WindowGeometry()"; } + QRect geom; + if(win==0){ return geom; } + xcb_get_geometry_cookie_t cookie = xcb_get_geometry(QX11Info::connection(), win); + xcb_get_geometry_reply_t *reply = xcb_get_geometry_reply(QX11Info::connection(), cookie, NULL); + //qDebug() << "Get Window Geometry:" << reply; + if(reply != 0){ + geom = QRect(0, 0, reply->width, reply->height); //make sure to use the origin point for the window + //qDebug() << " - "<<reply->x << reply->y << reply->width << reply->height; + free(reply); + if(includeFrame){ + //Need to add/include the frame extents as well (assuming the frame info is available) + xcb_get_property_cookie_t cookie = xcb_ewmh_get_frame_extents_unchecked(&EWMH, win); + if(cookie.sequence != 0){ + xcb_ewmh_get_extents_reply_t frame; + if(1== xcb_ewmh_get_frame_extents_reply(&EWMH, cookie, &frame, NULL) ){ + //adjust the origin point to account for the frame + geom.translate(-frame.left, -frame.top); //move to the orign point for the frame + //adjust the size (include the frame sizes) + geom.setWidth( geom.width() + frame.left + frame.right ); + geom.setHeight( geom.height() + frame.top + frame.bottom ); + } + //qDebug() << " - Frame:" << frame.left << frame.right << frame.top << frame.bottom; + //qDebug() << " - Modified with Frame:" << geom.x() << geom.y() << geom.width() << geom.height(); + } + } + //Now need to convert this to absolute coordinates (not parent-relavitve) + xcb_translate_coordinates_cookie_t tcookie = xcb_translate_coordinates(QX11Info::connection(), win, QX11Info::appRootWindow(), geom.x(), geom.y()); + xcb_translate_coordinates_reply_t *trans = xcb_translate_coordinates_reply(QX11Info::connection(), tcookie, NULL); + if(trans!=0){ + //qDebug() << " - Got Translation:" << trans->dst_x << trans->dst_y; + //Replace the origin point with the global position (sizing remains the same) + geom.moveLeft(trans->dst_x); //adjust X coordinate (no size change) + geom.moveTop(trans->dst_y); //adjust Y coordinate (no size change) + free(trans); + } + }else{ + //Need to do another catch for this situation (probably not mapped yet) + } + return geom; +} + +// === WindowFrameGeometry() === +QList<int> LXCB::WindowFrameGeometry(WId win){ + if(DEBUG){ qDebug() << "XCB: WindowFrameGeometry()"; } + //Returns: [top, bottom, left, right] sizes for the frame + QList<int> geom; + if(win!=0){ + xcb_get_property_cookie_t cookie = xcb_ewmh_get_frame_extents_unchecked(&EWMH, win); + if(cookie.sequence != 0){ + xcb_ewmh_get_extents_reply_t frame; + if(1== xcb_ewmh_get_frame_extents_reply(&EWMH, cookie, &frame, NULL) ){ + //adjust the origin point to account for the frame + geom << frame.top << frame.bottom << frame.left << frame.right; + } + } + } + if(geom.isEmpty()){ geom << 0 << 0 << 0 << 0; } + return geom; +} + +// === WindowState() === +LXCB::WINDOWVISIBILITY LXCB::WindowState(WId win){ + if(DEBUG){ qDebug() << "XCB: WindowState()"; } + if(win==0){ return IGNORE; } + xcb_get_property_cookie_t cookie = xcb_ewmh_get_wm_state_unchecked(&EWMH, win); + if(cookie.sequence == 0){ return IGNORE; } + xcb_ewmh_get_atoms_reply_t states; + WINDOWVISIBILITY cstate = IGNORE; + //First Check for special states (ATTENTION in particular); + if( 1 == xcb_ewmh_get_wm_state_reply(&EWMH, cookie, &states, NULL) ){ + for(unsigned int i=0; i<states.atoms_len; i++){ + if(states.atoms[i] == EWMH._NET_WM_STATE_DEMANDS_ATTENTION){ cstate = ATTENTION; break; } //nothing more urgent - stop here + else if(states.atoms[i] == EWMH._NET_WM_STATE_HIDDEN){ cstate = INVISIBLE; } + } + } + //Now check to see if the window is the active one + if(cstate == IGNORE){ + xcb_get_property_cookie_t cookie = xcb_ewmh_get_active_window_unchecked(&EWMH, 0); + xcb_window_t actwin; + if(1 == xcb_ewmh_get_active_window_reply(&EWMH, cookie, &actwin, NULL) ){ + if(actwin == win){ cstate = ACTIVE; } + } + } + //Now check for ICCCM Urgency hint (not sure if this is still valid with EWMH instead) + /*if(cstate == IGNORE){ + xcb_get_property_cookie_t cookie = xcb_icccm_get_wm_hints_unchecked(QX11Info::connection(), win); + xcb_icccm_wm_hints_t hints; + if( 1== xcb_icccm_get_wm_hints_reply(QX11Info::connection(), cookie, &hints, NULL) ){ + if(xcb_icccm_wm_hints_get_urgency(hints) ){ cstate = ATTENTION; }; + } + }*/ + //Now check for standard visible/invisible attribute (current mapping state) + if(cstate == IGNORE){ + xcb_get_window_attributes_cookie_t cookie = xcb_get_window_attributes(QX11Info::connection(), win); + xcb_get_window_attributes_reply_t *attr = xcb_get_window_attributes_reply(QX11Info::connection(), cookie, NULL); + if(attr!=0){ + if(attr->map_state==XCB_MAP_STATE_VIEWABLE){ cstate = VISIBLE; } + else{ cstate = INVISIBLE; } + free(attr); + } + } + return cstate; +} + +// === WindowVisibleIconName() === +QString LXCB::WindowVisibleIconName(WId win){ //_NET_WM_VISIBLE_ICON_NAME + if(DEBUG){ qDebug() << "XCB: WindowVisibleIconName()"; } + if(win==0){ return ""; } + QString out; + xcb_get_property_cookie_t cookie = xcb_ewmh_get_wm_visible_icon_name_unchecked(&EWMH, win); + if(cookie.sequence == 0){ return out; } + xcb_ewmh_get_utf8_strings_reply_t data; + if( 1 == xcb_ewmh_get_wm_visible_icon_name_reply(&EWMH, cookie, &data, NULL) ){ + out = QString::fromUtf8(data.strings, data.strings_len); + } + return out; +} + +// === WindowIconName() === +QString LXCB::WindowIconName(WId win){ //_NET_WM_ICON_NAME + if(DEBUG){ qDebug() << "XCB: WindowIconName()"; } + if(win==0){ return ""; } + QString out; + xcb_get_property_cookie_t cookie = xcb_ewmh_get_wm_icon_name_unchecked(&EWMH, win); + if(cookie.sequence == 0){ return out; } + xcb_ewmh_get_utf8_strings_reply_t data; + if( 1 == xcb_ewmh_get_wm_icon_name_reply(&EWMH, cookie, &data, NULL) ){ + out = QString::fromUtf8(data.strings, data.strings_len); + } + return out; +} + +// === WindowVisibleName() === +QString LXCB::WindowVisibleName(WId win){ //_NET_WM_VISIBLE_NAME + if(DEBUG){ qDebug() << "XCB: WindowVisibleName()"; } + if(win==0){ return ""; } + QString out; + xcb_get_property_cookie_t cookie = xcb_ewmh_get_wm_visible_name_unchecked(&EWMH, win); + if(cookie.sequence == 0){ return out; } + xcb_ewmh_get_utf8_strings_reply_t data; + if( 1 == xcb_ewmh_get_wm_visible_name_reply(&EWMH, cookie, &data, NULL) ){ + out = QString::fromUtf8(data.strings, data.strings_len); + } + return out; +} + +// === WindowName() === +QString LXCB::WindowName(WId win){ //_NET_WM_NAME + if(DEBUG){ qDebug() << "XCB: WindowName()"; } + if(win==0){ return ""; } + QString out; + xcb_get_property_cookie_t cookie = xcb_ewmh_get_wm_name_unchecked(&EWMH, win); + if(cookie.sequence == 0){ return out; } + xcb_ewmh_get_utf8_strings_reply_t data; + if( 1 == xcb_ewmh_get_wm_name_reply(&EWMH, cookie, &data, NULL) ){ + out = QString::fromUtf8(data.strings, data.strings_len); + } + return out; +} + +// === OldWindowName() === +QString LXCB::OldWindowName(WId win){ //WM_NAME (old standard) + if(DEBUG){ qDebug() << "XCB: OldWindowName()"; } + if(win==0){ return ""; } + xcb_get_property_cookie_t cookie = xcb_icccm_get_wm_name_unchecked(QX11Info::connection(), win); + xcb_icccm_get_text_property_reply_t reply; + if(1 == xcb_icccm_get_wm_name_reply(QX11Info::connection(), cookie, &reply, NULL) ){ + QString name = QString::fromLocal8Bit(reply.name); + xcb_icccm_get_text_property_reply_wipe(&reply); + return name; + }else{ + return ""; + } +} + +// === OldWindowIconName() === +QString LXCB::OldWindowIconName(WId win){ //WM_ICON_NAME (old standard) + if(DEBUG){ qDebug() << "XCB: OldWindowIconName()"; } + if(win==0){ return ""; } + xcb_get_property_cookie_t cookie = xcb_icccm_get_wm_icon_name_unchecked(QX11Info::connection(), win); + xcb_icccm_get_text_property_reply_t reply; + if(1 == xcb_icccm_get_wm_icon_name_reply(QX11Info::connection(), cookie, &reply, NULL) ){ + QString name = QString::fromLocal8Bit(reply.name); + xcb_icccm_get_text_property_reply_wipe(&reply); + return name; + }else{ + return ""; + } +} + +// === WindowIsMaximized() === +bool LXCB::WindowIsMaximized(WId win){ + if(DEBUG){ qDebug() << "XCB: WindowIsMaximized()"; } + if(win==0){ return ""; } + //See if the _NET_WM_STATE_MAXIMIZED_[VERT/HORZ] flags are set on the window + xcb_get_property_cookie_t cookie = xcb_ewmh_get_wm_state_unchecked(&EWMH, win); + if(cookie.sequence == 0){ return false; } + xcb_ewmh_get_atoms_reply_t states; + if( 1 == xcb_ewmh_get_wm_state_reply(&EWMH, cookie, &states, NULL) ){ + //Loop over the states + for(unsigned int i=0; i<states.atoms_len; i++){ + if(states.atoms[i] == EWMH._NET_WM_STATE_MAXIMIZED_HORZ \ + || states.atoms[i] == EWMH._NET_WM_STATE_MAXIMIZED_VERT ){ + return true; + } + } + } + return false; +} + +// === WindowIsFullscreen() === +int LXCB::WindowIsFullscreen(WId win){ + if(DEBUG){ qDebug() << "XCB: WindowIsFullscreen()"; } + if(win==0){ return -1; } + //bool fullS = false; + //See if the _NET_WM_STATE_FULLSCREEN flag is set on the window + /*xcb_get_property_cookie_t cookie = xcb_ewmh_get_wm_state_unchecked(&EWMH, win); + if(cookie.sequence == 0){ return false; } + xcb_ewmh_get_atoms_reply_t states; + if( 1 == xcb_ewmh_get_wm_state_reply(&EWMH, cookie, &states, NULL) ){ + //Loop over the states + for(unsigned int i=0; i<states.atoms_len; i++){ + if(states.atoms[i] == EWMH._NET_WM_STATE_FULLSCREEN){ + fullS = true; + break; + } + } + }*/ + //if(!fullS){ + //Fallback check for windows which are painted above everything else + // but don't have the FULLSCREEN flag set (even though they are technically full-screen) + int fscreen = -1; + //qDebug() << "FALLBACK FULLSCREEN CHECK:"; + QRect geom = LXCB::WindowGeometry(win, false); + QDesktopWidget *desk = QApplication::desktop(); + for(int i=0; i<desk->screenCount(); i++){ + QRect sgeom = desk->screenGeometry(i); + qDebug() << " -- Check Window Geom:" << sgeom << geom << this->WindowClass(win); + if( sgeom.contains(geom.center()) ){ + //Allow a 1 pixel variation in "full-screen" detection + qDebug() << " -- Found Screen:" << i; + if( geom.width() >= (sgeom.width()-1) && geom.height()>=(sgeom.height()-1) ){ + qDebug() << " -- Is Fullscreen!"; + //fullS = true; + fscreen = i; + } + break; //found the screen which contains this window + } + } + //} + //return fullS; + return fscreen; +} + +// === WindowIcon() === +QIcon LXCB::WindowIcon(WId win){ + //Fetch the _NET_WM_ICON for the window and return it as a QIcon + if(DEBUG){ qDebug() << "XCB: WindowIcon()"; } + QIcon icon; + if(win==0){ return icon; } + xcb_get_property_cookie_t cookie = xcb_ewmh_get_wm_icon_unchecked(&EWMH, win); + xcb_ewmh_get_wm_icon_reply_t reply; + if(1 == xcb_ewmh_get_wm_icon_reply(&EWMH, cookie, &reply, NULL)){ + xcb_ewmh_wm_icon_iterator_t iter = xcb_ewmh_get_wm_icon_iterator(&reply); + //Just use the first + bool done =false; + while(!done){ + //Now convert the current data into a Qt image + // - first 2 elements are width and height (removed via XCB functions) + // - data in rows from left to right and top to bottom + QImage image(iter.width, iter.height, QImage::Format_ARGB32); //initial setup + uint* dat = iter.data; + //dat+=2; //remember the first 2 element offset + for(int i=0; i<image.byteCount()/4; ++i, ++dat){ + ((uint*)image.bits())[i] = *dat; + } + icon.addPixmap(QPixmap::fromImage(image)); //layer this pixmap onto the icon + //Now see if there are any more icons available + done = (iter.rem<1); //number of icons remaining + if(!done){ xcb_ewmh_get_wm_icon_next(&iter); } //get the next icon data + } + xcb_ewmh_get_wm_icon_reply_wipe(&reply); + } + return icon; +} + +// === SelectInput() === +void LXCB::SelectInput(WId win, bool isEmbed){ + uint32_t mask; + if(isEmbed){ + mask = XCB_EVENT_MASK_ENTER_WINDOW | XCB_EVENT_MASK_LEAVE_WINDOW | XCB_EVENT_MASK_STRUCTURE_NOTIFY | XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY | XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT | XCB_EVENT_MASK_FOCUS_CHANGE | XCB_EVENT_MASK_PROPERTY_CHANGE; + }else{ + mask = XCB_EVENT_MASK_FOCUS_CHANGE | XCB_EVENT_MASK_PROPERTY_CHANGE; + } + xcb_change_window_attributes(QX11Info::connection(), win, XCB_CW_EVENT_MASK, &mask ); +} + +// === GenerateDamageID() === +uint LXCB::GenerateDamageID(WId win){ + //Now create/register the damage handler + xcb_damage_damage_t dmgID = xcb_generate_id(QX11Info::connection()); //This is a typedef for a 32-bit unsigned integer + xcb_damage_create(QX11Info::connection(), dmgID, win, XCB_DAMAGE_REPORT_LEVEL_RAW_RECTANGLES); + return ( (uint) dmgID ); +} + + +// === SetAsSticky() === +void LXCB::SetAsSticky(WId win){ + if(DEBUG){ qDebug() << "XCB: SetAsSticky()"; } + if(win==0){ return; } + //Need to send a client message event for the window so the WM picks it up + xcb_client_message_event_t event; + event.response_type = XCB_CLIENT_MESSAGE; + event.format = 32; + event.window = win; + event.type = EWMH._NET_WM_STATE; + event.data.data32[0] = 1; //set to enabled + event.data.data32[1] = EWMH._NET_WM_STATE_STICKY; + event.data.data32[2] = 0; + event.data.data32[3] = 0; + event.data.data32[4] = 0; + + xcb_send_event(QX11Info::connection(), 0, QX11Info::appRootWindow(), XCB_EVENT_MASK_STRUCTURE_NOTIFY | XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT, (const char *) &event); + + //This method changes the property on the window directly - the WM is not aware of it + /*xcb_change_property( QX11Info::connection(), XCB_PROP_MODE_APPEND, win, EWMH._NET_WM_STATE, XCB_ATOM_ATOM, 32, 1, &(EWMH._NET_WM_STATE_STICKY) ); + xcb_flush(QX11Info::connection()); //apply it right away*/ +} + +// === SetDisableWMActions() === +void LXCB::SetDisableWMActions(WId win){ + if(DEBUG){ qDebug() << "XCB: SetDisableWMActions()"; } + //This disables all the various control that a WM allows for the window (except for allowing the "Sticky" state) + xcb_atom_t list[1]; + list[0] = EWMH._NET_WM_ACTION_STICK; + xcb_ewmh_set_wm_allowed_actions(&EWMH, win, 1, list); +} + +// === SetAsPanel() === +void LXCB::SetAsPanel(WId win){ + if(DEBUG){ qDebug() << "XCB: SetAsPanel()"; } + if(win==0){ return; } + SetDisableWMActions(win); //also need to disable WM actions for this window + //Disable Input focus (panel activation ruins task manager window detection routines) + // - Disable Input flag in WM_HINTS + xcb_icccm_wm_hints_t hints; + //qDebug() << " - Disable WM_HINTS input flag"; + xcb_get_property_cookie_t cookie = xcb_icccm_get_wm_hints_unchecked(QX11Info::connection(), win); + //qDebug() << " -- got cookie"; + if(1 == xcb_icccm_get_wm_hints_reply(QX11Info::connection(), cookie, &hints, NULL) ){ + //qDebug() << " -- Set no inputs flag"; + xcb_icccm_wm_hints_set_input(&hints, false); //set no input focus + xcb_icccm_set_wm_hints(QX11Info::connection(), win, &hints); //save hints back to window + } + // - Remove WM_TAKE_FOCUS from the WM_PROTOCOLS for the window + // - - Generate the necessary atoms + //qDebug() << " - Generate WM_PROTOCOLS and WM_TAKE_FOCUS atoms"; + xcb_atom_t WM_PROTOCOLS, WM_TAKE_FOCUS; //the two atoms needed + xcb_intern_atom_reply_t *preply = xcb_intern_atom_reply(QX11Info::connection(), \ + xcb_intern_atom(QX11Info::connection(), 0, 12, "WM_PROTOCOLS"), NULL); + xcb_intern_atom_reply_t *freply = xcb_intern_atom_reply(QX11Info::connection(), \ + xcb_intern_atom(QX11Info::connection(), 0, 13, "WM_TAKE_FOCUS"), NULL); + bool gotatoms = false; + if(preply && freply){ + WM_PROTOCOLS = preply->atom; + WM_TAKE_FOCUS = freply->atom; + free(preply); + free(freply); + gotatoms = true; + //qDebug() << " -- success"; + } + // - - Now update the protocols for the window + if(gotatoms){ //requires the atoms + //qDebug() << " - Get WM_PROTOCOLS"; + xcb_icccm_get_wm_protocols_reply_t proto; + if( 1 == xcb_icccm_get_wm_protocols_reply(QX11Info::connection(), \ + xcb_icccm_get_wm_protocols_unchecked(QX11Info::connection(), win, WM_PROTOCOLS), \ + &proto, NULL) ){ + + //Found the current protocols, see if it has the focus atom set + //remove the take focus atom and re-save them + bool needremove = false; + //Note: This first loop is required so that we can initialize the modified list with a valid size + //qDebug() << " -- Check current protocols"; + for(unsigned int i=0; i<proto.atoms_len; i++){ + if(proto.atoms[i] == WM_TAKE_FOCUS){ needremove = true; break;} + } + if(needremove){ + //qDebug() << " -- Remove WM_TAKE_FOCUS protocol"; + xcb_atom_t *protolist = new xcb_atom_t[proto.atoms_len-1]; + int num = 0; + for(unsigned int i=0; i<proto.atoms_len; i++){ + if(proto.atoms[i] != WM_TAKE_FOCUS){ + protolist[num] = proto.atoms[i]; + num++; + } + } + //qDebug() << " -- Re-save modified protocols"; + xcb_icccm_set_wm_protocols(QX11Info::connection(), win, WM_PROTOCOLS, num, protolist); + } + //qDebug() << " -- Clear protocols reply"; + xcb_icccm_get_wm_protocols_reply_wipe(&proto); + }//end of get protocols check + } //end of gotatoms check + //Make sure it has the "dock" window type + // - get the current window types (Not necessary, only 1 type of window needed) + + // - set the adjusted window type(s) + //qDebug() << " - Adjust window type"; + xcb_atom_t list[1]; + list[0] = EWMH._NET_WM_WINDOW_TYPE_DOCK; + xcb_ewmh_set_wm_window_type(&EWMH, win, 1, list); + + //Make sure it is on all workspaces + //qDebug() << " - Set window as sticky"; + SetAsSticky(win); + +} + +// === SetAsDesktop() === +void LXCB::SetAsDesktop(WId win){ + if(DEBUG){ qDebug() << "XCB: SetAsDesktop()"; } + if(win==0){ return; } + SetDisableWMActions(win); //also need to disable WM actions for this window + xcb_atom_t list[1]; + list[0] = EWMH._NET_WM_WINDOW_TYPE_DESKTOP; + xcb_ewmh_set_wm_window_type(&EWMH, win, 1, list); +} + +// === CloseWindow() === +void LXCB::CloseWindow(WId win){ + if(DEBUG){ qDebug() << "XCB: CloseWindow()"; } + if(win==0){ return; } + //This will close the specified window (might not close the entire application) + xcb_ewmh_request_close_window(&EWMH, 0, win, QX11Info::getTimestamp(), XCB_EWMH_CLIENT_SOURCE_TYPE_OTHER); +} + +// === KillClient() === +void LXCB::KillClient(WId win){ + if(DEBUG){ qDebug() << "XCB: KillClient()"; } + if(win==0){ return; } + //This will forcibly close the application which created WIN + xcb_kill_client(QX11Info::connection(), win); +} + +// === MinimizeWindow() === +void LXCB::MinimizeWindow(WId win){ //request that the window be unmapped/minimized + if(DEBUG){ qDebug() << "XCB: MinimizeWindow()"; } + if(win==0){ return; } + //Note: Fluxbox completely removes this window from the open list if unmapped manually + // xcb_unmap_window(QX11Info::connection(), win); + //xcb_flush(QX11Info::connection()); //make sure the command is sent out right away + + //Need to send a client message event for the window so the WM picks it up + xcb_client_message_event_t event; + event.response_type = XCB_CLIENT_MESSAGE; + event.format = 32; + event.window = win; + event.type = EWMH._NET_WM_STATE; + event.data.data32[0] = 1; //set to toggle (switch back and forth) + event.data.data32[1] = EWMH._NET_WM_STATE_HIDDEN; + event.data.data32[2] = 0; + event.data.data32[3] = 0; + event.data.data32[4] = 0; + + xcb_send_event(QX11Info::connection(), 0, QX11Info::appRootWindow(), XCB_EVENT_MASK_STRUCTURE_NOTIFY | XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT, (const char *) &event); +} + +// === ActivateWindow() === +void LXCB::ActivateWindow(WId win){ //request that the window become active + if(DEBUG){ qDebug() << "XCB: ActivateWindow();"; } + if(win==0){ return; } + //First need to get the currently active window + xcb_get_property_cookie_t cookie = xcb_ewmh_get_active_window_unchecked(&EWMH, 0); + xcb_window_t actwin; + if(1 != xcb_ewmh_get_active_window_reply(&EWMH, cookie, &actwin, NULL) ){ + actwin = 0; + } + if(actwin == win){ return; } //requested window is already active + +//Need to send a client message event for the window so the WM picks it up + xcb_client_message_event_t event; + event.response_type = XCB_CLIENT_MESSAGE; + event.format = 32; + event.window = win; //window to activate + event.type = EWMH._NET_ACTIVE_WINDOW; + event.data.data32[0] = 2; //pager/direct user interaction + event.data.data32[1] = QX11Info::getTimestamp(); //current timestamp + event.data.data32[2] = actwin; //currently active window (0 if none) + event.data.data32[3] = 0; + event.data.data32[4] = 0; + + xcb_send_event(QX11Info::connection(), 0, QX11Info::appRootWindow(), XCB_EVENT_MASK_STRUCTURE_NOTIFY | XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT, (const char *) &event); + +} + +// ===== RestoreWindow() ===== +void LXCB::RestoreWindow(WId win){ + uint32_t val = XCB_STACK_MODE_ABOVE; + xcb_configure_window(QX11Info::connection(), win, XCB_CONFIG_WINDOW_STACK_MODE, &val); //raise it + xcb_map_window(QX11Info::connection(), win); //map it +} + +// === MaximizeWindow() === +void LXCB::MaximizeWindow(WId win, bool flagsonly){ //request that the window become maximized + if(DEBUG){ qDebug() << "XCB: MaximizeWindow()"; } + if(win==0){ return; } + if(flagsonly){ + //Directly set the flags on the window (bypassing the WM) + xcb_atom_t list[2]; + list[0] = EWMH._NET_WM_STATE_MAXIMIZED_VERT; + list[1] = EWMH._NET_WM_STATE_MAXIMIZED_HORZ; + xcb_ewmh_set_wm_state(&EWMH, win, 2, list); + + }else{ + //Need to send a client message event for the window so the WM picks it up + xcb_client_message_event_t event; + event.response_type = XCB_CLIENT_MESSAGE; + event.format = 32; + event.window = win; + event.type = EWMH._NET_WM_STATE; + event.data.data32[0] = 2; //set to toggle (switch back and forth) + event.data.data32[1] = EWMH._NET_WM_STATE_MAXIMIZED_VERT; + event.data.data32[2] = EWMH._NET_WM_STATE_MAXIMIZED_HORZ; + event.data.data32[3] = 0; + event.data.data32[4] = 0; + + xcb_send_event(QX11Info::connection(), 0, QX11Info::appRootWindow(), XCB_EVENT_MASK_STRUCTURE_NOTIFY | XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT, (const char *) &event); + } +} + +// === MoveResizeWindow() === +void LXCB::MoveResizeWindow(WId win, QRect geom){ + if(DEBUG){ qDebug() << "XCB: MoveResizeWindow()"; } + if(win==0){ return; } + //NOTE: geom needs to be in root/absolute coordinates! + //qDebug() << "MoveResize Window:" << geom.x() << geom.y() << geom.width() << geom.height(); + + //Move the window + /*xcb_ewmh_request_moveresize_window(&EWMH, 0, win, XCB_GRAVITY_STATIC, XCB_EWMH_CLIENT_SOURCE_TYPE_OTHER, \ + XCB_EWMH_MOVERESIZE_WINDOW_X | XCB_EWMH_MOVERESIZE_WINDOW_Y | XCB_MOVERESIZE_WINDOW_WIDTH | XCB_MOVERESIZE_WINDOW_HEIGHT, \ + geom.x(), geom.y(), geom.width(), geom.height());*/ + + //Use the basic XCB functions instead of ewmh (Issues with combining the XCB_EWMH_MOVERESIZE _*flags) + uint32_t values[4]; + values[0] = geom.x(); values[1] = geom.y(); + values[2] = geom.width(); values[3] = geom.height(); + xcb_configure_window(QX11Info::connection(), win, XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y | XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT, values); + +} + +// ===== ResizeWindow() ===== +void LXCB::ResizeWindow(WId win, int width, int height){ + //Use the basic XCB functions instead of ewmh + uint32_t values[] = {width, height}; + xcb_configure_window(QX11Info::connection(), win, XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT, values); +} + +// === ReserveLocation === +void LXCB::ReserveLocation(WId win, QRect geom, QString loc){ + loc = loc.toLower().simplified(); + //Put the values in the proper structures + xcb_ewmh_wm_strut_partial_t LOC; + //Initialize the structure to zeros + LOC.left = LOC.right = LOC.top = LOC.bottom = 0; //initial setting + LOC.left_start_y = LOC.left_end_y = LOC.right_start_y = LOC.right_end_y = 0; + LOC.top_start_x = LOC.top_end_x = LOC.bottom_start_x = LOC.bottom_end_x = 0; + //Now put the values into the structure based on location + if(loc=="top"){ + //top of screen + LOC.top = geom.height(); //top width + LOC.top_start_x = geom.x(); //top x start + LOC.top_end_x = geom.x()+geom.width(); //top x end + }else if(loc=="bottom"){ + //bottom of screen + LOC.bottom = geom.height(); //bottom width + LOC.bottom_start_x = geom.x(); //bottom x start + LOC.bottom_end_x = geom.x()+geom.width(); //bottom x end + }else if(loc=="left"){ + LOC.left = geom.width(); + LOC.left_start_y = geom.y(); + LOC.left_end_y = geom.y()+geom.height(); + }else{ //right + LOC.right = geom.width(); + LOC.right_start_y = geom.y(); + LOC.right_end_y = geom.y()+geom.height(); + } + + //Change the property + xcb_ewmh_set_wm_strut_partial(&EWMH, win, LOC); //_NET_WM_STRUT_PARTIAL (not always used) + xcb_ewmh_set_wm_strut(&EWMH, win, LOC.left, LOC.right, LOC.top, LOC.bottom); //_NET_WM_STRUT +} + +/*void LXCB::SetWindowBackground(QWidget *parent, QRect area, WId client){ + //Copy the image from the parent onto the client (parent/child - for system tray apps) + //First create the background graphics context + //qDebug() << "Create graphics context"; + //xcb_screen_t *root_screen = xcb_aux_get_screen(QX11Info::connection(), QX11Info::appScreen()); + uint32_t val = XCB_GX_CLEAR; + xcb_gcontext_t graphic_context = xcb_generate_id(QX11Info::connection()); + xcb_create_gc(QX11Info::connection(), graphic_context, client, XCB_GC_BACKGROUND | XCB_GC_FOREGROUND, &val); + //qDebug() << "Copy Background Area"; + //Now copy the image onto the client background + xcb_copy_area(QX11Info::connection(), + parent->winId(), + client, + graphic_context, + area.x(), area.y(), + 0, 0, + area.width(), area.height()); + //Now re-map the client so it paints on top of the new background + //qDebug() << "Map Window"; + //xcb_map_window(QX11Info::connection(), client); + //Clean up variables + xcb_free_gc(QX11Info::connection(), graphic_context); +}*/ + +// === EmbedWindow() === +uint LXCB::EmbedWindow(WId win, WId container){ + if(DEBUG){ qDebug() << "XCB: EmbedWindow()"; } + //This returns the damage control ID number (or 0 for a failure) + if(win==0 || container==0 || LXCB::WindowClass(win).isEmpty() ){ return 0; } //invalid window (destroyed before getting here?) + //qDebug() << "Embed Window:" << win << container; + + //Initialize any atoms that will be needed + xcb_intern_atom_cookie_t ecookie = xcb_intern_atom_unchecked(QX11Info::connection(), 0, 7, "_XEMBED"); + + xcb_intern_atom_reply_t *ereply = xcb_intern_atom_reply(QX11Info::connection(), ecookie, NULL); + if(ereply==0){ return 0; } //unable to initialize the atom + xcb_atom_t emb = ereply->atom; + free(ereply); //done with this structure + + //Reparent the window into the container + xcb_reparent_window(QX11Info::connection(), win, container, 0, 0); + xcb_map_window(QX11Info::connection(), win); + + //Now send the embed event to the app + //qDebug() << " - send _XEMBED event"; + xcb_client_message_event_t event; + event.response_type = XCB_CLIENT_MESSAGE; + event.format = 32; + event.window = win; + event.type = emb; //_XEMBED + event.data.data32[0] = XCB_TIME_CURRENT_TIME; //CurrentTime; + event.data.data32[1] = 0; //XEMBED_EMBEDDED_NOTIFY + event.data.data32[2] = 0; + event.data.data32[3] = container; //WID of the container + event.data.data32[4] = 0; + + xcb_send_event(QX11Info::connection(), 0, win, XCB_EVENT_MASK_STRUCTURE_NOTIFY | XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT, (const char *) &event); + + //Now setup any redirects and return + this->SelectInput(win, true); //Notify of structure changes + xcb_composite_redirect_window(QX11Info::connection(), win, XCB_COMPOSITE_REDIRECT_MANUAL); //XCB_COMPOSITE_REDIRECT_[MANUAL/AUTOMATIC]); + + //Now map the window (will be a transparent child of the container) + xcb_map_window(QX11Info::connection(), win); + + //Now create/register the damage handler + // -- XCB (Note: The XCB damage registration is completely broken at the moment - 9/15/15, Ken Moore) + //xcb_damage_damage_t dmgID = xcb_generate_id(QX11Info::connection()); //This is a typedef for a 32-bit unsigned integer + //xcb_damage_create(QX11Info::connection(), dmgID, win, XCB_DAMAGE_REPORT_LEVEL_RAW_RECTANGLES); + // -- XLib (Note: This is only used because the XCB routine above does not work - needs to be fixed upstream in XCB itself). + Damage dmgID = XDamageCreate(QX11Info::display(), win, XDamageReportRawRectangles); + + //qDebug() << " - Done"; + return ( (uint) dmgID ); +} + +// === Unembed Window() === +bool LXCB::UnembedWindow(WId win){ + if(DEBUG){ qDebug() << "XCB: UnembedWindow()"; } + if(win==0){ return false; } + //Remove redirects + uint32_t val[] = {XCB_EVENT_MASK_NO_EVENT}; + xcb_change_window_attributes(QX11Info::connection(), win, XCB_CW_EVENT_MASK, val); + //Make sure it is invisible + xcb_unmap_window(QX11Info::connection(), win); + //Reparent the window back to the root window + xcb_reparent_window(QX11Info::connection(), win, QX11Info::appRootWindow(), 0, 0); + return true; +} + +// === TrayImage() === +QPixmap LXCB::TrayImage(WId win){ + QPixmap pix; + + //Get the current QScreen (for XCB->Qt conversion) + QList<QScreen*> scrnlist = QApplication::screens(); + if(scrnlist.isEmpty()){ return pix; } + + //Try to grab the given window directly with Qt + if(pix.isNull()){ + pix = scrnlist[0]->grabWindow(win); + } + return pix; + + //NOTE: Code below here saved for reference later (as necessary) + // ------------------------------- + /*//First get the pixmap from the XCB compositing layer (since the tray images are redirected there) + xcb_pixmap_t pixmap = xcb_generate_id(QX11Info::connection()); + xcb_composite_name_window_pixmap(QX11Info::connection(), win, pixmap); + //Get the sizing information about the pixmap + xcb_get_geometry_cookie_t Gcookie = xcb_get_geometry_unchecked(QX11Info::connection(), pixmap); + xcb_get_geometry_reply_t *Greply = xcb_get_geometry_reply(QX11Info::connection(), Gcookie, NULL); + if(Greply==0){ qDebug() << "[Tray Image] - Geom Fetch Error:"; return QPixmap(); } //Error in geometry detection + + //Now convert the XCB pixmap into an XCB image + xcb_get_image_cookie_t GIcookie = xcb_get_image_unchecked(QX11Info::connection(), XCB_IMAGE_FORMAT_Z_PIXMAP, pixmap, 0, 0, Greply->width, Greply->height, 0xffffffff); + xcb_get_image_reply_t *GIreply = xcb_get_image_reply(QX11Info::connection(), GIcookie, NULL); + if(GIreply==0){ qDebug() << "[Tray Image] - Image Convert Error:"; return QPixmap(); } //Error in conversion + uint8_t *GIdata = xcb_get_image_data(GIreply); + uint32_t BPL = xcb_get_image_data_length(GIreply) / Greply->height; //bytes per line + //Now convert the XCB image into a Qt Image + QImage image(const_cast<uint8_t *>(GIdata), Greply->width, Greply->height, BPL, QImage::Format_ARGB32_Premultiplied); + //Free the various data structures + free(GIreply); //done with get image reply + xcb_free_pixmap(QX11Info::connection(), pixmap); //done with the raw pixmap + free(Greply); //done with geom reply*/ + + /* NOTE: Found these little bit in the Qt sources - not sure if it is needed, but keep it here for reference + // we may have to swap the byte order based on system type + uint8_t image_byte_order = connection->setup()->image_byte_order; + if ((QSysInfo::ByteOrder == QSysInfo::LittleEndian && image_byte_order == XCB_IMAGE_ORDER_MSB_FIRST) + || (QSysInfo::ByteOrder == QSysInfo::BigEndian && image_byte_order == XCB_IMAGE_ORDER_LSB_FIRST)) + { + for (int i=0; i < image.height(); i++) { + uint *p = (uint*)image.scanLine(i); + uint *end = p + image.width(); + while (p < end) { + *p = ((*p << 24) & 0xff000000) | ((*p << 8) & 0x00ff0000) + | ((*p >> 8) & 0x0000ff00) | ((*p >> 24) & 0x000000ff); + p++; + } + } + }*/ + + // fix-up alpha channel + /*if (image.format() == QImage::Format_RGB32) { + QRgb *p = (QRgb *)image.bits(); + for (int y = 0; y < height; ++y) { + for (int x = 0; x < width; ++x) + p[x] |= 0xff000000; + p += bytes_per_line / 4; + }*/ + + //Convert the QImage into a QPixmap and return it + //return QPixmap::fromImage(image.copy()); +} + +// ===== startSystemTray() ===== +WId LXCB::startSystemTray(int screen){ + qDebug() << "Starting System Tray:" << screen; + //Setup the freedesktop standards compliance + + //Get the appropriate atom for this screen + QString str = QString("_NET_SYSTEM_TRAY_S%1").arg(QString::number(screen)); + //qDebug() << "Default Screen Atom Name:" << str; + xcb_intern_atom_reply_t *treply = xcb_intern_atom_reply(QX11Info::connection(), \ + xcb_intern_atom(QX11Info::connection(), 0, str.length(), str.toLocal8Bit()), NULL); + xcb_intern_atom_reply_t *oreply = xcb_intern_atom_reply(QX11Info::connection(), \ + xcb_intern_atom(QX11Info::connection(), 0, 28, "_NET_SYSTEM_TRAY_ORIENTATION"), NULL); + xcb_intern_atom_reply_t *vreply = xcb_intern_atom_reply(QX11Info::connection(), \ + xcb_intern_atom(QX11Info::connection(), 0, 23, "_NET_SYSTEM_TRAY_VISUAL"), NULL); + if(treply==0){ + qDebug() << " - ERROR: Could not initialize _NET_SYSTEM_TRAY_S<num> atom"; + return 0; + } + if(oreply==0){ + qDebug() << " - ERROR: Could not initialize _NET_SYSTEM_TRAY_ORIENTATION atom"; + return 0; + } + if(vreply==0){ + qDebug() << " - ERROR: Could not initialize _NET_SYSTEM_TRAY_VISUAL atom"; + return 0; + } + xcb_atom_t _NET_SYSTEM_TRAY_S = treply->atom; + xcb_atom_t _NET_SYSTEM_TRAY_ORIENTATION = oreply->atom; + xcb_atom_t _NET_SYSTEM_TRAY_VISUAL = vreply->atom; + free(treply); //done with atom generation + free(oreply); + free(vreply); + + //Make sure that there is no other system tray running + xcb_get_selection_owner_reply_t *ownreply = xcb_get_selection_owner_reply(QX11Info::connection(), \ + xcb_get_selection_owner_unchecked(QX11Info::connection(), _NET_SYSTEM_TRAY_S), NULL); + if(ownreply==0){ + qWarning() << " - Could not get owner selection reply"; + return 0; + } + if(ownreply->owner != 0){ + free(ownreply); + qWarning() << " - An alternate system tray is currently in use"; + return 0; + } + free(ownreply); + + //Create a simple window to register as the tray (not visible - just off the screen) + xcb_screen_t *root_screen = xcb_aux_get_screen(QX11Info::connection(), QX11Info::appScreen()); + uint32_t params[] = {1}; + WId LuminaSessionTrayID = xcb_generate_id(QX11Info::connection()); //need a new ID + xcb_create_window(QX11Info::connection(), root_screen->root_depth, \ + LuminaSessionTrayID, root_screen->root, -1, -1, 1, 1, 0, \ + XCB_WINDOW_CLASS_INPUT_OUTPUT, root_screen->root_visual, \ + XCB_CW_OVERRIDE_REDIRECT, params); + + //Now register this widget as the system tray + xcb_set_selection_owner(QX11Info::connection(), LuminaSessionTrayID, _NET_SYSTEM_TRAY_S, XCB_CURRENT_TIME); + //Make sure that it was registered properly + ownreply = xcb_get_selection_owner_reply(QX11Info::connection(), \ + xcb_get_selection_owner_unchecked(QX11Info::connection(), _NET_SYSTEM_TRAY_S), NULL); + + if(ownreply==0 || ownreply->owner != LuminaSessionTrayID){ + if(ownreply!=0){ free(ownreply); } + qWarning() << " - Could not register the system tray"; + xcb_destroy_window(QX11Info::connection(), LuminaSessionTrayID); + return 0; + } + free(ownreply); //done with structure + + //Now register the orientation of the system tray + uint32_t orient = _NET_SYSTEM_TRAY_ORIENTATION_HORZ; + xcb_change_property(QX11Info::connection(), XCB_PROP_MODE_REPLACE, LuminaSessionTrayID, \ + _NET_SYSTEM_TRAY_ORIENTATION, XCB_ATOM_CARDINAL, 32, 1, &orient); + + //Now set the visual ID for the system tray (same as the root window, but TrueColor) + xcb_visualtype_t *type = xcb_aux_find_visual_by_attrs(root_screen, XCB_VISUAL_CLASS_TRUE_COLOR, 32); + if(type!=0){ + xcb_change_property(QX11Info::connection(), XCB_PROP_MODE_REPLACE, LuminaSessionTrayID, \ + _NET_SYSTEM_TRAY_VISUAL, XCB_ATOM_VISUALID, 32, 1, &type->visual_id); + }else{ + qWarning() << " - Could not set TrueColor visual for system tray"; + } + + //Finally, send out an X event letting others know that the system tray is up and running + xcb_client_message_event_t event; + event.response_type = XCB_CLIENT_MESSAGE; + event.format = 32; + event.window = root_screen->root; + event.type = EWMH.MANAGER; //MANAGER atom + event.data.data32[0] = XCB_TIME_CURRENT_TIME; //CurrentTime; + event.data.data32[1] = _NET_SYSTEM_TRAY_S; //_NET_SYSTEM_TRAY_S atom + event.data.data32[2] = LuminaSessionTrayID; + event.data.data32[3] = 0; + event.data.data32[4] = 0; + + xcb_send_event(QX11Info::connection(), 0, root_screen->root, XCB_EVENT_MASK_STRUCTURE_NOTIFY | XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT, (const char *) &event); + + //Success + return LuminaSessionTrayID; +} + +// ===== closeSystemTray() ===== +void LXCB::closeSystemTray(WId trayID){ + xcb_destroy_window(QX11Info::connection(), trayID); +} + +// === SetScreenWorkArea() === +/*void LXCB::SetScreenWorkArea(unsigned int screen, QRect rect){ + //This is only useful because Fluxbox does not set the _NET_WORKAREA root atom + // This needs to be better incorporated into the new window manager later + + //First get the current workarea array (for all monitors/screens) + xcb_get_property_cookie_t cookie = xcb_ewmh_get_workarea_unchecked(&EWMH, 0); + xcb_ewmh_get_workarea_reply_t work; + if(0==xcb_ewmh_get_workarea_reply(&EWMH, cookie, &work, NULL)){ return; } //Error: Could not retrieve current work areas + //Save what we need only from the reply + unsigned int desks = work.workarea_len; + if(desks <= screen){ return; } //invalid screen to modify + qDebug() << "Number of desktops/screens:" << desks; + xcb_ewmh_geometry_t *dareas = work.workarea; + //Adjust the work area for the input monitor/screen + dareas[screen].x = rect.x(); + dareas[screen].y = rect.y(); + dareas[screen].width = rect.width(); + dareas[screen].height = rect.height(); + //Now save the array again + xcb_ewmh_set_workarea(&EWMH, 0, desks, dareas); //_NET_WORKAREA + //Make sure to clear that reply + xcb_ewmh_get_workarea_reply_wipe(&work); +}*/ + +//============ +// WM Functions (directly changing properties/settings) +// - Using these directly may prevent the WM from seeing the change +//============ +void LXCB::WM_CloseWindow(WId win, bool force){ + + if(!force){ // && WM_ICCCM_GetProtocols(win).testFlag(LXCB::DELETE_WINDOW)){ + //Send the window a WM_DELETE_WINDOW message + if(atoms.isEmpty()){ createWMAtoms(); } //need these atoms + xcb_client_message_event_t event; + event.response_type = XCB_CLIENT_MESSAGE; + event.format = 32; + event.window = win; + event.type = ATOMS[atoms.indexOf("WM_PROTOCOLS")]; + event.data.data32[0] = ATOMS[atoms.indexOf("WM_DELETE_WINDOW")]; + event.data.data32[1] = XCB_TIME_CURRENT_TIME; //CurrentTime; + event.data.data32[2] = 0; + event.data.data32[3] = 0; + event.data.data32[4] = 0; + + xcb_send_event(QX11Info::connection(), 0, win, XCB_EVENT_MASK_STRUCTURE_NOTIFY | XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT, (const char *) &event); + xcb_flush(QX11Info::connection()); + }else{ xcb_destroy_window(QX11Info::connection(), win); } + +} + +void LXCB::WM_ShowWindow(WId win){ + xcb_map_window(QX11Info::connection(), win); +} + +void LXCB::WM_HideWindow(WId win){ + xcb_unmap_window(QX11Info::connection(), win); +} + +QList<WId> LXCB::WM_RootWindows(){ + xcb_query_tree_cookie_t cookie = xcb_query_tree(QX11Info::connection(), QX11Info::appRootWindow()); + xcb_query_tree_reply_t *reply = 0; + QList<WId> out; + reply=xcb_query_tree_reply(QX11Info::connection(), cookie, NULL); + if(reply!=0){ + int num = xcb_query_tree_children_length(reply); + xcb_window_t *children = xcb_query_tree_children(reply); + for(int i=0; i<num; i++){ + if(!out.contains(children[i])){ out << children[i]; } + } + free(reply); + } + return out; +} + +WId LXCB::WM_CreateWindow(WId parent){ + if(parent ==0){ parent = QX11Info::appRootWindow(); } + xcb_screen_t *root_screen = xcb_aux_get_screen(QX11Info::connection(), QX11Info::appScreen()); + uint32_t params[] = {1}; + WId win = xcb_generate_id(QX11Info::connection()); //need a new ID + xcb_create_window(QX11Info::connection(), root_screen->root_depth, \ + win, parent, -1, -1, 1, 1, 0, \ + XCB_WINDOW_CLASS_INPUT_OUTPUT, root_screen->root_visual, \ + XCB_CW_OVERRIDE_REDIRECT, params); + return win; +} + +bool LXCB::WM_ManageWindow(WId win, bool needsmap){ +#define CLIENT_WIN_EVENT_MASK (XCB_EVENT_MASK_PROPERTY_CHANGE | \ + XCB_EVENT_MASK_STRUCTURE_NOTIFY | \ + XCB_EVENT_MASK_FOCUS_CHANGE) + //return whether the window is/should be managed + if(WM_ICCCM_GetClass(win).isEmpty() ){ return false; } + xcb_get_window_attributes_cookie_t cookie = xcb_get_window_attributes(QX11Info::connection(), win); + xcb_get_window_attributes_reply_t *attr = xcb_get_window_attributes_reply(QX11Info::connection(), cookie, NULL); + if(attr == 0){ return false; } //could not get attributes of window + if(attr->override_redirect){ free(attr); return false; } //window has override redirect set (do not manage) + if(!needsmap && attr->map_state != XCB_MAP_STATE_VIEWABLE){ + //window is never supposed to be visible (lots of these) + //if( !WM_ICCCM_GetClass(win).contains("xterm") ){ //Some windows mis-set this flag + qDebug() << " - Not Viewable.." << WM_ICCCM_GetClass(win); + free(attr); return false; + //} + } + //Setup event handling on the window + uint32_t value_list[1] = {CLIENT_WIN_EVENT_MASK}; + if( xcb_request_check(QX11Info::connection(), \ + xcb_change_window_attributes_checked(QX11Info::connection(), win, XCB_CW_EVENT_MASK, value_list ) ) ){ + //Could not change event mask - did the window get deleted already? + free(attr); + qDebug() << " - Could not change event mask"; + return false; + } + + return true; +} + +QRect LXCB::WM_Window_Geom(WId win){ + xcb_get_geometry_cookie_t cookie = xcb_get_geometry_unchecked(QX11Info::connection(), win); + xcb_get_geometry_reply_t *reply = 0; + QRect geom; + reply = xcb_get_geometry_reply(QX11Info::connection(), cookie, NULL); + if(reply!=0){ + geom = QRect(reply->x, reply->y, reply->width, reply->height); + free(reply); + } + return geom; +} + +void LXCB::setupEventsForFrame(WId frame){ + #define FRAME_WIN_EVENT_MASK (XCB_EVENT_MASK_BUTTON_PRESS | \ + XCB_EVENT_MASK_BUTTON_RELEASE | \ + XCB_EVENT_MASK_POINTER_MOTION | \ + XCB_EVENT_MASK_BUTTON_MOTION | \ + XCB_EVENT_MASK_EXPOSURE | \ + XCB_EVENT_MASK_STRUCTURE_NOTIFY | \ + XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT | \ + XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY | \ + XCB_EVENT_MASK_ENTER_WINDOW) + + uint32_t value_list[1] = {FRAME_WIN_EVENT_MASK}; + xcb_change_window_attributes(QX11Info::connection(), frame, XCB_CW_EVENT_MASK, value_list); +} + +bool LXCB::setupEventsForRoot(WId root){ + #define ROOT_WIN_EVENT_MASK (XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT | \ + XCB_EVENT_MASK_BUTTON_PRESS | \ + XCB_EVENT_MASK_STRUCTURE_NOTIFY | \ + XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT | \ + XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY | \ + XCB_EVENT_MASK_POINTER_MOTION | \ + XCB_EVENT_MASK_PROPERTY_CHANGE | \ + XCB_EVENT_MASK_FOCUS_CHANGE | \ + XCB_EVENT_MASK_ENTER_WINDOW) + + if(root==0){ root = QX11Info::appRootWindow(); } + uint32_t value_list[1] = {ROOT_WIN_EVENT_MASK}; + xcb_generic_error_t *status = xcb_request_check( QX11Info::connection(), xcb_change_window_attributes_checked(QX11Info::connection(), root, XCB_CW_EVENT_MASK, value_list)); + return (status==0); +} +// -------------------------------------------------- +// ICCCM Standards (older standards) +// -------------------------------------------------- +// -- WM_NAME +QString LXCB::WM_ICCCM_GetName(WId win){ + xcb_get_property_cookie_t cookie = xcb_icccm_get_wm_name_unchecked(QX11Info::connection(), win); + xcb_icccm_get_text_property_reply_t reply; + if(1 != xcb_icccm_get_wm_name_reply(QX11Info::connection(), cookie, &reply, NULL) ){ + return ""; //error in fetching name + }else{ + return QString::fromLocal8Bit(reply.name); + } +} + +void LXCB::WM_ICCCM_SetName(WId win, QString name){ + xcb_icccm_set_wm_name(QX11Info::connection(), win, XCB_ATOM_STRING, 8, name.length(), name.toLocal8Bit()); +} + +// -- WM_ICON_NAME +QString LXCB::WM_ICCCM_GetIconName(WId win){ + xcb_get_property_cookie_t cookie = xcb_icccm_get_wm_icon_name_unchecked(QX11Info::connection(), win); + xcb_icccm_get_text_property_reply_t reply; + if(1 != xcb_icccm_get_wm_icon_name_reply(QX11Info::connection(), cookie, &reply, NULL) ){ + return ""; //error in fetching name + }else{ + return QString::fromLocal8Bit(reply.name); + } +} + +void LXCB::WM_ICCCM_SetIconName(WId win, QString name){ + xcb_icccm_set_wm_icon_name(QX11Info::connection(), win, XCB_ATOM_STRING, 8, name.length(), name.toLocal8Bit()); +} + +// -- WM_CLIENT_MACHINE +QString LXCB::WM_ICCCM_GetClientMachine(WId win){ + xcb_get_property_cookie_t cookie = xcb_icccm_get_wm_client_machine_unchecked(QX11Info::connection(), win); + xcb_icccm_get_text_property_reply_t reply; + if(1 != xcb_icccm_get_wm_client_machine_reply(QX11Info::connection(), cookie, &reply, NULL) ){ + return ""; //error in fetching name + }else{ + return QString::fromLocal8Bit(reply.name); + } +} + +void LXCB::WM_ICCCM_SetClientMachine(WId win, QString name){ + xcb_icccm_set_wm_client_machine(QX11Info::connection(), win, XCB_ATOM_STRING, 8, name.length(), name.toLocal8Bit()); +} + +// -- WM_CLASS +QString LXCB::WM_ICCCM_GetClass(WId win){ + xcb_get_property_cookie_t cookie = xcb_icccm_get_wm_class_unchecked(QX11Info::connection(), win); + xcb_icccm_get_wm_class_reply_t reply; + if(1 != xcb_icccm_get_wm_class_reply(QX11Info::connection(), cookie, &reply, NULL) ){ + return ""; //error in fetching name + }else{ + //Returns: "<instance name>::::<class name>" + return ( QString::fromLocal8Bit(reply.instance_name)+"::::"+QString::fromLocal8Bit(reply.class_name) ); + } +} + +void LXCB::WM_ICCCM_SetClass(WId win, QString name){ + xcb_icccm_set_wm_class(QX11Info::connection(), win, name.length(), name.toLocal8Bit()); +} + +// -- WM_TRANSIENT_FOR +WId LXCB::WM_ICCCM_GetTransientFor(WId win){ + xcb_get_property_cookie_t cookie = xcb_icccm_get_wm_transient_for_unchecked(QX11Info::connection(), win); + xcb_window_t trans; + if(1!= xcb_icccm_get_wm_transient_for_reply(QX11Info::connection(), cookie, &trans, NULL) ){ + return win; //error in fetching transient window ID (or none found) + }else{ + return trans; + } +} + +void LXCB::WM_ICCCM_SetTransientFor(WId win, WId transient){ + xcb_icccm_set_wm_transient_for(QX11Info::connection(), win, transient); +} + +// -- WM_SIZE_HINTS (older property?) +icccm_size_hints LXCB::WM_ICCCM_GetSizeHints(WId win){ + //most values in structure are -1 if not set + icccm_size_hints hints; + xcb_get_property_cookie_t cookie = xcb_icccm_get_wm_size_hints_unchecked(QX11Info::connection(), win, XCB_ATOM_WM_SIZE_HINTS); + xcb_size_hints_t reply; + if(1==xcb_icccm_get_wm_size_hints_reply(QX11Info::connection(), cookie, &reply, NULL) ){ + //Now go though and move any data into the output struct + if( (reply.flags&XCB_ICCCM_SIZE_HINT_US_POSITION)==XCB_ICCCM_SIZE_HINT_US_POSITION ){ hints.x=reply.x; hints.y=reply.y; } + if( (reply.flags&XCB_ICCCM_SIZE_HINT_US_SIZE)==XCB_ICCCM_SIZE_HINT_US_SIZE ){ hints.width=reply.width; hints.height=reply.height; } + if( (reply.flags&XCB_ICCCM_SIZE_HINT_P_POSITION)==XCB_ICCCM_SIZE_HINT_P_POSITION ){ hints.x=reply.x; hints.y=reply.y; } + if( (reply.flags&XCB_ICCCM_SIZE_HINT_P_SIZE)==XCB_ICCCM_SIZE_HINT_P_SIZE ){ hints.width=reply.width; hints.height=reply.height; } + if( (reply.flags&XCB_ICCCM_SIZE_HINT_P_MIN_SIZE)==XCB_ICCCM_SIZE_HINT_P_MIN_SIZE ){ hints.min_width=reply.min_width; hints.min_height=reply.min_height; } + if( (reply.flags&XCB_ICCCM_SIZE_HINT_P_MAX_SIZE)==XCB_ICCCM_SIZE_HINT_P_MAX_SIZE ){ hints.max_width=reply.max_width; hints.max_height=reply.max_height; } + if( (reply.flags&XCB_ICCCM_SIZE_HINT_P_RESIZE_INC)==XCB_ICCCM_SIZE_HINT_P_RESIZE_INC ){ hints.width_inc=reply.width_inc; hints.height_inc=reply.height_inc; } + if( (reply.flags&XCB_ICCCM_SIZE_HINT_P_ASPECT)==XCB_ICCCM_SIZE_HINT_P_ASPECT ){ hints.min_aspect_num=reply.min_aspect_num; hints.min_aspect_den=reply.min_aspect_den; hints.max_aspect_num=reply.max_aspect_num; hints.max_aspect_den=reply.max_aspect_den;} + if( (reply.flags&XCB_ICCCM_SIZE_HINT_BASE_SIZE)==XCB_ICCCM_SIZE_HINT_BASE_SIZE ){ hints.base_width=reply.base_width; hints.base_height=reply.base_height; } + if( (reply.flags&XCB_ICCCM_SIZE_HINT_P_WIN_GRAVITY)==XCB_ICCCM_SIZE_HINT_P_WIN_GRAVITY ){ hints.win_gravity=reply.win_gravity; } + //free(reply); + } + return hints; +} + +//void WM_ICCCM_SetSizeHints(WId win, icccm_size_hints hints); + +// -- WM_NORMAL_HINTS (newer property? - check for this before falling back on WM_SIZE_HINTS) +icccm_size_hints LXCB::WM_ICCCM_GetNormalHints(WId win){ +//most values in structure are -1 if not set + //most values in structure are -1 if not set + icccm_size_hints hints; + xcb_get_property_cookie_t cookie = xcb_icccm_get_wm_normal_hints_unchecked(QX11Info::connection(), win); + xcb_size_hints_t reply; + if(1==xcb_icccm_get_wm_normal_hints_reply(QX11Info::connection(), cookie, &reply, NULL) ){ + //Now go though and move any data into the output struct + if( (reply.flags&XCB_ICCCM_SIZE_HINT_US_POSITION)==XCB_ICCCM_SIZE_HINT_US_POSITION ){ hints.x=reply.x; hints.y=reply.y; } + if( (reply.flags&XCB_ICCCM_SIZE_HINT_US_SIZE)==XCB_ICCCM_SIZE_HINT_US_SIZE ){ hints.width=reply.width; hints.height=reply.height; } + if( (reply.flags&XCB_ICCCM_SIZE_HINT_P_POSITION)==XCB_ICCCM_SIZE_HINT_P_POSITION ){ hints.x=reply.x; hints.y=reply.y; } + if( (reply.flags&XCB_ICCCM_SIZE_HINT_P_SIZE)==XCB_ICCCM_SIZE_HINT_P_SIZE ){ hints.width=reply.width; hints.height=reply.height; } + if( (reply.flags&XCB_ICCCM_SIZE_HINT_P_MIN_SIZE)==XCB_ICCCM_SIZE_HINT_P_MIN_SIZE ){ hints.min_width=reply.min_width; hints.min_height=reply.min_height; } + if( (reply.flags&XCB_ICCCM_SIZE_HINT_P_MAX_SIZE)==XCB_ICCCM_SIZE_HINT_P_MAX_SIZE ){ hints.max_width=reply.max_width; hints.max_height=reply.max_height; } + if( (reply.flags&XCB_ICCCM_SIZE_HINT_P_RESIZE_INC)==XCB_ICCCM_SIZE_HINT_P_RESIZE_INC ){ hints.width_inc=reply.width_inc; hints.height_inc=reply.height_inc; } + if( (reply.flags&XCB_ICCCM_SIZE_HINT_P_ASPECT)==XCB_ICCCM_SIZE_HINT_P_ASPECT ){ hints.min_aspect_num=reply.min_aspect_num; hints.min_aspect_den=reply.min_aspect_den; hints.max_aspect_num=reply.max_aspect_num; hints.max_aspect_den=reply.max_aspect_den;} + if( (reply.flags&XCB_ICCCM_SIZE_HINT_BASE_SIZE)==XCB_ICCCM_SIZE_HINT_BASE_SIZE ){ hints.base_width=reply.base_width; hints.base_height=reply.base_height; } + if( (reply.flags&XCB_ICCCM_SIZE_HINT_P_WIN_GRAVITY)==XCB_ICCCM_SIZE_HINT_P_WIN_GRAVITY ){ hints.win_gravity=reply.win_gravity; } + //free(reply); + } + return hints; +} + +/*void LXCB::WM_ICCCM_SetNormalHints(WId win, icccm_size_hints hints){ + //Convert the data structure into the proper format + xcb_size_hints_t xhints; + if(hints.x>=0 || hints.y>=0){ xcb_icccm_size_hints_set_position(&xhints, 1, hints.x, hints.y); } + //if(hints.width>=0 + + xcb_icccm_set_wm_normal_hints(QX11Info::connection(), win, &xhints); +}*/ + +// -- WM_HINTS + +// -- WM_PROTOCOLS +LXCB::ICCCM_PROTOCOLS LXCB::WM_ICCCM_GetProtocols(WId win){ + if(atoms.isEmpty()){ createWMAtoms(); } + xcb_get_property_cookie_t cookie = xcb_icccm_get_wm_protocols(QX11Info::connection(), win, EWMH.WM_PROTOCOLS); + xcb_icccm_get_wm_protocols_reply_t reply; + LXCB::ICCCM_PROTOCOLS flags; + if(1==xcb_icccm_get_wm_protocols_reply(QX11Info::connection(), cookie, &reply, NULL) ){ + for(unsigned int i=0; i<reply.atoms_len; i++){ + if(reply.atoms[i]==ATOMS[atoms.indexOf("WM_TAKE_FOCUS")]){ flags = flags | TAKE_FOCUS; } + else if(reply.atoms[i]==ATOMS[atoms.indexOf("WM_DELETE_WINDOW")]){ flags = flags | DELETE_WINDOW; } + } + } + return flags; +} + +void LXCB::WM_ICCCM_SetProtocols(WId win, LXCB::ICCCM_PROTOCOLS flags){ + if(atoms.isEmpty()){ createWMAtoms(); } + xcb_atom_t *list; + int num; + if(flags.testFlag(TAKE_FOCUS) && flags.testFlag(DELETE_WINDOW)){ + num = 2; + list = new xcb_atom_t[2]; + list[0] = ATOMS[atoms.indexOf("WM_TAKE_FOCUS")]; + list[1] = ATOMS[atoms.indexOf("WM_DELETE_WINDOW")]; + }else if(flags.testFlag(TAKE_FOCUS)){ + num = 1; + list = new xcb_atom_t[1]; + list[0] = ATOMS[atoms.indexOf("WM_TAKE_FOCUS")]; + }else if(flags.testFlag(DELETE_WINDOW)){ + num = 1; + list = new xcb_atom_t[1]; + list[0] = ATOMS[atoms.indexOf("WM_DELETE_WINDOW")]; + }else{ + num = 0; + list = new xcb_atom_t[0]; + } + xcb_icccm_set_wm_protocols(QX11Info::connection(), win, EWMH.WM_PROTOCOLS, num, list); + +} + +// -------------------------------------------------------- +// NET_WM Standards (newer standards) +// -------------------------------------------------------- +// _NET_SUPPORTED (Root) +void LXCB::WM_Set_Root_Supported(){ + //NET_WM standards (ICCCM implied - no standard way to list those) + xcb_atom_t list[] = {}; + xcb_ewmh_set_supported(&EWMH, QX11Info::appScreen(), 0,list); +} + +// _NET_CLIENT_LIST +QList<WId> LXCB::WM_Get_Client_List(bool stacking){ + QList<WId> out; + if(stacking){ + xcb_get_property_cookie_t cookie = xcb_ewmh_get_client_list_stacking(&EWMH, QX11Info::appScreen()); + xcb_ewmh_get_windows_reply_t reply; + if(1==xcb_ewmh_get_client_list_stacking_reply(&EWMH, cookie, &reply, NULL) ){ + for(unsigned int i=0; i<reply.windows_len; i++){ + out << reply.windows[i]; + } + } + }else{ + xcb_get_property_cookie_t cookie = xcb_ewmh_get_client_list(&EWMH, QX11Info::appScreen()); + xcb_ewmh_get_windows_reply_t reply; + if(1==xcb_ewmh_get_client_list_reply(&EWMH, cookie, &reply, NULL) ){ + for(unsigned int i=0; i<reply.windows_len; i++){ + out << reply.windows[i]; + } + } + } + return out; +} + +void LXCB::WM_Set_Client_List(QList<WId> list, bool stacking){ + //convert the QList into a generic array + xcb_window_t array[list.length()]; + for(int i=0; i<list.length(); i++){ array[i] = list[i]; } + if(stacking){ + xcb_ewmh_set_client_list_stacking(&EWMH, QX11Info::appScreen(), list.length(), array); + }else{ + xcb_ewmh_set_client_list(&EWMH, QX11Info::appScreen(), list.length(), array); + } + +} + +// _NET_NUMBER_OF_DESKTOPS +unsigned int LXCB::WM_Get_Number_Desktops(){ + //return value equals 0 for errors + xcb_get_property_cookie_t cookie = xcb_ewmh_get_number_of_desktops_unchecked(&EWMH, QX11Info::appScreen()); + uint32_t number = 0; + xcb_ewmh_get_number_of_desktops_reply(&EWMH, cookie, &number, NULL); + return number; +} + +void LXCB::WM_SetNumber_Desktops(unsigned int number){ + //NOTE: number should be at least 1 + xcb_ewmh_set_number_of_desktops(&EWMH, QX11Info::appScreen(), number); +} + +// _NET_DESKTOP_GEOMETRY +QSize LXCB::WM_Get_Desktop_Geometry(){ + xcb_get_property_cookie_t cookie = xcb_ewmh_get_desktop_geometry(&EWMH, QX11Info::appScreen()); + uint32_t wid, hi; + wid = hi = 0; + xcb_ewmh_get_desktop_geometry_reply(&EWMH, cookie, &wid, &hi, NULL); + return QSize(wid,hi); +} + +void LXCB::WM_Set_Desktop_Geometry(QSize size){ + xcb_ewmh_set_desktop_geometry(&EWMH, QX11Info::appScreen(), size.width(), size.height()); +} + +// _NET_DESKTOP_VIEWPORT +QList<QPoint> LXCB::WM_Get_Desktop_Viewport(){ + QList<QPoint> out; + xcb_get_property_cookie_t cookie = xcb_ewmh_get_desktop_viewport_unchecked(&EWMH, QX11Info::appScreen()); + xcb_ewmh_get_desktop_viewport_reply_t reply; + if(1==xcb_ewmh_get_desktop_viewport_reply(&EWMH, cookie, &reply, NULL) ){ + for(unsigned int i=0; i<reply.desktop_viewport_len; i++){ + out << QPoint( reply.desktop_viewport[i].x, reply.desktop_viewport[i].y ); + } + xcb_ewmh_get_desktop_viewport_reply_wipe(&reply); //clean up the reply structure first + } + return out; +} + +void LXCB::WM_Set_Desktop_Viewport(QList<QPoint> list){ + //Turn the QList into xcb_ewmh_coordinates_t* + xcb_ewmh_coordinates_t array[list.length()]; + for(int i=0; i<list.length(); i++){ array[i].x=list[i].x(); array[i].y=list[i].y(); } + //Now set the property + xcb_ewmh_set_desktop_viewport(&EWMH, QX11Info::appScreen(), list.length(), array); +} + +// _NET_CURRENT_DESKTOP +int LXCB::WM_Get_Current_Desktop(){ + //Returns -1 for errors + xcb_get_property_cookie_t cookie = xcb_ewmh_get_current_desktop_unchecked(&EWMH, QX11Info::appScreen()); + uint32_t num = 0; + if(1==xcb_ewmh_get_current_desktop_reply(&EWMH, cookie, &num, NULL) ){ + return num; + }else{ + return -1; + } +} + +void LXCB::WM_Set_Current_Desktop(unsigned int num){ + xcb_ewmh_set_current_desktop(&EWMH, QX11Info::appScreen(), num); +} + +// _NET_DESKTOP_NAMES +QStringList LXCB::WM_Get_Desktop_Names(){ + QStringList out; + // ** ISSUES with the XCB_EWMH strings reply structure - 11/11/15 (skip for now) + // (Appears to be a char* instead of char** in the class definitions) + /*xcb_get_property_cookie_t cookie = xcb_ewmh_get_desktop_names_unchecked(&EWMH, QX11Info::appScreen()); + xcb_ewmh_get_utf8_strings_reply_t reply; + if(1==xcb_ewmh_get_desktop_names_reply(&EWMH, cookie, &reply, NULL) ){ + for(unsigned int i=0; i<reply.strings_len; i++){ + out << QString::fromUtf8( QByteArray(reply.strings[i]) ); + } + }*/ + return out; +} + +void LXCB::WM_Set_Desktop_Names(QStringList){// list){ + // ** ISSUES with the XCB_EWMH strings input structure - 11/11/15 (skip for now) + // (Appears to be a char* instead of char** in the class definitions) + /*//Convert to an array of char arrays + char *array[ list.length() ]; + for(int i=0; i<list.length(); i++){array[i] = list[i].toUtf8().data(); } + //Now set the property + xcb_ewmh_set_desktop_names(&EWMH, QX11Info::appScreen(), list.length(), array); + */ +} + +// _NET_ACTIVE_WINDOW +WId LXCB::WM_Get_Active_Window(){ + xcb_get_property_cookie_t cookie = xcb_ewmh_get_active_window_unchecked(&EWMH, QX11Info::appScreen()); + xcb_window_t win = 0; + xcb_ewmh_get_active_window_reply(&EWMH, cookie, &win, NULL); + return win; +} + +void LXCB::WM_Set_Active_Window(WId win){ + xcb_ewmh_set_active_window(&EWMH, QX11Info::appScreen(), win); +} + +// _NET_WORKAREA +QList<QRect> LXCB::WM_Get_Workarea(){ + QList<QRect> out; + xcb_get_property_cookie_t cookie = xcb_ewmh_get_workarea_unchecked(&EWMH, QX11Info::appScreen()); + xcb_ewmh_get_workarea_reply_t reply; + if(1==xcb_ewmh_get_workarea_reply(&EWMH, cookie, &reply, NULL) ){ + for(unsigned int i=0; i<reply.workarea_len ;i++){ + out << QRect( reply.workarea[i].x, reply.workarea[i].y, reply.workarea[i].width, reply.workarea[i].height); + } + xcb_ewmh_get_workarea_reply_wipe(&reply); + } + return out; +} + +void LXCB::WM_Set_Workarea(QList<QRect> list){ + //Convert to the XCB/EWMH data structures + xcb_ewmh_geometry_t array[list.length()]; + for(int i=0; i<list.length(); i++){ + array[i].x = list[i].x(); array[i].y = list[i].y(); + array[i].width = list[i].width(); array[i].height = list[i].height(); + } + //Now set the property + xcb_ewmh_set_workarea(&EWMH, QX11Info::appScreen(), list.length(), array); +} + +// _NET_SUPPORTING_WM_CHECK +WId LXCB::WM_Get_Supporting_WM(WId win){ + xcb_get_property_cookie_t cookie = xcb_ewmh_get_supporting_wm_check_unchecked(&EWMH, win); + xcb_window_t out = 0; + xcb_ewmh_get_supporting_wm_check_reply(&EWMH, cookie, &out, NULL); + return win; +} + +void LXCB::WM_Set_Supporting_WM(WId child){ + //Set this property on the root window first + xcb_ewmh_set_supporting_wm_check(&EWMH, QX11Info::appRootWindow(), child); + //Also set this property on the child window (pointing to itself) + xcb_ewmh_set_supporting_wm_check(&EWMH, child, child); +} + +// _NET_VIRTUAL_ROOTS +QList<WId> LXCB::WM_Get_Virtual_Roots(){ + QList<WId> out; + xcb_get_property_cookie_t cookie = xcb_ewmh_get_virtual_roots_unchecked(&EWMH, QX11Info::appScreen()); + xcb_ewmh_get_windows_reply_t reply; + if(1==xcb_ewmh_get_virtual_roots_reply(&EWMH, cookie, &reply, NULL) ){ + for(unsigned int i=0; i<reply.windows_len; i++){ + out << reply.windows[i]; + } + } + return out; +} + +void LXCB::WM_Set_Virtual_Roots(QList<WId> list){ + //Convert to XCB array + xcb_window_t array[list.length()]; + for(int i=0; i<list.length(); i++){ array[i] = list[i]; } + //Set the property + xcb_ewmh_set_virtual_roots(&EWMH, QX11Info::appScreen(), list.length(), array); +} + +// _NET_DESKTOP_LAYOUT +// -- skipped for now - see note in LuminaX11.h + +// _NET_SHOWING_DESKTOP +bool LXCB::WM_Get_Showing_Desktop(){ + xcb_get_property_cookie_t cookie = xcb_ewmh_get_showing_desktop_unchecked(&EWMH, QX11Info::appScreen()); + uint32_t reply = 0; + xcb_ewmh_get_showing_desktop_reply(&EWMH, cookie, &reply, NULL); + return (reply==1); +} + +void LXCB::WM_Set_Showing_Desktop(bool show){ + xcb_ewmh_set_showing_desktop(&EWMH, QX11Info::appScreen(), (show ? 1 : 0) ); +} + +// -- ROOT WINDOW MESSAGES/REQUESTS +// _NET_CLOSE_WINDOW +void LXCB::WM_Request_Close_Window(WId win){ + xcb_ewmh_request_close_window(&EWMH, QX11Info::appScreen(), win, XCB_TIME_CURRENT_TIME, XCB_EWMH_CLIENT_SOURCE_TYPE_OTHER); //user choice to close the window +} + +// _NET_MOVERESIZE_WINDOW +void LXCB::WM_Request_MoveResize_Window(WId win, QRect geom, bool fromuser, LXCB::GRAVITY grav, LXCB::MOVERESIZE_WINDOW_FLAGS flags){ + //Note: The LXCB::GRAVITY enum exactly matches the XCB values (just different names) + //Convert the flags into the XCB type + int eflags = 0; //xcb_ewmh_moveresize_window_opt_flags_t + if(flags.testFlag(LXCB::X)){ eflags = eflags | XCB_EWMH_MOVERESIZE_WINDOW_X; } + if(flags.testFlag(LXCB::Y)){ eflags = eflags | XCB_EWMH_MOVERESIZE_WINDOW_Y; } + if(flags.testFlag(LXCB::WIDTH)){ eflags = eflags | XCB_EWMH_MOVERESIZE_WINDOW_WIDTH; } + if(flags.testFlag(LXCB::HEIGHT)){ eflags = eflags | XCB_EWMH_MOVERESIZE_WINDOW_HEIGHT; } + + xcb_ewmh_request_moveresize_window(&EWMH, QX11Info::appScreen(), win, (xcb_gravity_t) grav, \ + (fromuser ? XCB_EWMH_CLIENT_SOURCE_TYPE_OTHER : XCB_EWMH_CLIENT_SOURCE_TYPE_NORMAL), \ + (xcb_ewmh_moveresize_window_opt_flags_t) eflags, geom.x(), geom.y(), geom.width(), geom.height() ); +} + +// _NET_WM_MOVERESIZE +// -- skipped for now - see note in LuminaX11.h + +// _NET_RESTACK_WINDOW +void LXCB::WM_Request_Restack_Window(WId win, WId sibling, LXCB::STACK_FLAG flag){ + //Note: The STACK_FLAG enum matches the xcb_stack_mode_t enum exactly (just different names) + xcb_ewmh_request_restack_window(&EWMH, QX11Info::appScreen(), win, sibling, (xcb_stack_mode_t) flag); +} + +// _NET_REQUEST_FRAME_EXTENTS +void LXCB::WM_Request_Frame_Extents(WId win){ + xcb_ewmh_request_frame_extents(&EWMH, QX11Info::appScreen(), win); +} + +// === WINDOW PROPERTIES === +// _NET_SUPPORTED (Window) +void LXCB::WM_Set_Window_Supported(WId win){ + //NET_WM standards (ICCCM implied - no standard way to list those) + xcb_atom_t list[] = {}; + xcb_ewmh_set_wm_allowed_actions(&EWMH, win, 0, list); +} + +// _NET_WM_NAME +QString LXCB::WM_Get_Name(WId win){ + xcb_get_property_cookie_t cookie = xcb_ewmh_get_wm_name_unchecked(&EWMH, win); + xcb_ewmh_get_utf8_strings_reply_t reply; + QString out; + if(1==xcb_ewmh_get_wm_name_reply(&EWMH, cookie,&reply, NULL) ){ + out = QString::fromUtf8(reply.strings); + } + return out; +} +void LXCB::WM_Set_Name(WId win, QString txt){ + xcb_ewmh_set_wm_name(&EWMH, win, txt.length(), txt.toUtf8().data()); +} + +// _NET_WM_VISIBLE_NAME +QString LXCB::WM_Get_Visible_Name(WId win){ + xcb_get_property_cookie_t cookie = xcb_ewmh_get_wm_visible_name_unchecked(&EWMH, win); + xcb_ewmh_get_utf8_strings_reply_t reply; + QString out; + if(1==xcb_ewmh_get_wm_visible_name_reply(&EWMH, cookie,&reply, NULL) ){ + out = QString::fromUtf8(reply.strings); + } + return out; +} +void LXCB::WM_Set_Visible_Name(WId win, QString txt){ + xcb_ewmh_set_wm_visible_name(&EWMH, win, txt.length(), txt.toUtf8().data()); +} + +// _NET_WM_ICON_NAME +QString LXCB::WM_Get_Icon_Name(WId win){ + xcb_get_property_cookie_t cookie = xcb_ewmh_get_wm_icon_name_unchecked(&EWMH, win); + xcb_ewmh_get_utf8_strings_reply_t reply; + QString out; + if(1==xcb_ewmh_get_wm_icon_name_reply(&EWMH, cookie,&reply, NULL) ){ + out = QString::fromUtf8(reply.strings); + } + return out; +} +void LXCB::WM_Set_Icon_Name(WId win, QString txt){ + xcb_ewmh_set_wm_icon_name(&EWMH, win, txt.length(), txt.toUtf8().data()); +} + +// _NET_WM_VISIBLE_ICON_NAME +QString LXCB::WM_Get_Visible_Icon_Name(WId win){ + xcb_get_property_cookie_t cookie = xcb_ewmh_get_wm_visible_icon_name_unchecked(&EWMH, win); + xcb_ewmh_get_utf8_strings_reply_t reply; + QString out; + if(1==xcb_ewmh_get_wm_visible_icon_name_reply(&EWMH, cookie,&reply, NULL) ){ + out = QString::fromUtf8(reply.strings); + } + return out; +} +void LXCB::WM_Set_Visible_Icon_Name(WId win, QString txt){ + xcb_ewmh_set_wm_visible_icon_name(&EWMH, win, txt.length(), txt.toUtf8().data()); +} + +// _NET_WM_DESKTOP +int LXCB::WM_Get_Desktop(WId win){ + //returns -1 if window on all desktops + xcb_get_property_cookie_t cookie = xcb_ewmh_get_wm_desktop_unchecked(&EWMH, win); + uint32_t num = 0; + int out = -1; + if(1==xcb_ewmh_get_wm_desktop_reply(&EWMH, cookie, &num, NULL) ){ + if(num!=0xFFFFFFFF){ out = num; } + }else{ + //Error in fetching property (not set?) + // - put it on the current screen + out = WM_Get_Current_Desktop(); + } + return out; +} + +void LXCB::WM_Set_Desktop(WId win, int num){ + //use -1 to set it for all desktops + xcb_ewmh_set_wm_desktop(&EWMH, win, (num<0 ? 0xFFFFFFFF : qAbs(num) ) ); +} + +// _NET_WM_WINDOW_TYPE +QList<LXCB::WINDOWTYPE> LXCB::WM_Get_Window_Type(WId win){ + // Note: This will silently discard any unknown/non-standard window type flags + // The client should ensure to set at least one standardized type flag per the specifications. + QList<LXCB::WINDOWTYPE> out; + xcb_get_property_cookie_t cookie = xcb_ewmh_get_wm_window_type_unchecked(&EWMH, win); + xcb_ewmh_get_atoms_reply_t reply; + if(1==xcb_ewmh_get_wm_window_type_reply(&EWMH, cookie, &reply, NULL) ){ + for(unsigned int i=0; i<reply.atoms_len; i++){ + if(reply.atoms[i]==EWMH._NET_WM_WINDOW_TYPE_DESKTOP){ out << LXCB::T_DESKTOP; } + else if(reply.atoms[i]==EWMH._NET_WM_WINDOW_TYPE_DOCK){ out << LXCB::T_DOCK; } + else if(reply.atoms[i]==EWMH._NET_WM_WINDOW_TYPE_TOOLBAR){ out << LXCB::T_TOOLBAR; } + else if(reply.atoms[i]==EWMH._NET_WM_WINDOW_TYPE_MENU){ out << LXCB::T_MENU; } + else if(reply.atoms[i]==EWMH._NET_WM_WINDOW_TYPE_UTILITY){ out << LXCB::T_UTILITY; } + else if(reply.atoms[i]==EWMH._NET_WM_WINDOW_TYPE_SPLASH){ out << LXCB::T_SPLASH; } + else if(reply.atoms[i]==EWMH._NET_WM_WINDOW_TYPE_DIALOG){ out << LXCB::T_DIALOG; } + else if(reply.atoms[i]==EWMH._NET_WM_WINDOW_TYPE_DROPDOWN_MENU){ out << LXCB::T_DROPDOWN_MENU; } + else if(reply.atoms[i]==EWMH._NET_WM_WINDOW_TYPE_POPUP_MENU){ out << LXCB::T_POPUP_MENU; } + else if(reply.atoms[i]==EWMH._NET_WM_WINDOW_TYPE_TOOLTIP){ out << LXCB::T_TOOLTIP; } + else if(reply.atoms[i]==EWMH._NET_WM_WINDOW_TYPE_NOTIFICATION){ out << LXCB::T_NOTIFICATION; } + else if(reply.atoms[i]==EWMH._NET_WM_WINDOW_TYPE_COMBO){ out << LXCB::T_COMBO; } + else if(reply.atoms[i]==EWMH._NET_WM_WINDOW_TYPE_DND){ out << LXCB::T_DND; } + else if(reply.atoms[i]==EWMH._NET_WM_WINDOW_TYPE_NORMAL){ out << LXCB::T_NORMAL; } + } + } + return out; +} + +void LXCB::WM_Set_Window_Type(WId win, QList<LXCB::WINDOWTYPE> list){ + //Convert to the XCB format + xcb_atom_t array[list.length()]; + for(int i=0; i<list.length(); i++){ + switch(list[i]){ + case LXCB::T_DESKTOP: + array[i] = EWMH._NET_WM_WINDOW_TYPE_DESKTOP; break; + case LXCB::T_DOCK: + array[i] = EWMH._NET_WM_WINDOW_TYPE_DOCK; break; + case LXCB::T_TOOLBAR: + array[i] = EWMH._NET_WM_WINDOW_TYPE_TOOLBAR; break; + case LXCB::T_MENU: + array[i] = EWMH._NET_WM_WINDOW_TYPE_MENU; break; + case LXCB::T_UTILITY: + array[i] = EWMH._NET_WM_WINDOW_TYPE_UTILITY; break; + case LXCB::T_SPLASH: + array[i] = EWMH._NET_WM_WINDOW_TYPE_SPLASH; break; + case LXCB::T_DIALOG: + array[i] = EWMH._NET_WM_WINDOW_TYPE_DIALOG; break; + case LXCB::T_DROPDOWN_MENU: + array[i] = EWMH._NET_WM_WINDOW_TYPE_DROPDOWN_MENU; break; + case LXCB::T_POPUP_MENU: + array[i] = EWMH._NET_WM_WINDOW_TYPE_POPUP_MENU; break; + case LXCB::T_TOOLTIP: + array[i] = EWMH._NET_WM_WINDOW_TYPE_TOOLTIP; break; + case LXCB::T_NOTIFICATION: + array[i] = EWMH._NET_WM_WINDOW_TYPE_NOTIFICATION; break; + case LXCB::T_COMBO: + array[i] = EWMH._NET_WM_WINDOW_TYPE_COMBO; break; + case LXCB::T_DND: + array[i] = EWMH._NET_WM_WINDOW_TYPE_DND; break; + default: + array[i] = EWMH._NET_WM_WINDOW_TYPE_NORMAL; + } + } + //Now set the property + xcb_ewmh_set_wm_window_type(&EWMH, win, list.length(), array); +} + +// _NET_WM_STATE +QList<LXCB::WINDOWSTATE> LXCB::WM_Get_Window_States(WId win){ + QList<LXCB::WINDOWSTATE> out; + xcb_get_property_cookie_t cookie = xcb_ewmh_get_wm_state_unchecked(&EWMH, win); + xcb_ewmh_get_atoms_reply_t reply; + if(1==xcb_ewmh_get_wm_state_reply(&EWMH, cookie, &reply, NULL) ){ + for(unsigned int i=0; i<reply.atoms_len; i++){ + if(reply.atoms[i]==EWMH._NET_WM_STATE_MODAL){ out << LXCB::S_MODAL; } + else if(reply.atoms[i]==EWMH._NET_WM_STATE_STICKY){ out << LXCB::S_STICKY; } + else if(reply.atoms[i]==EWMH._NET_WM_STATE_MAXIMIZED_VERT){ out << LXCB::S_MAX_VERT; } + else if(reply.atoms[i]==EWMH._NET_WM_STATE_MAXIMIZED_HORZ){ out << LXCB::S_MAX_HORZ; } + else if(reply.atoms[i]==EWMH._NET_WM_STATE_SHADED){ out << LXCB::S_SHADED; } + else if(reply.atoms[i]==EWMH._NET_WM_STATE_SKIP_TASKBAR){ out << LXCB::S_SKIP_TASKBAR; } + else if(reply.atoms[i]==EWMH._NET_WM_STATE_SKIP_PAGER){ out << LXCB::S_SKIP_PAGER; } + else if(reply.atoms[i]==EWMH._NET_WM_STATE_HIDDEN){ out << LXCB::S_HIDDEN; } + else if(reply.atoms[i]==EWMH._NET_WM_STATE_FULLSCREEN){ out << LXCB::S_FULLSCREEN; } + else if(reply.atoms[i]==EWMH._NET_WM_STATE_ABOVE){ out << LXCB::S_ABOVE; } + else if(reply.atoms[i]==EWMH._NET_WM_STATE_BELOW){ out << LXCB::S_BELOW; } + else if(reply.atoms[i]==EWMH._NET_WM_STATE_DEMANDS_ATTENTION){ out << LXCB::S_ATTENTION; } + //else if(reply.atoms[i]==EWMH._NET_WM_STATE_FOCUSED){ out << LXCB::FOCUSED; } + } + } + return out; +} + +void LXCB::WM_Set_Window_States(WId win, QList<LXCB::WINDOWSTATE> list){ + //Convert to the XCB format + xcb_atom_t array[list.length()]; + for(int i=0; i<list.length(); i++){ + switch(list[i]){ + case LXCB::S_MODAL: + array[i] = EWMH._NET_WM_STATE_MODAL; break; + case LXCB::S_STICKY: + array[i] = EWMH._NET_WM_STATE_STICKY; break; + case LXCB::S_MAX_VERT: + array[i] = EWMH._NET_WM_STATE_MAXIMIZED_VERT; break; + case LXCB::S_MAX_HORZ: + array[i] = EWMH._NET_WM_STATE_MAXIMIZED_HORZ; break; + case LXCB::S_SHADED: + array[i] = EWMH._NET_WM_STATE_SHADED; break; + case LXCB::S_SKIP_TASKBAR: + array[i] = EWMH._NET_WM_STATE_SKIP_TASKBAR; break; + case LXCB::S_SKIP_PAGER: + array[i] = EWMH._NET_WM_STATE_SKIP_PAGER; break; + case LXCB::S_HIDDEN: + array[i] = EWMH._NET_WM_STATE_HIDDEN; break; + case LXCB::S_FULLSCREEN: + array[i] = EWMH._NET_WM_STATE_FULLSCREEN; break; + case LXCB::S_ABOVE: + array[i] = EWMH._NET_WM_STATE_ABOVE; break; + case LXCB::S_BELOW: + array[i] = EWMH._NET_WM_STATE_BELOW; break; + case LXCB::S_ATTENTION: + array[i] = EWMH._NET_WM_STATE_DEMANDS_ATTENTION; break; + //case LXCB::FOCUSED: + //array[i] = EWMH._NET_WM_STATE_FOCUSED; break; + } + } + //Now set the property + xcb_ewmh_set_wm_state(&EWMH, win, list.length(), array); +} + +// _NET_WM_ALLOWED_ACTIONS +QList<LXCB::WINDOWACTION> LXCB::WM_Get_Window_Actions(WId win){ + QList<LXCB::WINDOWACTION> out; + xcb_get_property_cookie_t cookie = xcb_ewmh_get_wm_allowed_actions_unchecked(&EWMH, win); + xcb_ewmh_get_atoms_reply_t reply; + if(1==xcb_ewmh_get_wm_allowed_actions_reply(&EWMH, cookie, &reply, NULL) ){ + for(unsigned int i=0; i<reply.atoms_len; i++){ + if(reply.atoms[i]==EWMH._NET_WM_ACTION_MOVE){ out << LXCB::A_MOVE; } + else if(reply.atoms[i]==EWMH._NET_WM_ACTION_RESIZE){ out << LXCB::A_RESIZE; } + else if(reply.atoms[i]==EWMH._NET_WM_ACTION_MINIMIZE){ out << LXCB::A_MINIMIZE; } + else if(reply.atoms[i]==EWMH._NET_WM_ACTION_SHADE){ out << LXCB::A_SHADE; } + else if(reply.atoms[i]==EWMH._NET_WM_ACTION_STICK){ out << LXCB::A_STICK; } + else if(reply.atoms[i]==EWMH._NET_WM_ACTION_MAXIMIZE_HORZ){ out << LXCB::A_MAX_HORZ; } + else if(reply.atoms[i]==EWMH._NET_WM_ACTION_MAXIMIZE_VERT){ out << LXCB::A_MAX_VERT; } + else if(reply.atoms[i]==EWMH._NET_WM_ACTION_FULLSCREEN){ out << LXCB::A_FULLSCREEN; } + else if(reply.atoms[i]==EWMH._NET_WM_ACTION_CHANGE_DESKTOP){ out << LXCB::A_CHANGE_DESKTOP; } + else if(reply.atoms[i]==EWMH._NET_WM_ACTION_CLOSE){ out << LXCB::A_CLOSE; } + else if(reply.atoms[i]==EWMH._NET_WM_ACTION_ABOVE){ out << LXCB::A_ABOVE; } + else if(reply.atoms[i]==EWMH._NET_WM_ACTION_BELOW){ out << LXCB::A_BELOW; } + } + } + return out; +} + +void LXCB::WM_Set_Window_Actions(WId win, QList<LXCB::WINDOWACTION> list){ + //Convert to the XCB format + xcb_atom_t array[list.length()]; + for(int i=0; i<list.length(); i++){ + switch(list[i]){ + case LXCB::A_MOVE: + array[i] = EWMH._NET_WM_ACTION_MOVE; break; + case LXCB::A_RESIZE: + array[i] = EWMH._NET_WM_ACTION_RESIZE; break; + case LXCB::A_MINIMIZE: + array[i] = EWMH._NET_WM_ACTION_MINIMIZE; break; + case LXCB::A_SHADE: + array[i] = EWMH._NET_WM_ACTION_SHADE; break; + case LXCB::A_STICK: + array[i] = EWMH._NET_WM_ACTION_STICK; break; + case LXCB::A_MAX_HORZ: + array[i] = EWMH._NET_WM_ACTION_MAXIMIZE_HORZ; break; + case LXCB::A_MAX_VERT: + array[i] = EWMH._NET_WM_ACTION_MAXIMIZE_VERT; break; + case LXCB::A_FULLSCREEN: + array[i] = EWMH._NET_WM_ACTION_FULLSCREEN; break; + case LXCB::A_CHANGE_DESKTOP: + array[i] = EWMH._NET_WM_ACTION_CHANGE_DESKTOP; break; + case LXCB::A_CLOSE: + array[i] = EWMH._NET_WM_ACTION_CLOSE; break; + case LXCB::A_ABOVE: + array[i] = EWMH._NET_WM_ACTION_ABOVE; break; + case LXCB::A_BELOW: + array[i] = EWMH._NET_WM_ACTION_BELOW; break; + } + } + //Now set the property + xcb_ewmh_set_wm_allowed_actions(&EWMH, win, list.length(), array); +} + +// _NET_WM_STRUT +QList<unsigned int> LXCB::WM_Get_Window_Strut(WId win){ + //Returns: [left,right,top,bottom] margins in pixels (always length 4) + QList<unsigned int> out; out << 0 << 0 << 0 << 0; //init the output list + xcb_get_property_cookie_t cookie = xcb_ewmh_get_wm_strut_unchecked(&EWMH, win); + xcb_ewmh_get_extents_reply_t reply; + if(1==xcb_ewmh_get_wm_strut_reply(&EWMH, cookie, &reply, NULL) ){ + out[0] = reply.left; + out[1] = reply.right; + out[2] = reply.top; + out[3] = reply.bottom; + } + return out; +} + +void LXCB::WM_Set_Window_Strut(WId win, QList<unsigned int> margins){ + //Input: [left, right, top, bottom] - must be length 4 + while(margins.length()<4){ margins << 0; } + xcb_ewmh_set_wm_strut(&EWMH, win, margins[0], margins[1], margins[2], margins[3]); +} + +// _NET_WM_STRUT_PARTIAL +QList<strut_geom> LXCB::WM_Get_Window_Strut_Partial(WId win){ + //Returns: [left,right,top,bottom] struts + QList<strut_geom> out; out << strut_geom() << strut_geom() << strut_geom() << strut_geom(); + xcb_get_property_cookie_t cookie = xcb_ewmh_get_wm_strut_partial_unchecked(&EWMH, win); + xcb_ewmh_wm_strut_partial_t reply; + if(1==xcb_ewmh_get_wm_strut_partial_reply(&EWMH, cookie, &reply, NULL) ){ + if(reply.left>0){ + out[0].start = reply.left_start_y; out[0].end = reply.left_end_y; out[0].thick = reply.left; + } + if(reply.right>0){ + out[1].start = reply.right_start_y; out[1].end = reply.right_end_y; out[1].thick = reply.right; + } + if(reply.top>0){ + out[2].start = reply.top_start_x; out[2].end = reply.top_end_x; out[2].thick = reply.top; + } + if(reply.bottom>0){ + out[3].start = reply.bottom_start_x; out[3].end = reply.bottom_end_x; out[3].thick = reply.bottom; + } + } + return out; +} + +void LXCB::WM_Set_Window_Strut_Partial(WId win, QList<strut_geom> struts){ + //Input: [left,right,top,bottom] - must be length 4 + while(struts.length() < 4){ struts << strut_geom(); } + //Convert to the XCB input format + xcb_ewmh_wm_strut_partial_t input; + input.left=struts[0].thick; input.left_start_y=struts[0].start; input.left_end_y=struts[0].end; + input.right=struts[1].thick; input.right_start_y=struts[1].start; input.right_end_y=struts[1].end; + input.top=struts[2].thick; input.top_start_x=struts[2].start; input.top_end_x=struts[2].end; + input.bottom=struts[3].thick; input.bottom_start_x=struts[3].start; input.bottom_end_x=struts[3].end; + //Now set the property + xcb_ewmh_set_wm_strut_partial(&EWMH, win, input); +} + +// _NET_WM_ICON_GEOMETRY +QRect LXCB::WM_Get_Icon_Geometry(WId win){ + xcb_get_property_cookie_t cookie = xcb_ewmh_get_wm_icon_geometry_unchecked(&EWMH, win); + xcb_ewmh_geometry_t reply; + QRect out; + if(1==xcb_ewmh_get_wm_icon_geometry_reply(&EWMH, cookie, &reply, NULL) ){ + out = QRect(reply.x, reply.y, reply.width, reply.height); + } + return out; +} + +void LXCB::WM_Set_Icon_Geometry(WId win, QRect geom){ + //Note - 11/12/15: xcb_ewmh.h lists the inputs as "left/right/top/bottom" + // but this might be an error and the real inputs are "x/y/width/height" + // as in the other geometry get/set routines (and as returned by the xcb_ewmh_get_wm_icon_geometry() routine) + xcb_ewmh_set_wm_icon_geometry(&EWMH, win, geom.x(), geom.x()+geom.width(), geom.y(), geom.y()+geom.height()); + //xcb_ewmh_set_wm_icon_geometry(&EWMH, win, geom.x(), geom.y(), geom.width(), geom.height()); +} + +// _NET_WM_ICON +QIcon LXCB::WM_Get_Icon(WId win){ + //Note: The output is a QIcon because it allows for multiple varying-sized images to be loaded/used later as needed + // For each pixmap found here, add it (in its native size) to the icon structure + QIcon out; + xcb_get_property_cookie_t cookie = xcb_ewmh_get_wm_icon_unchecked(&EWMH, win); + xcb_ewmh_get_wm_icon_reply_t reply; + if(1==xcb_ewmh_get_wm_icon_reply(&EWMH, cookie, &reply, NULL) ){ + //Now iterate over all the pixmaps and load them into the QIcon + xcb_ewmh_wm_icon_iterator_t it = xcb_ewmh_get_wm_icon_iterator(&reply); + while(it.index < reply.num_icons){ + QImage img( (const unsigned char *) it.data, it.width, it.height, QImage::Format_ARGB32); + out.addPixmap( QPixmap::fromImage(img) ); + if(it.rem>0){ xcb_ewmh_get_wm_icon_next(&it); } //go to the next pixmap + else{ break; } //just finished the last one - ensure this breaks out now (just in case) + } + //Clean up any background buffer for the reply + xcb_ewmh_get_wm_icon_reply_wipe(&reply); + } + return out; +} + +// _NET_WM_PID +unsigned int LXCB::WM_Get_Pid(WId win){ + xcb_get_property_cookie_t cookie = xcb_ewmh_get_wm_pid_unchecked(&EWMH, win); + uint32_t pid = 0; + xcb_ewmh_get_wm_pid_reply(&EWMH, cookie, &pid, NULL); + return pid; +} + +// _NET_WM_HANDLED_ICONS +bool LXCB::WM_Get_Handled_Icons(WId win){ + xcb_get_property_cookie_t cookie = xcb_ewmh_get_wm_handled_icons_unchecked(&EWMH, win); + uint32_t num = 0; + xcb_ewmh_get_wm_handled_icons_reply(&EWMH, cookie, &num, NULL); + return (num!=0); //This flag is set on the window +} + +void LXCB::WM_Set_Handled_Icons(WId win, bool set){ + xcb_ewmh_set_wm_handled_icons(&EWMH, win, (set ? 1 : 0)); +} + +// _NET_WM_USER_TIME +unsigned int LXCB::WM_Get_User_Time(WId win){ + xcb_get_property_cookie_t cookie = xcb_ewmh_get_wm_user_time_unchecked(&EWMH, win); + uint32_t out = 0; + xcb_ewmh_get_wm_user_time_reply(&EWMH, cookie, &out, NULL); + return out; +} + +void LXCB::WM_Set_User_Time(WId win, unsigned int xtime){ + xcb_ewmh_set_wm_user_time(&EWMH, win, xtime); +} + +// _NET_WM_USER_TIME_WINDOW +/* +WId LXCB::WM_Get_User_Time_WIndow(WId win){ + xcb_get_property_cookie_t cookie = xcb_ewmh_get_user_time_window_unchecked(&EWMH, win); + xcb_window_t out; + xcb_ewmh_get_user_time_window_reply(&EWMH, cookie, &out, NULL); + return out; +} + +void LXCB::WM_Set_User_Time_Window(WId win, WId utwin){ + xcb_ewmh_set_wm_user_time_window(&EWMH, win, utwin); +}*/ + +// _NET_FRAME_EXTENTS +QList<unsigned int> LXCB::WM_Get_Frame_Extents(WId win){ + //Returns: [left,right,top,bottom] margins in pixels (always length 4) + QList<unsigned int> out; out << 0 << 0 << 0 << 0; //init the output list + xcb_get_property_cookie_t cookie = xcb_ewmh_get_frame_extents_unchecked(&EWMH, win); + xcb_ewmh_get_extents_reply_t reply; + if(1==xcb_ewmh_get_frame_extents_reply(&EWMH, cookie, &reply, NULL) ){ + out[0] = reply.left; + out[1] = reply.right; + out[2] = reply.top; + out[3] = reply.bottom; + } + return out; +} + +void LXCB::WM_Set_Frame_Extents(WId win, QList<unsigned int> margins){ + //Input: [left, right, top, bottom] - must be length 4 + while(margins.length()<4){ margins << 0; } + xcb_ewmh_set_frame_extents(&EWMH, win, margins[0], margins[1], margins[2], margins[3]); +} + +// _NET_WM_OPAQUE_REGION + +// _NET_WM_BYPASS_COMPOSITOR + +// === SPECIAL WM PROTOCOLS (EWMH) === +// _NET_WM_PING +void LXCB::WM_Send_Ping(WId win){ + xcb_ewmh_send_wm_ping(&EWMH, win, XCB_TIME_CURRENT_TIME); +} + +// _NET_WM_SYNC_REQUEST +uint64_t LXCB::WM_Get_Sync_Request_Counter(WId win){ + xcb_get_property_cookie_t cookie = xcb_ewmh_get_wm_sync_request_counter_unchecked(&EWMH, win); + uint64_t count = 0; + xcb_ewmh_get_wm_sync_request_counter_reply(&EWMH, cookie, &count, NULL); + return count; +} + +/*void LXCB::WM_Set_Sync_Request_Counter(WId win, uint64_t count){ + +}*/ + +// _NET_WM_FULLSCREEN_MONITORS +QList<unsigned int> LXCB::WM_Get_Fullscreen_Monitors(WId win){ + //Returns: [top,bottom,left,right] monitor numbers for window to use when fullscreen + QList<unsigned int> out; out << 0 << 0 << 0 << 0; //init the output array + xcb_get_property_cookie_t cookie = xcb_ewmh_get_wm_fullscreen_monitors_unchecked(&EWMH, win); + xcb_ewmh_get_wm_fullscreen_monitors_reply_t reply; + if(1==xcb_ewmh_get_wm_fullscreen_monitors_reply(&EWMH, cookie, &reply, NULL) ){ + out[0] = reply.top; out[1] = reply.bottom; + out[2] = reply.left; out[3] = reply.right; + } + return out; +} + +void LXCB::WM_Set_Fullscreen_Montors(WId win, QList<unsigned int> list){ + //Input: [top,bottom,left,right] monitor numbers + while(list.length()<4){ list << 0; } + xcb_ewmh_set_wm_fullscreen_monitors(&EWMH, win, list[0], list[1], list[2], list[3]); +} + +// _NET_WM_CM_S(n) +WId LXCB::WM_Get_CM_Owner(){ + xcb_get_selection_owner_cookie_t cookie = xcb_ewmh_get_wm_cm_owner_unchecked(&EWMH, QX11Info::appScreen()); + xcb_window_t owner = 0; + xcb_ewmh_get_wm_cm_owner_reply(&EWMH, cookie, &owner, NULL); + return owner; +} + +void LXCB::WM_Set_CM_Owner(WId win){ + xcb_ewmh_set_wm_cm_owner(&EWMH, QX11Info::appScreen(), win, XCB_TIME_CURRENT_TIME,0,0); +} diff --git a/src-qt5/core/libLumina/LuminaX11.h b/src-qt5/core/libLumina/LuminaX11.h new file mode 100644 index 00000000..b0a5d588 --- /dev/null +++ b/src-qt5/core/libLumina/LuminaX11.h @@ -0,0 +1,407 @@ +//=========================================== +// Lumina-DE source code +// Copyright (c) 2014-2015, Ken Moore +// Available under the 3-clause BSD license +// See the LICENSE file for full details +//=========================================== +// This class governs all the XLib usage and interactions +// and provides simpler Qt-based functions for use elsewhere +//=========================================== +#ifndef _LUMINA_LIBRARY_X11_H +#define _LUMINA_LIBRARY_X11_H + +//Qt includes +#include <QList> +#include <QString> +#include <QPixmap> +#include <QImage> +#include <QIcon> +#include <QPixmap> +#include <QX11Info> +#include <QDebug> +#include <QPainter> +#include <QObject> +#include <QFlags> + + +#include <xcb/xcb_ewmh.h> + +//SYSTEM TRAY STANDARD DEFINITIONS +#define _NET_SYSTEM_TRAY_ORIENTATION_HORZ 0 +#define _NET_SYSTEM_TRAY_ORIENTATION_VERT 1 +#define SYSTEM_TRAY_REQUEST_DOCK 0 +#define SYSTEM_TRAY_BEGIN_MESSAGE 1 +#define SYSTEM_TRAY_CANCEL_MESSAGE 2 + +#define URGENCYHINT (1L << 8) //For window urgency detection + +//Simple data container for the ICCCM hints (_size, _normal, _hints) + +//Simple data container for doing STRUT_PARTIAL input/output calculations +class strut_geom{ +public: + //Note: "Thick" will always be in the direction perpenticular to the start/end coordinates + //Example: A left strut will use start/end as Y coordinates, with "thick" in X coordinates starting from the left edge + unsigned int start, end, thick; + strut_geom(){ start = end = thick = 0; } + ~strut_geom(){} +}; + +class icccm_size_hints{ +public: + int x, y, width, height, min_width, min_height, max_width, max_height; + //Note: The "x","y","width", and "height" values are considered depreciated in the ICCCM specs + int width_inc, height_inc, min_aspect_num, min_aspect_den, max_aspect_num, max_aspect_den; + int base_width, base_height; + unsigned int win_gravity; //LXCB::GRAVITY value + + icccm_size_hints(){ + x=y=width=height=min_width=max_width=min_height=max_height = -1; + width_inc=height_inc=min_aspect_num=min_aspect_den=max_aspect_num=max_aspect_den = -1; + win_gravity = 0; + } + ~icccm_size_hints(){} + bool isValid(){ + //See if any of the values are different from the init values + return ( x>=0 || y>=0 || width>=0 || height>=0 || min_width>=0 || min_height>=0 || max_width>=0 || max_height>=0 \ + || width_inc>=0 || height_inc>=0 || min_aspect_num>=0 || min_aspect_den>=0 || max_aspect_num>=0 || max_aspect_den>=0 \ + || base_width>=0 || base_height>=0 || win_gravity>0 ); + } +}; + +//XCB Library replacement for LX11 (Qt5 uses XCB instead of XLib) +class LXCB{ + +public: + enum WINDOWVISIBILITY {IGNORE, INVISIBLE, VISIBLE, ACTIVE, ATTENTION}; //note that this in order of priority + enum ICCCM_STATE {WITHDRAWN, NORMAL, ICONIC}; + enum GRAVITY {FORGET=0, NW=1, N=2, NE=3, W=4, CENTER=5, E=6, SW=7, S=8, SE=9, STATIC=10}; + enum STACK_FLAG {ABOVE=0, BELOW=1, TOP_IF=2, BOTTOM_IF=3, OPPOSITE=4}; + enum WINDOWTYPE {T_DESKTOP, T_DOCK, T_TOOLBAR, T_MENU, T_UTILITY, T_SPLASH, T_DIALOG, T_DROPDOWN_MENU, T_POPUP_MENU, T_TOOLTIP, T_NOTIFICATION, T_COMBO, T_DND, T_NORMAL}; + enum WINDOWSTATE {S_MODAL, S_STICKY, S_MAX_VERT, S_MAX_HORZ, S_SHADED, S_SKIP_TASKBAR, S_SKIP_PAGER, S_HIDDEN, S_FULLSCREEN, S_ABOVE, S_BELOW, S_ATTENTION}; + enum WINDOWACTION {A_MOVE, A_RESIZE, A_MINIMIZE, A_SHADE, A_STICK, A_MAX_VERT, A_MAX_HORZ, A_FULLSCREEN, A_CHANGE_DESKTOP, A_CLOSE, A_ABOVE, A_BELOW}; + //Now enums which can have multiple values at once (Use the plural form for the QFlags) + enum ICCCM_PROTOCOL {TAKE_FOCUS = 0x0, DELETE_WINDOW = 0x1}; //any combination + Q_DECLARE_FLAGS(ICCCM_PROTOCOLS, ICCCM_PROTOCOL); + enum SIZE_HINT { US_POSITION=1<<0, US_SIZE=1<<1, P_POSITION=1<<2, P_SIZE=1<<3, P_MIN_SIZE=1<<4, P_MAX_SIZE=1<<5, P_RESIZE_INC=1<<6, P_ASPECT=1<<7, BASE_SIZE=1<<8, P_WIN_GRAVITY=1<<9 }; + Q_DECLARE_FLAGS(SIZE_HINTS, SIZE_HINT); + enum MOVERESIZE_WINDOW_FLAG { X=0x0, Y=0x1, WIDTH=0x2, HEIGHT=0x3}; + Q_DECLARE_FLAGS(MOVERESIZE_WINDOW_FLAGS, MOVERESIZE_WINDOW_FLAG); + + xcb_ewmh_connection_t EWMH; //This is where all the screen info and atoms are located + + LXCB(); + ~LXCB(); + + //== Main Interface functions == + // General Information + QList<WId> WindowList(bool rawlist = false); //list all non-Lumina windows (rawlist -> all workspaces) + unsigned int CurrentWorkspace(); + unsigned int NumberOfWorkspaces(); + WId ActiveWindow(); //fetch the ID for the currently active window + + //Session Modification + bool CheckDisableXinerama(); //returns true if Xinerama was initially set but now disabled + void RegisterVirtualRoots(QList<WId> roots); + void SetCurrentWorkspace(int); + + //Window Information + QString WindowClass(WId); + unsigned int WindowWorkspace(WId); //The workspace the window is on + QRect WindowGeometry(WId win, bool includeFrame = true); //the geometry of the window (frame excluded) + QList<int> WindowFrameGeometry(WId win); //Returns: [top,bottom,left,right] sizes of the frame + LXCB::WINDOWVISIBILITY WindowState(WId win); //Visible state of window + QString WindowVisibleIconName(WId win); //_NET_WM_VISIBLE_ICON_NAME + QString WindowIconName(WId win); //_NET_WM_ICON_NAME + QString WindowVisibleName(WId win); //_NET_WM_VISIBLE_NAME + QString WindowName(WId win); //_NET_WM_NAME + QString OldWindowName(WId win); //WM_NAME (old standard) + QString OldWindowIconName(WId win); //WM_ICON_NAME (old standard) + bool WindowIsMaximized(WId win); + int WindowIsFullscreen(WId win); //Returns the screen number if the window is fullscreen (or -1) + QIcon WindowIcon(WId win); //_NET_WM_ICON + + //Window Modification + // - SubStructure simplifications (not commonly used) + void SelectInput(WId win, bool isEmbed = false); //XSelectInput replacement (to see window events) + uint GenerateDamageID(WId); + + // - General Window Modifications + void SetAsSticky(WId); //Stick to all workspaces + void SetDisableWMActions(WId); //Disable WM control (shortcuts/automatic functions) + void SetAsPanel(WId); //Adjust all the window flags for a proper panel (cannot be done through Qt) + void SetAsDesktop(WId); //Adjust window flags to set as the desktop + void CloseWindow(WId); //request that the window be closed + void KillClient(WId); //Force the application that created the window to close + void MinimizeWindow(WId); //request that the window be unmapped/minimized + void ActivateWindow(WId); //request that the window become active + void RestoreWindow(WId); //Re-map/raise the window + void MaximizeWindow(WId win, bool flagsonly = false); //request that the window become maximized + void MoveResizeWindow(WId win, QRect geom); + void ResizeWindow(WId win, int width, int height); + void ResizeWindow(WId win, QSize sz){ ResizeWindow(win, sz.width(), sz.height()); } //overload for simplicity + void ReserveLocation(WId win, QRect geom, QString loc); + + //Window Embedding/Detaching (for system tray) + //void SetWindowBackground(QWidget *parent, QRect area, WId client); + uint EmbedWindow(WId win, WId container); //returns the damage ID (or 0 for an error) + bool UnembedWindow(WId win); + QPixmap TrayImage(WId win); + + //System Tray Management + WId startSystemTray(int screen = 0); //Startup the system tray (returns window ID for tray) + void closeSystemTray(WId); //Close the system tray + + + //============ + // WM Functions (directly changing/reading properties) + // - Using these directly may prevent the WM from seeing the change + //============ + void WM_CloseWindow(WId win, bool force = false); + void WM_ShowWindow(WId win); + void WM_HideWindow(WId win); + + WId WM_CreateWindow(WId parent = 0); + + // WM Utility Functions + QList<WId> WM_RootWindows(); //return all windows which have root as the parent + bool WM_ManageWindow(WId win, bool needsmap = true); //return whether the window is/should be managed + QRect WM_Window_Geom(WId win); //Return the current window geometry + void setupEventsForFrame(WId frame); + bool setupEventsForRoot(WId root = 0); + + // ICCCM Standards (older standards) + // -- WM_NAME + QString WM_ICCCM_GetName(WId win); + void WM_ICCCM_SetName(WId win, QString name); + // -- WM_ICON_NAME + QString WM_ICCCM_GetIconName(WId win); + void WM_ICCCM_SetIconName(WId win, QString name); + // --- WM_CLIENT_MACHINE + QString WM_ICCCM_GetClientMachine(WId win); + void WM_ICCCM_SetClientMachine(WId win, QString name); + // -- WM_CLASS + QString WM_ICCCM_GetClass(WId win); //Returns: "<instance name>::::<class name>" + void WM_ICCCM_SetClass(WId win, QString name); + // -- WM_TRANSIENT_FOR + WId WM_ICCCM_GetTransientFor(WId win); //Returns "win" for errors or no transient + void WM_ICCCM_SetTransientFor(WId win, WId transient); + // -- WM_SIZE_HINTS (older property?) + icccm_size_hints WM_ICCCM_GetSizeHints(WId win); //most values in structure are -1 if not set + //void WM_ICCCM_SetSizeHints(WId win, icccm_size_hints hints); + // -- WM_NORMAL_HINTS (newer property? - check for this before falling back on WM_SIZE_HINTS) + icccm_size_hints WM_ICCCM_GetNormalHints(WId win); //most values in structure are -1 if not set + //void WM_ICCCM_SetNormalHints(WId win, icccm_size_hints hints); + // -- WM_HINTS (contains WM_STATE) + + // -- WM_PROTOCOLS + ICCCM_PROTOCOLS WM_ICCCM_GetProtocols(WId win); + void WM_ICCCM_SetProtocols(WId win, ICCCM_PROTOCOLS flags); + + //NET_WM Standards (newer standards) + + // -- ROOT WINDOW PROPERTIES + // _NET_SUPPORTED + void WM_Set_Root_Supported(); //set the atom list of supported features on the root window + // _NET_CLIENT_LIST + // Note: client list ordered oldest->newest, stacking list ordered bottom->top + QList<WId> WM_Get_Client_List(bool stacking = false); + void WM_Set_Client_List(QList<WId> list, bool stacking=false); + + // _NET_NUMBER_OF_DESKTOPS + // Note: This is the number of virtual workspaces, not monitors + unsigned int WM_Get_Number_Desktops(); //return value equals 0 for errors + void WM_SetNumber_Desktops(unsigned int number); //should be at least 1 + + // _NET_DESKTOP_GEOMETRY + // Note: This property is the combined size and/or bounding rectangle of all monitors + // The definition works well for single-monitors, but gets really fuzzy for multiple monitors + QSize WM_Get_Desktop_Geometry(); + void WM_Set_Desktop_Geometry(QSize); + + // _NET_DESKTOP_VIEWPORT + // Note: This is the X/Y origin of the viewport for each monitor + // Thi is normally (0,0) , unless desktop larger than monitor supports + QList<QPoint> WM_Get_Desktop_Viewport(); + void WM_Set_Desktop_Viewport(QList<QPoint> list); + + // _NET_CURRENT_DESKTOP + // Note: Current workspace number. Range = 0 to (_NET_NUMBER_OF_DESKTOPS - 1) + int WM_Get_Current_Desktop(); //Returns -1 for errors + void WM_Set_Current_Desktop(unsigned int num); + + // _NET_DESKTOP_NAMES + QStringList WM_Get_Desktop_Names(); + void WM_Set_Desktop_Names(QStringList list); + + // _NET_ACTIVE_WINDOW + WId WM_Get_Active_Window(); + void WM_Set_Active_Window(WId win); + + // _NET_WORKAREA + // Note: The workarea is the recangle for each monitor where no space is reserved + // This accounts for any STRUT's that are set, within the current VIEWPORT + QList<QRect> WM_Get_Workarea(); + void WM_Set_Workarea(QList<QRect> list); + + // _NET_SUPPORTING_WM_CHECK + // Note: This needs to be set on two windows: root -> child, and child->child + // So the "set" function will do both at the same time + // The child window also needs the _NET_WM_NAME set to the window manager name + WId WM_Get_Supporting_WM(WId win); + void WM_Set_Supporting_WM(WId child); + + // _NET_VIRTUAL_ROOTS + QList<WId> WM_Get_Virtual_Roots(); + void WM_Set_Virtual_Roots(QList<WId> list); + + // _NET_DESKTOP_LAYOUT + // NOTE: Skip this implementation for now - is supposed to be set by a pager (not the WM) + // and the WM can choose to use/ignore it as necessary. + // (Just use the current XRandR layout instead of this setting/property) + + // _NET_SHOWING_DESKTOP + // Note: This is true/false depending on whether the WM is hiding all windows to show the desktop only + bool WM_Get_Showing_Desktop(); + void WM_Set_Showing_Desktop(bool show); + + // -- ROOT WINDOW MESSAGES/REQUESTS (for non-WM usage) + // _NET_CLOSE_WINDOW + void WM_Request_Close_Window(WId win); + + // _NET_MOVERESIZE_WINDOW + // Note: Used for finalized movement/resize operations + void WM_Request_MoveResize_Window(WId win, QRect geom, bool fromuser = false, LXCB::GRAVITY grav = LXCB::STATIC, LXCB::MOVERESIZE_WINDOW_FLAGS flags = LXCB::MOVERESIZE_WINDOW_FLAGS(LXCB::X | LXCB::Y | LXCB::WIDTH | LXCB::HEIGHT) ); + + // _NET_WM_MOVERESIZE + // Note: Used for interactive clicks/changes to a window size/position + // There are known race conditions/issues with this X format - so skip it for now (11/12/15) + + // _NET_RESTACK_WINDOW + // Note: Send a request to re-stack a window (win) with respect to another window (sibling) + // based on the flag to determine how the stack order should be changed + void WM_Request_Restack_Window(WId win, WId sibling, LXCB::STACK_FLAG flag); + + // _NET_REQUEST_FRAME_EXTENTS + // Note: This is used by client windows to get the _NET_FRAME_EXTENTS property pre-set + // by the WM before the window is actually mapped (just an estimate of the frame at least) + void WM_Request_Frame_Extents(WId win); + + // -- WINDOW PROPERTIES + // _NET_SUPPORTED + void WM_Set_Window_Supported(WId win); //set the atom list of supported features on the given window + + // _NET_WM_NAME + QString WM_Get_Name(WId win); + void WM_Set_Name(WId win, QString txt); + + // _NET_WM_VISIBLE_NAME + QString WM_Get_Visible_Name(WId win); + void WM_Set_Visible_Name(WId win, QString txt); + + // _NET_WM_ICON_NAME + QString WM_Get_Icon_Name(WId win); + void WM_Set_Icon_Name(WId win, QString txt); + + // _NET_WM_VISIBLE_ICON_NAME + QString WM_Get_Visible_Icon_Name(WId win); + void WM_Set_Visible_Icon_Name(WId win, QString txt); + + // _NET_WM_DESKTOP + // Note: This refers to the virtual workspace, not the monitor/screen number + int WM_Get_Desktop(WId win); //returns -1 if window on all desktops + void WM_Set_Desktop(WId win, int num); //use -1 to set it for all desktops + + // _NET_WM_WINDOW_TYPE + // Note: While this returns a list, they are ordered by priority for WM usage (use the first one known about) + QList<LXCB::WINDOWTYPE> WM_Get_Window_Type(WId win); + void WM_Set_Window_Type(WId win, QList<LXCB::WINDOWTYPE> list); + + // _NET_WM_STATE + QList<LXCB::WINDOWSTATE> WM_Get_Window_States(WId win); + void WM_Set_Window_States(WId win, QList<LXCB::WINDOWSTATE> list); + + // _NET_WM_ALLOWED_ACTIONS + QList<LXCB::WINDOWACTION> WM_Get_Window_Actions(WId win); + void WM_Set_Window_Actions(WId win, QList<LXCB::WINDOWACTION> list); + + // _NET_WM_STRUT + QList<unsigned int> WM_Get_Window_Strut(WId win); //Returns: [left,right,top,bottom] margins in pixels (always length 4) + void WM_Set_Window_Strut(WId win, QList<unsigned int> margins); //Input: [left, right, top, bottom] - must be length 4 + + // _NET_WM_STRUT_PARTIAL + QList<strut_geom> WM_Get_Window_Strut_Partial(WId win); //Returns: [left,right,top,bottom] struts + void WM_Set_Window_Strut_Partial(WId win, QList<strut_geom> struts); //Input: [left,right,top,bottom] - must be length 4 + + // _NET_WM_ICON_GEOMETRY + QRect WM_Get_Icon_Geometry(WId win); + void WM_Set_Icon_Geometry(WId win, QRect geom); + + // _NET_WM_ICON + // Note: Don't write a "Set" routine for this - is handled on the client side and not the WM/DE side + QIcon WM_Get_Icon(WId win); + + // _NET_WM_PID + // Note: Don't write a "Set" routine for this - is handled on the client side and not the WM/DE side + unsigned int WM_Get_Pid(WId win); + + // _NET_WM_HANDLED_ICONS + // Note: Probably not going to need this - is used by pagers exclusively to tell the WM + // not to provide task manager icons (not needed for an integrated WM/DE combination) + bool WM_Get_Handled_Icons(WId win); + void WM_Set_Handled_Icons(WId win, bool set); + + // _NET_WM_USER_TIME + // Note: The user time property on a client window is supposed to be updated on user activity, + // allowing the WM to be able to distinguish user activity from automated window actions + unsigned int WM_Get_User_Time(WId win); + void WM_Set_User_Time(WId win, unsigned int xtime); + + // _NET_WM_USER_TIME_WINDOW + // This returns the window to watch for time update events, + // instead of constantly polling all the (toplevel?) windows for the app + // IGNORED - xcb_ewmh library does not appear to have valid support for this property yet (11/13/15) + //WId WM_Get_User_Time_WIndow(WId win); + //void WM_Set_User_Time_Window(WId win, WId utwin); + + // _NET_FRAME_EXTENTS + QList<unsigned int> WM_Get_Frame_Extents(WId win); //Returns: [left,right,top,bottom] margins in pixels (always length 4) + void WM_Set_Frame_Extents(WId win, QList<unsigned int> margins); //Input: [left, right, top, bottom] - must be length 4 + + // _NET_WM_OPAQUE_REGION + // NOT SUPPORTED - missing in xcb_ewmh library (11/13/15) + + // _NET_WM_BYPASS_COMPOSITOR + // NOT SUPPORTED - missing in xcb_ewmh library (11/13/15) + + // === SPECIAL WM PROTOCOLS (EWMH) === + // _NET_WM_PING + // Note: Used to determine if a window/app is hung before killing the process (with PID) + // The client window should respond instantly if it is still active (waiting on user input for instance) + void WM_Send_Ping(WId win); + + // _NET_WM_SYNC_REQUEST + uint64_t WM_Get_Sync_Request_Counter(WId win); + //void WM_Set_Sync_Request_Counter(WId win, uint64_t count); + + // _NET_WM_FULLSCREEN_MONITORS + QList<unsigned int> WM_Get_Fullscreen_Monitors(WId win); //Returns: [top,bottom,left,right] monitor numbers for window to use when fullscreen + void WM_Set_Fullscreen_Montors(WId win, QList<unsigned int> list); //Input: [top,bottom,left,right] monitor numbers + + // _NET_WM_CM_S(n) + // Note: This is used to check/register the compositing manager for the current X screen (#n) + WId WM_Get_CM_Owner(); + void WM_Set_CM_Owner(WId win); + +private: + QList<xcb_atom_t> ATOMS; + QStringList atoms; + + void createWMAtoms(); //fill the private lists above +}; +//Now also declare the flags for Qt to be able to use normal operations on them +Q_DECLARE_OPERATORS_FOR_FLAGS(LXCB::ICCCM_PROTOCOLS); +Q_DECLARE_OPERATORS_FOR_FLAGS(LXCB::MOVERESIZE_WINDOW_FLAGS); +Q_DECLARE_OPERATORS_FOR_FLAGS(LXCB::SIZE_HINTS); + +#endif diff --git a/src-qt5/core/libLumina/LuminaXDG.cpp b/src-qt5/core/libLumina/LuminaXDG.cpp new file mode 100644 index 00000000..c3c89d37 --- /dev/null +++ b/src-qt5/core/libLumina/LuminaXDG.cpp @@ -0,0 +1,1157 @@ +//=========================================== +// Lumina-DE source code +// Copyright (c) 2013-2015, Ken Moore +// Available under the 3-clause BSD license +// See the LICENSE file for full details +//=========================================== +#include "LuminaXDG.h" +#include "LuminaOS.h" +#include "LuminaUtils.h" +#include <QObject> +#include <QMediaPlayer> +#include <QSvgRenderer> + +static QStringList mimeglobs; +static qint64 mimechecktime; + +//==== LFileInfo Functions ==== +//Need some extra information not usually available by a QFileInfo +void LFileInfo::loadExtraInfo(){ + //Now load the extra information + if(this->isDir()){ + mime = "inode/directory"; + //Special directory icons + QString name = this->fileName().toLower(); + if(name=="desktop"){ icon = "user-desktop"; } + else if(name=="tmp"){ icon = "folder-temp"; } + else if(name=="video" || name=="videos"){ icon = "folder-video"; } + else if(name=="music" || name=="audio"){ icon = "folder-sound"; } + else if(name=="projects" || name=="devel"){ icon = "folder-development"; } + else if(name=="notes"){ icon = "folder-txt"; } + else if(name=="downloads"){ icon = "folder-downloads"; } + else if(name=="documents"){ icon = "folder-documents"; } + else if(name=="images" || name=="pictures"){ icon = "folder-image"; } + else if( !this->isReadable() ){ icon = "folder-locked"; } + }else if( this->suffix()=="desktop"){ + mime = "application/x-desktop"; + icon = "application-x-desktop"; //default value + bool ok = false; + desk = LXDG::loadDesktopFile(this->absoluteFilePath(), ok); + if(ok){ + //use the specific desktop file info (if possible) + if(!desk.icon.isEmpty()){ icon = desk.icon; } + } + }else{ + //Generic file, just determine the mimetype + mime = LXDG::findAppMimeForFile(this->fileName()); + } +} +LFileInfo::LFileInfo(QString filepath){ //overloaded contructor + this->setFile(filepath); + loadExtraInfo(); +} +LFileInfo::LFileInfo(QFileInfo info){ //overloaded contructor + this->swap(info); //use the given QFileInfo without re-loading it + loadExtraInfo(); +} + +//Functions for accessing the extra information +// -- Return the mimetype for the file +QString LFileInfo::mimetype(){ + if(mime=="inode/directory"){ return ""; } + else{ return mime; } +} + +// -- Return the icon to use for this file +QString LFileInfo::iconfile(){ + if(!icon.isEmpty()){ + return icon; + }else{ + if(!mime.isEmpty()){ + QString tmp = mime; + tmp.replace("/","-"); + return tmp; + }else if(this->isExecutable()){ + return "application-x-executable"; + } + } + return ""; //Fall back to nothing +} + +// -- Check if this is an XDG desktop file +bool LFileInfo::isDesktopFile(){ + return (!desk.filePath.isEmpty()); +} + +// -- Allow access to the XDG desktop data structure +XDGDesktop* LFileInfo::XDG(){ + return &desk; +} + +// -- Check if this is a readable image file (for thumbnail support) +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 + return ( !LUtils::imageExtensions().filter(this->suffix().toLower()).isEmpty() ); +} + +bool LFileInfo::isAVFile(){ + return (mime.startsWith("audio/") || mime.startsWith("video/") ); +} + + +//==== LXDG Functions ==== +XDGDesktop LXDG::loadDesktopFile(QString filePath, bool& ok){ + //Create the outputs + ok=false; + XDGDesktop DF; + DF.isHidden=false; + DF.useTerminal=false; + DF.startupNotify=false; + DF.type = XDGDesktop::APP; + DF.filePath = filePath; + DF.lastRead = QDateTime::currentDateTime(); + DF.exec = DF.tryexec = ""; // just to make sure this is initialized + + //Get the current localization code + QString lang = QLocale::system().name(); //lang code + QString slang = lang.section("_",0,0); //short lang code + + //Read in the File + bool insection=false; + bool inaction=false; + QStringList file = LUtils::readFile(filePath); + if(file.isEmpty()){ return DF; } + //if(filePath.contains("pcbsd")){ qDebug() << "Check File:" << filePath << lang << slang; } + XDGDesktopAction CDA; //current desktop action + for(int i=0; i<file.length(); i++){ + 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){ + insection=false; inaction=false; + //Add the current Action structure to the main desktop structure if appropriate + if(!CDA.ID.isEmpty()){ DF.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 ")){ + //Grab the ID of the action out of the label + CDA.ID = line.section("]",0,0).section("Desktop Action",1,1).simplified(); + inaction = true; + continue; + }else if( (!insection && !inaction) || line.startsWith("#")){ continue; } + //Now parse out the file + line = line.simplified(); + QString var = line.section("=",0,0).simplified(); + QString loc = var.section("[",1,1).section("]",0,0).simplified(); // localization + var = var.section("[",0,0).simplified(); //remove the localization + QString val = line.section("=",1,50).simplified(); + //if(filePath.contains("pcbsd")){ qDebug() << " -- " << var << val << loc; } + //------------------- + if(var=="Name"){ + if(insection){ + if(DF.name.isEmpty() && loc.isEmpty()){ DF.name = val; } + else if(DF.name.isEmpty() && loc==slang){ DF.name = val; } //short locale code + else if(loc == lang){ DF.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; } + } + //hasName = true; + }else if(var=="GenericName" && insection){ + if(DF.genericName.isEmpty() && loc.isEmpty()){ DF.genericName = val; } + else if(DF.genericName.isEmpty() && loc==slang){ DF.genericName = val; } //short locale code + else if(loc == lang){ DF.genericName = val; } + }else if(var=="Comment" && insection){ + if(DF.comment.isEmpty() && loc.isEmpty()){ DF.comment = val; } + else if(DF.comment.isEmpty() && loc==slang){ DF.comment = val; } //short locale code + else if(loc == lang){ DF.comment = val; } + }else if(var=="Icon"){ + if(insection){ + if(DF.icon.isEmpty() && loc.isEmpty()){ DF.icon = val; } + else if(DF.icon.isEmpty() && loc==slang){ DF.icon = val; } //short locale code + else if(loc == lang){ DF.icon = val; } + }else if(inaction){ + if(CDA.icon.isEmpty() && loc.isEmpty()){ CDA.icon = val; } + else if(CDA.icon.isEmpty() && loc==slang){ CDA.icon = val; } //short locale code + else if(loc == lang){ CDA.icon = val; } + } + } + else if( (var=="TryExec") && (DF.tryexec.isEmpty()) && insection) { DF.tryexec = val; } + else if(var=="Exec"){ + if(insection && DF.exec.isEmpty() ){ DF.exec = val; } + else if(inaction && CDA.exec.isEmpty() ){ CDA.exec = val; } + } + else if( (var=="Path") && (DF.path.isEmpty() ) && insection){ DF.path = val; } + else if(var=="NoDisplay" && !DF.isHidden && insection){ DF.isHidden = (val.toLower()=="true"); } + else if(var=="Hidden" && !DF.isHidden && insection){ DF.isHidden = (val.toLower()=="true"); } + else if(var=="Categories" && insection){ DF.catList = val.split(";",QString::SkipEmptyParts); } + else if(var=="OnlyShowIn" && insection){ DF.showInList = val.split(";",QString::SkipEmptyParts); } + else if(var=="NotShowIn" && insection){ DF.notShowInList = val.split(";",QString::SkipEmptyParts); } + else if(var=="Terminal" && insection){ DF.useTerminal= (val.toLower()=="true"); } + else if(var=="Actions" && insection){ DF.actionList = val.split(";",QString::SkipEmptyParts); } + else if(var=="MimeType" && insection){ DF.mimeList = val.split(";",QString::SkipEmptyParts); } + else if(var=="Keywords" && insection){ + if(DF.keyList.isEmpty() && loc.isEmpty()){ DF.keyList = val.split(";",QString::SkipEmptyParts); } + else if(loc == lang){ DF.keyList = val.split(";",QString::SkipEmptyParts); } + } + else if(var=="StartupNotify" && insection){ DF.startupNotify = (val.toLower()=="true"); } + else if(var=="StartupWMClass" && insection){ DF.startupWM = val; } + else if(var=="URL" && insection){ DF.url = val;} + else if(var=="Type" && insection){ + if(val.toLower()=="application"){ DF.type = XDGDesktop::APP; } + else if(val.toLower()=="link"){ DF.type = XDGDesktop::LINK; } + else if(val.toLower()=="dir"){ DF.type = XDGDesktop::DIR; } + else{ DF.type = XDGDesktop::BAD; } //Unknown type + //hasType = true; + } + } //end reading file + //file.close(); + //If there are OnlyShowIn desktops listed, add them to the name + if( !DF.showInList.isEmpty() && !DF.showInList.contains("Lumina", Qt::CaseInsensitive) ){ + /*QStringList added; + //Need to be careful about case insensitivity here - the QList functions don't understand it + for(int i=0; i<DF.showInList.length(); i++){ + if(DF.showInList[i].toLower()!="lumina"){ added << DF.showInList[i]; } + }*/ + //if(!added.isEmpty()){ + DF.name.append(" ("+DF.showInList.join(", ")+")"); + //} + } + //Quick fix for showing "wine" applications (which quite often don't list a category, or have other differences) + if(DF.catList.isEmpty() && filePath.contains("/wine/")){ + DF.catList << "Wine"; //Internal Lumina category only (not in XDG specs as of 11/14/14) + //Also add a fix for the location of Wine icons + if( !DF.icon.isEmpty() ){ + QStringList sizes; sizes << "256x256" << "128x128" << "64x64" << "48x48" << "32x32" << "16x16"; + QString upath = QDir::homePath()+"/.local/share/icons/hicolor/%1/apps/%2.png"; + //qDebug() << "Wine App: Check icon" << upath; + for(int i=0; i<sizes.length(); i++){ + if( QFile::exists(upath.arg(sizes[i],DF.icon)) ){ + DF.icon = upath.arg(sizes[i],DF.icon); + //qDebug() << " - Found Icon:" << DF.icon; + break; + } + } + } + } + //Return the structure + //if (hasName && hasType) ok = true; //without Name and Type, the structure cannot be a valid .desktop file + ok = true; //was able to open/read the file - validity determined later + return DF; +} + +bool LXDG::saveDesktopFile(XDGDesktop dFile, bool merge){ + qDebug() << "Save Desktop File:" << dFile.filePath << "Merge:" << merge; + bool autofile = dFile.filePath.contains("/autostart/"); //use the "Hidden" field instead of the "NoDisplay" + int insertloc = -1; + QStringList info; + if(QFile::exists(dFile.filePath) && merge){ + //Load the existing file and merge in in any changes + info = LUtils::readFile(dFile.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; + QString tmp = ""; + if(!info.filter("Name=").isEmpty()){ tmp = info.filter("Name=").first().section("=",1,50); } + clearName=(tmp!=dFile.name); + tmp.clear(); + if(!info.filter("Comment=").isEmpty()){ tmp = info.filter("Comment=").first().section("=",1,50); } + clearComment=(tmp!=dFile.comment); + tmp.clear(); + if(!info.filter("GenericName=").isEmpty()){ tmp = info.filter("GenericName=").first().section("=",1,50); } + clearGName=(tmp!=dFile.genericName); + //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; + continue; + }else if(info[i].startsWith("[")){ + if(insection){ insertloc = i; } //save this location for later insertions + insection = false; + continue; + } + if(!insection || info[i].isEmpty() || info[i].section("#",0,0).simplified().isEmpty()){ continue; } + QString var = info[i].section("=",0,0); + QString val = info[i].section("=",1,50).simplified(); + //NOTE: Clear the dFile variable as it is found/set in the file (to keep track of what has been used already) + // For boolian values, set them to false + // --LOCALIZED VALUES -- + if(var.startsWith("Name")){ + if(var.contains("[") && clearName){ info.removeAt(i); i--; continue;} + else if(!var.contains("[")){ info[i] = var+"="+dFile.name; dFile.name.clear(); } + }else if(var.startsWith("GenericName")){ + if(var.contains("[") && clearGName){ info.removeAt(i); i--; continue;} + else if(!var.contains("[")){ info[i] = var+"="+dFile.genericName; dFile.genericName.clear(); } + }else if(var.startsWith("Comment")){ + if(var.contains("[") && clearComment){ info.removeAt(i); i--; continue;} + else if(!var.contains("[")){ info[i] = var+"="+dFile.comment; dFile.comment.clear(); } + + // --STRING/LIST VALUES-- + }else if(var=="Exec"){ info[i] = var+"="+dFile.exec; dFile.exec.clear(); } + else if(var=="TryExec"){ info[i] = var+"="+dFile.tryexec; dFile.tryexec.clear(); } + else if(var=="Path"){ info[i] = var+"="+dFile.path; dFile.path.clear(); } + else if(var=="Icon"){ info[i] = var+"="+dFile.icon; dFile.icon.clear(); } + else if(var=="StartupWMClass"){ info[i] = var+"="+dFile.startupWM; dFile.startupWM.clear(); } + else if(var=="MimeType"){ info[i] = var+"="+dFile.mimeList.join(";"); dFile.mimeList.clear(); } + else if(var=="Categories"){ info[i] = var+"="+dFile.catList.join(";"); dFile.catList.clear(); } + else if(var=="Keywords"){ info[i] = var+"="+dFile.keyList.join(";"); dFile.keyList.clear(); } + else if(var=="Actions"){ info[i] = var+"="+dFile.actionList.join(";"); dFile.actionList.clear(); } + else if(var=="OnlyShowIn"){ info[i] = var+"="+dFile.showInList.join(";"); dFile.showInList.clear(); } + else if(var=="NotShowIn"){ info[i] = var+"="+dFile.notShowInList.join(";"); dFile.notShowInList.clear(); } + else if(var=="URL"){ info[i] = var+"="+dFile.url; dFile.url.clear(); } + + // --BOOLIAN VALUES-- + else if(var=="Hidden"){ + if(!autofile){ info.removeAt(i); i--; continue; } + else{ info[i] = var+"="+(dFile.isHidden ? "true": "false"); dFile.isHidden=false;} + }else if(var=="NoDisplay"){ + if(autofile){ info.removeAt(i); i--; continue; } + else{ info[i] = var+"="+(dFile.isHidden ? "true": "false"); dFile.isHidden=false;} + }else if(var=="Terminal"){ + info[i] = var+"="+(dFile.useTerminal ? "true": "false"); dFile.useTerminal=false; + }else if(var=="StartupNotify"){ + info[i] = var+"="+(dFile.startupNotify ? "true": "false"); dFile.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 + // (pre-set some values here which are always required) + info << "[Desktop Entry]"; + info << "Version=1.0"; + if(dFile.type==XDGDesktop::APP){ info << "Type=Application"; } + else if(dFile.type==XDGDesktop::LINK){ info << "Type=Link"; } + else if(dFile.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( !dFile.exec.isEmpty() ){ info.insert(insertloc,"Exec="+dFile.exec); } + if( !dFile.tryexec.isEmpty() ){ info.insert(insertloc,"TryExec="+dFile.tryexec); } + if( !dFile.path.isEmpty() ){ info.insert(insertloc,"Path="+dFile.path); } + if( !dFile.icon.isEmpty() ){ info.insert(insertloc,"Icon="+dFile.icon); } + if( !dFile.name.isEmpty() ){ info.insert(insertloc,"Name="+dFile.name); } + if( !dFile.genericName.isEmpty() ){ info.insert(insertloc,"GenericName="+dFile.genericName); } + if( !dFile.comment.isEmpty() ){ info.insert(insertloc,"Comment="+dFile.comment); } + if( !dFile.startupWM.isEmpty() ){ info.insert(insertloc,"StartupWMClass="+dFile.startupWM); } + if( !dFile.mimeList.isEmpty() ){ info.insert(insertloc,"MimeType="+dFile.mimeList.join(";")); } + if( !dFile.catList.isEmpty() ){ info.insert(insertloc,"Categories="+dFile.catList.join(";")); } + if( !dFile.keyList.isEmpty() ){ info.insert(insertloc,"Keywords="+dFile.keyList.join(";")); } + if( !dFile.actionList.isEmpty() ){ info.insert(insertloc,"Actions="+dFile.actionList.join(";")); } + if( !dFile.showInList.isEmpty() ){ info.insert(insertloc,"OnlyShowIn="+dFile.showInList.join(";")); } + else if( !dFile.notShowInList.isEmpty() ){ info.insert(insertloc,"NotShowIn="+dFile.notShowInList.join(";")); } + if( !dFile.url.isEmpty() ){ info.insert(insertloc,"URL="+dFile.url); } + if( dFile.isHidden && autofile ){ info.insert(insertloc,"Hidden=true"); } + else if(dFile.isHidden){ info.insert(insertloc,"NoDisplay=true"); } + if( dFile.useTerminal){ info.insert(insertloc,"Terminal=true"); } + if( dFile.startupNotify ){ info.insert(insertloc,"StartupNotify=true"); } + + //Now save the file + return LUtils::writeFile(dFile.filePath, info, true); + +} + +bool LXDG::checkValidity(XDGDesktop dFile, bool showAll){ + bool ok=true; + bool DEBUG = false; + if(DEBUG){ qDebug() << "[LXDG] Check File validity:" << dFile.name << dFile.filePath; } + switch (dFile.type){ + case XDGDesktop::BAD: + ok=false; + if(DEBUG){ qDebug() << " - Bad file type"; } + break; + case XDGDesktop::APP: + if(!dFile.tryexec.isEmpty() && !LXDG::checkExec(dFile.tryexec)){ ok=false; if(DEBUG){ qDebug() << " - tryexec does not exist";} } + else if(dFile.exec.isEmpty() || dFile.name.isEmpty()){ ok=false; if(DEBUG){ qDebug() << " - exec or name is empty";} } + else if(!LXDG::checkExec(dFile.exec.section(" ",0,0,QString::SectionSkipEmpty)) ){ ok=false; if(DEBUG){ qDebug() << " - first exec binary does not exist";} } + break; + case XDGDesktop::LINK: + ok = !dFile.url.isEmpty(); + if(DEBUG && !ok){ qDebug() << " - Link with missing URL"; } + break; + case XDGDesktop::DIR: + ok = !dFile.path.isEmpty(); + if(DEBUG && !ok){ qDebug() << " - Dir with missing path"; } + break; + default: + ok=false; + if(DEBUG){ qDebug() << " - Unknown file type"; } + } + if(!showAll){ + if(!dFile.showInList.isEmpty()){ ok = dFile.showInList.contains("Lumina", Qt::CaseInsensitive); } + else if(!dFile.notShowInList.isEmpty()){ ok = !dFile.notShowInList.contains("Lumina",Qt::CaseInsensitive); } + else if(dFile.name.isEmpty()){ ok = false; } + } + return ok; +} + +bool LXDG::checkExec(QString exec){ + //Return true(good) or false(bad) + if(exec.startsWith("/")){ return QFile::exists(exec); } + else{ + QStringList paths = QString(getenv("PATH")).split(":"); + for(int i=0; i<paths.length(); i++){ + if(QFile::exists(paths[i]+"/"+exec)){ return true; } + } + } + return false; //could not find the executable in the current path(s) +} + +QStringList LXDG::systemApplicationDirs(){ + //Returns a list of all the directories where *.desktop files can be found + QStringList appDirs = QString(getenv("XDG_DATA_HOME")).split(":"); + appDirs << QString(getenv("XDG_DATA_DIRS")).split(":"); + if(appDirs.isEmpty()){ appDirs << "/usr/local/share" << "/usr/share" << LOS::AppPrefix()+"/share" << LOS::SysPrefix()+"/share" << L_SHAREDIR; } + appDirs.removeDuplicates(); + //Now create a valid list + QStringList out; + 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 + // (looking at you KDE - stick to the standards!!) + out << LUtils::listSubDirectories(appDirs[i]+"/applications"); + //QDir dir(appDirs[i]+"/applications"); + //QStringList subs = dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot, QDir::Name); + //qDebug() << "Adding subdirectories:" << appDirs[i]+"/applications/["+subs.join(", ")+"]"; + //for(int s=0; s<subs.length(); s++){ out << dir.absoluteFilePath(subs[s]); } + } + } + //qDebug() << "System Application Dirs:" << out; + return out; +} + +QList<XDGDesktop> LXDG::systemDesktopFiles(bool showAll, bool showHidden){ + //Returns a list of all the unique *.desktop files that were found + QStringList appDirs = LXDG::systemApplicationDirs(); + QStringList found; //for avoiding duplicate apps + QList<XDGDesktop> out; + bool ok; //for internal use only + for(int i=0; i<appDirs.length(); i++){ + QDir dir(appDirs[i]); + QStringList apps = dir.entryList(QStringList() << "*.desktop",QDir::Files, QDir::Name); + for(int a=0; a<apps.length(); a++){ + ok=false; + XDGDesktop dFile = LXDG::loadDesktopFile(dir.absoluteFilePath(apps[a]),ok); + if( LXDG::checkValidity(dFile, showAll) ){ + if( !found.contains(dFile.name) && (!dFile.isHidden || showHidden) ){ + out << dFile; + found << dFile.name; + } + } + } + } + return out; +} + +QHash<QString,QList<XDGDesktop> > LXDG::sortDesktopCats(QList<XDGDesktop> apps){ + //Sort the list of applications into their different categories (main categories only) + //Create the category lists + QList<XDGDesktop> multimedia, dev, ed, game, graphics, network, office, science, settings, sys, utility, other, wine; + //Sort the apps into the lists + for(int i=0; i<apps.length(); i++){ + if(apps[i].catList.contains("AudioVideo")){ multimedia << apps[i]; } + else if(apps[i].catList.contains("Development")){ dev << apps[i]; } + else if(apps[i].catList.contains("Education")){ ed << apps[i]; } + else if(apps[i].catList.contains("Game")){ game << apps[i]; } + else if(apps[i].catList.contains("Graphics")){ graphics << apps[i]; } + else if(apps[i].catList.contains("Network")){ network << apps[i]; } + else if(apps[i].catList.contains("Office")){ office << apps[i]; } + else if(apps[i].catList.contains("Science")){ science << apps[i]; } + else if(apps[i].catList.contains("Settings")){ settings << apps[i]; } + else if(apps[i].catList.contains("System")){ sys << apps[i]; } + else if(apps[i].catList.contains("Utility")){ utility << apps[i]; } + else if(apps[i].catList.contains("Wine")){ wine << apps[i]; } + else{ other << apps[i]; } + } + //Now create the output hash + QHash<QString,QList<XDGDesktop> > out; + if(!multimedia.isEmpty()){ out.insert("Multimedia", LXDG::sortDesktopNames(multimedia)); } + if(!dev.isEmpty()){ out.insert("Development", LXDG::sortDesktopNames(dev)); } + if(!ed.isEmpty()){ out.insert("Education", LXDG::sortDesktopNames(ed)); } + if(!game.isEmpty()){ out.insert("Game", LXDG::sortDesktopNames(game)); } + if(!graphics.isEmpty()){ out.insert("Graphics", LXDG::sortDesktopNames(graphics)); } + if(!network.isEmpty()){ out.insert("Network", LXDG::sortDesktopNames(network)); } + if(!office.isEmpty()){ out.insert("Office", LXDG::sortDesktopNames(office)); } + if(!science.isEmpty()){ out.insert("Science", LXDG::sortDesktopNames(science)); } + if(!settings.isEmpty()){ out.insert("Settings", LXDG::sortDesktopNames(settings)); } + if(!sys.isEmpty()){ out.insert("System", LXDG::sortDesktopNames(sys)); } + if(!utility.isEmpty()){ out.insert("Utility", LXDG::sortDesktopNames(utility)); } + if(!wine.isEmpty()){ out.insert("Wine", LXDG::sortDesktopNames(wine)); } + if(!other.isEmpty()){ out.insert("Unsorted", LXDG::sortDesktopNames(other)); } + //return the resulting hash + return out; +} + +//Return the icon to use for the given category +QString LXDG::DesktopCatToIcon(QString cat){ + QString icon = "applications-other"; + if(cat=="Multimedia"){ icon = "applications-multimedia"; } + else if(cat=="Development"){ icon = "applications-development"; } + else if(cat=="Education"){ icon = "applications-education"; } + else if(cat=="Game"){ icon = "applications-games"; } + else if(cat=="Graphics"){ icon = "applications-graphics"; } + else if(cat=="Network"){ icon = "applications-internet"; } + else if(cat=="Office"){ icon = "applications-office"; } + else if(cat=="Science"){ icon = "applications-science"; } + else if(cat=="Settings"){ icon = "preferences-system"; } + else if(cat=="System"){ icon = "applications-system"; } + else if(cat=="Utility"){ icon = "applications-utilities"; } + else if(cat=="Wine"){ icon = "wine"; } + return icon; +} + +QList<XDGDesktop> LXDG::sortDesktopNames(QList<XDGDesktop> apps){ + //Sort the list by name of the application + QHash<QString, XDGDesktop> sorter; + for(int i=0; i<apps.length(); i++){ + sorter.insert(apps[i].name.toLower(), apps[i]); + } + QStringList keys = sorter.keys(); + keys.sort(); + //Re-assemble the output list + QList<XDGDesktop> out; + for(int i=0; i<keys.length(); i++){ + out << sorter[keys[i]]; + } + return out; +} + +QString LXDG::getDesktopExec(XDGDesktop app, QString ActionID){ + //Generate the executable line for the application + QString out; + QString exec = app.exec; + if( !ActionID.isEmpty() ){ + //Go through and grab the proper exec for the listed action + for(int i=0; i<app.actions.length(); i++){ + if(app.actions[i].ID == ActionID){ + exec = app.actions[i].exec; + break; + } + } + } + + if(exec.isEmpty()){ return ""; } + else if(app.useTerminal){ + out = "xterm -lc -e "+exec; + }else{ + out =exec; + } + //Now perform any of the XDG flag substitutions as appropriate (9/2014 standards) + if(out.contains("%i") && !app.icon.isEmpty() ){ out.replace("%i", "--icon \'"+app.icon+"\'"); } + if(out.contains("%c")){ + if(!app.name.isEmpty()){ out.replace("%c", "\'"+app.name+"\'"); } + else if(!app.genericName.isEmpty()){ out.replace("%c", "\'"+app.genericName+"\'"); } + else{ out.replace("%c", "\'"+app.filePath.section("/",-1).section(".desktop",0,0)+"\'"); } + } + if(out.contains("%k")){ out.replace("%k", "\'"+app.filePath+"\'"); } + return out; +} + +void LXDG::setEnvironmentVars(){ + //Set the default XDG environment variables if not already set + setenv("XDG_DATA_HOME",QString(QDir::homePath()+"/.local/share").toUtf8(), 0); + setenv("XDG_CONFIG_HOME",QString(QDir::homePath()+"/.config").toUtf8(), 0); + setenv("XDG_DATA_DIRS","/usr/local/share:/usr/share", 0); + setenv("XDG_CONFIG_DIRS","/etc/xdg:/usr/local/etc/xdg", 0); + setenv("XDG_CACHE_HOME",QString(QDir::homePath()+"/.cache").toUtf8(), 0); + //Don't set "XDG_RUNTIME_DIR" yet - need to look into the specs +} + +QIcon LXDG::findIcon(QString iconName, QString fallback){ + //NOTE: This was re-written on 11/10/15 to avoid using the QIcon::fromTheme() framework + // -- Too many issues with SVG files and/or search paths with the built-in system + + //Check if the icon is an absolute path and exists + bool DEBUG =false; + if(DEBUG){ qDebug() << "[LXDG] Find icon for:" << iconName; } + if(QFile::exists(iconName) && iconName.startsWith("/")){ return QIcon(iconName); } + else if(iconName.startsWith("/")){ iconName.section("/",-1); } //Invalid absolute path, just look for the icon + //Check if the icon is actually given + if(iconName.isEmpty()){ + if(fallback.isEmpty()){ return QIcon(); } + else{ return LXDG::findIcon(fallback, ""); } + } + //Now try to find the icon from the theme + if(DEBUG){ qDebug() << "[LXDG] Start search for icon"; } + //Get the currently-set theme + QString cTheme = QIcon::themeName(); + if(cTheme.isEmpty()){ + QIcon::setThemeName("oxygen"); + cTheme = "oxygen"; + } + //Make sure the current search paths correspond to this theme + if( QDir::searchPaths("icontheme").filter("/"+cTheme+"/").isEmpty() ){ + //Need to reset search paths: setup the "icontheme" "oxygen" and "fallback" sets + // - Get all the base icon directories + QStringList paths; + paths << QDir::homePath()+"/.icons/"; //ordered by priority - local user dirs first + 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 load all the dirs into the search paths + QStringList theme, oxy, fall; + for(int i=0; i<paths.length(); i++){ + theme << getChildIconDirs( paths[i]+cTheme); + oxy << getChildIconDirs(paths[i]+"oxygen"); //Lumina base icon set + fall << getChildIconDirs(paths[i]+"hicolor"); //XDG fallback (apps add to this) + } + //fall << LOS::AppPrefix()+"share/pixmaps"; //always use this as well as a final fallback + QDir::setSearchPaths("icontheme", theme); + QDir::setSearchPaths("oxygen", oxy); + QDir::setSearchPaths("fallback", fall); + //qDebug() << "Setting Icon Search Paths:" << "\nicontheme:" << theme << "\noxygen:" << oxy << "\nfallback:" << fall; + } + //Find the icon in the search paths + QIcon ico; + QStringList srch; srch << "icontheme" << "oxygen" << "fallback"; + for(int i=0; i<srch.length() && ico.isNull(); i++){ + //Look for a svg first + if(QFile::exists(srch[i]+":"+iconName+".svg") ){ + //Be careful about how an SVG is loaded - needs to render the image onto a paint device + QSvgRenderer svg; + if( svg.load(srch[i]+":"+iconName+".svg") ){ + //Could be loaded - now check that it is version 1.1+ (Qt has issues with 1.0? (LibreOffice Icons) ) + float version = 1.1; //only downgrade files that explicitly set the version as older + QString svginfo = LUtils::readFile(srch[i]+":"+iconName+".svg").join("\n").section("<svg",1,1).section(">",0,0); + svginfo.replace("\t"," "); svginfo.replace("\n"," "); + if(svginfo.contains(" version=")){ version = svginfo.section(" version=\"",1,1).section("\"",0,0).toFloat(); } + if(version>=1.1){ + ico.addFile(srch[i]+":"+iconName+".svg"); //could be loaded/parsed successfully + }else{ + //qDebug() << "Old SVG Version file:" << iconName+".svg Theme:" << srch[i]; + //qDebug() << "SVGInfo:" << svginfo; + } + }else{ + qDebug() << "Found bad SVG file:" << iconName+".svg Theme:" << srch[i]; + } + } + if(QFile::exists(srch[i]+":"+iconName+".png")){ + //simple PNG image - load directly into the QIcon structure + ico.addFile(srch[i]+":"+iconName+".png"); + } + + } + //If still no icon found, look for any image format inthe "pixmaps" directory + if(ico.isNull()){ + if(QFile::exists(LOS::AppPrefix()+"share/pixmaps/"+iconName)){ + ico.addFile(LOS::AppPrefix()+"share/pixmaps/"+iconName); + }else{ + //Need to scan for any close match in the directory + QDir pix(LOS::AppPrefix()+"share/pixmaps"); + QStringList formats = LUtils::imageExtensions(); + QStringList found = pix.entryList(QStringList() << iconName, QDir::Files, QDir::Unsorted); + if(found.isEmpty()){ found = pix.entryList(QStringList() << iconName+"*", QDir::Files, QDir::Unsorted); } + //qDebug() << "Found pixmaps:" << found << formats; + //Use the first one found that is a valid format + for(int i=0; i<found.length(); i++){ + if( formats.contains(found[i].section(".",-1).toLower()) ){ + ico.addFile( pix.absoluteFilePath(found[i]) ); + break; + } + } + + } + } + //Use the fallback icon if necessary + if(ico.isNull() && !fallback.isEmpty()){ + ico = LXDG::findIcon(fallback,""); + } + if(ico.isNull()){ + qDebug() << "Could not find icon:" << iconName << fallback; + } + //Return the icon + return ico; +} + +QStringList LXDG::getChildIconDirs(QString parent){ + //This is a recursive function that returns the absolute path(s) of directories with *.png files + QDir D(parent); + QStringList out; + QStringList dirs = D.entryList(QDir::Dirs | QDir::NoDotAndDotDot, QDir::Name); + if(!dirs.isEmpty() && (dirs.contains("32x32") || dirs.contains("scalable")) ){ + //Need to sort these directories by image size + //qDebug() << " - Parent:" << parent << "Dirs:" << dirs; + for(int i=0; i<dirs.length(); i++){ + if(dirs[i].contains("x")){ dirs[i].prepend( QString::number(10-dirs[i].section("x",0,0).length())+QString::number(10-dirs[i].at(0).digitValue())+"::::"); } + else{ dirs[i].prepend( "0::::"); } + } + dirs.sort(); + for(int i=0; i<dirs.length(); i++){ dirs[i] = dirs[i].section("::::",1,50); } //chop the sorter off the front again + //qDebug() << "Sorted:" << dirs; + } + QStringList img = D.entryList(QStringList() << "*.png" << "*.svg", QDir::Files | QDir::NoDotAndDotDot, QDir::NoSort); + if(img.length() > 0){ out << D.absolutePath(); } + for(int i=0; i<dirs.length(); i++){ + img.clear(); + img = getChildIconDirs(D.absoluteFilePath(dirs[i])); //re-use the old list variable + if(img.length() > 0){ out << img; } + } + return out; +} + +QStringList LXDG::systemMimeDirs(){ + //Returns a list of all the directories where *.xml MIME files can be found + QStringList appDirs = QString(getenv("XDG_DATA_HOME")).split(":"); + appDirs << QString(getenv("XDG_DATA_DIRS")).split(":"); + if(appDirs.isEmpty()){ appDirs << "/usr/local/share" << "/usr/share"; } + //Now create a valid list + QStringList out; + for(int i=0; i<appDirs.length(); i++){ + if( QFile::exists(appDirs[i]+"/mime") ){ + out << appDirs[i]+"/mime"; + } + } + return out; +} + +QIcon LXDG::findMimeIcon(QString extension){ + QIcon ico; + QString mime = LXDG::findAppMimeForFile(extension); + if(mime.isEmpty()){ mime = LXDG::findAppMimeForFile(extension.toLower()); } + mime.replace("/","-"); //translate to icon mime name + if(!mime.isEmpty()){ ico = LXDG::findIcon(mime, "unknown");} //use the "unknown" mimetype icon as fallback + if(ico.isNull()){ ico = LXDG::findIcon("unknown",""); } //just in case + return ico; +} + +QString LXDG::findAppMimeForFile(QString filename, bool multiple){ + QString out; + QString extension = filename.section(".",-1); + if("."+extension == filename){ extension.clear(); } //hidden file without extension + //qDebug() << "MIME SEARCH:" << filename << extension; + QStringList mimefull = LXDG::loadMimeFileGlobs2(); + QStringList mimes; + //Just in case the extension/filename is a mimetype itself + if( mimefull.filter(":"+filename+":").length() == 1){ + return filename; + } + else if(mimefull.filter(":"+extension+":").length() == 1){ + return extension; + } + //Look for globs at the end of the filename + if(!extension.isEmpty()){ + mimes = mimefull.filter(":*."+extension); + //If nothing found, try a case-insensitive search + if(mimes.isEmpty()){ mimes = mimefull.filter(":*."+extension, Qt::CaseInsensitive); } + //Now ensure that the filter was accurate (*.<extention>.<something> will still be caught) + for(int i=0; i<mimes.length(); i++){ + if(!filename.endsWith( mimes[i].section(":*",-1), Qt::CaseInsensitive )){ mimes.removeAt(i); i--; } + } + } + //Look for globs at the start of the filename + if(mimes.isEmpty()){ + mimes = mimefull.filter(":"+filename.left(2)); //look for the first 2 characters initially + //Note: This initial filter will only work if the wildcard (*) is not within the first 2 characters of the pattern + //Now ensure that the filter was accurate + for(int i=0; i<mimes.length(); i++){ + if(!filename.startsWith( mimes[i].section(":",3,50,QString::SectionSkipEmpty).section("*",0,0), Qt::CaseInsensitive )){ mimes.removeAt(i); i--; } + } + } + mimes.sort(); //this automatically puts them in weight order (100 on down) + QStringList matches; + //qDebug() << "Mimes:" << mimes; + for(int m=0; m<mimes.length(); m++){ + QString mime = mimes[m].section(":",1,1,QString::SectionSkipEmpty); + matches << mime; + } + //qDebug() << "Matches:" << matches; + if(multiple && !matches.isEmpty() ){ out = matches.join("::::"); } + else if( !matches.isEmpty() ){ out = matches.first(); } + else{ //no mimetype found - assign one (internal only - no system database changes) + if(extension.isEmpty()){ out = "unknown/"+filename.toLower(); } + else{ out = "unknown/"+extension.toLower(); } + } + //qDebug() << "Out:" << out; + return out; +} + +QStringList LXDG::findFilesForMime(QString mime){ + QStringList out; + QStringList mimes = LXDG::loadMimeFileGlobs2().filter(mime); + for(int i=0; i<mimes.length(); i++){ + out << mimes[i].section(":",2,2); // "*.<extension>" + } + //qDebug() << "Mime to Files:" << mime << out; + return out; +} + +QStringList LXDG::listFileMimeDefaults(){ + //This will spit out a itemized list of all the mimetypes and relevant info + // Output format: <mimetype>::::<extension>::::<default>::::<localized comment> + QStringList mimes = LXDG::loadMimeFileGlobs2(); + //Remove all the application files from the list (only a single app defines/uses this type in general) + /*QStringList apps = mimes.filter(":application/"); + //qDebug() << "List Mime Defaults"; + for(int i=0; i<apps.length(); i++){ mimes.removeAll(apps[i]); }*/ + //Now start filling the output list + QStringList out; + for(int i=0; i<mimes.length(); i++){ + QString mimetype = mimes[i].section(":",1,1); + QStringList tmp = mimes.filter(mimetype); + //Collect all the different extensions with this mimetype + QStringList extlist; + for(int j=0; j<tmp.length(); j++){ + mimes.removeAll(tmp[j]); + extlist << tmp[j].section(":",2,2); + } + extlist.removeDuplicates(); //just in case + //Now look for a current default for this mimetype + QString dapp = LXDG::findDefaultAppForMime(mimetype); //default app; + + //Create the output entry + //qDebug() << "Mime entry:" << i << mimetype << dapp; + out << mimetype+"::::"+extlist.join(", ")+"::::"+dapp+"::::"+LXDG::findMimeComment(mimetype); + + i--; //go back one (continue until the list is empty) + } + return out; +} + +QString LXDG::findMimeComment(QString mime){ + QString comment; + QStringList dirs = LXDG::systemMimeDirs(); + QString lang = QString(getenv("LANG")).section(".",0,0); + QString shortlang = lang.section("_",0,0); + for(int i=0; i<dirs.length(); i++){ + if(QFile::exists(dirs[i]+"/"+mime+".xml")){ + QStringList info = LUtils::readFile(dirs[i]+"/"+mime+".xml"); + QStringList filter = info.filter("<comment xml:lang=\""+lang+"\">"); + //First look for a full language match, then short language, then general comment + if(filter.isEmpty()){ filter = info.filter("<comment xml:lang=\""+shortlang+"\">"); } + if(filter.isEmpty()){ filter = info.filter("<comment>"); } + if(!filter.isEmpty()){ + comment = filter.first().section(">",1,1).section("</",0,0); + break; + } + } + } + return comment; +} + +QString LXDG::findDefaultAppForMime(QString mime){ + //First get the priority-ordered list of default file locations + QStringList dirs; + dirs << QString(getenv("XDG_CONFIG_HOME"))+"/lumina-mimeapps.list" \ + << QString(getenv("XDG_CONFIG_HOME"))+"/mimeapps.list"; + QStringList tmp = QString(getenv("XDG_CONFIG_DIRS")).split(":"); + for(int i=0; i<tmp.length(); i++){ dirs << tmp[i]+"/lumina-mimeapps.list"; } + for(int i=0; i<tmp.length(); i++){ dirs << tmp[i]+"/mimeapps.list"; } + dirs << QString(getenv("XDG_DATA_HOME"))+"/applications/lumina-mimeapps.list" \ + << QString(getenv("XDG_DATA_HOME"))+"/applications/mimeapps.list"; + tmp = QString(getenv("XDG_DATA_DIRS")).split(":"); + for(int i=0; i<tmp.length(); i++){ dirs << tmp[i]+"/applications/lumina-mimeapps.list"; } + for(int i=0; i<tmp.length(); i++){ dirs << tmp[i]+"/applications/mimeapps.list"; } + + //Now go through all the files in order of priority until a default is found + QString cdefault; + QStringList white; //lists to keep track of during the search (black unused at the moment) + for(int i=0; i<dirs.length() && cdefault.isEmpty(); i++){ + if(!QFile::exists(dirs[i])){ continue; } + QStringList info = LUtils::readFile(dirs[i]); + if(info.isEmpty()){ continue; } + QString workdir = dirs[i].section("/",0,-1); //just the directory + int def = info.indexOf("[Default Applications]"); //find this line to start on + if(def>=0){ + for(int d=def+1; d<info.length(); d++){ + if(info[d].startsWith("[")){ break; } //starting a new section now - finished with defaults + if(info[d].contains(mime+"=")){ + white << info[d].section("=",1,50).split(";"); + break; + } + } + } + // Now check for any white-listed files in this work dir + // find the full path to the file (should run even if nothing in this file) + //qDebug() << "WhiteList:" << white; + for(int w=0; w<white.length(); w++){ + if(white[w].isEmpty()){ continue; } + //First check for absolute paths to *.desktop file + if( white[w].startsWith("/") ){ + if( QFile::exists(white[w]) ){ cdefault=white[w]; break; } + else{ white.removeAt(w); w--; } //invalid file path - remove it from the list + } + //Now check for relative paths to file (in current priority-ordered work dir) + else if( QFile::exists(workdir+"/"+white[w]) ){ cdefault=workdir+"/"+white[w]; break; } + //Now go through the XDG DATA dirs and see if the file is in there + else{ + QStringList xdirs; + xdirs << QString(getenv("XDG_DATA_HOME"))+"/applications/"; + tmp = QString(getenv("XDG_DATA_DIRS")).split(":"); + for(int t=0; t<tmp.length(); t++){ xdirs << tmp[t]+"/applications/"; } + //Now scan these dirs + bool found = false; + //qDebug() << "Scan dirs:" << white[w] << xdirs; + for(int x=0; x<xdirs.length() && !found; x++){ + if(QFile::exists(xdirs[x]+white[w])){cdefault=xdirs[x]+white[w]; found = true; } + } + if(found){ break; } + } + } + /* WRITTEN BUT UNUSED CODE FOR MIMETYPE ASSOCIATIONS + //Skip using this because it is simply an alternate/unsupported standard that conflicts with + the current mimetype database standards. It is better/faster to parse 1 or 2 database glob files + rather than have to iterate through hundreds of *.desktop files *every* time you need to + find an application + + if(addI<0 && remI<0){ + //Simple Format: <mimetype>=<*.desktop file>;<*.desktop file>;..... + // (usually only one desktop file listed) + info = info.filter(mimetype+"="); + //Load the listed default(s) + for(int w=0; w<info.length(); w++){ + white << info[w].section("=",1,50).split(";"); + } + }else{ + //Non-desktop specific mimetypes file: has a *very* convoluted/inefficient algorithm (required by spec) + if(addI<0){ addI = info.length(); } //no add section + if(remI<0){ remI = info.length(); } // no remove section: + //Whitelist items + for(int a=addI+1; a!=remI && a<info.length(); a++){ + if(info[a].contains(mimetype+"=")){ + QStringList tmp = info[a].section("=",1,50).split(";"); + for(int t=0; t<tmp.length(); t++){ + if(!black.contains(tmp[t])){ white << tmp[t]; } //make sure this item is not on the black list + } + break; + } + } + //Blacklist items + for(int a=remI+1; a!=addI && a<info.length(); a++){ + if(info[a].contains(mimetype+"=")){ black << info[a].section("=",1,50).split(";"); break;} + } + + //STEPS 3/4 not written yet + + } //End of non-DE mimetypes file */ + + } //End loop over files + + return cdefault; +} + +QStringList LXDG::findAvailableAppsForMime(QString mime){ + QStringList dirs = LXDG::systemApplicationDirs(); + QStringList out; + //Loop over all possible directories that contain *.destop files + // and check for the mimeinfo.cache file + for(int i=0; i<dirs.length(); i++){ + if(QFile::exists(dirs[i]+"/mimeinfo.cache")){ + QStringList matches = LUtils::readFile(dirs[i]+"/mimeinfo.cache").filter(mime+"="); + //Find any matches for our mimetype in the cache + for(int j=0; j<matches.length(); j++){ + QStringList files = matches[j].section("=",1,1).split(";",QString::SkipEmptyParts); + //Verify that each file exists before putting the full path to the file in the output + for(int m=0; m<files.length(); m++){ + if(QFile::exists(dirs[i]+"/"+files[m])){ + out << dirs[i]+"/"+files[m]; + }else if(files[m].contains("-")){ //kde4-<filename> -> kde4/<filename> (stupid KDE variations!!) + files[m].replace("-","/"); + if(QFile::exists(dirs[i]+"/"+files[m])){ + out << dirs[i]+"/"+files[m]; + } + } + } + } + } + } + //qDebug() << "Found Apps for Mime:" << mime << out << dirs; + return out; +} + +void LXDG::setDefaultAppForMime(QString mime, QString app){ + QString filepath = QString(getenv("XDG_CONFIG_HOME"))+"/lumina-mimeapps.list"; + QStringList cinfo = LUtils::readFile(filepath); + //If this is a new file, make sure to add the header appropriately + if(cinfo.isEmpty()){ cinfo << "#Automatically generated with lumina-config" << "# DO NOT CHANGE MANUALLY" << "[Default Applications]"; } + //Check for any current entry for this mime type + QStringList tmp = cinfo.filter(mime+"="); + int index = -1; + if(!tmp.isEmpty()){ index = cinfo.indexOf(tmp.first()); } + //Now add the new default entry (if necessary) + if(app.isEmpty()){ + if(index>=0){ cinfo.removeAt(index); } //Remove entry + }else{ + if(index<0){ + cinfo << mime+"="+app+";"; //new entry + }else{ + cinfo[index] = mime+"="+app+";"; //overwrite existing entry + } + } + LUtils::writeFile(filepath, cinfo, true); + return; +} + +QStringList LXDG::findAVFileExtensions(){ + //output format: QDir name filter for valid A/V file extensions + QStringList globs = LXDG::loadMimeFileGlobs2(); + QStringList av = globs.filter(":audio/"); + av << globs.filter(":video/"); + for(int i=0; i<av.length(); i++){ + //Just use all audio/video mimetypes (for now) + av[i] = av[i].section(":",2,2); + //Qt5 Auto detection (broken - QMediaPlayer seg faults with Qt 5.3 - 11/24/14) + /*if( QMultimedia::NotSupported != QMediaPlayer::hasSupport(av[i].section(":",1,1)) ){ av[i] = av[i].section(":",2,2); } + else{ av.removeAt(i); i--; }*/ + } + av.removeDuplicates(); + return av; +} + +QStringList LXDG::loadMimeFileGlobs2(){ + //output format: <weight>:<mime type>:<file extension (*.something)> + if(mimeglobs.isEmpty() || (mimechecktime < (QDateTime::currentMSecsSinceEpoch()-30000)) ){ + //qDebug() << "Loading globs2 mime DB files"; + mimeglobs.clear(); + mimechecktime = QDateTime::currentMSecsSinceEpoch(); //save the current time this was last checked + QStringList dirs = LXDG::systemMimeDirs(); + for(int i=0; i<dirs.length(); i++){ + if(QFile::exists(dirs[i]+"/globs2")){ + QFile file(dirs[i]+"/globs2"); + if(!file.exists()){ continue; } + if(!file.open(QIODevice::ReadOnly | QIODevice::Text)){ continue; } + QTextStream in(&file); + while(!in.atEnd()){ + QString line = in.readLine(); + if(!line.startsWith("#")){ + mimeglobs << line.simplified(); + } + } + file.close(); + } + } + } + return mimeglobs; +} + +//Find all the autostart *.desktop files +QList<XDGDesktop> LXDG::findAutoStartFiles(bool includeInvalid){ + + //First get the list of directories to search (system first, user-provided files come later and overwrite sys files as needed) + QStringList paths = QString(getenv("XDG_CONFIG_DIRS")).split(":"); + paths << QString(getenv("XDG_CONFIG_HOME")).split(":"); + //Now go through them and find any valid *.desktop files + QList<XDGDesktop> files; + QStringList filenames; //make it easy to see if this filename is an override + QDir dir; + for(int i=0;i<paths.length(); i++){ + if(!QFile::exists(paths[i]+"/autostart")){ continue; } + dir.cd(paths[i]+"/autostart"); + QStringList tmp = dir.entryList(QStringList() << "*.desktop", QDir::Files, QDir::Name); + for(int t=0; t<tmp.length(); t++){ + bool ok = false; + XDGDesktop desk = LXDG::loadDesktopFile(dir.absoluteFilePath(tmp[t]), ok); + if(!ok){ continue; } //could not read file + //Now figure out what to do with it + if(filenames.contains(tmp[t])){ + //This is an overwrite of a lower-priority (system?) autostart file + // find the other file + int old = -1; + for(int o=0; o<files.length(); o++){ + if(files[o].filePath.endsWith("/"+tmp[t])){ old = o; break; } //found it + } + if(LXDG::checkValidity(desk, false)){ + //Full override of the lower-priority file (might be replacing exec/tryexec fields) + files[old] = desk; + }else{ + //Small override file (only the "Hidden" field listed in spec) + files[old].isHidden = desk.isHidden; //replace this value with the override + //files << desk; //still add this to the array (will be ignored/skipped later) + } + }else{ + //This is a new autostart file + files << desk; + filenames << tmp[t]; + } + }//end of loop over *.desktop files + } //end of loop over directories + + //Now filter the results by validity if desired + if(!includeInvalid){ + for(int i=0; i<files.length(); i++){ + if( !LXDG::checkValidity(files[i], false) || files[i].isHidden ){ + //Invalid file - go ahead and remove it from the output list + files.removeAt(i); + i--; + } + } + } + + return files; +} + +bool LXDG::setAutoStarted(bool autostart, XDGDesktop app){ + //First get the list of system directories to search (system first, user-provided files come later and overwrite sys files as needed) + QStringList paths = QString(getenv("XDG_CONFIG_DIRS")).split(":"); + QString upath = QString(getenv("XDG_CONFIG_HOME")).section(":",0,0); + if(upath.isEmpty()){ upath = QDir::homePath()+"/.config/autostart/"; } + else{ upath.append("/autostart/"); } + //Quick check/finish for user-defined files which are getting disabled (just remove the file) + if(app.filePath.startsWith(upath) && !autostart){ + return QFile::remove(app.filePath); + } + bool sysfile = false; + for(int i=0; i<paths.length(); i++){ + if(app.filePath.startsWith(paths[i]+"/autostart/") ){ + sysfile = true; + //Change it to the user-modifiable directory + app.filePath = app.filePath.replace(paths[i]+"/autostart/", upath); + } + } + //Make sure the user-autostart dir is specified, and clean the app structure as necessary + if( !app.filePath.startsWith(upath) && autostart){ + //Some other non-override autostart file - set it up to open with lumina-open + if(!app.filePath.endsWith(".desktop")){ + app.exec = "lumina-open \""+app.filePath+"\""; + app.tryexec = app.filePath; //make sure this file exists + if(app.name.isEmpty()){ app.name = app.filePath.section("/",-1); } + if(app.icon.isEmpty()){ app.icon = LXDG::findAppMimeForFile(app.filePath); app.icon.replace("/","-"); } + app.filePath = upath+app.filePath.section("/",-1)+".desktop"; + app.type = XDGDesktop::APP; + }else{ + //Some other *.desktop file on the system (keep almost all the existing settings/values) + // - setup a redirect to the other file + app.exec = "lumina-open \""+app.filePath+"\""; + app.tryexec = app.filePath; //make sure this file exists + // - Adjust the actual path where this file will get saved + app.filePath = upath+app.filePath.section("/",-1); + } + } + //Now save the "hidden" value into the file + app.isHidden = !autostart; //if hidden, it will not be autostarted + //Now save the file as necessary + bool saved = false; + //qDebug() << " - Saving AutoStart File:" << app.filePath << app.name << app.isHidden; + if(sysfile){ + //Just an override file for the "hidden" field - nothing more + QStringList info; + info << "[Desktop Entry]" << "Type=Application" << QString("Hidden=")+ (app.isHidden ? QString("true"): QString("false")); + saved = LUtils::writeFile(app.filePath, info, true); + }else{ + //Need to actually save the full file + saved = LXDG::saveDesktopFile(app); + } + return saved; +} + +bool LXDG::setAutoStarted(bool autostart, QString filePath){ + //Convenience function for the auto-start setter + XDGDesktop desk; + if(filePath.endsWith(".desktop")){ + bool ok = false; + desk = LXDG::loadDesktopFile(filePath, ok); + if(!ok){ return false; } //error reading input file + }else{ + desk.filePath = filePath; + desk.useTerminal = false; + } + return LXDG::setAutoStarted(autostart, desk); +} diff --git a/src-qt5/core/libLumina/LuminaXDG.h b/src-qt5/core/libLumina/LuminaXDG.h new file mode 100644 index 00000000..5a9b1441 --- /dev/null +++ b/src-qt5/core/libLumina/LuminaXDG.h @@ -0,0 +1,168 @@ +//=========================================== +// Lumina-DE source code +// Copyright (c) 2013, Ken Moore +// Available under the 3-clause BSD license +// See the LICENSE file for full details +//=========================================== +// These structures/classes are for conforming to the FreeDesktop standards +// REFERENCE: (*.desktop files) http://standards.freedesktop.org/desktop-entry-spec/latest/ar01s05.html +// -- Current Implementation (OCT 2013) -- +// Desktop File Version Compliance: 1.0 (except "DBusActivatable") +// Icon Theme Compliance: Built in to Qt (QIcon::fromTheme()) with "oxygen" theme default +// *.desktop Exec Compliance Updated: 9/9/2014 +// Mime Application Version Compliance: 1.0.1 (11/14/14) (Skips random *.desktop parsing: ~80% compliant) +//=========================================== + + +#ifndef _LUMINA_LIBRARY_XDG_H +#define _LUMINA_LIBRARY_XDG_H + +#include <QFile> +#include <QDir> +#include <QFileInfo> +#include <QStringList> +#include <QString> +#include <QIcon> +#include <QList> +#include <QHash> +#include <QLocale> +#include <QTextStream> +#include <QDateTime> +#include <QDebug> + + +// ====================== +// FreeDesktop Desktop Actions Framework (data structure) +// ====================== +class XDGDesktopAction{ +public: + //Admin variables + QString ID; //The ID name for this action (should correspond to an entry in the "actionList" for the XDGDesktop below) + //General Variables + QString name, icon, exec; +}; + +// ====================== +// FreeDesktop Desktop Entry Framework (data structure) +// ====================== +class XDGDesktop{ +public: + enum XDGDesktopType { BAD, APP, LINK, DIR }; + //Admin variables + QString filePath; //which file this structure contains the information for (absolute path) + QDateTime lastRead; //when this structure was created from the file + XDGDesktopType type; + //General variables + QString name, genericName, comment, icon; + QStringList showInList, notShowInList; + bool isHidden; + //Type 1 (APP) variables + QString exec, tryexec, path, startupWM; + QStringList actionList, mimeList, catList, keyList; + bool useTerminal, startupNotify; + QList<XDGDesktopAction> actions; + //Type 2 (LINK) variables + QString url; + + //Constructor/destructor + XDGDesktop(){} + ~XDGDesktop(){} +}; + +// ======================== +// File Information simplification class (combine QFileInfo with XDGDesktop) +// Need some extra information not usually available by a QFileInfo +// ======================== +class LFileInfo : public QFileInfo{ +private: + QString mime, icon; + XDGDesktop desk; + + void loadExtraInfo(); + +public: + //Couple overloaded contructors + LFileInfo(QString filepath); + LFileInfo(QFileInfo info); + ~LFileInfo(){} + + //Functions for accessing the extra information + // -- Return the mimetype for the file + QString mimetype(); + + // -- Return the icon file to use for this file + QString iconfile(); //Note: This string is auto-formatted for use in the LXDG::findIcon() routine. + + // -- Check if this is an XDG desktop file + bool isDesktopFile(); + + // -- Allow access to the internal XDG desktop data structure + XDGDesktop* XDG(); + + //Other file type identification routines + bool isImage(); //Is a readable image file (for thumbnail support) + bool isAVFile(); //Is an audio/video file +}; +typedef QList<LFileInfo> LFileInfoList; + +// ================================ +// Collection of FreeDesktop standards interaction routines +// ================================ +class LXDG{ +public: + //Read/write a *.desktop file + static XDGDesktop loadDesktopFile(QString filePath, bool& ok); + static bool saveDesktopFile(XDGDesktop dFile, bool merge = true); + //Check a *.desktop file for validity (showAll skips the DE-exclusivity checks) + static bool checkValidity(XDGDesktop dFile, bool showAll = true); + //Check for a valid executable + static bool checkExec(QString exec); + //Get a list of all the directories where *.desktop files exist + static QStringList systemApplicationDirs(); + //Get a list of all the *.desktop files available on the system + static QList<XDGDesktop> systemDesktopFiles(bool showAll = false, bool showHidden = false); + //Sort a list of Desktop files into the proper categories + static QHash< QString, QList<XDGDesktop> > sortDesktopCats(QList<XDGDesktop> apps); + //Return the icon to use for the given category + static QString DesktopCatToIcon(QString cat); + //Sort a list of Desktop files by name + static QList<XDGDesktop> sortDesktopNames(QList<XDGDesktop> apps); + //Get the executable line from a Desktop file + static QString getDesktopExec(XDGDesktop app, QString ActionID = ""); + //Set all the default XDG Environment variables + static void setEnvironmentVars(); + //Find an icon from the current/default theme + static QIcon findIcon(QString iconName, QString fallback = ""); + //Recursivly compile a list of child directories with *.png files in them + static QStringList getChildIconDirs(QString parent); + //List all the mime-type directories + static QStringList systemMimeDirs(); + //Find the mime-type icon for a particular file extension + static QIcon findMimeIcon(QString extension); + //Find the mime-type of a particular file extension + static QString findAppMimeForFile(QString filename, bool multiple = false); + //Find the file extension for a particular mime-type + static QStringList findFilesForMime(QString mime); + // Simplification function for finding all info regarding current mime defaults + static QStringList listFileMimeDefaults(); + //Find the localized comment string for a particular mime-type + static QString findMimeComment(QString mime); + //Find the default application for a mime-type + static QString findDefaultAppForMime(QString mime); + //Fine the available applications for a mime-type + static QStringList findAvailableAppsForMime(QString mime); + //Set the default application for a mime-type + static void setDefaultAppForMime(QString mime, QString app); + //List all the registered audio/video file extensions + static QStringList findAVFileExtensions(); + //Load all the "globs2" mime database files + static QStringList loadMimeFileGlobs2(); + + //Find all the autostart *.desktop files + static QList<XDGDesktop> findAutoStartFiles(bool includeInvalid = false); + static bool setAutoStarted(bool autostart, XDGDesktop app); + static bool setAutoStarted(bool autostart, QString filePath); //for convenience +}; + +#endif + diff --git a/src-qt5/core/libLumina/colors/Black.qss.colors b/src-qt5/core/libLumina/colors/Black.qss.colors new file mode 100644 index 00000000..b6269188 --- /dev/null +++ b/src-qt5/core/libLumina/colors/Black.qss.colors @@ -0,0 +1,13 @@ +ACCENTCOLOR=rgba(204,204,204,200) +ACCENTDISABLECOLOR=rgba(204,204,204,100) +ALTBASECOLOR=rgb(37,37,37) +BASECOLOR=rgb(36,36,36) +HIGHLIGHTCOLOR=rgba(218,222,226,170) +HIGHLIGHTDISABLECOLOR=rgba(218,222,226,160) +PRIMARYCOLOR=rgba(0,0,4,250) +PRIMARYDISABLECOLOR=rgba(0,0,0,180) +SECONDARYCOLOR=rgba(192,192,192,200) +SECONDARYDISABLECOLOR=rgba(192,192,192,100) +TEXTCOLOR=white +TEXTDISABLECOLOR=darkgrey +TEXTHIGHLIGHTCOLOR=black diff --git a/src-qt5/core/libLumina/colors/Blue-Light.qss.colors b/src-qt5/core/libLumina/colors/Blue-Light.qss.colors new file mode 100644 index 00000000..5bcb85f6 --- /dev/null +++ b/src-qt5/core/libLumina/colors/Blue-Light.qss.colors @@ -0,0 +1,13 @@ +ACCENTCOLOR=rgba(157,161,165,200) +ACCENTDISABLECOLOR=rgba(156,156,157,200) +ALTBASECOLOR=rgb(239,247,255) +BASECOLOR=rgb(165,210,255) +HIGHLIGHTCOLOR=rgb(110,158,208) +HIGHLIGHTDISABLECOLOR=rgba(110,158,208,150) +PRIMARYCOLOR=rgba(181,212,238,200) +PRIMARYDISABLECOLOR=rgba(234,237,238,100) +SECONDARYCOLOR=rgba(200,222,243,200) +SECONDARYDISABLECOLOR=rgba(200,222,243,100) +TEXTCOLOR=black +TEXTDISABLECOLOR=grey +TEXTHIGHLIGHTCOLOR=black diff --git a/src-qt5/core/libLumina/colors/Grey-Dark.qss.colors b/src-qt5/core/libLumina/colors/Grey-Dark.qss.colors new file mode 100644 index 00000000..207edd04 --- /dev/null +++ b/src-qt5/core/libLumina/colors/Grey-Dark.qss.colors @@ -0,0 +1,13 @@ +ACCENTCOLOR=rgba(43,43,45,200) +ACCENTDISABLECOLOR=rgba(43,43,45,100) +ALTBASECOLOR=rgb(63,61,61) +BASECOLOR=rgb(93,92,92) +HIGHLIGHTCOLOR=rgba(218,222,226,170) +HIGHLIGHTDISABLECOLOR=rgba(218,222,226,160) +PRIMARYCOLOR=rgba(75,77,80,240) +PRIMARYDISABLECOLOR=rgba(75,77,80,180) +SECONDARYCOLOR=rgba(144,140,142,200) +SECONDARYDISABLECOLOR=rgba(144,140,142,100) +TEXTCOLOR=white +TEXTDISABLECOLOR=darkgrey +TEXTHIGHLIGHTCOLOR=black diff --git a/src-qt5/core/libLumina/colors/Lumina-Glass.qss.colors b/src-qt5/core/libLumina/colors/Lumina-Glass.qss.colors new file mode 100644 index 00000000..7b74e036 --- /dev/null +++ b/src-qt5/core/libLumina/colors/Lumina-Glass.qss.colors @@ -0,0 +1,13 @@ +ACCENTCOLOR=rgba(255,252,234,100) +ACCENTDISABLECOLOR=rgba(0,0,0,100) +ALTBASECOLOR=rgb(252,252,255) +BASECOLOR=rgb(247,246,244) +HIGHLIGHTCOLOR=rgba(212,212,212,170) +HIGHLIGHTDISABLECOLOR=rgba(184,184,184,100) +PRIMARYCOLOR=rgba(235,242,242,200) +PRIMARYDISABLECOLOR=rgba(214,220,220,200) +SECONDARYCOLOR=rgba(208,220,244,200) +SECONDARYDISABLECOLOR=rgba(168,179,200,100) +TEXTCOLOR=black +TEXTDISABLECOLOR=grey +TEXTHIGHLIGHTCOLOR=black
\ No newline at end of file diff --git a/src-qt5/core/libLumina/colors/Lumina-Gold.qss.colors b/src-qt5/core/libLumina/colors/Lumina-Gold.qss.colors new file mode 100644 index 00000000..cfad7069 --- /dev/null +++ b/src-qt5/core/libLumina/colors/Lumina-Gold.qss.colors @@ -0,0 +1,13 @@ +ACCENTCOLOR=rgba(149,144,122,200) +ACCENTDISABLECOLOR=rgba(74,71,60,200) +ALTBASECOLOR=rgb(230,227,204) +BASECOLOR=rgb(247,247,240) +HIGHLIGHTCOLOR=rgba(252,237,149,170) +HIGHLIGHTDISABLECOLOR=rgba(252,237,149,100) +PRIMARYCOLOR=rgba(238,234,226,200) +PRIMARYDISABLECOLOR=rgba(238,235,224,100) +SECONDARYCOLOR=rgba(247,209,112,200) +SECONDARYDISABLECOLOR=rgba(234,198,106,150) +TEXTCOLOR=black +TEXTDISABLECOLOR=grey +TEXTHIGHLIGHTCOLOR=black
\ No newline at end of file diff --git a/src-qt5/core/libLumina/colors/Lumina-Green.qss.colors b/src-qt5/core/libLumina/colors/Lumina-Green.qss.colors new file mode 100644 index 00000000..99f16acb --- /dev/null +++ b/src-qt5/core/libLumina/colors/Lumina-Green.qss.colors @@ -0,0 +1,13 @@ +ACCENTCOLOR=rgba(149,144,122,200) +ACCENTDISABLECOLOR=rgba(74,71,60,200) +ALTBASECOLOR=rgb(230,230,230) +BASECOLOR=rgb(247,246,244) +HIGHLIGHTCOLOR=rgba(66,153,76,170) +HIGHLIGHTDISABLECOLOR=rgba(66,153,76,100) +PRIMARYCOLOR=rgba(229,231,238,200) +PRIMARYDISABLECOLOR=rgba(229,231,238,100) +SECONDARYCOLOR=rgba(76,197,84,200) +SECONDARYDISABLECOLOR=rgba(76,197,84,150) +TEXTCOLOR=black +TEXTDISABLECOLOR=grey +TEXTHIGHLIGHTCOLOR=black diff --git a/src-qt5/core/libLumina/colors/Lumina-Purple.qss.colors b/src-qt5/core/libLumina/colors/Lumina-Purple.qss.colors new file mode 100644 index 00000000..f2ba7e05 --- /dev/null +++ b/src-qt5/core/libLumina/colors/Lumina-Purple.qss.colors @@ -0,0 +1,13 @@ +ACCENTCOLOR=rgba(247,231,255,200) +ACCENTDISABLECOLOR=rgba(167,166,166,200) +ALTBASECOLOR=rgb(63,61,61) +BASECOLOR=rgb(93,92,92) +HIGHLIGHTCOLOR=rgba(76,38,171,170) +HIGHLIGHTDISABLECOLOR=rgba(57,19,139,170) +PRIMARYCOLOR=rgba(65,67,80,240) +PRIMARYDISABLECOLOR=rgba(65,67,80,180) +SECONDARYCOLOR=rgba(74,65,120,200) +SECONDARYDISABLECOLOR=rgba(71,65,120,100) +TEXTCOLOR=white +TEXTDISABLECOLOR=darkgrey +TEXTHIGHLIGHTCOLOR=white
\ No newline at end of file diff --git a/src-qt5/core/libLumina/colors/Lumina-Red.qss.colors b/src-qt5/core/libLumina/colors/Lumina-Red.qss.colors new file mode 100644 index 00000000..f73bdb75 --- /dev/null +++ b/src-qt5/core/libLumina/colors/Lumina-Red.qss.colors @@ -0,0 +1,13 @@ +ACCENTCOLOR=rgba(255,244,245,200) +ACCENTDISABLECOLOR=rgba(167,166,166,200) +ALTBASECOLOR=rgb(63,61,61) +BASECOLOR=rgb(93,92,92) +HIGHLIGHTCOLOR=rgba(175,9,9,170) +HIGHLIGHTDISABLECOLOR=rgba(154,20,20,170) +PRIMARYCOLOR=rgba(80,66,66,240) +PRIMARYDISABLECOLOR=rgba(80,66,66,180) +SECONDARYCOLOR=rgba(120,22,23,200) +SECONDARYDISABLECOLOR=rgba(120,22,23,100) +TEXTCOLOR=white +TEXTDISABLECOLOR=darkgrey +TEXTHIGHLIGHTCOLOR=white
\ No newline at end of file diff --git a/src-qt5/core/libLumina/colors/PCBSD10-Default.qss.colors b/src-qt5/core/libLumina/colors/PCBSD10-Default.qss.colors new file mode 100644 index 00000000..efcea51d --- /dev/null +++ b/src-qt5/core/libLumina/colors/PCBSD10-Default.qss.colors @@ -0,0 +1,13 @@ +ACCENTCOLOR=rgba(182,186,191,200) +ACCENTDISABLECOLOR=rgba(190,190,191,200) +ALTBASECOLOR=rgb(241,241,241) +BASECOLOR=rgb(247,246,244) +HIGHLIGHTCOLOR=rgb(129,184,243) +HIGHLIGHTDISABLECOLOR=rgba(129,184,243,150) +PRIMARYCOLOR=rgba(224,236,238,200) +PRIMARYDISABLECOLOR=rgba(234,237,238,100) +SECONDARYCOLOR=rgba(200,222,243,200) +SECONDARYDISABLECOLOR=rgba(200,222,243,100) +TEXTCOLOR=black +TEXTDISABLECOLOR=grey +TEXTHIGHLIGHTCOLOR=black diff --git a/src-qt5/core/libLumina/colors/Solarized-Dark.qss.colors b/src-qt5/core/libLumina/colors/Solarized-Dark.qss.colors new file mode 100644 index 00000000..d4f0f1c9 --- /dev/null +++ b/src-qt5/core/libLumina/colors/Solarized-Dark.qss.colors @@ -0,0 +1,16 @@ +# Solarized is a theme created by Ethan Schoonover +# See the project site at http://ethanschoonover.com/solarized +# Or see the source at https://github.com/altercation/solarized +ACCENTCOLOR=rgb(181,137,0) +ACCENTDISABLECOLOR=rgb(181,137,0) +ALTBASECOLOR=rgb(0,43,54) +BASECOLOR=rgb(0,43,54) +HIGHLIGHTCOLOR=rgb(7,54,66) +HIGHLIGHTDISABLECOLOR=rgb(7,53,66) +PRIMARYCOLOR=rgb(0,43,54) +PRIMARYDISABLECOLOR=rgb(7,54,66) +SECONDARYCOLOR=rgb(0,43,54) +SECONDARYDISABLECOLOR=rgb(7,54,66) +TEXTCOLOR=rgb(131,148,150) +TEXTDISABLECOLOR=rgb(88,110,117) +TEXTHIGHLIGHTCOLOR=rgb(147,161,161) diff --git a/src-qt5/core/libLumina/colors/Solarized-Light.qss.colors b/src-qt5/core/libLumina/colors/Solarized-Light.qss.colors new file mode 100644 index 00000000..fead1915 --- /dev/null +++ b/src-qt5/core/libLumina/colors/Solarized-Light.qss.colors @@ -0,0 +1,16 @@ +# Solarized is a theme created by Ethan Schoonover +# See the project site at http://ethanschoonover.com/solarized +# Or see the source at https://github.com/altercation/solarized +ACCENTCOLOR=rgb(38,139,210) +ACCENTDISABLECOLOR=rgb(38,139,210) +ALTBASECOLOR=rgb(253,246,227) +BASECOLOR=rgb(253,246,227) +HIGHLIGHTCOLOR=rgb(238,232,213) +HIGHLIGHTDISABLECOLOR=rgb(238,232,213) +PRIMARYCOLOR=rgb(253,246,227) +PRIMARYDISABLECOLOR=rgb(238,232,213) +SECONDARYCOLOR=rgb(253,246,227) +SECONDARYDISABLECOLOR=rgb(238,232,213) +TEXTCOLOR=rgb(131,148,150) +TEXTDISABLECOLOR=rgb(147,161,161) +TEXTHIGHLIGHTCOLOR=rgb(88,110,117) diff --git a/src-qt5/core/libLumina/libLumina.pro b/src-qt5/core/libLumina/libLumina.pro new file mode 100644 index 00000000..63c56824 --- /dev/null +++ b/src-qt5/core/libLumina/libLumina.pro @@ -0,0 +1,65 @@ +include("$${PWD}/../../OS-detect.pri") + +QT += core network widgets x11extras multimedia concurrent svg + +define +#Setup any special defines (qmake -> C++) +GIT_VERSION=$$system(git describe --always) +!isEmpty(GIT_VERSION){ + DEFINES += GIT_VERSION='"\\\"$${GIT_VERSION}\\\""' +} +DEFINES += BUILD_DATE='"\\\"$$system(date)\\\""' + +TARGET=LuminaUtils + +target.path = $${L_LIBDIR} + +DESTDIR= $$_PRO_FILE_PWD_/ + +TEMPLATE = lib +LANGUAGE = C++ +VERSION = 1 + +HEADERS += LuminaXDG.h \ + LuminaUtils.h \ + LuminaX11.h \ + LuminaThemes.h \ + LuminaOS.h \ + LuminaSingleApplication.h + +SOURCES += LuminaXDG.cpp \ + LuminaUtils.cpp \ + LuminaX11.cpp \ + LuminaThemes.cpp \ + LuminaSingleApplication.cpp + +# Also load the OS template as available for +# LuminaOS support functions (or fall back to generic one) +exists($${PWD}/LuminaOS-$${LINUX_DISTRO}.cpp){ + SOURCES += LuminaOS-$${LINUX_DISTRO}.cpp +}else:exists($${PWD}/LuminaOS-$${OS}.cpp){ + SOURCES += LuminaOS-$${OS}.cpp +}else{ + SOURCES += LuminaOS-template.cpp +} + +LIBS += -lc -lxcb -lxcb-ewmh -lxcb-icccm -lxcb-image -lxcb-composite -lxcb-damage -lxcb-util -lXdamage + +include.path=$${L_INCLUDEDIR} +include.files=LuminaXDG.h \ + LuminaUtils.h \ + LuminaX11.h \ + LuminaThemes.h \ + LuminaOS.h \ + LuminaSingleApplication.h + +colors.path=$${L_SHAREDIR}/Lumina-DE/colors +colors.files=colors/*.qss.colors + +themes.path=$${L_SHAREDIR}/Lumina-DE/themes/ +themes.files=themes/*.qss.template + +#quickplugins.path=$${L_SHAREDIR}/Lumina-DE/quickplugins/ +#quickplugins.files=quickplugins/* + +INSTALLS += target include colors themes diff --git a/src-qt5/core/libLumina/quickplugins/quick-sample.qml b/src-qt5/core/libLumina/quickplugins/quick-sample.qml new file mode 100644 index 00000000..18b10d77 --- /dev/null +++ b/src-qt5/core/libLumina/quickplugins/quick-sample.qml @@ -0,0 +1,12 @@ +// Plugin-Name=Sample +// Plugin-Description=A simple example for QtQuick/QML plugins +// Plugin-Icon=preferences-plugin +// Created: Ken Moore (ken@pcbsd.org) May 2015 + +import QtQuick.Controls 1.3 + +Label { + text: "Sample" + color: "blue" + font.bold: true +}
\ No newline at end of file diff --git a/src-qt5/core/libLumina/themes/Lumina-default.qss.template b/src-qt5/core/libLumina/themes/Lumina-default.qss.template new file mode 100644 index 00000000..a40a3d48 --- /dev/null +++ b/src-qt5/core/libLumina/themes/Lumina-default.qss.template @@ -0,0 +1,488 @@ +/* ALL THE TEMPLATE WIDGETS */ +INHERITS=None + +/* ALL THE WIDGETS WITH THE BASE COLOR */ +QMainWindow, QMenu, QDialog, QMessageBox{ + background: %%BASECOLOR%%; + color: %%TEXTCOLOR%%; +} + +/* ALL THE WIDGETS WITH AN ALTERNATE BASE COLOR */ +QLineEdit, QTextEdit, QTextBrowser, QPlainTextEdit, QSpinBox, QDateEdit, QDateTimeEdit, QTimeEdit, QDoubleSpinBox{ + background: %%ALTBASECOLOR%%; + color: %%TEXTCOLOR%%; + border-color: %%ACCENTDISABLECOLOR%%; + selection-background-color: %%HIGHLIGHTCOLOR%%; + selection-color: %%TEXTHIGHLIGHTCOLOR%%; + +} + +/* PAGES OF CONTAINER WIDGETS */ +QStackedWidget .QWidget, QTabWidget .QWidget{ + background: %%ALTBASECOLOR%%; + color: %%TEXTCOLOR%%; + border: none; +} +QToolBox::tab{ + color: %%TEXTCOLOR%%; +} + +/* MENU WIDGETS */ +QMenuBar, QMenuBar::item,QToolBar{ + background: %%SECONDARYCOLOR%%; + border: none; + color: %%TEXTCOLOR%%; +} + +QStatusBar{ + background: QLinearGradient(x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 transparent, stop: 0.5 %%SECONDARYCOLOR%%); + border: none; + color: %%TEXTCOLOR%%; +} +QToolBar:top{ + border-bottom: 1px solid %%ACCENTCOLOR%%; +} +QToolBar:bottom{ + border-top: 1px solid %%ACCENTCOLOR%%; +} +QToolBar:left{ + border-right: 1px solid %%ACCENTCOLOR%%; +} +QToolBar:right{ + border-left: 1px solid %%ACCENTCOLOR%%; +} + +QMenuBar::item{ + background: transparent; /*Use the menu bar color*/ + padding-left: 4px; + padding-right: 2px; +} + +QMenuBar::item:selected, QMenuBar::item:pressed, QMenu::item:selected{ +background: QLinearGradient(x1: 0, y1: 0, x2: 1, y2: 1, stop: 0 %%HIGHLIGHTDISABLECOLOR%%, stop: 1 %%HIGHLIGHTCOLOR%%); +color: %%TEXTHIGHLIGHTCOLOR%%; +border: 1px solid %%ACCENTCOLOR%%; +} +QMenuBar::item:disabled{ + color: %%TEXTDISABLECOLOR%%; +} + +QMenu::item{ + background: transparent; + border: 1px solid transparent; + color: %%TEXTCOLOR%%; + padding: 2px 30px 2px 20px; +} + +/* TAB WIDGETS */ +/*QTabBar{ + Custom Font settings need to be here and NOT in the ::tab fields, + otherwise it will break auto-scaling of the tab sizes to fit the text +}*/ +QTabBar::tab { + background: QLinearGradient(x1: 0, y1: 0, x2: 1, y2: 1, stop: 0 %%SECONDARYDISABLECOLOR%%, stop: 1 %%SECONDARYCOLOR%%); + border: 1px solid %%ACCENTCOLOR%%; + padding: 2px; + color: %%TEXTCOLOR%%; +} +QTabBar::tab:top{ + border-top-left-radius: 4px; + border-top-right-radius: 4px; + max-width: 100em; + min-width: 0em; +} +QTabBar::tab:bottom{ + border-bottom-left-radius: 4px; + border-bottom-right-radius: 4px; + max-width: 100em; + min-width: 0em; +} +/* left/right tab indicators appear to be reversed in Qt*/ +QTabBar::tab:right{ + border-top-left-radius: 4px; + border-bottom-left-radius: 4px; + max-height: 100em; + min-height: 0em; +} +QTabBar::tab:left{ + border-top-right-radius: 4px; + border-bottom-right-radius: 4px; + max-height: 100em; + min-height: 0em; +} +QTabBar::tab:selected{ + background: %%HIGHLIGHTDISABLECOLOR%%; +} +QTabBar::tab:hover { + background: %%HIGHLIGHTCOLOR%%; + border: 1px solid %%ACCENTDISABLECOLOR%%; + } + +QTabBar::tab:!selected:top { + margin-top: 4px; +} +QTabBar::tab:!selected:bottom{ + margin-bottom: 4px; +} +QTabBar::tab:!selected:right{ + margin-left: 4px; +} +QTabBar::tab:!selected:left{ + margin-right: 4px; +} + +/* FRAME WIDGETS */ +QToolTip{ + background: QLinearGradient(x1: 0, y1: 0, x2: 1, y2: 1, stop: 0 %%BASECOLOR%%, stop: 1 %%ALTBASECOLOR%%); + border-radius: 3px; + border: 1px solid %%ACCENTCOLOR%%; + padding: 1px; + color: %%TEXTCOLOR%%; +} + +QLabel{ + background: transparent; + border: none; + color: %%TEXTCOLOR%%; +} +QAbstractButton::disabled{ + color: %%TEXTDISABLECOLOR%%; +} + +/* GROUP BOX */ +QGroupBox{ + background-color: transparent; + border-color: %%ACCENTCOLOR%%; + border-radius: 5px; + margin-top: 2ex; + font-weight: bold; +} +QGroupBox::title{ + subcontrol-origin: margin; + subcontrol-position: top center; + padding: 0 3px; + background: transparent; + /*border: none;*/ + color: %%TEXTCOLOR%%; +} + +/* COMBO BOX */ +QComboBox{ + /*border: 1px solid %%ACCENTCOLOR%%; + border-radius: 3px; + padding: 1px 18px 1px 3px;*/ + color: %%TEXTCOLOR%%; + background: %%ALTBASECOLOR%%; + selection-background-color: %%HIGHLIGHTCOLOR%%; + } + + +/* VIEW WIDGETS */ +QTreeView, QListView{ + background: %%ALTBASECOLOR%%; + alternate-background-color: %%BASECOLOR%%; + /*selection-background-color: %%SECONDARYCOLOR%%;*/ + border: 1px solid %%ACCENTCOLOR%%; + border-radius: 3px; + /*show-decoration-selected: 1;*/ + color: %%TEXTCOLOR%%; + selection-color: %%TEXTCOLOR%%; +} + +QTreeView:focus, QListView:focus{ + border: 1px solid %%HIGHLIGHTDISABLECOLOR%%; +} + +/* +QTreeView::item and QListView::item unneccessary: +Already set though parentage and causes usage errors if set manually +*/ + +/*QTreeView::item:selected, QListView::item:selected{ + background: %%SECONDARYDISABLECOLOR%%; + border-color: %%ACCENTCOLOR%%; + color: %%TEXTCOLOR%%; +}*/ + +QTreeView::item:hover, QListView::item:hover{ + background: %%HIGHLIGHTDISABLECOLOR%%; +} +QTreeView::item:selected:hover, QListView::item:selected:hover{ + background: %%HIGHLIGHTDISABLECOLOR%%; +} +QTreeView::item:selected, QListView::item:selected{ + background: %%SECONDARYDISABLECOLOR%%; +} +QTreeView::item:selected:focus, QListView::item:selected:focus{ + background: %%SECONDARYCOLOR%%; +} +QHeaderView{ + background: %%HIGHLIGHTDISABLECOLOR%%; + color: %%TEXTHIGHLIGHTCOLOR%%; + border: none; + border-top-left-radius: 3px; /*match the list/tree view widgets*/ + border-top-right-radius: 3px; /*match the list/tree view widgets*/ +} +QHeaderView::section{ + background: %%HIGHLIGHTDISABLECOLOR%%; /*QLinearGradient(x1: 0, y1: 0, x2: 1, y2: 1, stop: 0 %%PRIMARYDISABLECOLOR%%, stop: 1 %%PRIMARYCOLOR%%);*/ + color: %%TEXTHIGHLIGHTCOLOR%%; + border-color: %%ACCENTCOLOR%%; + padding: 1px; + padding-left: 4px; +} +QHeaderView::section:hover{ + background: %%PRIMARYCOLOR%%; + border-color: %%ACCENTDISABLECOLOR%%; + color: %%TEXTCOLOR%%; +} + +/* SCROLLBARS (NOTE: Changing 1 subcontrol means you have to change all of them)*/ +QScrollBar{ + background: QLinearGradient(x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 %%SECONDARYCOLOR%%, stop: 1 %%SECONDARYDISABLECOLOR%%); + /*border: 1px solid %%ACCENTCOLOR%%;*/ +} +QScrollBar:horizontal{ + margin: 0px 20px 0px 20px; +} +QScrollBar:vertical{ + margin: 20px 0px 20px 0px; +} +QScrollBar::sub-page, QScrollBar::add-page{ + background: %%BASECOLOR%%; + border: 1px solid %%ACCENTCOLOR%%; +} +QScrollBar::sub-page:vertical{ + border-bottom: none; +} +QScrollBar::add-page:vertical{ + border-top: none; +} +QScrollBar::sub-page:horizontal{ + border-right: none; +} +QScrollBar::add-page:horizontal{ + border-left: none; +} +QScrollBar::handle{ + background: QLinearGradient(x1: 0, y1: -0.3, x2: 0, y2: 1.3, stop: 0 %%BASECOLOR%%, stop: 0.5 %%SECONDARYCOLOR%%, stop: 1 %%BASECOLOR%%); + border: 1px solid %%ACCENTCOLOR%%; +} +QScrollBar::handle:hover, QScrollBar::add-line:hover, QScrollBar::sub-line:hover{ + background: %%HIGHLIGHTCOLOR%%; +} +QScrollBar::add-line{ + background: QLinearGradient(x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 %%SECONDARYDISABLECOLOR%%, stop: 1 %%SECONDARYCOLOR%%); + border: 1px solid %%ACCENTCOLOR%%; + border-radius: 3px; +subcontrol-position: bottom right; +subcontrol-origin: margin; +} +QScrollBar::add-line:vertical, QScrollBar::sub-line:vertical{ +height: 20px; +} +QScrollBar::add-line:horizontal, QScrollBar::sub-line:horizontal{ +width: 20px; +} +QScrollBar::sub-line{ + background: QLinearGradient(x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 %%SECONDARYCOLOR%%, stop: 1 %%SECONDARYDISABLECOLOR%%); + border: 1px solid %%ACCENTCOLOR%%; + border-radius: 3px; +subcontrol-position: top left; +subcontrol-origin: margin; +height: 20px; +} +/* SLIDERS */ +QSlider::groove:horizontal { +border: 1px solid %%ACCENTCOLOR%%; +background: %%ALTBASECOLOR%%; +height: 10px; +border-radius: 3px; +} +QSlider::groove:vertical { +border: 1px solid %%ACCENTCOLOR%%; +background: %%ALTBASECOLOR%%; +width: 10px; +border-radius: 3px; +} +QSlider::sub-page:horizontal { +background: qlineargradient(x1: 0, y1: 0, x2: 1, y2: 1, + stop: 0 %%HIGHLIGHTCOLOR%%, stop: 1 %%HIGHLIGHTDISABLECOLOR%%); +border: 1px solid %%ACCENTCOLOR%%; +height: 10px; +border-radius: 3px; +} +QSlider::sub-page:vertical { +background: qlineargradient(x1: 0, y1: 0, x2: 1, y2: 1, + stop: 0 %%HIGHLIGHTCOLOR%%, stop: 1 %%HIGHLIGHTDISABLECOLOR%%); +border: 1px solid %%ACCENTCOLOR%%; +width: 10px; +border-radius: 3px; +} +QSlider::add-page:horizontal{ +background: %%ALTBASECOLOR%%; +border: 1px solid %%ACCENTCOLOR%%; +height: 10px; +border-radius: 3px; +} +QSlider::add-page:vertical{ +background: %%ALTBASECOLOR%%; +border: 1px solid %%ACCENTCOLOR%%; +width: 10px; +border-radius: 3px; +} +QSlider::handle:horizontal{ +background: %%ALTBASECOLOR%%; +border: 1px solid %%ACCENTCOLOR%%; +width: 13px; +border-radius: 4px; +} +QSlider::handle:vertical{ +background: %%ALTBASECOLOR%%; +border: 1px solid %%ACCENTCOLOR%%; +height: 13px; +border-radius: 4px; +} +QSlider::handle:horizontal:hover, QSlider::handle:vertical:hover{ +border: 1px solid %%ACCENTDISABLECOLOR%%; +/*background: %%HIGHLIGHTCOLOR%%;*/ +} + +QSlider::sub-page:horizontal:disabled { +background: %%ACCENTDISABLECOLOR%%; +border-color: %%ACCENTCOLOR%%; +} + +QSlider::add-page:horizontal:disabled { +background: %%ACCENTDISABLECOLOR%%; +border-color: %%ACCENTCOLOR%%; +} + +QSlider::handle:horizontal:disabled { +background: %%ALTBASECOLOR%%; +border: 1px solid %%ACCENTCOLOR%%; +} + +/* BUTTONS */ +QPushButton{ + border: 1px solid %%ACCENTCOLOR%%; + border-radius: 3px; + background-color: QLinearGradient(x1: 0, y1: 0, x2: 1, y2: 1, stop: 0 %%SECONDARYDISABLECOLOR%%, stop: 1 %%SECONDARYCOLOR%%); + padding: 2px; + padding-right: 4px; + color: %%TEXTCOLOR%%; + } + +QToolButton{ /* Assume a flat button for every toolbutton by default*/ + color: %%TEXTCOLOR%%; + border: 1px solid transparent; + border-radius: 3px; + background-color: transparent; + padding: 1px; +} + + QPushButton:pressed, QPushButton:open, QPushButton:selected, QPushButton:checked, QPushButton:on, QToolButton:pressed, QToolButton:open, QToolButton:selected, QToolButton:checked, QToolButton:on{ + background-color: QLinearGradient(x1: 0, y1: 0, x2: 1, y2: 1, stop: 0 %%HIGHLIGHTDISABLECOLOR%%, stop: 1 %%HIGHLIGHTCOLOR%%); + margin-top: 2px; + } + +QPushButton:flat, QToolButton:flat{ + background-color: transparent; + border: 1px solid transparent; /* no border for a flat button */ +} + +QPushButton:hover, QToolButton:hover{ + border: 1px solid %%ACCENTCOLOR%%; + background-color: QLinearGradient(x1: 0, y1: 0, x2: 1, y2: 1, stop: 0 %%HIGHLIGHTDISABLECOLOR%%, stop: 1 %%HIGHLIGHTCOLOR%%); + color: %%TEXTHIGHLIGHTCOLOR%%; +} +QRadioButton, QCheckBox{ + padding: 2px; + border: 1px solid transparent; + border-radius: 3px; + color: %%TEXTCOLOR%%; +} +QRadioButton::hover, QCheckBox:hover{ + background: QLinearGradient(x1: 0, y1: 0, x2: 1, y2: 1, stop: 0 %%HIGHLIGHTDISABLECOLOR%%, stop: 1 %%HIGHLIGHTCOLOR%%); + border: 1px solid %%ACCENTCOLOR%%; + color: %%TEXTHIGHLIGHTCOLOR%%; +} +QRadioButton::indicator, QCheckBox::indicator, QGroupBox::indicator{ + border: 1px solid %%TEXTCOLOR%%; +} +QRadioButton::indicator{ + border-radius: 7px; +} +QRadioButton::indicator:checked{ + background: qradialgradient(spread:pad, cx:0.5, cy:0.5, radius:1, fx:0.5, fy:0.5, stop:0 %%TEXTCOLOR%%, stop:0.25 %%TEXTCOLOR%%, stop:0.25001 transparent); +} +QCheckBox::indicator:checked, QGroupBox::indicator:checked{ + padding: 1px; + background-origin: content; + background-clip: content; + background: %%TEXTCOLOR%%; +} +QCheckBox::indicator:indeterminate, QGroupBox::indicator:indeterminate{ + padding: 1px; + background-origin: content; + background-clip: content; + background: QLinearGradient(x1: 0, y1: 0, x2: 1, y2: 1, stop: 0.49 transparent, stop: 0.5 %%TEXTCOLOR%%); +} + +/* PROGRESSBAR */ +QProgressBar{ + background-color: %%ALTBASECOLOR%%; + border: 1px solid %%ACCENTCOLOR%%; + border-radius: 5px; + color: %%TEXTCOLOR%%; + text-align: center; + padding: 1px; +} + QProgressBar::chunk { + background-color: QLinearGradient(x1: 0, y1: 0, x2: 1, y2: 1, stop: 0 %%HIGHLIGHTCOLOR%%, stop: 1 %%HIGHLIGHTDISABLECOLOR%%); + /*border: 1px solid %%ACCENTDISABLECOLOR%%;*/ + border-radius: 5px; + } +QProgressBar::chunk:vertical{ + margin-left: 2px; + margin-right: 2px; +} +QProgressBar::chunk:horizontal{ + margin-top: 2px; + margin-bottom: 2px; +} + + /* SPINBOX */ +/*QAbstractSpinBox{ + background-color: %%ALTBASECOLOR%%; + border: 1px solid %%ACCENTCOLOR%%; + border-radius: 3px; +} +QAbstractSpinBox:disabled{ + color: %%ACCENTDISABLECOLOR%%; +}*/ +/*QAbstractSpinBox::down-button{ + subcontrol-origin: border; + subcontrol-position: left; + width: 16px; + border-width: 1px; +} +QAbstractSpinBox::up-button{ + subcontrol-origin: border; + subcontrol-position: right; + width: 16px; + border-width: 1px; +}*/ +/* +QAbstractSpinBox::down-arrow{ + border-image: url(":/trolltech/styles/commonstyle/images/left-16.png"); + width: 16px; + height: 16px; +} +QAbstractSpinBox::down-arrow:disabled, QAbstractSpinBox::up-arrow:disabled, QAbstractSpinBox::down-arrow:off, QAbstractSpinBox::up-arrow:off{ + border-image: url(:/none); +} +QAbstractSpinBox::up-arrow{ + border-image: url(":/trolltech/styles/commonstyle/images/right-16.png"); + width: 16px; + height: 16px; +}*/ diff --git a/src-qt5/core/libLumina/themes/None.qss.template b/src-qt5/core/libLumina/themes/None.qss.template new file mode 100644 index 00000000..7d923b1e --- /dev/null +++ b/src-qt5/core/libLumina/themes/None.qss.template @@ -0,0 +1,118 @@ +/* This is a blank stylesheet to disable the Lumina Themes almost entirely*/ +QWidget{ + font-family: %%FONT%%; + font-size: %%FONTSIZE%%; +} +/* Set the panel appearance for this theme (unless manually customized) */ +QWidget#LuminaPanelColor{ + background: QLinearGradient(x1: 0, y1: 0, x2: 1, y2: 1, stop: 0 %%PRIMARYCOLOR%%, stop: 1 %%PRIMARYDISABLECOLOR%%); + border-radius: 5px; +} +QWidget#LuminaBootSplash{ + background: %%BASECOLOR%%; + border-radius: 5px; +} + +/* Set the default canvas appearance for Lumina desktop plugins*/ +/* Default to a non-transparent background for all desktop plugins*/ +LDPlugin{ + background: %%BASECOLOR%%; + border-radius: 5px; +} +/* Now specify which plugins should have a transparent background */ +LDPlugin#applauncher, LDPlugin#desktopview{ + background: transparent; + border-radius: 5px; +} + +LDPlugin#applauncher QToolButton{ +background: transparent; + border: none; + border-radius: 5px; + color: white; +} +LDPlugin#applauncher QToolButton:hover{ + background: %%PRIMARYDISABLECOLOR%%; + border: none; + border-radius: 5px; + color: %%TEXTHIGHLIGHTCOLOR%%; +} + +LDPlugin#desktopview QListWidget{ + background: transparent; + border: 1px solid transparent; +} +LDPlugin#desktopview QListWidget::item{ + background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, + stop: 0 transparent, + stop: 0.7 transparent, + stop: 1.0 %%PRIMARYDISABLECOLOR%%); + border-radius: 5px; + color: %%TEXTCOLOR%%; +} + +LDPlugin#desktopview QListWidget::item:hover{ + background: %%PRIMARYDISABLECOLOR%%; + border-radius: 5px; + color: %%TEXTHIGHLIGHTCOLOR%%; +} +/*For the special widgets on the user button*/ +UserItemWidget, ItemWidget{ + background: transparent; + border-radius: 3px; +} +UserItemWidget:hover, ItemWidget:hover{ + background: %%HIGHLIGHTCOLOR%%; + color: %%TEXTHIGHLIGHTCOLOR%%; +} + +/*Special taskmanager window buttons: based on window state*/ +LTBWidget{ + border: 1px solid transparent; + border-radius: 3px; +} +LTBWidget::menu-indicator{ image: none; } /*disable the menu arrow*/ +LTBWidget#WindowVisible{ + background: QLinearGradient(x1: 0, y1: 0, x2: 1, y2: 1.1, stop: 0.3 %%SECONDARYCOLOR%%, stop: 1 transparent); +} +LTBWidget#WindowInvisible{ + /* Primary color is used for the panel appearance, so use that to make it disappear*/ + background: transparent; +} +LTBWidget#WindowActive{ + background: QLinearGradient(x1: 0, y1: 0, x2: 1, y2: 1.1, stop: 0.3 %%HIGHLIGHTDISABLECOLOR%%, stop: 1 transparent); +} +LTBWidget#WindowAttention{ + background: QLinearGradient(x1: 0, y1: 0, x2: 1, y2: 1.1, stop: 0.3 %%HIGHLIGHTCOLOR%%, stop: 1 transparent); +} +LTBWidget:hover, LTBWidget#WindowVisible:hover, LTBWidget#WindowInvisible:hover, LTBWidget#WindowActive:hover, LTBWidget#WindowAttention:hover{ + background: %%HIGHLIGHTCOLOR%%; + color: %%TEXTHIGHLIGHTCOLOR%%; + border: 1px solid %%ACCENTCOLOR%%; +} + +/* CALENDER WIDGET */ + /* (This is a special hack since there is no official support for stylesheets for this widget) */ + QCalendarWidget QWidget#qt_calendar_navigationbar{ + background-color: %%ALTBASECOLOR%%; + } +QCalendarWidget QWidget{ + background-color: %%BASECOLOR%%; + alternate-background-color: %%HIGHLIGHTDISABLECOLOR%%; + color: %%TEXTCOLOR%%; +} +QCalendarWidget QAbstractButton{ + background-color: transparent; +} +QCalendarWidget QAbstractButton::menu-indicator{ + image: none; +} +QCalendarWidget QAbstractItemView{ + background-color: %%SECONDARYCOLOR%%; + selection-background-color: QLinearGradient(x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 %%HIGHLIGHTDISABLECOLOR%%, stop: 1 %%HIGHLIGHTCOLOR%%);; + selection-color: %%TEXTHIGHLIGHTCOLOR%%; +} +QCalendarWidget QWidget#qt_calendar_calendarview{ + background-color: %%ALTBASECOLOR%%; + border: none; +}
\ No newline at end of file |