Made dual list selection widget generic.

This commit is contained in:
Jakob Friedl
2025-09-25 10:01:49 +02:00
parent 8baf65a96d
commit a4456723ce
4 changed files with 75 additions and 64 deletions

View File

@@ -1,43 +1,43 @@
[Window][Sessions [Table View]] [Window][Sessions [Table View]]
Pos=10,43 Pos=10,43
Size=1141,338 Size=1310,279
Collapsed=0 Collapsed=0
DockId=0x00000003,0 DockId=0x00000003,0
[Window][Listeners] [Window][Listeners]
Pos=10,383 Pos=10,324
Size=1888,654 Size=1888,665
Collapsed=0 Collapsed=0
DockId=0x00000006,0 DockId=0x00000006,0
[Window][Eventlog] [Window][Eventlog]
Pos=1153,43 Pos=1322,43
Size=745,338 Size=576,279
Collapsed=0 Collapsed=0
DockId=0x00000004,0 DockId=0x00000004,0
[Window][Dear ImGui Demo] [Window][Dear ImGui Demo]
Pos=1153,43 Pos=1322,43
Size=745,338 Size=576,279
Collapsed=0 Collapsed=0
DockId=0x00000004,1 DockId=0x00000004,1
[Window][Dockspace] [Window][Dockspace]
Pos=0,0 Pos=0,0
Size=1908,1047 Size=1908,999
Collapsed=0 Collapsed=0
[Window][[FACEDEAD] bob@LAPTOP-02] [Window][[FACEDEAD] bob@LAPTOP-02]
Pos=10,661 Pos=10,395
Size=2848,1024 Size=1888,594
Collapsed=0 Collapsed=0
DockId=0x00000006,3 DockId=0x00000006,1
[Window][[C9D8E7F6] charlie@SERVER-03] [Window][[C9D8E7F6] charlie@SERVER-03]
Pos=10,661 Pos=10,324
Size=2848,1024 Size=1888,665
Collapsed=0 Collapsed=0
DockId=0x00000006,4 DockId=0x00000006,1
[Window][Debug##Default] [Window][Debug##Default]
Pos=60,60 Pos=60,60
@@ -45,16 +45,16 @@ Size=400,400
Collapsed=0 Collapsed=0
[Window][[G1H2I3J5] diana@WORKSTATION-04] [Window][[G1H2I3J5] diana@WORKSTATION-04]
Pos=10,372 Pos=10,125
Size=1888,617 Size=784,665
Collapsed=0 Collapsed=0
DockId=0x00000006,1 DockId=0x00000006,1
[Window][[DEADBEEF] alice@DESKTOP-01] [Window][[DEADBEEF] alice@DESKTOP-01]
Pos=10,661 Pos=10,324
Size=2848,1024 Size=1888,665
Collapsed=0 Collapsed=0
DockId=0x00000006,2 DockId=0x00000006,1
[Window][Example: Console] [Window][Example: Console]
Pos=10,661 Pos=10,661
@@ -119,23 +119,23 @@ IsChild=1
Size=1363,540 Size=1363,540
[Window][Generate Payload] [Window][Generate Payload]
Pos=704,185 Pos=1075,509
Size=500,677 Size=717,677
Collapsed=0 Collapsed=0
[Window][Generate Payload/0_B6B17D5F] [Window][Generate Payload/0_B6B17D5F]
IsChild=1 IsChild=1
Size=217,310 Size=326,310
[Table][0x32886A44,8] [Table][0x32886A44,8]
Column 0 Weight=0.6465 Column 0 Weight=0.6432
Column 1 Weight=0.9697 Column 1 Weight=0.9647
Column 2 Weight=0.6696 Column 2 Weight=0.6694
Column 3 Weight=0.9639 Column 3 Weight=1.0960
Column 4 Weight=1.7143 Column 4 Weight=1.5816
Column 5 Weight=1.1544 Column 5 Weight=1.1551
Column 6 Weight=0.4271 Column 6 Weight=0.4331
Column 7 Weight=1.4546 Column 7 Weight=1.4570
[Table][0xB6880529,2] [Table][0xB6880529,2]
RefScale=27 RefScale=27
@@ -149,9 +149,9 @@ Column 3 Weight=0.9746
[Docking][Data] [Docking][Data]
DockNode ID=0x00000009 Pos=100,200 Size=754,103 Selected=0x64D005CF DockNode ID=0x00000009 Pos=100,200 Size=754,103 Selected=0x64D005CF
DockSpace ID=0x85940918 Window=0x260A4489 Pos=10,43 Size=1888,994 Split=Y DockSpace ID=0x85940918 Window=0x260A4489 Pos=10,43 Size=1888,946 Split=Y
DockNode ID=0x00000005 Parent=0x85940918 SizeRef=1888,338 Split=X DockNode ID=0x00000005 Parent=0x85940918 SizeRef=1888,279 Split=X
DockNode ID=0x00000003 Parent=0x00000005 SizeRef=2101,159 CentralNode=1 Selected=0x61E02D75 DockNode ID=0x00000003 Parent=0x00000005 SizeRef=1310,159 CentralNode=1 Selected=0x61E02D75
DockNode ID=0x00000004 Parent=0x00000005 SizeRef=745,159 Selected=0x5E5F7166 DockNode ID=0x00000004 Parent=0x00000005 SizeRef=576,159 Selected=0x5E5F7166
DockNode ID=0x00000006 Parent=0x85940918 SizeRef=1888,654 Selected=0x6BE22050 DockNode ID=0x00000006 Parent=0x85940918 SizeRef=1888,665 Selected=0x65D642C0

View File

@@ -172,20 +172,33 @@ proc draw*(component: ConsoleComponent, ws: WebSocket) =
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
let footerHeight = (consolePadding * 2) + (igGetStyle().ItemSpacing.y + igGetFrameHeightWithSpacing()) * 1.5f let footerHeight = (consolePadding * 2) + (igGetStyle().ItemSpacing.y + igGetFrameHeightWithSpacing()) * 0.75f
let textSpacing = igGetStyle().ItemSpacing.x let textSpacing = igGetStyle().ItemSpacing.x
# Padding # Padding
igDummy(vec2(0.0f, consolePadding)) igDummy(vec2(0.0f, consolePadding))
#[
Session information
]#
let domain = if component.agent.domain.isEmptyOrWhitespace(): "" else: fmt".{component.agent.domain}"
let sessionInfo = fmt"{component.agent.username}@{component.agent.hostname}{domain} | {component.agent.ip} | {$component.agent.pid}/{component.agent.process}"
igTextColored(GRAY, sessionInfo)
igSameLine(0.0f, 0.0f)
#[ #[
Filter & Options Filter & Options
]# ]#
var availableSize: ImVec2
igGetContentRegionAvail(addr availableSize)
var labelSize: ImVec2 var labelSize: ImVec2
igCalcTextSize(addr labelSize, ICON_FA_MAGNIFYING_GLASS, nil, false, 0.0f) igCalcTextSize(addr labelSize, ICON_FA_MAGNIFYING_GLASS, nil, false, 0.0f)
igSameLine(0.0f, igGetWindowWidth() - 200.0f - (labelSize.x + textSpacing) - (igGetStyle().WindowPadding.x * 2))
# SHow tooltip when hovering the search icon let searchBoxWidth: float32 = 200.0f
igSameLine(0.0f, availableSize.x - (labelSize.x + textSpacing) - searchBoxWidth)
# Show tooltip when hovering the search icon
igTextUnformatted(ICON_FA_MAGNIFYING_GLASS.cstring, nil) igTextUnformatted(ICON_FA_MAGNIFYING_GLASS.cstring, nil)
if igIsItemHovered(ImGuiHoveredFlags_None.int32): if igIsItemHovered(ImGuiHoveredFlags_None.int32):
igBeginTooltip() igBeginTooltip()
@@ -199,7 +212,7 @@ proc draw*(component: ConsoleComponent, ws: WebSocket) =
igSetKeyboardFocusHere(0) igSetKeyboardFocusHere(0)
igSameLine(0.0f, textSpacing) igSameLine(0.0f, textSpacing)
component.filter.ImGuiTextFilter_Draw("##ConsoleSearch", 200.0f) component.filter.ImGuiTextFilter_Draw("##ConsoleSearch", searchBoxWidth)
try: try:
# Set styles of the console window # Set styles of the console window
@@ -245,9 +258,8 @@ proc draw*(component: ConsoleComponent, ws: WebSocket) =
igSameLine(0.0f, textSpacing) igSameLine(0.0f, textSpacing)
# Calculate available width for input # Calculate available width for input
var availableWidth: ImVec2 igGetContentRegionAvail(addr availableSize)
igGetContentRegionAvail(addr availableWidth) igSetNextItemWidth(availableSize.x)
igSetNextItemWidth(availableWidth.x)
let inputFlags = ImGuiInputTextFlags_EnterReturnsTrue.int32 or ImGuiInputTextFlags_EscapeClearsAll.int32 or ImGuiInputTextFlags_CallbackHistory.int32 or ImGuiInputTextFlags_CallbackCompletion.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, callback, cast[pointer](component)): if igInputText("##Input", addr component.inputBuffer[0], MAX_INPUT_LENGTH, inputFlags, callback, cast[pointer](component)):
@@ -277,11 +289,4 @@ proc draw*(component: ConsoleComponent, ws: WebSocket) =
igSetItemDefaultFocus() igSetItemDefaultFocus()
if focusInput: if focusInput:
igSetKeyboardFocusHere(-1) igSetKeyboardFocusHere(-1)
#[
Session information
]#
let sessionInfo = fmt"{component.agent.username}@{component.agent.hostname}.{component.agent.domain} [{component.agent.ip}] [{component.agent.process}/{$component.agent.pid}]"
igTextColored(vec4(0.75f, 0.75f, 0.75f, 1.0f), sessionInfo)

View File

@@ -12,7 +12,7 @@ type
spoofStack: bool spoofStack: bool
listeners: seq[string] listeners: seq[string]
sleepMaskTechniques: seq[string] sleepMaskTechniques: seq[string]
moduleSelection: DualListSelectionComponent moduleSelection: DualListSelectionComponent[ModuleType]
proc AgentModal*(listeners: seq[Listener]): AgentModalComponent = proc AgentModal*(listeners: seq[Listener]): AgentModalComponent =
result = new AgentModalComponent result = new AgentModalComponent
@@ -30,7 +30,11 @@ proc AgentModal*(listeners: seq[Listener]): AgentModalComponent =
var modules: seq[ModuleType] var modules: seq[ModuleType]
for module in ModuleType: for module in ModuleType:
modules.add(module) modules.add(module)
result.moduleSelection = DualListSelection(modules)
proc moduleName(module: ModuleType): string =
return ($module).split("_")[1..^1].mapIt(it.toLowerAscii().capitalizeAscii()).join("")
result.moduleSelection = DualListSelection(modules, moduleName)
proc resetModalValues(component: AgentModalComponent) = proc resetModalValues(component: AgentModalComponent) =
discard discard

View File

@@ -4,18 +4,23 @@ import ../../utils/[appImGui, colors]
import ../../../common/[types, utils] import ../../../common/[types, utils]
type type
DualListSelectionComponent* = ref object of RootObj DualListSelectionComponent*[T] = ref object of RootObj
items*: array[2, seq[ModuleType]] items*: array[2, seq[T]]
selection: array[2, ptr ImGuiSelectionBasicStorage] selection: array[2, ptr ImGuiSelectionBasicStorage]
display: proc(item: T): string
proc DualListSelection*(items: seq[ModuleType]): DualListSelectionComponent = proc defaultDisplay[T](item: T): string =
result = new DualListSelectionComponent return $item
proc DualListSelection*[T](items: seq[T], display: proc(item: T): string = defaultDisplay): DualListSelectionComponent[T] =
result = new DualListSelectionComponent[T]
result.items[0] = items result.items[0] = items
result.items[1] = @[] result.items[1] = @[]
result.selection[0] = ImGuiSelectionBasicStorage_ImGuiSelectionBasicStorage() result.selection[0] = ImGuiSelectionBasicStorage_ImGuiSelectionBasicStorage()
result.selection[1] = ImGuiSelectionBasicStorage_ImGuiSelectionBasicStorage() result.selection[1] = ImGuiSelectionBasicStorage_ImGuiSelectionBasicStorage()
result.display = display
proc moveAll(component: DualListSelectionComponent, src, dst: int) = proc moveAll[T](component: DualListSelectionComponent[T], src, dst: int) =
for m in component.items[src]: for m in component.items[src]:
component.items[dst].add(m) component.items[dst].add(m)
component.items[dst].sort() component.items[dst].sort()
@@ -24,7 +29,7 @@ proc moveAll(component: DualListSelectionComponent, src, dst: int) =
ImGuiSelectionBasicStorage_Swap(component.selection[src], component.selection[dst]) ImGuiSelectionBasicStorage_Swap(component.selection[src], component.selection[dst])
ImGuiSelectionBasicStorage_Clear(component.selection[src]) ImGuiSelectionBasicStorage_Clear(component.selection[src])
proc moveSelection(component: DualListSelectionComponent, src, dst: int) = proc moveSelection[T](component: DualListSelectionComponent[T], src, dst: int) =
var keep: seq[ModuleType] var keep: seq[ModuleType]
for i in 0 ..< component.items[src].len(): for i in 0 ..< component.items[src].len():
let item = component.items[src][i] let item = component.items[src][i]
@@ -38,10 +43,7 @@ proc moveSelection(component: DualListSelectionComponent, src, dst: int) =
ImGuiSelectionBasicStorage_Swap(component.selection[src], component.selection[dst]) ImGuiSelectionBasicStorage_Swap(component.selection[src], component.selection[dst])
ImGuiSelectionBasicStorage_Clear(component.selection[src]) ImGuiSelectionBasicStorage_Clear(component.selection[src])
proc moduleName(module: ModuleType): string = proc draw*[T](component: DualListSelectionComponent[T]) =
return ($module).split("_")[1..^1].mapIt(it.toLowerAscii().capitalizeAscii()).join("")
proc draw*(component: DualListSelectionComponent) =
if igBeginTable("split", 3, ImGuiTableFlags_None.int32, vec2(0.0f, 0.0f), 0.0f): if igBeginTable("split", 3, ImGuiTableFlags_None.int32, vec2(0.0f, 0.0f), 0.0f):
@@ -77,7 +79,7 @@ proc draw*(component: DualListSelectionComponent) =
for row in 0 ..< modules.len().int32: for row in 0 ..< modules.len().int32:
var isSelected = ImGuiSelectionBasicStorage_Contains(selection, cast[ImGuiID](row)) var isSelected = ImGuiSelectionBasicStorage_Contains(selection, cast[ImGuiID](row))
igSetNextItemSelectionUserData(row) igSetNextItemSelectionUserData(row)
discard igSelectable_Bool(modules[row].moduleName(), isSelected, ImGuiSelectableFlags_AllowDoubleClick.int32, vec2(0.0f, 0.0f)) discard igSelectable_Bool(component.display(modules[row]), isSelected, ImGuiSelectableFlags_AllowDoubleClick.int32, vec2(0.0f, 0.0f))
# Move on Enter and double-click # Move on Enter and double-click
if igIsItemFocused(): if igIsItemFocused():
@@ -127,7 +129,7 @@ proc draw*(component: DualListSelectionComponent) =
for row in 0 ..< modules.len().int32: for row in 0 ..< modules.len().int32:
var isSelected = ImGuiSelectionBasicStorage_Contains(selection, cast[ImGuiID](row)) var isSelected = ImGuiSelectionBasicStorage_Contains(selection, cast[ImGuiID](row))
igSetNextItemSelectionUserData(row) igSetNextItemSelectionUserData(row)
discard igSelectable_Bool(modules[row].moduleName(), isSelected, ImGuiSelectableFlags_AllowDoubleClick.int32, vec2(0.0f, 0.0f)) discard igSelectable_Bool(component.display(modules[row]), isSelected, ImGuiSelectableFlags_AllowDoubleClick.int32, vec2(0.0f, 0.0f))
# Move on Enter and double-click # Move on Enter and double-click
if igIsItemFocused(): if igIsItemFocused():