Add initial bits of the termex testing framework
[centerim5.git] / cppconsui / CoreManager.cpp
blob7b29357d70b8991a9f1e6bc3c295f328cd67c7b7
1 // Copyright (C) 2007 Mark Pustjens <pustjens@dds.nl>
2 // Copyright (C) 2009-2015 Petr Pavlu <setup@dagobah.cz>
3 //
4 // This file is part of CenterIM.
5 //
6 // CenterIM 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.
11 // CenterIM 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.
16 // You should have received a copy of the GNU General Public License
17 // along with this program. If not, see <http://www.gnu.org/licenses/>.
19 #include "CoreManager.h"
21 #include "ColorScheme.h"
22 #include "KeyConfig.h"
24 #include "gettext.h"
25 #include <cassert>
26 #include <cerrno>
27 #include <cstdio>
28 #include <cstring>
29 #include <langinfo.h>
30 #include <signal.h>
31 #include <sys/ioctl.h>
32 #include <termios.h>
33 #include <time.h>
34 #include <unistd.h>
36 #define ICONV_NONE reinterpret_cast<iconv_t>(-1)
38 namespace CppConsUI {
40 int CoreManager::initializeInput(Error &error)
42 assert(tk_ == nullptr);
43 assert(iconv_desc_ == ICONV_NONE);
45 // Get the current character encoding.
46 const char *codeset = nl_langinfo(CODESET);
48 // Initialize libtermkey.
49 tk_ = termkey_new(STDIN_FILENO, TERMKEY_FLAG_NOTERMIOS);
50 if (tk_ == nullptr) {
51 error = Error(
52 ERROR_LIBTERMKEY_INITIALIZATION, _("Libtermkey initialization failed."));
53 goto error_cleanup;
55 termkey_set_canonflags(tk_, TERMKEY_CANON_DELBS);
57 // If the codeset differs from UTF-8, setup iconv for conversion.
58 if (std::strcmp(codeset, "UTF-8") != 0) {
59 iconv_desc_ = iconv_open("UTF-8", codeset);
60 if (iconv_desc_ == ICONV_NONE) {
61 error = Error(ERROR_ICONV_INITIALIZATION);
62 error.setFormattedString(
63 _("Iconv initialization failed. Cannot create a conversion descriptor "
64 "from %s to UTF-8."),
65 codeset);
66 goto error_cleanup;
70 return 0;
72 error_cleanup:
73 if (iconv_desc_ != ICONV_NONE) {
74 int res = iconv_close(iconv_desc_);
75 assert(res == 0);
76 iconv_desc_ = ICONV_NONE;
79 if (tk_ != nullptr) {
80 termkey_destroy(tk_);
81 tk_ = nullptr;
84 return error.getCode();
87 int CoreManager::finalizeInput(Error & /*error*/)
89 assert(tk_ != nullptr);
91 if (iconv_desc_ != ICONV_NONE) {
92 int res = iconv_close(iconv_desc_);
93 // Closing iconv can fail only if the conversion descriptor is invalid but
94 // that should never happen in CppConsUI.
95 assert(res == 0);
96 iconv_desc_ = ICONV_NONE;
99 termkey_destroy(tk_);
100 tk_ = nullptr;
102 return 0;
105 int CoreManager::initializeOutput(Error &error)
107 return Curses::initScreen(error);
110 int CoreManager::finalizeOutput(Error &error)
112 // Delete all windows. This must be done very carefully because deleting one
113 // window can lead to deletion of another one. It is however required for each
114 // window to deregister itself from CoreManager when it is deleted (and not to
115 // register any new windows).
116 while (!windows_.empty())
117 delete windows_.front();
119 return Curses::finalizeScreen(error);
122 int CoreManager::processStandardInput(int *wait, Error &error)
124 assert(wait != nullptr);
125 *wait = -1;
127 termkey_advisereadable(tk_);
129 TermKeyKey key;
130 TermKeyResult ret;
131 while ((ret = termkey_getkey(tk_, &key)) == TERMKEY_RES_KEY) {
132 if (key.type == TERMKEY_TYPE_UNICODE && iconv_desc_ != ICONV_NONE) {
133 std::size_t inbytesleft, outbytesleft;
134 char *inbuf, *outbuf;
135 std::size_t res;
136 char utf8[sizeof(key.utf8) - 1];
138 // Convert data from the user charset to UTF-8.
139 inbuf = key.utf8;
140 inbytesleft = strlen(key.utf8);
141 outbuf = utf8;
142 outbytesleft = sizeof(utf8);
143 res = iconv(iconv_desc_, &inbuf, &inbytesleft, &outbuf, &outbytesleft);
144 if (res != static_cast<std::size_t>(-1) && inbytesleft != 0) {
145 // No error occured but not all bytes have been converted.
146 errno = EINVAL;
147 res = static_cast<std::size_t>(-1);
149 if (res == static_cast<std::size_t>(-1)) {
150 error = Error(ERROR_INPUT_CONVERSION);
151 error.setFormattedString(
152 _("Error converting input to UTF-8 (%s)."), std::strerror(errno));
153 return error.getCode();
156 std::size_t outbytes = sizeof(utf8) - outbytesleft;
157 std::memcpy(key.utf8, utf8, outbytes);
158 key.utf8[outbytes] = '\0';
160 key.code.codepoint = UTF8::getUniChar(key.utf8);
163 processInput(key);
165 if (ret == TERMKEY_RES_AGAIN) {
166 *wait = termkey_get_waittime(tk_);
167 assert(*wait >= 0);
170 return 0;
173 int CoreManager::processStandardInputTimeout(Error & /*error*/)
175 TermKeyKey key;
176 if (termkey_getkey_force(tk_, &key) == TERMKEY_RES_KEY) {
177 // This should happen only for Esc key, so no need to do the locale -> UTF-8
178 // conversion.
179 processInput(key);
181 return 0;
184 int CoreManager::resize(Error &error)
186 struct winsize size;
187 if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &size) >= 0) {
188 if (Curses::resizeTerm(size.ws_col, size.ws_row, error) != 0)
189 return error.getCode();
192 // Update area.
193 updateArea();
195 // Make sure everything is redrawn from the scratch.
196 redraw(true);
198 // Trigger the resize event.
199 onScreenResized();
201 return 0;
204 int CoreManager::draw(Error &error)
206 if (pending_redraw_ == REDRAW_NONE)
207 return 0;
209 #if defined(DEBUG) && 0
210 struct timespec ts;
211 clock_gettime(CLOCK_MONOTONIC, &ts);
212 #endif // DEBUG
214 if (pending_redraw_ == REDRAW_FROM_SCRATCH)
215 DRAW(Curses::clear(error));
216 else
217 DRAW(Curses::erase(error));
219 // Non-focusable -> normal -> top.
220 for (Window *window : windows_)
221 if (window->isVisible() && window->getType() == Window::TYPE_NON_FOCUSABLE)
222 DRAW(drawWindow(*window, error));
224 for (Window *window : windows_)
225 if (window->isVisible() && window->getType() == Window::TYPE_NORMAL)
226 DRAW(drawWindow(*window, error));
228 for (Window *window : windows_)
229 if (window->isVisible() && window->getType() == Window::TYPE_TOP)
230 DRAW(drawWindow(*window, error));
232 // Copy virtual ncurses screen to the physical screen.
233 DRAW(Curses::refresh(error));
235 #if defined(DEBUG) && 0
236 struct timespec ts2;
237 clock_gettime(CLOCK_MONOTONIC, &ts2);
238 unsigned long tdiff =
239 (ts2.tv_sec - ts.tv_sec) * 1000000 + ts2.tv_nsec / 1000 - ts.tv_nsec / 1000;
241 char message[sizeof("redraw: time=us") + PRINTF_WIDTH(unsigned long)];
242 sprintf(message, "redraw: time=%luus", tdiff);
243 logDebug(message);
244 #endif // DEBUG
246 pending_redraw_ = REDRAW_NONE;
248 return 0;
251 void CoreManager::registerWindow(Window &window)
253 assert(!window.isVisible());
255 Windows::iterator i = findWindow(window);
256 assert(i == windows_.end());
258 windows_.push_front(&window);
259 updateWindowArea(window);
262 void CoreManager::removeWindow(Window &window)
264 Windows::iterator i = findWindow(window);
265 assert(i != windows_.end());
267 windows_.erase(i);
269 focusWindow();
270 redraw();
273 void CoreManager::hideWindow(Window &window)
275 Windows::iterator i = findWindow(window);
276 assert(i != windows_.end());
278 focusWindow();
279 redraw();
282 void CoreManager::topWindow(Window &window)
284 Windows::iterator i = findWindow(window);
285 assert(i != windows_.end());
287 windows_.erase(i);
288 windows_.push_back(&window);
290 focusWindow();
291 redraw();
294 Window *CoreManager::getTopWindow()
296 return dynamic_cast<Window *>(input_child_);
299 void CoreManager::logDebug(const char *message)
301 interface_.logDebug(message);
304 void CoreManager::redraw(bool from_scratch)
306 if (pending_redraw_ == REDRAW_NONE)
307 interface_.redraw();
309 if (pending_redraw_ == REDRAW_FROM_SCRATCH)
310 return;
311 pending_redraw_ = from_scratch ? REDRAW_FROM_SCRATCH : REDRAW_NORMAL;
314 bool CoreManager::isRedrawPending() const
316 return pending_redraw_ != REDRAW_NONE;
319 void CoreManager::onScreenResized()
321 // Signal the resize event.
322 signal_resize();
324 // Propagate the resize event to all windows.
325 for (Window *window : windows_)
326 window->onScreenResized();
329 void CoreManager::onWindowMoveResize(
330 Window &activator, const Rect & /*oldsize*/, const Rect & /*newsize*/)
332 updateWindowArea(activator);
335 void CoreManager::onWindowWishSizeChange(
336 Window &activator, const Size &oldsize, const Size &newsize)
338 if ((activator.getWidth() != Widget::AUTOSIZE ||
339 oldsize.getWidth() == newsize.getWidth()) &&
340 (activator.getHeight() != Widget::AUTOSIZE ||
341 oldsize.getHeight() == newsize.getHeight()))
342 return;
344 updateWindowArea(activator);
347 CoreManager::CoreManager(AppInterface &set_interface)
348 : top_input_processor_(nullptr), tk_(nullptr), iconv_desc_(ICONV_NONE),
349 pending_redraw_(REDRAW_NONE)
351 // Validate the passed interface.
352 assert(!set_interface.redraw.empty());
353 assert(!set_interface.logDebug.empty());
355 interface_ = set_interface;
357 declareBindables();
360 bool CoreManager::processInput(const TermKeyKey &key)
362 if (top_input_processor_ && top_input_processor_->processInput(key))
363 return true;
365 return InputProcessor::processInput(key);
368 void CoreManager::updateArea()
370 for (Window *window : windows_)
371 updateWindowArea(*window);
374 void CoreManager::updateWindowArea(Window &window)
376 int screen_width = Curses::getWidth();
377 int screen_height = Curses::getHeight();
379 int window_x = window.getLeft();
380 int window_y = window.getTop();
382 // Calculate the real width.
383 int window_width = window.getWidth();
384 if (window_width == Widget::AUTOSIZE) {
385 window_width = window.getWishWidth();
386 if (window_width == Widget::AUTOSIZE)
387 window_width = screen_width - window_x;
389 if (window_width < 0)
390 window_width = 0;
392 // Calculate the real height.
393 int window_height = window.getHeight();
394 if (window_height == Widget::AUTOSIZE) {
395 window_height = window.getWishHeight();
396 if (window_height == Widget::AUTOSIZE)
397 window_height = screen_height - window_y;
399 if (window_height < 0)
400 window_height = 0;
402 window.setRealPosition(window_x, window_y);
403 window.setRealSize(window_width, window_height);
406 int CoreManager::drawWindow(Window &window, Error &error)
408 int screen_width = Curses::getWidth();
409 int screen_height = Curses::getHeight();
411 int window_x = window.getRealLeft();
412 int window_y = window.getRealTop();
413 int window_width = window.getRealWidth();
414 int window_height = window.getRealHeight();
415 int window_x2 = window_x + window_width;
416 int window_y2 = window_y + window_height;
418 int window_view_x = 0;
419 int window_view_y = 0;
420 int window_view_width = window_width;
421 int window_view_height = window_height;
423 // Calculate a viewport for the window.
424 if (window_x < 0) {
425 window_view_x = -window_x;
426 window_x += window_view_x;
427 if (window_view_x > window_width)
428 window_view_x = window_width;
429 window_view_width -= window_view_x;
431 if (window_y < 0) {
432 window_view_y = -window_y;
433 window_y += window_view_y;
434 if (window_view_y > window_height)
435 window_view_y = window_height;
436 window_view_height -= window_view_y;
439 if (window_x2 > screen_width) {
440 window_view_width -= window_x2 - screen_width;
441 if (window_view_width < 0)
442 window_view_width = 0;
444 if (window_y2 > screen_height) {
445 window_view_height -= window_y2 - screen_height;
446 if (window_view_height < 0)
447 window_view_height = 0;
450 Curses::ViewPort window_area(window_x, window_y, window_view_x, window_view_y,
451 window_view_width, window_view_height);
452 return window.draw(window_area, error);
455 CoreManager::Windows::iterator CoreManager::findWindow(Window &window)
457 return std::find(windows_.begin(), windows_.end(), &window);
460 void CoreManager::focusWindow()
462 // Check if there are any windows left.
463 Window *win = nullptr;
464 Windows::reverse_iterator i;
466 // Try to find a top window first.
467 for (i = windows_.rbegin(); i != windows_.rend(); ++i)
468 if ((*i)->isVisible() && (*i)->getType() == Window::TYPE_TOP) {
469 win = *i;
470 break;
473 // Normal windows.
474 if (win == nullptr)
475 for (i = windows_.rbegin(); i != windows_.rend(); ++i)
476 if ((*i)->isVisible() && (*i)->getType() == Window::TYPE_NORMAL) {
477 win = *i;
478 break;
481 Window *focus = dynamic_cast<Window *>(getInputChild());
482 if (win == nullptr || win != focus) {
483 // Take the focus from the old window with the focus.
484 if (focus != nullptr) {
485 focus->ungrabFocus();
486 clearInputChild();
489 // Give the focus to the window.
490 if (win != nullptr) {
491 setInputChild(*win);
492 win->restoreFocus();
494 signal_top_window_change();
498 void CoreManager::redrawScreen()
500 // Make sure everything is redrawn from the scratch.
501 redraw(true);
504 void CoreManager::declareBindables()
506 declareBindable("coremanager", "redraw-screen",
507 sigc::mem_fun(this, &CoreManager::redrawScreen),
508 InputProcessor::BINDABLE_OVERRIDE);
511 } // namespace CppConsUI
513 // vim: set tabstop=2 shiftwidth=2 textwidth=80 expandtab: