* Copyright 2010, Ingo Weinhold, ingo_weinhold@gmx.de.
* Distributed under the terms of the MIT License.
*/
#include "TerminalRoster.h"
#include <stdio.h>
#include <new>
#include <Looper.h>
#include <Roster.h>
#include <String.h>
#include <AutoLocker.h>
#include "TermConst.h"
static const bigtime_t kAppsRunningCheckInterval = 1000000;
\c workspaces is set to 0 and \c minimized to \c true.
*/
TerminalRoster::Info::Info(int32 id, team_id team)
:
id(id),
team(team),
workspaces(0),
minimized(true)
{
}
*/
TerminalRoster::Info::Info(const BMessage& archive)
{
if (archive.FindInt32("id", &id) != B_OK)
id = -1;
if (archive.FindInt32("team", &team) != B_OK)
team = -1;
if (archive.FindUInt32("workspaces", &workspaces) != B_OK)
workspaces = 0;
if (archive.FindBool("minimized", &minimized) != B_OK)
minimized = true;
}
The const BMessage& constructor can restore an identical Info from it.
*/
status_t
TerminalRoster::Info::Archive(BMessage& archive) const
{
status_t error;
if ((error = archive.AddInt32("id", id)) != B_OK
|| (error = archive.AddInt32("team", team)) != B_OK
|| (error = archive.AddUInt32("workspaces", workspaces)) != B_OK
|| (error = archive.AddBool("minimized", minimized)) != B_OK) {
return error;
}
return B_OK;
}
Infos are considered equal, iff all data members are.
*/
bool
TerminalRoster::Info::operator==(const Info& other) const
{
return id == other.id && team == other.team
&& workspaces == other.workspaces && minimized == other.minimized;
}
Most methods cannot be used until Register() has been invoked.
*/
TerminalRoster::TerminalRoster()
:
BHandler("terminal roster"),
fLock("terminal roster"),
fClipboard(TERM_SIGNATURE),
fInfos(10),
fOurInfo(NULL),
fLastCheckedTime(0),
fListener(NULL),
fInfosUpdated(false)
{
}
Also makes sure the roster list is reasonably up-to-date.
*/
bool
TerminalRoster::Lock()
{
bool locked = fLock.Lock();
if (!locked)
return false;
if (fOurInfo == NULL) {
fLock.Unlock();
return false;
}
bigtime_t now = system_time();
if (fLastCheckedTime + kAppsRunningCheckInterval) {
bool needsUpdate = false;
for (int32 i = 0; const Info* info = TerminalAt(i); i++) {
if (!_TeamIsRunning(info->team)) {
needsUpdate = true;
break;
}
}
if (needsUpdate) {
AutoLocker<BClipboard> clipboardLocker(fClipboard);
if (clipboardLocker.IsLocked()) {
if (_UpdateInfos(true) == B_OK)
_UpdateClipboard();
}
} else
fLastCheckedTime = now;
}
return true;
}
As a side effect the listener will be notified, if the terminal list has
changed in any way.
*/
void
TerminalRoster::Unlock()
{
if (fOurInfo != NULL && fInfosUpdated) {
_NotifyListener();
}
fLock.Unlock();
}
The object attaches itself to the supplied \a looper and will receive
updates via messaging (obviously the looper must run (not necessarily
right now) for this to work).
\param teamID The team ID of this team.
\param looper A looper the object can attach itself to.
\return \c B_OK, if successful, another error code otherwise.
*/
status_t
TerminalRoster::Register(team_id teamID, BLooper* looper)
{
AutoLocker<BLocker> locker(fLock);
if (fOurInfo != NULL) {
return B_BAD_VALUE;
}
AutoLocker<BClipboard> clipboardLocker(fClipboard);
if (!clipboardLocker.IsLocked())
return B_BAD_VALUE;
status_t error = _UpdateInfos(true);
if (error != B_OK)
return error;
int32 id = 0;
for (int32 i = 0; const Info* info = TerminalAt(i); i++) {
if (info->id > id)
break;
id++;
}
fOurInfo = new(std::nothrow) Info(id, teamID);
if (fOurInfo == NULL)
return B_NO_MEMORY;
if (!fInfos.BinaryInsert(fOurInfo, &_CompareInfos)) {
delete fOurInfo;
fOurInfo = NULL;
return B_NO_MEMORY;
}
error = _UpdateClipboard();
if (error != B_OK) {
fInfos.MakeEmpty(true);
fOurInfo = NULL;
return error;
}
looper->AddHandler(this);
be_roster->StartWatching(this, B_REQUEST_QUIT);
fClipboard.StartWatching(this);
_UpdateInfos(false);
return B_OK;
}
Basically undoes all effects of Register().
*/
void
TerminalRoster::Unregister()
{
AutoLocker<BLocker> locker(fLock);
if (!locker.IsLocked())
return;
be_roster->StartWatching(this);
fClipboard.StartWatching(this);
Looper()->RemoveHandler(this);
AutoLocker<BClipboard> clipboardLocker(fClipboard);
if (!clipboardLocker.IsLocked() || _UpdateInfos(false) != B_OK)
return;
fInfos.RemoveItem(fOurInfo);
fOurInfo = NULL;
_UpdateClipboard();
}
*/
int32
TerminalRoster::ID() const
{
return fOurInfo != NULL ? fOurInfo->id : -1;
}
All other running terminals will be notified, if the status changed.
\param minimized \c true, if the window is minimized.
\param workspaces The window's workspaces mask.
*/
void
TerminalRoster::SetWindowInfo(bool minimized, uint32 workspaces)
{
AutoLocker<TerminalRoster> locker(this);
if (!locker.IsLocked())
return;
if (minimized == fOurInfo->minimized && workspaces == fOurInfo->workspaces)
return;
fOurInfo->minimized = minimized;
fOurInfo->workspaces = workspaces;
fInfosUpdated = true;
AutoLocker<BClipboard> clipboardLocker(fClipboard);
if (!clipboardLocker.IsLocked() || _UpdateInfos(false) != B_OK)
return;
_UpdateClipboard();
}
*/
void
TerminalRoster::MessageReceived(BMessage* message)
{
switch (message->what) {
case B_SOME_APP_QUIT:
{
BString signature;
if (message->FindString("be:signature", &signature) != B_OK
|| signature != TERM_SIGNATURE) {
break;
}
}
case B_CLIPBOARD_CHANGED:
{
AutoLocker<TerminalRoster> locker(this);
AutoLocker<BClipboard> clipboardLocker(fClipboard);
if (clipboardLocker.IsLocked()) {
_UpdateInfos(false);
if (fInfosUpdated)
_NotifyListener();
}
break;
}
default:
BHandler::MessageReceived(message);
break;
}
}
\param checkApps If \c true, it is checked for each found info whether the
respective team is still running. If not, the info is removed from the
list (though not from the clipboard).
\return \c B_OK, if the update went fine, another error code otherwise. When
an error occurs the object state will still be consistent, but might no
longer be up-to-date.
*/
status_t
TerminalRoster::_UpdateInfos(bool checkApps)
{
BMessage* data = fClipboard.Data();
type_code type;
int32 count;
status_t error = data->GetInfo("teams", &type, &count);
if (error != B_OK)
count = 0;
InfoList infos(10);
for (int32 i = 0; i < count; i++) {
BMessage teamData;
error = data->FindMessage("teams", i, &teamData);
if (error != B_OK)
return error;
Info* info = new(std::nothrow) Info(teamData);
if (info == NULL)
return B_NO_MEMORY;
if (info->id < 0 || info->team < 0
|| infos.BinarySearchByKey(info->id, &_CompareIDInfo) != NULL
|| (checkApps && !_TeamIsRunning(info->team))) {
delete info;
fInfosUpdated = true;
continue;
}
if (!infos.BinaryInsert(info, &_CompareInfos)) {
delete info;
return B_NO_MEMORY;
}
}
int32 oldIndex = 0;
int32 newIndex = 0;
while (oldIndex < fInfos.CountItems() || newIndex < infos.CountItems()) {
Info* oldInfo = fInfos.ItemAt(oldIndex);
Info* newInfo = infos.ItemAt(newIndex);
if (oldInfo == NULL || (newInfo != NULL && oldInfo->id > newInfo->id)) {
if (!fInfos.AddItem(newInfo, oldIndex++))
return B_NO_MEMORY;
infos.RemoveItemAt(newIndex);
fInfosUpdated = true;
} else if (newInfo == NULL || oldInfo->id < newInfo->id) {
if (oldInfo == fOurInfo) {
oldIndex++;
} else {
delete fInfos.RemoveItemAt(oldIndex);
fInfosUpdated = true;
}
} else {
if (oldInfo != fOurInfo) {
if (*oldInfo != *newInfo) {
*oldInfo = *newInfo;
fInfosUpdated = true;
}
}
oldIndex++;
newIndex++;
}
}
if (checkApps)
fLastCheckedTime = system_time();
return B_OK;
}
\return \c B_OK, if the update went fine, another error code otherwise. When
an error occurs the object state will still be consistent, but might no
longer be in sync with the clipboard.
*/
status_t
TerminalRoster::_UpdateClipboard()
{
BMessage* data = fClipboard.Data();
if (data == NULL)
return B_BAD_VALUE;
data->MakeEmpty();
for (int32 i = 0; const Info* info = TerminalAt(i); i++) {
BMessage teamData;
status_t error = info->Archive(teamData);
if (error != B_OK
|| (error = data->AddMessage("teams", &teamData)) != B_OK) {
fClipboard.Revert();
return error;
}
}
status_t error = fClipboard.Commit();
if (error != B_OK) {
fClipboard.Revert();
return error;
}
return B_OK;
}
*/
void
TerminalRoster::_NotifyListener()
{
if (!fInfosUpdated)
return;
if (fListener != NULL)
fListener->TerminalInfosUpdated(this);
fInfosUpdated = false;
}
int
TerminalRoster::_CompareInfos(const Info* a, const Info* b)
{
return a->id - b->id;
}
int
TerminalRoster::_CompareIDInfo(const int32* id, const Info* info)
{
return *id - info->id;
}
bool
TerminalRoster::_TeamIsRunning(team_id teamID)
{
if (fOurInfo != NULL && fOurInfo->team == teamID)
return true;
team_info info;
return get_team_info(teamID, &info) == B_OK;
}
TerminalRoster::Listener::~Listener()
{
}