1/*
2 * Copyright 1999-2014 Haiku, Inc. All rights reserved.
3 * Distributed under the terms of the MIT License.
4 *
5 * Authors:
6 *		Jeremy Friesner
7 *		Jessica Hamilton, jessica.l.hamilton@gmail.com
8 *		John Scipione, jscipione@gmail.com
9 */
10
11
12#include "KeyCommandMap.h"
13
14#include <stdio.h>
15
16#include <Beep.h>
17#include <Entry.h>
18#include <File.h>
19#include <FindDirectory.h>
20#include <MessageFilter.h>
21#include <NodeMonitor.h>
22#include <OS.h>
23#include <Path.h>
24#include <PathFinder.h>
25#include <PathMonitor.h>
26#include <StringList.h>
27#include <WindowScreen.h>
28
29#include "BitFieldTesters.h"
30#include "CommandActuators.h"
31#include "ShortcutsFilterConstants.h"
32
33
34#define FILE_UPDATED 'fiUp'
35
36
37//	#pragma mark - hks
38
39
40class hks {
41public:
42	hks(int32 key, BitFieldTester* tester, CommandActuator* actuator,
43		const BMessage& actuatorMessage)
44		:
45		fKey(key),
46		fTester(tester),
47		fActuator(actuator),
48		fActuatorMessage(actuatorMessage)
49	{
50	}
51
52	~hks()
53	{
54		delete fActuator;
55		delete fTester;
56	}
57
58	int32 GetKey() const
59		{ return fKey; }
60	bool DoModifiersMatch(uint32 bits) const
61		{ return fTester->IsMatching(bits); }
62	const BMessage& GetActuatorMessage() const
63		{ return fActuatorMessage; }
64	CommandActuator* GetActuator()
65		{ return fActuator; }
66
67private:
68	int32 				fKey;
69	BitFieldTester* 	fTester;
70	CommandActuator* 	fActuator;
71	const BMessage		fActuatorMessage;
72};
73
74
75//	#pragma mark - KeyCommandMap
76
77
78KeyCommandMap::KeyCommandMap(const char* file)
79	:
80	BLooper("Shortcuts map watcher"),
81	fSpecs(NULL)
82{
83	fFileName = new char[strlen(file) + 1];
84	strcpy(fFileName, file);
85
86	BEntry fileEntry(fFileName);
87
88	BPrivate::BPathMonitor::StartWatching(fFileName,
89		B_WATCH_STAT | B_WATCH_FILES_ONLY, this);
90
91	if (fileEntry.InitCheck() == B_OK) {
92		BMessage message(FILE_UPDATED);
93		PostMessage(&message);
94	}
95
96	fPort = create_port(1, SHORTCUTS_CATCHER_PORT_NAME);
97	_PutMessageToPort();
98		// advertise our BMessenger to the world
99}
100
101
102KeyCommandMap::~KeyCommandMap()
103{
104	if (fPort >= 0)
105		close_port(fPort);
106
107	for (int32 i = fInjects.CountItems() - 1; i >= 0; i--)
108		delete (BMessage*)fInjects.ItemAt(i);
109
110	BPrivate::BPathMonitor::StopWatching(BMessenger(this, this));
111		// don't know if this is necessary, but it can't hurt
112
113	_DeleteHKSList(fSpecs);
114
115	delete[] fFileName;
116}
117
118
119void
120KeyCommandMap::MouseMessageReceived(const BMessage* message)
121{
122	// Save the mouse state for later...
123	fLastMouseMessage = *message;
124}
125
126
127filter_result
128KeyCommandMap::KeyEvent(const BMessage* keyMessage, BList* outList,
129	const BMessenger& sendTo)
130{
131	filter_result result = B_DISPATCH_MESSAGE;
132	uint32 modifiers;
133	int32 key;
134
135	if (keyMessage->FindInt32("modifiers", (int32*)&modifiers) == B_OK
136		&& keyMessage->FindInt32("key", &key) == B_OK
137		&& fSpecs != NULL && fSyncSpecs.Lock()) {
138		int32 count = fSpecs->CountItems();
139		for (int32 i = 0; i < count; i++) {
140			hks* next = (hks*)fSpecs->ItemAt(i);
141
142			if (key == next->GetKey() && next->DoModifiersMatch(modifiers)) {
143				void* asyncData = NULL;
144				result = next->GetActuator()->KeyEvent(keyMessage, outList,
145					&asyncData, &fLastMouseMessage);
146
147				if (asyncData != NULL) {
148					BMessage newMessage(*keyMessage);
149					newMessage.AddMessage("act", &next->GetActuatorMessage());
150					newMessage.AddPointer("adata", asyncData);
151					sendTo.SendMessage(&newMessage);
152				}
153			}
154		}
155		fSyncSpecs.Unlock();
156	}
157
158	return result;
159}
160
161
162void
163KeyCommandMap::DrainInjectedEvents(const BMessage* keyMessage, BList* outList,
164	const BMessenger& sendTo)
165{
166	BList temp;
167	if (fSyncSpecs.Lock()) {
168		temp = fInjects;
169		fInjects.MakeEmpty();
170		fSyncSpecs.Unlock();
171	}
172
173	int32 count = temp.CountItems();
174	for (int32 i = 0; i < count; i++) {
175		BMessage* message = (BMessage*)temp.ItemAt(i);
176
177		BArchivable* archive = instantiate_object(message);
178		if (archive != NULL) {
179			CommandActuator* actuator
180				= dynamic_cast<CommandActuator*>(archive);
181			if (actuator != NULL) {
182				BMessage newMessage(*keyMessage);
183				newMessage.what = B_KEY_DOWN;
184
185				void* asyncData = NULL;
186				actuator->KeyEvent(&newMessage, outList, &asyncData,
187					&fLastMouseMessage);
188
189				if (asyncData != NULL) {
190					newMessage.AddMessage("act", message);
191					newMessage.AddPointer("adata", asyncData);
192					sendTo.SendMessage(&newMessage);
193				}
194			}
195			delete archive;
196		}
197		delete message;
198	}
199}
200
201
202void
203KeyCommandMap::MessageReceived(BMessage* message)
204{
205	switch (message->what) {
206		case EXECUTE_COMMAND:
207		{
208			BMessage actuatorMessage;
209			if (message->FindMessage("act", &actuatorMessage) == B_OK) {
210				if (fSyncSpecs.Lock()) {
211					fInjects.AddItem(new BMessage(actuatorMessage));
212					fSyncSpecs.Unlock();
213
214					// This evil hack forces input_server to call Filter() on
215					// us so we can process the injected event.
216					BPoint where;
217					status_t err = fLastMouseMessage.FindPoint("where", &where);
218					if (err == B_OK)
219						set_mouse_position((int32)where.x, (int32)where.y);
220				}
221			}
222			break;
223		}
224
225		case REPLENISH_MESSENGER:
226			_PutMessageToPort();
227			break;
228
229		case B_PATH_MONITOR:
230		{
231			const char* path = "";
232			// only fall through for appropriate file
233			if (!(message->FindString("path", &path) == B_OK
234					&& strcmp(path, fFileName) == 0)) {
235				dev_t device;
236				ino_t node;
237				if (message->FindInt32("device", &device) != B_OK
238					|| message->FindInt64("node", &node) != B_OK
239					|| device != fNodeRef.device
240					|| node != fNodeRef.node) {
241					break;
242				}
243			}
244		}
245		// fall-through
246		case FILE_UPDATED:
247		{
248			BMessage fileMessage;
249			BFile file(fFileName, B_READ_ONLY);
250			BList* newList = new BList;
251			BList* oldList = NULL;
252			if (file.InitCheck() == B_OK && fileMessage.Unflatten(&file)
253					== B_OK) {
254				file.GetNodeRef(&fNodeRef);
255				int32 i = 0;
256				BMessage message;
257				while (fileMessage.FindMessage("spec", i++, &message) == B_OK) {
258					uint32 key;
259					BMessage testerMessage;
260					BMessage actuatorMessage;
261
262					if (message.FindInt32("key", (int32*)&key) == B_OK
263						&& message.FindMessage("act", &actuatorMessage) == B_OK
264						&& message.FindMessage("modtester", &testerMessage)
265							== B_OK) {
266						// Leave handling of add-ons shortcuts to Tracker
267						BString command;
268						if (message.FindString("command", &command) == B_OK) {
269							BStringList paths;
270							BPathFinder::FindPaths(
271								B_FIND_PATH_ADD_ONS_DIRECTORY, "Tracker",
272								paths);
273							bool foundAddOn = false;
274							int32 count = paths.CountStrings();
275							for (int32 i = 0; i < count; i++) {
276								if (command.StartsWith(paths.StringAt(i))) {
277									foundAddOn = true;
278									break;
279								}
280							}
281							if (foundAddOn)
282								continue;
283						}
284
285						BArchivable* archive
286							= instantiate_object(&testerMessage);
287						if (BitFieldTester* tester
288								= dynamic_cast<BitFieldTester*>(archive)) {
289							archive = instantiate_object(&actuatorMessage);
290							CommandActuator* actuator
291								= dynamic_cast<CommandActuator*>(archive);
292							if (actuator != NULL) {
293								newList->AddItem(new hks(key, tester, actuator,
294									actuatorMessage));
295							} else {
296								delete archive;
297								delete tester;
298							}
299						} else
300							delete archive;
301					}
302				}
303			} else {
304				fNodeRef.device = -1;
305				fNodeRef.node = -1;
306			}
307
308			if (fSyncSpecs.Lock()) {
309				// swap in the new list
310				oldList = fSpecs;
311				fSpecs = newList;
312				fSyncSpecs.Unlock();
313			} else {
314				// This should never happen... but clean up if it does.
315				oldList = newList;
316			}
317
318			_DeleteHKSList(oldList);
319			break;
320		}
321	}
322}
323
324
325//	#pragma mark - KeyCommandMap private methods
326
327
328//! Deletes an HKS-filled BList and its contents.
329void
330KeyCommandMap::_DeleteHKSList(BList* list)
331{
332	if (list == NULL)
333		return;
334
335	int32 count = list->CountItems();
336	for (int32 i = 0; i < count; i++)
337		delete (hks*)list->ItemAt(i);
338
339	delete list;
340}
341
342
343void
344KeyCommandMap::_PutMessageToPort()
345{
346	if (fPort >= 0) {
347		BMessage message;
348		message.AddMessenger("target", this);
349
350		char buffer[2048];
351		ssize_t size = message.FlattenedSize();
352		if (size <= (ssize_t)sizeof(buffer)
353			&& message.Flatten(buffer, size) == B_OK) {
354			write_port_etc(fPort, 0, buffer, size, B_TIMEOUT, 250000);
355		}
356	}
357}
358