fusuma 1.10.2 → 2.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (94) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +49 -7
  3. data/lib/fusuma.rb +91 -30
  4. data/lib/fusuma/config.rb +59 -60
  5. data/lib/fusuma/config/index.rb +39 -6
  6. data/lib/fusuma/config/searcher.rb +166 -0
  7. data/lib/fusuma/config/yaml_duplication_checker.rb +42 -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 +91 -168
  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 -33
  87. data/.rubocop_todo.yml +0 -40
  88. data/.travis.yml +0 -11
  89. data/CHANGELOG.md +0 -419
  90. data/CODE_OF_CONDUCT.md +0 -74
  91. data/CONTRIBUTING.md +0 -72
  92. data/Gemfile +0 -6
  93. data/Rakefile +0 -15
  94. data/fusuma.gemspec +0 -38
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: dddc8b3508264aaa617b19ed621ed6ee97c5f0d8435cb988a29a9789f9fe2a22
4
- data.tar.gz: 8c99bd1dbddf7640d9422ed505ac090361b97b87fc0ea77584ddc6fb8f6416db
3
+ metadata.gz: 0b18c602c00e13b108d71c64d02a04798614bb7b4296cc886127c21a128dafd2
4
+ data.tar.gz: b4d146e105abd99d5d4cc4f65dee9da5c9e9f1ad35b4c8ca8815ca64664d9a1c
5
5
  SHA512:
6
- metadata.gz: 107aa86cb7e326e17e7cb5c183e3fcb49a6352fd1f9b6a04211d2820bd6e738b9abb00f2ecddeef31903f2bb77ad15b7dcc742ca835df9eb9f67e970b166e843
7
- data.tar.gz: 294b6a9c9fd0770bf470c38af08f5c48fd7fc14d38f3acd0f802ac3f51e21d200de6d02be1f197edea65c0e5d13ee95f4e43a85c8cea0a0a80372154eda3ea15
6
+ metadata.gz: 4e5bd0ad216bfadd274e3f4a48f96941d27fd80bddf940804228b8a691d8c0180ab7ffd92a2733a3f0e72e5a1ca9ced8797404058cad169aa4a45c3b9ded149d
7
+ data.tar.gz: f7daa7ba96974bdfa6840d4cc081cf1de3883e10d44e65b7f7370966b7822fc86f39d2dcb1af3ab9b1f385a7c44455da98e4a7cf5811c98fef98f0e759a75e8b
data/README.md CHANGED
@@ -17,7 +17,7 @@ This gem makes your linux able to recognize swipes or pinchs and assign commands
17
17
 
18
18
  ## Installation
19
19
 
20
- ### 1. Grant permission to read the touchpad device
20
+ ### Grant permission to read the touchpad device
21
21
 
22
22
  **IMPORTANT**: You **MUST** be a member of the **INPUT** group to read touchpad by Fusuma.
23
23
 
@@ -25,9 +25,15 @@ This gem makes your linux able to recognize swipes or pinchs and assign commands
25
25
  $ sudo gpasswd -a $USER input
26
26
  ```
27
27
 
28
- Then, You **MUST** **REBOOT** to assign this group.
28
+ Then, You apply the change with no logout or reboot.
29
29
 
30
- ### 2. Install libinput-tools
30
+ ```bash
31
+ $ newgrp input
32
+ ```
33
+
34
+ ### For Debian Based Distros (Ubuntu, Debian, Mint, Pop!OS)
35
+
36
+ #### 1. Install libinput-tools
31
37
 
32
38
  You need `libinput` release 1.0 or later.
33
39
 
@@ -35,7 +41,7 @@ You need `libinput` release 1.0 or later.
35
41
  $ sudo apt-get install libinput-tools
36
42
  ```
37
43
 
38
- ### 3. Install Ruby
44
+ #### 2. Install Ruby
39
45
 
40
46
  Fusuma runs in Ruby, so you must install it first.
41
47
 
@@ -43,13 +49,13 @@ Fusuma runs in Ruby, so you must install it first.
43
49
  $ sudo apt-get install ruby
44
50
  ```
45
51
 
46
- ### 4. Install Fusuma
52
+ #### 3. Install Fusuma
47
53
 
48
54
  ```bash
49
55
  $ sudo gem install fusuma
50
56
  ```
51
57
 
52
- ### 5. Install xdotool (optional)
58
+ #### 4. Install xdotool (optional)
53
59
 
54
60
  For sending shortcuts:
55
61
 
@@ -57,6 +63,42 @@ For sending shortcuts:
57
63
  $ sudo apt-get install xdotool
58
64
  ```
59
65
 
66
+ ### For Arch Based Distros (Manjaro, Arch)
67
+
68
+ #### 1. Install libinput.
69
+
70
+ You need `libinput` release 1.0 or later. This is most probably installed by default on Manjaro
71
+
72
+ ```z-h
73
+ $ sudo pacman -S libinput
74
+ ```
75
+
76
+ #### 2. Install Ruby
77
+
78
+ Fusuma runs in Ruby, so you must install it first.
79
+
80
+ ```zsh
81
+ $ sudo pacman -S ruby
82
+ ```
83
+
84
+ #### 3. Install Fusuma
85
+
86
+ **Note:** By default in Arch Linux, when running ```gem```, gems are installed per-user (into ```~/.gem/ruby/```), instead of system-wide (into ```/usr/lib/ruby/gems/```). This is considered the best way to manage gems on Arch, because otherwise they might interfere with gems installed by Pacman. (From Arch Wiki)
87
+
88
+ To install gems system-wide, see any of the methods listed on [Arch Wiki](https://wiki.archlinux.org/index.php/ruby#Installing_gems_system-wide)
89
+
90
+ ```zsh
91
+ $ sudo gem install fusuma
92
+ ```
93
+
94
+ #### 4. Install xdotool (optional)
95
+
96
+ For sending shortcuts:
97
+
98
+ ```zsh
99
+ $ sudo pacman -S xdotool
100
+ ```
101
+
60
102
  ### Touchpad not working in GNOME
61
103
 
62
104
  Ensure the touchpad events are being sent to the GNOME desktop by running the following command:
@@ -242,7 +284,7 @@ swipe:
242
284
  - [ydotool](https://github.com/ReimuNotMoe/ydotool)
243
285
  - Wayland compatible
244
286
  - Needs more maintainers.
245
- - Requires only replacing `xdotool` with `ydotool` in in fusuma conf.
287
+ - Requires only replacing `xdotool` with `ydotool` in fusuma conf.
246
288
 
247
289
  ## Options
248
290
 
data/lib/fusuma.rb CHANGED
@@ -2,10 +2,10 @@
2
2
 
3
3
  require_relative './fusuma/version'
4
4
  require_relative './fusuma/multi_logger'
5
- require_relative './fusuma/config.rb'
6
- require_relative './fusuma/environment.rb'
7
- require_relative './fusuma/device.rb'
8
- require_relative './fusuma/plugin/manager.rb'
5
+ require_relative './fusuma/config'
6
+ require_relative './fusuma/environment'
7
+ require_relative './fusuma/device'
8
+ require_relative './fusuma/plugin/manager'
9
9
 
10
10
  # this is top level module
11
11
  module Fusuma
@@ -16,6 +16,8 @@ module Fusuma
16
16
  set_trap
17
17
  read_options(option)
18
18
  instance = new
19
+ ## NOTE: Uncomment following line to measure performance
20
+ # instance.run_with_lineprof
19
21
  instance.run
20
22
  end
21
23
 
@@ -48,8 +50,6 @@ module Fusuma
48
50
  end
49
51
 
50
52
  def load_custom_config(config_path = nil)
51
- return unless config_path
52
-
53
53
  Config.custom_path = config_path
54
54
  end
55
55
  end
@@ -64,59 +64,120 @@ module Fusuma
64
64
  end
65
65
 
66
66
  def run
67
- # TODO: run with multi thread
68
- @inputs.first.run do |event|
69
- clear_expired_events
70
- filtered = filter(event) || next
71
- parsed = parse(filtered) || next
72
- buffered = buffer(parsed) || next
73
- detected = detect(buffered) || next
74
- merged = merge(detected) || next
75
- execute(merged)
67
+ loop { pipeline }
68
+ end
69
+
70
+ def pipeline
71
+ event = input || return
72
+ clear_expired_events
73
+ filtered = filter(event) || return
74
+ parsed = parse(filtered) || return
75
+ buffered = buffer(parsed) || return
76
+ detected = detect(buffered) || return
77
+ condition, context, event = merge(detected) || return
78
+ execute(condition, context, event)
79
+ end
80
+
81
+ # For performance monitoring
82
+ def run_with_lineprof(count: 1000)
83
+ require 'rblineprof'
84
+ require 'rblineprof-report'
85
+
86
+ profile = lineprof(%r{#{Pathname.new(__FILE__).parent}/.}) do
87
+ count.times { pipeline }
76
88
  end
89
+ LineProf.report(profile)
90
+ exit 0
91
+ end
92
+
93
+ # @return [Plugin::Events::Event]
94
+ def input
95
+ Plugin::Inputs::Input.select(@inputs)
77
96
  end
78
97
 
98
+ # @param [Plugin::Events::Event]
99
+ # @return [Plugin::Events::Event]
79
100
  def filter(event)
80
101
  event if @filters.any? { |f| f.filter(event) }
81
102
  end
82
103
 
104
+ # @param [Plugin::Events::Event]
105
+ # @return [Plugin::Events::Event]
83
106
  def parse(event)
84
107
  @parsers.reduce(event) { |e, p| p.parse(e) if e }
85
108
  end
86
109
 
110
+ # @param [Plugin::Events::Event]
111
+ # @return [Array<Plugin::Buffers::Buffer>]
112
+ # @return [NilClass]
87
113
  def buffer(event)
88
- @buffers.any? { |b| b.buffer(event) } && @buffers
114
+ @buffers.select { |b| b.buffer(event) }
89
115
  end
90
116
 
91
117
  # @param buffers [Array<Buffer>]
92
118
  # @return [Array<Event>]
93
119
  def detect(buffers)
94
- @detectors.each_with_object([]) do |detector, detected|
95
- if (event = detector.detect(buffers))
96
- detected << event
97
- end
120
+ matched_detectors = @detectors.select do |detector|
121
+ detector.watch? ||
122
+ buffers.any? { |b| detector.sources.include?(b.type) }
123
+ end
124
+
125
+ events = matched_detectors.each_with_object([]) do |detector, detected|
126
+ Array(detector.detect(@buffers)).each { |e| detected << e }
98
127
  end
128
+
129
+ return if events.empty?
130
+
131
+ events
99
132
  end
100
133
 
101
- # @param events [Array<Event>]
102
- # @return [Event] a Event merged all records from arguments
134
+ # @param events [Array<Plugin::Events::Event>]
135
+ # @return [Plugin::Events::Event] Event merged all records from arguments
103
136
  # @return [NilClass] when event is NOT given
104
137
  def merge(events)
105
- main_events, modifiers = events.partition { |event| event.record.mergable? }
106
- return nil unless (main_event = main_events.first)
138
+ index_events, context_events = events.partition { |event| event.record.type == :index }
139
+ main_events, modifiers = index_events.partition { |event| event.record.mergable? }
140
+ request_context = context_events.each_with_object({}) do |e, results|
141
+ results[e.record.name] = e.record.value
142
+ end
143
+ main_events.sort_by! { |e| e.record.trigger_priority }
144
+
145
+ condition = nil
146
+ matched_context = nil
147
+ event = main_events.find do |main_event|
148
+ matched_context = Config::Searcher.find_context(request_context) do
149
+ condition, index_record = Config::Searcher.find_condition do
150
+ main_event.record.merge(records: modifiers.map(&:record))
151
+ end
152
+ main_event if index_record
153
+ end
154
+ end
155
+ return if event.nil?
107
156
 
108
- main_event.record.merge(records: modifiers.map(&:record))
109
- main_event
157
+ [condition, matched_context, event]
110
158
  end
111
159
 
112
- def execute(event)
160
+ # @param event [Plugin::Events::Event]
161
+ def execute(condition, context, event)
113
162
  return unless event
114
163
 
115
- executor = @executors.find do |e|
116
- e.executable?(event)
164
+ # Find executable condition and executor
165
+ executor = Config::Searcher.with_context(context) do
166
+ Config::Searcher.with_condition(condition) do
167
+ @executors.find { |e| e.executable?(event) }
168
+ end
117
169
  end
118
170
 
119
- executor&.execute(event)
171
+ return if executor.nil?
172
+
173
+ # Check interval and execute
174
+ Config::Searcher.with_context(context) do
175
+ Config::Searcher.with_condition(condition) do
176
+ executor.enough_interval?(event) &&
177
+ executor.update_interval(event) &&
178
+ executor.execute(event)
179
+ end
180
+ end
120
181
  end
121
182
 
122
183
  def clear_expired_events
data/lib/fusuma/config.rb CHANGED
@@ -1,7 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative './multi_logger.rb'
4
- require_relative './config/index.rb'
3
+ require_relative './multi_logger'
4
+ require_relative './config/index'
5
+ require_relative './config/searcher'
6
+ require_relative './config/yaml_duplication_checker'
7
+ require_relative './hash_support'
5
8
  require 'singleton'
6
9
  require 'yaml'
7
10
 
@@ -9,11 +12,19 @@ require 'yaml'
9
12
  module Fusuma
10
13
  # read keymap from yaml file
11
14
  class Config
15
+ class NotFoundError < StandardError; end
16
+
17
+ class InvalidFileError < StandardError; end
18
+
12
19
  include Singleton
13
20
 
14
21
  class << self
15
- def search(keys)
16
- instance.search(keys)
22
+ def search(index)
23
+ instance.search(index)
24
+ end
25
+
26
+ def find_execute_key(index)
27
+ instance.find_execute_key(index)
17
28
  end
18
29
 
19
30
  def custom_path=(new_path)
@@ -21,14 +32,12 @@ module Fusuma
21
32
  end
22
33
  end
23
34
 
24
- attr_reader :keymap
25
- attr_reader :custom_path
35
+ attr_reader :keymap, :custom_path, :searcher
26
36
 
27
37
  def initialize
38
+ @searcher = Searcher.new
28
39
  @custom_path = nil
29
- @cache = nil
30
40
  @keymap = nil
31
- reload
32
41
  end
33
42
 
34
43
  def custom_path=(new_path)
@@ -37,40 +46,61 @@ module Fusuma
37
46
  end
38
47
 
39
48
  def reload
40
- @cache = nil
41
- @keymap = YAML.load_file(file_path).deep_symbolize_keys
42
- MultiLogger.info "reload config : #{file_path}"
49
+ @searcher = Searcher.new
50
+ path = find_filepath
51
+ MultiLogger.info "reload config: #{path}"
52
+ @keymap = validate(path)
43
53
  self
44
54
  end
45
55
 
56
+ # @return [Hash] If check passes
57
+ # @raise [InvalidFileError] If check does not pass
58
+ def validate(path)
59
+ duplicates = []
60
+ YAMLDuplicationChecker.check(File.read(path), path) do |ignored, duplicate|
61
+ MultiLogger.error "#{path}: #{ignored.value} is duplicated"
62
+ duplicates << duplicate.value
63
+ end
64
+ raise InvalidFileError, "Detect duplicate keys #{duplicates}" unless duplicates.empty?
65
+
66
+ yamls = YAML.load_stream(File.read(path)).compact
67
+ yamls.map(&:deep_symbolize_keys)
68
+ rescue StandardError => e
69
+ MultiLogger.error e.message
70
+ raise InvalidFileError, e.message
71
+ end
72
+
46
73
  # @param index [Index]
74
+ # @param context [Hash]
47
75
  def search(index)
48
- cache(index.cache_key) do
49
- index.keys.reduce(keymap) do |location, key|
50
- if location.is_a?(Hash)
51
- begin
52
- if key.skippable
53
- location.fetch(key.symbol, location)
54
- else
55
- location.fetch(key.symbol, nil)
56
- end
57
- end
58
- else
59
- location
60
- end
61
- end
62
- end
76
+ @searcher.search_with_cache(index, location: keymap)
77
+ end
78
+
79
+ # @param index [Config::Index]
80
+ # @return Symbol
81
+ def find_execute_key(index)
82
+ @execute_keys ||= Plugin::Executors::Executor.plugins.map do |executor|
83
+ executor.new.execute_keys
84
+ end.flatten
85
+
86
+ execute_params = search(index)
87
+ return if execute_params.nil?
88
+
89
+ @execute_keys.find { |k| execute_params.keys.include?(k) }
63
90
  end
64
91
 
65
92
  private
66
93
 
67
- def file_path
94
+ def find_filepath
68
95
  filename = 'fusuma/config.yml'
69
- if custom_path && File.exist?(expand_custom_path)
70
- expand_custom_path
96
+ if custom_path
97
+ return expand_custom_path if File.exist?(expand_custom_path)
98
+
99
+ raise NotFoundError, "#{expand_custom_path} is NOT FOUND"
71
100
  elsif File.exist?(expand_config_path(filename))
72
101
  expand_config_path(filename)
73
102
  else
103
+ MultiLogger.warn "config file: #{expand_config_path(filename)} is NOT FOUND"
74
104
  expand_default_path(filename)
75
105
  end
76
106
  end
@@ -86,36 +116,5 @@ module Fusuma
86
116
  def expand_default_path(filename)
87
117
  File.expand_path "../../#{filename}", __FILE__
88
118
  end
89
-
90
- def cache(key)
91
- @cache ||= {}
92
- key = key.join(',') if key.is_a? Array
93
- if @cache.key?(key)
94
- @cache[key]
95
- else
96
- @cache[key] = block_given? ? yield : nil
97
- end
98
- end
99
- end
100
- end
101
-
102
- # activesupport-4.1.1/lib/active_support/core_ext/hash/keys.rb
103
- class Hash
104
- def deep_symbolize_keys
105
- deep_transform_keys do |key|
106
- begin
107
- key.to_sym
108
- rescue StandardError
109
- key
110
- end
111
- end
112
- end
113
-
114
- def deep_transform_keys(&block)
115
- result = {}
116
- each do |key, value|
117
- result[yield(key)] = value.is_a?(Hash) ? value.deep_transform_keys(&block) : value
118
- end
119
- result
120
119
  end
121
120
  end