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