enum character {
CTRLC = '\003',
CTRLD,
+ BACKSPACE = '\010',
CLEAR = '\014',
ESCAPE = '\033',
FORWARD = 'f',
DOWN,
RIGHT,
LEFT,
- BACKSPACE = '\177',
+ DEL = '\177',
};
char buffer[BUFLEN + 2]; // Terminating ";"
char *input(void) {
char *cursor, *end;
- int c, i;
+ unsigned int c;
+ int i;
signal(SIGCHLD, waitbg); // TODO: Use sigaction for portability
history.c = history.t;
while (buffer == end) {
fputs(PROMPT, stdout);
- while ((c = getchar()) != '\n') {
- switch (c) {
- default:
- if (c >= ' ' && c <= '~') {
- if (end - buffer == BUFLEN) continue;
- memmove(cursor + 1, cursor, end - cursor);
- *cursor++ = c;
- *++end = '\0';
-
- putchar(c);
- fputs(cursor, stdout);
- for (i = end - cursor; i > 0; --i) putchar('\b');
- }
+ while ((c = getchar()) != '\r') switch (c) {
+ default:
+ if (c >= ' ' && c <= '~') {
+ if (end - buffer == BUFLEN) continue;
+ memmove(cursor + 1, cursor, end - cursor);
+ *cursor++ = c;
+ *++end = '\0';
+
+ putchar(c);
+ fputs(cursor, stdout);
+ for (i = end - cursor; i > 0; --i) putchar('\b');
+ }
+ break;
+ case CTRLC:
+ puts("^C\r");
+ *buffer = '\0';
+ return buffer;
+ case CTRLD:
+ puts("^D\r");
+ signal(SIGCHLD, SIG_DFL);
+ return NULL;
+ case CLEAR:
+ fputs("\033[H\033[J", stdout);
+ fputs(PROMPT, stdout);
+ fputs(buffer, stdout);
+ continue;
+ case ESCAPE:
+ switch ((c = getchar())) {
+ case FORWARD:
+ while (cursor != end && *cursor != ' ') putchar(*cursor++);
+ while (cursor != end && *cursor == ' ') putchar(*cursor++);
break;
- case CTRLC:
- puts("^C");
- *buffer = '\0';
- return buffer;
- case CTRLD:
- puts("^D");
- signal(SIGCHLD, SIG_DFL);
- return NULL;
- case CLEAR:
- fputs("\033[H\033[J", stdout);
- fputs(PROMPT, stdout);
- fputs(buffer, stdout);
- continue;
- case ESCAPE:
+ case BACKWARD:
+ while (cursor != buffer && *(cursor - 1) == ' ') putchar((--cursor, '\b'));
+ while (cursor != buffer && *(cursor - 1) != ' ') putchar((--cursor, '\b'));
+ break;
+ case ARROW:
switch ((c = getchar())) {
- case FORWARD:
- while (cursor != end && *cursor != ' ') putchar(*cursor++);
- while (cursor != end && *cursor == ' ') putchar(*cursor++);
+ case UP:
+ case DOWN:
+ if (history.c == (c == UP ? history.b : history.t)) continue;
+
+ putchar('\r');
+ for (i = end - buffer + strlen(PROMPT); i > 0; --i) putchar(' ');
+ putchar('\r');
+
+ if (strcmp(history.c, buffer) != 0) strcpy(history.t, buffer);
+ if (c == UP) DEC(history, c); else INC(history, c);
+ strcpy(buffer, history.c);
+ end = cursor = buffer + strlen(buffer);
+
+ fputs(PROMPT, stdout);
+ fputs(buffer, stdout);
break;
- case BACKWARD:
- while (cursor != buffer && *(cursor - 1) == ' ') putchar((--cursor, '\b'));
- while (cursor != buffer && *(cursor - 1) != ' ') putchar((--cursor, '\b'));
+ case LEFT:
+ if (cursor > buffer) putchar((--cursor, '\b'));
break;
- case ARROW:
- switch ((c = getchar())) {
- case UP:
- case DOWN:
- if (history.c == (c == UP ? history.b : history.t)) continue;
-
- putchar('\r');
- for (i = end - buffer + strlen(PROMPT); i > 0; --i) putchar(' ');
- putchar('\r');
-
- if (strcmp(history.c, buffer) != 0) strcpy(history.t, buffer);
- if (c == UP) DEC(history, c); else INC(history, c);
- strcpy(buffer, history.c);
- end = cursor = buffer + strlen(buffer);
-
- fputs(PROMPT, stdout);
- fputs(buffer, stdout);
- break;
- case LEFT:
- if (cursor > buffer) putchar((--cursor, '\b'));
- break;
- case RIGHT:
- if (cursor < end) putchar(*cursor++);
- break;
- }
+ case RIGHT:
+ if (cursor < end) putchar(*cursor++);
break;
- default:
- ungetc(c, stdin);
}
break;
- case BACKSPACE:
- if (cursor == buffer) continue;
- memmove(cursor - 1, cursor, end - cursor);
- --cursor;
- *--end = '\0';
-
- putchar('\b');
- fputs(cursor, stdout);
- putchar(' ');
- for (i = end - cursor + 1; i > 0; --i) putchar('\b');
-
- break;
+ default:
+ ungetc(c, stdin);
}
+ break;
+ case BACKSPACE:
+ case DEL:
+ if (cursor == buffer) continue;
+ memmove(cursor - 1, cursor, end - cursor);
+ --cursor;
+ *--end = '\0';
+
+ putchar('\b');
+ fputs(cursor, stdout);
+ putchar(' ');
+ for (i = end - cursor + 1; i > 0; --i) putchar('\b');
+
+ break;
}
+ puts("\r");
}
fpurge(stdout);
push(&history, buffer);
#include "stack.h"
struct termios raw, canonical;
-static pid_t self;
-static int tcsetraw(void) {
- if (tcsetattr(STDIN_FILENO, TCSANOW, &raw) == -1) {
- warn("Unable to set termios state to raw mode");
+static int setconfig(struct termios *mode) {
+ if (tcsetattr(STDIN_FILENO, TCSANOW, mode) == -1) {
+ warn("Unable to set termios config");
return 0;
}
-
return 1;
}
void initterm(void) {
+ cfmakeraw(&raw);
if (tcgetattr(STDIN_FILENO, &canonical) == -1)
- err(EXIT_FAILURE, "Unable to get termios structure");
- raw = canonical;
- raw.c_lflag &= ~ICANON & ~ECHO & ~ISIG;
- raw.c_lflag |= ECHONL;
- if (!tcsetraw()) exit(EXIT_FAILURE);
-
- if ((self = getpgid(0)) == -1) err(EXIT_FAILURE, "Unable to get pgid of self");
+ err(EXIT_FAILURE, "Unable to get default termios config");
+ if (!setconfig(&raw)) exit(EXIT_FAILURE);
}
void deinitterm(void) {
- if (tcsetattr(STDIN_FILENO, TCSANOW, &canonical) == -1)
- warn("Unable to return to initial terminal settings");
-}
-
-static void tcsetself(void) {
- if (signal(SIGTTOU, SIG_IGN) == SIG_ERR) {
- warn("Unable to ignore SIGTTOU signal");
- goto quit;
- }
- if (tcsetpgrp(STDIN_FILENO, self) == -1) {
- warn("Unable to set self as foreground process; exiting");
- goto quit;
- }
- if (signal(SIGTTOU, SIG_DFL) == SIG_ERR) warn("Ignoring signal SIGTTOU");
-
- return;
-
-quit:
- writehist();
- deinitterm();
- exit(EXIT_FAILURE);
-}
-
-static void sigkill(pid_t jobid) {
- if (killpg(jobid, SIGKILL) == -1)
- warn("Unable to kill process group %d; manual termination may be required",
- jobid);
+ setconfig(&canonical);
}
int setfg(struct job job) {
- if (tcsetattr(STDIN_FILENO, TCSANOW, &job.config) == -1)
- warn("Unable to set termios structure");
+ if (!setconfig(&job.config)) return 0;
if (tcsetpgrp(STDIN_FILENO, job.id) == -1) {
- warn("Unable to bring process group %d to foreground", job.id);
- goto setraw;
+ warn("Unable to bring job %d to foreground\r", job.id);
+ setconfig(&raw);
+ return 0;
}
if (killpg(job.id, SIGCONT) == -1) {
- warn("Unable to wake up suspended process group %d", job.id);
- goto setself;
+ warn("Unable to wake up job %d\r", job.id);
+ return 0;
}
-
return 1;
-
-setself:
- tcsetself();
-
-setraw:
- tcsetraw();
-
- sigkill(job.id);
-
- return 0;
}
int waitfg(struct job job) {
- int status, result;
-
-wait:
- if (waitpid(job.id, &status, WUNTRACED) != -1 && !WIFSTOPPED(status)) {
- while (waitpid(-job.id, NULL, 0) != -1);
- if (errno != ECHILD && killpg(job.id, SIGKILL) == -1)
- err(EXIT_FAILURE, "Unable to kill child process group %d", job.id);
+ int status, pgid, result;
+
+ errno = 0;
+ do waitpid(job.id, &status, WUNTRACED); while (errno == EINTR);
+ if (!errno && !WIFSTOPPED(status))
+ do while (waitpid(-job.id, NULL, 0) != -1); while (errno == EINTR);
+ result = errno;
+
+ // TODO: Use sigaction >:(
+ if ((pgid = getpgid(0)) == -1 || signal(SIGTTOU, SIG_IGN) == SIG_ERR
+ || tcsetpgrp(STDIN_FILENO, pgid) == -1
+ || signal(SIGTTOU, SIG_DFL) == SIG_ERR) {
+ warn("Unable to reclaim foreground; terminating\r");
+ writehist();
+ deinitterm();
+ exit(EXIT_FAILURE);
}
-
- tcsetself();
+ if (tcgetattr(STDIN_FILENO, &job.config) == -1)
+ warn("Unable to save termios config of job %d\r", job.id);
+ setconfig(&raw);
+ if (result) return 1;
if (WIFSIGNALED(status)) {
- putchar('\n');
result = WTERMSIG(status);
+ puts("\r");
} else if (WIFSTOPPED(status)) {
- if (tcgetattr(STDIN_FILENO, &job.config) == -1)
- warn("Unable to save termios state for stopped process group %d", job.id);
- job.type = SUSPENDED;
- if (!push(&jobs, &job)) {
- warnx("Unable to add process to job list; too many jobs\n"
- "Press any key to continue");
- getchar();
- if (setfg(job)) goto wait;
- warn("Unable to continue foreground process; terminating");
- sigkill(job.id);
- }
result = WSTOPSIG(status);
- } else result = WEXITSTATUS(status);
-
- tcsetraw();
+ job.type = SUSPENDED;
+ if (push(&jobs, &job)) return result;
+ warnx("Unable to add job %d to list; too many jobs\r\n"
+ "Press any key to continue\r", job.id);
+ getchar();
+ if (setfg(job)) return waitfg(job);
+ warnx("Manual intervention required for job %d\r", job.id);
+ } else if (WIFEXITED(status)) result = WEXITSTATUS(status);
return result;
}