├── icetop.gif ├── .gitignore ├── util ├── getenv.hh ├── getenv.cc ├── ti.hh └── ti.cc ├── README.md ├── meson.build ├── COPYING └── icetop.cc /icetop.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aperezdc/icetop/master/icetop.gif -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | icetop 3 | Makefile 4 | autom4te.cache/ 5 | configure 6 | config.log 7 | config.status 8 | aclocal.m4 9 | -------------------------------------------------------------------------------- /util/getenv.hh: -------------------------------------------------------------------------------- 1 | /* 2 | * getenv.hh 3 | * Copyright (C) 2016 Adrian Perez 4 | * 5 | * Distributed under terms of the GPLv2 license. 6 | */ 7 | 8 | #ifndef GETENV_HH 9 | #define GETENV_HH 10 | 11 | #include 12 | #include 13 | 14 | namespace util { 15 | using std::experimental::optional; 16 | optional getenv(const std::string&); 17 | } // namespace util 18 | 19 | #endif /* !GETENV_HH */ 20 | -------------------------------------------------------------------------------- /util/getenv.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * getenv.cc 3 | * Copyright (C) 2016 Adrian Perez 4 | * 5 | * Distributed under terms of the GPLv2 license. 6 | */ 7 | 8 | #include "getenv.hh" 9 | #include 10 | 11 | namespace util { 12 | 13 | optional getenv(const std::string& name) 14 | { 15 | auto value = ::getenv(name.c_str()); 16 | return value ? optional(value) : optional(); 17 | } 18 | 19 | } // namespace util 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | icetop 2 | ====== 3 | 4 | This is a simple, console-only monitor for 5 | [Icecream](https://github.com/icecc/icecream). Also, I am using it as a 6 | playground for [libdill](http://libdill.org), testing C++14 features 7 | (and C++1z, when compilers implementing it), and experimenting with text 8 | console output. 9 | 10 | Warning ⚠️ 11 | ----------- 12 | 13 | I rarely update this project, so it may or may not work for you. If you 14 | want a console monitor for Icecream that works *and* receives more 15 | maintenance, [icecream-sundae](https://github.com/JPEWdev/icecream-sundae) 16 | is a good alternative. 17 | 18 | 19 | Screenshot 20 | ---------- 21 | 22 | ![Screenshot](https://github.com/aperezdc/icetop/raw/master/icetop.gif) 23 | 24 | 25 | Building 26 | -------- 27 | 28 | You will need the development headers for Icecream and 29 | [libdill](http://libdill.org) installed: 30 | 31 | ```sh 32 | ./configure && make 33 | ``` 34 | 35 | Or, if you are using the sources from Git: 36 | 37 | ```sh 38 | autoreconf && ./configure && make 39 | ``` 40 | 41 | If your system does not have pacakages for `libdill`, you can build it 42 | yourself and link it statically in `icetop`: 43 | 44 | ```sh 45 | # Build libdill locally 46 | wget -O - http://libdill.org/libdill-0.5-beta.tar.gz | tar -xzf - 47 | pushd libdill-0.5-beta 48 | ./configure --prefix=$(pwd)/../libdill --enable-static --disable-shared 49 | make install 50 | popd 51 | # Build icetop using the local libdill build 52 | ./configure PKG_CONFIG_PATH=$(pwd)/libdill/lib/pkgconfig 53 | make 54 | ``` 55 | 56 | License 57 | ------- 58 | 59 | `icetop` is distributed under the terms of the GPLv2 license. See 60 | [COPYING](COPYING) for the complete text of the license. 61 | -------------------------------------------------------------------------------- /meson.build: -------------------------------------------------------------------------------- 1 | project('icetop', 'cpp', 2 | version: '0.1', 3 | license: 'GPL3') 4 | 5 | cpp = meson.get_compiler('cpp') 6 | 7 | tickit = dependency('tickit', required: true) 8 | icecc = dependency('icecc', required: true, version: '>= 1.0') 9 | 10 | libdill = dependency('libdill', required: false, version: '>= 1.0', modules: 'libdill::dill') 11 | if not libdill.found() 12 | libdill = cpp.find_library('dill', 13 | has_headers: 'libdill.h', 14 | required: true, 15 | ) 16 | endif 17 | 18 | cpp_args = [] 19 | if cpp.has_argument('-Wall') 20 | cpp_args += ['-Wall'] 21 | endif 22 | 23 | if cpp.has_argument('-std=gnu++14') 24 | cpp_args += ['-std=gnu++14'] 25 | elif cpp.has_argument('-std=c++14') 26 | cpp_args += ['-std=c++14'] 27 | else 28 | error('Your C++ compiler does not seem to support C++14') 29 | endif 30 | 31 | # Check whether icecc was compiled using the old C++ ABI, and work around this 32 | # to be able to still use C++11/14 features while linking to the icecc library 33 | abi_test_code = ''' 34 | #include 35 | int main() { 36 | DiscoverSched sched("foo"); 37 | } 38 | ''' 39 | 40 | if cpp.links(abi_test_code, 41 | name: 'IceCC uses the new C++11 ABI', 42 | args: cpp_args + ['-D_GLIBCXX_USE_CXX11_ABI=1'], 43 | dependencies: icecc) 44 | cpp_args += ['-D_GLIBCXX_USE_CXX11_ABI=1'] 45 | elif cpp.links(abi_test_code, 46 | name: 'IceCC uses the old pre-C++11 ABI', 47 | args: cpp_args + ['-D_GLIBCXX_USE_CXX11_ABI=0'], 48 | dependencies: icecc) 49 | cpp_args += ['-D_GLIBCXX_USE_CXX11_ABI=0'] 50 | else 51 | error('Could not determine which C++ ABI was used to build IceCC') 52 | endif 53 | 54 | 55 | icetop = executable('icetop', 56 | 'icetop.cc', 57 | 'util/getenv.cc', 58 | 'util/getenv.hh', 59 | 'util/ti.cc', 60 | 'util/ti.hh', 61 | dependencies: [libdill, icecc, tickit], 62 | cpp_args: cpp_args, 63 | install: true) 64 | -------------------------------------------------------------------------------- /util/ti.hh: -------------------------------------------------------------------------------- 1 | /* 2 | * ti.hh 3 | * Copyright (C) 2016 Adrian Perez 4 | * 5 | * Distributed under terms of the MIT license. 6 | */ 7 | 8 | #ifndef TI_HH 9 | #define TI_HH 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | using std::experimental::optional; 17 | 18 | struct TickitRenderBuffer; 19 | struct TickitRectSet; 20 | struct TickitWindow; 21 | struct TickitTerm; 22 | struct TickitPen; 23 | 24 | 25 | namespace ti { 26 | 27 | using uint = unsigned int; 28 | 29 | void _track(const char *tname, const char* ti_tname, void* ptr, const char *what); 30 | 31 | #define TI_UNCOPYABLE(name) \ 32 | private: \ 33 | name(const name&) = delete; \ 34 | name& operator=(const name&) = delete 35 | 36 | #define TI_MOVABLE(name) \ 37 | public: \ 38 | name(name&&) = default 39 | 40 | #define TI_WRAP__DEBUG(name, ti_name) \ 41 | static void track_delete(tickit_type* ptr); \ 42 | static void no_delete(tickit_type* ptr) { \ 43 | ti::_track(#name, #ti_name, ptr, "kept"); } \ 44 | explicit name(tickit_type* ptr, deleter d): \ 45 | m_ ## name(ptr, d) { assert(ptr); assert(d); \ 46 | ti::_track(#name, #ti_name, ptr, "wrap"); } 47 | 48 | #define TI_WRAP__NODEBUG(name, ti_name) \ 49 | static void no_delete(tickit_type*) { } \ 50 | explicit name(tickit_type* ptr, deleter d): \ 51 | m_ ## name(ptr, d) { assert(ptr); assert(d); } 52 | 53 | #if defined(TI_TRACE_POINTERS) && TI_TRACE_POINTERS 54 | # define TI_WRAP_ TI_WRAP__DEBUG 55 | #else 56 | # define TI_WRAP_ TI_WRAP__NODEBUG 57 | #endif 58 | 59 | #define TI_WRAP(name, ti_name) \ 60 | public: \ 61 | using tickit_type = ti_name; \ 62 | inline tickit_type* unwrap() { \ 63 | assert(m_ ## name); return m_ ## name.get(); } \ 64 | inline const tickit_type* unwrap() const { \ 65 | assert(m_ ## name); return m_ ## name.get(); } \ 66 | private: \ 67 | using deleter = std::function; \ 68 | TI_WRAP_(name, ti_name) \ 69 | std::unique_ptr m_ ## name 70 | 71 | 72 | // Forward declarations. 73 | template struct event_handler; 74 | class render_buffer; 75 | 76 | 77 | struct rect { 78 | uint top, left, lines, columns; 79 | 80 | rect(rect&&) = default; 81 | rect(const rect&) = default; 82 | 83 | bool operator==(const rect& other) const { 84 | return this == &other || 85 | (top == other.top && 86 | left == other.left && 87 | lines == other.lines && 88 | columns == other.columns); 89 | } 90 | }; 91 | 92 | 93 | template 94 | class event_binding_base { 95 | TI_MOVABLE(event_binding_base); 96 | 97 | public: 98 | void unbind(); 99 | 100 | private: 101 | event_binding_base(T& object, int event_id) 102 | : m_object(object), m_event_id(event_id) 103 | { 104 | } 105 | 106 | T& m_object; 107 | int m_event_id; 108 | 109 | template friend struct event_handler; 110 | }; 111 | 112 | 113 | template 114 | struct event_base 115 | { 116 | TI_UNCOPYABLE(event_base); 117 | 118 | public: 119 | using emitter_type = T; 120 | using event_type = E; 121 | using functor_type = std::function; 122 | 123 | private: 124 | event_base() = default; 125 | 126 | template struct event_handler; 127 | friend emitter_type; 128 | friend event_type; 129 | }; 130 | 131 | 132 | #define TI_EVENT_BASE(name) \ 133 | template \ 134 | struct name : public event_base> 135 | 136 | 137 | TI_EVENT_BASE(expose_event_base) 138 | { 139 | render_buffer& render; 140 | rect& area; 141 | 142 | expose_event_base(render_buffer& rb, rect& a) 143 | : render(rb), area(a) { } 144 | }; 145 | 146 | 147 | TI_EVENT_BASE(geometry_change_event_base) 148 | { 149 | rect& old_area; 150 | rect& area; 151 | 152 | geometry_change_event_base(rect& oa, rect& a) 153 | : old_area(oa), area(a) { } 154 | }; 155 | 156 | 157 | class terminal { 158 | TI_UNCOPYABLE(terminal); 159 | TI_MOVABLE(terminal); 160 | 161 | public: 162 | enum mouse { off, click, drag, move }; 163 | enum screen { normal, alt, altscreen = alt }; 164 | 165 | terminal(); 166 | 167 | terminal& flush(); 168 | terminal& clear(); 169 | terminal& wait_ready(uint msec = 50); 170 | terminal& wait_input(int msec = -1); 171 | terminal& set(enum mouse mode); 172 | terminal& set(enum screen mode); 173 | 174 | terminal& write(const std::string&); 175 | terminal& write(long long unsigned); 176 | terminal& write(long long int); 177 | 178 | private: 179 | TI_WRAP(terminal, TickitTerm); 180 | friend class window; 181 | }; 182 | 183 | 184 | template 185 | static inline terminal& operator<<(terminal& term, const T& v) 186 | { 187 | return term.write(v); 188 | } 189 | 190 | 191 | class pen { 192 | public: 193 | enum attr { 194 | attr_fg, 195 | attr_bg, 196 | attr_bold, 197 | attr_italic, 198 | attr_underline, 199 | attr_reverse, 200 | attr_strike, 201 | attr_blink 202 | }; 203 | using attr_reg = std::pair; 204 | 205 | static const attr_reg bold, italic, underline, reverse, strike, blink; 206 | 207 | static inline attr_reg fg(int color = -1) { return { attr_fg, color }; } 208 | static inline attr_reg bg(int color = -1) { return { attr_bg, color }; } 209 | 210 | pen(std::initializer_list l); 211 | pen(const pen& other); 212 | 213 | inline pen(pen&& other): m_pen(nullptr) { *this = std::move(other); } 214 | 215 | ~pen(); 216 | 217 | pen& operator=(pen&& other); 218 | 219 | pen& operator=(const pen& other) { 220 | if (this != &other) { 221 | pen copy(other); 222 | std::swap(m_pen, copy.m_pen); 223 | } 224 | return *this; 225 | } 226 | 227 | bool operator==(const pen& other) const; 228 | 229 | pen& set(attr tag, int value = -1); 230 | inline pen& set(const attr_reg& a) { 231 | return set(a.first, a.second); 232 | } 233 | 234 | enum copy_mode { copy_normal, overwrite }; 235 | pen& copy_from(const pen& other, copy_mode mode = copy_mode::copy_normal); 236 | 237 | inline pen copy() const { 238 | pen newpen {}; 239 | newpen.copy_from(*this); 240 | return newpen; 241 | } 242 | 243 | inline TickitPen* unwrap() { assert(m_pen); return m_pen; } 244 | inline const TickitPen* unwrap() const { assert(m_pen); return m_pen; } 245 | 246 | private: 247 | // XXX: Note that TickitPen keeps its own reference count, so this does 248 | // not use std::shared_ptr; instead we define the needed operators 249 | // to call tickit_pen_ref() and tickit_pen_unref(). 250 | TickitPen *m_pen; 251 | 252 | pen(TickitPen *p); 253 | }; 254 | 255 | 256 | class render_buffer { 257 | TI_UNCOPYABLE(render_buffer); 258 | TI_MOVABLE(render_buffer); 259 | 260 | public: 261 | // Generating output. 262 | render_buffer& write(const std::string&); 263 | render_buffer& write(long long unsigned); 264 | render_buffer& write(long long int); 265 | 266 | render_buffer& clear(); 267 | render_buffer& clear(const rect& r); 268 | render_buffer& clear(uint line, uint col, uint columns); 269 | 270 | // Paint state. 271 | render_buffer& save(); 272 | render_buffer& save_pen(); 273 | render_buffer& restore(); 274 | render_buffer& set_pen(const pen& p); 275 | render_buffer& add_pen(const pen& p); 276 | 277 | // Movement. 278 | render_buffer& at(uint line, uint col); 279 | 280 | private: 281 | TI_WRAP(render_buffer, TickitRenderBuffer); 282 | 283 | template friend struct event_handler; 284 | }; 285 | 286 | 287 | template 288 | static inline render_buffer& operator<<(render_buffer& rb, const T& v) 289 | { 290 | return rb.write(v); 291 | } 292 | 293 | template <> 294 | render_buffer& operator<<(render_buffer& rb, const pen& p) 295 | { 296 | return rb.add_pen(p); 297 | } 298 | 299 | 300 | class window { 301 | TI_UNCOPYABLE(window); 302 | TI_MOVABLE(window); 303 | 304 | public: 305 | using event_binding = event_binding_base; 306 | using expose_event = expose_event_base; 307 | using geometry_change_event = geometry_change_event_base; 308 | 309 | enum flags { 310 | no_flags = 0, 311 | hidden = 1 << 0, 312 | lowest = 1 << 1, 313 | root_parent = 1 << 2, 314 | steal_input = 1 << 3, 315 | popup = 1 << 4, 316 | }; 317 | window(terminal& term); 318 | window(window& parent, 319 | const rect& r, 320 | enum flags flags = no_flags); 321 | 322 | window root() const; 323 | optional parent() const; 324 | 325 | window& expose(); 326 | window& flush(); 327 | 328 | uint top() const; 329 | uint left() const; 330 | uint lines() const; 331 | uint columns() const; 332 | 333 | window& set_position(uint line, uint col); 334 | window& set_geometry(const rect& r); 335 | rect absolute_geometry() const; 336 | rect geometry() const; 337 | 338 | enum scroll { with_children }; 339 | window& scroll(int downward, int rightward, enum scroll); 340 | window& scroll(int downward, int rightward); 341 | window& scroll(int downward, int rightward, const rect& r); 342 | 343 | event_binding on_expose(expose_event::functor_type f); 344 | event_binding on_geometry_change(geometry_change_event::functor_type f); 345 | 346 | private: 347 | TI_WRAP(window, TickitWindow); 348 | }; 349 | 350 | 351 | } // namespace ti 352 | 353 | 354 | #undef TI_EVENT_BASE 355 | #undef TI_UNCOPYABLE 356 | #undef TI_MOVABLE 357 | #undef TI_WRAP__NODEBUG 358 | #undef TI_WRAP__DEBUG 359 | #undef TI_WRAP_ 360 | #undef TI_WRAP 361 | 362 | #endif /* !TI_HH */ 363 | -------------------------------------------------------------------------------- /util/ti.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * ti.cc 3 | * Copyright (C) 2016 Adrian Perez 4 | * 5 | * Distributed under terms of the MIT license. 6 | */ 7 | 8 | #include "ti.hh" 9 | 10 | extern "C" { 11 | #include 12 | } 13 | 14 | #include 15 | 16 | namespace ti { 17 | 18 | void _track(const char *tname, const char* ti_tname, void* ptr, const char *what) 19 | { 20 | if (tickit_debug_enabled) { 21 | tickit_debug_logf("Tp", "%s %p (%s)", what, ptr, ti_tname); 22 | } 23 | } 24 | 25 | #if defined(TI_TRACE_POINTERS) && TI_TRACE_POINTERS 26 | void terminal::track_delete(TickitTerm* ptr) 27 | { 28 | ti::_track("terminal", "TickitTerm", ptr, "free"); 29 | tickit_term_destroy(ptr); 30 | } 31 | void window::track_delete(TickitWindow* ptr) 32 | { 33 | ti::_track("window", "TickitWindow", ptr, "free"); 34 | tickit_window_destroy(ptr); 35 | } 36 | void render_buffer::track_delete(TickitRenderBuffer* ptr) 37 | { 38 | ti::_track("render_buffer", "TickitRenderBuffer", ptr, "free"); 39 | tickit_renderbuffer_destroy(ptr); 40 | } 41 | # define terminal_free terminal::track_delete 42 | # define window_free window::track_delete 43 | # define render_buffer_free render_buffer::track_delete 44 | # define trace_pointer ti::_track 45 | #else 46 | # define terminal_free tickit_term_destroy 47 | # define window_free tickit_window_destroy 48 | # define render_buffer_free tickit_renderbuffer_destroy 49 | # define trace_pointer(a, b, c, d) ((void)0) 50 | #endif 51 | 52 | 53 | /* 54 | * Map object types to their Tickit event callback types. 55 | */ 56 | template struct emitter_map { }; 57 | #define TI_EMITTER_MAP(emitter_name, event_name) \ 58 | template <> struct emitter_map { \ 59 | using event = event_name; \ 60 | using type = event_name ## Fn ; \ 61 | } 62 | 63 | TI_EMITTER_MAP(window, TickitWindowEvent); 64 | TI_EMITTER_MAP(terminal, TickitTermEvent); 65 | 66 | 67 | /* 68 | * Map event types to their Tickit "event info" struct types. 69 | */ 70 | template struct event_info_map { 71 | }; 72 | 73 | #define TI_EVENT_INFO(emitter_type, event_name, event_code, info_name) \ 74 | template <> struct event_info_map { \ 75 | using type = info_name; \ 76 | using EventType = typename emitter_map::event; \ 77 | static constexpr EventType code = event_code; \ 78 | } 79 | 80 | TI_EVENT_INFO(window, expose_event, TICKIT_WINDOW_ON_EXPOSE, TickitExposeEventInfo); 81 | TI_EVENT_INFO(window, geometry_change_event, TICKIT_WINDOW_ON_GEOMCHANGE, TickitGeomchangeEventInfo); 82 | 83 | 84 | struct debug_init { 85 | debug_init() { 86 | tickit_debug_init(); 87 | } 88 | }; 89 | 90 | static debug_init s_debug_init = {}; 91 | 92 | 93 | static inline uint i2u(int v) { 94 | assert(v >= 0); 95 | return static_cast(v); 96 | } 97 | 98 | static inline int u2i(uint v) { 99 | assert(v <= std::numeric_limits::max()); 100 | return static_cast(v); 101 | } 102 | 103 | 104 | template static inline TT to_tickit(T r); 105 | 106 | template <> 107 | TickitRect to_tickit(const rect& r) 108 | { 109 | TickitRect tr = { 110 | .top = u2i(r.top), 111 | .left = u2i(r.left), 112 | .lines = u2i(r.lines), 113 | .cols = u2i(r.columns), 114 | }; 115 | return tr; 116 | } 117 | 118 | template <> 119 | const TickitRect* to_tickit(const rect& r) 120 | { 121 | u2i(r.top); u2i(r.left); u2i(r.lines); u2i(r.columns); 122 | return reinterpret_cast(&r); 123 | } 124 | 125 | template static inline T from_tickit(TT r); 126 | 127 | template <> 128 | rect& from_tickit(TickitRect* r) 129 | { 130 | assert(r->top >= 0); 131 | assert(r->left >= 0); 132 | assert(r->lines >= 0); 133 | assert(r->cols >= 0); 134 | return *reinterpret_cast(r); 135 | } 136 | 137 | template <> 138 | rect from_tickit(const TickitRect& r) 139 | { 140 | return { i2u(r.top), i2u(r.left), i2u(r.lines), i2u(r.cols) }; 141 | } 142 | 143 | 144 | terminal::terminal() 145 | : terminal(tickit_term_open_stdio(), terminal_free) 146 | { 147 | trace_pointer("terminal", "TickitTerm", unwrap(), " +"); 148 | } 149 | 150 | terminal& terminal::flush() { tickit_term_flush(unwrap()); return *this; } 151 | terminal& terminal::clear() { tickit_term_clear(unwrap()); return *this; } 152 | 153 | terminal& terminal::wait_ready(uint msec) 154 | { 155 | tickit_term_await_started_msec(unwrap(), u2i(msec)); 156 | tickit_term_refresh_size(unwrap()); 157 | tickit_term_observe_sigwinch(unwrap(), true); 158 | return *this; 159 | } 160 | 161 | terminal& terminal::wait_input(int msec) 162 | { 163 | tickit_term_input_wait_msec(unwrap(), msec); 164 | return *this; 165 | } 166 | 167 | static inline TickitTermMouseMode to_tickit(enum terminal::mouse mode) 168 | { 169 | switch (mode) { 170 | case terminal::mouse::off: return TICKIT_TERM_MOUSEMODE_OFF; 171 | case terminal::mouse::click: return TICKIT_TERM_MOUSEMODE_CLICK; 172 | case terminal::mouse::drag: return TICKIT_TERM_MOUSEMODE_DRAG; 173 | case terminal::mouse::move: return TICKIT_TERM_MOUSEMODE_MOVE; 174 | } 175 | assert(false); 176 | return TICKIT_TERM_MOUSEMODE_OFF; 177 | } 178 | 179 | terminal& terminal::set(enum terminal::mouse mode) 180 | { 181 | tickit_term_setctl_int(unwrap(), TICKIT_TERMCTL_MOUSE, to_tickit(mode)); 182 | return *this; 183 | } 184 | 185 | terminal& terminal::set(enum terminal::screen mode) 186 | { 187 | tickit_term_setctl_int(unwrap(), TICKIT_TERMCTL_ALTSCREEN, 188 | (mode == screen::alt) ? 1 : 0); 189 | return *this; 190 | } 191 | 192 | terminal& terminal::write(const std::string& s) 193 | { 194 | tickit_term_printn(unwrap(), s.data(), s.size()); 195 | return *this; 196 | } 197 | 198 | terminal& terminal::write(long long unsigned v) 199 | { 200 | tickit_term_printf(unwrap(), "%llu", v); 201 | return *this; 202 | } 203 | 204 | terminal& terminal::write(long long int v) 205 | { 206 | tickit_term_printf(unwrap(), "%lli", v); 207 | return *this; 208 | } 209 | 210 | 211 | const pen::attr_reg pen::bold = { pen::attr_bold, 1 }; 212 | const pen::attr_reg pen::underline = { pen::attr_underline, 1 }; 213 | const pen::attr_reg pen::italic = { pen::attr_italic, 1 }; 214 | const pen::attr_reg pen::blink = { pen::attr_blink, 1 }; 215 | const pen::attr_reg pen::reverse = { pen::attr_reverse, 1 }; 216 | const pen::attr_reg pen::strike = { pen::attr_strike, 1 }; 217 | 218 | pen::pen(std::initializer_list l) 219 | : m_pen(tickit_pen_new()) 220 | { 221 | for (auto reg: l) set(reg); 222 | } 223 | 224 | pen::pen(const pen& other) 225 | : m_pen(tickit_pen_ref(other.m_pen)) 226 | { 227 | } 228 | 229 | pen::~pen() 230 | { 231 | tickit_pen_unref(m_pen); 232 | m_pen = nullptr; 233 | } 234 | 235 | pen& pen::operator=(pen&& other) 236 | { 237 | if (this != &other) { 238 | if (m_pen) { 239 | tickit_pen_unref(m_pen); 240 | m_pen = nullptr; 241 | } 242 | std::swap(m_pen, other.m_pen); 243 | } 244 | return *this; 245 | } 246 | 247 | bool pen::operator==(const pen& other) const { 248 | return this == &other 249 | || m_pen == other.m_pen 250 | || tickit_pen_equiv(m_pen, other.m_pen); 251 | } 252 | 253 | static inline TickitPenAttr to_tickit(pen::attr attr) 254 | { 255 | switch (attr) { 256 | case pen::attr_fg: return TICKIT_PEN_FG; 257 | case pen::attr_bg: return TICKIT_PEN_BG; 258 | case pen::attr_bold: return TICKIT_PEN_BOLD; 259 | case pen::attr_underline: return TICKIT_PEN_UNDER; 260 | case pen::attr_italic: return TICKIT_PEN_ITALIC; 261 | case pen::attr_reverse: return TICKIT_PEN_REVERSE; 262 | case pen::attr_strike: return TICKIT_PEN_STRIKE; 263 | case pen::attr_blink: return TICKIT_PEN_BLINK; 264 | } 265 | assert(false); 266 | return TICKIT_PEN_FG; 267 | } 268 | 269 | pen& pen::set(pen::attr tag, int value) 270 | { 271 | TickitPenAttr attr_tag = to_tickit(tag); 272 | switch (tickit_pen_attrtype(attr_tag)) { 273 | case TICKIT_PENTYPE_BOOL: 274 | tickit_pen_set_bool_attr(m_pen, attr_tag, value != 0); 275 | break; 276 | case TICKIT_PENTYPE_INT: 277 | tickit_pen_set_int_attr(m_pen, attr_tag, value); 278 | break; 279 | case TICKIT_PENTYPE_COLOUR: 280 | tickit_pen_set_colour_attr(m_pen, attr_tag, value); 281 | break; 282 | } 283 | return *this; 284 | } 285 | 286 | pen& pen::copy_from(const pen& other, pen::copy_mode mode) 287 | { 288 | tickit_pen_copy(m_pen, other.m_pen, mode == pen::copy_mode::overwrite); 289 | return *this; 290 | } 291 | 292 | 293 | render_buffer& render_buffer::write(const std::string& s) 294 | { 295 | tickit_renderbuffer_textn(unwrap(), s.data(), s.size()); 296 | return *this; 297 | } 298 | 299 | render_buffer& render_buffer::write(long long unsigned v) 300 | { 301 | tickit_renderbuffer_textf(unwrap(), "%llu", v); 302 | return *this; 303 | } 304 | 305 | render_buffer& render_buffer::write(long long int v) 306 | { 307 | tickit_renderbuffer_textf(unwrap(), "%lli", v); 308 | return *this; 309 | } 310 | 311 | render_buffer& render_buffer::clear() 312 | { 313 | tickit_renderbuffer_clear(unwrap()); 314 | return *this; 315 | } 316 | 317 | render_buffer& render_buffer::clear(const rect& r) 318 | { 319 | auto tr(to_tickit(r)); 320 | tickit_renderbuffer_eraserect(unwrap(), &tr); 321 | return *this; 322 | } 323 | 324 | render_buffer& render_buffer::clear(uint line, uint col, uint cols) 325 | { 326 | tickit_renderbuffer_erase_at(unwrap(), u2i(line), u2i(col), u2i(cols)); 327 | return *this; 328 | } 329 | 330 | render_buffer& render_buffer::save() 331 | { 332 | tickit_renderbuffer_save(unwrap()); 333 | return *this; 334 | } 335 | 336 | render_buffer& render_buffer::save_pen() 337 | { 338 | tickit_renderbuffer_savepen(unwrap()); 339 | return *this; 340 | } 341 | 342 | render_buffer& render_buffer::restore() 343 | { 344 | tickit_renderbuffer_restore(unwrap()); 345 | return *this; 346 | } 347 | 348 | render_buffer& render_buffer::set_pen(const pen& p) 349 | { 350 | tickit_renderbuffer_setpen(unwrap(), p.unwrap()); 351 | return *this; 352 | } 353 | 354 | render_buffer& render_buffer::add_pen(const pen& p) 355 | { 356 | tickit_renderbuffer_savepen(unwrap()); 357 | tickit_renderbuffer_setpen(unwrap(), p.unwrap()); 358 | return *this; 359 | } 360 | 361 | render_buffer& render_buffer::at(uint line, uint col) 362 | { 363 | tickit_renderbuffer_goto(unwrap(), u2i(line), u2i(col)); 364 | return *this; 365 | } 366 | 367 | 368 | static inline TickitWindowFlags to_tickit(enum window::flags flags) 369 | { 370 | uint r = 0; 371 | if (flags & window::flags::popup) r |= TICKIT_WINDOW_POPUP; 372 | if (flags & window::flags::hidden) r |= TICKIT_WINDOW_HIDDEN; 373 | if (flags & window::flags::lowest) r |= TICKIT_WINDOW_LOWEST; 374 | if (flags & window::flags::root_parent) r |= TICKIT_WINDOW_ROOT_PARENT; 375 | if (flags & window::flags::steal_input) r |= TICKIT_WINDOW_STEAL_INPUT; 376 | return static_cast(r); 377 | } 378 | 379 | window::window(terminal& term) 380 | : window(tickit_window_new_root(term.unwrap()), window_free) 381 | { 382 | trace_pointer("window", "TickitWindow", unwrap(), " +"); 383 | } 384 | 385 | window::window(window& parent, 386 | const rect& r, 387 | enum window::flags flags) 388 | : window(tickit_window_new(parent.unwrap(), 389 | to_tickit(r), 390 | to_tickit(flags)), 391 | window_free) 392 | { 393 | trace_pointer("window", "TickitWindow", unwrap(), " +"); 394 | } 395 | 396 | window window::root() const 397 | { 398 | return window { tickit_window_root(unwrap()), no_delete }; 399 | } 400 | 401 | optional window::parent() const 402 | { 403 | if (TickitWindow* w = tickit_window_parent(unwrap())) 404 | return { window { w, no_delete } }; 405 | return { }; 406 | } 407 | 408 | window& window::expose() 409 | { 410 | tickit_window_expose(unwrap(), nullptr); 411 | return *this; 412 | } 413 | 414 | window& window::flush() 415 | { 416 | tickit_window_flush(unwrap()); 417 | return *this; 418 | } 419 | 420 | window& window::set_position(uint line, uint col) 421 | { 422 | tickit_window_reposition(unwrap(), u2i(line), u2i(col)); 423 | return *this; 424 | } 425 | 426 | window& window::set_geometry(const rect& r) 427 | { 428 | tickit_window_set_geometry(unwrap(), to_tickit(r)); 429 | return *this; 430 | } 431 | 432 | rect window::absolute_geometry() const 433 | { 434 | return from_tickit(tickit_window_get_abs_geometry(unwrap())); 435 | } 436 | 437 | rect window::geometry() const 438 | { 439 | return from_tickit(tickit_window_get_geometry(unwrap())); 440 | } 441 | 442 | window& window::scroll(int downward, int rightward) 443 | { 444 | tickit_window_scroll(unwrap(), downward, rightward); 445 | return *this; 446 | } 447 | 448 | window& window::scroll(int downward, int rightward, enum window::scroll mode) 449 | { 450 | assert(mode == window::scroll::with_children); 451 | tickit_window_scroll_with_children(unwrap(), downward, rightward); 452 | return *this; 453 | } 454 | 455 | window& window::scroll(int downward, int rightward, const rect&r) 456 | { 457 | tickit_window_scrollrect(unwrap(), 458 | to_tickit(r), 459 | downward, 460 | rightward, 461 | tickit_window_get_pen(unwrap())); 462 | return *this; 463 | } 464 | 465 | uint window::top() const { return i2u(tickit_window_top(unwrap())); } 466 | uint window::left() const { return i2u(tickit_window_left(unwrap())); } 467 | uint window::lines() const { return i2u(tickit_window_lines(unwrap())); } 468 | uint window::columns() const { return i2u(tickit_window_cols(unwrap())); } 469 | 470 | 471 | 472 | template 473 | struct emitter_traits { 474 | using tickit_emitter_type = typename T::tickit_type; 475 | using tickit_callback_type = typename emitter_map::type; 476 | 477 | using tickit_bind_function_type = int (* const)(tickit_emitter_type*, 478 | TickitWindowEvent, 479 | TickitBindFlags, 480 | tickit_callback_type, 481 | void*); 482 | 483 | using tickit_unbind_function_type = void (* const)(tickit_emitter_type*, int); 484 | 485 | static const tickit_bind_function_type tickit_bind; 486 | static const tickit_unbind_function_type tickit_unbind; 487 | 488 | static inline int 489 | bind(T& emitter, TickitWindowEvent ev, tickit_callback_type callback, void* handler_info) { 490 | return tickit_bind(emitter.unwrap(), ev, static_cast(0), callback, handler_info); 491 | } 492 | }; 493 | 494 | #define EMITTER_BIND_FUNCS(emitter_name) \ 495 | template <> emitter_traits::tickit_bind_function_type \ 496 | emitter_traits::tickit_bind = tickit_ ## emitter_name ## _bind_event; \ 497 | template <> emitter_traits::tickit_unbind_function_type \ 498 | emitter_traits::tickit_unbind = tickit_ ## emitter_name ## _unbind_event_id 499 | 500 | EMITTER_BIND_FUNCS(window); 501 | 502 | 503 | template 504 | struct event_handler { 505 | using event_type = E; 506 | using emitter_type = typename event_type::emitter_type; 507 | using tickit_emitter_type = typename emitter_type::tickit_type; 508 | using tickit_callback_type = typename emitter_map::type; 509 | using tickit_event_info_type = typename event_info_map::type; 510 | 511 | event_binding_base bind(emitter_type& emitter) { 512 | return { 513 | emitter, 514 | emitter_traits::bind(emitter, event_info_map::code, callback, this) 515 | }; 516 | } 517 | 518 | inline bool run(TickitWindow*, TickitExposeEventInfo* info) { 519 | render_buffer rb { info->rb, render_buffer::no_delete }; 520 | window::expose_event event { 521 | rb, from_tickit(&info->rect) 522 | }; 523 | return handle(event); 524 | } 525 | 526 | inline bool run(TickitWindow*, TickitGeomchangeEventInfo *info) { 527 | window::geometry_change_event event { 528 | from_tickit(&info->oldrect), 529 | from_tickit(&info->rect) 530 | }; 531 | return handle(event); 532 | } 533 | 534 | static int callback(tickit_emitter_type* e, TickitEventFlags flags, void* info, void* user) { 535 | auto handler = reinterpret_cast*>(user); 536 | if (flags & TICKIT_EV_UNBIND) { 537 | delete handler; 538 | return 1; 539 | } else { 540 | return handler->run(e, reinterpret_cast(info)) ? 1 : 0; 541 | } 542 | } 543 | 544 | typename event_type::functor_type handle; 545 | }; 546 | 547 | 548 | template 549 | static inline event_binding_base 550 | bind_event(typename T::emitter_type& emitter, typename T::functor_type f) 551 | { 552 | auto handler = new event_handler{ f }; 553 | return handler->bind(emitter); 554 | } 555 | 556 | window::event_binding window::on_expose(window::expose_event::functor_type f) 557 | { 558 | return bind_event(*this, f); 559 | } 560 | 561 | window::event_binding window::on_geometry_change(window::geometry_change_event::functor_type f) 562 | { 563 | return bind_event(*this, f); 564 | } 565 | 566 | } // namespace ti 567 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | 294 | Copyright (C) 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | , 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | -------------------------------------------------------------------------------- /icetop.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * icetop.cc 3 | * Copyright (C) 2016 Adrian Perez 4 | * 5 | * Distributed under terms of the GPLv2 license. 6 | */ 7 | 8 | #include "util/getenv.hh" 9 | #include "util/ti.hh" 10 | 11 | extern "C" { 12 | #include 13 | } 14 | 15 | #include 16 | 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | 25 | static std::vector s_opt_netnames; 26 | 27 | using host_stats_map = std::unordered_map; 28 | 29 | struct host_info { 30 | public: 31 | unsigned int id; 32 | unsigned int max_jobs; 33 | int load; 34 | bool offline; 35 | std::string name; 36 | std::string platform; 37 | 38 | host_info(unsigned int id_): id(id_), name(), platform() {} 39 | host_info(const host_info&) = delete; 40 | host_info(host_info&&) = default; 41 | 42 | bool operator==(const host_info& rhs) const { return id == rhs.id; } 43 | bool operator!=(const host_info& rhs) const { return id != rhs.id; } 44 | 45 | void update_from_stats_map(const host_stats_map& stats) { 46 | auto item = stats.find("State"); 47 | if (item != stats.end()) { 48 | offline = item->second == "Offline"; 49 | return; 50 | } 51 | 52 | auto new_name = stats.at("Name"); 53 | if (name != new_name) { 54 | name = new_name; 55 | platform = stats.at("Platform"); 56 | } 57 | max_jobs = std::stoul(stats.at("MaxJobs")); 58 | load = std::stoi(stats.at("Load")); 59 | offline = false; 60 | } 61 | }; 62 | 63 | 64 | using host_info_map = std::unordered_map; 65 | 66 | 67 | struct team_info { 68 | public: 69 | const host_info* find(unsigned int id) const { 70 | auto item = host_infos.find(id); 71 | if (item != host_infos.end()) 72 | return &item->second; 73 | return nullptr; 74 | } 75 | 76 | const std::string& name_for(unsigned int id) const { 77 | static const std::string unknown_host_string(""); 78 | auto host = find(id); 79 | return host ? host->name : unknown_host_string; 80 | } 81 | 82 | const unsigned int max_jobs_for(unsigned int id) const { 83 | auto host = find(id); 84 | return host ? host->max_jobs : 0; 85 | } 86 | 87 | host_info* check_host(unsigned int id, const host_stats_map& stats) { 88 | auto item = host_infos.find(id); 89 | if (item == host_infos.end()) { 90 | item = host_infos.emplace(id, host_info(id)).first; 91 | } 92 | item->second.update_from_stats_map(stats); 93 | return &item->second; 94 | } 95 | 96 | private: 97 | host_info_map host_infos; 98 | }; 99 | 100 | 101 | struct icecc_monitor; 102 | 103 | 104 | struct job_info { 105 | enum job_state { 106 | WAITING, 107 | LOCAL, 108 | COMPILING, 109 | FINISHED, 110 | FAILED, 111 | IDLE, 112 | }; 113 | 114 | unsigned int id; 115 | job_state state; 116 | unsigned int client_id; 117 | unsigned int server_id; 118 | std::string filename; 119 | unsigned int real_msec; 120 | unsigned int user_msec; 121 | unsigned int sys_msec; 122 | unsigned int page_faults; 123 | int exit_code; 124 | 125 | const char* state_string() const { 126 | switch (state) { 127 | case WAITING: return "waiting"; 128 | case LOCAL: return "local"; 129 | case COMPILING: return "compiling"; 130 | case FINISHED: return "finished"; 131 | case FAILED: return "failed"; 132 | case IDLE: return "idle"; 133 | default: abort(); 134 | } 135 | } 136 | 137 | const host_info* server() const; 138 | const host_info* client() const; 139 | 140 | private: 141 | icecc_monitor& monitor; 142 | 143 | job_info(icecc_monitor& monitor_, 144 | unsigned int id_, 145 | unsigned int client_id_, 146 | const std::string& filename_) 147 | : id(id_), client_id(client_id_) 148 | , filename(filename_) 149 | , monitor(monitor_) { } 150 | 151 | friend struct icecc_monitor; 152 | }; 153 | 154 | 155 | using job_info_map = std::unordered_map; 156 | 157 | 158 | #define MESSAGE_TYPES(F) \ 159 | F (MON_LOCAL_JOB_BEGIN, MonLocalJobBeginMsg) \ 160 | F (JOB_LOCAL_DONE, JobLocalDoneMsg) \ 161 | F (MON_JOB_BEGIN, MonJobBeginMsg) \ 162 | F (MON_JOB_DONE, MonJobDoneMsg) \ 163 | F (MON_GET_CS, MonGetCSMsg) \ 164 | F (MON_STATS, MonStatsMsg) 165 | 166 | 167 | struct icecc_monitor { 168 | public: 169 | using host_updated_func = std::function; 170 | using job_updated_func = std::function; 171 | 172 | enum monitor_state { 173 | OFFLINE, 174 | ONLINE, 175 | }; 176 | 177 | icecc_monitor(host_updated_func on_host_updated_ = nullptr, 178 | job_updated_func on_job_updated_ = nullptr) 179 | : on_host_updated(on_host_updated_) 180 | , on_job_updated(on_job_updated_) 181 | , state(OFFLINE) 182 | { } 183 | 184 | 185 | 186 | coroutine void check_scheduler(bool deleteit=false) { 187 | if (auto env_scheduler = util::getenv("USE_SCHEDULER")) { 188 | s_opt_netnames.push_back(env_scheduler.value()); 189 | } 190 | if (auto env_scheduler = util::getenv("ICECREAM_SCHEDULER")) { 191 | s_opt_netnames.push_back(env_scheduler.value()); 192 | } 193 | if (!network_name.empty()) { 194 | s_opt_netnames.push_back(network_name); 195 | } else { 196 | s_opt_netnames.push_back("ICECREAM"); 197 | } 198 | 199 | if (deleteit) { 200 | scheduler = nullptr; 201 | } 202 | 203 | static constexpr auto max_wait_seconds = 3; 204 | while (!scheduler) { 205 | for (auto& name: s_opt_netnames) { 206 | auto discover = std::make_unique(name, max_wait_seconds); 207 | scheduler.reset(discover->try_get_scheduler()); 208 | while (!scheduler && !discover->timed_out()) { 209 | if (discover->listen_fd() != -1) { 210 | if (fdin(discover->listen_fd(), now() + 100) && (errno != ETIMEDOUT)) { 211 | perror("fdin"); 212 | exit(EXIT_FAILURE); 213 | } 214 | } else { 215 | msleep(now() + 50); 216 | } 217 | scheduler.reset(discover->try_get_scheduler()); 218 | } 219 | fdclean(discover->listen_fd()); 220 | if (scheduler) { 221 | state = ONLINE; 222 | network_name = discover->networkName(); 223 | scheduler_name = discover->schedulerName(); 224 | scheduler->setBulkTransfer(); 225 | return; 226 | } 227 | } 228 | } 229 | } 230 | 231 | coroutine void listen(int64_t deadline = -1) { 232 | if (!scheduler->send_msg(MonLoginMsg())) { 233 | // TODO: Recheck for the scheduler 234 | return; 235 | } 236 | while (true) { 237 | if (fdin(scheduler->fd, deadline)) { 238 | return; 239 | } 240 | while (!scheduler->read_a_bit() || scheduler->has_msg()) { 241 | if (!_handle_activity()) { 242 | fdclean(scheduler->fd); 243 | break; 244 | } 245 | } 246 | } 247 | } 248 | 249 | const host_info* find_host(unsigned int id) const { return team.find(id); } 250 | 251 | std::string network_name; 252 | std::string scheduler_name; 253 | std::unique_ptr scheduler; 254 | 255 | private: 256 | host_updated_func on_host_updated; 257 | job_updated_func on_job_updated; 258 | monitor_state state; 259 | team_info team; 260 | job_info_map jobs; 261 | 262 | bool _handle_activity(); 263 | 264 | #define MESSAGE_HANDLER(typecode, msgtype, msgvarname) \ 265 | void icecc_monitor::_handle_ ## typecode(const msgtype & msgvarname) 266 | 267 | #define DECLARE_MESSAGE_HANDLER(typecode, msgtype) \ 268 | void _handle_ ## typecode(const msgtype & m); 269 | 270 | MESSAGE_TYPES (DECLARE_MESSAGE_HANDLER) 271 | 272 | #undef DECLARE_MESSAGE_HANDLER 273 | }; 274 | 275 | 276 | const host_info* job_info::server() const { return monitor.find_host(server_id); } 277 | const host_info* job_info::client() const { return monitor.find_host(client_id); } 278 | 279 | 280 | bool icecc_monitor::_handle_activity() 281 | { 282 | std::unique_ptr m(scheduler->get_msg()); 283 | if (!m) { 284 | check_scheduler(); 285 | state = OFFLINE; 286 | return false; 287 | } 288 | 289 | #define SWITCH_MESSAGE_TYPE(typecode, msgtype) \ 290 | case M_ ## typecode: { \ 291 | msgtype * mm = dynamic_cast(m.get()); \ 292 | if (mm) _handle_ ## typecode(*mm); \ 293 | } break; 294 | 295 | switch (m->type) { 296 | MESSAGE_TYPES (SWITCH_MESSAGE_TYPE) 297 | case M_END: 298 | check_scheduler(true); 299 | // fall-through 300 | default: 301 | break; 302 | } 303 | 304 | #undef SWITCH_MESSAGE_TYPE 305 | 306 | return true; 307 | } 308 | 309 | static host_stats_map 310 | parse_stats(const std::string& input) 311 | { 312 | std::stringstream stream(input); 313 | std::string key, value; 314 | host_stats_map stats; 315 | while (std::getline(stream, key, ':') && std::getline(stream, value)) { 316 | stats.emplace(key, value); 317 | } 318 | return stats; 319 | } 320 | 321 | MESSAGE_HANDLER (MON_STATS, MonStatsMsg, m) 322 | { 323 | auto stats = parse_stats(m.statmsg); 324 | auto host = team.check_host(m.hostid, stats); 325 | if (on_host_updated) on_host_updated(*host); 326 | } 327 | 328 | MESSAGE_HANDLER (MON_LOCAL_JOB_BEGIN, MonLocalJobBeginMsg, m) 329 | { 330 | job_info& job = jobs.emplace(m.job_id, job_info(*this, 331 | m.job_id, 332 | m.hostid, 333 | m.file)).first->second; 334 | job.state = job_info::LOCAL; 335 | if (on_job_updated) on_job_updated(job); 336 | } 337 | 338 | MESSAGE_HANDLER (JOB_LOCAL_DONE, JobLocalDoneMsg, m) 339 | { 340 | auto item = jobs.find(m.job_id); 341 | if (item == jobs.end()) { 342 | return; // Monitoring started after the job was created. 343 | } 344 | job_info& job = item->second; 345 | job.state = job_info::FINISHED; 346 | if (on_job_updated) on_job_updated(job); 347 | } 348 | 349 | MESSAGE_HANDLER (MON_GET_CS, MonGetCSMsg, m) 350 | { 351 | job_info& job = jobs.emplace(m.job_id, job_info(*this, 352 | m.job_id, 353 | m.clientid, 354 | m.filename)).first->second; 355 | job.state = job_info::WAITING; 356 | if (on_job_updated) on_job_updated(job); 357 | } 358 | 359 | MESSAGE_HANDLER (MON_JOB_BEGIN, MonJobBeginMsg, m) 360 | { 361 | auto item = jobs.find(m.job_id); 362 | if (item == jobs.end()) { 363 | return; // Monitoring started after the job was created. 364 | } 365 | job_info& job = item->second; 366 | job.server_id = m.hostid; 367 | job.state = job_info::COMPILING; 368 | if (on_job_updated) on_job_updated(job); 369 | } 370 | 371 | MESSAGE_HANDLER (MON_JOB_DONE, MonJobDoneMsg, m) 372 | { 373 | auto item = jobs.find(m.job_id); 374 | if (item == jobs.end()) { 375 | return; // Monitoring started after the job was created. 376 | } 377 | 378 | job_info& job = item->second; 379 | 380 | if (m.exitcode) { 381 | job.state = job_info::FAILED; 382 | job.exit_code = m.exitcode; 383 | } else { 384 | job.state = job_info::FINISHED; 385 | job.real_msec = m.real_msec; 386 | job.user_msec = m.user_msec; 387 | job.sys_msec = m.sys_msec; 388 | job.page_faults = m.pfaults; 389 | } 390 | 391 | if (on_job_updated) on_job_updated(job); 392 | 393 | jobs.erase(item); 394 | } 395 | 396 | 397 | struct host_layout { 398 | static ti::pen line_pens[2]; 399 | static ti::pen busy_pen, warn_pen, okay_pen, host_pen; 400 | 401 | host_layout(ti::window&& w, const host_info& host) 402 | : window(std::move(w)), hostname(host.name) 403 | , platform(host.platform) 404 | , filename() 405 | , state_string("idle") 406 | { 407 | window.on_expose([this](ti::window::expose_event& event) { 408 | on_expose(event); 409 | return true; 410 | }); 411 | } 412 | 413 | host_layout(host_layout&&) = default; 414 | 415 | unsigned position() const { 416 | return window.top(); 417 | } 418 | 419 | void move_up() { 420 | window.set_position(position() - 1, window.left()); 421 | window.root().expose(); 422 | } 423 | 424 | void on_expose(ti::window::expose_event& ev) { 425 | ev.render.set_pen(line_pens[position() % 2]).clear(ev.area); 426 | ev.render.at(0, 1) << platform; 427 | ev.render.at(0, 9) << host_pen << hostname; 428 | ev.render.at(0, 30).restore() << filename; 429 | if (window.columns() >= (11 + origin.size())) { 430 | // TODO: Do something better than erasing the line all over. 431 | auto col = window.columns() - 12 - origin.size(); 432 | ev.render.clear(0, col, window.columns() - col); 433 | ev.render.at(0, ++col) << host_pen << origin; 434 | col = window.columns() - 11; 435 | ev.render.clear(0, col, window.columns() - col).restore(); 436 | if (auto pen = state_pen()) ev.render << *pen; 437 | ev.render.at(0, ++col) << state_string; 438 | ev.render.restore(); 439 | } 440 | } 441 | 442 | void host_info_updated(const host_info& host) { 443 | hostname = host.name; 444 | platform = host.platform; 445 | window.expose(); 446 | } 447 | 448 | void job_info_updated(const job_info& job) { 449 | if (job.state == job_info::WAITING) 450 | return; 451 | if (job.server()) { 452 | origin = job.client()->name; 453 | } else { 454 | origin = ""; 455 | } 456 | state = job.state; 457 | state_string = job.state_string(); 458 | filename = job.filename; 459 | window.expose(); 460 | } 461 | 462 | ti::pen* state_pen() const { 463 | switch (state) { 464 | case job_info::FAILED: 465 | return &warn_pen; 466 | case job_info::FINISHED: 467 | return &okay_pen; 468 | case job_info::LOCAL: 469 | case job_info::COMPILING: 470 | return &busy_pen; 471 | default: 472 | return nullptr; 473 | } 474 | } 475 | 476 | ti::window window; 477 | std::string hostname; 478 | std::string platform; 479 | std::string filename; 480 | std::string origin; 481 | const char *state_string; 482 | job_info::job_state state; 483 | }; 484 | 485 | ti::pen host_layout::line_pens[2] = { 486 | { ti::pen::bg() }, 487 | { ti::pen::bg(234) }, 488 | }; 489 | ti::pen host_layout::busy_pen = { ti::pen::fg(3), ti::pen::bold }; 490 | ti::pen host_layout::okay_pen = { ti::pen::fg(2), ti::pen::bold }; 491 | ti::pen host_layout::warn_pen = { ti::pen::fg(1), ti::pen::bold }; 492 | ti::pen host_layout::host_pen = { ti::pen::fg(7), ti::pen::bold }; 493 | 494 | 495 | struct screen_layout { 496 | static ti::pen status_pen; 497 | 498 | screen_layout(ti::terminal& term) 499 | : root(ti::window(term)) 500 | , status(root, { root.lines() - 1, 0, 1, root.columns() }) 501 | { 502 | status.on_expose([this](ti::window::expose_event& ev) { 503 | char timestring[15]; 504 | struct tm *t = localtime(&statustime); 505 | strftime(timestring, sizeof(timestring), "[%H:%M:%S] ", t); 506 | ev.render.set_pen(status_pen).clear().at(0, 1) << timestring << statusline; 507 | return true; 508 | }); 509 | 510 | root.on_expose([](ti::window::expose_event& ev) { 511 | // Just clear the backgrond. Avoids ghost text after certain 512 | // kinds of geometry changes. 513 | ev.render.clear(ev.area); 514 | return true; 515 | }); 516 | 517 | root.on_geometry_change([this, &term](ti::window::geometry_change_event& ev) { 518 | auto geom = status.geometry(); 519 | assert(geom.top > 0); 520 | geom.top--; 521 | status.set_geometry(geom); 522 | term.clear(); 523 | root.expose(); 524 | return true; 525 | }); 526 | } 527 | 528 | void host_info_updated(const host_info& host) { 529 | if (host.offline) { 530 | set_status("Host " + host.name + " went offline"); 531 | auto index_item = hostid_to_index.find(host.id); 532 | if (index_item == hostid_to_index.end()) { 533 | // No line for it: do nothing. 534 | return; 535 | } 536 | auto index = index_item->second; 537 | host_layouts.erase(host_layouts.begin() + index); 538 | for (auto it = host_layouts.begin() + index; it != host_layouts.end(); ++it) { 539 | it->get()->move_up(); 540 | } 541 | hostid_to_index.erase(index_item); 542 | } else { 543 | auto index_item = hostid_to_index.find(host.id); 544 | if (index_item == hostid_to_index.end()) { 545 | set_status("Host " + host.name + " (" + host.platform + ") came online"); 546 | unsigned index = host_layouts.size(); // Add it at the end. 547 | ti::window w { root, { index, 0, 1, root.columns() } }; 548 | host_layouts.emplace_back(std::make_unique(std::move(w), host)); 549 | hostid_to_index[host.id] = index; 550 | } else { 551 | set_status("Host " + host.name + " (" + host.platform + ") is still online"); 552 | host_layouts[index_item->second]->host_info_updated(host); 553 | } 554 | } 555 | } 556 | 557 | void job_info_updated(const job_info& job) { 558 | auto index_item = hostid_to_index.find(job.server() ? job.server_id : job.client_id); 559 | if (index_item == hostid_to_index.end()) 560 | return; 561 | host_layouts[index_item->second]->job_info_updated(job); 562 | } 563 | 564 | void flush() { 565 | root.flush(); 566 | } 567 | 568 | void set_status(const std::string& s) { 569 | statustime = time(nullptr); 570 | statusline = s; 571 | status.expose(); 572 | } 573 | 574 | ti::window root; 575 | ti::window status; 576 | 577 | std::unordered_map hostid_to_index; 578 | std::vector> host_layouts; 579 | std::string statusline; 580 | time_t statustime; 581 | }; 582 | 583 | 584 | ti::pen screen_layout::status_pen = { ti::pen::bg(4) }; 585 | 586 | 587 | #include 588 | 589 | static bool running = true; 590 | static void handle_sigint(int) 591 | { 592 | running = false; 593 | } 594 | 595 | 596 | int main(int argc, char **argv) 597 | { 598 | ti::terminal term { }; 599 | term.wait_ready(); 600 | 601 | int opt; 602 | while ((opt = getopt(argc, argv, "hn:")) != -1) { 603 | switch (opt) { 604 | case 'n': 605 | s_opt_netnames.emplace_back(optarg); 606 | break; 607 | default: 608 | term << "Usage: " << argv[0] << " [-h] [-n netname]\n"; 609 | return (opt == 'h') ? EXIT_SUCCESS : EXIT_FAILURE; 610 | } 611 | } 612 | 613 | screen_layout layout { term }; 614 | 615 | icecc_monitor monitor { 616 | [&layout](const host_info& host) { 617 | layout.host_info_updated(host); 618 | }, 619 | [&layout](const job_info& job) { 620 | layout.job_info_updated(job); 621 | } 622 | }; 623 | 624 | term << "Waiting for scheduler...\n"; 625 | go(monitor.check_scheduler()); 626 | while (!monitor.scheduler) msleep(now() + 100); 627 | 628 | go(monitor.listen()); 629 | 630 | term.set(ti::terminal::altscreen).clear(); 631 | 632 | signal(SIGINT, handle_sigint); 633 | while (running) { 634 | layout.flush(); 635 | term.wait_input(10); 636 | msleep(40); 637 | } 638 | } 639 | --------------------------------------------------------------------------------