feat: 第一个工作版本

master
Zhang Yueqian 2 years ago
parent ee1c2416f2
commit 336c45cba4
  1. 142
      README.md
  2. 11
      backend/medr-lightdm-dpms-disable
  3. 35
      backend/medr-lightdm-dpms-enable
  4. 38
      backend/medr-screensaver-backend.py
  5. 151
      dialog/medr-screensaver-dialog.py

@ -0,0 +1,142 @@
<!--
* @Author: Zhang Yueqian zhangyueqian@antiy.cn
* @Date: 2022-11-02 13: 41: 19
* @LastEditors: Zhang Yueqian zhangyueqian@antiy.cn
* @LastEditTime: 2022-11-02 16:00:55
* @FilePath: /screensaver/README.md
* @Description: 屏保软件架构说明
-->
# 概述
本程序主要用来替换 `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
```

@ -3,16 +3,25 @@
# @Author: Zhang Yueqian zhangyueqian@antiy.cn # @Author: Zhang Yueqian zhangyueqian@antiy.cn
# @Date: 2022-11-01 10:24:40 # @Date: 2022-11-01 10:24:40
# @LastEditors: Zhang Yueqian zhangyueqian@antiy.cn # @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 # @FilePath: /screensaver/backend/medr-lightdm-dpms-disable
# @Description: 在lightdm(greeter)登陆之后禁用屏幕休眠功能 # @Description: 在lightdm(greeter)登陆之后禁用屏幕休眠功能
# #
# 本程序需安装在 /usr/local/bin/ 目录下, 配置到 /etc/lightdm/50-medr-dpms.conf 文件中 # 本程序需安装在 /usr/local/bin/ 目录下, 配置到 /etc/lightdm/50-medr-dpms.conf 文件中
### ###
# 是否锁屏判断文件
LOCK_FILE=/var/run/medr-lightdm-lock
# 入口函数, 调用xset # 入口函数, 调用xset
function main() { function main() {
# 通知其他进程已解锁
echo '' >$LOCK_FILE
# 等待X启动(大概?) # 等待X启动(大概?)
while true; do
xset q 2>&1 >/dev/null && break
done
sleep 10 sleep 10
# 将超时配置设为0(禁用) # 将超时配置设为0(禁用)

@ -3,7 +3,7 @@
# @Author: Zhang Yueqian zhangyueqian@antiy.cn # @Author: Zhang Yueqian zhangyueqian@antiy.cn
# @Date: 2022-11-01 09:47:56 # @Date: 2022-11-01 09:47:56
# @LastEditors: Zhang Yueqian zhangyueqian@antiy.cn # @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 # @FilePath: /screensaver/backend/medr-lightdm-dpms-enable
# @Description: 在lightdm(greeter)界面启用屏幕休眠功能 # @Description: 在lightdm(greeter)界面启用屏幕休眠功能
# #
@ -13,6 +13,9 @@
# 休眠时间设置文件路径 # 休眠时间设置文件路径
TIMEOUT_FILE=/var/run/medr-lightdm TIMEOUT_FILE=/var/run/medr-lightdm
# 是否锁屏判断文件
LOCK_FILE=/var/run/medr-lightdm-lock
# 默认休眠时间(分钟) # 默认休眠时间(分钟)
DEFAULT_TIMEOUT=10 DEFAULT_TIMEOUT=10
# 确认文件存在 # 确认文件存在
@ -22,7 +25,6 @@ fi
# 获取休眠超时配置, 如果未设置或设置格式非数字, 则返回默认配置 $DEFAULT_TIMEOUT # 获取休眠超时配置, 如果未设置或设置格式非数字, 则返回默认配置 $DEFAULT_TIMEOUT
function get_timeout() { function get_timeout() {
# 首先保证TIMEOUT文件可被修改 # 首先保证TIMEOUT文件可被修改
chmod 777 $TIMEOUT_FILE chmod 777 $TIMEOUT_FILE
@ -35,17 +37,46 @@ function get_timeout() {
esac 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 # 入口函数, 读取设置并调用xset
function main() { function main() {
# 读取超时配置并转换为秒 # 读取超时配置并转换为秒
TIMEOUT=$(get_timeout) TIMEOUT=$(get_timeout)
TIMEOUT=$((TIMEOUT * 60)) TIMEOUT=$((TIMEOUT * 60))
# 读取是否黑屏
BLANK_SCREEN=$(get_blank)
# 必须的, 似乎是需要等待X启动 # 必须的, 似乎是需要等待X启动
while true; do
xset q 2>&1 >/dev/null && break
sleep 1
done
sleep 10 sleep 10
# 利用X的dpms功能触发屏幕休眠 # 利用X的dpms功能触发屏幕休眠
xset dpms 0 0 $TIMEOUT xset dpms 0 0 $TIMEOUT
sleep 10
if [ x"$BLANK_SCREEN" == "xyes" ]; then
xset dpms force off
fi
} }
main & main &

@ -34,6 +34,7 @@ from gi.repository import GObject, Gio
BUS_NAME = "org.ukui.ScreenSaver" BUS_NAME = "org.ukui.ScreenSaver"
TIMEOUT_FILE = "/var/run/medr-lightdm" TIMEOUT_FILE = "/var/run/medr-lightdm"
LOCK_FILE = "/var/run/medr-lightdm-lock"
DEFAULT_TIMEOUT = 10 DEFAULT_TIMEOUT = 10
@ -52,28 +53,26 @@ class Service(dbus.service.Object):
@dbus.service.method(BUS_NAME, in_signature="", out_signature="b") @dbus.service.method(BUS_NAME, in_signature="", out_signature="b")
def GetLockState(self): def GetLockState(self):
print(" sending out lockstate: ", self._state) print("GetLockState: %s" % self._locked)
return self._state return self._locked
@dbus.service.method(BUS_NAME, in_signature="b", out_signature="") @dbus.service.method(BUS_NAME, in_signature="b", out_signature="")
def SetLockState(self, state): def SetLockState(self, state):
print(" got lock state:", state) print("SetLockState: %s" % state)
self._state = state self._locked = state
@dbus.service.method(BUS_NAME, in_signature="", out_signature="") @dbus.service.method(BUS_NAME, in_signature="", out_signature="")
def Lock(self): def Lock(self):
print(" lock command called!") print("Lock")
self._locked = True
os.system("ukui-screensaver-dialog --lock") os.system("ukui-screensaver-dialog --lock")
@dbus.service.method(BUS_NAME, in_signature="", out_signature="") @dbus.service.method(BUS_NAME, in_signature="", out_signature="")
def Quit(self): def Quit(self):
print(" got quit request.")
self._loop.quit() self._loop.quit()
@dbus.service.signal(BUS_NAME) @dbus.service.signal(BUS_NAME)
def SessionIdle(self): def SessionIdle(self):
print(" signal triggered")
os.system("wall session idle triggerd!")
os.system("ukui-screensaver-dialog --session-idle") os.system("ukui-screensaver-dialog --session-idle")
@ -83,10 +82,29 @@ bus_name = dbus.service.BusName(BUS_NAME, bus=bus)
service = Service(bus_name, "/") 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): def sessionStatusChanged(sessionStatus):
print("---- Caught signal ----") print("---- Caught signal of status %s ----" % sessionStatus)
if sessionStatus == 3: if sessionStatus == 3 and (not isLocked()):
service.SessionIdle() service.SessionIdle()
print("\n") print("\n")

@ -8,21 +8,44 @@ LastEditTime: 2022-10-31 11:34:16
FilePath: /screensaver/dialog/medr-screensaver-dialog.py FilePath: /screensaver/dialog/medr-screensaver-dialog.py
Description: 屏保程序 for MEDR项目. 本文件为负责锁屏的部分. Description: 屏保程序 for MEDR项目. 本文件为负责锁屏的部分.
提供实际锁屏功能, 支持两个参数: --lock --session-idle 提供实际锁屏功能, 支持三个参数: --lock --session-idle --screensaver
锁屏包括两个含义: 熄灭屏幕 & 触发lightdm锁定 锁屏包括两个含义: 熄灭屏幕 & 触发lightdm锁定
--lock: 熄灭屏幕并直接锁定lightdm --lock: 直接锁定lightdm
--session-idle: 首先熄灭屏幕, 然后根据系统设置, 如果系统设置为idle后锁屏则锁屏 --session-idle: 根据系统设置进入session-idle模式
如果设置为空闲时激活屏保但不锁定, 那么关闭屏幕
如果设置了空闲激活屏保且锁定, 那么执行锁定, 通知lighdm执行黑屏
如果未设置空闲屏保但设置了锁定, 那么执行锁定, 不通知lightdm黑屏
--screensaver: 首先熄灭屏幕, 然后锁定屏幕
本应用为单例模式, 启动后当前用户只允许一个实例
实现机制:
首先获取sock文件的锁
如果获取失败, 表明已经运行一个了, 那么执行后将命令通过socket发送给当前活跃进程后退出.
如果获取成功, 说明是新运行, 那么启动server监听socket,并且执行命令
""" """
from argparse import ArgumentParser from argparse import ArgumentParser
import fcntl
from multiprocessing.managers import BaseManager from multiprocessing.managers import BaseManager
import os, socket import os, socket
import atexit
from time import sleep
import gi
MEDR_PIPE = "/tmp/medr-screensaver.sock" gi.require_version("Gtk", "3.0")
_command = [] 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(): def parse_args():
"""解析命令行参数
Returns:
(ArgumentParser.Namespace, str): 解析后的命令行参数及帮助信息
"""
argparser = ArgumentParser(add_help=False) argparser = ArgumentParser(add_help=False)
argparser.add_argument("-h", "--help", dest="help", action="store_true", help="Show this message and exit.") 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() 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): 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: if args.help:
print(help) print(help)
return return
@ -50,64 +108,45 @@ def action(args, help):
return return
if args.session_idle: 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 return
if args.screensaver: if args.screensaver:
os.system("xset dpms force off") write_lightdm("blank")
os.system("dm-tool lock")
return return
print(help) 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__": if __name__ == "__main__":
args, msg = parse_args() args, msg = parse_args()
# singleton()
action(args, msg) action(args, msg)

Loading…
Cancel
Save