fusuma 3.0.0 → 3.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4670e37d93cd689661c036a4d2a25361af7a283f407b3ffc98dd1d275d6ef675
4
- data.tar.gz: 78735688ac87ab4c09ebd0f7b69000a56e5543916bd41b490df02b7130525832
3
+ metadata.gz: 8ff7ea9fcd08ba0c48e1c08d9eceeb4a1e30684a0616f37fef6f67a3e035c005
4
+ data.tar.gz: c68adc10a8e2b7fb5396c7c789f941b13bacc9d60d6d669a7d8004c773503d75
5
5
  SHA512:
6
- metadata.gz: 998e0a7cd21c42870a7b747a0280f4aa55695bea79139f0bef3b2dbcedefc448548f4ed261b2993a67c6e9e5921fa2dcab93b595153fc9b9a04818816fdf605f
7
- data.tar.gz: 8892cf28ab5d7bce157fbf7eca3c9458565585b223d36f9c15f7cc0b2d3d08248c643504f00bc33aac51b88e86270c59b3c886d968e1ce52f7f51a4614dcb16e
6
+ metadata.gz: 46fdbcb0b697ba9000e55de86a9550d783c81c6407f13ebe82375c2b741341a591bcf45fe6c707247a9a2017e4f3ca4834111af551a4a65001cc7e944487b28e
7
+ data.tar.gz: e0e579b0d73ea237202ae17f641c07a1ed7587a771c71bfe12d55edec62b3d9e3281fd010f646e6e7077a6245e0365d0ed73c7042528a8c8c5ac72bbeed956de
data/exe/fusuma CHANGED
@@ -27,6 +27,10 @@ opt.on('--log=path/to/file',
27
27
  option[:log_filepath] = v
28
28
  end
29
29
 
30
+ opt.on('--show-config', 'Show config as YAML format which is loaded internally') do |v|
31
+ option[:show_config] = v
32
+ end
33
+
30
34
  opt.on('--device="Device name"',
31
35
  'Open the given device only (DEPRECATED)') do |v|
32
36
  option[:device] = v
data/fusuma.gemspec CHANGED
@@ -24,6 +24,7 @@ Gem::Specification.new do |spec|
24
24
  "yard.run" => "yri" # use "yard" to build full HTML docs.
25
25
  }
26
26
 
27
- spec.required_ruby_version = ">= 2.5.1" # https://packages.ubuntu.com/search?keywords=ruby&searchon=names&exact=1&suite=all&section=main
28
- # support bionic (18.04LTS) 2.5.1
27
+ spec.required_ruby_version = ">= 2.7"
28
+ # https://packages.ubuntu.com/search?keywords=ruby&searchon=names&exact=1&suite=all&section=main
29
+ # support focal (20.04LTS) 2.7
29
30
  end
@@ -44,7 +44,7 @@ module Fusuma
44
44
  if @skippable
45
45
  "#{@symbol}(skippable)"
46
46
  else
47
- "#{@symbol}"
47
+ @symbol.to_s
48
48
  end
49
49
  end
50
50
 
@@ -76,6 +76,8 @@ module Fusuma
76
76
  end
77
77
 
78
78
  class << self
79
+ attr_reader :context
80
+
79
81
  # Search with context from load_streamed Config
80
82
  # @param context [Hash]
81
83
  # @return [Object]
@@ -86,20 +88,50 @@ module Fusuma
86
88
  result
87
89
  end
88
90
 
91
+ CONEXT_SEARCH_ORDER = [:no_context, :complete_match_context, :partial_match_context]
89
92
  # Return a matching context from config
90
93
  # @params request_context [Hash]
91
94
  # @return [Hash]
92
- def find_context(request_context, &block)
95
+ def find_context(request_context, fallbacks = CONEXT_SEARCH_ORDER, &block)
93
96
  # Search in blocks in the following order.
94
97
  # 1. primary context(no context)
95
98
  # 2. complete match config[:context] == request_context
96
99
  # 3. partial match config[:context] =~ request_context
100
+ # no_context?(&block) ||
101
+ # complete_match_context(request_context, &block) ||
102
+ # partial_match_context(request_context, &block)
103
+ fallbacks.each do |method|
104
+ result = send(method, request_context, &block)
105
+ return result if result
106
+ end
107
+ end
108
+
109
+ private
110
+
111
+ # No context(primary context)
112
+ # @return [Hash]
113
+ # @return [NilClass]
114
+ def no_context(_request_context, &block)
97
115
  return {} if with_context({}, &block)
116
+ end
98
117
 
118
+ # Complete match request context
119
+ # @param request_context [Hash]
120
+ # @return [Hash] matched context
121
+ # @return [NilClass] if not matched
122
+ def complete_match_context(request_context, &block)
99
123
  Config.instance.keymap.each do |config|
100
124
  next unless config[:context] == request_context
101
125
  return config[:context] if with_context(config[:context], &block)
102
126
  end
127
+ nil
128
+ end
129
+
130
+ # One of multiple request contexts matched
131
+ # @param request_context [Hash]
132
+ # @return [Hash] matched context
133
+ # @return [NilClass] if not matched
134
+ def partial_match_context(request_context, &block)
103
135
  if request_context.keys.size > 1
104
136
  Config.instance.keymap.each do |config|
105
137
  next if config[:context].nil?
@@ -107,11 +139,34 @@ module Fusuma
107
139
  next unless config[:context].all? { |k, v| request_context[k] == v }
108
140
  return config[:context] if with_context(config[:context], &block)
109
141
  end
142
+ nil
110
143
  end
111
144
  end
112
145
 
113
- attr_reader :context
146
+ # Search context for plugin
147
+ # If the plugin_defaults key is a complete match,
148
+ # it is the default value for that plugin, so it is postponed.
149
+ # This is because prioritize overwriting by other plugins.
150
+ # The search order is as follows
151
+ # 1. complete match config[:context].key?(:plugin_defaults)
152
+ # 2. complete match config[:context] == request_context
153
+ # @param request_context [Hash]
154
+ # @return [Hash] matched context
155
+ # @return [NilClass] if not matched
156
+ def plugin_default_context(request_context, &block)
157
+ complete_match_context = nil
158
+ Config.instance.keymap.each do |config|
159
+ next unless config[:context]&.key?(:plugin_defaults)
160
+
161
+ if config[:context][:plugin_defaults] == request_context[:plugin_defaults]
162
+ complete_match_context = config[:context]
163
+ next
164
+ end
114
165
 
166
+ return config[:context] if with_context(config[:context], &block)
167
+ end
168
+ complete_match_context
169
+ end
115
170
  end
116
171
  end
117
172
  end
data/lib/fusuma/config.rb CHANGED
@@ -4,6 +4,7 @@ require_relative "./multi_logger"
4
4
  require_relative "./config/index"
5
5
  require_relative "./config/searcher"
6
6
  require_relative "./config/yaml_duplication_checker"
7
+ require_relative "./plugin/manager"
7
8
  require_relative "./hash_support"
8
9
  require "singleton"
9
10
  require "yaml"
@@ -32,7 +33,7 @@ module Fusuma
32
33
  end
33
34
  end
34
35
 
35
- attr_reader :keymap, :custom_path, :searcher
36
+ attr_reader :custom_path, :searcher
36
37
 
37
38
  def initialize
38
39
  @searcher = Searcher.new
@@ -45,17 +46,47 @@ module Fusuma
45
46
  reload
46
47
  end
47
48
 
49
+ def keymap
50
+ # FIXME: @keymap is not initialized when called from outside Fusuma::Runner like fusuma-senkey
51
+ @keymap || reload.keymap
52
+ end
53
+
48
54
  def reload
55
+ plugin_defaults = plugin_defaults_paths.map do |default_yml|
56
+ {
57
+ context: {plugin_defaults: default_yml.split("/").last.delete_suffix(".yml")},
58
+ **validate(default_yml)[0]
59
+ }
60
+ end
61
+
62
+ config_path = find_config_filepath
63
+ @keymap = validate(config_path) | plugin_defaults
64
+ MultiLogger.info "reload config: #{config_path}"
65
+
66
+ # reset searcher cache
49
67
  @searcher = Searcher.new
50
- path = find_filepath
51
- MultiLogger.info "reload config: #{path}"
52
- @keymap = validate(path)
68
+
53
69
  self
54
70
  rescue InvalidFileError => e
55
71
  MultiLogger.error e.message
56
72
  exit 1
57
73
  end
58
74
 
75
+ # @param key [Symbol]
76
+ # @param base [Config::Index]
77
+ # @return [Hash]
78
+ def fetch_config_params(key, base)
79
+ request_context = {plugin_defaults: base.keys.last.symbol.to_s}
80
+ fallbacks = [:no_context, :plugin_default_context]
81
+ Config::Searcher.find_context(request_context, fallbacks) do
82
+ ret = Config.search(base)
83
+ if ret&.key?(key)
84
+ return ret
85
+ end
86
+ end
87
+ {}
88
+ end
89
+
59
90
  # @return [Hash] If check passes
60
91
  # @raise [InvalidFileError] If check does not pass
61
92
  def validate(path)
@@ -75,7 +106,6 @@ module Fusuma
75
106
  end
76
107
 
77
108
  # @param index [Index]
78
- # @param context [Hash]
79
109
  def search(index)
80
110
  @searcher.search_with_cache(index, location: keymap)
81
111
  end
@@ -95,7 +125,7 @@ module Fusuma
95
125
 
96
126
  private
97
127
 
98
- def find_filepath
128
+ def find_config_filepath
99
129
  filename = "fusuma/config.yml"
100
130
  if custom_path
101
131
  return expand_custom_path if File.exist?(expand_custom_path)
@@ -120,5 +150,12 @@ module Fusuma
120
150
  def expand_default_path(filename)
121
151
  File.expand_path "../../#{filename}", __FILE__
122
152
  end
153
+
154
+ def plugin_defaults_paths
155
+ Plugin::Manager.load_paths.map do |plugin_path|
156
+ yml = plugin_path.gsub(/\.rb$/, ".yml")
157
+ yml if File.exist?(yml)
158
+ end.compact
159
+ end
123
160
  end
124
161
  end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "fusuma/string_support"
3
+ require_relative "./string_support"
4
4
 
5
5
  module Fusuma
6
6
  # Rename process
@@ -39,6 +39,12 @@ module Fusuma
39
39
  puts device.name
40
40
  end
41
41
  end
42
+
43
+ def print_config
44
+ Config.instance.keymap.each do |conf|
45
+ puts conf.deep_stringify_keys.to_yaml
46
+ end
47
+ end
42
48
  end
43
49
  end
44
50
  end
@@ -2,6 +2,28 @@
2
2
 
3
3
  # Patch to hash
4
4
  class Hash
5
+ # activesupport-5.2.0/lib/active_support/core_ext/hash/deep_merge.rb
6
+ def deep_merge(other_hash, &block)
7
+ dup.deep_merge!(other_hash, &block)
8
+ end
9
+
10
+ # Same as +deep_merge+, but modifies +self+.
11
+ def deep_merge!(other_hash, &block)
12
+ merge!(other_hash) do |key, this_val, other_val|
13
+ if this_val.is_a?(Hash) && other_val.is_a?(Hash)
14
+ this_val.deep_merge(other_val, &block)
15
+ elsif block
16
+ block.call(key, this_val, other_val)
17
+ else
18
+ other_val
19
+ end
20
+ end
21
+ end
22
+
23
+ def deep_stringify_keys
24
+ deep_transform_keys(&:to_s)
25
+ end
26
+
5
27
  # activesupport-4.1.1/lib/active_support/core_ext/hash/keys.rb
6
28
  def deep_symbolize_keys
7
29
  deep_transform_keys do |key|
@@ -31,14 +31,20 @@ module Fusuma
31
31
  raise NotImplementedError, "override #{self.class.name}##{__method__}"
32
32
  end
33
33
 
34
+ # @param key [Symbol]
35
+ # @param base [Config::Index]
34
36
  # @return [Object]
35
- def config_params(key = nil, base: config_index)
36
- params = Config.search(base) || {}
37
+ def config_params(key = nil)
38
+ @config_params ||= {}
39
+ if @config_params["#{config_index.cache_key},#{key}"]
40
+ return @config_params["#{config_index.cache_key},#{key}"]
41
+ end
42
+
43
+ params = Config.instance.fetch_config_params(key, config_index)
37
44
 
38
45
  return params unless key
39
46
 
40
- @config_params ||= {}
41
- @config_params["#{base.cache_key},#{key}"] ||=
47
+ @config_params["#{config_index.cache_key},#{key}"] =
42
48
  params.fetch(key, nil).tap do |val|
43
49
  next if val.nil?
44
50
 
@@ -48,14 +54,14 @@ module Fusuma
48
54
  next if param_types.any? { |klass| val.is_a?(klass) }
49
55
 
50
56
  MultiLogger.error("Please fix config.yml.")
51
- MultiLogger.error(":#{base.keys.map(&:symbol)
57
+ MultiLogger.error(":#{config_index.keys.map(&:symbol)
52
58
  .join(" => :")} => :#{key} should be #{param_types.join(" OR ")}.")
53
59
  exit 1
54
60
  end
55
61
  end
56
62
 
57
63
  def config_index
58
- Config::Index.new(self.class.name.gsub("Fusuma::", "").underscore.split("/"))
64
+ @config_index ||= Config::Index.new(self.class.name.gsub("Fusuma::", "").underscore.split("/"))
59
65
  end
60
66
  end
61
67
  end
@@ -11,7 +11,6 @@ module Fusuma
11
11
  DEFAULT_SOURCE = "libinput_gesture_parser"
12
12
  DEFAULT_SECONDS_TO_KEEP = 100
13
13
 
14
-
15
14
  def initialize(*args)
16
15
  super(*args)
17
16
  @cache = {}
@@ -84,15 +83,15 @@ module Fusuma
84
83
  # @param attr [Symbol]
85
84
  # @return [Float]
86
85
  def sum_last10_attrs(attr) # sums last 10 values of attr (or all if length < 10)
87
- cache_entry = ( @cache_sum10[attr] ||= CacheEntry.new(0, 0) )
86
+ cache_entry = (@cache_sum10[attr] ||= CacheEntry.new(0, 0))
88
87
  upd_ev = updating_events
89
- if upd_ev.length > cache_entry.checked + 1 then
88
+ if upd_ev.length > cache_entry.checked + 1
90
89
  cache_entry.value = upd_ev.last(10).map do |gesture_event|
91
90
  gesture_event.record.delta[attr].to_f
92
91
  end.reduce(:+)
93
92
  elsif upd_ev.length > cache_entry.checked
94
93
  cache_entry.value = cache_entry.value + upd_ev[-1].record.delta[attr].to_f - \
95
- (upd_ev.length > 10 ? upd_ev[-11].record.delta[attr].to_f : 0)
94
+ ((upd_ev.length > 10) ? upd_ev[-11].record.delta[attr].to_f : 0)
96
95
  else
97
96
  return cache_entry.value
98
97
  end
@@ -101,7 +100,7 @@ module Fusuma
101
100
  end
102
101
 
103
102
  def updating_events
104
- cache_entry = ( @cache[:updating_events] ||= CacheEntry.new(0, []) )
103
+ cache_entry = (@cache[:updating_events] ||= CacheEntry.new(0, []))
105
104
  cache_entry.checked.upto(@events.length - 1).each do |i|
106
105
  (cache_entry.value << @events[i]) if @events[i].record.status == "update"
107
106
  end
@@ -140,7 +139,7 @@ module Fusuma
140
139
  end
141
140
 
142
141
  def select_by_type(type)
143
- cache_entry = ( @cache_select_by[type] ||= CacheEntry.new(0, self.class.new([])) )
142
+ cache_entry = (@cache_select_by[type] ||= CacheEntry.new(0, self.class.new([])))
144
143
  cache_entry.checked.upto(@events.length - 1).each do |i|
145
144
  (cache_entry.value.events << @events[i]) if @events[i].record.gesture == type
146
145
  end
@@ -150,7 +149,7 @@ module Fusuma
150
149
 
151
150
  def select_from_last_begin
152
151
  return self if empty?
153
- cache_entry = ( @cache[:last_begin] ||= CacheEntry.new(0, nil) )
152
+ cache_entry = (@cache[:last_begin] ||= CacheEntry.new(0, nil))
154
153
 
155
154
  cache_entry.value = (@events.length - 1).downto(cache_entry.checked).find do |i|
156
155
  @events[i].record.status == "begin"
@@ -11,10 +11,14 @@ module Fusuma
11
11
  SOURCES = %w[gesture timer].freeze
12
12
  BUFFER_TYPE = "gesture"
13
13
  GESTURE_RECORD_TYPE = "hold"
14
- Timer = Inputs::TimerInput.instance
15
14
 
16
15
  BASE_THERESHOLD = 0.7
17
16
 
17
+ def initialize(*args)
18
+ super(*args)
19
+ @timer = Inputs::TimerInput.instance
20
+ end
21
+
18
22
  # @param buffers [Array<Buffers::Buffer>]
19
23
  # @return [Events::Event] if event is detected
20
24
  # @return [Array<Events::Event>] if hold end event is detected
@@ -50,10 +54,10 @@ module Fusuma
50
54
  repeat_index = create_repeat_index(finger: finger, status: status)
51
55
  oneshot_index = create_oneshot_index(finger: finger)
52
56
 
53
- if status == "begin" then
57
+ if status == "begin"
54
58
  @timeout = nil
55
- if threshold(index: oneshot_index) < Timer.interval then
56
- Timer.wake_early(Time.now + threshold(index: oneshot_index))
59
+ if threshold(index: oneshot_index) < @timer.interval
60
+ @timer.wake_early(Time.now + threshold(index: oneshot_index))
57
61
  end
58
62
  elsif status == "timer"
59
63
  return if @timeout
@@ -115,10 +119,10 @@ module Fusuma
115
119
 
116
120
  def enough?(index:, holding_time:)
117
121
  diff = threshold(index: index) - holding_time
118
- if diff < 0 then
122
+ if diff < 0
119
123
  true
120
- elsif diff < Timer.interval
121
- Timer.wake_early(Time.now + diff)
124
+ elsif diff < @timer.interval
125
+ @timer.wake_early(Time.now + diff)
122
126
  false
123
127
  end
124
128
  end
@@ -167,7 +167,7 @@ module Fusuma
167
167
  end
168
168
 
169
169
  def calc
170
- @x > @y ? @x.abs : @y.abs
170
+ (@x > @y) ? @x.abs : @y.abs
171
171
  end
172
172
  end
173
173
  end
@@ -0,0 +1,3 @@
1
+ plugin:
2
+ inputs:
3
+ libinput_command_input:
@@ -115,6 +115,12 @@ module Fusuma
115
115
  @plugins ||= {}
116
116
  end
117
117
 
118
+ # @return [Array<String>]
119
+ # @example
120
+ # Manager.load_paths
121
+ # => ["/path/to/fusuma/lib/fusuma/plugin/inputs/input.rb",
122
+ # "/path/to/fusuma/lib/fusuma/plugin/inputs/libinput_command_input.rb",
123
+ # "/path/to/fusuma/lib/fusuma/plugin/inputs/timer_input.rb"]
118
124
  def load_paths
119
125
  @load_paths ||= []
120
126
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Fusuma
4
- VERSION = "3.0.0"
4
+ VERSION = "3.1.0"
5
5
  end
data/lib/fusuma.rb CHANGED
@@ -27,13 +27,18 @@ module Fusuma
27
27
  MultiLogger.filepath = option[:log_filepath]
28
28
  MultiLogger.instance.debug_mode = option[:verbose]
29
29
 
30
- load_custom_config(option[:config_path])
31
-
32
30
  Plugin::Manager.require_base_plugins
33
31
 
32
+ load_custom_config(option[:config_path])
33
+
34
34
  Environment.dump_information
35
35
  Kernel.exit(0) if option[:version]
36
36
 
37
+ if option[:show_config]
38
+ Environment.print_config
39
+ Kernel.exit(0)
40
+ end
41
+
37
42
  if option[:list]
38
43
  Environment.print_device_list
39
44
  Kernel.exit(0)
@@ -147,7 +152,7 @@ module Fusuma
147
152
  matched_context = nil
148
153
  event = main_events.find do |main_event|
149
154
  matched_context = Config::Searcher.find_context(request_context) do
150
- if modified_record = main_event.record.merge(records: modifiers.map(&:record))
155
+ if (modified_record = main_event.record.merge(records: modifiers.map(&:record)))
151
156
  main_event.record = modified_record
152
157
  elsif !modifiers.empty?
153
158
  # try basically the same, but without any modifiers
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fusuma
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.0.0
4
+ version: 3.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - iberianpig
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2023-08-19 00:00:00.000000000 Z
11
+ date: 2023-09-03 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: Fusuma is multitouch gesture recognizer. This gem makes your touchpad
14
14
  on Linux able to recognize swipes or pinchs and assign command to them. Read installation
@@ -59,6 +59,7 @@ files:
59
59
  - lib/fusuma/plugin/filters/libinput_device_filter.rb
60
60
  - lib/fusuma/plugin/inputs/input.rb
61
61
  - lib/fusuma/plugin/inputs/libinput_command_input.rb
62
+ - lib/fusuma/plugin/inputs/libinput_command_input.yml
62
63
  - lib/fusuma/plugin/inputs/timer_input.rb
63
64
  - lib/fusuma/plugin/manager.rb
64
65
  - lib/fusuma/plugin/parsers/libinput_gesture_parser.rb
@@ -79,14 +80,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
79
80
  requirements:
80
81
  - - ">="
81
82
  - !ruby/object:Gem::Version
82
- version: 2.5.1
83
+ version: '2.7'
83
84
  required_rubygems_version: !ruby/object:Gem::Requirement
84
85
  requirements:
85
86
  - - ">="
86
87
  - !ruby/object:Gem::Version
87
88
  version: '0'
88
89
  requirements: []
89
- rubygems_version: 3.3.26
90
+ rubygems_version: 3.4.10
90
91
  signing_key:
91
92
  specification_version: 4
92
93
  summary: Multitouch gestures with libinput driver, Linux