rbnput-darwin-minimal 0.2.0 → 1.4.1
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 +3 -0
- data/examples/keyboard_listener.rb +38 -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
- data/rbnput-darwin-minimal.gemspec +35 -0
- metadata +8 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c2f2f0081d0d4b1874804b5bd5967531aa3b49672e5669c74d8d0b78ebc5deac
|
|
4
|
+
data.tar.gz: 73fbfa1bfb32019e32913ed3859ac9805742ba3d7c65c7fe1234d4b8b347933b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: cce2cccc53b4aaadd515a6f6f594ab9d2efbbc81c70f020c6ec34577504e7e0738f3471e24cee5f87a6fe9bb954f8fb2d973ae5a8777e6c7ba547576a7ba9578
|
|
7
|
+
data.tar.gz: 97d32793cc1cf609be17c97a3037c0c8c325cef3bda65d5e8dada8467acf3802a7525493420f05b0cee99cd7771f842e7ba67f76e2ae9abafba6f07f3b7485c8
|
data/README.md
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
# 🎹 rbnput-darwin-minimal 🍎
|
|
2
2
|
|
|
3
|
+
[](https://badge.fury.io/rb/rbnput-darwin-minimal)
|
|
4
|
+
[](https://github.com/krist7599555/rbnput-darwin-minimal)
|
|
5
|
+
|
|
3
6
|
ไลบรารี Ruby ขนาดเล็กสำหรับตรวจจับการกดแป้นพิมพ์บนระบบ macOS โดยใช้ FFI เชื่อมต่อกับ system library ของ Darwin โดยตรง
|
|
4
7
|
|
|
5
8
|
## ✨ คุณสมบัติ
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
MODE = ENV.fetch("APP_MODE", "local").to_sym
|
|
5
|
+
|
|
6
|
+
# กำหนดการโหลด Library หรือไฟล์
|
|
7
|
+
# Load Library
|
|
8
|
+
case MODE
|
|
9
|
+
when :local; require_relative '../lib/rbnput-darwin-minimal' # โหลดจากไฟล์ในเครื่อง (local)
|
|
10
|
+
when :prod; require 'rbnput-darwin-minimal' # โหลดจาก gem ที่ติดตั้งแล้ว (production)
|
|
11
|
+
else; raise "Unknown MODE #{MODE.inspect}" # แสดงข้อผิดพลาดหากไม่รู้จักโหมด
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# ตัวอย่าง: การดักจับเหตุการณ์คีย์บอร์ด
|
|
15
|
+
# Example: Monitoring keyboard events
|
|
16
|
+
puts "=== Keyboard Listener Example (#{MODE}) ==="
|
|
17
|
+
puts "Press any keys. Press Ctrl+C to exit."
|
|
18
|
+
puts "กดปุ่มใดๆ บนคีย์บอร์ด. กด Ctrl+C เพื่อออกจากโปรแกรม."
|
|
19
|
+
puts
|
|
20
|
+
|
|
21
|
+
# สร้างและเริ่มตัวดักจับ (Create and start listener)
|
|
22
|
+
listener = Rbnput::Listener.new
|
|
23
|
+
listener.on_press do |key|
|
|
24
|
+
# ทำงานเมื่อมีการกดปุ่ม
|
|
25
|
+
puts "\b ⬇️ up : #{key} (กด)"
|
|
26
|
+
end
|
|
27
|
+
listener.on_release do |key|
|
|
28
|
+
# ทำงานเมื่อมีการปล่อยปุ่ม
|
|
29
|
+
puts "\b ⬆️ down : #{key} (ปล่อย)"
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
begin
|
|
33
|
+
listener.start # เริ่ม thread การทำงาน (start thread)
|
|
34
|
+
listener.join # รอให้ thread ทำงานจนจบ (wait thread exit)
|
|
35
|
+
rescue Interrupt
|
|
36
|
+
puts "\nStopping listener..."
|
|
37
|
+
puts "Listener stopped. (หยุดการทำงาน)"
|
|
38
|
+
end
|
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
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
require_relative "lib/rbnput/version"
|
|
2
|
+
|
|
3
|
+
Gem::Specification.new do |spec|
|
|
4
|
+
spec.name = "rbnput-darwin-minimal"
|
|
5
|
+
spec.version = Rbnput::VERSION
|
|
6
|
+
spec.authors = ["Krist Ponpairin"]
|
|
7
|
+
spec.email = ["krist7599555@gmail.com"]
|
|
8
|
+
|
|
9
|
+
spec.summary = "Minimal Ruby keyboard listener for macOS using FFI"
|
|
10
|
+
spec.description = "Lightweight Ruby library for low-level keyboard monitoring on macOS, implemented through FFI bindings to Darwin system libraries."
|
|
11
|
+
|
|
12
|
+
# 🏡 Project Links
|
|
13
|
+
spec.homepage = "https://github.com/krist7599555/rbnput-darwin-minimal"
|
|
14
|
+
spec.metadata = {
|
|
15
|
+
"homepage_uri" => "https://github.com/krist7599555/rbnput-darwin-minimal",
|
|
16
|
+
"source_code_uri" => "https://github.com/krist7599555/rbnput-darwin-minimal",
|
|
17
|
+
"bug_tracker_uri" => "https://github.com/krist7599555/rbnput-darwin-minimal/issues",
|
|
18
|
+
"documentation_uri" => "https://github.com/krist7599555/rbnput-darwin-minimal",
|
|
19
|
+
"changelog_uri" => "https://github.com/krist7599555/rbnput-darwin-minimal/releases"
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
# 📦 Files
|
|
24
|
+
spec.files = Dir["{lib,examples}/**/*.rb"] + ['rbnput-darwin-minimal.gemspec', "README.md", "LICENSE"]
|
|
25
|
+
spec.require_paths = ["lib"]
|
|
26
|
+
|
|
27
|
+
# 💎 Dependencies
|
|
28
|
+
spec.add_dependency "ffi", "~> 1.15"
|
|
29
|
+
|
|
30
|
+
# Ruby version
|
|
31
|
+
spec.required_ruby_version = ">= 3.0"
|
|
32
|
+
|
|
33
|
+
spec.rdoc_options = ['--main', 'README.md']
|
|
34
|
+
spec.extra_rdoc_files = ['LICENSE']
|
|
35
|
+
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.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Krist Ponpairin
|
|
@@ -29,10 +29,12 @@ email:
|
|
|
29
29
|
- krist7599555@gmail.com
|
|
30
30
|
executables: []
|
|
31
31
|
extensions: []
|
|
32
|
-
extra_rdoc_files:
|
|
32
|
+
extra_rdoc_files:
|
|
33
|
+
- LICENSE
|
|
33
34
|
files:
|
|
34
35
|
- LICENSE
|
|
35
36
|
- README.md
|
|
37
|
+
- examples/keyboard_listener.rb
|
|
36
38
|
- lib/rbnput-darwin-minimal.rb
|
|
37
39
|
- lib/rbnput/darwin_ffi.rb
|
|
38
40
|
- lib/rbnput/darwin_listener.rb
|
|
@@ -40,6 +42,7 @@ files:
|
|
|
40
42
|
- lib/rbnput/key_code_const.rb
|
|
41
43
|
- lib/rbnput/simple_mutex_thread.rb
|
|
42
44
|
- lib/rbnput/version.rb
|
|
45
|
+
- rbnput-darwin-minimal.gemspec
|
|
43
46
|
homepage: https://github.com/krist7599555/rbnput-darwin-minimal
|
|
44
47
|
licenses: []
|
|
45
48
|
metadata:
|
|
@@ -48,7 +51,9 @@ metadata:
|
|
|
48
51
|
bug_tracker_uri: https://github.com/krist7599555/rbnput-darwin-minimal/issues
|
|
49
52
|
documentation_uri: https://github.com/krist7599555/rbnput-darwin-minimal
|
|
50
53
|
changelog_uri: https://github.com/krist7599555/rbnput-darwin-minimal/releases
|
|
51
|
-
rdoc_options:
|
|
54
|
+
rdoc_options:
|
|
55
|
+
- "--main"
|
|
56
|
+
- README.md
|
|
52
57
|
require_paths:
|
|
53
58
|
- lib
|
|
54
59
|
required_ruby_version: !ruby/object:Gem::Requirement
|