Skip to content

Commit

Permalink
move backup/restore of term settings to example
Browse files Browse the repository at this point in the history
  • Loading branch information
Tieske committed Apr 29, 2024
1 parent 71f9f2f commit 1aba4eb
Show file tree
Hide file tree
Showing 3 changed files with 148 additions and 144 deletions.
111 changes: 111 additions & 0 deletions examples/backup.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
-- This example shows how to backup and restore terminal settings

local sys = require "system"
local global_backup



local add_gc_method do
-- feature detection; __GC meta-method, not available in all Lua versions
local has_gc = false
local tt = setmetatable({}, { -- luacheck: ignore
__gc = function() has_gc = true end
})

-- clear table and run GC to trigger
tt = nil
collectgarbage()
collectgarbage()


if has_gc then
-- use default GC mechanism since it is available
function add_gc_method(t, f)
setmetatable(t, { __gc = f })
end
else
-- create workaround using a proxy userdata, typical for Lua 5.1
function add_gc_method(t, f)
local proxy = newproxy(true)
getmetatable(proxy).__gc = function()
t["__gc_proxy"] = nil
f(t)
end
t["__gc_proxy"] = proxy
end
end
end



--- Returns a backup of terminal setting for stdin/out/err.
-- Handles terminal/console flags and non-block flags on the streams.
-- Backs up terminal/console flags only if a stream is a tty.
-- @return table with backup of terminal settings
local function termbackup()
local backup = {}

if sys.isatty(io.stdin) then
backup.console_in = sys.getconsoleflags(io.stdin)
backup.term_in = sys.tcgetattr(io.stdin)
end
if sys.isatty(io.stdout) then
backup.console_out = sys.getconsoleflags(io.stdout)
backup.term_out = sys.tcgetattr(io.stdout)
end
if sys.isatty(io.stderr) then
backup.console_err = sys.getconsoleflags(io.stderr)
backup.term_err = sys.tcgetattr(io.stderr)
end

backup.block_in = sys.getnonblock(io.stdin)
backup.block_out = sys.getnonblock(io.stdout)
backup.block_err = sys.getnonblock(io.stderr)

return backup
end



--- Restores terminal settings from a backup
-- @param backup table with backup of terminal settings, see `termbackup`.
-- @return true
local function termrestore(backup)
if backup.console_in then sys.setconsoleflags(io.stdin, backup.console_in) end
if backup.term_in then sys.tcsetattr(io.stdin, sys.TCSANOW, backup.term_in) end
if backup.console_out then sys.setconsoleflags(io.stdout, backup.console_out) end
if backup.term_out then sys.tcsetattr(io.stdout, sys.TCSANOW, backup.term_out) end
if backup.console_err then sys.setconsoleflags(io.stderr, backup.console_err) end
if backup.term_err then sys.tcsetattr(io.stderr, sys.TCSANOW, backup.term_err) end

sys.setnonblock(io.stdin, backup.block_in)
sys.setnonblock(io.stdout, backup.block_out)
sys.setnonblock(io.stderr, backup.block_err)
return true
end



--- Backs up terminal settings and restores them on application exit.
-- Calls `termbackup` to back up terminal settings and sets up a GC method to
-- automatically restore them on application exit (also works on Lua 5.1).
-- @return[1] true
-- @return[2] nil, if the backup was already created
-- @return[2] string error message
local function autotermrestore()
if global_backup then
return nil, "global terminal backup was already set up"
end
global_backup = termbackup()
add_gc_method(global_backup, function(self)
termrestore(self) end)
return true
end


-- Export module table
return {
termbackup = termbackup,
termrestore = termrestore,
autotermrestore = autotermrestore,
}
37 changes: 37 additions & 0 deletions examples/compat.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
-- This example shows how to remove platform differences to create a
-- cross-platform level playing field.

local sys = require "system"



if sys.is_windows then
-- Windows holds multiple copies of environment variables, to ensure `getenv`
-- returns what `setenv` sets we need to use the `system.getenv` instead of
-- `os.getenv`.
os.getenv = sys.getenv -- luacheck: ignore

-- Set up the terminal to handle ANSI escape sequences on Windows.
if sys.isatty(io.stdout) then
sys.setconsoleflags(io.stdout, sys.getconsoleflags(io.stdout) + sys.COF_VIRTUAL_TERMINAL_PROCESSING)
end
if sys.isatty(io.stderr) then
sys.setconsoleflags(io.stderr, sys.getconsoleflags(io.stderr) + sys.COF_VIRTUAL_TERMINAL_PROCESSING)
end
if sys.isatty(io.stdin) then
sys.setconsoleflags(io.stdin, sys.getconsoleflags(io.stdout) + sys.ENABLE_VIRTUAL_TERMINAL_INPUT)
end


else
-- On Posix, one can set a variable to an empty string, but on Windows, this
-- will remove the variable from the environment. To make this consistent
-- across platforms, we will remove the variable from the environment if the
-- value is an empty string.
local old_setenv = sys.setenv
function sys.setenv(name, value)
if value == "" then value = nil end
return old_setenv(name, value)
end
end

144 changes: 0 additions & 144 deletions src/term.c
Original file line number Diff line number Diff line change
Expand Up @@ -671,142 +671,6 @@ static int lst_getnonblock(lua_State *L)
return 1;
}

/*-------------------------------------------------------------------------
* backup/restore terminal state
*-------------------------------------------------------------------------*/

static const char *backup_key = "LuaSystem.TermBackup";
static const char *backup_key_mt = "LuaSystem.TermBackup.mt";

typedef struct TermBackup {
#ifdef _WIN32
DWORD stdin_value;
DWORD stdout_value;
DWORD stderr_value;
#else
int stdin_file_flags;
int stdout_file_flags;
int stderr_file_flags;
// TODO: unix term restore
#endif
} TermBackup;



/***
Stores the current console flags (Windows), and the fileflags (Posix).
Creates a backup of the console settings. This backup can be restored using `termrestore`.
Upon exiting the Lua state, the backup will be automatically restored.
@function termbackup
@treturn[1] boolean true if the backup was successful
@treturn[2] nil
@treturn[2] string error message, if the backup already existed
*/
static int lst_termbackup(lua_State *L) {
// Check if the backup already exists in the registry
lua_getfield(L, LUA_REGISTRYINDEX, backup_key);
if (!lua_isnil(L, -1)) {
lua_pushnil(L);
lua_pushliteral(L, "backup already exists");
return 2;
}

lua_pop(L, 1); // Pop the nil value off the stack

TermBackup *backup = (TermBackup *)lua_newuserdata(L, sizeof(TermBackup));

#ifdef _WIN32
// Get current state of the console flags
if (!GetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), &backup->stdin_value)) {
backup->stdin_value = -1;
};
if (!GetConsoleMode(GetStdHandle(STD_OUTPUT_HANDLE), &backup->stdout_value)) {
backup->stdout_value = -1;
};
if (!GetConsoleMode(GetStdHandle(STD_ERROR_HANDLE), &backup->stderr_value)) {
backup->stderr_value = -1;
};

#else
backup->stdin_file_flags = fcntl(STDIN_FILENO, F_GETFL, 0);
backup->stdout_file_flags = fcntl(STDOUT_FILENO, F_GETFL, 0);
backup->stderr_file_flags = fcntl(STDERR_FILENO, F_GETFL, 0);
// TODO: unix term backup

#endif
luaL_getmetatable(L, backup_key_mt);
lua_setmetatable(L, -2);

// Store the backup in the registry
lua_setfield(L, LUA_REGISTRYINDEX, backup_key);

lua_pushboolean(L, TRUE);
return 1;
}


/***
Restores the console flags backup (Windows), and the fileflags (Posix).
Restores the console settings from a backup created with `termbackup`.
It will automatically be restored when the Lua state is closed, if not done already.
@function termrestore
@treturn[1] boolean true if the restore was successful
@treturn[2] nil
@treturn[2] string error message, if the backup didn't exist (anymore)
*/
static int lst_termrestore(lua_State *L) {
lua_getfield(L, LUA_REGISTRYINDEX, backup_key);
if (lua_isnil(L, -1)) {
lua_pushnil(L);
lua_pushliteral(L, "backup does not exist");
return 2;
}
TermBackup *backup = (TermBackup *)lua_touserdata(L, -1);

// Restore the backup values
#ifdef _WIN32
if (backup->stdin_value != -1) {
SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), backup->stdin_value);
backup->stdin_value = -1;
}
if (backup->stdout_value != -1) {
SetConsoleMode(GetStdHandle(STD_OUTPUT_HANDLE), backup->stdout_value);
backup->stdout_value = -1;
}
if (backup->stderr_value != -1) {
SetConsoleMode(GetStdHandle(STD_ERROR_HANDLE), backup->stderr_value);
backup->stderr_value = -1;
}

#else
if (backup->stdin_file_flags != -1) {
fcntl(STDIN_FILENO, F_SETFL, backup->stdin_file_flags);
backup->stdin_file_flags = -1;
}
if (backup->stdout_file_flags != -1) {
fcntl(STDOUT_FILENO, F_SETFL, backup->stdout_file_flags);
backup->stdout_file_flags = -1;
}
if (backup->stderr_file_flags != -1) {
fcntl(STDERR_FILENO, F_SETFL, backup->stderr_file_flags);
backup->stderr_file_flags = -1;
}
// TODO: unix term restore

#endif
lua_pushboolean(L, TRUE);
return 1;
}


/* GC handler for the terminal backup such that on exit it gets restored automatically */
static int termbackup_gc(lua_State *L) {
lst_termrestore(L);
// printf("termbackup_gc\n");
return 0;
}


/*-------------------------------------------------------------------------
Expand Down Expand Up @@ -882,8 +746,6 @@ static luaL_Reg func[] = {
{ "tcsetattr", lst_tcsetattr },
{ "getnonblock", lst_setnonblock },
{ "setnonblock", lst_setnonblock },
{ "termbackup", lst_termbackup },
{ "termrestore", lst_termrestore },
{ "readkey", lst_readkey },
{ "keypressed", lst_keypressed },
{ NULL, NULL }
Expand Down Expand Up @@ -928,12 +790,6 @@ void term_open(lua_State *L) {
lua_setfield(L, -2, nix_tcsetattr_actions[i].name);
}

// set up backup/restore meta-table
luaL_newmetatable(L, backup_key_mt);
lua_pushcfunction(L, termbackup_gc);
lua_setfield(L, -2, "__gc");
lua_pop(L, 1); // pop the metatable from the stack

// export functions
luaL_setfuncs(L, func, 0);
}

0 comments on commit 1aba4eb

Please sign in to comment.