xref: /haiku/src/apps/terminal/TerminalRoster.cpp (revision 1c9368988e35e0d0945c64632632a14868dd9f1a)
1 /*
2  * Copyright 2010, Ingo Weinhold, ingo_weinhold@gmx.de.
3  * Distributed under the terms of the MIT License.
4  */
5 
6 
7 #include "TerminalRoster.h"
8 
9 #include <stdio.h>
10 
11 #include <new>
12 
13 #include <Looper.h>
14 #include <Roster.h>
15 #include <String.h>
16 
17 #include <AutoLocker.h>
18 
19 #include "TermConst.h"
20 
21 
22 static const bigtime_t kAppsRunningCheckInterval = 1000000;
23 
24 
25 // #pragma mark - Info
26 
27 
28 /*!	Creates an Info with the given \a id and \a team ID.
29 	\c workspaces is set to 0 and \c minimized to \c true.
30 */
Info(int32 id,team_id team)31 TerminalRoster::Info::Info(int32 id, team_id team)
32 	:
33 	id(id),
34 	team(team),
35 	workspaces(0),
36 	minimized(true)
37 {
38 }
39 
40 
41 /*!	Create an Info and initializes its data from \a archive.
42 */
Info(const BMessage & archive)43 TerminalRoster::Info::Info(const BMessage& archive)
44 {
45 	if (archive.FindInt32("id", &id) != B_OK)
46 		id = -1;
47 	if (archive.FindInt32("team", &team) != B_OK)
48 		team = -1;
49 	if (archive.FindUInt32("workspaces", &workspaces) != B_OK)
50 		workspaces = 0;
51 	if (archive.FindBool("minimized", &minimized) != B_OK)
52 		minimized = true;
53 }
54 
55 
56 /*!	Writes the Info's data into fields of \a archive.
57 	The const BMessage& constructor can restore an identical Info from it.
58 */
59 status_t
Archive(BMessage & archive) const60 TerminalRoster::Info::Archive(BMessage& archive) const
61 {
62 	status_t error;
63 	if ((error = archive.AddInt32("id", id)) != B_OK
64 		|| (error = archive.AddInt32("team", team)) != B_OK
65 		|| (error = archive.AddUInt32("workspaces", workspaces)) != B_OK
66 		|| (error = archive.AddBool("minimized", minimized)) != B_OK) {
67 		return error;
68 	}
69 
70 	return B_OK;
71 }
72 
73 
74 /*!	Compares two Infos.
75 	Infos are considered equal, iff all data members are.
76 */
77 bool
operator ==(const Info & other) const78 TerminalRoster::Info::operator==(const Info& other) const
79 {
80 	return id == other.id && team == other.team
81 		&& workspaces == other.workspaces && minimized == other.minimized;
82 }
83 
84 
85 // #pragma mark - TerminalRoster
86 
87 
88 /*!	Creates a TerminalRoster.
89 	Most methods cannot be used until Register() has been invoked.
90 */
TerminalRoster()91 TerminalRoster::TerminalRoster()
92 	:
93 	BHandler("terminal roster"),
94 	fLock("terminal roster"),
95 	fClipboard(TERM_SIGNATURE),
96 	fInfos(10, true),
97 	fOurInfo(NULL),
98 	fLastCheckedTime(0),
99 	fListener(NULL),
100 	fInfosUpdated(false)
101 {
102 }
103 
104 
105 /*!	Locks the object.
106 	Also makes sure the roster list is reasonably up-to-date.
107 */
108 bool
Lock()109 TerminalRoster::Lock()
110 {
111 	// lock
112 	bool locked = fLock.Lock();
113 	if (!locked)
114 		return false;
115 
116 	// make sure we're registered
117 	if (fOurInfo == NULL) {
118 		fLock.Unlock();
119 		return false;
120 	}
121 
122 	// If the check interval has passed, make sure all infos still have running
123 	// teams.
124 	bigtime_t now = system_time();
125 	if (fLastCheckedTime + kAppsRunningCheckInterval) {
126 		bool needsUpdate = false;
127 		for (int32 i = 0; const Info* info = TerminalAt(i); i++) {
128 			if (!_TeamIsRunning(info->team)) {
129 				needsUpdate = true;
130 				break;
131 			}
132 		}
133 
134 		if (needsUpdate) {
135 			AutoLocker<BClipboard> clipboardLocker(fClipboard);
136 			if (clipboardLocker.IsLocked()) {
137 				if (_UpdateInfos(true) == B_OK)
138 					_UpdateClipboard();
139 			}
140 		} else
141 			fLastCheckedTime = now;
142 	}
143 
144 	return true;
145 }
146 
147 
148 /*!	Unlocks the object.
149 	As a side effect the listener will be notified, if the terminal list has
150 	changed in any way.
151 */
152 void
Unlock()153 TerminalRoster::Unlock()
154 {
155 	if (fOurInfo != NULL && fInfosUpdated) {
156 		// the infos have changed -- notify our listener
157 		_NotifyListener();
158 	}
159 
160 	fLock.Unlock();
161 }
162 
163 
164 /*!	Registers a terminal with the roster and establishes a link.
165 
166 	The object attaches itself to the supplied \a looper and will receive
167 	updates via messaging (obviously the looper must run (not necessarily
168 	right now) for this to work).
169 
170 	\param teamID The team ID of this team.
171 	\param looper A looper the object can attach itself to.
172 	\return \c B_OK, if successful, another error code otherwise.
173 */
174 status_t
Register(team_id teamID,BLooper * looper)175 TerminalRoster::Register(team_id teamID, BLooper* looper)
176 {
177 	AutoLocker<BLocker> locker(fLock);
178 
179 	if (fOurInfo != NULL) {
180 		// already registered
181 		return B_BAD_VALUE;
182 	}
183 
184 	// lock the clipboard
185 	AutoLocker<BClipboard> clipboardLocker(fClipboard);
186 	if (!clipboardLocker.IsLocked())
187 		return B_BAD_VALUE;
188 
189 	// get the current infos from the clipboard
190 	status_t error = _UpdateInfos(true);
191 	if (error != B_OK)
192 		return error;
193 
194 	// find an unused ID
195 	int32 id = 0;
196 	for (int32 i = 0; const Info* info = TerminalAt(i); i++) {
197 		if (info->id > id)
198 			break;
199 		id++;
200 	}
201 
202 	// create our own info
203 	fOurInfo = new(std::nothrow) Info(id, teamID);
204 	if (fOurInfo == NULL)
205 		return B_NO_MEMORY;
206 
207 	// insert it
208 	if (!fInfos.BinaryInsert(fOurInfo, &_CompareInfos)) {
209 		delete fOurInfo;
210 		fOurInfo = NULL;
211 		return B_NO_MEMORY;
212 	}
213 
214 	// update the clipboard
215 	error = _UpdateClipboard();
216 	if (error != B_OK) {
217 		fInfos.MakeEmpty(true);
218 		fOurInfo = NULL;
219 		return error;
220 	}
221 
222 	// add ourselves to the looper and start watching
223 	looper->AddHandler(this);
224 
225 	be_roster->StartWatching(this, B_REQUEST_QUIT);
226 	fClipboard.StartWatching(this);
227 
228 	// Update again in case we've missed a update message sent before we were
229 	// listening.
230 	_UpdateInfos(false);
231 
232 	return B_OK;
233 }
234 
235 
236 /*!	Unregisters the terminal from the roster and closes the link.
237 
238 	Basically undoes all effects of Register().
239 */
240 void
Unregister()241 TerminalRoster::Unregister()
242 {
243 	AutoLocker<BLocker> locker(fLock);
244 	if (!locker.IsLocked())
245 		return;
246 
247 	// stop watching and remove ourselves from the looper
248 	be_roster->StartWatching(this);
249 	fClipboard.StartWatching(this);
250 
251 	Looper()->RemoveHandler(this);
252 
253 	// lock the clipboard and get the current infos
254 	AutoLocker<BClipboard> clipboardLocker(fClipboard);
255 	if (!clipboardLocker.IsLocked() || _UpdateInfos(false) != B_OK)
256 		return;
257 
258 	// remove our info and update the clipboard
259 	fInfos.RemoveItem(fOurInfo);
260 	fOurInfo = NULL;
261 
262 	_UpdateClipboard();
263 }
264 
265 
266 /*!	Returns the ID assigned to this terminal when it was registered.
267 */
268 int32
ID() const269 TerminalRoster::ID() const
270 {
271 	return fOurInfo != NULL ? fOurInfo->id : -1;
272 }
273 
274 
275 /*!	Updates this terminal's window status.
276 	All other running terminals will be notified, if the status changed.
277 
278 	\param minimized \c true, if the window is minimized.
279 	\param workspaces The window's workspaces mask.
280 */
281 void
SetWindowInfo(bool minimized,uint32 workspaces)282 TerminalRoster::SetWindowInfo(bool minimized, uint32 workspaces)
283 {
284 	AutoLocker<TerminalRoster> locker(this);
285 	if (!locker.IsLocked())
286 		return;
287 
288 	if (minimized == fOurInfo->minimized && workspaces == fOurInfo->workspaces)
289 		return;
290 
291 	fOurInfo->minimized = minimized;
292 	fOurInfo->workspaces = workspaces;
293 	fInfosUpdated = true;
294 
295 	// lock the clipboard and get the current infos
296 	AutoLocker<BClipboard> clipboardLocker(fClipboard);
297 	if (!clipboardLocker.IsLocked() || _UpdateInfos(false) != B_OK)
298 		return;
299 
300 	// update the clipboard to make our change known to the others
301 	_UpdateClipboard();
302 }
303 
304 
305 /*!	Overriden to handle update messages.
306 */
307 void
MessageReceived(BMessage * message)308 TerminalRoster::MessageReceived(BMessage* message)
309 {
310 	switch (message->what) {
311 		case B_SOME_APP_QUIT:
312 		{
313 			BString signature;
314 			if (message->FindString("be:signature", &signature) != B_OK
315 				|| signature != TERM_SIGNATURE) {
316 				break;
317 			}
318 			// fall through
319 		}
320 
321 		case B_CLIPBOARD_CHANGED:
322 		{
323 			// lock ourselves and the clipboard and update the infos
324 			AutoLocker<TerminalRoster> locker(this);
325 			AutoLocker<BClipboard> clipboardLocker(fClipboard);
326 			if (clipboardLocker.IsLocked()) {
327 				_UpdateInfos(false);
328 
329 				if (fInfosUpdated)
330 					_NotifyListener();
331 			}
332 
333 			break;
334 		}
335 
336 		default:
337 			BHandler::MessageReceived(message);
338 			break;
339 	}
340 }
341 
342 
343 /*!	Updates the terminal info list from the clipboard.
344 
345 	\param checkApps If \c true, it is checked for each found info whether the
346 		respective team is still running. If not, the info is removed from the
347 		list (though not from the clipboard).
348 	\return \c B_OK, if the update went fine, another error code otherwise. When
349 		an error occurs the object state will still be consistent, but might no
350 		longer be up-to-date.
351 */
352 status_t
_UpdateInfos(bool checkApps)353 TerminalRoster::_UpdateInfos(bool checkApps)
354 {
355 	BMessage* data = fClipboard.Data();
356 
357 	// find out how many infos we can expect
358 	type_code type;
359 	int32 count;
360 	status_t error = data->GetInfo("teams", &type, &count);
361 	if (error != B_OK)
362 		count = 0;
363 
364 	// create an info list from the message
365 	InfoList infos(10, true);
366 	for (int32 i = 0; i < count; i++) {
367 		// get the team's message
368 		BMessage teamData;
369 		error = data->FindMessage("teams", i, &teamData);
370 		if (error != B_OK)
371 			return error;
372 
373 		// create the info
374 		Info* info = new(std::nothrow) Info(teamData);
375 		if (info == NULL)
376 			return B_NO_MEMORY;
377 		if (info->id < 0 || info->team < 0
378 			|| infos.BinarySearchByKey(info->id, &_CompareIDInfo) != NULL
379 			|| (checkApps && !_TeamIsRunning(info->team))) {
380 			// invalid/duplicate info -- skip
381 			delete info;
382 			fInfosUpdated = true;
383 			continue;
384 		}
385 
386 		// add it to the list
387 		if (!infos.BinaryInsert(info, &_CompareInfos)) {
388 			delete info;
389 			return B_NO_MEMORY;
390 		}
391 	}
392 
393 	// update the current info list from the infos we just read
394 	int32 oldIndex = 0;
395 	int32 newIndex = 0;
396 	while (oldIndex < fInfos.CountItems() || newIndex < infos.CountItems()) {
397 		Info* oldInfo = fInfos.ItemAt(oldIndex);
398 		Info* newInfo = infos.ItemAt(newIndex);
399 
400 		if (oldInfo == NULL || (newInfo != NULL && oldInfo->id > newInfo->id)) {
401 			// new info is not in old list -- transfer it
402 			if (!fInfos.AddItem(newInfo, oldIndex++))
403 				return B_NO_MEMORY;
404 			infos.RemoveItemAt(newIndex);
405 			fInfosUpdated = true;
406 		} else if (newInfo == NULL || oldInfo->id < newInfo->id) {
407 			// old info is not in new list -- delete it, unless it's our own
408 			if (oldInfo == fOurInfo) {
409 				oldIndex++;
410 			} else {
411 				delete fInfos.RemoveItemAt(oldIndex);
412 				fInfosUpdated = true;
413 			}
414 		} else {
415 			// info is in both lists -- update the old info, unless it's our own
416 			if (oldInfo != fOurInfo) {
417 				if (*oldInfo != *newInfo) {
418 					*oldInfo = *newInfo;
419 					fInfosUpdated = true;
420 				}
421 			}
422 
423 			oldIndex++;
424 			newIndex++;
425 		}
426 	}
427 
428 	if (checkApps)
429 		fLastCheckedTime = system_time();
430 
431 	return B_OK;
432 }
433 
434 
435 /*!	Updates the clipboard with the object's terminal info list.
436 
437 	\return \c B_OK, if the update went fine, another error code otherwise. When
438 		an error occurs the object state will still be consistent, but might no
439 		longer be in sync with the clipboard.
440 */
441 status_t
_UpdateClipboard()442 TerminalRoster::_UpdateClipboard()
443 {
444 	// get the clipboard data message
445 	BMessage* data = fClipboard.Data();
446 	if (data == NULL)
447 		return B_BAD_VALUE;
448 
449 	// clear the message and add all infos
450 	data->MakeEmpty();
451 
452 	for (int32 i = 0; const Info* info = TerminalAt(i); i++) {
453 		BMessage teamData;
454 		status_t error = info->Archive(teamData);
455 		if (error != B_OK
456 			|| (error = data->AddMessage("teams", &teamData)) != B_OK) {
457 			fClipboard.Revert();
458 			return error;
459 		}
460 	}
461 
462 	// commit the changes
463 	status_t error = fClipboard.Commit();
464 	if (error != B_OK) {
465 		fClipboard.Revert();
466 		return error;
467 	}
468 
469 	return B_OK;
470 }
471 
472 
473 /*!	Notifies the listener, if something has changed.
474 */
475 void
_NotifyListener()476 TerminalRoster::_NotifyListener()
477 {
478 	if (!fInfosUpdated)
479 		return;
480 
481 	if (fListener != NULL)
482 		fListener->TerminalInfosUpdated(this);
483 
484 	fInfosUpdated = false;
485 }
486 
487 
488 /*static*/ int
_CompareInfos(const Info * a,const Info * b)489 TerminalRoster::_CompareInfos(const Info* a, const Info* b)
490 {
491 	return a->id - b->id;
492 }
493 
494 
495 /*static*/ int
_CompareIDInfo(const int32 * id,const Info * info)496 TerminalRoster::_CompareIDInfo(const int32* id, const Info* info)
497 {
498 	return *id - info->id;
499 }
500 
501 
502 bool
_TeamIsRunning(team_id teamID)503 TerminalRoster::_TeamIsRunning(team_id teamID)
504 {
505 	// we are running for sure
506 	if (fOurInfo != NULL && fOurInfo->team == teamID)
507 		return true;
508 
509 	team_info info;
510 	return get_team_info(teamID, &info) == B_OK;
511 }
512 
513 
514 // #pragma mark - Listener
515 
516 
~Listener()517 TerminalRoster::Listener::~Listener()
518 {
519 }
520