fusuma 1.11.1 → 2.0.2
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 +49 -7
- data/fusuma.gemspec +6 -16
- data/lib/fusuma.rb +91 -28
- data/lib/fusuma/config.rb +34 -62
- data/lib/fusuma/config/index.rb +39 -6
- data/lib/fusuma/config/searcher.rb +166 -0
- data/lib/fusuma/custom_process.rb +13 -0
- data/lib/fusuma/device.rb +22 -7
- data/lib/fusuma/environment.rb +6 -4
- data/lib/fusuma/hash_support.rb +40 -0
- data/lib/fusuma/libinput_command.rb +17 -21
- data/lib/fusuma/multi_logger.rb +2 -6
- data/lib/fusuma/plugin/base.rb +18 -15
- data/lib/fusuma/plugin/buffers/buffer.rb +3 -2
- data/lib/fusuma/plugin/buffers/gesture_buffer.rb +34 -25
- data/lib/fusuma/plugin/buffers/timer_buffer.rb +46 -0
- data/lib/fusuma/plugin/detectors/detector.rb +26 -5
- data/lib/fusuma/plugin/detectors/pinch_detector.rb +109 -58
- data/lib/fusuma/plugin/detectors/rotate_detector.rb +91 -50
- data/lib/fusuma/plugin/detectors/swipe_detector.rb +93 -56
- data/lib/fusuma/plugin/events/event.rb +5 -4
- data/lib/fusuma/plugin/events/records/context_record.rb +27 -0
- data/lib/fusuma/plugin/events/records/gesture_record.rb +9 -6
- data/lib/fusuma/plugin/events/records/index_record.rb +46 -14
- data/lib/fusuma/plugin/events/records/record.rb +1 -1
- data/lib/fusuma/plugin/events/records/text_record.rb +2 -1
- data/lib/fusuma/plugin/executors/command_executor.rb +21 -6
- data/lib/fusuma/plugin/executors/executor.rb +45 -3
- data/lib/fusuma/plugin/filters/filter.rb +1 -1
- data/lib/fusuma/plugin/filters/libinput_device_filter.rb +6 -7
- data/lib/fusuma/plugin/filters/libinput_timeout_filter.rb +2 -2
- data/lib/fusuma/plugin/inputs/input.rb +64 -8
- data/lib/fusuma/plugin/inputs/libinput_command_input.rb +19 -9
- data/lib/fusuma/plugin/inputs/timer_input.rb +63 -0
- data/lib/fusuma/plugin/manager.rb +22 -29
- data/lib/fusuma/plugin/parsers/libinput_gesture_parser.rb +10 -8
- data/lib/fusuma/plugin/parsers/parser.rb +8 -9
- data/lib/fusuma/string_support.rb +16 -0
- data/lib/fusuma/version.rb +1 -1
- data/spec/helpers/config_helper.rb +20 -0
- data/spec/lib/config/searcher_spec.rb +97 -0
- data/spec/lib/config_spec.rb +112 -0
- data/spec/lib/custom_process_spec.rb +28 -0
- data/spec/lib/device_spec.rb +98 -0
- data/spec/lib/dummy_config.yml +31 -0
- data/spec/lib/fusuma_spec.rb +103 -0
- data/spec/lib/libinput-list-devices_iberianpig-XPS-9360.txt +181 -0
- data/spec/lib/libinput-list-devices_magic_trackpad.txt +51 -0
- data/spec/lib/libinput-list-devices_razer_razer_blade.txt +252 -0
- data/spec/lib/libinput-list-devices_thejinx0r.txt +361 -0
- data/spec/lib/libinput-list-devices_unavailable.txt +36 -0
- data/spec/lib/libinput_command_spec.rb +167 -0
- data/spec/lib/plugin/base_spec.rb +74 -0
- data/spec/lib/plugin/buffers/buffer_spec.rb +80 -0
- data/spec/lib/plugin/buffers/dummy_buffer.rb +20 -0
- data/spec/lib/plugin/buffers/gesture_buffer_spec.rb +172 -0
- data/spec/lib/plugin/detectors/detector_spec.rb +43 -0
- data/spec/lib/plugin/detectors/dummy_detector.rb +24 -0
- data/spec/lib/plugin/detectors/pinch_detector_spec.rb +119 -0
- data/spec/lib/plugin/detectors/rotate_detector_spec.rb +125 -0
- data/spec/lib/plugin/detectors/swipe_detector_spec.rb +118 -0
- data/spec/lib/plugin/events/event_spec.rb +30 -0
- data/spec/lib/plugin/events/records/gesture_record_spec.rb +22 -0
- data/spec/lib/plugin/events/records/record_spec.rb +31 -0
- data/spec/lib/plugin/events/records/text_record_spec.rb +26 -0
- data/spec/lib/plugin/executors/command_executor_spec.rb +57 -0
- data/spec/lib/plugin/executors/executor_spec.rb +160 -0
- data/spec/lib/plugin/filters/filter_spec.rb +92 -0
- data/spec/lib/plugin/filters/libinput_filter_spec.rb +120 -0
- data/spec/lib/plugin/inputs/input_spec.rb +70 -0
- data/spec/lib/plugin/inputs/libinput_command_input_spec.rb +120 -0
- data/spec/lib/plugin/inputs/timer_input_spec.rb +40 -0
- data/spec/lib/plugin/manager_spec.rb +27 -0
- data/spec/lib/plugin/parsers/parser_spec.rb +45 -0
- data/spec/spec_helper.rb +20 -0
- metadata +90 -167
- data/.github/FUNDING.yml +0 -8
- data/.github/ISSUE_TEMPLATE/bug_report.md +0 -32
- data/.github/ISSUE_TEMPLATE/feature_request.md +0 -17
- data/.github/pull_request_template.md +0 -9
- data/.github/stale.yml +0 -18
- data/.gitignore +0 -17
- data/.reek.yml +0 -96
- data/.rspec +0 -2
- data/.rubocop.yml +0 -37
- data/.rubocop_todo.yml +0 -40
- data/.travis.yml +0 -11
- data/CHANGELOG.md +0 -456
- data/CODE_OF_CONDUCT.md +0 -74
- data/CONTRIBUTING.md +0 -72
- data/Gemfile +0 -6
- data/Rakefile +0 -15
    
        data/lib/fusuma/multi_logger.rb
    CHANGED
    
    | @@ -12,15 +12,11 @@ module Fusuma | |
| 12 12 | 
             
                attr_accessor :debug_mode
         | 
| 13 13 |  | 
| 14 14 | 
             
                def initialize
         | 
| 15 | 
            -
                  super( | 
| 16 | 
            -
                  @err_logger = Logger.new( | 
| 15 | 
            +
                  super($stdout)
         | 
| 16 | 
            +
                  @err_logger = Logger.new($stderr)
         | 
| 17 17 | 
             
                  @debug_mode = false
         | 
| 18 18 | 
             
                end
         | 
| 19 19 |  | 
| 20 | 
            -
                def info(msg)
         | 
| 21 | 
            -
                  super(msg)
         | 
| 22 | 
            -
                end
         | 
| 23 | 
            -
             | 
| 24 20 | 
             
                def debug(msg)
         | 
| 25 21 | 
             
                  return unless debug_mode?
         | 
| 26 22 |  | 
    
        data/lib/fusuma/plugin/base.rb
    CHANGED
    
    | @@ -1,22 +1,23 @@ | |
| 1 1 | 
             
            # frozen_string_literal: true
         | 
| 2 2 |  | 
| 3 | 
            -
            require_relative './manager | 
| 4 | 
            -
            require_relative '../config | 
| 3 | 
            +
            require_relative './manager'
         | 
| 4 | 
            +
            require_relative '../config'
         | 
| 5 | 
            +
            require_relative '../custom_process'
         | 
| 5 6 |  | 
| 6 7 | 
             
            module Fusuma
         | 
| 7 8 | 
             
              module Plugin
         | 
| 8 9 | 
             
                # Create a Plugin Class with extending this class
         | 
| 9 10 | 
             
                class Base
         | 
| 11 | 
            +
                  include CustomProcess
         | 
| 10 12 | 
             
                  # when inherited from subclass
         | 
| 11 13 | 
             
                  def self.inherited(subclass)
         | 
| 14 | 
            +
                    super
         | 
| 12 15 | 
             
                    subclass_path = caller_locations(1..1).first.path
         | 
| 13 16 | 
             
                    Manager.add(plugin_class: subclass, plugin_path: subclass_path)
         | 
| 14 17 | 
             
                  end
         | 
| 15 18 |  | 
| 16 | 
            -
                  # get  | 
| 17 | 
            -
                  # @ | 
| 18 | 
            -
                  #  [Vectors::Vector]
         | 
| 19 | 
            -
                  # @return [Array]
         | 
| 19 | 
            +
                  # get subclasses
         | 
| 20 | 
            +
                  # @return [Array<Class>]
         | 
| 20 21 | 
             
                  def self.plugins
         | 
| 21 22 | 
             
                    Manager.plugins[name]
         | 
| 22 23 | 
             
                  end
         | 
| @@ -33,19 +34,21 @@ module Fusuma | |
| 33 34 |  | 
| 34 35 | 
             
                    return params unless key
         | 
| 35 36 |  | 
| 36 | 
            -
                     | 
| 37 | 
            -
             | 
| 37 | 
            +
                    @config_params ||= {}
         | 
| 38 | 
            +
                    @config_params["#{base.cache_key},#{key}"] ||=
         | 
| 39 | 
            +
                      params.fetch(key, nil).tap do |val|
         | 
| 40 | 
            +
                        next if val.nil?
         | 
| 38 41 |  | 
| 39 | 
            -
             | 
| 40 | 
            -
             | 
| 42 | 
            +
                        # NOTE: Type checking for config.yml
         | 
| 43 | 
            +
                        param_types = Array(config_param_types.fetch(key))
         | 
| 41 44 |  | 
| 42 | 
            -
             | 
| 45 | 
            +
                        next if param_types.any? { |klass| val.is_a?(klass) }
         | 
| 43 46 |  | 
| 44 | 
            -
             | 
| 45 | 
            -
             | 
| 47 | 
            +
                        MultiLogger.error('Please fix config.yml.')
         | 
| 48 | 
            +
                        MultiLogger.error(":#{base.keys.map(&:symbol)
         | 
| 46 49 | 
             
                        .join(' => :')} => :#{key} should be #{param_types.join(' OR ')}.")
         | 
| 47 | 
            -
             | 
| 48 | 
            -
             | 
| 50 | 
            +
                        exit 1
         | 
| 51 | 
            +
                      end
         | 
| 49 52 | 
             
                  end
         | 
| 50 53 |  | 
| 51 54 | 
             
                  def config_index
         | 
| @@ -1,6 +1,6 @@ | |
| 1 1 | 
             
            # frozen_string_literal: true
         | 
| 2 2 |  | 
| 3 | 
            -
            require_relative '../base | 
| 3 | 
            +
            require_relative '../base'
         | 
| 4 4 |  | 
| 5 5 | 
             
            module Fusuma
         | 
| 6 6 | 
             
              module Plugin
         | 
| @@ -8,6 +8,7 @@ module Fusuma | |
| 8 8 | 
             
                  # buffer events and output
         | 
| 9 9 | 
             
                  class Buffer < Base
         | 
| 10 10 | 
             
                    def initialize(*args)
         | 
| 11 | 
            +
                      super()
         | 
| 11 12 | 
             
                      @events = Array.new(*args)
         | 
| 12 13 | 
             
                    end
         | 
| 13 14 |  | 
| @@ -15,7 +16,7 @@ module Fusuma | |
| 15 16 |  | 
| 16 17 | 
             
                    # @return [String]
         | 
| 17 18 | 
             
                    def type
         | 
| 18 | 
            -
                      self.class.name.underscore.split('/').last.gsub('_buffer', '')
         | 
| 19 | 
            +
                      @type ||= self.class.name.underscore.split('/').last.gsub('_buffer', '')
         | 
| 19 20 | 
             
                    end
         | 
| 20 21 |  | 
| 21 22 | 
             
                    # @param event [Event]
         | 
| @@ -1,6 +1,6 @@ | |
| 1 1 | 
             
            # frozen_string_literal: true
         | 
| 2 2 |  | 
| 3 | 
            -
            require_relative './buffer | 
| 3 | 
            +
            require_relative './buffer'
         | 
| 4 4 |  | 
| 5 5 | 
             
            module Fusuma
         | 
| 6 6 | 
             
              module Plugin
         | 
| @@ -8,17 +8,17 @@ module Fusuma | |
| 8 8 | 
             
                  # manage events and generate command
         | 
| 9 9 | 
             
                  class GestureBuffer < Buffer
         | 
| 10 10 | 
             
                    DEFAULT_SOURCE = 'libinput_gesture_parser'
         | 
| 11 | 
            -
                    DEFAULT_SECONDS_TO_KEEP =  | 
| 11 | 
            +
                    DEFAULT_SECONDS_TO_KEEP = 100
         | 
| 12 12 |  | 
| 13 13 | 
             
                    def config_param_types
         | 
| 14 14 | 
             
                      {
         | 
| 15 | 
            -
                         | 
| 16 | 
            -
                         | 
| 15 | 
            +
                        source: [String],
         | 
| 16 | 
            +
                        seconds_to_keep: [Float, Integer]
         | 
| 17 17 | 
             
                      }
         | 
| 18 18 | 
             
                    end
         | 
| 19 19 |  | 
| 20 20 | 
             
                    # @param event [Event]
         | 
| 21 | 
            -
                    # @return [Buffer,  | 
| 21 | 
            +
                    # @return [Buffer, FalseClass]
         | 
| 22 22 | 
             
                    def buffer(event)
         | 
| 23 23 | 
             
                      # TODO: buffering events into buffer plugins
         | 
| 24 24 | 
             
                      # - gesture event buffer
         | 
| @@ -26,16 +26,13 @@ module Fusuma | |
| 26 26 | 
             
                      # - other event buffer
         | 
| 27 27 | 
             
                      return if event&.tag != source
         | 
| 28 28 |  | 
| 29 | 
            -
                       | 
| 30 | 
            -
             | 
| 31 | 
            -
                        self
         | 
| 32 | 
            -
                      else
         | 
| 33 | 
            -
                        clear
         | 
| 34 | 
            -
                        false
         | 
| 35 | 
            -
                      end
         | 
| 29 | 
            +
                      @events.push(event)
         | 
| 30 | 
            +
                      self
         | 
| 36 31 | 
             
                    end
         | 
| 37 32 |  | 
| 38 33 | 
             
                    def clear_expired(current_time: Time.now)
         | 
| 34 | 
            +
                      clear if ended?
         | 
| 35 | 
            +
             | 
| 39 36 | 
             
                      @seconds_to_keep ||= (config_params(:seconds_to_keep) || DEFAULT_SECONDS_TO_KEEP)
         | 
| 40 37 | 
             
                      @events.each do |e|
         | 
| 41 38 | 
             
                        break if current_time - e.time < @seconds_to_keep
         | 
| @@ -46,17 +43,28 @@ module Fusuma | |
| 46 43 | 
             
                      end
         | 
| 47 44 | 
             
                    end
         | 
| 48 45 |  | 
| 46 | 
            +
                    def ended?
         | 
| 47 | 
            +
                      return false if empty?
         | 
| 48 | 
            +
             | 
| 49 | 
            +
                      @events.last.record.status == 'end'
         | 
| 50 | 
            +
                    end
         | 
| 51 | 
            +
             | 
| 49 52 | 
             
                    # @param attr [Symbol]
         | 
| 50 53 | 
             
                    # @return [Float]
         | 
| 51 54 | 
             
                    def sum_attrs(attr)
         | 
| 52 | 
            -
                       | 
| 53 | 
            -
             | 
| 55 | 
            +
                      updating_events.map do |gesture_event|
         | 
| 56 | 
            +
                        gesture_event.record.delta[attr].to_f
         | 
| 57 | 
            +
                      end.inject(:+)
         | 
| 58 | 
            +
                    end
         | 
| 59 | 
            +
             | 
| 60 | 
            +
                    def updating_events
         | 
| 61 | 
            +
                      @events.select { |e| e.record.status == 'update' }
         | 
| 54 62 | 
             
                    end
         | 
| 55 63 |  | 
| 56 64 | 
             
                    # @param attr [Symbol]
         | 
| 57 65 | 
             
                    # @return [Float]
         | 
| 58 66 | 
             
                    def avg_attrs(attr)
         | 
| 59 | 
            -
                      sum_attrs(attr).to_f /  | 
| 67 | 
            +
                      sum_attrs(attr).to_f / updating_events.length
         | 
| 60 68 | 
             
                    end
         | 
| 61 69 |  | 
| 62 70 | 
             
                    # return [Integer]
         | 
| @@ -76,20 +84,21 @@ module Fusuma | |
| 76 84 | 
             
                      @events.empty?
         | 
| 77 85 | 
             
                    end
         | 
| 78 86 |  | 
| 79 | 
            -
                    def select_by_events
         | 
| 80 | 
            -
                      return enum_for(: | 
| 87 | 
            +
                    def select_by_events(&block)
         | 
| 88 | 
            +
                      return enum_for(:select_by_events) unless block_given?
         | 
| 81 89 |  | 
| 82 | 
            -
                      events = @events.select | 
| 90 | 
            +
                      events = @events.select(&block)
         | 
| 83 91 | 
             
                      self.class.new events
         | 
| 84 92 | 
             
                    end
         | 
| 85 93 |  | 
| 86 | 
            -
                    def  | 
| 87 | 
            -
                       | 
| 88 | 
            -
             | 
| 89 | 
            -
             | 
| 90 | 
            -
                       | 
| 91 | 
            -
             | 
| 92 | 
            -
                       | 
| 94 | 
            +
                    def select_from_last_begin
         | 
| 95 | 
            +
                      return self if empty?
         | 
| 96 | 
            +
             | 
| 97 | 
            +
                      index_from_last = @events.reverse.find_index { |e| e.record.status == 'begin' }
         | 
| 98 | 
            +
                      return GestureBuffer.new([]) if index_from_last.nil?
         | 
| 99 | 
            +
             | 
| 100 | 
            +
                      index_last_begin = events.length - index_from_last - 1
         | 
| 101 | 
            +
                      GestureBuffer.new(@events[index_last_begin..-1])
         | 
| 93 102 | 
             
                    end
         | 
| 94 103 | 
             
                  end
         | 
| 95 104 | 
             
                end
         | 
| @@ -0,0 +1,46 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require_relative './buffer'
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            module Fusuma
         | 
| 6 | 
            +
              module Plugin
         | 
| 7 | 
            +
                module Buffers
         | 
| 8 | 
            +
                  # manage events and generate command
         | 
| 9 | 
            +
                  class TimerBuffer < Buffer
         | 
| 10 | 
            +
                    DEFAULT_SOURCE = 'timer_input'
         | 
| 11 | 
            +
                    DEFAULT_SECONDS_TO_KEEP = 60
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                    def config_param_types
         | 
| 14 | 
            +
                      {
         | 
| 15 | 
            +
                        source: [String],
         | 
| 16 | 
            +
                        seconds_to_keep: [Float, Integer]
         | 
| 17 | 
            +
                      }
         | 
| 18 | 
            +
                    end
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                    # @param event [Event]
         | 
| 21 | 
            +
                    # @return [Buffer, NilClass]
         | 
| 22 | 
            +
                    def buffer(event)
         | 
| 23 | 
            +
                      return if event&.tag != source
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                      @events.push(event)
         | 
| 26 | 
            +
                      self
         | 
| 27 | 
            +
                    end
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                    def clear_expired(current_time: Time.now)
         | 
| 30 | 
            +
                      @seconds_to_keep ||= (config_params(:seconds_to_keep) || DEFAULT_SECONDS_TO_KEEP)
         | 
| 31 | 
            +
                      @events.each do |e|
         | 
| 32 | 
            +
                        break if current_time - e.time < @seconds_to_keep
         | 
| 33 | 
            +
             | 
| 34 | 
            +
                        MultiLogger.debug("#{self.class.name}##{__method__}")
         | 
| 35 | 
            +
             | 
| 36 | 
            +
                        @events.delete(e)
         | 
| 37 | 
            +
                      end
         | 
| 38 | 
            +
                    end
         | 
| 39 | 
            +
             | 
| 40 | 
            +
                    def empty?
         | 
| 41 | 
            +
                      @events.empty?
         | 
| 42 | 
            +
                    end
         | 
| 43 | 
            +
                  end
         | 
| 44 | 
            +
                end
         | 
| 45 | 
            +
              end
         | 
| 46 | 
            +
            end
         | 
| @@ -1,13 +1,24 @@ | |
| 1 1 | 
             
            # frozen_string_literal: true
         | 
| 2 2 |  | 
| 3 | 
            -
            require_relative '../base | 
| 4 | 
            -
            require_relative '../events/event | 
| 3 | 
            +
            require_relative '../base'
         | 
| 4 | 
            +
            require_relative '../events/event'
         | 
| 5 5 |  | 
| 6 6 | 
             
            module Fusuma
         | 
| 7 7 | 
             
              module Plugin
         | 
| 8 8 | 
             
                module Detectors
         | 
| 9 9 | 
             
                  # Inherite this base
         | 
| 10 10 | 
             
                  class Detector < Base
         | 
| 11 | 
            +
                    # @return [Array<String>]
         | 
| 12 | 
            +
                    def sources
         | 
| 13 | 
            +
                      @source ||= self.class.const_get('SOURCES')
         | 
| 14 | 
            +
                    end
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                    # Always watch buffers and detect them or not
         | 
| 17 | 
            +
                    # @return [TrueClass,FalseClass]
         | 
| 18 | 
            +
                    def watch?
         | 
| 19 | 
            +
                      false
         | 
| 20 | 
            +
                    end
         | 
| 21 | 
            +
             | 
| 11 22 | 
             
                    # @param _buffers [Array<Buffer>]
         | 
| 12 23 | 
             
                    # @return [Event] if event is detected
         | 
| 13 24 | 
             
                    # @return [NilClass] if event is NOT detected
         | 
| @@ -21,7 +32,7 @@ module Fusuma | |
| 21 32 | 
             
                    # @return [Events::Event]
         | 
| 22 33 | 
             
                    def create_event(record:)
         | 
| 23 34 | 
             
                      @last_time = Time.now
         | 
| 24 | 
            -
                      Events::Event.new(time:  | 
| 35 | 
            +
                      Events::Event.new(time: @last_time, tag: tag, record: record)
         | 
| 25 36 | 
             
                    end
         | 
| 26 37 |  | 
| 27 38 | 
             
                    def last_time
         | 
| @@ -33,11 +44,21 @@ module Fusuma | |
| 33 44 | 
             
                    end
         | 
| 34 45 |  | 
| 35 46 | 
             
                    def tag
         | 
| 36 | 
            -
                      self.class. | 
| 47 | 
            +
                      self.class.tag
         | 
| 37 48 | 
             
                    end
         | 
| 38 49 |  | 
| 39 50 | 
             
                    def type
         | 
| 40 | 
            -
                      self.class. | 
| 51 | 
            +
                      self.class.type
         | 
| 52 | 
            +
                    end
         | 
| 53 | 
            +
             | 
| 54 | 
            +
                    class << self
         | 
| 55 | 
            +
                      def tag
         | 
| 56 | 
            +
                        name.split('Detectors::').last.underscore
         | 
| 57 | 
            +
                      end
         | 
| 58 | 
            +
             | 
| 59 | 
            +
                      def type(tag_name = tag)
         | 
| 60 | 
            +
                        tag_name.gsub('_detector', '')
         | 
| 61 | 
            +
                      end
         | 
| 41 62 | 
             
                    end
         | 
| 42 63 | 
             
                  end
         | 
| 43 64 | 
             
                end
         | 
| @@ -1,50 +1,111 @@ | |
| 1 1 | 
             
            # frozen_string_literal: true
         | 
| 2 2 |  | 
| 3 | 
            -
            require_relative './detector | 
| 3 | 
            +
            require_relative './detector'
         | 
| 4 4 |  | 
| 5 5 | 
             
            module Fusuma
         | 
| 6 6 | 
             
              module Plugin
         | 
| 7 7 | 
             
                module Detectors
         | 
| 8 8 | 
             
                  class PinchDetector < Detector
         | 
| 9 | 
            +
                    SOURCES = ['gesture'].freeze
         | 
| 9 10 | 
             
                    BUFFER_TYPE = 'gesture'
         | 
| 10 11 | 
             
                    GESTURE_RECORD_TYPE = 'pinch'
         | 
| 11 12 |  | 
| 12 13 | 
             
                    FINGERS = [2, 3, 4].freeze
         | 
| 13 | 
            -
                    BASE_THERESHOLD =  | 
| 14 | 
            -
                    BASE_INTERVAL   = 0.1
         | 
| 14 | 
            +
                    BASE_THERESHOLD = 1.3
         | 
| 15 15 |  | 
| 16 16 | 
             
                    # @param buffers [Array<Buffer>]
         | 
| 17 | 
            -
                    # @return [Event] if event is detected
         | 
| 17 | 
            +
                    # @return [Events::Event] if event is detected
         | 
| 18 18 | 
             
                    # @return [NilClass] if event is NOT detected
         | 
| 19 19 | 
             
                    def detect(buffers)
         | 
| 20 | 
            -
                       | 
| 21 | 
            -
             | 
| 22 | 
            -
             | 
| 23 | 
            -
             | 
| 24 | 
            -
             | 
| 25 | 
            -
                       | 
| 26 | 
            -
             | 
| 27 | 
            -
                       | 
| 28 | 
            -
             | 
| 29 | 
            -
                       | 
| 30 | 
            -
             | 
| 31 | 
            -
             | 
| 32 | 
            -
             | 
| 33 | 
            -
             | 
| 34 | 
            -
             | 
| 35 | 
            -
             | 
| 36 | 
            -
             | 
| 37 | 
            -
             | 
| 38 | 
            -
             | 
| 20 | 
            +
                      gesture_buffer = buffers.find { |b| b.type == BUFFER_TYPE }
         | 
| 21 | 
            +
                                              .select_from_last_begin
         | 
| 22 | 
            +
                                              .select_by_events { |e| e.record.gesture == GESTURE_RECORD_TYPE }
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                      updating_events = gesture_buffer.updating_events
         | 
| 25 | 
            +
                      return if updating_events.empty?
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                      finger = gesture_buffer.finger
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                      status = case gesture_buffer.events.last.record.status
         | 
| 30 | 
            +
                               when 'end'
         | 
| 31 | 
            +
                                 'end'
         | 
| 32 | 
            +
                               when 'update'
         | 
| 33 | 
            +
                                 if updating_events.length == 1
         | 
| 34 | 
            +
                                   'begin'
         | 
| 35 | 
            +
                                 else
         | 
| 36 | 
            +
                                   'update'
         | 
| 37 | 
            +
                                 end
         | 
| 38 | 
            +
                               else
         | 
| 39 | 
            +
                                 gesture_buffer.events.last.record.status
         | 
| 40 | 
            +
                               end
         | 
| 41 | 
            +
             | 
| 42 | 
            +
                      prev_event, event = if status == 'end'
         | 
| 43 | 
            +
                                            [
         | 
| 44 | 
            +
                                              gesture_buffer.events[-3],
         | 
| 45 | 
            +
                                              gesture_buffer.events[-2]
         | 
| 46 | 
            +
                                            ]
         | 
| 47 | 
            +
                                          else
         | 
| 48 | 
            +
                                            [
         | 
| 49 | 
            +
                                              gesture_buffer.events[-2],
         | 
| 50 | 
            +
                                              gesture_buffer.events[-1]
         | 
| 51 | 
            +
                                            ]
         | 
| 52 | 
            +
                                          end
         | 
| 53 | 
            +
                      delta = event.record.delta
         | 
| 54 | 
            +
                      prev_delta = prev_event.record.delta
         | 
| 55 | 
            +
             | 
| 56 | 
            +
                      repeat_direction = Direction.new(target: delta.zoom, base: (prev_delta&.zoom || 1.0)).to_s
         | 
| 57 | 
            +
                      # repeat_quantity = Quantity.new(target: delta.zoom, base: (prev_delta&.zoom || 1.0)).to_f
         | 
| 58 | 
            +
             | 
| 59 | 
            +
                      repeat_index = create_repeat_index(gesture: type, finger: finger,
         | 
| 60 | 
            +
                                                         direction: repeat_direction,
         | 
| 61 | 
            +
                                                         status: status)
         | 
| 62 | 
            +
                      if status == 'update'
         | 
| 63 | 
            +
                        return unless moved?(prev_event, event)
         | 
| 64 | 
            +
             | 
| 65 | 
            +
                        avg_zoom = gesture_buffer.avg_attrs(:zoom)
         | 
| 66 | 
            +
                        first_zoom = updating_events.first.record.delta.zoom
         | 
| 67 | 
            +
             | 
| 68 | 
            +
                        oneshot_quantity = Quantity.new(target: avg_zoom, base: first_zoom).to_f
         | 
| 69 | 
            +
                        oneshot_direction = Direction.new(target: avg_zoom, base: first_zoom).to_s
         | 
| 70 | 
            +
                        oneshot_index = create_oneshot_index(gesture: type, finger: finger,
         | 
| 71 | 
            +
                                                             direction: oneshot_direction)
         | 
| 72 | 
            +
                        if enough_oneshot_threshold?(index: oneshot_index, quantity: oneshot_quantity)
         | 
| 73 | 
            +
                          return [
         | 
| 74 | 
            +
                            create_event(record: Events::Records::IndexRecord.new(
         | 
| 75 | 
            +
                              index: oneshot_index, trigger: :oneshot, args: delta.to_h
         | 
| 76 | 
            +
                            )),
         | 
| 77 | 
            +
                            create_event(record: Events::Records::IndexRecord.new(
         | 
| 78 | 
            +
                              index: repeat_index, trigger: :repeat, args: delta.to_h
         | 
| 79 | 
            +
                            ))
         | 
| 80 | 
            +
                          ]
         | 
| 81 | 
            +
                        end
         | 
| 82 | 
            +
                      end
         | 
| 83 | 
            +
                      create_event(record: Events::Records::IndexRecord.new(
         | 
| 84 | 
            +
                        index: repeat_index, trigger: :repeat, args: delta.to_h
         | 
| 85 | 
            +
                      ))
         | 
| 86 | 
            +
                    end
         | 
| 39 87 |  | 
| 40 | 
            -
             | 
| 88 | 
            +
                    # @param [String] gesture
         | 
| 89 | 
            +
                    # @param [Integer] finger
         | 
| 90 | 
            +
                    # @param [String] direction
         | 
| 91 | 
            +
                    # @param [String] status
         | 
| 92 | 
            +
                    # @return [Config::Index]
         | 
| 93 | 
            +
                    def create_repeat_index(gesture:, finger:, direction:, status:)
         | 
| 94 | 
            +
                      Config::Index.new(
         | 
| 95 | 
            +
                        [
         | 
| 96 | 
            +
                          Config::Index::Key.new(gesture),
         | 
| 97 | 
            +
                          Config::Index::Key.new(finger.to_i),
         | 
| 98 | 
            +
                          Config::Index::Key.new(direction, skippable: true),
         | 
| 99 | 
            +
                          Config::Index::Key.new(status)
         | 
| 100 | 
            +
                        ]
         | 
| 101 | 
            +
                      )
         | 
| 41 102 | 
             
                    end
         | 
| 42 103 |  | 
| 43 104 | 
             
                    # @param [String] gesture
         | 
| 44 105 | 
             
                    # @param [Integer] finger
         | 
| 45 106 | 
             
                    # @param [String] direction
         | 
| 46 107 | 
             
                    # @return [Config::Index]
         | 
| 47 | 
            -
                    def  | 
| 108 | 
            +
                    def create_oneshot_index(gesture:, finger:, direction:)
         | 
| 48 109 | 
             
                      Config::Index.new(
         | 
| 49 110 | 
             
                        [
         | 
| 50 111 | 
             
                          Config::Index::Key.new(gesture),
         | 
| @@ -56,41 +117,25 @@ module Fusuma | |
| 56 117 |  | 
| 57 118 | 
             
                    private
         | 
| 58 119 |  | 
| 59 | 
            -
                    def  | 
| 60 | 
            -
                       | 
| 120 | 
            +
                    def moved?(prev_event, event)
         | 
| 121 | 
            +
                      zoom_delta = (event.record.delta.zoom - prev_event.record.delta.zoom).abs
         | 
| 122 | 
            +
                      updating_time = (event.time - prev_event.time) * 100
         | 
| 123 | 
            +
                      zoom_delta / updating_time > 0.01
         | 
| 61 124 | 
             
                    end
         | 
| 62 125 |  | 
| 63 | 
            -
                    def  | 
| 126 | 
            +
                    def enough_oneshot_threshold?(index:, quantity:)
         | 
| 64 127 | 
             
                      quantity >= threshold(index: index)
         | 
| 65 128 | 
             
                    end
         | 
| 66 129 |  | 
| 67 | 
            -
                    def enough_interval?(index:)
         | 
| 68 | 
            -
                      return true if first_time?
         | 
| 69 | 
            -
                      return true if (Time.now - last_time) > interval_time(index: index)
         | 
| 70 | 
            -
             | 
| 71 | 
            -
                      false
         | 
| 72 | 
            -
                    end
         | 
| 73 | 
            -
             | 
| 74 130 | 
             
                    def threshold(index:)
         | 
| 75 131 | 
             
                      @threshold ||= {}
         | 
| 76 132 | 
             
                      @threshold[index.cache_key] ||= begin
         | 
| 77 | 
            -
             | 
| 78 | 
            -
             | 
| 79 | 
            -
             | 
| 80 | 
            -
             | 
| 81 | 
            -
             | 
| 82 | 
            -
             | 
| 83 | 
            -
                    end
         | 
| 84 | 
            -
             | 
| 85 | 
            -
                    def interval_time(index:)
         | 
| 86 | 
            -
                      @interval_time ||= {}
         | 
| 87 | 
            -
                      @interval_time[index.cache_key] ||= begin
         | 
| 88 | 
            -
                                           keys_specific = Config::Index.new [*index.keys, 'interval']
         | 
| 89 | 
            -
                                           keys_global = Config::Index.new ['interval', type]
         | 
| 90 | 
            -
                                           config_value = Config.search(keys_specific) ||
         | 
| 91 | 
            -
                                                          Config.search(keys_global) || 1
         | 
| 92 | 
            -
                                           BASE_INTERVAL * config_value
         | 
| 93 | 
            -
                                         end
         | 
| 133 | 
            +
                        keys_specific = Config::Index.new [*index.keys, 'threshold']
         | 
| 134 | 
            +
                        keys_global   = Config::Index.new ['threshold', type]
         | 
| 135 | 
            +
                        config_value  = Config.search(keys_specific) ||
         | 
| 136 | 
            +
                                        Config.search(keys_global) || 1
         | 
| 137 | 
            +
                        BASE_THERESHOLD * config_value
         | 
| 138 | 
            +
                      end
         | 
| 94 139 | 
             
                    end
         | 
| 95 140 |  | 
| 96 141 | 
             
                    # direction of gesture
         | 
| @@ -98,8 +143,9 @@ module Fusuma | |
| 98 143 | 
             
                      IN = 'in'
         | 
| 99 144 | 
             
                      OUT = 'out'
         | 
| 100 145 |  | 
| 101 | 
            -
                      def initialize( | 
| 102 | 
            -
                        @ | 
| 146 | 
            +
                      def initialize(target:, base:)
         | 
| 147 | 
            +
                        @target = target.to_f
         | 
| 148 | 
            +
                        @base = base.to_f
         | 
| 103 149 | 
             
                      end
         | 
| 104 150 |  | 
| 105 151 | 
             
                      def to_s
         | 
| @@ -107,7 +153,7 @@ module Fusuma | |
| 107 153 | 
             
                      end
         | 
| 108 154 |  | 
| 109 155 | 
             
                      def calc
         | 
| 110 | 
            -
                        if @ | 
| 156 | 
            +
                        if @target > @base
         | 
| 111 157 | 
             
                          IN
         | 
| 112 158 | 
             
                        else
         | 
| 113 159 | 
             
                          OUT
         | 
| @@ -117,8 +163,9 @@ module Fusuma | |
| 117 163 |  | 
| 118 164 | 
             
                    # quantity of gesture
         | 
| 119 165 | 
             
                    class Quantity
         | 
| 120 | 
            -
                      def initialize( | 
| 121 | 
            -
                        @ | 
| 166 | 
            +
                      def initialize(target:, base:)
         | 
| 167 | 
            +
                        @target = target.to_f
         | 
| 168 | 
            +
                        @base = base.to_f
         | 
| 122 169 | 
             
                      end
         | 
| 123 170 |  | 
| 124 171 | 
             
                      def to_f
         | 
| @@ -126,7 +173,11 @@ module Fusuma | |
| 126 173 | 
             
                      end
         | 
| 127 174 |  | 
| 128 175 | 
             
                      def calc
         | 
| 129 | 
            -
                         | 
| 176 | 
            +
                        if @target > @base
         | 
| 177 | 
            +
                          @target / @base
         | 
| 178 | 
            +
                        else
         | 
| 179 | 
            +
                          @base / @target
         | 
| 180 | 
            +
                        end
         | 
| 130 181 | 
             
                      end
         | 
| 131 182 | 
             
                    end
         | 
| 132 183 | 
             
                  end
         |