├── .clang-format ├── .gitignore ├── LICENSE ├── README.md ├── qml-launcher.pro ├── qml ├── AppEntry.qml ├── main.qml ├── qml.qrc └── qtquickcontrols2.conf └── src ├── imageprovider.cpp ├── imageprovider.h ├── main.cpp ├── process.cpp └── process.h /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | Language: Cpp 3 | # BasedOnStyle: LLVM 4 | AccessModifierOffset: -4 5 | AlignAfterOpenBracket: DontAlign 6 | AlignConsecutiveAssignments: false 7 | AlignConsecutiveDeclarations: false 8 | AlignEscapedNewlinesLeft: false 9 | AlignOperands: false 10 | AlignTrailingComments: false 11 | AllowAllParametersOfDeclarationOnNextLine: false 12 | AllowShortBlocksOnASingleLine: true 13 | AllowShortCaseLabelsOnASingleLine: true 14 | AllowShortFunctionsOnASingleLine: All 15 | AllowShortIfStatementsOnASingleLine: true 16 | AllowShortLoopsOnASingleLine: true 17 | AlwaysBreakAfterDefinitionReturnType: None 18 | AlwaysBreakAfterReturnType: None 19 | AlwaysBreakBeforeMultilineStrings: false 20 | AlwaysBreakTemplateDeclarations: false 21 | BinPackArguments: true 22 | BinPackParameters: true 23 | BraceWrapping: 24 | AfterClass: false 25 | AfterControlStatement: false 26 | AfterEnum: false 27 | AfterFunction: false 28 | AfterNamespace: false 29 | AfterObjCDeclaration: false 30 | AfterStruct: false 31 | AfterUnion: false 32 | BeforeCatch: false 33 | BeforeElse: false 34 | IndentBraces: false 35 | BreakBeforeBinaryOperators: None 36 | BreakBeforeBraces: Attach 37 | BreakBeforeTernaryOperators: false 38 | BreakConstructorInitializersBeforeComma: true 39 | ColumnLimit: 0 40 | CommentPragmas: '^ IWYU pragma:' 41 | ConstructorInitializerAllOnOneLineOrOnePerLine: false 42 | ConstructorInitializerIndentWidth: 8 43 | ContinuationIndentWidth: 4 44 | Cpp11BracedListStyle: true 45 | DerivePointerAlignment: false 46 | DisableFormat: false 47 | ExperimentalAutoDetectBinPacking: false 48 | ForEachMacros: [ foreach, Q_FOREACH, BOOST_FOREACH ] 49 | IncludeCategories: 50 | #core 51 | - Regex: '^<(core)/' 52 | Priority: 9 53 | #components 54 | - Regex: '^<(components)/' 55 | Priority: 8 56 | #gui 57 | - Regex: '^<(gui)/' 58 | Priority: 7 59 | #Qt 60 | - Regex: '^ -1) { 56 | return true; 57 | } 58 | } 59 | return false; 60 | } 61 | 62 | function refresh(){ 63 | var page = 0 64 | var itemsPerPage = 24 65 | console.log(searchField.text) 66 | appPages = []; 67 | appPages[page] = [] 68 | 69 | pageRepeater.model = 0 70 | 71 | for (var i in apps) { 72 | if (appPages[page].length >= itemsPerPage) 73 | page++ 74 | 75 | if (!appPages[page]) 76 | appPages[page] = [] 77 | 78 | var app = apps[i] 79 | if (filter(app, searchField.text)) 80 | appPages[page].push(app) 81 | } 82 | 83 | pageRepeater.model = appPages.length 84 | } 85 | 86 | Component.onCompleted: { 87 | x= Qt.application.screens[0].virtualX 88 | refresh() 89 | searchField.forceActiveFocus() 90 | } 91 | 92 | SwipeView { 93 | Keys.onEscapePressed: Qt.quit() 94 | id: swipeView 95 | anchors.fill: parent 96 | 97 | property int selectedIndex: 0 98 | 99 | function select(index) { 100 | selectedIndex = index 101 | } 102 | 103 | Keys.onUpPressed: { 104 | var place = selectedIndex - rootWindow.columns 105 | if (place >= 0) 106 | selectedIndex = place 107 | } 108 | 109 | Keys.onDownPressed: { 110 | var place = selectedIndex + rootWindow.columns 111 | if (place < currentItem.count()) 112 | selectedIndex = place 113 | } 114 | 115 | Keys.onRightPressed: { 116 | selectedIndex = Math.min(selectedIndex + 1, currentItem.count() - 1) 117 | } 118 | Keys.onLeftPressed: { 119 | selectedIndex = Math.max(0, selectedIndex - 1) 120 | } 121 | 122 | Keys.onReturnPressed: { 123 | var app = appPages[currentIndex][selectedIndex] 124 | if (app) { 125 | exec(app[2]) 126 | } 127 | } 128 | 129 | onFocusChanged: { 130 | if (focus) { 131 | select(0) 132 | } 133 | } 134 | 135 | Repeater { 136 | id: pageRepeater 137 | model: appPages.length 138 | 139 | Item { 140 | id: pageContainer 141 | function count() { 142 | return page.length; 143 | } 144 | 145 | property var page: appPages[index] 146 | 147 | MouseArea { 148 | anchors.fill: parent 149 | onClicked: Qt.quit() 150 | } 151 | 152 | Grid { 153 | spacing: rootWindow.height * 0.01 154 | anchors.centerIn: parent 155 | columns: rootWindow.columns 156 | horizontalItemAlignment: Grid.AlignHCenter 157 | 158 | populate: Transition { 159 | id: trans 160 | SequentialAnimation { 161 | NumberAnimation { 162 | properties: "opacity"; 163 | from: 1 164 | to: 0 165 | duration: 0 166 | } 167 | PauseAnimation { 168 | duration: (trans.ViewTransition.index - 169 | trans.ViewTransition.targetIndexes[0]) * 20 170 | } 171 | ParallelAnimation { 172 | NumberAnimation { 173 | properties: "opacity"; 174 | from: 0 175 | to: 1 176 | duration: 600 177 | easing.type: Easing.OutCubic 178 | } 179 | NumberAnimation { 180 | properties: "y"; 181 | from: trans.ViewTransition.destination.y + 50 182 | duration: 620 183 | easing.type: Easing.OutCubic 184 | } 185 | } 186 | } 187 | } 188 | 189 | Repeater { 190 | model: page !== undefined ? page.length : 0 191 | 192 | AppEntry { 193 | app: page !== undefined ? page[index] : ["undefined", "", ""] 194 | height: pageContainer.height * 0.19 195 | width: height 196 | padding: 10 197 | selected: swipeView.selectedIndex === index 198 | onHovered: { 199 | swipeView.select(index) 200 | } 201 | onClicked: { 202 | exec(app[2]) 203 | } 204 | } 205 | } 206 | } 207 | } 208 | } 209 | } 210 | 211 | function exec(program) { 212 | console.debug("Exec: " + program) 213 | proc.start(program) 214 | Qt.quit(); 215 | } 216 | 217 | footer: ToolBar { 218 | background: Item{} 219 | height: rootWindow.height * 0.05 220 | PageIndicator { 221 | count: swipeView.count 222 | currentIndex: swipeView.currentIndex 223 | anchors.centerIn: parent 224 | } 225 | } 226 | } 227 | -------------------------------------------------------------------------------- /qml/qml.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | main.qml 4 | qtquickcontrols2.conf 5 | AppEntry.qml 6 | 7 | 8 | -------------------------------------------------------------------------------- /qml/qtquickcontrols2.conf: -------------------------------------------------------------------------------- 1 | ; This file can be edited to change the style of the application 2 | ; See Styling Qt Quick Controls 2 in the documentation for details: 3 | ; http://doc.qt.io/qt-5/qtquickcontrols2-styles.html 4 | 5 | [Controls] 6 | Style=Material 7 | 8 | [Universal] 9 | Theme=Light 10 | ;Accent=Steel 11 | 12 | [Material] 13 | Theme=Dark 14 | Accent=Blue 15 | ;Primary=BlueGray 16 | -------------------------------------------------------------------------------- /src/imageprovider.cpp: -------------------------------------------------------------------------------- 1 | #include "imageprovider.h" 2 | 3 | #include 4 | #include 5 | 6 | ImageProvider::ImageProvider() 7 | : QQuickImageProvider(QQuickImageProvider::Pixmap) { 8 | } 9 | 10 | QPixmap ImageProvider::requestPixmap(const QString &id, QSize *, const QSize &requestedSize) { 11 | QIcon icon = QIcon::fromTheme(id); 12 | 13 | if (requestedSize.isValid()) 14 | return icon.pixmap(requestedSize); 15 | 16 | return icon.pixmap(128, 128); 17 | } 18 | -------------------------------------------------------------------------------- /src/imageprovider.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | class ImageProvider : public QQuickImageProvider { 6 | public: 7 | explicit ImageProvider(); 8 | QPixmap requestPixmap(const QString &id, QSize *size, const QSize &requestedSize) final; 9 | }; 10 | -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include "imageprovider.h" 13 | #include "process.h" 14 | 15 | struct AppInfo { 16 | QString name; 17 | QString icon{"application"}; 18 | QString exec; 19 | }; 20 | 21 | constexpr auto DESKTOP_FILE_SYSTEM_DIR = "/usr/share/applications"; 22 | constexpr auto DESKTOP_FILE_USER_DIR = "%1/.local/share/applications"; 23 | constexpr auto DESKTOP_ENTRY_STRING = "Desktop Entry"; 24 | 25 | class SettingsGroupRaii { 26 | public: 27 | SettingsGroupRaii(QSettings &settings, const QString &groupName) 28 | : m_settings(settings) { 29 | m_settings.beginGroup(groupName); 30 | } 31 | 32 | ~SettingsGroupRaii() { 33 | m_settings.endGroup(); 34 | } 35 | 36 | private: 37 | QSettings &m_settings; 38 | }; 39 | 40 | QVariantList createAppsList(const QString &path) { 41 | QDirIterator it(path, {"*.desktop"}, QDir::NoFilter, QDirIterator::Subdirectories); 42 | QVariantList ret; 43 | 44 | while (it.hasNext()) { 45 | const auto filename = it.next(); 46 | QSettings desktopFile(filename, QSettings::IniFormat); 47 | 48 | if (!desktopFile.childGroups().contains(DESKTOP_ENTRY_STRING)) 49 | continue; 50 | 51 | SettingsGroupRaii raii(desktopFile, DESKTOP_ENTRY_STRING); 52 | 53 | if (desktopFile.contains("NoDisplay")) 54 | if (desktopFile.value("NoDisplay").toBool() == 1) 55 | continue; 56 | 57 | AppInfo app; 58 | app.exec = desktopFile.value("Exec").toString().remove("\"").remove(QRegExp(" %.")); 59 | app.icon = desktopFile.value("Icon", "application").toString(); 60 | app.name = desktopFile.value("Name").toString(); 61 | 62 | ret.append(QStringList{app.name, app.icon, app.exec}); 63 | } 64 | 65 | return ret; 66 | } 67 | 68 | QVariantList apps() { 69 | QVariantList ret; 70 | ret.append(createAppsList(DESKTOP_FILE_SYSTEM_DIR)); 71 | ret.append(createAppsList(QString(DESKTOP_FILE_USER_DIR).arg(QDir::homePath()))); 72 | return ret; 73 | } 74 | 75 | int main(int argc, char *argv[]) { 76 | qputenv("QT_QUICK_CONTROLS_STYLE", "material"); 77 | 78 | QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); 79 | QGuiApplication app(argc, argv); 80 | 81 | QQmlApplicationEngine engine; 82 | engine.addImageProvider("icons", new ImageProvider()); 83 | engine.rootContext()->setContextProperty("apps", apps()); 84 | engine.rootContext()->setContextProperty("proc", new Process(&engine)); 85 | engine.load(QUrl(QLatin1String("qrc:/main.qml"))); 86 | if (engine.rootObjects().isEmpty()) 87 | return -1; 88 | 89 | return app.exec(); 90 | } 91 | -------------------------------------------------------------------------------- /src/process.cpp: -------------------------------------------------------------------------------- 1 | #include "process.h" 2 | 3 | #include 4 | 5 | Process::Process(QObject *parent) 6 | : QProcess(parent) { 7 | } 8 | 9 | void Process::start(const QString &program, const QVariantList &arguments) { 10 | QStringList args; 11 | 12 | qDebug() << "Running" << program; 13 | // convert QVariantList from QML to QStringList for QProcess 14 | 15 | for (const auto &arg : arguments) 16 | args << arg.toString(); 17 | 18 | //unused args 19 | 20 | QProcess::startDetached(program); 21 | } 22 | 23 | QByteArray Process::readAll() { 24 | return QProcess::readAll(); 25 | } 26 | -------------------------------------------------------------------------------- /src/process.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | class Process : public QProcess { 7 | Q_OBJECT 8 | 9 | public: 10 | Process(QObject *parent = nullptr); 11 | 12 | Q_INVOKABLE void start(const QString &program, const QVariantList &arguments = {}); 13 | Q_INVOKABLE QByteArray readAll(); 14 | }; 15 | --------------------------------------------------------------------------------