1
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
3
* Copyright (C) 2007 Bastien Nocera <hadess@hadess.net>
5
* This program is free software; you can redistribute it and/or modify
6
* it under the terms of the GNU General Public License as published by
7
* the Free Software Foundation; either version 2 of the License, or
8
* (at your option) any later version.
10
* This program is distributed in the hope that it will be useful,
11
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
* GNU General Public License for more details.
15
* You should have received a copy of the GNU General Public License
16
* along with this program; if not, write to the Free Software
17
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19
* The Totem project hereby grant permission for non-gpl compatible GStreamer
20
* plugins to be used and distributed together with GStreamer and Totem. This
21
* permission are above and beyond the permissions granted by the GPL license
22
* Totem is covered by.
24
* See license_change file for details.
31
#include <glib-object.h>
32
#include <glib/gi18n-lib.h>
33
#include <gconf/gconf-client.h>
37
#include "totem-video-list.h"
38
#include "totem-cell-renderer-video.h"
39
#include "video-utils.h"
40
#include "totem-plugin.h"
43
#include <gdk-pixbuf/gdk-pixbuf.h>
45
#include <gmyth/gmyth_backendinfo.h>
46
#include <gmyth/gmyth_file_transfer.h>
47
#include <gmyth/gmyth_scheduler.h>
48
#include <gmyth/gmyth_epg.h>
49
#include <gmyth/gmyth_util.h>
50
#include <gmyth_upnp.h>
53
#define TOTEM_TYPE_MYTHTV_PLUGIN (totem_mythtv_plugin_get_type ())
54
#define TOTEM_MYTHTV_PLUGIN(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), TOTEM_TYPE_MYTHTV_PLUGIN, TotemMythtvPlugin))
55
#define TOTEM_MYTHTV_PLUGIN_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), TOTEM_TYPE_MYTHTV_PLUGIN, TotemMythtvPluginClass))
56
#define TOTEM_IS_MYTHTV_PLUGIN(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), TOTEM_TYPE_MYTHTV_PLUGIN))
57
#define TOTEM_IS_MYTHTV_PLUGIN_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), TOTEM_TYPE_MYTHTV_PLUGIN))
58
#define TOTEM_MYTHTV_PLUGIN_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), TOTEM_TYPE_MYTHTV_PLUGIN, TotemMythtvPluginClass))
60
#define MYTHTV_SIDEBAR_RECORDINGS "mythtv-recordings"
61
#define MYTHTV_SIDEBAR_LIVETV "mythtv-livetv"
82
GtkWidget *sidebar_recordings;
83
GtkWidget *sidebar_livetv;
88
TotemPluginClass parent_class;
89
} TotemMythtvPluginClass;
92
G_MODULE_EXPORT GType register_totem_plugin (GTypeModule *module);
93
GType totem_mythtv_plugin_get_type (void) G_GNUC_CONST;
94
static void totem_mythtv_plugin_finalize (GObject *object);
95
static gboolean impl_activate (TotemPlugin *plugin, TotemObject *totem, GError **error);
96
static void impl_deactivate (TotemPlugin *plugin, TotemObject *totem);
98
TOTEM_PLUGIN_REGISTER(TotemMythtvPlugin, totem_mythtv_plugin)
100
#define MAX_THUMB_SIZE 500 * 1024 * 1024
101
#define THUMB_HEIGHT 32
104
get_thumbnail (TotemMythtvPlugin *plugin, GMythBackendInfo *b_info, char *fname)
106
GMythFileTransfer *transfer;
107
GdkPixbufLoader *loader;
109
GMythFileReadResult res;
113
if (gmyth_util_file_exists (b_info, fname) == FALSE)
116
transfer = gmyth_file_transfer_new (b_info);
117
if (gmyth_file_transfer_open(transfer, fname) == FALSE)
120
to_read = gmyth_file_transfer_get_filesize (transfer);
121
/* Leave if the thumbnail is just too big */
122
if (to_read > MAX_THUMB_SIZE) {
123
gmyth_file_transfer_close (transfer);
127
loader = gdk_pixbuf_loader_new_with_type ("png", NULL);
128
data = g_byte_array_sized_new (to_read);
130
res = gmyth_file_transfer_read(transfer, data, to_read, FALSE);
131
if (gdk_pixbuf_loader_write (loader, data->data, to_read, NULL) == FALSE) {
132
res = GMYTH_FILE_READ_ERROR;
135
gmyth_file_transfer_close (transfer);
136
g_object_unref (transfer);
137
g_byte_array_free (data, TRUE);
139
if (res != GMYTH_FILE_READ_OK && res != GMYTH_FILE_READ_EOF) {
140
g_object_unref (loader);
144
if (gdk_pixbuf_loader_close (loader, NULL) == FALSE) {
145
g_object_unref (loader);
149
pixbuf = gdk_pixbuf_loader_get_pixbuf (loader);
151
g_object_ref (pixbuf);
152
g_object_unref (loader);
157
if (gdk_pixbuf_get_height (pixbuf) != THUMB_HEIGHT) {
161
height = THUMB_HEIGHT;
162
width = gdk_pixbuf_get_width (pixbuf) * height / gdk_pixbuf_get_height (pixbuf);
163
scaled = gdk_pixbuf_scale_simple (pixbuf, width, height, GDK_INTERP_BILINEAR);
164
g_object_unref (pixbuf);
173
create_treeview (TotemMythtvPlugin *plugin)
175
TotemCellRendererVideo *renderer;
179
/* Treeview and model */
180
model = GTK_TREE_MODEL (gtk_list_store_new (NUM_COLS,
187
treeview = GTK_WIDGET (g_object_new (TOTEM_TYPE_VIDEO_LIST,
188
"totem", plugin->totem,
189
"mrl-column", URI_COL,
190
"tooltip-column", DESCRIPTION_COL,
193
gtk_tree_view_set_model (GTK_TREE_VIEW (treeview),
194
GTK_TREE_MODEL (model));
196
renderer = totem_cell_renderer_video_new (TRUE);
197
gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (treeview), 0,
198
_("Recordings"), GTK_CELL_RENDERER (renderer),
199
"thumbnail", THUMBNAIL_COL,
203
gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (treeview), FALSE);
209
totem_mythtv_list_recordings (TotemMythtvPlugin *tm,
210
GMythBackendInfo *b_info)
212
GMythScheduler *scheduler;
219
scheduler = gmyth_scheduler_new();
220
if (gmyth_scheduler_connect_with_timeout(scheduler,
221
b_info, 5) == FALSE) {
222
g_message ("Couldn't connect to scheduler");
223
g_object_unref (scheduler);
227
if (gmyth_scheduler_get_recorded_list(scheduler, &list) < 0) {
228
g_message ("Couldn't get recordings list");
229
gmyth_scheduler_disconnect (scheduler);
230
g_object_unref (scheduler);
234
gmyth_scheduler_disconnect (scheduler);
235
g_object_unref (scheduler);
237
model = g_object_get_data (G_OBJECT (tm->sidebar_recordings), "model");
239
for (l = list; l != NULL; l = l->next) {
240
RecordedInfo *recorded_info = (RecordedInfo *) l->data;
242
if (gmyth_util_file_exists
243
(b_info, recorded_info->basename->str)) {
246
char *full_name = NULL;
247
char *thumb_fname, *uri;
249
if (recorded_info->subtitle->str != NULL && recorded_info->subtitle->str[0] != '\0')
250
full_name = g_strdup_printf ("%s - %s",
251
recorded_info->title->str,
252
recorded_info->subtitle->str);
253
thumb_fname = g_strdup_printf ("%s.png", recorded_info->basename->str);
254
uri = g_strdup_printf ("myth://%s:%d/%s",
257
recorded_info->basename->str);
258
pixbuf = get_thumbnail (tm, b_info, thumb_fname);
259
g_free (thumb_fname);
261
gtk_list_store_insert_with_values (GTK_LIST_STORE (model), &iter, G_MAXINT32,
262
FILENAME_COL, recorded_info->basename->str,
264
THUMBNAIL_COL, pixbuf,
265
NAME_COL, full_name ? full_name : recorded_info->title->str,
266
DESCRIPTION_COL, recorded_info->description->str,
271
gmyth_recorded_info_free(recorded_info);
278
sort_channels (GMythChannelInfo *a, GMythChannelInfo *b)
282
a_int = g_strtod (a->channel_num->str, NULL);
283
b_int = g_strtod (b->channel_num->str, NULL);
293
totem_mythtv_list_livetv (TotemMythtvPlugin *tm,
294
GMythBackendInfo *b_info)
301
epg = gmyth_epg_new ();
302
if (!gmyth_epg_connect (epg, b_info)) {
303
g_object_unref (epg);
307
length = gmyth_epg_get_channel_list (epg, &clist);
308
gmyth_epg_disconnect (epg);
309
g_object_unref (epg);
311
model = g_object_get_data (G_OBJECT (tm->sidebar_livetv), "model");
312
clist = g_list_sort (clist, (GCompareFunc) sort_channels);
314
for (ch = clist; ch != NULL; ch = ch->next) {
315
GMythChannelInfo *info = (GMythChannelInfo *) ch->data;
320
if ((info->channel_name == NULL) || (info->channel_num == NULL))
324
if (info->channel_icon != NULL && info->channel_icon->str[0] != '\0') {
325
pixbuf = get_thumbnail (tm, b_info, info->channel_icon->str);
326
g_message ("icon name: %s", info->channel_icon->str);
329
uri = g_strdup_printf ("myth://%s:%d/?channel=%s", b_info->hostname, b_info->port, info->channel_num->str);
331
gtk_list_store_insert_with_values (GTK_LIST_STORE (model), &iter, G_MAXINT32,
333
THUMBNAIL_COL, pixbuf,
334
NAME_COL, info->channel_name->str,
338
gmyth_free_channel_list (clist);
342
totem_mythtv_plugin_class_init (TotemMythtvPluginClass *klass)
344
GObjectClass *object_class = G_OBJECT_CLASS (klass);
345
TotemPluginClass *plugin_class = TOTEM_PLUGIN_CLASS (klass);
347
object_class->finalize = totem_mythtv_plugin_finalize;
349
plugin_class->activate = impl_activate;
350
plugin_class->deactivate = impl_deactivate;
354
device_found_cb (GMythUPnP *upnp,
355
GMythBackendInfo *b_info,
356
TotemMythtvPlugin *plugin)
358
if (!g_list_find (plugin->lst_b_info, b_info)) {
359
plugin->lst_b_info = g_list_append (plugin->lst_b_info,
360
g_object_ref (b_info));
361
totem_mythtv_list_recordings (plugin, b_info);
362
totem_mythtv_list_livetv (plugin, b_info);
367
device_lost_cb (GMythUPnP *upnp,
368
GMythBackendInfo *info,
369
TotemMythtvPlugin *plugin)
372
GMythBackendInfo *b_info;
374
elem = g_list_find (plugin->lst_b_info, info);
375
if (elem && elem->data) {
377
plugin->lst_b_info = g_list_remove (plugin->lst_b_info,
379
g_object_unref (b_info);
385
totem_mythtv_update_binfo (TotemMythtvPlugin *plugin)
390
/* Clear old b_info */
391
if (plugin->lst_b_info != NULL) {
392
g_list_foreach (plugin->lst_b_info, (GFunc) g_object_unref, NULL);
393
g_list_free (plugin->lst_b_info);
394
plugin->lst_b_info = NULL;
398
/* Using GMythUPnP to search for avaliable servers */
399
if (plugin->upnp == NULL) {
400
plugin->upnp = gmyth_upnp_get_instance ();
401
g_signal_connect (G_OBJECT (plugin->upnp),
403
G_CALLBACK (device_found_cb),
405
plugin->upnp = gmyth_upnp_get_instance ();
406
g_signal_connect (G_OBJECT (plugin->upnp),
408
G_CALLBACK (device_lost_cb),
412
/* Load current servers */
413
lst = gmyth_upnp_get_devices (plugin->upnp);
414
for (w = lst; w != NULL; w = w->next) {
415
GMythBackendInfo *b_info;
417
b_info = (GMythBackendInfo *) w->data;
418
plugin->lst_b_info = g_list_append (plugin->lst_b_info,
420
totem_mythtv_list_recordings (plugin, b_info);
421
totem_mythtv_list_livetv (plugin, b_info);
424
gmyth_upnp_search (plugin->upnp);
428
refresh_cb (GtkWidget *button, TotemMythtvPlugin *tm)
432
gtk_widget_set_sensitive (button, FALSE);
433
totem_gdk_window_set_waiting_cursor (gtk_widget_get_window (tm->sidebar_recordings));
434
totem_gdk_window_set_waiting_cursor (gtk_widget_get_window (tm->sidebar_livetv));
436
model = g_object_get_data (G_OBJECT (tm->sidebar_recordings), "model");
437
gtk_list_store_clear (GTK_LIST_STORE (model));
438
model = g_object_get_data (G_OBJECT (tm->sidebar_livetv), "model");
439
gtk_list_store_clear (GTK_LIST_STORE (model));
441
totem_mythtv_update_binfo (tm);
443
gdk_window_set_cursor (gtk_widget_get_window (tm->sidebar_recordings), NULL);
444
gdk_window_set_cursor (gtk_widget_get_window (tm->sidebar_livetv), NULL);
445
gtk_widget_set_sensitive (button, TRUE);
449
totem_mythtv_plugin_init (TotemMythtvPlugin *plugin)
451
totem_mythtv_update_binfo (plugin);
455
totem_mythtv_plugin_finalize (GObject *object)
457
TotemMythtvPlugin *tm = TOTEM_MYTHTV_PLUGIN(object);
459
if (tm->lst_b_info != NULL) {
460
g_list_foreach (tm->lst_b_info, (GFunc ) g_object_unref, NULL);
461
g_list_free (tm->lst_b_info);
462
tm->lst_b_info = NULL;
464
if (tm->client != NULL) {
465
g_object_unref (tm->client);
468
if (tm->upnp != NULL) {
469
g_object_unref (tm->upnp);
472
if (tm->sidebar_recordings != NULL) {
473
gtk_widget_destroy (tm->sidebar_recordings);
474
tm->sidebar_recordings = NULL;
476
if (tm->sidebar_livetv != NULL) {
477
gtk_widget_destroy (tm->sidebar_livetv);
478
tm->sidebar_livetv = NULL;
481
G_OBJECT_CLASS (totem_mythtv_plugin_parent_class)->finalize (object);
485
add_sidebar (TotemMythtvPlugin *tm, const char *name, const char *label)
487
GtkWidget *box, *scrolled, *treeview, *button;
489
box = gtk_vbox_new (FALSE, 6);
490
button = gtk_button_new_from_stock (GTK_STOCK_REFRESH);
491
g_signal_connect (G_OBJECT (button), "clicked",
492
G_CALLBACK (refresh_cb), tm);
493
scrolled = gtk_scrolled_window_new (NULL, NULL);
494
gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled),
496
GTK_POLICY_AUTOMATIC);
497
treeview = create_treeview (tm);
498
gtk_scrolled_window_add_with_viewport (GTK_SCROLLED_WINDOW (scrolled),
499
GTK_WIDGET (treeview));
500
gtk_container_add (GTK_CONTAINER (box), scrolled);
501
gtk_box_pack_end (GTK_BOX (box), button, FALSE, FALSE, 0);
502
gtk_widget_show_all (box);
503
totem_add_sidebar_page (tm->totem, name, label, box);
505
g_object_set_data (G_OBJECT (box), "treeview", treeview);
506
g_object_set_data (G_OBJECT (box), "model",
507
gtk_tree_view_get_model (GTK_TREE_VIEW (treeview)));
509
return g_object_ref (box);
513
impl_activate (TotemPlugin *plugin,
517
TotemMythtvPlugin *tm = TOTEM_MYTHTV_PLUGIN(plugin);
519
tm->totem = g_object_ref (totem);
521
tm->sidebar_recordings = add_sidebar (tm, "mythtv-recordings", _("MythTV Recordings"));
522
tm->sidebar_livetv = add_sidebar (tm, "mythtv-livetv", _("MythTV LiveTV"));
524
totem_mythtv_update_binfo (TOTEM_MYTHTV_PLUGIN(plugin));
526
/* FIXME we should only do that if it will be done in the background */
528
totem_gdk_window_set_waiting_cursor (gtk_widget_get_window (box);
529
totem_mythtv_list_recordings (TOTEM_MYTHTV_PLUGIN(plugin));
530
totem_mythtv_list_livetv (TOTEM_MYTHTV_PLUGIN(plugin));
531
gdk_window_set_cursor (gtk_widget_get_window (box), NULL);
537
impl_deactivate (TotemPlugin *plugin,
540
TotemMythtvPlugin *tm = TOTEM_MYTHTV_PLUGIN(plugin);
542
totem_remove_sidebar_page (totem, MYTHTV_SIDEBAR_RECORDINGS);
543
totem_remove_sidebar_page (totem, MYTHTV_SIDEBAR_LIVETV);
545
if (tm->lst_b_info != NULL) {
546
g_list_foreach (tm->lst_b_info, (GFunc ) g_object_unref, NULL);
547
g_list_free (tm->lst_b_info);
548
tm->lst_b_info = NULL;
550
if (tm->client != NULL) {
551
g_object_unref (tm->client);
554
if (tm->upnp != NULL) {
555
g_object_unref (tm->upnp);
558
if (tm->sidebar_recordings != NULL) {
559
gtk_widget_destroy (tm->sidebar_recordings);
560
tm->sidebar_recordings = NULL;
562
if (tm->sidebar_livetv != NULL) {
563
gtk_widget_destroy (tm->sidebar_livetv);
564
tm->sidebar_livetv = NULL;
566
g_object_unref (totem);