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>
31 #include "audacious/beepctrl.h"
32 #include "dock-master.xpm"
38 int x
, y
, width
, height
, pressed_x
, pressed_y
, normal_x
, normal_y
;
39 gboolean focus
, pressed
;
40 void (*callback
) (void);
44 void action_play(void);
45 void action_pause(void);
46 void action_eject(void);
47 void action_prev(void);
48 void action_next(void);
49 void action_stop(void);
53 {21, 32, 9, 11, 84, 0, 64, 0, FALSE
, FALSE
, action_play
}, /* PLAY */
54 {34, 32, 9, 11, 94, 0, 74, 0, FALSE
, FALSE
, action_pause
}, /* PAUSE */
55 {47, 32, 9, 11, 84, 11, 64, 11, FALSE
, FALSE
, action_eject
}, /* EJECT */
56 {21, 46, 9, 11, 84, 22, 64, 22, FALSE
, FALSE
, action_prev
}, /* PREV */
57 {34, 46, 9, 11, 94, 22, 74, 22, FALSE
, FALSE
, action_next
}, /* NEXT */
58 {47, 46, 9, 11, 94, 11, 74, 11, FALSE
, FALSE
, action_stop
}, /* STOP */
67 unsigned char ascii
; gint x
, y
;
70 Charentry chartable
[] =
72 { '-', 60, 73}, /* put here coordinates of characters */
73 { '.', 72, 73}, /* in xmms-dock-master.xpm */
81 { 'ä', 114, 73}, /* toupper doesn't convert umlauts */
95 #define VOLSLIDER_Y 17
96 #define VOLSLIDER_WIDTH 7
99 #define VOLSLIDER_HEIGHT 40
101 #define SEEKSLIDER_X 21
102 #define SEEKSLIDER_Y 20
103 #define SEEKSLIDER_WIDTH 30
104 #define SEEKSLIDER_HEIGHT 7
105 #define SEEKSLIDER_KNOB_WIDTH 3
106 #define SEEKSLIDER_MAX (SEEKSLIDER_WIDTH - SEEKSLIDER_KNOB_WIDTH)
108 #define SCROLLTEXT_X 5
109 #define SCROLLTEXT_Y 6
110 #define SCROLLTEXT_WIDTH 40
111 #define SCROLLTEXT_HEIGHT 9
112 #define SCROLLTEXT_CHARS 9
114 gboolean volslider_dragging
= FALSE
;
115 int volslider_pos
= 0;
116 gboolean seekslider_visible
= FALSE
, seekslider_dragging
= FALSE
;
117 int seekslider_pos
= -1, seekslider_drag_offset
= 0;
124 GdkPixmap
*pixmap
, *launch_pixmap
;
125 GdkBitmap
*mask
, *launch_mask
;
127 GtkTooltips
*tooltips
= NULL
;
129 int xmms_session
= 0;
130 char *xmms_cmd
= "audacious";
131 gboolean xmms_running
= FALSE
;
133 gboolean has_geometry
= FALSE
, single_click
= FALSE
, song_title
= FALSE
;
134 char *icon_name
= NULL
;
137 GtkTargetEntry drop_types
[] =
142 void action_play(void)
144 xmms_remote_play(xmms_session
);
147 void action_pause(void)
149 xmms_remote_pause(xmms_session
);
152 void action_eject(void)
154 xmms_remote_eject(xmms_session
);
157 void action_prev(void)
159 xmms_remote_playlist_prev(xmms_session
);
162 void action_next(void)
164 xmms_remote_playlist_next(xmms_session
);
167 void action_stop(void)
169 xmms_remote_stop(xmms_session
);
172 gboolean
inside_region(int mx
, int my
, int x
, int y
, int w
, int h
)
174 if ((mx
>= x
&& mx
< x
+ w
) && (my
>= y
&& my
< y
+ h
))
179 void real_draw_button(GdkWindow
*w
, Button
*button
)
183 gdk_draw_pixmap(w
, dock_gc
, pixmap
,
184 button
->pressed_x
, button
->pressed_y
,
185 button
->x
, button
->y
,
186 button
->width
, button
->height
);
188 gdk_draw_pixmap(w
, dock_gc
, pixmap
,
189 button
->normal_x
, button
->normal_y
,
190 button
->x
, button
->y
,
191 button
->width
, button
->height
);
194 void draw_button(Button
*button
)
196 real_draw_button(icon_win
->window
, button
);
199 void draw_buttons(GList
*list
)
201 for (; list
; list
= g_list_next(list
))
202 draw_button(list
->data
);
205 void real_draw_volslider(GdkWindow
*w
)
207 gdk_draw_pixmap(w
, dock_gc
, pixmap
, 112, 1, VOLSLIDER_X
, VOLSLIDER_Y
,
208 VOLSLIDER_WIDTH
, VOLSLIDER_HEIGHT
);
209 gdk_draw_pixmap(w
, dock_gc
, pixmap
, 106,
210 1 + VOLSLIDER_HEIGHT
- volslider_pos
,
212 VOLSLIDER_Y
+ VOLSLIDER_HEIGHT
- volslider_pos
,
213 VOLSLIDER_WIDTH
, volslider_pos
);
216 void draw_volslider(void)
218 real_draw_volslider(icon_win
->window
);
221 void real_draw_seekslider(GdkWindow
*w
)
225 if (seekslider_visible
)
227 gdk_draw_pixmap(w
, dock_gc
, pixmap
, 66, 54,
228 SEEKSLIDER_X
, SEEKSLIDER_Y
, 35, 10);
229 if (seekslider_pos
< SEEKSLIDER_MAX
/ 3)
231 else if (seekslider_pos
< (SEEKSLIDER_MAX
* 2) / 3)
235 gdk_draw_pixmap(w
, dock_gc
, pixmap
, slider_x
, 48,
236 SEEKSLIDER_X
+ seekslider_pos
,
237 SEEKSLIDER_Y
, 3, SEEKSLIDER_HEIGHT
);
240 gdk_draw_pixmap(w
, dock_gc
, pixmap
, 66, 39,
241 SEEKSLIDER_X
, SEEKSLIDER_Y
, 35, 10);
244 void draw_seekslider(void)
246 real_draw_seekslider(icon_win
->window
);
249 void real_draw_scrolltext(GdkWindow
* w
)
251 /* get titlestring */
252 gint pl_pos
= xmms_remote_get_playlist_pos(xmms_session
);
256 char *title
= xmms_remote_get_playlist_title(xmms_session
, pl_pos
);
260 gint i
= 0, c
= 0, pos
= 0, dest
= 0;
262 for (i
=0; i
<SCROLLTEXT_CHARS
; i
++)
265 scrollpos
%= (strlen(title
)+16) * 6;
266 pos
= i
+ (scrollpos
/ 6) -8;
267 if (pos
< strlen(title
) && pos
>= 0)
268 c
= toupper(title
[pos
]);
272 dest
= SCROLLTEXT_X
+ (i
* 6 - (scrollpos
% 6));
274 if (c
>= 'A' && c
<= 'Z')
279 else if (c
>= '0' && c
<= '9')
284 for (i
=0; i
<NUM_CHARS
; i
++)
285 if (c
== chartable
[i
].ascii
)
292 gdk_draw_pixmap(w
, dock_gc
, pixmap
, x
, y
,
293 dest
, SCROLLTEXT_Y
, 7, 9);
302 void draw_scrolltext(void)
304 real_draw_scrolltext(icon_win
->window
);
307 void redraw_window(void)
311 gdk_draw_pixmap(icon_win
->window
, dock_gc
, pixmap
,
313 draw_buttons(button_list
);
320 gdk_draw_pixmap(icon_win
->window
, dock_gc
, launch_pixmap
,
325 void expose_cb(GtkWidget
*w
, GdkEventExpose
*event
, gpointer data
)
330 void button_press_cb(GtkWidget
*w
, GdkEventButton
*event
, gpointer data
)
339 if (event
->button
== 2)
341 if(xmms_remote_is_pl_win(xmms_session
))
342 xmms_remote_pl_win_toggle(xmms_session
, 0);
344 xmms_remote_pl_win_toggle(xmms_session
, 1);
346 else if (event
->button
== 3)
348 if(xmms_remote_is_main_win(xmms_session
))
349 xmms_remote_main_win_toggle(xmms_session
, 0);
351 xmms_remote_main_win_toggle(xmms_session
, 1);
353 else if (event
->button
== 4 || event
->button
== 5)
355 if (event
->button
== 4)
359 if (volslider_pos
< 0)
361 if (volslider_pos
> VOLSLIDER_HEIGHT
)
362 volslider_pos
= VOLSLIDER_HEIGHT
;
363 xmms_remote_set_main_volume(xmms_session
, (volslider_pos
* 100) / VOLSLIDER_HEIGHT
);
368 if (event
->button
!= 1)
372 for (node
= button_list
; node
; node
= g_list_next(node
))
375 if (inside_region(event
->x
, event
->y
, btn
->x
, btn
->y
, btn
->width
, btn
->height
))
382 if (inside_region(event
->x
, event
->y
, VOLSLIDER_X
, VOLSLIDER_Y
, VOLSLIDER_WIDTH
, VOLSLIDER_HEIGHT
))
384 volslider_pos
= VOLSLIDER_HEIGHT
- (event
->y
- VOLSLIDER_Y
);
385 xmms_remote_set_main_volume(xmms_session
, (volslider_pos
* 100) / VOLSLIDER_HEIGHT
);
387 volslider_dragging
= TRUE
;
389 if (inside_region(event
->x
, event
->y
, SEEKSLIDER_X
, SEEKSLIDER_Y
, SEEKSLIDER_WIDTH
, SEEKSLIDER_HEIGHT
) && seekslider_visible
)
391 pos
= event
->x
- SEEKSLIDER_X
;
393 if (pos
>= seekslider_pos
&&
394 pos
< seekslider_pos
+ SEEKSLIDER_KNOB_WIDTH
)
395 seekslider_drag_offset
= pos
- seekslider_pos
;
398 seekslider_drag_offset
= 1;
399 seekslider_pos
= pos
- seekslider_drag_offset
;
400 if (seekslider_pos
< 0)
402 if (seekslider_pos
> SEEKSLIDER_MAX
)
403 seekslider_pos
= SEEKSLIDER_MAX
;
406 seekslider_dragging
= TRUE
;
409 else if ((!single_click
&& event
->type
== GDK_2BUTTON_PRESS
) ||
410 (single_click
&& event
->type
== GDK_BUTTON_PRESS
))
412 cmd
= g_strconcat(xmms_cmd
, " &", NULL
);
418 void button_release_cb(GtkWidget
*w
, GdkEventButton
*event
, gpointer data
)
424 if (event
->button
!= 1)
427 for (node
= button_list
; node
; node
= g_list_next(node
))
433 btn
->pressed
= FALSE
;
439 volslider_dragging
= FALSE
;
440 if (seekslider_dragging
)
442 len
= xmms_remote_get_playlist_time(xmms_session
, xmms_remote_get_playlist_pos(xmms_session
));
443 xmms_remote_jump_to_time(xmms_session
, (seekslider_pos
* len
) / SEEKSLIDER_MAX
);
444 seekslider_dragging
= FALSE
;
449 void motion_notify_cb(GtkWidget
*w
, GdkEventMotion
*event
, gpointer data
)
455 for (node
= button_list
; node
; node
= g_list_next(node
))
460 inside
= inside_region(event
->x
, event
->y
,
462 btn
->width
, btn
->height
);
463 if ((inside
&& !btn
->pressed
) ||
464 (!inside
&& btn
->pressed
))
466 btn
->pressed
= inside
;
471 if (volslider_dragging
)
473 volslider_pos
= VOLSLIDER_HEIGHT
- (event
->y
- VOLSLIDER_Y
);
474 if (volslider_pos
< 0)
476 if (volslider_pos
> VOLSLIDER_HEIGHT
)
477 volslider_pos
= VOLSLIDER_HEIGHT
;
478 xmms_remote_set_main_volume(xmms_session
, (volslider_pos
* 100) / VOLSLIDER_HEIGHT
);
481 if (seekslider_dragging
)
484 event
->x
- SEEKSLIDER_X
- seekslider_drag_offset
;
485 if (seekslider_pos
< 0)
487 if (seekslider_pos
> SEEKSLIDER_MAX
)
488 seekslider_pos
= SEEKSLIDER_MAX
;
494 void destroy_cb(GtkWidget
*w
, gpointer data
)
499 static void update_tooltip(void)
501 static int pl_pos
= -1;
502 static char *filename
;
508 new_pos
= xmms_remote_get_playlist_pos(xmms_session
);
513 * Need to do some extra checking, as we get 0 also on
516 char *current
= xmms_remote_get_playlist_file(xmms_session
, 0);
517 if (!filename
&& current
)
522 else if (filename
&& !current
)
528 else if (filename
&& current
&& strcmp(filename
, current
))
536 if (pl_pos
!= new_pos
)
540 xmms_remote_get_playlist_title(xmms_session
, new_pos
);
543 tip
= g_strdup_printf("%d. %s", new_pos
+1, title
);
546 gtk_tooltips_set_tip(tooltips
, icon_win
, tip
, NULL
);
552 int timeout_func(gpointer data
)
555 gboolean playing
, running
;
557 running
= xmms_remote_is_running(xmms_session
);
563 gtk_widget_shape_combine_mask(icon_win
, mask
, 0, 0);
564 xmms_running
= running
;
567 if (!volslider_dragging
)
570 xmms_remote_get_volume(xmms_session
, &vl
, &vr
);
572 new_pos
= ((vl
> vr
? vl
: vr
) * 40) / 100;
576 if (new_pos
> VOLSLIDER_HEIGHT
)
577 new_pos
= VOLSLIDER_HEIGHT
;
579 if (volslider_pos
!= new_pos
)
581 volslider_pos
= new_pos
;
589 playing
= xmms_remote_is_playing(xmms_session
);
590 if (!playing
&& seekslider_visible
)
592 seekslider_visible
= FALSE
;
593 seekslider_dragging
= FALSE
;
599 int len
, p
= xmms_remote_get_playlist_pos(xmms_session
);
600 len
= xmms_remote_get_playlist_time(xmms_session
, p
);
603 seekslider_visible
= FALSE
;
604 seekslider_dragging
= FALSE
;
608 else if (!seekslider_dragging
)
610 seekslider_visible
= TRUE
;
611 pos
= xmms_remote_get_output_time(xmms_session
);
613 new_pos
= (pos
* SEEKSLIDER_MAX
) / len
;
618 if (new_pos
> SEEKSLIDER_MAX
)
619 new_pos
= SEEKSLIDER_MAX
;
620 if (seekslider_pos
!= new_pos
)
622 seekslider_pos
= new_pos
;
632 if (tooltips
!= NULL
)
633 gtk_tooltips_set_tip(tooltips
, icon_win
, NULL
, NULL
);
634 gtk_widget_shape_combine_mask(icon_win
, launch_mask
, 0, 0);
635 xmms_running
= FALSE
;
643 void drag_data_received(GtkWidget
*widget
, GdkDragContext
*context
,
644 int x
, int y
, GtkSelectionData
*selection_data
,
645 guint info
, guint time
)
647 if (selection_data
->data
)
649 char *url
= selection_data
->data
;
650 xmms_remote_playlist_clear(xmms_session
);
651 xmms_remote_playlist_add_url_string(xmms_session
, url
);
652 xmms_remote_play(xmms_session
);
666 for (i
= 0; i
< NUM_BUTTONS
; i
++)
667 button_list
= g_list_append(button_list
, &buttons
[i
]);
671 tooltips
= gtk_tooltips_new();
672 gtk_tooltips_set_delay(tooltips
, 1000);
675 icon_win
= gtk_window_new(GTK_WINDOW_TOPLEVEL
);
676 gtk_widget_set_app_paintable(icon_win
, TRUE
);
677 gtk_widget_set_uposition(icon_win
, 0, 0);
678 gtk_widget_set_usize(icon_win
, 64, 64);
679 gtk_widget_set_events(icon_win
,
680 GDK_BUTTON_MOTION_MASK
| GDK_BUTTON_PRESS_MASK
|
681 GDK_BUTTON_RELEASE_MASK
| GDK_EXPOSURE_MASK
);
682 gtk_signal_connect(GTK_OBJECT(icon_win
), "expose_event",
683 GTK_SIGNAL_FUNC(expose_cb
), NULL
);
684 gtk_signal_connect(GTK_OBJECT(icon_win
), "button_press_event",
685 GTK_SIGNAL_FUNC(button_press_cb
), NULL
);
686 gtk_signal_connect(GTK_OBJECT(icon_win
), "button_release_event",
687 GTK_SIGNAL_FUNC(button_release_cb
), NULL
);
688 gtk_signal_connect(GTK_OBJECT(icon_win
), "motion_notify_event",
689 GTK_SIGNAL_FUNC(motion_notify_cb
), NULL
);
690 gtk_signal_connect(GTK_OBJECT(icon_win
), "destroy",
691 GTK_SIGNAL_FUNC(destroy_cb
), NULL
);
692 gtk_drag_dest_set(icon_win
, GTK_DEST_DEFAULT_ALL
, drop_types
, 1,
694 gtk_signal_connect(GTK_OBJECT(icon_win
), "drag_data_received",
695 GTK_SIGNAL_FUNC(drag_data_received
), NULL
);
696 gtk_widget_realize(icon_win
);
700 gdk_colormap_alloc_color(gdk_colormap_get_system(),
701 &bg_color
, FALSE
, TRUE
);
702 gdk_window_set_background(icon_win
->window
, &bg_color
);
703 gdk_window_clear(icon_win
->window
);
704 dock_gc
= gdk_gc_new(icon_win
->window
);
706 launch_pixmap
= gdk_pixmap_new(icon_win
->window
, 64, 64,
707 gdk_visual_get_best_depth());
709 launch_mask
= gdk_pixmap_new(icon_win
->window
, 64, 64, 1);
710 mask_gc
= gdk_gc_new(launch_mask
);
712 gdk_gc_set_foreground(mask_gc
, &bg_color
);
713 gdk_draw_rectangle(launch_mask
, mask_gc
, TRUE
, 0, 0, -1, -1);
716 icon_name
= g_strdup_printf("%s/wmauda.xpm", DATA_DIR
);
717 pixmap
= gdk_pixmap_create_from_xpm(icon_win
->window
, &mask
,
721 printf("ERROR: Couldn't find %s\n", icon_name
);
726 gdk_window_get_size(pixmap
, &w
, &h
);
731 gdk_draw_pixmap(launch_pixmap
, dock_gc
, pixmap
,
732 0, 0, 32 - (w
/ 2), 32 - (h
/ 2), w
, h
);
733 gdk_draw_pixmap(launch_mask
, mask_gc
, mask
,
734 0, 0, 32 - (w
/ 2), 32 - (h
/ 2), w
, h
);
735 gdk_gc_unref(mask_gc
);
736 gdk_pixmap_unref(pixmap
);
737 gdk_bitmap_unref(mask
);
739 gtk_widget_shape_combine_mask(icon_win
, launch_mask
, 0, 0);
741 pixmap
= gdk_pixmap_create_from_xpm_d(icon_win
->window
,
742 &mask
, NULL
, dock_master_xpm
);
746 attr
.title
= "wmauda";
747 attr
.event_mask
= GDK_BUTTON_PRESS_MASK
| GDK_ENTER_NOTIFY_MASK
| GDK_LEAVE_NOTIFY_MASK
| GDK_POINTER_MOTION_HINT_MASK
;
748 attr
.wclass
= GDK_INPUT_OUTPUT
;
749 attr
.visual
= gdk_visual_get_system();
750 attr
.colormap
= gdk_colormap_get_system();
751 attr
.wmclass_name
= "wmauda";
752 attr
.wmclass_class
= "wmauda";
753 attr
.window_type
= GDK_WINDOW_TOPLEVEL
;
755 leader
= gdk_window_new(NULL
, &attr
, GDK_WA_TITLE
| GDK_WA_WMCLASS
| GDK_WA_VISUAL
| GDK_WA_COLORMAP
);
757 gdk_window_set_icon(leader
, icon_win
->window
, NULL
, NULL
);
758 gdk_window_reparent(icon_win
->window
, leader
, 0, 0);
759 gdk_window_show(leader
);
761 hints
.initial_state
= WithdrawnState
;
762 hints
.flags
= StateHint
| IconWindowHint
| IconPositionHint
| WindowGroupHint
;
763 hints
.icon_window
= GDK_WINDOW_XWINDOW(icon_win
->window
);
766 hints
.window_group
= GDK_WINDOW_XWINDOW(leader
);
768 XSetWMHints(GDK_DISPLAY(), GDK_WINDOW_XWINDOW(leader
), &hints
);
770 gtk_widget_show(icon_win
);
771 timeout_tag
= gtk_timeout_add(100, timeout_func
, NULL
);
775 void display_usage(char *cmd
)
777 printf( "Usage: %s [options]\n\n"
780 "-h, --help Display this text and exit.\n"
781 "-g, --geometry Set the geometry (for example +20+20)\n"
782 "-s, --session Set the Audacious session to use (Default: 0)\n"
783 "-c, --command Command to launch Audacious (Default: audacious)\n"
784 "-i, --icon Set the icon to use when Audacious is not running\n"
785 "-n, --single Only a single click is needed to start Audacious\n"
786 "-t, --title Display song title when mouse is in window\n"
787 "-v, --version Display version information and exit\n\n",
791 int main(int argc
, char **argv
)
795 static struct option lopt
[] =
797 {"help", no_argument
, 0, 'h'},
798 {"geometry", required_argument
, 0, 'g'},
799 {"session", required_argument
, 0, 's'},
800 {"command", required_argument
, 0, 'c'},
801 {"icon", required_argument
, 0, 'i'},
802 {"single", no_argument
, 0, 'n'},
803 {"title", no_argument
, 0, 't'},
804 {"version", no_argument
, 0, 'v'},
810 gtk_init(&argc
, &argv
);
812 while ((c
= getopt_long(argc
, argv
, "hg:s:c:i:ntv", lopt
, NULL
)) != -1)
817 display_usage(argv
[0]);
821 XParseGeometry(optarg
, &win_x
, &win_y
,
826 xmms_session
= atoi(optarg
);
829 xmms_cmd
= g_strdup(optarg
);
832 icon_name
= g_strdup(optarg
);
841 printf("wmauda %s\n", VERSION
);