0

函数 CGEventKeyboardSetUnicodeString 采用 UniCharCount 和 const UniChar unicodeString[]。我无法弄清楚如何使用 pyobjc 从 python 调用它。理想情况下,我希望能够只传入一个 python unicode 字符串。有没有办法修改这个函数的元数据,所以我可以这样做?或者,有没有办法让我将 python unicode 字符串转换为 UNICHAR 数组和使用 pyobjc 的长度?

澄清:

我正在使用 2.5.1 版的 pyobjc

CGEventKeyboardSetUnicodeString 的元数据:

>>> pprint(CGEventKeyboardSetUnicodeString.__metadata__())
{'arguments': ({'already_cfretained': False,
            'already_retained': False,
            'null_accepted': True,
            'type': '^{__CGEvent=}'},
           {'already_cfretained': False,
            'already_retained': False,
            'type': 'L'},
           {'already_cfretained': False,
            'already_retained': False,
            'null_accepted': True,
            'type': '^T'}),
'retval': {'already_cfretained': False,
        'already_retained': False,
        'type': 'v'},
'variadic': False}

我尝试了以下方法:

>>> CGEventKeyboardSetUnicodeString(event, 1, 'a')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: depythonifying 'pointer', got 'str'

>>> CGEventKeyboardSetUnicodeString(event, 1, u'a')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: depythonifying 'pointer', got 'unicode'

>>> CGEventKeyboardSetUnicodeString(event, 1, [ord('a')])
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: depythonifying 'pointer', got 'list'

>>> Quartz.CGEventKeyboardSetUnicodeString(event, 1, struct.unpack('>{}H'.format(1), u'a'.encode('utf-16-le')))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: depythonifying 'pointer', got 'tuple'
4

2 回答 2

2

调用字符串的函数UniChar *从来都不是一件容易的事,但有一种解决方法。不幸的是,对于 2.5,除非您首先解决另一个问题,否则即使该解决方法也不起作用。首先,我将解释早期版本的解决方法,然后我会回到 2.5。

在早期版本的 PyObjC 中,函数UniChar *的参数类型声明为unsigned short *'^S'在元数据中);在以后的版本中,它是正确的UniChar *(即'^T')。但无论哪种方式,PyObjC 都不会将其视为字符串类型,而是将其视为 0-65535 的整数序列。所以,这就是你必须通过的。


如果您有一个unicode所有字符都在 BMP 中,* 那只是ord字符串中每个字符的。这很容易:

>>> s = u'abc'
>>> e = Quartz.CGEventCreateKeyboardEvent(None, 0, True)
>>> Quartz.CGEventKeyboardSetUnicodeString(e, len(s), map(ord, s))
>>> slen, swords = Quartz.CGEventKeyboardGetUnicodeString(e, 100, None, None)
>>> slen
3
>>> u''.join(map(unichr, swords))
u'abc'

请注意,该Get函数还向我们返回了一个短整数序列,而不是一个unicode.


但是,如果您有或可能有一些非 BMP 字符怎么办?AUniChar *是 UTF-16 码位序列,不是 Unicode 字符序列,所以需要编码为 UTF-16。您可以显式执行此操作(然后struct.unpack获取H值列表),但有一种更简单的方法:a or 也是CFString一个NSStringUTF -16 代码点序列。因此,如果您只使用原始代码,但使用 a而不是 a ,它可以工作:NSStringunicode

>>> s = u'abc\U00010000d'
>>> nss = Foundation.NSString.stringWithString_(s)
>>> len(s), len(nss)
(5, 6, 6)
>>> Quartz.CGEventKeyboardSetUnicodeString(e, len(nss), map(ord, nss))

如果您发现自己经常这样做,您可能想要编写一个简单的包装器:

def to_unichr(s):
    nss = Foundation.NSStringWithString_(s)
    return len(nss), map(ord, nss)

所以你可以这样做:

Quartz.CGEventKeyboardSetUnicodeString(e, *to_unichr(s))

以上所有内容均适用于 Python 2.x,但在 3.x 中应该同样有效,只要您删除多余的u前缀(适用于 3.2 及更早版本)并使用list(map(…))而不是map(…)在您需要可以打印的内容时使用。


同时,PyObjC 2.5 改变了桥接支持元数据的声明方式,并改变了大部分元数据的细节。据我所知,在 2.5.0 或 2.5.1 中调用这个函数实际上是不可能的。有关详细信息,请参阅问题 #64

问题似乎是_C_IN( 'n') 类型修饰符已被删除。您可以将序列传递给期望输入参数指针的函数,PyObjC 会自动适当地转换它,但由于明显的原因,它不适用于输出、输入输出或未指定的指针。

您可以通过自己修改元数据来解决此问题。在 2.5.1 中,您会在Quartz/CoreGraphics/_metadata.py第 29 行的 char 1351 处的 file 中找到它(或者,更简单地说,搜索CGEventKeyboardSetUnicodeString)。您必须向元组添加两个值。而不是这个:

'CGEventKeyboardSetUnicodeString': (sel32or64(b'v^{__CGEvent=}L^T', b'v^{__CGEvent=}Q^T'),)

你需要这个:

'CGEventKeyboardSetUnicodeString': (sel32or64(b'v^{__CGEvent=}L^T', b'v^{__CGEvent=}Q^T'), '', {'arguments': {2: {'type_modifier': 'n'}}})

通过该更改,上述解决方法适用于 2.5.0 或 2.5.1。


* 它也适用于只有 ASCII 字符的str/bytes对象,但不要那样做。

于 2013-08-02T23:52:34.943 回答
0

我发现我可以使用 ctypes 进行调用,并且仍然可以很好地使用 pyobjc。但是,如果有人知道如何完全用 pyobjc 做到这一点,那么我很乐意接受这个答案。

我的解决方案如下所示:

import ctypes
import ctypes.util
import objc
import Quartz
import sys

event = Quartz.CGEventCreateKeyboardEvent(None, 0, True)
cf = ctypes.cdll.LoadLibrary(ctypes.util.find_library('ApplicationServices'))
cf.CGEventKeyboardSetUnicodeString.restype = None
event_id = objc.pyobjc_id(event)
s = u'\U0001F600'
utf16_native = 'utf-16-le' if sys.byteorder == 'little' else 'utf-16-be'
buf = s.encode(utf16_native)
cf.CGEventKeyboardSetUnicodeString(event_id, len(buf) / 2, buf)
Quartz.CGEventPost(Quartz.kCGSessionEventTap, event) 
于 2013-08-04T18:08:49.120 回答