Skip to content

Commit 2c19a9b

Browse files
committed
feat: enhance login reminder with configurable settings
1. Added DConfig support for LoginReminder configuration with default value false 2. Implemented password expiration check as fallback when LoginReminder is disabled 3. Replaced systemd service with desktop autostart file for better session integration 4. Added translation support with multiple language files (en_US, zh_CN, zh_HK, zh_TW) 5. Added D-Bus service watcher to wait for notification service availability 6. Improved notification content with detailed login information and password warnings 7. Added action button to directly open password change page in control center Log: Added configurable login reminder feature with password expiration warnings Influence: 1. Test login reminder functionality with DConfig enabled and disabled states 2. Verify password expiration notifications work independently 3. Check translation loading for different locales 4. Test notification action button opens control center password page 5. Verify autostart behavior after system login 6. Test D-Bus service dependency handling feat: 增强登录提醒功能并支持配置设置 1. 添加 DConfig 支持登录提醒配置,默认值为关闭 2. 实现密码过期检查作为登录提醒禁用时的备用功能 3. 将 systemd 服务替换为桌面自启动文件以实现更好的会话集成 4. 添加多语言翻译支持(英文、简体中文、繁体中文香港、繁体中文台湾) 5. 添加 D-Bus 服务监视器以等待通知服务可用 6. 改进通知内容,包含详细的登录信息和密码警告 7. 添加操作按钮可直接打开控制中心的密码修改页面 Log: 新增可配置的登录提醒功能,包含密码过期警告 Influence: 1. 测试 DConfig 启用和禁用状态下的登录提醒功能 2. 验证密码过期通知是否独立工作 3. 检查不同语言环境的翻译加载 4. 测试通知操作按钮是否打开控制中心密码页面 5. 验证系统登录后的自启动行为 6. 测试 D-Bus 服务依赖处理
1 parent 011e812 commit 2c19a9b

13 files changed

Lines changed: 560 additions & 31 deletions

.tx/transifex.yaml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
filters:
2+
- filter_type: file
3+
source_file: tools/dde-login-reminder/translations/dde-login-reminder.ts
4+
file_format: QT
5+
source_language: en_US
6+
translation_files_expression: tools/dde-login-reminder/translations/dde-login-reminder_<lang>.ts
7+
settings:
8+
pr_branch_name: transifex_update_<br_unique_id>

CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ include(GNUInstallDirs)
1414
set(QT_VERSION_MAJOR 6)
1515
set(DTK_VERSION_MAJOR 6)
1616

17+
find_package(DtkBuildHelper REQUIRED)
18+
1719
macro(install_symlink filepath wantsdir)
1820
file(MAKE_DIRECTORY ${PROJECT_BINARY_DIR}/link/${wantsdir}/)
1921
execute_process(COMMAND ${CMAKE_COMMAND} -E create_symlink ${CMAKE_INSTALL_PREFIX}/lib/systemd/user/${filepath} ${PROJECT_BINARY_DIR}/link/${wantsdir}/${filepath})

lupdate.sh

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
#!/bin/bash
2+
3+
# 更新 dde-login-reminder 翻译文件
4+
lupdate tools/dde-login-reminder -ts -no-obsolete translations/dde-login-reminder.ts
5+
6+
# tx push -s -f --branch m20

misc/CMakeLists.txt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,9 @@ install(
2121

2222
install(FILES ${XSESSION} DESTINATION /etc/X11/Xsession.d/)
2323
install(FILES ${PROFILE} DESTINATION /etc/profile.d)
24+
25+
# Install dconf configuration files
26+
dtk_add_config_meta_files(
27+
APPID org.deepin.dde.session
28+
FILES ${CMAKE_CURRENT_SOURCE_DIR}/dconf/org.deepin.dde.session.json
29+
)
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{
2+
"magic": "dsg.config.meta",
3+
"version": "1.0",
4+
"contents": {
5+
"LoginReminder": {
6+
"value": false,
7+
"serial": 0,
8+
"flags": [],
9+
"name": "login reminder",
10+
"name[zh_CN]": "登录提醒",
11+
"description": "Display login reminder notification including login information, password expiration warning and failed login attempts",
12+
"description[zh_CN]": "显示登录提醒通知,包括登录信息、密码过期警告和登录失败次数",
13+
"permissions": "readwrite",
14+
"visibility": "public"
15+
}
16+
}
17+
}

tools/dde-login-reminder/CMakeLists.txt

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ set(CMAKE_AUTOMOC ON)
55
set(CMAKE_AUTORCC ON)
66

77
find_package(Qt${QT_VERSION_MAJOR} COMPONENTS Core Network DBus REQUIRED)
8+
find_package(Dtk${DTK_VERSION_MAJOR} COMPONENTS Core REQUIRED)
89

910
find_package(PkgConfig REQUIRED)
1011

@@ -21,17 +22,27 @@ target_link_libraries(${BIN_NAME}
2122
Qt${QT_VERSION_MAJOR}::Core
2223
Qt${QT_VERSION_MAJOR}::DBus
2324
Qt${QT_VERSION_MAJOR}::Network
25+
Dtk${DTK_VERSION_MAJOR}::Core
2426
)
2527

2628
target_include_directories(${BIN_NAME} PUBLIC
2729
${PROJECT_SOURCE_DIR}/src/utils
2830
)
2931

32+
target_compile_definitions(${BIN_NAME} PRIVATE
33+
CMAKE_INSTALL_FULL_DATADIR="${CMAKE_INSTALL_FULL_DATADIR}"
34+
)
35+
3036
install(TARGETS ${BIN_NAME} DESTINATION bin)
3137

32-
set(SYSTEMD_FILES
33-
dde-login-reminder.service
34-
)
35-
install(FILES ${SYSTEMD_FILES} DESTINATION lib/systemd/user/)
36-
install(DIRECTORY DESTINATION lib/systemd/user/dde-osd.target.wants/)
37-
install_symlink(dde-login-reminder.service dde-osd.target.wants)
38+
# Install desktop file to autostart directory
39+
install(FILES dde-login-reminder.desktop DESTINATION /etc/xdg/autostart/)
40+
41+
# Translation files
42+
file(GLOB TS_FILES ${CMAKE_CURRENT_SOURCE_DIR}/translations/*.ts)
43+
find_package(Qt${QT_VERSION_MAJOR} COMPONENTS LinguistTools)
44+
if(Qt${QT_VERSION_MAJOR}LinguistTools_FOUND AND TS_FILES)
45+
qt_add_translation(QM_FILES ${TS_FILES})
46+
add_custom_target(${BIN_NAME}_translations ALL DEPENDS ${QM_FILES})
47+
install(FILES ${QM_FILES} DESTINATION ${CMAKE_INSTALL_DATADIR}/${BIN_NAME}/translations)
48+
endif()
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
[Desktop Entry]
2+
Type=Application
3+
Name=DDE Login Reminder
4+
Comment=Display login reminder notification
5+
Exec=/usr/bin/dde-login-reminder
6+
Icon=preferences-system
7+
Terminal=false
8+
Categories=System;
9+
X-Deepin-AutoStart=true
10+
NoDisplay=true

tools/dde-login-reminder/dde-login-reminder.service

Lines changed: 0 additions & 10 deletions
This file was deleted.

tools/dde-login-reminder/main.cpp

Lines changed: 182 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,26 +2,45 @@
22
//
33
// SPDX-License-Identifier: GPL-3.0-or-later
44

5+
#include <QCoreApplication>
56
#include <QDateTime>
7+
#include <QDBusConnectionInterface>
68
#include <QDBusInterface>
79
#include <QDBusPendingReply>
10+
#include <QDBusServiceWatcher>
811
#include <QNetworkInterface>
12+
#include <QTranslator>
13+
#include <QLocale>
14+
#include <QLibraryInfo>
15+
#include <DConfig>
916

1017
#include <unistd.h>
1118

12-
#include "utils.h"
1319
#include "loginreminderinfo.h"
1420

21+
DCORE_USE_NAMESPACE
22+
23+
// 前置声明
24+
int checkPasswordExpired();
1525

1626
int start()
1727
{
1828
qDebug() << "login reminder init";
19-
#if 0
20-
// TODO has no 'com.deepin.dde.startdde' gsetting
21-
if (!Utils::SettingValue("com.deepin.dde.startdde", QByteArray(), "login-reminder", false).toBool())
22-
return 0;
23-
#endif
24-
// 登录后展示横幅通知信息
29+
30+
// 读取 DConfig 配置,检查 LoginReminder 是否开启
31+
DConfig *config = DConfig::create("org.deepin.dde.session", "org.deepin.dde.session");
32+
bool loginReminderEnabled = false; // 默认关闭
33+
if (config && config->isValid()) {
34+
loginReminderEnabled = config->value("LoginReminder", false).toBool();
35+
qDebug() << "LoginReminder enabled:" << loginReminderEnabled;
36+
}
37+
38+
// 如果 login-reminder 没有开启,仅检查密码过期情况
39+
if (!loginReminderEnabled) {
40+
qDebug() << "LoginReminder is disabled, only checking password expiration";
41+
return checkPasswordExpired();
42+
}
43+
2544
QDBusInterface userInter("org.deepin.dde.Accounts1", QString("/org/deepin/dde/Accounts1/User%1").arg(getuid()), "org.deepin.dde.Accounts1.User", QDBusConnection::systemBus());
2645
QDBusPendingReply<LoginReminderInfo> reply = userInter.call("GetReminderInfo");
2746
if (reply.isError()) {
@@ -69,11 +88,18 @@ int start()
6988
const QString &address = (info.CurrentLogin.Address == "0.0.0.0") ? getFirstIpAddress() : info.CurrentLogin.Address;
7089
QString body = QString("%1 %2 %3").arg(info.Username).arg(address).arg(currentLoginTime);
7190
int daysLeft = info.spent.LastChange + info.spent.Max - int(QDateTime::currentSecsSinceEpoch() / SECONDS_PER_DAY);
91+
bool needNotify = false;
7292
if ((info.spent.Max != -1) && (info.spent.Warn != -1) && (info.spent.Warn > daysLeft)) {
93+
needNotify = true;
7394
body += " " + QString(QObject::tr("Your password will expire in %1 days")).arg(daysLeft);
7495
}
75-
body += "\n" + QString(QObject::tr("%1 login failures since the last successful login")).arg(info.FailCountSinceLastLogin);
76-
96+
if (info.FailCountSinceLastLogin > 0) {
97+
needNotify = true;
98+
body += "\n" + QString(QObject::tr("%1 login failures since the last successful login")).arg(info.FailCountSinceLastLogin);
99+
}
100+
if (!needNotify) {
101+
return 0;
102+
}
77103
const QString &lastLoginTime = info.LastLogin.Time.left(QString("yyyy-MM-dd hh:mm:ss").length());
78104
QString content;
79105
content += QString("<p>%1</p>").arg(info.Username);
@@ -95,7 +121,7 @@ int start()
95121
uint notifyId = notifyIdReply.value();
96122

97123
// 5秒后自动关闭通知,避免在通知中心中显示
98-
sleep(5);
124+
sleep(10);
99125

100126
QDBusPendingReply<> closeReply = notifyInter->call("CloseNotification", notifyId);
101127
if (closeReply.isError()) {
@@ -107,13 +133,154 @@ int start()
107133
return 0;
108134
}
109135

136+
int checkPasswordExpired() {
137+
qDebug() << "check password expired";
138+
139+
// 调用 PasswordExpiredInfo 获取密码过期信息
140+
QDBusInterface userInter("org.deepin.dde.Accounts1",
141+
QString("/org/deepin/dde/Accounts1/User%1").arg(getuid()),
142+
"org.deepin.dde.Accounts1.User",
143+
QDBusConnection::systemBus());
144+
145+
QDBusMessage reply = userInter.call("PasswordExpiredInfo");
146+
if (reply.type() == QDBusMessage::ErrorMessage) {
147+
qWarning() << "failed to retrieve password expired info, error: " << reply.errorMessage();
148+
return -1;
149+
}
150+
151+
// 解析返回值: (Int32 expiredStatus, Int64 dayLeft)
152+
QList<QVariant> args = reply.arguments();
153+
if (args.size() < 2) {
154+
qWarning() << "invalid reply from PasswordExpiredInfo";
155+
return -1;
156+
}
157+
158+
int expiredStatus = args[0].toInt();
159+
qint64 dayLeft = args[1].toLongLong();
160+
161+
qDebug() << "Password expired status:" << expiredStatus << ", days left:" << dayLeft;
162+
163+
// expiredStatus: 0=正常, 1=即将过期, 2=已过期
164+
if (expiredStatus == 0) {
165+
qDebug() << "Password is valid, no notification needed";
166+
return 0;
167+
}
168+
169+
// 构建通知内容
170+
QString title = QObject::tr("Password Expiration Warning");
171+
QString body;
172+
QString content;
173+
174+
if (expiredStatus == 2) {
175+
// 密码已过期
176+
body = QObject::tr("Your password has expired. Please change it immediately.");
177+
content = QString("<p><b>%1</b></p>").arg(QObject::tr("Your password has expired!"));
178+
content += QString("<p>%1</p>").arg(QObject::tr("For security reasons, please change your password immediately."));
179+
} else if (expiredStatus == 1 && dayLeft > 0) {
180+
// 密码即将过期
181+
body = QObject::tr("Your password will expire in %1 days. Please change it soon.").arg(dayLeft);
182+
content = QString("<p><b>%1</b></p>").arg(QObject::tr("Password Expiration Warning"));
183+
content += QString("<p>%1</p>").arg(QObject::tr("Your password will expire in %1 days.").arg(dayLeft));
184+
content += QString("<p>%1</p>").arg(QObject::tr("Please change your password as soon as possible."));
185+
} else {
186+
// 其他情况
187+
qDebug() << "Unknown password status, no notification";
188+
return 0;
189+
}
190+
191+
// 发送通知
192+
QDBusInterface *notifyInter = new QDBusInterface("org.freedesktop.Notifications",
193+
"/org/freedesktop/Notifications",
194+
"org.freedesktop.Notifications");
195+
QVariantMap hints;
196+
// 点击通知后打开控制中心的账户密码修改页面
197+
hints.insert(QString("x-deepin-action-change_password"),
198+
QVariant(QString("dbus-send,--print-reply,--dest=org.deepin.dde.ControlCenter1,/org/deepin/dde/ControlCenter1,org.deepin.dde.ControlCenter1.ShowPage,string:accountsloginMethodItempassword")));
199+
200+
QDBusPendingReply<uint> notifyIdReply = notifyInter->call("Notify",
201+
"dde-control-center",
202+
uint(0),
203+
"preferences-system",
204+
title,
205+
body,
206+
QStringList() << "change_password" << QObject::tr("Change Password"),
207+
hints,
208+
int(0));
209+
if (notifyIdReply.isError()) {
210+
qWarning() << "failed to call notification, error: " << notifyIdReply.error().message();
211+
return -1;
212+
}
213+
uint notifyId = notifyIdReply.value();
214+
215+
// 10秒后自动关闭通知
216+
sleep(5);
217+
218+
QDBusPendingReply<> closeReply = notifyInter->call("CloseNotification", notifyId);
219+
if (closeReply.isError()) {
220+
qWarning() << "failed to close notification, error: " << closeReply.error().message();
221+
return -1;
222+
}
223+
224+
qDebug() << "password expired check finished";
225+
return 0;
226+
}
227+
110228
int main(int argc, char *argv[])
111229
{
112-
Q_UNUSED(argc);
113-
Q_UNUSED(argv);
230+
QCoreApplication app(argc, argv);
231+
app.setOrganizationName("deepin");
232+
app.setApplicationName("dde-login-reminder");
233+
234+
// 加载翻译文件
235+
QTranslator *translator = new QTranslator(&app);
236+
QString locale = QLocale::system().name();
237+
238+
// 尝试多个可能的翻译路径
239+
QStringList translationPaths;
240+
translationPaths << QString(CMAKE_INSTALL_FULL_DATADIR) + "/dde-login-reminder/translations"
241+
<< "/usr/share/dde-login-reminder/translations"
242+
<< "/usr/local/share/dde-login-reminder/translations";
243+
244+
bool loaded = false;
245+
for (const QString &path : translationPaths) {
246+
if (translator->load(QString("dde-login-reminder_%1").arg(locale), path)) {
247+
app.installTranslator(translator);
248+
qDebug() << "Loaded translation for locale:" << locale << "from" << path;
249+
loaded = true;
250+
break;
251+
}
252+
}
253+
254+
if (!loaded) {
255+
qDebug() << "Failed to load translation for locale:" << locale;
256+
qDebug() << "Tried paths:" << translationPaths;
257+
}
114258

115259
registerLoginReminderInfoMetaType();
116260

117-
return 0;
118-
// return start();
119-
}
261+
// 检查 org.deepin.dde.Notification1 服务是否已启动
262+
QDBusConnectionInterface *interface = QDBusConnection::sessionBus().interface();
263+
if (interface->isServiceRegistered("org.deepin.dde.Notification1")) {
264+
// 服务已启动,直接执行
265+
qDebug() << "Notification service already available, starting immediately";
266+
int result = start();
267+
return result;
268+
}
269+
270+
// 服务未启动,动态监听服务注册
271+
qDebug() << "Notification service not available, waiting for it to start...";
272+
QDBusServiceWatcher *watcher = new QDBusServiceWatcher(
273+
"org.deepin.dde.Notification1",
274+
QDBusConnection::sessionBus(),
275+
QDBusServiceWatcher::WatchForRegistration,
276+
&app
277+
);
278+
279+
QObject::connect(watcher, &QDBusServiceWatcher::serviceRegistered, [&app](const QString &serviceName) {
280+
qDebug() << "Notification service registered:" << serviceName;
281+
int result = start();
282+
app.exit(result);
283+
});
284+
285+
return app.exec();
286+
}

0 commit comments

Comments
 (0)