-
-
Save dperelman/9678cc001c22c4dd07d98fd6743ea48e to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python | |
# pkg-config --variable=xcbincludedir xcb-proto | |
import six | |
import re | |
import sys | |
from collections import namedtuple | |
def err(*args): | |
sys.stderr.write(' '.join(str(a) for a in args) + '\n') | |
PyType = namedtuple('PyType', 'name is_complex') | |
py_types = { | |
('int8_t',) : PyType('rq.Int8', False), | |
('int16_t',) : PyType('rq.Int16', False), | |
('int32_t',) : PyType('rq.Int32', False), | |
('uint8_t',) : PyType('rq.Card8', False), | |
('uint16_t',): PyType('rq.Card16', False), | |
('uint32_t',): PyType('rq.Card32', False), | |
} | |
py_level = 0 | |
ext_requests = {} | |
ext_ge_events = {} | |
ext_events = {} | |
ext_errors = {} | |
def uncamel(s): | |
s = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', s) | |
s = re.sub('([a-z0-9])([A-Z])', r'\1_\2', s) | |
return s.lower() | |
def py_out(fmt=None, indent=True, endl=True, *args): | |
if fmt is None: | |
six.print_() | |
return | |
if indent: | |
indent = ' ' * py_level * 4 | |
else: | |
indent = '' | |
code = (fmt % args).lstrip() | |
six.print_(indent + code, end='\n' if endl else '') | |
def py_indent(level): | |
global py_level | |
py_level += level | |
def py_name(name): | |
if isinstance(name, str): | |
pyn = name | |
else: | |
prefix_len = len(module.namespace.prefix) | |
if len(name) == 2 and name[1] in ('_fields', '_reply', '_request'): | |
pyn = name[1:] | |
else: | |
assert len(name) >= prefix_len and name[:prefix_len] == module.namespace.prefix, \ | |
'bad name prefix: %s / %s' % (str(name), str(module.namespace.prefix)) | |
pyn = name[prefix_len:] | |
assert len(pyn) == 1, '%s: %s' % (name, pyn) | |
pyn = pyn[0] | |
if module.namespace.is_ext: | |
if 'XInputExtension' == module.namespace.ext_xname: | |
pass | |
# Conflicts: UngrabDevice, XIUngrabDevice. | |
# if pyn.startswith('XI'): | |
# pyn = pyn[2:] | |
if re.match('^[A-Za-z_]', pyn) is None: | |
pyn = '_' + pyn | |
elif pyn in ('None', 'or', 'and', | |
'if', 'class', 'while', | |
'def', 'else', 'elif'): | |
pyn = '_' + pyn | |
return pyn | |
def py_open(self): | |
py_out('# Automatically generated file; DO NOT EDIT.') | |
py_out('# Generated from: %s' % self.namespace.path) | |
py_out() | |
py_out('from Xlib.protocol import rq, structs') | |
py_out() | |
py_out() | |
if module.namespace.is_ext: | |
# FIXME | |
py_types[('xcb', 'WINDOW')] = PyType('rq.Window', False) | |
py_types[('xcb', 'POINT')] = PyType('structs.Point', True) | |
py_types[('xcb', 'RECTANGLE')] = PyType('structs.Rectangle', True) | |
py_out("extname = '%s'" % module.namespace.ext_xname) | |
py_out() | |
def py_close(self): | |
if ext_errors: | |
py_out('class Error:') | |
py_indent(+1) | |
for error_name, error_code in sorted(ext_errors.items(), key=lambda v: v[1]): | |
pyn = py_name(error_name) | |
py_out('%s = %s' % (pyn, error_code)) | |
py_indent(-1) | |
py_out() | |
if not module.namespace.is_ext: | |
return | |
if (len(ext_events) + len(ext_ge_events)) > 0: | |
py_out('class Event:') | |
py_indent(+1) | |
if len(ext_events) > 0: | |
py_out('# Sub events.') | |
for event_number, event_name in sorted(ext_events.items()): | |
py_out('%s = %s' % (event_name, event_number)) | |
if len(ext_ge_events) > 0: | |
py_out('# Generic events.') | |
for event_number, event_name in sorted(ext_ge_events.items()): | |
py_out('%s = %s' % (event_name, event_number)) | |
py_indent(-1) | |
py_out() | |
method_prefix = '' | |
method_list = [] | |
for name, params in sorted(ext_requests.items()): | |
init_fields, has_reply = params | |
request_name = py_name(name) | |
method_name = uncamel(request_name) | |
if 'XInputExtension' == module.namespace.ext_xname: | |
method_prefix = 'xinput_' | |
else: | |
method_prefix = module.namespace.prefix[-1] + '_' | |
window_field_name = None | |
param_list = [] | |
for field_name, field in init_fields: | |
if ('xcb', 'WINDOW') == field.field_type: | |
window_field_name = field_name | |
elif 'opcode' == field_name: | |
continue | |
else: | |
param_list.append(field_name) | |
if window_field_name is not None: | |
method_type = 'window' | |
else: | |
method_type = 'display' | |
py_out("def %s(self, %s):" % (method_name, ', '.join(param_list))) | |
py_indent(+1) | |
if has_reply: | |
py_out('return %s(' % request_name) | |
else: | |
py_out('%s(' % request_name) | |
py_indent(+1) | |
py_out('display=self.display,') | |
py_out('opcode=self.display.get_extension_major(extname),') | |
if window_field_name is not None: | |
py_out('%s=self,' % window_field_name) | |
for param in param_list: | |
py_out('%s=%s,' % (param, param)) | |
py_indent(-1) | |
py_out(')') | |
py_indent(-1) | |
py_out() | |
method_list.append((method_type, method_name)) | |
py_out('def init(disp, info):') | |
py_indent(+1) | |
for method_type, method_name in method_list: | |
py_out("disp.extension_add_method('%s', '%s%s', %s)" % (method_type, | |
method_prefix, | |
method_name, | |
method_name)) | |
for event_number, event_name in sorted(ext_events.items()): | |
py_out("disp.extension_add_event(info.first_event + Event.%s, %sEventData)" % (event_name, | |
event_name)) | |
for event_number, event_name in sorted(ext_ge_events.items()): | |
py_out("disp.ge_add_event_data(info.major_opcode, Event.%s, %sEventData)" % (event_name, | |
event_name)) | |
py_indent(-1) | |
py_out() | |
def py_simple(self, name): | |
assert name not in py_types, 'type already defined %s' % str(name) | |
if ('xcb', 'xkb', 'STRING8') == name: | |
# FIXME | |
return | |
pyn = py_name(name) | |
pyt = py_types[self.name] | |
py_types[name] = PyType(pyn, False) | |
py_out('%s = %s' % (pyn, pyt.name)) | |
py_out() | |
def py_enum(self, name): | |
pyn = py_name(name) | |
py_out('class %s:' % pyn) | |
py_indent(+1) | |
for member_name, member_value in self.values: | |
py_out('%s = %s' % (py_name(member_name), member_value)) | |
py_indent(-1) | |
py_out() | |
def py_op(self, parent=None): | |
if self is None: | |
return '', () | |
if self.fixed_size(): | |
assert self.op is None | |
assert self.lhs is None | |
assert self.rhs is None | |
assert self.lenfield_name is None | |
return str(self.nmemb), () | |
if self.op is None: | |
assert self.lhs is None | |
assert self.rhs is None | |
assert self.lenfield_name is not None | |
name = uncamel(self.lenfield_name) | |
if parent is None: | |
return name, (name,) | |
name = '%s.%s' % (parent, name) | |
return name, () | |
if 'listelement-ref' == self.op: | |
assert self.lhs is None | |
assert self.rhs is None | |
assert self.lenfield_name is None | |
return 'n', () | |
result = [] | |
reflist = set() | |
r, rl = py_op(self.lhs, parent=parent) | |
result.append(r) | |
reflist.update(rl) | |
if 'popcount' == self.op: | |
assert self.lhs is None | |
assert self.rhs is not None | |
assert self.lenfield_name == self.rhs.lenfield_name | |
result.append('rq.popcount') | |
result.append('(') | |
elif 'sumof' == self.op: | |
assert self.lhs is None | |
assert self.lenfield_name is not None | |
name = uncamel(self.lenfield_name) | |
result.append('rq.sumof') | |
result.extend(('(', name)) | |
reflist.add(name) | |
if self.rhs is not None: | |
result.append(', lambda n: ') | |
parent = 'n' | |
else: | |
if '/' == self.op: | |
op = '//' | |
else: | |
op = self.op | |
result.insert(0, '(') | |
if self.lhs is None: | |
op = ' ' + op | |
else: | |
op = ' ' + op + ' ' | |
result.append(op) | |
r, rl = py_op(self.rhs, parent=parent) | |
reflist.update(rl) | |
result.append(r) | |
result.append(')') | |
return ''.join(result), reflist | |
def py_fields(fields, fixups={}, context=None): | |
if context is None: | |
def collect_fields(field_list): | |
all_field_list = [] | |
all_field_names = set() | |
for field in field_list: | |
if not field.wire: | |
continue | |
if field.type.is_pad: | |
continue | |
if field.field_name is not None: | |
field_name = uncamel(field.field_name) | |
assert field_name not in all_field_names, field_name | |
all_field_names.add(field_name) | |
if field.type.is_switch: | |
if field.type.bitcases[0].type.is_bitcase: | |
# Bitcase switch: all sub-fields may be present, | |
# so no overlap in names allowed. | |
for subfield in field.type.bitcases: | |
subfield_list, subfield_names = collect_fields(subfield.type.fields) | |
assert subfield_names not in all_field_names | |
all_field_names.update(subfield_names) | |
all_field_list.extend(subfield_list) | |
else: | |
# Case switch: only one subfield will be present, | |
# overlapping names between them is allowed. | |
all_subfield_names = set() | |
for subfield in field.type.bitcases: | |
subfield_list, subfield_names = collect_fields(subfield.type.fields) | |
all_subfield_names.update(subfield_names) | |
all_field_list.extend(subfield_list) | |
assert all_subfield_names not in all_field_names | |
all_field_names.update(all_subfield_names) | |
continue | |
if field.type.is_container: | |
continue | |
all_field_list.append(field) | |
return all_field_list, all_field_names | |
all_fields, _ = collect_fields(fields) | |
all_lenfield_names = {} | |
needname_names = set() | |
for field in all_fields: | |
if field.type.fixed_size(): | |
continue | |
assert hasattr(field.type, 'expr'), '%s %s' % (field.field_name, field.type.name) | |
field_name = uncamel(field.field_name) | |
ref_name = uncamel(field.type.expr.lenfield_name) | |
op, reflist = py_op(field.type.expr) | |
if len(reflist) > 1 or op != tuple(reflist)[0]: | |
needname_names.update(reflist) | |
continue | |
if ref_name in all_lenfield_names: | |
all_lenfield_names[ref_name].append(field_name) | |
else: | |
all_lenfield_names[ref_name] = [field_name] | |
else: | |
all_lenfield_names, needname_names = context | |
init_fields = [] | |
skip_fields = [] | |
for n, field in enumerate(fields): | |
if not field.wire: | |
continue | |
if n in skip_fields: | |
continue | |
# Padding. | |
if field.type.is_pad: | |
if field.type.fixed_size(): | |
py_out("rq.Pad(%u)," % field.type.nmemb) | |
else: | |
assert hasattr(field.type, 'align') | |
py_out("rq.Align(%u)," % field.type.align) | |
continue | |
pad = 0 | |
if (n + 1) < len(fields): | |
next_field = fields[n + 1] | |
if next_field.type.is_pad and 4 == next_field.type.align: | |
skip_fields.append(n + 1) | |
pad = 1 | |
pyt = py_types.get(field.field_type, py_types.get(field.type.name, None)) | |
if (field.field_name, field.type.name) in fixups: | |
assert 0 == pad | |
new_pyn, fmt = fixups[(field.field_name, field.type.name)] | |
out = fmt.format(field_type=pyt.name, field_size=field.type.size) | |
if new_pyn is not None: | |
init_fields.append((new_pyn, field)) | |
if out: | |
py_out(out) | |
continue | |
field_name = uncamel(field.field_name) | |
# Lengths. | |
if field_name in all_lenfield_names: | |
assert 0 == pad | |
assert 1 == field.type.nmemb | |
assert pyt.name in ('rq.Card8', 'rq.Card16', 'rq.Card32') | |
target_name = all_lenfield_names[field_name] | |
if 1 == len(target_name): | |
target_name = target_name[0] | |
else: | |
target_name = tuple(target_name) | |
if field_name in needname_names: | |
field_name = "'%s'" % field_name | |
else: | |
field_name = 'None' | |
py_out("rq.LengthOf(%s, %u, name=%s)," % (repr(target_name), | |
field.type.size, | |
field_name)) | |
# FIXME: add to init_fields if in needname_names? | |
continue | |
# Strings. | |
if ('char',) == field.type.name: | |
assert field.type.is_list | |
if field.type.fixed_size(): | |
assert 0 == pad | |
py_out("rq.FixedString('%s', %u)," % (field_name, field.type.nmemb)) | |
else: | |
py_out("rq.String8('%s', pad=%u)," % (field_name, pad)) | |
# Switches. | |
elif field.type.is_switch: | |
assert 0 == pad | |
is_bitcase = field.type.bitcases[0].type.is_bitcase | |
py_out("rq.%sSwitch('%s', '%s', (" % ( | |
'Bitcase' if is_bitcase else 'Case', | |
field.field_name, | |
field.type.expr.lenfield_name), | |
) | |
py_indent(+1) | |
for subfield in field.type.bitcases: | |
assert subfield.type.is_container | |
assert isinstance(subfield.type.expr, list) | |
assert is_bitcase == subfield.type.is_bitcase | |
value_list = [] | |
for expr in subfield.type.expr: | |
value_list.append('%s.%s' % (py_name(expr.lenfield_type.name), | |
py_name(expr.lenfield_name))) | |
if is_bitcase: | |
py_out('(%s, (' % ' | '.join(value_list)) | |
else: | |
py_out('((%s,), (' % ', '.join(value_list)) | |
py_indent(+1) | |
py_fields(subfield.type.fields, context=(all_lenfield_names, needname_names)) | |
py_indent(-1) | |
py_out(')),') | |
py_indent(-1) | |
py_out(')),') | |
# Lists. | |
elif field.type.is_list: | |
assert field.type.is_list | |
if field.type.fixed_size(): | |
py_out("rq.FixedList('%s', %u, %s, pad=%u)," % (field_name, | |
field.type.nmemb, | |
pyt.name, | |
pad)) | |
else: | |
assert hasattr(field.type, 'expr') | |
op, reflist = py_op(field.type.expr) | |
assert reflist | |
if len(reflist) > 1 or op != tuple(reflist)[0]: | |
calc_length = 'lambda %s: %s' % ( | |
', '.join(sorted(reflist)), op | |
) | |
else: | |
calc_length = 'None' | |
py_out("rq.List('%s', %s, pad=%u, calc_length=%s)," % ( | |
field_name, pyt.name, pad, calc_length, | |
)) | |
elif pyt.is_complex: | |
assert 0 == pad | |
assert 1 == field.type.nmemb | |
assert not field.type.is_simple | |
py_out("rq.Object('%s', %s)," % (field_name, pyt.name)) | |
else: | |
assert 0 == pad | |
assert 1 == field.type.nmemb | |
assert not field.type.is_container | |
py_out("%s('%s')," % (pyt.name, field_name)) | |
init_fields.append((field_name, field)) | |
return init_fields | |
def py_struct(self, name): | |
if name is not None: | |
pyn = py_name(name) | |
py_out('%s = rq.Struct(' % pyn) | |
else: | |
pyn = '???' | |
py_out('rq.Struct(', indent=False) | |
py_indent(+1) | |
py_fields(self.fields) | |
py_indent(-1) | |
if name is not None: | |
py_out(')') | |
py_out() | |
py_types[name] = PyType(pyn, True) | |
else: | |
py_out(')', endl=False) | |
def py_union(self, name): | |
pyn = py_name(name) | |
py_out('%s = rq.Union(' % pyn) | |
py_indent(+1) | |
py_fields(self.fields) | |
py_indent(-1) | |
py_out(')') | |
py_out() | |
py_types[name] = PyType(pyn, True) | |
def py_request(self, name): | |
pyn = py_name(name) | |
if self.reply: | |
request_class = 'rq.ReplyRequest' | |
else: | |
request_class = 'rq.Request' | |
py_out('class %s(%s):' % (pyn, request_class)) | |
py_out() | |
py_indent(+1) | |
py_out('_request = rq.Struct(') | |
py_indent(+1) | |
fixups = { | |
('major_opcode', ('uint8_t',)): ('opcode', "{field_type}('opcode'),"), | |
('minor_opcode', ('uint8_t',)): (None , 'rq.Opcode(%s),' % self.opcode), | |
('length', ('uint16_t',)): (None , 'rq.RequestLength(),'), | |
} | |
init_fields = py_fields(self.fields, fixups) | |
py_indent(-1) | |
py_out(')') | |
py_out() | |
if self.reply: | |
py_out('_reply = rq.Struct(') | |
py_indent(+1) | |
fixups = { | |
('response_type', ('uint8_t',)): (None, 'rq.ReplyCode(),'), | |
('sequence', ('uint16_t',)): (None, "{field_type}('sequence_number'),"), | |
('length', ('uint32_t',)): (None, 'rq.ReplyLength(),'), | |
} | |
py_fields(self.reply.fields, fixups) | |
py_indent(-1) | |
py_out(')') | |
py_out() | |
py_indent(-1) | |
assert name not in ext_requests | |
ext_requests[name] = (init_fields, not not self.reply) | |
def py_event(self, name): | |
pyn = py_name(name) + 'EventData' | |
if self.is_ge_event: | |
assert [(f.field_name, f.type.name) for f in self.fields[0:5]] == [ | |
('response_type', ('uint8_t' , )), | |
('extension' , ('uint8_t' , )), | |
('sequence' , ('uint16_t', )), | |
('length' , ('uint32_t', )), | |
('event_type' , ('uint16_t', )), | |
] | |
py_out('%s = rq.Struct(' % pyn) | |
py_indent(+1) | |
py_fields(self.fields[5:]) | |
py_indent(-1) | |
py_out(')') | |
py_out() | |
else: | |
py_out('class %s(rq.Event):' % pyn) | |
py_indent(+1) | |
py_out('_code = None') | |
py_out('_fields = rq.Struct(') | |
py_indent(+1) | |
py_fields(self.fields) | |
py_indent(-1) | |
py_out(')') | |
py_indent(-1) | |
py_out() | |
pyn = py_name(name) | |
opcode = int(self.opcodes[name]) | |
if self.is_ge_event: | |
events_dict = ext_ge_events | |
else: | |
events_dict = ext_events | |
assert opcode not in events_dict | |
events_dict[opcode] = pyn | |
def py_error(self, name): | |
ext_errors.update(self.opcodes) | |
output = {'open' : py_open, | |
'close' : py_close, | |
'simple' : py_simple, | |
'enum' : py_enum, | |
'struct' : py_struct, | |
'union' : py_union, | |
'request' : py_request, | |
'event' : py_event, | |
'eventstruct' : py_event, | |
'error' : py_error, | |
} | |
from xcbgen.state import Module | |
from xcbgen.xtypes import * | |
module = Module(sys.argv[1], output) | |
module.register() | |
module.resolve() | |
module.generate() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment