1 /* 2 * Copyright 2001-2005, Haiku. 3 * Distributed under the terms of the MIT License. 4 * 5 * Authors: 6 * Adrian Oanca <adioanca@cotty.iren.ro> 7 * Stephan Aßmus <superstippi@gmx.de> 8 * Axel Dörfler, axeld@pinc-software.de 9 */ 10 11 /** Class used to encapsulate desktop management */ 12 13 14 #include <stdio.h> 15 16 #include <Message.h> 17 #include <Region.h> 18 19 #include <WindowInfo.h> 20 #include <ServerProtocol.h> 21 22 #include "AppServer.h" 23 #include "DrawingEngine.h" 24 #include "Layer.h" 25 #include "RootLayer.h" 26 #include "ServerConfig.h" 27 #include "ServerScreen.h" 28 #include "ServerApp.h" 29 #include "ServerWindow.h" 30 #include "WinBorder.h" 31 #include "Workspace.h" 32 #include "DesktopSettingsPrivate.h" 33 34 35 #ifdef __HAIKU__ 36 # define USE_ACCELERANT 1 37 #else 38 # define USE_ACCELERANT 0 39 #endif 40 41 #if USE_ACCELERANT 42 # include "AccelerantHWInterface.h" 43 #else 44 # include "ViewHWInterface.h" 45 #endif 46 47 #include "Desktop.h" 48 49 //#define DEBUG_DESKTOP 50 51 #ifdef DEBUG_DESKTOP 52 # define STRACE(a) printf(a) 53 #else 54 # define STRACE(a) /* nothing */ 55 #endif 56 57 58 Desktop::Desktop(uid_t userID) 59 : MessageLooper("desktop"), 60 fUserID(userID), 61 fSettings(new DesktopSettings::Private()), 62 fAppListLock("application list"), 63 fShutdownSemaphore(-1), 64 fWinBorderList(64), 65 fActiveScreen(NULL), 66 fCursorManager() 67 { 68 // TODO: use user name 69 char name[B_OS_NAME_LENGTH]; 70 snprintf(name, sizeof(name), "d:%s", "baron"); 71 72 fMessagePort = create_port(DEFAULT_MONITOR_PORT_SIZE, name); 73 if (fMessagePort < B_OK) 74 return; 75 76 fLink.SetReceiverPort(fMessagePort); 77 gFontManager->AttachUser(fUserID); 78 } 79 80 81 Desktop::~Desktop() 82 { 83 for (int32 i = 0; WinBorder *border = (WinBorder *)fWinBorderList.ItemAt(i); i++) 84 delete border; 85 86 delete fRootLayer; 87 delete fSettings; 88 89 delete_port(fMessagePort); 90 gFontManager->DetachUser(fUserID); 91 } 92 93 94 void 95 Desktop::Init() 96 { 97 fVirtualScreen.RestoreConfiguration(*this, fSettings->WorkspacesMessage(0)); 98 99 // TODO: temporary workaround, fActiveScreen will be removed 100 fActiveScreen = fVirtualScreen.ScreenAt(0); 101 102 // TODO: add user identity to the name 103 char name[32]; 104 sprintf(name, "RootLayer %d", 1); 105 fRootLayer = new RootLayer(name, 4, this, GetDrawingEngine()); 106 107 #if TEST_MODE 108 RegisterInputServer(find_port(SERVER_INPUT_PORT)); 109 // this is where the ViewHWInterface will send its input events to 110 #endif 111 112 // take care of setting the default cursor 113 ServerCursor *cursor = fCursorManager.GetCursor(B_CURSOR_DEFAULT); 114 if (cursor) 115 fVirtualScreen.HWInterface()->SetCursor(cursor); 116 } 117 118 119 void 120 Desktop::_GetLooperName(char* name, size_t length) 121 { 122 snprintf(name, length, "d:%d:%s", /*id*/0, /*name*/"baron"); 123 } 124 125 126 void 127 Desktop::_PrepareQuit() 128 { 129 // let's kill all remaining applications 130 131 fAppListLock.Lock(); 132 133 int32 count = fAppList.CountItems(); 134 for (int32 i = 0; i < count; i++) { 135 ServerApp *app = (ServerApp*)fAppList.ItemAt(i); 136 team_id clientTeam = app->ClientTeam(); 137 138 app->Quit(); 139 kill_team(clientTeam); 140 } 141 142 // wait for the last app to die 143 if (count > 0) 144 acquire_sem_etc(fShutdownSemaphore, fShutdownCount, B_RELATIVE_TIMEOUT, 250000); 145 146 fAppListLock.Unlock(); 147 } 148 149 150 void 151 Desktop::_DispatchMessage(int32 code, BPrivate::LinkReceiver &link) 152 { 153 switch (code) { 154 case AS_CREATE_APP: 155 { 156 // Create the ServerApp to node monitor a new BApplication 157 158 // Attached data: 159 // 1) port_id - receiver port of a regular app 160 // 2) port_id - client looper port - for sending messages to the client 161 // 2) team_id - app's team ID 162 // 3) int32 - handler token of the regular app 163 // 4) char * - signature of the regular app 164 165 // Find the necessary data 166 team_id clientTeamID = -1; 167 port_id clientLooperPort = -1; 168 port_id clientReplyPort = -1; 169 int32 htoken = B_NULL_TOKEN; 170 char *appSignature = NULL; 171 172 link.Read<port_id>(&clientReplyPort); 173 link.Read<port_id>(&clientLooperPort); 174 link.Read<team_id>(&clientTeamID); 175 link.Read<int32>(&htoken); 176 if (link.ReadString(&appSignature) != B_OK) 177 break; 178 179 ServerApp *app = new ServerApp(this, clientReplyPort, 180 clientLooperPort, clientTeamID, htoken, appSignature); 181 if (app->InitCheck() == B_OK 182 && app->Run()) { 183 // add the new ServerApp to the known list of ServerApps 184 fAppListLock.Lock(); 185 fAppList.AddItem(app); 186 fAppListLock.Unlock(); 187 } else { 188 delete app; 189 190 // if everything went well, ServerApp::Run() will notify 191 // the client - but since it didn't, we do it here 192 BPrivate::LinkSender reply(clientReplyPort); 193 reply.StartMessage(SERVER_FALSE); 194 reply.Flush(); 195 } 196 197 // This is necessary because BPortLink::ReadString allocates memory 198 free(appSignature); 199 break; 200 } 201 202 case AS_DELETE_APP: 203 { 204 // Delete a ServerApp. Received only from the respective ServerApp when a 205 // BApplication asks it to quit. 206 207 // Attached Data: 208 // 1) thread_id - thread ID of the ServerApp to be deleted 209 210 thread_id thread = -1; 211 if (link.Read<thread_id>(&thread) < B_OK) 212 break; 213 214 fAppListLock.Lock(); 215 216 // Run through the list of apps and nuke the proper one 217 218 int32 count = fAppList.CountItems(); 219 ServerApp *removeApp = NULL; 220 221 for (int32 i = 0; i < count; i++) { 222 ServerApp *app = (ServerApp *)fAppList.ItemAt(i); 223 224 if (app != NULL && app->Thread() == thread) { 225 fAppList.RemoveItem(i); 226 removeApp = app; 227 break; 228 } 229 } 230 231 fAppListLock.Unlock(); 232 233 if (removeApp != NULL) 234 removeApp->Quit(fShutdownSemaphore); 235 236 if (fQuitting && count <= 1) { 237 // wait for the last app to die 238 acquire_sem_etc(fShutdownSemaphore, fShutdownCount, B_RELATIVE_TIMEOUT, 500000); 239 PostMessage(kMsgQuitLooper); 240 } 241 break; 242 } 243 244 case AS_ACTIVATE_APP: 245 { 246 // Someone is requesting to activation of a certain app. 247 248 // Attached data: 249 // 1) port_id reply port 250 // 2) team_id team 251 252 status_t status; 253 254 // get the parameters 255 port_id replyPort; 256 team_id team; 257 if (link.Read(&replyPort) == B_OK 258 && link.Read(&team) == B_OK) 259 status = _ActivateApp(team); 260 else 261 status = B_ERROR; 262 263 // send the reply 264 BPrivate::PortLink replyLink(replyPort); 265 replyLink.StartMessage(status); 266 replyLink.Flush(); 267 break; 268 } 269 270 case AS_SET_SYSCURSOR_DEFAULTS: 271 { 272 GetCursorManager().SetDefaults(); 273 break; 274 } 275 276 case B_QUIT_REQUESTED: 277 // We've been asked to quit, so (for now) broadcast to all 278 // test apps to quit. This situation will occur only when the server 279 // is compiled as a regular Be application. 280 281 fAppListLock.Lock(); 282 fShutdownSemaphore = create_sem(0, "desktop shutdown"); 283 fShutdownCount = fAppList.CountItems(); 284 fAppListLock.Unlock(); 285 286 fQuitting = true; 287 BroadcastToAllApps(AS_QUIT_APP); 288 289 // We now need to process the remaining AS_DELETE_APP messages and 290 // wait for the kMsgShutdownServer message. 291 // If an application does not quit as asked, the picasso thread 292 // will send us this message in 2-3 seconds. 293 294 // if there are no apps to quit, shutdown directly 295 if (fShutdownCount == 0) 296 PostMessage(kMsgQuitLooper); 297 break; 298 299 default: 300 printf("Desktop %d:%s received unexpected code %ld\n", 0, "baron", code); 301 302 if (link.NeedsReply()) { 303 // the client is now blocking and waiting for a reply! 304 fLink.StartMessage(B_ERROR); 305 fLink.Flush(); 306 } 307 break; 308 } 309 } 310 311 312 /*! 313 \brief activate one of the app's windows. 314 */ 315 status_t 316 Desktop::_ActivateApp(team_id team) 317 { 318 status_t status = B_BAD_TEAM_ID; 319 320 // search for an unhidden window to give focus to 321 int32 windowCount = WindowList().CountItems(); 322 for (int32 i = 0; i < windowCount; ++i) { 323 // is this layer in fact a WinBorder? 324 WinBorder *winBorder = WindowList().ItemAt(i); 325 326 // if winBorder is valid and not hidden, then we've found our target 327 if (winBorder != NULL && !winBorder->IsHidden() 328 && winBorder->App()->ClientTeam() == team) { 329 if (fRootLayer->Lock()) { 330 fRootLayer->SetActive(winBorder); 331 fRootLayer->Unlock(); 332 333 if (fRootLayer->Active() == winBorder) 334 return B_OK; 335 336 status = B_ERROR; 337 } 338 } 339 } 340 341 return status; 342 } 343 344 345 void 346 Desktop::RegisterInputServer(port_id port) 347 { 348 fInputPort = port; 349 fRootLayer->RunThread(); 350 351 fVirtualScreen.HWInterface()->SetCursorVisible(true); 352 } 353 354 355 /*! 356 \brief Send a quick (no attachments) message to all applications 357 358 Quite useful for notification for things like server shutdown, system 359 color changes, etc. 360 */ 361 void 362 Desktop::BroadcastToAllApps(int32 code) 363 { 364 BAutolock locker(fAppListLock); 365 366 for (int32 i = 0; i < fAppList.CountItems(); i++) { 367 ServerApp *app = (ServerApp *)fAppList.ItemAt(i); 368 369 if (!app) { 370 printf("PANIC in Broadcast()\n"); 371 continue; 372 } 373 app->PostMessage(code); 374 } 375 } 376 377 378 // #pragma mark - Methods for WinBorder manipulation 379 380 381 void 382 Desktop::AddWinBorder(WinBorder *winBorder) 383 { 384 if (!winBorder) 385 return; 386 387 // R2: how to determine the RootLayer to which this window should be added??? 388 // for now, use ActiveRootLayer() because we only have one instance. 389 390 int32 feel = winBorder->Feel(); 391 392 // we are ServerApp thread, we need to lock RootLayer here. 393 ActiveRootLayer()->Lock(); 394 395 // we're playing with window list. lock first. 396 Lock(); 397 398 if (fWinBorderList.HasItem(winBorder)) { 399 Unlock(); 400 ActiveRootLayer()->Unlock(); 401 debugger("AddWinBorder: WinBorder already in Desktop list\n"); 402 return; 403 } 404 405 // we have a new window. store a record of it. 406 fWinBorderList.AddItem(winBorder); 407 408 // add FLOATING_APP windows to the local list of all normal windows. 409 // This is to keep the order all floating windows (app or subset) when we go from 410 // one normal window to another. 411 if (feel == B_FLOATING_APP_WINDOW_FEEL || feel == B_NORMAL_WINDOW_FEEL) { 412 WinBorder *wb = NULL; 413 int32 count = fWinBorderList.CountItems(); 414 int32 feelToLookFor = (feel == B_NORMAL_WINDOW_FEEL ? 415 B_FLOATING_APP_WINDOW_FEEL : B_NORMAL_WINDOW_FEEL); 416 417 for (int32 i = 0; i < count; i++) { 418 wb = (WinBorder *)fWinBorderList.ItemAt(i); 419 420 if (wb->App()->ClientTeam() == winBorder->App()->ClientTeam() 421 && wb->Feel() == feelToLookFor) { 422 // R2: RootLayer comparison is needed. 423 feel == B_NORMAL_WINDOW_FEEL ? 424 winBorder->fSubWindowList.AddWinBorder(wb) : 425 wb->fSubWindowList.AddWinBorder(winBorder); 426 } 427 } 428 } 429 430 // add application's list of modal windows. 431 if (feel == B_MODAL_APP_WINDOW_FEEL) { 432 winBorder->App()->fAppSubWindowList.AddWinBorder(winBorder); 433 } 434 435 // send WinBorder to be added to workspaces 436 ActiveRootLayer()->AddWinBorder(winBorder); 437 438 // hey, unlock! 439 Unlock(); 440 441 ActiveRootLayer()->Unlock(); 442 } 443 444 445 void 446 Desktop::RemoveWinBorder(WinBorder *winBorder) 447 { 448 if (!winBorder) 449 return; 450 451 // we are ServerApp thread, we need to lock RootLayer here. 452 ActiveRootLayer()->Lock(); 453 454 // we're playing with window list. lock first. 455 Lock(); 456 457 // remove from main WinBorder list. 458 if (fWinBorderList.RemoveItem(winBorder)) { 459 int32 feel = winBorder->Feel(); 460 461 // floating app/subset and modal_subset windows require special atention because 462 // they are/may_be added to the list of a lot normal windows. 463 if (feel == B_FLOATING_SUBSET_WINDOW_FEEL 464 || feel == B_MODAL_SUBSET_WINDOW_FEEL 465 || feel == B_FLOATING_APP_WINDOW_FEEL) 466 { 467 WinBorder *wb = NULL; 468 int32 count = fWinBorderList.CountItems(); 469 470 for (int32 i = 0; i < count; i++) { 471 wb = (WinBorder*)fWinBorderList.ItemAt(i); 472 473 if (wb->Feel() == B_NORMAL_WINDOW_FEEL 474 && wb->App()->ClientTeam() == winBorder->App()->ClientTeam()) { 475 // R2: RootLayer comparison is needed. We'll see. 476 wb->fSubWindowList.RemoveItem(winBorder); 477 } 478 } 479 } 480 481 // remove from application's list 482 if (feel == B_MODAL_APP_WINDOW_FEEL) { 483 winBorder->App()->fAppSubWindowList.RemoveItem(winBorder); 484 } 485 } else { 486 Unlock(); 487 ActiveRootLayer()->Unlock(); 488 debugger("RemoveWinBorder: WinBorder not found in Desktop list\n"); 489 return; 490 } 491 492 // Tell to winBorder's RootLayer about this. 493 ActiveRootLayer()->RemoveWinBorder(winBorder); 494 495 Unlock(); 496 ActiveRootLayer()->Unlock(); 497 } 498 499 500 void 501 Desktop::AddWinBorderToSubset(WinBorder *winBorder, WinBorder *toWinBorder) 502 { 503 // NOTE: we can safely lock the entire method body, because this method is called from 504 // RootLayer's thread only. 505 506 // we're playing with window list. lock first. 507 Lock(); 508 509 if (!winBorder || !toWinBorder 510 || !fWinBorderList.HasItem(winBorder) 511 || !fWinBorderList.HasItem(toWinBorder)) { 512 Unlock(); 513 debugger("AddWinBorderToSubset: NULL WinBorder or not found in Desktop list\n"); 514 return; 515 } 516 517 if ((winBorder->Feel() == B_FLOATING_SUBSET_WINDOW_FEEL 518 || winBorder->Feel() == B_MODAL_SUBSET_WINDOW_FEEL) 519 && toWinBorder->Feel() == B_NORMAL_WINDOW_FEEL 520 && toWinBorder->App()->ClientTeam() == winBorder->App()->ClientTeam() 521 && !toWinBorder->fSubWindowList.HasItem(winBorder)) { 522 // add to normal_window's list 523 toWinBorder->fSubWindowList.AddWinBorder(winBorder); 524 } else { 525 Unlock(); 526 debugger("AddWinBorderToSubset: you must add a subset_window to a normal_window's subset with the same team_id\n"); 527 return; 528 } 529 530 // send WinBorder to be added to workspaces, if not already in there. 531 ActiveRootLayer()->AddSubsetWinBorder(winBorder, toWinBorder); 532 533 Unlock(); 534 } 535 536 537 void 538 Desktop::RemoveWinBorderFromSubset(WinBorder *winBorder, WinBorder *fromWinBorder) 539 { 540 // NOTE: we can safely lock the entire method body, because this method is called from 541 // RootLayer's thread only. 542 543 // we're playing with window list. lock first. 544 Lock(); 545 546 if (!winBorder || !fromWinBorder 547 || !fWinBorderList.HasItem(winBorder) 548 || !fWinBorderList.HasItem(fromWinBorder)) { 549 Unlock(); 550 debugger("RemoveWinBorderFromSubset: NULL WinBorder or not found in Desktop list\n"); 551 return; 552 } 553 554 // remove WinBorder from workspace, if needed - some other windows may still have it in their subset 555 ActiveRootLayer()->RemoveSubsetWinBorder(winBorder, fromWinBorder); 556 557 if (fromWinBorder->Feel() == B_NORMAL_WINDOW_FEEL) { 558 //remove from this normal_window's subset. 559 fromWinBorder->fSubWindowList.RemoveItem(winBorder); 560 } else { 561 Unlock(); 562 debugger("RemoveWinBorderFromSubset: you must remove a subset_window from a normal_window's subset\n"); 563 return; 564 } 565 566 Unlock(); 567 } 568 569 570 void 571 Desktop::SetWinBorderFeel(WinBorder *winBorder, uint32 feel) 572 { 573 // NOTE: this method is called from RootLayer thread only 574 575 // we're playing with window list. lock first. 576 Lock(); 577 578 RemoveWinBorder(winBorder); 579 winBorder->QuietlySetFeel(feel); 580 AddWinBorder(winBorder); 581 582 Unlock(); 583 } 584 585 586 WinBorder * 587 Desktop::FindWinBorderByClientToken(int32 token, team_id teamID) 588 { 589 BAutolock locker(this); 590 591 WinBorder *wb; 592 for (int32 i = 0; (wb = (WinBorder *)fWinBorderList.ItemAt(i)); i++) { 593 if (wb->Window()->ClientToken() == token 594 && wb->Window()->ClientTeam() == teamID) 595 return wb; 596 } 597 598 return NULL; 599 } 600 601 602 const BObjectList<WinBorder> & 603 Desktop::WindowList() const 604 { 605 if (!IsLocked()) 606 debugger("You must lock before getting registered windows list\n"); 607 608 return fWinBorderList; 609 } 610 611 612 void 613 Desktop::WriteWindowList(team_id team, BPrivate::LinkSender& sender) 614 { 615 BAutolock locker(this); 616 617 // compute the number of windows 618 619 int32 count = 0; 620 if (team >= B_OK) { 621 for (int32 i = 0; i < fWinBorderList.CountItems(); i++) { 622 WinBorder* border = fWinBorderList.ItemAt(i); 623 624 if (border->Window()->ClientTeam() == team) 625 count++; 626 } 627 } else 628 count = fWinBorderList.CountItems(); 629 630 // write list 631 632 sender.StartMessage(SERVER_TRUE); 633 sender.Attach<int32>(count); 634 635 for (int32 i = 0; i < fWinBorderList.CountItems(); i++) { 636 WinBorder* border = fWinBorderList.ItemAt(i); 637 638 if (team >= B_OK && border->Window()->ClientTeam() != team) 639 continue; 640 641 sender.Attach<int32>(border->Window()->ServerToken()); 642 } 643 644 sender.Flush(); 645 } 646 647 648 void 649 Desktop::WriteWindowInfo(int32 serverToken, BPrivate::LinkSender& sender) 650 { 651 BAutolock locker(this); 652 BAutolock tokenLocker(BPrivate::gDefaultTokens); 653 654 ServerWindow* window; 655 if (BPrivate::gDefaultTokens.GetToken(serverToken, 656 B_SERVER_TOKEN, (void**)&window) != B_OK) { 657 sender.StartMessage(B_ENTRY_NOT_FOUND); 658 sender.Flush(); 659 return; 660 } 661 662 window_info info; 663 window->GetInfo(info); 664 665 int32 length = window->Title() ? strlen(window->Title()) : 0; 666 667 sender.StartMessage(B_OK); 668 sender.Attach<int32>(sizeof(window_info) + length + 1); 669 sender.Attach(&info, sizeof(window_info)); 670 if (length > 0) 671 sender.Attach(window->Title(), length + 1); 672 else 673 sender.Attach<char>('\0'); 674 sender.Flush(); 675 } 676 677