/* hexedit.c - Hexadecimal file editor * * Copyright 2015 Rob Landley * * No standard USE_HEXEDIT(NEWTOY(hexedit, "<1>1r", TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_LOCALE)) config HEXEDIT bool "hexedit" default y help usage: hexedit FILENAME Hexadecimal file editor. -r Read only (display but don't edit) */ #define FOR_hexedit #include "toys.h" GLOBALS( char *data; long long len, base; int numlen; unsigned height; ) static void esc(char *s) { printf("\033[%s", s); } static void jump(int x, int y) { char s[32]; sprintf(s, "%d;%dH", y+1, x+1); esc(s); } static void fix_terminal(void) { set_terminal(1, 0, 0); esc("?25h"); esc("0m"); jump(0, 999); esc("K"); } static void sigttyreset(int i) { fix_terminal(); // how do I re-raise the signal so it dies with right signal info for wait()? _exit(127); } // Render all characters printable, using color to distinguish. static void draw_char(char broiled) { if (broiled<32 || broiled>=127) { if (broiled>127) { esc("2m"); broiled &= 127; } if (broiled<32 || broiled==127) { esc("7m"); if (broiled==127) broiled = 32; else broiled += 64; } printf("%c", broiled); esc("0m"); } else printf("%c", broiled); } static void draw_tail(void) { int i = 0, width = 0, w, len; char *start = *toys.optargs, *end; jump(0, TT.height); esc("K"); // First time, make sure we fit in 71 chars (advancing start as necessary). // Second time, print from start to end, escaping nonprintable chars. for (i=0; i<2; i++) { for (end = start; *end;) { wchar_t wc; len = mbrtowc(&wc, end, 99, 0); if (len<0 || wc<32 || (w = wcwidth(wc))<0) { len = w = 1; if (i) draw_char(*end); } else if (i) fwrite(end, len, 1, stdout); end += len; if (!i) { width += w; while (width > 71) { len = mbrtowc(&wc, start, 99, 0); if (len<0 || wc<32 || (w = wcwidth(wc))<0) len = w = 1; width -= w; start += len; } } } } } static void draw_line(long long yy) { int x, xx = 16; yy = (TT.base+yy)*16; if (yy+xx>=TT.len) xx = TT.len-yy; if (yy1) printf("%02X", cc); else for (i=0; i<2;) { if (side==i) esc("32m"); printf("%X", (cc>>(4*(1&++i)))&15); } esc("0m"); jump(TT.numlen+17*3+xx, yy); draw_char(cc); } #define KEY_UP 256 #define KEY_DOWN 257 #define KEY_RIGHT 258 #define KEY_LEFT 259 #define KEY_PGUP 260 #define KEY_PGDN 261 #define KEY_HOME 262 #define KEY_END 263 #define KEY_INSERT 264 void hexedit_main(void) { // up down right left pgup pgdn home end ins char *keys[] = {"\033[A", "\033[B", "\033[C", "\033[D", "\033[5~", "\033[6~", "\033OH", "\033OF", "\033[2~", 0}; long long pos; int x, y, i, side = 0, key, ro = toys.optflags&FLAG_r, fd = xopen(*toys.optargs, ro ? O_RDONLY : O_RDWR); TT.height = 25; terminal_size(0, &TT.height); if (TT.height) TT.height--; sigatexit(sigttyreset); esc("0m"); esc("?25l"); fflush(0); set_terminal(1, 1, 0); if ((TT.len = fdlength(fd))<0) error_exit("bad length"); if (sizeof(long)==32 && TT.len>SIZE_MAX) TT.len = SIZE_MAX; // count file length hex digits, rounded up to multiple of 4 for (pos = TT.len, TT.numlen = 0; pos; pos >>= 4, TT.numlen++); TT.numlen += (4-TT.numlen)&3; TT.data = mmap(0, TT.len, PROT_READ|(PROT_WRITE*!ro), MAP_SHARED, fd, 0); draw_page(); y = x = 0; for (;;) { // Get position within file, trimming if we overshot end. pos = 16*(TT.base+y)+x; if (pos>=TT.len) { pos = TT.len-1; x = pos&15; y = (pos/16)-TT.base; } // Display cursor highlight(x, y, ro ? 3 : side); xprintf(""); // Wait for next key key = scan_key(toybuf, keys, 1); // Exit for q, ctrl-c, ctrl-d, escape, or EOF if (key==-1 || key==3 || key==4 || key==27 || key=='q') break; highlight(x, y, 2); if (key>='a' && key<='f') key-=32; if (!ro && ((key>='0' && key<='9') || (key>='A' && key<='F'))) { i = key - '0'; if (i>9) i -= 7; TT.data[pos] &= 15<<(4*side); TT.data[pos] |= i<<(4*!side); highlight(x, y, ++side); if (side==2) { side = 0; if (++pos255) side = 0; if (key==KEY_UP) { if (--y<0) { if (TT.base) { TT.base--; esc("1T"); draw_tail(); jump(0, 0); draw_line(0); } y = 0; } } else if (key==KEY_DOWN) { if (y == TT.height-1 && (pos|15)+1=TT.height) y--; } else if (key==KEY_RIGHT) { if (x<15 && pos+1=TT.len) TT.base=(TT.len-1)/16; while ((TT.base+y)*16>=TT.len) y--; if (16*(TT.base+y)+x>=TT.len) x = (TT.len-1)&15; draw_page(); } else if (key==KEY_HOME) { TT.base = 0; x = 0; draw_page(); } else if (key==KEY_END) { TT.base=(TT.len-1)/16; x = (TT.len-1)&15; draw_page(); } } munmap(TT.data, TT.len); close(fd); fix_terminal(); }