fusuma 0.11.1 → 1.0
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.
- checksums.yaml +4 -4
- data/.github/FUNDING.yml +8 -0
- data/.gitignore +6 -0
- data/.reek.yml +93 -45
- data/.rubocop.yml +2 -0
- data/.rubocop_todo.yml +16 -75
- data/Gemfile +2 -0
- data/README.md +12 -5
- data/Rakefile +2 -1
- data/bin/console +1 -1
- data/exe/fusuma +1 -0
- data/fusuma.gemspec +9 -2
- data/lib/fusuma.rb +88 -31
- data/lib/fusuma/config.rb +65 -66
- data/lib/fusuma/config/index.rb +49 -0
- data/lib/fusuma/device.rb +58 -37
- data/lib/fusuma/multi_logger.rb +3 -0
- data/lib/fusuma/plugin/base.rb +56 -0
- data/lib/fusuma/plugin/buffers/buffer.rb +41 -0
- data/lib/fusuma/plugin/buffers/gesture_buffer.rb +70 -0
- data/lib/fusuma/plugin/detectors/detector.rb +41 -0
- data/lib/fusuma/plugin/detectors/pinch_detector.rb +141 -0
- data/lib/fusuma/plugin/detectors/rotate_detector.rb +135 -0
- data/lib/fusuma/plugin/detectors/swipe_detector.rb +145 -0
- data/lib/fusuma/plugin/events/event.rb +38 -0
- data/lib/fusuma/plugin/events/records/gesture_record.rb +31 -0
- data/lib/fusuma/plugin/events/records/index_record.rb +53 -0
- data/lib/fusuma/plugin/events/records/record.rb +20 -0
- data/lib/fusuma/plugin/events/records/text_record.rb +28 -0
- data/lib/fusuma/plugin/executors/command_executor.rb +39 -0
- data/lib/fusuma/plugin/executors/executor.rb +27 -0
- data/lib/fusuma/plugin/filters/filter.rb +40 -0
- data/lib/fusuma/plugin/filters/libinput_device_filter.rb +42 -0
- data/lib/fusuma/plugin/inputs/input.rb +28 -0
- data/lib/fusuma/plugin/inputs/libinput_command_input.rb +133 -0
- data/lib/fusuma/plugin/manager.rb +118 -0
- data/lib/fusuma/plugin/parsers/libinput_gesture_parser.rb +54 -0
- data/lib/fusuma/plugin/parsers/parser.rb +46 -0
- data/lib/fusuma/version.rb +3 -1
- metadata +74 -14
- data/lib/fusuma/command_executor.rb +0 -43
- data/lib/fusuma/event_stack.rb +0 -87
- data/lib/fusuma/gesture_event.rb +0 -50
- data/lib/fusuma/libinput_commands.rb +0 -98
- data/lib/fusuma/pinch.rb +0 -58
- data/lib/fusuma/swipe.rb +0 -59
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../base.rb'
|
4
|
+
require_relative './records/record.rb'
|
5
|
+
require_relative './records/text_record.rb'
|
6
|
+
|
7
|
+
module Fusuma
|
8
|
+
module Plugin
|
9
|
+
module Events
|
10
|
+
# Event format
|
11
|
+
class Event < Base
|
12
|
+
attr_reader :time
|
13
|
+
attr_accessor :tag, :record
|
14
|
+
|
15
|
+
# @param time [Time]
|
16
|
+
# @param tag [Tag]
|
17
|
+
# @param record [String, Record]
|
18
|
+
def initialize(time: Time.now, tag:, record:)
|
19
|
+
@time = time
|
20
|
+
@tag = tag
|
21
|
+
@record = case record
|
22
|
+
when Records::Record
|
23
|
+
record
|
24
|
+
when String
|
25
|
+
Records::TextRecord.new(record)
|
26
|
+
else
|
27
|
+
raise ArgumentError,
|
28
|
+
'@record should be String or Record'
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def inspect
|
33
|
+
"time: #{time}, tag: #{tag}, record: #{record}"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative './text_record.rb'
|
4
|
+
|
5
|
+
module Fusuma
|
6
|
+
module Plugin
|
7
|
+
module Events
|
8
|
+
module Records
|
9
|
+
# Gesture Record
|
10
|
+
class GestureRecord < Record
|
11
|
+
# define gesture format
|
12
|
+
attr_reader :status, :gesture, :finger, :direction
|
13
|
+
|
14
|
+
Delta = Struct.new(:move_x, :move_y, :zoom, :rotate)
|
15
|
+
|
16
|
+
# @param status [String]
|
17
|
+
def initialize(status:, gesture:, finger:, direction:)
|
18
|
+
@status = status
|
19
|
+
@gesture = gesture
|
20
|
+
@finger = finger
|
21
|
+
@direction = direction
|
22
|
+
end
|
23
|
+
|
24
|
+
def type
|
25
|
+
:gesture
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Fusuma
|
4
|
+
module Plugin
|
5
|
+
module Events
|
6
|
+
module Records
|
7
|
+
# Vector Record
|
8
|
+
# have index
|
9
|
+
class IndexRecord < Record
|
10
|
+
# define gesture format
|
11
|
+
attr_reader :index
|
12
|
+
|
13
|
+
# @param [Config::Index] index
|
14
|
+
# @param [Symbol] position [:prefix, :body, :surfix]
|
15
|
+
def initialize(index:, position: :body)
|
16
|
+
@index = index
|
17
|
+
@position = position
|
18
|
+
end
|
19
|
+
|
20
|
+
def type
|
21
|
+
:index
|
22
|
+
end
|
23
|
+
|
24
|
+
# @param records [Array<IndexRecord>]
|
25
|
+
# @return [IndexRecord]
|
26
|
+
def merge(records:)
|
27
|
+
raise "position is NOT body: #{self}" unless mergable?
|
28
|
+
|
29
|
+
@index = records.each_with_object(@index) do |record, merged_index|
|
30
|
+
case record.position
|
31
|
+
when :prefix
|
32
|
+
Index.new([*record.index.keys, *merged_index.keys])
|
33
|
+
when :surfix
|
34
|
+
Index.new([*merged_index.keys, *record.index.keys])
|
35
|
+
else
|
36
|
+
raise "invalid index position: #{record}"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
self
|
40
|
+
end
|
41
|
+
|
42
|
+
def mergable?
|
43
|
+
@position == :body
|
44
|
+
end
|
45
|
+
|
46
|
+
protected
|
47
|
+
|
48
|
+
attr_reader :position
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../../base.rb'
|
4
|
+
|
5
|
+
module Fusuma
|
6
|
+
module Plugin
|
7
|
+
module Events
|
8
|
+
module Records
|
9
|
+
# Record
|
10
|
+
# @abstract Subclass and override {#type} to implement
|
11
|
+
class Record < Base
|
12
|
+
# @return [Symbol]
|
13
|
+
def type
|
14
|
+
raise NotImplementedError, 'override #type'
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative './record.rb'
|
4
|
+
|
5
|
+
module Fusuma
|
6
|
+
module Plugin
|
7
|
+
module Events
|
8
|
+
module Records
|
9
|
+
# Default Record
|
10
|
+
class TextRecord < Record
|
11
|
+
# @param text [String]
|
12
|
+
def initialize(text)
|
13
|
+
@text = text
|
14
|
+
end
|
15
|
+
|
16
|
+
def type
|
17
|
+
:text
|
18
|
+
end
|
19
|
+
|
20
|
+
# @return [String]
|
21
|
+
def to_s
|
22
|
+
@text
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative './executor.rb'
|
4
|
+
|
5
|
+
module Fusuma
|
6
|
+
module Plugin
|
7
|
+
module Executors
|
8
|
+
# Exector plugin
|
9
|
+
class CommandExecutor < Executor
|
10
|
+
def execute(event)
|
11
|
+
search_command(event).tap do |command|
|
12
|
+
break unless command
|
13
|
+
|
14
|
+
MultiLogger.info(command: command)
|
15
|
+
pid = fork do
|
16
|
+
Process.daemon(true)
|
17
|
+
exec(command.to_s)
|
18
|
+
end
|
19
|
+
|
20
|
+
Process.detach(pid)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def executable?(event)
|
25
|
+
event.tag.end_with?('_detector') &&
|
26
|
+
event.record.type == :index &&
|
27
|
+
search_command(event)
|
28
|
+
end
|
29
|
+
|
30
|
+
# @param event [Event]
|
31
|
+
# @return [String]
|
32
|
+
def search_command(event)
|
33
|
+
command_index = Config::Index.new([*event.record.index.keys, :command])
|
34
|
+
Config.search(command_index)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../base.rb'
|
4
|
+
|
5
|
+
module Fusuma
|
6
|
+
module Plugin
|
7
|
+
# executor class
|
8
|
+
module Executors
|
9
|
+
# Inherite this base
|
10
|
+
class Executor < Base
|
11
|
+
# check executable
|
12
|
+
# @param _event [Event]
|
13
|
+
# @return [TrueClass, FalseClass]
|
14
|
+
def executable?(_event)
|
15
|
+
raise NotImplementedError, "override #{self.class.name}##{__method__}"
|
16
|
+
end
|
17
|
+
|
18
|
+
# execute somthing
|
19
|
+
# @param _event [Event]
|
20
|
+
# @return [nil]
|
21
|
+
def execute(_event)
|
22
|
+
raise NotImplementedError, "override #{self.class.name}##{__method__}"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../base.rb'
|
4
|
+
|
5
|
+
module Fusuma
|
6
|
+
module Plugin
|
7
|
+
# filter class
|
8
|
+
module Filters
|
9
|
+
# Inherite this base
|
10
|
+
class Filter < Base
|
11
|
+
# Filter input event
|
12
|
+
# @param event [Event]
|
13
|
+
# @return [Event, nil]
|
14
|
+
def filter(event)
|
15
|
+
event.tap do |e|
|
16
|
+
next if e.tag != source
|
17
|
+
next if keep?(e.record)
|
18
|
+
|
19
|
+
MultiLogger.debug(filtered: e)
|
20
|
+
|
21
|
+
break nil
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
# @abstract override `#keep?` to implement
|
26
|
+
# @param record [String]
|
27
|
+
# @return [True, False]
|
28
|
+
def keep?(record)
|
29
|
+
true if record
|
30
|
+
end
|
31
|
+
|
32
|
+
# Set source for tag from config.yml.
|
33
|
+
# DEFAULT_SOURCE is defined in each Filter plugins.
|
34
|
+
def source
|
35
|
+
@source ||= config_params(:source) || self.class.const_get('DEFAULT_SOURCE')
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative './filter.rb'
|
4
|
+
require_relative '../../device.rb'
|
5
|
+
|
6
|
+
module Fusuma
|
7
|
+
module Plugin
|
8
|
+
module Filters
|
9
|
+
# Filter device log
|
10
|
+
class LibinputDeviceFilter < Filter
|
11
|
+
DEFAULT_SOURCE = 'libinput_command_input'
|
12
|
+
|
13
|
+
def config_param_types
|
14
|
+
{
|
15
|
+
source: String,
|
16
|
+
keep_device_names: [Array, String]
|
17
|
+
}
|
18
|
+
end
|
19
|
+
|
20
|
+
def keep?(record)
|
21
|
+
keep_device_ids.any? { |device_id| record.to_s =~ /^\s?#{device_id}/ }
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
# @return [Array]
|
27
|
+
def keep_device_ids
|
28
|
+
@keep_device_ids ||= Device.all.select do |device|
|
29
|
+
keep_device_names.any? { |name| device.name.match? name }
|
30
|
+
end.map(&:id)
|
31
|
+
end
|
32
|
+
|
33
|
+
# @return [Array]
|
34
|
+
def keep_device_names
|
35
|
+
Array(config_params(:keep_device_names)).tap do |names|
|
36
|
+
break Device.all.map(&:name) if names.empty?
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../base.rb'
|
4
|
+
require_relative '../events/event.rb'
|
5
|
+
|
6
|
+
module Fusuma
|
7
|
+
module Plugin
|
8
|
+
# input class
|
9
|
+
module Inputs
|
10
|
+
# Inherite this base
|
11
|
+
class Input < Base
|
12
|
+
def run
|
13
|
+
raise NotImplementedError, "override #{self.class.name}##{__method__}"
|
14
|
+
end
|
15
|
+
|
16
|
+
def event(record: 'dummy input')
|
17
|
+
Events::Event.new(tag: tag, record: record).tap do |e|
|
18
|
+
MultiLogger.debug(input_event: e)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def tag
|
23
|
+
self.class.name.split('Inputs::').last.underscore
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,133 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative './input.rb'
|
4
|
+
require 'open3'
|
5
|
+
|
6
|
+
module Fusuma
|
7
|
+
module Plugin
|
8
|
+
module Inputs
|
9
|
+
# libinput commands wrapper
|
10
|
+
class LibinputCommandInput < Input
|
11
|
+
def config_param_types
|
12
|
+
{
|
13
|
+
'enable-tap': [TrueClass, FalseClass],
|
14
|
+
'enable-dwt': [TrueClass, FalseClass],
|
15
|
+
'device': [String]
|
16
|
+
}
|
17
|
+
end
|
18
|
+
|
19
|
+
def run
|
20
|
+
debug_events do |line|
|
21
|
+
yield event(record: line)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
# `libinput-list-devices` and `libinput-debug-events` are deprecated,
|
26
|
+
# use `libinput list-devices` and `libinput debug-events` from 1.8.
|
27
|
+
NEW_CLI_OPTION_VERSION = 1.8
|
28
|
+
|
29
|
+
# @return [Boolean]
|
30
|
+
def new_cli_option_available?
|
31
|
+
Gem::Version.new(version) >= Gem::Version.new(NEW_CLI_OPTION_VERSION)
|
32
|
+
end
|
33
|
+
|
34
|
+
# @return [String]
|
35
|
+
def version
|
36
|
+
# versiom_command prints "1.6.3\n"
|
37
|
+
@version ||= `#{version_command}`.strip
|
38
|
+
end
|
39
|
+
|
40
|
+
# @yield [line] gives a line in libinput list-devices output to the block
|
41
|
+
def list_devices
|
42
|
+
cmd = list_devices_command
|
43
|
+
MultiLogger.debug(list_devices: cmd)
|
44
|
+
Open3.popen3(cmd) do |_i, o, _e, _w|
|
45
|
+
o.each { |line| yield(line) }
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
# @yield [line] gives a line in libinput debug-events output to the block
|
50
|
+
def debug_events
|
51
|
+
prefix = 'stdbuf -oL --'
|
52
|
+
options = [*libinput_options, device_option]
|
53
|
+
cmd = "#{prefix} #{debug_events_command} #{options.join(' ')}".strip
|
54
|
+
MultiLogger.debug(debug_events: cmd)
|
55
|
+
Open3.popen3(cmd) do |_i, o, _e, _w|
|
56
|
+
o.each do |line|
|
57
|
+
yield(line.chomp)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
# @return [String] command
|
63
|
+
# @raise [SystemExit]
|
64
|
+
def version_command
|
65
|
+
if which('libinput')
|
66
|
+
'libinput --version'
|
67
|
+
elsif which('libinput-list-devices')
|
68
|
+
'libinput-list-devices --version'
|
69
|
+
else
|
70
|
+
MultiLogger.error 'install libinput-tools'
|
71
|
+
exit 1
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def list_devices_command
|
76
|
+
if new_cli_option_available?
|
77
|
+
'libinput list-devices'
|
78
|
+
else
|
79
|
+
'libinput-list-devices'
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def debug_events_command
|
84
|
+
if new_cli_option_available?
|
85
|
+
'libinput debug-events'
|
86
|
+
else
|
87
|
+
'libinput-debug-events'
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
private
|
92
|
+
|
93
|
+
# use device option only if libinput detect only 1 device
|
94
|
+
# @return [String]
|
95
|
+
def device_option
|
96
|
+
return unless Device.available.size == 1
|
97
|
+
|
98
|
+
"--device=/dev/input/#{Device.available.first.id}"
|
99
|
+
end
|
100
|
+
|
101
|
+
# TODO: add specs
|
102
|
+
def libinput_options
|
103
|
+
enable_tap = '--enable-tap' if config_params(:'enable-tap')
|
104
|
+
device = ("--device=#{config_params(:device)}" if config_params(:device))
|
105
|
+
enable_dwt = '--enable-dwt' if config_params(:'enable-dwt')
|
106
|
+
|
107
|
+
[
|
108
|
+
enable_tap,
|
109
|
+
device,
|
110
|
+
enable_dwt
|
111
|
+
].compact
|
112
|
+
end
|
113
|
+
|
114
|
+
# which in ruby: Checking if program exists in $PATH from ruby
|
115
|
+
# (https://stackoverflow.com/questions/2108727/which-in-ruby-checking-if-program-exists-in-path-from-ruby)
|
116
|
+
# Cross-platform way of finding an executable in the $PATH.
|
117
|
+
#
|
118
|
+
# which('ruby') #=> /usr/bin/ruby
|
119
|
+
# @return [String, nil]
|
120
|
+
def which(command)
|
121
|
+
exts = ENV['PATHEXT'] ? ENV['PATHEXT'].split(';') : ['']
|
122
|
+
ENV['PATH'].split(File::PATH_SEPARATOR).each do |path|
|
123
|
+
exts.each do |ext|
|
124
|
+
exe = File.join(path, "#{command}#{ext}")
|
125
|
+
return exe if File.executable?(exe) && !File.directory?(exe)
|
126
|
+
end
|
127
|
+
end
|
128
|
+
nil
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|