{"id":15007490,"url":"https://github.com/rickhull/device_input","last_synced_at":"2026-03-17T23:35:53.572Z","repository":{"id":62557067,"uuid":"79876252","full_name":"rickhull/device_input","owner":"rickhull","description":"Read Linux Kernel Device Input Events from Ruby","archived":false,"fork":false,"pushed_at":"2024-01-10T16:10:21.000Z","size":124,"stargazers_count":16,"open_issues_count":0,"forks_count":1,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-04-09T16:01:54.583Z","etag":null,"topics":["device","input-reader","linux-kernel","minimalist","minimalist-library","ruby"],"latest_commit_sha":null,"homepage":null,"language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/rickhull.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2017-01-24T03:50:48.000Z","updated_at":"2025-04-09T10:11:37.000Z","dependencies_parsed_at":"2024-01-10T17:58:28.783Z","dependency_job_id":null,"html_url":"https://github.com/rickhull/device_input","commit_stats":{"total_commits":174,"total_committers":1,"mean_commits":174.0,"dds":0.0,"last_synced_commit":"254a4d534c9fb8877cd03c3b20e4dc4ecaa6c0e4"},"previous_names":[],"tags_count":10,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rickhull%2Fdevice_input","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rickhull%2Fdevice_input/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rickhull%2Fdevice_input/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rickhull%2Fdevice_input/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/rickhull","download_url":"https://codeload.github.com/rickhull/device_input/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248065289,"owners_count":21041871,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["device","input-reader","linux-kernel","minimalist","minimalist-library","ruby"],"created_at":"2024-09-24T19:10:18.016Z","updated_at":"2026-03-17T23:35:53.528Z","avatar_url":"https://github.com/rickhull.png","language":"Ruby","readme":"[![CI Status](https://github.com/rickhull/device_input/actions/workflows/ci.yaml/badge.svg)](https://github.com/rickhull/device_input/actions/workflows/ci.yaml)\n[![Gem Version](https://badge.fury.io/rb/device_input.svg)](http://badge.fury.io/rb/device_input)\n[![Code Climate](https://codeclimate.com/github/rickhull/device_input/badges/gpa.svg)](https://codeclimate.com/github/rickhull/device_input)\n\n# Device Input\n\n*for the Linux kernel*\n\nThis is a very basic tool for reading device input events from e.g.\n`/dev/input/event0` in Ruby.  For example, in order to see what's happening\n\"on the wire\" when a special laptop function key is pressed.  While this tool\ncan plausibly be used for the purpose of malicious keystroke logging, it is\nnot well suited for it and does not provide the root privileges in order to\nread `/dev/input`.\n\nOnce you've given up the privilege to read `/dev/input` it's *game over* anyway.\n\n## Rationale\n\n`/dev/input/eventX` is a *character special* file, which represents a\n*character device*, like a keyboard, as opposed to a *block device* like\na hard drive.  Character special files are used to pass information from\nthe character device into the Linux kernel.  Can we read a character special\nfile with text-oriented Unix tooling? No.\n\nThe messages in the character special file are binary messages, represented and\ndefined by C structs. Thus, we require the ability to delimit individual\nmessages and decode them.  We can't simply read a byte at a time and try to\nmake sense of it.  In fact, on my system, `/dev/input/event0` refuses any\nread that is not a multiple of the struct / message size, so we need to know\nthe message size before even attempting a `read()`, let alone a `decode()`.\n\nTo determine the message size, we need to know the data structure.  For a\nlong time, it was pretty simple: events are 16 bytes:\n\n* timestamp - 8 bytes\n* type - 2 bytes\n* code - 2 bytes\n* value - 4 bytes\n\nHowever, this is only true for 32-bit platforms.  On 64-bit platforms, event\ntimestamps became 16 bytes, increasing the size of events from 16 to 24 bytes.\nThis is because a timestamp is defined (ultimately) as two `long`s, which\nare bigger on 64-bit platforms.  It's easy to remember:\n\n* 32-bit platform: 32-bit `long` (4 bytes)\n* 64-bit platform: 64-bit `long` (8 bytes)\n\n`read(/dev/input/event0, 16)` will fail on a 64-bit machine.\n\nYour tooling must be aware of this distinction and choose the correct\nunderlying data types just to be able to delimit messages and perform a\nsuccessful read.  This software does that, decodes the message, maps the\nencoded values to friendly strings for display, and provides both library and\nexecutable code to assist in examining kernel input events.\n\n# Installation\n\n**REQUIREMENTS**\n\n* Ruby \u003e= 2.3\n* Earlier rubies are supported on pre-1.0 versions\n\n**DEPENDENCIES**\n\n* none\n\nInstall the gem:\n```shell\n$ gem install device_input\n```\n\nOr, if using [Bundler](http://bundler.io/), add to your `Gemfile`:\n```ruby\ngem 'device_input', '~\u003e 1.0'\n```\n\n# Usage\n\n## Executable\n\n```shell\n$ evdump /dev/input/event0 # sudo as necessary\n```\n\nWhen the `f` key is pressed:\n```\nEV_MSC:ScanCode:33\nEV_KEY:F:1\nEV_SYN:SYN_REPORT:0\n```\n\nAnd released immediately (1=pressed, 0=released):\n```\nEV_MSC:ScanCode:33\nEV_KEY:F:1\nEV_SYN:SYN_REPORT:0\nEV_MSC:ScanCode:33\nEV_KEY:F:0\nEV_SYN:SYN_REPORT:0\n```\n\nHow about pretty mode?\n```shell\n$ sudo cat /dev/input/event0 | evdump --print pretty\n\n# f\n\n2017-01-24 05:29:43.923 Misc:ScanCode:33\n2017-01-24 05:29:43.923 Key:F:1\n2017-01-24 05:29:43.923 Sync:Report:0\n2017-01-24 05:29:44.012 Misc:ScanCode:33\n2017-01-24 05:29:44.012 Key:F:0\n2017-01-24 05:29:44.012 Sync:Report:0\n```\n\nWe can pull off the labels and go raw:\n```shell\n$ sudo cat /dev/input/event0 | evdump --print raw\n\n# f\n\n4:4:33\n1:33:1\n0:0:0\n4:4:33\n1:33:0\n0:0:0\n```\n\nFulfill your hacker-matrix fantasies:\n```shell\n$ sudo cat /dev/input/event0 | evdump --print hex\n\n# f\n\n00000000588757bd 00000000000046ca 0004 0004 00000021\n00000000588757bd 00000000000046ca 0001 0021 00000001\n00000000588757bd 00000000000046ca 0000 0000 00000000\n00000000588757bd 000000000001a298 0004 0004 00000021\n00000000588757bd 000000000001a298 0001 0021 00000000\n00000000588757bd 000000000001a298 0000 0000 00000000\n```\n\n## Library\n\n```ruby\nrequire 'device_input'\n\nFile.open('/dev/input/event0', 'r') do |dev|\n  # this loops forever and blocks waiting for input\n  DeviceInput.read_loop(dev) do |event|\n    puts event\n    # break if event.time \u003e start + 30\n  end\nend\n```\n\nAn event has:\n\n* `#data`: Struct of ints (class name Data)\n* `#time`: Time, accurate to usecs\n* `#type`: String label, possibly `UNK-X` where X is the integer from `#data`\n* `#code`: String label, possibly `UNK-X-Y` where X and Y are from `#data`\n* `#value`: Fixnum (signed) from `#data`\n\nYou will probably want to write your own read loop for your own project.\n[`DeviceInput.read_loop`](lib/device_input.rb#L98) is very simple and can\neasily be rewritten outside of this project's namespace and adapted for your\nneeds.\n\n# Background\n\n## Kernel docs\n\n* https://www.kernel.org/doc/Documentation/input/input.txt\n* https://www.kernel.org/doc/Documentation/input/event-codes.txt\n\n### Kernel structs\n\nfrom https://www.kernel.org/doc/Documentation/input/input.txt\n```\nstruct input_event {\n       struct timeval time;\n       unsigned short type;\n       unsigned short code;\n       unsigned int value;\n};\n```\n\nfrom\nhttps://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/tree/include/uapi/linux/input.h#n25\n```\nstruct input_event {\n       struct timeval time;\n        __u16 type;\n        __u16 code;\n        __s32 value;\n};\n```\n\nWhat's a [`timeval`](https://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/tree/include/uapi/linux/time.h#n15)?\n```\nstruct timeval {\n       __kernel_time_t          tv_sec;      /* seconds */\n       __kernel_suseconds_t     tv_usec;     /* microseconds */\n};\n```\n\nWhat's a [`__kernel_time_t`](https://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/tree/include/uapi/asm-generic/posix_types.h#n88)?\n```\ntypedef long            __kernel_long_t;\n# ...\ntypedef __kernel_long_t __kernel_suseconds_t;\n# ...\ntypedef __kernel_long_t __kernel_time_t;\n```\n\nWhat's a [`__u16`](https://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/tree/include/uapi/asm-generic/int-l64.h#n23)?\nWe're pretty sure it's an unsigned 16 bit integer.  Likewise `__s32` should\nbe a signed 32-bit integer:\n```\ntypedef unsigned short __u16;\n\ntypedef __signed__ int __s32;\n```\n\nWhy is the value signed?  It's meant to be able to communicate an \"analog\"\nrange, say -127 to +127 as determined by the position of a joystick.\n\n## Review\n\n`input_event`\n\n* time (timeval)\n  - tv_sec (long)\n  - tv_usec (long)\n* type (__u16)\n* code (__u16)\n* value (__s32)\n\nFlattened: `SEC` `USEC` `TYPE` `CODE` `VALUE`\n\nHow many bytes is a `long`?  Well, it's platform-dependent.  On a 32-bit\nplatform, you get 32 bits (4 bytes).  On a 64-bit platform you get 64 bits\n(8 bytes).  This means that the event is 16 bytes on a 32-bit machine and\n24 bytes on a 64-bit machine.  Software will need to accommodate.\n\n## Ruby tools\n\nWe can use\n[`RbConfig::SIZEOF`](http://idiosyncratic-ruby.com/42-ruby-config.html#rbconfigsizeof)\nand `Array#pack`/`String#unpack` to help us read these binary structs:\n```\nFIELD      C        RbConfig  Pack\n---        ---      ---       ---\ntv_sec     long     long      l!\ntv_usec    long     long      l!\ntype       __u16    uint16_t  S\ncode       __u16    uint16_t  S\nvalue      __s32    int32_t   l\n```\n\n# Acknowledgments\n\n* Inspired by https://github.com/prullmann/libdevinput (don't use it)\n  - also the source of an early version of the\n    [event code labels](lib/device_input/labels.rb)\n* Thanks to al2o3-cr from #ruby on Freenode for feedback\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frickhull%2Fdevice_input","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frickhull%2Fdevice_input","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frickhull%2Fdevice_input/lists"}