| # Copyright 2020 The Chromium Authors |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| # This script generates header/source files with C++ language bindings |
| # for the X11 protocol and its extensions. The protocol information |
| # is obtained from xcbproto which provides XML files describing the |
| # wire format. However, we don't parse the XML here; xcbproto ships |
| # with xcbgen, a python library that parses the files into python data |
| # structures for us. |
| |
| from __future__ import print_function |
| |
| import argparse |
| import collections |
| import itertools |
| import os |
| import re |
| import sys |
| import types |
| |
| # __main__.output must be defined before importing xcbgen, |
| # so this global is unavoidable. |
| output = collections.defaultdict(int) |
| |
| RENAME = { |
| 'ANIMCURSORELT': 'AnimationCursorElement', |
| 'CA': 'ChangeAlarmAttribute', |
| 'CHAR2B': 'Char16', |
| 'CHARINFO': 'CharInfo', |
| 'COLORITEM': 'ColorItem', |
| 'COLORMAP': 'ColorMap', |
| 'Connection': 'RandRConnection', |
| 'CP': 'CreatePictureAttribute', |
| 'CS': 'ClientSpec', |
| 'CW': 'CreateWindowAttribute', |
| 'DAMAGE': 'DamageId', |
| 'DIRECTFORMAT': 'DirectFormat', |
| 'DOTCLOCK': 'DotClock', |
| 'FBCONFIG': 'FbConfig', |
| 'FONTPROP': 'FontProperty', |
| 'GC': 'GraphicsContextAttribute', |
| 'GCONTEXT': 'GraphicsContext', |
| 'GLYPHINFO': 'GlyphInfo', |
| 'GLYPHSET': 'GlyphSet', |
| 'INDEXVALUE': 'IndexValue', |
| 'KB': 'Keyboard', |
| 'KEYCODE': 'KeyCode', |
| 'KEYCODE32': 'KeyCode32', |
| 'KEYSYM': 'KeySym', |
| 'LINEFIX': 'LineFix', |
| 'OP': 'Operation', |
| 'PBUFFER': 'PBuffer', |
| 'PCONTEXT': 'PContext', |
| 'PICTDEPTH': 'PictDepth', |
| 'PICTFORMAT': 'PictFormat', |
| 'PICTFORMINFO': 'PictFormInfo', |
| 'PICTSCREEN': 'PictScreen', |
| 'PICTVISUAL': 'PictVisual', |
| 'POINTFIX': 'PointFix', |
| 'SPANFIX': 'SpanFix', |
| 'SUBPICTURE': 'SubPicture', |
| 'SYSTEMCOUNTER': 'SystemCounter', |
| 'TIMECOORD': 'TimeCoord', |
| 'TIMESTAMP': 'Time', |
| 'VISUALID': 'VisualId', |
| 'VISUALTYPE': 'VisualType', |
| 'WAITCONDITION': 'WaitCondition', |
| } |
| |
| READ_SPECIAL = set([ |
| ('xcb', 'Setup'), |
| ]) |
| |
| WRITE_SPECIAL = set([ |
| ('xcb', 'ClientMessage'), |
| ('xcb', 'Expose'), |
| ('xcb', 'UnmapNotify'), |
| ('xcb', 'SelectionNotify'), |
| ('xcb', 'MotionNotify'), |
| ('xcb', 'Key'), |
| ('xcb', 'Button'), |
| ('xcb', 'PropertyNotify'), |
| ]) |
| |
| FILE_HEADER = \ |
| '''// Copyright 2021 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| // This file was automatically generated with: |
| // %s |
| ''' % ' \\\n// '.join(sys.argv) |
| |
| |
| def adjust_type_name(name): |
| if name in RENAME: |
| return RENAME[name] |
| # If there's an underscore, then this is either snake case or upper case. |
| if '_' in name: |
| return ''.join([ |
| token[0].upper() + token[1:].lower() for token in name.split('_') |
| ]) |
| if name.isupper(): |
| name = name.lower() |
| # Now the only possibilities are caml case and pascal case. It could also |
| # be snake case with a single word, but that would be same as caml case. |
| # To convert all of these, just capitalize the first letter. |
| return name[0].upper() + name[1:] |
| |
| |
| # Given a list of event names like ["KeyPress", "KeyRelease"], returns a name |
| # suitable for use as a base event like "Key". |
| def event_base_name(names): |
| # If there's only one event in this group, the "common name" is just |
| # the event name. |
| if len(names) == 1: |
| return names[0] |
| |
| # Handle a few special cases where the longest common prefix is empty: eg. |
| # EnterNotify/LeaveNotify/FocusIn/FocusOut -> Crossing. |
| EVENT_NAMES = [ |
| ('TouchBegin', 'Device'), |
| ('RawTouchBegin', 'RawDevice'), |
| ('Enter', 'Crossing'), |
| ('EnterNotify', 'Crossing'), |
| ('DeviceButtonPress', 'LegacyDevice'), |
| ] |
| for name, rename in EVENT_NAMES: |
| if name in names: |
| return rename |
| |
| # Use the longest common prefix of the event names as the base name. |
| name = ''.join( |
| chars[0] |
| for chars in itertools.takewhile(lambda chars: len(set(chars)) == 1, |
| zip(*names))) |
| assert name |
| return name |
| |
| |
| def list_size(name, list_type): |
| separator = '->' if list_type.is_ref_counted_memory else '.' |
| return '%s%ssize()' % (name, separator) |
| |
| |
| # Left-pad with 2 spaces while this class is alive. |
| class Indent: |
| def __init__(self, xproto, opening_line, closing_line): |
| self.xproto = xproto |
| self.opening_line = opening_line |
| self.closing_line = closing_line |
| |
| def __enter__(self): |
| self.xproto.write(self.opening_line) |
| self.xproto.indent += 1 |
| |
| def __exit__(self, exc_type, exc_value, exc_traceback): |
| self.xproto.indent -= 1 |
| self.xproto.write(self.closing_line) |
| |
| |
| # Make all members of |obj|, given by |fields|, visible in |
| # the local scope while this class is alive. |
| class ScopedFields: |
| def __init__(self, xproto, obj, fields): |
| self.xproto = xproto |
| self.obj = obj |
| self.fields = fields |
| self.n_pushed = 0 |
| |
| def __enter__(self): |
| for field in self.fields: |
| self.n_pushed += self.xproto.add_field_to_scope(field, self.obj) |
| |
| if self.n_pushed: |
| self.xproto.write() |
| |
| def __exit__(self, exc_type, exc_value, exc_traceback): |
| for _ in range(self.n_pushed): |
| self.xproto.scope.pop() |
| |
| |
| # Ensures |name| is usable as a C++ field by avoiding keywords and |
| # symbols that start with numbers. |
| def safe_name(name): |
| RESERVED = [ |
| 'and', |
| 'xor', |
| 'or', |
| 'class', |
| 'explicit', |
| 'new', |
| 'delete', |
| 'default', |
| 'private', |
| ] |
| if name[0].isdigit() or name in RESERVED: |
| return 'c_' + name |
| return name |
| |
| |
| class FileWriter: |
| def __init__(self): |
| self.indent = 0 |
| |
| # Write a line to the current file. |
| def write(self, line=''): |
| indent = self.indent if line and not line.startswith('#') else 0 |
| print((' ' * indent) + line, file=self.file) |
| |
| def write_header(self): |
| for header_line in FILE_HEADER.split('\n'): |
| self.write(header_line) |
| |
| |
| class GenXproto(FileWriter): |
| def __init__(self, proto, proto_dir, gen_dir, xcbgen, all_types): |
| FileWriter.__init__(self) |
| |
| # Command line arguments |
| self.proto = proto |
| self.xml_filename = os.path.join(proto_dir, '%s.xml' % proto) |
| self.header_file = open(os.path.join(gen_dir, '%s.h' % proto), 'w') |
| self.source_file = open(os.path.join(gen_dir, '%s.cc' % proto), 'w') |
| |
| # Top-level xcbgen python module |
| self.xcbgen = xcbgen |
| |
| # Types for every module including this one |
| self.all_types = all_types |
| |
| # The last used UID for making unique names |
| self.prev_id = -1 |
| |
| # Current file to write to |
| self.file = None |
| |
| # Flag to indicate if we're generating code to serialize or |
| # deserialize data. |
| self.is_read = False |
| |
| # List of the fields in scope |
| self.scope = [] |
| |
| # Current place in C++ namespace hierarchy (including classes) |
| self.namespace = [] |
| |
| # Map from type names to a set of types. Certain types |
| # like enums and simple types can alias each other. |
| self.types = collections.defaultdict(list) |
| |
| # Set of names of simple types to be replaced with enums |
| self.replace_with_enum = set() |
| |
| # Map of enums to their underlying types |
| self.enum_types = collections.defaultdict(set) |
| |
| # Map from (XML tag, XML name) to XML element |
| self.module_names = {} |
| |
| # Enums that represent bit masks. |
| self.bitenums = [] |
| |
| # Generate an ID suitable for use in temporary variable names. |
| def new_uid(self, ): |
| self.prev_id += 1 |
| return self.prev_id |
| |
| def is_eq_comparable(self, type): |
| if type.is_list: |
| return self.is_eq_comparable(type.member) |
| if type.is_simple or type.is_pad: |
| return True |
| if (type.is_switch or type.is_union |
| or isinstance(type, self.xcbgen.xtypes.Request) |
| or isinstance(type, self.xcbgen.xtypes.Reply)): |
| return False |
| assert type.is_container |
| return all(self.is_eq_comparable(field.type) for field in type.fields) |
| |
| def type_suffix(self, t): |
| if isinstance(t, self.xcbgen.xtypes.Error): |
| return 'Error' |
| elif isinstance(t, self.xcbgen.xtypes.Request): |
| return 'Request' |
| elif t.is_reply: |
| return 'Reply' |
| elif t.is_event: |
| return 'Event' |
| return '' |
| |
| def rename_type(self, t, name): |
| name = list(name) |
| |
| if name[0] == 'xcb': |
| # Use namespace x11 instead of xcb. |
| name[0] = 'x11' |
| |
| for i in range(1, len(name)): |
| name[i] = adjust_type_name(name[i]) |
| name[-1] += self.type_suffix(t) |
| return name |
| |
| # Given an unqualified |name| like ('Window') and a namespace like ['x11'], |
| # returns a fully qualified name like ('x11', 'Window'). |
| def qualify_type(self, name, namespace): |
| if tuple(namespace + name) in self.all_types: |
| return namespace + name |
| return self.qualify_type(name, namespace[:-1]) |
| |
| # Given an xcbgen.xtypes.Type, returns a C++-namespace-qualified |
| # string that looks like Input::InputClass::Key. |
| def qualtype(self, t, name): |
| name = self.rename_type(t, name) |
| |
| # Try to avoid adding namespace qualifiers if they're not necessary. |
| chop = 0 |
| for t1, t2 in zip(name, self.namespace): |
| if t1 != t2: |
| break |
| if self.qualify_type(name[chop + 1:], self.namespace) != name: |
| break |
| chop += 1 |
| return '::'.join(name[chop:]) |
| |
| def fieldtype(self, field): |
| if field.isfd: |
| return 'RefCountedFD' |
| return self.qualtype(field.type, |
| field.enum if field.enum else field.field_type) |
| |
| def switch_fields(self, switch): |
| fields = [] |
| for case in switch.bitcases: |
| if case.field_name: |
| fields.append(case) |
| else: |
| fields.extend(case.type.fields) |
| return fields |
| |
| def add_field_to_scope(self, field, obj): |
| if not field.visible or (not field.wire and not field.isfd): |
| return 0 |
| |
| field_name = safe_name(field.field_name) |
| |
| if field.type.is_switch: |
| self.write('auto& %s = %s;' % (field_name, obj)) |
| return 0 |
| |
| self.scope.append(field) |
| |
| if field.for_list or field.for_switch: |
| self.write('%s %s{};' % (self.fieldtype(field), field_name)) |
| else: |
| self.write('auto& %s = %s.%s;' % (field_name, obj, field_name)) |
| |
| if field.type.is_list: |
| len_name = field_name + '_len' |
| if not self.field_from_scope(len_name): |
| len_expr = list_size(field_name, field.type) |
| if field.type.is_ref_counted_memory: |
| len_expr = '%s ? %s : 0' % (field_name, len_expr) |
| self.write('size_t %s = %s;' % (len_name, len_expr)) |
| |
| return 1 |
| |
| # Lookup |name| in the current scope. Returns the deepest |
| # (most local) occurrence of |name|. |
| def field_from_scope(self, name): |
| for field in reversed(self.scope): |
| if field.field_name == name: |
| return field |
| return None |
| |
| def expr(self, expr): |
| if expr.op == 'popcount': |
| return 'PopCount(%s)' % self.expr(expr.rhs) |
| if expr.op == '~': |
| return 'BitNot(%s)' % self.expr(expr.rhs) |
| if expr.op == '&': |
| return 'BitAnd(%s, %s)' % (self.expr(expr.lhs), self.expr( |
| expr.rhs)) |
| if expr.op in ('+', '-', '*', '/', '|'): |
| return ('(%s) %s (%s)' % |
| (self.expr(expr.lhs), expr.op, self.expr(expr.rhs))) |
| if expr.op == 'calculate_len': |
| return expr.lenfield_name |
| if expr.op == 'sumof': |
| tmp_id = self.new_uid() |
| lenfield = self.field_from_scope(expr.lenfield_name) |
| elem_type = lenfield.type.member |
| fields = elem_type.fields if elem_type.is_container else [] |
| header = 'auto sum%d_ = SumOf([](%sauto& listelem_ref) {' % ( |
| tmp_id, '' if self.is_read else 'const ') |
| footer = '}, %s);' % expr.lenfield_name |
| with Indent(self, header, |
| footer), ScopedFields(self, 'listelem_ref', fields): |
| body = self.expr(expr.rhs) if expr.rhs else 'listelem_ref' |
| self.write('return %s;' % body) |
| return 'sum%d_' % tmp_id |
| if expr.op == 'listelement-ref': |
| return 'listelem_ref' |
| if expr.op == 'enumref': |
| return '%s::%s' % (self.qualtype( |
| expr.lenfield_type, |
| expr.lenfield_type.name), safe_name(expr.lenfield_name)) |
| |
| assert expr.op == None |
| if expr.nmemb: |
| return str(expr.nmemb) |
| |
| assert expr.lenfield_name |
| return expr.lenfield_name |
| |
| def get_xidunion_element(self, name): |
| key = ('xidunion', name[-1]) |
| return self.module_names.get(key, None) |
| |
| def declare_xidunion(self, xidunion, xidname): |
| names = [type_element.text for type_element in xidunion] |
| types = list(set([self.module.get_type(name) for name in names])) |
| assert len(types) == 1 |
| value_type = types[0] |
| value_typename = self.qualtype(value_type, value_type.name) |
| with Indent(self, 'struct %s {' % xidname, '};'): |
| self.write('%s() : value{} {}' % xidname) |
| self.write() |
| for name in names: |
| cpp_name = self.module.get_type_name(name) |
| typename = self.qualtype(value_type, cpp_name) |
| self.write('%s(%s value) : value{static_cast<%s>(value)} {}' % |
| (xidname, typename, value_typename)) |
| self.write( |
| 'operator %s() const { return static_cast<%s>(value); }' % |
| (typename, typename)) |
| self.write() |
| self.write('%s value{};' % value_typename) |
| |
| def declare_simple(self, item, name): |
| renamed = tuple(self.rename_type(item, name)) |
| if renamed in self.replace_with_enum: |
| return |
| |
| xidunion = self.get_xidunion_element(name) |
| if xidunion: |
| self.declare_xidunion(xidunion, renamed[-1]) |
| else: |
| self.write('enum class %s : %s {};' % |
| (renamed[-1], self.qualtype(item, item.name))) |
| self.write() |
| |
| def copy_primitive(self, name): |
| if self.is_read: |
| self.write('Read(&%s, &buf);' % name) |
| else: |
| self.write('buf.Write(&%s);' % name) |
| |
| def copy_fd(self, field, name): |
| if self.is_read: |
| self.write('%s = RefCountedFD(buf.TakeFd());' % name) |
| else: |
| # We take the request struct as const&, so dup() the fd to preserve |
| # const-correctness because XCB close()s it after writing it. |
| self.write('buf.fds().push_back(HANDLE_EINTR(dup(%s.get())));' % |
| name) |
| |
| def copy_special_field(self, field): |
| type_name = self.fieldtype(field) |
| name = safe_name(field.field_name) |
| |
| def copy_basic(): |
| self.write('%s %s;' % (type_name, name)) |
| self.copy_primitive(name) |
| |
| if name in ('major_opcode', 'minor_opcode'): |
| assert not self.is_read |
| is_ext = self.module.namespace.is_ext |
| self.write( |
| '%s %s = %s;' % |
| (type_name, name, 'info_.major_opcode' if is_ext |
| and name == 'major_opcode' else field.parent[0].opcode)) |
| self.copy_primitive(name) |
| elif name == 'response_type': |
| if self.is_read: |
| copy_basic() |
| else: |
| container_type, container_name = field.parent |
| assert container_type.is_event |
| # Extension events require offsetting the opcode, so make |
| # sure this path is only hit for non-extension events for now. |
| assert not self.module.namespace.is_ext |
| opcode = container_type.opcodes.get(container_name, |
| 'obj.opcode') |
| self.write('%s %s = %s;' % (type_name, name, opcode)) |
| self.copy_primitive(name) |
| elif name in ('extension', 'error_code', 'event_type'): |
| assert self.is_read |
| copy_basic() |
| elif name == 'length': |
| if not self.is_read: |
| self.write('// Caller fills in length for writes.') |
| self.write('Pad(&buf, sizeof(%s));' % type_name) |
| else: |
| copy_basic() |
| else: |
| assert field.type.is_expr |
| assert (not isinstance(field.type, self.xcbgen.xtypes.Enum)) |
| self.write('%s %s = %s;' % |
| (type_name, name, self.expr(field.type.expr))) |
| self.copy_primitive(name) |
| |
| def declare_case(self, case): |
| assert case.type.is_case != case.type.is_bitcase |
| |
| fields = [ |
| field for case_field in case.type.fields |
| for field in self.declare_field(case_field) |
| ] |
| if not case.field_name: |
| return fields |
| name = safe_name(case.field_name) |
| typename = adjust_type_name(name) |
| with Indent(self, 'struct %s {' % typename, '};'): |
| for field in fields: |
| self.write('%s %s{};' % field) |
| return [(typename, name)] |
| |
| def copy_case(self, case, switch_name): |
| op = 'CaseEq' if case.type.is_case else 'CaseAnd' |
| condition = ' || '.join([ |
| '%s(%s_expr, %s)' % (op, switch_name, self.expr(expr)) |
| for expr in case.type.expr |
| ]) |
| |
| with Indent(self, 'if (%s) {' % condition, '}'): |
| if case.field_name: |
| fields = [case] |
| obj = '(*%s.%s)' % (switch_name, safe_name(case.field_name)) |
| else: |
| fields = case.type.fields |
| obj = '*' + switch_name |
| for case_field in fields: |
| name = safe_name(case_field.field_name) |
| if case_field.visible and self.is_read: |
| self.write('%s.%s.emplace();' % (switch_name, name)) |
| with ScopedFields(self, obj, case.type.fields): |
| for case_field in case.type.fields: |
| self.copy_field(case_field) |
| |
| def declare_switch(self, field): |
| return [('absl::optional<%s>' % field_type, field_name) |
| for case in field.type.bitcases |
| for field_type, field_name in self.declare_case(case)] |
| |
| def copy_switch(self, field): |
| t = field.type |
| name = safe_name(field.field_name) |
| |
| self.write('auto %s_expr = %s;' % (name, self.expr(t.expr))) |
| for case in t.bitcases: |
| self.copy_case(case, name) |
| |
| def declare_list(self, field): |
| t = field.type |
| type_name = self.fieldtype(field) |
| name = safe_name(field.field_name) |
| |
| assert (t.nmemb not in (0, 1)) |
| if t.is_ref_counted_memory: |
| type_name = 'scoped_refptr<base::RefCountedMemory>' |
| elif t.nmemb: |
| type_name = 'std::array<%s, %d>' % (type_name, t.nmemb) |
| elif type_name == 'char': |
| type_name = 'std::string' |
| else: |
| type_name = 'std::vector<%s>' % type_name |
| return [(type_name, name)] |
| |
| def copy_list(self, field): |
| t = field.type |
| name = safe_name(field.field_name) |
| size = self.expr(t.expr) |
| |
| if t.is_ref_counted_memory: |
| if self.is_read: |
| self.write('%s = buffer->ReadAndAdvance(%s);' % (name, size)) |
| else: |
| self.write('buf.AppendBuffer(%s, %s);' % (name, size)) |
| return |
| |
| if not t.nmemb: |
| if self.is_read: |
| self.write('%s.resize(%s);' % (name, size)) |
| else: |
| left = 'static_cast<size_t>(%s)' % size |
| self.write('DCHECK_EQ(%s, %s.size());' % (left, name)) |
| with Indent(self, 'for (auto& %s_elem : %s) {' % (name, name), '}'): |
| elem_name = name + '_elem' |
| elem_type = t.member |
| elem_field = self.xcbgen.expr.Field(elem_type, field.field_type, |
| elem_name, field.visible, |
| field.wire, field.auto, |
| field.enum, field.isfd) |
| elem_field.for_list = None |
| elem_field.for_switch = None |
| self.copy_field(elem_field) |
| |
| def generate_switch_var(self, field): |
| name = safe_name(field.field_name) |
| for case in field.for_switch.type.bitcases: |
| case_field = case if case.field_name else case.type.fields[0] |
| self.write('SwitchVar(%s, %s.%s.has_value(), %s, &%s);' % |
| (self.expr(case.type.expr[0]), |
| safe_name(field.for_switch.field_name), |
| safe_name(case_field.field_name), |
| 'true' if case.type.is_bitcase else 'false', name)) |
| |
| def is_field_hidden_from_api(self, field): |
| return not field.visible or getattr( |
| field, 'for_list', False) or getattr(field, 'for_switch', False) |
| |
| def declare_field(self, field): |
| t = field.type |
| name = safe_name(field.field_name) |
| |
| if self.is_field_hidden_from_api(field): |
| return [] |
| |
| if t.is_switch: |
| return self.declare_switch(field) |
| if t.is_list: |
| return self.declare_list(field) |
| return [(self.fieldtype(field), name)] |
| |
| def copy_field(self, field): |
| if not field.wire and not field.isfd: |
| return |
| |
| t = field.type |
| renamed = tuple(self.rename_type(field.type, field.field_type)) |
| if t.is_list: |
| t.member = self.all_types.get(renamed, t.member) |
| else: |
| t = self.all_types.get(renamed, t) |
| name = safe_name(field.field_name) |
| |
| self.write('// ' + name) |
| |
| # If this is a generated field, initialize the value of the field |
| # variable from the given context. |
| if not self.is_read: |
| if field.for_list: |
| size = list_size(safe_name(field.for_list.field_name), |
| field.for_list.type) |
| self.write('%s = %s;' % (name, size)) |
| if field.for_switch: |
| self.generate_switch_var(field) |
| |
| if t.is_pad: |
| if t.align > 1: |
| assert t.nmemb == 1 |
| assert t.align in (2, 4) |
| self.write('Align(&buf, %d);' % t.align) |
| else: |
| self.write('Pad(&buf, %d);' % t.nmemb) |
| elif not field.visible: |
| self.copy_special_field(field) |
| elif t.is_switch: |
| self.copy_switch(field) |
| elif t.is_list: |
| self.copy_list(field) |
| elif t.is_union: |
| self.copy_primitive(name) |
| elif t.is_container: |
| with Indent(self, '{', '}'): |
| self.copy_container(t, name) |
| else: |
| assert t.is_simple |
| if field.isfd: |
| self.copy_fd(field, name) |
| elif field.enum: |
| self.copy_enum(field) |
| else: |
| self.copy_primitive(name) |
| |
| self.write() |
| |
| def declare_enum(self, enum): |
| def declare_enum_entry(name, value): |
| name = safe_name(name) |
| self.write('%s = %s,' % (name, value)) |
| |
| with Indent( |
| self, 'enum class %s : %s {' % |
| (adjust_type_name(enum.name[-1]), self.enum_types[enum.name][0] |
| if enum.name in self.enum_types else 'int'), '};'): |
| bitnames = set([name for name, _ in enum.bits]) |
| for name, value in enum.values: |
| if name not in bitnames: |
| declare_enum_entry(name, value) |
| for name, value in enum.bits: |
| declare_enum_entry(name, '1 << ' + value) |
| self.write() |
| |
| def copy_enum(self, field): |
| # The size of enum types may be different depending on the |
| # context, so they should always be casted to the contextual |
| # underlying type before calling Read() or Write(). |
| underlying_type = self.qualtype(field.type, field.type.name) |
| tmp_name = 'tmp%d' % self.new_uid() |
| real_name = safe_name(field.field_name) |
| self.write('%s %s;' % (underlying_type, tmp_name)) |
| if not self.is_read: |
| self.write('%s = static_cast<%s>(%s);' % |
| (tmp_name, underlying_type, real_name)) |
| self.copy_primitive(tmp_name) |
| if self.is_read: |
| enum_type = self.qualtype(field.type, field.enum) |
| self.write('%s = static_cast<%s>(%s);' % |
| (real_name, enum_type, tmp_name)) |
| |
| def declare_fields(self, fields): |
| for field in fields: |
| for field_type_name in self.declare_field(field): |
| self.write('%s %s{};' % field_type_name) |
| |
| # This tries to match XEvent.xany.window, except the window will be |
| # Window::None for events that don't have a window, unlike the XEvent |
| # union which will get whatever data happened to be at the offset of |
| # xany.window. |
| def get_window_field(self, event): |
| # The window field is not stored at any particular offset in the event, |
| # so get a list of all the window fields. |
| WINDOW_TYPES = set([ |
| ('xcb', 'WINDOW'), |
| ('xcb', 'DRAWABLE'), |
| ('xcb', 'Glx', 'DRAWABLE'), |
| ]) |
| # The window we want may not be the first in the list if there are |
| # multiple windows. This is a list of all possible window names, |
| # ordered from highest to lowest priority. |
| WINDOW_NAMES = [ |
| 'window', |
| 'event', |
| 'request_window', |
| 'owner', |
| ] |
| windows = set([ |
| field.field_name for field in event.fields |
| if field.field_type in WINDOW_TYPES |
| ]) |
| if len(windows) == 0: |
| return '' |
| if len(windows) == 1: |
| return list(windows)[0] |
| for name in WINDOW_NAMES: |
| if name in windows: |
| return name |
| assert False |
| |
| def declare_event(self, event, name): |
| event_name = name[-1] + 'Event' |
| with Indent(self, 'struct %s {' % adjust_type_name(event_name), '};'): |
| self.write('static constexpr int type_id = %d;' % event.type_id) |
| if len(event.opcodes) == 1: |
| self.write('static constexpr uint8_t opcode = %s;' % |
| event.opcodes[name]) |
| else: |
| with Indent(self, 'enum Opcode {', '} opcode{};'): |
| items = [(int(x), y) |
| for (y, x) in event.enum_opcodes.items()] |
| for opcode, opname in sorted(items): |
| self.write('%s = %s,' % (opname, opcode)) |
| self.declare_fields(event.fields) |
| self.write() |
| window_field = self.get_window_field(event) |
| ret = ('reinterpret_cast<x11::Window*>(&%s)' % |
| window_field if window_field else 'nullptr') |
| self.write('x11::Window* GetWindow() { return %s; }' % ret) |
| self.write() |
| |
| def declare_error(self, error, name): |
| name = adjust_type_name(name[-1] + 'Error') |
| with Indent(self, 'struct %s : public x11::Error {' % name, '};'): |
| self.declare_fields(error.fields) |
| self.write() |
| self.write('std::string ToString() const override;') |
| self.write() |
| |
| def declare_container(self, struct, struct_name): |
| name = adjust_type_name(struct_name[-1] + self.type_suffix(struct)) |
| with Indent(self, 'struct %s {' % name, '};'): |
| if self.is_eq_comparable(struct): |
| sig = 'bool operator==(const %s& other) const {' % name |
| with Indent(self, sig, '}'): |
| terms = [ |
| '%s == other.%s' % (field_name, field_name) |
| for field in struct.fields |
| for _, field_name in self.declare_field(field) |
| ] |
| expr = ' && '.join(terms) if terms else 'true' |
| self.write('return %s;' % expr) |
| self.write() |
| self.declare_fields(struct.fields) |
| self.write() |
| |
| def copy_container(self, struct, name): |
| assert not struct.is_union |
| with ScopedFields(self, name, struct.fields): |
| for field in struct.fields: |
| self.copy_field(field) |
| |
| def read_special_container(self, struct, name): |
| self.namespace = ['x11'] |
| name = self.qualtype(struct, name) |
| self.write('template <> COMPONENT_EXPORT(X11)') |
| self.write('%s Read<%s>(' % (name, name)) |
| with Indent(self, ' ReadBuffer* buffer) {', '}'): |
| self.write('auto& buf = *buffer;') |
| self.write('%s obj;' % name) |
| self.write() |
| self.is_read = True |
| self.copy_container(struct, 'obj') |
| self.write('return obj;') |
| self.write() |
| |
| def write_special_container(self, struct, name): |
| self.namespace = ['x11'] |
| name = self.qualtype(struct, name) |
| self.write('template <> COMPONENT_EXPORT(X11)') |
| self.write('WriteBuffer Write<%s>(' % name) |
| with Indent(self, ' const %s& obj) {' % name, '}'): |
| self.write('WriteBuffer buf;') |
| self.write() |
| self.is_read = False |
| self.copy_container(struct, 'obj') |
| self.write('return buf;') |
| self.write() |
| |
| def declare_union(self, union): |
| name = union.name[-1] |
| if union.elt.tag == 'eventstruct': |
| # There's only one of these in all of the protocol descriptions. |
| # It's just used to represent any 32-byte event for XInput. |
| self.write('using %s = std::array<uint8_t, 32>;' % name) |
| return |
| with Indent(self, 'union %s {' % name, '};'): |
| self.write('%s() { memset(this, 0, sizeof(*this)); }' % name) |
| self.write() |
| for field in union.fields: |
| field_type_names = self.declare_field(field) |
| assert len(field_type_names) == 1 |
| self.write('%s %s;' % field_type_names[0]) |
| self.write( |
| 'static_assert(std::is_trivially_copyable<%s>::value, "");' % name) |
| self.write() |
| |
| # Returns a list of strings suitable for use as a default-initializer for |
| # |field|. There may be 0 strings (if the field is hidden from the public |
| # API), 1 string (for normal cases), or many strings (for switch fields). |
| def get_initializer(self, field): |
| if self.is_field_hidden_from_api(field): |
| return [] |
| |
| if field.type.is_switch: |
| return ['absl::nullopt'] * len(self.declare_switch(field)) |
| if field.type.is_list or not field.type.is_container: |
| return ['{}'] |
| |
| # While using {} as an initializer for structs is fine when nested |
| # in other structs, it causes compiler errors when used as a default |
| # argument initializer, so explicitly initialize each field. |
| return [ |
| '{%s}' % ', '.join([ |
| init for subfield in field.type.fields |
| if not self.is_field_hidden_from_api(subfield) |
| for init in self.get_initializer(subfield) |
| ]) |
| ] |
| |
| def declare_request(self, request): |
| method_name = request.name[-1] |
| request_name = method_name + 'Request' |
| reply_name = method_name + 'Reply' if request.reply else 'void' |
| |
| in_class = self.namespace == ['x11', self.class_name] |
| |
| if not in_class or self.module.namespace.is_ext: |
| self.declare_container(request, request.name) |
| if request.reply: |
| self.declare_container(request.reply, request.reply.name) |
| |
| self.write('using %sResponse = Response<%s>;' % |
| (method_name, reply_name)) |
| self.write() |
| |
| if in_class: |
| # Generate a request method that takes a Request object. |
| self.write('Future<%s> %s(' % (reply_name, method_name)) |
| self.write(' const %s& request);' % request_name) |
| self.write() |
| |
| # Generate a request method that takes fields as arguments and |
| # forwards them as a Request object to the above implementation. |
| field_type_names = [ |
| field_type_name for field in request.fields |
| for field_type_name in self.declare_field(field) |
| ] |
| inits = [ |
| init for field in request.fields |
| for init in self.get_initializer(field) |
| ] |
| assert len(field_type_names) == len(inits) |
| args = [ |
| 'const %s& %s = %s' % (field_type_name + (init, )) |
| for (field_type_name, init) in zip(field_type_names, inits) |
| ] |
| self.write('Future<%s> %s(%s);' % |
| (reply_name, method_name, ', '.join(args))) |
| self.write() |
| |
| def define_request(self, request): |
| method_name = '%s::%s' % (self.class_name, request.name[-1]) |
| prefix = (method_name |
| if self.module.namespace.is_ext else request.name[-1]) |
| request_name = prefix + 'Request' |
| reply_name = prefix + 'Reply' |
| |
| reply = request.reply |
| if not reply: |
| reply_name = 'void' |
| |
| # Generate a request method that takes a Request object. |
| self.write('Future<%s>' % reply_name) |
| self.write('%s(' % method_name) |
| with Indent(self, ' const %s& request) {' % request_name, '}'): |
| cond = '!connection_->Ready()' |
| if self.module.namespace.is_ext: |
| cond += ' || !present()' |
| self.write('if (%s)' % cond) |
| self.write(' return {};') |
| self.write() |
| self.namespace = ['x11', self.class_name] |
| self.write('WriteBuffer buf;') |
| self.write() |
| self.is_read = False |
| self.copy_container(request, 'request') |
| self.write('Align(&buf, 4);') |
| self.write() |
| reply_has_fds = reply and any(field.isfd for field in reply.fields) |
| self.write( |
| 'return connection_->SendRequest<%s>(&buf, "%s", %s);' % |
| (reply_name, prefix, 'true' if reply_has_fds else 'false')) |
| self.write() |
| |
| # Generate a request method that takes fields as arguments and |
| # forwards them as a Request object to the above implementation. |
| self.write('Future<%s>' % reply_name) |
| self.write('%s(' % method_name) |
| args = [ |
| 'const %s& %s' % field_type_name for field in request.fields |
| for field_type_name in self.declare_field(field) |
| ] |
| with Indent(self, '%s) {' % ', '.join(args), '}'): |
| self.write('return %s(%s{%s});' % |
| (method_name, request_name, ', '.join([ |
| field_name for field in request.fields |
| for (_, field_name) in self.declare_field(field) |
| ]))) |
| self.write() |
| |
| if not reply: |
| return |
| |
| self.write('template<> COMPONENT_EXPORT(X11)') |
| self.write('std::unique_ptr<%s>' % reply_name) |
| sig = 'detail::ReadReply<%s>(ReadBuffer* buffer) {' % reply_name |
| with Indent(self, sig, '}'): |
| self.namespace = ['x11'] |
| self.write('auto& buf = *buffer;') |
| self.write('auto reply = std::make_unique<%s>();' % reply_name) |
| self.write() |
| self.is_read = True |
| self.copy_container(reply, '(*reply)') |
| self.write('Align(&buf, 4);') |
| offset = 'buf.offset < 32 ? 0 : buf.offset - 32' |
| self.write('DCHECK_EQ(%s, 4 * length);' % offset) |
| self.write() |
| self.write('return reply;') |
| self.write() |
| |
| def define_event(self, event, name): |
| self.namespace = ['x11'] |
| name = self.qualtype(event, name) |
| self.write('template <> COMPONENT_EXPORT(X11)') |
| self.write('void ReadEvent<%s>(' % name) |
| with Indent(self, ' %s* event_, ReadBuffer* buffer) {' % name, '}'): |
| self.write('auto& buf = *buffer;') |
| self.write() |
| self.is_read = True |
| self.copy_container(event, '(*event_)') |
| if event.is_ge_event: |
| self.write('Align(&buf, 4);') |
| self.write('DCHECK_EQ(buf.offset, 32 + 4 * length);') |
| else: |
| self.write('DCHECK_LE(buf.offset, 32ul);') |
| self.write() |
| |
| def define_error(self, error, name): |
| self.namespace = ['x11'] |
| name = self.qualtype(error, name) |
| with Indent(self, 'std::string %s::ToString() const {' % name, '}'): |
| self.write('std::stringstream ss_;') |
| self.write('ss_ << "%s{";' % name) |
| fields = [field for field in error.fields if field.visible] |
| for i, field in enumerate(fields): |
| terminator = '' if i == len(fields) - 1 else ' << ", "' |
| self.write('ss_ << ".%s = " << static_cast<uint64_t>(%s)%s;' % |
| (field.field_name, field.field_name, terminator)) |
| self.write('ss_ << "}";') |
| self.write('return ss_.str();') |
| self.write() |
| self.write('template <>') |
| self.write('void ReadError<%s>(' % name) |
| with Indent(self, ' %s* error_, ReadBuffer* buffer) {' % name, '}'): |
| self.write('auto& buf = *buffer;') |
| self.write() |
| self.is_read = True |
| self.copy_container(error, '(*error_)') |
| self.write('DCHECK_LE(buf.offset, 32ul);') |
| |
| def define_type(self, item, name): |
| if name in READ_SPECIAL: |
| self.read_special_container(item, name) |
| if name in WRITE_SPECIAL: |
| self.write_special_container(item, name) |
| if isinstance(item, self.xcbgen.xtypes.Request): |
| self.define_request(item) |
| elif item.is_event: |
| self.define_event(item, name) |
| elif isinstance(item, self.xcbgen.xtypes.Error): |
| self.define_error(item, name) |
| |
| def declare_type(self, item, name): |
| if item.is_union: |
| self.declare_union(item) |
| elif isinstance(item, self.xcbgen.xtypes.Request): |
| self.declare_request(item) |
| elif item.is_event: |
| self.declare_event(item, name) |
| elif isinstance(item, self.xcbgen.xtypes.Error): |
| self.declare_error(item, name) |
| elif item.is_container: |
| self.declare_container(item, name) |
| elif isinstance(item, self.xcbgen.xtypes.Enum): |
| self.declare_enum(item) |
| else: |
| assert item.is_simple |
| self.declare_simple(item, name) |
| |
| # Additional type information identifying the enum/mask is present in the |
| # XML data, but xcbgen doesn't make use of it: it only uses the underlying |
| # type, as it appears on the wire. We want additional type safety, so |
| # extract this information the from XML directly. |
| def resolve_element(self, xml_element, fields): |
| for child in xml_element: |
| if 'name' not in child.attrib: |
| if child.tag == 'case' or child.tag == 'bitcase': |
| self.resolve_element(child, fields) |
| continue |
| name = child.attrib['name'] |
| field = fields[name] |
| field.elt = child |
| enums = [ |
| child.attrib[attr] for attr in ['enum', 'mask'] |
| if attr in child.attrib |
| ] |
| if enums: |
| assert len(enums) == 1 |
| enum = enums[0] |
| field.enum = self.module.get_type(enum).name |
| self.enum_types[enum].add(field.type.name) |
| else: |
| field.enum = None |
| |
| def resolve_type(self, t, name): |
| renamed = tuple(self.rename_type(t, name)) |
| assert renamed[0] == 'x11' |
| assert t not in self.types[renamed] |
| self.types[renamed].append(t) |
| self.all_types[renamed] = t |
| |
| if isinstance(t, self.xcbgen.xtypes.Enum): |
| self.bitenums.append((t, name)) |
| |
| if not t.is_container: |
| return |
| |
| fields = { |
| field.field_name: field |
| for field in (self.switch_fields(t) if t.is_switch else t.fields) |
| } |
| |
| self.resolve_element(t.elt, fields) |
| |
| for field in fields.values(): |
| if field.field_name == 'sequence': |
| field.visible = True |
| field.parent = (t, name) |
| |
| if field.type.is_list: |
| # xcb uses void* in some places to represent arbitrary data. |
| field.type.is_ref_counted_memory = ( |
| not field.type.nmemb and field.field_type[0] == 'void') |
| |
| # |for_list| and |for_switch| may have already been set when |
| # processing other fields in this structure. |
| field.for_list = getattr(field, 'for_list', None) |
| field.for_switch = getattr(field, 'for_switch', None) |
| |
| for is_type, for_type in ((field.type.is_list, 'for_list'), |
| (field.type.is_switch, 'for_switch')): |
| if not is_type: |
| continue |
| expr = field.type.expr |
| field_name = expr.lenfield_name |
| if (expr.op in (None, 'calculate_len') |
| and field_name in fields): |
| setattr(fields[field_name], for_type, field) |
| |
| if field.type.is_switch or field.type.is_case_or_bitcase: |
| self.resolve_type(field.type, field.field_type) |
| |
| if isinstance(t, self.xcbgen.xtypes.Request) and t.reply: |
| self.resolve_type(t.reply, t.reply.name) |
| |
| # Multiple event names may map to the same underlying event. For these |
| # cases, we want to avoid duplicating the event structure. Instead, put |
| # all of these events under one structure with an additional opcode field |
| # to indicate the type of event. |
| def uniquify_events(self): |
| types = [] |
| events = set() |
| for name, t in self.module.all: |
| if not t.is_event or len(t.opcodes) == 1: |
| types.append((name, t)) |
| continue |
| |
| renamed = tuple(self.rename_type(t, name)) |
| self.all_types[renamed] = t |
| if t in events: |
| continue |
| events.add(t) |
| |
| names = [name[-1] for name in t.opcodes.keys()] |
| name = name[:-1] + (event_base_name(names), ) |
| types.append((name, t)) |
| |
| t.enum_opcodes = {} |
| for opname in t.opcodes: |
| opcode = t.opcodes[opname] |
| opname = opname[-1] |
| if opname.startswith(name[-1]): |
| opname = opname[len(name[-1]):] |
| t.enum_opcodes[opname] = opcode |
| self.module.all = types |
| |
| # Perform preprocessing like renaming, reordering, and adding additional |
| # data fields. |
| def resolve(self): |
| self.class_name = (adjust_type_name(self.module.namespace.ext_name) |
| if self.module.namespace.is_ext else 'XProto') |
| |
| self.uniquify_events() |
| |
| for name, t in self.module.all: |
| self.resolve_type(t, name) |
| |
| for enum, types in list(self.enum_types.items()): |
| if len(types) == 1: |
| self.enum_types[enum] = list(types)[0] |
| else: |
| del self.enum_types[enum] |
| |
| for t in self.types: |
| l = self.types[t] |
| if len(l) == 1: |
| continue |
| |
| # Allow simple types and enums to alias each other after renaming. |
| # This is done because we want strong typing even for simple types. |
| # If the types were not merged together, then a cast would be |
| # necessary to convert from eg. AtomEnum to AtomSimple. |
| assert len(l) == 2 |
| if isinstance(l[0], self.xcbgen.xtypes.Enum): |
| enum = l[0] |
| simple = l[1] |
| elif isinstance(l[1], self.xcbgen.xtypes.Enum): |
| enum = l[1] |
| simple = l[0] |
| assert simple.is_simple |
| assert enum and simple |
| self.replace_with_enum.add(t) |
| self.enum_types[enum.name] = simple.name |
| |
| for node in self.module.namespace.root: |
| if 'name' in node.attrib: |
| key = (node.tag, node.attrib['name']) |
| assert key not in self.module_names |
| self.module_names[key] = node |
| |
| # The order of types in xcbproto's xml files are inconsistent, so sort |
| # them in the order {type aliases, enums, xidunions, structs, |
| # requests/replies}. |
| def type_order_priority(module_type): |
| name, item = module_type |
| if item.is_simple: |
| return 2 if self.get_xidunion_element(name) else 0 |
| if isinstance(item, self.xcbgen.xtypes.Enum): |
| return 1 |
| if isinstance(item, self.xcbgen.xtypes.Request): |
| return 4 |
| return 3 |
| |
| # sort() is guaranteed to be stable. |
| self.module.all.sort(key=type_order_priority) |
| |
| def gen_header(self): |
| self.file = self.header_file |
| self.write_header() |
| include_guard = 'UI_GFX_X_GENERATED_PROTOS_%s_' % ( |
| self.header_file.name.split('/')[-1].upper().replace('.', '_')) |
| self.write('#ifndef ' + include_guard) |
| self.write('#define ' + include_guard) |
| self.write() |
| self.write('#include <array>') |
| self.write('#include <cstddef>') |
| self.write('#include <cstdint>') |
| self.write('#include <cstring>') |
| self.write('#include <vector>') |
| self.write() |
| self.write('#include "base/component_export.h"') |
| self.write('#include "base/memory/ref_counted_memory.h"') |
| self.write('#include "base/memory/scoped_refptr.h"') |
| self.write('#include "third_party/abseil-cpp/absl/types/optional.h"') |
| self.write('#include "base/files/scoped_file.h"') |
| self.write('#include "ui/gfx/x/ref_counted_fd.h"') |
| self.write('#include "ui/gfx/x/error.h"') |
| imports = set(self.module.direct_imports) |
| if self.module.namespace.is_ext: |
| imports.add(('xproto', 'xproto')) |
| for direct_import in sorted(list(imports)): |
| self.write('#include "%s.h"' % direct_import[-1]) |
| self.write() |
| self.write('namespace x11 {') |
| self.write() |
| self.write('class Connection;') |
| self.write() |
| self.write('template <typename Reply>') |
| self.write('struct Response;') |
| self.write() |
| self.write('template <typename Reply>') |
| self.write('class Future;') |
| self.write() |
| |
| self.namespace = ['x11'] |
| if not self.module.namespace.is_ext: |
| for (name, item) in self.module.all: |
| self.declare_type(item, name) |
| |
| name = self.class_name |
| with Indent(self, 'class COMPONENT_EXPORT(X11) %s {' % name, '};'): |
| self.namespace = ['x11', self.class_name] |
| self.write('public:') |
| if self.module.namespace.is_ext: |
| self.write('static constexpr unsigned major_version = %s;' % |
| self.module.namespace.major_version) |
| self.write('static constexpr unsigned minor_version = %s;' % |
| self.module.namespace.minor_version) |
| self.write() |
| self.write(name + '(Connection* connection,') |
| self.write(' const x11::QueryExtensionReply& info);') |
| self.write() |
| with Indent(self, 'uint8_t present() const {', '}'): |
| self.write('return info_.present;') |
| with Indent(self, 'uint8_t major_opcode() const {', '}'): |
| self.write('return info_.major_opcode;') |
| with Indent(self, 'uint8_t first_event() const {', '}'): |
| self.write('return info_.first_event;') |
| with Indent(self, 'uint8_t first_error() const {', '}'): |
| self.write('return info_.first_error;') |
| else: |
| self.write('explicit %s(Connection* connection);' % name) |
| self.write() |
| self.write( |
| 'Connection* connection() const { return connection_; }') |
| self.write() |
| for (name, item) in self.module.all: |
| if self.module.namespace.is_ext: |
| self.declare_type(item, name) |
| elif isinstance(item, self.xcbgen.xtypes.Request): |
| self.declare_request(item) |
| self.write('private:') |
| self.write('Connection* const connection_;') |
| if self.module.namespace.is_ext: |
| self.write('x11::QueryExtensionReply info_{};') |
| |
| self.write() |
| self.write('} // namespace x11') |
| self.write() |
| self.namespace = [] |
| |
| def binop(op, name): |
| self.write('inline constexpr %s operator%s(' % (name, op)) |
| with Indent(self, ' {0} l, {0} r)'.format(name) + ' {', '}'): |
| self.write('using T = std::underlying_type_t<%s>;' % name) |
| self.write('return static_cast<%s>(' % name) |
| self.write(' static_cast<T>(l) %s static_cast<T>(r));' % op) |
| self.write() |
| |
| for enum, name in self.bitenums: |
| name = self.qualtype(enum, name) |
| binop('|', name) |
| binop('&', name) |
| |
| self.write() |
| self.write('#endif // ' + include_guard) |
| |
| def gen_source(self): |
| self.file = self.source_file |
| self.write_header() |
| self.write('#include "%s.h"' % self.module.namespace.header) |
| self.write() |
| self.write('#include <unistd.h>') |
| self.write('#include <xcb/xcb.h>') |
| self.write('#include <xcb/xcbext.h>') |
| self.write() |
| self.write('#include "base/logging.h"') |
| self.write('#include "base/posix/eintr_wrapper.h"') |
| self.write('#include "ui/gfx/x/xproto_internal.h"') |
| self.write() |
| self.write('namespace x11 {') |
| self.write() |
| ctor = '%s::%s' % (self.class_name, self.class_name) |
| if self.module.namespace.is_ext: |
| self.write(ctor + '(Connection* connection,') |
| self.write(' const x11::QueryExtensionReply& info)') |
| self.write(' : connection_(connection), info_(info) {}') |
| else: |
| self.write(ctor + |
| '(Connection* connection) : connection_(connection) {}') |
| self.write() |
| for (name, item) in self.module.all: |
| self.define_type(item, name) |
| self.write('} // namespace x11') |
| |
| def parse(self): |
| self.module = self.xcbgen.state.Module(self.xml_filename, None) |
| self.module.register() |
| self.module.resolve() |
| |
| def generate(self): |
| self.gen_header() |
| self.gen_source() |
| |
| |
| class GenExtensionManager(FileWriter): |
| def __init__(self, gen_dir, genprotos): |
| FileWriter.__init__(self) |
| |
| self.gen_dir = gen_dir |
| self.genprotos = genprotos |
| self.extensions = [ |
| proto for proto in genprotos if proto.module.namespace.is_ext |
| ] |
| |
| def gen_header(self): |
| self.file = open(os.path.join(self.gen_dir, 'extension_manager.h'), |
| 'w') |
| self.write_header() |
| self.write('#ifndef UI_GFX_X_GENERATED_PROTOS_EXTENSION_MANAGER_H_') |
| self.write('#define UI_GFX_X_GENERATED_PROTOS_EXTENSION_MANAGER_H_') |
| self.write() |
| self.write('#include <memory>') |
| self.write() |
| self.write('#include "base/component_export.h"') |
| self.write() |
| self.write('namespace x11 {') |
| self.write() |
| self.write('class Connection;') |
| self.write() |
| for genproto in self.genprotos: |
| self.write('class %s;' % genproto.class_name) |
| self.write() |
| with Indent(self, 'class COMPONENT_EXPORT(X11) ExtensionManager {', |
| '};'): |
| self.write('public:') |
| self.write('ExtensionManager();') |
| self.write('~ExtensionManager();') |
| self.write() |
| for extension in self.extensions: |
| name = extension.proto |
| self.write('%s& %s() { return *%s_; }' % |
| (extension.class_name, name, name)) |
| self.write() |
| self.write('protected:') |
| self.write('void Init(Connection* conn);') |
| self.write() |
| self.write('private:') |
| for extension in self.extensions: |
| self.write('std::unique_ptr<%s> %s_;' % |
| (extension.class_name, extension.proto)) |
| self.write() |
| self.write('} // namespace x11') |
| self.write() |
| self.write('#endif // UI_GFX_X_GENERATED_PROTOS_EXTENSION_MANAGER_H_') |
| |
| def gen_source(self): |
| self.file = open(os.path.join(self.gen_dir, 'extension_manager.cc'), |
| 'w') |
| self.write_header() |
| self.write('#include "ui/gfx/x/extension_manager.h"') |
| self.write() |
| self.write('#include "ui/gfx/x/connection.h"') |
| self.write('#include "ui/gfx/x/xproto_internal.h"') |
| for genproto in self.genprotos: |
| self.write('#include "ui/gfx/x/%s.h"' % genproto.proto) |
| self.write() |
| self.write('namespace x11 {') |
| self.write() |
| init = 'void ExtensionManager::Init' |
| with Indent(self, init + '(Connection* conn) {', '}'): |
| for extension in self.extensions: |
| self.write( |
| 'auto %s_future = conn->QueryExtension("%s");' % |
| (extension.proto, extension.module.namespace.ext_xname)) |
| # Flush so all requests are sent before waiting on any replies. |
| self.write('conn->Flush();') |
| self.write() |
| for extension in self.extensions: |
| name = extension.proto |
| self.write( |
| '%s_ = MakeExtension<%s>(conn, std::move(%s_future));' % |
| (name, extension.class_name, name)) |
| self.write() |
| self.write('ExtensionManager::ExtensionManager() = default;') |
| self.write('ExtensionManager::~ExtensionManager() = default;') |
| self.write() |
| self.write('} // namespace x11') |
| |
| |
| class GenReadEvent(FileWriter): |
| def __init__(self, gen_dir, genprotos): |
| FileWriter.__init__(self) |
| |
| self.gen_dir = gen_dir |
| self.genprotos = genprotos |
| |
| self.events = [] |
| for proto in self.genprotos: |
| for name, item in proto.module.all: |
| if item.is_event: |
| self.events.append((name, item, proto)) |
| |
| def event_condition(self, event, typename, proto): |
| ext = 'conn->%s()' % proto.proto |
| |
| conds = [] |
| if not proto.module.namespace.is_ext: |
| # Core protocol event |
| opcode = 'evtype' |
| elif event.is_ge_event: |
| # GenericEvent extension event |
| conds.extend([ |
| 'evtype == GeGenericEvent::opcode', |
| '%s.present()' % ext, |
| 'ge->extension == %s.major_opcode()' % ext, |
| ]) |
| opcode = 'ge->event_type' |
| else: |
| # Extension event |
| opcode = 'evtype - %s.first_event()' % ext |
| conds.append('%s.present()' % ext) |
| |
| if len(event.opcodes) == 1: |
| conds.append('%s == %s::opcode' % (opcode, typename)) |
| else: |
| conds.append('(%s)' % ' || '.join([ |
| '%s == %s::%s' % (opcode, typename, opname) |
| for opname in event.enum_opcodes.keys() |
| ])) |
| |
| return ' && '.join(conds), opcode |
| |
| def gen_event(self, name, event, proto): |
| # We can't ever have a plain generic event. It must be a concrete |
| # event provided by an extension. |
| if name == ('xcb', 'GeGeneric'): |
| return |
| |
| name = [adjust_type_name(part) for part in name[1:]] |
| typename = '::'.join(name) + 'Event' |
| |
| cond, opcode = self.event_condition(event, typename, proto) |
| with Indent(self, 'if (%s) {' % cond, '}'): |
| self.write('event->type_id_ = %d;' % event.type_id) |
| with Indent(self, 'auto deleter_ = [](void* e) {', '};'): |
| self.write('if(e){delete reinterpret_cast<%s*>(e);}' % |
| typename) |
| self.write('auto* event_ = new %s;' % typename) |
| self.write('ReadEvent(event_, buffer);') |
| if len(event.opcodes) > 1: |
| self.write('{0} = static_cast<decltype({0})>({1});'.format( |
| 'event_->opcode', opcode)) |
| self.write('event->event_ = {event_, deleter_};') |
| self.write('event->window_ = event_->GetWindow();') |
| self.write('return;') |
| self.write() |
| |
| def gen_source(self): |
| self.file = open(os.path.join(self.gen_dir, 'read_event.cc'), 'w') |
| self.write_header() |
| self.write('#include "ui/gfx/x/event.h"') |
| self.write() |
| self.write('#include <xcb/xcb.h>') |
| self.write() |
| self.write('#include "ui/gfx/x/connection.h"') |
| self.write('#include "ui/gfx/x/xproto_types.h"') |
| for genproto in self.genprotos: |
| self.write('#include "ui/gfx/x/%s.h"' % genproto.proto) |
| self.write() |
| self.write('namespace x11 {') |
| self.write() |
| self.write('void ReadEvent(') |
| args = 'Event* event, Connection* conn, ReadBuffer* buffer' |
| with Indent(self, ' %s) {' % args, '}'): |
| self.write('auto* buf = buffer->data->data();') |
| cast = 'auto* %s = reinterpret_cast<const %s*>(buf);' |
| self.write(cast % ('ev', 'xcb_generic_event_t')) |
| self.write(cast % ('ge', 'xcb_ge_generic_event_t')) |
| self.write('auto evtype = ev->response_type & ~kSendEventMask;') |
| self.write() |
| for name, event, proto in self.events: |
| self.gen_event(name, event, proto) |
| self.write('// Leave `event` default-initialized.') |
| self.write() |
| self.write('} // namespace x11') |
| |
| |
| class GenReadError(FileWriter): |
| def __init__(self, gen_dir, genprotos, xcbgen): |
| FileWriter.__init__(self) |
| |
| self.gen_dir = gen_dir |
| self.genprotos = genprotos |
| self.xcbgen = xcbgen |
| |
| def get_errors_for_proto(self, proto): |
| errors = {} |
| for _, item in proto.module.all: |
| if isinstance(item, self.xcbgen.xtypes.Error): |
| for name in item.opcodes: |
| id = int(item.opcodes[name]) |
| if id < 0: |
| continue |
| name = [adjust_type_name(part) for part in name[1:]] |
| typename = '::'.join(name) + 'Error' |
| errors[id] = typename |
| return errors |
| |
| def gen_errors_for_proto(self, errors, proto): |
| if proto.module.namespace.is_ext: |
| cond = 'if (%s().present()) {' % proto.proto |
| first_error = '%s().first_error()' % proto.proto |
| else: |
| cond = '{' |
| first_error = '0' |
| with Indent(self, cond, '}'): |
| self.write('uint8_t first_error = %s;' % first_error) |
| for id, name in sorted(errors.items()): |
| with Indent(self, '{', '}'): |
| self.write('auto error_code = first_error + %d;' % id) |
| self.write('auto parse = MakeError<%s>;' % name) |
| self.write('add_parser(error_code, first_error, parse);') |
| self.write() |
| |
| def gen_init_error_parsers(self): |
| self.write('uint8_t first_errors[256];') |
| self.write('memset(first_errors, 0, sizeof(first_errors));') |
| self.write() |
| args = 'uint8_t error_code, uint8_t first_error, ErrorParser parser' |
| with Indent(self, 'auto add_parser = [&](%s) {' % args, '};'): |
| cond = ('!error_parsers_[error_code] || ' + |
| 'first_error > first_errors[error_code]') |
| with Indent(self, 'if (%s) {' % cond, '}'): |
| self.write('first_errors[error_code] = error_code;') |
| self.write('error_parsers_[error_code] = parser;') |
| self.write() |
| for proto in self.genprotos: |
| errors = self.get_errors_for_proto(proto) |
| if errors: |
| self.gen_errors_for_proto(errors, proto) |
| |
| def gen_source(self): |
| self.file = open(os.path.join(self.gen_dir, 'read_error.cc'), 'w') |
| self.write_header() |
| self.write('#include "ui/gfx/x/connection.h"') |
| self.write('#include "ui/gfx/x/error.h"') |
| self.write('#include "ui/gfx/x/xproto_internal.h"') |
| self.write() |
| for genproto in self.genprotos: |
| self.write('#include "ui/gfx/x/%s.h"' % genproto.proto) |
| self.write() |
| self.write('namespace x11 {') |
| self.write() |
| self.write('namespace {') |
| self.write() |
| self.write('template <typename T>') |
| sig = 'std::unique_ptr<Error> MakeError(Connection::RawError error_)' |
| with Indent(self, '%s {' % sig, '}'): |
| self.write('ReadBuffer buf(error_);') |
| self.write('auto error = std::make_unique<T>();') |
| self.write('ReadError(error.get(), &buf);') |
| self.write('return error;') |
| self.write() |
| self.write('} // namespace') |
| self.write() |
| with Indent(self, 'void Connection::InitErrorParsers() {', '}'): |
| self.gen_init_error_parsers() |
| |
| self.write() |
| self.write('} // namespace x11') |
| |
| |
| def main(): |
| parser = argparse.ArgumentParser() |
| parser.add_argument('xcbproto_dir', type=str) |
| parser.add_argument('gen_dir', type=str) |
| parser.add_argument('protos', type=str, nargs='*') |
| args = parser.parse_args() |
| |
| sys.path.insert(1, args.xcbproto_dir) |
| import xcbgen.xtypes |
| import xcbgen.state |
| |
| all_types = {} |
| proto_src_dir = os.path.join(args.xcbproto_dir, 'src') |
| genprotos = [ |
| GenXproto(proto, proto_src_dir, args.gen_dir, xcbgen, all_types) |
| for proto in args.protos |
| ] |
| for genproto in genprotos: |
| genproto.parse() |
| for genproto in genprotos: |
| genproto.resolve() |
| |
| # Give each event a unique type ID. This is used by Event to |
| # implement downcasting for events. |
| type_id = 1 |
| for proto in genprotos: |
| for _, item in proto.module.all: |
| if item.is_event: |
| item.type_id = type_id |
| type_id += 1 |
| |
| for genproto in genprotos: |
| genproto.generate() |
| |
| gen_extension_manager = GenExtensionManager(args.gen_dir, genprotos) |
| gen_extension_manager.gen_header() |
| gen_extension_manager.gen_source() |
| |
| GenReadEvent(args.gen_dir, genprotos).gen_source() |
| |
| GenReadError(args.gen_dir, genprotos, xcbgen).gen_source() |
| |
| return 0 |
| |
| |
| if __name__ == '__main__': |
| sys.exit(main()) |