8

Qt 开源作品 | 小伙子,给你的 Linux 系统写个 Launcher 吧

 3 years ago
source link: https://segmentfault.com/a/1190000039978418
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

今天给大家分享一下:如何用 Qt 实现一个 launcher (程序启动器)。

运行效果:

图片

github 链接:

https://github.com/alamminsal...

代码很少,C++ 部分大约 100行代码。

下面是实现过程。

1. 创建 QML 应用

在 Qt Creator 依次点击:

-> File -> New File or Project

-> Applications -> Qt Quick Application

图片

然后一路点击 next 直到 finish 。

2. 解析配置文件

Linux 系统里安装过的应用,

都会在 /usr/share/applications 目录下有相应的配置文件,

用于说明如何启动该应用,如下:

# ls -1X /usr/share/applications/
apport-gtk.desktop
apturl.desktop
arduino.desktop
audacity.desktop
bcompare.desktop
...

以 bcompare.desktop 为例:

[Desktop Entry]
Name=Beyond Compare
Exec=bcompare
Icon=bcompare
...

字段含义:

  • Name 字段是应用的名称,
  • Exec 字段是应用的启动命令,
  • Icon 字段是应用的图标名称,

解析配置文件:

// 文件:main.cpp

QVariantList apps()
{
    QVariantList ret;
    QDirIterator it(DESKTOP_FILE_SYSTEM_DIR, ...);

    while (it.hasNext()) {
        const auto filename = it.next();
        QSettings desktopFile(filename, QSettings::IniFormat);
        
        // 定位到 [Desktop Entry]
        desktopFile.beginGroup(DESKTOP_ENTRY_STRING);

        // 提取 app 信息
        AppInfo app;
        app.exec = desktopFile.value("Exec").toString().remove("\"").remove(QRegExp(" %."));
        app.icon = desktopFile.value("Icon").toString();
        app.name = desktopFile.value("Name").toString();

        // 保存 app 信息
        ret.append(QStringList{app.name, app.icon, app.exec});
    }
    return ret;
}

int main(int argc, char *argv[])
{
    [...]
    // 将解析到的 app 信息传递给 QML 前端
    engine.rootContext()->setContextProperty("apps", apps());
    [...]
}

核心就是遍历某个目录下的所有文件,解析配置文件的工作则由 QSettings 负责。

运行效果:

// 打印出所有的 app 启动信息

exec:  "xpad" 
icon:  "xpad" 
name:  "Xpad"
[...]

3. 实现整体布局

我们通过 SwipeView 来实现滑动翻页的功能,参考我之前的文章:

《Qt 官方示例 | 这几个 QML 版的 HelloWorld 你学会了吗?》

至于单独一页的布局,我们可以使用 Repeater 这个控件。

Repeater 可以帮我们生成重复的内容,这里我们规定一页最多显示 24 个 app。

通过 SwipeView + Repeater 实现布局:

// 文件: main.qml

SwipeView {
        [...]
        property int selectedIndex: 0
        Repeater {
            id: pageRepeater
            model: appPages.length

            Item {
                property var page: appPages[index]
                Grid {
                    columns: 6
                    Repeater {
                        model: page.length

                        Image {
                            source: "qrc:/images/qtlogo.png"
                        }
                    }
                }
            }
        }
    }

第一个 Repeater 用于实现生成所有的页面,

第二个 Repeater 用于生成页面里的所有 APP 的图标,这里我们先用 Qt 的 logo 来代替真实的 APP 图标。

运行效果:

图片

这时候已经支持左右滑动了,但是还没填入 APP 信息。

4. 支持显示应用图标

在 main() 里,我们设置了一个名为 apps 的属性,它包含了所有 APP 的信息:

engine.rootContext()->setContextProperty("apps", apps());

我们需要在前端界面中使用 APP 的图标替换掉 Qt logo。

显示 APP 图标:

// 文件:main.qml

Grid {
    [...]
    Repeater {
        model: page !== undefined ? page.length : 0

        Column {
            Image {
                property var app: page[index]
                // APP 图标
                source: "image://icons/" + app[1]
                [...]
            }

            Label {
                property var app: page[index]
                id: label
                // APP 的名称
                text: app[0]
                [...]
            }
        }
    }
}

改动非常少。

运行效果:

图片

这时仅支持显示图标,但是仍不支持鼠标选中。

5. 支持选中应用

选中应用需要添加对鼠标 hover 事件的处理。

当鼠标移动到某个图标上时,Qt 会捕获到鼠标 hover 事件,并传递给当前焦点所在的控件上。

我们将 APP 的界面代码抽取出来,单独放在 AppEntry.qml,使其成为一个独立的控件,

然后再在其中添加对鼠标 hover 事件的处理。

图标控件:AppEntry.qml

/// 文件:AppEntry.qml 

Pane {
    id: root
    property var app
    [...]

    // 当鼠标移动到该图标时,发送信号 hovered()
    signal hovered()
    MouseArea {
        [...]
        onHoveredChanged: {
            if (hovered) {
                root.hovered()
            }
        }
    }

    Column {
        anchors.fill: parent

        Image {
            source: "image://icons/" + app[1]
            [...]
        }

        Label {
            [...]
        }
    }
}

在 main.qml 中接受到 AppEntry 控件的 hovered 信号时,

需改变其背景色以提示用户已选中图标。

// 文件:main.qml

Repeater {
    model: page.length

    AppEntry {
        app: page[index]
        [...]
        // selected 改变时,背景色会变化
        selected: swipeView.selectedIndex === index
        onHovered: {
            swipeView.select(index)
        }
        [...]
    }
}

运行效果:

图片

这是已经能显示选中状态了,但是仍无法启动应用。

6. 支持启动应用

在 Qt 里,可以使用 QProcess 来创建进程。

这里我们创建一个 QProcess 的子类用于运行 APP。

QProcess 的子类:

// 文件:process.cpp
void Process::start(const QString &program, const QVariantList &arguments)
{
    [...]
    QProcess::startDetached(program);
}

// 文件:process.h
class Process : public QProcess
{
    Q_OBJECT

public:
    Process(QObject *parent = nullptr);

    Q_INVOKABLE void start(const QString &program, const QVariantList &arguments = {});
};

// 文件:main.cpp
int main(int argc, char *argv[])
{
    // 将 Process 的实例传递给前端
    engine.rootContext()->setContextProperty("proc", new Process(&engine));
}

前端处理点击事件:

// 文件:AppEntry.qml

signal clicked()
MouseArea {
    [...]
    onClicked: {
        root.clicked()
    }
}

当用户点击图标时,AppEntry 控件会发出 clicked() 信号。

// 文件:main.qml

AppEntry {
    app: page[index]
    [...]
    // 主窗口启动 APP
    onClicked: {
        exec(app[2])
    }
    [...]
}

function exec(program) {
    console.debug("Exec: " + program)
    proc.start(program)
    Qt.quit();
}

最后调用到 Process::start(),启动 APP。

运行效果:

图片

怎么样,你们学会了吗?

—— The End ——

推荐阅读:

专辑 | Linux 驱动开发

专辑 | Linux 系统编程

专辑 | 每天一点 C

专辑 | Qt 入门

想进交流群?

后台回复【加群】,我拉你进群。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK