1 /* wmauda - Dockapp for controlling Audacious
3 * Copyright (C) 2006 Michael Stewart <michael@alteredeclipse.org>
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.
22 #include <X11/Xatom.h>
29 #include <audacious/dbus.h>
30 #include <audacious/audctrl.h>
32 #include "dock-master.xpm"
41 # define PIXMAP_DIR "/usr/local/share/pixmaps"
46 int x
, y
, width
, height
, pressed_x
, pressed_y
, normal_x
, normal_y
;
47 gboolean focus
, pressed
;
48 void (*callback
) (void);
52 void action_play(void);
53 void action_pause(void);
54 void action_eject(void);
55 void action_prev(void);
56 void action_next(void);
57 void action_stop(void);
61 {21, 32, 9, 11, 84, 0, 64, 0, FALSE
, FALSE
, action_play
}, /* PLAY */
62 {34, 32, 9, 11, 94, 0, 74, 0, FALSE
, FALSE
, action_pause
}, /* PAUSE */
63 {47, 32, 9, 11, 84, 11, 64, 11, FALSE
, FALSE
, action_eject
}, /* EJECT */
64 {21, 46, 9, 11, 84, 22, 64, 22, FALSE
, FALSE
, action_prev
}, /* PREV */
65 {34, 46, 9, 11, 94, 22, 74, 22, FALSE
, FALSE
, action_next
}, /* NEXT */
66 {47, 46, 9, 11, 94, 11, 74, 11, FALSE
, FALSE
, action_stop
}, /* STOP */
75 unsigned char ascii
; gint x
, y
;
78 Charentry chartable
[] =
80 { '-', 60, 73}, /* put here coordinates of characters */
81 { '.', 72, 73}, /* in xmms-dock-master.xpm */
89 { 'ä', 114, 73}, /* toupper doesn't convert umlauts */
102 #define VOLSLIDER_X 8
103 #define VOLSLIDER_Y 17
104 #define VOLSLIDER_WIDTH 7
107 #define VOLSLIDER_HEIGHT 40
109 #define SEEKSLIDER_X 21
110 #define SEEKSLIDER_Y 20
111 #define SEEKSLIDER_WIDTH 30
112 #define SEEKSLIDER_HEIGHT 7
113 #define SEEKSLIDER_KNOB_WIDTH 3
114 #define SEEKSLIDER_MAX (SEEKSLIDER_WIDTH - SEEKSLIDER_KNOB_WIDTH)
116 #define SCROLLTEXT_X 5
117 #define SCROLLTEXT_Y 6
118 #define SCROLLTEXT_WIDTH 40
119 #define SCROLLTEXT_HEIGHT 9
120 #define SCROLLTEXT_CHARS 9
122 gboolean volslider_dragging
= FALSE
;
123 int volslider_pos
= 0;
124 gboolean seekslider_visible
= FALSE
, seekslider_dragging
= FALSE
;
125 int seekslider_pos
= -1, seekslider_drag_offset
= 0;
132 GdkPixmap
*pixmap
, *launch_pixmap
;
133 GdkBitmap
*mask
, *launch_mask
;
135 GtkTooltips
*tooltips
= NULL
;
137 char *xmms_cmd
= "audacious";
138 gboolean xmms_running
= FALSE
;
140 gboolean has_geometry
= FALSE
, single_click
= FALSE
, song_title
= FALSE
;
141 char *icon_name
= NULL
;
144 DBusGProxy
*dbus_proxy
= NULL
;
145 static DBusGConnection
*connection
= NULL
;
147 GtkTargetEntry drop_types
[] =
152 void action_play(void)
154 audacious_remote_play(dbus_proxy
);
157 void action_pause(void)
159 audacious_remote_pause(dbus_proxy
);
162 void action_eject(void)
164 audacious_remote_playlist_clear(dbus_proxy
);
165 audacious_remote_stop(dbus_proxy
);
168 void action_prev(void)
170 audacious_remote_playlist_prev(dbus_proxy
);
173 void action_next(void)
175 audacious_remote_playlist_next(dbus_proxy
);
178 void action_stop(void)
180 audacious_remote_stop(dbus_proxy
);
183 gboolean
inside_region(int mx
, int my
, int x
, int y
, int w
, int h
)
185 if ((mx
>= x
&& mx
< x
+ w
) && (my
>= y
&& my
< y
+ h
))
190 void real_draw_button(GdkWindow
*w
, Button
*button
)
194 gdk_draw_pixmap(w
, dock_gc
, pixmap
,
195 button
->pressed_x
, button
->pressed_y
,
196 button
->x
, button
->y
,
197 button
->width
, button
->height
);
199 gdk_draw_pixmap(w
, dock_gc
, pixmap
,
200 button
->normal_x
, button
->normal_y
,
201 button
->x
, button
->y
,
202 button
->width
, button
->height
);
205 void draw_button(Button
*button
)
207 real_draw_button(icon_win
->window
, button
);
210 void draw_buttons(GList
*list
)
212 for (; list
; list
= g_list_next(list
))
213 draw_button(list
->data
);
216 void real_draw_volslider(GdkWindow
*w
)
218 gdk_draw_pixmap(w
, dock_gc
, pixmap
, 112, 1, VOLSLIDER_X
, VOLSLIDER_Y
,
219 VOLSLIDER_WIDTH
, VOLSLIDER_HEIGHT
);
220 gdk_draw_pixmap(w
, dock_gc
, pixmap
, 106,
221 1 + VOLSLIDER_HEIGHT
- volslider_pos
,
223 VOLSLIDER_Y
+ VOLSLIDER_HEIGHT
- volslider_pos
,
224 VOLSLIDER_WIDTH
, volslider_pos
);
227 void draw_volslider(void)
229 real_draw_volslider(icon_win
->window
);
232 void real_draw_seekslider(GdkWindow
*w
)
236 if (seekslider_visible
)
238 gdk_draw_pixmap(w
, dock_gc
, pixmap
, 66, 54,
239 SEEKSLIDER_X
, SEEKSLIDER_Y
, 35, 10);
240 if (seekslider_pos
< SEEKSLIDER_MAX
/ 3)
242 else if (seekslider_pos
< (SEEKSLIDER_MAX
* 2) / 3)
246 gdk_draw_pixmap(w
, dock_gc
, pixmap
, slider_x
, 48,
247 SEEKSLIDER_X
+ seekslider_pos
,
248 SEEKSLIDER_Y
, 3, SEEKSLIDER_HEIGHT
);
251 gdk_draw_pixmap(w
, dock_gc
, pixmap
, 66, 39,
252 SEEKSLIDER_X
, SEEKSLIDER_Y
, 35, 10);
255 void draw_seekslider(void)
257 real_draw_seekslider(icon_win
->window
);
260 void real_draw_scrolltext(GdkWindow
* w
)
262 /* get titlestring */
263 gint pl_pos
= audacious_remote_get_playlist_pos(dbus_proxy
);
267 char *title
= audacious_remote_get_playlist_title(dbus_proxy
, pl_pos
);
271 gint i
= 0, c
= 0, pos
= 0, dest
= 0;
273 for (i
=0; i
<SCROLLTEXT_CHARS
; i
++)
276 scrollpos
%= (strlen(title
)+16) * 6;
277 pos
= i
+ (scrollpos
/ 6) -8;
278 if (pos
< strlen(title
) && pos
>= 0)
279 c
= toupper(title
[pos
]);
283 dest
= SCROLLTEXT_X
+ (i
* 6 - (scrollpos
% 6));
285 if (c
>= 'A' && c
<= 'Z')
290 else if (c
>= '0' && c
<= '9')
295 for (i
=0; i
<NUM_CHARS
; i
++)
296 if (c
== chartable
[i
].ascii
)
303 gdk_draw_pixmap(w
, dock_gc
, pixmap
, x
, y
,
304 dest
, SCROLLTEXT_Y
, 7, 9);
313 void draw_scrolltext(void)
315 real_draw_scrolltext(icon_win
->window
);
318 void redraw_window(void)
322 gdk_draw_pixmap(icon_win
->window
, dock_gc
, pixmap
,
324 draw_buttons(button_list
);
331 gdk_draw_pixmap(icon_win
->window
, dock_gc
, launch_pixmap
,
336 void expose_cb(GtkWidget
*w
, GdkEventExpose
*event
, gpointer data
)
341 void wheel_scroll_cb(GtkWidget
*w
, GdkEventScroll
*event
)
345 if (event
->direction
== GDK_SCROLL_UP
|| event
->direction
== GDK_SCROLL_DOWN
)
347 if (event
->direction
== GDK_SCROLL_UP
)
351 if (volslider_pos
< 0)
353 if (volslider_pos
> VOLSLIDER_HEIGHT
)
354 volslider_pos
= VOLSLIDER_HEIGHT
;
355 audacious_remote_set_main_volume(dbus_proxy
, (volslider_pos
* 100) / VOLSLIDER_HEIGHT
);
360 void button_press_cb(GtkWidget
*w
, GdkEventButton
*event
, gpointer data
)
369 if ((event
->button
== 2) || (event
->button
== 3))
371 if(audacious_remote_is_main_win(dbus_proxy
))
372 audacious_remote_main_win_toggle(dbus_proxy
, FALSE
);
374 audacious_remote_main_win_toggle(dbus_proxy
, TRUE
);
378 if (event
->button
!= 1)
382 for (node
= button_list
; node
; node
= g_list_next(node
))
385 if (inside_region(event
->x
, event
->y
, btn
->x
, btn
->y
, btn
->width
, btn
->height
))
392 if (inside_region(event
->x
, event
->y
, VOLSLIDER_X
, VOLSLIDER_Y
, VOLSLIDER_WIDTH
, VOLSLIDER_HEIGHT
))
394 volslider_pos
= VOLSLIDER_HEIGHT
- (event
->y
- VOLSLIDER_Y
);
395 audacious_remote_set_main_volume(dbus_proxy
, (volslider_pos
* 100) / VOLSLIDER_HEIGHT
);
397 volslider_dragging
= TRUE
;
399 if (inside_region(event
->x
, event
->y
, SEEKSLIDER_X
, SEEKSLIDER_Y
, SEEKSLIDER_WIDTH
, SEEKSLIDER_HEIGHT
) && seekslider_visible
)
401 pos
= event
->x
- SEEKSLIDER_X
;
403 if (pos
>= seekslider_pos
&&
404 pos
< seekslider_pos
+ SEEKSLIDER_KNOB_WIDTH
)
405 seekslider_drag_offset
= pos
- seekslider_pos
;
408 seekslider_drag_offset
= 1;
409 seekslider_pos
= pos
- seekslider_drag_offset
;
410 if (seekslider_pos
< 0)
412 if (seekslider_pos
> SEEKSLIDER_MAX
)
413 seekslider_pos
= SEEKSLIDER_MAX
;
416 seekslider_dragging
= TRUE
;
419 else if ((!single_click
&& event
->type
== GDK_2BUTTON_PRESS
) ||
420 (single_click
&& event
->type
== GDK_BUTTON_PRESS
))
422 cmd
= g_strconcat(xmms_cmd
, " &", NULL
);
428 void button_release_cb(GtkWidget
*w
, GdkEventButton
*event
, gpointer data
)
434 if (event
->button
!= 1)
437 for (node
= button_list
; node
; node
= g_list_next(node
))
443 btn
->pressed
= FALSE
;
449 volslider_dragging
= FALSE
;
450 if (seekslider_dragging
)
452 len
= audacious_remote_get_playlist_time(dbus_proxy
, audacious_remote_get_playlist_pos(dbus_proxy
));
453 audacious_remote_jump_to_time(dbus_proxy
, (seekslider_pos
* len
) / SEEKSLIDER_MAX
);
454 seekslider_dragging
= FALSE
;
459 void motion_notify_cb(GtkWidget
*w
, GdkEventMotion
*event
, gpointer data
)
465 for (node
= button_list
; node
; node
= g_list_next(node
))
470 inside
= inside_region(event
->x
, event
->y
,
472 btn
->width
, btn
->height
);
473 if ((inside
&& !btn
->pressed
) ||
474 (!inside
&& btn
->pressed
))
476 btn
->pressed
= inside
;
481 if (volslider_dragging
)
483 volslider_pos
= VOLSLIDER_HEIGHT
- (event
->y
- VOLSLIDER_Y
);
484 if (volslider_pos
< 0)
486 if (volslider_pos
> VOLSLIDER_HEIGHT
)
487 volslider_pos
= VOLSLIDER_HEIGHT
;
488 audacious_remote_set_main_volume(dbus_proxy
, (volslider_pos
* 100) / VOLSLIDER_HEIGHT
);
491 if (seekslider_dragging
)
494 event
->x
- SEEKSLIDER_X
- seekslider_drag_offset
;
495 if (seekslider_pos
< 0)
497 if (seekslider_pos
> SEEKSLIDER_MAX
)
498 seekslider_pos
= SEEKSLIDER_MAX
;
504 void destroy_cb(GtkWidget
*w
, gpointer data
)
509 static void update_tooltip(void)
511 static int pl_pos
= -1;
512 static char *filename
;
518 new_pos
= audacious_remote_get_playlist_pos(dbus_proxy
);
523 * Need to do some extra checking, as we get 0 also on
526 char *current
= audacious_remote_get_playlist_file(dbus_proxy
, 0);
527 if (!filename
&& current
)
532 else if (filename
&& !current
)
538 else if (filename
&& current
&& strcmp(filename
, current
))
546 if (pl_pos
!= new_pos
)
550 audacious_remote_get_playlist_title(dbus_proxy
, new_pos
);
553 tip
= g_strdup_printf("%d. %s", new_pos
+1, title
);
556 gtk_tooltips_set_tip(tooltips
, icon_win
, tip
, NULL
);
562 int timeout_func(gpointer data
)
565 gboolean playing
, running
;
567 running
= audacious_remote_is_running(dbus_proxy
);
573 gtk_widget_shape_combine_mask(icon_win
, mask
, 0, 0);
574 xmms_running
= running
;
577 if (!volslider_dragging
)
579 new_pos
= (audacious_remote_get_main_volume(dbus_proxy
) * 40) / 100;
583 if (new_pos
> VOLSLIDER_HEIGHT
)
584 new_pos
= VOLSLIDER_HEIGHT
;
586 if (volslider_pos
!= new_pos
)
588 volslider_pos
= new_pos
;
596 playing
= audacious_remote_is_playing(dbus_proxy
);
597 if (!playing
&& seekslider_visible
)
599 seekslider_visible
= FALSE
;
600 seekslider_dragging
= FALSE
;
606 int len
, p
= audacious_remote_get_playlist_pos(dbus_proxy
);
607 len
= audacious_remote_get_playlist_time(dbus_proxy
, p
);
610 seekslider_visible
= FALSE
;
611 seekslider_dragging
= FALSE
;
615 else if (!seekslider_dragging
)
617 seekslider_visible
= TRUE
;
618 pos
= audacious_remote_get_output_time(dbus_proxy
);
620 new_pos
= (pos
* SEEKSLIDER_MAX
) / len
;
625 if (new_pos
> SEEKSLIDER_MAX
)
626 new_pos
= SEEKSLIDER_MAX
;
627 if (seekslider_pos
!= new_pos
)
629 seekslider_pos
= new_pos
;
639 if (tooltips
!= NULL
)
640 gtk_tooltips_set_tip(tooltips
, icon_win
, NULL
, NULL
);
641 gtk_widget_shape_combine_mask(icon_win
, launch_mask
, 0, 0);
642 xmms_running
= FALSE
;
650 void drag_data_received(GtkWidget
*widget
, GdkDragContext
*context
,
651 int x
, int y
, GtkSelectionData
*selection_data
,
652 guint info
, guint time
)
654 if (selection_data
->data
)
656 char *url
= selection_data
->data
;
657 audacious_remote_playlist_clear(dbus_proxy
);
658 audacious_remote_playlist_add_url_string(dbus_proxy
, url
);
659 audacious_remote_play(dbus_proxy
);
663 static gboolean
dbus_init(void)
665 GError
*error
= NULL
;
667 connection
= dbus_g_bus_get(DBUS_BUS_SESSION
, &error
);
668 if (connection
== NULL
)
671 dbus_proxy
= dbus_g_proxy_new_for_name(connection
, AUDACIOUS_DBUS_SERVICE
,
673 AUDACIOUS_DBUS_INTERFACE
);
674 if (dbus_proxy
== NULL
)
689 for (i
= 0; i
< NUM_BUTTONS
; i
++)
690 button_list
= g_list_append(button_list
, &buttons
[i
]);
694 tooltips
= gtk_tooltips_new();
695 gtk_tooltips_set_delay(tooltips
, 1000);
698 icon_win
= gtk_window_new(GTK_WINDOW_TOPLEVEL
);
699 gtk_widget_set_app_paintable(icon_win
, TRUE
);
700 gtk_widget_set_uposition(icon_win
, 0, 0);
701 gtk_widget_set_usize(icon_win
, 64, 64);
702 gtk_widget_set_events(icon_win
,
703 GDK_BUTTON_MOTION_MASK
| GDK_BUTTON_PRESS_MASK
|
704 GDK_BUTTON_RELEASE_MASK
| GDK_EXPOSURE_MASK
);
705 gtk_signal_connect(GTK_OBJECT(icon_win
), "expose_event",
706 GTK_SIGNAL_FUNC(expose_cb
), NULL
);
707 gtk_signal_connect(GTK_OBJECT(icon_win
), "button_press_event",
708 GTK_SIGNAL_FUNC(button_press_cb
), NULL
);
709 gtk_signal_connect(GTK_OBJECT(icon_win
), "scroll_event",
710 GTK_SIGNAL_FUNC(wheel_scroll_cb
), NULL
);
711 gtk_signal_connect(GTK_OBJECT(icon_win
), "button_release_event",
712 GTK_SIGNAL_FUNC(button_release_cb
), NULL
);
713 gtk_signal_connect(GTK_OBJECT(icon_win
), "motion_notify_event",
714 GTK_SIGNAL_FUNC(motion_notify_cb
), NULL
);
715 gtk_signal_connect(GTK_OBJECT(icon_win
), "destroy",
716 GTK_SIGNAL_FUNC(destroy_cb
), NULL
);
717 gtk_drag_dest_set(icon_win
, GTK_DEST_DEFAULT_ALL
, drop_types
, 1,
719 gtk_signal_connect(GTK_OBJECT(icon_win
), "drag_data_received",
720 GTK_SIGNAL_FUNC(drag_data_received
), NULL
);
721 gtk_widget_realize(icon_win
);
725 gdk_colormap_alloc_color(gdk_colormap_get_system(),
726 &bg_color
, FALSE
, TRUE
);
727 gdk_window_set_background(icon_win
->window
, &bg_color
);
728 gdk_window_clear(icon_win
->window
);
729 dock_gc
= gdk_gc_new(icon_win
->window
);
731 launch_pixmap
= gdk_pixmap_new(icon_win
->window
, 64, 64, -1);
733 launch_mask
= gdk_pixmap_new(icon_win
->window
, 64, 64, 1);
734 mask_gc
= gdk_gc_new(launch_mask
);
736 gdk_gc_set_foreground(mask_gc
, &bg_color
);
737 gdk_draw_rectangle(launch_mask
, mask_gc
, TRUE
, 0, 0, -1, -1);
740 icon_name
= g_strdup_printf("%s/wmauda.xpm", PIXMAP_DIR
);
741 pixmap
= gdk_pixmap_create_from_xpm(icon_win
->window
, &mask
,
745 printf("ERROR: Couldn't find %s\n", icon_name
);
750 gdk_window_get_size(pixmap
, &w
, &h
);
755 gdk_draw_pixmap(launch_pixmap
, dock_gc
, pixmap
,
756 0, 0, 32 - (w
/ 2), 32 - (h
/ 2), w
, h
);
757 gdk_draw_pixmap(launch_mask
, mask_gc
, mask
,
758 0, 0, 32 - (w
/ 2), 32 - (h
/ 2), w
, h
);
759 gdk_gc_unref(mask_gc
);
760 gdk_pixmap_unref(pixmap
);
761 gdk_bitmap_unref(mask
);
763 gtk_widget_shape_combine_mask(icon_win
, launch_mask
, 0, 0);
765 pixmap
= gdk_pixmap_create_from_xpm_d(icon_win
->window
,
766 &mask
, NULL
, dock_master_xpm
);
770 attr
.title
= "wmauda";
771 attr
.event_mask
= GDK_BUTTON_PRESS_MASK
| GDK_ENTER_NOTIFY_MASK
| GDK_LEAVE_NOTIFY_MASK
| GDK_POINTER_MOTION_HINT_MASK
;
772 attr
.wclass
= GDK_INPUT_OUTPUT
;
773 attr
.visual
= gdk_visual_get_system();
774 attr
.colormap
= gdk_colormap_get_system();
775 attr
.wmclass_name
= "wmauda";
776 attr
.wmclass_class
= "wmauda";
777 attr
.window_type
= GDK_WINDOW_TOPLEVEL
;
779 leader
= gdk_window_new(NULL
, &attr
, GDK_WA_TITLE
| GDK_WA_WMCLASS
| GDK_WA_VISUAL
| GDK_WA_COLORMAP
);
781 gdk_window_set_icon(leader
, icon_win
->window
, NULL
, NULL
);
782 gdk_window_reparent(icon_win
->window
, leader
, 0, 0);
783 gdk_window_show(leader
);
785 hints
.initial_state
= WithdrawnState
;
786 hints
.flags
= StateHint
| IconWindowHint
| IconPositionHint
| WindowGroupHint
;
787 hints
.icon_window
= GDK_WINDOW_XWINDOW(icon_win
->window
);
790 hints
.window_group
= GDK_WINDOW_XWINDOW(leader
);
792 XSetWMHints(GDK_DISPLAY(), GDK_WINDOW_XWINDOW(leader
), &hints
);
794 gtk_widget_show(icon_win
);
795 timeout_tag
= gtk_timeout_add(100, timeout_func
, NULL
);
799 void display_usage(char *cmd
)
801 printf( "Usage: %s [options]\n\n"
804 "-h, --help Display this text and exit.\n"
805 "-g, --geometry Set the geometry (for example +20+20)\n"
806 "-c, --command Command to launch Audacious (Default: audacious)\n"
807 "-i, --icon Set the icon to use when Audacious is not running\n"
808 "-n, --single Only a single click is needed to start Audacious\n"
809 "-t, --title Display song title when mouse is in window\n"
810 "-v, --version Display version information and exit\n\n",
814 int main(int argc
, char **argv
)
818 static struct option lopt
[] =
820 {"help", no_argument
, 0, 'h'},
821 {"geometry", required_argument
, 0, 'g'},
822 {"session", required_argument
, 0, 's'},
823 {"command", required_argument
, 0, 'c'},
824 {"icon", required_argument
, 0, 'i'},
825 {"single", no_argument
, 0, 'n'},
826 {"title", no_argument
, 0, 't'},
827 {"version", no_argument
, 0, 'v'},
833 gtk_init(&argc
, &argv
);
835 while ((c
= getopt_long(argc
, argv
, "hg:s:c:i:ntv", lopt
, NULL
)) != -1)
840 display_usage(argv
[0]);
844 XParseGeometry(optarg
, &win_x
, &win_y
,
849 xmms_cmd
= g_strdup(optarg
);
852 icon_name
= g_strdup(optarg
);
861 printf("wmauda %s\n", VERSION
);