Commit | Line | Data |
---|---|---|
1479465f GJ |
1 | /* |
2 | * dselect - Debian package maintenance user interface | |
3 | * main.cc - main program | |
4 | * | |
5 | * Copyright © 1994-1996 Ian Jackson <ijackson@chiark.greenend.org.uk> | |
6 | * Copyright © 2000,2001 Wichert Akkerman <wakkerma@debian.org> | |
7 | * Copyright © 2006-2015 Guillem Jover <guillem@debian.org> | |
8 | * | |
9 | * This is free software; you can redistribute it and/or modify | |
10 | * it under the terms of the GNU General Public License as published by | |
11 | * the Free Software Foundation; either version 2 of the License, or | |
12 | * (at your option) any later version. | |
13 | * | |
14 | * This is distributed in the hope that it will be useful, | |
15 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
16 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
17 | * GNU General Public License for more details. | |
18 | * | |
19 | * You should have received a copy of the GNU General Public License | |
20 | * along with this program. If not, see <https://www.gnu.org/licenses/>. | |
21 | */ | |
22 | ||
23 | #include <config.h> | |
24 | #include <compat.h> | |
25 | ||
26 | #include <sys/types.h> | |
27 | #include <sys/wait.h> | |
28 | ||
29 | #include <assert.h> | |
30 | #include <errno.h> | |
31 | #include <limits.h> | |
32 | #if HAVE_LOCALE_H | |
33 | #include <locale.h> | |
34 | #endif | |
35 | #include <ctype.h> | |
36 | #include <string.h> | |
37 | #include <fcntl.h> | |
38 | #include <dirent.h> | |
39 | #include <unistd.h> | |
40 | #include <stdlib.h> | |
41 | #include <stdio.h> | |
42 | ||
43 | // Solaris requires curses.h to be included before term.h | |
44 | #include "dselect-curses.h" | |
45 | ||
46 | #if defined(HAVE_NCURSESW_TERM_H) | |
47 | #include <ncursesw/term.h> | |
48 | #elif defined(HAVE_NCURSES_TERM_H) | |
49 | #include <ncurses/term.h> | |
50 | #else | |
51 | #include <term.h> | |
52 | #endif | |
53 | ||
54 | #include <dpkg/i18n.h> | |
55 | #include <dpkg/dpkg.h> | |
56 | #include <dpkg/dpkg-db.h> | |
57 | #include <dpkg/options.h> | |
58 | ||
59 | #include "dselect.h" | |
60 | #include "bindings.h" | |
61 | #include "pkglist.h" | |
62 | ||
63 | static const char printforhelp[] = N_("Type dselect --help for help."); | |
64 | ||
65 | bool expertmode = false; | |
66 | ||
67 | static const char *admindir = ADMINDIR; | |
68 | ||
69 | static keybindings packagelistbindings(packagelist_kinterps,packagelist_korgbindings); | |
70 | ||
71 | struct table_t { | |
72 | const char *name; | |
73 | const int num; | |
74 | }; | |
75 | ||
76 | static const struct table_t colourtable[]= { | |
77 | {"black", COLOR_BLACK }, | |
78 | {"red", COLOR_RED }, | |
79 | {"green", COLOR_GREEN }, | |
80 | {"yellow", COLOR_YELLOW }, | |
81 | {"blue", COLOR_BLUE }, | |
82 | {"magenta", COLOR_MAGENTA }, | |
83 | {"cyan", COLOR_CYAN }, | |
84 | {"white", COLOR_WHITE }, | |
85 | {nullptr, 0}, | |
86 | }; | |
87 | ||
88 | static const struct table_t attrtable[]= { | |
89 | {"normal", A_NORMAL }, | |
90 | {"standout", A_STANDOUT }, | |
91 | {"underline", A_UNDERLINE }, | |
92 | {"reverse", A_REVERSE }, | |
93 | {"blink", A_BLINK }, | |
94 | {"bright", A_BLINK }, // on some terminals | |
95 | {"dim", A_DIM }, | |
96 | {"bold", A_BOLD }, | |
97 | {nullptr, 0}, | |
98 | }; | |
99 | ||
100 | /* A slightly confusing mapping from dselect's internal names to | |
101 | * the user-visible names.*/ | |
102 | static const struct table_t screenparttable[]= { | |
103 | {"list", list }, | |
104 | {"listsel", listsel }, | |
105 | {"title", title }, | |
106 | {"infohead", thisstate }, | |
107 | {"pkgstate", selstate }, | |
108 | {"pkgstatesel", selstatesel }, | |
109 | {"listhead", colheads }, | |
110 | {"query", query }, | |
111 | {"info", info_body }, | |
112 | {"infodesc", info_head }, | |
113 | {"infofoot", whatinfo }, | |
114 | {"helpscreen", helpscreen }, | |
115 | {nullptr, 0}, | |
116 | }; | |
117 | ||
118 | /* Historical (patriotic?) colours. */ | |
119 | struct colordata color[]= { | |
120 | /* fore back attr */ | |
121 | {COLOR_WHITE, COLOR_BLACK, 0 }, // default, not used | |
122 | {COLOR_WHITE, COLOR_BLACK, 0 }, // list | |
123 | {COLOR_WHITE, COLOR_BLACK, A_REVERSE }, // listsel | |
124 | {COLOR_WHITE, COLOR_RED, 0 }, // title | |
125 | {COLOR_WHITE, COLOR_BLUE, 0 }, // thisstate | |
126 | {COLOR_WHITE, COLOR_BLACK, A_BOLD }, // selstate | |
127 | {COLOR_WHITE, COLOR_BLACK, A_REVERSE | A_BOLD }, // selstatesel | |
128 | {COLOR_WHITE, COLOR_BLUE, 0 }, // colheads | |
129 | {COLOR_WHITE, COLOR_RED, 0 }, // query | |
130 | {COLOR_WHITE, COLOR_BLACK, 0 }, // info_body | |
131 | {COLOR_WHITE, COLOR_BLACK, A_BOLD }, // info_head | |
132 | {COLOR_WHITE, COLOR_BLUE, 0 }, // whatinfo | |
133 | {COLOR_WHITE, COLOR_BLACK, 0 }, // help | |
134 | }; | |
135 | ||
136 | struct menuentry { | |
137 | const char *command; | |
138 | const char *key; | |
139 | const char *option; | |
140 | const char *menuent; | |
141 | urqfunction *fn; | |
142 | }; | |
143 | ||
144 | static const menuentry menuentries[]= { | |
145 | { "access", N_("a"), N_("[A]ccess"), N_("Choose the access method to use."), &urq_setup }, | |
146 | { "update", N_("u"), N_("[U]pdate"), N_("Update list of available packages, if possible."), &urq_update }, | |
147 | { "select", N_("s"), N_("[S]elect"), N_("Request which packages you want on your system."), &urq_list }, | |
148 | { "install", N_("i"), N_("[I]nstall"),N_("Install and upgrade wanted packages."), &urq_install }, | |
149 | { "config", N_("c"), N_("[C]onfig"), N_("Configure any packages that are unconfigured."), &urq_config }, | |
150 | { "remove", N_("r"), N_("[R]emove"), N_("Remove unwanted software."), &urq_remove }, | |
151 | { "quit", N_("q"), N_("[Q]uit"), N_("Quit dselect."), &urq_quit }, | |
152 | { nullptr, nullptr, N_("menu"), nullptr, &urq_menu }, | |
153 | { nullptr } | |
154 | }; | |
155 | ||
156 | static const char programdesc[]= | |
157 | N_("Debian '%s' package handling frontend version %s.\n"); | |
158 | ||
159 | static const char licensestring[]= N_( | |
160 | "This is free software; see the GNU General Public License version 2 or\n" | |
161 | "later for copying conditions. There is NO warranty.\n"); | |
162 | ||
163 | static void DPKG_ATTR_NORET | |
164 | printversion(const struct cmdinfo *ci, const char *value) | |
165 | { | |
166 | printf(gettext(programdesc), DSELECT, PACKAGE_RELEASE); | |
167 | printf("%s", gettext(licensestring)); | |
168 | ||
169 | m_output(stdout, _("<standard output>")); | |
170 | ||
171 | exit(0); | |
172 | } | |
173 | ||
174 | static void DPKG_ATTR_NORET | |
175 | usage(const struct cmdinfo *ci, const char *value) | |
176 | { | |
177 | int i; | |
178 | ||
179 | printf(_( | |
180 | "Usage: %s [<option>...] [<command>...]\n" | |
181 | "\n"), DSELECT); | |
182 | ||
183 | printf(_("Commands:\n")); | |
184 | for (i = 0; menuentries[i].command; i++) | |
185 | printf(" %-10s %s\n", menuentries[i].command, menuentries[i].menuent); | |
186 | fputs("\n", stdout); | |
187 | ||
188 | printf(_( | |
189 | "Options:\n" | |
190 | " --admindir <directory> Use <directory> instead of %s.\n" | |
191 | " --expert Turn on expert mode.\n" | |
192 | " -D, --debug <file> Turn on debugging, send output to <file>.\n" | |
193 | " --color <color-spec> Configure screen colors.\n" | |
194 | " --colour <color-spec> Ditto.\n" | |
195 | ), ADMINDIR); | |
196 | ||
197 | printf(_( | |
198 | " -?, --help Show this help message.\n" | |
199 | " --version Show the version.\n" | |
200 | "\n")); | |
201 | ||
202 | printf(_("<color-spec> is <screen-part>:[<foreground>],[<background>][:<attr>[+<attr>]...]\n")); | |
203 | ||
204 | printf(_("<screen-part> is:")); | |
205 | for (i=0; screenparttable[i].name; i++) | |
206 | printf(" %s", screenparttable[i].name); | |
207 | fputs("\n", stdout); | |
208 | ||
209 | printf(_("<color> is:")); | |
210 | for (i=0; colourtable[i].name; i++) | |
211 | printf(" %s", colourtable[i].name); | |
212 | fputs("\n", stdout); | |
213 | ||
214 | printf(_("<attr> is:")); | |
215 | for (i=0; attrtable[i].name; i++) | |
216 | printf(" %s", attrtable[i].name); | |
217 | fputs("\n", stdout); | |
218 | ||
219 | m_output(stdout, _("<standard output>")); | |
220 | ||
221 | exit(0); | |
222 | } | |
223 | ||
224 | /* These are called by C code, so need to have C calling convention */ | |
225 | extern "C" { | |
226 | ||
227 | static void | |
228 | set_debug(const struct cmdinfo*, const char *v) | |
229 | { | |
230 | FILE *fp; | |
231 | ||
232 | fp = fopen(v, "a"); | |
233 | if (!fp) | |
234 | ohshite(_("couldn't open debug file '%.255s'\n"), v); | |
235 | ||
236 | debug_set_output(fp, v); | |
237 | debug_set_mask(dbg_general | dbg_depcon); | |
238 | } | |
239 | ||
240 | static void | |
241 | set_expert(const struct cmdinfo*, const char *v) | |
242 | { | |
243 | expertmode = true; | |
244 | } | |
245 | ||
246 | static int | |
247 | findintable(const struct table_t *table, const char *item, const char *tablename) | |
248 | { | |
249 | int i; | |
250 | ||
251 | for (i = 0; item && (table[i].name != nullptr); i++) | |
252 | if (strcasecmp(item, table[i].name) == 0) | |
253 | return table[i].num; | |
254 | ||
255 | ohshit(_("invalid %s '%s'"), tablename, item); | |
256 | } | |
257 | ||
258 | /* | |
259 | * The string's format is: | |
260 | * screenpart:[forecolor][,backcolor][:[<attr>, ...] | |
261 | * Examples: --color title:black,cyan:bright+underline | |
262 | * --color list:red,yellow | |
263 | * --color colheads:,green:bright | |
264 | * --color selstate::reverse // doesn't work FIXME | |
265 | */ | |
266 | static void | |
267 | set_color(const struct cmdinfo*, const char *string) | |
268 | { | |
269 | char *s; | |
270 | char *colours, *attributes, *attrib, *colourname; | |
271 | int screenpart, aval; | |
272 | ||
273 | s = m_strdup(string); // strtok modifies strings, keep string const | |
274 | screenpart= findintable(screenparttable, strtok(s, ":"), _("screen part")); | |
275 | colours = strtok(nullptr, ":"); | |
276 | attributes = strtok(nullptr, ":"); | |
277 | ||
278 | if ((colours == nullptr || ! strlen(colours)) && | |
279 | (attributes == nullptr || ! strlen(attributes))) { | |
280 | ohshit(_("null colour specification")); | |
281 | } | |
282 | ||
283 | if (colours != nullptr && strlen(colours)) { | |
284 | colourname= strtok(colours, ","); | |
285 | if (colourname != nullptr && strlen(colourname)) { | |
286 | // normalize attributes to prevent confusion | |
287 | color[screenpart].attr= A_NORMAL; | |
288 | color[screenpart].fore=findintable(colourtable, colourname, _("colour")); | |
289 | } | |
290 | colourname = strtok(nullptr, ","); | |
291 | if (colourname != nullptr && strlen(colourname)) { | |
292 | color[screenpart].attr= A_NORMAL; | |
293 | color[screenpart].back=findintable(colourtable, colourname, _("colour")); | |
294 | } | |
295 | } | |
296 | ||
297 | if (attributes != nullptr && strlen(attributes)) { | |
298 | for (attrib= strtok(attributes, "+"); | |
299 | attrib != nullptr && strlen(attrib); | |
300 | attrib = strtok(nullptr, "+")) { | |
301 | aval=findintable(attrtable, attrib, _("colour attribute")); | |
302 | if (aval == A_NORMAL) // set to normal | |
303 | color[screenpart].attr= aval; | |
304 | else // add to existing attribs | |
305 | color[screenpart].attr= color[screenpart].attr | aval; | |
306 | } | |
307 | } | |
308 | ||
309 | free(s); | |
310 | } | |
311 | ||
312 | } /* End of extern "C" */ | |
313 | ||
314 | static const struct cmdinfo cmdinfos[]= { | |
315 | { "admindir", 0, 1, nullptr, &admindir, nullptr }, | |
316 | { "debug", 'D', 1, nullptr, nullptr, set_debug }, | |
317 | { "expert", 'E', 0, nullptr, nullptr, set_expert }, | |
318 | { "help", '?', 0, nullptr, nullptr, usage }, | |
319 | { "version", 0, 0, nullptr, nullptr, printversion }, | |
320 | { "color", 0, 1, nullptr, nullptr, set_color }, /* US spelling */ | |
321 | { "colour", 0, 1, nullptr, nullptr, set_color }, /* UK spelling */ | |
322 | { nullptr, 0, 0, nullptr, nullptr, nullptr } | |
323 | }; | |
324 | ||
325 | static bool cursesareon = false; | |
326 | void curseson() { | |
327 | if (!cursesareon) { | |
328 | const char *cup, *smso; | |
329 | initscr(); | |
330 | cup= tigetstr("cup"); | |
331 | smso= tigetstr("smso"); | |
332 | if (!cup || !smso) { | |
333 | endwin(); | |
334 | if (!cup) | |
335 | fputs(_("Terminal does not appear to support cursor addressing.\n"),stderr); | |
336 | if (!smso) | |
337 | fputs(_("Terminal does not appear to support highlighting.\n"),stderr); | |
338 | fprintf(stderr, | |
339 | _("Set your TERM variable correctly, use a better terminal,\n" | |
340 | "or make do with the per-package management tool %s.\n"), | |
341 | DPKG); | |
342 | ohshit(_("terminal lacks necessary features, giving up")); | |
343 | } | |
344 | } | |
345 | cursesareon = true; | |
346 | } | |
347 | ||
348 | void cursesoff() { | |
349 | if (cursesareon) { | |
350 | clear(); | |
351 | refresh(); | |
352 | endwin(); | |
353 | } | |
354 | cursesareon = false; | |
355 | } | |
356 | ||
357 | urqresult urq_list(void) { | |
358 | modstatdb_open((modstatdb_rw)(msdbrw_writeifposs | | |
359 | msdbrw_available_readonly)); | |
360 | ||
361 | curseson(); | |
362 | ||
363 | packagelist *l= new packagelist(&packagelistbindings); | |
364 | l->resolvesuggest(); | |
365 | l->display(); | |
366 | delete l; | |
367 | ||
368 | modstatdb_shutdown(); | |
369 | ||
370 | return urqr_normal; | |
371 | } | |
372 | ||
373 | static void | |
374 | dme(int i, int so) | |
375 | { | |
376 | const menuentry *me= &menuentries[i]; | |
377 | ||
378 | varbuf buf; | |
379 | buf.fmt(" %c %d. %-11.11s %-80.80s ", | |
380 | so ? '*' : ' ', i, | |
381 | gettext(me->option), | |
382 | gettext(me->menuent)); | |
383 | ||
384 | int x, y DPKG_ATTR_UNUSED; | |
385 | getmaxyx(stdscr,y,x); | |
386 | ||
387 | attrset(so ? A_REVERSE : A_NORMAL); | |
388 | mvaddnstr(i + 2, 0, buf.string(), x - 1); | |
389 | attrset(A_NORMAL); | |
390 | } | |
391 | ||
392 | static int | |
393 | refreshmenu(void) | |
394 | { | |
395 | curseson(); cbreak(); noecho(); nonl(); keypad(stdscr,TRUE); | |
396 | ||
397 | int x, y DPKG_ATTR_UNUSED; | |
398 | getmaxyx(stdscr,y,x); | |
399 | ||
400 | varbuf buf; | |
401 | buf.fmt(gettext(programdesc), DSELECT, PACKAGE_RELEASE); | |
402 | ||
403 | clear(); | |
404 | attrset(A_BOLD); | |
405 | mvaddnstr(0, 0, buf.string(), x - 1); | |
406 | ||
407 | attrset(A_NORMAL); | |
408 | const struct menuentry *mep; int i; | |
409 | for (mep=menuentries, i=0; mep->option && mep->menuent; mep++, i++) | |
410 | dme(i,0); | |
411 | ||
412 | attrset(A_BOLD); | |
413 | addstr(_("\n\n" | |
414 | "Move around with ^P and ^N, cursor keys, initial letters, or digits;\n" | |
415 | "Press <enter> to confirm selection. ^L redraws screen.\n\n")); | |
416 | ||
417 | attrset(A_NORMAL); | |
418 | addstr(_("Copyright (C) 1994-1996 Ian Jackson.\n" | |
419 | "Copyright (C) 2000,2001 Wichert Akkerman.\n")); | |
420 | addstr(gettext(licensestring)); | |
421 | ||
422 | modstatdb_init(); | |
423 | if (!modstatdb_can_lock()) | |
424 | addstr(_("\n\n" | |
425 | "Read-only access: only preview of selections is available!")); | |
426 | modstatdb_done(); | |
427 | ||
428 | return i; | |
429 | } | |
430 | ||
431 | urqresult urq_menu(void) { | |
432 | int entries, c; | |
433 | entries= refreshmenu(); | |
434 | int cursor=0; | |
435 | dme(0,1); | |
436 | for (;;) { | |
437 | refresh(); | |
438 | do | |
439 | c= getch(); | |
440 | while (c == ERR && errno == EINTR); | |
441 | if (c==ERR) { | |
442 | if(errno != 0) | |
443 | ohshite(_("failed to getch in main menu")); | |
444 | else { | |
445 | clearok(stdscr,TRUE); clear(); refreshmenu(); dme(cursor,1); | |
446 | } | |
447 | } | |
448 | ||
449 | if (c == CTRL('n') || c == KEY_DOWN || c == ' ' || c == 'j') { | |
450 | dme(cursor,0); cursor++; cursor %= entries; dme(cursor,1); | |
451 | } else if (c == CTRL('p') || c == KEY_UP || c == CTRL('h') || | |
452 | c==KEY_BACKSPACE || c==KEY_DC || c=='k') { | |
453 | dme(cursor,0); cursor+= entries-1; cursor %= entries; dme(cursor,1); | |
454 | } else if (c=='\n' || c=='\r' || c==KEY_ENTER) { | |
455 | clear(); refresh(); | |
456 | ||
457 | /* FIXME: trap errors in urq_... */ | |
458 | urqresult res = menuentries[cursor].fn(); | |
459 | switch (res) { | |
460 | case urqr_quitmenu: | |
461 | return urqr_quitmenu; | |
462 | case urqr_normal: | |
463 | cursor++; cursor %= entries; | |
464 | case urqr_fail: | |
465 | break; | |
466 | default: | |
467 | internerr("unknown menufn %d", res); | |
468 | } | |
469 | refreshmenu(); dme(cursor,1); | |
470 | } else if (c == CTRL('l')) { | |
471 | clearok(stdscr,TRUE); clear(); refreshmenu(); dme(cursor,1); | |
472 | } else if (isdigit(c)) { | |
473 | char buf[2]; buf[0]=c; buf[1]=0; c=atoi(buf); | |
474 | if (c < entries) { | |
475 | dme(cursor,0); cursor=c; dme(cursor,1); | |
476 | } else { | |
477 | beep(); | |
478 | } | |
479 | } else if (isalpha(c)) { | |
480 | c= tolower(c); | |
481 | int i = 0; | |
482 | while (i < entries && gettext(menuentries[i].key)[0] != c) | |
483 | i++; | |
484 | if (i < entries) { | |
485 | dme(cursor,0); cursor=i; dme(cursor,1); | |
486 | } else { | |
487 | beep(); | |
488 | } | |
489 | } else { | |
490 | beep(); | |
491 | } | |
492 | } | |
493 | } | |
494 | ||
495 | urqresult urq_quit(void) { | |
496 | /* FIXME: check packages OK. */ | |
497 | return urqr_quitmenu; | |
498 | } | |
499 | ||
500 | static void | |
501 | dselect_catch_fatal_error() | |
502 | { | |
503 | cursesoff(); | |
504 | catch_fatal_error(); | |
505 | } | |
506 | ||
507 | int | |
508 | main(int, const char *const *argv) | |
509 | { | |
510 | dpkg_locales_init(DSELECT); | |
511 | dpkg_set_progname(DSELECT); | |
512 | ||
513 | push_error_context_func(dselect_catch_fatal_error, print_fatal_error, nullptr); | |
514 | ||
515 | dpkg_options_load(DSELECT, cmdinfos); | |
516 | dpkg_options_parse(&argv, cmdinfos, printforhelp); | |
517 | ||
518 | admindir = dpkg_db_set_dir(admindir); | |
519 | ||
520 | if (*argv) { | |
521 | const char *a; | |
522 | while ((a = *argv++) != nullptr) { | |
523 | const menuentry *me = menuentries; | |
524 | while (me->command && strcmp(me->command, a)) | |
525 | me++; | |
526 | if (!me->command) | |
527 | badusage(_("unknown action string '%.50s'"), a); | |
528 | me->fn(); | |
529 | } | |
530 | } else { | |
531 | urq_menu(); | |
532 | } | |
533 | ||
534 | cursesoff(); | |
535 | dpkg_program_done(); | |
536 | ||
537 | return(0); | |
538 | } |