libtmux 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +16 -7
- data/lib/libtmux/capabilities.rb +2 -2
- data/lib/libtmux/endpoint.rb +70 -17
- data/lib/libtmux/entity.rb +1 -1
- data/lib/libtmux/native_guard.rb +8 -2
- data/lib/libtmux/server.rb +4 -1
- data/lib/libtmux/version.rb +1 -1
- data/sig/libtmux.rbs +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e121eada829f9816c2d77a1910ae0ff9c473bb10539250a80a732156d7cf2804
|
|
4
|
+
data.tar.gz: 725a646a69288b57ec37d9ded5f02444d40ac8968419bf0470706d2d3ec5505d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 062be2f34e7a702808bc822350dca3bca1ef456957aab5f0885b069c518ce68b3bcfb3914b758ff1607ed98b25edef72c560e6c28ce79a5ef09ca566389261bd
|
|
7
|
+
data.tar.gz: 0f2434316cd39f28f75618aa47b5fe3bb122f1dcec91017d256df182fcd564801602be30c7d84d803a3d4fa829a4af632fab3b5ff315f45f6ba3f29399788236
|
data/README.md
CHANGED
|
@@ -19,6 +19,9 @@ as handles, [snapshots][snapshot] you filter in plain Ruby, and
|
|
|
19
19
|
[Guides](https://libtmux.org/en/ruby/latest/guides/) ·
|
|
20
20
|
[RubyGems](https://rubygems.org/gems/libtmux)
|
|
21
21
|
|
|
22
|
+
[](https://libtmux.org/en/ruby/)
|
|
23
|
+
[](https://rubygems.org/gems/libtmux)
|
|
24
|
+
|
|
22
25
|
</div>
|
|
23
26
|
|
|
24
27
|
Part of [libtmux for Ruby](https://github.com/libtmux/libtmux-ruby#readme).
|
|
@@ -338,8 +341,10 @@ connected sessions: 1
|
|
|
338
341
|
sessions after client close: 1
|
|
339
342
|
```
|
|
340
343
|
|
|
341
|
-
- [`Endpoint.new`][endpoint-new] takes
|
|
342
|
-
|
|
344
|
+
- [`Endpoint.new`][endpoint-new] takes a socket path or socket name. With
|
|
345
|
+
neither, it picks the socket tmux would: `LIBTMUX_SOCKET_PATH`,
|
|
346
|
+
`LIBTMUX_SOCKET`, the server `$TMUX` names, then tmux's default socket.
|
|
347
|
+
[`Endpoint.from_env`][endpoint-from_env] follows `$TMUX` alone.
|
|
343
348
|
- A binding keeps a private link to the socket it bound, so commands never
|
|
344
349
|
reach a different daemon that later takes the same path.
|
|
345
350
|
- A ref is valid on the server that issued it. Another server raises
|
|
@@ -708,8 +713,11 @@ server and lists every option.
|
|
|
708
713
|
[Live handles][behavior-live-handles].
|
|
709
714
|
- **A terminal emulator.** [`Pane#capture`][pane-capture] returns the bytes
|
|
710
715
|
tmux holds for the screen, with the flags above to shape them.
|
|
711
|
-
- **
|
|
712
|
-
|
|
716
|
+
- **A server it starts on its own.** [`Server.new`][server-new] and
|
|
717
|
+
[`Server.open`][server-open] bind a running server and never start one. With
|
|
718
|
+
no socket named, [`Endpoint.new`][endpoint-new] picks the one tmux would:
|
|
719
|
+
`LIBTMUX_SOCKET_PATH`, `LIBTMUX_SOCKET`, the server `TMUX` names, then tmux's
|
|
720
|
+
default socket. The recipes name a socket or own one.
|
|
713
721
|
- **Replay.** A reconnected control connection reports a gap and never replays
|
|
714
722
|
requests or missed output. See [Control flow][behavior-control-flow].
|
|
715
723
|
- **Transactions.** A [group][server-run_group] runs in order and keeps the
|
|
@@ -735,10 +743,11 @@ See the [changelog](../../CHANGELOG.md).
|
|
|
735
743
|
[optional feature table](../../docs/reference/behavior.md#optional-tmux-features)
|
|
736
744
|
lists the other per-release requirements.
|
|
737
745
|
- **Linux, including WSL, and macOS.** The [compatibility workflow][compat]
|
|
738
|
-
runs Ruby 3.3, 3.4 and 4.0 with tmux 3.3a through 3.8 on Ubuntu 24.04.
|
|
739
|
-
|
|
746
|
+
runs Ruby 3.3, 3.4 and 4.0 with tmux 3.3a through 3.8 on Ubuntu 24.04. A push
|
|
747
|
+
and a pull request run the oldest and newest of those releases, 3.3a and 3.8,
|
|
748
|
+
and a release runs all of them. macOS is supported on a best-effort basis, and CI coverage for it is highly
|
|
740
749
|
limited.
|
|
741
|
-
- **tmux master and old daemons.** On Ubuntu
|
|
750
|
+
- **tmux master and old daemons.** On Ubuntu a release also runs the suite
|
|
742
751
|
against a pinned tmux master, and checks that tmux 3.0a, 3.1c, 3.2a and 3.3
|
|
743
752
|
daemons are refused.
|
|
744
753
|
- **Per-revision results.** Read the workflow's results for your revision
|
data/lib/libtmux/capabilities.rb
CHANGED
|
@@ -91,10 +91,10 @@ module LibTmux
|
|
|
91
91
|
summary: "Apply main-horizontal-mirrored and main-vertical-mirrored.",
|
|
92
92
|
apis: ["LibTmux::Window#select_layout"],
|
|
93
93
|
commands: [Command.new(command: "select-layout")]),
|
|
94
|
-
Capability.new(id: "layout-json", name: "JSON layout strings", since: "3.8
|
|
94
|
+
Capability.new(id: "layout-json", name: "JSON layout strings", since: "3.8",
|
|
95
95
|
handling: :refuse, error: ERROR,
|
|
96
96
|
summary: "Apply a layout saved in the JSON form tmux reports.",
|
|
97
|
-
apis: ["LibTmux::Window#select_layout"],
|
|
97
|
+
apis: ["LibTmux::Window#select_layout", "LibTmux::Server#run", "LibTmux::ControlConnection#exchange"],
|
|
98
98
|
commands: [Command.new(command: "select-layout")]),
|
|
99
99
|
Capability.new(id: "clipboard-query", name: "Clipboard query", since: "3.6a", handling: :refuse, error: ERROR,
|
|
100
100
|
summary: "Ask an attached client's terminal for its clipboard with refresh-client -l.",
|
data/lib/libtmux/endpoint.rb
CHANGED
|
@@ -5,38 +5,52 @@ require "securerandom"
|
|
|
5
5
|
require_relative "errors"
|
|
6
6
|
|
|
7
7
|
module LibTmux
|
|
8
|
-
#
|
|
8
|
+
# Executable and socket selection. Construction starts no clients.
|
|
9
|
+
#
|
|
10
|
+
# {#initialize Endpoint.new} takes a `socket_path:` or a `socket_name:`.
|
|
11
|
+
# With neither, it picks the socket as tmux does when it is given none, and
|
|
12
|
+
# `environment:` (the process environment by default) decides which: the
|
|
13
|
+
# path in `LIBTMUX_SOCKET_PATH`, else the name in `LIBTMUX_SOCKET`, else the
|
|
14
|
+
# socket `TMUX` names when run inside tmux, else tmux's `default` socket. A
|
|
15
|
+
# variable that is empty counts as unset, and a socket given as an argument
|
|
16
|
+
# ignores the variables. {.from_env} skips the `LIBTMUX_*` variables.
|
|
9
17
|
class Endpoint
|
|
10
18
|
attr_reader :executable, :socket_path
|
|
11
19
|
|
|
12
|
-
def initialize(socket_path: nil, socket_name: nil, executable: "tmux", socket_directory: nil)
|
|
13
|
-
unless
|
|
14
|
-
raise ArgumentError, "choose
|
|
20
|
+
def initialize(socket_path: nil, socket_name: nil, executable: "tmux", socket_directory: nil, environment: ENV)
|
|
21
|
+
unless socket_path.nil? || socket_name.nil?
|
|
22
|
+
raise ArgumentError, "choose at most one socket_path or socket_name"
|
|
23
|
+
end
|
|
24
|
+
origin = path_label = nil
|
|
25
|
+
if socket_path.nil? && socket_name.nil?
|
|
26
|
+
raise ArgumentError, "socket_directory applies only to socket_name" if socket_directory
|
|
27
|
+
socket_path, socket_name, origin = Internal::SocketChoice.call(environment)
|
|
15
28
|
end
|
|
16
29
|
if socket_name
|
|
17
|
-
|
|
30
|
+
label = origin || "socket_name"
|
|
31
|
+
validate_string(socket_name, label)
|
|
18
32
|
if socket_name.include?("/") || [".", ".."].include?(socket_name)
|
|
19
|
-
raise ArgumentError, "
|
|
33
|
+
raise ArgumentError, "#{label} must be a single filename"
|
|
20
34
|
end
|
|
21
|
-
directory = socket_directory ||
|
|
35
|
+
directory = socket_directory || Internal::SocketChoice.present(environment["TMUX_TMPDIR"]) || "/tmp"
|
|
22
36
|
validate_string(directory, "socket_directory")
|
|
37
|
+
path_label = socket_directory ? "socket_directory" : "TMUX_TMPDIR"
|
|
23
38
|
socket_path = File.join(directory, "tmux-#{Process.uid}", socket_name)
|
|
24
39
|
elsif socket_directory
|
|
25
40
|
raise ArgumentError, "socket_directory applies only to socket_name"
|
|
26
41
|
end
|
|
27
|
-
validate_string(socket_path, "socket_path")
|
|
42
|
+
validate_string(socket_path, origin || "socket_path")
|
|
28
43
|
validate_string(executable, "executable")
|
|
29
|
-
@socket_path =
|
|
44
|
+
@socket_path = expand(socket_path, path_label || origin || "socket_path").freeze
|
|
30
45
|
@executable = resolve_executable(executable).freeze
|
|
31
46
|
freeze
|
|
32
47
|
end
|
|
33
48
|
|
|
49
|
+
# The socket of the tmux server this process runs inside, else tmux's
|
|
50
|
+
# `default` socket. It ignores `LIBTMUX_SOCKET_PATH` and `LIBTMUX_SOCKET`.
|
|
34
51
|
def self.from_env(env = ENV, **options)
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
else
|
|
38
|
-
new(socket_name: "default", socket_directory: env["TMUX_TMPDIR"], **options)
|
|
39
|
-
end
|
|
52
|
+
socket_path, socket_name = Internal::SocketChoice.tmux(env)
|
|
53
|
+
new(socket_path: socket_path, socket_name: socket_name, environment: env, **options)
|
|
40
54
|
end
|
|
41
55
|
|
|
42
56
|
def ==(other)
|
|
@@ -49,11 +63,19 @@ module LibTmux
|
|
|
49
63
|
end
|
|
50
64
|
|
|
51
65
|
def inspect
|
|
52
|
-
"#<#{self.class}
|
|
66
|
+
"#<#{self.class} Unix socket>"
|
|
53
67
|
end
|
|
54
68
|
|
|
55
69
|
private
|
|
56
70
|
|
|
71
|
+
# File.expand_path puts the user it cannot find in its message, so the
|
|
72
|
+
# message here names the argument or variable instead.
|
|
73
|
+
def expand(path, label, base = nil)
|
|
74
|
+
base ? File.expand_path(path, base) : File.expand_path(path)
|
|
75
|
+
rescue ArgumentError
|
|
76
|
+
raise ArgumentError, "#{label} cannot be expanded to a path; its leading ~ names no home directory"
|
|
77
|
+
end
|
|
78
|
+
|
|
57
79
|
def validate_string(value, name)
|
|
58
80
|
unless value.is_a?(String) && !value.empty? && !value.include?("\0")
|
|
59
81
|
raise ArgumentError, "#{name} must be a nonempty String without NUL"
|
|
@@ -62,10 +84,10 @@ module LibTmux
|
|
|
62
84
|
|
|
63
85
|
def resolve_executable(value)
|
|
64
86
|
candidates = if value.include?(File::SEPARATOR)
|
|
65
|
-
[
|
|
87
|
+
[expand(value, "executable")]
|
|
66
88
|
else
|
|
67
89
|
ENV.fetch("PATH", "").split(File::PATH_SEPARATOR).map do |directory|
|
|
68
|
-
|
|
90
|
+
expand(value, "executable", directory.empty? ? Dir.pwd : directory)
|
|
69
91
|
end
|
|
70
92
|
end
|
|
71
93
|
candidates.find { |path| File.file?(path) && File.executable?(path) } ||
|
|
@@ -74,6 +96,37 @@ module LibTmux
|
|
|
74
96
|
end
|
|
75
97
|
|
|
76
98
|
module Internal
|
|
99
|
+
# Picks the socket tmux would use if it were given neither -S nor -L, from
|
|
100
|
+
# the variables that stand for them and the ones tmux reads itself: -S
|
|
101
|
+
# beats -L, and -L beats TMUX. Each pick is [path, name, variable], with
|
|
102
|
+
# the variable named so that a bad value is reported by name.
|
|
103
|
+
module SocketChoice
|
|
104
|
+
module_function
|
|
105
|
+
|
|
106
|
+
def call(environment)
|
|
107
|
+
if (path = present(environment["LIBTMUX_SOCKET_PATH"]))
|
|
108
|
+
[path, nil, "LIBTMUX_SOCKET_PATH"]
|
|
109
|
+
elsif (name = present(environment["LIBTMUX_SOCKET"]))
|
|
110
|
+
[nil, name, "LIBTMUX_SOCKET"]
|
|
111
|
+
else
|
|
112
|
+
tmux(environment)
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# tmux ignores a TMUX that is empty or starts with a comma, and takes
|
|
117
|
+
# the socket path before the first comma of one that does not.
|
|
118
|
+
def tmux(environment)
|
|
119
|
+
inside = present(environment["TMUX"])
|
|
120
|
+
return [nil, "default", nil] if inside.nil? || inside.start_with?(",")
|
|
121
|
+
|
|
122
|
+
[inside.split(",", 2).first, nil, "TMUX"]
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def present(value)
|
|
126
|
+
value if value.is_a?(String) && !value.empty?
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
|
|
77
130
|
# Keep the socket inode alive and route through it; PID timestamps can repeat.
|
|
78
131
|
class SocketIdentity
|
|
79
132
|
attr_reader :key, :release
|
data/lib/libtmux/entity.rb
CHANGED
|
@@ -135,7 +135,7 @@ module LibTmux
|
|
|
135
135
|
value = layout.is_a?(Symbol) ? layout.to_s.tr("_", "-") : layout
|
|
136
136
|
budget = server.__send__(:operation_budget, timeout, cancel)
|
|
137
137
|
# Older tmux can access an invalid pointer for a malformed layout header.
|
|
138
|
-
# tmux 3.8
|
|
138
|
+
# tmux 3.8 and later report and accept saved layouts as JSON.
|
|
139
139
|
saved = /\A[0-9a-fA-F]{4},/.match?(value)
|
|
140
140
|
if !saved && value.start_with?("{")
|
|
141
141
|
json = Internal::Capabilities.capability("layout-json")
|
data/lib/libtmux/native_guard.rb
CHANGED
|
@@ -317,11 +317,17 @@ module LibTmux
|
|
|
317
317
|
end
|
|
318
318
|
|
|
319
319
|
# tmux 3.3a faults on a header that is not four hex digits and a comma, so
|
|
320
|
-
# only checksummed layouts and built-in names pass before 3.4.
|
|
320
|
+
# only checksummed layouts and built-in names pass before 3.4. A JSON layout
|
|
321
|
+
# needs tmux 3.8.
|
|
321
322
|
def check_layout(item, command, release)
|
|
323
|
+
given = flags(item, command)
|
|
324
|
+
json = Internal::Capabilities.capability("layout-json")
|
|
325
|
+
if !json.available?(release) && operands(item, command).first.to_s.start_with?("{") && given.slice("E", "n", "p").empty?
|
|
326
|
+
raise UnsupportedFeatureError.new("JSON layouts require tmux #{json.since} or newer " \
|
|
327
|
+
"(connected version #{release.text})", phase: :admission, delivery: :not_sent)
|
|
328
|
+
end
|
|
322
329
|
return if Internal::Capabilities.capability("layout-without-checksum").available?(release)
|
|
323
330
|
|
|
324
|
-
given = flags(item, command)
|
|
325
331
|
return if given.key?("E") || given.key?("n") || given.key?("p")
|
|
326
332
|
|
|
327
333
|
layout = operands(item, command).first
|
data/lib/libtmux/server.rb
CHANGED
|
@@ -32,7 +32,10 @@ module LibTmux
|
|
|
32
32
|
#
|
|
33
33
|
# **Connecting.** {#initialize Server.new} and {Server.open} bind an endpoint
|
|
34
34
|
# that already exists; {Server.open} with a block closes the server when the
|
|
35
|
-
# block ends.
|
|
35
|
+
# block ends. Given no `endpoint:`, `socket_path:` or `socket_name:`, they
|
|
36
|
+
# bind the socket {Endpoint#initialize Endpoint.new} picks from
|
|
37
|
+
# `LIBTMUX_SOCKET_PATH`, `LIBTMUX_SOCKET`, `TMUX` and tmux's default socket,
|
|
38
|
+
# in that order. {Server.start} starts a private daemon instead, creates no
|
|
36
39
|
# session, and owns the daemon's exit. {#endpoint} answers the {Endpoint}
|
|
37
40
|
# and {#owned?} says whether this server started its daemon. {#diagnostics}
|
|
38
41
|
# reports the request and control slots in use, and the limits. {#close}
|
data/lib/libtmux/version.rb
CHANGED
data/sig/libtmux.rbs
CHANGED
|
@@ -58,7 +58,7 @@ module LibTmux
|
|
|
58
58
|
class Endpoint
|
|
59
59
|
attr_reader executable: String
|
|
60
60
|
attr_reader socket_path: String
|
|
61
|
-
def initialize: (?socket_path: String?, ?socket_name: String?, ?executable: String, ?socket_directory: String?) -> void
|
|
61
|
+
def initialize: (?socket_path: String?, ?socket_name: String?, ?executable: String, ?socket_directory: String?, ?environment: untyped) -> void
|
|
62
62
|
def self.from_env: (?untyped, **untyped) -> Endpoint
|
|
63
63
|
def ==: (untyped) -> bool
|
|
64
64
|
alias eql? ==
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: libtmux
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- libtmux contributors
|
|
@@ -131,7 +131,7 @@ licenses:
|
|
|
131
131
|
- MIT
|
|
132
132
|
metadata:
|
|
133
133
|
source_code_uri: https://github.com/libtmux/libtmux-ruby
|
|
134
|
-
documentation_uri: https://libtmux.org/en/ruby/v0.1.
|
|
134
|
+
documentation_uri: https://libtmux.org/en/ruby/v0.1.1/
|
|
135
135
|
rdoc_options: []
|
|
136
136
|
require_paths:
|
|
137
137
|
- lib
|