├── .gitignore ├── .rspec ├── .travis.yml ├── Gemfile ├── LICENSE.txt ├── README.md ├── Rakefile ├── evdev.gemspec ├── lib ├── evdev.rb └── evdev │ ├── abs_axis.rb │ ├── converter.rb │ └── version.rb └── spec ├── evdev ├── abs_axis_spec.rb └── converter_spec.rb ├── evdev_spec.rb └── spec_helper.rb /.gitignore: -------------------------------------------------------------------------------- 1 | /.bundle/ 2 | /.yardoc 3 | /Gemfile.lock 4 | /_yardoc/ 5 | /coverage/ 6 | /doc/ 7 | /pkg/ 8 | /spec/reports/ 9 | /tmp/ 10 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --format documentation 2 | --color 3 | --require spec_helper.rb 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: ruby 2 | rvm: 3 | - 2.2.0 4 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gemspec 4 | 5 | group :test do 6 | gem "rspec", "~> 3.5" 7 | gem 'rspec-is_expected_block', '~> 3.0' 8 | end -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Christopher Aue 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Evdev 2 | 3 | A ruby wrapper around [Libevdev](https://github.com/christopheraue/ruby-libevdev) 4 | for a nice and easy access to linux's event devices like keyboards and mice. 5 | 6 | ## Installation 7 | 8 | Add this line to your application's Gemfile: 9 | 10 | ```ruby 11 | gem 'evdev' 12 | ``` 13 | 14 | And then execute: 15 | 16 | $ bundle 17 | 18 | Or install it yourself as: 19 | 20 | $ gem install evdev 21 | 22 | ## Usage 23 | 24 | Let's have a look at my keyboard: 25 | 26 | ### Initializing a device 27 | 28 | ```ruby 29 | require 'evdev' 30 | keyboard = Evdev.new('/dev/input/event0') 31 | ``` 32 | 33 | ### Querying the device 34 | 35 | ```ruby 36 | keyboard.name # => "Logitech USB Receiver" 37 | keyboard.phys # => "usb-0000:00:1d.2-1/input0" 38 | keyboard.uniq # => "" 39 | keyboard.vendor_id # => 1133 40 | keyboard.product_id # => 50473 41 | keyboard.bustype # => 3 (LinuxInput::BUS_USB) 42 | keyboard.version # => 273 43 | keyboard.driver_version # => 65537 44 | keyboard.has_property? :pointer # => false 45 | ``` 46 | 47 | ### Grabbing a device 48 | 49 | ```ruby 50 | keyboard.grab # => true if successful, else false 51 | keyboard.ungrab # => true if successful, else false 52 | ``` 53 | 54 | ### Event handling 55 | 56 | Evdev lumps event type and code together. You just need to give it the name of 57 | the code and it derives the implied type from it internally. 58 | 59 | ```ruby 60 | keyboard.supports_event? :KEY_A # => true 61 | keyboard.supports_event? :ABS_X # => false 62 | 63 | key_handler = keyboard.on(:KEY_A, :KEY_S, :KEY_D, :KEY_F) do |state, key| 64 | puts "#{%w(released pressed repeated)[state]} #{key}" 65 | end 66 | 67 | loop do 68 | begin 69 | keyboard.handle_event 70 | rescue Evdev::AwaitEvent 71 | Kernel.select([keyboard.event_channel]) 72 | retry 73 | end 74 | end 75 | ``` 76 | 77 | Removing the handler: 78 | 79 | ```ruby 80 | key_handler.cancel 81 | ``` 82 | 83 | Evdev includes the [CallbacksAttachable](https://github.com/christopheraue/ruby-callbacks_attachable) 84 | mixin to attach and detach callbacks. Have a look at its API in its own documentation. -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require "bundler/gem_tasks" 2 | 3 | -------------------------------------------------------------------------------- /evdev.gemspec: -------------------------------------------------------------------------------- 1 | require_relative 'lib/evdev/version' 2 | 3 | Gem::Specification.new do |spec| 4 | spec.name = "evdev" 5 | spec.version = Evdev::VERSION 6 | spec.authors = ["Christopher Aue"] 7 | spec.email = ["rubygems@christopheraue.net"] 8 | 9 | spec.summary = %q{A ruby object wrapper around libevdev bindings.} 10 | spec.homepage = "https://github.com/christopheraue/ruby-evdev" 11 | spec.license = "MIT" 12 | 13 | spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) } 14 | spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } 15 | spec.require_paths = ["lib"] 16 | 17 | spec.add_runtime_dependency "libevdev", "~> 1.0" 18 | spec.add_runtime_dependency "callbacks_attachable", "~> 2.3" 19 | end 20 | -------------------------------------------------------------------------------- /lib/evdev.rb: -------------------------------------------------------------------------------- 1 | require "callbacks_attachable" 2 | require "libevdev" 3 | 4 | require_relative "evdev/version" 5 | require_relative "evdev/converter" 6 | require_relative "evdev/abs_axis" 7 | 8 | class Evdev 9 | include CallbacksAttachable 10 | 11 | module AwaitEvent; end 12 | 13 | class << self 14 | def finalize(device) 15 | proc { Libevdev.free(device) } 16 | end 17 | 18 | def all(file_paths = '/dev/input/event*') 19 | Dir[file_paths].map{ |file_path| new(file_path) } 20 | end 21 | end 22 | 23 | def initialize(file_path) 24 | @file = File.open(file_path) 25 | device_ptr = FFI::MemoryPointer.new :pointer 26 | Libevdev.new_from_fd(@file.fileno, device_ptr) 27 | @device = device_ptr.read_pointer 28 | 29 | ObjectSpace.define_finalizer(self, self.class.finalize(@device)) 30 | end 31 | 32 | attr_reader :file 33 | alias_method :event_channel, :file 34 | 35 | def name 36 | Libevdev.get_name(@device) 37 | end 38 | 39 | def phys 40 | Libevdev.get_phys(@device) 41 | end 42 | 43 | def uniq 44 | Libevdev.get_uniq(@device) 45 | end 46 | 47 | def vendor_id 48 | Libevdev.get_id_vendor(@device) 49 | end 50 | 51 | def product_id 52 | Libevdev.get_id_product(@device) 53 | end 54 | 55 | def bustype 56 | Libevdev.get_id_bustype(@device) 57 | end 58 | 59 | def version 60 | Libevdev.get_id_version(@device) 61 | end 62 | 63 | def driver_version 64 | Libevdev.get_driver_version(@device) 65 | end 66 | 67 | def has_property?(property) 68 | 1 == Libevdev.has_property(@device, Converter.property_to_int(property)) 69 | end 70 | 71 | def abs_axis(code) 72 | AbsAxis.new(@device, Converter.code_to_int(code)) 73 | end 74 | 75 | def grab 76 | 0 == Libevdev.grab(@device, Libevdev::GRAB) 77 | end 78 | 79 | def ungrab 80 | 0 == Libevdev.grab(@device, Libevdev::UNGRAB) 81 | end 82 | 83 | def supports_event?(event) 84 | type = Converter.code_to_type(event) 85 | handles_event_type?(type) and handles_event_code?(type, event) 86 | end 87 | 88 | def handle_event(mode = :blocking) 89 | event = LinuxInput::InputEvent.new 90 | Libevdev.next_event(@device, Libevdev.const_get(:"READ_FLAG_#{mode.upcase}"), event.pointer) 91 | event_name = Converter.int_to_name(event[:type], event[:code]) 92 | trigger(event_name, event[:value], event_name) 93 | rescue Errno::EAGAIN => e 94 | raise e.extend AwaitEvent 95 | end 96 | 97 | def events_pending? 98 | 1 == Libevdev.has_event_pending(@device) 99 | end 100 | 101 | def event_value(event) 102 | return unless supports_event?(event) 103 | type = Converter.code_to_type(event) 104 | Libevdev.get_event_value(@device, Converter.type_to_int(type), Converter.code_to_int(event)) 105 | end 106 | 107 | private 108 | 109 | def handles_event_type?(type) 110 | 1 == Libevdev.has_event_type(@device, Converter.type_to_int(type)) 111 | end 112 | 113 | def handles_event_code?(type, code) 114 | 1 == Libevdev.has_event_code(@device, Converter.type_to_int(type), Converter.code_to_int(code)) 115 | end 116 | end 117 | -------------------------------------------------------------------------------- /lib/evdev/abs_axis.rb: -------------------------------------------------------------------------------- 1 | class Evdev 2 | class AbsAxis 3 | def initialize(device, code) 4 | @device = device 5 | @code = code 6 | end 7 | 8 | def maximum 9 | Libevdev.get_abs_maximum(@device, @code) 10 | end 11 | 12 | def minimum 13 | Libevdev.get_abs_minimum(@device, @code) 14 | end 15 | 16 | def fuzz 17 | Libevdev.get_abs_fuzz(@device, @code) 18 | end 19 | 20 | def flat 21 | Libevdev.get_abs_flat(@device, @code) 22 | end 23 | 24 | def resolution 25 | Libevdev.get_abs_resolution(@device, @code) 26 | end 27 | end 28 | end -------------------------------------------------------------------------------- /lib/evdev/converter.rb: -------------------------------------------------------------------------------- 1 | class Evdev 2 | module Converter; end 3 | class << Converter 4 | def code_to_type(code) 5 | :"EV_#{code.to_s.split('_').first}" 6 | end 7 | 8 | def property_to_int(property) 9 | LinuxInput.const_get(:"INPUT_PROP_#{property.upcase}") 10 | end 11 | 12 | def type_to_int(type) 13 | LinuxInput.const_get(type.upcase) 14 | end 15 | 16 | def code_to_int(code) 17 | LinuxInput.const_get(code.upcase) 18 | end 19 | 20 | def int_to_name(type_int, code_int) 21 | @event_names ||= {} 22 | @event_names[type_int] ||= consts_starting_with("#{int_to_type(type_int)}_") 23 | @event_names[type_int][code_int] 24 | end 25 | 26 | private 27 | 28 | def int_to_type(int) 29 | @event_types ||= consts_starting_with('EV_') 30 | @event_types[int][3..-1] 31 | end 32 | 33 | def consts_starting_with(prefix) 34 | LinuxInput.constants(false).map do |const| 35 | [LinuxInput.const_get(const), const] if const.to_s.start_with? prefix 36 | end.compact.to_h 37 | end 38 | end 39 | end -------------------------------------------------------------------------------- /lib/evdev/version.rb: -------------------------------------------------------------------------------- 1 | class Evdev 2 | VERSION = "1.0.0" 3 | end 4 | -------------------------------------------------------------------------------- /spec/evdev/abs_axis_spec.rb: -------------------------------------------------------------------------------- 1 | describe Evdev::AbsAxis do 2 | subject(:klass) { described_class } 3 | subject(:instance) { klass.new(:input_device, :code) } 4 | 5 | describe "#maximum: Gets its maximum value" do 6 | subject { instance.maximum } 7 | before { allow(Libevdev).to receive(:get_abs_maximum).with(:input_device, :code).and_return(:maximum) } 8 | it { is_expected.to be :maximum } 9 | end 10 | 11 | describe "#minimum: Gets its minimum value" do 12 | subject { instance.minimum } 13 | before { allow(Libevdev).to receive(:get_abs_minimum).with(:input_device, :code).and_return(:minimum) } 14 | it { is_expected.to be :minimum } 15 | end 16 | 17 | describe "#fuzz: Gets its fuzz value" do 18 | subject { instance.fuzz } 19 | before { allow(Libevdev).to receive(:get_abs_fuzz).with(:input_device, :code).and_return(:fuzz) } 20 | it { is_expected.to be :fuzz } 21 | end 22 | 23 | describe "#flat: Gets its flat value" do 24 | subject { instance.flat } 25 | before { allow(Libevdev).to receive(:get_abs_flat).with(:input_device, :code).and_return(:flat) } 26 | it { is_expected.to be :flat } 27 | end 28 | 29 | describe "#resolution: Gets its resolution" do 30 | subject { instance.resolution } 31 | before { allow(Libevdev).to receive(:get_abs_resolution).with(:input_device, :code).and_return(:resolution) } 32 | it { is_expected.to be :resolution } 33 | end 34 | end -------------------------------------------------------------------------------- /spec/evdev/converter_spec.rb: -------------------------------------------------------------------------------- 1 | describe Evdev::Converter do 2 | subject(:instance) { described_class.clone } 3 | 4 | describe ".code_to_type: Converts an event code into a type" do 5 | subject { instance.code_to_type(:TYPE_CODE) } 6 | it { is_expected.to be :EV_TYPE } 7 | end 8 | 9 | describe ".property_to_int: Converts a property name into its int representation" do 10 | subject { instance.property_to_int(:pointer) } 11 | it { is_expected.to be LinuxInput::INPUT_PROP_POINTER } 12 | end 13 | 14 | describe ".type_to_int: Converts an event type into its int representation" do 15 | subject { instance.type_to_int(:ev_key) } 16 | it { is_expected.to be LinuxInput::EV_KEY } 17 | end 18 | 19 | describe ".code_to_int: Converts an event code into its int representation" do 20 | subject { instance.code_to_int(:key_a) } 21 | it { is_expected.to be LinuxInput::KEY_A } 22 | end 23 | 24 | describe ".int_to_name: Converts event type and code ints into a name" do 25 | subject { instance.int_to_name(LinuxInput::EV_KEY, LinuxInput::KEY_A) } 26 | it { is_expected.to be :KEY_A } 27 | end 28 | end -------------------------------------------------------------------------------- /spec/evdev_spec.rb: -------------------------------------------------------------------------------- 1 | describe Evdev do 2 | subject(:klass) { described_class } 3 | subject(:instance) { klass.new(:file_path) } 4 | 5 | let(:file) { instance_double(File, fileno: :fd) } 6 | let(:device_ptr) { instance_double(FFI::MemoryPointer, read_pointer: :input_device) } 7 | before { allow(File).to receive(:open).with(:file_path).and_return(file) } 8 | before { allow(FFI::MemoryPointer).to receive(:new).with(:pointer).and_return(device_ptr) } 9 | before { allow(Libevdev).to receive(:free).with(:input_device) } 10 | before { allow(Libevdev).to receive(:new_from_fd).with(:fd, device_ptr) } 11 | 12 | it 'has a version number' do 13 | expect(Evdev::VERSION).not_to be nil 14 | end 15 | 16 | describe "Freeing the device after an instance is gc'ed" do 17 | subject { GC.start } 18 | before { @instance = klass.new(:file_path) } 19 | before { expect(Libevdev).to receive(:free).with(:input_device) } 20 | before { @instance = nil } 21 | it { is_expected.not_to raise_error } 22 | end 23 | 24 | describe ".all: Gets all event devices" do 25 | subject { klass.all } 26 | 27 | before { allow(Dir).to receive(:[]).with('/dev/input/event*'). 28 | and_return(%w(/dev/input/event0 /dev/input/event1)) } 29 | before { allow(klass).to receive(:new).with('/dev/input/event0').and_return(:device0) } 30 | before { allow(klass).to receive(:new).with('/dev/input/event1').and_return(:device1) } 31 | 32 | it { is_expected.to eq %i(device0 device1) } 33 | end 34 | 35 | describe "#file: Gets its file handle" do 36 | subject { instance.file } 37 | it { is_expected.to eq file } 38 | end 39 | 40 | describe "#event_channel: Alias for #file" do 41 | subject { instance.event_channel } 42 | it { is_expected.to eq file } 43 | end 44 | 45 | describe "#name: Gets its name" do 46 | subject { instance.name } 47 | before { allow(Libevdev).to receive(:get_name).with(:input_device).and_return(:name) } 48 | it { is_expected.to be :name } 49 | end 50 | 51 | describe "#phys: Gets its physical location" do 52 | subject { instance.phys } 53 | before { allow(Libevdev).to receive(:get_phys).with(:input_device).and_return(:phys) } 54 | it { is_expected.to be :phys } 55 | end 56 | 57 | describe "#uniq: Gets its unique identifier" do 58 | subject { instance.uniq } 59 | before { allow(Libevdev).to receive(:get_uniq).with(:input_device).and_return(:uniq) } 60 | it { is_expected.to be :uniq } 61 | end 62 | 63 | describe "#vendor_id: Gets its vendor id" do 64 | subject { instance.vendor_id } 65 | before { allow(Libevdev).to receive(:get_id_vendor).with(:input_device).and_return(:vendor_id) } 66 | it { is_expected.to be :vendor_id } 67 | end 68 | 69 | describe "#product_id: Gets its product id" do 70 | subject { instance.product_id } 71 | before { allow(Libevdev).to receive(:get_id_product).with(:input_device).and_return(:product_id) } 72 | it { is_expected.to be :product_id } 73 | end 74 | 75 | describe "#bustype: Gets its bus type" do 76 | subject { instance.bustype } 77 | before { allow(Libevdev).to receive(:get_id_bustype).with(:input_device).and_return(:bustype) } 78 | it { is_expected.to be :bustype } 79 | end 80 | 81 | describe "#version: Gets its firmware version" do 82 | subject { instance.version } 83 | before { allow(Libevdev).to receive(:get_id_version).with(:input_device).and_return(:version) } 84 | it { is_expected.to be :version } 85 | end 86 | 87 | describe "#driver_version: Gets its driver's version" do 88 | subject { instance.driver_version } 89 | before { allow(Libevdev).to receive(:get_driver_version).with(:input_device).and_return(:driver_version) } 90 | it { is_expected.to be :driver_version } 91 | end 92 | 93 | describe "#has_property?: Checks if it has the given property" do 94 | subject { instance.has_property?(:property) } 95 | 96 | before { allow(klass::Converter).to receive(:property_to_int).with(:property). 97 | and_return(:property_int) } 98 | 99 | context "when it has the property" do 100 | before { allow(Libevdev).to receive(:has_property).with(:input_device, :property_int).and_return(1) } 101 | it { is_expected.to be true } 102 | end 103 | 104 | context "when it does not have the property" do 105 | before { allow(Libevdev).to receive(:has_property).with(:input_device, :property_int).and_return(0) } 106 | it { is_expected.to be false } 107 | end 108 | end 109 | 110 | describe "#abs_axis: Gets the information object of the given absolute axis" do 111 | subject { instance.abs_axis(:ABS_AXIS) } 112 | before { allow(klass::Converter).to receive(:code_to_int).with(:ABS_AXIS). 113 | and_return(:code_int) } 114 | before { allow(klass::AbsAxis).to receive(:new).with(:input_device, :code_int).and_return(:abs_axis) } 115 | it { is_expected.to be :abs_axis } 116 | end 117 | 118 | describe "#grab: Grabs the device" do 119 | subject { instance.grab } 120 | before { expect(Libevdev).to receive(:grab).with(:input_device, Libevdev::GRAB) } 121 | 122 | context "when the grab is successful" do 123 | before { allow(Libevdev).to receive(:grab).and_return(0) } 124 | it { is_expected.to be true } 125 | end 126 | 127 | context "when the grab failed" do 128 | before { allow(Libevdev).to receive(:grab).and_return(-1) } 129 | it { is_expected.to be false } 130 | end 131 | end 132 | 133 | describe "#ungrab: Ungrabs the device" do 134 | subject { instance.ungrab } 135 | before { expect(Libevdev).to receive(:grab).with(:input_device, Libevdev::UNGRAB) } 136 | 137 | context "when the ungrab is successful" do 138 | before { allow(Libevdev).to receive(:grab).and_return(0) } 139 | it { is_expected.to be true } 140 | end 141 | 142 | context "when the ungrab failed" do 143 | before { allow(Libevdev).to receive(:grab).and_return(-1) } 144 | it { is_expected.to be false } 145 | end 146 | end 147 | 148 | describe "#supports_event?: Checks if it supports the given event" do 149 | subject { instance.supports_event?(:TYPE_CODE) } 150 | 151 | before { allow(klass::Converter).to receive(:code_to_type).with(:TYPE_CODE).and_return(:EV_TYPE) } 152 | before { allow(klass::Converter).to receive(:type_to_int).with(:EV_TYPE).and_return(:type_int) } 153 | before { allow(klass::Converter).to receive(:code_to_int).with(:TYPE_CODE).and_return(:code_int) } 154 | 155 | context "when it does not support the event's type" do 156 | before { allow(Libevdev).to receive(:has_event_type).with(:input_device, :type_int).and_return(0) } 157 | before { allow(Libevdev).to receive(:has_event_code).with(:input_device, :type_int, :code_int). 158 | and_return(1) } 159 | it { is_expected.to be false } 160 | end 161 | 162 | context "when it does not support the event's code" do 163 | before { allow(Libevdev).to receive(:has_event_type).with(:input_device, :type_int).and_return(1) } 164 | before { allow(Libevdev).to receive(:has_event_code).with(:input_device, :type_int, :code_int). 165 | and_return(0) } 166 | it { is_expected.to be false } 167 | end 168 | 169 | context "when it does support the event" do 170 | before { allow(Libevdev).to receive(:has_event_type).with(:input_device, :type_int).and_return(1) } 171 | before { allow(Libevdev).to receive(:has_event_code).with(:input_device, :type_int, :code_int). 172 | and_return(1) } 173 | it { is_expected.to be true } 174 | end 175 | end 176 | 177 | describe "#handle_event: Reads the next event in the given mode and processes it" do 178 | subject { instance.handle_event } 179 | 180 | let(:event) { instance_double(LinuxInput::InputEvent, pointer: :event_ptr) } 181 | before { allow(event).to receive(:[]).with(:type).and_return(:event_type) } 182 | before { allow(event).to receive(:[]).with(:code).and_return(:event_code) } 183 | before { allow(event).to receive(:[]).with(:value).and_return(:event_value) } 184 | before { allow(klass::Converter).to receive(:int_to_name).with(:event_type, :event_code). 185 | and_return(:event_name) } 186 | before { allow(LinuxInput::InputEvent).to receive(:new).and_return(event) } 187 | 188 | before { allow(Libevdev).to receive(:next_event) } 189 | before { allow(instance).to receive(:trigger) } 190 | 191 | context "when a read mode is given implicitly" do 192 | before { expect(Libevdev).to receive(:next_event).with(:input_device, 193 | Libevdev::READ_FLAG_BLOCKING, :event_ptr) } 194 | 195 | before { expect(instance).to receive(:trigger).with(:event_name, :event_value, :event_name) } 196 | it { is_expected.not_to raise_error } 197 | end 198 | 199 | context "when a read mode is given explicitly" do 200 | subject { instance.handle_event(:normal) } 201 | before { expect(Libevdev).to receive(:next_event).with(:input_device, 202 | Libevdev::READ_FLAG_NORMAL, :event_ptr) } 203 | 204 | before { expect(instance).to receive(:trigger).with(:event_name, :event_value, :event_name) } 205 | it { is_expected.not_to raise_error } 206 | end 207 | 208 | context "when no events are pending" do 209 | before { expect(Libevdev).to receive(:next_event).and_raise Errno::EAGAIN } 210 | 211 | before { expect(instance).not_to receive(:trigger) } 212 | it { is_expected.to raise_error(Evdev::AwaitEvent) } 213 | end 214 | end 215 | 216 | describe "#event_pending?: Checks if it has pending events" do 217 | subject { instance.events_pending? } 218 | 219 | context "when it has pending events" do 220 | before { allow(Libevdev).to receive(:has_event_pending).with(:input_device).and_return(1) } 221 | it { is_expected.to be true } 222 | end 223 | 224 | context "when it does not have pending events" do 225 | before { allow(Libevdev).to receive(:has_event_pending).with(:input_device).and_return(0) } 226 | it { is_expected.to be false } 227 | end 228 | end 229 | 230 | describe "event_value: Gets the value of a the last event of the given type" do 231 | subject { instance.event_value(:TYPE_CODE) } 232 | 233 | before { allow(instance).to receive(:supports_event?).with(:TYPE_CODE).and_return(true) } 234 | before { allow(klass::Converter).to receive(:code_to_type).with(:TYPE_CODE).and_return(:EV_TYPE) } 235 | before { allow(klass::Converter).to receive(:type_to_int).with(:EV_TYPE).and_return(:type_int) } 236 | before { allow(klass::Converter).to receive(:code_to_int).with(:TYPE_CODE).and_return(:code_int) } 237 | 238 | before { allow(Libevdev).to receive(:get_event_value).with(:input_device, :type_int, :code_int). 239 | and_return(:event_value) } 240 | it { is_expected.to be :event_value } 241 | 242 | context "when it does not support the event" do 243 | before { allow(instance).to receive(:supports_event?).with(:TYPE_CODE).and_return(false) } 244 | it { is_expected.to be nil } 245 | end 246 | end 247 | end 248 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require 'bundler/setup' 2 | 3 | Bundler.require :test 4 | 5 | require_relative '../lib/evdev' 6 | --------------------------------------------------------------------------------