diff --git a/README.md b/README.md new file mode 100644 index 0000000..748e114 --- /dev/null +++ b/README.md @@ -0,0 +1,142 @@ + + +# 概述 + +本程序主要用来替换 `ukui-screensaver` 程序 + +## 组件 + +- medr-screensaver-backend + + 后台程序, 开机(登陆桌面)运行. + 监控系统 session 状态,当进入 idle 状态后触发 dialog 程序. + +- medr-screensaver-command + + 手动调用屏保功能 + +- medr-screensaver-dialog + + 被 backend 调用进入锁屏状态, 主要实现 --session-idle 功能 + +### backend + +#### 后端程序需求分析 + +- 开机(登陆)启动 +- 监控 session 状态并触发 dialog 程序 + +#### 后端程序代码设计 + +- /etc/xdg/autostart/medr-screensaver.desktop + + 同时要禁用 ukui-screensaver + +- 监听 dbus 信号 `org.gnome.SessionManager.Presence.StatusChanged` + + 同时, 还要监控 gsettings 设置 `org.mate.session.idle-delay` 并将设置写入到 /var/run/medr-lightdm + +### command + +#### command 程序需求分析 + +- 支持 --lock 命令 +- 支持 --query 命令 + +#### command 程序代码设计 + +- 调用 dialog --lock 即可 +- TODO: 后续修改实现 + +### dialog + +#### dialog 程序需求分析 + +- 支持 --lock 命令 +- 支持 --screensaver 命令 +- 支持 --session-idle 命令 + +#### dialog 程序代码设计 + +- 不做任何配置, 直接调用 `dm-tool lock` +- 直接调用 `xset dpms force off` +- 配置后调用 `dm-tool lock` + 如何配置 + + - 设置 lightdm 调用脚本 + + ```conf + # 本文件需安装在 /etc/lightdm/lightdm.conf.d/ 目录下 + [SeatDefaults] + display-setup-script=/usr/local/bin/medr-lightdm-dpms-enable + session-setup-script=/usr/local/bin/medr-lightdm-dpms-disable + ``` + + - 实现脚本 `/usr/local/bin/medr-lightdm-dpms-enable` + + 1. 根据用户提供的设置来配置锁屏时长 + + 2. 判断用户配置以执行`xset dpms force off` + + 由于是 root 用户运行 lightdm, 需要由用户态程序`dialog`来设置 + 用来通信的文件为 `/var/run/medr-lightdm-lock` + 如果需要黑屏, 写入内容 `blank` + +## 部署 + +### 安装 + +请按照如下说明安装 + +```sh +# 以下命令请以root权限执行! + +# lightdm 配置 +cp backend/50-medr-dpms.conf /etc/lightdm/lightdm.conf.d/ +cp medr-lightdm-dpms-enable /usr/local/bin +cp medr-lightdm-dpms-disable /usr/local/bin + +# ukui屏保 +cp backend/medr-screensaver-backend.py /usr/local/bin/ukui-screensaver-backend +cp command/medr-screensaver-dialog.py /usr/local/bin/ukui-screensaver-dialog + +# 额外工作 +mv /usr/bin/ukui-screensaver-backend{,.bak} +mv /usr/bin/ukui-screensaver-dialog{,.bak} + +# 支持开始菜单的锁屏 +cp /usr/local/bin/{ukui-screensaver-dialog,mate-screensaver-command} + +# 准备一些额外的文件 +touch /var/run/medr-lightdm +touch /var/run/medr-lightdm-lock +touch /tmp/medr-screensaver.lock +``` + +### 卸载 + +```sh +# 以下命令请以root权限执行! + +# 删除medr屏保 +rm /usr/local/bin/ukui-screensaver-backend +rm /usr/local/bin/ukui-screensaver-dialog +rm /usr/local/bin/mate-screensaver-command + +# 恢复麒麟原生屏保 +mv /usr/bin/ukui-screensaver-backend{.bak,} +mv /usr/bin/ukui-screensaver-dialog{.bak,} + +# 删除配置文件 +rm /etc/lightdm/lightdm.conf.d/50-medr-dpms.conf +rm /usr/local/bin/medr-lightdm-dpms-{enable,disable} +rm /var/run/medr-lightdm{,-lock} +rm /tmp/medr-screensaver.lock +``` diff --git a/backend/medr-lightdm-dpms-disable b/backend/medr-lightdm-dpms-disable index c7c4968..63ebd18 100755 --- a/backend/medr-lightdm-dpms-disable +++ b/backend/medr-lightdm-dpms-disable @@ -3,16 +3,25 @@ # @Author: Zhang Yueqian zhangyueqian@antiy.cn # @Date: 2022-11-01 10:24:40 # @LastEditors: Zhang Yueqian zhangyueqian@antiy.cn -# @LastEditTime: 2022-11-01 10:28:04 +# @LastEditTime: 2022-11-02 15:09:16 # @FilePath: /screensaver/backend/medr-lightdm-dpms-disable # @Description: 在lightdm(greeter)登陆之后禁用屏幕休眠功能 # # 本程序需安装在 /usr/local/bin/ 目录下, 配置到 /etc/lightdm/50-medr-dpms.conf 文件中 ### +# 是否锁屏判断文件 +LOCK_FILE=/var/run/medr-lightdm-lock + # 入口函数, 调用xset function main() { + # 通知其他进程已解锁 + echo '' >$LOCK_FILE + # 等待X启动(大概?) + while true; do + xset q 2>&1 >/dev/null && break + done sleep 10 # 将超时配置设为0(禁用) diff --git a/backend/medr-lightdm-dpms-enable b/backend/medr-lightdm-dpms-enable index 5af8f87..35fa3f1 100755 --- a/backend/medr-lightdm-dpms-enable +++ b/backend/medr-lightdm-dpms-enable @@ -3,7 +3,7 @@ # @Author: Zhang Yueqian zhangyueqian@antiy.cn # @Date: 2022-11-01 09:47:56 # @LastEditors: Zhang Yueqian zhangyueqian@antiy.cn -# @LastEditTime: 2022-11-01 13:45:07 +# @LastEditTime: 2022-11-02 15:08:56 # @FilePath: /screensaver/backend/medr-lightdm-dpms-enable # @Description: 在lightdm(greeter)界面启用屏幕休眠功能 # @@ -13,6 +13,9 @@ # 休眠时间设置文件路径 TIMEOUT_FILE=/var/run/medr-lightdm +# 是否锁屏判断文件 +LOCK_FILE=/var/run/medr-lightdm-lock + # 默认休眠时间(分钟) DEFAULT_TIMEOUT=10 # 确认文件存在 @@ -22,7 +25,6 @@ fi # 获取休眠超时配置, 如果未设置或设置格式非数字, 则返回默认配置 $DEFAULT_TIMEOUT function get_timeout() { - # 首先保证TIMEOUT文件可被修改 chmod 777 $TIMEOUT_FILE @@ -35,17 +37,46 @@ function get_timeout() { esac } +# 获取调用方有没有要求黑屏 +function get_blank() { + # 确保文件可修改 + chmod 777 $LOCK_FILE + + # 读取命令 + LOCK_CMD=$(cat $LOCK_FILE) + case $LOCK_CMD in + "BLANK" | "blank") echo "yes" ;; + *) echo "no" ;; + esac + + # 清空命令文件 + echo 'done' >$LOCK_FILE +} + # 入口函数, 读取设置并调用xset function main() { # 读取超时配置并转换为秒 TIMEOUT=$(get_timeout) TIMEOUT=$((TIMEOUT * 60)) + # 读取是否黑屏 + BLANK_SCREEN=$(get_blank) + # 必须的, 似乎是需要等待X启动 + while true; do + xset q 2>&1 >/dev/null && break + sleep 1 + done sleep 10 # 利用X的dpms功能触发屏幕休眠 xset dpms 0 0 $TIMEOUT + + sleep 10 + + if [ x"$BLANK_SCREEN" == "xyes" ]; then + xset dpms force off + fi } main & diff --git a/backend/medr-screensaver-backend.py b/backend/medr-screensaver-backend.py index 37cbfe3..e833bfa 100755 --- a/backend/medr-screensaver-backend.py +++ b/backend/medr-screensaver-backend.py @@ -34,6 +34,7 @@ from gi.repository import GObject, Gio BUS_NAME = "org.ukui.ScreenSaver" TIMEOUT_FILE = "/var/run/medr-lightdm" +LOCK_FILE = "/var/run/medr-lightdm-lock" DEFAULT_TIMEOUT = 10 @@ -52,28 +53,26 @@ class Service(dbus.service.Object): @dbus.service.method(BUS_NAME, in_signature="", out_signature="b") def GetLockState(self): - print(" sending out lockstate: ", self._state) - return self._state + print("GetLockState: %s" % self._locked) + return self._locked @dbus.service.method(BUS_NAME, in_signature="b", out_signature="") def SetLockState(self, state): - print(" got lock state:", state) - self._state = state + print("SetLockState: %s" % state) + self._locked = state @dbus.service.method(BUS_NAME, in_signature="", out_signature="") def Lock(self): - print(" lock command called!") + print("Lock") + self._locked = True os.system("ukui-screensaver-dialog --lock") @dbus.service.method(BUS_NAME, in_signature="", out_signature="") def Quit(self): - print(" got quit request.") self._loop.quit() @dbus.service.signal(BUS_NAME) def SessionIdle(self): - print(" signal triggered") - os.system("wall session idle triggerd!") os.system("ukui-screensaver-dialog --session-idle") @@ -83,10 +82,29 @@ bus_name = dbus.service.BusName(BUS_NAME, bus=bus) service = Service(bus_name, "/") +def isLocked(): + """确认当前是否是锁定状态. + + Returns: + boolean: 是否锁定. + """ + if not os.path.exists(LOCK_FILE): + return False + + status = "" + with open(LOCK_FILE, "r") as fp: + status = fp.readline().strip() + + if len(status) != 0 and status == "done": + return True + + return False + + def sessionStatusChanged(sessionStatus): - print("---- Caught signal ----") + print("---- Caught signal of status %s ----" % sessionStatus) - if sessionStatus == 3: + if sessionStatus == 3 and (not isLocked()): service.SessionIdle() print("\n") diff --git a/dialog/medr-screensaver-dialog.py b/dialog/medr-screensaver-dialog.py index afa06eb..d4dab65 100755 --- a/dialog/medr-screensaver-dialog.py +++ b/dialog/medr-screensaver-dialog.py @@ -8,21 +8,44 @@ LastEditTime: 2022-10-31 11:34:16 FilePath: /screensaver/dialog/medr-screensaver-dialog.py Description: 屏保程序 for MEDR项目. 本文件为负责锁屏的部分. -提供实际锁屏功能, 支持两个参数: --lock 和 --session-idle -锁屏包括两个含义: 熄灭屏幕 & 触发lightdm锁定 ---lock: 熄灭屏幕并直接锁定lightdm ---session-idle: 首先熄灭屏幕, 然后根据系统设置, 如果系统设置为idle后锁屏则锁屏 +提供实际锁屏功能, 支持三个参数: --lock 、--session-idle 和 --screensaver + 锁屏包括两个含义: 熄灭屏幕 & 触发lightdm锁定 + --lock: 直接锁定lightdm + --session-idle: 根据系统设置进入session-idle模式 + 如果设置为空闲时激活屏保但不锁定, 那么关闭屏幕 + 如果设置了空闲激活屏保且锁定, 那么执行锁定, 通知lighdm执行黑屏 + 如果未设置空闲屏保但设置了锁定, 那么执行锁定, 不通知lightdm黑屏 + --screensaver: 首先熄灭屏幕, 然后锁定屏幕 + +本应用为单例模式, 启动后当前用户只允许一个实例 +实现机制: + 首先获取sock文件的锁 + 如果获取失败, 表明已经运行一个了, 那么执行后将命令通过socket发送给当前活跃进程后退出. + 如果获取成功, 说明是新运行, 那么启动server监听socket,并且执行命令 """ from argparse import ArgumentParser +import fcntl from multiprocessing.managers import BaseManager import os, socket +import atexit +from time import sleep +import gi -MEDR_PIPE = "/tmp/medr-screensaver.sock" -_command = [] +gi.require_version("Gtk", "3.0") +from gi.repository import GObject, Gio + +MEDR_LOCK = "/tmp/medr-screensaver.lock" +TIMEOUT_FILE = "/var/run/medr-lightdm" +LOCK_FILE = "/var/run/medr-lightdm-lock" def parse_args(): + """解析命令行参数 + + Returns: + (ArgumentParser.Namespace, str): 解析后的命令行参数及帮助信息 + """ argparser = ArgumentParser(add_help=False) argparser.add_argument("-h", "--help", dest="help", action="store_true", help="Show this message and exit.") @@ -40,7 +63,42 @@ def parse_args(): return argparser.parse_args(), argparser.format_help() +def check_singleton(): + """检查是否是单例运行""" + if not os.path.exists(MEDR_LOCK): + open(MEDR_LOCK, "w").close() + + fd = open(MEDR_LOCK, "w") + try: + fcntl.flock(fd.fileno(), fcntl.LOCK_EX | fcntl.LOCK_NB) + except IOError: + fd.close() + return False + + atexit.register(lambda: fd.close()) + return True + + +def write_lightdm(cmd): + try: + with open(LOCK_FILE, "w") as fp: + fp.write(cmd) + except Exception: + return + + def action(args, help): + """执行命令 + + Args: + args (ArgumentParser.Namespace): ArgumentParser解析后的参数 + help (str): 帮助信息 + """ + first_run = check_singleton() + if not first_run: + print("Already running") + return + if args.help: print(help) return @@ -50,64 +108,45 @@ def action(args, help): return if args.session_idle: - # if idle-lock checked, locks + # 从Gio获取gsettings设置 + # 1 判断是否设置了 /org/ukui/screensaver/idle-activation-enabled (空闲时是否激活屏保) + screensaver = Gio.Settings("org.ukui.screensaver") + idle_activation = screensaver.get_value("idle-activation-enabled").get_boolean() + # 2 判断是否设置了 /org/ukui/screensaver/lock-enabled (空闲时是否锁定屏幕) + lock_enabled = screensaver.get_value("lock-enabled").get_boolean() + + # idle_activation 0 0 1 1 + # lock_enabled 0 1 0 1 + # 动作 无 锁 黑不锁 黑锁 + + if not idle_activation: + if not lock_enabled: + # 既不黑屏,也不锁定 + return + else: + # 只锁定不黑屏 + os.system("dm-tool lock") + else: + if not lock_enabled: + # 黑屏, 不锁定 + os.system("xset dpms force off") + else: + # 黑屏且锁定 + os.system("xset dpms force off") + sleep(1) + write_lightdm("blank") + os.system("dm-tool lock") + return if args.screensaver: - os.system("xset dpms force off") + write_lightdm("blank") + os.system("dm-tool lock") return print(help) -def get_command(): - return _command - - -def singleton(): - # 通过client连接已有的BaseManager, 如果失败则自己启动一个, 成功则退出 - BaseManager.register("medr-screensaver") - m = BaseManager(address=("127.0.0.1", 5000), authkey=b"medr-screensaver") - try: - print("Trying connect to %s" % m) - m.connect() - except Exception as e: - print(e) - else: - print("Failed, trying start server") - BaseManager.register("medr-screensaver", callable=get_command) - m = BaseManager(address=("127.0.0.1", 5000), authkey=b"medr-screensaver") - m.start() - - -# 必须先删除 -# try: -# os.unlink(MEDR_PIPE) -# except OSError: -# if os.path.exists(MEDR_PIPE): -# raise - -# 指定协议 -server = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) -try: - server.bind(MEDR_PIPE) -except socket.error as e: - # running - print("Error: ", e) - print("Running") -finally: - # 监听 - server.listen(1) - -clientsocket, address = server.accept() -# 接收消息 -data = clientsocket.recv(1024) -print(data) -# 关闭socket -clientsocket.close() -server.close() - if __name__ == "__main__": args, msg = parse_args() - # singleton() action(args, msg)