pidgin/purple-plugin-pack
Clone
Summary
Browse
Changes
Graph
Filling out the structure for further plugin additions here. Hopefully
org.guifications.plugins.smartear
2007-07-28, rekkanoryo
5ce77e47e23b
Filling out the structure for further plugin additions here. Hopefully
before too much longer I'll have real code here.
/* SmartEar by Matt Perry
* Works for purple 2.0.0
* Plugin to assign different sounds to different buddies and groups.
* TODO: figure out contact support
*/
#ifdef HAVE_CONFIG_H
#
include
"../pp_config.h"
#endif
#include
"../common/i18n.h"
#include
"pidgin.h"
#include
"gtkplugin.h"
#include
"gtkutils.h"
#include
"version.h"
#include
"debug.h"
#include
"util.h"
#include
"sound.h"
#include
"gtkprefs.h"
#include
"gtkblist.h"
#include
"signals.h"
#ifndef PURPLE_PLUGINS
#define PURPLE_PLUGINS
#endif
#include
<stdio.h>
#include
<stdlib.h>
#include
<string.h>
#include
<time.h>
#ifdef _WIN32
#include
"win32dep.h"
#endif
#define BUF_LEN 2048
/* lifted from libpurple's internal.h */
#define RCFILE_VERSION 2
#define RC_EMPTY_SOUND " "
/* must be at least 1 char long for sscanf */
#define SMARTEAR_PLUGIN_ID "smartear"
#define SMARTEAR_VERSION VERSION ".1"
#define FIND(s) lookup_widget(config, s)
#define EFIND(s) lookup_widget(edit_win, s)
/* an entry in the prefs file to indicate what to play for who and when. */
struct
smartear_entry
{
char
type
;
/* B,G */
char
*
name
;
#define EVENT_M 0
#define EVENT_S 1
#define EVENT_I 2
#define EVENT_A 3
#define EVENT_NUM 4
char
*
sound
[
EVENT_NUM
];
};
struct
timer_data
{
int
event
;
guint
id
;
};
struct
message_data
{
PurpleAccount
*
account
;
char
*
buddy
;
time_t
last_active
;
time_t
last_sent
;
struct
timer_data
*
timer
;
};
enum
{
/* Treeview columns */
DATA_COL
=
0
,
TYPE_COL
=
0
,
NAME_COL
,
ON_MESSAGE_COL
,
ON_SIGNON_COL
,
ON_UNIDLE_COL
,
ON_UNAWAY_COL
,
NUM_COLS
};
#define DEFAULT_NAME "(Default)"
#define IS_DEFAULT(entry) (strcmp(entry->name, DEFAULT_NAME)==0)
#define IS_EMPTY(str) (!str || !str[0])
struct
smartear_entry
default_entry
=
{
'B'
,
DEFAULT_NAME
,
{
""
,
""
,
""
,
""
}};
int
message_delay
=
60
;
int
focused_quiet
=
TRUE
;
int
smartear_timers
=
FALSE
;
GtkWidget
*
config
=
NULL
;
GtkWidget
*
edit_win
=
NULL
;
GtkWidget
*
file_browse
=
NULL
;
GtkWidget
*
file_browse_entry
=
NULL
;
GtkTreeView
*
treeview
=
NULL
;
GtkTreeSelection
*
treeselect
=
NULL
;
GtkTreeModel
*
treemodel
=
NULL
;
struct
smartear_entry
*
selected_entry
=
NULL
;
GtkTreeIter
selected_iter
;
GSList
*
sounds_list
=
NULL
;
GSList
*
messages_list
=
NULL
;
GtkWidget
*
create_file_browse
(
void
);
GtkWidget
*
create_edit_win
(
void
);
GtkWidget
*
create_config
(
void
);
static
void
new_entry
(
struct
smartear_entry
*
entry
);
static
gboolean
get_selection
(
void
);
static
void
clear_selection
(
void
);
static
void
populate_edit_win
(
struct
smartear_entry
*
entry
);
static
void
update_list
(
void
);
static
void
populate_list
(
GtkListStore
*
store
);
static
void
list_set
(
GtkListStore
*
store
,
GtkTreeIter
*
iter
,
struct
smartear_entry
*
entry
);
static
void
set_option_entries
(
void
);
static
gint
sound_cmp
(
gconstpointer
p1
,
gconstpointer
p2
);
static
struct
smartear_entry
*
sound_new_with_name
(
gchar
*
name
);
static
struct
smartear_entry
*
sound_dup
(
struct
smartear_entry
*
entry
);
static
void
sound_free
(
struct
smartear_entry
*
entry
,
int
free_entry
);
static
void
soundlist_free
(
void
);
static
gchar
*
get_basename
(
gchar
*
path
);
static
void
on_treeselect_changed
(
GtkTreeSelection
*
selection
,
gpointer
data
);
static
void
smartear_save
(
void
);
static
void
smartear_load
(
void
);
static
void
play_sound_alias
(
char
*
sound
,
PurpleAccount
*
account
);
/*** Glade's Support Function ***/
GtkWidget
*
lookup_widget
(
GtkWidget
*
widget
,
const
gchar
*
widget_name
)
{
GtkWidget
*
found_widget
;
found_widget
=
(
GtkWidget
*
)
g_object_get_data
(
G_OBJECT
(
widget
),
widget_name
);
if
(
!
found_widget
)
g_warning
(
"smartear: Widget not found: %s"
,
widget_name
);
return
found_widget
;
}
/*** GTK Callbacks ***/
/* Options Frame */
void
on_delay_changed
(
GtkEditable
*
editable
,
gpointer
user_data
)
{
gchar
*
text
=
gtk_editable_get_chars
(
editable
,
0
,
-1
);
message_delay
=
atoi
(
text
);
g_free
(
text
);
}
void
on_focus_toggled
(
GtkToggleButton
*
togglebutton
,
gpointer
user_data
)
{
focused_quiet
=
gtk_toggle_button_get_active
(
togglebutton
);
}
void
on_timer_toggled
(
GtkToggleButton
*
togglebutton
,
gpointer
user_data
)
{
smartear_timers
=
gtk_toggle_button_get_active
(
togglebutton
);
}
/* Entries Frame */
void
on_cell_edited
(
GtkCellRendererText
*
cell
,
gchar
*
path
,
gchar
*
text
,
gpointer
data
)
{
GtkTreeIter
iter
;
gint
col
=
GPOINTER_TO_INT
(
data
);
struct
smartear_entry
*
entry
;
if
(
!
gtk_tree_model_get_iter_from_string
(
treemodel
,
&
iter
,
path
))
return
;
gtk_tree_model_get
(
treemodel
,
&
iter
,
DATA_COL
,
&
entry
,
-1
);
switch
(
col
)
{
case
NAME_COL
:
if
(
IS_DEFAULT
(
entry
))
return
;
g_free
(
entry
->
name
);
entry
->
name
=
g_strdup
(
text
);
update_list
();
break
;
}
}
void
on_treeselect_changed
(
GtkTreeSelection
*
selection
,
gpointer
data
)
{
get_selection
();
update_list
();
}
void
on_new_clicked
(
GtkButton
*
button
,
gpointer
user_data
)
{
if
(
edit_win
)
return
;
new_entry
(
sound_new_with_name
(
""
));
}
void
on_delete_clicked
(
GtkButton
*
button
,
gpointer
user_data
)
{
GtkTreeIter
iter
;
if
(
edit_win
)
return
;
if
(
!
get_selection
())
return
;
if
(
IS_DEFAULT
(
selected_entry
))
return
;
iter
=
selected_iter
;
sounds_list
=
g_slist_remove
(
sounds_list
,
selected_entry
);
sound_free
(
selected_entry
,
TRUE
);
if
(
!
gtk_tree_model_iter_next
(
treemodel
,
&
iter
))
{
GtkTreePath
*
path
=
gtk_tree_model_get_path
(
treemodel
,
&
selected_iter
);
gtk_tree_path_prev
(
path
);
gtk_tree_model_get_iter
(
treemodel
,
&
iter
,
path
);
gtk_tree_path_free
(
path
);
}
g_signal_handlers_block_by_func
(
G_OBJECT
(
treeselect
),
on_treeselect_changed
,
0
);
gtk_list_store_remove
(
GTK_LIST_STORE
(
treemodel
),
&
selected_iter
);
gtk_tree_selection_select_iter
(
treeselect
,
&
iter
);
g_signal_handlers_unblock_by_func
(
G_OBJECT
(
treeselect
),
on_treeselect_changed
,
0
);
get_selection
();
update_list
();
}
void
on_edit_clicked
(
GtkButton
*
button
,
gpointer
user_data
)
{
if
(
!
get_selection
())
return
;
if
(
edit_win
)
return
;
edit_win
=
create_edit_win
();
gtk_widget_show_all
(
edit_win
);
populate_edit_win
(
selected_entry
);
}
void
on_save_clicked
(
GtkButton
*
button
,
gpointer
user_data
)
{
smartear_save
();
}
void
on_revert_clicked
(
GtkButton
*
button
,
gpointer
user_data
)
{
selected_entry
=
NULL
;
if
(
edit_win
)
{
gtk_widget_destroy
(
edit_win
);
edit_win
=
NULL
;
}
soundlist_free
();
smartear_load
();
gtk_list_store_clear
(
GTK_LIST_STORE
(
treemodel
));
populate_list
(
GTK_LIST_STORE
(
treemodel
));
update_list
();
set_option_entries
();
}
/* Edit Window */
void
on_browse_ok_clicked
(
GtkButton
*
button
,
gpointer
user_data
)
{
gtk_entry_set_text
(
GTK_ENTRY
(
file_browse_entry
),
gtk_file_selection_get_filename
(
GTK_FILE_SELECTION
(
file_browse
)));
}
void
on_file_browse_destroy
(
GtkObject
*
object
,
gpointer
user_data
)
{
file_browse
=
NULL
;
file_browse_entry
=
NULL
;
}
void
on_browse_clicked
(
GtkButton
*
button
,
gchar
*
user_data
)
{
gchar
*
text
;
if
(
file_browse
)
return
;
file_browse
=
create_file_browse
();
gtk_widget_show
(
file_browse
);
file_browse_entry
=
EFIND
(
user_data
);
text
=
gtk_editable_get_chars
(
GTK_EDITABLE
(
file_browse_entry
),
0
,
-1
);
gtk_file_selection_set_filename
(
GTK_FILE_SELECTION
(
file_browse
),
text
);
g_free
(
text
);
}
void
on_im_browse_clicked
(
GtkButton
*
button
,
gpointer
user_data
)
{
on_browse_clicked
(
button
,
"im_sound_entry"
);
}
void
on_signon_browse_clicked
(
GtkButton
*
button
,
gpointer
user_data
)
{
on_browse_clicked
(
button
,
"signon_sound_entry"
);
}
void
on_unidle_browse_clicked
(
GtkButton
*
button
,
gpointer
user_data
)
{
on_browse_clicked
(
button
,
"unidle_sound_entry"
);
}
void
on_unaway_browse_clicked
(
GtkButton
*
button
,
gpointer
user_data
)
{
on_browse_clicked
(
button
,
"unaway_sound_entry"
);
}
void
on_test_clicked
(
GtkButton
*
button
,
gchar
*
user_data
)
{
gchar
*
text
=
gtk_editable_get_chars
(
GTK_EDITABLE
(
EFIND
(
user_data
)),
0
,
-1
);
play_sound_alias
(
text
,
NULL
);
g_free
(
text
);
}
void
on_im_test_clicked
(
GtkButton
*
button
,
gpointer
user_data
)
{
on_test_clicked
(
button
,
"im_sound_entry"
);
}
void
on_signon_test_clicked
(
GtkButton
*
button
,
gpointer
user_data
)
{
on_test_clicked
(
button
,
"signon_sound_entry"
);
}
void
on_unidle_test_clicked
(
GtkButton
*
button
,
gpointer
user_data
)
{
on_test_clicked
(
button
,
"unidle_sound_entry"
);
}
void
on_unaway_test_clicked
(
GtkButton
*
button
,
gpointer
user_data
)
{
on_test_clicked
(
button
,
"unaway_sound_entry"
);
}
void
on_apply_clicked
(
GtkButton
*
button
,
gpointer
user_data
)
{
struct
smartear_entry
*
entry
,
tmp
;
if
(
get_selection
())
{
entry
=
selected_entry
;
g_free
(
entry
->
name
);
g_free
(
entry
->
sound
[
0
]);
g_free
(
entry
->
sound
[
1
]);
g_free
(
entry
->
sound
[
2
]);
g_free
(
entry
->
sound
[
3
]);
}
else
{
entry
=
&
tmp
;
}
entry
->
name
=
gtk_editable_get_chars
(
GTK_EDITABLE
(
EFIND
(
"name_entry"
)),
0
,
-1
);
entry
->
type
=
gtk_option_menu_get_history
(
GTK_OPTION_MENU
(
EFIND
(
"type_option"
)))
==
0
?
'B'
:
'G'
;
entry
->
sound
[
EVENT_M
]
=
gtk_editable_get_chars
(
GTK_EDITABLE
(
EFIND
(
"im_sound_entry"
)),
0
,
-1
);
entry
->
sound
[
EVENT_S
]
=
gtk_editable_get_chars
(
GTK_EDITABLE
(
EFIND
(
"signon_sound_entry"
)),
0
,
-1
);
entry
->
sound
[
EVENT_I
]
=
gtk_editable_get_chars
(
GTK_EDITABLE
(
EFIND
(
"unidle_sound_entry"
)),
0
,
-1
);
entry
->
sound
[
EVENT_A
]
=
gtk_editable_get_chars
(
GTK_EDITABLE
(
EFIND
(
"unaway_sound_entry"
)),
0
,
-1
);
if
(
!
selected_entry
)
{
GSList
*
lp
;
if
((
lp
=
g_slist_find_custom
(
sounds_list
,
entry
,
(
GCompareFunc
)
sound_cmp
)))
{
sound_free
((
struct
smartear_entry
*
)
lp
->
data
,
FALSE
);
*
(
struct
smartear_entry
*
)
lp
->
data
=
*
entry
;
}
else
{
struct
smartear_entry
*
copy
=
g_new
(
struct
smartear_entry
,
1
);
*
copy
=
*
entry
;
new_entry
(
copy
);
}
}
update_list
();
}
void
on_edit_win_destroy
(
GtkObject
*
object
,
gpointer
user_data
)
{
edit_win
=
NULL
;
if
(
config
)
{
gtk_widget_set_sensitive
(
GTK_WIDGET
(
treeview
),
TRUE
);
}
}
void
on_config_destroy
(
GtkObject
*
object
,
gpointer
user_data
)
{
config
=
NULL
;
}
/*** Util Functions ***/
static
void
new_entry
(
struct
smartear_entry
*
entry
)
{
GtkTreeIter
iter
;
GtkTreePath
*
path
;
GtkTreeViewColumn
*
name_column
;
sounds_list
=
g_slist_append
(
sounds_list
,
entry
);
if
(
!
config
)
return
;
if
(
get_selection
())
gtk_list_store_insert_after
(
GTK_LIST_STORE
(
treemodel
),
&
iter
,
&
selected_iter
);
else
gtk_list_store_append
(
GTK_LIST_STORE
(
treemodel
),
&
iter
);
path
=
gtk_tree_model_get_path
(
treemodel
,
&
iter
);
list_set
(
GTK_LIST_STORE
(
treemodel
),
&
iter
,
entry
);
g_signal_handlers_block_by_func
(
G_OBJECT
(
treeselect
),
on_treeselect_changed
,
0
);
path
=
gtk_tree_model_get_path
(
treemodel
,
&
iter
);
name_column
=
gtk_tree_view_get_column
(
treeview
,
NAME_COL
);
if
(
entry
->
name
&&
entry
->
name
[
0
])
gtk_tree_view_set_cursor
(
treeview
,
path
,
0
,
FALSE
);
else
{
gtk_tree_view_set_cursor
(
treeview
,
path
,
name_column
,
TRUE
);
}
gtk_tree_path_free
(
path
);
g_signal_handlers_unblock_by_func
(
G_OBJECT
(
treeselect
),
on_treeselect_changed
,
0
);
get_selection
();
}
static
gboolean
get_selection
(
void
)
{
selected_entry
=
NULL
;
if
(
!
config
)
return
FALSE
;
if
(
!
gtk_tree_selection_get_selected
(
treeselect
,
&
treemodel
,
&
selected_iter
))
return
FALSE
;
gtk_tree_model_get
(
treemodel
,
&
selected_iter
,
DATA_COL
,
&
selected_entry
,
-1
);
return
TRUE
;
}
void
clear_selection
(
void
)
{
selected_entry
=
NULL
;
if
(
!
config
)
return
;
if
(
gtk_tree_selection_get_selected
(
treeselect
,
&
treemodel
,
&
selected_iter
))
{
gtk_tree_selection_unselect_iter
(
treeselect
,
&
selected_iter
);
}
}
static
void
list_set
(
GtkListStore
*
store
,
GtkTreeIter
*
iter
,
struct
smartear_entry
*
entry
)
{
gtk_list_store_set
(
store
,
iter
,
DATA_COL
,
entry
,
-1
);
}
static
char
*
my_normalize
(
const
char
*
input
)
{
static
char
buf
[
BUF_LEN
];
char
*
str
,
*
beg
;
int
i
=
0
;
g_return_val_if_fail
((
input
!=
NULL
),
NULL
);
beg
=
str
=
g_strdup
(
input
);
while
(
*
str
&&
i
<=
BUF_LEN
)
{
if
(
*
str
!=
' '
)
buf
[
i
++
]
=
*
str
;
str
++
;
}
buf
[
i
]
=
'\0'
;
g_free
(
beg
);
return
buf
;
}
static
struct
smartear_entry
*
sound_new_with_name
(
gchar
*
name
)
{
struct
smartear_entry
tmp
=
default_entry
;
tmp
.
name
=
name
;
return
sound_dup
(
&
tmp
);
}
static
struct
smartear_entry
*
sound_dup
(
struct
smartear_entry
*
entry
)
{
struct
smartear_entry
*
edup
=
g_new
(
struct
smartear_entry
,
1
);
int
i
;
edup
->
type
=
entry
->
type
;
edup
->
name
=
g_strdup
(
my_normalize
(
entry
->
name
));
for
(
i
=
0
;
i
<
EVENT_NUM
;
i
++
)
{
edup
->
sound
[
i
]
=
g_strdup
(
entry
->
sound
[
i
]);
}
return
edup
;
}
static
void
sound_free
(
struct
smartear_entry
*
entry
,
int
free_entry
)
{
g_free
(
entry
->
name
);
g_free
(
entry
->
sound
[
0
]);
g_free
(
entry
->
sound
[
1
]);
g_free
(
entry
->
sound
[
2
]);
g_free
(
entry
->
sound
[
3
]);
if
(
free_entry
)
{
g_free
(
entry
);
}
}
static
void
soundlist_free
(
void
)
{
GSList
*
lp
;
for
(
lp
=
sounds_list
;
lp
;
lp
=
g_slist_next
(
lp
))
sound_free
((
struct
smartear_entry
*
)
lp
->
data
,
TRUE
);
if
(
sounds_list
)
{
g_slist_free
(
sounds_list
);
sounds_list
=
0
;
}
}
static
gint
sound_cmp
(
gconstpointer
p1
,
gconstpointer
p2
)
{
struct
smartear_entry
*
entry1
=
(
struct
smartear_entry
*
)
p1
;
struct
smartear_entry
*
entry2
=
(
struct
smartear_entry
*
)
p2
;
if
(
!
entry1
||
IS_DEFAULT
(
entry1
))
return
-1
;
if
(
!
entry2
||
IS_DEFAULT
(
entry2
))
return
1
;
/* only compare types if both are nonzero */
if
(
entry1
->
type
!=
entry2
->
type
&&
entry1
->
type
!=
0
&&
entry2
->
type
!=
0
)
return
(
entry1
->
type
-
entry2
->
type
);
return
(
g_strcasecmp
(
entry1
->
name
,
entry2
->
name
));
}
static
void
populate_edit_win
(
struct
smartear_entry
*
entry
)
{
gtk_entry_set_text
(
GTK_ENTRY
(
EFIND
(
"name_entry"
)),
entry
->
name
);
gtk_entry_set_text
(
GTK_ENTRY
(
EFIND
(
"im_sound_entry"
)),
entry
->
sound
[
EVENT_M
]);
gtk_entry_set_text
(
GTK_ENTRY
(
EFIND
(
"signon_sound_entry"
)),
entry
->
sound
[
EVENT_S
]);
gtk_entry_set_text
(
GTK_ENTRY
(
EFIND
(
"unidle_sound_entry"
)),
entry
->
sound
[
EVENT_I
]);
gtk_entry_set_text
(
GTK_ENTRY
(
EFIND
(
"unaway_sound_entry"
)),
entry
->
sound
[
EVENT_A
]);
gtk_option_menu_set_history
(
GTK_OPTION_MENU
(
EFIND
(
"type_option"
)),
(
entry
->
type
==
'B'
?
0
:
1
));
if
(
IS_DEFAULT
(
entry
))
{
gtk_widget_set_sensitive
(
EFIND
(
"name_entry"
),
FALSE
);
gtk_widget_set_sensitive
(
EFIND
(
"type_menu"
),
FALSE
);
}
if
(
config
)
{
gtk_widget_set_sensitive
(
GTK_WIDGET
(
treeview
),
FALSE
);
}
}
static
void
set_option_entries
(
void
)
{
GtkSpinButton
*
delay_spin
=
GTK_SPIN_BUTTON
(
FIND
(
"delay_spin"
));
GtkToggleButton
*
focus_but
=
GTK_TOGGLE_BUTTON
(
FIND
(
"focus_but"
));
GtkToggleButton
*
timer_but
=
GTK_TOGGLE_BUTTON
(
FIND
(
"timer_but"
));
g_signal_handlers_block_by_func
(
G_OBJECT
(
delay_spin
),
on_delay_changed
,
0
);
g_signal_handlers_block_by_func
(
G_OBJECT
(
focus_but
),
on_focus_toggled
,
0
);
g_signal_handlers_block_by_func
(
G_OBJECT
(
timer_but
),
on_timer_toggled
,
0
);
gtk_spin_button_set_value
(
delay_spin
,
(
gdouble
)
message_delay
);
gtk_toggle_button_set_active
(
focus_but
,
focused_quiet
);
gtk_toggle_button_set_active
(
timer_but
,
smartear_timers
);
g_signal_handlers_unblock_by_func
(
G_OBJECT
(
delay_spin
),
on_delay_changed
,
0
);
g_signal_handlers_unblock_by_func
(
G_OBJECT
(
focus_but
),
on_focus_toggled
,
0
);
g_signal_handlers_unblock_by_func
(
G_OBJECT
(
timer_but
),
on_timer_toggled
,
0
);
}
static
void
update_list
(
void
)
{
if
(
selected_entry
)
list_set
(
GTK_LIST_STORE
(
treemodel
),
&
selected_iter
,
selected_entry
);
}
static
gchar
*
get_basename
(
gchar
*
path
)
{
static
gchar
base
[
BUF_LEN
];
gchar
*
p
;
if
(
!
path
||
!
path
[
0
])
{
return
""
;
}
if
((
p
=
strrchr
(
path
,
'/'
)))
{
p
++
;
}
else
{
p
=
path
;
}
strncpy
(
base
,
p
,
sizeof
(
base
));
return
base
;
}
/*** Init Functions ***/
static
void
populate_list
(
GtkListStore
*
store
)
{
GtkTreeIter
iter
;
GSList
*
lp
;
sounds_list
=
g_slist_sort
(
sounds_list
,
(
GCompareFunc
)
sound_cmp
);
for
(
lp
=
sounds_list
;
lp
;
lp
=
g_slist_next
(
lp
))
{
struct
smartear_entry
*
entry
=
(
struct
smartear_entry
*
)
lp
->
data
;
gtk_list_store_append
(
store
,
&
iter
);
list_set
(
store
,
&
iter
,
entry
);
if
(
entry
==
selected_entry
)
selected_iter
=
iter
;
}
}
static
void
render_type
(
GtkTreeViewColumn
*
column
,
GtkCellRenderer
*
cell
,
GtkTreeModel
*
model
,
GtkTreeIter
*
iter
,
gpointer
data
)
{
struct
smartear_entry
*
entry
;
gtk_tree_model_get
(
model
,
iter
,
DATA_COL
,
&
entry
,
-1
);
if
(
IS_DEFAULT
(
entry
))
g_object_set
(
cell
,
"text"
,
""
,
NULL
);
else
g_object_set
(
cell
,
"text"
,
entry
->
type
==
'B'
?
"Buddy"
:
"Group"
,
NULL
);
}
static
void
render_name
(
GtkTreeViewColumn
*
column
,
GtkCellRenderer
*
cell
,
GtkTreeModel
*
model
,
GtkTreeIter
*
iter
,
gpointer
data
)
{
struct
smartear_entry
*
entry
;
gtk_tree_model_get
(
model
,
iter
,
DATA_COL
,
&
entry
,
-1
);
g_object_set
(
cell
,
"text"
,
entry
->
name
,
NULL
);
}
static
void
render_when
(
GtkTreeViewColumn
*
column
,
GtkCellRenderer
*
cell
,
GtkTreeModel
*
model
,
GtkTreeIter
*
iter
,
gpointer
data
)
{
struct
smartear_entry
*
entry
;
gint
which
=
GPOINTER_TO_INT
(
data
);
gtk_tree_model_get
(
model
,
iter
,
DATA_COL
,
&
entry
,
-1
);
g_object_set
(
cell
,
"text"
,
get_basename
(
entry
->
sound
[
which
]),
NULL
);
}
static
gint
list_cmp
(
GtkTreeModel
*
model
,
GtkTreeIter
*
a
,
GtkTreeIter
*
b
,
gpointer
d
)
{
struct
smartear_entry
*
entry1
,
*
entry2
;
gtk_tree_model_get
(
model
,
a
,
DATA_COL
,
&
entry1
,
-1
);
gtk_tree_model_get
(
model
,
b
,
DATA_COL
,
&
entry2
,
-1
);
return
sound_cmp
(
entry1
,
entry2
);
}
static
void
setup_list
(
void
)
{
GtkListStore
*
store
;
GtkCellRenderer
*
cell
;
treeview
=
GTK_TREE_VIEW
(
FIND
(
"treeview"
));
store
=
gtk_list_store_new
(
1
,
G_TYPE_POINTER
);
populate_list
(
store
);
gtk_tree_view_set_model
(
treeview
,
GTK_TREE_MODEL
(
store
));
g_object_unref
(
G_OBJECT
(
store
));
// treeview has it's own copy.
treemodel
=
gtk_tree_view_get_model
(
treeview
);
gtk_tree_sortable_set_default_sort_func
(
GTK_TREE_SORTABLE
(
treemodel
),
(
GtkTreeIterCompareFunc
)
&
list_cmp
,
0
,
0
);
gtk_tree_sortable_set_sort_column_id
(
GTK_TREE_SORTABLE
(
treemodel
),
GTK_TREE_SORTABLE_DEFAULT_SORT_COLUMN_ID
,
GTK_SORT_ASCENDING
);
treeselect
=
gtk_tree_view_get_selection
(
treeview
);
gtk_tree_selection_set_mode
(
treeselect
,
GTK_SELECTION_SINGLE
);
g_signal_connect
(
G_OBJECT
(
treeselect
),
"changed"
,
G_CALLBACK
(
on_treeselect_changed
),
NULL
);
/* Type column */
cell
=
gtk_cell_renderer_text_new
();
gtk_tree_view_insert_column_with_data_func
(
treeview
,
-1
,
"Type"
,
cell
,
render_type
,
0
,
0
);
/* Name column */
cell
=
gtk_cell_renderer_text_new
();
g_signal_connect
(
G_OBJECT
(
cell
),
"edited"
,
G_CALLBACK
(
on_cell_edited
),
GINT_TO_POINTER
(
NAME_COL
));
g_object_set
(
G_OBJECT
(
cell
),
"editable"
,
TRUE
,
NULL
);
gtk_tree_view_insert_column_with_data_func
(
treeview
,
-1
,
"Name"
,
cell
,
render_name
,
0
,
0
);
cell
=
gtk_cell_renderer_text_new
();
gtk_tree_view_insert_column_with_data_func
(
treeview
,
-1
,
"On Message"
,
cell
,
render_when
,
GINT_TO_POINTER
(
EVENT_M
),
0
);
cell
=
gtk_cell_renderer_text_new
();
gtk_tree_view_insert_column_with_data_func
(
treeview
,
-1
,
"On Signon"
,
cell
,
render_when
,
GINT_TO_POINTER
(
EVENT_S
),
0
);
cell
=
gtk_cell_renderer_text_new
();
gtk_tree_view_insert_column_with_data_func
(
treeview
,
-1
,
"On Unidle"
,
cell
,
render_when
,
GINT_TO_POINTER
(
EVENT_I
),
0
);
cell
=
gtk_cell_renderer_text_new
();
gtk_tree_view_insert_column_with_data_func
(
treeview
,
-1
,
"On Unaway"
,
cell
,
render_when
,
GINT_TO_POINTER
(
EVENT_A
),
0
);
}
static
void
smartear_save
(
void
)
{
char
file
[
BUF_LEN
]
=
"smartear.rc - you have no home dir"
;
FILE
*
fp
=
NULL
;
GSList
*
lp
;
struct
smartear_entry
*
entry
;
if
(
purple_user_dir
())
{
g_snprintf
(
file
,
sizeof
(
file
),
"%s/smartear.rc"
,
purple_user_dir
());
fp
=
fopen
(
file
,
"w"
);
}
if
(
fp
==
NULL
)
{
g_warning
(
"couldn't open %s
\n
"
,
file
);
return
;
}
fprintf
(
fp
,
"version %d
\n
"
,
RCFILE_VERSION
);
fprintf
(
fp
,
"smartear_timers %d
\n
"
,
smartear_timers
);
fprintf
(
fp
,
"delay %d
\n
"
,
message_delay
);
fprintf
(
fp
,
"focused_quiet %d
\n
"
,
focused_quiet
);
/* save empties as a single space so scanf doesn't get confused */
#define SAVE_STR(str) (IS_EMPTY(str) ? RC_EMPTY_SOUND : str)
for
(
lp
=
sounds_list
;
lp
;
lp
=
g_slist_next
(
lp
))
{
entry
=
(
struct
smartear_entry
*
)
lp
->
data
;
fprintf
(
fp
,
"%c {%s} {%s} {%s} {%s} {%s}
\n
"
,
entry
->
type
,
entry
->
name
,
SAVE_STR
(
entry
->
sound
[
EVENT_M
]),
SAVE_STR
(
entry
->
sound
[
EVENT_S
]),
SAVE_STR
(
entry
->
sound
[
EVENT_I
]),
SAVE_STR
(
entry
->
sound
[
EVENT_A
]));
}
fclose
(
fp
);
}
static
void
smartear_load
(
void
)
{
char
file
[
BUF_LEN
]
=
"smartear.rc - you have no home dir"
;
FILE
*
fp
=
NULL
;
struct
smartear_entry
*
entry
;
char
buf
[
BUF_LEN
];
gboolean
has_default
=
FALSE
;
int
rcfile_version
=
1
;
if
(
purple_user_dir
())
{
g_snprintf
(
file
,
sizeof
(
file
),
"%s/smartear.rc"
,
purple_user_dir
());
fp
=
fopen
(
file
,
"r"
);
}
if
(
fp
==
NULL
)
{
g_warning
(
"smartear: couldn't open %s
\n
"
,
file
);
sounds_list
=
g_slist_append
(
sounds_list
,
sound_dup
(
&
default_entry
));
return
;
}
while
((
fgets
(
buf
,
sizeof
(
buf
),
fp
)))
{
int
tmp
;
if
(
sscanf
(
buf
,
"version %d"
,
&
tmp
)
==
1
)
rcfile_version
=
tmp
;
else
if
(
sscanf
(
buf
,
"smarterear_timers %d"
,
&
tmp
)
==
1
)
smartear_timers
=
tmp
;
else
if
(
sscanf
(
buf
,
"smartear_timers %d"
,
&
tmp
)
==
1
)
smartear_timers
=
tmp
;
else
if
(
sscanf
(
buf
,
"delay %d"
,
&
tmp
)
==
1
)
message_delay
=
tmp
;
else
if
(
sscanf
(
buf
,
"focused_quiet %d"
,
&
tmp
)
==
1
)
focused_quiet
=
tmp
;
else
if
(
rcfile_version
==
1
)
{
char
name
[
BUF_LEN
];
char
flags
[
BUF_LEN
];
char
sound
[
BUF_LEN
];
char
type
;
if
(
sscanf
(
buf
,
"%c {%[^}]} {%[^}]} {%[^}]}"
,
&
type
,
name
,
flags
,
sound
)
==
4
)
{
if
(
type
!=
'G'
&&
type
!=
'B'
)
{
g_warning
(
"smartear: rc no such type: %c"
,
type
);
continue
;
}
entry
=
g_new
(
struct
smartear_entry
,
1
);
entry
->
type
=
type
;
entry
->
name
=
g_strdup
(
my_normalize
(
name
));
if
(
strchr
(
flags
,
'M'
))
entry
->
sound
[
EVENT_M
]
=
g_strdup
(
sound
);
else
entry
->
sound
[
EVENT_M
]
=
g_strdup
(
""
);
if
(
strchr
(
flags
,
'S'
))
entry
->
sound
[
EVENT_S
]
=
g_strdup
(
sound
);
else
entry
->
sound
[
EVENT_S
]
=
g_strdup
(
""
);
if
(
strchr
(
flags
,
'I'
))
entry
->
sound
[
EVENT_I
]
=
g_strdup
(
sound
);
else
entry
->
sound
[
EVENT_I
]
=
g_strdup
(
""
);
if
(
strchr
(
flags
,
'A'
))
entry
->
sound
[
EVENT_A
]
=
g_strdup
(
sound
);
else
entry
->
sound
[
EVENT_A
]
=
g_strdup
(
""
);
sounds_list
=
g_slist_append
(
sounds_list
,
entry
);
if
(
IS_DEFAULT
(
entry
))
{
default_entry
=
*
entry
;
has_default
=
TRUE
;
}
}
else
g_warning
(
"smartear: rc1 syntax error: %s"
,
buf
);
}
else
if
(
rcfile_version
==
2
)
{
char
name
[
BUF_LEN
];
char
sound
[
EVENT_NUM
][
BUF_LEN
];
char
type
;
int
i
;
if
(
sscanf
(
buf
,
"%c {%[^}]} {%[^}]} {%[^}]} {%[^}]} {%[^}]}"
,
&
type
,
name
,
sound
[
EVENT_M
],
sound
[
EVENT_S
],
sound
[
EVENT_I
],
sound
[
EVENT_A
])
==
6
)
{
if
(
type
!=
'G'
&&
type
!=
'B'
)
{
g_warning
(
"smartear: rc no such type: %c"
,
type
);
continue
;
}
for
(
i
=
0
;
i
<
EVENT_NUM
;
i
++
)
{
if
(
strcmp
(
sound
[
i
],
RC_EMPTY_SOUND
)
==
0
)
sound
[
i
][
0
]
=
0
;
}
entry
=
g_new
(
struct
smartear_entry
,
1
);
entry
->
type
=
type
;
entry
->
name
=
g_strdup
(
my_normalize
(
name
));
entry
->
sound
[
EVENT_M
]
=
g_strdup
(
sound
[
EVENT_M
]);
entry
->
sound
[
EVENT_S
]
=
g_strdup
(
sound
[
EVENT_S
]);
entry
->
sound
[
EVENT_I
]
=
g_strdup
(
sound
[
EVENT_I
]);
entry
->
sound
[
EVENT_A
]
=
g_strdup
(
sound
[
EVENT_A
]);
sounds_list
=
g_slist_append
(
sounds_list
,
entry
);
if
(
IS_DEFAULT
(
entry
))
{
default_entry
=
*
entry
;
has_default
=
TRUE
;
}
}
else
g_warning
(
"smartear: rc2 syntax error: %s"
,
buf
);
}
else
g_warning
(
"smartear: rc syntax error: %s"
,
buf
);
}
if
(
!
has_default
)
sounds_list
=
g_slist_append
(
sounds_list
,
sound_dup
(
&
default_entry
));
fclose
(
fp
);
}
/*** Purple callbacks ***/
static
struct
message_data
*
find_message_by_name
(
PurpleAccount
*
account
,
const
char
*
pname
)
{
GSList
*
lp
;
struct
message_data
*
msg
=
NULL
;
char
*
name
=
g_strdup
(
my_normalize
(
pname
));
for
(
lp
=
messages_list
;
lp
;
lp
=
g_slist_next
(
lp
))
{
msg
=
(
struct
message_data
*
)
lp
->
data
;
if
(
msg
->
account
==
account
&&
g_strcasecmp
(
msg
->
buddy
,
name
)
==
0
)
break
;
}
g_free
(
name
);
if
(
lp
)
return
msg
;
return
0
;
}
static
void
message_timer_remove
(
struct
message_data
*
msg
)
{
if
(
msg
&&
msg
->
timer
)
{
g_source_remove
(
msg
->
timer
->
id
);
g_free
(
msg
->
timer
);
msg
->
timer
=
NULL
;
}
}
static
void
message_free
(
struct
message_data
*
msg
)
{
message_timer_remove
(
msg
);
g_free
(
msg
->
buddy
);
g_free
(
msg
);
}
static
void
messagelist_free
()
{
GSList
*
lp
;
for
(
lp
=
messages_list
;
lp
;
lp
=
g_slist_next
(
lp
))
message_free
((
struct
message_data
*
)
lp
->
data
);
if
(
messages_list
)
{
g_slist_free
(
messages_list
);
messages_list
=
NULL
;
}
}
static
void
on_smartear_clicked
(
PurpleBlistNode
*
node
,
gpointer
data
)
{
struct
smartear_entry
tmp
,
*
entry
;
GSList
*
lp
;
clear_selection
();
if
(
!
edit_win
)
{
edit_win
=
create_edit_win
();
}
gtk_widget_show_all
(
edit_win
);
tmp
=
default_entry
;
if
(
!
node
)
{
return
;
}
else
if
(
PURPLE_BLIST_NODE_IS_BUDDY
(
node
))
{
tmp
.
type
=
'B'
;
tmp
.
name
=
((
PurpleBuddy
*
)
node
)
->
name
;
purple_debug
(
PURPLE_DEBUG_INFO
,
"smartear"
,
"adding buddy %s"
,
tmp
.
name
);
}
else
if
(
PURPLE_BLIST_NODE_IS_CONTACT
(
node
))
{
tmp
.
type
=
'B'
;
tmp
.
name
=
((
PurpleContact
*
)
node
)
->
alias
;
if
(
!
tmp
.
name
)
{
tmp
.
name
=
((
PurpleContact
*
)
node
)
->
priority
->
name
;
}
purple_debug
(
PURPLE_DEBUG_INFO
,
"smartear"
,
"adding contact %s"
,
tmp
.
name
);
}
else
if
(
PURPLE_BLIST_NODE_IS_GROUP
(
node
))
{
tmp
.
type
=
'G'
;
tmp
.
name
=
((
PurpleGroup
*
)
node
)
->
name
;
g_warning
(
"group %p, %p %s"
,
node
,
tmp
.
name
,
tmp
.
name
);
purple_debug
(
PURPLE_DEBUG_INFO
,
"smartear"
,
"adding group %s"
,
tmp
.
name
);
}
else
{
return
;
}
if
((
lp
=
g_slist_find_custom
(
sounds_list
,
&
tmp
,
(
GCompareFunc
)
sound_cmp
)))
{
entry
=
(
struct
smartear_entry
*
)
lp
->
data
;
}
else
{
entry
=
&
tmp
;
}
populate_edit_win
(
entry
);
}
static
void
play_sound_alias
(
char
*
sound
,
PurpleAccount
*
account
)
{
struct
smartear_entry
tmp
,
*
entry
;
GSList
*
lp
;
if
(
!
sound
||
!*
sound
)
return
;
/* sound aliases: mostly so you can put (Default) for sound, and it'll
* play that one */
tmp
.
type
=
0
;
tmp
.
name
=
sound
;
if
((
lp
=
g_slist_find_custom
(
sounds_list
,
&
tmp
,
(
GCompareFunc
)
sound_cmp
)))
{
entry
=
(
struct
smartear_entry
*
)
lp
->
data
;
play_sound_alias
(
entry
->
sound
[
EVENT_M
],
account
);
}
else
{
purple_sound_play_file
(
sound
,
account
);
}
}
void
play_matching_sound
(
PurpleBuddy
*
buddy
,
int
event
)
{
GSList
*
lp
;
struct
smartear_entry
*
entry
;
char
*
sound
=
NULL
;
char
*
name
=
buddy
?
g_strdup
(
my_normalize
(
buddy
->
name
))
:
NULL
;
PurpleGroup
*
g
=
buddy
?
purple_buddy_get_group
(
buddy
)
:
NULL
;
for
(
lp
=
sounds_list
;
lp
;
lp
=
g_slist_next
(
lp
))
{
entry
=
(
struct
smartear_entry
*
)
lp
->
data
;
if
(
!
entry
->
sound
[
event
])
continue
;
if
(
entry
->
type
==
'B'
&&
name
&&
g_strcasecmp
(
name
,
entry
->
name
)
==
0
)
{
sound
=
entry
->
sound
[
event
];
/* found a buddy match.. this takes precedence, so stop */
break
;
}
if
(
entry
->
type
==
'G'
&&
g
&&
g_strcasecmp
(
my_normalize
(
g
->
name
),
entry
->
name
)
==
0
)
{
sound
=
entry
->
sound
[
event
];
/* keep going... buddy overrides group */
}
if
(
IS_DEFAULT
(
entry
))
{
if
(
!
sound
)
sound
=
entry
->
sound
[
event
];
/* keep going.. other 2 override default */
}
}
if
(
!
IS_EMPTY
(
sound
))
{
purple_debug
(
PURPLE_DEBUG_INFO
,
"smartear"
,
"found %s for %s on event %d
\n
"
,
sound
,
name
,
event
);
play_sound_alias
(
sound
,
purple_buddy_get_account
(
buddy
));
}
else
{
purple_debug
(
PURPLE_DEBUG_INFO
,
"smartear"
,
"no sound found for %s on event %d
\n
"
,
name
,
event
);
}
g_free
(
name
);
}
static
gint
play_sound_timer_hook
(
gpointer
data
)
{
struct
message_data
*
msg
=
(
struct
message_data
*
)
data
;
if
(
!
msg
||
!
msg
->
timer
)
{
g_warning
(
"smartear: timer called without being active!
\n
"
);
return
FALSE
;
}
play_matching_sound
(
purple_find_buddy
(
msg
->
account
,
msg
->
buddy
),
msg
->
timer
->
event
);
g_free
(
msg
->
timer
);
msg
->
timer
=
NULL
;
return
FALSE
;
}
static
gboolean
on_im_recv
(
PurpleAccount
*
account
,
char
*
who
,
char
*
what
,
gint32
flags
,
void
*
junk
)
{
struct
message_data
*
msg
;
time_t
now
=
time
(
0
);
PurpleConversation
*
conv
=
purple_find_conversation_with_account
(
PURPLE_CONV_TYPE_ANY
,
who
,
account
);
PidginConversation
*
gtkconv
=
conv
?
PIDGIN_CONVERSATION
(
conv
)
:
NULL
;
/* FIXME: message list is never trimmed. However, if we DO trim it,
* we need to consider race conditions on the msg field of the timer_hook */
if
((
msg
=
find_message_by_name
(
account
,
who
)))
{
/* only install a timer if there's no active timers and we've just
* sent a message. If we haven't just sent a message, then
* a beep will go off anyways when the IM arrives. */
if
(
smartear_timers
&&
!
msg
->
timer
&&
msg
->
last_sent
+
message_delay
>
now
)
{
msg
->
timer
=
g_new0
(
struct
timer_data
,
1
);
msg
->
timer
->
event
=
EVENT_M
;
msg
->
timer
->
id
=
g_timeout_add
(
message_delay
*
1000
,
play_sound_timer_hook
,
msg
);
}
if
(
msg
->
last_active
+
message_delay
>
now
&&
conv
)
{
purple_debug
(
PURPLE_DEBUG_INFO
,
"smartear"
,
"received IM from %s, but only %d/%d seconds passed.
\n
"
,
who
,
now
-
msg
->
last_active
,
message_delay
);
return
FALSE
;
}
}
else
{
msg
=
g_new0
(
struct
message_data
,
1
);
msg
->
account
=
account
;
msg
->
buddy
=
g_strdup
(
my_normalize
(
who
));
msg
->
timer
=
NULL
;
messages_list
=
g_slist_append
(
messages_list
,
msg
);
}
msg
->
last_active
=
now
;
if
(
focused_quiet
&&
gtkconv
&&
GTK_WIDGET_HAS_FOCUS
(
gtkconv
->
entry
))
return
FALSE
;
/* don't play sounds for the focused convo */
if
(
gtkconv
&&
!
gtkconv
->
make_sound
)
return
FALSE
;
play_matching_sound
(
purple_find_buddy
(
msg
->
account
,
msg
->
buddy
),
EVENT_M
);
return
FALSE
;
}
static
void
on_im_send
(
PurpleAccount
*
account
,
char
*
who
,
char
*
what
,
void
*
junk
)
{
struct
message_data
*
msg
;
time_t
now
=
time
(
0
);
if
((
msg
=
find_message_by_name
(
account
,
who
)))
{
message_timer_remove
(
msg
);
}
else
{
msg
=
g_new0
(
struct
message_data
,
1
);
msg
->
account
=
account
;
msg
->
buddy
=
g_strdup
(
my_normalize
(
who
));
msg
->
timer
=
NULL
;
messages_list
=
g_slist_append
(
messages_list
,
msg
);
}
msg
->
last_active
=
now
;
msg
->
last_sent
=
now
;
}
static
void
on_buddy_signon
(
PurpleBuddy
*
buddy
,
void
*
data
)
{
play_matching_sound
(
buddy
,
EVENT_S
);
}
static
void
on_buddy_back
(
PurpleBuddy
*
buddy
,
void
*
data
)
{
play_matching_sound
(
buddy
,
EVENT_A
);
}
static
void
on_buddy_unidle
(
PurpleBuddy
*
buddy
,
void
*
data
)
{
play_matching_sound
(
buddy
,
EVENT_I
);
}
static
void
on_signon
(
PurpleConnection
*
gc
,
void
*
m
)
{
}
static
void
on_signoff
(
PurpleConnection
*
gc
,
void
*
m
)
{
messagelist_free
();
}
static
void
on_blist_node_extended_menu
(
PurpleBlistNode
*
node
,
GList
**
menu
)
{
PurpleMenuAction
*
menu_action
;
menu_action
=
purple_menu_action_new
(
_
(
"Edit SmartEar Entry"
),
PURPLE_CALLBACK
(
on_smartear_clicked
),
NULL
,
NULL
);
*
menu
=
g_list_append
(
*
menu
,
menu_action
);
}
static
gboolean
purple_plugin_init
(
PurplePlugin
*
plugin
)
{
void
*
blist_handle
=
purple_blist_get_handle
();
void
*
conv_handle
=
purple_conversations_get_handle
();
config
=
NULL
;
smartear_load
();
purple_signal_connect
(
blist_handle
,
"blist-node-extended-menu"
,
plugin
,
PURPLE_CALLBACK
(
on_blist_node_extended_menu
),
NULL
);
purple_signal_connect
(
blist_handle
,
"buddy-signed-on"
,
plugin
,
PURPLE_CALLBACK
(
on_buddy_signon
),
NULL
);
purple_signal_connect
(
blist_handle
,
"buddy-back"
,
plugin
,
PURPLE_CALLBACK
(
on_buddy_back
),
NULL
);
purple_signal_connect
(
blist_handle
,
"buddy-unidle"
,
plugin
,
PURPLE_CALLBACK
(
on_buddy_unidle
),
NULL
);
purple_signal_connect
(
conv_handle
,
"received-im-msg"
,
plugin
,
PURPLE_CALLBACK
(
on_im_recv
),
NULL
);
purple_signal_connect
(
conv_handle
,
"sent-im-msg"
,
plugin
,
PURPLE_CALLBACK
(
on_im_send
),
NULL
);
purple_signal_connect
(
purple_connections_get_handle
(),
"signed-on"
,
plugin
,
PURPLE_CALLBACK
(
on_signon
),
NULL
);
purple_signal_connect
(
purple_connections_get_handle
(),
"signed-off"
,
plugin
,
PURPLE_CALLBACK
(
on_signoff
),
NULL
);
return
TRUE
;
}
static
gboolean
purple_plugin_remove
(
PurplePlugin
*
h
)
{
config
=
NULL
;
soundlist_free
();
messagelist_free
();
return
TRUE
;
}
static
GtkWidget
*
purple_plugin_config_gtk
(
PurplePlugin
*
plugin
)
{
config
=
create_config
();
setup_list
();
set_option_entries
();
gtk_widget_show_all
(
config
);
return
config
;
}
static
PurplePluginUiInfo
ui_info
=
{
purple_plugin_config_gtk
,
0
/* page_num (reserved) */
};
static
PurplePluginInfo
info
=
{
PURPLE_PLUGIN_MAGIC
,
PURPLE_MAJOR_VERSION
,
PURPLE_MINOR_VERSION
,
PURPLE_PLUGIN_STANDARD
,
PIDGIN_PLUGIN_TYPE
,
0
,
NULL
,
PURPLE_PRIORITY_DEFAULT
,
SMARTEAR_PLUGIN_ID
,
NULL
,
PP_VERSION
,
NULL
,
NULL
,
"Matt Perry <guy@fscked.org>"
,
PP_WEBSITE
,
purple_plugin_init
,
purple_plugin_remove
,
NULL
,
&
ui_info
,
NULL
,
NULL
,
NULL
,
NULL
,
NULL
,
NULL
,
NULL
};
static
void
init_plugin
(
PurplePlugin
*
plugin
)
{
#ifdef ENABLE_NLS
bindtextdomain
(
GETTEXT_PACKAGE
,
PP_LOCALEDIR
);
bind_textdomain_codeset
(
GETTEXT_PACKAGE
,
"UTF-8"
);
#endif
info
.
name
=
_
(
"Smart Ear"
);
info
.
summary
=
_
(
"Assign different sounds to different buddies and groups"
);
info
.
description
=
_
(
"Allows you to assign different sounds to play for different buddies "
"or whole groups of buddies. Smart Ear allows you to opt to play "
"sounds when a buddy sends you an IM, signs on, returns from away "
"or idle, or any combination of these, so you'll know by the sound "
"what the important people are doing."
);
}
PURPLE_INIT_PLUGIN
(
smartear
,
init_plugin
,
info
);