1/********************************************************************
2 KWin - the KDE window manager
3 This file is part of the KDE project.
4
5Copyright (C) 1999, 2000 Matthias Ettrich <ettrich@kde.org>
6Copyright (C) 2003 Lubos Lunak <l.lunak@kde.org>
7Copyright (C) 2012 Martin Gräßlin <mgraesslin@kde.org>
8
9This program is free software; you can redistribute it and/or modify
10it under the terms of the GNU General Public License as published by
11the Free Software Foundation; either version 2 of the License, or
12(at your option) any later version.
13
14This program is distributed in the hope that it will be useful,
15but WITHOUT ANY WARRANTY; without even the implied warranty of
16MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17GNU General Public License for more details.
18
19You should have received a copy of the GNU General Public License
20along with this program. If not, see <http://www.gnu.org/licenses/>.
21*********************************************************************/
22#include "killwindow.h"
23#include "client.h"
24#include "cursor.h"
25#include "workspace.h"
26// Qt
27#include <QCursor>
28// XLib
29#include <X11/cursorfont.h>
30#include <X11/Xcursor/Xcursor.h>
31// XCB
32#include <xcb/xcb_keysyms.h>
33
34namespace KWin
35{
36
37KillWindow::KillWindow()
38 : m_active(false)
39{
40}
41
42KillWindow::~KillWindow()
43{
44}
45
46void KillWindow::start()
47{
48 static xcb_cursor_t kill_cursor = XCB_CURSOR_NONE;
49 if (kill_cursor == XCB_CURSOR_NONE) {
50 kill_cursor = createCursor();
51 }
52 if (m_active) {
53 return;
54 }
55
56 xcb_connection_t *c = connection();
57 ScopedCPointer<xcb_grab_pointer_reply_t> grabPointer(xcb_grab_pointer_reply(c, xcb_grab_pointer_unchecked(c, false, rootWindow(),
58 XCB_EVENT_MASK_BUTTON_PRESS | XCB_EVENT_MASK_BUTTON_RELEASE |
59 XCB_EVENT_MASK_POINTER_MOTION |
60 XCB_EVENT_MASK_ENTER_WINDOW | XCB_EVENT_MASK_LEAVE_WINDOW,
61 XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC, XCB_WINDOW_NONE,
62 kill_cursor, XCB_TIME_CURRENT_TIME), NULL));
63 if (grabPointer.isNull() || grabPointer->status != XCB_GRAB_STATUS_SUCCESS) {
64 return;
65 }
66 m_active = grabXKeyboard();
67 if (!m_active) {
68 xcb_ungrab_pointer(connection(), XCB_TIME_CURRENT_TIME);
69 return;
70 }
71 grabXServer();
72}
73
74xcb_cursor_t KillWindow::createCursor()
75{
76 // XCursor is an XLib only lib
77 const char *theme = XcursorGetTheme(display());
78 const int size = XcursorGetDefaultSize(display());
79 XcursorImage *ximg = XcursorLibraryLoadImage("pirate", theme, size);
80 if (ximg) {
81 xcb_cursor_t cursor = XcursorImageLoadCursor(display(), ximg);
82 XcursorImageDestroy(ximg);
83 return cursor;
84 }
85 // fallback on font
86 xcb_connection_t *c = connection();
87 const xcb_font_t cursorFont = xcb_generate_id(c);
88 xcb_open_font(c, cursorFont, strlen ("cursor"), "cursor");
89 xcb_cursor_t cursor = xcb_generate_id(c);
90 xcb_create_glyph_cursor(c, cursor, cursorFont, cursorFont,
91 XC_pirate, /* source character glyph */
92 XC_pirate + 1, /* mask character glyph */
93 0, 0, 0, 0, 0, 0); /* r b g r b g */
94 return cursor;
95}
96
97bool KillWindow::isResponsibleForEvent(int eventType) const
98{
99 switch (eventType) {
100 case XCB_BUTTON_PRESS:
101 case XCB_BUTTON_RELEASE:
102 case XCB_MOTION_NOTIFY:
103 case XCB_ENTER_NOTIFY:
104 case XCB_LEAVE_NOTIFY:
105 case XCB_KEY_PRESS:
106 case XCB_KEY_RELEASE:
107 case XCB_FOCUS_IN:
108 case XCB_FOCUS_OUT:
109 return true;
110 default:
111 return false;
112 }
113}
114
115void KillWindow::processEvent(XEvent *event)
116{
117 if (event->type == XCB_BUTTON_RELEASE) {
118 handleButtonRelease(event->xbutton.button, event->xbutton.subwindow);
119 } else if (event->type == XCB_KEY_PRESS) {
120 handleKeyPress(event->xkey.keycode, event->xkey.state);
121 }
122}
123
124void KillWindow::processEvent(xcb_generic_event_t *event)
125{
126 if (event->response_type == XCB_BUTTON_RELEASE) {
127 xcb_button_release_event_t *buttonEvent = reinterpret_cast<xcb_button_release_event_t*>(event);
128 handleButtonRelease(buttonEvent->detail, buttonEvent->child);
129 } else if (event->response_type == XCB_KEY_PRESS) {
130 xcb_key_press_event_t *keyEvent = reinterpret_cast<xcb_key_press_event_t*>(event);
131 handleKeyPress(keyEvent->detail, keyEvent->state);
132 }
133}
134
135void KillWindow::handleButtonRelease(xcb_button_t button, xcb_window_t window)
136{
137 if (button == XCB_BUTTON_INDEX_3) {
138 release();
139 return;
140 }
141 if (button == XCB_BUTTON_INDEX_1 || button == XCB_BUTTON_INDEX_2) {
142 killWindowId(window);
143 release();
144 return;
145 }
146}
147
148void KillWindow::handleKeyPress(xcb_keycode_t keycode, uint16_t state)
149{
150 xcb_key_symbols_t *symbols = xcb_key_symbols_alloc(connection());
151 xcb_keysym_t kc = xcb_key_symbols_get_keysym(symbols, keycode, 0);
152 int mx = 0;
153 int my = 0;
154 const bool returnPressed = (kc == XK_Return) || (kc == XK_space);
155 const bool escapePressed = (kc == XK_Escape);
156 if (kc == XK_Left) {
157 mx = -10;
158 }
159 if (kc == XK_Right) {
160 mx = 10;
161 }
162 if (kc == XK_Up) {
163 my = -10;
164 }
165 if (kc == XK_Down) {
166 my = 10;
167 }
168 if (state & XCB_MOD_MASK_CONTROL) {
169 mx /= 10;
170 my /= 10;
171 }
172 Cursor::setPos(Cursor::pos() + QPoint(mx, my));
173 if (returnPressed) {
174 performKill();
175 }
176 if (returnPressed || escapePressed) {
177 release();
178 }
179 xcb_key_symbols_free(symbols);
180}
181
182void KillWindow::performKill()
183{
184 xcb_connection_t *c = connection();
185 ScopedCPointer<xcb_query_pointer_reply_t> pointer(xcb_query_pointer_reply(c, xcb_query_pointer_unchecked(c, rootWindow()), NULL));
186 if (!pointer.isNull() && pointer->child != XCB_WINDOW_NONE) {
187 killWindowId(pointer->child);
188 }
189}
190
191void KillWindow::release()
192{
193 ungrabXKeyboard();
194 xcb_ungrab_pointer(connection(), XCB_TIME_CURRENT_TIME);
195 ungrabXServer();
196 m_active = false;
197}
198
199void KillWindow::killWindowId(xcb_window_t window_to_kill)
200{
201 if (window_to_kill == XCB_WINDOW_NONE)
202 return;
203 xcb_window_t window = window_to_kill;
204 Client* client = NULL;
205 while (true) {
206 client = Workspace::self()->findClient(FrameIdMatchPredicate(window));
207 if (client) {
208 break; // Found the client
209 }
210 Xcb::Tree tree(window);
211 if (window == tree->root) {
212 // We didn't find the client, probably an override-redirect window
213 break;
214 }
215 window = tree->parent; // Go up
216 }
217 if (client)
218 client->killWindow();
219 else
220 xcb_kill_client(connection(), window_to_kill);
221}
222
223} // namespace
224