The Battle for Wesnoth  1.13.4+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 - 2016 by Chris Beck <[email protected]>
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 
15 #include "dbus_notification.hpp"
16 #include "global.hpp"
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_notification.cpp"
24 #endif
25 
26 #include <dbus/dbus.h>
27 
28 #include <boost/cstdint.hpp>
29 #include <boost/multi_index_container.hpp>
30 #include <boost/multi_index/hashed_index.hpp>
31 #include <boost/multi_index/member.hpp>
32 #include <stdlib.h>
33 #include <string>
34 
35 #pragma GCC diagnostic ignored "-Wold-style-cast"
36 
37 using boost::uint32_t;
38 
39 static lg::log_domain log_desktop("desktop");
40 #define ERR_DU LOG_STREAM(err, log_desktop)
41 #define LOG_DU LOG_STREAM(info, log_desktop)
42 #define DBG_DU LOG_STREAM(info, log_desktop)
43 
44 namespace { // anonymous namespace
45 
46 /** Use KDE 4 notifications. */
47 bool kde_style = false;
48 
49 struct wnotify
50 {
51  wnotify(uint32_t id_arg, std::string owner_arg, std::string message_arg)
52  : id(id_arg)
53  , owner(owner_arg)
54  , message(message_arg)
55  {
56  }
57 
58  uint32_t id;
59  std::string owner;
60  mutable std::string message;
61 };
62 
63 struct by_id {};
64 struct by_owner {};
65 
66 using boost::multi_index::hashed_unique;
67 using boost::multi_index::indexed_by;
68 using boost::multi_index::tag;
69 using boost::multi_index::member;
70 
71 typedef boost::multi_index_container<
72  wnotify,
73  indexed_by<
74  //hashed by ids
75  hashed_unique<tag<by_id>, member<wnotify,const uint32_t,&wnotify::id> >,
76  //hashed by owners
77  hashed_unique<tag<by_owner>, member<wnotify,const std::string,&wnotify::owner> >
78  >
79 > wnotify_set;
80 
81 typedef wnotify_set::index<by_owner>::type wnotify_by_owner;
82 typedef wnotify_by_owner::iterator wnotify_owner_it;
83 
84 wnotify_set notifications; //!< Holds all the notifications transaction records
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  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_connection()
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 uint32_t send_dbus_notification(DBusConnection *connection, uint32_t replaces_id,
133  const std::string &owner, const std::string &message)
134 {
135  DBusMessage *buf = dbus_message_new_method_call(
136  kde_style ? "org.kde.VisualNotifications" : "org.freedesktop.Notifications",
137  kde_style ? "/VisualNotifications" : "/org/freedesktop/Notifications",
138  kde_style ? "org.kde.VisualNotifications" : "org.freedesktop.Notifications",
139  "Notify");
140  const char *app_name = "Battle for Wesnoth";
141  dbus_message_append_args(buf,
142  DBUS_TYPE_STRING, &app_name,
143  DBUS_TYPE_UINT32, &replaces_id,
144  DBUS_TYPE_INVALID);
145  if (kde_style) {
146  const char *event_id = "";
147  dbus_message_append_args(buf,
148  DBUS_TYPE_STRING, &event_id,
149  DBUS_TYPE_INVALID);
150  }
151 
153  if (!filesystem::file_exists(app_icon_)) {
154  ERR_DU << "Error: Could not find notification icon.\n"
155  << "raw path =\'" << game_config::path << "\' / \'" << game_config::images::app_icon << "\'\n"
156  << "normalized path =\'" << app_icon_ << "\'\n";
157  } else {
158  DBG_DU << "app_icon_=\'" << app_icon_ << "\'\n";
159  }
160 
161  const char *app_icon = app_icon_.c_str();
162  const char *summary = owner.c_str();
163  const char *body = message.c_str();
164  const char **actions = nullptr;
165  dbus_message_append_args(buf,
166  DBUS_TYPE_STRING, &app_icon,
167  DBUS_TYPE_STRING, &summary,
168  DBUS_TYPE_STRING, &body,
169  DBUS_TYPE_ARRAY, DBUS_TYPE_STRING, &actions, 0,
170  DBUS_TYPE_INVALID);
171  DBusMessageIter iter, hints;
172  dbus_message_iter_init_append(buf, &iter);
173  dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sv}", &hints);
174  dbus_message_iter_close_container(&iter, &hints);
175  int expire_timeout = kde_style ? 5000 : -1;
176  dbus_message_append_args(buf,
177  DBUS_TYPE_INT32, &expire_timeout,
178  DBUS_TYPE_INVALID);
179  DBusError err;
180  dbus_error_init(&err);
181  DBusMessage *ret = dbus_connection_send_with_reply_and_block(connection, buf, 1000, &err);
182  dbus_message_unref(buf);
183  if (!ret) {
184  ERR_DU << "Failed to send visual notification: " << err.message << '\n';
185  dbus_error_free(&err);
186  if (kde_style) {
187  ERR_DU << " Retrying with the freedesktop protocol." << std::endl;
188  kde_style = false;
189  return send_dbus_notification(connection, replaces_id, owner, message);
190  }
191  return 0;
192  }
193  uint32_t id;
194  dbus_message_get_args(ret, nullptr,
195  DBUS_TYPE_UINT32, &id,
196  DBUS_TYPE_INVALID);
197  dbus_message_unref(ret);
198  // TODO: remove once closing signals for KDE are handled in filter_dbus_signal.
199  if (kde_style) return 0;
200  return id;
201 }
202 
203 } // end anonymous namespace
204 
205 namespace dbus {
206 
207 const size_t MAX_MSG_LINES = 5;
208 
209 void send_notification(const std::string & owner, const std::string & message, bool with_history)
210 {
211  DBusConnection *connection = get_dbus_connection();
212  if (!connection) return;
213 
214  wnotify_by_owner & noticias = notifications.get<by_owner>();
215 
216  wnotify_owner_it i = noticias.find(owner);
217 
218  if (i != noticias.end()) {
219  if (with_history) {
220  i->message = message + "\n" + i->message;
221 
222  size_t endl_pos = i->message.find('\n');
223  size_t ctr = 1;
224 
225  while (ctr < MAX_MSG_LINES && endl_pos != std::string::npos) {
226  endl_pos = i->message.find('\n', endl_pos+1);
227  ctr++;
228  }
229 
230  i->message = i->message.substr(0,endl_pos);
231  } else {
232  i->message = message;
233  }
234 
235  send_dbus_notification(connection, i->id, owner, i->message);
236  return;
237  } else {
238  uint32_t id = send_dbus_notification(connection, 0, owner, message);
239  if (!id) return;
240  wnotify visual(id,owner,message);
241  std::pair<wnotify_owner_it, bool> result = noticias.insert(visual);
242  if (!result.second) {
243  ERR_DU << "Failed to insert a dbus notification message:\n"
244  << "New Item:\n" << "\tid=" << id << "\n\towner=" << owner << "\n\tmessage=" << message << "\n"
245  << "Old Item:\n" << "\tid=" << result.first->id << "\n\towner=" << result.first->owner << "\n\tmessage=" << result.first->message << "\n";
246  }
247  }
248 }
249 
250 } // end namespace dbus
251 
boost::uint32_t uint32_t
Definition: xbrz.hpp:45
const size_t MAX_MSG_LINES
static void body(LexState *ls, expdesc *e, int ismethod, int line)
Definition: lparser.cpp:787
#define DBG_DU
void send_notification(const std::string &owner, const std::string &message, bool with_history)
#define LOG_DU
GLuint64EXT * result
Definition: glew.h:10727
GLuint id
Definition: glew.h:1647
std::string normalize_path(const std::string &path)
Returns the absolute path of a file.
void erase(const std::string &key)
GLenum GLuint GLsizei const char * buf
Definition: glew.h:2498
std::string path
logger & err()
Definition: log.cpp:79
size_t i
Definition: function.cpp:1057
Declarations for File-IO.
static lg::log_domain log_desktop("desktop")
#define ERR_DU
Standard logging facilities (interface).
GLsizei GLenum GLuint GLuint GLsizei char * message
Definition: glew.h:2499
int connection
Definition: network.hpp:74
std::string::const_iterator iterator
Definition: tokenizer.hpp:21
bool file_exists(const std::string &name)
Returns true if a file or directory with such name already exists.
GLsizei const GLcharARB ** string
Definition: glew.h:4503
std::string app_icon