598 lines
15 KiB
C
598 lines
15 KiB
C
/* This file is part of the Project Athena Zephyr Notification System.
|
|
* It is one of the source files comprising zwgc, the Zephyr WindowGram
|
|
* client.
|
|
*
|
|
* Created by: Marc Horowitz <marc@athena.mit.edu>
|
|
*
|
|
* $Id$
|
|
*
|
|
* Copyright (c) 1989 by the Massachusetts Institute of Technology.
|
|
* For copying and distribution information, see the file
|
|
* "mit-copyright.h".
|
|
*/
|
|
|
|
#include <sysdep.h>
|
|
|
|
#if (!defined(lint) && !defined(SABER))
|
|
static const char rcsid_tty_filter_c[] = "$Id$";
|
|
#endif
|
|
|
|
#include <zephyr/mit-copyright.h>
|
|
|
|
/****************************************************************************/
|
|
/* */
|
|
/* The tty & plain filters: */
|
|
/* */
|
|
/****************************************************************************/
|
|
#ifdef HAVE_TERMCAP_H
|
|
#include <termcap.h>
|
|
#else
|
|
#ifdef HAVE_TERM_H
|
|
#ifdef HAVE_TERMIO_H
|
|
/* I blame Solaris. Solaris to blame. */
|
|
#include <termio.h>
|
|
#endif
|
|
#ifdef HAVE_CURSES_H
|
|
#include <curses.h>
|
|
#endif
|
|
#include <term.h>
|
|
#endif
|
|
#endif
|
|
|
|
#include <zephyr/zephyr.h>
|
|
|
|
#include "new_memory.h"
|
|
#include "new_string.h"
|
|
#include "variables.h"
|
|
#include "string_dictionary_aux.h"
|
|
#include "formatter.h"
|
|
#include "zwgc.h"
|
|
#include "error.h"
|
|
#include "tty_filter.h"
|
|
|
|
/***************************************************************************/
|
|
#ifndef HAVE_TERMCAP_H
|
|
extern int tgetent();
|
|
extern char *tgetstr(),*getenv();
|
|
#ifdef linux
|
|
extern speed_t ospeed;
|
|
#else
|
|
extern short ospeed;
|
|
#endif
|
|
char PC;
|
|
#endif
|
|
|
|
/* Dictionary naming convention:
|
|
|
|
B.xxx is the termcap sequence to begin environment xxx.
|
|
E.xxx is the termcap sequence to end environment xxx.
|
|
|
|
*/
|
|
|
|
static string_dictionary termcap_dict;
|
|
static char code_buf[10240], *code_buf_pos = code_buf, *code;
|
|
|
|
/* Define the following commands:
|
|
|
|
(Hopefully) shared with all devices:
|
|
|
|
@center Guess.
|
|
|
|
@em Emphasis. User underline if available, else reverse video.
|
|
@bold Bold letters. If not available, reverse video, else underline.
|
|
@beep "bl" termcap entry, else "^G"
|
|
|
|
Other:
|
|
|
|
@blink "mb"/"me" termcap entry, else nothing.
|
|
@rv "so"/"se" termcap entry.
|
|
@u "us"/"ue" termcap entry.
|
|
*/
|
|
|
|
#define TD_SET(k,v) (string_dictionary_Define(termcap_dict, (k), &ex)->value \
|
|
= (v))
|
|
#define EXPAND(k) (code = code_buf_pos, tputs(tmp, 1, tty_outc), \
|
|
*code_buf_pos++ = 0, TD_SET(k, code))
|
|
|
|
static int
|
|
tty_outc(int c)
|
|
{
|
|
*code_buf_pos++ = c;
|
|
return 0;
|
|
}
|
|
|
|
/* ARGSUSED */
|
|
int
|
|
tty_filter_init(char *drivername,
|
|
char notfirst,
|
|
int *pargc,
|
|
char **argv)
|
|
{
|
|
static char st_buf[128];
|
|
char tc_buf[1024], *p = st_buf, *tmp, *term;
|
|
int ex;
|
|
string_dictionary_binding *b;
|
|
int isrealtty = string_Eq(drivername, "tty");
|
|
struct termios tbuf;
|
|
|
|
ospeed = (tcgetattr(STDIN_FILENO, &tbuf) == 0) ? cfgetospeed(&tbuf) : 2400;
|
|
|
|
if (termcap_dict == (string_dictionary) NULL)
|
|
termcap_dict = string_dictionary_Create(7);
|
|
|
|
if (!(term = getenv("TERM"))) { /* Only use termcap if $TERM. */
|
|
if (isrealtty && !notfirst)
|
|
/* only complain if initializing tty mode, and would be first
|
|
available port */
|
|
ERROR("$TERM not set. tty mode will be plain.\n");
|
|
}
|
|
#ifdef _AIX
|
|
/*
|
|
* This is a temporary KLUDGE to get around the problem where some people
|
|
* might start zwgc in their ~/.startup.X and it hangs on the RISC/6000.
|
|
* Apparently, the call to tgetent() with the Athena console window causes
|
|
* the process to get stopped on tty access. Since the terminal type is
|
|
* "dumb" (set by tcsh), we can pretty much assume there isn't anything
|
|
* to setup from the termcap information.
|
|
*/
|
|
else if (!strcmp(term, "dumb")) { }
|
|
#endif
|
|
else {
|
|
tgetent(tc_buf, term);
|
|
|
|
/* Step 1: get all of {rv,bold,u,bell,blink} that are available. */
|
|
|
|
/* We cheat here, and ignore the padding (if any) specified for
|
|
the mode-change strings (it's a real pain to do "right") */
|
|
|
|
tmp = tgetstr("pc", &p);
|
|
PC = (tmp) ? *tmp : 0;
|
|
tmp = tgetstr("md", &p);
|
|
if (tmp) { /* bold ? */
|
|
EXPAND("B.bold");
|
|
tmp = tgetstr("me",&p);
|
|
EXPAND("E.bold");
|
|
}
|
|
tmp = tgetstr("mr", &p);
|
|
if (tmp) { /* reverse video? */
|
|
EXPAND("B.rw");
|
|
tmp = tgetstr("me", &p);
|
|
EXPAND("E.rw");
|
|
}
|
|
tmp = tgetstr("bl", &p);
|
|
if (tmp) { /* Bell ? */
|
|
EXPAND("B.bell");
|
|
TD_SET("E.bell", NULL);
|
|
}
|
|
tmp = tgetstr("mb", &p);
|
|
if (tmp) { /* Blink ? */
|
|
EXPAND("B.blink");
|
|
tmp = tgetstr("me", &p);
|
|
EXPAND("E.blink");
|
|
}
|
|
tmp = tgetstr("us", &p);
|
|
if (tmp) { /* Underline ? */
|
|
EXPAND("B.u");
|
|
tmp = tgetstr("ue", &p);
|
|
EXPAND("E.u");
|
|
}
|
|
tmp = tgetstr("so", &p);
|
|
if (tmp) { /* Standout ? */
|
|
EXPAND("B.so");
|
|
tmp = tgetstr("se", &p);
|
|
EXPAND("E.so");
|
|
}
|
|
}
|
|
/* Step 2: alias others to the nearest substitute */
|
|
|
|
/* Bold = so, else rv, else ul */
|
|
if (NULL == string_dictionary_Lookup(termcap_dict, "B.bold")) {
|
|
if((b = string_dictionary_Lookup(termcap_dict, "B.so"))) {
|
|
TD_SET("B.bold", b->value);
|
|
TD_SET("E.bold",
|
|
string_dictionary_Lookup(termcap_dict, "E.so")->value);
|
|
} else if ((b = string_dictionary_Lookup(termcap_dict, "B.rv"))) {
|
|
TD_SET("B.bold", b->value);
|
|
TD_SET("E.bold",
|
|
string_dictionary_Lookup(termcap_dict, "E.rv")->value);
|
|
} else if ((b = string_dictionary_Lookup(termcap_dict,"B.u"))) {
|
|
TD_SET("B.bold", b->value);
|
|
TD_SET("E.bold",
|
|
string_dictionary_Lookup(termcap_dict, "E.u")->value);
|
|
}
|
|
}
|
|
|
|
/* Bell = ^G */
|
|
if (NULL == string_dictionary_Lookup(termcap_dict, "B.bell")) {
|
|
TD_SET("B.bell", "\007");
|
|
TD_SET("E.bell", NULL);
|
|
}
|
|
|
|
/* Underline -> nothing */
|
|
/* Blink -> nothing */
|
|
|
|
return(0);
|
|
}
|
|
|
|
/***************************************************************************/
|
|
|
|
|
|
|
|
|
|
static int
|
|
fixed_string_eq(string pattern,
|
|
char *text,
|
|
int text_length)
|
|
{
|
|
while (*pattern && text_length>0 && *pattern == *text) {
|
|
pattern++;
|
|
text++;
|
|
text_length--;
|
|
}
|
|
|
|
return(!*pattern && !text_length);
|
|
}
|
|
|
|
typedef struct _tty_str_info {
|
|
struct _tty_str_info *next;
|
|
|
|
char *str;
|
|
int len;
|
|
|
|
char alignment; /* 'l', 'c', 'r', or ' ' to indicate newline */
|
|
unsigned int bold_p : 1;
|
|
unsigned int italic_p : 1;
|
|
unsigned int bell_p : 1;
|
|
unsigned int ignore: 1;
|
|
} tty_str_info;
|
|
|
|
const char *info_default_string = "";
|
|
|
|
static void
|
|
free_info(tty_str_info *info)
|
|
{
|
|
tty_str_info *next_info;
|
|
|
|
while (info) {
|
|
next_info = info->next;
|
|
if (info->str != info_default_string)
|
|
free(info->str);
|
|
free(info);
|
|
info = next_info;
|
|
}
|
|
}
|
|
|
|
static int
|
|
do_mode_change(tty_str_info *current_mode_p,
|
|
char *text,
|
|
int text_length)
|
|
{
|
|
/* alignment commands: */
|
|
if (fixed_string_eq("left", text, text_length) ||
|
|
fixed_string_eq("l", text, text_length))
|
|
current_mode_p->alignment = 'l';
|
|
else if (fixed_string_eq("center", text, text_length) ||
|
|
fixed_string_eq("c", text, text_length))
|
|
current_mode_p->alignment = 'c';
|
|
else if (fixed_string_eq("right", text, text_length) ||
|
|
fixed_string_eq("r", text, text_length))
|
|
current_mode_p->alignment = 'r';
|
|
|
|
/* font commands: */
|
|
else if (fixed_string_eq("bold", text, text_length) ||
|
|
fixed_string_eq("b", text, text_length))
|
|
current_mode_p->bold_p = 1;
|
|
else if (fixed_string_eq("italic", text, text_length) ||
|
|
fixed_string_eq("i", text, text_length))
|
|
current_mode_p->italic_p = 1;
|
|
else if (fixed_string_eq("roman", text, text_length)) {
|
|
current_mode_p->bold_p = 0;
|
|
current_mode_p->italic_p = 0;
|
|
} else if (fixed_string_eq("beep", text, text_length)) {
|
|
current_mode_p->bell_p = 1;
|
|
return 1;
|
|
}
|
|
|
|
/* commands ignored in tty mode: */
|
|
else if (fixed_string_eq("color", text, text_length) ||
|
|
fixed_string_eq("font", text, text_length)) {
|
|
current_mode_p->ignore = 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
zwgc_transliterate(char *in, int inlen, char **out, int *outlen){
|
|
int retval = 0;
|
|
string notice_charset = var_get_variable("notice_charset");
|
|
string tty_charset = var_get_variable("tty_charset");
|
|
|
|
if (string_Eq(notice_charset, "UNKNOWN") ||
|
|
string_Eq(notice_charset, tty_charset) ||
|
|
(retval = ZTransliterate(in, inlen,
|
|
notice_charset, tty_charset,
|
|
out, outlen)) != 0) {
|
|
*out = string_CreateFromData(in, inlen);
|
|
*outlen = inlen;
|
|
}
|
|
if (retval != 0)
|
|
var_set_variable("error", strerror(retval));
|
|
}
|
|
|
|
static tty_str_info *
|
|
convert_desc_to_tty_str_info(desctype *desc)
|
|
{
|
|
tty_str_info *temp;
|
|
tty_str_info *result = NULL;
|
|
tty_str_info *last_result_block = NULL;
|
|
int isbeep, did_beep = 0;
|
|
|
|
#if !defined(SABER) && defined(__STDC__)
|
|
tty_str_info current_mode = { NULL, "", 0, 'l', 0 , 0, 0, 0};
|
|
#else
|
|
/* This is needed due to a bug in saber, and lack of pre-ANSI support. */
|
|
tty_str_info current_mode;
|
|
|
|
current_mode.next = NULL;
|
|
current_mode.str = info_default_string; /* "" */
|
|
current_mode.len = 0;
|
|
current_mode.alignment = 'l';
|
|
current_mode.bold_p = 0;
|
|
current_mode.italic_p = 0;
|
|
current_mode.bell_p = 0;
|
|
current_mode.ignore = 0;
|
|
#endif
|
|
|
|
for (; desc->code!=DT_EOF; desc=desc->next) {
|
|
isbeep = 0;
|
|
/* Handle environments: */
|
|
if (desc->code == DT_ENV) {
|
|
/* PUSH! */
|
|
temp = (tty_str_info *)malloc(sizeof(struct _tty_str_info));
|
|
*temp = current_mode;
|
|
current_mode.next = temp;
|
|
|
|
isbeep = do_mode_change(¤t_mode, desc->str, desc->len);
|
|
if (!isbeep || did_beep)
|
|
continue; /* process one beep, ignore other envs */
|
|
} else if (desc->code == DT_END) {
|
|
/* POP! */
|
|
temp = current_mode.next;
|
|
current_mode = *temp;
|
|
free(temp);
|
|
continue;
|
|
}
|
|
|
|
/* Add new block (call it temp) to result: */
|
|
temp = (tty_str_info *)malloc(sizeof(struct _tty_str_info));
|
|
*temp = current_mode;
|
|
if (last_result_block) {
|
|
last_result_block->next = temp;
|
|
last_result_block = temp;
|
|
} else {
|
|
result = temp;
|
|
last_result_block = temp;
|
|
}
|
|
|
|
if (isbeep) {
|
|
/* special processing: need to insert a bell */
|
|
string_dictionary_binding *b;
|
|
b = string_dictionary_Lookup(termcap_dict,"B.bell");
|
|
if (b) {
|
|
temp->str = strdup(b->value);
|
|
temp->len = string_Length(temp->str);
|
|
} else
|
|
/* shouldn't get here! */
|
|
abort();
|
|
did_beep++;
|
|
continue;
|
|
}
|
|
if (desc->code == DT_STR) {
|
|
/* just combine string info with current mode: */
|
|
zwgc_transliterate(desc->str, desc->len, &temp->str, &temp->len);
|
|
} else if (desc->code == DT_NL) {
|
|
/* make the new block a ' ' alignment block with an empty string */
|
|
temp->alignment = ' ';
|
|
temp->len = 0;
|
|
temp->ignore = 0;
|
|
}
|
|
}
|
|
|
|
if (last_result_block)
|
|
last_result_block->next = NULL;
|
|
|
|
return(result);
|
|
}
|
|
|
|
#define max(a,b) ((a)>(b)?(a):(b))
|
|
|
|
static int
|
|
line_width(int left_width,
|
|
int center_width,
|
|
int right_width)
|
|
{
|
|
if (center_width>0) {
|
|
if (left_width==0 && right_width==0)
|
|
return(center_width);
|
|
return(center_width+2+max(left_width,right_width)*2);
|
|
} else {
|
|
if (left_width && right_width)
|
|
return(1+left_width+right_width);
|
|
else
|
|
return(left_width+right_width);
|
|
}
|
|
}
|
|
|
|
static int
|
|
calc_max_line_width(tty_str_info *info)
|
|
{
|
|
int max_line_width = 0;
|
|
int left = 0;
|
|
int center = 0;
|
|
int right = 0;
|
|
|
|
for (; info; info=info->next) {
|
|
if (info->ignore)
|
|
continue;
|
|
switch (info->alignment) {
|
|
case 'l':
|
|
left += info->len;
|
|
break;
|
|
|
|
case 'c':
|
|
center += info->len;
|
|
break;
|
|
|
|
case 'r':
|
|
right += info->len;
|
|
break;
|
|
|
|
case ' ':
|
|
#ifdef DEBUG
|
|
if (zwgc_debug)
|
|
printf("width: %d %d %d = %d\n", left, center, right,
|
|
line_width(left, center, right));
|
|
#endif
|
|
max_line_width = max(max_line_width,
|
|
line_width(left, center, right));
|
|
left = center = right = 0;
|
|
break;
|
|
}
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
if (zwgc_debug)
|
|
printf("width: %d %d %d = %d\n", left, center, right,
|
|
line_width(left, center, right));
|
|
#endif
|
|
max_line_width = max(max_line_width,
|
|
line_width(left, center, right));
|
|
|
|
return(max_line_width);
|
|
}
|
|
|
|
string
|
|
tty_filter(string text,
|
|
int use_fonts)
|
|
{
|
|
string text_copy = string_Copy(text);
|
|
string result_so_far = string_Copy("");
|
|
desctype *desc;
|
|
int number_of_strs;
|
|
int number_of_lines;
|
|
tty_str_info *info, *info_head;
|
|
int max_line_width;
|
|
|
|
desc = disp_get_cmds(text_copy, &number_of_strs, &number_of_lines);
|
|
info_head = info = convert_desc_to_tty_str_info(desc);
|
|
free_desc(desc);
|
|
|
|
#ifdef DEBUG
|
|
if (zwgc_debug)
|
|
{ tty_str_info *ptr;
|
|
for (ptr=info; ptr; ptr=ptr->next) {
|
|
printf("%c: %s %s %s <%s>\n", ptr->alignment,
|
|
ptr->bold_p ? "(bold)" : "",
|
|
ptr->italic_p ? "(italic)" : "",
|
|
ptr->bell_p ? "(bell)" : "",
|
|
string_CreateFromData(ptr->str, ptr->len));
|
|
}
|
|
}
|
|
#endif
|
|
|
|
max_line_width = calc_max_line_width(info);
|
|
dprintf1("max width = %d\n", max_line_width);
|
|
|
|
while (info) {
|
|
string left, center, right;
|
|
int left_width, center_width, right_width;
|
|
char *temp;
|
|
|
|
left_width = center_width = right_width = 0;
|
|
left = string_Copy("");
|
|
center = string_Copy("");
|
|
right = string_Copy("");
|
|
|
|
for (; info && info->alignment!=' '; info=info->next) {
|
|
string item;
|
|
|
|
if (info->ignore)
|
|
continue;
|
|
|
|
item = string_Copy("");
|
|
|
|
if (info->bold_p && use_fonts) {
|
|
temp = string_dictionary_Fetch(termcap_dict, "B.bold");
|
|
if (temp)
|
|
item = string_Concat2(item, temp);
|
|
} else if (info->italic_p && use_fonts) {
|
|
temp = string_dictionary_Fetch(termcap_dict, "B.u");
|
|
if (temp)
|
|
item = string_Concat2(item, temp);
|
|
}
|
|
temp = string_CreateFromData(info->str, info->len);
|
|
item = string_Concat2(item, temp);
|
|
free(temp);
|
|
|
|
if (info->bold_p && use_fonts) {
|
|
temp = string_dictionary_Fetch(termcap_dict, "E.bold");
|
|
if (temp)
|
|
item = string_Concat2(item, temp);
|
|
} else if (info->italic_p && use_fonts) {
|
|
temp = string_dictionary_Fetch(termcap_dict, "E.u");
|
|
if (temp)
|
|
item = string_Concat2(item, temp);
|
|
}
|
|
|
|
switch (info->alignment) {
|
|
default:
|
|
case 'l':
|
|
left = string_Concat2(left, item);
|
|
left_width += info->len;
|
|
break;
|
|
|
|
case 'c':
|
|
center = string_Concat2(center, item);
|
|
center_width += info->len;
|
|
break;
|
|
|
|
case 'r':
|
|
right = string_Concat2(right, item);
|
|
right_width += info->len;
|
|
break;
|
|
}
|
|
free(item);
|
|
}
|
|
|
|
result_so_far = string_Concat2(result_so_far, left);
|
|
if (center_width)
|
|
while (left_width < (max_line_width-center_width)/2 ) {
|
|
result_so_far = string_Concat2(result_so_far, " ");
|
|
left_width++;
|
|
}
|
|
result_so_far = string_Concat2(result_so_far, center);
|
|
left_width += center_width;
|
|
|
|
if (right_width)
|
|
while (left_width<max_line_width-right_width) {
|
|
result_so_far = string_Concat2(result_so_far, " ");
|
|
left_width++;
|
|
}
|
|
result_so_far = string_Concat2(result_so_far, right);
|
|
free(left); free(center); free(right);
|
|
|
|
if (info && info->alignment == ' ') {
|
|
info = info->next;
|
|
result_so_far = string_Concat2(result_so_far, "\r\n");
|
|
}
|
|
}
|
|
|
|
free_info(info_head);
|
|
free(text_copy);
|
|
if (number_of_lines &&
|
|
(result_so_far[string_Length(result_so_far)-1] != '\n'))
|
|
/* CRLF-terminate all results */
|
|
result_so_far = string_Concat2(result_so_far, "\r\n");
|
|
return(result_so_far);
|
|
}
|