GIMP Control
GIMP (/Applications/GIMP.app) embeds Python 3 and exposes its whole document model
through libgimp over GObject Introspection (gi.repository.Gimp), with GEGL for
pixels and filters. Drive it through GIMP's own Script-Fu server — a TCP endpoint
GIMP ships with, so there is nothing to install — which hands each script to GIMP's
python-fu-eval. The script runs against the live GIMP: the canvas updates, the layer
stack is the one the user is looking at, and nothing happens headless. Every call in
this skill was run against GIMP 3.2.4.
- scripts/gimp-send.sh — send a
.py file (or -c 'inline code')
and print its captured output; --ping checks the connection, --scheme evaluates
raw Script-Fu.
- scripts/gimp-start.sh — launch the GIMP GUI with the
Script-Fu server listening, and wait until it answers.
- scripts/gimp_client.py — the wire protocol, used by both.
- scripts/example-poster.py — a complete
build → verify → export script to copy the shape from.
- references/api-reference.md — the version-checked
libgimp API (images, layers, selections, drawing, text, GEGL filters, PDB calls,
pixel readback, export). Read it before writing anything past the cheatsheet below.
- references/recipes.md — working on the user's own image,
batch folders, per-layer export, masks, animation, and what the API cannot do.
The control loop
Check the connection: bash scripts/gimp-send.sh --ping →
{"ok": true, "bridge": "gimp", "port": 10008, "reply": "3.2.4 | open images: 2"}.
If nothing answers, there are two different situations and they need different moves:
- GIMP is not running →
bash scripts/gimp-start.sh starts the GUI with the
server switched on and waits for it. This opens a window on the user's screen, so
say that you are doing it.
- GIMP is running without the server → you cannot switch it on remotely, and you
must not start a second instance to get around that: two GIMPs share one config
directory and the user's unsaved work is in the one already open. Ask them for one
click: Filters ▸ Development ▸ Script-Fu ▸ Start Server…, listening on
127.0.0.1, port 10008. (Development stays English on a localised GIMP; on a
Hungarian one the path reads Szűrők ▸ Development ▸ Script-Fu ▸ Kiszolgáló
indítása….)
Write a build script to the scratchpad and send it:
OUT=/path/to/outdir bash scripts/gimp-send.sh /path/build.py. OUT arrives both as
a global and as os.environ["OUT"]; GIMP_SEND_TIMEOUT=600 (seconds) covers batches
and big exports.
Read the feedback. The sender returns the script's captured stdout/stderr, and on
failure the Python traceback, exiting non-zero — so print(...) is your channel back.
Nothing else reports: GIMP's own error console stays inside the app.
Look at the result. image.get_thumbnail(w, h, Gimp.PixbufTransparency.SMALL_CHECKS)
renders the composited image straight to a PNG you can Read. Do this at the end of
every build script — one line, and it catches what no assertion will.
Iterate, then deliver. Fix the script and re-send. Scripts must be
re-runnable: create a fresh image per version rather than mutating the one already
open, so a re-send cannot stack layers onto the previous attempt.
Each send is a fresh python-fu-eval process, so nothing persists between sends except
what is in GIMP (open images) or on disk. Round-trip is roughly a second — cheap enough
to probe with a throwaway send rather than guess an API.
Scripting cheatsheet
import gi
gi.require_version("Babl", "0.1"); gi.require_version("Gegl", "0.4")
gi.require_version("Gimp", "3.0")
from gi.repository import Babl, Gegl, Gimp, Gio
Gegl.init(None)
Image and layer stack
image = Gimp.Image.new(1000, 1400, Gimp.ImageBaseType.RGB)
layer = Gimp.Layer.new(image, "background", 1000, 1400,
Gimp.ImageType.RGBA_IMAGE, 100.0, Gimp.LayerMode.NORMAL)
image.insert_layer(layer, None, 0) # parent=None -> top level; 0 = topmost
layer.set_opacity(70.0) # 0–100 float, not 0–255
layer.set_mode(Gimp.LayerMode.SCREEN)
Gimp.Display.new(image) # show it — the user watches the build happen
Gimp.displays_flush() # nothing repaints without this
To work on what the user already has: Gimp.get_images()[0], most recently active
first. Duplicate it before destructive work — see
recipes.md.
Colour: sRGB in, sRGB out
Gegl.Color stores linear RGB, so the constructor and the setter are not the same
thing and the difference is a visibly washed-out image:
Gegl.Color.new("#1a73e8") # parses sRGB — use this
colour.set_rgba(0.10, 0.45, 0.85, 1.0) # LINEAR components — rarely what you meant
list(colour.get_bytes(Babl.format("R'G'B'A u8")).get_data()) # [26,115,232,255]
Gimp.context_set_foreground(Gegl.Color.new("#ffb703"))
Selections, fills, drawing
image.select_rectangle(Gimp.ChannelOps.REPLACE, x, y, w, h) # also ellipse, polygon,
layer.edit_fill(Gimp.FillType.FOREGROUND) # round_rectangle, color
Gimp.Selection.none(image) # every later edit respects the selection
layer.fill(Gimp.FillType.FOREGROUND) # whole layer, ignores the selection
Gimp.context_set_gradient_fg_bg_rgb()
layer.edit_gradient_fill(Gimp.GradientType.LINEAR, 0.0, False, 1.0, 0.0, True,
x1, y1, x2, y2)
Gimp.context_set_brush(Gimp.Brush.get_by_name("2. Hardness 100"))
Gimp.context_set_brush_size(20.0)
Gimp.paintbrush_default(layer, [40.0, 40.0, 200.0, 200.0]) # ONE drawable, flat x/y list
Text
font = Gimp.context_get_font() # safer than naming one: get_by_name
text = Gimp.TextLayer.new(image, "GIMP", font, 150.0, Gimp.Unit.pixel())
image.insert_layer(text, None, 0)
text.set_color(Gegl.Color.new("#f8f9fa"))
text.set_justification(Gimp.TextJustification.CENTER)
text.set_offsets((image.get_width() - text.get_width()) // 2, 300) # measure AFTER
Filters — prefer non-destructive
GIMP 3 filters stay live and editable, which is the right default on someone else's
artwork:
blur = Gimp.DrawableFilter.new(layer, "gegl:gaussian-blur", "glow blur")
config = blur.get_config()
config.set_property("std-dev-x", 90.0); config.set_property("std-dev-y", 90.0)
blur.update() # push config into the filter
layer.append_filter(blur) # or layer.merge_filter(blur) to bake it in
Discover ops and property names instead of guessing — Gegl.list_operations() and
Gegl.Operation.list_properties("gegl:gaussian-blur"). The legacy plug-in-gauss
family is gone in GIMP 3.
Anything without a binding: the PDB
proc = Gimp.get_pdb().lookup_procedure("script-fu-drop-shadow") # None if absent
config = proc.create_config()
config.set_property("run-mode", Gimp.RunMode.NONINTERACTIVE)
config.set_property("image", image)
config.set_core_object_array("drawables", [layer]) # NOT set_property
result = proc.run(config)
Export
Gimp.file_save(Gimp.RunMode.NONINTERACTIVE, image,
Gio.File.new_for_path(OUT + "/poster.xcf"), None) # .png/.jpg/... too
flat = image.duplicate(); flat.flatten() # flatten a copy, not the original
Gimp.file_save(Gimp.RunMode.NONINTERACTIVE, flat,
Gio.File.new_for_path(OUT + "/poster.png"), None)
flat.delete()
.xcf keeps every layer, mask and live filter editable for the user; ship it alongside
the flat export unless they said otherwise. Format options (JPEG quality, GIF timing)
come from calling the export plug-in through the PDB — see
api-reference.md.
Verification
- Returned stdout is the immediate signal —
print(...) comes straight back and a
failure returns the traceback with a non-zero exit.
- A thumbnail PNG is the visual check: save
image.get_thumbnail(500, 700, Gimp.PixbufTransparency.SMALL_CHECKS) into $OUT and
Read it. It is the composited image, so it is honest about blend modes and filters in
a way a layer-by-layer description is not.
- Numbers:
image.pick_color([...], x, y, True, False, 0.0) sampled through
Babl.format("R'G'B'A u8") proves a colour landed exactly; layer.histogram(...)
gives mean/median on a 0–255 scale. Dump size, layer names, offsets, modes and
attached filters as metrics.json and Read that rather than eyeballing the stack.
- Hand-off: the exported PNG/JPEG opens anywhere; the
.xcf keeps the work editable.
Gotchas
Gegl.Color.set_rgba is linear, Gegl.Color.new("#hex") is sRGB. Mixing them up
silently lightens every colour; read values back through
get_bytes(Babl.format("R'G'B'A u8")), not get_rgba().
set_core_object_array for arrays of GIMP objects. config.set_property( "drawables", [layer]) raises could not convert [...] to type 'GimpCoreObjectArray',
and reading one back needs result.get_core_object_array(i) — result.index(i)
returns a GBoxed that will not iterate.
- Paint calls take one drawable, not a list (
Gimp.paintbrush_default(layer, pts)),
while pick_color takes a list of drawables. The asymmetry is real.
Gimp.Font.get_by_name returns None for a missing font, and the failure only
surfaces inside TextLayer.new as Argument 2 does not allow None as a value. Use
Gimp.context_get_font() unless the user named a font, and check the return.
- Text layers resize themselves — centre and position them after setting text, size
and spacing, or you are measuring the previous layout.
Gimp.displays_flush() after edits, or the user stares at a stale canvas and you
both think the script did nothing.
- Success flags come first in
get_offsets, pick_color, histogram and
selection.bounds. Unpack the tuple; don't index into it blindly.
- Resource names are localised — on a Hungarian GIMP the gradients are
Előtérből háttérbe (RGB), not FG to BG (RGB). List them at runtime
(gimp-*-get-list via the PDB) instead of hard-coding English names.
image.delete() discards unsaved changes without asking. Call it only on images
your own script created; never on the user's.
- Group edits with
image.undo_group_start() / undo_group_end() so one Ctrl+Z
undoes the whole build rather than four hundred individual operations.
- No state survives a send — each one is a new
python-fu-eval process. Version
your images ("poster_v2") so re-runs don't fight the previous attempt.
--batch needs --batch-interpreter in GIMP 3; without it GIMP prints a list of
interpreters and quits. gimp-start.sh already passes it.
- A dead GIMP can leave the port bound. The
script-fu-server plug-in is its own
process and outlives a quitting GIMP for a while, so a bare port check says "up" when
nothing is behind it. --ping sees through this (the connection closes mid-reply, and
the orphan then exits) — if it reports that, just start GIMP again.
- GIMP 2.10 tutorials do not port.
gimpfu, pdb.gimp_*, RGB tuples and the
plug-in-gauss filters are all gone; check lookup_procedure for None before
trusting a procedure name you read somewhere.
- Tools are not scriptable — no transform widget, no free select, no menu-action
trigger. Fills, paint calls, selections and GEGL filters are the whole vocabulary; see
recipes.md.
Security
There is nothing to install here, but starting the Script-Fu server still turns GIMP into
a code-execution server. Say so before asking for that click.
- The server listens on
127.0.0.1:10008 and hands whatever arrives to python-fu-eval —
arbitrary Python with the user's privileges, over the user's open images. It carries no
authentication: every local process, and every other user on a shared machine, can
drive GIMP through it.
- Always 127.0.0.1. The Start Server dialog accepts any listen address, and a server
bound to
0.0.0.0 or a LAN address is remote code execution for the whole network.
gimp-start.sh pins loopback; if the user starts it by hand, tell them the address.
- It is not HTTP — the protocol is length-prefixed with a
G magic byte, so a browser's
request is rejected at the first byte. That is a side effect of the wire format, not an
origin check.
- To stop it, quit GIMP. The port can stay bound briefly after GIMP exits while the
plug-in process winds down, so a port check is not proof anything is live —
--ping
is (gimp-send.sh --ping).
1---2name: gimp3description: Remote-control a running GIMP (the open-source image editor) by Python script through its built-in Script-Fu server — build documents and layer stacks, fill, gradient and brush-stroke drawables, add text layers, attach non-destructive GEGL filters, cut out with masks and selections, retouch photos, and export PNG/JPEG/TIFF/GIF/.xcf or batch-convert whole folders in the session the user is looking at. Use whenever the user wants to create or edit a GIMP document (.xcf), composite, retouch or resize a raster image, batch-process a folder of images, script or automate GIMP, export layers separately, or says "GIMP", "xcf", "gimp script", "batch resize these photos", "open this in GIMP" — even if they do not mention scripting. Also covers Hungarian: "csinálj egy képet GIMP-ben", "vezéreld a GIMP-et", "rétegek", "exportáld PNG-be", "kötegelt átméretezés". Not for natural-media painting (use krita or rebelle), vector and layout design (use affinity), or keyframed motion graphics (use cavalry).4---56# GIMP Control78GIMP (`/Applications/GIMP.app`) embeds Python 3 and exposes its whole document model9through **libgimp** over GObject Introspection (`gi.repository.Gimp`), with **GEGL** for10pixels and filters. Drive it through GIMP's own **Script-Fu server** — a TCP endpoint11GIMP ships with, so there is nothing to install — which hands each script to GIMP's12`python-fu-eval`. The script runs against the *live* GIMP: the canvas updates, the layer13stack is the one the user is looking at, and nothing happens headless. Every call in14this skill was run against **GIMP 3.2.4**.1516- [scripts/gimp-send.sh](scripts/gimp-send.sh) — send a `.py` file (or `-c 'inline code'`)17 and print its captured output; `--ping` checks the connection, `--scheme` evaluates18 raw Script-Fu.19- [scripts/gimp-start.sh](scripts/gimp-start.sh) — launch the GIMP GUI with the20 Script-Fu server listening, and wait until it answers.21- [scripts/gimp_client.py](scripts/gimp_client.py) — the wire protocol, used by both.22- [scripts/example-poster.py](scripts/example-poster.py) — a complete23 build → verify → export script to copy the shape from.24- [references/api-reference.md](references/api-reference.md) — the version-checked25 libgimp API (images, layers, selections, drawing, text, GEGL filters, PDB calls,26 pixel readback, export). **Read it before writing anything past the cheatsheet below.**27- [references/recipes.md](references/recipes.md) — working on the user's own image,28 batch folders, per-layer export, masks, animation, and what the API cannot do.2930## The control loop31321. **Check the connection**: `bash scripts/gimp-send.sh --ping` →33 `{"ok": true, "bridge": "gimp", "port": 10008, "reply": "3.2.4 | open images: 2"}`.3435 If nothing answers, there are two different situations and they need different moves:3637 - **GIMP is not running** → `bash scripts/gimp-start.sh` starts the GUI with the38 server switched on and waits for it. This opens a window on the user's screen, so39 say that you are doing it.40 - **GIMP is running without the server** → you cannot switch it on remotely, and you41 must not start a second instance to get around that: two GIMPs share one config42 directory and the user's unsaved work is in the one already open. Ask them for one43 click: **Filters ▸ Development ▸ Script-Fu ▸ Start Server…**, listening on44 `127.0.0.1`, port `10008`. (*Development* stays English on a localised GIMP; on a45 Hungarian one the path reads Szűrők ▸ Development ▸ Script-Fu ▸ Kiszolgáló46 indítása….)47482. **Write a build script** to the scratchpad and send it:49 `OUT=/path/to/outdir bash scripts/gimp-send.sh /path/build.py`. `OUT` arrives both as50 a global and as `os.environ["OUT"]`; `GIMP_SEND_TIMEOUT=600` (seconds) covers batches51 and big exports.52533. **Read the feedback.** The sender returns the script's captured stdout/stderr, and on54 failure the Python traceback, exiting non-zero — so `print(...)` is your channel back.55 Nothing else reports: GIMP's own error console stays inside the app.56574. **Look at the result.** `image.get_thumbnail(w, h, Gimp.PixbufTransparency.SMALL_CHECKS)`58 renders the composited image straight to a PNG you can Read. Do this at the end of59 every build script — one line, and it catches what no assertion will.60615. **Iterate, then deliver.** Fix the script and re-send. Scripts must be62 **re-runnable**: create a fresh image per version rather than mutating the one already63 open, so a re-send cannot stack layers onto the previous attempt.6465Each send is a fresh `python-fu-eval` process, so nothing persists between sends except66what is in GIMP (open images) or on disk. Round-trip is roughly a second — cheap enough67to probe with a throwaway send rather than guess an API.6869## Scripting cheatsheet7071```python72import gi73gi.require_version("Babl", "0.1"); gi.require_version("Gegl", "0.4")74gi.require_version("Gimp", "3.0")75from gi.repository import Babl, Gegl, Gimp, Gio76Gegl.init(None)77```7879### Image and layer stack8081```python82image = Gimp.Image.new(1000, 1400, Gimp.ImageBaseType.RGB)83layer = Gimp.Layer.new(image, "background", 1000, 1400,84 Gimp.ImageType.RGBA_IMAGE, 100.0, Gimp.LayerMode.NORMAL)85image.insert_layer(layer, None, 0) # parent=None -> top level; 0 = topmost86layer.set_opacity(70.0) # 0–100 float, not 0–25587layer.set_mode(Gimp.LayerMode.SCREEN)88Gimp.Display.new(image) # show it — the user watches the build happen89Gimp.displays_flush() # nothing repaints without this90```9192To work on what the user already has: `Gimp.get_images()[0]`, most recently active93first. Duplicate it before destructive work — see94[recipes.md](references/recipes.md#work-on-the-image-the-user-already-has-open).9596### Colour: sRGB in, sRGB out9798`Gegl.Color` stores **linear** RGB, so the constructor and the setter are not the same99thing and the difference is a visibly washed-out image:100101```python102Gegl.Color.new("#1a73e8") # parses sRGB — use this103colour.set_rgba(0.10, 0.45, 0.85, 1.0) # LINEAR components — rarely what you meant104list(colour.get_bytes(Babl.format("R'G'B'A u8")).get_data()) # [26,115,232,255]105Gimp.context_set_foreground(Gegl.Color.new("#ffb703"))106```107108### Selections, fills, drawing109110```python111image.select_rectangle(Gimp.ChannelOps.REPLACE, x, y, w, h) # also ellipse, polygon,112layer.edit_fill(Gimp.FillType.FOREGROUND) # round_rectangle, color113Gimp.Selection.none(image) # every later edit respects the selection114115layer.fill(Gimp.FillType.FOREGROUND) # whole layer, ignores the selection116Gimp.context_set_gradient_fg_bg_rgb()117layer.edit_gradient_fill(Gimp.GradientType.LINEAR, 0.0, False, 1.0, 0.0, True,118 x1, y1, x2, y2)119120Gimp.context_set_brush(Gimp.Brush.get_by_name("2. Hardness 100"))121Gimp.context_set_brush_size(20.0)122Gimp.paintbrush_default(layer, [40.0, 40.0, 200.0, 200.0]) # ONE drawable, flat x/y list123```124125### Text126127```python128font = Gimp.context_get_font() # safer than naming one: get_by_name129text = Gimp.TextLayer.new(image, "GIMP", font, 150.0, Gimp.Unit.pixel())130image.insert_layer(text, None, 0)131text.set_color(Gegl.Color.new("#f8f9fa"))132text.set_justification(Gimp.TextJustification.CENTER)133text.set_offsets((image.get_width() - text.get_width()) // 2, 300) # measure AFTER134```135136### Filters — prefer non-destructive137138GIMP 3 filters stay live and editable, which is the right default on someone else's139artwork:140141```python142blur = Gimp.DrawableFilter.new(layer, "gegl:gaussian-blur", "glow blur")143config = blur.get_config()144config.set_property("std-dev-x", 90.0); config.set_property("std-dev-y", 90.0)145blur.update() # push config into the filter146layer.append_filter(blur) # or layer.merge_filter(blur) to bake it in147```148149Discover ops and property names instead of guessing — `Gegl.list_operations()` and150`Gegl.Operation.list_properties("gegl:gaussian-blur")`. The legacy `plug-in-gauss`151family is gone in GIMP 3.152153### Anything without a binding: the PDB154155```python156proc = Gimp.get_pdb().lookup_procedure("script-fu-drop-shadow") # None if absent157config = proc.create_config()158config.set_property("run-mode", Gimp.RunMode.NONINTERACTIVE)159config.set_property("image", image)160config.set_core_object_array("drawables", [layer]) # NOT set_property161result = proc.run(config)162```163164### Export165166```python167Gimp.file_save(Gimp.RunMode.NONINTERACTIVE, image,168 Gio.File.new_for_path(OUT + "/poster.xcf"), None) # .png/.jpg/... too169flat = image.duplicate(); flat.flatten() # flatten a copy, not the original170Gimp.file_save(Gimp.RunMode.NONINTERACTIVE, flat,171 Gio.File.new_for_path(OUT + "/poster.png"), None)172flat.delete()173```174175`.xcf` keeps every layer, mask and live filter editable for the user; ship it alongside176the flat export unless they said otherwise. Format options (JPEG quality, GIF timing)177come from calling the export plug-in through the PDB — see178[api-reference.md](references/api-reference.md#loading-saving-exporting).179180## Verification181182- **Returned stdout** is the immediate signal — `print(...)` comes straight back and a183 failure returns the traceback with a non-zero exit.184- **A thumbnail PNG** is the visual check: save185 `image.get_thumbnail(500, 700, Gimp.PixbufTransparency.SMALL_CHECKS)` into `$OUT` and186 Read it. It is the composited image, so it is honest about blend modes and filters in187 a way a layer-by-layer description is not.188- **Numbers**: `image.pick_color([...], x, y, True, False, 0.0)` sampled through189 `Babl.format("R'G'B'A u8")` proves a colour landed exactly; `layer.histogram(...)`190 gives mean/median on a 0–255 scale. Dump size, layer names, offsets, modes and191 attached filters as `metrics.json` and Read that rather than eyeballing the stack.192- **Hand-off**: the exported PNG/JPEG opens anywhere; the `.xcf` keeps the work editable.193194## Gotchas195196- **`Gegl.Color.set_rgba` is linear, `Gegl.Color.new("#hex")` is sRGB.** Mixing them up197 silently lightens every colour; read values back through198 `get_bytes(Babl.format("R'G'B'A u8"))`, not `get_rgba()`.199- **`set_core_object_array` for arrays of GIMP objects.** `config.set_property(200 "drawables", [layer])` raises `could not convert [...] to type 'GimpCoreObjectArray'`,201 and reading one back needs `result.get_core_object_array(i)` — `result.index(i)`202 returns a `GBoxed` that will not iterate.203- **Paint calls take one drawable, not a list** (`Gimp.paintbrush_default(layer, pts)`),204 while `pick_color` takes a *list* of drawables. The asymmetry is real.205- **`Gimp.Font.get_by_name` returns `None` for a missing font**, and the failure only206 surfaces inside `TextLayer.new` as `Argument 2 does not allow None as a value`. Use207 `Gimp.context_get_font()` unless the user named a font, and check the return.208- **Text layers resize themselves** — centre and position them after setting text, size209 and spacing, or you are measuring the previous layout.210- **`Gimp.displays_flush()` after edits**, or the user stares at a stale canvas and you211 both think the script did nothing.212- **Success flags come first** in `get_offsets`, `pick_color`, `histogram` and213 `selection.bounds`. Unpack the tuple; don't index into it blindly.214- **Resource names are localised** — on a Hungarian GIMP the gradients are215 `Előtérből háttérbe (RGB)`, not `FG to BG (RGB)`. List them at runtime216 (`gimp-*-get-list` via the PDB) instead of hard-coding English names.217- **`image.delete()` discards unsaved changes without asking.** Call it only on images218 your own script created; never on the user's.219- **Group edits with `image.undo_group_start()` / `undo_group_end()`** so one Ctrl+Z220 undoes the whole build rather than four hundred individual operations.221- **No state survives a send** — each one is a new `python-fu-eval` process. Version222 your images (`"poster_v2"`) so re-runs don't fight the previous attempt.223- **`--batch` needs `--batch-interpreter` in GIMP 3**; without it GIMP prints a list of224 interpreters and quits. `gimp-start.sh` already passes it.225- **A dead GIMP can leave the port bound.** The `script-fu-server` plug-in is its own226 process and outlives a quitting GIMP for a while, so a bare port check says "up" when227 nothing is behind it. `--ping` sees through this (the connection closes mid-reply, and228 the orphan then exits) — if it reports that, just start GIMP again.229- **GIMP 2.10 tutorials do not port.** `gimpfu`, `pdb.gimp_*`, RGB tuples and the230 `plug-in-gauss` filters are all gone; check `lookup_procedure` for `None` before231 trusting a procedure name you read somewhere.232- **Tools are not scriptable** — no transform widget, no free select, no menu-action233 trigger. Fills, paint calls, selections and GEGL filters are the whole vocabulary; see234 [recipes.md](references/recipes.md#what-the-api-cannot-do).235236## Security237238There is nothing to install here, but starting the Script-Fu server still turns GIMP into239a **code-execution server**. Say so before asking for that click.240241- The server listens on `127.0.0.1:10008` and hands whatever arrives to `python-fu-eval` —242 arbitrary Python with the user's privileges, over the user's open images. It carries **no243 authentication**: every local process, and every other user on a shared machine, can244 drive GIMP through it.245- **Always 127.0.0.1.** The Start Server dialog accepts any listen address, and a server246 bound to `0.0.0.0` or a LAN address is remote code execution for the whole network.247 `gimp-start.sh` pins loopback; if the user starts it by hand, tell them the address.248- It is not HTTP — the protocol is length-prefixed with a `G` magic byte, so a browser's249 request is rejected at the first byte. That is a side effect of the wire format, not an250 origin check.251- **To stop it, quit GIMP.** The port can stay bound briefly after GIMP exits while the252 plug-in process winds down, so a port check is not proof anything is live — `--ping`253 is (`gimp-send.sh --ping`).