rbnput-darwin-minimal 0.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 594358ceb0f0f0e7221573889dc49812811c4650bacfc7ac83632f9a34c786ea
4
+ data.tar.gz: a796a9ad610243fdfe74a3f35ccadf1cb27700bd7c38c72219c5f50767922d4f
5
+ SHA512:
6
+ metadata.gz: a8927a2cbc7c1253c5cf79c68393c2008824688147a8086ba8fb41c46e53ba01b6efb682c2b33c02b4d420eec584e3d47470dbee78762b2c7f5b9cdcdbd4a80e
7
+ data.tar.gz: fafd61a4756920e3c8c32012e9e6717fb8af19c6459862aec7b413bc2949081dc50e0dfd44a3a16e0b4904a275efe0a9f973fbe408c7b53f5f5a13434eac89ce
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'ffi'
4
+
5
+ module Rbnput::DarwinFFI
6
+ extend FFI::Library
7
+ ffi_lib ['/System/Library/Frameworks/ApplicationServices.framework/ApplicationServices',
8
+ '/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation']
9
+
10
+ # CoreFoundation types
11
+ typedef :pointer, :CFMachPortRef
12
+ typedef :pointer, :CFRunLoopSourceRef
13
+ typedef :pointer, :CFRunLoopRef
14
+ typedef :pointer, :CFStringRef
15
+ typedef :pointer, :CGEventTapProxy
16
+ typedef :pointer, :CGEventRef
17
+
18
+ # Constants
19
+ KCGSessionEventTap = 0
20
+ KCGHeadInsertEventTap = 0
21
+ KCGEventTapOptionDefault = 0x00000000
22
+ KCGEventTapOptionListenOnly = 0x00000001
23
+
24
+ KCFRunLoopRunFinished = 1
25
+ KCFRunLoopRunStopped = 2
26
+ KCFRunLoopRunTimedOut = 3
27
+ KCFRunLoopRunHandledSource = 4
28
+
29
+ KCGEventSourceUnixProcessID = 1
30
+ KCGKeyboardEventKeycode = 9
31
+
32
+ KCGScrollWheelEventDeltaAxis1 = 11 # Y
33
+ KCGScrollWheelEventDeltaAxis2 = 12 # X
34
+
35
+ KCGEventKeyDown = 10
36
+ KCGEventKeyUp = 11
37
+ KCGEventFlagsChanged = 12
38
+
39
+ # We need to get the kCFRunLoopDefaultMode constant value
40
+ # It's a CFStringRef. For simplicity in FFI, we can often pass NULL (0) for default mode in some APIs,
41
+ # but CFRunLoopAddSource requires a mode.
42
+ # A common workaround is to look it up or define it if we know the symbol name.
43
+ # However, getting the actual pointer value of a constant exported by a dylib in FFI can be tricky.
44
+ # We'll try to attach it.
45
+ attach_variable :kCFRunLoopDefaultMode, :kCFRunLoopDefaultMode, :pointer
46
+
47
+ # CGEventTapCallback
48
+ callback :CGEventTapCallback, [:pointer, :int, :pointer, :pointer], :pointer
49
+
50
+ # Functions
51
+ attach_function :CGEventTapCreate, [:int, :int, :int, :uint64, :CGEventTapCallback, :pointer], :CFMachPortRef
52
+ attach_function :CGEventTapEnable, [:CFMachPortRef, :bool], :void
53
+ attach_function :CFMachPortCreateRunLoopSource, [:pointer, :CFMachPortRef, :long], :CFRunLoopSourceRef
54
+ attach_function :CFRunLoopGetCurrent, [], :CFRunLoopRef
55
+ attach_function :CFRunLoopAddSource, [:CFRunLoopRef, :CFRunLoopSourceRef, :pointer], :void
56
+ attach_function :CFRunLoopRunInMode, [:pointer, :double, :bool], :int
57
+ attach_function :CFRunLoopStop, [:CFRunLoopRef], :void
58
+ attach_function :CFRelease, [:pointer], :void
59
+ attach_function :AXIsProcessTrusted, [], :bool
60
+ attach_function :CGEventGetIntegerValueField, [:pointer, :int], :int64
61
+ attach_function :CGEventGetType, [:pointer], :int
62
+ attach_function :CGEventGetFlags, [:pointer], :uint64
63
+ attach_function :CGEventCreateKeyboardEvent, [:pointer, :uint16, :bool], :pointer
64
+ attach_function :CGEventPost, [:int, :pointer], :void
65
+ attach_function :CGEventSourceCreate, [:int], :pointer
66
+ attach_function :CFRelease, [:pointer], :void
67
+ attach_function :CGEventSetFlags, [:pointer, :uint64], :void
68
+ end
@@ -0,0 +1,91 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'set'
4
+ require_relative './key_code'
5
+ require_relative './simple_mutex_thread'
6
+ require_relative './darwin_ffi'
7
+
8
+ module Rbnput
9
+ # Base listener for keyboard events
10
+ class DarwinListener < Rbnput::SimpleMutexThread
11
+ def initialize(on_press: nil, on_release: nil, **kwargs)
12
+ super(*kwargs)
13
+ @on_press = on_press
14
+ @on_release = on_release
15
+
16
+ @loop = nil
17
+ @tap = nil
18
+ @callback_proc = nil # Keep reference to prevent GC
19
+ end
20
+ attr_reader :on_press, :on_release
21
+
22
+ def on_press(&proc)
23
+ @on_press = proc
24
+ end
25
+ def on_release(&proc)
26
+ @on_press = proc
27
+ end
28
+
29
+ def _run
30
+ unless Rbnput::DarwinFFI.AXIsProcessTrusted()
31
+ @log.warn("Process is not trusted! Input monitoring will not work until added to accessibility clients.")
32
+ end
33
+
34
+ # Create the callback
35
+ @callback_proc = FFI::Function.new(:pointer, [:pointer, :int, :pointer, :pointer]) do |proxy, type, event, refcon|
36
+ key_code = DarwinFFI
37
+ .CGEventGetIntegerValueField(event, Rbnput::DarwinFFI::KCGKeyboardEventKeycode)
38
+ .then { |vk| KeyCode.from_vk(vk) }
39
+
40
+ case type
41
+ when Rbnput::DarwinFFI::KCGEventKeyDown; @on_press&.call(key_code)
42
+ when Rbnput::DarwinFFI::KCGEventKeyUp; @on_release&.call(key_code)
43
+ when Rbnput::DarwinFFI::KCGEventFlagsChanged; @on_press&.call(key_code)
44
+ end
45
+ event
46
+ end
47
+
48
+ @tap = Rbnput::DarwinFFI.CGEventTapCreate(
49
+ Rbnput::DarwinFFI::KCGSessionEventTap,
50
+ Rbnput::DarwinFFI::KCGHeadInsertEventTap,
51
+ Rbnput::DarwinFFI::KCGEventTapOptionDefault,
52
+ (1 << Rbnput::DarwinFFI::KCGEventKeyDown) | (1 << Rbnput::DarwinFFI::KCGEventKeyUp) | (1 << Rbnput::DarwinFFI::KCGEventFlagsChanged),
53
+ @callback_proc,
54
+ nil
55
+ )
56
+
57
+ if @tap.null?
58
+ @log.error("Failed to create event tap")
59
+ return
60
+ end
61
+
62
+ # Create run loop source
63
+ source = Rbnput::DarwinFFI.CFMachPortCreateRunLoopSource(nil, @tap, 0)
64
+
65
+ # Add to current run loop
66
+ @loop = Rbnput::DarwinFFI.CFRunLoopGetCurrent()
67
+ Rbnput::DarwinFFI.CFRunLoopAddSource(@loop, source, Rbnput::DarwinFFI.kCFRunLoopDefaultMode)
68
+
69
+ # Enable tap
70
+ Rbnput::DarwinFFI.CGEventTapEnable(@tap, true)
71
+
72
+ # Run loop
73
+ while @running
74
+ _result = Rbnput::DarwinFFI.CFRunLoopRunInMode(Rbnput::DarwinFFI.kCFRunLoopDefaultMode, 0.1, false)
75
+
76
+ # 0.1 second timeout allows us to check @running flag
77
+ end
78
+ ensure
79
+ Rbnput::DarwinFFI.CFRelease(@tap) if @tap && !@tap.null?
80
+ Rbnput::DarwinFFI.CFRelease(source) if source && !source.null?
81
+ @tap = nil
82
+ @loop = nil
83
+ end
84
+
85
+ def stop
86
+ super
87
+ Rbnput::DarwinFFI.CFRunLoopStop(@loop) if @loop && !@loop.null?
88
+ end
89
+
90
+ end
91
+ end
@@ -0,0 +1,45 @@
1
+ require_relative "./key_code_const"
2
+
3
+ class Rbnput::KeyCode
4
+ attr_reader :vk, :is_media
5
+
6
+ def initialize(vk: nil, is_media: false)
7
+ @vk = vk
8
+ @is_media = is_media
9
+ end
10
+
11
+ def to_s
12
+ [
13
+ @vk.nil? ? "" : "vk=#{@vk}",
14
+ @is_media ? "media" : "",
15
+ ]
16
+ .reject(&:empty?)
17
+ .join(", ")
18
+ .then { "KeyCode(#{_1}, #{key})" }
19
+ end
20
+
21
+ def key
22
+ KEY_CODE_HEX_TO_NAME[@vk] || "UNKNOW"
23
+ end
24
+
25
+ def ==(other)
26
+ return false unless other.is_a?(KeyCode)
27
+ @vk == other.vk && @is_media == other.is_media
28
+ end
29
+
30
+ alias eql? ==
31
+
32
+ def hash
33
+ [@vk, @is_media].hash
34
+ end
35
+
36
+ def self.from_vk(vk, **kwargs)
37
+ new(vk: vk, is_media: false, **kwargs)
38
+ end
39
+
40
+ def self.from_media(vk, **kwargs)
41
+ new(vk: vk, is_media: true, **kwargs)
42
+ end
43
+
44
+ end
45
+
@@ -0,0 +1,128 @@
1
+ KEY_CODE_NAME_TO_HEX = {
2
+ :kVK_ANSI_A => 0x00,
3
+ :kVK_ANSI_S => 0x01,
4
+ :kVK_ANSI_D => 0x02,
5
+ :kVK_ANSI_F => 0x03,
6
+ :kVK_ANSI_H => 0x04,
7
+ :kVK_ANSI_G => 0x05,
8
+ :kVK_ANSI_Z => 0x06,
9
+ :kVK_ANSI_X => 0x07,
10
+ :kVK_ANSI_C => 0x08,
11
+ :kVK_ANSI_V => 0x09,
12
+ :kVK_ANSI_B => 0x0B,
13
+ :kVK_ANSI_Q => 0x0C,
14
+ :kVK_ANSI_W => 0x0D,
15
+ :kVK_ANSI_E => 0x0E,
16
+ :kVK_ANSI_R => 0x0F,
17
+ :kVK_ANSI_Y => 0x10,
18
+ :kVK_ANSI_T => 0x11,
19
+ :kVK_ANSI_1 => 0x12,
20
+ :kVK_ANSI_2 => 0x13,
21
+ :kVK_ANSI_3 => 0x14,
22
+ :kVK_ANSI_4 => 0x15,
23
+ :kVK_ANSI_6 => 0x16,
24
+ :kVK_ANSI_5 => 0x17,
25
+ :kVK_ANSI_Equal => 0x18,
26
+ :kVK_ANSI_9 => 0x19,
27
+ :kVK_ANSI_7 => 0x1A,
28
+ :kVK_ANSI_Minus => 0x1B,
29
+ :kVK_ANSI_8 => 0x1C,
30
+ :kVK_ANSI_0 => 0x1D,
31
+ :kVK_ANSI_RightBracket => 0x1E,
32
+ :kVK_ANSI_O => 0x1F,
33
+ :kVK_ANSI_U => 0x20,
34
+ :kVK_ANSI_LeftBracket => 0x21,
35
+ :kVK_ANSI_I => 0x22,
36
+ :kVK_ANSI_P => 0x23,
37
+ :kVK_ANSI_L => 0x25,
38
+ :kVK_ANSI_J => 0x26,
39
+ :kVK_ANSI_Quote => 0x27,
40
+ :kVK_ANSI_K => 0x28,
41
+ :kVK_ANSI_Semicolon => 0x29,
42
+ :kVK_ANSI_Backslash => 0x2A,
43
+ :kVK_ANSI_Comma => 0x2B,
44
+ :kVK_ANSI_Slash => 0x2C,
45
+ :kVK_ANSI_N => 0x2D,
46
+ :kVK_ANSI_M => 0x2E,
47
+ :kVK_ANSI_Period => 0x2F,
48
+ :kVK_ANSI_Grave => 0x32,
49
+ :kVK_ANSI_KeypadDecimal => 0x41,
50
+ :kVK_ANSI_KeypadMultiply => 0x43,
51
+ :kVK_ANSI_KeypadPlus => 0x45,
52
+ :kVK_ANSI_KeypadClear => 0x47,
53
+ :kVK_ANSI_KeypadDivide => 0x4B,
54
+ :kVK_ANSI_KeypadEnter => 0x4C,
55
+ :kVK_ANSI_KeypadMinus => 0x4E,
56
+ :kVK_ANSI_KeypadEquals => 0x51,
57
+ :kVK_ANSI_Keypad0 => 0x52,
58
+ :kVK_ANSI_Keypad1 => 0x53,
59
+ :kVK_ANSI_Keypad2 => 0x54,
60
+ :kVK_ANSI_Keypad3 => 0x55,
61
+ :kVK_ANSI_Keypad4 => 0x56,
62
+ :kVK_ANSI_Keypad5 => 0x57,
63
+ :kVK_ANSI_Keypad6 => 0x58,
64
+ :kVK_ANSI_Keypad7 => 0x59,
65
+ :kVK_ANSI_Keypad8 => 0x5B,
66
+ :kVK_ANSI_Keypad9 => 0x5C,
67
+ :kVK_F1 => 0x7A,
68
+ :kVK_F2 => 0x78,
69
+ :kVK_F3 => 0x63,
70
+ :kVK_F4 => 0x76,
71
+ :kVK_F5 => 0x60,
72
+ :kVK_F6 => 0x61,
73
+ :kVK_F7 => 0x62,
74
+ :kVK_F8 => 0x64,
75
+ :kVK_F9 => 0x65,
76
+ :kVK_F10 => 0x6D,
77
+ :kVK_F11 => 0x67,
78
+ :kVK_F12 => 0x6F,
79
+ :kVK_F13 => 0x69,
80
+ :kVK_F14 => 0x6B,
81
+ :kVK_F15 => 0x71,
82
+ :kVK_F16 => 0x6A,
83
+ :kVK_F17 => 0x40,
84
+ :kVK_F18 => 0x4F,
85
+ :kVK_F19 => 0x50,
86
+ :kVK_F20 => 0x5A,
87
+ :kVK_Space => 0x31,
88
+ :kVK_Return => 0x24,
89
+ :kVK_Tab => 0x30,
90
+ :kVK_Delete => 0x33,
91
+ :kVK_Escape => 0x35,
92
+ :kVK_LeftCommand => 0x37,
93
+ :kVK_LeftShift => 0x38,
94
+ :kVK_CapsLock => 0x39,
95
+ :kVK_LeftOption => 0x3A,
96
+ :kVK_LeftControl => 0x3B,
97
+ :kVK_RightShift => 0x3C,
98
+ :kVK_RightOption => 0x3D,
99
+ :kVK_RightControl => 0x3E,
100
+ :kVK_RightCommand => 0x36, # ใช่ ตัวนี้! Apple ไม่บอก แต่ใช้อันนี้
101
+ :kVK_Function => 0x3F,
102
+ :kVK_Home => 0x73,
103
+ :kVK_PageUp => 0x74,
104
+ :kVK_ForwardDelete => 0x75,
105
+ :kVK_End => 0x77,
106
+ :kVK_PageDown => 0x79,
107
+ :kVK_LeftArrow => 0x7B,
108
+ :kVK_RightArrow => 0x7C,
109
+ :kVK_DownArrow => 0x7D,
110
+ :kVK_UpArrow => 0x7E
111
+
112
+ # Not Implement Yet
113
+ # • NSEvent.otherEventWithType(NSSystemDefined)
114
+ # • subtype = 8
115
+ # • data1 = (0xA << 16) | (keycode << 8)
116
+ # NX_KEYTYPE_SOUND_UP = 0
117
+ # NX_KEYTYPE_SOUND_DOWN = 1
118
+ # NX_KEYTYPE_MUTE = 7
119
+ # NX_KEYTYPE_PLAY = 16
120
+ # NX_KEYTYPE_NEXT = 17
121
+ # NX_KEYTYPE_PREVIOUS = 18
122
+ # NX_KEYTYPE_ILLUMINATION_UP = 21
123
+ # NX_KEYTYPE_ILLUMINATION_DOWN = 20
124
+ # NX_KEYTYPE_FAST = 19
125
+ # NX_KEYTYPE_REWIND = 20
126
+ }
127
+
128
+ KEY_CODE_HEX_TO_NAME = KEY_CODE_NAME_TO_HEX.invert
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'thread'
4
+
5
+ class Rbnput::SimpleMutexThread
6
+
7
+ protected
8
+ # Platform-specific run implementation
9
+ # Must be implemented by subclasses
10
+ def _run; raise NotImplementedError, "Subclasses must implement _run" end
11
+
12
+ public
13
+ attr_reader :running
14
+ def initialize
15
+ @running = false
16
+ @thread = nil
17
+ @mutex = Mutex.new
18
+ end
19
+
20
+ # Start the listener in a separate thread
21
+ def start
22
+ @mutex.synchronize do
23
+ return if @running
24
+ @running = true
25
+ @thread = Thread.new do
26
+ begin; _run
27
+ ensure; @running = false end
28
+ end
29
+ end
30
+ self
31
+ end
32
+
33
+ # Stop the listener
34
+ def stop
35
+ @mutex.synchronize do; @running = false end
36
+ @thread&.join(5) # Wait up to 5 seconds
37
+ self
38
+ end
39
+
40
+ def join; @thread&.join end
41
+ def alive?; @running && @thread&.alive? end
42
+
43
+
44
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rbnput
4
+ VERSION = "0.1.0"
5
+ AUTHOR = "Krist Ponpairin"
6
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Rbnput - Ruby Input Library
4
+ # Copyright (C) 2025
5
+ #
6
+ # This program is free software: you can redistribute it and/or modify it under
7
+ # the terms of the GNU Lesser General Public License as published by the Free
8
+ # Software Foundation, either version 3 of the License, or (at your option) any
9
+ # later version.
10
+ #
11
+ # This program is distributed in the hope that it will be useful, but WITHOUT
12
+ # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
13
+ # FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
14
+ # details.
15
+
16
+ require_relative "rbnput/version"
17
+ require_relative "rbnput/darwin_listener"
18
+ # require_relative "rbnput/mouse"
19
+
20
+ # The main Rbnput module
21
+ #
22
+ # This module imports keyboard and mouse submodules for controlling
23
+ # and monitoring input devices.
24
+ module Rbnput
25
+ Listener = ::Rbnput::DarwinListener
26
+ end
metadata ADDED
@@ -0,0 +1,59 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rbnput-darwin-minimal
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Krist Ponpairin
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: ffi
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - "~>"
17
+ - !ruby/object:Gem::Version
18
+ version: '1.15'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - "~>"
24
+ - !ruby/object:Gem::Version
25
+ version: '1.15'
26
+ description: A Ruby library for keyboard monitoring in mac
27
+ email:
28
+ - krist7599555@gmail.com
29
+ executables: []
30
+ extensions: []
31
+ extra_rdoc_files: []
32
+ files:
33
+ - lib/rbnput-darwin-minimal.rb
34
+ - lib/rbnput/darwin_ffi.rb
35
+ - lib/rbnput/darwin_listener.rb
36
+ - lib/rbnput/key_code.rb
37
+ - lib/rbnput/key_code_const.rb
38
+ - lib/rbnput/simple_mutex_thread.rb
39
+ - lib/rbnput/version.rb
40
+ licenses: []
41
+ metadata: {}
42
+ rdoc_options: []
43
+ require_paths:
44
+ - lib
45
+ required_ruby_version: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - ">="
48
+ - !ruby/object:Gem::Version
49
+ version: '0'
50
+ required_rubygems_version: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ requirements: []
56
+ rubygems_version: 3.7.2
57
+ specification_version: 4
58
+ summary: Ruby Input library (rbnput)
59
+ test_files: []