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