diff --git a/apps/attacher.c b/apps/attacher.c index 37b0359..4603ba8 100644 --- a/apps/attacher.c +++ b/apps/attacher.c @@ -5,6 +5,7 @@ #include #include +#include #include #include #include @@ -12,26 +13,72 @@ #include #include "email_common.h" +#define PROMPT_ROW 21 + +#define EOL '\r' + #define BELL 0x07 #define BACKSPACE 0x08 #define RETURN 0x0d #define NORMAL 0x0e #define INVERSE 0x0f +#define ESC 0x1b #define DELETE 0x7f #define NETBUFSZ 1500 #define LINEBUFSZ 1000 // According to RFC2822 Section 2.1.1 (998+CRLF) #define READSZ 1024 // Must be less than NETBUFSZ to fit in buf[] +#define IOBUFSZ 4096 -static unsigned char buf[NETBUFSZ+1]; // One extra byte for null terminator -static char linebuf[LINEBUFSZ]; -static char userentry[80]; -static uint8_t quit_to_email; // If 1, launch EMAIL.SYSTEM on quit -static char filename[80]; +unsigned char buf[NETBUFSZ+1]; // One extra byte for null terminator +char linebuf[LINEBUFSZ]; +char userentry[80]; +uint8_t quit_to_email; // If 1, launch EMAIL.SYSTEM on quit +char filename[80]; +char iobuf[IOBUFSZ]; #define ERR_NONFATAL 0 #define ERR_FATAL 1 +/* + * Annoying beep + */ +void beep(void) { + uint8_t *p = (uint8_t*)0xc030; // Speaker + uint8_t junk; + uint16_t i; + for (i = 0; i < 200; ++i) { + junk = *p; + for (junk = 0; junk < 50; ++junk); // Reduce pitch + } +} + +/* + * Clear to EOL + */ +void clreol(void) { + uint8_t x = wherex(), y = wherey(); + cclear(80 - x); + gotoxy(x, y); +} + +/* + * Clear line + */ +void clrline(void) { + uint8_t x = wherex(); + gotox(0); + cclear(80); + gotox(x); +} + +/* + * Put cursor at beginning of PROMPT_ROW + */ +void goto_prompt_row(void) { + gotoxy(0, PROMPT_ROW); +} + /* * Show error messages */ @@ -170,23 +217,28 @@ uint16_t encode_base64(char *p, char *q, uint16_t len) { } /* - * Prompt for a name, store it in userentry + * Prompt for a name in the bottom line of the screen * Returns number of chars read. * prompt - Message to display before > prompt * is_file - if 1, restrict chars to those allowed in ProDOS filename - * Returns number of chars read + * Returns number of chars read, or 255 if ESC pressed */ uint8_t prompt_for_name(char *prompt, uint8_t is_file) { uint16_t i; char c; - printf("%s>", prompt); + cursor(0); + goto_prompt_row(); + clreol(); + revers(1); + cprintf("%s>", prompt); + revers(0); + gotox(2 + strlen(prompt)); i = 0; while (1) { c = cgetc(); - if (is_file && !isalnum(c) && - (c != RETURN) && (c != BACKSPACE) && (c != DELETE) && - (c != '.') && (c != '/')) { - putchar(BELL); + if (is_file && !isalnum(c) && (c != RETURN) && (c != BACKSPACE) && + (c != DELETE) && (c != ESC) && (c != '.') && (c != '/')) { + beep(); continue; } switch (c) { @@ -195,15 +247,19 @@ uint8_t prompt_for_name(char *prompt, uint8_t is_file) { case BACKSPACE: case DELETE: if (i > 0) { - putchar(BACKSPACE); - putchar(' '); - putchar(BACKSPACE); + gotox(wherex() - 1); + cputc(' '); + gotox(wherex() - 1); --i; } else - putchar(BELL); + beep(); break; + case ESC: + userentry[0] = '\0'; + i = 255; + goto esc_pressed; default: - putchar(c); + cputc(c); userentry[i++] = c; } if (i == 79) @@ -211,9 +267,294 @@ uint8_t prompt_for_name(char *prompt, uint8_t is_file) { } done: userentry[i] = '\0'; +esc_pressed: + clrline(); + cursor(1); return i; } +/* + * Prompt ok? + * Returns 0 for yes + * 1 for no + * 2 for ESC + */ +char prompt_okay(char *msg) { + char c; + cursor(0); + goto_prompt_row(); + clreol(); + revers(1); + cprintf("%s (y/n/ESC)", msg); + revers(0); + while (1) { + c = cgetc(); + if ((c == 'y') || (c == 'Y') || (c == 'n') || (c == 'N') || c == ESC) + break; + beep(); + } + if ((c == 'y') || (c == 'Y')) + c = 0; + else if (c == ESC) + c = 2; + else + c = 1; + return c; +} + +struct tabent { + char name[16]; + uint8_t type; + uint32_t size; +} *entry; + +#define FILELINES 16 + +/* + * Draw one line in file chooser UI + * i - index of file to draw + * first - index of first file on screen + * selected - index of currently selected file + * entries - total number of file entries in directory + */ +void file_ui_draw(uint16_t i, uint16_t first, uint16_t selected, uint16_t entries) { + struct tabent *entry; + gotoxy(5, i - first + 6); + if (i < entries) { + entry = (struct tabent*)iobuf + i; + if (entry->type == 0x0f) { + sprintf(userentry, "[ %s ] ", entry->name); + userentry[34] = '\0'; + } else { + sprintf(userentry, " %s ", entry->name); + sprintf(&userentry[18], " %8lu ", entry->size); + switch (entry->type) { + case 0x04: + sprintf(&userentry[30], "TXT "); + break; + case 0x06: + sprintf(&userentry[30], "BIN "); + break; + case 0x19: + sprintf(&userentry[30], "ADB "); + break; + case 0x1a: + sprintf(&userentry[30], "AWP "); + break; + case 0x1b: + sprintf(&userentry[30], "ASP "); + break; + case 0xfc: + sprintf(&userentry[30], "BAS "); + break; + case 0xff: + sprintf(&userentry[30], "SYS "); + break; + default: + sprintf(&userentry[30], "$%02x ", entry->type); + } + } + if (i == selected) + revers(1); + cputs(userentry); + if (i == selected) + revers(0); + } else { + strcpy(userentry, " "); + cputs(userentry); + } +} + +/* + * File chooser UI + * first - index of first file on screen + * selected - index of currently selected file + * entries - total number of file entries in directory + */ +void file_ui_draw_all(uint16_t first, uint16_t selected, uint16_t entries) { + uint16_t i; + uint16_t last = first + FILELINES; + for (i = first; i < last; ++i) + file_ui_draw(i, first, selected, entries); +} + +/* + * Perform ProDOS MLI ON_LINE call to + * write all online volume names into iobuf[] + * Return the number of entries + */ +uint16_t online(void) { + uint16_t entries = 0; + struct tabent *entry; + uint8_t i, j, len; + __asm__("lda #$00"); // All devices + __asm__("sta mliparam + 1"); + __asm__("lda #<%v", iobuf); // iobuf LSB + __asm__("sta mliparam + 2"); + __asm__("lda #>%v", iobuf); // iobuf MSB + __asm__("ina"); // Bump up 256 bytes into iobuf + __asm__("sta mliparam + 3"); + __asm__("lda #$c5"); // ON_LINE + __asm__("ldx #$02"); // Two parms + __asm__("jsr callmli"); + entry = (struct tabent*)iobuf; + for (i = 0; i < 16; ++i) { + len = iobuf[256 + i * 16] & 0x0f; + if (len > 0) { + entry->type = 0x0f; + entry->name[0] = '/'; + for (j = 0; j < len; ++j) + entry->name[j + 1] = iobuf[256 + i * 16 + j + 1]; + entry->name[j + 1] = '\0'; + ++entry; + ++entries; + } + } + return entries; +} + +/* + * File chooser UI + * Leaves file name in userentry[], or empty string if error/cancel + * msg1 - Message for top line + * msg2 - Message for second line + * msg3 - Message for third line + */ +void file_ui(char *msg1, char *msg2, char *msg3) { + struct tabent *entry; + DIR *dp; + struct dirent *ent; + char c; + uint16_t entries, current, first; + uint8_t toplevel = 0; +restart: + clrscr(); + gotoxy(0,0); + revers(1); + cprintf("%s", msg1); + revers(0); + gotoxy(0,1); + cprintf("%s", msg2); + gotoxy(0,2); + cprintf("%s", msg3); + getcwd(userentry, 80); + gotoxy(0,4); + revers(1); + cprintf("%s", (toplevel ? "Volumes" : userentry)); + revers(0); + entries = current = first = 0; + if (toplevel) { + entries = online(); + } else { + entry = (struct tabent*)iobuf; + strcpy(entry->name, ".."); // Add fake '..' entry + entry->type = 0x0f; + ++entry; + ++entries; + cursor(0); + dp = opendir("."); + while (1) { + ent = readdir(dp); + if (!ent) + break; + memcpy(entry->name, ent->d_name, 16); + entry->type = ent->d_type; + entry->size = ent->d_size; + ++entry; + ++entries; + if ((char*)entry > (char*)iobuf + IOBUFSZ - 100) { + beep(); + break; + } + } + closedir(dp); + } +redraw: + file_ui_draw_all(first, current, entries); + while (1) { + c = cgetc(); + switch (c) { + case 0x0b: // Up + if (current > 0) + --current; + if (current < first) { + if (first > FILELINES) + first -= FILELINES; + else + first = 0; + goto redraw; + } + file_ui_draw(current, first, current, entries); + file_ui_draw(current + 1, first, current, entries); + break; + case 0x0a: // Down + if (current < entries - 1) + ++current; + if (current >= first + FILELINES) { + first += FILELINES; + goto redraw; + } + file_ui_draw(current - 1, first, current, entries); + file_ui_draw(current, first, current, entries); + break; + case EOL: + entry = (struct tabent*)iobuf + current; + switch (entry->type) { + case 0x0f: // Directory + getcwd(userentry, 80); + if (strcmp(entry->name, "..") == 0) { + for (c = strlen(userentry); c > 0; --c) + if (userentry[c] == '/') { + userentry[c] = '\0'; + break; + } + if (c <= 1) + toplevel = 1; + else + chdir(userentry); + goto restart; + } else { + if (toplevel) { + strcpy(userentry, entry->name); + chdir(userentry); + } else { + getcwd(userentry, 80); + strcat(userentry, "/"); + strcat(userentry, entry->name); + chdir(userentry); + } + toplevel = 0; + goto restart; + } + break; + default: // All other file types + strcpy(userentry, entry->name); + goto done; + break; + } + break; + case ESC: + strcpy(userentry, ""); + goto done; + break; + case 0x09: // Tab + if (prompt_for_name("Enter filename", 0) == 255) + goto restart; // ESC pressed + if (userentry[0] == '/') // Absolute path + goto done; + getcwd(linebuf, 80); // Otherwise relative path + strcat(linebuf, "/"); + strcat(linebuf, userentry); + strcpy(userentry, linebuf); + strcpy(linebuf, ""); + goto done; + } + } +done: + clrscr(); + cursor(1); +} + /* * Optionally attach files to outgoing email. * filename - Name of file containing email message @@ -222,7 +563,8 @@ void attach(char *fname) { FILE *fp, *fp2, *destfp; uint16_t chars, i; uint32_t size; - char *s; + char *s, c; + uint8_t attachcount = 0; videomode(VIDEOMODE_80COL); printf("%c%s ATTACHER%c\n\n", 0x0f, PROGNAME, 0x0e); fp = fopen(fname, "rb+"); @@ -233,7 +575,7 @@ void attach(char *fname) { if (!destfp) error(ERR_FATAL, "Can't open TMPFILE"); - printf("Copying email content ... "); // Space is for spinner to eat + printf(" Copying email content ... "); // Space is for spinner to eat size = 0; while ((chars = get_line(fp, linebuf)) != -1) { size += chars; @@ -255,28 +597,55 @@ void attach(char *fname) { } spinner(size, 1); - printf("\rEnter the filename or filenames to attach to the email.\n"); - printf("An empty entry means you are done attaching files.\n\n"); - while (prompt_for_name("File to attach", 1)) { + while (1) { + cursor(0); + if (attachcount == 1) + printf("\n There is currently 1 attachment.\n\n", attachcount); + else + printf("\n There are currently %u attachments.\n\n", attachcount); + goto_prompt_row(); + printf("%c A)dd attachment | D)one with attachments | %c", INVERSE, NORMAL); + printf("%c | | %c", INVERSE, NORMAL); +ask: + c = cgetc(); + if ((c == 'D') || (c == 'd')) + goto done; + if ((c != 'A') && (c != 'a')) { + beep(); + goto ask; + } + sprintf(userentry, "Attachment #%u : Select a File to Attach", ++attachcount); + file_ui(userentry, + "", + " Select file from tree browser, or [Tab] to enter filename. [Esc] cancels."); + printf("%c%s ATTACHER%c\n\n", 0x0f, PROGNAME, 0x0e); + if (strlen(userentry) == 0) { + beep(); + printf("No file was selected!\n"); + continue; + } s = strrchr(userentry, '/'); if (!s) s = userentry; else s = s + 1; // Character after the slash if (strlen(s) == 0) { - error(ERR_NONFATAL, "Illegal trailing /"); + beep(); + printf("Illegal trailing slash!\n"); continue; } fp2 = fopen(userentry, "rb"); if (!fp2) { - error(ERR_NONFATAL, "Can't open %s", userentry); + beep(); + sprintf(userentry, "Can't open %s!\n", userentry); + printf(userentry); continue; } fprintf(destfp, "\r--a2forever\r"); fprintf(destfp, "Content-Type: application/octet-stream\r"); fprintf(destfp, "Content-Transfer-Encoding: base64\r"); fprintf(destfp, "Content-Disposition: attachment; filename=%s;\r\r", s); - printf("\r "); // Space is for spinner to eat + printf(" Attaching '%s' ... ", userentry); // Space is for spinner to eat size = 0; do { i = fread(buf, 1, 72 * 3 / 4 * 5, fp2); // Multiple of 72*3/4 bytes @@ -290,6 +659,7 @@ void attach(char *fname) { fclose(fp2); spinner(size, 1); } +done: fprintf(destfp, "\r--a2forever--\r"); fclose(fp); fclose(destfp); @@ -300,16 +670,14 @@ void attach(char *fname) { } /* - * Load EMAIL.SYSTEM to $2000 and jump to it - * (This code is in language card space so it can't possibly be trashed) + * Exec EMAIL.SYSTEM */ -#pragma code-name (push, "LC") void load_email(void) { revers(0); clrscr(); - exec("EMAIL.SYSTEM", NULL); // Assume it is in current directory + chdir(cfg_instdir); + exec("EMAIL.SYSTEM", NULL); } -#pragma code-name (pop) void main(int argc, char *argv[]) { readconfigfile();