Compare commits
3
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9823d77b55 | ||
|
|
48f7d5262c | ||
|
|
e56771aa22 |
No files matched your search
@@ -10,9 +10,9 @@ Repository material and both Tentacles are licensed under GPL-3.0-or-later; see
|
|||||||
Tentacles, each in a direct top-level folder with its own `tentacle.toml`.
|
Tentacles, each in a direct top-level folder with its own `tentacle.toml`.
|
||||||
The catalog lists native packages for Linux amd64 and arm64. Hello World 0.1.0 is
|
The catalog lists native packages for Linux amd64 and arm64. Hello World 0.1.0 is
|
||||||
available from the [release page](https://git.stormux.org/storm/cthulhu-tentacles/releases/tag/hello-world-v0.1.0).
|
available from the [release page](https://git.stormux.org/storm/cthulhu-tentacles/releases/tag/hello-world-v0.1.0).
|
||||||
Wormhole 0.1.1 requires the matching reader with interactive Tentacle support.
|
Wormhole 0.1.2 requires the matching reader with interactive Tentacle settings support.
|
||||||
Its release archives are prepared for upload to the Wormhole release page;
|
Native packages are available from the
|
||||||
installation becomes available once both archives have been uploaded.
|
[Wormhole release page](https://git.stormux.org/storm/cthulhu-tentacles/releases/tag/wormhole-v0.1.2).
|
||||||
Local build artifacts live in the ignored `dist/` directory.
|
Local build artifacts live in the ignored `dist/` directory.
|
||||||
The discovery source URL is
|
The discovery source URL is
|
||||||
`https://git.stormux.org/storm/cthulhu-tentacles`.
|
`https://git.stormux.org/storm/cthulhu-tentacles`.
|
||||||
|
|||||||
+2
-2
@@ -24,10 +24,10 @@ manifest = "wormhole/tentacle.toml"
|
|||||||
platform = "linux"
|
platform = "linux"
|
||||||
architecture = "amd64"
|
architecture = "amd64"
|
||||||
url = "https://git.stormux.org/storm/cthulhu-tentacles/releases/download/wormhole-v0.1.1/wormhole-0.1.1-linux-amd64.tar.gz"
|
url = "https://git.stormux.org/storm/cthulhu-tentacles/releases/download/wormhole-v0.1.1/wormhole-0.1.1-linux-amd64.tar.gz"
|
||||||
sha256 = "fbad678824f7ea003231197e6d131ab9588d9b896297d4da152b191efe2f77f3"
|
sha256 = "4e1e58885c20bfcb6e274db392e6a765476bce7bde08494f71e38065359d2968"
|
||||||
|
|
||||||
[[tentacles.releases]]
|
[[tentacles.releases]]
|
||||||
platform = "linux"
|
platform = "linux"
|
||||||
architecture = "arm64"
|
architecture = "arm64"
|
||||||
url = "https://git.stormux.org/storm/cthulhu-tentacles/releases/download/wormhole-v0.1.1/wormhole-0.1.1-linux-arm64.tar.gz"
|
url = "https://git.stormux.org/storm/cthulhu-tentacles/releases/download/wormhole-v0.1.1/wormhole-0.1.1-linux-arm64.tar.gz"
|
||||||
sha256 = "d6783f8f003b59ea7d81f5a9ff741f53155c4eaea7e97f8b56c86529cde5d8bd"
|
sha256 = "035d18a049c93b0fabeca8424d12b3f788e15f3fd0cd119766fc598900798326"
|
||||||
+16
-4
@@ -36,7 +36,7 @@ def check_metadata(repoRoot):
|
|||||||
require(item["manifest"] == f"{name}/tentacle.toml", "Manifest path must match ID")
|
require(item["manifest"] == f"{name}/tentacle.toml", "Manifest path must match ID")
|
||||||
manifest = read_toml(repoRoot / item["manifest"])
|
manifest = read_toml(repoRoot / item["manifest"])
|
||||||
require(manifest["format-version"] == 1 and manifest["id"] == name
|
require(manifest["format-version"] == 1 and manifest["id"] == name
|
||||||
and manifest["name"] and manifest["version"] == ("0.1.1" if name == "wormhole" else "0.1.0"), "Invalid Tentacle metadata")
|
and manifest["name"] and manifest["version"] == ("0.1.2" if name == "wormhole" else "0.1.0"), "Invalid Tentacle metadata")
|
||||||
expectedGrants = ["presentation"] + (["interaction"] if name == "wormhole" else [])
|
expectedGrants = ["presentation"] + (["interaction"] if name == "wormhole" else [])
|
||||||
expectedProgram = "wormhole-tentacle" if name == "wormhole" else name
|
expectedProgram = "wormhole-tentacle" if name == "wormhole" else name
|
||||||
require(manifest["entry-point"] == [f"bin/{expectedProgram}"]
|
require(manifest["entry-point"] == [f"bin/{expectedProgram}"]
|
||||||
@@ -81,11 +81,14 @@ def check_registration(programPath, name):
|
|||||||
require(registration["id"] == "1" and registration["result"]["session"] == "check",
|
require(registration["id"] == "1" and registration["result"]["session"] == "check",
|
||||||
"Invalid registration response")
|
"Invalid registration response")
|
||||||
commands = registration["result"]["commands"]
|
commands = registration["result"]["commands"]
|
||||||
require(len(commands) == (7 if name == "wormhole" else 1) and commands[0]["id"] == name
|
require(len(commands) == (9 if name == "wormhole" else 1) and commands[0]["id"] == name
|
||||||
and commands[0]["presentation"] is True, "Unexpected commands or grant")
|
and commands[0]["presentation"] is True, "Unexpected commands or grant")
|
||||||
require(commands[0]["bindings"] == [
|
require(commands[0]["bindings"] == [
|
||||||
{"layout": layout, "scope": "global", "key": ("Cthulhu+Alt+Shift+W" if name == "wormhole" else "Cthulhu+Alt+Shift+H")}
|
{"layout": layout, "scope": "global", "key": ("Cthulhu+Ctrl+Shift+W" if name == "wormhole" else "Cthulhu+Alt+Shift+H")}
|
||||||
for layout in ("desktop", "laptop")], "Wrong suggested shortcut")
|
for layout in ("desktop", "laptop")], "Wrong suggested shortcut")
|
||||||
|
if name == "wormhole":
|
||||||
|
require([command["id"] for command in commands if command.get("settings")]
|
||||||
|
== ["settings"], "Wrong registered settings entry point")
|
||||||
process.stdin.close()
|
process.stdin.close()
|
||||||
require(process.wait(timeout=5) == 0, "Tentacle did not stop cleanly on EOF")
|
require(process.wait(timeout=5) == 0, "Tentacle did not stop cleanly on EOF")
|
||||||
finally:
|
finally:
|
||||||
@@ -109,6 +112,15 @@ def check_host(repoRoot, programPath, name):
|
|||||||
or value == {"speech": "Magic wormhole is not installed",
|
or value == {"speech": "Magic wormhole is not installed",
|
||||||
"braille": "Magic wormhole is not installed", "uninterruptible": True},
|
"braille": "Magic wormhole is not installed", "uninterruptible": True},
|
||||||
"Host returned an unexpected Wormhole menu")
|
"Host returned an unexpected Wormhole menu")
|
||||||
|
result = subprocess.run([
|
||||||
|
"go", "run", "./cmd/tentacle-host", "-presentation", "-interaction",
|
||||||
|
"-command", "settings", str(programPath),
|
||||||
|
], cwd=readerRoot, env=environment, capture_output=True, text=True,
|
||||||
|
timeout=60, check=True)
|
||||||
|
value = json.loads(result.stdout)
|
||||||
|
require(value.get("form", {}).get("title") == "Wormhole preferences"
|
||||||
|
and value.get("next") == "save-settings",
|
||||||
|
"Host returned an unexpected Wormhole settings form")
|
||||||
return
|
return
|
||||||
require(json.loads(result.stdout) == {
|
require(json.loads(result.stdout) == {
|
||||||
"speech": "Hello world!", "braille": "Hello world!", "uninterruptible": True,
|
"speech": "Hello world!", "braille": "Hello world!", "uninterruptible": True,
|
||||||
@@ -123,7 +135,7 @@ def main():
|
|||||||
programPath = repoRoot / name / "bin" / binary
|
programPath = repoRoot / name / "bin" / binary
|
||||||
check_registration(programPath, name)
|
check_registration(programPath, name)
|
||||||
check_host(repoRoot, programPath, name)
|
check_host(repoRoot, programPath, name)
|
||||||
print("Repository metadata, shortcut registration and actual-host greeting/menu checks passed.")
|
print("Repository metadata, shortcut/settings registration and actual-host greeting/menu/settings checks passed.")
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
+12
-5
@@ -1,9 +1,17 @@
|
|||||||
# Wormhole
|
# Wormhole
|
||||||
|
|
||||||
An experimental Magic Wormhole Tentacle for Cthulhu-Go. Suggested shortcut:
|
An experimental Magic Wormhole Tentacle for Cthulhu-Go. Suggested shortcut:
|
||||||
**Cthulhu+Alt+Shift+W**, on desktop and laptop layouts. It opens the reader's
|
**Cthulhu+Ctrl+Shift+W**, on desktop and laptop layouts. It opens the reader's
|
||||||
internal **Wormhole menu**, preserving the underlying application's focus.
|
internal **Wormhole menu**, preserving the underlying application's focus.
|
||||||
|
|
||||||
|
With a reader supporting Tentacle settings registration, global Preferences →
|
||||||
|
Tentacles → select Wormhole → Settings opens **Wormhole preferences** directly.
|
||||||
|
Save persists the two checkboxes and returns to reader preferences; Escape
|
||||||
|
discards the form's edits. This entry point works without the Wormhole CLI.
|
||||||
|
Use Wormhole 0.1.2 and rebuild the reader to obtain settings registration and the
|
||||||
|
Ctrl+Shift shortcut; the published 0.1.1 binary uses Alt+Shift and has no
|
||||||
|
registered settings entry point.
|
||||||
|
|
||||||
Install the `magic-wormhole` CLI using your distribution's package manager.
|
Install the `magic-wormhole` CLI using your distribution's package manager.
|
||||||
Clipboard operations require `xclip` on Xorg. Missing `wormhole` does not stop
|
Clipboard operations require `xclip` on Xorg. Missing `wormhole` does not stop
|
||||||
reader startup: the shortcut speaks and brailles **Magic wormhole is not
|
reader startup: the shortcut speaks and brailles **Magic wormhole is not
|
||||||
@@ -102,9 +110,8 @@ interaction = true
|
|||||||
|
|
||||||
The normal installation path is global Preferences → Tentacles → Add repository
|
The normal installation path is global Preferences → Tentacles → Add repository
|
||||||
with `https://git.stormux.org/storm/cthulhu-tentacles`, followed by selecting
|
with `https://git.stormux.org/storm/cthulhu-tentacles`, followed by selecting
|
||||||
Wormhole and Install and enable. Version 0.1.1 downloads use the normal
|
Wormhole and Install and enable. Version 0.1.2 downloads use the normal
|
||||||
`wormhole-v0.1.1` release attachments. Installation becomes available after the
|
[`wormhole-v0.1.2` release attachments](https://git.stormux.org/storm/cthulhu-tentacles/releases/tag/wormhole-v0.1.2). The manifest
|
||||||
prepared archives are uploaded. The manifest
|
|
||||||
requests presentation and interaction permissions. Local metadata above is an
|
requests presentation and interaction permissions. Local metadata above is an
|
||||||
optional development path.
|
optional development path.
|
||||||
Prepare amd64/arm64 archives with
|
Prepare amd64/arm64 archives with
|
||||||
@@ -117,7 +124,7 @@ After configuring the local entry:
|
|||||||
|
|
||||||
1. Quit Cthulhu with Cthulhu+Q. Press Ctrl+Escape, then Shift+G. These are
|
1. Quit Cthulhu with Cthulhu+Q. Press Ctrl+Escape, then Shift+G. These are
|
||||||
separate key presses. Shift+G rebuilds the reader and braille helper.
|
separate key presses. Shift+G rebuilds the reader and braille helper.
|
||||||
2. Focus an ordinary multiline text field. Open Cthulhu+Alt+Shift+W. Expect
|
2. Focus an ordinary multiline text field. Open Cthulhu+Ctrl+Shift+W. Expect
|
||||||
“Wormhole menu. Send focused item” in speech and the menu option on braille.
|
“Wormhole menu. Send focused item” in speech and the menu option on braille.
|
||||||
Native application focus must stay on the original field. Send it to a
|
Native application focus must stay on the original field. Send it to a
|
||||||
second Wormhole endpoint; the recipient must get exactly the current line.
|
second Wormhole endpoint; the recipient must get exactly the current line.
|
||||||
|
|||||||
+22
-1
@@ -90,6 +90,25 @@ func (w *wormhole) preferencesForm() tentacle.Interaction {
|
|||||||
{ID: "save", Label: "Save", Kind: tentacle.Button},
|
{ID: "save", Label: "Save", Kind: tentacle.Button},
|
||||||
}}, Next: "preferences"}
|
}}, Next: "preferences"}
|
||||||
}
|
}
|
||||||
|
func (w *wormhole) openPreferences(ctx context.Context, _ json.RawMessage) (any, error) {
|
||||||
|
w.mutex.Lock()
|
||||||
|
defer w.mutex.Unlock()
|
||||||
|
if err := ctx.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
result := w.preferencesForm()
|
||||||
|
result.Next = "save-settings"
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
func (w *wormhole) saveSettings(ctx context.Context, raw json.RawMessage) (any, error) {
|
||||||
|
value, err := w.savePreferences(ctx, raw)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
result := value.(tentacle.Interaction)
|
||||||
|
result.Form, result.Next, result.Done = nil, "", true
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
func (w *wormhole) savePreferences(ctx context.Context, raw json.RawMessage) (any, error) {
|
func (w *wormhole) savePreferences(ctx context.Context, raw json.RawMessage) (any, error) {
|
||||||
value, err := submission(raw)
|
value, err := submission(raw)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -146,11 +165,13 @@ func (w *wormhole) commands() []tentacle.Command {
|
|||||||
{"wormhole", "Wormhole menu", w.open}, {"menu", "Wormhole menu selection", w.choose},
|
{"wormhole", "Wormhole menu", w.open}, {"menu", "Wormhole menu selection", w.choose},
|
||||||
{"send", "Send Wormhole item", w.send}, {"receive", "Receive Wormhole item", w.receive},
|
{"send", "Send Wormhole item", w.send}, {"receive", "Receive Wormhole item", w.receive},
|
||||||
{"preferences", "Save Wormhole preferences", w.savePreferences}, {"control", "Wormhole transfer controls", w.control}, {"poll", "Wormhole transfer status", w.poll},
|
{"preferences", "Save Wormhole preferences", w.savePreferences}, {"control", "Wormhole transfer controls", w.control}, {"poll", "Wormhole transfer status", w.poll},
|
||||||
|
{"settings", "Wormhole preferences", w.openPreferences}, {"save-settings", "Save Wormhole settings", w.saveSettings},
|
||||||
} {
|
} {
|
||||||
command := tentacle.Command{ID: entry.id, Title: entry.title, Description: entry.title, Presentation: true, Interaction: true, Handle: entry.handler}
|
command := tentacle.Command{ID: entry.id, Title: entry.title, Description: entry.title, Presentation: true, Interaction: true, Handle: entry.handler}
|
||||||
if entry.id == "wormhole" {
|
if entry.id == "wormhole" {
|
||||||
command.Bindings = []tentacle.Binding{{Layout: "desktop", Scope: "global", Key: "Cthulhu+Alt+Shift+W"}, {Layout: "laptop", Scope: "global", Key: "Cthulhu+Alt+Shift+W"}}
|
command.Bindings = []tentacle.Binding{{Layout: "desktop", Scope: "global", Key: "Cthulhu+Ctrl+Shift+W"}, {Layout: "laptop", Scope: "global", Key: "Cthulhu+Ctrl+Shift+W"}}
|
||||||
}
|
}
|
||||||
|
command.Settings = entry.id == "settings"
|
||||||
commands = append(commands, command)
|
commands = append(commands, command)
|
||||||
}
|
}
|
||||||
return commands
|
return commands
|
||||||
|
|||||||
@@ -89,6 +89,57 @@ func TestMenuPreferencesAndMissingDependency(t *testing.T) {
|
|||||||
t.Fatal(result, err)
|
t.Fatal(result, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRegisteredSettingsAndDefaultShortcut(t *testing.T) {
|
||||||
|
w := testWormhole(t, "exit 0\n")
|
||||||
|
w.program = filepath.Join(t.TempDir(), "missing")
|
||||||
|
settings := 0
|
||||||
|
for _, command := range w.commands() {
|
||||||
|
if command.ID == "wormhole" {
|
||||||
|
if len(command.Bindings) != 2 {
|
||||||
|
t.Fatal(command.Bindings)
|
||||||
|
}
|
||||||
|
for index, layout := range []string{"desktop", "laptop"} {
|
||||||
|
binding := command.Bindings[index]
|
||||||
|
if binding.Layout != layout || binding.Scope != "global" || binding.Key != "Cthulhu+Ctrl+Shift+W" {
|
||||||
|
t.Fatal(binding)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !command.Settings {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
settings++
|
||||||
|
value, err := command.Handle(context.Background(), nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
form := value.(tentacle.Interaction)
|
||||||
|
if !command.Interaction || !command.Presentation || form.Form.Title != "Wormhole preferences" || form.Next != "save-settings" {
|
||||||
|
t.Fatal(command, form)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if settings != 1 {
|
||||||
|
t.Fatalf("settings entry points: %d", settings)
|
||||||
|
}
|
||||||
|
value, err := w.saveSettings(context.Background(), arguments(tentacle.Submission{Activated: "save", Values: map[string]tentacle.Value{"copy-code": {Checked: true}}}))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
result := value.(tentacle.Interaction)
|
||||||
|
if !result.Done || result.Form != nil || result.Next != "" || !w.preferences.CopySendCode {
|
||||||
|
t.Fatal(result)
|
||||||
|
}
|
||||||
|
data, err := os.ReadFile(w.settingsPath)
|
||||||
|
var saved preferences
|
||||||
|
if err != nil || json.Unmarshal(data, &saved) != nil || !saved.CopySendCode {
|
||||||
|
t.Fatal("settings did not persist", err)
|
||||||
|
}
|
||||||
|
_, err = w.saveSettings(context.Background(), arguments(tentacle.Submission{Cancelled: true}))
|
||||||
|
if err != nil || !w.preferences.CopySendCode {
|
||||||
|
t.Fatal("Cancel changed saved settings", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
func TestSendPreservesClipboardTextAndCodePreference(t *testing.T) {
|
func TestSendPreservesClipboardTextAndCodePreference(t *testing.T) {
|
||||||
for _, copyCode := range []bool{false, true} {
|
for _, copyCode := range []bool{false, true} {
|
||||||
t.Run(map[bool]string{false: "read-only", true: "copy-code"}[copyCode], func(t *testing.T) {
|
t.Run(map[bool]string{false: "read-only", true: "copy-code"}[copyCode], func(t *testing.T) {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ format-version = 1
|
|||||||
id = "wormhole"
|
id = "wormhole"
|
||||||
name = "Wormhole"
|
name = "Wormhole"
|
||||||
description = "Send a focused line or clipboard item and receive files, folders, and text with Magic Wormhole."
|
description = "Send a focused line or clipboard item and receive files, folders, and text with Magic Wormhole."
|
||||||
version = "0.1.1"
|
version = "0.1.2"
|
||||||
authors = ["Storm Dragon"]
|
authors = ["Storm Dragon"]
|
||||||
license = "GPL-3.0-or-later"
|
license = "GPL-3.0-or-later"
|
||||||
source = "https://git.stormux.org/storm/cthulhu-tentacles"
|
source = "https://git.stormux.org/storm/cthulhu-tentacles"
|
||||||
@@ -18,6 +18,6 @@ major = 1
|
|||||||
minor = 0
|
minor = 0
|
||||||
|
|
||||||
[requirements]
|
[requirements]
|
||||||
reader = "Cthulhu-Go with interactive Tentacle support"
|
reader = "Cthulhu-Go with interactive Tentacle settings support"
|
||||||
runtime = "Magic Wormhole CLI (wormhole); xclip for clipboard operations"
|
runtime = "Magic Wormhole CLI (wormhole); xclip for clipboard operations"
|
||||||
build = "Go 1.24 or newer and the adjacent cthulhu-go source checkout"
|
build = "Go 1.24 or newer and the adjacent cthulhu-go source checkout"
|
||||||
Reference in new issue
Block a user