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.
Files changed (93) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +49 -7
  3. data/fusuma.gemspec +6 -16
  4. data/lib/fusuma.rb +91 -28
  5. data/lib/fusuma/config.rb +34 -62
  6. data/lib/fusuma/config/index.rb +39 -6
  7. data/lib/fusuma/config/searcher.rb +166 -0
  8. data/lib/fusuma/custom_process.rb +13 -0
  9. data/lib/fusuma/device.rb +22 -7
  10. data/lib/fusuma/environment.rb +6 -4
  11. data/lib/fusuma/hash_support.rb +40 -0
  12. data/lib/fusuma/libinput_command.rb +17 -21
  13. data/lib/fusuma/multi_logger.rb +2 -6
  14. data/lib/fusuma/plugin/base.rb +18 -15
  15. data/lib/fusuma/plugin/buffers/buffer.rb +3 -2
  16. data/lib/fusuma/plugin/buffers/gesture_buffer.rb +34 -25
  17. data/lib/fusuma/plugin/buffers/timer_buffer.rb +46 -0
  18. data/lib/fusuma/plugin/detectors/detector.rb +26 -5
  19. data/lib/fusuma/plugin/detectors/pinch_detector.rb +109 -58
  20. data/lib/fusuma/plugin/detectors/rotate_detector.rb +91 -50
  21. data/lib/fusuma/plugin/detectors/swipe_detector.rb +93 -56
  22. data/lib/fusuma/plugin/events/event.rb +5 -4
  23. data/lib/fusuma/plugin/events/records/context_record.rb +27 -0
  24. data/lib/fusuma/plugin/events/records/gesture_record.rb +9 -6
  25. data/lib/fusuma/plugin/events/records/index_record.rb +46 -14
  26. data/lib/fusuma/plugin/events/records/record.rb +1 -1
  27. data/lib/fusuma/plugin/events/records/text_record.rb +2 -1
  28. data/lib/fusuma/plugin/executors/command_executor.rb +21 -6
  29. data/lib/fusuma/plugin/executors/executor.rb +45 -3
  30. data/lib/fusuma/plugin/filters/filter.rb +1 -1
  31. data/lib/fusuma/plugin/filters/libinput_device_filter.rb +6 -7
  32. data/lib/fusuma/plugin/filters/libinput_timeout_filter.rb +2 -2
  33. data/lib/fusuma/plugin/inputs/input.rb +64 -8
  34. data/lib/fusuma/plugin/inputs/libinput_command_input.rb +19 -9
  35. data/lib/fusuma/plugin/inputs/timer_input.rb +63 -0
  36. data/lib/fusuma/plugin/manager.rb +22 -29
  37. data/lib/fusuma/plugin/parsers/libinput_gesture_parser.rb +10 -8
  38. data/lib/fusuma/plugin/parsers/parser.rb +8 -9
  39. data/lib/fusuma/string_support.rb +16 -0
  40. data/lib/fusuma/version.rb +1 -1
  41. data/spec/helpers/config_helper.rb +20 -0
  42. data/spec/lib/config/searcher_spec.rb +97 -0
  43. data/spec/lib/config_spec.rb +112 -0
  44. data/spec/lib/custom_process_spec.rb +28 -0
  45. data/spec/lib/device_spec.rb +98 -0
  46. data/spec/lib/dummy_config.yml +31 -0
  47. data/spec/lib/fusuma_spec.rb +103 -0
  48. data/spec/lib/libinput-list-devices_iberianpig-XPS-9360.txt +181 -0
  49. data/spec/lib/libinput-list-devices_magic_trackpad.txt +51 -0
  50. data/spec/lib/libinput-list-devices_razer_razer_blade.txt +252 -0
  51. data/spec/lib/libinput-list-devices_thejinx0r.txt +361 -0
  52. data/spec/lib/libinput-list-devices_unavailable.txt +36 -0
  53. data/spec/lib/libinput_command_spec.rb +167 -0
  54. data/spec/lib/plugin/base_spec.rb +74 -0
  55. data/spec/lib/plugin/buffers/buffer_spec.rb +80 -0
  56. data/spec/lib/plugin/buffers/dummy_buffer.rb +20 -0
  57. data/spec/lib/plugin/buffers/gesture_buffer_spec.rb +172 -0
  58. data/spec/lib/plugin/detectors/detector_spec.rb +43 -0
  59. data/spec/lib/plugin/detectors/dummy_detector.rb +24 -0
  60. data/spec/lib/plugin/detectors/pinch_detector_spec.rb +119 -0
  61. data/spec/lib/plugin/detectors/rotate_detector_spec.rb +125 -0
  62. data/spec/lib/plugin/detectors/swipe_detector_spec.rb +118 -0
  63. data/spec/lib/plugin/events/event_spec.rb +30 -0
  64. data/spec/lib/plugin/events/records/gesture_record_spec.rb +22 -0
  65. data/spec/lib/plugin/events/records/record_spec.rb +31 -0
  66. data/spec/lib/plugin/events/records/text_record_spec.rb +26 -0
  67. data/spec/lib/plugin/executors/command_executor_spec.rb +57 -0
  68. data/spec/lib/plugin/executors/executor_spec.rb +160 -0
  69. data/spec/lib/plugin/filters/filter_spec.rb +92 -0
  70. data/spec/lib/plugin/filters/libinput_filter_spec.rb +120 -0
  71. data/spec/lib/plugin/inputs/input_spec.rb +70 -0
  72. data/spec/lib/plugin/inputs/libinput_command_input_spec.rb +120 -0
  73. data/spec/lib/plugin/inputs/timer_input_spec.rb +40 -0
  74. data/spec/lib/plugin/manager_spec.rb +27 -0
  75. data/spec/lib/plugin/parsers/parser_spec.rb +45 -0
  76. data/spec/spec_helper.rb +20 -0
  77. metadata +90 -167
  78. data/.github/FUNDING.yml +0 -8
  79. data/.github/ISSUE_TEMPLATE/bug_report.md +0 -32
  80. data/.github/ISSUE_TEMPLATE/feature_request.md +0 -17
  81. data/.github/pull_request_template.md +0 -9
  82. data/.github/stale.yml +0 -18
  83. data/.gitignore +0 -17
  84. data/.reek.yml +0 -96
  85. data/.rspec +0 -2
  86. data/.rubocop.yml +0 -37
  87. data/.rubocop_todo.yml +0 -40
  88. data/.travis.yml +0 -11
  89. data/CHANGELOG.md +0 -456
  90. data/CODE_OF_CONDUCT.md +0 -74
  91. data/CONTRIBUTING.md +0 -72
  92. data/Gemfile +0 -6
  93. data/Rakefile +0 -15
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative './text_record.rb'
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, :direction
12
+ attr_reader :status, :gesture, :finger, :delta
13
13
 
14
- Delta = Struct.new(:move_x, :move_y, :zoom, :rotate)
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 direction [Delta, NilClass]
20
- def initialize(status:, gesture:, finger:, direction:)
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
- @direction = direction
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
- attr_reader :index
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
- def initialize(index:, position: :body)
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
- def merge(records:)
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
- @index = records.reduce(@index) do |merged_index, record|
31
- case record.position
32
- when :prefix
33
- Config::Index.new([*record.index.keys, *merged_index.keys])
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 '../../base.rb'
3
+ require_relative '../../base'
4
4
 
5
5
  module Fusuma
6
6
  module Plugin
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative './record.rb'
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
- require_relative './executor.rb'
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
- pid = fork do
16
- Process.daemon(true)
17
- exec(command.to_s)
18
- end
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.rb'
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
- # execute somthing
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,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative '../base.rb'
3
+ require_relative '../base'
4
4
 
5
5
  module Fusuma
6
6
  module Plugin
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative './filter.rb'
4
- require_relative '../../device.rb'
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
- from_config = Array(config_params(:keep_device_names))
35
- KeepDevice.new(name_patterns: from_config)
36
- end
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,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative './filter.rb'
4
- require_relative '../../libinput_command.rb'
3
+ require_relative './filter'
4
+ require_relative '../../libinput_command'
5
5
 
6
6
  module Fusuma
7
7
  module Plugin
@@ -1,22 +1,57 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative '../base.rb'
4
- require_relative '../events/event.rb'
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
- def run
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
- def event(record: 'dummy input')
17
- Events::Event.new(tag: tag, record: record).tap do |e|
18
- MultiLogger.debug(input_event: e)
19
- end
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.rb'
4
- require_relative './input.rb'
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
- 'device': [String],
15
+ device: [String],
14
16
  'enable-dwt': [TrueClass, FalseClass],
15
17
  'enable-tap': [TrueClass, FalseClass],
16
18
  'show-keycodes': [TrueClass, FalseClass],
17
- 'verbose': [TrueClass, FalseClass],
19
+ verbose: [TrueClass, FalseClass],
18
20
  'libinput-debug-events': [String],
19
21
  'libinput-list-devices': [String]
20
22
  }
21
23
  end
22
24
 
23
- def run
24
- LibinputCommand.new(
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
- ).debug_events do |line|
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')