changeset: 46:3a4e782cc38a
tag: tip
user: Anselm R. Garbe <arg_AT_suckless.org>
date: Fri Mar 02 12:35:34 2007 +0100
summary: important several blurt portions to get a working initial st version
diff -r f5aec9cf9188 -r 3a4e782cc38a LICENSE
--- a/LICENSE Thu Mar 01 16:42:13 2007 +0100
+++ b/LICENSE Fri Mar 02 12:35:34 2007 +0100
@@ -1,5 +1,4 @@ MIT/X Consortium License
MIT/X Consortium License
-
(C)opyright MMVI-MMVII Anselm R. Garbe <garbeam at gmail dot com>
Permission is hereby granted, free of charge, to any person obtaining a
@@ -19,3 +18,39 @@ LIABILITY, WHETHER IN AN ACTION OF CONTR
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
+
+
+Note, some portions of st are borrowed from blurt, which has been developed by
+Ivo Timmermans and which is BSD-licensed as follows:
+
+Copyright (c) 2001 Ivo Timmermans <irt_AT_cistron.nl>.
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the
+ distribution.
+
+3. Neither the name of the author, the name of the program, nor the
+ names of its contributors may be used to endorse or promote
+ products derived from this software without specific prior written
+ permission.
+
+THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS
+BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff -r f5aec9cf9188 -r 3a4e782cc38a Makefile
--- a/Makefile Thu Mar 01 16:42:13 2007 +0100
+++ b/Makefile Fri Mar 02 12:35:34 2007 +0100
@@ -3,7 +3,7 @@
include config.mk
-SRC = main.c util.c
+SRC = events.c main.c process.c screen.c ui.c util.c
OBJ = ${SRC:.c=.o}
all: options st
@@ -13,7 +13,6 @@ options:
@echo "CFLAGS = ${CFLAGS}"
@echo "LDFLAGS = ${LDFLAGS}"
@echo "CC = ${CC}"
- @echo "LD = ${LD}"
.c.o:
@echo CC $<
@@ -22,7 +21,7 @@ options:
${OBJ}: st.h
st: ${OBJ}
- @echo LD $@
+ @echo CC -o $@
@${LD} -o $@ ${OBJ} ${LDFLAGS}
@strip $@
diff -r f5aec9cf9188 -r 3a4e782cc38a README
--- a/README Thu Mar 01 16:42:13 2007 +0100
+++ b/README Fri Mar 02 12:35:34 2007 +0100
@@ -1,11 +1,13 @@ st - simple terminal
st - simple terminal
--------------------
-st is an X11 terminal written from scratch. It sucks less.
+st is a simple virtual terminal emulator for X. Several portions are based on
+the blurt terminal emulator originally written by Ivo Timmermans, most portions
+are completely written from scratch, however. It sucks less.
Requirements
------------
-In order to build dt you need the Xlib header files.
+In order to build st you need the Xlib header files.
Installation
@@ -13,7 +15,7 @@ Edit config.mk to match your local setup
Edit config.mk to match your local setup (st is installed into
the /usr/local namespace by default).
-Afterwards enter the following command to build and install dt (if
+Afterwards enter the following command to build and install st (if
necessary as root):
make clean install
diff -r f5aec9cf9188 -r 3a4e782cc38a events.c
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/events.c Fri Mar 02 12:35:34 2007 +0100
@@ -0,0 +1,444 @@
+/* (C)opyright MMVI-MMVII Anselm R. Garbe <garbeam at gmail dot com>
+ * See LICENSE file for license details.
+ */
+#include "st.h"
+#include <assert.h>
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/ioctl.h>
+#include <termios.h>
+#include <X11/Xatom.h>
+#include <X11/Xutil.h>
+#include <X11/cursorfont.h>
+#include <X11/keysym.h>
+#include <X11/Xmd.h>
+
+#define REFRESH_NO 0
+#define REFRESH_FULL 1
+#define REFRESH_PARTIALLY 2
+#define KEYSEQLEN 10
+#define ModMetaMask Mod4Mask
+#define Atom32 CARD32
+
+int refresh_type = 0;
+int saved_cursor_x, saved_cursor_y;
+int application_keypad_mode = 0;
+int insert_mode = 0;
+int decckm_mode = 0;
+int wraparound_mode = 0;
+int cursor_visible = 1;
+int using_alternate_screen = 0;
+
+char *eventnames[] = {
+ "",
+ "",
+ "KeyPress",
+ "KeyRelease",
+ "ButtonPress",
+ "ButtonRelease",
+ "MotionNotify",
+ "EnterNotify",
+ "LeaveNotify",
+ "FocusIn",
+ "FocusOut",
+ "KeymapNotify",
+ "Expose",
+ "GraphicsExpose",
+ "NoExpose",
+ "VisibilityNotify",
+ "CreateNotify",
+ "DestroyNotify",
+ "UnmapNotify",
+ "MapNotify",
+ "MapRequest",
+ "ReparentNotify",
+ "ConfigureNotify",
+ "ConfigureRequest",
+ "GravityNotify",
+ "ResizeRequest",
+ "CirculateNotify",
+ "CirculateRequest",
+ "PropertyNotify",
+ "SelectionClear",
+ "SelectionRequest",
+ "SelectionNotify",
+ "ColormapNotify",
+ "ClientMessage",
+ "MappingNotify"
+};
+
+void
+selection_paste(Time tm) {
+ int nbytes;
+ char *text;
+
+ XConvertSelection(Xdisplay, XA_PRIMARY, XA_CUT_BUFFER0, None, MainWindow, tm);
+ text = XFetchBytes(Xdisplay, &nbytes);
+ cmd_write(text, nbytes);
+}
+
+void
+handle_keypress(XEvent *xev) {
+ char kbuf[KEYSEQLEN];
+ KeySym keysym;
+ static XComposeStatus compose = {NULL, 0};
+ int len;
+ int meta;
+ int shift;
+
+ meta = xev->xkey.state & ModMetaMask;
+ shift = xev->xkey.state & ShiftMask;
+ len = XLookupString(&(xev->xkey), &kbuf[0], sizeof(kbuf), &keysym, &compose);
+ if(len > 0) {
+ kbuf[KEYSEQLEN-1] = 0;
+ if(meta && len == 1)
+ cmd_write("\033", 1);
+ cmd_write(kbuf, len);
+ return;
+ }
+ if(keysym >= XK_Shift_L && keysym <= XK_Hyper_R)
+ /* modifiers, these are handled by XLookupString */
+ return;
+ switch(keysym) {
+ default:
+ fprintf(stderr, "Unhandled special key: %d\n", (int)keysym);
+ break;
+ case XK_Up:
+ case XK_Down:
+ case XK_Left:
+ case XK_Right:
+ kbuf[0] = '\033';
+ if(decckm_mode)
+ kbuf[1] = 'O';
+ else
+ kbuf[1] = '[';
+ kbuf[2] = "DACB"[keysym - XK_Left];
+ cmd_write(kbuf, 3);
+ break;
+ case XK_Delete:
+ cmd_write(DELETE_KEY, sizeof(DELETE_KEY)-1);
+ break;
+ case XK_Home:
+ cmd_write(HOME_KEY, sizeof(HOME_KEY)-1);
+ break;
+ case XK_End:
+ cmd_write(END_KEY, sizeof(END_KEY)-1);
+ break;
+ case XK_Prior:
+ cmd_write(PREV_KEY, sizeof(PREV_KEY)-1);
+ break;
+ case XK_Next:
+ cmd_write(NEXT_KEY, sizeof(NEXT_KEY)-1);
+ break;
+ case XK_Insert:
+ if(shift)
+ selection_paste(CurrentTime);
+ break;
+ }
+}
+
+void
+handle_resize(XEvent *xev) {
+ int new_cols, new_rows;
+
+ new_cols = xev->xconfigure.width / font_width;
+ new_rows = xev->xconfigure.height / font_height;
+ if(new_cols != screen_cols || new_rows != screen_rows) {
+ buffer_resize(new_rows, new_cols);
+ screen_cols = new_cols;
+ screen_rows = new_rows;
+ scroll_region_start = 1;
+ scroll_region_end = screen_rows;
+ window_width = xev->xconfigure.width;
+ window_height = xev->xconfigure.height;
+ /* make sure the cursor is within the window */
+ cursor_rego();
+ /* Finally: pass the info to the application */
+ resize_app();
+ /* and redraw */
+ force_redraw_screen();
+ }
+}
+
+static int selection_start_col, selection_start_row, selection_end_col, selection_end_row;
+static int selection_mode = 0;
+
+static unsigned char *selection_text = NULL;
+
+#define SELECT_NONE 0
+#define SELECT_LETTER 1
+#define SELECT_WORD 2
+#define SELECT_LINE 3
+
+void
+selection_reset(void) {
+ int i, j;
+
+ selection_mode = SELECT_NONE;
+ for(i = 0; i < screen_rows; i++) {
+ for(j = 0; j < screen_cols; j++)
+ text_screen[i].line[j].sel = 0;
+ text_screen[i].needs_update = 1;
+ }
+ if(selection_text) {
+ free(selection_text);
+ selection_text = NULL;
+ }
+}
+
+void
+selection_start(int row, int col, Time tm) {
+ fprintf(stderr, "Selection started, row=%d, col=%d\n", row, col);
+ selection_start_col = col, selection_start_row = row;
+ selection_end_col = col, selection_end_row = row;
+ selection_mode = SELECT_LETTER;
+}
+
+void
+selection_select_word(int row, int col, Time tm) {
+ int i, len;
+
+ if(selection_text)
+ free(selection_text);
+ selection_text = malloc(screen_cols+1);
+ assert(selection_text);
+ fprintf(stderr, "Select word on %d,%d\n", col, row);
+ i = col - 1;
+ while(i >= 0 && isalnum(text_screen[row-1].line[i].letter))
+ i--;
+ i++;
+ len = 0;
+ while((i < screen_cols) && isalnum(text_screen[row-1].line[i].letter)) {
+ selection_text[len++] = text_screen[row-1].line[i].letter;
+ text_screen[row-1].line[i].sel = 1;
+ i++;
+ }
+ fprintf(stderr, "len=%d\n", len);
+ text_screen[row-1].needs_update = 1;
+ XStoreBuffer(Xdisplay, selection_text, len, XA_CUT_BUFFER0);
+ XSetSelectionOwner(Xdisplay, XA_PRIMARY, MainWindow, tm);
+}
+
+void
+selection_select_line(int row, int col, Time tm) {
+ int i;
+
+ if(selection_text)
+ free(selection_text);
+ selection_text = malloc(screen_cols+1);
+ assert(selection_text);
+ fprintf(stderr, "select line %d\n", row);
+ for(i = 0; i < screen_cols; i++) {
+ selection_text[i] = text_screen[row-1].line[i].letter;
+ text_screen[row-1].line[i].sel = 1;
+ }
+ while(selection_text[i] == ' ') i--;
+ text_screen[row-1].needs_update=1;
+ XStoreBuffer(Xdisplay, selection_text, screen_cols, XA_CUT_BUFFER0);
+ XSetSelectionOwner(Xdisplay, XA_PRIMARY, MainWindow, tm);
+}
+
+void
+handle_buttonpress(XEvent *xev) {
+ static int lastclick_time = 0;
+ static int times_clicked = 0;
+ int click_col, click_row;
+ int button;
+
+ click_col = xev->xbutton.x / font_width + 1;
+ click_row = xev->xbutton.y / font_height + 1;
+ click_col = click_col > screen_cols ? screen_cols : (click_col < 1 ? 1 : click_col);
+ click_row = click_row > screen_rows ? screen_rows : (click_row < 1 ? 1 : click_row);
+ button = xev->xbutton.button;
+ fprintf(stderr, "Mouse button %d pressed on (%d,%d)\n", button, click_col, click_row);
+ switch(button) {
+ case 1:
+ if(xev->xbutton.time <= lastclick_time + 600 &&
+ xev->xbutton.time > lastclick_time)
+ times_clicked = (times_clicked) % 3 + 1;
+ else
+ times_clicked = 0;
+ switch(times_clicked) {
+ case SELECT_NONE:
+ case SELECT_LETTER:
+ selection_reset();
+ selection_start(click_row, click_col, xev->xbutton.time);
+ times_clicked = 1;
+ break;
+ case SELECT_WORD:
+ selection_select_word(click_row, click_col, xev->xbutton.time);
+ break;
+ case SELECT_LINE:
+ selection_select_line(click_row, click_col, xev->xbutton.time);
+ break;
+ }
+ lastclick_time = xev->xbutton.time;
+ break;
+ case 2: /* middle button */
+ selection_paste(xev->xbutton.time);
+ break;
+ case 3: /* right button */
+ break;
+ }
+}
+
+void
+handle_motionnotify(XEvent *xev) {
+ int unused;
+
+ while(XCheckTypedWindowEvent(Xdisplay, MainWindow, MotionNotify, xev));
+ XQueryPointer(Xdisplay, MainWindow, &unused, &unused,
+ &unused, &unused, &(xev->xbutton.x), &(xev->xbutton.y), &unused);
+}
+
+void
+selection_copy(XEvent *xev) {
+ XEvent ev;
+ XSelectionRequestEvent *rq = (XSelectionRequestEvent*)xev;
+ Atom32 target_list[4];
+ Atom target;
+ static Atom xa_targets = None;
+ static Atom xa_compound_text = None;
+ static Atom xa_text = None;
+ XTextProperty ct;
+ XICCEncodingStyle style;
+ char *cl[4];
+
+ if(!selection_text)
+ return;
+ ev.xselection.type = SelectionNotify;
+ ev.xselection.property = None;
+ ev.xselection.display = rq->display;
+ ev.xselection.requestor = rq->requestor;
+ ev.xselection.selection = rq->selection;
+ ev.xselection.target = rq->target;
+ ev.xselection.time = rq->time;
+ if(rq->target == None) {
+ target_list[0] = (Atom32) xa_targets;
+ target_list[1] = (Atom32) XA_STRING;
+ target_list[2] = (Atom32) xa_text;
+ target_list[3] = (Atom32) xa_compound_text;
+ XChangeProperty(Xdisplay, rq->requestor, rq->property, rq->target,
+ (8 * sizeof(target_list[0])), PropModeReplace,
+ (unsigned char *)target_list,
+ (sizeof(target_list) / sizeof(target_list[0])));
+ ev.xselection.property = rq->property;
+ }
+ else if(rq->target == XA_STRING || rq->target == xa_compound_text || rq->target == xa_text) {
+ if(rq->target == XA_STRING) {
+ style = XStringStyle;
+ target = XA_STRING;
+ }
+ else {
+ target = xa_compound_text;
+ style = (rq->target == xa_compound_text) ? XCompoundTextStyle
+ : XStdICCTextStyle;
+ }
+ cl[0] = selection_text;
+ XmbTextListToTextProperty(Xdisplay, cl, 1, style, &ct);
+ XChangeProperty(Xdisplay, rq->requestor, rq->property,
+ target, 8, PropModeReplace,
+ ct.value, ct.nitems);
+ ev.xselection.property = rq->property;
+ }
+ XSendEvent(Xdisplay, rq->requestor, False, 0, &ev);
+}
+
+int
+handle_x_events(void) {
+ XEvent xev;
+
+ /* Input from the X server */
+ while (XPending(Xdisplay)) {
+ /* process pending X events */
+ XNextEvent(Xdisplay, &xev);
+
+ switch(xev.type) {
+ default:
+ fprintf(stderr, "Got unhandled X event %d (%s)\n", xev.type, eventnames[xev.type]);
+ case ButtonPress:
+ handle_buttonpress(&xev);
+ break;
+ case MotionNotify:
+ handle_motionnotify(&xev);
+ break;
+ case KeyPress:
+ handle_keypress(&xev);
+ break;
+ case ConfigureNotify:
+ handle_resize(&xev);
+ break;
+ case SelectionNotify:
+ /* Ignore this? */
+ break;
+ case GraphicsExpose:
+ case Expose:
+ /* Part of the window needs a redraw */
+ switch(refresh_type) {
+ case REFRESH_FULL:
+ force_redraw_screen();
+ break;
+ case REFRESH_PARTIALLY:
+ {
+ int a,b,c,d;
+ a = xev.xexpose.x / font_width + 1;
+ b = xev.xexpose.y / font_height + 1;
+ c = (xev.xexpose.x + xev.xexpose.width) / font_width + 1;
+ d = (xev.xexpose.y + xev.xexpose.height) / font_height + 1;
+ a = a > screen_cols ? screen_cols : a;
+ b = b > screen_rows ? screen_rows : b;
+ c = c > screen_cols ? screen_cols : c;
+ d = d > screen_rows ? screen_rows : d;
+ redraw_region(a,b,c,d);
+ }
+ break;
+ default:
+ /* No refreshing needs to be done */
+ break;
+ }
+ break;
+ case VisibilityNotify:
+ /* Some part of the screen has to be redrawn */
+ switch(xev.xvisibility.state)
+ {
+ case VisibilityUnobscured:
+ refresh_type = REFRESH_FULL;
+ break;
+ case VisibilityPartiallyObscured:
+ refresh_type = REFRESH_PARTIALLY;
+ break;
+ default:
+ refresh_type = REFRESH_NO;
+ break;
+ }
+ break;
+ case MapNotify:
+ case UnmapNotify:
+ case FocusIn:
+ case FocusOut:
+ case NoExpose:
+ /* Silently unhandled for now */
+ break;
+ case SelectionClear:
+ selection_reset();
+ free(selection_text);
+ selection_text = NULL;
+ break;
+ case SelectionRequest:
+ fprintf(stderr, "SelectionRequest\n");
+ selection_copy(&xev);
+ break;
+ }
+ }
+ return 0;
+}
+
+int
+set_x_event_mask(void) {
+ XSelectInput(Xdisplay, MainWindow, KeyPressMask | ButtonPressMask
+ | FocusChangeMask | VisibilityChangeMask
+ | StructureNotifyMask | ExposureMask
+ | Button1MotionMask | Button3MotionMask);
+ return 0;
+}
diff -r f5aec9cf9188 -r 3a4e782cc38a main.c
--- a/main.c Thu Mar 01 16:42:13 2007 +0100
+++ b/main.c Fri Mar 02 12:35:34 2007 +0100
@@ -2,170 +2,27 @@
* See LICENSE file for license details.
*/
#include "st.h"
-#define _XOPEN_SOURCE
-#include <errno.h>
-#include <fcntl.h>
-#include <locale.h>
-#include <pwd.h>
-#include <signal.h>
#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <sys/ioctl.h>
-#include <sys/select.h>
-#include <sys/stat.h>
-#include <sys/time.h>
-#include <sys/types.h>
-#include <sys/wait.h>
-#include <unistd.h>
-
-#define TIMEOUT_USEC 10000
-
-extern int getpt(void);
-extern int grantpt(int fd);
-extern int unlockpt(int fd);
-
-/* static */
-
-static pid_t pid;
-static Window root;
-
-static int
-getpty(void) {
- extern char *ptsname(int);
- int fd;
-
- if((fd = getpt()) >= 0) {
- if(grantpt(fd) == 0 && unlockpt(fd) == 0) {
- ptydev = ptsname(fd);
- if((slave = open(ptydev, O_RDWR | O_NOCTTY)) < 0) {
- fprintf(stderr, "cannot open '%s'\n", ptydev);
- return -1;
- }
- fcntl(fd, F_SETFL, O_NDELAY);
- return fd;
- }
- close(fd);
- }
- fputs("cannot open pseudo-tty\n", stderr);
- return -1;
-}
-
-void
-getty(void) {
- int i;
-
- for(i = 0; i < 100; i++)
- if(i != slave)
- close(i);
- master = slave;
- setsid();
- dup2(master, 0);
- dup2(master, 1);
- dup2(master, 2);
- if(ioctl(master, TIOCSCTTY, NULL) < 0)
- eprint("cannot set controlling terminal\n");
- if(ioctl(master, TIOCSWINSZ, &wsz) < 0)
- eprint("cannot set window size\n");
-}
-
-static void
-sigchld(int sig) {
- int status = 0;
-
- if(waitpid(pid, &status, 0) < 0) {
- fprintf(stderr, "Waiting for pid %hd failed: %d\n", pid, sig);
- exit(EXIT_FAILURE);
- }
- if(WIFEXITED(status)) { /* child exited already */
- if(WEXITSTATUS(status))
- exit(WEXITSTATUS(status));
- }
- else /* child killed */
- exit(EXIT_FAILURE);
- exit(EXIT_SUCCESS);
-}
-
-void
-rc(char *args[]) {
- if((master = getpty()) < 0)
- eprint("cannot open pseudo-tty\n");
- switch((pid = fork())) {
- case -1:
- eprint("cannot fork");
- case 0: /* child */
- getty();
- putenv("TERM=xterm");
- execvp(args[0], args);
- eprint("cannot execvp");
- default:
- close(slave);
- signal(SIGCHLD, sigchld);
- break;
- }
-}
-
-/* extern */
-
-char buffer[BUFFER_SIZE], *ptydev;
-int master, screen, slave;
-struct winsize wsz;
-void (*handler[LASTEvent]) (XEvent *) = {0};
-Display *dpy;
-Fnt normal;
-Fnt bold;
int
-main(int argc, char *argv[]) {
- char *args[3];
- fd_set rd;
- int xfd;
- unsigned int len;
- XEvent ev;
- struct timeval tv;
+main(int argc, char **argv) {
+ if(init_display())
+ return 1;
+ if(init_colors())
+ return 1;
+ if(init_font())
+ return 1;
+ if(init_window())
+ return 1;
+ if(init_vt_buffer())
+ return 1;
- if(argc == 2 && !strncmp("-v", argv[1], 3))
- eprint("st-"VERSION", (C)opyright MMVI-MMVII Anselm R. Garbe\n");
- setlocale(LC_CTYPE, "");
- dpy = XOpenDisplay(0);
- if(!dpy)
- eprint("st: cannot open display\n");
- xfd = ConnectionNumber(dpy);
- screen = DefaultScreen(dpy);
- root = RootWindow(dpy, screen);
- wsz.ws_row = 25;
- wsz.ws_col = 80;
- wsz.ws_xpixel = wsz.ws_ypixel = 0;
- args[0] = "sh";
- args[1] = "-i";
- args[2] = NULL;
- rc(args);
- for(;;) {
- FD_ZERO(&rd);
- FD_SET(master, &rd);
- FD_SET(xfd, &rd);
- tv.tv_usec = TIMEOUT_USEC;
- tv.tv_sec = 0;
- if(select(xfd + 1, &rd, NULL, NULL, &tv) == -1) {
- if(errno == EINTR)
- continue;
- eprint("select failed\n");
- }
- if(FD_ISSET(master, &rd)) {
- len = read(master, buffer, sizeof buffer);
- if(len < 0)
- eprint("cannot read from command\n");
- if(len == 0)
- eprint("no more data, exiting\n");
- buffer[len] = 0;
- fprintf(stderr, "got '%s'\n", buffer);
- }
- while(XPending(dpy)) {
- XNextEvent(dpy, &ev);
- if(handler[ev.type])
- (handler[ev.type])(&ev);
- }
- }
- XCloseDisplay(dpy);
+ set_buffer_fg_color(7);
+ set_buffer_bg_color(0);
+ set_buffer_attrs(0);
+ set_x_event_mask();
+ execute_command(argc, argv);
+ main_loop();
+ exit_display();
return 0;
}
diff -r f5aec9cf9188 -r 3a4e782cc38a process.c
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/process.c Fri Mar 02 12:35:34 2007 +0100
@@ -0,0 +1,244 @@
+/* (C)opyright MMVI-MMVII Anselm R. Garbe <garbeam at gmail dot com>
+ * See LICENSE file for license details.
+ */
+#include "st.h"
+#include <assert.h>
+#include <fcntl.h>
+#include <pwd.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+static unsigned char output_buf[512];
+static unsigned int startptr = 0; /* index into output_buf to where data starts */
+static unsigned int endptr = 0; /* index into output_buf to where data ends */
+pid_t pid;
+static int cmd_fd = -1;
+static int slave;
+char *ptydev, *ttydev;
+
+void
+sigchld_handler(int a) {
+ int status = 0;
+
+ if(waitpid(pid, &status, 0) < 0) {
+ fprintf(stderr, "Waiting for pid %hd failed: %m\n",
+ pid);
+ exit(1);
+ }
+ if(WIFEXITED(status)) /* Child exited by itself */ {
+ if(WEXITSTATUS(status))
+ exit(WEXITSTATUS(status));
+ }
+ else if(WIFSIGNALED(status)) /* Child was killed by a signal */
+ exit(1);
+ else /* Something strange happened */
+ exit(1);
+ exit(0);
+}
+
+struct passwd *
+find_user(void) {
+ uid_t uid;
+ struct passwd *pw;
+
+ if((uid = getuid()) == -1) {
+ fprintf(stderr, "Couldn't find current uid: %m\n");
+ exit(2);
+ }
+ if((pw = getpwuid(uid)) == NULL) {
+ fprintf(stderr, "Couldn't find password entry for user %d: %m\n", uid);
+ exit(2);
+ }
+ return pw;
+}
+
+int
+get_pty(void) {
+ extern char *ptsname();
+ int fd;
+
+ if((fd = getpt()) >= 0) {
+ if(grantpt(fd) == 0 && unlockpt(fd) == 0) {
+ ptydev = ptsname(fd);
+ if((slave = open(ptydev, O_RDWR | O_NOCTTY)) < 0) {
+ fprintf(stderr, "Error opening slave pty: %m\n");
+ return -1;
+ }
+ fcntl(fd, F_SETFL, O_NDELAY);
+ return fd;
+ }
+ close(fd);
+ }
+ fprintf(stderr, "Can't open a pseudo-tty\n");
+ return -1;
+}
+
+int
+resize_app(void) {
+ if(ioctl(cmd_fd, TIOCSWINSZ, get_font_dim()) < 0) {
+ fprintf(stderr, "Couldn't set window size: %m\n");
+ return -1;
+ }
+ return 0;
+}
+
+int
+get_tty(void) {
+ int i;
+
+ for(i = 0; i < 100 ; i++)
+ if(i != slave)
+ close(i);
+ cmd_fd = slave;
+ setsid(); /* create a new process group */
+ dup2(cmd_fd, 0);
+ dup2(cmd_fd, 1);
+ dup2(cmd_fd, 2);
+ if(ioctl(cmd_fd, TIOCSCTTY, NULL) < 0) {
+ fprintf(stderr, "Couldn't set controlling terminal: %m\n");
+ return -1;
+ }
+ if(ioctl(cmd_fd, TIOCSWINSZ, get_font_dim()) < 0) {
+ fprintf(stderr, "Couldn't set window size: %m\n");
+ return -1;
+ }
+ return 0;
+}
+
+int
+execute_command(int argc, const char **argv) {
+ char **args;
+ int master;
+ struct passwd *pw;
+
+ pw = find_user();
+ if((cmd_fd = get_pty()) < 0)
+ return -1;
+ if((pid = fork()) < 0) {
+ fprintf(stderr, "Couldn't fork: %m\n");
+ return -1;
+ }
+ if(!pid) {
+ /* child */
+ get_tty();
+ putenv("TERM=st");
+ chdir(pw->pw_dir);
+ args = calloc(3, sizeof(char*));
+ args[0] = malloc(strlen(pw->pw_shell) + 1);
+ strcpy(args[0], pw->pw_shell);
+ args[1] = "-i";
+ args[2] = NULL;
+ execvp(pw->pw_shell, args);
+ /* shouldn't be here */
+ fprintf(stderr, "Error executing %s: %m\n", pw->pw_shell);
+ exit(1);
+ }
+ /* parent */
+ close(slave);
+ signal(SIGCHLD, sigchld_handler);
+ return 0;
+}
+
+#define TIMEOUT_USEC 10000
+void
+wait_for_input(void) {
+ int len;
+ fd_set fset;
+ struct timeval tv;
+ int r;
+ int X_fd;
+ int need_redraw = 0;
+
+ X_fd = get_x_filedes();
+ hide_cursor();
+ redraw_screen();
+ for(;;) {
+ FD_ZERO(&fset);
+ if(cmd_fd >= 0)
+ FD_SET(cmd_fd, &fset);
+ FD_SET(X_fd, &fset);
+ tv.tv_usec = TIMEOUT_USEC;
+ tv.tv_sec = 0;
+ r = select(FD_SETSIZE, &fset, NULL, NULL, &tv);
+ if(r < 0) {
+ /* error */
+ fprintf(stderr, "select: %m\n");
+ exit(1);
+ }
+ if(!r) {
+ /* timeout */
+ if(need_redraw) {
+ hide_cursor();
+ redraw_screen();
+ need_redraw = 0;
+ }
+ show_cursor();
+ continue;
+ }
+ if(FD_ISSET(cmd_fd, &fset)) {
+ /* Input from the command */
+ hide_cursor();
+ return;
+ }
+ if(FD_ISSET(X_fd, &fset)) {
+ hide_cursor();
+ handle_x_events();
+ need_redraw = 1;
+ }
+ }
+}
+
+void
+fill_buffer(int fd) {
+ int len;
+
+ wait_for_input();
+ len = read(fd, &output_buf[0], sizeof(output_buf));
+ if(len < 0) {
+ fprintf(stderr, "Error reading from command: %m\n");
+ exit(1);
+ }
+ if(len == 0) {
+ fprintf(stderr, "No more data! exiting.\n");
+ exit(1);
+ }
+ endptr = len;
+ startptr = 0;
+}
+
+unsigned char
+cmdgetc() {
+ unsigned char r;
+ static int bla = 0;
+
+ if(startptr >= endptr) /* there is no more data available */
+ fill_buffer(cmd_fd);
+ r = output_buf[startptr];
+#if 0
+ /* For debugging purposes :) Produces quite a lot of output. */
+ fprintf(stderr, " %02x %c ", r, isprint(r)?r:'.');
+ bla++;
+ if(bla % 10 == 0)
+ fprintf(stderr, "\n");
+#endif
+ startptr++;
+ return r;
+}
+
+int
+cmd_write(const char *buf, int len) {
+ if(write(cmd_fd, buf, len) < 0) {
+ fprintf(stderr, "Error writing to process: %m\n");
+ exit(2);
+ }
+ return 0;
+}
+
diff -r f5aec9cf9188 -r 3a4e782cc38a screen.c
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/screen.c Fri Mar 02 12:35:34 2007 +0100
@@ -0,0 +1,713 @@
+/* (C)opyright MMVI-MMVII Anselm R. Garbe <garbeam at gmail dot com>
+ * See LICENSE file for license details.
+ */
+#include "st.h"
+#include <assert.h>
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/ioctl.h>
+#include <termios.h>
+
+/* Maximum (numerical) parameters to an escape sequence */
+#define NPAR 16
+
+/* Dimensions of the visible screen in characters */
+int screen_rows = -1;
+int screen_cols = -1;
+/* Start and end rows of the scrolling region */
+int scroll_region_start = 1;
+int scroll_region_end = DEFAULT_NR_ROWS;
+int scroll_in_region = 0;
+text_row_t *text_screen;
+text_row_t *saved_screen;
+int screen_rows;
+int screen_cols;
+text_letter_t text_attrs;
+int curr_row = 1;
+int curr_col = 1;
+
+text_row_t *
+buffer_create(int rows, int cols) {
+ int i;
+ text_row_t *tr;
+
+ tr = calloc(rows, sizeof(text_row_t));
+ if(!tr) {
+ fprintf(stderr, "Could not allocate %dx%d bytes\n",
+ rows, sizeof(text_row_t));
+ exit(1);
+ }
+ for(i = 0; i < rows; i++) {
+ tr[i].line = calloc(cols, sizeof(text_letter_t));
+ assert(tr[i].line);
+ }
+ return tr;
+}
+
+/* Cursor movement functions */
+int
+cursor_move_up(int n) {
+ curr_row -= n;
+ if(curr_row < 1)
+ curr_row = 0;
+ return curr_row;
+}
+
+int
+cursor_move_down(int n) {
+ curr_row += n;
+ if(curr_row > screen_rows)
+ curr_row = screen_rows;
+ return curr_row;
+}
+
+int
+cursor_move_left(int n) {
+ curr_col -= n;
+ if(curr_col < 1)
+ curr_col = 1;
+ return curr_col;
+}
+
+int
+cursor_move_right(int n) {
+ curr_col += n;
+ if(curr_col > screen_cols)
+ curr_col = screen_cols;
+ return curr_col;
+}
+
+int
+cursor_move_col(int n) {
+ curr_col = n;
+ if(curr_col < 1)
+ curr_col = 1;
+ if(curr_col > screen_cols)
+ curr_col = screen_cols;
+ return curr_col;
+}
+
+int
+cursor_move_row(int n) {
+ curr_row = n;
+ if(curr_row < 1)
+ curr_row = 1;
+ if(curr_row > screen_rows)
+ curr_row = screen_rows;
+ return curr_row;
+}
+
+void
+cursor_goto(int x, int y) {
+ curr_col = x;
+ curr_row = y;
+ if(curr_col < 1)
+ curr_col = 1;
+ if(curr_col > screen_cols)
+ curr_col = screen_cols;
+ if(curr_row < 1)
+ curr_row = 1;
+ if(curr_row > screen_rows)
+ curr_row = screen_rows;
+}
+
+void
+cursor_rego(void) {
+ cursor_goto(curr_col, curr_row);
+}
+
+void
+delete_rows(text_row_t *screen, int start, int n) {
+ int i;
+
+ for(i = 0; i < n; i++)
+ free(screen[start + i - 1].line);
+}
+
+void
+add_rows(text_row_t *screen, int pos, int n) {
+ int i, j;
+
+ text_attrs.letter = ' ';
+ for(i = 0; i < n; i++) {
+ screen[pos + i - 1].line = calloc(screen_cols, sizeof(text_letter_t));
+ assert(screen[pos + i - 1].line);
+ for(j = 0; j < screen_cols; j++)
+ screen[pos + i - 1].line[j] = text_attrs;
+ screen[pos + i - 1].needs_update = 1;
+ }
+}
+
+
+int
+init_vt_buffer(void) {
+ text_screen = buffer_create(screen_rows, screen_cols);
+ curr_col = curr_row = 1;
+ scroll_region_start = 1;
+ scroll_region_end = screen_rows;
+ return 0;
+}
+
+void
+buffer_destroy(text_row_t *screen) {
+ int i;
+
+ for(i = 0; i < screen_rows; i++)
+ free(screen[i].line);
+ free(screen);
+}
+
+void
+save_current_screen(void) {
+ saved_screen = text_screen;
+ text_screen = buffer_create(screen_rows, screen_cols);
+}
+
+void
+restore_saved_screen(void) {
+ int i;
+
+ buffer_destroy(text_screen);
+ text_screen = saved_screen;
+ for(i = 0; i < screen_rows; i++)
+ text_screen[i].needs_update = 1;
+}
+
+void
+_buffer_resize(text_row_t **s, int rows, int cols) {
+ int i;
+
+ if(rows < screen_rows) {
+ delete_rows(*s, 1, screen_rows - rows);
+ moverows(*s, screen_rows - rows + 1, 1, rows);
+ }
+ *s = realloc(*s, rows * sizeof(text_row_t));
+ assert(*s);
+ if(rows > screen_rows)
+ add_rows(*s, screen_rows + 1, rows - screen_rows);
+ if(cols != screen_cols) {
+ for(i = 0; i < rows; i++) {
+ int j;
+
+ (*s)[i].line = realloc((*s)[i].line, cols * sizeof(text_letter_t));
+ assert((*s)[i].line);
+ for(j = screen_cols; j < cols; j++)
+ *(int*)(&((*s)[i]).line[j]) = 0;
+ (*s)[i].needs_update = 1;
+ }
+ }
+}
+
+void
+buffer_resize(int rows, int cols) {
+ if(using_alternate_screen)
+ _buffer_resize(&saved_screen, rows, cols);
+ _buffer_resize(&text_screen, rows, cols);
+}
+
+void
+clear_area(int x1, int y1, int x2, int y2) {
+ int i, j;
+
+ win_clear_region((x1 - 1) * font_width, (y1 - 1) * font_height,
+ x2 * font_width, y2 * font_height,
+ GET_BG_COLOR(text_attrs));
+ for(i = y1; i <= y2; i++)
+ for(j = x1; j <= x2; j++)
+ {
+ text_screen[i-1].line[j-1] = text_attrs;
+ text_screen[i-1].line[j-1].letter = ' ';
+ }
+}
+
+void
+clear_row(text_row_t *r) {
+ memset(r->line, '\0', screen_cols * sizeof(text_letter_t));
+}
+
+void
+moverows(text_row_t *screen, int from, int to, int n) {
+ memmove(&(screen[to-1]), &(screen[from-1]), n * sizeof(text_row_t));
+}
+
+void
+scroll_up(int rows) {
+ if(scroll_in_region) {
+ /* Only scroll region */
+ region_scroll_up(scroll_region_start, scroll_region_end, rows);
+ delete_rows(text_screen, scroll_region_start, rows);
+ moverows(text_screen, rows + scroll_region_start, scroll_region_start, scroll_region_end - scroll_region_start - rows + 1);
+ add_rows(text_screen, scroll_region_end - rows + 1, rows);
+ }
+ else {
+ /* Scroll entire buffer */
+ window_scroll_up(rows);
+ delete_rows(text_screen, 1, rows);
+ moverows(text_screen, rows + 1, 1, screen_rows - rows);
+ add_rows(text_screen, screen_rows - rows + 1, rows);
+ }
+}
+
+void
+scroll_down(int rows) {
+ if(scroll_in_region) {
+ /* Only scroll region */
+ region_scroll_down(scroll_region_start, scroll_region_end, rows);
+ delete_rows(text_screen, scroll_region_end - rows + 1, rows);
+ moverows(text_screen, scroll_region_start, scroll_region_start + rows, scroll_region_end - scroll_region_start - rows + 1);
+ add_rows(text_screen, scroll_region_start, rows);
+ }
+ else {
+ /* Scroll entire buffer */
+ window_scroll_down(rows);
+ delete_rows(text_screen, screen_rows - rows + 1, rows);
+ moverows(text_screen, 1, rows + 1, screen_rows - rows);
+ add_rows(text_screen, 1, rows);
+ }
+}
+
+void
+insert_lines(int lines) {
+ int a,b;
+
+ /* Fake region scroll */
+ a = scroll_region_start;
+ b = scroll_in_region;
+ scroll_region_start = curr_row;
+ scroll_in_region = 1;
+ scroll_down(lines);
+ scroll_region_start = a;
+ scroll_in_region = b;
+}
+
+void
+delete_lines(int lines) {
+ int a,b;
+
+ /* Fake region scroll */
+ a = scroll_region_start;
+ b = scroll_in_region;
+ scroll_region_start = curr_row;
+ scroll_in_region = 1;
+ scroll_up(lines);
+ scroll_region_start = a;
+ scroll_in_region = b;
+}
+
+void
+set_buffer_fg_color(int c) {
+ text_attrs.fg = c;
+}
+
+void
+set_buffer_bg_color(int c) {
+ text_attrs.bg = c;
+}
+
+void
+set_buffer_attrs(int bold) {
+ text_attrs.bold = bold ? 1 : 0;
+}
+
+void set_buffer_reverse(int rev)
+{
+ text_attrs.rev = rev ? 1 : 0;
+}
+
+struct winsize *
+get_font_dim() {
+ static struct winsize w;
+
+ w.ws_row = screen_rows;
+ w.ws_col = screen_cols;
+ w.ws_xpixel = w.ws_ypixel = 0;
+ return &w;
+}
+
+void
+write_buffer(unsigned char c) {
+ text_attrs.letter = c;
+ text_screen[curr_row-1].line[curr_col-1] = text_attrs;
+ text_screen[curr_row-1].needs_update = 1;
+}
+
+void
+xterm_seq(int op) {
+ unsigned char *buf;
+ int len;
+ int buflen;
+ unsigned char c;
+
+ buf = malloc(buflen = 20);
+ c = cmdgetc();
+ for(len = 0; c != '\007'; len++) {
+ if(len > buflen + 1) {
+ buflen <<= 1;
+ buf = realloc(buf, buflen);
+ assert(buf);
+ }
+ buf[len] = c;
+ c = cmdgetc();
+ }
+ buf[len] = 0;
+ switch(op) {
+ case 0: /* set window and icon title */
+ case 1: /* set icon title */
+ case 2: /* set window title */
+ win_set_window_title(buf);
+ break;
+ }
+}
+
+void
+wrap_line(void) {
+ while(curr_col > screen_cols) {
+ curr_col -= screen_cols;
+ if(!wraparound_mode)
+ curr_row++;
+ if(curr_row > scroll_region_end) {
+ scroll_up(curr_row - scroll_region_end);
+ curr_row = scroll_region_end;
+ }
+ }
+}
+
+void
+dispatch_escape(void) {
+ int len;
+ int pos;
+ int args[NPAR];
+ int narg = 0;
+ int digit;
+ int rows, cols;
+ int i;
+ int questionmark;
+ unsigned char c;
+
+ for(pos = 0; pos < NPAR; pos++)
+ args[pos] = 0;
+
+ c = cmdgetc();
+ switch(c) {
+ default:
+ fprintf(stderr, "Unsupported ESC sequence ESC %c\n", c);
+ break;
+ case '[': /* CSI */
+ digit = 0;
+ questionmark = 0;
+ c = cmdgetc();
+ while(isdigit(c) || c == ';' || c == '?') {
+ if(c == ';') {
+ args[narg] = 0;
+ digit = 0;
+ }
+ else {
+ if(c == '?')
+ questionmark = 1;
+ else {
+ if(!digit)
+ narg++;
+ digit = 1;
+ args[narg-1] *= 10;
+ args[narg-1] += c - '0';
+ }
+ }
+ c = cmdgetc();
+ }
+
+ switch(c) {
+ default:
+ fprintf(stderr, "Unsupported CSI sequence ESC [");
+ if(questionmark)
+ fprintf(stderr, " ?");
+ for(i = 0; i < narg; i++)
+ fprintf(stderr, " %d", args[i]);
+ fprintf(stderr, " %c\n", c);
+ break;
+ case 'A':
+ cursor_move_up(narg ? args[0] : 1);
+ break;
+ case 'B':
+ cursor_move_down(narg ? args[0] : 1);
+ break;
+ case 'C':
+ cursor_move_right(narg ? args[0] : 1);
+ break;
+ case 'D':
+ cursor_move_left(narg ? args[0] : 1);
+ break;
+ case 'G':
+ cursor_move_col(narg ? args[0] : 1);
+ break;
+ case 'H':
+ cursor_goto(args[1] ? args[1] : 1, args[0] ? args[0] : 1);
+ break;
+ case 'J':
+ if(narg) {
+ if(args[0] == 1) {
+ /* erase from start to cursor */
+ clear_area(1, 1, screen_cols, curr_row);
+ }
+ if(args[0] == 2) {
+ /* erase whole display */
+ clear_area(1, 1, screen_cols, screen_rows);
+ }
+ }
+ else {
+ /* erase from cursor to end of display */
+ clear_area(1, curr_row, screen_cols, screen_rows);
+ }
+ break;
+ case 'K':
+ if(narg) {
+ if(args[0] == 1) {
+ /* erase from start of line to cursor */
+ clear_area(1, curr_row, curr_col, curr_row);
+ }
+ if(args[0] == 2) {
+ /* erase whole line */
+ clear_area(1, curr_row, screen_cols, curr_row);
+ }
+ }
+ else {
+ /* erase from cursor to end of line */
+ clear_area(curr_col, curr_row, screen_cols, curr_row);
+ }
+ break;
+ case 'L': /* Insert lines */
+ insert_lines(narg ? args[0] : 1);
+ break;
+ case 'M': /* Delete lines */
+ delete_lines(narg ? args[0] : 1);
+ break;
+ case 'd': /* line position absolute */
+ cursor_move_row(narg ? args[0] : 1);
+ break;
+ case 'P': /* clear # of characters */
+ clear_area(curr_col, curr_row, curr_col + args[0], curr_row);
+ break;
+ case 'h': /* set mode */
+ switch(args[0]) {
+ default:
+ fprintf(stderr, "Unsupported ESC [%s h mode %d\n",
+ questionmark?" ?":"",
+ args[0]);
+ break;
+ case 1:
+ if(questionmark)
+ /* DEC CKM mode */
+ decckm_mode = 1;
+ break;
+ case 4:
+ if(!questionmark)
+ /* insert mode */
+ insert_mode = 1;
+ break;
+ case 7:
+ if(questionmark)
+ wraparound_mode = 1;
+ break;
+ case 25:
+ if(questionmark)
+ cursor_visible = 1;
+ break;
+ case 47:
+ if(questionmark) {
+ using_alternate_screen = 1;
+ save_current_screen();
+ }
+ break;
+ }
+ break;
+ case 'l': /* reset mode */
+ switch(args[0]) {
+ default:
+ fprintf(stderr, "Unsupported ESC [%s l mode %d\n",
+ questionmark?" ?":"",
+ args[0]);
+ break;
+ case 1:
+ if(questionmark)
+ /* DEC CKM mode */
+ decckm_mode = 0;
+ break;
+ case 4: /* insert mode */
+ insert_mode = 0;
+ break;
+ case 7:
+ if(questionmark)
+ wraparound_mode = 0;
+ break;
+ case 25:
+ if(questionmark)
+ cursor_visible = 0;
+ break;
+ case 47:
+ if(questionmark)
+ {
+ using_alternate_screen = 0;
+ restore_saved_screen();
+ }
+ break;
+ }
+ break;
+ case 'm':
+ /* reset attrs */
+ if(!narg) {
+ set_buffer_attrs(0);
+ set_buffer_fg_color(7);
+ set_buffer_reverse(0);
+ set_buffer_bg_color(0);
+ }
+ for(i = 0; i < narg; i++) {
+ if(args[i] == 0) {
+ set_buffer_attrs(0);
+ set_buffer_fg_color(7);
+ }
+ else if(args[i] == 1)
+ set_buffer_attrs(1);
+ else if(args[i] == 7)
+ set_buffer_reverse(1);
+ else if(args[i] == 27)
+ set_buffer_reverse(0);
+ else if(args[i] >= 30 && args[i] <= 37)
+ set_buffer_fg_color(args[i] - 30);
+ else if(args[i] >= 40 && args[i] <= 47)
+ set_buffer_bg_color(args[i] - 40);
+ else if(args[i] == 39)
+ set_buffer_fg_color(7);
+ else if(args[i] == 49)
+ set_buffer_bg_color(0);
+ else
+ fprintf(stderr, "Unsupported mode %d\n",
+ args[i]);
+ }
+ break;
+ case 'n':
+ /* status report */
+ {
+ char buf[20];
+ switch(args[0]) {
+ default:
+ fprintf(stderr, "Unknown status request id %d\n", args[0]);
+ break;
+ case 6:
+ /* cursor position */
+ snprintf(buf, sizeof(buf), "\033[%d;%dR",
+ curr_row, curr_col);
+ cmd_write(buf, strlen(buf));
+ break;
+ }
+ }
+ break;
+ case 'r': /* set scrolling region */
+ scroll_region_start = args[0] ? args[0] : 1;
+ scroll_region_end = args[1] ? args[1] : screen_rows;
+ if(!narg)
+ /* Reset scroll region */
+ scroll_in_region = 0;
+ else if(args[0] == 1 && args[1] == screen_rows)
+ scroll_in_region = 0;
+ else
+ scroll_in_region = 1;
+ break;
+ case '[': /* echoed function key */
+ cmdgetc();
+ break;
+ }
+ break;
+ case ']': /* xterm sequence */
+ digit = 0;
+ c = cmdgetc();
+ while(isdigit(c)) {
+ if(!digit)
+ narg++;
+ digit = 1;
+ args[narg-1] *= 10;
+ args[narg-1] += c - '0';
+ c = cmdgetc();
+ }
+ if(c != ';' || !narg) {
+ fprintf(stderr, "Invalid xterm sequence\n");
+ break;
+ }
+ xterm_seq(args[0]);
+ break;
+ case '7': /* save cursor position */
+ saved_cursor_x = curr_col;
+ saved_cursor_y = curr_row;
+ break;
+ case '8': /* restore cursor position */
+ curr_col = saved_cursor_x;
+ curr_row = saved_cursor_y;
+ break;
+ case '=': /* set application keypad mode */
+ application_keypad_mode = 1;
+ break;
+ case '>': /* set numeric keypad mode */
+ application_keypad_mode = 0;
+ break;
+ case 'M': /* reverse linefeed */
+ curr_row--;
+ if(curr_row < scroll_region_start) {
+ curr_row = scroll_region_start;
+ scroll_down(1);
+ }
+ break;
+ }
+}
+
+void
+main_loop() {
+ unsigned char c;
+
+ for(;;)
+ switch((c = cmdgetc())) {
+ default:
+ wrap_line();
+ if(c >= ' ')
+ {
+ write_buffer(c);
+ curr_col++;
+ }
+ break;
+ case '\007': /* Bell */
+ bell();
+ break;
+ case '\010': /* backspace */
+ curr_col--;
+ if(curr_col < 1)
+ curr_col = 1;
+ break;
+ case '\011': /* tab */
+ curr_col = ((curr_col) | 7) + 2;
+ if(curr_col > screen_cols) {
+ curr_col = 8;
+ if(!wraparound_mode)
+ curr_row++;
+ if(curr_row > scroll_region_end) {
+ scroll_up(curr_row - scroll_region_end);
+ curr_row = scroll_region_end;
+ }
+ }
+ break;
+ case '\033': /* escape */
+ /* handle esc codes */
+ dispatch_escape();
+ break;
+ case '\n': /* newline */
+ if(!application_keypad_mode)
+ /* return carriage */
+ curr_col = 1;
+ curr_row++;
+ if(curr_row > scroll_region_end) {
+ scroll_up(curr_row - scroll_region_end);
+ curr_row = scroll_region_end;
+ }
+ break;
+ case '\r': /* carriage return */
+ curr_col = 1;
+ break;
+ }
+}
diff -r f5aec9cf9188 -r 3a4e782cc38a st.h
--- a/st.h Thu Mar 01 16:42:13 2007 +0100
+++ b/st.h Fri Mar 02 12:35:34 2007 +0100
@@ -1,10 +1,30 @@
/* (C)opyright MMVI-MMVII Anselm R. Garbe <garbeam at gmail dot com>
* See LICENSE file for license details.
*/
-#include <pty.h>
#include <X11/Xlib.h>
-#define BUFFER_SIZE 32768 /* ring input buffer size */
+#define DELETE_KEY "\033[3~"
+#define HOME_KEY "\033[1~"
+#define END_KEY "\033[4~"
+#define PREV_KEY "\033[5~"
+#define NEXT_KEY "\033[6~"
+#define DEFAULT_FONT "-*-proggyclean-medium-r-*-*-13-*-*-*-*-*-*-*"
+#define DEFAULT_BOLD_FONT DEFAULT_FONT
+#define DEFAULT_NR_ROWS 24
+#define DEFAULT_NR_COLS 80
+#define DELETE_KEY "\033[3~"
+#define HOME_KEY "\033[1~"
+#define END_KEY "\033[4~"
+#define PREV_KEY "\033[5~"
+#define NEXT_KEY "\033[6~"
+#define DEFAULT_BACKGROUND_COLOR colors[0]
+#define DEFAULT_FOREGROUND_COLOR colors[7]
+#define win_set_icon_title win_set_window_title
+#define FIXME fprintf(stderr, "FIXME: %s: %d (%s)\n", __FILE__, __LINE__, __FUNCTION__)
+#define CP fprintf(stderr, "Checkpoint: %s:%d\n", __FILE__, __LINE__)
+#define min(a,b) (((a)<(b))?(a):(b))
+#define max(a,b) (((a)>(b))?(a):(b))
+#define GET_BG_COLOR(p) (((p.rev)^(p.sel))?(bgcolors[(p).fg].pixel):(bgcolors[(p).bg].pixel))
typedef struct Fnt Fnt;
typedef struct Glyph Glyph;
@@ -24,13 +44,77 @@ struct Glyph {
Fnt *font; /* bold or normal */
};
-char buffer[BUFFER_SIZE], *ptydev;
-int master, screen, slave;
-struct winsize wsz;
-void (*handler[LASTEvent])(XEvent *); /* event handler */
-Display *dpy;
-Fnt normal;
-Fnt bold;
+typedef struct text_letter_t {
+#ifndef BIGENDIAN
+ unsigned letter: 8;
+ unsigned fg: 3;
+ unsigned bg: 3;
+ unsigned bold: 1;
+ unsigned rev: 1;
+ unsigned sel: 1;
+ int padding: 15;
+#else
+ int padding: 15;
+ unsigned sel: 1;
+ unsigned rev: 1;
+ unsigned bold: 1;
+ unsigned fg: 3;
+ unsigned bg: 3;
+ unsigned letter: 8;
+#endif /* BIGENDIAN */
+} text_letter_t;
+
+typedef struct text_row_t {
+ text_letter_t *line;
+ int needs_update;
+} text_row_t;
+
+extern int saved_cursor_x, saved_cursor_y;
+extern int application_keypad_mode;
+extern int insert_mode;
+extern int decckm_mode;
+extern int wraparound_mode;
+extern int cursor_visible;
+extern int using_alternate_screen;
+extern int screen_cols;
+extern int screen_rows;
+extern int scroll_region_start;
+extern int scroll_region_end ;
+extern int scroll_in_region;
+extern text_row_t *text_screen;
+extern text_letter_t text_attrs;
+extern XColor colors[9];
+extern XColor bgcolors[8];
+extern XColor cursor_color;
+extern Display *Xdisplay;
+extern Window MainWindow;
+extern XFontStruct *Xfont;
+extern XFontStruct *Xboldfont;
+extern GC Xgc;
+extern Colormap Xcolmap;
+extern Cursor Xcursor;
+extern int Xdepth;
+extern Visual *Xvisual;
+extern int Xscreen;
+extern int window_width;
+extern int window_height;
+extern int font_height;
+extern int font_width;
+
+extern int cmd_write(const char *, int);
+extern unsigned char cmdgetc(void);
+extern int resize_app(void);
+
+extern int init_screen(void);
+extern int init_window(void);
+extern void wait_for_specific_event(int);
+extern struct winsize *get_font_dim(void);
+extern void buffer_resize(int,int);
+extern void cursor_rego(void);
+extern void redraw_screen(void);
+extern void redraw_region(int,int,int,int);
+extern void force_redraw_screen(void);
+extern void win_set_window_title(char *);
/* util.c */
void *emalloc(unsigned int size);
diff -r f5aec9cf9188 -r 3a4e782cc38a st.ti
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/st.ti Fri Mar 02 12:35:34 2007 +0100
@@ -0,0 +1,31 @@
+# terminfo description
+st|simple terminal emulator for X,
+ bce, eo, km, mir, msgr, xenl,
+ cols#80, it#8, lines#24,
+ bel=^G, bold=\E[1m, civis=\E[?25l,
+ clear=\E[H\E[2J, cnorm=\E[?25h, cr=^M,
+ csr=\E[%i%p1%d;%p2%dr, cub=\E[%p1%dD, cub1=\E[D,
+ cud=\E[%p1%dB, cud1=\E[B, cuf=\E[%p1%dC, cuf1=\E[C,
+ cup=\E[%i%p1%d;%p2%dH, cuu=\E[%p1%dA, cuu1=\E[A,
+ cvvis=\E[?25h, dl=\E[%p1%dM,
+ dl1=\E[M, ed=\E[J, el=\E[K, el1=\E[1K,
+ home=\E[H, hpa=\E[%i%p1%dG, ht=^I,
+ il=\E[%p1%dL, il1=\E[L,
+ ind=^J, is1=\E[?47l\E=\E[?1l,
+ ka1=\EOw, ka3=\EOy, kb2=\EOu, kbs=^?,
+ kc1=\EOq, kc3=\EOs, kcbt=\E[Z, kcub1=\E[D, kcud1=\E[B,
+ kcuf1=\E[C, kcuu1=\E[A, kdch1=\E[3~, kel=\E[8\^,
+ kend=\E[8~, kent=\EOM, kf1=\E[11~, kf10=\E[21~,
+ kf11=\E[23~, kf12=\E[24~, kf13=\E[25~, kf14=\E[26~,
+ kf15=\E[28~, kf16=\E[29~, kf17=\E[31~, kf18=\E[32~,
+ kf19=\E[33~, kf2=\E[12~, kf20=\E[34~, kf3=\E[13~,
+ kf4=\E[14~, kf5=\E[15~, kf6=\E[17~, kf7=\E[18~, kf8=\E[19~,
+ kf9=\E[20~, kfnd=\E[1~, khome=\E[7~, kich1=\E[2~,
+ knp=\E[6~, kpp=\E[5~, kslt=\E[4~, op=\E[39;49m, rc=\E8,
+ rev=\E[7m, ri=\EM, rmcup=\E[?47l\E8,
+ rmkx=\E>, rmso=\E[27m,
+ s0ds=\E(B, s1ds=\E(0, sc=\E7, smcup=\E7\E[?47h,
+ smkx=\E=, smso=\E[7m, vpa=\E[%i%p1%dd,
+ colors#8, pairs#64,
+ op=\E[39;49m, setab=\E[%p1%{40}%+%dm,
+ sgr0=\E[m\017, setaf=\E[%p1%{30}%+%dm,
diff -r f5aec9cf9188 -r 3a4e782cc38a ui.c
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/ui.c Fri Mar 02 12:35:34 2007 +0100
@@ -0,0 +1,390 @@
+/* (C)opyright MMVI-MMVII Anselm R. Garbe <garbeam at gmail dot com>
+ * See LICENSE file for license details.
+ */
+#include "st.h"
+#include <assert.h>
+#include <ctype.h>
+#include <stdio.h>
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+#include <X11/cursorfont.h>
+
+Display *Xdisplay;
+Window MainWindow;
+XFontStruct *Xfont;
+XFontStruct *Xboldfont;
+GC Xgc;
+Colormap Xcolmap;
+Cursor Xcursor;
+
+XColor colors[9];
+XColor bgcolors[8];
+XColor cursor_color;
+int Xdepth;
+Visual *Xvisual;
+int Xscreen;
+int window_width;
+int window_height;
+int font_height = -1;
+int font_width = -1;
+
+int
+init_display(void) {
+ Xdisplay = XOpenDisplay(NULL);
+
+ if(!Xdisplay) {
+ fprintf(stderr, "Couldn't open display\n");
+ return -1;
+ }
+ Xscreen = DefaultScreen(Xdisplay);
+ Xdepth = DefaultDepth(Xdisplay, Xscreen);
+ Xcolmap = DefaultColormap(Xdisplay, Xscreen);
+ Xvisual = DefaultVisual(Xdisplay, Xscreen);
+ if(!(Xcursor = XCreateFontCursor(Xdisplay, XC_xterm))) {
+ fprintf(stderr, "Couldn't create cursor\n");
+ return -1;
+ }
+ return 0;
+}
+
+int
+init_window(void) {
+ char *args[] = { NULL };
+ XSizeHints sh;
+ XWMHints wh;
+ XClassHint ch;
+
+ MainWindow = XCreateSimpleWindow(Xdisplay, DefaultRootWindow(Xdisplay), 0, 0,
+ window_width, window_height,
+ 0, DEFAULT_BACKGROUND_COLOR.pixel, DEFAULT_BACKGROUND_COLOR.pixel);
+ XSelectInput(Xdisplay, MainWindow, StructureNotifyMask);
+ XMapWindow(Xdisplay, MainWindow);
+ /* Wait for the window to be mapped */
+ wait_for_specific_event(MapNotify);
+ Xgc = XCreateGC(Xdisplay, MainWindow, 0, NULL);
+ XSetForeground(Xdisplay, Xgc, DEFAULT_FOREGROUND_COLOR.pixel);
+ XSetBackground(Xdisplay, Xgc, DEFAULT_BACKGROUND_COLOR.pixel);
+ XDefineCursor(Xdisplay, MainWindow, Xcursor);
+ sh.x = sh.y = 0;
+ sh.width = window_width;
+ sh.height = window_height;
+ sh.width_inc = font_width;
+ sh.height_inc = font_height;
+ sh.flags = PPosition | PSize | PResizeInc;
+ wh.input = 1;
+ wh.flags = InputHint;
+ ch.res_name = "st";
+ ch.res_class = "st";
+ XSetWMProperties(Xdisplay, MainWindow, NULL, NULL, &args[0], 0, &sh, &wh, &ch);
+ XStoreName(Xdisplay, MainWindow, "st");
+ XSync(Xdisplay, 0);
+ set_text_attrs(-1, 0);
+ XSetFont(Xdisplay, Xgc, Xfont->fid);
+ return 0;
+}
+
+int
+init_font() {
+ Xfont = XLoadQueryFont(Xdisplay, DEFAULT_FONT);
+ if(!Xfont) {
+ fprintf(stderr, "Couldn't open font `%s'\n",
+ DEFAULT_FONT);
+ return -1;
+ }
+ Xboldfont = XLoadQueryFont(Xdisplay, DEFAULT_BOLD_FONT);
+ if(!Xfont) {
+ fprintf(stderr, "Couldn't open font `%s'\n",
+ DEFAULT_FONT);
+ return -1;
+ }
+ if(font_height == -1 || font_width == -1) {
+ font_height = Xfont->ascent + Xfont->descent;
+ font_width = Xfont->max_bounds.width;
+ }
+ screen_rows = screen_rows == -1 ? DEFAULT_NR_ROWS : screen_rows;
+ screen_cols = screen_cols == -1 ? DEFAULT_NR_COLS : screen_cols;
+ window_width = screen_cols * font_width;
+ window_height = screen_rows * font_height;
+ return 0;
+}
+
+void
+wait_for_specific_event(int event_type) {
+ XEvent e;
+ for(;;) {
+ XNextEvent(Xdisplay, &e);
+ if(e.type == event_type)
+ break;
+ }
+}
+
+int
+find_color(XColor *Xc, char *def) {
+ if((XParseColor(Xdisplay, Xcolmap, def, Xc)))
+ if(!(XAllocColor(Xdisplay, Xcolmap, Xc)))
+ return -1;
+ return 0;
+}
+
+int
+init_colors(void) {
+ int color;
+ char *color_values[9] = {
+ "black",
+ "red",
+ "green",
+ "yellow",
+ "blue",
+ "magenta",
+ "#00d0d0",
+ "#d0d0d0",
+ "#404040"
+ };
+
+ char *bgcolor_values[8] = {
+ "black",
+ "#d00000",
+ "#00d000",
+ "#d0d000",
+ "#0000d0",
+ "#d000d0",
+ "#00d0d0",
+ "#d0d0d0"
+ };
+
+ for(color = 0; color < 9; color++)
+ if(find_color(&(colors[color]), color_values[color]))
+ fprintf(stderr, "Couldn't allocate color %d (%s)\n",
+ color, color_values[color]);
+ for(color = 0; color < 8; color++)
+ if(find_color(&(bgcolors[color]), bgcolor_values[color]))
+ fprintf(stderr, "Couldn't allocate bgcolor %d (%s)\n",
+ color, bgcolor_values[color]);
+ if(find_color(&(cursor_color), "lightgreen"))
+ fprintf(stderr, "Couldn't allocate cursor color (%s)\n",
+ "lightgreen");
+ return 0;
+}
+
+void
+win_clear_region(int x1, int y1, int x2, int y2, int color) {
+ XSetBackground(Xdisplay, Xgc, color);
+ XClearArea(Xdisplay, MainWindow, x1, y1, x2 - x1, y2 - y1, 0);
+}
+
+void
+win_draw_string(int row, int col, char *s, int l) {
+ XDrawImageString(Xdisplay, MainWindow, Xgc,
+ col * font_width, row * font_height + Xfont->ascent,
+ s, l);
+}
+
+void
+win_set_window_title(char *title) {
+ XStoreName(Xdisplay, MainWindow, title);
+}
+
+void
+window_scroll_up(int lines) {
+ XCopyArea(Xdisplay, MainWindow, MainWindow, Xgc,
+ 0, lines * font_height,
+ window_width, (screen_rows - lines + 1) * font_height,
+ 0, 0);
+ win_clear_region(0, (screen_rows - lines) * font_height,
+ window_width, screen_rows * font_height,
+ GET_BG_COLOR(text_attrs));
+}
+
+void
+region_scroll_up(int start, int end, int lines) {
+ XCopyArea(Xdisplay, MainWindow, MainWindow, Xgc,
+ 0, (start - 1 + lines) * font_height,
+ window_width, (end - start + 2 - lines) * font_height,
+ 0, (start - 1) * font_height);
+ win_clear_region(0, (end - lines) * font_height,
+ window_width, end * font_height,
+ GET_BG_COLOR(text_attrs));
+}
+
+void
+window_scroll_down(int lines) {
+ XCopyArea(Xdisplay, MainWindow, MainWindow, Xgc,
+ 0, 0,
+ window_width, (screen_rows - lines + 1) * font_height,
+ 0, lines * font_height);
+ win_clear_region(0, 0,
+ window_width, (lines) * font_height,
+ GET_BG_COLOR(text_attrs));
+}
+
+void
+region_scroll_down(int start, int end, int lines) {
+ XCopyArea(Xdisplay, MainWindow, MainWindow, Xgc,
+ 0, (start - 1) * font_height,
+ window_width, (end - start + 2 - lines) * font_height,
+ 0, (start - 1 + lines) * font_height);
+ win_clear_region(0, (start - 1) * font_height,
+ window_width, (start + lines - 1) * font_height,
+ GET_BG_COLOR(text_attrs));
+}
+
+void
+set_text_attrs(text_letter_t l) {
+ int fg, bg;
+
+ if(l.rev ^ l.sel)
+ fg = l.bg, bg = l.fg;
+ else
+ fg = l.fg, bg = l.bg;
+ if(l.bold && fg == 0)
+ XSetForeground(Xdisplay, Xgc, colors[8].pixel);
+ else
+ XSetForeground(Xdisplay, Xgc, colors[fg].pixel);
+ if(l.bold)
+ XSetFont(Xdisplay, Xgc, Xboldfont->fid);
+ else
+ XSetFont(Xdisplay, Xgc, Xfont->fid);
+ XSetBackground(Xdisplay, Xgc, bgcolors[bg].pixel);
+}
+
+int
+exit_display(void) {
+ XCloseDisplay(Xdisplay);
+ return 0;
+}
+
+int
+get_x_filedes(void) {
+ return XConnectionNumber(Xdisplay);
+}
+
+/* Max number of characters to be drawn at once */
+#define MAXRUNLENGTH 100
+
+void
+redraw_screen(void) {
+ int i, j;
+ int a;
+ int sl, start;
+ unsigned char buf[MAXRUNLENGTH];
+
+ a = 0;
+ set_text_attrs(text_screen[0].line[0]);
+ for(i = 0; i < screen_rows; i++) {
+ if(!text_screen[i].needs_update)
+ continue;
+ /* win_clear_region(0, i * font_height, window_width-1, (i + 1) * font_height - 1); */
+ sl = 0; start = 0;
+ for(j = 0; j <= screen_cols; j++) {
+ if(j == screen_cols) {
+ if(sl > 0)
+ win_draw_string(i, start, &buf[0], sl);
+ break;
+ }
+ if(a != ((*(int*)&(text_screen[i].line[j])) & ~0xff) || sl >= sizeof(buf)) {
+ if(sl > 0)
+ win_draw_string(i, start, &buf[0], sl);
+ set_text_attrs(text_screen[i].line[j]);
+ a = (*(int*)&(text_screen[i].line[j])) & ~0xff;
+ start = j;
+ sl = 0;
+ }
+ buf[sl] = text_screen[i].line[j].letter;
+ sl++;
+ }
+ text_screen[i].needs_update = 0;
+ }
+ XFlush(Xdisplay);
+}
+
+void
+force_redraw_screen(void) {
+ int i;
+ for(i = 0; i < screen_rows; i++)
+ text_screen[i].needs_update = 1;
+ redraw_screen();
+}
+
+void
+bell(void) {
+ XBell(Xdisplay, 0);
+}
+
+void
+redraw_region(int x1, int y1, int x2, int y2) {
+ int i, j;
+ int a;
+ int sl, start;
+ unsigned char buf[MAXRUNLENGTH];
+
+ /* win_clear_region((x1 - 1) * font_width, (y1 - 1) * font_height, x2 * font_width, y2 * font_height); */
+ a = (*(int*)&(text_screen[y1-1].line[x1-1])) & ~0xff;
+ set_text_attrs(text_screen[y1-1].line[x1-1]);
+ for(i = y1 - 1; i < y2; i++) {
+ sl = 0; start = x1 - 1;
+ for(j = x1 - 1; j <= x2; j++) {
+ if(j == x2) {
+ if(sl > 0)
+ win_draw_string(i, start, &buf[0], sl);
+ break;
+ }
+ if(a != ((*(int*)&(text_screen[i].line[j])) & ~0xff) || sl >= sizeof(buf)) {
+ if(sl > 0)
+ win_draw_string(i, start, &buf[0], sl);
+ set_text_attrs(text_screen[i].line[j]);
+ a = (*(int*)&(text_screen[i].line[j])) & ~0xff;
+ start = j;
+ sl = 0;
+ }
+ buf[sl] = text_screen[i].line[j].letter;
+ sl++;
+ }
+ text_screen[i].needs_update = 0;
+ }
+ XFlush(Xdisplay);
+}
+
+extern int curr_row, curr_col;
+static int cursor_row, cursor_col;
+static int cursor_is_visible = 0;
+
+void
+show_cursor(void) {
+ if(cursor_is_visible)
+ return;
+ if(!cursor_visible)
+ return;
+ cursor_row = curr_row;
+ cursor_col = curr_col;
+ if(curr_col > screen_cols)
+ cursor_col = screen_cols;
+ if(text_screen[cursor_row-1].line[cursor_col-1].bold)
+ XSetFont(Xdisplay, Xgc, Xboldfont->fid);
+ else
+ XSetFont(Xdisplay, Xgc, Xfont->fid);
+ win_clear_region((cursor_col - 1) * font_width, (cursor_row - 1) * font_height,
+ cursor_col * font_width, cursor_row * font_height,
+ cursor_color.pixel);
+
+ XSetForeground(Xdisplay, Xgc, colors[0].pixel);
+ XSetBackground(Xdisplay, Xgc, cursor_color.pixel);
+ win_draw_string(cursor_row - 1, cursor_col - 1, &(text_screen[cursor_row - 1].line[cursor_col - 1]), 1);
+ XFlush(Xdisplay);
+ cursor_is_visible = 1;
+}
+
+void
+hide_cursor(void) {
+ if(!cursor_is_visible)
+ return;
+ if(cursor_row <= screen_rows && cursor_col <= screen_cols)
+ {
+ set_text_attrs(text_screen[cursor_row-1].line[cursor_col-1]);
+ win_clear_region((cursor_col - 1) * font_width, (cursor_row - 1) * font_height,
+ cursor_col * font_width, cursor_row * font_height,
+ GET_BG_COLOR(text_screen[cursor_row-1].line[cursor_col-1]));
+ win_draw_string(cursor_row - 1, cursor_col - 1, &(text_screen[cursor_row - 1].line[cursor_col - 1]), 1);
+ XFlush(Xdisplay);
+ }
+ cursor_is_visible = 0;
+}
Received on Fri Mar 02 2007 - 12:37:14 UTC
This archive was generated by hypermail 2.2.0 : Sun Jul 13 2008 - 15:56:22 UTC