group creation, invites and message sending (untested)

This commit is contained in:
ingvar1995 2016-07-11 22:19:35 +03:00
parent c8bdb32e86
commit f13274882a
8 changed files with 165 additions and 105 deletions

View File

@ -91,10 +91,11 @@ class BaseContact:
if not os.path.isfile(avatar_path): # load default image if not os.path.isfile(avatar_path): # load default image
avatar_path = default_path avatar_path = default_path
os.chdir(curr_directory() + '/images/') os.chdir(curr_directory() + '/images/')
pixmap = QtGui.QPixmap(QtCore.QSize(64, 64)) width = self._widget.avatar_label.width()
pixmap = QtGui.QPixmap(QtCore.QSize(width, width))
pixmap.load(avatar_path) pixmap.load(avatar_path)
self._widget.avatar_label.setScaledContents(False) self._widget.avatar_label.setScaledContents(False)
self._widget.avatar_label.setPixmap(pixmap.scaled(64, 64, QtCore.Qt.KeepAspectRatio)) self._widget.avatar_label.setPixmap(pixmap.scaled(width, width, QtCore.Qt.KeepAspectRatio))
self._widget.avatar_label.repaint() self._widget.avatar_label.repaint()
def reset_avatar(self): def reset_avatar(self):

View File

@ -287,6 +287,20 @@ def callback_audio(toxav, friend_number, samples, audio_samples_per_channel, aud
rate) rate)
# -----------------------------------------------------------------------------------------------------------------
# Callbacks - group chats
# -----------------------------------------------------------------------------------------------------------------
def group_message(tox, group_number, peer_id, message, length, user_data):
pass
def group_invite(tox, friend_number, invite_data, length, user_data):
invoke_in_main_thread(Profile.get_instance().process_group_invite,
friend_number,
invite_data[:length])
# ----------------------------------------------------------------------------------------------------------------- # -----------------------------------------------------------------------------------------------------------------
# Callbacks - initialization # Callbacks - initialization
# ----------------------------------------------------------------------------------------------------------------- # -----------------------------------------------------------------------------------------------------------------

View File

@ -5,9 +5,6 @@ except ImportError:
import basecontact import basecontact
from messages import * from messages import *
from history import * from history import *
from settings import ProfileHelper
from toxcore_enums_and_consts import *
from util import curr_directory
class Contact(basecontact.BaseContact): class Contact(basecontact.BaseContact):
@ -17,7 +14,7 @@ class Contact(basecontact.BaseContact):
widget - widget for update widget - widget for update
""" """
def __init__(self, message_getter, name, status_message, widget, tox_id): def __init__(self, number, message_getter, name, status_message, widget, tox_id):
""" """
:param name: name, example: 'Toxygen user' :param name: name, example: 'Toxygen user'
:param status_message: status message, example: 'Toxing on Toxygen' :param status_message: status message, example: 'Toxing on Toxygen'
@ -28,6 +25,7 @@ class Contact(basecontact.BaseContact):
self._message_getter = message_getter self._message_getter = message_getter
self._new_messages = False self._new_messages = False
self._visible = True self._visible = True
self._number = number
self._corr = [] self._corr = []
self._unsaved_messages = 0 self._unsaved_messages = 0
self._history_loaded = self._new_actions = False self._history_loaded = self._new_actions = False
@ -150,40 +148,5 @@ class Contact(basecontact.BaseContact):
self._widget.messages.update(self._new_messages) self._widget.messages.update(self._new_messages)
self._widget.connection_status.update(self.status, False) self._widget.connection_status.update(self.status, False)
# -----------------------------------------------------------------------------------------------------------------
# Avatars
# -----------------------------------------------------------------------------------------------------------------
def load_avatar(self):
"""
Tries to load avatar of contact or uses default avatar
"""
avatar_path = '{}.png'.format(self._tox_id[:TOX_PUBLIC_KEY_SIZE * 2])
os.chdir(ProfileHelper.get_path() + 'avatars/')
if not os.path.isfile(avatar_path): # load default image
avatar_path = 'avatar.png'
os.chdir(curr_directory() + '/images/')
width = self._widget.avatar_label.width()
pixmap = QtGui.QPixmap(QtCore.QSize(width, width))
pixmap.load(avatar_path)
self._widget.avatar_label.setScaledContents(False)
self._widget.avatar_label.setPixmap(pixmap.scaled(width, width, QtCore.Qt.KeepAspectRatio))
self._widget.avatar_label.repaint()
def reset_avatar(self):
avatar_path = (ProfileHelper.get_path() + 'avatars/{}.png').format(self._tox_id[:TOX_PUBLIC_KEY_SIZE * 2])
if os.path.isfile(avatar_path):
os.remove(avatar_path)
self.load_avatar()
def set_avatar(self, avatar):
avatar_path = (ProfileHelper.get_path() + 'avatars/{}.png').format(self._tox_id[:TOX_PUBLIC_KEY_SIZE * 2])
with open(avatar_path, 'wb') as f:
f.write(avatar)
self.load_avatar()
def get_pixmap(self):
return self._widget.avatar_label.pixmap()
messages = property(get_messages) messages = property(get_messages)

View File

@ -10,13 +10,11 @@ class Friend(contact.Contact):
Friend in list of friends. Can be hidden, properties 'has unread messages' and 'has alias' added Friend in list of friends. Can be hidden, properties 'has unread messages' and 'has alias' added
""" """
def __init__(self, message_getter, number, *args): def __init__(self, *args):
""" """
:param message_getter: gets messages from db
:param number: number of friend. :param number: number of friend.
""" """
super(Friend, self).__init__(message_getter, *args) super(Friend, self).__init__(*args)
self._number = number
self._alias = False self._alias = False
self._receipts = 0 self._receipts = 0

View File

@ -3,10 +3,11 @@ import contact
class GroupChat(contact.Contact): class GroupChat(contact.Contact):
def __init__(self, group_id, tox, *args): def __init__(self, tox, *args):
super().__init__(*args) super().__init__(*args)
self._id = group_id
self._tox = tox self._tox = tox
def load_avatar(self, default_path='group.png'): def load_avatar(self, default_path='group.png'):
super().load_avatar(default_path) super().load_avatar(default_path)
# TODO: get peers list and add other methods

View File

@ -82,7 +82,7 @@ class MainWindow(QtGui.QMainWindow):
self.actionAbout_program.triggered.connect(self.about_program) self.actionAbout_program.triggered.connect(self.about_program)
self.actionNetwork.triggered.connect(self.network_settings) self.actionNetwork.triggered.connect(self.network_settings)
self.actionAdd_friend.triggered.connect(self.add_contact) self.actionAdd_friend.triggered.connect(self.add_contact)
self.actionSettings.triggered.connect(self.profilesettings) self.actionSettings.triggered.connect(self.profile_settings)
self.actionPrivacy_settings.triggered.connect(self.privacy_settings) self.actionPrivacy_settings.triggered.connect(self.privacy_settings)
self.actionInterface_settings.triggered.connect(self.interface_settings) self.actionInterface_settings.triggered.connect(self.interface_settings)
self.actionNotifications.triggered.connect(self.notification_settings) self.actionNotifications.triggered.connect(self.notification_settings)
@ -205,9 +205,9 @@ class MainWindow(QtGui.QMainWindow):
Form.status_message.setObjectName("status_message") Form.status_message.setObjectName("status_message")
self.connection_status = Form.connection_status = StatusCircle(Form) self.connection_status = Form.connection_status = StatusCircle(Form)
Form.connection_status.setGeometry(QtCore.QRect(230, 35, 32, 32)) Form.connection_status.setGeometry(QtCore.QRect(230, 35, 32, 32))
self.avatar_label.mouseReleaseEvent = self.profilesettings self.avatar_label.mouseReleaseEvent = self.profile_settings
self.status_message.mouseReleaseEvent = self.profilesettings self.status_message.mouseReleaseEvent = self.profile_settings
self.name.mouseReleaseEvent = self.profilesettings self.name.mouseReleaseEvent = self.profile_settings
self.connection_status.raise_() self.connection_status.raise_()
Form.connection_status.setObjectName("connection_status") Form.connection_status.setObjectName("connection_status")
@ -232,6 +232,11 @@ class MainWindow(QtGui.QMainWindow):
font.setBold(False) font.setBold(False)
self.account_status.setFont(font) self.account_status.setFont(font)
self.account_status.setObjectName("account_status") self.account_status.setObjectName("account_status")
self.account_status.mouseReleaseEvent = self.show_chat_menu
self.account_name.mouseReleaseEvent = self.show_chat_menu
self.account_avatar.mouseReleaseEvent = self.show_chat_menu
self.callButton = QtGui.QPushButton(Form) self.callButton = QtGui.QPushButton(Form)
self.callButton.setGeometry(QtCore.QRect(550, 30, 50, 50)) self.callButton.setGeometry(QtCore.QRect(550, 30, 50, 50))
self.callButton.setObjectName("callButton") self.callButton.setObjectName("callButton")
@ -389,7 +394,7 @@ class MainWindow(QtGui.QMainWindow):
self.a_c = AddContact(link) self.a_c = AddContact(link)
self.a_c.show() self.a_c.show()
def profilesettings(self, *args): def profile_settings(self, *args):
self.p_s = ProfileSettings() self.p_s = ProfileSettings()
self.p_s.show() self.p_s.show()
@ -455,6 +460,11 @@ class MainWindow(QtGui.QMainWindow):
self.gc = AddGroupchat() self.gc = AddGroupchat()
self.gc.show() self.gc.show()
def show_chat_menu(self):
pr = Profile.get_instance()
if not pr.is_active_a_friend():
pass # TODO: show list of users in chat
# ----------------------------------------------------------------------------------------------------------------- # -----------------------------------------------------------------------------------------------------------------
# Messages, calls and file transfers # Messages, calls and file transfers
# ----------------------------------------------------------------------------------------------------------------- # -----------------------------------------------------------------------------------------------------------------
@ -533,6 +543,7 @@ class MainWindow(QtGui.QMainWindow):
auto = QtGui.QApplication.translate("MainWindow", 'Disallow auto accept', None, QtGui.QApplication.UnicodeUTF8) if allowed else QtGui.QApplication.translate("MainWindow", 'Allow auto accept', None, QtGui.QApplication.UnicodeUTF8) auto = QtGui.QApplication.translate("MainWindow", 'Disallow auto accept', None, QtGui.QApplication.UnicodeUTF8) if allowed else QtGui.QApplication.translate("MainWindow", 'Allow auto accept', None, QtGui.QApplication.UnicodeUTF8)
if item is not None: if item is not None:
self.listMenu = QtGui.QMenu() self.listMenu = QtGui.QMenu()
if type(friend) is Friend: # TODO: add `invite to gc` submenu
set_alias_item = self.listMenu.addAction(QtGui.QApplication.translate("MainWindow", 'Set alias', None, QtGui.QApplication.UnicodeUTF8)) set_alias_item = self.listMenu.addAction(QtGui.QApplication.translate("MainWindow", 'Set alias', None, QtGui.QApplication.UnicodeUTF8))
clear_history_item = self.listMenu.addAction(QtGui.QApplication.translate("MainWindow", 'Clear history', None, QtGui.QApplication.UnicodeUTF8)) clear_history_item = self.listMenu.addAction(QtGui.QApplication.translate("MainWindow", 'Clear history', None, QtGui.QApplication.UnicodeUTF8))
copy_menu = self.listMenu.addMenu(QtGui.QApplication.translate("MainWindow", 'Copy', None, QtGui.QApplication.UnicodeUTF8)) copy_menu = self.listMenu.addMenu(QtGui.QApplication.translate("MainWindow", 'Copy', None, QtGui.QApplication.UnicodeUTF8))
@ -556,6 +567,8 @@ class MainWindow(QtGui.QMainWindow):
self.connect(notes_item, QtCore.SIGNAL("triggered()"), lambda: self.show_note(friend)) self.connect(notes_item, QtCore.SIGNAL("triggered()"), lambda: self.show_note(friend))
self.connect(copy_name_item, QtCore.SIGNAL("triggered()"), lambda: self.copy_name(friend)) self.connect(copy_name_item, QtCore.SIGNAL("triggered()"), lambda: self.copy_name(friend))
self.connect(copy_status_item, QtCore.SIGNAL("triggered()"), lambda: self.copy_status(friend)) self.connect(copy_status_item, QtCore.SIGNAL("triggered()"), lambda: self.copy_status(friend))
else:
pass # TODO: add menu for gc
parent_position = self.friends_list.mapToGlobal(QtCore.QPoint(0, 0)) parent_position = self.friends_list.mapToGlobal(QtCore.QPoint(0, 0))
self.listMenu.move(parent_position + pos) self.listMenu.move(parent_position + pos)
self.listMenu.show() self.listMenu.show()

View File

@ -60,7 +60,7 @@ class Profile(basecontact.BaseContact, Singleton):
if not self._history.friend_exists_in_db(tox_id): if not self._history.friend_exists_in_db(tox_id):
self._history.add_friend_to_db(tox_id) self._history.add_friend_to_db(tox_id)
message_getter = self._history.messages_getter(tox_id) message_getter = self._history.messages_getter(tox_id)
friend = Friend(message_getter, i, name, status_message, item, tox_id) friend = Friend(i, message_getter, name, status_message, item, tox_id)
friend.set_alias(alias) friend.set_alias(alias)
self._friends_and_gc.append(friend) self._friends_and_gc.append(friend)
self.filtration(self._show_online) self.filtration(self._show_online)
@ -138,7 +138,10 @@ class Profile(basecontact.BaseContact, Singleton):
self.filtration(self._show_online, self._filter_string) self.filtration(self._show_online, self._filter_string)
def get_friend_by_number(self, num): def get_friend_by_number(self, num):
return list(filter(lambda x: x.number == num, self._friends_and_gc))[0] return list(filter(lambda x: x.number == num and type(x) is Friend, self._friends_and_gc))[0]
def get_gc_by_number(self, num):
return list(filter(lambda x: x.number == num and type(x) is not Friend, self._friends_and_gc))[0]
def get_friend_or_gc(self, num): def get_friend_or_gc(self, num):
return self._friends_and_gc[num] return self._friends_and_gc[num]
@ -350,12 +353,13 @@ class Profile(basecontact.BaseContact, Singleton):
except: except:
pass pass
def split_and_send(self, number, message_type, message): def split_and_send(self, number, message_type, message, is_group=False):
""" """
Message splitting Message splitting
:param number: friend's number :param number: friend or gc number
:param message_type: type of message :param message_type: type of message
:param message: message text :param message: message text
:param is_group: send to group
""" """
while len(message) > TOX_MAX_MESSAGE_LENGTH: while len(message) > TOX_MAX_MESSAGE_LENGTH:
size = TOX_MAX_MESSAGE_LENGTH * 4 / 5 size = TOX_MAX_MESSAGE_LENGTH * 4 / 5
@ -369,9 +373,15 @@ class Profile(basecontact.BaseContact, Singleton):
else: else:
index = TOX_MAX_MESSAGE_LENGTH - size - 1 index = TOX_MAX_MESSAGE_LENGTH - size - 1
index += size + 1 index += size + 1
if not is_group:
self._tox.friend_send_message(number, message_type, message[:index]) self._tox.friend_send_message(number, message_type, message[:index])
else:
self._tox.group_send_message(number, message_type, message[:index])
message = message[index:] message = message[index:]
if not is_group:
self._tox.friend_send_message(number, message_type, message) self._tox.friend_send_message(number, message_type, message)
else:
self._tox.group_send_message(number, message_type, message)
def new_message(self, friend_num, message_type, message): def new_message(self, friend_num, message_type, message):
""" """
@ -394,18 +404,20 @@ class Profile(basecontact.BaseContact, Singleton):
if not friend.visibility: if not friend.visibility:
self.update_filtration() self.update_filtration()
def send_message(self, text, friend_num=None): def send_message(self, text, number=None, is_gc=False):
""" """
Send message Send message
:param text: message text :param text: message text
:param friend_num: num of friend :param number: num of friend or gc
:param is_gc: is group chat
""" """
if friend_num is None: if number is None:
friend_num = self.get_active_number() number = self.get_active_number()
is_gc = not self.is_active_a_friend()
if text.startswith('/plugin '): if text.startswith('/plugin '):
plugin_support.PluginLoader.get_instance().command(text[8:]) plugin_support.PluginLoader.get_instance().command(text[8:])
self._screen.messageEdit.clear() self._screen.messageEdit.clear()
elif text and friend_num + 1: elif text and number + 1:
text = ''.join(c if c <= '\u10FFFF' else '\u25AF' for c in text) text = ''.join(c if c <= '\u10FFFF' else '\u25AF' for c in text)
if text.startswith('/me '): if text.startswith('/me '):
@ -414,22 +426,30 @@ class Profile(basecontact.BaseContact, Singleton):
else: else:
message_type = TOX_MESSAGE_TYPE['NORMAL'] message_type = TOX_MESSAGE_TYPE['NORMAL']
friend = self.get_friend_by_number(friend_num) if not is_gc:
# TODO: send to gc friend_or_gc = self.get_friend_by_number(number)
# friend = self._friends_and_gc[self._active_friend_or_gc] else:
friend_or_gc = self.get_gc_by_number(number)
friend.inc_receipts()
if friend.status is not None:
self.split_and_send(friend.number, message_type, text.encode('utf-8'))
t = time.time() t = time.time()
if friend.number == self.get_active_number():
if not is_gc:
friend_or_gc.inc_receipts()
if friend_or_gc.status is not None:
self.split_and_send(friend_or_gc.number, message_type, text.encode('utf-8'))
if friend_or_gc.number == self.get_active_number() and self.is_active_a_friend():
self.create_message_item(text, t, MESSAGE_OWNER['NOT_SENT'], message_type) self.create_message_item(text, t, MESSAGE_OWNER['NOT_SENT'], message_type)
self._screen.messageEdit.clear() self._screen.messageEdit.clear()
self._messages.scrollToBottom() self._messages.scrollToBottom()
friend.append_message(TextMessage(text, MESSAGE_OWNER['NOT_SENT'], t, message_type)) else:
self.split_and_send(friend_or_gc.number, message_type, text.encode('utf-8'), True)
if friend_or_gc.number == self.get_active_number() and not self.is_active_a_friend():
self.create_message_item(text, t, MESSAGE_OWNER['ME'], message_type)
self._screen.messageEdit.clear()
self._messages.scrollToBottom()
friend_or_gc.append_message(TextMessage(text, MESSAGE_OWNER['NOT_SENT'], t, message_type))
def delete_message(self, time): def delete_message(self, time):
friend = self._friends[self._active_friend] friend = self._friends_and_gc[self._active_friend_or_gc]
friend.delete_message(time) friend.delete_message(time)
self._history.delete_message(friend.tox_id, time) self._history.delete_message(friend.tox_id, time)
self.update() self.update()
@ -1198,12 +1218,43 @@ class Profile(basecontact.BaseContact, Singleton):
# Group chats support # Group chats support
# ----------------------------------------------------------------------------------------------------------------- # -----------------------------------------------------------------------------------------------------------------
def add_gc(self, num):
tox_id = self._tox.group_get_chat_id(num)
name = self._tox.group_get_name(num)
topic = self._tox.group_get_topic(num)
item = self.create_friend_item()
try:
if not self._history.friend_exists_in_db(tox_id):
self._history.add_friend_to_db(tox_id)
message_getter = self._history.messages_getter(tox_id)
except Exception as ex: # something is wrong
log('Accept friend request failed! ' + str(ex))
message_getter = None
gc = GroupChat(self._tox, message_getter, num, name, topic, item, tox_id)
self._friends_and_gc.append(gc)
def create_gc(self, name, is_public, password): def create_gc(self, name, is_public, password):
privacy_state = TOX_GROUP_PRIVACY_STATE['TOX_GROUP_PRIVACY_STATE_PUBLIC'] if is_public else TOX_GROUP_PRIVACY_STATE['TOX_GROUP_PRIVACY_STATE_PRIVATE'] privacy_state = TOX_GROUP_PRIVACY_STATE['TOX_GROUP_PRIVACY_STATE_PUBLIC'] if is_public else TOX_GROUP_PRIVACY_STATE['TOX_GROUP_PRIVACY_STATE_PRIVATE']
num = self._tox.group_new(privacy_state, bytes(name, 'utf-8')) num = self._tox.group_new(privacy_state, bytes(name, 'utf-8'))
if password: if password:
self._tox.group_founder_set_password(num, password) self._tox.group_founder_set_password(num, bytes(password, 'utf-8'))
# self._friends_and_gc.append(Groupchat(num, self._tox, )) self.add_gc(num)
def process_group_invite(self, friend_num, data):
# TODO: add info to list and support password
try:
text = QtGui.QApplication.translate('MainWindow', 'User {} invites you to group',
None, QtGui.QApplication.UnicodeUTF8)
info = text.format(self.get_friend_by_number(friend_num).name)
fr_req = QtGui.QApplication.translate('MainWindow', 'Group chat invite', None, QtGui.QApplication.UnicodeUTF8)
reply = QtGui.QMessageBox.question(None, fr_req, info, QtGui.QMessageBox.Yes, QtGui.QMessageBox.No)
if reply == QtGui.QMessageBox.Yes: # accepted
num = self._tox.group_invite_accept(data)
data = self._tox.get_savedata()
ProfileHelper.get_instance().save_profile(data)
self.add_gc(num)
except Exception as ex: # something is wrong
log('Accept group chat invite failed! ' + str(ex))
def tox_factory(data=None, settings=None): def tox_factory(data=None, settings=None):

View File

@ -1893,13 +1893,14 @@ class Tox:
""" """
Write the Chat ID designated by the given group number to a byte array. Write the Chat ID designated by the given group number to a byte array.
`chat_id` should have room for at least TOX_GROUP_CHAT_ID_SIZE bytes. `chat_id` should have room for at least TOX_GROUP_CHAT_ID_SIZE bytes.
:return true on success. :return chat id.
""" """
error = c_int() error = c_int()
buff = create_string_buffer(TOX_GROUP_CHAT_ID_SIZE)
result = Tox.libtoxcore.tox_group_get_chat_id(self._tox_pointer, groupnumber, result = Tox.libtoxcore.tox_group_get_chat_id(self._tox_pointer, groupnumber,
create_string_buffer(TOX_GROUP_CHAT_ID_SIZE), byref(error)) buff, byref(error))
return result return bin_to_string(buff[:TOX_GROUP_CHAT_ID_SIZE], TOX_GROUP_CHAT_ID_SIZE)
def group_get_number_groups(self): def group_get_number_groups(self):
""" """
@ -2093,6 +2094,15 @@ class Tox:
""" """
Set the callback for the `group_message` event. Pass NULL to unset. Set the callback for the `group_message` event. Pass NULL to unset.
This event is triggered when the client receives a group message. This event is triggered when the client receives a group message.
Callback: python function with params:
tox Tox* instance
groupnumber The group number of the group the message is intended for.
peer_id The ID of the peer who sent the message.
type The type of message (normal, action, ...).
message The message data.
length The length of the message.
user_data - user data
""" """
c_callback = CFUNCTYPE(None, c_void_p, c_uint32, c_uint32, c_int, c_char_p, c_size_t, c_void_p) c_callback = CFUNCTYPE(None, c_void_p, c_uint32, c_uint32, c_int, c_char_p, c_size_t, c_void_p)
@ -2140,7 +2150,7 @@ class Tox:
result = Tox.libtoxcore.tox_group_invite_friend(self._tox_pointer, groupnumber, friend_number, byref(error)) result = Tox.libtoxcore.tox_group_invite_friend(self._tox_pointer, groupnumber, friend_number, byref(error))
return result return result
def group_invite_accept(self, invite_data, password): def group_invite_accept(self, invite_data, password=None):
""" """
Accept an invite to a group chat that the client previously received from a friend. The invite Accept an invite to a group chat that the client previously received from a friend. The invite
is only valid while the inviter is present in the group. is only valid while the inviter is present in the group.
@ -2151,8 +2161,10 @@ class Tox:
""" """
error = c_int() error = c_int()
result = Tox.libtoxcore.tox_group_invite_accept(self._tox_pointer, invite_data, len(invite_data), password, result = Tox.libtoxcore.tox_group_invite_accept(self._tox_pointer, invite_data, len(invite_data),
len(password), byref(error)) password,
len(password) if password is not None else 0,
byref(error))
return result return result
def callback_group_invite(self, callback, user_data): def callback_group_invite(self, callback, user_data):
@ -2161,6 +2173,13 @@ class Tox:
This event is triggered when the client receives a group invite from a friend. The client must store This event is triggered when the client receives a group invite from a friend. The client must store
invite_data which is used to join the group via tox_group_invite_accept. invite_data which is used to join the group via tox_group_invite_accept.
Callback: python function with params:
tox - Tox*
friend_number The friend number of the contact who sent the invite.
invite_data The invite data.
length The length of invite_data.
user_data - user data
""" """
c_callback = CFUNCTYPE(None, c_void_p, c_uint32, c_char_p, c_size_t, c_void_p) c_callback = CFUNCTYPE(None, c_void_p, c_uint32, c_char_p, c_size_t, c_void_p)