1 // Copyright (C) 2007 Mark Pustjens <pustjens@dds.nl>
2 // Copyright (C) 2009-2015 Petr Pavlu <setup@dagobah.cz>
4 // This file is part of CenterIM.
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"
31 #include <sys/ioctl.h>
36 #define ICONV_NONE reinterpret_cast<iconv_t>(-1)
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
**> {
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_
); }
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
);
74 ERROR_LIBTERMKEY_INITIALIZATION
, _("Libtermkey initialization failed."));
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 "
95 if (iconv_desc_
!= ICONV_NONE
) {
96 int res
= iconv_close(iconv_desc_
);
98 iconv_desc_
= ICONV_NONE
;
101 if (tk_
!= nullptr) {
102 termkey_destroy(tk_
);
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.
118 iconv_desc_
= ICONV_NONE
;
121 termkey_destroy(tk_
);
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);
149 termkey_advisereadable(tk_
);
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
;
158 char utf8
[sizeof(key
.utf8
) - 1];
160 // Convert data from the user charset to UTF-8.
162 inbytesleft
= strlen(key
.utf8
);
164 outbytesleft
= sizeof(utf8
);
165 res
= iconv(iconv_desc_
, sloppy
<char **>(&inbuf
), &inbytesleft
, &outbuf
,
167 if (res
!= static_cast<std::size_t>(-1) && inbytesleft
!= 0) {
168 // No error occured but not all bytes have been converted.
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
);
188 if (ret
== TERMKEY_RES_AGAIN
) {
189 *wait
= termkey_get_waittime(tk_
);
196 int CoreManager::processStandardInputTimeout(Error
& /*error*/)
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
207 int CoreManager::resize(Error
&error
)
210 if (ioctl(STDOUT_FILENO
, TIOCGWINSZ
, &size
) >= 0) {
211 if (Curses::resizeTerm(size
.ws_col
, size
.ws_row
, error
) != 0)
212 return error
.getCode();
218 // Make sure everything is redrawn from the scratch.
221 // Trigger the resize event.
227 int CoreManager::draw(Error
&error
)
229 if (pending_redraw_
== REDRAW_NONE
)
232 #if defined(DEBUG) && 0
234 clock_gettime(CLOCK_MONOTONIC
, &ts
);
237 if (pending_redraw_
== REDRAW_FROM_SCRATCH
)
238 DRAW(Curses::clear(error
));
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
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
);
269 pending_redraw_
= REDRAW_NONE
;
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());
296 void CoreManager::hideWindow(Window
&window
)
298 Windows::iterator i
= findWindow(window
);
299 assert(i
!= windows_
.end());
305 void CoreManager::topWindow(Window
&window
)
307 Windows::iterator i
= findWindow(window
);
308 assert(i
!= windows_
.end());
311 windows_
.push_back(&window
);
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
)
332 if (pending_redraw_
== REDRAW_FROM_SCRATCH
)
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.
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()))
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
;
383 bool CoreManager::processInput(const TermKeyKey
&key
)
385 if (top_input_processor_
&& top_input_processor_
->processInput(key
))
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)
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)
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.
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
;
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
) {
498 for (i
= windows_
.rbegin(); i
!= windows_
.rend(); ++i
)
499 if ((*i
)->isVisible() && (*i
)->getType() == Window::TYPE_NORMAL
) {
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();
512 // Give the focus to the window.
513 if (win
!= nullptr) {
517 signal_top_window_change();
521 void CoreManager::redrawScreen()
523 // Make sure everything is redrawn from the scratch.
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: