fltk/src/drivers/X11/Fl_X11_Screen_Driver.cxx
Albrecht Schlosser d3a3ab40b7 Replace setenv() with putenv() on old systems (+937)
Add system check for setenv() function in configure and CMake.
2024-03-18 22:29:50 +01:00

1397 lines
43 KiB
C++

//
// Definition of X11 Screen interface
//
// Copyright 1998-2024 by Bill Spitzak and others.
//
// This library is free software. Distribution and use rights are outlined in
// the file "COPYING" which should have been included with this file. If this
// file is missing or damaged, see the license at:
//
// https://www.fltk.org/COPYING.php
//
// Please see the following page on how to report bugs and issues:
//
// https://www.fltk.org/bugs.php
//
#include <config.h>
#include "Fl_X11_Screen_Driver.H"
#include "Fl_X11_Window_Driver.H"
#include "../Posix/Fl_Posix_System_Driver.H"
#include <FL/Fl.H>
#include <FL/platform.H>
#include <FL/fl_ask.H>
#include <FL/Fl_Box.H>
#include <FL/Fl_Image_Surface.H>
#include <FL/Fl_Tooltip.H>
#include <FL/filename.H>
#include <sys/time.h>
#include "../../Fl_Timeout.h"
#include "../../flstring.h"
#if HAVE_XINERAMA
# include <X11/extensions/Xinerama.h>
#endif
# include <X11/Xutil.h>
# ifdef __sgi
# include <X11/extensions/readdisplay.h>
# else
# include <stdlib.h>
# endif // __sgi
#ifdef DEBUG
# include <stdio.h>
#endif // DEBUG
extern Atom fl_NET_WORKAREA;
// these are set by Fl::args() and FL_OVERRIDE any system colors: from Fl_get_system_colors.cxx
extern const char *fl_fg;
extern const char *fl_bg;
extern const char *fl_bg2;
// end of extern additions workaround
#if !USE_XFT
extern char *fl_get_font_xfld(int fnum, int size);
#endif
XIM Fl_X11_Screen_Driver::xim_im = 0;
XIC Fl_X11_Screen_Driver::xim_ic = 0;
int Fl_X11_Screen_Driver::fl_spotf = -1;
int Fl_X11_Screen_Driver::fl_spots = -1;
XRectangle Fl_X11_Screen_Driver::fl_spot;
char Fl_X11_Screen_Driver::fl_is_over_the_spot = 0;
Window Fl_X11_Screen_Driver::xim_win = 0;
Fl_X11_Screen_Driver::Fl_X11_Screen_Driver() : Fl_Unix_Screen_Driver() {
// X11 screen driver does not use a key table
key_table = NULL;
key_table_size = 0;
}
void Fl_X11_Screen_Driver::display(const char *d) {
if (!d) return;
// Issue #937:
// setenv() is available since POSIX.1-2001
// https://pubs.opengroup.org/onlinepubs/009604499/functions/setenv.html
#if HAVE_SETENV
setenv("DISPLAY", d, 1);
#else // HAVE_SETENV
// Use putenv() for old systems (similar to FLTK 1.3)
static char e[1024];
strcpy(e, "DISPLAY=");
strlcat(e, d, sizeof(e));
for (char *c = e + 8; *c != ':'; c++) {
if (!*c) {
strlcat(e,":0.0",sizeof(e));
break;
}
}
putenv(e);
#endif // HAVE_SETENV
}
void fl_x11_use_display(Display *d) {
fl_display = d;
}
int Fl_X11_Screen_Driver::XParseGeometry(const char* string, int* x, int* y,
unsigned int* width, unsigned int* height) {
return ::XParseGeometry(string, x, y, width, height);
}
void Fl_X11_Screen_Driver::own_colormap() {
fl_open_display();
#if USE_COLORMAP
switch (fl_visual->c_class) {
case GrayScale :
case PseudoColor :
case DirectColor :
break;
default:
return; // don't do anything for non-colormapped visuals
}
int i;
XColor colors[16];
// Get the first 16 colors from the default colormap...
for (i = 0; i < 16; i ++) colors[i].pixel = i;
XQueryColors(fl_display, fl_colormap, colors, 16);
// Create a new colormap...
fl_colormap = XCreateColormap(fl_display,
RootWindow(fl_display,fl_screen),
fl_visual->visual, AllocNone);
// Copy those first 16 colors to our own colormap:
for (i = 0; i < 16; i ++)
XAllocColor(fl_display, fl_colormap, colors + i);
#endif // USE_COLORMAP
}
const char *Fl_X11_Screen_Driver::shortcut_add_key_name(unsigned key, char *p, char *buf, const char **eom)
{
const char* q;
if (key == FL_Enter || key == '\r') q = "Enter"; // don't use Xlib's "Return":
else if (key > 32 && key < 0x100) q = 0;
else q = XKeysymToString(key);
if (!q) {
p += fl_utf8encode(fl_toupper(key), p);
*p = 0;
return buf;
}
if (p > buf) {
strcpy(p,q);
return buf;
} else {
if (eom) *eom = q;
return q;
}
}
static int test_visual(XVisualInfo& v, int flags) {
if (v.screen != fl_screen) return 0;
#if USE_COLORMAP
if (!(flags & FL_INDEX)) {
if (v.c_class != StaticColor && v.c_class != TrueColor) return 0;
if (v.depth <= 8) return 0; // fltk will work better in colormap mode
}
if (flags & FL_RGB8) {
if (v.depth < 24) return 0;
}
// for now, fltk does not like colormaps of more than 8 bits:
if ((v.c_class&1) && v.depth > 8) return 0;
#else
// simpler if we can't use colormapped visuals at all:
if (v.c_class != StaticColor && v.c_class != TrueColor) return 0;
#endif
return 1;
}
int Fl_X11_Screen_Driver::visual(int flags)
{
if (flags & FL_DOUBLE) return 0;
open_display();
// always use default if possible:
if (test_visual(*fl_visual, flags)) return 1;
// get all the visuals:
XVisualInfo vTemplate;
int num;
XVisualInfo *visualList = XGetVisualInfo(fl_display, 0, &vTemplate, &num);
// find all matches, use the one with greatest depth:
XVisualInfo *found = 0;
for (int i=0; i<num; i++) if (test_visual(visualList[i], flags)) {
if (!found || found->depth < visualList[i].depth)
found = &visualList[i];
}
if (!found) {XFree((void*)visualList); return 0;}
fl_visual = found;
fl_colormap = XCreateColormap(fl_display, RootWindow(fl_display,fl_screen),
fl_visual->visual, AllocNone);
return 1;
}
static int fl_workarea_xywh[4] = { -1, -1, -1, -1 };
void Fl_X11_Screen_Driver::init_workarea()
{
Atom actual;
unsigned long count, remaining;
int format;
long *xywh = 0;
/* If there are several screens, the _NET_WORKAREA property
does not give the work area of the main screen, but that of all screens together.
Therefore, we use this property only when there is a single screen,
and fall back to the main screen full area when there are several screens.
*/
if (Fl::screen_count() > 1 || XGetWindowProperty(fl_display, RootWindow(fl_display, fl_screen),
fl_NET_WORKAREA, 0, 4, False,
XA_CARDINAL, &actual, &format, &count, &remaining,
(unsigned char **)&xywh) || !xywh || !xywh[2] ||
!xywh[3])
{
fl_workarea_xywh[0] = screens[0].x_org;
fl_workarea_xywh[1] = screens[0].y_org;
fl_workarea_xywh[2] = screens[0].width;
fl_workarea_xywh[3] = screens[0].height;
}
else
{
fl_workarea_xywh[0] = xywh[0];
fl_workarea_xywh[1] = xywh[1];
fl_workarea_xywh[2] = xywh[2];
fl_workarea_xywh[3] = xywh[3];
}
if ( xywh ) { XFree(xywh); xywh = 0; }
}
int Fl_X11_Screen_Driver::x() {
if (!fl_display) open_display();
return fl_workarea_xywh[0]
#if USE_XFT
/ screens[0].scale
#endif
;
}
int Fl_X11_Screen_Driver::y() {
if (!fl_display) open_display();
return fl_workarea_xywh[1]
#if USE_XFT
/ screens[0].scale
#endif
;
}
int Fl_X11_Screen_Driver::w() {
if (!fl_display) open_display();
return fl_workarea_xywh[2]
#if USE_XFT
/ screens[0].scale
#endif
;
}
int Fl_X11_Screen_Driver::h() {
if (!fl_display) open_display();
return fl_workarea_xywh[3]
#if USE_XFT
/ screens[0].scale
#endif
;
}
#define USE_XRANDR (HAVE_DLSYM && HAVE_DLFCN_H) // means attempt to dynamically load libXrandr.so
#if USE_XRANDR
#include <dlfcn.h>
typedef struct {
int width, height;
int mwidth, mheight;
} XRRScreenSize;
typedef XRRScreenSize* (*XRRSizes_type)(Display *dpy, int screen, int *nsizes);
#endif // USE_XRANDR
void Fl_X11_Screen_Driver::init() {
if (!fl_display) open_display();
int dpi_by_randr = 0;
float dpih = 0.0f, dpiv = 0.0f;
#if USE_XRANDR
static XRRSizes_type XRRSizes_f = NULL;
if (!XRRSizes_f) {
XRRSizes_f = (XRRSizes_type)Fl_Posix_System_Driver::dlopen_or_dlsym("libXrandr", "XRRSizes");
}
if (XRRSizes_f) {
int nscreens;
XRRScreenSize *ssize = XRRSizes_f(fl_display, fl_screen, &nscreens);
//for (int i=0; i<nscreens; i++)
// printf("width=%d height=%d mwidth=%d mheight=%d\n",
// ssize[i].width,ssize[i].height,ssize[i].mwidth,ssize[i].mheight);
if (nscreens > 0) { // Note: XRRSizes() *may* return nscreens == 0, see docs
int mm = ssize[0].mwidth;
dpih = mm ? ssize[0].width*25.4f/mm : 0.0f;
mm = ssize[0].mheight;
dpiv = mm ? ssize[0].height*25.4f/mm : 0.0f;
dpi_by_randr = 1;
}
}
#endif // USE_XRANDR
#if HAVE_XINERAMA
if (XineramaIsActive(fl_display)) {
XineramaScreenInfo *xsi = XineramaQueryScreens(fl_display, &num_screens);
if (num_screens > MAX_SCREENS) num_screens = MAX_SCREENS;
/* There's no way to use different DPI for different Xinerama screens. */
for (int i=0; i<num_screens; i++) {
screens[i].x_org = xsi[i].x_org;
screens[i].y_org = xsi[i].y_org;
screens[i].width = xsi[i].width;
screens[i].height = xsi[i].height;
if (dpi_by_randr) {
dpi[i][0] = dpih;
dpi[i][1] = dpiv;
} else {
int mm = DisplayWidthMM(fl_display, fl_screen);
dpi[i][0] = mm ? screens[i].width*25.4f/mm : 0.0f;
mm = DisplayHeightMM(fl_display, fl_screen);
dpi[i][1] = mm ? screens[i].height*25.4f/mm : 0.0f;
}
}
if (xsi) XFree(xsi);
} else
#endif // HAVE_XINERAMA
{ // ! HAVE_XINERAMA || ! XineramaIsActive()
num_screens = ScreenCount(fl_display);
if (num_screens > MAX_SCREENS) num_screens = MAX_SCREENS;
for (int i=0; i<num_screens; i++) {
screens[i].x_org = 0;
screens[i].y_org = 0;
screens[i].width = DisplayWidth(fl_display, i);
screens[i].height = DisplayHeight(fl_display, i);
#if USE_XFT
screens[i].scale = 1;
#endif
if (dpi_by_randr) {
dpi[i][0] = dpih;
dpi[i][1] = dpiv;
} else {
int mm = DisplayWidthMM(fl_display, i);
dpi[i][0] = mm ? screens[i].width*25.4f/mm : 0.0f;
mm = DisplayHeightMM(fl_display, fl_screen);
dpi[i][1] = mm ? screens[i].height*25.4f/mm : 0.0f;
}
}
}
init_workarea();
}
void Fl_X11_Screen_Driver::screen_work_area(int &X, int &Y, int &W, int &H, int n)
{
if (num_screens < 0) init();
if (n < 0 || n >= num_screens) n = 0;
if (n == 0) { // for the main screen, these return the work area
X = Fl::x();
Y = Fl::y();
W = Fl::w();
H = Fl::h();
} else { // for other screens, work area is full screen,
screen_xywh(X, Y, W, H, n);
}
}
void Fl_X11_Screen_Driver::screen_xywh(int &X, int &Y, int &W, int &H, int n)
{
if (num_screens < 0) init();
if ((n < 0) || (n >= num_screens))
n = 0;
if (num_screens > 0) {
#if USE_XFT
float s = screens[n].scale;
#else
float s = 1;
#endif
X = screens[n].x_org / s;
Y = screens[n].y_org / s;
W = screens[n].width / s;
H = screens[n].height / s;
}
}
void Fl_X11_Screen_Driver::screen_dpi(float &h, float &v, int n)
{
if (num_screens < 0) init();
h = v = 0.0f;
if (n >= 0 && n < num_screens) {
h = dpi[n][0];
v = dpi[n][1];
}
}
// Implements fl_beep(). See documentation in src/fl_ask.cxx.
void Fl_X11_Screen_Driver::beep(int type)
{
int vol;
switch (type) {
case FL_BEEP_ERROR :
vol = 100;
break;
case FL_BEEP_DEFAULT :
default :
vol = 0;
break;
}
if (!fl_display) open_display();
XBell(fl_display, vol);
}
void Fl_X11_Screen_Driver::flush()
{
if (fl_display)
XFlush(fl_display);
}
extern void fl_fix_focus(); // in Fl.cxx
void Fl_X11_Screen_Driver::grab(Fl_Window* win)
{
const char *p;
static bool using_kde =
( p = getenv("XDG_CURRENT_DESKTOP") , (p && (strcmp(p, "KDE") == 0)) );
Fl_Window *fullscreen_win = NULL;
for (Fl_Window *W = Fl::first_window(); W; W = Fl::next_window(W)) {
if (W->fullscreen_active()) {
fullscreen_win = W;
break;
}
}
if (win) {
if (!Fl::grab()) {
Window xid = fullscreen_win ? fl_xid(fullscreen_win) : fl_xid(Fl::first_window());
XGrabPointer(fl_display,
xid,
1,
ButtonPressMask|ButtonReleaseMask|
ButtonMotionMask|PointerMotionMask,
GrabModeAsync,
GrabModeAsync,
None,
0,
fl_event_time);
if (!using_kde) { // grabbing tends to stick with KDE (#904)
XGrabKeyboard(fl_display,
xid,
1,
GrabModeAsync,
GrabModeAsync,
fl_event_time);
}
}
Fl::grab_ = win; // FIXME: Fl::grab_ "should be private", but we need
// a way to *set* the variable from the driver!
} else {
if (Fl::grab()) {
// We must keep the grab in the non-EWMH fullscreen case
if (!fullscreen_win || ewmh_supported()) {
XUngrabKeyboard(fl_display, fl_event_time);
}
XUngrabPointer(fl_display, fl_event_time);
// this flush is done in case the picked menu item goes into
// an infinite loop, so we don't leave the X server locked up:
XFlush(fl_display);
Fl::grab_ = 0; // FIXME: Fl::grab_ "should be private", but we need
// a way to *set* the variable from the driver!
fl_fix_focus();
}
}
}
// Wrapper around XParseColor...
int Fl_X11_Screen_Driver::parse_color(const char* p, uchar& r, uchar& g, uchar& b)
{
// before w open the display, we try interpreting this ourselves
// "None" will ultimately always return 0
if ( (fl_ascii_strcasecmp(p, "none") == 0)
|| (fl_ascii_strcasecmp(p, "#transparent") == 0) )
return 0;
// if it's #rgb, we can do that ourselves
if (Fl_Screen_Driver::parse_color(p, r, g, b))
return 1;
// it's neither "None" nor hex, so finally open the diplay and ask X11
XColor x;
if (!fl_display) open_display();
if (XParseColor(fl_display, fl_colormap, p, &x)) {
r = (uchar)(x.red>>8);
g = (uchar)(x.green>>8);
b = (uchar)(x.blue>>8);
return 1;
} else return 0;
}
// Read colors that KDE writes to the xrdb database.
// XGetDefault does not do the expected thing: it does not like
// periods in either word. Therefore it cannot match class.Text.background.
// However *.Text.background is matched by pretending the program is "Text".
// But this will also match *.background if there is no *.Text.background
// entry, requiring users to put in both (unless they want the text fields
// the same color as the windows).
static void set_selection_color(uchar r, uchar g, uchar b)
{
Fl::set_color(FL_SELECTION_COLOR,r,g,b);
}
static void getsyscolor(const char *key1, const char* key2, const char *arg, const char *defarg, void (*func)(uchar,uchar,uchar))
{
if (!arg) {
arg = XGetDefault(fl_display, key1, key2);
if (!arg) arg = defarg;
}
XColor x;
if (!XParseColor(fl_display, fl_colormap, arg, &x))
Fl::error("Unknown color: %s", arg);
else
func(x.red>>8, x.green>>8, x.blue>>8);
}
void Fl_X11_Screen_Driver::get_system_colors()
{
open_display();
const char* key1 = 0;
if (Fl::first_window()) key1 = Fl::first_window()->xclass();
if (!key1) key1 = "fltk";
if (!bg2_set)
getsyscolor("Text","background", fl_bg2, "#ffffff", Fl::background2);
if (!fg_set)
getsyscolor(key1, "foreground", fl_fg, "#000000", Fl::foreground);
if (!bg_set)
getsyscolor(key1, "background", fl_bg, "#c0c0c0", Fl::background);
getsyscolor("Text", "selectBackground", 0, "#000080", set_selection_color);
}
const char *Fl_X11_Screen_Driver::get_system_scheme()
{
const char *s = 0L;
if ((s = fl_getenv("FLTK_SCHEME")) == NULL) {
const char* key = 0;
if (Fl::first_window()) key = Fl::first_window()->xclass();
if (!key) key = "fltk";
open_display();
s = XGetDefault(fl_display, key, "scheme");
}
return s;
}
int Fl_X11_Screen_Driver::compose(int& del) {
int condition;
unsigned char ascii = (unsigned char)Fl::e_text[0];
condition = (Fl::e_state & (FL_ALT | FL_META | FL_CTRL)) && !(ascii & 128) ;
if (condition) { del = 0; return 0;} // this stuff is to be treated as a function key
del = Fl::compose_state;
Fl::compose_state = 0;
// Only insert non-control characters:
if ( (!Fl::compose_state) && ! (ascii & ~31 && ascii!=127)) { return 0; }
return 1;
}
void Fl_X11_Screen_Driver::compose_reset()
{
Fl::compose_state = 0;
if (xim_ic) XmbResetIC(xim_ic);
}
int Fl_X11_Screen_Driver::text_display_can_leak() const {
#if USE_XFT
return 1;
#else
return 0;
#endif
}
//
// 'fl_subimage_offsets()' - Calculate subimage offsets for an axis
static inline int
fl_subimage_offsets(int a, int aw, int b, int bw, int &obw)
{
int off;
int ob;
if (b >= a) {
ob = b;
off = 0;
} else {
ob = a;
off = a - b;
}
bw -= off;
if (ob + bw <= a + aw) {
obw = bw;
} else {
obw = (a + aw) - ob;
}
return off;
}
// this handler will catch and ignore exceptions during XGetImage
// to avoid an application crash
extern "C" {
static int xgetimageerrhandler(Display *display, XErrorEvent *error) {
return 0;
}
}
Fl_RGB_Image *Fl_X11_Screen_Driver::read_win_rectangle(int X, int Y, int w, int h, Fl_Window *win, bool may_capture_subwins, bool *did_capture_subwins)
{
XImage *image; // Captured image
int i, maxindex; // Looping vars
int x, y; // Current X & Y in image
unsigned char *line, // Array to hold image row
*line_ptr; // Pointer to current line image
unsigned char *pixel; // Current color value
XColor colors[4096]; // Colors from the colormap...
unsigned char cvals[4096][3]; // Color values from the colormap...
unsigned index_mask,
index_shift,
red_mask,
red_shift,
green_mask,
green_shift,
blue_mask,
blue_shift;
//
// Under X11 we have the option of the XGetImage() interface or SGI's
// ReadDisplay extension which does all of the really hard work for
// us...
//
int allow_outside = w < 0; // negative w allows negative X or Y, that is, window frame
if (w < 0) w = - w;
Window xid = (win && !allow_outside ? fl_xid(win) : fl_window);
float s = allow_outside ? Fl::screen_driver()->scale(win->screen_num()) : Fl_Surface_Device::surface()->driver()->scale();
int Xs = Fl_Scalable_Graphics_Driver::floor(X, s);
int Ys = Fl_Scalable_Graphics_Driver::floor(Y, s);
int ws = Fl_Scalable_Graphics_Driver::floor(X+w, s) - Xs;
int hs = Fl_Scalable_Graphics_Driver::floor(Y+h, s) - Ys;
# ifdef __sgi
if (XReadDisplayQueryExtension(fl_display, &i, &i)) {
image = XReadDisplay(fl_display, xid, Xs, Ys, ws, hs, 0, NULL);
} else
# else
image = 0;
# endif // __sgi
if (!image) {
// fetch absolute coordinates
int dx = 0, dy = 0, sx = 0, sy = 0, sw = 0, sh = 0;
Window child_win;
if (win) {
XTranslateCoordinates(fl_display, xid,
RootWindow(fl_display, fl_screen), Xs, Ys, &dx, &dy, &child_win);
// screen dimensions
int ns = Fl_Window_Driver::driver(win)->screen_num();
sx = screens[ns].x_org;
sy = screens[ns].y_org;
sw = screens[ns].width;
sh = screens[ns].height;
}
#if ! HAVE_XRENDER
if (win && !allow_outside && int(s) != s) {
ws = (w+1) * s; // approximates what Fl_Graphics_Driver::cache_size() does
hs = (h+1) * s;
}
#endif
if (win && Xs + ws >= int(win->w()*s)) ws = win->w()*s - Xs -1;
if (win && Ys + hs >= int(win->h()*s)) hs = win->h()*s - Ys -1;
if (ws < 1) ws = 1;
if (hs < 1) hs = 1;
if (!win || (dx >= sx && dy >= sy && dx + ws <= sx+sw && dy + hs <= sy+sh) ) {
// the image is fully contained, we can use the traditional method
// however, if the window is obscured etc. the function will still fail. Make sure we
// catch the error and continue, otherwise an exception will be thrown.
XErrorHandler old_handler = XSetErrorHandler(xgetimageerrhandler);
image = XGetImage(fl_display, xid, Xs, Ys, ws, hs, AllPlanes, ZPixmap);
XSetErrorHandler(old_handler);
} else {
// image is crossing borders, determine visible region
int nw, nh, noffx, noffy;
noffx = fl_subimage_offsets(sx, sw, dx, ws, nw);
noffy = fl_subimage_offsets(sy, sh, dy, hs, nh);
if (nw <= 0 || nh <= 0) return 0;
// allocate the image
int bpp = fl_visual->depth + ((fl_visual->depth / 8) % 2) * 8;
char* buf = (char*)malloc((bpp / 8) * ws * hs);
image = XCreateImage(fl_display, fl_visual->visual,
fl_visual->depth, ZPixmap, 0, buf, ws, hs, bpp, 0);
if (!image) {
if (buf) free(buf);
return 0;
}
XErrorHandler old_handler = XSetErrorHandler(xgetimageerrhandler);
XImage *subimg = XGetSubImage(fl_display, xid, Xs + noffx, Ys + noffy,
nw, nh, AllPlanes, ZPixmap, image, noffx, noffy);
XSetErrorHandler(old_handler);
if (!subimg) {
XDestroyImage(image);
return 0;
}
}
}
if (!image) return 0;
if (s != 1) {
w = ws;
h = hs;
}
#ifdef DEBUG
printf("width = %d\n", image->width);
printf("height = %d\n", image->height);
printf("xoffset = %d\n", image->xoffset);
printf("format = %d\n", image->format);
printf("data = %p\n", image->data);
printf("byte_order = %d\n", image->byte_order);
printf("bitmap_unit = %d\n", image->bitmap_unit);
printf("bitmap_bit_order = %d\n", image->bitmap_bit_order);
printf("bitmap_pad = %d\n", image->bitmap_pad);
printf("depth = %d\n", image->depth);
printf("bytes_per_line = %d\n", image->bytes_per_line);
printf("bits_per_pixel = %d\n", image->bits_per_pixel);
printf("red_mask = %08x\n", image->red_mask);
printf("green_mask = %08x\n", image->green_mask);
printf("blue_mask = %08x\n", image->blue_mask);
printf("map_entries = %d\n", fl_visual->visual->map_entries);
#endif // DEBUG
const int d = 3; // Depth of image
uchar *p = NULL;
// Allocate the image data array as needed...
p = new uchar[w * h * d];
// Initialize the default colors/alpha in the whole image...
memset(p, 0, w * h * d);
// Check that we have valid mask/shift values...
if (!image->red_mask && image->bits_per_pixel > 12) {
// Greater than 12 bits must be TrueColor...
image->red_mask = fl_visual->visual->red_mask;
image->green_mask = fl_visual->visual->green_mask;
image->blue_mask = fl_visual->visual->blue_mask;
#ifdef DEBUG
// Defined in Fl_Xlib_Graphics_Driver_color.cxx
extern uchar fl_redmask, fl_greenmask, fl_bluemask;
extern int fl_redshift, fl_greenshift, fl_blueshift;
puts("\n---- UPDATED ----");
printf("fl_redmask = %08x\n", fl_redmask);
printf("fl_redshift = %d\n", fl_redshift);
printf("fl_greenmask = %08x\n", fl_greenmask);
printf("fl_greenshift = %d\n", fl_greenshift);
printf("fl_bluemask = %08x\n", fl_bluemask);
printf("fl_blueshift = %d\n", fl_blueshift);
printf("red_mask = %08x\n", image->red_mask);
printf("green_mask = %08x\n", image->green_mask);
printf("blue_mask = %08x\n", image->blue_mask);
#endif // DEBUG
}
// Check if we have colormap image...
if (!image->red_mask) {
// Get the colormap entries for this window...
maxindex = fl_visual->visual->map_entries;
for (i = 0; i < maxindex; i ++) colors[i].pixel = i;
XQueryColors(fl_display, fl_colormap, colors, maxindex);
for (i = 0; i < maxindex; i ++) {
cvals[i][0] = colors[i].red >> 8;
cvals[i][1] = colors[i].green >> 8;
cvals[i][2] = colors[i].blue >> 8;
}
// Read the pixels and output an RGB image...
for (y = 0; y < image->height; y ++) {
pixel = (unsigned char *)(image->data + y * image->bytes_per_line);
line = p + y * w * d;
switch (image->bits_per_pixel) {
case 1 :
for (x = image->width, line_ptr = line, index_mask = 128;
x > 0;
x --, line_ptr += d) {
if (*pixel & index_mask) {
line_ptr[0] = cvals[1][0];
line_ptr[1] = cvals[1][1];
line_ptr[2] = cvals[1][2];
} else {
line_ptr[0] = cvals[0][0];
line_ptr[1] = cvals[0][1];
line_ptr[2] = cvals[0][2];
}
if (index_mask > 1) {
index_mask >>= 1;
} else {
index_mask = 128;
pixel ++;
}
}
break;
case 2 :
for (x = image->width, line_ptr = line, index_shift = 6;
x > 0;
x --, line_ptr += d) {
i = (*pixel >> index_shift) & 3;
line_ptr[0] = cvals[i][0];
line_ptr[1] = cvals[i][1];
line_ptr[2] = cvals[i][2];
if (index_shift > 0) {
index_shift -= 2;
} else {
index_shift = 6;
pixel ++;
}
}
break;
case 4 :
for (x = image->width, line_ptr = line, index_shift = 4;
x > 0;
x --, line_ptr += d) {
if (index_shift == 4) i = (*pixel >> 4) & 15;
else i = *pixel & 15;
line_ptr[0] = cvals[i][0];
line_ptr[1] = cvals[i][1];
line_ptr[2] = cvals[i][2];
if (index_shift > 0) {
index_shift = 0;
} else {
index_shift = 4;
pixel ++;
}
}
break;
case 8 :
for (x = image->width, line_ptr = line;
x > 0;
x --, line_ptr += d, pixel ++) {
line_ptr[0] = cvals[*pixel][0];
line_ptr[1] = cvals[*pixel][1];
line_ptr[2] = cvals[*pixel][2];
}
break;
case 12 :
for (x = image->width, line_ptr = line, index_shift = 0;
x > 0;
x --, line_ptr += d) {
if (index_shift == 0) {
i = ((pixel[0] << 4) | (pixel[1] >> 4)) & 4095;
} else {
i = ((pixel[1] << 8) | pixel[2]) & 4095;
}
line_ptr[0] = cvals[i][0];
line_ptr[1] = cvals[i][1];
line_ptr[2] = cvals[i][2];
if (index_shift == 0) {
index_shift = 4;
} else {
index_shift = 0;
pixel += 3;
}
}
break;
}
}
} else {
// RGB(A) image, so figure out the shifts & masks...
red_mask = image->red_mask;
red_shift = 0;
while ((red_mask & 1) == 0) {
red_mask >>= 1;
red_shift ++;
}
green_mask = image->green_mask;
green_shift = 0;
while ((green_mask & 1) == 0) {
green_mask >>= 1;
green_shift ++;
}
blue_mask = image->blue_mask;
blue_shift = 0;
while ((blue_mask & 1) == 0) {
blue_mask >>= 1;
blue_shift ++;
}
// Read the pixels and output an RGB image...
for (y = 0; y < image->height; y ++) {
pixel = (unsigned char *)(image->data + y * image->bytes_per_line);
line = p + y * w * d;
switch (image->bits_per_pixel) {
case 8 :
for (x = image->width, line_ptr = line;
x > 0;
x --, line_ptr += d, pixel ++) {
i = *pixel;
line_ptr[0] = 255 * ((i >> red_shift) & red_mask) / red_mask;
line_ptr[1] = 255 * ((i >> green_shift) & green_mask) / green_mask;
line_ptr[2] = 255 * ((i >> blue_shift) & blue_mask) / blue_mask;
}
break;
case 12 :
for (x = image->width, line_ptr = line, index_shift = 0;
x > 0;
x --, line_ptr += d) {
if (index_shift == 0) {
i = ((pixel[0] << 4) | (pixel[1] >> 4)) & 4095;
} else {
i = ((pixel[1] << 8) | pixel[2]) & 4095;
}
line_ptr[0] = 255 * ((i >> red_shift) & red_mask) / red_mask;
line_ptr[1] = 255 * ((i >> green_shift) & green_mask) / green_mask;
line_ptr[2] = 255 * ((i >> blue_shift) & blue_mask) / blue_mask;
if (index_shift == 0) {
index_shift = 4;
} else {
index_shift = 0;
pixel += 3;
}
}
break;
case 16 :
if (image->byte_order == LSBFirst) {
// Little-endian...
for (x = image->width, line_ptr = line;
x > 0;
x --, line_ptr += d, pixel += 2) {
i = (pixel[1] << 8) | pixel[0];
line_ptr[0] = 255 * ((i >> red_shift) & red_mask) / red_mask;
line_ptr[1] = 255 * ((i >> green_shift) & green_mask) / green_mask;
line_ptr[2] = 255 * ((i >> blue_shift) & blue_mask) / blue_mask;
}
} else {
// Big-endian...
for (x = image->width, line_ptr = line;
x > 0;
x --, line_ptr += d, pixel += 2) {
i = (pixel[0] << 8) | pixel[1];
line_ptr[0] = 255 * ((i >> red_shift) & red_mask) / red_mask;
line_ptr[1] = 255 * ((i >> green_shift) & green_mask) / green_mask;
line_ptr[2] = 255 * ((i >> blue_shift) & blue_mask) / blue_mask;
}
}
break;
case 24 :
if (image->byte_order == LSBFirst) {
// Little-endian...
for (x = image->width, line_ptr = line;
x > 0;
x --, line_ptr += d, pixel += 3) {
i = (((pixel[2] << 8) | pixel[1]) << 8) | pixel[0];
line_ptr[0] = 255 * ((i >> red_shift) & red_mask) / red_mask;
line_ptr[1] = 255 * ((i >> green_shift) & green_mask) / green_mask;
line_ptr[2] = 255 * ((i >> blue_shift) & blue_mask) / blue_mask;
}
} else {
// Big-endian...
for (x = image->width, line_ptr = line;
x > 0;
x --, line_ptr += d, pixel += 3) {
i = (((pixel[0] << 8) | pixel[1]) << 8) | pixel[2];
line_ptr[0] = 255 * ((i >> red_shift) & red_mask) / red_mask;
line_ptr[1] = 255 * ((i >> green_shift) & green_mask) / green_mask;
line_ptr[2] = 255 * ((i >> blue_shift) & blue_mask) / blue_mask;
}
}
break;
case 32 :
if (image->byte_order == LSBFirst) {
// Little-endian...
for (x = image->width, line_ptr = line;
x > 0;
x --, line_ptr += d, pixel += 4) {
i = (((((pixel[3] << 8) | pixel[2]) << 8) | pixel[1]) << 8) | pixel[0];
line_ptr[0] = 255 * ((i >> red_shift) & red_mask) / red_mask;
line_ptr[1] = 255 * ((i >> green_shift) & green_mask) / green_mask;
line_ptr[2] = 255 * ((i >> blue_shift) & blue_mask) / blue_mask;
}
} else {
// Big-endian...
for (x = image->width, line_ptr = line;
x > 0;
x --, line_ptr += d, pixel += 4) {
i = (((((pixel[0] << 8) | pixel[1]) << 8) | pixel[2]) << 8) | pixel[3];
line_ptr[0] = 255 * ((i >> red_shift) & red_mask) / red_mask;
line_ptr[1] = 255 * ((i >> green_shift) & green_mask) / green_mask;
line_ptr[2] = 255 * ((i >> blue_shift) & blue_mask) / blue_mask;
}
}
break;
}
}
}
// Destroy the X image we've read and return the RGB(A) image...
XDestroyImage(image);
Fl_RGB_Image *rgb = new Fl_RGB_Image(p, w, h, d);
rgb->alloc_array = 1;
return rgb;
}
void Fl_X11_Screen_Driver::offscreen_size(Fl_Offscreen off, int &width, int &height)
{
int px, py;
unsigned w, h, b, d;
Window root;
XGetGeometry(fl_display, (Pixmap)off, &root, &px, &py, &w, &h, &b, &d);
width = (int)w;
height = (int)h;
}
void Fl_X11_Screen_Driver::reset_spot(void)
{
fl_spot.x = -1;
fl_spot.y = -1;
//if (xim_ic) XUnsetICFocus(xim_ic);
}
void Fl_X11_Screen_Driver::set_spot(int font, int size, int X, int Y, int W, int H, Fl_Window *win)
{
int change = 0;
XVaNestedList preedit_attr;
static XFontSet fs = NULL;
char **missing_list = NULL;
int missing_count = 0;
char *def_string = NULL;
char *fnt = NULL;
bool must_free_fnt = true;
static XIC ic = NULL;
if (!xim_ic || !fl_is_over_the_spot) return;
if (Fl::focus()) { // handle case when text widget is inside subwindow
Fl_Window *focuswin = Fl::focus()->window();
while (focuswin && focuswin->parent()) {
X += focuswin->x(); Y += focuswin->y();
focuswin = focuswin->window();
}
}
// XSetICFocus(xim_ic);
if (X != fl_spot.x || Y != fl_spot.y) {
fl_spot.x = X;
fl_spot.y = Y;
fl_spot.height = H;
fl_spot.width = W;
change = 1;
}
if (font != fl_spotf || size != fl_spots) {
fl_spotf = font;
fl_spots = size;
change = 1;
if (fs) {
XFreeFontSet(fl_display, fs);
}
#if USE_XFT
fnt = NULL; // FIXME: missing XFT support here
#else
fnt = fl_get_font_xfld(font, size);
#endif
if (!fnt) {
fnt = (char*)"-misc-fixed-*";
must_free_fnt = false;
}
fs = XCreateFontSet(fl_display, fnt, &missing_list, &missing_count, &def_string);
if (missing_list)
XFreeStringList(missing_list);
}
if (xim_ic != ic) {
ic = xim_ic;
change = 1;
}
if (fnt && must_free_fnt) free(fnt);
if (!change) return;
float s = Fl_Graphics_Driver::default_driver().scale();
XRectangle fl_spot_unscaled = { short(fl_spot.x * s), short(fl_spot.y * s),
(unsigned short)(fl_spot.width * s), (unsigned short)(fl_spot.height * s) };
preedit_attr = XVaCreateNestedList(0,
XNSpotLocation, &fl_spot_unscaled,
XNFontSet, fs, NULL);
XSetICValues(xim_ic, XNPreeditAttributes, preedit_attr, NULL);
XFree(preedit_attr);
}
#if USE_XFT
//NOTICE: returns -1 if x,y is not in any screen
int Fl_X11_Screen_Driver::screen_num_unscaled(int x, int y)
{
int screen = -1;
if (num_screens < 0) init();
for (int i = 0; i < num_screens; i ++) {
int sx = screens[i].x_org, sy = screens[i].y_org, sw = screens[i].width, sh = screens[i].height;
if ((x >= sx) && (x < (sx+sw)) && (y >= sy) && (y < (sy+sh))) {
screen = i;
break;
}
}
return screen;
}
/*
#if HAVE_DLSYM && HAVE_DLFCN_H
// returns true when name is among the list of known names
static bool is_name_in_list(const char *name, const char **list) {
int i = 0;
while (list[i]) {
if (strcmp(list[i++], name) == 0) return true;
}
return false;
}
// define types needed for dynamic lib functions
typedef const char** (*g_settings_list_schemas_ftype)(void);
typedef void (*g_variant_get_ftype)(void *value, const char *format_string, ...);
typedef bool (*g_variant_iter_loop_ftype)(void *iter, const char *format_string, ...);
typedef const char **(*g_settings_list_keys_ftype)(void *);
typedef void* (*g_settings_new_ftype)(const char *);
typedef void* (*g_settings_get_value_ftype)(void *settings, const char *key);
typedef void (*pter_ftype)(void*);
// define run-time pointers to functions from dynamic libs
static g_settings_new_ftype g_settings_new_f;
static g_settings_list_keys_ftype g_settings_list_keys_f;
static pter_ftype g_object_unref_f;
static pter_ftype g_strfreev_f;
static g_settings_get_value_ftype g_settings_get_value_f;
static void* value_of_key_in_schema(const char **known, const char *schema, const char *key) {
void *retval = NULL;
if (is_name_in_list(schema, known)) {
void *gset = g_settings_new_f(schema);
const char **list = g_settings_list_keys_f(gset);
if (is_name_in_list(key, list)) retval = g_settings_get_value_f(gset, key);
g_strfreev_f(list);
g_object_unref_f(gset);
}
return retval;
}*/
// DEPRECATED: gnome apparently no longer stores the display scale factor value
// in the gsettings database.
/*
returns true under Ubuntu or Debian or FreeBSD and when the gnome scaling value has been found
Ubuntu:
Change the gnome scaling factor with:
System Settings ==> Displays ==> Scale for menu and title bars
Read the current gnome scaling factor with:
gsettings get com.ubuntu.user-interface scale-factor
Example value: {'VGA-0': 10}
Its type is "a{si}". This value should be divided by 8 to get the correct scaling factor.
In Ubuntu 18, file $HOME/.config/monitors.xml contains the gnome scaling factor value,
and FLTK reads that.
Debian or FreeBSD :
Change the gnome scaling factor with:
Tweak tools ==> Windows ==> Window scaling
Read the current gnome scaling factor with:
gsettings get org.gnome.settings-daemon.plugins.xsettings overrides
Example value: {'Gdk/WindowScalingFactor': <2>}
Its type is "a{sv}" and v itself is of type i
It's also possible to use 'Tweak tools' under Ubuntu. With the standard Ubuntu desktop,
the modified value goes to "org.gnome.settings-daemon.plugins.xsettings" as above.
With Gnome session flashback under Ubuntu 'Tweak tools' puts the scaling value (1 or 2)
in "org.gnome.desktop.interface scaling-factor".
Read the current gnome scaling factor with:
gsettings get org.gnome.desktop.interface scaling-factor
Its type is "u"
Thus, under Ubuntu, we read the 3 possible factor values and
return the first value different from 1 to get the scaling factor.
=================================================================================================
Ubuntu | default ubuntu desktop | System Settings => Displays => Scale for menu and title bars
com.ubuntu.user-interface scale-factor
-----------------------
Tweak tools => Windows => Window scaling
org.gnome.settings-daemon.plugins.xsettings overrides
-----------------------
Gnome session flashback | System Settings => Displays => Scale for menu and title bars
no effect
-----------------------
Tweak tools => Windows => Window scaling
org.gnome.desktop.interface scaling-factor
=================================================================================================
Debian or FreeBSD | gnome | Tweak tools => Windows => Window scaling
org.gnome.settings-daemon.plugins.xsettings overrides
=================================================================================================
*/
/*static bool gnome_scale_factor(float& factor) {
// open dynamic libs
void *glib = dlopen("libglib-2.0.so", RTLD_LAZY);
void *gio = dlopen("libgio-2.0.so", RTLD_LAZY);
void *gobj = dlopen("libgobject-2.0.so", RTLD_LAZY);
//fprintf(stderr,"glib=%p gio=%p gobj=%p\n",glib,gio,gobj);
if (!glib || !gio || !gobj) return false;
// define pters to used functions and variables
g_settings_list_schemas_ftype g_settings_list_schemas_f = (g_settings_list_schemas_ftype)dlsym(gio, "g_settings_list_schemas"); // 2.26
g_settings_new_f = (g_settings_new_ftype)dlsym(gio, "g_settings_new"); // 2.26
g_settings_get_value_f =
(g_settings_get_value_ftype)dlsym(gio, "g_settings_get_value"); // 2.26
if (!g_settings_list_schemas_f || !g_settings_new_f || !g_settings_get_value_f) return false;
g_variant_get_ftype g_variant_get_f = (g_variant_get_ftype)dlsym(glib, "g_variant_get"); //2.24
g_variant_iter_loop_ftype g_variant_iter_loop_f = (g_variant_iter_loop_ftype)dlsym(glib, "g_variant_iter_loop"); // 2.24
pter_ftype g_variant_iter_free_f = (pter_ftype)dlsym(glib, "g_variant_iter_free"); // 2.24
g_object_unref_f = (pter_ftype)dlsym(gobj, "g_object_unref");
pter_ftype g_variant_unref_f = (pter_ftype)dlsym(glib, "g_variant_unref"); // 2.24
g_settings_list_keys_f = (g_settings_list_keys_ftype)dlsym(gio, "g_settings_list_keys");
g_strfreev_f = (pter_ftype)dlsym(glib, "g_strfreev");
//g_variant_get_type_ftype g_variant_get_type_f = (g_variant_get_type_ftype)dlsym(glib, "g_variant_get_type"); // 2.24
const unsigned *glib_major_version = (const unsigned *)dlsym(glib, "glib_major_version");
const unsigned *glib_minor_version = (const unsigned *)dlsym(glib, "glib_minor_version");
// call dynamic lib functions
if (*glib_major_version * 1000 + *glib_minor_version < 2036) {
typedef void (*init_ftype)(void);
init_ftype g_type_init_f = (init_ftype)dlsym(gobj, "g_type_init");
g_type_init_f(); // necessary only if GLib version < 2.36
}
const char **known = g_settings_list_schemas_f(); // list of available GSettings schemas
float ubuntu_f = 1, ubuntu_desktop_f = 1, gnome_f = 1;
bool found = false;
void *gvar;
bool ubuntu = false;
// determine whether we run Ubuntu
char line[400] = "";
FILE *in = fopen("/proc/version", "r");
if (in) {
char *s = fgets(line, sizeof(line), in);
fclose(in);
if (s && strstr(line, "Ubuntu")) ubuntu = true;
}
if (ubuntu) {
gvar = value_of_key_in_schema(known, "com.ubuntu.user-interface", "scale-factor");
if (gvar) {
found = true;
void *iter;
char str[10], *str2; int v=8, v2;
g_variant_get_f(gvar, "a{si}", &iter);
while (g_variant_iter_loop_f(iter, "{si}", &str2, &v2)) { // read the last couple of values
strcpy(str, str2); v = v2;
}
ubuntu_f = v/8.;
// printf("com.ubuntu.user-interface scale-factor name=%s value=%d factor=%g\n", str, v, ubuntu_f);
g_variant_iter_free_f(iter);
g_variant_unref_f(gvar);
if (ubuntu_f != 1) {
factor = ubuntu_f;
return true;
}
}
gvar = value_of_key_in_schema(known, "org.gnome.desktop.interface", "scaling-factor");
if (gvar) {
found = true;
unsigned v;
g_variant_get_f(gvar, "u", &v);
ubuntu_desktop_f = v;
// printf("org.gnome.desktop.interface scaling-factor value=%u factor=%g\n", v, ubuntu_desktop_f);
g_variant_unref_f(gvar);
if (ubuntu_desktop_f != 1) {
factor = ubuntu_desktop_f;
return true;
}
}
}
gvar = value_of_key_in_schema(known, "org.gnome.settings-daemon.plugins.xsettings", "overrides");
if (gvar) {
void *iter;
char *str; int v;
//str = (char*)g_variant_get_type_f(gvar); // -> "a{sv}"
g_variant_get_f(gvar, "a{sv}", &iter);
g_variant_unref_f(gvar);
gvar = NULL;
while (g_variant_iter_loop_f(iter, "{sv}", &str, &gvar)) {
if (strstr(str, "WindowScalingFactor") == NULL) continue;
found = true;
//str = (char*)g_variant_get_type_f(gvar); // -> "i"
g_variant_get_f(gvar, "i", &v);
gnome_f = v;
// printf("org.gnome.settings-daemon.plugins.xsettings overrides name=%s value=%d factor=%g\n", str, v, gnome_f);
free(str);
break;
}
g_variant_iter_free_f(iter);
if (gvar) g_variant_unref_f(gvar);
}
if (!found) return false;
factor = gnome_f;
return true;
}
#endif // HAVE_DLSYM && HAVE_DLFCN_H
*/
// set the desktop's default scaling value
void Fl_X11_Screen_Driver::desktop_scale_factor()
{
if (this->current_xft_dpi == 0.) { // Try getting the Xft.dpi resource value
char *s = XGetDefault(fl_display, "Xft", "dpi");
if (s && sscanf(s, "%f", &(this->current_xft_dpi)) == 1) {
float factor = this->current_xft_dpi / 96.;
// checks to prevent potential crash (factor <= 0) or very large factors
if (factor < 0.25) factor = 0.25;
else if (factor > 10.0) factor = 10.0;
for (int i = 0; i < screen_count(); i++) scale(i, factor);
}
}
}
#endif // USE_XFT