openclonk/src/platform/C4WindowX.cpp

583 lines
17 KiB
C++

/*
* OpenClonk, http://www.openclonk.org
*
* Copyright (c) 2005-2009, 2011 Günther Brammer
* Copyright (c) 2005 Peter Wortmann
* Copyright (c) 2006, 2008, 2010 Armin Burgmeier
* Copyright (c) 2010 Benjamin Herr
* Copyright (c) 2005-2009, RedWolf Design GmbH, http://www.clonk.de
*
* Portions might be copyrighted by other authors who have contributed
* to OpenClonk.
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
* See isc_license.txt for full license and disclaimer.
*
* "Clonk" is a registered trademark of Matthes Bender.
* See clonk_trademark_license.txt for full license.
*/
/* A wrapper class to OS dependent event and window interfaces, X11 version */
#include <C4Include.h>
#ifdef USE_X11
#include <C4Window.h>
#include <C4App.h>
#include <StdGL.h>
#include <StdDDraw2.h>
#include <StdFile.h>
#include <StdBuf.h>
#include <C4Config.h>
#include <C4Rect.h>
#include "C4Version.h"
#include "c4x.xpm"
#include <X11/Xlib.h>
#include <X11/xpm.h>
#include <X11/Xatom.h>
#include <X11/extensions/xf86vmode.h>
#include <GL/glx.h>
#include <string>
#include <map>
#include <sstream>
#include <sys/time.h>
#include <time.h>
#include <errno.h>
#include "C4AppXImpl.h"
// Some helper functions for choosing a proper visual
#ifdef USE_GL
// Returns which XVisual attribute for two given attributes is greater.
static int CompareVisualAttribute(Display* dpy, XVisualInfo* first, XVisualInfo* second, int attrib)
{
int first_value, second_value;
glXGetConfig(dpy, first, attrib, &first_value);
glXGetConfig(dpy, second, attrib, &second_value);
if(first_value != second_value) return first_value > second_value ? 1 : -1;
return 0;
}
// Given two X visuals, check which one is superior, according to
// the following rule: Double buffering is preferred over single
// buffering, then highest color buffer is preferred. If both are equal
// then the buffers are considered equal (return value 0).
static int CompareVisual(Display* dpy, XVisualInfo* first, XVisualInfo* second)
{
int result = CompareVisualAttribute(dpy, first, second, GLX_DOUBLEBUFFER);
if(result != 0) return result;
result = CompareVisualAttribute(dpy, first, second, GLX_BUFFER_SIZE);
return result;
}
// Compare otherwise equivalent visuals. If the function above
// considered two visuals to be equivalent then this function can
// be used to decide which one to use. We prefer visuals with high depth
// beffer size and low accumulation and stencil buffer sizes since the latter
// two are not used in Clonk.
static int CompareEquivalentVisual(Display* dpy, XVisualInfo* first, XVisualInfo* second)
{
int result = CompareVisualAttribute(dpy, first, second, GLX_DEPTH_SIZE);
if(result != 0) return result;
result = CompareVisualAttribute(dpy, first, second, GLX_STENCIL_SIZE);
if(result != 0) return -result;
result = CompareVisualAttribute(dpy, first, second, GLX_ACCUM_RED_SIZE);
if(result != 0) return -result;
result = CompareVisualAttribute(dpy, first, second, GLX_ACCUM_GREEN_SIZE);
if(result != 0) return -result;
result = CompareVisualAttribute(dpy, first, second, GLX_ACCUM_BLUE_SIZE);
if(result != 0) return -result;
result = CompareVisualAttribute(dpy, first, second, GLX_ACCUM_ALPHA_SIZE);
return -result;
}
// This function generates a list of acceptable visuals. The most
// superiour visual as defined by CompareVisual is chosen. If there
// are two or more visuals which compare equal with CompareVisual then
// we add all of them to the output list as long as their multi
// sampling properties differ. If they do not differ then we use
// CompareEquivalentVisual to decide which one to put into the output
// list.
static std::vector<XVisualInfo> EnumerateVisuals(Display* dpy)
{
XVisualInfo templateInfo;
templateInfo.screen = DefaultScreen(dpy);
long vinfo_mask = VisualScreenMask;
int nitems;
XVisualInfo* infos = XGetVisualInfo(dpy, vinfo_mask, &templateInfo, &nitems);
std::vector<XVisualInfo> selected_infos;
for(int i = 0; i < nitems; ++i)
{
// Require minimum depth and color buffer
if(infos[i].depth < 8 || infos[i].bits_per_rgb < 4) continue;
// Require it to be an RGBA visual
int value;
glXGetConfig(dpy, &infos[i], GLX_RGBA, &value);
if(!value) continue;
// Require GL rendering to be supported (probably always true...)
glXGetConfig(dpy, &infos[i], GLX_USE_GL, &value);
if(!value) continue;
// Multisampling with only 1 sample gives the same result as
// no multisampling at all, so simply ignore these visuals.
int second_value;
glXGetConfig(dpy, &infos[i], GLX_SAMPLE_BUFFERS_ARB, &value);
glXGetConfig(dpy, &infos[i], GLX_SAMPLES_ARB, &second_value);
if(value == 1 && second_value == 1) continue;
// This visual is acceptable in principle. Use it if
// we don't have any other.
if(selected_infos.empty())
{
selected_infos.push_back(infos[i]);
}
// Otherwise, check which one is superior. Note that all selected
// visuals have same buffering and RGBA sizes.
else
{
unsigned int j;
switch(CompareVisual(dpy, &infos[i], &selected_infos[0]))
{
case 1:
// The new visual is superior.
selected_infos.clear();
selected_infos.push_back(infos[i]);
break;
case -1:
// The old visual is superior.
break;
case 0:
// The visuals are equal. OK, so check whether there is an otherwise equivalent
// visual (read: same multisampling properties) but with different depth, stencil or
// auxiliary buffer sizes. If so, replace it, otherwise add the new one.
for(j = 0; j < selected_infos.size(); ++j)
{
if(CompareVisualAttribute(dpy, &infos[i], &selected_infos[j], GLX_SAMPLE_BUFFERS_ARB) != 0) continue;
if(CompareVisualAttribute(dpy, &infos[i], &selected_infos[j], GLX_SAMPLES_ARB) != 0) continue;
// The new visual has the same multi sampling properties then the current one.
// Use CompareEquivalentVisual() to decide
switch(CompareEquivalentVisual(dpy, &infos[i], &selected_infos[j]))
{
case 1:
// The current info is more suitable
selected_infos[j] = infos[i];
break;
case -1:
// The existing info is more suitable;
break;
case 0:
// No decision. Keep the existing one, but we could as well take
// the new one since we don't know what the difference between the two is.
break;
}
// Break the for loop. There is only one visual
// with the same multi sampling properties.
break;
}
// If we did not find a visual with the same multisampling in the for loop
// then add this visual to the result list
if(j == selected_infos.size())
selected_infos.push_back(infos[i]);
break;
}
}
}
XFree(infos);
return selected_infos;
}
#endif // USE_GL
static Window CreateRenderWindow(Display* dpy, Window parent, XVisualInfo* info)
{
XWindowAttributes parent_attr;
if(!XGetWindowAttributes(dpy, parent, &parent_attr)) return None;
XSetWindowAttributes attr;
attr.border_pixel = 0;
attr.background_pixel = 0;
attr.colormap = XCreateColormap(dpy, DefaultRootWindow(dpy), info->visual, AllocNone);
unsigned long attrmask = CWBackPixel | CWBorderPixel | CWColormap;
return XCreateWindow(dpy, parent, 0, 0, parent_attr.width, parent_attr.height, 0,
info->depth, InputOutput, info->visual, attrmask, &attr);
}
/* C4Window */
C4Window::C4Window ():
Active(false), pSurface(0), wnd(0), renderwnd(0), dpy(0), Hints(0), HasFocus(false), Info(0)
{
}
C4Window::~C4Window ()
{
Clear();
}
C4Window * C4Window::Init(C4Window::WindowKind windowKind, C4AbstractApp * pApp, const char * Title, C4Window * pParent, bool HideCursor)
{
Active = true;
dpy = pApp->dpy;
// Try to get a multisampling visual
if(!FindInfo(Config.Graphics.MultiSampling, &Info))
{
// Disable multisampling if we don't find a visual which
// supports the currently configured setting.
if(!FindInfo(0, &Info)) return NULL;
Config.Graphics.MultiSampling = 0;
}
XSetWindowAttributes attr;
attr.border_pixel = 0;
attr.background_pixel = 0;
// Which events we want to receive
attr.event_mask =
//EnterWindowMask |
//LeaveWindowMask |
StructureNotifyMask |
FocusChangeMask |
KeyPressMask |
KeyReleaseMask |
PointerMotionMask |
ButtonPressMask |
ButtonReleaseMask;
unsigned long attrmask = CWBackPixel | CWBorderPixel | CWEventMask;
Pixmap bitmap = 0;
if (HideCursor)
{
// Hide the mouse cursor
XColor cursor_color;
// We do not care what color the invisible cursor has
memset(&cursor_color, 0, sizeof(cursor_color));
bitmap = XCreateBitmapFromData(dpy, DefaultRootWindow(dpy), "\000", 1, 1);
if (bitmap)
{
attr.cursor = XCreatePixmapCursor(dpy, bitmap, bitmap, &cursor_color, &cursor_color, 0, 0);
if (attr.cursor)
attrmask |= CWCursor;
else
Log("Error creating cursor.");
}
else
{
Log("Error creating bitmap for cursor.");
attr.cursor = 0;
}
}
else
{
attr.cursor = 0;
}
wnd = XCreateWindow(dpy, DefaultRootWindow(dpy),
0, 0, Config.Graphics.ResX, Config.Graphics.ResY, 0, CopyFromParent, InputOutput, CopyFromParent,
attrmask, &attr);
if (attr.cursor)
XFreeCursor(dpy, attr.cursor);
if (bitmap)
XFreePixmap(dpy, bitmap);
if (!wnd)
{
Log("Error creating window.");
return NULL;
}
if (pApp->Priv->xim && !pApp->Priv->xic)
{
pApp->Priv->xic = XCreateIC(pApp->Priv->xim,
XNClientWindow, wnd,
XNFocusWindow, wnd,
XNInputStyle, XIMPreeditNothing | XIMStatusNothing,
XNResourceName, C4ENGINENAME,
XNResourceClass, C4ENGINENAME,
NULL);
if(pApp->Priv->xic)
{
long ic_event_mask;
if (XGetICValues(pApp->Priv->xic, XNFilterEvents, &ic_event_mask, NULL) == NULL)
attr.event_mask |= ic_event_mask;
XSelectInput(dpy, wnd, attr.event_mask);
XSetICFocus(pApp->Priv->xic);
}
else
{
Log("Failed to create input context.");
XCloseIM(pApp->Priv->xim);
pApp->Priv->xim=0;
}
}
#if 0
// Set _NET_WM_STATE
if(n_net_wm_state_atoms)
{
XChangeProperty(dpy, wnd_info.wnd, XInternAtom(dpy, "_NET_WM_STATE", False), XInternAtom(dpy, "ATOM", False), 32, PropModeReplace, reinterpret_cast<unsigned char*>(net_wm_state_atoms), n_net_wm_state_atoms);
}
#endif
// We want notification of closerequests and be killed if we hang
Atom WMProtocols[2];
const char * WMProtocolnames[] = { "WM_DELETE_WINDOW", "_NET_WM_PING" };
XInternAtoms(dpy, const_cast<char **>(WMProtocolnames), 2, false, WMProtocols);
XSetWMProtocols(dpy, wnd, WMProtocols, 2);
// Let the window manager know our pid so it can kill us
Atom PID = XInternAtom(dpy, "_NET_WM_PID", false);
int32_t pid = getpid();
if (PID != None) XChangeProperty(dpy, wnd, PID, XA_CARDINAL, 32, PropModeReplace, reinterpret_cast<const unsigned char*>(&pid), 1);
// State and Icon
XWMHints* wm_hints = XAllocWMHints();
wm_hints->input = 1;
wm_hints->flags = StateHint | InputHint | IconPixmapHint | IconMaskHint;
wm_hints->initial_state = NormalState;
// Trust XpmCreatePixmapFromData to not modify the xpm...
XpmCreatePixmapFromData (dpy, wnd, const_cast<char **>(c4x_xpm), &wm_hints->icon_pixmap, &wm_hints->icon_mask, 0);
// Window class
XClassHint * class_hint = XAllocClassHint();
class_hint->res_name = const_cast<char *>(C4ENGINENAME);
class_hint->res_class = const_cast<char *>(C4ENGINENAME);
Xutf8SetWMProperties(dpy, wnd, const_cast<char*>(Title), const_cast<char*>(Title), pApp->Priv->argv, pApp->Priv->argc, 0, wm_hints, class_hint);
XFree(class_hint);
Hints = wm_hints;
// Set "parent". Clonk does not use "real" parent windows, but multiple toplevel windows.
if (pParent) XSetTransientForHint(dpy, wnd, pParent->wnd);
// Update XWindow<->StdWindow map
C4X11AppImpl::SetWindow(wnd, this);
// Create the subwindow which we render into
renderwnd = CreateRenderWindow(dpy, wnd, static_cast<XVisualInfo*>(Info));
if(!renderwnd)
{
XDestroyWindow(dpy, wnd); wnd = None;
Log("Error creating render window.");
return NULL;
}
// Clean up
// The pixmap has to stay as long as the window exists, so it does not hurt to never free it.
//XFreePixmap(dpy,xwmh->icon_pixmap);
//XFreePixmap(dpy,xwmh->icon_mask);
// Show window
XMapWindow (dpy, renderwnd);
XMapWindow (dpy, wnd);
return this;
}
bool C4Window::ReInit(C4AbstractApp* pApp)
{
// Can only re-init if we have been initialized already
if(!wnd) return false;
// Check whether multisampling settings was changed. If not then we
// don't need to ReInit anything.
#ifdef USE_GL
int value;
glXGetConfig(dpy, static_cast<XVisualInfo*>(Info), GLX_SAMPLES_ARB, &value);
if(value == Config.Graphics.MultiSampling) return true;
#else
return true;
#endif
// Check whether we have a visual with the requested number of samples
void* new_info;
if(!FindInfo(Config.Graphics.MultiSampling, &new_info)) return false;
Window new_window = CreateRenderWindow(dpy, wnd, static_cast<XVisualInfo*>(new_info));
if(!new_window)
{
delete static_cast<XVisualInfo*>(new_info);
return false;
}
delete static_cast<XVisualInfo*>(Info);
Info = new_info;
// Replace existing render window with new one
XUnmapWindow(dpy, renderwnd);
XMapWindow(dpy, new_window);
XDestroyWindow(dpy, renderwnd);
renderwnd = new_window;
return true;
}
void C4Window::Clear()
{
// Destroy window
if (wnd)
{
C4X11AppImpl::SetWindow(wnd, 0);
XUnmapWindow(dpy, wnd);
XDestroyWindow(dpy, renderwnd);
XDestroyWindow(dpy, wnd);
if (Hints) XFree(Hints);
Hints = NULL;
// Might be necessary when the last window is closed
XFlush(dpy);
}
wnd = renderwnd = 0;
if (Info) delete static_cast<XVisualInfo*>(Info);
Info = NULL;
}
bool C4Window::FindInfo(int samples, void** info)
{
#ifdef USE_GL
std::vector<XVisualInfo> infos = EnumerateVisuals(dpy);
for(unsigned int i = 0; i < infos.size(); ++i)
{
int v_buffers, v_samples;
glXGetConfig(dpy, &infos[i], GLX_SAMPLE_BUFFERS_ARB, &v_buffers);
glXGetConfig(dpy, &infos[i], GLX_SAMPLES_ARB, &v_samples);
if((samples == 0 && v_buffers == 0) ||
(samples > 0 && v_buffers == 1 && v_samples == samples))
{
*info = new XVisualInfo(infos[i]);
return true;
}
}
#else
// TODO: Do we need to handle this case?
#endif // USE_GL
return false;
}
void C4Window::EnumerateMultiSamples(std::vector<int>& samples) const
{
#ifdef USE_GL
std::vector<XVisualInfo> infos = EnumerateVisuals(dpy);
for(unsigned int i = 0; i < infos.size(); ++i)
{
int v_buffers, v_samples;
glXGetConfig(dpy, &infos[i], GLX_SAMPLE_BUFFERS_ARB, &v_buffers);
glXGetConfig(dpy, &infos[i], GLX_SAMPLES_ARB, &v_samples);
if(v_buffers == 1) samples.push_back(v_samples);
}
#endif
}
bool C4Window::StorePosition(const char *, const char *, bool) { return true; }
bool C4Window::RestorePosition(const char *, const char *, bool)
{
// The Windowmanager is responsible for window placement.
return true;
}
bool C4Window::GetSize(C4Rect * pRect)
{
Window winDummy;
unsigned int borderDummy;
int x, y;
unsigned int width, height;
unsigned int depth;
XGetGeometry(dpy, wnd, &winDummy, &x, &y,
&width, &height, &borderDummy, &depth);
pRect->Wdt = width;
pRect->Hgt = height;
pRect->y = y;
pRect->x = x;
return true;
}
void C4Window::SetSize(unsigned int X, unsigned int Y)
{
XResizeWindow(dpy, wnd, X, Y);
}
void C4Window::SetTitle(const char * Title)
{
XTextProperty title_property;
StdStrBuf tbuf(Title, true);
char * tbufstr = tbuf.getMData();
XStringListToTextProperty(&tbufstr, 1, &title_property);
XSetWMName(dpy, wnd, &title_property);
}
void C4Window::FlashWindow()
{
// This tries to implement flashing via
// _NET_WM_STATE_DEMANDS_ATTENTION, but it simply does not work for me.
// -ck.
#if 0
XEvent e;
e.xclient.type = ClientMessage;
e.xclient.message_type = XInternAtom(dpy, "_NET_WM_STATE", True);
e.xclient.window = wnd;
e.xclient.display = dpy;
e.xclient.format = 32;
e.xclient.data.l[0] = 1;
e.xclient.data.l[1] = XInternAtom(dpy, "_NET_WM_STATE_DEMANDS_ATTENTION", True);
e.xclient.data.l[2] = 0l;
e.xclient.data.l[3] = 0l;
e.xclient.data.l[4] = 0l;
XSendEvent(dpy, DefaultRootWindow(dpy), false, SubstructureNotifyMask | SubstructureRedirectMask, &e);
#endif
if (!HasFocus)
{
XWMHints * wm_hint = static_cast<XWMHints*>(Hints);
wm_hint->flags |= XUrgencyHint;
XSetWMHints(dpy, wnd, wm_hint);
}
}
void C4Window::HandleMessage(XEvent& event)
{
if (event.type == FocusIn)
{
HasFocus = true;
// Clear urgency flag
XWMHints * wm_hint = static_cast<XWMHints*>(Hints);
if (wm_hint->flags & XUrgencyHint)
{
wm_hint->flags &= ~XUrgencyHint;
XSetWMHints(dpy, wnd, wm_hint);
}
}
else if (event.type == FocusOut /*|| event.type == UnmapNotify*/)
{
int detail = reinterpret_cast<XFocusChangeEvent*>(&event)->detail;
// StdGtkWindow gets two FocusOut events, one of which comes
// directly after a FocusIn event even when the window has
// focus. For these FocusOut events, detail is set to
// NotifyInferior which is why we are ignoring it here.
if (detail != NotifyInferior)
{
HasFocus = false;
}
}
}
#endif // USE_X11