Added console history handling with arrow keys.
This commit is contained in:
@@ -1,24 +1,24 @@
|
|||||||
[Window][Sessions [Table View]]
|
[Window][Sessions [Table View]]
|
||||||
Pos=10,43
|
Pos=10,43
|
||||||
Size=2117,223
|
Size=2117,435
|
||||||
Collapsed=0
|
Collapsed=0
|
||||||
DockId=0x00000003,0
|
DockId=0x00000003,0
|
||||||
|
|
||||||
[Window][Listeners]
|
[Window][Listeners]
|
||||||
Pos=10,268
|
Pos=10,480
|
||||||
Size=2528,1081
|
Size=2528,869
|
||||||
Collapsed=0
|
Collapsed=0
|
||||||
DockId=0x00000002,0
|
DockId=0x00000006,0
|
||||||
|
|
||||||
[Window][Eventlog]
|
[Window][Eventlog]
|
||||||
Pos=2129,43
|
Pos=2129,43
|
||||||
Size=409,223
|
Size=409,435
|
||||||
Collapsed=0
|
Collapsed=0
|
||||||
DockId=0x00000004,0
|
DockId=0x00000004,0
|
||||||
|
|
||||||
[Window][Dear ImGui Demo]
|
[Window][Dear ImGui Demo]
|
||||||
Pos=2129,43
|
Pos=2129,43
|
||||||
Size=409,223
|
Size=409,435
|
||||||
Collapsed=0
|
Collapsed=0
|
||||||
DockId=0x00000004,1
|
DockId=0x00000004,1
|
||||||
|
|
||||||
@@ -28,16 +28,16 @@ Size=2548,1359
|
|||||||
Collapsed=0
|
Collapsed=0
|
||||||
|
|
||||||
[Window][[FACEDEAD] bob@LAPTOP-02]
|
[Window][[FACEDEAD] bob@LAPTOP-02]
|
||||||
Pos=10,268
|
Pos=10,480
|
||||||
Size=2528,1081
|
Size=2528,869
|
||||||
Collapsed=0
|
Collapsed=0
|
||||||
DockId=0x00000002,1
|
DockId=0x00000006,1
|
||||||
|
|
||||||
[Window][[C9D8E7F6] charlie@SERVER-03]
|
[Window][[C9D8E7F6] charlie@SERVER-03]
|
||||||
Pos=10,268
|
Pos=10,480
|
||||||
Size=2528,1081
|
Size=1261,869
|
||||||
Collapsed=0
|
Collapsed=0
|
||||||
DockId=0x00000002,1
|
DockId=0x00000005,0
|
||||||
|
|
||||||
[Window][Debug##Default]
|
[Window][Debug##Default]
|
||||||
Pos=60,60
|
Pos=60,60
|
||||||
@@ -45,22 +45,22 @@ Size=400,400
|
|||||||
Collapsed=0
|
Collapsed=0
|
||||||
|
|
||||||
[Window][[G1H2I3J5] diana@WORKSTATION-04]
|
[Window][[G1H2I3J5] diana@WORKSTATION-04]
|
||||||
Pos=10,268
|
Pos=10,480
|
||||||
Size=2528,1081
|
Size=2528,869
|
||||||
Collapsed=0
|
Collapsed=0
|
||||||
DockId=0x00000002,1
|
DockId=0x00000006,1
|
||||||
|
|
||||||
[Window][[DEADBEEF] alice@DESKTOP-01]
|
[Window][[DEADBEEF] alice@DESKTOP-01]
|
||||||
Pos=10,604
|
Pos=10,604
|
||||||
Size=2848,1081
|
Size=2848,1081
|
||||||
Collapsed=0
|
Collapsed=0
|
||||||
DockId=0x00000002,2
|
DockId=0x00000006,2
|
||||||
|
|
||||||
[Window][Example: Console]
|
[Window][Example: Console]
|
||||||
Pos=10,466
|
Pos=10,466
|
||||||
Size=1888,523
|
Size=1888,523
|
||||||
Collapsed=0
|
Collapsed=0
|
||||||
DockId=0x00000002,1
|
DockId=0x00000006,1
|
||||||
|
|
||||||
[Window][Example: Assets Browser]
|
[Window][Example: Assets Browser]
|
||||||
Pos=60,60
|
Pos=60,60
|
||||||
@@ -93,8 +93,10 @@ Column 0 Sort=0v
|
|||||||
|
|
||||||
[Docking][Data]
|
[Docking][Data]
|
||||||
DockSpace ID=0x85940918 Window=0x260A4489 Pos=10,43 Size=2528,1306 Split=Y
|
DockSpace ID=0x85940918 Window=0x260A4489 Pos=10,43 Size=2528,1306 Split=Y
|
||||||
DockNode ID=0x00000001 Parent=0x85940918 SizeRef=1024,559 Split=X
|
DockNode ID=0x00000001 Parent=0x85940918 SizeRef=1024,435 Split=X
|
||||||
DockNode ID=0x00000003 Parent=0x00000001 SizeRef=613,159 CentralNode=1 Selected=0x61E02D75
|
DockNode ID=0x00000003 Parent=0x00000001 SizeRef=613,159 CentralNode=1 Selected=0x61E02D75
|
||||||
DockNode ID=0x00000004 Parent=0x00000001 SizeRef=409,159 Selected=0x5E5F7166
|
DockNode ID=0x00000004 Parent=0x00000001 SizeRef=409,159 Selected=0x5E5F7166
|
||||||
DockNode ID=0x00000002 Parent=0x85940918 SizeRef=1024,1081 Selected=0x65D642C0
|
DockNode ID=0x00000002 Parent=0x85940918 SizeRef=1024,869 Split=X Selected=0x8D780333
|
||||||
|
DockNode ID=0x00000005 Parent=0x00000002 SizeRef=1261,869 Selected=0x65D642C0
|
||||||
|
DockNode ID=0x00000006 Parent=0x00000002 SizeRef=1265,869 Selected=0x8D780333
|
||||||
|
|
||||||
|
|||||||
@@ -6,12 +6,18 @@ import ../../common/[types]
|
|||||||
const MAX_INPUT_LENGTH = 512
|
const MAX_INPUT_LENGTH = 512
|
||||||
type
|
type
|
||||||
ConsoleComponent* = ref object of RootObj
|
ConsoleComponent* = ref object of RootObj
|
||||||
agent: Agent
|
agent*: Agent
|
||||||
showConsole*: bool
|
showConsole*: bool
|
||||||
inputBuffer: array[MAX_INPUT_LENGTH, char]
|
inputBuffer: array[MAX_INPUT_LENGTH, char]
|
||||||
console: ConsoleItems
|
console*: ConsoleItems
|
||||||
|
history: seq[string]
|
||||||
|
historyPosition: int
|
||||||
|
currentInput: string
|
||||||
textSelect: ptr TextSelect
|
textSelect: ptr TextSelect
|
||||||
|
|
||||||
|
#[
|
||||||
|
Helper functions for text selection
|
||||||
|
]#
|
||||||
proc getItemText(item: ConsoleItem): cstring =
|
proc getItemText(item: ConsoleItem): cstring =
|
||||||
let timestamp = item.timestamp.format("dd-MM-yyyy HH:mm:ss")
|
let timestamp = item.timestamp.format("dd-MM-yyyy HH:mm:ss")
|
||||||
return fmt"[{timestamp}] {$item.itemType} {item.text}".string
|
return fmt"[{timestamp}] {$item.itemType} {item.text}".string
|
||||||
@@ -36,11 +42,69 @@ proc Console*(agent: Agent): ConsoleComponent =
|
|||||||
result.agent = agent
|
result.agent = agent
|
||||||
result.showConsole = true
|
result.showConsole = true
|
||||||
zeroMem(addr result.inputBuffer[0], MAX_INPUT_LENGTH)
|
zeroMem(addr result.inputBuffer[0], MAX_INPUT_LENGTH)
|
||||||
|
|
||||||
result.console = new ConsoleItems
|
result.console = new ConsoleItems
|
||||||
result.console.items = @[]
|
result.console.items = @[]
|
||||||
|
result.history = @[]
|
||||||
|
result.historyPosition = -1
|
||||||
|
result.currentInput = ""
|
||||||
result.textSelect = textselect_create(getLineAtIndex, getNumLines, cast[pointer](result.console), 0)
|
result.textSelect = textselect_create(getLineAtIndex, getNumLines, cast[pointer](result.console), 0)
|
||||||
|
|
||||||
|
#[
|
||||||
|
Text input callback function for managing console history and autocompletion
|
||||||
|
]#
|
||||||
|
proc callback(data: ptr ImGuiInputTextCallbackData): cint {.cdecl.} =
|
||||||
|
|
||||||
|
let component = cast[ConsoleComponent](data.UserData)
|
||||||
|
|
||||||
|
case data.EventFlag:
|
||||||
|
of ImGui_InputTextFlags_CallbackHistory.int32:
|
||||||
|
# Handle command history using arrow-keys
|
||||||
|
|
||||||
|
# Store current input
|
||||||
|
if component.historyPosition == -1:
|
||||||
|
component.currentInput = $(data.Buf)
|
||||||
|
|
||||||
|
let prev = component.historyPosition
|
||||||
|
|
||||||
|
# Move to a new console history item
|
||||||
|
if data.EventKey == ImGuiKey_UpArrow:
|
||||||
|
if component.history.len() > 0:
|
||||||
|
if component.historyPosition < 0: # We are at the current input and move to the last item in the console history
|
||||||
|
component.historyPosition = component.history.len() - 1
|
||||||
|
else:
|
||||||
|
component.historyPosition = max(0, component.historyPosition - 1)
|
||||||
|
|
||||||
|
elif data.EventKey == ImGuiKey_DownArrow:
|
||||||
|
if component.historyPosition != -1:
|
||||||
|
component.historyPosition = min(component.history.len(), component.historyPosition + 1)
|
||||||
|
|
||||||
|
if component.historyPosition == component.history.len():
|
||||||
|
component.historyPosition = -1
|
||||||
|
|
||||||
|
# Update the text buffer if another item was selected
|
||||||
|
if prev != component.historyPosition:
|
||||||
|
let newText = if component.historyPosition == -1:
|
||||||
|
component.currentInput
|
||||||
|
else:
|
||||||
|
component.history[component.historyPosition]
|
||||||
|
|
||||||
|
# Replace text input
|
||||||
|
data.ImGuiInputTextCallbackData_DeleteChars(0, data.BufTextLen)
|
||||||
|
data.ImGuiInputTextCallbackData_InsertChars(0, newText.cstring, nil)
|
||||||
|
|
||||||
|
# Set the cursor to the end of the updated input text
|
||||||
|
data.CursorPos = newText.len().cint
|
||||||
|
data.SelectionStart = newText.len().cint
|
||||||
|
data.SelectionEnd = newText.len().cint
|
||||||
|
|
||||||
|
return 0
|
||||||
|
|
||||||
|
of ImGui_InputTextFlags_CallbackCompletion.int32:
|
||||||
|
# Handle Tab-autocompletion
|
||||||
|
discard
|
||||||
|
|
||||||
|
else: discard
|
||||||
|
|
||||||
proc draw*(component: ConsoleComponent) =
|
proc draw*(component: ConsoleComponent) =
|
||||||
igBegin(fmt"[{component.agent.agentId}] {component.agent.username}@{component.agent.hostname}", addr component.showConsole, 0)
|
igBegin(fmt"[{component.agent.agentId}] {component.agent.username}@{component.agent.hostname}", addr component.showConsole, 0)
|
||||||
defer: igEnd()
|
defer: igEnd()
|
||||||
@@ -49,15 +113,16 @@ proc draw*(component: ConsoleComponent) =
|
|||||||
|
|
||||||
#[
|
#[
|
||||||
Console items/text section using ImGuiTextSelect in a child window
|
Console items/text section using ImGuiTextSelect in a child window
|
||||||
Supports:
|
Features:
|
||||||
- horizontal+vertical scrolling,
|
- Horizontal+vertical scrolling,
|
||||||
- autoscroll
|
- Autoscroll
|
||||||
- colored text
|
- Colored text output
|
||||||
- text selection and copy functionality
|
- Text highlighting, copy/paste
|
||||||
|
|
||||||
Problems I encountered with other approaches (Multi-line Text Input, TextEditor, ...):
|
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/383#issuecomment-2080346129
|
||||||
- https://github.com/ocornut/imgui/issues/950
|
- https://github.com/ocornut/imgui/issues/950
|
||||||
|
|
||||||
Huge thanks to @dinau for implementing ImGuiTextSelect into imguin very rapidly after I requested it.
|
Huge thanks to @dinau for implementing ImGuiTextSelect into imguin very rapidly after I requested it.
|
||||||
]#
|
]#
|
||||||
let consolePadding: float = 10.0f
|
let consolePadding: float = 10.0f
|
||||||
@@ -113,8 +178,8 @@ proc draw*(component: ConsoleComponent) =
|
|||||||
igGetContentRegionAvail(addr availableWidth)
|
igGetContentRegionAvail(addr availableWidth)
|
||||||
igSetNextItemWidth(availableWidth.x)
|
igSetNextItemWidth(availableWidth.x)
|
||||||
|
|
||||||
let inputFlags = ImGuiInputTextFlags_EnterReturnsTrue.int32 or ImGuiInputTextFlags_EscapeClearsAll.int32 # or ImGuiInputTextFlags_CallbackCompletion.int32 or ImGuiInputTextFlags_CallbackHistory.int32
|
let inputFlags = ImGuiInputTextFlags_EnterReturnsTrue.int32 or ImGuiInputTextFlags_EscapeClearsAll.int32 or ImGuiInputTextFlags_CallbackHistory.int32 or ImGuiInputTextFlags_CallbackCompletion.int32
|
||||||
if igInputText("##Input", addr component.inputBuffer[0], MAX_INPUT_LENGTH, inputFlags, nil, nil):
|
if igInputText("##Input", addr component.inputBuffer[0], MAX_INPUT_LENGTH, inputFlags, callback, cast[pointer](component)):
|
||||||
|
|
||||||
let command = $(addr component.inputBuffer[0]).cstring
|
let command = $(addr component.inputBuffer[0]).cstring
|
||||||
let commandItem = ConsoleItem(
|
let commandItem = ConsoleItem(
|
||||||
@@ -125,6 +190,11 @@ proc draw*(component: ConsoleComponent) =
|
|||||||
component.console.items.add(commandItem)
|
component.console.items.add(commandItem)
|
||||||
|
|
||||||
# TODO: Handle command execution
|
# TODO: Handle command execution
|
||||||
|
# console.handleCommand(command)
|
||||||
|
|
||||||
|
# Add command to console history
|
||||||
|
component.history.add(command)
|
||||||
|
component.historyPosition = -1
|
||||||
|
|
||||||
zeroMem(addr component.inputBuffer[0], MAX_INPUT_LENGTH)
|
zeroMem(addr component.inputBuffer[0], MAX_INPUT_LENGTH)
|
||||||
focusInput = true
|
focusInput = true
|
||||||
|
|||||||
@@ -75,11 +75,11 @@ type
|
|||||||
# LOG_WARNING = "[ ! ] "
|
# LOG_WARNING = "[ ! ] "
|
||||||
# LOG_COMMAND = "[ > ] "
|
# LOG_COMMAND = "[ > ] "
|
||||||
# LOG_OUTPUT = ""
|
# LOG_OUTPUT = ""
|
||||||
LOG_INFO = "[INFO] "
|
LOG_INFO = "[INFO]"
|
||||||
LOG_ERROR = "[FAIL] "
|
LOG_ERROR = "[FAIL]"
|
||||||
LOG_SUCCESS = "[DONE] "
|
LOG_SUCCESS = "[DONE]"
|
||||||
LOG_WARNING = "[WARN] "
|
LOG_WARNING = "[WARN]"
|
||||||
LOG_COMMAND = "[>>>>] "
|
LOG_COMMAND = "[>>>>]"
|
||||||
LOG_OUTPUT = ""
|
LOG_OUTPUT = ""
|
||||||
|
|
||||||
SleepObfuscationTechnique* = enum
|
SleepObfuscationTechnique* = enum
|
||||||
|
|||||||
@@ -43,16 +43,16 @@ template writeLine*(cq: Conquest, args: varargs[untyped] = "") =
|
|||||||
|
|
||||||
# Wrapper functions for logging/console output
|
# Wrapper functions for logging/console output
|
||||||
template info*(cq: Conquest, args: varargs[untyped] = "") =
|
template info*(cq: Conquest, args: varargs[untyped] = "") =
|
||||||
cq.writeLine(fgBlack, styleBright, fmt"[{getTimestamp()}] ", $LOG_INFO, resetStyle, args)
|
cq.writeLine(fgBlack, styleBright, fmt"[{getTimestamp()}] ", $LOG_INFO, resetStyle, " ", args)
|
||||||
|
|
||||||
template error*(cq: Conquest, args: varargs[untyped] = "") =
|
template error*(cq: Conquest, args: varargs[untyped] = "") =
|
||||||
cq.writeLine(fgBlack, styleBright, fmt"[{getTimestamp()}] ", fgRed, $LOG_ERROR, resetStyle, args)
|
cq.writeLine(fgBlack, styleBright, fmt"[{getTimestamp()}] ", fgRed, $LOG_ERROR, resetStyle, " ", args)
|
||||||
|
|
||||||
template success*(cq: Conquest, args: varargs[untyped] = "") =
|
template success*(cq: Conquest, args: varargs[untyped] = "") =
|
||||||
cq.writeLine(fgBlack, styleBright, fmt"[{getTimestamp()}] ", fgGreen, $LOG_SUCCESS, resetStyle, args)
|
cq.writeLine(fgBlack, styleBright, fmt"[{getTimestamp()}] ", fgGreen, $LOG_SUCCESS, resetStyle, " ", args)
|
||||||
|
|
||||||
template warning*(cq: Conquest, args: varargs[untyped] = "") =
|
template warning*(cq: Conquest, args: varargs[untyped] = "") =
|
||||||
cq.writeLine(fgBlack, styleBright, fmt"[{getTimestamp()}] ", fgYellow, styleDim, $LOG_WARNING, resetStyle, args)
|
cq.writeLine(fgBlack, styleBright, fmt"[{getTimestamp()}] ", fgYellow, styleDim, $LOG_WARNING, resetStyle, " ", args)
|
||||||
|
|
||||||
template input*(cq: Conquest, args: varargs[untyped] = "") =
|
template input*(cq: Conquest, args: varargs[untyped] = "") =
|
||||||
if cq.interactAgent != nil:
|
if cq.interactAgent != nil:
|
||||||
|
|||||||
Reference in New Issue
Block a user