Gin
Loading...
Searching...
No Matches
Classes | Public Types | Public Member Functions | Static Public Member Functions | List of all members
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  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...
 
struct  Options
 

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 lightweight 2D point class for projects that don't use juce_graphics.
Definition gin_point.h:25

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

Member Typedef Documentation

◆ CommandHandler

Handler for a command that runs on the message thread.

◆ AsyncCommandHandler

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()

static 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()

static 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()

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

Demangled class name of a component, e.g.

"juce::TextButton".

◆ getComponentPath()

static 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()

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

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

◆ getComponentValue()

static 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()

static 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()

static 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()

static 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()

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

◆ injectKey()

static 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()

static 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()

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

The peer under a screen position, if any.

◆ getFocusedPeer()

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

The focused peer, falling back to the first one.

◆ setMoveRealCursor()

static void RemoteServer::setMoveRealCursor ( bool  shouldMove)
staticnoexcept

See Options::moveRealCursor.

Applies to every server in the process.

◆ getMoveRealCursor()

static bool RemoteServer::getMoveRealCursor ( )
staticnoexcept

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