noctalia-plugin: captures as streams (no 5 s timeout), valw's error message in notifications, full state on every --set
This commit is contained in:
1 parent
9fa71fd399
commit
619eab9dc9
11 files changed
+234
-81
No files matched your search
@@ -1247,7 +1247,7 @@ noctalia msg plugins enable elars/valw
|
|||||||
noctalia msg panel-toggle elars/valw:toolbar
|
noctalia msg panel-toggle elars/valw:toolbar
|
||||||
```
|
```
|
||||||
|
|
||||||
Expected: the panel appears near the bar with the four modes, the timer and three toggles, in Noctalia's style. Ask the user to confirm what they see, then close it (`noctalia msg panel-close`). Check `grep -i valw ~/.cache/noctalia/noctalia.log | tail -20` for Luau errors (unknown glyph names, bad props); fix any in the plugin, rerun Task 3's tests, and commit (`jj commit -m "noctalia-plugin: fixes from the live Noctalia" noctalia-plugin`).
|
Expected: the panel appears near the bar with the four modes, the timer and three toggles, in Noctalia's style. Ask the user to confirm what they see, then close it (`noctalia msg panel-close`). With the user's go-ahead, also take two real shots from the panel: one with the timer at 5 s (the countdown runs, the shot lands, no error notification) and one Region shot held open for more than 6 s before selecting (it must not be killed). Check that nothing is clipped at 360×220 (the "Play sound" row, the four mode buttons in Turkish); adjust `width`/`height` in `plugin.toml` if needed. Check `grep -i valw ~/.cache/noctalia/noctalia.log | tail -20` for Luau errors (unknown glyph names, bad props); fix any in the plugin, rerun Task 3's tests, and commit (`jj commit -m "noctalia-plugin: fixes from the live Noctalia" noctalia-plugin`).
|
||||||
|
|
||||||
- [ ] **Step 4: Undo the dev install**
|
- [ ] **Step 4: Undo the dev install**
|
||||||
|
|
||||||
|
|||||||
@@ -89,18 +89,23 @@ about 360 × 220.
|
|||||||
- On open (`onOpen`): `valw toolbar --state` → `noctalia.json.decode` →
|
- On open (`onOpen`): `valw toolbar --state` → `noctalia.json.decode` →
|
||||||
render. If valw is missing (`noctalia.commandExists`) or the call fails:
|
render. If valw is missing (`noctalia.commandExists`) or the call fails:
|
||||||
a single label "valw is not available" and `noctalia.notifyError` with
|
a single label "valw is not available" and `noctalia.notifyError` with
|
||||||
the stderr's last line.
|
valw's error message (its `error:` line).
|
||||||
- A row of four buttons with glyphs and labels: Screen (`device-desktop`),
|
- A row of four buttons with glyphs and labels: Screen (`device-desktop`),
|
||||||
Region (`crop`), Window (`app-window`), Zoom (`zoom-in`). The
|
Region (`crop`), Window (`app-window`), Zoom (`zoom-in`). The
|
||||||
remembered mode is `variant = "primary"`, the others `"outline"`.
|
remembered mode is `variant = "primary"`, the others `"outline"`.
|
||||||
- A click on a mode: `panel.close()`, then
|
- A click on a mode: `panel.close()`, then
|
||||||
`sleep 0.3 && valw toolbar --run <mode>` (the wait lets the panel
|
`sleep 0.3 && valw toolbar --run <mode>` (the wait lets the panel
|
||||||
disappear before the screen is frozen or captured). A non-zero exit
|
disappear before the screen is frozen or captured). Captures run with
|
||||||
other than 3 → `noctalia.notifyError("valw", <stderr's last line>)`.
|
`noctalia.runStream` (stderr folded in, the exit status echoed last):
|
||||||
|
`runAsync` with a callback kills its command after 5 s, and a region,
|
||||||
|
a zoom or the timer can take longer. A non-zero exit other than 3 →
|
||||||
|
`noctalia.notifyError("valw", <valw's error: line>)`.
|
||||||
- Below: "Timer" `ui.select` with `Off`, `5 s`, `10 s` (from `timers`),
|
- Below: "Timer" `ui.select` with `Off`, `5 s`, `10 s` (from `timers`),
|
||||||
and three `ui.toggle`s: "Show cursor", "Show preview", "Play sound".
|
and three `ui.toggle`s: "Show cursor", "Show preview", "Play sound".
|
||||||
A change re-renders at once and runs `valw toolbar --set <key>=<value>`;
|
A change re-renders at once and runs `valw toolbar --set` with every
|
||||||
a failure → `notifyError`.
|
choice (so two quick changes can't undo each other); a failure →
|
||||||
|
`notifyError`. `--state` and `--set` start no run log (they would push
|
||||||
|
the capture logs out).
|
||||||
- Colours only by Noctalia role names (`primary`, `on_surface`,
|
- Colours only by Noctalia role names (`primary`, `on_surface`,
|
||||||
`outline`, …); no hex colours anywhere in the plugin.
|
`outline`, …); no hex colours anywhere in the plugin.
|
||||||
|
|
||||||
|
|||||||
+25
-10
@@ -5,18 +5,33 @@
|
|||||||
|
|
||||||
local PANEL = "elars/valw:toolbar"
|
local PANEL = "elars/valw:toolbar"
|
||||||
|
|
||||||
local function lastLine(text)
|
-- valw's stderr on an error starts with "error: <message>", then cause,
|
||||||
local last = ""
|
-- hint and log lines.
|
||||||
for line in string.gmatch(text or "", "[^\n]+") do
|
local function errorMessage(lines)
|
||||||
last = line
|
for _, line in ipairs(lines) do
|
||||||
|
local message = string.match(line, "^error: (.+)$")
|
||||||
|
if message then
|
||||||
|
return message
|
||||||
end
|
end
|
||||||
return last
|
end
|
||||||
|
return noctalia.tr("error.failed")
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Exit 3 is a cancel (Esc): not an error.
|
-- A capture lasts as long as the user takes (a region, a zoom, the timer),
|
||||||
local function report(result)
|
-- so it runs as a stream: runAsync with a callback kills its command after
|
||||||
if result.exitCode ~= 0 and result.exitCode ~= 3 then
|
-- 5 s. stderr joins the stream and the exit status comes last.
|
||||||
noctalia.notifyError("valw", lastLine(result.stderr))
|
local function capture(command)
|
||||||
|
local lines = {}
|
||||||
|
local started = noctalia.runStream("(" .. command .. ") 2>&1; echo valw-exit:$?", function(line)
|
||||||
|
local code = string.match(line, "^valw%-exit:(%d+)$")
|
||||||
|
if code == nil then
|
||||||
|
table.insert(lines, line)
|
||||||
|
elseif code ~= "0" and code ~= "3" then
|
||||||
|
noctalia.notifyError("valw", errorMessage(lines))
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
if not started then
|
||||||
|
noctalia.notifyError("valw", noctalia.tr("error.failed"))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -32,5 +47,5 @@ function onClick()
|
|||||||
end
|
end
|
||||||
|
|
||||||
function onRightClick()
|
function onRightClick()
|
||||||
noctalia.runAsync("valw region", report)
|
capture("valw region")
|
||||||
end
|
end
|
||||||
+49
-15
@@ -11,38 +11,71 @@ local MODES = {
|
|||||||
{ id = "zoom", glyph = "zoom-in" },
|
{ id = "zoom", glyph = "zoom-in" },
|
||||||
}
|
}
|
||||||
local OPTIONS = { "cursor", "preview", "sound" }
|
local OPTIONS = { "cursor", "preview", "sound" }
|
||||||
|
local KEYS = { "mode", "timer", "cursor", "preview", "sound" }
|
||||||
|
|
||||||
-- The decoded `--state` JSON; nil until it arrives.
|
-- The decoded `--state` JSON; nil until it arrives.
|
||||||
local state = nil
|
local state = nil
|
||||||
local unavailable = false
|
local unavailable = false
|
||||||
|
|
||||||
local function lastLine(text)
|
-- valw's stderr on an error starts with "error: <message>", then cause,
|
||||||
local last = ""
|
-- hint and log lines.
|
||||||
for line in string.gmatch(text or "", "[^\n]+") do
|
local function errorMessage(lines)
|
||||||
last = line
|
for _, line in ipairs(lines) do
|
||||||
|
local message = string.match(line, "^error: (.+)$")
|
||||||
|
if message then
|
||||||
|
return message
|
||||||
end
|
end
|
||||||
return last
|
end
|
||||||
|
return noctalia.tr("error.failed")
|
||||||
|
end
|
||||||
|
|
||||||
|
local function linesOf(text)
|
||||||
|
local lines = {}
|
||||||
|
for line in string.gmatch(text or "", "[^\n]+") do
|
||||||
|
table.insert(lines, line)
|
||||||
|
end
|
||||||
|
return lines
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Exit 3 is a cancel (Esc): not an error.
|
-- Exit 3 is a cancel (Esc): not an error.
|
||||||
local function report(result)
|
local function report(result)
|
||||||
if result.exitCode ~= 0 and result.exitCode ~= 3 then
|
if result.exitCode ~= 0 and result.exitCode ~= 3 then
|
||||||
noctalia.notifyError("valw", lastLine(result.stderr))
|
noctalia.notifyError("valw", errorMessage(linesOf(result.stderr)))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- A capture lasts as long as the user takes (a region, a zoom, the timer),
|
||||||
|
-- so it runs as a stream: runAsync with a callback kills its command after
|
||||||
|
-- 5 s. stderr joins the stream and the exit status comes last.
|
||||||
|
local function capture(command)
|
||||||
|
local lines = {}
|
||||||
|
local started = noctalia.runStream("(" .. command .. ") 2>&1; echo valw-exit:$?", function(line)
|
||||||
|
local code = string.match(line, "^valw%-exit:(%d+)$")
|
||||||
|
if code == nil then
|
||||||
|
table.insert(lines, line)
|
||||||
|
elseif code ~= "0" and code ~= "3" then
|
||||||
|
noctalia.notifyError("valw", errorMessage(lines))
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
if not started then
|
||||||
|
noctalia.notifyError("valw", noctalia.tr("error.failed"))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local render
|
local render
|
||||||
|
|
||||||
local function capture(mode)
|
-- Every choice goes with every --set, not just the changed one: each
|
||||||
panel.close()
|
-- --set loads, changes and saves the file, so two quick changes sending
|
||||||
noctalia.runAsync("sleep 0.3 && valw toolbar --run " .. mode, report)
|
-- one key each could undo each other. The values are valw's own tokens
|
||||||
end
|
-- (from --state or our lists), never user text.
|
||||||
|
|
||||||
-- `key` and `value` are always one of our own tokens, never user text.
|
|
||||||
local function remember(key, value)
|
local function remember(key, value)
|
||||||
state[key] = value
|
state[key] = value
|
||||||
render()
|
render()
|
||||||
noctalia.runAsync("valw toolbar --set " .. key .. "=" .. tostring(value), report)
|
local sets = {}
|
||||||
|
for _, k in ipairs(KEYS) do
|
||||||
|
table.insert(sets, "--set " .. k .. "=" .. tostring(state[k]))
|
||||||
|
end
|
||||||
|
noctalia.runAsync("valw toolbar " .. table.concat(sets, " "), report)
|
||||||
end
|
end
|
||||||
|
|
||||||
local function modeButton(m)
|
local function modeButton(m)
|
||||||
@@ -51,7 +84,8 @@ local function modeButton(m)
|
|||||||
glyph = m.glyph,
|
glyph = m.glyph,
|
||||||
variant = if state.mode == m.id then "primary" else "outline",
|
variant = if state.mode == m.id then "primary" else "outline",
|
||||||
onClick = function()
|
onClick = function()
|
||||||
capture(m.id)
|
panel.close()
|
||||||
|
capture("sleep 0.3 && valw toolbar --run " .. m.id)
|
||||||
end,
|
end,
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
@@ -123,7 +157,7 @@ function onOpen(_context)
|
|||||||
end
|
end
|
||||||
noctalia.runAsync("valw toolbar --state", function(result)
|
noctalia.runAsync("valw toolbar --state", function(result)
|
||||||
if result.exitCode ~= 0 then
|
if result.exitCode ~= 0 then
|
||||||
fail(lastLine(result.stderr))
|
fail(errorMessage(linesOf(result.stderr)))
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
local decoded = noctalia.json.decode(result.stdout)
|
local decoded = noctalia.json.decode(result.stdout)
|
||||||
|
|||||||
@@ -4,18 +4,33 @@
|
|||||||
|
|
||||||
local PANEL = "elars/valw:toolbar"
|
local PANEL = "elars/valw:toolbar"
|
||||||
|
|
||||||
local function lastLine(text)
|
-- valw's stderr on an error starts with "error: <message>", then cause,
|
||||||
local last = ""
|
-- hint and log lines.
|
||||||
for line in string.gmatch(text or "", "[^\n]+") do
|
local function errorMessage(lines)
|
||||||
last = line
|
for _, line in ipairs(lines) do
|
||||||
|
local message = string.match(line, "^error: (.+)$")
|
||||||
|
if message then
|
||||||
|
return message
|
||||||
end
|
end
|
||||||
return last
|
end
|
||||||
|
return noctalia.tr("error.failed")
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Exit 3 is a cancel (Esc): not an error.
|
-- A capture lasts as long as the user takes (a region, a zoom, the timer),
|
||||||
local function report(result)
|
-- so it runs as a stream: runAsync with a callback kills its command after
|
||||||
if result.exitCode ~= 0 and result.exitCode ~= 3 then
|
-- 5 s. stderr joins the stream and the exit status comes last.
|
||||||
noctalia.notifyError("valw", lastLine(result.stderr))
|
local function capture(command)
|
||||||
|
local lines = {}
|
||||||
|
local started = noctalia.runStream("(" .. command .. ") 2>&1; echo valw-exit:$?", function(line)
|
||||||
|
local code = string.match(line, "^valw%-exit:(%d+)$")
|
||||||
|
if code == nil then
|
||||||
|
table.insert(lines, line)
|
||||||
|
elseif code ~= "0" and code ~= "3" then
|
||||||
|
noctalia.notifyError("valw", errorMessage(lines))
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
if not started then
|
||||||
|
noctalia.notifyError("valw", noctalia.tr("error.failed"))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -23,7 +38,7 @@ shortcut.setLabel(noctalia.tr("tile.label"))
|
|||||||
shortcut.setIcon("camera", "camera")
|
shortcut.setIcon("camera", "camera")
|
||||||
|
|
||||||
function onClick()
|
function onClick()
|
||||||
noctalia.runAsync("noctalia msg panel-close; sleep 0.3 && valw region", report)
|
capture("noctalia msg panel-close >/dev/null 2>&1; sleep 0.3 && valw region")
|
||||||
end
|
end
|
||||||
|
|
||||||
function onRightClick()
|
function onRightClick()
|
||||||
|
|||||||
@@ -7,18 +7,19 @@ ok("renders")
|
|||||||
|
|
||||||
onClick()
|
onClick()
|
||||||
assert(calls.toggled[1] == "elars/valw:toolbar", "left click toggles the panel")
|
assert(calls.toggled[1] == "elars/valw:toolbar", "left click toggles the panel")
|
||||||
assert(#calls.run == 0)
|
assert(#calls.run == 0 and #calls.stream == 0)
|
||||||
ok("left click")
|
ok("left click")
|
||||||
|
|
||||||
reset()
|
reset()
|
||||||
onRightClick()
|
onRightClick()
|
||||||
assert(calls.run[1] == "valw region", calls.run[1])
|
assert(calls.stream[1] == "(valw region) 2>&1; echo valw-exit:$?", calls.stream[1])
|
||||||
|
assert(#calls.run == 0, "never through runAsync's timeout")
|
||||||
reset()
|
reset()
|
||||||
fake.answers["valw region"] = { exitCode = 1, stdout = "", stderr = "error: busy\n" }
|
fake.streams["(valw region)"] = valwFailure("busy", 1)
|
||||||
onRightClick()
|
onRightClick()
|
||||||
assert(calls.notify[1][2] == "error: busy", "failure notified")
|
assert(calls.notify[1][2] == "busy", "failure notified")
|
||||||
reset()
|
reset()
|
||||||
fake.answers["valw region"] = { exitCode = 3, stdout = "", stderr = "" }
|
fake.streams["(valw region)"] = { "valw-exit:3" }
|
||||||
onRightClick()
|
onRightClick()
|
||||||
assert(#calls.notify == 0, "cancel is silent")
|
assert(#calls.notify == 0, "cancel is silent")
|
||||||
ok("right click")
|
ok("right click")
|
||||||
@@ -12,13 +12,13 @@ assert(#calls.notify == 1, "notified once")
|
|||||||
assert(#calls.run == 0, "nothing run")
|
assert(#calls.run == 0, "nothing run")
|
||||||
ok("valw missing")
|
ok("valw missing")
|
||||||
|
|
||||||
-- --state failing: the same.
|
-- --state failing: the same, with valw's error message (not its log line).
|
||||||
reset()
|
reset()
|
||||||
fake.exists = true
|
fake.exists = true
|
||||||
fake.answers["valw toolbar --state"] = { exitCode = 1, stdout = "", stderr = "warn\nerror: broken\n" }
|
fake.answers["valw toolbar --state"] = { exitCode = 1, stdout = "", stderr = valwError("broken config") }
|
||||||
onOpen(nil)
|
onOpen(nil)
|
||||||
assert(findAll(rendered, function(n) return n.kind == "label" and n.props.text == "panel.unavailable" end)[1], "label")
|
assert(findAll(rendered, function(n) return n.kind == "label" and n.props.text == "panel.unavailable" end)[1], "label")
|
||||||
assert(calls.notify[1][2] == "error: broken", "the stderr's last line: " .. tostring(calls.notify[1][2]))
|
assert(calls.notify[1][2] == "broken config", "valw's message: " .. tostring(calls.notify[1][2]))
|
||||||
ok("--state failing")
|
ok("--state failing")
|
||||||
|
|
||||||
-- A working valw: the remembered mode is primary, the timer selected.
|
-- A working valw: the remembered mode is primary, the timer selected.
|
||||||
@@ -46,42 +46,56 @@ end)
|
|||||||
assert(#hex == 0, "no hex colours")
|
assert(#hex == 0, "no hex colours")
|
||||||
ok("renders the state")
|
ok("renders the state")
|
||||||
|
|
||||||
-- A mode click closes the panel first, then captures after the wait.
|
-- A mode click closes the panel first, then captures after the wait, as a
|
||||||
|
-- stream (no timeout: a region or a timer can take longer than 5 s).
|
||||||
reset()
|
reset()
|
||||||
button("mode.region").props.onClick()
|
button("mode.region").props.onClick()
|
||||||
assert(calls.order[1] == "close" and calls.order[2] == "run", "close, then run")
|
assert(calls.order[1] == "close" and calls.order[2] == "stream", "close, then capture")
|
||||||
assert(calls.run[1] == "sleep 0.3 && valw toolbar --run region", calls.run[1])
|
assert(calls.stream[1] == "(sleep 0.3 && valw toolbar --run region) 2>&1; echo valw-exit:$?", calls.stream[1])
|
||||||
|
assert(#calls.run == 0, "never through runAsync's timeout")
|
||||||
assert(#calls.notify == 0, "success is silent")
|
assert(#calls.notify == 0, "success is silent")
|
||||||
ok("mode click")
|
ok("mode click")
|
||||||
|
|
||||||
-- A failed capture notifies; a cancelled one doesn't.
|
-- A failed capture notifies with valw's message; a cancel doesn't; a
|
||||||
|
-- failure without a message gets a generic one.
|
||||||
reset()
|
reset()
|
||||||
fake.answers["sleep 0.3 && valw toolbar --run"] = { exitCode = 1, stdout = "", stderr = "error: no outputs\n" }
|
fake.streams["(sleep 0.3 && valw toolbar --run"] = valwFailure("no outputs", 1)
|
||||||
button("mode.screen").props.onClick()
|
button("mode.screen").props.onClick()
|
||||||
assert(calls.notify[1][2] == "error: no outputs", "failure notified")
|
assert(calls.notify[1][2] == "no outputs", "failure notified: " .. tostring(calls.notify[1][2]))
|
||||||
reset()
|
reset()
|
||||||
fake.answers["sleep 0.3 && valw toolbar --run"] = { exitCode = 3, stdout = "", stderr = "" }
|
fake.streams["(sleep 0.3 && valw toolbar --run"] = { "valw-exit:3" }
|
||||||
button("mode.window").props.onClick()
|
button("mode.window").props.onClick()
|
||||||
assert(#calls.notify == 0, "cancel is silent")
|
assert(#calls.notify == 0, "cancel is silent")
|
||||||
|
reset()
|
||||||
|
fake.streams["(sleep 0.3 && valw toolbar --run"] = { "valw-exit:143" }
|
||||||
|
button("mode.window").props.onClick()
|
||||||
|
assert(calls.notify[1][2] == "error.failed", "a generic message")
|
||||||
|
fake.streams["(sleep 0.3 && valw toolbar --run"] = nil
|
||||||
ok("capture results")
|
ok("capture results")
|
||||||
|
|
||||||
-- The timer and the toggles write through --set and re-render at once.
|
-- The timer and the toggles re-render at once and send every choice, so
|
||||||
|
-- two quick changes can't undo each other.
|
||||||
reset()
|
reset()
|
||||||
fake.answers["sleep 0.3 && valw toolbar --run"] = nil
|
|
||||||
select = findAll(rendered, function(n) return n.kind == "select" end)[1]
|
select = findAll(rendered, function(n) return n.kind == "select" end)[1]
|
||||||
select.props.onChange("2")
|
select.props.onChange("2")
|
||||||
assert(calls.run[1] == "valw toolbar --set timer=10", calls.run[1])
|
assert(
|
||||||
|
calls.run[1] == "valw toolbar --set mode=zoom --set timer=10 --set cursor=false --set preview=true --set sound=true",
|
||||||
|
calls.run[1]
|
||||||
|
)
|
||||||
select = findAll(rendered, function(n) return n.kind == "select" end)[1]
|
select = findAll(rendered, function(n) return n.kind == "select" end)[1]
|
||||||
assert(select.props.selectedIndex == 2, "re-rendered with 10 s")
|
assert(select.props.selectedIndex == 2, "re-rendered with 10 s")
|
||||||
reset()
|
reset()
|
||||||
toggles = findAll(rendered, function(n) return n.kind == "toggle" end)
|
toggles = findAll(rendered, function(n) return n.kind == "toggle" end)
|
||||||
toggles[3].props.onChange("false")
|
toggles[3].props.onChange("false")
|
||||||
assert(calls.run[1] == "valw toolbar --set sound=false", calls.run[1])
|
assert(
|
||||||
|
calls.run[1] == "valw toolbar --set mode=zoom --set timer=10 --set cursor=false --set preview=true --set sound=false",
|
||||||
|
calls.run[1]
|
||||||
|
)
|
||||||
toggles = findAll(rendered, function(n) return n.kind == "toggle" end)
|
toggles = findAll(rendered, function(n) return n.kind == "toggle" end)
|
||||||
assert(toggles[3].props.checked == false, "re-rendered with sound off")
|
assert(toggles[3].props.checked == false, "re-rendered with sound off")
|
||||||
reset()
|
reset()
|
||||||
fake.answers["valw toolbar --set"] = { exitCode = 1, stdout = "", stderr = "error: read-only\n" }
|
fake.answers["valw toolbar --set"] = { exitCode = 1, stdout = "", stderr = valwError("read-only file system") }
|
||||||
toggles[1].props.onChange("true")
|
toggles[1].props.onChange("true")
|
||||||
assert(calls.run[1] == "valw toolbar --set cursor=true", calls.run[1])
|
assert(string.find(calls.run[1], "--set cursor=true", 1, true), calls.run[1])
|
||||||
assert(calls.notify[1][2] == "error: read-only", "a failed --set notifies")
|
assert(calls.notify[1][2] == "read-only file system", "a failed --set notifies")
|
||||||
ok("timer and toggles")
|
ok("timer and toggles")
|
||||||
@@ -2,13 +2,17 @@
|
|||||||
-- The control-center tile. Runs after stubs.luau and shortcut.luau.
|
-- The control-center tile. Runs after stubs.luau and shortcut.luau.
|
||||||
|
|
||||||
onClick()
|
onClick()
|
||||||
assert(calls.run[1] == "noctalia msg panel-close; sleep 0.3 && valw region", calls.run[1])
|
assert(
|
||||||
|
calls.stream[1] == "(noctalia msg panel-close >/dev/null 2>&1; sleep 0.3 && valw region) 2>&1; echo valw-exit:$?",
|
||||||
|
calls.stream[1]
|
||||||
|
)
|
||||||
|
assert(#calls.run == 0, "never through runAsync's timeout")
|
||||||
reset()
|
reset()
|
||||||
fake.answers["noctalia msg panel-close"] = { exitCode = 1, stdout = "", stderr = "error: busy\n" }
|
fake.streams["(noctalia msg panel-close"] = valwFailure("busy", 1)
|
||||||
onClick()
|
onClick()
|
||||||
assert(calls.notify[1][2] == "error: busy", "failure notified")
|
assert(calls.notify[1][2] == "busy", "failure notified")
|
||||||
reset()
|
reset()
|
||||||
fake.answers["noctalia msg panel-close"] = { exitCode = 3, stdout = "", stderr = "" }
|
fake.streams["(noctalia msg panel-close"] = { "valw-exit:3" }
|
||||||
onClick()
|
onClick()
|
||||||
assert(#calls.notify == 0, "cancel is silent")
|
assert(#calls.notify == 0, "cancel is silent")
|
||||||
ok("click")
|
ok("click")
|
||||||
|
|||||||
@@ -2,11 +2,13 @@
|
|||||||
-- Stand-ins for Noctalia's globals. Each test is run as one chunk:
|
-- Stand-ins for Noctalia's globals. Each test is run as one chunk:
|
||||||
-- these stubs, then the entry script, then the test (see run.sh).
|
-- these stubs, then the entry script, then the test (see run.sh).
|
||||||
|
|
||||||
calls = { run = {}, notify = {}, toggled = {}, closed = 0, order = {} }
|
calls = { run = {}, stream = {}, notify = {}, toggled = {}, closed = 0, order = {} }
|
||||||
fake = {
|
fake = {
|
||||||
exists = true,
|
exists = true,
|
||||||
-- runAsync answers by command prefix: { exitCode, stdout, stderr }
|
-- runAsync answers by command prefix: { exitCode, stdout, stderr }
|
||||||
answers = {},
|
answers = {},
|
||||||
|
-- runStream lines by command prefix; default: a clean exit
|
||||||
|
streams = {},
|
||||||
decoded = nil,
|
decoded = nil,
|
||||||
}
|
}
|
||||||
rendered = nil
|
rendered = nil
|
||||||
@@ -38,6 +40,17 @@ noctalia = {
|
|||||||
end
|
end
|
||||||
return true
|
return true
|
||||||
end,
|
end,
|
||||||
|
-- No timeout here (runAsync with a callback kills after 5 s): captures.
|
||||||
|
runStream = function(command, onLine)
|
||||||
|
table.insert(calls.stream, command)
|
||||||
|
table.insert(calls.order, "stream")
|
||||||
|
local lines = { "valw-exit:0" }
|
||||||
|
for prefix, l in pairs(fake.streams) do
|
||||||
|
if string.sub(command, 1, #prefix) == prefix then lines = l end
|
||||||
|
end
|
||||||
|
for _, line in ipairs(lines) do onLine(line) end
|
||||||
|
return true
|
||||||
|
end,
|
||||||
notifyError = function(title, body) table.insert(calls.notify, { title, body }) end,
|
notifyError = function(title, body) table.insert(calls.notify, { title, body }) end,
|
||||||
togglePanel = function(id) table.insert(calls.toggled, id) end,
|
togglePanel = function(id) table.insert(calls.toggled, id) end,
|
||||||
json = { decode = function(_text) return fake.decoded end },
|
json = { decode = function(_text) return fake.decoded end },
|
||||||
@@ -77,7 +90,17 @@ function button(text)
|
|||||||
end
|
end
|
||||||
|
|
||||||
function reset()
|
function reset()
|
||||||
calls = { run = {}, notify = {}, toggled = {}, closed = 0, order = {} }
|
calls = { run = {}, stream = {}, notify = {}, toggled = {}, closed = 0, order = {} }
|
||||||
|
end
|
||||||
|
|
||||||
|
-- valw's stderr on an error: the message first, then cause, hint and log.
|
||||||
|
function valwError(message)
|
||||||
|
return "error: " .. message .. "\n cause: something\n log: /home/u/.local/state/valw/logs/x.log\n"
|
||||||
|
end
|
||||||
|
|
||||||
|
-- The same as stream lines, ending with the exit status.
|
||||||
|
function valwFailure(message, code)
|
||||||
|
return { "error: " .. message, " log: /home/u/.local/state/valw/logs/x.log", "valw-exit:" .. code }
|
||||||
end
|
end
|
||||||
|
|
||||||
function ok(name)
|
function ok(name)
|
||||||
|
|||||||
@@ -1,8 +1,29 @@
|
|||||||
{
|
{
|
||||||
"bar": { "tooltip": "Screenshot" },
|
"bar": {
|
||||||
"tile": { "label": "Screenshot" },
|
"tooltip": "Screenshot"
|
||||||
"panel": { "unavailable": "valw is not available" },
|
},
|
||||||
"mode": { "screen": "Screen", "region": "Region", "window": "Window", "zoom": "Zoom" },
|
"tile": {
|
||||||
"timer": { "label": "Timer", "off": "Off" },
|
"label": "Screenshot"
|
||||||
"option": { "cursor": "Show cursor", "preview": "Show preview", "sound": "Play sound" }
|
},
|
||||||
|
"panel": {
|
||||||
|
"unavailable": "valw is not available"
|
||||||
|
},
|
||||||
|
"mode": {
|
||||||
|
"screen": "Screen",
|
||||||
|
"region": "Region",
|
||||||
|
"window": "Window",
|
||||||
|
"zoom": "Zoom"
|
||||||
|
},
|
||||||
|
"timer": {
|
||||||
|
"label": "Timer",
|
||||||
|
"off": "Off"
|
||||||
|
},
|
||||||
|
"option": {
|
||||||
|
"cursor": "Show cursor",
|
||||||
|
"preview": "Show preview",
|
||||||
|
"sound": "Play sound"
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"failed": "valw failed"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,8 +1,29 @@
|
|||||||
{
|
{
|
||||||
"bar": { "tooltip": "Ekran görüntüsü" },
|
"bar": {
|
||||||
"tile": { "label": "Ekran görüntüsü" },
|
"tooltip": "Ekran görüntüsü"
|
||||||
"panel": { "unavailable": "valw bulunamadı" },
|
},
|
||||||
"mode": { "screen": "Ekran", "region": "Bölge", "window": "Pencere", "zoom": "Yakınlaştır" },
|
"tile": {
|
||||||
"timer": { "label": "Zamanlayıcı", "off": "Kapalı" },
|
"label": "Ekran görüntüsü"
|
||||||
"option": { "cursor": "İmleci göster", "preview": "Önizlemeyi göster", "sound": "Ses çal" }
|
},
|
||||||
|
"panel": {
|
||||||
|
"unavailable": "valw bulunamadı"
|
||||||
|
},
|
||||||
|
"mode": {
|
||||||
|
"screen": "Ekran",
|
||||||
|
"region": "Bölge",
|
||||||
|
"window": "Pencere",
|
||||||
|
"zoom": "Yakınlaştır"
|
||||||
|
},
|
||||||
|
"timer": {
|
||||||
|
"label": "Zamanlayıcı",
|
||||||
|
"off": "Kapalı"
|
||||||
|
},
|
||||||
|
"option": {
|
||||||
|
"cursor": "İmleci göster",
|
||||||
|
"preview": "Önizlemeyi göster",
|
||||||
|
"sound": "Ses çal"
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"failed": "valw başarısız oldu"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user