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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
22 #include <X11/Xatom.h>
29 #include <audacious/dbus.h>
30 #include <audacious/audctrl.h>
34 #include "dock-master.xpm"
40 int x
, y
, width
, height
, pressed_x
, pressed_y
, normal_x
, normal_y
;
41 gboolean focus
, pressed
;
42 void (*callback
) (void);
46 void action_play(void);
47 void action_pause(void);
48 void action_eject(void);
49 void action_prev(void);
50 void action_next(void);
51 void action_stop(void);
55 {21, 32, 9, 11, 84, 0, 64, 0, FALSE
, FALSE
, action_play
}, /* PLAY */
56 {34, 32, 9, 11, 94, 0, 74, 0, FALSE
, FALSE
, action_pause
}, /* PAUSE */
57 {47, 32, 9, 11, 84, 11, 64, 11, FALSE
, FALSE
, action_eject
}, /* EJECT */
58 {21, 46, 9, 11, 84, 22, 64, 22, FALSE
, FALSE
, action_prev
}, /* PREV */
59 {34, 46, 9, 11, 94, 22, 74, 22, FALSE
, FALSE
, action_next
}, /* NEXT */
60 {47, 46, 9, 11, 94, 11, 74, 11, FALSE
, FALSE
, action_stop
}, /* STOP */
69 unsigned char ascii
; gint x
, y
;
72 Charentry chartable
[] =
74 { '-', 60, 73}, /* put here coordinates of characters */
75 { '.', 72, 73}, /* in xmms-dock-master.xpm */
83 { 'ä', 114, 73}, /* toupper doesn't convert umlauts */
97 #define VOLSLIDER_Y 17
98 #define VOLSLIDER_WIDTH 7
101 #define VOLSLIDER_HEIGHT 40
103 #define SEEKSLIDER_X 21
104 #define SEEKSLIDER_Y 20
105 #define SEEKSLIDER_WIDTH 30
106 #define SEEKSLIDER_HEIGHT 7
107 #define SEEKSLIDER_KNOB_WIDTH 3
108 #define SEEKSLIDER_MAX (SEEKSLIDER_WIDTH - SEEKSLIDER_KNOB_WIDTH)
110 #define SCROLLTEXT_X 5
111 #define SCROLLTEXT_Y 6
112 #define SCROLLTEXT_WIDTH 40
113 #define SCROLLTEXT_HEIGHT 9
114 #define SCROLLTEXT_CHARS 9
116 gboolean volslider_dragging
= FALSE
;
117 int volslider_pos
= 0;
118 gboolean seekslider_visible
= FALSE
, seekslider_dragging
= FALSE
;
119 int seekslider_pos
= -1, seekslider_drag_offset
= 0;
126 GdkPixmap
*pixmap
, *launch_pixmap
;
127 GdkBitmap
*mask
, *launch_mask
;
129 GtkTooltips
*tooltips
= NULL
;
131 char *xmms_cmd
= "audacious";
132 gboolean xmms_running
= FALSE
;
134 gboolean has_geometry
= FALSE
, single_click
= FALSE
, song_title
= FALSE
;
135 char *icon_name
= NULL
;
138 DBusGProxy
*dbus_proxy
= NULL
;
139 static DBusGConnection
*connection
= NULL
;
141 GtkTargetEntry drop_types
[] =
146 void action_play(void)
148 audacious_remote_play(dbus_proxy
);
151 void action_pause(void)
153 audacious_remote_pause(dbus_proxy
);
156 void action_eject(void)
158 audacious_remote_playlist_clear(dbus_proxy
);
159 audacious_remote_stop(dbus_proxy
);
162 void action_prev(void)
164 audacious_remote_playlist_prev(dbus_proxy
);
167 void action_next(void)
169 audacious_remote_playlist_next(dbus_proxy
);
172 void action_stop(void)
174 audacious_remote_stop(dbus_proxy
);
177 gboolean
inside_region(int mx
, int my
, int x
, int y
, int w
, int h
)
179 if ((mx
>= x
&& mx
< x
+ w
) && (my
>= y
&& my
< y
+ h
))
184 void real_draw_button(GdkWindow
*w
, Button
*button
)
188 gdk_draw_pixmap(w
, dock_gc
, pixmap
,
189 button
->pressed_x
, button
->pressed_y
,
190 button
->x
, button
->y
,
191 button
->width
, button
->height
);
193 gdk_draw_pixmap(w
, dock_gc
, pixmap
,
194 button
->normal_x
, button
->normal_y
,
195 button
->x
, button
->y
,
196 button
->width
, button
->height
);
199 void draw_button(Button
*button
)
201 real_draw_button(icon_win
->window
, button
);
204 void draw_buttons(GList
*list
)
206 for (; list
; list
= g_list_next(list
))
207 draw_button(list
->data
);
210 void real_draw_volslider(GdkWindow
*w
)
212 gdk_draw_pixmap(w
, dock_gc
, pixmap
, 112, 1, VOLSLIDER_X
, VOLSLIDER_Y
,
213 VOLSLIDER_WIDTH
, VOLSLIDER_HEIGHT
);
214 gdk_draw_pixmap(w
, dock_gc
, pixmap
, 106,
215 1 + VOLSLIDER_HEIGHT
- volslider_pos
,
217 VOLSLIDER_Y
+ VOLSLIDER_HEIGHT
- volslider_pos
,
218 VOLSLIDER_WIDTH
, volslider_pos
);
221 void draw_volslider(void)
223 real_draw_volslider(icon_win
->window
);
226 void real_draw_seekslider(GdkWindow
*w
)
230 if (seekslider_visible
)
232 gdk_draw_pixmap(w
, dock_gc
, pixmap
, 66, 54,
233 SEEKSLIDER_X
, SEEKSLIDER_Y
, 35, 10);
234 if (seekslider_pos
< SEEKSLIDER_MAX
/ 3)
236 else if (seekslider_pos
< (SEEKSLIDER_MAX
* 2) / 3)
240 gdk_draw_pixmap(w
, dock_gc
, pixmap
, slider_x
, 48,
241 SEEKSLIDER_X
+ seekslider_pos
,
242 SEEKSLIDER_Y
, 3, SEEKSLIDER_HEIGHT
);
245 gdk_draw_pixmap(w
, dock_gc
, pixmap
, 66, 39,
246 SEEKSLIDER_X
, SEEKSLIDER_Y
, 35, 10);
249 void draw_seekslider(void)
251 real_draw_seekslider(icon_win
->window
);
254 void real_draw_scrolltext(GdkWindow
* w
)
256 /* get titlestring */
257 gint pl_pos
= audacious_remote_get_playlist_pos(dbus_proxy
);
261 char *title
= audacious_remote_get_playlist_title(dbus_proxy
, pl_pos
);
265 gint i
= 0, c
= 0, pos
= 0, dest
= 0;
267 for (i
=0; i
<SCROLLTEXT_CHARS
; i
++)
270 scrollpos
%= (strlen(title
)+16) * 6;
271 pos
= i
+ (scrollpos
/ 6) -8;
272 if (pos
< strlen(title
) && pos
>= 0)
273 c
= toupper(title
[pos
]);
277 dest
= SCROLLTEXT_X
+ (i
* 6 - (scrollpos
% 6));
279 if (c
>= 'A' && c
<= 'Z')
284 else if (c
>= '0' && c
<= '9')
289 for (i
=0; i
<NUM_CHARS
; i
++)
290 if (c
== chartable
[i
].ascii
)
297 gdk_draw_pixmap(w
, dock_gc
, pixmap
, x
, y
,
298 dest
, SCROLLTEXT_Y
, 7, 9);
307 void draw_scrolltext(void)
309 real_draw_scrolltext(icon_win
->window
);
312 void redraw_window(void)
316 gdk_draw_pixmap(icon_win
->window
, dock_gc
, pixmap
,
318 draw_buttons(button_list
);
325 gdk_draw_pixmap(icon_win
->window
, dock_gc
, launch_pixmap
,
330 void expose_cb(GtkWidget
*w
, GdkEventExpose
*event
, gpointer data
)
335 void wheel_scroll_cb(GtkWidget
*w
, GdkEventScroll
*event
)
339 if (event
->direction
== GDK_SCROLL_UP
|| event
->direction
== GDK_SCROLL_DOWN
)
341 if (event
->direction
== GDK_SCROLL_UP
)
345 if (volslider_pos
< 0)
347 if (volslider_pos
> VOLSLIDER_HEIGHT
)
348 volslider_pos
= VOLSLIDER_HEIGHT
;
349 audacious_remote_set_main_volume(dbus_proxy
, (volslider_pos
* 100) / VOLSLIDER_HEIGHT
);
354 void button_press_cb(GtkWidget
*w
, GdkEventButton
*event
, gpointer data
)
363 if ((event
->button
== 2) || (event
->button
== 3))
365 if(audacious_remote_is_main_win(dbus_proxy
))
366 audacious_remote_main_win_toggle(dbus_proxy
, FALSE
);
368 audacious_remote_main_win_toggle(dbus_proxy
, TRUE
);
372 if (event
->button
!= 1)
376 for (node
= button_list
; node
; node
= g_list_next(node
))
379 if (inside_region(event
->x
, event
->y
, btn
->x
, btn
->y
, btn
->width
, btn
->height
))
386 if (inside_region(event
->x
, event
->y
, VOLSLIDER_X
, VOLSLIDER_Y
, VOLSLIDER_WIDTH
, VOLSLIDER_HEIGHT
))
388 volslider_pos
= VOLSLIDER_HEIGHT
- (event
->y
- VOLSLIDER_Y
);
389 audacious_remote_set_main_volume(dbus_proxy
, (volslider_pos
* 100) / VOLSLIDER_HEIGHT
);
391 volslider_dragging
= TRUE
;
393 if (inside_region(event
->x
, event
->y
, SEEKSLIDER_X
, SEEKSLIDER_Y
, SEEKSLIDER_WIDTH
, SEEKSLIDER_HEIGHT
) && seekslider_visible
)
395 pos
= event
->x
- SEEKSLIDER_X
;
397 if (pos
>= seekslider_pos
&&
398 pos
< seekslider_pos
+ SEEKSLIDER_KNOB_WIDTH
)
399 seekslider_drag_offset
= pos
- seekslider_pos
;
402 seekslider_drag_offset
= 1;
403 seekslider_pos
= pos
- seekslider_drag_offset
;
404 if (seekslider_pos
< 0)
406 if (seekslider_pos
> SEEKSLIDER_MAX
)
407 seekslider_pos
= SEEKSLIDER_MAX
;
410 seekslider_dragging
= TRUE
;
413 else if ((!single_click
&& event
->type
== GDK_2BUTTON_PRESS
) ||
414 (single_click
&& event
->type
== GDK_BUTTON_PRESS
))
416 cmd
= g_strconcat(xmms_cmd
, " &", NULL
);
422 void button_release_cb(GtkWidget
*w
, GdkEventButton
*event
, gpointer data
)
428 if (event
->button
!= 1)
431 for (node
= button_list
; node
; node
= g_list_next(node
))
437 btn
->pressed
= FALSE
;
443 volslider_dragging
= FALSE
;
444 if (seekslider_dragging
)
446 len
= audacious_remote_get_playlist_time(dbus_proxy
, audacious_remote_get_playlist_pos(dbus_proxy
));
447 audacious_remote_jump_to_time(dbus_proxy
, (seekslider_pos
* len
) / SEEKSLIDER_MAX
);
448 seekslider_dragging
= FALSE
;
453 void motion_notify_cb(GtkWidget
*w
, GdkEventMotion
*event
, gpointer data
)
459 for (node
= button_list
; node
; node
= g_list_next(node
))
464 inside
= inside_region(event
->x
, event
->y
,
466 btn
->width
, btn
->height
);
467 if ((inside
&& !btn
->pressed
) ||
468 (!inside
&& btn
->pressed
))
470 btn
->pressed
= inside
;
475 if (volslider_dragging
)
477 volslider_pos
= VOLSLIDER_HEIGHT
- (event
->y
- VOLSLIDER_Y
);
478 if (volslider_pos
< 0)
480 if (volslider_pos
> VOLSLIDER_HEIGHT
)
481 volslider_pos
= VOLSLIDER_HEIGHT
;
482 audacious_remote_set_main_volume(dbus_proxy
, (volslider_pos
* 100) / VOLSLIDER_HEIGHT
);
485 if (seekslider_dragging
)
488 event
->x
- SEEKSLIDER_X
- seekslider_drag_offset
;
489 if (seekslider_pos
< 0)
491 if (seekslider_pos
> SEEKSLIDER_MAX
)
492 seekslider_pos
= SEEKSLIDER_MAX
;
498 void destroy_cb(GtkWidget
*w
, gpointer data
)
503 static void update_tooltip(void)
505 static int pl_pos
= -1;
506 static char *filename
;
512 new_pos
= audacious_remote_get_playlist_pos(dbus_proxy
);
517 * Need to do some extra checking, as we get 0 also on
520 char *current
= audacious_remote_get_playlist_file(dbus_proxy
, 0);
521 if (!filename
&& current
)
526 else if (filename
&& !current
)
532 else if (filename
&& current
&& strcmp(filename
, current
))
540 if (pl_pos
!= new_pos
)
544 audacious_remote_get_playlist_title(dbus_proxy
, new_pos
);
547 tip
= g_strdup_printf("%d. %s", new_pos
+1, title
);
550 gtk_tooltips_set_tip(tooltips
, icon_win
, tip
, NULL
);
556 int timeout_func(gpointer data
)
559 gboolean playing
, running
;
561 running
= audacious_remote_is_running(dbus_proxy
);
567 gtk_widget_shape_combine_mask(icon_win
, mask
, 0, 0);
568 xmms_running
= running
;
571 if (!volslider_dragging
)
573 new_pos
= (audacious_remote_get_main_volume(dbus_proxy
) * 40) / 100;
577 if (new_pos
> VOLSLIDER_HEIGHT
)
578 new_pos
= VOLSLIDER_HEIGHT
;
580 if (volslider_pos
!= new_pos
)
582 volslider_pos
= new_pos
;
590 playing
= audacious_remote_is_playing(dbus_proxy
);
591 if (!playing
&& seekslider_visible
)
593 seekslider_visible
= FALSE
;
594 seekslider_dragging
= FALSE
;
600 int len
, p
= audacious_remote_get_playlist_pos(dbus_proxy
);
601 len
= audacious_remote_get_playlist_time(dbus_proxy
, p
);
604 seekslider_visible
= FALSE
;
605 seekslider_dragging
= FALSE
;
609 else if (!seekslider_dragging
)
611 seekslider_visible
= TRUE
;
612 pos
= audacious_remote_get_output_time(dbus_proxy
);
614 new_pos
= (pos
* SEEKSLIDER_MAX
) / len
;
619 if (new_pos
> SEEKSLIDER_MAX
)
620 new_pos
= SEEKSLIDER_MAX
;
621 if (seekslider_pos
!= new_pos
)
623 seekslider_pos
= new_pos
;
633 if (tooltips
!= NULL
)
634 gtk_tooltips_set_tip(tooltips
, icon_win
, NULL
, NULL
);
635 gtk_widget_shape_combine_mask(icon_win
, launch_mask
, 0, 0);
636 xmms_running
= FALSE
;
644 void drag_data_received(GtkWidget
*widget
, GdkDragContext
*context
,
645 int x
, int y
, GtkSelectionData
*selection_data
,
646 guint info
, guint time
)
648 if (selection_data
->data
)
650 char *url
= selection_data
->data
;
651 audacious_remote_playlist_clear(dbus_proxy
);
652 audacious_remote_playlist_add_url_string(dbus_proxy
, url
);
653 audacious_remote_play(dbus_proxy
);
657 static gboolean
dbus_init(void)
659 GError
*error
= NULL
;
661 connection
= dbus_g_bus_get(DBUS_BUS_SESSION
, &error
);
662 if (connection
== NULL
)
665 dbus_proxy
= dbus_g_proxy_new_for_name(connection
, AUDACIOUS_DBUS_SERVICE
,
667 AUDACIOUS_DBUS_INTERFACE
);
668 if (dbus_proxy
== NULL
)
683 for (i
= 0; i
< NUM_BUTTONS
; i
++)
684 button_list
= g_list_append(button_list
, &buttons
[i
]);
688 tooltips
= gtk_tooltips_new();
689 gtk_tooltips_set_delay(tooltips
, 1000);
692 icon_win
= gtk_window_new(GTK_WINDOW_TOPLEVEL
);
693 gtk_widget_set_app_paintable(icon_win
, TRUE
);
694 gtk_widget_set_uposition(icon_win
, 0, 0);
695 gtk_widget_set_usize(icon_win
, 64, 64);
696 gtk_widget_set_events(icon_win
,
697 GDK_BUTTON_MOTION_MASK
| GDK_BUTTON_PRESS_MASK
|
698 GDK_BUTTON_RELEASE_MASK
| GDK_EXPOSURE_MASK
);
699 gtk_signal_connect(GTK_OBJECT(icon_win
), "expose_event",
700 GTK_SIGNAL_FUNC(expose_cb
), NULL
);
701 gtk_signal_connect(GTK_OBJECT(icon_win
), "button_press_event",
702 GTK_SIGNAL_FUNC(button_press_cb
), NULL
);
703 gtk_signal_connect(GTK_OBJECT(icon_win
), "scroll_event",
704 GTK_SIGNAL_FUNC(wheel_scroll_cb
), NULL
);
705 gtk_signal_connect(GTK_OBJECT(icon_win
), "button_release_event",
706 GTK_SIGNAL_FUNC(button_release_cb
), NULL
);
707 gtk_signal_connect(GTK_OBJECT(icon_win
), "motion_notify_event",
708 GTK_SIGNAL_FUNC(motion_notify_cb
), NULL
);
709 gtk_signal_connect(GTK_OBJECT(icon_win
), "destroy",
710 GTK_SIGNAL_FUNC(destroy_cb
), NULL
);
711 gtk_drag_dest_set(icon_win
, GTK_DEST_DEFAULT_ALL
, drop_types
, 1,
713 gtk_signal_connect(GTK_OBJECT(icon_win
), "drag_data_received",
714 GTK_SIGNAL_FUNC(drag_data_received
), NULL
);
715 gtk_widget_realize(icon_win
);
719 gdk_colormap_alloc_color(gdk_colormap_get_system(),
720 &bg_color
, FALSE
, TRUE
);
721 gdk_window_set_background(icon_win
->window
, &bg_color
);
722 gdk_window_clear(icon_win
->window
);
723 dock_gc
= gdk_gc_new(icon_win
->window
);
725 launch_pixmap
= gdk_pixmap_new(icon_win
->window
, 64, 64, -1);
727 launch_mask
= gdk_pixmap_new(icon_win
->window
, 64, 64, 1);
728 mask_gc
= gdk_gc_new(launch_mask
);
730 gdk_gc_set_foreground(mask_gc
, &bg_color
);
731 gdk_draw_rectangle(launch_mask
, mask_gc
, TRUE
, 0, 0, -1, -1);
734 icon_name
= g_strdup_printf("%s/wmauda.xpm", DATA_DIR
);
735 pixmap
= gdk_pixmap_create_from_xpm(icon_win
->window
, &mask
,
739 printf("ERROR: Couldn't find %s\n", icon_name
);
744 gdk_window_get_size(pixmap
, &w
, &h
);
749 gdk_draw_pixmap(launch_pixmap
, dock_gc
, pixmap
,
750 0, 0, 32 - (w
/ 2), 32 - (h
/ 2), w
, h
);
751 gdk_draw_pixmap(launch_mask
, mask_gc
, mask
,
752 0, 0, 32 - (w
/ 2), 32 - (h
/ 2), w
, h
);
753 gdk_gc_unref(mask_gc
);
754 gdk_pixmap_unref(pixmap
);
755 gdk_bitmap_unref(mask
);
757 gtk_widget_shape_combine_mask(icon_win
, launch_mask
, 0, 0);
759 pixmap
= gdk_pixmap_create_from_xpm_d(icon_win
->window
,
760 &mask
, NULL
, dock_master_xpm
);
764 attr
.title
= "wmauda";
765 attr
.event_mask
= GDK_BUTTON_PRESS_MASK
| GDK_ENTER_NOTIFY_MASK
| GDK_LEAVE_NOTIFY_MASK
| GDK_POINTER_MOTION_HINT_MASK
;
766 attr
.wclass
= GDK_INPUT_OUTPUT
;
767 attr
.visual
= gdk_visual_get_system();
768 attr
.colormap
= gdk_colormap_get_system();
769 attr
.wmclass_name
= "wmauda";
770 attr
.wmclass_class
= "wmauda";
771 attr
.window_type
= GDK_WINDOW_TOPLEVEL
;
773 leader
= gdk_window_new(NULL
, &attr
, GDK_WA_TITLE
| GDK_WA_WMCLASS
| GDK_WA_VISUAL
| GDK_WA_COLORMAP
);
775 gdk_window_set_icon(leader
, icon_win
->window
, NULL
, NULL
);
776 gdk_window_reparent(icon_win
->window
, leader
, 0, 0);
777 gdk_window_show(leader
);
779 hints
.initial_state
= WithdrawnState
;
780 hints
.flags
= StateHint
| IconWindowHint
| IconPositionHint
| WindowGroupHint
;
781 hints
.icon_window
= GDK_WINDOW_XWINDOW(icon_win
->window
);
784 hints
.window_group
= GDK_WINDOW_XWINDOW(leader
);
786 XSetWMHints(GDK_DISPLAY(), GDK_WINDOW_XWINDOW(leader
), &hints
);
788 gtk_widget_show(icon_win
);
789 timeout_tag
= gtk_timeout_add(100, timeout_func
, NULL
);
793 void display_usage(char *cmd
)
795 printf( "Usage: %s [options]\n\n"
798 "-h, --help Display this text and exit.\n"
799 "-g, --geometry Set the geometry (for example +20+20)\n"
800 "-c, --command Command to launch Audacious (Default: audacious)\n"
801 "-i, --icon Set the icon to use when Audacious is not running\n"
802 "-n, --single Only a single click is needed to start Audacious\n"
803 "-t, --title Display song title when mouse is in window\n"
804 "-v, --version Display version information and exit\n\n",
808 int main(int argc
, char **argv
)
812 static struct option lopt
[] =
814 {"help", no_argument
, 0, 'h'},
815 {"geometry", required_argument
, 0, 'g'},
816 {"session", required_argument
, 0, 's'},
817 {"command", required_argument
, 0, 'c'},
818 {"icon", required_argument
, 0, 'i'},
819 {"single", no_argument
, 0, 'n'},
820 {"title", no_argument
, 0, 't'},
821 {"version", no_argument
, 0, 'v'},
827 gtk_init(&argc
, &argv
);
829 while ((c
= getopt_long(argc
, argv
, "hg:s:c:i:ntv", lopt
, NULL
)) != -1)
834 display_usage(argv
[0]);
838 XParseGeometry(optarg
, &win_x
, &win_y
,
843 xmms_cmd
= g_strdup(optarg
);
846 icon_name
= g_strdup(optarg
);
855 printf("wmauda %s\n", VERSION
);