Skip to content

fix(dbus): activate existing window on repeated launch#355

Open
add-uos wants to merge 1 commit into
linuxdeepin:develop/snipefrom
add-uos:fix-364253-activate-existing-window-on-repeated-launch
Open

fix(dbus): activate existing window on repeated launch#355
add-uos wants to merge 1 commit into
linuxdeepin:develop/snipefrom
add-uos:fix-364253-activate-existing-window-on-repeated-launch

Conversation

@add-uos
Copy link
Copy Markdown
Contributor

@add-uos add-uos commented Jun 3, 2026

Add ActivateWindow D-Bus method to restore and raise the existing window when user tries to launch a second instance.

添加ActivateWindow D-Bus接口,在重复启动时激活已有实例窗口,
从最小化状态恢复并显示到前台。

Log: 修复重复启动时激活已有窗口
PMS: BUG-364253
Influence: 重复启动语音笔记时,已有实例窗口会被激活显示到前台,
而非静默退出。

Add ActivateWindow D-Bus method to restore and raise the existing
window when user tries to launch a second instance.

添加ActivateWindow D-Bus接口,在重复启动时激活已有实例窗口,
从最小化状态恢复并显示到前台。

Log: 修复重复启动时激活已有窗口
PMS: BUG-364253
Influence: 重复启动语音笔记时,已有实例窗口会被激活显示到前台,
而非静默退出。
@add-uos add-uos force-pushed the fix-364253-activate-existing-window-on-repeated-launch branch from 7bfeced to 68336fb Compare June 3, 2026 06:59
@deepin-ci-robot
Copy link
Copy Markdown

deepin pr auto review

你好!我是CodeGeeX。我已经仔细审查了你提供的Git Diff。这次代码变更的主要目的是实现单实例应用的主窗口激活:当用户尝试启动第二个实例时,通过D-Bus通知正在运行的第一实例将其主窗口从最小化状态恢复并置顶。

整体思路是正确的,但在语法逻辑、代码质量和性能方面有一些值得注意和改进的地方。以下是详细的审查意见:

1. 语法与逻辑

  • D-Bus调用时序问题
    main.cpp 中,你使用了 iface.asyncCall("ActivateWindow")。虽然异步调用不会阻塞当前进程,但紧接着就调用了 QCoreApplication::exit(0)return 0
    风险:在进程退出时,Qt的事件循环可能会停止,导致底层的D-Bus连接被销毁,从而使得异步消息未能真正发送出去。
    建议:将 asyncCall 改为同步调用 iface.call("ActivateWindow")。因为这是一个极快的D-Bus本地通信,耗时可以忽略不计,且能确保消息在进程退出前一定发送成功。
  • 重复的单实例判断逻辑
    main.cpp 中存在两段几乎一模一样的单实例判断逻辑(一段在 DApplication 属性设置的回调中,一段在 main 函数主体中)。这属于历史遗留的冗余代码,通常只需要保留一处即可。如果必须保留两处,确保两处都调用了激活逻辑(你目前做到了,但建议后续重构清理)。

2. 代码质量

  • 日志输出过于频繁
    VoiceNoteDBusService.cppActivateWindow 函数中,使用了 qInfo()qWarning() 打印正常流程日志。D-Bus接口属于服务性质,可能被频繁调用,过多的日志不仅没有太大价值,反而会污染系统日志。
    建议:正常激活成功的日志可以使用 qDebug(),仅在找不到窗口或发生错误时使用 qWarning()
  • 魔法字符串
    main.cpp 中的 D-Bus 服务名、路径和接口名是硬编码的字符串。
    建议:建议将其提取为常量,或者使用 VoiceNoteDBusService 类中的静态常量,以保证与注册时的命名绝对一致,避免后期修改漏掉导致D-Bus调用失败。

3. 代码性能

  • 窗口查找逻辑
    qApp->topLevelWindows() 会返回应用内所有顶层窗口(包括主窗口、隐藏的工具窗口、弹出窗口等)。直接取 windows.first() 并不一定能拿到你期望的“主窗口”。
    建议:如果主窗口有特定的类名或对象名,建议遍历查找;或者更推荐的做法是,在 VoiceNoteDBusService 中保存主窗口的指针(QPointer<MainWindow>),直接调用该指针的方法,这样性能更高且行为更确定。

4. 代码安全

  • 窗口指针空指针风险
    QWindow *window = windows.first(); 虽然在 !windows.isEmpty() 的保护下不会立刻崩溃,但如果该窗口在别的线程被销毁,或者它本身不是一个合适的可视化窗口,后续的 showNormal() 等操作可能会引发异常。
    建议:对取出的窗口进行有效性校验。
  • 版权年份更新
    你修改了 SPDX 版权声明从 20252025 - 2026。请确保这符合你们项目的版权规范,通常年份应该是创建年份到当前修改年份(如 2023 - 2024),如果是提前写为 2026,请确认合规性。

改进后的代码建议

基于以上分析,我为你提供了修改后的代码参考:

1. src/dbus/VoiceNoteDBusService.h

建议增加一个获取主窗口的辅助函数,或者直接持有主窗口指针(视你的架构而定,这里以更通用的查找优化为例):

// SPDX-FileCopyrightText: 2025 - 2026 UnionTech Software Technology Co., Ltd.
//
// SPDX-License-Identifier: GPL-3.0-or-later

#ifndef VOICENOTEDBUSSERVICE_H
#define VOICENOTEDBUSSERVICE_H

#include <QObject>

class VoiceNoteDBusService : public QObject
{
    Q_OBJECT
    // ... 其他宏定义
public:
    explicit VoiceNoteDBusService(QObject *parent = nullptr);

public slots:
    // D-Bus接口1: 获取列表
    QString GetNotesList();

    // D-Bus接口2: 录音
    bool RecordVoice(int folderId, int noteId);

    // D-Bus接口3: 激活窗口(从最小化恢复并显示到前台)
    void ActivateWindow();
};

#endif // VOICENOTEDBUSSERVICE_H

2. src/dbus/VoiceNoteDBusService.cpp

优化窗口查找逻辑和日志级别:

// SPDX-FileCopyrightText: 2025 - 2026 UnionTech Software Technology Co., Ltd.
//
// SPDX-License-Identifier: GPL-3.0-or-later

#include "VoiceNoteDBusService.h"
#include <QGuiApplication>
#include <QWindow>

// ... 其他代码

void VoiceNoteDBusService::ActivateWindow()
{
    qDebug() << "D-Bus: ActivateWindow called";

    const auto windows = qApp->topLevelWindows();
    QWindow *mainWindow = nullptr;

    // 优化:尽量寻找可见的主窗口,而不是盲目取第一个
    for (QWindow *window : windows) {
        if (window->objectName() == "MainWindow" || window->isModal() == false) { 
            // 建议通过 objectName 或特定属性来寻找真正的主窗口
            mainWindow = window;
            break;
        }
    }

    // 降级方案:如果没找到指定的,且列表不为空,则取第一个
    if (!mainWindow && !windows.isEmpty()) {
        mainWindow = windows.first();
    }

    if (mainWindow) {
        mainWindow->showNormal();
        mainWindow->raise();
        mainWindow->requestActivate();
        qDebug() << "Window activated successfully";
    } else {
        qWarning() << "No top-level windows found to activate";
    }
}

3. src/main.cpp

将异步调用改为同步,提取常量,确保进程退出前消息一定发出:

// Copyright (C) 2020 - 2026 Deepin Technology Co., Ltd.
// SPDX-FileCopyrightText: 2023 - 2026 UnionTech Software Technology Co., Ltd.
//
// SPDX-License-Identifier: GPL-3.0-or-later

#include <QDBusInterface>
#include <QDBusConnection>

DWIDGET_USE_NAMESPACE
DCORE_USE_NAMESPACE

namespace {
const QString DBUS_SERVICE = "org.deepin.voicenote";
const QString DBUS_PATH = "/org/deepin/voicenote";
const QString DBUS_INTERFACE = "org.deepin.voicenote";
}

static void activateExistingInstanceViaDBus()
{
    QDBusInterface iface(DBUS_SERVICE, DBUS_PATH, DBUS_INTERFACE,
                         QDBusConnection::sessionBus());
    if (iface.isValid()) {
        qInfo() << "Calling ActivateWindow on existing instance via D-Bus";
        // 修改:使用同步调用,确保在进程退出前D-Bus消息已经发送完毕
        iface.call("ActivateWindow");
    } else {
        qWarning() << "D-Bus interface not available:" << QDBusConnection::sessionBus().lastError().message();
    }
}

int main(int argc, char *argv[])
{
#ifdef __mips64
    // ... mips64 特殊处理
#endif

    // ... 前置代码

    DApplication *app = DApplication::globalApplication(argc, argv);
    
    // ... 属性设置

    QObject::connect(app, &DApplication::newInstanceStarted, app, [&]() {
        DGuiApplicationHelper::instance()->setSingleInstanceInterval(-1);
        if (!DGuiApplicationHelper::instance()->setSingleInstance(app->applicationName(), DGuiApplicationHelper::UserScope)) {
            qWarning() << "Another instance of deepin-voice-note is already running";
            activateExistingInstanceViaDBus();
            QCoreApplication::exit(0);
        }
    });

    DGuiApplicationHelper::instance()->setSingleInstanceInterval(-1);
    if (!DGuiApplicationHelper::instance()->setSingleInstance(app->applicationName(), DGuiApplicationHelper::UserScope)) {
        qWarning() << "Another instance of deepin-voice-note is already running";
        activateExistingInstanceViaDBus();
        return 0;
    }

    // ... 后续代码
}

希望这些审查意见和代码改进能对你有所帮助!如果有任何疑问,欢迎随时提问。

@deepin-ci-robot
Copy link
Copy Markdown

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: add-uos, lzwind

The full list of commands accepted by this bot can be found here.

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants