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