1 /* vifm
2 * Copyright (C) 2001 Ken Steen.
3 * Copyright (C) 2011 xaizek.
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
18 */
19
20 #include "status.h"
21
22 #ifdef HAVE_LIBGTK
23 #include <gio/gio.h>
24 #include <gtk/gtk.h>
25 #undef MAX
26 #undef MIN
27 #endif
28
29 #include <sys/types.h> /* ino_t */
30
31 #include <assert.h> /* assert() */
32 #include <limits.h> /* INT_MIN */
33 #include <stddef.h> /* NULL */
34 #include <string.h>
35 #include <time.h> /* time_t time() */
36
37 #include "cfg/config.h"
38 #include "compat/fs_limits.h"
39 #include "compat/pthread.h"
40 #include "compat/reallocarray.h"
41 #include "modes/modes.h"
42 #include "ui/colors.h"
43 #include "ui/ui.h"
44 #include "utils/env.h"
45 #include "utils/fsdata.h"
46 #include "utils/log.h"
47 #include "utils/macros.h"
48 #include "utils/path.h"
49 #include "utils/str.h"
50 #include "utils/utils.h"
51 #include "cmd_completion.h"
52 #include "cmd_core.h"
53 #include "filelist.h"
54 #include "filetype.h"
55 #include "opt_handlers.h"
56
57 /* Environment variables by which application hosted by terminal multiplexer can
58 * identify the host. */
59 #define SCREEN_ENVVAR "STY"
60 #define TMUX_ENVVAR "TMUX"
61
62 /* dcache entry. */
63 typedef struct
64 {
65 uint64_t value; /* Stored value. */
66 #ifndef _WIN32
67 ino_t inode; /* Inode number. */
68 #endif
69 time_t timestamp; /* When the value was set. */
70 }
71 dcache_data_t;
72
73 static void load_def_values(status_t *stats, config_t *config);
74 static void determine_fuse_umount_cmd(status_t *stats);
75 static void set_gtk_available(status_t *stats);
76 static int reset_dircache(void);
77 static void set_last_cmdline_command(const char cmd[]);
78 static void dcache_get(const char path[], time_t mtime, uint64_t inode,
79 dcache_result_t *size, dcache_result_t *nitems);
80 static void size_updater(void *data, void *arg);
81
82 status_t curr_stats;
83
84 /* Whether redraw operation is scheduled. */
85 static int pending_redraw;
86 /* Whether reload operation is scheduled. Redrawing is then assumed to be
87 * scheduled too, as it's part of reloading. */
88 static int pending_refresh;
89 static int inside_screen;
90 static int inside_tmux;
91
92 /* Thread-safety guard for dcache_size variable. */
93 static pthread_mutex_t dcache_size_mutex = PTHREAD_MUTEX_INITIALIZER;
94 /* Thread-safety guard for dcache_nitems variable. */
95 static pthread_mutex_t dcache_nitems_mutex = PTHREAD_MUTEX_INITIALIZER;
96 /* Cache for directory sizes. */
97 static fsdata_t *dcache_size;
98 /* Cache for directory item count. */
99 static fsdata_t *dcache_nitems;
100
101 /* Whether UI updates should be "paused" (a counter, not a flag). */
102 static int silent_ui;
103 /* Whether silencing UI led to skipping of screen updates. */
104 static int silence_skipped_updates;
105
106 int
stats_init(config_t * config)107 stats_init(config_t *config)
108 {
109 inside_screen = !is_null_or_empty(env_get(SCREEN_ENVVAR));
110 inside_tmux = !is_null_or_empty(env_get(TMUX_ENVVAR));
111
112 load_def_values(&curr_stats, config);
113 determine_fuse_umount_cmd(&curr_stats);
114 set_gtk_available(&curr_stats);
115 curr_stats.exec_env_type = get_exec_env_type();
116 stats_update_shell_type(config->shell);
117
118 (void)hist_init(&curr_stats.cmd_hist, config->history_len);
119 (void)hist_init(&curr_stats.search_hist, config->history_len);
120 (void)hist_init(&curr_stats.prompt_hist, config->history_len);
121 (void)hist_init(&curr_stats.filter_hist, config->history_len);
122
123 hists_resize(config->history_len);
124
125 return stats_reset(config);
126 }
127
128 /* Initializes most fields of the status structure, some are left to be
129 * initialized by the stats_reset() function. */
130 static void
load_def_values(status_t * stats,config_t * config)131 load_def_values(status_t *stats, config_t *config)
132 {
133 pending_redraw = 0;
134 pending_refresh = 0;
135
136 stats->last_char = 0;
137 stats->save_msg = 0;
138 stats->use_register = 0;
139 stats->curr_register = -1;
140 stats->register_saved = 0;
141 stats->number_of_windows = 2;
142 stats->use_input_bar = 1;
143 stats->drop_new_dir_hist = 0;
144 stats->load_stage = 0;
145 stats->term_state = TS_NORMAL;
146 stats->ch_pos = 1;
147 stats->confirmed = 0;
148 stats->skip_shellout_redraw = 0;
149 stats->cs = &config->cs;
150 strcpy(stats->color_scheme, "");
151
152 stats->preview.on = 0;
153 stats->preview.kind = VK_TEXTUAL;
154 update_string(&stats->preview.cleanup_cmd, NULL);
155 stats->preview.clearing = 0;
156
157 stats->msg_head = 0;
158 stats->msg_tail = 0;
159 stats->save_msg_in_list = 1;
160 stats->allow_sb_msg_truncation = 1;
161 size_t i;
162 for(i = 0U; i < ARRAY_LEN(stats->msgs); ++i)
163 {
164 update_string(&stats->msgs[i], NULL);
165 }
166
167 stats->scroll_bind_off = 0;
168 stats->split = VSPLIT;
169 stats->splitter_pos = -1;
170 stats->splitter_ratio = 0.5;
171
172 stats->sourcing_state = SOURCING_NONE;
173
174 stats->restart_in_progress = 0;
175
176 stats->exec_env_type = EET_EMULATOR;
177
178 stats->term_multiplexer = TM_NONE;
179
180 stats->initial_lines = INT_MIN;
181 stats->initial_columns = INT_MIN;
182
183 stats->ellipsis = "...";
184
185 stats->shell_type = ST_NORMAL;
186
187 stats->fuse_umount_cmd = "";
188
189 stats->original_stdout = NULL;
190
191 update_string(&stats->chosen_files_out, NULL);
192 update_string(&stats->chosen_dir_out , NULL);
193 (void)replace_string(&stats->output_delimiter, "\n");
194
195 update_string(&stats->on_choose, NULL);
196
197 stats->preview_hint = NULL;
198
199 stats->global_local_settings = 0;
200
201 stats->history_size = 0;
202
203 stats->ipc = NULL;
204
205 #ifdef HAVE_LIBGTK
206 stats->gtk_available = 0;
207 #endif
208 }
209
210 /* Initializes stats->fuse_umount_cmd field of the stats. */
211 static void
determine_fuse_umount_cmd(status_t * stats)212 determine_fuse_umount_cmd(status_t *stats)
213 {
214 if(external_command_exists("fusermount"))
215 {
216 stats->fuse_umount_cmd = "fusermount -u";
217 }
218 else if(external_command_exists("umount"))
219 {
220 /* Some systems use regular umount command for FUSE. */
221 stats->fuse_umount_cmd = "umount";
222 }
223 else
224 {
225 /* Leave default value. */
226 }
227 }
228
229 static void
set_gtk_available(status_t * stats)230 set_gtk_available(status_t *stats)
231 {
232 #ifdef HAVE_LIBGTK
233 char *argv[] = { "vifm", NULL };
234 int argc = ARRAY_LEN(argv) - 1;
235 char **ptr = argv;
236 stats->gtk_available = gtk_init_check(&argc, &ptr);
237 #endif
238 }
239
240 int
stats_reset(const config_t * config)241 stats_reset(const config_t *config)
242 {
243 set_last_cmdline_command("");
244
245 curr_stats.initial_lines = config->lines;
246 curr_stats.initial_columns = config->columns;
247
248 return reset_dircache();
249 }
250
251 /* Returns non-zero on error. */
252 static int
reset_dircache(void)253 reset_dircache(void)
254 {
255 fsdata_free(dcache_size);
256 dcache_size = fsdata_create(0, 1);
257
258 fsdata_free(dcache_nitems);
259 dcache_nitems = fsdata_create(0, 1);
260
261 return (dcache_size == NULL || dcache_nitems == NULL);
262 }
263
264 void
stats_set_use_multiplexer(int use_term_multiplexer)265 stats_set_use_multiplexer(int use_term_multiplexer)
266 {
267 if(!use_term_multiplexer)
268 {
269 curr_stats.term_multiplexer = TM_NONE;
270 }
271 else if(inside_screen)
272 {
273 curr_stats.term_multiplexer = TM_SCREEN;
274 }
275 else if(inside_tmux)
276 {
277 curr_stats.term_multiplexer = TM_TMUX;
278 }
279 else
280 {
281 curr_stats.term_multiplexer = TM_NONE;
282 }
283 }
284
285 void
stats_update_shell_type(const char shell_cmd[])286 stats_update_shell_type(const char shell_cmd[])
287 {
288 curr_stats.shell_type = get_shell_type(shell_cmd);
289 }
290
291 TermState
stats_update_term_state(int screen_x,int screen_y)292 stats_update_term_state(int screen_x, int screen_y)
293 {
294 if(screen_x < MIN_TERM_WIDTH || screen_y < MIN_TERM_HEIGHT)
295 {
296 curr_stats.term_state = TS_TOO_SMALL;
297 }
298 else if(curr_stats.term_state != TS_NORMAL)
299 {
300 curr_stats.term_state = TS_BACK_TO_NORMAL;
301 }
302
303 return curr_stats.term_state;
304 }
305
306 void
stats_set_chosen_files_out(const char output[])307 stats_set_chosen_files_out(const char output[])
308 {
309 (void)replace_string(&curr_stats.chosen_files_out, output);
310 }
311
312 void
stats_set_chosen_dir_out(const char output[])313 stats_set_chosen_dir_out(const char output[])
314 {
315 (void)replace_string(&curr_stats.chosen_dir_out, output);
316 }
317
318 void
stats_set_output_delimiter(const char delimiter[])319 stats_set_output_delimiter(const char delimiter[])
320 {
321 (void)replace_string(&curr_stats.output_delimiter, delimiter);
322 }
323
324 void
stats_set_on_choose(const char command[])325 stats_set_on_choose(const char command[])
326 {
327 (void)replace_string(&curr_stats.on_choose, command);
328 }
329
330 int
stats_file_choose_action_set(void)331 stats_file_choose_action_set(void)
332 {
333 return !is_null_or_empty(curr_stats.chosen_files_out)
334 || !is_null_or_empty(curr_stats.on_choose);
335 }
336
337 void
stats_save_msg(const char msg[])338 stats_save_msg(const char msg[])
339 {
340 if(!curr_stats.save_msg_in_list || msg[0] == '\0')
341 {
342 return;
343 }
344
345 if(curr_stats.msg_tail != curr_stats.msg_head &&
346 strcmp(curr_stats.msgs[curr_stats.msg_tail], msg) == 0)
347 {
348 return;
349 }
350
351 curr_stats.msg_tail = (curr_stats.msg_tail + 1)%ARRAY_LEN(curr_stats.msgs);
352 if(curr_stats.msg_tail == curr_stats.msg_head)
353 {
354 free(curr_stats.msgs[curr_stats.msg_head]);
355 curr_stats.msg_head = (curr_stats.msg_head + 1)%ARRAY_LEN(curr_stats.msgs);
356 }
357 curr_stats.msgs[curr_stats.msg_tail] = strdup(msg);
358 }
359
360 void
stats_set_quickview(int on)361 stats_set_quickview(int on)
362 {
363 curr_stats.preview.on = on;
364 load_quickview_option();
365 }
366
367 void
stats_set_splitter_pos(int position)368 stats_set_splitter_pos(int position)
369 {
370 double max = (curr_stats.split == HSPLIT ? cfg.lines : cfg.columns);
371 double ratio = (position < 0 ? 0.5 : (max == INT_MIN ? -1 : position/max));
372
373 curr_stats.splitter_ratio = ratio;
374 if(curr_stats.splitter_pos != position)
375 {
376 curr_stats.splitter_pos = position;
377 stats_redraw_later();
378 }
379 }
380
381 void
stats_set_splitter_ratio(double ratio)382 stats_set_splitter_ratio(double ratio)
383 {
384 if(ratio == -1)
385 {
386 stats_set_splitter_pos(curr_stats.splitter_pos);
387 return;
388 }
389
390 curr_stats.splitter_ratio = ratio;
391
392 int max = (curr_stats.split == HSPLIT ? cfg.lines : cfg.columns);
393 if(max == INT_MIN)
394 {
395 /* Can't compute position from ratio, so leave it as is. */
396 return;
397 }
398
399 int position = (ratio == 0.5 ? -1 : max*ratio + 0.5);
400 if(curr_stats.splitter_pos != position)
401 {
402 curr_stats.splitter_pos = position;
403 stats_redraw_later();
404 }
405 }
406
407 void
stats_redraw_later(void)408 stats_redraw_later(void)
409 {
410 pending_redraw = 1;
411 }
412
413 void
stats_refresh_later(void)414 stats_refresh_later(void)
415 {
416 pending_refresh = 1;
417 }
418
419 UpdateType
stats_update_fetch(void)420 stats_update_fetch(void)
421 {
422 if(pending_refresh)
423 {
424 pending_refresh = 0;
425 pending_redraw = 0;
426 return UT_FULL;
427 }
428 if(pending_redraw)
429 {
430 pending_redraw = 0;
431 return UT_REDRAW;
432 }
433 return UT_NONE;
434 }
435
436 int
stats_redraw_planned(void)437 stats_redraw_planned(void)
438 {
439 return pending_refresh != 0
440 || pending_redraw != 0;
441 }
442
443 void
stats_silence_ui(int more)444 stats_silence_ui(int more)
445 {
446 if(more)
447 {
448 ++silent_ui;
449 }
450 else if(--silent_ui == 0)
451 {
452 stats_unsilence_ui();
453 }
454 else if(silent_ui < 0)
455 {
456 silent_ui = 0;
457 }
458 }
459
460 int
stats_silenced_ui(void)461 stats_silenced_ui(void)
462 {
463 if(silent_ui)
464 {
465 silence_skipped_updates = 1;
466 return 1;
467 }
468 return 0;
469 }
470
471 void
stats_unsilence_ui(void)472 stats_unsilence_ui(void)
473 {
474 silent_ui = 0;
475 if(silence_skipped_updates)
476 {
477 silence_skipped_updates = 0;
478 pending_redraw = 0;
479 modes_redraw();
480 }
481 }
482
483 void
hists_resize(int new_size)484 hists_resize(int new_size)
485 {
486 curr_stats.history_size = new_size;
487
488 hist_resize(&curr_stats.search_hist, new_size);
489 hist_resize(&curr_stats.cmd_hist, new_size);
490 hist_resize(&curr_stats.prompt_hist, new_size);
491 hist_resize(&curr_stats.filter_hist, new_size);
492 }
493
494 void
hists_commands_save(const char command[])495 hists_commands_save(const char command[])
496 {
497 if(is_history_command(command))
498 {
499 if(!curr_stats.restart_in_progress && curr_stats.load_stage == 3)
500 {
501 set_last_cmdline_command(command);
502 }
503 hist_add(&curr_stats.cmd_hist, command, -1);
504 }
505 }
506
507 /* Sets last_cmdline_command field of the status structure. */
508 static void
set_last_cmdline_command(const char cmd[])509 set_last_cmdline_command(const char cmd[])
510 {
511 const int err = replace_string(&curr_stats.last_cmdline_command, cmd);
512 if(err != 0)
513 {
514 LOG_ERROR_MSG("replace_string() failed on duplicating: %s", cmd);
515 }
516 assert(curr_stats.last_cmdline_command != NULL &&
517 "The field was not initialized properly");
518 }
519
520 void
hists_search_save(const char pattern[])521 hists_search_save(const char pattern[])
522 {
523 hist_add(&curr_stats.search_hist, pattern, -1);
524 }
525
526 void
hists_prompt_save(const char input[])527 hists_prompt_save(const char input[])
528 {
529 hist_add(&curr_stats.prompt_hist, input, -1);
530 }
531
532 void
hists_filter_save(const char input[])533 hists_filter_save(const char input[])
534 {
535 hist_add(&curr_stats.filter_hist, input, -1);
536 }
537
538 const char *
hists_search_last(void)539 hists_search_last(void)
540 {
541 return hist_is_empty(&curr_stats.search_hist)
542 ? ""
543 : curr_stats.search_hist.items[0].text;
544 }
545
546 void
dcache_get_at(const char path[],time_t mtime,uint64_t inode,uint64_t * size,uint64_t * nitems)547 dcache_get_at(const char path[], time_t mtime, uint64_t inode, uint64_t *size,
548 uint64_t *nitems)
549 {
550 dcache_result_t size_res, nitems_res;
551 dcache_get(path, mtime, inode, (size == NULL ? NULL : &size_res),
552 (nitems == NULL ? NULL : &nitems_res));
553
554 if(size != NULL)
555 {
556 *size = (size_res.is_valid ? size_res.value : DCACHE_UNKNOWN);
557 }
558 if(nitems != NULL)
559 {
560 *nitems = (nitems_res.is_valid ? nitems_res.value : DCACHE_UNKNOWN);
561 }
562 }
563
564 void
dcache_get_of(const dir_entry_t * entry,dcache_result_t * size,dcache_result_t * nitems)565 dcache_get_of(const dir_entry_t *entry, dcache_result_t *size,
566 dcache_result_t *nitems)
567 {
568 if(size == NULL && nitems == NULL)
569 {
570 return;
571 }
572
573 char full_path[PATH_MAX + 1];
574 get_full_path_of(entry, sizeof(full_path), full_path);
575
576 uint64_t inode = get_true_inode(entry);
577 dcache_get(full_path, entry->mtime, inode, size, nitems);
578 }
579
580 /* Retrieves information about the path checking whether it's outdated. size
581 * and/or nitems can be NULL. */
582 static void
dcache_get(const char path[],time_t mtime,uint64_t inode,dcache_result_t * size,dcache_result_t * nitems)583 dcache_get(const char path[], time_t mtime, uint64_t inode,
584 dcache_result_t *size, dcache_result_t *nitems)
585 {
586 if(size != NULL)
587 {
588 size->value = DCACHE_UNKNOWN;
589 size->is_valid = 0;
590
591 pthread_mutex_lock(&dcache_size_mutex);
592 dcache_data_t size_data;
593 if(fsdata_get(dcache_size, path, &size_data, sizeof(size_data)) == 0)
594 {
595 size->value = size_data.value;
596 /* We check strictly for less than to handle scenario when multiple
597 * changes occurred during the same second. */
598 size->is_valid = (mtime < size_data.timestamp);
599 #ifndef _WIN32
600 size->is_valid &= (inode == size_data.inode);
601 #endif
602 }
603 pthread_mutex_unlock(&dcache_size_mutex);
604 }
605
606 if(nitems != NULL)
607 {
608 nitems->value = DCACHE_UNKNOWN;
609 nitems->is_valid = 0;
610
611 pthread_mutex_lock(&dcache_nitems_mutex);
612 dcache_data_t nitems_data;
613 if(fsdata_get(dcache_nitems, path, &nitems_data, sizeof(nitems_data)) == 0)
614 {
615 nitems->value = nitems_data.value;
616 /* We check strictly for less than to handle scenario when multiple
617 * changes occurred during the same second. */
618 nitems->is_valid = (mtime < nitems_data.timestamp);
619 #ifndef _WIN32
620 nitems->is_valid &= (inode == nitems_data.inode);
621 #endif
622 }
623 pthread_mutex_unlock(&dcache_nitems_mutex);
624 }
625 }
626
627 void
dcache_update_parent_sizes(const char path[],uint64_t by)628 dcache_update_parent_sizes(const char path[], uint64_t by)
629 {
630 pthread_mutex_lock(&dcache_size_mutex);
631 (void)fsdata_map_parents(dcache_size, path, &size_updater, &by);
632 pthread_mutex_unlock(&dcache_size_mutex);
633 }
634
635 /* Updates cached value by a fixed amount. */
636 static void
size_updater(void * data,void * arg)637 size_updater(void *data, void *arg)
638 {
639 const uint64_t *const by = arg;
640 dcache_data_t *const what = data;
641
642 what->value += *by;
643 }
644
645 int
dcache_set_at(const char path[],uint64_t inode,uint64_t size,uint64_t nitems)646 dcache_set_at(const char path[], uint64_t inode, uint64_t size, uint64_t nitems)
647 {
648 int ret = 0;
649 const time_t ts = time(NULL);
650
651 if(size != DCACHE_UNKNOWN)
652 {
653 dcache_data_t data = { .value = size, .timestamp = ts };
654 #ifndef _WIN32
655 data.inode = (ino_t)inode;
656 #endif
657
658 pthread_mutex_lock(&dcache_size_mutex);
659 ret |= fsdata_set(dcache_size, path, &data, sizeof(data));
660 pthread_mutex_unlock(&dcache_size_mutex);
661 }
662
663 if(nitems != DCACHE_UNKNOWN)
664 {
665 dcache_data_t data = { .value = nitems, .timestamp = ts };
666 #ifndef _WIN32
667 data.inode = (ino_t)inode;
668 #endif
669
670 pthread_mutex_lock(&dcache_nitems_mutex);
671 ret |= fsdata_set(dcache_nitems, path, &data, sizeof(data));
672 pthread_mutex_unlock(&dcache_nitems_mutex);
673 }
674
675 return ret;
676 }
677
678 /* vim: set tabstop=2 softtabstop=2 shiftwidth=2 noexpandtab cinoptions-=(0 : */
679 /* vim: set cinoptions+=t0 filetype=c : */
680