Pyrogenesis  13997
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Pages
x.cpp
Go to the documentation of this file.
1 /* Copyright (c) 2013 Wildfire Games
2  *
3  * Permission is hereby granted, free of charge, to any person obtaining
4  * a copy of this software and associated documentation files (the
5  * "Software"), to deal in the Software without restriction, including
6  * without limitation the rights to use, copy, modify, merge, publish,
7  * distribute, sublicense, and/or sell copies of the Software, and to
8  * permit persons to whom the Software is furnished to do so, subject to
9  * the following conditions:
10  *
11  * The above copyright notice and this permission notice shall be included
12  * in all copies or substantial portions of the Software.
13  *
14  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15  * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
17  * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
18  * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
19  * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
20  * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21  */
22 
23 // X Window System-specific code
24 
25 #include "precompiled.h"
26 
27 #if OS_LINUX || OS_BSD
28 # define HAVE_X 1
29 #else
30 # define HAVE_X 0
31 #endif
32 
33 #if HAVE_X
34 
35 #include "lib/debug.h"
36 #include "lib/sysdep/gfx.h"
37 #include "lib/sysdep/cursor.h"
38 
39 #include "ps/VideoMode.h"
40 
41 #define Cursor X__Cursor
42 
43 #include <Xlib.h>
44 #include <stdlib.h>
45 #include <Xatom.h>
46 #include <Xcursor/Xcursor.h>
47 
48 #include "SDL.h"
49 #include "SDL_syswm.h"
50 
51 #include <algorithm>
52 #undef Cursor
53 #undef Status
54 
55 static Display *g_SDL_Display;
56 static Window g_SDL_Window;
57 #if !SDL_VERSION_ATLEAST(1, 3, 0)
58 static void (*g_Lock_Display)(void);
59 static void (*g_Unlock_Display)(void);
60 #endif
61 static wchar_t *selection_data=NULL;
62 static size_t selection_size=0;
63 
64 namespace gfx {
65 
66 Status GetVideoMode(int* xres, int* yres, int* bpp, int* freq)
67 {
68  Display* disp = XOpenDisplay(0);
69  if(!disp)
71 
72  int screen = XDefaultScreen(disp);
73 
74  /* 2004-07-13
75  NOTE: The XDisplayWidth/Height functions don't actually return the current
76  display mode - they return the size of the root window. This means that
77  users with "Virtual Desktops" bigger than what their monitors/graphics
78  card can handle will have to set their 0AD screen resolution manually.
79 
80  There's supposed to be an X extension that can give you the actual display
81  mode, probably including refresh rate info etc, but it's not worth
82  researching and implementing that at this stage.
83  */
84 
85  if(xres)
86  *xres = XDisplayWidth(disp, screen);
87  if(yres)
88  *yres = XDisplayHeight(disp, screen);
89  if(bpp)
90  *bpp = XDefaultDepth(disp, screen);
91  if(freq)
92  *freq = 0;
93  XCloseDisplay(disp);
94  return INFO::OK;
95 }
96 
97 
98 Status GetMonitorSize(int& width_mm, int& height_mm)
99 {
100  Display* disp = XOpenDisplay(0);
101  if(!disp)
103 
104  int screen = XDefaultScreen(disp);
105 
106  width_mm = XDisplayWidthMM(disp, screen);
107  height_mm = XDisplayHeightMM(disp, screen);
108 
109  XCloseDisplay(disp);
110  return INFO::OK;
111 }
112 
113 } // namespace gfx
114 
115 
116 static bool get_wminfo(SDL_SysWMinfo& wminfo)
117 {
118  SDL_VERSION(&wminfo.version);
119 
120 #if SDL_VERSION_ATLEAST(2, 0, 0)
121  const int ret = SDL_GetWindowWMInfo(g_VideoMode.GetWindow(), &wminfo);
122 #else
123  const int ret = SDL_GetWMInfo(&wminfo);
124 #endif
125  if(ret == 1)
126  return true;
127 
128  if(ret == -1)
129  {
130  debug_printf(L"SDL_GetWMInfo failed\n");
131  return false;
132  }
133  if(ret == 0)
134  {
135  debug_printf(L"SDL_GetWMInfo is not implemented on this platform\n");
136  return false;
137  }
138 
139  debug_printf(L"SDL_GetWMInfo returned an unknown value: %d\n", ret);
140  return false;
141 }
142 
143 /*
144 Oh, boy, this is heavy stuff...
145 
146 <User-End Stuff - Definitions and Conventions>
147 http://www.freedesktop.org/standards/clipboards-spec/clipboards.txt
148 <Technical, API stuff>
149 http://www.mail-archive.com/xfree86@xfree86.org/msg15594.html
150 http://michael.toren.net/mirrors/doc/X-copy+paste.txt
151 http://devdocs.wesnoth.org/clipboard_8cpp-source.html
152 http://tronche.com/gui/x/xlib/window-information/XGetWindowProperty.html
153 http://www.jwz.org/doc/x-cut-and-paste.html
154 
155 The basic run-down on X Selection Handling:
156 * One window owns the "current selection" at any one time
157 * Accessing the Selection (i.e. "paste"), Step-by-step
158  * Ask the X server for the current selection owner
159  * Ask the selection owner window to convert the selection into a format
160  we can understand (XA_STRING - Latin-1 string - for now)
161  * The converted result is stored as a property of the *selection owner*
162  window. It is possible to specify the current application window as the
163  target - but that'd require some X message handling... easier to skip that..
164  * The final step is to acquire the property value of the selection owner
165  window
166 
167 Notes:
168 An "Atom" is a server-side object that represents a string by an index into some
169 kind of table or something. Pretty much like a handle that refers to one unique
170 string. Atoms are used here to refer to property names and value formats.
171 
172 Expansions:
173 * Implement UTF-8 format support (should be interresting for international users)
174 
175 */
176 wchar_t *sys_clipboard_get()
177 {
178  Display *disp=XOpenDisplay(NULL);
179  if(!disp)
180  return NULL;
181 
182  // We use CLIPBOARD as the default, since the CLIPBOARD semantics are much
183  // closer to windows semantics.
184  Atom selSource=XInternAtom(disp, "CLIPBOARD", False);
185 
186  Window selOwner=XGetSelectionOwner(disp, selSource);
187  if(selOwner == None)
188  {
189  // However, since many apps don't use CLIPBOARD, but use PRIMARY instead
190  // we use XA_PRIMARY as a fallback clipboard. This is true for xterm,
191  // for example.
192  selSource=XA_PRIMARY;
193  selOwner=XGetSelectionOwner(disp, selSource);
194  }
195  if(selOwner != None) {
196  Atom pty=XInternAtom(disp, "SelectionPropertyTemp", False);
197  XConvertSelection(disp, selSource, XA_STRING, pty, selOwner, CurrentTime);
198  XFlush(disp);
199 
200  Atom type;
201  int format=0, result=0;
202  unsigned long len=0, bytes_left=0, dummy=0;
203  u8 *data=NULL;
204 
205  // Get the length of the property and some attributes
206  // bytes_left will contain the length of the selection
207  result = XGetWindowProperty (disp, selOwner, pty,
208  0, 0, // offset - len
209  0, // Delete 0==FALSE
210  AnyPropertyType,//flag
211  &type, // return type
212  &format, // return format
213  &len, &bytes_left,
214  &data);
215  if(result != Success)
216  debug_printf(L"clipboard_get: result: %d type:%lu len:%lu format:%d bytes_left:%lu\n",
217  result, type, len, format, bytes_left);
218  if(result == Success && bytes_left > 0)
219  {
220  result = XGetWindowProperty (disp, selOwner,
221  pty, 0, bytes_left, 0,
222  AnyPropertyType, &type, &format,
223  &len, &dummy, &data);
224 
225  if(result == Success)
226  {
227  debug_printf(L"clipboard_get: XGetWindowProperty succeeded, returning data\n");
228  debug_printf(L"clipboard_get: data was: \"%hs\", type was %lu, XA_STRING atom is %lu\n", data, type, XA_STRING);
229 
230  if(type == XA_STRING) //Latin-1: Just copy into low byte of wchar_t
231  {
232  wchar_t *ret=(wchar_t *)malloc((bytes_left+1)*sizeof(wchar_t));
233  std::copy(data, data+bytes_left, ret);
234  ret[bytes_left]=0;
235  return ret;
236  }
237  // TODO: Handle UTF8 strings
238  }
239  else
240  {
241  debug_printf(L"clipboard_get: XGetWindowProperty failed!\n");
242  return NULL;
243  }
244  }
245  }
246 
247  return NULL;
248 }
249 
250 Status sys_clipboard_free(wchar_t *clip_buf)
251 {
252  free(clip_buf);
253  return INFO::OK;
254 }
255 
256 /**
257  * An SDL Event filter that intercepts other applications' requests for the
258  * X selection buffer.
259  *
260  * @see x11_clipboard_init
261  * @see sys_clipboard_set
262  */
263 #if SDL_VERSION_ATLEAST(1, 3, 0)
264 int clipboard_filter(void* UNUSED(userdata), SDL_Event* event)
265 #else
266 int clipboard_filter(const SDL_Event* event)
267 #endif
268 {
269  /* Pass on all non-window manager specific events immediately */
270  /* And do nothing if we don't actually have a clip-out to send out */
271  if(event->type != SDL_SYSWMEVENT || !selection_data)
272  return 1;
273 
274  /* Handle window-manager specific clipboard events */
275  /* (Note: libsdl must be compiled with X11 support (SDL_VIDEO_DRIVER_X11 in SDL_config.h) -
276  else you'll get errors like "'struct SDL_SysWMmsg' has no member named 'xevent'") */
277 #if SDL_VERSION_ATLEAST(1, 3, 0)
278  XEvent* xevent = &event->syswm.msg->msg.x11.event;
279 #else
280  XEvent* xevent = &event->syswm.msg->event.xevent;
281 #endif
282  switch(xevent->type) {
283  /* Copy the selection from our buffer to the requested property, and
284  convert to the requested target format */
285  case SelectionRequest: {
286  XSelectionRequestEvent *req;
287  XEvent sevent;
288 
289  req = &xevent->xselectionrequest;
290  sevent.xselection.type = SelectionNotify;
291  sevent.xselection.display = req->display;
292  sevent.xselection.selection = req->selection;
293  sevent.xselection.target = req->target;
294  sevent.xselection.property = None;
295  sevent.xselection.requestor = req->requestor;
296  sevent.xselection.time = req->time;
297  // Simply strip all non-Latin1 characters and replace with '?'
298  // We should support XA_UTF8
299  if(req->target == XA_STRING)
300  {
301  size_t size = wcslen(selection_data);
302  u8* buf = (u8*)alloca(size);
303 
304  for(size_t i = 0; i < size; i++)
305  {
306  buf[i] = selection_data[i] < 0x100 ? selection_data[i] : '?';
307  }
308 
309  XChangeProperty(g_SDL_Display, req->requestor, req->property,
310  sevent.xselection.target, 8, PropModeReplace,
311  buf, size);
312  sevent.xselection.property = req->property;
313  }
314  // TODO Add more target formats
315  XSendEvent(g_SDL_Display, req->requestor, False, 0, &sevent);
316  XSync(g_SDL_Display, False);
317  }
318  break;
319  }
320 
321  return 1;
322 }
323 
324 /**
325  * Initialization for X clipboard handling, called on-demand by
326  * sys_clipboard_set.
327  */
328 Status x11_clipboard_init()
329 {
330  SDL_SysWMinfo info;
331 
332  if(get_wminfo(info))
333  {
334  /* Save the information for later use */
335  if(info.subsystem == SDL_SYSWM_X11)
336  {
337  g_SDL_Display = info.info.x11.display;
338  g_SDL_Window = info.info.x11.window;
339 #if !SDL_VERSION_ATLEAST(1, 3, 0)
340  g_Lock_Display = info.info.x11.lock_func;
341  g_Unlock_Display = info.info.x11.unlock_func;
342 #endif
343 
344  /* Enable the special window hook events */
345  SDL_EventState(SDL_SYSWMEVENT, SDL_ENABLE);
346 #if SDL_VERSION_ATLEAST(1, 3, 0)
347  SDL_SetEventFilter(clipboard_filter, NULL);
348 #else
349  SDL_SetEventFilter(clipboard_filter);
350 #endif
351 
352  return INFO::OK;
353  }
354  else
355  {
356  return ERR::FAIL;
357  }
358  }
359 
360  return INFO::OK;
361 }
362 
363 /**
364  * Set the Selection (i.e. "copy")
365  *
366  * Step-by-step (X11)
367  * <ul>
368  * <li>Store the selection text in a local buffer
369  * <li>Tell the X server that we want to own the selection
370  * <li>Listen for Selection events and respond to them as appropriate
371  * </ul>
372  */
373 Status sys_clipboard_set(const wchar_t *str)
374 {
375  ONCE(x11_clipboard_init());
376 
377  debug_printf(L"sys_clipboard_set: %ls\n", str);
378 
379  if(selection_data)
380  {
381  free(selection_data);
382  selection_data = NULL;
383  }
384 
385  selection_size = (wcslen(str)+1)*sizeof(wchar_t);
386  selection_data = (wchar_t *)malloc(selection_size);
387  wcscpy(selection_data, str);
388 
389 #if !SDL_VERSION_ATLEAST(1, 3, 0)
390  g_Lock_Display();
391 #endif
392 
393  // Like for the clipboard_get code above, we rather use CLIPBOARD than
394  // PRIMARY - more windows'y behaviour there.
395  Atom clipboard_atom = XInternAtom(g_SDL_Display, "CLIPBOARD", False);
396  XSetSelectionOwner(g_SDL_Display, clipboard_atom, g_SDL_Window, CurrentTime);
397  XSetSelectionOwner(g_SDL_Display, XA_PRIMARY, g_SDL_Window, CurrentTime);
398 
399 #if !SDL_VERSION_ATLEAST(1, 3, 0)
400  g_Unlock_Display();
401 #else
402  // SDL 1.3 doesn't have a lockable event thread, so it just uses
403  // XSync directly instead of lock_func/unlock_func
404  XSync(g_SDL_Display, False);
405 #endif
406 
407  return INFO::OK;
408 }
409 
410 struct sys_cursor_impl
411 {
412  XcursorImage* image;
413  X__Cursor cursor;
414 };
415 
416 static XcursorPixel cursor_pixel_to_x11_format(const XcursorPixel& bgra_pixel)
417 {
418  BOOST_STATIC_ASSERT(sizeof(XcursorPixel) == 4 * sizeof(u8));
419  XcursorPixel ret;
420  u8* dst = reinterpret_cast<u8*>(&ret);
421  const u8* b = reinterpret_cast<const u8*>(&bgra_pixel);
422  const u8 a = b[3];
423 
424  for(size_t i = 0; i < 3; ++i)
425  *dst++ = (b[i]) * a / 255;
426  *dst = a;
427  return ret;
428 }
429 
430 Status sys_cursor_create(int w, int h, void* bgra_img, int hx, int hy, sys_cursor* cursor)
431 {
432  debug_printf(L"sys_cursor_create: using Xcursor to create %d x %d cursor\n", w, h);
433  XcursorImage* image = XcursorImageCreate(w, h);
434  if(!image)
436 
437  const XcursorPixel* bgra_img_begin = reinterpret_cast<XcursorPixel*>(bgra_img);
438  std::transform(bgra_img_begin, bgra_img_begin + (w*h), image->pixels,
439  cursor_pixel_to_x11_format);
440  image->xhot = hx;
441  image->yhot = hy;
442 
443  SDL_SysWMinfo wminfo;
444  if(!get_wminfo(wminfo))
446 
447  sys_cursor_impl* impl = new sys_cursor_impl;
448  impl->image = image;
449  impl->cursor = XcursorImageLoadCursor(wminfo.info.x11.display, image);
450  if(impl->cursor == None)
452 
453  *cursor = static_cast<sys_cursor>(impl);
454  return INFO::OK;
455 }
456 
457 // returns a dummy value representing an empty cursor
459 {
460  static u8 transparent_bgra[] = { 0x0, 0x0, 0x0, 0x0 };
461 
462  return sys_cursor_create(1, 1, static_cast<void*>(transparent_bgra), 0, 0, cursor);
463 }
464 
465 // replaces the current system cursor with the one indicated. need only be
466 // called once per cursor; pass 0 to restore the default.
468 {
469  if(!cursor) // restore default cursor
471  else
472  {
473  SDL_SysWMinfo wminfo;
474  if(!get_wminfo(wminfo))
476 
477  if(wminfo.subsystem != SDL_SYSWM_X11)
479 
480 #if !SDL_VERSION_ATLEAST(1, 3, 0)
481  wminfo.info.x11.lock_func();
482 #endif
483 
485 
486  Window window;
487  if(wminfo.info.x11.window)
488  window = wminfo.info.x11.window;
489  else
490  {
491 #if !SDL_VERSION_ATLEAST(1, 3, 0)
492  // wminfo.info.x11.window is sometimes 0, in which case
493  // it causes a crash; in these cases use fswindow instead
494  window = wminfo.info.x11.fswindow;
495 #else
497 #endif
498  }
499 
500  XDefineCursor(wminfo.info.x11.display, window,
501  static_cast<sys_cursor_impl*>(cursor)->cursor);
502 #if !SDL_VERSION_ATLEAST(1, 3, 0)
503  wminfo.info.x11.unlock_func();
504 #else
505  // SDL 1.3 doesn't have a lockable event thread, so it just uses
506  // XSync directly instead of lock_func/unlock_func
507  XSync(wminfo.info.x11.display, False);
508 #endif
509  }
510 
511  return INFO::OK;}
512 
513 // destroys the indicated cursor and frees its resources. if it is
514 // currently the system cursor, the default cursor is restored first.
516 {
517  // bail now to prevent potential confusion below; there's nothing to do.
518  if(!cursor)
519  return INFO::OK;
520 
521  sys_cursor_set(0); // restore default cursor
522  sys_cursor_impl* impl = static_cast<sys_cursor_impl*>(cursor);
523 
524  XcursorImageDestroy(impl->image);
525 
526  SDL_SysWMinfo wminfo;
527  if(!get_wminfo(wminfo))
528  return ERR::FAIL;
529  XFreeCursor(wminfo.info.x11.display, impl->cursor);
530 
531  delete impl;
532 
533  return INFO::OK;
534 }
535 
537 {
538  return INFO::OK;
539 }
540 
541 
542 #endif // #if HAVE_X
#define u8
Definition: types.h:39
#define UNUSED(param)
mark a function parameter as unused and avoid the corresponding compiler warning. ...
int SDL_ShowCursor(int toggle)
Definition: wsdl.cpp:1106
const Status OK
Definition: status.h:386
Status sys_clipboard_set(const wchar_t *text)
Definition: android.cpp:30
void * sys_cursor
Definition: cursor.h:30
Status sys_cursor_create(int w, int h, void *bgra_img, int hx, int hy, sys_cursor *cursor)
Create a cursor from the given color image.
Definition: android.cpp:72
Status sys_cursor_reset()
reset any cached cursor data.
Definition: android.cpp:116
LIB_API Status GetVideoMode(int *xres, int *yres, int *bpp, int *freq)
(useful for choosing a new video mode)
Definition: android.cpp:47
Status sys_clipboard_free(wchar_t *copy)
Definition: android.cpp:40
Status sys_cursor_create_empty(sys_cursor *cursor)
Create a transparent cursor (used to hide the system cursor).
Definition: android.cpp:85
CVideoMode g_VideoMode
Definition: VideoMode.cpp:42
Uint8 type
Definition: wsdl.h:302
SDL_Window * GetWindow()
Definition: VideoMode.cpp:506
#define ONCE(ONCE_code__)
execute the code passed as a parameter only the first time this is reached.
i64 Status
Error handling system.
Definition: status.h:171
LIB_API Status GetMonitorSize(int &width_mm, int &height_mm)
(useful for determining aspect ratio)
Definition: wgfx.cpp:197
wchar_t * sys_clipboard_get()
Definition: android.cpp:35
unsigned short wchar_t
Definition: wgl.h:78
#define WARN_RETURN(status)
Definition: status.h:255
Status sys_cursor_set(sys_cursor cursor)
override the current system cursor.
Definition: android.cpp:93
const Status FAIL
Definition: status.h:406
Status sys_cursor_free(sys_cursor cursor)
destroy the indicated cursor and frees its resources.
Definition: android.cpp:105
void debug_printf(const wchar_t *fmt,...)
write a formatted string to the debug channel, subject to filtering (see below).
Definition: debug.cpp:142