1 /*
2  * This file Copyright (C) 2008-2014 Mnemosyne LLC
3  *
4  * It may be used under the GNU GPL versions 2 or 3
5  * or any future license endorsed by Mnemosyne LLC.
6  *
7  */
8 
9 #include <gio/gio.h>
10 
11 #include <glib/gi18n.h>
12 #include "conf.h"
13 #include "notify.h"
14 #include "tr-prefs.h"
15 #include "util.h"
16 
17 #define NOTIFICATIONS_DBUS_NAME "org.freedesktop.Notifications"
18 #define NOTIFICATIONS_DBUS_CORE_OBJECT "/org/freedesktop/Notifications"
19 #define NOTIFICATIONS_DBUS_CORE_INTERFACE "org.freedesktop.Notifications"
20 
21 static GDBusProxy* proxy = NULL;
22 static GHashTable* active_notifications = NULL;
23 static gboolean server_supports_actions = FALSE;
24 
25 typedef struct TrNotification
26 {
27     guint id;
28     TrCore* core;
29     int torrent_id;
30 }
31 TrNotification;
32 
tr_notification_free(gpointer data)33 static void tr_notification_free(gpointer data)
34 {
35     TrNotification* n = data;
36 
37     if (n->core != NULL)
38     {
39         g_object_unref(G_OBJECT(n->core));
40     }
41 
42     g_free(n);
43 }
44 
get_capabilities_callback(GObject * source,GAsyncResult * res,gpointer user_data UNUSED)45 static void get_capabilities_callback(GObject* source, GAsyncResult* res, gpointer user_data UNUSED)
46 {
47     char** caps;
48     GVariant* result;
49 
50     result = g_dbus_proxy_call_finish(G_DBUS_PROXY(source), res, NULL);
51 
52     if (result == NULL || !g_variant_is_of_type(result, G_VARIANT_TYPE("(as)")))
53     {
54         if (result != NULL)
55         {
56             g_variant_unref(result);
57         }
58 
59         return;
60     }
61 
62     g_variant_get(result, "(^a&s)", &caps);
63 
64     for (int i = 0; caps[i] != NULL; i++)
65     {
66         if (g_strcmp0(caps[i], "actions") == 0)
67         {
68             server_supports_actions = TRUE;
69             break;
70         }
71     }
72 
73     g_free(caps);
74     g_variant_unref(result);
75 }
76 
g_signal_callback(GDBusProxy * proxy UNUSED,char * sender_name UNUSED,char * signal_name,GVariant * params,gpointer user_data UNUSED)77 static void g_signal_callback(GDBusProxy* proxy UNUSED, char* sender_name UNUSED, char* signal_name, GVariant* params,
78     gpointer user_data UNUSED)
79 {
80     guint id;
81     TrNotification* n;
82 
83     g_return_if_fail(g_variant_is_of_type(params, G_VARIANT_TYPE("(u*)")));
84 
85     g_variant_get(params, "(u*)", &id, NULL);
86     n = g_hash_table_lookup(active_notifications, GINT_TO_POINTER((int*)&id));
87 
88     if (n == NULL)
89     {
90         return;
91     }
92 
93     if (g_strcmp0(signal_name, "NotificationClosed") == 0)
94     {
95         g_hash_table_remove(active_notifications, GINT_TO_POINTER((int*)&n->id));
96     }
97     else if (g_strcmp0(signal_name, "ActionInvoked") == 0 && g_variant_is_of_type(params, G_VARIANT_TYPE("(us)")))
98     {
99         char* action;
100         tr_torrent* tor;
101 
102         tor = gtr_core_find_torrent(n->core, n->torrent_id);
103 
104         if (tor == NULL)
105         {
106             return;
107         }
108 
109         g_variant_get(params, "(u&s)", NULL, &action);
110 
111         if (g_strcmp0(action, "folder") == 0)
112         {
113             gtr_core_open_folder(n->core, n->torrent_id);
114         }
115         else if (g_strcmp0(action, "file") == 0)
116         {
117             tr_info const* inf = tr_torrentInfo(tor);
118             char const* dir = tr_torrentGetDownloadDir(tor);
119             char* path = g_build_filename(dir, inf->files[0].name, NULL);
120             gtr_open_file(path);
121             g_free(path);
122         }
123     }
124 }
125 
dbus_proxy_ready_callback(GObject * source UNUSED,GAsyncResult * res,gpointer user_data UNUSED)126 static void dbus_proxy_ready_callback(GObject* source UNUSED, GAsyncResult* res, gpointer user_data UNUSED)
127 {
128     proxy = g_dbus_proxy_new_for_bus_finish(res, NULL);
129 
130     if (proxy == NULL)
131     {
132         g_warning("Failed to create proxy for %s", NOTIFICATIONS_DBUS_NAME);
133         return;
134     }
135 
136     g_signal_connect(proxy, "g-signal", G_CALLBACK(g_signal_callback), NULL);
137     g_dbus_proxy_call(proxy, "GetCapabilities", g_variant_new("()"), G_DBUS_CALL_FLAGS_NONE, -1, NULL,
138         get_capabilities_callback, NULL);
139 }
140 
gtr_notify_init(void)141 void gtr_notify_init(void)
142 {
143     active_notifications = g_hash_table_new_full(g_int_hash, g_int_equal, NULL, tr_notification_free);
144     g_dbus_proxy_new_for_bus(G_BUS_TYPE_SESSION, G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES, NULL, NOTIFICATIONS_DBUS_NAME,
145         NOTIFICATIONS_DBUS_CORE_OBJECT, NOTIFICATIONS_DBUS_CORE_INTERFACE, NULL, dbus_proxy_ready_callback, NULL);
146 }
147 
notify_callback(GObject * source,GAsyncResult * res,gpointer user_data)148 static void notify_callback(GObject* source, GAsyncResult* res, gpointer user_data)
149 {
150     GVariant* result;
151     TrNotification* n = user_data;
152 
153     result = g_dbus_proxy_call_finish(G_DBUS_PROXY(source), res, NULL);
154 
155     if (result == NULL || !g_variant_is_of_type(result, G_VARIANT_TYPE("(u)")))
156     {
157         if (result != NULL)
158         {
159             g_variant_unref(result);
160         }
161 
162         tr_notification_free(n);
163         return;
164     }
165 
166     g_variant_get(result, "(u)", &n->id);
167     g_hash_table_insert(active_notifications, GINT_TO_POINTER((int*)&n->id), n);
168 
169     g_variant_unref(result);
170 }
171 
gtr_notify_torrent_completed(TrCore * core,int torrent_id)172 void gtr_notify_torrent_completed(TrCore* core, int torrent_id)
173 {
174     GVariantBuilder actions_builder;
175     TrNotification* n;
176     tr_torrent* tor;
177     char const* cmd = gtr_pref_string_get(TR_KEY_torrent_complete_sound_command);
178 
179     if (gtr_pref_flag_get(TR_KEY_torrent_complete_sound_enabled))
180     {
181         g_spawn_command_line_async(cmd, NULL);
182     }
183 
184     if (!gtr_pref_flag_get(TR_KEY_torrent_complete_notification_enabled))
185     {
186         return;
187     }
188 
189     g_return_if_fail(G_IS_DBUS_PROXY(proxy));
190 
191     tor = gtr_core_find_torrent(core, torrent_id);
192 
193     n = g_new0(TrNotification, 1);
194     g_object_ref(G_OBJECT(core));
195     n->core = core;
196     n->torrent_id = torrent_id;
197 
198     g_variant_builder_init(&actions_builder, G_VARIANT_TYPE("as"));
199 
200     if (server_supports_actions)
201     {
202         tr_info const* inf = tr_torrentInfo(tor);
203 
204         if (inf->fileCount == 1)
205         {
206             g_variant_builder_add(&actions_builder, "s", "file");
207             g_variant_builder_add(&actions_builder, "s", _("Open File"));
208         }
209         else
210         {
211             g_variant_builder_add(&actions_builder, "s", "folder");
212             g_variant_builder_add(&actions_builder, "s", _("Open Folder"));
213         }
214     }
215 
216     g_dbus_proxy_call(proxy, "Notify", g_variant_new("(susssasa{sv}i)", "Transmission", n->id, "transmission",
217         _("Torrent Complete"), tr_torrentName(tor), &actions_builder, NULL, -1), G_DBUS_CALL_FLAGS_NONE, -1, NULL,
218         notify_callback, n);
219 }
220 
gtr_notify_torrent_added(char const * name)221 void gtr_notify_torrent_added(char const* name)
222 {
223     TrNotification* n;
224 
225     g_return_if_fail(G_IS_DBUS_PROXY(proxy));
226 
227     if (!gtr_pref_flag_get(TR_KEY_torrent_added_notification_enabled))
228     {
229         return;
230     }
231 
232     n = g_new0(TrNotification, 1);
233     g_dbus_proxy_call(proxy, "Notify", g_variant_new("(susssasa{sv}i)", "Transmission", 0, "transmission", _("Torrent Added"),
234         name, NULL, NULL, -1), G_DBUS_CALL_FLAGS_NONE, -1, NULL, notify_callback, n);
235 }
236