xref: /haiku/src/apps/terminal/TermApp.cpp (revision 746cac055adc6ac3308c7bc2d29040fb95689cc9)
1 /*
2  * Copyright 2001-2007, 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_IS_MINIMIZE = 'mtim';
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-2005, 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_IS_MINIMIZE:
163 		{
164 			BMessage reply(B_REPLY);
165 			reply.AddBool("result", fTermWindow->IsMinimized());
166 			msg->SendReply(&reply);
167 			break;
168 		}
169 
170 		case MSG_CHECK_CHILDREN:
171 			_HandleChildCleanup();
172 			break;
173 
174 		default:
175 			BApplication::MessageReceived(msg);
176 			break;
177 	}
178 }
179 
180 
181 void
182 TermApp::ArgvReceived(int32 argc, char **argv)
183 {
184 	fArgs->Parse(argc, argv);
185 
186 	if (fArgs->UsageRequested()) {
187 		_Usage(argv[0]);
188 		sUsageRequested = true;
189 		PostMessage(B_QUIT_REQUESTED);
190 		return;
191 	}
192 
193 	if (fArgs->Title() != NULL)
194 		fWindowTitle = fArgs->Title();
195 
196 	fStartFullscreen = fArgs->FullScreen();
197 }
198 
199 
200 void
201 TermApp::RefsReceived(BMessage* message)
202 {
203 	// Works Only Launced by Double-Click file, or Drags file to App.
204 	if (!IsLaunching())
205 		return;
206 
207 	entry_ref ref;
208 	if (message->FindRef("refs", 0, &ref) != B_OK)
209 		return;
210 
211 	BFile file;
212 	if (file.SetTo(&ref, B_READ_WRITE) != B_OK)
213 		return;
214 
215 	BNodeInfo info(&file);
216 	char mimetype[B_MIME_TYPE_LENGTH];
217 	info.GetType(mimetype);
218 
219 	// if App opened by Pref file
220 	if (!strcmp(mimetype, PREFFILE_MIMETYPE)) {
221 
222 		BEntry ent(&ref);
223 		BPath path(&ent);
224 		PrefHandler::Default()->OpenText(path.Path());
225 		return;
226 	}
227 
228 	// if App opened by Shell Script
229 	if (!strcmp(mimetype, "text/x-haiku-shscript")){
230 		// Not implemented.
231 		//    beep();
232 		return;
233 	}
234 }
235 
236 
237 status_t
238 TermApp::_MakeTermWindow(BRect &frame)
239 {
240 	try {
241 		fTermWindow = new TermWindow(frame, fWindowTitle.String(), fArgs);
242 	} catch (int error) {
243 		return (status_t)error;
244 	} catch (...) {
245 		return B_ERROR;
246 	}
247 
248 	fTermWindow->Show();
249 
250 	return B_OK;
251 }
252 
253 
254 void
255 TermApp::_ActivateTermWindow(team_id id)
256 {
257 	BMessenger app(TERM_SIGNATURE, id);
258 	if (app.IsTargetLocal())
259 		fTermWindow->Activate();
260 	else
261 		app.SendMessage(MSG_ACTIVATE_TERM);
262 }
263 
264 
265 void
266 TermApp::_SwitchTerm()
267 {
268 	team_id myId = be_app->Team(); // My id
269 	BList teams;
270 	be_roster->GetAppList(TERM_SIGNATURE, &teams);
271 	int32 numTerms = teams.CountItems();
272 
273 	if (numTerms <= 1 )
274 		return; //Can't Switch !!
275 
276 	// Find position of mine in app teams.
277 	int32 i;
278 
279 	for (i = 0; i < numTerms; i++) {
280 		if (myId == reinterpret_cast<team_id>(teams.ItemAt(i)))
281 			break;
282 	}
283 
284 	do {
285 		if (--i < 0)
286 			i = numTerms - 1;
287 	} while (_IsMinimized(reinterpret_cast<team_id>(teams.ItemAt(i))));
288 
289 	// Activate switched terminal.
290 	_ActivateTermWindow(reinterpret_cast<team_id>(teams.ItemAt(i)));
291 }
292 
293 
294 bool
295 TermApp::_IsMinimized(team_id id)
296 {
297 	BMessenger app(TERM_SIGNATURE, id);
298 	if (app.IsTargetLocal())
299 		return fTermWindow->IsMinimized();
300 
301 	BMessage reply;
302 	if (app.SendMessage(MSG_TERM_IS_MINIMIZE, &reply) != B_OK)
303 		return true;
304 
305 	bool hidden;
306 	reply.FindBool("result", &hidden);
307 	return hidden;
308 }
309 
310 
311 /*!
312 	Checks if all teams that have an ID-to-team mapping in the message
313 	are still running.
314 	The IDs for teams that are gone will be made available again, and
315 	their mapping is removed from the message.
316 */
317 void
318 TermApp::_SanitizeIDs(BMessage* data, uint8* windows, ssize_t length)
319 {
320 	BList teams;
321 	be_roster->GetAppList(TERM_SIGNATURE, &teams);
322 
323 	for (int32 i = 0; i < length; i++) {
324 		if (!windows[i])
325 			continue;
326 
327 		BString id("id-");
328 		id << i;
329 
330 		team_id team;
331 		if (data->FindInt32(id.String(), &team) != B_OK)
332 			continue;
333 
334 		if (!teams.HasItem((void*)team)) {
335 			windows[i] = false;
336 			data->RemoveName(id.String());
337 		}
338 	}
339 }
340 
341 
342 /*!
343 	Removes the current fWindowNumber (ID) from the supplied array, or
344 	finds a free ID in it, and sets fWindowNumber accordingly.
345 */
346 bool
347 TermApp::_UpdateIDs(bool set, uint8* windows, ssize_t maxLength,
348 	ssize_t* _length)
349 {
350 	ssize_t length = *_length;
351 
352 	if (set) {
353 		int32 i;
354 		for (i = 0; i < length; i++) {
355 			if (!windows[i]) {
356 				windows[i] = true;
357 				fWindowNumber = i + 1;
358 				break;
359 			}
360 		}
361 
362 		if (i == length) {
363 			if (length >= maxLength)
364 				return false;
365 
366 			windows[length] = true;
367 			length++;
368 			fWindowNumber = length;
369 		}
370 	} else {
371 		// update information and write it back
372 		windows[fWindowNumber - 1] = false;
373 	}
374 
375 	*_length = length;
376 	return true;
377 }
378 
379 
380 void
381 TermApp::_UpdateRegistration(bool set)
382 {
383 	if (set)
384 		fWindowNumber = -1;
385 	else if (fWindowNumber < 0)
386 		return;
387 
388 #ifdef __HAIKU__
389 	// use BClipboard - it supports atomic access in Haiku
390 	BClipboard clipboard(TERM_SIGNATURE);
391 
392 	while (true) {
393 		if (!clipboard.Lock())
394 			return;
395 
396 		BMessage* data = clipboard.Data();
397 
398 		const uint8* windowsData;
399 		uint8 windows[512];
400 		ssize_t length;
401 		if (data->FindData("ids", B_RAW_TYPE,
402 				(const void**)&windowsData, &length) != B_OK)
403 			length = 0;
404 
405 		if (length > (ssize_t)sizeof(windows))
406 			length = sizeof(windows);
407 		if (length > 0)
408 			memcpy(windows, windowsData, length);
409 
410 		_SanitizeIDs(data, windows, length);
411 
412 		status_t status = B_OK;
413 		if (_UpdateIDs(set, windows, sizeof(windows), &length)) {
414 			// add/remove our ID-to-team mapping
415 			BString id("id-");
416 			id << fWindowNumber;
417 
418 			if (set)
419 				data->AddInt32(id.String(), Team());
420 			else
421 				data->RemoveName(id.String());
422 
423 			data->RemoveName("ids");
424 			//if (data->ReplaceData("ids", B_RAW_TYPE, windows, length) != B_OK)
425 			data->AddData("ids", B_RAW_TYPE, windows, length);
426 
427 			status = clipboard.Commit(true);
428 		}
429 
430 		clipboard.Unlock();
431 
432 		if (status == B_OK)
433 			break;
434 	}
435 #else	// !__HAIKU__
436 	// use a file to store the IDs - unfortunately, locking
437 	// doesn't work on BeOS either here
438 	int fd = open("/tmp/terminal_ids", O_RDWR | O_CREAT);
439 	if (fd < 0)
440 		return;
441 
442 	struct flock lock;
443 	lock.l_type = F_WRLCK;
444 	lock.l_whence = SEEK_CUR;
445 	lock.l_start = 0;
446 	lock.l_len = -1;
447 	fcntl(fd, F_SETLKW, &lock);
448 
449 	uint8 windows[512];
450 	ssize_t length = read_pos(fd, 0, windows, sizeof(windows));
451 	if (length < 0) {
452 		close(fd);
453 		return;
454 	}
455 
456 	if (length > (ssize_t)sizeof(windows))
457 		length = sizeof(windows);
458 
459 	if (_UpdateIDs(set, windows, sizeof(windows), &length))
460 		write_pos(fd, 0, windows, length);
461 
462 	close(fd);
463 #endif	// !__HAIKU__
464 }
465 
466 
467 void
468 TermApp::_UnregisterTerminal()
469 {
470 	_UpdateRegistration(false);
471 }
472 
473 
474 void
475 TermApp::_RegisterTerminal()
476 {
477 	_UpdateRegistration(true);
478 }
479 
480 
481 //#ifndef B_NETPOSITIVE_APP_SIGNATURE
482 //#define B_NETPOSITIVE_APP_SIGNATURE "application/x-vnd.Be-NPOS"
483 //#endif
484 //
485 //void
486 //TermApp::ShowHTML(BMessage *msg)
487 //{
488 //  const char *url;
489 //  msg->FindString("Url", &url);
490 //  BMessage message;
491 //
492 //  message.what = B_NETPOSITIVE_OPEN_URL;
493 //  message.AddString("be:url", url);
494 
495 //  be_roster->Launch(B_NETPOSITIVE_APP_SIGNATURE, &message);
496 //  while(!(be_roster->IsRunning(B_NETPOSITIVE_APP_SIGNATURE)))
497 //    snooze(10000);
498 //
499 //  // Activate net+
500 //  be_roster->ActivateApp(be_roster->TeamFor(B_NETPOSITIVE_APP_SIGNATURE));
501 //}
502 
503 
504 void
505 TermApp::_HandleChildCleanup()
506 {
507 }
508 
509 
510 /*static*/ void
511 TermApp::_SigChildHandler(int signal, void* data)
512 {
513 	// Spawing a thread that does the actual signal handling is pretty much
514 	// the only safe thing to do in a multi-threaded application. The
515 	// interrupted thread might have been anywhere, e.g. in a critical section,
516 	// holding locks. If we do anything that does require locking at any point
517 	// (e.g. memory allocation, messaging), we risk a dead-lock or data
518 	// structure corruption. Spawing a thread is safe though, since its only
519 	// a system call.
520 	thread_id thread = spawn_thread(_ChildCleanupThread, "child cleanup",
521 		B_NORMAL_PRIORITY, ((TermApp*)data)->fTermWindow);
522 	if (thread >= 0)
523 		resume_thread(thread);
524 }
525 
526 
527 /*static*/ status_t
528 TermApp::_ChildCleanupThread(void* data)
529 {
530 	// Just drop the windowa message and let it do the actual work. This
531 	// saves us additional synchronization measures.
532 	return ((TermWindow*)data)->PostMessage(MSG_CHECK_CHILDREN);
533 }
534 
535 
536 
537 void
538 TermApp::_Usage(char *name)
539 {
540 	fprintf(stderr, "Haiku Terminal\n"
541 		"Copyright 2001-2007 Haiku, Inc.\n"
542 		"Copyright(C) 1999 Kazuho Okui and Takashi Murai.\n"
543 		"\n"
544 		"Usage: %s [OPTION] [SHELL]\n", name);
545 
546 	fprintf(stderr,
547 		"  -h,     --help               print this help\n"
548 		//"  -p,     --preference         load preference file\n"
549 		"  -t,     --title              set window title\n"
550 		"  -f,     --fullscreen         start fullscreen\n"
551 		//"  -geom,  --geometry           set window geometry\n"
552 		//"                               An example of geometry is \"80x25+100+100\"\n"
553 		);
554 }
555 
556