1 // Support for exposing the terminal size.
2 
3 #include "config.h"  // IWYU pragma: keep
4 #ifndef FISH_TERMSIZE_H
5 #define FISH_TERMSIZE_H
6 
7 #include <stdint.h>
8 
9 #include "common.h"
10 #include "global_safety.h"
11 
12 class environment_t;
13 class parser_t;
14 struct termsize_tester_t;
15 
16 /// A simple value type wrapping up a terminal size.
17 struct termsize_t {
18     /// Default width and height.
19     static constexpr int DEFAULT_WIDTH = 80;
20     static constexpr int DEFAULT_HEIGHT = 24;
21 
22     /// width of the terminal, in columns.
23     int width{DEFAULT_WIDTH};
24 
25     /// height of the terminal, in rows.
26     int height{DEFAULT_HEIGHT};
27 
28     /// Construct from width and height.
termsize_ttermsize_t29     termsize_t(int w, int h) : width(w), height(h) {}
30 
31     /// Return a default-sized termsize.
defaultstermsize_t32     static termsize_t defaults() { return termsize_t{DEFAULT_WIDTH, DEFAULT_HEIGHT}; }
33 
34     bool operator==(termsize_t rhs) const {
35         return this->width == rhs.width && this->height == rhs.height;
36     }
37 
38     bool operator!=(termsize_t rhs) const { return !(*this == rhs); }
39 };
40 
41 /// Termsize monitoring is more complicated than one may think.
42 /// The main source of complexity is the interaction between the environment variables COLUMNS/ROWS,
43 /// the WINCH signal, and the TIOCGWINSZ ioctl.
44 /// Our policy is "last seen wins": if COLUMNS or LINES is modified, we respect that until we get a
45 /// SIGWINCH.
46 struct termsize_container_t {
47     /// \return the termsize without applying any updates.
48     /// Return the default termsize if none.
49     termsize_t last() const;
50 
51     /// If our termsize is stale, update it, using \p parser firing any events that may be
52     /// registered for COLUMNS and LINES.
53     /// \return the updated termsize.
54     termsize_t updating(parser_t &parser);
55 
56     /// Initialize our termsize, using the given environment stack.
57     /// This will prefer to use COLUMNS and LINES, but will fall back to the tty size reader.
58     /// This does not change any variables in the environment.
59     termsize_t initialize(const environment_t &vars);
60 
61     /// Note that a WINCH signal is received.
62     /// Naturally this may be called from within a signal handler.
63     static void handle_winch();
64 
65     /// Invalidate the tty in the sense that we need to re-fetch its termsize.
66     static void invalidate_tty();
67 
68     /// Note that COLUMNS and/or LINES global variables changed.
69     void handle_columns_lines_var_change(const environment_t &vars);
70 
71     /// \return the singleton shared container.
72     static termsize_container_t &shared();
73 
74    private:
75     /// A function used for accessing the termsize from the tty. This is only exposed for testing.
76     using tty_size_reader_func_t = maybe_t<termsize_t> (*)();
77 
78     struct data_t {
79         // The last termsize returned by TIOCGWINSZ, or none if none.
80         maybe_t<termsize_t> last_from_tty{};
81 
82         // The last termsize seen from the environment (COLUMNS/LINES), or none if none.
83         maybe_t<termsize_t> last_from_env{};
84 
85         // The last-seen tty-invalidation generation count.
86         // Set to a huge value so it's initially stale.
87         uint32_t last_tty_gen_count{UINT32_MAX};
88 
89         /// \return the current termsize from this data.
90         termsize_t current() const;
91 
92         /// Mark that our termsize is (for the time being) from the environment, not the tty.
93         void mark_override_from_env(termsize_t ts);
94     };
95 
96     // Construct from a reader function.
termsize_container_ttermsize_container_t97     explicit termsize_container_t(tty_size_reader_func_t func) : tty_size_reader_(func) {}
98 
99     // Update COLUMNS and LINES in the parser's stack.
100     void set_columns_lines_vars(termsize_t val, parser_t &parser);
101 
102     // Our lock-protected data.
103     mutable owning_lock<data_t> data_{};
104 
105     // An indication that we are currently in the process of setting COLUMNS and LINES, and so do
106     // not react to any changes.
107     relaxed_atomic_bool_t setting_env_vars_{false};
108     const tty_size_reader_func_t tty_size_reader_;
109 
110     friend termsize_tester_t;
111 };
112 
113 /// Convenience helper to return the last known termsize.
termsize_last()114 inline termsize_t termsize_last() { return termsize_container_t::shared().last(); }
115 
116 #endif  // FISH_TERMSIZE_H
117