The Battle for Wesnoth  1.15.12+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, const std::string & owner_arg, const 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 /** Holds all the notifications transaction records */
83 wnotify_set notifications;
84 
85 DBusHandlerResult filter_dbus_signal(DBusConnection *, DBusMessage *buf, void *)
86 {
87  if (!dbus_message_is_signal(buf, "org.freedesktop.Notifications", "NotificationClosed")) {
88  return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
89  }
90 
91  uint32_t id;
92  dbus_message_get_args(buf, nullptr,
93  DBUS_TYPE_UINT32, &id,
94  DBUS_TYPE_INVALID);
95 
96  std::size_t num_erased = notifications.get<by_id>().erase(id);
97  LOG_DU << "Erased " << num_erased << " notifications records matching id=" << id;
98 
99  return DBUS_HANDLER_RESULT_HANDLED;
100 }
101 
102 DBusConnection *get_dbus_session_bus()
103 {
104  static bool initted = false;
105  static DBusConnection *connection = nullptr;
106 
107  if (!initted)
108  {
109  initted = true;
110  if (getenv("KDE_SESSION_VERSION")) {
111  // This variable is defined for KDE 4 only.
112  kde_style = true;
113  }
114  DBusError err;
115  dbus_error_init(&err);
116  connection = dbus_bus_get(DBUS_BUS_SESSION, &err);
117  if (!connection) {
118  ERR_DU << "Failed to open DBus session: " << err.message << '\n';
119  dbus_error_free(&err);
120  return nullptr;
121  }
122  dbus_connection_add_filter(connection, filter_dbus_signal, nullptr, nullptr);
123  }
124  if (connection) {
125  dbus_connection_read_write(connection, 0);
126  while (dbus_connection_dispatch(connection) == DBUS_DISPATCH_DATA_REMAINS) {}
127  }
128  return connection;
129 }
130 
131 DBusConnection *get_dbus_system_bus()
132 {
133  static DBusConnection *connection = nullptr;
134 
135  if (connection == nullptr)
136  {
137  DBusError err;
138  dbus_error_init(&err);
139  connection = dbus_bus_get(DBUS_BUS_SYSTEM, &err);
140  dbus_error_free(&err);
141  }
142 
143  return connection;
144 }
145 
146 uint32_t send_dbus_notification(DBusConnection *connection, uint32_t replaces_id,
147  const std::string &owner, const std::string &message)
148 {
149  DBusMessage *buf = dbus_message_new_method_call(
150  kde_style ? "org.kde.VisualNotifications" : "org.freedesktop.Notifications",
151  kde_style ? "/VisualNotifications" : "/org/freedesktop/Notifications",
152  kde_style ? "org.kde.VisualNotifications" : "org.freedesktop.Notifications",
153  "Notify");
154  const char *app_name = "Battle for Wesnoth";
155  dbus_message_append_args(buf,
156  DBUS_TYPE_STRING, &app_name,
157  DBUS_TYPE_UINT32, &replaces_id,
158  DBUS_TYPE_INVALID);
159  if (kde_style) {
160  const char *event_id = "";
161  dbus_message_append_args(buf,
162  DBUS_TYPE_STRING, &event_id,
163  DBUS_TYPE_INVALID);
164  }
165 
167  if (!filesystem::file_exists(app_icon_)) {
168  ERR_DU << "Error: Could not find notification icon.\n"
169  << "raw path =\'" << game_config::path << "\' / \'" << game_config::images::app_icon << "\'\n"
170  << "normalized path =\'" << app_icon_ << "\'\n";
171  } else {
172  DBG_DU << "app_icon_=\'" << app_icon_ << "\'\n";
173  }
174 
175  const char *app_icon = app_icon_.c_str();
176  const char *summary = owner.c_str();
177  const char *body = message.c_str();
178  const char **actions = nullptr;
179  dbus_message_append_args(buf,
180  DBUS_TYPE_STRING, &app_icon,
181  DBUS_TYPE_STRING, &summary,
182  DBUS_TYPE_STRING, &body,
183  DBUS_TYPE_ARRAY, DBUS_TYPE_STRING, &actions, 0,
184  DBUS_TYPE_INVALID);
185  DBusMessageIter iter, hints;
186  dbus_message_iter_init_append(buf, &iter);
187  dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sv}", &hints);
188  dbus_message_iter_close_container(&iter, &hints);
189  int expire_timeout = kde_style ? 5000 : -1;
190  dbus_message_append_args(buf,
191  DBUS_TYPE_INT32, &expire_timeout,
192  DBUS_TYPE_INVALID);
193  DBusError err;
194  dbus_error_init(&err);
195  DBusMessage *ret = dbus_connection_send_with_reply_and_block(connection, buf, 1000, &err);
196  dbus_message_unref(buf);
197  if (!ret) {
198  ERR_DU << "Failed to send visual notification: " << err.message << '\n';
199  dbus_error_free(&err);
200  if (kde_style) {
201  ERR_DU << " Retrying with the freedesktop protocol." << std::endl;
202  kde_style = false;
203  return send_dbus_notification(connection, replaces_id, owner, message);
204  }
205  return 0;
206  }
207  uint32_t id;
208  dbus_message_get_args(ret, nullptr,
209  DBUS_TYPE_UINT32, &id,
210  DBUS_TYPE_INVALID);
211  dbus_message_unref(ret);
212  // TODO: remove once closing signals for KDE are handled in filter_dbus_signal.
213  if (kde_style) return 0;
214  return id;
215 }
216 
217 template<typename T>
218 T get_power_source_property(const std::string &name, T fallback)
219 {
220  DBusConnection *connection = get_dbus_system_bus();
221  if (!connection) return fallback;
222 
223  std::unique_ptr<DBusMessage, std::function<void(DBusMessage*)>> msg(dbus_message_new_method_call(
224  "org.freedesktop.UPower",
225  "/org/freedesktop/UPower/devices/DisplayDevice",
226  "org.freedesktop.DBus.Properties",
227  "Get"),
228  dbus_message_unref);
229 
230  const char *interface_name = "org.freedesktop.UPower.Device";
231  const char *property_name = name.data();
232  dbus_message_append_args(msg.get(),
233  DBUS_TYPE_STRING, &interface_name,
234  DBUS_TYPE_STRING, &property_name,
235  DBUS_TYPE_INVALID);
236 
237  DBusError err;
238  dbus_error_init(&err);
239 
240  std::unique_ptr<DBusMessage, std::function<void(DBusMessage*)>> ret(dbus_connection_send_with_reply_and_block(
241  connection, msg.get(), 1000, &err), dbus_message_unref);
242  if (ret == nullptr) {
243  DBG_DU << "Failed to query power source properties: " << err.message << '\n';
244  dbus_error_free(&err);
245  return fallback;
246  }
247 
248  // The value is returned as a variant. We need to recurse into it to read the underlying value.
249  DBusMessageIter iter;
250  dbus_message_iter_init(ret.get(), &iter);
251  DBusMessageIter sub_iter;
252  dbus_message_iter_recurse(&iter, &sub_iter);
253  T value;
254  dbus_message_iter_get_basic(&sub_iter, &value);
255 
256  return value;
257 }
258 
259 } // end anonymous namespace
260 
261 namespace dbus {
262 
263 const std::size_t MAX_MSG_LINES = 5;
264 
265 void send_notification(const std::string & owner, const std::string & message, bool with_history)
266 {
267  DBusConnection *connection = get_dbus_session_bus();
268  if (!connection) return;
269 
270  wnotify_by_owner & noticias = notifications.get<by_owner>();
271 
272  wnotify_owner_it i = noticias.find(owner);
273 
274  if (i != noticias.end()) {
275  if (with_history) {
276  i->message = message + "\n" + i->message;
277 
278  std::size_t endl_pos = i->message.find('\n');
279  std::size_t ctr = 1;
280 
281  while (ctr < MAX_MSG_LINES && endl_pos != std::string::npos) {
282  endl_pos = i->message.find('\n', endl_pos+1);
283  ctr++;
284  }
285 
286  i->message = i->message.substr(0,endl_pos);
287  } else {
288  i->message = message;
289  }
290 
291  send_dbus_notification(connection, i->id, owner, i->message);
292  return;
293  } else {
294  uint32_t id = send_dbus_notification(connection, 0, owner, message);
295  if (!id) return;
296  wnotify visual(id,owner,message);
297  std::pair<wnotify_owner_it, bool> result = noticias.insert(visual);
298  if (!result.second) {
299  ERR_DU << "Failed to insert a dbus notification message:\n"
300  << "New Item:\n" << "\tid=" << id << "\n\towner=" << owner << "\n\tmessage=" << message << "\n"
301  << "Old Item:\n" << "\tid=" << result.first->id << "\n\towner=" << result.first->owner << "\n\tmessage=" << result.first->message << "\n";
302  }
303  }
304 }
305 
307 {
308  return get_power_source_property<uint32_t>("Type", 0) == 2;
309 }
310 
312 {
313  return get_power_source_property<double>("Percentage", 0.0);
314 }
315 
316 } // 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: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()
void erase(const std::string &key)
Definition: general.cpp:218
std::string path
Definition: game_config.cpp:38
std::size_t i
Definition: function.cpp:940
logger & err()
Definition: log.cpp:76
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