fusuma 2.0.0.pre2 → 2.0.4

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 (66) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +7 -6
  3. data/fusuma.gemspec +3 -5
  4. data/lib/fusuma/config/searcher.rb +2 -0
  5. data/lib/fusuma/device.rb +3 -3
  6. data/lib/fusuma/environment.rb +1 -0
  7. data/lib/fusuma/libinput_command.rb +12 -12
  8. data/lib/fusuma/plugin/executors/command_executor.rb +10 -10
  9. data/lib/fusuma/plugin/inputs/input.rb +1 -0
  10. data/lib/fusuma/plugin/inputs/libinput_command_input.rb +9 -2
  11. data/lib/fusuma/plugin/manager.rb +12 -1
  12. data/lib/fusuma/version.rb +1 -1
  13. data/spec/helpers/config_helper.rb +20 -0
  14. data/spec/lib/config/searcher_spec.rb +97 -0
  15. data/spec/lib/config_spec.rb +112 -0
  16. data/spec/lib/custom_process_spec.rb +28 -0
  17. data/spec/lib/device_spec.rb +96 -0
  18. data/spec/lib/dummy_config.yml +31 -0
  19. data/spec/lib/fusuma_spec.rb +103 -0
  20. data/spec/lib/libinput-list-devices_iberianpig-XPS-9360.txt +181 -0
  21. data/spec/lib/libinput-list-devices_magic_trackpad.txt +51 -0
  22. data/spec/lib/libinput-list-devices_razer_razer_blade.txt +252 -0
  23. data/spec/lib/libinput-list-devices_thejinx0r.txt +361 -0
  24. data/spec/lib/libinput-list-devices_unavailable.txt +36 -0
  25. data/spec/lib/libinput_command_spec.rb +164 -0
  26. data/spec/lib/plugin/base_spec.rb +74 -0
  27. data/spec/lib/plugin/buffers/buffer_spec.rb +80 -0
  28. data/spec/lib/plugin/buffers/dummy_buffer.rb +20 -0
  29. data/spec/lib/plugin/buffers/gesture_buffer_spec.rb +172 -0
  30. data/spec/lib/plugin/detectors/detector_spec.rb +43 -0
  31. data/spec/lib/plugin/detectors/dummy_detector.rb +24 -0
  32. data/spec/lib/plugin/detectors/pinch_detector_spec.rb +119 -0
  33. data/spec/lib/plugin/detectors/rotate_detector_spec.rb +125 -0
  34. data/spec/lib/plugin/detectors/swipe_detector_spec.rb +118 -0
  35. data/spec/lib/plugin/events/event_spec.rb +30 -0
  36. data/spec/lib/plugin/events/records/gesture_record_spec.rb +22 -0
  37. data/spec/lib/plugin/events/records/record_spec.rb +31 -0
  38. data/spec/lib/plugin/events/records/text_record_spec.rb +26 -0
  39. data/spec/lib/plugin/executors/command_executor_spec.rb +57 -0
  40. data/spec/lib/plugin/executors/executor_spec.rb +160 -0
  41. data/spec/lib/plugin/filters/filter_spec.rb +92 -0
  42. data/spec/lib/plugin/filters/libinput_filter_spec.rb +120 -0
  43. data/spec/lib/plugin/inputs/input_spec.rb +70 -0
  44. data/spec/lib/plugin/inputs/libinput_command_input_spec.rb +121 -0
  45. data/spec/lib/plugin/inputs/timer_input_spec.rb +40 -0
  46. data/spec/lib/plugin/manager_spec.rb +27 -0
  47. data/spec/lib/plugin/parsers/parser_spec.rb +45 -0
  48. data/spec/spec_helper.rb +20 -0
  49. metadata +83 -42
  50. data/.github/FUNDING.yml +0 -8
  51. data/.github/ISSUE_TEMPLATE/bug_report.md +0 -32
  52. data/.github/ISSUE_TEMPLATE/feature_request.md +0 -17
  53. data/.github/pull_request_template.md +0 -9
  54. data/.github/stale.yml +0 -18
  55. data/.gitignore +0 -17
  56. data/.reek.yml +0 -96
  57. data/.rspec +0 -2
  58. data/.rubocop.yml +0 -43
  59. data/.rubocop_todo.yml +0 -55
  60. data/.solargraph.yml +0 -16
  61. data/.travis.yml +0 -9
  62. data/CHANGELOG.md +0 -456
  63. data/CODE_OF_CONDUCT.md +0 -74
  64. data/CONTRIBUTING.md +0 -72
  65. data/Gemfile +0 -23
  66. data/Rakefile +0 -15
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6f7ab066b275d3d5bdf59d18a948d297d0e164f71d0611e6f999c71d962a8f6d
4
- data.tar.gz: f92ff70084e6b98482236b67b2f2c68d7424f7cb8ff1e9ad6b61f4a7d4bea244
3
+ metadata.gz: 8f19305771a0704b1f7294b9b54653f09d7d4f6153ffbdd19a6c23a753870b09
4
+ data.tar.gz: 3b6c9addcda816e7379e69bb258bd11f091ea39fbae37fc8e0ef4c9ca4b7dff7
5
5
  SHA512:
6
- metadata.gz: 885b781c3ec389cc6e37a68eb0799044e8792587145875d7c3facb59dc3a003cd53c4beb9d2a539f534f4947b2a5cf4d43442a9d3a0dba929744881feac7607c
7
- data.tar.gz: a79c1f4390694af343cb98901f30916c32642ff5318181a8191c855997eff64aea3dc4a88f02b51a7bfb2565ac7037965bb2816b24141eab6bcd6a23f9ce04dc
6
+ metadata.gz: 422dc7e58b8db650754c4939ec1946456bfdfb0a8e4af33d79a20d88d84190f393952e05e1d086f711fb1ee2e6c415c4ddf6861dba8d94bf84df4f4e0585374f
7
+ data.tar.gz: 02e75550adfd4a6f3e3d43b4a6326377d5182131bdd9d9f3ff3ca0abcb89888e0bca9eea8551868cc87edb0a706fb9b51ee97cc517e093566c7448729359ded2
data/README.md CHANGED
@@ -330,12 +330,13 @@ Fusuma plugins are provided with the `fusuma-plugin-XXXXX` naming convention and
330
330
 
331
331
  ### Available plugins
332
332
 
333
- | Name | About |
334
- | ------------------------------------------------------------------------------ | --------------------------------------------- |
335
- | [fusuma-plugin-sendkey](https://github.com/iberianpig/fusuma-plugin-sendkey) | Emulates keyboard events |
336
- | [fusuma-plugin-wmctrl](https://github.com/iberianpig/fusuma-plugin-wmctrl) | Manages Window and Workspace |
337
- | [fusuma-plugin-keypress](https://github.com/iberianpig/fusuma-plugin-keypress) | Detects gestures while pressing multiple keys |
338
- | [fusuma-plugin-tap](https://github.com/iberianpig/fusuma-plugin-tap) | Detects Tap and Hold gestures |
333
+ | Name | Version | About |
334
+ | ---------------------------------------------------------------------------------- | --------------------------------------------------------------------- | --------------------------------------------- |
335
+ | [fusuma-plugin-sendkey](https://github.com/iberianpig/fusuma-plugin-sendkey) | ![Gem Version](https://badge.fury.io/rb/fusuma-plugin-sendkey.svg) | Emulates keyboard events |
336
+ | [fusuma-plugin-wmctrl](https://github.com/iberianpig/fusuma-plugin-wmctrl) | ![Gem Version](https://badge.fury.io/rb/fusuma-plugin-wmctrl.svg) | Manages Window and Workspace |
337
+ | [fusuma-plugin-keypress](https://github.com/iberianpig/fusuma-plugin-keypress) | ![Gem Version](https://badge.fury.io/rb/fusuma-plugin-keypress.svg) | Detects gestures while pressing multiple keys |
338
+ | [fusuma-plugin-tap](https://github.com/iberianpig/fusuma-plugin-tap) | ![Gem Version](https://badge.fury.io/rb/fusuma-plugin-tap.svg) | Detects Tap and Hold gestures |
339
+ | [fusuma-plugin-appmatcher](https://github.com/iberianpig/fusuma-plugin-appmatcher) | ![Gem Version](https://badge.fury.io/rb/fusuma-plugin-appmatcher.svg) | Configure app-specific gestures |
339
340
 
340
341
  ## Tutorial Video
341
342
 
data/fusuma.gemspec CHANGED
@@ -10,14 +10,13 @@ Gem::Specification.new do |spec|
10
10
  spec.authors = ['iberianpig']
11
11
  spec.email = ['yhkyky@gmail.com']
12
12
 
13
- spec.summary = 'Multitouch gestures with libinput driver on X11, Linux'
13
+ spec.summary = 'Multitouch gestures with libinput driver, Linux'
14
14
  spec.description = 'Fusuma is multitouch gesture recognizer. This gem makes your touchpad on Linux able to recognize swipes or pinchs and assign command to them. Read installation on Github(https://github.com/iberianpig/fusuma#installation).'
15
15
  spec.homepage = 'https://github.com/iberianpig/fusuma'
16
16
  spec.license = 'MIT'
17
17
 
18
- spec.files = `git ls-files -z`.split("\x0").reject do |f|
19
- f.match(%r{^(test|spec|features)/})
20
- end
18
+ spec.files = Dir['{bin,lib,exe}/**/*', 'LICENSE*', 'README*', '*.gemspec']
19
+ spec.test_files = Dir['{test,spec,features}/**/*']
21
20
  spec.bindir = 'exe'
22
21
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
23
22
  spec.require_paths = ['lib']
@@ -25,5 +24,4 @@ Gem::Specification.new do |spec|
25
24
 
26
25
  spec.required_ruby_version = '>= 2.5.1' # https://packages.ubuntu.com/search?keywords=ruby&searchon=names&exact=1&suite=all&section=main
27
26
  # support bionic (18.04LTS) 2.5.1
28
- spec.add_dependency 'posix-spawn'
29
27
  end
@@ -32,6 +32,8 @@ module Fusuma
32
32
  end
33
33
 
34
34
  def search_with_context(index, location:, context:)
35
+ return nil if location.nil?
36
+
35
37
  return search(index, location: location[0]) if context == {}
36
38
 
37
39
  new_location = location.find do |conf|
data/lib/fusuma/device.rb CHANGED
@@ -36,9 +36,9 @@ module Fusuma
36
36
  # sort devices by capabilities of gesture
37
37
  # @return [Array]
38
38
  def all
39
- @all ||= fetch_devices.sort_by do |d|
40
- d.capabilities.match(/gesture/).to_s
41
- end
39
+ @all ||= fetch_devices.partition do |d|
40
+ d.capabilities.match?(/gesture/)
41
+ end.flatten
42
42
  end
43
43
 
44
44
  # @raise [SystemExit]
@@ -20,6 +20,7 @@ module Fusuma
20
20
  libinput_command = Plugin::Inputs::LibinputCommandInput.new.command
21
21
  MultiLogger.info "Fusuma: #{VERSION}"
22
22
  MultiLogger.info "libinput: #{libinput_command.version}"
23
+ MultiLogger.info "ruby #{ RUBY_VERSION }p#{ RUBY_PATCHLEVEL }"
23
24
  MultiLogger.info "OS: #{`uname -rsv`}".strip
24
25
  MultiLogger.info "Distribution: #{`cat /etc/issue`}".strip
25
26
  MultiLogger.info "Desktop session: #{`echo $DESKTOP_SESSION $XDG_SESSION_TYPE`}".strip
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'posix/spawn'
3
+ require 'open3'
4
4
 
5
5
  module Fusuma
6
6
  # Execute libinput command
@@ -33,20 +33,20 @@ module Fusuma
33
33
  def list_devices(&block)
34
34
  cmd = list_devices_command
35
35
  MultiLogger.debug(list_devices: cmd)
36
- p, i, o, e = POSIX::Spawn.popen4(cmd)
36
+ i, o, e, _w = Open3.popen3(cmd)
37
+ MultiLogger.error(e.read) if o.eof?
37
38
  i.close
39
+ e.close
38
40
  o.each(&block)
39
- ensure
40
- [i, o, e].each { |io| io.close unless io.closed? }
41
- Process.waitpid(p)
42
41
  end
43
42
 
44
- # @return [Integer, IO] return a latest line libinput debug-events
45
- def debug_events
46
- @debug_events = begin
47
- p, i, o, _e = POSIX::Spawn.popen4(debug_events_with_options)
48
- i.close
49
- [p, o]
43
+ # @return [Integer] return a latest line libinput debug-events
44
+ def debug_events(writer)
45
+ @debug_events ||= begin
46
+ pid = Process.spawn(debug_events_with_options, out: writer,
47
+ in: '/dev/null')
48
+ Process.detach(pid)
49
+ pid
50
50
  end
51
51
  end
52
52
 
@@ -60,7 +60,7 @@ module Fusuma
60
60
  elsif which('libinput-list-devices')
61
61
  'libinput-list-devices --version'
62
62
  else
63
- MultiLogger.error 'install libinput-tools'
63
+ MultiLogger.error 'Please install libinput-tools'
64
64
  exit 1
65
65
  end
66
66
  end
@@ -1,6 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'posix/spawn'
4
3
  require_relative './executor'
5
4
 
6
5
  module Fusuma
@@ -15,18 +14,19 @@ module Fusuma
15
14
  end
16
15
 
17
16
  def execute(event)
18
- search_command(event).tap do |command|
19
- break unless command
17
+ command = search_command(event)
20
18
 
21
- MultiLogger.info(command: command, args: event.record.args)
19
+ MultiLogger.info(command: command, args: event.record.args)
22
20
 
23
- additional_env = event.record.args
24
- .deep_transform_keys(&:to_s)
25
- .deep_transform_values { |v| (v * args_accel(event)).to_s }
21
+ accel = args_accel(event)
22
+ additional_env = event.record.args
23
+ .deep_transform_keys(&:to_s)
24
+ .deep_transform_values { |v| (v * accel).to_s }
26
25
 
27
- pid = POSIX::Spawn.spawn(additional_env, command.to_s)
28
- Process.detach(pid)
29
- end
26
+ pid = Process.spawn(additional_env, command.to_s)
27
+ Process.detach(pid)
28
+ rescue SystemCallError => e
29
+ MultiLogger.error("#{event.record.index.keys}": e.message.to_s)
30
30
  end
31
31
 
32
32
  def executable?(event)
@@ -25,6 +25,7 @@ module Fusuma
25
25
  warn "#{input.class.name}: #{e}"
26
26
  warn 'Send SIGKILL to fusuma processes'
27
27
  inputs.reject { |i| i == input }.each do |i|
28
+ warn "stop process: #{i.class.name.underscore}"
28
29
  Process.kill(:SIGKILL, i.pid)
29
30
  end
30
31
  exit 1
@@ -25,8 +25,9 @@ module Fusuma
25
25
  # @return [IO]
26
26
  def io
27
27
  @io ||= begin
28
- @pid, io = command.debug_events
29
- io
28
+ reader, writer = create_io
29
+ @pid = command.debug_events(writer)
30
+ reader
30
31
  end
31
32
  end
32
33
 
@@ -64,6 +65,12 @@ module Fusuma
64
65
  def list_devices_command
65
66
  config_params(:'libinput-list-devices')
66
67
  end
68
+
69
+ private
70
+
71
+ def create_io
72
+ IO.pipe
73
+ end
67
74
  end
68
75
  end
69
76
  end
@@ -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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Fusuma
4
- VERSION = '2.0.0.pre2'
4
+ VERSION = '2.0.4'
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