Rewrite for Syndicate-nim changes

This commit is contained in:
Emery Hemingway 2023-05-18 10:46:17 +01:00
parent 3caf7d05fb
commit 73e63bdc77
8 changed files with 120 additions and 116 deletions

View File

@ -8,7 +8,8 @@ Matching patterns to actions is done with `action-handler` records:
```preserves
<action-handler "foo://(.*):(.*)" $entity <krempel ["--host=$1" "--port=$2"]> >
```
In the preceding example the URI `foo://bar:42` would cause the message `<krempel ["--host=bar" "--port=42"]>` to be sent to `$entity`.
See [handlers-example.pr](./handlers-example.pr) for more information.
The [protocol.nim](./src/protocol.nim) file is generated from the [protocol.prs](./protocol.prs) schema, a [Tupfile](https://gittup.org/tup/) file is provided to do this.

27
handlers-example.pr Normal file
View File

@ -0,0 +1,27 @@
; the <exec-space #!…> dataspace starts programs when it receives exec messages
? <exec-space ?execspace> [
<action-handler "file://(.*.pdf)" $execspace
<exec ["/run/current-system/sw/bin/mupdf" "$1"]>>
<action-handler "file://(.*.png)" $execspace
<exec ["/run/current-system/sw/bin/imv" "$1"]>>
<action-handler "(magnet:?.*xt=urn:btih.*)" $execspace
<exec ["/run/current-system/sw/bin/transmission-gtk" "$1"]>>
<action-handler "(tg://.*)" $execspace
<exec ["/run/current-system/sw/bin/telegram-desktop" "$1"]>>
<action-handler "https://twitter.com/(.*)" $execspace
<exec ["/run/current-system/sw/bin/firefox" "--new-tab" "https://nitter.net/$1"]>>
<action-handler "(http://.*)|(https://.*)" $execspace
<exec ["/run/current-system/sw/bin/firefox" "$1"]>>
]
; when mpv is available send it commands directly
? <mpv ?mpv> [
<action-handler "(.*mp4)|(.*mp3)" $mpv <play-file "$1">>
]

View File

@ -1,7 +1,10 @@
version 1 .
XdgOpen = <xdg-open @uris [string ...]> .
XdgOpen = <xdg-open @uri string> .
UriRunnerConfig = {
handlerspace: #!any
urispace: #!any
} .
UriRunnerConfig = ListenOn / ActionHandler .
ListenOn = <listen-on @dataspace #!any> .
ActionHandler = <action-handler @pat string @entity #!any @action any> .

View File

@ -1,32 +1,22 @@
import
std/typetraits, preserves
preserves
type
XdgOpen* {.preservesRecord: "xdg-open".} = object
`uris`*: seq[string]
`uri`*: string
UriRunnerConfigKind* {.pure.} = enum
`ListenOn`, `ActionHandler`
`UriRunnerConfig`* {.preservesOr.} = object
case orKind*: UriRunnerConfigKind
of UriRunnerConfigKind.`ListenOn`:
`listenon`*: ListenOn
of UriRunnerConfigKind.`ActionHandler`:
`actionhandler`*: ActionHandler
ListenOn* {.preservesRecord: "listen-on".} = object
`dataspace`* {.preservesEmbedded.}: Preserve[void]
UriRunnerConfig* {.preservesDictionary.} = object
`handlerspace`* {.preservesEmbedded.}: Preserve[void]
`urispace`* {.preservesEmbedded.}: Preserve[void]
ActionHandler* {.preservesRecord: "action-handler".} = object
`pat`*: string
`entity`* {.preservesEmbedded.}: Preserve[void]
`action`*: Preserve[void]
proc `$`*(x: XdgOpen | UriRunnerConfig | ListenOn | ActionHandler): string =
proc `$`*(x: XdgOpen | UriRunnerConfig | ActionHandler): string =
`$`(toPreserve(x))
proc encode*(x: XdgOpen | UriRunnerConfig | ListenOn | ActionHandler): seq[byte] =
proc encode*(x: XdgOpen | UriRunnerConfig | ActionHandler): seq[byte] =
encode(toPreserve(x))

View File

@ -1,11 +1,15 @@
# SPDX-FileCopyrightText: ☭ Emery Hemingway
# SPDX-License-Identifier: Unlicense
import std/[asyncdispatch, re]
import preserves, syndicate
import std/[re, tables]
import preserves, syndicate, syndicate/patterns
import ./protocol
type RegexAction = tuple[regex: Regex; entity: Ref; action: Assertion]
# importing std/re doesn't actually link against PCRE
{.passC: staticExec("pkg-config --cflags libpcre").}
{.passL: staticExec("pkg-config --libs libpcre").}
type RegexAction = tuple[regex: Regex, entity: Ref, action: Assertion]
proc rewrite(result: var Assertion; uri: string; regex: Regex) =
proc op(pr: var Assertion) =
@ -15,24 +19,36 @@ proc rewrite(result: var Assertion; uri: string; regex: Regex) =
runActor("main") do (root: Ref; turn: var Turn):
connectStdio(root, turn)
var handlers: seq[RegexAction]
during(turn, root, ?UriRunnerConfig) do (handlerspace: Ref, urispace: Ref):
onPublish(turn, root, ?ActionHandler) do (pat: string; entity: Ref; response: Assertion):
handlers.add (re(pat, {reIgnoreCase, reStudy}), entity, response,)
# sanity chek
onMessage(turn, handlerspace, ?XdgOpen) do (uri: string):
raiseAssert "got a message in the wrong dataspace!"
onPublish(turn, urispace, ?ActionHandler) do (pat: string; entity: Ref; act: Assertion):
raiseAssert "got an assertion in the wrong dataspace!"
during(turn, root, ?ListenOn) do (ds: Ref):
onMessage(turn, ds, ?XdgOpen) do (uris: seq[string]):
for uri in uris:
var matched: bool
for handler in handlers:
during(turn, handlerspace, dropType(ActionHandler)) do:
var handlers: Table[Handle, RegexAction]
during(turn, handlerspace, ?ActionHandler) do (pat: string; entity: Ref; act: Assertion):
# `duringHandle` is a symbol exposed by the `during` macro
handlers[duringHandle] = (re(pat, {reIgnoreCase, reStudy}), entity, act,)
do:
del(handlers, duringHandle)
onMessage(turn, urispace, ?XdgOpen) do (uri: string):
assert len(handlers) > 0
var matched = false
for handler in handlers.values:
if match(uri, handler.regex):
matched = true
var response = handler.action
rewrite(response, uri, handler.regex)
message(turn, handler.entity, response)
var action = handler.action
try:
rewrite(action, uri, handler.regex)
message(turn, handler.entity, action)
except CatchableError:
stderr.writeLine "rewrite failed on ", action
if not matched:
stderr.writeLine "no actions matched for ", uri
do:
# The Syndicate server retracts all assertions when
# the config is rewritten.
handlers.setLen 0

View File

@ -1,24 +1,29 @@
# SPDX-FileCopyrightText: ☭ 2022 Emery Hemingway
# SPDX-FileCopyrightText: ☭ Emery Hemingway
# SPDX-License-Identifier: Unlicense
## Facsimile of xdg-open from xdg-utils.
## https://portland.freedesktop.org/doc/xdg-open.html
import std/[asyncdispatch, os]
from std/sequtils import map
import syndicate, syndicate/capabilities
import preserves, syndicate, syndicate/capabilities
import ./protocol
proc unixSocketPath: string =
result = getEnv("SYNDICATE_SOCK")
if result == "":
result = getEnv("XDG_RUNTIME_DIR", "/run/user/1000") / "dataspace"
proc publishUri(turn: var Turn; ds: Ref) =
for arg in commandLineParams():
var xo = XdgOpen(uri: arg)
if fileExists(xo.uri):
xo.uri = "file://" & absolutePath(arg)
message(turn, ds, xo)
stop(turn)
bootDataspace("main") do (root: Ref; turn: var Turn):
connectUnix(turn, unixSocketPath(), mint()) do (turn: var Turn; ds: Ref):
var uris = commandLineParams().map do (param: string) -> string:
if fileExists param:
"file://" & absolutePath(param)
else:
param
message(turn, ds, XdgOpen(uris: uris))
proc unixSocketPath: Unix =
result.path = getEnv("SYNDICATE_SOCK")
if result.path == "":
result.path = getEnv("XDG_RUNTIME_DIR", "/run/user/1000") / "dataspace"
for i in 0..7: poll(20)
bootDataspace("xdg-open") do (root: Ref; turn: var Turn):
let cap = mint().toPreserve(Ref)
connect(turn, unixSocketPath(), cap, publishUri)
for i in 0..4: poll(20)
# A hack to exit

View File

@ -1,68 +1,30 @@
; Expose a dataspace over a unix socket
let ?socketspace = dataspace
<require-service <relay-listener <unix "/run/user/1000/dataspace"> $gatekeeper>>
<bind "syndicate" #x"" $socketspace>
<require-service <daemon uri_runner>>
; Create a new dataspace for receiving "exec" messages
; from the uri_runner
let ?execspace = dataspace
$execspace ?? <exec ?argv> $config [
let ?id = timestamp
let ?facet = facet
let ?d = <uri_runner-exec $id $argv>
<run-service <daemon $d>>
<daemon $d {
argv: $argv,
env: { WAYLAND_DISPLAY: "wayland-1" DISPLAY: ":0" XDG_CACHE_HOME: "/home/cache" }
readyOnStart: #f,
restart: =never,
; configure the daemon when it is built
; (this is an artifact of the author's build system)
? <built uri_runner ?path ?hash> [
<daemon uri_runner {
argv: [$path]
protocol: application/syndicate
env: { BUILD_SUM: $hash }
}>
? <service-state <daemon $d> complete> [$facet ! stop]
? <service-state <daemon $d> failed> [$facet ! stop]
]
; Start the uri_runner
<require-service <daemon uri_runner>>
<daemon uri_runner {
argv: "uri_runner"
protocol: text/syndicate
}>
; grab a dataspace for observing <xdg-open …> messages
? <socketspace ?socketspace> [
? <service-object <daemon uri_runner> ?cap> $cap [
; log xdg-open messages
$socketspace ?? <xdg-open ?stuff> [
$log ! <log "-" { line: <xdg-open $stuff> }>
]
; send configuration to uri_runner
<listen-on $socketspace>
; When http* is matched send a message to $execspace
; that indicates the Syndicate server should start Firefox.
<action-handler "(http://.*|https://.*|.*html)" $execspace
<exec ["/bin/firefox" "--new-tab" "$1"]>>
; Capture a pattern within a pattern
<action-handler "https://twitter.com/(.*)" $execspace
<exec ["/bin/firefox" "--new-tab" "https://nitter.net/$1"]>>
; Local file-system paths should always be prefixed
; by file:// but that can be removed after matching
<action-handler "file://(.*.pdf)" $execspace
<exec ["/bin/mupdf" "$1"]>>
<action-handler "(magnet:?.*xt=urn:btih.*)" $execspace
<exec ["/bin/transmission-remote-gtk" "$1"]>>
<action-handler "(tox:.*)|uri:(tox:.*)" $execspace
<exec ["/bin/qtox" "$1"]>>
<action-handler "(gemini://.*|file:///.*.gmi)" $execspace
<exec ["/bin/lagrange" "$1"]>>
<action-handler "(https://.*\\.twitch.tv/.*)" $execspace
<exec ["/bin/streamlink" "-p" "mpv" "$1"]>>
; when the mpv-transator is available send it commands directly
$config ? <service-object <daemon mpv-translator> ?mpv> $mpv [
$cap <action-handler "(.*\\.avi|.*\\.mkv|.*mp4|.*ogg|.*youtu.*|.*\\.m3u8|.*webm)" $mpv
<mpv 1 { "command": ["loadfile" "$1" "append-play"] }>>
; configure the uri_runner
? <service-object <daemon uri_runner> ?cap> [
$cap {
; watch the config dataspace for handler configuration
handlerspace: $config
urispace: $socketspace
}
]
]

View File

@ -1,6 +1,6 @@
# Package
version = "20230506"
version = "20230518"
author = "Emery"
description = "A better xdg-open"
license = "Unlicense"
@ -10,4 +10,4 @@ bin = @[ "uri_runner", "xdg_open"]
# Dependencies
requires "nim >= 1.6.4", "syndicate >= 1.3.0"
requires "nim >= 1.6.4", "syndicate >= 20230518"