00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00031 #include "stdinc.h"
00032
00033 #include "audio_output.h"
00034 #include "config.h"
00035 #include "dbus.h"
00036 #include "gui.h"
00037 #include "gui_internal.h"
00038 #include "playq.h"
00039 #include "scrobbler.h"
00040 #include "vfs.h"
00041
00045 #define GUI_FOCUS_BROWSER 0
00046
00049 #define GUI_FOCUS_PLAYQ 1
00050
00053 #define GUI_FOCUS_COUNT 2
00054
00057 static int gui_input_curfocus = GUI_FOCUS_BROWSER;
00061 static struct vfsmatch *cursearch = NULL;
00065 static char *curseek = NULL;
00070 static int shutting_down = 0;
00074 #define CTRL(x) (((x) - 'A' + 1) & 0x7f)
00075
00080 static void
00081 gui_input_quit(void)
00082 {
00083 shutting_down = 1;
00084
00085 playq_shutdown();
00086 #ifdef BUILD_SCROBBLER
00087 scrobbler_shutdown();
00088 #endif
00089 audio_output_close();
00090 gui_draw_destroy();
00091 exit(0);
00092 }
00093
00098 static void
00099 gui_input_askquit(void)
00100 {
00101 int ret;
00102 char *msg;
00103
00104 if (!config_getopt_bool("gui.input.may_quit")) {
00105 gui_msgbar_warn(_("Use kill(1) to quit."));
00106 return;
00107 }
00108
00109 msg = g_strdup_printf(_("Quit %s?"), APP_NAME);
00110 ret = gui_input_askyesno(msg);
00111 g_free(msg);
00112
00113 if (ret == 0)
00114 gui_input_quit();
00115 }
00116
00121 static int
00122 gui_input_getch(void)
00123 {
00124 int ch;
00125
00126 for (;;) {
00127 ch = getch();
00128
00129 switch (ch) {
00130
00131 case KEY_RESIZE:
00132 case CTRL('L'):
00133 gui_draw_resize();
00134 continue;
00135
00136
00137 case CTRL('H'):
00138 case CTRL('?'):
00139 return (KEY_BACKSPACE);
00140
00141
00142 case KEY_SELECT:
00143 return (KEY_END);
00144
00145
00146 case ERR:
00147 switch (errno) {
00148 case 0:
00149 case EINTR:
00150
00151 continue;
00152 default:
00153
00154 gui_input_quit();
00155 }
00156 }
00157
00158 break;
00159 }
00160
00161 return (ch);
00162 }
00163
00167 static void
00168 gui_input_switchfocus(void)
00169 {
00170 gui_input_curfocus++;
00171 gui_input_curfocus %= GUI_FOCUS_COUNT;
00172
00173
00174 gui_playq_setfocus(gui_input_curfocus == GUI_FOCUS_PLAYQ);
00175 gui_browser_setfocus(gui_input_curfocus == GUI_FOCUS_BROWSER);
00176 }
00177
00181 static int
00182 gui_input_asksearch(void)
00183 {
00184 char *str;
00185 const char *old = NULL;
00186 struct vfsmatch *vm;
00187
00188
00189 if (cursearch != NULL)
00190 old = vfs_match_value(cursearch);
00191
00192
00193 str = gui_input_askstring(_("Search for"), old, NULL);
00194 if (str == NULL)
00195 return (-1);
00196
00197 vm = vfs_match_new(str);
00198 if (vm == NULL) {
00199 gui_msgbar_warn(_("Bad pattern."));
00200 g_free(str);
00201 return (-1);
00202 }
00203
00204
00205 if (cursearch != NULL)
00206 vfs_match_free(cursearch);
00207 cursearch = vm;
00208
00209 str = g_strdup_printf(_("Searching for \"%s\"..."),
00210 vfs_match_value(vm));
00211 gui_msgbar_warn(str);
00212 g_free(str);
00213
00214 return (0);
00215 }
00216
00221 static void
00222 gui_input_searchnext(void)
00223 {
00224 int nfocus = GUI_FOCUS_PLAYQ;
00225
00226 if (cursearch == NULL) {
00227
00228 if (gui_input_asksearch() != 0)
00229 return;
00230 }
00231
00232
00233
00234
00235
00236
00237
00238
00239 if (gui_input_curfocus == GUI_FOCUS_PLAYQ &&
00240 gui_playq_searchnext(cursearch) == 0) {
00241 goto found;
00242 } else if (gui_browser_searchnext(cursearch) == 0) {
00243 nfocus = GUI_FOCUS_BROWSER;
00244 goto found;
00245 } else if (gui_input_curfocus != GUI_FOCUS_PLAYQ &&
00246 gui_playq_searchnext(cursearch) == 0) {
00247 goto found;
00248 }
00249
00250
00251 gui_msgbar_warn(_("Not found."));
00252 return;
00253
00254 found:
00255 gui_input_curfocus = nfocus;
00256 gui_playq_setfocus(gui_input_curfocus == GUI_FOCUS_PLAYQ);
00257 gui_browser_setfocus(gui_input_curfocus == GUI_FOCUS_BROWSER);
00258 }
00259
00264 static void
00265 gui_input_search(void)
00266 {
00267
00268 if (gui_input_asksearch() != 0)
00269 return;
00270
00271
00272 gui_input_searchnext();
00273 }
00274
00279 static void
00280 gui_input_locate(void)
00281 {
00282
00283 if (gui_input_asksearch() != 0)
00284 return;
00285
00286
00287 if (gui_browser_locate(cursearch) != 0)
00288 gui_msgbar_warn(_("Not found."));
00289 else
00290 gui_msgbar_flush();
00291 }
00292
00297 static void
00298 gui_input_cursong_seek_backward(void)
00299 {
00300 playq_cursong_seek(-5, 1);
00301 }
00302
00307 static void
00308 gui_input_cursong_seek_forward(void)
00309 {
00310 playq_cursong_seek(5, 1);
00311 }
00312
00316 static int
00317 gui_input_cursong_seek_validator(const char *str, char c)
00318 {
00319 const char *s;
00320
00321 if (c == '+' || c == '-') {
00322
00323 if (str[0] != '\0')
00324 return (-1);
00325 } else if (c == ':') {
00326
00327 if (strpbrk(str, "0123456789") == NULL)
00328 return (-1);
00329
00330 s = strrchr(str, ':');
00331 if (s != NULL) {
00332
00333 if (strlen(s + 1) != 2)
00334 return (-1);
00335
00336 if (s - strchr(str, ':') == 3)
00337 return (-1);
00338 }
00339 } else if (g_ascii_isdigit(c)) {
00340 s = strrchr(str, ':');
00341 if (s != NULL) {
00342
00343 if (strlen(s + 1) == 2)
00344 return (-1);
00345
00346 if (s[1] == '\0' && c > '5')
00347 return (-1);
00348 }
00349 } else {
00350
00351 return (-1);
00352 }
00353
00354 return (0);
00355 }
00356
00360 static void
00361 gui_input_cursong_seek_jump(void)
00362 {
00363 char *str, *t;
00364 int total = 0, split = 0, digit = 0, value, relative = 0;
00365
00366 str = gui_input_askstring(_("Jump to position"), curseek,
00367 gui_input_cursong_seek_validator);
00368 if (str == NULL)
00369 return;
00370
00371 for (t = str; *t != '\0'; t++) {
00372 switch (*t) {
00373 case ':':
00374
00375
00376
00377
00378
00379 g_assert(split <= 1 && digit != 0);
00380 g_assert(split == 0 || digit == 2);
00381 split++;
00382 digit = 0;
00383 break;
00384 case '+':
00385
00386 g_assert(t == str);
00387 relative = 1;
00388 break;
00389 case '-':
00390
00391 g_assert(t == str);
00392 relative = -1;
00393 break;
00394 default:
00395
00396 value = g_ascii_digit_value(*t);
00397 g_assert(value != -1);
00398 g_assert(split == 0 || digit != 0 || value <= 5);
00399
00400 total *= (digit == 0) ? 6 : 10;
00401 total += value;
00402 digit++;
00403 }
00404 }
00405
00406
00407 if (split > 0 && digit != 2)
00408 goto bad;
00409
00410 if (relative != 0) {
00411 total *= relative;
00412 if (total == 0)
00413 goto bad;
00414 }
00415 playq_cursong_seek(total, relative);
00416
00417
00418 g_free(curseek);
00419 curseek = str;
00420 return;
00421
00422 bad: gui_msgbar_warn(_("Bad time format."));
00423 g_free(str);
00424 }
00425
00429 struct gui_binding {
00433 int focus;
00437 int input;
00441 void (*func)(void);
00442 };
00443
00447 static struct gui_binding kbdbindings[] = {
00448
00449 #ifdef BUILD_VOLUME
00450 { -1, '(', gui_playq_volume_down },
00451 { -1, ')', gui_playq_volume_up },
00452 #endif
00453 { -1, '<', gui_input_cursong_seek_backward },
00454 { -1, '>', gui_input_cursong_seek_forward },
00455 { -1, 'a', gui_browser_playq_add_after },
00456 { -1, 'A', gui_browser_playq_add_tail },
00457 { -1, 'b', playq_cursong_next },
00458 { -1, 'c', playq_cursong_pause },
00459 { -1, 'C', gui_browser_chdir },
00460 { -1, 'd', gui_playq_song_remove },
00461 { -1, 'D', gui_playq_song_remove_all },
00462 { -1, 'h', gui_browser_dir_parent },
00463 { -1, 'i', gui_browser_playq_add_before },
00464 { -1, 'I', gui_browser_playq_add_head },
00465 { -1, 'J', gui_input_cursong_seek_jump },
00466 { -1, 'l', gui_browser_dir_enter },
00467 { -1, 'L', gui_input_locate },
00468 { -1, 'q', gui_input_askquit },
00469 { -1, 'r', playq_repeat_toggle },
00470 { -1, 'R', gui_playq_song_randomize },
00471 { -1, 'v', playq_cursong_stop },
00472 { -1, 'w', gui_browser_write_playlist },
00473 { -1, 'x', gui_playq_song_select },
00474 { -1, 'z', playq_cursong_prev },
00475 { -1, '[', gui_playq_song_move_up },
00476 { -1, ']', gui_playq_song_move_down },
00477 { -1, '{', gui_playq_song_move_head },
00478 { -1, '}', gui_playq_song_move_tail },
00479 { -1, '~', gui_browser_gotohome },
00480 { -1, '\t', gui_input_switchfocus },
00481 { -1, CTRL('W'), gui_input_switchfocus },
00482 { -1, '/', gui_input_search },
00483 { -1, 'n', gui_input_searchnext },
00484 { -1, KEY_LEFT, gui_browser_dir_parent },
00485 { -1, KEY_RIGHT, gui_browser_dir_enter },
00486
00487
00488 { GUI_FOCUS_BROWSER, ' ', gui_browser_cursor_pagedown },
00489 { GUI_FOCUS_BROWSER, 'F', gui_browser_gotofolder },
00490 { GUI_FOCUS_BROWSER, 'f', gui_browser_fullpath },
00491 { GUI_FOCUS_BROWSER, 'G', gui_browser_cursor_tail },
00492 { GUI_FOCUS_BROWSER, 'g', gui_browser_cursor_head },
00493 { GUI_FOCUS_BROWSER, 'j', gui_browser_cursor_down },
00494 { GUI_FOCUS_BROWSER, 'k', gui_browser_cursor_up },
00495 { GUI_FOCUS_BROWSER, CTRL('B'), gui_browser_cursor_pageup },
00496 { GUI_FOCUS_BROWSER, CTRL('F'), gui_browser_cursor_pagedown },
00497 { GUI_FOCUS_BROWSER, KEY_DOWN, gui_browser_cursor_down },
00498 { GUI_FOCUS_BROWSER, KEY_END, gui_browser_cursor_tail },
00499 { GUI_FOCUS_BROWSER, KEY_HOME, gui_browser_cursor_head },
00500 { GUI_FOCUS_BROWSER, KEY_NPAGE, gui_browser_cursor_pagedown },
00501 { GUI_FOCUS_BROWSER, KEY_PPAGE, gui_browser_cursor_pageup },
00502 { GUI_FOCUS_BROWSER, KEY_UP, gui_browser_cursor_up },
00503
00504
00505 { GUI_FOCUS_PLAYQ, ' ', gui_playq_cursor_pagedown },
00506 { GUI_FOCUS_PLAYQ, 'F', gui_playq_gotofolder },
00507 { GUI_FOCUS_PLAYQ, 'f', gui_playq_fullpath },
00508 { GUI_FOCUS_PLAYQ, 'G', gui_playq_cursor_tail },
00509 { GUI_FOCUS_PLAYQ, 'g', gui_playq_cursor_head },
00510 { GUI_FOCUS_PLAYQ, 'j', gui_playq_cursor_down },
00511 { GUI_FOCUS_PLAYQ, 'k', gui_playq_cursor_up },
00512 { GUI_FOCUS_PLAYQ, CTRL('B'), gui_playq_cursor_pageup },
00513 { GUI_FOCUS_PLAYQ, CTRL('F'), gui_playq_cursor_pagedown },
00514 { GUI_FOCUS_PLAYQ, KEY_DOWN, gui_playq_cursor_down },
00515 { GUI_FOCUS_PLAYQ, KEY_END, gui_playq_cursor_tail },
00516 { GUI_FOCUS_PLAYQ, KEY_HOME, gui_playq_cursor_head },
00517 { GUI_FOCUS_PLAYQ, KEY_NPAGE, gui_playq_cursor_pagedown },
00518 { GUI_FOCUS_PLAYQ, KEY_PPAGE, gui_playq_cursor_pageup },
00519 { GUI_FOCUS_PLAYQ, KEY_UP, gui_playq_cursor_up },
00520 };
00524 #define NUM_BINDINGS (sizeof kbdbindings / sizeof(struct gui_binding))
00525
00526 void
00527 gui_input_sigmask(void)
00528 {
00529 #ifdef G_THREADS_IMPL_POSIX
00530 sigset_t sset;
00531
00532 sigemptyset(&sset);
00533 sigaddset(&sset, SIGHUP);
00534 sigaddset(&sset, SIGINT);
00535 sigaddset(&sset, SIGPIPE);
00536 sigaddset(&sset, SIGQUIT);
00537 sigaddset(&sset, SIGTERM);
00538 #ifdef SIGWINCH
00539 sigaddset(&sset, SIGWINCH);
00540 #endif
00541 pthread_sigmask(SIG_BLOCK, &sset, NULL);
00542 #endif
00543 }
00544
00545 #ifdef G_OS_UNIX
00546
00549 static void
00550 gui_input_sighandler(int signal)
00551 {
00552 if (shutting_down)
00553 return;
00554
00555 switch (signal) {
00556 case SIGHUP:
00557 case SIGINT:
00558 case SIGPIPE:
00559 case SIGQUIT:
00560 case SIGTERM:
00561 gui_input_quit();
00562 g_assert_not_reached();
00563 }
00564 }
00565 #endif
00566
00567 void
00568 gui_input_loop(void)
00569 {
00570 int ch;
00571 unsigned int i;
00572
00573 #ifdef G_OS_UNIX
00574 signal(SIGHUP, gui_input_sighandler);
00575 signal(SIGINT, gui_input_sighandler);
00576 signal(SIGPIPE, gui_input_sighandler);
00577 signal(SIGQUIT, gui_input_sighandler);
00578 signal(SIGTERM, gui_input_sighandler);
00579 #endif
00580
00581 for (;;) {
00582 ch = gui_input_getch();
00583 gui_msgbar_flush();
00584
00585 for (i = 0; i < NUM_BINDINGS; i++) {
00586
00587 if (kbdbindings[i].input != ch ||
00588 (kbdbindings[i].focus != -1 &&
00589 kbdbindings[i].focus != gui_input_curfocus))
00590 continue;
00591
00592 #ifdef BUILD_DBUS
00593 dbus_lock();
00594 kbdbindings[i].func();
00595 dbus_unlock();
00596 #else
00597 kbdbindings[i].func();
00598 #endif
00599 break;
00600 }
00601
00602 gui_draw_done();
00603 }
00604 }
00605
00606 int
00607 gui_input_askyesno(const char *question)
00608 {
00609 char *msg, input;
00610 const char *yes, *no;
00611 int ret;
00612
00613
00614 if (!config_getopt_bool("gui.input.confirm"))
00615 return (0);
00616
00617 yes = _("yes");
00618 no = _("no");
00619
00620
00621 msg = g_strdup_printf("%s ([%s]/%s): ", question, yes, no);
00622 gui_msgbar_ask(msg);
00623 g_free(msg);
00624
00625 for (;;) {
00626 input = gui_input_getch();
00627
00628 #ifdef BUILD_NLS
00629
00630 if (input == yes[0]) {
00631 ret = 0;
00632 goto done;
00633 } else if (input == no[0]) {
00634 ret = -1;
00635 goto done;
00636 }
00637 #endif
00638
00639
00640 switch (input) {
00641 case 'y':
00642 case 'Y':
00643 case '\r':
00644 ret = 0;
00645 goto done;
00646 case CTRL('['):
00647 case 'n':
00648 case 'N':
00649 case CTRL('C'):
00650 ret = -1;
00651 goto done;
00652 }
00653 }
00654 done:
00655 gui_msgbar_flush();
00656 return (ret);
00657 }
00658
00664 static int
00665 gui_input_trimword(GString *gs)
00666 {
00667 const char *end;
00668
00669
00670 end = (gs->str + gs->len) - 1;
00671
00672
00673 for (;;) {
00674 if (end < gs->str) return (0);
00675 if (!isspace(*end)) break;
00676 end--;
00677 }
00678
00679 if (isalnum(*end)) {
00680
00681 do {
00682 if (--end < gs->str) return (0);
00683 } while (isalnum(*end));
00684 } else {
00685
00686 do {
00687 if (--end < gs->str) return (0);
00688 } while (!isalnum(*end) && !isspace(*end));
00689 }
00690
00691 return (end - gs->str) + 1;
00692 }
00693
00694 char *
00695 gui_input_askstring(const char *question, const char *defstr,
00696 int (*validator)(const char *str, char c))
00697 {
00698 GString *msg;
00699 unsigned int origlen, newlen;
00700 int c, clearfirst = 0;
00701 char *ret = NULL;
00702 const char *vstr;
00703
00704 msg = g_string_new(question);
00705 g_string_append(msg, ": ");
00706 origlen = msg->len;
00707 if (defstr != NULL) {
00708 g_string_append(msg, defstr);
00709 clearfirst = 1;
00710 }
00711
00712 for(;;) {
00713 gui_msgbar_ask(msg->str);
00714
00715 switch (c = gui_input_getch()) {
00716 case '\r':
00717 goto done;
00718 case KEY_BACKSPACE:
00719 clearfirst = 0;
00720 if (msg->len > origlen) {
00721
00722 g_string_truncate(msg, msg->len - 1);
00723 }
00724 break;
00725 case CTRL('C'):
00726 case CTRL('['):
00727
00728 g_string_truncate(msg, origlen);
00729 goto done;
00730 case CTRL('U'):
00731 g_string_truncate(msg, origlen);
00732 break;
00733 case CTRL('W'):
00734 clearfirst = 0;
00735 newlen = gui_input_trimword(msg);
00736 g_string_truncate(msg, MAX(newlen, origlen));
00737 break;
00738 default:
00739
00740 if (g_ascii_iscntrl(c))
00741 break;
00742
00743 if (validator != NULL) {
00744 vstr = clearfirst ? "" : msg->str + origlen;
00745 if (validator(vstr, c) != 0)
00746 break;
00747 }
00748 if (clearfirst) {
00749 g_string_truncate(msg, origlen);
00750 clearfirst = 0;
00751 }
00752 g_string_append_c(msg, c);
00753 }
00754 }
00755
00756 done:
00757 gui_msgbar_flush();
00758
00759
00760 if (msg->len > origlen)
00761 ret = g_strdup(msg->str + origlen);
00762
00763 g_string_free(msg, TRUE);
00764 return ret;
00765 }