xref: /haiku/src/servers/app/Desktop.cpp (revision 93aeb8c3bc3f13cb1f282e3e749258a23790d947)
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