From 9f3183df9ab2eb82616bfc40c251d9d643e2a728 Mon Sep 17 00:00:00 2001 From: Kyle Bader Date: Mon, 23 Aug 2021 20:55:11 -0600 Subject: [PATCH 01/10] multiapp launcher init --- include/plugins/launcher_plugin.hpp | 8 + include/plugins/plugin.hpp | 4 +- plugins/brightness/mocked/mocked.hpp | 2 +- .../brightness/official_rpi/official_rpi.hpp | 2 +- plugins/brightness/x/x.hpp | 2 +- plugins/launcher/app/app.hpp | 2 +- plugins/launcher/home/Entry.ui | 63 ++ plugins/launcher/home/app.cpp | 565 ++++++++++++++++++ plugins/launcher/home/app.hpp | 183 ++++++ plugins/launcher/home/home.json | 4 + plugins/radio/rtl_sdr/rtl_sdr.hpp | 2 +- plugins/vehicle/test/test.hpp | 2 +- src/app/pages/launcher.cpp | 8 + 13 files changed, 840 insertions(+), 7 deletions(-) create mode 100644 plugins/launcher/home/Entry.ui create mode 100644 plugins/launcher/home/app.cpp create mode 100644 plugins/launcher/home/app.hpp create mode 100644 plugins/launcher/home/home.json diff --git a/include/plugins/launcher_plugin.hpp b/include/plugins/launcher_plugin.hpp index feef6e15..0dd520a4 100644 --- a/include/plugins/launcher_plugin.hpp +++ b/include/plugins/launcher_plugin.hpp @@ -1,13 +1,21 @@ #pragma once +#include #include "plugins/plugin.hpp" class LauncherPlugin : public Plugin { + Q_OBJECT + public: LauncherPlugin() { this->settings.beginGroup("Launcher"); } virtual ~LauncherPlugin() = default; virtual void remove_widget(int idx) { this->loaded_widgets.removeAt(idx); } +public slots: + + signals: + void widget_added(QWidget *widget); + protected: QList loaded_widgets; }; diff --git a/include/plugins/plugin.hpp b/include/plugins/plugin.hpp index eeb1c3c1..a0e61739 100644 --- a/include/plugins/plugin.hpp +++ b/include/plugins/plugin.hpp @@ -7,7 +7,9 @@ class Arbiter; -class Plugin { +class Plugin : public QObject { + Q_OBJECT + public: Plugin() : settings(qApp->organizationName(), "plugins") {} virtual ~Plugin() = default; diff --git a/plugins/brightness/mocked/mocked.hpp b/plugins/brightness/mocked/mocked.hpp index 7825a0a0..12dffcbd 100644 --- a/plugins/brightness/mocked/mocked.hpp +++ b/plugins/brightness/mocked/mocked.hpp @@ -5,7 +5,7 @@ #include "plugins/brightness_plugin.hpp" -class Mocked : public QObject, BrightnessPlugin { +class Mocked : public BrightnessPlugin { Q_OBJECT Q_PLUGIN_METADATA(IID BrightnessPlugin_iid FILE "mocked.json") Q_INTERFACES(BrightnessPlugin) diff --git a/plugins/brightness/official_rpi/official_rpi.hpp b/plugins/brightness/official_rpi/official_rpi.hpp index 5344a94b..16bd4adb 100644 --- a/plugins/brightness/official_rpi/official_rpi.hpp +++ b/plugins/brightness/official_rpi/official_rpi.hpp @@ -6,7 +6,7 @@ #include "plugins/brightness_plugin.hpp" -class OfficialRPi : public QObject, BrightnessPlugin { +class OfficialRPi : public BrightnessPlugin { Q_OBJECT Q_PLUGIN_METADATA(IID BrightnessPlugin_iid FILE "official_rpi.json") Q_INTERFACES(BrightnessPlugin) diff --git a/plugins/brightness/x/x.hpp b/plugins/brightness/x/x.hpp index ccb4265b..9e622867 100644 --- a/plugins/brightness/x/x.hpp +++ b/plugins/brightness/x/x.hpp @@ -3,7 +3,7 @@ #include #include "plugins/brightness_plugin.hpp" -class X : public QObject, BrightnessPlugin { +class X : public BrightnessPlugin { Q_OBJECT Q_PLUGIN_METADATA(IID BrightnessPlugin_iid FILE "x.json") Q_INTERFACES(BrightnessPlugin) diff --git a/plugins/launcher/app/app.hpp b/plugins/launcher/app/app.hpp index 845d45f2..9f40178e 100644 --- a/plugins/launcher/app/app.hpp +++ b/plugins/launcher/app/app.hpp @@ -100,7 +100,7 @@ class Launcher : public QWidget { }; -class App : public QObject, LauncherPlugin { +class App : public LauncherPlugin { Q_OBJECT Q_PLUGIN_METADATA(IID LauncherPlugin_iid FILE "app.json") Q_INTERFACES(LauncherPlugin) diff --git a/plugins/launcher/home/Entry.ui b/plugins/launcher/home/Entry.ui new file mode 100644 index 00000000..ab00c53d --- /dev/null +++ b/plugins/launcher/home/Entry.ui @@ -0,0 +1,63 @@ + + + Form + + + + 0 + 0 + 128 + 128 + + + + Form + + + + + + + + background-color: red; + + + + + + Qt::AlignCenter + + + + + + + + + + 16777215 + 30 + + + + 1 + + + Qt Maintenance Launcher + + + false + + + Qt::AlignCenter + + + true + + + + + + + + diff --git a/plugins/launcher/home/app.cpp b/plugins/launcher/home/app.cpp new file mode 100644 index 00000000..a40f9269 --- /dev/null +++ b/plugins/launcher/home/app.cpp @@ -0,0 +1,565 @@ +#include +#include +#include +#include + +#include "app/config.hpp" +#include "app/arbiter.hpp" +#include "app/session.hpp" +#include "app/pages/launcher.hpp" + +#include "app.hpp" + + +XWorker::WindowProp::WindowProp(char *prop, unsigned long size) +{ + this->size = size; + this->prop = new char[this->size + 1]; + + std::copy(prop, prop + size, (char *)this->prop); + ((char *)this->prop)[size] = '\0'; +} + +XWorker::WindowProp::~WindowProp() +{ + if (this->prop != nullptr) { + delete (char *)this->prop; + this->prop = nullptr; + } +} + +XWorker::XWorker(QObject *parent) : QObject(parent) +{ + this->display = XOpenDisplay(0); + this->root_window = DefaultRootWindow(this->display); +} + +int XWorker::get_window(uint64_t pid) +{ + int retries = 0; + while (retries < MAX_RETRIES) { + WindowProp client_list = this->get_window_prop(this->root_window, XA_WINDOW, "_NET_CLIENT_LIST"); + Window *windows = (Window *)client_list.prop; + for (unsigned long i = 0; i < (client_list.size / sizeof(Window)); i++) { + if (pid == *(uint64_t *)this->get_window_prop(windows[i], XA_CARDINAL, "_NET_WM_PID").prop) + return windows[i]; + } + usleep(500000); + retries++; + } + + return -1; +} + +XWorker::WindowProp XWorker::get_window_prop(Window window, Atom type, const char *name) +{ + Atom prop = XInternAtom(this->display, name, false); + + Atom actual_type_return; + int actual_format_return; + unsigned long nitems_return; + unsigned long bytes_after_return; + unsigned char *prop_return; + XGetWindowProperty(this->display, window, prop, 0, 1024, false, type, &actual_type_return, &actual_format_return, + &nitems_return, &bytes_after_return, &prop_return); + + unsigned long size = (actual_format_return / 8) * nitems_return; + if (actual_format_return == 32) size *= sizeof(long) / 4; + + WindowProp window_prop((char *)prop_return, size); + XFree(prop_return); + + return window_prop; +} + +EmbeddedApp::EmbeddedApp(Arbiter *arbiter, QWidget *parent) : QWidget(parent), process() +{ + this->arbiter = arbiter; + this->worker = new XWorker(this); + + this->process.setStandardOutputFile(QProcess::nullDevice()); + this->process.setStandardErrorFile(QProcess::nullDevice()); + connect(&this->process, QOverload::of(&QProcess::finished), + [this](int, QProcess::ExitStatus) { this->end(); }); + + this->container = new QVBoxLayout(this); + this->container->setContentsMargins(0, 0, 0, 0); +} + +EmbeddedApp::~EmbeddedApp() +{ + this->process.kill(); + this->process.waitForFinished(); + + delete this->container; + delete this->worker; +} + +void EmbeddedApp::start(QString app) +{ + qDebug() << "[Home] starting program: " << app; + this->process.setProgram(app); + this->process.start(); + this->process.waitForStarted(); + + QWindow *window = QWindow::fromWinId(worker->get_window(this->process.processId())); + + QWindow *thisWindow = QWindow::fromWinId(this->winId()); + window->setParent(thisWindow); + window->setFlags(Qt::FramelessWindowHint); + usleep(50000); + + if(window->isModal()) qDebug() << "app is modal: " << app; + if(window->isTopLevel()) qDebug() << "app is Top Level: " << app; + if(!window->isExposed()) qDebug() << "window not exposed: " << app; + if(!window->isActive()) qDebug() << "window not active: " << app; + if(window == nullptr) { + qDebug() << "error opening application: " << app; + return; + } + + this->container->addWidget(QWidget::createWindowContainer(window, this)); + emit opened(); +} + +void EmbeddedApp::end() +{ + this->process.terminate(); + delete this->container->takeAt(0); + emit closed(); +} + +Launcher::Launcher(Arbiter *arbiter, QSettings &settings, int idx, App *plugin, QWidget *parent) + : QWidget(parent) + , arbiter(arbiter) + , settings(settings) + , idx(idx) +{ + this->plugin = plugin; + this->setObjectName("Home"); + + QStackedLayout *layout = new QStackedLayout(this); + layout->setContentsMargins(0, 0, 0, 0); + + this->app = new EmbeddedApp(this->arbiter, this); + connect(this->app, &EmbeddedApp::opened, [layout]() { layout->setCurrentIndex(1); }); + connect(this->app, &EmbeddedApp::closed, [layout]() { layout->setCurrentIndex(0); }); + + auto launcher_app = this->settings.value(this->app_key()).toString(); + //this->auto_launch = !launcher_app.isEmpty(); + + layout->addWidget(this->home_widget()); + layout->addWidget(this->app); + +} + +Launcher::~Launcher() +{ + delete this->app; +} + +void Launcher::update_idx(int idx) +{ + //TODO: reimplement + if (idx == this->idx) + return; + + QString home; + QString app; + if (this->settings.contains(this->home_key())) + home = this->settings.value(this->home_key(), this->DEFAULT_DIR).toString(); + if (this->settings.contains(this->app_key())) + app = this->settings.value(this->app_key()).toString(); + + this->settings.remove(QString::number(this->idx)); + this->idx = idx; + if (!home.isNull()) + this->settings.setValue(this->home_key(), home); + if (!app.isNull()) + this->settings.setValue(this->app_key(), app); +} + +QWidget *Launcher::home_widget() +{ + auto homePage = new QWidget(this); + auto gridLayout = new QGridLayout(homePage); + gridLayout->setObjectName(QString::fromUtf8("gridLayout")); + gridLayout->setSizeConstraint(QLayout::SetDefaultConstraint); + homePage->setLayout(gridLayout); + auto entries = DesktopEntry::get_entries(this->arbiter, this->plugin); + + //TODO: detect width and set appropriately + int x = 0; + int y = 0; + for (int i = 0; i < entries.count(); ++i){ + + DesktopEntry *entry = entries[i]; + connect(entry, &DesktopEntry::clicked, [this, entry]() { + + EmbeddedApp *app = new EmbeddedApp(this->arbiter, this); + + this->plugin->add_widget(app); + + app->start(entry->get_exec()); + + }); + gridLayout->addWidget(entry, y, x, 1, 1); + + //set x & y + if(x > 6) { + x = 0; + y += 1; + } else { + x += 1; + } + + } + + QScrollArea *scroll_area = new QScrollArea(this); + Session::Forge::to_touch_scroller(scroll_area); + scroll_area->setWidgetResizable(true); + scroll_area->setWidget(homePage); + return scroll_area; + +} + +QWidget *Launcher::config_widget() +{ + //TODO: why do i need this? + QWidget *widget = new QWidget(this); + //QVBoxLayout *layout = new QVBoxLayout(widget); + + return widget; +} + + +QList App::widgets() +{ + if(this->_widgets.count() == 0) this->init(); + + return this->_widgets; +} + +void App::init(){ + + this->_widgets.push_front(new Launcher(this->arbiter, this->settings, 0, this)); +} + +App::App(){ + + this->settings.beginGroup("App"); + +} + +void App::add_widget(QWidget *widget){ + + this->_widgets.push_back(widget); + emit widget_added(widget); + +} + +void App::remove_widget(int idx) +{ + LauncherPlugin::remove_widget(idx); + + this->settings.remove(QString::number(idx)); + for (int i = 0; i < this->loaded_widgets.size(); i++) { + if (Launcher *launcher = qobject_cast(this->loaded_widgets[i])) + launcher->update_idx(i); + } +} + +DesktopEntry::DesktopEntry(QString fileLocation, Arbiter *arbiter, App *plugin, QWidget *parent) + +{ + this->arbiter = arbiter; + this->plugin = plugin; + + QFile inputFile(fileLocation); + inputFile.open(QIODevice::ReadOnly); + if (!inputFile.isOpen()){ + + qDebug() << "[Home] invalid Desktop Entry " << inputFile.errorString(); + return; + } + + QTextStream stream(&inputFile); + for (QString line = stream.readLine(); + !line.isNull(); + line = stream.readLine()) + { + + if(line.contains("Exec")){ + QStringList vals = line.split( "=" ); + this->exec_ = vals.value(1); + + QStringList args = vals.value(1).split( " " ); + this->exec_ = args.value(0); + args.removeAt(0); + this->args_ = args; + //TODO: remove flags and add to separate field + qDebug() << this->exec_; + } + + if(line.contains("Name") && !line.contains("Generic") && !line.contains("[") && !line.contains("]")){ + QStringList vals = line.split( "=" ); + this->name_ = vals.value(1); + } + + if(line.contains("Icon")){ + QStringList vals = line.split( "=" ); + this->icon_ = vals.value(1); + } + + if(line.contains("[Desktop") && !line.contains("Entry]")) break;//skip extra stuff in Desktop Entry + + }; + + this->setup_ui(); + +} + +void DesktopEntry::setup_ui(){ + + //setup ui + QVBoxLayout *verticalLayout_3; + QHBoxLayout *horizontalLayout; + QLabel *iconLabel; + QLabel *nameLabel; + + verticalLayout_3 = new QVBoxLayout(this); + verticalLayout_3->setObjectName(QString::fromUtf8("verticalLayout_3")); + horizontalLayout = new QHBoxLayout(); + horizontalLayout->setObjectName(QString::fromUtf8("horizontalLayout")); + iconLabel = new QLabel(this); + iconLabel->setMaximumSize(QSize(64, 64)); + iconLabel->setObjectName(QString::fromUtf8("iconLabel")); + iconLabel->setAlignment(Qt::AlignCenter); + //iconLabel->setPixmap(this->get_pixmap()); + iconLabel->setPixmap (this->get_pixmap().scaled(64,64,Qt::KeepAspectRatio)); + iconLabel->show(); + + horizontalLayout->addWidget(iconLabel); + verticalLayout_3->addLayout(horizontalLayout); + + nameLabel = new QLabel(this); + QFontMetrics metrics(nameLabel->font()); + QString elidedText = metrics.elidedText(this->get_name(), Qt::ElideRight, (nameLabel->width() * 2) - 16); + nameLabel->setObjectName(QString::fromUtf8("nameLabel")); + nameLabel->setText(elidedText); + nameLabel->setMaximumSize(QSize(112, 64)); + nameLabel->setAlignment(Qt::AlignCenter); + nameLabel->setWordWrap(true); + + verticalLayout_3->addWidget(nameLabel); + this->setMaximumSize(QSize(128, 128)); + +} + +void DesktopEntry::mousePressEvent(QMouseEvent *event){ + emit clicked(); +} + + +int DesktopEntry::resolutionFromString(QString string){ + + if(string.contains("scalable")) return 10000; + if(string.contains("128")) return 128; + if(string.contains("64")) return 64; + if(string.contains("48")) return 48; + if(string.contains("42")) return 42; + if(string.contains("36")) return 36; + if(string.contains("32")) return 32; + if(string.contains("24")) return 24; + if(string.contains("22")) return 22; + if(string.contains("16")) return 16; + return 0;//error +} + +QPixmap DesktopEntry::get_pixmap(){ + + /* The freedesktop.org standard specifies in which order and directories programs should look for icons: + + $HOME/.icons (for backwards compatibility) + $XDG_DATA_DIRS/icons + /usr/share/pixmaps + + from the spec: + + "In the theme directory are also a set of subdirectories containing image files. + Each directory contains icons designed for a certain nominal icon size and scale, + as described by the index.theme file. The subdirectories are allowed to be several levels deep, + e.g. the subdirectory "48x48/apps" in the theme "hicolor" would end up at $basedir/icons/hicolor/48x48/apps{icon_}.{png|svg}" + + */ + + //return widgets ico if blank + if(this->icon_ == "") { + + QPixmap pixmap(":/icons/widgets.svg"); + QSize size(512, 512); + auto icon_mask = pixmap.scaled(size).createMaskFromColor(Qt::transparent); + auto color = (this->arbiter->theme().mode == Session::Theme::Light) ? QColor(0, 0, 0) : QColor(255, 255, 255); + color.setAlpha((this->arbiter->theme().mode == Session::Theme::Light) ? 162 : 134); + QPixmap icon(size); + icon.fill(color); + icon.setMask(icon_mask); + + return icon; + + } + //check if icon is path + if(this->icon_.contains("/") && (this->icon_.contains(".png") || this->icon_.contains(".svg") || this->icon_.contains(".xpm"))){ + if(!QFile::exists(this->icon_)){ + return QPixmap(":/icons/widgets.svg"); + } + return QPixmap(this->icon_); + } + + //check in $HOME/.icons + auto h_png_path = QDir::homePath() + "/" + this->icon_ + ".png"; + if(QFile::exists(h_png_path)) return QPixmap(h_png_path); + + auto h_svg_path = QDir::homePath() + "/" + this->icon_ + ".svg"; + if(QFile::exists(h_svg_path)) return QPixmap(h_svg_path); + + auto h_xpm_path = QDir::homePath() + "/" + this->icon_ + ".xpm"; + if(QFile::exists(h_xpm_path)) return QPixmap(h_xpm_path); + + //get theme dirs from DATA_DIRS + QString xdg = qgetenv("XDG_DATA_DIRS"); + auto xdgArr = xdg.split(":"); + for(QString dir_name : xdgArr) + { + + //drop repeated dirs + if(dir_name.endsWith("/")) continue; + qDebug() << "Checking Dir: " << dir_name; + + //get themes from dir + //TODO: prefer certain themes + QDirIterator themes( dir_name + "/icons", QDir::Dirs | QDir::NoDotAndDotDot); + while ( themes.hasNext() ) { + + QString themeDirPath = themes.next(); + QDir themeDir(themeDirPath); + QString theme = themeDir.dirName(); + qDebug() << "Checking Theme: " << theme; + + //TODO: get resolution folders and sort by preference(scalable then size) + QDirIterator resolutions( themeDirPath, QDir::Dirs | QDir::NoDotAndDotDot); + QStringList resolutionList = {}; + + int highestResolution = 0; + QString highestResPath = ""; + while ( resolutions.hasNext() ) { + + QString resolutionPath = resolutions.next(); + QDir resolutionDir(resolutionPath); + QString resolution = resolutionDir.dirName(); + qDebug() << "Getting resolution" << resolution; + int iRes = DesktopEntry::resolutionFromString(resolution); + + //skip if higher resolution already available + if(highestResolution > iRes) continue; + + QDirIterator subdirs( resolutionPath, QDir::Dirs | QDir::NoDotAndDotDot); + while ( subdirs.hasNext() ) { + + QDir subDir(subdirs.next()); + QString sub_dir_name = subDir.dirName(); + auto xdg_png_path = dir_name + "/icons/" + theme + "/" + resolution + "/" + sub_dir_name + "/" + this->icon_ + ".png"; + qDebug() << "Searching for " << xdg_png_path; + if(QFile::exists(xdg_png_path)) { + highestResolution = iRes; + highestResPath = xdg_png_path; + + } + auto xdg_svg_path = dir_name + "/icons/" + theme + "/" + resolution + "/" + sub_dir_name + "/" + this->icon_ + ".svg"; + if(QFile::exists(xdg_svg_path)) { + highestResolution = iRes; + highestResPath = xdg_svg_path; + } + + auto xdg_xpm_path = dir_name + "/icons/" + theme + "/" + resolution + "/" + sub_dir_name + "/" + this->icon_ + ".xpm"; + if(QFile::exists(xdg_xpm_path)) { + highestResolution = iRes; + highestResPath = xdg_xpm_path; + } + + } + + } + + if(highestResolution > 0){ + return QPixmap(highestResPath); + } + + } + } + + //check pixmaps + auto pixmap_png_path = "/usr/share/pixmaps/" + this->icon_ + ".png"; + if(QFile::exists(pixmap_png_path)) { + return QPixmap(pixmap_png_path); + } + auto pixmap_svg_path = "/usr/share/pixmaps/" + this->icon_ + ".svg"; + if(QFile::exists(pixmap_svg_path)) { + return QPixmap(pixmap_svg_path); + } + + auto pixmap_xpm_path = "/usr/share/pixmaps/" + this->icon_ + ".xpm"; + if(QFile::exists(pixmap_xpm_path)) { + return QPixmap(pixmap_xpm_path); + } + + //nothing else worked + qDebug() << "no icon found"; + + QPixmap pixmap(":/icons/widgets.svg"); + QSize size(512, 512); + auto icon_mask = pixmap.scaled(size).createMaskFromColor(Qt::transparent); + auto color = (this->arbiter->theme().mode == Session::Theme::Light) ? QColor(0, 0, 0) : QColor(255, 255, 255); + color.setAlpha((this->arbiter->theme().mode == Session::Theme::Light) ? 162 : 134); + QPixmap icon(size); + icon.fill(color); + icon.setMask(icon_mask); + + return icon; +} + +QList DesktopEntry::get_entries(Arbiter *arbiter, App *plugin) +{ + /* + Desktop entry files must reside in the $XDG_DATA_DIRS/applications directory and must have a .desktop file extension. + If $XDG_DATA_DIRS is not set, then the default path is /usr/share is used. + If $XDG_DATA_HOME is not set, then the default path ~/.local/share is used. + Desktop entries are collected from all directories in the $XDG_DATA_DIRS environment variable. + Directories which appear first in $XDG_CONFIG_DIRS are given precedence when there are several .desktop files with the same name. + */ + + QList rtn; + QString xdg = qgetenv("XDG_DATA_DIRS"); + for(QString dir_name : xdg.split(":")) + { + + auto dir = dir_name + "/applications"; + QDir source(dir); + if (!source.exists()) + continue; + + QStringList files = source.entryList(QStringList() << "*.desktop", QDir::Files); + for (QString file: files) + { + //TODO: validate & error handle + QString path = source.absoluteFilePath(file); + DesktopEntry *entry = new DesktopEntry(path, arbiter, plugin); + rtn.push_back(entry); + + } + + } + + return rtn; + +} diff --git a/plugins/launcher/home/app.hpp b/plugins/launcher/home/app.hpp new file mode 100644 index 00000000..2e5a017f --- /dev/null +++ b/plugins/launcher/home/app.hpp @@ -0,0 +1,183 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "plugins/launcher_plugin.hpp" + +#include +#include +#undef Bool +#undef CurrentTime +#undef CursorShape +#undef Expose +#undef KeyPress +#undef KeyRelease +#undef FocusIn +#undef FocusOut +#undef FontChange +#undef None +#undef Status +#undef Unsorted + +class Arbiter; +class Config; +class Theme; + +class XWorker : public QObject { + Q_OBJECT + + public: + XWorker(QObject *parent = nullptr); + int get_window(uint64_t pid); + + private: + const int MAX_RETRIES = 60; + + struct WindowProp { + WindowProp(char *prop, unsigned long size); + ~WindowProp(); + + void *prop; + unsigned long size; + }; + + WindowProp get_window_prop(Window window, Atom type, const char *name); + + Display *display; + Window root_window; +}; + +class EmbeddedApp : public QWidget { + Q_OBJECT + + public: + EmbeddedApp(Arbiter *arbiter, QWidget *parent = nullptr); + ~EmbeddedApp(); + + void start(QString app); + void end(); + + private: + QProcess process; + QVBoxLayout *container; + XWorker *worker; + Arbiter *arbiter; + + signals: + void closed(); + void opened(); +}; + + +//TODO: REFACTOR - change name to ILauncherPlugin +class App : public LauncherPlugin { + Q_OBJECT + Q_PLUGIN_METADATA(IID LauncherPlugin_iid FILE "home.json") + Q_INTERFACES(LauncherPlugin) + +public: + App(); + QList widgets() override; + void remove_widget(int idx) override; + + void add_widget(QWidget *widget); + +public slots: + + signals: + + // void widget_added() override; + + +private: + void init(); + QList _widgets; + + +}; + +class DesktopEntry : public QWidget{ + Q_OBJECT + +public: + DesktopEntry(QString fileLocation, Arbiter *arbiter, App *plugin, QWidget *parent = nullptr); + QWidget *get_widget(); + static QList get_entries(Arbiter *arbiter, App *plugin); + static int resolutionFromString(QString string); + // static QList get_all_widgets(); + inline QString get_exec() { return this->exec_; }; + inline QString get_icon() { return this->icon_; }; + inline QString get_name() { return this->name_; }; + inline QString get_path() { return this->path_; }; + inline QList get_args() {return this->args_;}; + QPixmap get_pixmap(); + +public slots: + +signals: + void clicked(); + +protected: + void mousePressEvent(QMouseEvent *event) override; + +private: + QVBoxLayout *verticalLayout_3; + QHBoxLayout *horizontalLayout; + QLabel *iconLabel; + QLabel *nameLabel; + + Arbiter *arbiter; + App *plugin; + QString exec_; + QString path_; + QString name_; + QString icon_; + QList args_; + + void setup_ui(); + +}; +//TODO: REFACTOR - rename to home +class Launcher : public QWidget { + Q_OBJECT + + public: + Launcher(Arbiter *arbiter, QSettings &settings, int idx, App *plugin, QWidget *parent = nullptr); + ~Launcher(); + void update_idx(int idx); + + private: + const QString DEFAULT_DIR = QDir().absolutePath(); + +// QWidget *launcher_widget(); + // QWidget *homepage_widget(); + QWidget *home_widget(); + // QWidget *app_select_widget(); + QWidget *config_widget(); + // void populate_dirs(QString path); + // void populate_apps(QString path); + + inline QString home_key() { return QString("%1/home").arg(this->idx); } + inline QString app_key() { return QString("%1/app").arg(this->idx); } + + Arbiter *arbiter; + QSettings &settings; + EmbeddedApp *app; + App *plugin; + QLabel *path_label; + // QListWidget *folders; + // QListWidget *apps; + int idx; + //bool auto_launch = false; + +}; + diff --git a/plugins/launcher/home/home.json b/plugins/launcher/home/home.json new file mode 100644 index 00000000..c7de6519 --- /dev/null +++ b/plugins/launcher/home/home.json @@ -0,0 +1,4 @@ +{ + "name" : "home", + "version" : "1.0" +} diff --git a/plugins/radio/rtl_sdr/rtl_sdr.hpp b/plugins/radio/rtl_sdr/rtl_sdr.hpp index 319c7730..37190d12 100644 --- a/plugins/radio/rtl_sdr/rtl_sdr.hpp +++ b/plugins/radio/rtl_sdr/rtl_sdr.hpp @@ -6,7 +6,7 @@ #include "plugins/radio_plugin.hpp" -class RtlSdr : public QObject, RadioPlugin { +class RtlSdr : public RadioPlugin { Q_OBJECT Q_PLUGIN_METADATA(IID RadioPlugin_iid FILE "rtl_sdr.json") Q_INTERFACES(RadioPlugin) diff --git a/plugins/vehicle/test/test.hpp b/plugins/vehicle/test/test.hpp index 36c53c7e..a5a1c054 100644 --- a/plugins/vehicle/test/test.hpp +++ b/plugins/vehicle/test/test.hpp @@ -5,7 +5,7 @@ #include "app/widgets/climate.hpp" #include "canbus/socketcanbus.hpp" -class Test : public QObject, VehiclePlugin { +class Test : public VehiclePlugin { Q_OBJECT Q_PLUGIN_METADATA(IID VehiclePlugin_iid FILE "test.json") Q_INTERFACES(VehiclePlugin) diff --git a/src/app/pages/launcher.cpp b/src/app/pages/launcher.cpp index d2bc5204..2d747e83 100644 --- a/src/app/pages/launcher.cpp +++ b/src/app/pages/launcher.cpp @@ -32,6 +32,14 @@ LauncherPlugins::LauncherPlugins(Arbiter &arbiter, QWidget *parent) plugin->dashize(&this->arbiter); for (QWidget *tab : plugin->widgets()) this->addTab(tab, tab->objectName()); + + + connect(plugin, &LauncherPlugin::widget_added, [this](QWidget *tab){ + this->addTab(tab, "window"); + qDebug() << "widget added"; + + }); + this->active_plugins.append(plugin_loader); this->active_plugins_list->addItem(key); this->config->set_launcher_plugin(key); From 704a604169662460c34286bb954c587a8fd4df3c Mon Sep 17 00:00:00 2001 From: Kyle Bader Date: Mon, 23 Aug 2021 21:17:30 -0600 Subject: [PATCH 02/10] rm Entry.ui --- plugins/launcher/home/Entry.ui | 63 ---------------------------------- 1 file changed, 63 deletions(-) delete mode 100644 plugins/launcher/home/Entry.ui diff --git a/plugins/launcher/home/Entry.ui b/plugins/launcher/home/Entry.ui deleted file mode 100644 index ab00c53d..00000000 --- a/plugins/launcher/home/Entry.ui +++ /dev/null @@ -1,63 +0,0 @@ - - - Form - - - - 0 - 0 - 128 - 128 - - - - Form - - - - - - - - background-color: red; - - - - - - Qt::AlignCenter - - - - - - - - - - 16777215 - 30 - - - - 1 - - - Qt Maintenance Launcher - - - false - - - Qt::AlignCenter - - - true - - - - - - - - From 0c1858ff14671d1e7b0b74c723c599001e76463b Mon Sep 17 00:00:00 2001 From: Kyle Bader Date: Tue, 24 Aug 2021 17:28:11 -0600 Subject: [PATCH 03/10] fixed window tab not opening --- include/plugins/launcher_plugin.hpp | 3 +- install.sh | 2 +- plugins/launcher/app/app.cpp | 297 ---------------------------- plugins/launcher/app/app.hpp | 112 ----------- plugins/launcher/app/app.json | 4 - plugins/launcher/home/app.cpp | 1 + plugins/launcher/home/app.hpp | 8 +- src/app/pages/launcher.cpp | 6 + 8 files changed, 11 insertions(+), 422 deletions(-) delete mode 100644 plugins/launcher/app/app.cpp delete mode 100644 plugins/launcher/app/app.hpp delete mode 100644 plugins/launcher/app/app.json diff --git a/include/plugins/launcher_plugin.hpp b/include/plugins/launcher_plugin.hpp index 0dd520a4..8e77c07e 100644 --- a/include/plugins/launcher_plugin.hpp +++ b/include/plugins/launcher_plugin.hpp @@ -14,7 +14,8 @@ class LauncherPlugin : public Plugin { public slots: signals: - void widget_added(QWidget *widget); + + void widget_added(QWidget *widget); protected: QList loaded_widgets; diff --git a/install.sh b/install.sh index d8a5c649..6786dc08 100755 --- a/install.sh +++ b/install.sh @@ -395,7 +395,7 @@ else fi echo Running Dash make - make + make -j4 if [[ $? -eq 0 ]]; then echo -e Dash make ok, executable can be found ../bin/dash echo diff --git a/plugins/launcher/app/app.cpp b/plugins/launcher/app/app.cpp deleted file mode 100644 index 5cbd21b8..00000000 --- a/plugins/launcher/app/app.cpp +++ /dev/null @@ -1,297 +0,0 @@ -#include -#include - -#include "app/config.hpp" -#include "app/arbiter.hpp" -#include "app/session.hpp" - -#include "app.hpp" - -XWorker::WindowProp::WindowProp(char *prop, unsigned long size) -{ - this->size = size; - this->prop = new char[this->size + 1]; - - std::copy(prop, prop + size, (char *)this->prop); - ((char *)this->prop)[size] = '\0'; -} - -XWorker::WindowProp::~WindowProp() -{ - if (this->prop != nullptr) { - delete (char *)this->prop; - this->prop = nullptr; - } -} - -XWorker::XWorker(QObject *parent) : QObject(parent) -{ - this->display = XOpenDisplay(nullptr); - this->root_window = DefaultRootWindow(this->display); -} - -int XWorker::get_window(uint64_t pid) -{ - int retries = 0; - while (retries < MAX_RETRIES) { - WindowProp client_list = this->get_window_prop(this->root_window, XA_WINDOW, "_NET_CLIENT_LIST"); - Window *windows = (Window *)client_list.prop; - for (unsigned long i = 0; i < (client_list.size / sizeof(Window)); i++) { - if (pid == *(uint64_t *)this->get_window_prop(windows[i], XA_CARDINAL, "_NET_WM_PID").prop) - return windows[i]; - } - usleep(500000); - retries++; - } - - return -1; -} - -XWorker::WindowProp XWorker::get_window_prop(Window window, Atom type, const char *name) -{ - Atom prop = XInternAtom(this->display, name, false); - - Atom actual_type_return; - int actual_format_return; - unsigned long nitems_return; - unsigned long bytes_after_return; - unsigned char *prop_return; - XGetWindowProperty(this->display, window, prop, 0, 1024, false, type, &actual_type_return, &actual_format_return, - &nitems_return, &bytes_after_return, &prop_return); - - unsigned long size = (actual_format_return / 8) * nitems_return; - if (actual_format_return == 32) size *= sizeof(long) / 4; - - WindowProp window_prop((char *)prop_return, size); - XFree(prop_return); - - return window_prop; -} - -EmbeddedApp::EmbeddedApp(QWidget *parent) : QWidget(parent), process() -{ - this->worker = new XWorker(this); - - this->process.setStandardOutputFile(QProcess::nullDevice()); - this->process.setStandardErrorFile(QProcess::nullDevice()); - connect(&this->process, QOverload::of(&QProcess::finished), - [this](int, QProcess::ExitStatus) { this->end(); }); - - this->container = new QVBoxLayout(this); - this->container->setContentsMargins(0, 0, 0, 0); -} - -EmbeddedApp::~EmbeddedApp() -{ - this->process.kill(); - this->process.waitForFinished(); - - delete this->container; - delete this->worker; -} - -void EmbeddedApp::start(QString app) -{ - this->process.setProgram(app); - this->process.start(); - - this->process.waitForStarted(); - - QWindow *window = QWindow::fromWinId(worker->get_window(this->process.processId())); - window->setFlags(Qt::FramelessWindowHint); - usleep(500000); - - this->container->addWidget(QWidget::createWindowContainer(window, this)); - - emit opened(); -} - -void EmbeddedApp::end() -{ - this->process.terminate(); - delete this->container->takeAt(0); - emit closed(); -} - -Launcher::Launcher(Arbiter &arbiter, QSettings &settings, int idx, QWidget *parent) - : QWidget(parent) - , arbiter(arbiter) - , settings(settings) - , idx(idx) -{ - this->setObjectName("App"); - - QStackedLayout *layout = new QStackedLayout(this); - layout->setContentsMargins(0, 0, 0, 0); - - this->app = new EmbeddedApp(this); - connect(this->app, &EmbeddedApp::opened, [layout]() { layout->setCurrentIndex(1); }); - connect(this->app, &EmbeddedApp::closed, [layout]() { layout->setCurrentIndex(0); }); - - auto launcher_app = this->settings.value(this->app_key()).toString(); - this->auto_launch = !launcher_app.isEmpty(); - - layout->addWidget(this->launcher_widget()); - layout->addWidget(this->app); - - if (this->auto_launch) - this->app->start(launcher_app); -} - -Launcher::~Launcher() -{ - delete this->app; -} - -void Launcher::update_idx(int idx) -{ - if (idx == this->idx) - return; - - QString home; - QString app; - if (this->settings.contains(this->home_key())) - home = this->settings.value(this->home_key(), this->DEFAULT_DIR).toString(); - if (this->settings.contains(this->app_key())) - app = this->settings.value(this->app_key()).toString(); - - this->settings.remove(QString::number(this->idx)); - this->idx = idx; - if (!home.isNull()) - this->settings.setValue(this->home_key(), home); - if (!app.isNull()) - this->settings.setValue(this->app_key(), app); -} - -QWidget *Launcher::launcher_widget() -{ - QWidget *widget = new QWidget(this); - QVBoxLayout *layout = new QVBoxLayout(widget); - - this->path_label = new QLabel(this->settings.value(this->home_key(), this->DEFAULT_DIR).toString(), this); - - layout->addStretch(1); - layout->addWidget(this->path_label, 1); - layout->addWidget(this->app_select_widget(), 6); - layout->addWidget(this->config_widget(), 1, Qt::AlignRight); - layout->addStretch(1); - - return widget; -} - -QWidget *Launcher::app_select_widget() -{ - QWidget *widget = new QWidget(this); - QHBoxLayout *layout = new QHBoxLayout(widget); - - QString root_path(this->path_label->text()); - - QPushButton *home_button = new QPushButton(widget); - home_button->setFlat(true); - home_button->setCheckable(true); - home_button->setChecked(true); - connect(home_button, &QPushButton::clicked, [this](bool checked = false) { - if (checked) - this->settings.setValue(this->home_key(), this->path_label->text()); - else - this->settings.remove(this->home_key()); - }); - this->arbiter.forge().iconize("playlist_add", "playlist_add_check", home_button, 32, true); - layout->addWidget(home_button, 0, Qt::AlignTop); - - this->folders = new QListWidget(widget); - Session::Forge::to_touch_scroller(this->folders); - this->populate_dirs(root_path); - layout->addWidget(this->folders, 4); - - this->apps = new QListWidget(widget); - Session::Forge::to_touch_scroller(this->apps); - this->populate_apps(root_path); - connect(this->apps, &QListWidget::itemClicked, [this](QListWidgetItem *item) { - QString app_path = this->path_label->text() + '/' + item->text(); - if (this->auto_launch) - this->settings.setValue(this->app_key(), app_path); - this->app->start(app_path); - }); - connect(this->folders, &QListWidget::itemClicked, [this, home_button](QListWidgetItem *item) { - if (!item->isSelected()) - return; - - this->apps->clear(); - QString current_path(item->data(Qt::UserRole).toString()); - this->path_label->setText(current_path); - this->populate_apps(current_path); - this->populate_dirs(current_path); - - home_button->setChecked(this->settings.value(this->home_key(), this->DEFAULT_DIR).toString() == current_path); - }); - layout->addWidget(this->apps, 5); - - return widget; -} - -QWidget *Launcher::config_widget() -{ - QWidget *widget = new QWidget(this); - QVBoxLayout *layout = new QVBoxLayout(widget); - - QCheckBox *checkbox = new QCheckBox("launch at startup", widget); - checkbox->setChecked(this->auto_launch); - connect(checkbox, &QCheckBox::toggled, [this, checkbox](bool checked) { - this->auto_launch = checked; - if (this->auto_launch && this->apps->currentItem()) - this->settings.setValue(this->app_key(), this->path_label->text() + '/' + this->apps->currentItem()->text()); - else - this->settings.remove(this->app_key()); - }); - - layout->addWidget(checkbox); - - return widget; -} - -void Launcher::populate_dirs(QString path) -{ - this->folders->clear(); - QDir current_dir(path); - for (QFileInfo dir : current_dir.entryInfoList(QDir::AllDirs | QDir::Readable)) { - if (dir.fileName() == ".") - continue; - - QListWidgetItem *item = new QListWidgetItem(dir.fileName(), this->folders); - if (dir.fileName() == "..") { - item->setText("↲"); - - if (current_dir.isRoot()) - item->setFlags(Qt::NoItemFlags); - } - else { - item->setText(dir.fileName()); - } - item->setData(Qt::UserRole, QVariant(dir.absoluteFilePath())); - } -} - -void Launcher::populate_apps(QString path) -{ - for (QString app : QDir(path).entryList(QDir::Files | QDir::Executable)) - new QListWidgetItem(app, this->apps); -} - -QList App::widgets() -{ - int size = this->loaded_widgets.size(); - this->loaded_widgets.append(new Launcher(*this->arbiter, this->settings, size)); - return this->loaded_widgets.mid(size); -} - -void App::remove_widget(int idx) -{ - LauncherPlugin::remove_widget(idx); - - this->settings.remove(QString::number(idx)); - for (int i = 0; i < this->loaded_widgets.size(); i++) { - if (Launcher *launcher = qobject_cast(this->loaded_widgets[i])) - launcher->update_idx(i); - } -} diff --git a/plugins/launcher/app/app.hpp b/plugins/launcher/app/app.hpp deleted file mode 100644 index 9f40178e..00000000 --- a/plugins/launcher/app/app.hpp +++ /dev/null @@ -1,112 +0,0 @@ -#pragma once - -#include -#include -#include - -#include "plugins/launcher_plugin.hpp" - -#include -#include -#undef Bool -#undef CurrentTime -#undef CursorShape -#undef Expose -#undef KeyPress -#undef KeyRelease -#undef FocusIn -#undef FocusOut -#undef FontChange -#undef None -#undef Status -#undef Unsorted - -class Arbiter; -class Config; -class Theme; - -class XWorker : public QObject { - Q_OBJECT - - public: - XWorker(QObject *parent = nullptr); - int get_window(uint64_t pid); - - private: - const int MAX_RETRIES = 60; - - struct WindowProp { - WindowProp(char *prop, unsigned long size); - ~WindowProp(); - - void *prop; - unsigned long size; - }; - - WindowProp get_window_prop(Window window, Atom type, const char *name); - - Display *display; - Window root_window; -}; - -class EmbeddedApp : public QWidget { - Q_OBJECT - - public: - EmbeddedApp(QWidget *parent = nullptr); - ~EmbeddedApp(); - - void start(QString app); - void end(); - - private: - QProcess process; - QVBoxLayout *container; - XWorker *worker; - - signals: - void closed(); - void opened(); -}; - -class Launcher : public QWidget { - Q_OBJECT - - public: - Launcher(Arbiter &arbiter, QSettings &settings, int idx, QWidget *parent = nullptr); - ~Launcher(); - void update_idx(int idx); - - private: - const QString DEFAULT_DIR = QDir().absolutePath(); - - QWidget *launcher_widget(); - QWidget *app_select_widget(); - QWidget *config_widget(); - void populate_dirs(QString path); - void populate_apps(QString path); - - inline QString home_key() { return QString("%1/home").arg(this->idx); } - inline QString app_key() { return QString("%1/app").arg(this->idx); } - - Arbiter &arbiter; - QSettings &settings; - EmbeddedApp *app; - QLabel *path_label; - QListWidget *folders; - QListWidget *apps; - int idx; - bool auto_launch = false; - -}; - -class App : public LauncherPlugin { - Q_OBJECT - Q_PLUGIN_METADATA(IID LauncherPlugin_iid FILE "app.json") - Q_INTERFACES(LauncherPlugin) - - public: - App() { this->settings.beginGroup("App"); } - QList widgets() override; - void remove_widget(int idx) override; -}; diff --git a/plugins/launcher/app/app.json b/plugins/launcher/app/app.json deleted file mode 100644 index 1521878d..00000000 --- a/plugins/launcher/app/app.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "name" : "app", - "version" : "1.0" -} diff --git a/plugins/launcher/home/app.cpp b/plugins/launcher/home/app.cpp index a40f9269..bba60708 100644 --- a/plugins/launcher/home/app.cpp +++ b/plugins/launcher/home/app.cpp @@ -28,6 +28,7 @@ XWorker::WindowProp::~WindowProp() } } +//TODO: rename to XInfo. implement worker thread that monitors X windows XWorker::XWorker(QObject *parent) : QObject(parent) { this->display = XOpenDisplay(0); diff --git a/plugins/launcher/home/app.hpp b/plugins/launcher/home/app.hpp index 2e5a017f..2ca64974 100644 --- a/plugins/launcher/home/app.hpp +++ b/plugins/launcher/home/app.hpp @@ -89,13 +89,7 @@ class App : public LauncherPlugin { QList widgets() override; void remove_widget(int idx) override; - void add_widget(QWidget *widget); - -public slots: - - signals: - - // void widget_added() override; + //void add_widget(QWidget *widget); private: diff --git a/src/app/pages/launcher.cpp b/src/app/pages/launcher.cpp index 2d747e83..59b7835c 100644 --- a/src/app/pages/launcher.cpp +++ b/src/app/pages/launcher.cpp @@ -65,6 +65,12 @@ LauncherPlugins::LauncherPlugins(Arbiter &arbiter, QWidget *parent) plugin->dashize(&this->arbiter); for (QWidget *tab : plugin->widgets()) this->addTab(tab, tab->objectName()); + + connect(plugin, &LauncherPlugin::widget_added, [this](QWidget *tab){ + this->addTab(tab, "window"); + qDebug() << "widget added"; + + }); this->active_plugins.append(plugin_loader); this->active_plugins_list->addItem(launcher_plugin); } From 8f6217df17f0f88ce5cd14ef436894d712700cb8 Mon Sep 17 00:00:00 2001 From: Kyle Bader Date: Wed, 25 Aug 2021 19:19:32 -0600 Subject: [PATCH 04/10] refactor launcher --- include/plugins/launcher_plugin.hpp | 4 +- plugins/launcher/home/app.cpp | 193 ++++++++++++---------------- plugins/launcher/home/app.hpp | 85 ++++++------ src/app/pages/launcher.cpp | 10 +- 4 files changed, 128 insertions(+), 164 deletions(-) diff --git a/include/plugins/launcher_plugin.hpp b/include/plugins/launcher_plugin.hpp index 8e77c07e..0a9baeb2 100644 --- a/include/plugins/launcher_plugin.hpp +++ b/include/plugins/launcher_plugin.hpp @@ -10,11 +10,11 @@ class LauncherPlugin : public Plugin { LauncherPlugin() { this->settings.beginGroup("Launcher"); } virtual ~LauncherPlugin() = default; virtual void remove_widget(int idx) { this->loaded_widgets.removeAt(idx); } + virtual void add_widget(QWidget *widget){} public slots: - signals: - + signals: void widget_added(QWidget *widget); protected: diff --git a/plugins/launcher/home/app.cpp b/plugins/launcher/home/app.cpp index bba60708..7d453072 100644 --- a/plugins/launcher/home/app.cpp +++ b/plugins/launcher/home/app.cpp @@ -28,7 +28,8 @@ XWorker::WindowProp::~WindowProp() } } -//TODO: rename to XInfo. implement worker thread that monitors X windows +//REFACTOR: create XInfo Class and implement classes for traversing the x tree, tracking newly opened +//windows XWorker::XWorker(QObject *parent) : QObject(parent) { this->display = XOpenDisplay(0); @@ -130,65 +131,76 @@ void EmbeddedApp::end() emit closed(); } -Launcher::Launcher(Arbiter *arbiter, QSettings &settings, int idx, App *plugin, QWidget *parent) - : QWidget(parent) - , arbiter(arbiter) - , settings(settings) - , idx(idx) -{ - this->plugin = plugin; - this->setObjectName("Home"); +ILauncherPlugin::~ILauncherPlugin() { - QStackedLayout *layout = new QStackedLayout(this); - layout->setContentsMargins(0, 0, 0, 0); + //TODO: delete home and close opened apps - this->app = new EmbeddedApp(this->arbiter, this); - connect(this->app, &EmbeddedApp::opened, [layout]() { layout->setCurrentIndex(1); }); - connect(this->app, &EmbeddedApp::closed, [layout]() { layout->setCurrentIndex(0); }); - - auto launcher_app = this->settings.value(this->app_key()).toString(); - //this->auto_launch = !launcher_app.isEmpty(); +} - layout->addWidget(this->home_widget()); - layout->addWidget(this->app); +void ILauncherPlugin::init(){ + + this->loaded_widgets.push_front(new Home(this->arbiter, this->settings, 0, this)); } -Launcher::~Launcher() +QList ILauncherPlugin::widgets() { - delete this->app; + if(this->loaded_widgets.count() == 0) this->init(); + return this->loaded_widgets; +} + +void ILauncherPlugin::remove_widget(int idx){ + + //TODO: remove apps + } -void Launcher::update_idx(int idx) +void ILauncherPlugin::add_widget(QWidget *widget){ + + this->loaded_widgets.push_back(widget); + emit widget_added(widget); + +} + +Home::Home(Arbiter *arbiter, QSettings &settings, int idx, ILauncherPlugin *plugin, QWidget *parent) + : QWidget(parent) + , arbiter(arbiter) + , settings(settings) + , idx(idx) { - //TODO: reimplement - if (idx == this->idx) - return; + this->plugin = plugin; + this->setup_ui(); + +} + +Home::~Home(){ + + //TODO: remove desktop entries - QString home; - QString app; - if (this->settings.contains(this->home_key())) - home = this->settings.value(this->home_key(), this->DEFAULT_DIR).toString(); - if (this->settings.contains(this->app_key())) - app = this->settings.value(this->app_key()).toString(); - - this->settings.remove(QString::number(this->idx)); - this->idx = idx; - if (!home.isNull()) - this->settings.setValue(this->home_key(), home); - if (!app.isNull()) - this->settings.setValue(this->app_key(), app); } -QWidget *Launcher::home_widget() +void Home::setup_ui() { - auto homePage = new QWidget(this); - auto gridLayout = new QGridLayout(homePage); - gridLayout->setObjectName(QString::fromUtf8("gridLayout")); - gridLayout->setSizeConstraint(QLayout::SetDefaultConstraint); - homePage->setLayout(gridLayout); + this->setObjectName("Home"); + QStackedLayout *layout = new QStackedLayout(this); + layout->setContentsMargins(0, 0, 0, 0); + + container = new QWidget(this); + + entries_grid = new QGridLayout(container); + entries_grid->setObjectName(QString::fromUtf8("entries_grid")); + entries_grid->setSizeConstraint(QLayout::SetDefaultConstraint); + container->setLayout(entries_grid); auto entries = DesktopEntry::get_entries(this->arbiter, this->plugin); + QScrollArea *scroll_area = new QScrollArea(this); + Session::Forge::to_touch_scroller(scroll_area); + scroll_area->setWidgetResizable(true); + scroll_area->setWidget(container); + + layout->addWidget(scroll_area); + + //TODO: detect width and set appropriately int x = 0; int y = 0; @@ -198,13 +210,15 @@ QWidget *Launcher::home_widget() connect(entry, &DesktopEntry::clicked, [this, entry]() { EmbeddedApp *app = new EmbeddedApp(this->arbiter, this); - + app->setObjectName(entry->get_name()); + qDebug() << entry->get_name(); this->plugin->add_widget(app); - + + //TODO: startup routine - do some kind of loading animation(ie zoom out icon?) to notify user of action(loading occasionaly takes a while) app->start(entry->get_exec()); }); - gridLayout->addWidget(entry, y, x, 1, 1); + entries_grid->addWidget(entry, y, x, 1, 1); //set x & y if(x > 6) { @@ -215,63 +229,24 @@ QWidget *Launcher::home_widget() } } - - QScrollArea *scroll_area = new QScrollArea(this); - Session::Forge::to_touch_scroller(scroll_area); - scroll_area->setWidgetResizable(true); - scroll_area->setWidget(homePage); - return scroll_area; - -} - -QWidget *Launcher::config_widget() -{ - //TODO: why do i need this? - QWidget *widget = new QWidget(this); - //QVBoxLayout *layout = new QVBoxLayout(widget); - - return widget; -} - - -QList App::widgets() -{ - if(this->_widgets.count() == 0) this->init(); - - return this->_widgets; -} - -void App::init(){ - - this->_widgets.push_front(new Launcher(this->arbiter, this->settings, 0, this)); -} - -App::App(){ - - this->settings.beginGroup("App"); - + } -void App::add_widget(QWidget *widget){ +void Home::update_idx(int idx){ - this->_widgets.push_back(widget); - emit widget_added(widget); + if (idx == this->idx) + return; } -void App::remove_widget(int idx) +QWidget *Home::config_widget() { - LauncherPlugin::remove_widget(idx); - - this->settings.remove(QString::number(idx)); - for (int i = 0; i < this->loaded_widgets.size(); i++) { - if (Launcher *launcher = qobject_cast(this->loaded_widgets[i])) - launcher->update_idx(i); - } + //*NOT IMPLEMENTED + QWidget *widget = new QWidget(this); + return widget; } -DesktopEntry::DesktopEntry(QString fileLocation, Arbiter *arbiter, App *plugin, QWidget *parent) - +DesktopEntry::DesktopEntry(QString fileLocation, Arbiter *arbiter, ILauncherPlugin *plugin, QWidget *parent) { this->arbiter = arbiter; this->plugin = plugin; @@ -284,6 +259,7 @@ DesktopEntry::DesktopEntry(QString fileLocation, Arbiter *arbiter, App *plugin, return; } + //parse desktop entry QTextStream stream(&inputFile); for (QString line = stream.readLine(); !line.isNull(); @@ -361,7 +337,6 @@ void DesktopEntry::mousePressEvent(QMouseEvent *event){ emit clicked(); } - int DesktopEntry::resolutionFromString(QString string){ if(string.contains("scalable")) return 10000; @@ -392,11 +367,14 @@ QPixmap DesktopEntry::get_pixmap(){ as described by the index.theme file. The subdirectories are allowed to be several levels deep, e.g. the subdirectory "48x48/apps" in the theme "hicolor" would end up at $basedir/icons/hicolor/48x48/apps{icon_}.{png|svg}" + **Note: + This implementation isn't fully spec compliant. It doesn't lookup themes. + The current implementation searches the icon directories for all themes and grabs the highest + resolution it can from the first theme it finds a match in. */ - //return widgets ico if blank if(this->icon_ == "") { - + //TODO: change icons on dark / light change QPixmap pixmap(":/icons/widgets.svg"); QSize size(512, 512); auto icon_mask = pixmap.scaled(size).createMaskFromColor(Qt::transparent); @@ -405,10 +383,10 @@ QPixmap DesktopEntry::get_pixmap(){ QPixmap icon(size); icon.fill(color); icon.setMask(icon_mask); - return icon; } + //check if icon is path if(this->icon_.contains("/") && (this->icon_.contains(".png") || this->icon_.contains(".svg") || this->icon_.contains(".xpm"))){ if(!QFile::exists(this->icon_)){ @@ -420,10 +398,8 @@ QPixmap DesktopEntry::get_pixmap(){ //check in $HOME/.icons auto h_png_path = QDir::homePath() + "/" + this->icon_ + ".png"; if(QFile::exists(h_png_path)) return QPixmap(h_png_path); - auto h_svg_path = QDir::homePath() + "/" + this->icon_ + ".svg"; if(QFile::exists(h_svg_path)) return QPixmap(h_svg_path); - auto h_xpm_path = QDir::homePath() + "/" + this->icon_ + ".xpm"; if(QFile::exists(h_xpm_path)) return QPixmap(h_xpm_path); @@ -432,12 +408,10 @@ QPixmap DesktopEntry::get_pixmap(){ auto xdgArr = xdg.split(":"); for(QString dir_name : xdgArr) { - //drop repeated dirs if(dir_name.endsWith("/")) continue; - qDebug() << "Checking Dir: " << dir_name; - //get themes from dir + //search themes in dir //TODO: prefer certain themes QDirIterator themes( dir_name + "/icons", QDir::Dirs | QDir::NoDotAndDotDot); while ( themes.hasNext() ) { @@ -445,14 +419,12 @@ QPixmap DesktopEntry::get_pixmap(){ QString themeDirPath = themes.next(); QDir themeDir(themeDirPath); QString theme = themeDir.dirName(); - qDebug() << "Checking Theme: " << theme; - - //TODO: get resolution folders and sort by preference(scalable then size) QDirIterator resolutions( themeDirPath, QDir::Dirs | QDir::NoDotAndDotDot); - QStringList resolutionList = {}; - + int highestResolution = 0; QString highestResPath = ""; + + //get highest resolution icon possible while ( resolutions.hasNext() ) { QString resolutionPath = resolutions.next(); @@ -463,7 +435,6 @@ QPixmap DesktopEntry::get_pixmap(){ //skip if higher resolution already available if(highestResolution > iRes) continue; - QDirIterator subdirs( resolutionPath, QDir::Dirs | QDir::NoDotAndDotDot); while ( subdirs.hasNext() ) { @@ -529,7 +500,7 @@ QPixmap DesktopEntry::get_pixmap(){ return icon; } -QList DesktopEntry::get_entries(Arbiter *arbiter, App *plugin) +QList DesktopEntry::get_entries(Arbiter *arbiter, ILauncherPlugin *plugin) { /* Desktop entry files must reside in the $XDG_DATA_DIRS/applications directory and must have a .desktop file extension. diff --git a/plugins/launcher/home/app.hpp b/plugins/launcher/home/app.hpp index 2ca64974..ed6c4a32 100644 --- a/plugins/launcher/home/app.hpp +++ b/plugins/launcher/home/app.hpp @@ -31,6 +31,8 @@ class Arbiter; class Config; class Theme; +class DesktopEntry; +class ILauncherPlugin; class XWorker : public QObject { Q_OBJECT @@ -77,43 +79,66 @@ class EmbeddedApp : public QWidget { void opened(); }; - -//TODO: REFACTOR - change name to ILauncherPlugin -class App : public LauncherPlugin { +class ILauncherPlugin : public LauncherPlugin { Q_OBJECT Q_PLUGIN_METADATA(IID LauncherPlugin_iid FILE "home.json") Q_INTERFACES(LauncherPlugin) public: - App(); + ILauncherPlugin() { this->settings.beginGroup("App"); } + ~ILauncherPlugin(); QList widgets() override; void remove_widget(int idx) override; - - //void add_widget(QWidget *widget); - + void add_widget(QWidget *widget) override; private: void init(); - QList _widgets; + + +}; + +class Home : public QWidget { + Q_OBJECT + + public: + Home(Arbiter *arbiter, QSettings &settings, int idx, ILauncherPlugin *plugin, QWidget *parent = nullptr); + ~Home(); + void update_idx(int idx); + private: + //const QString DEFAULT_DIR = QDir().absolutePath(); + QGridLayout *entries_grid; + QWidget *container; + QScrollArea *scroll_area; + QStackedLayout *layout; + QWidget *config_widget();//not implemented + + Arbiter *arbiter; + QSettings &settings; + ILauncherPlugin *plugin; + int idx; + void setup_ui(); + }; + class DesktopEntry : public QWidget{ Q_OBJECT public: - DesktopEntry(QString fileLocation, Arbiter *arbiter, App *plugin, QWidget *parent = nullptr); - QWidget *get_widget(); - static QList get_entries(Arbiter *arbiter, App *plugin); + DesktopEntry(QString fileLocation, Arbiter *arbiter, ILauncherPlugin *plugin, QWidget *parent = nullptr); + + static QList get_entries(Arbiter *arbiter, ILauncherPlugin *plugin); static int resolutionFromString(QString string); - // static QList get_all_widgets(); + inline QString get_exec() { return this->exec_; }; inline QString get_icon() { return this->icon_; }; inline QString get_name() { return this->name_; }; inline QString get_path() { return this->path_; }; inline QList get_args() {return this->args_;}; QPixmap get_pixmap(); + QWidget *get_widget(); public slots: @@ -130,7 +155,7 @@ public slots: QLabel *nameLabel; Arbiter *arbiter; - App *plugin; + ILauncherPlugin *plugin; QString exec_; QString path_; QString name_; @@ -140,38 +165,4 @@ public slots: void setup_ui(); }; -//TODO: REFACTOR - rename to home -class Launcher : public QWidget { - Q_OBJECT - - public: - Launcher(Arbiter *arbiter, QSettings &settings, int idx, App *plugin, QWidget *parent = nullptr); - ~Launcher(); - void update_idx(int idx); - - private: - const QString DEFAULT_DIR = QDir().absolutePath(); - -// QWidget *launcher_widget(); - // QWidget *homepage_widget(); - QWidget *home_widget(); - // QWidget *app_select_widget(); - QWidget *config_widget(); - // void populate_dirs(QString path); - // void populate_apps(QString path); - - inline QString home_key() { return QString("%1/home").arg(this->idx); } - inline QString app_key() { return QString("%1/app").arg(this->idx); } - - Arbiter *arbiter; - QSettings &settings; - EmbeddedApp *app; - App *plugin; - QLabel *path_label; - // QListWidget *folders; - // QListWidget *apps; - int idx; - //bool auto_launch = false; - -}; diff --git a/src/app/pages/launcher.cpp b/src/app/pages/launcher.cpp index 59b7835c..379dd8f3 100644 --- a/src/app/pages/launcher.cpp +++ b/src/app/pages/launcher.cpp @@ -27,15 +27,14 @@ LauncherPlugins::LauncherPlugins(Arbiter &arbiter, QWidget *parent) if (!key.isNull()) { auto plugin_loader = new QPluginLoader(this); plugin_loader->setFileName(this->plugins[key].absoluteFilePath()); - + qDebug() << this->plugins[key].absoluteFilePath(); if (LauncherPlugin *plugin = qobject_cast(plugin_loader->instance())) { plugin->dashize(&this->arbiter); for (QWidget *tab : plugin->widgets()) this->addTab(tab, tab->objectName()); - connect(plugin, &LauncherPlugin::widget_added, [this](QWidget *tab){ - this->addTab(tab, "window"); + this->addTab(tab, tab->objectName()); qDebug() << "widget added"; }); @@ -45,6 +44,9 @@ LauncherPlugins::LauncherPlugins(Arbiter &arbiter, QWidget *parent) this->config->set_launcher_plugin(key); } else { + auto p = plugin_loader->instance(); + if(p != nullptr) qDebug() << plugin_loader->errorString(); + qDebug() << plugin_loader->errorString(); delete plugin_loader; } } @@ -67,7 +69,7 @@ LauncherPlugins::LauncherPlugins(Arbiter &arbiter, QWidget *parent) this->addTab(tab, tab->objectName()); connect(plugin, &LauncherPlugin::widget_added, [this](QWidget *tab){ - this->addTab(tab, "window"); + this->addTab(tab, tab->objectName()); qDebug() << "widget added"; }); From fa510f9cd6d620bccf6fd6ded096bdff17f1bfb4 Mon Sep 17 00:00:00 2001 From: Kyle Bader Date: Wed, 25 Aug 2021 19:25:14 -0600 Subject: [PATCH 05/10] revert install.sh; remove -j4 from make command --- install.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install.sh b/install.sh index 6786dc08..d8a5c649 100755 --- a/install.sh +++ b/install.sh @@ -395,7 +395,7 @@ else fi echo Running Dash make - make -j4 + make if [[ $? -eq 0 ]]; then echo -e Dash make ok, executable can be found ../bin/dash echo From 9e9d34d5af93f33edf0cbc6a6df7603387ac05d5 Mon Sep 17 00:00:00 2001 From: Kyle Bader Date: Wed, 25 Aug 2021 19:53:27 -0600 Subject: [PATCH 06/10] rename files --- plugins/launcher/home/app.cpp | 537 ---------------------------------- plugins/launcher/home/app.hpp | 168 ----------- 2 files changed, 705 deletions(-) delete mode 100644 plugins/launcher/home/app.cpp delete mode 100644 plugins/launcher/home/app.hpp diff --git a/plugins/launcher/home/app.cpp b/plugins/launcher/home/app.cpp deleted file mode 100644 index 7d453072..00000000 --- a/plugins/launcher/home/app.cpp +++ /dev/null @@ -1,537 +0,0 @@ -#include -#include -#include -#include - -#include "app/config.hpp" -#include "app/arbiter.hpp" -#include "app/session.hpp" -#include "app/pages/launcher.hpp" - -#include "app.hpp" - - -XWorker::WindowProp::WindowProp(char *prop, unsigned long size) -{ - this->size = size; - this->prop = new char[this->size + 1]; - - std::copy(prop, prop + size, (char *)this->prop); - ((char *)this->prop)[size] = '\0'; -} - -XWorker::WindowProp::~WindowProp() -{ - if (this->prop != nullptr) { - delete (char *)this->prop; - this->prop = nullptr; - } -} - -//REFACTOR: create XInfo Class and implement classes for traversing the x tree, tracking newly opened -//windows -XWorker::XWorker(QObject *parent) : QObject(parent) -{ - this->display = XOpenDisplay(0); - this->root_window = DefaultRootWindow(this->display); -} - -int XWorker::get_window(uint64_t pid) -{ - int retries = 0; - while (retries < MAX_RETRIES) { - WindowProp client_list = this->get_window_prop(this->root_window, XA_WINDOW, "_NET_CLIENT_LIST"); - Window *windows = (Window *)client_list.prop; - for (unsigned long i = 0; i < (client_list.size / sizeof(Window)); i++) { - if (pid == *(uint64_t *)this->get_window_prop(windows[i], XA_CARDINAL, "_NET_WM_PID").prop) - return windows[i]; - } - usleep(500000); - retries++; - } - - return -1; -} - -XWorker::WindowProp XWorker::get_window_prop(Window window, Atom type, const char *name) -{ - Atom prop = XInternAtom(this->display, name, false); - - Atom actual_type_return; - int actual_format_return; - unsigned long nitems_return; - unsigned long bytes_after_return; - unsigned char *prop_return; - XGetWindowProperty(this->display, window, prop, 0, 1024, false, type, &actual_type_return, &actual_format_return, - &nitems_return, &bytes_after_return, &prop_return); - - unsigned long size = (actual_format_return / 8) * nitems_return; - if (actual_format_return == 32) size *= sizeof(long) / 4; - - WindowProp window_prop((char *)prop_return, size); - XFree(prop_return); - - return window_prop; -} - -EmbeddedApp::EmbeddedApp(Arbiter *arbiter, QWidget *parent) : QWidget(parent), process() -{ - this->arbiter = arbiter; - this->worker = new XWorker(this); - - this->process.setStandardOutputFile(QProcess::nullDevice()); - this->process.setStandardErrorFile(QProcess::nullDevice()); - connect(&this->process, QOverload::of(&QProcess::finished), - [this](int, QProcess::ExitStatus) { this->end(); }); - - this->container = new QVBoxLayout(this); - this->container->setContentsMargins(0, 0, 0, 0); -} - -EmbeddedApp::~EmbeddedApp() -{ - this->process.kill(); - this->process.waitForFinished(); - - delete this->container; - delete this->worker; -} - -void EmbeddedApp::start(QString app) -{ - qDebug() << "[Home] starting program: " << app; - this->process.setProgram(app); - this->process.start(); - this->process.waitForStarted(); - - QWindow *window = QWindow::fromWinId(worker->get_window(this->process.processId())); - - QWindow *thisWindow = QWindow::fromWinId(this->winId()); - window->setParent(thisWindow); - window->setFlags(Qt::FramelessWindowHint); - usleep(50000); - - if(window->isModal()) qDebug() << "app is modal: " << app; - if(window->isTopLevel()) qDebug() << "app is Top Level: " << app; - if(!window->isExposed()) qDebug() << "window not exposed: " << app; - if(!window->isActive()) qDebug() << "window not active: " << app; - if(window == nullptr) { - qDebug() << "error opening application: " << app; - return; - } - - this->container->addWidget(QWidget::createWindowContainer(window, this)); - emit opened(); -} - -void EmbeddedApp::end() -{ - this->process.terminate(); - delete this->container->takeAt(0); - emit closed(); -} - -ILauncherPlugin::~ILauncherPlugin() { - - //TODO: delete home and close opened apps - -} - -void ILauncherPlugin::init(){ - - this->loaded_widgets.push_front(new Home(this->arbiter, this->settings, 0, this)); - -} - -QList ILauncherPlugin::widgets() -{ - if(this->loaded_widgets.count() == 0) this->init(); - return this->loaded_widgets; -} - -void ILauncherPlugin::remove_widget(int idx){ - - //TODO: remove apps - -} - -void ILauncherPlugin::add_widget(QWidget *widget){ - - this->loaded_widgets.push_back(widget); - emit widget_added(widget); - -} - -Home::Home(Arbiter *arbiter, QSettings &settings, int idx, ILauncherPlugin *plugin, QWidget *parent) - : QWidget(parent) - , arbiter(arbiter) - , settings(settings) - , idx(idx) -{ - this->plugin = plugin; - this->setup_ui(); - -} - -Home::~Home(){ - - //TODO: remove desktop entries - -} - -void Home::setup_ui() -{ - this->setObjectName("Home"); - QStackedLayout *layout = new QStackedLayout(this); - layout->setContentsMargins(0, 0, 0, 0); - - container = new QWidget(this); - - entries_grid = new QGridLayout(container); - entries_grid->setObjectName(QString::fromUtf8("entries_grid")); - entries_grid->setSizeConstraint(QLayout::SetDefaultConstraint); - container->setLayout(entries_grid); - auto entries = DesktopEntry::get_entries(this->arbiter, this->plugin); - - QScrollArea *scroll_area = new QScrollArea(this); - Session::Forge::to_touch_scroller(scroll_area); - scroll_area->setWidgetResizable(true); - scroll_area->setWidget(container); - - layout->addWidget(scroll_area); - - - //TODO: detect width and set appropriately - int x = 0; - int y = 0; - for (int i = 0; i < entries.count(); ++i){ - - DesktopEntry *entry = entries[i]; - connect(entry, &DesktopEntry::clicked, [this, entry]() { - - EmbeddedApp *app = new EmbeddedApp(this->arbiter, this); - app->setObjectName(entry->get_name()); - qDebug() << entry->get_name(); - this->plugin->add_widget(app); - - //TODO: startup routine - do some kind of loading animation(ie zoom out icon?) to notify user of action(loading occasionaly takes a while) - app->start(entry->get_exec()); - - }); - entries_grid->addWidget(entry, y, x, 1, 1); - - //set x & y - if(x > 6) { - x = 0; - y += 1; - } else { - x += 1; - } - - } - -} - -void Home::update_idx(int idx){ - - if (idx == this->idx) - return; - -} - -QWidget *Home::config_widget() -{ - //*NOT IMPLEMENTED - QWidget *widget = new QWidget(this); - return widget; -} - -DesktopEntry::DesktopEntry(QString fileLocation, Arbiter *arbiter, ILauncherPlugin *plugin, QWidget *parent) -{ - this->arbiter = arbiter; - this->plugin = plugin; - - QFile inputFile(fileLocation); - inputFile.open(QIODevice::ReadOnly); - if (!inputFile.isOpen()){ - - qDebug() << "[Home] invalid Desktop Entry " << inputFile.errorString(); - return; - } - - //parse desktop entry - QTextStream stream(&inputFile); - for (QString line = stream.readLine(); - !line.isNull(); - line = stream.readLine()) - { - - if(line.contains("Exec")){ - QStringList vals = line.split( "=" ); - this->exec_ = vals.value(1); - - QStringList args = vals.value(1).split( " " ); - this->exec_ = args.value(0); - args.removeAt(0); - this->args_ = args; - //TODO: remove flags and add to separate field - qDebug() << this->exec_; - } - - if(line.contains("Name") && !line.contains("Generic") && !line.contains("[") && !line.contains("]")){ - QStringList vals = line.split( "=" ); - this->name_ = vals.value(1); - } - - if(line.contains("Icon")){ - QStringList vals = line.split( "=" ); - this->icon_ = vals.value(1); - } - - if(line.contains("[Desktop") && !line.contains("Entry]")) break;//skip extra stuff in Desktop Entry - - }; - - this->setup_ui(); - -} - -void DesktopEntry::setup_ui(){ - - //setup ui - QVBoxLayout *verticalLayout_3; - QHBoxLayout *horizontalLayout; - QLabel *iconLabel; - QLabel *nameLabel; - - verticalLayout_3 = new QVBoxLayout(this); - verticalLayout_3->setObjectName(QString::fromUtf8("verticalLayout_3")); - horizontalLayout = new QHBoxLayout(); - horizontalLayout->setObjectName(QString::fromUtf8("horizontalLayout")); - iconLabel = new QLabel(this); - iconLabel->setMaximumSize(QSize(64, 64)); - iconLabel->setObjectName(QString::fromUtf8("iconLabel")); - iconLabel->setAlignment(Qt::AlignCenter); - //iconLabel->setPixmap(this->get_pixmap()); - iconLabel->setPixmap (this->get_pixmap().scaled(64,64,Qt::KeepAspectRatio)); - iconLabel->show(); - - horizontalLayout->addWidget(iconLabel); - verticalLayout_3->addLayout(horizontalLayout); - - nameLabel = new QLabel(this); - QFontMetrics metrics(nameLabel->font()); - QString elidedText = metrics.elidedText(this->get_name(), Qt::ElideRight, (nameLabel->width() * 2) - 16); - nameLabel->setObjectName(QString::fromUtf8("nameLabel")); - nameLabel->setText(elidedText); - nameLabel->setMaximumSize(QSize(112, 64)); - nameLabel->setAlignment(Qt::AlignCenter); - nameLabel->setWordWrap(true); - - verticalLayout_3->addWidget(nameLabel); - this->setMaximumSize(QSize(128, 128)); - -} - -void DesktopEntry::mousePressEvent(QMouseEvent *event){ - emit clicked(); -} - -int DesktopEntry::resolutionFromString(QString string){ - - if(string.contains("scalable")) return 10000; - if(string.contains("128")) return 128; - if(string.contains("64")) return 64; - if(string.contains("48")) return 48; - if(string.contains("42")) return 42; - if(string.contains("36")) return 36; - if(string.contains("32")) return 32; - if(string.contains("24")) return 24; - if(string.contains("22")) return 22; - if(string.contains("16")) return 16; - return 0;//error -} - -QPixmap DesktopEntry::get_pixmap(){ - - /* The freedesktop.org standard specifies in which order and directories programs should look for icons: - - $HOME/.icons (for backwards compatibility) - $XDG_DATA_DIRS/icons - /usr/share/pixmaps - - from the spec: - - "In the theme directory are also a set of subdirectories containing image files. - Each directory contains icons designed for a certain nominal icon size and scale, - as described by the index.theme file. The subdirectories are allowed to be several levels deep, - e.g. the subdirectory "48x48/apps" in the theme "hicolor" would end up at $basedir/icons/hicolor/48x48/apps{icon_}.{png|svg}" - - **Note: - This implementation isn't fully spec compliant. It doesn't lookup themes. - The current implementation searches the icon directories for all themes and grabs the highest - resolution it can from the first theme it finds a match in. - */ - - if(this->icon_ == "") { - //TODO: change icons on dark / light change - QPixmap pixmap(":/icons/widgets.svg"); - QSize size(512, 512); - auto icon_mask = pixmap.scaled(size).createMaskFromColor(Qt::transparent); - auto color = (this->arbiter->theme().mode == Session::Theme::Light) ? QColor(0, 0, 0) : QColor(255, 255, 255); - color.setAlpha((this->arbiter->theme().mode == Session::Theme::Light) ? 162 : 134); - QPixmap icon(size); - icon.fill(color); - icon.setMask(icon_mask); - return icon; - - } - - //check if icon is path - if(this->icon_.contains("/") && (this->icon_.contains(".png") || this->icon_.contains(".svg") || this->icon_.contains(".xpm"))){ - if(!QFile::exists(this->icon_)){ - return QPixmap(":/icons/widgets.svg"); - } - return QPixmap(this->icon_); - } - - //check in $HOME/.icons - auto h_png_path = QDir::homePath() + "/" + this->icon_ + ".png"; - if(QFile::exists(h_png_path)) return QPixmap(h_png_path); - auto h_svg_path = QDir::homePath() + "/" + this->icon_ + ".svg"; - if(QFile::exists(h_svg_path)) return QPixmap(h_svg_path); - auto h_xpm_path = QDir::homePath() + "/" + this->icon_ + ".xpm"; - if(QFile::exists(h_xpm_path)) return QPixmap(h_xpm_path); - - //get theme dirs from DATA_DIRS - QString xdg = qgetenv("XDG_DATA_DIRS"); - auto xdgArr = xdg.split(":"); - for(QString dir_name : xdgArr) - { - //drop repeated dirs - if(dir_name.endsWith("/")) continue; - - //search themes in dir - //TODO: prefer certain themes - QDirIterator themes( dir_name + "/icons", QDir::Dirs | QDir::NoDotAndDotDot); - while ( themes.hasNext() ) { - - QString themeDirPath = themes.next(); - QDir themeDir(themeDirPath); - QString theme = themeDir.dirName(); - QDirIterator resolutions( themeDirPath, QDir::Dirs | QDir::NoDotAndDotDot); - - int highestResolution = 0; - QString highestResPath = ""; - - //get highest resolution icon possible - while ( resolutions.hasNext() ) { - - QString resolutionPath = resolutions.next(); - QDir resolutionDir(resolutionPath); - QString resolution = resolutionDir.dirName(); - qDebug() << "Getting resolution" << resolution; - int iRes = DesktopEntry::resolutionFromString(resolution); - - //skip if higher resolution already available - if(highestResolution > iRes) continue; - QDirIterator subdirs( resolutionPath, QDir::Dirs | QDir::NoDotAndDotDot); - while ( subdirs.hasNext() ) { - - QDir subDir(subdirs.next()); - QString sub_dir_name = subDir.dirName(); - auto xdg_png_path = dir_name + "/icons/" + theme + "/" + resolution + "/" + sub_dir_name + "/" + this->icon_ + ".png"; - qDebug() << "Searching for " << xdg_png_path; - if(QFile::exists(xdg_png_path)) { - highestResolution = iRes; - highestResPath = xdg_png_path; - - } - auto xdg_svg_path = dir_name + "/icons/" + theme + "/" + resolution + "/" + sub_dir_name + "/" + this->icon_ + ".svg"; - if(QFile::exists(xdg_svg_path)) { - highestResolution = iRes; - highestResPath = xdg_svg_path; - } - - auto xdg_xpm_path = dir_name + "/icons/" + theme + "/" + resolution + "/" + sub_dir_name + "/" + this->icon_ + ".xpm"; - if(QFile::exists(xdg_xpm_path)) { - highestResolution = iRes; - highestResPath = xdg_xpm_path; - } - - } - - } - - if(highestResolution > 0){ - return QPixmap(highestResPath); - } - - } - } - - //check pixmaps - auto pixmap_png_path = "/usr/share/pixmaps/" + this->icon_ + ".png"; - if(QFile::exists(pixmap_png_path)) { - return QPixmap(pixmap_png_path); - } - auto pixmap_svg_path = "/usr/share/pixmaps/" + this->icon_ + ".svg"; - if(QFile::exists(pixmap_svg_path)) { - return QPixmap(pixmap_svg_path); - } - - auto pixmap_xpm_path = "/usr/share/pixmaps/" + this->icon_ + ".xpm"; - if(QFile::exists(pixmap_xpm_path)) { - return QPixmap(pixmap_xpm_path); - } - - //nothing else worked - qDebug() << "no icon found"; - - QPixmap pixmap(":/icons/widgets.svg"); - QSize size(512, 512); - auto icon_mask = pixmap.scaled(size).createMaskFromColor(Qt::transparent); - auto color = (this->arbiter->theme().mode == Session::Theme::Light) ? QColor(0, 0, 0) : QColor(255, 255, 255); - color.setAlpha((this->arbiter->theme().mode == Session::Theme::Light) ? 162 : 134); - QPixmap icon(size); - icon.fill(color); - icon.setMask(icon_mask); - - return icon; -} - -QList DesktopEntry::get_entries(Arbiter *arbiter, ILauncherPlugin *plugin) -{ - /* - Desktop entry files must reside in the $XDG_DATA_DIRS/applications directory and must have a .desktop file extension. - If $XDG_DATA_DIRS is not set, then the default path is /usr/share is used. - If $XDG_DATA_HOME is not set, then the default path ~/.local/share is used. - Desktop entries are collected from all directories in the $XDG_DATA_DIRS environment variable. - Directories which appear first in $XDG_CONFIG_DIRS are given precedence when there are several .desktop files with the same name. - */ - - QList rtn; - QString xdg = qgetenv("XDG_DATA_DIRS"); - for(QString dir_name : xdg.split(":")) - { - - auto dir = dir_name + "/applications"; - QDir source(dir); - if (!source.exists()) - continue; - - QStringList files = source.entryList(QStringList() << "*.desktop", QDir::Files); - for (QString file: files) - { - //TODO: validate & error handle - QString path = source.absoluteFilePath(file); - DesktopEntry *entry = new DesktopEntry(path, arbiter, plugin); - rtn.push_back(entry); - - } - - } - - return rtn; - -} diff --git a/plugins/launcher/home/app.hpp b/plugins/launcher/home/app.hpp deleted file mode 100644 index ed6c4a32..00000000 --- a/plugins/launcher/home/app.hpp +++ /dev/null @@ -1,168 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "plugins/launcher_plugin.hpp" - -#include -#include -#undef Bool -#undef CurrentTime -#undef CursorShape -#undef Expose -#undef KeyPress -#undef KeyRelease -#undef FocusIn -#undef FocusOut -#undef FontChange -#undef None -#undef Status -#undef Unsorted - -class Arbiter; -class Config; -class Theme; -class DesktopEntry; -class ILauncherPlugin; - -class XWorker : public QObject { - Q_OBJECT - - public: - XWorker(QObject *parent = nullptr); - int get_window(uint64_t pid); - - private: - const int MAX_RETRIES = 60; - - struct WindowProp { - WindowProp(char *prop, unsigned long size); - ~WindowProp(); - - void *prop; - unsigned long size; - }; - - WindowProp get_window_prop(Window window, Atom type, const char *name); - - Display *display; - Window root_window; -}; - -class EmbeddedApp : public QWidget { - Q_OBJECT - - public: - EmbeddedApp(Arbiter *arbiter, QWidget *parent = nullptr); - ~EmbeddedApp(); - - void start(QString app); - void end(); - - private: - QProcess process; - QVBoxLayout *container; - XWorker *worker; - Arbiter *arbiter; - - signals: - void closed(); - void opened(); -}; - -class ILauncherPlugin : public LauncherPlugin { - Q_OBJECT - Q_PLUGIN_METADATA(IID LauncherPlugin_iid FILE "home.json") - Q_INTERFACES(LauncherPlugin) - -public: - ILauncherPlugin() { this->settings.beginGroup("App"); } - ~ILauncherPlugin(); - QList widgets() override; - void remove_widget(int idx) override; - void add_widget(QWidget *widget) override; - -private: - void init(); - - -}; - -class Home : public QWidget { - Q_OBJECT - - public: - Home(Arbiter *arbiter, QSettings &settings, int idx, ILauncherPlugin *plugin, QWidget *parent = nullptr); - ~Home(); - void update_idx(int idx); - - private: - //const QString DEFAULT_DIR = QDir().absolutePath(); - QGridLayout *entries_grid; - QWidget *container; - QScrollArea *scroll_area; - QStackedLayout *layout; - QWidget *config_widget();//not implemented - - Arbiter *arbiter; - QSettings &settings; - ILauncherPlugin *plugin; - int idx; - - void setup_ui(); - -}; - - -class DesktopEntry : public QWidget{ - Q_OBJECT - -public: - DesktopEntry(QString fileLocation, Arbiter *arbiter, ILauncherPlugin *plugin, QWidget *parent = nullptr); - - static QList get_entries(Arbiter *arbiter, ILauncherPlugin *plugin); - static int resolutionFromString(QString string); - - inline QString get_exec() { return this->exec_; }; - inline QString get_icon() { return this->icon_; }; - inline QString get_name() { return this->name_; }; - inline QString get_path() { return this->path_; }; - inline QList get_args() {return this->args_;}; - QPixmap get_pixmap(); - QWidget *get_widget(); - -public slots: - -signals: - void clicked(); - -protected: - void mousePressEvent(QMouseEvent *event) override; - -private: - QVBoxLayout *verticalLayout_3; - QHBoxLayout *horizontalLayout; - QLabel *iconLabel; - QLabel *nameLabel; - - Arbiter *arbiter; - ILauncherPlugin *plugin; - QString exec_; - QString path_; - QString name_; - QString icon_; - QList args_; - - void setup_ui(); - -}; - From e661868207a3668c6f5cdd7590b17fe2205171da Mon Sep 17 00:00:00 2001 From: Kyle Bader Date: Wed, 25 Aug 2021 20:11:21 -0600 Subject: [PATCH 07/10] wheres my files? --- plugins/launcher/app/app.cpp | 298 +++++++++++++++++++ plugins/launcher/app/app.hpp | 112 +++++++ plugins/launcher/app/app.json | 4 + plugins/launcher/home/home.cpp | 516 +++++++++++++++++++++++++++++++++ plugins/launcher/home/home.hpp | 168 +++++++++++ src/app/pages/launcher.cpp | 8 +- 6 files changed, 1099 insertions(+), 7 deletions(-) create mode 100644 plugins/launcher/app/app.cpp create mode 100644 plugins/launcher/app/app.hpp create mode 100644 plugins/launcher/app/app.json create mode 100644 plugins/launcher/home/home.cpp create mode 100644 plugins/launcher/home/home.hpp diff --git a/plugins/launcher/app/app.cpp b/plugins/launcher/app/app.cpp new file mode 100644 index 00000000..1b40cf25 --- /dev/null +++ b/plugins/launcher/app/app.cpp @@ -0,0 +1,298 @@ +#include +#include + +#include "app/config.hpp" +#include "app/arbiter.hpp" +#include "app/session.hpp" + +#include "app.hpp" + +XWorker::WindowProp::WindowProp(char *prop, unsigned long size) +{ + this->size = size; + this->prop = new char[this->size + 1]; + + std::copy(prop, prop + size, (char *)this->prop); + ((char *)this->prop)[size] = '\0'; +} + +XWorker::WindowProp::~WindowProp() +{ + if (this->prop != nullptr) { + delete (char *)this->prop; + this->prop = nullptr; + } +} + +XWorker::XWorker(QObject *parent) : QObject(parent) +{ + this->display = XOpenDisplay(nullptr); + this->root_window = DefaultRootWindow(this->display); +} + +int XWorker::get_window(uint64_t pid) +{ + int retries = 0; + while (retries < MAX_RETRIES) { + WindowProp client_list = this->get_window_prop(this->root_window, XA_WINDOW, "_NET_CLIENT_LIST"); + Window *windows = (Window *)client_list.prop; + for (unsigned long i = 0; i < (client_list.size / sizeof(Window)); i++) { + if (pid == *(uint64_t *)this->get_window_prop(windows[i], XA_CARDINAL, "_NET_WM_PID").prop) + return windows[i]; + } + usleep(500000); + retries++; + } + + return -1; +} + +XWorker::WindowProp XWorker::get_window_prop(Window window, Atom type, const char *name) +{ + Atom prop = XInternAtom(this->display, name, false); + + Atom actual_type_return; + int actual_format_return; + unsigned long nitems_return; + unsigned long bytes_after_return; + unsigned char *prop_return; + XGetWindowProperty(this->display, window, prop, 0, 1024, false, type, &actual_type_return, &actual_format_return, + &nitems_return, &bytes_after_return, &prop_return); + + unsigned long size = (actual_format_return / 8) * nitems_return; + if (actual_format_return == 32) size *= sizeof(long) / 4; + + WindowProp window_prop((char *)prop_return, size); + XFree(prop_return); + + return window_prop; +} + +EmbeddedApp::EmbeddedApp(QWidget *parent) : QWidget(parent), process() +{ + this->worker = new XWorker(this); + + this->process.setStandardOutputFile(QProcess::nullDevice()); + this->process.setStandardErrorFile(QProcess::nullDevice()); + connect(&this->process, QOverload::of(&QProcess::finished), + [this](int, QProcess::ExitStatus) { this->end(); }); + + this->container = new QVBoxLayout(this); + this->container->setContentsMargins(0, 0, 0, 0); +} + +EmbeddedApp::~EmbeddedApp() +{ + this->process.kill(); + this->process.waitForFinished(); + + delete this->container; + delete this->worker; +} + +void EmbeddedApp::start(QString app) +{ + this->process.setProgram(app); + this->process.start(); + + this->process.waitForStarted(); + + QWindow *window = QWindow::fromWinId(worker->get_window(this->process.processId())); + window->setFlags(Qt::FramelessWindowHint); + usleep(500000); + + this->container->addWidget(QWidget::createWindowContainer(window, this)); + + emit opened(); +} + +void EmbeddedApp::end() +{ + this->process.terminate(); + delete this->container->takeAt(0); + emit closed(); +} + +Launcher::Launcher(Arbiter &arbiter, QSettings &settings, int idx, QWidget *parent) + : QWidget(parent) + , arbiter(arbiter) + , settings(settings) + , idx(idx) +{ + this->setObjectName("App"); + + QStackedLayout *layout = new QStackedLayout(this); + layout->setContentsMargins(0, 0, 0, 0); + + this->app = new EmbeddedApp(this); + connect(this->app, &EmbeddedApp::opened, [layout]() { layout->setCurrentIndex(1); }); + connect(this->app, &EmbeddedApp::closed, [layout]() { layout->setCurrentIndex(0); }); + + auto launcher_app = this->settings.value(this->app_key()).toString(); + this->auto_launch = !launcher_app.isEmpty(); + + layout->addWidget(this->launcher_widget()); + layout->addWidget(this->app); + + if (this->auto_launch) + //TODO: wait until fully loaded? + this->app->start(launcher_app); +} + +Launcher::~Launcher() +{ + delete this->app; +} + +void Launcher::update_idx(int idx) +{ + if (idx == this->idx) + return; + + QString home; + QString app; + if (this->settings.contains(this->home_key())) + home = this->settings.value(this->home_key(), this->DEFAULT_DIR).toString(); + if (this->settings.contains(this->app_key())) + app = this->settings.value(this->app_key()).toString(); + + this->settings.remove(QString::number(this->idx)); + this->idx = idx; + if (!home.isNull()) + this->settings.setValue(this->home_key(), home); + if (!app.isNull()) + this->settings.setValue(this->app_key(), app); +} + +QWidget *Launcher::launcher_widget() +{ + QWidget *widget = new QWidget(this); + QVBoxLayout *layout = new QVBoxLayout(widget); + + this->path_label = new QLabel(this->settings.value(this->home_key(), this->DEFAULT_DIR).toString(), this); + + layout->addStretch(1); + layout->addWidget(this->path_label, 1); + layout->addWidget(this->app_select_widget(), 6); + layout->addWidget(this->config_widget(), 1, Qt::AlignRight); + layout->addStretch(1); + + return widget; +} + +QWidget *Launcher::app_select_widget() +{ + QWidget *widget = new QWidget(this); + QHBoxLayout *layout = new QHBoxLayout(widget); + + QString root_path(this->path_label->text()); + + QPushButton *home_button = new QPushButton(widget); + home_button->setFlat(true); + home_button->setCheckable(true); + home_button->setChecked(true); + connect(home_button, &QPushButton::clicked, [this](bool checked = false) { + if (checked) + this->settings.setValue(this->home_key(), this->path_label->text()); + else + this->settings.remove(this->home_key()); + }); + this->arbiter.forge().iconize("playlist_add", "playlist_add_check", home_button, 32, true); + layout->addWidget(home_button, 0, Qt::AlignTop); + + this->folders = new QListWidget(widget); + Session::Forge::to_touch_scroller(this->folders); + this->populate_dirs(root_path); + layout->addWidget(this->folders, 4); + + this->apps = new QListWidget(widget); + Session::Forge::to_touch_scroller(this->apps); + this->populate_apps(root_path); + connect(this->apps, &QListWidget::itemClicked, [this](QListWidgetItem *item) { + QString app_path = this->path_label->text() + '/' + item->text(); + if (this->auto_launch) + this->settings.setValue(this->app_key(), app_path); + this->app->start(app_path); + }); + connect(this->folders, &QListWidget::itemClicked, [this, home_button](QListWidgetItem *item) { + if (!item->isSelected()) + return; + + this->apps->clear(); + QString current_path(item->data(Qt::UserRole).toString()); + this->path_label->setText(current_path); + this->populate_apps(current_path); + this->populate_dirs(current_path); + + home_button->setChecked(this->settings.value(this->home_key(), this->DEFAULT_DIR).toString() == current_path); + }); + layout->addWidget(this->apps, 5); + + return widget; +} + +QWidget *Launcher::config_widget() +{ + QWidget *widget = new QWidget(this); + QVBoxLayout *layout = new QVBoxLayout(widget); + + QCheckBox *checkbox = new QCheckBox("launch at startup", widget); + checkbox->setChecked(this->auto_launch); + connect(checkbox, &QCheckBox::toggled, [this, checkbox](bool checked) { + this->auto_launch = checked; + if (this->auto_launch && this->apps->currentItem()) + this->settings.setValue(this->app_key(), this->path_label->text() + '/' + this->apps->currentItem()->text()); + else + this->settings.remove(this->app_key()); + }); + + layout->addWidget(checkbox); + + return widget; +} + +void Launcher::populate_dirs(QString path) +{ + this->folders->clear(); + QDir current_dir(path); + for (QFileInfo dir : current_dir.entryInfoList(QDir::AllDirs | QDir::Readable)) { + if (dir.fileName() == ".") + continue; + + QListWidgetItem *item = new QListWidgetItem(dir.fileName(), this->folders); + if (dir.fileName() == "..") { + item->setText("↲"); + + if (current_dir.isRoot()) + item->setFlags(Qt::NoItemFlags); + } + else { + item->setText(dir.fileName()); + } + item->setData(Qt::UserRole, QVariant(dir.absoluteFilePath())); + } +} + +void Launcher::populate_apps(QString path) +{ + for (QString app : QDir(path).entryList(QDir::Files | QDir::Executable)) + new QListWidgetItem(app, this->apps); +} + +QList App::widgets() +{ + int size = this->loaded_widgets.size(); + this->loaded_widgets.append(new Launcher(*this->arbiter, this->settings, size)); + return this->loaded_widgets.mid(size); +} + +void App::remove_widget(int idx) +{ + LauncherPlugin::remove_widget(idx); + + this->settings.remove(QString::number(idx)); + for (int i = 0; i < this->loaded_widgets.size(); i++) { + if (Launcher *launcher = qobject_cast(this->loaded_widgets[i])) + launcher->update_idx(i); + } +} diff --git a/plugins/launcher/app/app.hpp b/plugins/launcher/app/app.hpp new file mode 100644 index 00000000..9f40178e --- /dev/null +++ b/plugins/launcher/app/app.hpp @@ -0,0 +1,112 @@ +#pragma once + +#include +#include +#include + +#include "plugins/launcher_plugin.hpp" + +#include +#include +#undef Bool +#undef CurrentTime +#undef CursorShape +#undef Expose +#undef KeyPress +#undef KeyRelease +#undef FocusIn +#undef FocusOut +#undef FontChange +#undef None +#undef Status +#undef Unsorted + +class Arbiter; +class Config; +class Theme; + +class XWorker : public QObject { + Q_OBJECT + + public: + XWorker(QObject *parent = nullptr); + int get_window(uint64_t pid); + + private: + const int MAX_RETRIES = 60; + + struct WindowProp { + WindowProp(char *prop, unsigned long size); + ~WindowProp(); + + void *prop; + unsigned long size; + }; + + WindowProp get_window_prop(Window window, Atom type, const char *name); + + Display *display; + Window root_window; +}; + +class EmbeddedApp : public QWidget { + Q_OBJECT + + public: + EmbeddedApp(QWidget *parent = nullptr); + ~EmbeddedApp(); + + void start(QString app); + void end(); + + private: + QProcess process; + QVBoxLayout *container; + XWorker *worker; + + signals: + void closed(); + void opened(); +}; + +class Launcher : public QWidget { + Q_OBJECT + + public: + Launcher(Arbiter &arbiter, QSettings &settings, int idx, QWidget *parent = nullptr); + ~Launcher(); + void update_idx(int idx); + + private: + const QString DEFAULT_DIR = QDir().absolutePath(); + + QWidget *launcher_widget(); + QWidget *app_select_widget(); + QWidget *config_widget(); + void populate_dirs(QString path); + void populate_apps(QString path); + + inline QString home_key() { return QString("%1/home").arg(this->idx); } + inline QString app_key() { return QString("%1/app").arg(this->idx); } + + Arbiter &arbiter; + QSettings &settings; + EmbeddedApp *app; + QLabel *path_label; + QListWidget *folders; + QListWidget *apps; + int idx; + bool auto_launch = false; + +}; + +class App : public LauncherPlugin { + Q_OBJECT + Q_PLUGIN_METADATA(IID LauncherPlugin_iid FILE "app.json") + Q_INTERFACES(LauncherPlugin) + + public: + App() { this->settings.beginGroup("App"); } + QList widgets() override; + void remove_widget(int idx) override; +}; diff --git a/plugins/launcher/app/app.json b/plugins/launcher/app/app.json new file mode 100644 index 00000000..1521878d --- /dev/null +++ b/plugins/launcher/app/app.json @@ -0,0 +1,4 @@ +{ + "name" : "app", + "version" : "1.0" +} diff --git a/plugins/launcher/home/home.cpp b/plugins/launcher/home/home.cpp new file mode 100644 index 00000000..0a15c4ad --- /dev/null +++ b/plugins/launcher/home/home.cpp @@ -0,0 +1,516 @@ +#include +#include +#include +#include + +#include "app/config.hpp" +#include "app/arbiter.hpp" +#include "app/session.hpp" +#include "app/pages/launcher.hpp" + +#include "home.hpp" + + +XWorker::WindowProp::WindowProp(char *prop, unsigned long size) +{ + this->size = size; + this->prop = new char[this->size + 1]; + + std::copy(prop, prop + size, (char *)this->prop); + ((char *)this->prop)[size] = '\0'; +} + +XWorker::WindowProp::~WindowProp() +{ + if (this->prop != nullptr) { + delete (char *)this->prop; + this->prop = nullptr; + } +} + +//REFACTOR: create XInfo Class and implement classes for traversing the x tree, tracking newly opened +//windows +XWorker::XWorker(QObject *parent) : QObject(parent) +{ + this->display = XOpenDisplay(0); + this->root_window = DefaultRootWindow(this->display); +} + +int XWorker::get_window(uint64_t pid) +{ + int retries = 0; + while (retries < MAX_RETRIES) { + WindowProp client_list = this->get_window_prop(this->root_window, XA_WINDOW, "_NET_CLIENT_LIST"); + Window *windows = (Window *)client_list.prop; + for (unsigned long i = 0; i < (client_list.size / sizeof(Window)); i++) { + if (pid == *(uint64_t *)this->get_window_prop(windows[i], XA_CARDINAL, "_NET_WM_PID").prop) + return windows[i]; + } + usleep(500000); + retries++; + } + + return -1; +} + +XWorker::WindowProp XWorker::get_window_prop(Window window, Atom type, const char *name) +{ + Atom prop = XInternAtom(this->display, name, false); + + Atom actual_type_return; + int actual_format_return; + unsigned long nitems_return; + unsigned long bytes_after_return; + unsigned char *prop_return; + XGetWindowProperty(this->display, window, prop, 0, 1024, false, type, &actual_type_return, &actual_format_return, + &nitems_return, &bytes_after_return, &prop_return); + + unsigned long size = (actual_format_return / 8) * nitems_return; + if (actual_format_return == 32) size *= sizeof(long) / 4; + + WindowProp window_prop((char *)prop_return, size); + XFree(prop_return); + + return window_prop; +} + +EmbeddedApp::EmbeddedApp(Arbiter *arbiter, QWidget *parent) : QWidget(parent), process() +{ + this->arbiter = arbiter; + this->worker = new XWorker(this); + + this->process.setStandardOutputFile(QProcess::nullDevice()); + this->process.setStandardErrorFile(QProcess::nullDevice()); + connect(&this->process, QOverload::of(&QProcess::finished), + [this](int, QProcess::ExitStatus) { this->end(); }); + + this->container = new QVBoxLayout(this); + this->container->setContentsMargins(0, 0, 0, 0); +} + +EmbeddedApp::~EmbeddedApp() +{ + this->process.kill(); + this->process.waitForFinished(); + + delete this->container; + delete this->worker; +} + +void EmbeddedApp::start(QString app) +{ + this->process.setProgram(app); + this->process.start(); + this->process.waitForStarted(); + + QWindow *window = QWindow::fromWinId(worker->get_window(this->process.processId())); + + QWindow *thisWindow = QWindow::fromWinId(this->winId()); + window->setParent(thisWindow); + window->setFlags(Qt::FramelessWindowHint); + usleep(50000); + + this->container->addWidget(QWidget::createWindowContainer(window, this)); + emit opened(); +} + +void EmbeddedApp::end() +{ + this->process.terminate(); + delete this->container->takeAt(0); + emit closed(); +} + +ILauncherPlugin::~ILauncherPlugin() { + + //TODO: delete home and close opened apps + +} + +void ILauncherPlugin::init(){ + + this->loaded_widgets.push_front(new Home(this->arbiter, this->settings, 0, this)); + +} + +QList ILauncherPlugin::widgets() +{ + if(this->loaded_widgets.count() == 0) this->init(); + return this->loaded_widgets; +} + +void ILauncherPlugin::remove_widget(int idx){ + + //TODO: remove apps + +} + +void ILauncherPlugin::add_widget(QWidget *widget){ + + this->loaded_widgets.push_back(widget); + emit widget_added(widget); + +} + +Home::Home(Arbiter *arbiter, QSettings &settings, int idx, ILauncherPlugin *plugin, QWidget *parent) + : QWidget(parent) + , arbiter(arbiter) + , settings(settings) + , idx(idx) +{ + this->plugin = plugin; + this->setup_ui(); + +} + +Home::~Home(){ + + //TODO: remove desktop entries + +} + +void Home::setup_ui() +{ + this->setObjectName("Home"); + QStackedLayout *layout = new QStackedLayout(this); + layout->setContentsMargins(0, 0, 0, 0); + + container = new QWidget(this); + + entries_grid = new QGridLayout(container); + entries_grid->setObjectName(QString::fromUtf8("entries_grid")); + entries_grid->setSizeConstraint(QLayout::SetDefaultConstraint); + container->setLayout(entries_grid); + auto entries = DesktopEntry::get_entries(this->arbiter, this->plugin); + + QScrollArea *scroll_area = new QScrollArea(this); + Session::Forge::to_touch_scroller(scroll_area); + scroll_area->setWidgetResizable(true); + scroll_area->setWidget(container); + + layout->addWidget(scroll_area); + + + //TODO: detect width and set appropriately + int x = 0; + int y = 0; + for (int i = 0; i < entries.count(); ++i){ + + DesktopEntry *entry = entries[i]; + connect(entry, &DesktopEntry::clicked, [this, entry]() { + + EmbeddedApp *app = new EmbeddedApp(this->arbiter, this); + app->setObjectName(entry->get_name()); + this->plugin->add_widget(app); + app->start(entry->get_exec()); + + }); + entries_grid->addWidget(entry, y, x, 1, 1); + + //set x & y + if(x > 6) { + x = 0; + y += 1; + } else { + x += 1; + } + + } + +} + +void Home::update_idx(int idx){ + + if (idx == this->idx) + return; + +} + +QWidget *Home::config_widget() +{ + //*NOT IMPLEMENTED + QWidget *widget = new QWidget(this); + return widget; +} + +DesktopEntry::DesktopEntry(QString fileLocation, Arbiter *arbiter, ILauncherPlugin *plugin, QWidget *parent) +{ + this->arbiter = arbiter; + this->plugin = plugin; + + QFile inputFile(fileLocation); + inputFile.open(QIODevice::ReadOnly); + + if (!inputFile.isOpen()) return; + + //parse desktop entry + QTextStream stream(&inputFile); + for (QString line = stream.readLine(); + !line.isNull(); + line = stream.readLine()) + { + + if(line.contains("Exec")){ + QStringList vals = line.split( "=" ); + this->exec_ = vals.value(1); + + QStringList args = vals.value(1).split( " " ); + this->exec_ = args.value(0); + args.removeAt(0); + this->args_ = args; + + } + + if(line.contains("Name") && !line.contains("Generic") && !line.contains("[") && !line.contains("]")){ + QStringList vals = line.split( "=" ); + this->name_ = vals.value(1); + } + + if(line.contains("Icon")){ + QStringList vals = line.split( "=" ); + this->icon_ = vals.value(1); + } + + if(line.contains("[Desktop") && !line.contains("Entry]")) break;//skip extra stuff in Desktop Entry + + }; + + this->setup_ui(); + +} + +void DesktopEntry::setup_ui(){ + + //setup ui + QVBoxLayout *verticalLayout_3; + QHBoxLayout *horizontalLayout; + QLabel *iconLabel; + QLabel *nameLabel; + + verticalLayout_3 = new QVBoxLayout(this); + verticalLayout_3->setObjectName(QString::fromUtf8("verticalLayout_3")); + horizontalLayout = new QHBoxLayout(); + horizontalLayout->setObjectName(QString::fromUtf8("horizontalLayout")); + iconLabel = new QLabel(this); + iconLabel->setMaximumSize(QSize(64, 64)); + iconLabel->setObjectName(QString::fromUtf8("iconLabel")); + iconLabel->setAlignment(Qt::AlignCenter); + //iconLabel->setPixmap(this->get_pixmap()); + iconLabel->setPixmap (this->get_pixmap().scaled(64,64,Qt::KeepAspectRatio)); + iconLabel->show(); + + horizontalLayout->addWidget(iconLabel); + verticalLayout_3->addLayout(horizontalLayout); + + nameLabel = new QLabel(this); + QFontMetrics metrics(nameLabel->font()); + QString elidedText = metrics.elidedText(this->get_name(), Qt::ElideRight, (nameLabel->width() * 2) - 16); + nameLabel->setObjectName(QString::fromUtf8("nameLabel")); + nameLabel->setText(elidedText); + nameLabel->setMaximumSize(QSize(112, 64)); + nameLabel->setAlignment(Qt::AlignCenter); + nameLabel->setWordWrap(true); + + verticalLayout_3->addWidget(nameLabel); + this->setMaximumSize(QSize(128, 128)); + +} + +void DesktopEntry::mousePressEvent(QMouseEvent *event){ + emit clicked(); +} + +int DesktopEntry::resolutionFromString(QString string){ + + if(string.contains("scalable")) return 10000; + if(string.contains("128")) return 128; + if(string.contains("64")) return 64; + if(string.contains("48")) return 48; + if(string.contains("42")) return 42; + if(string.contains("36")) return 36; + if(string.contains("32")) return 32; + if(string.contains("24")) return 24; + if(string.contains("22")) return 22; + if(string.contains("16")) return 16; + return 0;//error +} + +QPixmap DesktopEntry::get_pixmap(){ + + /* The freedesktop.org standard specifies in which order and directories programs should look for icons: + + $HOME/.icons (for backwards compatibility) + $XDG_DATA_DIRS/icons + /usr/share/pixmaps + + from the spec: + + "In the theme directory are also a set of subdirectories containing image files. + Each directory contains icons designed for a certain nominal icon size and scale, + as described by the index.theme file. The subdirectories are allowed to be several levels deep, + e.g. the subdirectory "48x48/apps" in the theme "hicolor" would end up at $basedir/icons/hicolor/48x48/apps{icon_}.{png|svg}" + + **Note: + This implementation isn't fully spec compliant. It doesn't lookup themes. + The current implementation searches the icon directories for all themes and grabs the highest + resolution it can from the first theme it finds a match in. + */ + + if(this->icon_ == "") { + //TODO: change icons on dark / light change + QPixmap pixmap(":/icons/widgets.svg"); + QSize size(512, 512); + auto icon_mask = pixmap.scaled(size).createMaskFromColor(Qt::transparent); + auto color = (this->arbiter->theme().mode == Session::Theme::Light) ? QColor(0, 0, 0) : QColor(255, 255, 255); + color.setAlpha((this->arbiter->theme().mode == Session::Theme::Light) ? 162 : 134); + QPixmap icon(size); + icon.fill(color); + icon.setMask(icon_mask); + return icon; + + } + + //check if icon is path + if(this->icon_.contains("/") && (this->icon_.contains(".png") || this->icon_.contains(".svg") || this->icon_.contains(".xpm"))){ + if(!QFile::exists(this->icon_)){ + return QPixmap(":/icons/widgets.svg"); + } + return QPixmap(this->icon_); + } + + //check in $HOME/.icons + auto h_png_path = QDir::homePath() + "/" + this->icon_ + ".png"; + if(QFile::exists(h_png_path)) return QPixmap(h_png_path); + auto h_svg_path = QDir::homePath() + "/" + this->icon_ + ".svg"; + if(QFile::exists(h_svg_path)) return QPixmap(h_svg_path); + auto h_xpm_path = QDir::homePath() + "/" + this->icon_ + ".xpm"; + if(QFile::exists(h_xpm_path)) return QPixmap(h_xpm_path); + + //get theme dirs from DATA_DIRS + QString xdg = qgetenv("XDG_DATA_DIRS"); + auto xdgArr = xdg.split(":"); + for(QString dir_name : xdgArr) + { + //drop repeated dirs + if(dir_name.endsWith("/")) continue; + + //search themes in dir + //TODO: prefer certain themes + QDirIterator themes( dir_name + "/icons", QDir::Dirs | QDir::NoDotAndDotDot); + while ( themes.hasNext() ) { + + QString themeDirPath = themes.next(); + QDir themeDir(themeDirPath); + QString theme = themeDir.dirName(); + QDirIterator resolutions( themeDirPath, QDir::Dirs | QDir::NoDotAndDotDot); + + int highestResolution = 0; + QString highestResPath = ""; + + //get highest resolution icon possible + while ( resolutions.hasNext() ) { + + QString resolutionPath = resolutions.next(); + QDir resolutionDir(resolutionPath); + QString resolution = resolutionDir.dirName(); + int iRes = DesktopEntry::resolutionFromString(resolution); + + //skip if higher resolution already available + if(highestResolution > iRes) continue; + QDirIterator subdirs( resolutionPath, QDir::Dirs | QDir::NoDotAndDotDot); + while ( subdirs.hasNext() ) { + + QDir subDir(subdirs.next()); + QString sub_dir_name = subDir.dirName(); + auto xdg_png_path = dir_name + "/icons/" + theme + "/" + resolution + "/" + sub_dir_name + "/" + this->icon_ + ".png"; + if(QFile::exists(xdg_png_path)) { + highestResolution = iRes; + highestResPath = xdg_png_path; + + } + auto xdg_svg_path = dir_name + "/icons/" + theme + "/" + resolution + "/" + sub_dir_name + "/" + this->icon_ + ".svg"; + if(QFile::exists(xdg_svg_path)) { + highestResolution = iRes; + highestResPath = xdg_svg_path; + } + + auto xdg_xpm_path = dir_name + "/icons/" + theme + "/" + resolution + "/" + sub_dir_name + "/" + this->icon_ + ".xpm"; + if(QFile::exists(xdg_xpm_path)) { + highestResolution = iRes; + highestResPath = xdg_xpm_path; + } + + } + + } + + if(highestResolution > 0){ + return QPixmap(highestResPath); + } + + } + } + + //check pixmaps + auto pixmap_png_path = "/usr/share/pixmaps/" + this->icon_ + ".png"; + if(QFile::exists(pixmap_png_path)) { + return QPixmap(pixmap_png_path); + } + auto pixmap_svg_path = "/usr/share/pixmaps/" + this->icon_ + ".svg"; + if(QFile::exists(pixmap_svg_path)) { + return QPixmap(pixmap_svg_path); + } + + auto pixmap_xpm_path = "/usr/share/pixmaps/" + this->icon_ + ".xpm"; + if(QFile::exists(pixmap_xpm_path)) { + return QPixmap(pixmap_xpm_path); + } + + //no icon found, return default + QPixmap pixmap(":/icons/widgets.svg"); + QSize size(512, 512); + auto icon_mask = pixmap.scaled(size).createMaskFromColor(Qt::transparent); + auto color = (this->arbiter->theme().mode == Session::Theme::Light) ? QColor(0, 0, 0) : QColor(255, 255, 255); + color.setAlpha((this->arbiter->theme().mode == Session::Theme::Light) ? 162 : 134); + QPixmap icon(size); + icon.fill(color); + icon.setMask(icon_mask); + + return icon; +} + +QList DesktopEntry::get_entries(Arbiter *arbiter, ILauncherPlugin *plugin) +{ + /* + Desktop entry files must reside in the $XDG_DATA_DIRS/applications directory and must have a .desktop file extension. + If $XDG_DATA_DIRS is not set, then the default path is /usr/share is used. + If $XDG_DATA_HOME is not set, then the default path ~/.local/share is used. + Desktop entries are collected from all directories in the $XDG_DATA_DIRS environment variable. + Directories which appear first in $XDG_CONFIG_DIRS are given precedence when there are several .desktop files with the same name. + */ + + QList rtn; + QString xdg = qgetenv("XDG_DATA_DIRS"); + for(QString dir_name : xdg.split(":")) + { + + auto dir = dir_name + "/applications"; + QDir source(dir); + if (!source.exists()) + continue; + + QStringList files = source.entryList(QStringList() << "*.desktop", QDir::Files); + for (QString file: files) + { + //TODO: validate & error handle + QString path = source.absoluteFilePath(file); + DesktopEntry *entry = new DesktopEntry(path, arbiter, plugin); + rtn.push_back(entry); + + } + + } + + return rtn; + +} diff --git a/plugins/launcher/home/home.hpp b/plugins/launcher/home/home.hpp new file mode 100644 index 00000000..ed6c4a32 --- /dev/null +++ b/plugins/launcher/home/home.hpp @@ -0,0 +1,168 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "plugins/launcher_plugin.hpp" + +#include +#include +#undef Bool +#undef CurrentTime +#undef CursorShape +#undef Expose +#undef KeyPress +#undef KeyRelease +#undef FocusIn +#undef FocusOut +#undef FontChange +#undef None +#undef Status +#undef Unsorted + +class Arbiter; +class Config; +class Theme; +class DesktopEntry; +class ILauncherPlugin; + +class XWorker : public QObject { + Q_OBJECT + + public: + XWorker(QObject *parent = nullptr); + int get_window(uint64_t pid); + + private: + const int MAX_RETRIES = 60; + + struct WindowProp { + WindowProp(char *prop, unsigned long size); + ~WindowProp(); + + void *prop; + unsigned long size; + }; + + WindowProp get_window_prop(Window window, Atom type, const char *name); + + Display *display; + Window root_window; +}; + +class EmbeddedApp : public QWidget { + Q_OBJECT + + public: + EmbeddedApp(Arbiter *arbiter, QWidget *parent = nullptr); + ~EmbeddedApp(); + + void start(QString app); + void end(); + + private: + QProcess process; + QVBoxLayout *container; + XWorker *worker; + Arbiter *arbiter; + + signals: + void closed(); + void opened(); +}; + +class ILauncherPlugin : public LauncherPlugin { + Q_OBJECT + Q_PLUGIN_METADATA(IID LauncherPlugin_iid FILE "home.json") + Q_INTERFACES(LauncherPlugin) + +public: + ILauncherPlugin() { this->settings.beginGroup("App"); } + ~ILauncherPlugin(); + QList widgets() override; + void remove_widget(int idx) override; + void add_widget(QWidget *widget) override; + +private: + void init(); + + +}; + +class Home : public QWidget { + Q_OBJECT + + public: + Home(Arbiter *arbiter, QSettings &settings, int idx, ILauncherPlugin *plugin, QWidget *parent = nullptr); + ~Home(); + void update_idx(int idx); + + private: + //const QString DEFAULT_DIR = QDir().absolutePath(); + QGridLayout *entries_grid; + QWidget *container; + QScrollArea *scroll_area; + QStackedLayout *layout; + QWidget *config_widget();//not implemented + + Arbiter *arbiter; + QSettings &settings; + ILauncherPlugin *plugin; + int idx; + + void setup_ui(); + +}; + + +class DesktopEntry : public QWidget{ + Q_OBJECT + +public: + DesktopEntry(QString fileLocation, Arbiter *arbiter, ILauncherPlugin *plugin, QWidget *parent = nullptr); + + static QList get_entries(Arbiter *arbiter, ILauncherPlugin *plugin); + static int resolutionFromString(QString string); + + inline QString get_exec() { return this->exec_; }; + inline QString get_icon() { return this->icon_; }; + inline QString get_name() { return this->name_; }; + inline QString get_path() { return this->path_; }; + inline QList get_args() {return this->args_;}; + QPixmap get_pixmap(); + QWidget *get_widget(); + +public slots: + +signals: + void clicked(); + +protected: + void mousePressEvent(QMouseEvent *event) override; + +private: + QVBoxLayout *verticalLayout_3; + QHBoxLayout *horizontalLayout; + QLabel *iconLabel; + QLabel *nameLabel; + + Arbiter *arbiter; + ILauncherPlugin *plugin; + QString exec_; + QString path_; + QString name_; + QString icon_; + QList args_; + + void setup_ui(); + +}; + diff --git a/src/app/pages/launcher.cpp b/src/app/pages/launcher.cpp index 379dd8f3..14e5f772 100644 --- a/src/app/pages/launcher.cpp +++ b/src/app/pages/launcher.cpp @@ -27,7 +27,6 @@ LauncherPlugins::LauncherPlugins(Arbiter &arbiter, QWidget *parent) if (!key.isNull()) { auto plugin_loader = new QPluginLoader(this); plugin_loader->setFileName(this->plugins[key].absoluteFilePath()); - qDebug() << this->plugins[key].absoluteFilePath(); if (LauncherPlugin *plugin = qobject_cast(plugin_loader->instance())) { plugin->dashize(&this->arbiter); for (QWidget *tab : plugin->widgets()) @@ -35,8 +34,6 @@ LauncherPlugins::LauncherPlugins(Arbiter &arbiter, QWidget *parent) connect(plugin, &LauncherPlugin::widget_added, [this](QWidget *tab){ this->addTab(tab, tab->objectName()); - qDebug() << "widget added"; - }); this->active_plugins.append(plugin_loader); @@ -44,9 +41,7 @@ LauncherPlugins::LauncherPlugins(Arbiter &arbiter, QWidget *parent) this->config->set_launcher_plugin(key); } else { - auto p = plugin_loader->instance(); - if(p != nullptr) qDebug() << plugin_loader->errorString(); - qDebug() << plugin_loader->errorString(); + if(!plugin_loader->isLoaded()) qDebug() << plugin_loader->errorString(); delete plugin_loader; } } @@ -70,7 +65,6 @@ LauncherPlugins::LauncherPlugins(Arbiter &arbiter, QWidget *parent) connect(plugin, &LauncherPlugin::widget_added, [this](QWidget *tab){ this->addTab(tab, tab->objectName()); - qDebug() << "widget added"; }); this->active_plugins.append(plugin_loader); From aa88e3d2516450435dde4b64dc1d47bdff30dc08 Mon Sep 17 00:00:00 2001 From: Kyle Bader Date: Wed, 25 Aug 2021 21:53:02 -0600 Subject: [PATCH 08/10] a little more refactoring plus multicore make in install script --- install.sh | 9 +++++-- plugins/launcher/app/app.cpp | 3 +-- plugins/launcher/home/home.cpp | 44 ++++++---------------------------- plugins/launcher/home/home.hpp | 10 +++----- 4 files changed, 18 insertions(+), 48 deletions(-) diff --git a/install.sh b/install.sh index d8a5c649..de8d48b0 100755 --- a/install.sh +++ b/install.sh @@ -394,8 +394,13 @@ else exit 1 fi - echo Running Dash make - make + + coreQ=$(grep 'cpu cores' /proc/cpuinfo | uniq) + cores=${coreQ:12:1} + echo Running Dash make -j$cores + make -j$cores + + if [[ $? -eq 0 ]]; then echo -e Dash make ok, executable can be found ../bin/dash echo diff --git a/plugins/launcher/app/app.cpp b/plugins/launcher/app/app.cpp index 1b40cf25..291fa60f 100644 --- a/plugins/launcher/app/app.cpp +++ b/plugins/launcher/app/app.cpp @@ -134,8 +134,7 @@ Launcher::Launcher(Arbiter &arbiter, QSettings &settings, int idx, QWidget *pare layout->addWidget(this->launcher_widget()); layout->addWidget(this->app); - if (this->auto_launch) - //TODO: wait until fully loaded? + if (this->auto_launch) this->app->start(launcher_app); } diff --git a/plugins/launcher/home/home.cpp b/plugins/launcher/home/home.cpp index 0a15c4ad..723bea26 100644 --- a/plugins/launcher/home/home.cpp +++ b/plugins/launcher/home/home.cpp @@ -10,7 +10,6 @@ #include "home.hpp" - XWorker::WindowProp::WindowProp(char *prop, unsigned long size) { this->size = size; @@ -121,12 +120,6 @@ void EmbeddedApp::end() emit closed(); } -ILauncherPlugin::~ILauncherPlugin() { - - //TODO: delete home and close opened apps - -} - void ILauncherPlugin::init(){ this->loaded_widgets.push_front(new Home(this->arbiter, this->settings, 0, this)); @@ -163,34 +156,23 @@ Home::Home(Arbiter *arbiter, QSettings &settings, int idx, ILauncherPlugin *plug } -Home::~Home(){ - - //TODO: remove desktop entries - -} - void Home::setup_ui() { this->setObjectName("Home"); QStackedLayout *layout = new QStackedLayout(this); layout->setContentsMargins(0, 0, 0, 0); - container = new QWidget(this); - entries_grid = new QGridLayout(container); entries_grid->setObjectName(QString::fromUtf8("entries_grid")); entries_grid->setSizeConstraint(QLayout::SetDefaultConstraint); container->setLayout(entries_grid); auto entries = DesktopEntry::get_entries(this->arbiter, this->plugin); - QScrollArea *scroll_area = new QScrollArea(this); Session::Forge::to_touch_scroller(scroll_area); scroll_area->setWidgetResizable(true); scroll_area->setWidget(container); - layout->addWidget(scroll_area); - //TODO: detect width and set appropriately int x = 0; int y = 0; @@ -206,7 +188,6 @@ void Home::setup_ui() }); entries_grid->addWidget(entry, y, x, 1, 1); - //set x & y if(x > 6) { x = 0; @@ -228,7 +209,7 @@ void Home::update_idx(int idx){ QWidget *Home::config_widget() { - //*NOT IMPLEMENTED + //NOT IMPLEMENTED QWidget *widget = new QWidget(this); return widget; } @@ -241,8 +222,7 @@ DesktopEntry::DesktopEntry(QString fileLocation, Arbiter *arbiter, ILauncherPlug QFile inputFile(fileLocation); inputFile.open(QIODevice::ReadOnly); - if (!inputFile.isOpen()) return; - + if (!inputFile.isOpen()) return; //parse desktop entry QTextStream stream(&inputFile); for (QString line = stream.readLine(); @@ -280,38 +260,28 @@ DesktopEntry::DesktopEntry(QString fileLocation, Arbiter *arbiter, ILauncherPlug } void DesktopEntry::setup_ui(){ - //setup ui - QVBoxLayout *verticalLayout_3; - QHBoxLayout *horizontalLayout; - QLabel *iconLabel; - QLabel *nameLabel; - - verticalLayout_3 = new QVBoxLayout(this); - verticalLayout_3->setObjectName(QString::fromUtf8("verticalLayout_3")); + verticalLayout = new QVBoxLayout(this); + verticalLayout->setObjectName(QString::fromUtf8("verticalLayout")); horizontalLayout = new QHBoxLayout(); horizontalLayout->setObjectName(QString::fromUtf8("horizontalLayout")); iconLabel = new QLabel(this); iconLabel->setMaximumSize(QSize(64, 64)); iconLabel->setObjectName(QString::fromUtf8("iconLabel")); iconLabel->setAlignment(Qt::AlignCenter); - //iconLabel->setPixmap(this->get_pixmap()); iconLabel->setPixmap (this->get_pixmap().scaled(64,64,Qt::KeepAspectRatio)); iconLabel->show(); - horizontalLayout->addWidget(iconLabel); - verticalLayout_3->addLayout(horizontalLayout); - + verticalLayout->addLayout(horizontalLayout); nameLabel = new QLabel(this); QFontMetrics metrics(nameLabel->font()); QString elidedText = metrics.elidedText(this->get_name(), Qt::ElideRight, (nameLabel->width() * 2) - 16); nameLabel->setObjectName(QString::fromUtf8("nameLabel")); nameLabel->setText(elidedText); - nameLabel->setMaximumSize(QSize(112, 64)); + nameLabel->setMaximumSize(QSize(124, 64)); nameLabel->setAlignment(Qt::AlignCenter); nameLabel->setWordWrap(true); - - verticalLayout_3->addWidget(nameLabel); + verticalLayout->addWidget(nameLabel); this->setMaximumSize(QSize(128, 128)); } diff --git a/plugins/launcher/home/home.hpp b/plugins/launcher/home/home.hpp index ed6c4a32..f61351af 100644 --- a/plugins/launcher/home/home.hpp +++ b/plugins/launcher/home/home.hpp @@ -86,15 +86,13 @@ class ILauncherPlugin : public LauncherPlugin { public: ILauncherPlugin() { this->settings.beginGroup("App"); } - ~ILauncherPlugin(); QList widgets() override; void remove_widget(int idx) override; void add_widget(QWidget *widget) override; private: void init(); - - + }; class Home : public QWidget { @@ -102,7 +100,6 @@ class Home : public QWidget { public: Home(Arbiter *arbiter, QSettings &settings, int idx, ILauncherPlugin *plugin, QWidget *parent = nullptr); - ~Home(); void update_idx(int idx); private: @@ -130,8 +127,7 @@ class DesktopEntry : public QWidget{ DesktopEntry(QString fileLocation, Arbiter *arbiter, ILauncherPlugin *plugin, QWidget *parent = nullptr); static QList get_entries(Arbiter *arbiter, ILauncherPlugin *plugin); - static int resolutionFromString(QString string); - + static int resolutionFromString(QString string); inline QString get_exec() { return this->exec_; }; inline QString get_icon() { return this->icon_; }; inline QString get_name() { return this->name_; }; @@ -149,7 +145,7 @@ public slots: void mousePressEvent(QMouseEvent *event) override; private: - QVBoxLayout *verticalLayout_3; + QVBoxLayout *verticalLayout; QHBoxLayout *horizontalLayout; QLabel *iconLabel; QLabel *nameLabel; From afcd8d03acc4932ab3fd178294d995c733b0365e Mon Sep 17 00:00:00 2001 From: Kyle Bader Date: Thu, 26 Aug 2021 06:33:40 -0600 Subject: [PATCH 09/10] more refactorng --- plugins/launcher/app/app.cpp | 6 ++++++ plugins/launcher/home/home.cpp | 4 ++-- plugins/launcher/home/home.hpp | 1 - 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/plugins/launcher/app/app.cpp b/plugins/launcher/app/app.cpp index 291fa60f..33b8933b 100644 --- a/plugins/launcher/app/app.cpp +++ b/plugins/launcher/app/app.cpp @@ -1,5 +1,6 @@ #include #include +#include #include "app/config.hpp" #include "app/arbiter.hpp" @@ -291,7 +292,12 @@ void App::remove_widget(int idx) this->settings.remove(QString::number(idx)); for (int i = 0; i < this->loaded_widgets.size(); i++) { + auto typeStr(typeid(this->loaded_widgets[i]).name()); + qDebug() << typeStr; + if (Launcher *launcher = qobject_cast(this->loaded_widgets[i])) launcher->update_idx(i); + + } } diff --git a/plugins/launcher/home/home.cpp b/plugins/launcher/home/home.cpp index 723bea26..d3b1be29 100644 --- a/plugins/launcher/home/home.cpp +++ b/plugins/launcher/home/home.cpp @@ -163,8 +163,6 @@ void Home::setup_ui() layout->setContentsMargins(0, 0, 0, 0); container = new QWidget(this); entries_grid = new QGridLayout(container); - entries_grid->setObjectName(QString::fromUtf8("entries_grid")); - entries_grid->setSizeConstraint(QLayout::SetDefaultConstraint); container->setLayout(entries_grid); auto entries = DesktopEntry::get_entries(this->arbiter, this->plugin); QScrollArea *scroll_area = new QScrollArea(this); @@ -205,6 +203,8 @@ void Home::update_idx(int idx){ if (idx == this->idx) return; + this->idx = idx; + } QWidget *Home::config_widget() diff --git a/plugins/launcher/home/home.hpp b/plugins/launcher/home/home.hpp index f61351af..26f4596b 100644 --- a/plugins/launcher/home/home.hpp +++ b/plugins/launcher/home/home.hpp @@ -47,7 +47,6 @@ class XWorker : public QObject { struct WindowProp { WindowProp(char *prop, unsigned long size); ~WindowProp(); - void *prop; unsigned long size; }; From 6826ad99811fca6b0e585cd4e7c47b2c681223c4 Mon Sep 17 00:00:00 2001 From: Kyle Bader Date: Fri, 27 Aug 2021 06:39:57 -0600 Subject: [PATCH 10/10] window manager --- plugins/launcher/home/home.cpp | 20 + plugins/launcher/home/home.hpp | 8 +- plugins/launcher/home/util.cpp | 366 +++++++++++++++++ plugins/launcher/home/util.hpp | 241 +++++++++++ plugins/launcher/home/window_manager.cpp | 483 +++++++++++++++++++++++ plugins/launcher/home/window_manager.hpp | 116 ++++++ 6 files changed, 1233 insertions(+), 1 deletion(-) create mode 100644 plugins/launcher/home/util.cpp create mode 100644 plugins/launcher/home/util.hpp create mode 100644 plugins/launcher/home/window_manager.cpp create mode 100644 plugins/launcher/home/window_manager.hpp diff --git a/plugins/launcher/home/home.cpp b/plugins/launcher/home/home.cpp index d3b1be29..c25d7c02 100644 --- a/plugins/launcher/home/home.cpp +++ b/plugins/launcher/home/home.cpp @@ -120,6 +120,26 @@ void EmbeddedApp::end() emit closed(); } +ILauncherPlugin::ILauncherPlugin(){ + this->settings.beginGroup("Home"); + + window_manager = WindowManager::Create(); + if (!window_manager) { + qDebug() << "[ERROR] could not load Window Manager!"; + //TODO: remove self(plugin) + } else { + window_manager->Run(); + if(window_manager->isRunning) + qDebug() << "Window manager started"; + else + qDebug() << "[INFO] a window manager is already running, unable to take control"; + qDebug() << "[TIP] start dash in a new session without a window manager to take control"; + //TODO: connect slots / signals + + } + +} + void ILauncherPlugin::init(){ this->loaded_widgets.push_front(new Home(this->arbiter, this->settings, 0, this)); diff --git a/plugins/launcher/home/home.hpp b/plugins/launcher/home/home.hpp index 26f4596b..78c23615 100644 --- a/plugins/launcher/home/home.hpp +++ b/plugins/launcher/home/home.hpp @@ -1,5 +1,6 @@ #pragma once +#include #include #include #include @@ -13,6 +14,10 @@ #include "plugins/launcher_plugin.hpp" +#include "util.hpp" +#include "window_manager.hpp" +#include + #include #include #undef Bool @@ -84,13 +89,14 @@ class ILauncherPlugin : public LauncherPlugin { Q_INTERFACES(LauncherPlugin) public: - ILauncherPlugin() { this->settings.beginGroup("App"); } + ILauncherPlugin(); QList widgets() override; void remove_widget(int idx) override; void add_widget(QWidget *widget) override; private: void init(); + WindowManager *window_manager; }; diff --git a/plugins/launcher/home/util.cpp b/plugins/launcher/home/util.cpp new file mode 100644 index 00000000..512e5ab6 --- /dev/null +++ b/plugins/launcher/home/util.cpp @@ -0,0 +1,366 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (C) 2013-2017 Chuan Ji * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); * + * you may not use this file except in compliance with the License. * + * You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include "util.hpp" +#include +#include +#include + +using ::std::string; +using ::std::vector; +using ::std::pair; +using ::std::ostringstream; + +string ToString(const XEvent& e) { + static const char* const X_EVENT_TYPE_NAMES[] = { + "", + "", + "KeyPress", + "KeyRelease", + "ButtonPress", + "ButtonRelease", + "MotionNotify", + "EnterNotify", + "LeaveNotify", + "FocusIn", + "FocusOut", + "KeymapNotify", + "Expose", + "GraphicsExpose", + "NoExpose", + "VisibilityNotify", + "CreateNotify", + "DestroyNotify", + "UnmapNotify", + "MapNotify", + "MapRequest", + "ReparentNotify", + "ConfigureNotify", + "ConfigureRequest", + "GravityNotify", + "ResizeRequest", + "CirculateNotify", + "CirculateRequest", + "PropertyNotify", + "SelectionClear", + "SelectionRequest", + "SelectionNotify", + "ColormapNotify", + "ClientMessage", + "MappingNotify", + "GeneralEvent", + }; + + if (e.type < 2 || e.type >= LASTEvent) { + ostringstream out; + out << "Unknown (" << e.type << ")"; + return out.str(); + } + + // 1. Compile properties we care about. + vector> properties; + switch (e.type) { + case CreateNotify: + properties.emplace_back( + "window", ToString(e.xcreatewindow.window)); + properties.emplace_back( + "parent", ToString(e.xcreatewindow.parent)); + properties.emplace_back( + "size", + Size(e.xcreatewindow.width, e.xcreatewindow.height).ToString()); + properties.emplace_back( + "position", + Position(e.xcreatewindow.x, e.xcreatewindow.y).ToString()); + properties.emplace_back( + "border_width", + ToString(e.xcreatewindow.border_width)); + properties.emplace_back( + "override_redirect", + ToString(static_cast(e.xcreatewindow.override_redirect))); + break; + case DestroyNotify: + properties.emplace_back( + "window", ToString(e.xdestroywindow.window)); + break; + case MapNotify: + properties.emplace_back( + "window", ToString(e.xmap.window)); + properties.emplace_back( + "event", ToString(e.xmap.event)); + properties.emplace_back( + "override_redirect", + ToString(static_cast(e.xmap.override_redirect))); + break; + case UnmapNotify: + properties.emplace_back( + "window", ToString(e.xunmap.window)); + properties.emplace_back( + "event", ToString(e.xunmap.event)); + properties.emplace_back( + "from_configure", + ToString(static_cast(e.xunmap.from_configure))); + break; + case ConfigureNotify: + properties.emplace_back( + "window", ToString(e.xconfigure.window)); + properties.emplace_back( + "size", + Size(e.xconfigure.width, e.xconfigure.height).ToString()); + properties.emplace_back( + "position", + Position(e.xconfigure.x, e.xconfigure.y).ToString()); + properties.emplace_back( + "border_width", + ToString(e.xconfigure.border_width)); + properties.emplace_back( + "override_redirect", + ToString(static_cast(e.xconfigure.override_redirect))); + break; + case ReparentNotify: + properties.emplace_back( + "window", ToString(e.xreparent.window)); + properties.emplace_back( + "parent", ToString(e.xreparent.parent)); + properties.emplace_back( + "position", + Position(e.xreparent.x, e.xreparent.y).ToString()); + properties.emplace_back( + "override_redirect", + ToString(static_cast(e.xreparent.override_redirect))); + break; + case MapRequest: + properties.emplace_back( + "window", ToString(e.xmaprequest.window)); + break; + case ConfigureRequest: + properties.emplace_back( + "window", ToString(e.xconfigurerequest.window)); + properties.emplace_back( + "parent", ToString(e.xconfigurerequest.parent)); + properties.emplace_back( + "value_mask", + XConfigureWindowValueMaskToString(e.xconfigurerequest.value_mask)); + properties.emplace_back( + "position", + Position(e.xconfigurerequest.x, + e.xconfigurerequest.y).ToString()); + properties.emplace_back( + "size", + Size(e.xconfigurerequest.width, + e.xconfigurerequest.height).ToString()); + properties.emplace_back( + "border_width", + ToString(e.xconfigurerequest.border_width)); + break; + case ButtonPress: + case ButtonRelease: + properties.emplace_back( + "window", ToString(e.xbutton.window)); + properties.emplace_back( + "button", ToString(e.xbutton.button)); + properties.emplace_back( + "position_root", + Position(e.xbutton.x_root, e.xbutton.y_root).ToString()); + break; + case MotionNotify: + properties.emplace_back( + "window", ToString(e.xmotion.window)); + properties.emplace_back( + "position_root", + Position(e.xmotion.x_root, e.xmotion.y_root).ToString()); + properties.emplace_back( + "state", ToString(e.xmotion.state)); + properties.emplace_back( + "time", ToString(e.xmotion.time)); + break; + case KeyPress: + case KeyRelease: + properties.emplace_back( + "window", ToString(e.xkey.window)); + properties.emplace_back( + "state", ToString(e.xkey.state)); + properties.emplace_back( + "keycode", ToString(e.xkey.keycode)); + break; + default: + // No properties are printed for unused events. + break; + } + + // 2. Build final string. + const string properties_string = Join( + properties, ", ", [] (const pair &pair) { + return pair.first + ": " + pair.second; + }); + ostringstream out; + out << X_EVENT_TYPE_NAMES[e.type] << " { " << properties_string << " }"; + return out.str(); +} + +string XConfigureWindowValueMaskToString(unsigned long value_mask) { + vector masks; + if (value_mask & CWX) { + masks.emplace_back("X"); + } + if (value_mask & CWY) { + masks.emplace_back("Y"); + } + if (value_mask & CWWidth) { + masks.emplace_back("Width"); + } + if (value_mask & CWHeight) { + masks.emplace_back("Height"); + } + if (value_mask & CWBorderWidth) { + masks.emplace_back("BorderWidth"); + } + if (value_mask & CWSibling) { + masks.emplace_back("Sibling"); + } + if (value_mask & CWStackMode) { + masks.emplace_back("StackMode"); + } + return Join(masks, "|"); +} + +string XRequestCodeToString(unsigned char request_code) { + static const char* const X_REQUEST_CODE_NAMES[] = { + "", + "CreateWindow", + "ChangeWindowAttributes", + "GetWindowAttributes", + "DestroyWindow", + "DestroySubwindows", + "ChangeSaveSet", + "ReparentWindow", + "MapWindow", + "MapSubwindows", + "UnmapWindow", + "UnmapSubwindows", + "ConfigureWindow", + "CirculateWindow", + "GetGeometry", + "QueryTree", + "InternAtom", + "GetAtomName", + "ChangeProperty", + "DeleteProperty", + "GetProperty", + "ListProperties", + "SetSelectionOwner", + "GetSelectionOwner", + "ConvertSelection", + "SendEvent", + "GrabPointer", + "UngrabPointer", + "GrabButton", + "UngrabButton", + "ChangeActivePointerGrab", + "GrabKeyboard", + "UngrabKeyboard", + "GrabKey", + "UngrabKey", + "AllowEvents", + "GrabServer", + "UngrabServer", + "QueryPointer", + "GetMotionEvents", + "TranslateCoords", + "WarpPointer", + "SetInputFocus", + "GetInputFocus", + "QueryKeymap", + "OpenFont", + "CloseFont", + "QueryFont", + "QueryTextExtents", + "ListFonts", + "ListFontsWithInfo", + "SetFontPath", + "GetFontPath", + "CreatePixmap", + "FreePixmap", + "CreateGC", + "ChangeGC", + "CopyGC", + "SetDashes", + "SetClipRectangles", + "FreeGC", + "ClearArea", + "CopyArea", + "CopyPlane", + "PolyPoint", + "PolyLine", + "PolySegment", + "PolyRectangle", + "PolyArc", + "FillPoly", + "PolyFillRectangle", + "PolyFillArc", + "PutImage", + "GetImage", + "PolyText8", + "PolyText16", + "ImageText8", + "ImageText16", + "CreateColormap", + "FreeColormap", + "CopyColormapAndFree", + "InstallColormap", + "UninstallColormap", + "ListInstalledColormaps", + "AllocColor", + "AllocNamedColor", + "AllocColorCells", + "AllocColorPlanes", + "FreeColors", + "StoreColors", + "StoreNamedColor", + "QueryColors", + "LookupColor", + "CreateCursor", + "CreateGlyphCursor", + "FreeCursor", + "RecolorCursor", + "QueryBestSize", + "QueryExtension", + "ListExtensions", + "ChangeKeyboardMapping", + "GetKeyboardMapping", + "ChangeKeyboardControl", + "GetKeyboardControl", + "Bell", + "ChangePointerControl", + "GetPointerControl", + "SetScreenSaver", + "GetScreenSaver", + "ChangeHosts", + "ListHosts", + "SetAccessControl", + "SetCloseDownMode", + "KillClient", + "RotateProperties", + "ForceScreenSaver", + "SetPointerMapping", + "GetPointerMapping", + "SetModifierMapping", + "GetModifierMapping", + "NoOperation", + }; + return X_REQUEST_CODE_NAMES[request_code]; +} diff --git a/plugins/launcher/home/util.hpp b/plugins/launcher/home/util.hpp new file mode 100644 index 00000000..0cbf764e --- /dev/null +++ b/plugins/launcher/home/util.hpp @@ -0,0 +1,241 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (C) 2013-2017 Chuan Ji * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); * + * you may not use this file except in compliance with the License. * + * You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef UTIL_HPP +#define UTIL_HPP + +extern "C" { +#include +} +#include +#include + +// Represents a 2D size. +template +struct Size { + T width, height; + + Size() = default; + Size(T w, T h) + : width(w), height(h) { + } + + ::std::string ToString() const; +}; + +// Outputs a Size as a string to a std::ostream. +template +::std::ostream& operator << (::std::ostream& out, const Size& size); + +// Represents a 2D position. +template +struct Position { + T x, y; + + Position() = default; + Position(T _x, T _y) + : x(_x), y(_y) { + } + + ::std::string ToString() const; +}; + +// Represents a 2D vector. +template +struct Vector2D { + T x, y; + + Vector2D() = default; + Vector2D(T _x, T _y) + : x(_x), y(_y) { + } + + ::std::string ToString() const; +}; + +// Outputs a Size as a string to a std::ostream. +template +::std::ostream& operator << (::std::ostream& out, const Position& pos); + +// Position operators. +template +Vector2D operator - (const Position& a, const Position& b); +template +Position operator + (const Position& a, const Vector2D &v); +template +Position operator + (const Vector2D &v, const Position& a); +template +Position operator - (const Position& a, const Vector2D &v); + +// Size operators. +template +Vector2D operator - (const Size& a, const Size& b); +template +Size operator + (const Size& a, const Vector2D &v); +template +Size operator + (const Vector2D &v, const Size& a); +template +Size operator - (const Size& a, const Vector2D &v); + +// Joins a container of elements into a single string, with elements separated +// by a delimiter. Any element can be used as long as an operator << on ostream +// is defined. +template +::std::string Join(const Container& container, const ::std::string& delimiter); + +// Joins a container of elements into a single string, with elements separated +// by a delimiter. The elements are converted to string using a converter +// function. +template +::std::string Join( + const Container& container, + const ::std::string& delimiter, + Converter converter); + +// Returns a string representation of a built-in type that we already have +// ostream support for. +template +::std::string ToString(const T& x); + +// Returns a string describing an X event for debugging purposes. +extern ::std::string ToString(const XEvent& e); + +// Returns a string describing an X window configuration value mask. +extern ::std::string XConfigureWindowValueMaskToString(unsigned long value_mask); + +// Returns the name of an X request code. +extern ::std::string XRequestCodeToString(unsigned char request_code); + + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * IMPLEMENTATION * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ +#include +#include +#include + +template +::std::string Size::ToString() const { + ::std::ostringstream out; + out << width << 'x' << height; + return out.str(); +} + +template +::std::ostream& operator << (::std::ostream& out, const Size& size) { + return out << size.ToString(); +} + +template +::std::string Position::ToString() const { + ::std::ostringstream out; + out << "(" << x << ", " << y << ")"; + return out.str(); +} + +template +::std::ostream& operator << (::std::ostream& out, const Position& size) { + return out << size.ToString(); +} + +template +::std::string Vector2D::ToString() const { + ::std::ostringstream out; + out << "(" << x << ", " << y << ")"; + return out.str(); +} + +template +::std::ostream& operator << (::std::ostream& out, const Vector2D& size) { + return out << size.ToString(); +} + +template +Vector2D operator - (const Position& a, const Position& b) { + return Vector2D(a.x - b.x, a.y - b.y); +} + +template +Position operator + (const Position& a, const Vector2D &v) { + return Position(a.x + v.x, a.y + v.y); +} + +template +Position operator + (const Vector2D &v, const Position& a) { + return Position(a.x + v.x, a.y + v.y); +} + +template +Position operator - (const Position& a, const Vector2D &v) { + return Position(a.x - v.x, a.y - v.y); +} + +template +Vector2D operator - (const Size& a, const Size& b) { + return Vector2D(a.width - b.width, a.height - b.height); +} + +template +Size operator + (const Size& a, const Vector2D &v) { + return Size(a.width + v.x, a.height + v.y); +} + +template +Size operator + (const Vector2D &v, const Size& a) { + return Size(a.width + v.x, a.height + v.y); +} + +template +Size operator - (const Size& a, const Vector2D &v) { + return Size(a.width - v.x, a.height - v.y); +} + +template +::std::string Join(const Container& container, const ::std::string& delimiter) { + ::std::ostringstream out; + for (auto i = container.cbegin(); i != container.cend(); ++i) { + if (i != container.cbegin()) { + out << delimiter; + } + out << *i; + } + return out.str(); +} + +template +::std::string Join( + const Container& container, + const ::std::string& delimiter, + Converter converter) { + ::std::vector<::std::string> converted_container(container.size()); + ::std::transform( + container.cbegin(), + container.cend(), + converted_container.begin(), + converter); + return Join(converted_container, delimiter); +} + +template +::std::string ToString(const T& x) { + ::std::ostringstream out; + out << x; + return out.str(); +} + +#endif diff --git a/plugins/launcher/home/window_manager.cpp b/plugins/launcher/home/window_manager.cpp new file mode 100644 index 00000000..9bc1e6d5 --- /dev/null +++ b/plugins/launcher/home/window_manager.cpp @@ -0,0 +1,483 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (C) 2013-2017 Chuan Ji * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); * + * you may not use this file except in compliance with the License. * + * You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include "window_manager.hpp" +extern "C" { +#include +} +#include +#include +#include +#include "util.hpp" + +using ::std::max; +using ::std::mutex; +using ::std::string; +using ::std::unique_ptr; + +bool WindowManager::wm_detected_; +mutex WindowManager::wm_detected_mutex_; + +WindowManager* WindowManager::Create(const string& display_str) { + // 1. Open X display. + const char* display_c_str = + display_str.empty() ? nullptr : display_str.c_str(); + Display* display = XOpenDisplay(display_c_str); + if (display == nullptr) { + qCritical() << "Failed to open X display " << XDisplayName(display_c_str); + return nullptr; + } + // 2. Construct WindowManager instance. + return new WindowManager(display); +} + +WindowManager::WindowManager(Display* display) + : display_(CHECK_NOTNULL(display)), + root_(DefaultRootWindow(display_)), + WM_PROTOCOLS(XInternAtom(display_, "WM_PROTOCOLS", false)), + WM_DELETE_WINDOW(XInternAtom(display_, "WM_DELETE_WINDOW", false)) { +} + +WindowManager::~WindowManager() { + XCloseDisplay(display_); +} + +void WindowManager::Run() { + // 1. Initialization. + // a. Select events on root window. Use a special error handler so we can + // exit gracefully if another window manager is already running. + { + ::std::lock_guard lock(wm_detected_mutex_); + + wm_detected_ = false; + XSetErrorHandler(&WindowManager::OnWMDetected); + XSelectInput( + display_, + root_, + SubstructureRedirectMask | SubstructureNotifyMask); + XSync(display_, false); + if (wm_detected_) { + qDebug() << "Detected another window manager on display " + << XDisplayString(display_); + return; + } else { + this->isRunning = true; + qDebug() << "No window manager running, proceeding..."; + } + } + // b. Set error handler. + XSetErrorHandler(&WindowManager::OnXError); + // c. Grab X server to prevent windows from changing under us. + XGrabServer(display_); + // d. Reparent existing top-level windows. + // i. Query existing top-level windows. + Window returned_root, returned_parent; + Window* top_level_windows; + unsigned int num_top_level_windows; + XQueryTree(display_, root_, &returned_root, &returned_parent, &top_level_windows, &num_top_level_windows); + if(returned_root != root_) qDebug() << "[ERROR] root mismatch"; + // ii. Frame each top-level window. + for (unsigned int i = 0; i < num_top_level_windows; ++i) { + Frame(top_level_windows[i], true); + } + // iii. Free top-level window array. + XFree(top_level_windows); + // e. Ungrab X server. + XUngrabServer(display_); + + // 2. Main event loop. + for (;;) { + // 1. Get next event. + XEvent e; + XNextEvent(display_, &e); + LOG(INFO) << "Received event: " << ToString(e); + QMessageBox msgBox; + QString qstr = QString::fromStdString(ToString(e)); + msgBox.setText("Received event: " + qstr); + msgBox.exec(); + + // 2. Dispatch event. + switch (e.type) { + case CreateNotify: + OnCreateNotify(e.xcreatewindow); + break; + case DestroyNotify: + OnDestroyNotify(e.xdestroywindow); + break; + case ReparentNotify: + OnReparentNotify(e.xreparent); + break; + case MapNotify: + OnMapNotify(e.xmap); + break; + case UnmapNotify: + OnUnmapNotify(e.xunmap); + break; + case ConfigureNotify: + OnConfigureNotify(e.xconfigure); + break; + case MapRequest: + OnMapRequest(e.xmaprequest); + break; + case ConfigureRequest: + OnConfigureRequest(e.xconfigurerequest); + break; + case ButtonPress: + OnButtonPress(e.xbutton); + break; + case ButtonRelease: + OnButtonRelease(e.xbutton); + break; + case MotionNotify: + // Skip any already pending motion events. + while (XCheckTypedWindowEvent( + display_, e.xmotion.window, MotionNotify, &e)) {} + OnMotionNotify(e.xmotion); + break; + case KeyPress: + OnKeyPress(e.xkey); + break; + case KeyRelease: + OnKeyRelease(e.xkey); + break; + default: + LOG(WARNING) << "Ignored event"; + } + } +} + +void WindowManager::Frame(Window w, bool was_created_before_window_manager) { + // Visual properties of the frame to create. + const unsigned int BORDER_WIDTH = 3; + const unsigned long BORDER_COLOR = 0xff0000; + const unsigned long BG_COLOR = 0x0000ff; + + // We shouldn't be framing windows we've already framed. + CHECK(!clients_.count(w)); + + // 1. Retrieve attributes of window to frame. + XWindowAttributes x_window_attrs; + CHECK(XGetWindowAttributes(display_, w, &x_window_attrs)); + + // 2. If window was created before window manager started, we should frame + // it only if it is visible and doesn't set override_redirect. + if (was_created_before_window_manager) { + if (x_window_attrs.override_redirect || + x_window_attrs.map_state != IsViewable) { + return; + } + } + + // 3. Create frame. + const Window frame = XCreateSimpleWindow( + display_, + root_, + x_window_attrs.x, + x_window_attrs.y, + x_window_attrs.width, + x_window_attrs.height, + BORDER_WIDTH, + BORDER_COLOR, + BG_COLOR); + // 4. Select events on frame. + XSelectInput( + display_, + frame, + SubstructureRedirectMask | SubstructureNotifyMask); + // 5. Add client to save set, so that it will be restored and kept alive if we + // crash. + XAddToSaveSet(display_, w); + // 6. Reparent client window. + XReparentWindow( + display_, + w, + frame, + 0, 0); // Offset of client window within frame. + // 7. Map frame. + XMapWindow(display_, frame); + // 8. Save frame handle. + clients_[w] = frame; + // 9. Grab universal window management actions on client window. + // a. Move windows with alt + left button. + XGrabButton( + display_, + Button1, + Mod1Mask, + w, + false, + ButtonPressMask | ButtonReleaseMask | ButtonMotionMask, + GrabModeAsync, + GrabModeAsync, + None, + None); + // b. Resize windows with alt + right button. + XGrabButton( + display_, + Button3, + Mod1Mask, + w, + false, + ButtonPressMask | ButtonReleaseMask | ButtonMotionMask, + GrabModeAsync, + GrabModeAsync, + None, + None); + // c. Kill windows with alt + f4. + XGrabKey( + display_, + XKeysymToKeycode(display_, XK_F4), + Mod1Mask, + w, + false, + GrabModeAsync, + GrabModeAsync); + // d. Switch windows with alt + tab. + XGrabKey( + display_, + XKeysymToKeycode(display_, XK_Tab), + Mod1Mask, + w, + false, + GrabModeAsync, + GrabModeAsync); + + LOG(INFO) << "Framed window " << w << " [" << frame << "]"; +} + +void WindowManager::Unframe(Window w) { + CHECK(clients_.count(w)); + + // We reverse the steps taken in Frame(). + const Window frame = clients_[w]; + // 1. Unmap frame. + XUnmapWindow(display_, frame); + // 2. Reparent client window. + XReparentWindow( + display_, + w, + root_, + 0, 0); // Offset of client window within root. + // 3. Remove client window from save set, as it is now unrelated to us. + XRemoveFromSaveSet(display_, w); + // 4. Destroy frame. + XDestroyWindow(display_, frame); + // 5. Drop reference to frame handle. + clients_.erase(w); + + LOG(INFO) << "Unframed window " << w << " [" << frame << "]"; +} + +void WindowManager::OnCreateNotify(const XCreateWindowEvent& e) {} + +void WindowManager::OnDestroyNotify(const XDestroyWindowEvent& e) {} + +void WindowManager::OnReparentNotify(const XReparentEvent& e) {} + +void WindowManager::OnMapNotify(const XMapEvent& e) {} + +void WindowManager::OnUnmapNotify(const XUnmapEvent& e) { + // If the window is a client window we manage, unframe it upon UnmapNotify. We + // need the check because we will receive an UnmapNotify event for a frame + // window we just destroyed ourselves. + if (!clients_.count(e.window)) { + LOG(INFO) << "Ignore UnmapNotify for non-client window " << e.window; + return; + } + + // Ignore event if it is triggered by reparenting a window that was mapped + // before the window manager started. + // + // Since we receive UnmapNotify events from the SubstructureNotify mask, the + // event attribute specifies the parent window of the window that was + // unmapped. This means that an UnmapNotify event from a normal client window + // should have this attribute set to a frame window we maintain. Only an + // UnmapNotify event triggered by reparenting a pre-existing window will have + // this attribute set to the root window. + if (e.event == root_) { + LOG(INFO) << "Ignore UnmapNotify for reparented pre-existing window " + << e.window; + return; + } + + Unframe(e.window); +} + +void WindowManager::OnConfigureNotify(const XConfigureEvent& e) {} + +void WindowManager::OnMapRequest(const XMapRequestEvent& e) { + // 1. Frame or re-frame window. + Frame(e.window, false); + // 2. Actually map window. + XMapWindow(display_, e.window); +} + +void WindowManager::OnConfigureRequest(const XConfigureRequestEvent& e) { + XWindowChanges changes; + changes.x = e.x; + changes.y = e.y; + changes.width = e.width; + changes.height = e.height; + changes.border_width = e.border_width; + changes.sibling = e.above; + changes.stack_mode = e.detail; + if (clients_.count(e.window)) { + const Window frame = clients_[e.window]; + XConfigureWindow(display_, frame, e.value_mask, &changes); + LOG(INFO) << "Resize [" << frame << "] to " << Size(e.width, e.height); + } + XConfigureWindow(display_, e.window, e.value_mask, &changes); + LOG(INFO) << "Resize " << e.window << " to " << Size(e.width, e.height); +} + +void WindowManager::OnButtonPress(const XButtonEvent& e) { + CHECK(clients_.count(e.window)); + const Window frame = clients_[e.window]; + + // 1. Save initial cursor position. + drag_start_pos_ = Position(e.x_root, e.y_root); + + // 2. Save initial window info. + Window returned_root; + int x, y; + unsigned width, height, border_width, depth; + CHECK(XGetGeometry( + display_, + frame, + &returned_root, + &x, &y, + &width, &height, + &border_width, + &depth)); + drag_start_frame_pos_ = Position(x, y); + drag_start_frame_size_ = Size(width, height); + + // 3. Raise clicked window to top. + XRaiseWindow(display_, frame); +} + +void WindowManager::OnButtonRelease(const XButtonEvent& e) {} + +void WindowManager::OnMotionNotify(const XMotionEvent& e) { + CHECK(clients_.count(e.window)); + const Window frame = clients_[e.window]; + const Position drag_pos(e.x_root, e.y_root); + const Vector2D delta = drag_pos - drag_start_pos_; + + if (e.state & Button1Mask ) { + // alt + left button: Move window. + const Position dest_frame_pos = drag_start_frame_pos_ + delta; + XMoveWindow( + display_, + frame, + dest_frame_pos.x, dest_frame_pos.y); + } else if (e.state & Button3Mask) { + // alt + right button: Resize window. + // Window dimensions cannot be negative. + const Vector2D size_delta( + max(delta.x, -drag_start_frame_size_.width), + max(delta.y, -drag_start_frame_size_.height)); + const Size dest_frame_size = drag_start_frame_size_ + size_delta; + // 1. Resize frame. + XResizeWindow( + display_, + frame, + dest_frame_size.width, dest_frame_size.height); + // 2. Resize client window. + XResizeWindow( + display_, + e.window, + dest_frame_size.width, dest_frame_size.height); + } +} + +void WindowManager::OnKeyPress(const XKeyEvent& e) { + if ((e.state & Mod1Mask) && + (e.keycode == XKeysymToKeycode(display_, XK_F4))) { + // alt + f4: Close window. + // + // There are two ways to tell an X window to close. The first is to send it + // a message of type WM_PROTOCOLS and value WM_DELETE_WINDOW. If the client + // has not explicitly marked itself as supporting this more civilized + // behavior (using XSetWMProtocols()), we kill it with XKillClient(). + Atom* supported_protocols; + int num_supported_protocols; + if (XGetWMProtocols(display_, + e.window, + &supported_protocols, + &num_supported_protocols) && + (::std::find(supported_protocols, + supported_protocols + num_supported_protocols, + WM_DELETE_WINDOW) != + supported_protocols + num_supported_protocols)) { + LOG(INFO) << "Gracefully deleting window " << e.window; + // 1. Construct message. + XEvent msg; + memset(&msg, 0, sizeof(msg)); + msg.xclient.type = ClientMessage; + msg.xclient.message_type = WM_PROTOCOLS; + msg.xclient.window = e.window; + msg.xclient.format = 32; + msg.xclient.data.l[0] = WM_DELETE_WINDOW; + // 2. Send message to window to be closed. + CHECK(XSendEvent(display_, e.window, false, 0, &msg)); + } else { + LOG(INFO) << "Killing window " << e.window; + XKillClient(display_, e.window); + } + } else if ((e.state & Mod1Mask) && + (e.keycode == XKeysymToKeycode(display_, XK_Tab))) { + // alt + tab: Switch window. + // 1. Find next window. + auto i = clients_.find(e.window); + CHECK(i != clients_.end()); + ++i; + if (i == clients_.end()) { + i = clients_.begin(); + } + // 2. Raise and set focus. + XRaiseWindow(display_, i->second); + XSetInputFocus(display_, i->first, RevertToPointerRoot, CurrentTime); + } +} + +void WindowManager::OnKeyRelease(const XKeyEvent& e) {} + +int WindowManager::OnXError(Display* display, XErrorEvent* e) { + const int MAX_ERROR_TEXT_LENGTH = 1024; + char error_text[MAX_ERROR_TEXT_LENGTH]; + XGetErrorText(display, e->error_code, error_text, sizeof(error_text)); + LOG(ERROR) << "Received X error:\n" + << " Request: " << int(e->request_code) + << " - " << XRequestCodeToString(e->request_code) << "\n" + << " Error code: " << int(e->error_code) + << " - " << error_text << "\n" + << " Resource ID: " << e->resourceid; + // The return value is ignored. + return 0; +} + +int WindowManager::OnWMDetected(Display* display, XErrorEvent* e) { + // In the case of an already running window manager, the error code from + // XSelectInput is BadAccess. We don't expect this handler to receive any + // other errors. + CHECK_EQ(static_cast(e->error_code), BadAccess); + // Set flag. + wm_detected_ = true; + // The return value is ignored. + return 0; +} diff --git a/plugins/launcher/home/window_manager.hpp b/plugins/launcher/home/window_manager.hpp new file mode 100644 index 00000000..6493f9d9 --- /dev/null +++ b/plugins/launcher/home/window_manager.hpp @@ -0,0 +1,116 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (C) 2013-2017 Chuan Ji * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); * + * you may not use this file except in compliance with the License. * + * You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ +#pragma once + +#include +#include +#include +#include +#include + +extern "C" { +#include +} +#include +#include +#include +#include +#include "util.hpp" + + +// Implementation of a window manager for an X screen. +class WindowManager : QObject { + Q_OBJECT + + public: + // Creates a WindowManager instance for the X display/screen specified by the + // argument string, or if unspecified, the DISPLAY environment variable. On + // failure, returns nullptr. + static WindowManager* Create(const std::string& display_str = std::string()); + ~WindowManager(); + // The entry point to this class. Enters the main event loop. + void Run(); + bool isRunning = false; + + public slots: + + signals: + + void OnCreateSignal(const XCreateWindowEvent& e); + void OnDestroySignal(const XDestroyWindowEvent& e); + void OnReparentSignal(const XReparentEvent& e); + void OnMapSignal(const XMapEvent& e); + void OnUnmapSignal(const XUnmapEvent& e); + + private: + // Invoked internally by Create(). + WindowManager(Display* display); + // Frames a top-level window. + void Frame(Window w, bool was_created_before_window_manager); + // Unframes a client window. + void Unframe(Window w); + + // Event handlers. + void OnCreateNotify(const XCreateWindowEvent& e); + void OnDestroyNotify(const XDestroyWindowEvent& e); + void OnReparentNotify(const XReparentEvent& e); + void OnMapNotify(const XMapEvent& e); + void OnUnmapNotify(const XUnmapEvent& e); + void OnConfigureNotify(const XConfigureEvent& e); + void OnMapRequest(const XMapRequestEvent& e); + void OnConfigureRequest(const XConfigureRequestEvent& e); + void OnButtonPress(const XButtonEvent& e); + void OnButtonRelease(const XButtonEvent& e); + void OnMotionNotify(const XMotionEvent& e); + void OnKeyPress(const XKeyEvent& e); + void OnKeyRelease(const XKeyEvent& e); + + // Xlib error handler. It must be static as its address is passed to Xlib. + static int OnXError(Display* display, XErrorEvent* e); + // Xlib error handler used to determine whether another window manager is + // running. It is set as the error handler right before selecting substructure + // redirection mask on the root window, so it is invoked if and only if + // another window manager is running. It must be static as its address is + // passed to Xlib. + static int OnWMDetected(Display* display, XErrorEvent* e); + // Whether an existing window manager has been detected. Set by OnWMDetected, + // and hence must be static. + static bool wm_detected_; + // A mutex for protecting wm_detected_. It's not strictly speaking needed as + // this program is single threaded, but better safe than sorry. + static ::std::mutex wm_detected_mutex_; + + // Handle to the underlying Xlib Display struct. + Display* display_; + // Handle to root window. + const Window root_; + // Maps top-level windows to their frame windows. + ::std::unordered_map clients_; + + // The cursor position at the start of a window move/resize. + Position drag_start_pos_; + // The position of the affected window at the start of a window + // move/resize. + Position drag_start_frame_pos_; + // The size of the affected window at the start of a window move/resize. + Size drag_start_frame_size_; + + // Atom constants. + const Atom WM_PROTOCOLS; + const Atom WM_DELETE_WINDOW; +};