No issues found
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
2 *
3 * Implementation of DACP (iTunes Remote) pairing page object
4 *
5 * Copyright (C) 2010 Alexandre Rosenfeld <alexandre.rosenfeld@gmail.com>
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
11 *
12 * The Rhythmbox authors hereby grant permission for non-GPL compatible
13 * GStreamer plugins to be used and distributed together with GStreamer
14 * and Rhythmbox. This permission is above and beyond the permissions granted
15 * by the GPL license by which Rhythmbox is covered. If you modify this code
16 * you may extend this exception to your version of the code, but you are not
17 * obligated to do so. If you do not wish to do so, delete this exception
18 * statement from your version.
19 *
20 * This program is distributed in the hope that it will be useful,
21 * but WITHOUT ANY WARRANTY; without even the implied warranty of
22 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23 * GNU General Public License for more details.
24 *
25 * You should have received a copy of the GNU General Public License
26 * along with this program; if not, write to the Free Software
27 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
28 *
29 */
30
31 #include "config.h"
32
33 #include <string.h>
34 #include <ctype.h>
35 #include <math.h>
36
37 #include <glib/gi18n.h>
38 #include <gtk/gtk.h>
39
40 #include "rhythmdb.h"
41 #include "rb-shell.h"
42 #include "rb-display-page-group.h"
43 #include "rb-stock-icons.h"
44 #include "rb-debug.h"
45 #include "rb-util.h"
46 #include "rb-file-helpers.h"
47 #include "rb-builder-helpers.h"
48 #include "rb-dialog.h"
49 #include "rb-playlist-manager.h"
50 #include "rb-shell-player.h"
51 #include "rb-display-page-model.h"
52 #include "rb-rhythmdb-dmap-db-adapter.h"
53 #include "rb-dmap-container-db-adapter.h"
54
55 #include "rb-daap-plugin.h"
56 #include "rb-daap-sharing.h"
57 #include "rb-dacp-player.h"
58
59 #include <libdmapsharing/dmap.h>
60
61 #include "rb-dacp-pairing-page.h"
62
63 static void impl_constructed (GObject *object);
64 static void impl_dispose (GObject *object);
65 static void impl_set_property (GObject *object,
66 guint prop_id,
67 const GValue *value,
68 GParamSpec *pspec);
69 static void impl_get_property (GObject *object,
70 guint prop_id,
71 GValue *value,
72 GParamSpec *pspec);
73
74 static void rb_dacp_pairing_page_connecting (RBDACPPairingPage *page, gboolean connecting);
75 static gboolean entry_insert_text_cb (GtkWidget *entry, gchar *text, gint len, gint *position, RBDACPPairingPage *page);
76 static gboolean entry_backspace_cb (GtkWidget *entry, RBDACPPairingPage *page);
77 static void remote_paired_cb (DACPShare *share, gchar *service_name, gboolean connected, RBDACPPairingPage *page);
78
79 static void dacp_remote_added (DACPShare *share, gchar *service_name, gchar *display_name, RBDaapPlugin *plugin);
80 static void dacp_remote_removed (DACPShare *share, gchar *service_name, RBDaapPlugin *plugin);
81
82 /* DACPShare signals */
83 static gboolean dacp_lookup_guid (DACPShare *share, gchar *guid, GSettings *settings);
84 static void dacp_add_guid (DACPShare *share, gchar *guid, GSettings *settings);
85
86 static void dacp_player_updated (RBDACPPlayer *player, DACPShare *share);
87
88 struct RBDACPPairingPagePrivate
89 {
90 char *service_name;
91
92 gboolean done_pairing;
93
94 DACPShare *dacp_share;
95
96 GtkBuilder *builder;
97 GtkWidget *entries[4];
98 GtkWidget *finished_widget;
99 GtkWidget *pairing_widget;
100 GtkWidget *pairing_status_widget;
101 };
102
103 enum {
104 PROP_0,
105 PROP_SERVICE_NAME
106 };
107
108 G_DEFINE_DYNAMIC_TYPE (RBDACPPairingPage, rb_dacp_pairing_page, RB_TYPE_DISPLAY_PAGE)
109
110 static gboolean
111 entry_insert_text_cb (GtkWidget *entry, gchar *text, gint len, gint *position, RBDACPPairingPage *page)
112 {
113 gchar new_char = text[*position];
114 gint entry_pos = 0;
115 gchar passcode[4];
116 int i;
117
118 /* Find out which entry the user just entered text */
119 for (entry_pos = 0; entry_pos < 4; entry_pos++) {
120 if (entry == page->priv->entries[entry_pos]) {
121 break;
122 }
123 }
124
125 if (!isdigit (new_char)) {
126 /* is this a number? If not, don't let it in */
127 g_signal_stop_emission_by_name (entry, "insert-text");
128 return TRUE;
129 }
130 if (entry_pos < 3) {
131 /* Focus the next entry */
132 gtk_widget_grab_focus (page->priv->entries[entry_pos + 1]);
133 } else if (entry_pos == 3) {
134 /* The user entered all 4 characters of the passcode, so let's pair */
135 for (i = 0; i < 3; i++) {
136 const gchar *text = gtk_entry_get_text (GTK_ENTRY (page->priv->entries[i]));
137 passcode[i] = text[0];
138 }
139 /* The last character is still not in the entry */
140 passcode[3] = new_char;
141 rb_dacp_pairing_page_connecting (page, TRUE);
142 /* Let DACPShare do the heavy-lifting */
143 dacp_share_pair (page->priv->dacp_share,
144 page->priv->service_name,
145 passcode);
146 }
147 /* let the default handler display the number */
148 return FALSE;
149 }
150
151 static gboolean
152 entry_backspace_cb (GtkWidget *entry, RBDACPPairingPage *page)
153 {
154 gint entry_pos = 0;
155
156 /* Find out which entry the user just entered text */
157 for (entry_pos = 0; entry_pos < 4; entry_pos++) {
158 if (entry == page->priv->entries[entry_pos]) {
159 break;
160 }
161 }
162
163 if (entry_pos > 0) {
164 gtk_entry_set_text (GTK_ENTRY (page->priv->entries[entry_pos]), "");
165 /* Focus the previous entry */
166 gtk_widget_grab_focus (page->priv->entries[entry_pos - 1]);
167 }
168
169 return FALSE;
170 }
171
172 static gboolean
173 close_pairing_clicked_cb (GtkButton *button, RBDACPPairingPage *page)
174 {
175 rb_display_page_delete_thyself (RB_DISPLAY_PAGE (page));
176 return FALSE;
177 }
178
179 static void
180 impl_dispose (GObject *object)
181 {
182 RBDACPPairingPage *page = RB_DACP_PAIRING_PAGE (object);
183
184 if (page->priv->builder != NULL) {
185 g_object_unref (page->priv->builder);
186 page->priv->builder = NULL;
187 }
188
189 if (page->priv->dacp_share != NULL) {
190 g_object_unref (page->priv->dacp_share);
191 page->priv->dacp_share = NULL;
192 }
193
194 G_OBJECT_CLASS (rb_dacp_pairing_page_parent_class)->dispose (object);
195 }
196
197 static void
198 impl_finalize (GObject *object)
199 {
200 RBDACPPairingPage *page = RB_DACP_PAIRING_PAGE (object);
201
202 g_free (page->priv->service_name);
203
204 G_OBJECT_CLASS (rb_dacp_pairing_page_parent_class)->finalize (object);
205 }
206
207 static void
208 rb_dacp_pairing_page_class_init (RBDACPPairingPageClass *klass)
209 {
210 GObjectClass *object_class = G_OBJECT_CLASS (klass);
211
212 object_class->constructed = impl_constructed;
213 object_class->dispose = impl_dispose;
214 object_class->finalize = impl_finalize;
215 object_class->get_property = impl_get_property;
216 object_class->set_property = impl_set_property;
217
218 g_object_class_install_property (object_class,
219 PROP_SERVICE_NAME,
220 g_param_spec_string ("service-name",
221 "Service name",
222 "mDNS/DNS-SD service name of the share",
223 NULL,
224 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
225
226 g_type_class_add_private (klass, sizeof (RBDACPPairingPagePrivate));
227 }
228
229 static void
230 rb_dacp_pairing_page_class_finalize (RBDACPPairingPageClass *klass)
231 {
232 }
233
234 static void
235 rb_dacp_pairing_page_init (RBDACPPairingPage *page)
236 {
237 page->priv = G_TYPE_INSTANCE_GET_PRIVATE (page,
238 RB_TYPE_DACP_PAIRING_PAGE,
239 RBDACPPairingPagePrivate);
240 }
241
242 static void
243 impl_constructed (GObject *object)
244 {
245 RBDACPPairingPage *page = RB_DACP_PAIRING_PAGE (object);
246 char *builder_filename;
247 GtkWidget *passcode_widget;
248 GtkWidget *close_pairing_button;
249 PangoFontDescription *font;
250 GObject *plugin;
251 int i;
252
253 g_object_get (page, "plugin", &plugin, NULL);
254
255 builder_filename = rb_find_plugin_data_file (G_OBJECT (plugin), "daap-prefs.ui");
256 g_assert (builder_filename != NULL);
257
258 page->priv->builder = rb_builder_load (builder_filename, NULL);
259 g_free (builder_filename);
260
261 passcode_widget = GTK_WIDGET (gtk_builder_get_object (page->priv->builder, "passcode_widget"));
262 gtk_container_add (GTK_CONTAINER (page), passcode_widget);
263
264 close_pairing_button = GTK_WIDGET (gtk_builder_get_object (page->priv->builder, "close_pairing_button"));
265 g_signal_connect_object (close_pairing_button, "clicked", G_CALLBACK (close_pairing_clicked_cb), page, 0);
266
267 page->priv->finished_widget = GTK_WIDGET (gtk_builder_get_object (page->priv->builder, "finished_widget"));
268 page->priv->pairing_widget = GTK_WIDGET (gtk_builder_get_object (page->priv->builder, "pairing_widget"));
269 page->priv->pairing_status_widget = GTK_WIDGET (gtk_builder_get_object (page->priv->builder, "pairing_status_widget"));
270
271 font = pango_font_description_from_string ("normal 28");
272
273 for (i = 0; i < 4; i++) {
274 char *entry_name;
275
276 entry_name = g_strdup_printf ("passcode_entry%d", i + 1);
277 page->priv->entries[i] = GTK_WIDGET (gtk_builder_get_object (page->priv->builder, entry_name));
278 gtk_widget_override_font (page->priv->entries[i], font);
279 g_signal_connect_object (page->priv->entries[i],
280 "insert-text",
281 G_CALLBACK (entry_insert_text_cb),
282 page,
283 0);
284 g_signal_connect_object (page->priv->entries[i],
285 "backspace",
286 G_CALLBACK (entry_backspace_cb),
287 page,
288 0);
289 g_free (entry_name);
290 }
291
292 pango_font_description_free (font);
293
294 gtk_widget_show (passcode_widget);
295
296 g_object_unref (plugin);
297 }
298
299 static void
300 impl_set_property (GObject *object,
301 guint prop_id,
302 const GValue *value,
303 GParamSpec *pspec)
304 {
305 RBDACPPairingPage *page = RB_DACP_PAIRING_PAGE (object);
306
307 switch (prop_id) {
308 case PROP_SERVICE_NAME:
309 page->priv->service_name = g_value_dup_string (value);
310 break;
311 default:
312 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
313 break;
314 }
315 }
316
317 static void
318 impl_get_property (GObject *object,
319 guint prop_id,
320 GValue *value,
321 GParamSpec *pspec)
322 {
323 RBDACPPairingPage *page = RB_DACP_PAIRING_PAGE (object);
324
325 switch (prop_id) {
326 case PROP_SERVICE_NAME:
327 g_value_set_string (value, page->priv->service_name);
328 break;
329 default:
330 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
331 break;
332 }
333 }
334
335 RBDACPPairingPage *
336 rb_dacp_pairing_page_new (GObject *plugin,
337 RBShell *shell,
338 DACPShare *dacp_share,
339 const char *display_name,
340 const char *service_name)
341 {
342 RBDACPPairingPage *page;
343 char *icon_filename;
344 int icon_size;
345 GdkPixbuf *icon_pixbuf;
346
347 icon_filename = rb_find_plugin_data_file (plugin, "remote-icon.png");
348 gtk_icon_size_lookup (GTK_ICON_SIZE_LARGE_TOOLBAR, &icon_size, NULL);
349 icon_pixbuf = gdk_pixbuf_new_from_file_at_size (icon_filename, icon_size, icon_size, NULL);
350
351 page = RB_DACP_PAIRING_PAGE (g_object_new (RB_TYPE_DACP_PAIRING_PAGE,
352 "name", display_name,
353 "service-name", service_name,
354 "pixbuf", icon_pixbuf,
355 "shell", shell,
356 "plugin", plugin,
357 NULL));
358
359 g_object_ref (dacp_share);
360 page->priv->dacp_share = dacp_share;
361 /* Retrieve notifications when the remote is finished pairing */
362 g_signal_connect_object (dacp_share, "remote-paired", G_CALLBACK (remote_paired_cb), page, 0);
363
364 g_free (icon_filename);
365 g_object_unref (icon_pixbuf);
366
367 return page;
368 }
369
370
371 static void
372 rb_dacp_pairing_page_reset_passcode (RBDACPPairingPage *page)
373 {
374 int i;
375
376 for (i = 0; i < 4; i++) {
377 gtk_entry_set_text (GTK_ENTRY (page->priv->entries[i]), "");
378 }
379 gtk_widget_grab_focus (page->priv->entries [0]);
380 }
381
382 void
383 rb_dacp_pairing_page_remote_found (RBDACPPairingPage *page)
384 {
385 if (page->priv->done_pairing) {
386 rb_dacp_pairing_page_reset_passcode (page);
387 gtk_widget_show (page->priv->pairing_widget);
388 gtk_widget_hide (page->priv->pairing_status_widget);
389 gtk_widget_hide (page->priv->finished_widget);
390 page->priv->done_pairing = FALSE;
391 }
392 }
393
394 void
395 rb_dacp_pairing_page_remote_lost (RBDACPPairingPage *page)
396 {
397 if (!page->priv->done_pairing) {
398 rb_display_page_delete_thyself (RB_DISPLAY_PAGE (page));
399 }
400 }
401
402 static void
403 rb_dacp_pairing_page_connecting (RBDACPPairingPage *page, gboolean connecting) {
404 int i;
405
406 if (connecting) {
407 gtk_widget_show (page->priv->pairing_status_widget);
408 gtk_label_set_markup (GTK_LABEL (page->priv->pairing_status_widget), _("Connecting..."));
409 } else {
410 gtk_label_set_markup (GTK_LABEL (page->priv->pairing_status_widget), _("Could not pair with this Remote."));
411 }
412
413 for (i = 0; i < 4; i++) {
414 gtk_widget_set_sensitive (page->priv->entries [i], !connecting);
415 }
416 }
417
418 static void
419 remote_paired_cb (DACPShare *share, gchar *service_name, gboolean connected, RBDACPPairingPage *page)
420 {
421 /* Check if this remote is the remote paired */
422 if (g_strcmp0 (service_name, page->priv->service_name) != 0)
423 return;
424
425 rb_dacp_pairing_page_connecting (page, FALSE);
426 if (connected) {
427 gtk_widget_hide (page->priv->pairing_widget);
428 gtk_widget_show (page->priv->finished_widget);
429 page->priv->done_pairing = TRUE;
430 } else {
431 gtk_widget_show (page->priv->pairing_status_widget);
432 rb_dacp_pairing_page_reset_passcode (page);
433 }
434 }
435
436 DACPShare *
437 rb_daap_create_dacp_share (GObject *plugin)
438 {
439 DACPShare *share;
440 DACPPlayer *player;
441 RhythmDB *rdb;
442 DMAPDb *db;
443 DMAPContainerDb *container_db;
444 RBPlaylistManager *playlist_manager;
445 RBShell *shell;
446 GSettings *share_settings;
447 GSettings *daap_settings;
448 GSettings *settings;
449 gchar *name;
450
451 g_object_get (plugin, "object", &shell, NULL);
452
453 g_object_get (shell,
454 "db", &rdb,
455 "playlist-manager", &playlist_manager,
456 NULL);
457 db = DMAP_DB (rb_rhythmdb_dmap_db_adapter_new (rdb, RHYTHMDB_ENTRY_TYPE_SONG));
458 container_db = DMAP_CONTAINER_DB (rb_dmap_container_db_adapter_new (playlist_manager));
459
460 player = DACP_PLAYER (rb_dacp_player_new (shell));
461
462 share_settings = g_settings_new ("org.gnome.rhythmbox.sharing");
463 name = g_settings_get_string (share_settings, "share-name");
464 if (name == NULL || *name == '\0') {
465 g_free (name);
466 name = rb_daap_sharing_default_share_name ();
467 }
468 g_object_unref (share_settings);
469
470 share = dacp_share_new (name, player, db, container_db);
471
472 daap_settings = g_settings_new ("org.gnome.rhythmbox.plugins.daap");
473 settings = g_settings_get_child (daap_settings, "dacp");
474 g_object_unref (daap_settings);
475
476 g_signal_connect_object (share,
477 "add-guid",
478 G_CALLBACK (dacp_add_guid),
479 settings,
480 0);
481 g_signal_connect_object (share,
482 "lookup-guid",
483 G_CALLBACK (dacp_lookup_guid),
484 settings,
485 0);
486
487 g_signal_connect_object (share,
488 "remote-found",
489 G_CALLBACK (dacp_remote_added),
490 RB_DAAP_PLUGIN (plugin),
491 0);
492 g_signal_connect_object (share,
493 "remote-lost",
494 G_CALLBACK (dacp_remote_removed),
495 RB_DAAP_PLUGIN (plugin),
496 0);
497
498 g_signal_connect_object (player,
499 "player-updated",
500 G_CALLBACK (dacp_player_updated),
501 share,
502 0);
503
504 g_object_unref (db);
505 g_object_unref (container_db);
506 g_object_unref (rdb);
507 g_object_unref (playlist_manager);
508 g_object_unref (player);
509 g_object_unref (shell);
510
511 return share;
512 }
513
514 static void
515 dacp_player_updated (RBDACPPlayer *player,
516 DACPShare *share)
517 {
518 dacp_share_player_updated (share);
519 }
520
521 static void
522 dacp_add_guid (DACPShare *share,
523 gchar *guid,
524 GSettings *settings)
525 {
526 GVariantBuilder *vb;
527 GVariantIter iter;
528 GVariant *v;
529 const char *g;
530
531 v = g_settings_get_value (settings, "known-remotes");
532
533 vb = g_variant_builder_new (G_VARIANT_TYPE ("as"));
534 g_variant_iter_init (&iter, v);
535 while (g_variant_iter_loop (&iter, "s", &g)) {
536 g_variant_builder_add (vb, "s", g);
537 }
538
539 g_variant_builder_add (vb, "s", guid);
540 g_variant_unref (v);
541
542 g_settings_set_value (settings, "known-remotes", g_variant_builder_end (vb));
543 g_variant_builder_unref (vb);
544 }
545
546 static gboolean
547 dacp_lookup_guid (DACPShare *share,
548 gchar *guid,
549 GSettings *settings)
550 {
551 char **guids;
552 gboolean found;
553
554 guids = g_settings_get_strv (settings, "known-remotes");
555 found = rb_str_in_strv (guid, (const char **)guids);
556 g_strfreev (guids);
557
558 return found;
559 }
560
561 typedef struct {
562 const char *name;
563 RBDACPPairingPage *page;
564 } FindPage;
565
566 static gboolean
567 find_dacp_page_foreach (GtkTreeModel *model,
568 GtkTreePath *path,
569 GtkTreeIter *iter,
570 FindPage *fp)
571 {
572 gchar *name;
573 RBDisplayPage *page;
574
575 gtk_tree_model_get (model, iter,
576 RB_DISPLAY_PAGE_MODEL_COLUMN_PAGE, &page,
577 -1);
578 if (page && RB_IS_DACP_PAIRING_PAGE (page)) {
579 g_object_get (page, "service-name", &name, NULL);
580 if (strcmp (name, fp->name) == 0) {
581 fp->page = RB_DACP_PAIRING_PAGE (page);
582 }
583 g_free (name);
584 }
585
586 return (fp->page != NULL);
587 }
588
589 static RBDACPPairingPage *
590 find_dacp_page (RBShell *shell, const gchar *service_name)
591 {
592 RBDisplayPageModel *page_model;
593 FindPage find_page;
594
595 find_page.name = service_name;
596 find_page.page = NULL;
597
598 g_object_get (shell, "display-page-model", &page_model, NULL);
599
600 gtk_tree_model_foreach (GTK_TREE_MODEL (page_model),
601 (GtkTreeModelForeachFunc) find_dacp_page_foreach,
602 &find_page);
603
604 return find_page.page;
605 }
606
607 static void
608 dacp_remote_added (DACPShare *share,
609 gchar *service_name,
610 gchar *display_name,
611 RBDaapPlugin *plugin)
612 {
613 RBDACPPairingPage *page;
614 RBShell *shell;
615
616 rb_debug ("Remote %s (%s) found", service_name, display_name);
617
618 g_object_get (plugin, "object", &shell, NULL);
619
620 GDK_THREADS_ENTER ();
621
622 page = find_dacp_page (shell, service_name);
623 if (page == NULL) {
624 RBDisplayPageGroup *page_group;
625
626 page_group = rb_display_page_group_get_by_id ("remotes");
627 if (page_group == NULL) {
628 page_group = rb_display_page_group_new (G_OBJECT (shell),
629 "remotes",
630 _("Remotes"),
631 RB_DISPLAY_PAGE_GROUP_CATEGORY_TRANSIENT);
632 rb_shell_append_display_page (shell, RB_DISPLAY_PAGE (page_group), NULL);
633 }
634
635 page = rb_dacp_pairing_page_new (G_OBJECT (plugin), shell, share, display_name, service_name);
636
637 rb_shell_append_display_page (shell, RB_DISPLAY_PAGE (page), RB_DISPLAY_PAGE (page_group));
638 } else {
639 rb_dacp_pairing_page_remote_found (page);
640 }
641
642 GDK_THREADS_LEAVE ();
643
644 g_object_unref (shell);
645 }
646
647 static void
648 dacp_remote_removed (DACPShare *share,
649 gchar *service_name,
650 RBDaapPlugin *plugin)
651 {
652 RBDACPPairingPage *page;
653 RBShell *shell;
654
655 rb_debug ("Remote '%s' went away", service_name);
656
657 g_object_get (plugin, "object", &shell, NULL);
658
659 GDK_THREADS_ENTER ();
660
661 page = find_dacp_page (shell, service_name);
662 if (page != NULL) {
663 rb_dacp_pairing_page_remote_lost (page);
664 }
665
666 GDK_THREADS_LEAVE ();
667
668 g_object_unref (shell);
669 }
670
671 void
672 _rb_dacp_pairing_page_register_type (GTypeModule *module)
673 {
674 rb_dacp_pairing_page_register_type (module);
675 }