整体逻辑
xscreensaver程序会等待键盘和鼠标空闲一段时间,然后运行随机选择的图形演示。只要有任何鼠标或键盘活动,它就会关闭。这个程序可以锁定你的终端,以防止其他人使用它,虽然它的默认操作模式只是在你的屏幕上显示漂亮的图片时,它不使用。它还提供配置和控制您的显示器的节能功能。
Deepin-screensaver:常驻服务,负责接受x.org screensaver 的消息,比如Start,Preview 等DBus消息,当受到消息时,建立渲染窗口,并拉起当前用户所选屏保应用,并将渲染窗口window id传给应用,应用根据指定window id进行渲染。
源码片段
deepin-screensaver会根据屏幕个数建立windowmap
,并实例化渲染窗口
void DBusScreenSaver::ensureWindowMap()
{
if (!m_windowMap.isEmpty())
return;
connect(qGuiApp, &QGuiApplication::screenAdded, this, &DBusScreenSaver::onScreenAdded, Qt::UniqueConnection);
connect(qGuiApp, &QGuiApplication::screenRemoved, this, &DBusScreenSaver::onScreenRemoved, Qt::UniqueConnection);
for (QScreen *s : qGuiApp->screens()) {
onScreenAdded(s);
}
}
渲染窗口:
class ScreenSaverView : public QQuickView
{
Q_OBJECT
public:
explicit ScreenSaverView(QWindow *parent = nullptr);
~ScreenSaverView();
bool start(const QString &filePath);
void stop();
signals:
void inputEvent(QEvent::Type type);
private:
bool event(QEvent *event) override;
QProcess *m_process = nullptr;
friend class ScreenSaverWindow;
};
从代码可以看出,渲染窗口提供的是继承QQuickView
,QQuickView可以直接加载QML文件,无需执行文件,从下面的拉屏保应用的代码也可以看出,屏保程序是可以直接加载qml文件。
拉起进程:
bool ScreenSaverView::start(const QString &filePath)
{
stop();
if (filePath.endsWith(".qml")) {
setSource(QUrl(filePath));
} else {
if (!m_process) {
m_process = new QProcess(this);
m_process->setProcessChannelMode(QProcess::ForwardedChannels);
}
create();
m_process->start(filePath, {"-window-id", QString::number(winId())}, QIODevice::ReadOnly);
if (!m_process->waitForStarted(3000)) {
qDebug() << "Failed on start:" << m_process->program() << ", error string:" << m_process->errorString();
return false;
}
}
return true;
}
到这里deepin-screensaver的任务就完成了,剩下的就需要屏保硬保根据deepin-screensaver传入的-window-id
进行渲染。
屏保调试程序
deepin和UOS自带了二进制screensaver工具,可以直接通过命令行使用
➜ ~ deepin-screensaver -h
Usage: deepin-screensaver [options] [name]
Options:
-d, --dbus Register DBus service.
-s, --start Start screen saver.
-h, --help Displays this help.
-v, --version Displays version information.
Arguments:
screensaer-name Use the screensaver application.
我们可以直接deep-screensaver your_app
的方式加载自己的屏保,类似于windows预览功能。
屏保的接入方式
rcc文件
这种方法是基于Qt开发环境,将资源文件打包成app.rcc文件,放到/usr/lib/deepin-screensaver/resources目录下,deepin-screensaver程序则会自动加载该rcc文件,具体操作方式:如何开发一款属于自己的时尚屏保应用
通过ScreenHack框架接入
xscreensaver的作者提供了一套框架及一些屏保的demo供开发者参考,介入方式:screenhack
内嵌窗口
代码实现
#include <QGuiApplication>
#include <QApplication>
#include <QQuickView>
#include <QX11Info>
#include <QtX11Extras>
#include <QVariant>
#include <QCommandLineParser>
#include <X11/Xlib.h>
#include <QWindow>
#include <QDebug>
#include "instance.h"
int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app(argc, argv);
qmlRegisterType<Instance>("FrontEnd",1,0, "Instance");
QCommandLineParser parser;
QString windowId;
QQuickView s;
s.setSource(QUrl("qrc:/rectwindow.qml")); // 加载自己需要的文件
XWindowAttributes xwa;
parser.setApplicationDescription("Test helper");
parser.addHelpOption();
parser.addVersionOption();
parser.addPositionalArgument("source", QCoreApplication::translate("main", "Screensaver for ukui-screensaver"));
parser.setSingleDashWordOptionMode(QCommandLineParser::ParseAsLongOptions);
parser.addOptions({
{{"r", "root"},
QCoreApplication::translate("main", "show on root window")},
{{"w", "window-id"},
QCoreApplication::translate("main", "show on window."),
QCoreApplication::translate("main", "window id")},
});
parser.process(app);
bool onWindow = parser.isSet("window-id");
bool onRoot = parser.isSet("root");
double scale = 1;
QScreen *screen = QApplication::primaryScreen();
scale = screen->devicePixelRatio();
if(onWindow){
windowId = parser.value("window-id");
WId wid = windowId.toULong();
QWindow* window = QWindow::fromWinId(wid);
window->setProperty("_q_embedded_native_parent_handle",QVariant(wid));
s.winId();
s.setParent(window);
XGetWindowAttributes (QX11Info::display(), wid, &xwa);
//获取屏保所在屏幕对应的缩放比例。
for(auto screen : QGuiApplication::screens())
{
QPoint pos(xwa.x,xwa.y);
if(screen->geometry().contains(pos)){
scale = screen->devicePixelRatio();
}
}
s.resize(xwa.width/scale + 1,xwa.height/scale + 1);
s.setX(0);
s.setY(0);
s.show();
}
else if(onRoot){
WId wid = QX11Info::appRootWindow();
QWindow* window = QWindow::fromWinId(wid);
window->setProperty("_q_embedded_native_parent_handle",QVariant(wid));
s.winId();
s.setParent(window);
XGetWindowAttributes (QX11Info::display(), wid, &xwa);
qDebug()<<"xwa.width = "<<xwa.width<<"xwa.height = "<<xwa.height;
s.resize(xwa.width/scale + 1,xwa.height/scale + 1);
s.setX(0);
s.setY(0);
s.show();
}
else{
s.resize(1366,768);
s.show();
}
return app.exec();
}
这里根据自己的需要加载不同的qml
文件即可。
接入方式对比
方式 | 优点 | 缺点 |
---|---|---|
rcc文件 | 直接编写QML文件 开发效率高 对屏保的支持程度好 | 无法调用c++代码 |
内嵌窗口 | 与正常使用Qt开发差异不大 | 无 |
ScreenHack框架 | 官方提供的框架,只需要关注于绘制细节 | 开发效率低 |
综合评估,采用内嵌窗口的方式为最佳方案