fusuma 2.0.4 → 2.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -24,9 +24,17 @@ module Fusuma
24
24
  updating_events = gesture_buffer.updating_events
25
25
  return if updating_events.empty?
26
26
 
27
- updating_time = 100 * (updating_events.last.time - updating_events.first.time)
28
- oneshot_move_x = gesture_buffer.sum_attrs(:move_x) / updating_time
29
- oneshot_move_y = gesture_buffer.sum_attrs(:move_y) / updating_time
27
+ oneshot_move_x, oneshot_move_y = if updating_events.size >= 10
28
+ updating_time = 100 * (updating_events[-1].time - updating_events[-10].time)
29
+ last_10 = gesture_buffer.class.new(updating_events[-10..-1])
30
+ [last_10.sum_attrs(:move_x) / updating_time,
31
+ last_10.sum_attrs(:move_y) / updating_time]
32
+ else
33
+ updating_time = 100 * (updating_events.last.time - updating_events.first.time)
34
+ [gesture_buffer.sum_attrs(:move_x) / updating_time,
35
+ gesture_buffer.sum_attrs(:move_y) / updating_time]
36
+ end
37
+ gesture_buffer.sum_attrs(:move_x) / updating_time
30
38
 
31
39
  finger = gesture_buffer.finger
32
40
  status = case gesture_buffer.events.last.record.status
@@ -15,7 +15,7 @@ module Fusuma
15
15
  # @return [Array<Symbol>]
16
16
  def execute_keys
17
17
  # [name.split('Executors::').last.underscore.gsub('_executor', '').to_sym]
18
- raise NotImplementedError, "override #{name}##{__method__}"
18
+ raise NotImplementedError, "override #{self.class.name}##{__method__}"
19
19
  end
20
20
 
21
21
  # check executable
@@ -14,7 +14,7 @@ module Fusuma
14
14
  # @return [Records::GestureRecord, nil]
15
15
  def parse_record(record)
16
16
  case line = record.to_s
17
- when /GESTURE_SWIPE|GESTURE_PINCH/
17
+ when /GESTURE_SWIPE|GESTURE_PINCH|GESTURE_HOLD/
18
18
  gesture, status, finger, delta = parse_libinput(line)
19
19
  else
20
20
  return
@@ -32,13 +32,18 @@ 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
+ gesture, status = *detect_gesture(event_name)
36
+
37
+ status = 'cancelled' if gesture == 'hold' && status == 'end' && other == 'cancelled'
35
38
  delta = parse_delta(other)
36
- [*detect_gesture(event_name), finger, delta]
39
+ [gesture, status, finger, delta]
37
40
  end
38
41
 
39
42
  def detect_gesture(event_name)
40
- event_name =~ /GESTURE_(SWIPE|PINCH)_(BEGIN|UPDATE|END)/
41
- [Regexp.last_match(1).downcase, Regexp.last_match(2).downcase]
43
+ event_name =~ /GESTURE_(SWIPE|PINCH|HOLD)_(BEGIN|UPDATE|END)/
44
+ gesture = Regexp.last_match(1).downcase
45
+ status = Regexp.last_match(2).downcase
46
+ [gesture, status]
42
47
  end
43
48
 
44
49
  def parse_delta(line)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Fusuma
4
- VERSION = '2.0.4'
4
+ VERSION = '2.3.0'
5
5
  end
data/lib/fusuma.rb CHANGED
@@ -29,6 +29,7 @@ module Fusuma
29
29
  end
30
30
 
31
31
  def read_options(option)
32
+ MultiLogger.filepath = option[:log_filepath]
32
33
  MultiLogger.instance.debug_mode = option[:verbose]
33
34
 
34
35
  load_custom_config(option[:config_path])
@@ -142,19 +143,26 @@ module Fusuma
142
143
  end
143
144
  main_events.sort_by! { |e| e.record.trigger_priority }
144
145
 
145
- condition = nil
146
+ matched_condition = nil
146
147
  matched_context = nil
147
148
  event = main_events.find do |main_event|
148
149
  matched_context = Config::Searcher.find_context(request_context) do
149
- condition, index_record = Config::Searcher.find_condition do
150
+ matched_condition, modified_record = Config::Searcher.find_condition do
150
151
  main_event.record.merge(records: modifiers.map(&:record))
151
152
  end
152
- main_event if index_record
153
+ if matched_condition && modified_record
154
+ main_event.record = modified_record
155
+ else
156
+ matched_condition, = Config::Searcher.find_condition do
157
+ Config.search(main_event.record.index) &&
158
+ Config.find_execute_key(main_event.record.index)
159
+ end
160
+ end
153
161
  end
154
162
  end
155
163
  return if event.nil?
156
164
 
157
- [condition, matched_context, event]
165
+ [matched_condition, matched_context, event]
158
166
  end
159
167
 
160
168
  # @param event [Plugin::Events::Event]
@@ -7,80 +7,162 @@ require './lib/fusuma/config/searcher'
7
7
  # spec for Config
8
8
  module Fusuma
9
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
10
+ around do |example|
11
+ ConfigHelper.load_config_yml = <<~CONFIG
12
+ swipe:
13
+ 3:
14
+ left:
15
+ command: 'alt+Left'
16
+ right:
17
+ command: 'alt+Right'
18
+ 4:
19
+ left:
20
+ command: 'super+Left'
21
+ right:
22
+ command: 'super+Right'
23
+ pinch:
24
+ in:
25
+ command: 'ctrl+plus'
26
+ out:
27
+ command: 'ctrl+minus'
28
+ CONFIG
28
29
 
29
- let(:keymap_without_finger) do
30
- {
31
- 'swipe' => {
32
- 'left' => { 'command' => 'alt+Left' }
33
- }
34
- }
35
- end
30
+ example.run
36
31
 
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
32
+ ConfigHelper.clear_config_yml
45
33
  end
46
34
 
47
35
  describe '.search' do
48
36
  let(:index) { nil }
49
- subject { Config::Searcher.new.search(index, location: keymap.deep_symbolize_keys) }
37
+ let(:location) { Config.instance.keymap[0] }
38
+ let(:search) { Config::Searcher.new.search(index, location: location) }
50
39
  context 'index correct order' do
51
40
  let(:index) { Config::Index.new %w[pinch in command] }
52
- it { is_expected.to eq 'ctrl+plus' }
41
+ it { expect(Config::Searcher.new.search(index, location: location)).to eq 'ctrl+plus' }
53
42
  end
54
43
 
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' }
44
+ context 'index incorrect order' do
45
+ let(:index) { Config::Index.new %w[in pinch 2 command] }
46
+ it { expect(Config::Searcher.new.search(index, location: location)).not_to eq 'ctrl+plus' }
65
47
  end
66
48
 
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
- ]
49
+ context 'with Skip condtions' do
50
+ context 'when index includes skippable key' do
51
+ let(:index) do
52
+ Config::Index.new [
53
+ Config::Index::Key.new('pinch'),
54
+ Config::Index::Key.new(2, skippable: true),
55
+ Config::Index::Key.new('out'),
56
+ Config::Index::Key.new('command')
57
+ ]
58
+ end
59
+ it 'detects ctrl+minus with skip' do
60
+ condition, value = Config::Searcher.find_condition do
61
+ Config::Searcher.new.search(index, location: location)
62
+ end
63
+ expect([condition, value]).to eq([:skip, 'ctrl+minus'])
64
+ end
77
65
  end
78
- it { expect(Config::Searcher.skip { subject }).to eq 'ctrl+plus' }
79
- end
80
66
 
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' }
67
+ context 'when index includes 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 'detects ctrl+plus with skip' do
79
+ condition, value = Config::Searcher.find_condition do
80
+ Config::Searcher.new.search(index, location: location)
81
+ end
82
+ expect([condition, value]).to eq([:skip, 'ctrl+plus'])
83
+ end
84
+ end
85
+
86
+ context 'with begin/update/end' do
87
+ around do |example|
88
+ ConfigHelper.load_config_yml = <<~CONFIG
89
+ swipe:
90
+ 3:
91
+ begin:
92
+ command: 'echo begin'
93
+ update:
94
+ command: 'echo update'
95
+ end:
96
+ command: 'echo end'
97
+ keypress:
98
+ LEFTCTRL:
99
+ command: 'echo end+ctrl'
100
+ CONFIG
101
+
102
+ example.run
103
+
104
+ ConfigHelper.clear_config_yml
105
+ end
106
+
107
+ context 'without keypress' do
108
+ let(:index) do
109
+ Config::Index.new [
110
+ Config::Index::Key.new(:swipe),
111
+ Config::Index::Key.new(3),
112
+ Config::Index::Key.new('left', skippable: true),
113
+ Config::Index::Key.new('end'),
114
+ Config::Index::Key.new('command')
115
+ ]
116
+ end
117
+
118
+ it 'detects with skip' do
119
+ condition, value = Config::Searcher.find_condition do
120
+ Config::Searcher.new.search(index, location: location)
121
+ end
122
+ expect([condition, value]).to eq([:skip, 'echo end'])
123
+ end
124
+ end
125
+ context 'with keypress' do
126
+ context 'with valid key existing in config.yml' do
127
+ let(:index) do
128
+ Config::Index.new [
129
+ Config::Index::Key.new(:swipe),
130
+ Config::Index::Key.new(3),
131
+ Config::Index::Key.new('left', skippable: true),
132
+ Config::Index::Key.new('end'),
133
+ Config::Index::Key.new('keypress', skippable: true),
134
+ Config::Index::Key.new('LEFTCTRL', skippable: true),
135
+ Config::Index::Key.new('command')
136
+ ]
137
+ end
138
+ it 'detects end+ctrl with skip' do
139
+ condition, value = Config::Searcher.find_condition do
140
+ Config::Searcher.new.search(index, location: location)
141
+ end
142
+ expect([condition, value]).to eq([:skip, 'echo end+ctrl'])
143
+ end
144
+ end
145
+ context 'with non-existing key not existing in config.yml' do
146
+ let(:index) do
147
+ Config::Index.new [
148
+ Config::Index::Key.new(:swipe),
149
+ Config::Index::Key.new(3),
150
+ Config::Index::Key.new('up', skippable: true),
151
+ Config::Index::Key.new('end'),
152
+ Config::Index::Key.new('keypress', skippable: true),
153
+ Config::Index::Key.new('LEFTSHIFT', skippable: true), # Invalid key
154
+ Config::Index::Key.new('command')
155
+ ]
156
+ end
157
+ it 'detects end with skip (fallback to no keypress)' do
158
+ condition, value = Config::Searcher.find_condition do
159
+ Config::Searcher.new.search(index, location: location)
160
+ end
161
+ expect([condition, value]).to eq([:skip, 'echo end'])
162
+ end
163
+ end
164
+ end
165
+ end
84
166
  end
85
167
  end
86
168
 
@@ -25,14 +25,6 @@ module Fusuma
25
25
  }
26
26
  end
27
27
 
28
- let(:keymap_without_finger) do
29
- {
30
- 'swipe' => {
31
- 'left' => { 'command' => 'alt+Left' }
32
- }
33
- }
34
- end
35
-
36
28
  describe '.custom_path=' do
37
29
  before { Singleton.__init__(Config) }
38
30
  it 'should reload keymap file' do
@@ -68,7 +68,7 @@ module Fusuma
68
68
  context 'without NEW_CLI_OPTION_VERSION' do
69
69
  before do
70
70
  allow(libinput_command).to receive(:version)
71
- .and_return(LibinputCommand::NEW_CLI_OPTION_VERSION - 0.1)
71
+ .and_return('1.7')
72
72
  end
73
73
  it { is_expected.to eq false }
74
74
  end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'spec_helper'
4
+ require 'rspec-parameterized'
4
5
 
5
6
  require './lib/fusuma/plugin/events/event'
6
7
  require './lib/fusuma/plugin/events/records/gesture_record'
@@ -13,7 +14,7 @@ module Fusuma
13
14
  before do
14
15
  @buffer = GestureBuffer.new
15
16
  delta = Events::Records::GestureRecord::Delta.new(-1, 0, 0, 0, 0, 0)
16
- @event_generator = lambda { |time = nil, status = 'updating'|
17
+ @event_generator = lambda { |time = nil, status = 'update'|
17
18
  Events::Event.new(time: time,
18
19
  tag: 'libinput_gesture_parser',
19
20
  record: Events::Records::GestureRecord.new(
@@ -53,20 +54,39 @@ module Fusuma
53
54
  end
54
55
 
55
56
  describe '#clear_expired' do
56
- it 'should keep only events generated within 30 seconds' do
57
- time = Time.now
58
- event1 = @event_generator.call(time)
59
- @buffer.buffer(event1)
60
- event2 = @event_generator.call(time + 100)
61
- event3 = @event_generator.call(time + 100)
62
- @buffer.buffer(event2)
63
- @buffer.buffer(event3)
64
-
65
- @buffer.clear_expired(current_time: time + 100.1)
66
-
67
- expect(@buffer.events).to eq [event2, event3]
57
+ context 'default' do
58
+ before do
59
+ @time = Time.now
60
+ event1 = @event_generator.call(@time)
61
+ @buffer.buffer(event1)
62
+ @event2 = @event_generator.call(@time + 100)
63
+ @event3 = @event_generator.call(@time + 100)
64
+ @buffer.buffer(@event2)
65
+ @buffer.buffer(@event3)
66
+ end
67
+ it 'should keep only events generated within 30 seconds' do
68
+ @buffer.clear_expired(current_time: @time + 100.1)
69
+ expect(@buffer.events).to eq [@event2, @event3]
70
+ end
71
+ context 'with cancelled or ended' do
72
+ where(:last_state, :want) do
73
+ [
74
+ ['end', []],
75
+ ['cancelled', []]
76
+ ]
77
+ end
78
+
79
+ with_them do
80
+ it 'should clear events' do
81
+ event4 = @event_generator.call(@time + 100, last_state)
82
+ @buffer.buffer(event4)
83
+
84
+ @buffer.clear_expired(current_time: @time + 100.1)
85
+ expect(@buffer.events).to eq want
86
+ end
87
+ end
88
+ end
68
89
  end
69
-
70
90
  context 'change seconds to keep' do
71
91
  around do |example|
72
92
  ConfigHelper.load_config_yml = <<~CONFIG
@@ -27,7 +27,7 @@ module Fusuma
27
27
 
28
28
  example.run
29
29
 
30
- Config.custom_path = nil
30
+ ConfigHelper.clear_config_yml
31
31
  end
32
32
 
33
33
  describe '#detect' do
@@ -0,0 +1,145 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ require './lib/fusuma/plugin/detectors/hold_detector'
6
+ require './lib/fusuma/plugin/buffers/gesture_buffer'
7
+ require './lib/fusuma/plugin/events/records/gesture_record'
8
+ require './lib/fusuma/config'
9
+
10
+ module Fusuma
11
+ module Plugin
12
+ module Detectors
13
+ RSpec.describe HoldDetector do
14
+ before do
15
+ @detector = HoldDetector.new
16
+ @buffer = Buffers::GestureBuffer.new
17
+ @timer_buffer = Buffers::TimerBuffer.new
18
+ end
19
+
20
+ around do |example|
21
+ ConfigHelper.load_config_yml = <<~CONFIG
22
+ threshold:
23
+ hold: 1
24
+ CONFIG
25
+
26
+ example.run
27
+
28
+ Config.custom_path = nil
29
+ end
30
+
31
+ describe '#detect' do
32
+ context 'with no hold event in buffer' do
33
+ before do
34
+ @buffer.clear
35
+ end
36
+ it { expect(@detector.detect([@buffer, @timer_buffer])).to eq nil }
37
+ end
38
+
39
+ context 'with only hold begin event' do
40
+ before do
41
+ events = create_hold_events(statuses: ['begin'])
42
+ events.each { |event| @buffer.buffer(event) }
43
+ end
44
+ it { expect(@detector.detect([@buffer, @timer_buffer])).to be_a Events::Event }
45
+ it { expect(@detector.detect([@buffer, @timer_buffer]).record).to be_a Events::Records::IndexRecord }
46
+ it { expect(@detector.detect([@buffer, @timer_buffer]).record.index).to be_a Config::Index }
47
+ it 'should detect 3 fingers hold' do
48
+ event = @detector.detect([@buffer, @timer_buffer])
49
+ expect(event.record.index.keys.map(&:symbol)).to eq([:hold, 3, :begin])
50
+ end
51
+ end
52
+
53
+ context 'with hold events(begin,cancelled)' do
54
+ before do
55
+ events = create_hold_events(statuses: %w[begin cancelled])
56
+ events.each { |event| @buffer.buffer(event) }
57
+ end
58
+ it { expect(@detector.detect([@buffer, @timer_buffer])).to be_a Events::Event }
59
+ it { expect(@detector.detect([@buffer, @timer_buffer]).record).to be_a Events::Records::IndexRecord }
60
+ it { expect(@detector.detect([@buffer, @timer_buffer]).record.index).to be_a Config::Index }
61
+ it 'should detect 3 fingers hold canclled' do
62
+ event = @detector.detect([@buffer, @timer_buffer])
63
+ expect(event.record.index.keys.map(&:symbol)).to eq([:hold, 3, :cancelled])
64
+ end
65
+ end
66
+
67
+ context 'with hold events(begin,end)' do
68
+ before do
69
+ events = create_hold_events(statuses: %w[begin end])
70
+ events.each { |event| @buffer.buffer(event) }
71
+ end
72
+ it { expect(@detector.detect([@buffer, @timer_buffer])).to be_a Events::Event }
73
+ it { expect(@detector.detect([@buffer, @timer_buffer]).record).to be_a Events::Records::IndexRecord }
74
+ it { expect(@detector.detect([@buffer, @timer_buffer]).record.index).to be_a Config::Index }
75
+ it 'should detect 3 fingers hold' do
76
+ events = @detector.detect([@buffer, @timer_buffer])
77
+ expect(events.record.index.keys.map(&:symbol)).to eq([:hold, 3, :end])
78
+ end
79
+ end
80
+
81
+ context 'with hold events and timer events' do
82
+ context 'with begin event and timer events' do
83
+ before do
84
+ events = create_hold_events(statuses: %w[begin])
85
+ events.each { |event| @buffer.buffer(event) }
86
+ @time = events.last.time
87
+ @timer_buffer.buffer(create_timer_event(time: @time + HoldDetector::BASE_THERESHOLD))
88
+ end
89
+ it { expect(@detector.detect([@buffer, @timer_buffer])).to eq nil }
90
+
91
+ context 'with enough holding time' do
92
+ before do
93
+ @timer_buffer.clear
94
+ @timer_buffer.buffer(create_timer_event(time: @time + HoldDetector::BASE_THERESHOLD + 0.01))
95
+ end
96
+ it { expect(@detector.detect([@buffer, @timer_buffer])).to be_a Events::Event }
97
+ it { expect(@detector.detect([@buffer, @timer_buffer]).record).to be_a Events::Records::IndexRecord }
98
+ it { expect(@detector.detect([@buffer, @timer_buffer]).record.index).to be_a Config::Index }
99
+ it 'should detect 3 fingers hold' do
100
+ events = @detector.detect([@buffer, @timer_buffer])
101
+ expect(events.record.index.keys.map(&:symbol)).to eq([:hold, 3])
102
+ end
103
+ end
104
+ context 'with changing threshold' do
105
+ around do |example|
106
+ ConfigHelper.load_config_yml = <<~CONFIG
107
+ threshold:
108
+ hold: 0.9
109
+ CONFIG
110
+
111
+ example.run
112
+
113
+ Config.custom_path = nil
114
+ end
115
+
116
+ it { expect(@detector.detect([@buffer, @timer_buffer])).not_to eq nil }
117
+ it 'should detect 3 fingers hold' do
118
+ events = @detector.detect([@buffer, @timer_buffer])
119
+ expect(events.record.index.keys.map(&:symbol)).to eq([:hold, 3])
120
+ end
121
+ end
122
+ end
123
+ end
124
+ end
125
+
126
+ private
127
+
128
+ def create_hold_events(statuses:)
129
+ record_type = HoldDetector::GESTURE_RECORD_TYPE
130
+ statuses.map do |status|
131
+ gesture_record = Events::Records::GestureRecord.new(status: status,
132
+ gesture: record_type,
133
+ finger: 3,
134
+ delta: nil)
135
+ Events::Event.new(tag: 'libinput_gesture_parser', record: gesture_record)
136
+ end
137
+ end
138
+
139
+ def create_timer_event(time: Time.now)
140
+ Events::Event.new(time: time, tag: 'timer_input', record: Events::Records::TextRecord.new('timer'))
141
+ end
142
+ end
143
+ end
144
+ end
145
+ end
@@ -13,20 +13,24 @@ module Fusuma
13
13
  RSpec.describe Executor do
14
14
  before { @executor = Executor.new }
15
15
 
16
+ describe '#execute_key' do
17
+ it { expect { @executor.execute_keys }.to raise_error(NotImplementedError) }
18
+ end
19
+
16
20
  describe '#execute' do
17
- it do
18
- expect { @executor.execute('dummy') }.to raise_error(NotImplementedError)
19
- end
21
+ it { expect { @executor.execute('dummy') }.to raise_error(NotImplementedError) }
20
22
  end
21
23
 
22
24
  describe '#executable?' do
23
- it do
24
- expect { @executor.executable?('dummy') }.to raise_error(NotImplementedError)
25
- end
25
+ it { expect { @executor.executable?('dummy') }.to raise_error(NotImplementedError) }
26
26
  end
27
27
  end
28
28
 
29
29
  class DummyExecutor < Executor
30
+ def execute_keys
31
+ [:dummy]
32
+ end
33
+
30
34
  def execute(event)
31
35
  index = Config::Index.new([*event.record.index.keys, :dummy])
32
36
  content = Config.search(index)