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