fusuma 1.10.1 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/pull_request_template.md +9 -0
- data/.rubocop.yml +27 -0
- data/.rubocop_todo.yml +34 -19
- data/.solargraph.yml +16 -0
- data/.travis.yml +1 -3
- data/CHANGELOG.md +38 -1
- data/CONTRIBUTING.md +72 -0
- data/Gemfile +17 -0
- data/README.md +53 -7
- data/fusuma.gemspec +4 -13
- data/lib/fusuma.rb +91 -29
- data/lib/fusuma/config.rb +59 -60
- data/lib/fusuma/config/index.rb +39 -6
- data/lib/fusuma/config/searcher.rb +166 -0
- data/lib/fusuma/config/yaml_duplication_checker.rb +42 -0
- data/lib/fusuma/custom_process.rb +13 -0
- data/lib/fusuma/device.rb +22 -7
- data/lib/fusuma/environment.rb +6 -4
- data/lib/fusuma/hash_support.rb +40 -0
- data/lib/fusuma/libinput_command.rb +17 -21
- data/lib/fusuma/multi_logger.rb +2 -6
- data/lib/fusuma/plugin/base.rb +18 -15
- data/lib/fusuma/plugin/buffers/buffer.rb +3 -2
- data/lib/fusuma/plugin/buffers/gesture_buffer.rb +34 -25
- data/lib/fusuma/plugin/buffers/timer_buffer.rb +46 -0
- data/lib/fusuma/plugin/detectors/detector.rb +26 -5
- data/lib/fusuma/plugin/detectors/pinch_detector.rb +109 -58
- data/lib/fusuma/plugin/detectors/rotate_detector.rb +91 -50
- data/lib/fusuma/plugin/detectors/swipe_detector.rb +93 -56
- data/lib/fusuma/plugin/events/event.rb +5 -4
- data/lib/fusuma/plugin/events/records/context_record.rb +27 -0
- data/lib/fusuma/plugin/events/records/gesture_record.rb +9 -6
- data/lib/fusuma/plugin/events/records/index_record.rb +46 -14
- data/lib/fusuma/plugin/events/records/record.rb +1 -1
- data/lib/fusuma/plugin/events/records/text_record.rb +2 -1
- data/lib/fusuma/plugin/executors/command_executor.rb +21 -6
- data/lib/fusuma/plugin/executors/executor.rb +45 -3
- data/lib/fusuma/plugin/filters/filter.rb +1 -1
- data/lib/fusuma/plugin/filters/libinput_device_filter.rb +6 -7
- data/lib/fusuma/plugin/filters/libinput_timeout_filter.rb +2 -2
- data/lib/fusuma/plugin/inputs/input.rb +64 -8
- data/lib/fusuma/plugin/inputs/libinput_command_input.rb +19 -9
- data/lib/fusuma/plugin/inputs/timer_input.rb +63 -0
- data/lib/fusuma/plugin/manager.rb +22 -29
- data/lib/fusuma/plugin/parsers/libinput_gesture_parser.rb +10 -8
- data/lib/fusuma/plugin/parsers/parser.rb +8 -9
- data/lib/fusuma/string_support.rb +16 -0
- data/lib/fusuma/version.rb +1 -1
- metadata +21 -150
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative '../base
|
3
|
+
require_relative '../base'
|
4
4
|
|
5
5
|
module Fusuma
|
6
6
|
module Plugin
|
@@ -8,14 +8,56 @@ module Fusuma
|
|
8
8
|
module Executors
|
9
9
|
# Inherite this base
|
10
10
|
class Executor < Base
|
11
|
+
BASE_ONESHOT_INTERVAL = 0.3
|
12
|
+
BASE_REPEAT_INTERVAL = 0.1
|
13
|
+
|
14
|
+
# Executor parameter on config.yml
|
15
|
+
# @return [Array<Symbol>]
|
16
|
+
def execute_keys
|
17
|
+
# [name.split('Executors::').last.underscore.gsub('_executor', '').to_sym]
|
18
|
+
raise NotImplementedError, "override #{name}##{__method__}"
|
19
|
+
end
|
20
|
+
|
11
21
|
# check executable
|
12
|
-
# @param _event [Event]
|
22
|
+
# @param _event [Events::Event]
|
13
23
|
# @return [TrueClass, FalseClass]
|
14
24
|
def executable?(_event)
|
15
25
|
raise NotImplementedError, "override #{self.class.name}##{__method__}"
|
16
26
|
end
|
17
27
|
|
18
|
-
#
|
28
|
+
# @param event [Events::Event]
|
29
|
+
# @param time [Time]
|
30
|
+
# @return [TrueClass, FalseClass]
|
31
|
+
def enough_interval?(event)
|
32
|
+
# NOTE: Cache at the index that is actually used, reflecting Fallback and Skip.
|
33
|
+
# Otherwise, a wrong index will cause invalid intervals.
|
34
|
+
return true if event.record.index.with_context.keys.any? { |key| key.symbol == :end }
|
35
|
+
|
36
|
+
return false if @wait_until && event.time < @wait_until
|
37
|
+
|
38
|
+
true
|
39
|
+
end
|
40
|
+
|
41
|
+
def update_interval(event)
|
42
|
+
@wait_until = event.time + interval(event).to_f
|
43
|
+
end
|
44
|
+
|
45
|
+
def interval(event)
|
46
|
+
@interval_time ||= {}
|
47
|
+
index = event.record.index
|
48
|
+
@interval_time[index.cache_key] ||= begin
|
49
|
+
config_value =
|
50
|
+
Config.search(Config::Index.new([*index.keys, 'interval'])) ||
|
51
|
+
Config.search(Config::Index.new(['interval', Detectors::Detector.type(event.tag)]))
|
52
|
+
if event.record.trigger == :oneshot
|
53
|
+
(config_value || 1) * BASE_ONESHOT_INTERVAL
|
54
|
+
else
|
55
|
+
(config_value || 1) * BASE_REPEAT_INTERVAL
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
# execute something
|
19
61
|
# @param _event [Event]
|
20
62
|
# @return [nil]
|
21
63
|
def execute(_event)
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative './filter
|
4
|
-
require_relative '../../device
|
3
|
+
require_relative './filter'
|
4
|
+
require_relative '../../device'
|
5
5
|
|
6
6
|
module Fusuma
|
7
7
|
module Plugin
|
@@ -25,15 +25,14 @@ module Fusuma
|
|
25
25
|
keep_device.reset
|
26
26
|
return false
|
27
27
|
end
|
28
|
-
|
29
|
-
keep_device.all.map(&:id).any? { |device_id| record.to_s =~ /^\s?#{device_id}\s/ }
|
28
|
+
keep_device.all.map(&:id).any? { |device_id| record.to_s =~ /^[\s-]?#{device_id}\s/ }
|
30
29
|
end
|
31
30
|
|
32
31
|
def keep_device
|
33
32
|
@keep_device ||= begin
|
34
|
-
|
35
|
-
|
36
|
-
|
33
|
+
from_config = Array(config_params(:keep_device_names))
|
34
|
+
KeepDevice.new(name_patterns: from_config)
|
35
|
+
end
|
37
36
|
end
|
38
37
|
|
39
38
|
def config_param_sample
|
@@ -1,22 +1,57 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative '../base
|
4
|
-
require_relative '../events/event
|
3
|
+
require_relative '../base'
|
4
|
+
require_relative '../events/event'
|
5
5
|
|
6
6
|
module Fusuma
|
7
7
|
module Plugin
|
8
|
-
# input class
|
9
8
|
module Inputs
|
10
9
|
# Inherite this base
|
10
|
+
# @abstract Subclass and override {#io} to implement
|
11
11
|
class Input < Base
|
12
|
-
|
12
|
+
# Wait multiple inputs until it becomes readable
|
13
|
+
# and read lines with nonblock
|
14
|
+
# @param inputs [Array<Input>]
|
15
|
+
# @return [Event]
|
16
|
+
def self.select(inputs)
|
17
|
+
ios = IO.select(inputs.map(&:io))
|
18
|
+
io = ios&.first&.first
|
19
|
+
|
20
|
+
input = inputs.find { |i| i.io == io }
|
21
|
+
|
22
|
+
begin
|
23
|
+
line = io.readline_nonblock("\n").chomp
|
24
|
+
rescue EOFError => e
|
25
|
+
warn "#{input.class.name}: #{e}"
|
26
|
+
warn 'Send SIGKILL to fusuma processes'
|
27
|
+
inputs.reject { |i| i == input }.each do |i|
|
28
|
+
warn "stop process: #{i.class.name.underscore}"
|
29
|
+
Process.kill(:SIGKILL, i.pid)
|
30
|
+
end
|
31
|
+
exit 1
|
32
|
+
rescue StandardError => e
|
33
|
+
warn "#{input.class.name}: #{e}"
|
34
|
+
exit 1
|
35
|
+
end
|
36
|
+
|
37
|
+
input.create_event(record: line)
|
38
|
+
end
|
39
|
+
|
40
|
+
# @return [Integer]
|
41
|
+
def pid
|
13
42
|
raise NotImplementedError, "override #{self.class.name}##{__method__}"
|
14
43
|
end
|
15
44
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
45
|
+
# @return [IO]
|
46
|
+
def io
|
47
|
+
raise NotImplementedError, "override #{self.class.name}##{__method__}"
|
48
|
+
end
|
49
|
+
|
50
|
+
# @return [Event]
|
51
|
+
def create_event(record: 'dummy input')
|
52
|
+
e = Events::Event.new(tag: tag, record: record)
|
53
|
+
MultiLogger.debug(input_event: e)
|
54
|
+
e
|
20
55
|
end
|
21
56
|
|
22
57
|
def tag
|
@@ -26,3 +61,24 @@ module Fusuma
|
|
26
61
|
end
|
27
62
|
end
|
28
63
|
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,37 +1,47 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative '../../libinput_command
|
4
|
-
require_relative './input
|
3
|
+
require_relative '../../libinput_command'
|
4
|
+
require_relative './input'
|
5
5
|
|
6
6
|
module Fusuma
|
7
7
|
module Plugin
|
8
8
|
module Inputs
|
9
9
|
# libinput commands wrapper
|
10
10
|
class LibinputCommandInput < Input
|
11
|
+
attr_reader :pid
|
12
|
+
|
11
13
|
def config_param_types
|
12
14
|
{
|
13
|
-
|
15
|
+
device: [String],
|
14
16
|
'enable-dwt': [TrueClass, FalseClass],
|
15
17
|
'enable-tap': [TrueClass, FalseClass],
|
16
18
|
'show-keycodes': [TrueClass, FalseClass],
|
17
|
-
|
19
|
+
verbose: [TrueClass, FalseClass],
|
18
20
|
'libinput-debug-events': [String],
|
19
21
|
'libinput-list-devices': [String]
|
20
22
|
}
|
21
23
|
end
|
22
24
|
|
23
|
-
|
24
|
-
|
25
|
+
# @return [IO]
|
26
|
+
def io
|
27
|
+
@io ||= begin
|
28
|
+
@pid, io = command.debug_events
|
29
|
+
io
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# @return [LibinputCommand]
|
34
|
+
def command
|
35
|
+
@command ||= LibinputCommand.new(
|
25
36
|
libinput_options: libinput_options,
|
26
37
|
commands: {
|
27
38
|
debug_events_command: debug_events_command,
|
28
39
|
list_devices_command: list_devices_command
|
29
40
|
}
|
30
|
-
)
|
31
|
-
yield event(record: line)
|
32
|
-
end
|
41
|
+
)
|
33
42
|
end
|
34
43
|
|
44
|
+
# @return [Array]
|
35
45
|
def libinput_options
|
36
46
|
device = ("--device='#{config_params(:device)}'" if config_params(:device))
|
37
47
|
enable_tap = '--enable-tap' if config_params(:'enable-tap')
|
@@ -0,0 +1,63 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative './input'
|
4
|
+
|
5
|
+
module Fusuma
|
6
|
+
module Plugin
|
7
|
+
module Inputs
|
8
|
+
# libinput commands wrapper
|
9
|
+
class TimerInput < Input
|
10
|
+
DEFAULT_INTERVAL = 0.3
|
11
|
+
def config_param_types
|
12
|
+
{
|
13
|
+
interval: [Float]
|
14
|
+
}
|
15
|
+
end
|
16
|
+
|
17
|
+
attr_reader :pid
|
18
|
+
|
19
|
+
def io
|
20
|
+
@io ||= begin
|
21
|
+
reader, writer = create_io
|
22
|
+
@pid = start(reader, writer)
|
23
|
+
|
24
|
+
reader
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def start(reader, writer)
|
29
|
+
pid = fork do
|
30
|
+
timer_loop(reader, writer)
|
31
|
+
end
|
32
|
+
Process.detach(pid)
|
33
|
+
writer.close
|
34
|
+
pid
|
35
|
+
end
|
36
|
+
|
37
|
+
def timer_loop(reader, writer)
|
38
|
+
reader.close
|
39
|
+
begin
|
40
|
+
loop do
|
41
|
+
sleep interval
|
42
|
+
writer.puts 'timer'
|
43
|
+
end
|
44
|
+
rescue Errno::EPIPE
|
45
|
+
exit 0
|
46
|
+
rescue StandardError => e
|
47
|
+
MultiLogger.error e
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
def create_io
|
54
|
+
IO.pipe
|
55
|
+
end
|
56
|
+
|
57
|
+
def interval
|
58
|
+
config_params(:interval) || DEFAULT_INTERVAL
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
4
|
-
require_relative '../
|
3
|
+
require_relative '../multi_logger'
|
4
|
+
require_relative '../string_support'
|
5
5
|
|
6
6
|
module Fusuma
|
7
7
|
module Plugin
|
@@ -21,8 +21,19 @@ module Fusuma
|
|
21
21
|
def require_siblings_from_gems
|
22
22
|
search_key = File.join(plugin_dir_name, '*.rb')
|
23
23
|
Gem.find_latest_files(search_key).each do |siblings_plugin|
|
24
|
-
|
24
|
+
next unless siblings_plugin =~ %r{fusuma-plugin-(.+).*/lib/#{plugin_dir_name}/\1_.+.rb}
|
25
|
+
|
26
|
+
match_data = siblings_plugin.match(%r{(.*)/(.*)/lib/(.*)})
|
27
|
+
gemspec_path = Dir.glob("#{match_data[1]}/#{match_data[2]}/*.gemspec").first
|
28
|
+
raise "Not Found: #{match_data[1]}/#{match_data[2]}/*.gemspec" unless gemspec_path
|
29
|
+
|
30
|
+
gemspec = Gem::Specification.load gemspec_path
|
31
|
+
fusuma_gemspec = Gem::Specification.load File.expand_path('../../../fusuma.gemspec',
|
32
|
+
__dir__)
|
33
|
+
if gemspec.dependencies.find { |d| d.name == 'fusuma' }&.match?(fusuma_gemspec)
|
25
34
|
require siblings_plugin
|
35
|
+
else
|
36
|
+
MultiLogger.warn "#{gemspec.name} #{gemspec.version} is incompatible with running #{fusuma_gemspec.name} #{fusuma_gemspec.version}"
|
26
37
|
end
|
27
38
|
end
|
28
39
|
end
|
@@ -58,14 +69,14 @@ module Fusuma
|
|
58
69
|
end
|
59
70
|
|
60
71
|
def require_base_plugins
|
61
|
-
require_relative './base
|
62
|
-
require_relative './events/event
|
63
|
-
require_relative './inputs/input
|
64
|
-
require_relative './filters/filter
|
65
|
-
require_relative './parsers/parser
|
66
|
-
require_relative './buffers/buffer
|
67
|
-
require_relative './detectors/detector
|
68
|
-
require_relative './executors/executor
|
72
|
+
require_relative './base'
|
73
|
+
require_relative './events/event'
|
74
|
+
require_relative './inputs/input'
|
75
|
+
require_relative './filters/filter'
|
76
|
+
require_relative './parsers/parser'
|
77
|
+
require_relative './buffers/buffer'
|
78
|
+
require_relative './detectors/detector'
|
79
|
+
require_relative './executors/executor'
|
69
80
|
end
|
70
81
|
|
71
82
|
def plugins
|
@@ -90,21 +101,3 @@ module Fusuma
|
|
90
101
|
end
|
91
102
|
end
|
92
103
|
end
|
93
|
-
|
94
|
-
# support camerize and underscore
|
95
|
-
class String
|
96
|
-
def camerize
|
97
|
-
split('_').map do |w|
|
98
|
-
w[0].upcase!
|
99
|
-
w
|
100
|
-
end.join
|
101
|
-
end
|
102
|
-
|
103
|
-
def underscore
|
104
|
-
gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
|
105
|
-
.gsub(/([a-z\d])([A-Z])/, '\1_\2')
|
106
|
-
.gsub('::', '/')
|
107
|
-
.tr('-', '_')
|
108
|
-
.downcase
|
109
|
-
end
|
110
|
-
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative '../events/records/record
|
4
|
-
require_relative '../events/records/gesture_record
|
3
|
+
require_relative '../events/records/record'
|
4
|
+
require_relative '../events/records/gesture_record'
|
5
5
|
|
6
6
|
module Fusuma
|
7
7
|
module Plugin
|
@@ -15,7 +15,7 @@ module Fusuma
|
|
15
15
|
def parse_record(record)
|
16
16
|
case line = record.to_s
|
17
17
|
when /GESTURE_SWIPE|GESTURE_PINCH/
|
18
|
-
gesture, status, finger,
|
18
|
+
gesture, status, finger, delta = parse_libinput(line)
|
19
19
|
else
|
20
20
|
return
|
21
21
|
end
|
@@ -23,7 +23,7 @@ module Fusuma
|
|
23
23
|
Events::Records::GestureRecord.new(status: status,
|
24
24
|
gesture: gesture,
|
25
25
|
finger: finger,
|
26
|
-
|
26
|
+
delta: delta)
|
27
27
|
end
|
28
28
|
|
29
29
|
private
|
@@ -32,8 +32,8 @@ module Fusuma
|
|
32
32
|
_device, event_name, _time, other = line.strip.split(nil, 4)
|
33
33
|
finger, other = other.split(nil, 2)
|
34
34
|
|
35
|
-
|
36
|
-
[*detect_gesture(event_name), finger,
|
35
|
+
delta = parse_delta(other)
|
36
|
+
[*detect_gesture(event_name), finger, delta]
|
37
37
|
end
|
38
38
|
|
39
39
|
def detect_gesture(event_name)
|
@@ -41,11 +41,13 @@ module Fusuma
|
|
41
41
|
[Regexp.last_match(1).downcase, Regexp.last_match(2).downcase]
|
42
42
|
end
|
43
43
|
|
44
|
-
def
|
44
|
+
def parse_delta(line)
|
45
45
|
return if line.nil?
|
46
46
|
|
47
|
-
move_x, move_y,
|
47
|
+
move_x, move_y, unaccelerated_x, unaccelerated_y, _, zoom, _, rotate =
|
48
|
+
line.tr('/|(|)', ' ').split
|
48
49
|
Events::Records::GestureRecord::Delta.new(move_x.to_f, move_y.to_f,
|
50
|
+
unaccelerated_x.to_f, unaccelerated_y.to_f,
|
49
51
|
zoom.to_f, rotate.to_f)
|
50
52
|
end
|
51
53
|
end
|