no need to have this VMS-only; removed TABs
[wxWidgets.git] / src / unix / utilsx11.cpp
1 /////////////////////////////////////////////////////////////////////////////
2 // Name: src/unix/utilsx11.cpp
3 // Purpose: Miscellaneous X11 functions
4 // Author: Mattia Barbon, Vaclav Slavik, Robert Roebling
5 // Modified by:
6 // Created: 25.03.02
7 // RCS-ID: $Id$
8 // Copyright: (c) wxWindows team
9 // Licence: wxWindows license
10 /////////////////////////////////////////////////////////////////////////////
11
12 #if defined(__WXX11__) || defined(__WXGTK__) || defined(__WXMOTIF__)
13
14 #include "wx/unix/utilsx11.h"
15 #include "wx/iconbndl.h"
16 #include "wx/image.h"
17 #include "wx/icon.h"
18 #include "wx/log.h"
19
20 #ifdef __VMS
21 #pragma message disable nosimpint
22 #endif
23 #include <X11/Xlib.h>
24 #include <X11/Xatom.h>
25 #ifdef __VMS
26 #pragma message enable nosimpint
27 #endif
28
29 #ifdef __WXGTK20__
30 #include <gdk/gdk.h>
31 #include <gdk/gdkx.h>
32 #endif
33
34 // Various X11 Atoms used in this file:
35 static Atom _NET_WM_ICON = 0;
36 static Atom _NET_WM_STATE = 0;
37 static Atom _NET_WM_STATE_FULLSCREEN = 0;
38 static Atom _NET_WM_STATE_STAYS_ON_TOP = 0;
39 static Atom _NET_WM_WINDOW_TYPE = 0;
40 static Atom _NET_WM_WINDOW_TYPE_NORMAL = 0;
41 static Atom _KDE_NET_WM_WINDOW_TYPE_OVERRIDE = 0;
42 static Atom _WIN_LAYER = 0;
43 static Atom KWIN_RUNNING = 0;
44 #ifndef __WXGTK20__
45 static Atom _NET_SUPPORTING_WM_CHECK = 0;
46 static Atom _NET_SUPPORTED = 0;
47 #endif
48
49 #define wxMAKE_ATOM(name, display) \
50 if (name == 0) name = XInternAtom((display), #name, False)
51
52
53 // Is the window mapped?
54 static bool IsMapped(Display *display, Window window)
55 {
56 XWindowAttributes attr;
57 XGetWindowAttributes(display, window, &attr);
58 return (attr.map_state != IsUnmapped);
59 }
60
61
62
63 // Suspends X11 errors. Used when we expect errors but they are not fatal
64 // for us.
65 class wxX11ErrorsSuspender
66 {
67 public:
68 wxX11ErrorsSuspender(Display *d) : m_display(d)
69 {
70 m_old = XSetErrorHandler(handler);
71 }
72 ~wxX11ErrorsSuspender()
73 {
74 XFlush(m_display);
75 XSetErrorHandler(m_old);
76 }
77 private:
78 Display *m_display;
79 int (*m_old)(Display*, XErrorEvent *);
80 static int handler(Display *, XErrorEvent *) { return 0; }
81 };
82
83
84
85 // ----------------------------------------------------------------------------
86 // Setting icons for window manager:
87 // ----------------------------------------------------------------------------
88
89 void wxSetIconsX11( WXDisplay* display, WXWindow window,
90 const wxIconBundle& ib )
91 {
92 #if !wxUSE_NANOX
93 size_t size = 0;
94 size_t i, max = ib.m_icons.GetCount();
95
96 for( i = 0; i < max; ++i )
97 if( ib.m_icons[i].Ok() )
98 size += 2 + ib.m_icons[i].GetWidth() * ib.m_icons[i].GetHeight();
99
100 wxMAKE_ATOM(_NET_WM_ICON, (Display*)display);
101
102 if( size > 0 )
103 {
104 wxUint32* data = new wxUint32[size];
105 wxUint32* ptr = data;
106
107 for( i = 0; i < max; ++i )
108 {
109 const wxImage image = ib.m_icons[i].ConvertToImage();
110 int width = image.GetWidth(), height = image.GetHeight();
111 unsigned char* imageData = image.GetData();
112 unsigned char* imageDataEnd = imageData + ( width * height * 3 );
113 bool hasMask = image.HasMask();
114 unsigned char rMask, gMask, bMask;
115 unsigned char r, g, b, a;
116
117 if( hasMask )
118 {
119 rMask = image.GetMaskRed();
120 gMask = image.GetMaskGreen();
121 bMask = image.GetMaskBlue();
122 }
123 else // no mask, but still init the variables to avoid warnings
124 {
125 rMask =
126 gMask =
127 bMask = 0;
128 }
129
130 *ptr++ = width;
131 *ptr++ = height;
132
133 while( imageData < imageDataEnd ) {
134 r = imageData[0];
135 g = imageData[1];
136 b = imageData[2];
137 if( hasMask && r == rMask && g == gMask && b == bMask )
138 a = 0;
139 else
140 a = 255;
141
142 *ptr++ = ( a << 24 ) | ( r << 16 ) | ( g << 8 ) | b;
143
144 imageData += 3;
145 }
146 }
147
148 XChangeProperty( (Display*)display,
149 (Window)window,
150 _NET_WM_ICON,
151 XA_CARDINAL, 32,
152 PropModeReplace,
153 (unsigned char*)data, size );
154 delete[] data;
155 }
156 else
157 {
158 XDeleteProperty( (Display*)display,
159 (Window)window,
160 _NET_WM_ICON );
161 }
162 #endif
163 }
164
165
166 // ----------------------------------------------------------------------------
167 // Fullscreen mode:
168 // ----------------------------------------------------------------------------
169
170 // NB: Setting fullscreen mode under X11 is a complicated matter. There was
171 // no standard way of doing it until recently. ICCCM doesn't know the
172 // concept of fullscreen windows and the only way to make a window
173 // fullscreen is to remove decorations, resize it to cover entire screen
174 // and set WIN_LAYER_ABOVE_DOCK.
175 //
176 // This doesn't always work, though. Specifically, at least kwin from
177 // KDE 3 ignores the hint. The only way to make kwin accept our request
178 // is to emulate the way Qt does it. That is, unmap the window, set
179 // _NET_WM_WINDOW_TYPE to _KDE_NET_WM_WINDOW_TYPE_OVERRIDE (KDE extension),
180 // add _NET_WM_STATE_STAYS_ON_TOP (ditto) to _NET_WM_STATE and map
181 // the window again.
182 //
183 // Version 1.2 of Window Manager Specification (aka wm-spec aka
184 // Extended Window Manager Hints) introduced _NET_WM_STATE_FULLSCREEN
185 // window state which provides cleanest and simplest possible way of
186 // making a window fullscreen. WM-spec is a de-facto standard adopted
187 // by GNOME and KDE folks, but _NET_WM_STATE_FULLSCREEN isn't yet widely
188 // supported. As of January 2003, only GNOME 2's default WM Metacity
189 // implements, KDE will support it from version 3.2. At toolkits level,
190 // GTK+ >= 2.1.2 uses it as the only method of making windows fullscreen
191 // (that's why wxGTK will *not* switch to using gtk_window_fullscreen
192 // unless it has better compatiblity with older WMs).
193 //
194 //
195 // This is what wxWindows does in wxSetFullScreenStateX11:
196 // 1) if _NET_WM_STATE_FULLSCREEN is supported, use it
197 // 2) otherwise try WM-specific hacks (KDE, IceWM)
198 // 3) use _WIN_LAYER and hope that the WM will recognize it
199 // The code was tested with:
200 // twm, IceWM, WindowMaker, Metacity, kwin, sawfish, lesstif-mwm
201
202
203 #define WIN_LAYER_NORMAL 4
204 #define WIN_LAYER_ABOVE_DOCK 10
205
206 static void wxWinHintsSetLayer(Display *display, Window rootWnd,
207 Window window, int layer)
208 {
209 wxX11ErrorsSuspender noerrors(display);
210
211 XEvent xev;
212
213 wxMAKE_ATOM( _WIN_LAYER, display );
214
215 if (IsMapped(display, window))
216 {
217 xev.type = ClientMessage;
218 xev.xclient.type = ClientMessage;
219 xev.xclient.window = window;
220 xev.xclient.message_type = _WIN_LAYER;
221 xev.xclient.format = 32;
222 xev.xclient.data.l[0] = (long)layer;
223 xev.xclient.data.l[1] = CurrentTime;
224
225 XSendEvent(display, rootWnd, False,
226 SubstructureNotifyMask, (XEvent*) &xev);
227 }
228 else
229 {
230 long data[1];
231
232 data[0] = layer;
233 XChangeProperty(display, window,
234 _WIN_LAYER, XA_CARDINAL, 32,
235 PropModeReplace, (unsigned char *)data, 1);
236 }
237 }
238
239
240
241 #ifdef __WXGTK20__
242 static bool wxQueryWMspecSupport(Display* WXUNUSED(display),
243 Window WXUNUSED(rootWnd),
244 Atom (feature))
245 {
246 GdkAtom gatom = gdk_x11_xatom_to_atom(feature);
247 return gdk_net_wm_supports(gatom);
248 }
249 #else
250 static bool wxQueryWMspecSupport(Display *display, Window rootWnd, Atom feature)
251 {
252 wxMAKE_ATOM(_NET_SUPPORTING_WM_CHECK, display);
253 wxMAKE_ATOM(_NET_SUPPORTED, display);
254
255 // FIXME: We may want to cache these checks. Note that we can't simply
256 // remember the results in global variable because the WM may go
257 // away and be replaced by another one! One possible approach
258 // would be invalidate the case every 15 seconds or so. Since this
259 // code is currently only used by wxTopLevelWindow::ShowFullScreen,
260 // it is not important that it is not optimized.
261 //
262 // If the WM supports ICCCM (i.e. the root window has
263 // _NET_SUPPORTING_WM_CHECK property that points to a WM-owned
264 // window), we could watch for DestroyNotify event on the window
265 // and invalidate our cache when the windows goes away (= WM
266 // is replaced by another one). This is what GTK+ 2 does.
267 // Let's do it only if it is needed, it requires changes to
268 // the event loop.
269
270 Atom type;
271 Window *wins;
272 Atom *atoms;
273 int format;
274 unsigned long after;
275 unsigned long nwins, natoms;
276
277 // Is the WM ICCCM supporting?
278 XGetWindowProperty(display, rootWnd,
279 _NET_SUPPORTING_WM_CHECK, 0, LONG_MAX,
280 False, XA_WINDOW, &type, &format, &nwins,
281 &after, (unsigned char **)&wins);
282 if ( type != XA_WINDOW || nwins <= 0 || wins[0] == None )
283 return FALSE;
284 XFree(wins);
285
286 // Query for supported features:
287 XGetWindowProperty(display, rootWnd,
288 _NET_SUPPORTED, 0, LONG_MAX,
289 False, XA_ATOM, &type, &format, &natoms,
290 &after, (unsigned char **)&atoms);
291 if ( type != XA_ATOM || atoms == NULL )
292 return FALSE;
293
294 // Lookup the feature we want:
295 for (unsigned i = 0; i < natoms; i++)
296 {
297 if ( atoms[i] == feature )
298 {
299 XFree(atoms);
300 return TRUE;
301 }
302 }
303 XFree(atoms);
304 return FALSE;
305 }
306 #endif
307
308
309 #define _NET_WM_STATE_REMOVE 0
310 #define _NET_WM_STATE_ADD 1
311
312 static void wxWMspecSetState(Display *display, Window rootWnd,
313 Window window, int operation, Atom state)
314 {
315 wxMAKE_ATOM(_NET_WM_STATE, display);
316
317 if ( IsMapped(display, window) )
318 {
319 XEvent xev;
320 xev.type = ClientMessage;
321 xev.xclient.type = ClientMessage;
322 xev.xclient.serial = 0;
323 xev.xclient.send_event = True;
324 xev.xclient.display = display;
325 xev.xclient.window = window;
326 xev.xclient.message_type = _NET_WM_STATE;
327 xev.xclient.format = 32;
328 xev.xclient.data.l[0] = operation;
329 xev.xclient.data.l[1] = state;
330 xev.xclient.data.l[2] = None;
331
332 XSendEvent(display, rootWnd,
333 False,
334 SubstructureRedirectMask | SubstructureNotifyMask,
335 &xev);
336 }
337 // FIXME - must modify _NET_WM_STATE property list if the window
338 // wasn't mapped!
339 }
340
341 static void wxWMspecSetFullscreen(Display *display, Window rootWnd,
342 Window window, bool fullscreen)
343 {
344 wxMAKE_ATOM(_NET_WM_STATE_FULLSCREEN, display);
345 wxWMspecSetState(display, rootWnd,
346 window,
347 fullscreen ? _NET_WM_STATE_ADD : _NET_WM_STATE_REMOVE,
348 _NET_WM_STATE_FULLSCREEN);
349 }
350
351
352 // Is the user running KDE's kwin window manager? At least kwin from KDE 3
353 // sets KWIN_RUNNING property on the root window.
354 static bool wxKwinRunning(Display *display, Window rootWnd)
355 {
356 wxMAKE_ATOM(KWIN_RUNNING, display);
357
358 long *data;
359 Atom type;
360 int format;
361 unsigned long nitems, after;
362 if (XGetWindowProperty(display, rootWnd,
363 KWIN_RUNNING, 0, 1, False, KWIN_RUNNING,
364 &type, &format, &nitems, &after,
365 (unsigned char**)&data) != Success)
366 {
367 return FALSE;
368 }
369
370 bool retval = (type == KWIN_RUNNING &&
371 nitems == 1 && data && data[0] == 1);
372 XFree(data);
373 return retval;
374 }
375
376 // KDE's kwin is Qt-centric so much than no normal method of fullscreen
377 // mode will work with it. We have to carefully emulate the Qt way.
378 static void wxSetKDEFullscreen(Display *display, Window rootWnd,
379 Window w, bool fullscreen, wxRect *origRect)
380 {
381 long data[2];
382 unsigned lng;
383
384 wxMAKE_ATOM(_NET_WM_WINDOW_TYPE, display);
385 wxMAKE_ATOM(_NET_WM_WINDOW_TYPE_NORMAL, display);
386 wxMAKE_ATOM(_KDE_NET_WM_WINDOW_TYPE_OVERRIDE, display);
387 wxMAKE_ATOM(_NET_WM_STATE_STAYS_ON_TOP, display);
388
389 if (fullscreen)
390 {
391 data[0] = _KDE_NET_WM_WINDOW_TYPE_OVERRIDE;
392 data[1] = _NET_WM_WINDOW_TYPE_NORMAL;
393 lng = 2;
394 }
395 else
396 {
397 data[0] = _NET_WM_WINDOW_TYPE_NORMAL;
398 data[1] = None;
399 lng = 1;
400 }
401
402 // it is neccessary to unmap the window, otherwise kwin will ignore us:
403 bool wasMapped = IsMapped(display, w);
404 if (wasMapped)
405 XUnmapWindow(display, w);
406 XChangeProperty(display, w, _NET_WM_WINDOW_TYPE, XA_ATOM, 32,
407 PropModeReplace, (unsigned char *) &data, lng);
408 if (wasMapped)
409 XMapRaised(display, w);
410
411 wxWMspecSetState(display, rootWnd, w,
412 fullscreen ? _NET_WM_STATE_ADD : _NET_WM_STATE_REMOVE,
413 _NET_WM_STATE_STAYS_ON_TOP);
414
415 if (!fullscreen)
416 {
417 // NB: like many other WMs, kwin ignores first request for window
418 // position change after the window was mapped. This additional
419 // move+resize event will ensure that the window is restored in
420 // exactly same position as before it was made fullscreen (because
421 // wxTopLevelWindow::ShowFullScreen will call SetSize, thus
422 // setting the position for second time).
423 XMoveResizeWindow(display, w,
424 origRect->x, origRect->y,
425 origRect->width, origRect->height);
426 }
427 }
428
429
430 wxX11FullScreenMethod wxGetFullScreenMethodX11(WXDisplay* display,
431 WXWindow rootWindow)
432 {
433 Window root = (Window)rootWindow;
434 Display *disp = (Display*)display;
435
436 // if WM supports _NET_WM_STATE_FULLSCREEN from wm-spec 1.2, use it:
437 wxMAKE_ATOM(_NET_WM_STATE_FULLSCREEN, disp);
438 if (wxQueryWMspecSupport(disp, root, _NET_WM_STATE_FULLSCREEN))
439 {
440 wxLogTrace(_T("fullscreen"),
441 _T("detected _NET_WM_STATE_FULLSCREEN support"));
442 return wxX11_FS_WMSPEC;
443 }
444
445 // if the user is running KDE's kwin WM, use a legacy hack because
446 // kwin doesn't understand any other method:
447 if (wxKwinRunning(disp, root))
448 {
449 wxLogTrace(_T("fullscreen"), _T("detected kwin"));
450 return wxX11_FS_KDE;
451 }
452
453 // finally, fall back to ICCCM heuristic method:
454 wxLogTrace(_T("fullscreen"), _T("unknown WM, using _WIN_LAYER"));
455 return wxX11_FS_GENERIC;
456 }
457
458
459 void wxSetFullScreenStateX11(WXDisplay* display, WXWindow rootWindow,
460 WXWindow window, bool show,
461 wxRect *origRect,
462 wxX11FullScreenMethod method)
463 {
464 // NB: please see the comment under "Fullscreen mode:" title above
465 // for implications of changing this code.
466
467 Window wnd = (Window)window;
468 Window root = (Window)rootWindow;
469 Display *disp = (Display*)display;
470
471 if (method == wxX11_FS_AUTODETECT)
472 method = wxGetFullScreenMethodX11(display, rootWindow);
473
474 switch (method)
475 {
476 case wxX11_FS_WMSPEC:
477 wxWMspecSetFullscreen(disp, root, wnd, show);
478 break;
479 case wxX11_FS_KDE:
480 wxSetKDEFullscreen(disp, root, wnd, show, origRect);
481 break;
482 default:
483 wxWinHintsSetLayer(disp, root, wnd,
484 show ? WIN_LAYER_ABOVE_DOCK : WIN_LAYER_NORMAL);
485 break;
486 }
487 }
488
489 #endif
490