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

Subversion Repositories openrisc

[/] [openrisc/] [trunk/] [rtos/] [ecos-3.0/] [packages/] [hal/] [synth/] [arch/] [current/] [host/] [ecosynth.tcl] - Blame information for rev 786

Details | Compare with Previous | View Log

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

powered by: WebSVN 2.1.0

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