C-Menu 0.2.9
A User Interface Toolkit
Loading...
Searching...
No Matches
pick_engine.c
Go to the documentation of this file.
1/** @file pick_engine.c
2 @brief pick from a list of choices
3 @author Bill Waller
4 Copyright (c) 2025
5 MIT License
6 billxwaller@gmail.com
7 @date 2026-02-09
8 */
9
10/** @defgroup pick_engine Object Selection
11 @brief Functions to Navigate, Select, and Perform Action on Objects
12 */
13
14#include <common.h>
15#include <fcntl.h>
16#include <stdlib.h>
17#include <string.h>
18#include <sys/stat.h>
19#include <sys/wait.h>
20#include <termios.h>
21#include <unistd.h>
22
25int pick_engine(Init *);
26void save_object(Pick *, char *);
27int picker(Init *);
28void display_page(Pick *);
29void reverse_object(Pick *);
30void unreverse_object(Pick *);
31void toggle_object(Pick *);
32int output_objects(Pick *);
33int exec_objects(Init *);
34int open_pick_win(Init *);
35void display_pick_help(Init *);
36int read_pick_input(Init *);
37void deselect_object(Pick *);
38
39int pipe_fd[2];
40
41char const pagers_editors[12][10] = {"view", "view", "less", "more",
42 "vi", "vim", "nano", "nvim",
43 "pico", "emacs", "edit", ""};
44
45/** @brief Initializes pick structure and opens pick input file or pipe
46 * @ingroup pick_engine
47 @param init Pointer to Init structure
48 @param argc Argument count
49 @param argv Argument vector
50 @param begy Beginning y coordinate for pick window
51 @param begx Beginning x coordinate for pick window
52 @note If provider_cmd is specified, it takes precedence over in_spec and
53 input file arguments
54 @note provider_cmd is executed and its output is read as pick input
55 @note If provider_cmd is not specified, in_spec is used to read pick
56 input from a file or stdin
57 @note If provider_cmd is specified, it is executed and its output is
58 read as pick input */
59int init_pick(Init *init, int argc, char **argv, int begy, int begx) {
60 struct stat sb;
61 char *s_argv[MAXARGS];
62 int s_argc;
63 char tmp_str[MAXLEN];
64 int m;
65 pid_t pid = 0;
66
67 if (init->pick != nullptr)
69 Pick *pick = new_pick(init, argc, argv, begy, begx);
70 if (init->pick != pick)
71 abend(-1, "init->pick != pick\n");
72 SIO *sio = init->sio;
73 if (pick->provider_cmd[0] != '\0') {
74 s_argc = str_to_args(s_argv, pick->provider_cmd, MAXARGS - 1);
75 if (pipe(pipe_fd) == -1) {
76 Perror("pipe(pipe_fd) failed in init_pick");
77 return (1);
78 }
79 if ((pid = fork()) == -1) {
80 Perror("fork() failed in init_pick");
81 return (1);
82 }
83 if (pid == 0) {
84 /** Spawn Child to execute provider_cmd
85 Close read end of pipe as Child only needs to write to pipe */
86 close(pipe_fd[P_READ]);
87 /** Connect CHILD STDOUT to write end of pipe */
88 dup2(pipe_fd[P_WRITE], STDOUT_FILENO);
89 dup2(pipe_fd[P_WRITE], STDERR_FILENO);
90 /** STDOUT attached to write end of pipe, so close pipe fd */
91 close(pipe_fd[P_WRITE]);
92 execvp(s_argv[0], s_argv);
93 m = MAXLEN - 24;
94 strnz__cpy(tmp_str, "Can't exec pick start cmd: ", m);
95 m -= strlen(s_argv[0]);
96 strnz__cat(tmp_str, s_argv[0], m);
97 Perror(tmp_str);
98 exit(EXIT_FAILURE);
99 }
100 /** Return to Parent
101 Close write end of pipe as Parent only needs to read from pipe */
102 close(pipe_fd[P_WRITE]);
103 /** Open a file pointer on read end of pipe */
104 pick->in_fp = fdopen(pipe_fd[P_READ], "rb");
105 pick->f_in_pipe = true;
106 destroy_argv(s_argc, s_argv);
107 } else {
108 if ((pick->in_spec[0] == '\0') || strcmp(pick->in_spec, "-") == 0 ||
109 strcmp(pick->in_spec, "/dev/stdin") == 0) {
110 strnz__cpy(pick->in_spec, "/dev/stdin", MAXLEN - 1);
111 pick->in_fp = fdopen(STDIN_FILENO, "rb");
112 pick->f_in_pipe = true;
113 }
114 }
115 if (!pick->f_in_pipe) {
116 /** No provider_cmd specified, so read pick input from file or stdin */
117 if (lstat(pick->in_spec, &sb) == -1) {
118 m = MAXLEN - 29;
119 strnz__cpy(tmp_str, "Can\'t stat pick input file: ", m);
120 m -= strlen(pick->in_spec);
121 strnz__cat(tmp_str, pick->in_spec, m);
122 Perror(tmp_str);
123 return (1);
124 }
125 if (sb.st_size == 0) {
126 m = MAXLEN - 24;
127 strnz__cpy(tmp_str, "Pick input file empty: ", m);
128 m -= strlen(pick->in_spec);
129 strnz__cat(tmp_str, pick->in_spec, m);
130 Perror(tmp_str);
131 return (1);
132 }
133 if ((pick->in_fp = fopen(pick->in_spec, "rb")) == nullptr) {
134 m = MAXLEN - 29;
135 strnz__cpy(tmp_str, "Can't open pick input file: ", m);
136 m -= strlen(pick->in_spec);
137 strnz__cat(tmp_str, pick->in_spec, m);
138 Perror(tmp_str);
139 return (1);
140 }
141 }
142 /*------------------------------------------------------------*/
143 bool f_wait = false;
144 int ready;
145 fd_set read_fds;
146 struct timeval timeout;
147 Chyron *wait_chyron;
148 WINDOW *wait_win;
149 int remaining;
150 if (pick->in_fp == nullptr) {
151 Perror("No pick input available");
152 destroy_pick(init);
153 return (1);
154 }
155 int in_fd = fileno(pick->in_fp);
156 FD_ZERO(&read_fds);
157 FD_SET(in_fd, &read_fds);
158 timeout.tv_sec = 0;
159 timeout.tv_usec =
160 200000; /**< Initial timeout of 200ms to check for pick input */
161 ready = select(in_fd + 1, &read_fds, nullptr, nullptr, &timeout);
162 if (ready == 0) {
163 f_wait = true;
164 remaining = wait_timeout;
165 wait_chyron = wait_mk_chyron();
166 wait_win = wait_mk_win(wait_chyron, "WAITING for PICK INPUT");
167 }
168 cmd_key = 0;
169 while (ready == 0 && remaining > 0 && cmd_key != KEY_F(9)) {
170 cmd_key = wait_continue(wait_win, wait_chyron, remaining);
171 if (cmd_key == KEY_F(9))
172 break;
173 FD_ZERO(&read_fds);
174 FD_SET(in_fd, &read_fds);
175 timeout.tv_sec = 0;
176 timeout.tv_usec = 0;
177 ready = select(in_fd + 1, &read_fds, nullptr, nullptr, &timeout);
178 remaining--;
179 }
180 if (f_wait) {
181 if (wait_chyron != nullptr)
182 wait_destroy(wait_chyron);
183 }
184 if (cmd_key == KEY_F(9)) {
185 if (pick->f_in_pipe && pid > 0) {
186 /** If user cancels while waiting for pick input, kill provider_cmd
187 * child process and close pipe */
188 kill(pid, SIGKILL);
189 waitpid(pid, nullptr, 0);
190 close(pipe_fd[P_READ]);
191 }
192 Perror("No pick input available");
193 destroy_pick(init);
194 return (1);
195 }
196 if (ready == -1) {
197 Perror("Error waiting for pick input");
198 if (pick->f_in_pipe && pid > 0) {
199 /** If error occurs while waiting for pick input, kill provider_cmd
200 * child process and close pipe */
201 kill(pid, SIGKILL);
202 waitpid(pid, nullptr, 0);
203 close(pipe_fd[P_READ]);
204 }
205 destroy_pick(init);
206 return (1);
207 }
208 if (ready == 0) {
209 Perror("Timeout waiting for pick input");
210 if (pick->f_in_pipe && pid > 0) {
211 /** If timeout occurs while waiting for pick input, kill
212 * provider_cmd child process and close pipe */
213 kill(pid, SIGKILL);
214 waitpid(pid, nullptr, 0);
215 close(pipe_fd[P_READ]);
216 }
217 destroy_pick(init);
218 return (1);
219 }
220 if (ready == 1 && !FD_ISSET(in_fd, &read_fds)) {
221 Perror("Unexpected error waiting for pick input");
222 if (pick->f_in_pipe && pid > 0) {
223 /** If unexpected error occurs while waiting for pick input, kill
224 * provider_cmd child process and close pipe */
225 kill(pid, SIGKILL);
226 waitpid(pid, nullptr, 0);
227 close(pipe_fd[P_READ]);
228 }
229 destroy_pick(init);
230 return (1);
231 }
232 /*------------------------------------------------------------*/
234 if (pick->f_in_pipe && pid > 0) {
235 /** Wait for provider_cmd child process to finish before proceeding */
237 // waitpid(pid, nullptr, 0);
238 close(pipe_fd[P_READ]);
239 dup2(sio->stdin_fd, STDIN_FILENO);
240 dup2(sio->stdout_fd, STDOUT_FILENO);
243 keypad(pick->win, true);
244 }
245 if (pick->obj_cnt == 0) {
246 Perror("No pick objects available");
247 destroy_pick(init);
248 return (1);
249 }
250 /** Enter pick_engine */
251 pick_engine(init);
252 win_del();
253 destroy_pick(init);
254 return 0;
255}
256/** @brief Reads pick input from file pointer and saves objects into pick
257 structure
258 * @ingroup pick_engine
259 @param init Pointer to Init structure containing pick information
260 @return 0 on success, -1 if no objects were read
261 @note Reads lines from pick->in_fp and saves them as objects in the pick
262 structure using save_object function. If no objects are read, returns -1.
263 Otherwise, sets obj_cnt to the number of objects read and resets obj_idx to 0
264 before returning 0. */
265int read_pick_input(Init *init) {
266 int i;
267
268 Pick *pick = init->pick;
269 pick->select_cnt = 0;
270 pick->obj_cnt = pick->pg_lines = pick->tbl_cols = 0;
271 pick->obj_idx = pick->tbl_page = pick->y = pick->tbl_col = pick->x = 0;
272 pick->tbl_pages = 1;
273
274 if (pick->in_fp) {
275 while (fgets(pick->in_buf, sizeof(pick->in_buf), pick->in_fp) !=
276 nullptr)
277 save_object(pick, pick->in_buf);
278 } else
279 for (i = 1; i < pick->argc; i++)
280 save_object(pick, pick->argv[i]);
281 if (pick->in_fp != nullptr)
282 fclose(pick->in_fp);
283 if (!pick->obj_idx)
284 return (-1);
285 pick->obj_cnt = pick->obj_idx;
286 pick->obj_idx = 0;
287 return 0;
288}
289/** @brief Initializes pick interface, calculates window size and position, and
290 enters picker loop
291 * @ingroup pick_engine
292 @param init Pointer to Init structure containing pick information
293 @return Count of selected objects on success, -1 if user cancels
294 @note Initializes key command strings for chyron display and calculates pick
295 window size and position based on terminal size and pick parameters.
296 @note Opens pick window and displays first page of objects. Enters picker
297 loop to handle user input and interactions. If user cancels selection,
298 returns -1.
299 @note If user accepts selection, returns count of selected objects. */
300int pick_engine(Init *init) {
301 /** Initialize window and data structures */
302 int rc;
303 int maxy, maxx, win_maxy, win_maxx;
304 int tbl_max_cols, pg_max_objs;
305 /** Initialize key command strings for chyron display */
306
307 getmaxyx(stdscr, maxy, maxx);
308 /** Calculate pick window size and position based on terminal size and pick
309 parameters */
310 win_maxy = min(((maxy * 8) / 10), (maxy - pick->begy - 1));
311 win_maxx = min(((maxx * 9) / 10), (maxx - pick->begx - 1));
314 if (pick->obj_cnt <= win_maxy) {
316 pick->tbl_cols = 1;
317 } else {
318 tbl_max_cols = (win_maxx / (pick->tbl_col_width + 1));
319 pg_max_objs = win_maxy * tbl_max_cols;
320 if (pick->obj_cnt > pg_max_objs)
321 pick->tbl_cols = tbl_max_cols;
322 else
323 pick->tbl_cols = pick->obj_cnt / win_maxy;
324 pick->tbl_lines = pick->obj_cnt / tbl_max_cols;
325 }
326 pick->tbl_pages = (pick->tbl_lines / (win_maxy - 1)) + 1;
329 pick->tbl_page = 0;
330 if (pick->begy == 0)
331 pick->begy = (LINES - pick->win_lines) / 5;
332 else if (pick->begy + pick->win_lines > LINES - 4)
333 pick->begy = LINES - pick->win_lines - 2;
335
337 set_chyron_key(pick->chyron, 1, "F1 Help", KEY_F(1));
338 set_chyron_key(pick->chyron, 9, "F9 Cancel", KEY_F(9));
339 set_chyron_key(pick->chyron, 10, "F10 Continue", KEY_F(10));
340 if (pick->tbl_pages > 1) {
341 set_chyron_key(pick->chyron, 11, "PgUp", KEY_PPAGE);
342 set_chyron_key(pick->chyron, 12, "PgDn", KEY_NPAGE);
343 }
344 set_chyron_key(pick->chyron, 13, "Toggle", 't');
346 if (pick->chyron->l > win_maxx)
351 if (pick->begx + pick->win_width > COLS - 2)
352 pick->begx = COLS - pick->win_width - 2;
353 else if (pick->begx == 0)
354 pick->begx = (COLS - pick->win_width) / 2;
355
356 rc = open_pick_win(init);
357 if (rc)
358 return (rc);
359 /** Enter picker loop to handle user input and interactions */
360 pick->obj_idx = 0;
361 pick->x = 1;
362 do {
365 mousemask(BUTTON1_CLICKED | BUTTON1_DOUBLE_CLICKED, nullptr);
366 rc = picker(init);
367 if (rc == -1)
368 break;
369 else {
370 if (pick->select_cnt > 0) {
373 if (pick->f_cmd && pick->cmd[0])
374 exec_objects(init);
375 }
376 }
378 } while (1);
380 return (rc);
381}
382/** @brief Saves a string as an object in the pick structure
383 * @ingroup pick_engine
384 @param pick Pointer to Pick structure
385 @param s String to save as an object
386 @note If the current object index is less than the maximum allowed, saves
387 the string as an object in the pick structure. Updates the column width if
388 necessary and marks the object as not selected. Increments the object index
389 for the next object to be saved. */
390void save_object(Pick *pick, char *s) {
391 int l;
392
393 if (pick->obj_idx < OBJ_MAXCNT - 1) {
394 l = strlen(s);
395 if (l > OBJ_MAXLEN - 1)
396 s[OBJ_MAXLEN - 1] = '\0';
397 pick->tbl_col_width = max(pick->tbl_col_width, l);
398 l = max(l, 1);
399 pick->object[pick->obj_idx] = (char *)calloc(l + 1, sizeof(char));
401 pick->f_selected[pick->obj_idx] = false;
402 pick->obj_idx++;
403 }
404}
405
406/** @brief Main loop to handle user input and interactions for pick interface
407 * @ingroup pick_engine
408 @param init Pointer to Init structure
409 @return Number of selected objects or -1 if user cancels
410 @note Handles user input for navigating and selecting objects in the pick
411 interface.
412 @note Supports various key commands for navigation, selection, and
413 accepting/canceling the selection.
414 @note Updates the display accordingly based on user interactions.
415 @note If the user cancels the selection, returns -1.
416 @note If the user accepts the selection, returns the count of selected
417 objects. */
418int picker(Init *init) {
419 int cmd_key;
420 int display_tbl_page;
421 bool f_no_reset_cmd_key;
422 pick = init->pick;
423 click_y = click_x = -1;
424 cmd_key = 0;
425 while (1) {
426 tcflush(tty_fd, TCIFLUSH);
427 f_no_reset_cmd_key = false;
428 if (cmd_key == 0)
430 switch (
431 cmd_key) { /** 'q', or KEY_F(9) cancel selection and exit picker */
432 case 'q':
433 case 'Q':
434 case KEY_F(9):
436 return -1;
437 /** KEY_F(1) or 'H' Displays help screen for pick interface */
438 case KEY_F(1):
439 case 'H':
443 break;
444 /** 't' or Space Toggles selection of current object */
445 case ' ':
446 case 't':
447 case 'T':
451 return pick->select_cnt;
452 break;
453 /** Enter or KEY_F(10) Accepts current selection and exits picker,
454 * returning count of selected objects */
455 case KEY_F(10):
456 case '\n':
457 case KEY_ENTER:
458 return pick->select_cnt;
459 /** KEY_END Moves selection to last object in list */
460 case KEY_END:
463 display_tbl_page = pick->tbl_page;
469 if (display_tbl_page != pick->tbl_page) {
471 }
473 break;
474 /** 'l' or KEY_RIGHT Moves selection to next object in list */
475 case 'l':
476 case KEY_RIGHT:
479 /** pick->obj_idx += pick->tbl_lines -> next column */
482 pick->obj_cnt - 1 &&
488 break;
489 /** 'h' or KEY_LEFT or Backspace Moves selection to previous object
490 * in list */
491 case 'h':
492 case KEY_LEFT:
493 case KEY_BACKSPACE:
496 if (pick->tbl_col > 0)
501 break;
502 /** 'j' or KEY_DOWN Moves selection to next object in list, 'k' or
503 * KEY_UP Moves selection to previous object in list */
504 case 'j':
505 case KEY_DOWN:
510 pick->obj_cnt - 1 &&
516 break;
517 /** 'k' or KEY_UP Moves selection to previous object in list */
518 case 'k':
519 case KEY_UP:
522 if (pick->tbl_line > 0)
527 break;
528 /** KEY_NPAGE or 'Ctrl+f' Moves selection to next page of objects, */
529 case KEY_NPAGE:
530 case '\06':
531 if (pick->tbl_pages == 1)
532 break;
535 pick->pg_line = 0;
536 pick->tbl_col = 0;
537 }
542 break;
543 /** KEY_PPAGE or 'Ctrl+b' Moves selection to previous page of
544 * objects */
545 case KEY_PPAGE:
546 case '\02':
547 if (pick->tbl_pages == 1)
548 break;
549 if (pick->tbl_page > 0)
555 break;
556 /** KEY_HOME Moves selection to first object in list */
557 case KEY_HOME:
558 pick->tbl_page = 0;
559 pick->tbl_line = 0;
560 pick->tbl_col = 0;
565 break;
566 /** KEY_LL (lower left of numeric pad) Moves selection to last
567 object in list */
568 case KEY_LL:
574 break;
575 /** KEY_MOUSE Handles mouse events for selection and chyron key
576 * activation */
577 case KEY_MOUSE:
578 if (click_y == -1 || click_x == -1)
579 break;
584 break;
587 cmd_key =
588 't'; /** Set cmdkey to 't' to toggle selection on mouse click */
589 f_no_reset_cmd_key = true;
590 click_y = click_x = -1;
591 break;
592 default:
593 break;
594 }
595 if (!f_no_reset_cmd_key)
596 cmd_key = 0;
597 }
598 return 0;
599}
600/** @brief Displays current page of objects in pick window
601 * @ingroup pick_engine
602 @param pick Pointer to Pick structure containing objects and display
603 information
604 @note Clears the pick window and displays the current page of objects based
605 on the current table page, line, and column. Marks selected objects with an
606 asterisk. Updates the chyron with page information at the bottom of the pick
607 window. */
608void display_page(Pick *pick) {
609 int y, col, pidx;
610 for (y = 0; y < pick->pg_lines; y++) {
611 wmove(pick->win, y, 0);
612 wclrtoeol(pick->win);
613 }
614 pidx = pick->tbl_page * pick->pg_lines * pick->tbl_cols;
615 for (col = 0; col < pick->tbl_cols; col++) {
616 pick->x = col * (pick->tbl_col_width + 1) + 1;
617 for (y = 0; y < pick->pg_lines; y++, pidx++) {
618 if (pidx < pick->obj_cnt) {
619 if (pick->f_selected[pidx])
620 mvwaddstr(pick->win, y, pick->x - 1, "*");
621 mvwaddstr_fill(pick->win, y, pick->x, pick->object[pidx],
623 }
624 }
625 }
628}
629/** @brief Reverses the display of the currently selected object in pick window
630 * @ingroup pick_engine
631 @param pick Pointer to Pick structure containing object and display
632 information
633 @note Calculates the x coordinate for the currently selected object based on
634 the current table column and column width. Moves the cursor to the object's
635 position in the pick window, turns on reverse video attribute, and displays
636 the object's text. Turns off reverse video attribute and refreshes the pick
637 window to show the updated display. Moves the cursor back to the position
638 before the object text for potential further interactions. */
639void reverse_object(Pick *pick) {
640 pick->x = pick->tbl_col * (pick->tbl_col_width + 1) + 1;
641 pick->tbl_line = (pick->obj_idx / pick->tbl_cols) % pick->pg_lines;
642 pick->y = pick->tbl_line;
643 wmove(pick->win, pick->y, pick->x);
644 wattron(pick->win, WA_REVERSE);
647 wattroff(pick->win, WA_REVERSE);
648 wmove(pick->win, pick->y, pick->x - 1);
649}
650/** @brief Unreverses the display of the currently selected object in pick
651 window
652 @ingroup pick_engine
653 @param pick Pointer to Pick structure containing object and display
654 information
655 @note Calculates the x coordinate for the currently selected object based on
656 the current table column and column width. Moves the cursor to the object's
657 position in the pick window and displays the object's text without reverse
658 video attribute. Refreshes the pick window to show the updated display. Moves
659 the cursor back to the position before the object text for potential further
660 interactions. */
661void unreverse_object(Pick *pick) {
662 pick->x = pick->tbl_col * (pick->tbl_col_width + 1) + 1;
663 wmove(pick->win, pick->y, pick->x);
666 wmove(pick->win, pick->y, pick->x - 1);
667}
668/** @brief Toggles the selection state of the currently selected object in pick
669 window
670 @ingroup pick_engine
671 @param pick Pointer to Pick structure containing object and selection
672 information
673 @note Calculates the x coordinate for the currently selected object based on
674 the current table column and column width. If the object is currently
675 selected, it is deselected by updating the selection count, marking it as not
676 selected, and displaying a space before the object text. If the object is not
677 currently selected, it is selected by updating the selection count, marking
678 it as selected, and displaying an asterisk before the object text. Refreshes
679 the pick window to show the updated display. Moves the cursor back to the
680 position before the object text for potential further interactions. */
681void toggle_object(Pick *pick) {
682 pick->x = pick->tbl_col * (pick->tbl_col_width + 1) + 1;
683 if (pick->f_selected[pick->obj_idx]) {
684 pick->select_cnt--;
685 pick->f_selected[pick->obj_idx] = false;
686 mvwaddstr(pick->win, pick->y, pick->x - 1, " ");
687 } else {
688 pick->select_cnt++;
689 pick->f_selected[pick->obj_idx] = true;
690 mvwaddstr(pick->win, pick->y, pick->x - 1, "*");
691 }
692}
693/** @brief Deselects the currently selected object in pick window
694 @ingroup pick_engine
695 @details like toggle, but only deselects object */
696void deselect_object(Pick *pick) {
697 pick->x = pick->tbl_col * (pick->tbl_col_width + 1) + 1;
698 if (pick->f_selected[pick->obj_idx]) {
699 pick->select_cnt--;
700 pick->f_selected[pick->obj_idx] = false;
701 mvwaddstr(pick->win, pick->y, pick->x - 1, " ");
702 }
703}
704/** @brief Outputs selected objects to specified output file
705 @ingroup pick_engine
706 @param pick Pointer to Pick structure containing selected objects and
707 output file information
708 @return 0 on success, 1 on failure
709 @note If output file cannot be opened, an error message is printed and
710 the function returns 1. Otherwise, selected objects are written to the
711 output file, one per line, and the file is closed before returning 0.
712*/
713int output_objects(Pick *pick) {
714 char tmp_str[MAXLEN];
715 int m;
716 if ((pick->out_fp = fopen(pick->out_spec, "w")) == nullptr) {
717 m = MAXLEN - 30;
718 strnz__cpy(tmp_str, "Can't open pick output file: ", m);
719 m -= strlen(pick->in_spec);
720 strnz__cat(tmp_str, pick->out_spec, m);
721 }
722 for (pick->obj_idx = 0; pick->obj_idx < pick->obj_cnt; pick->obj_idx++) {
723 if (pick->f_selected[pick->obj_idx])
724 fprintf(stdout, "%s\n", pick->object[pick->obj_idx]);
725 }
726 fflush(stdout);
727 if (pick->out_fp != nullptr)
728 fclose(pick->out_fp);
729 return (0);
730}
731/** @brief Executes specified command with selected objects as arguments
732 @ingroup pick_engine
733 @param init Pointer to Init structure
734 @return 0 on success, 1 on failure
735 @note Parses command string and appends selected objects as arguments to the
736 command. If command contains "%%", it is replaced with a space- separated
737 list of selected objects. Executes the command using execvp in a child
738 process and waits for it to finish. If the command is a pager or editor, it
739 is executed within the pick interface using popup_view instead of execvp.
740 @note If f_append_args is true, the argument containing %% is replaced with
741 the concatenated selected objects. If f_append_args is false, selected
742 objects are added as separate arguments and the original command arguments
743 remain unchanged.
744 @note eargv should be null-terminated to indicate the end of arguments for
745 execvp
746 @note Memory allocated for arguments is freed after execution to prevent
747 memory leaks.
748 @note If execvp fails, an error message is printed and the child process
749 exits with failure status
750 @note The parent process waits for the child process to finish before
751 proceeding and restores terminal settings
752 @note If the command is a pager or editor, it is executed within the pick
753 interface using popup_view instead of execvp
754 @note The base name of the command is extracted to check if it is a pager or
755 editor
756 @note If the command is a pager or editor, the pick interface is used to
757 display the command output instead of executing it in a separate terminal
758 This allows the user to view the command output without leaving the pick
759 interface and provides a more seamless user experience.
760 @note If the command is not a pager or editor, it is executed in a separate
761 terminal and the pick interface is restored after execution
762 @note If the command to be executed is view, an external command is not
763 needed, instead the popup_view function can be used to display the output
764 within the pick interface */
765int exec_objects(Init *init) {
766 int rc = -1;
767 int eargc;
768 char *eargv[MAXARGS];
769 char tmp_str[MAXLEN];
770 char title[MAXLEN];
771 char sav_arg[MAXLEN];
772 char *out_s;
773 int eargx = 0;
774 int i = 0;
775 pid_t pid = 0;
776 bool f_append_args = false;
777 // char *s1;
778
779 title[0] = '\0';
780 if (pick->cmd[0] == '\0')
781 return -1;
782 if (pick->cmd[0] == '\\' || pick->cmd[0] == '\"') {
783 size_t len = strlen(pick->cmd);
784 if (len > 1 && pick->cmd[len - 1] == '\"') {
785 memmove(pick->cmd, pick->cmd + 1, len - 2);
786 pick->cmd[len - 2] = '\0';
787 }
788 }
789 eargc = str_to_args(eargv, pick->cmd, MAXARGS - 1);
790 tmp_str[0] = '\0';
792 for (i = 0; i < pick->obj_cnt; i++) {
793 if (pick->f_selected[i] && eargc < MAXARGS) {
794 if (tmp_str[0] != '\0')
795 strnz__cat(tmp_str, " ", MAXLEN - 1);
796 strnz__cat(tmp_str, pick->object[i], MAXLEN - 1);
797 }
798 }
799 eargv[eargc++] = strdup(tmp_str);
800 } else {
801 f_append_args = false;
802 i = 0;
803 while (i < eargc) {
804 /** This is the line that gets the selected objects */
805 if (strstr(eargv[i], "%%") != nullptr) {
806 tmp_str[0] = '\0';
807 f_append_args = true;
808 strnz__cpy(sav_arg, eargv[i], MAXLEN - 1);
809 eargx = i;
810 break;
811 }
812 i++;
813 }
814 for (i = 0; i < pick->obj_cnt; i++) {
815 /** append arguments onto tmp_str */
816 if (pick->f_selected[i] && eargc < MAXARGS - 1) {
817 if (f_append_args == true) {
818 if (tmp_str[0] != '\0')
819 strnz__cat(tmp_str, " ", MAXLEN - 1);
820 strnz__cat(tmp_str, pick->object[i], MAXLEN - 1);
821 continue;
822 }
823 eargv[eargc++] = strdup(pick->object[i]);
824 }
825 }
826 if (f_append_args == true) {
827 if (eargv[eargx] != nullptr) {
828 free(eargv[eargx]);
829 eargv[eargx] = nullptr;
830 }
831 out_s = rep_substring(sav_arg, "%%", tmp_str);
832 if (out_s == nullptr || out_s[0] == '\0') {
833 i = 0;
834 while (i < eargc) {
835 if (eargv[i] != nullptr)
836 free(eargv[i]);
837 i++;
838 }
839 Perror("rep_substring() failed in exec_objects");
840 return 1;
841 }
842 strnz__cpy(title, out_s, MAXLEN - 1);
843 eargv[eargx] = strdup(out_s);
844 if (out_s != nullptr) {
845 free(out_s);
846 out_s = nullptr;
847 }
848 }
849 }
850 strnz__cpy(tmp_str, eargv[0], MAXLEN - 1);
851 eargv[eargc] = nullptr;
852 // s1 = tmp_str;
853 char *sp;
854 // Scan-build false positive: strtok_r modifies the input string, but
855 // tmp_str is a local array and is not used after this point, so it is safe
856 // to modify it with strtok_r
857 char *tok;
858 tok = strtok_r(tmp_str, " ", &sp);
859 strnz__cpy(sav_arg, tok, MAXLEN - 1);
860 base_name(tmp_str, sav_arg);
861 if (tmp_str[0] != '\0' &&
862 (strcmp(tmp_str, "view") == 0 || strcmp(tmp_str, "view") == 0)) {
863 /** initialize popup_view arguments and execute popup_view to display
864 command output within pick interface */
865 init->lines = 60;
866 init->cols = 80;
867 init->begy = pick->begy + 1;
868 init->begx = pick->begx + 1;
869 if (title[0] != '\0')
870 strnz__cpy(init->title, title, MAXLEN - 1);
871 else
872 strnz__cpy(init->title, eargv[eargc], MAXLEN - 1);
873 popup_view(init, eargc, eargv, init->lines, init->cols, init->begy,
874 init->begx);
875 i = 0;
876 while (i < eargc) {
877 if (eargv[i] != nullptr)
878 free(eargv[i]);
879 i++;
880 }
881 return 0;
882 } else {
883 if ((pid = fork()) == -1) {
884 /** fork failed, free eargv and return error */
885 i = 0;
886 while (i < eargc) {
887 if (eargv[i] != nullptr)
888 free(eargv[i]);
889 i++;
890 }
891 Perror("fork() failed in exec_objects");
892 return (1);
893 }
894 if (pid == 0) {
895 /** Child process to execute command */
896 execvp(eargv[0], eargv);
897 /** If execvp returns, it means execution failed, so free eargv
898 and print error message before exiting */
899 destroy_argv(eargc, eargv);
900 strnz__cpy(tmp_str, "Can't exec pick cmd: ", MAXLEN - 1);
901 strnz__cat(tmp_str, eargv[0], MAXLEN - 1);
902 Perror(tmp_str);
903 exit(EXIT_FAILURE);
904 }
905 }
906 // waitpid_with_timeout(pid, wait_timeout);
907 // action_disposition("Notification", "Command executed");
908 waitpid(pid, nullptr, 0);
909 // win_del();
910 destroy_argv(eargc, eargv);
913 werase(stdscr);
914 wrefresh(stdscr);
916 return rc;
917}
918/** @brief Initializes the pick window based on the parameters specified in
919the Pick structure
920 @ingroup pick_engine
921 @param init Pointer to Init structure containing pick information
922 @return 0 on success, 1 on failure
923 @note Creates a new window for the pick interface using win_new function
924with the specified parameters from the Pick structure. If window creation
925fails, an error message is printed and the function returns 1. Otherwise,
926initializes the window and box pointers in the Pick structure, sets scrollok
927and keypad options for the window, and returns 0 on success. */
928int open_pick_win(Init *init) {
929 char tmp_str[MAXLEN];
930 pick = init->pick;
932 pick->title, 0)) {
933 ssnprintf(tmp_str, MAXLEN - 1, "win_new(%d, %d, %d, %d, %s, %b) failed",
936 Perror(tmp_str);
937
938 return (1);
939 }
942 scrollok(pick->win, false);
943 keypad(pick->win, true);
944 return 0;
945}
946/** @brief Displays the help screen for the pick interface using view
947 @ingroup pick_engine
948 @param init Pointer to Init structure containing pick information
949 @note Initializes the help_spec field in the Pick structure with the
950 path to the pick help file. Then, constructs the argument list for
951 executing popup_view with the help file as an argument. Finally, calls
952 popup_view function to display the help screen within the pick interface. */
953void display_pick_help(Init *init) {
954 char tmp_str[MAXLEN];
955 eargc = 0;
956 if (pick->f_help_spec && pick->help_spec[0] != '\0')
958 else {
959 strnz__cpy(tmp_str, init->mapp_help, MAXLEN - 1);
960 strnz__cat(tmp_str, "/", MAXLEN - 1);
962 }
963 eargv[eargc++] = strdup("view");
964 eargv[eargc++] = strdup("-N");
965 eargv[eargc++] = strdup("f");
966 eargv[eargc++] = strdup(tmp_str);
968 init->lines = 30;
969 init->cols = 76;
970 init->begy = pick->begy + 1;
971 init->begx = pick->begx + 1;
972 strnz__cpy(init->title, "Pick Help", MAXLEN - 1);
974 init->begx);
976 return;
977}
#define P_READ
Definition common.h:50
#define PICK_HELP_FILE
Definition common.h:38
int popup_view(Init *, int, char **, int, int, int, int)
Definition popups.c:47
#define P_WRITE
Definition common.h:51
Pick * pick
Definition mem.c:46
#define OBJ_MAXLEN
Definition pick.h:16
#define OBJ_MAXCNT
Definition pick.h:17
int eargc
Definition futil.c:41
#define MAXARGS
Definition cm.h:30
int wait_timeout
Definition futil.c:98
#define nullptr
Definition cm.h:23
char * eargv[MAXARGS]
Definition futil.c:42
#define min(x, y)
min macro evaluates two expressions, returning least result
Definition cm.h:55
#define max(a, b)
max macro evaluates two expressions, returning greatest result.
Definition cm.h:48
#define MAXLEN
Definition curskeys.c:15
int tbl_line
Definition pick_engine.c:23
int obj_idx
Definition pick_engine.c:24
int tbl_cols
Definition pick_engine.c:23
char const pagers_editors[12][10]
Definition pick_engine.c:41
int tbl_col
Definition pick_engine.c:23
int pipe_fd[2]
Definition pick_engine.c:39
int tbl_pages
Definition pick_engine.c:23
int calculated_idx
Definition pick_engine.c:24
int pg_lines
Definition pick_engine.c:23
int tbl_page
Definition pick_engine.c:23
unsigned int cmd_key
Definition dwin.c:117
WINDOW * win_win[MAXWIN]
Definition dwin.c:114
bool waitpid_with_timeout(pid_t pid, int timeout)
Definition dwin.c:1431
int tty_fd
Definition dwin.c:153
void display_chyron(WINDOW *win, Chyron *chyron, int line, int col)
Definition dwin.c:297
int click_x
Definition dwin.c:45
int win_ptr
Definition dwin.c:121
int click_y
Definition dwin.c:44
void set_chyron_key(Chyron *, int, char *, int)
Definition dwin.c:245
WINDOW * win_box[MAXWIN]
Definition dwin.c:115
int xwgetch(WINDOW *, Chyron *, int)
Wrapper for wgetch that handles signals, mouse events, checks for clicks on the chyron line,...
Definition dwin.c:1359
void restore_wins()
Restore all windows after a screen resize.
Definition dwin.c:938
int win_new(int, int, int, int, char *, int)
Create a new window with optional box and title.
Definition dwin.c:783
WINDOW * win_del()
Delete the current window and its associated box window.
Definition dwin.c:902
void mvwaddstr_fill(WINDOW *, int, int, char *, int)
For lines shorter than their display area, fill the rest with spaces.
Definition dwin.c:1262
Chyron * destroy_chyron(Chyron *chyron)
Destroy Chyron structure.
Definition dwin.c:198
void compile_chyron(Chyron *)
construct the chyron string from the chyron structure
Definition dwin.c:268
Chyron * new_chyron()
Create and initialize Chyron structure.
Definition dwin.c:183
bool wait_destroy(Chyron *)
Destroy the waiting message window and chyron.
Definition dwin.c:1203
int wait_continue(WINDOW *, Chyron *, int)
Update the waiting message with remaining time and check for user input.
Definition dwin.c:1215
WINDOW * wait_mk_win(Chyron *, char *)
Display a popup waiting message.
Definition dwin.c:1172
int Perror(char *)
Display a simple error message window or print to stderr.
Definition dwin.c:1110
void abend(int, char *)
Abnormal program termination.
Definition dwin.c:1331
Chyron * wait_mk_chyron()
Create a Chyron struct for the waiting message.
Definition dwin.c:1161
void destroy_argv(int argc, char **argv)
Deallocates memory allocated for argument strings in argv.
Definition futil.c:221
size_t strnz__cpy(char *, const char *, size_t)
safer alternative to strncpy
Definition futil.c:269
size_t strnz(char *, size_t)
terminates string at New Line, Carriage Return, or max_len
Definition futil.c:340
size_t ssnprintf(char *, size_t, const char *,...)
ssnprintf was designed to be a safer alternative to snprintf.
Definition futil.c:147
size_t strnz__cat(char *, const char *, size_t)
safer alternative to strncat
Definition futil.c:298
char * rep_substring(const char *, const char *, const char *)
Replace all occurrences of "tgt_s" in "org_s" with "rep_s".
Definition futil.c:1292
bool base_name(char *, char *)
Returns the base name of a file specification.
Definition futil.c:775
int str_to_args(char **, char *, int)
Converts a string into an array of argument strings.
Definition futil.c:167
Pick * new_pick(Init *, int, char **, int, int)
Create and initialize Pick structure.
Definition mem.c:200
Pick * destroy_pick(Init *init)
Destroy Pick structure.
Definition mem.c:230
void save_object(Pick *, char *)
Saves a string as an object in the pick structure.
void toggle_object(Pick *)
Toggles the selection state of the currently selected object in pick window.
int init_pick(Init *, int, char **, int, int)
Initializes pick structure and opens pick input file or pipe.
Definition pick_engine.c:59
void unreverse_object(Pick *)
Unreverses the display of the currently selected object in pick window.
int output_objects(Pick *)
Outputs selected objects to specified output file.
void deselect_object(Pick *)
Deselects the currently selected object in pick window.
int read_pick_input(Init *)
Reads pick input from file pointer and saves objects into pick structure.
void reverse_object(Pick *)
Reverses the display of the currently selected object in pick window.
int pick_engine(Init *)
Initializes pick interface, calculates window size and position, and enters picker loop.
int picker(Init *)
Main loop to handle user input and interactions for pick interface.
void display_page(Pick *)
Displays current page of objects in pick window.
void display_pick_help(Init *)
Displays the help screen for the pick interface using view.
int open_pick_win(Init *)
Initializes the pick window based on the parameters specified in the Pick structure.
int exec_objects(Init *)
Executes specified command with selected objects as arguments.
bool restore_curses_tioctl()
restore_curses_tioctl() - restore curses terminal settings
Definition scriou.c:81
void sig_prog_mode()
Set up signal handlers for interrupt signals.
Definition sig.c:62
char title[MAXLEN]
Definition common.h:123
char mapp_help[MAXLEN]
Definition common.h:140
int begx
Definition common.h:112
SIO * sio
Definition common.h:108
int cols
Definition common.h:110
int begy
Definition common.h:111
int lines
Definition common.h:109
Pick * pick
Definition common.h:176
int l
Definition cm.h:244
char s[MAXLEN]
Definition cm.h:242
int stdout_fd
Definition cm.h:662
int stdin_fd
Definition cm.h:661
int pg_line
Definition pick.h:72
int tbl_lines
Definition pick.h:79
int tbl_pages
Definition pick.h:76
int tbl_cols
Definition pick.h:80
bool f_cmd
Definition pick.h:64
int obj_idx
Definition pick.h:71
int obj_cnt
Definition pick.h:70
int pg_lines
Definition pick.h:73
int win_width
Definition pick.h:30
WINDOW * box
Definition pick.h:36
char cmd[MAXLEN]
Definition pick.h:51
bool f_help_spec
Definition pick.h:58
FILE * in_fp
Definition pick.h:40
int tbl_col_width
Definition pick.h:82
bool f_out_spec
Definition pick.h:55
int argc
Definition pick.h:38
int tbl_page
Definition pick.h:77
bool f_multiple_cmd_args
Definition pick.h:59
int x
Definition pick.h:34
bool f_in_pipe
Definition pick.h:56
bool f_selected[OBJ_MAXCNT]
Definition pick.h:60
int select_cnt
Definition pick.h:68
char title[MAXLEN]
Definition pick.h:37
int win_lines
Definition pick.h:29
char help_spec[MAXLEN]
Definition pick.h:47
char in_buf[BUFSIZ]
Definition pick.h:65
int tbl_line
Definition pick.h:78
char ** argv
Definition pick.h:39
int select_max
Definition pick.h:69
char in_spec[MAXLEN]
Definition pick.h:45
WINDOW * win
Definition pick.h:35
FILE * out_fp
Definition pick.h:41
int begx
Definition pick.h:32
Chyron * chyron
Definition pick.h:83
int begy
Definition pick.h:31
int tbl_col
Definition pick.h:81
char ** object
Definition pick.h:66
char provider_cmd[MAXLEN]
Definition pick.h:49
char out_spec[MAXLEN]
Definition pick.h:46
int y
Definition pick.h:33