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 */ 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 */ 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 60 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 78 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 */ 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 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 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 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 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 269 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 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 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 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 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 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 489 TerminalRoster::_CompareInfos(const Info* a, const Info* b) 490 { 491 return a->id - b->id; 492 } 493 494 495 /*static*/ int 496 TerminalRoster::_CompareIDInfo(const int32* id, const Info* info) 497 { 498 return *id - info->id; 499 } 500 501 502 bool 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 517 TerminalRoster::Listener::~Listener() 518 { 519 } 520