Start wrapping of windows SAPI interface

Can now get list of all voices installed in the system
This commit is contained in:
Kovid Goyal 2020-10-21 21:58:06 +05:30
parent ebb6f89b5b
commit 4bb557ec41
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
4 changed files with 227 additions and 1 deletions

View File

@ -144,6 +144,14 @@
"libraries": "shell32 wininet advapi32",
"cflags": "/X"
},
{
"name": "winsapi",
"only": "windows",
"headers": "calibre/utils/windows/common.h",
"sources": "calibre/utils/windows/winsapi.cpp",
"libraries": "SAPI Ole32",
"cflags": "/X"
},
{
"name": "wpd",
"only": "windows",

View File

@ -249,7 +249,7 @@ class ExtensionsImporter:
'certgen',
)
if iswindows:
extra = ('winutil', 'wpd', 'winfonts')
extra = ('winutil', 'wpd', 'winfonts', 'winsapi')
elif ismacos:
extra = ('usbobserver', 'cocoa', 'libusb', 'libmtp')
elif isfreebsd or ishaiku or islinux:

View File

@ -7,8 +7,21 @@
#pragma once
#define PY_SSIZE_T_CLEAN
#define UNICODE
#define _UNICODE
#include <Windows.h>
#include <Python.h>
#include <comdef.h>
static inline PyObject*
set_error_from_hresult(const char *file, const int line, const HRESULT hr, const char *prefix="") {
_com_error err(hr);
LPCWSTR msg = err.ErrorMessage();
PyObject *pmsg = PyUnicode_FromWideChar(msg, -1);
PyObject *ans = PyErr_Format(PyExc_OSError, "%s:%d:%s:%V", file, line, prefix, pmsg, "Out of memory");
Py_CLEAR(pmsg);
return ans;
}
#define error_from_hresult(hr, ...) set_error_from_hresult(__FILE__, __LINE__, hr, __VA_ARGS__)
class wchar_raii {
private:
@ -30,6 +43,48 @@ class wchar_raii {
void set_ptr(wchar_t *val) { handle = val; }
};
class com_wchar_raii {
private:
wchar_t *handle;
com_wchar_raii( const com_wchar_raii & ) ;
com_wchar_raii & operator=( const com_wchar_raii & ) ;
public:
com_wchar_raii() : handle(NULL) {}
~com_wchar_raii() {
if (handle) {
CoTaskMemFree(handle);
handle = NULL;
}
}
wchar_t *ptr() { return handle; }
wchar_t **address() { return &handle; }
explicit operator bool() const { return handle != NULL; }
};
class pyobject_raii {
private:
PyObject *handle;
pyobject_raii( const pyobject_raii & ) ;
pyobject_raii & operator=( const pyobject_raii & ) ;
public:
pyobject_raii() : handle(NULL) {}
pyobject_raii(PyObject* h) : handle(h) {}
~pyobject_raii() { Py_CLEAR(handle); }
PyObject *ptr() { return handle; }
void set_ptr(PyObject *val) { handle = val; }
PyObject **address() { return &handle; }
explicit operator bool() const { return handle != NULL; }
PyObject *detach() { PyObject *ans = handle; handle = NULL; return ans; }
};
class handle_raii {
private:
HANDLE handle;

View File

@ -0,0 +1,163 @@
/*
* winsapi.cpp
* Copyright (C) 2020 Kovid Goyal <kovid at kovidgoyal.net>
*
* Distributed under terms of the GPL3 license.
*/
#define _ATL_APARTMENT_THREADED
#include "common.h"
#include <atlbase.h>
extern CComModule _Module;
#include <atlcom.h>
#include <sapi.h>
#pragma warning( push )
#pragma warning( disable : 4996 ) // sphelper.h uses deprecated GetVersionEx
#include <sphelper.h>
#pragma warning( pop )
typedef struct {
PyObject_HEAD
ISpVoice *voice;
} Voice;
static PyTypeObject VoiceType = {
PyVarObject_HEAD_INIT(NULL, 0)
};
static PyObject *
Voice_new(PyTypeObject *type, PyObject *args, PyObject *kwds) {
HRESULT hr = CoInitialize(NULL);
if (hr != S_OK && hr != S_FALSE) {
if (hr == RPC_E_CHANGED_MODE) {
return error_from_hresult(hr, "COM initialization failed as it was already initialized in multi-threaded mode");
}
return PyErr_NoMemory();
}
Voice *self = (Voice *) type->tp_alloc(type, 0);
if (self) {
if (FAILED(hr = CoCreateInstance(CLSID_SpVoice, NULL, CLSCTX_ALL, IID_ISpVoice, (void **)&self->voice))) {
Py_CLEAR(self);
return error_from_hresult(hr, "Failed to create ISpVoice instance");
}
}
return (PyObject*)self;
}
static void
Voice_dealloc(Voice *self) {
if (self->voice) { self->voice->Release(); self->voice = NULL; }
CoUninitialize();
}
static PyObject*
Voice_get_all_voices(Voice *self, PyObject *args) {
HRESULT hr = S_OK;
CComPtr<IEnumSpObjectTokens> iterator = NULL;
if (FAILED(hr = SpEnumTokens(SPCAT_VOICES, NULL, NULL, &iterator))) {
return error_from_hresult(hr, "Failed to create voice category iterator");
return NULL;
}
pyobject_raii ans(PyList_New(0));
if (!ans) return NULL;
while (true) {
CComPtr<ISpObjectToken> token = NULL;
if (FAILED(hr = iterator->Next(1, &token, NULL)) || hr == S_FALSE || !token) break;
pyobject_raii dict(PyDict_New());
if (!dict) return NULL;
com_wchar_raii id, description;
if (FAILED(hr = token->GetId(id.address()))) continue;
pyobject_raii idpy(PyUnicode_FromWideChar(id.ptr(), -1));
if (!idpy) return NULL;
if (PyDict_SetItemString(dict.ptr(), "id", idpy.ptr()) != 0) return NULL;
if (FAILED(hr = SpGetDescription(token, description.address(), NULL))) continue;
pyobject_raii descriptionpy(PyUnicode_FromWideChar(description.ptr(), -1));
if (!descriptionpy) return NULL;
if (PyDict_SetItemString(dict.ptr(), "description", descriptionpy.ptr()) != 0) return NULL;
CComPtr<ISpDataKey> attributes = NULL;
if (FAILED(hr = token->OpenKey(L"Attributes", &attributes))) continue;
#define ATTR(name) {\
com_wchar_raii val; \
if (SUCCEEDED(attributes->GetStringValue(TEXT(#name), val.address()))) { \
pyobject_raii pyval(PyUnicode_FromWideChar(val.ptr(), -1)); if (!pyval) return NULL; \
if (PyDict_SetItemString(dict.ptr(), #name, pyval.ptr()) != 0) return NULL; \
}\
}
ATTR(gender); ATTR(name); ATTR(vendor); ATTR(age);
#undef ATTR
com_wchar_raii val;
if (SUCCEEDED(attributes->GetStringValue(L"language", val.address()))) {
int lcid = wcstol(val.ptr(), NULL, 16);
wchar_t buf[LOCALE_NAME_MAX_LENGTH];
if (LCIDToLocaleName(lcid, buf, LOCALE_NAME_MAX_LENGTH, 0) > 0) {
pyobject_raii pyval(PyUnicode_FromWideChar(buf, -1)); if (!pyval) return NULL;
if (PyDict_SetItemString(dict.ptr(), "language", pyval.ptr()) != 0) return NULL;
}
}
if (PyList_Append(ans.ptr(), dict.ptr()) != 0) return NULL;
}
return ans.detach();
}
#define M(name, args) { #name, (PyCFunction)Voice_##name, args, ""}
static PyMethodDef Voice_methods[] = {
M(get_all_voices, METH_NOARGS),
{NULL, NULL, 0, NULL}
};
#undef M
#define M(name, args) { #name, name, args, ""}
static PyMethodDef winsapi_methods[] = {
{NULL, NULL, 0, NULL}
};
#undef M
static struct PyModuleDef winsapi_module = {
/* m_base */ PyModuleDef_HEAD_INIT,
/* m_name */ "winsapi",
/* m_doc */ "SAPI wrapper",
/* m_size */ -1,
/* m_methods */ winsapi_methods,
/* m_slots */ 0,
/* m_traverse */ 0,
/* m_clear */ 0,
/* m_free */ 0,
};
extern "C" {
CALIBRE_MODINIT_FUNC PyInit_winsapi(void) {
VoiceType.tp_name = "winsapi.Voice";
VoiceType.tp_doc = "Wrapper for ISpVoice";
VoiceType.tp_basicsize = sizeof(Voice);
VoiceType.tp_itemsize = 0;
VoiceType.tp_flags = Py_TPFLAGS_DEFAULT;
VoiceType.tp_new = Voice_new;
VoiceType.tp_methods = Voice_methods;
VoiceType.tp_dealloc = (destructor)Voice_dealloc;
if (PyType_Ready(&VoiceType) < 0) return NULL;
PyObject *m = PyModule_Create(&winsapi_module);
if (m == NULL) return NULL;
Py_INCREF(&VoiceType);
if (PyModule_AddObject(m, "Voice", (PyObject *) &VoiceType) < 0) {
Py_DECREF(&VoiceType);
Py_DECREF(m);
return NULL;
}
return m;
}
}