Jump to content
Josh

Linux Window class source

Recommended Posts

EDIT: fullscreen has been implemented.

 

-------------------------

 

I am releasing the source code for the Linux implementation of the Leadwerks Window class. I added support for the mouse wheel, but fullscreen windows are a difficult problem that don't seem to have a simple answer. Maybe someone can figure it out.

 

Linux.window.h

#pragma once

#include <string>
#include <stdio.h>
#include <stdlib.h>

/*
#include <GL/glx.h>
#include <GL/gl.h>
*/

#include <X11/X.h> /* X11 constant (e.g. TrueColor) */
#include <X11/keysym.h>
#include <X11/Xatom.h>

//Interferes with Collision::None:
#undef None

#include "../Leadwerks.h"

/*
ToDo:
ADD: Remove titlebar (untested): http://help.lockergnome.com/linux/lib-titlebar--ftopict199622.html
ADD: Maximized/minimized set and detect
ADD: Change resolution / fullscreen
FIX: If window is created, then immediately hidden, it will still be visible: same thing happens with maximized window
ADD: X11 key codes: http://www.gp32x.com/board/index.php?/topic/57164-raw-x11-keycodes/
*/
namespace Leadwerks
{
#define MOUSE_LEFT 1
#define MOUSE_RIGHT 2

class Window : public Object
{
	static Cursor x_cursor;
public:
::Window window;
std::string title;
bool closed;
Atom wmDeleteMessage;
Vec3 mousecoords;
bool mousedownstate[3];
bool mousehitstate[3];
bool keydownstate[256];
bool keyhitstate[256];
int x,y;
Context* context;
	static ::XVisualInfo *visualinfo;
	static ::Display* display;
bool takeownership;
	int getxkey(XEvent *event);

	Window();
	virtual ~Window();

virtual bool Closed();
	virtual void Show();
	virtual void Hide();
	virtual std::string Debug();

	virtual void Activate();
virtual bool Minimized();
virtual bool Maximized();
virtual void Update();
	virtual std::string GetClassName();
	virtual int GetX();
	virtual int GetY();
	virtual int GetWidth();
	virtual int GetHeight();
	virtual int GetClientWidth();
	virtual int GetClientHeight();
virtual void SetLayout(const int x, const int y, const int width, const int height);
	virtual void SetMousePosition(const float x, const float y);
	virtual void SetMousePosition(const float x, const float y, const float z);
virtual Vec3 GetMousePosition();
	virtual bool MouseDown(const int button=1);
	virtual bool MouseHit(const int button=1);
	virtual bool KeyDown(const int keycode);
	virtual bool KeyHit(const int keycode);		//virtual void Show();
	//virtual void Hide();
	//virtual bool KeyDown(const int keycode);
	//virtual bool KeyHit(const int keycode);
	//virtual void HideMouse();
	//virtual void ShowMouse();
	virtual void Minimize();
	virtual void Maximize();
	virtual void Restore();
	//virtual bool Minimized();
	//virtual bool Maximized();

	static const int Titlebar;
	static const int Resizable;
	static const int Center;
	static const int Hidden;
	static const int FullScreen;
static Leadwerks::Window* current;
static Window* Create(XID xid);

static Atom _NET_WM_STATE_MAXIMIZED_HORZ;
static Atom _NET_WM_STATE_MAXIMIZED_VERT;
static Atom _NET_WM_STATE;
static Atom _NET_WM_STATE_HIDDEN;

static Leadwerks::Window* GetCurrent();

/*
	virtual void Show();
	virtual void Hide();
	virtual std::string Debug();

	virtual std::string GetClassName();
	virtual int GetX();
	virtual int GetY();
	virtual int GetWidth();
	virtual int GetHeight();
	virtual int GetClientWidth();
	virtual int GetClientHeight();

	virtual void SetLayout(const int x, const int y, const int width, const int height);

	virtual void Minimize();
	virtual void Maximize();
	virtual void Restore();
	virtual bool Minimized();
	virtual bool Maximized();
	virtual bool Closed();

	virtual bool Active();
	virtual void Activate();

	virtual bool KeyDown(const int keycode);
	virtual bool KeyHit(const int keycode);*/
	virtual void FlushKeys();

	virtual void HideMouse();
	virtual void ShowMouse();

	/*virtual void SetMousePosition(const float x, const float y);
	virtual void SetMousePosition(const float x, const float y, const float z);
	virtual Vec3 GetMousePosition();
	virtual bool MouseDown(const int button=MOUSE_LEFT);
	virtual bool MouseHit(const int button=MOUSE_RIGHT);*/
	virtual int MouseX();
	virtual int MouseY();
	virtual int MouseZ();
	virtual void FlushMouse();



	virtual Vec2 GetTouchPosition(const int index);
	virtual bool TouchDown(const int index);
	virtual bool TouchHit(const int index);

	//static Window* current;
	//static Window* GetCurrent();

	static Window* Create(const std::string& title="Leadwerks",const int x=0, const int y=0, const int width=1024, const int height=768, const int style=Window::Titlebar);
};
}

 

Linux.window.cpp

#include "Linux.window.h"
#include <limits.h>
//#include <curses.h>

namespace Leadwerks
{
Display* Window::display = NULL;
XVisualInfo* Window::visualinfo = NULL;
Leadwerks::Window* Window::current = NULL;
Atom Window::_NET_WM_STATE_MAXIMIZED_HORZ;
Atom Window::_NET_WM_STATE_MAXIMIZED_VERT;
Atom Window::_NET_WM_STATE;
Atom Window::_NET_WM_STATE_HIDDEN;
Cursor Window::x_cursor=0;

const int Window::Titlebar=1;
const int Window::Resizable=2;
const int Window::Center=16;
const int Window::Hidden=32;
const int Window::FullScreen=64;

Window::Window() : closed(false), context(NULL), takeownership(false)
{
memset(keydownstate,0,sizeof(keydownstate));
memset(keyhitstate,0,sizeof(keyhitstate));
memset(mousedownstate,0,sizeof(mousedownstate));
memset(mousehitstate,0,sizeof(mousehitstate));
int i;
for (i=0; i<3; i++)
{
mousedownstate[i]=false;
mousehitstate[i]=false;
}
for (i=0; i<256; i++)
{
keydownstate[i]=false;
keyhitstate[i]=false;
}
for (i=0; i<6; i++)
{
mousedownstate[i]=false;
mousehitstate[i]=false;
}
}

//bool Window::GetHidden()
//{
//_NET_WM_STATE_HIDDEN
//}

bool Window::KeyDown(const int keycode)
{
Update();
if (keycode<0 || keycode>255) return false;
return keydownstate[keycode];
}

bool Window::KeyHit(const int keycode)
{
Update();
if (keycode<0 || keycode>255) return false;
bool result = keyhitstate[keycode];
keyhitstate[keycode] = false;
return result;
}

bool Window::MouseDown(const int button)
{
Update();
if (button<1 || button>3) return false;
return mousedownstate[button-1];
}

Vec3 Window::GetMousePosition()
{
Update();
return mousecoords;
}

void Window::SetMousePosition(const float x, const float y)
{
XWarpPointer(display,0L,window,0,0,0,0,x,y);
}

void Window::SetMousePosition(const float x, const float y, const float z)
{
XWarpPointer(display,0L,window,0,0,0,0,x,y);
}

bool Window::MouseHit(const int button)
{
Update();
if (button<1 || button>3) return false;
bool result = mousehitstate[button-1];
mousehitstate[button-1]=false;
return result;
}

Window::~Window()
{
if (context)
{
context->Release();
context=NULL;
}
if (takeownership) XDestroyWindow(display,window);
window = NULL;
}

int Window::getxkey(XEvent *event)
{
int key;
key=XLookupKeysym(&event->xkey,0)&255;
if (key>=97&&key<=126) return key+(65-97); //a..z
if (key>=81&&key<=84) return key+(37-81); //arrow keys
if (key>=190 && key<=201) return key+(112-190); //function keys
if (key==99) return 45; //insert
if (key==227) return 162; //lctrl
if (key==233) return 164; //lalt
if (key==234) return 165; //ralt
if (key==228) return 163; //rctrl
if (key==225) return 16;//160; //lshift
if (key==226) return 16;//161; //rshiftz
if (key==127) return 144; //numlock
if (key==20) return 145; //scroll
if (key==158) return 96; //numkeys 0..9
if (key==156) return 97;
if (key==153) return 98;
if (key==155) return 99;
if (key==150) return 100;
if (key==157) return 101;
if (key==152) return 102;
if (key==149) return 103;
if (key==151) return 104;
if (key==154) return 105;
if (key==235) return 91; //left windows key
if (key==236) return 92; //right windows key
if (key==103) return 93; //startmenu windows key
if (key==80) return 36; //home
if (key==85) return 33; //pageup
if (key==255) return 46; //delete
if (key==87) return 35; //end
if (key==86) return 34; //pagedown
if (key==97) return 42; //print screen
if (key==159) return 110; //keypad .
if (key==141) return 13; //keypad enter
if (key==171) return 107; //keypad add
if (key==173) return 109; //keypad minus
if (key==170) return 106; //keypad mult
if (key==175) return 111; //keypad divide
if (key==96) return 192; //tilde key
if (key=='-') return 189; //minus
if (key=='=') return 187; //equals
if (key==91) return 219; //[
if (key==93) return 221; //]
if (key==92) return 226; //backslash
if (key==59) return 186; //semicolon
if (key==39) return 222; //quotes
if (key==44) return 188; //comma key
if (key==46) return 190; //period key
if (key==47) return 191; //questionmark key

return key;
}

void Window::Update()
{
XEvent event;
unsigned int keycode;

while(XPending(display))
{
XNextEvent(display, &event);
switch (event.type)
{

case FocusIn:
current = this;
break;
case FocusOut:
//printf("FOCUSOUT\n");
break;
case KeyPress:
keycode = getxkey(&event);//XLookupKeysym(&event.xkey, 0);
//printf("%i\n",event.xkey.keycode);
if (keycode>0 && keycode<256)
{
keydownstate[keycode]=true;
keyhitstate[keycode]=true;
}
break;
case KeyRelease:
keycode = getxkey(&event);
//printf("%i\n",event.xkey.keycode);
if (keycode>0 && keycode<256)
{
keydownstate[keycode]=false;
}
break;
case MotionNotify:
mousecoords.x = event.xbutton.x;
mousecoords.y = event.xbutton.y;
break;
case ButtonRelease:
switch (event.xbutton.button)
{
case Button1:
mousedownstate[0]=false;
break;
case Button2:
mousedownstate[1]=false;
break;
case Button3:
mousedownstate[2]=false;
break;
case Button4:
mousecoords.z += 1.0f;
break;
case Button5:
mousecoords.z -= 1.0f;
break;
}
break;
case ButtonPress:
switch (event.xbutton.button)
{
case Button1:
mousedownstate[0]=true;
mousehitstate[0]=true;
break;
case Button2:
mousedownstate[1]=true;
mousehitstate[1]=true;
break;
case Button3:
mousedownstate[2]=true;
mousehitstate[2]=true;
break;
}
break;
case ClientMessage:
if (event.xclient.data.l[0] == wmDeleteMessage)
closed = true;
break;
default:
//printf("%i\n",event.type);
break;
}
}
}

void Window::Activate()
{
//TODO
}

bool Window::Closed()
{
Update();
return closed;
}

Window* Window::Create(XID xid)
{
Window* window = new Window;
window->window = xid;
window->takeownership=false;
return window;
}

Window* Window::Create(const std::string& title, const int x, const int y, const int width, const int height, const int style)
{
Window* window = new Window;
window->x = x; window->y=y;
window->title = title;
XSetWindowAttributes swa;
if (display==NULL)
{
display = XOpenDisplay(NULL);
if (display==NULL) Debug::Error("Failed to open X display.");
/* find an OpenGL-capable RGB visual with depth buffer */
int dblBuf[] = {GLX_RGBA, GLX_DEPTH_SIZE, 16, GLX_DOUBLEBUFFER, 0L};
//int snglBuf[] = {GLX_RGBA, GLX_DEPTH_SIZE, 16, 0L};
visualinfo = glXChooseVisual(display, DefaultScreen(display), dblBuf);
/*if (vi == NULL)
{
visualinfo = glXChooseVisual(display, DefaultScreen(display), snglBuf);
if (visualinfo == NULL) fatalError("no RGB visual with depth buffer");
//doubleBuffer = GL_FALSE;
}*/
//XSynchronize(display,True);
_NET_WM_STATE_MAXIMIZED_HORZ = XInternAtom(display, "_NET_WM_STATE_MAXIMIZED_HORZ", True);
_NET_WM_STATE_MAXIMIZED_VERT = XInternAtom(display, "_NET_WM_STATE_MAXIMIZED_VERT", True);
_NET_WM_STATE = XInternAtom(display, "_NET_WM_STATE", True);
_NET_WM_STATE_HIDDEN = XInternAtom(display, "_NET_WM_STATE_HIDDEN", True);
}

Colormap cmap;
cmap = XCreateColormap(display, RootWindow(display, visualinfo->screen), visualinfo->visual, AllocNone);
swa.colormap = cmap;
swa.border_pixel = 0;
swa.event_mask = FocusChangeMask | KeyPressMask | KeyReleaseMask | ExposureMask | ButtonPressMask | ButtonReleaseMask | StructureNotifyMask | ButtonMotionMask | PointerMotionMask;

//if (FullScreen & style)
swa.override_redirect = true;

window->window = XCreateWindow(window->display, RootWindow(display, visualinfo->screen), x, y, width, height, 0, visualinfo->depth, InputOutput, visualinfo->visual, CWBorderPixel | CWColormap | CWEventMask, &swa);

//Some weird stuff to catch window close events
window->wmDeleteMessage = XInternAtom(display, "WM_DELETE_WINDOW", False);
XSetWMProtocols(display, window->window, &window->wmDeleteMessage, 1);

std::string s = title;
const char* name = s.c_str();
XSizeHints sizehints;

if (Window::Resizable & style)
{
sizehints.flags = PMinSize | PMaxSize;
sizehints.min_width = width;
sizehints.min_height = height;
sizehints.max_width = width;
sizehints.max_height = height;
XSetStandardProperties(display, window->window, name, name, 0L, NULL, 0, &sizehints);
}
else
{
XSetStandardProperties(display, window->window, name, name, 0L, NULL, 0, NULL);
}

if (!(Hidden & style))
{
XMapWindow(display,window->window);
XMoveWindow(display, window->window,x,y);
}

current = window;

//XFlush(display);
//XSync(display,true);
//XFlush(display);

return window;
}

std::string Window::GetClassName()
{
return "Window";
}

int Window::GetX()
{
XWindowAttributes attrib;
XGetWindowAttributes(display,window,&attrib);
return attrib.x;
}

int Window::GetY()
{
XWindowAttributes attrib;
XGetWindowAttributes(display,window,&attrib);
return attrib.y;
}

int Window:: GetWidth()
{
XWindowAttributes attrib;
XGetWindowAttributes(display,window,&attrib);
return attrib.width;// + attrib.border_width * 2;
}

int Window::GetHeight()
{
XWindowAttributes attrib;
XGetWindowAttributes(display,window,&attrib);
return attrib.height;// + attrib.border_width * 2 + attrib.;
}

void Window::SetLayout(const int x, const int y, const int width, const int height)
{
XMoveResizeWindow(display,window,x,y,width,height);
}

int Window::GetClientWidth()
{
::Window root_return;
int x_return, y_return;
unsigned int width_return, height_return;
unsigned int border_width_return;
unsigned int depth_return;
XGetGeometry(display,window,&root_return,&x_return,&y_return,&width_return,&height_return,&border_width_return,&depth_return);
return width_return;
}

int Window::GetClientHeight()
{
::Window root_return;
int x_return, y_return;
unsigned int width_return, height_return;
unsigned int border_width_return;
unsigned int depth_return;
XGetGeometry(display,window,&root_return,&x_return,&y_return,&width_return,&height_return,&border_width_return,&depth_return);
return height_return;
}

std::string Window::Debug()
{
return "";//"//return "{title=\""+title+"\",shape=_{"+String(GetX())+","+String(GetY())+","+String(GetWidth())+","+String(GetHeight())+"}}";
}

void Window::Minimize()
{
XIconifyWindow(display,window,0);
}

bool Window::Minimized()
{
//Atom wmState = XInternAtom(display, "_NET_WM_STATE", True);
Atom type;
int format;
//Atom _NET_WM_STATE_HIDDEN = XInternAtom(display, "_NET_WM_STATE_HIDDEN", False);
unsigned long count, bytesAfter;
unsigned char *properties = NULL;
if (XGetWindowProperty(display, window, _NET_WM_STATE, 0, LONG_MAX, False, AnyPropertyType, &type, &format, &count, &bytesAfter, &properties)==Success)
{
Atom* atoms = (Atom*)properties;
for (int i = 0; i < count; i++)
{
if (atoms[i]==_NET_WM_STATE_HIDDEN) return true;
}
}
return false;
}

bool Window::Maximized()
{
bool xmax = false;
bool ymax = false;
//Atom wmState = XInternAtom(display, "_NET_WM_STATE", True);
Atom type;
int format;
//Atom _NET_WM_STATE_MAXIMIZED_HORZ = XInternAtom(display, "_NET_WM_STATE_MAXIMIZED_HORZ", True);
//Atom _NET_WM_STATE_MAXIMIZED_VERT = XInternAtom(display, "_NET_WM_STATE_MAXIMIZED_VERT", True);
unsigned long count, bytesAfter;
unsigned char *properties = NULL;
if (XGetWindowProperty(display, window, _NET_WM_STATE, 0, LONG_MAX, False, AnyPropertyType, &type, &format, &count, &bytesAfter, &properties)==Success)
{
Atom* atoms = (Atom*)properties;
for (int i = 0; i < count; i++)
{
if (atoms[i]==_NET_WM_STATE_MAXIMIZED_HORZ) xmax = true;
if (atoms[i]==_NET_WM_STATE_MAXIMIZED_VERT) ymax = true;
if (xmax==true && ymax==true) return true;

}
}
return false;
}

void Window::Maximize()
{
Atom atoms[2];
atoms[0] = _NET_WM_STATE_MAXIMIZED_HORZ;
atoms[1] = _NET_WM_STATE_MAXIMIZED_VERT;
XChangeProperty(display, window, _NET_WM_STATE, XA_ATOM, 32, PropModeReplace, (unsigned char *)atoms, 2);
}

void Window::Hide()
{
x = GetX();
y = GetY();
XUnmapWindow(display,window);
}

void Window::Show()
{
XMapWindow(display,window);
XMoveWindow(display,window,x,y);
}

Window* Window::GetCurrent()
{
return current;
}

void Window::Restore() {}

void Window::FlushKeys()
{
int i;
for (i=0; i<256; i++)
{
keydownstate[i]=false;
keyhitstate[i]=false;
}
}

void Window::FlushMouse()
{
int i;
for (i=0; i<5; i++)
{
mousedownstate[i]=false;
mousehitstate[i]=false;
}
}

void Window::HideMouse()
{
if (!x_cursor)
{
XColor black;
char bm[]={0,0,0,0,0,0,0,0};
Pixmap pix=XCreateBitmapFromData(display,window,bm,8,8);
memset(&black,0,sizeof(XColor));
black.flags=DoRed|DoGreen|DoBlue;
x_cursor=XCreatePixmapCursor(display,pix,pix,&black,&black,0,0);
XFreePixmap(display,pix);
}
XDefineCursor(display,window,x_cursor);
}

void Window::ShowMouse()
{
XUndefineCursor(display,window);
}

int Window::MouseX() { return 0; }
int Window::MouseY() { return 0; }
int Window::MouseZ() { return 0; }
Vec2 Window::GetTouchPosition(const int index) { return Vec2(0); }
bool Window::TouchDown(const int index) { return false; }
bool Window::TouchHit(const int index) { return false; }
}

Share this post


Link to post

I've forwarded this in a few places and looked at it myself and while I'm not that great a coder I don't even fully comprehend the where and how you are trying to create a full screen window after reading it (I only see redirecting the override but that sounds like stuff is missing). Mind pointing it out?

 

Also: http://www.tonyobryan.com/index.php?article=9

At the bottom there's a Linux/GLX related tutorial: http://nehe.gamedev.net/tutorial/creating_an_opengl_window_win32/13001/

 

And have you looked at how SDL does it? Even if you are not using it wholly you can still use their code, the zlib license allows for it.

Share this post


Link to post

BTW: The Leadwerks account has over 1k followers on Twitter, maybe put out an official call for help there, so more people can see it and will RT it. The more eyeballs on this, the better. X versed devs don't exactly grow on trees.

 

EDIT:

 

Nerds react too...

 

Writing directly to X11!

 

m***u: just borrow from SDL

 

N***r: oh he's coding directly for X? sad.png

N***r: maybe create an SDL wrapper for him and say 'just use this damnit'

 

f***o: **** me who seriously writes to X anymore

f***o: i don't know what this code does and neither does anyone else in the world

Share this post


Link to post

Here's something a little more helpful somebody dug out of his IRC logs.

 

N***r: <S2Slacker> It uses EWMH fullscreen instead of using an override-redirect window on top that ignores the window manager

N***r: <N***r> but it still uses xrandr or something to change resolution?

N***r: <S2Slacker> yes, NV-CONTROL for nvidia and RANDR for ati/intel (and nvidia if dynamic twinview is disabled)

N***r: <S2Slacker> just can't use XFree86-VidModeExtension as the fullscreen hint doesn't work right for gnome/kde when you change resolutions using it

N***r: <S2Slacker> that's why you can't change the fullscreen resolution when using Xinerama

 

This is quite old though.

Share this post


Link to post

I am far from an expert but I think you need the _NET_WM_STATE_FULLSCREEN hint also.

 

I did take a quick look for _NET_WM_STATE_FULLSCREEN in SDL2 and the interesting files to start digging in are the following:

SDL2-2.0.3\src\video\x11\SDL_x11video.c
SDL2-2.0.3\src\video\x11\SDL_x11video.h
SDL2-2.0.3\src\video\x11\SDL_x11window.c

 

Download https://www.libsdl.org/release/SDL2-2.0.3.zip

 

A trustful source for these kind of things is anything Ryan "icculus" Gordon has written.

Share this post


Link to post
N***r: oh he's coding directly for X? sad.png

N***r: maybe create an SDL wrapper for him and say 'just use this damnit'

 

f***o: **** me who seriously writes to X anymore

f***o: i don't know what this code does and neither does anyone else in the world

Other than this fullscreen stuff, I have found the X API to be incredibly simple and easy. sleep.png

Share this post


Link to post

The last guy in that quote has shipped plenty of Linux and OS X game ports already but he is prone to hyperbole.

But even if you don't want to use it, reading SDL2's code would still be good, especially to get a proper full screen window and not just a borderless full screen window, like them lazy devs do.

Share this post


Link to post

I'd copy SDL's implementation as well;

 

https://hg.libsdl.org/SDL/file/3331d2f57704/src/video/x11/SDL_x11window.c#l1242

 

There are simply way to many edge cases for it to be a productive use of your time, especially when the license is so liberal;

 

Simple DirectMedia Layer

Copyright © 1997-2014 Sam Lantinga <slouken@libsdl.org>

 

This software is provided 'as-is', without any express or implied

warranty. In no event will the authors be held liable for any damages

arising from the use of this software.

 

Permission is granted to anyone to use this software for any purpose,

including commercial applications, and to alter it and redistribute it

freely, subject to the following restrictions:

 

1. The origin of this software must not be misrepresented; you must not

claim that you wrote the original software. If you use this software

in a product, an acknowledgment in the product documentation would be

appreciated but is not required.

2. Altered source versions must be plainly marked as such, and must not be

misrepresented as being the original software.

3. This notice may not be removed or altered from any source distribution.

 

Selective bold is mine - to highlight the relevant parts.

 

Only 3 is an issue and even there is just when you sell source code that you have to have a comment going "/*the following X functions were copied from SDL under the following license, the rest of this document is not released under that license.*/"

 

Really shouldn't be a problem

Share this post


Link to post

If this hasn't been worked out yet, what about similar options?

 

I actually prefer using maximized windows rather than fullscreen, because then I'm less likely to lose track of time. Several games employ alternatives already, such as maximized bordered or maximized borderless windows.

 

In the case of "Don't Starve" and "Delver" , the game is aware of how much space is available after window decorations and panels, and resizes the context accordingly (maximizing the window removes the borders but leaves the title bar).

 

In the case of "Eldritch" setting the game to 1920x1080 without enabling full screen gives a borderless window-the game takes up the entire screen but leaves your panel visible. This is..... well, an easier option, as it does not seem to take in account how much space the panel takes up, just displaying behind it. My panel only takes up 20px at least, so you really shouldn't be placing crucial information that close to the edge, anyways.

 

 

I'm hoping that a resizable window with fitting context is an easy option. I was happy to see that the maximize button was not disabled on the game test window, but looked around and didn't easily find any easy way to unlock the mouse to try it. I've done it with OpenGL in Java (just basic stuff) so I know it's possible.

Share this post


Link to post

I notice that the class uses XSetStandardProperties but that one has been superseded by XSetWMProperties.

Share this post


Link to post

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...