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