xref: /haiku/src/apps/terminal/TermApp.cpp (revision 4720c31bb08f5c6d1c8ddb616463c6fba9b350a1)
1 /*
2  * Copyright 2001-2008, Haiku.
3  * Copyright (c) 2003-2004 Kian Duffy <myob@users.sourceforge.net>
4  * Copyright (C) 1998,99 Kazuho Okui and Takashi Murai.
5  *
6  * Distributed unter the terms of the MIT license.
7  */
8 
9 
10 #include "TermApp.h"
11 
12 #include <errno.h>
13 #include <signal.h>
14 #include <stdio.h>
15 #include <stdlib.h>
16 #include <unistd.h>
17 
18 #include <Alert.h>
19 #include <Clipboard.h>
20 #include <NodeInfo.h>
21 #include <Path.h>
22 #include <Roster.h>
23 #include <Screen.h>
24 #include <String.h>
25 #include <TextView.h>
26 
27 #include "Arguments.h"
28 #include "CodeConv.h"
29 #include "Globals.h"
30 #include "PrefHandler.h"
31 #include "TermWindow.h"
32 #include "TermConst.h"
33 
34 
35 static bool sUsageRequested = false;
36 //static bool sGeometryRequested = false;
37 
38 
39 const ulong MSG_ACTIVATE_TERM = 'msat';
40 const ulong MSG_TERM_WINDOW_INFO = 'mtwi';
41 
42 
43 TermApp::TermApp()
44 	: BApplication(TERM_SIGNATURE),
45 	fStartFullscreen(false),
46 	fWindowNumber(-1),
47 	fTermWindow(NULL),
48 	fArgs(NULL)
49 {
50 	fArgs = new Arguments();
51 
52 	fWindowTitle = "Terminal";
53 	_RegisterTerminal();
54 
55 	if (fWindowNumber > 0)
56 		fWindowTitle << " " << fWindowNumber;
57 
58 	int i = fWindowNumber / 16;
59 	int j = fWindowNumber % 16;
60 	int k = (j * 16) + (i * 64) + 50;
61 	int l = (j * 16)  + 50;
62 
63 	fTermFrame.Set(k, l, k + 50, k + 50);
64 }
65 
66 
67 TermApp::~TermApp()
68 {
69 	delete fArgs;
70 }
71 
72 
73 void
74 TermApp::ReadyToRun()
75 {
76 	// Prevent opeing window when option -h is given.
77 	if (sUsageRequested)
78 		return;
79 
80 	// Install a SIGCHLD signal handler, so that we will be notified, when
81 	// a shell exits.
82 	struct sigaction action;
83 #ifdef __HAIKU__
84 	action.sa_handler = (sighandler_t)_SigChildHandler;
85 #else
86 	action.sa_handler = (__signal_func_ptr)_SigChildHandler;
87 #endif
88 	sigemptyset(&action.sa_mask);
89 #ifdef SA_NODEFER
90 	action.sa_flags = SA_NODEFER;
91 #endif
92 	action.sa_userdata = this;
93 	if (sigaction(SIGCHLD, &action, NULL) < 0) {
94 		fprintf(stderr, "sigaction() failed: %s\n", strerror(errno));
95 		// continue anyway
96 	}
97 
98 	// init the mouse copy'n'paste clipboard
99 	gMouseClipboard = new BClipboard(MOUSE_CLIPBOARD_NAME, true);
100 
101 	status_t status = _MakeTermWindow(fTermFrame);
102 
103 	// failed spawn, print stdout and open alert panel
104 	// TODO: This alert does never show up.
105 	if (status < B_OK) {
106 		(new BAlert("alert", "Terminal couldn't start the shell. Sorry.",
107 			"ok", NULL, NULL, B_WIDTH_FROM_LABEL,
108 			B_INFO_ALERT))->Go(NULL);
109 		PostMessage(B_QUIT_REQUESTED);
110 		return;
111 	}
112 
113 	// using BScreen::Frame isn't enough
114 	if (fStartFullscreen)
115 		BMessenger(fTermWindow).SendMessage(FULLSCREEN);
116 }
117 
118 
119 void
120 TermApp::Quit()
121 {
122 	if (!sUsageRequested)
123 		_UnregisterTerminal();
124 
125 	BApplication::Quit();
126 }
127 
128 
129 void
130 TermApp::AboutRequested()
131 {
132 	BAlert *alert = new BAlert("about", "Terminal\n"
133 		"\twritten by Kazuho Okui and Takashi Murai\n"
134 		"\tupdated by Kian Duffy and others\n\n"
135 		"\tCopyright " B_UTF8_COPYRIGHT "2003-2008, Haiku.\n", "Ok");
136 	BTextView *view = alert->TextView();
137 
138 	view->SetStylable(true);
139 
140 	BFont font;
141 	view->GetFont(&font);
142 	font.SetSize(18);
143 	font.SetFace(B_BOLD_FACE);
144 	view->SetFontAndColor(0, 8, &font);
145 
146 	alert->Go();
147 }
148 
149 
150 void
151 TermApp::MessageReceived(BMessage* msg)
152 {
153 	switch (msg->what) {
154 		case MENU_SWITCH_TERM:
155 			_SwitchTerm();
156 			break;
157 
158 		case MSG_ACTIVATE_TERM:
159 			fTermWindow->Activate();
160 			break;
161 
162 		case MSG_TERM_WINDOW_INFO:
163 		{
164 			BMessage reply(B_REPLY);
165 			reply.AddBool("minimized", fTermWindow->IsMinimized());
166 			reply.AddInt32("workspaces", fTermWindow->Workspaces());
167 			msg->SendReply(&reply);
168 			break;
169 		}
170 
171 		case MSG_CHECK_CHILDREN:
172 			_HandleChildCleanup();
173 			break;
174 
175 		default:
176 			BApplication::MessageReceived(msg);
177 			break;
178 	}
179 }
180 
181 
182 void
183 TermApp::ArgvReceived(int32 argc, char **argv)
184 {
185 	fArgs->Parse(argc, argv);
186 
187 	if (fArgs->UsageRequested()) {
188 		_Usage(argv[0]);
189 		sUsageRequested = true;
190 		PostMessage(B_QUIT_REQUESTED);
191 		return;
192 	}
193 
194 	if (fArgs->Title() != NULL)
195 		fWindowTitle = fArgs->Title();
196 
197 	fStartFullscreen = fArgs->FullScreen();
198 }
199 
200 
201 void
202 TermApp::RefsReceived(BMessage* message)
203 {
204 	// Works Only Launced by Double-Click file, or Drags file to App.
205 	if (!IsLaunching())
206 		return;
207 
208 	entry_ref ref;
209 	if (message->FindRef("refs", 0, &ref) != B_OK)
210 		return;
211 
212 	BFile file;
213 	if (file.SetTo(&ref, B_READ_WRITE) != B_OK)
214 		return;
215 
216 	BNodeInfo info(&file);
217 	char mimetype[B_MIME_TYPE_LENGTH];
218 	info.GetType(mimetype);
219 
220 	// if App opened by Pref file
221 	if (!strcmp(mimetype, PREFFILE_MIMETYPE)) {
222 
223 		BEntry ent(&ref);
224 		BPath path(&ent);
225 		PrefHandler::Default()->OpenText(path.Path());
226 		return;
227 	}
228 
229 	// if App opened by Shell Script
230 	if (!strcmp(mimetype, "text/x-haiku-shscript")){
231 		// Not implemented.
232 		//    beep();
233 		return;
234 	}
235 }
236 
237 
238 status_t
239 TermApp::_MakeTermWindow(BRect &frame)
240 {
241 	try {
242 		fTermWindow = new TermWindow(frame, fWindowTitle.String(), fArgs);
243 	} catch (int error) {
244 		return (status_t)error;
245 	} catch (...) {
246 		return B_ERROR;
247 	}
248 
249 	fTermWindow->Show();
250 
251 	return B_OK;
252 }
253 
254 
255 void
256 TermApp::_ActivateTermWindow(team_id id)
257 {
258 	BMessenger app(TERM_SIGNATURE, id);
259 	if (app.IsTargetLocal())
260 		fTermWindow->Activate();
261 	else
262 		app.SendMessage(MSG_ACTIVATE_TERM);
263 }
264 
265 
266 void
267 TermApp::_SwitchTerm()
268 {
269 	team_id myId = be_app->Team();
270 	BList teams;
271 	be_roster->GetAppList(TERM_SIGNATURE, &teams);
272 
273 	int32 numTerms = teams.CountItems();
274 	if (numTerms <= 1)
275 		return;
276 
277 	// Find our position in the app teams.
278 	int32 i;
279 
280 	for (i = 0; i < numTerms; i++) {
281 		if (myId == reinterpret_cast<team_id>(teams.ItemAt(i)))
282 			break;
283 	}
284 
285 	do {
286 		if (--i < 0)
287 			i = numTerms - 1;
288 	} while (!_IsSwitchTarget(reinterpret_cast<team_id>(teams.ItemAt(i))));
289 
290 	// Activate switched terminal.
291 	_ActivateTermWindow(reinterpret_cast<team_id>(teams.ItemAt(i)));
292 }
293 
294 
295 bool
296 TermApp::_IsSwitchTarget(team_id id)
297 {
298 	uint32 currentWorkspace = 1L << current_workspace();
299 
300 	BMessenger app(TERM_SIGNATURE, id);
301 	if (app.IsTargetLocal()) {
302 		return !fTermWindow->IsMinimized()
303 			&& (fTermWindow->Workspaces() & currentWorkspace) != 0;
304 	}
305 
306 	BMessage reply;
307 	if (app.SendMessage(MSG_TERM_WINDOW_INFO, &reply) != B_OK)
308 		return false;
309 
310 	bool minimized;
311 	int32 workspaces;
312 	if (reply.FindBool("minimized", &minimized) != B_OK
313 		|| reply.FindInt32("workspaces", &workspaces) != B_OK)
314 		return false;
315 
316 	return !minimized && (workspaces & currentWorkspace) != 0;
317 }
318 
319 
320 /*!	Checks if all teams that have an ID-to-team mapping in the message
321 	are still running.
322 	The IDs for teams that are gone will be made available again, and
323 	their mapping is removed from the message.
324 */
325 void
326 TermApp::_SanitizeIDs(BMessage* data, uint8* windows, ssize_t length)
327 {
328 	BList teams;
329 	be_roster->GetAppList(TERM_SIGNATURE, &teams);
330 
331 	for (int32 i = 0; i < length; i++) {
332 		if (!windows[i])
333 			continue;
334 
335 		BString id("id-");
336 		id << i;
337 
338 		team_id team;
339 		if (data->FindInt32(id.String(), &team) != B_OK)
340 			continue;
341 
342 		if (!teams.HasItem((void*)team)) {
343 			windows[i] = false;
344 			data->RemoveName(id.String());
345 		}
346 	}
347 }
348 
349 
350 /*!
351 	Removes the current fWindowNumber (ID) from the supplied array, or
352 	finds a free ID in it, and sets fWindowNumber accordingly.
353 */
354 bool
355 TermApp::_UpdateIDs(bool set, uint8* windows, ssize_t maxLength,
356 	ssize_t* _length)
357 {
358 	ssize_t length = *_length;
359 
360 	if (set) {
361 		int32 i;
362 		for (i = 0; i < length; i++) {
363 			if (!windows[i]) {
364 				windows[i] = true;
365 				fWindowNumber = i + 1;
366 				break;
367 			}
368 		}
369 
370 		if (i == length) {
371 			if (length >= maxLength)
372 				return false;
373 
374 			windows[length] = true;
375 			length++;
376 			fWindowNumber = length;
377 		}
378 	} else {
379 		// update information and write it back
380 		windows[fWindowNumber - 1] = false;
381 	}
382 
383 	*_length = length;
384 	return true;
385 }
386 
387 
388 void
389 TermApp::_UpdateRegistration(bool set)
390 {
391 	if (set)
392 		fWindowNumber = -1;
393 	else if (fWindowNumber < 0)
394 		return;
395 
396 #ifdef __HAIKU__
397 	// use BClipboard - it supports atomic access in Haiku
398 	BClipboard clipboard(TERM_SIGNATURE);
399 
400 	while (true) {
401 		if (!clipboard.Lock())
402 			return;
403 
404 		BMessage* data = clipboard.Data();
405 
406 		const uint8* windowsData;
407 		uint8 windows[512];
408 		ssize_t length;
409 		if (data->FindData("ids", B_RAW_TYPE,
410 				(const void**)&windowsData, &length) != B_OK)
411 			length = 0;
412 
413 		if (length > (ssize_t)sizeof(windows))
414 			length = sizeof(windows);
415 		if (length > 0)
416 			memcpy(windows, windowsData, length);
417 
418 		_SanitizeIDs(data, windows, length);
419 
420 		status_t status = B_OK;
421 		if (_UpdateIDs(set, windows, sizeof(windows), &length)) {
422 			// add/remove our ID-to-team mapping
423 			BString id("id-");
424 			id << fWindowNumber;
425 
426 			if (set)
427 				data->AddInt32(id.String(), Team());
428 			else
429 				data->RemoveName(id.String());
430 
431 			data->RemoveName("ids");
432 			//if (data->ReplaceData("ids", B_RAW_TYPE, windows, length) != B_OK)
433 			data->AddData("ids", B_RAW_TYPE, windows, length);
434 
435 			status = clipboard.Commit(true);
436 		}
437 
438 		clipboard.Unlock();
439 
440 		if (status == B_OK)
441 			break;
442 	}
443 #else	// !__HAIKU__
444 	// use a file to store the IDs - unfortunately, locking
445 	// doesn't work on BeOS either here
446 	int fd = open("/tmp/terminal_ids", O_RDWR | O_CREAT);
447 	if (fd < 0)
448 		return;
449 
450 	struct flock lock;
451 	lock.l_type = F_WRLCK;
452 	lock.l_whence = SEEK_CUR;
453 	lock.l_start = 0;
454 	lock.l_len = -1;
455 	fcntl(fd, F_SETLKW, &lock);
456 
457 	uint8 windows[512];
458 	ssize_t length = read_pos(fd, 0, windows, sizeof(windows));
459 	if (length < 0) {
460 		close(fd);
461 		return;
462 	}
463 
464 	if (length > (ssize_t)sizeof(windows))
465 		length = sizeof(windows);
466 
467 	if (_UpdateIDs(set, windows, sizeof(windows), &length))
468 		write_pos(fd, 0, windows, length);
469 
470 	close(fd);
471 #endif	// !__HAIKU__
472 }
473 
474 
475 void
476 TermApp::_UnregisterTerminal()
477 {
478 	_UpdateRegistration(false);
479 }
480 
481 
482 void
483 TermApp::_RegisterTerminal()
484 {
485 	_UpdateRegistration(true);
486 }
487 
488 
489 //#ifndef B_NETPOSITIVE_APP_SIGNATURE
490 //#define B_NETPOSITIVE_APP_SIGNATURE "application/x-vnd.Be-NPOS"
491 //#endif
492 //
493 //void
494 //TermApp::ShowHTML(BMessage *msg)
495 //{
496 //  const char *url;
497 //  msg->FindString("Url", &url);
498 //  BMessage message;
499 //
500 //  message.what = B_NETPOSITIVE_OPEN_URL;
501 //  message.AddString("be:url", url);
502 
503 //  be_roster->Launch(B_NETPOSITIVE_APP_SIGNATURE, &message);
504 //  while(!(be_roster->IsRunning(B_NETPOSITIVE_APP_SIGNATURE)))
505 //    snooze(10000);
506 //
507 //  // Activate net+
508 //  be_roster->ActivateApp(be_roster->TeamFor(B_NETPOSITIVE_APP_SIGNATURE));
509 //}
510 
511 
512 void
513 TermApp::_HandleChildCleanup()
514 {
515 }
516 
517 
518 /*static*/ void
519 TermApp::_SigChildHandler(int signal, void* data)
520 {
521 	// Spawing a thread that does the actual signal handling is pretty much
522 	// the only safe thing to do in a multi-threaded application. The
523 	// interrupted thread might have been anywhere, e.g. in a critical section,
524 	// holding locks. If we do anything that does require locking at any point
525 	// (e.g. memory allocation, messaging), we risk a dead-lock or data
526 	// structure corruption. Spawing a thread is safe though, since its only
527 	// a system call.
528 	thread_id thread = spawn_thread(_ChildCleanupThread, "child cleanup",
529 		B_NORMAL_PRIORITY, ((TermApp*)data)->fTermWindow);
530 	if (thread >= 0)
531 		resume_thread(thread);
532 }
533 
534 
535 /*static*/ status_t
536 TermApp::_ChildCleanupThread(void* data)
537 {
538 	// Just drop the windowa message and let it do the actual work. This
539 	// saves us additional synchronization measures.
540 	return ((TermWindow*)data)->PostMessage(MSG_CHECK_CHILDREN);
541 }
542 
543 
544 
545 void
546 TermApp::_Usage(char *name)
547 {
548 	fprintf(stderr, "Haiku Terminal\n"
549 		"Copyright 2001-2007 Haiku, Inc.\n"
550 		"Copyright(C) 1999 Kazuho Okui and Takashi Murai.\n"
551 		"\n"
552 		"Usage: %s [OPTION] [SHELL]\n", name);
553 
554 	fprintf(stderr,
555 		"  -h,     --help               print this help\n"
556 		//"  -p,     --preference         load preference file\n"
557 		"  -t,     --title              set window title\n"
558 		"  -f,     --fullscreen         start fullscreen\n"
559 		//"  -geom,  --geometry           set window geometry\n"
560 		//"                               An example of geometry is \"80x25+100+100\"\n"
561 		);
562 }
563 
564