1 | /******************************************************************** |
2 | KWin - the KDE window manager |
3 | This file is part of the KDE project. |
4 | |
5 | Copyright (C) 2006 Lubos Lunak <l.lunak@kde.org> |
6 | |
7 | This program is free software; you can redistribute it and/or modify |
8 | it under the terms of the GNU General Public License as published by |
9 | the Free Software Foundation; either version 2 of the License, or |
10 | (at your option) any later version. |
11 | |
12 | This program is distributed in the hope that it will be useful, |
13 | but WITHOUT ANY WARRANTY; without even the implied warranty of |
14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
15 | GNU General Public License for more details. |
16 | |
17 | You should have received a copy of the GNU General Public License |
18 | along with this program. If not, see <http://www.gnu.org/licenses/>. |
19 | *********************************************************************/ |
20 | |
21 | #include "toplevel.h" |
22 | |
23 | #include <kxerrorhandler.h> |
24 | |
25 | #ifdef KWIN_BUILD_ACTIVITIES |
26 | #include "activities.h" |
27 | #endif |
28 | #include "atoms.h" |
29 | #include "client.h" |
30 | #include "client_machine.h" |
31 | #include "effects.h" |
32 | #include "screens.h" |
33 | #include "shadow.h" |
34 | #include "xcbutils.h" |
35 | |
36 | namespace KWin |
37 | { |
38 | |
39 | Toplevel::Toplevel() |
40 | : vis(NULL) |
41 | , info(NULL) |
42 | , ready_for_painting(true) |
43 | , m_isDamaged(false) |
44 | , client(None) |
45 | , frame(None) |
46 | , damage_handle(None) |
47 | , is_shape(false) |
48 | , effect_window(NULL) |
49 | , m_clientMachine(new ClientMachine(this)) |
50 | , wmClientLeaderWin(0) |
51 | , unredirect(false) |
52 | , unredirectSuspend(false) |
53 | , m_damageReplyPending(false) |
54 | , m_screen(0) |
55 | , m_skipCloseAnimation(false) |
56 | { |
57 | connect(this, SIGNAL(damaged(KWin::Toplevel*,QRect)), SIGNAL(needsRepaint())); |
58 | connect(screens(), SIGNAL(changed()), SLOT(checkScreen())); |
59 | connect(screens(), SIGNAL(countChanged(int,int)), SLOT(checkScreen())); |
60 | setupCheckScreenConnection(); |
61 | } |
62 | |
63 | Toplevel::~Toplevel() |
64 | { |
65 | assert(damage_handle == None); |
66 | delete info; |
67 | } |
68 | |
69 | QDebug& operator<<(QDebug& stream, const Toplevel* cl) |
70 | { |
71 | if (cl == NULL) |
72 | return stream << "\'NULL\'" ; |
73 | cl->debug(stream); |
74 | return stream; |
75 | } |
76 | |
77 | QDebug& operator<<(QDebug& stream, const ToplevelList& list) |
78 | { |
79 | stream << "LIST:(" ; |
80 | bool first = true; |
81 | for (ToplevelList::ConstIterator it = list.begin(); |
82 | it != list.end(); |
83 | ++it) { |
84 | if (!first) |
85 | stream << ":" ; |
86 | first = false; |
87 | stream << *it; |
88 | } |
89 | stream << ")" ; |
90 | return stream; |
91 | } |
92 | |
93 | QRect Toplevel::decorationRect() const |
94 | { |
95 | return rect(); |
96 | } |
97 | |
98 | void Toplevel::detectShape(Window id) |
99 | { |
100 | const bool wasShape = is_shape; |
101 | is_shape = Xcb::Extensions::self()->hasShape(id); |
102 | if (wasShape != is_shape) { |
103 | emit shapedChanged(); |
104 | } |
105 | } |
106 | |
107 | // used only by Deleted::copy() |
108 | void Toplevel::copyToDeleted(Toplevel* c) |
109 | { |
110 | geom = c->geom; |
111 | vis = c->vis; |
112 | bit_depth = c->bit_depth; |
113 | info = c->info; |
114 | client = c->client; |
115 | frame = c->frame; |
116 | ready_for_painting = c->ready_for_painting; |
117 | damage_handle = None; |
118 | damage_region = c->damage_region; |
119 | repaints_region = c->repaints_region; |
120 | is_shape = c->is_shape; |
121 | effect_window = c->effect_window; |
122 | if (effect_window != NULL) |
123 | effect_window->setWindow(this); |
124 | resource_name = c->resourceName(); |
125 | resource_class = c->resourceClass(); |
126 | m_clientMachine = c->m_clientMachine; |
127 | m_clientMachine->setParent(this); |
128 | wmClientLeaderWin = c->wmClientLeader(); |
129 | window_role = c->windowRole(); |
130 | opaque_region = c->opaqueRegion(); |
131 | m_screen = c->m_screen; |
132 | m_skipCloseAnimation = c->m_skipCloseAnimation; |
133 | } |
134 | |
135 | // before being deleted, remove references to everything that's now |
136 | // owner by Deleted |
137 | void Toplevel::disownDataPassedToDeleted() |
138 | { |
139 | info = NULL; |
140 | } |
141 | |
142 | QRect Toplevel::visibleRect() const |
143 | { |
144 | QRect r = decorationRect(); |
145 | if (hasShadow() && !shadow()->shadowRegion().isEmpty()) { |
146 | r |= shadow()->shadowRegion().boundingRect(); |
147 | } |
148 | return r.translated(geometry().topLeft()); |
149 | } |
150 | |
151 | void Toplevel::getWindowRole() |
152 | { |
153 | window_role = getStringProperty(window(), atoms->wm_window_role).toLower(); |
154 | } |
155 | |
156 | /*! |
157 | Returns SM_CLIENT_ID property for a given window. |
158 | */ |
159 | QByteArray Toplevel::staticSessionId(WId w) |
160 | { |
161 | return getStringProperty(w, atoms->sm_client_id); |
162 | } |
163 | |
164 | /*! |
165 | Returns WM_COMMAND property for a given window. |
166 | */ |
167 | QByteArray Toplevel::staticWmCommand(WId w) |
168 | { |
169 | return getStringProperty(w, XA_WM_COMMAND, ' '); |
170 | } |
171 | |
172 | /*! |
173 | Returns WM_CLIENT_LEADER property for a given window. |
174 | */ |
175 | Window Toplevel::staticWmClientLeader(WId w) |
176 | { |
177 | Atom type; |
178 | int format, status; |
179 | unsigned long nitems = 0; |
180 | unsigned long = 0; |
181 | unsigned char *data = 0; |
182 | Window result = w; |
183 | KXErrorHandler err; |
184 | status = XGetWindowProperty(display(), w, atoms->wm_client_leader, 0, 10000, |
185 | false, XA_WINDOW, &type, &format, |
186 | &nitems, &extra, &data); |
187 | if (status == Success && !err.error(false)) { |
188 | if (data && nitems > 0) |
189 | result = *((Window*) data); |
190 | XFree(data); |
191 | } |
192 | return result; |
193 | } |
194 | |
195 | |
196 | void Toplevel::getWmClientLeader() |
197 | { |
198 | wmClientLeaderWin = staticWmClientLeader(window()); |
199 | } |
200 | |
201 | /*! |
202 | Returns sessionId for this client, |
203 | taken either from its window or from the leader window. |
204 | */ |
205 | QByteArray Toplevel::sessionId() const |
206 | { |
207 | QByteArray result = staticSessionId(window()); |
208 | if (result.isEmpty() && wmClientLeaderWin && wmClientLeaderWin != window()) |
209 | result = staticSessionId(wmClientLeaderWin); |
210 | return result; |
211 | } |
212 | |
213 | /*! |
214 | Returns command property for this client, |
215 | taken either from its window or from the leader window. |
216 | */ |
217 | QByteArray Toplevel::wmCommand() |
218 | { |
219 | QByteArray result = staticWmCommand(window()); |
220 | if (result.isEmpty() && wmClientLeaderWin && wmClientLeaderWin != window()) |
221 | result = staticWmCommand(wmClientLeaderWin); |
222 | return result; |
223 | } |
224 | |
225 | void Toplevel::getWmClientMachine() |
226 | { |
227 | m_clientMachine->resolve(window(), wmClientLeader()); |
228 | } |
229 | |
230 | /*! |
231 | Returns client machine for this client, |
232 | taken either from its window or from the leader window. |
233 | */ |
234 | QByteArray Toplevel::wmClientMachine(bool use_localhost) const |
235 | { |
236 | if (!m_clientMachine) { |
237 | // this should never happen |
238 | return QByteArray(); |
239 | } |
240 | if (use_localhost && m_clientMachine->isLocal()) { |
241 | // special name for the local machine (localhost) |
242 | return ClientMachine::localhost(); |
243 | } |
244 | return m_clientMachine->hostName(); |
245 | } |
246 | |
247 | /*! |
248 | Returns client leader window for this client. |
249 | Returns the client window itself if no leader window is defined. |
250 | */ |
251 | Window Toplevel::wmClientLeader() const |
252 | { |
253 | if (wmClientLeaderWin) |
254 | return wmClientLeaderWin; |
255 | return window(); |
256 | } |
257 | |
258 | void Toplevel::getResourceClass() |
259 | { |
260 | XClassHint classHint; |
261 | if (XGetClassHint(display(), window(), &classHint)) { |
262 | // Qt3.2 and older had this all lowercase, Qt3.3 capitalized resource class. |
263 | // Force lowercase, so that workarounds listing resource classes still work. |
264 | resource_name = QByteArray(classHint.res_name).toLower(); |
265 | resource_class = QByteArray(classHint.res_class).toLower(); |
266 | XFree(classHint.res_name); |
267 | XFree(classHint.res_class); |
268 | } else { |
269 | resource_name = resource_class = QByteArray(); |
270 | } |
271 | } |
272 | |
273 | double Toplevel::opacity() const |
274 | { |
275 | if (info->opacity() == 0xffffffff) |
276 | return 1.0; |
277 | return info->opacity() * 1.0 / 0xffffffff; |
278 | } |
279 | |
280 | void Toplevel::setOpacity(double new_opacity) |
281 | { |
282 | double old_opacity = opacity(); |
283 | new_opacity = qBound(0.0, new_opacity, 1.0); |
284 | if (old_opacity == new_opacity) |
285 | return; |
286 | info->setOpacity(static_cast< unsigned long >(new_opacity * 0xffffffff)); |
287 | if (compositing()) { |
288 | addRepaintFull(); |
289 | emit opacityChanged(this, old_opacity); |
290 | } |
291 | } |
292 | |
293 | void Toplevel::setReadyForPainting() |
294 | { |
295 | if (!ready_for_painting) { |
296 | ready_for_painting = true; |
297 | if (compositing()) { |
298 | addRepaintFull(); |
299 | emit windowShown(this); |
300 | if (Client *cl = dynamic_cast<Client*>(this)) { |
301 | if (cl->tabGroup() && cl->tabGroup()->current() == cl) |
302 | cl->tabGroup()->setCurrent(cl, true); |
303 | } |
304 | } |
305 | } |
306 | } |
307 | |
308 | void Toplevel::deleteEffectWindow() |
309 | { |
310 | delete effect_window; |
311 | effect_window = NULL; |
312 | } |
313 | |
314 | void Toplevel::checkScreen() |
315 | { |
316 | if (screens()->count() == 1) { |
317 | if (m_screen != 0) { |
318 | m_screen = 0; |
319 | emit screenChanged(); |
320 | } |
321 | return; |
322 | } |
323 | const int s = screens()->number(geometry().center()); |
324 | if (s != m_screen) { |
325 | m_screen = s; |
326 | emit screenChanged(); |
327 | } |
328 | } |
329 | |
330 | void Toplevel::setupCheckScreenConnection() |
331 | { |
332 | connect(this, SIGNAL(geometryShapeChanged(KWin::Toplevel*,QRect)), SLOT(checkScreen())); |
333 | connect(this, SIGNAL(geometryChanged()), SLOT(checkScreen())); |
334 | checkScreen(); |
335 | } |
336 | |
337 | void Toplevel::removeCheckScreenConnection() |
338 | { |
339 | disconnect(this, SIGNAL(geometryShapeChanged(KWin::Toplevel*,QRect)), this, SLOT(checkScreen())); |
340 | disconnect(this, SIGNAL(geometryChanged()), this, SLOT(checkScreen())); |
341 | } |
342 | |
343 | int Toplevel::screen() const |
344 | { |
345 | return m_screen; |
346 | } |
347 | |
348 | bool Toplevel::isOnScreen(int screen) const |
349 | { |
350 | return screens()->geometry(screen).intersects(geometry()); |
351 | } |
352 | |
353 | bool Toplevel::isOnActiveScreen() const |
354 | { |
355 | return isOnScreen(screens()->current()); |
356 | } |
357 | |
358 | void Toplevel::getShadow() |
359 | { |
360 | QRect dirtyRect; // old & new shadow region |
361 | const QRect oldVisibleRect = visibleRect(); |
362 | if (hasShadow()) { |
363 | dirtyRect = shadow()->shadowRegion().boundingRect(); |
364 | effectWindow()->sceneWindow()->shadow()->updateShadow(); |
365 | } else { |
366 | Shadow::createShadow(this); |
367 | } |
368 | if (hasShadow()) |
369 | dirtyRect |= shadow()->shadowRegion().boundingRect(); |
370 | if (oldVisibleRect != visibleRect()) |
371 | emit paddingChanged(this, oldVisibleRect); |
372 | if (dirtyRect.isValid()) { |
373 | dirtyRect.translate(pos()); |
374 | addLayerRepaint(dirtyRect); |
375 | } |
376 | } |
377 | |
378 | bool Toplevel::hasShadow() const |
379 | { |
380 | if (effectWindow() && effectWindow()->sceneWindow()) { |
381 | return effectWindow()->sceneWindow()->shadow() != NULL; |
382 | } |
383 | return false; |
384 | } |
385 | |
386 | Shadow *Toplevel::shadow() |
387 | { |
388 | if (effectWindow() && effectWindow()->sceneWindow()) { |
389 | return effectWindow()->sceneWindow()->shadow(); |
390 | } else { |
391 | return NULL; |
392 | } |
393 | } |
394 | |
395 | const Shadow *Toplevel::shadow() const |
396 | { |
397 | if (effectWindow() && effectWindow()->sceneWindow()) { |
398 | return effectWindow()->sceneWindow()->shadow(); |
399 | } else { |
400 | return NULL; |
401 | } |
402 | } |
403 | |
404 | void Toplevel::getWmOpaqueRegion() |
405 | { |
406 | const int length=32768; |
407 | unsigned long bytes_after_return=0; |
408 | QRegion new_opaque_region; |
409 | do { |
410 | unsigned long* data; |
411 | Atom type; |
412 | int rformat; |
413 | unsigned long nitems; |
414 | if (XGetWindowProperty(display(), client, |
415 | atoms->net_wm_opaque_region, 0, length, false, XA_CARDINAL, |
416 | &type, &rformat, &nitems, &bytes_after_return, |
417 | reinterpret_cast< unsigned char** >(&data)) == Success) { |
418 | if (type != XA_CARDINAL || rformat != 32 || nitems%4) { |
419 | // it can happen, that the window does not provide this property |
420 | XFree(data); |
421 | break; |
422 | } |
423 | |
424 | for (unsigned int i = 0; i < nitems;) { |
425 | const int x = data[i++]; |
426 | const int y = data[i++]; |
427 | const int w = data[i++]; |
428 | const int h = data[i++]; |
429 | |
430 | new_opaque_region += QRect(x,y,w,h); |
431 | } |
432 | XFree(data); |
433 | } else { |
434 | kWarning(1212) << "XGetWindowProperty failed" ; |
435 | break; |
436 | } |
437 | } while (bytes_after_return > 0); |
438 | |
439 | opaque_region = new_opaque_region; |
440 | } |
441 | |
442 | bool Toplevel::isClient() const |
443 | { |
444 | return false; |
445 | } |
446 | |
447 | bool Toplevel::isDeleted() const |
448 | { |
449 | return false; |
450 | } |
451 | |
452 | bool Toplevel::isOnCurrentActivity() const |
453 | { |
454 | #ifdef KWIN_BUILD_ACTIVITIES |
455 | return isOnActivity(Activities::self()->current()); |
456 | #else |
457 | return true; |
458 | #endif |
459 | } |
460 | |
461 | void Toplevel::elevate(bool elevate) |
462 | { |
463 | if (!effectWindow()) { |
464 | return; |
465 | } |
466 | effectWindow()->elevate(elevate); |
467 | addWorkspaceRepaint(visibleRect()); |
468 | } |
469 | |
470 | pid_t Toplevel::pid() const |
471 | { |
472 | return info->pid(); |
473 | } |
474 | |
475 | void Toplevel::getSkipCloseAnimation() |
476 | { |
477 | xcb_get_property_cookie_t cookie = xcb_get_property_unchecked(connection(), false, window(), atoms->kde_skip_close_animation, XCB_ATOM_CARDINAL, 0, 1); |
478 | ScopedCPointer<xcb_get_property_reply_t> reply(xcb_get_property_reply(connection(), cookie, NULL)); |
479 | bool newValue = false; |
480 | if (!reply.isNull()) { |
481 | if (reply->format == 32 && reply->type == XCB_ATOM_CARDINAL && reply->value_len == 1) { |
482 | const uint32_t value = *reinterpret_cast<uint32_t*>(xcb_get_property_value(reply.data())); |
483 | newValue = (value != 0); |
484 | } |
485 | } |
486 | setSkipCloseAnimation(newValue); |
487 | } |
488 | |
489 | bool Toplevel::skipsCloseAnimation() const |
490 | { |
491 | return m_skipCloseAnimation; |
492 | } |
493 | |
494 | void Toplevel::setSkipCloseAnimation(bool set) |
495 | { |
496 | if (set == m_skipCloseAnimation) { |
497 | return; |
498 | } |
499 | m_skipCloseAnimation = set; |
500 | emit skipCloseAnimationChanged(); |
501 | } |
502 | |
503 | } // namespace |
504 | |
505 | #include "toplevel.moc" |
506 | |