2 * Copyright 2010, Ingo Weinhold, ingo_weinhold@gmx.de.
3 * Distributed under the terms of the MIT License.
7 #include "TerminalRoster.h"
17 #include <AutoLocker.h>
19 #include "TermConst.h"
22 static const bigtime_t kAppsRunningCheckInterval
= 1000000;
25 // #pragma mark - Info
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.
31 TerminalRoster::Info::Info(int32 id
, team_id team
)
41 /*! Create an Info and initializes its data from \a archive.
43 TerminalRoster::Info::Info(const BMessage
& archive
)
45 if (archive
.FindInt32("id", &id
) != B_OK
)
47 if (archive
.FindInt32("team", &team
) != B_OK
)
49 if (archive
.FindUInt32("workspaces", &workspaces
) != B_OK
)
51 if (archive
.FindBool("minimized", &minimized
) != B_OK
)
56 /*! Writes the Info's data into fields of \a archive.
57 The const BMessage& constructor can restore an identical Info from it.
60 TerminalRoster::Info::Archive(BMessage
& archive
) const
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
) {
74 /*! Compares two Infos.
75 Infos are considered equal, iff all data members are.
78 TerminalRoster::Info::operator==(const Info
& other
) const
80 return id
== other
.id
&& team
== other
.team
81 && workspaces
== other
.workspaces
&& minimized
== other
.minimized
;
85 // #pragma mark - TerminalRoster
88 /*! Creates a TerminalRoster.
89 Most methods cannot be used until Register() has been invoked.
91 TerminalRoster::TerminalRoster()
93 BHandler("terminal roster"),
94 fLock("terminal roster"),
95 fClipboard(TERM_SIGNATURE
),
105 /*! Locks the object.
106 Also makes sure the roster list is reasonably up-to-date.
109 TerminalRoster::Lock()
112 bool locked
= fLock
.Lock();
116 // make sure we're registered
117 if (fOurInfo
== NULL
) {
122 // If the check interval has passed, make sure all infos still have running
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
)) {
135 AutoLocker
<BClipboard
> clipboardLocker(fClipboard
);
136 if (clipboardLocker
.IsLocked()) {
137 if (_UpdateInfos(true) == B_OK
)
141 fLastCheckedTime
= now
;
148 /*! Unlocks the object.
149 As a side effect the listener will be notified, if the terminal list has
153 TerminalRoster::Unlock()
155 if (fOurInfo
!= NULL
&& fInfosUpdated
) {
156 // the infos have changed -- notify our listener
164 /*! Registers a terminal with the roster and establishes a link.
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).
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.
175 TerminalRoster::Register(team_id teamID
, BLooper
* looper
)
177 AutoLocker
<BLocker
> locker(fLock
);
179 if (fOurInfo
!= NULL
) {
180 // already registered
184 // lock the clipboard
185 AutoLocker
<BClipboard
> clipboardLocker(fClipboard
);
186 if (!clipboardLocker
.IsLocked())
189 // get the current infos from the clipboard
190 status_t error
= _UpdateInfos(true);
196 for (int32 i
= 0; const Info
* info
= TerminalAt(i
); i
++) {
202 // create our own info
203 fOurInfo
= new(std::nothrow
) Info(id
, teamID
);
204 if (fOurInfo
== NULL
)
208 if (!fInfos
.BinaryInsert(fOurInfo
, &_CompareInfos
)) {
214 // update the clipboard
215 error
= _UpdateClipboard();
217 fInfos
.MakeEmpty(true);
222 // add ourselves to the looper and start watching
223 looper
->AddHandler(this);
225 be_roster
->StartWatching(this, B_REQUEST_QUIT
);
226 fClipboard
.StartWatching(this);
228 // Update again in case we've missed a update message sent before we were
236 /*! Unregisters the terminal from the roster and closes the link.
238 Basically undoes all effects of Register().
241 TerminalRoster::Unregister()
243 AutoLocker
<BLocker
> locker(fLock
);
244 if (!locker
.IsLocked())
247 // stop watching and remove ourselves from the looper
248 be_roster
->StartWatching(this);
249 fClipboard
.StartWatching(this);
251 Looper()->RemoveHandler(this);
253 // lock the clipboard and get the current infos
254 AutoLocker
<BClipboard
> clipboardLocker(fClipboard
);
255 if (!clipboardLocker
.IsLocked() || _UpdateInfos(false) != B_OK
)
258 // remove our info and update the clipboard
259 fInfos
.RemoveItem(fOurInfo
);
266 /*! Returns the ID assigned to this terminal when it was registered.
269 TerminalRoster::ID() const
271 return fOurInfo
!= NULL
? fOurInfo
->id
: -1;
275 /*! Updates this terminal's window status.
276 All other running terminals will be notified, if the status changed.
278 \param minimized \c true, if the window is minimized.
279 \param workspaces The window's workspaces mask.
282 TerminalRoster::SetWindowInfo(bool minimized
, uint32 workspaces
)
284 AutoLocker
<TerminalRoster
> locker(this);
285 if (!locker
.IsLocked())
288 if (minimized
== fOurInfo
->minimized
&& workspaces
== fOurInfo
->workspaces
)
291 fOurInfo
->minimized
= minimized
;
292 fOurInfo
->workspaces
= workspaces
;
293 fInfosUpdated
= true;
295 // lock the clipboard and get the current infos
296 AutoLocker
<BClipboard
> clipboardLocker(fClipboard
);
297 if (!clipboardLocker
.IsLocked() || _UpdateInfos(false) != B_OK
)
300 // update the clipboard to make our change known to the others
305 /*! Overriden to handle update messages.
308 TerminalRoster::MessageReceived(BMessage
* message
)
310 switch (message
->what
) {
311 case B_SOME_APP_QUIT
:
314 if (message
->FindString("be:signature", &signature
) != B_OK
315 || signature
!= TERM_SIGNATURE
) {
321 case B_CLIPBOARD_CHANGED
:
323 // lock ourselves and the clipboard and update the infos
324 AutoLocker
<TerminalRoster
> locker(this);
325 AutoLocker
<BClipboard
> clipboardLocker(fClipboard
);
326 if (clipboardLocker
.IsLocked()) {
337 BHandler::MessageReceived(message
);
343 /*! Updates the terminal info list from the clipboard.
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.
353 TerminalRoster::_UpdateInfos(bool checkApps
)
355 BMessage
* data
= fClipboard
.Data();
357 // find out how many infos we can expect
360 status_t error
= data
->GetInfo("teams", &type
, &count
);
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
369 error
= data
->FindMessage("teams", i
, &teamData
);
374 Info
* info
= new(std::nothrow
) Info(teamData
);
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
382 fInfosUpdated
= true;
386 // add it to the list
387 if (!infos
.BinaryInsert(info
, &_CompareInfos
)) {
393 // update the current info list from the infos we just read
396 while (oldIndex
< fInfos
.CountItems() || newIndex
< infos
.CountItems()) {
397 Info
* oldInfo
= fInfos
.ItemAt(oldIndex
);
398 Info
* newInfo
= infos
.ItemAt(newIndex
);
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
++))
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
) {
411 delete fInfos
.RemoveItemAt(oldIndex
);
412 fInfosUpdated
= true;
415 // info is in both lists -- update the old info, unless it's our own
416 if (oldInfo
!= fOurInfo
) {
417 if (*oldInfo
!= *newInfo
) {
419 fInfosUpdated
= true;
429 fLastCheckedTime
= system_time();
435 /*! Updates the clipboard with the object's terminal info list.
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.
442 TerminalRoster::_UpdateClipboard()
444 // get the clipboard data message
445 BMessage
* data
= fClipboard
.Data();
449 // clear the message and add all infos
452 for (int32 i
= 0; const Info
* info
= TerminalAt(i
); i
++) {
454 status_t error
= info
->Archive(teamData
);
456 || (error
= data
->AddMessage("teams", &teamData
)) != B_OK
) {
462 // commit the changes
463 status_t error
= fClipboard
.Commit();
473 /*! Notifies the listener, if something has changed.
476 TerminalRoster::_NotifyListener()
481 if (fListener
!= NULL
)
482 fListener
->TerminalInfosUpdated(this);
484 fInfosUpdated
= false;
489 TerminalRoster::_CompareInfos(const Info
* a
, const Info
* b
)
491 return a
->id
- b
->id
;
496 TerminalRoster::_CompareIDInfo(const int32
* id
, const Info
* info
)
498 return *id
- info
->id
;
503 TerminalRoster::_TeamIsRunning(team_id teamID
)
505 // we are running for sure
506 if (fOurInfo
!= NULL
&& fOurInfo
->team
== teamID
)
510 return get_team_info(teamID
, &info
) == B_OK
;
514 // #pragma mark - Listener
517 TerminalRoster::Listener::~Listener()