diff --git a/setup/extensions.json b/setup/extensions.json index d9a9f682d4..95805b8fff 100644 --- a/setup/extensions.json +++ b/setup/extensions.json @@ -163,7 +163,7 @@ "name": "winutil", "only": "windows", "sources": "calibre/utils/windows/winutil.c", - "libraries": "shell32 wininet", + "libraries": "shell32 wininet advapi32", "cflags": "/X" }, { diff --git a/src/calibre/constants.py b/src/calibre/constants.py index afded4d597..bdc86eb0e2 100644 --- a/src/calibre/constants.py +++ b/src/calibre/constants.py @@ -281,6 +281,10 @@ def get_portable_base(): def get_unicode_windows_env_var(name): + winutil = plugins['winutil'][0] + getenv = getattr(winutil, 'getenv', None) + if getenv is not None: + return getenv(unicode(name)) import ctypes name = unicode(name) n = ctypes.windll.kernel32.GetEnvironmentVariableW(name, None, 0) @@ -293,19 +297,26 @@ def get_unicode_windows_env_var(name): def get_windows_username(): ''' - Return the user name of the currently loggen in user as a unicode string. + Return the user name of the currently logged in user as a unicode string. Note that usernames on windows are case insensitive, the case of the value returned depends on what the user typed into the login box at login time. ''' + winutil = plugins['winutil'][0] + username = getattr(winutil, 'username', None) + if username is not None: + return username() import ctypes + from ctypes import wintypes try: advapi32 = ctypes.windll.advapi32 GetUserName = getattr(advapi32, u'GetUserNameW') + GetUserName.argtypes = [wintypes.LPWSTR, ctypes.POINTER(wintypes.DWORD)] + GetUserName.restype = wintypes.BOOL except AttributeError: pass else: buf = ctypes.create_unicode_buffer(257) - n = ctypes.c_int(257) + n = wintypes.DWORD(257) if GetUserName(buf, ctypes.byref(n)): return buf.value @@ -313,6 +324,10 @@ def get_windows_username(): def get_windows_temp_path(): + winutil = plugins['winutil'][0] + temp_path = getattr(winutil, 'temp_path', None) + if temp_path is not None: + return temp_path() import ctypes n = ctypes.windll.kernel32.GetTempPathW(0, None) if n == 0: @@ -324,6 +339,10 @@ def get_windows_temp_path(): def get_windows_user_locale_name(): + winutil = plugins['winutil'][0] + locale_name = getattr(winutil, 'locale_name', None) + if locale_name is not None: + return locale_name() import ctypes k32 = ctypes.windll.kernel32 n = 255 @@ -334,32 +353,17 @@ def get_windows_user_locale_name(): return u'_'.join(buf.value.split(u'-')[:2]) -number_formats = None - - def get_windows_number_formats(): - # This can be changed to use localeconv() once we switch to Visual Studio - # 2015 as localeconv() in that version has unicode variants for all strings. - global number_formats - if number_formats is None: - import ctypes - from ctypes.wintypes import DWORD - k32 = ctypes.windll.kernel32 - n = 25 - buf = ctypes.create_unicode_buffer(u'\0'*n) - k32.GetNumberFormatEx.argtypes = [ctypes.c_wchar_p, DWORD, ctypes.c_wchar_p, ctypes.c_void_p, ctypes.c_wchar_p, ctypes.c_int] - k32.GetNumberFormatEx.restype = ctypes.c_int - if k32.GetNumberFormatEx(None, 0, u'123456.7', None, buf, n) == 0: - raise ctypes.WinError() - src = buf.value - thousands_sep, decimal_point = u',.' - idx = src.find(u'6') - if idx > -1 and src[idx+1] != u'7': - decimal_point = src[idx+1] - src = src[:idx] - for c in src: - if c not in u'123456': - thousands_sep = c - break - number_formats = (thousands_sep, decimal_point) - return number_formats + ans = getattr(get_windows_number_formats, 'ans', None) + if ans is None: + winutil = plugins['winutil'][0] + localeconv = getattr(winutil, 'localeconv', None) + if localeconv is not None: + d = localeconv() + thousands_sep, decimal_point = d['thousands_sep'], d['decimal_point'] + else: + from locale import localeconv + d = localeconv() + thousands_sep, decimal_point = d['thousands_sep'].decode('mbcs'), d['decimal_point'].decode('mbcs') + ans = get_windows_number_formats.ans = thousands_sep, decimal_point + return ans diff --git a/src/calibre/test_build.py b/src/calibre/test_build.py index b520f02bea..897e6b5168 100644 --- a/src/calibre/test_build.py +++ b/src/calibre/test_build.py @@ -121,8 +121,20 @@ class BuildTest(unittest.TestCase): def test_winutil(self): from calibre.constants import plugins winutil = plugins['winutil'][0] + + def au(x, name): + self.assertTrue(isinstance(x, unicode), name + '() did not return a unicode string') for x in winutil.argv(): - self.assertTrue(isinstance(x, unicode), 'argv() not returning unicode string') + au(x, 'argv') + for x in 'username temp_path locale_name'.split(): + au(getattr(winutil, x)(), x) + d = winutil.localeconv() + au(d['thousands_sep'], 'localeconv') + au(d['decimal_point'], 'localeconv') + for k, v in d.iteritems(): + au(v, k) + for k in os.environ.keys(): + au(winutil.getenv(unicode(k)), 'getenv-' + k) def test_sqlite(self): import sqlite3 diff --git a/src/calibre/utils/windows/winutil.c b/src/calibre/utils/windows/winutil.c index dee10862f0..bcad9f0c60 100644 --- a/src/calibre/utils/windows/winutil.c +++ b/src/calibre/utils/windows/winutil.c @@ -42,6 +42,8 @@ wherever possible in this module. #define UNICODE #include #include +#include +#include #include #include #include @@ -228,6 +230,62 @@ winutil_set_max_stdio(PyObject *self, PyObject *args) { Py_RETURN_NONE; } +static PyObject * +winutil_getenv(PyObject *self, PyObject *args) { + const wchar_t *q; + if (!PyArg_ParseTuple(args, "u", &q)) return NULL; + wchar_t *ans = _wgetenv(q); + if (ans == NULL) Py_RETURN_NONE; + return PyUnicode_FromWideChar(ans, wcslen(ans)); +} + +static PyObject * +winutil_username(PyObject *self) { + wchar_t buf[UNLEN + 1] = {0}; + DWORD sz = sizeof(buf)/sizeof(buf[0]); + if (!GetUserName(buf, &sz)) { + PyErr_SetFromWindowsErr(0); + return NULL; + } + return PyUnicode_FromWideChar(buf, wcslen(buf)); +} + +static PyObject * +winutil_temp_path(PyObject *self) { + wchar_t buf[MAX_PATH + 1] = {0}; + DWORD sz = sizeof(buf)/sizeof(buf[0]); + if (!GetTempPath(sz, buf)) { + PyErr_SetFromWindowsErr(0); + return NULL; + } + return PyUnicode_FromWideChar(buf, wcslen(buf)); +} + + +static PyObject * +winutil_locale_name(PyObject *self) { + wchar_t buf[LOCALE_NAME_MAX_LENGTH + 1] = {0}; + if (!GetUserDefaultLocaleName(buf, sizeof(buf)/sizeof(buf[0]))) { + PyErr_SetFromWindowsErr(0); + return NULL; + } + return PyUnicode_FromWideChar(buf, wcslen(buf)); +} + + +static PyObject * +winutil_localeconv(PyObject *self) { + struct lconv *d = localeconv(); +#define W(name) #name, d->_W_##name + return Py_BuildValue( + "{su su su su su su su su}", + W(decimal_point), W(thousands_sep), W(int_curr_symbol), W(currency_symbol), + W(mon_decimal_point), W(mon_thousands_sep), W(positive_sign), W(negative_sign) + ); +#undef W +} + + static PyObject * winutil_strftime(PyObject *self, PyObject *args) { @@ -381,6 +439,26 @@ be a unicode string. Returns unicode strings." "setmaxstdio(num)\n\nSet the maximum number of open file handles." }, + {"getenv", (PyCFunction)winutil_getenv, METH_VARARGS, + "getenv(name)\n\nGet the value of the specified env var as a unicode string." + }, + + {"username", (PyCFunction)winutil_username, METH_NOARGS, + "username()\n\nGet the current username as a unicode string." + }, + + {"temp_path", (PyCFunction)winutil_temp_path, METH_NOARGS, + "temp_path()\n\nGet the current temporary dir as a unicode string." + }, + + {"locale_name", (PyCFunction)winutil_locale_name, METH_NOARGS, + "locale_name()\n\nGet the current locale name as a unicode string." + }, + + {"localeconv", (PyCFunction)winutil_localeconv, METH_NOARGS, + "localeconv()\n\nGet the locale conventions as unicode strings." + }, + {NULL, NULL, 0, NULL} };