Bug Summary

File:keybindings.c
Location:line 1700, column 48
Description:Array access (via field 'files') results in a null pointer dereference

Annotated Source Code

1/*
2 * Copyright (c) 2010, 2011, 2012 Ryan Flannery <ryan.flannery@gmail.com>
3 *
4 * Permission to use, copy, modify, and distribute this software for any
5 * purpose with or without fee is hereby granted, provided that the above
6 * copyright notice and this permission notice appear in all copies.
7 *
8 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15 */
16
17#include "keybindings.h"
18
19
20/* This table maps KeyActions to their string representations */
21typedef struct {
22 KeyAction action;
23 char *name;
24} KeyActionName;
25
26const KeyActionName KeyActionNames[] = {
27 { scroll_up_e, "scroll_up" },
28 { scroll_down_e, "scroll_down" },
29 { scroll_up_page_e, "scroll_up_page" },
30 { scroll_down_page_e, "scroll_down_page" },
31 { scroll_up_halfpage_e, "scroll_up_halfpage" },
32 { scroll_down_halfpage_e, "scroll_down_halfpage" },
33 { scroll_up_wholepage_e, "scroll_up_wholepage" },
34 { scroll_down_wholepage_e, "scroll_down_wholepage" },
35 { scroll_left_e, "scroll_left" },
36 { scroll_right_e, "scroll_right" },
37 { scroll_leftmost_e, "scroll_leftmost" },
38 { scroll_rightmost_e, "scroll_rightmost" },
39 { jumpto_screen_top_e, "jumpto_screen_top" },
40 { jumpto_screen_middle_e, "jumpto_screen_middle" },
41 { jumpto_screen_bottom_e, "jumpto_screen_bottom" },
42 { jumpto_line_e, "jumpto_line" },
43 { jumpto_percent_e, "jumpto_percent" },
44 { search_forward_e, "search_forward" },
45 { search_backward_e, "search_backward" },
46 { find_next_forward_e, "find_next_forward" },
47 { find_next_backward_e, "find_next_backward" },
48 { visual_e, "visual" },
49 { cut_e, "cut" },
50 { yank_e, "yank" },
51 { paste_after_e, "paste_after" },
52 { paste_before_e, "paste_before" },
53 { undo_e, "undo" },
54 { redo_e, "redo" },
55 { go_e, "go" },
56 { quit_e, "quit" },
57 { redraw_e, "redraw" },
58 { command_mode_e, "command_mode" },
59 { shell_e, "shell" },
60 { switch_windows_e, "switch_window" },
61 { show_file_info_e, "show_file_info" },
62 { load_playlist_e, "load_playlist" },
63 { media_play_e, "media_play" },
64 { media_pause_e, "media_pause" },
65 { media_stop_e, "media_stop" },
66 { media_next_e, "media_next" },
67 { media_prev_e, "media_prev" },
68 { volume_increase_e, "volume_increase" },
69 { volume_decrease_e, "volume_decrease" },
70 { seek_forward_seconds_e, "seek_forward_seconds" },
71 { seek_backward_seconds_e, "seek_backward_seconds" },
72 { seek_forward_minutes_e, "seek_forward_minutes" },
73 { seek_backward_minutes_e, "seek_backward_minutes" },
74 { toggle_forward_e, "toggle_forward" },
75 { toggle_backward_e, "toggle_backward" }
76};
77const size_t KeyActionNamesSize = sizeof(KeyActionNames) / sizeof(KeyActionName);
78
79
80/* This table maps KeyActions to their handler-functions (with arguments) */
81typedef struct {
82 KeyAction action;
83 ActionHandler handler;
84 bool_Bool visual;
85 KbaArgs args;
86} KeyActionHandler;
87
88#define ARG_NOT_USED{ .num = 0 } { .num = 0 }
89const KeyActionHandler KeyActionHandlers[] = {
90{ scroll_up_e, kba_scroll_row, true1, { .direction = UP }},
91{ scroll_down_e, kba_scroll_row, true1, { .direction = DOWN }},
92{ scroll_up_page_e, kba_scroll_page, true1, { .direction = UP, .amount = SINGLE }},
93{ scroll_down_page_e, kba_scroll_page, true1, { .direction = DOWN, .amount = SINGLE }},
94{ scroll_up_halfpage_e, kba_scroll_page, true1, { .direction = UP, .amount = HALF }},
95{ scroll_down_halfpage_e, kba_scroll_page, true1, { .direction = DOWN, .amount = HALF }},
96{ scroll_up_wholepage_e, kba_scroll_page, true1, { .direction = UP, .amount = WHOLE }},
97{ scroll_down_wholepage_e, kba_scroll_page, true1, { .direction = DOWN, .amount = WHOLE }},
98{ scroll_left_e, kba_scroll_col, true1, { .direction = LEFT, .amount = SINGLE }},
99{ scroll_right_e, kba_scroll_col, true1, { .direction = RIGHT, .amount = SINGLE }},
100{ scroll_leftmost_e, kba_scroll_col, true1, { .direction = LEFT, .amount = WHOLE }},
101{ scroll_rightmost_e, kba_scroll_col, true1, { .direction = RIGHT, .amount = WHOLE }},
102{ jumpto_screen_top_e, kba_jumpto_screen, true1, { .placement = TOP }},
103{ jumpto_screen_middle_e, kba_jumpto_screen, true1, { .placement = MIDDLE }},
104{ jumpto_screen_bottom_e, kba_jumpto_screen, true1, { .placement = BOTTOM }},
105{ jumpto_line_e, kba_jumpto_file, true1, { .scale = NUMBER, .num = 'G' }},
106{ jumpto_percent_e, kba_jumpto_file, true1, { .scale = PERCENT }},
107{ search_forward_e, kba_search, true1, { .direction = FORWARDS }},
108{ search_backward_e, kba_search, true1, { .direction = BACKWARDS }},
109{ find_next_forward_e, kba_search_find, true1, { .direction = SAME }},
110{ find_next_backward_e, kba_search_find, true1, { .direction = OPPOSITE }},
111{ visual_e, kba_visual, true1, ARG_NOT_USED{ .num = 0 } },
112{ cut_e, kba_cut, true1, ARG_NOT_USED{ .num = 0 } },
113{ yank_e, kba_yank, true1, ARG_NOT_USED{ .num = 0 } },
114{ paste_after_e, kba_paste, false0, { .placement = AFTER }},
115{ paste_before_e, kba_paste, false0, { .placement = BEFORE }},
116{ undo_e, kba_undo, false0, ARG_NOT_USED{ .num = 0 } },
117{ redo_e, kba_redo, false0, ARG_NOT_USED{ .num = 0 } },
118{ go_e, kba_go, true1, ARG_NOT_USED{ .num = 0 } },
119{ quit_e, kba_quit, false0, ARG_NOT_USED{ .num = 0 } },
120{ redraw_e, kba_redraw, false0, ARG_NOT_USED{ .num = 0 } },
121{ command_mode_e, kba_command_mode, false0, ARG_NOT_USED{ .num = 0 } },
122{ shell_e, kba_shell, false0, ARG_NOT_USED{ .num = 0 } },
123{ switch_windows_e, kba_switch_windows, false0, ARG_NOT_USED{ .num = 0 } },
124{ show_file_info_e, kba_show_file_info, false0, ARG_NOT_USED{ .num = 0 } },
125{ load_playlist_e, kba_load_playlist, false0, ARG_NOT_USED{ .num = 0 } },
126{ media_play_e, kba_play, false0, ARG_NOT_USED{ .num = 0 } },
127{ media_pause_e, kba_pause, false0, ARG_NOT_USED{ .num = 0 } },
128{ media_stop_e, kba_stop, false0, ARG_NOT_USED{ .num = 0 } },
129{ media_next_e, kba_play_next, false0, ARG_NOT_USED{ .num = 0 } },
130{ media_prev_e, kba_play_prev, false0, ARG_NOT_USED{ .num = 0 } },
131{ volume_increase_e, kba_volume, false0, { .direction = FORWARDS }},
132{ volume_decrease_e, kba_volume, false0, { .direction = BACKWARDS }},
133{ seek_forward_seconds_e, kba_seek, false0, { .direction = FORWARDS, .scale = SECONDS, .num = 10 }},
134{ seek_backward_seconds_e, kba_seek, false0, { .direction = BACKWARDS, .scale = SECONDS, .num = 10 }},
135{ seek_forward_minutes_e, kba_seek, false0, { .direction = FORWARDS, .scale = MINUTES, .num = 1 }},
136{ seek_backward_minutes_e, kba_seek, false0, { .direction = BACKWARDS, .scale = MINUTES, .num = 1 }},
137{ toggle_forward_e, kba_toggle, false0, { .direction = FORWARDS }},
138{ toggle_backward_e, kba_toggle, false0, { .direction = BACKWARDS }}
139};
140const size_t KeyActionHandlersSize = sizeof(KeyActionHandlers) / sizeof(KeyActionHandler);
141
142
143/* This table contains the default keybindings */
144typedef struct {
145 KeyCode key;
146 KeyAction action;
147} KeyBinding;
148
149#define MY_K_TAB9 9
150#define MY_K_ENTER13 13
151#define MY_K_ESCAPE27 27
152#define kb_CONTROL(x)((x) - 'a' + 1) ((x) - 'a' + 1)
153
154const KeyBinding DefaultKeyBindings[] = {
155 { 'k', scroll_up_e },
156 { '-', scroll_up_e },
157 { KEY_UP0403, scroll_up_e },
158 { 'j', scroll_down_e },
159 { KEY_DOWN0402, scroll_down_e },
160 { kb_CONTROL('y')(('y') - 'a' + 1), scroll_up_page_e },
161 { kb_CONTROL('e')(('e') - 'a' + 1), scroll_down_page_e },
162 { kb_CONTROL('u')(('u') - 'a' + 1), scroll_up_halfpage_e },
163 { kb_CONTROL('d')(('d') - 'a' + 1), scroll_down_halfpage_e },
164 { kb_CONTROL('b')(('b') - 'a' + 1), scroll_up_wholepage_e },
165 { KEY_PPAGE0523, scroll_up_wholepage_e },
166 { kb_CONTROL('f')(('f') - 'a' + 1), scroll_down_wholepage_e },
167 { KEY_NPAGE0522, scroll_down_wholepage_e },
168 { 'h', scroll_left_e },
169 { KEY_LEFT0404, scroll_left_e },
170 { KEY_BACKSPACE0407, scroll_left_e },
171 { 'l', scroll_right_e },
172 { KEY_RIGHT0405, scroll_right_e },
173 { ' ', scroll_right_e },
174 { '^', scroll_leftmost_e },
175 { '0', scroll_leftmost_e },
176 { '|', scroll_leftmost_e },
177 { '$', scroll_rightmost_e },
178 { 'H', jumpto_screen_top_e },
179 { 'M', jumpto_screen_middle_e },
180 { 'L', jumpto_screen_bottom_e },
181 { 'G', jumpto_line_e },
182 { '%', jumpto_percent_e },
183 { '/', search_forward_e },
184 { '?', search_backward_e },
185 { 'n', find_next_forward_e },
186 { 'N', find_next_backward_e },
187 { 'v', visual_e },
188 { 'V', visual_e },
189 { 'd', cut_e },
190 { 'y', yank_e },
191 { 'p', paste_after_e },
192 { 'P', paste_before_e },
193 { 'u', undo_e },
194 { kb_CONTROL('r')(('r') - 'a' + 1), redo_e },
195 { 'g', go_e },
196 { kb_CONTROL('c')(('c') - 'a' + 1), quit_e },
197 { kb_CONTROL('/')(('/') - 'a' + 1), quit_e },
198 { kb_CONTROL('l')(('l') - 'a' + 1), redraw_e },
199 { ':', command_mode_e },
200 { '!', shell_e },
201 { MY_K_TAB9, switch_windows_e },
202 { 'm', show_file_info_e },
203 { MY_K_ENTER13, load_playlist_e },
204 { MY_K_ENTER13, media_play_e },
205 { 'z', media_pause_e },
206 { 's', media_stop_e },
207 { 'f', seek_forward_seconds_e },
208 { ']', seek_forward_seconds_e },
209 { 'b', seek_backward_seconds_e },
210 { '[', seek_backward_seconds_e },
211 { 'F', seek_forward_minutes_e },
212 { '}', seek_forward_minutes_e },
213 { 'B', seek_backward_minutes_e },
214 { '{', seek_backward_minutes_e },
215 { '(', media_prev_e },
216 { ')', media_next_e },
217 { '<', volume_decrease_e },
218 { '>', volume_increase_e },
219 { 't', toggle_forward_e },
220 { 'T', toggle_backward_e }
221};
222const size_t DefaultKeyBindingsSize = sizeof(DefaultKeyBindings) / sizeof(KeyBinding);
223
224
225/* Mapping of special keys to their values */
226typedef struct {
227 char *str;
228 KeyCode code;
229} SpecialKeyCode;
230SpecialKeyCode SpecialKeys[] = {
231 { "BACKSPACE", KEY_BACKSPACE0407 },
232 { "ENTER", MY_K_ENTER13 },
233 { "SPACE", ' ' },
234 { "TAB", MY_K_TAB9 },
235 { "UP", KEY_UP0403 },
236 { "DOWN", KEY_DOWN0402 },
237 { "LEFT", KEY_LEFT0404 },
238 { "RIGHT", KEY_RIGHT0405 },
239 { "PAGEUP", KEY_PPAGE0523 },
240 { "PAGEDOWN", KEY_NPAGE0522 }
241};
242const size_t SpecialKeysSize = sizeof(SpecialKeys) / sizeof(SpecialKeyCode);
243
244
245/*
246 * This is the actual keybinding table mapping KeyCodes to actions. It is
247 * loaded at initial runtime (kb_init()), and modified thereafter, either via
248 * "bind" commands in the config file or issued during runtime.
249 */
250#define KEYBINDINGS_CHUNK_SIZE100 100
251KeyBinding *KeyBindings;
252size_t KeyBindingsSize;
253size_t KeyBindingsCapacity;
254
255void
256kb_increase_capacity()
257{
258 KeyBinding *new_buffer;
259 size_t nbytes;
260
261 KeyBindingsCapacity += KEYBINDINGS_CHUNK_SIZE100;
262 nbytes = KeyBindingsCapacity * sizeof(KeyBinding);
263 if ((new_buffer = realloc(KeyBindings, nbytes)) == NULL((void *)0))
264 err(1, "%s: failed to realloc(3) keybindings", __FUNCTION__);
265
266 KeyBindings = new_buffer;
267}
268
269
270/****************************************************************************
271 *
272 * Routines for initializing, free'ing, manipulating,
273 * and retrieving keybindings.
274 *
275 ****************************************************************************/
276
277void
278kb_init()
279{
280 size_t i;
281
282 /* setup empty buffer */
283 KeyBindingsSize = 0;
284 KeyBindingsCapacity = 0;
285 kb_increase_capacity();
286
287 /* install default bindings */
288 for (i = 0; i < DefaultKeyBindingsSize; i++)
289 kb_bind(DefaultKeyBindings[i].action, DefaultKeyBindings[i].key);
290}
291
292void
293kb_free()
294{
295 free(KeyBindings);
296 KeyBindingsSize = 0;
297}
298
299void
300kb_bind(KeyAction action, KeyCode key)
301{
302 kb_unbind_key(key);
303
304 if (KeyBindingsSize == KeyBindingsCapacity)
305 kb_increase_capacity();
306
307 KeyBindings[KeyBindingsSize].action = action;
308 KeyBindings[KeyBindingsSize].key = key;
309 KeyBindingsSize++;
310}
311
312void
313kb_unbind_action(KeyAction action)
314{
315 size_t i, j;
316
317 for (i = 0; i < KeyBindingsSize; i++) {
318 if (KeyBindings[i].action == action) {
319 for (j = i; j < KeyBindingsSize - 1; j++)
320 KeyBindings[j] = KeyBindings[j + 1];
321
322 KeyBindingsSize--;
323 }
324 }
325}
326
327void
328kb_unbind_key(KeyCode key)
329{
330 size_t i, j;
331
332 for (i = 0; i < KeyBindingsSize; i++) {
333 if (KeyBindings[i].key == key) {
334 for (j = i; j < KeyBindingsSize - 1; j++)
335 KeyBindings[j] = KeyBindings[j + 1];
336
337 KeyBindingsSize--;
338 }
339 }
340}
341
342void
343kb_unbind_all()
344{
345 KeyBindingsSize = 0;
346}
347
348bool_Bool
349kb_str2action(const char *s, KeyAction *action)
350{
351 size_t i;
352
353 for (i = 0; i < KeyActionNamesSize; i++) {
354 if (strcasecmp(KeyActionNames[i].name, s) == 0) {
355 *action = KeyActionNames[i].action;
356 return true1;
357 }
358 }
359
360 return false0;
361}
362
363KeyCode
364kb_str2specialKey(char *s)
365{
366 size_t i;
367
368 for (i = 0; i < SpecialKeysSize; i++) {
369 if (strcasecmp(SpecialKeys[i].str, s) == 0)
370 return SpecialKeys[i].code;
371 }
372
373 return -1;
374}
375
376KeyCode
377kb_str2keycode(char *s)
378{
379 KeyCode val;
380
381 if (strlen(s) == 1)
382 val = s[0];
383 else
384 val = kb_str2specialKey(s);
385
386 return val;
387}
388
389KeyCode
390kb_str2keycode2(char *control, char *key)
391{
392 KeyCode val;
393
394 if (strcasecmp(control, "control") != 0)
395 return -1;
396
397 if ((val = kb_str2keycode(key)) > 0)
398 return kb_CONTROL(val)((val) - 'a' + 1);
399 else
400 return -1;
401}
402
403bool_Bool
404kb_execute(KeyCode k)
405{
406 KeyAction action;
407 size_t i;
408 bool_Bool found;
409
410 /* visual mode and ESC pressed ?*/
411 if (visual_mode_start != -1 && k == MY_K_ESCAPE27) {
412 visual_mode_start = -1;
413 return true1;
414 }
415
416 /* Is the key bound? */
417 found = false0;
418 action = -1;
419 for (i = 0; i < KeyBindingsSize; i++) {
420 if (KeyBindings[i].key == k) {
421 action = KeyBindings[i].action;
422 found = true1;
423 }
424 }
425
426 if (!found)
427 return false0;
428
429 /* Execute theaction handler. */
430 for (i = 0; i < KeyActionHandlersSize; i++) {
431 if (KeyActionHandlers[i].action == action) {
432 if (visual_mode_start == -1 || KeyActionHandlers[i].visual) {
433 ((KeyActionHandlers[i].handler)(KeyActionHandlers[i].args));
434 return true1;
435 }
436 }
437 }
438
439 return false0;
440}
441
442bool_Bool
443kb_execute_by_name(const char *name)
444{
445 KeyAction a;
446 size_t x;
447
448 if (!kb_str2action(name, &a))
449 return false0;
450
451 for(x = 0; x < KeyActionHandlersSize; x++) {
452 if(a == KeyActionHandlers[x].action) {
453 KeyActionHandlers[x].handler(KeyActionHandlers[x].args);
454 return true1;
455 }
456 }
457 return false0;
458}
459
460
461/*****************************************************************************
462 *
463 * Individual Keybinding Handlers
464 *
465 ****************************************************************************/
466
467KbaArgs
468get_dummy_args()
469{
470 KbaArgs dummy = { .direction = -1, .scale = -1, .amount = -1,
471 .placement = -1, .num = -1 };
472
473 return dummy;
474}
475
476void
477kba_scroll_row(KbaArgs a)
478{
479 int n = gnum_retrieve();
480
481 /* update current row */
482 switch (a.direction) {
483 case DOWN:
484 ui.active->crow += n;
485 break;
486 case UP:
487 ui.active->crow -= n;
488 break;
489 default:
490 errx(1, "%s: invalid direction", __FUNCTION__);
491 }
492
493 /* handle off-the-edge cases */
494 if (ui.active->crow < 0) {
495 swindow_scroll(ui.active, UP, -1 * ui.active->crow);
496 ui.active->crow = 0;
497 }
498
499 if (ui.active->voffset + ui.active->crow >= ui.active->nrows)
500 ui.active->crow = ui.active->nrows - ui.active->voffset - 1;
501
502 if (ui.active->crow >= ui.active->h) {
503 swindow_scroll(ui.active, DOWN, ui.active->crow - ui.active->h + 1);
504 ui.active->crow = ui.active->h - 1;
505 }
506
507 if (ui.active->crow < 0)
508 ui.active->crow = 0;
509
510 /* redraw */
511 redraw_active();
512 paint_status_bar();
513}
514
515void
516kba_scroll_page(KbaArgs a)
517{
518 bool_Bool maintain_row_idx = true1;
519 int voffset_original;
520 int max_row;
521 int diff = 0;
522 int n;
523
524 voffset_original = ui.active->voffset;
525
526 /* determine max row number */
527 if (ui.active->voffset + ui.active->h >= ui.active->nrows)
528 max_row = ui.active->nrows - ui.active->voffset - 1;
529 else
530 max_row = ui.active->h - 1;
531
532 /* determine how many pages to scroll */
533 n = 1;
534 if (gnum_get() > 0) {
535 n = gnum_get();
536 gnum_clear();
537 }
538
539 /* determine how much crow should change */
540 switch (a.amount) {
541 case SINGLE:
542 diff = 1 * n;
543 maintain_row_idx = true1;
544 break;
545 case HALF:
546 diff = (ui.active->h / 2) * n;
547 maintain_row_idx = false0;
548 break;
549 case WHOLE:
550 diff = ui.active->h * n;
551 maintain_row_idx = false0;
552 break;
553 default:
554 errx(1, "scroll_page: invalid amount");
555 }
556 swindow_scroll(ui.active, a.direction, diff);
557
558 /* handle updating the current row */
559 if (maintain_row_idx) {
560
561 if (a.direction == DOWN)
562 ui.active->crow -= diff;
563 else
564 ui.active->crow += diff;
565
566 /* handle off-the-edge cases */
567 if (ui.active->crow < 0)
568 ui.active->crow = 0;
569
570 if (ui.active->voffset + ui.active->crow >= ui.active->nrows)
571 ui.active->crow = ui.active->nrows - ui.active->voffset - 1;
572
573 if (ui.active->crow >= ui.active->h)
574 ui.active->crow = ui.active->h - 1;
575
576 if (ui.active->crow < 0)
577 ui.active->crow = 0;
578
579 } else {
580 /*
581 * We only move current row here if there was a change this time
582 * in the vertical offset
583 */
584
585 if (a.direction == DOWN
586 && ui.active->voffset == voffset_original) {
587 ui.active->crow += diff;
588 if (ui.active->crow > max_row)
589 ui.active->crow = max_row;
590 }
591
592 if (a.direction == UP
593 && ui.active->voffset == 0 && voffset_original == 0) {
594 ui.active->crow -= diff;
595 if (ui.active->crow < 0)
596 ui.active->crow = 0;
597 }
598 }
599
600 redraw_active();
601 paint_status_bar();
602}
603
604void
605kba_scroll_col(KbaArgs a)
606{
607 int maxlen;
608 int maxhoff;
609 int n;
610 int i;
611
612 /* determine how many cols to scroll */
613 n = 1;
614 if (gnum_get() > 0) {
615 n = gnum_get();
616 gnum_clear();
617 }
618
619 /* determine maximum horizontal offset */
620 maxhoff = 0;
621 if (ui.active == ui.playlist) {
622 if (mi_display_getwidth() > ui.active->w)
623 maxhoff = mi_display_getwidth() - ui.active->w;
624 } else {
625 maxlen = 0;
626 for (i = 0; i < mdb.nplaylists; i++) {
627 if ((int) strlen(mdb.playlists[i]->name) > maxlen)
628 maxlen = strlen(mdb.playlists[i]->name);
629 }
630 if (maxlen > ui.active->w)
631 maxhoff = maxlen - ui.active->w;
632 }
633
634 /* scroll */
635 switch (a.amount) {
636 case SINGLE:
637 swindow_scroll(ui.active, a.direction, n);
638 if (ui.active->hoffset > maxhoff)
639 ui.active->hoffset = maxhoff;
640 break;
641 case WHOLE:
642 switch (a.direction) {
643 case LEFT:
644 ui.active->hoffset = 0;
645 break;
646 case RIGHT:
647 ui.active->hoffset = maxhoff;
648 break;
649 default:
650 errx(1, "scroll_col: invalid direction");
651 }
652 break;
653 default:
654 errx(1, "scroll_col: invalid amount");
655 }
656
657 /* redraw */
658 redraw_active();
659 paint_status_bar();
660}
661
662void
663kba_jumpto_screen(KbaArgs a)
664{
665 int n;
666 int max_row;
667
668 /* get offset */
669 n = 0;
670 if (gnum_get() > 0) {
671 n = gnum_get();
672 gnum_clear();
673 }
674
675 /* determine max row number */
676 if (ui.active->voffset + ui.active->h >= ui.active->nrows)
677 max_row = ui.active->nrows - ui.active->voffset - 1;
678 else
679 max_row = ui.active->h - 1;
680
681 /* update current row */
682 switch (a.placement) {
683 case TOP:
684 ui.active->crow = n - 1;
685 break;
686 case MIDDLE:
687 ui.active->crow = max_row / 2;
688 break;
689 case BOTTOM:
690 ui.active->crow = max_row - n + 1;
691 break;
692 default:
693 errx(1, "jumpto_page: invalid location");
694 }
695
696 /* sanitize current row */
697 if (ui.active->crow < 0)
698 ui.active->crow = 0;
699
700 if (ui.active->crow >= ui.active->h)
701 ui.active->crow = ui.active->h - 1;
702
703 if (ui.active->crow > max_row)
704 ui.active->crow = max_row;
705
706 /* redraw */
707 redraw_active();
708 paint_status_bar();
709}
710
711void
712kba_jumpto_file(KbaArgs a)
713{
714 float pct;
715 int n, line, input;
716
717 /* determine line/percent to jump to */
718 line = 0;
719 n = -1;
720 if (gnum_get() > 0) {
721 n = gnum_get();
722 gnum_clear();
723 }
724
725 /* get line number to jump to */
726 switch (a.scale) {
727 case NUMBER:
728 switch (a.num) {
729 case 'g':
730 /* retrieve second 'g' (or cancel) and determine jump-point */
731 while ((input = getch()wgetch(stdscr)) && input != 'g') {
732 if (input != ERR(-1)) {
733 ungetch(input);
734 return;
735 }
736 }
737
738 if (n < 0)
739 line = 1;
740 else if (n >= ui.active->nrows)
741 line = ui.active->nrows;
742 else
743 line = n;
744
745 break;
746
747 case 'G':
748 /* determine jump-point */
749 if (n < 0 || n >= ui.active->nrows)
750 line = ui.active->nrows;
751 else
752 line = n;
753
754 break;
755
756 default:
757 errx(1, "jumpto_file: NUMBER type with no num!");
758 }
759
760 break;
761
762 case PERCENT:
763 if (n <= 0 || n > 100)
764 return;
765
766 pct = (float) n / 100;
767 line = pct * (float) ui.active->nrows;
768 break;
769
770 default:
771 errx(1, "jumpto_file: invalid scale");
772 }
773
774 /* jump */
775 if (line <= ui.active->voffset) {
776 swindow_scroll(ui.active, UP, ui.active->voffset - line + 1);
777 ui.active->crow = 0;
778 } else if (line > ui.active->voffset + ui.active->h) {
779 swindow_scroll(ui.active, DOWN, line - (ui.active->voffset + ui.active->h));
780 ui.active->crow = ui.active->h - 1;
781 } else {
782 ui.active->crow = line - ui.active->voffset - 1;
783 }
784
785 /* redraw */
786 redraw_active();
787
788 if (a.num != -1) /* XXX a damn dirty hack for now. see search_find */
789 paint_status_bar();
790}
791
792void
793kba_go(KbaArgs a UNUSED__attribute__((__unused__)))
794{
795 /* NOTE: While I intend to support most of vim's g* commands, currently
796 * this only supports 'gg' ... simply because I use it.
797 */
798 KbaArgs args = get_dummy_args();
799
800 args.scale = NUMBER;
801 args.num = 'g';
802 kba_jumpto_file(args);
803}
804
805void
806kba_search(KbaArgs a)
807{
808 const char *errmsg = NULL((void *)0);
809 KbaArgs find_args;
810 char *search_phrase;
811 char *prompt = NULL((void *)0);
812 char **argv = NULL((void *)0);
813 int argc = 0;
814 int i;
815
816 /* determine prompt to use */
817 switch (a.direction) {
818 case FORWARDS:
819 prompt = "/";
820 break;
821 case BACKWARDS:
822 prompt = "?";
823 break;
824 default:
825 errx(1, "search: invalid direction");
826 }
827
828 /* get search phrase from user */
829 if (user_getstr(prompt, &search_phrase) != 0) {
830 paint_status_bar();
831 return;
832 }
833
834 /* set the global query description and the search direction */
835 if (str2argv(search_phrase, &argc, &argv, &errmsg) != 0) {
836 paint_error("parse error: %s in '%s'", errmsg, search_phrase);
837 free(search_phrase);
838 return;
839 }
840
841 /* setup query */
842 mi_query_clear();
843 mi_query_setraw(search_phrase);
844 for (i = 0; i < argc; i++)
845 mi_query_add_token(argv[i]);
846
847 search_dir_set(a.direction);
848 argv_free(&argc, &argv);
849 free(search_phrase);
850
851 /* do the search */
852 find_args = get_dummy_args();
853 find_args.direction = SAME;
854 kba_search_find(find_args);
855}
856
857void
858kba_search_find(KbaArgs a)
859{
860 KbaArgs foo;
861 bool_Bool matches;
862 char *msg;
863 int dir = FORWARDS;
864 int start_idx;
865 int idx;
866 int c;
867
868 /* determine direction to do the search */
869 switch (a.direction) {
870 case SAME:
871 dir = search_dir_get();
872 break;
873
874 case OPPOSITE:
875 if (search_dir_get() == FORWARDS)
876 dir = BACKWARDS;
877 else
878 dir = FORWARDS;
879 break;
880
881 default:
882 errx(1, "search_find: invalid direction");
883 }
884
885 /* start looking from current row */
886 start_idx = ui.active->voffset + ui.active->crow;
887 msg = NULL((void *)0);
888 for (c = 1; c < ui.active->nrows + 1; c++) {
889
890 /* get idx of record */
891 if (dir == FORWARDS)
892 idx = start_idx + c;
893 else
894 idx = start_idx - c;
895
896 /* normalize idx */
897 if (idx < 0) {
898 idx = ui.active->nrows + idx;
899 msg = "search hit TOP, continuing at BOTTOM";
900
901 } else if (idx >= ui.active->nrows) {
902 idx %= ui.active->nrows;
903 msg = "search hit BOTTOM, continuing at TOP";
904 }
905
906 /* check if record at idx matches */
907 if (ui.active == ui.library)
908 matches = str_match_query(mdb.playlists[idx]->name);
909 else
910 matches = mi_match(viewing_playlist->files[idx]);
911
912 /* found one, jump to it */
913 if (matches) {
914 if (msg != NULL((void *)0))
915 paint_message(msg);
916
917 gnum_set(idx + 1);
918 foo = get_dummy_args();
919 foo.scale = NUMBER;
920 foo.num = 'G';
921 kba_jumpto_file(foo);
922 return;
923 }
924 }
925
926 paint_error("Pattern not found: %s", mi_query_getraw());
927}
928
929
930void
931kba_visual(KbaArgs a UNUSED__attribute__((__unused__)))
932{
933 if (ui.active == ui.library) {
934 paint_message("No visual mode in library window. Sorry.");
935 return;
936 }
937
938 if (visual_mode_start == -1)
939 visual_mode_start = ui.active->voffset + ui.active->crow;
940 else
941 visual_mode_start = -1;
942
943 paint_playlist();
944}
945
946/*
947 * TODO so much of the logic in cut() is similar with that of yank() above.
948 * combine the two, use an Args parameter to determine if the operation is
949 * a yank or a delete
950 */
951void
952kba_cut(KbaArgs a UNUSED__attribute__((__unused__)))
953{
954 playlist *p;
955 char *warning;
956 bool_Bool got_target;
957 int start, end;
958 int response;
959 int input;
960 int n;
961
962 if (visual_mode_start != -1) {
963 start = visual_mode_start;
964 end = ui.active->voffset + ui.active->crow;
965 visual_mode_start = -1;
966 if (start > end) {
967 int tmp = end;
968 end = start;
969 start = tmp;
970 }
971 end++;
972 } else {
973 /* determine range */
974 n = 1;
975 if (gnum_get() > 0) {
976 n = gnum_get();
977 gnum_clear();
978 }
979
980 /* get range */
981 got_target = false0;
982 start = 0;
983 end = 0;
984 while ((input = getch()wgetch(stdscr)) && !got_target) {
985 if (input == ERR(-1))
986 continue;
987
988 switch (input) {
989 case 'd': /* delete next n lines */
990 start = ui.active->voffset + ui.active->crow;
991 end = start + n;
992 got_target = true1;
993 break;
994
995 case 'G': /* delete to end of current playlist */
996 start = ui.active->voffset + ui.active->crow;
997 end = ui.active->nrows;
998 got_target = true1;
999 break;
1000
1001 default:
1002 ungetch(input);
1003 return;
1004 }
1005 }
1006 }
1007
1008 if (start >= ui.active->nrows) {
1009 paint_message("nothing to delete here!");
1010 return;
1011 }
1012
1013 /* handle deleting of a whole playlist */
1014 if (ui.active == ui.library) {
1015 /* get playlist to delete */
1016 n = ui.active->voffset + ui.active->crow;
1017 p = mdb.playlists[n];
1018
1019 /*
1020 * XXX i simply do NOT want to be able to delete multiple playlists
1021 * while drunk.
1022 */
1023 if (end != start + 1) {
1024 paint_error("cannot delete multiple playlists");
1025 return;
1026 }
1027 if (p == mdb.library || p == mdb.filter_results) {
1028 paint_error("cannot delete pseudo-playlists like LIBRARY or FILTER");
1029 return;
1030 }
1031
1032 if (asprintf(&warning, "Are you sure you want to delete '%s'?", p->name) == -1)
1033 err(1, "cut: asprintf failed");
1034
1035 /* make sure user wants this */
1036 if (user_get_yesno(warning, &response) != 0) {
1037 paint_message("delete of '%s' cancelled", p->name);
1038 free(warning);
1039 return;
1040 }
1041 if (response != 1) {
1042 paint_message("playlist '%s' not deleted", p->name);
1043 free(warning);
1044 return;
1045 }
1046
1047 /* delete playlist and redraw library window */
1048 if (viewing_playlist == p) {
1049 viewing_playlist = mdb.library;
1050 ui.playlist->nrows = mdb.library->nfiles;
1051 ui.playlist->crow = 0;
1052 ui.playlist->voffset = 0;
1053 ui.playlist->hoffset = 0;
1054 paint_playlist();
1055 }
1056 ui.library->nrows--;
1057 if (ui.library->voffset + ui.library->crow >= ui.library->nrows)
1058 ui.library->crow = ui.library->nrows - ui.library->voffset - 1;
1059
1060 medialib_playlist_remove(n);
1061 paint_library();
1062 free(warning);
1063 return;
1064 }
1065
1066 /* return to handling of deleting files in a playlist... */
1067
1068 /* can't delete from library */
1069 if (viewing_playlist == mdb.library) {
1070 paint_error("cannot delete from library");
1071 return;
1072 }
1073
1074 /* sanitize start and end */
1075 if (end > ui.active->nrows)
1076 end = ui.active->nrows;
1077
1078 /* clear existing yank buffer and add new stuff */
1079 ybuffer_clear();
1080 for (n = start; n < end; n++)
1081 ybuffer_add(viewing_playlist->files[n]);
1082
1083 /* delete files */
1084 playlist_files_remove(viewing_playlist, start, end - start, true1);
1085
1086 /* update ui appropriately */
1087 viewing_playlist->needs_saving = true1;
1088 ui.active->nrows = viewing_playlist->nfiles;
1089 if (ui.active->voffset + ui.active->crow >= ui.active->nrows)
1090 ui.active->crow = ui.active->nrows - ui.active->voffset - 1;
1091
1092
1093 /* redraw */
1094 paint_playlist();
1095 paint_library();
1096 paint_message("%d fewer files.", end - start);
1097}
1098
1099void
1100kba_yank(KbaArgs a UNUSED__attribute__((__unused__)))
1101{
1102 bool_Bool got_target;
1103 int start, end;
1104 int input;
1105 int n;
1106
1107 if (ui.active == ui.library) {
1108 paint_error("cannot yank in library window");
1109 return;
1110 }
1111
1112 if (viewing_playlist->nfiles == 0) {
1113 paint_error("nothing to yank!");
1114 return;
1115 }
1116
1117 if (visual_mode_start != -1) {
1118 start = visual_mode_start;
1119 end = ui.active->voffset + ui.active->crow;
1120 visual_mode_start = -1;
1121 if (start > end) {
1122 int tmp = end;
1123 end = start;
1124 start = tmp;
1125 }
1126 end++;
1127 } else {
1128 /* determine range */
1129 n = 1;
1130 if (gnum_get() > 0) {
1131 n = gnum_get();
1132 gnum_clear();
1133 }
1134 /* get next input from user */
1135 got_target = false0;
1136 start = 0;
1137 end = 0;
1138 while ((input = getch()wgetch(stdscr)) && !got_target) {
1139 if (input == ERR(-1))
1140 continue;
1141
1142 switch (input) {
1143 case 'y': /* yank next n lines */
1144 start = ui.active->voffset + ui.active->crow;
1145 end = start + n;
1146 got_target = true1;
1147 break;
1148
1149 case 'G': /* yank to end of current playlist */
1150 start = ui.active->voffset + ui.active->crow;
1151 end = ui.active->nrows;
1152 got_target = true1;
1153 break;
1154
1155 /*
1156 * TODO handle other directions ( j/k/H/L/M/^u/^d/ etc. )
1157 * here. this will be ... tricky.
1158 * might want to re-organize other stuff?
1159 */
1160
1161 default:
1162 ungetch(input);
1163 return;
1164 }
1165 }
1166 }
1167
1168 /* sanitize start and end */
1169 if (end > ui.active->nrows)
1170 end = ui.active->nrows;
1171
1172 /* clear existing yank buffer and add new stuff */
1173 ybuffer_clear();
1174 for (n = start; n < end; n++)
1175 ybuffer_add(viewing_playlist->files[n]);
1176
1177 paint_playlist();
1178 /* notify user # of rows yanked */
1179 paint_message("Yanked %d files.", end - start);
1180}
1181
1182void
1183kba_paste(KbaArgs a)
1184{
1185 playlist *p;
1186 int start = 0;
1187
1188 if (_yank_buffer.nfiles == 0) {
1189 paint_error("nothing to paste");
1190 return;
1191 }
1192
1193 /* determine the playlist we're pasting into */
1194 if (ui.active == ui.library) {
1195 int i = ui.active->voffset + ui.active->crow;
1196 p = mdb.playlists[i];
1197 } else {
1198 p = viewing_playlist;
1199 }
1200
1201 /* can't alter library */
1202 if (p == mdb.library) {
1203 paint_error("Cannot alter %s pseudo-playlist", mdb.library->name);
1204 return;
1205 }
1206
1207 if (ui.active == ui.library) {
1208 /* figure out where to paste into playlist */
1209 switch (a.placement) {
1210 case BEFORE:
1211 start = 0;
1212 break;
1213 case AFTER:
1214 start = p->nfiles;
1215 break;
1216 default:
1217 errx(1, "paste: invalid placement [if]");
1218 }
1219
1220 } else {
1221 /* determine index in playlist to insert new files after */
1222 switch (a.placement) {
1223 case BEFORE:
1224 start = ui.active->voffset + ui.active->crow;
1225 break;
1226 case AFTER:
1227 start = ui.active->voffset + ui.active->crow + 1;
1228 if (start > p->nfiles) start = p->nfiles;
1229 break;
1230 default:
1231 errx(1, "paste: invalid placement [else]");
1232 }
1233 }
1234
1235 /* add files */
1236 playlist_files_add(p, _yank_buffer.files, start, _yank_buffer.nfiles, true1);
1237
1238 if (p == viewing_playlist)
1239 ui.playlist->nrows = p->nfiles;
1240
1241 p->needs_saving = true1;
1242
1243 /* redraw */
1244 paint_library();
1245 paint_playlist();
1246 if (ui.active == ui.library)
1247 paint_message("Pasted %d files to '%s'", _yank_buffer.nfiles, p->name);
1248 else
1249 paint_message("Pasted %d files.", _yank_buffer.nfiles);
1250}
1251
1252
1253void
1254kba_undo(KbaArgs a UNUSED__attribute__((__unused__)))
1255{
1256 if (ui.active == ui.library) {
1257 paint_message("Cannot undo in library window.");
1258 return;
1259 }
1260
1261 if (playlist_undo(viewing_playlist) != 0)
1262 paint_message("Nothing to undo.");
1263 else
1264 paint_message("Undo successfull.");
1265
1266 /* TODO more informative message like in vim */
1267
1268 ui.playlist->nrows = viewing_playlist->nfiles;
1269 if (ui.playlist->voffset + ui.playlist->crow >= ui.playlist->nrows)
1270 ui.playlist->crow = ui.playlist->nrows - ui.playlist->voffset - 1;
1271
1272 paint_playlist();
1273}
1274
1275void
1276kba_redo(KbaArgs a UNUSED__attribute__((__unused__)))
1277{
1278 if (ui.active == ui.library) {
1279 paint_message("Cannot redo in library window.");
1280 return;
1281 }
1282
1283 if (playlist_redo(viewing_playlist) != 0)
1284 paint_message("Nothing to redo.");
1285 else
1286 paint_message("Redo successfull.");
1287
1288 /* TODO */
1289
1290 ui.playlist->nrows = viewing_playlist->nfiles;
1291 if (ui.playlist->voffset + ui.playlist->crow >= ui.playlist->nrows)
1292 ui.playlist->crow = ui.playlist->nrows - ui.playlist->voffset - 1;
1293
1294 paint_playlist();
1295}
1296
1297void
1298kba_command_mode(KbaArgs a UNUSED__attribute__((__unused__)))
1299{
1300 char *cmd;
1301
1302 /* get command from user */
1303 if (user_getstr(":", &cmd) != 0 || strlen(cmd) == 0) {
1304 paint_status_bar();
1305 return;
1306 }
1307
1308 /* check for '!' used for executing external commands */
1309 if (cmd[0] == '!') {
1310 execute_external_command(cmd + 1);
1311 free(cmd);
1312 return;
1313 }
1314
1315 cmd_execute(cmd);
1316 free(cmd);
1317 return;
1318}
1319
1320void
1321kba_shell(KbaArgs a UNUSED__attribute__((__unused__)))
1322{
1323 char *cmd;
1324
1325 /* get command from user */
1326 if (user_getstr("!", &cmd) != 0) {
1327 paint_status_bar();
1328 return;
1329 }
1330
1331 execute_external_command(cmd);
1332 free(cmd);
1333}
1334
1335void
1336kba_quit(KbaArgs a UNUSED__attribute__((__unused__)))
1337{
1338 VSIG_QUIT = 1;
1339}
1340
1341void
1342kba_redraw(KbaArgs a UNUSED__attribute__((__unused__)))
1343{
1344 ui_clear();
1345 paint_all();
1346}
1347
1348void
1349kba_switch_windows(KbaArgs a UNUSED__attribute__((__unused__)))
1350{
1351 if (ui.active == ui.library) {
1352 ui.active = ui.playlist;
1353 if (ui.lhide)
1354 ui_hide_library();
1355 } else {
1356 ui.active = ui.library;
1357 if (ui.lhide)
1358 ui_unhide_library();
1359 }
1360
1361 paint_all();
1362 paint_status_bar();
1363}
1364
1365void
1366kba_show_file_info(KbaArgs a UNUSED__attribute__((__unused__)))
1367{
1368 if (ui.active == ui.library)
1369 return;
1370
1371 if (ui.active->crow >= ui.active->nrows) {
1372 paint_message("no file here");
1373 return;
1374 }
1375
1376 if (showing_file_info)
1377 paint_playlist();
1378 else {
1379 /* get file index and show */
1380 int idx = ui.active->voffset + ui.active->crow;
1381 paint_playlist_file_info(viewing_playlist->files[idx]);
1382 }
1383}
1384
1385void
1386kba_load_playlist(KbaArgs a UNUSED__attribute__((__unused__)))
1387{
1388 if (ui.active == ui.library) {
1389 /* load playlist & switch focus */
1390 int idx = ui.library->voffset + ui.library->crow;
1391 viewing_playlist = mdb.playlists[idx];
1392 ui.playlist->nrows = mdb.playlists[idx]->nfiles;
1393 ui.playlist->crow = 0;
1394 ui.playlist->voffset = 0;
1395 ui.playlist->hoffset = 0;
1396
1397 paint_playlist();
1398 kba_switch_windows(get_dummy_args());
1399 } else {
1400 /* play song */
1401 if (ui.active->crow >= ui.active->nrows) {
1402 paint_message("no file here");
1403 return;
1404 }
1405 player_set_queue(viewing_playlist, ui.active->voffset + ui.active->crow);
1406 playing_playlist = viewing_playlist;
1407 player_play();
1408 }
1409}
1410
1411void
1412kba_play(KbaArgs a UNUSED__attribute__((__unused__)))
1413{
1414 if (ui.active == ui.library) {
1415 /* load playlist & switch focus */
1416 int idx = ui.library->voffset + ui.library->crow;
1417 viewing_playlist = mdb.playlists[idx];
1418 ui.playlist->nrows = mdb.playlists[idx]->nfiles;
1419 ui.playlist->crow = 0;
1420 ui.playlist->voffset = 0;
1421 ui.playlist->hoffset = 0;
1422
1423 paint_playlist();
1424 kba_switch_windows(get_dummy_args());
1425 } else {
1426 /* play song */
1427 if (ui.active->crow >= ui.active->nrows) {
1428 paint_message("no file here");
1429 return;
1430 }
1431 player_set_queue(viewing_playlist, ui.active->voffset + ui.active->crow);
1432 playing_playlist = viewing_playlist;
1433 player_play();
1434 }
1435}
1436
1437void
1438kba_pause(KbaArgs a UNUSED__attribute__((__unused__)))
1439{
1440 player_pause();
1441}
1442
1443void
1444kba_stop(KbaArgs a UNUSED__attribute__((__unused__)))
1445{
1446 player_stop();
1447 playing_playlist = NULL((void *)0);
1448}
1449
1450void
1451kba_play_next(KbaArgs a UNUSED__attribute__((__unused__)))
1452{
1453 int n = 1;
1454
1455 /* is there a multiplier? */
1456 if (gnum_get() > 0) {
1457 n = gnum_get();
1458 gnum_clear();
1459 }
1460
1461 player_skip_song(n);
1462}
1463
1464void
1465kba_play_prev(KbaArgs a UNUSED__attribute__((__unused__)))
1466{
1467 int n = 1;
1468
1469 /* is there a multiplier? */
1470 if (gnum_get() > 0) {
1471 n = gnum_get();
1472 gnum_clear();
1473 }
1474
1475 player_skip_song(n * -1);
1476}
1477
1478void
1479kba_volume(KbaArgs a)
1480{
1481 float pcnt;
1482
1483 if (gnum_get() > 0)
1484 pcnt = gnum_retrieve();
1485 else
1486 pcnt = 1;
1487
1488 switch (a.direction) {
1489 case FORWARDS:
1490 break;
1491 case BACKWARDS:
1492 pcnt *= -1;
1493 break;
1494 default:
1495 errx(1, "kba_volume: invalid direction");
1496 }
1497
1498 player_volume_step(pcnt);
1499}
1500
1501void
1502kba_seek(KbaArgs a)
1503{
1504 int n, secs;
1505
1506 /* determine number of seconds to seek */
1507 secs = 0;
1508 switch (a.scale) {
1509 case SECONDS:
1510 secs = a.num;
1511 break;
1512 case MINUTES:
1513 secs = a.num * 60;
1514 break;
1515 default:
1516 errx(1, "seek_playback: invalid scale");
1517 }
1518
1519 /* adjust for direction */
1520 switch (a.direction) {
1521 case FORWARDS:
1522 /* no change */
1523 break;
1524 case BACKWARDS:
1525 secs *= -1;
1526 break;
1527 default:
1528 errx(1, "seek_playback: invalid direction");
1529 }
1530
1531 /* is there a multiplier? */
1532 n = 1;
1533 if (gnum_get() > 0) {
1534 n = gnum_get();
1535 gnum_clear();
1536 }
1537
1538 /* apply n & seek */
1539 player_seek(secs * n);
1540}
1541
1542void
1543kba_toggle(KbaArgs a)
1544{
1545 toggle_list *t;
1546 char *cmd;
1547 bool_Bool got_register;
1548 int n, input, registr;
1549
1550 /* is there a multiplier? */
1551 n = 1;
1552 if (gnum_get() > 0) {
1553 n = gnum_get();
1554 gnum_clear();
1555 }
1556
1557 /* get the register */
1558 got_register = false0;
1559 registr = -1;
1560 while ((input = getch()wgetch(stdscr)) && !got_register) {
1561 if (input == ERR(-1))
1562 continue;
1563
1564 if (('a' <= input && input <= 'z')
1565 || ('A' <= input && input <= 'Z')) {
1566 got_register = true1;
1567 registr = input;
1568 }
1569 }
1570
1571 /* get the command to execute */
1572 if ((t = toggle_get(registr)) == NULL((void *)0)) {
1573 paint_error("No toggle list in register %c (%i).", registr, registr);
1574 return;
1575 }
1576
1577 /* update index */
1578 n %= t->size;
1579 switch (a.direction) {
1580 case FORWARDS:
1581 t->index += n;
1582 t->index %= t->size;
1583 break;
1584 case BACKWARDS:
1585 if (n <= (int)t->index) {
1586 t->index -= n;
1587 t->index %= t->size;
1588 } else {
1589 t->index = t->size - (n - t->index);
1590 }
1591 break;
1592 default:
1593 errx(1, "%s: invalid direction", __FUNCTION__);
1594 }
1595
1596 /* execute */
1597 cmd = t->commands[t->index];
1598 cmd_execute(cmd);
1599}
1600
1601
1602/*****************************************************************************
1603 *
1604 * Routines for Working with 'gnum'
1605 *
1606 ****************************************************************************/
1607
1608int _global_input_num = 0;
1609
1610void gnum_clear()
1611{ _global_input_num = 0; }
1612
1613int gnum_get()
1614{ return _global_input_num; }
1615
1616void gnum_set(int x)
1617{ _global_input_num = x; }
1618
1619void gnum_add(int x)
1620{
1621 _global_input_num = _global_input_num * 10 + x;
1622}
1623
1624int
1625gnum_retrieve()
1626{
1627 int n = 1;
1628 if (gnum_get() > 0) {
1629 n = gnum_get();
1630 gnum_clear();
1631 }
1632 return n;
1633}
1634
1635
1636/*****************************************************************************
1637 *
1638 * Routines for Working with Search Direction
1639 *
1640 ****************************************************************************/
1641
1642Direction _global_search_dir = FORWARDS;
1643
1644Direction search_dir_get()
1645{ return _global_search_dir; }
1646
1647void search_dir_set(Direction dir)
1648{ _global_search_dir = dir; }
1649
1650
1651/*****************************************************************************
1652 *
1653 * Routines for Working with Copy/Cut/Paste Buffer
1654 *
1655 ****************************************************************************/
1656
1657yank_buffer _yank_buffer;
1658
1659void
1660ybuffer_init()
1661{
1662 _yank_buffer.files = calloc(YANK_BUFFER_CHUNK_SIZE100, sizeof(meta_info*));
1663 if (_yank_buffer.files == NULL((void *)0))
1664 err(1, "ybuffer_init: calloc(3) failed");
1665
1666 _yank_buffer.capacity = YANK_BUFFER_CHUNK_SIZE100;
1667 _yank_buffer.nfiles = 0;
1668}
1669
1670void
1671ybuffer_clear()
1672{
1673 _yank_buffer.nfiles = 0;
1674}
1675
1676void
1677ybuffer_free()
1678{
1679 free(_yank_buffer.files);
1680 _yank_buffer.capacity = 0;
1681 _yank_buffer.nfiles = 0;
1682}
1683
1684void
1685ybuffer_add(meta_info *f)
1686{
1687 meta_info **new_buff;
1688
1689 /* do we need to realloc()? */
1690 if (_yank_buffer.nfiles == _yank_buffer.capacity) {
1
Taking true branch
1691 _yank_buffer.capacity += YANK_BUFFER_CHUNK_SIZE100;
1692 int new_capacity = _yank_buffer.capacity * sizeof(meta_info*);
1693 if ((new_buff = realloc(_yank_buffer.files, new_capacity)) == NULL((void *)0))
2
Value assigned to 'new_buff'
3
Assuming pointer value is null
4
Taking true branch
1694 err(1, "ybuffer_add: realloc(3) failed [%i]", new_capacity);
1695
1696 _yank_buffer.files = new_buff;
5
Null pointer value stored to '_yank_buffer.files'
1697 }
1698
1699 /* add the file */
1700 _yank_buffer.files[ _yank_buffer.nfiles++ ] = f;
6
Array access (via field 'files') results in a null pointer dereference
1701}
1702
1703
1704/*****************************************************************************
1705 *
1706 * Misc. Handy Routines
1707 *
1708 ****************************************************************************/
1709
1710void
1711redraw_active()
1712{
1713 if (ui.active == ui.library)
1714 paint_library();
1715 else
1716 paint_playlist();
1717}
1718
1719/*
1720 * Given string input from user (argv[0]) and a command name, check if the
1721 * input matches the command name, taking into acount '!' weirdness and
1722 * abbreviations.
1723 */
1724bool_Bool
1725match_command_name(const char *input, const char *cmd)
1726{
1727 bool_Bool found;
1728 char *icopy;
1729
1730 if (input == NULL((void *)0) || strlen(input) == 0)
1731 return false0;
1732
1733 if (strcmp(input, cmd) == 0)
1734 return true1;
1735
1736 /* check for '!' weirdness and abbreviations */
1737
1738 if ((icopy = strdup(input)) == NULL((void *)0))
1739 err(1, "match_command_name: strdup(3) failed");
1740
1741 /* remove '!' from input, if present */
1742 if (strstr(icopy, "!") != NULL((void *)0))
1743 *strstr(icopy, "!") = '\0';
1744
1745 /* now check against command & abbreviation */
1746 if (strstr(cmd, icopy) == cmd)
1747 found = true1;
1748 else
1749 found = false0;
1750
1751 free(icopy);
1752 return found;
1753}
1754
1755void
1756execute_external_command(const char *cmd)
1757{
1758 int input;
1759
1760 def_prog_mode();
1761 endwin();
1762
1763 system(cmd);
1764 printf("\nPress ENTER or type command to continue");
1765 fflush(stdout(&__sF[1]));
1766 raw();
1767 while(!VSIG_QUIT) {
1768 if ((input = getch()wgetch(stdscr)) && input != ERR(-1)) {
1769 if (input != '\r')
1770 ungetch(input);
1771 break;
1772 }
1773 }
1774 reset_prog_mode();
1775 paint_all();
1776}
1777