2025-09-14 22:55:44 +02:00
import strformat , strutils , times
2025-09-10 18:25:15 +02:00
import imguin / [ cimgui , glfw_opengl , simple ]
import .. / utils / appImGui
import .. / .. / common / [ types ]
2025-09-16 20:17:48 +02:00
const MAX_INPUT_LENGTH = 512
type
2025-09-10 18:25:15 +02:00
ConsoleComponent * = ref object of RootObj
agent : Agent
showConsole * : bool
2025-09-16 20:17:48 +02:00
inputBuffer : array [ MAX_INPUT_LENGTH , char ]
console : ConsoleItems
2025-09-14 22:55:44 +02:00
textSelect : ptr TextSelect
2025-09-16 20:17:48 +02:00
proc getItemText ( item : ConsoleItem ) : cstring =
let timestamp = item . timestamp . format ( " dd-MM-yyyy HH:mm:ss " )
return fmt" [{timestamp}] { $item .itemType} {item.text} " . string
2025-09-14 22:55:44 +02:00
proc getNumLines ( data : pointer ) : csize_t {. cdecl . } =
if data . isNil :
return 0
2025-09-16 20:17:48 +02:00
let console = cast [ ConsoleItems ] ( data )
return console . items . len ( ) . csize_t
2025-09-14 22:55:44 +02:00
proc getLineAtIndex ( i : csize_t , data : pointer , outLen : ptr csize_t ) : cstring {. cdecl . } =
if data . isNil :
return nil
2025-09-16 20:17:48 +02:00
let console = cast [ ConsoleItems ] ( data )
let line = getItemText ( console . items [ i ] )
2025-09-14 22:55:44 +02:00
if not outLen . isNil :
outLen [ ] = line . len . csize_t
return line
proc Console * ( agent : Agent ) : ConsoleComponent =
2025-09-10 18:25:15 +02:00
result = new ConsoleComponent
result . agent = agent
result . showConsole = true
2025-09-16 20:17:48 +02:00
zeroMem ( addr result . inputBuffer [ 0 ] , MAX_INPUT_LENGTH )
2025-09-11 19:11:11 +02:00
2025-09-16 20:17:48 +02:00
result . console = new ConsoleItems
result . console . items = @ [ ]
result . textSelect = textselect_create ( getLineAtIndex , getNumLines , cast [ pointer ] ( result . console ) , 0 )
2025-09-10 18:25:15 +02:00
2025-09-14 22:55:44 +02:00
proc draw * ( component : ConsoleComponent ) =
2025-09-10 18:25:15 +02:00
igBegin ( fmt" [{component.agent.agentId}] {component.agent.username}@{component.agent.hostname} " , addr component . showConsole , 0 )
2025-09-14 22:55:44 +02:00
defer : igEnd ( )
2025-09-16 20:17:48 +02:00
var focusInput = false
2025-09-12 10:15:13 +02:00
#[
2025-09-14 22:55:44 +02:00
Console items / text section using ImGuiTextSelect in a child window
Supports :
- horizontal + vertical scrolling ,
- autoscroll
- colored text
- text selection and copy functionality
Problems I encountered with other approaches ( Multi - line Text Input , TextEditor , .. . ) :
- https : / / github . com / ocornut / imgui / issues / 383 #issuecomment-2080346129
- https : / / github . com / ocornut / imgui / issues / 950
Huge thanks to @ dinau for implementing ImGuiTextSelect into imguin very rapidly after I requested it .
2025-09-12 10:15:13 +02:00
] #
2025-09-16 20:17:48 +02:00
let consolePadding : float = 10 .0 f
let footerHeight = ( consolePadding * 2 ) + ( igGetStyle ( ) . ItemSpacing . y + igGetFrameHeightWithSpacing ( ) )
let textSpacing = igGetStyle ( ) . ItemSpacing . x
# Padding
igDummy ( vec2 ( 0 .0 f, consolePadding))
try :
# Set styles of the console window
igPushStyleColor_Vec4 ( ImGui_Col_FrameBg . int32 , vec4 ( 0 .1 f, 0.1f, 0.1f, 1.0f))
igPushStyleColor_Vec4 ( ImGui_Col_ScrollbarBg . int32 , vec4 ( 0 .1 f, 0.1f, 0.1f, 1.0f))
igPushStyleColor_Vec4 ( ImGui_Col_Border . int32 , vec4 ( 0 .2 f, 0.2f, 0.2f, 1.0f))
igPushStyleVar_Float ( ImGui_StyleVar_FrameBorderSize . int32 , 1 .0 f)
let childWindowFlags = ImGuiChildFlags_NavFlattened . int32 or ImGui_ChildFlags_Borders . int32 or ImGui_ChildFlags_AlwaysUseWindowPadding . int32 or ImGuiChildFlags_FrameStyle . int32
if igBeginChild_Str ( " ##Console " , vec2 ( - 1 .0 f, -footerHeight), childWindowFlags, ImGuiWindowFlags_HorizontalScrollbar.int32):
# Display console items
for entry in component . console . items :
let timestamp = entry . timestamp . format ( " dd-MM-yyyy HH:mm:ss " )
igTextColored ( vec4 ( 0 .6 f, 0.6f, 0.6f, 1.0f), fmt"[{timestamp}]".cstring)
igSameLine ( 0 .0 f, textSpacing)
igTextColored ( vec4 ( 0 .0 f, 1.0f, 1.0f, 1.0f), $entry.itemType)
igSameLine ( 0 .0 f, textSpacing)
igTextUnformatted ( entry . text . cstring , nil )
component . textSelect . textselect_update ( )
# Auto-scroll to bottom
if igGetScrollY ( ) > = igGetScrollMaxY ( ) :
igSetScrollHereY ( 1 .0 f)
except IndexDefect :
# CTRL+A crashes when no items are in the console
discard
2025-09-14 22:55:44 +02:00
2025-09-16 20:17:48 +02:00
finally :
igPopStyleColor ( 3 )
igPopStyleVar ( 1 )
igEndChild ( )
2025-09-14 22:55:44 +02:00
2025-09-16 20:17:48 +02:00
# Padding
igDummy ( vec2 ( 0 .0 f, consolePadding))
2025-09-14 22:55:44 +02:00
2025-09-12 10:15:13 +02:00
#[
Input field with prompt indicator
2025-09-14 22:55:44 +02:00
] #
igText ( fmt" [{component.agent.agentId}] " )
2025-09-16 20:17:48 +02:00
igSameLine ( 0 .0 f, textSpacing)
2025-09-14 22:55:44 +02:00
# Calculate available width for input
2025-09-12 10:15:13 +02:00
var availableWidth : ImVec2
igGetContentRegionAvail ( addr availableWidth )
igSetNextItemWidth ( availableWidth . x )
2025-09-14 22:55:44 +02:00
2025-09-16 20:17:48 +02:00
let inputFlags = ImGuiInputTextFlags_EnterReturnsTrue . int32 or ImGuiInputTextFlags_EscapeClearsAll . int32 # or ImGuiInputTextFlags_CallbackCompletion.int32 or ImGuiInputTextFlags_CallbackHistory.int32
if igInputText ( " ##Input " , addr component . inputBuffer [ 0 ] , MAX_INPUT_LENGTH , inputFlags , nil , nil ) :
let command = $ ( addr component . inputBuffer [ 0 ] ) . cstring
let commandItem = ConsoleItem (
timestamp : now ( ) ,
itemType : LOG_COMMAND ,
text : command
)
component . console . items . add ( commandItem )
# TODO: Handle command execution
zeroMem ( addr component . inputBuffer [ 0 ] , MAX_INPUT_LENGTH )
focusInput = true
2025-09-14 22:55:44 +02:00
#[
Session information ( optional footer )
] #
2025-09-12 10:15:13 +02:00
# igSeparator()
2025-09-14 22:55:44 +02:00
# let sessionInfo = fmt"{component.agent.username}@{component.agent.hostname} [{component.agent.ip}]"
# igText(sessionInfo)
2025-09-12 10:15:13 +02:00
2025-09-11 18:18:13 +02:00
igSetItemDefaultFocus ( )
2025-09-16 20:17:48 +02:00
if focusInput :
igSetKeyboardFocusHere ( - 1 )