The Battle for Wesnoth  1.17.0-dev
dbus_features.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2014 - 2021
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 
38 static 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 
43 namespace { // anonymous namespace
44 
45 /** Use KDE 4 notifications. */
46 bool kde_style = false;
47 
48 struct 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 
62 struct by_id {};
63 struct by_owner {};
64 
65 using boost::multi_index::hashed_unique;
66 using boost::multi_index::indexed_by;
67 using boost::multi_index::tag;
68 using boost::multi_index::member;
69 
70 typedef 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 
80 typedef wnotify_set::index<by_owner>::type wnotify_by_owner;
81 typedef wnotify_by_owner::iterator wnotify_owner_it;
82 
83 /** Holds all the notifications transaction records */
84 wnotify_set notifications;
85 
86 DBusHandlerResult 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 
103 DBusConnection *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 << '\n';
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 
132 DBusConnection *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 
147 uint32_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.\n"
170  << "raw path =\'" << game_config::path << "\' / \'" << game_config::images::app_icon << "\'\n"
171  << "normalized path =\'" << app_icon_ << "\'\n";
172  } else {
173  DBG_DU << "app_icon_=\'" << app_icon_ << "\'\n";
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 << '\n';
200  dbus_error_free(&err);
201  if (kde_style) {
202  ERR_DU << " Retrying with the freedesktop protocol." << std::endl;
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 
218 template<typename T>
219 T 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 << '\n';
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 
262 namespace dbus {
263 
264 const std::size_t MAX_MSG_LINES = 5;
265 
266 void 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:\n"
301  << "New Item:\n" << "\tid=" << id << "\n\towner=" << owner << "\n\tmessage=" << message << "\n"
302  << "Old Item:\n" << "\tid=" << result.first->id << "\n\towner=" << result.first->owner << "\n\tmessage=" << result.first->message << "\n";
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
const std::size_t MAX_MSG_LINES
static bool file_exists(const bfs::path &fpath)
Definition: filesystem.cpp:263
static void body(LexState *ls, expdesc *e, int ismethod, int line)
Definition: lparser.cpp:978
static void msg(const char *act, debug_info &i, const char *to="", const char *result="")
Definition: debugger.cpp:110
std::string normalize_path(const std::string &fpath, bool normalize_separators, bool resolve_dot_entries)
Returns the absolute path of a file.
void send_notification(const std::string &owner, const std::string &message, bool with_history)
#define ERR_DU
bool does_device_have_battery()
void erase(const std::string &key)
Definition: general.cpp:201
std::string path
Definition: game_config.cpp:39
std::string id
Text to match against addon_info.tags()
Definition: manager.cpp:215
std::size_t i
Definition: function.cpp:941
logger & err()
Definition: log.cpp:77
double get_battery_percentage()
Declarations for File-IO.
#define LOG_DU
#define DBG_DU
Standard logging facilities (interface).
static lg::log_domain log_desktop("desktop")
std::string::const_iterator iterator
Definition: tokenizer.hpp:25
std::string app_icon