The Battle for Wesnoth 1.21.0-dev
 
Loading...
Searching...
No Matches
dbus_features.cpp
Go to the documentation of this file.
1/*
2 Copyright (C) 2014 - 2025
3 by Chris Beck <render787@gmail.com>
4 Part of the Battle for Wesnoth Project https://www.wesnoth.org/
5
6 This program is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2 of the License, or
9 (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY.
12
13 See the COPYING file for more details.
14*/
15
17
18#include "filesystem.hpp"
19#include "game_config.hpp"
20#include "log.hpp"
21
22#ifndef HAVE_LIBDBUS
23#error "The HAVE_LIBDBUS symbol is not defined, you do not have lib dbus available, you should not be trying to compile dbus_features.cpp"
24#endif
25
26#include <dbus/dbus.h>
27
28#include <boost/multi_index_container.hpp>
29#include <boost/multi_index/hashed_index.hpp>
30#include <boost/multi_index/member.hpp>
31#include <cstdlib>
32#include <functional>
33#include <memory>
34#include <string>
35
36#pragma GCC diagnostic ignored "-Wold-style-cast"
37
38static lg::log_domain log_desktop("desktop");
39#define ERR_DU LOG_STREAM(err, log_desktop)
40#define LOG_DU LOG_STREAM(info, log_desktop)
41#define DBG_DU LOG_STREAM(info, log_desktop)
42
43namespace { // anonymous namespace
44
45/** Use KDE 4 notifications. */
46bool kde_style = false;
47
48struct wnotify
49{
50 wnotify(uint32_t id_arg, const std::string & owner_arg, const std::string & message_arg)
51 : id(id_arg)
52 , owner(owner_arg)
53 , message(message_arg)
54 {
55 }
56
57 uint32_t id;
58 std::string owner;
59 mutable std::string message;
60};
61
62struct by_id {};
63struct by_owner {};
64
65using boost::multi_index::hashed_unique;
66using boost::multi_index::indexed_by;
67using boost::multi_index::tag;
68using boost::multi_index::member;
69
70typedef boost::multi_index_container<
71 wnotify,
72 indexed_by<
73 //hashed by ids
74 hashed_unique<tag<by_id>, member<wnotify,const uint32_t,&wnotify::id>>,
75 //hashed by owners
76 hashed_unique<tag<by_owner>, member<wnotify,const std::string,&wnotify::owner>>
77 >
78> wnotify_set;
79
80typedef wnotify_set::index<by_owner>::type wnotify_by_owner;
81typedef wnotify_by_owner::iterator wnotify_owner_it;
82
83/** Holds all the notifications transaction records */
84wnotify_set notifications;
85
86DBusHandlerResult filter_dbus_signal(DBusConnection *, DBusMessage *buf, void *)
87{
88 if (!dbus_message_is_signal(buf, "org.freedesktop.Notifications", "NotificationClosed")) {
89 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
90 }
91
92 uint32_t id;
93 dbus_message_get_args(buf, nullptr,
94 DBUS_TYPE_UINT32, &id,
95 DBUS_TYPE_INVALID);
96
97 std::size_t num_erased = notifications.get<by_id>().erase(id);
98 LOG_DU << "Erased " << num_erased << " notifications records matching id=" << id;
99
100 return DBUS_HANDLER_RESULT_HANDLED;
101}
102
103DBusConnection *get_dbus_session_bus()
104{
105 static bool initted = false;
106 static DBusConnection *connection = nullptr;
107
108 if (!initted)
109 {
110 initted = true;
111 if (getenv("KDE_SESSION_VERSION")) {
112 // This variable is defined for KDE 4 only.
113 kde_style = true;
114 }
115 DBusError err;
116 dbus_error_init(&err);
117 connection = dbus_bus_get(DBUS_BUS_SESSION, &err);
118 if (!connection) {
119 ERR_DU << "Failed to open DBus session: " << err.message;
120 dbus_error_free(&err);
121 return nullptr;
122 }
123 dbus_connection_add_filter(connection, filter_dbus_signal, nullptr, nullptr);
124 }
125 if (connection) {
126 dbus_connection_read_write(connection, 0);
127 while (dbus_connection_dispatch(connection) == DBUS_DISPATCH_DATA_REMAINS) {}
128 }
129 return connection;
130}
131
132DBusConnection *get_dbus_system_bus()
133{
134 static DBusConnection *connection = nullptr;
135
136 if (connection == nullptr)
137 {
138 DBusError err;
139 dbus_error_init(&err);
140 connection = dbus_bus_get(DBUS_BUS_SYSTEM, &err);
141 dbus_error_free(&err);
142 }
143
144 return connection;
145}
146
147uint32_t send_dbus_notification(DBusConnection *connection, uint32_t replaces_id,
148 const std::string &owner, const std::string &message)
149{
150 DBusMessage *buf = dbus_message_new_method_call(
151 kde_style ? "org.kde.VisualNotifications" : "org.freedesktop.Notifications",
152 kde_style ? "/VisualNotifications" : "/org/freedesktop/Notifications",
153 kde_style ? "org.kde.VisualNotifications" : "org.freedesktop.Notifications",
154 "Notify");
155 const char *app_name = "Battle for Wesnoth";
156 dbus_message_append_args(buf,
157 DBUS_TYPE_STRING, &app_name,
158 DBUS_TYPE_UINT32, &replaces_id,
159 DBUS_TYPE_INVALID);
160 if (kde_style) {
161 const char *event_id = "";
162 dbus_message_append_args(buf,
163 DBUS_TYPE_STRING, &event_id,
164 DBUS_TYPE_INVALID);
165 }
166
168 if (!filesystem::file_exists(app_icon_)) {
169 ERR_DU << "Error: Could not find notification icon.";
170 ERR_DU << "raw path =\'" << game_config::path << "\' / \'" << game_config::images::app_icon << "\'";
171 ERR_DU << "normalized path =\'" << app_icon_ << "\'";
172 } else {
173 DBG_DU << "app_icon_=\'" << app_icon_ << "\'";
174 }
175
176 const char *app_icon = app_icon_.c_str();
177 const char *summary = owner.c_str();
178 const char *body = message.c_str();
179 const char **actions = nullptr;
180 dbus_message_append_args(buf,
181 DBUS_TYPE_STRING, &app_icon,
182 DBUS_TYPE_STRING, &summary,
183 DBUS_TYPE_STRING, &body,
184 DBUS_TYPE_ARRAY, DBUS_TYPE_STRING, &actions, 0,
185 DBUS_TYPE_INVALID);
186 DBusMessageIter iter, hints;
187 dbus_message_iter_init_append(buf, &iter);
188 dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sv}", &hints);
189 dbus_message_iter_close_container(&iter, &hints);
190 int expire_timeout = kde_style ? 5000 : -1;
191 dbus_message_append_args(buf,
192 DBUS_TYPE_INT32, &expire_timeout,
193 DBUS_TYPE_INVALID);
194 DBusError err;
195 dbus_error_init(&err);
196 DBusMessage *ret = dbus_connection_send_with_reply_and_block(connection, buf, 1000, &err);
197 dbus_message_unref(buf);
198 if (!ret) {
199 ERR_DU << "Failed to send visual notification: " << err.message;
200 dbus_error_free(&err);
201 if (kde_style) {
202 ERR_DU << " Retrying with the freedesktop protocol.";
203 kde_style = false;
204 return send_dbus_notification(connection, replaces_id, owner, message);
205 }
206 return 0;
207 }
208 uint32_t id;
209 dbus_message_get_args(ret, nullptr,
210 DBUS_TYPE_UINT32, &id,
211 DBUS_TYPE_INVALID);
212 dbus_message_unref(ret);
213 // TODO: remove once closing signals for KDE are handled in filter_dbus_signal.
214 if (kde_style) return 0;
215 return id;
216}
217
218template<typename T>
219T get_power_source_property(const std::string &name, T fallback)
220{
221 DBusConnection *connection = get_dbus_system_bus();
222 if (!connection) return fallback;
223
224 std::unique_ptr<DBusMessage, std::function<void(DBusMessage*)>> msg(dbus_message_new_method_call(
225 "org.freedesktop.UPower",
226 "/org/freedesktop/UPower/devices/DisplayDevice",
227 "org.freedesktop.DBus.Properties",
228 "Get"),
229 dbus_message_unref);
230
231 const char *interface_name = "org.freedesktop.UPower.Device";
232 const char *property_name = name.data();
233 dbus_message_append_args(msg.get(),
234 DBUS_TYPE_STRING, &interface_name,
235 DBUS_TYPE_STRING, &property_name,
236 DBUS_TYPE_INVALID);
237
238 DBusError err;
239 dbus_error_init(&err);
240
241 std::unique_ptr<DBusMessage, std::function<void(DBusMessage*)>> ret(dbus_connection_send_with_reply_and_block(
242 connection, msg.get(), 1000, &err), dbus_message_unref);
243 if (ret == nullptr) {
244 DBG_DU << "Failed to query power source properties: " << err.message;
245 dbus_error_free(&err);
246 return fallback;
247 }
248
249 // The value is returned as a variant. We need to recurse into it to read the underlying value.
250 DBusMessageIter iter;
251 dbus_message_iter_init(ret.get(), &iter);
252 DBusMessageIter sub_iter;
253 dbus_message_iter_recurse(&iter, &sub_iter);
254 T value;
255 dbus_message_iter_get_basic(&sub_iter, &value);
256
257 return value;
258}
259
260} // end anonymous namespace
261
262namespace dbus {
263
264const std::size_t MAX_MSG_LINES = 5;
265
266void send_notification(const std::string & owner, const std::string & message, bool with_history)
267{
268 DBusConnection *connection = get_dbus_session_bus();
269 if (!connection) return;
270
271 wnotify_by_owner & noticias = notifications.get<by_owner>();
272
273 wnotify_owner_it i = noticias.find(owner);
274
275 if (i != noticias.end()) {
276 if (with_history) {
277 i->message = message + "\n" + i->message;
278
279 std::size_t endl_pos = i->message.find('\n');
280 std::size_t ctr = 1;
281
282 while (ctr < MAX_MSG_LINES && endl_pos != std::string::npos) {
283 endl_pos = i->message.find('\n', endl_pos+1);
284 ctr++;
285 }
286
287 i->message = i->message.substr(0,endl_pos);
288 } else {
289 i->message = message;
290 }
291
292 send_dbus_notification(connection, i->id, owner, i->message);
293 return;
294 } else {
295 uint32_t id = send_dbus_notification(connection, 0, owner, message);
296 if (!id) return;
297 wnotify visual(id,owner,message);
298 std::pair<wnotify_owner_it, bool> result = noticias.insert(visual);
299 if (!result.second) {
300 ERR_DU << "Failed to insert a dbus notification message:";
301 ERR_DU << "New Item:\n" << "\tid=" << id << "\n\towner=" << owner << "\n\tmessage=" << message;
302 ERR_DU << "Old Item:\n" << "\tid=" << result.first->id << "\n\towner=" << result.first->owner << "\n\tmessage=" << result.first->message;
303 }
304 }
305}
306
308{
309 return get_power_source_property<uint32_t>("Type", 0) == 2;
310}
311
313{
314 return get_power_source_property<double>("Percentage", 0.0);
315}
316
317} // end namespace dbus
#define DBG_DU
static lg::log_domain log_desktop("desktop")
#define LOG_DU
#define ERR_DU
Declarations for File-IO.
std::string id
Text to match against addon_info.tags()
Definition manager.cpp:199
Standard logging facilities (interface).
void send_notification(const std::string &owner, const std::string &message, bool with_history)
double get_battery_percentage()
const std::size_t MAX_MSG_LINES
bool does_device_have_battery()
bool file_exists(const std::string &name)
Returns true if a file or directory with such name already exists.
std::string normalize_path(const std::string &path, bool normalize_separators=false, bool resolve_dot_entries=false)
Returns the absolute path of a file.
std::string path
std::unique_ptr< MIX_Audio, decltype(&MIX_DestroyAudio)> value
Definition sound.cpp:139
#define i