12to11/xsettings.c
2022-09-12 13:24:50 +00:00

432 lines
10 KiB
C

/* Wayland compositor running on top of an X server.
Copyright (C) 2022 to various contributors.
This file is part of 12to11.
12to11 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, either version 3 of the License, or (at your
option) any later version.
12to11 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 General Public License
for more details.
You should have received a copy of the GNU General Public License
along with 12to11. If not, see <https://www.gnu.org/licenses/>. */
#include <byteswap.h>
#include <stdio.h>
#include <string.h>
#include "compositor.h"
typedef struct _IntegerValueListener IntegerValueListener;
typedef enum _SettingType SettingType;
enum _SettingType
{
Integer = 0,
String = 1,
RgbColor = 2,
};
struct _IntegerValueListener
{
/* Function called when the value of the setting changes. */
void (*new_value) (int);
/* The last serial at which the value changed; -1 if the value is
not yet known. */
long long last_change_serial;
/* The name of the setting that this listener wants to react to. */
const char *setting_name;
/* The next listener in this chain. */
IntegerValueListener *next;
};
/* The settings manager window. */
static Window xsettings_window;
/* Manager selection atom. */
static Atom xsettings_atom;
/* List of all listeners for integer setting changes. */
IntegerValueListener *integer_listeners;
/* Key for selected input. */
static RootWindowSelection *input_key;
#define PadValue(n, m) ((n + m - 1) & (~(m - 1)))
static void
Swap32 (unsigned char byteorder, uint32_t *cardinal)
{
#ifdef __BIG_ENDIAN__
if (byteorder == MSBFirst)
return;
#else
if (byteorder == LSBFirst)
return;
#endif
*cardinal = bswap_32 (*cardinal);
}
static void
SwapI32 (unsigned char byteorder, int *cardinal)
{
#ifdef __BIG_ENDIAN__
if (byteorder == MSBFirst)
return;
#else
if (byteorder == LSBFirst)
return;
#endif
*cardinal = bswap_32 (*cardinal);
}
static void
Swap16 (unsigned char byteorder, uint16_t *cardinal)
{
#ifdef __BIG_ENDIAN__
if (byteorder == MSBFirst)
return;
#else
if (byteorder == LSBFirst)
return;
#endif
*cardinal = bswap_16 (*cardinal);
}
static void
HandleIntegerValue (char *name, int value, uint32_t last_change_serial)
{
IntegerValueListener *listener;
for (listener = integer_listeners; listener; listener = listener->next)
{
if (!strcmp (listener->setting_name, name)
&& last_change_serial > listener->last_change_serial)
{
listener->last_change_serial = last_change_serial;
listener->new_value (value);
}
}
}
static void
ReadSettingsData (void)
{
unsigned char *prop_data, *read, *name_start, *value_start;
Atom actual_type;
int actual_format;
Status rc;
unsigned long nitems_return, bytes_after;
unsigned char byteorder;
uint32_t serial, n_settings, value_length, last_change_serial;
uint16_t name_length;
int i, value;
uint8_t type;
XRenderColor color;
ptrdiff_t nitems;
char *name_buffer;
prop_data = NULL;
name_buffer = NULL;
/* Now read the actual property data. */
CatchXErrors ();
rc = XGetWindowProperty (compositor.display, xsettings_window,
_XSETTINGS_SETTINGS, 0, LONG_MAX, False,
_XSETTINGS_SETTINGS, &actual_type,
&actual_format, &nitems_return, &bytes_after,
&prop_data);
if (UncatchXErrors (NULL))
{
/* An error occured while reading property data. This means
that the manager window is gone, so begin watching for it
again. */
if (prop_data)
XFree (prop_data);
XLInitXSettings ();
return;
}
if (rc != Success || actual_type != _XSETTINGS_SETTINGS
|| actual_format != 8 || !nitems_return)
{
/* The property is invalid. */
if (prop_data)
XFree (prop_data);
return;
}
read = prop_data;
nitems = nitems_return;
/* Begin reading property data. */
if (nitems < 12)
goto end;
nitems -= 12;
/* CARD8, byte-order. */
byteorder = prop_data[0];
prop_data++;
/* CARD8 + CARD16, padding. */
prop_data += 3;
/* CARD32, serial. */
serial = ((uint32_t *) prop_data)[0];
prop_data += 4;
Swap32 (byteorder, &serial);
/* CARD32, number of settings in the property. */
n_settings = ((uint32_t *) prop_data)[0];
prop_data += 4;
Swap32 (byteorder, &n_settings);
/* Begin reading each entry. */
i = 0;
while (i < n_settings)
{
if (nitems < 4)
goto end;
/* CARD8, settings type. */
type = prop_data[0];
prop_data++;
/* CARD8, padding. */
prop_data++;
/* CARD16, name length. */
name_length = ((uint16_t *) prop_data)[0];
prop_data += 2;
Swap16 (byteorder, &name_length);
if (nitems < PadValue (name_length, 4) +4)
goto end;
nitems -= PadValue (name_length, 4) + 4;
/* NAME_LENGTH + padding, property name. */
name_start = prop_data;
prop_data += PadValue (name_length, 4);
/* CARD32, last-change-serial. */
last_change_serial = ((uint32_t *) prop_data)[0];
prop_data += 4;
switch (type)
{
case String:
if (nitems < 4)
goto end;
nitems -= 4;
/* CARD32, value length. */
value_length = ((uint32_t *) prop_data)[0];
prop_data += 4;
Swap32 (byteorder, &value_length);
if (nitems < PadValue (value_length, 4))
goto end;
nitems -= PadValue (value_length, 4);
/* VALUE_LENGTH + padding, property value. */
value_start = prop_data;
prop_data += PadValue (value_length, 4);
/* Note that string values are not yet handled. */
(void) value_start;
break;
case Integer:
if (nitems < 4)
goto end;
nitems -= 4;
/* INT32, value. */
value = ((int32_t *) prop_data)[0];
prop_data += 4;
SwapI32 (byteorder, &value);
/* Now, write the name to the name buffer, with NULL
termination. */
name_buffer = XLRealloc (name_buffer, name_length + 1);
memcpy (name_buffer, name_start, name_length);
name_buffer[name_length] = '\0';
/* And run any change handlers. */
HandleIntegerValue (name_buffer, value, last_change_serial);
break;
case RgbColor:
if (nitems < 8)
goto end;
nitems -= 8;
/* CARD16, red. */
color.red = ((uint16_t *) prop_data)[0];
prop_data += 2;
Swap16 (byteorder, &color.red);
/* CARD16, green. */
color.green = ((uint16_t *) prop_data)[0];
prop_data += 2;
Swap16 (byteorder, &color.green);
/* CARD16, blue. */
color.blue = ((uint16_t *) prop_data)[0];
prop_data += 2;
Swap16 (byteorder, &color.blue);
/* CARD16, alpha. */
color.alpha = ((uint16_t *) prop_data)[0];
prop_data += 2;
Swap16 (byteorder, &color.alpha);
break;
}
i++;
}
end:
if (read)
XFree (read);
XFree (name_buffer);
}
Bool
XLHandleOneXEventForXSettings (XEvent *event)
{
if (event->type == ClientMessage
&& event->xclient.message_type == MANAGER
&& event->xclient.data.l[1] == xsettings_atom)
{
/* Set the settings manager window, deselect for StructureNotify
on the root window, and read the new settings data. */
if (input_key)
XLDeselectInputFromRootWindow (input_key);
input_key = NULL;
xsettings_window = event->xclient.data.l[2];
CatchXErrors ();
/* Also select for PropertyNotify on the settings window, so we
can get notifications once properties change. */
XSelectInput (compositor.display, xsettings_window,
PropertyChangeMask);
if (UncatchXErrors (NULL))
/* The settings window vanished; select for manager events
again until we obtain the new settings window. */
XLInitXSettings ();
else
/* Begin reading settings data. */
ReadSettingsData ();
return True;
}
else if (event->type == PropertyNotify
&& event->xproperty.window == xsettings_window
&& event->xproperty.atom == _XSETTINGS_SETTINGS)
{
CatchXErrors ();
/* Also select for PropertyNotify on the settings window, so we
can get notifications once properties change. */
XSelectInput (compositor.display, xsettings_window,
PropertyChangeMask);
if (UncatchXErrors (NULL))
/* The settings window vanished; select for manager events
again until we obtain the new settings window. */
XLInitXSettings ();
else
/* Begin reading settings data. */
ReadSettingsData ();
return True;
}
else if (event->type == DestroyNotify
&& event->xdestroywindow.window == xsettings_window)
{
xsettings_window = None;
/* The settings window was destroyed; select for manager events
again until the settings window reappears. */
XLInitXSettings ();
}
return False;
}
void
XLListenToIntegerSetting (const char *name, void (*callback) (int))
{
IntegerValueListener *listener;
listener = XLMalloc (sizeof *listener);
listener->last_change_serial = -1;
listener->new_value = callback;
listener->setting_name = name;
listener->next = integer_listeners;
integer_listeners = listener;
}
void
XLInitXSettings (void)
{
IntegerValueListener *listener;
char buffer[64];
if (xsettings_atom == None)
{
/* Intern the manager selection atom, if it doesn't already
exist. */
sprintf (buffer, "_XSETTINGS_S%d",
DefaultScreen (compositor.display));
xsettings_atom = XInternAtom (compositor.display, buffer,
False);
}
/* Reset the last change serial of all listeners, since the settings
provider window has vanished. */
for (listener = integer_listeners; listener; listener = listener->next)
listener->last_change_serial = -1;
/* Grab the server, and get the value of the manager selection. */
XGrabServer (compositor.display);
xsettings_window = XGetSelectionOwner (compositor.display,
xsettings_atom);
/* If the settings window doesn't exist yet, select for MANAGER
messages on the root window. */
if (!xsettings_window && !input_key)
input_key = XLSelectInputFromRootWindow (StructureNotifyMask);
/* If the settings window exists, then begin reading property
data. */
if (xsettings_window != None)
{
/* Also select for PropertyNotify events. */
XSelectInput (compositor.display, xsettings_window,
PropertyChangeMask);
ReadSettingsData ();
}
/* Finally, ungrab the X server. */
XUngrabServer (compositor.display);
}