mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Start work on event handling with windows SAPI
This commit is contained in:
parent
21bd53d398
commit
3309f3728f
@ -3,15 +3,28 @@
|
|||||||
# License: GPL v3 Copyright: 2020, Kovid Goyal <kovid at kovidgoyal.net>
|
# License: GPL v3 Copyright: 2020, Kovid Goyal <kovid at kovidgoyal.net>
|
||||||
|
|
||||||
|
|
||||||
|
from threading import Thread
|
||||||
|
|
||||||
|
|
||||||
class Client:
|
class Client:
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
from calibre.utils.windows.winsapi import ISpVoice
|
from calibre.utils.windows.winsapi import ISpVoice
|
||||||
self.sp_voice = ISpVoice()
|
self.sp_voice = ISpVoice()
|
||||||
|
self.events_thread = Thread(name='SAPIEvents', target=self.wait_for_events, daemon=True)
|
||||||
|
self.events_thread.start()
|
||||||
|
|
||||||
def __del__(self):
|
def __del__(self):
|
||||||
|
self.sp_voice.shutdown_event_loop()
|
||||||
|
self.events_thread.join(5)
|
||||||
self.sp_voice = None
|
self.sp_voice = None
|
||||||
|
|
||||||
|
def wait_for_events(self):
|
||||||
|
self.sp_voice.run_event_loop(self.process_event)
|
||||||
|
|
||||||
|
def process_event(self, stream_number, event_type, event_data=None):
|
||||||
|
pass
|
||||||
|
|
||||||
def speak_simple_text(self, text):
|
def speak_simple_text(self, text):
|
||||||
from calibre_extensions.winsapi import SPF_ASYNC, SPF_PURGEBEFORESPEAK, SPF_IS_NOT_XML
|
from calibre_extensions.winsapi import SPF_ASYNC, SPF_PURGEBEFORESPEAK, SPF_IS_NOT_XML
|
||||||
self.sp_voice.speak(text, SPF_ASYNC | SPF_PURGEBEFORESPEAK | SPF_IS_NOT_XML)
|
self.sp_voice.speak(text, SPF_ASYNC | SPF_PURGEBEFORESPEAK | SPF_IS_NOT_XML)
|
||||||
|
@ -22,6 +22,7 @@ extern CComModule _Module;
|
|||||||
typedef struct {
|
typedef struct {
|
||||||
PyObject_HEAD
|
PyObject_HEAD
|
||||||
ISpVoice *voice;
|
ISpVoice *voice;
|
||||||
|
HANDLE shutdown_events_thread, events_available;
|
||||||
} Voice;
|
} Voice;
|
||||||
|
|
||||||
|
|
||||||
@ -44,6 +45,28 @@ Voice_new(PyTypeObject *type, PyObject *args, PyObject *kwds) {
|
|||||||
Py_CLEAR(self);
|
Py_CLEAR(self);
|
||||||
return error_from_hresult(hr, "Failed to create ISpVoice instance");
|
return error_from_hresult(hr, "Failed to create ISpVoice instance");
|
||||||
}
|
}
|
||||||
|
if (FAILED(hr = self->voice->SetNotifyWin32Event())) {
|
||||||
|
Py_CLEAR(self);
|
||||||
|
return error_from_hresult(hr, "Failed to set event based notify mechanism");
|
||||||
|
}
|
||||||
|
self->events_available = self->voice->GetNotifyEventHandle();
|
||||||
|
if (self->events_available == INVALID_HANDLE_VALUE) {
|
||||||
|
Py_CLEAR(self);
|
||||||
|
PyErr_SetString(PyExc_OSError, "Failed to get events handle for ISpVoice");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
self->shutdown_events_thread = CreateEvent(NULL, true, false, NULL);
|
||||||
|
if (self->shutdown_events_thread == INVALID_HANDLE_VALUE) {
|
||||||
|
Py_CLEAR(self);
|
||||||
|
PyErr_SetFromWindowsErr(0);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
ULONGLONG events = SPFEI(SPEI_START_INPUT_STREAM) | SPFEI(SPEI_END_INPUT_STREAM) | SPFEI(SPEI_TTS_BOOKMARK);
|
||||||
|
if (FAILED(hr = self->voice->SetInterest(events, events))) {
|
||||||
|
CloseHandle(self->shutdown_events_thread);
|
||||||
|
Py_CLEAR(self);
|
||||||
|
return error_from_hresult(hr, "Failed to register event interest");
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
return (PyObject*)self;
|
return (PyObject*)self;
|
||||||
@ -52,6 +75,10 @@ Voice_new(PyTypeObject *type, PyObject *args, PyObject *kwds) {
|
|||||||
static void
|
static void
|
||||||
Voice_dealloc(Voice *self) {
|
Voice_dealloc(Voice *self) {
|
||||||
if (self->voice) { self->voice->Release(); self->voice = NULL; }
|
if (self->voice) { self->voice->Release(); self->voice = NULL; }
|
||||||
|
if (self->shutdown_events_thread != INVALID_HANDLE_VALUE) {
|
||||||
|
CloseHandle(self->shutdown_events_thread);
|
||||||
|
self->shutdown_events_thread = INVALID_HANDLE_VALUE;
|
||||||
|
}
|
||||||
CoUninitialize();
|
CoUninitialize();
|
||||||
}
|
}
|
||||||
// }}}
|
// }}}
|
||||||
@ -246,7 +273,7 @@ Voice_speak(Voice *self, PyObject *args) {
|
|||||||
hr = self->voice->Speak(text_or_path.ptr(), flags, &stream_number);
|
hr = self->voice->Speak(text_or_path.ptr(), flags, &stream_number);
|
||||||
Py_END_ALLOW_THREADS;
|
Py_END_ALLOW_THREADS;
|
||||||
if (FAILED(hr)) return error_from_hresult(hr, "Failed to speak", PyTuple_GET_ITEM(args, 0));
|
if (FAILED(hr)) return error_from_hresult(hr, "Failed to speak", PyTuple_GET_ITEM(args, 0));
|
||||||
return PyLong_FromLong(stream_number);
|
return PyLong_FromUnsignedLong(stream_number);
|
||||||
}
|
}
|
||||||
|
|
||||||
static PyObject*
|
static PyObject*
|
||||||
@ -305,6 +332,66 @@ Voice_create_recording_wav(Voice *self, PyObject *args) {
|
|||||||
Py_RETURN_NONE;
|
Py_RETURN_NONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static PyObject*
|
||||||
|
Voice_shutdown_event_loop(Voice *self, PyObject *args) {
|
||||||
|
if (!SetEvent(self->shutdown_events_thread)) return PyErr_SetFromWindowsErr(0);
|
||||||
|
Py_RETURN_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void
|
||||||
|
dispatch_events(Voice *self, PyObject *callback) {
|
||||||
|
HRESULT hr;
|
||||||
|
const ULONG asz = 32;
|
||||||
|
ULONG num_events;
|
||||||
|
SPEVENT events[asz];
|
||||||
|
PyObject *ret;
|
||||||
|
long long val;
|
||||||
|
int etype;
|
||||||
|
while (true) {
|
||||||
|
Py_BEGIN_ALLOW_THREADS;
|
||||||
|
hr = self->voice->GetEvents(asz, events, &num_events);
|
||||||
|
Py_END_ALLOW_THREADS;
|
||||||
|
if (hr != S_OK && hr != S_FALSE) break;
|
||||||
|
if (num_events == 0) break;
|
||||||
|
for (ULONG i = 0; i < num_events; i++) {
|
||||||
|
etype = events[i].eEventId;
|
||||||
|
#define CALL(fmt, ...) { ret = PyObject_CallFunction(callback, fmt, __VA_ARGS__); if (ret) Py_DECREF(ret); else PyErr_Print(); } break;
|
||||||
|
switch(etype) {
|
||||||
|
case SPEI_TTS_BOOKMARK:
|
||||||
|
val = events[i].wParam;
|
||||||
|
CALL("kiL", events[i].ulStreamNum, etype, val);
|
||||||
|
case SPEI_START_INPUT_STREAM:
|
||||||
|
case SPEI_END_INPUT_STREAM:
|
||||||
|
CALL("ki", events[i].ulStreamNum, etype);
|
||||||
|
}
|
||||||
|
#undef CALL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static PyObject*
|
||||||
|
Voice_run_event_loop(Voice *self, PyObject *callback) {
|
||||||
|
if (!PyCallable_Check(callback)) { PyErr_SetString(PyExc_TypeError, "callback object is not callable"); return NULL; }
|
||||||
|
HANDLE handles[2] = {self->shutdown_events_thread, self->events_available};
|
||||||
|
bool keep_going = true;
|
||||||
|
DWORD ev;
|
||||||
|
while(keep_going) {
|
||||||
|
Py_BEGIN_ALLOW_THREADS;
|
||||||
|
ev = WaitForMultipleObjects(2, handles, true, INFINITE);
|
||||||
|
Py_END_ALLOW_THREADS;
|
||||||
|
switch (ev) {
|
||||||
|
case WAIT_OBJECT_0:
|
||||||
|
keep_going = false;
|
||||||
|
break;
|
||||||
|
case WAIT_OBJECT_0 + 1:
|
||||||
|
dispatch_events(self, callback);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Py_RETURN_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
// Boilerplate {{{
|
// Boilerplate {{{
|
||||||
#define M(name, args) { #name, (PyCFunction)Voice_##name, args, ""}
|
#define M(name, args) { #name, (PyCFunction)Voice_##name, args, ""}
|
||||||
static PyMethodDef Voice_methods[] = {
|
static PyMethodDef Voice_methods[] = {
|
||||||
@ -326,6 +413,8 @@ static PyMethodDef Voice_methods[] = {
|
|||||||
M(set_current_volume, METH_VARARGS),
|
M(set_current_volume, METH_VARARGS),
|
||||||
M(set_current_sound_output, METH_VARARGS),
|
M(set_current_sound_output, METH_VARARGS),
|
||||||
|
|
||||||
|
M(shutdown_event_loop, METH_NOARGS),
|
||||||
|
M(run_event_loop, METH_O),
|
||||||
{NULL, NULL, 0, NULL}
|
{NULL, NULL, 0, NULL}
|
||||||
};
|
};
|
||||||
#undef M
|
#undef M
|
||||||
@ -501,7 +590,7 @@ exec_module(PyObject *m) {
|
|||||||
AI(SPEI_RESERVED1);
|
AI(SPEI_RESERVED1);
|
||||||
AI(SPEI_RESERVED2);
|
AI(SPEI_RESERVED2);
|
||||||
#undef AI
|
#undef AI
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static PyModuleDef_Slot slots[] = { {Py_mod_exec, (void*)exec_module}, {0, NULL} };
|
static PyModuleDef_Slot slots[] = { {Py_mod_exec, (void*)exec_module}, {0, NULL} };
|
||||||
|
Loading…
x
Reference in New Issue
Block a user