Skip to content

Commit de7466a

Browse files
committed
add c subscribe interface, fix thread issue between python and c
1 parent 85babcc commit de7466a

6 files changed

+420
-1
lines changed

README.md

+40-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,40 @@
1+
# Pre Installation
2+
3+
## foohid
4+
https://github.com/unbit/foohid.git
5+
6+
1. 到 recovery mode disable kext
7+
a. 重開機, cmd + R 進 Recovery mode
8+
b. 打開 terminal: util -> terminal
9+
c. $ csrutil disable
10+
d. $ csrutil enable --without kext
11+
12+
2. 下載專案,編完 foohid.text 放到 /Library/Extensions
13+
14+
3. $ sudo kextload /Library/Extensions/foohid.kext
15+
16+
## other
17+
18+
$ pip3 install pyobjc
19+
20+
# Installation
21+
22+
```bash
23+
$ python3 setup.py install
24+
```
25+
26+
# Update
27+
28+
1. 在 foohid.c 實作 subscribe
29+
2. python 層設定 callback 到 C
30+
3. 不要在 C 層呼叫 CFRunLoopRun, 這樣會卡住 python thread
31+
4. 透過 pyobjc,在 python 呼叫底層 CFRunLoop
32+
33+
34+
# Test
35+
36+
$ python3 test_mouse.py
37+
138
# foohid-py
239
Python wrapper for the foohid OSX driver. Compatible with Python 2 and 3.
340

@@ -38,4 +75,6 @@ $ sudo kextunload /Library/Extensions/foohid.kext
3875
$ sudo rm -rf /System/Library/Extensions/foohid.kext
3976
```
4077

41-
Then reinstall the kernel extension: https://github.com/unbit/foohid/releases/latest (no restart required)
78+
Then reinstall the kernel extension: https://github.com/unbit/foohid/releases/latest (no restart required)
79+
80+

foohid.c

+140
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,31 @@
11
#include <Python.h>
22
#include <IOKit/IOKitLib.h>
3+
#include <CoreFoundation/CFRunLoop.h>
4+
5+
#include "foohid_types.h"
6+
7+
#include <Python.h>
38

49
#define FOOHID_SERVICE "it_unbit_foohid"
510

611
#define FOOHID_CREATE 0
712
#define FOOHID_DESTROY 1
813
#define FOOHID_SEND 2
914
#define FOOHID_LIST 3
15+
#define FOOHID_NOTIFY 4
16+
17+
CFRunLoopRef run_loop = NULL;
18+
IONotificationPortRef notification_port = NULL;
19+
20+
static PyObject* pyfunc_event_handler = NULL;
1021

1122
static int foohid_connect(io_connect_t *conn) {
23+
if (!PyEval_ThreadsInitialized()) {
24+
printf("will PyEval_InitThreads\n");
25+
PyEval_InitThreads();
26+
}
27+
printf("did PyEval_InitThreads\n");
28+
1229
io_iterator_t iterator;
1330
io_service_t service;
1431
kern_return_t ret = IOServiceGetMatchingServices(kIOMasterPortDefault, IOServiceMatching(FOOHID_SERVICE), &iterator);
@@ -113,6 +130,127 @@ static PyObject *foohid_send(PyObject *self, PyObject *args) {
113130
return Py_True;
114131
}
115132

133+
void callback(void *refcon, IOReturn result, io_user_reference_t* args, uint32_t numArgs) {
134+
foohid_report *report;
135+
136+
printf("callback called.\n");
137+
if (sizeof(io_user_reference_t) * numArgs != sizeof(foohid_report)) {
138+
printf("unexpected number of arguments.\n");
139+
return;
140+
}
141+
142+
report = (foohid_report *)args;
143+
printf("received report (%llu bytes).\n", report->size);
144+
145+
PyGILState_STATE state = PyGILState_Ensure();
146+
147+
if (pyfunc_event_handler == NULL){
148+
printf("pyfunc_event_handler == null!!!\n");
149+
fflush(stdout);
150+
}
151+
152+
PyObject* arglist = Py_BuildValue("(y#)", report->data, report->size);
153+
// 一定要 tuple... 不然 PyObject_CallFunctionObjArgs 會錯
154+
155+
if (arglist == NULL){
156+
printf("arglist NULL\n");
157+
fflush(stdout);
158+
}
159+
160+
PyObject *pyobjresult = PyObject_CallObject(pyfunc_event_handler, arglist);
161+
162+
if (pyobjresult == NULL){
163+
printf("pyobjresult NULL\n");
164+
fflush(stdout);
165+
}
166+
Py_DECREF(arglist);
167+
fflush(stdout);
168+
PyGILState_Release(state);
169+
}
170+
171+
172+
static PyObject *foohid_subscribe(PyObject *self, PyObject *args) {
173+
// set handler
174+
// PyObject *result = NULL;
175+
PyObject *temp;
176+
177+
char *name;
178+
Py_ssize_t name_len;
179+
180+
if (!PyArg_ParseTuple(args, "s#O:set_callback", &name, &name_len, &temp)) {
181+
return NULL;
182+
}
183+
// set handler
184+
if (!PyCallable_Check(temp)) {
185+
PyErr_SetString(PyExc_TypeError, "parameter must be a function");
186+
return NULL;
187+
}
188+
Py_XINCREF(temp); /* Add a reference to new func */
189+
Py_XDECREF(pyfunc_event_handler); /* Dispose of previous callback */
190+
pyfunc_event_handler = temp; /* Remember new callback */
191+
192+
if (name_len == 0) {
193+
return PyErr_Format(PyExc_ValueError, "invalid values");
194+
}
195+
196+
io_connect_t conn;
197+
if (foohid_connect(&conn)) {
198+
return PyErr_Format(PyExc_SystemError, "unable to open " FOOHID_SERVICE " service");
199+
}
200+
201+
// from u2f.c
202+
mach_port_t mnotification_port;
203+
CFRunLoopSourceRef run_loop_source;
204+
io_async_ref64_t async_ref;
205+
kern_return_t ret;
206+
207+
// Create port to listen for kernel notifications on.
208+
notification_port = IONotificationPortCreate(kIOMasterPortDefault);
209+
if (!notification_port) {
210+
printf("Error getting notification port.\n");
211+
return PyErr_Format(PyExc_ValueError, "invalid notification_port");
212+
}
213+
214+
// Get lower level mach port from notification port.
215+
mnotification_port = IONotificationPortGetMachPort(notification_port);
216+
if (!mnotification_port) {
217+
printf("Error getting mach notification port.\n");
218+
return PyErr_Format(PyExc_ValueError, "invalid mnotification_port");
219+
}
220+
221+
// Create a run loop source from our notification port so we can add the port to our run loop.
222+
run_loop_source = IONotificationPortGetRunLoopSource(notification_port);
223+
if (run_loop_source == NULL) {
224+
printf("Error getting run loop source.\n");
225+
return PyErr_Format(PyExc_ValueError, "invalid run_loop_source");
226+
}
227+
228+
// Add the notification port and timer to the run loop.
229+
CFRunLoopAddSource(CFRunLoopGetCurrent(), run_loop_source, kCFRunLoopDefaultMode);
230+
231+
// Params to pass to the kernel.
232+
async_ref[kIOAsyncCalloutFuncIndex] = (uint64_t)callback;
233+
async_ref[kIOAsyncCalloutRefconIndex] = 0;
234+
235+
uint32_t input_count = 2;
236+
uint64_t input[input_count];
237+
input[0] = (uint64_t) name;
238+
input[1] = (uint64_t) name_len;
239+
240+
ret = IOConnectCallAsyncScalarMethod(conn, FOOHID_NOTIFY, mnotification_port, async_ref, kIOAsyncCalloutCount, input, input_count, NULL, 0);
241+
242+
foohid_close(conn);
243+
244+
if (ret != KERN_SUCCESS) {
245+
return PyErr_Format(PyExc_SystemError, "unable to subscribe hid message");
246+
}
247+
248+
run_loop = CFRunLoopGetCurrent();
249+
250+
Py_INCREF(Py_True);
251+
return Py_True;
252+
}
253+
116254
static PyObject *foohid_destroy(PyObject *self, PyObject *args) {
117255
char *name;
118256
Py_ssize_t name_len;
@@ -209,6 +347,8 @@ static PyMethodDef foohidMethods[] = {
209347
{"destroy", foohid_destroy, METH_VARARGS, "destroy a foohid device"},
210348
{"send", foohid_send, METH_VARARGS, "send a hid message to a foohid device"},
211349
{"list", foohid_list, METH_VARARGS, "list the currently available foohid devices"},
350+
{"subscribe", foohid_subscribe, METH_VARARGS, "subscribe foohid devices"},
351+
212352
{NULL, NULL, 0, NULL}
213353
};
214354

foohid_types.h

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
//
2+
// foohid_types.h
3+
// foohid
4+
//
5+
// Created by Benjamin P Toews on 2/3/17.
6+
// Copyright © 2017 unbit. All rights reserved.
7+
//
8+
9+
#ifndef foohid_types_h
10+
#define foohid_types_h
11+
12+
const uint8_t foohid_max_report = 64;
13+
14+
typedef struct foohid_report {
15+
uint64_t size;
16+
uint8_t data[foohid_max_report];
17+
} foohid_report;
18+
19+
#endif

mouse.c

+128
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
/**
2+
* Create a virtual mouse.
3+
* Compile me with: gcc mouse.c -o virtual_mouse -framework IOKit
4+
*/
5+
6+
#include <IOKit/IOKitLib.h>
7+
#include <stdio.h>
8+
#include <stdlib.h>
9+
#include <string.h>
10+
11+
unsigned char report_descriptor[] = {
12+
0x05, 0x01, // USAGE_PAGE (Generic Desktop)
13+
0x09, 0x02, // USAGE (Mouse)
14+
0xa1, 0x01, // COLLECTION (Application)
15+
0x09, 0x01, // USAGE (Pointer)
16+
0xa1, 0x00, // COLLECTION (Physical)
17+
0x05, 0x09, // USAGE_PAGE (Button)
18+
0x19, 0x01, // USAGE_MINIMUM (Button 1)
19+
0x29, 0x03, // USAGE_MAXIMUM (Button 3)
20+
0x15, 0x00, // LOGICAL_MINIMUM (0)
21+
0x25, 0x01, // LOGICAL_MAXIMUM (1)
22+
0x95, 0x03, // REPORT_COUNT (3)
23+
0x75, 0x01, // REPORT_SIZE (1)
24+
0x81, 0x02, // INPUT (Data,Var,Abs)
25+
0x95, 0x01, // REPORT_COUNT (1)
26+
0x75, 0x05, // REPORT_SIZE (5)
27+
0x81, 0x03, // INPUT (Cnst,Var,Abs)
28+
0x05, 0x01, // USAGE_PAGE (Generic Desktop)
29+
0x09, 0x30, // USAGE (X)
30+
0x09, 0x31, // USAGE (Y)
31+
0x15, 0x81, // LOGICAL_MINIMUM (-127)
32+
0x25, 0x7f, // LOGICAL_MAXIMUM (127)
33+
0x75, 0x08, // REPORT_SIZE (8)
34+
0x95, 0x02, // REPORT_COUNT (2)
35+
0x81, 0x06, // INPUT (Data,Var,Rel)
36+
0xc0, // END_COLLECTION
37+
0xc0 // END_COLLECTION
38+
};
39+
40+
struct mouse_report_t {
41+
uint8_t buttons;
42+
int8_t x;
43+
int8_t y;
44+
};
45+
46+
#define SERVICE_NAME "it_unbit_foohid"
47+
48+
#define FOOHID_CREATE 0 // create selector
49+
#define FOOHID_SEND 2 // send selector
50+
51+
#define DEVICE_NAME "Foohid Virtual Mouse"
52+
#define DEVICE_SN "SN 123456"
53+
54+
int main() {
55+
io_iterator_t iterator;
56+
io_service_t service;
57+
io_connect_t connect;
58+
59+
// Get a reference to the IOService
60+
kern_return_t ret = IOServiceGetMatchingServices(kIOMasterPortDefault, IOServiceMatching(SERVICE_NAME), &iterator);
61+
62+
if (ret != KERN_SUCCESS) {
63+
printf("Unable to access IOService.\n");
64+
exit(1);
65+
}
66+
67+
// Iterate till success
68+
int found = 0;
69+
while ((service = IOIteratorNext(iterator)) != IO_OBJECT_NULL) {
70+
printf("IOIteratorNext");
71+
72+
ret = IOServiceOpen(service, mach_task_self(), 0, &connect);
73+
printf("%d", ret);
74+
75+
if (ret == KERN_SUCCESS) {
76+
found = 1;
77+
break;
78+
}
79+
80+
IOObjectRelease(service);
81+
}
82+
IOObjectRelease(iterator);
83+
84+
if (!found) {
85+
printf("Unable to open IOService.\n");
86+
exit(1);
87+
}
88+
89+
// Fill up the input arguments.
90+
uint32_t input_count = 8;
91+
uint64_t input[input_count];
92+
input[0] = (uint64_t) strdup(DEVICE_NAME); // device name
93+
input[1] = strlen((char *)input[0]); // name length
94+
input[2] = (uint64_t) report_descriptor; // report descriptor
95+
input[3] = sizeof(report_descriptor); // report descriptor len
96+
input[4] = (uint64_t) strdup(DEVICE_SN); // serial number
97+
input[5] = strlen((char *)input[4]); // serial number len
98+
input[6] = (uint64_t) 2; // vendor ID
99+
input[7] = (uint64_t) 3; // device ID
100+
101+
ret = IOConnectCallScalarMethod(connect, FOOHID_CREATE, input, input_count, NULL, 0);
102+
if (ret != KERN_SUCCESS) {
103+
printf("Unable to create HID device.\n");
104+
exit(1);
105+
}
106+
107+
// Arguments to be passed through the HID message.
108+
struct mouse_report_t mouse;
109+
uint32_t send_count = 4;
110+
uint64_t send[send_count];
111+
send[0] = (uint64_t)input[0]; // device name
112+
send[1] = strlen((char *)input[0]); // name length
113+
send[2] = (uint64_t) &mouse; // mouse struct
114+
send[3] = sizeof(struct mouse_report_t); // mouse struct len
115+
116+
for(;;) {
117+
mouse.buttons = 0;
118+
mouse.x = rand();
119+
mouse.y = rand();
120+
121+
ret = IOConnectCallScalarMethod(connect, FOOHID_SEND, send, send_count, NULL, 0);
122+
if (ret != KERN_SUCCESS) {
123+
printf("Unable to send message to HID device.\n");
124+
}
125+
126+
sleep(1); // sleep for a second
127+
}
128+
}

stoppableThread.py

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import threading
2+
3+
4+
class StoppableThread(threading.Thread):
5+
worker = None
6+
def __init__(self, w):
7+
threading.Thread.__init__(self)
8+
self.worker = w
9+
10+
def run(self):
11+
self.alive = True
12+
def c():
13+
return self.shouldStop()
14+
self.worker(c)
15+
16+
def shouldStop(self):
17+
return not self.alive
18+
19+
def stop(self):
20+
self.alive = False
21+
self.join()
22+

0 commit comments

Comments
 (0)