1 /*
2 * $Id$
3 *
4 * (c) 2004 by Kalle Wallin <kaw@linux.se>
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18 *
19 */
21 #include <ctype.h>
22 #include <stdlib.h>
23 #include <string.h>
24 #include <glib.h>
25 #include <ncurses.h>
27 #include "config.h"
28 #include "ncmpc.h"
29 #include "options.h"
30 #include "support.h"
31 #include "mpdclient.h"
32 #include "strfsong.h"
33 #include "command.h"
34 #include "screen.h"
35 #include "screen_utils.h"
36 #include "screen_browse.h"
37 #include "screen_play.h"
39 #define USE_OLD_LAYOUT
40 #undef USE_OLD_ADD
42 #define BUFSIZE 1024
44 #define HIGHLIGHT (0x01)
47 static list_window_t *lw = NULL;
48 static list_window_state_t *lw_state = NULL;
49 static mpdclient_filelist_t *filelist = NULL;
53 /* clear the highlight flag for all items in the filelist */
54 void
55 clear_highlights(mpdclient_filelist_t *filelist)
56 {
57 GList *list = g_list_first(filelist->list);
59 while( list )
60 {
61 filelist_entry_t *entry = list->data;
63 entry->flags &= ~HIGHLIGHT;
64 list = list->next;
65 }
66 }
68 /* change the highlight flag for a song */
69 void
70 set_highlight(mpdclient_filelist_t *filelist, mpd_Song *song, int highlight)
71 {
72 GList *list = g_list_first(filelist->list);
74 if( !song )
75 return;
77 while( list )
78 {
79 filelist_entry_t *entry = list->data;
80 mpd_InfoEntity *entity = entry->entity;
82 if( entity && entity->type==MPD_INFO_ENTITY_TYPE_SONG )
83 {
84 mpd_Song *song2 = entity->info.song;
86 if( strcmp(song->file, song2->file) == 0 )
87 {
88 if(highlight)
89 entry->flags |= HIGHLIGHT;
90 else
91 entry->flags &= ~HIGHLIGHT;
92 }
93 }
94 list = list->next;
95 }
96 }
98 /* sync highlight flags with playlist */
99 void
100 sync_highlights(mpdclient_t *c, mpdclient_filelist_t *filelist)
101 {
102 GList *list = g_list_first(filelist->list);
104 while(list)
105 {
106 filelist_entry_t *entry = list->data;
107 mpd_InfoEntity *entity = entry->entity;
109 if( entity && entity->type==MPD_INFO_ENTITY_TYPE_SONG )
110 {
111 mpd_Song *song = entity->info.song;
113 if( playlist_get_index_from_file(c, song->file) >= 0 )
114 entry->flags |= HIGHLIGHT;
115 else
116 entry->flags &= ~HIGHLIGHT;
117 }
118 list=list->next;
119 }
120 }
122 /* the db have changed -> update the filelist */
123 static void
124 file_changed_callback(mpdclient_t *c, int event, gpointer data)
125 {
126 D("screen_file.c> filelist_callback() [%d]\n", event);
127 filelist = mpdclient_filelist_update(c, filelist);
128 sync_highlights(c, filelist);
129 list_window_check_selected(lw, filelist->length);
130 }
132 /* the playlist have been updated -> fix highlights */
133 static void
134 playlist_changed_callback(mpdclient_t *c, int event, gpointer data)
135 {
136 D("screen_file.c> playlist_callback() [%d]\n", event);
137 switch(event)
138 {
139 case PLAYLIST_EVENT_CLEAR:
140 clear_highlights(filelist);
141 break;
142 case PLAYLIST_EVENT_ADD:
143 set_highlight(filelist, (mpd_Song *) data, 1);
144 break;
145 case PLAYLIST_EVENT_DELETE:
146 set_highlight(filelist, (mpd_Song *) data, 0);
147 break;
148 case PLAYLIST_EVENT_MOVE:
149 break;
150 default:
151 sync_highlights(c, filelist);
152 break;
153 }
154 }
156 /* list_window callback */
157 char *
158 browse_lw_callback(int index, int *highlight, void *data)
159 {
160 static char buf[BUFSIZE];
161 mpdclient_filelist_t *filelist = (mpdclient_filelist_t *) data;
162 filelist_entry_t *entry;
163 mpd_InfoEntity *entity;
165 *highlight = 0;
166 if( (entry=(filelist_entry_t *)g_list_nth_data(filelist->list,index))==NULL )
167 return NULL;
169 entity = entry->entity;
170 *highlight = (entry->flags & HIGHLIGHT);
172 if( entity == NULL )
173 {
174 return "[..]";
175 }
176 if( entity->type==MPD_INFO_ENTITY_TYPE_DIRECTORY )
177 {
178 mpd_Directory *dir = entity->info.directory;
179 char *dirname = utf8_to_locale(basename(dir->path));
181 g_snprintf(buf, BUFSIZE, "[%s]", dirname);
182 g_free(dirname);
183 return buf;
184 }
185 else if( entity->type==MPD_INFO_ENTITY_TYPE_SONG )
186 {
187 mpd_Song *song = entity->info.song;
189 strfsong(buf, BUFSIZE, LIST_FORMAT, song);
190 return buf;
191 }
192 else if( entity->type==MPD_INFO_ENTITY_TYPE_PLAYLISTFILE )
193 {
194 mpd_PlaylistFile *plf = entity->info.playlistFile;
195 char *filename = utf8_to_locale(basename(plf->path));
197 #ifdef USE_OLD_LAYOUT
198 g_snprintf(buf, BUFSIZE, "*%s*", filename);
199 #else
200 g_snprintf(buf, BUFSIZE, "<Playlist> %s", filename);
201 #endif
202 g_free(filename);
203 return buf;
204 }
205 return "Error: Unknown entry!";
206 }
208 /* chdir */
209 static int
210 change_directory(screen_t *screen, mpdclient_t *c, filelist_entry_t *entry, char *new_path)
211 {
212 mpd_InfoEntity *entity = NULL;
213 gchar *path = NULL;
215 if( entry!=NULL )
216 entity = entry->entity;
217 else if( new_path==NULL )
218 return -1;
220 if( entity==NULL )
221 {
222 if( entry || 0==strcmp(new_path, "..") )
223 {
224 /* return to parent */
225 char *parent = g_path_get_dirname(filelist->path);
226 if( strcmp(parent, ".") == 0 )
227 {
228 parent[0] = '\0';
229 }
230 path = g_strdup(parent);
231 list_window_reset(lw);
232 /* restore previous list window state */
233 list_window_pop_state(lw_state,lw);
234 }
235 else
236 {
237 /* entry==NULL, then new_path ("" is root) */
238 path = g_strdup(new_path);
239 list_window_reset(lw);
240 /* restore first list window state (pop while returning true) */
241 while(list_window_pop_state(lw_state,lw));
242 }
243 }
244 else
245 if( entity->type==MPD_INFO_ENTITY_TYPE_DIRECTORY)
246 {
247 /* enter sub */
248 mpd_Directory *dir = entity->info.directory;
249 path = utf8_to_locale(dir->path);
250 /* save current list window state */
251 list_window_push_state(lw_state,lw);
252 }
253 else
254 return -1;
256 filelist = mpdclient_filelist_free(filelist);
257 filelist = mpdclient_filelist_get(c, path);
258 sync_highlights(c, filelist);
259 list_window_check_selected(lw, filelist->length);
260 g_free(path);
261 return 0;
262 }
264 static int
265 load_playlist(screen_t *screen, mpdclient_t *c, filelist_entry_t *entry)
266 {
267 mpd_InfoEntity *entity = entry->entity;
268 mpd_PlaylistFile *plf = entity->info.playlistFile;
269 char *filename = utf8_to_locale(plf->path);
271 if( mpdclient_cmd_load_playlist_utf8(c, plf->path) == 0 )
272 screen_status_printf(_("Loading playlist %s..."), basename(filename));
273 g_free(filename);
274 return 0;
275 }
277 static int
278 handle_save(screen_t *screen, mpdclient_t *c)
279 {
280 filelist_entry_t *entry;
281 char *defaultname = NULL;
284 entry=( filelist_entry_t *) g_list_nth_data(filelist->list,lw->selected);
285 if( entry && entry->entity )
286 {
287 mpd_InfoEntity *entity = entry->entity;
288 if( entity->type==MPD_INFO_ENTITY_TYPE_PLAYLISTFILE )
289 {
290 mpd_PlaylistFile *plf = entity->info.playlistFile;
291 defaultname = plf->path;
292 }
293 }
294 return playlist_save(screen, c, NULL, defaultname);
295 }
297 static int
298 handle_delete(screen_t *screen, mpdclient_t *c)
299 {
300 filelist_entry_t *entry;
301 mpd_InfoEntity *entity;
302 mpd_PlaylistFile *plf;
303 char *str, *buf;
304 int key;
306 entry=( filelist_entry_t *) g_list_nth_data(filelist->list,lw->selected);
307 if( entry==NULL || entry->entity==NULL )
308 return -1;
310 entity = entry->entity;
312 if( entity->type!=MPD_INFO_ENTITY_TYPE_PLAYLISTFILE )
313 {
314 screen_status_printf(_("You can only delete playlists!"));
315 screen_bell();
316 return -1;
317 }
319 plf = entity->info.playlistFile;
320 str = utf8_to_locale(basename(plf->path));
321 buf = g_strdup_printf(_("Delete playlist %s [%s/%s] ? "), str, YES, NO);
322 g_free(str);
323 key = tolower(screen_getch(screen->status_window.w, buf));
324 g_free(buf);
325 if( key==KEY_RESIZE )
326 screen_resize();
327 if( key != YES[0] )
328 {
329 screen_status_printf(_("Aborted!"));
330 return 0;
331 }
333 if( mpdclient_cmd_delete_playlist_utf8(c, plf->path) )
334 {
335 return -1;
336 }
337 screen_status_printf(_("Playlist deleted!"));
338 return 0;
339 }
341 static int
342 enqueue_and_play(screen_t *screen, mpdclient_t *c, filelist_entry_t *entry)
343 {
344 int index;
345 mpd_InfoEntity *entity = entry->entity;
346 mpd_Song *song = entity->info.song;
348 if(!( entry->flags & HIGHLIGHT ))
349 {
350 if( mpdclient_cmd_add(c, song) == 0 )
351 {
352 char buf[BUFSIZE];
354 entry->flags |= HIGHLIGHT;
355 strfsong(buf, BUFSIZE, LIST_FORMAT, song);
356 screen_status_printf(_("Adding \'%s\' to playlist\n"), buf);
357 mpdclient_update(c); /* get song id */
358 }
359 else
360 return -1;
361 }
363 index = playlist_get_index_from_file(c, song->file);
364 mpdclient_cmd_play(c, index);
365 return 0;
366 }
368 int
369 browse_handle_enter(screen_t *screen,
370 mpdclient_t *c,
371 list_window_t *lw,
372 mpdclient_filelist_t *filelist)
373 {
374 filelist_entry_t *entry;
375 mpd_InfoEntity *entity;
377 if ( filelist==NULL )
378 return -1;
379 entry = ( filelist_entry_t *) g_list_nth_data(filelist->list, lw->selected);
380 if( entry==NULL )
381 return -1;
383 entity = entry->entity;
384 if( entity==NULL || entity->type==MPD_INFO_ENTITY_TYPE_DIRECTORY )
385 return change_directory(screen, c, entry, NULL);
386 else if( entity->type==MPD_INFO_ENTITY_TYPE_PLAYLISTFILE )
387 return load_playlist(screen, c, entry);
388 else if( entity->type==MPD_INFO_ENTITY_TYPE_SONG )
389 return enqueue_and_play(screen, c, entry);
390 return -1;
391 }
394 #ifdef USE_OLD_ADD
395 /* NOTE - The add_directory functions should move to mpdclient.c */
396 extern gint mpdclient_finish_command(mpdclient_t *c);
398 static int
399 add_directory(mpdclient_t *c, char *dir)
400 {
401 mpd_InfoEntity *entity;
402 GList *subdir_list = NULL;
403 GList *list = NULL;
404 char *dirname;
406 dirname = utf8_to_locale(dir);
407 screen_status_printf(_("Adding directory %s...\n"), dirname);
408 doupdate();
409 g_free(dirname);
410 dirname = NULL;
412 mpd_sendLsInfoCommand(c->connection, dir);
413 mpd_sendCommandListBegin(c->connection);
414 while( (entity=mpd_getNextInfoEntity(c->connection)) )
415 {
416 if( entity->type==MPD_INFO_ENTITY_TYPE_SONG )
417 {
418 mpd_Song *song = entity->info.song;
419 mpd_sendAddCommand(c->connection, song->file);
420 mpd_freeInfoEntity(entity);
421 }
422 else if( entity->type==MPD_INFO_ENTITY_TYPE_DIRECTORY )
423 {
424 subdir_list = g_list_append(subdir_list, (gpointer) entity);
425 }
426 else
427 mpd_freeInfoEntity(entity);
428 }
429 mpd_sendCommandListEnd(c->connection);
430 mpdclient_finish_command(c);
431 c->need_update = TRUE;
433 list = g_list_first(subdir_list);
434 while( list!=NULL )
435 {
436 mpd_Directory *dir;
438 entity = list->data;
439 dir = entity->info.directory;
440 add_directory(c, dir->path);
441 mpd_freeInfoEntity(entity);
442 list->data=NULL;
443 list=list->next;
444 }
445 g_list_free(subdir_list);
446 return 0;
447 }
448 #endif
450 int
451 browse_handle_select(screen_t *screen,
452 mpdclient_t *c,
453 list_window_t *lw,
454 mpdclient_filelist_t *filelist)
455 {
456 filelist_entry_t *entry;
458 if ( filelist==NULL )
459 return -1;
460 entry=( filelist_entry_t *) g_list_nth_data(filelist->list, lw->selected);
461 if( entry==NULL || entry->entity==NULL)
462 return -1;
464 if( entry->entity->type==MPD_INFO_ENTITY_TYPE_PLAYLISTFILE )
465 return load_playlist(screen, c, entry);
467 if( entry->entity->type==MPD_INFO_ENTITY_TYPE_DIRECTORY )
468 {
469 mpd_Directory *dir = entry->entity->info.directory;
470 #ifdef USE_OLD_ADD
471 add_directory(c, tmp);
472 #else
473 if( mpdclient_cmd_add_path_utf8(c, dir->path) == 0 )
474 {
475 char *tmp = utf8_to_locale(dir->path);
477 screen_status_printf(_("Adding \'%s\' to playlist\n"), tmp);
478 g_free(tmp);
479 }
480 #endif
481 return 0;
482 }
484 if( entry->entity->type!=MPD_INFO_ENTITY_TYPE_SONG )
485 return -1;
487 if( entry->flags & HIGHLIGHT )
488 entry->flags &= ~HIGHLIGHT;
489 else
490 entry->flags |= HIGHLIGHT;
492 if( entry->flags & HIGHLIGHT )
493 {
494 if( entry->entity->type==MPD_INFO_ENTITY_TYPE_SONG )
495 {
496 mpd_Song *song = entry->entity->info.song;
498 if( mpdclient_cmd_add(c, song) == 0 )
499 {
500 char buf[BUFSIZE];
502 strfsong(buf, BUFSIZE, LIST_FORMAT, song);
503 screen_status_printf(_("Adding \'%s\' to playlist\n"), buf);
504 }
505 }
506 }
507 else
508 {
509 /* remove song from playlist */
510 if( entry->entity->type==MPD_INFO_ENTITY_TYPE_SONG )
511 {
512 mpd_Song *song = entry->entity->info.song;
514 if( song )
515 {
516 int index = playlist_get_index_from_file(c, song->file);
518 while( (index=playlist_get_index_from_file(c, song->file))>=0 )
519 mpdclient_cmd_delete(c, index);
520 }
521 }
522 }
523 return 0;
524 }
526 int
527 browse_handle_select_all (screen_t *screen,
528 mpdclient_t *c,
529 list_window_t *lw,
530 mpdclient_filelist_t *filelist)
531 {
532 filelist_entry_t *entry;
533 GList *temp = filelist->list;
535 if ( filelist==NULL )
536 return -1;
537 for (filelist->list = g_list_first(filelist->list);
538 filelist->list;
539 filelist->list = g_list_next(filelist->list))
540 {
541 entry=( filelist_entry_t *) filelist->list->data;
542 if( entry==NULL || entry->entity==NULL)
543 return -1;
545 if( entry->entity->type==MPD_INFO_ENTITY_TYPE_PLAYLISTFILE )
546 load_playlist(screen, c, entry);
548 if( entry->entity->type==MPD_INFO_ENTITY_TYPE_DIRECTORY )
549 {
550 mpd_Directory *dir = entry->entity->info.directory;
551 #ifdef USE_OLD_ADD
552 add_directory(c, tmp);
553 #else
554 if( mpdclient_cmd_add_path_utf8(c, dir->path) == 0 )
555 {
556 char *tmp = utf8_to_locale(dir->path);
558 screen_status_printf(_("Adding \'%s\' to playlist\n"), tmp);
559 g_free(tmp);
560 }
561 #endif
562 }
564 if( entry->entity->type!=MPD_INFO_ENTITY_TYPE_SONG )
565 continue;
567 if( entry->flags & HIGHLIGHT )
568 entry->flags &= ~HIGHLIGHT;
569 else
570 entry->flags |= HIGHLIGHT;
572 if( entry->flags & HIGHLIGHT )
573 {
574 if( entry->entity->type==MPD_INFO_ENTITY_TYPE_SONG )
575 {
576 mpd_Song *song = entry->entity->info.song;
578 if( mpdclient_cmd_add(c, song) == 0 )
579 {
580 char buf[BUFSIZE];
582 strfsong(buf, BUFSIZE, LIST_FORMAT, song);
583 screen_status_printf(_("Adding \'%s\' to playlist\n"), buf);
584 }
585 }
586 }
587 /*else
588 {
589 //remove song from playlist
590 if( entry->entity->type==MPD_INFO_ENTITY_TYPE_SONG )
591 {
592 mpd_Song *song = entry->entity->info.song;
594 if( song )
595 {
596 int index = playlist_get_index_from_file(c, song->file);
598 while( (index=playlist_get_index_from_file(c, song->file))>=0 )
599 mpdclient_cmd_delete(c, index);
600 }
601 }
602 }
603 return 0;*/
604 }
605 filelist->list = temp;
606 return 0;
607 }
609 static void
610 browse_init(WINDOW *w, int cols, int rows)
611 {
612 lw = list_window_init(w, cols, rows);
613 lw_state = list_window_init_state();
614 }
616 static void
617 browse_resize(int cols, int rows)
618 {
619 lw->cols = cols;
620 lw->rows = rows;
621 }
623 static void
624 browse_exit(void)
625 {
626 if( filelist )
627 filelist = mpdclient_filelist_free(filelist);
628 lw = list_window_free(lw);
629 lw_state = list_window_free_state(lw_state);
630 }
632 static void
633 browse_open(screen_t *screen, mpdclient_t *c)
634 {
635 if( filelist == NULL )
636 {
637 filelist = mpdclient_filelist_get(c, "");
638 mpdclient_install_playlist_callback(c, playlist_changed_callback);
639 mpdclient_install_browse_callback(c, file_changed_callback);
640 }
641 }
643 static void
644 browse_close(void)
645 {
646 }
648 static char *
649 browse_title(char *str, size_t size)
650 {
651 char *pathcopy;
652 char *parentdir;
653 pathcopy = strdup(filelist->path);
654 parentdir = dirname(pathcopy);
655 parentdir = basename(parentdir);
656 if( parentdir[0] == '.' && strlen(parentdir) == 1 )
657 {
658 parentdir = NULL;
659 }
660 g_snprintf(str, size, _("Browse: %s%s%s"),
661 parentdir ? parentdir : "",
662 parentdir ? "/" : "",
663 basename(filelist->path));
664 free(pathcopy);
665 return str;
666 }
668 static void
669 browse_paint(screen_t *screen, mpdclient_t *c)
670 {
671 lw->clear = 1;
673 list_window_paint(lw, browse_lw_callback, (void *) filelist);
674 wnoutrefresh(lw->w);
675 }
677 static void
678 browse_update(screen_t *screen, mpdclient_t *c)
679 {
680 if( filelist->updated )
681 {
682 browse_paint(screen, c);
683 filelist->updated = FALSE;
684 return;
685 }
686 list_window_paint(lw, browse_lw_callback, (void *) filelist);
687 wnoutrefresh(lw->w);
688 }
691 #ifdef HAVE_GETMOUSE
692 int
693 browse_handle_mouse_event(screen_t *screen,
694 mpdclient_t *c,
695 list_window_t *lw,
696 mpdclient_filelist_t *filelist)
697 {
698 int row;
699 int prev_selected = lw->selected;
700 unsigned long bstate;
701 int length;
703 if ( filelist )
704 length = filelist->length;
705 else
706 length = 0;
708 if( screen_get_mouse_event(c, lw, length, &bstate, &row) )
709 return 1;
711 lw->selected = lw->start+row;
712 list_window_check_selected(lw, length);
714 if( bstate & BUTTON1_CLICKED )
715 {
716 if( prev_selected == lw->selected )
717 browse_handle_enter(screen, c, lw, filelist);
718 }
719 else if( bstate & BUTTON3_CLICKED )
720 {
721 if( prev_selected == lw->selected )
722 browse_handle_select(screen, c, lw, filelist);
723 }
725 return 1;
726 }
727 #endif
729 static int
730 browse_cmd(screen_t *screen, mpdclient_t *c, command_t cmd)
731 {
732 switch(cmd)
733 {
734 case CMD_PLAY:
735 browse_handle_enter(screen, c, lw, filelist);
736 return 1;
737 case CMD_GO_ROOT_DIRECTORY:
738 return change_directory(screen, c, NULL, "");
739 break;
740 case CMD_GO_PARENT_DIRECTORY:
741 return change_directory(screen, c, NULL, "..");
742 break;
743 case CMD_SELECT:
744 if( browse_handle_select(screen, c, lw, filelist) == 0 )
745 {
746 /* continue and select next item... */
747 cmd = CMD_LIST_NEXT;
748 }
749 break;
750 case CMD_DELETE:
751 handle_delete(screen, c);
752 break;
753 case CMD_SAVE_PLAYLIST:
754 handle_save(screen, c);
755 break;
756 case CMD_SCREEN_UPDATE:
757 screen->painted = 0;
758 lw->clear = 1;
759 lw->repaint = 1;
760 filelist = mpdclient_filelist_update(c, filelist);
761 list_window_check_selected(lw, filelist->length);
762 screen_status_printf(_("Screen updated!"));
763 return 1;
764 case CMD_DB_UPDATE:
765 if( !c->status->updatingDb )
766 {
767 if( mpdclient_cmd_db_update_utf8(c,filelist->path)==0 )
768 {
769 if(strcmp(filelist->path,"")) {
770 screen_status_printf(_("Database update of %s started!"),
771 filelist->path);
772 } else {
773 screen_status_printf(_("Database update started!"));
774 }
775 /* set updatingDb to make shure the browse callback gets called
776 * even if the updated has finished before status is updated */
777 c->status->updatingDb = 1;
778 }
779 }
780 else
781 screen_status_printf(_("Database update running..."));
782 return 1;
783 case CMD_LIST_FIND:
784 case CMD_LIST_RFIND:
785 case CMD_LIST_FIND_NEXT:
786 case CMD_LIST_RFIND_NEXT:
787 return screen_find(screen, c,
788 lw, filelist->length,
789 cmd, browse_lw_callback, (void *) filelist);
790 case CMD_MOUSE_EVENT:
791 return browse_handle_mouse_event(screen,c,lw,filelist);
792 default:
793 break;
794 }
795 return list_window_cmd(lw, filelist->length, cmd);
796 }
799 list_window_t *
800 get_filelist_window()
801 {
802 return lw;
803 }
808 screen_functions_t *
809 get_screen_browse(void)
810 {
811 static screen_functions_t functions;
813 memset(&functions, 0, sizeof(screen_functions_t));
814 functions.init = browse_init;
815 functions.exit = browse_exit;
816 functions.open = browse_open;
817 functions.close = browse_close;
818 functions.resize = browse_resize;
819 functions.paint = browse_paint;
820 functions.update = browse_update;
821 functions.cmd = browse_cmd;
822 functions.get_lw = get_filelist_window;
823 functions.get_title = browse_title;
825 return &functions;
826 }