Fix build on systems that have a separate libintl library
[centerim5.git] / cppconsui / CoreManager.cpp
blobb82d39a8466da8b8db80721f1791f3a1d8301205
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 CenterIM. 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 namespace {
42 // Workaround for implementations that declare the inbuf parameter of the
43 // iconv() function as "const char **".
44 template <class T> class sloppy {
47 // Convert between "T **" and "const T **".
48 template <class T> class sloppy<T **> {
49 public:
50 sloppy(T **t) : t_(t) {}
51 sloppy(const T **t) : t_(const_cast<T **>(t)) {}
53 operator T **() const { return t_; }
54 operator const T **() const { return const_cast<const T **>(t_); }
56 private:
57 T **t_;
60 } // anonymous namespace
62 int CoreManager::initializeInput(Error &error)
64 assert(tk_ == nullptr);
65 assert(iconv_desc_ == ICONV_NONE);
67 // Get the current character encoding.
68 const char *codeset = nl_langinfo(CODESET);
70 // Initialize libtermkey.
71 tk_ = termkey_new(STDIN_FILENO, TERMKEY_FLAG_NOTERMIOS);
72 if (tk_ == nullptr) {
73 error = Error(
74 ERROR_LIBTERMKEY_INITIALIZATION, _("Libtermkey initialization failed."));
75 goto error_cleanup;
77 termkey_set_canonflags(tk_, TERMKEY_CANON_DELBS);
79 // If the codeset differs from UTF-8, setup iconv for conversion.
80 if (std::strcmp(codeset, "UTF-8") != 0) {
81 iconv_desc_ = iconv_open("UTF-8", codeset);
82 if (iconv_desc_ == ICONV_NONE) {
83 error = Error(ERROR_ICONV_INITIALIZATION);
84 error.setFormattedString(
85 _("Iconv initialization failed. Cannot create a conversion descriptor "
86 "from %s to UTF-8."),
87 codeset);
88 goto error_cleanup;
92 return 0;
94 error_cleanup:
95 if (iconv_desc_ != ICONV_NONE) {
96 int res = iconv_close(iconv_desc_);
97 assert(res == 0);
98 iconv_desc_ = ICONV_NONE;
101 if (tk_ != nullptr) {
102 termkey_destroy(tk_);
103 tk_ = nullptr;
106 return error.getCode();
109 int CoreManager::finalizeInput(Error & /*error*/)
111 assert(tk_ != nullptr);
113 if (iconv_desc_ != ICONV_NONE) {
114 int res = iconv_close(iconv_desc_);
115 // Closing iconv can fail only if the conversion descriptor is invalid but
116 // that should never happen in CppConsUI.
117 assert(res == 0);
118 iconv_desc_ = ICONV_NONE;
121 termkey_destroy(tk_);
122 tk_ = nullptr;
124 return 0;
127 int CoreManager::initializeOutput(Error &error)
129 return Curses::initScreen(error);
132 int CoreManager::finalizeOutput(Error &error)
134 // Delete all windows. This must be done very carefully because deleting one
135 // window can lead to deletion of another one. It is however required for each
136 // window to deregister itself from CoreManager when it is deleted (and not to
137 // register any new windows).
138 while (!windows_.empty())
139 delete windows_.front();
141 return Curses::finalizeScreen(error);
144 int CoreManager::processStandardInput(int *wait, Error &error)
146 assert(wait != nullptr);
147 *wait = -1;
149 termkey_advisereadable(tk_);
151 TermKeyKey key;
152 TermKeyResult ret;
153 while ((ret = termkey_getkey(tk_, &key)) == TERMKEY_RES_KEY) {
154 if (key.type == TERMKEY_TYPE_UNICODE && iconv_desc_ != ICONV_NONE) {
155 std::size_t inbytesleft, outbytesleft;
156 char *inbuf, *outbuf;
157 std::size_t res;
158 char utf8[sizeof(key.utf8) - 1];
160 // Convert data from the user charset to UTF-8.
161 inbuf = key.utf8;
162 inbytesleft = strlen(key.utf8);
163 outbuf = utf8;
164 outbytesleft = sizeof(utf8);
165 res = iconv(iconv_desc_, sloppy<char **>(&inbuf), &inbytesleft, &outbuf,
166 &outbytesleft);
167 if (res != static_cast<std::size_t>(-1) && inbytesleft != 0) {
168 // No error occured but not all bytes have been converted.
169 errno = EINVAL;
170 res = static_cast<std::size_t>(-1);
172 if (res == static_cast<std::size_t>(-1)) {
173 error = Error(ERROR_INPUT_CONVERSION);
174 error.setFormattedString(
175 _("Error converting input to UTF-8 (%s)."), std::strerror(errno));
176 return error.getCode();
179 std::size_t outbytes = sizeof(utf8) - outbytesleft;
180 std::memcpy(key.utf8, utf8, outbytes);
181 key.utf8[outbytes] = '\0';
183 key.code.codepoint = UTF8::getUniChar(key.utf8);
186 processInput(key);
188 if (ret == TERMKEY_RES_AGAIN) {
189 *wait = termkey_get_waittime(tk_);
190 assert(*wait >= 0);
193 return 0;
196 int CoreManager::processStandardInputTimeout(Error & /*error*/)
198 TermKeyKey key;
199 if (termkey_getkey_force(tk_, &key) == TERMKEY_RES_KEY) {
200 // This should happen only for Esc key, so no need to do the locale -> UTF-8
201 // conversion.
202 processInput(key);
204 return 0;
207 int CoreManager::resize(Error &error)
209 struct winsize size;
210 if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &size) >= 0) {
211 if (Curses::resizeTerm(size.ws_col, size.ws_row, error) != 0)
212 return error.getCode();
215 // Update area.
216 updateArea();
218 // Make sure everything is redrawn from the scratch.
219 redraw(true);
221 // Trigger the resize event.
222 onScreenResized();
224 return 0;
227 int CoreManager::draw(Error &error)
229 if (pending_redraw_ == REDRAW_NONE)
230 return 0;
232 #if defined(DEBUG) && 0
233 struct timespec ts;
234 clock_gettime(CLOCK_MONOTONIC, &ts);
235 #endif // DEBUG
237 if (pending_redraw_ == REDRAW_FROM_SCRATCH)
238 DRAW(Curses::clear(error));
239 else
240 DRAW(Curses::erase(error));
242 // Non-focusable -> normal -> top.
243 for (Window *window : windows_)
244 if (window->isVisible() && window->getType() == Window::TYPE_NON_FOCUSABLE)
245 DRAW(drawWindow(*window, error));
247 for (Window *window : windows_)
248 if (window->isVisible() && window->getType() == Window::TYPE_NORMAL)
249 DRAW(drawWindow(*window, error));
251 for (Window *window : windows_)
252 if (window->isVisible() && window->getType() == Window::TYPE_TOP)
253 DRAW(drawWindow(*window, error));
255 // Copy virtual ncurses screen to the physical screen.
256 DRAW(Curses::refresh(error));
258 #if defined(DEBUG) && 0
259 struct timespec ts2;
260 clock_gettime(CLOCK_MONOTONIC, &ts2);
261 unsigned long tdiff =
262 (ts2.tv_sec - ts.tv_sec) * 1000000 + ts2.tv_nsec / 1000 - ts.tv_nsec / 1000;
264 char message[sizeof("redraw: time=us") + PRINTF_WIDTH(unsigned long)];
265 sprintf(message, "redraw: time=%luus", tdiff);
266 logDebug(message);
267 #endif // DEBUG
269 pending_redraw_ = REDRAW_NONE;
271 return 0;
274 void CoreManager::registerWindow(Window &window)
276 assert(!window.isVisible());
278 Windows::iterator i = findWindow(window);
279 assert(i == windows_.end());
281 windows_.push_front(&window);
282 updateWindowArea(window);
285 void CoreManager::removeWindow(Window &window)
287 Windows::iterator i = findWindow(window);
288 assert(i != windows_.end());
290 windows_.erase(i);
292 focusWindow();
293 redraw();
296 void CoreManager::hideWindow(Window &window)
298 Windows::iterator i = findWindow(window);
299 assert(i != windows_.end());
301 focusWindow();
302 redraw();
305 void CoreManager::topWindow(Window &window)
307 Windows::iterator i = findWindow(window);
308 assert(i != windows_.end());
310 windows_.erase(i);
311 windows_.push_back(&window);
313 focusWindow();
314 redraw();
317 Window *CoreManager::getTopWindow()
319 return dynamic_cast<Window *>(input_child_);
322 void CoreManager::logDebug(const char *message)
324 interface_.logDebug(message);
327 void CoreManager::redraw(bool from_scratch)
329 if (pending_redraw_ == REDRAW_NONE)
330 interface_.redraw();
332 if (pending_redraw_ == REDRAW_FROM_SCRATCH)
333 return;
334 pending_redraw_ = from_scratch ? REDRAW_FROM_SCRATCH : REDRAW_NORMAL;
337 bool CoreManager::isRedrawPending() const
339 return pending_redraw_ != REDRAW_NONE;
342 void CoreManager::onScreenResized()
344 // Signal the resize event.
345 signal_resize();
347 // Propagate the resize event to all windows.
348 for (Window *window : windows_)
349 window->onScreenResized();
352 void CoreManager::onWindowMoveResize(
353 Window &activator, const Rect & /*oldsize*/, const Rect & /*newsize*/)
355 updateWindowArea(activator);
358 void CoreManager::onWindowWishSizeChange(
359 Window &activator, const Size &oldsize, const Size &newsize)
361 if ((activator.getWidth() != Widget::AUTOSIZE ||
362 oldsize.getWidth() == newsize.getWidth()) &&
363 (activator.getHeight() != Widget::AUTOSIZE ||
364 oldsize.getHeight() == newsize.getHeight()))
365 return;
367 updateWindowArea(activator);
370 CoreManager::CoreManager(AppInterface &set_interface)
371 : top_input_processor_(nullptr), tk_(nullptr), iconv_desc_(ICONV_NONE),
372 pending_redraw_(REDRAW_NONE)
374 // Validate the passed interface.
375 assert(!set_interface.redraw.empty());
376 assert(!set_interface.logDebug.empty());
378 interface_ = set_interface;
380 declareBindables();
383 bool CoreManager::processInput(const TermKeyKey &key)
385 if (top_input_processor_ && top_input_processor_->processInput(key))
386 return true;
388 return InputProcessor::processInput(key);
391 void CoreManager::updateArea()
393 for (Window *window : windows_)
394 updateWindowArea(*window);
397 void CoreManager::updateWindowArea(Window &window)
399 int screen_width = Curses::getWidth();
400 int screen_height = Curses::getHeight();
402 int window_x = window.getLeft();
403 int window_y = window.getTop();
405 // Calculate the real width.
406 int window_width = window.getWidth();
407 if (window_width == Widget::AUTOSIZE) {
408 window_width = window.getWishWidth();
409 if (window_width == Widget::AUTOSIZE)
410 window_width = screen_width - window_x;
412 if (window_width < 0)
413 window_width = 0;
415 // Calculate the real height.
416 int window_height = window.getHeight();
417 if (window_height == Widget::AUTOSIZE) {
418 window_height = window.getWishHeight();
419 if (window_height == Widget::AUTOSIZE)
420 window_height = screen_height - window_y;
422 if (window_height < 0)
423 window_height = 0;
425 window.setRealPosition(window_x, window_y);
426 window.setRealSize(window_width, window_height);
429 int CoreManager::drawWindow(Window &window, Error &error)
431 int screen_width = Curses::getWidth();
432 int screen_height = Curses::getHeight();
434 int window_x = window.getRealLeft();
435 int window_y = window.getRealTop();
436 int window_width = window.getRealWidth();
437 int window_height = window.getRealHeight();
438 int window_x2 = window_x + window_width;
439 int window_y2 = window_y + window_height;
441 int window_view_x = 0;
442 int window_view_y = 0;
443 int window_view_width = window_width;
444 int window_view_height = window_height;
446 // Calculate a viewport for the window.
447 if (window_x < 0) {
448 window_view_x = -window_x;
449 window_x += window_view_x;
450 if (window_view_x > window_width)
451 window_view_x = window_width;
452 window_view_width -= window_view_x;
454 if (window_y < 0) {
455 window_view_y = -window_y;
456 window_y += window_view_y;
457 if (window_view_y > window_height)
458 window_view_y = window_height;
459 window_view_height -= window_view_y;
462 if (window_x2 > screen_width) {
463 window_view_width -= window_x2 - screen_width;
464 if (window_view_width < 0)
465 window_view_width = 0;
467 if (window_y2 > screen_height) {
468 window_view_height -= window_y2 - screen_height;
469 if (window_view_height < 0)
470 window_view_height = 0;
473 Curses::ViewPort window_area(window_x, window_y, window_view_x, window_view_y,
474 window_view_width, window_view_height);
475 return window.draw(window_area, error);
478 CoreManager::Windows::iterator CoreManager::findWindow(Window &window)
480 return std::find(windows_.begin(), windows_.end(), &window);
483 void CoreManager::focusWindow()
485 // Check if there are any windows left.
486 Window *win = nullptr;
487 Windows::reverse_iterator i;
489 // Try to find a top window first.
490 for (i = windows_.rbegin(); i != windows_.rend(); ++i)
491 if ((*i)->isVisible() && (*i)->getType() == Window::TYPE_TOP) {
492 win = *i;
493 break;
496 // Normal windows.
497 if (win == nullptr)
498 for (i = windows_.rbegin(); i != windows_.rend(); ++i)
499 if ((*i)->isVisible() && (*i)->getType() == Window::TYPE_NORMAL) {
500 win = *i;
501 break;
504 Window *focus = dynamic_cast<Window *>(getInputChild());
505 if (win == nullptr || win != focus) {
506 // Take the focus from the old window with the focus.
507 if (focus != nullptr) {
508 focus->ungrabFocus();
509 clearInputChild();
512 // Give the focus to the window.
513 if (win != nullptr) {
514 setInputChild(*win);
515 win->restoreFocus();
517 signal_top_window_change();
521 void CoreManager::redrawScreen()
523 // Make sure everything is redrawn from the scratch.
524 redraw(true);
527 void CoreManager::declareBindables()
529 declareBindable("coremanager", "redraw-screen",
530 sigc::mem_fun(this, &CoreManager::redrawScreen),
531 InputProcessor::BINDABLE_OVERRIDE);
534 } // namespace CppConsUI
536 // vim: set tabstop=2 shiftwidth=2 textwidth=80 expandtab: