通过 DBus 使用 Qt 设置系统时间

Set system time using Qt through DBus

我尝试通过以下方式使用 Qt through DBus 设置系统时间:

#include <QDBusConnection>
#include <QDBusInterface>
#include <QDBusMessage>
#include <QDebug>
#include <QDateTime>
#include <cstdlib>

int main (int /*argc*/, char ** /*argv*/)
{
    QDBusConnection dbConnection = QDBusConnection::systemBus ();
    QDBusInterface dbInterface (
            "org.freedesktop.timedate1.set-time"
          , "/org/freedesktop/timedate1/set-time/Manager"
          , "org.freedesktop.timedate1.set-time.Manager"
          , dbConnection);
    qDebug () << "DBus interface validation: " << dbInterface.isValid ();
    if (dbInterface.isValid () ) {
        QDBusMessage dbMessage = dbInterface.call ("SetTime", QDateTime::currentDateTime ().toMSecsSinceEpoch () * 1000, false, false);
        qDebug () << "DBus message: " << dbMessage;
    }

    return EXIT_SUCCESS;
}

但我有:DBus interface validation: false

如果我在控制台调用:

$ gdbus introspect \
      --system \
      --dest org.freedesktop.timedate1 \
      --object-path /org/freedesktop/timedate1

我得到了一些相关的输出(所以看起来环境没有问题):

node /org/freedesktop/timedate1 {
  interface org.freedesktop.DBus.Peer {
        ...
  };
  interface org.freedesktop.DBus.Introspectable {
        ...
  };
  interface org.freedesktop.DBus.Properties {
    methods:
        ...
    signals:
        ...
    properties:
  };
  interface org.freedesktop.timedate1 {
    methods:
      SetTime(in  x arg_0,
              in  b arg_1,
              in  b arg_2);
        ...
    signals:
    properties:
        ...
  };
};

可在 GitLab.

获取源代码和构建脚本

简而言之:QDBusInterface 创建时的重试循环完成了工作。

我进行了更多探索。该 dbus 对象由 systemd-timedated 服务提供。了解其状态:

sudo systemctl status systemd-timedated

服务的配置在/lib/systemd/system/systemd-timedated.service:

[Unit]
Description=Time & Date Service
Documentation=man:systemd-timedated.service(8) man:localtime(5)
Documentation=http://www.freedesktop.org/wiki/Software/systemd/timedated

[Service]
ExecStart=/lib/systemd/systemd-timedated
BusName=org.freedesktop.timedate1
CapabilityBoundingSet=CAP_SYS_TIME
WatchdogSec=1min
PrivateTmp=yes
ProtectSystem=yes
ProtectHome=yes

BusName设置负责所谓的'D-Bus activation of a service'。因此,当有人试图访问名称 org.freedesktop.timedate1.

时,该服务将启动

但显然需要时间才能开始。我不知道应该如何干净地完成它,但是您可以创建一个重试循环来创建 QDBusInterface。您会看到 sudo systemctl status systemd-timedated 变为活动状态并且 Qt 检索到有效接口。

我尝试过的对象名称和路径:

QDBusInterface dbInterface (
    "org.freedesktop.timedate1"
    , "/org/freedesktop/timedate1"
    , "org.freedesktop.timedate1"
    , dbConnection);

有几个问题。

  1. 使用了错误的 D-Bus 命令。在尝试编写 Qt 程序之前,我必须使用控制台调试命令。所以正确的命令是:

    dbus-send \
        --system \
        --print-reply \
        --type=method_call \
        --dest='org.freedesktop.timedate1' \
               '/org/freedesktop/timedate1' \
                org.freedesktop.timedate1.SetTime \
                    int64:120000000 \
                    boolean:true \
                    boolean:false
    
  2. 当使用 ntp 服务时,命令将执行错误:Automatic time synchronization is enabled。因此(如建议 here)必须禁用同步:

    timedatectl set-ntp 0
    
  3. 如@Velcan 所述,定时服务处于非活动状态:

    the service is started when someone tries to access the name org.freedesktop.timedate1

    在我的环境 (KUbuntu 15.10 x86_64) 中,服务在上次调用 30 秒后处于活动状态。

  4. 根据Qt documentation

    bool QDBusAbstractInterface::isValid() const

    Returns true if this is a valid reference to a remote object. It returns false if there was an error during the creation of this interface (for instance, if the remote application does not exist).

    Note: when dealing with remote objects, it is not always possible to determine if it exists when creating a QDBusInterface.

  5. 即使 QDBusAbstractInterface::isValid() returns false call 函数执行成功。

  6. 所以最后,正确的代码非常简短:

    QDBusInterface dbInterface (
        "org.freedesktop.timedate1"
      , "/org/freedesktop/timedate1"
      , "org.freedesktop.timedate1"
      , QDBusConnection::systemBus () );
    qDebug () << dbInterface.call ("SetTime", 120000000ll, true, false);  
    

    此命令将时间设置为提前两分钟。

    感谢@Velkan帮忙解答问题并提供有用信息!