/* Analog VU meter plugin for xmms * * Copyright (C) 2002 Pekka Harjamäki * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include #include #include #include #include #include #include #include #include #include #include #include #include "vumeter.h" #include "mini_icon.xpm" #include "../config.h" /************************************************ External functions & Variables *************************************************/ extern GtkWidget *vumeter_config_win,*vumeter_about_win; // Defined in vumeter_dialog.c extern GtkWidget *mainwin, // This is xmms main window *playlistwin, // Xmms playlist window *equalizerwin, // Xmms eq window *clist_skinlist; // List of all the vumeter skins found from system extern GList *dock_window_list; // xmms dockwinlist /************************************************ Initialize variables, etc *************************************************/ VisPlugin *get_vplugin_info(void); // Required by xmms // Idea for docklist support from Dual Scope plugin by Joakim Elofsson extern GList *dock_add_window(GList *, GtkWidget *); extern gboolean dock_is_moving(GtkWidget *); extern void dock_move_motion(GtkWidget *,GdkEventMotion *); extern void dock_move_press(GList *, GtkWidget *, GdkEventButton *, gboolean); extern void dock_move_release(GtkWidget *); // function initialization static void vumeter_init(void); static void vumeter_cleanup(void); static void vumeter_loadconfig(void); static void vumeter_writeconfig(void); static void vumeter_render(gint16 data[2][512]); static void vumeter_pause(void); static void vumeter_play(void); static void win_press(GtkWidget * widget, GdkEventButton * event, gpointer callback_data); static void win_release(GtkWidget * widget, GdkEventButton * event, gpointer callback_data); static void win_motion(GtkWidget * widget, GdkEventMotion * event, gpointer callback_data); static gint expose_cb (GtkWidget *widget, GdkEventExpose *event, gpointer data); static void win_focus_in(GtkWidget * widget, GdkEvent * event, gpointer callback_data); static void win_focus_out(GtkWidget * widget, GdkEvent * event, gpointer callback_data); static gint vumeter_stop_timer( gpointer data ); GdkGC *linestyle; // Style for drawing line GtkWidget *vumeterwin,*area=NULL; // Main win and draw area GdkPixmap *doublebuf; // Used for doublebuffering static gint timer=0, win_move=0, win_move_y=0, win_move_x=0, delay_counter=0,cleanup_done=0, win_x_pos=0,win_y_pos=0; static pthread_t worker_thread; static pthread_attr_t worker_attr; pthread_mutex_t pcm_data_lock; // These are shared between source files GdkPixbuf *background,*titlebar_on,*titlebar_off, *skin_pic,*overlayimg,*led_on_img, *led_off_img; gint16 shared_pcm_data[2][512],worker_running; float left_needle_power[max_avarage_samples], right_needle_power[max_avarage_samples]; struct vumeter_skin_info vumeter_skin; struct vumeter_cfg_info vumeter_cfg; // Visplug structure for xmms VisPlugin vumeter_vp = { NULL, NULL, 0, PACKAGE_STRING, 2, /* pcm channels */ 0, /* Freq channels */ vumeter_init, /* init */ vumeter_cleanup, /* cleanup */ vumeter_about, /* about */ vumeter_config, /* configure */ NULL, /* disable_plugin */ vumeter_play, /* playback_start */ vumeter_pause, /* playback_stop */ vumeter_render, /* render_pcm */ NULL /* render_freq */ }; /************************************************ Initialization function and icon function *************************************************/ gint vumeter_error_timer( gpointer data ) { vumeter_vp.disable_plugin(&vumeter_vp); return(0); } // If we call this during vumeter_init, we can't // call disable_plugin from here, so instead we use // timeout void vumeter_error_handler(char *message) { printf("[VUmeter plugin error]: %s\n",message); printf("[VUmeter plugin error]: Disabling plugin...\n"); gtk_timeout_add(300,vumeter_error_timer,NULL); return; } // Function borrowed from Dual scope plugin void vumeter_set_icon(void) { static GdkPixmap *icon; static GdkBitmap *mask; Atom icon_atom; glong data[2]; if (!icon) icon = gdk_pixmap_create_from_xpm_d (vumeterwin->window, &mask,&vumeterwin->style->bg[GTK_STATE_NORMAL],mini_icon_xpm); data[0] = GDK_WINDOW_XWINDOW(icon); data[1] = GDK_WINDOW_XWINDOW(mask); icon_atom = gdk_atom_intern ("KWM_WIN_ICON", FALSE); gdk_property_change (vumeterwin->window, icon_atom, icon_atom, 32, GDK_PROP_MODE_REPLACE, (guchar *)data, 2); } void vumeter_init(void) { int loop; background= NULL; titlebar_on=NULL; titlebar_off=NULL; skin_pic=NULL; overlayimg=NULL; led_on_img=NULL; led_off_img=NULL; doublebuf=NULL; cleanup_done=0; worker_running=-1; vumeterwin=NULL; /* Do we have default skin somewhere. If it is not found, then we should quit right away, to avoid futher problems. */ if(vumeter_test_skin("default",NULL)==0) { vumeter_error_handler("Create skin directory, install default skin, and try again.."); return; } // Load config vumeter_loadconfig(); // Create main window vumeterwin = gtk_window_new(GTK_WINDOW_DIALOG); gtk_window_set_title(GTK_WINDOW(vumeterwin), PACKAGE_STRING); gtk_window_set_policy(GTK_WINDOW(vumeterwin), FALSE, FALSE, FALSE); gtk_widget_set_events(vumeterwin, GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_BUTTON_MOTION_MASK | GDK_FOCUS_CHANGE_MASK); gtk_widget_realize(vumeterwin); vumeter_set_icon(); gdk_window_set_decorations(vumeterwin->window, 0); gtk_widget_set_usize (vumeterwin, 275, 116); gtk_signal_connect (GTK_OBJECT (vumeterwin), "button_press_event",GTK_SIGNAL_FUNC(win_press),NULL); gtk_signal_connect (GTK_OBJECT (vumeterwin), "button_release_event", GTK_SIGNAL_FUNC(win_release), NULL); gtk_signal_connect (GTK_OBJECT (vumeterwin), "motion_notify_event", GTK_SIGNAL_FUNC(win_motion), NULL); gtk_signal_connect (GTK_OBJECT (vumeterwin), "focus_in_event", GTK_SIGNAL_FUNC(win_focus_in), NULL); gtk_signal_connect (GTK_OBJECT (vumeterwin), "focus_out_event", GTK_SIGNAL_FUNC(win_focus_out), NULL); gdk_window_clear(vumeterwin->window); // Create drawing area doublebuf=gdk_pixmap_new(vumeterwin->window,275,116,-1); if(doublebuf==NULL) { vumeter_error_handler("Unable to initialize doublebuffer (out of memory?)"); return; } area = gtk_drawing_area_new(); gtk_signal_connect (GTK_OBJECT (area), "expose_event", GTK_SIGNAL_FUNC (expose_cb), NULL); gtk_container_add(GTK_CONTAINER(vumeterwin), area); gtk_widget_realize(area); gdk_window_clear(area->window); /* Show window and all widgets within */ gtk_widget_show_all (vumeterwin); /* Set window position */ gdk_window_move(vumeterwin->window,win_x_pos,win_y_pos); win_move=FALSE; /* Create draw style for needles */ linestyle=gdk_gc_new(vumeterwin->window); /* Load skin (or atleast try to do so) */ if(vumeter_load_skin(vumeter_cfg.old_skin)==0) { vumeter_error_handler("Unable to load last skin to plugin. Even 'default' is missing (?)"); return; } /* Create worker thread */ worker_running=1; pthread_mutex_init(&pcm_data_lock, NULL); pthread_attr_init(&worker_attr); pthread_attr_setdetachstate(&worker_attr, PTHREAD_CREATE_JOINABLE); pthread_create(&worker_thread,&worker_attr,vumeter_worker, NULL); pthread_attr_destroy(&worker_attr); /* Clean up buffer */ for(loop=0; loop=0) ltmp=-1.0; if(ltmp=0) rtmp=-1.0; if(rtmp=1; c--) { left_needle_power[c]=left_needle_power[c-1]; right_needle_power[c]=right_needle_power[c-1]; } left_needle_power[0]=ltmp; right_needle_power[0]=rtmp; // renew callback if power > (db_min+3.0) units if(ltmp>(db_min+3.0) || rtmp>(db_min+3.0)) { timer=gtk_timeout_add(30,vumeter_stop_timer,NULL); } else { for(c=0; c=vumeter_cfg.frame_delay) { // Precalculate values angle_limit=(float)vumeter_skin.needle_max_angle-(float)vumeter_skin.needle_min_angle; ctemp=(M_PI/180.0); // Angle -> Radian conversion value // Calculate avarage value of all samples for(i=0; istyle->black_gc, // src,dst,gc 0,0,0,0, // src x,y ; dst x,y gdk_pixbuf_get_width(background),gdk_pixbuf_get_height(background), // src size GDK_RGB_DITHER_NONE,0,0); // No dither today darling if(vumeter_skin.analogvu_enabled) { // Draw lines to doublebuf gdk_draw_line(doublebuf,linestyle,vumeter_skin.left_x,vumeter_skin.left_y,xtmp,ytmp); gdk_draw_line(doublebuf,linestyle,vumeter_skin.right_x,vumeter_skin.right_y,xtmp_2,ytmp_2); // Draw mirrored lines to doublebuf if(vumeter_skin.left_mirror!=0) gdk_draw_line(doublebuf,linestyle,vumeter_skin.left_x,vumeter_skin.left_y,xtmp_m,ytmp_m); if(vumeter_skin.right_mirror!=0) gdk_draw_line(doublebuf,linestyle,vumeter_skin.right_x,vumeter_skin.right_y,xtmp_2_m,ytmp_2_m); } // Draw clipping leds (if enabled) if(vumeter_skin.clipping_led) if(led_on_img!=NULL && led_off_img!=NULL) { if(lpower>(float)vumeter_cfg.sensitivity) { gdk_pixbuf_render_to_drawable_alpha (led_on_img,doublebuf,0,0,vumeter_skin.led_left_x,vumeter_skin.led_left_y,gdk_pixbuf_get_width(led_on_img),gdk_pixbuf_get_height(led_on_img),GDK_PIXBUF_ALPHA_BILEVEL,100,GDK_RGB_DITHER_NONE,0,0); } else { gdk_pixbuf_render_to_drawable_alpha (led_off_img,doublebuf,0,0,vumeter_skin.led_left_x,vumeter_skin.led_left_y,gdk_pixbuf_get_width(led_off_img),gdk_pixbuf_get_height(led_off_img),GDK_PIXBUF_ALPHA_BILEVEL,100,GDK_RGB_DITHER_NONE,0,0); } if(rpower>(float)vumeter_cfg.sensitivity) { gdk_pixbuf_render_to_drawable_alpha (led_on_img,doublebuf,0,0,vumeter_skin.led_right_x,vumeter_skin.led_right_y,gdk_pixbuf_get_width(led_on_img),gdk_pixbuf_get_height(led_on_img),GDK_PIXBUF_ALPHA_BILEVEL,100,GDK_RGB_DITHER_NONE,0,0); } else { gdk_pixbuf_render_to_drawable_alpha (led_off_img,doublebuf,0,0,vumeter_skin.led_right_x,vumeter_skin.led_right_y,gdk_pixbuf_get_width(led_off_img),gdk_pixbuf_get_height(led_off_img),GDK_PIXBUF_ALPHA_BILEVEL,100,GDK_RGB_DITHER_NONE,0,0); } } // Draw overlay image if(vumeter_skin.overlay_enabled) if(overlayimg!=NULL) { gdk_pixbuf_render_to_drawable_alpha (overlayimg, // Source doublebuf, // Target 0,0, // SRC position vumeter_skin.overlay_x,vumeter_skin.overlay_y, // DST position gdk_pixbuf_get_width(overlayimg),gdk_pixbuf_get_height(overlayimg), // SIZE GDK_PIXBUF_ALPHA_BILEVEL,100, // Alphamode 100% GDK_RGB_DITHER_NONE,0,0); // No dithering } // Draw doublebuf to screen gdk_draw_pixmap( widget->window,widget->style->black_gc, doublebuf,0,0,0,0,-1,-1); delay_counter=0; } return(1); } /**************************************************************************** Functions for window events *****************************************************************************/ static void win_release(GtkWidget * widget, GdkEventButton * event, gpointer callback_data) { if (event->type == GDK_BUTTON_RELEASE && event->button == 1) if (dock_is_moving(vumeterwin)) dock_move_release(vumeterwin); } static void win_press(GtkWidget * widget, GdkEventButton * event, gpointer callback_data) { win_move_x=event->x; win_move_y=event->y; /* Handle exitbutton press */ if (event->button == 1 && event->type == GDK_BUTTON_PRESS && event->x >= vumeter_skin.exit_x1 && event->x <= vumeter_skin.exit_x2 && event->y >= vumeter_skin.exit_y1 && event->y <= vumeter_skin.exit_y2) { vumeter_vp.disable_plugin(&vumeter_vp); // Start cleanup process return; // Continuing would cause segfault } /* Handle configbutton press */ if (event->button == 1 && event->type == GDK_BUTTON_PRESS && event->x >= vumeter_skin.config_x1 && event->x <= vumeter_skin.config_x2 && event->y >= vumeter_skin.config_y1 && event->y <= vumeter_skin.config_y2) { vumeter_config(); return; } /* Handle window drag event */ if (event->type == GDK_BUTTON_PRESS && event->button == 1) if(event->y<=vumeter_skin.titlebar_ymax) dock_move_press(dock_window_list, vumeterwin, event, FALSE); } static void win_motion(GtkWidget * widget, GdkEventMotion * event, gpointer callback_data) { if (dock_is_moving(vumeterwin)) dock_move_motion(vumeterwin, event); } static void win_focus_in(GtkWidget * widget, GdkEvent * event, gpointer callback_data) { gdk_pixbuf_copy_area( titlebar_on,0,0,vumeter_skin.width,gdk_pixbuf_get_height(titlebar_on), background,0,0); } static void win_focus_out(GtkWidget * widget, GdkEvent * event, gpointer callback_data) { gdk_pixbuf_copy_area( titlebar_off,0,0,vumeter_skin.width,gdk_pixbuf_get_height(titlebar_off), background,0,0); } /**************************************************************************** These are only for exiting, etc *****************************************************************************/ void vumeter_cleanup(void) { if(cleanup_done==0) { // Make sure, that we will not come here again cleanup_done=1; if(worker_running!=-1) { // Give signal to thread, that it is time to quit worker_running=0; // Give some time for thread to exit xmms_usleep(20000); // Delete mutex pthread_mutex_destroy(&pcm_data_lock); // Write config and remove countdown timer if (vumeterwin!=NULL) vumeter_writeconfig(); if(timer!=0) { gtk_timeout_remove(timer); timer=0; } } // Remove window from docklist if (vumeterwin!=NULL) { if (g_list_find(dock_window_list, vumeterwin)) g_list_remove(dock_window_list, vumeterwin); } // Release images from memory, just to be sure if(skin_pic!=NULL) { gdk_pixbuf_unref(skin_pic); skin_pic=NULL; } if(titlebar_on!=NULL) { gdk_pixbuf_unref(titlebar_on); titlebar_on=NULL; } if(titlebar_off!=NULL) { gdk_pixbuf_unref(titlebar_off); titlebar_off=NULL; } if(overlayimg!=NULL) { gdk_pixbuf_unref(overlayimg); overlayimg=NULL; } if(led_on_img!=NULL) { gdk_pixbuf_unref(led_on_img); led_on_img=NULL; } if(led_off_img!=NULL) { gdk_pixbuf_unref(led_off_img); led_off_img=NULL; } if(doublebuf!=NULL) { gdk_pixmap_unref(doublebuf); doublebuf=NULL; } // Destroy all windows (and bill gates while you're at it).. if (vumeter_about_win!=NULL) { gtk_widget_destroy(vumeter_about_win); vumeter_about_win=NULL; } if (vumeter_config_win!=NULL) { gtk_widget_destroy(vumeter_config_win); vumeter_config_win=NULL; } if (vumeterwin!=NULL) { gtk_widget_destroy(vumeterwin); vumeterwin=NULL; } } } /**************************************************************************** Load/Save config, render, return info functions *****************************************************************************/ static void vumeter_loadconfig(void) { ConfigFile *myconfig; gchar *fn=NULL,*s=NULL; fn = g_strconcat(g_get_home_dir(), "/.xmms/config", NULL); myconfig = xmms_cfg_open_file(fn); // Set defaults vumeter_cfg.av_samples=max_avarage_samples/2; vumeter_cfg.frame_delay=0; vumeter_cfg.sensitivity=-3; vumeter_cfg.win_width=275; vumeter_cfg.win_height=116; strcpy(vumeter_cfg.old_skin,"default"); if(myconfig) { xmms_cfg_read_int(myconfig, "analog_vumeter", "window_pos_x", &win_x_pos); xmms_cfg_read_int(myconfig, "analog_vumeter", "window_pos_y", &win_y_pos); xmms_cfg_read_int(myconfig, "analog_vumeter", "av_samples",&vumeter_cfg.av_samples); xmms_cfg_read_int(myconfig, "analog_vumeter", "frame_delay",&vumeter_cfg.frame_delay); xmms_cfg_read_int(myconfig, "analog_vumeter", "led_sensitivity",&vumeter_cfg.sensitivity); xmms_cfg_read_string(myconfig, "analog_vumeter", "old_skin",&s); if(s) { strcpy(vumeter_cfg.old_skin,s); g_free(s); } xmms_cfg_free(myconfig); } g_free(fn); /* Try to make sure, that values are correct */ if(vumeter_cfg.sensitivity>0) vumeter_cfg.sensitivity=0; if(vumeter_cfg.sensitivity<-10) vumeter_cfg.sensitivity=-10; if(vumeter_cfg.av_samples<1) vumeter_cfg.av_samples=1; if(vumeter_cfg.av_samples>max_avarage_samples) vumeter_cfg.av_samples=max_avarage_samples; } static void vumeter_writeconfig(void) { ConfigFile *myconfig; gchar *fn; gint x_pos=-1,y_pos=-1; fn = g_strconcat(g_get_home_dir(), "/.xmms/config", NULL); myconfig = xmms_cfg_open_file(fn); if(!myconfig) myconfig= xmms_cfg_new(); if(myconfig) { gdk_window_get_position(vumeterwin->window, &x_pos, &y_pos); xmms_cfg_write_int(myconfig, "analog_vumeter", "window_pos_x", x_pos); xmms_cfg_write_int(myconfig, "analog_vumeter", "window_pos_y", y_pos); xmms_cfg_write_int(myconfig, "analog_vumeter", "av_samples", vumeter_cfg.av_samples); xmms_cfg_write_int(myconfig, "analog_vumeter", "frame_delay", vumeter_cfg.frame_delay); xmms_cfg_write_int(myconfig, "analog_vumeter", "led_sensitivity", vumeter_cfg.sensitivity); xmms_cfg_write_string(myconfig, "analog_vumeter", "old_skin", vumeter_skin.name); xmms_cfg_write_file(myconfig, fn); xmms_cfg_free(myconfig); } g_free(fn); } static void vumeter_render(gint16 data[2][512]) { if(worker_running!=1) return; // New data for processing pthread_mutex_lock(&pcm_data_lock); memcpy(shared_pcm_data, data, sizeof(gint16) * 2 * 512); worker_running=2; pthread_mutex_unlock(&pcm_data_lock); } /**************************************************************************** One line miracles *****************************************************************************/ VisPlugin *get_vplugin_info(void) { return &vumeter_vp; } void vumeter_pause(void) { if(doublebuf!=NULL) timer=gtk_timeout_add(30,vumeter_stop_timer,NULL); } void vumeter_play(void) { if(timer!=0) gtk_timeout_remove(timer); }