Hey there! I just saw this on Itch.io and purchased it; I'm super pumped to play! However, I ran into an issue - the game is not detecting any keypresses at all. I can't even ctrl-c to quit, I have to open another window and kill it that way.
Also, is there any way to give a feature request of some command line options for the game? I'd love to launch it intentionally with no audio, no wizard mode, etc.
Thanks!
linux terminal mode keypresses not working headless
-
- Posts: 3
- Joined: Tue Jun 03, 2025 5:21 am
Re: linux terminal mode keypresses not working headless
Hi cloverskull,
Thanks so much for the bug report! To let me support controller across both the terminal and non-terminal the terminal builds I setup an invisible SDL window that handles the keyboard/controller input in the terminal version of the game. The invisible SDL window must have focus to play the game, the game starts with the invisible window having focus so normally you don't have to do anything unless you alt-tab or otherwise move focus away from the game. The game will attempt to tell you if the SDL window doesn't have focus with an error message. I've uploaded a demo of it here:
https://www.youtube.com/watch?v=OAocMO0klg4
You mention in the title that it's not working in headless, if this (above) isn't what you were referring too could you give me a little more detail? Rogue assumes that it will run in an environment where showing an SDL window (even an invisible one) will always work. If your running in a truly headless environment, I have some old code I could maybe integrate back into the project that handles the keyboard code using pure ncurses.
Also I'll add a feature request section to the forum, thanks! I don't have any command line options at the moment, but easy enough to add! However for your case the options menu will handle the features you mentioned specifically. The game will load the options menu every time it's launched so you only need to set it once.
Thanks so much for the bug report! To let me support controller across both the terminal and non-terminal the terminal builds I setup an invisible SDL window that handles the keyboard/controller input in the terminal version of the game. The invisible SDL window must have focus to play the game, the game starts with the invisible window having focus so normally you don't have to do anything unless you alt-tab or otherwise move focus away from the game. The game will attempt to tell you if the SDL window doesn't have focus with an error message. I've uploaded a demo of it here:
https://www.youtube.com/watch?v=OAocMO0klg4
You mention in the title that it's not working in headless, if this (above) isn't what you were referring too could you give me a little more detail? Rogue assumes that it will run in an environment where showing an SDL window (even an invisible one) will always work. If your running in a truly headless environment, I have some old code I could maybe integrate back into the project that handles the keyboard code using pure ncurses.
Also I'll add a feature request section to the forum, thanks! I don't have any command line options at the moment, but easy enough to add! However for your case the options menu will handle the features you mentioned specifically. The game will load the options menu every time it's launched so you only need to set it once.
Code: Select all
//#include <platform/shared.hpp>
//#include <platform/curses/api.hpp>
//
//#include <core/cast.hpp>
//#include <core/int.hpp>
//#include <core/macro/assert.hpp>
//#include <core/macro/control_flow.hpp>
//#include <core/macro/scope_exit.hpp>
//#include <external/sdl_full.hpp>
//struct CursesKey final
//{
// int code = 0;
// bool alt = false;
// bool control = false;
// bool shift = false;
//};
//namespace talc = tal::curses;
//namespace
//{
///// strip_ctrl_from_ch:
///// This function converts the keyboard character for CTRL+key stored in an int
///// int with the ctrl data removed.
//constexpr auto
//strip_ctrl_from_ch(int const ctrlch) noexcept
//{
// auto const CTRL = [](char const ch) { return ch & 037; };
//
// /*static*/ constexpr int AT_SYMBOL = '@';
// static_assert(AT_SYMBOL == ('A' - CTRL('A')));
// static_assert('H' == (CTRL('H') + AT_SYMBOL));
// auto const result = ctrlch + AT_SYMBOL;
// return result;
//}
//
//bool
//is_control_key(int const code) noexcept
//{
// std::string_view const key_name = ::keyname(code);
// RETURN_FALSE_IF(std::size(key_name) <= 1);
// RETURN_FALSE_IF('^' != key_name.front());
// return true;
//}
//
//auto
//check_f1_through_f4(WINDOW* window, CursesKey& result) noexcept
//{
// switch (::wgetch(window))
// {
// case 80: result.code = KEY_F(1); break;
// case 81: result.code = KEY_F(2); break;
// case 82: result.code = KEY_F(3); break;
// case 83: result.code = KEY_F(4); break;
// }
// return result;
//}
//
//auto
//check_f5_through_f8(WINDOW* window, CursesKey& result) noexcept
//{
// switch (::wgetch(window))
// {
// case 53: result.code = KEY_F(5); break;
// case 55: result.code = KEY_F(6); break;
// case 56: result.code = KEY_F(7); break;
// case 57: result.code = KEY_F(8); break;
// }
// return result;
//}
//
//auto
//check_f9_through_f12(WINDOW* window, CursesKey& result) noexcept
//{
// switch (::wgetch(window))
// {
// case 48: result.code = KEY_F(9); break;
// case 49: result.code = KEY_F(10); break;
// case 51: result.code = KEY_F(11); break;
// case 52: result.code = KEY_F(12); break;
//
// // NOTE: Insert key and KEYPAD0 both parse the exact same.
// // TODO: Maybe we can let the user decide which to return in this case??
// //case 126: result.code = KEY_IC; break;
// case 126: result.code = MY_KPAD0; break;
// }
// return result;
//}
//
//auto
//check_numpad(int const ch, CursesKey& result) noexcept
//{
// switch (ch)
// {
// case 70: result.code = MY_KPAD1; break;
// //case 70: result.code = KEY_END; break;
//
// case 66: result.code = MY_KPAD2; break;
// case 54: result.code = MY_KPAD3; break;
// //case 54: result.code = KEY_NPAGE; break;
//
// case 68: result.code = MY_KPAD4; break;
// case 69: result.code = MY_KPAD5; break;
// case 67: result.code = MY_KPAD6; break;
//
// // TODO: parse the same
// case 72: result.code = MY_KPAD7; break;
// //case 72: result.code = KEY_HOME; break;
//
// case 65: result.code = MY_KPAD8; break;
// case 53: result.code = MY_KPAD9; break;
// //case 53: result.code = KEY_PPAGE; break;
//
// case 51: result.code = MY_KPADSTOP; break; // KP-PERIOD
// //case 51: result.code = KEY_DC; break; // DEL key
// }
// return result;
//}
//
//auto
//check_fkeys_and_numpad(WINDOW* window, CursesKey& result) noexcept
//{
// switch (auto const ch = ::wgetch(window); ch)
// {
// case 49: return check_f5_through_f8(window, result);
// case 50: return check_f9_through_f12(window, result);
// default: return check_numpad(ch, result);
// }
//}
//
//auto
//check_other_escape_keys(CursesKey& result) noexcept
//{
// std::string_view const key_name = ::keyname(result.code);
// CORE_ASSERT(1 == key_name.length());
//
// result.code = ccast::to_int(key_name.front());
// result.alt = true;
//
// // Don't support ctrl+shift or ctrl+alt with ncurses
// result.control = false;
//
// if (cint::is_upper_alpha(result.code))
// {
// result.shift = true;
// result.code = cint::to_lower(result.code);
// }
// return result;
//}
//
//auto
//figure_out_if_escape_or_alt_key(WINDOW* window) noexcept
//{
// CursesKey result;
// result.code = ::wgetch(window);
//
// // In order to get all the bytes for the keypresses, we need to set a larger timeout (than 0)
// // temporarily for ncurses to give them to us.
// ::wtimeout(window, 10);
// ON_SCOPE_EXIT([&]() { ::wtimeout(window, 0); });
//
// // The following code was figured out through lots of debugging, I'm not entirely sure
// // how cross-platform it is.
// RETURN_VALUE_IF(79 == result.code, check_f1_through_f4(window, result));
//
// switch (result.code)
// {
// case 91:
// return check_fkeys_and_numpad(window, result);
//
// case talc::CURSES_ERR:
// result.code = SDLK_ESCAPE;
// return result;
//
// // We can detect alt+enter
// case '\r':
// result.alt = true;
// return result;
//
// default:
// return check_other_escape_keys(result);
// }
//}
//} // namespace
/*
bool
get_terminal_key(WINDOW* window) noexcept
{
bool pressed = false;
for (;;)
{
int const result = ::wgetch(window);
pressed |= (tal::curses::CURSES_ERR != result);
BREAK_IF(tal::curses::CURSES_ERR == result);
}
return pressed;
}
namespace tal::curses
{
std::optional<KeyInfo>
get_key(WINDOW *window) noexcept
{
CursesKey result;
result.code = ::wgetch(window);
switch (result.code)
{
case '\033': // ESC or ALT+Key
result = figure_out_if_escape_or_alt_key(window);
return result;
// Turns out detecting backspace isn't the most straight-forward with curses
case KEY_BACKSPACE:
case 127: // "DEL" on ascii table
result.code = KEY_BACKSPACE;
return result;
// ENTER gets passed through. In keypad(false) mode, there doesn't seem to be an obvious way to
// distinguish between enter and keypad-enter, so just let it pass through unchanged
case '\r':
return result;
case KEY_BTAB:
case '\t':
return result;
}
if (is_control_key(result.code))
{
result.code = strip_ctrl_from_ch(result.code);
result.control = true;
// Don't support ctrl+shift or ctrl+alt with ncurses
result.shift = false;
result.alt = false;
}
if (cint::is_upper_alpha(result.code))
{
result.code = cint::to_lower(result.code);
// Don't try to support ctrl+shift with ncurses
result.shift = !result.control;
}
else if (!cint::is_alpha(result.code))
{
result.shift = false;
}
return result;
}
} // namespace tal::curses
*/
Last edited by badamson on Tue Jun 03, 2025 2:25 pm, edited 1 time in total.
-
- Posts: 3
- Joined: Tue Jun 03, 2025 5:21 am
Re: linux terminal mode keypresses not working headless
Thanks for your reply, apologies for not following up back to you here!
Yeah, if there's a way to do pure ncurses, that'd be pretty stellar
I love having these sorts of games installed to play in a terminal window via ssh. That way I can play it from work, too, lol.
That said, I think your invisible sdl window is super clever! I wish headless could function with it but alas. :/
Yeah, if there's a way to do pure ncurses, that'd be pretty stellar

That said, I think your invisible sdl window is super clever! I wish headless could function with it but alas. :/