From cca66b9ec4e494c1d919bff0f71a820d8afab1fa Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 20:24:48 +0200 Subject: Adding upstream version 1.2.2. Signed-off-by: Daniel Baumann --- src/ege-color-prof-tracker.cpp | 629 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 629 insertions(+) create mode 100644 src/ege-color-prof-tracker.cpp (limited to 'src/ege-color-prof-tracker.cpp') diff --git a/src/ege-color-prof-tracker.cpp b/src/ege-color-prof-tracker.cpp new file mode 100644 index 0000000..b84e2d2 --- /dev/null +++ b/src/ege-color-prof-tracker.cpp @@ -0,0 +1,629 @@ +// SPDX-License-Identifier: GPL-2.0-or-later OR MPL-1.1 OR LGPL-2.1-or-later +/* -*- 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 +#include +#include + +#include + +#ifdef HAVE_X11 +#include + +#include +#endif /* HAVE_X11 */ + +#include "ege-color-prof-tracker.h" +#include "helper/sp-marshal.h" + +enum { + CHANGED = 0, + ADDED, + REMOVED, + MODIFIED, + LAST_SIGNAL +}; + +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 ); +static void ege_color_prof_tracker_dispose(GObject *); + +class ScreenTrack { + public: +#ifdef HAVE_X11 + gboolean zeroSeen; + gboolean otherSeen; +#endif /* HAVE_X11 */ + std::vector *trackers; + GPtrArray* profiles; + ~ScreenTrack(){ delete trackers; } +}; + + +#ifdef HAVE_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); +static void fire(gint monitor); +static void clear_profile( guint monitor ); +static void set_profile( guint monitor, const guint8* data, guint len ); +#endif /* HAVE_X11 */ + +static guint signals[LAST_SIGNAL] = {0}; + +// There is only one GdkScreen in Gtk+ 3 +static ScreenTrack *tracked_screen = nullptr; + +static std::vector abstract_trackers; + +typedef struct +{ + GtkWidget* _target; + gint _monitor; +} EgeColorProfTrackerPrivate; + +#define EGE_COLOR_PROF_TRACKER_GET_PRIVATE( o ) \ + reinterpret_cast( ege_color_prof_tracker_get_instance_private (o)) + +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 track_screen( GdkScreen* screen, EgeColorProfTracker* tracker ); + +G_DEFINE_TYPE_WITH_PRIVATE (EgeColorProfTracker, ege_color_prof_tracker, G_TYPE_OBJECT); + +void ege_color_prof_tracker_class_init( EgeColorProfTrackerClass* klass ) +{ + if ( 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 = nullptr; + + objClass->dispose = ege_color_prof_tracker_dispose; + + signals[CHANGED] = g_signal_new( "changed", + G_TYPE_FROM_CLASS(klass), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET(EgeColorProfTrackerClass, changed), + nullptr, nullptr, + 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, + nullptr, nullptr, + sp_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, + nullptr, nullptr, + sp_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, + nullptr, nullptr, + g_cclosure_marshal_VOID__INT, + G_TYPE_NONE, 1, + G_TYPE_INT); + } +} + + +void ege_color_prof_tracker_init( EgeColorProfTracker* tracker ) +{ + auto priv = EGE_COLOR_PROF_TRACKER_GET_PRIVATE( tracker ); + priv->_target = nullptr; + priv->_monitor = 0; +} + +EgeColorProfTracker* ege_color_prof_tracker_new( GtkWidget* target ) +{ + GObject* obj = (GObject*)g_object_new( EGE_TYPE_COLOR_PROF_TRACKER, + nullptr ); + + EgeColorProfTracker* tracker = EGE_COLOR_PROF_TRACKER( obj ); + auto priv = EGE_COLOR_PROF_TRACKER_GET_PRIVATE (tracker); + priv->_target = target; + + if ( target ) { + 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, nullptr, obj ); + target_screen_changed_cb( target, nullptr, obj ); + } else { + abstract_trackers.push_back(tracker); + + if(tracked_screen) { + for ( gint monitor = 0; monitor < (gint)tracked_screen->profiles->len; monitor++ ) { + g_signal_emit( G_OBJECT(tracker), signals[MODIFIED], 0, monitor ); + } + } + + } + + return tracker; +} + +void ege_color_prof_tracker_dispose(GObject *obj) +{ + auto *tracker = EGE_COLOR_PROF_TRACKER(obj); + auto *priv = EGE_COLOR_PROF_TRACKER_GET_PRIVATE(tracker); + + if (priv->_target) { + // remove from trackers + auto *trackers = tracked_screen->trackers; + auto it = std::find(trackers->begin(), trackers->end(), tracker); + if (it != trackers->end()) { + trackers->erase(it); + } + + // disconnect signals + g_signal_handlers_disconnect_by_data(G_OBJECT(priv->_target), obj); + g_signal_handlers_disconnect_by_data(G_OBJECT(gtk_widget_get_toplevel(priv->_target)), obj); + + priv->_target = nullptr; + } + + // chain up to parent + auto *parent_class = G_OBJECT_CLASS(ege_color_prof_tracker_parent_class); + if (parent_class->dispose) { + (*parent_class->dispose)(obj); + } +} + +void ege_color_prof_tracker_get_profile( EgeColorProfTracker const * tracker, gpointer* ptr, guint* len ) +{ + auto priv = EGE_COLOR_PROF_TRACKER_GET_PRIVATE( const_cast(tracker) ); + gpointer dataPos = nullptr; + guint dataLen = 0; + if (tracker) { + if (priv->_target ) { + //GdkScreen* screen = gtk_widget_get_screen(priv->_target); + if ( tracked_screen ) { + if ( priv->_monitor >= 0 && priv->_monitor < (static_cast(tracked_screen->profiles->len))) { + GByteArray* gba = static_cast(g_ptr_array_index(tracked_screen->profiles, priv->_monitor)); + if ( gba ) { + dataPos = gba->data; + dataLen = gba->len; + } + } else { + g_warning("No profile data tracked for the specified item."); + } + } + } + } + if ( ptr ) { + *ptr = dataPos; + } + if ( len ) { + *len = dataLen; + } +} + +void ege_color_prof_tracker_get_profile_for( guint monitor, gpointer* ptr, guint* len ) +{ + gpointer dataPos = nullptr; + guint dataLen = 0; + GdkDisplay *display = gdk_display_get_default(); + GdkScreen *screen = gdk_display_get_default_screen(display); + + if ( screen ) { + if ( tracked_screen ) { + if ( monitor < tracked_screen->profiles->len ) { + GByteArray* gba = (GByteArray*)g_ptr_array_index( tracked_screen->profiles, monitor ); + if ( gba ) { + dataPos = gba->data; + dataLen = gba->len; + } + } else { + g_warning("No profile data tracked for the specified item."); + } + } + } + + 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 ) +{ + if ( tracked_screen ) { + /* We found the screen already being tracked */ + if ( std::find(tracked_screen->trackers->begin(),tracked_screen->trackers->end(),tracker)==tracked_screen->trackers->end() ) { + tracked_screen->trackers->push_back(tracker); + } + } else { + tracked_screen = g_new(ScreenTrack, 1); + + GdkDisplay* display = gdk_display_get_default(); + + int numMonitors = gdk_display_get_n_monitors(display); + +#ifdef HAVE_X11 + tracked_screen->zeroSeen = FALSE; + tracked_screen->otherSeen = FALSE; +#endif /* HAVE_X11 */ + tracked_screen->trackers= new std::vector; + tracked_screen->trackers->push_back(tracker ); + tracked_screen->profiles = g_ptr_array_new(); + for ( int i = 0; i < numMonitors; i++ ) { + g_ptr_array_add( tracked_screen->profiles, nullptr ); + } + + g_signal_connect( G_OBJECT(screen), "size-changed", G_CALLBACK( screen_size_changed_cb ), tracker ); + +#ifdef HAVE_X11 + if (GDK_IS_X11_DISPLAY (display) ) { + // printf( "track_screen: Display is using X11\n" ); + add_x11_tracking_for_screen(screen); + } else { + // printf( "track_screen: Display is not using X11\n" ); + } +#endif // HAVE_X11 + } +} + + +void event_after_cb( GtkWidget* widget, GdkEvent* event, gpointer user_data ) +{ + if ( event->type == GDK_CONFIGURE ) { + GdkWindow* window = gtk_widget_get_window (widget); + EgeColorProfTracker* tracker = (EgeColorProfTracker*)user_data; + auto priv = EGE_COLOR_PROF_TRACKER_GET_PRIVATE (tracker); + + // In Gtk+ >= 3.22, we need to figure out the screen ID + auto display = gdk_display_get_default(); + auto monitor = gdk_display_get_monitor_at_window(display, window); + + int n_monitors = gdk_display_get_n_monitors(display); + + int monitorNum = -1; + + // Now loop through the set of monitors and figure out whether this monitor matches + for (int i_monitor = 0; i_monitor < n_monitors; ++i_monitor) { + auto monitor_at_index = gdk_display_get_monitor(display, i_monitor); + if(monitor_at_index == monitor) monitorNum = i_monitor; + } + + if ( monitorNum != priv->_monitor && monitorNum != -1 ) { + priv->_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_is_toplevel(top) ) { + GtkWindow* win = GTK_WINDOW(top); + g_signal_connect( G_OBJECT(win), "event-after", G_CALLBACK( event_after_cb ), 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) +{ + (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); */ + if ( tracked_screen ) { + GdkDisplay* display = gdk_display_get_default(); + + int numMonitors = gdk_display_get_n_monitors(display); + + if ( numMonitors > (gint)tracked_screen->profiles->len ) { + for ( guint i = tracked_screen->profiles->len; i < (guint)numMonitors; i++ ) { + g_ptr_array_add( tracked_screen->profiles, nullptr ); +#ifdef HAVE_X11 + if (GDK_IS_X11_DISPLAY (display) ) { + gchar* name = g_strdup_printf( "_ICC_PROFILE_%d", i ); + handle_property_change( screen, name ); + g_free(name); + } +#endif /* HAVE_X11 */ + } + } else if ( numMonitors < (gint)tracked_screen->profiles->len ) { +/* g_message("The count of monitors decreased, remove some"); */ + } + } +} + +#ifdef HAVE_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; + XWindowAttributes tmp; + Status stat = XGetWindowAttributes( native->xproperty.display, native->xproperty.window, &tmp ); + if ( stat ) { + GdkDisplay* display = gdk_x11_lookup_xdisplay(native->xproperty.display); + if ( display ) { + GdkScreen* targetScreen = nullptr; + GdkScreen* sc = gdk_display_get_default_screen(display); + 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, nullptr, 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 = nullptr; + + clear_profile( 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 = nullptr; + } + if ( XGetWindowProperty( xdisplay, GDK_WINDOW_XID(gdk_screen_get_root_window(screen)), + atom, 0, size, False, AnyPropertyType, + &actualType, &actualFormat, &nitems, &bytesAfter, &prop ) == Success ) { + set_profile( monitor, static_cast(prop), nitems ); + XFree(prop); + } else { + g_warning("Problem reading profile from root window"); + } + } else { + /* clear it */ + set_profile( monitor, nullptr, 0 ); + } + } else { + g_warning("error loading profile property"); + } + } + fire(monitor); +} + +void add_x11_tracking_for_screen(GdkScreen* screen) +{ + 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; + + auto display = gdk_display_get_default(); + int numMonitors = gdk_display_get_n_monitors(display); + + if ( baseAtom != None ) { + for ( i = 0; i < numWinProps; i++ ) { + if ( baseAtom == propArray[i] ) { + tracked_screen->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] ) { + tracked_screen->otherSeen = TRUE; + handle_property_change( screen, name ); + } + } + } + g_free(name); + } + XFree(propArray); + propArray = nullptr; + } + } +} + +void fire(gint monitor) +{ + if ( tracked_screen ) { + for (auto tracker:(*(tracked_screen->trackers))) { + auto priv = EGE_COLOR_PROF_TRACKER_GET_PRIVATE (tracker); + if ( (monitor == -1) || (priv->_monitor == monitor) ) { + g_signal_emit( G_OBJECT(tracker), signals[CHANGED], 0 ); + } + } + } +} + +static void clear_profile( guint monitor ) +{ + if ( tracked_screen ) { + GByteArray* previous = nullptr; + for ( guint i = tracked_screen->profiles->len; i <= monitor; i++ ) { + g_ptr_array_add( tracked_screen->profiles, nullptr ); + } + previous = (GByteArray*)g_ptr_array_index( tracked_screen->profiles, monitor ); + if ( previous ) { + g_byte_array_free( previous, TRUE ); + } + + tracked_screen->profiles->pdata[monitor] = nullptr; + } +} + +static void set_profile( guint monitor, const guint8* data, guint len ) +{ + if ( tracked_screen ) { + for ( guint i = tracked_screen->profiles->len; i <= monitor; i++ ) { + g_ptr_array_add( tracked_screen->profiles, nullptr ); + } + GByteArray *previous = (GByteArray*)g_ptr_array_index( tracked_screen->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 ); + tracked_screen->profiles->pdata[monitor] = newBytes; + } else { + tracked_screen->profiles->pdata[monitor] = nullptr; + } + + for (auto i:abstract_trackers) { + g_signal_emit( G_OBJECT(i), signals[MODIFIED], 0, monitor ); + } + } +} +#endif /* HAVE_X11 */ +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 : -- cgit v1.2.3