/* Prefs.c */ #include "Sys.h" #include "Curses.h" #include #include #include #include "Util.h" #include "Cmds.h" #include "Progress.h" #include "Hostwin.h" #include "Prefs.h" #include "RCmd.h" #include "Bookmark.h" #include "Main.h" #ifdef USE_CURSES #include "WGets.h" /* This is the full-screen window that pops up when you run the * preferences editor. */ WINDOW *gPrefsWin = NULL; extern void WAddCenteredStr(WINDOW *w, int y, char *str); void WAttr(WINDOW *w, int attr, int on); #endif /* USE_CURSES */ jmp_buf gPrefsWinJmp; extern int gWinInit; extern string gAnonPassword; extern int gMayUTime, gStartupMsgs, gRemoteMsgs, gBlankLines; extern int gAnonOpen, gWhichProgMeter, gMaxLogSize, gMaxBookmarks; extern int gDefaultVisualMode, gTotalRuns, gRememberLCWD; extern int gNetworkTimeout, gTrace, gPreferredDataPortMode; extern longstring gLocalCWD, gPager, gDownloadDir; extern long gTotalXferKiloBytes, gTotalXferHSeconds; extern int gMarkTrailingSpace; PrefOpt gPrefOpts[] = { { "anonopen", "Default open mode:", kToggleMsg, PREFBOOL(gAnonOpen) }, { "anonpass", "Anonymous password:", "Type new anonymous password, or hit to continue.", PREFSTR(gAnonPassword, kNotOkayIfEmpty, kGetAndEcho) }, { "blank-lines", "Blank lines between cmds:", kToggleMsg, PREFBOOL(gBlankLines) }, { "ftp-mode", "Default FTP mode:", kToggleMsg, PREFTOGGLE(gPreferredDataPortMode, kSendPortMode, kFallBackToSendPortMode) }, { "logsize", "User log size:", "Enter the maximum number of bytes allowed for log file, or 0 for no log.", PREFINT(gMaxLogSize) }, { "maxbookmarks", "Max bookmarks to save:", "Enter the max number of bookmarks for bookmarks file, or 0 for no limit.", PREFINT(gMaxBookmarks) }, { "pager", "Pager:", "Type the pathname of the program you use to view text a page at a time.", PREFSTR(gPager, kOkayIfEmpty, kGetAndEcho) }, { "progress-meter", "Progress meter:", kToggleMsg, PREFTOGGLE(gWhichProgMeter, kPrNone, kPrLast) }, { "remote-msgs", "Remote messages:", kToggleMsg, PREFTOGGLE(gRemoteMsgs, kAllRmtMsgs, (kNoChdirMsgs | kNoConnectMsg)) }, #if 0 { "restore-lcwd", "Restore local CWD:", kToggleMsg, PREFBOOL(gRememberLCWD) }, #endif { "startup-lcwd", "Startup in Local Dir:", "Type directory to always lcd to, or hit to not lcd at startup.", PREFSTR(gDownloadDir, kOkayIfEmpty, kGetAndEcho) }, { "startup-msgs", "Startup messages:", kToggleMsg, PREFTOGGLE(gStartupMsgs, kNoStartupMsgs, (kStartupMsg | kTips)) }, { "timeout", "Network timeout:", "Enter the maximum amount of time to wait on a connection before giving up.", PREFINT(gNetworkTimeout) }, { "trace", "Trace logging:", kToggleMsg, PREFBOOL(gTrace) }, { "utime", "File timestamps:", kToggleMsg, PREFBOOL(gMayUTime) }, { "visual", "Screen graphics:", kToggleMsg, PREFBOOL(gDefaultVisualMode) }, }; /* These are options that are for information only, or options I don't feel * like wasting screen space on in the prefs editor. */ PrefOpt gNonEditPrefOpts[] = { { "show-trailing-space", "Show trailing space:", kToggleMsg, PREFBOOL(gMarkTrailingSpace) }, { "total-runs", NULL, NULL, PREFINT(gTotalRuns) }, { "total-xfer-hundredths-of-seconds", NULL, NULL, PREFINT(gTotalXferHSeconds) }, { "total-xfer-kbytes", NULL, NULL, PREFINT(gTotalXferKiloBytes) }, }; int gNumEditablePrefOpts = ((int)(sizeof(gPrefOpts) / sizeof(PrefOpt))); #define kNumNonEditablePrefOpts ((int)(sizeof(gNonEditPrefOpts) / sizeof(PrefOpt))) void TogglePref(int *val, int min, int max) { int newVal; newVal = *val + 1; if (newVal > max) newVal = min; *val = newVal; } /* TogglePref */ void GetPrefSetting(char *dst, size_t siz, int item) { char *cp; string str; size_t len; *dst = '\0'; switch (item) { case kAnonOpenPrefsWinItem: cp = gAnonOpen ? "anonymous" : "user & password"; (void) Strncpy(dst, cp, siz); break; case kAnonPassPrefsWinItem: (void) Strncpy(dst, gAnonPassword, siz); break; case kBlankLinesWinItem: cp = gBlankLines ? "yes" : "no"; (void) Strncpy(dst, cp, siz); break; case kFTPModePrefsWinItem: if (gPreferredDataPortMode == kFallBackToSendPortMode) cp = "Passive, but fall back to port if needed"; else if (gPreferredDataPortMode == kPassiveMode) cp = "Passive FTP only (PASV)"; else cp = "Send-Port FTP only (PORT)"; (void) Strncpy(dst, cp, siz); break; case kLogSizePrefsWinItem: if (gMaxLogSize == 0) (void) Strncpy(str, "no logging", siz); else sprintf(str, "%d", gMaxLogSize); (void) Strncpy(dst, str, siz); break; case kMaxBookmarksWinItem: if (gMaxBookmarks == kNoBookmarkLimit) (void) Strncpy(str, "unlimited", siz); else sprintf(str, "%d", gMaxBookmarks); (void) Strncpy(dst, str, siz); break; case kPagerPrefsWinItem: if ((len = strlen(gPager)) > 47) { /* Abbreviate a long program path. */ STRNCPY(str, "..."); STRNCAT(str, gPager + len - 44); } else { if (gPager[0] == '\0') STRNCPY(str, "(none)"); else STRNCPY(str, gPager); } (void) Strncpy(dst, str, siz); break; case kProgressPrefsWinItem: switch (gWhichProgMeter) { case kPrPercent: cp = "percent meter"; break; case kPrPhilBar: cp = "bar graph"; break; case kPrKBytes: cp = "kilobyte meter"; break; case kPrDots: cp = "hash dots"; break; case kPrStatBar: cp = "stat meter"; break; default: cp = "no progress reports"; } (void) Strncpy(dst, cp, siz); break; case kRmtMsgsPrefsWinItem: if ((gRemoteMsgs & (kNoChdirMsgs | kNoConnectMsg)) == (kNoChdirMsgs | kNoConnectMsg)) cp = "ignore startup messages and chdir messages"; else if ((gRemoteMsgs & kNoChdirMsgs) != 0) cp = "ignore change-directory messages"; else if ((gRemoteMsgs & kNoConnectMsg) != 0) cp = "ignore startup messages"; else cp = "allow startup messages and chdir messages"; (void) Strncpy(dst, cp, siz); break; case kStartupLCWDWinItem: cp = (gDownloadDir[0] != '\0') ? gDownloadDir : "(none)"; (void) Strncpy(dst, cp, siz); break; case kStartupMsgsPrefsWinItem: if ((gStartupMsgs & (kStartupMsg | kTips)) == (kStartupMsg | kTips)) cp = "headers and tips"; else if ((gStartupMsgs & kStartupMsg) != 0) cp = "headers only"; else if ((gStartupMsgs & kTips) != 0) cp = "tips only"; else cp = "no startup messages"; (void) Strncpy(dst, cp, siz); break; case kTimeoutPrefsWinItem: sprintf(dst, "%d", gNetworkTimeout); break; case kTracePrefsWinItem: if (gTrace) { cp = "yes"; OpenTraceLog(); } else { cp = "no"; CloseTraceLog(); } (void) Strncpy(dst, cp, siz); break; case kUTimePrefsWinItem: cp = gMayUTime ? "try to preserve file timestamps" : "do not preserve file timestamps"; (void) Strncpy(dst, cp, siz); break; case kVisualPrefsWinItem: cp = gDefaultVisualMode == 1 ? "visual (curses)" : "line-oriented"; (void) Strncpy(dst, cp, siz); break; } } /* GetPrefSetting */ #ifdef USE_CURSES /* Draws the screen when we're using the host editor's main screen. * You can can specify whether to draw each character whether it needs * it or not if you like. */ void UpdatePrefsWindow(int uptAll) { if (uptAll) { touchwin(gPrefsWin); } wnoutrefresh(gPrefsWin); doupdate(); } /* UpdatePrefsWindow */ /* This displays a message in the preferences window. */ void PrefsWinWinMsg(char *msg) { int maxx, maxy; getmaxyx(gPrefsWin, maxy, maxx); mvwaddstr(gPrefsWin, maxy - 2, 0, msg); wclrtoeol(gPrefsWin); wmove(gPrefsWin, maxy - 1, 0); wrefresh(gPrefsWin); } /* PrefsWinWinMsg */ /* Prompts for a line of input. */ void PrefsWinGetStr(char *dst, int canBeEmpty, int canEcho) { string str; WGetsParams wgp; int maxx, maxy; getmaxyx(gPrefsWin, maxy, maxx); WAttr(gPrefsWin, kBold, 1); mvwaddstr(gPrefsWin, maxy - 1, 0, "> "); WAttr(gPrefsWin, kBold, 0); wclrtoeol(gPrefsWin); wrefresh(gPrefsWin); curs_set(1); wgp.w = gPrefsWin; wgp.sy = maxy - 1; wgp.sx = 2; wgp.fieldLen = maxx - 3; wgp.dst = str; wgp.dstSize = sizeof(str); wgp.useCurrentContents = 0; wgp.echoMode = canEcho ? wg_RegularEcho : wg_BulletEcho; wgp.history = wg_NoHistory; (void) wg_Gets(&wgp); cbreak(); /* wg_Gets turns off cbreak and delay. */ TraceMsg("[%s]\n", str); /* See if the user just hit return. We may not want to overwrite * the dst here, which would make it an empty string. */ if ((wgp.changed) || (canBeEmpty == kOkayIfEmpty)) strcpy(dst, str); wmove(gPrefsWin, maxy - 1, 0); wclrtoeol(gPrefsWin); wrefresh(gPrefsWin); curs_set(0); } /* PrefsWinGetStr */ /* Prompts for an integer of input. */ void PrefsWinGetNum(int *dst) { string str; WGetsParams wgp; int maxx, maxy; getmaxyx(gPrefsWin, maxy, maxx); WAttr(gPrefsWin, kBold, 1); mvwaddstr(gPrefsWin, maxy - 1, 0, "> "); WAttr(gPrefsWin, kBold, 0); wclrtoeol(gPrefsWin); wrefresh(gPrefsWin); curs_set(1); wgp.w = gPrefsWin; wgp.sy = maxy - 1; wgp.sx = 2; wgp.fieldLen = maxx - 3; wgp.dst = str; wgp.dstSize = sizeof(str); wgp.useCurrentContents = 0; wgp.echoMode = wg_RegularEcho; wgp.history = wg_NoHistory; (void) wg_Gets(&wgp); cbreak(); /* wg_Gets turns off cbreak and delay. */ TraceMsg("[%s]\n", str); AtoIMaybe(dst, str); wmove(gPrefsWin, maxy - 1, 0); wclrtoeol(gPrefsWin); wrefresh(gPrefsWin); curs_set(0); } /* PrefsWinGetNum */ /* This is the meat of the preferences window. We can selectively update * portions of the window by using a bitmask with bits set for items * we want to update. */ void PrefsWinDraw(int flags, int hilite) { int i; string value; char spec[32]; int maxx, maxy; getmaxyx(gPrefsWin, maxy, maxx); /* Draw the keys the user can type in reverse text. */ WAttr(gPrefsWin, kReverse, 1); for (i = kFirstPrefsWinItem; i <= kLastPrefsWinItem; i++) { if (TESTBIT(flags, i)) mvwaddch(gPrefsWin, 2 + i, 2, 'A' + i); } /* The "quit" item is a special item that is offset a line, and * always has the "X" key assigned to it. */ i = kQuitPrefsWinItem; if (TESTBIT(flags, i)) mvwaddch(gPrefsWin, 3 + i, 2, 'X'); WAttr(gPrefsWin, kReverse, 0); /* We can use this to hilite a whole line, to indicate to the * user that a certain item is being edited. */ if (hilite) WAttr(gPrefsWin, kReverse, 1); sprintf(spec, " %%-28s%%-%ds", maxx - 28 - 6); for (i = kFirstPrefsWinItem; i <= kLastPrefsWinItem; i++) { if (TESTBIT(flags, i)) { GetPrefSetting(value, sizeof(value), i); mvwprintw(gPrefsWin, i + 2 , 3, spec, gPrefOpts[i].label, value); wclrtoeol(gPrefsWin); } } if (TESTBIT(flags, kQuitPrefsWinItem)) { mvwprintw(gPrefsWin, kQuitPrefsWinItem + 3, 3, " %-28s", "(Done editing)" ); wclrtoeol(gPrefsWin); } if (hilite) WAttr(gPrefsWin, kReverse, 0); wmove(gPrefsWin, maxy - 1, 0); wrefresh(gPrefsWin); } /* PrefsWinDraw */ /* The user can hit space to change values. For these toggle * functions we do an update each time so the user can see the change * immediately. */ void PrefsWinToggle(int *val, int bitNum, int min, int max) { int c; while (1) { c = wgetch(gPrefsWin); TraceMsg("[%c, 0x%x]\n", c, c); if ((c == 'q') || (c == 10) || (c == 13) #ifdef KEY_ENTER || (c == KEY_ENTER) #endif ) break; else if (isspace(c)) { TogglePref(val, min, max); PrefsWinDraw(BIT(bitNum), kHilite); } } } /* PrefsWinToggle */ /*ARGSUSED*/ void SigIntPrefsWin(void) { alarm(0); longjmp(gPrefsWinJmp, 1); } /* SigIntPrefsWin */ #endif /* USE_CURSES */ /* Runs the preferences editor. */ int PrefsWindow(void) { #ifdef USE_CURSES int c, field, i; if (gWinInit) { gPrefsWin = newwin(LINES, COLS, 0, 0); if (gPrefsWin == NULL) return (kCmdErr); if (setjmp(gPrefsWinJmp) == 0) { /* Gracefully cleanup the screen if the user ^C's. */ SIGNAL(SIGINT, SigIntPrefsWin); curs_set(0); cbreak(); /* leaveok(gPrefsWin, TRUE); * Not sure if I like this... */ /* Set the clear flag for the first update. */ wclear(gPrefsWin); WAttr(gPrefsWin, kBold, 1); WAddCenteredStr(gPrefsWin, 0, "Preferences"); WAttr(gPrefsWin, kBold, 0); PrefsWinDraw(kAllWindowItems, kNoHilite); while (1) { PrefsWinWinMsg("Select an item to edit by typing its corresponding letter."); c = wgetch(gPrefsWin); TraceMsg("[%c, 0x%x]\n", c, c); if (islower(c)) c = toupper(c); if (!isupper(c)) continue; if (c == 'X') break; field = c - 'A'; i = field; if (i > kLastPrefsWinItem) continue; /* Hilite the current item to edit. */ PrefsWinDraw(BIT(i), kHilite); /* Print the instructions. */ PrefsWinWinMsg(gPrefOpts[i].msg); switch (gPrefOpts[i].type) { case kPrefInt: PrefsWinGetNum((int *) gPrefOpts[i].storage); break; case kPrefToggle: PrefsWinToggle((int *) gPrefOpts[i].storage, i, gPrefOpts[i].min, gPrefOpts[i].max); break; case kPrefStr: PrefsWinGetStr((char *) gPrefOpts[i].storage, gPrefOpts[i].min, /* Used for Empty */ gPrefOpts[i].max /* Used for Echo */ ); break; } /* Update and unhilite it. */ PrefsWinDraw(BIT(field), kNoHilite); } } delwin(gPrefsWin); gPrefsWin = NULL; UpdateScreen(1); flushinp(); nocbreak(); curs_set(1); } #endif /* USE_CURSES */ return (kNoErr); } /* PrefsWindow */ void ShowAll(void) { int i; string value; MultiLineInit(); for (i = kFirstPrefsWinItem; i <= kLastPrefsWinItem; i++) { GetPrefSetting(value, sizeof(value), i); MultiLinePrintF("%-28s%s\n", gPrefOpts[i].label, value); } } /* ShowAll */ static void ShowSetHelp(void) { int i; MultiLineInit(); for (i = kFirstPrefsWinItem; i <= kLastPrefsWinItem; i++) { MultiLinePrintF("%-15s %s\n", gPrefOpts[i].name, gPrefOpts[i].label); } } /* ShowSetHelp */ int SetCmd(int argc, char **argv) { int i, j, match; size_t len; string value; if ((argc == 1) || ISTREQ(argv[1], "all")) { ShowAll(); } else if (ISTREQ(argv[1], "help")) { ShowSetHelp(); } else { len = strlen(argv[1]); for (i = kFirstPrefsWinItem, match = -1; i <= kLastPrefsWinItem; i++) { if (ISTRNEQ(gPrefOpts[i].name, argv[1], len)) { if (match >= 0) { Error(kDontPerror, "Ambiguous option name \"%s.\"\n", argv[1]); return (kCmdErr); } match = i; } } if (match < 0) { Error(kDontPerror, "Unknown option name \"%s.\"\n", argv[1]); return (kCmdErr); } i = match; switch (gPrefOpts[i].type) { case kPrefInt: if (argc > 2) AtoIMaybe((int *) gPrefOpts[i].storage, argv[2]); break; case kPrefToggle: if (argc > 2) { /* User can set it directly instead of cycling through * the choices. */ AtoIMaybe((int *) &j, argv[2]); if (j < gPrefOpts[i].min) j = gPrefOpts[i].min; else if (j > gPrefOpts[i].max) j = gPrefOpts[i].max; * (int *) gPrefOpts[i].storage = j; } else { /* User toggled to the next choice. */ TogglePref((int *) gPrefOpts[i].storage, gPrefOpts[i].min, gPrefOpts[i].max); } break; case kPrefStr: if (argc > 2) (void) Strncpy((char *) gPrefOpts[i].storage, argv[2], gPrefOpts[i].siz); break; } /* Print the (possibly new) value. */ GetPrefSetting(value, sizeof(value), i); PrintF("%-28s%s\n", gPrefOpts[i].label, value); } return (kNoErr); } /* SetCmd */ int PrefsCmd(void) { #ifdef USE_CURSES int err; if (!gWinInit) { EPrintF("%s\n%s\n", "The preferences editor only works in visual mode.", "However, you can use the 'set' command to set a single option at a time." ); return (kCmdErr); } err = PrefsWindow(); Beep(0); /* User should be aware that it took a while, so no beep. */ return (err); #else EPrintF("%s\n%s\n", "You can't do this because the program doesn't have the curses library.", "However, you can use the 'set' command to set a single option at a time." ); return (kCmdErr); #endif /* USE_CURSES */ } /* PrefsCmd */ void WritePrefs(void) { longstring path; FILE *fp; int i; if (gOurDirectoryPath[0] == '\0') return; /* Don't create in root directory. */ OurDirectoryPath(path, sizeof(path), kPrefsName); fp = fopen(path, "w"); if (fp == NULL) { Error(kDoPerror, "Can't open %s for writing.\n", path); return; } for (i = kFirstPrefsWinItem; i <= kLastPrefsWinItem; i++) { switch (gPrefOpts[i].type) { case kPrefInt: case kPrefToggle: fprintf(fp, "%s %d\n", gPrefOpts[i].name, * (int *) gPrefOpts[i].storage); break; case kPrefStr: fprintf(fp, "%s %s\n", gPrefOpts[i].name, (char *) gPrefOpts[i].storage); break; } } for (i = 0; i < kNumNonEditablePrefOpts; i++) { switch (gNonEditPrefOpts[i].type) { case kPrefInt: case kPrefToggle: fprintf(fp, "%s %d\n", gNonEditPrefOpts[i].name, * (int *) gNonEditPrefOpts[i].storage); break; case kPrefStr: fprintf(fp, "%s %s\n", gNonEditPrefOpts[i].name, (char *) gNonEditPrefOpts[i].storage); break; } } fclose(fp); } /* WritePrefs */ static int PrefSearchProc(char *key, const PrefOpt *b) { return (ISTRCMP(key, (*b).name)); } /* PrefSearchProc */ void ReadPrefs(void) { longstring path; FILE *fp; string option; longstring val; longstring line; int o; PrefOpt *pop; OurDirectoryPath(path, sizeof(path), kPrefsName); fp = fopen(path, "r"); if (fp == NULL) { /* It's okay if we don't have one. */ return; } while (FGets(line, (int) sizeof(line) - 1, fp) != NULL) { if (sscanf(line, "%s", option) < 1) continue; o = strlen(option) + 1; STRNCPY(val, line + o); pop = (PrefOpt *) BSEARCH(option, gPrefOpts, SZ(gNumEditablePrefOpts), sizeof(PrefOpt), PrefSearchProc); if (pop == NULL) { pop = (PrefOpt *) BSEARCH(option, gNonEditPrefOpts, SZ(kNumNonEditablePrefOpts), sizeof(PrefOpt), PrefSearchProc); if (pop == NULL) { Error(kDontPerror, "Unrecognized preference option \"%s\".\n", option); continue; } } switch (pop->type) { case kPrefInt: case kPrefToggle: * (int *) pop->storage = atoi(val); break; case kPrefStr: (void) Strncpy((char *) pop->storage, val, pop->siz); break; } } fclose(fp); } /* ReadPrefs */