1 /*
2 * This file is part of GNOME Twitch - 'Enjoy Twitch on your GNU/Linux desktop'
3 * Copyright © 2017 Vincent Szolnoky <vinszent@vinszent.com>
4 *
5 * GNOME Twitch 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 3 of the License, or
8 * (at your option) any later version.
9 *
10 * GNOME Twitch 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.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with GNOME Twitch. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19 #include "gt-win.h"
20 #include <glib/gprintf.h>
21 #include <glib/gi18n.h>
22 #include <gio/gio.h>
23 #include "gt-twitch.h"
24 #include "gt-player.h"
25 #include "gt-browse-header-bar.h"
26 #include "gt-channel-header-bar.h"
27 #include "gt-channel-container-view.h"
28 #include "gt-followed-container-view.h"
29 #include "gt-game-container-view.h"
30 #include "gt-channel-vod-container.h"
31 #include "gt-settings-dlg.h"
32 #include "gt-twitch-login-dlg.h"
33 #include "gt-twitch-channel-info-dlg.h"
34 #include "gt-chat.h"
35 #include "gt-enums.h"
36 #include "config.h"
37 #include "version.h"
38 #include "utils.h"
39
40 #define TAG "GtWin"
41 #include "gnome-twitch/gt-log.h"
42
43 typedef struct
44 {
45 gchar* msg;
46 GCallback cb;
47 gpointer udata;
48 } QueuedInfoData;
49
50 typedef struct
51 {
52 GtkWidget* main_stack;
53 GtkWidget* header_stack;
54 GtkWidget* browse_stack;
55 GtkWidget* browse_header_bar;
56 GtkWidget* browse_stack_switcher;
57 GtkWidget* chat_view;
58 GtkWidget* player;
59 GtkWidget* channel_stack;
60 GtkWidget* channel_vod_container;
61 GtkWidget* channel_header_bar;
62
63 GtkWidget* info_revealer;
64 GtkWidget* info_label;
65 GtkWidget* info_bar;
66 GtkWidget* info_bar_yes_button;
67 GtkWidget* info_bar_no_button;
68 GtkWidget* info_bar_ok_button;
69 GtkWidget* info_bar_details_button;
70 GtkWidget* info_bar_close_button;
71
72 QueuedInfoData* cur_info_data;
73 GQueue* info_queue;
74
75 GtSettingsDlg* settings_dlg;
76
77 gboolean fullscreen;
78
79 GBinding* search_binding;
80 GBinding* back_binding;
81
82 guint inhibit_cookie;
83 } GtWinPrivate;
84
85 G_DEFINE_TYPE_WITH_PRIVATE(GtWin, gt_win, GTK_TYPE_APPLICATION_WINDOW)
86
87 enum
88 {
89 PROP_0,
90 PROP_FULLSCREEN,
91 PROP_OPEN_CHANNEL,
92 NUM_PROPS,
93 };
94
95 static GParamSpec* props[NUM_PROPS];
96
97 GtWin*
gt_win_new(GtApp * app)98 gt_win_new(GtApp* app)
99 {
100 return g_object_new(GT_TYPE_WIN,
101 "application", app,
102 "show-menubar", FALSE,
103 NULL);
104 }
105
106 static void
container_view_changed_cb(GObject * source,GParamSpec * pspec,gpointer udata)107 container_view_changed_cb(GObject* source,
108 GParamSpec* pspec, gpointer udata)
109 {
110 g_assert(GT_IS_WIN(udata));
111
112 GtWin* self = GT_WIN(udata);
113 GtWinPrivate* priv = gt_win_get_instance_private(self);
114
115 GtkWidget* current_view = gtk_stack_get_visible_child(GTK_STACK(priv->browse_stack));
116
117 g_assert(GT_IS_CONTAINER_VIEW(current_view));
118
119 g_clear_object(&priv->back_binding);
120 g_clear_object(&priv->search_binding);
121
122 g_object_set(priv->browse_header_bar,
123 "search-active",
124 gt_container_view_get_search_active(GT_CONTAINER_VIEW(current_view)),
125 "show-back-button",
126 gt_container_view_get_show_back_button(GT_CONTAINER_VIEW(current_view)),
127 NULL);
128
129 priv->search_binding = g_object_bind_property(priv->browse_header_bar, "search-active",
130 current_view, "search-active", G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE);
131
132 priv->back_binding = g_object_bind_property(current_view, "show-back-button",
133 priv->browse_header_bar, "show-back-button", G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE);
134 }
135
136 static void
show_about_cb(GSimpleAction * action,GVariant * par,gpointer udata)137 show_about_cb(GSimpleAction* action,
138 GVariant* par,
139 gpointer udata)
140 {
141 GtWin* self = GT_WIN(udata);
142 GtWinPrivate* priv = gt_win_get_instance_private(self);
143 GtkWidget* about_dlg = NULL;
144
145 const char* authors[] = {"Vincent Szolnoky", NULL};
146 const char* contributors[] = {"Dimitrios Christidis", NULL};
147
148 about_dlg = gtk_about_dialog_new();
149
150 g_object_set(about_dlg,
151 "version", GT_VERSION,
152 "program-name", "GNOME Twitch",
153 "authors", &authors,
154 "license-type", GTK_LICENSE_GPL_3_0,
155 "copyright", "Copyright © 2017 Vincent Szolnoky",
156 "comments", _("Enjoy Twitch on your GNU/Linux desktop"),
157 "logo-icon-name", "com.vinszent.GnomeTwitch",
158 "website", "https://github.com/vinszent/gnome-twitch",
159 "website-label", "GitHub",
160 // Translators: Put your details here :)
161 "translator-credits", _("translator-credits"),
162 NULL);
163
164 gtk_about_dialog_add_credit_section(GTK_ABOUT_DIALOG(about_dlg), _("Contributors"), contributors);
165 gtk_window_set_transient_for(GTK_WINDOW(about_dlg), GTK_WINDOW(self));
166
167 gtk_dialog_run(GTK_DIALOG(about_dlg));
168 gtk_widget_destroy(about_dlg);
169 }
170
171 static void
show_settings_cb(GSimpleAction * action,GVariant * par,gpointer udata)172 show_settings_cb(GSimpleAction* action,
173 GVariant* par,
174 gpointer udata)
175 {
176 GtWin* self = GT_WIN(udata);
177 GtWinPrivate* priv = gt_win_get_instance_private(self);
178
179 if (!priv->settings_dlg)
180 {
181 priv->settings_dlg = gt_settings_dlg_new(self);
182 g_object_add_weak_pointer(G_OBJECT(priv->settings_dlg), (gpointer *) &priv->settings_dlg);
183 }
184
185 if (par)
186 {
187 GEnumClass* eclass = g_type_class_ref(GT_TYPE_SETTINGS_DLG_VIEW);
188 GEnumValue* eval = g_enum_get_value_by_nick(eclass, g_variant_get_string(par, NULL));
189
190 gt_settings_dlg_set_view(priv->settings_dlg, eval->value);
191
192 g_simple_action_set_state(action, par);
193
194 g_type_class_unref(eclass);
195 }
196
197 gtk_window_present(GTK_WINDOW(priv->settings_dlg));
198 }
199
200 static void
refresh_login_cb(GtkInfoBar * info_bar,gint res,gpointer udata)201 refresh_login_cb(GtkInfoBar* info_bar,
202 gint res,
203 gpointer udata)
204 {
205 GtWin* self = GT_WIN(udata);
206 GtWinPrivate* priv = gt_win_get_instance_private(self);
207
208 switch (res)
209 {
210 case GTK_RESPONSE_YES:
211 gtk_window_present(GTK_WINDOW(gt_twitch_login_dlg_new(GTK_WINDOW(self))));
212 break;
213 }
214 }
215
216 static void
show_error_dialogue_cb(GtkInfoBar * info_bar,gint res,gchar ** udata)217 show_error_dialogue_cb(GtkInfoBar* info_bar,
218 gint res, gchar** udata)
219 {
220 GtkBuilder* builder = gtk_builder_new_from_resource("/com/vinszent/GnomeTwitch/ui/gt-error-dlg.ui");
221 GtkWidget* dlg = GTK_WIDGET(gtk_builder_get_object(builder, "dlg"));
222 GtkTextView* details_text_view = GTK_TEXT_VIEW(gtk_builder_get_object(builder, "details_text_view"));
223 GtkLabel* sub_label = GTK_LABEL(gtk_builder_get_object(builder, "error_label"));
224 GtkWidget* close_button = GTK_WIDGET(gtk_builder_get_object(builder, "close_button"));
225 GtkWidget* report_button = GTK_WIDGET(gtk_builder_get_object(builder, "report_button"));
226 g_autofree gchar* info_text = NULL;
227
228 if (res != GTK_RESPONSE_OK) goto cleanup;
229
230 gtk_text_buffer_set_text(gtk_text_view_get_buffer(details_text_view),
231 *(udata+1), -1);
232
233 info_text = g_strdup_printf(_("<b>Error message:</b> %s"), *udata);
234
235 gtk_label_set_label(sub_label, info_text);
236
237 g_signal_connect_swapped(close_button, "clicked", G_CALLBACK(gtk_widget_destroy), dlg);
238
239 //TODO: Hook up report button
240
241 gtk_window_set_transient_for(GTK_WINDOW(dlg), GTK_WINDOW(GT_WIN_ACTIVE));
242 gtk_widget_show(dlg);
243
244 cleanup:
245 g_strfreev(udata);
246 }
247
248
249 static void
show_info_bar(GtWin * self)250 show_info_bar(GtWin* self)
251 {
252 GtWinPrivate* priv = gt_win_get_instance_private(self);
253 QueuedInfoData* data = g_queue_pop_head(priv->info_queue);
254
255 if (data)
256 {
257 if (data->cb)
258 {
259 if (data->cb == G_CALLBACK(show_error_dialogue_cb))
260 {
261 gtk_widget_set_visible(priv->info_bar_ok_button, FALSE);
262 gtk_widget_set_visible(priv->info_bar_yes_button, FALSE);
263 gtk_widget_set_visible(priv->info_bar_no_button, FALSE);
264 gtk_widget_set_visible(priv->info_bar_details_button, TRUE);
265 gtk_widget_set_visible(priv->info_bar_close_button, TRUE);
266 gtk_label_set_markup(GTK_LABEL(priv->info_label), data->msg);
267 gtk_info_bar_set_message_type(GTK_INFO_BAR(priv->info_bar), GTK_MESSAGE_ERROR);
268
269 g_signal_connect(priv->info_bar, "response", G_CALLBACK(data->cb), data->udata);
270 }
271 else
272 {
273
274 g_signal_connect(priv->info_bar, "response", G_CALLBACK(data->cb), data->udata);
275
276 gtk_widget_set_visible(priv->info_bar_ok_button, FALSE);
277 gtk_widget_set_visible(priv->info_bar_yes_button, TRUE);
278 gtk_widget_set_visible(priv->info_bar_no_button, TRUE);
279 gtk_widget_set_visible(priv->info_bar_details_button, FALSE);
280 gtk_widget_set_visible(priv->info_bar_close_button, FALSE);
281 gtk_label_set_markup(GTK_LABEL(priv->info_label), data->msg);
282 gtk_info_bar_set_message_type(GTK_INFO_BAR(priv->info_bar), GTK_MESSAGE_QUESTION);
283 }
284 }
285 else
286 {
287 gtk_widget_set_visible(priv->info_bar_yes_button, FALSE);
288 gtk_widget_set_visible(priv->info_bar_no_button, FALSE);
289 gtk_widget_set_visible(priv->info_bar_ok_button, TRUE);
290 gtk_widget_set_visible(priv->info_bar_details_button, FALSE);
291 gtk_widget_set_visible(priv->info_bar_close_button, FALSE);
292 gtk_label_set_markup(GTK_LABEL(priv->info_label), data->msg);
293 gtk_info_bar_set_message_type(GTK_INFO_BAR(priv->info_bar), GTK_MESSAGE_INFO);
294 }
295
296 priv->cur_info_data = data;
297
298 gtk_revealer_set_reveal_child(GTK_REVEALER(priv->info_revealer), TRUE);
299 }
300 else
301 {
302 priv->cur_info_data = NULL;
303
304 gtk_revealer_set_reveal_child(GTK_REVEALER(priv->info_revealer), FALSE);
305 }
306 }
307
308 static void
close_info_bar_cb(GtkInfoBar * bar,gint res,gpointer udata)309 close_info_bar_cb(GtkInfoBar* bar,
310 gint res,
311 gpointer udata)
312 {
313 GtWin* self = GT_WIN(udata);
314 GtWinPrivate* priv = gt_win_get_instance_private(self);
315
316 if (priv->cur_info_data)
317 {
318 if (priv->cur_info_data->cb)
319 {
320 g_signal_handlers_disconnect_by_func(priv->info_bar,
321 priv->cur_info_data->cb,
322 priv->cur_info_data->udata);
323 }
324
325 g_free(priv->cur_info_data->msg);
326 g_free(priv->cur_info_data);
327 }
328
329 show_info_bar(self);
330 }
331
332
333 static void
show_twitch_login_cb(GSimpleAction * action,GVariant * par,gpointer udata)334 show_twitch_login_cb(GSimpleAction* action,
335 GVariant* par,
336 gpointer udata)
337 {
338 GtWin* self = GT_WIN(udata);
339 GtWinPrivate* priv = gt_win_get_instance_private(self);
340
341 if (gt_app_is_logged_in(main_app))
342 {
343 gt_win_ask_question(self, _("Already logged into Twitch, refresh login?"),
344 G_CALLBACK(refresh_login_cb), self);
345 }
346 else
347 {
348 GtTwitchLoginDlg* dlg = gt_twitch_login_dlg_new(GTK_WINDOW(self));
349
350 gtk_window_present(GTK_WINDOW(dlg));
351 }
352 }
353
354 static void
show_channel_info_cb(GSimpleAction * action,GVariant * arg,gpointer udata)355 show_channel_info_cb(GSimpleAction* action,
356 GVariant* arg,
357 gpointer udata)
358 {
359 GtWin* self = GT_WIN(udata);
360 GtWinPrivate* priv = gt_win_get_instance_private(self);
361 GtTwitchChannelInfoDlg* dlg = gt_twitch_channel_info_dlg_new(GTK_WINDOW(self));
362 GtChannel* channel;
363 const gchar* name;
364
365 g_object_get(self->player, "channel", &channel, NULL);
366
367 name = gt_channel_get_name(channel);
368
369 g_message("{GtWin} Showing channel info for '%s'", name);
370
371 gtk_window_present(GTK_WINDOW(dlg));
372
373 gt_twitch_channel_info_dlg_load_channel(dlg, name);
374
375 g_object_unref(channel);
376 }
377
378 static void
go_back_cb(GSimpleAction * action,GVariant * arg,gpointer udata)379 go_back_cb(GSimpleAction* action,
380 GVariant* arg, gpointer udata)
381 {
382 g_assert(GT_IS_WIN(udata));
383
384 GtWin* self = GT_WIN(udata);
385 GtWinPrivate* priv = gt_win_get_instance_private(self);
386
387 GtkWidget* current_view = gtk_stack_get_visible_child(GTK_STACK(priv->browse_stack));
388
389 g_assert(GT_IS_CONTAINER_VIEW(current_view));
390
391 gt_container_view_go_back(GT_CONTAINER_VIEW(current_view));
392 }
393
394 static void
refresh_cb(GSimpleAction * action,GVariant * arg,gpointer udata)395 refresh_cb(GSimpleAction* action,
396 GVariant* arg, gpointer udata)
397 {
398 g_assert(GT_IS_WIN(udata));
399
400 GtWin* self = GT_WIN(udata);
401 GtWinPrivate* priv = gt_win_get_instance_private(self);
402
403 GtkWidget* current_view = gtk_stack_get_visible_child(GTK_STACK(priv->browse_stack));
404
405 g_assert(GT_IS_CONTAINER_VIEW(current_view));
406
407 gt_container_view_refresh(GT_CONTAINER_VIEW(current_view));
408 }
409
410 static gboolean
key_press_cb(GtkWidget * widget,GdkEventKey * evt,gpointer udata)411 key_press_cb(GtkWidget* widget,
412 GdkEventKey* evt,
413 gpointer udata)
414 {
415 GtWin* self = GT_WIN(udata);
416 GtWinPrivate* priv = gt_win_get_instance_private(self);
417 GdkModifierType modifiers = gtk_accelerator_get_default_mod_mask();
418 GtkWidget* visible_child = gtk_stack_get_visible_child(GTK_STACK(priv->main_stack));
419 gboolean playing;
420 GAction *action = NULL;
421
422 g_object_get(self->player, "playing", &playing, NULL);
423
424 if (visible_child == GTK_WIDGET(self->player))
425 {
426 if (evt->keyval == GDK_KEY_Escape)
427 {
428 if (priv->fullscreen)
429 g_object_set(self, "fullscreen", FALSE, NULL);
430 else
431 {
432 action = g_action_map_lookup_action(G_ACTION_MAP(self), "close_player");
433 g_action_activate(action, NULL);
434 }
435 }
436 else if (evt->keyval == GDK_KEY_f)
437 {
438 g_object_set(self, "fullscreen", !priv->fullscreen, NULL);
439 }
440 }
441 else
442 {
443 if (evt->keyval == GDK_KEY_Escape)
444 gt_browse_header_bar_stop_search(GT_BROWSE_HEADER_BAR(priv->browse_header_bar));
445 else if (evt->keyval == GDK_KEY_f && (evt->state & modifiers) == GDK_CONTROL_MASK)
446 gt_browse_header_bar_toggle_search(GT_BROWSE_HEADER_BAR(priv->browse_header_bar));
447 else
448 {
449 /* GtkWidget* view = gtk_stack_get_visible_child(GTK_STACK(priv->browse_stack)); */
450
451 /* if (view == priv->channels_view) */
452 /* gt_channels_view_handle_event(GT_CHANNELS_VIEW(priv->channels_view), (GdkEvent*) evt); */
453 /* else if (view == priv->games_view) */
454 /* gt_games_view_handle_event(GT_GAMES_VIEW(priv->games_view), (GdkEvent*) evt); */
455 /* else if (view == priv->follows_view) */
456 /* gt_follows_view_handle_event(GT_FOLLOWS_VIEW(priv->follows_view), (GdkEvent* )evt); */
457 }
458 }
459
460 return FALSE;
461 }
462
463 static gboolean
delete_cb(GtkWidget * widget,GdkEvent * evt,gpointer udata)464 delete_cb(GtkWidget* widget,
465 GdkEvent* evt,
466 gpointer udata)
467 {
468 GtWin* self = GT_WIN(udata);
469 GtWinPrivate* priv = gt_win_get_instance_private(self);
470 int width;
471 int height;
472
473 gtk_window_get_size(GTK_WINDOW(self), &width, &height);
474
475 g_settings_set_int(main_app->settings, "window-width", width);
476 g_settings_set_int(main_app->settings, "window-height", height);
477
478 return FALSE;
479 }
480
481 static void
close_channel_cb(GSimpleAction * action,GVariant * arg,gpointer udata)482 close_channel_cb(GSimpleAction* action,
483 GVariant* arg, gpointer udata)
484 {
485 RETURN_IF_FAIL(G_IS_SIMPLE_ACTION(action));
486 RETURN_IF_FAIL(arg == NULL);
487 RETURN_IF_FAIL(GT_IS_WIN(udata));
488
489 GtWin* self = GT_WIN(udata);
490 GtWinPrivate* priv = gt_win_get_instance_private(self);
491
492 gt_player_close_channel(GT_PLAYER(self->player));
493
494 gtk_stack_set_visible_child(GTK_STACK(priv->main_stack),
495 priv->browse_stack);
496 }
497
498 static void
close_player_cb(GSimpleAction * action,GVariant * arg,gpointer udata)499 close_player_cb(GSimpleAction* action,
500 GVariant* arg,
501 gpointer udata)
502 {
503 GtWin* self = GT_WIN(udata);
504 GtWinPrivate* priv = gt_win_get_instance_private(self);
505
506 gt_player_close_channel(GT_PLAYER(self->player));
507
508 gtk_stack_set_visible_child_name(GTK_STACK(priv->header_stack),
509 "browse");
510 gtk_stack_set_visible_child_name(GTK_STACK(priv->main_stack),
511 "browse");
512 }
513
514 static void
show_vods_cb(GSimpleAction * action,GVariant * arg,gpointer udata)515 show_vods_cb(GSimpleAction* action,
516 GVariant* arg, gpointer udata)
517 {
518 RETURN_IF_FAIL(G_IS_SIMPLE_ACTION(action));
519 RETURN_IF_FAIL(GT_IS_WIN(udata));
520
521 GtWin* self = GT_WIN(udata);
522 GtWinPrivate* priv = gt_win_get_instance_private(self);
523
524 RETURN_IF_FAIL(self->open_channel);
525
526 gtk_stack_set_visible_child(GTK_STACK(priv->channel_stack), priv->channel_vod_container);
527 }
528
529 static gboolean
window_state_event_cb(GtkWidget * widget,GdkEventWindowState * evt,gpointer udata)530 window_state_event_cb(GtkWidget* widget,
531 GdkEventWindowState* evt, gpointer udata)
532 {
533 RETURN_VAL_IF_FAIL(GT_IS_WIN(widget), GDK_EVENT_PROPAGATE);
534 RETURN_VAL_IF_FAIL(udata == NULL, GDK_EVENT_PROPAGATE);
535
536 GtWin* self = GT_WIN(widget);
537 GtWinPrivate* priv = gt_win_get_instance_private(self);
538
539 priv->fullscreen = evt->new_window_state & GDK_WINDOW_STATE_FULLSCREEN;
540
541 g_object_notify_by_pspec(G_OBJECT(self), props[PROP_FULLSCREEN]);
542
543 return GDK_EVENT_PROPAGATE;
544 }
545
546 static void
update_fullscreen(GtWin * self,gboolean fullscreen)547 update_fullscreen(GtWin* self, gboolean fullscreen)
548 {
549 if (fullscreen)
550 gtk_window_fullscreen(GTK_WINDOW(self));
551 else
552 gtk_window_unfullscreen(GTK_WINDOW(self));
553 }
554
555 static GActionEntry win_actions[] =
556 {
557 {"refresh", refresh_cb, NULL, NULL, NULL},
558 {"go_back", go_back_cb, NULL, NULL, NULL},
559 {"show_about", show_about_cb, NULL, NULL, NULL},
560 {"show_settings", show_settings_cb, NULL, NULL},
561 {"show_settings_with_view", NULL, "s", "'general'", show_settings_cb},
562 {"show_twitch_login", show_twitch_login_cb, NULL, NULL, NULL},
563 {"show_channel_info", show_channel_info_cb, NULL, NULL, NULL},
564 {"close_channel", close_channel_cb, NULL, NULL, NULL},
565 {"show_vods", show_vods_cb, NULL, NULL, NULL},
566 };
567
568 static void
finalize(GObject * object)569 finalize(GObject* object)
570 {
571 GtWin* self = (GtWin*) object;
572 GtWinPrivate* priv = gt_win_get_instance_private(self);
573
574 G_OBJECT_CLASS(gt_win_parent_class)->finalize(object);
575
576 g_message("{GtWin} Finalise");
577 }
578
579 static void
get_property(GObject * obj,guint prop,GValue * val,GParamSpec * pspec)580 get_property (GObject* obj,
581 guint prop,
582 GValue* val,
583 GParamSpec* pspec)
584 {
585 GtWin* self = GT_WIN(obj);
586 GtWinPrivate* priv = gt_win_get_instance_private(self);
587
588 switch (prop)
589 {
590 case PROP_FULLSCREEN:
591 g_value_set_boolean(val, priv->fullscreen);
592 break;
593 case PROP_OPEN_CHANNEL:
594 g_value_set_object(val, self->open_channel);
595 break;
596 default:
597 G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop, pspec);
598 }
599 }
600
601 static void
set_property(GObject * obj,guint prop,const GValue * val,GParamSpec * pspec)602 set_property(GObject* obj,
603 guint prop,
604 const GValue* val,
605 GParamSpec* pspec)
606 {
607 GtWin* self = GT_WIN(obj);
608
609 switch (prop)
610 {
611 case PROP_FULLSCREEN:
612 update_fullscreen(self, g_value_get_boolean(val));
613 break;
614 case PROP_OPEN_CHANNEL:
615 g_clear_object(&self->open_channel);
616 self->open_channel = g_value_dup_object(val);
617 break;
618 default:
619 G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop, pspec);
620 }
621 }
622
623 static void
constructed(GObject * obj)624 constructed(GObject* obj)
625 {
626 GtWin* self = GT_WIN(obj);
627 GtWinPrivate* priv = gt_win_get_instance_private(self);
628
629 g_signal_connect(priv->browse_stack, "notify::visible-child",
630 G_CALLBACK(container_view_changed_cb), self);
631
632 container_view_changed_cb(NULL, NULL, self);
633
634 G_OBJECT_CLASS(gt_win_parent_class)->constructed(obj);
635 }
636
637 static void
gt_win_class_init(GtWinClass * klass)638 gt_win_class_init(GtWinClass* klass)
639 {
640 GObjectClass* object_class = G_OBJECT_CLASS(klass);
641
642 object_class->finalize = finalize;
643 object_class->get_property = get_property;
644 object_class->set_property = set_property;
645 object_class->constructed = constructed;
646
647 props[PROP_FULLSCREEN] = g_param_spec_boolean("fullscreen", "Fullscreen", "Whether window is fullscreen",
648 FALSE, G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_EXPLICIT_NOTIFY);
649
650 props[PROP_OPEN_CHANNEL] = g_param_spec_object("open-channel", "Open channel", "Currenly open channel",
651 GT_TYPE_CHANNEL, G_PARAM_READWRITE);
652
653 g_object_class_install_properties(object_class, NUM_PROPS, props);
654
655 gtk_widget_class_set_template_from_resource(GTK_WIDGET_CLASS(klass),
656 "/com/vinszent/GnomeTwitch/ui/gt-win.ui");
657 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS(klass), GtWin, main_stack);
658 gtk_widget_class_bind_template_child(GTK_WIDGET_CLASS(klass), GtWin, player);
659 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS(klass), GtWin, header_stack);
660 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS(klass), GtWin, browse_header_bar);
661 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS(klass), GtWin, browse_stack);
662 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS(klass), GtWin, browse_stack_switcher);
663 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS(klass), GtWin, info_revealer);
664 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS(klass), GtWin, info_label);
665 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS(klass), GtWin, info_bar);
666 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS(klass), GtWin, info_bar_yes_button);
667 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS(klass), GtWin, info_bar_no_button);
668 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS(klass), GtWin, info_bar_ok_button);
669 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS(klass), GtWin, info_bar_details_button);
670 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS(klass), GtWin, info_bar_close_button);
671 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS(klass), GtWin, channel_stack);
672 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS(klass), GtWin, channel_vod_container);
673 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS(klass), GtWin, channel_header_bar);
674
675 GT_TYPE_PLAYER; // Hack to load GtPlayer into the symbols table
676 GT_TYPE_BROWSE_HEADER_BAR;
677 GT_TYPE_CHANNEL_CONTAINER_VIEW;
678 GT_TYPE_FOLLOWED_CONTAINER_VIEW;
679 GT_TYPE_GAME_CONTAINER_VIEW;
680 GT_TYPE_CHANNEL_VOD_CONTAINER;
681 GT_TYPE_CHANNEL_HEADER_BAR;
682 GT_TYPE_CHAT;
683 }
684
685 static void
gt_win_init(GtWin * self)686 gt_win_init(GtWin* self)
687 {
688 GtWinPrivate* priv = gt_win_get_instance_private(self);
689 GPropertyAction* action;
690
691 gtk_window_set_application(GTK_WINDOW(self), GTK_APPLICATION(main_app));
692
693 gtk_widget_init_template(GTK_WIDGET(self));
694 /* NOTE: Win will already be realized after we init the template */
695 gtk_widget_realize(priv->channel_header_bar);
696
697 priv->cur_info_data = NULL;
698 priv->info_queue = g_queue_new();
699
700 gtk_window_set_default_size(GTK_WINDOW(self),
701 g_settings_get_int(main_app->settings, "window-width"),
702 g_settings_get_int(main_app->settings, "window-height"));
703
704 gtk_window_set_default_icon_name("com.vinszent.GnomeTwitch");
705
706 gtk_window_set_icon_name(GTK_WINDOW(self), "com.vinszent.GnomeTwitch");
707
708 GdkScreen* screen = gdk_screen_get_default();
709 GtkCssProvider* css = gtk_css_provider_new();
710 gtk_css_provider_load_from_resource(css, "/com/vinszent/GnomeTwitch/com.vinszent.GnomeTwitch.style.css");
711 gtk_style_context_add_provider_for_screen(screen, GTK_STYLE_PROVIDER(css),
712 GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
713
714 g_signal_connect_after(self, "key-press-event", G_CALLBACK(key_press_cb), self);
715 g_signal_connect(self, "delete-event", G_CALLBACK(delete_cb), self);
716 g_signal_connect_after(priv->info_bar, "response", G_CALLBACK(close_info_bar_cb), self);
717 g_signal_connect(self, "window-state-event", G_CALLBACK(window_state_event_cb), NULL);
718
719 g_action_map_add_action_entries(G_ACTION_MAP(self),
720 win_actions,
721 G_N_ELEMENTS(win_actions),
722 self);
723
724 action = g_property_action_new("toggle_fullscreen", self, "fullscreen");
725 g_action_map_add_action(G_ACTION_MAP(self),
726 G_ACTION(action));
727 g_object_unref(action);
728
729 GtkWindowGroup* window_group = gtk_window_group_new();
730 gtk_window_group_add_window(window_group, GTK_WINDOW(self));
731 g_object_unref(window_group);
732 }
733
734 void
gt_win_open_channel(GtWin * self,GtChannel * chan)735 gt_win_open_channel(GtWin* self, GtChannel* chan)
736 {
737 g_assert(GT_IS_WIN(self));
738 g_assert(GT_IS_CHANNEL(chan));
739
740 GtWinPrivate* priv = gt_win_get_instance_private(self);
741
742 g_object_set(self, "open-channel", chan, NULL);
743
744 gt_player_open_channel(GT_PLAYER(self->player), chan);
745
746 g_object_set(G_OBJECT(priv->channel_vod_container), "channel-id", gt_channel_get_id(chan), NULL);
747
748 gt_item_container_refresh(GT_ITEM_CONTAINER(priv->channel_vod_container));
749
750 gtk_stack_set_visible_child(GTK_STACK(priv->channel_stack), GTK_WIDGET(self->player));
751 gtk_stack_set_visible_child(GTK_STACK(priv->main_stack), priv->channel_stack);
752 }
753
754 void
gt_win_play_vod(GtWin * self,GtVOD * vod)755 gt_win_play_vod(GtWin* self, GtVOD* vod)
756 {
757 RETURN_IF_FAIL(GT_IS_WIN(self));
758 RETURN_IF_FAIL(GT_IS_VOD(vod));
759
760 GtWinPrivate* priv = gt_win_get_instance_private(self);
761
762 gt_player_open_vod(self->player, vod);
763 gtk_stack_set_visible_child(GTK_STACK(priv->channel_stack), GTK_WIDGET(self->player));
764 }
765
766 gboolean
gt_win_is_fullscreen(GtWin * self)767 gt_win_is_fullscreen(GtWin* self)
768 {
769 GtWinPrivate* priv = gt_win_get_instance_private(self);
770
771 return priv->fullscreen;
772 }
773
774 void
gt_win_toggle_fullscreen(GtWin * self)775 gt_win_toggle_fullscreen(GtWin* self)
776 {
777 GtWinPrivate* priv = gt_win_get_instance_private(self);
778
779 g_object_set(self, "fullscreen", !priv->fullscreen, NULL);
780 }
781
782 void
gt_win_show_info_message(GtWin * self,const gchar * msg_fmt,...)783 gt_win_show_info_message(GtWin* self, const gchar* msg_fmt, ...)
784 {
785 RETURN_IF_FAIL(GT_IS_WIN(self));
786
787 GtWinPrivate* priv = gt_win_get_instance_private(self);
788 QueuedInfoData* data = g_new0(QueuedInfoData, 1);
789 va_list fmt_list;
790
791 va_start(fmt_list, msg_fmt);
792 data->msg = g_strdup_vprintf(msg_fmt, fmt_list);
793 va_end(fmt_list);
794
795 g_queue_push_tail(priv->info_queue, data);
796
797 if (!gtk_revealer_get_reveal_child(GTK_REVEALER(priv->info_revealer)))
798 show_info_bar(self);
799 }
800
801 void
gt_win_show_error_message(GtWin * self,const gchar * secondary,const gchar * details_fmt,...)802 gt_win_show_error_message(GtWin* self, const gchar* secondary, const gchar* details_fmt, ...)
803 {
804 g_assert(GT_IS_WIN(self));
805
806 gchar** udata = g_malloc(sizeof(gchar*)*3);
807 // Translators: Please keep the markup tags
808 gchar* msg = g_strdup_printf(_("<b>Something went wrong:</b> %s."), secondary);
809 va_list details_list;
810
811 *udata = g_strdup(secondary);
812
813 va_start(details_list, details_fmt);
814 *(udata+1) = g_strdup_vprintf(details_fmt, details_list);
815 va_end(details_list);
816 *(udata+2) = NULL;
817
818 gt_win_ask_question(self, msg, G_CALLBACK(show_error_dialogue_cb), udata);
819 }
820
821 void
gt_win_show_error_dialogue(GtWin * self,const gchar * secondary,const gchar * details_fmt,...)822 gt_win_show_error_dialogue(GtWin* self, const gchar* secondary, const gchar* details_fmt, ...)
823 {
824 g_assert(GT_IS_WIN(self));
825
826 gchar** udata = g_malloc(sizeof(gchar*)*3);
827 va_list details_list;
828
829 *udata = g_strdup(secondary);
830
831 va_start(details_list, details_fmt);
832 *(udata+1) = g_strdup_vprintf(details_fmt, details_list);
833 va_end(details_list);
834 *(udata+2) = NULL;
835
836 show_error_dialogue_cb(NULL, GTK_RESPONSE_OK, udata);
837 }
838
839 void
gt_win_ask_question(GtWin * self,const gchar * msg,GCallback cb,gpointer udata)840 gt_win_ask_question(GtWin* self, const gchar* msg, GCallback cb, gpointer udata)
841 {
842 GtWinPrivate* priv = gt_win_get_instance_private(self);
843 QueuedInfoData* data = g_new(QueuedInfoData, 1);
844
845 data->msg = g_strdup(msg);
846 data->cb = cb;
847 data->udata = udata;
848
849 g_queue_push_tail(priv->info_queue, data);
850
851 if (!gtk_revealer_get_reveal_child(GTK_REVEALER(priv->info_revealer)))
852 show_info_bar(self);
853 }
854
855 void
gt_win_inhibit_screensaver(GtWin * self)856 gt_win_inhibit_screensaver(GtWin* self)
857 {
858 RETURN_IF_FAIL(GT_IS_WIN(self));
859
860 GtWinPrivate* priv = gt_win_get_instance_private(self);
861
862 if (priv->inhibit_cookie > 0)
863 {
864 WARNING("Already inhibiting screensaver");
865 return;
866 }
867
868 priv->inhibit_cookie = gtk_application_inhibit(GTK_APPLICATION(main_app),
869 GTK_WINDOW(self), GTK_APPLICATION_INHIBIT_IDLE, "Playing a stream");
870
871 if (priv->inhibit_cookie == 0)
872 {
873 INFO("Unable to inhibit screensaver via application, attempting via DBus");
874
875 /* TODO: Use the async versions of the dbus funcs */
876 g_autoptr(GError) err = NULL;
877 g_autoptr(GDBusProxy) proxy = g_dbus_proxy_new_for_bus_sync(G_BUS_TYPE_SESSION, G_DBUS_PROXY_FLAGS_NONE, NULL,
878 "org.freedesktop.ScreenSaver", "/org/freedesktop/ScreenSaver", "org.freedesktop.ScreenSaver", NULL, &err);
879 g_autoptr(GVariant) ret = NULL;
880
881 if (err)
882 {
883 WARNING("Unable to create DBus proxy to inhibit screensaver because: %s", err->message);
884 return;
885 }
886
887 ret = g_dbus_proxy_call_sync(proxy, "Inhibit",
888 g_variant_new("(ss)", "GNOME Twitch", "Playing a stream"),
889 G_DBUS_CALL_FLAGS_NONE, -1, NULL, &err);
890
891 if (err)
892 {
893 WARNING("Unable to call inhibit function on DBus proxy because: %s", err->message);
894 return;
895 }
896
897 g_variant_get(ret, "(u)", &priv->inhibit_cookie);
898 }
899
900 if (priv->inhibit_cookie > 0)
901 INFO("Successfully inhibited screensaver");
902 }
903
904 void
gt_win_uninhibit_screensaver(GtWin * self)905 gt_win_uninhibit_screensaver(GtWin* self)
906 {
907 RETURN_IF_FAIL(GT_IS_WIN(self));
908
909 GtWinPrivate* priv = gt_win_get_instance_private(self);
910
911 if (priv->inhibit_cookie == 0)
912 {
913 WARNING("Not currently inhibiting screensaver");
914 return;
915 }
916
917 if (gtk_application_is_inhibited(GTK_APPLICATION(main_app), GTK_APPLICATION_INHIBIT_IDLE))
918 gtk_application_uninhibit(GTK_APPLICATION(main_app), priv->inhibit_cookie);
919
920 /* NOTE: If we are still inhibited, try uninhibiting via DBus */
921 if (gtk_application_is_inhibited(GTK_APPLICATION(main_app), GTK_APPLICATION_INHIBIT_IDLE))
922 {
923 g_autoptr(GError) err = NULL;
924 g_autoptr(GDBusProxy) proxy = g_dbus_proxy_new_for_bus_sync(G_BUS_TYPE_SESSION, G_DBUS_PROXY_FLAGS_NONE, NULL,
925 "org.freedesktop.ScreenSaver", "/org/freedesktop/ScreenSaver", "org.freedesktop.ScreenSaver", NULL, &err);
926 g_autoptr(GVariant) ret = NULL;
927
928 if (err)
929 {
930 WARNING("Unable to create DBus proxy to uninhibit screensaver because: %s", err->message);
931 return;
932 }
933
934 ret = g_dbus_proxy_call_sync(proxy, "UnInhibit",
935 g_variant_new("(u)", priv->inhibit_cookie),
936 G_DBUS_CALL_FLAGS_NONE, -1, NULL, &err);
937
938 if (err)
939 {
940 WARNING("Unable to call uninhibit function on DBus proxy because: %s", err->message);
941 return;
942 }
943 }
944
945 INFO("Successfully uninhibited screensaver");
946
947 priv->inhibit_cookie = 0;
948 }
949