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. 149
      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
# @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(禁用)

@ -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 &

@ -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")

@ -8,21 +8,44 @@ LastEditTime: 2022-10-31 11:34:16
FilePath: /screensaver/dialog/medr-screensaver-dialog.py
Description: 屏保程序 for MEDR项目. 本文件为负责锁屏的部分.
提供实际锁屏功能, 支持个参数: --lock --session-idle
提供实际锁屏功能, 支持个参数: --lock --session-idle --screensaver
锁屏包括两个含义: 熄灭屏幕 & 触发lightdm锁定
--lock: 熄灭屏幕并直接锁定lightdm
--session-idle: 首先熄灭屏幕, 然后根据系统设置, 如果系统设置为idle后锁屏则锁屏
--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)

Loading…
Cancel
Save