I'm trying to draw on a canvas that is in the top level of my Tcl/Tk script, but from inside a call by fileevent like this:
canvas .myCanvas {}
proc plot_Data { myC inp } { $myC create rectangle {} }
fileevent $inp readable [list plot_Data .myCanvas $inp ]
pack .myCanvas
I have found out that the script called by fileevent (plot_Data) lives in a different space.
The script for a file event is executed at global level (outside the context of any Tcl procedure) in the interpreter in which the fileevent command was invoked.
I cannot make the two meet. I have definitely narrowed it down to this: plot_Data just can't access .myCanvas . Question: How can the fileevent script plot on the canvas?
The goal of this is live plotting, by the way. $inp is a pipe to a C-program that reads data from a measurement device. It is imho rightly configured with fconfigure $inp -blocking 0 -buffering none.
Callback scripts (except for traces, which you aren't using) are always called from the context of the global namespace. They cannot see any stack frames above them. This is because they are called at times that aren't closely controlled; there's no telling what the actual stack would be, so it is forced into a known state.
However, canvases (and other widgets) have names in the global namespace as well. Your callbacks most certainly can access them, provided the widget has not been destroyed, and might indeed be working. You just happen to have given it an empty list of coordinates to create, which is not usually legal to use with a canvas item.
Since you are using non-blocking I/O, you need to be aware that gets may return the empty string when reading an incomplete line. Use fblocked to determine if a partial read happened; if it does, the data is in a buffer on the Tcl side waiting for the rest of the line to turn up, and it is usually best to just go to sleep and wait for the next fileevent to fire.
A problem that might bite you overall is if the C program is in fully buffered mode; this is true by default when writing output from C to a pipe. Setting the buffering on the Tcl side won't affect it; you need to use setvbuf on the C side, or insert regular fflush calls, or use Expect (which pretends to be an interactive destination, though at quite a lot of cost of complexity of interaction) or even unbuffer (if you can find a copy).
Related
As indicated by Tom Browder in this issue, the $*ARGFILES dynamic variable might contain invalid filehandles if any of the files mentioned in the command line is not present.
for $*ARGFILES.handles -> $fh {
say $fh;
}
will fail with and X::AdHoc exception (this should probably be improved too):
Failed to open file /home/jmerelo/Code/perl6/my-perl6-examples/args/no-file: No such file or directory
The problem will occur as soon as the invalid filehandle is used for anything. Would there be a way of checking if the filehandle is valid before incurring in an exception?
You can check if something is a Failure by checking for truthiness or definedness without the Failure throwing:
for $*ARGFILES.handles -> $fh {
say $fh if $fh; # check truthiness
.say with $fh; # check definedness + topicalization
}
If you still want to throw the Exception that the Failure encompasses, then you can just .throw it.
TL;DR I thought Liz had it nailed but it seems like there's a bug or perhaps Ugh.
A bug?
It looks like whenever the IO::CatHandle class's .handles method reaches a handle that ought by rights produce a Failure (delaying any exception throw) it instead immediately throws an exception (perhaps the very one that would work if it were just delayed or perhaps something broken).
This seems either wrong or very wrong.
Ugh
See the exchange between Zoffix and Brad Gilbert and Zoffix's answer to the question How should I handle Perl 6 $*ARGFILES that can't be read by lines()?
Also:
https://github.com/rakudo/rakudo/issues/1313
https://github.com/rakudo/rakudo/search?q=argfiles&type=Issues
https://github.com/rakudo/rakudo/search?q=cathandle&type=Issues
A potential workaround is currently another bug?
In discussing "Implement handler for failed open on IO::CatHandle" Zoffix++ closed it with this code as a solution:
.say for ($*ARGFILES but role {
method next-handle {
loop {try return self.IO::CatHandle::next-handle}
}
})
I see that tbrowder has reopened this issue as part of the related issue this SO is about saying:
If this works, it would at least be a usable example for the $*ARGFILES var in the docs.
But when I run it in 6.d (and see similar results for a 6.c), with or without valid input, I get:
say not yet implemented
(similar if I .put or whatever).
This is nuts and suggests something gutsy is getting messed up.
I've searched rt and gh/rakudo issues for "not yet implemented" and see no relevant matches.
Another workaround?
Zoffix clearly intended their code as a permanent solution, not merely a workaround. But it unfortunately doesn't seem to work at all for now.
The best I've come up with so far:
try {$*ARGFILES} andthen say $_ # $_ is a defined ArgFiles instance
orelse say $!; # $! is an error encountered inside the `try`
Perhaps this works as a black-and-white it either all works or none of it does solution. (Though I'm not convinced it's even that.)
What the doc has to say about $*ARGFILES
$*ARGFILES says it is an instance of
IO::ArgFiles which is doc'd as a class which
exists for backwards compatibility reasons and provides no methods.
And
All the functionality is inherited from
IO::CatHandle which is subtitled as
Use multiple IO handles as if they were one
and doc'd as a class that is
IO::Handle which is subtitled as
Opened file or stream
and doc'd as a class that doesn't inherit from any other class (so defaults to inheriting from Any) or do any role.
So, $*ARGFILES is (exactly functionally the same as) a IO::CatHandle object which is (a superset of the functionality of) an IO::Handle object, specifically:
The IO::CatHandle class provides a means to create an IO::Handle that seamlessly gathers input from multiple IO::Handle and IO::Pipe sources. All of IO::Handle's methods are implemented, and while attempt to use write methods will (currently) throw an exception, an IO::CatHandle is usable anywhere a read-only IO::Handle can be used.
Exploring the code for IO::CatHandle
(To be filled in later?)
I have a class DataDialog, which contains a destructor like
destructor {
puts "DataDialog has been destructed"
#further code
}
If I close the application via the X-window-button this destructor is not called. If I close it over file->close it is called.
On the toplevel I have the following
wm protocol . WM_DELETE_WINDOW {
Exit 0
}
How can I change this behaviour to call all destructors (or at least the one of my class DataDialog)?
How about
wm protocol . WM_DELETE_WINDOW {
DataDialog destroy
Exit 0
}
If you call exit (or if you delete the interpreter) then Tcl does not guarantee to call destructors. That's because tearing down everything in memory can be surprisingly expensive. Critical resources typically have extra exit handlers registered at the C level to ensure that they get cleaned up correctly, but they're very much the exception; the only ones you likely use on a common basis are channels (which are flushed on exit). There isn't any Tcl-level for doing this; those handlers are usually called at points where it is no longer safe to call Tcl commands.
However, the default behaviour for handling cooperative window closure is effectively to send a <Destroy> message to the window. Those aren't entirely interceptable (the window will go away) but you can bind to them to find out when they occur. Be aware of one quirk though: toplevel windows also listen to all the events of their children (though they don't get killed by passing <Destroy>s unless they're sent to them directly). Check that %W actually refers to the window that you think you're really listening to before taking special action.
I start interactive tcl interpreter by calling Tcl_main() and take some tcl commands there. After that, I want to quit tcl interpreter and resume my program. However, when I type "exit", it will exit the whole program. Is there a way to exit tcl interpreter and continue my program ? Thanks.
The Tcl_Main() function does not provide any way for code to resume after it finishes; it's a convenience wrapper for handling simple programs so that you don't have to worry about such things, but that wrapper is limited in a number of ways, and one of those is that you can't do what you're seeking to do. (At the end of the function, it calls Tcl_Exit().)
But you don't need to use Tcl_Main()! It just uses Tcl's public API (with a few exceptions that probably don't matter for you). You can duplicate everything by calling (approximately):
Tcl_FindExecutable(argv[0]); // ONCE PER PROCESS; INITIALISES TCL C LIBRARY
Tcl_Interp *interp = Tcl_CreateInterp();
// ... now in your loop evaluate code, perhaps from the user ...
Tcl_Eval(interp, "some code here");
// ...
Tcl_DeleteInterp(interp);
// Carry on as normal
There's no great magic in there. Just initialise the library, create an interpreter, evaluate your code in there (perhaps after getting it from the user; invent your own reader loop if you want or code it up in Tcl), and then delete the interpreter when you're done.
I have a tcl (expect) script to log into devices and transfer files. Unfortunately, the files are large, and during the transfer period the ssh connection ends (the files are still transferred though). So I basically have to login again before I can perform more actions on the device. Since the whole login process is long, I put it in a proc. The issue is that the proc logs into the device, but after the login, the script sends the commands to the terminal as, for some reason, the commands no longer reach the device. I cant figure out why the session I logged into in the proc does not carry over to the rest of the script.
proc login {} {
#login code - it works because I took it from the main script (which works).
# variables are all declared as global, no errors are thrown. Login is successful
}
login
send "show\r" ;# this command is not sent to the device,
#instead it prints to the terminal. When in the main script,
#these commands would not be printed to the terminal window.
Is there a command I am missing to maybe return the login session to the rest of the script? something similar to the interact command, but to the rest of the script.
This is a tricky one. The expect man page says this:
Expect takes a rather liberal view of scoping. In particular, variables read by commands specific
to the Expect program will be sought first from the local scope, and if not found, in the global
scope. For example, this obviates the need to place "global timeout" in every procedure you write
that uses expect. On the other hand, variables written are always in the local scope (unless a
"global" command has been issued). The most common problem this causes is when spawn is executed in
a procedure. Outside the procedure, spawn_id no longer exists, so the spawned process is no longer
accessible simply because of scoping. Add a global spawn_id to such a procedure.
So, add global spawn_id as the first line of the login proc
To make a procedure that evaluates code in its caller's scope, you need to use the uplevel command inside it. This lets you do what is essentially a macro very easily:
proc login {} {
uplevel {
# Put your code in here, which might look like this
spawn ssh user#host ...
expect Password:
send $thePassword\r
expect "bash$"
}
}
Then, when you use login it will work exactly like the commands you have inside the uplevel in the procedure, as if they'd been typed in place of the login call.
This isn't usually a particularly recommended approach, as it is very easy to make code that is inflexible and inclined to break unexpectedly, but in your case it is a very easy approach since you can easily guarantee to only call login at a sensible place in the overall program structure. (The uplevel command is more commonly used with scripts passed in with arguments — it's just like you're passing in a block — but that's not what you need.)
I have a C shell that usually calls Tcl routines using Tcl_Eval. Normally I was fine with just executing what the user typed and getting some status as a result. However, now I need to receive the actual stdio output from the command that user typed. Is there any way to get it using the Tcl C procedures?
As a side note: I need to figure out the list of current procedures available in the Tcl interpreter, both built in and user sourced. Basically, the output from info procs *.
I think you could go like this:
Create a pipe by calling pipe(2).
Then in your interp:
Close stdout by calling Tcl_Close() on it.
Turn the write-end file descriptor of your pipe into Tcl's stdout channel by calling Tcl_MakeChannel() right after closing stdout.
Or use just replace the stdout with a call to Tcl_SetStdChannel().
Process the data coming from the pipe.
As to your side note — I think you could just call Tcl_Eval() in your interpreter and process the returned list using the list-processing functions from the Tcl API.
Update (from one of my comments): after some more thought I think it might be possible to just create a custom Tcl channel which implementation would just save away the data written to it and then register an instance of such a channel as stdout. See Tcl_CreateChannel() and Tcl_RegisterChannel().