Gin
Loading...
Searching...
No Matches
RemoteServer Class Reference

A small TCP server that lets an external process drive a JUCE UI. More...

#include <gin_remoteserver.h>

Inherits juce::Thread.

Classes

struct  Options
struct  CommandResult
 The outcome of a command. More...
class  Context
 Passed to async commands so they can hop onto the message thread and pause between steps. More...

Public Types

using CommandHandler = std::function<CommandResult (const juce::var& args)>
 Handler for a command that runs on the message thread.
using AsyncCommandHandler = std::function<CommandResult (Context&, const juce::var& args)>
 Handler for a command that runs on the server thread.
using ComponentInfoProvider = std::function<void (juce::Component&, juce::DynamicObject& info)>
 Called for every component description so products can add their own fields.

Public Member Functions

 RemoteServer ()
 RemoteServer (const Options &)
 ~RemoteServer () override
bool start ()
 Starts listening.
void stop ()
 Stops listening and disconnects any client.
bool isRunning () const noexcept
int getPort () const noexcept
const Options & getOptions () const noexcept
void addCommand (const juce::String &name, const juce::String &description, const juce::String &argsSpec, CommandHandler handler)
 Adds a command that runs on the message thread.
void addAsyncCommand (const juce::String &name, const juce::String &description, const juce::String &argsSpec, AsyncCommandHandler handler)
 Adds a command that runs on the server thread.
void removeCommand (const juce::String &name)
void addComponentInfoProvider (ComponentInfoProvider)
 Registers a hook that can add fields to every component description.
void log (const juce::String &line)
 Appends a line to the buffer returned by the log command.
juce::var describeComponent (juce::Component &, bool full=false)
 Describes a component as a json object, including fields from the registered providers.
juce::var describeTree (juce::Component &, int depth=-1, bool visibleOnly=true, int maxNodes=5000)
 Describes a component and its descendants.

Static Public Member Functions

static juce::Array< juce::Component * > findComponents (const juce::String &selector, bool visibleOnly=true, juce::Component *root=nullptr)
 Returns every component matching a selector.
static juce::Component * findComponent (const juce::String &selector, bool visibleOnly=true, juce::Component *root=nullptr)
 Returns the first component matching a selector, or nullptr.
static juce::String getClassName (juce::Component &)
 Demangled class name of a component, e.g.
static juce::String getComponentPath (juce::Component &)
 Index path of a component, e.g.
static juce::String getComponentText (juce::Component &)
 Text shown by a component if it is a button, label, text editor or combo box.
static juce::var getComponentValue (juce::Component &)
 Value of a component if it is a slider, toggle button, combo box, etc.
static juce::String setComponentValue (juce::Component &, const juce::var &value)
 Sets the value of a standard component.
static juce::var getArg (const juce::var &args, const char *name, const juce::var &defaultValue={})
 Reads an argument with a default.
static bool injectMouse (juce::Point< float > screenPos, juce::ModifierKeys mods, juce::Component *target=nullptr)
 Synthesises a mouse event through the component peer, so it travels the same route as a real one.
static bool injectWheel (juce::Point< float > screenPos, float deltaX, float deltaY, juce::ModifierKeys mods, juce::Component *target=nullptr)
static bool injectKey (const juce::KeyPress &, juce::Component *target=nullptr)
 Sends a key press to the focused peer, or the peer owning target.
static juce::Component * getComponentAt (juce::Point< int > screenPos)
 The deepest component under a screen position, across all desktop windows.
static juce::ComponentPeer * getPeerAt (juce::Point< int > screenPos)
 The peer under a screen position, if any.
static juce::ComponentPeer * getFocusedPeer ()
 The focused peer, falling back to the first one.
static void setMoveRealCursor (bool shouldMove) noexcept
 See Options::moveRealCursor.
static bool getMoveRealCursor () noexcept

Detailed Description

A small TCP server that lets an external process drive a JUCE UI.

Intended for UI automation, scripted testing and AI coding agents. Clients connect over a local socket and exchange newline-delimited JSON:

-> {"id": 1, "cmd": "tree", "args": {"depth": 2}}
<- {"id": 1, "ok": true, "result": {...}}
<- {"id": 2, "ok": false, "error": "No component matches '#missing'"}

Built in commands cover the component tree (tree, find, describe, at, windows), input (click, drag, mouse, wheel, key, type, focus), state (get, set, press, resize), screenshots and waiting. Products add their own with addCommand(), and can decorate every component description with addComponentInfoProvider().

Nothing happens unless you create and start a server, so it is safe to compile into a shared module. Only start it in development builds.

Components are addressed with a tiny selector language. A selector is a space separated list of steps, each step narrowing the search to the descendants of the previous matches:

#id component ID (Component::getComponentID) .ClassName class name, with or without namespace name component name, or button / label text ~text substring match of name, text or id @x,y deepest component at a screen point /0/3/1 index path: desktop window 0, child 3, child 1 any component step[n] nth match of that step

e.g. "#sidebar .TextButton[2]" or "Save" or "@120,240"

gin::RemoteServer::Options opts;
opts.port = 27182;
remote = std::make_unique<gin::RemoteServer> (opts);
remote->addCommand ("loadPreset", "Load a preset by name", R"({"name":"string!: preset name"})",
[this] (const juce::var& args)
{
if (! loadPreset (args["name"].toString()))
return gin::RemoteServer::CommandResult::fail ("No such preset");
return gin::RemoteServer::CommandResult (juce::var (true));
});
remote->start();

A command line client and MCP server live in tools/gin_remote.

Member Typedef Documentation

◆ CommandHandler

using RemoteServer::CommandHandler = std::function<CommandResult (const juce::var& args)>

Handler for a command that runs on the message thread.

◆ AsyncCommandHandler

using RemoteServer::AsyncCommandHandler = std::function<CommandResult (Context&, const juce::var& args)>

Handler for a command that runs on the server thread.

Use this when a command needs to span several message loop iterations, e.g. a drag or a wait.

◆ ComponentInfoProvider

using RemoteServer::ComponentInfoProvider = std::function<void (juce::Component&, juce::DynamicObject& info)>

Called for every component description so products can add their own fields.

Constructor & Destructor Documentation

◆ RemoteServer() [1/2]

RemoteServer::RemoteServer ( )

◆ RemoteServer() [2/2]

RemoteServer::RemoteServer ( const Options & )
explicit

◆ ~RemoteServer()

RemoteServer::~RemoteServer ( )
override

Member Function Documentation

◆ start()

bool RemoteServer::start ( )

Starts listening.

Returns false if no port in the configured range could be bound.

◆ stop()

void RemoteServer::stop ( )

Stops listening and disconnects any client.

◆ isRunning()

bool RemoteServer::isRunning ( ) const
noexcept

◆ getPort()

int RemoteServer::getPort ( ) const
noexcept

◆ getOptions()

const Options & RemoteServer::getOptions ( ) const
noexcept

◆ addCommand()

void RemoteServer::addCommand ( const juce::String & name,
const juce::String & description,
const juce::String & argsSpec,
CommandHandler handler )

Adds a command that runs on the message thread.

Parameters
namecommand name, e.g. "loadPreset"
descriptionone line of help, shown by the commands command and used as the MCP tool description
argsSpecoptional json object describing the arguments, mapping name to "type: description". Types are string, int, number, bool, array, object. Add ! after the type for required args. e.g. R"({"name":"string!: preset name","layer":"int: 0 based layer, default 0"})"
handlerthe handler

Referenced by addAudioProcessorCommands().

◆ addAsyncCommand()

void RemoteServer::addAsyncCommand ( const juce::String & name,
const juce::String & description,
const juce::String & argsSpec,
AsyncCommandHandler handler )

Adds a command that runs on the server thread.

See AsyncCommandHandler.

◆ removeCommand()

void RemoteServer::removeCommand ( const juce::String & name)

◆ addComponentInfoProvider()

void RemoteServer::addComponentInfoProvider ( ComponentInfoProvider )

Registers a hook that can add fields to every component description.

◆ log()

void RemoteServer::log ( const juce::String & line)

Appends a line to the buffer returned by the log command.

Thread safe.

◆ findComponents()

juce::Array< juce::Component * > RemoteServer::findComponents ( const juce::String & selector,
bool visibleOnly = true,
juce::Component * root = nullptr )
static

Returns every component matching a selector.

root == nullptr searches all desktop windows.

◆ findComponent()

juce::Component * RemoteServer::findComponent ( const juce::String & selector,
bool visibleOnly = true,
juce::Component * root = nullptr )
static

Returns the first component matching a selector, or nullptr.

◆ describeComponent()

juce::var RemoteServer::describeComponent ( juce::Component & ,
bool full = false )

Describes a component as a json object, including fields from the registered providers.

◆ describeTree()

juce::var RemoteServer::describeTree ( juce::Component & ,
int depth = -1,
bool visibleOnly = true,
int maxNodes = 5000 )

Describes a component and its descendants.

depth < 0 means unlimited.

◆ getClassName()

juce::String RemoteServer::getClassName ( juce::Component & )
static

Demangled class name of a component, e.g.

"juce::TextButton".

◆ getComponentPath()

juce::String RemoteServer::getComponentPath ( juce::Component & )
static

Index path of a component, e.g.

"/0/3/1". The first index is the desktop window.

◆ getComponentText()

juce::String RemoteServer::getComponentText ( juce::Component & )
static

Text shown by a component if it is a button, label, text editor or combo box.

◆ getComponentValue()

juce::var RemoteServer::getComponentValue ( juce::Component & )
static

Value of a component if it is a slider, toggle button, combo box, etc.

Returns void var if none.

◆ setComponentValue()

juce::String RemoteServer::setComponentValue ( juce::Component & ,
const juce::var & value )
static

Sets the value of a standard component.

Returns an error message on failure.

◆ getArg()

juce::var RemoteServer::getArg ( const juce::var & args,
const char * name,
const juce::var & defaultValue = {} )
static

Reads an argument with a default.

Accepts missing args object.

Referenced by addAudioProcessorCommands().

◆ injectMouse()

bool RemoteServer::injectMouse ( juce::Point< float > screenPos,
juce::ModifierKeys mods,
juce::Component * target = nullptr )
static

Synthesises a mouse event through the component peer, so it travels the same route as a real one.

pos is in screen coordinates. Pass the button in mods for a press.

◆ injectWheel()

bool RemoteServer::injectWheel ( juce::Point< float > screenPos,
float deltaX,
float deltaY,
juce::ModifierKeys mods,
juce::Component * target = nullptr )
static

◆ injectKey()

bool RemoteServer::injectKey ( const juce::KeyPress & ,
juce::Component * target = nullptr )
static

Sends a key press to the focused peer, or the peer owning target.

◆ getComponentAt()

juce::Component * RemoteServer::getComponentAt ( juce::Point< int > screenPos)
static

The deepest component under a screen position, across all desktop windows.

Unlike Desktop::findComponentAt this ignores windows belonging to other applications, so it works when the window being driven is behind a terminal.

◆ getPeerAt()

juce::ComponentPeer * RemoteServer::getPeerAt ( juce::Point< int > screenPos)
static

The peer under a screen position, if any.

◆ getFocusedPeer()

juce::ComponentPeer * RemoteServer::getFocusedPeer ( )
static

The focused peer, falling back to the first one.

◆ setMoveRealCursor()

void RemoteServer::setMoveRealCursor ( bool shouldMove)
staticnoexcept

See Options::moveRealCursor.

Applies to every server in the process.

◆ getMoveRealCursor()

bool RemoteServer::getMoveRealCursor ( )
staticnoexcept

The documentation for this class was generated from the following file: