diff --git a/src/client/layout.ini b/src/client/layout.ini index e95954f..08350c7 100644 --- a/src/client/layout.ini +++ b/src/client/layout.ini @@ -1,24 +1,24 @@ [Window][Sessions [Table View]] Pos=10,43 -Size=2117,223 +Size=2117,435 Collapsed=0 DockId=0x00000003,0 [Window][Listeners] -Pos=10,268 -Size=2528,1081 +Pos=10,480 +Size=2528,869 Collapsed=0 -DockId=0x00000002,0 +DockId=0x00000006,0 [Window][Eventlog] Pos=2129,43 -Size=409,223 +Size=409,435 Collapsed=0 DockId=0x00000004,0 [Window][Dear ImGui Demo] Pos=2129,43 -Size=409,223 +Size=409,435 Collapsed=0 DockId=0x00000004,1 @@ -28,16 +28,16 @@ Size=2548,1359 Collapsed=0 [Window][[FACEDEAD] bob@LAPTOP-02] -Pos=10,268 -Size=2528,1081 +Pos=10,480 +Size=2528,869 Collapsed=0 -DockId=0x00000002,1 +DockId=0x00000006,1 [Window][[C9D8E7F6] charlie@SERVER-03] -Pos=10,268 -Size=2528,1081 +Pos=10,480 +Size=1261,869 Collapsed=0 -DockId=0x00000002,1 +DockId=0x00000005,0 [Window][Debug##Default] Pos=60,60 @@ -45,22 +45,22 @@ Size=400,400 Collapsed=0 [Window][[G1H2I3J5] diana@WORKSTATION-04] -Pos=10,268 -Size=2528,1081 +Pos=10,480 +Size=2528,869 Collapsed=0 -DockId=0x00000002,1 +DockId=0x00000006,1 [Window][[DEADBEEF] alice@DESKTOP-01] Pos=10,604 Size=2848,1081 Collapsed=0 -DockId=0x00000002,2 +DockId=0x00000006,2 [Window][Example: Console] Pos=10,466 Size=1888,523 Collapsed=0 -DockId=0x00000002,1 +DockId=0x00000006,1 [Window][Example: Assets Browser] Pos=60,60 @@ -93,8 +93,10 @@ Column 0 Sort=0v [Docking][Data] 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=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 diff --git a/src/client/views/console.nim b/src/client/views/console.nim index bfd2b79..5178faf 100644 --- a/src/client/views/console.nim +++ b/src/client/views/console.nim @@ -6,12 +6,18 @@ import ../../common/[types] const MAX_INPUT_LENGTH = 512 type ConsoleComponent* = ref object of RootObj - agent: Agent + agent*: Agent showConsole*: bool inputBuffer: array[MAX_INPUT_LENGTH, char] - console: ConsoleItems + console*: ConsoleItems + history: seq[string] + historyPosition: int + currentInput: string textSelect: ptr TextSelect +#[ + Helper functions for text selection +]# proc getItemText(item: ConsoleItem): cstring = let timestamp = item.timestamp.format("dd-MM-yyyy HH:mm:ss") return fmt"[{timestamp}] {$item.itemType} {item.text}".string @@ -36,11 +42,69 @@ proc Console*(agent: Agent): ConsoleComponent = result.agent = agent result.showConsole = true zeroMem(addr result.inputBuffer[0], MAX_INPUT_LENGTH) - 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) +#[ + 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) = igBegin(fmt"[{component.agent.agentId}] {component.agent.username}@{component.agent.hostname}", addr component.showConsole, 0) defer: igEnd() @@ -49,15 +113,16 @@ proc draw*(component: ConsoleComponent) = #[ Console items/text section using ImGuiTextSelect in a child window - Supports: - - horizontal+vertical scrolling, - - autoscroll - - colored text - - text selection and copy functionality + Features: + - Horizontal+vertical scrolling, + - Autoscroll + - Colored text output + - Text highlighting, copy/paste 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 + - 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. ]# let consolePadding: float = 10.0f @@ -113,8 +178,8 @@ proc draw*(component: ConsoleComponent) = igGetContentRegionAvail(addr availableWidth) igSetNextItemWidth(availableWidth.x) - 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 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, callback, cast[pointer](component)): let command = $(addr component.inputBuffer[0]).cstring let commandItem = ConsoleItem( @@ -125,6 +190,11 @@ proc draw*(component: ConsoleComponent) = component.console.items.add(commandItem) # 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) focusInput = true diff --git a/src/common/types.nim b/src/common/types.nim index d53e14f..dc76220 100644 --- a/src/common/types.nim +++ b/src/common/types.nim @@ -75,11 +75,11 @@ type # LOG_WARNING = "[ ! ] " # LOG_COMMAND = "[ > ] " # LOG_OUTPUT = "" - LOG_INFO = "[INFO] " - LOG_ERROR = "[FAIL] " - LOG_SUCCESS = "[DONE] " - LOG_WARNING = "[WARN] " - LOG_COMMAND = "[>>>>] " + LOG_INFO = "[INFO]" + LOG_ERROR = "[FAIL]" + LOG_SUCCESS = "[DONE]" + LOG_WARNING = "[WARN]" + LOG_COMMAND = "[>>>>]" LOG_OUTPUT = "" SleepObfuscationTechnique* = enum diff --git a/src/server/core/logger.nim b/src/server/core/logger.nim index 1bc1515..a644f8b 100644 --- a/src/server/core/logger.nim +++ b/src/server/core/logger.nim @@ -43,16 +43,16 @@ template writeLine*(cq: Conquest, args: varargs[untyped] = "") = # Wrapper functions for logging/console output 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] = "") = - 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] = "") = - 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] = "") = - 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] = "") = if cq.interactAgent != nil: