fusuma 1.10.0 → 2.0.0.pre2
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/.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 +56 -4
- 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 +164 -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 +5 -4
- data/lib/fusuma/hash_support.rb +40 -0
- data/lib/fusuma/libinput_command.rb +16 -20
- 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 +12 -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 +63 -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 +10 -28
- 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 +20 -149
@@ -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,56 @@
|
|
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
|
+
Process.kill(:SIGKILL, i.pid)
|
29
|
+
end
|
30
|
+
exit 1
|
31
|
+
rescue StandardError => e
|
32
|
+
warn "#{input.class.name}: #{e}"
|
33
|
+
exit 1
|
34
|
+
end
|
35
|
+
|
36
|
+
input.create_event(record: line)
|
37
|
+
end
|
38
|
+
|
39
|
+
# @return [Integer]
|
40
|
+
def pid
|
13
41
|
raise NotImplementedError, "override #{self.class.name}##{__method__}"
|
14
42
|
end
|
15
43
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
44
|
+
# @return [IO]
|
45
|
+
def io
|
46
|
+
raise NotImplementedError, "override #{self.class.name}##{__method__}"
|
47
|
+
end
|
48
|
+
|
49
|
+
# @return [Event]
|
50
|
+
def create_event(record: 'dummy input')
|
51
|
+
e = Events::Event.new(tag: tag, record: record)
|
52
|
+
MultiLogger.debug(input_event: e)
|
53
|
+
e
|
20
54
|
end
|
21
55
|
|
22
56
|
def tag
|
@@ -26,3 +60,24 @@ module Fusuma
|
|
26
60
|
end
|
27
61
|
end
|
28
62
|
end
|
63
|
+
|
64
|
+
# ref: https://github.com/Homebrew/brew/blob/6b2dbbc96f7d8aa12f9b8c9c60107c9cc58befc4/Library/Homebrew/extend/io.rb
|
65
|
+
class IO
|
66
|
+
def readline_nonblock(sep = $INPUT_RECORD_SEPARATOR)
|
67
|
+
line = +''
|
68
|
+
buffer = +''
|
69
|
+
|
70
|
+
loop do
|
71
|
+
break if buffer == sep
|
72
|
+
|
73
|
+
read_nonblock(1, buffer)
|
74
|
+
line.concat(buffer)
|
75
|
+
end
|
76
|
+
|
77
|
+
line.freeze
|
78
|
+
rescue IO::WaitReadable, EOFError => e
|
79
|
+
raise e if line.empty?
|
80
|
+
|
81
|
+
line.freeze
|
82
|
+
end
|
83
|
+
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
|
@@ -58,14 +58,14 @@ module Fusuma
|
|
58
58
|
end
|
59
59
|
|
60
60
|
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
|
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'
|
69
69
|
end
|
70
70
|
|
71
71
|
def plugins
|
@@ -90,21 +90,3 @@ module Fusuma
|
|
90
90
|
end
|
91
91
|
end
|
92
92
|
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
|
@@ -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
|
@@ -13,15 +13,14 @@ module Fusuma
|
|
13
13
|
# @param event [Event]
|
14
14
|
# @return [Event]
|
15
15
|
def parse(event)
|
16
|
-
event.
|
17
|
-
next if e.tag != source
|
16
|
+
return event if event.tag != source
|
18
17
|
|
19
|
-
|
20
|
-
|
18
|
+
new_record = parse_record(event.record)
|
19
|
+
return event if new_record.nil?
|
21
20
|
|
22
|
-
|
23
|
-
|
24
|
-
|
21
|
+
event.record = new_record
|
22
|
+
event.tag = tag
|
23
|
+
event
|
25
24
|
end
|
26
25
|
|
27
26
|
# Set source for tag from config.yml.
|
@@ -31,7 +30,7 @@ module Fusuma
|
|
31
30
|
end
|
32
31
|
|
33
32
|
def tag
|
34
|
-
self.class.name.split('::').last.underscore
|
33
|
+
@tag ||= self.class.name.split('::').last.underscore
|
35
34
|
end
|
36
35
|
|
37
36
|
# parse Record object
|