fusuma 1.10.2 → 2.0.1

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 (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