Compare commits

..

No commits in common. "master" and "wip/rearrange" have entirely different histories.

13 changed files with 89 additions and 187 deletions

4
TODO Normal file
View File

@ -0,0 +1,4 @@
- Investigate memory leaks (relevant now that we have a single user mode,
so qsrun may stay open after launch)
- Provide --help
- ESC/CTRL+Q close the app. May not be expected behaviour

View File

@ -13,11 +13,6 @@ EntryProvider::EntryProvider(QStringList userEntriesDirsPaths, QStringList syste
<< "%u"; << "%u";
} }
bool EntryProvider::isSavable(const EntryConfig &config) const
{
return ! config.entryPath.isEmpty() && (config.type == EntryType::USER || config.type == EntryType::INHERIT);
}
EntryConfig EntryProvider::readFromDesktopFile(const QString &path) EntryConfig EntryProvider::readFromDesktopFile(const QString &path)
{ {
EntryConfig result; EntryConfig result;
@ -25,22 +20,20 @@ EntryConfig EntryProvider::readFromDesktopFile(const QString &path)
if(!file.open(QIODevice::ReadOnly | QIODevice::Text)) if(!file.open(QIODevice::ReadOnly | QIODevice::Text))
{ {
// TODO: better exception class // TODO: better exception class
throw std::runtime_error("Failed to open file"); throw new std::runtime_error("Failed to open file");
} }
QTextStream stream(&file); QTextStream stream(&file);
// There should be nothing preceding this group in the desktop entry file but possibly one or more comments. // There should be nothing preceding this group in the desktop entry file but possibly one or more comments.
// https://standards.freedesktop.org/desktop-entry-spec/latest/ar01s03.html#group-header // https://standards.freedesktop.org/desktop-entry-spec/latest/ar01s03.html#group-header
// Ignore that as there some that violate that in the wild QString firstLine;
const QString startSection = "[Desktop Entry]";
QString line;
do do
{ {
line = stream.readLine().trimmed(); firstLine = stream.readLine().trimmed();
} while(!stream.atEnd() && line != startSection); } while(!stream.atEnd() && (firstLine.isEmpty() || firstLine[0] == '#'));
if(line != startSection) if(firstLine != "[Desktop Entry]")
{ {
throw ConfigFormatException(".desktop file does not contain [Desktop Entry] section: " + path.toStdString()); throw ConfigFormatException(".desktop file does not start with [Desktop Entry]: " + path.toStdString());
} }
while(!stream.atEnd()) while(!stream.atEnd())
@ -86,10 +79,6 @@ EntryConfig EntryProvider::readFromDesktopFile(const QString &path)
{ {
result.hidden = args == "true"; result.hidden = args == "true";
} }
if(key == "terminal")
{
result.isTerminalCommand = args == "true";
}
} }
result.type = EntryType::SYSTEM; result.type = EntryType::SYSTEM;
return result; return result;
@ -122,7 +111,7 @@ EntryConfig EntryProvider::readqsrunFile(const QString &path)
if(!file.open(QIODevice::ReadOnly | QIODevice::Text)) if(!file.open(QIODevice::ReadOnly | QIODevice::Text))
{ {
// TODO: better exception class // TODO: better exception class
throw std::runtime_error("Failed to open file"); throw new std::runtime_error("Failed to open file");
} }
QHash<QString, QString> map; QHash<QString, QString> map;
QTextStream stream(&file); QTextStream stream(&file);
@ -133,7 +122,7 @@ EntryConfig EntryProvider::readqsrunFile(const QString &path)
int spacePos = line.indexOf(' '); int spacePos = line.indexOf(' ');
if(spacePos == -1) if(spacePos == -1)
{ {
throw ConfigFormatException("misformated line in .qsrun config file " + path.toStdString()); throw new ConfigFormatException("misformated line in .qsrun config file " + path.toStdString());
} }
QString key = line.mid(0, spacePos); QString key = line.mid(0, spacePos);
@ -141,7 +130,7 @@ EntryConfig EntryProvider::readqsrunFile(const QString &path)
if(key == "" || value == "") if(key == "" || value == "")
{ {
throw ConfigFormatException("empty key or value in .qsrun config file " + path.toStdString()); throw new ConfigFormatException("empty key or value in .qsrun config file " + path.toStdString());
} }
map[key] = value; map[key] = value;
} }
@ -156,7 +145,7 @@ EntryConfig EntryProvider::readqsrunFile(const QString &path)
} }
else else
{ {
throw ConfigFormatException("Error attempting to read inherited entry"); throw new ConfigFormatException("Error attempting to read inherited entry");
} }
} }
QString type = map["type"]; QString type = map["type"];
@ -164,7 +153,7 @@ EntryConfig EntryProvider::readqsrunFile(const QString &path)
{ {
if(type == "system") if(type == "system")
{ {
throw ConfigFormatException(".qsrun files cannot be designated as system entries " + throw new ConfigFormatException(".qsrun files cannot be designated as system entries " +
path.toStdString()); path.toStdString());
} }
else if(type == "inherit") else if(type == "inherit")
@ -177,7 +166,7 @@ EntryConfig EntryProvider::readqsrunFile(const QString &path)
} }
else else
{ {
throw ConfigFormatException("Invalid value for type provided in file: " + path.toStdString()); throw new ConfigFormatException("Invalid value for type provided in file: " + path.toStdString());
} }
} }
else else
@ -230,7 +219,6 @@ EntryConfig EntryProvider::readqsrunFile(const QString &path)
} }
result.col = map["col"].toInt(); result.col = map["col"].toInt();
result.row = map["row"].toInt(); result.row = map["row"].toInt();
result.isTerminalCommand = map["terminal"] == "true";
return result; return result;
} }
@ -292,7 +280,7 @@ QVector<EntryConfig> EntryProvider::getSystemEntries()
void EntryProvider::saveUserEntry(const EntryConfig &config) void EntryProvider::saveUserEntry(const EntryConfig &config)
{ {
if(!isSavable(config)) if(config.type == EntryType::SYSTEM || config.entryPath.isEmpty())
{ {
throw std::runtime_error("Only user/inherited entries can be saved"); throw std::runtime_error("Only user/inherited entries can be saved");
} }
@ -303,35 +291,32 @@ void EntryProvider::saveUserEntry(const EntryConfig &config)
throw std::runtime_error("Error: Can not open file for writing"); throw std::runtime_error("Error: Can not open file for writing");
} }
QTextStream outStream(&file); QTextStream outStream(&file);
outStream << "type" << " " << ((config.type == EntryType::USER) ? "user" : "inherit") << Qt::endl; outStream << "type" << " " << ((config.type == EntryType::USER) ? "user" : "inherit") << endl;
if(!config.inherit.isEmpty()) if(!config.inherit.isEmpty())
{ {
outStream << "inherit" << " " << config.inherit << Qt::endl; outStream << "inherit" << " " << config.inherit << endl;
}
outStream << "row" << " " << config.row << Qt::endl;
outStream << "col" << " " << config.col << Qt::endl;
outStream << "hidden" << " " << config.hidden << Qt::endl;
if(!config.key.isEmpty())
{
outStream << "key" << " " << config.key << Qt::endl;
} }
outStream << "row" << " " << config.row << endl;
outStream << "col" << " " << config.col << endl;
outStream << "hidden" << " " << config.hidden << endl;
outStream << "key" << " " << config.key << endl;
if(config.type == EntryType::USER) if(config.type == EntryType::USER)
{ {
if(!config.name.isEmpty()) if(!config.name.isEmpty())
{ {
outStream << "name" << " " << config.name << Qt::endl; outStream << "name" << " " << config.name << endl;
} }
if(!config.command.isEmpty()) if(!config.command.isEmpty())
{ {
outStream << "command" << " " << config.command << Qt::endl; outStream << "command" << " " << config.command << endl;
} }
if(!config.iconPath.isEmpty()) if(!config.iconPath.isEmpty())
{ {
outStream << "icon" << " " << config.iconPath << Qt::endl; outStream << "icon" << " " << config.iconPath << endl;
} }
if(!config.arguments.empty()) if(!config.arguments.empty())
{ {
outStream << "arguments" << " " << config.arguments.join(' ') << Qt::endl; outStream << "arguments" << " " << config.arguments.join(' ') << endl;
} }
} }
@ -349,7 +334,7 @@ void EntryProvider::saveUserEntry(const EntryConfig &config)
bool EntryProvider::deleteUserEntry(const EntryConfig &config) bool EntryProvider::deleteUserEntry(const EntryConfig &config)
{ {
if(!isSavable(config)) if(config.type == EntryType::SYSTEM || config.entryPath.isEmpty())
{ {
throw std::runtime_error("Only user/inherited entries can be deleted"); throw std::runtime_error("Only user/inherited entries can be deleted");
} }

View File

@ -19,8 +19,7 @@ enum EntryType
{ {
USER, USER,
INHERIT, INHERIT,
SYSTEM, SYSTEM
DYNAMIC
}; };
class EntryConfig class EntryConfig
@ -28,7 +27,6 @@ class EntryConfig
public: public:
EntryType type = SYSTEM; EntryType type = SYSTEM;
bool hidden = false; bool hidden = false;
bool isTerminalCommand = false;
QString entryPath; QString entryPath;
QString key; QString key;
QString name; QString name;
@ -56,7 +54,6 @@ class EntryProvider
public: public:
EntryProvider(QStringList userEntriesDirsPaths, QStringList systemEntriesDirsPaths); EntryProvider(QStringList userEntriesDirsPaths, QStringList systemEntriesDirsPaths);
bool isSavable(const EntryConfig &config) const;
QVector<EntryConfig> getUserEntries(); QVector<EntryConfig> getUserEntries();
QVector<EntryConfig> getSystemEntries(); QVector<EntryConfig> getSystemEntries();
void saveUserEntry(const EntryConfig &config); void saveUserEntry(const EntryConfig &config);

View File

@ -23,17 +23,21 @@ EntryPushButton::EntryPushButton(const EntryConfig &config) : QPushButton()
{ {
this->setText(config.name); this->setText(config.name);
this->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); this->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
QIcon icon; QIcon icon = resolveIcon(config.iconPath);
if(config.isTerminalCommand && config.iconPath.isEmpty())
{
icon = resolveIcon("utilities-terminal");
}
else
{
icon = resolveIcon(config.iconPath);
}
this->setIcon(icon); this->setIcon(icon);
this->setIconSize(QSize{256, 256}); if(!icon.availableSizes().isEmpty())
{
auto sizes = icon.availableSizes();
QSize maxSize = sizes.first();
for(QSize &current : sizes)
{
if(current.width() > maxSize.width())
{
maxSize = current;
}
}
this->setIconSize(maxSize);
}
this->config = config; this->config = config;
connect(this, SIGNAL(clicked()), this, SLOT(emitOwnClicked())); connect(this, SIGNAL(clicked()), this, SLOT(emitOwnClicked()));
@ -94,7 +98,7 @@ void EntryPushButton::mousePressEvent(QMouseEvent *event)
{ {
this->userEntryMenu.exec(QCursor::pos()); this->userEntryMenu.exec(QCursor::pos());
} }
else if(this->config.type == EntryType::SYSTEM) else
{ {
this->systemEntryMenu.exec(QCursor::pos()); this->systemEntryMenu.exec(QCursor::pos());
} }
@ -104,7 +108,7 @@ void EntryPushButton::mousePressEvent(QMouseEvent *event)
void EntryPushButton::mouseMoveEvent(QMouseEvent *event) void EntryPushButton::mouseMoveEvent(QMouseEvent *event)
{ {
if(this->config.type == EntryType::SYSTEM || this->config.type == EntryType::DYNAMIC) if(this->config.type == EntryType::SYSTEM)
{ {
return; return;
} }

View File

@ -14,7 +14,6 @@
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/ */
#include <QApplication> #include <QApplication>
#include <QCommandLineParser>
#include <QFuture> #include <QFuture>
#include <QFutureWatcher> #include <QFutureWatcher>
#include <QtConcurrent/QtConcurrentRun> #include <QtConcurrent/QtConcurrentRun>
@ -31,76 +30,56 @@ int main(int argc, char *argv[])
QApplication app(argc, argv); QApplication app(argc, argv);
QString configDirectoryPath; QString configDirectoryPath;
QDir dir; QDir dir;
bool newInstanceRequested = false;
if(argc >= 2) if(argc >= 2)
{ {
QCommandLineParser parser; configDirectoryPath = QCoreApplication::arguments().at(1);
parser.addOptions({ if(!dir.exists(configDirectoryPath))
{"new-instance", "Launch a new instance, ignoring any running ones"},
{"config", "Use supplied config dir instead of default"},
});
parser.addHelpOption();
parser.process(app.arguments());
configDirectoryPath = parser.value("config");
newInstanceRequested = parser.isSet("new-instance");
if(!configDirectoryPath.isEmpty() && !dir.exists(configDirectoryPath))
{ {
QMessageBox::warning(nullptr, "Directory not found", configDirectoryPath + " was not found"); QMessageBox::warning(nullptr, "Directory not found", configDirectoryPath + " was not found");
return 1; return 1;
} }
} }
if(configDirectoryPath.isEmpty()) else
{ {
configDirectoryPath = QDir::homePath() + "/.config/qsrun/"; configDirectoryPath = QDir::homePath() + "/.config/qsrun/";
} }
qRegisterMetaType<QVector<QString> >("QVector<QString>");
qRegisterMetaType<QVector<QString>>("QVector<QString>");
if(!dir.exists(configDirectoryPath)) if(!dir.exists(configDirectoryPath))
{ {
if(!dir.mkdir(configDirectoryPath)) if(!dir.mkdir(configDirectoryPath))
{ {
QMessageBox::warning(nullptr, "Failed to create dir", QMessageBox::warning(nullptr, "Failed to create dir", configDirectoryPath + " was not found and could not be created!");
configDirectoryPath + " was not found and could not be created!");
return 1; return 1;
} }
} }
QSettings settings(configDirectoryPath + "qsrun.config", QSettings::NativeFormat); QSettings settings(configDirectoryPath + "qsrun.config", QSettings::NativeFormat);
SettingsProvider settingsProvider{settings}; SettingsProvider settingsProvider { settings };
EntryProvider entryProvider(settingsProvider.userEntriesPaths(), settingsProvider.systemApplicationsEntriesPaths()); EntryProvider entryProvider(settingsProvider.userEntriesPaths(), settingsProvider.systemApplicationsEntriesPaths());
//TODO if setting single instance mode
SingleInstanceServer *server = nullptr;
bool singleInstanceMode = !newInstanceRequested && settingsProvider.singleInstanceMode();
if(singleInstanceMode)
{
QLocalSocket localSocket; QLocalSocket localSocket;
localSocket.connectToServer(settingsProvider.socketPath()); localSocket.connectToServer("/tmp/qsrun.socket");
SingleInstanceServer server;
if(localSocket.isOpen() && localSocket.isWritable()) if(localSocket.isOpen() && localSocket.isWritable())
{ {
QDataStream stream(&localSocket); QDataStream stream(&localSocket);
stream << (int)0x01; // maximize stream << (int)0x01; //maximize
localSocket.flush(); localSocket.flush();
localSocket.waitForBytesWritten(); localSocket.waitForBytesWritten();
localSocket.disconnectFromServer(); localSocket.disconnectFromServer();
return 0; return 0;
} }
else
server = new SingleInstanceServer(); {
if(!server->listen(settingsProvider.socketPath())) if(!server.listen("/tmp/qsrun.socket"))
{ {
qDebug() << "Failed to listen on socket!"; qDebug() << "Failed to listen on socket!";
return 1;
} }
} Window *w = new Window { entryProvider, settingsProvider };
QObject::connect(&server, &SingleInstanceServer::receivedMaximizationRequest, [&w]{
Window *w = new Window{entryProvider, settingsProvider};
if(singleInstanceMode && server != nullptr)
{
QObject::connect(server, &SingleInstanceServer::receivedMaximizationRequest, [&w] {
if(w != nullptr) if(w != nullptr)
{ {
qInfo() << "maximizing as requested by other instance"; qInfo() << "maximizing as requested by other instance";
@ -110,10 +89,12 @@ int main(int argc, char *argv[])
w->focusInput(); w->focusInput();
} }
}); });
}
w->showMaximized(); w->showMaximized();
w->focusInput(); w->focusInput();
}
return app.exec(); return app.exec();
} }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

BIN
screenshots/startview.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 112 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 205 KiB

View File

@ -28,13 +28,3 @@ bool SettingsProvider::singleInstanceMode() const
{ {
return settings->value("singleInstance", true).toBool(); return settings->value("singleInstance", true).toBool();
} }
QString SettingsProvider::getTerminalCommand() const
{
return settings->value("terminal", "/usr/bin/x-terminal-emulator -e %c").toString();
}
QString SettingsProvider::socketPath() const
{
return settings->value("singleInstanceSocket", "/tmp/qsrun").toString();
}

View File

@ -15,8 +15,6 @@ class SettingsProvider
virtual QStringList systemApplicationsEntriesPaths() const; virtual QStringList systemApplicationsEntriesPaths() const;
virtual int getMaxCols() const; virtual int getMaxCols() const;
virtual bool singleInstanceMode() const; virtual bool singleInstanceMode() const;
QString getTerminalCommand() const;
QString socketPath() const;
}; };
#endif // SETTINGSPROVIDER_H #endif // SETTINGSPROVIDER_H

View File

@ -120,17 +120,7 @@ void Window::populateGrid(const QVector<EntryPushButton *> &list)
void Window::executeConfig(const EntryConfig &config) void Window::executeConfig(const EntryConfig &config)
{ {
if(config.isTerminalCommand || QApplication::keyboardModifiers().testFlag(Qt::ShiftModifier))
{
QString cmd = settingsProvider->getTerminalCommand();
cmd.replace("%c", config.command);
QStringList args = QProcess::splitCommand(cmd);
QProcess::startDetached(args[0], args);
}
else
{
QProcess::startDetached(config.command, config.arguments); QProcess::startDetached(config.command, config.arguments);
}
this->closeWindow(); this->closeWindow();
} }
@ -167,7 +157,6 @@ void Window::addToFavourites(const EntryConfig &config)
when we add it to the favourites. the alternative would be to reload the whole config, when we add it to the favourites. the alternative would be to reload the whole config,
but that's probably overkill. */ but that's probably overkill. */
userConfig.update(config); userConfig.update(config);
userConfig.key = "";
userEntryButtons.append(createEntryButton(userConfig)); userEntryButtons.append(createEntryButton(userConfig));
} }
@ -267,7 +256,6 @@ void Window::addPATHSuggestion(const QString &text)
e.row = 0; e.row = 0;
e.command = suggestions[0]; e.command = suggestions[0];
e.iconPath = suggestions[0]; e.iconPath = suggestions[0];
e.type = EntryType::DYNAMIC;
EntryPushButton *button = createEntryButton(e); EntryPushButton *button = createEntryButton(e);
clearGrid(); clearGrid();
grid->addWidget(button, 0, 0); grid->addWidget(button, 0, 0);
@ -345,7 +333,6 @@ void Window::lineEditTextChanged(QString text)
} }
e.command = arguments[0]; e.command = arguments[0];
e.iconPath = "utilities-terminal"; e.iconPath = "utilities-terminal";
e.type = EntryType::DYNAMIC;
EntryPushButton *button = createEntryButton(e); EntryPushButton *button = createEntryButton(e);
clearGrid(); clearGrid();
@ -384,12 +371,9 @@ void Window::keyPressEvent(QKeyEvent *event)
} }
for(EntryPushButton *button : buttonsInGrid) for(EntryPushButton *button : buttonsInGrid)
{
if(!button->getEntryConfig().key.isEmpty())
{ {
button->showShortcut(); button->showShortcut();
} }
}
QKeySequence seq(event->key()); QKeySequence seq(event->key());
QString key = seq.toString().toLower(); QString key = seq.toString().toLower();
@ -404,27 +388,6 @@ void Window::keyPressEvent(QKeyEvent *event)
QWidget::keyPressEvent(event); QWidget::keyPressEvent(event);
} }
int Window::rankConfig(const EntryConfig &config, QString filter) const
{
if(config.name.startsWith(filter, Qt::CaseInsensitive))
{
return 0;
}
else if(config.command.startsWith(filter, Qt::CaseInsensitive))
{
return 1;
}
else if(config.name.contains(filter, Qt::CaseInsensitive))
{
return 2;
}
else if(config.command.contains(filter, Qt::CaseInsensitive))
{
return 3;
}
return -1;
}
void Window::filterGridFor(QString filter) void Window::filterGridFor(QString filter)
{ {
if(filter.length() > 0) if(filter.length() > 0)
@ -433,8 +396,7 @@ void Window::filterGridFor(QString filter)
bool userEntryMatch = false; bool userEntryMatch = false;
for(EntryPushButton *button : this->userEntryButtons) for(EntryPushButton *button : this->userEntryButtons)
{ {
if(button->getName().contains(filter, Qt::CaseInsensitive) || if(button->getName().contains(filter, Qt::CaseInsensitive))
button->getCommand().contains(filter, Qt::CaseInsensitive))
{ {
button->setVisible(true); button->setVisible(true);
grid->addWidget(button, button->getRow(), button->getCol()); grid->addWidget(button, button->getRow(), button->getCol());
@ -444,27 +406,14 @@ void Window::filterGridFor(QString filter)
} }
if(!userEntryMatch) if(!userEntryMatch)
{ {
QVector<RankedButton> rankedEntries;
int currow = 0; int currow = 0;
int curcol = 0; int curcol = 0;
int i = 1; int i = 1;
const int MAX_COLS = this->settingsProvider->getMaxCols(); const int MAX_COLS = this->settingsProvider->getMaxCols();
for(EntryPushButton *button : this->systemEntryButtons) for(EntryPushButton *button : this->systemEntryButtons)
{ {
int ranking = rankConfig(button->getEntryConfig(), filter); if(button->getName().contains(filter, Qt::CaseInsensitive))
if(ranking > -1)
{ {
RankedButton rb;
rb.button = button;
rb.ranking = ranking;
rankedEntries.append(rb);
}
}
std::sort(rankedEntries.begin(), rankedEntries.end(),
[](const RankedButton &a, const RankedButton &b) -> bool { return a.ranking < b.ranking; });
for(RankedButton &rankedButton : rankedEntries)
{
EntryPushButton *button = rankedButton.button;
button->setVisible(true); button->setVisible(true);
if(i < 10) if(i < 10)
{ {
@ -480,6 +429,7 @@ void Window::filterGridFor(QString filter)
} }
} }
} }
}
else else
{ {
populateGrid(this->userEntryButtons); populateGrid(this->userEntryButtons);

View File

@ -37,13 +37,6 @@
#include "calculationengine.h" #include "calculationengine.h"
#include "settingsprovider.h" #include "settingsprovider.h"
class RankedButton
{
public:
EntryPushButton *button = nullptr;
int ranking;
};
class Window : public QWidget class Window : public QWidget
{ {
Q_OBJECT Q_OBJECT
@ -84,7 +77,7 @@ class Window : public QWidget
QStringList generatePATHSuggestions(const QString &text); QStringList generatePATHSuggestions(const QString &text);
void closeWindow(); void closeWindow();
std::pair<int, int> getNextFreeCell(); std::pair<int, int> getNextFreeCell();
int rankConfig(const EntryConfig &config, QString filter) const;
private slots: private slots:
void lineEditReturnPressed(); void lineEditReturnPressed();
void showCalculationResultContextMenu(const QPoint &point); void showCalculationResultContextMenu(const QPoint &point);