1 //
2 // FocusToggleEventHandler.cc for pekwm
3 // Copyright (C) 2021 Claes Nästén <pekdon@gmail.com>
4 //
5 // This program is licensed under the GNU GPL.
6 // See the LICENSE file for more information.
7 //
8 
9 #include "Debug.hh"
10 #include "FocusToggleEventHandler.hh"
11 #include "Workspaces.hh"
12 
FocusToggleEventHandler(Config * cfg,uint button,uint raise,int off,bool show_iconified,bool mru)13 FocusToggleEventHandler::FocusToggleEventHandler(Config* cfg, uint button,
14                                                  uint raise, int off,
15                                                  bool show_iconified, bool mru)
16 	: _cfg(cfg),
17 	  _button(button),
18 	  _raise(raise),
19 	  _off(off),
20 	  _show_iconified(show_iconified),
21 	  _mru(mru),
22 	  _menu(nullptr),
23 	  _fo_wo(nullptr),
24 	  _was_iconified(false)
25 {
26 }
27 
~FocusToggleEventHandler(void)28 FocusToggleEventHandler::~FocusToggleEventHandler(void)
29 {
30 	setFocusedWo(nullptr);
31 	delete _menu;
32 }
33 
34 void
notify(Observable * observable,Observation * observation)35 FocusToggleEventHandler::notify(Observable *observable,
36 				Observation *observation)
37 {
38 	if (observation == &PWinObj::pwin_obj_deleted
39 	    && observable == _fo_wo) {
40 		P_TRACE("decor " << _fo_wo << " lost while moving");
41 		_fo_wo = nullptr;
42 	}
43 }
44 
45 bool
initEventHandler(void)46 FocusToggleEventHandler::initEventHandler(void)
47 {
48 	_menu = createNextPrevMenu();
49 
50 	// no clients in the list
51 	if (_menu->size() == 0) {
52 		return false;
53 	}
54 
55 	// unable to grab keyboard
56 	if (! X11::grabKeyboard(X11::getRoot())) {
57 		return false;
58 	}
59 
60 	// find the focused window object
61 	if (PWinObj::isFocusedPWinObj(PWinObj::WO_CLIENT)) {
62 		PWinObj *fo_wo = PWinObj::getFocusedPWinObj()->getParent();
63 
64 		PMenu::item_cit it(_menu->m_begin());
65 		for (; it != _menu->m_end(); ++it) {
66 			if ((*it)->getWORef() == fo_wo) {
67 				_menu->selectItem(it);
68 				break;
69 			}
70 		}
71 		fo_wo->setFocused(false);
72 	}
73 
74 	if (_cfg->getShowFrameList()) {
75 		_menu->buildMenu();
76 
77 		Geometry head;
78 		CurrHeadSelector chs = pekwm::config()->getCurrHeadSelector();
79 		X11::getHeadInfo(X11Util::getCurrHead(chs), head);
80 		_menu->move(head.x + ((head.width - _menu->getWidth()) / 2),
81 			    head.y + ((head.height - _menu->getHeight()) / 2));
82 		_menu->setFocused(true);
83 		_menu->mapWindowRaised();
84 		PWinObj::setSkipEnterAfter(_menu);
85 	}
86 
87 	_menu->selectItemRel(_off);
88 	setFocusedWo(_menu->getItemCurr()->getWORef());
89 
90 	return true;
91 }
92 
93 EventHandler::Result
handleButtonPressEvent(XButtonEvent *)94 FocusToggleEventHandler::handleButtonPressEvent(XButtonEvent*)
95 {
96 	// mark as processed disabling wm processing of these events.
97 	return EventHandler::EVENT_PROCESSED;
98 }
99 
100 EventHandler::Result
handleButtonReleaseEvent(XButtonEvent *)101 FocusToggleEventHandler::handleButtonReleaseEvent(XButtonEvent*)
102 {
103 	// mark as processed disabling wm processing of these events.
104 	return EventHandler::EVENT_PROCESSED;
105 }
106 
107 EventHandler::Result
handleExposeEvent(XExposeEvent * ev)108 FocusToggleEventHandler::handleExposeEvent(XExposeEvent *ev)
109 {
110 	if (_menu->isMapped() && *_menu == ev->window) {
111 		_menu->handleExposeEvent(ev);
112 		return EventHandler::EVENT_PROCESSED;
113 	}
114 	return EventHandler::EVENT_SKIP;
115 }
116 
117 EventHandler::Result
handleMotionNotifyEvent(XMotionEvent *)118 FocusToggleEventHandler::handleMotionNotifyEvent(XMotionEvent*)
119 {
120 	// mark as processed disabling wm processing of these events.
121 	return EventHandler::EVENT_PROCESSED;
122 }
123 
124 EventHandler::Result
handleKeyEvent(XKeyEvent * ev)125 FocusToggleEventHandler::handleKeyEvent(XKeyEvent *ev)
126 {
127 	if (ev->type == KeyRelease) {
128 		if (IsModifierKey(X11::getKeysymFromKeycode(ev->keycode))) {
129 			return stop();
130 		}
131 		return EventHandler::EVENT_PROCESSED;
132 	}
133 
134 	if (ev->keycode == _button) {
135 		if (_fo_wo) {
136 			if (_raise == TEMP_RAISE) {
137 				Workspaces::fixStacking(_fo_wo);
138 			}
139 			// Restore iconified state
140 			if (_was_iconified) {
141 				_was_iconified = false;
142 				_fo_wo->iconify();
143 			}
144 			_fo_wo->setFocused(false);
145 		}
146 
147 		_menu->selectItemRel(_off);
148 		setFocusedWo(_menu->getItemCurr()->getWORef());
149 
150 		return EventHandler::EVENT_PROCESSED;
151 	}
152 
153 	return stop();
154 }
155 
156 EventHandler::Result
stop(void)157 FocusToggleEventHandler::stop(void)
158 {
159 	X11::ungrabKeyboard();
160 
161 	// Got something to focus
162 	if (_fo_wo) {
163 		if (_raise == TEMP_RAISE) {
164 			_fo_wo->raise();
165 			_fo_wo->setFocused(true);
166 		}
167 
168 		// De-iconify if iconified, user probably wants this
169 		if (_fo_wo->isIconified()) {
170 			// If the window was iconfied, and sticky
171 			_fo_wo->setWorkspace(Workspaces::getActive());
172 			_fo_wo->mapWindow();
173 			_fo_wo->raise();
174 		} else if (_raise == END_RAISE) {
175 			_fo_wo->raise();
176 		}
177 
178 		// Give focus
179 		_fo_wo->giveInputFocus();
180 	}
181 
182 	return EventHandler::EVENT_STOP_PROCESSED;
183 }
184 
185 void
setFocusedWo(PWinObj * fo_wo)186 FocusToggleEventHandler::setFocusedWo(PWinObj *fo_wo)
187 {
188 	if (_fo_wo) {
189 		pekwm::observerMapping()->removeObserver(_fo_wo, this);
190 	}
191 	_fo_wo = fo_wo;
192 	if (_fo_wo) {
193 		pekwm::observerMapping()->addObserver(_fo_wo, this);
194 
195 		_fo_wo->setFocused(true);
196 		if (_raise == ALWAYS_RAISE) {
197 			// Make sure it's not iconified if raise is on.
198 			if (_fo_wo->isIconified()) {
199 				_was_iconified = true;
200 				_fo_wo->mapWindow();
201 			}
202 			_fo_wo->raise();
203 		} else if (_raise == TEMP_RAISE) {
204 			Window winlist[] = { _menu->getWindow(), _fo_wo->getWindow() };
205 			X11::stackWindows(winlist, 2);
206 		}
207 	}
208 }
209 
210 /**
211  * Creates a menu containing a list of Frames currently visible
212  * @param show_iconified Flag to show/hide iconified windows
213  * @param mru Whether MRU order should be used or not.
214  */
215 PMenu*
createNextPrevMenu(void)216 FocusToggleEventHandler::createNextPrevMenu(void)
217 {
218 	PMenu *menu = new PMenu(_mru ? "MRU Windows" : "Windows", "" /* name*/);
219 
220 	Frame::frame_cit it, end;
221 	if (_mru) {
222 		it = Workspaces::mru_begin();
223 		end = Workspaces::mru_end();
224 	} else {
225 		it = Frame::frame_begin();
226 		end = Frame::frame_end();
227 	}
228 
229 	for (; it != end; ++it) {
230 		Frame *frame = *it;
231 		if (createMenuInclude(frame, _show_iconified)) {
232 			Client *client = static_cast<Client*>(frame->getActiveChild());
233 			menu->insert(client->getTitle()->getVisible(), ActionEvent(),
234 				     frame, client->getIcon());
235 		}
236 	}
237 
238 	return menu;
239 }
240 
241 /**
242  * Helper to decide wheter or not to include Frame in menu
243  *
244  * @param frame Frame to check
245  * @param show_iconified Wheter or not to include iconified windows
246  * @return true if it should be included, else false
247  */
248 bool
createMenuInclude(Frame * frame,bool show_iconified)249 FocusToggleEventHandler::createMenuInclude(Frame *frame, bool show_iconified)
250 {
251 	// focw == frame on current workspace
252 	bool focw = frame->isSticky()
253 		|| frame->getWorkspace() == Workspaces::getActive();
254 	// ibs == iconified but should be shown
255 	bool ibs = (!frame->isIconified() || show_iconified) && focw;
256 	return ! frame->isSkip(SKIP_FOCUS_TOGGLE)
257 		&& frame->isFocusable()
258 		&& ibs;
259 }
260