The Hive's screen
Laboratory. The screen language exists to press on the language and to drive Quantum OS; it carries no stability promise and may change in any release (
SUPPORT_TIERS.md).
A q:application type="screen" is what the Hive of Quantum OS shows on the machine's screen: a small pixel-art scene and, per event of the Hive, what happens in it. It compiles to screen.toml, a font sheet and the sprite sheets, which hive-screen (os/crates/hive-screen) loads: a daemon of a few hundred kilobytes that draws the scene on the console, lists the Hive's offers for an administrator to approve with the keyboard, and sends the approval back through the same socket the dashboard uses. No engine, no compositor, no GPU: the scene is drawn on the CPU and scaled up by a whole number to fill the display.
quantum run screen.q --out projects/screen/screen # writes screen.toml, font.png and the sheets
python scripts/generate-screen-config.py # the OS's own: os/config/screen/screen.q -> os/config/screen/The vocabulary of the scene is the games' (qg:): a sheet of frames, an animation, a tween with the same curves, the same particle presets, with the same meaning. What moves the scene are the Hive's events, the same ones the rules are on, with the same {field}s, filled in from the event itself. There is nothing to compute: no statements, no expressions, and text where a tag goes is a compile error.
The shape of a screen
<q:application id="screen" type="screen">
<qs:sheet name="cell" src="art/cells.png" tile="24">
<qs:animation name="smoke" frames="2,3,4,5,6,2" fps="4" loop="false" />
</qs:sheet>
<qs:particles name="stars" preset="stars" />
<qs:scene width="320" height="180" background="#1a1a2e">
<qs:bar side="top" title="Quantum OS" left="{host}" right="{level} · {clock}" />
<qs:sprite name="nginx" sheet="cell" frame="1" x="6" y="22" />
<qs:text value="nginx" x="34" y="29" />
<qs:offers title="Offers" x="188" y="18" width="126" rows="9" />
<qs:notices title="Notices" x="6" y="132" width="308" rows="2" />
</qs:scene>
<qs:on event="service-crash">
<qs:animate target="{service}" name="smoke" /> <!-- the sprite named after the service -->
<qs:say text="{service} stopped (exit {exit_code})" />
</qs:on>
<qs:on event="done">
<qs:frame target="{service}" frame="0" />
<qs:emit particles="stars" at="{service}" dx="12" dy="12" />
</qs:on>
</q:application>A sprite's name is how a rule reaches it: {service} on service-crash is nginx, and the sprite named nginx smokes. An action whose target no sprite is named, or whose {field} the event does not provide, is left out, as the rules leave out an offer they cannot fill in. The rule on service-crash compiles to this, in screen.toml:
[[rules]]
event = "service-crash"
[[rules.actions]]
kind = "animate"
target = "{service}"
name = "smoke"
[[rules.actions]]
kind = "say"
text = "{service} stopped (exit {exit_code})"
seconds = 4.0The font
Every screen gets the same face: a 5x7 pixel font of our own, in 6x10 cells, with the printable ASCII characters and the Latin-1 letters (the accents of Portuguese, Spanish, French, German composed from the base letters). It is written as font.png next to the scene, so to the daemon it is one more sheet. A character the font lacks shows as a box.
Attribute types
| Type | What it is |
|---|---|
text | a string; in a rule, {field} placeholders allowed |
ident | a name (letters, digits, _ @ . : -) |
int | a whole number |
float | a number |
bool | true or false |
color | #rgb or #rrggbb |
tile | a frame size in pixels: 24, or 16x24 |
frames | frame numbers separated by commas |
src | a PNG next to the .q, or in a folder below it |
enum | one of the values listed |
The tags
qs:sheet
A PNG of equal frames, numbered left to right and top to bottom from 0.
Goes inside <q:application>.
| Attribute | Type | Default | What it is |
|---|---|---|---|
name= | ident | required | |
src= | src | required | |
tile= | tile | required | frame size in pixels: 24, or 16x24 |
qs:animation
Frames of its sheet, cycled: what a qs:sprite plays from the start (animation=) or a rule starts (qs:animate).
Goes inside <qs:sheet>.
| Attribute | Type | Default | What it is |
|---|---|---|---|
name= | ident | required | |
frames= | frames | required | |
fps= | float | 8.0 | |
loop= | bool | True | false: it plays once and stays on its last frame |
qs:particles
A particle effect: a preset and a few overrides, as in a game. qs:emit plays it once where a sprite is; qs:emitter keeps it going in the scene.
Goes inside <q:application>.
| Attribute | Type | Default | What it is |
|---|---|---|---|
name= | ident | required | |
preset= | enum: puff, sparks, smoke, dust, fire, rain, snow, confetti, stars | required | puff, sparks, smoke, dust, confetti and stars play once; fire, rain and snow keep going |
color= | color | the preset's own when not given | |
amount= | int | how many particles | |
lifetime= | float | seconds each one lives | |
speed= | float | pixels a second, about | |
size= | float | pixels, about |
qs:scene
What is on the screen, in pixels of the scene: the daemon scales it up by a whole number to fill the display. One per file.
Goes inside <q:application>.
| Attribute | Type | Default | What it is |
|---|---|---|---|
width= | int | 320 | |
height= | int | 180 | |
background= | color | #000000 |
qs:sprite
A picture in the scene: one frame of a sheet, or an animation of it. Its name is how a rule finds it: a sprite named after a service is the one {service} reaches.
Goes inside <qs:scene>.
| Attribute | Type | Default | What it is |
|---|---|---|---|
name= | ident | ||
sheet= | ident | required | |
frame= | int | 0 | |
x= | int | required | |
y= | int | required | |
animation= | ident | an animation of its sheet, played from the start | |
hidden= | bool | False | not drawn until a qs:show |
qs:text
Fixed text in the scene, in the screen's font. What an event says goes in a qs:say or the qs:notices.
Goes inside <qs:scene>.
| Attribute | Type | Default | What it is |
|---|---|---|---|
name= | ident | ||
value= | text | required | |
x= | int | required | |
y= | int | required | |
color= | color | #ffffff | |
align= | enum: left, center, right | left | which side of the text x is |
qs:emitter
A particle effect that keeps going in the scene: rain over it, a fire on a spot.
Goes inside <qs:scene>.
| Attribute | Type | Default | What it is |
|---|---|---|---|
particles= | ident | required | |
x= | int | required | |
y= | int | required | |
width= | int | 0 | for rain and snow, how wide a band they fall across |
level= | int | 1 | the presentation level it plays from: 1 whenever the effects are on, 2 only at the fine level, which the daemon affords with the cores |
qs:offers
Where the Hive's offers are listed, one a row, for an administrator to approve: up and down move, Enter approves the one with the focus (a dangerous action asks for Enter twice), Escape dismisses it. At most one.
Goes inside <qs:scene>.
| Attribute | Type | Default | What it is |
|---|---|---|---|
x= | int | required | |
y= | int | required | |
width= | int | required | |
rows= | int | 4 | |
title= | text | given, the rows sit in a panel with a bevelled frame, a shadow and this title on its bar; x, y and width are then the panel's, and it is as tall as its rows | |
color= | color | #ffffff | |
focus= | color | #ffd34d | the row with the focus |
qs:notices
The last notices, one a row, newest at the bottom. At most one.
Goes inside <qs:scene>.
| Attribute | Type | Default | What it is |
|---|---|---|---|
x= | int | required | |
y= | int | required | |
width= | int | required | |
rows= | int | 3 | |
title= | text | given, the rows sit in a panel with a bevelled frame, a shadow and this title on its bar; x, y and width are then the panel's, and it is as tall as its rows | |
color= | color | #c0c0c0 |
qs:bar
A bar across the scene, as a window manager's: at the top or at the bottom, a title in the accent at the left, a text after it and one at the right. The texts may hold the screen's own {field}s (below). The bar at the bottom shows what a qs:say says while it lasts. At most one a side.
Goes inside <qs:scene>.
| Attribute | Type | Default | What it is |
|---|---|---|---|
side= | enum: top, bottom | required | |
title= | text | at the left, in the accent | |
left= | text | after the title | |
right= | text | against the right edge | |
color= | color | #8a8aa0 | |
accent= | color | #ffd34d | the title's colour |
The fields a bar's texts may hold, which the daemon fills in as it runs (a test's screen shows none of it, so its frames are the same on every machine):
| Field | What it is |
|---|---|
{clock} | the time, HH:MM (--:-- until the daemon has it) |
{level} | the presentation level: plain, effects or fine |
{cores} | how many cores the machine gives the screen |
{host} | the machine's name |
{offers} | how many offers wait for an approval |
qs:on
What happens in the scene when an event arrives: the actions inside, in order. One per event.
Goes inside <q:application>.
| Attribute | Type | Default | What it is |
|---|---|---|---|
event= | enum: service-crash, log-anomaly, port-conflict, disk-low, memory-pressure, command-observed, command-not-found, notice, offer, request, done, failed | required |
qs:animate
Plays an animation of the sprite's sheet on it, from its first frame.
Goes inside <qs:on>.
| Attribute | Type | Default | What it is |
|---|---|---|---|
target= | text | required | a qs:sprite's name, or a {field} that is one (a service name); nothing happens when no sprite has that name |
name= | ident | required |
qs:frame
Shows one frame of the sprite's sheet on it, and stops whatever it was playing.
Goes inside <qs:on>.
| Attribute | Type | Default | What it is |
|---|---|---|---|
target= | text | required | a qs:sprite's name, or a {field} that is one (a service name); nothing happens when no sprite has that name |
frame= | int | required |
qs:show
Shows or hides a sprite.
Goes inside <qs:on>.
| Attribute | Type | Default | What it is |
|---|---|---|---|
target= | text | required | a qs:sprite's name, or a {field} that is one (a service name); nothing happens when no sprite has that name |
visible= | bool | True |
qs:tween
Eases a property of a sprite from what it is to a value, over some seconds, with the games' curves.
Goes inside <qs:on>.
| Attribute | Type | Default | What it is |
|---|---|---|---|
target= | text | required | a qs:sprite's name, or a {field} that is one (a service name); nothing happens when no sprite has that name |
property= | enum: x, y, scale, alpha | required | x, y: where it is; scale: its size (1 is as drawn); alpha: how opaque (0 to 1) |
to= | float | required | |
from= | float | where it starts (what it is now when not given) | |
seconds= | float | 0.5 | |
trans= | enum: linear, sine, quad, cubic, back, bounce, elastic | sine | the curve |
ease= | enum: in, out, in-out | out | which end of the curve is slow |
repeat= | int | 0 | times it plays again (-1: for ever) |
yoyo= | bool | False | each repeat goes back the other way |
qs:emit
Plays a qs:particles effect once where a sprite is.
Goes inside <qs:on>.
| Attribute | Type | Default | What it is |
|---|---|---|---|
particles= | ident | required | |
at= | text | required | a qs:sprite's name, or a {field} that is one (a service name); nothing happens when no sprite has that name |
dx= | int | 0 | |
dy= | int | 0 | offset in pixels from the sprite's top left |
qs:say
Shows a line of text at the bottom of the scene for a while: what happened, in words.
Goes inside <qs:on>.
| Attribute | Type | Default | What it is |
|---|---|---|---|
text= | text | required | |
seconds= | float | 4.0 |
The events
What a qs:on can be on. The observations are the ones the rules name, as hive-cortex tells them to its clients, each with title, body and severity besides its own fields; the rest are the screen's own.
service-crash
A unit stopped unexpectedly (hive-nerve-journal saw it in the journal).
| Field | Type | What it is |
|---|---|---|
{unit} | text | the unit, as systemd names it (nginx.service) |
{service} | text | the service name the actuator uses (nginx); absent when the unit is not a .service, and the offers that need it are left out |
{exit_code} | int | how it exited |
{title} | text | the notice, as hive-cortex titles it |
{body} | text | what it says |
{severity} | text | info, notice, warning, error or critical |
log-anomaly
A unit logs an unusual number of errors.
| Field | Type | What it is |
|---|---|---|
{unit} | text | the unit, as systemd names it |
{service} | text | the service name, as in service-crash |
{title} | text | the notice, as hive-cortex titles it |
{body} | text | what it says |
{severity} | text | info, notice, warning, error or critical |
port-conflict
A program could not bind a port another one holds (hive-nerve-network).
| Field | Type | What it is |
|---|---|---|
{port} | int | the port |
{requester} | text | the program that failed to bind |
{holder} | text | the program that holds the port |
{title} | text | the notice, as hive-cortex titles it |
{body} | text | what it says |
{severity} | text | info, notice, warning, error or critical |
disk-low
A filesystem is nearly full.
| Field | Type | What it is |
|---|---|---|
{mount} | text | the mount point |
{percent} | int | how full, in percent |
{free_mb} | int | megabytes free |
{title} | text | the notice, as hive-cortex titles it |
{body} | text | what it says |
{severity} | text | info, notice, warning, error or critical |
memory-pressure
The system is running low on memory.
| Field | Type | What it is |
|---|---|---|
{free_mb} | int | megabytes available |
{total_mb} | int | megabytes in all |
{title} | text | the notice, as hive-cortex titles it |
{body} | text | what it says |
{severity} | text | info, notice, warning, error or critical |
command-observed
A user ran a command the Hive judges dangerous (hive-nerve-process, from the audit log).
| Field | Type | What it is |
|---|---|---|
{cmd} | text | the command line |
{program} | text | the program |
{title} | text | the notice, as hive-cortex titles it |
{body} | text | what it says |
{severity} | text | info, notice, warning, error or critical |
command-not-found
A user ran a program that is not installed, and a package of that name exists (grove).
| Field | Type | What it is |
|---|---|---|
{command} | text | what the user typed |
{package} | text | the package that would provide it |
{title} | text | the notice, as hive-cortex titles it |
{body} | text | what it says |
{severity} | text | info, notice, warning, error or critical |
notice
A notice with no observation behind it (hive-nerve-fs reports its filesystem events this way).
| Field | Type | What it is |
|---|---|---|
{title} | text | the notice, as hive-cortex titles it |
{body} | text | what it says |
{severity} | text | info, notice, warning, error or critical |
offer
The Hive offered something an administrator may approve; the qs:offers lists it.
| Field | Type | What it is |
|---|---|---|
{count} | int | how many offers came together |
{title} | text | the notice, as hive-cortex titles it |
{body} | text | what it says |
{severity} | text | info, notice, warning, error or critical |
request
A user asked the Hive for a change (the Settings app, hive request): the offer carries it, for an administrator to approve as any other; the qs:offers lists it.
| Field | Type | What it is |
|---|---|---|
{user} | text | who asked |
{action} | text | what they asked for: the action's name (set-hostname) |
{what} | text | what it does, in words (name the machine forge) |
{count} | int | how many offers came with it: one |
{title} | text | the notice, as hive-cortex titles it |
{body} | text | what it says |
{severity} | text | info, notice, warning, error or critical |
done
An action an administrator approved on this screen ran and succeeded.
| Field | Type | What it is |
|---|---|---|
{action} | text | what ran: the action's name (restart-service) |
{service} | text | the service it was on, when it was on one |
{package} | text | the package it was about, when it was about one |
{output} | text | what it printed, if anything |
failed
An action an administrator approved on this screen ran and failed.
| Field | Type | What it is |
|---|---|---|
{action} | text | what ran: the action's name (restart-service) |
{service} | text | the service it was on, when it was on one |
{package} | text | the package it was about, when it was about one |
{output} | text | what it said, if anything |
The actions
What a qs:on holds, in order: qs:animate, qs:frame, qs:show, qs:tween, qs:emit, qs:say. A target is a sprite's name or a {field} that is one.
What is refused
A compile error names the line. The compiler refuses:
- a tag, an attribute, an event or an action the schema does not list;
- a sheet that is not there, is not a PNG, or is not a whole number of its frames; a frame a sheet does not have; an animation its sheet lacks; a sprite of a sheet that does not exist;
- a
{field}that is not a field of the rule's event; a{field}in the scene (it is filled in from an event, so it goes in aqs:on); - a literal target no sprite is named; an animation the target's sheet lacks; particles no
qs:particlesdeclares; - a
q:statement, an expression, or text where a tag goes; - two
qs:onon one event, aqs:onwith nothing to do, twoqs:scene, twoqs:offers, twoqs:baron one side, two sprites of one name, a tween of no time; a{field}of an event in aqs:bar(a bar shows the screen's own fields) or in a panel's title.
hive-screen refuses a screen.toml that is not what the compiler writes, whole; it never guesses at a scene.
How it reaches the daemon
screen.toml, font.png and the sheets are installed under /etc/hive/screen/. The OS's own are written from os/config/screen/screen.q by scripts/generate-screen-config.py (the art by scripts/art/hive_screen.py), committed as written and checked in CI. hive-screen connects to hive-cortex's user socket as the dashboard does, and cortex lets it in as one: it receives every notice with the event's name and fields, and every offer, and may approve and dismiss. The Hive lab (os/crates/hive-lab) runs the real hive-screen with no display and reads what it shows as data ({"op":"screen","play":{"op":"until","offer":"Restart nginx"}}); a test of the daemon compares its frames pixel for pixel with references.
The levels
How much the daemon shows is a presentation level, chosen as it runs by the frame time it measures, never above what the machine's cores allow (read as it runs: a vCPU plugged in counts within half a second):
| Level | Needs | Shows |
|---|---|---|
0 plain | 1 core | the scene as declared, no particles, at most 15 pictures a second |
1 effects | 2 cores | the particles too (qs:emit, and every qs:emitter of level="1"), at most 30 a second |
2 fine | 4 cores | two pixels to the scene's one: a tween or a particle lands on its own pixel, the font's pixels are blocks; and the emitters of level="2". A page on the screen is drawn pixel for pixel instead, so its window has 100 columns and up to 29 rows there, against 49 by up to 13 |
A level changes how the scene shows, never what happens in it: the same ticks run at every level, and a rule's actions run the same. The frame is drawn in bands, one thread a core, and is the same picture whatever their number. A scene at rest costs nothing at any level. The lab, the tests and --play play at level 1 unless asked ({"op":"level","level":2}).
The look of the scene
The scene is drawn in the look of a window manager in pixel art, the same the pages below take. A qs:bar spans the scene: at the top, 14 pixels (a light line, the bar, a rule), at the bottom 12 (a rule and the bar); its title sits at the left in the accent, the text after it and the text at the right in the bar's colour, 4 pixels in from each edge, and what a qs:say says takes the left of the bottom bar while it lasts (with no bottom bar it is a strip at the bottom of the scene). A qs:offers or qs:notices with a title is a panel: a 1-px bevel (light above and left, dark below and right), a 3-px shadow, a title bar of 10 pixels with a rule under it, and its rows inside, 3 pixels in from the frame, each a row of the font; the panel is as tall as its rows. The cells of the OS's honeycomb (scripts/art/hive_screen.py) carry their own bevel and a 2-px shadow in their frames.
Pages on the screen
The screen is also where the machine's applications show: quantum pixel, run by any local user in a project's folder, brings the application's pages over the scene (one app, many screens). The page's runtime is the application's own server, started in that process as quantum console starts it; it connects to hive-screen's pixel socket (/run/hive-screen/pixel.sock, anyone's) and sends each page's view tree as JSON ({"op":"show","page":{...}}), whole, every time it changes. The screen lays it out in cells of its font and draws it over the scene as a window: a frame with a 1-px bevel and a 3-px shadow, the scene dimmed around it, a honey title bar with the Esc key drawn on it, the content, and a status bar with the keys that apply; fields are sunken, buttons and badges raised, a scrollbar appears when the page is longer than the window. The window is as tall as its content, up to the canvas, and sits in the middle: 49 columns and up to 13 rows at 320×180; 100 by up to 29 at the fine level, where the page is drawn pixel for pixel on the canvas of two pixels to the scene's one, and flows again when the level changes, keeping its focus. The page moves as the window managers of 2008 did, in pixel art and in ticks: it opens with the jelly (grows from 60% with an overshoot, 12 ticks), closes with the lamp (bends and is sucked into the bottom left corner, 14 ticks), and a page shown again after an action replaces the one before through an ordered dither, the curtain (8 ticks); nothing moves at the plain level, and HIVE_SCREEN_MOTION=off in the unit's environment stops it all. It is drawn with the keyboard its own, and sends back what the user did, one JSON line an event: click and submit (with the form's fields, as a browser would post them), change (a search field, after its pause), navigate (a link), reload (F5) and close (Esc, after which the page's runtime ends). Only one page shows at a time; a second quantum pixel is told so. Keyboard only: Tab and Shift+Tab between the elements that react, the arrows inside one (Up and Down between elements when what has the focus does not take them, Home and End to the first and the last), Enter acts, Space marks, Esc closes; the last line says the keys that apply, and a scrollbar at the right edge says where in a long page the screen is. F1 opens the Hive's offers in a menu over the page (or over the scene), a drawer that slides in from the bottom left and pushes the comb aside: Up and Down choose, Enter approves (twice for a dangerous action), Del dismisses, Esc shuts it and the page has its keys back; the window's status bar says how many offers wait (F1 · 2 ofertas), and the bar at the bottom shows the open page as a button, as a taskbar does. The keyboard is read by the machine's layout: US, or ABNT2 (XKBLAYOUT="br" in /etc/default/keyboard, or HIVE_SCREEN_KEYBOARD=br in the unit's environment) with ç on its key and the dead accents (´ ` ^ ~ ¨) composed with the vowel after them; the font has every Latin-1 letter.
In the play protocol (--play), {"op":"show","page":{...}} shows a page with no runtime, act takes type (a text) and every key by name (tab, shift-tab, left, right, home, end, backspace, delete, space, pgup, pgdn, f1, f5, ctrl-u, or one character), until takes pixel (an event), text (shown on the page) and shown (how many times a page was shown), still (no transition runs), and the observation's window says the page as data and motion the transition it is in: its title, flash, the elements that react in order with their state and which has the focus, the texts, the help line. --pixel-socket PATH gives a test the socket too: the UI parity test runs its one script on the screen with quantum pixel as the page's runtime.
The screen Quantum OS ships
os/config/screen/screen.q, whole: a bar at the top with the machine's name, its cores, the level and the time, a honeycomb with a cell per service the whitelist names, a bee, the offers and the notices as panels, and a bar at the bottom where what the Hive says shows.
<!-- The Hive's screen: what hive-screen draws on the machine's console, and what happens in it when
the Hive's events arrive. Compiles to screen.toml and font.png next to this file
(python scripts/generate-screen-config.py), which hive-screen loads from /etc/hive/screen/. -->
<q:application id="hive-screen" type="screen">
<!-- the cells of the honeycomb: 24x24 frames, drawn by scripts/art/hive_screen.py -->
<qs:sheet name="cell" src="art/cells.png" tile="24">
<qs:animation name="smoke" frames="2,3,4,5,6,2" fps="4" loop="false" />
<qs:animation name="worry" frames="7,1" fps="2" />
</qs:sheet>
<qs:sheet name="bee" src="art/bee.png" tile="16">
<qs:animation name="fly" frames="0,1" fps="10" />
</qs:sheet>
<qs:particles name="stars" preset="stars" />
<qs:particles name="dust" preset="dust" color="#8a7a5a" />
<qs:particles name="snow" preset="snow" amount="24" color="#c8d0f0" />
<qs:scene width="320" height="180" background="#1a1a2e">
<!-- the fine level, which the daemon affords with the machine's cores: snow over the scene -->
<qs:emitter particles="snow" x="0" y="0" width="320" level="2" />
<!-- the bar at the top: the machine, its cores, the level the screen is at and the time -->
<qs:bar side="top" title="Quantum OS" left="{host}" right="{cores} cores · {level} · {clock}" />
<!-- one cell a service the whitelist names; a cell fills with honey when the Hive restarts it -->
<qs:sprite name="nginx" sheet="cell" frame="1" x="6" y="18" />
<qs:text value="nginx" x="34" y="24" />
<qs:sprite name="apache2" sheet="cell" frame="1" x="6" y="46" />
<qs:text value="apache2" x="34" y="52" />
<qs:sprite name="postgresql" sheet="cell" frame="1" x="6" y="74" />
<qs:text value="postgresql" x="34" y="80" />
<qs:sprite name="mysql" sheet="cell" frame="1" x="6" y="102" />
<qs:text value="mysql" x="34" y="108" />
<qs:sprite name="redis-server" sheet="cell" frame="1" x="98" y="18" />
<qs:text value="redis" x="126" y="24" />
<qs:sprite name="mongodb" sheet="cell" frame="1" x="98" y="46" />
<qs:text value="mongodb" x="126" y="52" />
<qs:sprite name="docker" sheet="cell" frame="1" x="98" y="74" />
<qs:text value="docker" x="126" y="80" />
<qs:sprite name="containerd" sheet="cell" frame="1" x="98" y="102" />
<qs:text value="containerd" x="126" y="108" />
<qs:sprite name="bee" sheet="bee" x="168" y="36" animation="fly" />
<!-- the offers and the notices as panels with their own title bars -->
<qs:offers title="Offers" x="188" y="18" width="126" rows="9" />
<qs:notices title="Notices" x="6" y="132" width="308" rows="2" />
<!-- the bar at the bottom: what the Hive said last shows here while it lasts -->
<qs:bar side="bottom" right="offers: {offers} · F1" />
</qs:scene>
<qs:on event="service-crash">
<qs:animate target="{service}" name="smoke" />
<qs:emit particles="dust" at="{service}" dx="12" dy="12" />
<qs:say text="{service} stopped (exit {exit_code})" />
</qs:on>
<qs:on event="log-anomaly">
<qs:animate target="{service}" name="worry" />
<qs:say text="{service} is logging errors" />
</qs:on>
<qs:on event="port-conflict">
<qs:say text="port {port}: {requester} wanted it, {holder} holds it" />
</qs:on>
<qs:on event="disk-low">
<qs:say text="{mount} is {percent}% full, {free_mb} MB free" />
</qs:on>
<qs:on event="memory-pressure">
<qs:say text="memory: {free_mb} of {total_mb} MB free" />
</qs:on>
<qs:on event="command-observed">
<qs:say text="watched: {cmd}" />
</qs:on>
<qs:on event="command-not-found">
<qs:say text="{command}: not installed; {package} would provide it" />
</qs:on>
<qs:on event="notice">
<qs:say text="{title}: {body}" />
</qs:on>
<qs:on event="request">
<qs:emit particles="stars" at="bee" dx="8" dy="8" />
<qs:say text="{user} asks: {what}; Enter approves, Escape dismisses" seconds="6" />
</qs:on>
<qs:on event="offer">
<qs:emit particles="stars" at="bee" dx="8" dy="8" />
<qs:tween target="bee" property="y" to="42" from="36" seconds="0.4" trans="sine" ease="in-out" repeat="3" yoyo="true" />
<qs:say text="the Hive offers {count}: up and down choose, Enter approves, Escape dismisses" seconds="6" />
</qs:on>
<qs:on event="done">
<qs:frame target="{service}" frame="0" />
<qs:emit particles="stars" at="{service}" dx="12" dy="12" />
<qs:say text="{action}: done" />
</qs:on>
<qs:on event="failed">
<qs:say text="{action} failed: {output}" seconds="8" />
</qs:on>
</q:application>