The Battle for Wesnoth  1.13.10+dev
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Modules Pages
dbus_notification.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2014 - 2017 by Chris Beck <render787@gmail.com>
3  Part of the Battle for Wesnoth Project http://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_notification.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 <string>
32 
33 #pragma GCC diagnostic ignored "-Wold-style-cast"
34 
35 static lg::log_domain log_desktop("desktop");
36 #define ERR_DU LOG_STREAM(err, log_desktop)
37 #define LOG_DU LOG_STREAM(info, log_desktop)
38 #define DBG_DU LOG_STREAM(info, log_desktop)
39 
40 namespace { // anonymous namespace
41 
42 /** Use KDE 4 notifications. */
43 bool kde_style = false;
44 
45 struct wnotify
46 {
47  wnotify(uint32_t id_arg, std::string owner_arg, std::string message_arg)
48  : id(id_arg)
49  , owner(owner_arg)
50  , message(message_arg)
51  {
52  }
53 
54  uint32_t id;
55  std::string owner;
56  mutable std::string message;
57 };
58 
59 struct by_id {};
60 struct by_owner {};
61 
62 using boost::multi_index::hashed_unique;
63 using boost::multi_index::indexed_by;
64 using boost::multi_index::tag;
65 using boost::multi_index::member;
66 
67 typedef boost::multi_index_container<
68  wnotify,
69  indexed_by<
70  //hashed by ids
71  hashed_unique<tag<by_id>, member<wnotify,const uint32_t,&wnotify::id> >,
72  //hashed by owners
73  hashed_unique<tag<by_owner>, member<wnotify,const std::string,&wnotify::owner> >
74  >
75 > wnotify_set;
76 
77 typedef wnotify_set::index<by_owner>::type wnotify_by_owner;
78 typedef wnotify_by_owner::iterator wnotify_owner_it;
79 
80 wnotify_set notifications; //!< Holds all the notifications transaction records
81 
82 DBusHandlerResult filter_dbus_signal(DBusConnection *, DBusMessage *buf, void *)
83 {
84  if (!dbus_message_is_signal(buf, "org.freedesktop.Notifications", "NotificationClosed")) {
85  return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
86  }
87 
88  uint32_t id;
89  dbus_message_get_args(buf, nullptr,
90  DBUS_TYPE_UINT32, &id,
91  DBUS_TYPE_INVALID);
92 
93  size_t num_erased = notifications.get<by_id>().erase(id);
94  LOG_DU << "Erased " << num_erased << " notifications records matching id=" << id;
95 
96  return DBUS_HANDLER_RESULT_HANDLED;
97 }
98 
99 DBusConnection *get_dbus_connection()
100 {
101  static bool initted = false;
102  static DBusConnection *connection = nullptr;
103 
104  if (!initted)
105  {
106  initted = true;
107  if (getenv("KDE_SESSION_VERSION")) {
108  // This variable is defined for KDE 4 only.
109  kde_style = true;
110  }
111  DBusError err;
112  dbus_error_init(&err);
113  connection = dbus_bus_get(DBUS_BUS_SESSION, &err);
114  if (!connection) {
115  ERR_DU << "Failed to open DBus session: " << err.message << '\n';
116  dbus_error_free(&err);
117  return nullptr;
118  }
119  dbus_connection_add_filter(connection, filter_dbus_signal, nullptr, nullptr);
120  }
121  if (connection) {
122  dbus_connection_read_write(connection, 0);
123  while (dbus_connection_dispatch(connection) == DBUS_DISPATCH_DATA_REMAINS) {}
124  }
125  return connection;
126 }
127 
128 uint32_t send_dbus_notification(DBusConnection *connection, uint32_t replaces_id,
129  const std::string &owner, const std::string &message)
130 {
131  DBusMessage *buf = dbus_message_new_method_call(
132  kde_style ? "org.kde.VisualNotifications" : "org.freedesktop.Notifications",
133  kde_style ? "/VisualNotifications" : "/org/freedesktop/Notifications",
134  kde_style ? "org.kde.VisualNotifications" : "org.freedesktop.Notifications",
135  "Notify");
136  const char *app_name = "Battle for Wesnoth";
137  dbus_message_append_args(buf,
138  DBUS_TYPE_STRING, &app_name,
139  DBUS_TYPE_UINT32, &replaces_id,
140  DBUS_TYPE_INVALID);
141  if (kde_style) {
142  const char *event_id = "";
143  dbus_message_append_args(buf,
144  DBUS_TYPE_STRING, &event_id,
145  DBUS_TYPE_INVALID);
146  }
147 
149  if (!filesystem::file_exists(app_icon_)) {
150  ERR_DU << "Error: Could not find notification icon.\n"
151  << "raw path =\'" << game_config::path << "\' / \'" << game_config::images::app_icon << "\'\n"
152  << "normalized path =\'" << app_icon_ << "\'\n";
153  } else {
154  DBG_DU << "app_icon_=\'" << app_icon_ << "\'\n";
155  }
156 
157  const char *app_icon = app_icon_.c_str();
158  const char *summary = owner.c_str();
159  const char *body = message.c_str();
160  const char **actions = nullptr;
161  dbus_message_append_args(buf,
162  DBUS_TYPE_STRING, &app_icon,
163  DBUS_TYPE_STRING, &summary,
164  DBUS_TYPE_STRING, &body,
165  DBUS_TYPE_ARRAY, DBUS_TYPE_STRING, &actions, 0,
166  DBUS_TYPE_INVALID);
167  DBusMessageIter iter, hints;
168  dbus_message_iter_init_append(buf, &iter);
169  dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sv}", &hints);
170  dbus_message_iter_close_container(&iter, &hints);
171  int expire_timeout = kde_style ? 5000 : -1;
172  dbus_message_append_args(buf,
173  DBUS_TYPE_INT32, &expire_timeout,
174  DBUS_TYPE_INVALID);
175  DBusError err;
176  dbus_error_init(&err);
177  DBusMessage *ret = dbus_connection_send_with_reply_and_block(connection, buf, 1000, &err);
178  dbus_message_unref(buf);
179  if (!ret) {
180  ERR_DU << "Failed to send visual notification: " << err.message << '\n';
181  dbus_error_free(&err);
182  if (kde_style) {
183  ERR_DU << " Retrying with the freedesktop protocol." << std::endl;
184  kde_style = false;
185  return send_dbus_notification(connection, replaces_id, owner, message);
186  }
187  return 0;
188  }
189  uint32_t id;
190  dbus_message_get_args(ret, nullptr,
191  DBUS_TYPE_UINT32, &id,
192  DBUS_TYPE_INVALID);
193  dbus_message_unref(ret);
194  // TODO: remove once closing signals for KDE are handled in filter_dbus_signal.
195  if (kde_style) return 0;
196  return id;
197 }
198 
199 } // end anonymous namespace
200 
201 namespace dbus {
202 
203 const size_t MAX_MSG_LINES = 5;
204 
205 void send_notification(const std::string & owner, const std::string & message, bool with_history)
206 {
207  DBusConnection *connection = get_dbus_connection();
208  if (!connection) return;
209 
210  wnotify_by_owner & noticias = notifications.get<by_owner>();
211 
212  wnotify_owner_it i = noticias.find(owner);
213 
214  if (i != noticias.end()) {
215  if (with_history) {
216  i->message = message + "\n" + i->message;
217 
218  size_t endl_pos = i->message.find('\n');
219  size_t ctr = 1;
220 
221  while (ctr < MAX_MSG_LINES && endl_pos != std::string::npos) {
222  endl_pos = i->message.find('\n', endl_pos+1);
223  ctr++;
224  }
225 
226  i->message = i->message.substr(0,endl_pos);
227  } else {
228  i->message = message;
229  }
230 
231  send_dbus_notification(connection, i->id, owner, i->message);
232  return;
233  } else {
234  uint32_t id = send_dbus_notification(connection, 0, owner, message);
235  if (!id) return;
236  wnotify visual(id,owner,message);
237  std::pair<wnotify_owner_it, bool> result = noticias.insert(visual);
238  if (!result.second) {
239  ERR_DU << "Failed to insert a dbus notification message:\n"
240  << "New Item:\n" << "\tid=" << id << "\n\towner=" << owner << "\n\tmessage=" << message << "\n"
241  << "Old Item:\n" << "\tid=" << result.first->id << "\n\towner=" << result.first->owner << "\n\tmessage=" << result.first->message << "\n";
242  }
243  }
244 }
245 
246 } // end namespace dbus
247 
std::vector< char_t > string
const size_t MAX_MSG_LINES
static void body(LexState *ls, expdesc *e, int ismethod, int line)
Definition: lparser.cpp:782
std::string normalize_path(const std::string &path, bool normalize_separators=false, bool resolve_dot_entries=false)
Returns the absolute path of a file.
#define DBG_DU
void send_notification(const std::string &owner, const std::string &message, bool with_history)
#define LOG_DU
void erase(const std::string &key)
Definition: general.cpp:220
std::string path
Definition: game_config.cpp:56
logger & err()
Definition: log.cpp:79
#define i
Declarations for File-IO.
static lg::log_domain log_desktop("desktop")
#define ERR_DU
Standard logging facilities (interface).
bool file_exists(const std::string &name)
Returns true if a file or directory with such name already exists.
std::string::const_iterator iterator
Definition: tokenizer.hpp:24
std::string app_icon