Compare commits
15 Commits
ed6c87638f
...
v2.1
Author | SHA1 | Date | |
---|---|---|---|
29434bc41e
|
|||
c97f1fc87c
|
|||
78f9939afc
|
|||
8bf3a92cbc
|
|||
d75053b243
|
|||
b11e18e560
|
|||
3a85891f2e | |||
cbbefae284
|
|||
6fc7f86a89
|
|||
9dc8fb2640
|
|||
7b5fe4b9d1
|
|||
bbb4efde18
|
|||
ad19cb19d1
|
|||
e72976840c
|
|||
8d43caadcc
|
22
.gitea/workflows/deploy.yaml
Normal file
22
.gitea/workflows/deploy.yaml
Normal file
@ -0,0 +1,22 @@
|
||||
name: Deploy
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- '**'
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: buildenv
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Archive
|
||||
run: |
|
||||
DISTRIBUTION=$(echo $GITHUB_REF_NAME | awk '{ gsub(/^v/, "slack-timedate-"); print $0 }')
|
||||
ln -s . $DISTRIBUTION
|
||||
touch $DISTRIBUTION.tar.xz
|
||||
tar --exclude="${DISTRIBUTION}/${DISTRIBUTION}*" --exclude-vcs -Jcvhf $DISTRIBUTION.tar.xz $DISTRIBUTION
|
||||
- uses: akkuman/gitea-release-action@v1
|
||||
with:
|
||||
files: "*.tar.xz"
|
||||
token: ${{ secrets.API_KEY }}
|
@ -1,5 +1,7 @@
|
||||
find_program(SED sed)
|
||||
|
||||
configure_file("${CMAKE_CURRENT_SOURCE_DIR}/resources.h.in" "resources.h")
|
||||
|
||||
add_custom_command(OUTPUT ${PROJECT_BINARY_DIR}/org.freedesktop.timedate1.service
|
||||
COMMAND ${SED} -e s|@LIBEXECDIR@|${CMAKE_INSTALL_FULL_LIBEXECDIR}| ${CMAKE_CURRENT_SOURCE_DIR}/org.freedesktop.timedate1.service.in > ${PROJECT_BINARY_DIR}/org.freedesktop.timedate1.service
|
||||
MAIN_DEPENDENCY org.freedesktop.timedate1.service.in
|
||||
|
@ -5,6 +5,10 @@
|
||||
<property name="Timezone" type="s" access="read"/>
|
||||
<property name="LocalRTC" type="b" access="read"/>
|
||||
<property name="NTP" type="b" access="read"/>
|
||||
<property name="CanNTP" type="b" access="read"/>
|
||||
<property name="NTPSynchronized" type="b" access="read"/>
|
||||
<property name="TimeUSec" type="t" access="read"/>
|
||||
<property name="RTCTimeUSec" type="t" access="read"/>
|
||||
<method name="SetTime">
|
||||
<arg name="usec_utc" type="x" direction="in"/>
|
||||
<arg name="relative" type="b" direction="in"/>
|
||||
|
1
data/resources.h.in
Normal file
1
data/resources.h.in
Normal file
@ -0,0 +1 @@
|
||||
#define DATADIR_INTERFACES "@CMAKE_INSTALL_PREFIX@/@CMAKE_INSTALL_DATADIR@/dbus-1/interfaces"
|
@ -1,10 +1,10 @@
|
||||
find_package(PkgConfig)
|
||||
pkg_check_modules(GDBUS REQUIRED gio-2.0 dbus-1 glibmm-2.4 giomm-2.4)
|
||||
|
||||
add_executable(slack-timedate
|
||||
slack-timedate.cpp slack-timedate.h
|
||||
add_executable(slack-timedate main.cpp
|
||||
timedate.cpp timedate.h
|
||||
)
|
||||
target_include_directories(slack-timedate PRIVATE ${GDBUS_INCLUDE_DIRS})
|
||||
target_include_directories(slack-timedate PRIVATE ${CMAKE_BINARY_DIR}/data ${GDBUS_INCLUDE_DIRS})
|
||||
target_link_libraries(slack-timedate ${GDBUS_LIBRARIES})
|
||||
|
||||
install(TARGETS slack-timedate DESTINATION ${CMAKE_INSTALL_LIBEXECDIR})
|
||||
|
50
src/main.cpp
Normal file
50
src/main.cpp
Normal file
@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright (C) 2013-2024 Eugen Wissner <belka@caraus.de>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
#include "timedate.h"
|
||||
|
||||
bool timeout_callback(Glib::RefPtr<Glib::MainLoop> loop2quit)
|
||||
{
|
||||
loop2quit->quit();
|
||||
return false;
|
||||
}
|
||||
|
||||
constexpr unsigned int default_exit_sec = 300;
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
Gio::init();
|
||||
auto timedate1 = std::make_unique<dlackware::timedate::timedate1>();
|
||||
|
||||
guint owner_id = Gio::DBus::own_name(Gio::DBus::BUS_TYPE_SYSTEM,
|
||||
Glib::ustring(dlackware::timedate::bus_name.cbegin(), dlackware::timedate::bus_name.cend()),
|
||||
std::bind(&dlackware::timedate::timedate1::on_bus_acquired, timedate1.get(),
|
||||
std::placeholders::_1, std::placeholders::_2),
|
||||
Gio::DBus::SlotNameAcquired(),
|
||||
std::bind(&dlackware::timedate::timedate1::on_name_lost, timedate1.get(),
|
||||
std::placeholders::_1, std::placeholders::_2),
|
||||
Gio::DBus::BUS_NAME_OWNER_FLAGS_ALLOW_REPLACEMENT | Gio::DBus::BUS_NAME_OWNER_FLAGS_REPLACE);
|
||||
|
||||
Glib::RefPtr<Glib::MainLoop> loop = Glib::MainLoop::create(false);
|
||||
|
||||
Glib::signal_timeout().connect_seconds(sigc::bind(&timeout_callback, loop), default_exit_sec);
|
||||
loop->run();
|
||||
|
||||
Gio::DBus::unown_name(owner_id);
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
@ -1,330 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2013-2024 Eugen Wissner <belka@caraus.de>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <cstring>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/time.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include "slack-timedate.h"
|
||||
|
||||
static void slack_method_call(const Glib::RefPtr<Gio::DBus::Connection>& connection, const Glib::ustring& sender,
|
||||
const Glib::ustring& object_path, const Glib::ustring& interface_name, const Glib::ustring& method_name,
|
||||
const Glib::VariantContainerBase& parameters, const Glib::RefPtr<Gio::DBus::MethodInvocation>& invocation) {
|
||||
gchar *timezone;
|
||||
gboolean user_interaction, relative, is_localtime, use_ntp;
|
||||
gint64 usec_utc;
|
||||
auto parameters_copy = parameters.gobj_copy();
|
||||
|
||||
// Set time zone
|
||||
if (method_name == "SetTimezone")
|
||||
{
|
||||
g_variant_get(parameters_copy, "(&sb)", &timezone, &user_interaction);
|
||||
std::error_code error_code;
|
||||
|
||||
if (dlackware::timedate::set_timezone(timezone, error_code))
|
||||
{
|
||||
g_dbus_method_invocation_return_value(invocation->gobj(), NULL);
|
||||
}
|
||||
else
|
||||
{
|
||||
g_dbus_method_invocation_return_error(invocation->gobj(), G_IO_ERROR, G_IO_ERROR_FAILED,
|
||||
"Set time zone: %s",
|
||||
error_code ? error_code.message().c_str() : "Zone info is not a regular file");
|
||||
}
|
||||
g_free (timezone);
|
||||
}
|
||||
else if (method_name == "SetTime")
|
||||
{
|
||||
g_variant_get(parameters_copy, "(xbb)", &usec_utc, &relative, &user_interaction);
|
||||
|
||||
// Set time
|
||||
//if (!slack_set_time (usec_utc, slack_get_is_localtime ())) {
|
||||
if (slack_set_time(usec_utc, relative))
|
||||
{
|
||||
g_dbus_method_invocation_return_value(invocation->gobj(), NULL);
|
||||
}
|
||||
else
|
||||
{
|
||||
g_dbus_method_invocation_return_error(invocation->gobj(), G_IO_ERROR, G_IO_ERROR_FAILED,
|
||||
"Failed to set system clock");
|
||||
}
|
||||
}
|
||||
else if (method_name == "SetNTP")
|
||||
{
|
||||
g_variant_get(parameters_copy, "(bb)", &use_ntp, &user_interaction);
|
||||
|
||||
// Enable NTP
|
||||
if (slack_set_ntp (use_ntp))
|
||||
{
|
||||
g_dbus_method_invocation_return_value(invocation->gobj(), NULL);
|
||||
}
|
||||
else
|
||||
{
|
||||
g_dbus_method_invocation_return_error(invocation->gobj(),
|
||||
G_IO_ERROR,
|
||||
G_IO_ERROR_FAILED,
|
||||
"Error enabling NTP");
|
||||
}
|
||||
}
|
||||
else if (method_name == "ListTimezones")
|
||||
{
|
||||
try
|
||||
{
|
||||
auto return_tuple = Glib::Variant<std::vector<Glib::ustring>>::create_tuple({
|
||||
dlackware::timedate::list_timezones()
|
||||
});
|
||||
g_dbus_method_invocation_return_value(invocation->gobj(), return_tuple.gobj());
|
||||
}
|
||||
catch (const std::exception& exception)
|
||||
{
|
||||
g_dbus_method_invocation_return_error(invocation->gobj(), G_IO_ERROR, G_IO_ERROR_FAILED,
|
||||
"List time zones: %s", exception.what());
|
||||
}
|
||||
}
|
||||
g_free(parameters_copy);
|
||||
}
|
||||
|
||||
static void slack_get_property(Glib::VariantBase& result, const Glib::RefPtr<Gio::DBus::Connection>& connection,
|
||||
const Glib::ustring& sender, const Glib::ustring& object_path, const Glib::ustring& interface_name,
|
||||
const Glib::ustring& prop_name)
|
||||
{
|
||||
if (prop_name == "Timezone")
|
||||
{
|
||||
result = Glib::Variant<Glib::ustring>::create(dlackware::timedate::timezone());
|
||||
}
|
||||
else if (prop_name == "LocalRTC")
|
||||
{
|
||||
result = Glib::Variant<bool>::create(slack_get_is_localtime());
|
||||
}
|
||||
else if (prop_name == "NTP")
|
||||
{
|
||||
result = Glib::Variant<bool>::create(slack_get_ntp());
|
||||
}
|
||||
}
|
||||
|
||||
gboolean timeout_callback(Glib::RefPtr<Glib::MainLoop> loop2quit)
|
||||
{
|
||||
loop2quit->quit();
|
||||
return false;
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
Gio::init();
|
||||
auto timedate1 = std::make_unique<dlackware::timedate::timedate1>();
|
||||
|
||||
guint owner_id = Gio::DBus::own_name(Gio::DBus::BUS_TYPE_SYSTEM, BUS_NAME,
|
||||
std::bind(&dlackware::timedate::timedate1::on_bus_acquired, timedate1.get(),
|
||||
std::placeholders::_1, std::placeholders::_2),
|
||||
Gio::DBus::SlotNameAcquired(),
|
||||
std::bind(&dlackware::timedate::timedate1::on_name_lost, timedate1.get(),
|
||||
std::placeholders::_1, std::placeholders::_2),
|
||||
Gio::DBus::BUS_NAME_OWNER_FLAGS_ALLOW_REPLACEMENT | Gio::DBus::BUS_NAME_OWNER_FLAGS_REPLACE);
|
||||
|
||||
Glib::RefPtr<Glib::MainLoop> loop = Glib::MainLoop::create(false);
|
||||
|
||||
Glib::signal_timeout().connect_seconds(sigc::bind(&timeout_callback, loop), DEFAULT_EXIT_SEC);
|
||||
loop->run();
|
||||
|
||||
Gio::DBus::unown_name(owner_id);
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
namespace dlackware::timedate
|
||||
{
|
||||
timedate1::timedate1()
|
||||
: interface_vtable{ &slack_method_call, &slack_get_property }
|
||||
{
|
||||
}
|
||||
|
||||
void timedate1::on_name_lost(const Glib::RefPtr<Gio::DBus::Connection>& connection, const Glib::ustring& name)
|
||||
{
|
||||
g_warning("Failed to acquire the service %s.\n", name.data());
|
||||
exit(1);
|
||||
}
|
||||
|
||||
void timedate1::on_bus_acquired(const Glib::RefPtr<Gio::DBus::Connection>& connection, const Glib::ustring& name)
|
||||
{
|
||||
Glib::RefPtr<Gio::DBus::NodeInfo> introspection_data;
|
||||
|
||||
try
|
||||
{
|
||||
introspection_data = Gio::DBus::NodeInfo::create_for_xml(INTROSPECTION_XML);
|
||||
}
|
||||
catch (Glib::Error& slack_err)
|
||||
{
|
||||
g_error("Failed to parse D-Bus introspection XML: %s\n", slack_err.what().data());
|
||||
}
|
||||
try
|
||||
{
|
||||
guint registration_id = connection->register_object(BUS_PATH,
|
||||
introspection_data->lookup_interface(), this->interface_vtable);
|
||||
}
|
||||
catch (Glib::Error& slack_err)
|
||||
{
|
||||
g_critical("Failed to register callbacks for the exported object with the D-Bus interface: %s\n",
|
||||
slack_err.what().data());
|
||||
}
|
||||
}
|
||||
|
||||
void list_timezones(const std::string& prefix, std::vector<Glib::ustring>& accumulator)
|
||||
{
|
||||
auto zoneinfo_path = std::filesystem::path(zoneinfo_database) / prefix;
|
||||
|
||||
for (auto zoneinfo_entry : std::filesystem::directory_iterator(zoneinfo_path))
|
||||
{
|
||||
auto new_prefix = prefix + zoneinfo_entry.path().filename().string();
|
||||
|
||||
if (zoneinfo_entry.is_directory())
|
||||
{
|
||||
list_timezones(new_prefix + "/", accumulator);
|
||||
}
|
||||
else if (zoneinfo_entry.is_regular_file() && !zoneinfo_entry.path().has_extension()
|
||||
&& zoneinfo_entry.path().filename() != "leapseconds")
|
||||
{
|
||||
accumulator.emplace_back(new_prefix);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Glib::Variant<std::vector<Glib::ustring>> list_timezones()
|
||||
{
|
||||
std::vector<Glib::ustring> result;
|
||||
|
||||
list_timezones("", result);
|
||||
return Glib::Variant<std::vector<Glib::ustring>>::create(result);
|
||||
}
|
||||
|
||||
Glib::ustring timezone()
|
||||
{
|
||||
std::unique_ptr<gchar[], decltype(&g_free)> zone_copied_from(
|
||||
g_file_read_link("/etc/localtime", NULL), &g_free);
|
||||
|
||||
if (zone_copied_from == nullptr)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
return Glib::ustring{ zone_copied_from.get() + strlen(zoneinfo_database) + 1 };
|
||||
}
|
||||
|
||||
bool set_timezone(gchar *zone, std::error_code& ec)
|
||||
{
|
||||
ec.clear();
|
||||
auto zone_file = std::filesystem::path("/usr/share/zoneinfo") / zone;
|
||||
|
||||
if (!std::filesystem::is_regular_file(zone_file, ec) || ec)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
std::filesystem::path etc_localtime = "/etc/localtime";
|
||||
|
||||
std::filesystem::remove(etc_localtime);
|
||||
std::filesystem::create_symlink(zone_file, etc_localtime, ec);
|
||||
|
||||
return !ec;
|
||||
}
|
||||
}
|
||||
|
||||
gboolean slack_set_time(gint64 seconds_since_epoch, gboolean relative)
|
||||
{
|
||||
timespec ts;
|
||||
|
||||
if (relative)
|
||||
{
|
||||
if (clock_gettime(CLOCK_REALTIME, &ts) == -1)
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
ts.tv_sec += static_cast<time_t>(seconds_since_epoch / USEC_PER_SEC);
|
||||
ts.tv_nsec += (seconds_since_epoch % USEC_PER_SEC) * NSEC_PER_USEC;
|
||||
|
||||
if (ts.tv_nsec < 0)
|
||||
{
|
||||
--ts.tv_sec;
|
||||
ts.tv_nsec += USEC_PER_SEC;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ts.tv_sec = static_cast<time_t>(seconds_since_epoch / USEC_PER_SEC);
|
||||
ts.tv_nsec = (seconds_since_epoch % USEC_PER_SEC) * NSEC_PER_USEC;
|
||||
}
|
||||
return clock_settime (CLOCK_REALTIME, &ts) == 0;
|
||||
}
|
||||
|
||||
gboolean slack_get_is_localtime()
|
||||
{
|
||||
std::ifstream fh{ "/etc/hardwareclock", std::ios::in };
|
||||
if (!fh.is_open())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
std::array<char, 10> time_str; // "localtime" is longer than "UTC" and has 9 letters + \0
|
||||
|
||||
while (fh.good())
|
||||
{
|
||||
fh.getline(time_str.data(), time_str.size());
|
||||
fh.clear(fh.rdstate() & (~std::ios_base::failbit));
|
||||
|
||||
if (std::strncmp(time_str.data(), "localtime", time_str.size()) == 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
gboolean slack_get_ntp()
|
||||
{
|
||||
return Glib::file_test("/etc/rc.d/rc.ntpd", Glib::FileTest::FILE_TEST_IS_EXECUTABLE);
|
||||
}
|
||||
|
||||
gboolean slack_set_ntp(gboolean xntp)
|
||||
{
|
||||
std::filesystem::perms rc_mode = std::filesystem::perms::owner_read | std::filesystem::perms::owner_write
|
||||
| std::filesystem::perms::group_read | std::filesystem::perms::others_read;
|
||||
|
||||
if (xntp)
|
||||
{
|
||||
rc_mode |= std::filesystem::perms::owner_exec
|
||||
| std::filesystem::perms::group_exec | std::filesystem::perms::others_exec;
|
||||
g_message("Please don't forget to configure the NTP daemon");
|
||||
|
||||
/* It doesn't matter if fails.
|
||||
* The ntpdate command is considered obsolete, but "orthodox" ntpd -g
|
||||
* will fail if your system clock is off for more than half an hour. */
|
||||
g_spawn_command_line_async("/usr/sbin/ntpdate pool.ntp.org", NULL);
|
||||
}
|
||||
if (Glib::file_test("/etc/rc.d/rc.ntpd", Glib::FileTest::FILE_TEST_IS_REGULAR))
|
||||
{
|
||||
std::error_code ec;
|
||||
std::filesystem::permissions("/etc/rc.d/rc.ntpd", rc_mode, ec);
|
||||
return !ec;
|
||||
}
|
||||
else
|
||||
{
|
||||
g_error("The NTP daemon isn't installed");
|
||||
return false;
|
||||
}
|
||||
}
|
@ -1,102 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2013-2024 Eugen Wissner <belka@caraus.de>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <dbus/dbus.h>
|
||||
#include <glibmm.h>
|
||||
#include <giomm.h>
|
||||
#include <gio/gio.h>
|
||||
|
||||
#define BUS_NAME "org.freedesktop.timedate1"
|
||||
#define BUS_PATH "/org/freedesktop/timedate1"
|
||||
#define BUS_INTERFACE "org.freedesktop.timedate1"
|
||||
|
||||
#define INTROSPECTION_XML DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE \
|
||||
"<node>\n" \
|
||||
" <interface name=\"org.freedesktop.timedate1\">\n" \
|
||||
" <property name=\"Timezone\" type=\"s\" access=\"read\"/>\n" \
|
||||
" <property name=\"LocalRTC\" type=\"b\" access=\"read\"/>\n" \
|
||||
" <property name=\"NTP\" type=\"b\" access=\"read\"/>\n" \
|
||||
" <method name=\"SetTime\">\n" \
|
||||
" <arg name=\"usec_utc\" type=\"x\" direction=\"in\"/>\n" \
|
||||
" <arg name=\"relative\" type=\"b\" direction=\"in\"/>\n" \
|
||||
" <arg name=\"user_interaction\" type=\"b\" direction=\"in\"/>\n" \
|
||||
" </method>\n" \
|
||||
" <method name=\"SetTimezone\">\n" \
|
||||
" <arg name=\"timezone\" type=\"s\" direction=\"in\"/>\n" \
|
||||
" <arg name=\"user_interaction\" type=\"b\" direction=\"in\"/>\n" \
|
||||
" </method>\n" \
|
||||
" <method name=\"SetLocalRTC\">\n" \
|
||||
" <arg name=\"local_rtc\" type=\"b\" direction=\"in\"/>\n" \
|
||||
" <arg name=\"fix_system\" type=\"b\" direction=\"in\"/>\n" \
|
||||
" <arg name=\"user_interaction\" type=\"b\" direction=\"in\"/>\n" \
|
||||
" </method>\n" \
|
||||
" <method name=\"SetNTP\">\n" \
|
||||
" <arg name=\"use_ntp\" type=\"b\" direction=\"in\"/>\n" \
|
||||
" <arg name=\"user_interaction\" type=\"b\" direction=\"in\"/>\n" \
|
||||
" </method>\n" \
|
||||
" <method name=\"ListTimezones\">\n" \
|
||||
" <arg name=\"timezones\" type=\"as\" direction=\"out\"/>\n" \
|
||||
" </method>" \
|
||||
" </interface>\n" \
|
||||
"</node>\n"
|
||||
|
||||
#define DEFAULT_EXIT_SEC 300
|
||||
|
||||
constexpr gint32 USEC_PER_SEC = 1000000ULL;
|
||||
constexpr gint32 NSEC_PER_USEC = 1000ULL;
|
||||
|
||||
namespace dlackware::timedate
|
||||
{
|
||||
constexpr const char *zoneinfo_database = "/usr/share/zoneinfo";
|
||||
|
||||
// Returns the timezones available on the system.
|
||||
Glib::Variant<std::vector<Glib::ustring>> list_timezones();
|
||||
void list_timezones(const std::string& prefix, std::vector<Glib::ustring>& accumulator);
|
||||
|
||||
// Returns the system time zone.
|
||||
Glib::ustring timezone();
|
||||
|
||||
// Sets the system time zone to the one passed by the argument
|
||||
// Returns true on success, false otherwise
|
||||
bool set_timezone(gchar *, std::error_code& ec);
|
||||
|
||||
class timedate1
|
||||
{
|
||||
const Gio::DBus::InterfaceVTable interface_vtable;
|
||||
|
||||
public:
|
||||
timedate1();
|
||||
|
||||
void on_bus_acquired(const Glib::RefPtr<Gio::DBus::Connection>& connection, const Glib::ustring& name);
|
||||
void on_name_lost(const Glib::RefPtr<Gio::DBus::Connection>& connection, const Glib::ustring& name);
|
||||
};
|
||||
}
|
||||
|
||||
// Changes the date/time
|
||||
// Takes the amount of seconds since UNIX epoche and
|
||||
// Returns true on success, false otherwise
|
||||
gboolean slack_set_time(gint64 seconds_since_epoch, gboolean relative);
|
||||
|
||||
// Returns if the hardware clock is set to local time or not
|
||||
gboolean slack_get_is_localtime ();
|
||||
|
||||
// Returns if NTP is enabled
|
||||
gboolean slack_get_ntp ();
|
||||
|
||||
// Sets NTP
|
||||
gboolean slack_set_ntp (gboolean);
|
439
src/timedate.cpp
Normal file
439
src/timedate.cpp
Normal file
@ -0,0 +1,439 @@
|
||||
/*
|
||||
* Copyright (C) 2013-2024 Eugen Wissner <belka@caraus.de>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
#include <cstring>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <sys/timex.h>
|
||||
#include "timedate.h"
|
||||
|
||||
static void slack_method_call(const Glib::RefPtr<Gio::DBus::Connection>& connection, const Glib::ustring& sender,
|
||||
const Glib::ustring& object_path, const Glib::ustring& interface_name, const Glib::ustring& method_name,
|
||||
const Glib::VariantContainerBase& parameters, const Glib::RefPtr<Gio::DBus::MethodInvocation>& invocation)
|
||||
{
|
||||
// Set time zone
|
||||
if (method_name == "SetTimezone")
|
||||
{
|
||||
Glib::Variant<Glib::ustring> timezone;
|
||||
Glib::Variant<bool> user_interaction;
|
||||
|
||||
parameters.get_child(timezone, 0);
|
||||
parameters.get_child(user_interaction, 1);
|
||||
try
|
||||
{
|
||||
dlackware::timedate::set_timezone(timezone.get(), user_interaction.get());
|
||||
invocation->return_value(Glib::VariantContainerBase());
|
||||
}
|
||||
catch (const std::filesystem::filesystem_error& filesystem_error)
|
||||
{
|
||||
invocation->return_error(Gio::DBus::Error{ Gio::DBus::Error::FAILED, filesystem_error.what() });
|
||||
}
|
||||
}
|
||||
else if (method_name == "SetLocalRTC")
|
||||
{
|
||||
Glib::Variant<bool> local_rtc;
|
||||
Glib::Variant<bool> fix_system;
|
||||
Glib::Variant<bool> user_interaction;
|
||||
|
||||
parameters.get_child(local_rtc, 0);
|
||||
parameters.get_child(fix_system, 1);
|
||||
parameters.get_child(user_interaction, 2);
|
||||
|
||||
try
|
||||
{
|
||||
dlackware::timedate::set_local_rtc(local_rtc.get(), fix_system.get(), user_interaction.get());
|
||||
invocation->return_value(Glib::VariantContainerBase());
|
||||
}
|
||||
catch (const std::system_error& error)
|
||||
{
|
||||
invocation->return_error(Gio::DBus::Error{ Gio::DBus::Error::FAILED, error.what() });
|
||||
}
|
||||
catch (const Glib::SpawnError& spawn_error)
|
||||
{
|
||||
invocation->return_error(Gio::DBus::Error{ Gio::DBus::Error::FAILED, spawn_error.what() });
|
||||
}
|
||||
}
|
||||
else if (method_name == "SetTime")
|
||||
{
|
||||
Glib::Variant<gint64> usec_utc;
|
||||
Glib::Variant<bool> relative;
|
||||
Glib::Variant<bool> user_interaction;
|
||||
|
||||
parameters.get_child(usec_utc, 0);
|
||||
parameters.get_child(relative, 1);
|
||||
parameters.get_child(user_interaction, 2);
|
||||
|
||||
try
|
||||
{
|
||||
dlackware::timedate::set_time(usec_utc.get(), relative.get(), user_interaction.get());
|
||||
invocation->return_value(Glib::VariantContainerBase());
|
||||
}
|
||||
catch (const std::system_error& error)
|
||||
{
|
||||
invocation->return_error(Gio::DBus::Error{ Gio::DBus::Error::FAILED, error.what() });
|
||||
}
|
||||
}
|
||||
else if (method_name == "SetNTP")
|
||||
{
|
||||
Glib::Variant<bool> use_ntp;
|
||||
Glib::Variant<bool> user_interaction;
|
||||
|
||||
parameters.get_child(use_ntp, 0);
|
||||
parameters.get_child(user_interaction, 1);
|
||||
|
||||
// Enable NTP
|
||||
try
|
||||
{
|
||||
dlackware::timedate::set_ntp(use_ntp.get(), user_interaction.get());
|
||||
invocation->return_value(Glib::VariantContainerBase());
|
||||
}
|
||||
catch (const std::filesystem::filesystem_error& filesystem_error)
|
||||
{
|
||||
invocation->return_error(Gio::DBus::Error{ Gio::DBus::Error::FAILED, filesystem_error.what() });
|
||||
}
|
||||
}
|
||||
else if (method_name == "ListTimezones")
|
||||
{
|
||||
try
|
||||
{
|
||||
auto return_tuple = Glib::VariantContainerBase::create_tuple({
|
||||
Glib::Variant<std::vector<Glib::ustring>>::create(dlackware::timedate::list_timezones())
|
||||
});
|
||||
invocation->return_value(return_tuple);
|
||||
}
|
||||
catch (const std::exception& exception)
|
||||
{
|
||||
Gio::DBus::Error error{ Gio::DBus::Error::FAILED, exception.what() };
|
||||
invocation->return_error(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void slack_get_property(Glib::VariantBase& result, const Glib::RefPtr<Gio::DBus::Connection>& connection,
|
||||
const Glib::ustring& sender, const Glib::ustring& object_path, const Glib::ustring& interface_name,
|
||||
const Glib::ustring& prop_name)
|
||||
{
|
||||
if (prop_name == "Timezone")
|
||||
{
|
||||
result = Glib::Variant<Glib::ustring>::create(dlackware::timedate::timezone());
|
||||
}
|
||||
else if (prop_name == "LocalRTC")
|
||||
{
|
||||
result = Glib::Variant<bool>::create(dlackware::timedate::local_rtc());
|
||||
}
|
||||
else if (prop_name == "NTP")
|
||||
{
|
||||
result = Glib::Variant<bool>::create(dlackware::timedate::ntp());
|
||||
}
|
||||
else if (prop_name == "CanNTP")
|
||||
{
|
||||
result = Glib::Variant<bool>::create(dlackware::timedate::can_ntp());
|
||||
}
|
||||
else if (prop_name == "NTPSynchronized")
|
||||
{
|
||||
result = Glib::Variant<bool>::create(dlackware::timedate::ntp_synchronized());
|
||||
}
|
||||
else if (prop_name == "TimeUSec")
|
||||
{
|
||||
result = Glib::Variant<std::uint64_t>::create(dlackware::timedate::time_usec());
|
||||
}
|
||||
else if (prop_name == "RTCTimeUSec")
|
||||
{
|
||||
result = Glib::Variant<std::uint64_t>::create(dlackware::timedate::rtc_time_usec());
|
||||
}
|
||||
}
|
||||
|
||||
namespace dlackware::timedate
|
||||
{
|
||||
timedate1::timedate1()
|
||||
: interface_vtable{ &slack_method_call, &slack_get_property }
|
||||
{
|
||||
}
|
||||
|
||||
void timedate1::on_name_lost(const Glib::RefPtr<Gio::DBus::Connection>& connection, const Glib::ustring& name)
|
||||
{
|
||||
g_warning("Failed to acquire the service %s.\n", name.data());
|
||||
exit(1);
|
||||
}
|
||||
|
||||
void timedate1::on_bus_acquired(const Glib::RefPtr<Gio::DBus::Connection>& connection, const Glib::ustring& name)
|
||||
{
|
||||
Glib::RefPtr<Gio::DBus::NodeInfo> introspection_data;
|
||||
try
|
||||
{
|
||||
auto introspection_xml = Glib::file_get_contents(
|
||||
(std::filesystem::path(DATADIR_INTERFACES) / dlackware::timedate::bus_name).concat(".xml")
|
||||
);
|
||||
introspection_data = Gio::DBus::NodeInfo::create_for_xml(introspection_xml);
|
||||
}
|
||||
catch (Glib::Error& slack_err)
|
||||
{
|
||||
g_error("Failed to parse D-Bus introspection XML: %s\n", slack_err.what().data());
|
||||
}
|
||||
try
|
||||
{
|
||||
guint registration_id = connection->register_object(bus_path,
|
||||
introspection_data->lookup_interface(), this->interface_vtable);
|
||||
}
|
||||
catch (Glib::Error& slack_err)
|
||||
{
|
||||
g_critical("Failed to register callbacks for the exported object with the D-Bus interface: %s\n",
|
||||
slack_err.what().data());
|
||||
}
|
||||
}
|
||||
|
||||
static void list_timezones(const std::string& prefix, std::vector<Glib::ustring>& accumulator)
|
||||
{
|
||||
auto zoneinfo_path = std::filesystem::path(zoneinfo_database) / prefix;
|
||||
|
||||
for (auto zoneinfo_entry : std::filesystem::directory_iterator(zoneinfo_path))
|
||||
{
|
||||
auto new_prefix = prefix + zoneinfo_entry.path().filename().string();
|
||||
|
||||
if (zoneinfo_entry.is_directory())
|
||||
{
|
||||
list_timezones(new_prefix + "/", accumulator);
|
||||
}
|
||||
else if (zoneinfo_entry.is_regular_file() && !zoneinfo_entry.path().has_extension()
|
||||
&& zoneinfo_entry.path().filename() != "leapseconds")
|
||||
{
|
||||
accumulator.emplace_back(new_prefix);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<Glib::ustring> list_timezones()
|
||||
{
|
||||
std::vector<Glib::ustring> result;
|
||||
|
||||
list_timezones("", result);
|
||||
return result;
|
||||
}
|
||||
|
||||
Glib::ustring timezone()
|
||||
{
|
||||
std::unique_ptr<gchar[], decltype(&g_free)> zone_copied_from(
|
||||
g_file_read_link("/etc/localtime", NULL), &g_free);
|
||||
|
||||
if (zone_copied_from == nullptr)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
return Glib::ustring{ zone_copied_from.get() + zoneinfo_database.size() + 1 };
|
||||
}
|
||||
|
||||
void set_timezone(const Glib::ustring& zone, bool)
|
||||
{
|
||||
auto zone_file = std::filesystem::path("/usr/share/zoneinfo") / zone.data();
|
||||
std::error_code ec{};
|
||||
|
||||
if (!std::filesystem::is_regular_file(zone_file, ec))
|
||||
{
|
||||
throw std::filesystem::filesystem_error("Zone info is not a regular file", zone_file, ec);
|
||||
}
|
||||
std::filesystem::path etc_localtime = "/etc/localtime";
|
||||
|
||||
std::filesystem::remove(etc_localtime);
|
||||
std::filesystem::create_symlink(zone_file, etc_localtime);
|
||||
}
|
||||
|
||||
void set_ntp(bool use_ntp, bool)
|
||||
{
|
||||
try
|
||||
{
|
||||
std::string service_state = use_ntp ? "-u" : "-d";
|
||||
const auto command_flags = Glib::SpawnFlags::SPAWN_SEARCH_PATH
|
||||
| Glib::SpawnFlags::SPAWN_STDERR_TO_DEV_NULL | Glib::SpawnFlags::SPAWN_STDERR_TO_DEV_NULL;
|
||||
int wait_status{-1};
|
||||
|
||||
Glib::spawn_sync("", std::vector<std::string>{ "s6-rc", service_state, "change", "ntpd" },
|
||||
command_flags, {}, nullptr, nullptr, &wait_status);
|
||||
|
||||
if (wait_status == 0 || wait_status == 0x300) // Unknown service name in the arguments.
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
catch (const Glib::SpawnError& spawn_error)
|
||||
{
|
||||
}
|
||||
std::filesystem::perms rc_mode = std::filesystem::perms::owner_read | std::filesystem::perms::owner_write
|
||||
| std::filesystem::perms::group_read | std::filesystem::perms::others_read;
|
||||
|
||||
if (use_ntp)
|
||||
{
|
||||
rc_mode |= std::filesystem::perms::owner_exec
|
||||
| std::filesystem::perms::group_exec | std::filesystem::perms::others_exec;
|
||||
}
|
||||
std::error_code ec;
|
||||
const std::filesystem::path service_path{ "/etc/rc.d/rc.ntpd" };
|
||||
|
||||
if (std::filesystem::is_regular_file(service_path, ec))
|
||||
{
|
||||
std::filesystem::permissions("/etc/rc.d/rc.ntpd", rc_mode);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw std::filesystem::filesystem_error("The NTP daemon isn't installed", service_path, ec);
|
||||
}
|
||||
}
|
||||
|
||||
void set_time(std::int64_t usec_utc, bool relative, bool)
|
||||
{
|
||||
timespec ts;
|
||||
std::chrono::system_clock::duration current_time_since_epoch;
|
||||
|
||||
if (relative)
|
||||
{
|
||||
current_time_since_epoch = std::chrono::system_clock::now().time_since_epoch();
|
||||
current_time_since_epoch += std::chrono::microseconds(usec_utc);
|
||||
}
|
||||
else
|
||||
{
|
||||
current_time_since_epoch = std::chrono::microseconds(usec_utc);
|
||||
}
|
||||
auto seconds_since_epoch = std::chrono::floor<std::chrono::seconds>(current_time_since_epoch);
|
||||
|
||||
ts.tv_sec = seconds_since_epoch.count();
|
||||
ts.tv_nsec = std::chrono::duration_cast<std::chrono::microseconds>(
|
||||
current_time_since_epoch - seconds_since_epoch).count();
|
||||
|
||||
if (clock_settime(CLOCK_REALTIME, &ts) == -1)
|
||||
{
|
||||
throw std::system_error(errno, std::generic_category());
|
||||
}
|
||||
}
|
||||
|
||||
void set_local_rtc(bool local_rtc, bool fix_system, bool)
|
||||
{
|
||||
std::ofstream hardwareclock{ "/etc/hardwareclock", std::ios::trunc };
|
||||
std::string hwclock_argument;
|
||||
|
||||
hardwareclock << "# /etc/hardwareclock" << std::endl
|
||||
<< "#" << std::endl
|
||||
<< "# Tells how the hardware clock time is stored." << std::endl
|
||||
<< "# You should run timeconfig to edit this file." << std::endl
|
||||
<< std::endl;
|
||||
|
||||
if (local_rtc)
|
||||
{
|
||||
hwclock_argument = "--localtime";
|
||||
hardwareclock << "localtime";
|
||||
}
|
||||
else
|
||||
{
|
||||
hwclock_argument = "--utc";
|
||||
hardwareclock << "UTC";
|
||||
}
|
||||
hardwareclock << std::endl;
|
||||
|
||||
if (!fix_system)
|
||||
{
|
||||
Glib::spawn_sync("", std::vector<std::string>{ "/sbin/hwclock", hwclock_argument, "--hctosys" },
|
||||
Glib::SpawnFlags::SPAWN_STDOUT_TO_DEV_NULL | Glib::SpawnFlags::SPAWN_STDERR_TO_DEV_NULL,
|
||||
{}, nullptr, nullptr, static_cast<int *>(nullptr));
|
||||
}
|
||||
}
|
||||
|
||||
bool local_rtc()
|
||||
{
|
||||
std::ifstream fh{ "/etc/hardwareclock", std::ios::in };
|
||||
if (!fh.is_open())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
std::array<char, 10> time_str; // "localtime" is longer than "UTC" and has 9 letters + \0
|
||||
|
||||
while (fh.good())
|
||||
{
|
||||
fh.getline(time_str.data(), time_str.size());
|
||||
fh.clear(fh.rdstate() & (~std::ios_base::failbit));
|
||||
|
||||
if (std::strncmp(time_str.data(), "localtime", time_str.size()) == 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ntp()
|
||||
{
|
||||
return Glib::file_test("/etc/rc.d/rc.ntpd", Glib::FileTest::FILE_TEST_IS_EXECUTABLE);
|
||||
}
|
||||
|
||||
bool can_ntp()
|
||||
{
|
||||
std::string standard_output;
|
||||
int wait_status{-1};
|
||||
|
||||
try
|
||||
{
|
||||
Glib::spawn_sync("", std::vector<std::string>{ "s6-rc-db", "type", "ntpd" },
|
||||
Glib::SpawnFlags::SPAWN_SEARCH_PATH | Glib::SpawnFlags::SPAWN_STDERR_TO_DEV_NULL,
|
||||
{}, &standard_output, nullptr, &wait_status);
|
||||
}
|
||||
catch (const Glib::SpawnError& spawn_error)
|
||||
{
|
||||
return std::filesystem::exists("/etc/rc.d/rc.ntpd");
|
||||
}
|
||||
return wait_status == 0 && standard_output == "bundle\n";
|
||||
}
|
||||
|
||||
bool ntp_synchronized()
|
||||
{
|
||||
timex buffer{ .modes = 0 };
|
||||
int ntp_result = adjtimex(&buffer);
|
||||
|
||||
if (ntp_result == -1)
|
||||
{
|
||||
throw std::system_error(errno, std::generic_category());
|
||||
}
|
||||
return ntp_result != TIME_ERROR && (buffer.status & STA_UNSYNC) == 0;
|
||||
}
|
||||
|
||||
std::uint64_t time_usec()
|
||||
{
|
||||
timespec spec;
|
||||
|
||||
if (clock_gettime(CLOCK_REALTIME, &spec) == -1)
|
||||
{
|
||||
throw std::system_error(errno, std::generic_category());
|
||||
}
|
||||
time_t seconds = spec.tv_sec;
|
||||
tm time;
|
||||
|
||||
if (local_rtc())
|
||||
{
|
||||
gmtime_r(&seconds, &time);
|
||||
}
|
||||
else
|
||||
{
|
||||
localtime_r(&seconds, &time);
|
||||
}
|
||||
return std::chrono::duration_cast<std::chrono::microseconds>(
|
||||
std::chrono::seconds(std::mktime(&time)) + std::chrono::nanoseconds(spec.tv_nsec)).count();
|
||||
}
|
||||
|
||||
std::uint64_t rtc_time_usec()
|
||||
{
|
||||
return std::chrono::duration_cast<std::chrono::microseconds>(
|
||||
std::chrono::system_clock::now().time_since_epoch()).count();
|
||||
}
|
||||
}
|
81
src/timedate.h
Normal file
81
src/timedate.h
Normal file
@ -0,0 +1,81 @@
|
||||
/*
|
||||
* Copyright (C) 2013-2024 Eugen Wissner <belka@caraus.de>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <dbus/dbus.h>
|
||||
#include <glibmm.h>
|
||||
#include <giomm.h>
|
||||
#include "resources.h"
|
||||
|
||||
namespace dlackware::timedate
|
||||
{
|
||||
constexpr const char *bus_path = "/org/freedesktop/timedate1";
|
||||
constexpr const std::string_view bus_name = "org.freedesktop.timedate1";
|
||||
constexpr const std::string_view zoneinfo_database = "/usr/share/zoneinfo";
|
||||
|
||||
// Returns the system time zone.
|
||||
Glib::ustring timezone();
|
||||
|
||||
// Returns if the hardware clock is set to local time or not
|
||||
bool local_rtc();
|
||||
|
||||
// Returns whether NTP is enabled.
|
||||
bool ntp();
|
||||
|
||||
// Returns whether NTP is available.
|
||||
bool can_ntp();
|
||||
|
||||
// Shows whether the kernel reports the time as synchronized.
|
||||
bool ntp_synchronized();
|
||||
|
||||
// Shows the current time in the RTC.
|
||||
std::uint64_t rtc_time_usec();
|
||||
|
||||
// Shows the current time on the system.
|
||||
std::uint64_t time_usec();
|
||||
|
||||
// Returns the timezones available on the system.
|
||||
std::vector<Glib::ustring> list_timezones();
|
||||
|
||||
// Sets the system time zone to the one passed by the argument
|
||||
// Throws std::filesystem::filesystem_error.
|
||||
void set_timezone(const Glib::ustring& zone, bool user_interaction);
|
||||
|
||||
// Sets NTP
|
||||
// Throws std::filesystem::filesystem_error.
|
||||
void set_ntp(bool use_ntp, bool user_interaction);
|
||||
|
||||
// Changes the date/time
|
||||
// Takes the amount of seconds since UNIX epoche and
|
||||
// Throws std::system_error.
|
||||
void set_time(std::int64_t usec_utc, bool relative, bool user_interaction);
|
||||
|
||||
// Controls whether the RTC is local time or UTC.
|
||||
void set_local_rtc(bool local_rtc, bool fix_system, bool user_interaction);
|
||||
|
||||
class timedate1
|
||||
{
|
||||
const Gio::DBus::InterfaceVTable interface_vtable;
|
||||
|
||||
public:
|
||||
timedate1();
|
||||
|
||||
void on_bus_acquired(const Glib::RefPtr<Gio::DBus::Connection>& connection, const Glib::ustring& name);
|
||||
void on_name_lost(const Glib::RefPtr<Gio::DBus::Connection>& connection, const Glib::ustring& name);
|
||||
};
|
||||
}
|
Reference in New Issue
Block a user