My first initial attempts have been on Windows. On Windows, external programs send commands to Skype through sending WM_COPYDATA messages to the Skype Window (and Skype will answer the same way). There are both synchronous and asynchronous commands and your program should be able to receive commands and information from Skype at any time. One good program for understanding the Skype API and running some example tests is SkypeTracer [2].I could not find any easy way to send (and receive) WM_COPYDATA messages from Tcl, so I went for using an external component for controlling Skype. ActiveS [3] is an ActiveX layer around the Skype API that provides a higher level of abstraction. What I have succeeded in doing so far are simply a number of interactive proof-of-concept examples. ActiveS, once installed, makes available a COM object called SKYPEAPI. This object can easily be called from tcom. ActiveS comes with its own documentation, and most of its functionality is available through the Access class. The piece of code below, meant to be run line by line from a command prompt, will initiate connection to Skype, call me and hang up after a while.
package require tcom # Get a reference to the Access class in the SKYPEAPI COM object installed by ActiveS set skype [::tcom::ref createobject "SKYPEAPI.Access"] # Connect to skype and wait for connection to succeed within 5 s. $skype ConnectAndWait 5000 # Call a user (me but I'm off-line right now!) set call [$skype PlaceCall efrecon] # Hang up, when you don't want to talk to me anymore $call Status 7Still, this relies on an external COM API and on an abstraction that perhaps is not suitable for Tcl. For example, ending a call through setting a call property to "7" feels a bit awkward. Perhaps is the raw API call through WM_COPYDATA messages a better approach anyhow.
EF In the process of trying to write a more generic library to access Skype, I discovered that the API did not have support to send the video window of the other party in fullscreen. There is no keyboard shortcut either. Consequently, I had no other choice than to send appropriate mouse events to appropriate windows to do the trick. The following (rather hugly) code will do the trick. It requires my newly written winapi (which really was written for that sole purpose).
package require winapi
set topwin [::winapi::FindWindows 0 "*MainForm*" "Skype*efrecon"]
puts "TOP:$topwin: '[::winapi::GetClassName $topwin]' '[::winapi::GetWindowText $topwin]'"
proc waitForWins { top class text { poll 500 } } {
set wins ""
while { $wins == "" } {
set wins [::winapi::FindDescendantWindows $top $class $text]
if { $wins == "" } {
after $poll
}
}
set allwins ""
foreach w $wins {
set rect [::winapi::GetWindowRect $w]
foreach {left top right bottom} $rect {}
lappend allwins $w $left $top $right $bottom
}
return $allwins
}
set video [waitForWins $topwin "tSkLocalVideoControl" ""]
foreach {video left top right bottom} $video {}
puts "Video is at : $video $left $top $right $bottom"
set x [expr $left + (($right - $left) / 2)]
set y [expr $top + (($bottom - $top) / 2)]
set sx [::winapi::GetSystemMetrics SM_CXSCREEN]
set sy [::winapi::GetSystemMetrics SM_CYSCREEN]
::winapi::SendMouseInput [expr {$x * 65535 / $sx}] [expr {$y *65535 / $sy}]
{ABSOLUTE MOVE}
# Pick the button that is closest to the top of the screen and most to
# the left (in *that* order, beware, otherwise we will get hold of the
# video closing buttons that are hidden but in the hiearchy anyhow.
# First build a list of all buttons.
set allbuttons [waitForWins $video "tRegionButton" ""]
# Second find the ones closest to the top of the screen
set min_top ""
set buttons ""
foreach {w left top right bottom } $allbuttons {
if { $min_top == "" || $top < $min_top } {
set min_top $top
}
}
foreach {w left top right bottom } $allbuttons {
if { $top == $min_top } {
lappend buttons $w $left $top $right $bottom
}
}
# Third find the one mostly to the left.
set min_left ""
set button ""
foreach {w left top right bottom } $buttons {
if { $min_left == "" || $left < $min_left } {
set min_left $left
}
}
foreach {w left top right bottom } $buttons {
if { $left == $min_left } {
set button [list $w $left $top $right $bottom]
break
}
}
# Found, compute center of button, send the mouse there and simulate a click
foreach {w left top right bottom } $button {}
set bx [expr $left + (($right - $left) / 2)]
set by [expr $top + (($bottom - $top) / 2)]
::winapi::SetWindowPos $topwin HWND_TOPMOST
::winapi::SendMouseInput [expr {$bx * 65535 / $sx}] [expr {$by *65535 / $sy}]
{ABSOLUTE MOVE LEFTDOWN LEFTUP}
::winapi::SetWindowPos $topwin HWND_NOTOPMOST
::winapi::SetWindowPos $topwin HWND_BOTTOMmsorc One may have a look at tclskype
for simple interface from Unix or Windows (Mac OS X port is planned)sbron On linux it's also possible to communicate with Skype through dbus, for example using dbus-tcl:
package require dbus
dbus connect
# Handle notifications from Skype
dbus method /com/Skype/Client com.Skype.API.Client.Notify notify
# Setup a handler for name change signals ...
dbus listen /org/freedesktop/DBus org.freedesktop.DBus.NameOwnerChanged owner
# ... and ask the D-Bus server to notify us about name changes
dbus filter add -type signal -path /org/freedesktop/DBus \
-interface org.freedesktop.DBus -member NameOwnerChanged
# Print a string on stdout with a timestamp in milliseconds
proc ts {str} {
set now [clock milliseconds]
set time [clock format [expr {$now / 1000}] -format %T]
puts [format {%s.%03d: %s} $time [expr {$now % 1000}] $str]
}
# Send a command to Skype
proc skypecmd {args} {
ts [info level 0]
set rc [dbus call -autostart no -dest com.Skype.API \
/com/Skype com.Skype.API Invoke [join $args]]
ts "=> $rc"
return $rc
}
# Handle notify calls from Skype
proc notify {info args} {
ts [join $args]
}
# Connect to Skype
proc connect {} {
after cancel connect
# We get an error if Skype is not running
if {[catch {skypecmd NAME skypemon} result]} return
if {$result eq "OK"} {
# Skype is running and connected. Initiate communication.
skypecmd PROTOCOL 8
skypecmd GET SKYPEVERSION
} else {
# Skype is not yet ready. Try again a little later.
after 500 connect
}
}
proc owner {info name old new} {
if {$name eq "com.Skype.API"} {
# As soon as Skype starts, try to connect
if {$old eq ""} connect
# Report when Skype terminates
if {$new eq ""} {ts "Skype terminated"}
}
}
connect
vwait forever
