Compare commits

...

15 commits

Author SHA1 Message Date
Christian Burger f48641bb58 cmake: changed project homepage URL 2022-05-18 23:30:04 +02:00
Christian Burger 3be5336ca0 refactoring tiling window manager
Laying out the windows in the window manager frame, is a very similar
process regardless of doing it horizontally or vertically. So the
template design pattern was applied and the general functionality was
pulled up; only specific functionalities were put into the corresponding
classes.

kNCursesDemoApp: Separated the screen into three windows vertically. The
top and bottom window contain another two windows each, lay out
horizontally. The top left window is fixed to width 1 and the top right
takes the rest of the space. Bottom two windows take 50 % of the space
each.

Remarks: There are still some rendering bugs, when hiding and showing
windows. E. g. when hiding the top or middle window with `<F1>` or
`<F2>` respectively, the bottom window has some errors. Maybe some
timing issue.
2022-05-18 21:15:13 +02:00
Christian Burger 0a086ff604 horizontal window manager now resizes correctly
Window managers are now descendants of windows instead of owning the
window in which they exist. This makes a clean window hierarchy.
2022-05-08 22:52:23 +02:00
Christian Burger 9ddb769cb4 added HorizontalWindowManager to DemoApp 2022-05-08 21:37:06 +02:00
Christian Burger 4fb2e6a976 renamed App.* to DemoApp.* 2022-05-08 21:26:12 +02:00
Christian Burger 7e7372ee52 refactored DemoApp
Separated `run()` into `setUpWindows()` and
`mainLoop()`.
2022-05-08 21:24:40 +02:00
Christian Burger a801752620 added class HorizontalTilingWindowManager
Just some minor changes to
`VerticalTilingWindowManager`. Not part of the
demo, yet.
2022-05-08 21:12:06 +02:00
Christian Burger 792f12c96c created abstract class TilingWindowManager
`TilingWindowManager` was created from `VerticalTilingWindowManager` and
separated its functionality so that there can be a
`HorizontalTilingWindowManager` next.
2022-05-08 20:57:48 +02:00
Christian Burger 1357a7f6bf renamed to project to kNCurses 2022-05-06 22:42:09 +02:00
Christian Burger b723aa5f33 fixed crash when all windows were hidden 2022-05-06 22:15:34 +02:00
Christian Burger 320f5ba63a improved demo a bit, refactoring a bit 2022-05-06 22:05:57 +02:00
Christian Burger e42711f123 switched to scoped_lock 2022-05-06 18:37:48 +02:00
Christian Burger b9e32941fb hide and show windows in window manager
* switching to recursive mutexes for now; easier to implement
* need to read user input from the window manager; hidden windows are
drawn when reading input from there
* note: occasional dead lock between PTY and ncurses mutex
* fixed type in class `SingleUserInput`
2022-05-06 14:02:18 +02:00
Christian Burger 0462a68c54 added simple vertical tiling window manager 2022-05-03 11:23:32 +02:00
Christian Burger f8db9dc660 renamed project to NCurses 2022-05-01 22:51:17 +02:00
20 changed files with 663 additions and 162 deletions

8
.vscode/launch.json vendored
View file

@ -5,10 +5,10 @@
"version": "0.2.0",
"configurations": [
{
"name": "(gdb) start NCursesPtyApp",
"name": "(gdb) start kNCursesDemoApp",
"type": "cppdbg",
"request": "launch",
"program": "${workspaceFolder}/build/NCursesPtyApp",
"program": "${workspaceFolder}/build/kNCursesDemoApp",
"args": [],
"stopAtEntry": false,
"cwd": "${workspaceFolder}",
@ -29,10 +29,10 @@
]
},
{
"name": "(gdb) attach to running NCursesPtyApp",
"name": "(gdb) attach to running kNCursesDemoApp",
"type": "cppdbg",
"request": "attach",
"program": "${workspaceFolder}/build/NCursesPtyApp",
"program": "${workspaceFolder}/build/kNCursesDemoApp",
"processId": "${command:pickProcess}",
"MIMode": "gdb",
"setupCommands": [

View file

@ -53,7 +53,10 @@
"iostream": "cpp",
"stdexcept": "cpp",
"typeinfo": "cpp",
"pointers": "cpp"
"pointers": "cpp",
"list": "cpp",
"condition_variable": "cpp",
"mutex": "cpp"
},
"C_Cpp.default.configurationProvider": "ms-vscode.cmake-tools",
"C_Cpp.default.includePath": [

64
App.cpp
View file

@ -1,64 +0,0 @@
/**
* @author Christian Burger (christian@krikkel.de)
*/
#include "App.hpp"
#include "Debug.hpp"
#include <NCursesPtyWindow/PtyWindow.hpp>
#include <unistd.h>
#include <utmp.h>
#include <cstdlib>
#include <mutex>
namespace krikkel::NCursesPtyWindow
{
App::App() : NCursesApplication(false)
{
}
int App::run()
{
std::mutex writeMutex;
PtyWindow *ptyWindow = new PtyWindow(&writeMutex
, Root_Window->lines()
, Root_Window->cols()
, 0
, 0);
if(fork() == 0)
{
const char *shellPath = getenv("SHELL");
if(!shellPath)
shellPath = "/bin/bash";
login_tty(ptyWindow->getFdPtyClient());
const int sizeArg = 2;
const char *arg[sizeArg] = {shellPath, NULL};
fprintf(stderr, "<CTRL>+C exits shell.\n\n");
execv(shellPath, const_cast<char * const *>(arg));
fprintf(stderr, "Well, well, well … could not start a shell. We "
"tried `%s`. Maybe set `SHELL` environment variable"
" to a working value?", shellPath);
exit(1);
}
while(true)
{
SingleUserInput input = ptyWindow->readSingleUserInput();
if(input.isNormalKey())
ptyWindow->writeUnicodeCharToClient(input.getRawInput());
if(input.isFunctionKey())
{
if(input.getRawInput() == KEY_RESIZE)
ptyWindow->wresize(Root_Window->lines(), Root_Window->cols());
else
ptyWindow->writeKeyToClient(input.mapKeyNcursesToVTerm());
}
}
return 0;
}
}

23
App.hpp
View file

@ -1,23 +0,0 @@
/**
* @brief demo application for the library
* @author Christian Burger (christian@krikkel.de)
*/
#ifndef A3B2AE4E_0A39_468C_8CCA_E6508166702A
#define A3B2AE4E_0A39_468C_8CCA_E6508166702A
#include <cursesapp.h>
namespace krikkel::NCursesPtyWindow
{
class App : public NCursesApplication
{
public:
App();
private:
int run() override;
};
}
#endif /* A3B2AE4E_0A39_468C_8CCA_E6508166702A */

View file

@ -4,8 +4,8 @@
cmake_minimum_required(VERSION 3.16.3)
include("cmake/version.cmake")
project(NCursesPtyWindow
HOMEPAGE_URL "https://gitea.xndr.de/christian/NCursesPtyWindow"
project(kNCurses
HOMEPAGE_URL "https://gitea.xndr.de/christian/kNCurses"
VERSION ${SEMANTIC_VERSION})
set(CMAKE_CXX_STANDARD 17)
@ -13,7 +13,9 @@ set(CMAKE_CXX_STANDARD 17)
include(CTest)
enable_testing()
add_library(NCursesPtyWindow Window.cpp PtyWindow.cpp SingleUserInput.cpp Debug.cpp)
add_library(kNCurses Window.cpp PtyWindow.cpp SingleUserInput.cpp Debug.cpp
TilingWindowManager.cpp VerticalTilingWindowManager.cpp
HorizontalTilingWindowManager.cpp)
### path to own system includes
include_directories(SYSTEM "${CMAKE_SOURCE_DIR}/include")
@ -21,34 +23,41 @@ include_directories(SYSTEM "${CMAKE_SOURCE_DIR}/include")
### libraries
include("cmake/ncurses.cmake")
target_link_libraries(NCursesPtyWindow ${CURSES_LIBRARIES})
target_link_libraries(kNCurses ${CURSES_LIBRARIES})
include("cmake/gsl.cmake")
include("cmake/libvterm.cmake")
if(EXISTS libvtermProject)
add_dependencies(NCursesPtyWindow libvtermProject)
add_dependencies(kNCurses libvtermProject)
endif()
target_link_libraries(NCursesPtyWindow ${LIBVTERM_LIBRARY})
target_link_libraries(kNCurses ${LIBVTERM_LIBRARY})
find_library(UTIL_LIBRARY util)
target_link_libraries(NCursesPtyWindow ${UTIL_LIBRARY})
target_link_libraries(kNCurses ${UTIL_LIBRARY})
set(THREADS_PREFER_PTHREAD_FLAG true)
find_package(Threads REQUIRED)
target_link_libraries(NCursesPtyWindow Threads::Threads)
target_link_libraries(kNCurses Threads::Threads)
### demo application
add_executable(NCursesPtyApp main.cpp App.cpp)
target_link_libraries(NCursesPtyApp NCursesPtyWindow)
add_executable(kNCursesDemoApp main.cpp DemoApp.cpp)
target_link_libraries(kNCursesDemoApp kNCurses)
### installation and packaging
set(NCURSES_PTY_WINDOW_SYSTEM_INCLUDE "include/NCursesPtyWindow")
set_target_properties(NCursesPtyWindow PROPERTIES PUBLIC_HEADER "${NCURSES_PTY_WINDOW_SYSTEM_INCLUDE}/Window.hpp;${NCURSES_PTY_WINDOW_SYSTEM_INCLUDE}/SingleUserInput.hpp;${NCURSES_PTY_WINDOW_SYSTEM_INCLUDE}/PtyWindow.hpp"
set(NCURSES_SYSTEM_INCLUDE "include/kNCurses")
set_target_properties(kNCurses PROPERTIES
PUBLIC_HEADER "${NCURSES_SYSTEM_INCLUDE}/Window.hpp;
${NCURSES_SYSTEM_INCLUDE}/SingleUserInput.hpp;
${NCURSES_SYSTEM_INCLUDE}/PtyWindow.hpp;
${NCURSES_SYSTEM_INCLUDE}/TilingWindowManager.hpp;
${NCURSES_SYSTEM_INCLUDE}/VerticalTilingWindowManager.hpp;
${NCURSES_SYSTEM_INCLUDE}/HorizontalTilingWindowManager.hpp;
"
VERSION "${CMAKE_PROJECT_VERSION}")
include(GNUInstallDirs)
install(TARGETS NCursesPtyWindow ARCHIVE
PUBLIC_HEADER DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/NCursesPtyWindow/")
install(TARGETS kNCurses ARCHIVE
PUBLIC_HEADER DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/kNCurses/")
set(CPACK_PROJECT_NAME ${PROJECT_NAME})
set(CPACK_PROJECT_VERSION ${PROJECT_VERSION})

142
DemoApp.cpp Normal file
View file

@ -0,0 +1,142 @@
/**
* @author Christian Burger (christian@krikkel.de)
*/
#include "DemoApp.hpp"
#include "Debug.hpp"
#include <kNCurses/VerticalTilingWindowManager.hpp>
#include <kNCurses/HorizontalTilingWindowManager.hpp>
#include <kNCurses/Window.hpp>
#include <kNCurses/PtyWindow.hpp>
#include <unistd.h>
#include <utmp.h>
#include <cstdlib>
namespace krikkel::NCurses
{
using std::scoped_lock;
DemoApp::DemoApp() : NCursesApplication(false)
{
}
int DemoApp::run()
{
setUpWindows();
forkShell();
mainLoop();
return 0;
}
void DemoApp::setUpWindows()
{
windowManager
= new VerticalTilingWindowManager(Root_Window, &ncursesMutex);
topWindowManager
= new HorizontalTilingWindowManager(new Window(), &ncursesMutex);
bottomWindowManager
= new HorizontalTilingWindowManager(new Window(), &ncursesMutex);
windowManager->addWindow(topWindowManager);
ptyWindow = new PtyWindow(&ncursesMutex, 0, 0, 0, 0);
windowManager->addWindow(ptyWindow);
windowManager->addWindow(bottomWindowManager);
windowManager->updateLayout();
windowManager->refresh();
dummyWindowTopLeft = new Window();
topWindowManager->addWindow(dummyWindowTopLeft, 1);
dummyWindowTopLeft->addstr("t\no\np\n \nl\ne\nf\nt\n w\ni\nn\nd\no\nw");
dummyWindowTopRight = new Window(topWindowManager);
dummyWindowTopRight->addstr("top right window");
topWindowManager->updateLayout();
topWindowManager->refresh();
dummyWindowBottomLeft = new Window(bottomWindowManager);
dummyWindowBottomLeft->addstr("bottom left window");
dummyWindowBottomRight = new Window(bottomWindowManager);
dummyWindowBottomRight->addstr("bottom right window");
bottomWindowManager->updateLayout();
bottomWindowManager->refresh();
}
void DemoApp::forkShell()
{
if(fork() == 0)
{
const char *shellPath = getenv("SHELL");
if(!shellPath)
shellPath = "/bin/bash";
login_tty(ptyWindow->getFdPtyClient());
const int sizeArg = 2;
const char *arg[sizeArg] = {shellPath, NULL};
fprintf(stderr, "<CTRL>+C exits shell.\n\n");
execv(shellPath, const_cast<char * const *>(arg));
fprintf(stderr, "Well, well, well … could not start a shell. We "
"tried `%s`. Maybe set `SHELL` environment variable"
" to a working value?", shellPath);
exit(1);
}
}
void DemoApp::mainLoop()
{
while(true)
{
SingleUserInput input = windowManager->readSingleUserInput();
if(input.isNormalKey())
{
ptyWindow->writeUnicodeCharToClient(input.getRawInput());
{
scoped_lock lock(ncursesMutex);
dummyWindowTopRight->addch(input.getRawInput());
dummyWindowTopRight->refresh();
}
}
if(input.isFunctionKey())
{
switch(input.getRawInput())
{
case KEY_RESIZE:
windowManager->updateLayout();
break;
case KEY_F(1):
if(topWindowManager->isHidden())
windowManager->showWindow(topWindowManager);
else
windowManager->hideWindow(topWindowManager);
windowManager->updateLayout();
windowManager->refresh();
break;
case KEY_F(2):
if(ptyWindow->isHidden())
windowManager->showWindow(ptyWindow);
else
windowManager->hideWindow(ptyWindow);
windowManager->updateLayout();
windowManager->refresh();
break;
case KEY_F(3):
if(bottomWindowManager->isHidden())
windowManager->showWindow(bottomWindowManager);
else
windowManager->hideWindow(bottomWindowManager);
windowManager->updateLayout();
windowManager->refresh();
break;
case KEY_F(5):
windowManager->refresh();
break;
default:
ptyWindow->writeFunctionKeyToClient(input.mapKeyNcursesToVTerm());
}
}
}
}
}

39
DemoApp.hpp Normal file
View file

@ -0,0 +1,39 @@
/**
* @brief demo application for the library
* @author Christian Burger (christian@krikkel.de)
*/
#ifndef A3B2AE4E_0A39_468C_8CCA_E6508166702A
#define A3B2AE4E_0A39_468C_8CCA_E6508166702A
#include <cursesapp.h>
#include <mutex>
namespace krikkel::NCurses
{
class VerticalTilingWindowManager;
class HorizontalTilingWindowManager;
class Window;
class PtyWindow;
class DemoApp : public NCursesApplication
{
public:
DemoApp();
private:
VerticalTilingWindowManager *windowManager;
HorizontalTilingWindowManager *topWindowManager, *bottomWindowManager;
Window *dummyWindowTopLeft, *dummyWindowTopRight
, *dummyWindowBottomLeft, *dummyWindowBottomRight;
PtyWindow *ptyWindow;
std::recursive_mutex ncursesMutex;
int run() override;
void setUpWindows();
void forkShell();
void mainLoop();
};
}
#endif /* A3B2AE4E_0A39_468C_8CCA_E6508166702A */

View file

@ -0,0 +1,40 @@
/**
* @author Christian Burger (christian@krikkel.de)
*/
#include "Debug.hpp"
#include <kNCurses/HorizontalTilingWindowManager.hpp>
#include <kNCurses/Window.hpp>
#include <ncursesw/ncurses.h>
#include <algorithm>
namespace krikkel::NCurses
{
using std::recursive_mutex;
HorizontalTilingWindowManager::HorizontalTilingWindowManager(NCursesWindow *rootWindow, std::recursive_mutex *ncursesMutex)
: TilingWindowManager(rootWindow, ncursesMutex)
{}
void HorizontalTilingWindowManager::resizeAndMoveWindow(Window *window, windowDimension dimension, windowPosition position)
{
__debug_log("(" + std::to_string(position + begx()) + ", " + std::to_string(0 + begy()) + ", " + std::to_string(dimension) + ", " + std::to_string(height()) + ")");
window->resize(height(), dimension);
window->mvwin(0 + begy(), position + begx());
}
TilingWindowManager::windowDimension HorizontalTilingWindowManager::getAvailableSpace()
{
return width();
}
void HorizontalTilingWindowManager::windowBorder()
{
vline(height());
}
void HorizontalTilingWindowManager::moveCursor(TilingWindowManager::windowPosition position)
{
move(0, position);
}
}

View file

@ -2,7 +2,7 @@
* @author Christian Burger (christian@krikkel.de)
*/
#include <NCursesPtyWindow/PtyWindow.hpp>
#include <kNCurses/PtyWindow.hpp>
#include "Debug.hpp"
#include <cstdio>
@ -13,13 +13,14 @@
#include <termios.h>
#include <fcntl.h>
namespace krikkel::NCursesPtyWindow
namespace krikkel::NCurses
{
using gsl::narrow;
using std::lock_guard;
using std::scoped_lock;
using std::recursive_mutex;
PtyWindow::PtyWindow(std::mutex *writeToNCursesMutex, int lines, int columns, int y, int x)
: writeToNCursesMutex(writeToNCursesMutex), Window(lines, columns, y, x)
PtyWindow::PtyWindow(recursive_mutex *ncursesMutex, int lines, int columns, int y, int x)
: ncursesMutex(ncursesMutex), Window(lines, columns, y, x)
{
// to get the original terminal we need to shutdown ncurses for a moment
/// @todo maybe try `reset_prog_mode()` and `reset_shell_mode()` instead
@ -43,9 +44,10 @@ namespace krikkel::NCursesPtyWindow
vterm_screen_enable_altscreen(pseudoTerminalScreen, true);
//raw(); // — cbreak might suffice
//noecho(); — already set
//nodelay(true); — @todo needs some reprogramming
keypad(true);
//noecho(); // — already set by NCursesWindow
keypad(true); /// — already set by NCursesWindow
//meta(true); // — already set by NCursesWindow
//nodelay(true); // — @todo would need some programming, not sure if useful
nonl();
/// @todo block all signals, this thread does not handle any
@ -65,21 +67,22 @@ namespace krikkel::NCursesPtyWindow
return fdPtyClient;
}
void PtyWindow::writeToClient(const char *output, size_t length)
void PtyWindow::writeToPtyClient(const char *output, size_t length)
{
lock_guard writeLock(writeToPseudoTerminalMutex);
write(fdPtyHost, output, length);
__debug_log("written to PTY client: '" + __debug_make_bytes_printable(std::string(output, length)) + '\'');
//__debug_log("written to PTY client: '" + __debug_make_bytes_printable(std::string(output, length)) + '\'');
}
void PtyWindow::writeUnicodeCharToClient(wint_t character)
{
scoped_lock lock(ptyMutex);
vterm_keyboard_unichar(pseudoTerminal, character, VTERM_MOD_NONE);
}
void PtyWindow::writeKeyToClient(VTermKey key)
void PtyWindow::writeFunctionKeyToClient(VTermKey key)
{
__debug_log("writing key: " + std::to_string(key));
scoped_lock lock(ptyMutex);
//__debug_log("writing key: " + std::to_string(key));
vterm_keyboard_key(pseudoTerminal, key, VTERM_MOD_NONE);
}
@ -105,10 +108,10 @@ namespace krikkel::NCursesPtyWindow
size_t bytesRead = read(fdPtyHost, ptyClientOutputBuffer, PTY_CLIENT_OUTPUT_BUFFER_SIZE);
if(bytesRead != -1 && bytesRead != 0)
{
lock_guard writeLock(writeToPseudoTerminalMutex);
scoped_lock lock(ptyMutex, *ncursesMutex);
vterm_input_write(pseudoTerminal, ptyClientOutputBuffer, bytesRead);
}
__debug_log("read from PTY client: '" + __debug_make_bytes_printable(std::string(ptyClientOutputBuffer, bytesRead)) + '\'');
//__debug_log("read from PTY client: '" + __debug_make_bytes_printable(std::string(ptyClientOutputBuffer, bytesRead)) + '\'');
}
VTermScreenCallbacks PtyWindow::screenCallbacks =
@ -125,6 +128,8 @@ namespace krikkel::NCursesPtyWindow
int PtyWindow::handlerDamage(VTermRect rect)
{
scoped_lock lock(ptyMutex, *ncursesMutex);
for(int x = rect.start_col; x < rect.end_col; ++x)
for(int y = rect.start_row; y < rect.end_row; ++y)
copyPtyCellToNCursesWindow(x, y);
@ -153,7 +158,7 @@ namespace krikkel::NCursesPtyWindow
int PtyWindow::handlerMoveCursor(VTermPos pos, VTermPos oldpos, int visible)
{
/// @todo maybe use `mvcur()` instead?
scoped_lock lock(*ncursesMutex);
cursorX = pos.col;
cursorY = pos.row;
refresh();
@ -238,9 +243,9 @@ namespace krikkel::NCursesPtyWindow
else
character = *vTermCell.chars;
{
lock_guard nCursesLock(*writeToNCursesMutex);
//__debug_log(std::string("written '") + (char) character + std::string("' ") + std::to_string(x) + ", " + std::to_string(y));
setcchar(&converted, &character, formatting, colorPair, NULL);
{
move(cellPosition.row, cellPosition.col);
chgat(1, formatting, colorPair, NULL);
add_wch(&converted);
@ -249,17 +254,18 @@ namespace krikkel::NCursesPtyWindow
int PtyWindow::refresh()
{
lock_guard nCursesLock(*writeToNCursesMutex);
//__debug_log("refreshing");
scoped_lock lock(*ncursesMutex);
move(cursorY, cursorX);
return NCursesWindow::refresh();
return Window::refresh();
}
/// @todo potential racing condition where drawing into terminal while
/// resizing?
int PtyWindow::wresize(int rows, int cols)
int PtyWindow::resize(int rows, int cols)
{
{
lock_guard writeLock(writeToPseudoTerminalMutex);
scoped_lock lock(ptyMutex, *ncursesMutex);
winsize windowSize =
{
.ws_row = narrow<unsigned short>(rows)
@ -267,11 +273,8 @@ namespace krikkel::NCursesPtyWindow
};
ioctl(fdPtyHost, TIOCSWINSZ, &windowSize);
vterm_set_size(pseudoTerminal, rows, cols);
}
{
lock_guard nCursesLock(*writeToNCursesMutex);
return NCursesWindow::wresize(rows, cols);
}
return Window::resize(rows, cols);
}
int PtyWindow::staticHandlerDamage(VTermRect rect, void *user)
@ -325,7 +328,6 @@ namespace krikkel::NCursesPtyWindow
void PtyWindow::staticHandlerOutput(const char *s, size_t len, void *user)
{
PtyWindow *instance = static_cast<PtyWindow *>(user);
__debug_log("output handler writing to client: '" + __debug_make_bytes_printable(std::string(s, len)) + "'");
instance->writeToClient(s, len);
instance->writeToPtyClient(s, len);
}
}

View file

@ -2,7 +2,7 @@
* @author Christian Burger (christian@krikkel.de)
*/
#include <NCursesPtyWindow/SingleUserInput.hpp>
#include <kNCurses/SingleUserInput.hpp>
#include "Debug.hpp"
#include <vterm.h>
@ -13,7 +13,7 @@
#define KEY_ALT_BACKSPACE 127
#define KEY_ALT_ALT_BACKSPACE '\b'
namespace krikkel::NCursesPtyWindow
namespace krikkel::NCurses
{
SingleUserInput::SingleUserInput(int _resultGetWchCall, wint_t _input)
: input(_input), resultGetWchCall(_resultGetWchCall)
@ -42,7 +42,7 @@ namespace krikkel::NCursesPtyWindow
// it is only processing user input, it is not time critical enough for
// the wasted space
// @tode unmapped keys: keys erase
// @todo unmapped keys: keys erase
if(resultGetWchCall == OK)
switch(input)
{

145
TilingWindowManager.cpp Normal file
View file

@ -0,0 +1,145 @@
/**
* @author Christian Burger (christian@krikkel.de)
*/
#include <kNCurses/TilingWindowManager.hpp>
#include <kNCurses/Window.hpp>
#include "Debug.hpp"
#include <ncursesw/ncurses.h>
#include <algorithm>
namespace krikkel::NCurses
{
using std::list;
using std::pair;
using std::recursive_mutex;
using std::scoped_lock;
TilingWindowManager::TilingWindowManager(NCursesWindow *rootWindow, recursive_mutex *ncursesMutex)
: Window(*rootWindow), ncursesMutex(ncursesMutex)
{}
void TilingWindowManager::addWindow(Window *window, windowDimension size)
{
WindowStackElement stackElement(window, size);
stack.push_back(stackElement);
visibleStack.push_back(stackElement);
}
void TilingWindowManager::hideWindow(Window *window)
{
if(window->hidden)
return;
visibleStack.remove_if(
[window](WindowStackElement element)
{
return element.first == window;
});
window->hidden = true;
}
void TilingWindowManager::showWindow(Window *window)
{
if(!window->hidden)
return;
list<WindowStackElement>::iterator currentVisibleWindowElement = visibleStack.begin();
for(WindowStackElement currentWindowElement : stack)
{
if(currentWindowElement.first == window)
{
visibleStack.insert(currentVisibleWindowElement, currentWindowElement);
window->hidden = false;
break;
}
if(currentWindowElement != (*currentVisibleWindowElement))
continue;
++currentVisibleWindowElement;
}
}
int TilingWindowManager::resize(int rows, int cols)
{
int result = Window::resize(rows, cols);
updateLayout();
return result;
}
int TilingWindowManager::refresh()
{
scoped_lock lock(*ncursesMutex);
int result = Window::refresh();
for(WindowStackElement stackElement : visibleStack)
// @todo there are return values; compound them?
stackElement.first->refresh();
return result;
}
void TilingWindowManager::updateLayout()
{
scoped_lock lock(*ncursesMutex);
size_t stackSize = visibleStack.size();
if(stackSize == 0)
{
clear();
return;
}
int unitSize = getRelativeUnitSizeForVisibleWindows(getAvailableSpace()) - 1;
windowPosition position = 0;
auto lastButNotLeast = prev(visibleStack.end());
for(auto stackElementIterator = visibleStack.begin()
; stackElementIterator != lastButNotLeast
; ++stackElementIterator)
{
WindowStackElement stackElement = *stackElementIterator;
Window *window = stackElement.first;
if(!window->isHidden())
{
int windowShift;
int windowSize = stackElement.second;
if(windowSize < 0)
windowShift = unitSize * (-windowSize);
else
windowShift = windowSize;
resizeAndMoveWindow(window, windowShift, position);
position += windowShift;
__debug_log("drawing line at " + std::to_string(position));
moveCursor(position++);
windowBorder();
}
}
resizeAndMoveWindow((*lastButNotLeast).first, getAvailableSpace() - position, position);
}
TilingWindowManager::windowDimension TilingWindowManager::getRelativeUnitSizeForVisibleWindows(windowDimension spaceAvailable)
{
uint16_t numberOfRelativeUnits = 0;
windowDimension absoluteSum = 0;
list<WindowStackElement>::iterator windowIterator = stack.begin();
for(WindowStackElement stackElement : visibleStack)
{
windowDimension size = stackElement.second;
if(size < 0)
numberOfRelativeUnits -= size;
else
absoluteSum += size;
}
windowDimension relativeUnitSize = (spaceAvailable - absoluteSum) / numberOfRelativeUnits;
windowDimension remainder = (spaceAvailable - absoluteSum - relativeUnitSize * numberOfRelativeUnits);
if(remainder > 0 && remainder * 2 < numberOfRelativeUnits)
++relativeUnitSize;
return relativeUnitSize;
}
}

View file

@ -0,0 +1,41 @@
/**
* @author Christian Burger (christian@krikkel.de)
*/
#include "Debug.hpp"
#include <kNCurses/VerticalTilingWindowManager.hpp>
#include <kNCurses/Window.hpp>
#include <ncursesw/ncurses.h>
#include <algorithm>
namespace krikkel::NCurses
{
using std::recursive_mutex;
VerticalTilingWindowManager::VerticalTilingWindowManager(NCursesWindow *rootWindow, std::recursive_mutex *ncursesMutex)
: TilingWindowManager(rootWindow, ncursesMutex)
{}
void VerticalTilingWindowManager::resizeAndMoveWindow(Window *window, windowDimension dimension, windowPosition position)
{
__debug_log("(" + std::to_string(0 + begx()) + ", " + std::to_string(position + begy()) + ", " + std::to_string(width()) + ", " + std::to_string(dimension) + ")");
window->resize(dimension, width());
window->mvwin(position + begy(), 0 + begx());
}
TilingWindowManager::windowDimension VerticalTilingWindowManager::getAvailableSpace()
{
return height();
}
void VerticalTilingWindowManager::windowBorder()
{
hline(width());
}
void VerticalTilingWindowManager::moveCursor(TilingWindowManager::windowPosition position)
{
move(position, 0);
}
}

View file

@ -2,10 +2,25 @@
* @author Christian Burger (christian@krikkel.de)
*/
#include <NCursesPtyWindow/Window.hpp>
#include <kNCurses/Window.hpp>
#include <kNCurses/VerticalTilingWindowManager.hpp>
namespace krikkel::NCursesPtyWindow
namespace krikkel::NCurses
{
Window::Window() : NCursesWindow(0, 0, 0, 0)
{}
Window::Window(TilingWindowManager *windowManager)
: NCursesWindow(0, 0, 0, 0)
{
windowManager->addWindow(this);
windowManager->updateLayout();
}
Window::Window(const NCursesWindow &window)
: NCursesWindow(window)
{}
Window::Window(int lines, int columns, int y, int x)
: NCursesWindow(lines, columns, y, x)
{}
@ -30,6 +45,11 @@ namespace krikkel::NCursesPtyWindow
return ::wget_wch(w, character);
}
bool Window::isHidden()
{
return hidden;
}
SingleUserInput Window::readSingleUserInput()
{
wint_t input;
@ -37,4 +57,15 @@ namespace krikkel::NCursesPtyWindow
return SingleUserInput(result, input);
}
int Window::resize(int rows, int cols)
{
return NCursesWindow::wresize(rows, cols);
}
int Window::refresh()
{
if(!hidden)
return NCursesWindow::refresh();
return 0;
}
}

View file

@ -0,0 +1,31 @@
/**
* @brief Window manager
* @author Christian Burger (christian@krikkel.de)
*/
#ifndef B7BCF793_2FAB_49CC_9E00_CDEA370D38F9
#define B7BCF793_2FAB_49CC_9E00_CDEA370D38F9
#include "TilingWindowManager.hpp"
namespace krikkel::NCurses
{
class Window;
class SingleUserInput;
class HorizontalTilingWindowManager : public TilingWindowManager
{
public:
HorizontalTilingWindowManager(NCursesWindow *rootWindow, std::recursive_mutex *ncursesMutex);
void resizeAndMoveWindow(Window *window
, windowDimension dimension
, windowPosition position) override;
windowDimension getAvailableSpace() override;
void windowBorder() override;
void moveCursor(windowPosition position) override;
};
}
#endif /* B7BCF793_2FAB_49CC_9E00_CDEA370D38F9 */

View file

@ -1,12 +1,12 @@
/**
* @brief `ncurses` window displaying contents of a pseudo terminal
* @author Christian Burger (christian@krikkel.de)
* @todo remove all invalid constructors
*/
#ifndef __WINDOW_H__
#define __WINDOW_H__
#include "SingleUserInput.hpp"
#include "Window.hpp"
#include <cursesw.h>
@ -16,28 +16,27 @@
#include <thread>
#include <mutex>
namespace krikkel::NCursesPtyWindow
namespace krikkel::NCurses
{
class PtyWindow : public Window
{
public:
PtyWindow(std::mutex *writeToNCursesMutex, int lines, int columns, int y, int x);
PtyWindow(std::recursive_mutex *ncursesMutex, int lines, int columns, int y, int x);
~PtyWindow();
int getFdPtyClient() const;
void writeToClient(const char * string, size_t length);
void writeUnicodeCharToClient(wint_t character);
void writeKeyToClient(VTermKey key);
void writeFunctionKeyToClient(VTermKey key);
int refresh() override;
int wresize(int rows, int cols);
int resize(int rows, int cols) override;
private:
int fdPtyHost, fdPtyClient;
struct termios terminalParameters;
VTerm *pseudoTerminal;
std::mutex writeToPseudoTerminalMutex;
std::mutex *writeToNCursesMutex;
std::recursive_mutex ptyMutex;
std::recursive_mutex *ncursesMutex;
VTermScreen *pseudoTerminalScreen;
static VTermScreenCallbacks screenCallbacks;
/// @todo one line is at most 4096 chars long
@ -48,6 +47,7 @@ namespace krikkel::NCursesPtyWindow
std::thread readPtyClientThread;
void readFromPtyClientThreadMethod();
void readFromPtyClient();
void writeToPtyClient(const char * string, size_t length);
int handlerDamage(VTermRect rect);
int handlerMoveRect(VTermRect dest, VTermRect src);

View file

@ -9,7 +9,7 @@
#include <cursesw.h>
#include <vterm_keycodes.h>
namespace krikkel::NCursesPtyWindow
namespace krikkel::NCurses
{
class SingleUserInput
{

View file

@ -0,0 +1,61 @@
/**
* @brief Window manager for tiling (template pattern, abstract parent)
*
* A window manager contains windows of a given size, lays them out and can hide
* or show them. There is no concrete window manager for tiling horizontally and
* vertically at the same time. Instead nesting the window managers is adviced,
* since the window manager classes are derived from windows in the end.
*
* This is an abstract class used by the concrete classes
* `VerticalTilingWindowManager` and `HorizontalTilingWindowManager`. Both
* specify the virtual and protected, declared but undefined methods used by the
* public interface to facilitate the function depending on the orientation
* (horizontally or vertically).
*
* @author Christian Burger (christian@krikkel.de)
*/
#ifndef C51BA18F_0915_43B9_BD5D_129F0CDBC1CD
#define C51BA18F_0915_43B9_BD5D_129F0CDBC1CD
#include "Window.hpp"
#include <list>
#include <mutex>
#include <utility>
namespace krikkel::NCurses
{
class SingleUserInput;
class TilingWindowManager : public Window
{
public:
/// @todo figure out more appropiate names …
typedef int16_t windowDimension;
typedef uint16_t windowPosition;
typedef std::pair<Window *, windowDimension> WindowStackElement;
TilingWindowManager(NCursesWindow *rootWindow, std::recursive_mutex *ncursesMutex);
void addWindow(Window *window, windowDimension size = -1);
int resize(int rows, int cols) override;
int refresh() override;
void updateLayout();
void hideWindow(Window *window);
void showWindow(Window *window);
protected:
std::recursive_mutex *ncursesMutex;
std::list<WindowStackElement> stack, visibleStack;
windowDimension getRelativeUnitSizeForVisibleWindows(windowDimension spaceAvailable);
virtual void resizeAndMoveWindow(Window *window
, windowDimension dimension
, windowPosition position) = 0;
virtual void windowBorder() = 0;
virtual windowDimension getAvailableSpace() = 0;
virtual void moveCursor(windowPosition position) = 0;
};
}
#endif /* C51BA18F_0915_43B9_BD5D_129F0CDBC1CD */

View file

@ -0,0 +1,31 @@
/**
* @brief Window manager
* @author Christian Burger (christian@krikkel.de)
*/
#ifndef C10F5DF3_1DB4_4714_A84D_115F492F5CDC
#define C10F5DF3_1DB4_4714_A84D_115F492F5CDC
#include "TilingWindowManager.hpp"
namespace krikkel::NCurses
{
class Window;
class SingleUserInput;
class VerticalTilingWindowManager : public TilingWindowManager
{
public:
VerticalTilingWindowManager(NCursesWindow *rootWindow, std::recursive_mutex *ncursesMutex);
void resizeAndMoveWindow(Window *window
, windowDimension dimension
, windowPosition position) override;
windowDimension getAvailableSpace() override;
void windowBorder() override;
void moveCursor(windowPosition position) override;
};
}
#endif /* C10F5DF3_1DB4_4714_A84D_115F492F5CDC */

View file

@ -34,18 +34,31 @@ inline int UNDEF(get_wch)(wint_t *character) { get_wch(character); }
#define get_wch UNDEF(get_wch)
#endif
namespace krikkel::NCursesPtyWindow
namespace krikkel::NCurses
{
class TilingWindowManager;
class Window : public NCursesWindow
{
friend class TilingWindowManager;
public:
Window();
Window(TilingWindowManager *windowManager);
Window(const NCursesWindow &window);
Window(int lines, int columns, int y, int x);
int addnwstr(const wchar_t *wstr, int n);
int add_wch(const cchar_t *character);
int ins_wch(const cchar_t *character);
int get_wch(wint_t *character);
bool isHidden();
SingleUserInput readSingleUserInput();
virtual int resize(int rows, int cols);
virtual int refresh() override;
protected:
bool hidden = false;
};
}

View file

@ -4,6 +4,6 @@
* @author Christian Burger (christian@krikkel.de)
*/
#include "App.hpp"
#include "DemoApp.hpp"
krikkel::NCursesPtyWindow::App cursesPtyApp;
krikkel::NCurses::DemoApp demoApp;