fusuma 2.5.1 → 3.1.0
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 +55 -34
- data/exe/fusuma +4 -0
- data/fusuma.gemspec +3 -2
- data/lib/fusuma/config/index.rb +19 -46
- data/lib/fusuma/config/searcher.rb +68 -60
- data/lib/fusuma/config.rb +43 -6
- data/lib/fusuma/custom_process.rb +34 -2
- data/lib/fusuma/device.rb +1 -0
- data/lib/fusuma/environment.rb +6 -0
- data/lib/fusuma/hash_support.rb +22 -0
- data/lib/fusuma/multi_logger.rb +16 -0
- data/lib/fusuma/plugin/base.rb +16 -7
- data/lib/fusuma/plugin/buffers/gesture_buffer.rb +62 -6
- data/lib/fusuma/plugin/detectors/detector.rb +10 -9
- data/lib/fusuma/plugin/detectors/hold_detector.rb +28 -14
- data/lib/fusuma/plugin/detectors/pinch_detector.rb +10 -10
- data/lib/fusuma/plugin/detectors/rotate_detector.rb +4 -11
- data/lib/fusuma/plugin/detectors/swipe_detector.rb +8 -16
- data/lib/fusuma/plugin/events/records/gesture_record.rb +5 -2
- data/lib/fusuma/plugin/events/records/index_record.rb +1 -1
- data/lib/fusuma/plugin/executors/command_executor.rb +2 -2
- data/lib/fusuma/plugin/executors/executor.rb +1 -4
- data/lib/fusuma/plugin/filters/libinput_device_filter.rb +2 -1
- data/lib/fusuma/plugin/inputs/input.rb +14 -36
- data/lib/fusuma/plugin/inputs/libinput_command_input.yml +3 -0
- data/lib/fusuma/plugin/inputs/timer_input.rb +36 -21
- data/lib/fusuma/plugin/manager.rb +17 -6
- data/lib/fusuma/version.rb +1 -1
- data/lib/fusuma.rb +51 -36
- metadata +5 -4
@@ -9,8 +9,14 @@ module Fusuma
|
|
9
9
|
# Inherite this base
|
10
10
|
# @abstract Subclass and override {#io} to implement
|
11
11
|
class Input < Base
|
12
|
+
def initialize(*args)
|
13
|
+
super(*args)
|
14
|
+
@tag = self.class.name.split("Inputs::").last.underscore
|
15
|
+
end
|
16
|
+
|
17
|
+
attr_reader :tag
|
18
|
+
|
12
19
|
# Wait multiple inputs until it becomes readable
|
13
|
-
# and read lines with nonblock
|
14
20
|
# @param inputs [Array<Input>]
|
15
21
|
# @return [Event]
|
16
22
|
def self.select(inputs)
|
@@ -20,20 +26,17 @@ module Fusuma
|
|
20
26
|
input = inputs.find { |i| i.io == io }
|
21
27
|
|
22
28
|
begin
|
23
|
-
|
29
|
+
# NOTE: io.readline is blocking method
|
30
|
+
# each input plugin must write line to pipe (include `\n`)
|
31
|
+
line = io.readline(chomp: true)
|
24
32
|
rescue EOFError => e
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
warn "stop process: #{i.class.name.underscore}"
|
29
|
-
Process.kill(:SIGKILL, i.pid)
|
30
|
-
end
|
31
|
-
exit 1
|
33
|
+
MultiLogger.error "#{input.class.name}: #{e}"
|
34
|
+
MultiLogger.error "Shutdown fusuma process..."
|
35
|
+
Process.kill("TERM", Process.pid)
|
32
36
|
rescue => e
|
33
|
-
|
37
|
+
MultiLogger.error "#{input.class.name}: #{e}"
|
34
38
|
exit 1
|
35
39
|
end
|
36
|
-
|
37
40
|
input.create_event(record: line)
|
38
41
|
end
|
39
42
|
|
@@ -53,32 +56,7 @@ module Fusuma
|
|
53
56
|
MultiLogger.debug(input_event: e)
|
54
57
|
e
|
55
58
|
end
|
56
|
-
|
57
|
-
def tag
|
58
|
-
self.class.name.split("Inputs::").last.underscore
|
59
|
-
end
|
60
59
|
end
|
61
60
|
end
|
62
61
|
end
|
63
62
|
end
|
64
|
-
|
65
|
-
# ref: https://github.com/Homebrew/brew/blob/6b2dbbc96f7d8aa12f9b8c9c60107c9cc58befc4/Library/Homebrew/extend/io.rb
|
66
|
-
class IO
|
67
|
-
def readline_nonblock(sep = $INPUT_RECORD_SEPARATOR)
|
68
|
-
line = +""
|
69
|
-
buffer = +""
|
70
|
-
|
71
|
-
loop do
|
72
|
-
break if buffer == sep
|
73
|
-
|
74
|
-
read_nonblock(1, buffer)
|
75
|
-
line.concat(buffer)
|
76
|
-
end
|
77
|
-
|
78
|
-
line.freeze
|
79
|
-
rescue IO::WaitReadable, EOFError => e
|
80
|
-
raise e if line.empty?
|
81
|
-
|
82
|
-
line.freeze
|
83
|
-
end
|
84
|
-
end
|
@@ -1,20 +1,29 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require_relative "./input"
|
4
|
+
require "timeout"
|
4
5
|
|
5
6
|
module Fusuma
|
6
7
|
module Plugin
|
7
8
|
module Inputs
|
8
9
|
# libinput commands wrapper
|
9
10
|
class TimerInput < Input
|
10
|
-
|
11
|
+
include Singleton
|
12
|
+
DEFAULT_INTERVAL = 5
|
13
|
+
EPSILON_TIME = 0.02
|
11
14
|
def config_param_types
|
12
15
|
{
|
13
16
|
interval: [Float]
|
14
17
|
}
|
15
18
|
end
|
16
19
|
|
17
|
-
|
20
|
+
def initialize(*args, interval: nil)
|
21
|
+
super(*args)
|
22
|
+
@interval = interval || config_params(:interval) || DEFAULT_INTERVAL
|
23
|
+
@early_wake_queue = Queue.new
|
24
|
+
end
|
25
|
+
|
26
|
+
attr_reader :pid, :interval
|
18
27
|
|
19
28
|
def io
|
20
29
|
@io ||= begin
|
@@ -26,26 +35,36 @@ module Fusuma
|
|
26
35
|
end
|
27
36
|
|
28
37
|
def start(reader, writer)
|
29
|
-
|
30
|
-
timer_loop(
|
38
|
+
Thread.new do
|
39
|
+
timer_loop(writer)
|
31
40
|
end
|
32
|
-
|
33
|
-
writer.close
|
34
|
-
pid
|
41
|
+
nil
|
35
42
|
end
|
36
43
|
|
37
|
-
def timer_loop(
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
44
|
+
def timer_loop(writer)
|
45
|
+
delta_t = @interval
|
46
|
+
next_wake = Time.now + delta_t
|
47
|
+
loop do
|
48
|
+
sleep_time = next_wake - Time.now
|
49
|
+
if sleep_time <= 0
|
50
|
+
raise Timeout::Error
|
51
|
+
end
|
52
|
+
|
53
|
+
Timeout.timeout(sleep_time) do
|
54
|
+
next_wake = [@early_wake_queue.deq, next_wake].min
|
43
55
|
end
|
44
|
-
rescue
|
45
|
-
|
46
|
-
|
47
|
-
MultiLogger.error e
|
56
|
+
rescue Timeout::Error
|
57
|
+
writer.puts "timer"
|
58
|
+
next_wake = Time.now + delta_t
|
48
59
|
end
|
60
|
+
rescue Errno::EPIPE
|
61
|
+
exit 0
|
62
|
+
rescue => e
|
63
|
+
MultiLogger.error e
|
64
|
+
end
|
65
|
+
|
66
|
+
def wake_early(t)
|
67
|
+
@early_wake_queue.push(t + EPSILON_TIME)
|
49
68
|
end
|
50
69
|
|
51
70
|
private
|
@@ -53,10 +72,6 @@ module Fusuma
|
|
53
72
|
def create_io
|
54
73
|
IO.pipe
|
55
74
|
end
|
56
|
-
|
57
|
-
def interval
|
58
|
-
config_params(:interval) || DEFAULT_INTERVAL
|
59
|
-
end
|
60
75
|
end
|
61
76
|
end
|
62
77
|
end
|
@@ -27,29 +27,34 @@ module Fusuma
|
|
27
27
|
@_fusuma_default_plugin_paths ||= Dir.glob(File.expand_path("#{__dir__}/../../#{search_key}")).grep_v(exclude_path_pattern).sort
|
28
28
|
end
|
29
29
|
|
30
|
+
# @return [Array<String>] paths of external plugins (installed by gem)
|
30
31
|
def fusuma_external_plugin_paths
|
31
32
|
@_fusuma_external_plugin_paths ||=
|
32
33
|
Gem.find_latest_files(search_key).map do |siblings_plugin|
|
33
34
|
next unless %r{fusuma-plugin-(.+).*/lib/#{plugin_dir_name}/.+\.rb}.match?(siblings_plugin)
|
34
35
|
|
35
36
|
match_data = siblings_plugin.match(%r{(.*)/(.*)/lib/(.*)})
|
36
|
-
|
37
|
-
raise "Not Found: #{match_data[1]}/#{match_data[2]}/*.gemspec" unless
|
37
|
+
plugin_gemspec_path = Dir.glob("#{match_data[1]}/#{match_data[2]}/*.gemspec").first
|
38
|
+
raise "Not Found: #{match_data[1]}/#{match_data[2]}/*.gemspec" unless plugin_gemspec_path
|
38
39
|
|
39
|
-
|
40
|
+
plugin_gemspec = Gem::Specification.load(plugin_gemspec_path)
|
40
41
|
fusuma_gemspec_path = File.expand_path("../../../fusuma.gemspec", __dir__)
|
41
42
|
fusuma_gemspec = Gem::Specification.load(fusuma_gemspec_path)
|
42
43
|
|
43
|
-
if
|
44
|
+
if plugin_gemspec.dependencies.find { |d| d.name == "fusuma" }&.match?(fusuma_gemspec)
|
44
45
|
siblings_plugin
|
45
46
|
else
|
46
|
-
MultiLogger.warn "#{
|
47
|
-
MultiLogger.warn "gemspec: #{
|
47
|
+
MultiLogger.warn "#{plugin_gemspec.name} #{plugin_gemspec.version} is incompatible with running #{fusuma_gemspec.name} #{fusuma_gemspec.version}"
|
48
|
+
MultiLogger.warn "gemspec: #{plugin_gemspec_path}"
|
48
49
|
next
|
49
50
|
end
|
50
51
|
end.compact.grep_v(exclude_path_pattern).sort
|
51
52
|
end
|
52
53
|
|
54
|
+
# @return [String] search key for plugin
|
55
|
+
# @example
|
56
|
+
# search_key
|
57
|
+
# => "fusuma/plugin/detectors/*rb"
|
53
58
|
def search_key
|
54
59
|
File.join(plugin_dir_name, "*rb")
|
55
60
|
end
|
@@ -110,6 +115,12 @@ module Fusuma
|
|
110
115
|
@plugins ||= {}
|
111
116
|
end
|
112
117
|
|
118
|
+
# @return [Array<String>]
|
119
|
+
# @example
|
120
|
+
# Manager.load_paths
|
121
|
+
# => ["/path/to/fusuma/lib/fusuma/plugin/inputs/input.rb",
|
122
|
+
# "/path/to/fusuma/lib/fusuma/plugin/inputs/libinput_command_input.rb",
|
123
|
+
# "/path/to/fusuma/lib/fusuma/plugin/inputs/timer_input.rb"]
|
113
124
|
def load_paths
|
114
125
|
@load_paths ||= []
|
115
126
|
end
|
data/lib/fusuma/version.rb
CHANGED
data/lib/fusuma.rb
CHANGED
@@ -13,9 +13,9 @@ module Fusuma
|
|
13
13
|
class Runner
|
14
14
|
class << self
|
15
15
|
def run(option = {})
|
16
|
-
set_trap
|
17
16
|
read_options(option)
|
18
17
|
instance = new
|
18
|
+
instance.set_trap
|
19
19
|
## NOTE: Uncomment following line to measure performance
|
20
20
|
# instance.run_with_lineprof
|
21
21
|
instance.run
|
@@ -23,22 +23,22 @@ module Fusuma
|
|
23
23
|
|
24
24
|
private
|
25
25
|
|
26
|
-
def set_trap
|
27
|
-
Signal.trap("INT") { puts exit } # Trap ^C
|
28
|
-
Signal.trap("TERM") { puts exit } # Trap `Kill `
|
29
|
-
end
|
30
|
-
|
31
26
|
def read_options(option)
|
32
27
|
MultiLogger.filepath = option[:log_filepath]
|
33
28
|
MultiLogger.instance.debug_mode = option[:verbose]
|
34
29
|
|
35
|
-
load_custom_config(option[:config_path])
|
36
|
-
|
37
30
|
Plugin::Manager.require_base_plugins
|
38
31
|
|
32
|
+
load_custom_config(option[:config_path])
|
33
|
+
|
39
34
|
Environment.dump_information
|
40
35
|
Kernel.exit(0) if option[:version]
|
41
36
|
|
37
|
+
if option[:show_config]
|
38
|
+
Environment.print_config
|
39
|
+
Kernel.exit(0)
|
40
|
+
end
|
41
|
+
|
42
42
|
if option[:list]
|
43
43
|
Environment.print_device_list
|
44
44
|
Kernel.exit(0)
|
@@ -56,7 +56,9 @@ module Fusuma
|
|
56
56
|
end
|
57
57
|
|
58
58
|
def initialize
|
59
|
-
@inputs = Plugin::Inputs::Input.plugins.map
|
59
|
+
@inputs = Plugin::Inputs::Input.plugins.map do |cls|
|
60
|
+
cls.ancestors.include?(Singleton) ? cls.instance : cls.new
|
61
|
+
end
|
60
62
|
@filters = Plugin::Filters::Filter.plugins.map(&:new)
|
61
63
|
@parsers = Plugin::Parsers::Parser.plugins.map(&:new)
|
62
64
|
@buffers = Plugin::Buffers::Buffer.plugins.map(&:new)
|
@@ -75,8 +77,8 @@ module Fusuma
|
|
75
77
|
parsed = parse(filtered) || return
|
76
78
|
buffered = buffer(parsed) || return
|
77
79
|
detected = detect(buffered) || return
|
78
|
-
|
79
|
-
execute(
|
80
|
+
context, event = merge(detected) || return
|
81
|
+
execute(context, event)
|
80
82
|
end
|
81
83
|
|
82
84
|
# For performance monitoring
|
@@ -98,12 +100,14 @@ module Fusuma
|
|
98
100
|
|
99
101
|
# @param [Plugin::Events::Event]
|
100
102
|
# @return [Plugin::Events::Event]
|
103
|
+
# @return [NilClass]
|
101
104
|
def filter(event)
|
102
105
|
event if @filters.any? { |f| f.filter(event) }
|
103
106
|
end
|
104
107
|
|
105
108
|
# @param [Plugin::Events::Event]
|
106
109
|
# @return [Plugin::Events::Event]
|
110
|
+
# @return [NilClass]
|
107
111
|
def parse(event)
|
108
112
|
@parsers.reduce(event) { |e, p| p.parse(e) if e }
|
109
113
|
end
|
@@ -117,6 +121,7 @@ module Fusuma
|
|
117
121
|
|
118
122
|
# @param buffers [Array<Buffer>]
|
119
123
|
# @return [Array<Event>]
|
124
|
+
# @return [NilClass]
|
120
125
|
def detect(buffers)
|
121
126
|
matched_detectors = @detectors.select do |detector|
|
122
127
|
detector.watch? ||
|
@@ -124,7 +129,8 @@ module Fusuma
|
|
124
129
|
end
|
125
130
|
|
126
131
|
events = matched_detectors.each_with_object([]) do |detector, detected|
|
127
|
-
Array(detector.detect(@buffers)).each { |e| detected << e }
|
132
|
+
# Array(detector.detect(@buffers)).each { |e| detected << e }
|
133
|
+
detected.concat(Array(detector.detect(@buffers)))
|
128
134
|
end
|
129
135
|
|
130
136
|
return if events.empty?
|
@@ -133,7 +139,7 @@ module Fusuma
|
|
133
139
|
end
|
134
140
|
|
135
141
|
# @param events [Array<Plugin::Events::Event>]
|
136
|
-
# @return [Plugin::Events::Event] Event merged all
|
142
|
+
# @return [Array<Hash, Plugin::Events::Event>] Event merged all events from arguments and used context
|
137
143
|
# @return [NilClass] when event is NOT given
|
138
144
|
def merge(events)
|
139
145
|
index_events, context_events = events.partition { |event| event.record.type == :index }
|
@@ -143,44 +149,34 @@ module Fusuma
|
|
143
149
|
end
|
144
150
|
main_events.sort_by! { |e| e.record.trigger_priority }
|
145
151
|
|
146
|
-
matched_condition = nil
|
147
152
|
matched_context = nil
|
148
153
|
event = main_events.find do |main_event|
|
149
154
|
matched_context = Config::Searcher.find_context(request_context) do
|
150
|
-
|
151
|
-
main_event.record.merge(records: modifiers.map(&:record))
|
152
|
-
end
|
153
|
-
if matched_condition && modified_record
|
155
|
+
if (modified_record = main_event.record.merge(records: modifiers.map(&:record)))
|
154
156
|
main_event.record = modified_record
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
157
|
+
elsif !modifiers.empty?
|
158
|
+
# try basically the same, but without any modifiers
|
159
|
+
# if modifiers is empty then we end up here only if there is no execute key for this
|
160
|
+
Config.instance.search(main_event.record.index) &&
|
161
|
+
Config.instance.find_execute_key(main_event.record.index)
|
160
162
|
end
|
161
163
|
end
|
162
164
|
end
|
163
165
|
return if event.nil?
|
164
166
|
|
165
|
-
[
|
167
|
+
[matched_context, event]
|
166
168
|
end
|
167
169
|
|
170
|
+
# @return [NilClass] when event is NOT given or executable context is NOT found
|
168
171
|
# @param event [Plugin::Events::Event]
|
169
|
-
def execute(
|
172
|
+
def execute(context, event)
|
170
173
|
return unless event
|
171
174
|
|
172
|
-
# Find executable
|
173
|
-
executor = Config::Searcher.with_context(context) do
|
174
|
-
Config::Searcher.with_condition(condition) do
|
175
|
-
@executors.find { |e| e.executable?(event) }
|
176
|
-
end
|
177
|
-
end
|
178
|
-
|
179
|
-
return if executor.nil?
|
180
|
-
|
181
|
-
# Check interval and execute
|
175
|
+
# Find executable context
|
182
176
|
Config::Searcher.with_context(context) do
|
183
|
-
|
177
|
+
executor = @executors.find { |e| e.executable?(event) }
|
178
|
+
if executor
|
179
|
+
# Check interval and execute
|
184
180
|
executor.enough_interval?(event) &&
|
185
181
|
executor.update_interval(event) &&
|
186
182
|
executor.execute(event)
|
@@ -191,5 +187,24 @@ module Fusuma
|
|
191
187
|
def clear_expired_events
|
192
188
|
@buffers.each(&:clear_expired)
|
193
189
|
end
|
190
|
+
|
191
|
+
def set_trap
|
192
|
+
Signal.trap("INT") {
|
193
|
+
shutdown
|
194
|
+
puts exit
|
195
|
+
} # Trap ^C
|
196
|
+
Signal.trap("TERM") {
|
197
|
+
shutdown
|
198
|
+
puts exit
|
199
|
+
} # Trap `Kill `
|
200
|
+
end
|
201
|
+
|
202
|
+
private
|
203
|
+
|
204
|
+
def shutdown
|
205
|
+
[@inputs, @filters, @parsers, @buffers, @detectors, @executors].flatten.each do |plugin|
|
206
|
+
plugin.shutdown
|
207
|
+
end
|
208
|
+
end
|
194
209
|
end
|
195
210
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: fusuma
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 3.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- iberianpig
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2023-09-03 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: Fusuma is multitouch gesture recognizer. This gem makes your touchpad
|
14
14
|
on Linux able to recognize swipes or pinchs and assign command to them. Read installation
|
@@ -59,6 +59,7 @@ files:
|
|
59
59
|
- lib/fusuma/plugin/filters/libinput_device_filter.rb
|
60
60
|
- lib/fusuma/plugin/inputs/input.rb
|
61
61
|
- lib/fusuma/plugin/inputs/libinput_command_input.rb
|
62
|
+
- lib/fusuma/plugin/inputs/libinput_command_input.yml
|
62
63
|
- lib/fusuma/plugin/inputs/timer_input.rb
|
63
64
|
- lib/fusuma/plugin/manager.rb
|
64
65
|
- lib/fusuma/plugin/parsers/libinput_gesture_parser.rb
|
@@ -79,14 +80,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
79
80
|
requirements:
|
80
81
|
- - ">="
|
81
82
|
- !ruby/object:Gem::Version
|
82
|
-
version: 2.
|
83
|
+
version: '2.7'
|
83
84
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
84
85
|
requirements:
|
85
86
|
- - ">="
|
86
87
|
- !ruby/object:Gem::Version
|
87
88
|
version: '0'
|
88
89
|
requirements: []
|
89
|
-
rubygems_version: 3.
|
90
|
+
rubygems_version: 3.4.10
|
90
91
|
signing_key:
|
91
92
|
specification_version: 4
|
92
93
|
summary: Multitouch gestures with libinput driver, Linux
|