OpenCores
URL https://opencores.org/ocsvn/openrisc/openrisc/trunk

Subversion Repositories openrisc

[/] [openrisc/] [trunk/] [rtos/] [ecos-2.0/] [packages/] [hal/] [synth/] [arch/] [v2_0/] [host/] [ecosynth.tcl] - Blame information for rev 565

Go to most recent revision | Details | Compare with Previous | View Log

Line No. Rev Author Line
1 27 unneback
# {{{  Banner                                                   
2
 
3
# ============================================================================
4
# 
5
#      ecosynth.tcl
6
# 
7
#      The eCos synthetic target I/O auxiliary
8
# 
9
# ============================================================================
10
# ####COPYRIGHTBEGIN####
11
#                                                                           
12
#  ----------------------------------------------------------------------------
13
#  Copyright (C) 2002 Bart Veer
14
# 
15
#  This file is part of the eCos host tools.
16
# 
17
#  This program is free software; you can redistribute it and/or modify it 
18
#  under the terms of the GNU General Public License as published by the Free 
19
#  Software Foundation; either version 2 of the License, or (at your option) 
20
#  any later version.
21
#  
22
#  This program is distributed in the hope that it will be useful, but WITHOUT 
23
#  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 
24
#  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for 
25
#  more details.
26
#  
27
#  You should have received a copy of the GNU General Public License along with
28
#  this program; if not, write to the Free Software Foundation, Inc., 
29
#  59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
30
# 
31
#  ----------------------------------------------------------------------------
32
#                                                                           
33
# ####COPYRIGHTEND####
34
# ============================================================================
35
# #####DESCRIPTIONBEGIN####
36
# 
37
#  Author(s):   bartv
38
#  Contact(s):  bartv
39
#  Date:        2002/08/05
40
#  Version:     0.01
41
#  Description:
42
#      The main script for the eCos synthetic target auxiliary. This should
43
#      only ever be invoked from inside ecosynth.
44
# 
45
# ####DESCRIPTIONEND####
46
# ============================================================================
47
 
48
# }}}
49
 
50
# {{{  Overview                                                 
51
 
52
# When an eCos synthetic target application runs it will usually
53
# fork/execve an auxiliary program, ecosynth, to provide certain
54
# I/O functionality. This happens as part of hardware initialization.
55
#
56
# The ecosynth executable in turn runs this ecosynth.tcl script which
57
# either does most of the real work or delegates it to other scripts.
58
# Those other scripts may in turn exec other programs to perform any
59
# I/O operations that cannot easily be done at the Tcl level. For
60
# example performing low-level ethernet operations generally requires
61
# working at the C level or equivalent, so a separate executable would
62
# be appropriate. The ecosynth executable will transfer control to
63
# this script after the appinit() call, which should have performed
64
# certain initialization steps.
65
#
66
#  1) the Tcl interpreter will be fully initialized.
67
#
68
#  2) usually Tk will have been loaded and initialized as well. This
69
#     can be suppressed using a command-line option -nw.
70
#
71
#  3) there will be a namespace synth:: for use by ecosynth.tcl
72
#     Auxiliary scripts are expected to use their own namespace
73
#     where possible.
74
#
75
#  4) there will be two channels synth::_channel_from_app and
76
#     synth::_channel_to_app. These correspond to a pipe between
77
#     the eCos application and the auxiliary. The application will
78
#     send requests using this pipe and expect replies. I/O
79
#     operations are always initiated by a request from the
80
#     application, but the auxiliary can raise an interrupt via
81
#     the SIGIO signal.
82
#
83
#     Other standard channels stdin, stdout and stderr will have
84
#     been inherited from the eCos application.
85
#
86
#  5) some Tcl commands implemented in C will have been added to the
87
#     interpreter. The most notable is synth::_send_SIGIO, used to
88
#     raise an interrupt within the application. 
89
#
90
#  6) similarly some variables will have been added to the interpreter.
91
#
92
#
93
# Configuring everything can get somewhat complicated. It is the eCos
94
# application that knows what I/O facilities within the auxiliary it
95
# wants to access, but it may not know all the details. The eCos
96
# component architecture makes things a bit more complicated still,
97
# generic code such as this ecosynth.tcl script has no way of knowing
98
# what I/O facilities might be provided by some package or other.
99
#
100
# For example, a target-side ethernet driver may want to send outgoing
101
# ethernet packets to some script or program on the host and receive
102
# incoming ethernet packets. However it does not necessarily know what
103
# the host-side should do with those ethernet packets, e.g. use a
104
# spare Linux ethernet device, use the Linux kernel's ethertap
105
# facility, ... Although that kind of information could be handled by
106
# target-side configury, host-side configuration files will often be
107
# more appropriate. Specifically it would allow a single eCos
108
# synthetic target application to run in a variety of environments,
109
# using different ways to provide the I/O, with no need to do any
110
# reconfiguring or rebuilding of the target side.
111
#
112
#
113
# The basic approach that is taken is:
114
#
115
#   1) the eCos application tells the auxiliary what I/O facilities
116
#      it is interested in. This should happen as a result
117
#      of static constructors or of device driver initialization
118
#      routines. The application has no control over the implementation
119
#      of the I/O facilities, it just expects something on the other
120
#      end to respond sensibly to requests.
121
#
122
#      For example, a synthetic target ethernet driver will supply
123
#      an initialization routine via its NETDEVTAB_ENTRY. This
124
#      routine will send a request to the auxiliary asking for a
125
#      device of type ethernet, id "eth0", provided by
126
#      CYGPKG_DEVS_ETH_ECOSYNTH, version current. The auxiliary will
127
#      now attempt to load a suitable Tcl script ethernet.tcl from a
128
#      location determined using the package name and version.
129
#      
130
#   2) there is a primary target definition file which can be
131
#      specified as the final argument on the command line, with
132
#      "default" being the default value. The code will look for
133
#      this file with or without a .tdf extension, first in the
134
#      current directory, then in ~/.ecos/synth/. This file is
135
#      actually a Tcl script that gets executed in the current
136
#      interpreter. However typically it will only contain
137
#      entries such as:
138
#
139
#          synth_device eth0 {
140
#              ...
141
#          }
142
#
143
#   3) There are additional optional configuration files
144
#      ~/.ecos/synth/initrc.tcl and ~/.ecos/synth/mainrc.tcl which can
145
#      be used for further customization. initrc.tcl will get run
146
#      early on, mainrc.tcl will get run once initialization is
147
#      complete. Specifically the target-side code will send an
148
#      appropriate request after invoking all the static constructors.
149
#      At this time the auxiliary will run mainrc.tcl, and in addition
150
#      it may issue warnings about unused arguments etc.
151
#
152
#   4) there may also be separate configuration files for GUI
153
#      preferences etc. These are distinct from the initrc and
154
#      mainrc files in that they are generated rather than
155
#      hand-written.
156
 
157
# }}}
158
# {{{  Basic initialization                                     
159
 
160
# ----------------------------------------------------------------------------
161
# There should be two channels corresponding to the pipe between the
162
# eCos application and the auxiliary. These should be configured
163
# appropriately. If either channel does not exist then that is a
164
# very good indication that the system is not properly initialized,
165
# i.e. that this script is not being run by the ecosynth executable
166
# and by implication that no eCos application is involved.
167
 
168
if {([info exists synth::_channel_to_app] == 0) ||
169
    ([info exists synth::_channel_from_app] == 0) ||
170
    ([info exists synth::_ecosynth_version] == 0) ||
171
    ([info exists synth::_ppid] == 0) ||
172
    ([info exists synth::_ecosynth_repository] == 0) ||
173
    ([info exists synth::_ecosynth_libexecdir] == 0) ||
174
    ([info exists synth::_ecosynth_package_dir] == 0) ||
175
    ([info exists synth::_ecosynth_package_version] == 0) ||
176
    ([info exists synth::_ecosynth_package_install] == 0) ||
177
    ([info commands synth::_send_SIGIO] == "") ||
178
    ([info commands synth::_send_SIGKILL] == "") } {
179
 
180
    puts stderr "ecosynth.tcl: the current interpreter has not been properly initialized."
181
    puts stderr "    This script should only be invoked by the ecosynth executable when"
182
    puts stderr "    an eCos synthetic target application is run."
183
    exit 1
184
}
185
 
186
# Is the script currently being executed the most recent version?
187
# This check should only happen if an environment variable
188
# ECOSYNTH_DEVEL is set, because the installed tools may have come
189
# from somewhere other than the current repository.
190
if { [info exists ::env(ECOSYNTH_DEVEL)] } {
191
    set _orig_name [file join $synth::_ecosynth_repository $synth::_ecosynth_package_dir $synth::_ecosynth_package_version \
192
            "host" [file tail [info script]]]
193
    if { [file exists $_orig_name] && [file readable $_orig_name] && ($_orig_name != [info script]) } {
194
        if { [file mtime $_orig_name] >= [file mtime [info script]] } {
195
            puts "$_orig_name is more recent than install: executing that."
196
            source $_orig_name
197
            return
198
        }
199
    }
200
    unset _orig_name
201
}
202
 
203
fconfigure $synth::_channel_to_app   -buffering none
204
fconfigure $synth::_channel_from_app -encoding binary
205
fconfigure $synth::_channel_to_app   -encoding binary
206
fconfigure $synth::_channel_from_app -translation binary
207
fconfigure $synth::_channel_to_app   -translation binary
208
 
209
# Define additional globals and procedures inside the synth:: namespace.
210
# Variables and functions that begin with an _ are considered internal
211
# and should not be used directly.
212
namespace eval synth {
213
 
214
    # Unfortunately the name of the eCos executable is lost at this stage.
215
    # Within the eCos application it was held in argv[0], but that has been
216
    # overridden with the name of the auxiliary. However we have access to the
217
    # parent process id so we can use /proc to get the required information.
218
    variable ecos_appname ""
219
    catch {
220
        set synth::ecos_appname [file readlink "/proc/[set synth::_ppid]/exe"]
221
        set synth::ecos_appname [file tail $synth::ecos_appname]
222
    }
223
 
224
    # The install location can be determined from the script name.
225
    # This is used for e.g. loading bitmaps, even if ECOSYNTH_DEVEL
226
    # is set, because some of the files may be generated.
227
    # ECOSYNTH_DEVEL only affects Tcl scripts.
228
    variable install_dir [file join $synth::_ecosynth_libexecdir "ecos" $synth::_ecosynth_package_install]
229
 
230
    # Is the eCos application still running? This is worth keeping
231
    # track of so that send_reply and raise_interrupt do not try to
232
    # interact with a program that is no longer running.
233
    variable ecos_running 1
234
 
235
    # This variable is used to enter the event loop
236
    variable _ecosynth_exit 0
237
 
238
    # Is GUI mode enabled?
239
    variable flag_gui [expr { "" != [info commands "tk"] } ]
240
 
241
    # When running in GUI mode the GUI should stay up even after the application
242
    # has exited, so that the user can take a good look around. When running in
243
    # non-GUI mode this program should exit as soon it has finished cleaning up.
244
    variable flag_immediate_exit    [expr { 0 == $synth::flag_gui} ]
245
 
246
    # Is the GUI ready to accept output?
247
    variable flag_gui_ready 0
248
 
249
    # Flags and variables related to command-line arguments
250
    variable flag_help          0
251
    variable flag_keep_going    0
252
    variable flag_no_rc         0
253
    variable flag_verbose       0
254
    variable flag_debug         0
255
    variable logfile            ""
256
    variable target_definition  ""
257
    variable geometry           "<none>"
258
}
259
 
260
# }}}
261
# {{{  Hooks & atexit support                                   
262
 
263
# ----------------------------------------------------------------------------
264
# A lot of the flexibility of ecosynth is provided by hooks. Device scripts
265
# and, more importantly, the per-user initrc and mainrc scripts can install
266
# hooks that get called when an event occurs, for example when the eCos
267
# applications attempts to transmit an ethernet packet.
268
#
269
# A special hook is used to implement atexit handling. This involves redefining
270
# the "exit" command so that it will invoke the appropriate hooks first.
271
 
272
namespace eval synth {
273
    # All hooks are held in an array, indexed by the hook name, with each
274
    # array entry being a list of functions to be invoked.
275
    array set _hooks [list]
276
 
277
    proc hook_define { name } {
278
        if { [info exists synth::_hooks($name)] } {
279
            synth::report_error "Attempt to create hook $name which already exists.\n"
280
        } else {
281
            set synth::_hooks($name) [list]
282
        }
283
    }
284
 
285
    proc hook_defined { name } {
286
        return [info exists synth::_hooks($name)]
287
    }
288
 
289
    proc hook_add { name function } {
290
        if { ! [info exists synth::_hooks($name)] } {
291
            synth::report_error "Attempt to attach a function to an unknown hook $name\n"
292
            set synth::_hooks($name) [list]
293
        }
294
        lappend synth::_hooks($name) $function
295
    }
296
 
297
    proc hook_call { name args } {
298
        if { ! [info exists synth::_hooks($name) ] } {
299
            synth::report_error "Attempt to invoke unknown hook $name\n"
300
        } else {
301
            foreach function $synth::_hooks($name) {
302
                $function $args
303
            }
304
        }
305
    }
306
 
307
    # Define an initial set of hooks
308
    synth::hook_define "exit"                  ;# The auxiliary is exiting
309
    synth::hook_define "ecos_exit"             ;# The eCos application has exited
310
    synth::hook_define "ecos_initialized"      ;# eCos static constructors have run
311
    synth::hook_define "help"                  ;# --help
312
}
313
 
314
# Rename the builtin exit command so that it can still be accessed.
315
rename exit _hook_real_exit
316
 
317
# And define a replacement for exit which will invoke the appropriate
318
# hook. Care has to be taken in case of recursive exit calls, each
319
# hook function is only called once.
320
 
321
proc exit { { code 0 } } {
322
    while { [llength $synth::_hooks(exit)] > 0 } {
323
        set handler [lindex $synth::_hooks(exit) end]
324
        set synth::_hooks(exit) [lrange $synth::_hooks(exit) 0 end-1]
325
 
326
        # For now assume no errors - it is not clear what could be done
327
        # about them anyway.
328
        catch { eval $handler [list]}
329
    }
330
    _hook_real_exit $code
331
}
332
 
333
# }}}
334
# {{{  Output                                                   
335
 
336
# ----------------------------------------------------------------------------
337
# The usual set of utilities for issuing warnings, errors, ...
338
#
339
# There are three possibilities to consider:
340
#
341
#   1) running in text-only mode. The output should just go to stdout
342
#
343
#   2) running in GUI mode and the text window exists. Just update the
344
#      window
345
#
346
#   3) running in GUI mode but the text window is not yet ready. The
347
#      output needs to be buffered for now, and will be flushed
348
#      later.
349
#
350
# Also, if for some reason this program exits while there is output still
351
# buffered that output should also go to stdout.
352
#
353
# If any errors occur during initialization, e.g. an invalid device script
354
# or user initialization scripts, those get reported and an error count
355
# is maintained. When the eCos application reports that initialization is
356
# complete it will be sent back a status for the auxiliary, and will
357
# exit if things have not started up correctly. This tries to ensure that
358
# if there are multiple errors the user sees all of them.
359
 
360
namespace eval synth {
361
 
362
    variable _pending_output [list]
363
    variable _logfd ""
364
    variable _error_count 0
365
 
366
    proc logfile_open { } {
367
        synth::report "Opening logfile $synth::logfile"
368
        set msg ""
369
        if { [catch { set synth::_logfd [open $synth::logfile "w"] } msg ] } {
370
            synth::report_error "Unable to open logfile \"$synth::logfile\"\n    $msg\n"
371
        }
372
    }
373
 
374
    # A default implementation of output. This gets overwritten later when running
375
    # in GUI mode, so if GUI mode is enabled then this proc must be called before
376
    # the GUI is ready and the data must be queued.
377
    proc output { msg filter } {
378
        if { ! $synth::flag_gui } {
379
            # If a logfile exists, output normally goes there rather than
380
            # to standard output. The exception is for errors which
381
            # always go to stderr, in addition to the logfile.
382
            if { "" != $synth::_logfd } {
383
                puts -nonewline $synth::_logfd $msg
384
                if { "error" == $filter } {
385
                    puts -nonewline stderr $msg
386
                }
387
            } else {
388
                if { "error" == $filter } {
389
                    puts -nonewline stderr $msg
390
                } else {
391
                    puts -nonewline $msg
392
                }
393
            }
394
        } else {
395
            lappend synth::_pending_output [list $msg $filter]
396
        }
397
    }
398
 
399
    # Invoked by the text window code once everything is ready
400
    # and synth::output has been redefined.
401
    proc _flush_output { } {
402
        foreach msg $synth::_pending_output {
403
            synth::output [lindex $msg 0] [lindex $msg 1]
404
        }
405
        set synth::_pending_output [list]
406
    }
407
 
408
    # Cope with early exits. This will only have an effect if
409
    # _flush_output has not been called yet, and by implication
410
    # if synth::output has not yet been redefined.
411
    proc _exit_flush_output { arg_list } {
412
        if { 0 != [llength $synth::_pending_output] } {
413
            set synth::flag_gui 0
414
            synth::_flush_output
415
        }
416
    }
417
    synth::hook_add "exit" synth::_exit_flush_output
418
 
419
    proc report { msg } {
420
        synth::output $msg "report"
421
    }
422
 
423
    proc report_warning { msg } {
424
        synth::output "Warning: $msg" "warning"
425
    }
426
 
427
    proc report_error { msg } {
428
        incr synth::_error_count
429
        synth::output "Error: $msg" "error"
430
    }
431
 
432
    # Internal errors indicate a serious problem within ecosynth or
433
    # a device-specific script. For now this results in output to
434
    # stderr, a backtrace, and termination of the auxiliary, which
435
    # should also cause the eCos application to shut down.
436
    #
437
    # An alternative approach would involve calling ::error and
438
    # benefitting from its backtrace generation, but there are various
439
    # places where it makes to sense to catch problems and call
440
    # synth::error rather than internal_error
441
    proc internal_error { msg } {
442
        puts stderr "ecosynth, an internal error has occurred:"
443
        puts stderr "    $msg"
444
        puts stderr "---------- backtrace -------------------------------------------------"
445
        for { set level [info level] } { $level > 0 } { incr level -1 } {
446
            puts stderr [info level $level]
447
        }
448
        puts stderr "----------------------------------------------------------------------"
449
        puts stderr "ecosynth, exiting."
450
        exit 1
451
    }
452
 
453
    # Dummy implementations of the exported filter routines, in case a script
454
    # tries to create a filter even when not running in graphical mode
455
    variable _dummy_filters [list]
456
 
457
    proc filter_exists { name } {
458
        set result 0
459
        if { -1 != [lsearch -exact $synth::_dummy_filters $name] } {
460
            set result 1
461
        }
462
        return $result
463
    }
464
 
465
    proc filter_get_list { } {
466
        return $synth::_dummy_filters
467
    }
468
 
469
    proc filter_add { name args } {
470
        if { [synth::filter_exists $name] } {
471
            synth::internal_error "attempt to install filter $name twice.\n"
472
        }
473
        lappend synth::_dummy_filters $name
474
    }
475
}
476
 
477
# }}}
478
# {{{  Argument processing and global options                   
479
 
480
# ----------------------------------------------------------------------------
481
# Argument processing. The eCos application will usually just pass its
482
# command line arguments to the auxiliary. Four special arguments will
483
# have been examined already:
484
#
485
#    -io, --io
486
#        I/O facilities, i.e. the auxiliary should run
487
#    -ni, -nio, --ni, --nio
488
#        No I/O facilities, i.e. the auxiliary should not be run.
489
#    -nw, --nw, --no-windows
490
#        No windows, i.e. disable the GUI
491
#    -w, --w, --windows
492
#        Enable the GUI
493
#
494
# There are a number of additional flags available as standard:
495
#
496
#    -v, --version
497
#        The usual
498
#    -h, --help
499
#        Ditto
500
#    -k, --k, --keep-going
501
#        Ignore errors as much as possible
502
#    -nr, --no-rc
503
#        Skip the initrc and mainrc files
504
#    -x, --exit
505
#        The auxiliary should exit at the same time as the eCos application.
506
#    -nx, --no-exit
507
#        Inverse of the above
508
#    -V, --verbose
509
#        The usual
510
#    --debug
511
#        Not intended for end users
512
#    -l <file>, -l=<file>, --logfile <file>, --logfile=<file>
513
#        Send all output to the specified file. In GUI mode this is in addition
514
#        to the main text window. In non-GUI mode this is instead of stdout.
515
#    -t <file>, -t=<file>, --target <file>, --target=<file>
516
#        Specify the target definition file.
517
#
518
# Many X applications accept a common set of options, e.g. -display,
519
# -geometry, etc. Although usually Tk will process these, there are
520
# some problems - see ecosynth.c, ecosynth_appinit() for details.
521
# Hence -geometry has to be processed here.
522
#
523
#    -geometry <geom>
524
#
525
#
526
# Some device-specific scripts may want to support additional
527
# command line arguments. This is somewhat messy, since the core
528
# code has no way of knowing what devices might be available and
529
# hence what the actual valid arguments are. It would be possible
530
# to just ignore any arguments that are not used by any device,
531
# but that could really confuse a user who has made a typo. Instead
532
# the code below keeps track of which arguments have been "consumed",
533
# allowing it to issue a warning about unconsumed arguments after
534
# initialization.
535
#
536
# Arguments can take the following forms:
537
#
538
#    1) -flag or --flag.
539
#    2) -name=value or --name=value
540
#    3) -name value or --name value
541
#
542
# There is a possibility of confusion if any of the values begin with a hyphen.
543
# It is hard to do anything about this without advance knowledge of what all
544
# the valid arguments are. Instead the user can avoid problems by using
545
# the -name=value variant on the command line.
546
#
547
# There is also possible confusion if a single argument can occur multiple
548
# times. If that is permitted then things can get rather messy, and
549
# the current API does not really handle it.
550
 
551
namespace eval synth {
552
 
553
    # Keep track of all arguments which have not yet been consumed.
554
    array set _argv_unconsumed [list]
555
    for { set i 0 } { $i < $::argc } { incr i } {
556
        set synth::_argv_unconsumed($i) [lindex $::argv $i]
557
    }
558
 
559
    # Provide a list of just those arguments that have not yet
560
    # been consumed.
561
    proc argv_get_unconsumed { } {
562
        set result [list]
563
        for { set i 0 } { $i < $::argc } {incr i } {
564
            if { [info exists synth::_argv_unconsumed($i)] } {
565
                lappend result $synth::_argv_unconsumed($i)
566
            }
567
        }
568
        return $result
569
    }
570
 
571
    proc _argv_consume { index } {
572
        if { [info exists synth::_argv_unconsumed($index)] } {
573
            unset synth::_argv_unconsumed($index)
574
        }
575
    }
576
 
577
    # Internal routine. Given a string of the form "-flag" or "-name=",
578
    # return an index within argv or -1 if not found. As a side effect
579
    # this "consumes" the argument.
580
    proc _argv_lookup { name } {
581
        set result -1
582
        if { "=" != [string index $name end] } {
583
            for { set i 0 } { $i < $::argc } { incr i } {
584
                set arg [lindex $::argv $i]
585
                if { [string equal $arg $name] || [string equal $arg "-[set name]"] } {
586
                    set result $i
587
                    synth::_argv_consume $i
588
                    break
589
                }
590
            }
591
        } else {
592
            set name [string range $name 0 end-1]
593
            set len  [string length $name]
594
            for { set i 0 } { $i < $::argc } { incr i } {
595
                set arg [lindex $::argv $i]
596
                if { [string equal -length $len $arg $name] } {
597
                    if { "=" == [string index $arg $len] } {
598
                        set result $i
599
                        synth::_argv_consume $i
600
                        break;
601
                    } elseif { ([string length $arg] == $len) && ($i < ($::argc - 1)) } {
602
                        set result $i
603
                        synth::_argv_consume $i
604
                        synth::_argv_consume [expr $i + 1]
605
                        break
606
                    }
607
                } elseif { [string equal -length [expr $len + 1] $arg "-[set name]"] } {
608
                    if { "=" == [string index $arg [expr $len + 1]] } {
609
                        set result $i
610
                        synth::_argv_consume $i
611
                        break
612
                    } elseif { ([string length $arg] == [expr $len + 1]) && ($i < ($::argc - 1)) } {
613
                        set result $i
614
                        synth::_argv_consume $i
615
                        synth::_argv_consume [expr $i + 1]
616
                        break
617
                    }
618
                }
619
            }
620
        }
621
        return $result
622
    }
623
 
624
    # Look for a given argument on the command line.
625
    proc argv_defined { name } {
626
        set result 0
627
        set index [synth::_argv_lookup $name]
628
        if { -1 != $index } {
629
            set result 1
630
        }
631
        return $result
632
    }
633
 
634
    # Return the value associated with a given argument, which must be present.
635
    proc argv_get_value { name } {
636
        if { "=" != [string index $name end] } {
637
            synth::internal_error "attempt to get a value for a simple flag argument \"$name\".\n"
638
        }
639
        set result ""
640
        set index [synth::_argv_lookup $name]
641
        if { -1 == $index } {
642
            synth::internal_error "attempt to get the value associated with a non-existent argument \"$name\".\n"
643
        }
644
        set arg [lindex $::argv $index]
645
        set len [string length $name]
646
        if { [string equal -length $len $arg $name] } {
647
            set result [string range $arg $len end]
648
        } elseif { [string equal -length [expr $len + 1] $arg "-[set name]"] } {
649
            set result [string range $arg [expr $len + 1] end]
650
        } else {
651
            set result [lindex $::argv [expr $index + 1]]
652
        }
653
        return $result
654
    }
655
 
656
    # -ni/-nio are not relevant. If present then they would have been handled
657
    # within the eCos application, the auxiliary would not have been spawned,
658
    # and this script would not be running.
659
 
660
    # -io will have been processed by the eCos application.
661
    # -nw, -w, and related options have been processed by the C code.
662
    # Look them up anyway to consume them.
663
    synth::_argv_lookup "-io"
664
    synth::_argv_lookup "-nw"
665
    synth::_argv_lookup "-no-windows"
666
    synth::_argv_lookup "-w"
667
    synth::_argv_lookup "-windows"
668
 
669
    # Now cope with the other standard flags
670
    if { [synth::argv_defined "-v"] || [synth::argv_defined "--version"] } {
671
        # Just output the version message and exit. The eCos application
672
        # will do the same. The version is obtained from configure.in,
673
        # and also from the install directory. The synthetic target
674
        # startup code will exit quietly if the auxiliary exits at this
675
        # stage. This output should go via puts rather than the
676
        # synth:: output routines, since the GUI will never appear if
677
        # --version is specified.
678
        puts "ecosynth: version $synth::_ecosynth_version"
679
        puts "        : install location [file dirname [info script]]"
680
        exit 0
681
    }
682
 
683
    if { [synth::argv_defined "-l="] } {
684
        set synth::logfile [synth::argv_get_value "-l="]
685
    } elseif { [synth::argv_defined "-logfile="] } {
686
        set synth::logfile [synth::argv_get_value "-logfile="]
687
    }
688
    if { "" != $synth::logfile } {
689
        synth::logfile_open
690
    }
691
 
692
    # -h/--help would normally also result in an immediate exit. However,
693
    # the device-specific scripts have not yet been loaded so there
694
    # is no way of reporting their options. Hence the usage information
695
    # is delayed until later. Suppressing GUI mode as a side effect is
696
    # probably a good idea as well, that way the output appears in the
697
    # current console window.
698
    if { [synth::argv_defined "-h"] || [synth::argv_defined "-help"] } {
699
        set synth::flag_help 1
700
        set synth::flag_gui 0
701
    }
702
 
703
    if { [synth::argv_defined "-debug"] } {
704
        set synth::flag_debug 1
705
    }
706
 
707
    if { [synth::argv_defined "-k"] || [synth::argv_defined "-keep-going"] } {
708
        set synth::flag_keep_going 1
709
    }
710
 
711
    if { [synth::argv_defined "-nr"] || [synth::argv_defined "-no-rc"]} {
712
        set synth::flag_no_rc 1
713
    }
714
 
715
    if { [synth::argv_defined "-x"] || [synth::argv_defined "-exit"] } {
716
        set synth::flag_immediate_exit 1
717
    } elseif { [synth::argv_defined "-nx"] || [synth::argv_defined "-no-exit"] } {
718
        set synth::flag_immediate_exit 0
719
    }
720
 
721
    if { [synth::argv_defined "-V"] || [synth::argv_defined "-verbose"] } {
722
        set synth::flag_verbose 1
723
    }
724
 
725
    if { [synth::argv_defined "-t="] } {
726
        set synth::target_definition [synth::argv_get_value "-t="]
727
    } elseif { [synth::argv_defined "-target="] } {
728
        set synth::target_definition [synth::argv_get_value "-target="]
729
    }
730
 
731
    # Arguably -geometry should only be checked when the GUI is enabled,
732
    # but doing so at all times is harmless.
733
    # Note that '-geometry ""' means that any value held in the
734
    # preferences file should be ignored. Hence the regexp below 
735
    # accepts the empty string, and treats it separately from
736
    # uninitialized.
737
    if { [synth::argv_defined "-geometry="] } {
738
        set synth::geometry [synth::argv_get_value "-geometry="]
739
 
740
        if { ![regexp -- {^([0-9]+x[0-9]+)?([+-][0-9]+[+-][0-9]+)?$} $synth::geometry] } {
741
            synth::report_warning "Invalid geometry string $synth::geometry\n"
742
            set synth::geometry "<none>"
743
        }
744
    }
745
 
746
    if { $synth::flag_debug } {
747
        synth::report \
748
            "Results of initial command-line parsing:\n   \
749
                  --help            $synth::flag_help\n   \
750
                  --keep-going      $synth::flag_keep_going\n   \
751
                  --no-rc           $synth::flag_no_rc\n   \
752
                  --exit            $synth::flag_immediate_exit\n   \
753
                  --verbose         $synth::flag_verbose\n   \
754
                  logfile           $synth::logfile\n   \
755
                  target definition $synth::target_definition\n   \
756
                  geometry          $synth::geometry\n   \
757
                  unconsumed        [synth::get_unconsumed_args]\n"
758
    }
759
}
760
 
761
# }}}
762
# {{{  Create and populate ~/.ecos/synth                        
763
 
764
# ----------------------------------------------------------------------------
765
# If the per-user configuration directories do not exist yet, create them.
766
# Also install default initrc.tcl and mainrc.tcl files which do nothing, but
767
# which can be edited. If problems occur then the user gets a warning
768
# but execution proceeds.
769
#
770
# Some people may object to this automatic creation of directories and
771
# configuration files. However there is plenty of precedent, and the
772
# files involved are small. Messages are generated so that the user
773
# knows what has happened.
774
#
775
# Currently the default target definition file is not copied from
776
# the install tree into the per-user tree. Although some users will
777
# be happy having this file in ~/.ecos/synth, others may prefer it
778
# to be more visible in the current directory.
779
 
780
if { ![file exists "~/.ecos"] } {
781
    synth::report "Creating new directory ~/.ecos for eCos configuration files.\n"
782
    if { 0 != [catch { file mkdir "~/.ecos" }] } {
783
        synth::report_warning "failed to create directory ~/.ecos\n"
784
    }
785
}
786
if { [file exists "~/.ecos"] && [file isdirectory "~/.ecos"] && ![file exists "~/.ecos/synth"] } {
787
    synth::report "Creating new directory ~/.ecos/synth for synthetic target configuration files.\n"
788
    if { 0 != [catch { file mkdir "~/.ecos/synth" } ] } {
789
        synth::report_warning "failed to create directory ~/.ecos/synth\n"
790
    } else {
791
        # initrc and mainrc are only copied when the directory is first created,
792
        # so that users can delete them if unwanted - even though the
793
        # default versions do nothing.
794
        synth::report "Installing default configuration files ~/.ecos/synth/initrc.tcl and ~/.ecos/synth/mainrc.tcl\n"
795
        catch { file copy -- [file join $synth::install_dir "user_initrc.tcl"] "~/.ecos/synth/initrc.tcl"}
796
        catch { file copy -- [file join $synth::install_dir "user_mainrc.tcl"] "~/.ecos/synth/mainrc.tcl"}
797
    }
798
}
799
 
800
# }}}
801
# {{{  Read target definition file                              
802
 
803
# ----------------------------------------------------------------------------
804
# Once the GUI is up and running it is possible to start reading in some
805
# configuration files. The first of these is the target definition file.
806
# Typically this would be ~/.ecos/synth/default.tdf. An alternative
807
# definition file can be specified on the command line with the
808
# -t argument, and the code will look in the current directory,
809
# in ~/.ecos/synth, and in the install tree.
810
#
811
# The purpose of the target definition file is to specify exactly
812
# how I/O gets implemented. For example the eCos application may
813
# want to access a network device eth0, but that device could be
814
# implemented in a variety of ways (e.g. a real ethernet device
815
# on the Linux host, or a fake device provided by the ethertap
816
# facility). It is the target definition file that provides
817
# this information.
818
#
819
# The file is of course just another Tcl script, running in the
820
# current interpreter. There seems little point in using a safe
821
# interpreter given the considerable number of other Tcl scripts
822
# that are being used, some of which need the ability to e.g.
823
# run other programs. The main command is synth_device which
824
# takes two arguments, a device name and some options for that
825
# device, e.g.:
826
#
827
#    synth_device eth0 {
828
#        use eth1    
829
#    }
830
#
831
#    synth_device eth1 {
832
#        use tap0
833
#    }    
834
#
835
# When the eCos device driver looks up eth0 this will cause a
836
# device-specific Tcl script to be loaded, which can examine
837
# this data.
838
#
839
# This code has no way of knowing what constitutes valid or invalid
840
# contents for an eth0 device, especially since the Tcl script that
841
# could provide such knowledge has not been loaded. Instead it is
842
# assumed that the contents is another set of Tcl commands such as
843
# "physical", which will of course not be defined so the Tcl interpreter
844
# will invoke "unknown" which is temporarily redefined here. This makes
845
# it possible for the device-specific commands to have arbitrary number
846
# of arguments, or to define Tcl fragments for hooks, or whatever.
847
#
848
# As with argument processing, the code attempts to keep track of
849
# which devices and options have been "consumed" and can issue
850
# warnings about any unused devices or options. This helps to track
851
# down typos and similar problems. These warnings are only output
852
# when running at verbose mode, since it is fairly normal practice
853
# to have a single target definition file which supports 
854
# a number of different eCos applications with different I/O
855
# requirements.
856
 
857
namespace eval synth {
858
    # A list of all devices specified in the target definition file.
859
    # For now assume that a given device will only be specified once.
860
    variable _tdf_devices [list]
861
 
862
    # An array with additional details of each device. This will have
863
    # entries such as _tdf_device_options(eth0,4), where the second
864
    # argument is a per-device index. The value of each entry is
865
    # a list of the actual command and its arguments. This use of
866
    # an index allows for multiple instances of a per-device
867
    # option.
868
    array set _tdf_device_options [list]
869
 
870
    # While reading in the device details it is necessary to keep track
871
    # of the current device, if any. Otherwise the implementation of
872
    # "unknown" will not be able to update _tdf_device_options. An index
873
    # is needed as well.
874
    variable _tdf_current_device ""
875
    variable _tdf_current_index  0
876
 
877
    # Keep track of which devices and options have been consumed
878
    variable _tdf_consumed_devices [list]
879
    variable _tdf_consumed_options [list]
880
 
881
    proc tdf_has_device { name } {
882
        return [expr -1 != [lsearch -exact $synth::_tdf_devices $name]]
883
    }
884
 
885
    proc tdf_get_devices { } {
886
        return $synth::_tdf_devices
887
    }
888
 
889
    proc _tdf_get_option_index { devname option } {
890
        synth::_tdf_consume_device $devname
891
        for { set i 0 } { [info exists synth::_tdf_device_options($devname,$i)] } { incr i } {
892
            if { $option == [lindex $synth::_tdf_device_options($devname,$i) 0] } {
893
                synth::_tdf_consume_option $devname $i
894
                return $i
895
            }
896
        }
897
        return -1
898
    }
899
 
900
    proc _tdf_get_option_indices { devname option } {
901
        synth::_tdf_consume_device $devname
902
        set result [list]
903
        for { set i 0 } { [info exists synth::_tdf_device_options($devname,$i)] } { incr i } {
904
            if { $option == [lindex $synth::_tdf_device_options($devname,$i) 0] } {
905
                synth::_tdf_consume_option $devname $i
906
                lappend result $i
907
            }
908
        }
909
        return $result
910
    }
911
 
912
    proc tdf_has_option { devname option } {
913
        return [expr -1 != [synth::_tdf_get_option_index $devname $option]]
914
    }
915
 
916
    proc tdf_get_option { devname option } {
917
        set index [synth::_tdf_get_option_index $devname $option]
918
        if { -1 != $index } {
919
            lappend synth::_tdf_consumed_options "$devname,$index"
920
            return [lrange $synth::_tdf_device_options($devname,$index) 1 end]
921
        } else {
922
            return [list]
923
        }
924
    }
925
 
926
    proc tdf_get_options { devname option } {
927
        set result [list]
928
        set indices [synth::_tdf_get_option_indices $devname $option]
929
        foreach index $indices {
930
            lappend result [lrange $synth::_tdf_device_options($devname,$index) 1 end]
931
        }
932
        return $result
933
    }
934
 
935
    proc tdf_get_all_options { devname } {
936
        set result [list]
937
        for { set i 0 } { [info exists synth::_tdf_device_options($devname,$i)] } { incr i } {
938
            lappend synth::_tdf_consumed_options "$devname,$index"
939
            lappend result $synth::_tdf_device_options($devname,$i)
940
        }
941
        return $result
942
    }
943
 
944
    proc _tdf_consume_device { name } {
945
        if { -1 == [lsearch -exact $synth::_tdf_consumed_devices $name] } {
946
            lappend synth::_tdf_consumed_devices $name
947
        }
948
    }
949
 
950
    proc _tdf_consume_option { devname index } {
951
        if { -1 == [lsearch -exact $synth::_tdf_consumed_options "$devname,$index"] } {
952
            lappend synth::_tdf_consumed_options "$devname,$index"
953
        }
954
    }
955
 
956
    proc tdf_get_unconsumed_devices { } {
957
        set result [list]
958
        foreach devname $synth::_tdf_devices {
959
            if { -1 == [lsearch -exact $synth::_tdf_consumed_devices $devname] } {
960
                lappend result $devname
961
            }
962
        }
963
        return $result
964
    }
965
 
966
    proc tdf_get_unconsumed_options { } {
967
        set result [list]
968
        foreach devname $synth::_tdf_devices {
969
            if { -1 == [lsearch -exact $synth::_tdf_consumed_devices $devname] } {
970
                # Do not report all the options for a device that has not been consumed at all
971
                continue
972
            }
973
            for { set i 0 } { [info exists synth::_tdf_device_options($devname,$i)] } { incr i } {
974
                if { -1 == [lsearch -exact $synth::_tdf_consumed_options "$devname,$i"] } {
975
                    lappend result [list $devname $synth::_tdf_device_options($devname,$i)]
976
                }
977
            }
978
        }
979
        return $result
980
    }
981
}
982
 
983
# Look for the target definition file.
984
set _tdf $synth::target_definition
985
if { "" == $_tdf } {
986
    set _tdf "default"
987
}
988
set _config_file ""
989
 
990
set _dirlist [list [pwd] "~/.ecos/synth" $synth::install_dir]
991
foreach _dir $_dirlist {
992
    set _candidate "[file join $_dir $_tdf].tdf"  ; # file join does the right thing for absolute paths
993
    if { [file isfile $_candidate] } {
994
        set _config_file $_candidate
995
        break
996
    } else {
997
        set _candidate [file join $_dir $_tdf]
998
        if { [file isfile $_candidate] } {
999
            set _config_file $_candidate
1000
            break
1001
        }
1002
    }
1003
}
1004
if { "" == $_config_file } {
1005
    if { "" != $synth::target_definition } {
1006
        # The user explicitly specified a file, so it must be read in.
1007
        # If it cannot be found then that should be treated as an error.
1008
        set msg "Unable to find target definition file $synth::target_definition\n"
1009
        if { "absolute" !=  [file pathtype $synth::target_definition] } {
1010
            append msg "    Searched $_dirlist\n"
1011
        }
1012
        synth::report_error $msg
1013
        exit 1
1014
    } else {
1015
        # This is a mild error, because default.tdf should be installed
1016
        # below libexec. However the default file does not actually
1017
        # define anything, it is just a set of comments, so there is
1018
        # nothing to be gained by issuing a warning.
1019
    }
1020
} else {
1021
 
1022
    set synth::target_definition $_config_file
1023
 
1024
    proc synth_device { name data } {
1025
        if { "" != $synth::_tdf_current_device } {
1026
            error "synth_device $name is nested inside $synth::_tdf_current_device\nNesting of synth_device entries is not allowed."
1027
        }
1028
        if { -1 != [lsearch -exact $synth::_tdf_devices $name] } {
1029
            error "Duplicate entry for synth_device $name"
1030
        }
1031
        set synth::_tdf_current_device $name
1032
        set synth::_tdf_current_index 0
1033
        lappend synth::_tdf_devices $name
1034
        eval $data
1035
        # If the eval resulted in an error, propagate it immediately rather than attempt
1036
        # any form of recovery. The downside is that only error per run will be
1037
        # reported.
1038
        set synth::_tdf_current_device ""
1039
    }
1040
    rename unknown _synth_unknown
1041
    proc unknown { args } {
1042
        if { "" == $synth::_tdf_current_device } {
1043
            # An unknown command at the toplevel. Pass this to the
1044
            # original "unknown" command, in the unlikely event that
1045
            # the user really did want to autoload a library or do
1046
            # something similar. 
1047
            eval _synth_unknown $arg
1048
            return
1049
        }
1050
 
1051
        # Anything else is treated as an option within the synth_device
1052
        set synth::_tdf_device_options($synth::_tdf_current_device,$synth::_tdf_current_index) $args
1053
        incr synth::_tdf_current_index
1054
    }
1055
 
1056
    set _config_file_msg ""
1057
    set _result [catch { source $_config_file } _config_file_msg ]
1058
 
1059
    rename unknown ""
1060
    rename synth_device ""
1061
    rename _synth_unknown unknown
1062
 
1063
    if { $_result } {
1064
        # Any problems reading in the target definition file should be
1065
        # treated as an error: I/O is unlikely to behave in the way
1066
        # that the user expects.
1067
        set msg "An error occurred while reading in the target definition file\n    $_config_file\n    $_config_file_msg\n"
1068
        synth::report_error $msg
1069
        exit 1
1070
    }
1071
    unset _result _config_file_msg
1072
}
1073
 
1074
unset _dirlist _tdf _config_file _candidate
1075
 
1076
# }}}
1077
 
1078
if { $synth::flag_gui } {
1079
# {{{  Main GUI code                                            
1080
 
1081
# {{{  Session file                                             
1082
 
1083
# ----------------------------------------------------------------------------
1084
# The tool manages a file ~/.ecos/synth/guisession, holding information
1085
# such as the size and position of the main window. The aim is to give
1086
# the user a fairly consistent interface between sessions. The information
1087
# is saved during exit handling, and also in response to the window
1088
# manager WM_SAVE_YOURSELF request. However note that the latter does
1089
# not extend to user session information - restarting the eCos application
1090
# the next time a user logs in is inappropriate for eCos, plus if
1091
# the application is being run inside gdb (a likely scenario) it is gdb
1092
# that should handle restarting the application.
1093
#
1094
# Using a single file has limitations. Specifically the user may be
1095
# switching between a number of different target definition files,
1096
# each resulting in a subtly different layout, and arguably there
1097
# should be separate session information for each one. However
1098
# distinguishing between per-target and global settings would be
1099
# very complicated.
1100
#
1101
# The most obvious implementation involves the options database.
1102
#
1103
# FIXME: implement properly
1104
 
1105
namespace eval synth {
1106
    # Make sure we are using the right options from .Xdefaults etc.
1107
    tk appname "ecosynth"
1108
 
1109
    if { $synth::flag_debug } {
1110
        # synth::report "Reading in session file ~/.ecos/synth/guisession\n"
1111
    }
1112
 
1113
    # synth::report_warning "Support for reading session file ~/.ecos/synth/guisession not yet implemented.\n"
1114
 
1115
    if { [file exists "~/.ecos/synth/guisession"] } {
1116
        if {0 != [catch { option readfile "~/.ecos/synth/guisession" userDefault} msg]} {
1117
            # synth::report_error "Failed to read GUI session information from file ~/.ecos/synth/guisession\n    $msg\n"
1118
        }
1119
    }
1120
 
1121
    proc _update_session_file { arg_list } {
1122
        # synth::report_warning "Support for updating session file ~/.ecos/synth/guisession not yet implemented.\n"
1123
    }
1124
    proc _handle_wm_save_yourself { } {
1125
        # synth::report_warning "Support for WM_SAVE_YOURSELF not yet implemented\n"
1126
    }
1127
 
1128
    synth::hook_add "exit" synth::_update_session_file
1129
}
1130
 
1131
# }}}
1132
# {{{  Load images                                              
1133
 
1134
# ----------------------------------------------------------------------------
1135
# Load various useful bitmaps etc. into memory, so that they can be accessed
1136
# by any code that needs them.
1137
#
1138
# Running1 is a coloured version of the eCos logo. running2 and running3 are
1139
# used by alternative implementations of the heartbeat: running2 has the
1140
# red and black reversed, and running3 is mostly a mirror image of the normal
1141
# logo.
1142
namespace eval synth {
1143
 
1144
    proc load_image { image_name filename } {
1145
        set result 0
1146
        set type [file extension $filename]
1147
        if { ! [file exists $filename] } {
1148
            synth::report_error "Image $filename has not been installed.\n"
1149
        } elseif { ! [file readable $filename] } {
1150
            synth::report_error "Image $filename is not readable.\n"
1151
        } elseif { (".xbm" == $type) } {
1152
            if { 0 == [catch { set $image_name [image create bitmap -file $filename] }] } {
1153
                set result 1
1154
            } else {
1155
                synth::report_error "Bitmap image $filename is invalid.\n"
1156
            }
1157
        } else {
1158
            if { 0 == [catch { set $image_name [image create photo -file $filename] }] } {
1159
                set result 1
1160
            } else {
1161
                synth::report_error "Image $filename is invalid.\n"
1162
            }
1163
        }
1164
        return $result
1165
    }
1166
 
1167
    set _images [list "tick_yes.xbm" "tick_no.xbm" "save.xbm" "cut.xbm" "copy.xbm" "paste.xbm" \
1168
            "help.xbm" "running1.ppm" "saveall.xbm" ]
1169
    foreach _image $_images {
1170
        variable image_[file rootname $_image]
1171
        synth::load_image "synth::image_[file rootname $_image]" [file join $synth::install_dir $_image]
1172
    }
1173
    unset _images _image
1174
}
1175
 
1176
# }}}
1177
# {{{  Balloon help                                             
1178
 
1179
namespace eval synth {
1180
 
1181
    variable _balloon_messages
1182
    variable _balloon_pending ""
1183
 
1184
    toplevel .balloon
1185
    label .balloon.info -borderwidth 2 -relief groove -background "light yellow"
1186
    pack .balloon.info -side left -fill both -expand 1
1187
    wm overrideredirect .balloon 1
1188
    wm withdraw .balloon
1189
 
1190
    proc register_balloon_help { widget message } {
1191
        set synth::_balloon_messages($widget) $message
1192
        bind $widget <Enter> { synth::_balloonhelp_pending %W }
1193
        bind $widget <Leave> { synth::_balloonhelp_cancel }
1194
    }
1195
 
1196
    proc _balloonhelp_pending { widget } {
1197
        synth::_balloonhelp_cancel
1198
        set synth::_balloon_pending [after 1200 [list synth::_balloonhelp_show $widget]]
1199
    }
1200
 
1201
    proc _balloonhelp_cancel { } {
1202
        if { "" != $synth::_balloon_pending } {
1203
            after cancel $synth::_balloon_pending
1204
            set synth::_balloon_pending ""
1205
        } else {
1206
            wm withdraw .balloon
1207
        }
1208
    }
1209
 
1210
    proc _balloonhelp_show { widget } {
1211
        .balloon.info configure -text $synth::_balloon_messages($widget)
1212
        set x [expr [winfo rootx $widget] + 2]
1213
        set y [expr [winfo rooty $widget] + [winfo height $widget] + 2]
1214
        wm geometry .balloon +$x+$y
1215
        wm deiconify .balloon
1216
        raise .balloon
1217
        set synth::_balloon_pending ""
1218
    }
1219
}
1220
 
1221
# }}}
1222
# {{{  Window manager settings                                  
1223
 
1224
# ----------------------------------------------------------------------------
1225
# Set up the current program name in the title bar etc.
1226
 
1227
namespace eval synth {
1228
 
1229
    if { $synth::flag_debug } {
1230
        synth::report "Performing required interactions with window manager\n"
1231
    }
1232
 
1233
    # The toplevel is withdrawn during startup. It is possible that
1234
    # some of the windows and other objects created initially will end
1235
    # up being deleted again before the system is fully up and running,
1236
    # and the event loop is entered before then to accept requests from
1237
    # the eCos application. This could cause confusing changes. The
1238
    # toplevel is displayed in response to the constructors-done request.
1239
    wm withdraw .
1240
 
1241
    # For now disable all attempts to use the "send" command. Leaving it
1242
    # enabled would introduce security problems.
1243
    rename "::send" {}
1244
 
1245
    variable title "eCos synthetic target"
1246
    if { "" != $synth::ecos_appname} {
1247
        append synth::title ": $synth::ecos_appname ($synth::_ppid)"
1248
    }
1249
    wm title . $synth::title
1250
 
1251
    # Use the specified geometry, or that from the last session.
1252
    # Obviously how well this works depends very much on the
1253
    # window manager being used.
1254
    set _geometry ""
1255
    if { "" ==  $synth::geometry} {
1256
        # Command line request to suppress the preferences. Revert
1257
        # to a default size.
1258
        set _geometry "640x480"
1259
    } elseif { "<none>" == $synth::geometry } {
1260
        # No command line option, use the value from the preferences file
1261
        # FIXME: implement
1262
        set _geometry "640x480"
1263
    } else {
1264
        # There was an explicit -geometry option on the command line. Use it.
1265
        set synth::_geometry $synth::geometry
1266
        if { [regexp -- {^([0-9]+x[0-9]+).*$} $synth::_geometry] } {
1267
            wm sizefrom . "user"
1268
        }
1269
        if { [regexp -- {^.*([+-][0-9]+[+-][0-9]+)$} $synth::_geometry] } {
1270
            wm positionfrom . "user"
1271
        }
1272
    }
1273
    wm geometry . $synth::_geometry
1274
    unset synth::_geometry
1275
 
1276
    set _file [file join $synth::install_dir "ecosicon.xbm"]
1277
    if { [file readable $synth::_file] } {
1278
        wm iconbitmap . "@$synth::_file"
1279
    }
1280
    set _file [file join $synth::install_dir "ecosiconmask.xbm"]
1281
    if { [file readable $synth::_file] } {
1282
        wm iconmask . "@$synth::_file"
1283
    }
1284
    unset synth::_file
1285
 
1286
    if { "" != $synth::ecos_appname } {
1287
        wm iconname . "ecosynth: $synth::ecos_appname"
1288
    } else {
1289
        wm iconname . "ecosynth"
1290
    }
1291
 
1292
    wm protocol . "WM_DELETE_WINDOW" synth::_handle_exit_request
1293
    wm protocol . "WM_SAVE_YOURSELF" synth::_handle_wm_save_yourself
1294
}
1295
 
1296
# }}}
1297
# {{{  Exit and kill handling                                   
1298
 
1299
# ----------------------------------------------------------------------------
1300
# Exit handling. The user may request program termination using various
1301
# different ways:
1302
#   1) File->Exit
1303
#   2) ctrl-Q, the shortcut for the above
1304
#   3) the Window Manager's delete-window request
1305
#
1306
# If eCos has already exited then the request can be handled straightaway.
1307
# The invocation of exit will go via the exit hooks so appropriate
1308
# clean-ups will take place.
1309
#
1310
# If eCos has not already exited then it is assumed that the user wants
1311
# the eCos application to terminate as well as the GUI. This can be achieved
1312
# via the interrupt subsystem. However, there is a risk that the application
1313
# has crashed, or is blocked in gdb, or has interrupts permanently disabled,
1314
# in which case it is not going to respond to the SIGIO. To allow for this
1315
# a number of retries are attempted, and after five seconds of this the
1316
# application is killed off forcibly.
1317
 
1318
namespace eval synth {
1319
 
1320
    variable _handle_exit_retries 0
1321
    variable _handle_exit_after   ""
1322
 
1323
    proc _handle_exit_request { } {
1324
 
1325
        if { !$synth::ecos_running } {
1326
            exit 0
1327
        }
1328
        # Setting this flag causes ecosynth to exit immediately once
1329
        # the application terminates.
1330
        set synth::flag_immediate_exit 1
1331
 
1332
        # Now ask the application to exit
1333
        synth::request_application_exit
1334
 
1335
        # Set up a timer to retry this
1336
        if { "" == $synth::_handle_exit_after } {
1337
            set synth::_handle_exit_after [after 1000 synth::_handle_exit_timer]
1338
 
1339
            # And output something so the user knows the request has been received
1340
            synth::report "Waiting for the eCos application to exit.\n"
1341
        }
1342
    }
1343
 
1344
    # This routine requests termination of eCos, but not of 
1345
    # ecosynth
1346
    proc _handle_kill_request { } {
1347
        if { $synth::ecos_running } {
1348
            synth::request_application_exit
1349
            if { "" == $synth::_handle_exit_after } {
1350
                set synth::_handle_exit_after [after 1000 synth::_handle_exit_timer]
1351
            }
1352
        }
1353
    }
1354
 
1355
    proc _handle_exit_timer { } {
1356
        if { $synth::ecos_running } {
1357
            incr synth::_handle_exit_retries
1358
            if { $synth::_handle_exit_retries < 5 } {
1359
                synth::request_application_exit
1360
                synth::report "Still waiting for the eCos application to exit.\n"
1361
            } else {
1362
                synth::_send_SIGKILL
1363
            }
1364
            set synth::_handle_exit_after [after 1000 synth::_handle_exit_timer]
1365
        }
1366
    }
1367
}
1368
 
1369
# }}}
1370
# {{{  Main window layout                                       
1371
 
1372
# ----------------------------------------------------------------------------
1373
# The window layout is as follows:
1374
#  1) a menu bar at the top (surprise surprise). Many of the menus will be
1375
#     empty or nearly so, but device-specific scripts may want to extend
1376
#     the menus.
1377
#  2) a toolbar. This is primarily for use by device-specific scripts
1378
#  3) a central grid.
1379
#  4) a status line at the bottom.
1380
#
1381
# The central grid is organised as a 3x3 set of frames. The centre frame holds
1382
# the main text display, plus associated scrollbars, and is the frame that
1383
# will expand or shrink as the toplevel is resized. The remaining eight frames
1384
# (nw, n, ne, e, se, s, sw, w) are available for use by device-specific
1385
# scripts, typically under control of settings in the target definition file.
1386
# It is very possible that some or all of these eight frames will be empty,
1387
# and if an entire row or column is empty then Tk will make them invisible.
1388
#
1389
# Possible enhancements:
1390
# 1) implement some sort of paning/resizing around the central text window.
1391
#    That would provide some way of letting the user control the space
1392
#    taken by device-specific subwindows. This would be implemented
1393
#    by modifying the weights assigned to different rows/columns.
1394
# 2) it would be very useful if the main text window could be split,
1395
#    like emacs. This would require multiple text widgets, with output
1396
#    being pasted in each one.
1397
# 3) possibly the text window should not be hard-wired to the centre frame,
1398
#    instead its frame could be selected by preferences somehow.
1399
 
1400
if { $synth::flag_debug } {
1401
    synth::report "Creating main window layout\n"
1402
}
1403
 
1404
# The various frames are generally accessed via variables
1405
 
1406
menu .menubar -borderwidth 1
1407
menu .menubar.file
1408
menu .menubar.edit
1409
menu .menubar.view
1410
menu .menubar.windows
1411
menu .menubar.help
1412
 
1413
. configure -menu .menubar
1414
.menubar add cascade -label "File"    -underline 0 -menu .menubar.file
1415
.menubar add cascade -label "Edit"    -underline 0 -menu .menubar.edit
1416
.menubar add cascade -label "View"    -underline 0 -menu .menubar.view
1417
.menubar add cascade -label "Windows" -underline 0 -menu .menubar.windows
1418
.menubar add cascade -label "Help"    -underline 0 -menu .menubar.help
1419
 
1420
.menubar.file add command -label "Save"        -underline 0 -accelerator "Ctrl-S" -command [list synth::_handle_file_save]
1421
.menubar.file add command -label "Save As..."  -underline 5                       -command [list synth::_handle_file_save_as]
1422
.menubar.file add command -label "Save All..." -underline 6                       -command [list synth::_handle_file_save_all]
1423
.menubar.file add command -label "Kill eCos"   -underline 0                       -command [list synth::_handle_kill_request]
1424
.menubar.file add command -label "Exit"        -underline 1 -accelerator "Ctrl-Q" -command [list synth::_handle_exit_request]
1425
bind . <Control-KeyPress-q> [list synth::_handle_exit_request]
1426
bind . <Control-KeyPress-s> [list synth::_handle_file_save]
1427
 
1428
# Once eCos has exited, the kill option should be disabled
1429
namespace eval synth {
1430
    proc _menubar_ecos_exit_clean { arg_list } {
1431
        .menubar.file entryconfigure "Kill eCos" -state disabled
1432
    }
1433
    synth::hook_add "ecos_exit" synth::_menubar_ecos_exit_clean
1434
}
1435
 
1436
frame .toolbar -borderwidth 1 -relief groove
1437
if { "" != $synth::image_save } {
1438
    button .toolbar.save    -image $synth::image_save -borderwidth 0 -command [list synth::_handle_file_save]
1439
    pack .toolbar.save -side left -padx 2
1440
    synth::register_balloon_help .toolbar.save "Save visible output"
1441
}
1442
if { "" != $synth::image_saveall } {
1443
    button .toolbar.saveall -image $synth::image_saveall -borderwidth 0 -command [list synth::_handle_file_save_all]
1444
    pack .toolbar.saveall -side left -padx 2
1445
    synth::register_balloon_help .toolbar.saveall "Save all output"
1446
}
1447
if { "" != $synth::image_cut } {
1448
    button .toolbar.cut    -image $synth::image_cut -borderwidth 0 -state disabled -command [list synth::_handle_edit_cut]
1449
    pack .toolbar.cut -side left -padx 2
1450
    synth::register_balloon_help .toolbar.cut "Cut"
1451
}
1452
if { "" != $synth::image_copy } {
1453
 
1454
    button .toolbar.copy    -image $synth::image_copy -borderwidth 0 -command [list synth::_handle_edit_copy]
1455
    pack .toolbar.copy -side left -padx 2
1456
    synth::register_balloon_help .toolbar.copy "Copy"
1457
}
1458
if { "" != $synth::image_paste } {
1459
    button .toolbar.paste    -image $synth::image_paste -borderwidth 0 -state disabled -command [list synth::_handle_edit_paste]
1460
    pack .toolbar.paste -side left -padx 2
1461
    synth::register_balloon_help .toolbar.paste "Paste"
1462
}
1463
pack .toolbar -side top -fill x
1464
 
1465
frame .main
1466
frame .main.nw -borderwidth 0
1467
frame .main.n  -borderwidth 0
1468
frame .main.ne -borderwidth 0
1469
frame .main.e  -borderwidth 0
1470
frame .main.se -borderwidth 0
1471
frame .main.s  -borderwidth 0
1472
frame .main.sw -borderwidth 0
1473
frame .main.w  -borderwidth 0
1474
 
1475
frame .main.centre
1476
 
1477
frame .main.border_nw_n      -width 2 -background black -borderwidth 0
1478
frame .main.border_n_ne      -width 2 -background black -borderwidth 0
1479
frame .main.border_w_centre  -width 2 -background black -borderwidth 0
1480
frame .main.border_centre_e  -width 2 -background black -borderwidth 0
1481
frame .main.border_sw_s      -width 2 -background black -borderwidth 0
1482
frame .main.border_s_se      -width 2 -background black -borderwidth 0
1483
frame .main.border_nw_w      -height 2 -background black -borderwidth 0
1484
frame .main.border_n_centre  -height 2 -background black -borderwidth 0
1485
frame .main.border_ne_e      -height 2 -background black -borderwidth 0
1486
frame .main.border_w_sw      -height 2 -background black -borderwidth 0
1487
frame .main.border_centre_s  -height 2 -background black -borderwidth 0
1488
frame .main.border_e_se      -height 2 -background black -borderwidth 0
1489
 
1490
text .main.centre.text -xscrollcommand [list .main.centre.horizontal set] -yscrollcommand [list .main.centre.vertical set]
1491
scrollbar .main.centre.horizontal -orient horizontal -command [list .main.centre.text xview]
1492
scrollbar .main.centre.vertical   -orient vertical -command [list .main.centre.text yview]
1493
grid configure .main.centre.text -row 0 -column 0 -sticky news
1494
grid configure .main.centre.vertical -row 0 -column 1 -sticky ns
1495
grid configure .main.centre.horizontal -row 1 -column 0 -sticky ew
1496
# Is there anything useful to be done in 1,1? e.g. a >> button to
1497
# go directly to perform ".main.centre.text see end"
1498
 
1499
# Make sure that row 0 column 0, i.e. the text widget rather than the
1500
# scrollbars, grows to fit all available space.
1501
grid rowconfigure .main.centre 0 -weight 1
1502
grid rowconfigure .main.centre 1 -weight 0
1503
grid columnconfigure .main.centre 0 -weight 1
1504
grid columnconfigure .main.centre 1 -weight 0
1505
 
1506
grid configure .main.nw              -row 0 -column 0 -sticky news
1507
grid configure .main.border_nw_n     -row 0 -column 1 -sticky ns
1508
grid configure .main.n               -row 0 -column 2 -sticky news
1509
grid configure .main.border_n_ne     -row 0 -column 3 -sticky ns
1510
grid configure .main.ne              -row 0 -column 4 -sticky news
1511
grid configure .main.border_nw_w     -row 1 -column 0 -sticky ew
1512
grid configure .main.border_n_centre -row 1 -column 1 -columnspan 3  -sticky ew
1513
grid configure .main.border_ne_e     -row 1 -column 4 -sticky ew
1514
grid configure .main.w               -row 2 -column 0 -sticky news
1515
grid configure .main.border_w_centre -row 2 -column 1 -sticky ns
1516
grid configure .main.centre          -row 2 -column 2 -sticky news
1517
grid configure .main.border_centre_e -row 2 -column 3 -sticky ns
1518
grid configure .main.e               -row 2 -column 4 -sticky news
1519
grid configure .main.border_w_sw     -row 3 -column 0 -sticky ew
1520
grid configure .main.border_centre_s -row 3 -column 1 -columnspan 3 -sticky ew
1521
grid configure .main.border_e_se     -row 3 -column 4 -sticky ew
1522
grid configure .main.sw              -row 4 -column 0 -sticky news
1523
grid configure .main.border_sw_s     -row 4 -column 1 -sticky ns
1524
grid configure .main.s               -row 4 -column 2 -sticky news
1525
grid configure .main.border_s_se     -row 4 -column 3 -sticky ns
1526
grid configure .main.se              -row 4 -column 4 -sticky news
1527
grid columnconfigure .main 0 -weight 0
1528
grid columnconfigure .main 1 -weight 0
1529
grid columnconfigure .main 2 -weight 1
1530
grid columnconfigure .main 3 -weight 0
1531
grid columnconfigure .main 4 -weight 0
1532
grid rowconfigure .main 0 -weight 0
1533
grid rowconfigure .main 1 -weight 0
1534
grid rowconfigure .main 2 -weight 1
1535
grid rowconfigure .main 3 -weight 0
1536
grid rowconfigure .main 4 -weight 0
1537
 
1538
# The .main frame should not be packed into the main window yet.
1539
# Until all devices have been instantiated the various subwindows
1540
# are not yet known, so the desired size of .main is not known
1541
# either. Packing it too early and then adding more windows
1542
# causes confusion.
1543
 
1544
# }}}
1545
# {{{  Help                                                     
1546
 
1547
# ----------------------------------------------------------------------------
1548
# Two main sources of documentation are of interest to the synthetic
1549
# target. The first is the toplevel eCos documentation. The second
1550
# is the documentation specific to the generic target. Device-specific
1551
# scripts may want to add menu entries for their own documentation.
1552
#
1553
# The problems are:
1554
#   1) where to find the documentation
1555
#   2) how to view it?
1556
#
1557
# The documentation should be in the component repository. If there is
1558
# a variable ECOS_REPOSITORY then that gives the appropriate information.
1559
# Otherwise things get messy because the repository being used for
1560
# eCos may not match the repository used when building the host-side
1561
# support - the versions should match but the repository may have
1562
# moved. Never the less that is the best we can do.
1563
# NOTE: sources.redhat.com might provide another alternative, but the
1564
# documentation is not organized in the same way as the repository.
1565
#
1566
# As for how to view the documentation, this is up to user preferences
1567
# but ecosynth has built-in knowledge of three different viewers. 
1568
 
1569
namespace eval synth {
1570
 
1571
    if { $synth::flag_debug } {
1572
        synth::report "Setting up help menu\n"
1573
    }
1574
 
1575
    variable _browser1   "mozilla -remote openURL(%s)"
1576
    variable _browser2   "mozilla %s"
1577
    variable _browser3   "gnome-help-browser %s"
1578
    variable _main_help  ""
1579
    variable _synth_help ""
1580
    set _repo ""
1581
 
1582
    if { [info exists env(ECOS_REPOSITORY)] } {
1583
        set _repo $env(ECOS_REPOSITORY)
1584
    } else {
1585
        set _repo $synth::_ecos_repository
1586
    }
1587
    if { ![file exists [file join $_repo "ecos.db"]] } {
1588
        synth::report_warning "Failed to locate eCos component repository.\n   \
1589
                Please define an environment variable ECOS_REPOSITORY.\n"
1590
    } else {
1591
        # FIXME: this needs attention once the documentation is more sorted
1592
        set synth::_main_help [file join $_repo "index.html"]
1593
        if { ![file exists $synth::_main_help] } {
1594
            if { 0 } {
1595
                synth::report_warning "Failed to locate toplevel documentation file $synth::_main_help\n   \
1596
                                       Help->eCos menu option disabled.\n"
1597
            }
1598
            set synth::_main_help ""
1599
        } else {
1600
            set synth::_main_help "file://$_main_help"
1601
        }
1602
 
1603
        set synth::_synth_help [file join $_repo $synth::_ecosynth_package_dir $synth::_ecosynth_package_version "doc/hal-synth-arch.html"]
1604
        if { ![file exists $synth::_synth_help] } {
1605
            synth::report_warning "Failed to locate synthetic target documentation $synth::_synth_help\n   \
1606
                    Help->Synthetic target menu option disabled.\n"
1607
            set synth::_synth_help ""
1608
        } else {
1609
            set synth::_synth_help "file://$_synth_help"
1610
        }
1611
    }
1612
 
1613
    if { "" != $_main_help } {
1614
        .menubar.help add command -label "eCos" -command [list synth::_menu_help $synth::handle_help]
1615
    } else {
1616
        .menubar.help add command -label "eCos" -state disabled
1617
    }
1618
    if { "" != $_synth_help } {
1619
        .menubar.help add command -label "Synthetic target" -command [list synth::handle_help "$synth::_synth_help"]
1620
    } else {
1621
        .menubar.help add command -label "Synthetic target" -state disabled
1622
    }
1623
 
1624
    unset _repo
1625
 
1626
    proc handle_help { which } {
1627
        set command [format $synth::_browser1 $which]
1628
        if { 0 != [catch { eval exec -- "$command > /dev/null" } & ] } {
1629
            set command [format $synth::_browser2 $which]
1630
            if { 0 != [catch { eval exec -- "$command > /dev/null &" } ] } {
1631
                set command [format $synth::_browser3 $which]
1632
                if { 0 != [catch { eval exec -- "$command > /dev/null &"} ] } {
1633
                    synth::report_warning "Unable to start a help browser.\n   Please check the settings in Edit->Preferences.\n"
1634
                }
1635
            }
1636
        }
1637
    }
1638
 
1639
    # FIXME: add an about box as well.
1640
}
1641
 
1642
# }}}
1643
# {{{  Filters                                                  
1644
 
1645
# ----------------------------------------------------------------------------
1646
# The central text window is expected to provide the bulk of the information
1647
# to the user. This information can be voluminous, so filtering is desirable.
1648
#
1649
# There are two types of filters. System filters are provided by ecosynth
1650
# itself and by device-specific scripts. For example ecosynth has a filter
1651
# for warnings, and the console device has a filter for eCos trace messages.
1652
# In addition users can specify their own filters using regular expressions,
1653
# and those filters take priority. Note that there is an assumption that
1654
# output is predominantly line-based: if partial lines get output then
1655
# some confusion is possible.
1656
#
1657
# With tk the implementation is relatively straightforward: the text widget's
1658
# tag facility does all the hard work of controlling how text gets displayed.
1659
# It is possible to show or hide text using -elide, colours can be controlled
1660
# using -background and -foreground, ... Not all of this functionality
1661
# is made available to the user just yet.
1662
 
1663
namespace eval synth {
1664
    # The bulk of the information is held in arrays, indexed by the name of
1665
    # the filter. Lists are used to keep track of all valid names.
1666
    variable _system_filter_list      [list]
1667
    variable _system_filter_settings
1668
    variable _user_filter_list        [list]
1669
    variable _user_filter_settings
1670
 
1671
    # Does a given system filter already exist?
1672
    proc filter_exists { name } {
1673
        set result 0
1674
        if { -1 != [lsearch -exact $synth::_system_filter_list $name] } {
1675
            set result 1
1676
        }
1677
        return $result
1678
    }
1679
 
1680
    proc filter_get_list { } {
1681
        return $synth::_system_filter_list
1682
    }
1683
 
1684
    # Parsing support. All filters take a number of standard options:
1685
    #
1686
    #   -text "message"      - how to identify this filter to the user
1687
    #   -hide [0|1]          - whether or not this text should be hidden by default
1688
    #   -foreground <colour>
1689
    #   -background <colour>
1690
    #
1691
    # The details of the currently supported options are known only to
1692
    # filter_parse_options and filter_add, allowing new options such
1693
    # as font manipulation to be added in future.
1694
    #
1695
    # There are two ways of adding a filter. filter_add is typically used
1696
    # inside ecosynth.tcl with known good data. filter_add_parsed is
1697
    # used with user-provided data, e.g. from the target definition file,
1698
    # after a call to filter_validate.
1699
    proc filter_parse_options { arg_list result_ref message_ref } {
1700
        upvar 1 $result_ref result
1701
        upvar 1 $message_ref message
1702
        set message ""
1703
 
1704
        set text_set        0
1705
        set hide_set        0
1706
        set foreground_set  0
1707
        set background_set  0
1708
 
1709
        set len [llength $arg_list]
1710
        for { set i 0 } { $i < $len } { incr i } {
1711
            set arg [lindex $arg_list $i]
1712
            if { ( "-text" == $arg) ||
1713
                 ( "-hide" == $arg) ||
1714
                 ( "-foreground" == $arg) || ( "-fg" == $arg) ||
1715
                 ( "-background" == $arg) || ( "-bg" == $arg) } {
1716
 
1717
                 incr i
1718
                 if { $i >= $len } {
1719
                     append message "    Missing data after argument $arg\n"
1720
                 } else {
1721
                     set data [lindex $arg_list $i]
1722
                     if { "-text" == $arg } {
1723
                         if { $text_set } {
1724
                             append message "    Attempt to set -text option twice.\n"
1725
                         } else {
1726
                             set text_set 1
1727
                             set result("-text") $data
1728
                         }
1729
                     } elseif { "-hide" == $arg } {
1730
                         if { $hide_set } {
1731
                             append message "    Attempt to set -hide option twice.\n"
1732
                         } else {
1733
                             set hide_set 1
1734
                             if { ! [string is boolean -strict $data] } {
1735
                                 append message "    -hide should be given a boolean value, not \"$data\"\n"
1736
                             } else {
1737
                                 set result("-hide") [expr $data ? 1 : 0]
1738
                             }
1739
                         }
1740
                     } elseif { ( "-foreground" == $arg) || ( "-fg" == $arg ) } {
1741
                         if { $foreground_set } {
1742
                             append message "    Attempt to set -foreground twice.\n"
1743
                         } else {
1744
                             set foreground_set 1
1745
                             # FIXME: is there some way of validating this color?
1746
                             set result("-foreground") $data
1747
                         }
1748
                     } elseif { ( "-background" == $arg) || ( "-bg" == $arg ) } {
1749
                         if { $background_set } {
1750
                             append message "    Attempt to set -background twice.\n"
1751
                         } else {
1752
                             set background_set 1
1753
                             # FIXME: is there some way of validating this color?
1754
                             set result("-background") $data
1755
                         }
1756
                     }
1757
                 }
1758
             } else {
1759
                 append message "    Unknown option \"$arg\".\n"
1760
             }
1761
        }
1762
 
1763
        if { "" == $message } {
1764
            return 1
1765
        } else {
1766
            return 0
1767
        }
1768
    }
1769
 
1770
    # Add a new system filter, after the options have been parsed 
1771
    proc filter_add_parsed { name data_ref } {
1772
        upvar 1 $data_ref data
1773
 
1774
        set text       $name
1775
        set hide       0
1776
        set foreground "<default>"
1777
        set background "<default>"
1778
        if { [info exists data("-text")] } {
1779
            set text $data("-text")
1780
        }
1781
        if { [info exists data("-hide")] } {
1782
            set hide $data("-hide")
1783
        }
1784
        if { [info exists data("-foreground")] } {
1785
            set foreground $data("-foreground")
1786
        }
1787
        if { [info exists data("-background")] } {
1788
            set background $data("-background")
1789
        }
1790
 
1791
        if { $hide } {
1792
            .main.centre.text tag configure $name -elide 1
1793
        } else {
1794
            .main.centre.text tag configure $name -elide 0
1795
        }
1796
        if { "<default>" == $foreground } {
1797
            .main.centre.text tag configure $name -foreground [.main.centre.text cget -foreground]
1798
        } else {
1799
            set msg ""
1800
            if [catch { .main.centre.text tag configure $name -foreground $foreground } msg ] {
1801
                synth::report_warning "Unable to configure color \"$foreground\"\n    $msg\n"
1802
                set foreground "<default>"
1803
                .main.centre.text tag configure $name -foreground [.main.centre.text cget -foreground]
1804
            }
1805
        }
1806
        if { "<default>" == $background } {
1807
            .main.centre.text tag configure $name -background [.main.centre.text cget -background]
1808
        } else {
1809
            set msg ""
1810
            if [catch { .main.centre.text tag configure $name -background $background } msg ] {
1811
                synth::report_warning "Unable to configure color \"$background\"\n    $msg\n"
1812
                set background "<default>"
1813
                .main.centre.text tag configure $name -background [.main.centre.text cget -background]
1814
            }
1815
        }
1816
 
1817
        lappend synth::_system_filter_list $name
1818
        set synth::_system_filter_settings($name,text)       $text
1819
        set synth::_system_filter_settings($name,hide)       $hide
1820
        set synth::_system_filter_settings($name,foreground) $foreground
1821
        set synth::_system_filter_settings($name,background) $background
1822
 
1823
        # System tags should only get added during initialization. Hence the
1824
        # first time the system filters window is brought up all filters
1825
        # should be defined. However, just in case a new filter is added
1826
        # in the middle of a run...
1827
        if { [winfo exists .system_filters] } {
1828
            destroy .system_filters
1829
        }
1830
    }
1831
 
1832
    # Add a new system filter, performing the appropriate parsing.
1833
    proc filter_add { name args } {
1834
 
1835
        if { [synth::filter_exists $name] } {
1836
            synth::internal_error "attempt to install filter $name twice.\n"
1837
        }
1838
        array set data [list]
1839
        set   msg ""
1840
 
1841
        if { ![synth::filter_parse_options $args data msg] } {
1842
            # Any dubious arguments to the internal filter_add are treated as fatal.
1843
            synth::internal_error "unable to create new filter $name.\n$msg"
1844
        } else {
1845
            filter_add_parsed $name data
1846
        }
1847
    }
1848
 
1849
    filter_add "report"  -text "ecosynth messages"
1850
    filter_add "error"   -text "ecosynth errors"   -foreground red
1851
    # amber is not a standard colour. Amber leds emit light in the range
1852
    # 595-605 nm, corresponding to rgb values of approx. FF4200.
1853
    # OrangeRed is close enough at FF4500
1854
    filter_add "warning" -text "ecosynth warnings" -foreground OrangeRed
1855
 
1856
    # Bring up the system filters window, creating it if necessary.
1857
    # Keeping the toplevel around but iconified/withdrawn when
1858
    # unwanted means that properties such as size and position will
1859
    # tend to be preserved.
1860
    variable _system_filter_new_settings
1861
    variable _system_filter_widgets
1862
 
1863
    proc _menu_view_system_filters { } {
1864
        if { [winfo exists .system_filters] } {
1865
            if { "normal" == [wm state .system_filters] } {
1866
                raise .system_filters
1867
            } else {
1868
                wm deiconify .system_filters
1869
            }
1870
            return
1871
        }
1872
        toplevel .system_filters
1873
        wm title .system_filters "ecosynth system filters"
1874
        wm protocol .system_filters "WM_DELETE_WINDOW" [list synth::_menu_view_system_filters_cancel]
1875
        wm group .system_filters .
1876
 
1877
        frame .system_filters.main
1878
        label .system_filters.main.header1 -text "Filter"
1879
        label .system_filters.main.header2 -text "Hide"
1880
        label .system_filters.main.header3 -text "Foreground"
1881
        label .system_filters.main.header4 -text "Background"
1882
        set text_fg [.system_filters.main.header1 cget -foreground]
1883
        frame .system_filters.main.row0 -height 1 -background $text_fg
1884
        frame .system_filters.main.row2 -height 1 -background $text_fg
1885
        frame .system_filters.main.col0 -width 1  -background $text_fg
1886
        frame .system_filters.main.col2 -width 1  -background $text_fg
1887
        frame .system_filters.main.col4 -width 1  -background $text_fg
1888
        frame .system_filters.main.col6 -width 1  -background $text_fg
1889
        frame .system_filters.main.col8 -width 1  -background $text_fg
1890
        grid .system_filters.main.row0 -row 0 -column 0 -columnspan 9 -sticky ew
1891
        grid .system_filters.main.header1 -row 1 -column 1 -sticky news
1892
        grid .system_filters.main.header2 -row 1 -column 3 -sticky news
1893
        grid .system_filters.main.header3 -row 1 -column 5 -sticky news
1894
        grid .system_filters.main.header4 -row 1 -column 7 -sticky news
1895
        grid .system_filters.main.row2 -row 2 -column 0 -columnspan 9 -sticky ew
1896
 
1897
        set row 3
1898
        foreach filter $synth::_system_filter_list {
1899
            set synth::_system_filter_new_settings($filter,hide) $synth::_system_filter_settings($filter,hide)
1900
            set synth::_system_filter_new_settings($filter,foreground) $synth::_system_filter_settings($filter,foreground)
1901
            set synth::_system_filter_new_settings($filter,background) $synth::_system_filter_settings($filter,background)
1902
 
1903
            set synth::_system_filter_widgets($filter,label) \
1904
                    [label .system_filters.main.filter_name_$row -text $synth::_system_filter_settings($filter,text)]
1905
            set synth::_system_filter_widgets($filter,hide) \
1906
                    [checkbutton .system_filters.main.filter_hide_$row -borderwidth 2 -indicatoron false -selectcolor "" \
1907
                    -image $synth::image_tick_no -selectimage $synth::image_tick_yes -variable synth::_system_filter_new_settings($filter,hide)]
1908
            set synth::_system_filter_widgets($filter,foreground) [button .system_filters.main.filter_foreground_$row -borderwidth 2 \
1909
                    -command [list synth::_menu_view_system_filters_choose_foreground $filter]]
1910
            set synth::_system_filter_widgets($filter,background) [button .system_filters.main.filter_background_$row -borderwidth 2 \
1911
                    -command [list synth::_menu_view_system_filters_choose_background $filter]]
1912
 
1913
            grid .system_filters.main.filter_name_$row       -row $row -column 1 -sticky news
1914
            grid .system_filters.main.filter_hide_$row       -row $row -column 3 -sticky news
1915
            grid .system_filters.main.filter_foreground_$row -row $row -column 5 -sticky news
1916
            grid .system_filters.main.filter_background_$row -row $row -column 7 -sticky news
1917
 
1918
            incr row
1919
            frame .system_filters.main.row$row -height 1 -background $text_fg
1920
            grid .system_filters.main.row$row -row $row -column 0 -columnspan 9 -sticky ew
1921
            incr row
1922
        }
1923
        grid .system_filters.main.col0 -row 0 -column 0 -rowspan $row -sticky ns
1924
        grid .system_filters.main.col2 -row 0 -column 2 -rowspan $row -sticky ns
1925
        grid .system_filters.main.col4 -row 0 -column 4 -rowspan $row -sticky ns
1926
        grid .system_filters.main.col6 -row 0 -column 6 -rowspan $row -sticky ns
1927
        grid .system_filters.main.col8 -row 0 -column 8 -rowspan $row -sticky ns
1928
 
1929
        for { set i 0 } { $i < $row } { incr i 2 } {
1930
            grid rowconfigure .system_filters.main $i -weight 0
1931
        }
1932
        for { set i 1 } { $i < $row } { incr i 2 } {
1933
            grid rowconfigure .system_filters.main $i -weight 1
1934
        }
1935
        for { set i 0 } { $i < 9 } { incr i 2 } {
1936
            grid columnconfigure .system_filters.main $i -weight 0
1937
        }
1938
        for { set i 1 } { $i < 9 } { incr i 2 } {
1939
            grid columnconfigure .system_filters.main $i -weight 1
1940
        }
1941
 
1942
        pack .system_filters.main -side top -fill both -expand 1 -pady 4 -padx 4
1943
 
1944
        # FIXME: add try and revert buttons
1945
        frame .system_filters.buttons
1946
        button .system_filters.buttons.ok     -text "OK"     -command [list synth::_menu_view_system_filters_ok]
1947
        button .system_filters.buttons.cancel -text "Cancel" -command [list synth::_menu_view_system_filters_cancel]
1948
        pack .system_filters.buttons.ok .system_filters.buttons.cancel -side left -expand 1
1949
        pack .system_filters.buttons -side bottom -fill x -pady 4
1950
 
1951
        frame .system_filters.separator -height 2 -borderwidth 1 -relief sunken
1952
        pack .system_filters.separator -side bottom -fill x -pady 4
1953
 
1954
        bind .system_filters <KeyPress-Return> [list synth::_menu_view_system_filters_ok]
1955
        bind .system_filters <KeyPress-Escape> [list synth::_menu_view_system_filters_cancel]
1956
 
1957
        synth::_menu_view_system_filters_reset
1958
    }
1959
 
1960
    proc _menu_view_system_filters_reset { } {
1961
        foreach filter $synth::_system_filter_list {
1962
            set synth::_system_filter_new_settings($filter,hide) $synth::_system_filter_settings($filter,hide)
1963
            set synth::_system_filter_new_settings($filter,foreground) $synth::_system_filter_settings($filter,foreground)
1964
            set synth::_system_filter_new_settings($filter,background) $synth::_system_filter_settings($filter,background)
1965
 
1966
            set colour $synth::_system_filter_new_settings($filter,foreground)
1967
            if { "<default>" == $colour } {
1968
                set colour [.system_filters.main.header1 cget -foreground]
1969
            }
1970
            $synth::_system_filter_widgets($filter,label) configure -foreground $colour
1971
            $synth::_system_filter_widgets($filter,foreground) configure -background $colour -activebackground $colour
1972
 
1973
            set colour $synth::_system_filter_new_settings($filter,background)
1974
            if { "<default>" == $colour } {
1975
                set colour [.system_filters.main.header1 cget -background]
1976
            }
1977
            $synth::_system_filter_widgets($filter,label) configure -background $colour
1978
            $synth::_system_filter_widgets($filter,background) configure -background $colour -activebackground $colour
1979
        }
1980
    }
1981
 
1982
    # Change a colour. For now this involves calling Tk's chooseColor utility.
1983
    # This is simple but not quite right: it would be much better to allow
1984
    # the foreground and background to be modified in the same dialog, providing
1985
    # immediate feedback on how the text will actually appear; it should also
1986
    # provide some simple way of reverting to the default.
1987
    proc _menu_view_system_filters_choose_foreground { filter } {
1988
        set current_colour $synth::_system_filter_new_settings($filter,foreground)
1989
        if { "<default>" == $current_colour } {
1990
            set current_colour [.system_filters.main.header1 cget -foreground]
1991
        }
1992
        set new_colour [tk_chooseColor -parent .system_filters -title "$synth::_system_filter_settings($filter,text) foreground" \
1993
                -initialcolor $current_colour]
1994
        if { "" != $new_colour } {
1995
            set synth::_system_filter_new_settings($filter,foreground) $new_colour
1996
            $synth::_system_filter_widgets($filter,label) configure -foreground $new_colour
1997
            $synth::_system_filter_widgets($filter,foreground) configure -background $new_colour -activebackground $new_colour
1998
        }
1999
    }
2000
 
2001
    proc _menu_view_system_filters_choose_background { filter } {
2002
        set current_colour $synth::_system_filter_new_settings($filter,background)
2003
        if { "<default>" == $current_colour } {
2004
            set current_colour [.system_filters.main.header1 cget -background]
2005
        }
2006
        set new_colour [tk_chooseColor -parent .system_filters -title "$synth::_system_filter_settings($filter,text) background" \
2007
                -initialcolor $current_colour]
2008
        if { "" != $new_colour } {
2009
            set synth::_system_filter_new_settings($filter,background) $new_colour
2010
            $synth::_system_filter_widgets($filter,label) configure -background $new_colour
2011
            $synth::_system_filter_widgets($filter,background) configure -background $new_colour -activebackground $new_colour
2012
        }
2013
    }
2014
 
2015
    proc _menu_view_system_filters_ok { } {
2016
        wm withdraw .system_filters
2017
        foreach filter $synth::_system_filter_list {
2018
            if { $synth::_system_filter_settings($filter,hide) != $synth::_system_filter_new_settings($filter,hide) } {
2019
                set hide $synth::_system_filter_new_settings($filter,hide)
2020
                set synth::_system_filter_settings($filter,hide) $hide
2021
                if { $hide } {
2022
                    .main.centre.text tag configure $filter -elide 1
2023
                } else {
2024
                    .main.centre.text tag configure $filter -elide 0
2025
                }
2026
            }
2027
            if { $synth::_system_filter_settings($filter,foreground) != $synth::_system_filter_new_settings($filter,foreground) } {
2028
                set foreground $synth::_system_filter_new_settings($filter,foreground)
2029
                set synth::_system_filter_settings($filter,foreground) $foreground
2030
                .main.centre.text tag configure $filter -foreground $foreground
2031
            }
2032
            if { $synth::_system_filter_settings($filter,background) != $synth::_system_filter_new_settings($filter,background) } {
2033
                set background $synth::_system_filter_new_settings($filter,background)
2034
                set synth::_system_filter_settings($filter,background) $background
2035
                .main.centre.text tag configure $filter -background $background
2036
            }
2037
        }
2038
    }
2039
 
2040
    proc _menu_view_system_filters_cancel { } {
2041
        wm withdraw .system_filters
2042
        synth::_menu_view_system_filters_reset
2043
    }
2044
 
2045
    # Now add a suitable entry to the View menu.
2046
    .menubar.view add command -label "System filters..." -command [list synth::_menu_view_system_filters]
2047
 
2048
    # User filters.
2049
    # FIXME: implement
2050
    # .menubar.view add command -label "User filters..." -command [list synth::_menu_view_filters] -state disabled
2051
}
2052
 
2053
# }}}
2054
# {{{  Text window                                              
2055
 
2056
# ----------------------------------------------------------------------------
2057
# The central text window is expected to provide the bulk of the information
2058
# to the user. Various filtering mechanisms are desirable. For example the
2059
# user should be able to control whether or not eCos trace messages are
2060
# currently visible, not to mention other characteristics such as font
2061
# and colours. The text widget's tag mechanism makes this relatively simple.
2062
 
2063
namespace eval synth {
2064
 
2065
    # Should the user be able to edit the text window, e.g. to add annotations?
2066
    # This is disabled by default but can be enabled.
2067
    variable flag_read_only 1
2068
 
2069
    # By default disable wrapping. Possibly it should be possible to
2070
    # enable this on a per-tag basis.
2071
    .main.centre.text configure -wrap "none"
2072
 
2073
    # Give the text widget the focus by default. That way operations
2074
    # like page-up work immediately.
2075
    focus .main.centre.text
2076
 
2077
    # If editing is currently disallowed, do not accept any input.
2078
    # The code below is probably not quite sufficient, it is
2079
    # ASCII-centric. A separate binding for Alt- sequences ensures
2080
    # that the top-level menu processing continues to work.
2081
    # Similarly a separate binding for Control- sequences ensures
2082
    # that the shortcuts continue to work.
2083
    bind .main.centre.text <Alt-KeyPress> {
2084
        continue
2085
    }
2086
    bind .main.centre.text <Control-KeyPress> {
2087
        continue
2088
    }
2089
    bind .main.centre.text <KeyPress> {
2090
        if { !$synth::flag_read_only } {
2091
            continue
2092
        } elseif { 0 != [llength %A] } {
2093
            break
2094
        } elseif { ("Return" == "%K") || ("Tab" == "%K") || ("space" == "%K") } {
2095
            break
2096
        } else {
2097
            continue
2098
        }
2099
    }
2100
    # There are a few special control- bindings built in to the Tk text
2101
    # widget which perform editing operations
2102
    bind .main.centre.text <Control-KeyPress-h> {
2103
        if { !$synth::flag_read_only } {
2104
            continue
2105
        } else {
2106
            break
2107
        }
2108
    }
2109
    bind .main.centre.text <Control-KeyPress-d> {
2110
        if { !$synth::flag_read_only } {
2111
            continue
2112
        } else {
2113
            break
2114
        }
2115
    }
2116
    bind .main.centre.text <Control-KeyPress-k> {
2117
        if { !$synth::flag_read_only } {
2118
            continue
2119
        } else {
2120
            break
2121
        }
2122
    }
2123
    bind .main.centre.text <Control-KeyPress-o> {
2124
        if { !$synth::flag_read_only } {
2125
            continue
2126
        } else {
2127
            break
2128
        }
2129
    }
2130
    bind .main.centre.text <Control-KeyPress-t> {
2131
        if { !$synth::flag_read_only } {
2132
            continue
2133
        } else {
2134
            break
2135
        }
2136
    }
2137
 
2138
    # Implement support for the normal edit menu operations.
2139
    # FIXME: add a search facility
2140
    .menubar.edit insert end command -label "Cut"        -command [list synth::_handle_edit_cut]        -underline 2 -accelerator "Ctrl-X" -state disabled
2141
    .menubar.edit insert end command -label "Copy"       -command [list synth::_handle_edit_copy]       -underline 0 -accelerator "Ctrl-C"
2142
    .menubar.edit insert end command -label "Paste"      -command [list synth::_handle_edit_paste]      -underline 0 -accelerator "Ctrl-V" -state disabled
2143
    .menubar.edit insert end command -label "Clear"      -command [list synth::_handle_edit_clear]      -underline 3 -accelerator "Del"    -state disabled
2144
    .menubar.edit insert end command -label "Select All" -command [list synth::_handle_edit_select_all] -underline 9 -accelerator "Ctrl-A"
2145
    .menubar.edit insert end checkbutton -label "Read Only" -variable synth::flag_read_only
2146
    .menubar.edit insert end separator
2147
    proc _trace_read_only { name1 name2 op } {
2148
        if { !$synth::flag_read_only } {
2149
            .menubar.edit entryconfigure "Cut"   -state normal
2150
            .menubar.edit entryconfigure "Paste" -state normal
2151
            .menubar.edit entryconfigure "Clear" -state normal
2152
            .toolbar.cut configure -state normal
2153
            .toolbar.paste configure -state normal
2154
        } else {
2155
            .menubar.edit entryconfigure "Cut"   -state disabled
2156
            .menubar.edit entryconfigure "Paste" -state disabled
2157
            .menubar.edit entryconfigure "Clear" -state disabled
2158
            .toolbar.cut configure -state disabled
2159
            .toolbar.paste configure -state disabled
2160
        }
2161
    }
2162
    trace variable synth::flag_read_only "w" synth::_trace_read_only
2163
 
2164
    # Support for cut'n'paste etc. The widget does most of the hard
2165
    # work, but this code has to distinguish between read-only and
2166
    # read-write windows.
2167
    #
2168
    # Some operations such as clear may operate on everything in the
2169
    # selection, including hidden text that happens to be in the
2170
    # range. That may or may not be the right thing to do. It is right
2171
    # if the intent is to get rid of all events during a period of
2172
    # time, but wrong if the user wants to get rid of specific text.
2173
    bind . <Control-KeyPress-x> [list synth::_handle_edit_cut]
2174
    bind . <Control-KeyPress-c> [list synth::_handle_edit_copy]
2175
    bind . <Control-KeyPress-v> [list synth::_handle_edit_paste]
2176
    bind . <KeyPress-Delete>    [list synth::_handle_edit_clear]
2177
    bind . <Control-KeyPress-a> [list synth::_handle_edit_select_all]
2178
 
2179
    bind .main.centre.text <<Paste>> {
2180
        if { !$synth::flag_read_only } {
2181
            continue
2182
        } else {
2183
            break
2184
        }
2185
    }
2186
    bind .main.centre.text <<Cut>> {
2187
        if { !$synth::flag_read_only } {
2188
            continue
2189
        } else {
2190
            break
2191
        }
2192
    }
2193
    bind .main.centre.text <<Clear>> {
2194
        if { !$synth::flag_read_only } {
2195
            continue
2196
        } else {
2197
            break
2198
        }
2199
    }
2200
 
2201
    proc _handle_edit_cut { } {
2202
        event generate .main.centre.text "<<Cut>>"
2203
    }
2204
 
2205
    proc _handle_edit_copy { } {
2206
        event generate .main.centre.text "<<Copy>>"
2207
    }
2208
 
2209
    proc _handle_edit_paste { } {
2210
        event generate .main.centre.text "<<Paste>>"
2211
    }
2212
 
2213
    proc _handle_edit_clear { } {
2214
        event generate .main.centre.text "<<Clear>>"
2215
    }
2216
 
2217
    proc _handle_edit_select_all { } {
2218
        .main.centre.text tag add sel 1.0 "end - 1 chars"
2219
    }
2220
 
2221
    # Most output to the text window goes through this routine. It inserts
2222
    # some text with an appropriate tag. In addition it will ensure that
2223
    # the new text is visible if appropriate, and if a logfile has been
2224
    # specified then that will be updated as well.
2225
    proc output { msg tag } {
2226
        set ytail [lindex [.main.centre.text yview] 1]
2227
        set xhead [lindex [.main.centre.text xview] 0]
2228
        .main.centre.text insert end $msg $tag
2229
        if { (1.0 == $ytail) && (0.0 == $xhead) } {
2230
            .main.centre.text see end
2231
        }
2232
        if { "" != $synth::_logfd } {
2233
            puts -nonewline $synth::_logfd $msg
2234
        }
2235
    }
2236
 
2237
    # Text output is now possible, so flush anything that is still buffered.
2238
    # xview and yview may not give the right results until the window
2239
    # is mapped, so always make the last text visible.
2240
    set synth::flag_gui_ready 1
2241
    synth::_flush_output
2242
    .main.centre.text see end
2243
 
2244
 
2245
    # Support for saving the current document. Save applies only to
2246
    # the currently visible text. SaveAll gives the hidden text as
2247
    # well.
2248
    variable _savefile ""
2249
    proc _handle_file_save { } {
2250
        if { "" == $synth::_savefile } {
2251
            set synth::_savefile [tk_getSaveFile -parent .]
2252
            if { "" == $synth::_savefile } {
2253
                return
2254
            }
2255
        }
2256
        set msg ""
2257
        if { 0 != [catch { set fd [open $synth::_savefile "w"] } msg] } {
2258
            synth::report_error "$msg\n"
2259
            if { $synth::_system_filter_settings(error,hide) } {
2260
                tk_messageBox -type "ok" -icon "error" -parent . -message "$msg\n"
2261
            }
2262
            return
2263
        }
2264
        set number_lines [expr int([.main.centre.text index end])]
2265
        for { set i 1 } { $i < $number_lines } { incr i } {
2266
            set tags [.main.centre.text tag names "[set i].0"]
2267
            if {[llength $tags] > 0 } {
2268
                set tag [lindex $tags 0]
2269
                if { [info exists synth::_system_filter_settings($tag,hide)] &&
2270
                     $synth::_system_filter_settings($tag,hide) } {
2271
                    continue
2272
                }
2273
            }
2274
            puts $fd [.main.centre.text get "[set i].0" "[set i].end"]
2275
        }
2276
        close $fd
2277
    }
2278
 
2279
    proc _handle_file_save_as { } {
2280
        set new_savefile [tk_getSaveFile -parent .]
2281
        if { "" == $new_savefile } {
2282
            return
2283
        }
2284
        set synth::_savefile $new_savefile
2285
        synth::_handle_file_save
2286
    }
2287
 
2288
    proc _handle_file_save_all { } {
2289
        set new_savefile [tk_getSaveFile -parent .]
2290
        if { "" == $new_savefile } {
2291
            return
2292
        }
2293
        set msg ""
2294
        if { 0 != [catch { set fd [open $new_savefile "w"] } msg] } {
2295
            synth::report_error "$msg\n"
2296
            if { $synth::_system_filter_settings(error,hide) } {
2297
                tk_messageBox -type "ok" -icon "error" -parent . -message "$msg\n"
2298
            }
2299
            return
2300
        }
2301
        puts -nonewline $fd [.main.centre.text get 1.0 end]
2302
        close $fd
2303
    }
2304
}
2305
 
2306
# }}}
2307
# {{{  Heartbeat and status                                     
2308
 
2309
# ----------------------------------------------------------------------------
2310
# This code manages a status line at the bottom of the main window.
2311
# This involves a little heartbeat window, a label with the
2312
# text Running or Exited, some padding, and an additional status
2313
# line for use by other code.
2314
#
2315
# A variety of heartbeats have been attempted. The current one is
2316
# still not very good, but will do for now. Others are if 0'd out.
2317
# Note that these others may require additional images to be
2318
# preloaded.
2319
 
2320
namespace eval synth {
2321
    frame .status -borderwidth 1 -relief groove
2322
 
2323
    if { 1 } {
2324
        # The eCos logo, bouncing horizontally
2325
        variable _heartbeat_image_width [image width $synth::image_running1]
2326
        variable _heartbeat_offset 0
2327
        variable _heartbeat_ltor   1
2328
 
2329
        frame .status.heartbeat -width $synth::_heartbeat_image_width -height [image height $synth::image_running1]
2330
        pack  .status.heartbeat -side left
2331
        label .status.heartbeat.image -image $synth::image_running1 -anchor w -borderwidth 0
2332
        place .status.heartbeat.image -x $synth::_heartbeat_offset -y 0
2333
 
2334
        proc _heartbeat_update { } {
2335
            if { ! $synth::ecos_running } {
2336
                place configure .status.heartbeat.image -x 0 -y 0
2337
            } else {
2338
                if { $synth::_heartbeat_ltor } {
2339
                    incr synth::_heartbeat_offset 4
2340
                } else {
2341
                    incr synth::_heartbeat_offset -4
2342
                }
2343
                place configure .status.heartbeat.image -x $synth::_heartbeat_offset
2344
 
2345
                if { $synth::_heartbeat_offset < (5 - $synth::_heartbeat_image_width) } {
2346
                    set synth::_heartbeat_ltor 1
2347
                } elseif { $synth::_heartbeat_offset > ( $synth::_heartbeat_image_width -5) } {
2348
                    set synth::_heartbeat_ltor 0
2349
                }
2350
                after 100 synth::_heartbeat_update
2351
            }
2352
        }
2353
        after 100 synth::_heartbeat_update
2354
 
2355
    } elseif { 0 } {
2356
        # The eCos logo, alternating between a normal and an inverse version
2357
        variable _heartbeat_image_width [image width $synth::image_running1]
2358
        variable _heartbeat_inverse ""
2359
        variable _heartbeat_normal  ""
2360
        variable _heartbeat_inverse_width     1
2361
        variable _heartbeat_normal_width      1
2362
 
2363
        canvas .status.heartbeat_canvas -width [image width $synth::image_running1] -height [image height $synth::image_running1]
2364
        pack .status.heartbeat_canvas -side left
2365
        label .status.heartbeat_canvas.background -image $synth::image_running1 -anchor w -borderwidth 0
2366
        label .status.heartbeat_canvas.inverse    -image $synth::image_running2 -anchor w -borderwidth 0
2367
        label .status.heartbeat_canvas.normal     -image $synth::image_running1 -anchor w -borderwidth 0
2368
        .status.heartbeat_canvas create window 0 0 -anchor nw -window .status.heartbeat_canvas.background
2369
        set synth::_heartbeat_inverse [.status.heartbeat_canvas create window 0 0 -anchor nw -window .status.heartbeat_canvas.inverse]
2370
        raise .status.heartbeat_canvas.inverse .status.heartbeat_canvas.background
2371
        set synth::_heartbeat_normal  [.status.heartbeat_canvas create window 0 0 -anchor nw -window .status.heartbeat_canvas.normal]
2372
        raise .status.heartbeat_canvas.normal .status.heartbeat_canvas.inverse
2373
 
2374
        .status.heartbeat_canvas itemconfigure $synth::_heartbeat_inverse -width $synth::_heartbeat_inverse_width
2375
        .status.heartbeat_canvas itemconfigure $synth::_heartbeat_normal  -width $synth::_heartbeat_normal_width
2376
 
2377
        proc _heartbeat_update { } {
2378
            if { ! $synth::ecos_running } {
2379
                .status.heartbeat_canvas delete $synth::_heartbeat_inverse
2380
                .status.heartbeat_canvas delete $synth::_heartbeat_normal
2381
            } else {
2382
                if { $synth::_heartbeat_inverse_width < $synth::_heartbeat_image_width } {
2383
                    incr synth::_heartbeat_inverse_width 2
2384
                    .status.heartbeat_canvas itemconfigure $synth::_heartbeat_inverse -width $synth::_heartbeat_inverse_width
2385
                } elseif { $synth::_heartbeat_normal_width < $synth::_heartbeat_image_width } {
2386
                    incr synth::_heartbeat_normal_width 2
2387
                    .status.heartbeat_canvas itemconfigure $synth::_heartbeat_normal -width $synth::_heartbeat_normal_width
2388
                } else {
2389
                    set synth::_heartbeat_inverse_width 1
2390
                    set synth::_heartbeat_normal_width 1
2391
                    .status.heartbeat_canvas itemconfigure $synth::_heartbeat_inverse -width $synth::_heartbeat_inverse_width
2392
                    .status.heartbeat_canvas itemconfigure $synth::_heartbeat_normal  -width $synth::_heartbeat_normal_width
2393
                }
2394
                after 100 synth::_heartbeat_update
2395
            }
2396
        }
2397
        after 100 synth::_heartbeat_update
2398
 
2399
    } elseif { 0 } {
2400
        # The eCos logo moving left to right, then replaced by a slightly smaller
2401
        # mirror version moving right to left, sort of as if rotating around a torus
2402
        variable _heartbeat_image_width [image width $synth::image_running1]
2403
        variable _heartbeat_offset [expr -1 * [image width $synth::image_running1]]
2404
        variable _heartbeat_ltor 1
2405
 
2406
        frame .status.heartbeat -width $synth::_heartbeat_image_width -height [image height $synth::image_running1]
2407
        pack  .status.heartbeat -side left
2408
        label .status.heartbeat.label -image $synth::image_running1 -anchor w -borderwidth 0
2409
 
2410
        place .status.heartbeat.label -x $synth::_heartbeat_offset -y 0
2411
 
2412
        proc _heartbeat_update { } {
2413
            if { ! $synth::ecos_running } {
2414
                .status.heartbeat.label configure -image $synth::image_running1
2415
                place configure .status.heartbeat.label -x 0
2416
            } else {
2417
                if { $synth::_heartbeat_ltor } {
2418
                    incr synth::_heartbeat_offset 4
2419
                } else {
2420
                    incr synth::_heartbeat_offset -4
2421
                }
2422
                place configure .status.heartbeat.label -x $synth::_heartbeat_offset
2423
                if { $synth::_heartbeat_offset < (0 - $synth::_heartbeat_image_width) } {
2424
                    .status.heartbeat.label configure -image $synth::image_running1
2425
                    set synth::_heartbeat_ltor 1
2426
                } elseif { $synth::_heartbeat_offset > $synth::_heartbeat_image_width } {
2427
                    .status.heartbeat.label configure -image $synth::image_running3
2428
                    set synth::_heartbeat_ltor 0
2429
                }
2430
                after 100 synth::_heartbeat_update
2431
            }
2432
        }
2433
        after 100 synth::_heartbeat_update
2434
    }
2435
 
2436
    label .status.running -width 10 -text "Running" -anchor w
2437
    pack .status.running -side left
2438
    proc _heartbeat_exit_hook { arg_list } {
2439
        .status.running configure -text "Exited"
2440
    }
2441
    synth::hook_add "ecos_exit" synth::_heartbeat_exit_hook
2442
 
2443
    label .status.text -text "" -anchor w
2444
    pack .status.text -side left -fill x -expand 1
2445
    pack .status -side bottom -fill x
2446
}
2447
 
2448
# }}}
2449
# {{{  Preferences                                              
2450
 
2451
namespace eval synth {
2452
 
2453
    if { $synth::flag_debug } {
2454
        synth::report "Setting up preferences window.\n"
2455
    }
2456
 
2457
    variable _pref_browser1 ""
2458
    variable _pref_browser2 ""
2459
    variable _pref_browser3 ""
2460
 
2461
    toplevel .preferences
2462
    wm title .preferences "ecosynth preferences"
2463
    wm withdraw .preferences
2464
    wm protocol .preferences "WM_DELETE_WINDOW" [list synth::_menu_edit_preferences_cancel]
2465
    wm group .preferences .
2466
 
2467
    # NOTE: the fixed-size padx/pady arguments should probably be determined 
2468
    # using a font calculation. The fixed width for the column 0 entries is also
2469
    # a cheat.
2470
    set _pref_col0_width 24
2471
 
2472
    frame .preferences.help
2473
    frame .preferences.help.frame -borderwidth 2 -relief groove
2474
    pack .preferences.help.frame -fill both -expand 1 -pady 10
2475
    frame .preferences.help.frame.blank -height 10
2476
    label .preferences.help.frame.label1 -text "Preferred browser"  -width $synth::_pref_col0_width -anchor w
2477
    label .preferences.help.frame.label2 -text "First alternative"  -width $synth::_pref_col0_width -anchor w
2478
    label .preferences.help.frame.label3 -text "Second alternative" -width $synth::_pref_col0_width -anchor w
2479
    entry .preferences.help.frame.entry1 -width 40 -relief sunken -textvariable synth::_pref_browser1
2480
    entry .preferences.help.frame.entry2 -width 40 -relief sunken -textvariable synth::_pref_browser2
2481
    entry .preferences.help.frame.entry3 -width 40 -relief sunken -textvariable synth::_pref_browser3
2482
    grid .preferences.help.frame.blank -row 0 -column 0
2483
    grid .preferences.help.frame.label1  -row 1 -column 0 -sticky w
2484
    grid .preferences.help.frame.label2  -row 2 -column 0 -sticky w
2485
    grid .preferences.help.frame.label3  -row 3 -column 0 -sticky w
2486
    grid .preferences.help.frame.entry1  -row 1 -column 1 -sticky ew
2487
    grid .preferences.help.frame.entry2  -row 2 -column 1 -sticky ew
2488
    grid .preferences.help.frame.entry3  -row 3 -column 1 -sticky ew
2489
    grid columnconfigure .preferences.help.frame 0 -weight 0
2490
    grid columnconfigure .preferences.help.frame 1 -weight 1
2491
 
2492
    label .preferences.help.title -text "Help"
2493
    place .preferences.help.title -in .preferences.help.frame -relx .1 -x -5 -y -10 -bordermode outside
2494
    pack .preferences.help -fill both -expand 1 -padx 10
2495
 
2496
    frame .preferences.buttons
2497
    button .preferences.buttons.ok     -text "OK"     -command [list synth::_menu_edit_preferences_ok]
2498
    button .preferences.buttons.cancel -text "Cancel" -command [list synth::_menu_edit_preferences_cancel]
2499
    pack .preferences.buttons.ok .preferences.buttons.cancel -side left -expand 1
2500
    pack .preferences.buttons -side bottom -fill x -pady 4
2501
 
2502
    frame .preferences.separator -height 2 -borderwidth 1 -relief sunken
2503
    pack .preferences.separator -side bottom -fill x -pady 4
2504
 
2505
    bind .preferences <KeyPress-Return> [list synth::_menu_edit_preferences_ok]
2506
    bind .preferences <KeyPress-Escape> [list synth::_menu_edit_preferences_cancel]
2507
 
2508
    variable _saved_focus ""
2509
    proc _menu_edit_preferences { } {
2510
        set synth::_saved_focus [focus]
2511
        set synth::_pref_browser1 $synth::_browser1
2512
        set synth::_pref_browser2 $synth::_browser2
2513
        set synth::_pref_browser3 $synth::_browser3
2514
        if { "normal" == [wm state .preferences] } {
2515
            raise .preferences
2516
        } else {
2517
            wm deiconify .preferences
2518
        }
2519
        focus .preferences.help.frame.entry1
2520
    }
2521
 
2522
    proc _menu_edit_preferences_ok { } {
2523
        if { $synth::_browser1 != $synth::_pref_browser1 } {
2524
            set synth::_browser1 $synth::_pref_browser1
2525
        }
2526
        if { $synth::_browser2 != $synth::_pref_browser2 } {
2527
            set synth::_browser2 $synth::_pref_browser2
2528
        }
2529
        if { $synth::_browser3 != $synth::_pref_browser3 } {
2530
            set synth::_browser3 $synth::_pref_browser3
2531
        }
2532
 
2533
        wm withdraw .preferences
2534
        catch { focus $synth::_saved_focus }
2535
    }
2536
 
2537
    proc _menu_edit_preferences_cancel { } {
2538
        wm withdraw .preferences
2539
        catch { focus $synth::_saved_focus }
2540
    }
2541
 
2542
    .menubar.edit add command -label "Preferences..." -command [list synth::_menu_edit_preferences]
2543
}
2544
 
2545
# }}}
2546
# {{{  Clean-up                                                 
2547
 
2548
# ----------------------------------------------------------------------------
2549
# GUI clean-up.
2550
#
2551
# Once all the device-specific scripts have been loaded and initialized, it
2552
# is time to go through the various components of the GUI and clean up 
2553
# anything that is not actually required.
2554
namespace eval synth {
2555
 
2556
    proc _cleanup_gui { } {
2557
 
2558
        if { $synth::flag_debug } {
2559
            synth::report "Cleaning up unused GUI items.\n"
2560
        }
2561
 
2562
        # File, Edit, View and Help should always have contents, unless
2563
        # the user has deleted entries via the mainrc file. The Windows
2564
        # menu will be empty unless contents have been added. There is
2565
        # always a global binding for ctrl-Q, and the window manager
2566
        # should always provide a way of killing off the application,
2567
        # so there is no need to treat File specially.
2568
        if { 0 == [.menubar.file index end] } {
2569
            .menubar delete "File"
2570
        }
2571
        if { 0 == [.menubar.edit index end] } {
2572
            .menubar delete "Edit"
2573
        }
2574
        if { 0 == [.menubar.view index end] } {
2575
            .menubar delete "View"
2576
        }
2577
        if { 0 == [.menubar.windows index end] } {
2578
            .menubar delete "Windows"
2579
        }
2580
        if { 0 == [.menubar.help index end] } {
2581
            .menubar delete "Help"
2582
        }
2583
 
2584
        # If the toolbar is empty get rid of it.
2585
        if { 0 == [llength [winfo children .toolbar]] } {
2586
            pack forget .toolbar
2587
            destroy .toolbar
2588
        }
2589
 
2590
        set can_destroy [list]
2591
        # Remove some or all of the top, left hand, right hand or bottom
2592
        # sets of frames, if nobody is using them.
2593
        if { (0 == [llength [pack slaves .main.nw]]) &&
2594
             (0 == [llength [pack slaves .main.n]]) &&
2595
             (0 == [llength [pack slaves .main.ne]]) } {
2596
            lappend can_destroy .main.nw .main.border_nw_n .main.n .main.border_n_ne .main.ne
2597
            lappend can_destroy .main.border_nw_w .main.border_n_centre .main.border_ne_e
2598
        }
2599
        if { (0 == [llength [pack slaves .main.nw]]) &&
2600
             (0 == [llength [pack slaves .main.w]]) &&
2601
             (0 == [llength [pack slaves .main.sw]]) } {
2602
            lappend can_destroy .main.nw .main.border_nw_w .main.w .main.border_w_sw .main.sw
2603
            lappend can_destroy .main.border_nw_n .main.border_w_centre .main.border_w_sw
2604
        }
2605
        if { (0 == [llength [pack slaves .main.ne]]) &&
2606
             (0 == [llength [pack slaves .main.e]]) &&
2607
             (0 == [llength [pack slaves .main.se]]) } {
2608
            lappend can_destroy .main.ne .main.border_ne_e .main.e .main.border_e_se .main.se
2609
            lappend can_destroy .main.border_n_ne .main.border_centre_e .main.border_s_se
2610
        }
2611
        if { (0 == [llength [pack slaves .main.sw]]) &&
2612
             (0 == [llength [pack slaves .main.s]]) &&
2613
             (0 == [llength [pack slaves .main.se]]) } {
2614
            lappend can_destroy .main.sw .main.border_sw_s .main.s .main.border_s_se .main.se
2615
            lappend can_destroy .main.border_w_sw .main.border_centre_s .main.border_e_se
2616
        }
2617
 
2618
        foreach frame [lsort -unique $can_destroy] {
2619
            grid forget $frame
2620
        }
2621
        foreach frame [lsort -unique $can_destroy] {
2622
            destroy $frame
2623
        }
2624
 
2625
        # Now that the full window layout is known the .main frame can be
2626
        # packed. Doing this before now could cause problems because the
2627
        # desired sizes of the subwindows are not known.
2628
        pack .main -expand 1 -fill both
2629
    }
2630
}
2631
 
2632
# }}}
2633
# {{{  Screen dump support                                      
2634
 
2635
# Create screen dumps for the main window or for various subwindows.
2636
# Normally disabled, but useful when generating documentation.
2637
# FIXME: there seem to be problems getting the desired info about
2638
# transient windows, e.g. sizes. Hence the generated dumps still
2639
# require a lot of hand editing for now.
2640
if { 0 } {
2641
 
2642
    bind . <Alt-w> {
2643
        exec xwd -out main.xwd -frame -id [winfo id .]
2644
    }
2645
 
2646
    bind . <Alt-f> {
2647
        .menubar invoke "File"
2648
        after 100 exec xwd -out menu_file.xwd -frame -id [winfo id .]
2649
    }
2650
 
2651
    bind . <Alt-e> {
2652
        .menubar invoke "Edit"
2653
        after 100 exec xwd -out menu_edit.xwd -frame -id [winfo id .]
2654
    }
2655
 
2656
    bind . <Alt-v> {
2657
        .menubar invoke "View"
2658
        after 100 exec xwd -out menu_view.xwd -frame -id [winfo id .]
2659
    }
2660
 
2661
    # The Help menu will extend beyond the window boundaries
2662
    bind . <Alt-h> {
2663
        .menubar invoke "Help"
2664
        after 100 exec xwd -out menu_help.xwd -root
2665
    }
2666
}
2667
 
2668
# }}}
2669
 
2670
# }}}
2671
}
2672
 
2673
# {{{  Device instantiation                                     
2674
 
2675
# ----------------------------------------------------------------------------
2676
# This code handles the loading of device-specific scripts in response
2677
# to requests from the eCos application, and the instantiation of devices.
2678
# The application's request provides four pieces of information, held in
2679
# null-terminated strings in the request buffer:
2680
#
2681
#   package name      e.g. hal/synth/arch
2682
#   package version   e.g. current
2683
#   device type       e.g. console or ethernet
2684
#   device instance   e.g. eth0, or an empty string
2685
#   device data       e.g. 1024x768 for frame buffer resolution
2686
#
2687
# The first two pieces of information can be concatenated to give a
2688
# path to the install location. The third identifies a suitable
2689
# tcl script, e.g. console.tcl. This is sufficient to locate and load
2690
# the tcl script. It should return an instantiation procedure which will
2691
# be invoked with the instance name (or an empty string if there will only
2692
# ever be one instance of this device type). The instantiation procedure
2693
# will then be called with a number and the device instance string, and
2694
# should return a handler for all requests intended for that device.
2695
#
2696
# If the package name and version are empty strings then an application-specific
2697
# device is being initialized, and the code will search in the current
2698
# directory and in ~/.ecos/synth
2699
 
2700
namespace eval synth {
2701
    # Map package/version/type on to an instantiation procedure
2702
    array set _instantiation_procs [list]
2703
 
2704
    # Map device instances on to handlers.
2705
    array set _device_handlers [list]
2706
    array set _device_names    [list]
2707
    variable _next_device_id 1
2708
 
2709
    # Let scripts know their install location and their source dir
2710
    variable device_install_dir ""
2711
    variable device_src_dir     ""
2712
 
2713
    # One handler is predefined.
2714
    set synth::_device_handlers(0)    synth::_handle_ecosynth_requests
2715
    set synth::_device_names(0)       "ecosynth I/O auxiliary"
2716
 
2717
    proc _handle_INSTANTIATE { data } {
2718
 
2719
        set list [split $data \0]
2720
        if { [llength $list] < 5 } {
2721
            synth::send_reply -1 0 ""
2722
            return
2723
        }
2724
        set package_dir     [lindex $list 0]
2725
        set package_version [lindex $list 1]
2726
        set device_type     [lindex $list 2]
2727
        set device_instance [lindex $list 3]
2728
        set device_data     [lindex $list 4]
2729
 
2730
        if { ![info exists synth::_instantiation_procs($package_dir,$package_version,$device_type)] } {
2731
            # The required script has not yet been loaded.
2732
            if { "" != $package_dir } {
2733
                # The device is provided by a package
2734
                set synth::device_install_dir [file join $synth::_ecosynth_libexecdir "ecos" $package_dir $package_version]
2735
                set synth::device_src_dir     [file join $synth::_ecosynth_repository $package_dir $package_version]
2736
 
2737
                set script [file join $::synth::device_install_dir "[set device_type].tcl"]
2738
                if { ![file exists $script] } {
2739
                    synth::report_error "Unable to initialize device $device_type\n    Script \"$script\" not found.\n"
2740
                    synth::send_reply -1 0 ""
2741
                    return
2742
                } elseif { ![file readable $script] } {
2743
                    synth::report_error "Unable to initialize device $device_type\n    Script \"$script\" not readable.\n"
2744
                    synth::send_reply -1 0 ""
2745
                    return
2746
                }
2747
 
2748
                # Is there a more recent version in the repository
2749
                if { [info exists ::env(ECOSYNTH_DEVEL)] } {
2750
                    set _orig_name [file join $synth::device_src_dir "host" "[set device_type].tcl"]
2751
                    if { [file exists $_orig_name] && [file readable $_orig_name] } {
2752
                        if { [file mtime $_orig_name] >= [file mtime $script] } {
2753
                            puts "$_orig_name is more recent than install: executing that."
2754
                            set script $_orig_name
2755
                        }
2756
                    }
2757
                }
2758
            } else {
2759
                # The device is application-specific
2760
                set script [file join [pwd] "[set device_type].tcl"]
2761
                if { ![file exists $script] || ![file readable $script] } {
2762
                    set script [file join "~/.ecos/synth" "[set device_type].tcl"]
2763
                    if { ![file exists $script] || ![file readable $script] } {
2764
                        synth::report_error "Unable to initialize device $device_type\n    Script $device_type.tcl not found in [pwd] or ~/.ecos/synth\n"
2765
                        synth::send_reply -1 0 ""
2766
                        return
2767
                    }
2768
                }
2769
            }
2770
 
2771
            # The uplevel ensures that the device script operates at the global
2772
            # level, so any namespaces it creates are also at global level
2773
            # and not nested inside synth. This avoids having to add
2774
            # synth:: to lots of variable accesses and generally avoids confusion
2775
            set result [catch { uplevel #0 source $script } instantiator]
2776
            if { 0 != $result } {
2777
                synth::report_error "Unable to initialize device $device_type\n  Error loading script \"$script\"\n  $instantiator\n"
2778
                synth::send_reply -1 0 ""
2779
                return
2780
            }
2781
 
2782
            set synth::_instantiation_procs($package_dir,$package_version,$device_type) $instantiator
2783
        }
2784
 
2785
        set handler [$synth::_instantiation_procs($package_dir,$package_version,$device_type) \
2786
                $synth::_next_device_id $device_instance $device_data]
2787
        if { "" == $handler } {
2788
            synth::send_reply -1 0 ""
2789
        } else {
2790
            set result $synth::_next_device_id
2791
            incr synth::_next_device_id
2792
 
2793
            set synth::_device_handlers($result) $handler
2794
            if { "" != $device_instance } {
2795
                set synth::_device_names($result) $device_instance
2796
            } else {
2797
                set synth::_device_names($result) $device_type
2798
            }
2799
            synth::send_reply $result 0 ""
2800
        }
2801
    }
2802
}
2803
 
2804
# }}}
2805
# {{{  Interrupt handling                                       
2806
 
2807
# ----------------------------------------------------------------------------
2808
# Interrupt handling. Device handlers can request an interrupt number
2809
# using allocate_interrupt, and typically they will transmit this
2810
# number to the eCos device driver during initialization. Device handlers
2811
# can at any time call raise_interrupt with that number, which typically
2812
# will result in SIGIO being sent to the eCos application. The latter will
2813
# send a request to retrieve a mask of current pending interrupts.
2814
#
2815
# Exit handling, in the sense of the user selecting File->Exit, is also
2816
# handled here. Such an exit request also involves raising SIGIO and
2817
# then sending a specially format response to the get-pending request.
2818
 
2819
namespace eval synth {
2820
 
2821
    # The next interrupt number to be allocated. Interrupt source 0 is reserved
2822
    # for the timer, which is handled within eCos itself via SIGALRM
2823
    # rather than by the I/O auxiliary.
2824
    variable _interrupt_next 1
2825
 
2826
    # Keep track of which interrupts belong to which devices, for display and
2827
    # diagnostic purposes.
2828
    array set _interrupt_names [list]
2829
    set _interrupt_names(0) "system clock"
2830
 
2831
    # A mask of current pending interrupts
2832
    variable _interrupt_pending 0
2833
 
2834
    # Is an exit request pending?
2835
    variable _interrupt_exit_pending 0
2836
 
2837
    # Allow other code to hook into the interrupt system, e.g. to display
2838
    # pending interrupts.
2839
    synth::hook_define "interrupt"
2840
 
2841
    # For now interrupts are always allocated dynamically, which effectively
2842
    # means in the order of C++ static constructors. This means that interrupt
2843
    # allocation depends on the application, and may even change as the application
2844
    # is relinked.
2845
    #
2846
    # An alternative approach would allow device scripts to request specific
2847
    # interrupt numbers, making the system a bit more deterministic, but
2848
    # introducing complications such as shared interrupt numbers. On the other
2849
    # hand that would make it easier to test chained interrupt support and
2850
    # the like.
2851
    # FIXME: add support for allocating specific interrupt numbers
2852
    proc interrupt_allocate { name } {
2853
        if { $synth::_interrupt_next == 32 } {
2854
            synth::report_error "Unable to allocate an interrupt vector for $name\nAll 32 interrupt vectors are already in use.\n"
2855
            return -1
2856
        }
2857
        set result $synth::_interrupt_next
2858
        set synth::_interrupt_names($result) $name
2859
        incr synth::_interrupt_next
2860
        return $result
2861
    }
2862
 
2863
    # Allow information about the device->interrupt mappings to be retrieved
2864
    proc interrupt_get_max { } {
2865
        return [expr $synth::_interrupt_next - 1]
2866
    }
2867
    proc interrupt_get_devicename { number } {
2868
        if { [info exists synth::_interrupt_names($number) ] } {
2869
            return $synth::_interrupt_names($number)
2870
        } else {
2871
            return ""
2872
        }
2873
    }
2874
 
2875
    # Raise a specific interrupt. If the interrupt is already pending
2876
    # this has no effect because a SIGIO will have been sent to the
2877
    # eCos application already. Otherwise SIGIO needs to be raised.
2878
    proc interrupt_raise { number } {
2879
        if { $number >= $synth::_interrupt_next } {
2880
            error "Attempt to raise invalid interrupt $number."
2881
        }
2882
        if { !$synth::ecos_running } {
2883
            return
2884
        }
2885
        set or_mask [expr 0x01 << $number]
2886
        if { 0 == ($or_mask & $synth::_interrupt_pending) } {
2887
            # This interrupt was not previously pending, so action is needed.
2888
            set synth::_interrupt_pending [expr $synth::_interrupt_pending | $or_mask]
2889
            synth::hook_call "interrupt" $number
2890
            synth::_send_SIGIO
2891
        }
2892
    }
2893
 
2894
    # Request application exit. This is typically called in response to
2895
    # File->Exit.
2896
    proc request_application_exit { } {
2897
        set synth::_interrupt_exit_pending 1
2898
        synth::_send_SIGIO
2899
    }
2900
 
2901
    # The eCos application wants to know about pending interrupts. It maintains
2902
    # its own set of pending interrupts, so once the information has been
2903
    # transferred there are no pending interrupts left in the I/O auxiliary,
2904
    # only in the eCos app. A pending exit is indicated by non-empty data,
2905
    # the actual data does not matter.
2906
    proc _handle_GET_IRQ_PENDING {  } {
2907
        if { $synth::_interrupt_exit_pending } {
2908
            synth::send_reply $synth::_interrupt_pending 1 "x"
2909
        } else {
2910
            synth::send_reply $synth::_interrupt_pending 0 ""
2911
        }
2912
        set synth::_interrupt_pending 0
2913
    }
2914
}
2915
 
2916
# }}}
2917
# {{{  Initialization complete                                  
2918
 
2919
# ----------------------------------------------------------------------------
2920
# This is called once all static constructors have been run, i.e. when all
2921
# eCos devices should be initialized. It does the following:
2922
#
2923
# 1) invoke any "initialized" hooks set up by device scripts.
2924
#
2925
# 2) run the per-user mainrc.tcl script, if it exists, so that users can
2926
#    install hooks, modify the GUI display, etc.
2927
#
2928
# 3) warn about any unused command line arguments
2929
#
2930
# 4) optionally warn about any unused entries in the target definition file
2931
#
2932
# 5) clean up the GUI, e.g. remove unwanted windows and borders, and display it.
2933
#
2934
# However if the user specified --help then, instead of all the above,
2935
# a help message is displayed and the auxiliary exits, hopefully taking the
2936
# eCos application with it.
2937
 
2938
namespace eval synth {
2939
 
2940
    proc _handle_CONSTRUCTORS_DONE { } {
2941
 
2942
        if { $synth::flag_help } {
2943
            puts "Usage : <eCos application> <options>"
2944
            puts "    Options are passed to the I/O auxiliary, and are not"
2945
            puts "    accessible to the eCos application."
2946
            puts "Standard options:"
2947
            puts " -io                         : run with I/O facilities."
2948
            puts " -nio                        : run with no I/O facilities at all."
2949
            puts " -nw, --no-windows           : run in console mode."
2950
            puts " -w, --windows               : run in GUI mode (default)."
2951
            puts " -v, --version               : display the version of the I/O auxiliary."
2952
            puts " -h, --help                  : show this help text."
2953
            puts " -k, --keep-going            : ignore errors in init scripts or the"
2954
            puts "                               target definition file."
2955
            puts " -nr, --no-rc                : do not run the user's init scripts."
2956
            puts " -x, --exit                  : terminate I/O auxiliary as soon as the eCos"
2957
            puts "                               application exits (default in console mode)."
2958
            puts " -nx, --no-exit              : I/O auxiliary keeps running even after eCos"
2959
            puts "                               application has exited (default in GUI mode)."
2960
            puts " -V, --verbose               : provide additional output during the run."
2961
            puts " -l <file>, --logfile <file> : send all output to the specified file. In"
2962
            puts "                               GUI mode this in addition to the main text"
2963
            puts "                               window. In console mode this is instead of"
2964
            puts "                               stdout."
2965
            puts " -t <file>, --target <file>  : use the specified .tdf file as the target"
2966
            puts "                               definition. The auxiliary will look for this"
2967
            puts "                               file in the current directory, ~/.ecos, and"
2968
            puts "                               finally the install location."
2969
            puts " -geometry <geometry>        : size and position for the main window."
2970
            synth::hook_call "help"
2971
            exit 1
2972
        }
2973
 
2974
        synth::hook_call "ecos_initialized"
2975
 
2976
        # ----------------------------------------------------------------------------
2977
        if { !$synth::flag_no_rc } {
2978
            set _config_file [file join "~/.ecos/synth" "mainrc.tcl"]
2979
            if { [file exists $_config_file] } {
2980
                if { [file readable $_config_file] } {
2981
                    if { [catch { source $_config_file } msg ] } {
2982
                        set error "Failed to execute user initialization file  \"$_config_file\"\n"
2983
                        append error "  $msg\n"
2984
                        if { $synth::flag_verbose } {
2985
                            append error "------- backtrace ------------------------------------------\n"
2986
                            append error $::errorInfo
2987
                            append error "\n------- backtrace ends -------------------------------------\n"
2988
                        }
2989
                        synth::report_error $error
2990
                    }
2991
                } else {
2992
                    synth::report_error "No read access to user initialization file \"$_config_file\"\n"
2993
                }
2994
            }
2995
            unset _config_file
2996
        }
2997
 
2998
        # ----------------------------------------------------------------------------
2999
        # Report any arguments that have not been used up by the auxiliary itself
3000
        # or by any device handlers
3001
        set unconsumed_args [synth::argv_get_unconsumed]
3002
        foreach arg $unconsumed_args {
3003
            synth::report_warning "Unrecognised command line option \"$arg\", ignored.\n"
3004
        }
3005
 
3006
        # ----------------------------------------------------------------------------
3007
        if { $synth::flag_verbose } {
3008
            set unconsumed_devices [synth::tdf_get_unconsumed_devices]
3009
            set unconsumed_options [synth::tdf_get_unconsumed_options]
3010
            if { (0 != [llength $unconsumed_devices]) || (0 != [llength $unconsumed_options]) } {
3011
                set msg "Target definition file $synth::target_definition\n"
3012
                foreach dev $unconsumed_devices {
3013
                    append msg "    synth_device \"$dev\" not recognised.\n"
3014
                }
3015
                foreach option $unconsumed_options {
3016
                    set dev [lindex $option 0]
3017
                    set opt [lindex $option 1]
3018
                    append msg "    synth_device \"$dev\", option \"$opt\" not recognised.\n"
3019
                }
3020
                synth::report_warning $msg
3021
            }
3022
        }
3023
 
3024
        #  ----------------------------------------------------------------------------
3025
        if { $synth::flag_gui } {
3026
            synth::_cleanup_gui
3027
            wm deiconify .
3028
        }
3029
 
3030
        # ----------------------------------------------------------------------------
3031
        # Finally send a reply back to the application so it can really
3032
        # start running. Alternatively, if any errors occurred during
3033
        # initialization and the user did not specify --keep-going then
3034
        # send back an error code, causing the eCos application to terminate.
3035
        if { (0 == $synth::_error_count) || $synth::flag_keep_going } {
3036
            synth::send_reply 1 0 ""
3037
        } else {
3038
            synth::send_reply 0 0 ""
3039
        }
3040
    }
3041
}
3042
 
3043
# }}}
3044
# {{{  Requests for the I/O auxiliary itself                    
3045
 
3046
# ----------------------------------------------------------------------------
3047
# There are three requests which can be aimed at the I/O auxiliary itself,
3048
# rather than at device-specific scripts. These are: INSTANTIATE to instantiate
3049
# a device; CONSTRUCTORS_DONE to indicate when initialization is complete;
3050
# and GET_IRQ_PENDING which deals with interrupts.
3051
 
3052
namespace eval synth {
3053
 
3054
    proc _handle_ecosynth_requests { devid request arg1 arg2 request_data request_len reply_len } {
3055
        if { 0x01 == $request } {
3056
            synth::_handle_INSTANTIATE $request_data
3057
        } elseif { 0x02 == $request } {
3058
            synth::_handle_CONSTRUCTORS_DONE
3059
        } elseif { 0x03 == $request } {
3060
            synth::_handle_GET_IRQ_PENDING
3061
        } elseif { 0x04 == $request } {
3062
            synth::_handle_GET_VERSION
3063
        } else {
3064
            error "The eCos application has sent an invalid request sent to the I/O auxiliary"
3065
        }
3066
    }
3067
 
3068
    variable _SYNTH_AUXILIARY_PROTOCOL_VERSION  0x01
3069
    proc _handle_GET_VERSION { } {
3070
        synth::send_reply $synth::_SYNTH_AUXILIARY_PROTOCOL_VERSION 0 ""
3071
    }
3072
}
3073
 
3074
# }}}
3075
# {{{  Application exit                                         
3076
 
3077
# ----------------------------------------------------------------------------
3078
# The application has exited. This is detected by an EOF event on the pipe
3079
# from the eCos application.
3080
#
3081
# First the rest of the system is informed about the event using the
3082
# appropriate hook. This should ensure that the various device-specific
3083
# scripts do the right thing, e.g shut down sub-processes. Next, if
3084
# the immediate exit flag is set then that is obeyed. This flag is set by
3085
# default when in command-line mode because there is no point in continuing
3086
# to run if there is neither an application nor a GUI for the user to interact
3087
# with. It also gets set if the user has explicitly requested an exit.
3088
#
3089
# The exit call will invoke the appropriate hooks.
3090
namespace eval synth {
3091
 
3092
    proc _application_has_exited { } {
3093
 
3094
        set synth::ecos_running 0
3095
        synth::hook_call "ecos_exit"
3096
 
3097
        # Depending on command-line arguments and whether or not the GUI is present,
3098
        # the auxiliary should now exit
3099
        if { $synth::flag_immediate_exit } {
3100
            exit 0
3101
        } elseif { !$synth::flag_gui } {
3102
            synth::report "eCos application has exited: I/O auxiliary still running in the background.\n"
3103
        }
3104
    }
3105
}
3106
 
3107
# }}}
3108
# {{{  Communication with the eCos application                  
3109
 
3110
namespace eval synth {
3111
 
3112
    # ----------------------------------------------------------------------------
3113
    # The basic communication routines between the auxiliary and the
3114
    # eCos application. _read_request is invoked whenever there is
3115
    # a pending event on the pipe from the eCos application, either
3116
    # a request or an EOF. It 
3117
 
3118
    # Keep track of a couple of things to detect protocol mismatches.
3119
    variable _reply_expected 0
3120
    variable _expected_rxlen 0
3121
 
3122
    # Receive a single request from the eCos application and invoke the
3123
    # appropriate handler.
3124
    proc _read_request { } {
3125
        # Read a single request from the application, or possibly EOF
3126
        set devid   0
3127
        set reqcode 0
3128
        set arg1    0
3129
        set arg2    0
3130
        set txlen   0
3131
        set txdata  ""
3132
        set rxlen   0
3133
        set request [read $synth::_channel_from_app 24]
3134
 
3135
        if { [eof $synth::_channel_from_app] } {
3136
            fileevent $synth::_channel_from_app readable ""
3137
            synth::_application_has_exited
3138
            return
3139
        }
3140
 
3141
        # If a real request is sent then currently the application should
3142
        # not be expecting a reply
3143
        if { $synth::_reply_expected } {
3144
            error "The eCos application should not be sending a request when there is still a reply pending"
3145
        }
3146
 
3147
        set binary_result [binary scan $request "iiiiii" devid reqcode arg1 arg2 txlen rxlen]
3148
        if { 6 != $binary_result } {
3149
            error "Internal error decoding request from eCos application"
3150
        }
3151
 
3152
        # If running on a 64-bit platform then the above numbers will have been sign-extended,
3153
        # which could lead to confusing results
3154
        set devid   [expr $devid   & 0x0FFFFFFFF]
3155
        set reqcode [expr $reqcode & 0x0FFFFFFFF]
3156
        set arg1    [expr $arg1    & 0x0FFFFFFFF]
3157
        set arg2    [expr $arg2    & 0x0FFFFFFFF]
3158
        set txlen   [expr $txlen   & 0x0FFFFFFFF]
3159
        set rxlen   [expr $rxlen   & 0x0FFFFFFFF]
3160
 
3161
        # The top bit of rxlen is special and indicates whether or not a reply is expected.
3162
        set synth::_reply_expected [expr 0 != ($rxlen & 0x080000000)]
3163
        set synth::_expected_rxlen [expr $rxlen & 0x07FFFFFFF]
3164
 
3165
        # Is there additional data to be read
3166
        if { $txlen > 0 } {
3167
            set txdata [read $synth::_channel_from_app $txlen]
3168
            if { [eof $synth::_channel_from_app] } {
3169
                fileevent $synth::_channel_from_app readable ""
3170
                synth::_application_has_exited
3171
                return
3172
            }
3173
        }
3174
 
3175
        # The devid can be used to get hold of a handler function, and that will do
3176
        # the hard work.
3177
        if { ![info exists synth::_device_handlers($devid)] } {
3178
            error "A request has been received for an unknown device $devid"
3179
        }
3180
 
3181
        $synth::_device_handlers($devid) $devid $reqcode $arg1 $arg2 $txdata $txlen $synth::_expected_rxlen
3182
    }
3183
 
3184
    # Register _read_request as the handler for file events on the pipe from
3185
    # the application.
3186
    fileevent $synth::_channel_from_app readable synth::_read_request
3187
 
3188
    # Send a reply back to eCos. This consists of a two-word structure,
3189
    # result and length, followed by the data if any. Currently this
3190
    # raises an error if there is a mismatch between the specified and
3191
    # actual length of the data. Possibly the code should cope with
3192
    # data strings that exceed the specified length, extracting a
3193
    # suitable substring.
3194
    proc send_reply { result { length 0 } { data "" } } {
3195
        # Make sure that a reply is actually expected.
3196
        if { !$synth::_reply_expected } {
3197
            error "Attempt to send reply to application when no request has been sent"
3198
        } else {
3199
            set synth::_reply_expected 0
3200
        }
3201
 
3202
        if { $length > $synth::_expected_rxlen } {
3203
            error "Reply contains more data than the application expects: $length bytes instead of $synth::_expected_rxlen"
3204
        }
3205
        if { ($length > 0) && ([string length $data] != $length) } {
3206
            error "Mismatch between specified and actual data length: $length [string length $data]"
3207
        }
3208
        if { !$synth::ecos_running } {
3209
            return
3210
        }
3211
 
3212
        set struct [binary format "ii" $result $length]
3213
        # Ignore any errors when writing down the pipe. The only likely error is
3214
        # when the application has exited, causing a SIGPIPE which Tcl
3215
        # will handle. The application should be waiting for this response.
3216
        catch {
3217
            puts -nonewline $synth::_channel_to_app $struct
3218
            if { $length > 0 } {
3219
                puts -nonewline $synth::_channel_to_app $data
3220
            }
3221
        }
3222
    }
3223
}
3224
 
3225
# }}}
3226
 
3227
# {{{  initrc                                                   
3228
 
3229
# ----------------------------------------------------------------------------
3230
# Just before control is returned to the eCos application, run the per-user
3231
# file, ~/.ecos/synth/initrc.tcl. The main GUI is now in place and the target
3232
# definition file has been read in,  but no eCos static constructors have
3233
# been run yet and hence no devices have been loaded or activated.
3234
# All the various core procedures have been defined. initrc gives the user
3235
# a chance to install hooks, redefine some internals, and so on.
3236
# Another initialization file mainrc.tcl gets read in later, just before
3237
# the eCos application really starts running.
3238
#
3239
# Possibly ecosynth should also read in a system-wide initialization
3240
# file equivalent to emacs' site-start.el, but the extra complexity
3241
# does not seem warranted just yet.
3242
 
3243
if { !$synth::flag_no_rc } {
3244
    set _config_file [file join "~/.ecos/synth" "initrc.tcl"]
3245
    if { [file exists $_config_file] } {
3246
        if { [file readable $_config_file] } {
3247
            if { [catch { source $_config_file } msg ] } {
3248
                set error "Failed to execute user initialization file  \"$_config_file\"\n"
3249
                append error "  $msg\n"
3250
                if { $synth::flag_verbose } {
3251
                    append error "------- backtrace ------------------------------------------\n"
3252
                    append error $::errorInfo
3253
                    append error "\n------- backtrace ends -------------------------------------\n"
3254
                }
3255
                synth::report_error $error
3256
            }
3257
        } else {
3258
            synth::report_error "No read access to user initialization file \"$_config_file\"\n"
3259
        }
3260
    }
3261
    unset _config_file
3262
}
3263
 
3264
# }}}
3265
# {{{  Done                                                     
3266
 
3267
# ----------------------------------------------------------------------------
3268
# The last few steps.
3269
 
3270
# Once everything has been initialized the application can be woken up again.
3271
# It should be blocked waiting for a single byte on the pipe.
3272
if { $synth::flag_debug } {
3273
    synth::report "Core initialization complete, resuming the eCos application.\n"
3274
}
3275
 
3276
puts -nonewline $synth::_channel_to_app  "."
3277
 
3278
# Enter the event loop. In console mode there is a problem if -nx has been
3279
# specified: there may not be any event handlers left once the eCos application
3280
# has exited, so the vwait would abort. This is avoided by a dummy after proc.
3281
if { !$synth::flag_gui && !$synth::flag_immediate_exit } {
3282
    namespace eval synth {
3283
        proc _dummy_after_handler { } {
3284
            after 1000000 synth::_dummy_after_handler
3285
        }
3286
    }
3287
    after 1000000 synth::_dummy_after_handler
3288
}
3289
 
3290
vwait synth::_ecosynth_exit
3291
 
3292
# }}}

powered by: WebSVN 2.1.0

© copyright 1999-2024 OpenCores.org, equivalent to Oliscience, all rights reserved. OpenCores®, registered trademark.