fusuma 1.11.1 → 2.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +49 -7
- data/fusuma.gemspec +6 -16
- data/lib/fusuma.rb +91 -28
- data/lib/fusuma/config.rb +34 -62
- data/lib/fusuma/config/index.rb +39 -6
- data/lib/fusuma/config/searcher.rb +166 -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
- data/spec/helpers/config_helper.rb +20 -0
- data/spec/lib/config/searcher_spec.rb +97 -0
- data/spec/lib/config_spec.rb +112 -0
- data/spec/lib/custom_process_spec.rb +28 -0
- data/spec/lib/device_spec.rb +98 -0
- data/spec/lib/dummy_config.yml +31 -0
- data/spec/lib/fusuma_spec.rb +103 -0
- data/spec/lib/libinput-list-devices_iberianpig-XPS-9360.txt +181 -0
- data/spec/lib/libinput-list-devices_magic_trackpad.txt +51 -0
- data/spec/lib/libinput-list-devices_razer_razer_blade.txt +252 -0
- data/spec/lib/libinput-list-devices_thejinx0r.txt +361 -0
- data/spec/lib/libinput-list-devices_unavailable.txt +36 -0
- data/spec/lib/libinput_command_spec.rb +167 -0
- data/spec/lib/plugin/base_spec.rb +74 -0
- data/spec/lib/plugin/buffers/buffer_spec.rb +80 -0
- data/spec/lib/plugin/buffers/dummy_buffer.rb +20 -0
- data/spec/lib/plugin/buffers/gesture_buffer_spec.rb +172 -0
- data/spec/lib/plugin/detectors/detector_spec.rb +43 -0
- data/spec/lib/plugin/detectors/dummy_detector.rb +24 -0
- data/spec/lib/plugin/detectors/pinch_detector_spec.rb +119 -0
- data/spec/lib/plugin/detectors/rotate_detector_spec.rb +125 -0
- data/spec/lib/plugin/detectors/swipe_detector_spec.rb +118 -0
- data/spec/lib/plugin/events/event_spec.rb +30 -0
- data/spec/lib/plugin/events/records/gesture_record_spec.rb +22 -0
- data/spec/lib/plugin/events/records/record_spec.rb +31 -0
- data/spec/lib/plugin/events/records/text_record_spec.rb +26 -0
- data/spec/lib/plugin/executors/command_executor_spec.rb +57 -0
- data/spec/lib/plugin/executors/executor_spec.rb +160 -0
- data/spec/lib/plugin/filters/filter_spec.rb +92 -0
- data/spec/lib/plugin/filters/libinput_filter_spec.rb +120 -0
- data/spec/lib/plugin/inputs/input_spec.rb +70 -0
- data/spec/lib/plugin/inputs/libinput_command_input_spec.rb +120 -0
- data/spec/lib/plugin/inputs/timer_input_spec.rb +40 -0
- data/spec/lib/plugin/manager_spec.rb +27 -0
- data/spec/lib/plugin/parsers/parser_spec.rb +45 -0
- data/spec/spec_helper.rb +20 -0
- metadata +90 -167
- data/.github/FUNDING.yml +0 -8
- data/.github/ISSUE_TEMPLATE/bug_report.md +0 -32
- data/.github/ISSUE_TEMPLATE/feature_request.md +0 -17
- data/.github/pull_request_template.md +0 -9
- data/.github/stale.yml +0 -18
- data/.gitignore +0 -17
- data/.reek.yml +0 -96
- data/.rspec +0 -2
- data/.rubocop.yml +0 -37
- data/.rubocop_todo.yml +0 -40
- data/.travis.yml +0 -11
- data/CHANGELOG.md +0 -456
- data/CODE_OF_CONDUCT.md +0 -74
- data/CONTRIBUTING.md +0 -72
- data/Gemfile +0 -6
- data/Rakefile +0 -15
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative './text_record
|
3
|
+
require_relative './text_record'
|
4
4
|
|
5
5
|
module Fusuma
|
6
6
|
module Plugin
|
@@ -9,19 +9,22 @@ module Fusuma
|
|
9
9
|
# Gesture Record
|
10
10
|
class GestureRecord < Record
|
11
11
|
# define gesture format
|
12
|
-
attr_reader :status, :gesture, :finger, :
|
12
|
+
attr_reader :status, :gesture, :finger, :delta
|
13
13
|
|
14
|
-
Delta = Struct.new(:move_x, :move_y,
|
14
|
+
Delta = Struct.new(:move_x, :move_y,
|
15
|
+
:unaccelerated_x, :unaccelerated_y,
|
16
|
+
:zoom, :rotate)
|
15
17
|
|
16
18
|
# @param status [String]
|
17
19
|
# @param gesture [String]
|
18
20
|
# @param finger [String, Integer]
|
19
|
-
# @param
|
20
|
-
def initialize(status:, gesture:, finger:,
|
21
|
+
# @param delta [Delta, NilClass]
|
22
|
+
def initialize(status:, gesture:, finger:, delta:)
|
23
|
+
super()
|
21
24
|
@status = status
|
22
25
|
@gesture = gesture
|
23
26
|
@finger = finger.to_i
|
24
|
-
@
|
27
|
+
@delta = delta
|
25
28
|
end
|
26
29
|
end
|
27
30
|
end
|
@@ -8,36 +8,68 @@ module Fusuma
|
|
8
8
|
# have index
|
9
9
|
class IndexRecord < Record
|
10
10
|
# define gesture format
|
11
|
-
|
12
|
-
attr_reader :position
|
11
|
+
attr_accessor :index
|
12
|
+
attr_reader :position, :trigger, :args
|
13
13
|
|
14
14
|
# @param [Config::Index] index
|
15
15
|
# @param [Symbol] position [:prefix, :body, :surfix]
|
16
|
-
|
16
|
+
# @param [Symbol] trigger [:oneshot, :repeat]
|
17
|
+
def initialize(index:, position: :body, trigger: :oneshot, args: {})
|
18
|
+
super()
|
17
19
|
@index = index
|
18
20
|
@position = position
|
21
|
+
@trigger = trigger
|
22
|
+
@args = args
|
19
23
|
end
|
20
24
|
|
21
25
|
def type
|
22
26
|
:index
|
23
27
|
end
|
24
28
|
|
29
|
+
# FIXME: move to Config::Index
|
25
30
|
# @param records [Array<IndexRecord>]
|
26
|
-
# @return [IndexRecord]
|
27
|
-
|
31
|
+
# @return [IndexRecord] when merge is succeeded
|
32
|
+
# @return [NilClass] when merge is not succeeded
|
33
|
+
def merge(records:, index: @index)
|
34
|
+
# FIXME: cache
|
28
35
|
raise "position is NOT body: #{self}" unless mergable?
|
29
36
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
when :surfix
|
35
|
-
Config::Index.new([*merged_index.keys, *record.index.keys])
|
36
|
-
else
|
37
|
-
raise "invalid index position: #{record}"
|
37
|
+
if records.empty?
|
38
|
+
if Config.find_execute_key(index)
|
39
|
+
@index = index
|
40
|
+
return self
|
38
41
|
end
|
42
|
+
return nil
|
43
|
+
end
|
44
|
+
|
45
|
+
record = records.shift
|
46
|
+
new_index = case record.position
|
47
|
+
when :surfix
|
48
|
+
Config::Index.new([*index.keys, *record.index.keys])
|
49
|
+
else
|
50
|
+
raise "invalid index position: #{record}"
|
51
|
+
end
|
52
|
+
|
53
|
+
return unless exist_on_conf?(new_index)
|
54
|
+
|
55
|
+
merge(records: records, index: new_index)
|
56
|
+
end
|
57
|
+
|
58
|
+
# @param [Config::Searcher] searcher
|
59
|
+
def exist_on_conf?(index = @index)
|
60
|
+
Config.search(index)
|
61
|
+
end
|
62
|
+
|
63
|
+
# @return [Integer]
|
64
|
+
def trigger_priority
|
65
|
+
case @trigger
|
66
|
+
when :oneshot
|
67
|
+
10
|
68
|
+
when :repeat
|
69
|
+
100
|
70
|
+
else
|
71
|
+
1000
|
39
72
|
end
|
40
|
-
self
|
41
73
|
end
|
42
74
|
|
43
75
|
def mergable?
|
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative './record
|
3
|
+
require_relative './record'
|
4
4
|
|
5
5
|
module Fusuma
|
6
6
|
module Plugin
|
@@ -10,6 +10,7 @@ module Fusuma
|
|
10
10
|
class TextRecord < Record
|
11
11
|
# @param text [String]
|
12
12
|
def initialize(text)
|
13
|
+
super()
|
13
14
|
@text = text
|
14
15
|
end
|
15
16
|
|
@@ -1,22 +1,30 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
3
|
+
require 'posix/spawn'
|
4
|
+
require_relative './executor'
|
4
5
|
|
5
6
|
module Fusuma
|
6
7
|
module Plugin
|
7
8
|
module Executors
|
8
9
|
# Exector plugin
|
9
10
|
class CommandExecutor < Executor
|
11
|
+
# Executor parameter on config.yml
|
12
|
+
# @return [Array<Symbol>]
|
13
|
+
def execute_keys
|
14
|
+
[:command]
|
15
|
+
end
|
16
|
+
|
10
17
|
def execute(event)
|
11
18
|
search_command(event).tap do |command|
|
12
19
|
break unless command
|
13
20
|
|
14
|
-
MultiLogger.info(command: command)
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
21
|
+
MultiLogger.info(command: command, args: event.record.args)
|
22
|
+
|
23
|
+
additional_env = event.record.args
|
24
|
+
.deep_transform_keys(&:to_s)
|
25
|
+
.deep_transform_values { |v| (v * args_accel(event)).to_s }
|
19
26
|
|
27
|
+
pid = POSIX::Spawn.spawn(additional_env, command.to_s)
|
20
28
|
Process.detach(pid)
|
21
29
|
end
|
22
30
|
end
|
@@ -33,6 +41,13 @@ module Fusuma
|
|
33
41
|
command_index = Config::Index.new([*event.record.index.keys, :command])
|
34
42
|
Config.search(command_index)
|
35
43
|
end
|
44
|
+
|
45
|
+
# @param event [Event]
|
46
|
+
# @return [Float]
|
47
|
+
def args_accel(event)
|
48
|
+
accel_index = Config::Index.new([*event.record.index.keys, :accel])
|
49
|
+
(Config.search(accel_index) || 1).to_f
|
50
|
+
end
|
36
51
|
end
|
37
52
|
end
|
38
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
|
@@ -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')
|