this repo has no description
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

add --remote flag to allow remote control of ui

+332 -16
+7 -16
src/bootstrap/java/boot/StarRodBootstrap.java
··· 52 52 .toURI() 53 53 .getPath(); 54 54 55 - ProcessBuilder processBuilder; 56 - if (args.length > 0) { 57 - processBuilder = new ProcessBuilder( 58 - javaExec, 59 - "-cp", 60 - jarPath, 61 - "app.StarRodMain", 62 - String.join(" ", args).trim()); 63 - } 64 - else { 65 - processBuilder = new ProcessBuilder( 66 - javaExec, 67 - "-cp", 68 - jarPath, 69 - "app.StarRodMain"); 70 - } 55 + String[] command = new String[4 + args.length]; 56 + command[0] = javaExec; 57 + command[1] = "-cp"; 58 + command[2] = jarPath; 59 + command[3] = "app.StarRodMain"; 60 + System.arraycopy(args, 0, command, 4, args.length); 71 61 62 + ProcessBuilder processBuilder = new ProcessBuilder(command); 72 63 processBuilder.inheritIO(); 73 64 Process process = processBuilder.start(); 74 65 process.waitFor();
+28
src/main/java/app/StarRodMain.java
··· 49 49 import game.texture.editor.ImageEditor; 50 50 import game.worldmap.WorldMapEditor; 51 51 import net.miginfocom.swing.MigLayout; 52 + import tools.SwingInspectorKt; 52 53 import util.Logger; 53 54 54 55 public class StarRodMain extends StarRodFrame ··· 61 62 62 63 public static void main(String[] args) throws InterruptedException 63 64 { 65 + // Handle --remote flag without initializing environment 66 + if (args.length > 0 && args[0].equalsIgnoreCase("--remote")) { 67 + String[] remoteArgs = new String[args.length - 1]; 68 + System.arraycopy(args, 1, remoteArgs, 0, remoteArgs.length); 69 + SwingInspectorKt.main(remoteArgs); 70 + return; 71 + } 72 + 64 73 boolean isCommandLine = args.length > 0 || GraphicsEnvironment.isHeadless(); 65 74 66 75 if (isCommandLine) { ··· 90 99 91 100 // Menu bar 92 101 JMenuBar menuBar = new JMenuBar(); 102 + menuBar.getAccessibleContext().setAccessibleName("menuBar"); 93 103 JMenu fileMenu = new JMenu("File"); 104 + fileMenu.getAccessibleContext().setAccessibleName("fileMenu"); 94 105 JMenu editMenu = new JMenu("Edit"); 106 + editMenu.getAccessibleContext().setAccessibleName("editMenu"); 95 107 menuBar.add(fileMenu); 96 108 menuBar.add(editMenu); 97 109 setJMenuBar(menuBar); ··· 99 111 // TODO: click this to change project 100 112 JLabel projectIdLabel = new JLabel(Environment.getProject().getManifest().getId()); 101 113 SwingUtils.setFontSize(projectIdLabel, 11); 114 + projectIdLabel.getAccessibleContext().setAccessibleName("projectIdLabel"); 102 115 103 116 JButton mapEditorButton = new JButton("Map Editor"); 104 117 trySetIcon(mapEditorButton, ExpectedAsset.ICON_MAP_EDITOR); 105 118 SwingUtils.setFontSize(mapEditorButton, 12); 119 + mapEditorButton.getAccessibleContext().setAccessibleName("mapEditorButton"); 106 120 mapEditorButton.addActionListener((e) -> { 107 121 action_openMapEditor(); 108 122 }); ··· 111 125 JButton spriteEditorButton = new JButton("Sprite Editor"); 112 126 trySetIcon(spriteEditorButton, ExpectedAsset.ICON_SPRITE_EDITOR); 113 127 SwingUtils.setFontSize(spriteEditorButton, 12); 128 + spriteEditorButton.getAccessibleContext().setAccessibleName("spriteEditorButton"); 114 129 spriteEditorButton.addActionListener((e) -> { 115 130 action_openSpriteEditor(); 116 131 }); ··· 119 134 JButton msgEditorButton = new JButton("Message Editor"); 120 135 trySetIcon(msgEditorButton, ExpectedAsset.ICON_MSG_EDITOR); 121 136 SwingUtils.setFontSize(msgEditorButton, 12); 137 + msgEditorButton.getAccessibleContext().setAccessibleName("messageEditorButton"); 122 138 msgEditorButton.addActionListener((e) -> { 123 139 action_openMessageEditor(); 124 140 }); ··· 127 143 JButton globalsEditorButton = new JButton("Globals Editor"); 128 144 trySetIcon(globalsEditorButton, ExpectedAsset.ICON_GLOBALS_EDITOR); 129 145 SwingUtils.setFontSize(globalsEditorButton, 12); 146 + globalsEditorButton.getAccessibleContext().setAccessibleName("globalsEditorButton"); 130 147 globalsEditorButton.addActionListener((e) -> { 131 148 action_openGlobalsEditor(); 132 149 }); ··· 135 152 JButton worldEditorButton = new JButton("World Map Editor"); 136 153 trySetIcon(worldEditorButton, ExpectedAsset.ICON_WORLD_EDITOR); 137 154 SwingUtils.setFontSize(worldEditorButton, 12); 155 + worldEditorButton.getAccessibleContext().setAccessibleName("worldMapEditorButton"); 138 156 worldEditorButton.addActionListener((e) -> { 139 157 action_openWorldMapEditor(); 140 158 }); ··· 143 161 JButton imageEditorButton = new JButton("Image Editor"); 144 162 trySetIcon(imageEditorButton, ExpectedAsset.ICON_IMAGE_EDITOR); 145 163 SwingUtils.setFontSize(imageEditorButton, 12); 164 + imageEditorButton.getAccessibleContext().setAccessibleName("imageEditorButton"); 146 165 imageEditorButton.addActionListener((e) -> { 147 166 action_openImageEditor(); 148 167 }); ··· 151 170 JButton themesMenuButton = new JButton("Choose Theme"); 152 171 trySetIcon(themesMenuButton, ExpectedAsset.ICON_THEMES); 153 172 SwingUtils.setFontSize(themesMenuButton, 12); 173 + themesMenuButton.getAccessibleContext().setAccessibleName("themesMenuButton"); 154 174 themesMenuButton.addActionListener((e) -> { 155 175 action_openThemesMenu(); 156 176 }); ··· 160 180 JButton buildProjectButton = new JButton("Build Project"); 161 181 trySetIcon(buildProjectButton, ExpectedAsset.ICON_GOLD); 162 182 SwingUtils.setFontSize(buildProjectButton, 12); 183 + buildProjectButton.getAccessibleContext().setAccessibleName("buildProjectButton"); 163 184 buildProjectButton.addActionListener((e) -> { 164 185 action_buildProject(); 165 186 }); ··· 169 190 JButton openConfigDirButton = new JButton("Open Config Dir"); 170 191 trySetIcon(openConfigDirButton, ExpectedAsset.ICON_SILVER); 171 192 SwingUtils.setFontSize(openConfigDirButton, 12); 193 + openConfigDirButton.getAccessibleContext().setAccessibleName("openConfigDirButton"); 172 194 openConfigDirButton.addActionListener((e) -> { 173 195 action_openDir(Environment.getUserConfigDir()); 174 196 }); ··· 177 199 JButton openProjectDirButton = new JButton("Open Project Dir"); 178 200 trySetIcon(openProjectDirButton, ExpectedAsset.ICON_GOLD); 179 201 SwingUtils.setFontSize(openProjectDirButton, 12); 202 + openProjectDirButton.getAccessibleContext().setAccessibleName("openProjectDirButton"); 180 203 openProjectDirButton.addActionListener((e) -> { 181 204 action_openDir(Environment.getProjectDirectory()); 182 205 }); ··· 208 231 // Left pane - buttons panel 209 232 Pane leftPane = new Pane(); 210 233 leftPane.setLayout(new MigLayout("fill, ins 8, wrap 1")); 234 + leftPane.getAccessibleContext().setAccessibleName("leftPane"); 211 235 212 236 JPanel buttonsPanel = new JPanel(new MigLayout("fillx, wrap 1, hidemode 3")); 237 + buttonsPanel.getAccessibleContext().setAccessibleName("buttonsPanel"); 213 238 buttonsPanel.add(mapEditorButton, "growx"); 214 239 buttonsPanel.add(spriteEditorButton, "growx"); 215 240 buttonsPanel.add(globalsEditorButton, "growx"); ··· 227 252 // Middle pane - placeholder for now 228 253 Pane middlePane = new Pane(); 229 254 middlePane.setLayout(new MigLayout("fill, ins 8")); 255 + middlePane.getAccessibleContext().setAccessibleName("middlePane"); 230 256 middlePane.add(new JLabel("Middle Pane"), "center"); 231 257 middlePane.setMinimumSize(new Dimension(MIN_PANE_WIDTH, 0)); 232 258 233 259 // Right pane - placeholder for now 234 260 Pane rightPane = new Pane(); 235 261 rightPane.setLayout(new MigLayout("fill, ins 8")); 262 + rightPane.getAccessibleContext().setAccessibleName("rightPane"); 236 263 rightPane.add(new JLabel("Right Pane"), "center"); 237 264 rightPane.setMinimumSize(new Dimension(MIN_PANE_WIDTH, 0)); 238 265 239 266 // Dock (bottom panel in middle column) 240 267 Dock dock = new Dock(); 268 + dock.getAccessibleContext().setAccessibleName("dock"); 241 269 dock.setMinimumSize(new Dimension(0, MIN_DOCK_HEIGHT)); 242 270 243 271 // Create vertical split pane (middlePane | dock) for center column
+297
src/main/java/tools/SwingInspector.kt
··· 1 + package tools 2 + 3 + import com.google.gson.GsonBuilder 4 + import com.google.gson.JsonArray 5 + import com.google.gson.JsonObject 6 + import java.awt.* 7 + import java.awt.image.BufferedImage 8 + import java.io.File 9 + import javax.accessibility.* 10 + import javax.imageio.ImageIO 11 + import kotlin.system.exitProcess 12 + 13 + /** 14 + * SwingInspector for remote controlling Star Rod's UI from within the same JVM. 15 + * 16 + * Usage (via StarRod.jar): 17 + * java -jar StarRod.jar --remote list-windows 18 + * java -jar StarRod.jar --remote tree <window-title> 19 + * java -jar StarRod.jar --remote find <window-title> <component-name> 20 + * java -jar StarRod.jar --remote click <window-title> <component-name> 21 + * java -jar StarRod.jar --remote screenshot <window-title> <output.png> 22 + */ 23 + 24 + private val gson = GsonBuilder().setPrettyPrinting().create() 25 + 26 + fun main(args: Array<String>) { 27 + if (args.isEmpty()) { 28 + printUsage() 29 + exitProcess(1) 30 + } 31 + 32 + val command = args[0] 33 + 34 + try { 35 + when (command) { 36 + "list-windows" -> listWindows() 37 + "tree" -> { 38 + if (args.size < 2) { 39 + System.err.println("Usage: tree <window-title>") 40 + exitProcess(1) 41 + } 42 + printTree(args[1]) 43 + } 44 + "find" -> { 45 + if (args.size < 3) { 46 + System.err.println("Usage: find <window-title> <component-name>") 47 + exitProcess(1) 48 + } 49 + findComponent(args[1], args[2]) 50 + } 51 + "click" -> { 52 + if (args.size < 3) { 53 + System.err.println("Usage: click <window-title> <component-name>") 54 + exitProcess(1) 55 + } 56 + clickComponent(args[1], args[2]) 57 + } 58 + "screenshot" -> { 59 + if (args.size < 3) { 60 + System.err.println("Usage: screenshot <window-title> <output.png>") 61 + exitProcess(1) 62 + } 63 + takeScreenshot(args[1], args[2]) 64 + } 65 + else -> { 66 + System.err.println("Unknown command: $command") 67 + printUsage() 68 + exitProcess(1) 69 + } 70 + } 71 + } catch (e: Exception) { 72 + System.err.println("Error: ${e.message}") 73 + e.printStackTrace() 74 + exitProcess(1) 75 + } 76 + } 77 + 78 + private fun printUsage() { 79 + println("SwingInspector - CLI tool for inspecting Swing applications") 80 + println() 81 + println("IMPORTANT: Start Star Rod first, then run this tool from the same GUI session.") 82 + println() 83 + println("Commands:") 84 + println(" list-windows List all visible windows") 85 + println(" tree <window-title> Show component tree") 86 + println(" find <window-title> <component-name> Find component by name") 87 + println(" click <window-title> <component-name> Click a component") 88 + println(" screenshot <window-title> <file.png> Take screenshot") 89 + } 90 + 91 + private fun listWindows() { 92 + val windows = Window.getWindows() 93 + val result = JsonArray() 94 + 95 + for (window in windows) { 96 + if (!window.isVisible) continue 97 + 98 + val obj = JsonObject().apply { 99 + addProperty("title", window.windowTitle) 100 + addProperty("class", window.javaClass.name) 101 + addProperty("visible", window.isVisible) 102 + addProperty("showing", window.isShowing) 103 + 104 + val bounds = window.bounds 105 + add("bounds", JsonObject().apply { 106 + addProperty("x", bounds.x) 107 + addProperty("y", bounds.y) 108 + addProperty("width", bounds.width) 109 + addProperty("height", bounds.height) 110 + }) 111 + } 112 + 113 + result.add(obj) 114 + } 115 + 116 + println(gson.toJson(result)) 117 + } 118 + 119 + private fun printTree(windowTitle: String) { 120 + val window = findWindow(windowTitle) ?: run { 121 + System.err.println("Window not found: $windowTitle") 122 + exitProcess(1) 123 + } 124 + 125 + val ac = window.accessibleContext ?: run { 126 + System.err.println("Window has no accessible context") 127 + exitProcess(1) 128 + } 129 + 130 + val tree = buildComponentTree(ac) 131 + println(gson.toJson(tree)) 132 + } 133 + 134 + private fun findComponent(windowTitle: String, componentName: String) { 135 + val window = findWindow(windowTitle) ?: run { 136 + System.err.println("Window not found: $windowTitle") 137 + exitProcess(1) 138 + } 139 + 140 + val ac = findAccessibleComponent(window.accessibleContext, componentName) ?: run { 141 + System.err.println("Component not found: $componentName") 142 + exitProcess(1) 143 + } 144 + 145 + val info = buildComponentInfo(ac) 146 + println(gson.toJson(info)) 147 + } 148 + 149 + private fun clickComponent(windowTitle: String, componentName: String) { 150 + val window = findWindow(windowTitle) ?: run { 151 + System.err.println("Window not found: $windowTitle") 152 + exitProcess(1) 153 + } 154 + 155 + val ac = findAccessibleComponent(window.accessibleContext, componentName) ?: run { 156 + System.err.println("Component not found: $componentName") 157 + exitProcess(1) 158 + } 159 + 160 + val action = ac.accessibleAction ?: run { 161 + System.err.println("Component has no actions") 162 + exitProcess(1) 163 + } 164 + 165 + // Trigger the default action (usually index 0) 166 + val success = action.doAccessibleAction(0) 167 + 168 + val result = JsonObject().apply { 169 + addProperty("success", success) 170 + addProperty("action", action.getAccessibleActionDescription(0)) 171 + } 172 + println(gson.toJson(result)) 173 + } 174 + 175 + private fun takeScreenshot(windowTitle: String, outputFile: String) { 176 + val window = findWindow(windowTitle) ?: run { 177 + System.err.println("Window not found: $windowTitle") 178 + exitProcess(1) 179 + } 180 + 181 + val robot = Robot() 182 + val bounds = window.bounds 183 + val screenshot = robot.createScreenCapture(bounds) 184 + 185 + ImageIO.write(screenshot, "png", File(outputFile)) 186 + 187 + val result = JsonObject().apply { 188 + addProperty("success", true) 189 + addProperty("file", outputFile) 190 + addProperty("width", screenshot.width) 191 + addProperty("height", screenshot.height) 192 + } 193 + println(gson.toJson(result)) 194 + } 195 + 196 + private fun findWindow(title: String): Window? { 197 + val windows = Window.getWindows() 198 + return windows.firstOrNull { window -> 199 + window.isVisible && window.windowTitle?.contains(title) == true 200 + } 201 + } 202 + 203 + private val Window.windowTitle: String? 204 + get() = when (this) { 205 + is Frame -> this.title 206 + is Dialog -> this.title 207 + else -> accessibleContext?.accessibleName ?: javaClass.simpleName 208 + } 209 + 210 + private fun buildComponentTree(ac: AccessibleContext): JsonObject { 211 + val obj = buildComponentInfo(ac) 212 + 213 + val childCount = ac.accessibleChildrenCount 214 + if (childCount > 0) { 215 + val children = JsonArray() 216 + for (i in 0 until childCount) { 217 + val child = ac.getAccessibleChild(i) 218 + val childContext = child?.accessibleContext 219 + if (childContext != null) { 220 + children.add(buildComponentTree(childContext)) 221 + } 222 + } 223 + obj.add("children", children) 224 + } 225 + 226 + return obj 227 + } 228 + 229 + private fun buildComponentInfo(ac: AccessibleContext): JsonObject { 230 + return JsonObject().apply { 231 + addProperty("name", ac.accessibleName) 232 + addProperty("description", ac.accessibleDescription) 233 + addProperty("role", ac.accessibleRole.toString()) 234 + 235 + // States 236 + ac.accessibleStateSet?.let { states -> 237 + val statesArray = JsonArray() 238 + states.toArray().forEach { state -> 239 + statesArray.add(state.toString()) 240 + } 241 + add("states", statesArray) 242 + } 243 + 244 + // Text (if available) 245 + ac.accessibleText?.let { text -> 246 + try { 247 + val textContent = text.getAtIndex(AccessibleText.SENTENCE, 0) 248 + if (textContent != null) { 249 + addProperty("text", textContent) 250 + } 251 + } catch (e: Exception) { 252 + // Ignore 253 + } 254 + } 255 + 256 + // Actions (if available) 257 + ac.accessibleAction?.let { actions -> 258 + val actionsArray = JsonArray() 259 + for (i in 0 until actions.accessibleActionCount) { 260 + actionsArray.add(actions.getAccessibleActionDescription(i)) 261 + } 262 + add("actions", actionsArray) 263 + } 264 + 265 + // Component info 266 + ac.accessibleComponent?.let { comp -> 267 + val bounds = comp.bounds 268 + add("bounds", JsonObject().apply { 269 + addProperty("x", bounds.x) 270 + addProperty("y", bounds.y) 271 + addProperty("width", bounds.width) 272 + addProperty("height", bounds.height) 273 + }) 274 + 275 + addProperty("visible", comp.isVisible) 276 + addProperty("showing", comp.isShowing) 277 + addProperty("enabled", comp.isEnabled) 278 + } 279 + } 280 + } 281 + 282 + private fun findAccessibleComponent(root: AccessibleContext?, name: String): AccessibleContext? { 283 + if (root == null) return null 284 + 285 + if (root.accessibleName == name) return root 286 + 287 + // Search children 288 + val childCount = root.accessibleChildrenCount 289 + for (i in 0 until childCount) { 290 + val child = root.getAccessibleChild(i) 291 + val childContext = child?.accessibleContext 292 + val found = findAccessibleComponent(childContext, name) 293 + if (found != null) return found 294 + } 295 + 296 + return null 297 + }