rbnput-darwin-minimal 0.1.0 → 1.4.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 +4 -4
- data/LICENSE +22 -0
- data/README.md +70 -0
- data/lib/rbnput/darwin_ffi.rb +147 -63
- data/lib/rbnput/darwin_listener.rb +47 -8
- data/lib/rbnput/key_code.rb +8 -0
- data/lib/rbnput/key_code_const.rb +5 -0
- data/lib/rbnput/simple_mutex_thread.rb +27 -4
- data/lib/rbnput/version.rb +4 -1
- data/lib/rbnput-darwin-minimal.rb +3 -0
- metadata +16 -6
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: bde651d183da7dc9b3e17dcf9a4789fb1f2e16ca089909f8440b87056a23baf7
|
|
4
|
+
data.tar.gz: eb0416ba45e6545674ff3cbf12172db87feee358fddd3eeedfa96056869e6f6a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 9ae0c01ddfcc21e87a38e3d5977ff54a7233ff73bbf9544bb1903534ca6cc26388abbc9c2871a34e00b271aec767f58e00d3191445e5abb6f3b984d0a55c92f0
|
|
7
|
+
data.tar.gz: 3f8b61f1abdeae98b6ba07ce6f1126ea6142df78fe446d8a5933dab1b57384bb23e5ca68adcb2d9b654307a851ba67b4ded64b2eb105f7612ebc6e3643b0eb09
|
data/LICENSE
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
This is free and unencumbered software released into the public domain.
|
|
2
|
+
|
|
3
|
+
Anyone is free to copy, modify, publish, use, compile, sell, or
|
|
4
|
+
distribute this software, either in source code form or as a compiled
|
|
5
|
+
binary, for any purpose, commercial or non-commercial, and by any
|
|
6
|
+
means.
|
|
7
|
+
|
|
8
|
+
In jurisdictions that recognize copyright laws, the author or authors
|
|
9
|
+
of this software dedicate any and all copyright interest in the
|
|
10
|
+
software to the public domain. We make this dedication for the benefit
|
|
11
|
+
of the public at large and to the detriment of our heirs and
|
|
12
|
+
successors. We intend this dedication to be an overt act of
|
|
13
|
+
relinquishment in perpetuity of all present and future rights to this
|
|
14
|
+
software under copyright law.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
|
19
|
+
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
|
20
|
+
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
|
21
|
+
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
|
22
|
+
OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# 🎹 rbnput-darwin-minimal 🍎
|
|
2
|
+
|
|
3
|
+
[](https://badge.fury.io/rb/rbnput-darwin-minimal)
|
|
4
|
+
[](https://github.com/krist7599555/rbnput-darwin-minimal)
|
|
5
|
+
|
|
6
|
+
ไลบรารี Ruby ขนาดเล็กสำหรับตรวจจับการกดแป้นพิมพ์บนระบบ macOS โดยใช้ FFI เชื่อมต่อกับ system library ของ Darwin โดยตรง
|
|
7
|
+
|
|
8
|
+
## ✨ คุณสมบัติ
|
|
9
|
+
|
|
10
|
+
- 🔍 ตรวจจับการกดคีย์แบบ low-level
|
|
11
|
+
- 🧬 ใช้ FFI เชื่อมต่อกับ Carbon / IOKit
|
|
12
|
+
- 🪶 ไม่มี dependency หนัก
|
|
13
|
+
- 📦 โค้ดสั้นและเข้าใจง่าย เหมาะสำหรับเรียนรู้หรือฝังใช้งานในโปรเจคเล็กๆ
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
## 🚀 ตัวอย่างเริ่ม Listener เพื่อรับ keycode
|
|
17
|
+
|
|
18
|
+
```ruby
|
|
19
|
+
require "rbnput"
|
|
20
|
+
listener = Rbnput::Listener.new
|
|
21
|
+
listener.on_press do |key|
|
|
22
|
+
puts "Key up : #{key}"
|
|
23
|
+
end
|
|
24
|
+
listener.on_release do |key|
|
|
25
|
+
puts "Key down : #{key}"
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
listener.start
|
|
29
|
+
listener.join
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## 🖥️ การรันแบบ Shell
|
|
33
|
+
|
|
34
|
+
### 💎 ใช้ ruby ตรงๆ
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
APP_MODE=local ruby ./examples/keyboard_listener.rb
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### 🧰 ใช้ rake
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
# สำหรับทดสอบ local lib
|
|
44
|
+
rake test_local
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
# สำหรับทดสอบ production gem
|
|
49
|
+
rake test_local
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
⚠️ ข้อจำกัด
|
|
53
|
+
- 🐧 รองรับเฉพาะ macOS (Darwin)
|
|
54
|
+
- 💎 ต้องรันบน Ruby ที่รองรับ ffi
|
|
55
|
+
- 🔐 ต้องมีสิทธิ์ “Input Monitoring” ใน System Settings
|
|
56
|
+
|
|
57
|
+
🔑 การให้สิทธิ์ (Permissions)
|
|
58
|
+
1. ⚙️ เปิด System Settings
|
|
59
|
+
2. 🔐 ไปที่ Privacy & Security
|
|
60
|
+
3. ⌨️ เลือก Input Monitoring
|
|
61
|
+
4. ➕ เพิ่ม Ruby หรือ Terminal ที่ใช้รันโปรแกรมของคุณเข้าไป
|
|
62
|
+
|
|
63
|
+
📂 โฟลเดอร์ / ไฟล์สำคัญ
|
|
64
|
+
|
|
65
|
+
- [./lib/rbnput/darwin_listener.rb | Most Of Implement 🧠](./lib/rbnput/darwin_listener.rb)
|
|
66
|
+
- [./lib/rbnput.rb | Lib Endpoint](./lib/rbnput.rb)
|
|
67
|
+
- [./lib/rbnput/key_code_const.rb | All Key Map](./lib/rbnput/key_code_const.rb)
|
|
68
|
+
- [./lib/rbnput/key_code.rb | Class KeyCode(vk, is_media)](./lib/rbnput/key_code.rb)
|
|
69
|
+
- [./lib/rbnput/darwin_ffi.rb | SystemLibrary Bind FFI 🪪](./lib/rbnput/darwin_ffi.rb)
|
|
70
|
+
- [./lib/rbnput/simple_mutex_thread.rb | Boring Thread Implement ](./lib/rbnput/simple_mutex_thread.rb)
|
data/lib/rbnput/darwin_ffi.rb
CHANGED
|
@@ -2,67 +2,151 @@
|
|
|
2
2
|
|
|
3
3
|
require 'ffi'
|
|
4
4
|
|
|
5
|
-
module Rbnput
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
5
|
+
module Rbnput
|
|
6
|
+
# FFI bindings for macOS ApplicationServices and CoreFoundation.
|
|
7
|
+
# Provides low-level access to Quartz Event Services.
|
|
8
|
+
#
|
|
9
|
+
# @api private
|
|
10
|
+
module DarwinFFI
|
|
11
|
+
extend FFI::Library
|
|
12
|
+
ffi_lib ['/System/Library/Frameworks/ApplicationServices.framework/ApplicationServices',
|
|
13
|
+
'/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation']
|
|
14
|
+
|
|
15
|
+
# CoreFoundation types
|
|
16
|
+
typedef :pointer, :CFMachPortRef
|
|
17
|
+
typedef :pointer, :CFRunLoopSourceRef
|
|
18
|
+
typedef :pointer, :CFRunLoopRef
|
|
19
|
+
typedef :pointer, :CFStringRef
|
|
20
|
+
typedef :pointer, :CGEventTapProxy
|
|
21
|
+
typedef :pointer, :CGEventRef
|
|
22
|
+
|
|
23
|
+
# Constants
|
|
24
|
+
KCGSessionEventTap = 0
|
|
25
|
+
KCGHeadInsertEventTap = 0
|
|
26
|
+
KCGEventTapOptionDefault = 0x00000000
|
|
27
|
+
KCGEventTapOptionListenOnly = 0x00000001
|
|
28
|
+
|
|
29
|
+
KCFRunLoopRunFinished = 1
|
|
30
|
+
KCFRunLoopRunStopped = 2
|
|
31
|
+
KCFRunLoopRunTimedOut = 3
|
|
32
|
+
KCFRunLoopRunHandledSource = 4
|
|
33
|
+
|
|
34
|
+
KCGEventSourceUnixProcessID = 1
|
|
35
|
+
KCGKeyboardEventKeycode = 9
|
|
36
|
+
|
|
37
|
+
KCGScrollWheelEventDeltaAxis1 = 11 # Y
|
|
38
|
+
KCGScrollWheelEventDeltaAxis2 = 12 # X
|
|
39
|
+
|
|
40
|
+
KCGEventKeyDown = 10
|
|
41
|
+
KCGEventKeyUp = 11
|
|
42
|
+
KCGEventFlagsChanged = 12
|
|
43
|
+
|
|
44
|
+
# We need to get the kCFRunLoopDefaultMode constant value
|
|
45
|
+
# It's a CFStringRef. For simplicity in FFI, we can often pass NULL (0) for default mode in some APIs,
|
|
46
|
+
# but CFRunLoopAddSource requires a mode.
|
|
47
|
+
# A common workaround is to look it up or define it if we know the symbol name.
|
|
48
|
+
# However, getting the actual pointer value of a constant exported by a dylib in FFI can be tricky.
|
|
49
|
+
# We'll try to attach it.
|
|
50
|
+
attach_variable :kCFRunLoopDefaultMode, :kCFRunLoopDefaultMode, :pointer
|
|
51
|
+
|
|
52
|
+
# CGEventTapCallback function signature
|
|
53
|
+
# @param proxy [CGEventTapProxy]
|
|
54
|
+
# @param type [Integer] The event type
|
|
55
|
+
# @param event [CGEventRef] The event reference
|
|
56
|
+
# @param refcon [Pointer] User data
|
|
57
|
+
# @return [CGEventRef] The event to pass through, or NULL to suppress
|
|
58
|
+
callback :CGEventTapCallback, [:pointer, :int, :pointer, :pointer], :pointer
|
|
59
|
+
|
|
60
|
+
# Functions
|
|
61
|
+
|
|
62
|
+
# Creates an event tap.
|
|
63
|
+
# @param tapLocation [Integer]
|
|
64
|
+
# @param place [Integer]
|
|
65
|
+
# @param options [Integer]
|
|
66
|
+
# @param eventsOfInterest [Integer] Mask of events to listen for
|
|
67
|
+
# @param callback [Proc] Block to be called
|
|
68
|
+
# @param userInfo [Pointer]
|
|
69
|
+
# @return [CFMachPortRef]
|
|
70
|
+
attach_function :CGEventTapCreate, [:int, :int, :int, :uint64, :CGEventTapCallback, :pointer], :CFMachPortRef
|
|
71
|
+
|
|
72
|
+
# Enables or disables an event tap.
|
|
73
|
+
# @param tap [CFMachPortRef]
|
|
74
|
+
# @param enable [Boolean]
|
|
75
|
+
attach_function :CGEventTapEnable, [:CFMachPortRef, :bool], :void
|
|
76
|
+
|
|
77
|
+
# Creates a run loop source for a Mach port.
|
|
78
|
+
# @param allocator [Pointer]
|
|
79
|
+
# @param port [CFMachPortRef]
|
|
80
|
+
# @param order [Integer]
|
|
81
|
+
# @return [CFRunLoopSourceRef]
|
|
82
|
+
attach_function :CFMachPortCreateRunLoopSource, [:pointer, :CFMachPortRef, :long], :CFRunLoopSourceRef
|
|
83
|
+
|
|
84
|
+
# Returns the current run loop.
|
|
85
|
+
# @return [CFRunLoopRef]
|
|
86
|
+
attach_function :CFRunLoopGetCurrent, [], :CFRunLoopRef
|
|
87
|
+
|
|
88
|
+
# Adds a source to a run loop mode.
|
|
89
|
+
# @param rl [CFRunLoopRef]
|
|
90
|
+
# @param source [CFRunLoopSourceRef]
|
|
91
|
+
# @param mode [CFStringRef]
|
|
92
|
+
attach_function :CFRunLoopAddSource, [:CFRunLoopRef, :CFRunLoopSourceRef, :pointer], :void
|
|
93
|
+
|
|
94
|
+
# Runs the current run loop in a specific mode.
|
|
95
|
+
# @param mode [CFStringRef]
|
|
96
|
+
# @param seconds [Float]
|
|
97
|
+
# @param returnAfterSourceHandled [Boolean]
|
|
98
|
+
# @return [Integer] Result code
|
|
99
|
+
attach_function :CFRunLoopRunInMode, [:pointer, :double, :bool], :int
|
|
100
|
+
|
|
101
|
+
# Stops a run loop.
|
|
102
|
+
# @param rl [CFRunLoopRef]
|
|
103
|
+
attach_function :CFRunLoopStop, [:CFRunLoopRef], :void
|
|
104
|
+
|
|
105
|
+
# RE-DEFINED: Removes a value from a retain/release system.
|
|
106
|
+
# This was defined twice in the original file, keeping one.
|
|
107
|
+
# @param cf [Pointer]
|
|
108
|
+
attach_function :CFRelease, [:pointer], :void
|
|
109
|
+
|
|
110
|
+
# Checks if the current process is trusted for accessibility.
|
|
111
|
+
# @return [Boolean]
|
|
112
|
+
attach_function :AXIsProcessTrusted, [], :bool
|
|
113
|
+
|
|
114
|
+
# Gets an integer value from an event field.
|
|
115
|
+
# @param event [CGEventRef]
|
|
116
|
+
# @param field [Integer]
|
|
117
|
+
# @return [Integer]
|
|
118
|
+
attach_function :CGEventGetIntegerValueField, [:pointer, :int], :int64
|
|
119
|
+
|
|
120
|
+
# Gets the event type.
|
|
121
|
+
# @param event [CGEventRef]
|
|
122
|
+
# @return [Integer]
|
|
123
|
+
attach_function :CGEventGetType, [:pointer], :int
|
|
124
|
+
|
|
125
|
+
# Gets the event flags.
|
|
126
|
+
# @param event [CGEventRef]
|
|
127
|
+
# @return [Integer]
|
|
128
|
+
attach_function :CGEventGetFlags, [:pointer], :uint64
|
|
129
|
+
|
|
130
|
+
# Creates a keyboard event.
|
|
131
|
+
# @param source [Pointer]
|
|
132
|
+
# @param virtualKey [Integer]
|
|
133
|
+
# @param keyDown [Boolean]
|
|
134
|
+
# @return [CGEventRef]
|
|
135
|
+
attach_function :CGEventCreateKeyboardEvent, [:pointer, :uint16, :bool], :pointer
|
|
136
|
+
|
|
137
|
+
# Posts an event to the event stream.
|
|
138
|
+
# @param tapLocation [Integer]
|
|
139
|
+
# @param event [CGEventRef]
|
|
140
|
+
attach_function :CGEventPost, [:int, :pointer], :void
|
|
141
|
+
|
|
142
|
+
# Creates an event source.
|
|
143
|
+
# @param stateID [Integer]
|
|
144
|
+
# @return [Pointer]
|
|
145
|
+
attach_function :CGEventSourceCreate, [:int], :pointer
|
|
146
|
+
|
|
147
|
+
# Sets the event flags.
|
|
148
|
+
# @param event [CGEventRef]
|
|
149
|
+
# @param flags [Integer]
|
|
150
|
+
attach_function :CGEventSetFlags, [:pointer, :uint64], :void
|
|
151
|
+
end
|
|
68
152
|
end
|
|
@@ -7,83 +7,122 @@ require_relative './darwin_ffi'
|
|
|
7
7
|
|
|
8
8
|
module Rbnput
|
|
9
9
|
# Base listener for keyboard events
|
|
10
|
+
# คลาส Listener พื้นฐานสำหรับเหตุการณ์คีย์บอร์ด บน macOS
|
|
10
11
|
class DarwinListener < Rbnput::SimpleMutexThread
|
|
12
|
+
# Initializes the DarwinListener.
|
|
13
|
+
# สร้าง instance ใหม่สำหรับดักจับคีย์บอร์ด
|
|
14
|
+
#
|
|
15
|
+
# @param on_press [Proc, nil] Callback when a key is pressed.
|
|
16
|
+
# @param on_release [Proc, nil] Callback when a key is released.
|
|
17
|
+
# @param kwargs [Hash] Additional options passed to SimpleMutexThread.
|
|
11
18
|
def initialize(on_press: nil, on_release: nil, **kwargs)
|
|
12
19
|
super(*kwargs)
|
|
13
|
-
@on_press = on_press
|
|
14
|
-
@on_release = on_release
|
|
20
|
+
@on_press = on_press # callback เมื่อกดปุ่ม
|
|
21
|
+
@on_release = on_release # callback เมื่อปล่อยปุ่ม
|
|
15
22
|
|
|
16
23
|
@loop = nil
|
|
17
24
|
@tap = nil
|
|
18
|
-
@callback_proc = nil # Keep reference to prevent GC
|
|
25
|
+
@callback_proc = nil # Keep reference to prevent GC (เก็บ reference ไว้เพื่อป้องกัน Garbage Collection)
|
|
19
26
|
end
|
|
20
27
|
attr_reader :on_press, :on_release
|
|
21
28
|
|
|
29
|
+
# Sets the callback for key press events.
|
|
30
|
+
# ตั้งค่า callback สำหรับการกดปุ่ม
|
|
31
|
+
#
|
|
32
|
+
# @yield [key]
|
|
33
|
+
# @yieldparam key [KeyCode] The key that was pressed.
|
|
22
34
|
def on_press(&proc)
|
|
23
35
|
@on_press = proc
|
|
24
36
|
end
|
|
37
|
+
|
|
38
|
+
# Sets the callback for key release events.
|
|
39
|
+
# ตั้งค่า callback สำหรับการปล่อยปุ่ม
|
|
40
|
+
#
|
|
41
|
+
# @yield [key]
|
|
42
|
+
# @yieldparam key [KeyCode] The key that was released.
|
|
25
43
|
def on_release(&proc)
|
|
26
|
-
@
|
|
44
|
+
@on_release = proc
|
|
27
45
|
end
|
|
28
46
|
|
|
47
|
+
# The main run loop for the listener.
|
|
48
|
+
# Internal method called by the thread.
|
|
49
|
+
#
|
|
50
|
+
# @api private
|
|
29
51
|
def _run
|
|
52
|
+
# ตรวจสอบว่า Process ได้รับสิทธิ์ Accessibility หรือไม่
|
|
30
53
|
unless Rbnput::DarwinFFI.AXIsProcessTrusted()
|
|
31
54
|
@log.warn("Process is not trusted! Input monitoring will not work until added to accessibility clients.")
|
|
55
|
+
@log.warn("โปรแกรมนี้ยังไม่ได้รับสิทธิ์ Accessibility! การดักจับอินพุตจะไม่ทำงานจนกว่าจะได้รับอนุญาต")
|
|
32
56
|
end
|
|
33
57
|
|
|
34
58
|
# Create the callback
|
|
59
|
+
# สร้าง callback function ที่จะถูกเรียกเมื่อมี event
|
|
35
60
|
@callback_proc = FFI::Function.new(:pointer, [:pointer, :int, :pointer, :pointer]) do |proxy, type, event, refcon|
|
|
61
|
+
# ดึงค่า key code จาก event และแปลงเป็น KeyCode object
|
|
36
62
|
key_code = DarwinFFI
|
|
37
63
|
.CGEventGetIntegerValueField(event, Rbnput::DarwinFFI::KCGKeyboardEventKeycode)
|
|
38
64
|
.then { |vk| KeyCode.from_vk(vk) }
|
|
39
65
|
|
|
40
66
|
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)
|
|
67
|
+
when Rbnput::DarwinFFI::KCGEventKeyDown; @on_press&.call(key_code) # กดปุ่ม
|
|
68
|
+
when Rbnput::DarwinFFI::KCGEventKeyUp; @on_release&.call(key_code) # ปล่อยปุ่ม
|
|
69
|
+
when Rbnput::DarwinFFI::KCGEventFlagsChanged; @on_press&.call(key_code) # ปุ่ม Modifier เปลี่ยนแปลง (เช่น Shift, Ctrl)
|
|
44
70
|
end
|
|
45
71
|
event
|
|
46
72
|
end
|
|
47
73
|
|
|
74
|
+
# สร้าง Event Tap เพื่อดักจับ event ของระบบ
|
|
48
75
|
@tap = Rbnput::DarwinFFI.CGEventTapCreate(
|
|
49
76
|
Rbnput::DarwinFFI::KCGSessionEventTap,
|
|
50
77
|
Rbnput::DarwinFFI::KCGHeadInsertEventTap,
|
|
51
78
|
Rbnput::DarwinFFI::KCGEventTapOptionDefault,
|
|
52
|
-
(1 << Rbnput::DarwinFFI::KCGEventKeyDown) | (1 << Rbnput::DarwinFFI::KCGEventKeyUp) | (1 << Rbnput::DarwinFFI::KCGEventFlagsChanged),
|
|
79
|
+
(1 << Rbnput::DarwinFFI::KCGEventKeyDown) | (1 << Rbnput::DarwinFFI::KCGEventKeyUp) | (1 << Rbnput::DarwinFFI::KCGEventFlagsChanged), # ระบุประเภท event ที่ต้องการดักจับ
|
|
53
80
|
@callback_proc,
|
|
54
81
|
nil
|
|
55
82
|
)
|
|
56
83
|
|
|
57
84
|
if @tap.null?
|
|
58
85
|
@log.error("Failed to create event tap")
|
|
86
|
+
@log.error("ไม่สามารถสร้าง event tap ได้")
|
|
59
87
|
return
|
|
60
88
|
end
|
|
61
89
|
|
|
62
90
|
# Create run loop source
|
|
91
|
+
# สร้าง run loop source จาก tap
|
|
63
92
|
source = Rbnput::DarwinFFI.CFMachPortCreateRunLoopSource(nil, @tap, 0)
|
|
64
93
|
|
|
65
94
|
# Add to current run loop
|
|
95
|
+
# เพิ่ม source เข้าไปใน run loop ปัจจุบัน
|
|
66
96
|
@loop = Rbnput::DarwinFFI.CFRunLoopGetCurrent()
|
|
67
97
|
Rbnput::DarwinFFI.CFRunLoopAddSource(@loop, source, Rbnput::DarwinFFI.kCFRunLoopDefaultMode)
|
|
68
98
|
|
|
69
99
|
# Enable tap
|
|
100
|
+
# เปิดใช้งาน tap
|
|
70
101
|
Rbnput::DarwinFFI.CGEventTapEnable(@tap, true)
|
|
71
102
|
|
|
72
103
|
# Run loop
|
|
104
|
+
# เริ่มทำงาน loop เพื่อรอรับ event
|
|
73
105
|
while @running
|
|
74
106
|
_result = Rbnput::DarwinFFI.CFRunLoopRunInMode(Rbnput::DarwinFFI.kCFRunLoopDefaultMode, 0.1, false)
|
|
75
107
|
|
|
76
108
|
# 0.1 second timeout allows us to check @running flag
|
|
109
|
+
# timeout 0.1 วินาที เพื่อให้สามารถตรวจสอบ flag @running ได้ (เพื่อให้หยุด loop ได้อย่างนุ่มนวล)
|
|
77
110
|
end
|
|
78
111
|
ensure
|
|
112
|
+
# Cleanup เมื่อจบการทำงาน
|
|
79
113
|
Rbnput::DarwinFFI.CFRelease(@tap) if @tap && !@tap.null?
|
|
80
114
|
Rbnput::DarwinFFI.CFRelease(source) if source && !source.null?
|
|
81
115
|
@tap = nil
|
|
82
116
|
@loop = nil
|
|
83
117
|
end
|
|
84
118
|
|
|
119
|
+
# Stops the listener and the run loop.
|
|
120
|
+
# หยุดการทำงานของ listener
|
|
121
|
+
#
|
|
122
|
+
# @return [void]
|
|
85
123
|
def stop
|
|
86
124
|
super
|
|
125
|
+
# หยุด run loop
|
|
87
126
|
Rbnput::DarwinFFI.CFRunLoopStop(@loop) if @loop && !@loop.null?
|
|
88
127
|
end
|
|
89
128
|
|
data/lib/rbnput/key_code.rb
CHANGED
|
@@ -3,11 +3,15 @@ require_relative "./key_code_const"
|
|
|
3
3
|
class Rbnput::KeyCode
|
|
4
4
|
attr_reader :vk, :is_media
|
|
5
5
|
|
|
6
|
+
# สร้าง Object KeyCode
|
|
7
|
+
# @param vk [Integer] Virtual Key Code
|
|
8
|
+
# @param is_media [Boolean] เป็น Media Key หรือไม่
|
|
6
9
|
def initialize(vk: nil, is_media: false)
|
|
7
10
|
@vk = vk
|
|
8
11
|
@is_media = is_media
|
|
9
12
|
end
|
|
10
13
|
|
|
14
|
+
# แปลงเป็น String สำหรับแสดงผล
|
|
11
15
|
def to_s
|
|
12
16
|
[
|
|
13
17
|
@vk.nil? ? "" : "vk=#{@vk}",
|
|
@@ -18,10 +22,12 @@ class Rbnput::KeyCode
|
|
|
18
22
|
.then { "KeyCode(#{_1}, #{key})" }
|
|
19
23
|
end
|
|
20
24
|
|
|
25
|
+
# ชื่อของปุ่ม (จากค่า vk)
|
|
21
26
|
def key
|
|
22
27
|
KEY_CODE_HEX_TO_NAME[@vk] || "UNKNOW"
|
|
23
28
|
end
|
|
24
29
|
|
|
30
|
+
# เปรียบเทียบ object
|
|
25
31
|
def ==(other)
|
|
26
32
|
return false unless other.is_a?(KeyCode)
|
|
27
33
|
@vk == other.vk && @is_media == other.is_media
|
|
@@ -33,10 +39,12 @@ class Rbnput::KeyCode
|
|
|
33
39
|
[@vk, @is_media].hash
|
|
34
40
|
end
|
|
35
41
|
|
|
42
|
+
# สร้าง KeyCode จาก Virtual Key
|
|
36
43
|
def self.from_vk(vk, **kwargs)
|
|
37
44
|
new(vk: vk, is_media: false, **kwargs)
|
|
38
45
|
end
|
|
39
46
|
|
|
47
|
+
# สร้าง KeyCode จาก Media Key
|
|
40
48
|
def self.from_media(vk, **kwargs)
|
|
41
49
|
new(vk: vk, is_media: true, **kwargs)
|
|
42
50
|
end
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Mapping from key names to macOS unique virtual key codes (Hex).
|
|
4
|
+
# Based on the system header files.
|
|
1
5
|
KEY_CODE_NAME_TO_HEX = {
|
|
2
6
|
:kVK_ANSI_A => 0x00,
|
|
3
7
|
:kVK_ANSI_S => 0x01,
|
|
@@ -125,4 +129,5 @@ KEY_CODE_NAME_TO_HEX = {
|
|
|
125
129
|
# NX_KEYTYPE_REWIND = 20
|
|
126
130
|
}
|
|
127
131
|
|
|
132
|
+
# Reverse mapping from virtual key codes to key names.
|
|
128
133
|
KEY_CODE_HEX_TO_NAME = KEY_CODE_NAME_TO_HEX.invert
|
|
@@ -2,22 +2,35 @@
|
|
|
2
2
|
|
|
3
3
|
require 'thread'
|
|
4
4
|
|
|
5
|
+
# A simple thread wrapper with mutex synchronization.
|
|
6
|
+
# Provides a base class for threaded listeners.
|
|
5
7
|
class Rbnput::SimpleMutexThread
|
|
6
8
|
|
|
7
9
|
protected
|
|
8
|
-
|
|
9
|
-
#
|
|
10
|
+
|
|
11
|
+
# Platform-specific run implementation.
|
|
12
|
+
# Must be implemented by subclasses.
|
|
13
|
+
#
|
|
14
|
+
# @return [void]
|
|
15
|
+
# @raise [NotImplementedError] If not implemented by subclass
|
|
10
16
|
def _run; raise NotImplementedError, "Subclasses must implement _run" end
|
|
11
17
|
|
|
12
18
|
public
|
|
19
|
+
|
|
20
|
+
# @return [Boolean] true if the thread is running
|
|
13
21
|
attr_reader :running
|
|
22
|
+
|
|
23
|
+
# Initializes the SimpleMutexThread.
|
|
14
24
|
def initialize
|
|
15
25
|
@running = false
|
|
16
26
|
@thread = nil
|
|
17
27
|
@mutex = Mutex.new
|
|
18
28
|
end
|
|
19
29
|
|
|
20
|
-
#
|
|
30
|
+
# Starts the listener in a separate thread.
|
|
31
|
+
# Check is running before start new thread.
|
|
32
|
+
#
|
|
33
|
+
# @return [self]
|
|
21
34
|
def start
|
|
22
35
|
@mutex.synchronize do
|
|
23
36
|
return if @running
|
|
@@ -30,14 +43,24 @@ class Rbnput::SimpleMutexThread
|
|
|
30
43
|
self
|
|
31
44
|
end
|
|
32
45
|
|
|
33
|
-
#
|
|
46
|
+
# Stops the listener.
|
|
47
|
+
# Sets running flag to false and waits for the thread to exit.
|
|
48
|
+
#
|
|
49
|
+
# @return [self]
|
|
34
50
|
def stop
|
|
35
51
|
@mutex.synchronize do; @running = false end
|
|
36
52
|
@thread&.join(5) # Wait up to 5 seconds
|
|
37
53
|
self
|
|
38
54
|
end
|
|
39
55
|
|
|
56
|
+
# Joins the thread.
|
|
57
|
+
#
|
|
58
|
+
# @return [Thread, nil]
|
|
40
59
|
def join; @thread&.join end
|
|
60
|
+
|
|
61
|
+
# Checks if the thread is alive.
|
|
62
|
+
#
|
|
63
|
+
# @return [Boolean]
|
|
41
64
|
def alive?; @running && @thread&.alive? end
|
|
42
65
|
|
|
43
66
|
|
data/lib/rbnput/version.rb
CHANGED
|
@@ -18,9 +18,12 @@ require_relative "rbnput/darwin_listener"
|
|
|
18
18
|
# require_relative "rbnput/mouse"
|
|
19
19
|
|
|
20
20
|
# The main Rbnput module
|
|
21
|
+
# โมดูลหลักสำหรับ Rbnput
|
|
21
22
|
#
|
|
22
23
|
# This module imports keyboard and mouse submodules for controlling
|
|
23
24
|
# and monitoring input devices.
|
|
25
|
+
# โมดูลนี้จะนำเข้าโมดูลย่อยของคีย์บอร์ดและเมาส์เพื่อควบคุมและตรวจสอบอุปกรณ์อินพุต
|
|
24
26
|
module Rbnput
|
|
27
|
+
# กำหนด Listener เป็น DarwinListener สำหรับระบบ macOS
|
|
25
28
|
Listener = ::Rbnput::DarwinListener
|
|
26
29
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: rbnput-darwin-minimal
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version:
|
|
4
|
+
version: 1.4.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Krist Ponpairin
|
|
@@ -23,13 +23,17 @@ dependencies:
|
|
|
23
23
|
- - "~>"
|
|
24
24
|
- !ruby/object:Gem::Version
|
|
25
25
|
version: '1.15'
|
|
26
|
-
description:
|
|
26
|
+
description: Lightweight Ruby library for low-level keyboard monitoring on macOS,
|
|
27
|
+
implemented through FFI bindings to Darwin system libraries.
|
|
27
28
|
email:
|
|
28
29
|
- krist7599555@gmail.com
|
|
29
30
|
executables: []
|
|
30
31
|
extensions: []
|
|
31
|
-
extra_rdoc_files:
|
|
32
|
+
extra_rdoc_files:
|
|
33
|
+
- README.md
|
|
32
34
|
files:
|
|
35
|
+
- LICENSE
|
|
36
|
+
- README.md
|
|
33
37
|
- lib/rbnput-darwin-minimal.rb
|
|
34
38
|
- lib/rbnput/darwin_ffi.rb
|
|
35
39
|
- lib/rbnput/darwin_listener.rb
|
|
@@ -37,8 +41,14 @@ files:
|
|
|
37
41
|
- lib/rbnput/key_code_const.rb
|
|
38
42
|
- lib/rbnput/simple_mutex_thread.rb
|
|
39
43
|
- lib/rbnput/version.rb
|
|
44
|
+
homepage: https://github.com/krist7599555/rbnput-darwin-minimal
|
|
40
45
|
licenses: []
|
|
41
|
-
metadata:
|
|
46
|
+
metadata:
|
|
47
|
+
homepage_uri: https://github.com/krist7599555/rbnput-darwin-minimal
|
|
48
|
+
source_code_uri: https://github.com/krist7599555/rbnput-darwin-minimal
|
|
49
|
+
bug_tracker_uri: https://github.com/krist7599555/rbnput-darwin-minimal/issues
|
|
50
|
+
documentation_uri: https://github.com/krist7599555/rbnput-darwin-minimal
|
|
51
|
+
changelog_uri: https://github.com/krist7599555/rbnput-darwin-minimal/releases
|
|
42
52
|
rdoc_options: []
|
|
43
53
|
require_paths:
|
|
44
54
|
- lib
|
|
@@ -46,7 +56,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
46
56
|
requirements:
|
|
47
57
|
- - ">="
|
|
48
58
|
- !ruby/object:Gem::Version
|
|
49
|
-
version: '0'
|
|
59
|
+
version: '3.0'
|
|
50
60
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
51
61
|
requirements:
|
|
52
62
|
- - ">="
|
|
@@ -55,5 +65,5 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
55
65
|
requirements: []
|
|
56
66
|
rubygems_version: 3.7.2
|
|
57
67
|
specification_version: 4
|
|
58
|
-
summary: Ruby
|
|
68
|
+
summary: Minimal Ruby keyboard listener for macOS using FFI
|
|
59
69
|
test_files: []
|