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
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative './input'
4
+
5
+ module Fusuma
6
+ module Plugin
7
+ module Inputs
8
+ # libinput commands wrapper
9
+ class TimerInput < Input
10
+ DEFAULT_INTERVAL = 0.3
11
+ def config_param_types
12
+ {
13
+ interval: [Float]
14
+ }
15
+ end
16
+
17
+ attr_reader :pid
18
+
19
+ def io
20
+ @io ||= begin
21
+ reader, writer = create_io
22
+ @pid = start(reader, writer)
23
+
24
+ reader
25
+ end
26
+ end
27
+
28
+ def start(reader, writer)
29
+ pid = fork do
30
+ timer_loop(reader, writer)
31
+ end
32
+ Process.detach(pid)
33
+ writer.close
34
+ pid
35
+ end
36
+
37
+ def timer_loop(reader, writer)
38
+ reader.close
39
+ begin
40
+ loop do
41
+ sleep interval
42
+ writer.puts 'timer'
43
+ end
44
+ rescue Errno::EPIPE
45
+ exit 0
46
+ rescue StandardError => e
47
+ MultiLogger.error e
48
+ end
49
+ end
50
+
51
+ private
52
+
53
+ def create_io
54
+ IO.pipe
55
+ end
56
+
57
+ def interval
58
+ config_params(:interval) || DEFAULT_INTERVAL
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'pathname'
4
- require_relative '../multi_logger.rb'
3
+ require_relative '../multi_logger'
4
+ require_relative '../string_support'
5
5
 
6
6
  module Fusuma
7
7
  module Plugin
@@ -21,8 +21,19 @@ module Fusuma
21
21
  def require_siblings_from_gems
22
22
  search_key = File.join(plugin_dir_name, '*.rb')
23
23
  Gem.find_latest_files(search_key).each do |siblings_plugin|
24
- if siblings_plugin =~ %r{fusuma-plugin-(.+).*/lib/#{plugin_dir_name}/\1_.+.rb}
24
+ next unless siblings_plugin =~ %r{fusuma-plugin-(.+).*/lib/#{plugin_dir_name}/\1_.+.rb}
25
+
26
+ match_data = siblings_plugin.match(%r{(.*)/(.*)/lib/(.*)})
27
+ gemspec_path = Dir.glob("#{match_data[1]}/#{match_data[2]}/*.gemspec").first
28
+ raise "Not Found: #{match_data[1]}/#{match_data[2]}/*.gemspec" unless gemspec_path
29
+
30
+ gemspec = Gem::Specification.load(gemspec_path)
31
+ fusuma_gemspec_path = File.expand_path('../../../fusuma.gemspec', __dir__)
32
+ fusuma_gemspec = Gem::Specification.load(fusuma_gemspec_path)
33
+ if gemspec.dependencies.find { |d| d.name == 'fusuma' }&.match?(fusuma_gemspec)
25
34
  require siblings_plugin
35
+ else
36
+ MultiLogger.warn "#{gemspec.name} #{gemspec.version} is incompatible with running #{fusuma_gemspec.name} #{fusuma_gemspec.version}"
26
37
  end
27
38
  end
28
39
  end
@@ -58,14 +69,14 @@ module Fusuma
58
69
  end
59
70
 
60
71
  def require_base_plugins
61
- require_relative './base.rb'
62
- require_relative './events/event.rb'
63
- require_relative './inputs/input.rb'
64
- require_relative './filters/filter.rb'
65
- require_relative './parsers/parser.rb'
66
- require_relative './buffers/buffer.rb'
67
- require_relative './detectors/detector.rb'
68
- require_relative './executors/executor.rb'
72
+ require_relative './base'
73
+ require_relative './events/event'
74
+ require_relative './inputs/input'
75
+ require_relative './filters/filter'
76
+ require_relative './parsers/parser'
77
+ require_relative './buffers/buffer'
78
+ require_relative './detectors/detector'
79
+ require_relative './executors/executor'
69
80
  end
70
81
 
71
82
  def plugins
@@ -90,21 +101,3 @@ module Fusuma
90
101
  end
91
102
  end
92
103
  end
93
-
94
- # support camerize and underscore
95
- class String
96
- def camerize
97
- split('_').map do |w|
98
- w[0].upcase!
99
- w
100
- end.join
101
- end
102
-
103
- def underscore
104
- gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
105
- .gsub(/([a-z\d])([A-Z])/, '\1_\2')
106
- .gsub('::', '/')
107
- .tr('-', '_')
108
- .downcase
109
- end
110
- end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative '../events/records/record.rb'
4
- require_relative '../events/records/gesture_record.rb'
3
+ require_relative '../events/records/record'
4
+ require_relative '../events/records/gesture_record'
5
5
 
6
6
  module Fusuma
7
7
  module Plugin
@@ -15,7 +15,7 @@ module Fusuma
15
15
  def parse_record(record)
16
16
  case line = record.to_s
17
17
  when /GESTURE_SWIPE|GESTURE_PINCH/
18
- gesture, status, finger, direction = parse_libinput(line)
18
+ gesture, status, finger, delta = parse_libinput(line)
19
19
  else
20
20
  return
21
21
  end
@@ -23,7 +23,7 @@ module Fusuma
23
23
  Events::Records::GestureRecord.new(status: status,
24
24
  gesture: gesture,
25
25
  finger: finger,
26
- direction: direction)
26
+ delta: delta)
27
27
  end
28
28
 
29
29
  private
@@ -32,8 +32,8 @@ module Fusuma
32
32
  _device, event_name, _time, other = line.strip.split(nil, 4)
33
33
  finger, other = other.split(nil, 2)
34
34
 
35
- direction = parse_direction(other)
36
- [*detect_gesture(event_name), finger, direction]
35
+ delta = parse_delta(other)
36
+ [*detect_gesture(event_name), finger, delta]
37
37
  end
38
38
 
39
39
  def detect_gesture(event_name)
@@ -41,11 +41,13 @@ module Fusuma
41
41
  [Regexp.last_match(1).downcase, Regexp.last_match(2).downcase]
42
42
  end
43
43
 
44
- def parse_direction(line)
44
+ def parse_delta(line)
45
45
  return if line.nil?
46
46
 
47
- move_x, move_y, _, _, _, zoom, _, rotate = line.tr('/|(|)', ' ').split
47
+ move_x, move_y, unaccelerated_x, unaccelerated_y, _, zoom, _, rotate =
48
+ line.tr('/|(|)', ' ').split
48
49
  Events::Records::GestureRecord::Delta.new(move_x.to_f, move_y.to_f,
50
+ unaccelerated_x.to_f, unaccelerated_y.to_f,
49
51
  zoom.to_f, rotate.to_f)
50
52
  end
51
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
@@ -13,15 +13,14 @@ module Fusuma
13
13
  # @param event [Event]
14
14
  # @return [Event]
15
15
  def parse(event)
16
- event.tap do |e|
17
- next if e.tag != source
16
+ return event if event.tag != source
18
17
 
19
- new_record = parse_record(e.record)
20
- next unless new_record
18
+ new_record = parse_record(event.record)
19
+ return event if new_record.nil?
21
20
 
22
- e.record = new_record
23
- e.tag = tag
24
- end
21
+ event.record = new_record
22
+ event.tag = tag
23
+ event
25
24
  end
26
25
 
27
26
  # Set source for tag from config.yml.
@@ -31,7 +30,7 @@ module Fusuma
31
30
  end
32
31
 
33
32
  def tag
34
- self.class.name.split('::').last.underscore
33
+ @tag ||= self.class.name.split('::').last.underscore
35
34
  end
36
35
 
37
36
  # parse Record object
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ # support camerize and underscore
4
+ class String
5
+ def camelize
6
+ split('_').map(&:capitalize).join
7
+ end
8
+
9
+ def underscore
10
+ gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
11
+ .gsub(/([a-z\d])([A-Z])/, '\1_\2')
12
+ .gsub('::', '/')
13
+ .tr('-', '_')
14
+ .downcase
15
+ end
16
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Fusuma
4
- VERSION = '1.11.1'
4
+ VERSION = '2.0.2'
5
5
  end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'tempfile'
4
+ require './lib/fusuma/config'
5
+
6
+ module Fusuma
7
+ module ConfigHelper
8
+ module_function
9
+
10
+ def load_config_yml=(string)
11
+ Config.custom_path = Tempfile.open do |temp_file|
12
+ temp_file.tap { |f| f.write(string) }
13
+ end
14
+ end
15
+
16
+ def clear_config_yml
17
+ Config.custom_path = nil
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,97 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+ require './lib/fusuma/config'
5
+ require './lib/fusuma/config/searcher'
6
+
7
+ # spec for Config
8
+ module Fusuma
9
+ RSpec.describe Config::Searcher do
10
+ let(:keymap) do
11
+ {
12
+ 'swipe' => {
13
+ 3 => {
14
+ 'left' => { 'command' => 'alt+Left' },
15
+ 'right' => { 'command' => 'alt+Right' }
16
+ },
17
+ 4 => {
18
+ 'left' => { 'command' => 'super+Left' },
19
+ 'right' => { 'command' => 'super+Right' }
20
+ }
21
+ },
22
+ 'pinch' => {
23
+ 'in' => { 'command' => 'ctrl+plus' },
24
+ 'out' => { 'command' => 'ctrl+minus' }
25
+ }
26
+ }
27
+ end
28
+
29
+ let(:keymap_without_finger) do
30
+ {
31
+ 'swipe' => {
32
+ 'left' => { 'command' => 'alt+Left' }
33
+ }
34
+ }
35
+ end
36
+
37
+ describe '.custom_path=' do
38
+ before { Singleton.__init__(Config) }
39
+ it 'should reload keymap file' do
40
+ keymap = Config.instance.keymap
41
+ Config.custom_path = './spec/lib/dummy_config.yml'
42
+ custom_keymap = Config.instance.keymap
43
+ expect(keymap).not_to eq custom_keymap
44
+ end
45
+ end
46
+
47
+ describe '.search' do
48
+ let(:index) { nil }
49
+ subject { Config::Searcher.new.search(index, location: keymap.deep_symbolize_keys) }
50
+ context 'index correct order' do
51
+ let(:index) { Config::Index.new %w[pinch in command] }
52
+ it { is_expected.to eq 'ctrl+plus' }
53
+ end
54
+
55
+ context 'index include skippable key' do
56
+ let(:index) do
57
+ Config::Index.new [
58
+ Config::Index::Key.new('pinch'),
59
+ Config::Index::Key.new(2, skippable: true),
60
+ Config::Index::Key.new('out'),
61
+ Config::Index::Key.new('command')
62
+ ]
63
+ end
64
+ it { expect(Config::Searcher.skip { subject }).to eq 'ctrl+minus' }
65
+ end
66
+
67
+ context 'index include skippable key at first' do
68
+ let(:index) do
69
+ Config::Index.new [
70
+ Config::Index::Key.new(:hoge, skippable: true),
71
+ Config::Index::Key.new(:fuga, skippable: true),
72
+ Config::Index::Key.new('pinch'),
73
+ Config::Index::Key.new('in'),
74
+ Config::Index::Key.new(:piyo, skippable: true),
75
+ Config::Index::Key.new('command')
76
+ ]
77
+ end
78
+ it { expect(Config::Searcher.skip { subject }).to eq 'ctrl+plus' }
79
+ end
80
+
81
+ context 'index incorrect order' do
82
+ let(:index) { Config::Index.new %w[in pinch 2 command] }
83
+ it { is_expected.not_to eq 'ctrl+plus' }
84
+ end
85
+ end
86
+
87
+ describe 'private_method: :cache' do
88
+ it 'should cache command' do
89
+ key = %w[event_type finger direction command].join(',')
90
+ value = 'shourtcut string'
91
+ searcher = Config::Searcher.new
92
+ searcher.send(:cache, key) { value }
93
+ expect(searcher.send(:cache, key)).to eq value
94
+ end
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,112 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+ require './lib/fusuma/config'
5
+
6
+ # spec for Config
7
+ module Fusuma
8
+ RSpec.describe Config do
9
+ let(:keymap) do
10
+ {
11
+ 'swipe' => {
12
+ 3 => {
13
+ 'left' => { 'command' => 'alt+Left' },
14
+ 'right' => { 'command' => 'alt+Right' }
15
+ },
16
+ 4 => {
17
+ 'left' => { 'command' => 'super+Left' },
18
+ 'right' => { 'command' => 'super+Right' }
19
+ }
20
+ },
21
+ 'pinch' => {
22
+ 'in' => { 'command' => 'ctrl+plus' },
23
+ 'out' => { 'command' => 'ctrl+minus' }
24
+ }
25
+ }
26
+ end
27
+
28
+ let(:keymap_without_finger) do
29
+ {
30
+ 'swipe' => {
31
+ 'left' => { 'command' => 'alt+Left' }
32
+ }
33
+ }
34
+ end
35
+
36
+ describe '.custom_path=' do
37
+ before { Singleton.__init__(Config) }
38
+ it 'should reload keymap file' do
39
+ keymap = Config.instance.keymap
40
+ Config.custom_path = './spec/lib/dummy_config.yml'
41
+ custom_keymap = Config.instance.keymap
42
+ expect(keymap).not_to eq custom_keymap
43
+ end
44
+ end
45
+
46
+ describe '#reload' do
47
+ before { Singleton.__init__(Config) }
48
+ it 'set Seacher' do
49
+ old = Config.instance.searcher
50
+ Config.instance.reload
51
+ expect(Config.instance.searcher).not_to eq(old)
52
+ end
53
+ end
54
+
55
+ describe '#validate' do
56
+ context 'with valid yaml' do
57
+ before do
58
+ string = <<~CONFIG
59
+ swipe:
60
+ 3:
61
+ left:
62
+ command: echo 'swipe left'
63
+
64
+ CONFIG
65
+ @file_path = Tempfile.open do |temp_file|
66
+ temp_file.tap { |f| f.write(string) }
67
+ end
68
+ end
69
+
70
+ it 'should return Hash' do
71
+ Config.instance.validate(@file_path)
72
+ end
73
+ end
74
+
75
+ context 'with invalid yaml' do
76
+ before do
77
+ string = <<~CONFIG
78
+ this is not yaml
79
+ CONFIG
80
+ @file_path = Tempfile.open do |temp_file|
81
+ temp_file.tap { |f| f.write(string) }
82
+ end
83
+ end
84
+
85
+ it 'raise InvalidFileError' do
86
+ expect { Config.instance.validate(@file_path) }.to raise_error(Config::InvalidFileError)
87
+ end
88
+
89
+ context 'with duplicated key' do
90
+ before do
91
+ string = <<~CONFIG
92
+ pinch:
93
+ 2:
94
+ in:
95
+ command: "xdotool keydown ctrl click 4 keyup ctrl" # threshold: 0.5, interval: 0.5
96
+ 2:
97
+ out:
98
+ command: "xdotool keydown ctrl click 5 keyup ctrl" # threshold: 0.5, interval: 0.5
99
+ CONFIG
100
+ @file_path = Tempfile.open do |temp_file|
101
+ temp_file.tap { |f| f.write(string) }
102
+ end
103
+ end
104
+
105
+ it 'raise InvalidFileError' do
106
+ expect { Config.instance.validate(@file_path) }.to raise_error(Config::InvalidFileError)
107
+ end
108
+ end
109
+ end
110
+ end
111
+ end
112
+ end