/*
* Copyright (c) 2014 Red Hat, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see .
*/
#include "config.h"
#include
#include "general.h"
#include "gtkdebug.h"
#include "gtklabel.h"
#include "gtkscale.h"
#include "gtkswitch.h"
#include "gtklistbox.h"
#include "gtkprivate.h"
#include "gtksizegroup.h"
#include "gtkimage.h"
#include "gtkadjustment.h"
#include "gtkbox.h"
#ifdef GDK_WINDOWING_X11
#include "x11/gdkx.h"
#include
#endif
#ifdef GDK_WINDOWING_WIN32
#include "win32/gdkwin32.h"
#endif
#ifdef GDK_WINDOWING_QUARTZ
#include "quartz/gdkquartz.h"
#endif
#ifdef GDK_WINDOWING_WAYLAND
#include "wayland/gdkwayland.h"
#include
#endif
#ifdef GDK_WINDOWING_BROADWAY
#include "broadway/gdkbroadway.h"
#endif
struct _GtkInspectorGeneralPrivate
{
GtkWidget *version_box;
GtkWidget *env_box;
GtkWidget *x_box;
GtkWidget *gl_box;
GtkWidget *device_box;
GtkWidget *gtk_version;
GtkWidget *gdk_backend;
GtkWidget *gl_version;
GtkWidget *gl_vendor;
GtkWidget *prefix;
GtkWidget *xdg_data_home;
GtkWidget *xdg_data_dirs;
GtkWidget *gtk_path;
GtkWidget *gtk_exe_prefix;
GtkWidget *gtk_data_prefix;
GtkWidget *gsettings_schema_dir;
GtkWidget *x_display;
GtkWidget *x_rgba;
GtkWidget *x_composited;
GtkSizeGroup *labels;
GtkAdjustment *focus_adjustment;
};
G_DEFINE_TYPE_WITH_PRIVATE (GtkInspectorGeneral, gtk_inspector_general, GTK_TYPE_SCROLLED_WINDOW)
static void
init_version (GtkInspectorGeneral *gen)
{
const gchar *backend;
GdkDisplay *display;
display = gdk_display_get_default ();
#ifdef GDK_WINDOWING_X11
if (GDK_IS_X11_DISPLAY (display))
backend = "X11";
else
#endif
#ifdef GDK_WINDOWING_WAYLAND
if (GDK_IS_WAYLAND_DISPLAY (display))
backend = "Wayland";
else
#endif
#ifdef GDK_WINDOWING_BROADWAY
if (GDK_IS_BROADWAY_DISPLAY (display))
backend = "Broadway";
else
#endif
#ifdef GDK_WINDOWING_WIN32
if (GDK_IS_WIN32_DISPLAY (display))
backend = "Windows";
else
#endif
#ifdef GDK_WINDOWING_QUARTZ
if (GDK_IS_QUARTZ_DISPLAY (display))
backend = "Quartz";
else
#endif
backend = "Unknown";
gtk_label_set_text (GTK_LABEL (gen->priv->gtk_version), GTK_VERSION);
gtk_label_set_text (GTK_LABEL (gen->priv->gdk_backend), backend);
}
static G_GNUC_UNUSED void
append_extension_row (GtkInspectorGeneral *gen,
const gchar *ext,
gboolean have_ext)
{
GtkWidget *row, *box, *label, *check;
row = gtk_list_box_row_new ();
gtk_list_box_row_set_activatable (GTK_LIST_BOX_ROW (row), FALSE);
box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 40);
g_object_set (box, "margin", 10, NULL);
gtk_container_add (GTK_CONTAINER (row), box);
label = gtk_label_new (ext);
gtk_widget_set_halign (label, GTK_ALIGN_START);
gtk_widget_set_valign (label, GTK_ALIGN_BASELINE);
gtk_label_set_xalign (GTK_LABEL (label), 0.0);
gtk_box_pack_start (GTK_BOX (box), label, FALSE, FALSE, 0);
check = gtk_image_new_from_icon_name ("object-select-symbolic", GTK_ICON_SIZE_MENU);
gtk_widget_set_halign (check, GTK_ALIGN_END);
gtk_widget_set_valign (check, GTK_ALIGN_BASELINE);
gtk_widget_set_opacity (check, have_ext ? 1.0 : 0.0);
gtk_box_pack_start (GTK_BOX (box), check, TRUE, TRUE, 0);
gtk_widget_show_all (row);
gtk_list_box_insert (GTK_LIST_BOX (gen->priv->gl_box), row, -1);
gtk_size_group_add_widget (GTK_SIZE_GROUP (gen->priv->labels), label);
}
#ifdef GDK_WINDOWING_X11
static void
append_glx_extension_row (GtkInspectorGeneral *gen,
Display *dpy,
const gchar *ext)
{
append_extension_row (gen, ext, epoxy_has_glx_extension (dpy, 0, ext));
}
#endif
#ifdef GDK_WINDOWING_WAYLAND
static void
append_egl_extension_row (GtkInspectorGeneral *gen,
EGLDisplay *dpy,
const gchar *ext)
{
append_extension_row (gen, ext, epoxy_has_egl_extension (dpy, ext));
}
#endif
static void
init_gl (GtkInspectorGeneral *gen)
{
#ifdef GDK_WINDOWING_X11
if (GDK_IS_X11_DISPLAY (gdk_display_get_default ()))
{
GdkDisplay *display = gdk_display_get_default ();
Display *dpy = GDK_DISPLAY_XDISPLAY (display);
int error_base, event_base;
gchar *version;
if (!glXQueryExtension (dpy, &error_base, &event_base))
return;
version = g_strconcat ("GLX ", glXGetClientString (dpy, GLX_VERSION), NULL);
gtk_label_set_text (GTK_LABEL (gen->priv->gl_version), version);
g_free (version);
gtk_label_set_text (GTK_LABEL (gen->priv->gl_vendor), glXGetClientString (dpy, GLX_VENDOR));
append_glx_extension_row (gen, dpy, "GLX_ARB_create_context_profile");
append_glx_extension_row (gen, dpy, "GLX_SGI_swap_control");
append_glx_extension_row (gen, dpy, "GLX_EXT_texture_from_pixmap");
append_glx_extension_row (gen, dpy, "GLX_SGI_video_sync");
append_glx_extension_row (gen, dpy, "GLX_EXT_buffer_age");
append_glx_extension_row (gen, dpy, "GLX_OML_sync_control");
append_glx_extension_row (gen, dpy, "GLX_ARB_multisample");
append_glx_extension_row (gen, dpy, "GLX_EXT_visual_rating");
}
else
#endif
#ifdef GDK_WINDOWING_WAYLAND
if (GDK_IS_WAYLAND_DISPLAY (gdk_display_get_default ()))
{
GdkDisplay *display = gdk_display_get_default ();
EGLDisplay *dpy;
EGLint major, minor;
gchar *version;
dpy = eglGetDisplay ((EGLNativeDisplayType)gdk_wayland_display_get_wl_display (display));
if (!eglInitialize (dpy, &major, &minor))
return;
version = g_strconcat ("EGL ", eglQueryString (dpy, EGL_VERSION), NULL);
gtk_label_set_text (GTK_LABEL (gen->priv->gl_version), version);
g_free (version);
gtk_label_set_text (GTK_LABEL (gen->priv->gl_vendor), eglQueryString (dpy, EGL_VENDOR));
append_egl_extension_row (gen, dpy, "EGL_KHR_create_context");
append_egl_extension_row (gen, dpy, "EGL_EXT_buffer_age");
append_egl_extension_row (gen, dpy, "EGL_EXT_swap_buffers_with_damage");
append_egl_extension_row (gen, dpy, "EGL_KHR_surfaceless_context");
}
else
#endif
{
gtk_label_set_text (GTK_LABEL (gen->priv->gl_version), C_("GL version", "None"));
gtk_label_set_text (GTK_LABEL (gen->priv->gl_vendor), C_("GL vendor", "None"));
}
}
static void
set_monospace_font (GtkWidget *w)
{
PangoAttrList *attrs;
attrs = pango_attr_list_new ();
pango_attr_list_insert (attrs, pango_attr_fallback_new (FALSE));
pango_attr_list_insert (attrs, pango_attr_family_new ("Monospace"));
gtk_label_set_attributes (GTK_LABEL (w), attrs);
pango_attr_list_unref (attrs);
}
static void
set_path_label (GtkWidget *w,
const gchar *var)
{
const gchar *v;
v = g_getenv (var);
if (v != NULL)
{
set_monospace_font (w);
gtk_label_set_text (GTK_LABEL (w), v);
}
else
{
GtkWidget *r;
r = gtk_widget_get_ancestor (w, GTK_TYPE_LIST_BOX_ROW);
gtk_widget_hide (r);
}
}
static void
init_env (GtkInspectorGeneral *gen)
{
set_monospace_font (gen->priv->prefix);
gtk_label_set_text (GTK_LABEL (gen->priv->prefix), _gtk_get_data_prefix ());
set_path_label (gen->priv->xdg_data_home, "XDG_DATA_HOME");
set_path_label (gen->priv->xdg_data_dirs, "XDG_DATA_DIRS");
set_path_label (gen->priv->gtk_path, "GTK_PATH");
set_path_label (gen->priv->gtk_exe_prefix, "GTK_EXE_PREFIX");
set_path_label (gen->priv->gtk_data_prefix, "GTK_DATA_PREFIX");
set_path_label (gen->priv->gsettings_schema_dir, "GSETTINGS_SCHEMA_DIR");
}
static void
add_label_row (GtkListBox *list,
const char *name,
const char *value,
gint indent)
{
GtkWidget *box;
GtkWidget *label;
GtkWidget *row;
box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 40);
g_object_set (box,
"margin", 10,
"margin-start", 10 + indent,
NULL);
label = gtk_label_new (name);
gtk_widget_set_halign (label, GTK_ALIGN_START);
gtk_widget_set_valign (label, GTK_ALIGN_BASELINE);
gtk_label_set_xalign (GTK_LABEL (label), 0.0);
gtk_box_pack_start (GTK_BOX (box), label, FALSE, FALSE, 0);
label = gtk_label_new (value);
gtk_label_set_selectable (GTK_LABEL (label), TRUE);
gtk_widget_set_halign (label, GTK_ALIGN_END);
gtk_widget_set_valign (label, GTK_ALIGN_BASELINE);
gtk_label_set_xalign (GTK_LABEL (label), 1.0);
gtk_box_pack_start (GTK_BOX (box), label, TRUE, TRUE, 0);
row = gtk_list_box_row_new ();
gtk_container_add (GTK_CONTAINER (row), box);
gtk_list_box_row_set_activatable (GTK_LIST_BOX_ROW (row), FALSE);
gtk_widget_show_all (row);
gtk_list_box_insert (GTK_LIST_BOX (list), row, -1);
}
static void
populate_display (GdkScreen *screen, GtkInspectorGeneral *gen)
{
gchar *name;
gint i;
GList *children, *l;
GtkWidget *child;
children = gtk_container_get_children (GTK_CONTAINER (gen->priv->x_box));
for (l = children; l; l = l->next)
{
child = l->data;
if (gtk_widget_is_ancestor (gen->priv->x_display, child) ||
gtk_widget_is_ancestor (gen->priv->x_rgba, child) ||
gtk_widget_is_ancestor (gen->priv->x_composited, child))
continue;
gtk_widget_destroy (child);
}
g_list_free (children);
name = gdk_screen_make_display_name (screen);
gtk_label_set_label (GTK_LABEL (gen->priv->x_display), name);
g_free (name);
if (gdk_screen_get_rgba_visual (screen) != NULL)
gtk_widget_show (gen->priv->x_rgba);
if (gdk_screen_is_composited (screen))
gtk_widget_show (gen->priv->x_composited);
for (i = 0; i < gdk_screen_get_n_monitors (screen); i++)
{
gchar *name;
gchar *value;
gchar *plug_name;
GdkRectangle rect;
gint w, h, wmm, hmm, scale;
plug_name = gdk_screen_get_monitor_plug_name (screen, i);
if (plug_name)
name = g_strdup_printf ("Monitor %s", plug_name);
else
name = g_strdup_printf ("Monitor %d", i);
g_free (plug_name);
gdk_screen_get_monitor_geometry (screen, i, &rect);
w = rect.width;
h = rect.height;
wmm = gdk_screen_get_monitor_width_mm (screen, i);
hmm = gdk_screen_get_monitor_height_mm (screen, i);
scale = gdk_screen_get_monitor_scale_factor (screen, i);
value = g_strdup_printf ("%d × %d%s, %d × %d mm²",
w, h, scale == 2 ? " @ 2" : "",
wmm, hmm);
add_label_row (GTK_LIST_BOX (gen->priv->x_box), name, value, 0);
g_free (name);
g_free (value);
}
}
static void
init_display (GtkInspectorGeneral *gen)
{
GdkScreen *screen;
screen = gdk_screen_get_default ();
g_signal_connect (screen, "size-changed", G_CALLBACK (populate_display), gen);
g_signal_connect (screen, "composited-changed", G_CALLBACK (populate_display), gen);
g_signal_connect (screen, "monitors-changed", G_CALLBACK (populate_display), gen);
populate_display (screen, gen);
}
static void populate_seats (GtkInspectorGeneral *gen);
static void
add_device (GtkInspectorGeneral *gen,
GdkDevice *device)
{
const gchar *name, *value;
GString *str;
int i;
guint n_touches;
gchar *text;
name = gdk_device_get_name (device);
switch (gdk_device_get_source (device))
{
case GDK_SOURCE_MOUSE:
value = "Mouse";
break;
case GDK_SOURCE_PEN:
value = "Pen";
break;
case GDK_SOURCE_ERASER:
value = "Eraser";
break;
case GDK_SOURCE_CURSOR:
value = "Cursor";
break;
case GDK_SOURCE_KEYBOARD:
value = "Keyboard";
break;
case GDK_SOURCE_TOUCHSCREEN:
value = "Touchscreen";
break;
case GDK_SOURCE_TOUCHPAD:
value = "Touchpad";
break;
default:
value = "Unknown";
break;
}
add_label_row (GTK_LIST_BOX (gen->priv->device_box), name, value, 10);
str = g_string_new ("");
if (gdk_device_get_source (device) != GDK_SOURCE_KEYBOARD &&
gdk_device_get_n_axes (device) > 0)
{
for (i = 0; i < gdk_device_get_n_axes (device); i++)
{
switch (gdk_device_get_axis_use (device, i))
{
case GDK_AXIS_X:
if (str->len > 0)
g_string_append (str, ", ");
g_string_append (str, "X");
break;
case GDK_AXIS_Y:
if (str->len > 0)
g_string_append (str, ", ");
g_string_append (str, "Y");
break;
case GDK_AXIS_PRESSURE:
if (str->len > 0)
g_string_append (str, ", ");
g_string_append (str, "Pressure");
break;
case GDK_AXIS_XTILT:
if (str->len > 0)
g_string_append (str, ", ");
g_string_append (str, "X Tilt");
break;
case GDK_AXIS_YTILT:
if (str->len > 0)
g_string_append (str, ", ");
g_string_append (str, "Y Tilt");
break;
case GDK_AXIS_WHEEL:
if (str->len > 0)
g_string_append (str, ", ");
g_string_append (str, "Wheel");
break;
default:
break;
}
}
}
if (str->len > 0)
add_label_row (GTK_LIST_BOX (gen->priv->device_box), "Axes", str->str, 20);
g_string_free (str, TRUE);
g_object_get (device, "num-touches", &n_touches, NULL);
if (n_touches > 0)
{
text = g_strdup_printf ("%d", n_touches);
add_label_row (GTK_LIST_BOX (gen->priv->device_box), "Touches", text, 20);
g_free (text);
}
}
static void
add_seat (GtkInspectorGeneral *gen,
GdkSeat *seat,
int num)
{
GdkSeatCapabilities capabilities;
struct {
GdkSeatCapabilities cap;
const char *name;
} caps[] = {
{ GDK_SEAT_CAPABILITY_POINTER, "Pointer" },
{ GDK_SEAT_CAPABILITY_TOUCH, "Touch" },
{ GDK_SEAT_CAPABILITY_TABLET_STYLUS, "Tablet" },
{ GDK_SEAT_CAPABILITY_KEYBOARD, "Keyboard" },
{ 0, NULL }
};
GString *str;
char *text;
int i;
GList *list, *l;
if (!g_object_get_data (G_OBJECT (seat), "inspector-connected"))
{
g_object_set_data (G_OBJECT (seat), "inspector-connected", GINT_TO_POINTER (1));
g_signal_connect_swapped (seat, "device-added", G_CALLBACK (populate_seats), gen);
g_signal_connect_swapped (seat, "device-removed", G_CALLBACK (populate_seats), gen);
}
text = g_strdup_printf ("Seat %d", num);
str = g_string_new ("");
capabilities = gdk_seat_get_capabilities (seat);
for (i = 0; caps[i].cap != 0; i++)
{
if (capabilities & caps[i].cap)
{
if (str->len > 0)
g_string_append (str, ", ");
g_string_append (str, caps[i].name);
}
}
add_label_row (GTK_LIST_BOX (gen->priv->device_box), text, str->str, 0);
g_free (text);
g_string_free (str, FALSE);
list = gdk_seat_get_slaves (seat, GDK_SEAT_CAPABILITY_ALL);
for (l = list; l; l = l->next)
add_device (gen, GDK_DEVICE (l->data));
g_list_free (list);
}
static void
populate_seats (GtkInspectorGeneral *gen)
{
GdkDisplay *display = gdk_display_get_default ();
GList *list, *l;
int i;
list = gtk_container_get_children (GTK_CONTAINER (gen->priv->device_box));
for (l = list; l; l = l->next)
gtk_widget_destroy (GTK_WIDGET (l->data));
g_list_free (list);
list = gdk_display_list_seats (display);
for (l = list, i = 0; l; l = l->next, i++)
add_seat (gen, GDK_SEAT (l->data), i);
g_list_free (list);
}
static void
init_device (GtkInspectorGeneral *gen)
{
GdkDisplay *display = gdk_display_get_default ();
g_signal_connect_swapped (display, "seat-added", G_CALLBACK (populate_seats), gen);
g_signal_connect_swapped (display, "seat-removed", G_CALLBACK (populate_seats), gen);
populate_seats (gen);
}
static void
gtk_inspector_general_init (GtkInspectorGeneral *gen)
{
gen->priv = gtk_inspector_general_get_instance_private (gen);
gtk_widget_init_template (GTK_WIDGET (gen));
init_version (gen);
init_env (gen);
init_display (gen);
init_gl (gen);
init_device (gen);
}
static gboolean
keynav_failed (GtkWidget *widget, GtkDirectionType direction, GtkInspectorGeneral *gen)
{
GtkWidget *next;
gdouble value, lower, upper, page;
if (direction == GTK_DIR_DOWN && widget == gen->priv->version_box)
next = gen->priv->env_box;
else if (direction == GTK_DIR_DOWN && widget == gen->priv->env_box)
next = gen->priv->x_box;
else if (direction == GTK_DIR_DOWN && widget == gen->priv->x_box)
next = gen->priv->gl_box;
else if (direction == GTK_DIR_DOWN && widget == gen->priv->gl_box)
next = gen->priv->device_box;
else if (direction == GTK_DIR_UP && widget == gen->priv->device_box)
next = gen->priv->gl_box;
else if (direction == GTK_DIR_UP && widget == gen->priv->gl_box)
next = gen->priv->x_box;
else if (direction == GTK_DIR_UP && widget == gen->priv->x_box)
next = gen->priv->env_box;
else if (direction == GTK_DIR_UP && widget == gen->priv->env_box)
next = gen->priv->version_box;
else
next = NULL;
if (next)
{
gtk_widget_child_focus (next, direction);
return TRUE;
}
value = gtk_adjustment_get_value (gen->priv->focus_adjustment);
lower = gtk_adjustment_get_lower (gen->priv->focus_adjustment);
upper = gtk_adjustment_get_upper (gen->priv->focus_adjustment);
page = gtk_adjustment_get_page_size (gen->priv->focus_adjustment);
if (direction == GTK_DIR_UP && value > lower)
{
gtk_adjustment_set_value (gen->priv->focus_adjustment, lower);
return TRUE;
}
else if (direction == GTK_DIR_DOWN && value < upper - page)
{
gtk_adjustment_set_value (gen->priv->focus_adjustment, upper - page);
return TRUE;
}
return FALSE;
}
static void
gtk_inspector_general_constructed (GObject *object)
{
GtkInspectorGeneral *gen = GTK_INSPECTOR_GENERAL (object);
G_OBJECT_CLASS (gtk_inspector_general_parent_class)->constructed (object);
gen->priv->focus_adjustment = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (gen));
gtk_container_set_focus_vadjustment (GTK_CONTAINER (gtk_bin_get_child (GTK_BIN (gen))),
gen->priv->focus_adjustment);
g_signal_connect (gen->priv->version_box, "keynav-failed", G_CALLBACK (keynav_failed), gen);
g_signal_connect (gen->priv->env_box, "keynav-failed", G_CALLBACK (keynav_failed), gen);
g_signal_connect (gen->priv->x_box, "keynav-failed", G_CALLBACK (keynav_failed), gen);
g_signal_connect (gen->priv->gl_box, "keynav-failed", G_CALLBACK (keynav_failed), gen);
g_signal_connect (gen->priv->device_box, "keynav-failed", G_CALLBACK (keynav_failed), gen);
}
static void
gtk_inspector_general_class_init (GtkInspectorGeneralClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
object_class->constructed = gtk_inspector_general_constructed;
gtk_widget_class_set_template_from_resource (widget_class, "/org/gtk/libgtk/inspector/general.ui");
gtk_widget_class_bind_template_child_private (widget_class, GtkInspectorGeneral, version_box);
gtk_widget_class_bind_template_child_private (widget_class, GtkInspectorGeneral, env_box);
gtk_widget_class_bind_template_child_private (widget_class, GtkInspectorGeneral, x_box);
gtk_widget_class_bind_template_child_private (widget_class, GtkInspectorGeneral, gl_box);
gtk_widget_class_bind_template_child_private (widget_class, GtkInspectorGeneral, gtk_version);
gtk_widget_class_bind_template_child_private (widget_class, GtkInspectorGeneral, gdk_backend);
gtk_widget_class_bind_template_child_private (widget_class, GtkInspectorGeneral, gl_version);
gtk_widget_class_bind_template_child_private (widget_class, GtkInspectorGeneral, gl_vendor);
gtk_widget_class_bind_template_child_private (widget_class, GtkInspectorGeneral, prefix);
gtk_widget_class_bind_template_child_private (widget_class, GtkInspectorGeneral, xdg_data_home);
gtk_widget_class_bind_template_child_private (widget_class, GtkInspectorGeneral, xdg_data_dirs);
gtk_widget_class_bind_template_child_private (widget_class, GtkInspectorGeneral, gtk_path);
gtk_widget_class_bind_template_child_private (widget_class, GtkInspectorGeneral, gtk_exe_prefix);
gtk_widget_class_bind_template_child_private (widget_class, GtkInspectorGeneral, gtk_data_prefix);
gtk_widget_class_bind_template_child_private (widget_class, GtkInspectorGeneral, gsettings_schema_dir);
gtk_widget_class_bind_template_child_private (widget_class, GtkInspectorGeneral, labels);
gtk_widget_class_bind_template_child_private (widget_class, GtkInspectorGeneral, x_display);
gtk_widget_class_bind_template_child_private (widget_class, GtkInspectorGeneral, x_composited);
gtk_widget_class_bind_template_child_private (widget_class, GtkInspectorGeneral, x_rgba);
gtk_widget_class_bind_template_child_private (widget_class, GtkInspectorGeneral, device_box);
}
// vim: set et sw=2 ts=2: