Skip to content

Instantly share code, notes, and snippets.

@benoit-pierre
Created November 12, 2018 15:24
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save benoit-pierre/de5beb2cfb1a60abe78f83619796edea to your computer and use it in GitHub Desktop.
Save benoit-pierre/de5beb2cfb1a60abe78f83619796edea to your computer and use it in GitHub Desktop.
#!/usr/bin/env python
# pkg-config --variable=xcbincludedir xcb-proto
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, *args, indent=True, endl=True):
if fmt is None:
print()
return
if indent:
indent = ' ' * py_level * 4
else:
indent = ''
code = (fmt % args).lstrip()
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,
'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