gupnp-binding-tool-1.2 20 KB
Newer Older
1
#! /usr/bin/env python3
2
3

# Copyright (C) 2008 OpenedHand Ltd
4
# Copyright (C) 2008 Intel Corporation
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#
# This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free Software
# Foundation; either version 2, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along with
# this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
# St, Fifth Floor, Boston, MA 02110-1301 USA

Ross Burton's avatar
Ross Burton committed
19
20
# TODO:
# - finish code cleanup
21
# - currently allowedValueList is not used: could use it to turn
22
#   current char* value to an enum
23
24
# - could warn if values outside allowedValueRange are used in *_action_set()
# - add option to generate skeleton source code that uses the bindings?
25

26
27
28
29

import os.path
import re
import xml.etree.ElementTree as ET
30
31
32
from optparse import OptionParser


33
# upnp type:     (C type,      GType,                     type for g_value_get_*
34
#                                                         and g_value_set_*)
35
typemap = {
36
37
38
  'ui1':         ('guint ',    'G_TYPE_UINT',             'uint'),
  'ui2':         ('guint ',    'G_TYPE_UINT',             'uint'),
  'ui4':         ('guint ',    'G_TYPE_UINT',             'uint'),
39
  'i1':          ('gint',     'G_TYPE_INT',              'int'),
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
  'i2':          ('gint ',     'G_TYPE_INT',              'int'),
  'i4':          ('gint ',     'G_TYPE_INT',              'int'),
  'int':         ('gint ',     'G_TYPE_INT',              'int'),
  'r4':          ('gfloat ',   'G_TYPE_FLOAT',            'float'),
  'r8':          ('gdouble ',  'G_TYPE_DOUBLE',           'double'),
  'number':      ('gdouble ',  'G_TYPE_DOUBLE',           'double'),
  'fixed.14.4':  ('gdouble ',  'G_TYPE_DOUBLE',           'double'),
  'float':       ('gdouble ',  'G_TYPE_DOUBLE',           'double'),
  'char':        ('gchar ',    'G_TYPE_CHAR',             'char'),
  'string':      ('gchar *',   'G_TYPE_STRING',           'string'),
  'date':        ('gchar *',   'GUPNP_TYPE_DATE',         'string'),
  'dateTime':    ('gchar *',   'GUPNP_TYPE_DATE_TIME',    'string'),
  'dateTime.tz': ('gchar *',   'GUPNP_TYPE_DATE_TIME_TZ', 'string'),
  'time':        ('gchar *',   'GUPNP_TYPE_TIME',         'string'),
  'time.tz':     ('gchar *',   'GUPNP_TYPE_TIME_TZ',      'string'),
  'boolean':     ('gboolean ', 'G_TYPE_BOOLEAN',          'boolean'),
  'bin.base64':  ('gchar *',   'GUPNP_TYPE_BIN_BASE64',   'string'),
  'bin.hex':     ('gchar *',   'GUPNP_TYPE_BIN_HEX',      'string'),
  'uri':         ('gchar *',   'GUPNP_TYPE_URI',          'string'),
  'uuid':        ('gchar *',   'GUPNP_TYPE_UUID',         'string')
60
}
Ross Burton's avatar
Ross Burton committed
61
62


63
64
65
class Action:
    def __init__(self):
        self.name = None
66
        self.c_name = None
67
        self.c_prefixed_name = None
68
69
        self.in_args = []
        self.out_args = []
70
        self.notify_vars = []
71
72
73
74
75


class Argument:
    def __init__(self):
        self.name = None
76
        self.c_name = None
77
        self.direction = None
78
79
80
81
82
83
84
85
        self.related_var = None


class Variable:
    def __init__(self):
        self.name = None
        self.c_name = None
        self.c_prefixed_name = None
86
        self.ctype = None
87
88
        self.gtype = None
        self.get_function = None
89
90
91
        self.set_function = None
        self.notified = True
        self.dummy = False
92
93


94
def camelCaseToLowerCase(str):
95
96
97
98
    # http://www.djangosnippets.org/snippets/585/
    tmp = re.sub('(((?<=[a-z])[A-Z])|([A-Z](?![A-Z]|$)))', '_\\1', str)
    lower_case = tmp.lower().strip('_')
    return re.sub('[^a-z0-9]', '_', lower_case)
99
100


101
def getActions(action_elements, prefix, variables):
102
    """
103
    Parse the action element list into a list of Action objects.
104
    """
105

106
    actions = []
107

108
    for actionElement in action_elements:
109
110
111
        a = Action()
        actions.append(a)
        a.name = actionElement.find("{urn:schemas-upnp-org:service-1-0}name").text
112

113
114
        if a.name is None:
            raise Exception("No name found for action")
115
        a.c_name = camelCaseToLowerCase(a.name)
116
        a.c_prefixed_name = prefix + a.c_name
117

118
        for argElement in actionElement.findall("{urn:schemas-upnp-org:service-1-0}argumentList/{urn:schemas-upnp-org:service-1-0}argument"):
119
120
121
122
123
            arg = Argument()

            arg.name = argElement.find("{urn:schemas-upnp-org:service-1-0}name").text
            if arg.name is None:
                raise Exception("No name found for argument")
124
125
            arg.c_name = camelCaseToLowerCase(arg.name)

126
127
128
129
130
            var_name = argElement.find("{urn:schemas-upnp-org:service-1-0}relatedStateVariable").text
            for var in variables:
                if var.name == var_name:
                    arg.related_var = var
                    break
131
            if arg.related_var is None:
132
                raise Exception("%s: related state variable %s not found" % (arg.name, var_name))
133

134
            arg.direction = argElement.find("{urn:schemas-upnp-org:service-1-0}direction").text
135

136
137
138
139
            if arg.direction == "in":
                    a.in_args.append(arg)
            else:
                    a.out_args.append(arg)
140

141
142
143
    return actions


144
def getVariables(var_elements, prefix):
145
    """
146
    Parse the state variable element list into a list of Variable objects.
147
    """
148

149
    variables = []
150

151
    for varElement in var_elements:
152
153
        var = Variable()
        variables.append(var)
154

155
        var.name = varElement.find("{urn:schemas-upnp-org:service-1-0}name").text
156
157
158
        if var.name.startswith("A_ARG_TYPE_"):
            # dummy state variable, only there to give type info to getter/setter
            var.dummy = True
159
160

        var.c_name = camelCaseToLowerCase(var.name)
161
        var.c_prefixed_name = prefix + var.c_name
162

163
164
        if (varElement.get("sendEvents") == "no"):
            var.notified = False
165

166
        dataType = varElement.find("{urn:schemas-upnp-org:service-1-0}dataType").text
167
        if dataType not in typemap:
168
            raise Exception("Unknown dataType %s" % dataType)
169
        (var.ctype, var.gtype, g_value_type) = typemap[dataType]
170
171
        var.get_function = "g_value_get_" + g_value_type
        var.set_function = "g_value_set_" + g_value_type
172

173
174
175
    return variables


176
def printClientSyncActionBinding(a):
177
    indent = (2 + len(a.c_prefixed_name)) * " "
178

179
180
    print("static inline gboolean")
    print("%s (GUPnPServiceProxy *proxy," % a.c_prefixed_name)
181

182
    for arg in a.in_args:
183
        print("%sconst %sin_%s," % (indent, arg.related_var.ctype, arg.c_name))
184

185
    for arg in a.out_args:
186
        print("%s%s*out_%s," % (indent, arg.related_var.ctype, arg.c_name))
187

188
    print("%sGError **error)" % indent)
189

190
    print("{")
191

192
193
    print("  return gupnp_service_proxy_send_action")
    print("    (proxy, \"%s\", error," % a.name)
194

195
    for arg in a.in_args:
196
197
        print("     \"%s\", %s, in_%s," % (arg.name, arg.related_var.gtype, arg.c_name))
    print("     NULL,")
198

199
    for arg in a.out_args:
200
201
        print("     \"%s\", %s, out_%s," % (arg.name, arg.related_var.gtype, arg.c_name))
    print("     NULL);")
202

203
    print("}\n")
204
205


206
def printClientAsyncActionBinding(a):
207
    # User-callback prototype
208
    indent = (24 + len(a.c_prefixed_name)) * " "
209
    print("typedef void (*%s_reply) (GUPnPServiceProxy *proxy," % a.c_prefixed_name)
210
    for arg in a.out_args:
211
212
213
214
        print("%sconst %sout_%s," % (indent, arg.related_var.ctype, arg.c_name))
    print("%sGError *error," % indent)
    print("%sgpointer userdata);" % indent)
    print("")
215
216

    # Generated async callback handler, calls user-provided callback
217
    indent = (30 + len(a.c_prefixed_name)) * " "
218
219
220
221
222
223
    print("static void _%s_async_callback (GUPnPServiceProxy *proxy," % a.c_prefixed_name)
    print("%sGUPnPServiceProxyAction *action," % indent)
    print("%sgpointer user_data)" % indent)
    print("{")
    print("  GUPnPAsyncData *cbdata;")
    print("  GError *error = NULL;")
224
    for arg in a.out_args:
225
226
227
228
229
        print("  %s%s;" % (arg.related_var.ctype, arg.c_name))
    print("")
    print("  cbdata = (GUPnPAsyncData *) user_data;")
    print("  gupnp_service_proxy_end_action")
    print("    (proxy, action, &error,")
230
    for arg in a.out_args:
231
232
233
234
        print("     \"%s\", %s, &%s," % (arg.name, arg.related_var.gtype, arg.c_name))
    print("     NULL);")
    print("  ((%s_reply)cbdata->cb)" % a.c_prefixed_name)
    print("    (proxy,")
235
    for arg in a.out_args:
236
237
238
239
240
241
        print("     %s," % arg.c_name)
    print("     error, cbdata->userdata);")
    print("")
    print("  g_slice_free1 (sizeof (*cbdata), cbdata);")
    print("}")
    print("")
242
243

    # Entry point
244
    indent = (8 + len(a.c_prefixed_name)) * " "
245
246
    print("static inline GUPnPServiceProxyAction *")
    print("%s_async (GUPnPServiceProxy *proxy," % a.c_prefixed_name)
247
    for arg in a.in_args:
248
249
250
251
252
253
254
255
256
257
258
259
260
        print("%sconst %sin_%s," % (indent, arg.related_var.ctype, arg.c_name))
    print("%s%s_reply callback," % (indent, a.c_prefixed_name))
    print("%sgpointer userdata)" % indent)
    print("{")
    print("  GUPnPServiceProxyAction* action;")
    print("  GUPnPAsyncData *cbdata;")
    print("")
    print("  cbdata = (GUPnPAsyncData *) g_slice_alloc (sizeof (*cbdata));")
    print("  cbdata->cb = G_CALLBACK (callback);")
    print("  cbdata->userdata = userdata;")
    print("  action = gupnp_service_proxy_begin_action")
    print("    (proxy, \"%s\"," % a.name)
    print("     _%s_async_callback, cbdata," % a.c_prefixed_name)
261
    for arg in a.in_args:
262
263
264
265
266
        print("     \"%s\", %s, in_%s," % (arg.name, arg.related_var.gtype, arg.c_name))
    print("     NULL);")
    print("")
    print("  return action;")
    print("}")
267

268
269

def printClientVariableNotifyBinding(v):
270
271
    ctype = re.sub("^gchar", "const gchar", v.ctype)

272
    # callback prototype
273
    indent = (22 + len(v.c_prefixed_name)) * " "
274
275
276
277
278
    print("typedef void")
    print("(*%s_changed_callback) (GUPnPServiceProxy *proxy," % v.c_prefixed_name)
    print("%s%s%s," % (indent, ctype, v.c_name))
    print("%sgpointer userdata);" % indent)
    print("")
279

280
    # private callback
281
    indent = (20 + len(v.c_prefixed_name)) * " "
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
    print("static void")
    print("_%s_changed_callback (GUPnPServiceProxy *proxy," % v.c_prefixed_name)
    print("%sconst gchar *variable," % indent)
    print("%sGValue *value," % indent)
    print("%sgpointer userdata)" % indent)
    print("{")
    print("  GUPnPAsyncData *cbdata;")
    print("  %s%s;" % (ctype, v.c_name))
    print("")
    print("  cbdata = (GUPnPAsyncData *) userdata;")
    print("  %s = %s (value);" % (v.c_name, v.get_function))
    print("  ((%s_changed_callback)cbdata->cb)" % v.c_prefixed_name)
    print("    (proxy,")
    print("     %s," % v.c_name)
    print("     cbdata->userdata);")
    print("")
    print("  g_slice_free1 (sizeof (*cbdata), cbdata);")
    print("}")
    print("")
301

302
    # public notify_add function
303
    indent = (13 + len(v.c_prefixed_name)) * " "
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
    print("static inline gboolean")
    print("%s_add_notify (GUPnPServiceProxy *proxy," % v.c_prefixed_name)
    print("%s%s_changed_callback callback," % (indent, v.c_prefixed_name))
    print("%sgpointer userdata)" % indent)
    print("{")
    print("  GUPnPAsyncData *cbdata;")
    print("")
    print("  cbdata = (GUPnPAsyncData *) g_slice_alloc (sizeof (*cbdata));")
    print("  cbdata->cb = G_CALLBACK (callback);")
    print("  cbdata->userdata = userdata;")
    print("")
    print("  return gupnp_service_proxy_add_notify")
    print("    (proxy,")
    print("     \"%s\"," % v.name)
    print("     %s," % v.gtype)
    print("     _%s_changed_callback," % v.c_prefixed_name)
    print("     cbdata);")
    print("}")
322

323
324
325

def printServerVariableQueryBinding(v):
    # User callback proto
326
    indent = (28 + len(v.ctype)+len(v.c_prefixed_name)) * " "
327
328
329
    print("typedef %s(*%s_query_callback) (GUPnPService *service," % (v.ctype, v.c_prefixed_name))
    print("%sgpointer userdata);" % indent)
    print("")
330
331

    indent = (12 + len(v.c_prefixed_name)) * " "
332
333
334
335
336
337
338
339
340
    print("static void")
    print("_%s_query_cb (GUPnPService *service," % v.c_prefixed_name)
    print("%sgchar *variable," % indent)
    print("%sGValue *value," % indent)
    print("%sgpointer userdata)" % indent)
    print("{")
    print("  GUPnPAsyncData *cbdata;")
    print("  %s%s;" % (v.ctype, v.c_name))
    print("")
341
342

    indent = (36 + len(v.c_name) + len(v.c_prefixed_name)) * " "
343
344
345
346
347
348
349
    print("  cbdata = (GUPnPAsyncData *) userdata;")
    print("  %s = ((%s_query_callback)cbdata->cb) (service," % (v.c_name, v.c_prefixed_name))
    print("%scbdata->userdata);" % indent)
    print("  g_value_init (value, %s);" % v.gtype)
    print("  %s (value, %s);" % (v.set_function, v.c_name))
    print("}")
    print("")
350
351

    indent = (16 + len(v.c_prefixed_name)) * " "
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
    print("gulong")
    print("%s_query_connect (GUPnPService *service," % v.c_prefixed_name)
    print("%s%s_query_callback callback," % (indent, v.c_prefixed_name))
    print("%sgpointer userdata)" % indent)
    print("{")
    print("  GUPnPAsyncData *cbdata;")
    print("")
    print("  cbdata = (GUPnPAsyncData *) g_slice_alloc (sizeof (*cbdata));")
    print("  cbdata->cb = G_CALLBACK (callback);")
    print("  cbdata->userdata = userdata;")
    print("")
    print("  return g_signal_connect_data (service,")
    print("                                \"query-variable::%s\"," % v.name)
    print("                                G_CALLBACK (_%s_query_cb)," % v.c_prefixed_name)
    print("                                cbdata,")
    print("                                _free_cb_data,")
    print("                                (GConnectFlags) 0);")
    print("}")
    print("")
371
372
373
374


def printServerActionBinding(a):
    # getter and setter func for GUPnPServiceAction
375
376
    indent = (13 + len(a.c_prefixed_name)) * " "
    if len(a.in_args) > 0:
377
378
        print("static inline void")
        print("%s_action_get (GUPnPServiceAction *action," % (a.c_prefixed_name))
379
        for arg in a.in_args[:-1]:
380
381
382
383
            print("%s%s*in_%s," % (indent, arg.related_var.ctype, arg.c_name))
        print("%s%s*in_%s)" % (indent, a.in_args[-1].related_var.ctype, a.in_args[-1].c_name))
        print("{")
        print("  gupnp_service_action_get (action,")
384
        for arg in a.in_args:
385
386
387
388
            print("                            \"%s\", %s, in_%s," % (arg.name, arg.related_var.gtype, arg.c_name))
        print("                            NULL);")
        print("}")
        print("")
389
    if len(a.out_args) > 0:
390
391
        print("static inline void")
        print("%s_action_set (GUPnPServiceAction *action," % (a.c_prefixed_name))
392
        for arg in a.out_args[:-1]:
393
394
395
396
            print("%sconst %sout_%s," % (indent, arg.related_var.ctype, arg.c_name))
        print("%sconst %sout_%s)" % (indent, a.out_args[-1].related_var.ctype, a.out_args[-1].c_name))
        print("{")
        print("  gupnp_service_action_set (action,")
397
        for arg in a.out_args:
398
399
400
401
            print("                            \"%s\", %s, out_%s," % (arg.name, arg.related_var.gtype, arg.c_name))
        print("                            NULL);")
        print("}")
        print("")
402
403

    indent = (17 + len(a.c_prefixed_name)) * " "
404
405
406
407
408
409
410
411
412
413
414
    print("static inline gulong")
    print("%s_action_connect (GUPnPService *service," % a.c_prefixed_name)
    print("%sGCallback callback," % indent)
    print("%sgpointer userdata)" % indent)
    print("{")
    print("  return g_signal_connect (service,")
    print("                           \"action-invoked::%s\"," % a.name)
    print("                           callback,")
    print("                           userdata);")
    print("}")
    print("")
415

416

417
def PrintServerVariableNotifyBinding(v):
418
    indent = (18 + len(v.c_prefixed_name)) * " "
419
420
    print("void")
    print("%s_variable_notify (GUPnPService *service," % v.c_prefixed_name)
421
    print("%sconst %s%s)" % (indent, v.ctype, v.c_name))
422
423
424
425
426
427
    print("{")
    print("  gupnp_service_notify (service,")
    print("                        \"%s\", %s, %s," % (v.name, v.gtype, v.c_name))
    print("                        NULL);")
    print("}")
    print("")
428

429

430
def parseSCPD(scpd, prefix):
431
432
433
    """
    Parse the scpd file into lists of Action and Variable objects.
    """
434
    if prefix != "":
435
        prefix = prefix.lower() + "_"
436

437
438
    action_elements = scpd.findall("{urn:schemas-upnp-org:service-1-0}actionList/{urn:schemas-upnp-org:service-1-0}action")
    var_elements = scpd.findall("{urn:schemas-upnp-org:service-1-0}serviceStateTable/{urn:schemas-upnp-org:service-1-0}stateVariable")
439

440
441
    variables = getVariables(var_elements, prefix)
    actions = getActions(action_elements, prefix, variables)
442

443
444
445
    return (actions, variables)


446
447
def printClientBindings(binding_name, actions, variables):
    define = "GUPNP_GENERATED_CLIENT_BINDING_%s" % binding_name
448

449
450
451
452
453
454
455
456
457
458
459
460
461
    print("/* Generated by gupnp-binding-tool */")
    print("")
    print("#include <libgupnp/gupnp.h>")
    print("")
    print("#ifndef %s" % define)
    print("#define %s" % define)
    print("")
    print("G_BEGIN_DECLS")

    print("\n#ifndef __GUPNPASYNCDATA_TYPE__")
    print("#define __GUPNPASYNCDATA_TYPE__")
    print("typedef struct {GCallback cb; gpointer userdata; } GUPnPAsyncData;")
    print("#endif")
462

463
    for a in actions:
464
        print("\n/* action %s */\n" % a.name)
465
466
467
468
        printClientSyncActionBinding(a)
        printClientAsyncActionBinding(a)
    for v in variables:
        if (not v.dummy) and v.notified:
469
            print("\n/* state variable %s */\n" % v.name)
470
            printClientVariableNotifyBinding(v)
471

472
473
474
475
    print("")
    print("G_END_DECLS")
    print("")
    print("#endif /* %s */" % define)
476
477
478
479


def printServerBindings(binding_name, actions, variables):
    define = "GUPNP_GENERATED_CLIENT_BINDING_%s" % binding_name
480

481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
    print("/* Generated by gupnp-binding-tool */")
    print("")
    print("#include <libgupnp/gupnp.h>")
    print("")
    print("#ifndef %s" % define)
    print("#define %s" % define)
    print("")
    print("G_BEGIN_DECLS")

    print("\n#ifndef __GUPNPASYNCDATA_TYPE__")
    print("#define __GUPNPASYNCDATA_TYPE__")
    print("typedef struct {GCallback cb; gpointer userdata; } GUPnPAsyncData;")
    print("#endif")

    print("static void")
    print("_free_cb_data (gpointer data, GClosure *closure)")
    print("{")
    print("  GUPnPAsyncData *cbdata = (GUPnPAsyncData *) data;")
    print("  g_slice_free1 (sizeof (*cbdata), cbdata);")
    print("}")
    print("")
502

503
    for a in actions:
504
        print("\n/* action %s */\n" % a.name)
505
        printServerActionBinding(a)
506
    for v in variables:
507
        if not v.dummy:
508
            print("\n/* state variable %s */\n" % v.name)
509
510
511
            printServerVariableQueryBinding(v)
            if v.notified:
                PrintServerVariableNotifyBinding(v)
512

513
514
515
516
    print("")
    print("G_END_DECLS")
    print("")
    print("#endif /* %s */" % define)
517

518

519
def main():
520
    parser = OptionParser("Usage: %prog [options] service-filename")
521
    parser.add_option("-p", "--prefix", dest="prefix",
522
523
                      metavar="PREFIX", default="",
                      help="set prefix for generated function names")
524
    parser.add_option("-m", "--mode", type="choice", dest="mode",
525
526
527
                      metavar="MODE", default="client",
                      choices=("client", "server"),
                      help="select generation mode, 'client' or 'server'")
528
529

    (options, args) = parser.parse_args()
530
531
    if len(args) != 1:
        parser.error("Expected 1 argument, got %d" % len(args))
532

533
534
535
536
537
538
    # get a name for header ifdef
    if options.prefix == "":
        base = re.sub("[^a-zA-Z0-9]", "_", os.path.basename(args[0]))
        binding_name = base.upper()
    else:
        binding_name = options.prefix.upper()
539

540
541
    # parse scpd file, extract action list and state variable list
    scpd = ET.parse(args[0])
542
    (actions, variables) = parseSCPD(scpd, options.prefix)
543
    if (len(actions) == 0 and len(variables) == 0):
544
545
        raise Exception("No actions or variables found in document")

546
    # generate bindings
547
    if (options.mode == "client"):
548
        printClientBindings(binding_name, actions, variables)
549
    else:
550
551
        printServerBindings(binding_name, actions, variables)

552

553
554
if __name__ == "__main__":
    main()