1 | /****************************************************************************** |
---|
2 | * $Id: main.c 14670 2016-01-09 18:06:17Z mikedld $ |
---|
3 | * |
---|
4 | * Copyright (c) Transmission authors and contributors |
---|
5 | * |
---|
6 | * Permission is hereby granted, free of charge, to any person obtaining a |
---|
7 | * copy of this software and associated documentation files (the "Software"), |
---|
8 | * to deal in the Software without restriction, including without limitation |
---|
9 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, |
---|
10 | * and/or sell copies of the Software, and to permit persons to whom the |
---|
11 | * Software is furnished to do so, subject to the following conditions: |
---|
12 | * |
---|
13 | * The above copyright notice and this permission notice shall be included in |
---|
14 | * all copies or substantial portions of the Software. |
---|
15 | * |
---|
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
---|
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
---|
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
---|
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
---|
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING |
---|
21 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER |
---|
22 | * DEALINGS IN THE SOFTWARE. |
---|
23 | *****************************************************************************/ |
---|
24 | |
---|
25 | #include <locale.h> |
---|
26 | #include <signal.h> |
---|
27 | #include <string.h> |
---|
28 | #include <stdio.h> |
---|
29 | #include <stdlib.h> /* exit () */ |
---|
30 | #include <time.h> |
---|
31 | |
---|
32 | #include <glib/gi18n.h> |
---|
33 | #include <glib/gstdio.h> |
---|
34 | #include <gio/gio.h> |
---|
35 | #include <gtk/gtk.h> |
---|
36 | |
---|
37 | #include <libtransmission/transmission.h> |
---|
38 | #include <libtransmission/rpcimpl.h> |
---|
39 | #include <libtransmission/utils.h> |
---|
40 | #include <libtransmission/version.h> |
---|
41 | |
---|
42 | #include "actions.h" |
---|
43 | #include "conf.h" |
---|
44 | #include "details.h" |
---|
45 | #include "dialogs.h" |
---|
46 | #include "hig.h" |
---|
47 | #include "makemeta-ui.h" |
---|
48 | #include "msgwin.h" |
---|
49 | #include "notify.h" |
---|
50 | #include "open-dialog.h" |
---|
51 | #include "relocate.h" |
---|
52 | #include "stats.h" |
---|
53 | #include "tr-core.h" |
---|
54 | #include "tr-icon.h" |
---|
55 | #include "tr-prefs.h" |
---|
56 | #include "tr-window.h" |
---|
57 | #include "util.h" |
---|
58 | |
---|
59 | #define MY_CONFIG_NAME "transmission" |
---|
60 | #define MY_READABLE_NAME "transmission-gtk" |
---|
61 | |
---|
62 | #define TR_RESOURCE_PATH "/com/transmissionbt/transmission/" |
---|
63 | |
---|
64 | #define SHOW_LICENSE |
---|
65 | static const char * LICENSE = |
---|
66 | "Copyright 2005-2014. All code is copyrighted by the respective authors.\n" |
---|
67 | "\n" |
---|
68 | "Transmission can be redistributed and/or modified under the terms of the " |
---|
69 | "GNU GPL versions 2 or 3 or by any future license endorsed by Mnemosyne LLC.\n" |
---|
70 | "\n" |
---|
71 | "In addition, linking to and/or using OpenSSL is allowed.\n" |
---|
72 | "\n" |
---|
73 | "This program is distributed in the hope that it will be useful, " |
---|
74 | "but WITHOUT ANY WARRANTY; without even the implied warranty of " |
---|
75 | "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n" |
---|
76 | "\n" |
---|
77 | "Some of Transmission's source files have more permissive licenses. " |
---|
78 | "Those files may, of course, be used on their own under their own terms.\n"; |
---|
79 | |
---|
80 | struct cbdata |
---|
81 | { |
---|
82 | char * config_dir; |
---|
83 | gboolean start_paused; |
---|
84 | gboolean is_iconified; |
---|
85 | gboolean is_closing; |
---|
86 | |
---|
87 | guint activation_count; |
---|
88 | guint timer; |
---|
89 | guint update_model_soon_tag; |
---|
90 | guint refresh_actions_tag; |
---|
91 | gpointer icon; |
---|
92 | GtkWindow * wind; |
---|
93 | TrCore * core; |
---|
94 | GtkWidget * msgwin; |
---|
95 | GtkWidget * prefs; |
---|
96 | GSList * error_list; |
---|
97 | GSList * duplicates_list; |
---|
98 | GSList * details; |
---|
99 | GtkTreeSelection * sel; |
---|
100 | }; |
---|
101 | |
---|
102 | static void |
---|
103 | gtr_window_present (GtkWindow * window) |
---|
104 | { |
---|
105 | gtk_window_present_with_time (window, gtk_get_current_event_time ()); |
---|
106 | } |
---|
107 | |
---|
108 | /*** |
---|
109 | **** |
---|
110 | **** DETAILS DIALOGS MANAGEMENT |
---|
111 | **** |
---|
112 | ***/ |
---|
113 | |
---|
114 | static int |
---|
115 | compare_integers (gconstpointer a, gconstpointer b) |
---|
116 | { |
---|
117 | return GPOINTER_TO_INT (a) - GPOINTER_TO_INT (b); |
---|
118 | } |
---|
119 | |
---|
120 | static char* |
---|
121 | get_details_dialog_key (GSList * id_list) |
---|
122 | { |
---|
123 | GSList * l; |
---|
124 | GSList * tmp = g_slist_sort (g_slist_copy (id_list), compare_integers); |
---|
125 | GString * gstr = g_string_new (NULL); |
---|
126 | |
---|
127 | for (l=tmp; l!=NULL; l=l->next) |
---|
128 | g_string_append_printf (gstr, "%d ", GPOINTER_TO_INT (l->data)); |
---|
129 | |
---|
130 | g_slist_free (tmp); |
---|
131 | return g_string_free (gstr, FALSE); |
---|
132 | } |
---|
133 | |
---|
134 | static void |
---|
135 | get_selected_torrent_ids_foreach (GtkTreeModel * model, |
---|
136 | GtkTreePath * p UNUSED, |
---|
137 | GtkTreeIter * iter, |
---|
138 | gpointer gdata) |
---|
139 | { |
---|
140 | int id; |
---|
141 | GSList ** ids = gdata; |
---|
142 | gtk_tree_model_get (model, iter, MC_TORRENT_ID, &id, -1); |
---|
143 | *ids = g_slist_append (*ids, GINT_TO_POINTER (id)); |
---|
144 | } |
---|
145 | static GSList* |
---|
146 | get_selected_torrent_ids (struct cbdata * data) |
---|
147 | { |
---|
148 | GSList * ids = NULL; |
---|
149 | gtk_tree_selection_selected_foreach (data->sel, |
---|
150 | get_selected_torrent_ids_foreach, |
---|
151 | &ids); |
---|
152 | return ids; |
---|
153 | } |
---|
154 | |
---|
155 | static void |
---|
156 | on_details_dialog_closed (gpointer gdata, GObject * dead) |
---|
157 | { |
---|
158 | struct cbdata * data = gdata; |
---|
159 | |
---|
160 | data->details = g_slist_remove (data->details, dead); |
---|
161 | } |
---|
162 | |
---|
163 | static void |
---|
164 | show_details_dialog_for_selected_torrents (struct cbdata * data) |
---|
165 | { |
---|
166 | GtkWidget * dialog = NULL; |
---|
167 | GSList * l; |
---|
168 | GSList * ids = get_selected_torrent_ids (data); |
---|
169 | char * key = get_details_dialog_key (ids); |
---|
170 | |
---|
171 | for (l=data->details; dialog==NULL && l!=NULL; l=l->next) |
---|
172 | if (!g_strcmp0 (key, g_object_get_data (l->data, "key"))) |
---|
173 | dialog = l->data; |
---|
174 | |
---|
175 | if (dialog == NULL) |
---|
176 | { |
---|
177 | dialog = gtr_torrent_details_dialog_new (GTK_WINDOW (data->wind), data->core); |
---|
178 | gtr_torrent_details_dialog_set_torrents (dialog, ids); |
---|
179 | g_object_set_data_full (G_OBJECT (dialog), "key", g_strdup (key), g_free); |
---|
180 | g_object_weak_ref (G_OBJECT (dialog), on_details_dialog_closed, data); |
---|
181 | data->details = g_slist_append (data->details, dialog); |
---|
182 | gtk_widget_show (dialog); |
---|
183 | } |
---|
184 | |
---|
185 | gtr_window_present (GTK_WINDOW (dialog)); |
---|
186 | g_free (key); |
---|
187 | g_slist_free (ids); |
---|
188 | } |
---|
189 | |
---|
190 | /**** |
---|
191 | ***** |
---|
192 | ***** ON SELECTION CHANGED |
---|
193 | ***** |
---|
194 | ****/ |
---|
195 | |
---|
196 | struct counts_data |
---|
197 | { |
---|
198 | int total_count; |
---|
199 | int queued_count; |
---|
200 | int stopped_count; |
---|
201 | }; |
---|
202 | |
---|
203 | static void |
---|
204 | get_selected_torrent_counts_foreach (GtkTreeModel * model, GtkTreePath * path UNUSED, |
---|
205 | GtkTreeIter * iter, gpointer user_data) |
---|
206 | { |
---|
207 | int activity = 0; |
---|
208 | struct counts_data * counts = user_data; |
---|
209 | |
---|
210 | ++counts->total_count; |
---|
211 | |
---|
212 | gtk_tree_model_get (model, iter, MC_ACTIVITY, &activity, -1); |
---|
213 | |
---|
214 | if ((activity == TR_STATUS_DOWNLOAD_WAIT) || (activity == TR_STATUS_SEED_WAIT)) |
---|
215 | ++counts->queued_count; |
---|
216 | |
---|
217 | if (activity == TR_STATUS_STOPPED) |
---|
218 | ++counts->stopped_count; |
---|
219 | } |
---|
220 | |
---|
221 | static void |
---|
222 | get_selected_torrent_counts (struct cbdata * data, struct counts_data * counts) |
---|
223 | { |
---|
224 | counts->total_count = 0; |
---|
225 | counts->queued_count = 0; |
---|
226 | counts->stopped_count = 0; |
---|
227 | |
---|
228 | gtk_tree_selection_selected_foreach (data->sel, get_selected_torrent_counts_foreach, counts); |
---|
229 | } |
---|
230 | |
---|
231 | static void |
---|
232 | count_updatable_foreach (GtkTreeModel * model, GtkTreePath * path UNUSED, |
---|
233 | GtkTreeIter * iter, gpointer accumulated_status) |
---|
234 | { |
---|
235 | tr_torrent * tor; |
---|
236 | gtk_tree_model_get (model, iter, MC_TORRENT, &tor, -1); |
---|
237 | *(int*)accumulated_status |= tr_torrentCanManualUpdate (tor); |
---|
238 | } |
---|
239 | |
---|
240 | static gboolean |
---|
241 | refresh_actions (gpointer gdata) |
---|
242 | { |
---|
243 | struct cbdata * data = gdata; |
---|
244 | |
---|
245 | if (!data->is_closing) |
---|
246 | { |
---|
247 | int canUpdate; |
---|
248 | struct counts_data sel_counts; |
---|
249 | const size_t total = gtr_core_get_torrent_count (data->core); |
---|
250 | const size_t active = gtr_core_get_active_torrent_count (data->core); |
---|
251 | const int torrent_count = gtk_tree_model_iter_n_children (gtr_core_model (data->core), NULL); |
---|
252 | bool has_selection; |
---|
253 | |
---|
254 | get_selected_torrent_counts (data, &sel_counts); |
---|
255 | has_selection = sel_counts.total_count > 0; |
---|
256 | |
---|
257 | gtr_action_set_sensitive ("select-all", torrent_count != 0); |
---|
258 | gtr_action_set_sensitive ("deselect-all", torrent_count != 0); |
---|
259 | gtr_action_set_sensitive ("pause-all-torrents", active != 0); |
---|
260 | gtr_action_set_sensitive ("start-all-torrents", active != total); |
---|
261 | |
---|
262 | gtr_action_set_sensitive ("torrent-stop", (sel_counts.stopped_count < sel_counts.total_count)); |
---|
263 | gtr_action_set_sensitive ("torrent-start", (sel_counts.stopped_count) > 0); |
---|
264 | gtr_action_set_sensitive ("torrent-start-now", (sel_counts.stopped_count + sel_counts.queued_count) > 0); |
---|
265 | gtr_action_set_sensitive ("torrent-verify", has_selection); |
---|
266 | gtr_action_set_sensitive ("remove-torrent", has_selection); |
---|
267 | gtr_action_set_sensitive ("delete-torrent", has_selection); |
---|
268 | gtr_action_set_sensitive ("relocate-torrent", has_selection); |
---|
269 | gtr_action_set_sensitive ("queue-move-top", has_selection); |
---|
270 | gtr_action_set_sensitive ("queue-move-up", has_selection); |
---|
271 | gtr_action_set_sensitive ("queue-move-down", has_selection); |
---|
272 | gtr_action_set_sensitive ("queue-move-bottom", has_selection); |
---|
273 | gtr_action_set_sensitive ("show-torrent-properties", has_selection); |
---|
274 | gtr_action_set_sensitive ("open-torrent-folder", sel_counts.total_count == 1); |
---|
275 | gtr_action_set_sensitive ("copy-magnet-link-to-clipboard", sel_counts.total_count == 1); |
---|
276 | |
---|
277 | canUpdate = 0; |
---|
278 | gtk_tree_selection_selected_foreach (data->sel, count_updatable_foreach, &canUpdate); |
---|
279 | gtr_action_set_sensitive ("torrent-reannounce", canUpdate != 0); |
---|
280 | } |
---|
281 | |
---|
282 | data->refresh_actions_tag = 0; |
---|
283 | return G_SOURCE_REMOVE; |
---|
284 | } |
---|
285 | |
---|
286 | static void |
---|
287 | refresh_actions_soon (gpointer gdata) |
---|
288 | { |
---|
289 | struct cbdata * data = gdata; |
---|
290 | |
---|
291 | if (!data->is_closing && !data->refresh_actions_tag) |
---|
292 | data->refresh_actions_tag = gdk_threads_add_idle (refresh_actions, data); |
---|
293 | } |
---|
294 | |
---|
295 | static void |
---|
296 | on_selection_changed (GtkTreeSelection * s UNUSED, gpointer gdata) |
---|
297 | { |
---|
298 | refresh_actions_soon (gdata); |
---|
299 | } |
---|
300 | |
---|
301 | /*** |
---|
302 | **** |
---|
303 | ***/ |
---|
304 | |
---|
305 | static gboolean |
---|
306 | has_magnet_link_handler (void) |
---|
307 | { |
---|
308 | GAppInfo * app_info = g_app_info_get_default_for_uri_scheme ("magnet"); |
---|
309 | const gboolean has_handler = app_info != NULL; |
---|
310 | g_clear_object (&app_info); |
---|
311 | return has_handler; |
---|
312 | } |
---|
313 | |
---|
314 | static void |
---|
315 | register_magnet_link_handler (void) |
---|
316 | { |
---|
317 | GError * error; |
---|
318 | GAppInfo * app; |
---|
319 | const char * const content_type = "x-scheme-handler/magnet"; |
---|
320 | |
---|
321 | error = NULL; |
---|
322 | app = g_app_info_create_from_commandline ("transmission-gtk", |
---|
323 | "transmission-gtk", |
---|
324 | G_APP_INFO_CREATE_SUPPORTS_URIS, |
---|
325 | &error); |
---|
326 | g_app_info_set_as_default_for_type (app, content_type, &error); |
---|
327 | if (error != NULL) |
---|
328 | { |
---|
329 | g_warning (_("Error registering Transmission as a %s handler: %s"), |
---|
330 | content_type, |
---|
331 | error->message); |
---|
332 | g_error_free (error); |
---|
333 | } |
---|
334 | |
---|
335 | g_clear_object (&app); |
---|
336 | } |
---|
337 | static void |
---|
338 | ensure_magnet_handler_exists (void) |
---|
339 | { |
---|
340 | if (!has_magnet_link_handler ()) |
---|
341 | register_magnet_link_handler (); |
---|
342 | } |
---|
343 | |
---|
344 | static void |
---|
345 | on_main_window_size_allocated (GtkWidget * gtk_window, |
---|
346 | GtkAllocation * alloc UNUSED, |
---|
347 | gpointer gdata UNUSED) |
---|
348 | { |
---|
349 | GdkWindow * gdk_window = gtk_widget_get_window (gtk_window); |
---|
350 | const gboolean isMaximized = (gdk_window != NULL) |
---|
351 | && (gdk_window_get_state (gdk_window) & GDK_WINDOW_STATE_MAXIMIZED); |
---|
352 | |
---|
353 | gtr_pref_int_set (TR_KEY_main_window_is_maximized, isMaximized); |
---|
354 | |
---|
355 | if (!isMaximized) |
---|
356 | { |
---|
357 | int x, y, w, h; |
---|
358 | gtk_window_get_position (GTK_WINDOW (gtk_window), &x, &y); |
---|
359 | gtk_window_get_size (GTK_WINDOW (gtk_window), &w, &h); |
---|
360 | gtr_pref_int_set (TR_KEY_main_window_x, x); |
---|
361 | gtr_pref_int_set (TR_KEY_main_window_y, y); |
---|
362 | gtr_pref_int_set (TR_KEY_main_window_width, w); |
---|
363 | gtr_pref_int_set (TR_KEY_main_window_height, h); |
---|
364 | } |
---|
365 | } |
---|
366 | |
---|
367 | /*** |
---|
368 | **** listen to changes that come from RPC |
---|
369 | ***/ |
---|
370 | |
---|
371 | struct on_rpc_changed_struct |
---|
372 | { |
---|
373 | TrCore * core; |
---|
374 | tr_rpc_callback_type type; |
---|
375 | int torrent_id; |
---|
376 | }; |
---|
377 | |
---|
378 | static gboolean |
---|
379 | on_rpc_changed_idle (gpointer gdata) |
---|
380 | { |
---|
381 | tr_torrent * tor; |
---|
382 | struct on_rpc_changed_struct * data = gdata; |
---|
383 | |
---|
384 | switch (data->type) |
---|
385 | { |
---|
386 | case TR_RPC_SESSION_CLOSE: |
---|
387 | gtr_action_activate ("quit"); |
---|
388 | break; |
---|
389 | |
---|
390 | case TR_RPC_TORRENT_ADDED: |
---|
391 | if ((tor = gtr_core_find_torrent (data->core, data->torrent_id))) |
---|
392 | gtr_core_add_torrent (data->core, tor, true); |
---|
393 | break; |
---|
394 | |
---|
395 | case TR_RPC_TORRENT_REMOVING: |
---|
396 | gtr_core_remove_torrent (data->core, data->torrent_id, false); |
---|
397 | break; |
---|
398 | |
---|
399 | case TR_RPC_TORRENT_TRASHING: |
---|
400 | gtr_core_remove_torrent (data->core, data->torrent_id, true); |
---|
401 | break; |
---|
402 | |
---|
403 | case TR_RPC_SESSION_CHANGED: { |
---|
404 | int i; |
---|
405 | tr_variant tmp; |
---|
406 | tr_variant * newval; |
---|
407 | tr_variant * oldvals = gtr_pref_get_all (); |
---|
408 | tr_quark key; |
---|
409 | GSList * l; |
---|
410 | GSList * changed_keys = NULL; |
---|
411 | tr_session * session = gtr_core_session (data->core); |
---|
412 | tr_variantInitDict (&tmp, 100); |
---|
413 | tr_sessionGetSettings (session, &tmp); |
---|
414 | for (i=0; tr_variantDictChild (&tmp, i, &key, &newval); ++i) |
---|
415 | { |
---|
416 | bool changed; |
---|
417 | tr_variant * oldval = tr_variantDictFind (oldvals, key); |
---|
418 | if (!oldval) |
---|
419 | { |
---|
420 | changed = true; |
---|
421 | } |
---|
422 | else |
---|
423 | { |
---|
424 | char * a = tr_variantToStr (oldval, TR_VARIANT_FMT_BENC, NULL); |
---|
425 | char * b = tr_variantToStr (newval, TR_VARIANT_FMT_BENC, NULL); |
---|
426 | changed = g_strcmp0 (a, b) != 0; |
---|
427 | tr_free (b); |
---|
428 | tr_free (a); |
---|
429 | } |
---|
430 | |
---|
431 | if (changed) |
---|
432 | changed_keys = g_slist_append (changed_keys, GINT_TO_POINTER(key)); |
---|
433 | } |
---|
434 | tr_sessionGetSettings (session, oldvals); |
---|
435 | |
---|
436 | for (l=changed_keys; l!=NULL; l=l->next) |
---|
437 | gtr_core_pref_changed (data->core, GPOINTER_TO_INT(l->data)); |
---|
438 | |
---|
439 | g_slist_free (changed_keys); |
---|
440 | tr_variantFree (&tmp); |
---|
441 | break; |
---|
442 | } |
---|
443 | |
---|
444 | case TR_RPC_TORRENT_CHANGED: |
---|
445 | case TR_RPC_TORRENT_MOVED: |
---|
446 | case TR_RPC_TORRENT_STARTED: |
---|
447 | case TR_RPC_TORRENT_STOPPED: |
---|
448 | case TR_RPC_SESSION_QUEUE_POSITIONS_CHANGED: |
---|
449 | /* nothing interesting to do here */ |
---|
450 | break; |
---|
451 | } |
---|
452 | |
---|
453 | g_free (data); |
---|
454 | return G_SOURCE_REMOVE; |
---|
455 | } |
---|
456 | |
---|
457 | static tr_rpc_callback_status |
---|
458 | on_rpc_changed (tr_session * session G_GNUC_UNUSED, |
---|
459 | tr_rpc_callback_type type, |
---|
460 | struct tr_torrent * tor, |
---|
461 | void * gdata) |
---|
462 | { |
---|
463 | struct cbdata * cbdata = gdata; |
---|
464 | struct on_rpc_changed_struct * data; |
---|
465 | |
---|
466 | data = g_new (struct on_rpc_changed_struct, 1); |
---|
467 | data->core = cbdata->core; |
---|
468 | data->type = type; |
---|
469 | data->torrent_id = tr_torrentId (tor); |
---|
470 | gdk_threads_add_idle (on_rpc_changed_idle, data); |
---|
471 | |
---|
472 | return TR_RPC_NOREMOVE; |
---|
473 | } |
---|
474 | |
---|
475 | /*** |
---|
476 | **** signal handling |
---|
477 | ***/ |
---|
478 | |
---|
479 | static sig_atomic_t global_sigcount = 0; |
---|
480 | static struct cbdata * sighandler_cbdata = NULL; |
---|
481 | |
---|
482 | static void |
---|
483 | signal_handler (int sig) |
---|
484 | { |
---|
485 | if (++global_sigcount > 1) |
---|
486 | { |
---|
487 | signal (sig, SIG_DFL); |
---|
488 | raise (sig); |
---|
489 | } |
---|
490 | else if ((sig == SIGINT) || (sig == SIGTERM)) |
---|
491 | { |
---|
492 | g_message (_("Got signal %d; trying to shut down cleanly. Do it again if it gets stuck."), sig); |
---|
493 | gtr_actions_handler ("quit", sighandler_cbdata); |
---|
494 | } |
---|
495 | } |
---|
496 | |
---|
497 | /**** |
---|
498 | ***** |
---|
499 | ***** |
---|
500 | ****/ |
---|
501 | |
---|
502 | static void app_setup (GtkWindow * wind, struct cbdata * cbdata); |
---|
503 | |
---|
504 | static void |
---|
505 | on_startup (GApplication * application, gpointer user_data) |
---|
506 | { |
---|
507 | GError * error; |
---|
508 | const char * str; |
---|
509 | GtkWindow * win; |
---|
510 | GtkUIManager * ui_manager; |
---|
511 | tr_session * session; |
---|
512 | struct cbdata * cbdata = user_data; |
---|
513 | |
---|
514 | signal (SIGINT, signal_handler); |
---|
515 | signal (SIGTERM, signal_handler); |
---|
516 | |
---|
517 | sighandler_cbdata = cbdata; |
---|
518 | |
---|
519 | /* ensure the directories are created */ |
---|
520 | if ((str = gtr_pref_string_get (TR_KEY_download_dir))) |
---|
521 | g_mkdir_with_parents (str, 0777); |
---|
522 | if ((str = gtr_pref_string_get (TR_KEY_incomplete_dir))) |
---|
523 | g_mkdir_with_parents (str, 0777); |
---|
524 | |
---|
525 | /* initialize the libtransmission session */ |
---|
526 | session = tr_sessionInit (cbdata->config_dir, TRUE, gtr_pref_get_all ()); |
---|
527 | |
---|
528 | gtr_pref_flag_set (TR_KEY_alt_speed_enabled, tr_sessionUsesAltSpeed (session)); |
---|
529 | gtr_pref_int_set (TR_KEY_peer_port, tr_sessionGetPeerPort (session)); |
---|
530 | cbdata->core = gtr_core_new (session); |
---|
531 | |
---|
532 | /* init the ui manager */ |
---|
533 | error = NULL; |
---|
534 | ui_manager = gtk_ui_manager_new (); |
---|
535 | gtr_actions_init (ui_manager, cbdata); |
---|
536 | gtk_ui_manager_add_ui_from_resource (ui_manager, TR_RESOURCE_PATH "transmission-ui.xml", &error); |
---|
537 | g_assert_no_error (error); |
---|
538 | gtk_ui_manager_ensure_update (ui_manager); |
---|
539 | |
---|
540 | /* create main window now to be a parent to any error dialogs */ |
---|
541 | win = GTK_WINDOW (gtr_window_new (GTK_APPLICATION (application), ui_manager, cbdata->core)); |
---|
542 | g_signal_connect (win, "size-allocate", G_CALLBACK (on_main_window_size_allocated), cbdata); |
---|
543 | g_application_hold (application); |
---|
544 | g_object_weak_ref (G_OBJECT (win), (GWeakNotify)g_application_release, application); |
---|
545 | app_setup (win, cbdata); |
---|
546 | tr_sessionSetRPCCallback (session, on_rpc_changed, cbdata); |
---|
547 | |
---|
548 | /* check & see if it's time to update the blocklist */ |
---|
549 | if (gtr_pref_flag_get (TR_KEY_blocklist_enabled)) |
---|
550 | { |
---|
551 | if (gtr_pref_flag_get (TR_KEY_blocklist_updates_enabled)) |
---|
552 | { |
---|
553 | const int64_t last_time = gtr_pref_int_get (TR_KEY_blocklist_date); |
---|
554 | const int SECONDS_IN_A_WEEK = 7 * 24 * 60 * 60; |
---|
555 | const time_t now = time (NULL); |
---|
556 | if (last_time + SECONDS_IN_A_WEEK < now) |
---|
557 | gtr_core_blocklist_update (cbdata->core); |
---|
558 | } |
---|
559 | } |
---|
560 | |
---|
561 | /* if there's no magnet link handler registered, register us */ |
---|
562 | ensure_magnet_handler_exists (); |
---|
563 | } |
---|
564 | |
---|
565 | static void |
---|
566 | on_activate (GApplication * app UNUSED, struct cbdata * cbdata) |
---|
567 | { |
---|
568 | cbdata->activation_count++; |
---|
569 | |
---|
570 | /* GApplication emits an 'activate' signal when bootstrapping the primary. |
---|
571 | * Ordinarily we handle that by presenting the main window, but if the user |
---|
572 | * user started Transmission minimized, ignore that initial signal... */ |
---|
573 | if (cbdata->is_iconified && (cbdata->activation_count == 1)) |
---|
574 | return; |
---|
575 | |
---|
576 | gtr_action_activate ("present-main-window"); |
---|
577 | } |
---|
578 | |
---|
579 | static void |
---|
580 | open_files (GSList * files, gpointer gdata) |
---|
581 | { |
---|
582 | struct cbdata * cbdata = gdata; |
---|
583 | const gboolean do_start = gtr_pref_flag_get (TR_KEY_start_added_torrents) && !cbdata->start_paused; |
---|
584 | const gboolean do_prompt = gtr_pref_flag_get (TR_KEY_show_options_window); |
---|
585 | const gboolean do_notify = TRUE; |
---|
586 | |
---|
587 | gtr_core_add_files (cbdata->core, files, do_start, do_prompt, do_notify); |
---|
588 | } |
---|
589 | |
---|
590 | static void |
---|
591 | on_open (GApplication * application UNUSED, |
---|
592 | GFile ** f, |
---|
593 | gint file_count, |
---|
594 | gchar * hint UNUSED, |
---|
595 | gpointer gdata) |
---|
596 | { |
---|
597 | int i; |
---|
598 | GSList * files = NULL; |
---|
599 | |
---|
600 | for (i=0; i<file_count; i++) |
---|
601 | files = g_slist_prepend (files, f[i]); |
---|
602 | |
---|
603 | open_files (files, gdata); |
---|
604 | |
---|
605 | g_slist_free (files); |
---|
606 | } |
---|
607 | |
---|
608 | /*** |
---|
609 | **** |
---|
610 | ***/ |
---|
611 | |
---|
612 | int |
---|
613 | main (int argc, char ** argv) |
---|
614 | { |
---|
615 | int ret; |
---|
616 | struct stat sb; |
---|
617 | char * application_id; |
---|
618 | GtkApplication * app; |
---|
619 | GOptionContext * option_context; |
---|
620 | bool show_version = false; |
---|
621 | GError * error = NULL; |
---|
622 | struct cbdata cbdata; |
---|
623 | |
---|
624 | GOptionEntry option_entries[] = { |
---|
625 | { "config-dir", 'g', 0, G_OPTION_ARG_FILENAME, &cbdata.config_dir, _("Where to look for configuration files"), NULL }, |
---|
626 | { "paused", 'p', 0, G_OPTION_ARG_NONE, &cbdata.start_paused, _("Start with all torrents paused"), NULL }, |
---|
627 | { "minimized", 'm', 0, G_OPTION_ARG_NONE, &cbdata.is_iconified, _("Start minimized in notification area"), NULL }, |
---|
628 | { "version", 'v', 0, G_OPTION_ARG_NONE, &show_version, _("Show version number and exit"), NULL }, |
---|
629 | { NULL, 0, 0, 0, NULL, NULL, NULL } |
---|
630 | }; |
---|
631 | |
---|
632 | /* default settings */ |
---|
633 | memset (&cbdata, 0, sizeof (struct cbdata)); |
---|
634 | cbdata.config_dir = (char*) tr_getDefaultConfigDir (MY_CONFIG_NAME); |
---|
635 | |
---|
636 | /* init i18n */ |
---|
637 | setlocale (LC_ALL, ""); |
---|
638 | bindtextdomain (MY_READABLE_NAME, TRANSMISSIONLOCALEDIR); |
---|
639 | bind_textdomain_codeset (MY_READABLE_NAME, "UTF-8"); |
---|
640 | textdomain (MY_READABLE_NAME); |
---|
641 | |
---|
642 | /* init glib/gtk */ |
---|
643 | #if !GLIB_CHECK_VERSION(2,35,4) |
---|
644 | g_type_init (); |
---|
645 | #endif |
---|
646 | g_set_application_name (_("Transmission")); |
---|
647 | |
---|
648 | /* parse the command line */ |
---|
649 | option_context = g_option_context_new (_("[torrent files or urls]")); |
---|
650 | g_option_context_add_main_entries (option_context, option_entries, GETTEXT_PACKAGE); |
---|
651 | g_option_context_add_group (option_context, gtk_get_option_group (FALSE)); |
---|
652 | g_option_context_set_translation_domain (option_context, GETTEXT_PACKAGE); |
---|
653 | if (!g_option_context_parse (option_context, &argc, &argv, &error)) |
---|
654 | { |
---|
655 | g_print (_("%s\nRun '%s --help' to see a full list of available command line options.\n"), error->message, argv[0]); |
---|
656 | g_error_free (error); |
---|
657 | g_option_context_free (option_context); |
---|
658 | return 1; |
---|
659 | } |
---|
660 | g_option_context_free (option_context); |
---|
661 | |
---|
662 | /* handle the trivial "version" option */ |
---|
663 | if (show_version) |
---|
664 | { |
---|
665 | fprintf (stderr, "%s %s\n", MY_READABLE_NAME, LONG_VERSION_STRING); |
---|
666 | return 0; |
---|
667 | } |
---|
668 | |
---|
669 | gtk_window_set_default_icon_name (MY_CONFIG_NAME); |
---|
670 | |
---|
671 | /* init the unit formatters */ |
---|
672 | tr_formatter_mem_init (mem_K, _ (mem_K_str), _ (mem_M_str), _ (mem_G_str), _ (mem_T_str)); |
---|
673 | tr_formatter_size_init (disk_K, _ (disk_K_str), _ (disk_M_str), _ (disk_G_str), _ (disk_T_str)); |
---|
674 | tr_formatter_speed_init (speed_K, _ (speed_K_str), _ (speed_M_str), _ (speed_G_str), _ (speed_T_str)); |
---|
675 | |
---|
676 | /* set up the config dir */ |
---|
677 | gtr_pref_init (cbdata.config_dir); |
---|
678 | g_mkdir_with_parents (cbdata.config_dir, 0755); |
---|
679 | |
---|
680 | /* init notifications */ |
---|
681 | gtr_notify_init (); |
---|
682 | |
---|
683 | /* init the application for the specified config dir */ |
---|
684 | stat (cbdata.config_dir, &sb); |
---|
685 | application_id = g_strdup_printf ("com.transmissionbt.transmission_%lu_%lu", (unsigned long)sb.st_dev, (unsigned long)sb.st_ino); |
---|
686 | app = gtk_application_new (application_id, G_APPLICATION_HANDLES_OPEN); |
---|
687 | g_signal_connect (app, "open", G_CALLBACK (on_open), &cbdata); |
---|
688 | g_signal_connect (app, "startup", G_CALLBACK (on_startup), &cbdata); |
---|
689 | g_signal_connect (app, "activate", G_CALLBACK (on_activate), &cbdata); |
---|
690 | ret = g_application_run (G_APPLICATION (app), argc, argv); |
---|
691 | g_object_unref (app); |
---|
692 | g_free (application_id); |
---|
693 | return ret; |
---|
694 | } |
---|
695 | |
---|
696 | static void |
---|
697 | on_core_busy (TrCore * core UNUSED, gboolean busy, struct cbdata * c) |
---|
698 | { |
---|
699 | gtr_window_set_busy (c->wind, busy); |
---|
700 | } |
---|
701 | |
---|
702 | static void on_core_error (TrCore *, guint, const char *, struct cbdata *); |
---|
703 | static void on_add_torrent (TrCore *, tr_ctor *, gpointer); |
---|
704 | static void on_prefs_changed (TrCore * core, const tr_quark key, gpointer); |
---|
705 | static void main_window_setup (struct cbdata * cbdata, GtkWindow * wind); |
---|
706 | static gboolean update_model_loop (gpointer gdata); |
---|
707 | static gboolean update_model_once (gpointer gdata); |
---|
708 | |
---|
709 | static void |
---|
710 | app_setup (GtkWindow * wind, struct cbdata * cbdata) |
---|
711 | { |
---|
712 | if (cbdata->is_iconified) |
---|
713 | gtr_pref_flag_set (TR_KEY_show_notification_area_icon, TRUE); |
---|
714 | |
---|
715 | gtr_actions_set_core (cbdata->core); |
---|
716 | |
---|
717 | /* set up core handlers */ |
---|
718 | g_signal_connect (cbdata->core, "busy", G_CALLBACK (on_core_busy), cbdata); |
---|
719 | g_signal_connect (cbdata->core, "add-error", G_CALLBACK (on_core_error), cbdata); |
---|
720 | g_signal_connect (cbdata->core, "add-prompt", G_CALLBACK (on_add_torrent), cbdata); |
---|
721 | g_signal_connect (cbdata->core, "prefs-changed", G_CALLBACK (on_prefs_changed), cbdata); |
---|
722 | |
---|
723 | /* add torrents from command-line and saved state */ |
---|
724 | gtr_core_load (cbdata->core, cbdata->start_paused); |
---|
725 | gtr_core_torrents_added (cbdata->core); |
---|
726 | |
---|
727 | /* set up main window */ |
---|
728 | main_window_setup (cbdata, wind); |
---|
729 | |
---|
730 | /* set up the icon */ |
---|
731 | on_prefs_changed (cbdata->core, TR_KEY_show_notification_area_icon, cbdata); |
---|
732 | |
---|
733 | /* start model update timer */ |
---|
734 | cbdata->timer = gdk_threads_add_timeout_seconds (MAIN_WINDOW_REFRESH_INTERVAL_SECONDS, update_model_loop, cbdata); |
---|
735 | update_model_once (cbdata); |
---|
736 | |
---|
737 | /* either show the window or iconify it */ |
---|
738 | if (!cbdata->is_iconified) |
---|
739 | { |
---|
740 | gtk_widget_show (GTK_WIDGET (wind)); |
---|
741 | } |
---|
742 | else |
---|
743 | { |
---|
744 | gtk_window_set_skip_taskbar_hint (cbdata->wind, |
---|
745 | cbdata->icon != NULL); |
---|
746 | cbdata->is_iconified = FALSE; // ensure that the next toggle iconifies |
---|
747 | gtr_action_set_toggled ("toggle-main-window", FALSE); |
---|
748 | } |
---|
749 | |
---|
750 | if (!gtr_pref_flag_get (TR_KEY_user_has_given_informed_consent)) |
---|
751 | { |
---|
752 | GtkWidget * w = gtk_message_dialog_new (GTK_WINDOW (wind), |
---|
753 | GTK_DIALOG_DESTROY_WITH_PARENT, |
---|
754 | GTK_MESSAGE_OTHER, |
---|
755 | GTK_BUTTONS_NONE, |
---|
756 | "%s", |
---|
757 | _("Transmission is a file sharing program. When you run a torrent, its data will be made available to others by means of upload. Any content you share is your sole responsibility.")); |
---|
758 | gtk_dialog_add_button (GTK_DIALOG (w), GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT); |
---|
759 | gtk_dialog_add_button (GTK_DIALOG (w), _("I _Agree"), GTK_RESPONSE_ACCEPT); |
---|
760 | gtk_dialog_set_default_response (GTK_DIALOG (w), GTK_RESPONSE_ACCEPT); |
---|
761 | switch (gtk_dialog_run (GTK_DIALOG (w))) |
---|
762 | { |
---|
763 | case GTK_RESPONSE_ACCEPT: |
---|
764 | /* only show it once */ |
---|
765 | gtr_pref_flag_set (TR_KEY_user_has_given_informed_consent, TRUE); |
---|
766 | gtk_widget_destroy (w); |
---|
767 | break; |
---|
768 | |
---|
769 | default: |
---|
770 | exit (0); |
---|
771 | } |
---|
772 | } |
---|
773 | } |
---|
774 | |
---|
775 | static void |
---|
776 | presentMainWindow (struct cbdata * cbdata) |
---|
777 | { |
---|
778 | GtkWindow * window = cbdata->wind; |
---|
779 | |
---|
780 | if (cbdata->is_iconified) |
---|
781 | { |
---|
782 | cbdata->is_iconified = false; |
---|
783 | |
---|
784 | gtk_window_set_skip_taskbar_hint (window, FALSE); |
---|
785 | } |
---|
786 | |
---|
787 | if (!gtk_widget_get_visible (GTK_WIDGET (window))) |
---|
788 | { |
---|
789 | gtk_window_resize (window, gtr_pref_int_get (TR_KEY_main_window_width), |
---|
790 | gtr_pref_int_get (TR_KEY_main_window_height)); |
---|
791 | gtk_window_move (window, gtr_pref_int_get (TR_KEY_main_window_x), |
---|
792 | gtr_pref_int_get (TR_KEY_main_window_y)); |
---|
793 | gtr_widget_set_visible (GTK_WIDGET (window), TRUE); |
---|
794 | } |
---|
795 | |
---|
796 | gtr_window_present (window); |
---|
797 | gdk_window_raise (gtk_widget_get_window (GTK_WIDGET(window))); |
---|
798 | } |
---|
799 | |
---|
800 | static void |
---|
801 | hideMainWindow (struct cbdata * cbdata) |
---|
802 | { |
---|
803 | GtkWindow * window = cbdata->wind; |
---|
804 | gtk_window_set_skip_taskbar_hint (window, TRUE); |
---|
805 | gtr_widget_set_visible (GTK_WIDGET (window), FALSE); |
---|
806 | cbdata->is_iconified = true; |
---|
807 | } |
---|
808 | |
---|
809 | static void |
---|
810 | toggleMainWindow (struct cbdata * cbdata) |
---|
811 | { |
---|
812 | if (cbdata->is_iconified) |
---|
813 | presentMainWindow (cbdata); |
---|
814 | else |
---|
815 | hideMainWindow (cbdata); |
---|
816 | } |
---|
817 | |
---|
818 | static void on_app_exit (gpointer vdata); |
---|
819 | |
---|
820 | static gboolean |
---|
821 | winclose (GtkWidget * w UNUSED, |
---|
822 | GdkEvent * event UNUSED, |
---|
823 | gpointer gdata) |
---|
824 | { |
---|
825 | struct cbdata * cbdata = gdata; |
---|
826 | |
---|
827 | if (cbdata->icon != NULL) |
---|
828 | gtr_action_activate ("toggle-main-window"); |
---|
829 | else |
---|
830 | on_app_exit (cbdata); |
---|
831 | |
---|
832 | return TRUE; /* don't propagate event further */ |
---|
833 | } |
---|
834 | |
---|
835 | static void |
---|
836 | rowChangedCB (GtkTreeModel * model UNUSED, |
---|
837 | GtkTreePath * path, |
---|
838 | GtkTreeIter * iter UNUSED, |
---|
839 | gpointer gdata) |
---|
840 | { |
---|
841 | struct cbdata * data = gdata; |
---|
842 | |
---|
843 | if (gtk_tree_selection_path_is_selected (data->sel, path)) |
---|
844 | refresh_actions_soon (data); |
---|
845 | } |
---|
846 | |
---|
847 | static void |
---|
848 | on_drag_data_received (GtkWidget * widget UNUSED, |
---|
849 | GdkDragContext * drag_context, |
---|
850 | gint x UNUSED, |
---|
851 | gint y UNUSED, |
---|
852 | GtkSelectionData * selection_data, |
---|
853 | guint info UNUSED, |
---|
854 | guint time_, |
---|
855 | gpointer gdata) |
---|
856 | { |
---|
857 | guint i; |
---|
858 | char ** uris = gtk_selection_data_get_uris (selection_data); |
---|
859 | const guint file_count = g_strv_length (uris); |
---|
860 | GSList * files = NULL; |
---|
861 | |
---|
862 | for (i=0; i<file_count; ++i) |
---|
863 | files = g_slist_prepend (files, g_file_new_for_uri (uris[i])); |
---|
864 | |
---|
865 | open_files (files, gdata); |
---|
866 | |
---|
867 | /* cleanup */ |
---|
868 | g_slist_foreach (files, (GFunc)g_object_unref, NULL); |
---|
869 | g_slist_free (files); |
---|
870 | g_strfreev (uris); |
---|
871 | |
---|
872 | gtk_drag_finish (drag_context, true, FALSE, time_); |
---|
873 | } |
---|
874 | |
---|
875 | static void |
---|
876 | main_window_setup (struct cbdata * cbdata, GtkWindow * wind) |
---|
877 | { |
---|
878 | GtkWidget * w; |
---|
879 | GtkTreeModel * model; |
---|
880 | GtkTreeSelection * sel; |
---|
881 | |
---|
882 | g_assert (NULL == cbdata->wind); |
---|
883 | cbdata->wind = wind; |
---|
884 | cbdata->sel = sel = GTK_TREE_SELECTION (gtr_window_get_selection (cbdata->wind)); |
---|
885 | |
---|
886 | g_signal_connect (sel, "changed", G_CALLBACK (on_selection_changed), cbdata); |
---|
887 | on_selection_changed (sel, cbdata); |
---|
888 | model = gtr_core_model (cbdata->core); |
---|
889 | g_signal_connect (model, "row-changed", G_CALLBACK (rowChangedCB), cbdata); |
---|
890 | g_signal_connect (wind, "delete-event", G_CALLBACK (winclose), cbdata); |
---|
891 | refresh_actions (cbdata); |
---|
892 | |
---|
893 | /* register to handle URIs that get dragged onto our main window */ |
---|
894 | w = GTK_WIDGET (wind); |
---|
895 | gtk_drag_dest_set (w, GTK_DEST_DEFAULT_ALL, NULL, 0, GDK_ACTION_COPY); |
---|
896 | gtk_drag_dest_add_uri_targets (w); |
---|
897 | g_signal_connect (w, "drag-data-received", G_CALLBACK (on_drag_data_received), cbdata); |
---|
898 | } |
---|
899 | |
---|
900 | static gboolean |
---|
901 | on_session_closed (gpointer gdata) |
---|
902 | { |
---|
903 | GSList * tmp; |
---|
904 | struct cbdata * cbdata = gdata; |
---|
905 | |
---|
906 | tmp = g_slist_copy (cbdata->details); |
---|
907 | g_slist_foreach (tmp, (GFunc)gtk_widget_destroy, NULL); |
---|
908 | g_slist_free (tmp); |
---|
909 | |
---|
910 | if (cbdata->prefs) |
---|
911 | gtk_widget_destroy (GTK_WIDGET (cbdata->prefs)); |
---|
912 | if (cbdata->wind) |
---|
913 | gtk_widget_destroy (GTK_WIDGET (cbdata->wind)); |
---|
914 | g_object_unref (cbdata->core); |
---|
915 | if (cbdata->icon) |
---|
916 | g_object_unref (cbdata->icon); |
---|
917 | g_slist_foreach (cbdata->error_list, (GFunc)g_free, NULL); |
---|
918 | g_slist_free (cbdata->error_list); |
---|
919 | g_slist_foreach (cbdata->duplicates_list, (GFunc)g_free, NULL); |
---|
920 | g_slist_free (cbdata->duplicates_list); |
---|
921 | |
---|
922 | return G_SOURCE_REMOVE; |
---|
923 | } |
---|
924 | |
---|
925 | struct session_close_struct |
---|
926 | { |
---|
927 | tr_session * session; |
---|
928 | struct cbdata * cbdata; |
---|
929 | }; |
---|
930 | |
---|
931 | /* since tr_sessionClose () is a blocking function, |
---|
932 | * delegate its call to another thread here... when it's done, |
---|
933 | * punt the GUI teardown back to the GTK+ thread */ |
---|
934 | static gpointer |
---|
935 | session_close_threadfunc (gpointer gdata) |
---|
936 | { |
---|
937 | struct session_close_struct * data = gdata; |
---|
938 | tr_sessionClose (data->session); |
---|
939 | gdk_threads_add_idle (on_session_closed, data->cbdata); |
---|
940 | g_free (data); |
---|
941 | return NULL; |
---|
942 | } |
---|
943 | |
---|
944 | static void |
---|
945 | exit_now_cb (GtkWidget *w UNUSED, gpointer data UNUSED) |
---|
946 | { |
---|
947 | exit (0); |
---|
948 | } |
---|
949 | |
---|
950 | static void |
---|
951 | on_app_exit (gpointer vdata) |
---|
952 | { |
---|
953 | GtkWidget *r, *p, *b, *w, *c; |
---|
954 | struct cbdata * cbdata = vdata; |
---|
955 | struct session_close_struct * session_close_data; |
---|
956 | |
---|
957 | if (cbdata->is_closing) |
---|
958 | return; |
---|
959 | |
---|
960 | cbdata->is_closing = true; |
---|
961 | |
---|
962 | /* stop the update timer */ |
---|
963 | if (cbdata->timer) |
---|
964 | { |
---|
965 | g_source_remove (cbdata->timer); |
---|
966 | cbdata->timer = 0; |
---|
967 | } |
---|
968 | |
---|
969 | /* stop the refresh-actions timer */ |
---|
970 | if (cbdata->refresh_actions_tag) |
---|
971 | { |
---|
972 | g_source_remove (cbdata->refresh_actions_tag); |
---|
973 | cbdata->refresh_actions_tag = 0; |
---|
974 | } |
---|
975 | |
---|
976 | c = GTK_WIDGET (cbdata->wind); |
---|
977 | gtk_container_remove (GTK_CONTAINER (c), gtk_bin_get_child (GTK_BIN (c))); |
---|
978 | |
---|
979 | r = gtk_alignment_new (0.5, 0.5, 0.01, 0.01); |
---|
980 | gtk_container_add (GTK_CONTAINER (c), r); |
---|
981 | |
---|
982 | p = gtk_grid_new (); |
---|
983 | gtk_grid_set_column_spacing (GTK_GRID (p), GUI_PAD_BIG); |
---|
984 | gtk_container_add (GTK_CONTAINER (r), p); |
---|
985 | |
---|
986 | w = gtk_image_new_from_stock (GTK_STOCK_NETWORK, GTK_ICON_SIZE_DIALOG); |
---|
987 | gtk_grid_attach (GTK_GRID (p), w, 0, 0, 1, 2); |
---|
988 | |
---|
989 | w = gtk_label_new (NULL); |
---|
990 | gtk_label_set_markup (GTK_LABEL (w), _("<b>Closing Connections</b>")); |
---|
991 | gtk_misc_set_alignment (GTK_MISC (w), 0.0, 0.5); |
---|
992 | gtk_grid_attach (GTK_GRID (p), w, 1, 0, 1, 1); |
---|
993 | |
---|
994 | w = gtk_label_new (_("Sending upload/download totals to trackerâŠ")); |
---|
995 | gtk_misc_set_alignment (GTK_MISC (w), 0.0, 0.5); |
---|
996 | gtk_grid_attach (GTK_GRID (p), w, 1, 1, 1, 1); |
---|
997 | |
---|
998 | b = gtk_alignment_new (0.0, 1.0, 0.01, 0.01); |
---|
999 | w = gtk_button_new_with_mnemonic (_("_Quit Now")); |
---|
1000 | g_signal_connect (w, "clicked", G_CALLBACK (exit_now_cb), NULL); |
---|
1001 | gtk_container_add (GTK_CONTAINER (b), w); |
---|
1002 | gtk_grid_attach (GTK_GRID (p), b, 1, 2, 1, 1); |
---|
1003 | |
---|
1004 | gtk_widget_show_all (r); |
---|
1005 | gtk_widget_grab_focus (w); |
---|
1006 | |
---|
1007 | /* clear the UI */ |
---|
1008 | gtr_core_clear (cbdata->core); |
---|
1009 | |
---|
1010 | /* ensure the window is in its previous position & size. |
---|
1011 | * this seems to be necessary because changing the main window's |
---|
1012 | * child seems to unset the size */ |
---|
1013 | gtk_window_resize (cbdata->wind, gtr_pref_int_get (TR_KEY_main_window_width), |
---|
1014 | gtr_pref_int_get (TR_KEY_main_window_height)); |
---|
1015 | gtk_window_move (cbdata->wind, gtr_pref_int_get (TR_KEY_main_window_x), |
---|
1016 | gtr_pref_int_get (TR_KEY_main_window_y)); |
---|
1017 | |
---|
1018 | /* shut down libT */ |
---|
1019 | session_close_data = g_new (struct session_close_struct, 1); |
---|
1020 | session_close_data->cbdata = cbdata; |
---|
1021 | session_close_data->session = gtr_core_close (cbdata->core); |
---|
1022 | g_thread_new ("shutdown-thread", session_close_threadfunc, session_close_data); |
---|
1023 | } |
---|
1024 | |
---|
1025 | static void |
---|
1026 | show_torrent_errors (GtkWindow * window, const char * primary, GSList ** files) |
---|
1027 | { |
---|
1028 | GSList * l; |
---|
1029 | GtkWidget * w; |
---|
1030 | GString * s = g_string_new (NULL); |
---|
1031 | const char * leader = g_slist_length (*files) > 1 |
---|
1032 | ? gtr_get_unicode_string (GTR_UNICODE_BULLET) |
---|
1033 | : ""; |
---|
1034 | |
---|
1035 | for (l=*files; l!=NULL; l=l->next) |
---|
1036 | g_string_append_printf (s, "%s %s\n", leader, (const char*)l->data); |
---|
1037 | |
---|
1038 | w = gtk_message_dialog_new (window, |
---|
1039 | GTK_DIALOG_DESTROY_WITH_PARENT, |
---|
1040 | GTK_MESSAGE_ERROR, |
---|
1041 | GTK_BUTTONS_CLOSE, |
---|
1042 | "%s", primary); |
---|
1043 | gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (w), |
---|
1044 | "%s", s->str); |
---|
1045 | g_signal_connect_swapped (w, "response", |
---|
1046 | G_CALLBACK (gtk_widget_destroy), w); |
---|
1047 | gtk_widget_show (w); |
---|
1048 | g_string_free (s, TRUE); |
---|
1049 | |
---|
1050 | g_slist_foreach (*files, (GFunc)g_free, NULL); |
---|
1051 | g_slist_free (*files); |
---|
1052 | *files = NULL; |
---|
1053 | } |
---|
1054 | |
---|
1055 | static void |
---|
1056 | flush_torrent_errors (struct cbdata * cbdata) |
---|
1057 | { |
---|
1058 | if (cbdata->error_list) |
---|
1059 | show_torrent_errors (cbdata->wind, |
---|
1060 | ngettext ("Couldn't add corrupt torrent", |
---|
1061 | "Couldn't add corrupt torrents", |
---|
1062 | g_slist_length (cbdata->error_list)), |
---|
1063 | &cbdata->error_list); |
---|
1064 | |
---|
1065 | if (cbdata->duplicates_list) |
---|
1066 | show_torrent_errors (cbdata->wind, |
---|
1067 | ngettext ("Couldn't add duplicate torrent", |
---|
1068 | "Couldn't add duplicate torrents", |
---|
1069 | g_slist_length (cbdata->duplicates_list)), |
---|
1070 | &cbdata->duplicates_list); |
---|
1071 | } |
---|
1072 | |
---|
1073 | static void |
---|
1074 | on_core_error (TrCore * core UNUSED, guint code, const char * msg, struct cbdata * c) |
---|
1075 | { |
---|
1076 | switch (code) |
---|
1077 | { |
---|
1078 | case TR_PARSE_ERR: |
---|
1079 | c->error_list = g_slist_append (c->error_list, g_path_get_basename (msg)); |
---|
1080 | break; |
---|
1081 | |
---|
1082 | case TR_PARSE_DUPLICATE: |
---|
1083 | c->duplicates_list = g_slist_append (c->duplicates_list, g_strdup (msg)); |
---|
1084 | break; |
---|
1085 | |
---|
1086 | case TR_CORE_ERR_NO_MORE_TORRENTS: |
---|
1087 | flush_torrent_errors (c); |
---|
1088 | break; |
---|
1089 | |
---|
1090 | default: |
---|
1091 | g_assert_not_reached (); |
---|
1092 | break; |
---|
1093 | } |
---|
1094 | } |
---|
1095 | |
---|
1096 | static gboolean |
---|
1097 | on_main_window_focus_in (GtkWidget * widget UNUSED, |
---|
1098 | GdkEventFocus * event UNUSED, |
---|
1099 | gpointer gdata) |
---|
1100 | { |
---|
1101 | struct cbdata * cbdata = gdata; |
---|
1102 | |
---|
1103 | if (cbdata->wind) |
---|
1104 | gtk_window_set_urgency_hint (cbdata->wind, FALSE); |
---|
1105 | |
---|
1106 | return FALSE; |
---|
1107 | } |
---|
1108 | |
---|
1109 | static void |
---|
1110 | on_add_torrent (TrCore * core, tr_ctor * ctor, gpointer gdata) |
---|
1111 | { |
---|
1112 | struct cbdata * cbdata = gdata; |
---|
1113 | GtkWidget * w = gtr_torrent_options_dialog_new (cbdata->wind, core, ctor); |
---|
1114 | |
---|
1115 | g_signal_connect (w, "focus-in-event", |
---|
1116 | G_CALLBACK (on_main_window_focus_in), cbdata); |
---|
1117 | if (cbdata->wind) |
---|
1118 | gtk_window_set_urgency_hint (cbdata->wind, TRUE); |
---|
1119 | |
---|
1120 | gtk_widget_show (w); |
---|
1121 | } |
---|
1122 | |
---|
1123 | static void |
---|
1124 | on_prefs_changed (TrCore * core UNUSED, const tr_quark key, gpointer data) |
---|
1125 | { |
---|
1126 | struct cbdata * cbdata = data; |
---|
1127 | tr_session * tr = gtr_core_session (cbdata->core); |
---|
1128 | |
---|
1129 | switch (key) |
---|
1130 | { |
---|
1131 | case TR_KEY_encryption: |
---|
1132 | tr_sessionSetEncryption (tr, gtr_pref_int_get (key)); |
---|
1133 | break; |
---|
1134 | |
---|
1135 | case TR_KEY_download_dir: |
---|
1136 | tr_sessionSetDownloadDir (tr, gtr_pref_string_get (key)); |
---|
1137 | break; |
---|
1138 | |
---|
1139 | case TR_KEY_message_level: |
---|
1140 | tr_logSetLevel (gtr_pref_int_get (key)); |
---|
1141 | break; |
---|
1142 | |
---|
1143 | case TR_KEY_peer_port: |
---|
1144 | tr_sessionSetPeerPort (tr, gtr_pref_int_get (key)); |
---|
1145 | break; |
---|
1146 | |
---|
1147 | case TR_KEY_blocklist_enabled: |
---|
1148 | tr_blocklistSetEnabled (tr, gtr_pref_flag_get (key)); |
---|
1149 | break; |
---|
1150 | |
---|
1151 | case TR_KEY_blocklist_url: |
---|
1152 | tr_blocklistSetURL (tr, gtr_pref_string_get (key)); |
---|
1153 | break; |
---|
1154 | |
---|
1155 | case TR_KEY_show_notification_area_icon: |
---|
1156 | { |
---|
1157 | const bool show = gtr_pref_flag_get (key); |
---|
1158 | if (show && !cbdata->icon) |
---|
1159 | cbdata->icon = gtr_icon_new (cbdata->core); |
---|
1160 | else if (!show && cbdata->icon) |
---|
1161 | g_clear_object (&cbdata->icon); |
---|
1162 | break; |
---|
1163 | } |
---|
1164 | |
---|
1165 | case TR_KEY_speed_limit_down_enabled: |
---|
1166 | tr_sessionLimitSpeed (tr, TR_DOWN, gtr_pref_flag_get (key)); |
---|
1167 | break; |
---|
1168 | |
---|
1169 | case TR_KEY_speed_limit_down: |
---|
1170 | tr_sessionSetSpeedLimit_KBps (tr, TR_DOWN, gtr_pref_int_get (key)); |
---|
1171 | break; |
---|
1172 | |
---|
1173 | case TR_KEY_speed_limit_up_enabled: |
---|
1174 | tr_sessionLimitSpeed (tr, TR_UP, gtr_pref_flag_get (key)); |
---|
1175 | break; |
---|
1176 | |
---|
1177 | case TR_KEY_speed_limit_up: |
---|
1178 | tr_sessionSetSpeedLimit_KBps (tr, TR_UP, gtr_pref_int_get (key)); |
---|
1179 | break; |
---|
1180 | |
---|
1181 | case TR_KEY_ratio_limit_enabled: |
---|
1182 | tr_sessionSetRatioLimited (tr, gtr_pref_flag_get (key)); |
---|
1183 | break; |
---|
1184 | |
---|
1185 | case TR_KEY_ratio_limit: |
---|
1186 | tr_sessionSetRatioLimit (tr, gtr_pref_double_get (key)); |
---|
1187 | break; |
---|
1188 | |
---|
1189 | case TR_KEY_idle_seeding_limit: |
---|
1190 | tr_sessionSetIdleLimit (tr, gtr_pref_int_get (key)); |
---|
1191 | break; |
---|
1192 | |
---|
1193 | case TR_KEY_idle_seeding_limit_enabled: |
---|
1194 | tr_sessionSetIdleLimited (tr, gtr_pref_flag_get (key)); |
---|
1195 | break; |
---|
1196 | |
---|
1197 | case TR_KEY_port_forwarding_enabled: |
---|
1198 | tr_sessionSetPortForwardingEnabled (tr, gtr_pref_flag_get (key)); |
---|
1199 | break; |
---|
1200 | |
---|
1201 | case TR_KEY_pex_enabled: |
---|
1202 | tr_sessionSetPexEnabled (tr, gtr_pref_flag_get (key)); |
---|
1203 | break; |
---|
1204 | |
---|
1205 | case TR_KEY_rename_partial_files: |
---|
1206 | tr_sessionSetIncompleteFileNamingEnabled (tr, gtr_pref_flag_get (key)); |
---|
1207 | break; |
---|
1208 | |
---|
1209 | case TR_KEY_download_queue_size: |
---|
1210 | tr_sessionSetQueueSize (tr, TR_DOWN, gtr_pref_int_get (key)); |
---|
1211 | break; |
---|
1212 | |
---|
1213 | case TR_KEY_queue_stalled_minutes: |
---|
1214 | tr_sessionSetQueueStalledMinutes (tr, gtr_pref_int_get (key)); |
---|
1215 | break; |
---|
1216 | |
---|
1217 | case TR_KEY_dht_enabled: |
---|
1218 | tr_sessionSetDHTEnabled (tr, gtr_pref_flag_get (key)); |
---|
1219 | break; |
---|
1220 | |
---|
1221 | case TR_KEY_utp_enabled: |
---|
1222 | tr_sessionSetUTPEnabled (tr, gtr_pref_flag_get (key)); |
---|
1223 | break; |
---|
1224 | |
---|
1225 | case TR_KEY_lpd_enabled: |
---|
1226 | tr_sessionSetLPDEnabled (tr, gtr_pref_flag_get (key)); |
---|
1227 | break; |
---|
1228 | |
---|
1229 | case TR_KEY_rpc_port: |
---|
1230 | tr_sessionSetRPCPort (tr, gtr_pref_int_get (key)); |
---|
1231 | break; |
---|
1232 | |
---|
1233 | case TR_KEY_rpc_enabled: |
---|
1234 | tr_sessionSetRPCEnabled (tr, gtr_pref_flag_get (key)); |
---|
1235 | break; |
---|
1236 | |
---|
1237 | case TR_KEY_rpc_whitelist: |
---|
1238 | tr_sessionSetRPCWhitelist (tr, gtr_pref_string_get (key)); |
---|
1239 | break; |
---|
1240 | |
---|
1241 | case TR_KEY_rpc_whitelist_enabled: |
---|
1242 | tr_sessionSetRPCWhitelistEnabled (tr, gtr_pref_flag_get (key)); |
---|
1243 | break; |
---|
1244 | |
---|
1245 | case TR_KEY_rpc_username: |
---|
1246 | tr_sessionSetRPCUsername (tr, gtr_pref_string_get (key)); |
---|
1247 | break; |
---|
1248 | |
---|
1249 | case TR_KEY_rpc_password: |
---|
1250 | tr_sessionSetRPCPassword (tr, gtr_pref_string_get (key)); |
---|
1251 | break; |
---|
1252 | |
---|
1253 | case TR_KEY_rpc_authentication_required: |
---|
1254 | tr_sessionSetRPCPasswordEnabled (tr, gtr_pref_flag_get (key)); |
---|
1255 | break; |
---|
1256 | |
---|
1257 | case TR_KEY_alt_speed_up: |
---|
1258 | tr_sessionSetAltSpeed_KBps (tr, TR_UP, gtr_pref_int_get (key)); |
---|
1259 | break; |
---|
1260 | |
---|
1261 | case TR_KEY_alt_speed_down: |
---|
1262 | tr_sessionSetAltSpeed_KBps (tr, TR_DOWN, gtr_pref_int_get (key)); |
---|
1263 | break; |
---|
1264 | |
---|
1265 | case TR_KEY_alt_speed_enabled: |
---|
1266 | { |
---|
1267 | const bool b = gtr_pref_flag_get (key); |
---|
1268 | tr_sessionUseAltSpeed (tr, b); |
---|
1269 | gtr_action_set_toggled (tr_quark_get_string(key,NULL), b); |
---|
1270 | break; |
---|
1271 | } |
---|
1272 | |
---|
1273 | case TR_KEY_alt_speed_time_begin: |
---|
1274 | tr_sessionSetAltSpeedBegin (tr, gtr_pref_int_get (key)); |
---|
1275 | break; |
---|
1276 | |
---|
1277 | case TR_KEY_alt_speed_time_end: |
---|
1278 | tr_sessionSetAltSpeedEnd (tr, gtr_pref_int_get (key)); |
---|
1279 | break; |
---|
1280 | |
---|
1281 | case TR_KEY_alt_speed_time_enabled: |
---|
1282 | tr_sessionUseAltSpeedTime (tr, gtr_pref_flag_get (key)); |
---|
1283 | break; |
---|
1284 | |
---|
1285 | case TR_KEY_alt_speed_time_day: |
---|
1286 | tr_sessionSetAltSpeedDay (tr, gtr_pref_int_get (key)); |
---|
1287 | break; |
---|
1288 | |
---|
1289 | case TR_KEY_peer_port_random_on_start: |
---|
1290 | tr_sessionSetPeerPortRandomOnStart (tr, gtr_pref_flag_get (key)); |
---|
1291 | break; |
---|
1292 | |
---|
1293 | case TR_KEY_incomplete_dir: |
---|
1294 | tr_sessionSetIncompleteDir (tr, gtr_pref_string_get (key)); |
---|
1295 | break; |
---|
1296 | |
---|
1297 | case TR_KEY_incomplete_dir_enabled: |
---|
1298 | tr_sessionSetIncompleteDirEnabled (tr, gtr_pref_flag_get (key)); |
---|
1299 | break; |
---|
1300 | |
---|
1301 | case TR_KEY_script_torrent_done_enabled: |
---|
1302 | tr_sessionSetTorrentDoneScriptEnabled (tr, gtr_pref_flag_get (key)); |
---|
1303 | break; |
---|
1304 | |
---|
1305 | case TR_KEY_script_torrent_done_filename: |
---|
1306 | tr_sessionSetTorrentDoneScript (tr, gtr_pref_string_get (key)); |
---|
1307 | break; |
---|
1308 | |
---|
1309 | case TR_KEY_start_added_torrents: |
---|
1310 | tr_sessionSetPaused (tr, !gtr_pref_flag_get (key)); |
---|
1311 | break; |
---|
1312 | |
---|
1313 | case TR_KEY_trash_original_torrent_files: |
---|
1314 | tr_sessionSetDeleteSource (tr, gtr_pref_flag_get (key)); |
---|
1315 | break; |
---|
1316 | |
---|
1317 | default: |
---|
1318 | break; |
---|
1319 | } |
---|
1320 | } |
---|
1321 | |
---|
1322 | static gboolean |
---|
1323 | update_model_once (gpointer gdata) |
---|
1324 | { |
---|
1325 | struct cbdata *data = gdata; |
---|
1326 | |
---|
1327 | /* update the torrent data in the model */ |
---|
1328 | gtr_core_update (data->core); |
---|
1329 | |
---|
1330 | /* refresh the main window's statusbar and toolbar buttons */ |
---|
1331 | if (data->wind != NULL) |
---|
1332 | gtr_window_refresh (data->wind); |
---|
1333 | |
---|
1334 | /* update the actions */ |
---|
1335 | refresh_actions (data); |
---|
1336 | |
---|
1337 | /* update the status tray icon */ |
---|
1338 | if (data->icon != NULL) |
---|
1339 | gtr_icon_refresh (data->icon); |
---|
1340 | |
---|
1341 | data->update_model_soon_tag = 0; |
---|
1342 | return G_SOURCE_REMOVE; |
---|
1343 | } |
---|
1344 | |
---|
1345 | static void |
---|
1346 | update_model_soon (gpointer gdata) |
---|
1347 | { |
---|
1348 | struct cbdata *data = gdata; |
---|
1349 | |
---|
1350 | if (data->update_model_soon_tag == 0) |
---|
1351 | data->update_model_soon_tag = gdk_threads_add_idle (update_model_once, data); |
---|
1352 | } |
---|
1353 | |
---|
1354 | static gboolean |
---|
1355 | update_model_loop (gpointer gdata) |
---|
1356 | { |
---|
1357 | const gboolean done = global_sigcount; |
---|
1358 | |
---|
1359 | if (!done) |
---|
1360 | update_model_once (gdata); |
---|
1361 | |
---|
1362 | return !done; |
---|
1363 | } |
---|
1364 | |
---|
1365 | static void |
---|
1366 | show_about_dialog (GtkWindow * parent) |
---|
1367 | { |
---|
1368 | const char * uri = "http://www.transmissionbt.com/"; |
---|
1369 | const char * authors[] = { "Jordan Lee (Backend; GTK+)", |
---|
1370 | "Mitchell Livingston (Backend; OS X)", |
---|
1371 | NULL }; |
---|
1372 | |
---|
1373 | gtk_show_about_dialog (parent, |
---|
1374 | "authors", authors, |
---|
1375 | "comments", _("A fast and easy BitTorrent client"), |
---|
1376 | "copyright", _("Copyright (c) The Transmission Project"), |
---|
1377 | "logo-icon-name", MY_CONFIG_NAME, |
---|
1378 | "name", g_get_application_name (), |
---|
1379 | /* Translators: translate "translator-credits" as your name |
---|
1380 | to have it appear in the credits in the "About" |
---|
1381 | dialog */ |
---|
1382 | "translator-credits", _("translator-credits"), |
---|
1383 | "version", LONG_VERSION_STRING, |
---|
1384 | "website", uri, |
---|
1385 | "website-label", uri, |
---|
1386 | #ifdef SHOW_LICENSE |
---|
1387 | "license", LICENSE, |
---|
1388 | "wrap-license", TRUE, |
---|
1389 | #endif |
---|
1390 | NULL); |
---|
1391 | } |
---|
1392 | |
---|
1393 | static void |
---|
1394 | append_id_to_benc_list (GtkTreeModel * m, GtkTreePath * path UNUSED, |
---|
1395 | GtkTreeIter * iter, gpointer list) |
---|
1396 | { |
---|
1397 | tr_torrent * tor = NULL; |
---|
1398 | gtk_tree_model_get (m, iter, MC_TORRENT, &tor, -1); |
---|
1399 | tr_variantListAddInt (list, tr_torrentId (tor)); |
---|
1400 | } |
---|
1401 | |
---|
1402 | static gboolean |
---|
1403 | call_rpc_for_selected_torrents (struct cbdata * data, const char * method) |
---|
1404 | { |
---|
1405 | tr_variant top, *args, *ids; |
---|
1406 | gboolean invoked = FALSE; |
---|
1407 | GtkTreeSelection * s = data->sel; |
---|
1408 | tr_session * session = gtr_core_session (data->core); |
---|
1409 | |
---|
1410 | tr_variantInitDict (&top, 2); |
---|
1411 | tr_variantDictAddStr (&top, TR_KEY_method, method); |
---|
1412 | args = tr_variantDictAddDict (&top, TR_KEY_arguments, 1); |
---|
1413 | ids = tr_variantDictAddList (args, TR_KEY_ids, 0); |
---|
1414 | gtk_tree_selection_selected_foreach (s, append_id_to_benc_list, ids); |
---|
1415 | |
---|
1416 | if (tr_variantListSize (ids) != 0) |
---|
1417 | { |
---|
1418 | tr_rpc_request_exec_json (session, &top, NULL, NULL); |
---|
1419 | invoked = TRUE; |
---|
1420 | } |
---|
1421 | |
---|
1422 | tr_variantFree (&top); |
---|
1423 | return invoked; |
---|
1424 | } |
---|
1425 | |
---|
1426 | static void |
---|
1427 | open_folder_foreach (GtkTreeModel * model, GtkTreePath * path UNUSED, |
---|
1428 | GtkTreeIter * iter, gpointer core) |
---|
1429 | { |
---|
1430 | int id; |
---|
1431 | gtk_tree_model_get (model, iter, MC_TORRENT_ID, &id, -1); |
---|
1432 | gtr_core_open_folder (core, id); |
---|
1433 | } |
---|
1434 | |
---|
1435 | static gboolean |
---|
1436 | on_message_window_closed (void) |
---|
1437 | { |
---|
1438 | gtr_action_set_toggled ("toggle-message-log", FALSE); |
---|
1439 | return FALSE; |
---|
1440 | } |
---|
1441 | |
---|
1442 | static void |
---|
1443 | accumulate_selected_torrents (GtkTreeModel * model, GtkTreePath * path UNUSED, |
---|
1444 | GtkTreeIter * iter, gpointer gdata) |
---|
1445 | { |
---|
1446 | int id; |
---|
1447 | GSList ** data = gdata; |
---|
1448 | |
---|
1449 | gtk_tree_model_get (model, iter, MC_TORRENT_ID, &id, -1); |
---|
1450 | *data = g_slist_append (*data, GINT_TO_POINTER (id)); |
---|
1451 | } |
---|
1452 | |
---|
1453 | static void |
---|
1454 | remove_selected (struct cbdata * data, gboolean delete_files) |
---|
1455 | { |
---|
1456 | GSList * l = NULL; |
---|
1457 | |
---|
1458 | gtk_tree_selection_selected_foreach (data->sel, accumulate_selected_torrents, &l); |
---|
1459 | |
---|
1460 | if (l != NULL) |
---|
1461 | gtr_confirm_remove (data->wind, data->core, l, delete_files); |
---|
1462 | } |
---|
1463 | |
---|
1464 | static void |
---|
1465 | start_all_torrents (struct cbdata * data) |
---|
1466 | { |
---|
1467 | tr_session * session = gtr_core_session (data->core); |
---|
1468 | tr_variant request; |
---|
1469 | |
---|
1470 | tr_variantInitDict (&request, 1); |
---|
1471 | tr_variantDictAddStr (&request, TR_KEY_method, "torrent-start"); |
---|
1472 | tr_rpc_request_exec_json (session, &request, NULL, NULL); |
---|
1473 | tr_variantFree (&request); |
---|
1474 | } |
---|
1475 | |
---|
1476 | static void |
---|
1477 | pause_all_torrents (struct cbdata * data) |
---|
1478 | { |
---|
1479 | tr_session * session = gtr_core_session (data->core); |
---|
1480 | tr_variant request; |
---|
1481 | |
---|
1482 | tr_variantInitDict (&request, 1); |
---|
1483 | tr_variantDictAddStr (&request, TR_KEY_method, "torrent-stop"); |
---|
1484 | tr_rpc_request_exec_json (session, &request, NULL, NULL); |
---|
1485 | tr_variantFree (&request); |
---|
1486 | } |
---|
1487 | |
---|
1488 | static tr_torrent* |
---|
1489 | get_first_selected_torrent (struct cbdata * data) |
---|
1490 | { |
---|
1491 | tr_torrent * tor = NULL; |
---|
1492 | GtkTreeModel * m; |
---|
1493 | GList * l = gtk_tree_selection_get_selected_rows (data->sel, &m); |
---|
1494 | if (l != NULL) |
---|
1495 | { |
---|
1496 | GtkTreePath * p = l->data; |
---|
1497 | GtkTreeIter i; |
---|
1498 | if (gtk_tree_model_get_iter (m, &i, p)) |
---|
1499 | gtk_tree_model_get (m, &i, MC_TORRENT, &tor, -1); |
---|
1500 | } |
---|
1501 | g_list_foreach (l, (GFunc)gtk_tree_path_free, NULL); |
---|
1502 | g_list_free (l); |
---|
1503 | return tor; |
---|
1504 | } |
---|
1505 | |
---|
1506 | static void |
---|
1507 | copy_magnet_link_to_clipboard (GtkWidget * w, tr_torrent * tor) |
---|
1508 | { |
---|
1509 | char * magnet = tr_torrentGetMagnetLink (tor); |
---|
1510 | GdkDisplay * display = gtk_widget_get_display (w); |
---|
1511 | GdkAtom selection; |
---|
1512 | GtkClipboard * clipboard; |
---|
1513 | |
---|
1514 | /* this is The Right Thing for copy/paste... */ |
---|
1515 | selection = GDK_SELECTION_CLIPBOARD; |
---|
1516 | clipboard = gtk_clipboard_get_for_display (display, selection); |
---|
1517 | gtk_clipboard_set_text (clipboard, magnet, -1); |
---|
1518 | |
---|
1519 | /* ...but people using plain ol' X need this instead */ |
---|
1520 | selection = GDK_SELECTION_PRIMARY; |
---|
1521 | clipboard = gtk_clipboard_get_for_display (display, selection); |
---|
1522 | gtk_clipboard_set_text (clipboard, magnet, -1); |
---|
1523 | |
---|
1524 | /* cleanup */ |
---|
1525 | tr_free (magnet); |
---|
1526 | } |
---|
1527 | |
---|
1528 | void |
---|
1529 | gtr_actions_handler (const char * action_name, gpointer user_data) |
---|
1530 | { |
---|
1531 | gboolean changed = FALSE; |
---|
1532 | struct cbdata * data = user_data; |
---|
1533 | |
---|
1534 | if (!g_strcmp0 (action_name, "open-torrent-from-url")) |
---|
1535 | { |
---|
1536 | GtkWidget * w = gtr_torrent_open_from_url_dialog_new (data->wind, data->core); |
---|
1537 | gtk_widget_show (w); |
---|
1538 | } |
---|
1539 | else if (!g_strcmp0 (action_name, "open-torrent-menu") |
---|
1540 | || !g_strcmp0 (action_name, "open-torrent-toolbar")) |
---|
1541 | { |
---|
1542 | GtkWidget * w = gtr_torrent_open_from_file_dialog_new (data->wind, data->core); |
---|
1543 | gtk_widget_show (w); |
---|
1544 | } |
---|
1545 | else if (!g_strcmp0 (action_name, "show-stats")) |
---|
1546 | { |
---|
1547 | GtkWidget * dialog = gtr_stats_dialog_new (data->wind, data->core); |
---|
1548 | gtk_widget_show (dialog); |
---|
1549 | } |
---|
1550 | else if (!g_strcmp0 (action_name, "donate")) |
---|
1551 | { |
---|
1552 | gtr_open_uri ("http://www.transmissionbt.com/donate.php"); |
---|
1553 | } |
---|
1554 | else if (!g_strcmp0 (action_name, "pause-all-torrents")) |
---|
1555 | { |
---|
1556 | pause_all_torrents (data); |
---|
1557 | } |
---|
1558 | else if (!g_strcmp0 (action_name, "start-all-torrents")) |
---|
1559 | { |
---|
1560 | start_all_torrents (data); |
---|
1561 | } |
---|
1562 | else if (!g_strcmp0 (action_name, "copy-magnet-link-to-clipboard")) |
---|
1563 | { |
---|
1564 | tr_torrent * tor = get_first_selected_torrent (data); |
---|
1565 | if (tor != NULL) |
---|
1566 | { |
---|
1567 | copy_magnet_link_to_clipboard (GTK_WIDGET (data->wind), tor); |
---|
1568 | } |
---|
1569 | } |
---|
1570 | else if (!g_strcmp0 (action_name, "relocate-torrent")) |
---|
1571 | { |
---|
1572 | GSList * ids = get_selected_torrent_ids (data); |
---|
1573 | if (ids != NULL) |
---|
1574 | { |
---|
1575 | GtkWindow * parent = data->wind; |
---|
1576 | GtkWidget * w = gtr_relocate_dialog_new (parent, data->core, ids); |
---|
1577 | gtk_widget_show (w); |
---|
1578 | } |
---|
1579 | } |
---|
1580 | else if (!g_strcmp0 (action_name, "torrent-start") |
---|
1581 | || !g_strcmp0 (action_name, "torrent-start-now") |
---|
1582 | || !g_strcmp0 (action_name, "torrent-stop") |
---|
1583 | || !g_strcmp0 (action_name, "torrent-reannounce") |
---|
1584 | || !g_strcmp0 (action_name, "torrent-verify") |
---|
1585 | || !g_strcmp0 (action_name, "queue-move-top") |
---|
1586 | || !g_strcmp0 (action_name, "queue-move-up") |
---|
1587 | || !g_strcmp0 (action_name, "queue-move-down") |
---|
1588 | || !g_strcmp0 (action_name, "queue-move-bottom")) |
---|
1589 | { |
---|
1590 | changed |= call_rpc_for_selected_torrents (data, action_name); |
---|
1591 | } |
---|
1592 | else if (!g_strcmp0 (action_name, "open-torrent-folder")) |
---|
1593 | { |
---|
1594 | gtk_tree_selection_selected_foreach (data->sel, open_folder_foreach, data->core); |
---|
1595 | } |
---|
1596 | else if (!g_strcmp0 (action_name, "show-torrent-properties")) |
---|
1597 | { |
---|
1598 | show_details_dialog_for_selected_torrents (data); |
---|
1599 | } |
---|
1600 | else if (!g_strcmp0 (action_name, "new-torrent")) |
---|
1601 | { |
---|
1602 | GtkWidget * w = gtr_torrent_creation_dialog_new (data->wind, data->core); |
---|
1603 | gtk_widget_show (w); |
---|
1604 | } |
---|
1605 | else if (!g_strcmp0 (action_name, "remove-torrent")) |
---|
1606 | { |
---|
1607 | remove_selected (data, FALSE); |
---|
1608 | } |
---|
1609 | else if (!g_strcmp0 (action_name, "delete-torrent")) |
---|
1610 | { |
---|
1611 | remove_selected (data, TRUE); |
---|
1612 | } |
---|
1613 | else if (!g_strcmp0 (action_name, "quit")) |
---|
1614 | { |
---|
1615 | on_app_exit (data); |
---|
1616 | } |
---|
1617 | else if (!g_strcmp0 (action_name, "select-all")) |
---|
1618 | { |
---|
1619 | gtk_tree_selection_select_all (data->sel); |
---|
1620 | } |
---|
1621 | else if (!g_strcmp0 (action_name, "deselect-all")) |
---|
1622 | { |
---|
1623 | gtk_tree_selection_unselect_all (data->sel); |
---|
1624 | } |
---|
1625 | else if (!g_strcmp0 (action_name, "edit-preferences")) |
---|
1626 | { |
---|
1627 | if (NULL == data->prefs) |
---|
1628 | { |
---|
1629 | data->prefs = gtr_prefs_dialog_new (data->wind, G_OBJECT (data->core)); |
---|
1630 | g_signal_connect (data->prefs, "destroy", |
---|
1631 | G_CALLBACK (gtk_widget_destroyed), &data->prefs); |
---|
1632 | } |
---|
1633 | gtr_window_present (GTK_WINDOW (data->prefs)); |
---|
1634 | } |
---|
1635 | else if (!g_strcmp0 (action_name, "toggle-message-log")) |
---|
1636 | { |
---|
1637 | if (!data->msgwin) |
---|
1638 | { |
---|
1639 | GtkWidget * win = gtr_message_log_window_new (data->wind, data->core); |
---|
1640 | g_signal_connect (win, "destroy", G_CALLBACK (on_message_window_closed), NULL); |
---|
1641 | data->msgwin = win; |
---|
1642 | } |
---|
1643 | else |
---|
1644 | { |
---|
1645 | gtr_action_set_toggled ("toggle-message-log", FALSE); |
---|
1646 | gtk_widget_destroy (data->msgwin); |
---|
1647 | data->msgwin = NULL; |
---|
1648 | } |
---|
1649 | } |
---|
1650 | else if (!g_strcmp0 (action_name, "show-about-dialog")) |
---|
1651 | { |
---|
1652 | show_about_dialog (data->wind); |
---|
1653 | } |
---|
1654 | else if (!g_strcmp0 (action_name, "help")) |
---|
1655 | { |
---|
1656 | gtr_open_uri (gtr_get_help_uri ()); |
---|
1657 | } |
---|
1658 | else if (!g_strcmp0 (action_name, "toggle-main-window")) |
---|
1659 | { |
---|
1660 | toggleMainWindow (data); |
---|
1661 | } |
---|
1662 | else if (!g_strcmp0 (action_name, "present-main-window")) |
---|
1663 | { |
---|
1664 | presentMainWindow (data); |
---|
1665 | } |
---|
1666 | else |
---|
1667 | { |
---|
1668 | g_error ("Unhandled action: %s", action_name); |
---|
1669 | } |
---|
1670 | |
---|
1671 | if (changed) |
---|
1672 | update_model_soon (data); |
---|
1673 | } |
---|