From 20464061683ae8c5bb79d0e9f711002b351afe1a Mon Sep 17 00:00:00 2001 From: deepin-ci-robot Date: Thu, 11 Sep 2025 10:13:58 +0000 Subject: [PATCH] sync: from linuxdeepin/dde-session-shell Synchronize source files from linuxdeepin/dde-session-shell. Source-pull-request: https://github.com/linuxdeepin/dde-session-shell/pull/37 --- .gitignore | 2 +- .tx/config | 6 + .tx/transifex.yaml | 13 + CMakeLists.txt | 8 + ...org.deepin.dde.lightdm-deepin-greeter.json | 10 + configs/org.deepin.dde.lock.json | 20 + docs/development-guide.md | 1012 +++++++++++------ files/wayland/lightdm-deepin-greeter-wayland | 2 +- interface/base_module_interface.h | 3 +- interface/login_module_interface.h | 5 +- lupdate.sh | 6 +- plugins/CMakeLists.txt | 1 + plugins/login-gesture/CMakeLists.txt | 72 ++ .../login-gesture/configs/login-gesture.json | 20 + .../org.deepin.dde.dss-login-gesture.json | 16 + plugins/login-gesture/gesture.qrc | 5 + plugins/login-gesture/icons/firstEnroll.svg | 35 + plugins/login-gesture/login.json | 3 + .../loginPlugin/gestureloginmodule.cpp | 230 ++++ .../loginPlugin/gestureloginmodule.h | 70 ++ plugins/login-gesture/lupdate.sh | 5 + .../resetDialog/gesturedialog.cpp | 260 +++++ .../login-gesture/resetDialog/gesturedialog.h | 82 ++ plugins/login-gesture/resetDialog/main.cpp | 138 +++ .../login-gesture/resetDialog/resetdialog.qrc | 5 + .../resetDialog/resetpatterncontroller.cpp | 106 ++ .../resetDialog/resetpatterncontroller.h | 49 + plugins/login-gesture/resetDialog/success.svg | 19 + .../login-gesture/src/gestureauthworker.cpp | 260 +++++ plugins/login-gesture/src/gestureauthworker.h | 53 + .../login-gesture/src/gesturemodifyworker.cpp | 96 ++ .../login-gesture/src/gesturemodifyworker.h | 37 + plugins/login-gesture/src/gesturepannel.cpp | 165 +++ plugins/login-gesture/src/gesturepannel.h | 69 ++ plugins/login-gesture/src/modulewidget.cpp | 167 +++ plugins/login-gesture/src/modulewidget.h | 48 + plugins/login-gesture/src/waypointmodel.cpp | 367 ++++++ plugins/login-gesture/src/waypointmodel.h | 146 +++ plugins/login-gesture/src/waypointwidget.cpp | 93 ++ plugins/login-gesture/src/waypointwidget.h | 41 + plugins/login-gesture/translate_generation.sh | 11 + plugins/login-gesture/utils/tokenCrypt.h | 85 ++ .../login-gesture/utils/translastiondoc.cpp | 86 ++ plugins/login-gesture/utils/translastiondoc.h | 62 + plugins/login-gesture/utils/userservice.cpp | 137 +++ plugins/login-gesture/utils/userservice.h | 47 + plugins/one-key-login/login_module.cpp | 22 +- src/app/dde-lock.cpp | 4 +- src/app/greeter-display-setting.cpp | 67 ++ src/dde-lock/lockframe.cpp | 13 +- src/dde-lock/lockworker.cpp | 188 ++- src/dde-lock/lockworker.h | 3 +- .../plugin_manager/login_plugin.cpp | 32 + src/global_util/plugin_manager/login_plugin.h | 4 + .../plugin_manager/plugin_manager.cpp | 85 +- .../plugin_manager/plugin_manager.h | 5 + src/global_util/public_func.cpp | 15 - src/global_util/public_func.h | 5 - src/global_util/qt-compat-helper.cpp | 17 + src/global_util/qt-compat-helper.h | 32 + src/global_util/signal_bridge.h | 29 + src/libdde-auth/authcommon.h | 5 +- src/libdde-auth/deepinauthframework.cpp | 35 +- src/libdde-auth/deepinauthframework.h | 2 +- src/lightdm-deepin-greeter/greeterworker.cpp | 77 +- src/lightdm-deepin-greeter/greeterworker.h | 1 + src/session-widgets/assist_login_widget.cpp | 59 +- src/session-widgets/assist_login_widget.h | 8 + src/session-widgets/auth_custom.cpp | 54 +- src/session-widgets/auth_custom.h | 10 +- src/session-widgets/auth_face.cpp | 17 +- src/session-widgets/auth_face.h | 1 + src/session-widgets/auth_module.cpp | 8 +- src/session-widgets/auth_password.cpp | 134 ++- src/session-widgets/auth_password.h | 8 +- src/session-widgets/auth_widget.cpp | 12 + src/session-widgets/auth_widget.h | 2 + src/session-widgets/lockcontent.cpp | 87 +- src/session-widgets/lockcontent.h | 1 + src/session-widgets/mfa_widget.cpp | 83 +- src/session-widgets/mfa_widget.h | 3 + src/session-widgets/mfasequencecontrol.cpp | 248 ++++ src/session-widgets/mfasequencecontrol.h | 67 ++ src/session-widgets/sessionbasemodel.cpp | 35 + src/session-widgets/sessionbasemodel.h | 11 + src/session-widgets/sfa_widget.cpp | 63 +- src/session-widgets/sfa_widget.h | 2 +- src/session-widgets/userpanel.cpp | 12 +- src/session-widgets/userpanel.h | 7 +- src/session-widgets/userswiththesamename.cpp | 12 +- src/widgets/dlineeditex.cpp | 20 + src/widgets/dlineeditex.h | 2 + src/widgets/fullscreenbackground.cpp | 30 +- src/widgets/fullscreenbackground.h | 2 + src/widgets/passworderrortipswidget.cpp | 9 +- src/widgets/shutdown_black_widget.cpp | 123 ++ src/widgets/shutdown_black_widget.h | 27 + src/widgets/shutdownwidget.cpp | 3 +- tests/dde-lock/CMakeLists.txt | 1 + tests/lightdm-deepin-greeter/CMakeLists.txt | 1 + 100 files changed, 5356 insertions(+), 620 deletions(-) create mode 100644 .tx/transifex.yaml create mode 100644 plugins/login-gesture/CMakeLists.txt create mode 100644 plugins/login-gesture/configs/login-gesture.json create mode 100644 plugins/login-gesture/configs/org.deepin.dde.dss-login-gesture.json create mode 100644 plugins/login-gesture/gesture.qrc create mode 100644 plugins/login-gesture/icons/firstEnroll.svg create mode 100644 plugins/login-gesture/login.json create mode 100644 plugins/login-gesture/loginPlugin/gestureloginmodule.cpp create mode 100644 plugins/login-gesture/loginPlugin/gestureloginmodule.h create mode 100755 plugins/login-gesture/lupdate.sh create mode 100644 plugins/login-gesture/resetDialog/gesturedialog.cpp create mode 100644 plugins/login-gesture/resetDialog/gesturedialog.h create mode 100644 plugins/login-gesture/resetDialog/main.cpp create mode 100644 plugins/login-gesture/resetDialog/resetdialog.qrc create mode 100644 plugins/login-gesture/resetDialog/resetpatterncontroller.cpp create mode 100644 plugins/login-gesture/resetDialog/resetpatterncontroller.h create mode 100644 plugins/login-gesture/resetDialog/success.svg create mode 100644 plugins/login-gesture/src/gestureauthworker.cpp create mode 100644 plugins/login-gesture/src/gestureauthworker.h create mode 100644 plugins/login-gesture/src/gesturemodifyworker.cpp create mode 100644 plugins/login-gesture/src/gesturemodifyworker.h create mode 100644 plugins/login-gesture/src/gesturepannel.cpp create mode 100644 plugins/login-gesture/src/gesturepannel.h create mode 100644 plugins/login-gesture/src/modulewidget.cpp create mode 100644 plugins/login-gesture/src/modulewidget.h create mode 100644 plugins/login-gesture/src/waypointmodel.cpp create mode 100644 plugins/login-gesture/src/waypointmodel.h create mode 100644 plugins/login-gesture/src/waypointwidget.cpp create mode 100644 plugins/login-gesture/src/waypointwidget.h create mode 100755 plugins/login-gesture/translate_generation.sh create mode 100644 plugins/login-gesture/utils/tokenCrypt.h create mode 100644 plugins/login-gesture/utils/translastiondoc.cpp create mode 100644 plugins/login-gesture/utils/translastiondoc.h create mode 100644 plugins/login-gesture/utils/userservice.cpp create mode 100644 plugins/login-gesture/utils/userservice.h create mode 100644 src/global_util/qt-compat-helper.cpp create mode 100644 src/global_util/qt-compat-helper.h create mode 100644 src/global_util/signal_bridge.h create mode 100644 src/session-widgets/mfasequencecontrol.cpp create mode 100644 src/session-widgets/mfasequencecontrol.h create mode 100644 src/widgets/shutdown_black_widget.cpp create mode 100644 src/widgets/shutdown_black_widget.h diff --git a/.gitignore b/.gitignore index 74d8701ab..f4f7381b0 100644 --- a/.gitignore +++ b/.gitignore @@ -29,6 +29,6 @@ build*/ tests/report/* .transifexrc .cache/ - +.cursor/ # for snipe src/global_util/dbus/* diff --git a/.tx/config b/.tx/config index cc140acec..37e5948a7 100644 --- a/.tx/config +++ b/.tx/config @@ -8,3 +8,9 @@ file_filter = translations/dde-session-shell_.ts source_file = translations/dde-session-shell_en.ts source_lang = en type = QT + +[o:linuxdeepin:p:deepin-desktop-environment:r:login-gesture] +file_filter = plugins/login-gesture/translations/login-gesture_.ts +source_file = plugins/login-gesture/translations/login-gesture_en.ts +source_lang = en +type = QT diff --git a/.tx/transifex.yaml b/.tx/transifex.yaml new file mode 100644 index 000000000..1061912c5 --- /dev/null +++ b/.tx/transifex.yaml @@ -0,0 +1,13 @@ +filters: +- filter_type: file + source_file: translations/dde-session-shell_en.ts + file_format: QT + source_language: en + translation_files_expression: translations/dde-session-shell_.ts +- filter_type: file + source_file: plugins/login-gesture/translations/login-gesture_en.ts + file_format: QT + source_language: en + translation_files_expression: plugins/login-gesture/translations/login-gesture_.ts +settings: + pr_branch_name: transifex_update_ diff --git a/CMakeLists.txt b/CMakeLists.txt index 283ed3acb..903028dcd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -260,6 +260,7 @@ set(LOCK_SRCS src/widgets/passworderrortipswidget.cpp src/widgets/passworderrortipswidget.h src/global_util/dbusconstant.h + src/global_util/signal_bridge.h ) link_libraries( @@ -324,8 +325,15 @@ set(GREETER_SRCS src/lightdm-deepin-greeter/logincontent.cpp src/lightdm-deepin-greeter/logintipswindow.cpp src/lightdm-deepin-greeter/sessionwidget.cpp + src/global_util/signal_bridge.h ) +if (USE_DEEPIN_WAYLAND) + set(GREETER_SRCS + ${GREETER_SRCS} + ) +endif(USE_DEEPIN_WAYLAND) + add_executable(lightdm-deepin-greeter ${GREETER_SRCS} ${QRCS} diff --git a/configs/org.deepin.dde.lightdm-deepin-greeter.json b/configs/org.deepin.dde.lightdm-deepin-greeter.json index 4b51bdd3d..73fa46c83 100644 --- a/configs/org.deepin.dde.lightdm-deepin-greeter.json +++ b/configs/org.deepin.dde.lightdm-deepin-greeter.json @@ -301,6 +301,16 @@ "description": "是否长按小眼睛显示密码,true-鼠按长按小眼睛才显示密码,false-鼠标点击一下就一直显示密码", "permissions": "readwrite", "visibility": "private" + }, + "mfaSequence":{ + "value": {"userType":"","authSequence":[]}, + "serial": 0, + "flags": ["global"], + "name": "mfaSequence", + "name[zh_CN]": "多因认证时控制前端按认证顺序展示UI", + "description": "userType可用值,all:代表所有用户,adDomain:代表域用户,其它可选还有default、native,authSequence中的数组即代表认证类型的顺序", + "permissions": "readwrite", + "visibility": "private" } } } diff --git a/configs/org.deepin.dde.lock.json b/configs/org.deepin.dde.lock.json index 2de40b78c..78c96fe86 100755 --- a/configs/org.deepin.dde.lock.json +++ b/configs/org.deepin.dde.lock.json @@ -281,6 +281,26 @@ "description": "是否长按小眼睛显示密码,true-鼠按长按小眼睛才显示密码,false-鼠标点击一下就一直显示密码", "permissions": "readwrite", "visibility": "private" + }, + "mfaSequence":{ + "value": {"userType":"","authSequence":[]}, + "serial": 0, + "flags": ["global"], + "name": "mfaSequence", + "name[zh_CN]": "多因认证时控制前端按认证顺序展示UI", + "description": "userType可用值,all:代表所有用户,adDomain:代表域用户,其它可选还有default、native,authSequence中的数组即代表认证类型的顺序", + "permissions": "readwrite", + "visibility": "private" + }, + "enableShutdownBlackWidget":{ + "value": true, + "serial": 0, + "flags": ["global"], + "name": "enableShutdownBlackWidget", + "name[zh_CN]": "是否打开关机、重启黑屏界面", + "description": "是否打开关机、重启黑屏界面;false:不开,true:开启。默认值为true;", + "permissions": "readwrite", + "visibility": "private" } } } diff --git a/docs/development-guide.md b/docs/development-guide.md index a25a41a01..49b4ad9e7 100644 --- a/docs/development-guide.md +++ b/docs/development-guide.md @@ -1,26 +1,68 @@ +# 版本变更记录 + +| 版本 | 修订说明 | 修订人 | 修订时间 | +|:------|:---------|:-------|:-----------| +| V1.0 | 首次提交 | 殷杰 | 2022-05-17 | +| V1.1 | 1. 认证插件版本号更新为 2.0.0
2. 增加基于 JSON 数据的双向通讯方式
3. 认证插件可以自定义登录器的显示内容 | 殷杰 | 2022-05-17 | +| V1.2 | 补充缺失的接口函数和消息协议,优化文档格式和内容 | 殷杰 | 2023-07-22 | + # 概述 -本文档是针对 UOS 登录器插件给出开发指南,目的是为了让开发人员了解如何在 UOS 登录器上增加一种自定义认证方式,对插件接口做了详细说明以及实战练习。 +本文档是针对 UOS 登录器插件的开发指南,旨在指导开发人员如何在 UOS 登录器上增加自定义认证方式。插件必须基于 Qt 框架进行开发。 + +**适用读者:** +- 产品经理 +- 设计人员 +- 开发人员 +- 测试人员 -# 认证插件可以做什么? +# 认证插件功能介绍 -UOS 提供了丰富了登录方式:密码、UKey、指纹、人脸、虹膜等,这些登录方式是在登录器内部处理的,想要实现其它的登录方式(比如二维码)又不想与 UOS 的登录器有过多的耦合,那么就可以使用认证插件的方式来实现。只需根据认证插件的接口来开发一个动态库,即可在登录器上增加一种自定义登录方式。 +UOS 系统内置了多种登录认证方式: +- 密码认证 +- UKey 认证 +- 指纹识别 +- 人脸识别 +- 虹膜识别 + +这些认证方式都是在登录器内部实现的。如果需要添加新的认证方式(如二维码登录),又不想与 UOS 登录器代码产生强耦合,可以通过开发认证插件来实现。只需开发一个符合接口规范的动态库,即可为登录器增加新的认证方式,无需修改 UOS 登录器的源代码。 ## 认证流程 -登录器在开机/注销/锁屏后会加载认证插件,并发起认证,然后等待插件返回认证结果。用户鉴权通过后,插件发送认证成功给登录器,后面的如何进入系统由登录器来处理。在整个过程中,插件只需要关注自身的认证逻辑,把认证结果发送到登录器后插件的工作就完成了。 +认证插件的工作流程如下: + +1. **插件加载**:登录器在开机/注销/锁屏时加载认证插件 +2. **等待输入**:插件等待用户提供认证信息 +3. **验证处理**:插件验证用户提供的信息 +4. **结果返回**:将认证结果返回给登录器 +5. **完成退出**:登录器处理后续的系统登录流程 + +插件只需专注于认证逻辑,无需关心系统登录的具体实现。 ```plantuml @startuml 认证流程时序图 +skinparam handwritten false +skinparam monochrome true +skinparam shadowing false +skinparam defaultFontName sans-serif +skinparam sequenceMessageAlign center + title 认证流程时序图 + +participant 用户 +participant 登录器 +participant 认证插件 + autonumber 用户 -> 登录器: 开机、注销、锁屏 登录器 -> 认证插件: 加载认证插件 +登录器 -> 认证插件: 开始认证 认证插件 -> 认证插件: 等待用户输入 -用户 -> 认证插件: 提交鉴权信息(输入密码、扫码、生物信息等) -认证插件 -> 认证插件: 校验用户提交信息(本地校验、远程服务器校验等) +用户 -> 认证插件: 提交鉴权信息\n(密码/扫码/生物信息等) +认证插件 -> 认证插件: 校验用户信息\n(本地/远程验证) 认证插件 --> 登录器: 鉴权成功 登录器 -> 登录器: 进入系统 + @enduml ``` @@ -28,136 +70,238 @@ autonumber 登录器:即 UOS 系统中的登录/锁屏软件。 -登录是指用户开机、重启、注销后看到的 UI 界面,进程名称为 lightdm-deepin-greeter。 +登录界面:是指用户开机、重启、注销后看到的 UI 界面,进程名称为 lightdm-deepin-greeter。 + +锁屏界面:是用户按下 Meta+L 快捷键(没有修改快捷键的情况下),或者在电源界面选择"锁定"后看到的 UI 界面,进程名为 dde-lock。 -锁屏是用户按下 Meta+L 快捷键(没有修改快捷键的情况下),或者在电源界面选择“锁定”后看到的 UI 界面,进程名为 dde-lock。 +登录界面和锁屏界面是两个不同的进程,UI界面非常相似,但是功能和认证流程有很大的区别,在开发和调试的时候需要区分清楚。通过UI界面的`解锁按钮`可以轻松区分:登录界面解锁按钮是`箭头`图标,锁屏界面解锁按钮是`锁型`图标。 # 接口使用 -## 环境配置 +## 开发环境配置 -安装开发包 dde-session-shell-dev,安装完成后在 /usr/include/dde-session-shell/ 路径会有三个插件相关的头文件:base_module_interface.h 、login_module_interface.h、login_module_interface_v2.h,开发时使用 login_module_interface_v2.h 即可,有更完备的接口和功能。 +### 必需组件 -认证插件需要使用 Qt 框架,根据实际需求搭建 Qt 开发环境即可,这里不再赘述。 +1. **开发包安装** + ```bash + sudo apt install dde-session-shell-dev # 版本要求 ≥ 5.5.18 + ``` -## 数据结构说明 +### 头文件说明 -下面是开发认证插件时可能会用到的数据结构,可以在开发时作为工具文档使用。 +安装完成后,在 `/usr/include/dde-session-shell/` 目录下会有几个关键头文件: +- `base_module_interface.h`:基础接口定义 +- `login_module_interface.h`:登录相关接口定义 +- `login_module_interface_v2.h`:登录相关接口定义(2.x.x版本) -### ModuleType +> **注意**:开发时只需包含 `login_module_interface_v2.h` 即可,它已经包含了基础接口的定义。 -**说明:** 插件类型 +## 数据结构参考 + +本节介绍认证插件开发中使用的核心数据结构。 + +### 插件类型相关 + +#### ModuleType + +**说明:** 定义插件的基本类型和功能范围 **类型:** 枚举 -| 字段 | 说明 | 备注 | -| :-------- | :------- | :--------------------------------------------------------------------- | -| LoginType | 认证插件 | 认证插件默认使用此字段 | -| TrayType | 托盘插件 | 展示在登录器右下方控制区域的插件(例如网络,认证插件不用关注这个类型) | +**使用场景:** 在插件初始化时指定类型 + +| 字段 | 值 | 说明 | 使用建议 | +|:-----|:---|:-----|:---------| +| LoginType | 0 | 标准认证插件 | 🟢 默认推荐,适用于大多数认证场景 | +| TrayType | 1 | 托盘插件 | ⚪ 认证插件无需关注 | +| FullManagedLoginType | 2 | 全托管插件 | 🟡 需要完全控制登录流程时使用 | +| IpcAssistLoginType | 3 | IPC辅助登录插件 | 🟡 用于厂商密码插件集成 | +| PasswordExtendLoginType | 4 | 密码扩展插件 | 🟡 需要双重认证时使用 | -### AuthCallbackData +> **注意**:除非有特殊需求,默认使用 `LoginType`。其他类型建议先与 UOS 开发团队确认。 -**说明:** 认证插件需要传回的数据 +### 认证数据相关 +#### AuthCallbackData + +**说明:** 认证结果回传数据结构 **类型:** 结构体 +**使用场景:** 在认证完成时向登录器返回结果 -| 字段 | 数据类型 | 说明 | 必填 | 备注 | -| :------ | :------- | :------- | :--- | :--------------- | -| result | int | 认证结果 | 是 | 1 成功,其他失败 | -| account | string | 账户 | 否 | 用户账号 | -| token | string | 通行令牌 | 否 | 用户密码等 | -| message | string | 提示消息 | 否 | | -| json | string | 冗余字段 | 否 | 暂时没有使用 | +| 字段 | 类型 | 必填 | 说明 | 示例值 | +|:-----|:-----|:-----|:-----|:-------| +| result | int | 是 | 认证结果 | 1: 成功
其他: 失败 | +| account | string | 是 | 用户账号 | "user123" | +| token | string | 否 | 认证凭据 | "password123" | +| message | string | 否 | 提示信息 | "认证成功" | +| json | string | 否 | 预留的扩展数据字段,暂未使用 | | -### LoginCallBack +> **安全提示**: +> - 认证失败时应该在message中提供友好的错误提示 +> - 避免在message中包含敏感信息,该字段会在日志中打印出来 -**说明:** 回调函数以及登录器的回传指针 +#### AuthResult -**类型:** 结构体 +**说明:** 认证结果状态码 +**类型:** 枚举 +**使用场景:** 在 `AuthCallbackData` 的 result 字段中使用 -| 字段 | 类型 | 说明 | 备注 | -| :------------------ | :----------------- | :--------------- | :------------------------------------- | -| app_data | void\* | 登录器的回传指针 | 插件无需关注,只需在回调函数中传入即可 | -| authCallbackFun | AuthCallbackFun | 认证回调函数 | 用于认证完成后通知登录器 | -| messageCallbackFunc | MessageCallbackFun | 消息回调函数 | 用于主动与登录器交互 | +| 状态码 | 值 | 说明 | 使用场景 | +|:-------|:---|:-----|:---------| +| None | 0 | 未知状态 | 初始化或异常情况 | +| Success | 1 | 认证成功 | 用户验证通过 | +| Failure | 2 | 认证失败 | 验证不通过或发生错误 | -### AppType +> **最佳实践**: +> - 推荐使用头文件中提供的枚举,而不是直接使用数字 -**说明:** 发起认证的应用类型 +#### CustomLoginType +**说明:** 自定义登录方式类型 **类型:** 枚举 +**使用场景:** 指定插件提供的登录方式类型 -| 字段 | 说明 | 备注 | -| :---- | :----- | :-------------------------------------------- | -| None | 异常值 | 如果为 None 则说明出现异常 | -| Login | 登录 | 二进制文件为:/usr/bin/lightdm-deepin-greeter | -| Lock | 锁屏 | 二进制文件为:/usr/bin/dde-lock | +| 类型 | 值 | 说明 | 适用场景 | +|:-----|:---|:-----|:---------| +| CLT_Default | 0 | 标准登录 | 🟢 单一认证方式 | +| CLT_MFA | 1 | 多因子认证 | 🟡 需要多重验证 | +| CLT_ThirdParty | 2 | 第三方认证 | 🟡 外部认证服务 | -### LoadType -**说明:** 模块加载的类型 +### 回调函数相关 +#### LoginCallBack + +**说明:** 登录器提供的回调函数集合 +**类型:** 结构体 +**使用场景:** 插件与登录器之间的双向通信 + +| 字段 | 类型 | 说明 | 使用方式 | +|:-----|:-----|:-----|:---------| +| app_data | void* | 登录器上下文 | 仅作为回调参数传递,不要修改 | +| authCallbackFun | AuthCallbackFun | 认证结果回调 | 用于返回认证结果 | +| messageCallbackFunc | MessageCallbackFun | 消息通信回调 | 用于主动发送消息给登录器 | + +> **使用说明**: +> - 保存函数指针的变量必须初始化为空,并在使用指针前进行判空,避免使用空指针或野指针。 + +### 应用类型相关 + +#### AppType + +**说明:** 认证发起方的类型标识 **类型:** 枚举 +**使用场景:** 区分认证请求来源,采用不同的认证策略 -| 字段 | 说明 | 备注 | -| :------ | :--------- | :--- | -| Load | 加载插件 | | -| NotLoad | 不加载插件 | | +| 类型 | 值 | 说明 | 进程信息 | 特点 | +|:-----|:---|:-----|:---------|:-----| +| None | 0 | 异常状态 | - | 表示初始化失败或配置错误 | +| Login | 1 | 登录界面 | `/usr/bin/lightdm-deepin-greeter` | 和lightdm配合有标准化的登录流程 | +| Lock | 2 | 锁屏界面 | `/usr/bin/dde-lock` | 认证完成后隐藏界面,仅此而已 | -### AuthObjectType +> **处理建议**: +> - 如果在登录界面和锁屏界面有不同的业务逻辑,可以通过这个变量来判断。 -**说明:** 模块加载的类型 +#### LoadType +**说明:** 插件加载控制标识 **类型:** 枚举 +**使用场景:** 控制插件是否被登录器加载 -| 字段 | 说明 | 备注 | -| :----------------- | :--------- | :--- | -| LightDM | 显示管理器 | | -| DeepinAuthenticate | 不加载插件 | | +| 标识 | 值 | 说明 | 使用场景 | +|:-----|:---|:-----|:---------| +| Load | 0 | 允许加载 | 默认值 | +| Notload | 1 | 禁止加载 | 插件根据自身的业务逻辑不想进行认证,例如:
- 系统环境不支持
- 依赖服务未启动
- 配置文件缺失
- 业务需求等 | -### AuthType +> **最佳实践**: +> - 在插件构造时检查运行环境 +> - 插件应记录不加载的原因 -**说明:** 认证类型 +### 认证框架相关 +#### AuthObjectType + +**说明:** 认证服务提供方类型 **类型:** 枚举 +**使用场景:** 识别当前使用的认证框架,一般认证插件无需关注 + +| 框架类型 | 值 | 说明 | +|:---------|:---|:-----| +| LightDM | 0 | 显示管理器 | +| DeepinAuthenticate | 1 | 深度认证框架 | + + +### 认证方式相关 + +#### AuthType -| 字段 | 说明 | 备注 | -| :------ | :----- | :--- | -| 0 | 默认 | | -| 1 << 0 | 密码 | | -| 1 << 1 | 指纹 | | -| 1 << 2 | 人脸 | | -| 1 << 3 | AD 域 | | -| 1 << 4 | UKey | | -| 1 << 5 | 指静脉 | | -| 1 << 6 | 虹膜 | | -| 1 << 7 | PIN | | -| 1 << 29 | PAM | | -| 1 << 30 | 自定义 | | -| -1 | ALL | | - -### AuthState - -**说明:** 认证状态 +**说明:** 系统支持的认证方式 +**类型:** 枚举(位掩码) +**使用场景:** 指定认证方式或组合多种认证方式 +| 类型 | 值 | 说明 | +|:-----|:---|:-----| +| AT_None | 0 | 未指定 | +| AT_Password | 1<<0 | 密码认证 | +| AT_Fingerprint | 1<<1 | 指纹识别 | +| AT_Face | 1<<2 | 人脸识别 | +| AT_ActiveDirectory | 1<<3 | AD域认证 | +| AT_Ukey | 1<<4 | UKey认证 | +| AT_FingerVein | 1<<5 | 指静脉识别 | +| AT_Iris | 1<<6 | 虹膜识别 | +| AT_Passkey | 1<<7 | 安全密钥 | +| AT_Pattern | 1<<8 | 图案解锁 | +| AT_PAM | 1<<29 | PAM认证 | +| AT_Custom | 1<<30 | 自定义认证 | +| AT_All | -1 | 支持所有方式 | + +> **使用说明**: +> - 可以通过位运算组合多种认证方式,例如:`AT_Password | AT_Fingerprint` 表示同时支持密码和指纹 +> - 插件的认证方式是 `AT_Custom`。 + +#### AuthState + +**说明:** 认证过程的状态标识 **类型:** 枚举 +**使用场景:** 跟踪和管理认证流程的各个状态 + +| 字段 | 值 | 说明 | 备注 | +| :--- | :-- | :--------- | :---------------------------------------------------------------------------------- | +| AS_None | -1 | 默认 | | +| AS_Success | 0 | 成功 | 此次认证的最终结果 | +| AS_Failure | 1 | 失败 | 此次认证的最终结果 | +| AS_Cancel | 2 | 取消 | 当认证没有给出最终结果时,调用 End 会出发 Cancel 信号 | +| AS_Timeout | 3 | 超时 | 一些认证设备会有超时状态的设定 | +| AS_Error | 4 | 错误 | | +| AS_Verify | 5 | 验证中 | | +| AS_Exception | 6 | 设备异常 | 当前认证会被 End | +| AS_Prompt | 7 | 设备提示 | | +| AS_Started | 8 | 认证已启动 | 调用 Start 之后,每种成功开启都会发送此信号 | +| AS_Ended | 9 | 认证已结束 | 调用 End 之后,每种成功关闭的都会发送此信号,当某种认证类型被锁定时,也会触发此信号 | +| AS_Locked | 10 | 认证已锁定 | 认证类型已锁定,表示认证类型不可用(从安全角度考虑,失败次数过多时会导致这种情况) | +| AS_Recover | 11 | 设备恢复 | 需要调用 Start 重新开启认证,对应 AS_Exception | +| AS_Unlocked | 12 | 认证解锁 | 认证类型解除锁定,表示可以继续继续使用此认证类型开始认证了 | +| AS_Unknown | 13 | 未知状态 | | +| AS_VerifyCode | 14 | 需要验证码 | | + +### 认证级别相关 + +#### DefaultAuthLevel + +**说明:** 认证方式的优先级设置 +**类型:** 枚举 +**使用场景:** 配置默认的认证方式选择策略 + +| 级别 | 值 | 策略 | 使用建议 | +|:-----|:---|:-----|:---------| +| NoDefault | 0 | 智能选择 | 🟢 推荐使用
- 优先使用上次成功的认证方式
- 无历史记录时按系统默认顺序 | +| Default | 1 | 已废弃 | ⛔ 不要使用
- 保留向后兼容
- 功能与 NoDefault 类似 | +| StrongDefault | 2 | 强制插件 | 🟡 特殊场景
- 始终优先使用插件认证
| -| 字段 | 说明 | 备注 | -| :--- | :--------- | :---------------------------------------------------------------------------------- | -| -1 | 默认 | | -| 0 | 成功 | 此次认证的最终结果 | -| 1 | 失败 | 此次认证的最终结果 | -| 2 | 取消 | 当认证没有给出最终结果时,调用 End 会出发 Cancel 信号 | -| 3 | 超时 | 一些认证设备会有超时状态的设定 | -| 4 | 错误 | | -| 5 | 验证中 | | -| 6 | 设备异常 | 当前认证会被 End | -| 7 | 设备提示 | | -| 8 | 认证已启动 | 调用 Start 之后,每种成功开启都会发送此信号 | -| 9 | 认证已结束 | 调用 End 之后,每种成功关闭的都会发送此信号,当某种认证类型被锁定时,也会触发此信号 | -| 10 | 认证已锁定 | 认证类型已锁定,表示认证类型不可用(从安全角度考虑,失败次数过多时会导致这种情况) | -| 11 | 设备恢复 | 需要调用 Start 重新开启认证,对应 AS_Exception | -| 12 | 认证解锁 | 认证类型解除锁定,表示可以继续继续使用此认证类型开始认证了 | +> **选择建议**: +> - 一般情况使用 `NoDefault`,提供更好的用户体验 +> - 需要强制使用插件认证时,使用 `StrongDefault` +> - 避免使用已废弃的 `Default` 级别 ## 接口说明 @@ -193,7 +337,10 @@ virtual ModuleType type() const = 0 virtual void init() = 0 ``` -**说明:** 界面相关的初始化,插件在非主线程加载,故界面相关的初始化需要放在这个方法里,由主程序调用并初始化。 +**说明:** +1. 插件加载是在子线程进行的,界面相关的初始化操作放在这个函数中,不要放在构造函数里处理。 +2. 这个方法在主线程中调用,不要进行耗时操作,耗时操作请异步处理。 +3. 这个函数可能会重复调用,以重置界面内容。 **入参:** 无 @@ -217,7 +364,7 @@ virtual QString key() const = 0 | 类型 | 说明 | 备注 | | :----- | :------- | :----------------------- | -| string | 唯一编码 | 传入与项目相关的命名即可 | +| string | 唯一编码 | 传入与项目相关的命名即可,切记要带有独特性(比如公司、项目缩写),避免和其它插件重名 | **必须实现:** 是 @@ -230,15 +377,16 @@ virtual QString icon() const ``` **说明:** -当认证因子不止一种时,登录器会显示认证类型切换组件,它是由一个按钮组(Button Group)组成的,此函数返回的图标会展示在“认证插件切换按钮”上面。 +当认证因子不止一种时,登录器会显示认证类型切换组件,它是由一个按钮组(Button Group)组成的,此函数返回的图标会展示在"认证插件切换按钮"上面。 **入参:** 无 **返回值:** + | 类型 | 说明 | 备注 | | :----- | :------------- | :------------------------------------------------------------------------------------------------------------------------------------------- | -| string | 认证插件的图标 | 可以返回图标的绝对路径(加载方式为 QIcon(“absolute path”)),也可以返回系统图标的名称(加载方式为 QIcon::fromTheme(“system icon name”))。 | +| string | 认证插件的图标 | - 使用图标的绝对路径(加载方式为 QIcon("absolute path"))
- 使用系统图标的名称(加载方式为 QIcon::fromTheme("system icon name"))。 | **必须实现:** 否 @@ -286,21 +434,20 @@ virtual LoadType loadPluginType() const #### setAppData -**函数定义:** 无 +**函数定义:** ```c++ -virtual LoadType setAppdata(void*) const +virtual void setAppData(void*) const ``` **说明:** -设置登录器的回调指针,插件需要保存指针,在使用回调函数的时候回传给登录器。如果要使用回调函数,则必须实现此函数。 -函数可能会被重复调用,插件只需要保证回传的时候是最后一次设置的即可。 +设置登录器的回调指针,插件需要保存指针,在使用回调函数的时候回传给登录器。如果要使用回调函数,则必须实现此函数。函数可能会被重复调用,插件回传最后一次设置的即可。 -**入参:** 无 +**入参:** -| 类型 | 说明 | 备注 | -| :----- | :--- | :--- | -| void\* | - | - | +| 类型 | 说明 | 备注 | +| :----- | :------------------- | :--- | +| void\* | 登录器的回调指针 | - | **返回值:** 无 @@ -341,112 +488,174 @@ QString (*)(const QString &, void *) | 类型 | 说明 | 备注 | | :----- | :-------------------------------- | :-------------------------------------- | -| string | 发送给登录器的 json 格式数据 | json 数据的具体内容详见下面数据协议部分 | +| string | 发送给登录器的 JSON 格式数据 | JSON 数据的具体内容详见 "JSON 数据协议" 部分 | | void\* | 即 LoginCallBack 的 app_data 字段 | | **返回值:** | 类型 | 说明 | 备注 | | :----- | :--------------------------- | :--- | -| string | 发送给登录器的 json 格式数据 | | +| string | 登录器返回的 JSON 格式数据 | | -**数据协议:** -默认返回的数据: -| 字段 | 类型 | 说明 | -| :------ | :----- | :--------------- | -| Code | int | 0 成功,其他失败 | -| Message | string | 提示消息 | +### LoginModuleInterfaceV2 -例: +#### message -```json -{ - "Code": 0, - "Message": "Success" -} +**函数定义:** + +```c++ +virtual QString message(const QString &) ``` -1. 获取属性 +**说明:** +登录器主动向认证插件发起通讯,用于获取插件信息或者给插件提供信息。**这是插件与登录器交互的核心方法**,插件通过处理不同的 JSON 消息来实现各种功能。具体的 JSON 协议格式请参考本文档的 "JSON 数据协议" 部分。 + +**入参:** + +| 类型 | 说明 | 备注 | +| :------ | :-------------- | :--- | +| QString | json 格式字符串 | 包含CmdType等字段的JSON消息 | + +**返回值:** -请求: +| 类型 | 说明 | 备注 | +| :------ | :-------------- | :--- | +| QString | json 格式字符串 | 插件的响应数据 | -| 字段 | 类型 | 值 | 说明 | -| :------ | :----- | :------------------------ | :----------------------------------------------------------------------------------------------------------------------------------------- | -| CmdType | string | "GetProperties" | | -| Data | array | ["AppType","CurrentUser"] | [值]列中展示的是目前支持的值,传入其它值无效。`
`根据 api 版本号来判断支持哪些字段:`
`since api-2.0.0 ("AppType", "CurrentUser") | +**必须实现:** 否 -例: +**实现示例:** -```json -{ - "CmdType": "GetProperties", - "Data": ["AppType", "CurrentUser"] +```c++ +QString YourPlugin::message(const QString &jsonStr) { + QJsonDocument doc = QJsonDocument::fromJson(jsonStr.toUtf8()); + QJsonObject obj = doc.object(); + QString cmdType = obj["CmdType"].toString(); + + if (cmdType == "IsPluginEnabled") { + QJsonObject response; + response["Code"] = 0; + response["Message"] = "Success"; + QJsonObject data; + data["IsPluginEnabled"] = true; // 插件是否启用 + response["Data"] = data; + return QJsonDocument(response).toJson(); + } else if (cmdType == "GetConfigs") { + // 处理获取配置的请求 + QJsonObject response; + response["Code"] = 0; + response["Message"] = "Success"; + QJsonObject data; + data["ShowAvatar"] = true; + data["ShowUserName"] = true; + data["DefaultAuthLevel"] = 1; + response["Data"] = data; + return QJsonDocument(response).toJson(); + } + // 处理其他命令... + + return "{}"; // 默认返回空JSON对象 } ``` -返回值: -| 字段 | 类型 | 说明 | -| :-------------------- | :----- | :-------------------- | -| Code | int | 0 成功,其他失败 | -| Message | string | 提示消息 | -| Data | object | - | -| Data.AppType | int | 详见 AppType 枚举说明 | -| Data.CurrentUser | object | 当前用户信息 | -| Data.CurrentUser.Name | string | 当前用户的用户名 | +**必须实现:** 否 -例: +#### setAuthCallback -```json -{ - "Code": 0, - "Message": "Success", - "Data": { - "AppType": 2, - "CurrentUser": { - "Name": "uos" - } - } -} +**函数定义:** + +```c++ +virtual void setAuthCallback(AuthCallbackFunc *) = 0 ``` -#### message +**说明:** 设置认证结果回调函数,会在init函数之前调用。 + +**入参:** + +| 类型 | 说明 | 备注 | +| :----------------- | :-------------------------------- | :--- | +| AuthCallbackFunc\* | 详见 `AuthCallbackFunc`类型说明 | | + +**返回值:** 无 + +**必须实现:** 是 + +#### AuthCallbackFun **函数定义:** ```c++ -virtual QString message(const QString &) +void (*)(const AuthCallbackData *, void *) ``` -**说明:** -登录器主动向认证插件发起通讯,一般用于获取插件信息或者给插件提供信息,入参和返回值都是 json 格式的字符串。 +**说明:** 认证回调函数,用于插件返回认证的状态、结果等 **入参:** -| 类型 | 说明 | 备注 | -| :------ | :-------------- | :--- | -| QString | json 格式字符串 | | +| 类型 | 说明 | 备注 | +| :----------------- | :-------------------------------- | :--- | +| AuthCallbackData\* | 认证相关信息 | | +| void\* | 即 LoginCallBack 的 app_data 字段 | | -**返回值:** +**返回值:** 无 -| 类型 | 说明 | 备注 | -| :------ | :-------------- | :--- | -| QString | json 格式字符串 | | +#### reset -**必须实现:** 否 +**函数定义:** + +```c++ +virtual void reset() = 0 +``` + +**说明:** +插件需要在这个方法中重置UI和验证状态,通常验证开始之前登录器会调用这个方法,但是不保证每次验证前都会调用。插件必须实现这个函数,并在函数内重置之前的验证结果,避免将以前的结果应用在当前认证中。 + +**入参:** 无 + +**返回值:** 无 + +**必须实现:** 是 + +### 初始化接口调用 + +认证插件初始化时接口调用顺序如下: + +```plantuml +@startuml +start +if (loadPluginType() == Load) then (yes) +:setAuthCallback +setMessageCallback +setAppdata] +:init] +:初始化后其它方法都可以会被调用] +else (no) +endif +stop +@enduml + +``` -**数据协议:** +## JSON 数据协议 -默认返回的数据: +插件与登录器之间通过 JSON 格式进行消息交互。这些协议在 `message()` 方法和 `messageCallback()` 函数中使用。 + +### 指令方向说明 + +- **登录器→插件**:登录器主动发送给插件的协议(插件在 `message()` 方法中处理) +- **插件→登录器**:插件主动发送给登录器的协议(插件通过 `messageCallback()` 函数发送) + +### 默认响应格式 | 字段 | 类型 | 说明 | | :------ | :----- | :--------------- | | Code | int | 0 成功,其他失败 | | Message | string | 提示消息 | -例: +示例: ```json { @@ -455,64 +664,80 @@ virtual QString message(const QString &) } ``` -1. **用户发生变化(CurrentUserChanged)** - 说明:程序启动或者切换用户的时候登录器会主动发送此消息,告知认证插件当前正在认证的用户是谁。 +### 🔴 核心协议 -请求: +核心协议是认证插件必须关注的基础通信协议,确保插件能够正常集成到登录系统中。 -| 字段 | 类型 | 值 | 说明 | -| :-------- | :----- | :----------------- | :--------------- | -| CmdType | string | CurrentUserChanged | | -| Data | object | - | | -| Data.Name | string | - | 当前用户的用户名 | +#### IsPluginEnabled(登录器 ⟶ 插件) -示例: +登录器查询插件是否启用。 + +**请求:** + +| 字段 | 类型 | 值 | 说明 | +| :------ | :----- | :---------------- | :--- | +| CmdType | string | "IsPluginEnabled" | 插件没有实现则默认为ture | + +**响应:** + +| 字段 | 类型 | 必填项 | 说明 | +| :------------------- | :----- | :----- | :--------------- | +| Code | int | 否 | 0 成功,其他失败 | +| Message | string | 否 | 提示消息 | +| Data | object | 是 | | +| Data.IsPluginEnabled | bool | 是 | 默认为 true | + +**示例:** ```json +// 请求 { - "CmdType": "CurrentUserChanged", + "CmdType": "IsPluginEnabled" +} + +// 响应 +{ + "Code": 0, + "Message": "Success", "Data": { - "Name": "uos" + "IsPluginEnabled": true } } ``` -返回值:默认数据。 +#### GetConfigs(登录器 ⟶ 插件) -2. **获取配置(GetConfigs)** - 支持认证插件来控制登录器其它的 UI 控件。 +登录器获取插件的UI控制配置。 -请求: +**请求:** -| 字段 | 类型 | 值 | 说明 | -| :------ | :----- | :------------- | :--- | -| CmdType | string | “GetConfigs” | | +| 字段 | 类型 | 值 | 说明 | +| :------ | :----- | :----------- | :--- | +| CmdType | string | "GetConfigs" | | -示例: +**响应:** + +| 字段 | 类型 | 必填项 | 说明 | +| :---------------------- | :----- | :----- | :----------------------------------------------------------- | +| Code | int | 否 | 0 成功,其他失败 | +| Message | string | 否 | 提示消息 | +| Data | object | 是 | | +| Data.ShowAvatar | bool | 否 | 是否显示用户头像 | +| Data.ShowUserName | bool | 否 | 是否显示用户名 | +| Data.ShowSwitchButton | bool | 否 | 是否显示认证类型切换按钮 | +| Data.ShowLockButton | bool | 否 | 是否显示解锁按钮 | +| Data.DefaultAuthLevel | int | 否 | 见`DefaultAuthLevel`枚举说明 | +| Data.SupportDefaultUser | bool | 否 | 是否支持默认用户登录 | + +**示例:** ```json +// 请求 { "CmdType": "GetConfigs" } -``` - -返回值: - -| 字段 | 类型 | 必填项 | 说明 | -| :---------------------- | :----- | :----- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| Code | int | 否 | 0 成功,其他失败 | -| Message | string | 否 | 提示消息 | -| Data | object | 是 | | -| Data.ShowAvatar | bool | 否 | 是否显示用户头像 | -| Data.ShowUserName | bool | 否 | 是否显示用户名 | -| Data.ShowSwitchButton | bool | 否 | 是否显示认证类型切换按钮 | -| Data.ShowLockButton | bool | 否 | 是否显示解锁按钮 | -| Data.DefaultAuthLevel | int | 否 | 0:如果获取到上次认证成功的类型,则默认使用上次登录成功的类型,否则根据系统默认的顺序来选择。`
`1:如果获取到上次认证成功的类型,则默认使用上次登录成功的类型,否则使用插件认证。`
`2:无论是否可以获取到上次认证成功的类型,都默认选择插件验证。`
`默认为 1 | -| Data.SupportDefaultUser | bool | 否 | 插件是否支持默认用户的登录。默认用户:即当前没有指定用户,默认用户为"...",一般在域管和服务器环境下会出现。`
`支持的场景:用户在登录界面扫描二维码,认证成功后插件得知是扫码人员的身份,与系统中的人员进行比对,将人员身份和认证结果返回给登录器,登录器可以登录此用户。`
`不支持的场景:必须先知道当前验证人员的身份,才能进行验证。`
`插件不处理此接口,则默认为支持。 | - 示例: - -```json +// 响应 { "Code": 0, "Message": "Success", @@ -526,63 +751,120 @@ virtual QString message(const QString &) } ``` -3. **认证开始(StartAuth)** - 登陆界面会有两次开启验证,一次是 deepin-authenticate 服务,还有一次是 lightdm,插件需要等到这两个验证都开启后再发送验证结果。 - 锁屏界面只有 deepin-authenticate 会发起一次验证,插件需要等 deepin-authenticate 开启验证后再发送验证结果。 - 这两种验证类型都会在插件加载后的很短的一段时间后开启验证(1s 以内),主要是处理指纹一键登录的需求。如果插件发送验证结果的时间较晚(比如扫码、输入验证码等)则无需关心此消息。 +#### CurrentUserChanged(用户变化通知) -请求: +登录器通知插件当前用户发生变化。 -| 字段 | 类型 | 值 | 说明 | -| :------------------ | :----- | :-------- | :-------------------------- | -| CmdType | string | StartAuth | | -| Data | object | | | -| Data.AuthObjectType | int | | 见 `AuthObjectType`的说明 | +**请求:** -示例: +| 字段 | 类型 | 值 | 说明 | +| :-------- | :----- | :----------------- | :----------------- | +| CmdType | string | CurrentUserChanged | | +| Data | object | - | | +| Data.Name | string | - | 当前用户的用户名 | +| Data.Uid | int | - | 当前用户的ID | + +**响应:** 默认响应格式 + +**示例:** ```json +// 请求 { - "CmdType": "StartAuth", + "CmdType": "CurrentUserChanged", "Data": { - "AuthObjectType": 1 + "Name": "uos", + "Uid": 1001 } } ``` -返回值:默认数据。 +#### SetAuthTypeInfo(设置认证类型信息) -4. **认证状态(AuthState)** - 当前登录器的认证状态 +插件向登录器发送认证类型相关信息。 -请求: +**请求:** -| 字段 | 类型 | 值 | 说明 | -| :------------- | :----- | :---------- | :--------------------- | -| CmdType | string | "AuthState" | | -| Data | object | | | -| Data.AuthType | int | | 见 `AuthType`的说明 | -| Data.AuthState | int | | 见 `AuthState`的说明 | +| 字段 | 类型 | 值 | 说明 | +|:-----|:-----|:---|:-----| +| CmdType | string | "setAuthTypeInfo" | ⚠️ 这里首字母是小写(历史原因) | +| Data | object | - | | +| Data.AuthType | int | - | 见 `AuthType` 枚举说明 | -示例: +**示例:** ```json +// 请求 { - "CmdType": "AuthState", - "Data": { - "AuthType": 1, - "AuthState": 0 - - } + "CmdType": "SetAuthTypeInfo", + "Data": { + "AuthType": 1 + } } ``` -返回值:默认数据。 -5. **限制信息(LimitsInfo)** - 所有认证类型的限制信息 +### 🟡 扩展协议 + +扩展协议提供额外的功能特性,开发者可根据实际需求选择性实现,用于增强插件的功能表现。 + +#### GetProperties(登录器 ⟶ 插件) + +登录器获取系统属性信息。 + +**请求:** -请求: +| 字段 | 类型 | 值 | 说明 | +| :------ | :----- | :------------------------ | :----------------------------------------------------------- | +| CmdType | string | "GetProperties" | | +| Data | array | ["AppType","CurrentUser"] | 支持的属性列表,API 2.0.0+ 支持 "AppType", "CurrentUser" | + +**响应:** + +| 字段 | 类型 | 说明 | +| :-------------------- | :----- | :-------------------- | +| Code | int | 0 成功,其他失败 | +| Message | string | 提示消息 | +| Data | object | - | +| Data.AppType | int | 详见 AppType 枚举说明 | +| Data.CurrentUser | object | 当前用户信息 | +| Data.CurrentUser.Name | string | 当前用户的用户名 | +| Data.CurrentUser.Uid | int | 当前用户的ID | + +#### StartAuth(登录器 ⟶ 插件) + +登录器通知插件开始认证。 + +**请求:** + +| 字段 | 类型 | 值 | 说明 | +| :------------------ | :----- | :-------- | :-------------------------- | +| CmdType | string | StartAuth | | +| Data | object | | | +| Data.AuthObjectType | int | | 见 `AuthObjectType`的说明 | + +**响应:** 默认响应格式 + +#### AuthState(登录器 ⟶ 插件) + +登录器发送当前认证状态。 + +**请求:** + +| 字段 | 类型 | 值 | 说明 | +| :------------- | :----- | :---------- | :----------------------- | +| CmdType | string | "AuthState" | | +| Data | object | | | +| Data.AuthType | int | | 见 `AuthType`的说明 | +| Data.AuthState | int | | 见 `AuthState`的说明 | + +**响应:** 默认响应格式 + +#### LimitsInfo(登录器 ⟶ 插件) + +登录器发送认证限制信息。 + +**请求:** | 字段 | 类型 | 值 | 说明 | | :----------------- | :----- | :----------- | :------------------------ | @@ -595,148 +877,166 @@ virtual QString message(const QString &) | Object.UnlockSecs | int | | 还剩多久解锁 | | Object.UnlockTime | string | | 解锁时间 | -示例: +**响应:** 默认响应格式 -```json -{ - "CmdType": "LimitsInfo", - "Data": [ - { - "Flag": 1, - "Locked": false, - "MaxTries": 5, - "NumFailures": 0, - "UnlockSecs": 0, - "UnlockTime": "0001-01-01T00:00:00Z" - }, - { - "Flag": 2, - "Locked": false, - "MaxTries": 3, - "NumFailures": 0, - "UnlockSecs": -1, - "UnlockTime": "0001-01-01T00:00:00Z" - } - ] -} -``` +#### AccountError(登录器 ⟶ 插件) -返回值:默认数据。 +登录器通知插件账户验证出现错误。 -6. **是否启用插件** - 登录器在开始验证的时候会向插件发起此请求,插件自行决定现在是否要启用插件,登录器默认插件是启用的。 +**请求:** -请求: +| 字段 | 类型 | 值 | 说明 | +| :------ | :----- | :------------- | :------------------------------------------------- | +| CmdType | string | "AccountError" | 当账户信息错误的时候会发出这个信息,比如用户不存在,用户密码过期等 | -| 字段 | 类型 | 值 | 说明 | -| :------ | :----- | :---------------- | :--- | -| CmdType | string | "IsPluginEnabled" | | +**响应:** 默认响应格式 -示例: +#### AuthFactorsChanged(登录器 ⟶ 插件) -```json -{ - "CmdType": "IsPluginEnabled" -} -``` +登录器通知插件认证因子发生变化。 -返回值: +**请求:** -| 字段 | 类型 | 必填项 | 说明 | -| :------ | :----- | :----- | :--------------- | -| Code | int | 否 | 0 成功,其他失败 | -| Message | string | 否 | 提示消息 | -| Data | bool | 是 | 默认为 true | +| 字段 | 类型 | 值 | 说明 | +| :------ | :----- | :------------------ | :------- | +| CmdType | string | "AuthFactorsChanged" | | +| Data | int | | 认证因子 | -示例: +**响应:** 默认响应格式 -```json -{ - "Code": 0, - "Message": "Success", - "Data": { - "IsPluginEnabled": true - } -} -``` +#### ReadyToAuthChanged(插件 ⟶ 登录器) -### LoginModuleInterfaceV2 +插件通知登录器认证准备状态发生变化。 -#### setAuthCallback +**发送方式:** 插件通过 `messageCallback()` 发送 -**函数定义:** +**请求:** -```c++ -virtual void setAuthCallback(AuthCallbackFunc *) = 0 -``` +| 字段 | 类型 | 值 | 说明 | +| :------ | :----- | :------------------- | :----------- | +| CmdType | string | "ReadyToAuthChanged" | | +| Data | bool | | 是否准备就绪 | -**说明:** 设置回调函数,会在init函数之前调用。 +**响应:** 默认响应格式 -**入参:** +#### ReadyToAuth(登录器 ⟶ 插件) -| 类型 | 说明 | 备注 | -| :----------------- | :-------------------------------- | :--- | -| AuthCallbackFunc\* | 详见 `AuthCallbackFunc`类型说明 | | +登录器查询插件是否准备好进行认证。 -**返回值:** 无 +**请求:** -**必须实现:** 是 +| 字段 | 类型 | 值 | 说明 | +| :------ | :----- | :----------- | :--- | +| CmdType | string | "ReadyToAuth" | | -#### AuthCallbackFun +**响应:** -**函数定义:** +| 字段 | 类型 | 必填项 | 说明 | +| :--------------- | :----- | :----- | :--------------------------- | +| Code | int | 否 | 0 成功,其他失败 | +| Message | string | 否 | 提示消息 | +| Data | object | 是 | | +| Data.ReadyToAuth | bool | 是 | 是否准备好认证,默认true | -```c++ -void (*)(const AuthCallbackData *, void *) -``` +### ⚪ 特殊协议 -**说明:** 认证回调函数,用于插件返回认证的状态、结果等 +特殊协议专门用于处理复杂业务场景和特殊需求,适用于特殊定制化开发,按需实现。 -**入参:** +#### GetLevel(登录器 ⟶ 插件) -| 类型 | 说明 | 备注 | -| :----------------- | :-------------------------------- | :--- | -| AuthCallbackData\* | 认证相关信息 | | -| void\* | 即 LoginCallBack 的 app_data 字段 | | +用于多层级认证场景。 -**返回值:** 无 +**请求:** -#### reset +| 字段 | 类型 | 值 | 说明 | +| :------ | :----- | :-------- | :--- | +| CmdType | string | "GetLevel" | | -**函数定义:** +**响应:** -```c++ -virtual void reset() = 0 -``` +| 字段 | 类型 | 必填项 | 说明 | +| :--------- | :----- | :----- | :------------------- | +| Code | int | 否 | 0 成功,其他失败 | +| Message | string | 否 | 提示消息 | +| Data | object | 是 | | +| Data.Level | int | 是 | 插件层级,默认为1 | -**说明:** -插件需要在这个方法中重置UI和验证状态,通常验证开始之前登录器会调用这个方法,但是不保证每次验证前都会调用。插件必须实现这个函数,并在函数内重置之前的验证结果,避免将以前的结果应用在当前认证中。 +#### GetLoginType(登录器 ⟶ 插件) -**入参:** 无 +获取插件的登录类型。 -**返回值:** 无 +**请求:** -**必须实现:** 是 +| 字段 | 类型 | 值 | 说明 | +| :------ | :----- | :------------ | :--- | +| CmdType | string | "GetLoginType" | | -### 初始化接口调用 +**响应:** -认证插件初始化时接口调用顺序如下: +| 字段 | 类型 | 必填项 | 说明 | +| :------------- | :----- | :----- | :----------------------------------- | +| Code | int | 否 | 0 成功,其他失败 | +| Message | string | 否 | 提示消息 | +| Data | object | 是 | | +| Data.LoginType | int | 是 | 登录类型,见`CustomLoginType` | -```plantuml -@startuml -start -if (loadPluginType() == Load) then (yes) -:setAuthCallback -setMessageCallback -setAppdata] -:init] -:初始化后其它方法都可以会被调用] -else (no) -endif -stop -@enduml +#### HasSecondLevel(登录器 ⟶ 插件) -``` +检查指定用户是否需要第二层认证。 + +**请求:** + +| 字段 | 类型 | 值 | 说明 | +| :------ | :----- | :-------------- | :------- | +| CmdType | string | "HasSecondLevel" | | +| Data | string | | 用户名 | + +**响应:** + +| 字段 | 类型 | 必填项 | 说明 | +| :------------------ | :----- | :----- | :------------------- | +| Code | int | 否 | 0 成功,其他失败 | +| Message | string | 否 | 提示消息 | +| Data | object | 是 | | +| Data.HasSecondLevel | bool | 是 | 是否需要二层认证 | + +#### GetSessionTimeout(登录器 ⟶ 插件) + +获取插件自定义的会话超时时长。 + +**请求:** + +| 字段 | 类型 | 值 | 说明 | +| :------ | :----- | :----------------- | :--- | +| CmdType | string | "GetSessionTimeout" | | + +**响应:** + +| 字段 | 类型 | 必填项 | 说明 | +| :------------------ | :----- | :----- | :------------------------------- | +| Code | int | 否 | 0 成功,其他失败 | +| Message | string | 否 | 提示消息 | +| Data | object | 是 | | +| Data.SessionTimeout | int | 是 | 会话超时时长(毫秒),默认15000 | + +#### UpdateLoginType(登录器 ⟶ 插件) + +通知插件更新登录类型,登录类型信息依赖远程配置。 + +**请求:** + +| 字段 | 类型 | 值 | 说明 | +| :------ | :----- | :--------------- | :--- | +| CmdType | string | "UpdateLoginType" | | + +**响应:** + +| 字段 | 类型 | 必填项 | 说明 | +| :------------- | :----- | :----- | :------------------- | +| Code | int | 否 | 0 成功,其他失败 | +| Message | string | 否 | 提示消息 | +| Data | object | 是 | | +| Data.LoginType | int | 是 | 更新后的登录类型 | ## 编程实例 @@ -778,8 +1078,8 @@ find_package(DdeSessionShell REQUIRED) # find_package 命令还可以用来加载 cmake 的功能模块 # 并不是所有的库都直接支持 cmake 查找的,但大部分都支持了 pkg-config 这个标准, # 因此 cmake 提供了间接加载库的模块:FindPkgConfig, 下面这行命令表示加载 FindPkgConfig 模块, -# 这个 cmake 模块提供了额外的基于 “pkg-config” 加载库的能力 -# 执行下面的命令后后会设置如下变量,不过一般用不到: +# 这个 cmake 模块提供了额外的基于 "pkg-config" 加载库的能力 +# 执行下面的命令后会设置如下变量,不过一般用不到: # PKG_CONFIG_FOUND pkg-config 可执行文件是否找到了 # PKG_CONFIG_EXECUTABLE pkg-config 可执行文件的路径 # PKG_CONFIG_VERSION_STRING pkg-config 的版本信息 @@ -855,6 +1155,7 @@ public: void reset() override; void setAppData(AppDataPtr) override; void setAuthCallback(AuthCallbackFun) override; + QString message(const QString &) override; private: void initUI(); @@ -951,6 +1252,11 @@ void LoginModule::initUI() ### 元数据文件 +插件需要提供一个 JSON 格式的元数据文件,用于描述插件的基本信息和兼容性要求。 + +**文件名:** `login.json` + +**基本格式:** ```json { "api": "2.0.0", @@ -958,27 +1264,27 @@ void LoginModule::initUI() } ``` -# 安装 +### 二进制路径 -登录器会在启动的时候从 /usr/lib/dde-session-shell/modules 目录下加载后缀为.so 的插件,在开发的时候需要在 CMakeLists.txt 或.pro 文件中设置插件的安装路径为 /usr/lib/dde-session-shell/modules。 +登录器会在启动时从 `/usr/lib/dde-session-shell/modules` 目录下加载后缀为 `.so` 的插件。在开发时需要在 `CMakeLists.txt` 或 `.pro` 文件中设置插件的安装路径为 `/usr/lib/dde-session-shell/modules`。 # 安全性 -在整个流程中,登录器不参与用户身份的校验,用户身份的真实性和访问权限完全由认证插件来保证,所以在开发认证插件的时候对于安全性的考虑要全面且慎重。这是除了功能需求之外最重要的特性,下面列出一些常见的需要考量的问题: +在整个流程中,登录器不参与用户身份的校验,用户身份的真实性和访问权限完全由认证插件来保证。因此,在开发认证插件时对于安全性的考虑要全面且慎重。这是除了功能需求之外最重要的特性,下面列出一些常见的安全考量: -- 输入密码等认证信息应该使用加密手段保护数据的不被窃取以及中间人攻击。 -- 图形界面程序不应该把自己提权到 root 权限(含 capabilities)运行。 -- 存储的用户数据(如密码与隐私数据)应该加密保护且加密强度足够高。 -- 不应该未经用户允许保存或者传输用户数据。 +- 输入密码等认证信息必须使用加密手段防止数据被窃取和中间人攻击。 +- 图形界面程序不应该将自己提权到 root 权限(含 capabilities)运行。 +- 存储的用户数据(如密码与隐私数据)必须进行高强度加密保护。 +- 未经用户明确授权,不得保存或传输用户数据。 # 可靠性 -登录器会把插件加载到内存中,如果认证插件的质量不过关,出现指针错误、内存溢出、在 UI 线程做耗时操作等,会导致登录器崩溃、卡死,进而导致用户无法正常进入系统。登录器是系统的门户,如果用户无法正常使用登录器,将无法进入系统,这是致命性问题,故而在开发时要将可靠性做为重点去考量。 +登录器会将插件加载到内存中。如果认证插件的质量不过关,出现指针错误、内存溢出、在 UI 线程做耗时操作等问题,可能导致登录器崩溃或卡死,进而导致用户无法正常进入系统。由于登录器是系统的门户,如果用户无法正常使用登录器就无法进入系统,这是一个致命性问题。因此,在开发时必须将可靠性作为重点考虑因素。 -# 兼容性 +### API 版本兼容性 -认证插件的工程需要增加一个 json 文件,用来描述当前插件适配的 api 版本,在代码中使用 Q_PLUGIN_METADATA 将 json 文件设置为 metadata 文件,例如:Q_PLUGIN_METADATA(IID "com.deepin.dde.shell.Login" FILE "login.json")。登录器在加载插件的时候会解析 json 文件中的内容,获取 api 版本号。 -登录器会做到小版本向下兼容(1.x.x版本都互相兼容),如果出现无法兼容的情况(比如增加了接口头文件的虚函数,导致二进制不兼容),此时会修改大版本号(比如从1.x.x变更到2.x.x),登录器会比对 api 版本号,如果低于可兼容的版本号,则不会加载插件,以保证登录器本身能够正常运行。json 文件示例: +认证插件的工程需要增加一个 json 文件,用来描述当前插件适配的 api 版本,在代码中使用 Q_PLUGIN_METADATA 将 json 文件设置为 metadata 文件,例如:Q_PLUGIN_METADATA(IID "com.deepin.dde.shell.Login" FILE "login.json")。登录器在加载插件时会解析 JSON 文件中的内容,获取 API 版本号。 +登录器会做到小版本向下兼容(1.x.x 版本都互相兼容),如果出现无法兼容的情况(比如增加了接口头文件的虚函数,导致二进制不兼容),此时会修改大版本号(比如从 1.x.x 变更到 2.x.x)。登录器会比对 API 版本号,如果低于可兼容的版本号,则不会加载插件,以保证登录器本身能够正常运行。JSON 文件示例: ```c++ { @@ -987,4 +1293,4 @@ void LoginModule::initUI() } ``` -login_module_interface.h 中 API_VERSION 宏定义的字符串即为当前 api 的版本号,json 文件中 api 字段记录的版本号务必与 API_VERSION 保持一致。 +login_module_interface.h 中 API_VERSION 宏定义的字符串即为当前 API 的版本号,JSON 文件中 api 字段记录的版本号务必与 API_VERSION 保持一致。 diff --git a/files/wayland/lightdm-deepin-greeter-wayland b/files/wayland/lightdm-deepin-greeter-wayland index e9493a940..98f4caaeb 100755 --- a/files/wayland/lightdm-deepin-greeter-wayland +++ b/files/wayland/lightdm-deepin-greeter-wayland @@ -33,7 +33,7 @@ device_handle(){ local touchpad=$(dbus_values_get_bool "$dbus_touchpad") if [ "$touchpad" = "true" ]; then - dbus-send --session --dest=org.kde.KWin "$input" org.freedesktop.DBus.Properties.Set string:"org.kde.KWin.InputDevice" string:"tapToClick" variant:boolean:true + dbus-send --session --print-reply --dest=org.kde.KWin "$input" org.freedesktop.DBus.Properties.Set string:"org.kde.KWin.InputDevice" string:"tapToClick" variant:boolean:true fi } diff --git a/interface/base_module_interface.h b/interface/base_module_interface.h index 423217a6a..56f8be22c 100644 --- a/interface/base_module_interface.h +++ b/interface/base_module_interface.h @@ -38,7 +38,8 @@ class BaseModuleInterface LoginType, // 登陆插件 TrayType, // 托盘插件 FullManagedLoginType, // 全托管插件 - IpcAssistLoginType // 用于接收厂商密码插件 + IpcAssistLoginType, // 用于接收厂商密码插件 + PasswordExtendLoginType, // 密码认证扩展插件,需要两个都认证通过后 }; /** diff --git a/interface/login_module_interface.h b/interface/login_module_interface.h index cd273d1ea..90eff7cbe 100644 --- a/interface/login_module_interface.h +++ b/interface/login_module_interface.h @@ -75,6 +75,7 @@ enum AuthType { AT_FingerVein = 1 << 5, // 指静脉 AT_Iris = 1 << 6, // 虹膜 AT_Passkey = 1 << 7, // 安全密钥 + AT_Pattern = 1 << 8, // 手势 AT_PAM = 1 << 29, // PAM AT_Custom = 1 << 30, // 自定义 AT_All = -1 // all @@ -98,7 +99,9 @@ enum AuthState { AS_Ended, // 认证已结束,调用 End 之后,每种成功关闭的都会发送此信号,当某种认证类型被锁定时,也会触发此信号 AS_Locked, // 认证已锁定,当认证类型锁定时,触发此信号。该信号不会给出锁定等待时间信息 AS_Recover, // 设备恢复,需要调用 Start 重新开启认证,对应 AS_Exception - AS_Unlocked // 认证解锁,对应 AS_Locked + AS_Unlocked, // 认证解锁,对应 AS_Locked + AS_Unknown, // 未知状态 + AS_VerifyCode, // 需要验证码 }; /** diff --git a/lupdate.sh b/lupdate.sh index a878242b8..ba29ec638 100755 --- a/lupdate.sh +++ b/lupdate.sh @@ -1,4 +1,8 @@ #!/bin/bash -lupdate ./ -ts -no-obsolete -locations none translations/dde-session-shell_en.ts +lupdate ./interface ./src ./tests -ts -no-obsolete -locations none translations/dde-session-shell_en.ts +cd plugins/login-gesture +./lupdate.sh +cd ../../ + #tx push -s -b m20 # Push the files that need to be translated #tx pull -s -b m20 # Pull the translated files diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index edba21aee..a98068bd1 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -1,5 +1,6 @@ if (DISABLE_DSS_SNIPE) add_subdirectory(one-key-login) + add_subdirectory("login-gesture") endif() # add_subdirectory(examples) # add_subdirectory(assist_login) diff --git a/plugins/login-gesture/CMakeLists.txt b/plugins/login-gesture/CMakeLists.txt new file mode 100644 index 000000000..2ce96ce63 --- /dev/null +++ b/plugins/login-gesture/CMakeLists.txt @@ -0,0 +1,72 @@ +find_package(DtkWidget REQUIRED) +find_package(DtkCore REQUIRED) + +include(GNUInstallDirs) + +include_directories( + ${DFrameworkDBus_INCLUDE_DIRS} + global + src + resetDialog + utils +) + +link_libraries( + ${Qt_LIBS} + ${DtkWidget_LIBRARIES} + ${DtkCore_LIBRARIES} + crypt +) + +set(LIB_NAME login-gesture) +set(BIN_NAME reset-pattern-dialog) + +file(GLOB_RECURSE SHARED_SOURCE + ${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/utils/*.cpp +) + +file(GLOB_RECURSE LIB_SOURCE + ${CMAKE_CURRENT_SOURCE_DIR}/loginPlugin/*.cpp +) + +file(GLOB_RECURSE RESET_SOURCE + ${CMAKE_CURRENT_SOURCE_DIR}/resetDialog/*.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/resetDialog/*.qrc +) + +if (CMAKE_BUILD_TYPE STREQUAL "Debug") + ADD_DEFINITIONS(-DQM_FILES_DIR="${CMAKE_BINARY_DIR}/plugins/login-gesture/translations") +else() + ADD_DEFINITIONS(-DQM_FILES_DIR="/usr/share/dde-gesture-login/translations") +endif() + +set(QRCS + ${CMAKE_CURRENT_SOURCE_DIR}/gesture.qrc +) + +add_library(${LIB_NAME} SHARED + ${SHARED_SOURCE} + ${LIB_SOURCE} + ${QRCS} +) +set_target_properties(${LIB_NAME} PROPERTIES POSITION_INDEPENDENT_CODE ON) + +add_executable(${BIN_NAME} + ${SHARED_SOURCE} + ${RESET_SOURCE} + ${QRCS} +) + +execute_process(COMMAND bash "${CMAKE_CURRENT_SOURCE_DIR}/translate_generation.sh" + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) + +install(TARGETS ${LIB_NAME} LIBRARY DESTINATION lib/dde-session-shell/modules) +install(TARGETS ${BIN_NAME} DESTINATION lib/dde-control-center) + +file(GLOB QM_FILES "${CMAKE_CURRENT_SOURCE_DIR}/translations/*.qm") +install(FILES ${QM_FILES} DESTINATION share/dde-gesture-login/translations) + +install(FILES configs/login-gesture.json DESTINATION /lib/dde-session-shell/modules/config.d) +install(FILES configs/org.deepin.dde.dss-login-gesture.json DESTINATION ${CMAKE_INSTALL_DATADIR}/dsg/configs/org.deepin.dde.lock) +install(FILES configs/org.deepin.dde.dss-login-gesture.json DESTINATION ${CMAKE_INSTALL_DATADIR}/dsg/configs/org.deepin.dde.lightdm-deepin-greeter) \ No newline at end of file diff --git a/plugins/login-gesture/configs/login-gesture.json b/plugins/login-gesture/configs/login-gesture.json new file mode 100644 index 000000000..241eb1b8a --- /dev/null +++ b/plugins/login-gesture/configs/login-gesture.json @@ -0,0 +1,20 @@ +{ + "pluginEnabled": { + "lock": { + "dconfig": { + "appid": "org.deepin.dde.lock", + "resource": "org.deepin.dde.dss-login-gesture", + "subpath": "", + "key": "pluginEnabled" + } + }, + "greeter": { + "dconfig": { + "appid": "org.deepin.dde.lightdm-deepin-greeter", + "resource": "org.deepin.dde.dss-login-gesture", + "subpath": "", + "key": "pluginEnabled" + } + } + } +} \ No newline at end of file diff --git a/plugins/login-gesture/configs/org.deepin.dde.dss-login-gesture.json b/plugins/login-gesture/configs/org.deepin.dde.dss-login-gesture.json new file mode 100644 index 000000000..c209d0a92 --- /dev/null +++ b/plugins/login-gesture/configs/org.deepin.dde.dss-login-gesture.json @@ -0,0 +1,16 @@ +{ + "magic": "dsg.config.meta", + "version": "1.0", + "contents": { + "pluginEnabled": { + "value": false, + "serial": 0, + "flags": ["global"], + "name": "PluginEnabled", + "name[zh_CN]": "是否启用插件", + "description[zh_CN]": "是否启用插件,默认为否。", + "permissions": "readwrite", + "visibility": "private" + } + } +} diff --git a/plugins/login-gesture/gesture.qrc b/plugins/login-gesture/gesture.qrc new file mode 100644 index 000000000..298905902 --- /dev/null +++ b/plugins/login-gesture/gesture.qrc @@ -0,0 +1,5 @@ + + + icons/firstEnroll.svg + + diff --git a/plugins/login-gesture/icons/firstEnroll.svg b/plugins/login-gesture/icons/firstEnroll.svg new file mode 100644 index 000000000..ca0cf976e --- /dev/null +++ b/plugins/login-gesture/icons/firstEnroll.svg @@ -0,0 +1,35 @@ + + + icon/手势密码@2x + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/plugins/login-gesture/login.json b/plugins/login-gesture/login.json new file mode 100644 index 000000000..bec81f0da --- /dev/null +++ b/plugins/login-gesture/login.json @@ -0,0 +1,3 @@ +{ + "api": "2.0.0" +} diff --git a/plugins/login-gesture/loginPlugin/gestureloginmodule.cpp b/plugins/login-gesture/loginPlugin/gestureloginmodule.cpp new file mode 100644 index 000000000..7f21c5f35 --- /dev/null +++ b/plugins/login-gesture/loginPlugin/gestureloginmodule.cpp @@ -0,0 +1,230 @@ +// SPDX-FileCopyrightText: 2021 - 2024 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "gestureloginmodule.h" +#include "waypointmodel.h" +#include "modulewidget.h" +#include "userservice.h" +#include "gestureauthworker.h" +#include "gesturemodifyworker.h" + +using namespace gestureLogin; +using namespace gestureSetting; + +namespace dss { +namespace module_v2 { + +GestureLoginModule::GestureLoginModule(QObject *parent) + : QObject(parent) + , m_appData(nullptr) + , m_authCallback(nullptr) + , m_messageCallback(nullptr) + , m_loginWidget(nullptr) + , m_appType(AppType::Lock) +{ + setObjectName(QStringLiteral("gesture-login-plugin")); + // CAUTION: do not call initui here + // when object created, it's not in GUI thread +} + +GestureLoginModule::~GestureLoginModule() +{ + if (m_loginWidget) { + delete m_loginWidget.data(); + } +} + +void GestureLoginModule::init() +{ + updateInfo(); + initWorker(); +} + +QWidget *GestureLoginModule::content() +{ + return m_loginWidget; +} + +void GestureLoginModule::reset() +{ + initUI(); + init(); +} + +void GestureLoginModule::sendToken(const QString &token) +{ + AuthCallbackData data; + data.account = ""; + data.token = token; + data.result = 1; // 固定值,代表本地验证通过,通知dss去校验密码 + m_authCallback(&data, m_appData); +} + +void GestureLoginModule::enroll(const QString &token) +{ + UserService::instance()->enroll(token); +} + +void GestureLoginModule::initUI() +{ + if (m_loginWidget) { + qobject_cast(m_loginWidget)->reset(); + return; + } + + m_loginWidget = new ModuleWidget(); +} + +void GestureLoginModule::setAppData(AppDataPtr appData) +{ + m_appData = appData; +} + +void GestureLoginModule::setAuthCallback(AuthCallbackFun authCallback) +{ + m_authCallback = authCallback; +} + +void GestureLoginModule::setMessageCallback(MessageCallbackFunc messageCallback) +{ + m_messageCallback = messageCallback; +} + +void GestureLoginModule::updateInfo() +{ + if (!m_messageCallback) { + qWarning() << "message callback func is nullptr"; + return; + } + + // 发送获取属性的请求 + QJsonObject message; + message.insert("CmdType", "GetProperties"); + QJsonArray array; + array.append("AppType"); + array.append("CurrentUser"); + message["Data"] = array; + QJsonDocument doc; + doc.setObject(message); + QString ret = m_messageCallback(doc.toJson(), m_appData); + + // 解析返回值 + QJsonParseError jsonParseError; + const QJsonDocument retDoc = QJsonDocument::fromJson(ret.toLatin1(), &jsonParseError); + if (jsonParseError.error != QJsonParseError::NoError || retDoc.isEmpty()) { + qWarning() << "Failed to analysis AppType info from shell!: " << ret; + return; + } + + QJsonObject obj = retDoc.object(); + if (obj.value("Code").toInt() != 0) { + qWarning() << "Get properties failed, message: " << obj.value("Message").toString(); + return; + } + + QJsonObject data = obj.value("Data").toObject(); + if (data.contains("AppType")) { + m_appType = data.value("AppType").toInt(); + qInfo() << "App type: " << m_appType; + } + + if (data.contains("CurrentUser")) { + QJsonObject user = data.value("CurrentUser").toObject(); + updateUser(user.value("Name").toString()); + qInfo() << "Current user: " << user; + } +} + +void GestureLoginModule::initWorker() +{ + UserService::instance()->setUserName(m_userName); + + auto model = WayPointModel::instance(); + auto gestureState = model->getGestureState(); + + // 无手势,直接开始录入 + if (gestureState == GestureState::NotSet || gestureState == GestureState::SetAndRemoved) { + model->setCurrentMode(Mode::Enroll); + } else { + model->setCurrentMode(Mode::Auth); + } + + // 创建认证流程控制 + auto connectType = Qt::AutoConnection | Qt::UniqueConnection; + // 注意只创建一个对象 + static GestureAuthWorker *authWorker = new GestureAuthWorker(this); + authWorker->setActive(model->currentMode() == Mode::Auth); + connect(this, &GestureLoginModule::onAuthStateChanged, authWorker, &GestureAuthWorker::onAuthStateChanged, Qt::ConnectionType(connectType)); + connect(this, &GestureLoginModule::onLimitsInfoChanged, authWorker, &GestureAuthWorker::onLimitsInfoChanged, Qt::ConnectionType(connectType)); + connect(authWorker, &GestureAuthWorker::requestSendToken, this, &GestureLoginModule::sendToken, Qt::ConnectionType(connectType)); + + // 创建录入流程控制 + static GestureModifyWorker *enrollWorker = new GestureModifyWorker(this); + enrollWorker->setActive(model->currentMode() == Mode::Enroll); + connect(enrollWorker, &GestureModifyWorker::requestSaveToken, this, &GestureLoginModule::enroll, Qt::ConnectionType(connectType)); +} + +// 是否可以由dss去控制这个配置变化 +void GestureLoginModule::updateUser(const QString &userName) +{ + if (m_userName == userName) { + return; + } + + m_userName = userName; + initWorker(); +} + +QString GestureLoginModule::message(const QString &message) +{ + QJsonParseError jsonParseError; + const QJsonDocument messageDoc = QJsonDocument::fromJson(message.toLatin1(), &jsonParseError); + if (jsonParseError.error != QJsonParseError::NoError || messageDoc.isEmpty()) { + qWarning() << "Failed to obtain message from shell!: " << message; + return ""; + } + + QJsonObject retObj; + retObj["Code"] = 0; + retObj["Message"] = "Success"; + + QJsonObject msgObj = messageDoc.object(); + QString cmdType = msgObj.value("CmdType").toString(); + QJsonObject data = msgObj.value("Data").toObject(); + qInfo() << "Cmd type: " << cmdType; + if (cmdType == "CurrentUserChanged") { + updateUser(data.value("Name").toString()); + qDebug() << "Current user changed, user name: " << m_userName; + } else if (cmdType == "GetConfigs") { + QJsonObject retDataObj; + retDataObj["ShowAvatar"] = false; + retDataObj["ShowUserName"] = false; + retDataObj["ShowSwitchButton"] = false; + retDataObj["ShowLockButton"] = false; + retDataObj["DefaultAuthLevel"] = DefaultAuthLevel::Default; + retDataObj["DisableOtherAuthentications"] = false; + retDataObj["AuthType"] = AuthType::AT_Pattern; + retObj["Data"] = retDataObj; + } else if (cmdType == "AuthState") { + int authType = data.value("AuthType").toInt(); + int authState = data.value("AuthState").toInt(); + // 处理认证结果 + // 注意录入相关不在此处理 + Q_EMIT onAuthStateChanged(authType, authState); + } else if (cmdType == "LimitsInfo") { + bool locked = data.value("Locked").toBool(); + int maxTries = data.value("MaxTries").toInt(); + int numFailures = data.value("NumFailures").toInt(); + int unlockSecs = data.value("UnlockSecs").toInt(); + QString unlockTime = data.value("UnlockTime").toString(); + Q_EMIT onLimitsInfoChanged(locked, maxTries, numFailures, unlockSecs, unlockTime); + } + + QJsonDocument doc; + doc.setObject(retObj); + return doc.toJson(); +} + +} // namespace module_v2 +} // namespace dss diff --git a/plugins/login-gesture/loginPlugin/gestureloginmodule.h b/plugins/login-gesture/loginPlugin/gestureloginmodule.h new file mode 100644 index 000000000..f06814f7e --- /dev/null +++ b/plugins/login-gesture/loginPlugin/gestureloginmodule.h @@ -0,0 +1,70 @@ +// SPDX-FileCopyrightText: 2021 - 2024 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef GESTURELOGINMODULE_H +#define GESTURELOGINMODULE_H + +#include "login_module_interface_v2.h" + +#include +#include + +class GesturePannel; + +namespace dss { +namespace module_v2 { + +class GestureLoginModule : public QObject + , public LoginModuleInterfaceV2 +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID "com.deepin.dde.shell.Modules_v2.Login" FILE "login.json") + Q_INTERFACES(dss::module_v2::LoginModuleInterfaceV2) + +public: + enum DefaultAuthLevel { + NoDefault = 0, + Default, + StrongDefault + }; + + explicit GestureLoginModule(QObject *parent = nullptr); + ~GestureLoginModule() override; + + void init() override; + inline QString key() const override { return objectName(); } + QWidget *content() override; + inline QString icon() const override { return "code-gesture"; } + void setAppData(AppDataPtr) override; + void setAuthCallback(AuthCallbackFun) override; + void setMessageCallback(MessageCallbackFunc) override; + QString message(const QString &) override; + void reset() override; + +public Q_SLOTS: + void sendToken(const QString &); + void enroll(const QString &); + +Q_SIGNALS: + void onAuthStateChanged(int, int); + void onLimitsInfoChanged(bool, int, int, int, const QString &); + +private: + void initUI(); + void updateInfo(); + void initWorker(); + void updateUser(const QString &); + +private: + AppDataPtr m_appData; + AuthCallbackFun m_authCallback; + MessageCallbackFunc m_messageCallback; + QPointer m_loginWidget; + QString m_userName; + int m_appType; +}; + +} // namespace module_v2 +} // namespace dss +#endif // GESTURELOGINMODULE_H diff --git a/plugins/login-gesture/lupdate.sh b/plugins/login-gesture/lupdate.sh new file mode 100755 index 000000000..1f477afbf --- /dev/null +++ b/plugins/login-gesture/lupdate.sh @@ -0,0 +1,5 @@ +#!/bin/bash +lupdate ./ -ts -no-obsolete -locations none translations/login-gesture_en.ts +lupdate ./ -ts -no-obsolete -locations none translations/login-gesture_zh_CN.ts +lupdate ./ -ts -no-obsolete -locations none translations/login-gesture_zh_TW.ts +lupdate ./ -ts -no-obsolete -locations none translations/login-gesture_zh_HK.ts diff --git a/plugins/login-gesture/resetDialog/gesturedialog.cpp b/plugins/login-gesture/resetDialog/gesturedialog.cpp new file mode 100644 index 000000000..af5258354 --- /dev/null +++ b/plugins/login-gesture/resetDialog/gesturedialog.cpp @@ -0,0 +1,260 @@ +// SPDX-FileCopyrightText: 2021 - 2024 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "gesturedialog.h" +#include "gesturepannel.h" +#include "modulewidget.h" +#include "resetpatterncontroller.h" +#include "waypointmodel.h" +#include "translastiondoc.h" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +using namespace gestureSetting; +using namespace gestureLogin; + +GestureDialog::GestureDialog(ResetPatternController *worker, QWidget *parent) + : QWidget(parent) + , m_handle(new DPlatformWindowHandle(this)) + , m_titleBar(new QWidget(this)) + , m_mainContent(new QWidget(this)) + , m_mainLayout(new QStackedLayout(m_mainContent)) + , m_authWidget(new ModuleWidget(m_mainContent)) + , m_successWidget(new QWidget(m_mainContent)) + , m_confirmButton(nullptr) + , m_worker(worker) +{ + initUi(); + initConnection(); +} + +GestureDialog::~GestureDialog() {} + +void GestureDialog::initUi() +{ + setAccessibleName("ResetGesturePage"); + + m_handle->setEnableSystemMove(false); + m_handle->setShadowOffset(QPoint(5, 5)); + m_handle->setShadowRadius(20); + + m_titleBar->setFixedHeight(50 * qApp->devicePixelRatio()); + QHBoxLayout *layoutTitle = new QHBoxLayout(m_titleBar); + layoutTitle->setContentsMargins(10, 0, 10, 0); + layoutTitle->setSpacing(0); + // 警告图标 + layoutTitle->addWidget(createTitleIcon(QIcon::fromTheme("dialog-warning"))); + layoutTitle->addStretch(); + // 右侧关闭按钮 + QLabel *closeButton = createTitleIcon(QIcon::fromTheme("dialog-close")); + closeButton->setCursor(Qt::PointingHandCursor); + closeButton->setObjectName("close_button"); + closeButton->installEventFilter(this); + layoutTitle->addWidget(closeButton); + + auto *mainContentLayout = new QVBoxLayout(this); + mainContentLayout->setContentsMargins(0, 0, 0, 0); + mainContentLayout->addWidget(m_titleBar, 0, Qt::AlignTop); + mainContentLayout->addSpacing(30); + mainContentLayout->addWidget(m_mainContent); + mainContentLayout->addWidget(createCancelButton(), 0, Qt::AlignBottom); + + m_mainContent->setMinimumHeight(605); + m_mainLayout->addWidget(m_authWidget); + createSuccessWidget(); + m_mainLayout->addWidget(m_successWidget); + m_mainLayout->setCurrentWidget(m_authWidget); +} + +void GestureDialog::initConnection() +{ + // token输入完成并且已经调用父进程写入 + connect(m_worker, &ResetPatternController::inputFinished, this, &GestureDialog::onInputFinished); + connect(m_confirmButton, &QPushButton::clicked, this, &GestureDialog::close); + m_titleBar->installEventFilter(this); +} + +QWidget* GestureDialog::createCancelButton() +{ + auto *cancelWidget = new QWidget(this); + QHBoxLayout *layout = new QHBoxLayout(cancelWidget); + layout->setContentsMargins(10, 0, 10, 10); + m_confirmButton = new QPushButton(TranslastionDoc::instance()->getDoc(DocIndex::Cancel), cancelWidget); + m_confirmButton->setAttribute(Qt::WA_NoMousePropagation); + layout->addWidget(m_confirmButton); + + return cancelWidget; +} + +QLabel *GestureDialog::createTitleIcon(const QIcon &icon) +{ + QLabel *label = new QLabel(m_titleBar); + QPixmap pixmap = icon.pixmap(QSize(40, 40) * qApp->devicePixelRatio()); + pixmap.setDevicePixelRatio(qApp->devicePixelRatio()); + label->setFixedHeight(40 * qApp->devicePixelRatio()); + label->setPixmap(pixmap); + return label; +} + +void GestureDialog::createSuccessWidget() +{ + TranslastionDoc *transDoc = TranslastionDoc::instance(); + auto *resultLayout = new QVBoxLayout(m_successWidget); + resultLayout->setContentsMargins(0, 8, 0, 0); + + QLabel *labelTitle = new QLabel(m_successWidget); + DFontSizeManager::instance()->bind(labelTitle, DFontSizeManager::T3); + labelTitle->setAlignment(Qt::AlignCenter); + labelTitle->setText(transDoc->getDoc(DocIndex::SetPasswd)); + resultLayout->addWidget(labelTitle); + + auto *indicatorWidget = new QWidget(m_successWidget); + indicatorWidget->setFixedHeight(128); + indicatorWidget->setObjectName("indicatorSuccess"); + indicatorWidget->installEventFilter(this); + + resultLayout->addSpacing(10); + resultLayout->addWidget(indicatorWidget); + + auto *labelSuccess = new QLabel(transDoc->getDoc(DocIndex::EnrollDone), m_successWidget); + DFontSizeManager::instance()->bind(labelSuccess, DFontSizeManager::T5); + labelSuccess->setAlignment(Qt::AlignHCenter); + resultLayout->setSpacing(35); + resultLayout->addWidget(labelSuccess); + resultLayout->addStretch(); +} + +bool GestureDialog::eventFilter(QObject *watched, QEvent *event) +{ + if (watched == m_titleBar) { + switch (event->type()) { + case QEvent::MouseButtonPress: { + QMouseEvent *mouseEvent = static_cast(event); + if ((mouseEvent->buttons().testFlag(Qt::LeftButton))) { + m_dragPosition = mapFromGlobal(mouseEvent->globalPos()); + } + } break; + case QEvent::MouseButtonRelease: { + m_dragPosition = QPoint(); + } break; + case QEvent::MouseMove: { + // 如果按下的点为空,则不做任何处理 + if (m_dragPosition.isNull()) + break; + + QMouseEvent *mouseEvent = static_cast(event); + int x = mouseEvent->globalX() - m_dragPosition.x(); + int y = mouseEvent->globalY() - m_dragPosition.y(); + + move(x, y); + } break; + default: break; + } + } else if (watched->isWidgetType()) { + QWidget *widget = qobject_cast(watched); + if (widget) { + if (widget->objectName() == "indicatorSuccess" && event->type() == QEvent::Paint) { + // 绘制图标 + QSvgRenderer renderer(QString(":/success.svg")); + QPixmap pixmap(QSize(128, 128) * qApp->devicePixelRatio()); + pixmap.fill(Qt::transparent); + QPainter painter; + painter.begin(&pixmap); + painter.setRenderHints(QPainter::SmoothPixmapTransform | QPainter::Antialiasing); + painter.setPen(Qt::NoPen); + renderer.render(&painter); + painter.end(); + QSize pixmapSize(widget->height(), widget->height()); + QRect pixmapRect((widget->width() - pixmapSize.width()) / 2, + (widget->height() - pixmapSize.height()) / 2, + widget->height(), widget->height()); + painter.begin(widget); + painter.drawPixmap(pixmapRect, pixmap); + painter.end(); + } else if (widget->objectName() == "close_button" + && event->type() == QEvent::MouseButtonRelease) { + close(); + } + } + } + + return QWidget::eventFilter(watched, event); +} + +void GestureDialog::onInputFinished() +{ + // 两次校验通过 + m_mainLayout->setCurrentWidget(m_successWidget); + m_confirmButton->setText(TranslastionDoc::instance()->getDoc(DocIndex::Ok)); +} + +ResetPatternController *Manager::m_controller = nullptr; + +Manager::Manager(int old_file_id, int new_file_id, QObject *parent) + : QObject (parent) + , m_gestureDialog(nullptr) + , m_old_file_id (old_file_id) + , m_new_file_id(new_file_id) +{ +} + +Manager::~Manager() +{ + free(); + m_gestureDialog->deleteLater(); +} + +void Manager::start() +{ + if (m_gestureDialog) + return; + + if (!m_controller) { + m_controller = new ResetPatternController; + if (m_old_file_id >= 0) + m_controller->setOldPasswordFileId(m_old_file_id); + + if (m_new_file_id >= 0) + m_controller->setNewPasswordFileId(m_new_file_id); + + m_controller->start(); + } + + m_gestureDialog = new GestureDialog(m_controller); + + QDesktopWidget *desktopWidget = QApplication::desktop(); + m_gestureDialog->setFixedSize(380, 566); + int x = (desktopWidget->size().width() - m_gestureDialog->width()) / 2; + int y = (desktopWidget->size().height() - m_gestureDialog->height()) / 2; + m_gestureDialog->move(x, y); + m_gestureDialog->setWindowFlags(m_gestureDialog->windowFlags() | Qt::FramelessWindowHint); + m_gestureDialog->show(); +} + +void Manager::exit(int retCode) +{ + free(); + qDebug() << "exit code" << retCode; + qApp->quit(); +} + +void Manager::free() +{ + qDebug() << "free manager"; + if (m_controller) { + m_controller->close(); + m_controller->deleteLater(); + } +} diff --git a/plugins/login-gesture/resetDialog/gesturedialog.h b/plugins/login-gesture/resetDialog/gesturedialog.h new file mode 100644 index 000000000..9dcbcbc36 --- /dev/null +++ b/plugins/login-gesture/resetDialog/gesturedialog.h @@ -0,0 +1,82 @@ +// SPDX-FileCopyrightText: 2021 - 2024 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef GESTUREDIALOG_H +#define GESTUREDIALOG_H + +#include + +DWIDGET_USE_NAMESPACE + +class QStackedLayout; +class ResetPatternController; + +namespace gestureLogin { +class GesturePannel; +class ModuleWidget; +} + +DWIDGET_BEGIN_NAMESPACE +class DTitlebar; +class DPlatformWindowHandle; +DWIDGET_END_NAMESPACE + +namespace gestureSetting { + +class GestureDialog : public QWidget +{ + Q_OBJECT + +public: + explicit GestureDialog(ResetPatternController *worker, QWidget *parent = nullptr); + ~GestureDialog() override; + +protected: + bool eventFilter(QObject *watched, QEvent *event) override; // 过滤鼠标事件 + +private: + void initUi(); + void initConnection(); + void createSuccessWidget(); + QWidget* createCancelButton(); + QLabel *createTitleIcon(const QIcon &icon); + +private slots: + void onInputFinished(); + +private: + DPlatformWindowHandle *m_handle; + QWidget *m_titleBar; + QWidget *m_mainContent; + QStackedLayout *m_mainLayout; + gestureLogin::ModuleWidget *m_authWidget; + QWidget *m_successWidget; + QPushButton *m_confirmButton; + ResetPatternController *m_worker; + QPoint m_dragPosition; +}; + +class Manager : public QObject +{ + Q_OBJECT + +public: + explicit Manager(int old_file_id, int new_file_id, QObject *parent = nullptr); + ~Manager() override; + + void start(); + + static void exit(int retCode); + static void free(); + +private: + static ResetPatternController *m_controller; + GestureDialog *m_gestureDialog; + int m_old_file_id; + int m_new_file_id; +}; + +} // namespace gestureSetting + +#endif // GESTUREDIALOG_H diff --git a/plugins/login-gesture/resetDialog/main.cpp b/plugins/login-gesture/resetDialog/main.cpp new file mode 100644 index 000000000..ea524ae89 --- /dev/null +++ b/plugins/login-gesture/resetDialog/main.cpp @@ -0,0 +1,138 @@ +// SPDX-FileCopyrightText: 2018 - 2022 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "gesturedialog.h" + +#include +#include + +#include +#include +#include + +#include + +DCORE_USE_NAMESPACE +DWIDGET_USE_NAMESPACE + +using namespace gestureLogin; +using namespace gestureSetting; + +const bool IsWayland = qgetenv("XDG_SESSION_TYPE").contains("wayland"); + +double getScaleFormConfig() +{ + double defaultScaleFactor = 1.0; + QDBusInterface configInter("com.deepin.system.Display", + "/com/deepin/system/Display", + "com.deepin.system.Display", + QDBusConnection::systemBus()); + if (!configInter.isValid()) { + return defaultScaleFactor; + } + QDBusReply configReply = configInter.call("GetConfig"); + if (configReply.isValid()) { + QString config = configReply.value(); + QJsonParseError jsonError; + QJsonDocument jsonDoc(QJsonDocument::fromJson(config.toLatin1(), &jsonError)); + if (jsonError.error == QJsonParseError::NoError) { + QJsonObject rootObj = jsonDoc.object(); + QJsonObject Config = rootObj.value("Config").toObject(); + double scaleFactor = Config.value("ScaleFactors").toObject().value("ALL").toDouble(); + qDebug() << "Scale factor from system display config: " << scaleFactor; + if(scaleFactor == 0.0) { + scaleFactor = defaultScaleFactor; + } + return scaleFactor; + } + return defaultScaleFactor; + } + + qWarning() << "DBus call `GetConfig` failed, reply is invaild, error: " << configReply.error().message(); + return defaultScaleFactor; +} + +void setQtScaleFactorEnv() +{ + const double scaleFactor = getScaleFormConfig(); + qDebug() << "Final scale factor: " << scaleFactor; + if (scaleFactor > 0.0) { + qputenv("QT_SCALE_FACTOR", QByteArray::number(scaleFactor).toStdString().c_str()); + } else { + qputenv("QT_SCALE_FACTOR", "1"); + } +} + +int main(int argc, char *argv[]) +{ + if (qEnvironmentVariableIsEmpty("XDG_CURRENT_DESKTOP")) { + qputenv("XDG_CURRENT_DESKTOP", "Deepin"); + } + + if (IsWayland) { + setQtScaleFactorEnv(); + } + + QString user(qgetenv("USER")); + if (!user.isEmpty()) { + QString runtimeDirStr = QString("/tmp/runtime-%1").arg(user); + QFileInfo runtimeDir(runtimeDirStr); + if (runtimeDir.exists()) { + if (runtimeDir.isDir() && runtimeDir.owner() == user) { + qputenv("XDG_RUNTIME_DIR", runtimeDirStr.toUtf8()); + qputenv("DBUS_SESSION_BUS_ADDRESS", QString("%1/bus").arg(runtimeDirStr).toUtf8()); + } + } else { + qputenv("XDG_RUNTIME_DIR", runtimeDirStr.toUtf8()); + qputenv("DBUS_SESSION_BUS_ADDRESS", QString("%1/bus").arg(runtimeDirStr).toUtf8()); + } + } + + DApplication a(argc, argv); + a.setQuitOnLastWindowClosed(true); + a.setAttribute(Qt::AA_EnableHighDpiScaling, true); + a.setAttribute(Qt::AA_UseHighDpiPixmaps, true); + + a.setOrganizationName("deepin"); + a.setApplicationName("reset-pattern-dialog"); + + // 由于这个应用是由deepin-password-admin管理,因此需要指定日志目录 + QString logPath = QString("/tmp/%1/%1.log").arg(a.applicationName()); + QDir logDir = QFileInfo(logPath).dir(); + if (!logDir.exists()) + logDir.mkdir(logDir.path()); + DLogManager::setlogFilePath(logPath); + DLogManager::registerFileAppender(); + DLogManager::registerConsoleAppender(); + + QCommandLineOption oldPwdOption(QStringList() << "old", "read current password which set it", "oldPasswordId"); + QCommandLineOption newPwdOption(QStringList() << "new", "write the new password which you will set", "newPasswordId"); + QCommandLineOption userOption(QStringList() << "u", "caller's name", "user"); + QCommandLineOption fOption(QStringList() << "f", "caller's full name", "f"); + QCommandLineOption aOption(QStringList() << "a", "call's app", "a"); + QCommandLineParser parser; + parser.addOption(oldPwdOption); + parser.addOption(newPwdOption); + parser.addOption(userOption); + parser.addOption(fOption); + parser.addOption(aOption); + parser.process(a); + + + int old_file_id = -1; + int new_file_id = -1; + if (parser.isSet(oldPwdOption)) { + old_file_id = parser.value(oldPwdOption).toInt(); + } + if (parser.isSet(newPwdOption)) { + new_file_id = parser.value(newPwdOption).toInt(); + } + Manager manager(old_file_id, new_file_id); + manager.start(); + + std::signal(SIGTERM, Manager::exit); + std::signal(SIGKILL, Manager::exit); + + return a.exec(); +} diff --git a/plugins/login-gesture/resetDialog/resetdialog.qrc b/plugins/login-gesture/resetDialog/resetdialog.qrc new file mode 100644 index 000000000..388fff898 --- /dev/null +++ b/plugins/login-gesture/resetDialog/resetdialog.qrc @@ -0,0 +1,5 @@ + + + success.svg + + diff --git a/plugins/login-gesture/resetDialog/resetpatterncontroller.cpp b/plugins/login-gesture/resetDialog/resetpatterncontroller.cpp new file mode 100644 index 000000000..e449174ed --- /dev/null +++ b/plugins/login-gesture/resetDialog/resetpatterncontroller.cpp @@ -0,0 +1,106 @@ +// SPDX-FileCopyrightText: 2021 - 2024 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "resetpatterncontroller.h" +#include "gestureauthworker.h" +#include "gesturemodifyworker.h" +#include "waypointmodel.h" +#include "translastiondoc.h" + +#include +#include + +using namespace gestureLogin; +using namespace gestureSetting; + +ResetPatternController::ResetPatternController(QObject *parent) + : QObject(parent) + , m_fileId(0) + , m_file(new QFile(this)) + , m_authWorker(new gestureLogin::GestureAuthWorker(this)) + , m_modifyWorker(new gestureSetting::GestureModifyWorker(this)) +{ + initConnection(); +} + +ResetPatternController::~ResetPatternController() +{ +} + +void ResetPatternController::setOldPasswordFileId(int fileId) +{ + QFile file; + if (!file.open(fileId, QFile::ReadOnly)) { + qWarning() << "Failed to open file for writing, fileId:" << fileId; + return; + } + m_localPassword = file.readLine(); + // 需要去掉后面的\n,否则无法校验通过 + m_localPassword = m_localPassword.trimmed(); + m_authWorker->setLocalPassword(m_localPassword); +} + +void ResetPatternController::setNewPasswordFileId(int fileId) +{ + m_fileId = fileId; +} + +void ResetPatternController::start() +{ + auto *wayPointModel = WayPointModel::instance(); + + if (m_localPassword.isEmpty()) { + // 如果本地密码为空,则不进入校验阶段,直接请求密码 + wayPointModel->setCurrentMode(Mode::Enroll); + m_modifyWorker->setActive(true); + } else { + // 如果本地密码不为空,则先进行本地密码校验 + wayPointModel->setCurrentMode(Mode::Auth); + m_authWorker->setActive(true); + } +} + +void ResetPatternController::close() +{ + if (m_file->isOpen()) { + m_file->close(); + } +} + +void ResetPatternController::initConnection() +{ + auto *wayPointModel = WayPointModel::instance(); + + connect(wayPointModel, &WayPointModel::authDone, this, [this, wayPointModel] { + // 如果当前是重置密码模式,不做任何处理 + if (wayPointModel->currentMode() == Mode::Enroll) + return; + + // 停止认证的worker + m_authWorker->setActive(false); + // 密码验证通过后,设置当前为重置密码模式 + wayPointModel->setCurrentMode(Mode::Enroll); + // 进入设置密码页面后,设置标题和提示内容 + m_modifyWorker->setActive(true); + }); + + connect(m_modifyWorker, &GestureModifyWorker::inputFinished, this, &ResetPatternController::inputFinished); + connect(m_modifyWorker, &GestureModifyWorker::requestSaveToken, this, &ResetPatternController::onSaveToken); +} + +void ResetPatternController::onSaveToken(const QString &token) +{ + // 向父进程写入token + if (m_file->isOpen()) + m_file->close(); + + if (!m_file->open(m_fileId, QFile::WriteOnly)) { + qWarning() << "Failed to open file for writing, fileId:" << m_fileId; + return; + } + + qDebug() << "Writing token"; + QByteArray tmpData = QString("%1\n").arg(token).toLocal8Bit(); + m_file->write(tmpData, tmpData.length()); +} diff --git a/plugins/login-gesture/resetDialog/resetpatterncontroller.h b/plugins/login-gesture/resetDialog/resetpatterncontroller.h new file mode 100644 index 000000000..20ae4dbac --- /dev/null +++ b/plugins/login-gesture/resetDialog/resetpatterncontroller.h @@ -0,0 +1,49 @@ +// SPDX-FileCopyrightText: 2021 - 2024 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef RESETPATTERNCONTROLLER_H +#define RESETPATTERNCONTROLLER_H + +#include + +class QFile; + +namespace gestureLogin { +class GestureAuthWorker; +} + +namespace gestureSetting { +class GestureModifyWorker; +} + +class ResetPatternController : public QObject +{ + Q_OBJECT + +public: + explicit ResetPatternController(QObject *parent = nullptr); + ~ResetPatternController() override; + void setOldPasswordFileId(int fileId); + void setNewPasswordFileId(int fileId); + void start(); + void close(); + +signals: + void inputFinished(); // 第二次输入token合法并完成 + +private: + void initConnection(); + +private slots: + void onSaveToken(const QString &token); + +private: + int m_fileId; + QString m_localPassword; + QFile *m_file; + gestureLogin::GestureAuthWorker *m_authWorker; + gestureSetting::GestureModifyWorker *m_modifyWorker; +}; + +#endif // RESETPATTERNCONTROLLER_H diff --git a/plugins/login-gesture/resetDialog/success.svg b/plugins/login-gesture/resetDialog/success.svg new file mode 100644 index 000000000..3e5e1461e --- /dev/null +++ b/plugins/login-gesture/resetDialog/success.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/plugins/login-gesture/src/gestureauthworker.cpp b/plugins/login-gesture/src/gestureauthworker.cpp new file mode 100644 index 000000000..a905780b7 --- /dev/null +++ b/plugins/login-gesture/src/gestureauthworker.cpp @@ -0,0 +1,260 @@ +// SPDX-FileCopyrightText: 2021 - 2024 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "gestureauthworker.h" +#include "waypointmodel.h" +#include "login_module_interface_v2.h" +#include "translastiondoc.h" +#include "tokenCrypt.h" + +#include + +using namespace gestureLogin; + +// 负责手势认证过程 +GestureAuthWorker::GestureAuthWorker(QObject *parent) + : QObject(parent) + , m_userService(nullptr) + , m_stateMachine(nullptr) + +{ + m_unLockTimer.setSingleShot(true); + m_remaindTimer.setSingleShot(false); + m_remaindTimer.setInterval(1000 * 60); + + initConnection(); +} + +void GestureAuthWorker::setLocalPassword(const QString &localPassword) +{ + m_localPasswd = localPassword; +} + +bool GestureAuthWorker::gestureEnabled() +{ + return true; +} + +bool GestureAuthWorker::gestureExist() +{ + return true; +} + +void GestureAuthWorker::setActive(bool active) +{ + if (active) { + initState(); + } else { + if (m_stateMachine) { + m_stateMachine->stop(); + m_stateMachine->deleteLater(); + m_stateMachine = nullptr; + } + } +} + +void GestureAuthWorker::initConnection() +{ + WayPointModel *model = WayPointModel::instance(); + connect(model, &WayPointModel::gestureStateChanged, this, [this, model](int state) { + if (model->appType() == ModelAppType::LoginLock) { + bool enrolled = state == GestureState::Set; + setActive(enrolled); + model->setCurrentMode(enrolled ? Mode::Auth : Mode::Enroll); + } + }); + + // 只处理认证过程 + connect(model, &WayPointModel::pathDone, this, [this, model] { + // 仅处理认证过程 + auto mode = model->currentMode(); + auto type = model->appType(); + if (mode == Mode::Auth) { + // 在登录锁屏上解锁 + if (type == ModelAppType::LoginLock) { + qDebug() << "worker send token"; + Q_EMIT requestSendToken(model->getToken()); + } + + // 重置密码对话框上的认证是一个单纯的本地密文对比的过程 + if (type == ModelAppType::Reset) { + QString token = gestureEncrypt::cryptUserPassword(model->getToken(), m_localPasswd.toUtf8()); + // 从外部获取的密文与本地对比 + if (token == m_localPasswd) { + Q_EMIT model->authDone(); + } else { + Q_EMIT model->authError(); + } + } + } + }); +} + +void GestureAuthWorker::initState() +{ + if (m_stateMachine) { + delete m_stateMachine; + m_stateMachine = nullptr; + } + + m_stateMachine = new QStateMachine(m_stateMachine); + + auto model = WayPointModel::instance(); + auto tanslator = TranslastionDoc::instance(); + + QString startTitle = tanslator->getDoc(model->appType() == ModelAppType::Reset ? DocIndex::ModifyPasswd : DocIndex::LoginStart); + + // 开始认证 + auto startState = new QState(m_stateMachine); + startState->assignProperty(model, "title", startTitle); + startState->assignProperty(model, "tip", tanslator->getDoc(model->appType() == ModelAppType::Reset ? DocIndex::DrawCurrentPasswd : DocIndex::RequestDrawing)); + // 认证失败,需要更新锁定时间与错误次数 + auto authError = new QState(m_stateMachine); + auto authErrTip = model->errorTextStyle().arg(tanslator->getDoc(DocIndex::AuthErrorWithTimes)); + switch (model->appType()) { + case ModelAppType::LoginLock: + // authError->assignProperty(model, "title", tanslator->getDoc(DocIndex::LoginStart)); + // authError->assignProperty(model, "tip", authErrTip); + break; + case ModelAppType::Reset: + authError->assignProperty(model, "title", tanslator->getDoc(DocIndex::ModifyPasswd)); + authError->assignProperty(model, "tip", tanslator->getDoc(DocIndex::ContactAdmin)); + break; + default: + break; + } + + // 输入错误提示 + auto inputError = new QState(m_stateMachine); + inputError->assignProperty(model, "title", startTitle); + auto pathErrorTip = model->errorTextStyle().arg(tanslator->getDoc(DocIndex::PathError)); + inputError->assignProperty(model, "tip", pathErrorTip); + + startState->addTransition(model, SIGNAL(pathError()), inputError); + startState->addTransition(model, SIGNAL(authError()), authError); + startState->addTransition(model, SIGNAL(authDone()), startState); + + inputError->addTransition(model, SIGNAL(authDone()), startState); + inputError->addTransition(model, SIGNAL(authError()), authError); + inputError->addTransition(model, SIGNAL(pathError()), inputError); + + authError->addTransition(model, SIGNAL(pathError()), inputError); + authError->addTransition(model, SIGNAL(authDone()), startState); + authError->addTransition(model, SIGNAL(authError()), authError); + + m_stateMachine->setInitialState(startState); + m_stateMachine->start(); +} + +void GestureAuthWorker::onAuthStateChanged(int authType, int authState) +{ + using dss::module::AuthType; + using dss::module::AuthState; + + if (WayPointModel::instance()->currentMode() != Mode::Auth) { + return; + } + + if (authType == AuthType::AT_All && authState == AuthState::AS_Success) { + Q_EMIT WayPointModel::instance()->authDone(); + } + + if (authType != AuthType::AT_Pattern) { + return; + } + + switch (authState) { + case AuthState::AS_Success: + Q_EMIT WayPointModel::instance()->authDone(); + break; + case AuthState::AS_Failure: + Q_EMIT WayPointModel::instance()->authError(); + break; + case AuthState::AS_Locked: + // 认证发送的lock状态,代表当前认证锁定 + WayPointModel::instance()->setLocked(true); + break; + case AuthState::AS_Unlocked: + // 认证发送的unlock,代表当前认证解锁 + WayPointModel::instance()->setLocked(false); + break; + case AuthState::AS_Started: + // 插件框架响应的计时器重置认证,代表当前认证解锁 + WayPointModel::instance()->setLocked(false); + break; + default: + break; + } +} + +// 这里只处理UI响应,model的状态设置交给authStatus控制 +void GestureAuthWorker::onLimitsInfoChanged(bool locked, int maxTries, int numFailures, int unlockSecs, const QString &unlockTime) +{ + auto model = WayPointModel::instance(); + if (model->currentMode() != Mode::Auth) { + return; + } + + do { + auto translator = TranslastionDoc::instance(); + // 未锁定状态 + if (numFailures && maxTries && numFailures < maxTries && !unlockSecs) { + uint remains = static_cast(maxTries - numFailures); + auto remainStr = model->errorTextStyle().arg(translator->getDoc(DocIndex::AuthErrorWithTimes).arg(remains)); + model->setProperty("tip", remainStr); + break; + } + + m_unLockTimer.stop(); + m_unLockTimer.disconnect(); + + m_remaindTimer.stop(); + m_remaindTimer.disconnect(); + + model->setProperty("title", TranslastionDoc::instance()->getDoc(DocIndex::LoginStart)); + model->setProperty("tip", TranslastionDoc::instance()->getDoc(DocIndex::RequestDrawing)); + + // unlockSecs 是唯一判断条件 + if (unlockSecs) { + qDebug() << "input should be locked from limit info content"; + // 显示锁定时间 + uint intervalSeconds = QDateTime::fromString(unlockTime, Qt::ISODateWithMs).toLocalTime().toSecsSinceEpoch() + - QDateTime::currentDateTimeUtc().toSecsSinceEpoch(); + // 每次设置时,强制更新计时器 + m_unLockTimer.setInterval(static_cast(intervalSeconds) * 1000); + connect(&m_unLockTimer, &QTimer::timeout, this, [this] { + m_remaindTimer.stop(); + // 信号通知解除界面禁用 + auto model = WayPointModel::instance(); + model->setProperty("title", TranslastionDoc::instance()->getDoc(DocIndex::LoginStart)); + model->setProperty("tip", TranslastionDoc::instance()->getDoc(DocIndex::RequestDrawing)); + }); + + m_lockMinutes = unlockSecs / 60; + if (unlockSecs % 60) { + m_lockMinutes += 1; + } + + auto lockString = translator->getDoc(DocIndex::DeviceLock).arg(m_lockMinutes); + model->setProperty("title", lockString); + model->setProperty("tip", model->errorTextStyle().arg(translator->getDoc(DocIndex::DeviceLockHelp))); + + if (m_lockMinutes) { + connect(&m_remaindTimer, &QTimer::timeout, this, [this] { + // 更新锁定时间 + m_lockMinutes--; + if (m_lockMinutes) { + auto errStr = TranslastionDoc::instance()->getDoc(DocIndex::DeviceLock).arg(m_lockMinutes); + WayPointModel::instance()->setProperty("title", errStr); + } + }); + } + + m_remaindTimer.start(); + m_unLockTimer.start(); + + break; + } + } while (false); +} diff --git a/plugins/login-gesture/src/gestureauthworker.h b/plugins/login-gesture/src/gestureauthworker.h new file mode 100644 index 000000000..3bc25abf8 --- /dev/null +++ b/plugins/login-gesture/src/gestureauthworker.h @@ -0,0 +1,53 @@ +// SPDX-FileCopyrightText: 2021 - 2024 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef GESTUREWORKER_H +#define GESTUREWORKER_H + +#include +#include +#include +#include + +namespace gestureLogin { +class GestureAuthWorker : public QObject +{ + Q_OBJECT + +public: + GestureAuthWorker(QObject *parent = nullptr); + void setLocalPassword(const QString &localPassword); + + // 功能已开启 + bool gestureEnabled(); + + // 手势已录入 + bool gestureExist(); + + void setActive(bool); + +private: + void initConnection(); + void initState(); + +public Q_SLOTS: + void onAuthStateChanged(int, int); + void onLimitsInfoChanged(bool, int, int, int, const QString &); + +Q_SIGNALS: + void requestSendToken(const QString &); + +private: + QDBusInterface *m_userService; + QStateMachine *m_stateMachine; + QString m_localPasswd; + + int m_lockMinutes; + + QTimer m_remaindTimer; + QTimer m_unLockTimer; +}; +} // namespace gestureLogin + +#endif // GESTUREWORKER_H diff --git a/plugins/login-gesture/src/gesturemodifyworker.cpp b/plugins/login-gesture/src/gesturemodifyworker.cpp new file mode 100644 index 000000000..621713948 --- /dev/null +++ b/plugins/login-gesture/src/gesturemodifyworker.cpp @@ -0,0 +1,96 @@ +// SPDX-FileCopyrightText: 2021 - 2024 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "gesturemodifyworker.h" +#include "waypointmodel.h" +#include "gestureauthworker.h" +#include "translastiondoc.h" + +using namespace gestureSetting; +using namespace gestureLogin; + +GestureModifyWorker::GestureModifyWorker(QObject *parent) + : QObject(parent) +{ + initConnect(); +} + +void GestureModifyWorker::setActive(bool active) +{ + // 无论进入还是离开这个状态都清空内部数据 + m_token = ""; + + auto *wayPointModel = WayPointModel::instance(); + auto connectType = Qt::AutoConnection | Qt::UniqueConnection; + if (active) { + connect(wayPointModel, &WayPointModel::pathError, this, &GestureModifyWorker::onPathError, Qt::ConnectionType(connectType)); + connect(wayPointModel, &WayPointModel::pathDone, this, &GestureModifyWorker::onPathDone, Qt::ConnectionType(connectType)); + wayPointModel->setProperty("title", TranslastionDoc::instance()->getDoc(DocIndex::SetPasswd)); + wayPointModel->setProperty("tip", TranslastionDoc::instance()->getDoc(DocIndex::RequestDrawing)); + } +} + +void GestureModifyWorker::onPathDone() +{ + auto *wayPointModel = WayPointModel::instance(); + + // 这里只处理录入密码的模式 + if (wayPointModel->currentMode() != Mode::Enroll) + return; + + QString token = wayPointModel->getToken(); + // 密码为空,一般是从密码验证成功后进入到当前重设密码的过程,此时无需继续 + if (token.isEmpty()) + return; + + TranslastionDoc *transDoc = TranslastionDoc::instance(); + wayPointModel->clearPath(); + + if (m_token.isEmpty()) { + m_token = token; + // 如果存储的token为空,说明是第一次设置密码成功,此时更改提示为(请重新录入手势密码) + wayPointModel->setProperty("tip", transDoc->getDoc(DocIndex::RequestDrawingAgain)); + } else if (m_token == token) { + // 如果token不为空且两次的token值相同,则认为本次校验通过 + Q_EMIT requestSaveToken(m_token); + Q_EMIT inputFinished(); + } else { + // 如果两次的token不同,回到上一次输入密码的界面来重新录入 + m_token.clear(); + // 给出提示,认为本次和上次校验不一致 + auto tip = wayPointModel->errorTextStyle().arg(transDoc->getDoc(DocIndex::Inconsistent)); + wayPointModel->setProperty("tip", tip); + // 2秒过后文案变为“请绘制手势密码” + QTimer::singleShot(2000, this, [=] { + // 请绘制手势密码 + wayPointModel->setProperty("tip", transDoc->getDoc(DocIndex::RequestDrawing)); + }); + } +} + +void GestureModifyWorker::onPathError() +{ + auto *wayPointModel = WayPointModel::instance(); + + // 这里只处理录入密码的模式 + if (wayPointModel->currentMode() != Mode::Enroll) + return; + + auto pathErrorTip = wayPointModel->errorTextStyle().arg(TranslastionDoc::instance()->getDoc(DocIndex::PathError)); + wayPointModel->setProperty("tip", pathErrorTip); + +} + +void GestureModifyWorker::initConnect() +{ + auto model = WayPointModel::instance(); + if (model->appType() == ModelAppType::LoginLock) { + connect(WayPointModel::instance(), &WayPointModel::gestureStateChanged, this, [&] (int state) { + setActive(state != GestureState::Set ? true : false); + }); + } + + connect(model, &WayPointModel::pathDone, this, &GestureModifyWorker::onPathDone); + connect(model, &WayPointModel::pathError, this, &GestureModifyWorker::onPathError); +} diff --git a/plugins/login-gesture/src/gesturemodifyworker.h b/plugins/login-gesture/src/gesturemodifyworker.h new file mode 100644 index 000000000..c6474bd70 --- /dev/null +++ b/plugins/login-gesture/src/gesturemodifyworker.h @@ -0,0 +1,37 @@ +// SPDX-FileCopyrightText: 2021 - 2024 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef GESTUREMODIFYWORKER_H +#define GESTUREMODIFYWORKER_H + +#include + +namespace gestureSetting { + +class GestureModifyWorker : public QObject +{ + Q_OBJECT + +public: + explicit GestureModifyWorker(QObject *parent = nullptr); + void setActive(bool active); + +signals: + void inputFinished(); // 第二次输入token合法并完成 + void requestSaveToken(const QString &token); // 请求保存token + +private slots: + void onPathDone(); + void onPathError(); + +private: + void initConnect(); + +private: + QString m_token; +}; + +} // namespace gestureSetting + +#endif // GESTUREMODIFYWORKER_H diff --git a/plugins/login-gesture/src/gesturepannel.cpp b/plugins/login-gesture/src/gesturepannel.cpp new file mode 100644 index 000000000..87b47c450 --- /dev/null +++ b/plugins/login-gesture/src/gesturepannel.cpp @@ -0,0 +1,165 @@ +// SPDX-FileCopyrightText: 2021 - 2024 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "gesturepannel.h" + +#include "waypointwidget.h" +#include "waypointmodel.h" + +#include +#include +#include +#include +#include +#include +#include + +using namespace gestureLogin; + +static int itemsAccount = 9; + +GesturePannel::GesturePannel(QWidget *parent) + : QWidget(parent) + , m_pathStarted(false) +{ + setFocusPolicy(Qt::NoFocus); + setMouseTracking(true); + setFixedSize(250, 250); + setWindowFlag(Qt::FramelessWindowHint); + setAttribute(Qt::WA_TranslucentBackground); + setContentsMargins(0, 0, 0, 0); + init(); + initConnect(); +} + +void GesturePannel::init() +{ + if (!itemsAccount) { + return; + } + + qreal root = qSqrt(itemsAccount); + int column = static_cast(root); + + // 只处理N*N的排列图形 + if (itemsAccount % column != 0) { + return; + } + + int solidSpaceWidth = this->width() / column; + int fixWidth = (solidSpaceWidth - 50) / 2; + + m_itemRects.clear(); + for (int index = 0; index < itemsAccount; index++) { + auto wid = new WayPointWidget(this); + wid->setFocusPolicy(Qt::NoFocus); + wid->setId(index); + // 布局 + int x = this->contentsRect().top() + solidSpaceWidth * (index % column); + int y = this->contentsRect().left() + solidSpaceWidth * (index / column); + wid->setFixedSize(52, 52); + wid->move(x + fixWidth * (index % column), y + fixWidth * (index / column)); + m_widRects.insert(wid, wid->geometry()); + m_itemRects[index] = wid->geometry(); + + connect(WayPointModel::instance(), &WayPointModel::selected, wid, &WayPointWidget::onSelectedById); + connect(wid, &WayPointWidget::arrived, WayPointModel::instance(), &WayPointModel::onPathArrived); + connect(this, &GesturePannel::pathStarted, wid, &WayPointWidget::onPathStarted); + connect(this, &GesturePannel::pathStopped, wid, &WayPointWidget::onPathStopped); + } +} + +void GesturePannel::initConnect() +{ + connect(WayPointModel::instance(), &WayPointModel::selected, this, [this](int, bool) { + update(); + }); + connect(WayPointModel::instance(), &WayPointModel::widgetColorChanged, this, [this] { + update(); + }); + + connect(this, &GesturePannel::pathStopped, WayPointModel::instance(), &WayPointModel::onUserInputDone); + + connect(WayPointModel::instance(), &WayPointModel::inputLocked, this, &QWidget::setDisabled); + setDisabled(WayPointModel::instance()->locked()); +} + +void GesturePannel::reset() +{ +} + +void GesturePannel::mousePressEvent(QMouseEvent *event) +{ + if (!WayPointModel::instance()->locked()) { + // 起点不一定在结点上,仅记录起始点,交给mouseMove处理连接 + releaseMouse(); + // 当输入小于4个点时,将保留界面直到用户再输入 + WayPointModel::instance()->clearPath(); + m_pathStarted = true; + Q_EMIT pathStarted(event->pos()); + + m_currentPos = event->pos(); + + update(); + } + + QWidget::mousePressEvent(event); +} + +void GesturePannel::mouseReleaseEvent(QMouseEvent *event) +{ + if (!WayPointModel::instance()->locked()) { + m_pathStarted = false; + Q_EMIT pathStopped(); + + update(); + } + + QWidget::mouseReleaseEvent(event); +} + +// 最后一个结点的连接线 +void GesturePannel::mouseMoveEvent(QMouseEvent *event) +{ + if (m_pathStarted) { + m_currentPos = event->pos(); + foreach (auto rect, m_widRects) { + if (rect.contains(m_currentPos)) { + auto wid = m_widRects.key(rect); + Q_EMIT wid->arrived(wid->id()); + } + } + update(); + } + + QWidget::mouseMoveEvent(event); +} + +// 选中点的样式变化自己去处理,这里只画连接线 +void GesturePannel::paintEvent(QPaintEvent *event) +{ + Q_UNUSED(event) + + if (WayPointModel::instance()->currentPoints().size()) { + QVector wayPoints; + auto currentSelected = WayPointModel::instance()->currentPoints(); + foreach (auto index, currentSelected) { + wayPoints.push_back(m_itemRects.value(index).center()); + } + + if (m_pathStarted) { + wayPoints.push_back(m_currentPos); + } + + auto model = WayPointModel::instance(); + auto colorConfig = model->colorConfig(); + QColor lineColor = model->showErrorStyle() ? colorConfig.warningLine : colorConfig.line; + + QPainter painter(this); + painter.setRenderHint(QPainter::Antialiasing); + QPen pen(lineColor, 2); + painter.setPen(pen); + painter.drawPolyline(wayPoints.data(), wayPoints.size()); + } +} diff --git a/plugins/login-gesture/src/gesturepannel.h b/plugins/login-gesture/src/gesturepannel.h new file mode 100644 index 000000000..4adc72e23 --- /dev/null +++ b/plugins/login-gesture/src/gesturepannel.h @@ -0,0 +1,69 @@ +// SPDX-FileCopyrightText: 2021 - 2024 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef GESTUREPANNEL_H +#define GESTUREPANNEL_H + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace gestureLogin { +class WayPointWidget; + +class GesturePannel : public QWidget +{ + Q_OBJECT + +public: + explicit GesturePannel(QWidget *parent = nullptr); + + void init(); + void initConnect(); + + inline bool getStarted() const { return m_pathStarted; } + void reset(); + +Q_SIGNALS: + void pathStarted(const QPoint &); + void pathStopped(); + void gestureAuthFaile(); + void gestureAuthDone(); + + void inputError(); + void inputDone(); + + void authError(); + void authDone(); + +protected: + virtual void mousePressEvent(QMouseEvent *event) Q_DECL_OVERRIDE; + virtual void mouseReleaseEvent(QMouseEvent *event) Q_DECL_OVERRIDE; + virtual void mouseMoveEvent(QMouseEvent *event) Q_DECL_OVERRIDE; + + virtual void paintEvent(QPaintEvent *event) Q_DECL_OVERRIDE; + +private: + void initPannel(); + void initConnections(); + +private: + bool m_pathStarted; + QPainterPath m_gusturePath; + QPoint m_currentPos; + QMap m_itemRects; + + QStateMachine *m_stateMachine; + QMap m_widRects; +}; +} // namespace gestureLogin +#endif // GESTUREPANNEL_H diff --git a/plugins/login-gesture/src/modulewidget.cpp b/plugins/login-gesture/src/modulewidget.cpp new file mode 100644 index 000000000..24a186ec6 --- /dev/null +++ b/plugins/login-gesture/src/modulewidget.cpp @@ -0,0 +1,167 @@ +// SPDX-FileCopyrightText: 2021 - 2024 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "modulewidget.h" +#include "waypointmodel.h" +#include "translastiondoc.h" + +#include +#include + +#include +#include +#include + +using namespace gestureLogin; + +// 文案、图片与按钮 +// 考虑到 +ModuleWidget::ModuleWidget(QWidget *parent) + : QWidget(parent) + , m_title(nullptr) + , m_tip(nullptr) + , m_firstEnroll(nullptr) + , m_mainLayout(nullptr) + , m_startEnrollBtn(nullptr) + , m_inputWid(nullptr) + , m_layout(nullptr) +{ + this->setContentsMargins(0, 0, 0, 0); + + init(); + initConnection(); +} + +void ModuleWidget::reset() +{ +} + +void ModuleWidget::init() +{ + DGUI_USE_NAMESPACE + + auto model = WayPointModel::instance(); + m_title = new DLabel; + m_title->setFixedHeight(35); + + m_tip = new DTipLabel; + if (model->appType() == ModelAppType::LoginLock) { + m_tip->setForegroundRole(QPalette::ColorRole::Light); + } + + m_tip->setFixedHeight(26); + m_startEnrollBtn = new DPushButton; + m_startEnrollBtn->setFixedSize(200, 54); + + m_title->setVisible(false); + m_tip->setVisible(false); + m_startEnrollBtn->setVisible(false); + + DFontSizeManager::instance()->bind(m_title, DFontSizeManager::T3); + DFontSizeManager::instance()->bind(m_tip, DFontSizeManager::T5); + + m_iconLabel = new DLabel; + m_iconLabel->setVisible(false); + + m_inputWid = new GesturePannel; + m_inputWid->setVisible(false); + + m_layout = new QVBoxLayout; + this->setLayout(m_layout); + + if (model->getGestureState() == GestureState::NotSet && model->appType() == ModelAppType::LoginLock) { + showFirstEnroll(); + } else { + showUserInput(); + } +} + +void ModuleWidget::initConnection() +{ + auto model = WayPointModel::instance(); + + connect(model, &WayPointModel::titleChanged, m_title, [&](const QString &text) { + m_title->setVisible(true); + m_title->setEnabled(true); + m_title->setText(text); + }); + connect(model, &WayPointModel::tipChanged, m_tip, [&](const QString &text) { + m_tip->setVisible(true); + m_tip->setEnabled(true); + m_tip->setText(text); + }); +} + +void ModuleWidget::showFirstEnroll() +{ + DWIDGET_USE_NAMESPACE + + int count = m_layout->count(); + if (count) { + while (count != -1) { + m_layout->takeAt(--count); + } + } + + // 首次录入的布局 + m_layout->addStretch(50); + m_layout->addWidget(m_title, 0, Qt::AlignCenter); + m_layout->addSpacing(10); + m_layout->addWidget(m_tip, 0, Qt::AlignCenter); + m_layout->addSpacing(60); + + QPixmap iconPix = DHiDPIHelper::loadNxPixmap(":/icons/firstEnroll.svg"); + auto scaledPixmap = iconPix.scaled(120, 120, Qt::IgnoreAspectRatio, Qt::SmoothTransformation); + m_iconLabel->setPixmap(scaledPixmap); + m_layout->addWidget(m_iconLabel, 0, Qt::AlignCenter); + m_layout->addSpacing(130); + + m_layout->addWidget(m_startEnrollBtn, 0, Qt::AlignCenter); + + m_title->setVisible(true); + m_tip->setVisible(true); + m_startEnrollBtn->setVisible(true); + m_iconLabel->setVisible(true); + + m_layout->addStretch(100); + + // 固定文本,不参与动态变化 + auto trans = TranslastionDoc::instance(); + m_title->setText(trans->getDoc(DocIndex::Enabled)); + m_tip->setText(trans->getDoc(DocIndex::RequestFirstEnroll)); + m_startEnrollBtn->setText(trans->getDoc(DocIndex::SetPasswd)); + + connect(m_startEnrollBtn, &DPushButton::clicked, this, [this] { + showUserInput(); + }); +} + +void ModuleWidget::showUserInput() +{ + int count = m_layout->count(); + if (count) { + while (count != -1) { + m_layout->takeAt(--count); + } + } + + m_startEnrollBtn->setVisible(false); + m_iconLabel->setVisible(false); + + m_layout->setAlignment(Qt::AlignCenter); + m_layout->addWidget(m_title, 0, Qt::AlignCenter); + m_layout->addSpacing(10); + m_layout->addWidget(m_tip, 0, Qt::AlignCenter); + m_layout->addSpacing(40); + m_layout->addWidget(m_inputWid, 0, Qt::AlignCenter); + + m_title->setVisible(true); + m_tip->setVisible(true); + m_inputWid->setVisible(true); + + m_title->setText(WayPointModel::instance()->title()); + m_tip->setText(WayPointModel::instance()->tip()); + + m_layout->addStretch(100); +} diff --git a/plugins/login-gesture/src/modulewidget.h b/plugins/login-gesture/src/modulewidget.h new file mode 100644 index 000000000..bfa91218d --- /dev/null +++ b/plugins/login-gesture/src/modulewidget.h @@ -0,0 +1,48 @@ +// SPDX-FileCopyrightText: 2021 - 2024 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef LOGINWIDGET_H +#define LOGINWIDGET_H + +#include "gesturepannel.h" + +#include +#include +#include + +#include +#include + +DWIDGET_USE_NAMESPACE + +namespace gestureLogin { +class ModuleWidget : public QWidget +{ + Q_OBJECT + +public: + explicit ModuleWidget(QWidget *parent = nullptr); + void reset(); + +public Q_SLOTS: + void showUserInput(); + +private: + void init(); + void initConnection(); + void showFirstEnroll(); // 登录锁屏上首次设置 + +private: + // 大标题与小标题 + DLabel *m_title; + DLabel *m_tip; + QLayout *m_firstEnroll; + QLayout *m_mainLayout; + DPushButton *m_startEnrollBtn; + DLabel *m_iconLabel; + GesturePannel *m_inputWid; + QVBoxLayout *m_layout; +}; +} // namespace gestureLogin +#endif // LOGINWIDGET_H diff --git a/plugins/login-gesture/src/waypointmodel.cpp b/plugins/login-gesture/src/waypointmodel.cpp new file mode 100644 index 000000000..98ae22e44 --- /dev/null +++ b/plugins/login-gesture/src/waypointmodel.cpp @@ -0,0 +1,367 @@ +// SPDX-FileCopyrightText: 2021 - 2024 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "waypointmodel.h" +#include "tokenCrypt.h" + +#include +#include + +using namespace gestureLogin; + +// 明文内容字典 +const QMap kContent = {{0, "0"}, + {1, "1"}, + {2, "2"}, + {3, "3"}, + {4, "4"}, + {5, "5"}, + {6, "6"}, + {7, "7"}, + {8, "8"}}; + +WayPointModel::WayPointModel(QObject *parent) + : QObject(parent) + , m_size(9) // 3 * 3, 如果有需求,改从配置获取 + , m_currentMode(Mode::Auth) + , m_gestureStateFlag(-1) + , m_gestureEnable(false) + , m_showErrorStyle(false) + , m_locked(false) + , m_localeName("") +{ + initColorConfig(); + connect(this, &WayPointModel::authDone, this, &WayPointModel::clearPath); + connect(this, &WayPointModel::authError, this, &WayPointModel::clearPath); +} + +WayPointModel *WayPointModel::instance() +{ + static WayPointModel model; + return &model; +} + +const QList &WayPointModel::currentPoints() +{ + return m_selectedPoints; +} + +ModelAppType WayPointModel::appType() const +{ + auto name = QApplication::applicationName(); + if (name.contains("lock", Qt::CaseInsensitive) || name.contains("greeter", Qt::CaseInsensitive)) { + return ModelAppType::LoginLock; + } + + if (name.contains("reset", Qt::CaseInsensitive)) { + return ModelAppType::Reset; + } + + return ModelAppType::Unknow; +} + +bool WayPointModel::isLockApp() +{ + return QApplication::applicationName().contains("lock", Qt::CaseInsensitive); +} + +// 通过回调到查询 +bool WayPointModel::isGestureEnabled() +{ + return m_gestureEnable; +} + +const GestureColors &WayPointModel::colorConfig() +{ + return m_colorConfig; +} + +QString WayPointModel::errorTextStyle() const +{ + return "%1"; +} + +void WayPointModel::setLocaleName(const QString &locale) +{ + auto content = locale.split("."); + if (content.size()) { + auto name = content[0]; + if (m_localeName != name) { + m_localeName = name; + Q_EMIT localeChanged(m_localeName); + } + } +} + +void WayPointModel::initColorConfig() +{ + DGUI_USE_NAMESPACE; + // 登录锁屏样式无大变化 + if (appType() == ModelAppType::LoginLock) { + setColorByTheme(DGuiApplicationHelper::UnknownType); + } else { + // 对话框上存在明暗样式 + auto themeType = DGuiApplicationHelper::instance()->themeType(); + setColorByTheme(themeType); + + connect(DGuiApplicationHelper::instance(), &DGuiApplicationHelper::themeTypeChanged, this, [&] (DGuiApplicationHelper::ColorType t) { + setColorByTheme(t); + Q_EMIT widgetColorChanged(); + }); + } +} + +void WayPointModel::setColorByTheme(int type) +{ + DGUI_USE_NAMESPACE; + switch (type) { + case DGuiApplicationHelper::LightType: + m_colorConfig.line.setRgb(0, 129, 255); + m_colorConfig.unSelecteBoarder.setRgb(0, 0, 0, 0.3 * 255); + m_colorConfig.selectedBoarder.setRgb(0, 129, 255); + m_colorConfig.fill.setRgb(0, 129, 255, 0.1 * 255); + m_colorConfig.internalFill.setRgb(0, 129, 255); + m_colorConfig.warningFill.setRgb(255, 87, 54, 0.1 * 255); + m_colorConfig.warningLine.setRgb(255, 87, 54); + break; + case DGuiApplicationHelper::DarkType: + m_colorConfig.line.setRgb(0, 129, 255); + m_colorConfig.unSelecteBoarder.setRgb(255, 255, 255, 0.3 * 255); + m_colorConfig.selectedBoarder.setRgb(0, 129, 255); + m_colorConfig.fill.setRgb(0, 129, 255, 0.1 * 255); + m_colorConfig.internalFill.setRgb(0, 129, 255); + m_colorConfig.warningFill.setRgb(255, 87, 54, 0.1 * 255); + m_colorConfig.warningLine.setRgb(255, 87, 54); + break; + default: + m_colorConfig.line.setRgb(255, 255, 255); + m_colorConfig.unSelecteBoarder.setRgb(255, 255, 255, 0.74 * 255); + m_colorConfig.selectedBoarder.setRgb(255, 255, 255, 0.74 * 255); + m_colorConfig.fill.setRgb(255, 255, 255, 0.2 * 255); + m_colorConfig.internalFill.setRgb(255, 255, 255); + m_colorConfig.warningFill = m_colorConfig.fill; + m_colorConfig.warningLine = m_colorConfig.line; + break; + } +} + +QString WayPointModel::userName() const +{ + return m_userName; +} + +void WayPointModel::setUserName(const QString &name) +{ + m_userName = name; +} + +bool WayPointModel::isValidPath() const +{ + auto size = m_selectedPoints.size(); + return size >= 4; +} + +QString WayPointModel::getToken() const +{ + qDebug() << int(m_currentMode); + QString rst = ""; + foreach (auto index, m_selectedPoints) { + rst.append(kContent.value(index, "")); + } + + if (rst.isEmpty()) { + qDebug() << "skip crypt for empty input"; + return ""; + } + + if (appType() == ModelAppType::Reset && m_currentMode == Mode::Auth) + return rst; + + // 仅录入时加密,登录、解锁时由前端处理明文 + // 录入时两次需要使用相同盐值加密 + if (m_currentMode == Mode::Enroll || appType() == ModelAppType::Reset) { + static QString salt = gestureEncrypt::getSalt(); + rst = gestureEncrypt::cryptUserPassword(rst, salt.toLatin1()); + } + + return rst; +} + +Mode WayPointModel::currentMode() const +{ + return m_currentMode; +} + +void WayPointModel::setCurrentMode(Mode mode) +{ + m_currentMode = mode; + Q_EMIT modeChanged(m_currentMode); + + qDebug() << "model mode changed to:" << int(m_currentMode); +} + +void WayPointModel::setTitle(const QString &title) +{ + m_title = title; + Q_EMIT titleChanged(m_title); +} + +void WayPointModel::setTip(const QString &tip) +{ + m_tip = tip; + Q_EMIT tipChanged(m_tip); +} + +void WayPointModel::setGestureState(int flag) +{ + qDebug() << flag << m_gestureStateFlag; + if (m_gestureStateFlag != flag) { + m_gestureStateFlag = flag; + Q_EMIT gestureStateChanged(m_gestureStateFlag); + } +} + +void WayPointModel::setGestureEnabled(bool enable) +{ + m_gestureEnable = enable; +} + +int WayPointModel::getGestureState() +{ + return m_gestureStateFlag; +} + +void WayPointModel::onPathArrived(int index) +{ + if (index < 0) { + return; + } + + do { + const int current = index; + if (m_selectedPoints.contains(current)) { + // 如果索引与倒数第二个相同,代表撤消上一个选中 + if (m_selectedPoints.size() >= 2) { + if (m_selectedPoints.indexOf(current) == m_selectedPoints.size() - 2) { + // 先发信号再移除,注意顺序 + Q_EMIT selected(m_selectedPoints.last(), false); + m_selectedPoints.removeLast(); + + Q_EMIT dataChanged(); + } + } + break; + } + + // 不包含,判断是否有途经的点并优先插入 + if (!m_selectedPoints.size()) { + appendPath(current); + break; + } + + if (m_selectedPoints.size()) { + // 选中所有沿途点 + int currentX = current % 3; + int currentY = current / 3; + + auto last = m_selectedPoints.last(); + int lastX = last % 3; + int lastY = last / 3; + + auto step = (current - last) / qAbs(current - last); + + auto insertPassby = [&](int dx, int dy) { + auto passByX = lastX + dx; + auto passByY = lastY + dy; + + while (passByY != currentY || passByX != currentX) { + int passbyIndex = positionToIndex(passByX, passByY); + if (passbyIndex >= 0) { + appendPath(passbyIndex); + } + passByX += dx; + passByY += dy; + } + }; + + if (currentX == lastX) { + // 同列 + insertPassby(0, step); + } else if (currentY == lastY) { + // 同行 + insertPassby(step, 0); + } else if (qAbs(currentX - lastX) == qAbs(currentY - lastY)) { + // 同斜率 + auto stepX = (currentX - lastX) / qAbs(currentX - lastX); + auto stepY = (currentY - lastY) / qAbs(currentY - lastY); + insertPassby(stepX, stepY); + } + + // 插入路过的点后,判断当前点是否在期望的路径上 + last = m_selectedPoints.last(); + lastX = last % 3; + lastY = last / 3; + if (currentX == lastX || currentY == lastY || qAbs(currentX - lastX) == qAbs(currentY - lastY)) { + appendPath(current); + } + } + + } while (false); +} + +void WayPointModel::onUserInputDone() +{ + if (m_selectedPoints.size() < 4 || m_selectedPoints.size() > 9) { + m_showErrorStyle = true; + clearPath(); + Q_EMIT pathError(); + } else { + Q_EMIT pathDone(); + } +} + +void WayPointModel::clearPath() +{ + m_selectedPoints.clear(); + Q_EMIT selected(-1, false); // 清空所有结点 + Q_EMIT dataChanged(); + + m_showErrorStyle = false; +} + +void WayPointModel::setLocked(bool locked) +{ + if (m_locked != locked) { + m_locked = locked; + Q_EMIT inputLocked(m_locked); + } +} + +int WayPointModel::positionToIndex(int x, int y) +{ + if (x < 0 || y < 0) { + return -1; + } + + int index = static_cast(m_size) - 1; + while (index >= 0) { + int ix = index % 3; + int iy = index / 3; + if (ix == x && iy == y) { + return index; + } + + index--; + } + return -1; +} + +void WayPointModel::appendPath(int index) +{ + if (!m_selectedPoints.contains(index)) { + m_selectedPoints.push_back(index); + Q_EMIT selected(index, true); + Q_EMIT dataChanged(); + } +} diff --git a/plugins/login-gesture/src/waypointmodel.h b/plugins/login-gesture/src/waypointmodel.h new file mode 100644 index 000000000..9ab18c19c --- /dev/null +++ b/plugins/login-gesture/src/waypointmodel.h @@ -0,0 +1,146 @@ +// SPDX-FileCopyrightText: 2021 - 2024 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef WAYPOINTMODEL_H +#define WAYPOINTMODEL_H + +#include +#include +#include + +namespace gestureLogin { + +enum GestureState { + NotSet, + SetAndRemoved, + Set +}; + +enum class Mode { + Enroll, + Auth +}; + +enum class ModelAppType { + LoginLock, + Reset, + Unknow +}; + +struct GestureColors { + QColor line; + QColor unSelecteBoarder; + QColor selectedBoarder; + QColor fill; + QColor internalFill; + QColor warningFill; + QColor warningLine; +}; + +class WayPointModel : public QObject +{ + Q_OBJECT + + Q_PROPERTY(QString title MEMBER m_title WRITE setTitle NOTIFY titleChanged) + Q_PROPERTY(QString tip MEMBER m_tip WRITE setTip NOTIFY tipChanged) + +public: + static WayPointModel *instance(); + const QList ¤tPoints(); + + ModelAppType appType() const; + bool isLockApp(); + + // 响应userSwitch,重置校验过程与状态 + // 通过用户名查询user的dbuspath + QString userName() const; + void setUserName(const QString &); + + inline uint size() const { return m_size; } + + bool isValidPath() const; + QString getToken() const; + + Mode currentMode() const; + void setCurrentMode(Mode); + + void setTitle(const QString &); + void setTip(const QString &); + + inline QString title() const { return m_title; } + inline QString tip() const { return m_tip; } + inline bool locked() const { return m_locked; } + + void setGestureState(int); + void setGestureEnabled(bool); + + int getGestureState(); + bool isGestureEnabled(); + + inline bool showErrorStyle() { return m_showErrorStyle; } + const GestureColors& colorConfig(); + + QString errorTextStyle() const; + + void setLocaleName(const QString &); + inline QString localeName() const { return m_localeName; } + +public Q_SLOTS: + void onPathArrived(int); + void onUserInputDone(); + void clearPath(); + void setLocked(bool); + +Q_SIGNALS: + void inputLocked(bool); + + // 文案控制 + void titleChanged(const QString &); + void tipChanged(const QString &); + void widgetColorChanged(); + + void modeChanged(Mode); + // 录入 + void finished(); + + void pathError(); // 不满足条件 + void pathDone(); // 满足条件 + + // 校验的信号转发 + void authError(); // 验证失败 + void authDone(); // 验证成功 + + void dataChanged(); // 通知UI执行更新 + void selected(int, bool); // 单个点被选中或取消 + + void gestureStateChanged(int); + void localeChanged(const QString &); + +private: + explicit WayPointModel(QObject *parent = nullptr); + int positionToIndex(int, int); + void appendPath(int); + void removePath(int); + void initColorConfig(); + void setColorByTheme(int); + +private: + QList m_selectedPoints; + uint m_size; + Mode m_currentMode; + + QString m_userName; + QString m_title; + QString m_tip; + // 后端手势标志: 0 从未录入 1 已被重置 2 已录入 + int m_gestureStateFlag; + bool m_gestureEnable; + bool m_showErrorStyle; + GestureColors m_colorConfig; + bool m_locked; + QString m_localeName; +}; +} // namespace gestureLogin + +#endif // WAYPOINTMODEL_H diff --git a/plugins/login-gesture/src/waypointwidget.cpp b/plugins/login-gesture/src/waypointwidget.cpp new file mode 100644 index 000000000..7867b0d39 --- /dev/null +++ b/plugins/login-gesture/src/waypointwidget.cpp @@ -0,0 +1,93 @@ +// SPDX-FileCopyrightText: 2021 - 2024 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "waypointwidget.h" +#include "waypointmodel.h" + +#include +#include +#include + +using namespace gestureLogin; +WayPointWidget::WayPointWidget(QWidget *parent) + : QWidget(parent) + , m_isSelected(false) +{ + setContentsMargins(0, 0, 0, 0); + + setWindowFlag(Qt::FramelessWindowHint); + setAttribute(Qt::WA_TranslucentBackground); + + connect(WayPointModel::instance(), &WayPointModel::widgetColorChanged, this, [this] { + update(); + }); +} + +void WayPointWidget::setId(int id) +{ + m_id = id; +} + +void WayPointWidget::onSelectedById(int id, bool selected) +{ + if (id == m_id || id == -1) { + m_isSelected = selected; + update(); + } +} + +void WayPointWidget::onPathStarted(const QPoint &startPoint) +{ + if (geometry().contains(startPoint)) { + Q_EMIT arrived(this->m_id); + } + + m_started = true; +} + +void WayPointWidget::onPathStopped() +{ + m_started = false; +} + +void WayPointWidget::paintEvent(QPaintEvent *event) +{ + Q_UNUSED(event) + + QPainterPath pathBound; + QPainterPath pathCenter; + QPainter painter(this); + painter.setRenderHint(QPainter::Antialiasing); + + static auto model = WayPointModel::instance(); + auto colorConfig = model->colorConfig(); + + QColor unselectedBoarderColor = colorConfig.unSelecteBoarder; + QColor selectedBoarderColor = colorConfig.selectedBoarder; + QColor fillColor = colorConfig.fill; + QColor internalFillColor = colorConfig.internalFill; + + if (model->showErrorStyle()) { + selectedBoarderColor = colorConfig.warningLine; + fillColor = colorConfig.warningFill; + internalFillColor = selectedBoarderColor; + } + + pathBound.addEllipse(this->contentsRect().center(), 23, 23); + QPen pen; + pen.setColor(m_isSelected ? selectedBoarderColor : unselectedBoarderColor); + pen.setWidth(2); + painter.setPen(pen); + + if (m_isSelected) { + painter.setBrush(fillColor); + } + painter.drawPath(pathBound); + + if (m_isSelected) { + pathCenter.addEllipse(this->contentsRect().center(), 12, 12); + painter.setBrush(internalFillColor); + painter.drawPath(pathCenter); + } +} diff --git a/plugins/login-gesture/src/waypointwidget.h b/plugins/login-gesture/src/waypointwidget.h new file mode 100644 index 000000000..88d436f47 --- /dev/null +++ b/plugins/login-gesture/src/waypointwidget.h @@ -0,0 +1,41 @@ +// SPDX-FileCopyrightText: 2021 - 2024 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef WAYPOINTWIDGET_H +#define WAYPOINTWIDGET_H + +#include + +namespace gestureLogin { + +class WayPointWidget : public QWidget +{ + Q_OBJECT +public: + explicit WayPointWidget(QWidget *parent = nullptr); + static QList getCrossedNode(int startId, int stopId); + + void setId(int); + inline int id() { return m_id; } + +public Q_SLOTS: + void onSelectedById(int m_id, bool arrived); // 选中、撤消图形变化 + void onPathStarted(const QPoint &); + void onPathStopped(); + +Q_SIGNALS: + void arrived(int m_id); // 通知模型处理,如果可选中,再调用onSelectedById + +protected: + virtual void paintEvent(QPaintEvent *event) override; + +private: + QString m_iconPath; + int m_id; + bool m_isSelected; + bool m_started; +}; +} // namespace gestureLogin + +#endif // WAYPOINTWIDGET_H diff --git a/plugins/login-gesture/translate_generation.sh b/plugins/login-gesture/translate_generation.sh new file mode 100755 index 000000000..c9080923b --- /dev/null +++ b/plugins/login-gesture/translate_generation.sh @@ -0,0 +1,11 @@ +#!/bin/bash +# this file is used to auto-generate .qm file from .ts file. +# author: shibowen at linuxdeepin.com + +ts_list=(`ls translations/*.ts`) + +for ts in "${ts_list[@]}" +do + printf "\nprocess ${ts}\n" + lrelease "${ts}" +done diff --git a/plugins/login-gesture/utils/tokenCrypt.h b/plugins/login-gesture/utils/tokenCrypt.h new file mode 100644 index 000000000..7c8f2ad17 --- /dev/null +++ b/plugins/login-gesture/utils/tokenCrypt.h @@ -0,0 +1,85 @@ +// SPDX-FileCopyrightText: 2011 - 2023 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: LGPL-3.0-or-later + +#ifndef TOKENCRYPT_H +#define TOKENCRYPT_H + +#include +#include + +#include +#include + +namespace gestureEncrypt { +/** + * @brief crypt函数是否支持SM3算法 + * crypt使用固定的key和salt进行加密,把加密结果与已知的正确结果进行比对,相等则支持,反之不支持 + * @return true 支持 + * @return false 不支持 + */ +inline bool supportSM3() +{ + char password[] = "Hello world!"; + char salt[] = "$8$saltstring"; + const QString cryptResult = "$8$saltstring$6RCuSuWbADZmLALkvsvtcYYzhrw3xxpuDcqwdPIWxTD"; + return crypt(password, salt) == cryptResult; +} + +inline const QByteArray getCryptSalt(const QByteArray &key) +{ + QByteArray retsult = ""; + int count = 0; + for (const auto value : key) { + if (value == '$') { + retsult += value; + count++; + if (count == 3) { + return retsult; + } + } else { + if (count > 0) { + retsult += value; + } + } + } + + return retsult; +} + +inline QString cryptUserPassword(const QString &password, const QByteArray &salt) +{ + return crypt(password.toUtf8().data(), salt); +} + +inline QString getSalt() +{ + /* + NOTE(kirigaya): Password is a combination of salt and crypt function. + slat is begin with $6$, 16 byte of random values, at the end of $. + crypt function will return encrypted values. + */ + const QString seedChars("./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"); + char salt[] = "$6$................$"; + if (supportSM3()) { + salt[1] = '8'; + } + + std::random_device r; + std::default_random_engine e1(r()); + std::uniform_int_distribution uniform_dist(0, seedChars.size() - 1); // seedChars.size()是64,生成随机数的范围应该写成[0, 63]。 + + // Random access to a character in a restricted list + for (int i = 0; i != 16; i++) { + salt[3 + i] = seedChars.at(uniform_dist(e1)).toLatin1(); + } + + return salt; +} + +inline QString cryptUserPassword(const QString &password) +{ + return crypt(password.toUtf8().data(), getSalt().toLatin1()); +} +}; // namespace gestureEncrypt +#endif diff --git a/plugins/login-gesture/utils/translastiondoc.cpp b/plugins/login-gesture/utils/translastiondoc.cpp new file mode 100644 index 000000000..06c473d84 --- /dev/null +++ b/plugins/login-gesture/utils/translastiondoc.cpp @@ -0,0 +1,86 @@ +// SPDX-FileCopyrightText: 2021 - 2024 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "translastiondoc.h" +#include "waypointmodel.h" + +#include +#include +#include +#include +#include + +const QString kTransFile = "%1/login-gesture_%2.qm"; + +using namespace gestureLogin; + +TranslastionDoc::TranslastionDoc(QObject *parent) + : QObject(parent) + , m_translator(new QTranslator(this)) +{ + setLocale(WayPointModel::instance()->localeName()); + connect(WayPointModel::instance(), &WayPointModel::localeChanged, this, &TranslastionDoc::setLocale); +} + +TranslastionDoc *TranslastionDoc::instance() +{ + static TranslastionDoc doc; + return &doc; +} + +QString TranslastionDoc::getDoc(DocIndex index) +{ + return m_doc.value(index, ""); +} + +void TranslastionDoc::setLocale(const QString &localName) +{ + auto name = localName; + if (name.isEmpty()) { + name = QLocale::system().name(); + } + + qApp->removeTranslator(m_translator); + QString transFile = kTransFile.arg(QM_FILES_DIR).arg(name); + if (QFile(transFile).exists()) { + m_translator->load(transFile); + qApp->installTranslator(m_translator); + initDoc(); + } else { + qWarning() << transFile << "not exists"; + } +} + +void TranslastionDoc::initDoc() +{ + m_doc.clear(); + m_doc[DocIndex::Enabled] = tr("Gesture password is enabled"); + m_doc[DocIndex::RequestFirstEnroll] = tr("For first time use, please set the gesture password first"); + m_doc[DocIndex::SetPasswd] = tr("Set gesture password"); + m_doc[DocIndex::RequestDrawing] = tr("Please draw a gesture password"); + m_doc[DocIndex::RequestDrawingAgain] = tr("Please draw the gesture password again"); + m_doc[DocIndex::ForgotPasswd] = tr("Forgot gesture password"); + m_doc[DocIndex::PasswdCleared] = tr("Gesture password has been reset"); + m_doc[DocIndex::NeedResetPasswd] = tr("Please reset the gesture password"); + m_doc[DocIndex::PathError] = tr("Minimum 4 points, please redraw"); + m_doc[DocIndex::Inconsistent] = tr("Inconsistent with the last drawing, please redraw"); + m_doc[DocIndex::ContactAdmin] = tr("Drawing error, Contact the administrator to reset"); + m_doc[DocIndex::DeviceLock] = tr("Device is locked, unlocked after %1 minutes"); + m_doc[DocIndex::ModifyPasswd] = tr("Modify gesture password"); + m_doc[DocIndex::DrawCurrentPasswd] = tr("Please draw the current gesture password"); + m_doc[DocIndex::EnrollDone] = tr("Modified successfully"); + m_doc[DocIndex::Cancel] = tr("Cancel"); + m_doc[DocIndex::Ok] = tr("Ok"); + m_doc[DocIndex::AuthErrorWithTimes] = tr("Drawing error, %1 chances left. Contact the administrator to reset"); + m_doc[DocIndex::DeviceLockHelp] = tr("Contact the administrator to reset"); + + // 登录器与锁屏的差异部分 + if (WayPointModel::instance()->isLockApp()) { + m_doc[DocIndex::LoginAfterEnroll] = tr("Setup completed Start unlock"); + m_doc[DocIndex::LoginStart] = tr("Unlock with gesture password"); + } else { + m_doc[DocIndex::LoginAfterEnroll] = tr("Setup completed Start login"); + m_doc[DocIndex::LoginStart] = tr("Sign in with gesture password"); + } +} diff --git a/plugins/login-gesture/utils/translastiondoc.h b/plugins/login-gesture/utils/translastiondoc.h new file mode 100644 index 000000000..85d76a5d3 --- /dev/null +++ b/plugins/login-gesture/utils/translastiondoc.h @@ -0,0 +1,62 @@ +// SPDX-FileCopyrightText: 2021 - 2024 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef TRANSLASTIONDOC_H +#define TRANSLASTIONDOC_H + +#include +#include + +class QTranslator; + +namespace gestureLogin { + +enum class DocIndex { + Enabled, + RequestFirstEnroll, + SetPasswd, + RequestDrawing, + RequestDrawingAgain, + LoginAfterEnroll, + ForgotPasswd, + LoginStart, + GestureLockStart, + PasswdCleared, + NeedResetPasswd, + PathError, + Inconsistent, + GestureAuthError, + ContactAdmin, + DeviceLock, + ModifyPasswd, + DrawCurrentPasswd, + EnrollDone, + Cancel, + Ok, + AuthErrorWithTimes, + DeviceLockHelp +}; + +// 在这里加载翻译文件,提供翻译内容 +class TranslastionDoc : public QObject +{ + Q_OBJECT + +public: + static TranslastionDoc *instance(); + QString getDoc(DocIndex); + +public Q_SLOTS: + void setLocale(const QString &); + +private: + explicit TranslastionDoc(QObject *parent = nullptr); + void initDoc(); + +private: + QMap m_doc; + QTranslator *m_translator; +}; +} // namespace gestureLogin +#endif // TRANSLASTIONDOC_H diff --git a/plugins/login-gesture/utils/userservice.cpp b/plugins/login-gesture/utils/userservice.cpp new file mode 100644 index 000000000..8bb3ac50d --- /dev/null +++ b/plugins/login-gesture/utils/userservice.cpp @@ -0,0 +1,137 @@ +// SPDX-FileCopyrightText: 2021 - 2024 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "userservice.h" +#include "waypointmodel.h" + +#include +#include + +const QString kAccountService = "com.deepin.daemon.Accounts"; +const QString kAccountPath = "/com/deepin/daemon/Accounts"; +const QString kUserInterface = "com.deepin.daemon.Accounts.User"; +const QString kPropertyInterface = "org.freedesktop.DBus.Properties"; +const QString kEnableFlag = "PatternEnabled"; +const QString kStateFlag = "PatternState"; + +using namespace gestureLogin; + +UserService::UserService(const QString &name, QObject *parent) + : QObject(parent) + , m_userInter(nullptr) + , m_currentName(name) +{ + init(); +} + +UserService *UserService::instance() +{ + static UserService s(""); + return &s; +} + +void UserService::setUserName(const QString &userName) +{ + // 不论设置用户名多少,都重新做初始化 + m_currentName = userName; + init(); +} + +bool UserService::gestureEnabled() +{ + auto rst = false; + if (m_userInter && m_userInter->isValid()) { + rst = m_userInter->property("PatternEnabled").toBool(); + } + + return rst; +} + +int UserService::gestureFlags() +{ + auto rst = -1; + if (m_userInter && m_userInter->isValid()) { + rst = m_userInter->property("PatternState").toInt(); + } + + return rst; +} + +QString UserService::localeName() +{ + QString rst = ""; + if (m_userInter && m_userInter->isValid()) { + rst = m_userInter->property("Locale").toString(); + } + + return rst; +} + +void UserService::enroll(const QString &data) +{ + if (data.isEmpty()) { + qDebug() << Q_FUNC_INFO << "enroll data empty"; + return; + } + + if (m_userInter && m_userInter->isValid()) { + m_userInter->call("SetPattern", data); + qDebug() << Q_FUNC_INFO << "enrolled" << m_userInter->path(); + } +} + +void UserService::onPropertiesChanged(const QDBusMessage& msg) +{ + QList arguments = msg.arguments(); + if (3 != arguments.count()) + return; + + QString interfaceName = msg.arguments().at(0).toString(); + + if (interfaceName != kUserInterface) + return; + + QVariantMap changedProps = qdbus_cast(arguments.at(1).value()); + QStringList keys = changedProps.keys(); + if (changedProps.keys().contains(kStateFlag)) { + WayPointModel::instance()->setGestureState(changedProps.value(kStateFlag).toInt()); + } +} + +void UserService::init() +{ + if (m_userInter) { + m_userInter->disconnect(); + delete m_userInter; + m_userInter = nullptr; + } + + if (!m_userInterpath.isEmpty()) { + QDBusConnection::systemBus().disconnect(kAccountService, m_userInterpath, kPropertyInterface, "PropertiesChanged", "sa{sv}as", this, SLOT(onPropertiesChanged(QDBusMessage))); + } + + static QDBusInterface accountInter(kAccountService, kAccountPath, kAccountService, QDBusConnection::systemBus(), this); + QDBusReply reply = accountInter.call("FindUserByName", m_currentName); + if (reply.isValid()) { + m_userInterpath = reply; + } + + if (m_userInterpath.isEmpty()) { + return; + } + + m_userInter = new QDBusInterface(kAccountService, m_userInterpath, kUserInterface, QDBusConnection::systemBus(), this); + if (m_userInter->isValid()) { + bool connected = QDBusConnection::systemBus().connect(kAccountService, m_userInterpath, kPropertyInterface, "PropertiesChanged", "sa{sv}as", this, SLOT(onPropertiesChanged(QDBusMessage))); + qDebug() << Q_FUNC_INFO << "property change handle" << connected; + + auto model = WayPointModel::instance(); + model->setGestureEnabled(gestureEnabled()); + model->setGestureState(gestureFlags()); + model->setLocaleName(localeName()); + } else { + qWarning() << Q_FUNC_INFO << "user interface invalid" << m_userInterpath; + } + +} diff --git a/plugins/login-gesture/utils/userservice.h b/plugins/login-gesture/utils/userservice.h new file mode 100644 index 000000000..2e25f30ff --- /dev/null +++ b/plugins/login-gesture/utils/userservice.h @@ -0,0 +1,47 @@ +// SPDX-FileCopyrightText: 2021 - 2024 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef DBUSSERVICE_H +#define DBUSSERVICE_H + +#include +#include + +namespace gestureLogin { + +// 提供da状态查询与监控 +class UserService : public QObject +{ + Q_OBJECT +public: + static UserService *instance(); + + void setUserName(const QString &); + inline bool isServiceValid() { return m_userInter != nullptr; } + // 查询当前用户手势是否开启 + bool gestureEnabled(); + // 查询当前用户手势状态标识 + int gestureFlags(); + QString localeName(); + + void enroll(const QString &); + +public Q_SLOTS: + void onPropertiesChanged(const QDBusMessage& msg); + +Q_SIGNALS: + void userPatternStateChanged(int); + +private: + explicit UserService(const QString &, QObject *parent = nullptr); + void init(); + +private: + QDBusInterface *m_userInter; + QString m_currentName; + QString m_userInterpath; +}; +} // namespace gestureLogin + +#endif // DBUSSERVICE_H diff --git a/plugins/one-key-login/login_module.cpp b/plugins/one-key-login/login_module.cpp index 15b3c5538..daf5a0783 100644 --- a/plugins/one-key-login/login_module.cpp +++ b/plugins/one-key-login/login_module.cpp @@ -118,7 +118,7 @@ LoginModule::LoginModule(QObject *parent) if (!m_isAcceptFingerprintSignal) { // 防止刚切换指纹认证stop还没结束。 QTimer::singleShot(30, this, [this] { - sendAuthTypeToSession(AuthType::AT_Fingerprint); + sendAuthTypeToSession(AuthType::AT_All); }); } }, Qt::DirectConnection); @@ -167,7 +167,7 @@ void LoginModule::startCallHuaweiFingerprint() auto failedHandler = [this] { m_isAcceptFingerprintSignal = false; // FIXME 此处不能调用回调,因为还没初始化,此处的逻辑应该在setCallBack函数完成后再进行。 - sendAuthTypeToSession(AuthType::AT_Fingerprint); + sendAuthTypeToSession(AuthType::AT_All); }; QDBusMessage m = QDBusMessage::createMethodCall(DSS_DBUS::authenticateService, DSS_DBUS::fingerprintAuthPath, DSS_DBUS::fingerprintAuthInterface, @@ -200,7 +200,7 @@ void LoginModule::init() if (m_appType == AppType::Lock && !m_acceptSleepSignal) { //此处延迟0.5s发送,是需要等待dde-session-shell认证方式创建完成 QTimer::singleShot(500, this, [this] { - sendAuthTypeToSession(AuthType::AT_Fingerprint); + sendAuthTypeToSession(AuthType::AT_All); }); } } @@ -331,7 +331,7 @@ QString LoginModule::message(const QString &message) sendAuthData(m_lastAuthResult); } if (m_needSendAuthType) { - sendAuthTypeToSession(AuthType::AT_Fingerprint); + sendAuthTypeToSession(AuthType::AT_All); } } } else if (cmdType == "AuthState") { @@ -399,7 +399,7 @@ void LoginModule::slotIdentifyStatus(const QString &name, const int errorCode, c if (m_appType == AppType::Lock && m_userName != name && name != "") { // 防止stop还未完成就开启了指纹认证 QTimer::singleShot(30, this, [this] { - sendAuthTypeToSession(AuthType::AT_Fingerprint); + sendAuthTypeToSession(AuthType::AT_All); }); return ; } @@ -413,7 +413,7 @@ void LoginModule::slotIdentifyStatus(const QString &name, const int errorCode, c // 发送一键登录失败的信息 qWarning() << "Identify Status recive failed, error: " << msg; QTimer::singleShot(30, this, [this] { - sendAuthTypeToSession(AuthType::AT_Fingerprint); + sendAuthTypeToSession(AuthType::AT_All); }); m_lastAuthResult.result = AuthResult::Failure; @@ -499,7 +499,7 @@ void LoginModule::slotPrepareForSleep(bool active) m_spinner->start(); } else { //fix: 多用户时,第一个用户直接锁屏,然后待机唤醒,在直接切换到另一个用户时,m_login1SessionSelf没有激活,见159949 - sendAuthTypeToSession(AuthType::AT_Fingerprint); + sendAuthTypeToSession(AuthType::AT_All); } } @@ -525,12 +525,6 @@ void LoginModule::sendAuthTypeToSession(AuthType type) return; } - // 登录界面密码锁定后只允许切换到密码认证,等待密码锁定解除后才能切换到其它认证类型 - if (m_isLocked && m_appType == AppType::Login) { - qInfo() << "Password is locked and current application is greeter, change authentication type to password"; - type = AuthType::AT_Password; - } - // 这里主要为了防止 在发送切换信号的时候,lightdm还为开启认证,导致切换类型失败 if (m_authStatus == AuthStatus::None && !m_isLocked && type != AuthType::AT_Custom && m_appType != AppType::Lock) { m_needSendAuthType = true; @@ -543,7 +537,7 @@ void LoginModule::sendAuthTypeToSession(AuthType type) QJsonObject message; message.insert("CmdType", "setAuthTypeInfo"); QJsonObject retDataObj; - retDataObj["AuthType"] = type; + retDataObj["AuthType"] = static_cast(type); message["Data"] = retDataObj; QJsonDocument doc; doc.setObject(message); diff --git a/src/app/dde-lock.cpp b/src/app/dde-lock.cpp index 0236194e9..1ccb677ac 100644 --- a/src/app/dde-lock.cpp +++ b/src/app/dde-lock.cpp @@ -86,8 +86,9 @@ int main(int argc, char *argv[]) }); DLogManager::setLogFormat("%{time}{yyyy-MM-dd, HH:mm:ss.zzz} [%{type:-7}] [ %{function:-35} %{line}] %{message}\n"); +#ifdef QT_DEBUG DLogManager::registerConsoleAppender(); - DLogManager::registerFileAppender(); +#endif DLogManager::registerJournalAppender(); QCommandLineParser cmdParser; @@ -203,6 +204,7 @@ int main(int argc, char *argv[]) QObject::connect(WarningContent::instance(), &WarningContent::requestLockFrameHide, [model] { model->setVisible(false); }); + QObject::connect(LockContent::instance(), &LockContent::requestLockStateChange, worker, &LockWorker::setLocked); auto createFrame = [&](QPointer screen, int count) -> QWidget* { LockFrame *lockFrame = new LockFrame(model); diff --git a/src/app/greeter-display-setting.cpp b/src/app/greeter-display-setting.cpp index f54c83637..881d7eb52 100644 --- a/src/app/greeter-display-setting.cpp +++ b/src/app/greeter-display-setting.cpp @@ -108,6 +108,65 @@ static double calcMaxScaleFactor(unsigned int width, unsigned int height) { return maxScale; } +static QStringList getScaleList() +{ + qDebug() << "Get scale list"; + Display *display = XOpenDisplay(nullptr); + if (!display) { + qWarning() << "Display is null"; + return QStringList{}; + } + + XRRScreenResources *resources = XRRGetScreenResourcesCurrent(display, DefaultRootWindow(display)); + if (!resources) { + resources = XRRGetScreenResources(display, DefaultRootWindow(display)); + qCWarning(DDE_SHELL) << "Get current XRR screen resources failed, instead of getting XRR screen resources, resources: " << resources; + } + + static const float MinScreenWidth = 1024.0f; + static const float MinScreenHeight = 768.0f; + static const QStringList tvstring = {"1.0", "1.25", "1.5", "1.75", "2.0", "2.25", "2.5", "2.75", "3.0"}; + QStringList fscaleList; + + if (resources) { + for (int i = 0; i < resources->noutput; i++) { + XRROutputInfo* outputInfo = XRRGetOutputInfo(display, resources, resources->outputs[i]); + if (outputInfo->crtc == 0 || outputInfo->mm_width == 0) + continue; + + XRRCrtcInfo *crtInfo = XRRGetCrtcInfo(display, resources, outputInfo->crtc); + if (crtInfo == nullptr) + continue; + + auto maxWScale = crtInfo->width / MinScreenWidth; + auto maxHScale = crtInfo->height / MinScreenHeight; + auto maxScale = maxWScale < maxHScale ? maxWScale : maxHScale; + maxScale = maxScale < 3.0f ? maxScale : 3.0f; + if (fscaleList.isEmpty()) { + for (int idx = 0; idx * 0.25f + 1.0f <= maxScale; ++idx) { + fscaleList << tvstring[idx]; + } + qDebug() << "First screen scale list:" << fscaleList; + } else { + QStringList tmpList; + for (int idx = 0; idx * 0.25f + 1.0f <= maxScale; ++idx) { + tmpList << tvstring[idx]; + } + qDebug() << "Current screen scale list:" << tmpList; + // fscaleList、tmpList两者取交集 + for (const auto &scale : fscaleList) { + if (!tmpList.contains(scale)) + fscaleList.removeAll(scale); + } + } + } + } else { + qCWarning(DDE_SHELL) << "Get scale factor failed, please check X11 Extension"; + } + + return fscaleList; +} + static double getScaleFactor() { Display *display = XOpenDisplay(nullptr); double scaleFactor = 0.0; @@ -199,6 +258,14 @@ double getScaleFormConfig() qDebug() << "Scale factor from system display config: " << scaleFactor; if(scaleFactor == 0.0) { scaleFactor = defaultScaleFactor; + } else { + // 处理关机期间从高分屏换到低分屏的场景 + const auto &scales = getScaleList(); + qDebug() << "Scales:" << scales; + if (!scales.isEmpty() && !scales.contains(QString::number(scaleFactor, 'f', 2).replace("00", "0"))) { + qInfo() << "Scale factor is not in scales, use default scale factor"; + scaleFactor = defaultScaleFactor; + } } return scaleFactor; } else { diff --git a/src/dde-lock/lockframe.cpp b/src/dde-lock/lockframe.cpp index 079f5f1ad..cf594bcb2 100644 --- a/src/dde-lock/lockframe.cpp +++ b/src/dde-lock/lockframe.cpp @@ -95,7 +95,7 @@ LockFrame::LockFrame(SessionBaseModel *const model, QWidget *parent) if (DConfigHelper::instance()->getConfig("autoExit", false).toBool()) { m_autoExitTimer = new QTimer(this); - m_autoExitTimer->setInterval(1000*60); //1分钟 + m_autoExitTimer->setInterval(1000 * 60); //1分钟 m_autoExitTimer->setSingleShot(true); connect(m_autoExitTimer, &QTimer::timeout, qApp, &QApplication::quit); } @@ -115,7 +115,7 @@ LockFrame::~LockFrame() bool LockFrame::event(QEvent *event) { if (event->type() == QEvent::KeyRelease) { - QString keyValue = ""; + QString keyValue = ""; switch (static_cast(event)->key()) { case Qt::Key_PowerOff: { if (!handlePoweroffKey()) { @@ -165,8 +165,8 @@ bool LockFrame::event(QEvent *event) } // 锁屏时会独占键盘,需要单独处理F1待机事件 case Qt::Key_Sleep: { - m_model->setPowerAction(SessionBaseModel::RequireSuspend); - break; + m_model->setPowerAction(SessionBaseModel::RequireSuspend); + break; } } @@ -230,7 +230,9 @@ void LockFrame::keyPressEvent(QKeyEvent *e) { switch (e->key()) { #ifdef QT_DEBUG - case Qt::Key_Escape: qApp->quit(); break; + case Qt::Key_Escape: + qApp->quit(); + break; #endif } } @@ -255,7 +257,6 @@ void LockFrame::hideEvent(QHideEvent *event) return FullScreenBackground::hideEvent(event); } - void LockFrame::prepareForSleep(bool isSleep) { //不管是待机还是唤醒均不响应电源按键信号 diff --git a/src/dde-lock/lockworker.cpp b/src/dde-lock/lockworker.cpp index 75a59430e..7ce5e463d 100644 --- a/src/dde-lock/lockworker.cpp +++ b/src/dde-lock/lockworker.cpp @@ -14,6 +14,8 @@ #include "fullscreenbackground.h" #include "updateworker.h" #include "lockcontent.h" +#include "signal_bridge.h" +#include "mfasequencecontrol.h" #include @@ -160,8 +162,22 @@ void LockWorker::initConnections() destroyAuthentication(m_account); } }); + + // 在待机时启动定时器,如果定时器超时的时间离待机时间大于15秒,则认为已经唤醒,停止定时器,并设置黑屏模式为false + // prepareForSleep信号有时候会会延迟,所以需要定时器来确认是否已经唤醒,尽早显示出锁屏。 + static QDateTime doSuspendTime = QDateTime::currentDateTime(); + auto checkSystemWakeupTimer = new QTimer(this); + checkSystemWakeupTimer->setInterval(500); + connect(checkSystemWakeupTimer, &QTimer::timeout, this, [this, checkSystemWakeupTimer] { + const int secs = static_cast(doSuspendTime.secsTo(QDateTime::currentDateTime())); + if (secs >= 15) { // 小风险:如果15秒还没有待机下去,那就把锁屏显示出来了,不过15秒没有待机的才是大问题 + qCInfo(DDE_SHELL) << "System wakeup,hide black widget"; + checkSystemWakeupTimer->stop(); + m_model->setIsBlackMode(false); + } + }); /* org.freedesktop.login1.Manager */ - connect(m_login1Inter, &DBusLogin1Manager::PrepareForSleep, this, [this](bool isSleep) { + connect(m_login1Inter, &DBusLogin1Manager::PrepareForSleep, this, [this, checkSystemWakeupTimer](bool isSleep) { qCInfo(DDE_SHELL) << "DBus login1 manager prepare for sleep: " << isSleep; if (m_model->currentContentType() == SessionBaseModel::UpdateContent) { qCInfo(DDE_SHELL) << "Updating, do not answer `PrepareForSleep` signal"; @@ -174,25 +190,39 @@ void LockWorker::initConnections() const bool sleepLock = isSleepLock(); #endif qCInfo(DDE_SHELL) << "Lock screen when system wakes up: " << sleepLock << ", is visible:" << m_model->visible(); - if (isSleep) { - endAuthentication(m_account, AT_All); - destroyAuthentication(m_account); - } else { - // 非黑屏mode - m_model->setIsBlackMode(isSleep); - // 如果待机唤醒后需要密码则创建验证 - if(m_login1SessionSelf->active() && sleepLock) - createAuthentication(m_model->currentUser()->name()); - } - if (!m_model->visible() && sleepLock) { - m_model->setIsBlackMode(isSleep); - m_model->setVisible(true); - } + FullScreenBackground::setContent(LockContent::instance()); + m_model->setCurrentContentType(SessionBaseModel::LockContent); - if (!isSleep && !sleepLock) { - //待机唤醒后检查是否需要密码,若不需要密码直接隐藏锁定界面 - m_model->setVisible(false); + if (isSleep) { + checkSystemWakeupTimer->start(); + doSuspendTime = QDateTime::currentDateTime(); + m_model->setIsBlackMode(true); + // 休眠时按需拉起锁屏,注意此时是被动响应休眠 + if (sleepLock && m_login1SessionSelf->active()) { + // 仅sleepLock配置开启时执行 + m_model->setCurrentModeState(SessionBaseModel::ModeStatus::PasswordMode); + setLocked(true); + m_model->setVisible(true); + + qCInfo(DDE_SHELL) << "locked for sleepLock config"; + } + } else { + SessionBaseModel::ModeStatus status = m_model->currentModeState(); + if (isLocked() || (m_model->isNoPasswordLogin() && (status == SessionBaseModel::ModeStatus::PowerMode || status == SessionBaseModel::ModeStatus::PasswordMode))) { + // 唤醒时如果处于lock状态,重置认证 + if (m_login1SessionSelf->active()) { + endAuthentication(m_account, AT_All); + destroyAuthentication(m_account); + createAuthentication(m_model->currentUser()->name()); + } + } else { + // 处理其他流程设置的可见状态 + m_model->setVisible(false); + } + // 避免在电源选项处理中设置密码模式,会导致唤醒后闪锁屏 + m_model->setCurrentModeState(SessionBaseModel::ModeStatus::PasswordMode); + m_model->setIsBlackMode(false); } emit m_model->prepareForSleep(isSleep); }); @@ -263,44 +293,55 @@ void LockWorker::initConnections() FullScreenBackground::setContent(LockContent::instance()); m_model->setCurrentContentType(SessionBaseModel::LockContent); }); + connect(&SignalBridge::ref(), &SignalBridge::requestSendExtraInfo, this, &LockWorker::sendExtraInfo); } void LockWorker::initData() { - /* com.deepin.daemon.Accounts */ - m_model->updateUserList(m_accountsInter->userList()); - m_model->updateLoginedUserList(m_loginedInter->userList()); - - m_model->setUserlistVisible(valueByQSettings("", "userlist", true)); - /* com.deepin.udcp.iam */ - QDBusInterface ifc(DSS_DBUS::udcpIamService, DSS_DBUS::udcpIamPath, DSS_DBUS::udcpIamService, QDBusConnection::systemBus(), this); - const bool allowShowCustomUser = (!m_model->userlistVisible()) || valueByQSettings("", "loginPromptInput", false) || - ifc.property("Enable").toBool() || checkIsADDomain(); - m_model->setAllowShowCustomUser(allowShowCustomUser); - - /* init server user or custom user */ - if (DSysInfo::deepinType() == DSysInfo::DeepinServer || m_model->allowShowCustomUser()) { - std::shared_ptr user(new User()); - m_model->setIsServerModel(DSysInfo::deepinType() == DSysInfo::DeepinServer); - m_model->addUser(user); - } + auto list = m_accountsInter->userList(); + if (!list.isEmpty()) { + /* com.deepin.daemon.Accounts */ + m_model->updateUserList(m_accountsInter->userList()); + m_model->updateLoginedUserList(m_loginedInter->userList()); + + m_model->setUserlistVisible(valueByQSettings("", "userlist", true)); + /* com.deepin.udcp.iam */ + QDBusInterface ifc(DSS_DBUS::udcpIamService, DSS_DBUS::udcpIamPath, DSS_DBUS::udcpIamService, QDBusConnection::systemBus(), this); + const bool allowShowCustomUser = (!m_model->userlistVisible()) || valueByQSettings("", "loginPromptInput", false) || + ifc.property("Enable").toBool() || checkIsADDomain(); + m_model->setAllowShowCustomUser(allowShowCustomUser); + + /* init server user or custom user */ + if (DSysInfo::deepinType() == DSysInfo::DeepinServer || m_model->allowShowCustomUser()) { + std::shared_ptr user(new User()); + m_model->setIsServerModel(DSysInfo::deepinType() == DSysInfo::DeepinServer); + m_model->addUser(user); + } - /* com.deepin.dde.LockService */ - std::shared_ptr user_ptr = m_model->findUserByUid(getuid()); - if (user_ptr.get()) { - m_model->updateCurrentUser(user_ptr); - const QString &userJson = m_lockInter->CurrentUser(); - QJsonParseError jsonParseError; - const QJsonDocument userDoc = QJsonDocument::fromJson(userJson.toUtf8(), &jsonParseError); - if (jsonParseError.error != QJsonParseError::NoError || userDoc.isEmpty()) { - qCWarning(DDE_SHELL) << "Failed to obtain current user information from lock service!"; + /* com.deepin.dde.LockService */ + std::shared_ptr user_ptr = m_model->findUserByUid(getuid()); + if (user_ptr.get()) { + m_model->updateCurrentUser(user_ptr); + const QString &userJson = m_lockInter->CurrentUser(); + QJsonParseError jsonParseError; + const QJsonDocument userDoc = QJsonDocument::fromJson(userJson.toUtf8(), &jsonParseError); + if (jsonParseError.error != QJsonParseError::NoError || userDoc.isEmpty()) { + qCWarning(DDE_SHELL) << "Failed to obtain current user information from lock service!"; + } else { + const QJsonObject userObj = userDoc.object(); + m_model->currentUser()->setLastAuthType(AUTH_TYPE_CAST(userObj["AuthType"].toInt())); + m_model->currentUser()->setLastCustomAuth(userObj["LastCustomAuth"].toString()); + } } else { - const QJsonObject userObj = userDoc.object(); - m_model->currentUser()->setLastAuthType(AUTH_TYPE_CAST(userObj["AuthType"].toInt())); - m_model->currentUser()->setLastCustomAuth(userObj["LastCustomAuth"].toString()); + m_model->updateCurrentUser(m_lockInter->CurrentUser()); } } else { - m_model->updateCurrentUser(m_lockInter->CurrentUser()); + qCWarning(DDE_SHELL) << "dbus com.deepin.daemon.Accounts userList is empty, use ..."; + + std::shared_ptr user(new User()); + m_model->addUser(user); + m_model->updateCurrentUser(user); + m_model->setAllowShowCustomUser(true); } /* com.deepin.daemon.Authenticate */ @@ -324,6 +365,8 @@ void LockWorker::initConfiguration() m_model->setAllowShowUserSwitchButton(getDconfigValue("switchUser", Ondemand).toInt() == AuthInterface::Ondemand); #endif + m_model->setGsCheckpwd(isCheckPwdBeforeRebootOrShut()); + checkPowerInfo(); } @@ -363,7 +406,12 @@ void LockWorker::onAuthStateChanged(const int type, const int state, const QStri && m_model->currentModeState() != SessionBaseModel::ModeStatus::ConfirmPasswordMode) { m_model->setCurrentModeState(SessionBaseModel::ModeStatus::PasswordMode); } - m_resetSessionTimer->start(); + + // 处于序列化多因认证过程中时不要重置认证 + if (MFASequenceControl::instance().currentAuthType() == AuthType::AT_None) { + m_resetSessionTimer->start(); + } + m_model->updateAuthState(AuthType(type), AuthState(state), message); break; case AS_Failure: @@ -466,7 +514,6 @@ void LockWorker::doPowerAction(const SessionBaseModel::PowerAction action) case SessionBaseModel::PowerAction::RequireSuspend: { m_model->setIsBlackMode(true); - m_model->setCurrentModeState(SessionBaseModel::ModeStatus::PasswordMode); int delayTime = 500; #ifndef ENABLE_DSS_SNIPE @@ -496,7 +543,6 @@ void LockWorker::doPowerAction(const SessionBaseModel::PowerAction action) case SessionBaseModel::PowerAction::RequireHibernate: { m_model->setIsBlackMode(true); - m_model->setCurrentModeState(SessionBaseModel::ModeStatus::PasswordMode); int delayTime = 500; #ifndef ENABLE_DSS_SNIPE @@ -525,9 +571,10 @@ void LockWorker::doPowerAction(const SessionBaseModel::PowerAction action) } } break; - case SessionBaseModel::PowerAction::RequireRestart: - QProcess::startDetached("/usr/lib/deepin-daemon/dde-blackwidget", QStringList() << "nodbus"); - if (!isLocked() || m_model->currentModeState() == SessionBaseModel::ModeStatus::ShutDownMode || !isCheckPwdBeforeRebootOrShut()) { + case SessionBaseModel::PowerAction::RequireRestart: { + m_model->setShutdownMode(true); + auto gsCheckPwd = m_model->gsCheckpwd(); + if (!isLocked() || m_model->currentModeState() == SessionBaseModel::ModeStatus::ShutDownMode || !gsCheckPwd) { m_sessionManagerInter->RequestReboot(); } else { createAuthentication(m_account); @@ -537,13 +584,16 @@ void LockWorker::doPowerAction(const SessionBaseModel::PowerAction action) if (m_model->visibleShutdownWhenRebootOrShutdown()) { return; } - QTimer::singleShot(250, this, [=] { + + if (!gsCheckPwd) m_model->setVisible(false); - }); + return; - case SessionBaseModel::PowerAction::RequireShutdown: - QProcess::startDetached("/usr/lib/deepin-daemon/dde-blackwidget", QStringList() << "nodbus"); - if (!isLocked() || m_model->currentModeState() == SessionBaseModel::ModeStatus::ShutDownMode || !isCheckPwdBeforeRebootOrShut()) { + } + case SessionBaseModel::PowerAction::RequireShutdown: { + m_model->setShutdownMode(true); + auto gsCheckPwd = m_model->gsCheckpwd(); + if (!isLocked() || m_model->currentModeState() == SessionBaseModel::ModeStatus::ShutDownMode || !gsCheckPwd) { m_sessionManagerInter->RequestShutdown(); } else { createAuthentication(m_account); @@ -553,10 +603,12 @@ void LockWorker::doPowerAction(const SessionBaseModel::PowerAction action) if (m_model->visibleShutdownWhenRebootOrShutdown()) { return; } - QTimer::singleShot(250, this, [=] { + + if (!gsCheckPwd) m_model->setVisible(false); - }); + return; + } case SessionBaseModel::PowerAction::RequireLock: m_model->setCurrentModeState(SessionBaseModel::ModeStatus::PasswordMode); createAuthentication(m_model->currentUser()->name()); @@ -628,6 +680,9 @@ void LockWorker::setCurrentUser(const std::shared_ptr user) void LockWorker::switchToUser(std::shared_ptr user) { + // 发生用户切换时锁住 + setLocked(true); + qCDebug(DDE_SHELL) << "LockWorker::switchToUser:" << m_account << user->name(); if (user->name() == m_account || *user == *m_model->currentUser()) { qCInfo(DDE_SHELL) << "Switch to current user:" << user->name() << user->isLogin(); @@ -992,3 +1047,14 @@ void LockWorker::onNoPasswordLoginChanged(const QString &account, bool noPasswor } } } + +void LockWorker::sendExtraInfo(const QString &account, AuthCommon::AuthType authType, const QString &info) +{ + switch (m_model->getAuthProperty().FrameworkState) { + case Available: + m_authFramework->sendExtraInfo(account, authType, info); + break; + default: + break; + } +} diff --git a/src/dde-lock/lockworker.h b/src/dde-lock/lockworker.h index 47d2d1d01..273b37177 100644 --- a/src/dde-lock/lockworker.h +++ b/src/dde-lock/lockworker.h @@ -67,6 +67,8 @@ public slots: void authFinishedAction(); void onNoPasswordLoginChanged(const QString &account, bool noPassword); + void sendExtraInfo(const QString &account, AuthCommon::AuthType authType, const QString &info); + void setLocked(const bool locked); private: void initConnections(); @@ -75,7 +77,6 @@ public slots: void doPowerAction(const SessionBaseModel::PowerAction action); void setCurrentUser(const std::shared_ptr user); - void setLocked(const bool locked); // lock void lockServiceEvent(quint32 eventType, quint32 pid, const QString &username, const QString &message); diff --git a/src/global_util/plugin_manager/login_plugin.cpp b/src/global_util/plugin_manager/login_plugin.cpp index 0dc642f03..8c504d2ef 100644 --- a/src/global_util/plugin_manager/login_plugin.cpp +++ b/src/global_util/plugin_manager/login_plugin.cpp @@ -129,6 +129,20 @@ void LoginPlugin::notifyCurrentUserChanged(const QString &userName, uid_t uid) this->message(toJson(message)); } + +void LoginPlugin::authStateChanged(AuthCommon::AuthType type, AuthCommon::AuthState state, const QString &prompt) +{ + QJsonObject message; + message["CmdType"] = "AuthState"; + QJsonObject retDataObj; + retDataObj["AuthType"] = static_cast(type); + retDataObj["AuthState"] = state; + retDataObj["AuthPrompt"] = prompt; + message["Data"] = retDataObj; + + this->message(toJson(message)); +} + void LoginPlugin::updateConfig() { QJsonObject message; @@ -181,3 +195,21 @@ void LoginPlugin::notifyAuthFactorsChanged(int authFactors) this->message(toJson(message)); } + +/** + * @brief + * + * @return true 插件已经准备好做认证了(一般是用户已经输入了内容,比如验证码) + * @return false + */ +bool LoginPlugin::readyToAuth() +{ + QJsonObject message; + message["CmdType"] = "ReadyToAuth"; + const QString &result = this->message(toJson(message)); + const QJsonObject &dataObj = getDataObj(result); + if (dataObj.isEmpty()) + return true; + + return dataObj["ReadyToAuth"].toBool(true); +} diff --git a/src/global_util/plugin_manager/login_plugin.h b/src/global_util/plugin_manager/login_plugin.h index c628c0509..7a595ad9a 100644 --- a/src/global_util/plugin_manager/login_plugin.h +++ b/src/global_util/plugin_manager/login_plugin.h @@ -103,6 +103,10 @@ class LoginPlugin : public PluginBase inline PluginConfig pluginConfig() const { return m_pluginConfig; } + void authStateChanged(AuthCommon::AuthType type, AuthCommon::AuthState state, const QString &prompt); + + bool readyToAuth(); + private: PluginConfig m_pluginConfig; AuthType m_authType; diff --git a/src/global_util/plugin_manager/plugin_manager.cpp b/src/global_util/plugin_manager/plugin_manager.cpp index 4e5b1eb19..82c1b4e77 100644 --- a/src/global_util/plugin_manager/plugin_manager.cpp +++ b/src/global_util/plugin_manager/plugin_manager.cpp @@ -123,6 +123,23 @@ LoginPlugin* PluginManager::getFullManagedLoginPlugin() const return nullptr; } +LoginPlugin *PluginManager::getLoginPlugin(int authType) const +{ + for (const auto &plugin : m_plugins.values()) { + if (plugin && PluginBase::ModuleType::LoginType == plugin->type()) { + auto p = dynamic_cast(plugin); + p->updateConfig(); + if (p->defaultAuthType() == authType) { + return p; + } + } + } + + qDebug() << "plugin for type " << authType << "not found in " << m_plugins; + + return nullptr; +} + QList PluginManager::trayPlugins() const { QList list; @@ -139,7 +156,8 @@ PluginBase* PluginManager::createPlugin(dss::module::BaseModuleInterface* module { if (dss::module::BaseModuleInterface::LoginType == module->type() || dss::module::BaseModuleInterface::FullManagedLoginType == module->type() - || dss::module::BaseModuleInterface::IpcAssistLoginType == module->type()) { + || dss::module::BaseModuleInterface::IpcAssistLoginType == module->type() + || dss::module::BaseModuleInterface::PasswordExtendLoginType == module->type()) { return createLoginPlugin(module, version); } else if (dss::module::BaseModuleInterface::TrayType == module->type()) { TrayPlugin* plugin = createTrayPlugin(module, version); @@ -196,6 +214,17 @@ LoginPlugin* PluginManager::getAssistloginPlugin() const return nullptr; } +LoginPlugin *PluginManager::getFirstLoginPlugin(PluginBase::ModuleType type) const +{ + for (const auto& plugin : m_plugins.values()) { + if (plugin && type == plugin->type()) { + return dynamic_cast(plugin); + } + } + + return nullptr; +} + void PluginManager::setModel(SessionBaseModel *model) { if (!model) @@ -235,3 +264,57 @@ void PluginManager::broadcastAuthFactors(int authFactors) loginPlugin->notifyAuthFactorsChanged(authFactors); } } + +QPair PluginManager::parseMessage(const QString &message) +{ + QJsonParseError jsonParseError; + const QJsonDocument messageDoc = QJsonDocument::fromJson(message.toLatin1(), &jsonParseError); + QJsonObject retObj; + QJsonObject dataObj; + retObj["Code"] = 0; + retObj["Message"] = "Success"; + if (jsonParseError.error != QJsonParseError::NoError || messageDoc.isEmpty()) { + retObj["Code"] = -1; + retObj["Message"] = "Failed to analysis message info"; + qCWarning(DDE_SHELL) << "Failed to analysis message from plugin: " << message; + return qMakePair(toJson(retObj), messageDoc); + } + + return qMakePair(QStringLiteral(""), messageDoc); +} + +QString PluginManager::getProperties(const QJsonObject &messageObj) const +{ + QJsonArray properties; + properties = messageObj.value("Data").toArray(); + + QJsonObject retObj; + QJsonObject dataObj; + retObj["Code"] = 0; + retObj["Message"] = "Success"; + do + { + if (m_model.isNull()) { + retObj["Code"] = -1; + retObj["Message"] = "Session base model is nullptr"; + break; + } + if (properties.contains("AppType")) { + dataObj["AppType"] = m_model->appType(); + } + if (properties.contains("CurrentUser")) { + if (m_model->currentUser()) { + QJsonObject user; + user["Name"] = m_model->currentUser()->name(); + user["Uid"] = static_cast(m_model->currentUser()->uid()); + dataObj["CurrentUser"] = user; + } else { + retObj["Code"] = -1; + retObj["Message"] = "Current user is null!"; + } + } + } while(0); + retObj["Data"] = dataObj; + + return toJson(retObj); +} diff --git a/src/global_util/plugin_manager/plugin_manager.h b/src/global_util/plugin_manager/plugin_manager.h index be18cc2c8..76d88fe43 100644 --- a/src/global_util/plugin_manager/plugin_manager.h +++ b/src/global_util/plugin_manager/plugin_manager.h @@ -27,10 +27,15 @@ class PluginManager : public QObject QList getLoginPlugins(int level = 1) const; LoginPlugin *getFullManagedLoginPlugin() const; LoginPlugin *getAssistloginPlugin() const; + LoginPlugin *getFirstLoginPlugin(PluginBase::ModuleType type) const; + LoginPlugin *getLoginPlugin(int authType) const; QList trayPlugins() const; bool contains(const QString &key) const; PluginBase *findPlugin(const QString &key) const; void broadcastAuthFactors(int authFactors); + QString getProperties(const QJsonObject &messageObj) const; + + static QPair parseMessage(const QString &message); signals: void trayPluginAdded(TrayPlugin *); diff --git a/src/global_util/public_func.cpp b/src/global_util/public_func.cpp index 95cda7d28..20940968a 100644 --- a/src/global_util/public_func.cpp +++ b/src/global_util/public_func.cpp @@ -219,21 +219,6 @@ bool isDeepinAuth() return true; } -uint timeFromString(QString time) -{ -#ifndef ENABLE_DSS_SNIPE - if (time.isEmpty()) { - return QDateTime::currentDateTime().toTime_t(); - } - return QDateTime::fromString(time, Qt::ISODateWithMs).toLocalTime().toTime_t(); -#else - if (time.isEmpty()) { - return QDateTime::currentDateTime().toSecsSinceEpoch(); - } - return QDateTime::fromString(time, Qt::ISODateWithMs).toLocalTime().toSecsSinceEpoch(); -#endif -} - void setAppType(int type) { appType = type; diff --git a/src/global_util/public_func.h b/src/global_util/public_func.h index 69c02b385..38e010bcf 100644 --- a/src/global_util/public_func.h +++ b/src/global_util/public_func.h @@ -56,11 +56,6 @@ void setPointer(); */ bool isDeepinAuth(); -/** - * @brief 把字符串解析成时间,然后转换为Unix时间戳 - */ -uint timeFromString(QString time); - /** * @brief 设置app类型,让程序知道应该获取哪个配置文件 */ diff --git a/src/global_util/qt-compat-helper.cpp b/src/global_util/qt-compat-helper.cpp new file mode 100644 index 000000000..e0f7b9f2e --- /dev/null +++ b/src/global_util/qt-compat-helper.cpp @@ -0,0 +1,17 @@ +// SPDX-FileCopyrightText: 2021 - 2024 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "qt-compat-helper.h" + +#include +#include + +uint QtCompatHelper::toSecsSinceEpoch(const QDateTime &dateTime) +{ +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) + return static_cast(dateTime.toSecsSinceEpoch()); +#else + return static_cast(dateTime.toTime_t()); +#endif +} diff --git a/src/global_util/qt-compat-helper.h b/src/global_util/qt-compat-helper.h new file mode 100644 index 000000000..9d7bc397c --- /dev/null +++ b/src/global_util/qt-compat-helper.h @@ -0,0 +1,32 @@ +// SPDX-FileCopyrightText: 2021 - 2024 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef QT_COMPAT_HELPER_H +#define QT_COMPAT_HELPER_H + +#include + +class QString; + +/** + * @brief Qt版本兼容性帮助类 + * + * 用于统一处理Qt5和Qt6之间的API差异,避免在代码中到处使用版本检查宏。 + * 主要解决QDateTime相关方法在不同Qt版本中的差异。 + */ +class QtCompatHelper +{ +public: + /** + * @brief 获取QDateTime对象对应的秒级时间戳 + * + * 在Qt5中使用toTime_t()方法,在Qt6中使用toSecsSinceEpoch()方法 + * + * @param dateTime QDateTime对象 + * @return uint 秒级时间戳 + */ + static uint toSecsSinceEpoch(const QDateTime &dateTime); +}; + +#endif // QT_COMPAT_HELPER_H diff --git a/src/global_util/signal_bridge.h b/src/global_util/signal_bridge.h new file mode 100644 index 000000000..0d9f33d09 --- /dev/null +++ b/src/global_util/signal_bridge.h @@ -0,0 +1,29 @@ +// SPDX-FileCopyrightText: 2016 - 2022 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef SIGNALBRIDGE_H +#define SIGNALBRIDGE_H + +#include "constants.h" +#include "authcommon.h" + +#include + +#include + +class SignalBridge : public QObject, public Dtk::Core::DSingleton +{ + Q_OBJECT + friend class Dtk::Core::DSingleton; +private: + explicit SignalBridge(QObject *parent = nullptr) : QObject(parent) {} + +signals: + /** + * @brief 发送 extra info 给deepin-authentication + */ + void requestSendExtraInfo(const QString &account, const AuthCommon::AuthType authType, const QString &token); +}; + +#endif // SIGNALBRIDGE_H diff --git a/src/libdde-auth/authcommon.h b/src/libdde-auth/authcommon.h index 699327025..a4e5a47ed 100644 --- a/src/libdde-auth/authcommon.h +++ b/src/libdde-auth/authcommon.h @@ -60,6 +60,7 @@ enum AuthType { AT_FingerVein = 1 << 5, // 指静脉 AT_Iris = 1 << 6, // 虹膜 AT_Passkey = 1 << 7, // Passkey + AT_Pattern = 1 << 8, // 手势 AT_PAM = 1 << 29, // PAM AT_Custom = 1 << 30, // 自定义 AT_All = -1 // all @@ -86,7 +87,9 @@ enum AuthState { AS_Ended, // 认证已结束,调用 End 之后,每种成功关闭的都会发送此信号,当某种认证类型被锁定时,也会触发此信号 AS_Locked, // 认证已锁定,当认证类型锁定时,触发此信号。该信号不会给出锁定等待时间信息 AS_Recover, // 设备恢复,需要调用 Start 重新开启认证,对应 AS_Exception - AS_Unlocked // 认证解锁,对应 AS_Locked + AS_Unlocked, // 认证解锁,对应 AS_Locked + AS_Unknown, // 未知状态 + AS_VerifyCode, // 需要验证码 }; Q_ENUM_NS(AuthState) diff --git a/src/libdde-auth/deepinauthframework.cpp b/src/libdde-auth/deepinauthframework.cpp index 0c92db5e2..f1e6959b6 100644 --- a/src/libdde-auth/deepinauthframework.cpp +++ b/src/libdde-auth/deepinauthframework.cpp @@ -60,6 +60,12 @@ DeepinAuthFramework::DeepinAuthFramework(QObject *parent) watcher->deleteLater(); }); } + + connect(&m_authReminder, &QTimer::timeout, this, []{ + qCWarning(DDE_SHELL) << "No auth result recived after token send"; + }); + + m_authReminder.setSingleShot(false); } void DeepinAuthFramework::onDeviceChanged(const int authType, const int state) @@ -268,7 +274,7 @@ void DeepinAuthFramework::SendToken(const QString &token) if (!m_waitToken) { return; } - qCInfo(DDE_SHELL) << "Send token to PAM, token: " << token; + qCInfo(DDE_SHELL) << "Token arrived, is it empty ? " << token.isEmpty(); m_token = token; m_waitToken = false; } @@ -330,6 +336,11 @@ void DeepinAuthFramework::CreateAuthController(const QString &account, const Aut connect(authControllerInter, &AuthControllerInter::PINLenChanged, this, &DeepinAuthFramework::PINLenChanged); connect(authControllerInter, &AuthControllerInter::PromptChanged, this, &DeepinAuthFramework::PromptChanged); connect(authControllerInter, &AuthControllerInter::Status, this, [this](int flag, int state, const QString &msg) { + if (m_authReminder.isActive()) { + m_authReminder.stop(); + qCWarning(DDE_SHELL) << "reminder stopped for status changed"; + } + const AuthType type = AUTH_TYPE_CAST(flag); const AuthState authState = AUTH_STATE_CAST(state); emit AuthStateChanged(type, authState, msg); @@ -434,17 +445,15 @@ void DeepinAuthFramework::EndAuthentication(const QString &account, const AuthFl */ void DeepinAuthFramework::SendTokenToAuth(const QString &account, const AuthType authType, const QString &token) { + qCInfo(DDE_SHELL) << "Send token to auth, account: " << account << ", auth type:" << authType; if (!m_authenticateControllers->contains(account)) { qCWarning(DDE_SHELL) << "account not included"; - // 无论出于什么原因到这个逻辑,必须更新前端当前认证状态 - // 否则将导致登录停在这里 - Q_EMIT TokenAccountMismatch(m_lastAuthUser); return; } - qCInfo(DDE_SHELL) << "Send token to auth, account: " << account << ", auth type:" << authType; QByteArray ba = EncryptHelper::ref().getEncryptedToken(token); m_authenticateControllers->value(account)->SetToken(authType, ba); + m_authReminder.start(3000); } /** @@ -646,3 +655,19 @@ bool DeepinAuthFramework::isDeepinAuthValid() const return QDBusConnection::systemBus().interface()->isServiceRegistered(DSS_DBUS::authenticateService) && (Available == GetFrameworkState()); } + +void DeepinAuthFramework::sendExtraInfo(const QString &account, AuthType authType, const QString &info) +{ + if (!m_authenticateControllers->contains(account)) { + qCWarning(DDE_SHELL) << "Do not contain account"; + return; + } + qInfo() << "Send extra info to authentication:" << account << authType; + AuthControllerInter *inter = m_authenticateControllers->value(account); + QDBusMessage msg = QDBusMessage::createMethodCall(inter->service(), inter->path(), inter->interface(), "SetExtraInfo"); + msg << authType << QString("{\"type\":%1,\"data\":\"%2\"}").arg(authType).arg(info); + QDBusPendingCall reply = inter->connection().asyncCall(msg, 2000); + if (reply.isError()) { + qCWarning(DDE_SHELL) << "Send extra info error:" << reply.error(); + } +} diff --git a/src/libdde-auth/deepinauthframework.h b/src/libdde-auth/deepinauthframework.h index 394eab2fc..314559984 100644 --- a/src/libdde-auth/deepinauthframework.h +++ b/src/libdde-auth/deepinauthframework.h @@ -64,7 +64,7 @@ class DeepinAuthFramework : public QObject bool authSessionExist(const QString &account) const; bool isDeepinAuthValid() const; bool isDAStartupCompleted() const { return m_isDAStartupCompleted;} - + void sendExtraInfo(const QString &account, AuthCommon::AuthType authType, const QString &info); signals: void startupCompleted(); diff --git a/src/lightdm-deepin-greeter/greeterworker.cpp b/src/lightdm-deepin-greeter/greeterworker.cpp index 9461eb080..f8b1daac1 100644 --- a/src/lightdm-deepin-greeter/greeterworker.cpp +++ b/src/lightdm-deepin-greeter/greeterworker.cpp @@ -9,6 +9,8 @@ #include "userinfo.h" #include "dconfig_helper.h" #include "login_plugin_util.h" +#include "mfasequencecontrol.h" +#include "signal_bridge.h" #include @@ -324,6 +326,8 @@ void GreeterWorker::initConnections() // 监听terminal锁定状态 QDBusConnection::systemBus().connect(DSS_DBUS::accountsService, DSS_DBUS::accountsPath, "org.freedesktop.DBus.Properties", "PropertiesChanged", this, SLOT(terminalLockedChanged(QDBusMessage))); + + connect(&SignalBridge::ref(), &SignalBridge::requestSendExtraInfo, this, &GreeterWorker::sendExtraInfo); } void GreeterWorker::initData() @@ -333,38 +337,47 @@ void GreeterWorker::initData() m_model->setSEType(true); } - /* com.deepin.daemon.Accounts */ - m_model->updateUserList(m_accountsInter->userList()); - m_model->updateLoginedUserList(m_loginedInter->userList()); - - m_model->setUserlistVisible(valueByQSettings("", "userlist", true)); - /* com.deepin.udcp.iam */ - QDBusInterface ifc(DSS_DBUS::udcpIamService, DSS_DBUS::udcpIamPath, DSS_DBUS::udcpIamService, QDBusConnection::systemBus(), this); - const bool allowShowCustomUser = (!m_model->userlistVisible()) || valueByQSettings("", "loginPromptInput", false) || - ifc.property("Enable").toBool() || checkIsADDomain(); - m_model->setAllowShowCustomUser(allowShowCustomUser); - - /* init current user */ - if (DSysInfo::deepinType() == DSysInfo::DeepinServer || m_model->allowShowCustomUser()) { - // 如果是服务器版本或者loginPromptInput配置为true,默认显示空用户 - std::shared_ptr user(new User()); - m_model->setIsServerModel(DSysInfo::deepinType() == DSysInfo::DeepinServer); - m_model->addUser(user); - if (DSysInfo::deepinType() == DSysInfo::DeepinServer || valueByQSettings("", "loginPromptInput", false) || !m_model->userlistVisible()) { - m_model->updateCurrentUser(user); + auto list = m_accountsInter->userList(); + if (!list.isEmpty()) { + /* com.deepin.daemon.Accounts */ + m_model->updateUserList(list); + m_model->updateLoginedUserList(m_loginedInter->userList()); + + m_model->setUserlistVisible(valueByQSettings("", "userlist", true)); + /* com.deepin.udcp.iam */ + QDBusInterface ifc(DSS_DBUS::udcpIamService, DSS_DBUS::udcpIamPath, DSS_DBUS::udcpIamService, QDBusConnection::systemBus(), this); + const bool allowShowCustomUser = (!m_model->userlistVisible()) || valueByQSettings("", "loginPromptInput", false) || + ifc.property("Enable").toBool() || checkIsADDomain(); + m_model->setAllowShowCustomUser(allowShowCustomUser); + + /* init current user */ + if (DSysInfo::deepinType() == DSysInfo::DeepinServer || m_model->allowShowCustomUser()) { + // 如果是服务器版本或者loginPromptInput配置为true,默认显示空用户 + std::shared_ptr user(new User()); + m_model->setIsServerModel(DSysInfo::deepinType() == DSysInfo::DeepinServer); + m_model->addUser(user); + if (DSysInfo::deepinType() == DSysInfo::DeepinServer || valueByQSettings("", "loginPromptInput", false) || !m_model->userlistVisible()) { + m_model->updateCurrentUser(user); + } else { + /* com.deepin.dde.LockService */ + m_model->updateCurrentUser(m_lockInter->CurrentUser()); + } } else { /* com.deepin.dde.LockService */ m_model->updateCurrentUser(m_lockInter->CurrentUser()); } - } else { - /* com.deepin.dde.LockService */ - m_model->updateCurrentUser(m_lockInter->CurrentUser()); - } #ifndef ENABLE_DSS_SNIPE m_soundPlayerInter->PrepareShutdownSound(static_cast(m_model->currentUser()->uid())); #else prepareShutdownSound(); #endif + } else { + qCWarning(DDE_SHELL) << "dbus com.deepin.daemon.Accounts userList is empty, use ..."; + m_model->setAllowShowCustomUser(true); + std::shared_ptr user(new User()); + m_model->addUser(user); + m_model->updateCurrentUser(user); + } /* com.deepin.daemon.Authenticate */ if (m_authFramework->isDAStartupCompleted() ) { @@ -908,7 +921,12 @@ void GreeterWorker::onAuthStateChanged(const int type, const int state, const QS case AS_Success: if (m_model->currentModeState() != SessionBaseModel::ResetPasswdMode) m_model->setCurrentModeState(SessionBaseModel::ModeStatus::PasswordMode); - m_resetSessionTimer->start(); + + // 处于序列化多因认证过程中时不要重置认证 + if (MFASequenceControl::instance().currentAuthType() == AuthType::AT_None) { + m_resetSessionTimer->start(); + } + m_model->updateAuthState(AuthType(type), AuthState(state), message); break; case AS_Failure: @@ -1217,3 +1235,14 @@ void GreeterWorker::prepareShutdownSound() //soundPlayerInter.call("PrepareShutdownSound", static_cast(m_model->currentUser()->uid())); } #endif + +void GreeterWorker::sendExtraInfo(const QString &account, AuthCommon::AuthType authType, const QString &info) +{ + switch (m_model->getAuthProperty().FrameworkState) { + case Available: + m_authFramework->sendExtraInfo(account, authType, info); + break; + default: + break; + } +} diff --git a/src/lightdm-deepin-greeter/greeterworker.h b/src/lightdm-deepin-greeter/greeterworker.h index dbc5a6d57..ab38a8549 100644 --- a/src/lightdm-deepin-greeter/greeterworker.h +++ b/src/lightdm-deepin-greeter/greeterworker.h @@ -65,6 +65,7 @@ private slots: void onSessionCreated(); void terminalLockedChanged(const QDBusMessage &msg); void resetAuth(const QString &); + void sendExtraInfo(const QString &account, AuthCommon::AuthType authType, const QString &info); private: void initConnections(); diff --git a/src/session-widgets/assist_login_widget.cpp b/src/session-widgets/assist_login_widget.cpp index 52d1b4783..bb0a5f7c8 100644 --- a/src/session-widgets/assist_login_widget.cpp +++ b/src/session-widgets/assist_login_widget.cpp @@ -3,6 +3,9 @@ // SPDX-License-Identifier: GPL-3.0-or-later #include "assist_login_widget.h" +#include "plugin_manager.h" + +using DSS_PLUGIN_TYPE = dss::module::BaseModuleInterface::ModuleType; QList AssistLoginWidget::AssistLoginWidgetObjs = {}; @@ -30,6 +33,7 @@ void AssistLoginWidget::setModule(LoginPlugin *module) m_module = module; setCallback(); + updateVisible(); } void AssistLoginWidget::initUI() @@ -43,9 +47,18 @@ void AssistLoginWidget::initUI() qCInfo(DDE_SHELL) << Q_FUNC_INFO << "m_module->init()"; m_module->init(); m_mainLayout->addWidget(m_module->content()); + setFocusProxy(m_module->content()); } } +dss::module::BaseModuleInterface::ModuleType AssistLoginWidget::pluginType() const +{ + if (m_module) + return m_module->type(); + + return dss::module::BaseModuleInterface::ModuleType::LoginType; +} + void AssistLoginWidget::setCallback() { if (!m_module) { @@ -68,7 +81,7 @@ void AssistLoginWidget::authCallback(const LoginPlugin::AuthCallbackData *callba QString AssistLoginWidget::messageCallback(const QString &message, void *app_data) { - qCDebug(DDE_SHELL) << Q_FUNC_INFO << "Received message: " << message; + qCInfo(DDE_SHELL) << "Received message: " << message; QJsonParseError jsonParseError; const QJsonDocument messageDoc = QJsonDocument::fromJson(message.toLatin1(), &jsonParseError); @@ -94,7 +107,13 @@ QString AssistLoginWidget::messageCallback(const QString &message, void *app_dat QString cmdType = messageObj.value("CmdType").toString(); qCInfo(DDE_SHELL) << "Cmd type: " << cmdType; if (cmdType == "GetProperties") { - } else if (cmdType == "setAuthTypeInfo") { + return PluginManager::instance()->getProperties(messageObj); + } + + if (cmdType == "ReadyToAuthChanged") { + Q_EMIT assistLoginWidget->readyToAuthChanged(messageObj["Data"].toBool()); + } else if (cmdType == "PluginEnabledChanged") { + assistLoginWidget->setVisible(messageObj["Data"].toBool()); } retObj["Data"] = dataObj; @@ -104,13 +123,22 @@ QString AssistLoginWidget::messageCallback(const QString &message, void *app_dat void AssistLoginWidget::setAuthData(const LoginPlugin::AuthCallbackData &callbackData) { m_currentAuthData = callbackData; + qCDebug(DDE_SHELL) << Q_FUNC_INFO << m_currentAuthData.result; + const QString &account = callbackData.account; + const QString &token = callbackData.token; + if (DSS_PLUGIN_TYPE::PasswordExtendLoginType == pluginType()) { + qCInfo(DDE_SHELL) << "Request send extra info, result: " << callbackData.result; + m_extraInfo = token; + if (dss::module::AuthResult::Success == callbackData.result) + Q_EMIT requestSendExtraInfo(token); + return; + } + if (dss::module::AuthResult::Success != callbackData.result) { qCWarning(DDE_SHELL) << "Custom auth result is not success"; return; } - qCDebug(DDE_SHELL) << Q_FUNC_INFO << m_currentAuthData.result; - const QString &account = callbackData.account; - const QString &token = callbackData.token; + if (!account.isEmpty()) { qCInfo(DDE_SHELL) << Q_FUNC_INFO << "requestSendToken: " << account << token; emit requestSendToken(account, token); @@ -176,3 +204,24 @@ void AssistLoginWidget::setPluginConfig(const LoginPlugin::PluginConfig &pluginC Q_EMIT requestPluginConfigChanged(m_pluginConfig); } } + +void AssistLoginWidget::setAuthState(AuthCommon::AuthState state, const QString &result) +{ + if (m_module) { + m_module->authStateChanged(AuthCommon::AT_Password, state, result); + } +} + +void AssistLoginWidget::updateVisible() +{ + if (m_module) + setVisible(m_module->isPluginEnabled()); +} + +bool AssistLoginWidget::readyToAuth() const +{ + if (m_module) + return m_module->readyToAuth(); + + return true; +} diff --git a/src/session-widgets/assist_login_widget.h b/src/session-widgets/assist_login_widget.h index 11dd25850..c1af183bd 100644 --- a/src/session-widgets/assist_login_widget.h +++ b/src/session-widgets/assist_login_widget.h @@ -27,15 +27,22 @@ class AssistLoginWidget: public AuthModule void startAuth(); void resetAuth(); void updateConfig(); + void updateVisible(); const LoginPlugin::PluginConfig &getPluginConfig() const; void setPluginConfig(const LoginPlugin::PluginConfig &PluginConfig); + void setAuthState(AuthCommon::AuthState state, const QString &result); + dss::module::BaseModuleInterface::ModuleType pluginType() const; + bool readyToAuth() const; + QString extraInfo() const { return m_extraInfo; } Q_SIGNALS: void requestCheckAccount(const QString &account); void requestSendToken(const QString &account, const QString &token); void requestPluginConfigChanged(const LoginPlugin::PluginConfig &PluginConfig); void requestHidePlugin(); + void readyToAuthChanged(bool ready); + void requestSendExtraInfo(const QString &info); private: void setCallback(); @@ -49,6 +56,7 @@ class AssistLoginWidget: public AuthModule LoginPlugin *m_module; static QList AssistLoginWidgetObjs; LoginPlugin::PluginConfig m_pluginConfig; + QString m_extraInfo; }; diff --git a/src/session-widgets/auth_custom.cpp b/src/session-widgets/auth_custom.cpp index d6fd560aa..6f3592f87 100644 --- a/src/session-widgets/auth_custom.cpp +++ b/src/session-widgets/auth_custom.cpp @@ -13,8 +13,8 @@ using namespace dss::module; QList AuthCustom::AuthCustomObjs = {}; -AuthCustom::AuthCustom(QWidget *parent) - : AuthModule(AuthCommon::AT_Custom, parent) +AuthCustom::AuthCustom(QWidget *parent, AuthCommon::AuthType type) + : AuthModule(type, parent) , m_mainLayout(new QVBoxLayout(this)) , m_plugin(nullptr) , m_model(nullptr) @@ -26,6 +26,9 @@ AuthCustom::AuthCustom(QWidget *parent) m_mainLayout->setSpacing(0); AuthCustomObjs.append(this); + + // 需要父类的定时解锁 + AuthModule::initConnections(); } AuthCustom::~AuthCustom() @@ -45,7 +48,7 @@ void AuthCustom::detachPlugin() m_plugin = nullptr; } -void AuthCustom::setModule(LoginPlugin* module) +void AuthCustom::setModule(LoginPlugin *module) { if (m_plugin) { return; @@ -114,12 +117,17 @@ void AuthCustom::setAuthState(const AuthCommon::AuthState state, const QString & case AuthCommon::AS_Started: // greeter需要等lightdm开启验证后再发送认证信息 if (m_model->appType() == AuthCommon::AppType::Lock) { + // FIXME: 并不是所有插件都会有此场景下发token的需求,因此注意清空authdata sendAuthToken(); } break; case AuthCommon::AS_Success: emit authFinished(state); break; + case AuthCommon::AS_Unlocked: + // 切换用户时,会有解锁的动作,因此需要响应unlock + emit activeAuth(m_type); + break; default: break; } @@ -133,12 +141,15 @@ void AuthCustom::setAuthData(const LoginPlugin::AuthCallbackData &callbackData) return; } - const QString &account = callbackData.account; + const QString &account = m_currentAuthData.account; if (!account.isEmpty()) { qCInfo(DDE_SHELL) << "Request check account: " << account; emit requestCheckAccount(account, m_plugin->pluginConfig().switchUserWhenCheckAccount); } else { - emit requestSendToken(""); + // 如果插件的token信息带上了用户名,则通过checkAccount信号通知外面去做用户切换或发送token + // 隐式地要求插件仅在主动切换用户时需要填充token的account,注意需要在代码中增加信号响应,参见sfawidget + Q_EMIT requestSendToken(m_currentAuthData.token); + resetAuth(); } } @@ -275,6 +286,18 @@ bool AuthCustom::event(QEvent *e) return AuthModule::event(e); } +// 响应解锁 +void AuthCustom::updateUnlockPrompt() +{ + // DA不会主动刷新limit信息,以当前解锁时间判断 + if (m_integerMinutes == 0) { + QTimer::singleShot(1000, this, [this] { + emit activeAuth(m_type); + }); + qCInfo(DDE_SHELL) << "Waiting authentication service..."; + } +} + void AuthCustom::updateConfig() { if (!m_plugin) @@ -375,6 +398,27 @@ void AuthCustom::setLimitsInfo(const QMap &limitsInfo) m_plugin->message(toJson(message)); } +void AuthCustom::setLimitsInfo(const LimitsInfo &limitsInfo) +{ + if (!m_plugin) + return; + + QJsonObject message; + message["CmdType"] = "LimitsInfo"; + + // FIXME: 注意limitsinfo这个结构体没有必要存在两个定义 + QJsonObject obj; + obj["Locked"] = limitsInfo.locked; + obj["MaxTries"] = static_cast(limitsInfo.maxTries); + obj["NumFailures"] = static_cast(limitsInfo.numFailures); + obj["UnlockSecs"] = static_cast(limitsInfo.unlockSecs);; + obj["UnlockTime"] = limitsInfo.unlockTime; + message["Data"] = obj; + m_plugin->message(toJson(message)); + + AuthModule::setLimitsInfo(limitsInfo); +} + QJsonObject AuthCustom::getRootObj(const QString &jsonStr) { QJsonParseError jsonParseError; diff --git a/src/session-widgets/auth_custom.h b/src/session-widgets/auth_custom.h index 4d055f540..741f22204 100644 --- a/src/session-widgets/auth_custom.h +++ b/src/session-widgets/auth_custom.h @@ -11,6 +11,7 @@ #include "login_plugin.h" #include +#include class SessionBaseModel; @@ -23,11 +24,9 @@ class AuthCustom : public AuthModule DeepinAuthenticate // deepin authentication framework - 深度认证框架 }; - public: - - explicit AuthCustom(QWidget *parent = nullptr); - ~AuthCustom(); + explicit AuthCustom(QWidget *parent = nullptr, AuthCommon::AuthType type = AuthCommon::AT_Custom); + virtual ~AuthCustom() override; void setModule(LoginPlugin *module); LoginPlugin* getLoginPlugin() const; @@ -47,7 +46,7 @@ class AuthCustom : public AuthModule void lightdmAuthStarted(); void notifyAuthState(AuthCommon::AuthFlags authType, AuthCommon::AuthState state); - using AuthModule::setLimitsInfo; // 避免警告:XXX hides overloaded virtual function + void setLimitsInfo(const LimitsInfo &limitsInfo) override; void setLimitsInfo(const QMap &limitsInfo); void setCustomAuthIndex(int index) { m_customAuthIndex = index; } @@ -59,6 +58,7 @@ class AuthCustom : public AuthModule bool event(QEvent *e) override; private: + void updateUnlockPrompt() override; void setCallback(); static void authCallback(const LoginPlugin::AuthCallbackData *callbackData, void *app_data); static QString messageCallback(const QString &message, void *app_data); diff --git a/src/session-widgets/auth_face.cpp b/src/session-widgets/auth_face.cpp index e3ff5d394..11b8841d6 100644 --- a/src/session-widgets/auth_face.cpp +++ b/src/session-widgets/auth_face.cpp @@ -12,6 +12,7 @@ AuthFace::AuthFace(QWidget *parent) : AuthModule(AuthCommon::AT_Face, parent) , m_aniIndex(-1) , m_textLabel(new DLabel(this)) + , m_filterTimer(new QTimer(this)) { setObjectName(QStringLiteral("AuthFace")); setAccessibleName(QStringLiteral("AuthFace")); @@ -37,6 +38,10 @@ void AuthFace::initUI() m_authStateLabel->installEventFilter(this); setAuthStateStyle(LOGIN_WAIT); mainLayout->addWidget(m_authStateLabel, 0, Qt::AlignRight | Qt::AlignVCenter); + + // 多数摄像头模组前2s内会有由暗变亮的过程,特别是重新上电的第一次,例如待机/休眠/重启等场景,此时获取的人脸图像会报错,此处增加一个定时器,过滤前2s的错误 + m_filterTimer->setInterval(2000); + m_filterTimer->setSingleShot(true); } /** @@ -68,6 +73,7 @@ void AuthFace::reset() */ void AuthFace::setAuthState(const AuthCommon::AuthState state, const QString &result) { + static bool startFilterTimer = false; m_state = state; switch (state) { case AuthCommon::AS_Success: @@ -79,6 +85,7 @@ void AuthFace::setAuthState(const AuthCommon::AuthState state, const QString &re m_showPrompt = true; emit authFinished(state); emit retryButtonVisibleChanged(false); + startFilterTimer = false; break; case AuthCommon::AS_Failure: { setAnimationState(false); @@ -92,6 +99,7 @@ void AuthFace::setAuthState(const AuthCommon::AuthState state, const QString &re } emit retryButtonVisibleChanged(true); emit authFinished(state); + startFilterTimer = false; break; } case AuthCommon::AS_Cancel: @@ -106,9 +114,15 @@ void AuthFace::setAuthState(const AuthCommon::AuthState state, const QString &re m_showPrompt = true; break; case AuthCommon::AS_Verify: + if (!startFilterTimer) { + m_filterTimer->start(); + startFilterTimer = true; + } setAnimationState(false); setAuthStateStyle(isMFA() ? LOGIN_SPINNER : AUTH_LOCK); - m_textLabel->setText(result); + if (!m_filterTimer->isActive()) { + m_textLabel->setText(result); + } break; case AuthCommon::AS_Exception: setAnimationState(false); @@ -121,6 +135,7 @@ void AuthFace::setAuthState(const AuthCommon::AuthState state, const QString &re setAuthStateStyle(isMFA() ? LOGIN_WAIT : AUTH_LOCK); break; case AuthCommon::AS_Started: + startFilterTimer = false; m_textLabel->setText(tr("Verify your Face ID")); break; case AuthCommon::AS_Ended: diff --git a/src/session-widgets/auth_face.h b/src/session-widgets/auth_face.h index 00647b82c..e7400a5d5 100644 --- a/src/session-widgets/auth_face.h +++ b/src/session-widgets/auth_face.h @@ -38,6 +38,7 @@ public slots: private: int m_aniIndex; DLabel *m_textLabel; + QTimer *m_filterTimer; }; #endif // AUTHFACE_H diff --git a/src/session-widgets/auth_module.cpp b/src/session-widgets/auth_module.cpp index f5e6d7929..f3ddf6186 100644 --- a/src/session-widgets/auth_module.cpp +++ b/src/session-widgets/auth_module.cpp @@ -153,13 +153,7 @@ void AuthModule::updateUnlockTime() void AuthModule::updateIntegerMinutes() { if (QDateTime::fromString(m_limitsInfo->unlockTime, Qt::ISODateWithMs) > QDateTime::currentDateTime()) { -#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) - qreal intervalSeconds = QDateTime::fromString(m_limitsInfo->unlockTime, Qt::ISODateWithMs).toLocalTime().toSecsSinceEpoch() - - QDateTime::currentDateTimeUtc().toSecsSinceEpoch(); -#else - qreal intervalSeconds = QDateTime::fromString(m_limitsInfo->unlockTime, Qt::ISODateWithMs).toLocalTime().toTime_t() - - QDateTime::currentDateTimeUtc().toTime_t(); -#endif + qreal intervalSeconds = QDateTime::fromString(m_limitsInfo->unlockTime, Qt::ISODateWithMs).toLocalTime().toSecsSinceEpoch() - QDateTime::currentDateTimeUtc().toSecsSinceEpoch(); m_integerMinutes = static_cast(qCeil(intervalSeconds / 60)); } else { m_integerMinutes = 0; diff --git a/src/session-widgets/auth_password.cpp b/src/session-widgets/auth_password.cpp index 444ec6c7f..e8c3d956d 100644 --- a/src/session-widgets/auth_password.cpp +++ b/src/session-widgets/auth_password.cpp @@ -38,6 +38,7 @@ const QString PASSWORD_SHOWN = QStringLiteral(":/misc/images/password-shown.svg" const QString DConfig_LongPressDisplayPassword = "longPressDisplayPassword"; using namespace AuthCommon; +using DSS_PLUGIN_TYPE = dss::module::BaseModuleInterface::ModuleType; AuthPassword::AuthPassword(QWidget *parent) : AuthModule(AT_Password, parent) @@ -55,6 +56,7 @@ AuthPassword::AuthPassword(QWidget *parent) , m_isPasswdAuthWidgetReplaced(false) , m_assistLoginWidget(nullptr) , m_authenticationDconfig(DConfig::create("org.deepin.dde.authentication", "org.deepin.dde.authentication.errorecho", QString(), this)) + , m_canShowPasswordErrorTips(false) { qRegisterMetaType("LoginPlugin::PluginConfig"); @@ -71,6 +73,13 @@ AuthPassword::AuthPassword(QWidget *parent) setFocusProxy(m_lineEdit); } +AuthPassword::~AuthPassword() +{ + if (m_resetPasswordMessageVisible) { + closeResetPasswordMessage(); + } +} + /** * @brief 初始化界面 */ @@ -78,7 +87,7 @@ void AuthPassword::initUI() { QVBoxLayout *mainLayout = new QVBoxLayout(this); mainLayout->setContentsMargins(0, 0, 0, 0); - mainLayout->setSpacing(5); + mainLayout->setSpacing(10); m_lineEdit->setClearButtonEnabled(false); m_lineEdit->setEchoMode(QLineEdit::Password); @@ -105,9 +114,15 @@ void AuthPassword::initUI() /* 缩放因子 */ passwordLayout->addStretch(1); + /* 认证状态 */ + m_authStateLabel = new DLabel(this); + m_authStateLabel->setVisible(false); + setAuthStateStyle(LOGIN_WAIT); + passwordLayout->addWidget(m_authStateLabel, 0, Qt::AlignRight | Qt::AlignVCenter); + /*显示密码*/ m_passwordShowBtn->setAccessibleName(QStringLiteral("PasswordShow")); - m_passwordShowBtn->setContentsMargins(0,0,0,0); + m_passwordShowBtn->setContentsMargins(0, 0, 0, 0); m_passwordShowBtn->setFocusPolicy(Qt::NoFocus); m_passwordShowBtn->setCursor(Qt::ArrowCursor); m_passwordShowBtn->setFlat(true); @@ -116,11 +131,6 @@ void AuthPassword::initUI() m_passwordShowBtn->setVisible(true); passwordLayout->addWidget(m_passwordShowBtn, 0, Qt::AlignRight | Qt::AlignVCenter); - /* 认证状态 */ - m_authStateLabel = new DLabel(this); - m_authStateLabel->setVisible(false); - setAuthStateStyle(LOGIN_WAIT); - passwordLayout->addWidget(m_authStateLabel, 0, Qt::AlignRight | Qt::AlignVCenter); /* 密码提示 */ m_passwordHintBtn->setAccessibleName(QStringLiteral("PasswordHint")); m_passwordHintBtn->setContentsMargins(0, 0, 0, 0); @@ -132,12 +142,11 @@ void AuthPassword::initUI() m_passwordHintBtn->setVisible(false); passwordLayout->addWidget(m_passwordHintBtn, 0, Qt::AlignRight | Qt::AlignVCenter); mainLayout->addWidget(m_lineEdit); - auto plugin = PluginManager::instance()->getAssistloginPlugin(); + auto plugin = PluginManager::instance()->getAssistloginPlugin(); if (plugin) { m_isPasswdAuthWidgetReplaced = true; m_assistLoginWidget = new AssistLoginWidget(this); - m_assistLoginWidget->setModule(plugin); m_assistLoginWidget->initUI(); mainLayout->addWidget(m_assistLoginWidget); @@ -146,6 +155,16 @@ void AuthPassword::initUI() m_lineEdit->show(); } + // 密码框下面增加一个认证界面 + auto extendPlugin = PluginManager::instance()->getFirstLoginPlugin(dss::module::BaseModuleInterface::PasswordExtendLoginType); + if (extendPlugin) { + m_assistLoginWidget = new AssistLoginWidget(this); + m_assistLoginWidget->setModule(extendPlugin); + m_assistLoginWidget->initUI(); + mainLayout->addWidget(m_assistLoginWidget); + } else { + qCDebug(DDE_SHELL) << "There's no password extend plugin"; + } updatePasswordTextMargins(); m_passwordTipsWidget->hide(); } @@ -156,20 +175,27 @@ void AuthPassword::initUI() void AuthPassword::initConnections() { AuthModule::initConnections(); + + auto blockEditSig = [this]() -> bool { + return m_assistLoginWidget + && m_assistLoginWidget->isVisible() + && DSS_PLUGIN_TYPE::PasswordExtendLoginType == m_assistLoginWidget->pluginType() + && !m_assistLoginWidget->readyToAuth(); + }; /* 密码提示 */ connect(m_passwordHintBtn, &DIconButton::clicked, this, &AuthPassword::showPasswordHint); /* 密码输入框 */ - connect(m_lineEdit, &DLineEditEx::focusChanged, this, [this](const bool focus) { + connect(m_lineEdit, &DLineEditEx::focusChanged, this, [this, blockEditSig](const bool focus) { if (!focus) m_lineEdit->setAlert(false); m_authStateLabel->setVisible(!focus && m_showAuthState); updatePasswordTextMargins(); emit focusChanged(focus); - if (focus) { + if (focus && !blockEditSig()) { emit lineEditTextChanged(m_lineEdit->text()); } }); - connect(m_lineEdit, &DLineEditEx::textChanged, this, [this](const QString &text) { + connect(m_lineEdit, &DLineEditEx::textChanged, this, [this, blockEditSig](const QString &text) { m_lineEdit->hideAlertMessage(); hidePasswordHintWidget(); m_lineEdit->setAlert(false); @@ -181,11 +207,18 @@ void AuthPassword::initConnections() m_passwordTipsWidget->adjustSize(); emit passwordErrorTipsClearChanged(true); } - emit lineEditTextChanged(text); + if (!blockEditSig()) + emit lineEditTextChanged(text); }); - connect(m_lineEdit, &DLineEditEx::returnPressed, this, [this] { - if (!m_lineEdit->lineEdit()->isReadOnly()) // 避免用户在验证的时候反复点击 - emit requestAuthenticate(); + connect(m_lineEdit, &DLineEditEx::returnPressed, this, [this, blockEditSig] { + if (!m_lineEdit->lineEdit()->isReadOnly()) { // 避免用户在验证的时候反复点击 + if (!blockEditSig()) { + emit requestAuthenticate(); + } else { + setFocusProxy(m_assistLoginWidget); + m_assistLoginWidget->setFocus(); + } + } }); if (DConfigHelper::instance()->getConfig(DConfig_LongPressDisplayPassword, true).toBool()) { @@ -217,14 +250,38 @@ void AuthPassword::initConnections() } if (m_assistLoginWidget && m_isPasswdAuthWidgetReplaced) { - connect(m_assistLoginWidget, &AssistLoginWidget::requestPluginConfigChanged, this, [this] (const LoginPlugin::PluginConfig pluginConfig) { + connect(m_assistLoginWidget, &AssistLoginWidget::requestPluginConfigChanged, this, [this](const LoginPlugin::PluginConfig pluginConfig) { Q_EMIT requestPluginConfigChanged(pluginConfig); }); - connect(m_assistLoginWidget, &AssistLoginWidget::requestHidePlugin, this, [ = ] { + connect(m_assistLoginWidget, &AssistLoginWidget::requestHidePlugin, this, [=] { hidePlugin(); }); connect(m_assistLoginWidget, &AssistLoginWidget::requestSendToken, this, &AuthPassword::requestPluginAuthToken); } + + if (m_assistLoginWidget) { + connect(m_assistLoginWidget, &AssistLoginWidget::requestSendExtraInfo, this, [this](const QString &info) { + if (!m_lineEdit->text().isEmpty()) { + Q_EMIT requestAuthenticate(); + return; + } + + // 焦点切换到密码输入框 + m_lineEdit->setFocus(); + setFocusProxy(m_lineEdit); + }); + connect(m_assistLoginWidget, &AssistLoginWidget::readyToAuthChanged, this, &AuthPassword::onReadyToAuthChanged); + } + + if (m_authenticationDconfig) { + auto updateCanShow = [this] { + m_canShowPasswordErrorTips = m_authenticationDconfig->value("PasswordErrorEcho", false).toBool(); + }; + + connect(m_authenticationDconfig, &DConfig::valueChanged, this, updateCanShow); + + updateCanShow(); + } } /** @@ -235,6 +292,7 @@ void AuthPassword::reset() m_lineEdit->clear(); m_lineEdit->setAlert(false); m_lineEdit->hideAlertMessage(); + setFocusProxy(m_lineEdit); hidePasswordHintWidget(); setLineEditEnabled(true); setLineEditInfo(tr("Password"), PlaceHolderText); @@ -361,6 +419,11 @@ void AuthPassword::setAuthState(const AuthState state, const QString &result) setLineEditEnabled(true); m_showPrompt = true; break; + case AS_VerifyCode: + setAnimationState(false); + setAuthStateStyle(LOGIN_WAIT); + setLineEditEnabled(true); + break; default: setAnimationState(false); setAuthStateStyle(LOGIN_WAIT); @@ -369,6 +432,9 @@ void AuthPassword::setAuthState(const AuthState state, const QString &result) qCWarning(DDE_SHELL) << "Error! The state of Password Auth is wrong, state: " << state << ", result: " << result; break; } + if (m_assistLoginWidget) { + m_assistLoginWidget->setAuthState(state, result); + } update(); } @@ -576,8 +642,8 @@ void AuthPassword::setPasswordHintBtnVisible(const bool isVisible) void AuthPassword::setResetPasswordMessageVisible(const bool isVisible, bool fromResetDialog) { qCDebug(DDE_SHELL) << "Set reset password message visible, incoming visible:" << isVisible - << " current visible:" << m_resetPasswordMessageVisible - << " fromResetDialog " << fromResetDialog; + << " current visible:" << m_resetPasswordMessageVisible + << " fromResetDialog " << fromResetDialog; if (isVisible && fromResetDialog) { m_resetDialogShow = false; } @@ -629,7 +695,7 @@ void AuthPassword::showResetPasswordMessage() // DFloatingMessage中有两个按钮一个是DIconButton,另一个是继承于DIconButton的DDialogCloseButton,需要区分 QList btnList = m_resetPasswordFloatingMessage->findChildren(); foreach (const auto iconButton, btnList) { - DDialogCloseButton * closeButton = qobject_cast(iconButton); + DDialogCloseButton *closeButton = qobject_cast(iconButton); if (closeButton) { continue; } @@ -656,7 +722,7 @@ void AuthPassword::showResetPasswordMessage() emit m_resetPasswordFloatingMessage->closeButtonClicked(); }); - connect(m_resetPasswordFloatingMessage, &DFloatingMessage::closeButtonClicked, this, [this](){ + connect(m_resetPasswordFloatingMessage, &DFloatingMessage::closeButtonClicked, this, [this]() { if (m_resetPasswordFloatingMessage) { m_resetPasswordFloatingMessage->deleteLater(); m_resetPasswordFloatingMessage = nullptr; @@ -712,7 +778,7 @@ bool AuthPassword::isUserAccountBinded() } QString uuid = retUUID.toString(); - QDBusReply retLocalBindCheck= syncHelperInter.call("LocalBindCheck", uosid, uuid); + QDBusReply retLocalBindCheck = syncHelperInter.call("LocalBindCheck", uosid, uuid); if (!syncHelperInter.isValid()) { return false; } @@ -730,7 +796,7 @@ bool AuthPassword::isUserAccountBinded() m_bindCheckTimer = new QTimer(this); connect(m_bindCheckTimer, &QTimer::timeout, this, [this] { qCWarning(DDE_SHELL) << "BindCheck retry!"; - if(isUserAccountBinded()) { + if (isUserAccountBinded()) { setResetPasswordMessageVisible(true); updateResetPasswordUI(); } @@ -865,11 +931,9 @@ void AuthPassword::updatePasswordTextMargins() { QMargins textMargins = m_lineEdit->lineEdit()->textMargins(); // 右边控件宽度+控件间距 - const int rightWidth = (m_passwordShowBtn->isVisible() ? m_passwordShowBtn->width() + 5 : 0) + - (m_authStateLabel->isVisible() ? m_authStateLabel->width() + 5 : 0) + - (m_passwordHintBtn->isVisible() ? m_passwordHintBtn->width() + 5: 0); + const int rightWidth = (m_passwordShowBtn->isVisible() ? m_passwordShowBtn->width() + 5 : 0) + (m_authStateLabel->isVisible() ? m_authStateLabel->width() + 5 : 0) + (m_passwordHintBtn->isVisible() ? m_passwordHintBtn->width() + 5 : 0); // 左侧控件宽度 - const int leftWidth = (m_capsLock->isVisible() ? m_capsLock->width() + 5: 0); + const int leftWidth = (m_capsLock->isVisible() ? m_capsLock->width() + 5 : 0); textMargins.setRight(rightWidth); #if QT_VERSION >= QT_VERSION_CHECK(6, 6, 0) const int displayTextWidth = m_lineEdit->lineEdit()->fontMetrics().horizontalAdvance(m_lineEdit->lineEdit()->displayText()); @@ -917,15 +981,6 @@ bool AuthPassword::isShowPasswrodErrorTip() return m_passwordTipsWidget->isVisible(); } -bool AuthPassword::canShowPasswrodErrorTip() -{ - // 从da的dconfig配置获取 - if (!m_authenticationDconfig) { - return false; - } - return m_authenticationDconfig->value("PasswordErrorEcho", false).toBool(); -} - void AuthPassword::showErrorTip(const QString &text) { if (canShowPasswrodErrorTip()) { @@ -978,3 +1033,8 @@ void AuthPassword::moveEvent(QMoveEvent *event) } QWidget::moveEvent(event); } + +void AuthPassword::onReadyToAuthChanged(bool ready) +{ + ready ? emit lineEditTextChanged(m_lineEdit->text()) : emit lineEditTextChanged(""); +} diff --git a/src/session-widgets/auth_password.h b/src/session-widgets/auth_password.h index e8e500000..f970b3f32 100644 --- a/src/session-widgets/auth_password.h +++ b/src/session-widgets/auth_password.h @@ -27,6 +27,7 @@ class AuthPassword : public AuthModule Q_OBJECT public: explicit AuthPassword(QWidget *parent = nullptr); + ~AuthPassword() override; void reset(); QString lineEditText() const; @@ -61,11 +62,13 @@ class AuthPassword : public AuthModule m_isPasswdAuthWidgetReplaced = isPasswdAuthWidgetReplaced; } bool isShowPasswrodErrorTip(); - bool canShowPasswrodErrorTip(); + inline bool canShowPasswrodErrorTip() { return m_canShowPasswordErrorTips; } void showErrorTip(const QString &text); void clearPasswrodErrorTip(bool isClear); void updatePasswrodErrorTipUi(); + AssistLoginWidget * assistLoginWidget() const { return m_assistLoginWidget; } + signals: void focusChanged(const bool); void lineEditTextChanged(const QString &); // 数据同步 @@ -78,10 +81,12 @@ class AuthPassword : public AuthModule void requestPluginAuthToken(const QString accout, const QString token); void requestUpdateBlurEffectGeometry(); void passwordErrorTipsClearChanged(const bool isClear); + void requestSendExtraInfo(const QString &info); public slots: void setResetPasswordMessageVisible(const bool isVisible, bool fromResetDialog = false); void updateResetPasswordUI(); + void onReadyToAuthChanged(bool ready); protected: bool eventFilter(QObject *watched, QEvent *event) override; @@ -120,6 +125,7 @@ public slots: bool m_isPasswdAuthWidgetReplaced; AssistLoginWidget *m_assistLoginWidget; DConfig *m_authenticationDconfig; + bool m_canShowPasswordErrorTips; }; #endif // AUTHPASSWORD_H diff --git a/src/session-widgets/auth_widget.cpp b/src/session-widgets/auth_widget.cpp index 0a8a336be..b2679f53d 100644 --- a/src/session-widgets/auth_widget.cpp +++ b/src/session-widgets/auth_widget.cpp @@ -41,6 +41,7 @@ AuthWidget::AuthWidget(QWidget *parent) , m_faceAuth(nullptr) , m_irisAuth(nullptr) , m_passkeyAuth(nullptr) + , m_gestureAuth(nullptr) , m_customAuth(nullptr) , m_refreshTimer(new QTimer(this)) , m_authState(AuthCommon::AS_None) @@ -79,6 +80,7 @@ void AuthWidget::initUI() m_accountEdit->lineEdit()->setAlignment(Qt::AlignCenter); m_accountEdit->setClearButtonEnabled(false); m_accountEdit->setPlaceholderText(tr("Account")); + m_accountEdit->setEnableTableKeyEvent(true); // 账户名有效字符使用dsg配置 const QString accountExpression = DConfigHelper::instance()->getConfig("accountExpression", "[a-zA-Z0-9-_@]+$").toString(); @@ -294,6 +296,11 @@ void AuthWidget::setLimitsInfo(const QMap *limitsInfo) case AT_ActiveDirectory: case AT_Custom: break; + case AT_Pattern: + if (m_gestureAuth) { + m_gestureAuth->setLimitsInfo(limitsInfoTmp); + } + break; default: qCWarning(DDE_SHELL) << "Authentication type is wrong." << i.key(); break; @@ -546,6 +553,11 @@ int AuthWidget::getTopSpacing() const return qMax(15, deltaY); } +QWidget *AuthWidget::getAuthWidget() +{ + return this; +} + int AuthWidget::calcCurrentHeight(const int height) const { const int h = static_cast(((double) height / (double) BASE_SCREEN_HEIGHT) * topLevelWidget()->geometry().height()); diff --git a/src/session-widgets/auth_widget.h b/src/session-widgets/auth_widget.h index d2435e3ea..47e543f32 100644 --- a/src/session-widgets/auth_widget.h +++ b/src/session-widgets/auth_widget.h @@ -130,6 +130,7 @@ class AuthWidget : public QWidget virtual void setAuthType(const AuthCommon::AuthFlags type); virtual void setAuthState(const AuthCommon::AuthType type, const AuthCommon::AuthState state, const QString &message); virtual int getTopSpacing() const; + virtual QWidget* getAuthWidget(); void setAccountErrorMsg(const QString &message); void syncPasswordResetPasswordVisibleChanged(const QVariant &value); @@ -193,6 +194,7 @@ protected Q_SLOTS: QPointer m_faceAuth; // 面容 QPointer m_irisAuth; // 虹膜 QPointer m_passkeyAuth; // 安全密钥 + QPointer m_gestureAuth; // 手势 QPointer m_customAuth; // 自定义认证 QString m_passwordHint; // 密码提示 diff --git a/src/session-widgets/lockcontent.cpp b/src/session-widgets/lockcontent.cpp index fba328d78..074398ded 100644 --- a/src/session-widgets/lockcontent.cpp +++ b/src/session-widgets/lockcontent.cpp @@ -18,6 +18,7 @@ #include "keyboardmonitor.h" #include "dconfig_helper.h" #include "constants.h" +#include "mfasequencecontrol.h" #include #include @@ -41,9 +42,9 @@ LockContent::LockContent(QWidget *parent) } -LockContent* LockContent::instance() +LockContent *LockContent::instance() { - static LockContent* lockContent = nullptr; + static LockContent *lockContent = nullptr; if (!lockContent) { lockContent = new LockContent(); } @@ -148,7 +149,9 @@ void LockContent::initUI() initUserListWidget(); - initFMAWidget(); + if (PluginManager::instance()->getFullManagedLoginPlugin() != nullptr) { + initFMAWidget(); + } } void LockContent::initConnections() @@ -179,7 +182,8 @@ void LockContent::initConnections() connect(&VirtualKBInstance::Instance(), &VirtualKBInstance::initFinished, this, [&] { m_virtualKB = VirtualKBInstance::Instance().virtualKBWidget(); m_controlWidget->setVirtualKBVisible(true); - }, Qt::QueuedConnection); + }, + Qt::QueuedConnection); VirtualKBInstance::Instance().init(); } else { VirtualKBInstance::Instance().stopVirtualKBProcess(); @@ -205,11 +209,17 @@ void LockContent::initConnections() isMFA ? initMFAWidget() : initSFAWidget(); // 当前中间窗口为空或者中间窗口就是验证窗口的时候显示验证窗口 if (!m_centerWidget || m_centerWidget == m_authWidget) - setCenterContent(m_authWidget, 0, Qt::AlignTop, calcTopSpacing(m_authWidget->getTopSpacing())); + setCenterContent(m_authWidget->getAuthWidget(), 0, Qt::AlignTop, calcTopSpacing(m_authWidget->getTopSpacing())); }); connect(m_wmInter, &com::deepin::wm::WorkspaceSwitched, this, &LockContent::currentWorkspaceChanged); connect(m_localServer, &QLocalServer::newConnection, this, &LockContent::onNewConnection); + connect(m_controlWidget, &ControlWidget::notifyKeyboardLayoutHidden, this, [this] { + if (!m_model->isUseWayland() && isVisible() && window()->windowHandle()) { + qCDebug(DDE_SHELL) << "Grab keyboard after keyboard layout hidden"; + window()->windowHandle()->setKeyboardGrabEnabled(true); + } + }); connect(m_model, &SessionBaseModel::showUserList, this, &LockContent::showUserList); connect(m_model, &SessionBaseModel::showLockScreen, this, &LockContent::showLockScreen); @@ -245,6 +255,16 @@ void LockContent::initMFAWidget() connect(m_mfaWidget, &MFAWidget::requestEndAuthentication, this, &LockContent::requestEndAuthentication); connect(m_mfaWidget, &MFAWidget::requestCheckAccount, this, &LockContent::requestCheckAccount); connect(m_mfaWidget, &MFAWidget::requestCheckSameNameAccount, this, &LockContent::requestCheckSameNameAccount); + + connect(&MFASequenceControl::instance(), &MFASequenceControl::currentUIAuthTypeChanged, this, [&](int authType) { + Q_UNUSED(authType) + // 仅在当前处于认证界面上刷新 + auto isMFA = m_model->getAuthProperty().MFAFlag; + qCInfo(DDE_SHELL) << "model mfa flag" << isMFA; + if (m_model->currentModeState() == SessionBaseModel::ModeStatus::PasswordMode && isMFA) { + pushPasswordFrame(); + } + }); } /** @@ -320,7 +340,8 @@ void LockContent::initUserListWidget() void LockContent::onCurrentUserChanged(std::shared_ptr user) { - if (user.get() == nullptr) return; // if dbus is async + if (user.get() == nullptr) + return; // if dbus is async //如果是锁屏就用系统语言,如果是登陆界面就用用户语言 auto locale = qApp->applicationName() == "dde-lock" ? QLocale::system().name() : user->locale(); @@ -353,11 +374,11 @@ void LockContent::pushPasswordFrame() } } - setCenterContent(m_authWidget, 0, Qt::AlignTop, calcTopSpacing(m_authWidget->getTopSpacing())); + setCenterContent(m_authWidget->getAuthWidget(), 0, Qt::AlignTop, calcTopSpacing(m_authWidget->getTopSpacing())); m_authWidget->syncResetPasswordUI(); - if (!m_fmaWidget->isPluginLoaded()) { + if (m_fmaWidget && !m_fmaWidget->isPluginLoaded()) { showDefaultFrame(); return; } @@ -380,7 +401,7 @@ void LockContent::pushUserFrame() return; } - if(m_model->isServerModel()) + if (m_model->isServerModel()) m_controlWidget->setUserSwitchEnable(false); m_userListWidget->updateLayout(width()); @@ -393,17 +414,21 @@ void LockContent::pushUserFrame() m_controlWidget->setUserSwitchEnable(m_isUserSwitchVisible); } - showDefaultFrame(); + showDefaultFrame(); + + // fix: 解决用户界面多账户区域无焦点问题 + // showDefaultFrame() -> hideStackedWidgets() -> 会将焦点置为空 + // 导致默认用户无选中状态,多账户区域无键盘事件 + setFocus(); - // fix: 解决用户界面多账户区域无焦点问题 - // showDefaultFrame() -> hideStackedWidgets() -> 会将焦点置为空 - // 导致默认用户无选中状态,多账户区域无键盘事件 - setFocus(); + // 用户列表界面显示时,设置锁屏状态为锁定 + // 主要是为了处理需求:切换用户界面属于进入锁屏,待机唤醒时仍进入锁屏界面,需输入密码进入系统。 + Q_EMIT requestLockStateChange(true); } void LockContent::pushConfirmFrame() { - setCenterContent(m_authWidget, 0, Qt::AlignTop, calcTopSpacing(m_authWidget->getTopSpacing())); + setCenterContent(m_authWidget->getAuthWidget(), 0, Qt::AlignTop, calcTopSpacing(m_authWidget->getTopSpacing())); showDefaultFrame(); } @@ -475,7 +500,7 @@ void LockContent::onNewConnection() << "screenshot-ocr" << "screenshot-scroll" << "deepin-screen-recorder", - false, false); + false, false); } m_hasResetPasswordDialog = true; } @@ -488,9 +513,13 @@ void LockContent::onDisConnect() } // 这种情况下不必强制要求可以抓取到键盘,因为可能是网络弹窗抓取了键盘 tryGrabKeyboard(false); - enableSystemShortcut(QStringList() << "screenshot" << "screenshot-window" << "screenshot-delayed" - << "screenshot-ocr" << "screenshot-scroll" << "deepin-screen-recorder", true, false); - m_hasResetPasswordDialog = false; + enableSystemShortcut(QStringList() << "screenshot" + << "screenshot-window" + << "screenshot-delayed" + << "screenshot-ocr" + << "screenshot-scroll" + << "deepin-screen-recorder", + true, false); } void LockContent::onStatusChanged(SessionBaseModel::ModeStatus status) @@ -505,7 +534,7 @@ void LockContent::onStatusChanged(SessionBaseModel::ModeStatus status) PluginConfigMap::instance().requestRemoveConfig(PluginConfigMap::ConfigIndex::FullManagePlugin); } - if(m_model->isServerModel()) + if (m_model->isServerModel()) onUserListChanged(m_model->loginedUserList()); if (!m_isPANGUCpu && m_currentModeStatus == status) @@ -672,7 +701,7 @@ void LockContent::toggleVirtualKB() void LockContent::showModule(const QString &name, const bool callShowForce) { - PluginBase * plugin = PluginManager::instance()->findPlugin(name); + PluginBase *plugin = PluginManager::instance()->findPlugin(name); if (!plugin) { return; } @@ -702,12 +731,12 @@ bool LockContent::eventFilter(QObject *watched, QEvent *e) { // 点击插件弹窗以外区域,隐藏插件弹窗 if (m_popWin && m_currentTray && m_popWin->isVisible()) { - QWidget * w = qobject_cast(watched); + QWidget *w = qobject_cast(watched); if (!w) return false; - const bool isChild = m_currentTray->findChildren().contains(w); + const bool isChild = m_currentTray->findChildren().contains(w); if ((watched == m_currentTray || isChild) && e->type() == QEvent::Enter) { - Q_EMIT qobject_cast(m_currentTray)->requestHideTips(); + Q_EMIT qobject_cast(m_currentTray)->requestHideTips(); } else if (watched != m_currentTray && !isChild && e->type() == QEvent::MouseButtonRelease) { if (!m_popWin->geometry().contains(this->mapFromGlobal(QCursor::pos()))) { m_popWin->hide(); @@ -734,7 +763,7 @@ void LockContent::updateVirtualKBPosition() m_virtualKB->move(point); } -void LockContent::onUserListChanged(QList > list) +void LockContent::onUserListChanged(QList> list) { const bool allowShowUserSwitchButton = m_model->allowShowUserSwitchButton(); const bool alwaysShowUserSwitchButton = m_model->alwaysShowUserSwitchButton(); @@ -775,7 +804,7 @@ void LockContent::tryGrabKeyboard(bool exitIfFailed) } if (m_model->isUseWayland()) { - static QDBusInterface *kwinInter = new QDBusInterface("org.kde.KWin","/KWin","org.kde.KWin", QDBusConnection::sessionBus()); + static QDBusInterface *kwinInter = new QDBusInterface("org.kde.KWin", "/KWin", "org.kde.KWin", QDBusConnection::sessionBus()); if (!kwinInter || !kwinInter->isValid()) { qCWarning(DDE_SHELL) << "Kwin interface is invalid"; m_failures = 0; @@ -913,7 +942,7 @@ void LockContent::showTrayPopup(QWidget *trayWidget, QWidget *contentWidget, con window()->windowHandle()->setKeyboardGrabEnabled(true); } }); - + connect(this, &LockContent::parentChanged, this, [this] { if (m_popWin && m_popWin->isVisible()) { const QPoint &point = mapFromGlobal(m_currentTray->mapToGlobal(QPoint(m_currentTray->size().width() / 2, 0))); @@ -930,7 +959,7 @@ void LockContent::showTrayPopup(QWidget *trayWidget, QWidget *contentWidget, con } m_currentTray = trayWidget; - + // 隐藏后需要removeEventFilter,后期优化 for (auto child : this->findChildren()) { child->removeEventFilter(this); @@ -976,7 +1005,7 @@ void LockContent::showUserList() { if (m_model->userlistVisible()) { m_model->setCurrentModeState(SessionBaseModel::ModeStatus::UserMode); - QTimer::singleShot(10, this, [ = ] { + QTimer::singleShot(10, this, [=] { m_model->setVisible(true); }); } else { diff --git a/src/session-widgets/lockcontent.h b/src/session-widgets/lockcontent.h index 65f68c461..404780eb4 100644 --- a/src/session-widgets/lockcontent.h +++ b/src/session-widgets/lockcontent.h @@ -61,6 +61,7 @@ class LockContent : public SessionBaseWindow void requestLockFrameHide(); void parentChanged(); void noPasswordLoginChanged(const QString &account, bool noPassword); + void requestLockStateChange(const bool locked); public slots: void pushPasswordFrame(); diff --git a/src/session-widgets/mfa_widget.cpp b/src/session-widgets/mfa_widget.cpp index 8f2043676..e99818d24 100644 --- a/src/session-widgets/mfa_widget.cpp +++ b/src/session-widgets/mfa_widget.cpp @@ -13,6 +13,10 @@ #include "keyboardmonitor.h" #include "sessionbasemodel.h" #include "useravatar.h" +#include "mfasequencecontrol.h" +#include "plugin_manager.h" +#include "auth_custom.h" +#include "signal_bridge.h" MFAWidget::MFAWidget(QWidget *parent) : AuthWidget(parent) @@ -54,6 +58,10 @@ void MFAWidget::initConnections() AuthWidget::initConnections(); connect(m_model, &SessionBaseModel::authTypeChanged, this, &MFAWidget::setAuthType); connect(m_model, &SessionBaseModel::authStateChanged, this, &MFAWidget::setAuthState); + // 关闭--已开启但无UI的认证(针对手势这种开启功能,但可能因配置错误、未安装插件,导致无法完成登录/解锁的场景) + connect(&MFASequenceControl::instance(), &MFASequenceControl::requestEndUnsupportedAuth, this, [&](int authType) { + Q_EMIT requestEndAuthentication(m_model->currentUser()->name(), AUTH_FLAGS_CAST(authType)); + }); } void MFAWidget::setModel(const SessionBaseModel *model) @@ -110,6 +118,14 @@ void MFAWidget::setAuthType(const AuthFlags type) m_passwordAuth->deleteLater(); m_passwordAuth = nullptr; } + /* 手势 */ + if (type & AT_Pattern) { + initGestureAuth(); + } else if (m_gestureAuth) { + m_gestureAuth->deleteLater(); + m_gestureAuth = nullptr; + } + /* 账户 */ if (type == AT_None) { if (m_model->currentUser()->isNoPasswordLogin()) { @@ -158,6 +174,8 @@ void MFAWidget::setAuthType(const AuthFlags type) } } setFocus(); + + MFASequenceControl::instance().setAuthType(type); } /** @@ -194,12 +212,21 @@ void MFAWidget::setAuthState(const AuthCommon::AuthType type, const AuthCommon:: m_irisAuth->setAuthState(state, message); } break; + case AT_Pattern: + if (m_gestureAuth) { + m_gestureAuth->setAuthState(state, message); + // 插件需要认证结果 + m_gestureAuth->notifyAuthState(type, state); + } + break; case AT_All: checkAuthResult(type, state); break; default: break; } + + MFASequenceControl::instance().onAuthStatusChanged(type, state, message); } void MFAWidget::autoUnlock() @@ -236,6 +263,13 @@ void MFAWidget::initPasswdAuth() if (text.isEmpty()) { return; } + if (m_passwordAuth->assistLoginWidget()) { + if (m_user) { + Q_EMIT SignalBridge::ref().requestSendExtraInfo(m_user->name(), AT_Password, m_passwordAuth->assistLoginWidget()->extraInfo()); + } else { + qCWarning(DDE_SHELL) << "Current user is null, can not send extra info"; + } + } m_passwordAuth->setAuthStateStyle(LOGIN_SPINNER); m_passwordAuth->setAnimationState(true); m_passwordAuth->setLineEditEnabled(false); @@ -356,6 +390,41 @@ void MFAWidget::initIrisAuth() }); } +/** + * @brief 由插件加载,无法加载时,结束该认证 + */ +void MFAWidget::initGestureAuth() +{ + if (m_gestureAuth) { + return; + } + + auto plugin = PluginManager::instance()->getLoginPlugin(AuthType::AT_Pattern); + if (!plugin) { + // 如果插件不存在或被析构,次序控制将结束已开启的认证 + MFASequenceControl::instance().insertIsolateAuthWidget(AT_Pattern, nullptr); + qCWarning(DDE_SHELL) << "Pattern plug not found, should stop pattern auth"; + return; + } + + m_gestureAuth = new AuthCustom(this, AuthType::AT_Pattern); + m_gestureAuth->setModule(plugin); + m_gestureAuth->setModel(m_model); + m_gestureAuth->initUi(); + m_gestureAuth->reset(); + m_gestureAuth->hide(); + + connect(m_gestureAuth, &AuthCustom::requestSendToken, this, [this](const QString &token) { + Q_EMIT sendTokenToAuth(m_model->currentUser()->name(), AuthType::AT_Pattern, token); + }); + + connect(m_gestureAuth, &AuthCustom::activeAuth, this, [this] { + Q_EMIT requestStartAuthentication(m_model->currentUser()->name(), AuthType::AT_Pattern); + }); + + MFASequenceControl::instance().insertIsolateAuthWidget(AT_Pattern, m_gestureAuth->getLoginPlugin()->content()); +} + /** * @brief 检查多因子认证结果 * @@ -417,8 +486,9 @@ void MFAWidget::updateFocusPosition() int MFAWidget::getTopSpacing() const { const int calcTopHeight = static_cast(topLevelWidget()->geometry().height() * AUTH_WIDGET_TOP_SPACING_PERCENT); - // 验证类型数量*高度 - const int authWidgetHeight = m_index * 47 + MIN_AUTH_WIDGET_HEIGHT; + auto sequenceAuthWid = MFASequenceControl::instance().currentAuthWidget(); + // 验证类型数量*高度,或者是独立ui的高度 + const int authWidgetHeight = sequenceAuthWid == nullptr ? m_index * 47 + MIN_AUTH_WIDGET_HEIGHT : sequenceAuthWid->height(); // 当分辨率过低时,如果仍然保持用户头像到屏幕顶端的距离为屏幕高度的35%,那么验证窗口整体时偏下的。 // 计算当验证窗口居中时距离屏幕顶端的高度,与屏幕高度*0.35对比取较小值。 @@ -431,6 +501,15 @@ int MFAWidget::getTopSpacing() const return qMax(15, deltaY); } +/** + * @brief 根据认证序列,提供不同来源的UI + */ +QWidget *MFAWidget::getAuthWidget() +{ + auto wid = MFASequenceControl::instance().currentAuthWidget(); + return wid == nullptr ? this : wid; +} + void MFAWidget::resizeEvent(QResizeEvent *event) { QTimer::singleShot(0, this, &MFAWidget::updateBlurEffectGeometry); diff --git a/src/session-widgets/mfa_widget.h b/src/session-widgets/mfa_widget.h index 91cca7d07..a216231d6 100644 --- a/src/session-widgets/mfa_widget.h +++ b/src/session-widgets/mfa_widget.h @@ -22,6 +22,8 @@ class MFAWidget : public AuthWidget void autoUnlock(); int getTopSpacing() const override; + QWidget *getAuthWidget() override; + protected: void resizeEvent(QResizeEvent *event) override; @@ -34,6 +36,7 @@ class MFAWidget : public AuthWidget void initUKeyAuth(); void initFaceAuth(); void initIrisAuth(); + void initGestureAuth(); void checkAuthResult(const AuthCommon::AuthType type, const AuthCommon::AuthState state) override; diff --git a/src/session-widgets/mfasequencecontrol.cpp b/src/session-widgets/mfasequencecontrol.cpp new file mode 100644 index 000000000..246584309 --- /dev/null +++ b/src/session-widgets/mfasequencecontrol.cpp @@ -0,0 +1,248 @@ +#include "mfasequencecontrol.h" +#include "dconfig_helper.h" +#include "authcommon.h" + +#include + +#include +#include +#include +#include +#include + +const QString kConfigAttrName = "mfaSequence"; +const QString kConfigUserKey = "userType"; +const QString kConfigAuthTypeKey = "authSequence"; +const QString kDefaultUser = "default"; +const QString kADDomainUser = "adDomain"; +const QString kNativeUser = "native"; +const QString kAllUser = "all"; + +// 不处理某个类型的认证过程,只处理UI切换 +MFASequenceControl::MFASequenceControl(QObject *parent) + : QObject(parent) + , m_userType("") + , m_authType(-1) + , m_configUserType("") + , m_currentType(-1) + , m_validConfig(false) +{ + initConfig(); + DConfigHelper::instance()->bind(this, kConfigAttrName, &MFASequenceControl::onPropertyChanged); +} + +MFASequenceControl &MFASequenceControl::instance() +{ + static MFASequenceControl control; + return control; +} + +// 外部设置当前认证类型 +// 工行在此判断是否多因和是否满足配置场景 +void MFASequenceControl::setAuthType(int type) +{ + qCDebug(DDE_SHELL) << "set mfa auth type" << type << this; + + // 先重置到默认界面上 + m_currentType = AuthCommon::AT_None; + Q_EMIT currentUIAuthTypeChanged(m_currentType); + + if (type == AuthCommon::AT_None) { + return; + } + + m_authType = type; + updateAuthSequence(); +} + +// 注意,同类型不代表同用户,所以设置后也需要更新 +void MFASequenceControl::setUserType(User::UserType type) +{ + qCDebug(DDE_SHELL) << "set mfa user type" << type << this; + + static QMap userTypes = { + {User::UserType::Default, kDefaultUser}, + {User::UserType::Native, kNativeUser}, + {User::UserType::ADDomain, kADDomainUser}}; + + m_userType = userTypes.value(type, kDefaultUser); +} + +int MFASequenceControl::currentAuthType() +{ + return m_currentType; +} + +void MFASequenceControl::insertIsolateAuthWidget(int type, QWidget *authWidget) +{ + m_isolateWidgets[type] = authWidget; +} + +void MFASequenceControl::removeAuthWidget(int type) +{ + m_isolateWidgets.remove(type); +} + +// 设计如下:这个类仅被动响应由配置指定的认证序列 +// 比如配置为[1,256], 刚以MFAWidget开始认证,当被通知认证1成功时,推出256认证的UI +// 因此这个函数只会当开始指定认证时返回非空值 +QWidget *MFASequenceControl::currentAuthWidget() +{ + return m_isolateWidgets.value(m_currentType, nullptr); +} + +void MFASequenceControl::doNextAuth(int finishedAuth) +{ + if (!m_configAuthList.size()) { + return; + } + + auto currentIndex = m_configAuthList.indexOf(finishedAuth); + if (currentIndex == -1) { + // 无法处理,认证类型有异常 + return; + } + + if (currentIndex == m_configAuthList.size() - 1) { + qCDebug(DDE_SHELL) << "mfa auth sequence done, continue to other auth"; + + m_currentType = AuthCommon::AT_None; + + // 是否所有认证已结束 + auto allFinishedType = 0; + foreach (auto type, m_configAuthList) { + allFinishedType |= type; + } + + // 存在配置外的认证,切回默认认证UI + if (allFinishedType != m_authType) { + Q_EMIT currentUIAuthTypeChanged(AuthCommon::AT_None); + } + + return; + } + + // 通知UI显示下一个类型的认证界面 + m_currentType = m_configAuthList.value(currentIndex + 1); + qCDebug(DDE_SHELL) << "next auth type" << m_currentType; + + Q_EMIT currentUIAuthTypeChanged(m_currentType); +} + +void MFASequenceControl::initConfig() +{ + m_validConfig = false; + m_configAuthList.clear(); + m_configUserType = ""; + + auto obj = DConfigHelper::instance()->getConfig(kConfigAttrName, "").toJsonObject(); + toLocalConfig(obj); +} + +// json转内部配置 +void MFASequenceControl::toLocalConfig(const QJsonObject &obj) +{ + if (!obj.contains(kConfigUserKey) || !obj.contains(kConfigAuthTypeKey)) { + return; + } + + // 处理用户类型 + auto nameType = obj[kConfigUserKey].toString(); + if (nameType.isEmpty()) { + return; + } + + m_configUserType = nameType; + // 处理认证顺序 + auto authTypeList = obj[kConfigAuthTypeKey].toArray().toVariantList(); + // 多因必然类型比2多 + if (authTypeList.size() < 2) { + return; + } + + foreach (auto type, authTypeList) { + m_configAuthList.push_back(type.toInt()); + } + + // 注意仅在完成配置后才设置true值 + m_validConfig = true; +} + +// 如果配置类型在传入的类型中 +// 先完成配置认证,再执行其它认证 +void MFASequenceControl::updateAuthSequence() +{ + if (!m_validConfig) { + qCDebug(DDE_SHELL) << "config not valid"; + return; + } + + // FIXME 工行上不关注用户类型,DA仅给域用户配置 + if (!m_authType) { + qCDebug(DDE_SHELL) << "not expect value"; + return; + } + + if (m_configUserType != m_userType && m_configUserType != kAllUser) { + return; + } + + auto isolateAuthList = m_isolateWidgets.keys(); + bool isolateActive = false; + foreach (auto isolate, isolateAuthList) { + if (isolate & m_authType) { + if (m_isolateWidgets.value(isolate, nullptr) != nullptr) { + isolateActive = true; + } + } + } + + // 没有参与认证的独立界面,由于认证未提供,将在认证开始后关闭 + if (!isolateActive) { + return; + } + + // 先完成配置的认证顺序 + m_currentType = m_configAuthList.value(0); + + qCDebug(DDE_SHELL) << "start from type" << m_currentType << m_configAuthList; + Q_EMIT currentUIAuthTypeChanged(m_currentType); +} + +void MFASequenceControl::onAuthStatusChanged(int type, int status, const QString &message) +{ + //仅用于参数对齐 + Q_UNUSED(message) + + if (!m_configAuthList.size()) { + return; + } + + switch (status) { + case AuthCommon::AS_Success: + if (type == AuthCommon::AT_All) { + return; + } else { + doNextAuth(type); + } + break; + case AuthCommon::AS_Started: + // 多因认证开启了手势,但是并没有安装插件 + if (m_isolateWidgets.contains(type) && m_isolateWidgets.value(type, nullptr) == nullptr) { + Q_EMIT requestEndUnsupportedAuth(type); + } + } +} + +// 在下一次认证过程中生效 +void MFASequenceControl::onPropertyChanged(const QString &key, const QVariant &value, QObject *objPtr) +{ + auto obj = qobject_cast(objPtr); + if (!obj) + return; + + qCInfo(DDE_SHELL) << "DConfig property changed, key: " << key << ", value: " << value; + if (key == kConfigAttrName) { + obj->initConfig(); + } +} diff --git a/src/session-widgets/mfasequencecontrol.h b/src/session-widgets/mfasequencecontrol.h new file mode 100644 index 000000000..b3c859029 --- /dev/null +++ b/src/session-widgets/mfasequencecontrol.h @@ -0,0 +1,67 @@ +#ifndef MFASEQUENCECONTROL_H +#define MFASEQUENCECONTROL_H + +#include "userinfo.h" + +#include +#include + +class MFASequenceControl : public QObject +{ + Q_OBJECT +public: + static MFASequenceControl &instance(); + void setAuthType(int); + void setUserType(User::UserType); + + // 外部传入widget,如果为空也代表认证无效 + // 内置认证不参与 + void insertIsolateAuthWidget(int, QWidget*); + + int currentAuthType(); + QWidget *currentAuthWidget(); + + void doNextAuth(int); + +public Q_SLOTS: + void initConfig(); + // 响应认证状态变化 + void onAuthStatusChanged(int, int, const QString &); + // 响应配置变化 + static void onPropertyChanged(const QString &key, const QVariant &value, QObject *objPtr); + +Q_SIGNALS: + // 当某个认证成功时,通知UI转到下一个认证 + void currentUIAuthTypeChanged(int); + + // 某个认证的UI不存在,通知结束该认证 + void requestEndUnsupportedAuth(int); + +private: + explicit MFASequenceControl(QObject *parent = nullptr); + + // 从dconf初始化,greeter与lock各持一份,区别配置 + void toLocalConfig(const QJsonObject &); + void updateAuthSequence(); + void removeAuthWidget(int); + +private: + // 外部设置当前用户与开启的认证类型 + QString m_userType; + int m_authType; + + // 实际配置值 + QString m_configUserType; + QList m_configAuthList; + + // 当前处理中的认证 + int m_currentType; + + // 配置可靠性 + bool m_validConfig; + + // 外部已注册的提供UI的认证 + QMap> m_isolateWidgets; +}; + +#endif // MFASEQUENCECONTROL_H diff --git a/src/session-widgets/sessionbasemodel.cpp b/src/session-widgets/sessionbasemodel.cpp index 1dd532120..b7f09194d 100644 --- a/src/session-widgets/sessionbasemodel.cpp +++ b/src/session-widgets/sessionbasemodel.cpp @@ -5,6 +5,7 @@ #include "sessionbasemodel.h" #include "dconfig_helper.h" #include "dbus/dbusdisplaymanager.h" +#include "mfasequencecontrol.h" #include #include @@ -39,7 +40,9 @@ SessionBaseModel::SessionBaseModel(QObject *parent) , m_lightdmPamStarted(false) , m_authResult{AuthType::AT_None, AuthState::AS_None, ""} , m_enableShellBlackMode(DConfigHelper::instance()->getConfig("enableShellBlack", true).toBool()) + , m_enableShutdownBlackWidget(DConfigHelper::instance()->getConfig("enableShutdownBlackWidget", true).toBool()) , m_visibleShutdownWhenRebootOrShutdown(DConfigHelper::instance()->getConfig("visibleShutdownWhenRebootOrShutdown", true).toBool()) + , m_gsCheckpwd(false) { #ifndef ENABLE_DSS_SNIPE if (QGSettings::isSchemaInstalled("com.deepin.dde.power")) { @@ -120,6 +123,9 @@ void SessionBaseModel::setPowerAction(const PowerAction &powerAction) m_powerAction = powerAction; + if (m_enableShutdownBlackWidget && !gsCheckpwd() && (powerAction == SessionBaseModel::PowerAction::RequireRestart || powerAction == SessionBaseModel::PowerAction::RequireShutdown)) + Q_EMIT shutdownkModeChanged(true); + emit onPowerActionChanged(powerAction); } @@ -240,6 +246,14 @@ void SessionBaseModel::setIsBlackMode(bool is_black) emit blackModeChanged(is_black); } +void SessionBaseModel::setShutdownMode(bool is_black) +{ + if (!m_enableShutdownBlackWidget || gsCheckpwd()) { + return; + } + Q_EMIT shutdownkModeChanged(is_black); +} + void SessionBaseModel::setIsHibernateModel(bool is_Hibernate) { if (m_isHibernateMode == is_Hibernate) @@ -272,6 +286,7 @@ void SessionBaseModel::setAuthType(const AuthFlags type) if (type == m_authProperty.AuthType && type != AT_None) { return; } + if (m_currentUser->type() == User::Default) { m_authProperty.AuthType = type; emit authTypeChanged(AT_None); @@ -660,6 +675,7 @@ void SessionBaseModel::updateAuthState(const AuthType type, const AuthState stat m_authResult.authState = state; m_authResult.authType = type; m_authResult.authMessage = message; + switch (m_authProperty.FrameworkState) { case Available: emit authStateChanged(type, state, message); @@ -710,4 +726,23 @@ void SessionBaseModel::setUserlistVisible(bool visible) void SessionBaseModel::setQuickLoginProcess(bool val) { m_isQuickLoginProcess = val; +} + +void SessionBaseModel::setGsCheckpwd(bool value) +{ + if (m_gsCheckpwd != value) { + m_gsCheckpwd = value; + } +} + +bool SessionBaseModel::isNoPasswordLogin() const +{ + // 检查当前用户是否存在 + if (!m_currentUser) { + qCWarning(DDE_SHELL) << "Current user is null"; + return false; + } + + // 直接调用当前用户的isNoPasswordLogin方法 + return m_currentUser->isNoPasswordLogin(); } \ No newline at end of file diff --git a/src/session-widgets/sessionbasemodel.h b/src/session-widgets/sessionbasemodel.h index 31d26e481..784c7c611 100644 --- a/src/session-widgets/sessionbasemodel.h +++ b/src/session-widgets/sessionbasemodel.h @@ -152,6 +152,8 @@ class SessionBaseModel : public QObject inline bool isBlackMode() const { return m_isBlackMode; } void setIsBlackMode(bool is_black); + void setShutdownMode(bool is_black); + inline bool isHibernateMode() const { return m_isHibernateMode; } void setIsHibernateModel(bool is_Hibernate); @@ -187,6 +189,12 @@ class SessionBaseModel : public QObject inline bool isQuickLoginProcess() const { return m_isQuickLoginProcess; } void setQuickLoginProcess(bool ); + inline bool gsCheckpwd() const { return m_gsCheckpwd; } + void setGsCheckpwd(bool value); + + // 检查当前用户是否设置了免密登录 + bool isNoPasswordLogin() const; + signals: /* com.deepin.daemon.Accounts */ void currentUserChanged(const std::shared_ptr); @@ -252,6 +260,7 @@ public slots: void userListLoginedChanged(QList> list); void activeAuthChanged(bool active); void blackModeChanged(bool is_black); + void shutdownkModeChanged(bool is_black); void HibernateModeChanged(bool is_hibernate); //休眠信号改变 void prepareForSleep(bool is_Sleep); //待机信号改变 void shutdownInhibit(const SessionBaseModel::PowerAction action, bool needConfirm); @@ -311,8 +320,10 @@ public slots: bool m_lightdmPamStarted; // 标志lightdmpam是否已经开启,主要用于greeter,lock不涉及lightdm AuthResult m_authResult; // 记录认证结果 bool m_enableShellBlackMode; + bool m_enableShutdownBlackWidget; bool m_visibleShutdownWhenRebootOrShutdown; bool m_isQuickLoginProcess=false;//标志当前界面展示是否为快速登录流程 + bool m_gsCheckpwd; }; #endif // SESSIONBASEMODEL_H diff --git a/src/session-widgets/sfa_widget.cpp b/src/session-widgets/sfa_widget.cpp index e2f8fdef8..067d1391b 100644 --- a/src/session-widgets/sfa_widget.cpp +++ b/src/session-widgets/sfa_widget.cpp @@ -19,6 +19,7 @@ #include "useravatar.h" #include "plugin_manager.h" #include "login_plugin_util.h" +#include "signal_bridge.h" #include @@ -170,7 +171,7 @@ void SFAWidget::setAuthType(const AuthFlags type) chooseAuthType(authType); // 如果是无密码认证,焦点默认在解锁按钮上面 - if (m_model->currentUser()->isNoPasswordLogin()) { + if (m_model->currentUser()->isNoPasswordLogin() && !m_model->terminalLocked()) { m_lockButton->setEnabled(true); setFocusProxy(m_lockButton); setFocus(); @@ -192,8 +193,17 @@ void SFAWidget::initCustomFactors(const AuthFlags type) auto plugins = filtrateAuthPlugins(PluginManager::instance()->getLoginPlugins()); qCInfo(DDE_SHELL) << "Login plugin size:" << plugins.size(); if (!m_model->terminalLocked() && !plugins.isEmpty() && (type & AT_Custom)) { - for (const auto plugin : plugins) - initCustomAuth(plugin); + for (const auto plugin : plugins) { + if (plugin) { + plugin->updateConfig(); + // 手势认证当前还未适配单因认证 + if (plugin->defaultAuthType() & AT_Pattern) { + continue; + } + + initCustomAuth(plugin); + } + } // 析构不使用的插件 for (const auto &auth : m_customAuths.values()) { @@ -463,10 +473,19 @@ void SFAWidget::initPasswdAuth() if (text.isEmpty()) { return; } + if (m_passwordAuth->assistLoginWidget()) { + if (m_user) { + Q_EMIT SignalBridge::ref().requestSendExtraInfo(m_user->name(), AT_Password, m_passwordAuth->assistLoginWidget()->extraInfo()); + } else { + qCWarning(DDE_SHELL) << "Current user is null, can not send extra info"; + } + } m_passwordAuth->setAuthStateStyle(LOGIN_SPINNER); m_passwordAuth->setAnimationState(true); m_passwordAuth->setLineEditEnabled(false); m_lockButton->setEnabled(false); + // OPTIMIZING: DA没有发AS_Verify,这里自己发一个,后续DA优化 + m_passwordAuth->setAuthState(AuthCommon::AS_Verify, QStringLiteral("Verifying...")); if (m_model->currentUser()->isLdapUser() && LPUtil::updateLoginType() == LPUtil::Type::CLT_MFA) @@ -1256,28 +1275,42 @@ void SFAWidget::initAccount() }); } -void SFAWidget::onRequestChangeAuth(const AuthType authType) +void SFAWidget::onRequestChangeAuth(AuthType authType) { qCInfo(DDE_SHELL) << "Request change auth, auth type: " << authType << ", choose auth button box is enabled: " << m_chooseAuthButtonBox->isEnabled() << ", current authentication type: " << m_currentAuthType; + + int authTypeTmp = authType; const auto customAuth = qobject_cast(sender()); if (customAuth && customAuth->customAuthType() != m_currentAuthType) { - qCInfo(DDE_SHELL) << "Custom auth dismath with current auth type"; - return; + // 输出一下日志表明是哪个插件请求切换的,目前遇到的场景:锁屏时开启了密码认证,云桌面需要切换单点登录插件认证,自动解锁。 + qCInfo(DDE_SHELL) << "Custom auth mismatch with current auth type, custom auth type: " << customAuth->customAuthType(); } - if (authType == m_currentAuthType) { + if (authTypeTmp == m_currentAuthType) { qCInfo(DDE_SHELL) << "Current auth type is same with request auth type"; return; } - if (authType != AuthCommon::AT_Password && !m_chooseAuthButtonBox->isEnabled()) { - qCWarning(DDE_SHELL) << "Authentication button box is disabled and authentication type is not password."; - return; + if (authTypeTmp != AuthCommon::AT_Password && !m_chooseAuthButtonBox->isEnabled()) { + qCInfo(DDE_SHELL) << "Authentication button box is disabled and authentication type is not password, automatically switch to password authentication"; + authTypeTmp = AT_Password; + } + + // 如果请求切换到AT_All 表明请求方无所谓切换到某种特定的类型,交由登录器来处理。 + // 登录器会切换到上次认证成功的类型。 + if (authTypeTmp == AuthCommon::AT_All) { + qCInfo(DDE_SHELL) << "Request authentication type is AT_All, let the session-shell to handle it"; + if (m_user) { + authTypeTmp = m_user->lastAuthType(); + } else { + qCWarning(DDE_SHELL) << "No user, use first auth type"; + authTypeTmp = m_authButtons.firstKey(); + } } - if (!m_authButtons.contains(authType)) { + if (!m_authButtons.contains(authTypeTmp)) { qCWarning(DDE_SHELL) << "Authentication buttons do not contain the type"; // 登录选项默认显示上一次认证方式,当不存在上一次认证,默认显示第一种认证方式 @@ -1285,7 +1318,7 @@ void SFAWidget::onRequestChangeAuth(const AuthType authType) const auto lastAuthType = m_user->lastAuthType(); qCInfo(DDE_SHELL) << "Last authtication type:" << lastAuthType; if (lastAuthType == AuthCommon::AT_Custom) { - int authType = m_authButtons.firstKey(); + authTypeTmp = m_authButtons.firstKey(); // 获取上次自定义认证的类型 const auto &lastCustomAuth = m_user->lastCustomAuth(); qCInfo(DDE_SHELL) << "Last custom auth:" << lastCustomAuth; @@ -1294,10 +1327,10 @@ void SFAWidget::onRequestChangeAuth(const AuthType authType) if (it != m_customAuths.end() && it.value() != nullptr) { // 排除重复使用了当前自定义认证类型的情况 if (!customAuth || customAuth->customAuthType() != it.value()->customAuthType()) - authType = it.value()->customAuthType(); + authTypeTmp = it.value()->customAuthType(); } } - button = m_chooseAuthButtonBox->button(authType); + button = m_chooseAuthButtonBox->button(authTypeTmp); } else if (m_authButtons.contains(lastAuthType)) { button = m_chooseAuthButtonBox->button(lastAuthType); } else { @@ -1313,7 +1346,7 @@ void SFAWidget::onRequestChangeAuth(const AuthType authType) return ; } - QAbstractButton *btn = m_chooseAuthButtonBox->button(authType); + QAbstractButton *btn = m_chooseAuthButtonBox->button(authTypeTmp); if (!btn) { qCWarning(DDE_SHELL) << "The button of authentication is null"; return; diff --git a/src/session-widgets/sfa_widget.h b/src/session-widgets/sfa_widget.h index 46cee6441..1de2d6e6d 100644 --- a/src/session-widgets/sfa_widget.h +++ b/src/session-widgets/sfa_widget.h @@ -43,7 +43,7 @@ class SFAWidget : public AuthWidget public slots: void onRetryButtonVisibleChanged(bool visible); - void onRequestChangeAuth(const AuthCommon::AuthType authType); + void onRequestChangeAuth(AuthCommon::AuthType authType); void onLightdmPamStartChanged(); protected: diff --git a/src/session-widgets/userpanel.cpp b/src/session-widgets/userpanel.cpp index 121b025ac..a50b64268 100644 --- a/src/session-widgets/userpanel.cpp +++ b/src/session-widgets/userpanel.cpp @@ -9,16 +9,14 @@ #include -DWIDGET_USE_NAMESPACE - UserPanel::UserPanel(QWidget *parent) : ActionWidget(parent) , m_isSelected(false) , m_uid(UINT_MAX) , m_mainLayout(new QHBoxLayout(this)) , m_avatar(new UserAvatar(this)) - , m_displayNameLabel(new QLabel(this)) - , m_typeLabel(new QLabel(this)) + , m_displayNameLabel(new DLabel(this)) + , m_typeLabel(new DLabel(this)) { setObjectName(QStringLiteral("UserPanel")); setAccessibleName(QStringLiteral("UserPanel")); @@ -33,8 +31,12 @@ void UserPanel::initUI() setRadius(18); m_displayNameLabel->setAlignment(Qt::AlignLeft); DFontSizeManager::instance()->bind(m_displayNameLabel, DFontSizeManager::T5); + m_displayNameLabel->setElideMode(Qt::ElideRight); + m_typeLabel->setAlignment(Qt::AlignLeft); DFontSizeManager::instance()->bind(m_typeLabel, DFontSizeManager::T6); + m_typeLabel->setElideMode(Qt::ElideRight); + m_avatar->setAvatarSize(UserAvatarSize); m_mainLayout->setSpacing(12); m_mainLayout->setContentsMargins(10, 10, 10, 10); @@ -103,4 +105,4 @@ void UserPanel::setType(const QString &type) { m_typeLabel->setText(type); update(); -} \ No newline at end of file +} diff --git a/src/session-widgets/userpanel.h b/src/session-widgets/userpanel.h index 06db0689c..964a5d168 100644 --- a/src/session-widgets/userpanel.h +++ b/src/session-widgets/userpanel.h @@ -7,6 +7,8 @@ #include "actionwidget.h" +#include + #include const int UserPanelWidth = 330; @@ -16,6 +18,7 @@ const int UserAvatarSize = 56; class QHBoxLayout; class UserAvatar; +DWIDGET_USE_NAMESPACE class UserPanel : public ActionWidget { Q_OBJECT @@ -46,8 +49,8 @@ class UserPanel : public ActionWidget QString m_fullName; QString m_name; QString m_type; - QLabel *m_displayNameLabel; - QLabel *m_typeLabel; + DLabel *m_displayNameLabel; + DLabel *m_typeLabel; }; #endif // USERPANEL_H diff --git a/src/session-widgets/userswiththesamename.cpp b/src/session-widgets/userswiththesamename.cpp index 403cf846b..81e2620c2 100644 --- a/src/session-widgets/userswiththesamename.cpp +++ b/src/session-widgets/userswiththesamename.cpp @@ -34,9 +34,9 @@ UsersWithTheSameName::UsersWithTheSameName(SessionBaseModel *model, QWidget *par void UsersWithTheSameName::initUI() { m_backButton->setText(tr("Return")); - const QPixmap &normalBackPixmap = DHiDPIHelper::loadNxPixmap(":/img/back-normal.svg").scaled(m_backButton->size(), Qt::KeepAspectRatio, Qt::SmoothTransformation); + const QPixmap &normalBackPixmap = DHiDPIHelper::loadNxPixmap(":/img/back-normal.svg"); m_backButton->setNormalPixmap(normalBackPixmap); - const QPixmap &hoverBackPixmap = DHiDPIHelper::loadNxPixmap(":/img/back-hover.svg").scaled(m_backButton->size(), Qt::KeepAspectRatio, Qt::SmoothTransformation); + const QPixmap &hoverBackPixmap = DHiDPIHelper::loadNxPixmap(":/img/back-hover.svg"); m_backButton->setHoverPixmap(hoverBackPixmap); m_backButton->setFixedSize(200, 64); DFontSizeManager::instance()->bind(m_backButton, DFontSizeManager::T4); @@ -114,7 +114,13 @@ bool UsersWithTheSameName::findUsers(const QString nativeUserName, const QString return false; } const QJsonObject &jsonObject = jsonDoc.object(); - const QString &fullName = jsonObject.value("gecos").toString(); + QString fullName; + // 检查fullname字段是否存在,如果存在则使用,否则使用name字段的值 + if (jsonObject.contains("fullname")) { + fullName = jsonObject.value("fullname").toString(); + } else { + fullName = jsonObject.value("name").toString(); + } const QString &name = jsonObject.value("name").toString(); const uint uid = jsonObject.value("uid").toInt(); diff --git a/src/widgets/dlineeditex.cpp b/src/widgets/dlineeditex.cpp index 58c83ec72..ec0112651 100644 --- a/src/widgets/dlineeditex.cpp +++ b/src/widgets/dlineeditex.cpp @@ -10,6 +10,7 @@ #include #include #include +#include LoadSlider::LoadSlider(QWidget *parent) : QWidget(parent) @@ -37,6 +38,7 @@ DLineEditEx::DLineEditEx(QWidget *parent) : DLineEdit(parent) , m_loadSlider(new LoadSlider(this)) , m_animation(new QPropertyAnimation(m_loadSlider, "pos", m_loadSlider)) + , m_enableTableKeyEvent(false) { setObjectName(QStringLiteral("DLineEditEx")); setAccessibleName(QStringLiteral("DLineEditEx")); @@ -118,6 +120,14 @@ void DLineEditEx::stopAnimation() m_animation->stop(); } +void DLineEditEx::setEnableTableKeyEvent(bool enable) +{ + if (m_enableTableKeyEvent == enable) + return; + + m_enableTableKeyEvent = enable; +} + /** * @brief 重写 QLineEdit paintEvent 函数,实现当文本设置居中后,holderText 仍然显示的需求 * @@ -147,6 +157,16 @@ bool DLineEditEx::eventFilter(QObject *watched, QEvent *event) if ((watched == this || watched == this->lineEdit()) && event->type() == QEvent::InputMethodQuery) { return true; + } else if ((watched == this || watched == this->lineEdit()) + && event->type() == QEvent::KeyPress + && m_enableTableKeyEvent) { + QKeyEvent *keyEvent = static_cast(event); + if (keyEvent->key() == Qt::Key_Tab + && keyEvent->modifiers() == Qt::NoModifier + && !this->text().isEmpty()) { + emit this->returnPressed(); + return true; + } } return DLineEdit::eventFilter(watched, event); diff --git a/src/widgets/dlineeditex.h b/src/widgets/dlineeditex.h index 86ee29562..26c97c754 100644 --- a/src/widgets/dlineeditex.h +++ b/src/widgets/dlineeditex.h @@ -36,6 +36,7 @@ class DLineEditEx : public DLineEdit public slots: void startAnimation(); void stopAnimation(); + void setEnableTableKeyEvent(bool enable); protected: void paintEvent(QPaintEvent *event) override; @@ -48,6 +49,7 @@ public slots: private: LoadSlider *m_loadSlider; QPropertyAnimation *m_animation; + bool m_enableTableKeyEvent; }; #endif // DLINEEDITEX_H diff --git a/src/widgets/fullscreenbackground.cpp b/src/widgets/fullscreenbackground.cpp index fe0ac3e70..0aafcf6fa 100644 --- a/src/widgets/fullscreenbackground.cpp +++ b/src/widgets/fullscreenbackground.cpp @@ -11,6 +11,7 @@ #include "dconfig_helper.h" #include +#include #include #include @@ -43,6 +44,7 @@ FullScreenBackground::FullScreenBackground(SessionBaseModel *model, QWidget *par , m_useSolidBackground(false) , m_blackWidget(new BlackWidget(this)) , m_resetGeometryTimer(new QTimer(this)) + , m_shutdownBlackWidget(nullptr) { #ifndef QT_DEBUG if (!m_model->isUseWayland()) { @@ -53,6 +55,8 @@ FullScreenBackground::FullScreenBackground(SessionBaseModel *model, QWidget *par setAttribute(Qt::WA_NativeWindow); // 创建窗口 handle // onScreenDisplay 低于override,高于tooltip,希望显示在锁屏上方的界面,均需要调整层级为onScreenDisplay或者override windowHandle()->setProperty("_d_dwayland_window-type", "onScreenDisplay"); + DPlatformWindowHandle handle(this, this); + handle.setWindowRadius(-1); } #endif frameList.append(this); @@ -68,8 +72,20 @@ FullScreenBackground::FullScreenBackground(SessionBaseModel *model, QWidget *par } }); connect(m_resetGeometryTimer, &QTimer::timeout, this, [this] { - qCDebug(DDE_SHELL) << " setGeometry : " << m_geometryRect; - setGeometry(m_geometryRect); + const auto ¤tGeometry = geometry(); + if (currentGeometry != m_geometryRect) { + qCDebug(DDE_SHELL) << "Current geometry:" << currentGeometry <<"setGeometry:" << m_geometryRect; + setGeometry(m_geometryRect); + } + }); + + connect(m_model, &SessionBaseModel::shutdownkModeChanged, this, [this] (bool value){ + if (!m_shutdownBlackWidget) { + m_shutdownBlackWidget = new ShutdownBlackWidget(this); + } + qCInfo(DDE_SHELL) << "FullScreenBackground size : " << size(); + m_shutdownBlackWidget->setFixedSize(this->size()); + m_shutdownBlackWidget->setBlackMode(value); }); } @@ -154,7 +170,8 @@ void FullScreenBackground::setScreen(QPointer screen, bool isVisible) qCInfo(DDE_SHELL) << "Set screen:" << screen << ", screen geometry:" << screen->geometry() - << ", full screen background object:" << this; + << ", full screen background object:" << this + << ", visible:" << isVisible; if (isVisible) { updateCurrentFrame(this); } else { @@ -274,6 +291,7 @@ void FullScreenBackground::enterEvent(QEnterEvent *event) void FullScreenBackground::enterEvent(QEvent *event) #endif { + qCInfo(DDE_SS) << "Enter event, enable enter event:" << m_enableEnterEvent << ", visible:" << m_model->visible(); if (m_enableEnterEvent && m_model->visible()) { updateCurrentFrame(this); // 多屏情况下,此Frame晚于其它Frame显示出来时,可能处于未激活状态(特别是在wayland环境下比较明显) @@ -435,6 +453,8 @@ void FullScreenBackground::updateGeometry() } if (!m_screen.isNull()) { + if(m_model->isUseWayland()) + windowHandle()->setScreen(m_screen); setddeGeometry(m_screen->geometry()); qCInfo(DDE_SHELL) << "Update geometry, screen:" << m_screen @@ -634,6 +654,8 @@ void FullScreenBackground::updateCurrentFrame(FullScreenBackground *frame) if (frame->m_screen) qCInfo(DDE_SHELL) << "Update current frame:" << frame << ", screen:" << frame->m_screen->name(); + else + qWarning() << "Frame's screen is null, frame:" << frame; currentFrame = frame; setContent(currentContent); @@ -716,5 +738,5 @@ void FullScreenBackground::setddeGeometry(const QRect &rect) setGeometry(rect); m_geometryRect = rect; m_resetGeometryTimer->start(200); - QTimer::singleShot(200 * 5, m_resetGeometryTimer, &QTimer::stop); + QTimer::singleShot(400 * 5, m_resetGeometryTimer, &QTimer::stop); } diff --git a/src/widgets/fullscreenbackground.h b/src/widgets/fullscreenbackground.h index 8e1f4d1ed..8b89989ee 100644 --- a/src/widgets/fullscreenbackground.h +++ b/src/widgets/fullscreenbackground.h @@ -13,6 +13,7 @@ #include #include "abstractfullbackgroundinterface.h" +#include "shutdown_black_widget.h" Q_DECLARE_LOGGING_CATEGORY(DDE_SS) @@ -96,6 +97,7 @@ public slots: BlackWidget *m_blackWidget; QTimer *m_resetGeometryTimer; QRect m_geometryRect; + ShutdownBlackWidget *m_shutdownBlackWidget; }; #endif // FULLSCREENBACKGROUND_H diff --git a/src/widgets/passworderrortipswidget.cpp b/src/widgets/passworderrortipswidget.cpp index ce1e35158..4469bd1de 100644 --- a/src/widgets/passworderrortipswidget.cpp +++ b/src/widgets/passworderrortipswidget.cpp @@ -39,10 +39,12 @@ PasswordErrorTipsWidget::PasswordErrorTipsWidget(QWidget *parent) void PasswordErrorTipsWidget::initUi() { QVBoxLayout* mainLay = new QVBoxLayout(this); + mainLay->setContentsMargins(10, 5, 10, 5); mainLay->setSpacing(0); QHBoxLayout* hlay = new QHBoxLayout(m_tipsWidget); - hlay->setSpacing(0); + hlay->setContentsMargins(0, 0, 0, 0); + hlay->setSpacing(5); hlay->addWidget(m_tipLabel, 0, Qt::AlignmentFlag::AlignLeft | Qt::AlignmentFlag::AlignTop); DFontSizeManager::instance()->bind(m_tipLabel, DFontSizeManager::T6); m_tipLabel->setForegroundRole(DPalette::TextWarning); @@ -50,9 +52,8 @@ void PasswordErrorTipsWidget::initUi() QPixmap pixmap; pixmap.load(":img/warning.svg"); m_showMore->setPixmap(pixmap); - m_showMore->hide(); + m_showMore->setVisible(false); - hlay->addSpacing(5); hlay->addWidget(m_showMore, 0, Qt::AlignmentFlag::AlignCenter); mainLay->addWidget(m_tipsWidget, 0, Qt::AlignmentFlag::AlignTop | Qt::AlignmentFlag::AlignLeft); @@ -152,4 +153,4 @@ void PasswordErrorTipsWidget::reset() bool PasswordErrorTipsWidget::hasErrorTips() { return !m_tipLabel->text().isEmpty(); -} \ No newline at end of file +} diff --git a/src/widgets/shutdown_black_widget.cpp b/src/widgets/shutdown_black_widget.cpp new file mode 100644 index 000000000..b2d08d771 --- /dev/null +++ b/src/widgets/shutdown_black_widget.cpp @@ -0,0 +1,123 @@ +// SPDX-FileCopyrightText: 2020 - 2022 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "shutdown_black_widget.h" +#include + +#include +#include +#include +#include +#include +#include + +bool onPreparingForShutdown() { + QDBusInterface iface( + "org.freedesktop.login1", + "/org/freedesktop/login1", + "org.freedesktop.login1.Manager", + QDBusConnection::systemBus() + ); + QVariant preparingForShutdown = iface.property("PreparingForShutdown"); + + if (preparingForShutdown.isValid()) { + bool isPreparing = preparingForShutdown.toBool(); + qDebug() << "Preparing for shutdown property value:" << isPreparing; + return isPreparing; + } else { + qWarning() << "Failed to retrieve preparing for shutdown property, the property is invalid"; + } + return false; +} + +void handleSIGTERM(int signal) { + qInfo() << "handleSIGTERM: " << signal; + + bool bShutdown = onPreparingForShutdown(); + qInfo() << "Whether preparing for shutdown: " << bShutdown; + if (bShutdown) { + QTimer::singleShot(2500, qApp, SLOT(quit())); + } else { + QTimer time; + time.start(1000); + QObject::connect(&time, &QTimer::timeout, [&] { + bool bShutdown = onPreparingForShutdown(); + qInfo() << "Whether preparing for shutdown: " << bShutdown; + if (bShutdown) { + time.stop(); + QTimer::singleShot(2000, qApp, SLOT(quit())); + return; + } else { + qInfo() << " Get org.freedesktop.login1.Manager PreparingForShutdown again."; + } + }); + } +} + +ShutdownBlackWidget::ShutdownBlackWidget(QWidget *parent) + : QWidget(parent) + , m_cursor(cursor()) +{ + signal(SIGTERM, handleSIGTERM); + setObjectName(QStringLiteral("ShutdownBlackWidget")); + setAccessibleName(QStringLiteral("ShutdownBlackWidget")); + setVisible(false); + setWindowFlags(Qt::WindowStaysOnTopHint | Qt::X11BypassWindowManagerHint); + + QCursor cursor(Qt::BlankCursor); + this->setCursor(cursor); + + installEventFilter(this); +} + +void ShutdownBlackWidget::setBlackMode(const bool isBlackMode) +{ + qInfo() << "ShutdownBlackWidget setBlackMode : " << isBlackMode; + setVisible(isBlackMode); + if (isBlackMode) { + show(); + } +#ifndef QT_DEBUG + setCursor(isBlackMode ? Qt::BlankCursor : m_cursor); +#endif +} + +void ShutdownBlackWidget::setShutdownMode(const bool isBlackMode) +{ + setVisible(isBlackMode); +} + +void ShutdownBlackWidget::paintEvent(QPaintEvent *event) +{ + QPainter painter(this); + QRect rect(QPoint(0, 0), QSize(size() * devicePixelRatioF())); + painter.fillRect(rect, Qt::black); + + QWidget::paintEvent(event); +} + +void ShutdownBlackWidget::showEvent(QShowEvent *event) +{ + raise(); + grabMouse(); + grabKeyboard(); + QWidget::showEvent(event); +} + +bool ShutdownBlackWidget::eventFilter(QObject *watched, QEvent *event) +{ + if (event->type() == QEvent::MouseButtonPress || + event->type() == QEvent::MouseButtonRelease || + event->type() == QEvent::MouseButtonDblClick || + event->type() == QEvent::MouseMove || + event->type() == QEvent::KeyPress || + event->type() == QEvent::KeyRelease) { + qDebug() << "### All Mouse and Keyboard Event blocked!"; + + // 返回 true 表示事件已被处理,不再继续传递 + return true; + } + + return QWidget::eventFilter(watched, event); +} diff --git a/src/widgets/shutdown_black_widget.h b/src/widgets/shutdown_black_widget.h new file mode 100644 index 000000000..a200f3865 --- /dev/null +++ b/src/widgets/shutdown_black_widget.h @@ -0,0 +1,27 @@ +#pragma once + +#include + +class ShutdownBlackWidget : public QWidget +{ + Q_OBJECT +public: + explicit ShutdownBlackWidget(QWidget *parent = nullptr); + + void setBlackMode(const bool isBlackMode); + +public Q_SLOTS: + void setShutdownMode(const bool isBlackMode = false); + +protected: + void paintEvent(QPaintEvent *event) override; + void showEvent(QShowEvent *event) override; + bool eventFilter(QObject *watched, QEvent *event) override; + +private: + QCursor m_cursor; +}; + +// SPDX-FileCopyrightText: 2020 - 2022 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later diff --git a/src/widgets/shutdownwidget.cpp b/src/widgets/shutdownwidget.cpp index e25b41fd1..873a3afe6 100644 --- a/src/widgets/shutdownwidget.cpp +++ b/src/widgets/shutdownwidget.cpp @@ -615,7 +615,8 @@ void ShutdownWidget::recoveryLayout() void ShutdownWidget::onRequirePowerAction(SessionBaseModel::PowerAction powerAction, bool needConfirm) { - qCInfo(DDE_SHELL) << "Require power action: " << powerAction << ", need confirm: " << needConfirm; + qInfo() << "Require power action: " << powerAction << ", need confirm: " << needConfirm << ", m_model->appType(): " << m_model->appType(); + //锁屏或关机模式时,需要确认是否关机或检查是否有阻止关机 if (m_model->appType() == Lock) { switch (powerAction) { diff --git a/tests/dde-lock/CMakeLists.txt b/tests/dde-lock/CMakeLists.txt index b19f31c57..83dc41732 100644 --- a/tests/dde-lock/CMakeLists.txt +++ b/tests/dde-lock/CMakeLists.txt @@ -21,6 +21,7 @@ set(LOCK_TEST_SRCS ${PROJECT_SOURCE_DIR}/src/dde-lock/dbus/dbuslockfrontservice.cpp ${PROJECT_SOURCE_DIR}/src/dde-lock/dbus/dbusshutdownagent.cpp ${PROJECT_SOURCE_DIR}/src/dde-lock/dbus/dbusshutdownfrontservice.cpp + ${PROJECT_SOURCE_DIR}/src/global_util/signal_bridge.h ) add_executable(${BIN_NAME} diff --git a/tests/lightdm-deepin-greeter/CMakeLists.txt b/tests/lightdm-deepin-greeter/CMakeLists.txt index e4717cef5..e5c2dd3b8 100644 --- a/tests/lightdm-deepin-greeter/CMakeLists.txt +++ b/tests/lightdm-deepin-greeter/CMakeLists.txt @@ -20,6 +20,7 @@ set(GREETER_TEST_SRCS ${PROJECT_SOURCE_DIR}/src/lightdm-deepin-greeter/logincontent.cpp ${PROJECT_SOURCE_DIR}/src/lightdm-deepin-greeter/logintipswindow.cpp ${PROJECT_SOURCE_DIR}/src/lightdm-deepin-greeter/sessionwidget.cpp + ${PROJECT_SOURCE_DIR}/src/global_util/signal_bridge.h ) add_executable(${BIN_NAME}