⛏️ index : haiku.git

/*
 * Copyright 2001-2019, Haiku.
 * Copyright (c) 2003-2004 Kian Duffy <myob@users.sourceforge.net>
 * Parts Copyright (C) 1998,99 Kazuho Okui and Takashi Murai.
 *
 * Distributed unter the terms of the MIT license.
 *
 * Authors:
 *		Jeremiah Bailey, <jjbailey@gmail.com>
 *		Kian Duffy, <myob@users.sourceforge.net>
 *		Simon South, simon@simonsouth.net
 *		Siarzhuk Zharski, <zharik@gmx.li>
 */


#include "TermApp.h"

#include <errno.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#include <Alert.h>
#include <Catalog.h>
#include <Clipboard.h>
#include <Catalog.h>
#include <InterfaceDefs.h>
#include <Locale.h>
#include <NodeInfo.h>
#include <Path.h>
#include <Roster.h>
#include <Screen.h>
#include <String.h>

#include "Arguments.h"
#include "Globals.h"
#include "PrefHandler.h"
#include "TermConst.h"
#include "TermWindow.h"


static bool sUsageRequested = false;
//static bool sGeometryRequested = false;

rgb_color TermApp::fDefaultPalette[kTermColorCount];

int
main()
{
	TermApp app;
	app.Run();

	return 0;
}

#undef B_TRANSLATION_CONTEXT
#define B_TRANSLATION_CONTEXT "Terminal TermApp"

TermApp::TermApp()
	:
	BApplication(TERM_SIGNATURE),
	fChildCleanupThread(-1),
	fTerminating(false),
	fTermWindow(NULL),
	fArgs(NULL)
{
	fArgs = new Arguments(0, NULL);

	_InitDefaultPalette();
}


TermApp::~TermApp()
{
	delete fArgs;
}


void
TermApp::ReadyToRun()
{
	// Prevent opeing window when option -h is given.
	if (sUsageRequested)
		return;

	// Install a SIGCHLD signal handler, so that we will be notified, when
	// a shell exits. The handler itself will never be executed, since we block
	// the signal in all threads and handle it with sigwaitinfo() in the child
	// cleanup thread.
	struct sigaction action;
	action.sa_handler = (__sighandler_t)_SigChildHandler;
	sigemptyset(&action.sa_mask);
	action.sa_flags = 0;
	if (sigaction(SIGCHLD, &action, NULL) < 0) {
		fprintf(stderr, "sigaction() failed: %s\n", strerror(errno));
		// continue anyway
	}

	// block SIGCHLD and SIGUSR1 -- we send the latter to wake up the child
	// cleanup thread when quitting.
	sigset_t blockedSignals;
	sigemptyset(&blockedSignals);
	sigaddset(&blockedSignals, SIGCHLD);
	sigaddset(&blockedSignals, SIGUSR1);

	int error = pthread_sigmask(SIG_BLOCK, &blockedSignals, NULL);
	if (error != 0)
		fprintf(stderr, "pthread_sigmask() failed: %s\n", strerror(errno));

	// spawn the child cleanup thread
	fChildCleanupThread = spawn_thread(_ChildCleanupThreadEntry,
		"child cleanup", B_NORMAL_PRIORITY, this);
	if (fChildCleanupThread >= 0) {
		resume_thread(fChildCleanupThread);
	} else {
		fprintf(stderr, "Failed to start child cleanup thread: %s\n",
			strerror(fChildCleanupThread));
	}

	// init the mouse copy'n'paste clipboard
	gMouseClipboard = new BClipboard(MOUSE_CLIPBOARD_NAME, true);

	status_t status = _MakeTermWindow();

	// failed spawn, print stdout and open alert panel
	if (status < B_OK) {
		BAlert* alert = new BAlert("alert",
			B_TRANSLATE("Terminal couldn't start the shell. Sorry."),
			B_TRANSLATE("OK"), NULL, NULL, B_WIDTH_FROM_LABEL,
			B_INFO_ALERT);
		alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
		alert->Go();
		PostMessage(B_QUIT_REQUESTED);
		return;
	}

	// using BScreen::Frame isn't enough
	if (fArgs->FullScreen())
		BMessenger(fTermWindow).SendMessage(FULLSCREEN);
}


bool
TermApp::QuitRequested()
{
	// check whether the system is shutting down
	BMessage* message = CurrentMessage();
	bool shutdown;
	if (message != NULL && message->FindBool("_shutdown_", &shutdown) == B_OK
		&& shutdown) {
		// The system is shutting down. Quit the window synchronously. This
		// skips the checks for running processes and the "Are you sure..."
		// alert.
		if (fTermWindow->Lock())
			fTermWindow->Quit();
	}

	return BApplication::QuitRequested();
}


void
TermApp::Quit()
{
	fTerminating = true;

	if (fChildCleanupThread >= 0) {
		send_signal(fChildCleanupThread, SIGUSR1);
		wait_for_thread(fChildCleanupThread, NULL);
	}

	BApplication::Quit();
}


void
TermApp::MessageReceived(BMessage* message)
{
	switch (message->what) {
		case B_KEY_MAP_LOADED:
			fTermWindow->PostMessage(message);
			break;

		case MSG_ACTIVATE_TERM:
			fTermWindow->Activate();
			break;

		default:
			BApplication::MessageReceived(message);
			break;
	}
}


void
TermApp::ArgvReceived(int32 argc, char **argv)
{
	fArgs->Parse(argc, argv);

	if (fArgs->UsageRequested()) {
		_Usage(argv[0]);
		sUsageRequested = true;
		PostMessage(B_QUIT_REQUESTED);
		return;
	}
}


void
TermApp::RefsReceived(BMessage* message)
{
	// Works Only Launced by Double-Click file, or Drags file to App.
	if (!IsLaunching())
		return;

	entry_ref ref;
	if (message->FindRef("refs", 0, &ref) != B_OK)
		return;

	BFile file;
	if (file.SetTo(&ref, B_READ_WRITE) != B_OK)
		return;

	BNodeInfo info(&file);
	char mimetype[B_MIME_TYPE_LENGTH];
	info.GetType(mimetype);

	// if App opened by Pref file
	if (strcmp(mimetype, PREFFILE_MIMETYPE) == 0) {
		BEntry ent(&ref);
		BPath path(&ent);
		PrefHandler::Default()->OpenText(path.Path());
		return;
	}

	// if App opened by Shell Script
	if (strcmp(mimetype, "text/x-haiku-shscript") == 0) {
		// Not implemented.
		//    beep();
		return;
	}
}


status_t
TermApp::_MakeTermWindow()
{
	try {
		fTermWindow = new TermWindow(*fArgs);
	} catch (int error) {
		return (status_t)error;
	} catch (...) {
		return B_ERROR;
	}

	fTermWindow->Show();

	return B_OK;
}


/*static*/ void
TermApp::_SigChildHandler(int signal, void* data)
{
	fprintf(stderr, "Terminal: _SigChildHandler() called! That should never "
		"happen!\n");
}


/*static*/ status_t
TermApp::_ChildCleanupThreadEntry(void* data)
{
	return ((TermApp*)data)->_ChildCleanupThread();
}


status_t
TermApp::_ChildCleanupThread()
{
	sigset_t waitForSignals;
	sigemptyset(&waitForSignals);
	sigaddset(&waitForSignals, SIGCHLD);
	sigaddset(&waitForSignals, SIGUSR1);

	for (;;) {
		int signal;
		int error = sigwait(&waitForSignals, &signal);

		if (fTerminating)
			break;

		if (error == 0 && signal == SIGCHLD)
			fTermWindow->PostMessage(MSG_CHECK_CHILDREN);
	}

	return B_OK;
}


void
TermApp::_Usage(char *name)
{
	fprintf(stderr, B_TRANSLATE("Haiku Terminal\n"
		"Copyright 2001-2019 Haiku, Inc.\n"
		"Copyright(C) 1999 Kazuho Okui and Takashi Murai.\n"
		"\n"
		"Usage: %s [OPTION] [SHELL]\n"), name);

	fputs(B_TRANSLATE(
			"  -h,     --help               print this help\n"
			//"  -p,     --preference         load preference file\n"
			"  -t,     --title              set window title\n"
			"  -f,     --fullscreen         start fullscreen\n"
			"  -w,     --working-directory  set initial working directory\n")
			//"  -geom,  --geometry           set window geometry\n"
			//"                               An example of geometry is \"80x25+100+100\"\n"
		, stderr);
}


void
TermApp::_InitDefaultPalette()
{
	// 0 - 15 are system ANSI colors
	const char * keys[kANSIColorCount] = {
		PREF_ANSI_BLACK_COLOR,
		PREF_ANSI_RED_COLOR,
		PREF_ANSI_GREEN_COLOR,
		PREF_ANSI_YELLOW_COLOR,
		PREF_ANSI_BLUE_COLOR,
		PREF_ANSI_MAGENTA_COLOR,
		PREF_ANSI_CYAN_COLOR,
		PREF_ANSI_WHITE_COLOR,
		PREF_ANSI_BLACK_HCOLOR,
		PREF_ANSI_RED_HCOLOR,
		PREF_ANSI_GREEN_HCOLOR,
		PREF_ANSI_YELLOW_HCOLOR,
		PREF_ANSI_BLUE_HCOLOR,
		PREF_ANSI_MAGENTA_HCOLOR,
		PREF_ANSI_CYAN_HCOLOR,
		PREF_ANSI_WHITE_HCOLOR
	};

	rgb_color* color = fDefaultPalette;
	PrefHandler* handler = PrefHandler::Default();
	for (uint i = 0; i < kANSIColorCount; i++)
		*color++ = handler->getRGB(keys[i]);

	// 16 - 231 are 6x6x6 color "cubes" in xterm color model
	for (uint red = 0; red < 256; red += (red == 0) ? 95 : 40)
		for (uint green = 0; green < 256; green += (green == 0) ? 95 : 40)
			for (uint blue = 0; blue < 256; blue += (blue == 0) ? 95 : 40)
				(*color++).set_to(red, green, blue);

	// 232 - 255 are grayscale ramp in xterm color model
	for (uint gray = 8; gray < 240; gray += 10)
		(*color++).set_to(gray, gray, gray);
}