/*
* xfce4_mcs_cursor_plugin - Cursor theme plugin for xfce4 mcs manager
* Copyright (c) 2005 Pasi Orovuo <pasi.ov@gmail.com>
* Copyright (c) 2005 Brian Tarricone <bjt23@cornell.edu>
*
* 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; version 2 of the License ONLY.
*
* 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 Library 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.
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#ifdef HAVE_XCURSOR_EXTENSION
#ifdef HAVE_STRING_H
#include <string.h>
#endif
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <X11/Xlib.h>
#include <X11/Xcursor/Xcursor.h>
#include <gtk/gtk.h>
#include <libxfce4util/libxfce4util.h>
#include <libxfce4mcs/mcs-common.h>
#include <libxfce4mcs/mcs-manager.h>
#include <xfce-mcs-manager/manager-plugin.h>
#include <libxfcegui4/libxfcegui4.h>
#include "mouse-plugin-internal.h"
#define BORDER ( 6 )
#define CURSOR_SIZE_MIN ( 8 )
#define CURSOR_SIZE_MAX ( 64 )
static gchar *cursor_theme = NULL;
static guint cursor_size = 0;
/* XDG? */
static gchar *cursor_dirs[][2] = {
{ "%s/.icons/", "HOME" },
{ "%s/.themes/", "HOME" },
{ "/usr/share/cursors/xorg-x11/", NULL },
{ "/usr/share/cursors/xfree/", NULL },
{ "/usr/X11R6/lib/X11/icons/", NULL },
{ "/usr/Xorg/lib/X11/icons/", NULL },
{ "/usr/share/icons", NULL },
{ NULL, NULL }
};
/* Number of icons in preview */
#define NUM_PREVIEW ( 6 )
#define PREVIEW_SIZE ( 24 )
/* List from kde kcontrol */
const static gchar *preview_filenames[] = {
"left_ptr", "left_ptr_watch", "watch", "hand2",
"question_arrow", "sb_h_double_arrow", "sb_v_double_arrow", "bottom_left_corner",
"bottom_right_corner", "fleur", "pirate", "cross",
"X_cursor", "right_ptr", "right_side", "right_tee",
"sb_right_arrow", "sb_right_tee", "base_arrow_down", "base_arrow_up",
"bottom_side", "bottom_tee", "center_ptr", "circle",
"dot", "dot_box_mask", "dot_box_mask", "double_arrow",
"draped_box", "left_side", "left_tee", "ll_angle",
"top_side", "top_tee"
};
#define NUM_PREVIEW_NAMES ( sizeof( preview_filenames ) / sizeof( preview_filenames[0] ) )
static void
show_cursor_apply_warning_dlg(Itf *dialog)
{
GtkWidget *dlg, *hbox, *vbox, *img, *lbl, *chk;
dlg = gtk_dialog_new_with_buttons(_( "Mouse Settings" ),
GTK_WINDOW(dialog->mouse_dialog),
GTK_DIALOG_DESTROY_WITH_PARENT|GTK_DIALOG_NO_SEPARATOR,
GTK_STOCK_CLOSE, GTK_RESPONSE_ACCEPT, NULL);
vbox = gtk_vbox_new(FALSE, 0);
gtk_widget_show(vbox);
gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dlg)->vbox), vbox, TRUE, TRUE, 0);
hbox = gtk_hbox_new(FALSE, BORDER);
gtk_container_set_border_width(GTK_CONTAINER(hbox), BORDER);
gtk_widget_show(hbox);
gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
img = gtk_image_new_from_stock(GTK_STOCK_DIALOG_INFO, GTK_ICON_SIZE_DIALOG);
gtk_misc_set_alignment(GTK_MISC(img), 0.0, 0.0);
gtk_widget_show(img);
gtk_box_pack_start(GTK_BOX(hbox), img, FALSE, FALSE, 0);
lbl = gtk_label_new("");
gtk_label_set_markup(GTK_LABEL(lbl), _("<span weight='bold' size='large'>Cursor settings saved.</span>\n\nMouse cursor settings may not be applied until you restart Xfce."));
gtk_label_set_use_markup(GTK_LABEL(lbl), TRUE);
gtk_label_set_line_wrap(GTK_LABEL(lbl), TRUE);
gtk_widget_show(lbl);
gtk_box_pack_start(GTK_BOX(hbox), lbl, TRUE, TRUE, 0);
chk = gtk_check_button_new_with_mnemonic(_("_Don't show this again"));
gtk_widget_show(chk);
gtk_box_pack_start(GTK_BOX(vbox), chk, FALSE, FALSE, 0);
gtk_dialog_run(GTK_DIALOG(dlg));
if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(chk))) {
mcs_manager_set_int(dialog->mcs_plugin->manager, "Cursor/ShowApplyWarning", CHANNEL2, 0);
mcs_manager_notify(dialog->mcs_plugin->manager, CHANNEL2);
mouse_plugin_write_options(dialog->mcs_plugin);
}
gtk_widget_destroy(dlg);
}
static void
cursor_theme_set( const gchar *theme, guint size )
{
gchar *xrdb_file, *xrdb_file_new, *cmd;
FILE *fp;
GError *error = NULL;
if ( !theme || size <= 0 ) {
g_warning( "Mouse Settings: Can't set theme %s (%u)", theme ? theme : "(null)", size );
return;
}
xrdb_file = xfce_resource_save_location(XFCE_RESOURCE_CONFIG, "xfce4/Xcursor.xrdb", TRUE);
if(!xrdb_file) {
g_critical(_("Mouse Settings: Unable to create %s"), xrdb_file);
g_free(xrdb_file);
return;
}
xrdb_file_new = g_strconcat(xrdb_file, ".new", NULL);
fp = fopen(xrdb_file_new, "w");
if(!fp) {
g_critical(_("Mouse Settings: Unable to create %s"), xrdb_file_new);
g_free(xrdb_file_new);
g_free(xrdb_file);
return;
}
fprintf(fp, "Xcursor.theme: %s\n"
"Xcursor.theme_core: true\n"
"Xcursor.size: %d\n", theme, size );
fclose(fp);
if(rename(xrdb_file_new, xrdb_file)) {
g_critical(_("Mouse Settings: Unable to move %s to %s. Cursor settings may not be reapplied correctly on restart."), xrdb_file_new, xrdb_file);
g_free(xrdb_file_new);
g_free(xrdb_file);
return;
}
g_free(xrdb_file_new);
cmd = g_strdup_printf("xrdb -nocpp -merge \"%s\"", xrdb_file);
if(!g_spawn_command_line_async(cmd, &error)) {
g_critical(_("Mouse Settings: Failed to run xrdb. Cursor settings may not be applied correctly. (Error was: %s)"), error->message);
g_error_free(error);
}
g_free(cmd);
g_free(xrdb_file);
return;
}
static void
cursor_plugin_pixbuf_destroy_notify_cb( guchar *pixels, gpointer data )
{
g_free( pixels );
}
static GdkPixbuf *
cursor_image_get_pixbuf( XcursorImage *cursor )
{
GdkPixbuf *pixbuf = NULL;
guchar *data, *p;
gsize dlen = cursor->width * cursor->height * sizeof( XcursorPixel );
guint i;
data = g_malloc( dlen );
for ( i = 0, p = (guchar *) cursor->pixels; i < dlen; i += 4, p += 4 ) {
data[i] = p[2];
data[i + 1] = p[1];
data[i + 2] = p[0];
data[i + 3] = p[3];
}
pixbuf = gdk_pixbuf_new_from_data( data,
GDK_COLORSPACE_RGB,
TRUE,
8,
cursor->width, cursor->height,
cursor->width * sizeof( XcursorPixel ),
cursor_plugin_pixbuf_destroy_notify_cb,
NULL );
if ( pixbuf == NULL ) {
g_free( data );
return ( NULL );
}
if ( cursor->width != PREVIEW_SIZE || cursor->height != PREVIEW_SIZE ) {
gfloat f;
GdkPixbuf *tmp;
guint w, h;
f = (gfloat) cursor->width / (gfloat) cursor->height;
if ( f >= 1.0f ) {
w = (gfloat) PREVIEW_SIZE / f;
h = PREVIEW_SIZE;
}
else {
w = PREVIEW_SIZE;
h = (gfloat) PREVIEW_SIZE * f;
}
tmp = gdk_pixbuf_scale_simple( pixbuf, w, h, GDK_INTERP_BILINEAR );
g_return_val_if_fail( tmp != NULL, pixbuf );
gdk_pixbuf_unref( pixbuf );
pixbuf = tmp;
}
return ( pixbuf );
}
static GdkPixbuf *
generate_preview_image( GtkWidget *widget, const gchar *theme_path )
{
gint i, num_loaded;
GdkPixbuf *preview_pix = NULL;
GdkPixmap *pmap;
GtkStyle *style;
if(!GTK_WIDGET_REALIZED(widget))
gtk_widget_realize(widget);
pmap = gdk_pixmap_new(GDK_DRAWABLE(widget->window),
NUM_PREVIEW*PREVIEW_SIZE, PREVIEW_SIZE, -1);
style = gtk_widget_get_style(widget);
gdk_draw_rectangle(GDK_DRAWABLE(pmap), style->bg_gc[GTK_STATE_NORMAL], TRUE,
0, 0, NUM_PREVIEW*PREVIEW_SIZE, PREVIEW_SIZE);
for ( i = 0, num_loaded = 0; i < NUM_PREVIEW_NAMES && num_loaded < NUM_PREVIEW; i++ ) {
XcursorImage *cursor;
gchar *fn = g_build_filename( theme_path, preview_filenames[i], NULL );
cursor = XcursorFilenameLoadImage( fn, PREVIEW_SIZE );
if ( cursor ) {
GdkPixbuf *pb = cursor_image_get_pixbuf( cursor );
if ( pb ) {
gdk_draw_pixbuf(GDK_DRAWABLE(pmap),
style->bg_gc[GTK_STATE_NORMAL], pb, 0, 0,
num_loaded*PREVIEW_SIZE, 0,
gdk_pixbuf_get_width(pb),
gdk_pixbuf_get_height(pb),
GDK_RGB_DITHER_NONE, 0, 0);
g_object_unref( G_OBJECT(pb) );
num_loaded++;
}
else {
g_warning( "pb == NULL" );
}
XcursorImageDestroy( cursor );
}
}
if(num_loaded > 0) {
preview_pix = gdk_pixbuf_get_from_drawable(NULL, GDK_DRAWABLE(pmap),
NULL, 0, 0, 0, 0,
NUM_PREVIEW*PREVIEW_SIZE,
PREVIEW_SIZE);
}
g_object_unref(G_OBJECT(pmap));
return preview_pix;
}
static GtkWidget *
preview_list_create( void )
{
GtkWidget *preview_list;
preview_list = gtk_image_new();
gtk_widget_set_size_request( preview_list,
NUM_PREVIEW * PREVIEW_SIZE,
PREVIEW_SIZE + BORDER );
return ( preview_list );
}
enum {
TLIST_THEME_NAME,
TLIST_THEME_PATH,
TLIST_NUM_COLUMNS
};
static gint
tree_sort_cmp_alpha(GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b,
gpointer user_data)
{
gchar *a_s = NULL, *b_s = NULL;
gint ret = 0;
gtk_tree_model_get(model, a, TLIST_THEME_NAME, &a_s, -1);
gtk_tree_model_get(model, b, TLIST_THEME_NAME, &b_s, -1);
if(!a_s)
ret = -1;
else if(!b_s)
ret = 1;
else
ret = g_ascii_strcasecmp(a_s, b_s);
g_free(a_s);
g_free(b_s);
return ret;
}
static void
theme_list_populate( GtkWidget *widget, const gchar *current_theme )
{
GDir *dir = NULL;
guint i;
GtkTreeIter iter;
GtkListStore *store;
GHashTable *themes;
store = GTK_LIST_STORE( gtk_tree_view_get_model( GTK_TREE_VIEW( widget ) ) );
gtk_list_store_append( store, &iter );
gtk_list_store_set( store, &iter, 0, "default", -1 );
themes = g_hash_table_new_full(g_str_hash, g_str_equal,
(GDestroyNotify)g_free, NULL);
for ( i = 0; cursor_dirs[i][0]; i++ ) {
gchar *curdir = cursor_dirs[i][0];
if ( cursor_dirs[i][1] ) {
curdir = g_strdup_printf( cursor_dirs[i][0], g_getenv( cursor_dirs[i][1] ) );
}
if ( ( dir = g_dir_open( curdir, 0, NULL ) ) ) {
const gchar *theme;
for ( theme = g_dir_read_name( dir ); theme != NULL; theme = g_dir_read_name( dir ) ) {
gchar *full_path = g_build_filename( curdir, theme, "cursors", NULL );
if ( g_file_test( full_path, G_FILE_TEST_IS_DIR )
&& !g_hash_table_lookup(themes, theme))
{
gtk_list_store_append( store, &iter );
gtk_list_store_set( store, &iter,
TLIST_THEME_NAME, theme,
TLIST_THEME_PATH, full_path,
-1 );
g_hash_table_insert(themes, g_strdup(theme), GINT_TO_POINTER(1));
if ( current_theme && !strcmp( current_theme, theme ) ) {
GtkTreePath *path;
path = gtk_tree_model_get_path( GTK_TREE_MODEL( store ), &iter );
gtk_tree_view_set_cursor( GTK_TREE_VIEW( widget ), path, NULL, FALSE );
gtk_tree_view_scroll_to_cell( GTK_TREE_VIEW( widget ), path, NULL, FALSE, 0.5, 0.0 );
gtk_tree_path_free( path );
}
}
g_free( full_path );
}
g_dir_close( dir );
}
if ( cursor_dirs[i][1] ) {
g_free( curdir );
}
}
g_hash_table_destroy(themes);
gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(store), TLIST_THEME_NAME,
tree_sort_cmp_alpha, NULL, NULL);
gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(store),
TLIST_THEME_NAME, GTK_SORT_ASCENDING);
}
static void
theme_list_selection_changed_cb( GtkTreeSelection *selection, Itf *dialog )
{
GtkTreeIter iter;
GtkTreeModel *theme_model = NULL;
if ( gtk_tree_selection_get_selected( selection, &theme_model, &iter ) ) {
gchar *path = NULL, *theme = NULL;
GdkPixbuf *pb = NULL;
gtk_tree_model_get( theme_model, &iter,
TLIST_THEME_PATH, &path,
TLIST_THEME_NAME, &theme,
-1 );
if ( path ) {
pb = generate_preview_image( dialog->cursor_preview_list, path );
g_free( path );
}
gtk_image_set_from_pixbuf(GTK_IMAGE(dialog->cursor_preview_list), pb);
if(pb)
g_object_unref(G_OBJECT(pb));
if(theme) {
McsSetting *setting;
g_free(cursor_theme);
cursor_theme = theme;
mcs_manager_set_string( dialog->mcs_plugin->manager, "Gtk/CursorThemeName", CHANNEL1, cursor_theme );
mcs_manager_notify( dialog->mcs_plugin->manager, CHANNEL1 );
mouse_plugin_write_options( dialog->mcs_plugin );
cursor_theme_set(cursor_theme, cursor_size);
setting = mcs_manager_setting_lookup(dialog->mcs_plugin->manager, "Cursor/ShowApplyWarning", CHANNEL2);
if(!setting || setting->data.v_int)
show_cursor_apply_warning_dlg(dialog);
}
}
}
static void
cursor_size_changed_cb(GtkWidget *w, gpointer user_data)
{
Itf *dialog = user_data;
guint new_size;
new_size = gtk_spin_button_get_value_as_int( GTK_SPIN_BUTTON( dialog->cursor_size_spinbtn ) );
if(new_size != cursor_size) {
cursor_size = new_size;
mcs_manager_set_int( dialog->mcs_plugin->manager, "Gtk/CursorThemeSize", CHANNEL1, cursor_size );
mcs_manager_notify( dialog->mcs_plugin->manager, CHANNEL1 );
mouse_plugin_write_options( dialog->mcs_plugin );
cursor_theme_set(cursor_theme, cursor_size);
}
}
static GtkWidget *
theme_list_create()
{
GtkWidget *theme_list;
GtkCellRenderer *renderer;
GtkListStore *store;
GtkTreeSelection *selection;
store = gtk_list_store_new( TLIST_NUM_COLUMNS, G_TYPE_STRING, G_TYPE_STRING );
theme_list = gtk_tree_view_new_with_model( GTK_TREE_MODEL( store ) );
g_object_unref( store );
renderer = gtk_cell_renderer_text_new();
gtk_tree_view_insert_column_with_attributes( GTK_TREE_VIEW( theme_list ),
-1,
_( "Cursor theme" ),
renderer,
"text", TLIST_THEME_NAME,
NULL );
selection = gtk_tree_view_get_selection( GTK_TREE_VIEW( theme_list ) );
gtk_tree_selection_set_mode( selection, GTK_SELECTION_SINGLE );
gtk_tree_view_set_headers_visible( GTK_TREE_VIEW( theme_list ), FALSE );
return ( theme_list );
}
void
mouse_plugin_set_initial_cursor_values(McsPlugin *mcs_plugin)
{
McsSetting *setting;
setting = mcs_manager_setting_lookup( mcs_plugin->manager, "Gtk/CursorThemeName", CHANNEL1 );
if ( setting ) {
cursor_theme = g_strdup( setting->data.v_string );
}
else {
cursor_theme = g_strdup( "default" );
mcs_manager_set_string( mcs_plugin->manager, "Gtk/CursorThemeName",
CHANNEL1, cursor_theme );
}
setting = mcs_manager_setting_lookup( mcs_plugin->manager, "Gtk/CursorThemeSize", CHANNEL1 );
if ( setting ) {
cursor_size = setting->data.v_int;
}
else {
cursor_size = XcursorGetDefaultSize( GDK_DISPLAY() );
mcs_manager_set_int( mcs_plugin->manager, "Gtk/CursorThemeSize",
CHANNEL1, cursor_size );
}
/*
if ( strcmp( cursor_theme, "default" ) ) {
cursor_theme_set( cursor_theme, cursor_size );
}
*/
}
void
mouse_plugin_create_cursor_page(Itf *dialog)
{
GtkWidget *hbox, *vbox, *fbox, *frame_bin;
GtkWidget *scrolledwindow;
GtkTreeSelection *sel;
GtkTreeModel *model = NULL;
GtkTreeIter itr;
dialog->cursor_page = gtk_hbox_new( FALSE, 0 );
gtk_container_set_border_width(GTK_CONTAINER(dialog->cursor_page), BORDER);
gtk_widget_show( dialog->cursor_page );
scrolledwindow = gtk_scrolled_window_new( NULL, NULL );
gtk_widget_show( scrolledwindow );
gtk_box_pack_start( GTK_BOX( dialog->cursor_page ), scrolledwindow, TRUE, TRUE, 0 );
gtk_scrolled_window_set_policy( GTK_SCROLLED_WINDOW( scrolledwindow ),
GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC );
gtk_scrolled_window_set_shadow_type( GTK_SCROLLED_WINDOW( scrolledwindow ), GTK_SHADOW_IN );
dialog->cursor_theme_list = theme_list_create();
gtk_widget_show( dialog->cursor_theme_list );
gtk_container_add( GTK_CONTAINER( scrolledwindow ), dialog->cursor_theme_list );
vbox = gtk_vbox_new( FALSE, 0 );
gtk_widget_show( vbox );
gtk_box_pack_start( GTK_BOX( dialog->cursor_page ), vbox, TRUE, TRUE, 0 );
fbox = xfce_create_framebox( _( "Cursor Size" ), &frame_bin );
gtk_widget_show( fbox );
gtk_box_pack_start( GTK_BOX( vbox ), fbox, FALSE, FALSE, 0 );
hbox = gtk_hbox_new( FALSE, 0 );
gtk_widget_show( hbox );
gtk_container_add( GTK_CONTAINER( frame_bin ), hbox );
dialog->cursor_size_spinbtn = gtk_spin_button_new_with_range( CURSOR_SIZE_MIN, CURSOR_SIZE_MAX, 1.0 );
gtk_widget_show( dialog->cursor_size_spinbtn );
gtk_box_pack_start( GTK_BOX( hbox ), dialog->cursor_size_spinbtn, FALSE, FALSE, 0 );
gtk_spin_button_set_numeric( GTK_SPIN_BUTTON( dialog->cursor_size_spinbtn ), TRUE );
gtk_spin_button_set_value( GTK_SPIN_BUTTON( dialog->cursor_size_spinbtn ), cursor_size );
gtk_spin_button_set_wrap( GTK_SPIN_BUTTON( dialog->cursor_size_spinbtn ), FALSE );
g_signal_connect(G_OBJECT(dialog->cursor_size_spinbtn), "changed",
G_CALLBACK(cursor_size_changed_cb), dialog);
fbox = xfce_create_framebox( _( "Preview" ), &frame_bin );
gtk_widget_show( fbox );
gtk_box_pack_start( GTK_BOX( vbox ), fbox, FALSE, FALSE, 0 );
hbox = gtk_hbox_new( FALSE, 0 );
gtk_widget_show( hbox );
gtk_container_add( GTK_CONTAINER( frame_bin ), hbox );
gtk_container_set_border_width( GTK_CONTAINER( hbox ), BORDER );
dialog->cursor_preview_list = preview_list_create();
gtk_widget_show( dialog->cursor_preview_list );
gtk_box_pack_start( GTK_BOX( hbox ), dialog->cursor_preview_list, FALSE, FALSE, 0 );
theme_list_populate( dialog->cursor_theme_list, cursor_theme );
sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(dialog->cursor_theme_list));
if(gtk_tree_selection_get_selected(sel, &model, &itr)) {
gchar *path = NULL;
gtk_tree_model_get(model, &itr, TLIST_THEME_PATH, &path, -1);
if(path) {
GdkPixbuf *pb = generate_preview_image(dialog->mouse_dialog, path);
if(pb) {
gtk_image_set_from_pixbuf(GTK_IMAGE(dialog->cursor_preview_list), pb);
g_object_unref(G_OBJECT(pb));
}
g_free(path);
}
}
g_signal_connect( G_OBJECT(sel), "changed",
G_CALLBACK( theme_list_selection_changed_cb ), dialog );
}
#endif /* HAVE_XCURSOR_EXTENSION */
syntax highlighted by Code2HTML, v. 0.9.1