Files
haiku-beta6/src/apps/terminal/TermApp.cpp
T
Axel Dörfler 7f1085115e * Rewrote terminal ID code to use a temporary file - this fixes bug #1174.
* Tried to fix the usual deadlock on quit, but suspending the thread (to
  interrupt read()) doesn't have the expected outcome (might be a kernel
  bug).
* Removed superfluous MENU_FILE_QUIT and send a B_QUIT_REQUESTED instead.
* Cleanup.


git-svn-id: file:///srv/svn/repos/haiku/haiku/trunk@20816 a95241bf-73f2-0310-859d-f6bbb57e9c96
2007-04-25 17:00:51 +00:00

793 lines
17 KiB
C++

/*
* Copyright 2001-2007, Haiku.
* Copyright (c) 2003-2004 Kian Duffy <[email protected]>
* Copyright (C) 1998,99 Kazuho Okui and Takashi Murai.
*
* Distributed unter the terms of the MIT license.
*/
#include "TermApp.h"
#include "CodeConv.h"
#include "PrefHandler.h"
#include "spawn.h"
#include "TermBuffer.h"
#include "TermWindow.h"
#include "TermConst.h"
#include <Alert.h>
#include <NodeInfo.h>
#include <Path.h>
#include <Roster.h>
#include <Screen.h>
#include <String.h>
#include <TextView.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
// Preference temporary
extern PrefHandler *gTermPref;
extern char gWindowName[];
bool gUsageRequested = false;
bool gGeometryRequested = false;
bool gColorRequested = false;
struct standard_args {
char *name;
char *longname;
int priority;
int nargs;
const char *prefname;
};
struct standard_args standard_args[] = {
{ "-h", "--help", 90, 0, NULL },
{ "-f", "--fullscreen", 30, 0, NULL },
{ "-p", "--preference", 80, 1, NULL },
{ "-t", "--title", 70, 1, NULL },
{ "-geom", "--geometry", 50, 1, NULL },
#if 0
{ "-fg", "--text-fore-color", 40, 1, PREF_TEXT_FORE_COLOR },
{ "-bg", "--text-back-color", 35, 1, PREF_TEXT_BACK_COLOR },
{ "-curfg", "--cursor-fore-color", 30, 1, PREF_CURSOR_FORE_COLOR },
{ "-curbg", "--cursor-back-color", 25, 1, PREF_CURSOR_BACK_COLOR },
{ "-selfg", "--select-fore-color", 20, 1, PREF_SELECT_FORE_COLOR },
{ "-selbg", "--select-back-color", 15, 1, PREF_SELECT_BACK_COLOR },
{ "-imfg", "--im-fore-color", 10, 1, PREF_IM_FORE_COLOR },
{ "-imbg", "--im-back-color", 5, 1, PREF_IM_BACK_COLOR },
{ "-imsel", "--im-select-color", 0, 1, PREF_IM_SELECT_COLOR },
#endif
};
int argmatch(char **, int, char *, char *, int, char **, int *);
void sort_args(int, char **);
int text_to_rgb(char *, rgb_color *, char *);
const ulong MSG_ACTIVATE_TERM = 'msat';
const ulong MSG_TERM_IS_MINIMIZE = 'mtim';
TermApp::TermApp()
: BApplication(TERM_SIGNATURE),
fStartFullscreen(false),
fWindowNumber(-1),
fTermWindow(NULL)
{
fWindowTitle = "Terminal";
_RegisterTerminal();
if (fWindowNumber > 0)
fWindowTitle << " " << fWindowNumber;
int i = fWindowNumber / 16;
int j = fWindowNumber % 16;
int k = (j * 16) + (i * 64) + 50;
int l = (j * 16) + 50;
fTermFrame.Set(k, l, k + 50, k + 50);
gTermPref = new PrefHandler();
}
TermApp::~TermApp()
{
}
void
TermApp::ReadyToRun()
{
// Prevent opeing window when option -h is given.
if (gUsageRequested)
return;
status_t status = _MakeTermWindow(fTermFrame);
// failed spawn, print stdout and open alert panel
if (status < B_OK) {
(new BAlert("alert", "Terminal couldn't start the shell. Sorry.",
"ok", NULL, NULL, B_WIDTH_FROM_LABEL,
B_INFO_ALERT))->Go(NULL);
PostMessage(B_QUIT_REQUESTED);
return;
}
// using BScreen::Frame isn't enough
if (fStartFullscreen)
BMessenger(fTermWindow).SendMessage(FULLSCREEN);
}
void
TermApp::Quit()
{
if (!gUsageRequested){
int status;
kill(-sh_pid, SIGHUP);
wait(&status);
_UnregisterTerminal();
}
delete gTermPref;
BApplication::Quit();
}
void
TermApp::AboutRequested()
{
BAlert *alert = new BAlert("about", "Terminal\n"
"\twritten by Kazuho Okui and Takashi Murai\n"
"\tupdated by Kian Duffy and others\n\n"
"\tCopyright " B_UTF8_COPYRIGHT "2003-2005, Haiku.\n", "Ok");
BTextView *view = alert->TextView();
view->SetStylable(true);
BFont font;
view->GetFont(&font);
font.SetSize(18);
font.SetFace(B_BOLD_FACE);
view->SetFontAndColor(0, 8, &font);
alert->Go();
}
void
TermApp::MessageReceived(BMessage* msg)
{
switch (msg->what) {
case MENU_SWITCH_TERM:
_SwitchTerm();
break;
case MSG_ACTIVATE_TERM:
fTermWindow->TermWinActivate();
break;
case MSG_TERM_IS_MINIMIZE:
{
BMessage reply(B_REPLY);
reply.AddBool("result", fTermWindow->IsMinimized());
msg->SendReply(&reply);
break;
}
default:
BApplication::MessageReceived(msg);
break;
}
}
void
TermApp::ArgvReceived(int32 argc, char **argv)
{
int skip_args = 0;
char *value = 0;
if (argc < 2)
return;
sort_args(argc, argv);
argc = 0;
while (argv[argc])
argc++;
// Print usage
if (argmatch(argv, argc, "-help", "--help", 3, NULL, &skip_args)) {
_Usage(argv[0]);
gUsageRequested = true;
PostMessage(B_QUIT_REQUESTED);
}
// Start fullscreen
if (argmatch(argv, argc, "-f", "--fullscreen", 4, NULL, &skip_args))
fStartFullscreen = true;
// Load preference file
if (argmatch(argv, argc, "-p", "--preference", 4, &value, &skip_args))
gTermPref->Open(value);
// Set window title
if (argmatch(argv ,argc, "-t", "--title", 3, &value, &skip_args))
fWindowTitle = value;
// Set window geometry
if (argmatch(argv, argc, "-geom", "--geometry", 4, &value, &skip_args)) {
int width, height, xpos, ypos;
sscanf(value, "%dx%d+%d+%d", &width, &height, &xpos, &ypos);
if (width < 0 || height < 0 || xpos < 0 || ypos < 0
|| width >= 256 || height >= 256 || xpos >= 2048 || ypos >= 2048) {
fprintf(stderr, "%s: invalid geometry format or value.\n", argv[0]);
fprintf(stderr, "Try `%s --help' for more information.\n", argv[0]);
gUsageRequested = true;
PostMessage(B_QUIT_REQUESTED);
}
gTermPref->setInt32(PREF_COLS, width);
gTermPref->setInt32(PREF_ROWS, height);
fTermFrame.Set(xpos, ypos, xpos + 50, ypos + 50);
gGeometryRequested = true;
}
#if 0
// Open '/etc/rgb.txt' file
BFile inFile;
off_t size = 0;
status_t status = inFile.SetTo("/etc/rgb.txt", B_READ_ONLY);
if (status != B_OK) {
fprintf(stderr, "%s: Can't open /etc/rgb.txt file.\n", argv[0]);
gUsageRequested = true;
PostMessage(B_QUIT_REQUESTED);
}
inFile.GetSize(&size);
char *buffer = new char [size];
inFile.Read(buffer, size);
// Set window, cursor, area and IM color
for (int i = 4; i < 13; i++) {
if (argmatch(argv, argc, standard_args[i].name, standard_args[i].longname,
9, &value, &skip_args)) {
rgb_color color;
if (text_to_rgb(value, &color, buffer))
gTermPref->setRGB(standard_args[i].prefname, color);
else
fprintf(stderr, "%s: invalid color string -- %s\n", argv[0], value);
}
}
delete[] buffer;
#endif
skip_args++;
if (skip_args < argc) {
// Check invalid options
if (*argv[skip_args] == '-') {
fprintf(stderr, "%s: invalid option `%s'\n", argv[0], argv[skip_args]);
fprintf(stderr, "Try `%s --help' for more information.\n", argv[0]);
gUsageRequested = true;
PostMessage(B_QUIT_REQUESTED);
}
CommandLine += argv[skip_args++];
while (skip_args < argc) {
CommandLine += ' ';
CommandLine += argv[skip_args++];
}
}
}
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)) {
BEntry ent(&ref);
BPath path(&ent);
gTermPref->OpenText(path.Path());
return;
}
// if App opened by Shell Script
if (!strcmp(mimetype, "text/x-haiku-shscript")){
// Not implemented.
// beep();
return;
}
}
status_t
TermApp::_MakeTermWindow(BRect &frame)
{
const char *encoding = gTermPref->getString(PREF_TEXT_ENCODING);
// Get encoding name (setenv TTYPE in spawn_shell functions)
const etable *p = encoding_table;
while (p->name) {
if (!strcmp(p->name, encoding)) {
encoding = p->shortname;
break;
}
p++;
}
const char *command = NULL;
if (CommandLine.Length() > 0)
command = CommandLine.String();
else
command = gTermPref->getString(PREF_SHELL);
int rows = gTermPref->getInt32(PREF_ROWS);
if (rows < 1)
gTermPref->setInt32(PREF_ROWS, rows = 1);
int cols = gTermPref->getInt32(PREF_COLS);
if (cols < MIN_COLS)
gTermPref->setInt32(PREF_COLS, cols = MIN_COLS);
int pfd = spawn_shell(rows, cols, command, encoding);
if (pfd < 0)
return pfd;
fTermWindow = new TermWindow(frame, fWindowTitle.String(), pfd);
fTermWindow->Show();
return B_OK;
}
void
TermApp::_ActivateTermWindow(team_id id)
{
BMessenger app(TERM_SIGNATURE, id);
if (app.IsTargetLocal())
fTermWindow->Activate();
else
app.SendMessage(MSG_ACTIVATE_TERM);
}
void
TermApp::_SwitchTerm()
{
team_id myId = be_app->Team(); // My id
BList teams;
be_roster->GetAppList(TERM_SIGNATURE, &teams);
int32 numTerms = teams.CountItems();
if (numTerms <= 1 )
return; //Can't Switch !!
// Find position of mine in app teams.
int32 i;
for (i = 0; i < numTerms; i++) {
if (myId == reinterpret_cast<team_id>(teams.ItemAt(i)))
break;
}
do {
if (--i < 0)
i = numTerms - 1;
} while (_IsMinimized(reinterpret_cast<team_id>(teams.ItemAt(i))));
// Activate switched terminal.
_ActivateTermWindow(reinterpret_cast<team_id>(teams.ItemAt(i)));
}
bool
TermApp::_IsMinimized(team_id id)
{
BMessenger app(TERM_SIGNATURE, id);
if (app.IsTargetLocal())
return fTermWindow->IsMinimized();
BMessage reply;
if (app.SendMessage(MSG_TERM_IS_MINIMIZE, &reply) != B_OK)
return true;
bool hidden;
reply.FindBool("result", &hidden);
return hidden;
}
void
TermApp::_UpdateRegistration(bool set)
{
if (set)
fWindowNumber = -1;
else if (fWindowNumber < 0)
return;
int fd = open("/tmp/terminal_ids", O_RDWR | O_CREAT);
if (fd < 0)
return;
struct flock lock;
lock.l_type = F_WRLCK;
lock.l_whence = SEEK_CUR;
lock.l_start = 0;
lock.l_len = -1;
fcntl(fd, F_SETLKW, &lock);
uint8 windows[256];
ssize_t length = read_pos(fd, 0, windows, sizeof(windows));
if (length < 0) {
close(fd);
return;
}
if (length > (ssize_t)sizeof(windows))
length = sizeof(windows);
if (set) {
int32 i;
for (i = 0; i < length; i++) {
if (!windows[i]) {
windows[i] = true;
fWindowNumber = i + 1;
break;
}
}
if (i == length) {
if (length >= (ssize_t)sizeof(windows)) {
close(fd);
return;
}
windows[length] = true;
length++;
fWindowNumber = length;
}
} else {
// update information and write it back
windows[fWindowNumber - 1] = false;
}
write_pos(fd, 0, windows, length);
close(fd);
}
void
TermApp::_UnregisterTerminal()
{
_UpdateRegistration(false);
}
void
TermApp::_RegisterTerminal()
{
_UpdateRegistration(true);
}
//#ifndef B_NETPOSITIVE_APP_SIGNATURE
//#define B_NETPOSITIVE_APP_SIGNATURE "application/x-vnd.Be-NPOS"
//#endif
//
//void
//TermApp::ShowHTML(BMessage *msg)
//{
// const char *url;
// msg->FindString("Url", &url);
// BMessage message;
//
// message.what = B_NETPOSITIVE_OPEN_URL;
// message.AddString("be:url", url);
// be_roster->Launch(B_NETPOSITIVE_APP_SIGNATURE, &message);
// while(!(be_roster->IsRunning(B_NETPOSITIVE_APP_SIGNATURE)))
// snooze(10000);
//
// // Activate net+
// be_roster->ActivateApp(be_roster->TeamFor(B_NETPOSITIVE_APP_SIGNATURE));
//}
void
TermApp::_Usage(char *name)
{
fprintf(stderr, "Haiku Terminal\n"
"Copyright 2001-2007 Haiku, Inc.\n"
"Copyright(C) 1999 Kazuho Okui and Takashi Murai.\n"
"\n"
"Usage: %s [OPTION] [SHELL]\n", name);
fprintf(stderr, " -p, --preference load preference file\n"
" -t, --title set window title\n"
" -geom, --geometry set window geometry\n"
" An example of geometry is \"80x25+100+100\"\n");
#if 0
fprintf(stderr, " -fg, --text-fore-color set window foreground color\n"
" -bg, --text-back-color set window background color\n"
" -curfg, --cursor-fore-color set cursor foreground color\n"
" -curbg, --cursor-back-color set cursor background color\n"
" -selfg, --select-fore-color set selection area foreground color\n"
" -selbg, --select-back-color set selection area background color\n"
" Examples of color are \"#FF00FF\" and \"purple\"\n");
#endif
}
int
text_to_rgb(char *name, rgb_color *color, char *buffer)
{
if (name[0] != '#') {
// Convert from /etc/rgb.txt.
BString inStr(buffer);
int32 point, offset = 0;
// Search color name
do {
point = inStr.FindFirst(name, offset);
if (point < 0)
return false;
offset = point + 1;
} while(*(buffer + point -1) != '\t');
char *p = buffer + point;
while (*p != '\n')
p--;
p++;
if (sscanf(p, "%d %d %d", (int *)&color->red, (int *)&color->green,
(int *)&color->blue) == EOF)
return false;
color->alpha = 0;
} else if (name[0] == '#') {
// Convert from #RRGGBB format
sscanf(name, "#%2x%2x%2x", (int *)&color->red, (int *)&color->green,
(int *)&color->blue);
color->alpha = 0;
} else
return false;
return true;
}
// This routine copy from GNU Emacs.
// TODO: This might be a GPL licensing issue here. Investigate.
int
argmatch(char **argv, int argc, char *sstr, char *lstr,
int minlen, char **valptr, int *skipptr)
{
char *p = 0;
int arglen;
char *arg;
// Don't access argv[argc]; give up in advance
if (argc <= *skipptr + 1)
return 0;
arg = argv[*skipptr+1];
if (arg == NULL)
return 0;
if (strcmp(arg, sstr) == 0) {
if(valptr != NULL) {
*valptr = argv[*skipptr+2];
*skipptr += 2;
} else
*skipptr += 1;
return 1;
}
arglen =(valptr != NULL &&(p = strchr(arg, '=')) != NULL
? p - arg : strlen(arg));
if(lstr == 0 || arglen < minlen || strncmp(arg, lstr, arglen) != 0)
return 0;
else
if(valptr == NULL)
{
*skipptr += 1;
return 1;
}
else
if(p != NULL)
{
*valptr = p+1;
*skipptr += 1;
return 1;
}
else
if(argv[*skipptr+2] != NULL)
{
*valptr = argv[*skipptr+2];
*skipptr += 2;
return 1;
}
else
{
return 0;
}
}
// This routine copy from GNU Emacs.
// TODO: This might be a GPL licensing issue here. Investigate.
void
sort_args(int argc, char **argv)
{
/*
For each element of argv,
the corresponding element of options is:
0 for an option that takes no arguments,
1 for an option that takes one argument, etc.
-1 for an ordinary non-option argument.
*/
char **newargv =(char **) malloc(sizeof(char *) * argc);
int *options =(int *) malloc(sizeof(int) * argc);
int *priority =(int *) malloc(sizeof(int) * argc);
int to = 1;
int incoming_used = 1;
int from;
int i;
//int end_of_options = argc;
// Categorize all the options,
// and figure out which argv elts are option arguments
for(from = 1; from < argc; from++)
{
options[from] = -1;
priority[from] = 0;
if(argv[from][0] == '-')
{
int match, thislen;
char *equals;
// If we have found "--", don't consider any more arguments as options
if(argv[from][1] == '-' && argv[from][2] == 0)
{
// Leave the "--", and everything following it, at the end.
for(; from < argc; from++)
{
priority[from] = -100;
options[from] = -1;
}
break;
}
// Look for a match with a known old-fashioned option.
for(i = 0; i <(int)(sizeof(standard_args) / sizeof(standard_args[0])); i++)
if(!strcmp(argv[from], standard_args[i].name))
{
options[from] = standard_args[i].nargs;
priority[from] = standard_args[i].priority;
if(from + standard_args[i].nargs >= argc)
fprintf(stderr, "Option `%s' requires an argument\n", argv[from]);
from += standard_args[i].nargs;
goto done;
}
/*
Look for a match with a known long option.
MATCH is -1 if no match so far, -2 if two or more matches so far,
>= 0(the table index of the match) if just one match so far.
*/
if(argv[from][1] == '-')
{
match = -1;
thislen = strlen(argv[from]);
equals = strchr(argv[from], '=');
if(equals != 0)
thislen = equals - argv[from];
for(i = 0;i <(int )(sizeof(standard_args) / sizeof(standard_args[0])); i++)
if(standard_args[i].longname
&& !strncmp(argv[from], standard_args[i].longname, thislen))
{
if(match == -1)
match = i;
else
match = -2;
}
// If we found exactly one match, use that
if(match >= 0)
{
options[from] = standard_args[match].nargs;
priority[from] = standard_args[match].priority;
// If --OPTION=VALUE syntax is used,
// this option uses just one argv element
if(equals != 0)
options[from] = 0;
if(from + options[from] >= argc)
fprintf(stderr, "Option `%s' requires an argument\n", argv[from]);
from += options[from];
}
}
done: ;
}
}
// Copy the arguments, in order of decreasing priority, to NEW
newargv[0] = argv[0];
while (incoming_used < argc) {
int best = -1;
int best_priority = -9999;
// Find the highest priority remaining option.
// If several have equal priority, take the first of them.
for (from = 1; from < argc; from++) {
if (argv[from] != 0 && priority[from] > best_priority) {
best_priority = priority[from];
best = from;
}
// Skip option arguments--they are tied to the options.
if (options[from] > 0)
from += options[from];
}
if (best < 0)
abort();
// Copy the highest priority remaining option, with its args, to NEW.
// Unless it is a duplicate of the previous one
if (!(options[best] == 0 && ! strcmp(newargv[to - 1], argv[best]))) {
newargv[to++] = argv[best];
for(i = 0; i < options[best]; i++)
newargv[to++] = argv[best + i + 1];
}
incoming_used += 1 +(options[best] > 0 ? options[best] : 0);
// Clear out this option in ARGV
argv[best] = 0;
for (i = 0; i < options[best]; i++)
argv[best + i + 1] = 0;
}
// If duplicate options were deleted, fill up extra space with null ptrs
while (to < argc)
newargv[to++] = 0;
memcpy(argv, newargv, sizeof(char *) * argc);
free(options);
free(newargv);
free(priority);
}