/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- * */ /* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is EGE Color Profile Tracker. * * The Initial Developer of the Original Code is * Jon A. Cruz. * Portions created by the Initial Developer are Copyright (C) 2007 * the Initial Developer. All Rights Reserved. * * Contributor(s): * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ /* Note: this file should be kept compilable as both .cpp and .c */ #include <string.h> #include <gtk/gtkwidget.h> #include <gtk/gtkwindow.h> #include <gtk/gtkmarshal.h> #ifdef GDK_WINDOWING_X11 #include <X11/Xlib.h> #include <X11/Xatom.h> #include <gdk/gdkx.h> #endif /* GDK_WINDOWING_X11 */ #include "ege-color-prof-tracker.h" /* #define GDK_ROOT_WINDOW() (gdk_x11_get_default_root_xwindow ()) #define GDK_DISPLAY() gdk_display #define GDK_WINDOW_XDISPLAY(win) #define GDK_WINDOW_XID(win) #define GDK_DISPLAY_XDISPLAY(display) #define GDK_SCREEN_XDISPLAY(screen) #define GDK_SCREEN_XNUMBER(screen) #define GDK_SCREEN_XSCREEN(screen) #define GDK_WINDOW_XWINDOW #define GDK_DRAWABLE_XID(win) GdkWindow* gdk_window_lookup (GdkNativeWindow anid); GdkWindow* gdk_window_lookup_for_display (GdkDisplay *display, GdkNativeWindow anid); GdkDisplay* gdk_x11_lookup_xdisplay (Display *xdisplay); Display* gdk_x11_display_get_xdisplay (GdkDisplay *display); Window gdk_x11_get_default_root_xwindow (void); gint gdk_x11_get_default_screen (void); Display* gdk_x11_get_default_xdisplay (void); int gdk_x11_screen_get_screen_number (GdkScreen *screen); Screen* gdk_x11_screen_get_xscreen (GdkScreen *screen); const gchar* gdk_x11_get_xatom_name (Atom xatom); const gchar* gdk_x11_get_xatom_name_for_display (GdkDisplay *display, Atom xatom); */ enum { CHANGED = 0, ADDED, REMOVED, MODIFIED, LAST_SIGNAL}; static void ege_color_prof_tracker_class_init( EgeColorProfTrackerClass* klass ); static void ege_color_prof_tracker_init( EgeColorProfTracker* tracker ); static void ege_color_prof_tracker_get_property( GObject* obj, guint propId, GValue* value, GParamSpec * pspec ); static void ege_color_prof_tracker_set_property( GObject* obj, guint propId, const GValue *value, GParamSpec* pspec ); 00102 typedef struct _ScreenTrack { GdkScreen* screen; #ifdef GDK_WINDOWING_X11 gboolean zeroSeen; gboolean otherSeen; #endif /* GDK_WINDOWING_X11 */ GSList* trackers; GPtrArray* profiles; } ScreenTrack; #ifdef GDK_WINDOWING_X11 GdkFilterReturn x11_win_filter(GdkXEvent *xevent, GdkEvent *event, gpointer data); void handle_property_change(GdkScreen* screen, const gchar* name); void add_x11_tracking_for_screen(GdkScreen* screen, ScreenTrack* screenTrack); #endif /* GDK_WINDOWING_X11 */ static GObjectClass* gParentClass = 0; static guint signals[LAST_SIGNAL] = {0}; static GSList* tracked_screens = 0; static GSList* abstract_trackers = 0; 00124 struct _EgeColorProfTrackerPrivate { GtkWidget* _target; gint _monitor; }; #define EGE_GET_PRIVATE( o ) ( G_TYPE_INSTANCE_GET_PRIVATE( (o), EGE_COLOR_PROF_TRACKER_TYPE, EgeColorProfTrackerPrivate ) ) static void target_finalized( gpointer data, GObject* where_the_object_was ); static void window_finalized( gpointer data, GObject* where_the_object_was ); static void event_after_cb( GtkWidget* widget, GdkEvent* event, gpointer user_data ); static void target_hierarchy_changed_cb(GtkWidget* widget, GtkWidget* prev_top, gpointer user_data); static void target_screen_changed_cb(GtkWidget* widget, GdkScreen* prev_screen, gpointer user_data); static void screen_size_changed_cb(GdkScreen* screen, gpointer user_data); static void fire(GdkScreen* screen, gint monitor); static void clear_profile( GdkScreen* screen, guint monitor ); static void set_profile( GdkScreen* screen, guint monitor, const guint8* data, guint len ); static void track_screen( GdkScreen* screen, EgeColorProfTracker* tracker ); 00144 GType ege_color_prof_tracker_get_type( void ) { static GType myType = 0; if ( !myType ) { static const GTypeInfo myInfo = { sizeof( EgeColorProfTrackerClass ), NULL, /* base_init */ NULL, /* base_finalize */ (GClassInitFunc)ege_color_prof_tracker_class_init, NULL, /* class_finalize */ NULL, /* class_data */ sizeof( EgeColorProfTracker ), 0, /* n_preallocs */ (GInstanceInitFunc)ege_color_prof_tracker_init, NULL }; myType = g_type_register_static( G_TYPE_OBJECT, "EgeColorProfTracker", &myInfo, (GTypeFlags)0 ); } return myType; } void ege_color_prof_tracker_class_init( EgeColorProfTrackerClass* klass ) { if ( klass ) { gParentClass = G_OBJECT_CLASS( g_type_class_peek_parent( klass ) ); GObjectClass* objClass = G_OBJECT_CLASS( klass ); objClass->get_property = ege_color_prof_tracker_get_property; objClass->set_property = ege_color_prof_tracker_set_property; klass->changed = 0; signals[CHANGED] = g_signal_new( "changed", G_TYPE_FROM_CLASS(klass), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET(EgeColorProfTrackerClass, changed), NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0 ); signals[ADDED] = g_signal_new( "added", G_TYPE_FROM_CLASS(klass), G_SIGNAL_RUN_FIRST, 0, NULL, NULL, gtk_marshal_VOID__INT_INT, G_TYPE_NONE, 2, G_TYPE_INT, G_TYPE_INT); signals[REMOVED] = g_signal_new( "removed", G_TYPE_FROM_CLASS(klass), G_SIGNAL_RUN_FIRST, 0, NULL, NULL, gtk_marshal_VOID__INT_INT, G_TYPE_NONE, 2, G_TYPE_INT, G_TYPE_INT); signals[MODIFIED] = g_signal_new( "modified", G_TYPE_FROM_CLASS(klass), G_SIGNAL_RUN_FIRST, 0, NULL, NULL, gtk_marshal_VOID__INT_INT, G_TYPE_NONE, 2, G_TYPE_INT, G_TYPE_INT); g_type_class_add_private( klass, sizeof(EgeColorProfTrackerClass) ); } } void ege_color_prof_tracker_init( EgeColorProfTracker* tracker ) { tracker->private_data = EGE_GET_PRIVATE( tracker ); tracker->private_data->_target = 0; tracker->private_data->_monitor = 0; } 00227 EgeColorProfTracker* ege_color_prof_tracker_new( GtkWidget* target ) { GObject* obj = (GObject*)g_object_new( EGE_COLOR_PROF_TRACKER_TYPE, NULL ); EgeColorProfTracker* tracker = EGE_COLOR_PROF_TRACKER( obj ); tracker->private_data->_target = target; if ( target ) { g_object_weak_ref( G_OBJECT(target), target_finalized, obj ); g_signal_connect( G_OBJECT(target), "hierarchy-changed", G_CALLBACK( target_hierarchy_changed_cb ), obj ); g_signal_connect( G_OBJECT(target), "screen-changed", G_CALLBACK( target_screen_changed_cb ), obj ); /* invoke the callbacks now to connect if the widget is already visible */ target_hierarchy_changed_cb( target, 0, obj ); target_screen_changed_cb( target, 0, obj ); } else { abstract_trackers = g_slist_append( abstract_trackers, obj ); GSList* curr = tracked_screens; while ( curr ) { ScreenTrack* track = (ScreenTrack*)curr->data; gint screenNum = gdk_screen_get_number(track->screen); gint monitor = 0; for ( monitor = 0; monitor < (gint)track->profiles->len; monitor++ ) { g_signal_emit( G_OBJECT(tracker), signals[MODIFIED], 0, screenNum, monitor ); } curr = g_slist_next(curr); } } return tracker; } void ege_color_prof_tracker_get_profile( EgeColorProfTracker const * tracker, gpointer* ptr, guint* len ) { gpointer dataPos = 0; guint dataLen = 0; if ( tracker && tracker->private_data->_target ) { GdkScreen* screen = gtk_widget_get_screen(tracker->private_data->_target); GSList* curr = tracked_screens; while ( curr ) { ScreenTrack* screenTrack = (ScreenTrack*)curr->data; if ( screenTrack->screen == screen ) { if ( tracker->private_data->_monitor >= 0 && tracker->private_data->_monitor < (gint)screenTrack->profiles->len ) { GByteArray* gba = (GByteArray*)g_ptr_array_index( screenTrack->profiles, tracker->private_data->_monitor ); if ( gba ) { dataPos = gba->data; dataLen = gba->len; } } else { g_warning("No profile data tracked for the specified item."); } break; } curr = g_slist_next(curr); } } if ( ptr ) { *ptr = dataPos; } if ( len ) { *len = dataLen; } } void ege_color_prof_tracker_get_profile_for( guint screenNum, guint monitor, gpointer* ptr, guint* len ) { gpointer dataPos = 0; guint dataLen = 0; GdkDisplay* display = gdk_display_get_default(); gint numScreens = gdk_display_get_n_screens(display); GdkScreen* screen = (screenNum < (guint)numScreens) ? gdk_display_get_screen(display, screenNum) : 0; if ( screen ) { GSList* curr = tracked_screens; while ( curr ) { ScreenTrack* screenTrack = (ScreenTrack*)curr->data; if ( screenTrack->screen == screen ) { if ( monitor < screenTrack->profiles->len ) { GByteArray* gba = (GByteArray*)g_ptr_array_index( screenTrack->profiles, monitor ); if ( gba ) { dataPos = gba->data; dataLen = gba->len; } } else { g_warning("No profile data tracked for the specified item."); } break; } curr = g_slist_next(curr); } } if ( ptr ) { *ptr = dataPos; } if ( len ) { *len = dataLen; } } void ege_color_prof_tracker_get_property( GObject* obj, guint propId, GValue* value, GParamSpec * pspec ) { EgeColorProfTracker* tracker = EGE_COLOR_PROF_TRACKER( obj ); (void)tracker; (void)value; switch ( propId ) { default: G_OBJECT_WARN_INVALID_PROPERTY_ID( obj, propId, pspec ); } } void ege_color_prof_tracker_set_property( GObject* obj, guint propId, const GValue *value, GParamSpec* pspec ) { EgeColorProfTracker* tracker = EGE_COLOR_PROF_TRACKER( obj ); (void)tracker; (void)value; switch ( propId ) { default: G_OBJECT_WARN_INVALID_PROPERTY_ID( obj, propId, pspec ); } } void track_screen( GdkScreen* screen, EgeColorProfTracker* tracker ) { GSList* curr = tracked_screens; /* First remove the tracker from different screens */ while ( curr ) { ScreenTrack* screenTrack = (ScreenTrack*)curr->data; if ( screenTrack->screen != screen ) { screenTrack->trackers = g_slist_remove_all(screenTrack->trackers, tracker); } curr = g_slist_next(curr); } curr = tracked_screens; while ( curr && ((ScreenTrack*)curr->data)->screen != screen ) { curr = g_slist_next(curr); } if ( curr ) { /* We found the screen already being tracked */ ScreenTrack* screenTrack = (ScreenTrack*)curr->data; GSList* trackHook = g_slist_find( screenTrack->trackers, tracker ); if ( !trackHook ) { screenTrack->trackers = g_slist_append( screenTrack->trackers, tracker ); } } else { ScreenTrack* newTrack = g_new(ScreenTrack, 1); gint numMonitors = gdk_screen_get_n_monitors(screen); int i = 0; newTrack->screen = screen; #ifdef GDK_WINDOWING_X11 newTrack->zeroSeen = FALSE; newTrack->otherSeen = FALSE; #endif /* GDK_WINDOWING_X11 */ newTrack->trackers = g_slist_append( 0, tracker ); newTrack->profiles = g_ptr_array_new(); for ( i = 0; i < numMonitors; i++ ) { g_ptr_array_add( newTrack->profiles, 0 ); } tracked_screens = g_slist_append( tracked_screens, newTrack ); g_signal_connect( G_OBJECT(screen), "size-changed", G_CALLBACK( screen_size_changed_cb ), tracker ); #ifdef GDK_WINDOWING_X11 add_x11_tracking_for_screen(screen, newTrack); #endif // GDK_WINDOWING_X11 } } void target_finalized( gpointer data, GObject* where_the_object_was ) { (void)data; GSList* curr = tracked_screens; while ( curr ) { ScreenTrack* track = (ScreenTrack*)curr->data; GSList* trackHook = track->trackers; while ( trackHook ) { if ( (void*)(((EgeColorProfTracker*)(trackHook->data))->private_data->_target) == (void*)where_the_object_was ) { /* The tracked widget is now gone, remove it */ ((EgeColorProfTracker*)trackHook->data)->private_data->_target = 0; track->trackers = g_slist_remove( track->trackers, trackHook ); trackHook = 0; } else { trackHook = g_slist_next( trackHook ); } } curr = g_slist_next( curr ); } } void window_finalized( gpointer data, GObject* where_the_object_was ) { (void)data; (void)where_the_object_was; /* g_message("Window at %p is now going away", where_the_object_was); */ } void event_after_cb( GtkWidget* widget, GdkEvent* event, gpointer user_data ) { if ( event->type == GDK_CONFIGURE ) { GdkScreen* screen = gtk_widget_get_screen(widget); GdkWindow* window = widget->window; EgeColorProfTracker* tracker = (EgeColorProfTracker*)user_data; gint monitorNum = gdk_screen_get_monitor_at_window(screen, window); if ( monitorNum != tracker->private_data->_monitor ) { tracker->private_data->_monitor = monitorNum; g_signal_emit( G_OBJECT(tracker), signals[CHANGED], 0 ); } } } void target_hierarchy_changed_cb(GtkWidget* widget, GtkWidget* prev_top, gpointer user_data) { if ( !prev_top && gtk_widget_get_toplevel(widget) ) { GtkWidget* top = gtk_widget_get_toplevel(widget); if ( GTK_WIDGET_TOPLEVEL(top) ) { GtkWindow* win = GTK_WINDOW(top); g_signal_connect( G_OBJECT(win), "event-after", G_CALLBACK( event_after_cb ), user_data ); g_object_weak_ref( G_OBJECT(win), window_finalized, user_data ); } } } void target_screen_changed_cb(GtkWidget* widget, GdkScreen* prev_screen, gpointer user_data) { GdkScreen* screen = gtk_widget_get_screen(widget); if ( screen && (screen != prev_screen) ) { track_screen( screen, EGE_COLOR_PROF_TRACKER(user_data) ); } } void screen_size_changed_cb(GdkScreen* screen, gpointer user_data) { GSList* curr = tracked_screens; (void)user_data; /* g_message("screen size changed to (%d, %d) with %d monitors for obj:%p", */ /* gdk_screen_get_width(screen), gdk_screen_get_height(screen), */ /* gdk_screen_get_n_monitors(screen), */ /* user_data); */ while ( curr && ((ScreenTrack*)curr->data)->screen != screen ) { curr = g_slist_next(curr); } if ( curr ) { ScreenTrack* track = (ScreenTrack*)curr->data; gint numMonitors = gdk_screen_get_n_monitors(screen); if ( numMonitors > (gint)track->profiles->len ) { guint i = 0; for ( i = track->profiles->len; i < (guint)numMonitors; i++ ) { g_ptr_array_add( track->profiles, 0 ); #ifdef GDK_WINDOWING_X11 { gchar* name = g_strdup_printf( "_ICC_PROFILE_%d", i ); handle_property_change( screen, name ); g_free(name); } #endif /* GDK_WINDOWING_X11 */ } } else if ( numMonitors < (gint)track->profiles->len ) { /* g_message("The count of monitors decreased, remove some"); */ } } } void fire(GdkScreen* screen, gint monitor) { GSList* curr = tracked_screens; while ( curr ) { ScreenTrack* track = (ScreenTrack*)curr->data; if ( track->screen == screen) { GSList* trackHook = track->trackers; while ( trackHook ) { EgeColorProfTracker* tracker = (EgeColorProfTracker*)(trackHook->data); if ( (monitor == -1) || (tracker->private_data->_monitor == monitor) ) { g_signal_emit( G_OBJECT(tracker), signals[CHANGED], 0 ); } trackHook = g_slist_next(trackHook); } } curr = g_slist_next(curr); } } static void clear_profile( GdkScreen* screen, guint monitor ) { GSList* curr = tracked_screens; while ( curr && ((ScreenTrack*)curr->data)->screen != screen ) { curr = g_slist_next(curr); } if ( curr ) { ScreenTrack* track = (ScreenTrack*)curr->data; guint i = 0; GByteArray* previous = 0; for ( i = track->profiles->len; i <= monitor; i++ ) { g_ptr_array_add( track->profiles, 0 ); } previous = (GByteArray*)g_ptr_array_index( track->profiles, monitor ); if ( previous ) { g_byte_array_free( previous, TRUE ); } track->profiles->pdata[monitor] = 0; } } static void set_profile( GdkScreen* screen, guint monitor, const guint8* data, guint len ) { GSList* curr = tracked_screens; while ( curr && ((ScreenTrack*)curr->data)->screen != screen ) { curr = g_slist_next(curr); } if ( curr ) { /* Something happened to a screen being tracked. */ ScreenTrack* track = (ScreenTrack*)curr->data; gint screenNum = gdk_screen_get_number(screen); guint i = 0; GByteArray* previous = 0; GSList* abstracts = 0; for ( i = track->profiles->len; i <= monitor; i++ ) { g_ptr_array_add( track->profiles, 0 ); } previous = (GByteArray*)g_ptr_array_index( track->profiles, monitor ); if ( previous ) { g_byte_array_free( previous, TRUE ); } if ( data && len ) { GByteArray* newBytes = g_byte_array_sized_new( len ); newBytes = g_byte_array_append( newBytes, data, len ); track->profiles->pdata[monitor] = newBytes; } else { track->profiles->pdata[monitor] = 0; } for ( abstracts = abstract_trackers; abstracts; abstracts = g_slist_next(abstracts) ) { g_signal_emit( G_OBJECT(abstracts->data), signals[MODIFIED], 0, screenNum, monitor ); } } } #ifdef GDK_WINDOWING_X11 GdkFilterReturn x11_win_filter(GdkXEvent *xevent, GdkEvent *event, gpointer data) { XEvent* x11 = (XEvent*)xevent; (void)event; (void)data; if ( x11->type == PropertyNotify ) { XPropertyEvent* note = (XPropertyEvent*)x11; /*GdkAtom gatom = gdk_x11_xatom_to_atom(note->atom);*/ const gchar* name = gdk_x11_get_xatom_name(note->atom); if ( strncmp("_ICC_PROFILE", name, 12 ) == 0 ) { XEvent* native = (XEvent*)xevent; Status stat = Success; XWindowAttributes tmp; stat = XGetWindowAttributes( native->xproperty.display, native->xproperty.window, &tmp ); if ( stat ) { GdkDisplay* display = gdk_x11_lookup_xdisplay(native->xproperty.display); if ( display ) { gint screenCount = gdk_display_get_n_screens(display); GdkScreen* targetScreen = 0; gint i = 0; for ( i = 0; i < screenCount; i++ ) { GdkScreen* sc = gdk_display_get_screen( display, i ); if ( tmp.screen == GDK_SCREEN_XSCREEN(sc) ) { targetScreen = sc; } } handle_property_change( targetScreen, name ); } } else { /* g_message("%d failed XGetWindowAttributes with %d", GPOINTER_TO_INT(data), stat); */ } } } return GDK_FILTER_CONTINUE; } void handle_property_change(GdkScreen* screen, const gchar* name) { Display* xdisplay = GDK_SCREEN_XDISPLAY(screen); gint monitor = 0; Atom atom = XInternAtom(xdisplay, name, True); if ( strncmp("_ICC_PROFILE_", name, 13 ) == 0 ) { gint64 tmp = g_ascii_strtoll(name + 13, NULL, 10); if ( tmp != 0 && tmp != G_MAXINT64 && tmp != G_MININT64 ) { monitor = (gint)tmp; } } if ( atom != None ) { Atom actualType = None; int actualFormat = 0; unsigned long size = 128 * 1042; unsigned long nitems = 0; unsigned long bytesAfter = 0; unsigned char* prop = 0; clear_profile( screen, monitor ); if ( XGetWindowProperty( xdisplay, GDK_WINDOW_XID(gdk_screen_get_root_window(screen)), atom, 0, size, False, AnyPropertyType, &actualType, &actualFormat, &nitems, &bytesAfter, &prop ) == Success ) { if ( (actualType != None) && (bytesAfter + nitems) ) { size = nitems + bytesAfter; bytesAfter = 0; nitems = 0; if ( prop ) { XFree(prop); prop = 0; } if ( XGetWindowProperty( xdisplay, GDK_WINDOW_XID(gdk_screen_get_root_window(screen)), atom, 0, size, False, AnyPropertyType, &actualType, &actualFormat, &nitems, &bytesAfter, &prop ) == Success ) { gpointer profile = g_memdup( prop, nitems ); set_profile( screen, monitor, (const guint8*)profile, nitems ); XFree(prop); } else { g_warning("Problem reading profile from root window"); } } else { /* clear it */ set_profile( screen, monitor, 0, 0 ); } } else { g_warning("error loading profile property"); } } fire(screen, monitor); } void add_x11_tracking_for_screen(GdkScreen* screen, ScreenTrack* screenTrack) { Display* xdisplay = GDK_SCREEN_XDISPLAY(screen); GdkWindow* root = gdk_screen_get_root_window(screen); if ( root ) { Window rootWin = GDK_WINDOW_XID(root); Atom baseAtom = XInternAtom(xdisplay, "_ICC_PROFILE", True); int numWinProps = 0; Atom* propArray = XListProperties(xdisplay, rootWin, &numWinProps); gint i; gdk_window_set_events(root, (GdkEventMask)(gdk_window_get_events(root) | GDK_PROPERTY_CHANGE_MASK)); gdk_window_add_filter(root, x11_win_filter, GINT_TO_POINTER(1)); /* Look for any profiles attached to this root window */ if ( propArray ) { int j = 0; gint numMonitors = gdk_screen_get_n_monitors(screen); if ( baseAtom != None ) { for ( i = 0; i < numWinProps; i++ ) { if ( baseAtom == propArray[i] ) { screenTrack->zeroSeen = TRUE; handle_property_change( screen, "_ICC_PROFILE" ); } } } else { /* g_message("Base atom not found"); */ } for ( i = 1; i < numMonitors; i++ ) { gchar* name = g_strdup_printf("_ICC_PROFILE_%d", i); Atom atom = XInternAtom(xdisplay, name, True); if ( atom != None ) { for ( j = 0; j < numWinProps; j++ ) { if ( atom == propArray[j] ) { screenTrack->otherSeen = TRUE; handle_property_change( screen, name ); } } } g_free(name); } XFree(propArray); propArray = 0; } } } #endif /* GDK_WINDOWING_X11 */