1 /*
2 DeaDBeeF -- the music player
3 Copyright (C) 2009-2015 Alexey Yakovenko and other contributors
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17
18 2. Altered source versions must be plainly marked as such, and must not be
19 misrepresented as being the original software.
20
21 3. This notice may not be removed or altered from any source distribution.
22 */
23
24
25 #include "../../deadbeef.h"
26 #include <gtk/gtk.h>
27 #ifdef HAVE_CONFIG_H
28 #include "../../config.h"
29 #endif
30 #include <string.h>
31 #include <stdlib.h>
32 #include <math.h>
33 #include <sys/time.h>
34 #include <unistd.h>
35 #include <sys/stat.h>
36 #ifdef __linux__
37 #include <sys/prctl.h>
38 #endif
39 #include "../../gettext.h"
40 #include "gtkui.h"
41 #include "ddblistview.h"
42 #include "search.h"
43 #include "progress.h"
44 #include "interface.h"
45 #include "callbacks.h"
46 #include "support.h"
47 #include "../libparser/parser.h"
48 #include "drawing.h"
49 #include "trkproperties.h"
50 #include "../artwork/artwork.h"
51 #include "coverart.h"
52 #include "plcommon.h"
53 #include "ddbtabstrip.h"
54 #include "eq.h"
55 #include "actions.h"
56 #include "pluginconf.h"
57 #include "gtkui_api.h"
58 #include "wingeom.h"
59 #include "widgets.h"
60 #ifndef __APPLE__
61 #include "X11/Xlib.h"
62 #else
63 #include "retina.h"
64 #endif
65 #include "actionhandlers.h"
66 #include "hotkeys.h"
67 #include "../hotkeys/hotkeys.h"
68
69 #define trace(...) { fprintf(stderr, __VA_ARGS__); }
70 //#define trace(fmt,...)
71
72 static ddb_gtkui_t plugin;
73 DB_functions_t *deadbeef;
74
75 // main widgets
76 GtkWidget *mainwin;
77 GtkWidget *searchwin;
78 GtkStatusIcon *trayicon;
79 GtkWidget *traymenu;
80
81 static int gtkui_accept_messages = 0;
82
83 static gint refresh_timeout = 0;
84
85 int fileadded_listener_id;
86 int fileadd_beginend_listener_id;
87 // overriden API methods
88 #if 0
89 int (*gtkui_original_plt_add_dir) (ddb_playlist_t *plt, const char *dirname, int (*cb)(DB_playItem_t *it, void *data), void *user_data);
90 int (*gtkui_original_plt_add_file) (ddb_playlist_t *plt, const char *fname, int (*cb)(DB_playItem_t *it, void *data), void *user_data);
91 int (*gtkui_original_pl_add_files_begin) (ddb_playlist_t *plt);
92 void (*gtkui_original_pl_add_files_end) (void);
93 #endif
94
95 // cached config variables
96 int gtkui_embolden_current_track;
97 int gtkui_embolden_tracks;
98 int gtkui_embolden_selected_tracks;
99 int gtkui_italic_selected_tracks;
100 int gtkui_italic_tracks;
101 int gtkui_italic_current_track;
102
103 int gtkui_tabstrip_embolden_playing;
104 int gtkui_tabstrip_italic_playing;
105 int gtkui_tabstrip_embolden_selected;
106 int gtkui_tabstrip_italic_selected;
107
108 int gtkui_groups_pinned;
109
110 #ifdef __APPLE__
111 int gtkui_is_retina = 0;
112 #endif
113
114 int gtkui_unicode_playstate = 0;
115 int gtkui_disable_seekbar_overlay = 0;
116
117 #define TRAY_ICON "deadbeef_tray_icon"
118
119 void
gtkpl_free(DdbListview * pl)120 gtkpl_free (DdbListview *pl) {
121 #if 0
122 if (colhdr_anim.timeline) {
123 timeline_free (colhdr_anim.timeline, 1);
124 colhdr_anim.timeline = 0;
125 }
126 #endif
127 }
128
129 struct fromto_t {
130 DB_playItem_t *from;
131 DB_playItem_t *to;
132 };
133
134 static gboolean
135 update_win_title_idle (gpointer data);
136
137 // update status bar and window title
138 static int sb_context_id = -1;
139 static char sb_text[512];
140 static float last_songpos = -1;
141 static char sbitrate[20] = "";
142 static struct timeval last_br_update;
143
144 static gboolean
update_songinfo(gpointer ctx)145 update_songinfo (gpointer ctx) {
146 int iconified = gdk_window_get_state(gtk_widget_get_window(mainwin)) & GDK_WINDOW_STATE_ICONIFIED;
147 if (!gtk_widget_get_visible (mainwin) || iconified) {
148 return FALSE;
149 }
150 DB_output_t *output = deadbeef->get_output ();
151 char sbtext_new[512] = "-";
152
153 float pl_totaltime = deadbeef->pl_get_totaltime ();
154 int daystotal = (int)pl_totaltime / (3600*24);
155 int hourtotal = ((int)pl_totaltime / 3600) % 24;
156 int mintotal = ((int)pl_totaltime/60) % 60;
157 int sectotal = ((int)pl_totaltime) % 60;
158
159 char totaltime_str[512] = "";
160 if (daystotal == 0) {
161 snprintf (totaltime_str, sizeof (totaltime_str), "%d:%02d:%02d", hourtotal, mintotal, sectotal);
162 }
163 else if (daystotal == 1) {
164 snprintf (totaltime_str, sizeof (totaltime_str), _("1 day %d:%02d:%02d"), hourtotal, mintotal, sectotal);
165 }
166 else {
167 snprintf (totaltime_str, sizeof (totaltime_str), _("%d days %d:%02d:%02d"), daystotal, hourtotal, mintotal, sectotal);
168 }
169
170 DB_playItem_t *track = deadbeef->streamer_get_playing_track ();
171 DB_fileinfo_t *c = deadbeef->streamer_get_current_fileinfo (); // FIXME: might crash streamer
172
173 float duration = track ? deadbeef->pl_get_item_duration (track) : -1;
174
175 if (!output || (output->state () == OUTPUT_STATE_STOPPED || !track || !c)) {
176 snprintf (sbtext_new, sizeof (sbtext_new), _("Stopped | %d tracks | %s total playtime"), deadbeef->pl_getcount (PL_MAIN), totaltime_str);
177 }
178 else {
179 float playpos = deadbeef->streamer_get_playpos ();
180 int minpos = playpos / 60;
181 int secpos = playpos - minpos * 60;
182 int mindur = duration / 60;
183 int secdur = duration - mindur * 60;
184
185 const char *mode;
186 char temp[20];
187 if (c->fmt.channels <= 2) {
188 mode = c->fmt.channels == 1 ? _("Mono") : _("Stereo");
189 }
190 else {
191 snprintf (temp, sizeof (temp), "%dch Multichannel", c->fmt.channels);
192 mode = temp;
193 }
194 int samplerate = c->fmt.samplerate;
195 int bitspersample = c->fmt.bps;
196 // codec_unlock ();
197
198 char t[100];
199 if (duration >= 0) {
200 snprintf (t, sizeof (t), "%d:%02d", mindur, secdur);
201 }
202 else {
203 strcpy (t, "-:--");
204 }
205
206 struct timeval tm;
207 gettimeofday (&tm, NULL);
208 if (tm.tv_sec - last_br_update.tv_sec + (tm.tv_usec - last_br_update.tv_usec) / 1000000.0 >= 0.3) {
209 memcpy (&last_br_update, &tm, sizeof (tm));
210 int bitrate = deadbeef->streamer_get_apx_bitrate ();
211 if (bitrate > 0) {
212 snprintf (sbitrate, sizeof (sbitrate), _("| %4d kbps "), bitrate);
213 }
214 else {
215 sbitrate[0] = 0;
216 }
217 }
218 const char *spaused = deadbeef->get_output ()->state () == OUTPUT_STATE_PAUSED ? _("Paused | ") : "";
219 char filetype[20];
220 if (!deadbeef->pl_get_meta (track, ":FILETYPE", filetype, sizeof (filetype))) {
221 strcpy (filetype, "-");
222 }
223 snprintf (sbtext_new, sizeof (sbtext_new), _("%s%s %s| %dHz | %d bit | %s | %d:%02d / %s | %d tracks | %s total playtime"), spaused, filetype, sbitrate, samplerate, bitspersample, mode, minpos, secpos, t, deadbeef->pl_getcount (PL_MAIN), totaltime_str);
224 }
225
226 if (strcmp (sbtext_new, sb_text)) {
227 strcpy (sb_text, sbtext_new);
228
229 // form statusline
230 GtkStatusbar *sb = GTK_STATUSBAR (lookup_widget (mainwin, "statusbar"));
231 if (sb_context_id == -1) {
232 sb_context_id = gtk_statusbar_get_context_id (sb, "msg");
233 }
234
235 gtk_statusbar_pop (sb, sb_context_id);
236 gtk_statusbar_push (sb, sb_context_id, sb_text);
237 }
238
239 if (track) {
240 deadbeef->pl_item_unref (track);
241 }
242 return FALSE;
243 }
244
245 void
set_tray_tooltip(const char * text)246 set_tray_tooltip (const char *text) {
247 if (trayicon) {
248 #if !GTK_CHECK_VERSION(2,16,0)
249 gtk_status_icon_set_tooltip (trayicon, text);
250 #else
251 gtk_status_icon_set_tooltip_text (trayicon, text);
252 #endif
253 }
254 }
255
256 gboolean
on_trayicon_scroll_event(GtkWidget * widget,GdkEventScroll * event,gpointer user_data)257 on_trayicon_scroll_event (GtkWidget *widget,
258 GdkEventScroll *event,
259 gpointer user_data)
260 {
261 float vol = deadbeef->volume_get_db ();
262 int sens = deadbeef->conf_get_int ("gtkui.tray_volume_sensitivity", 1);
263 if (event->direction == GDK_SCROLL_UP || event->direction == GDK_SCROLL_RIGHT) {
264 vol += sens;
265 }
266 else if (event->direction == GDK_SCROLL_DOWN || event->direction == GDK_SCROLL_LEFT) {
267 vol -= sens;
268 }
269 if (vol > 0) {
270 vol = 0;
271 }
272 else if (vol < deadbeef->volume_get_min_db ()) {
273 vol = deadbeef->volume_get_min_db ();
274 }
275 deadbeef->volume_set_db (vol);
276
277 #if 0
278 char str[100];
279 if (deadbeef->conf_get_int ("gtkui.show_gain_in_db", 1)) {
280 snprintf (str, sizeof (str), "Gain: %s%d dB", vol == 0 ? "+" : "", (int)vol);
281 }
282 else {
283 snprintf (str, sizeof (str), "Gain: %d%%", (int)(deadbeef->volume_get_amp () * 100));
284 }
285 set_tray_tooltip (str);
286 #endif
287
288 return FALSE;
289 }
290
291 void
mainwin_toggle_visible(void)292 mainwin_toggle_visible (void) {
293 int iconified = gdk_window_get_state(gtk_widget_get_window(mainwin)) & GDK_WINDOW_STATE_ICONIFIED;
294 if (gtk_widget_get_visible (mainwin) && !iconified) {
295 gtk_widget_hide (mainwin);
296 }
297 else {
298 wingeom_restore (mainwin, "mainwin", 40, 40, 500, 300, 0);
299 if (iconified) {
300 gtk_window_deiconify (GTK_WINDOW(mainwin));
301 }
302 else {
303 gtk_window_present (GTK_WINDOW (mainwin));
304 }
305 }
306 }
307
308 #if !GTK_CHECK_VERSION(2,14,0)
309 gboolean
on_trayicon_activate(GtkWidget * widget,gpointer user_data)310 on_trayicon_activate (GtkWidget *widget,
311 gpointer user_data)
312 {
313 mainwin_toggle_visible ();
314 return FALSE;
315 }
316
317 #else
318
319 gboolean
on_trayicon_button_press_event(GtkWidget * widget,GdkEventButton * event,gpointer user_data)320 on_trayicon_button_press_event (GtkWidget *widget,
321 GdkEventButton *event,
322 gpointer user_data)
323 {
324 if (event->button == 1 && event->type == GDK_BUTTON_PRESS) {
325 mainwin_toggle_visible ();
326 }
327 else if (event->button == 2 && event->type == GDK_BUTTON_PRESS) {
328 deadbeef->sendmessage (DB_EV_TOGGLE_PAUSE, 0, 0, 0);
329 }
330 return FALSE;
331 }
332 #endif
333
334 gboolean
on_trayicon_popup_menu(GtkWidget * widget,guint button,guint time,gpointer user_data)335 on_trayicon_popup_menu (GtkWidget *widget,
336 guint button,
337 guint time,
338 gpointer user_data)
339 {
340 gtk_menu_popup (GTK_MENU (traymenu), NULL, NULL, gtk_status_icon_position_menu, trayicon, button, time);
341 return FALSE;
342 }
343
344 static gboolean
activate_cb(gpointer nothing)345 activate_cb (gpointer nothing) {
346 gtk_widget_show (mainwin);
347 gtk_window_present (GTK_WINDOW (mainwin));
348 return FALSE;
349 }
350
351 void
redraw_queued_tracks(DdbListview * pl)352 redraw_queued_tracks (DdbListview *pl) {
353 DB_playItem_t *it;
354 int idx = 0;
355 deadbeef->pl_lock ();
356 for (it = deadbeef->pl_get_first (PL_MAIN); it; idx++) {
357 if (deadbeef->pl_playqueue_test (it) != -1) {
358 ddb_listview_draw_row (pl, idx, (DdbListviewIter)it);
359 }
360 DB_playItem_t *next = deadbeef->pl_get_next (it, PL_MAIN);
361 deadbeef->pl_item_unref (it);
362 it = next;
363 }
364 deadbeef->pl_unlock ();
365 }
366
367 gboolean
redraw_queued_tracks_cb(gpointer plt)368 redraw_queued_tracks_cb (gpointer plt) {
369 DdbListview *list = plt;
370 int iconified = gdk_window_get_state(gtk_widget_get_window(mainwin)) & GDK_WINDOW_STATE_ICONIFIED;
371 if (!gtk_widget_get_visible (mainwin) || iconified) {
372 return FALSE;
373 }
374 redraw_queued_tracks (list);
375 return FALSE;
376 }
377
378 void
gtkpl_songchanged_wrapper(DB_playItem_t * from,DB_playItem_t * to)379 gtkpl_songchanged_wrapper (DB_playItem_t *from, DB_playItem_t *to) {
380 struct fromto_t *ft = malloc (sizeof (struct fromto_t));
381 ft->from = from;
382 ft->to = to;
383 if (from) {
384 deadbeef->pl_item_ref (from);
385 }
386 if (to) {
387 deadbeef->pl_item_ref (to);
388 }
389 g_idle_add (update_win_title_idle, ft);
390 if (searchwin && gtk_widget_get_window (searchwin)) {
391 int iconified = gdk_window_get_state(gtk_widget_get_window (searchwin)) & GDK_WINDOW_STATE_ICONIFIED;
392 if (gtk_widget_get_visible (searchwin) && !iconified) {
393 g_idle_add (redraw_queued_tracks_cb, DDB_LISTVIEW (lookup_widget (searchwin, "searchlist")));
394 }
395 }
396 }
397
398 const char *gtkui_default_titlebar_playing = "%artist% - %title% - DeaDBeeF-%_deadbeef_version%";
399 const char *gtkui_default_titlebar_stopped = "DeaDBeeF-%_deadbeef_version%";
400
401 static char *titlebar_playing_bc;
402 static char *titlebar_stopped_bc;
403
404 static void
titlebar_tf_free(void)405 titlebar_tf_free (void) {
406 if (titlebar_playing_bc) {
407 deadbeef->tf_free (titlebar_playing_bc);
408 titlebar_playing_bc = NULL;
409 }
410
411 if (titlebar_stopped_bc) {
412 deadbeef->tf_free (titlebar_stopped_bc);
413 titlebar_stopped_bc = NULL;
414 }
415 }
416
417 void
gtkui_titlebar_tf_init(void)418 gtkui_titlebar_tf_init (void) {
419 titlebar_tf_free ();
420
421 char fmt[500];
422 deadbeef->conf_get_str ("gtkui.titlebar_playing_tf", gtkui_default_titlebar_playing, fmt, sizeof (fmt));
423 titlebar_playing_bc = deadbeef->tf_compile (fmt);
424 deadbeef->conf_get_str ("gtkui.titlebar_stopped_tf", gtkui_default_titlebar_stopped, fmt, sizeof (fmt));
425 titlebar_stopped_bc = deadbeef->tf_compile (fmt);
426 }
427
428 void
gtkui_set_titlebar(DB_playItem_t * it)429 gtkui_set_titlebar (DB_playItem_t *it) {
430 if (!it) {
431 it = deadbeef->streamer_get_playing_track ();
432 }
433 else {
434 deadbeef->pl_item_ref (it);
435 }
436 char str[1024];
437 ddb_tf_context_t ctx = {
438 ._size = sizeof (ddb_tf_context_t),
439 .it = it,
440 // FIXME: current playlist is not correct here.
441 // need the playlist corresponding to the pointed track
442 .plt = deadbeef->plt_get_curr (),
443 };
444 deadbeef->tf_eval (&ctx, it ? titlebar_playing_bc : titlebar_stopped_bc, str, sizeof (str));
445 if (ctx.plt) {
446 deadbeef->plt_unref (ctx.plt);
447 ctx.plt = NULL;
448 }
449 gtk_window_set_title (GTK_WINDOW (mainwin), str);
450 if (it) {
451 deadbeef->pl_item_unref (it);
452 }
453 set_tray_tooltip (str);
454 }
455
456 static void
trackinfochanged_wrapper(DdbListview * playlist,DB_playItem_t * track,int iter)457 trackinfochanged_wrapper (DdbListview *playlist, DB_playItem_t *track, int iter) {
458 if (track) {
459 int idx = deadbeef->pl_get_idx_of_iter (track, iter);
460 if (idx != -1) {
461 ddb_listview_draw_row (playlist, idx, (DdbListviewIter)track);
462 }
463 }
464 }
465
466 void
gtkui_trackinfochanged(DB_playItem_t * track)467 gtkui_trackinfochanged (DB_playItem_t *track) {
468 if (searchwin && gtk_widget_get_visible (searchwin)) {
469 GtkWidget *search = lookup_widget (searchwin, "searchlist");
470 trackinfochanged_wrapper (DDB_LISTVIEW (search), track, PL_SEARCH);
471 }
472
473 DB_playItem_t *curr = deadbeef->streamer_get_playing_track ();
474 if (track == curr) {
475 gtkui_set_titlebar (track);
476 }
477 if (curr) {
478 deadbeef->pl_item_unref (curr);
479 }
480 }
481
482 static gboolean
trackinfochanged_cb(gpointer data)483 trackinfochanged_cb (gpointer data) {
484 gtkui_trackinfochanged (data);
485 if (data) {
486 deadbeef->pl_item_unref ((DB_playItem_t *)data);
487 }
488 return FALSE;
489 }
490
491 void
playlist_refresh(void)492 playlist_refresh (void) {
493 search_refresh ();
494 trkproperties_fill_metadata ();
495 }
496
497 static gboolean
playlistcontentchanged_cb(gpointer none)498 playlistcontentchanged_cb (gpointer none) {
499 playlist_refresh ();
500 return FALSE;
501 }
502
503 static gboolean
playlistswitch_cb(gpointer none)504 playlistswitch_cb (gpointer none) {
505 search_refresh ();
506 return FALSE;
507 }
508
509 static gboolean
gtkui_on_frameupdate(gpointer data)510 gtkui_on_frameupdate (gpointer data) {
511 update_songinfo (NULL);
512
513 return TRUE;
514 }
515
516 static gboolean
gtkui_update_status_icon(gpointer unused)517 gtkui_update_status_icon (gpointer unused) {
518 int hide_tray_icon = deadbeef->conf_get_int ("gtkui.hide_tray_icon", 0);
519 if (hide_tray_icon && !trayicon) {
520 return FALSE;
521 }
522 if (trayicon) {
523 if (hide_tray_icon) {
524 g_object_set (trayicon, "visible", FALSE, NULL);
525 }
526 else {
527 g_object_set (trayicon, "visible", TRUE, NULL);
528 }
529 return FALSE;
530 }
531 // system tray icon
532 traymenu = create_traymenu ();
533
534 char tmp[1000];
535 const char *icon_name = tmp;
536 deadbeef->conf_get_str ("gtkui.custom_tray_icon", TRAY_ICON, tmp, sizeof (tmp));
537 GtkIconTheme *theme = gtk_icon_theme_get_default();
538
539 if (!gtk_icon_theme_has_icon(theme, icon_name))
540 icon_name = "deadbeef";
541 else {
542 GtkIconInfo *icon_info = gtk_icon_theme_lookup_icon(theme, icon_name, 48, GTK_ICON_LOOKUP_USE_BUILTIN);
543 const gboolean icon_is_builtin = gtk_icon_info_get_filename(icon_info) == NULL;
544 gtk_icon_info_free(icon_info);
545 icon_name = icon_is_builtin ? "deadbeef" : icon_name;
546 }
547
548 if (!gtk_icon_theme_has_icon(theme, icon_name)) {
549 char iconpath[1024];
550 snprintf (iconpath, sizeof (iconpath), "%s/deadbeef.png", deadbeef->get_prefix ());
551 trayicon = gtk_status_icon_new_from_file(iconpath);
552 }
553 else {
554 trayicon = gtk_status_icon_new_from_icon_name(icon_name);
555 }
556 if (hide_tray_icon) {
557 g_object_set (trayicon, "visible", FALSE, NULL);
558 }
559
560 #if !GTK_CHECK_VERSION(2,14,0)
561 g_signal_connect ((gpointer)trayicon, "activate", G_CALLBACK (on_trayicon_activate), NULL);
562 #else
563 printf ("connecting button tray signals\n");
564 g_signal_connect ((gpointer)trayicon, "scroll_event", G_CALLBACK (on_trayicon_scroll_event), NULL);
565 g_signal_connect ((gpointer)trayicon, "button_press_event", G_CALLBACK (on_trayicon_button_press_event), NULL);
566 #endif
567 g_signal_connect ((gpointer)trayicon, "popup_menu", G_CALLBACK (on_trayicon_popup_menu), NULL);
568
569 gtkui_set_titlebar (NULL);
570
571 return FALSE;
572 }
573
574 static void
gtkui_hide_status_icon()575 gtkui_hide_status_icon () {
576 if (trayicon) {
577 g_object_set (trayicon, "visible", FALSE, NULL);
578 }
579 }
580
581 int
gtkui_get_curr_playlist_mod(void)582 gtkui_get_curr_playlist_mod (void) {
583 ddb_playlist_t *plt = deadbeef->plt_get_curr ();
584 int res = plt ? deadbeef->plt_get_modification_idx (plt) : 0;
585 if (plt) {
586 deadbeef->plt_unref (plt);
587 }
588 return res;
589 }
590
591 void
gtkui_setup_gui_refresh(void)592 gtkui_setup_gui_refresh (void) {
593 int tm = 1000/gtkui_get_gui_refresh_rate ();
594
595 if (refresh_timeout) {
596 g_source_remove (refresh_timeout);
597 refresh_timeout = 0;
598 }
599
600 refresh_timeout = g_timeout_add (tm, gtkui_on_frameupdate, NULL);
601 }
602
603
604 static gboolean
gtkui_on_configchanged(void * data)605 gtkui_on_configchanged (void *data) {
606 // order and looping
607 const char *w;
608
609 // order
610 const char *orderwidgets[4] = { "order_linear", "order_shuffle", "order_random", "order_shuffle_albums" };
611 w = orderwidgets[deadbeef->conf_get_int ("playback.order", PLAYBACK_ORDER_LINEAR)];
612 gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (lookup_widget (mainwin, w)), TRUE);
613
614 // looping
615 const char *loopingwidgets[3] = { "loop_all", "loop_disable", "loop_single" };
616 w = loopingwidgets[deadbeef->conf_get_int ("playback.loop", PLAYBACK_MODE_LOOP_ALL)];
617 gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (lookup_widget (mainwin, w)), TRUE);
618
619 // scroll follows playback
620 gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (lookup_widget (mainwin, "scroll_follows_playback")), deadbeef->conf_get_int ("playlist.scroll.followplayback", 1) ? TRUE : FALSE);
621
622 // cursor follows playback
623 gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (lookup_widget (mainwin, "cursor_follows_playback")), deadbeef->conf_get_int ("playlist.scroll.cursorfollowplayback", 1) ? TRUE : FALSE);
624
625 // stop after current track
626 int stop_after_current = deadbeef->conf_get_int ("playlist.stop_after_current", 0);
627 gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (lookup_widget (mainwin, "stop_after_current")), stop_after_current ? TRUE : FALSE);
628
629 // stop after current album
630 int stop_after_album = deadbeef->conf_get_int ("playlist.stop_after_album", 0);
631 gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (lookup_widget (mainwin, "stop_after_album")), stop_after_album ? TRUE : FALSE);
632
633 gtkui_embolden_current_track = deadbeef->conf_get_int ("gtkui.embolden_current_track", 0);
634 gtkui_embolden_tracks = deadbeef->conf_get_int ("gtkui.embolden_tracks", 0);
635 gtkui_embolden_selected_tracks = deadbeef->conf_get_int ("gtkui.embolden_selected_tracks", 0);
636 gtkui_italic_current_track = deadbeef->conf_get_int ("gtkui.italic_current_track", 0);
637 gtkui_italic_tracks = deadbeef->conf_get_int ("gtkui.italic_tracks", 0);
638 gtkui_italic_selected_tracks = deadbeef->conf_get_int ("gtkui.italic_selected_tracks", 0);
639 gtkui_tabstrip_embolden_playing = deadbeef->conf_get_int ("gtkui.tabstrip_embolden_playing", 0);
640 gtkui_tabstrip_italic_playing = deadbeef->conf_get_int ("gtkui.tabstrip_italic_playing", 0);
641 gtkui_tabstrip_embolden_selected = deadbeef->conf_get_int ("gtkui.tabstrip_embolden_selected", 0);
642 gtkui_tabstrip_italic_selected = deadbeef->conf_get_int ("gtkui.tabstrip_italic_selected", 0);
643
644 // titlebar tf
645 gtkui_titlebar_tf_init ();
646
647 // pin groups
648 gtkui_groups_pinned = deadbeef->conf_get_int ("playlist.pin.groups", 0);
649
650 // play state images
651 gtkui_unicode_playstate = deadbeef->conf_get_int ("gtkui.unicode_playstate", 0);
652
653 // seekbar overlay
654 gtkui_disable_seekbar_overlay = deadbeef->conf_get_int ("gtkui.disable_seekbar_overlay", 0);
655
656 // tray icon
657 gtkui_update_status_icon (NULL);
658
659 // statusbar refresh
660 gtkui_setup_gui_refresh ();
661
662 return FALSE;
663 }
664
665 static gboolean
outputchanged_cb(gpointer nothing)666 outputchanged_cb (gpointer nothing) {
667 preferences_fill_soundcards ();
668 return FALSE;
669 }
670
671 void
save_playlist_as(void)672 save_playlist_as (void) {
673 gdk_threads_add_idle (action_save_playlist_handler_cb, NULL);
674 }
675
676 void
on_playlist_save_as_activate(GtkMenuItem * menuitem,gpointer user_data)677 on_playlist_save_as_activate (GtkMenuItem *menuitem,
678 gpointer user_data)
679 {
680 save_playlist_as ();
681 }
682
683 void
on_playlist_load_activate(GtkMenuItem * menuitem,gpointer user_data)684 on_playlist_load_activate (GtkMenuItem *menuitem,
685 gpointer user_data)
686 {
687 gdk_threads_add_idle (action_load_playlist_handler_cb, NULL);
688 }
689
690 void
on_add_location_activate(GtkMenuItem * menuitem,gpointer user_data)691 on_add_location_activate (GtkMenuItem *menuitem,
692 gpointer user_data)
693 {
694 gdk_threads_add_idle (action_add_location_handler_cb, NULL);
695 }
696
697 static gboolean
update_win_title_idle(gpointer data)698 update_win_title_idle (gpointer data) {
699 struct fromto_t *ft = (struct fromto_t *)data;
700 DB_playItem_t *from = ft->from;
701 DB_playItem_t *to = ft->to;
702 free (ft);
703
704 // update window title
705 if (from || to) {
706 if (to) {
707 DB_playItem_t *it = deadbeef->streamer_get_playing_track ();
708 if (it) { // it might have been deleted after event was sent
709 gtkui_set_titlebar (it);
710 deadbeef->pl_item_unref (it);
711 }
712 else {
713 gtkui_set_titlebar (NULL);
714 }
715 }
716 else {
717 gtkui_set_titlebar (NULL);
718 }
719 }
720 if (from) {
721 deadbeef->pl_item_unref (from);
722 }
723 if (to) {
724 deadbeef->pl_item_unref (to);
725 }
726 return FALSE;
727 }
728
729 int
gtkui_add_new_playlist(void)730 gtkui_add_new_playlist (void) {
731 int cnt = deadbeef->plt_get_count ();
732 int i;
733 int idx = 0;
734 for (;;) {
735 char name[100];
736 if (!idx) {
737 strcpy (name, _("New Playlist"));
738 }
739 else {
740 snprintf (name, sizeof (name), _("New Playlist (%d)"), idx);
741 }
742 deadbeef->pl_lock ();
743 for (i = 0; i < cnt; i++) {
744 char t[100];
745 ddb_playlist_t *plt = deadbeef->plt_get_for_idx (i);
746 deadbeef->plt_get_title (plt, t, sizeof (t));
747 deadbeef->plt_unref (plt);
748 if (!strcasecmp (t, name)) {
749 break;
750 }
751 }
752 deadbeef->pl_unlock ();
753 if (i == cnt) {
754 return deadbeef->plt_add (cnt, name);
755 }
756 idx++;
757 }
758 return -1;
759 }
760
761 int
gtkui_get_gui_refresh_rate()762 gtkui_get_gui_refresh_rate () {
763 int fps = deadbeef->conf_get_int ("gtkui.refresh_rate", 10);
764 if (fps < 1) {
765 fps = 1;
766 }
767 else if (fps > 30) {
768 fps = 30;
769 }
770 return fps;
771 }
772
773 static void
send_messages_to_widgets(ddb_gtkui_widget_t * w,uint32_t id,uintptr_t ctx,uint32_t p1,uint32_t p2)774 send_messages_to_widgets (ddb_gtkui_widget_t *w, uint32_t id, uintptr_t ctx, uint32_t p1, uint32_t p2) {
775 for (ddb_gtkui_widget_t *c = w->children; c; c = c->next) {
776 send_messages_to_widgets (c, id, ctx, p1, p2);
777 }
778 if (w->message) {
779 w->message (w, id, ctx, p1, p2);
780 }
781 }
782
783 gboolean
add_mainmenu_actions_cb(void * data)784 add_mainmenu_actions_cb (void *data) {
785 add_mainmenu_actions ();
786 return FALSE;
787 }
788
789 int
790 gtkui_thread (void *ctx);
791
792 int
793 gtkui_plt_add_dir (ddb_playlist_t *plt, const char *dirname, int (*cb)(DB_playItem_t *it, void *data), void *user_data);
794
795 int
796 gtkui_plt_add_file (ddb_playlist_t *plt, const char *filename, int (*cb)(DB_playItem_t *it, void *data), void *user_data);
797
798 int
799 gtkui_pl_add_files_begin (ddb_playlist_t *plt);
800
801 void
802 gtkui_pl_add_files_end (void);
803
804 DB_playItem_t *
805 gtkui_plt_load (ddb_playlist_t *plt, DB_playItem_t *after, const char *fname, int *pabort, int (*cb)(DB_playItem_t *it, void *data), void *user_data);
806
807 int
gtkui_message(uint32_t id,uintptr_t ctx,uint32_t p1,uint32_t p2)808 gtkui_message (uint32_t id, uintptr_t ctx, uint32_t p1, uint32_t p2) {
809 if (!gtkui_accept_messages) {
810 return -1;
811 }
812 ddb_gtkui_widget_t *rootwidget = w_get_rootwidget ();
813 if (rootwidget) {
814 send_messages_to_widgets (rootwidget, id, ctx, p1, p2);
815 }
816
817 switch (id) {
818 case DB_EV_ACTIVATED:
819 g_idle_add (activate_cb, NULL);
820 break;
821 case DB_EV_SONGCHANGED:
822 {
823 ddb_event_trackchange_t *ev = (ddb_event_trackchange_t *)ctx;
824 gtkpl_songchanged_wrapper (ev->from, ev->to);
825 }
826 break;
827 case DB_EV_TRACKINFOCHANGED:
828 {
829 ddb_event_track_t *ev = (ddb_event_track_t *)ctx;
830 if (ev->track) {
831 deadbeef->pl_item_ref (ev->track);
832 }
833 g_idle_add (trackinfochanged_cb, ev->track);
834 }
835 break;
836 // case DB_EV_PAUSED:
837 // g_idle_add (paused_cb, NULL);
838 // break;
839 case DB_EV_PLAYLISTCHANGED:
840 if (p1 == DDB_PLAYLIST_CHANGE_CONTENT) {
841 g_idle_add (playlistcontentchanged_cb, NULL);
842 }
843 break;
844 case DB_EV_CONFIGCHANGED:
845 g_idle_add (gtkui_on_configchanged, NULL);
846 break;
847 case DB_EV_OUTPUTCHANGED:
848 g_idle_add (outputchanged_cb, NULL);
849 break;
850 case DB_EV_PLAYLISTSWITCHED:
851 g_idle_add (playlistswitch_cb, NULL);
852 break;
853 case DB_EV_ACTIONSCHANGED:
854 g_idle_add (add_mainmenu_actions_cb, NULL);
855 break;
856 case DB_EV_DSPCHAINCHANGED:
857 eq_refresh ();
858 break;
859 }
860 return 0;
861 }
862
863 static const char gtkui_def_layout[] = "vbox expand=\"0 1\" fill=\"1 1\" homogeneous=0 {hbox expand=\"0 1 0\" fill=\"1 1 1\" homogeneous=0 {playtb {} seekbar {} volumebar {} } tabbed_playlist hideheaders=0 {} } ";
864
865 static void
init_widget_layout(void)866 init_widget_layout (void) {
867 w_init ();
868 ddb_gtkui_widget_t *rootwidget = w_get_rootwidget ();
869 gtk_widget_show (rootwidget->widget);
870 gtk_box_pack_start (GTK_BOX(lookup_widget(mainwin, "plugins_bottom_vbox")), rootwidget->widget, TRUE, TRUE, 0);
871
872 // load layout
873 // config var name is defined in DDB_GTKUI_CONF_LAYOUT
874 // gtkui.layout: 0.6.0 and 0.6.1
875 // gtkui.layout.major.minor.point: later versions
876
877 char layout[20000];
878 deadbeef->conf_get_str (DDB_GTKUI_CONF_LAYOUT, "-", layout, sizeof (layout));
879 if (!strcmp (layout, "-")) {
880 // upgrade from 0.6.0 to 0.6.2
881 char layout_060[20000];
882 deadbeef->conf_get_str ("gtkui.layout", "-", layout_060, sizeof (layout_060));
883 if (!strcmp (layout_060, "-")) {
884 // new setup
885 strcpy (layout, gtkui_def_layout);
886 }
887 else {
888 // upgrade with top bar
889 snprintf (layout, sizeof (layout), "vbox expand=\"0 1\" fill=\"1 1\" homogeneous=0 {hbox expand=\"0 1 0\" fill=\"1 1 1\" homogeneous=0 {playtb {} seekbar {} volumebar {} } %s }", layout_060);
890 deadbeef->conf_set_str (DDB_GTKUI_CONF_LAYOUT, layout);
891 deadbeef->conf_save ();
892 }
893 }
894
895 ddb_gtkui_widget_t *w = NULL;
896 w_create_from_string (layout, &w);
897 if (!w) {
898 ddb_gtkui_widget_t *plt = w_create ("tabbed_playlist");
899 w_append (rootwidget, plt);
900 gtk_widget_show (plt->widget);
901 }
902 else {
903 w_append (rootwidget, w);
904 }
905 }
906
907 static DB_plugin_t *supereq_plugin;
908
909 gboolean
gtkui_connect_cb(void * none)910 gtkui_connect_cb (void *none) {
911 // equalizer
912 GtkWidget *eq_mi = lookup_widget (mainwin, "view_eq");
913 if (!supereq_plugin) {
914 gtk_widget_hide (GTK_WIDGET (eq_mi));
915 }
916 else {
917 if (deadbeef->conf_get_int ("gtkui.eq.visible", 0)) {
918 gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (eq_mi), TRUE);
919 eq_window_show ();
920 }
921 else {
922 gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (eq_mi), FALSE);
923 }
924 }
925
926 add_mainmenu_actions ();
927 ddb_event_t *e = deadbeef->event_alloc (DB_EV_TRACKINFOCHANGED);
928 deadbeef->event_send(e, 0, 0);
929 return FALSE;
930 }
931
932 int
gtkui_add_file_info_cb(ddb_fileadd_data_t * data,void * user_data)933 gtkui_add_file_info_cb (ddb_fileadd_data_t *data, void *user_data) {
934 if (data->visibility == 0) {
935 if (progress_is_aborted ()) {
936 return -1;
937 }
938 deadbeef->pl_lock ();
939 const char *fname = deadbeef->pl_find_meta (data->track, ":URI");
940 g_idle_add (gtkui_set_progress_text_idle, (gpointer)strdup(fname)); // slowwwww
941 deadbeef->pl_unlock ();
942 }
943 return 0;
944 }
945
946 void
gtkui_add_file_begin_cb(ddb_fileadd_data_t * data,void * user_data)947 gtkui_add_file_begin_cb (ddb_fileadd_data_t *data, void *user_data) {
948 if (data->visibility == 0) {
949 progress_show ();
950 }
951 }
952
953 void
gtkui_add_file_end_cb(ddb_fileadd_data_t * data,void * user_data)954 gtkui_add_file_end_cb (ddb_fileadd_data_t *data, void *user_data) {
955 if (data->visibility == 0) {
956 progress_hide ();
957 }
958 }
959
960 typedef struct {
961 void (*callback) (void *userdata);
962 void *userdata;
963 } window_init_hook_t;
964
965 #define WINDOW_INIT_HOOK_MAX 10
966 static window_init_hook_t window_init_hooks[WINDOW_INIT_HOOK_MAX];
967 static int window_init_hooks_count;
968
969 static void
add_window_init_hook(void (* callback)(void * userdata),void * userdata)970 add_window_init_hook (void (*callback) (void *userdata), void *userdata) {
971 if (window_init_hooks_count >= WINDOW_INIT_HOOK_MAX) {
972 fprintf (stderr, "gtkui: add_window_init_hook can't add another hook, maximum number of hooks (%d) exceeded\n", (int)WINDOW_INIT_HOOK_MAX);
973 return;
974 }
975
976 window_init_hooks[window_init_hooks_count].callback = callback;
977 window_init_hooks[window_init_hooks_count].userdata = userdata;
978 window_init_hooks_count++;
979 }
980
981 int
gtkui_thread(void * ctx)982 gtkui_thread (void *ctx) {
983 #ifdef __linux__
984 prctl (PR_SET_NAME, "deadbeef-gtkui", 0, 0, 0, 0);
985 #endif
986
987 int argc = 2;
988 const char **argv = alloca (sizeof (char *) * argc);
989 argv[0] = "deadbeef";
990 argv[1] = "--sync";
991 //argv[1] = "--g-fatal-warnings";
992 if (!deadbeef->conf_get_int ("gtkui.sync", 0)) {
993 argc = 1;
994 }
995
996 gtk_disable_setlocale ();
997 add_pixmap_directory (deadbeef->get_pixmap_dir ());
998
999 // let's start some gtk
1000 g_thread_init (NULL);
1001 gdk_threads_init ();
1002 gdk_threads_enter ();
1003
1004 gtk_init (&argc, (char ***)&argv);
1005
1006 // register widget types
1007 w_reg_widget (_("Playlist with tabs"), DDB_WF_SINGLE_INSTANCE, w_tabbed_playlist_create, "tabbed_playlist", NULL);
1008 w_reg_widget (_("Playlist"), DDB_WF_SINGLE_INSTANCE, w_playlist_create, "playlist", NULL);
1009 w_reg_widget (NULL, 0, w_box_create, "box", NULL);
1010 w_reg_widget (NULL, 0, w_dummy_create, "dummy", NULL);
1011 w_reg_widget (_("Splitter (top and bottom)"), 0, w_vsplitter_create, "vsplitter", NULL);
1012 w_reg_widget (_("Splitter (left and right)"), 0, w_hsplitter_create, "hsplitter", NULL);
1013 w_reg_widget (NULL, 0, w_placeholder_create, "placeholder", NULL);
1014 w_reg_widget (_("Tabs"), 0, w_tabs_create, "tabs", NULL);
1015 w_reg_widget (_("Playlist tabs"), 0, w_tabstrip_create, "tabstrip", NULL);
1016 w_reg_widget (_("Selection properties"), 0, w_selproperties_create, "selproperties", NULL);
1017 w_reg_widget (_("Album art display"), 0, w_coverart_create, "coverart", NULL);
1018 w_reg_widget (_("Scope"), 0, w_scope_create, "scope", NULL);
1019 w_reg_widget (_("Spectrum"), 0, w_spectrum_create, "spectrum", NULL);
1020 w_reg_widget (_("HBox"), 0, w_hbox_create, "hbox", NULL);
1021 w_reg_widget (_("VBox"), 0, w_vbox_create, "vbox", NULL);
1022 w_reg_widget (_("Button"), 0, w_button_create, "button", NULL);
1023 w_reg_widget (_("Seekbar"), 0, w_seekbar_create, "seekbar", NULL);
1024 w_reg_widget (_("Playback controls"), 0, w_playtb_create, "playtb", NULL);
1025 w_reg_widget (_("Volume bar"), 0, w_volumebar_create, "volumebar", NULL);
1026 w_reg_widget (_("Chiptune voices"), 0, w_ctvoices_create, "ctvoices", NULL);
1027
1028 mainwin = create_mainwin ();
1029
1030 // initialize default hotkey mapping
1031 if (!deadbeef->conf_get_int ("hotkeys_created", 0)) {
1032 // check if any hotkeys were created manually (e.g. beta versions of 0.6)
1033 if (!deadbeef->conf_find ("hotkey.key", NULL)) {
1034 gtkui_set_default_hotkeys ();
1035 gtkui_import_0_5_global_hotkeys ();
1036 DB_plugin_t *hkplug = deadbeef->plug_get_for_id ("hotkeys");
1037 if (hkplug) {
1038 ((DB_hotkeys_plugin_t *)hkplug)->reset ();
1039 }
1040 }
1041 deadbeef->conf_set_int ("hotkeys_created", 1);
1042 deadbeef->conf_save ();
1043 }
1044 #if GTK_CHECK_VERSION(3,0,0)
1045 gtk_widget_set_events (GTK_WIDGET (mainwin), gtk_widget_get_events (GTK_WIDGET (mainwin)) | GDK_SCROLL_MASK);
1046 #endif
1047
1048 pl_common_init();
1049
1050 GtkIconTheme *theme = gtk_icon_theme_get_default();
1051 if (gtk_icon_theme_has_icon(theme, "deadbeef")) {
1052 gtk_window_set_icon_name (GTK_WINDOW (mainwin), "deadbeef");
1053 }
1054 else {
1055 // try loading icon from $prefix/deadbeef.png (for static build)
1056 char iconpath[1024];
1057 snprintf (iconpath, sizeof (iconpath), "%s/deadbeef.png", deadbeef->get_prefix ());
1058 gtk_window_set_icon_from_file (GTK_WINDOW (mainwin), iconpath, NULL);
1059 }
1060
1061 wingeom_restore (mainwin, "mainwin", 40, 40, 500, 300, 0);
1062
1063 gtkui_on_configchanged (NULL);
1064 gtkui_init_theme_colors ();
1065
1066 // visibility of statusbar and headers
1067 GtkWidget *sb_mi = lookup_widget (mainwin, "view_status_bar");
1068 GtkWidget *sb = lookup_widget (mainwin, "statusbar");
1069 if (deadbeef->conf_get_int ("gtkui.statusbar.visible", 1)) {
1070 gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (sb_mi), TRUE);
1071 }
1072 else {
1073 gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (sb_mi), FALSE);
1074 gtk_widget_hide (sb);
1075 }
1076
1077 GtkWidget *menu = lookup_widget (mainwin, "menubar");
1078 if (deadbeef->conf_get_int ("gtkui.show_menu", 1)) {
1079 gtk_widget_show (menu);
1080 }
1081 else {
1082 gtk_widget_hide (menu);
1083 }
1084
1085 searchwin = create_searchwin ();
1086 gtk_window_set_transient_for (GTK_WINDOW (searchwin), GTK_WINDOW (mainwin));
1087
1088 DdbListview *search_playlist = DDB_LISTVIEW (lookup_widget (searchwin, "searchlist"));
1089 search_playlist_init (GTK_WIDGET (search_playlist));
1090
1091 progress_init ();
1092 cover_art_init ();
1093
1094 #ifdef __APPLE__
1095 #if 0
1096 GtkWidget *menubar = lookup_widget (mainwin, "menubar");
1097 gtk_widget_hide (menubar);
1098 GtkosxApplication *theApp = g_object_new(GTKOSX_TYPE_APPLICATION, NULL);
1099 gtkosx_application_set_menu_bar(theApp, GTK_MENU_SHELL(menubar));
1100 #endif
1101 #endif
1102
1103 for (int i = 0; i < window_init_hooks_count; i++) {
1104 window_init_hooks[i].callback (window_init_hooks[i].userdata);
1105 }
1106 gtk_widget_show (mainwin);
1107
1108 init_widget_layout ();
1109
1110 gtkui_set_titlebar (NULL);
1111
1112 fileadded_listener_id = deadbeef->listen_file_added (gtkui_add_file_info_cb, NULL);
1113 fileadd_beginend_listener_id = deadbeef->listen_file_add_beginend (gtkui_add_file_begin_cb, gtkui_add_file_end_cb, NULL);
1114
1115 supereq_plugin = deadbeef->plug_get_for_id ("supereq");
1116
1117 gtkui_connect_cb (NULL);
1118
1119 gtkui_accept_messages = 1;
1120 deadbeef->sendmessage (DB_EV_PLAYLISTCHANGED, 0, DDB_PLAYLIST_CHANGE_CONTENT, 0);
1121
1122 #ifdef __APPLE__
1123 gtkui_is_retina = is_retina (mainwin);
1124 #endif
1125
1126 gtk_main ();
1127
1128 deadbeef->unlisten_file_added (fileadded_listener_id);
1129 deadbeef->unlisten_file_add_beginend (fileadd_beginend_listener_id);
1130
1131 w_free ();
1132
1133 if (refresh_timeout) {
1134 g_source_remove (refresh_timeout);
1135 refresh_timeout = 0;
1136 }
1137 cover_art_free ();
1138 eq_window_destroy ();
1139 trkproperties_destroy ();
1140 progress_destroy ();
1141 gtkui_hide_status_icon ();
1142 pl_common_free();
1143 // draw_free ();
1144 titlebar_tf_free ();
1145 if (mainwin) {
1146 gtk_widget_destroy (mainwin);
1147 mainwin = NULL;
1148 }
1149 if (searchwin) {
1150 gtk_widget_destroy (searchwin);
1151 searchwin = NULL;
1152 }
1153 gdk_threads_leave ();
1154 return 0;
1155 }
1156
1157 gboolean
gtkui_set_progress_text_idle(gpointer data)1158 gtkui_set_progress_text_idle (gpointer data) {
1159 char *text = (char *)data;
1160 if (text) {
1161 progress_settext (text);
1162 free (text);
1163 }
1164 return FALSE;
1165 }
1166
1167 void
gtkui_playlist_set_curr(int playlist)1168 gtkui_playlist_set_curr (int playlist) {
1169 deadbeef->plt_set_curr_idx (playlist);
1170 deadbeef->conf_set_int ("playlist.current", playlist);
1171 }
1172
1173 void
on_gtkui_info_window_delete(GtkWidget * widget,GtkTextDirection previous_direction,GtkWidget ** pwindow)1174 on_gtkui_info_window_delete (GtkWidget *widget, GtkTextDirection previous_direction, GtkWidget **pwindow) {
1175 *pwindow = NULL;
1176 gtk_widget_hide (widget);
1177 gtk_widget_destroy (widget);
1178 }
1179
1180 void
gtkui_show_info_window(const char * fname,const char * title,GtkWidget ** pwindow)1181 gtkui_show_info_window (const char *fname, const char *title, GtkWidget **pwindow) {
1182 if (*pwindow) {
1183 return;
1184 }
1185 GtkWidget *widget = *pwindow = create_helpwindow ();
1186 g_object_set_data (G_OBJECT (widget), "pointer", pwindow);
1187 g_signal_connect (widget, "delete_event", G_CALLBACK (on_gtkui_info_window_delete), pwindow);
1188 gtk_window_set_title (GTK_WINDOW (widget), title);
1189 gtk_window_set_transient_for (GTK_WINDOW (widget), GTK_WINDOW (mainwin));
1190 GtkWidget *txt = lookup_widget (widget, "helptext");
1191 GtkTextBuffer *buffer = gtk_text_buffer_new (NULL);
1192
1193 FILE *fp = fopen (fname, "rb");
1194 if (fp) {
1195 fseek (fp, 0, SEEK_END);
1196 size_t s = ftell (fp);
1197 rewind (fp);
1198 char buf[s+1];
1199 if (fread (buf, 1, s, fp) != s) {
1200 fprintf (stderr, "error reading help file contents\n");
1201 const char *error = _("Failed while reading help file");
1202 gtk_text_buffer_set_text (buffer, error, strlen (error));
1203 }
1204 else {
1205 buf[s] = 0;
1206 gtk_text_buffer_set_text (buffer, buf, s);
1207 }
1208 fclose (fp);
1209 }
1210 else {
1211 const char *error = _("Failed to load help file");
1212 gtk_text_buffer_set_text (buffer, error, strlen (error));
1213 }
1214 gtk_text_view_set_buffer (GTK_TEXT_VIEW (txt), buffer);
1215 g_object_unref (buffer);
1216 gtk_widget_show (widget);
1217 }
1218
1219 gboolean
gtkui_quit_cb(void * ctx)1220 gtkui_quit_cb (void *ctx) {
1221 w_save ();
1222
1223 if (deadbeef->have_background_jobs ()) {
1224 GtkWidget *dlg = gtk_message_dialog_new (GTK_WINDOW (mainwin), GTK_DIALOG_MODAL, GTK_MESSAGE_WARNING, GTK_BUTTONS_YES_NO, _("The player is currently running background tasks. If you quit now, the tasks will be cancelled or interrupted. This may result in data loss."));
1225 gtk_window_set_transient_for (GTK_WINDOW (dlg), GTK_WINDOW (mainwin));
1226 gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dlg), _("Do you still want to quit?"));
1227 gtk_window_set_title (GTK_WINDOW (dlg), _("Warning"));
1228
1229 int response = gtk_dialog_run (GTK_DIALOG (dlg));
1230 gtk_widget_destroy (dlg);
1231 if (response != GTK_RESPONSE_YES) {
1232 return FALSE;
1233 }
1234 else {
1235 exit (0);
1236 }
1237 }
1238 else {
1239 progress_abort ();
1240 deadbeef->sendmessage (DB_EV_TERMINATE, 0, 0, 0);
1241 }
1242 return FALSE;
1243 }
1244
1245 void
gtkui_quit(void)1246 gtkui_quit (void) {
1247 gdk_threads_add_idle (gtkui_quit_cb, NULL);
1248 }
1249
1250 static void
import_legacy_tf(const char * key_from,const char * key_to)1251 import_legacy_tf (const char *key_from, const char *key_to) {
1252 deadbeef->conf_lock ();
1253 if (!deadbeef->conf_get_str_fast (key_to, NULL)
1254 && deadbeef->conf_get_str_fast (key_from, NULL)) {
1255 char old[200], new[200];
1256 deadbeef->conf_get_str (key_from, "", old, sizeof (old));
1257 deadbeef->tf_import_legacy (old, new, sizeof (new));
1258 deadbeef->conf_set_str (key_to, new);
1259 deadbeef->conf_save ();
1260 }
1261 deadbeef->conf_unlock ();
1262 }
1263
1264 static int
gtkui_start(void)1265 gtkui_start (void) {
1266 fprintf (stderr, "gtkui plugin compiled for gtk version: %d.%d.%d\n", GTK_MAJOR_VERSION, GTK_MINOR_VERSION, GTK_MICRO_VERSION);
1267
1268 import_legacy_tf ("gtkui.titlebar_playing", "gtkui.titlebar_playing_tf");
1269 import_legacy_tf ("gtkui.titlebar_stopped", "gtkui.titlebar_stopped_tf");
1270
1271 import_legacy_tf ("playlist.group_by", "gtkui.playlist.group_by_tf");
1272
1273 gtkui_thread (NULL);
1274
1275 return 0;
1276 }
1277
1278 static int
gtkui_connect(void)1279 gtkui_connect (void) {
1280 return 0;
1281 }
1282
1283 static int
gtkui_disconnect(void)1284 gtkui_disconnect (void) {
1285 supereq_plugin = NULL;
1286 return 0;
1287 }
1288
1289
1290 static gboolean
quit_gtk_cb(gpointer nothing)1291 quit_gtk_cb (gpointer nothing) {
1292 extern int trkproperties_modified;
1293 trkproperties_modified = 0;
1294 trkproperties_destroy ();
1295 search_destroy ();
1296 gtk_main_quit ();
1297 trace ("gtkui_stop completed\n");
1298 return FALSE;
1299 }
1300
1301 static int
gtkui_stop(void)1302 gtkui_stop (void) {
1303 trace ("quitting gtk\n");
1304 cover_art_disconnect();
1305 g_idle_add (quit_gtk_cb, NULL);
1306 return 0;
1307 }
1308
1309 GtkWidget *
gtkui_get_mainwin(void)1310 gtkui_get_mainwin (void) {
1311 return mainwin;
1312 }
1313
1314 static DB_plugin_action_t action_deselect_all = {
1315 .title = "Edit/Deselect All",
1316 .name = "deselect_all",
1317 .flags = DB_ACTION_COMMON,
1318 .callback2 = action_deselect_all_handler,
1319 .next = NULL
1320 };
1321
1322 static DB_plugin_action_t action_select_all = {
1323 .title = "Edit/Select All",
1324 .name = "select_all",
1325 .flags = DB_ACTION_COMMON,
1326 .callback2 = action_select_all_handler,
1327 .next = &action_deselect_all
1328 };
1329
1330 static DB_plugin_action_t action_quit = {
1331 .title = "Quit",
1332 .name = "quit",
1333 .flags = DB_ACTION_COMMON,
1334 .callback2 = action_quit_handler,
1335 .next = &action_select_all
1336 };
1337
1338 static DB_plugin_action_t action_delete_from_disk = {
1339 .title = "Remove From Disk",
1340 .name = "delete_from_disk",
1341 .flags = DB_ACTION_MULTIPLE_TRACKS,
1342 .callback2 = action_delete_from_disk_handler,
1343 .next = &action_quit
1344 };
1345
1346 static DB_plugin_action_t action_add_location = {
1347 .title = "File/Add Location",
1348 .name = "add_location",
1349 .flags = DB_ACTION_COMMON,
1350 .callback2 = action_add_location_handler,
1351 .next = &action_delete_from_disk
1352 };
1353
1354 static DB_plugin_action_t action_add_folders = {
1355 .title = "File/Add Folder(s)",
1356 .name = "add_folders",
1357 .flags = DB_ACTION_COMMON,
1358 .callback2 = action_add_folders_handler,
1359 .next = &action_add_location
1360 };
1361
1362 static DB_plugin_action_t action_add_files = {
1363 .title = "File/Add File(s)",
1364 .name = "add_files",
1365 .flags = DB_ACTION_COMMON,
1366 .callback2 = action_add_files_handler,
1367 .next = &action_add_folders
1368 };
1369
1370 static DB_plugin_action_t action_open_files = {
1371 .title = "File/Open File(s)",
1372 .name = "open_files",
1373 .flags = DB_ACTION_COMMON,
1374 .callback2 = action_open_files_handler,
1375 .next = &action_add_files
1376 };
1377
1378
1379 static DB_plugin_action_t action_track_properties = {
1380 .title = "Track Properties",
1381 .name = "track_properties",
1382 .flags = DB_ACTION_MULTIPLE_TRACKS,
1383 .callback2 = action_show_track_properties_handler,
1384 .next = &action_open_files
1385 };
1386
1387 static DB_plugin_action_t action_show_help = {
1388 .title = "Help/Show Help Page",
1389 .name = "help",
1390 .flags = DB_ACTION_COMMON,
1391 .callback2 = action_show_help_handler,
1392 .next = &action_track_properties
1393 };
1394
1395 static DB_plugin_action_t action_playback_loop_cycle = {
1396 .title = "Playback/Cycle Playback Looping Mode",
1397 .name = "loop_cycle",
1398 .flags = DB_ACTION_COMMON,
1399 .callback2 = action_playback_loop_cycle_handler,
1400 .next = &action_show_help
1401 };
1402
1403 static DB_plugin_action_t action_playback_loop_off = {
1404 .title = "Playback/Playback Looping - Don't loop",
1405 .name = "loop_off",
1406 .flags = DB_ACTION_COMMON,
1407 .callback2 = action_playback_loop_off_handler,
1408 .next = &action_playback_loop_cycle
1409 };
1410
1411 static DB_plugin_action_t action_playback_loop_single = {
1412 .title = "Playback/Playback Looping - Single track",
1413 .name = "loop_track",
1414 .flags = DB_ACTION_COMMON,
1415 .callback2 = action_playback_loop_single_handler,
1416 .next = &action_playback_loop_off
1417 };
1418
1419 static DB_plugin_action_t action_playback_loop_all = {
1420 .title = "Playback/Playback Looping - All",
1421 .name = "loop_all",
1422 .flags = DB_ACTION_COMMON,
1423 .callback2 = action_playback_loop_all_handler,
1424 .next = &action_playback_loop_single
1425 };
1426
1427 static DB_plugin_action_t action_playback_order_cycle = {
1428 .title = "Playback/Cycle Playback Order",
1429 .name = "order_cycle",
1430 .flags = DB_ACTION_COMMON,
1431 .callback2 = action_playback_order_cycle_handler,
1432 .next = &action_playback_loop_all
1433 };
1434
1435 static DB_plugin_action_t action_playback_order_random = {
1436 .title = "Playback/Playback Order - Random",
1437 .name = "order_random",
1438 .flags = DB_ACTION_COMMON,
1439 .callback2 = action_playback_order_random_handler,
1440 .next = &action_playback_order_cycle
1441 };
1442
1443 static DB_plugin_action_t action_playback_order_shuffle_albums = {
1444 .title = "Playback/Playback Order - Shuffle albums",
1445 .name = "order_shuffle_albums",
1446 .flags = DB_ACTION_COMMON,
1447 .callback2 = action_playback_order_shuffle_albums_handler,
1448 .next = &action_playback_order_random
1449 };
1450
1451 static DB_plugin_action_t action_playback_order_shuffle = {
1452 .title = "Playback/Playback Order - Shuffle tracks",
1453 .name = "order_shuffle",
1454 .flags = DB_ACTION_COMMON,
1455 .callback2 = action_playback_order_shuffle_handler,
1456 .next = &action_playback_order_shuffle_albums
1457 };
1458
1459 static DB_plugin_action_t action_playback_order_linear = {
1460 .title = "Playback/Playback Order - Linear",
1461 .name = "order_linear",
1462 .flags = DB_ACTION_COMMON,
1463 .callback2 = action_playback_order_linear_handler,
1464 .next = &action_playback_order_shuffle
1465 };
1466
1467
1468 static DB_plugin_action_t action_cursor_follows_playback = {
1469 .title = "Playback/Toggle Cursor Follows Playback",
1470 .name = "toggle_cursor_follows_playback",
1471 .flags = DB_ACTION_COMMON,
1472 .callback2 = action_cursor_follows_playback_handler,
1473 .next = &action_playback_order_linear
1474 };
1475
1476
1477 static DB_plugin_action_t action_scroll_follows_playback = {
1478 .title = "Playback/Toggle Scroll Follows Playback",
1479 .name = "toggle_scroll_follows_playback",
1480 .flags = DB_ACTION_COMMON,
1481 .callback2 = action_scroll_follows_playback_handler,
1482 .next = &action_cursor_follows_playback
1483 };
1484
1485 static DB_plugin_action_t action_toggle_menu = {
1486 .title = "View/Show\\/Hide menu",
1487 .name = "toggle_menu",
1488 .flags = DB_ACTION_COMMON,
1489 .callback2 = action_toggle_menu_handler,
1490 .next = &action_scroll_follows_playback
1491 };
1492
1493 static DB_plugin_action_t action_toggle_statusbar = {
1494 .title = "View/Show\\/Hide statusbar",
1495 .name = "toggle_statusbar",
1496 .flags = DB_ACTION_COMMON,
1497 .callback2 = action_toggle_statusbar_handler,
1498 .next = &action_toggle_menu
1499 };
1500
1501 static DB_plugin_action_t action_toggle_designmode = {
1502 .title = "Edit/Toggle Design Mode",
1503 .name = "toggle_design_mode",
1504 .flags = DB_ACTION_COMMON,
1505 .callback2 = action_toggle_designmode_handler,
1506 .next = &action_toggle_statusbar
1507 };
1508
1509 static DB_plugin_action_t action_preferences = {
1510 .title = "Edit/Preferences",
1511 .name = "preferences",
1512 .flags = DB_ACTION_COMMON,
1513 .callback2 = action_preferences_handler,
1514 .next = &action_toggle_designmode
1515 };
1516
1517 static DB_plugin_action_t action_sort_custom = {
1518 .title = "Edit/Sort Custom",
1519 .name = "sort_custom",
1520 .flags = DB_ACTION_COMMON,
1521 .callback2 = action_sort_custom_handler,
1522 .next = &action_preferences
1523 };
1524
1525 static DB_plugin_action_t action_crop_selected = {
1526 .title = "Edit/Crop Selected",
1527 .name = "crop_selected",
1528 .flags = DB_ACTION_COMMON,
1529 .callback2 = action_crop_selected_handler,
1530 .next = &action_sort_custom
1531 };
1532
1533 static DB_plugin_action_t action_remove_from_playlist = {
1534 .title = "Edit/Remove Track(s) From Playlist",
1535 .name = "remove_from_playlist",
1536 .flags = DB_ACTION_MULTIPLE_TRACKS,
1537 .callback2 = action_remove_from_playlist_handler,
1538 .next = &action_crop_selected
1539 };
1540
1541 static DB_plugin_action_t action_save_playlist = {
1542 .title = "File/Save Playlist",
1543 .name = "save_playlist",
1544 .flags = DB_ACTION_COMMON,
1545 .callback2 = action_save_playlist_handler,
1546 .next = &action_remove_from_playlist
1547 };
1548
1549 static DB_plugin_action_t action_load_playlist = {
1550 .title = "File/Load Playlist",
1551 .name = "load_playlist",
1552 .flags = DB_ACTION_COMMON,
1553 .callback2 = action_load_playlist_handler,
1554 .next = &action_save_playlist
1555 };
1556
1557 static DB_plugin_action_t action_remove_current_playlist = {
1558 .title = "File/Remove Current Playlist",
1559 .name = "remove_current_playlist",
1560 .flags = DB_ACTION_COMMON,
1561 .callback2 = action_remove_current_playlist_handler,
1562 .next = &action_load_playlist
1563 };
1564
1565
1566 static DB_plugin_action_t action_new_playlist = {
1567 .title = "File/New Playlist",
1568 .name = "new_playlist",
1569 .flags = DB_ACTION_COMMON,
1570 .callback2 = action_new_playlist_handler,
1571 .next = &action_remove_current_playlist
1572 };
1573
1574 static DB_plugin_action_t action_toggle_eq = {
1575 .title = "View/Show\\/Hide Equalizer",
1576 .name = "toggle_eq",
1577 .flags = DB_ACTION_COMMON,
1578 .callback2 = action_toggle_eq_handler,
1579 .next = &action_new_playlist
1580 };
1581
1582 static DB_plugin_action_t action_hide_eq = {
1583 .title = "View/Hide Equalizer",
1584 .name = "hide_eq",
1585 .flags = DB_ACTION_COMMON,
1586 .callback2 = action_hide_eq_handler,
1587 .next = &action_toggle_eq
1588 };
1589
1590 static DB_plugin_action_t action_show_eq = {
1591 .title = "View/Show Equalizer",
1592 .name = "show_eq",
1593 .flags = DB_ACTION_COMMON,
1594 .callback2 = action_show_eq_handler,
1595 .next = &action_hide_eq
1596 };
1597
1598 static DB_plugin_action_t action_toggle_mainwin = {
1599 .title = "View/Show\\/Hide Player Window",
1600 .name = "toggle_player_window",
1601 .flags = DB_ACTION_COMMON,
1602 .callback2 = action_toggle_mainwin_handler,
1603 .next = &action_show_eq
1604 };
1605
1606 static DB_plugin_action_t action_hide_mainwin = {
1607 .title = "View/Hide Player Window",
1608 .name = "hide_player_window",
1609 .flags = DB_ACTION_COMMON,
1610 .callback2 = action_hide_mainwin_handler,
1611 .next = &action_toggle_mainwin
1612 };
1613
1614 static DB_plugin_action_t action_show_mainwin = {
1615 .title = "View/Show Player Window",
1616 .name = "show_player_window",
1617 .flags = DB_ACTION_COMMON,
1618 .callback2 = action_show_mainwin_handler,
1619 .next = &action_hide_mainwin
1620 };
1621
1622 static DB_plugin_action_t action_find = {
1623 .title = "Edit/Find",
1624 .name = "find",
1625 .flags = DB_ACTION_COMMON,
1626 .callback2 = action_find_handler,
1627 .next = &action_show_mainwin
1628 };
1629
1630 static DB_plugin_action_t *
gtkui_get_actions(DB_playItem_t * it)1631 gtkui_get_actions (DB_playItem_t *it)
1632 {
1633 return &action_find;
1634 }
1635
1636 #if !GTK_CHECK_VERSION(3,0,0)
1637 DB_plugin_t *
ddb_gui_GTK2_load(DB_functions_t * api)1638 ddb_gui_GTK2_load (DB_functions_t *api) {
1639 deadbeef = api;
1640 return DB_PLUGIN (&plugin);
1641 }
1642 #else
1643 DB_plugin_t *
ddb_gui_GTK3_load(DB_functions_t * api)1644 ddb_gui_GTK3_load (DB_functions_t *api) {
1645 deadbeef = api;
1646 return DB_PLUGIN (&plugin);
1647 }
1648 #endif
1649
1650 static const char settings_dlg[] =
1651 "property \"Ask confirmation to delete files from disk\" checkbox gtkui.delete_files_ask 1;\n"
1652 "property \"Status icon volume control sensitivity\" entry gtkui.tray_volume_sensitivity 1;\n"
1653 "property \"Custom status icon\" entry gtkui.custom_tray_icon \"" TRAY_ICON "\" ;\n"
1654 "property \"Run gtk_init with --sync (debug mode)\" checkbox gtkui.sync 0;\n"
1655 "property \"Add separators between plugin context menu items\" checkbox gtkui.action_separators 0;\n"
1656 "property \"Use unicode chars instead of images for track state\" checkbox gtkui.unicode_playstate 0;\n"
1657 "property \"Disable seekbar overlay text\" checkbox gtkui.disable_seekbar_overlay 0;\n"
1658 ;
1659
1660 // define plugin interface
1661 static ddb_gtkui_t plugin = {
1662 .gui.plugin.api_vmajor = 1,
1663 .gui.plugin.api_vminor = 6,
1664 .gui.plugin.version_major = DDB_GTKUI_API_VERSION_MAJOR,
1665 .gui.plugin.version_minor = DDB_GTKUI_API_VERSION_MINOR,
1666 .gui.plugin.type = DB_PLUGIN_GUI,
1667 .gui.plugin.id = DDB_GTKUI_PLUGIN_ID,
1668 #if GTK_CHECK_VERSION(3,0,0)
1669 .gui.plugin.name = "GTK3 user interface",
1670 .gui.plugin.descr = "User interface using GTK+ 3.x",
1671 #else
1672 .gui.plugin.name = "GTK2 user interface",
1673 .gui.plugin.descr = "User interface using GTK+ 2.x",
1674 #endif
1675 .gui.plugin.copyright =
1676 "GTK+ user interface for DeaDBeeF Player.\n"
1677 "Copyright (C) 2009-2015 Alexey Yakovenko and other contributors\n"
1678 "\n"
1679 "This software is provided 'as-is', without any express or implied\n"
1680 "warranty. In no event will the authors be held liable for any damages\n"
1681 "arising from the use of this software.\n"
1682 "\n"
1683 "Permission is granted to anyone to use this software for any purpose,\n"
1684 "including commercial applications, and to alter it and redistribute it\n"
1685 "freely, subject to the following restrictions:\n"
1686 "\n"
1687 "1. The origin of this software must not be misrepresented; you must not\n"
1688 " claim that you wrote the original software. If you use this software\n"
1689 " in a product, an acknowledgment in the product documentation would be\n"
1690 " appreciated but is not required.\n"
1691 "\n"
1692 "2. Altered source versions must be plainly marked as such, and must not be\n"
1693 " misrepresented as being the original software.\n"
1694 "\n"
1695 "3. This notice may not be removed or altered from any source distribution.\n"
1696 "\n"
1697 "\n"
1698 "GTK - The GIMP Toolkit\n"
1699 "Copyright (C) GTK Developers\n"
1700 "\n"
1701 "This library is free software; you can redistribute it and/or\n"
1702 "modify it under the terms of the GNU Lesser General Public\n"
1703 "License as published by the Free Software Foundation; either\n"
1704 "version 2 of the License, or (at your option) any later version.\n"
1705 "\n"
1706 "This library is distributed in the hope that it will be useful,\n"
1707 "but WITHOUT ANY WARRANTY; without even the implied warranty of\n"
1708 "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU\n"
1709 "Lesser General Public License for more details.\n"
1710 "\n"
1711 "You should have received a copy of the GNU Lesser General Public\n"
1712 "License along with this library. If not, see <http://www.gnu.org/licenses/>.\n"
1713 ,
1714 .gui.plugin.website = "http://deadbeef.sf.net",
1715 .gui.plugin.start = gtkui_start,
1716 .gui.plugin.stop = gtkui_stop,
1717 .gui.plugin.connect = gtkui_connect,
1718 .gui.plugin.disconnect = gtkui_disconnect,
1719 .gui.plugin.configdialog = settings_dlg,
1720 .gui.plugin.message = gtkui_message,
1721 .gui.run_dialog = gtkui_run_dialog_root,
1722 .gui.plugin.get_actions = gtkui_get_actions,
1723 .get_mainwin = gtkui_get_mainwin,
1724 .w_reg_widget = w_reg_widget,
1725 .w_unreg_widget = w_unreg_widget,
1726 .w_override_signals = w_override_signals,
1727 .w_is_registered = w_is_registered,
1728 .w_get_rootwidget = w_get_rootwidget,
1729 .w_set_design_mode = w_set_design_mode,
1730 .w_get_design_mode = w_get_design_mode,
1731 .w_create = w_create,
1732 .w_destroy = w_destroy,
1733 .w_append = w_append,
1734 .w_replace = w_replace,
1735 .w_remove = w_remove,
1736 .create_pltmenu = gtkui_create_pltmenu,
1737 .get_cover_art_pixbuf = get_cover_art_callb, // deprecated
1738 .get_cover_art_primary = get_cover_art_primary,
1739 .get_cover_art_thumb = get_cover_art_thumb,
1740 .cover_get_default_pixbuf = cover_get_default_pixbuf,
1741 .add_window_init_hook = add_window_init_hook,
1742 };
1743