Josh Posted October 8, 2014 Share Posted October 8, 2014 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; } } 1 Quote My job is to make tools you love, with the features you want, and performance you can't live without. Link to comment Share on other sites More sharing options...
DerRidda Posted October 8, 2014 Share Posted October 8, 2014 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. Quote Link to comment Share on other sites More sharing options...
DerRidda Posted October 8, 2014 Share Posted October 8, 2014 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? 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 Quote Link to comment Share on other sites More sharing options...
DerRidda Posted October 9, 2014 Share Posted October 9, 2014 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. Quote Link to comment Share on other sites More sharing options...
Guppy Posted October 9, 2014 Share Posted October 9, 2014 I'll look it over this weekend, won't have time before then 1 Quote System: Linux Mint 17 ( = Ubuntu 14.04 with cinnamon desktop ) Ubuntu 14.04, AMD HD 6850, i5 2500k Link to comment Share on other sites More sharing options...
codeape Posted October 10, 2014 Share Posted October 10, 2014 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. Quote Link to comment Share on other sites More sharing options...
Josh Posted October 10, 2014 Author Share Posted October 10, 2014 N***r: oh he's coding directly for X? 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. Quote My job is to make tools you love, with the features you want, and performance you can't live without. Link to comment Share on other sites More sharing options...
DerRidda Posted October 10, 2014 Share Posted October 10, 2014 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. Quote Link to comment Share on other sites More sharing options...
Guppy Posted October 11, 2014 Share Posted October 11, 2014 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 1 Quote System: Linux Mint 17 ( = Ubuntu 14.04 with cinnamon desktop ) Ubuntu 14.04, AMD HD 6850, i5 2500k Link to comment Share on other sites More sharing options...
insomniac_lemon Posted October 24, 2014 Share Posted October 24, 2014 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. Quote Link to comment Share on other sites More sharing options...
DerRidda Posted October 31, 2014 Share Posted October 31, 2014 Any progress on this front? Quote Link to comment Share on other sites More sharing options...
codeape Posted March 17, 2015 Share Posted March 17, 2015 I notice that the class uses XSetStandardProperties but that one has been superseded by XSetWMProperties. Quote Link to comment Share on other sites More sharing options...
Recommended Posts
Join the conversation
You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.