fusuma 3.9.0 → 3.10.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/README.md +27 -1
- data/lib/fusuma/config/index.rb +12 -7
- data/lib/fusuma/config/searcher.rb +17 -6
- data/lib/fusuma/config/yaml_duplication_checker.rb +6 -0
- data/lib/fusuma/config.rb +17 -3
- data/lib/fusuma/custom_process.rb +3 -0
- data/lib/fusuma/device.rb +15 -0
- data/lib/fusuma/environment.rb +4 -0
- data/lib/fusuma/hash_support.rb +6 -1
- data/lib/fusuma/libinput_command.rb +11 -3
- data/lib/fusuma/multi_logger.rb +15 -4
- data/lib/fusuma/plugin/base.rb +11 -1
- data/lib/fusuma/plugin/buffers/buffer.rb +6 -0
- data/lib/fusuma/plugin/buffers/gesture_buffer.rb +15 -9
- data/lib/fusuma/plugin/buffers/timer_buffer.rb +2 -1
- data/lib/fusuma/plugin/detectors/detector.rb +15 -15
- data/lib/fusuma/plugin/detectors/hold_detector.rb +10 -2
- data/lib/fusuma/plugin/detectors/pinch_detector.rb +13 -1
- data/lib/fusuma/plugin/detectors/rotate_detector.rb +11 -0
- data/lib/fusuma/plugin/detectors/swipe_detector.rb +12 -0
- data/lib/fusuma/plugin/events/event.rb +5 -2
- data/lib/fusuma/plugin/events/records/gesture_record.rb +2 -0
- data/lib/fusuma/plugin/events/records/index_record.rb +2 -0
- data/lib/fusuma/plugin/events/records/record.rb +1 -0
- data/lib/fusuma/plugin/events/records/text_record.rb +3 -0
- data/lib/fusuma/plugin/executors/command_executor.rb +5 -1
- data/lib/fusuma/plugin/executors/executor.rb +6 -0
- data/lib/fusuma/plugin/filters/filter.rb +2 -0
- data/lib/fusuma/plugin/filters/libinput_device_filter.rb +10 -0
- data/lib/fusuma/plugin/inputs/input.rb +5 -0
- data/lib/fusuma/plugin/inputs/libinput_command_input.rb +10 -3
- data/lib/fusuma/plugin/inputs/timer_input.rb +4 -0
- data/lib/fusuma/plugin/manager.rb +11 -1
- data/lib/fusuma/plugin/parsers/libinput_gesture_parser.rb +9 -0
- data/lib/fusuma/plugin/parsers/parser.rb +4 -0
- data/lib/fusuma/string_support.rb +1 -0
- data/lib/fusuma/version.rb +1 -1
- data/lib/fusuma.rb +10 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 7161de044b786ed9f12f64be26a497307ba1ea87159760e68b5ac90d87ea1d73
|
|
4
|
+
data.tar.gz: aa9359be7d9c0bcc6ca283ba5c2aa17b55f07424a26d3f22b056c47b924488bc
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 1187109a3b4b3ba67b35feaa274ab73a58dc3411ae8b3de6f62e5ef153c7ca8647383ae0cebeeb8e5632bd69ec0218477826e5e5aa4d32002233a87b521eba43
|
|
7
|
+
data.tar.gz: eec0e66de8f07b7e64ffec123b16a1b51103701346819b0b048850d83e8bf8a78c020f3d51e889f7ff5474218d534a7e7e6bf834bac36c6ff739f279a06da4c8
|
data/README.md
CHANGED
|
@@ -9,7 +9,7 @@ Fusuma is a powerful tool designed to enable multitouch gesture recognition on L
|
|
|
9
9
|
|
|
10
10
|
## Features
|
|
11
11
|
|
|
12
|
-
- **Easy Installation**: Quick
|
|
12
|
+
- **Easy Installation**: Quick generate via RubyGems.
|
|
13
13
|
- **Flexible Configuration**: Customize gestures and actions freely in YAML file format.
|
|
14
14
|
- **Sensitivity Settings**: Fine-tune gesture recognition with adjustable thresholds and intervals to suit your preferences and enhance precision.
|
|
15
15
|
- **Extension through Plugins**: A [plugin system](https://github.com/iberianpig/fusuma/#fusuma-plugins) allows for additional functionality as needed.
|
|
@@ -449,6 +449,32 @@ I'm a Freelance Engineer in Japan and working on these products after finishing
|
|
|
449
449
|
Currently, my open-source contribution times is not enough.
|
|
450
450
|
If you like my work and want to contribute and become a sponsor, I will be able to focus on my projects.
|
|
451
451
|
|
|
452
|
+
## Development
|
|
453
|
+
|
|
454
|
+
### Type Checking
|
|
455
|
+
|
|
456
|
+
Fusuma uses [RBS](https://github.com/ruby/rbs) for type signatures and [Steep](https://github.com/soutaro/steep) for type checking.
|
|
457
|
+
|
|
458
|
+
#### Running Type Checks
|
|
459
|
+
|
|
460
|
+
```sh
|
|
461
|
+
# Generate RBS signatures and run type checking
|
|
462
|
+
bundle exec rake rbs:generate && bundle exec steep check
|
|
463
|
+
|
|
464
|
+
# Validate RBS files
|
|
465
|
+
bundle exec rake rbs:validate
|
|
466
|
+
|
|
467
|
+
# Generate inline RBS from code comments
|
|
468
|
+
bundle exec rbs-inline --opt-out lib --output --base .
|
|
469
|
+
```
|
|
470
|
+
|
|
471
|
+
#### RBS Development
|
|
472
|
+
|
|
473
|
+
The project uses both traditional RBS files and inline RBS comments:
|
|
474
|
+
- Inline RBS comments in source code (processed by `rbs-inline`)
|
|
475
|
+
- Generated RBS files in `sig/generated/`
|
|
476
|
+
- Type checking via Steep with configuration in `Steepfile`
|
|
477
|
+
|
|
452
478
|
## Contributing
|
|
453
479
|
|
|
454
480
|
Bug reports and pull requests are welcome on GitHub at https://github.com/iberianpig/fusuma. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
|
data/lib/fusuma/config/index.rb
CHANGED
|
@@ -5,6 +5,7 @@ module Fusuma
|
|
|
5
5
|
class Config
|
|
6
6
|
# index for config.yml
|
|
7
7
|
class Index
|
|
8
|
+
#: (Array[untyped] | String | Symbol | Integer) -> void
|
|
8
9
|
def initialize(keys)
|
|
9
10
|
case keys
|
|
10
11
|
when Array
|
|
@@ -21,6 +22,9 @@ module Fusuma
|
|
|
21
22
|
end
|
|
22
23
|
end
|
|
23
24
|
|
|
25
|
+
attr_reader :keys #: Array[Key]
|
|
26
|
+
attr_reader :cache_key #: Symbol | Integer
|
|
27
|
+
|
|
24
28
|
def to_s
|
|
25
29
|
@keys.map(&:inspect)
|
|
26
30
|
end
|
|
@@ -31,15 +35,18 @@ module Fusuma
|
|
|
31
35
|
cache_key == other.cache_key
|
|
32
36
|
end
|
|
33
37
|
|
|
34
|
-
attr_reader :keys, :cache_key
|
|
35
|
-
|
|
36
38
|
# Keys in Index
|
|
37
39
|
class Key
|
|
40
|
+
attr_reader :symbol #: Symbol | Integer
|
|
41
|
+
attr_reader :skippable #: bool
|
|
42
|
+
|
|
43
|
+
#: (String | Integer | Symbol, ?skippable: bool) -> void
|
|
38
44
|
def initialize(symbol_word, skippable: false)
|
|
39
|
-
@symbol =
|
|
40
|
-
|
|
41
|
-
rescue
|
|
45
|
+
@symbol = case symbol_word
|
|
46
|
+
when Integer, Symbol
|
|
42
47
|
symbol_word
|
|
48
|
+
else
|
|
49
|
+
symbol_word.to_sym
|
|
43
50
|
end
|
|
44
51
|
|
|
45
52
|
@skippable = skippable
|
|
@@ -52,8 +59,6 @@ module Fusuma
|
|
|
52
59
|
@symbol.to_s
|
|
53
60
|
end
|
|
54
61
|
end
|
|
55
|
-
|
|
56
|
-
attr_reader :symbol, :skippable
|
|
57
62
|
end
|
|
58
63
|
end
|
|
59
64
|
end
|
|
@@ -5,6 +5,7 @@ module Fusuma
|
|
|
5
5
|
class Config
|
|
6
6
|
# Search config.yml
|
|
7
7
|
class Searcher
|
|
8
|
+
#: () -> void
|
|
8
9
|
def initialize
|
|
9
10
|
@cache = {}
|
|
10
11
|
end
|
|
@@ -14,6 +15,7 @@ module Fusuma
|
|
|
14
15
|
# @return [NilClass]
|
|
15
16
|
# @return [Hash]
|
|
16
17
|
# @return [Object]
|
|
18
|
+
#: (Fusuma::Config::Index, location: untyped) -> untyped
|
|
17
19
|
def search(index, location:)
|
|
18
20
|
key = index.keys.first
|
|
19
21
|
return location if key.nil?
|
|
@@ -22,7 +24,7 @@ module Fusuma
|
|
|
22
24
|
|
|
23
25
|
return nil unless location.is_a?(Hash)
|
|
24
26
|
|
|
25
|
-
next_index = Index.new(index.keys[1..-1])
|
|
27
|
+
next_index = Index.new(Array(index.keys[1..-1]))
|
|
26
28
|
|
|
27
29
|
value = nil
|
|
28
30
|
next_location_cadidates(location, key).find do |next_location|
|
|
@@ -31,6 +33,7 @@ module Fusuma
|
|
|
31
33
|
value
|
|
32
34
|
end
|
|
33
35
|
|
|
36
|
+
#: (Fusuma::Config::Index, location: Array[untyped], context: Hash[untyped, untyped] | nil) -> untyped
|
|
34
37
|
def search_with_context(index, location:, context:)
|
|
35
38
|
return nil if location.nil?
|
|
36
39
|
|
|
@@ -48,12 +51,14 @@ module Fusuma
|
|
|
48
51
|
# @return [NilClass]
|
|
49
52
|
# @return [Hash]
|
|
50
53
|
# @return [Object]
|
|
54
|
+
#: (Fusuma::Config::Index, location: Array[untyped])
|
|
51
55
|
def search_with_cache(index, location:)
|
|
52
56
|
cache([index.cache_key, Searcher.context]) do
|
|
53
57
|
search_with_context(index, location: location, context: Searcher.context)
|
|
54
58
|
end
|
|
55
59
|
end
|
|
56
60
|
|
|
61
|
+
#: (Array[untyped] | String) { () -> untyped } -> untyped
|
|
57
62
|
def cache(key)
|
|
58
63
|
key = key.join(",") if key.is_a? Array
|
|
59
64
|
if @cache.key?(key)
|
|
@@ -68,6 +73,7 @@ module Fusuma
|
|
|
68
73
|
# next locations' candidates sorted by priority
|
|
69
74
|
# 1. look up location with key
|
|
70
75
|
# 2. skip the key and go to child location
|
|
76
|
+
#: (Hash[untyped, untyped], Fusuma::Config::Index::Key) -> Array[untyped]
|
|
71
77
|
def next_location_cadidates(location, key)
|
|
72
78
|
[
|
|
73
79
|
location[key.symbol],
|
|
@@ -81,19 +87,20 @@ module Fusuma
|
|
|
81
87
|
# Search with context from load_streamed Config
|
|
82
88
|
# @param context [Hash]
|
|
83
89
|
# @return [Object]
|
|
90
|
+
#: (?Hash[untyped, untyped]) { () -> untyped } -> untyped
|
|
84
91
|
def with_context(context = {}, &block)
|
|
85
92
|
before = @context
|
|
86
93
|
@context = context
|
|
87
|
-
|
|
94
|
+
block.call
|
|
88
95
|
ensure # NOTE: ensure is called even if return in block
|
|
89
96
|
@context = before
|
|
90
|
-
result
|
|
91
97
|
end
|
|
92
98
|
|
|
93
99
|
CONTEXT_SEARCH_ORDER = [:no_context, :complete_match_context, :partial_match_context]
|
|
94
100
|
# Return a matching context from config
|
|
95
101
|
# @params request_context [Hash]
|
|
96
102
|
# @return [Hash]
|
|
103
|
+
#: (Hash[untyped, untyped], ?Array[untyped]) { () -> untyped } -> Hash[untyped, untyped]?
|
|
97
104
|
def find_context(request_context, fallbacks = CONTEXT_SEARCH_ORDER, &block)
|
|
98
105
|
# Search in blocks in the following order.
|
|
99
106
|
# 1. primary context(no context)
|
|
@@ -113,6 +120,7 @@ module Fusuma
|
|
|
113
120
|
# No context(primary context)
|
|
114
121
|
# @return [Hash]
|
|
115
122
|
# @return [NilClass]
|
|
123
|
+
#: (Hash[untyped, untyped]) { () -> untyped } -> Hash[untyped, untyped]?
|
|
116
124
|
def no_context(_request_context, &block)
|
|
117
125
|
{} if with_context({}, &block)
|
|
118
126
|
end
|
|
@@ -121,6 +129,7 @@ module Fusuma
|
|
|
121
129
|
# @param request_context [Hash]
|
|
122
130
|
# @return [Hash] matched context
|
|
123
131
|
# @return [NilClass] if not matched
|
|
132
|
+
#: (Hash[untyped, untyped]) { () -> untyped } -> Hash[untyped, untyped]?
|
|
124
133
|
def complete_match_context(request_context, &block)
|
|
125
134
|
Config.instance.keymap.each do |config|
|
|
126
135
|
next unless config[:context] == request_context
|
|
@@ -133,6 +142,7 @@ module Fusuma
|
|
|
133
142
|
# @param request_context [Hash]
|
|
134
143
|
# @return [Hash] matched context
|
|
135
144
|
# @return [NilClass] if not matched
|
|
145
|
+
#: (Hash[untyped, untyped]) { () -> untyped } -> Hash[untyped, untyped]?
|
|
136
146
|
def partial_match_context(request_context, &block)
|
|
137
147
|
if request_context.keys.size > 1
|
|
138
148
|
Config.instance.keymap.each do |config|
|
|
@@ -155,6 +165,7 @@ module Fusuma
|
|
|
155
165
|
# @param request_context [Hash]
|
|
156
166
|
# @return [Hash] matched context
|
|
157
167
|
# @return [NilClass] if not matched
|
|
168
|
+
#: (Hash[untyped, untyped]) { () -> untyped } -> Hash[untyped, untyped]?
|
|
158
169
|
def plugin_default_context(request_context, &block)
|
|
159
170
|
complete_match_context = nil
|
|
160
171
|
Config.instance.keymap.each do |config|
|
|
@@ -167,9 +178,9 @@ module Fusuma
|
|
|
167
178
|
|
|
168
179
|
return config[:context] if with_context(config[:context], &block)
|
|
169
180
|
end
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
181
|
+
|
|
182
|
+
complete_match_context&.tap do |context|
|
|
183
|
+
with_context(context, &block)
|
|
173
184
|
end
|
|
174
185
|
end
|
|
175
186
|
end
|
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "yaml"
|
|
4
|
+
|
|
3
5
|
module Fusuma
|
|
4
6
|
class Config
|
|
5
7
|
# ref: https://github.com/rubocop-hq/rubocop/blob/97e4ffc8a71e9e5239a927c6a534dfc1e0da917f/lib/rubocop/yaml_duplication_checker.rb
|
|
6
8
|
# Find duplicated keys from YAML.
|
|
9
|
+
# steep:ignore:start
|
|
7
10
|
module YAMLDuplicationChecker
|
|
11
|
+
#: (String, String) { (string) -> void } -> void
|
|
8
12
|
def self.check(yaml_string, filename, &on_duplicated)
|
|
9
13
|
tree = YAML.parse(yaml_string, filename: filename)
|
|
10
14
|
return unless tree
|
|
@@ -12,6 +16,7 @@ module Fusuma
|
|
|
12
16
|
traverse(tree, &on_duplicated)
|
|
13
17
|
end
|
|
14
18
|
|
|
19
|
+
#: (untyped) { () -> void } -> Array[untyped]?
|
|
15
20
|
def self.traverse(tree, &on_duplicated)
|
|
16
21
|
case tree
|
|
17
22
|
when Psych::Nodes::Mapping
|
|
@@ -31,5 +36,6 @@ module Fusuma
|
|
|
31
36
|
|
|
32
37
|
private_class_method :traverse
|
|
33
38
|
end
|
|
39
|
+
# steep:ignore:end
|
|
34
40
|
end
|
|
35
41
|
end
|
data/lib/fusuma/config.rb
CHANGED
|
@@ -20,6 +20,7 @@ module Fusuma
|
|
|
20
20
|
include Singleton
|
|
21
21
|
|
|
22
22
|
class << self
|
|
23
|
+
#: (Fusuma::Config::Index) -> (String | Hash[untyped, untyped] | Integer | Float)?
|
|
23
24
|
def search(index)
|
|
24
25
|
instance.search(index)
|
|
25
26
|
end
|
|
@@ -28,6 +29,7 @@ module Fusuma
|
|
|
28
29
|
instance.find_execute_key(index)
|
|
29
30
|
end
|
|
30
31
|
|
|
32
|
+
#: (String?) -> Fusuma::Config
|
|
31
33
|
def custom_path=(new_path)
|
|
32
34
|
instance.custom_path = new_path
|
|
33
35
|
end
|
|
@@ -35,22 +37,26 @@ module Fusuma
|
|
|
35
37
|
|
|
36
38
|
attr_reader :custom_path, :searcher
|
|
37
39
|
|
|
40
|
+
#: () -> void
|
|
38
41
|
def initialize
|
|
39
42
|
@searcher = Searcher.new
|
|
40
43
|
@custom_path = nil
|
|
41
44
|
@keymap = nil
|
|
42
45
|
end
|
|
43
46
|
|
|
47
|
+
#: (String?) -> Fusuma::Config
|
|
44
48
|
def custom_path=(new_path)
|
|
45
49
|
@custom_path = new_path
|
|
46
50
|
reload
|
|
47
51
|
end
|
|
48
52
|
|
|
53
|
+
#: () -> Array[untyped]?
|
|
49
54
|
def keymap
|
|
50
55
|
# FIXME: @keymap is not initialized when called from outside Fusuma::Runner like fusuma-senkey
|
|
51
56
|
@keymap || reload.keymap
|
|
52
57
|
end
|
|
53
58
|
|
|
59
|
+
#: () -> Fusuma::Config
|
|
54
60
|
def reload
|
|
55
61
|
plugin_defaults = plugin_defaults_paths.map do |default_yml|
|
|
56
62
|
{
|
|
@@ -76,6 +82,7 @@ module Fusuma
|
|
|
76
82
|
# @param key [Symbol]
|
|
77
83
|
# @param base [Config::Index]
|
|
78
84
|
# @return [Hash]
|
|
85
|
+
#: (Symbol?, Fusuma::Config::Index) -> (String | Hash[untyped, untyped] | Float | Integer | bool)?
|
|
79
86
|
def fetch_config_params(key, base)
|
|
80
87
|
request_context = {plugin_defaults: base.keys.last.symbol.to_s}
|
|
81
88
|
fallbacks = [:no_context, :plugin_default_context]
|
|
@@ -90,15 +97,16 @@ module Fusuma
|
|
|
90
97
|
|
|
91
98
|
# @return [Hash] If check passes
|
|
92
99
|
# @raise [InvalidFileError] If check does not pass
|
|
100
|
+
#: (String) -> Array[Hash[Symbol, untyped]]
|
|
93
101
|
def validate(path)
|
|
94
102
|
duplicates = []
|
|
95
|
-
YAMLDuplicationChecker.check(File.read(path), path) do |ignored, duplicate|
|
|
103
|
+
YAMLDuplicationChecker.check(File.read(path), path) do |ignored, duplicate| # steep:ignore UnexpectedBlockGiven
|
|
96
104
|
MultiLogger.error "#{path}: #{ignored.value} is duplicated"
|
|
97
105
|
duplicates << duplicate.value
|
|
98
106
|
end
|
|
99
107
|
raise InvalidFileError, "Detect duplicate keys #{duplicates}" unless duplicates.empty?
|
|
100
108
|
|
|
101
|
-
yamls = YAML.load_stream(File.read(path)).compact
|
|
109
|
+
yamls = YAML.load_stream(File.read(path)).compact # steep:ignore NoMethod
|
|
102
110
|
yamls.map do |yaml|
|
|
103
111
|
raise InvalidFileError, "Invalid config.yml: #{path}" unless yaml.is_a? Hash
|
|
104
112
|
|
|
@@ -109,7 +117,9 @@ module Fusuma
|
|
|
109
117
|
end
|
|
110
118
|
|
|
111
119
|
# @param index [Index]
|
|
120
|
+
#: (Fusuma::Config::Index) -> (String | Hash[untyped, untyped] | Integer | Float)?
|
|
112
121
|
def search(index)
|
|
122
|
+
return nil if index.nil? || index.keys.empty?
|
|
113
123
|
@searcher.search_with_cache(index, location: keymap)
|
|
114
124
|
end
|
|
115
125
|
|
|
@@ -120,7 +130,7 @@ module Fusuma
|
|
|
120
130
|
executor.new.execute_keys
|
|
121
131
|
end.flatten
|
|
122
132
|
|
|
123
|
-
@cache_execute_keys ||= {}
|
|
133
|
+
@cache_execute_keys ||= {} #: Hash[String, untyped]
|
|
124
134
|
|
|
125
135
|
cache_key = [index.cache_key, Searcher.context].join
|
|
126
136
|
|
|
@@ -135,6 +145,7 @@ module Fusuma
|
|
|
135
145
|
|
|
136
146
|
private
|
|
137
147
|
|
|
148
|
+
#: () -> String
|
|
138
149
|
def find_config_filepath
|
|
139
150
|
filename = "fusuma/config.yml"
|
|
140
151
|
if custom_path
|
|
@@ -149,10 +160,12 @@ module Fusuma
|
|
|
149
160
|
end
|
|
150
161
|
end
|
|
151
162
|
|
|
163
|
+
#: () -> String
|
|
152
164
|
def expand_custom_path
|
|
153
165
|
File.expand_path(custom_path)
|
|
154
166
|
end
|
|
155
167
|
|
|
168
|
+
#: (String) -> String
|
|
156
169
|
def expand_config_path(filename)
|
|
157
170
|
File.expand_path "~/.config/#{filename}"
|
|
158
171
|
end
|
|
@@ -161,6 +174,7 @@ module Fusuma
|
|
|
161
174
|
File.expand_path "../../#{filename}", __FILE__
|
|
162
175
|
end
|
|
163
176
|
|
|
177
|
+
#: () -> Array[untyped]
|
|
164
178
|
def plugin_defaults_paths
|
|
165
179
|
Plugin::Manager.load_paths.map do |plugin_path|
|
|
166
180
|
yml = plugin_path.gsub(/\.rb$/, ".yml")
|
|
@@ -7,10 +7,12 @@ module Fusuma
|
|
|
7
7
|
module CustomProcess
|
|
8
8
|
attr_writer :proctitle
|
|
9
9
|
|
|
10
|
+
#: () -> Array[untyped]
|
|
10
11
|
def child_pids
|
|
11
12
|
@child_pids ||= []
|
|
12
13
|
end
|
|
13
14
|
|
|
15
|
+
#: () { () -> void } -> nil
|
|
14
16
|
def fork
|
|
15
17
|
pid = Process.fork do
|
|
16
18
|
Process.setproctitle(proctitle)
|
|
@@ -35,6 +37,7 @@ module Fusuma
|
|
|
35
37
|
end
|
|
36
38
|
end
|
|
37
39
|
|
|
40
|
+
#: () -> String
|
|
38
41
|
def proctitle
|
|
39
42
|
@proctitle ||= self.class.name.underscore
|
|
40
43
|
end
|
data/lib/fusuma/device.rb
CHANGED
|
@@ -8,6 +8,7 @@ module Fusuma
|
|
|
8
8
|
class Device
|
|
9
9
|
attr_reader :id, :name, :capabilities, :available
|
|
10
10
|
|
|
11
|
+
#: (?id: nil | String, ?name: nil | String, ?capabilities: nil, ?available: nil | bool) -> void
|
|
11
12
|
def initialize(id: nil, name: nil, capabilities: nil, available: nil)
|
|
12
13
|
@id = id
|
|
13
14
|
@name = name
|
|
@@ -16,6 +17,7 @@ module Fusuma
|
|
|
16
17
|
end
|
|
17
18
|
|
|
18
19
|
# @param attributes [Hash]
|
|
20
|
+
#: (Hash[untyped, untyped]) -> Hash[untyped, untyped]
|
|
19
21
|
def assign_attributes(attributes)
|
|
20
22
|
attributes.each do |k, v|
|
|
21
23
|
case k
|
|
@@ -35,6 +37,7 @@ module Fusuma
|
|
|
35
37
|
# Return devices
|
|
36
38
|
# sort devices by capabilities of gesture
|
|
37
39
|
# @return [Array]
|
|
40
|
+
#: () -> Array[Device]
|
|
38
41
|
def all
|
|
39
42
|
@all ||= fetch_devices.partition do |d|
|
|
40
43
|
d.capabilities.match?(/gesture/)
|
|
@@ -43,6 +46,7 @@ module Fusuma
|
|
|
43
46
|
|
|
44
47
|
# @raise [SystemExit]
|
|
45
48
|
# @return [Array]
|
|
49
|
+
#: () -> Array[Device]
|
|
46
50
|
def available
|
|
47
51
|
@available ||= all.select(&:available).tap do |d|
|
|
48
52
|
MultiLogger.debug(available_devices: d)
|
|
@@ -54,6 +58,7 @@ module Fusuma
|
|
|
54
58
|
exit 1
|
|
55
59
|
end
|
|
56
60
|
|
|
61
|
+
#: () -> nil
|
|
57
62
|
def reset
|
|
58
63
|
@all = nil
|
|
59
64
|
@available = nil
|
|
@@ -62,6 +67,7 @@ module Fusuma
|
|
|
62
67
|
private
|
|
63
68
|
|
|
64
69
|
# @return [Array]
|
|
70
|
+
#: () -> Array[Device]
|
|
65
71
|
def fetch_devices
|
|
66
72
|
line_parser = LineParser.new
|
|
67
73
|
|
|
@@ -72,6 +78,7 @@ module Fusuma
|
|
|
72
78
|
line_parser.generate_devices
|
|
73
79
|
end
|
|
74
80
|
|
|
81
|
+
#: () -> Fusuma::LibinputCommand
|
|
75
82
|
def libinput_command
|
|
76
83
|
@libinput_command ||= Plugin::Inputs::LibinputCommandInput.new.command
|
|
77
84
|
end
|
|
@@ -81,16 +88,19 @@ module Fusuma
|
|
|
81
88
|
class LineParser
|
|
82
89
|
attr_reader :lines
|
|
83
90
|
|
|
91
|
+
#: () -> void
|
|
84
92
|
def initialize
|
|
85
93
|
@lines = []
|
|
86
94
|
end
|
|
87
95
|
|
|
88
96
|
# @param line [String]
|
|
97
|
+
#: (String) -> Array[untyped]
|
|
89
98
|
def push(line)
|
|
90
99
|
lines.push(line)
|
|
91
100
|
end
|
|
92
101
|
|
|
93
102
|
# @return [Array]
|
|
103
|
+
#: () -> Array[Device]
|
|
94
104
|
def generate_devices
|
|
95
105
|
lines.each_with_object([]) do |line, devices|
|
|
96
106
|
attributes = extract_attribute(line: line)
|
|
@@ -108,6 +118,7 @@ module Fusuma
|
|
|
108
118
|
|
|
109
119
|
# @param line [String]
|
|
110
120
|
# @return [Hash]
|
|
121
|
+
#: (line: String) -> Hash[untyped, untyped]
|
|
111
122
|
def extract_attribute(line:)
|
|
112
123
|
if (id = id_from(line))
|
|
113
124
|
{id: id}
|
|
@@ -122,24 +133,28 @@ module Fusuma
|
|
|
122
133
|
end
|
|
123
134
|
end
|
|
124
135
|
|
|
136
|
+
#: (String) -> String?
|
|
125
137
|
def id_from(line)
|
|
126
138
|
line.match("^Kernel:[[:space:]]*") do |m|
|
|
127
139
|
m.post_match.match(/event[0-9]+/).to_s
|
|
128
140
|
end
|
|
129
141
|
end
|
|
130
142
|
|
|
143
|
+
#: (String) -> String?
|
|
131
144
|
def name_from(line)
|
|
132
145
|
line.match("^Device:[[:space:]]*") do |m|
|
|
133
146
|
m.post_match.strip
|
|
134
147
|
end
|
|
135
148
|
end
|
|
136
149
|
|
|
150
|
+
#: (String) -> String?
|
|
137
151
|
def capabilities_from(line)
|
|
138
152
|
line.match("^Capabilities:[[:space:]]*") do |m|
|
|
139
153
|
m.post_match.strip
|
|
140
154
|
end
|
|
141
155
|
end
|
|
142
156
|
|
|
157
|
+
#: (String) -> bool?
|
|
143
158
|
def available_from(line)
|
|
144
159
|
# NOTE: is natural scroll available?
|
|
145
160
|
if /^Nat.scrolling: /.match?(line)
|
data/lib/fusuma/environment.rb
CHANGED
|
@@ -8,6 +8,7 @@ module Fusuma
|
|
|
8
8
|
# Output Environment information
|
|
9
9
|
class Environment
|
|
10
10
|
class << self
|
|
11
|
+
#: () -> void
|
|
11
12
|
def dump_information
|
|
12
13
|
MultiLogger.info "---------------------------------------------"
|
|
13
14
|
print_version
|
|
@@ -16,6 +17,7 @@ module Fusuma
|
|
|
16
17
|
MultiLogger.info "---------------------------------------------"
|
|
17
18
|
end
|
|
18
19
|
|
|
20
|
+
#: () -> void
|
|
19
21
|
def print_version
|
|
20
22
|
libinput_command = Plugin::Inputs::LibinputCommandInput.new.command
|
|
21
23
|
MultiLogger.info "Fusuma: #{VERSION}"
|
|
@@ -26,6 +28,7 @@ module Fusuma
|
|
|
26
28
|
MultiLogger.info "Desktop session: #{`echo $DESKTOP_SESSION $XDG_SESSION_TYPE`}".strip
|
|
27
29
|
end
|
|
28
30
|
|
|
31
|
+
#: () -> void
|
|
29
32
|
def print_enabled_plugins
|
|
30
33
|
MultiLogger.info "Enabled Plugins: "
|
|
31
34
|
Plugin::Manager.plugins
|
|
@@ -34,6 +37,7 @@ module Fusuma
|
|
|
34
37
|
.flatten.sort.each { |name| MultiLogger.info(name) }
|
|
35
38
|
end
|
|
36
39
|
|
|
40
|
+
#: () -> void
|
|
37
41
|
def print_device_list
|
|
38
42
|
Plugin::Filters::LibinputDeviceFilter.new.keep_device.all.map do |device|
|
|
39
43
|
puts device.name
|
data/lib/fusuma/hash_support.rb
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
#
|
|
3
|
+
# @rbs generic unchecked out K
|
|
4
|
+
# @rbs generic unchecked out V
|
|
4
5
|
class Hash
|
|
5
6
|
# activesupport-5.2.0/lib/active_support/core_ext/hash/deep_merge.rb
|
|
6
7
|
def deep_merge(other_hash, &block)
|
|
@@ -25,6 +26,7 @@ class Hash
|
|
|
25
26
|
end
|
|
26
27
|
|
|
27
28
|
# activesupport-4.1.1/lib/active_support/core_ext/hash/keys.rb
|
|
29
|
+
#: () -> Hash[Symbol, untyped]
|
|
28
30
|
def deep_symbolize_keys
|
|
29
31
|
deep_transform_keys do |key|
|
|
30
32
|
key.to_sym
|
|
@@ -33,6 +35,7 @@ class Hash
|
|
|
33
35
|
end
|
|
34
36
|
end
|
|
35
37
|
|
|
38
|
+
#: [T] () { (untyped) -> T } -> Hash[T, untyped]
|
|
36
39
|
def deep_transform_keys(&block)
|
|
37
40
|
result = {}
|
|
38
41
|
each do |key, value|
|
|
@@ -42,6 +45,7 @@ class Hash
|
|
|
42
45
|
end
|
|
43
46
|
|
|
44
47
|
# activesupport/lib/active_support/core_ext/hash/deep_transform_values.rb
|
|
48
|
+
#: () { (untyped) -> untyped } -> Hash[untyped, untyped]
|
|
45
49
|
def deep_transform_values(&block)
|
|
46
50
|
_deep_transform_values_in_object(self, &block)
|
|
47
51
|
end
|
|
@@ -49,6 +53,7 @@ class Hash
|
|
|
49
53
|
private
|
|
50
54
|
|
|
51
55
|
# Support methods for deep transforming nested hashes and arrays.
|
|
56
|
+
#: (Hash[untyped, untyped]) { (untyped) -> untyped } -> Hash[untyped, untyped]
|
|
52
57
|
def _deep_transform_values_in_object(object, &block)
|
|
53
58
|
case object
|
|
54
59
|
when Hash
|
|
@@ -5,6 +5,7 @@ require "open3"
|
|
|
5
5
|
module Fusuma
|
|
6
6
|
# Execute libinput command
|
|
7
7
|
class LibinputCommand
|
|
8
|
+
#: (?libinput_options: Array[String], ?commands: Hash[untyped, untyped]) -> void
|
|
8
9
|
def initialize(libinput_options: [], commands: {})
|
|
9
10
|
@libinput_command = commands[:libinput_command]
|
|
10
11
|
@debug_events_command = commands[:debug_events_command]
|
|
@@ -17,6 +18,7 @@ module Fusuma
|
|
|
17
18
|
NEW_CLI_OPTION_VERSION = "1.8"
|
|
18
19
|
|
|
19
20
|
# @return [Boolean]
|
|
21
|
+
#: () -> bool
|
|
20
22
|
def new_cli_option_available?
|
|
21
23
|
Gem::Version.new(version) >= Gem::Version.new(NEW_CLI_OPTION_VERSION)
|
|
22
24
|
end
|
|
@@ -27,32 +29,35 @@ module Fusuma
|
|
|
27
29
|
end
|
|
28
30
|
|
|
29
31
|
# @return [String]
|
|
32
|
+
#: () -> String?
|
|
30
33
|
def version
|
|
31
34
|
# version_command prints "1.6.3\n"
|
|
32
35
|
@version ||= `#{version_command}`.strip
|
|
33
36
|
end
|
|
34
37
|
|
|
35
38
|
# @yieldparam [String] gives a line in libinput list-devices output to the block
|
|
39
|
+
#: () { (String) -> void } -> void
|
|
36
40
|
def list_devices(&block)
|
|
37
41
|
cmd = list_devices_command
|
|
38
42
|
MultiLogger.debug(list_devices: cmd)
|
|
39
|
-
o,
|
|
43
|
+
o, _, s = Open3.capture3(cmd)
|
|
40
44
|
|
|
41
45
|
unless s.success?
|
|
42
|
-
|
|
43
|
-
return
|
|
46
|
+
raise "libinput list-devices failed with output: #{o}"
|
|
44
47
|
end
|
|
45
48
|
|
|
46
49
|
o.each_line(&block)
|
|
47
50
|
end
|
|
48
51
|
|
|
49
52
|
# @return [Integer] return a latest line libinput debug-events
|
|
53
|
+
#: (StringIO) -> Array[untyped]
|
|
50
54
|
def debug_events(writer)
|
|
51
55
|
Open3.pipeline_start([debug_events_with_options], ["grep -v POINTER_ --line-buffered"], out: writer, in: "/dev/null")
|
|
52
56
|
end
|
|
53
57
|
|
|
54
58
|
# @return [String] command
|
|
55
59
|
# @raise [SystemExit]
|
|
60
|
+
#: () -> String?
|
|
56
61
|
def version_command
|
|
57
62
|
if @libinput_command
|
|
58
63
|
"#{@libinput_command} --version"
|
|
@@ -68,6 +73,7 @@ module Fusuma
|
|
|
68
73
|
end
|
|
69
74
|
end
|
|
70
75
|
|
|
76
|
+
#: () -> String
|
|
71
77
|
def list_devices_command
|
|
72
78
|
if @libinput_command
|
|
73
79
|
@libinput_command + " list-devices"
|
|
@@ -80,6 +86,7 @@ module Fusuma
|
|
|
80
86
|
end
|
|
81
87
|
end
|
|
82
88
|
|
|
89
|
+
#: () -> String
|
|
83
90
|
def debug_events_command
|
|
84
91
|
if @libinput_command
|
|
85
92
|
@libinput_command + " debug-events"
|
|
@@ -92,6 +99,7 @@ module Fusuma
|
|
|
92
99
|
end
|
|
93
100
|
end
|
|
94
101
|
|
|
102
|
+
#: () -> String
|
|
95
103
|
def debug_events_with_options
|
|
96
104
|
prefix = "stdbuf -oL --"
|
|
97
105
|
"#{prefix} #{debug_events_command} #{@libinput_options.join(" ")}".strip
|