1/*
2 * Copyright 2001-2019, Haiku.
3 * Copyright (c) 2003-2004 Kian Duffy <myob@users.sourceforge.net>
4 * Parts Copyright (C) 1998,99 Kazuho Okui and Takashi Murai.
5 *
6 * Distributed unter the terms of the MIT license.
7 *
8 * Authors:
9 *		Jeremiah Bailey, <jjbailey@gmail.com>
10 *		Kian Duffy, <myob@users.sourceforge.net>
11 *		Simon South, simon@simonsouth.net
12 *		Siarzhuk Zharski, <zharik@gmx.li>
13 */
14
15
16#include "TermApp.h"
17
18#include <errno.h>
19#include <signal.h>
20#include <stdio.h>
21#include <stdlib.h>
22#include <unistd.h>
23
24#include <Alert.h>
25#include <Catalog.h>
26#include <Clipboard.h>
27#include <Catalog.h>
28#include <InterfaceDefs.h>
29#include <Locale.h>
30#include <NodeInfo.h>
31#include <Path.h>
32#include <Roster.h>
33#include <Screen.h>
34#include <String.h>
35
36#include "Arguments.h"
37#include "Globals.h"
38#include "PrefHandler.h"
39#include "TermConst.h"
40#include "TermWindow.h"
41
42
43static bool sUsageRequested = false;
44//static bool sGeometryRequested = false;
45
46rgb_color TermApp::fDefaultPalette[kTermColorCount];
47
48int
49main()
50{
51	TermApp app;
52	app.Run();
53
54	return 0;
55}
56
57#undef B_TRANSLATION_CONTEXT
58#define B_TRANSLATION_CONTEXT "Terminal TermApp"
59
60TermApp::TermApp()
61	:
62	BApplication(TERM_SIGNATURE),
63	fChildCleanupThread(-1),
64	fTerminating(false),
65	fStartFullscreen(false),
66	fTermWindow(NULL),
67	fArgs(NULL)
68{
69	fArgs = new Arguments(0, NULL);
70
71	_InitDefaultPalette();
72}
73
74
75TermApp::~TermApp()
76{
77	delete fArgs;
78}
79
80
81void
82TermApp::ReadyToRun()
83{
84	// Prevent opeing window when option -h is given.
85	if (sUsageRequested)
86		return;
87
88	// Install a SIGCHLD signal handler, so that we will be notified, when
89	// a shell exits. The handler itself will never be executed, since we block
90	// the signal in all threads and handle it with sigwaitinfo() in the child
91	// cleanup thread.
92	struct sigaction action;
93	action.sa_handler = (__sighandler_t)_SigChildHandler;
94	sigemptyset(&action.sa_mask);
95	action.sa_flags = 0;
96	if (sigaction(SIGCHLD, &action, NULL) < 0) {
97		fprintf(stderr, "sigaction() failed: %s\n", strerror(errno));
98		// continue anyway
99	}
100
101	// block SIGCHLD and SIGUSR1 -- we send the latter to wake up the child
102	// cleanup thread when quitting.
103	sigset_t blockedSignals;
104	sigemptyset(&blockedSignals);
105	sigaddset(&blockedSignals, SIGCHLD);
106	sigaddset(&blockedSignals, SIGUSR1);
107
108	int error = pthread_sigmask(SIG_BLOCK, &blockedSignals, NULL);
109	if (error != 0)
110		fprintf(stderr, "pthread_sigmask() failed: %s\n", strerror(errno));
111
112	// spawn the child cleanup thread
113	fChildCleanupThread = spawn_thread(_ChildCleanupThreadEntry,
114		"child cleanup", B_NORMAL_PRIORITY, this);
115	if (fChildCleanupThread >= 0) {
116		resume_thread(fChildCleanupThread);
117	} else {
118		fprintf(stderr, "Failed to start child cleanup thread: %s\n",
119			strerror(fChildCleanupThread));
120	}
121
122	// init the mouse copy'n'paste clipboard
123	gMouseClipboard = new BClipboard(MOUSE_CLIPBOARD_NAME, true);
124
125	status_t status = _MakeTermWindow();
126
127	// failed spawn, print stdout and open alert panel
128	if (status < B_OK) {
129		BAlert* alert = new BAlert("alert",
130			B_TRANSLATE("Terminal couldn't start the shell. Sorry."),
131			B_TRANSLATE("OK"), NULL, NULL, B_WIDTH_FROM_LABEL,
132			B_INFO_ALERT);
133		alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
134		alert->Go();
135		PostMessage(B_QUIT_REQUESTED);
136		return;
137	}
138
139	// using BScreen::Frame isn't enough
140	if (fStartFullscreen)
141		BMessenger(fTermWindow).SendMessage(FULLSCREEN);
142}
143
144
145bool
146TermApp::QuitRequested()
147{
148	// check whether the system is shutting down
149	BMessage* message = CurrentMessage();
150	bool shutdown;
151	if (message != NULL && message->FindBool("_shutdown_", &shutdown) == B_OK
152		&& shutdown) {
153		// The system is shutting down. Quit the window synchronously. This
154		// skips the checks for running processes and the "Are you sure..."
155		// alert.
156		if (fTermWindow->Lock())
157			fTermWindow->Quit();
158	}
159
160	return BApplication::QuitRequested();
161}
162
163
164void
165TermApp::Quit()
166{
167	fTerminating = true;
168
169	if (fChildCleanupThread >= 0) {
170		send_signal(fChildCleanupThread, SIGUSR1);
171		wait_for_thread(fChildCleanupThread, NULL);
172	}
173
174	BApplication::Quit();
175}
176
177
178void
179TermApp::MessageReceived(BMessage* message)
180{
181	switch (message->what) {
182		case B_KEY_MAP_LOADED:
183			fTermWindow->PostMessage(message);
184			break;
185
186		case MSG_ACTIVATE_TERM:
187			fTermWindow->Activate();
188			break;
189
190		default:
191			BApplication::MessageReceived(message);
192			break;
193	}
194}
195
196
197void
198TermApp::ArgvReceived(int32 argc, char **argv)
199{
200	fArgs->Parse(argc, argv);
201
202	if (fArgs->UsageRequested()) {
203		_Usage(argv[0]);
204		sUsageRequested = true;
205		PostMessage(B_QUIT_REQUESTED);
206		return;
207	}
208
209	if (fArgs->Title() != NULL)
210		fWindowTitle = fArgs->Title();
211
212	if (fArgs->WorkingDir() != NULL) {
213		fWorkingDirectory = fArgs->WorkingDir();
214		chdir(fWorkingDirectory);
215	}
216
217	fStartFullscreen = fArgs->FullScreen();
218}
219
220
221void
222TermApp::RefsReceived(BMessage* message)
223{
224	// Works Only Launced by Double-Click file, or Drags file to App.
225	if (!IsLaunching())
226		return;
227
228	entry_ref ref;
229	if (message->FindRef("refs", 0, &ref) != B_OK)
230		return;
231
232	BFile file;
233	if (file.SetTo(&ref, B_READ_WRITE) != B_OK)
234		return;
235
236	BNodeInfo info(&file);
237	char mimetype[B_MIME_TYPE_LENGTH];
238	info.GetType(mimetype);
239
240	// if App opened by Pref file
241	if (strcmp(mimetype, PREFFILE_MIMETYPE) == 0) {
242
243		BEntry ent(&ref);
244		BPath path(&ent);
245		PrefHandler::Default()->OpenText(path.Path());
246		return;
247	}
248
249	// if App opened by Shell Script
250	if (strcmp(mimetype, "text/x-haiku-shscript") == 0) {
251		// Not implemented.
252		//    beep();
253		return;
254	}
255}
256
257
258status_t
259TermApp::_MakeTermWindow()
260{
261	try {
262		fTermWindow = new TermWindow(fWindowTitle, fArgs);
263	} catch (int error) {
264		return (status_t)error;
265	} catch (...) {
266		return B_ERROR;
267	}
268
269	fTermWindow->Show();
270
271	return B_OK;
272}
273
274
275//#ifndef B_NETPOSITIVE_APP_SIGNATURE
276//#define B_NETPOSITIVE_APP_SIGNATURE "application/x-vnd.Be-NPOS"
277//#endif
278//
279//void
280//TermApp::ShowHTML(BMessage *msg)
281//{
282//  const char *url;
283//  msg->FindString("Url", &url);
284//  BMessage message;
285//
286//  message.what = B_NETPOSITIVE_OPEN_URL;
287//  message.AddString("be:url", url);
288
289//  be_roster->Launch(B_NETPOSITIVE_APP_SIGNATURE, &message);
290//  while(!(be_roster->IsRunning(B_NETPOSITIVE_APP_SIGNATURE)))
291//    snooze(10000);
292//
293//  // Activate net+
294//  be_roster->ActivateApp(be_roster->TeamFor(B_NETPOSITIVE_APP_SIGNATURE));
295//}
296
297
298/*static*/ void
299TermApp::_SigChildHandler(int signal, void* data)
300{
301	fprintf(stderr, "Terminal: _SigChildHandler() called! That should never "
302		"happen!\n");
303}
304
305
306/*static*/ status_t
307TermApp::_ChildCleanupThreadEntry(void* data)
308{
309	return ((TermApp*)data)->_ChildCleanupThread();
310}
311
312
313status_t
314TermApp::_ChildCleanupThread()
315{
316	sigset_t waitForSignals;
317	sigemptyset(&waitForSignals);
318	sigaddset(&waitForSignals, SIGCHLD);
319	sigaddset(&waitForSignals, SIGUSR1);
320
321	for (;;) {
322		int signal;
323		int error = sigwait(&waitForSignals, &signal);
324
325		if (fTerminating)
326			break;
327
328		if (error == 0 && signal == SIGCHLD)
329			fTermWindow->PostMessage(MSG_CHECK_CHILDREN);
330	}
331
332	return B_OK;
333}
334
335
336void
337TermApp::_Usage(char *name)
338{
339	fprintf(stderr, B_TRANSLATE("Haiku Terminal\n"
340		"Copyright 2001-2019 Haiku, Inc.\n"
341		"Copyright(C) 1999 Kazuho Okui and Takashi Murai.\n"
342		"\n"
343		"Usage: %s [OPTION] [SHELL]\n"), name);
344
345	fputs(B_TRANSLATE(
346			"  -h,     --help               print this help\n"
347			//"  -p,     --preference         load preference file\n"
348			"  -t,     --title              set window title\n"
349			"  -f,     --fullscreen         start fullscreen\n"
350			"  -w,     --working-directory  set initial working directory\n")
351			//"  -geom,  --geometry           set window geometry\n"
352			//"                               An example of geometry is \"80x25+100+100\"\n"
353		, stderr);
354}
355
356
357void
358TermApp::_InitDefaultPalette()
359{
360	// 0 - 15 are system ANSI colors
361	const char * keys[kANSIColorCount] = {
362		PREF_ANSI_BLACK_COLOR,
363		PREF_ANSI_RED_COLOR,
364		PREF_ANSI_GREEN_COLOR,
365		PREF_ANSI_YELLOW_COLOR,
366		PREF_ANSI_BLUE_COLOR,
367		PREF_ANSI_MAGENTA_COLOR,
368		PREF_ANSI_CYAN_COLOR,
369		PREF_ANSI_WHITE_COLOR,
370		PREF_ANSI_BLACK_HCOLOR,
371		PREF_ANSI_RED_HCOLOR,
372		PREF_ANSI_GREEN_HCOLOR,
373		PREF_ANSI_YELLOW_HCOLOR,
374		PREF_ANSI_BLUE_HCOLOR,
375		PREF_ANSI_MAGENTA_HCOLOR,
376		PREF_ANSI_CYAN_HCOLOR,
377		PREF_ANSI_WHITE_HCOLOR
378	};
379
380	rgb_color* color = fDefaultPalette;
381	PrefHandler* handler = PrefHandler::Default();
382	for (uint i = 0; i < kANSIColorCount; i++)
383		*color++ = handler->getRGB(keys[i]);
384
385	// 16 - 231 are 6x6x6 color "cubes" in xterm color model
386	for (uint red = 0; red < 256; red += (red == 0) ? 95 : 40)
387		for (uint green = 0; green < 256; green += (green == 0) ? 95 : 40)
388			for (uint blue = 0; blue < 256; blue += (blue == 0) ? 95 : 40)
389				(*color++).set_to(red, green, blue);
390
391	// 232 - 255 are grayscale ramp in xterm color model
392	for (uint gray = 8; gray < 240; gray += 10)
393		(*color++).set_to(gray, gray, gray);
394}
395