fusuma 1.11.1 → 2.0.2
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 +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')
|