I would like to bind a very specific event, which is 'Control Key' followed by another 'Control Key', similar to KVM operation, to a widget.
I know how to bind various event but not this one, which comprises two sequential events.
Any idea how to implement it?
In theory, you'd be able to use a binding event sequence to do this:
# The keyboard often has two control keys; they have different names
bind $w <KeyPress-Control_L><KeyPress-Control_L> {puts "double-control (left)"}
bind $w <KeyPress-Control_R><KeyPress-Control_R> {puts "double-control (right)"}
However, the control keys (as with the other modifiers) are especially complicated to handle this way because they are repeated by the OS for you; they're a special case! Because of that, you instead need to build your own queue of keyboard events:
bind $w <KeyPress> {queueEvent %K}
set queue {no-such-key}
proc queueEvent {key} {
global queue
if {[llength [lappend queue $key]] > 2} {
set queue [lrange $queue end-1 end]
}
lassign $queue last this
if {$last eq $this && [string match Control* $this]} {
puts "double ctrl"
}
}
This can interact badly with other bindings. How to fix that depends on the wider context of your application.
Now with taking care of the quick delay required for "double-click"
bind . <KeyPress> {queueEvent %K}
set queue {}
set times {}
proc queueEvent {key} {
global queue times
if {![string match Control* $key]} {set queue {}; set times {}; return}
if {$key eq $queue} {
if {[expr [clock milliseconds] - $times] <= 500} {
puts "double ctrl $key"
set queue {}; set times {}
return
}
}
set queue $key
set times [clock milliseconds]
}
Related
In a tcl/tk dialog I need to get a text input from the user.
proc add_entry { command } {
global TestValue
entry .dialog_TC.enText -textvariable TestValue
grid .dialog_TC.enText -row 1 -column 1 -columnspan 2 -pady 1 -padx 1
}
The problem:
Whenever the user writes a single letter, into the entry-field, the dialog is closed immediately.
I'm guessing that you've got a trace elsewhere on the TestValue variable (possibly due to vwait or tkwait variable) that is detecting the change to the variable and destroying the widget when that happens, possibly by killing the whole dialog. You don't include the code, but it's probably something like:
proc make-me-a-dialog {} {
toplevel .dialog_TC
# ...
add_entry { something ... }
# ...
vwait TestValue
destroy .dialog_TC
return $TestValue
}
That's a guess, and probably greatly simplified too. But if that's the case, the first event to change the value in the variable (i.e., most key-presses in the entry) will cause the vwait to stop waiting and trigger the cascade of destruction.
You need to stop waiting on the contents of the entry. You don't want to trigger every time something is altered in it, but rather only when the user says “I'm done and want to make my changes, OK” or “I'm done and don't want to make my changes, Cancel”. Or, depending on interaction style, “I'm done; my changes are already live. Close this window”. With plenty of experience, the events that you are actually needing to listen for are the closing of the window, the press of Return and the press of Escape.
Let's fix.
proc make-me-a-dialog {}
global waiting
toplevel .dialog_TC
# ...
add_entry { something ... }
# ...
set dlg .dialog_TC
bind $dlg <Return> [list set waiting($dlg) 1]
bind $dlg <Escape> [list set waiting($dlg) 0]
# Trapping a window manager message; slightly different to normal events for historical reasons
wm protocol $dlg WM_DELETE_WINDOW [list set waiting($dlg) 0]
vwait waiting($dlg)
if {waiting($dlg)} {
return $ValueIndicatingOK
} else {
return $ValueIndicatingCancel
}
}
Ok, I did not think about my shortkeys, that I have also in that script. Whenever one of those letters is written into the entry field the window gets closed. I have to combine keys like ...
bind . <Control-Key-a> \
{ tk_messageBox -message "You pressed Control+A" } ;#Control+a
I would like to retrieve the text of a specific window. Using
twapi::get_window_text $handle
I get the caption of the window. But how can I get the actual content ? In C++ I was using
EM_GETLINE
How can I use these raw Windows API functions from TCL? For EM_GETLINE for example I have to define the numbers of lines to be fetched and the buffer where they shall be stored.
Could someone show me how to use raw Windows API functions from TCL or point me to a site where I can find examples? Thanks
You can send messages with Twapi's raw-API. I'm not fammilar with the the exact details how this message works, but you know that probably better than me:
package require twapi
proc get_richedit_text {hwnd line} {
set MAX_LEN 0x0100
# You have to lookup this value in the header.
set EM_GETLINE 0x00C4
set bufsize [expr {2 * ($MAX_LEN + 1)}]
# yes, twapi has malloc.
set szbuf [twapi::malloc $bufsize]
# catch everything, so we can free the buffer.
catch {
# set the first word to the size. Whatever a word is.
# I assume it is an int (type 1), but if it is a int64, use type 5, wchar is 3.
# arguments to Twapi_WriteMemory: type pointer(void*) offset bufferlen value
twapi::Twapi_WriteMemory 1 $szbuf 0 $bufsize $MAX_LEN
# send the message. You don't have SendMessage, only SendMessageTimeout
set ressize [twapi::SendMessageTimeout $hwnd $EM_GETLINE $line [twapi::pointer_to_address $szbuf] 0x0008 1000]
return [twapi::Twapi_ReadMemory 3 $szbuf 0 [expr {$ressize * 2}]]
} res opt
# free the buffer.
twapi::free $szbuf
return -options $opt $res
}
I used some internal/undocumented twapi API, the only documentation is twapi's source code.
I have a tcl script which runs multiple shell commands serially.
Something like this:
abc.tcl
command 1
command 2
command 3
...
command n
This script prints the outputs of these commands into a text file in the following format:
### ### ### ### ### ###
Command name
### ### ### ### ### ###
Command Output
### ### ### ### ### ##
I was trying to get the script to run faster but making the shell commands run in parallel instead of serially. By pushing them to the background (command a &). But I'm at a loss how to retain the formatting of my output text file as was the case before.
When I push the commands in to background I'm forced to append their outputs into a temporary file, but these files just have the output of the commands in a dump together. It's difficult to differentiate between the different outputs.
Is there someway I can redirect the output of each command running in the background to an individual temp file (maybe the name of the temp file can have the process id of the background running process). And once all commands have run, I can cat the outputs together in to the proper format? Any ideas/suggestions on how I can accomplish this.
If the commands don't have state that depends on each other, you can parallelize them. There are many ways to do this, but one of the easier is to use the thread package's thread pooling (which requires a threaded Tcl, the norm on many platform nowadays):
package require Thread
set pool [tpool::create -maxworkers 4]
# The list of *scripts* to evaluate
set tasks {
{command 1}
{command 2}
...
{command n}
}
# Post the work items (scripts to run)
foreach task $tasks {
lappend jobs [tpool::post $pool $task]
}
# Wait for all the jobs to finish
for {set running $jobs} {[llength $running]} {} {
tpool::wait $pool $running running
}
# Get the results; you might want a different way to print the results...
foreach task $tasks job $jobs {
set jobResult [tpool::get $pool $job]
puts "TASK: $task"
puts "RESULT: $jobResult"
}
The main tweakable is the size of the thread pool, which defaults to a limit of 4. (Set it via the -maxworkers option to tpool::create which I've listed explicitly above.) The best value to choose depends on how many CPU cores you've got and how much CPU load each task generates on average; you'll need to measure and tune…
You can also use the -initcmd option to pre-load each worker thread in the pool with a script of your choice. That's a good place to put your package require calls. The workers are all completely independent of each other and of the master thread; they do not share state. You'd get the same model if you ran each piece of code in a separate process (but then you'd end up writing more code to do the coordinating).
[EDIT]: Here's a version that will work with Tcl 8.4 and which uses subprocesses instead.
namespace eval background {}
proc background::task {script callback} {
set f [open |[list [info nameofexecutable]] "r+"]
fconfigure $f -buffering line
puts $f [list set script $script]
puts $f {fconfigure stdout -buffering line}
puts $f {puts [list [catch $script msg] $msg]; exit}
fileevent $f readable [list background::handle $f $script $callback]
}
proc background::handle {f script callback} {
foreach {code msg} [read $f] break
catch {close $f}
uplevel "#0" $callback [list $script $code $msg]
}
proc accumulate {script code msg} {
puts "#### COMMANDS\n$script"
puts "#### CODE\n$code"
puts "#### RESULT\n$msg"
# Some simple code to collect the results
if {[llength [lappend ::accumulator $msg]] == 3} {
set ::done yes
}
}
foreach task {
{after 1000;subst hi1}
{after 2000;subst hi2}
{after 3000;subst hi3}
} {
background::task $task accumulate
}
puts "WAITING FOR TASKS..."
vwait done
Notes: the tasks are Tcl commands that produce a result, but they must not print the result out; the fabric code (in background::task) handles that. These are subprocesses; they share nothing with one another, so anything you want them to do or be configured with must be sent as part of the task. A more sophisticated version could keep a hot pool of subprocesses around and in general work very much like a thread pool (subject to the subtle differences due to being in a subprocess and not a thread) but that was more code than I wanted to write here.
Result codes (i.e., exception codes) are 0 for “ok”, 1 for “error”, and other values in less common cases. They're exactly the values documented on the Tcl 8.6 catch manual page; it's up to you to interpret them correctly. (I suppose I should also add code to make the ::errorInfo and ::errorCode variable contents be reported back in the case of an error, but that makes the code rather more complex…)
The run method of my worker role is:
public override void Run()
{
Message msg=null;
while (true)
{
msg = queue.GetMessage();
if(msg!=null && msg.DequeueCount==1){
//delete message
...
//execute operations
...
}
else if(msg!=null && msg.DequeueCount>1){
//delete message
...
}
else{
int randomTime = ...
Thread.Sleep(randomTime);
}
}
}
For performance tests I would that a message could be analysed only by a worker (I don't consider failure problems on workers).
But seems by my tests, that two workers can pick up the same message and read DequeueCount equals to 1 (both workers). Is it possible?
Does exist a way that allow just a worker to read a message in a "mutex" way?
How is your "getAMessage(queue)" method defined? If you do PeekMessage(), a message will be visible by all workers. If you do GetMessage(), the message will be got only by the worker which firsts get it. But for the invisibility timeout either specified or the default (30 sec.). You have to delete the message before the invisibility timeout comes.
Check out the Queue Service API for more information. I am sure that there is something wrong in your code. I use queues and they behave as by documentation in dev storage and in production storage. You may want to explicitly put higher value of the Visibility Timeout when you do GetMessage. And make sure you do not sleep longer than the visibility timeout.
This is my coding. I am facing a problem where it didn't display the ".display_message" while it is still running. I receive the message only after the proc torun {} completed its task. Btw, after 2000 is actually my program running. It is a very long code where I feel it is not applicable so I removed it to simplified it. Please guide me in this. Thanks.
proc torun {} {
set total [.display_pc2 size]
.display_message insert end "test"
for {set x 0} {$x < $total} {incr x} {
set machine [.display_pc2 get $x]
.display_message insert end "Copy to $machine now. Please wait..."
.display_message see end
after 2000
.display_message insert end "Copy to $machine done"
.display_message see end
after 2000
}
}
You should completely change the way you write this code and use idle callbacks.
The idea is that you create a list of "tasks" to do (probably that would be a list of targets computers), save it somewhere and then process it one item at a time, rescheduling the execution using idle callback.
A sketch follows:
proc show_transfer_start {target} {
.display_progress add ... $target
}
proc show_transfer_result {res} {
.display_progress add ... $res
}
proc schedule_transfer {target rest} {
after idle [after 0 [list do_step $target $rest]]
}
proc do_step {target rest} {
set res [copy --to $target]
show_transfer_result $res
if {[llength $rest] > 0} {
set next [lindex $rest 0]
show_transfer_start $next
schedule_tranfer $next [lrange $rest 1 end]
}
}
set targets [list box1 box2 box3]
set first [lindex $targets 0]
show_transfer_start $first
schedule_transfer $first [lrange $targets 1 end]
I've never done tk (if it's gui what you're doing), but maybe you need some equivalent to flush when using puts to force-display a message.
Since I can't guess what's done inside the .display_message proc.
Edit:
Just got an idea: You can use the after command to fake-multithread your app.
after 0 [list .display_message insert end "Copy to $machine now. Please wait..."; .display_message see end]
Which will run independently from your current proc as event handler. Maybe that fixes your flush problem. (Requires the eventloop or update command)