ruby-skype 0.1.0.pre.1-mswin32
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/LICENSE +19 -0
- data/README.md +60 -0
- data/VERSION +1 -0
- data/examples/call +14 -0
- data/examples/listen +14 -0
- data/lib/skype/command_manager.rb +26 -0
- data/lib/skype/communication/dbus.rb +104 -0
- data/lib/skype/communication/protocol.rb +70 -0
- data/lib/skype/communication/windows/win32.rb +365 -0
- data/lib/skype/communication/windows.rb +200 -0
- data/lib/skype/data_maps/user_visibility.rb +18 -0
- data/lib/skype/data_maps.rb +7 -0
- data/lib/skype/errors/error_messages.rb +13 -0
- data/lib/skype/errors/exception_factory.rb +26 -0
- data/lib/skype/errors/general_error.rb +19 -0
- data/lib/skype.rb +213 -0
- metadata +128 -0
data/LICENSE
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
Copyright (c) 2012 Matthew Scharley
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a
|
4
|
+
copy of this software and associated documentation files (the "Software"),
|
5
|
+
to deal in the Software without restriction, including without limitation
|
6
|
+
the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
7
|
+
and/or sell copies of the Software, and to permit persons to whom the
|
8
|
+
Software is furnished to do so, subject to the following conditions:
|
9
|
+
|
10
|
+
The above copyright notice and this permission notice shall be included
|
11
|
+
in all copies or substantial portions of the Software.
|
12
|
+
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
14
|
+
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
16
|
+
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
18
|
+
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
19
|
+
DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,60 @@
|
|
1
|
+
Skype Public API for Ruby
|
2
|
+
=========================
|
3
|
+
|
4
|
+
**GitHub:** https://github.com/mscharley/ruby-skype
|
5
|
+
**Author:** Matthew Scharley
|
6
|
+
**Contributors:** [See contributors on GitHub][gh-contrib]
|
7
|
+
**Bugs/Support:** [Github Issues][gh-issues]
|
8
|
+
**Copyright:** 2012
|
9
|
+
**License:** [MIT license][license]
|
10
|
+
|
11
|
+
Synopsis
|
12
|
+
--------
|
13
|
+
|
14
|
+
This library is a binding for the [Skype Public API][skype-api]. Currently,
|
15
|
+
due to the Skype API being accessed in different ways on different platforms
|
16
|
+
this library will initially only be supported on Linux/DBus and Windows. The
|
17
|
+
Skype Public API is a way to hook into the Skype client released by Microsoft
|
18
|
+
and automate it. To use this library *you must have a copy of the Skype
|
19
|
+
client installed locally*. This is not a stand-alone library to access the
|
20
|
+
Skype network.
|
21
|
+
|
22
|
+
**This is very much still under development**
|
23
|
+
|
24
|
+
If you want to use this or help out, please feel free to clone/fork and play
|
25
|
+
with this, but it is prone to break or change at any time.
|
26
|
+
|
27
|
+
Installation
|
28
|
+
------------
|
29
|
+
|
30
|
+
For now, you will need to install from the git repository. Simply clone it
|
31
|
+
to wherever you like and then add it to your include path if needed.
|
32
|
+
|
33
|
+
Mac OS X Support
|
34
|
+
----------------
|
35
|
+
|
36
|
+
OS X integration should be possible, however I don't have a Mac to
|
37
|
+
test/develop with. If you want to help out, then look at the
|
38
|
+
`Skype::Communication::*` classes. `Skype::Communication::Protocol` is the
|
39
|
+
base interface you need to implement. Any help would be greatly appreciated!
|
40
|
+
|
41
|
+
Documentation
|
42
|
+
-------------
|
43
|
+
|
44
|
+
There is some documentation about the Skype API itself in the doc folder. The
|
45
|
+
[ruby-skype API documentation is available online][ruby-skype-rubydoc]. To
|
46
|
+
generate them locally, simply clone the library, then run `yard` in the repo
|
47
|
+
root. The API documentation will be available in `/doc/api` when complete.
|
48
|
+
|
49
|
+
Goals
|
50
|
+
-----
|
51
|
+
|
52
|
+
Generate a Ruby friendly front-end to access the Skype public API.
|
53
|
+
|
54
|
+
|
55
|
+
[skype-api]: http://developer.skype.com/public-api-reference
|
56
|
+
[license]: https://raw.github.com/mscharley/ruby-skype/master/LICENSE
|
57
|
+
[ruby-skype-rubydoc]: http://rubydoc.info/github/mscharley/ruby-skype/master/frames
|
58
|
+
[gh-contrib]: https://github.com/mscharley/ruby-skype/graphs/contributors
|
59
|
+
[gh-issues]: https://github.com/mscharley/ruby-skype/issues
|
60
|
+
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.1.0.pre.1
|
data/examples/call
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
$:.push('lib', '../lib')
|
4
|
+
|
5
|
+
require 'skype'
|
6
|
+
Skype.DEBUG = true
|
7
|
+
skype = Skype.new('ruby-skype-test')
|
8
|
+
puts "Using ruby-skype-#{Skype.VERSION}"
|
9
|
+
skype.connect
|
10
|
+
puts "Connected using protocol version #{skype.protocol_version}"
|
11
|
+
|
12
|
+
puts "Attempting to call the echo service..."
|
13
|
+
skype.send_raw_command('CALL echo123')
|
14
|
+
skype.run
|
data/examples/listen
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
# This program simply attaches to Skype and runs an event loop. Can be used as a starting point, or a way to
|
4
|
+
# listen for new update commands that we may not know about yet.
|
5
|
+
|
6
|
+
$:.push('lib', '../lib')
|
7
|
+
|
8
|
+
require 'skype'
|
9
|
+
Skype.DEBUG = true
|
10
|
+
puts "Using ruby-skype-#{Skype.VERSION}"
|
11
|
+
skype = Skype.new('ruby-skype-listen-in')
|
12
|
+
skype.connect
|
13
|
+
puts "Connected using protocol version #{skype.protocol_version}"
|
14
|
+
skype.run
|
@@ -0,0 +1,26 @@
|
|
1
|
+
|
2
|
+
class Skype
|
3
|
+
# This class is used to manage updates coming back from Skype.
|
4
|
+
#
|
5
|
+
# @see #process_message
|
6
|
+
class CommandManager
|
7
|
+
def initialize(skype)
|
8
|
+
@skype = skype
|
9
|
+
end
|
10
|
+
|
11
|
+
def process_command(command)
|
12
|
+
(command, args) = command.split(/\s+/, 2)
|
13
|
+
command = command.downcase.to_sym
|
14
|
+
|
15
|
+
self.send(command, args) if self.public_methods.include? command
|
16
|
+
end
|
17
|
+
|
18
|
+
def connstatus(args)
|
19
|
+
@skype.send :update_connection_status, args.downcase.to_sym
|
20
|
+
end
|
21
|
+
|
22
|
+
def userstatus(args)
|
23
|
+
@skype.send :update_user_status, DataMaps::USER_VISIBILITY.invert[args]
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
|
2
|
+
require 'dbus'
|
3
|
+
require 'skype/communication/protocol'
|
4
|
+
|
5
|
+
# Monkey-patch dbus to fix an error till it makes it into a release
|
6
|
+
class DBus::Connection
|
7
|
+
def update_buffer
|
8
|
+
@buffer += @socket.read_nonblock(MSG_BUF_SIZE)
|
9
|
+
rescue EOFError
|
10
|
+
raise # the caller expects it
|
11
|
+
rescue Errno::EWOULDBLOCK
|
12
|
+
# simply fail the read if it would block
|
13
|
+
return
|
14
|
+
rescue Exception => e
|
15
|
+
puts "Oops:", e
|
16
|
+
raise if @is_tcp # why?
|
17
|
+
puts "WARNING: read_nonblock failed, falling back to .recv"
|
18
|
+
@buffer += @socket.recv(MSG_BUF_SIZE)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
class Skype
|
23
|
+
module Communication
|
24
|
+
# This class handles communication with Skype via DBus.
|
25
|
+
#
|
26
|
+
# This communication method is available under Linux.
|
27
|
+
class DBus
|
28
|
+
include Skype::Communication::Protocol
|
29
|
+
|
30
|
+
# DBus service name to connect to.
|
31
|
+
SKYPE_DBUS_SERVICE = 'com.Skype.API'
|
32
|
+
# DBus communication path for client -> Skype communication.
|
33
|
+
SKYPE_SERVER_PATH = '/com/Skype'
|
34
|
+
# Interface for the client -> Skype communication function.
|
35
|
+
SKYPE_SERVER_INTERFACE = 'com.Skype.API'
|
36
|
+
# DBus communication path for Skype -> client communication.
|
37
|
+
SKYPE_CLIENT_PATH = '/com/Skype/Client'
|
38
|
+
# Interface for the Skype -> client communication function.
|
39
|
+
SKYPE_CLIENT_INTERFACE = 'com.Skype.API.Client'
|
40
|
+
|
41
|
+
# Create a communication link to Skype via DBus. This initialises DBus,
|
42
|
+
# but doesn't attempt to connect to Skype yet.
|
43
|
+
#
|
44
|
+
# @see #connect.
|
45
|
+
def initialize(application_name)
|
46
|
+
@application_name = application_name
|
47
|
+
@dbus = ::DBus::SessionBus.instance
|
48
|
+
@dbus_service = @dbus.service(SKYPE_DBUS_SERVICE)
|
49
|
+
@skype = @dbus_service.object(SKYPE_SERVER_PATH)
|
50
|
+
@skype.introspect
|
51
|
+
@skype.default_iface = SKYPE_SERVER_INTERFACE
|
52
|
+
end
|
53
|
+
|
54
|
+
# Attempt to connect to Skype.
|
55
|
+
#
|
56
|
+
# For DBus, this includes exporting the client interface and then
|
57
|
+
# identifying ourselves and negotiating protocol version.
|
58
|
+
#
|
59
|
+
# @return [void]
|
60
|
+
def connect
|
61
|
+
value = @skype.Invoke("NAME " + @application_name)
|
62
|
+
unless value == %w{OK}
|
63
|
+
Skype::Errors::ExceptionFactory.generate_exception *value
|
64
|
+
end
|
65
|
+
@protocol_version = @skype.Invoke("PROTOCOL 8")[0].
|
66
|
+
sub(/^PROTOCOL\s+/, '').to_i
|
67
|
+
@connected = true
|
68
|
+
end
|
69
|
+
|
70
|
+
# Send a command to Skype.
|
71
|
+
#
|
72
|
+
# @param [string] message The message to send to Skype
|
73
|
+
# @return [string] The direct response from Skype
|
74
|
+
def send(message)
|
75
|
+
unless @connected
|
76
|
+
raise "You must be connected before sending data."
|
77
|
+
end
|
78
|
+
puts "-> #{message}" if ::Skype.DEBUG
|
79
|
+
ret = @skype.Invoke(message)[0]
|
80
|
+
puts "<- #{ret}" if ::Skype.DEBUG
|
81
|
+
ret
|
82
|
+
end
|
83
|
+
|
84
|
+
# Poll DBus for incoming messages. We use this method for watching for
|
85
|
+
# our messages as it is simpler, and an event loop is required no matter
|
86
|
+
# what.
|
87
|
+
#
|
88
|
+
# @return [void]
|
89
|
+
def tick
|
90
|
+
@dbus.update_buffer
|
91
|
+
@dbus.messages.each do |msg|
|
92
|
+
# Pass messages through DBus
|
93
|
+
@dbus.process(msg)
|
94
|
+
|
95
|
+
# Process messages to us.
|
96
|
+
if msg.interface == SKYPE_CLIENT_INTERFACE &&
|
97
|
+
msg.path == SKYPE_CLIENT_PATH
|
98
|
+
receive(msg.params[0])
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
|
2
|
+
require 'observer'
|
3
|
+
|
4
|
+
class Skype
|
5
|
+
# You shouldn't try including this module, it is used purely for organisation.
|
6
|
+
# @private
|
7
|
+
module Communication
|
8
|
+
# Interface for the Skype::Communication::* classes. This provides basic
|
9
|
+
# input and output functionality with Skype.
|
10
|
+
#
|
11
|
+
# Includes Observable. Notifications sent will have a single string
|
12
|
+
# parameter which is the incoming command from Skype.
|
13
|
+
module Protocol
|
14
|
+
include Observable
|
15
|
+
|
16
|
+
# The protocol version supported by this communication protocol
|
17
|
+
#
|
18
|
+
# @return [Boolean]
|
19
|
+
attr_reader :protocol_version
|
20
|
+
|
21
|
+
# @!attribute [r] connected?
|
22
|
+
# Have we connected to Skype yet?
|
23
|
+
#
|
24
|
+
# @return [Boolean]
|
25
|
+
def connected?
|
26
|
+
@connected
|
27
|
+
end
|
28
|
+
|
29
|
+
# Sends a message to Skype.
|
30
|
+
#
|
31
|
+
# Must be implemented by Protocol implementers.
|
32
|
+
#
|
33
|
+
# @param [string] message The message to send to Skype
|
34
|
+
# @return [string] The direct response from Skype
|
35
|
+
# @return [string] The direct response from Skype
|
36
|
+
def send(message)
|
37
|
+
raise "#send(message) must be implemented."
|
38
|
+
end
|
39
|
+
|
40
|
+
# Connects to Skype.
|
41
|
+
#
|
42
|
+
# Must be implemented by Protocol implementers.
|
43
|
+
#
|
44
|
+
# @return [void]
|
45
|
+
def connect
|
46
|
+
raise "#connect must be implemented"
|
47
|
+
end
|
48
|
+
|
49
|
+
# Update processing. This is where you get a chance to check for input.
|
50
|
+
#
|
51
|
+
# Should be implemented by Protocol implementers, but no error is thrown
|
52
|
+
# if not.
|
53
|
+
#
|
54
|
+
# @return [void]
|
55
|
+
def tick
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
|
60
|
+
# Internal method to implement Observable.
|
61
|
+
#
|
62
|
+
# Protocol implementers may use this to send notifications of incoming
|
63
|
+
# commands.
|
64
|
+
def receive(message)
|
65
|
+
changed
|
66
|
+
notify_observers(message)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,365 @@
|
|
1
|
+
|
2
|
+
require 'ffi'
|
3
|
+
|
4
|
+
class Skype
|
5
|
+
module Communication
|
6
|
+
class Windows
|
7
|
+
# This module is used to provide access to the Win32 API to Ruby. There
|
8
|
+
# is lots of stuff here that is poorly named but tries stick closely to
|
9
|
+
# the original Win32 API for ease of reference.
|
10
|
+
#
|
11
|
+
# BEWARE: Here there be dragons aplenty.
|
12
|
+
module Win32
|
13
|
+
extend FFI::Library
|
14
|
+
ffi_lib('user32', 'kernel32')
|
15
|
+
ffi_convention(:stdcall)
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def self._func(*args)
|
20
|
+
attach_function *args
|
21
|
+
case args.size
|
22
|
+
when 3
|
23
|
+
module_function args[0]
|
24
|
+
when 4
|
25
|
+
module_function args[0]
|
26
|
+
alias_method(args[1], args[0])
|
27
|
+
module_function args[1]
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
# @!group Windows Data Types
|
32
|
+
# @see http://msdn.microsoft.com/en-us/library/aa383751%28VS.85%29.aspx
|
33
|
+
|
34
|
+
# Check for 64b operating systems.
|
35
|
+
if [nil].pack('p').bytesize == 8
|
36
|
+
ULONG_PTR = FFI::TypeDefs[:uint64]
|
37
|
+
LONG_PTR = FFI::TypeDefs[:int64]
|
38
|
+
else
|
39
|
+
ULONG_PTR = FFI::TypeDefs[:ulong]
|
40
|
+
LONG_PTR = FFI::TypeDefs[:long]
|
41
|
+
end
|
42
|
+
|
43
|
+
ULONG = FFI::TypeDefs[:ulong]
|
44
|
+
LONG = FFI::TypeDefs[:long]
|
45
|
+
PVOID = FFI::TypeDefs[:pointer]
|
46
|
+
LPVOID = FFI::TypeDefs[:pointer]
|
47
|
+
INT = FFI::TypeDefs[:int]
|
48
|
+
BYTE = FFI::TypeDefs[:uint16]
|
49
|
+
DWORD = FFI::TypeDefs[:ulong]
|
50
|
+
BOOL = FFI::TypeDefs[:int]
|
51
|
+
UINT = FFI::TypeDefs[:uint]
|
52
|
+
POINTER = FFI::TypeDefs[:pointer]
|
53
|
+
VOID = FFI::TypeDefs[:void]
|
54
|
+
|
55
|
+
HANDLE = ULONG_PTR
|
56
|
+
HWND = HANDLE
|
57
|
+
HICON = HANDLE
|
58
|
+
HCURSOR = HANDLE
|
59
|
+
HBRUSH = HANDLE
|
60
|
+
HINSTANCE = HANDLE
|
61
|
+
HGDIOBJ = HANDLE
|
62
|
+
HMENU = HANDLE
|
63
|
+
HMODULE = HANDLE
|
64
|
+
|
65
|
+
LPARAM = LONG_PTR
|
66
|
+
WPARAM = ULONG_PTR
|
67
|
+
LPMSG = LPVOID
|
68
|
+
LPCTSTR = LPVOID
|
69
|
+
LRESULT = LONG_PTR
|
70
|
+
ATOM = BYTE
|
71
|
+
|
72
|
+
# @!endgroup
|
73
|
+
|
74
|
+
public
|
75
|
+
|
76
|
+
# Provide a NULL constant so we can be a little more explicit.
|
77
|
+
NULL = 0
|
78
|
+
|
79
|
+
# This is the callback function used to process window messages.
|
80
|
+
WNDPROC = callback(:WindowProc, [HWND, UINT, WPARAM, LPARAM], LRESULT)
|
81
|
+
|
82
|
+
# @see http://msdn.microsoft.com/en-us/library/windows/desktop/ms633577.aspx MSDN
|
83
|
+
class WNDCLASSEX < FFI::Struct
|
84
|
+
layout :cbSize, UINT,
|
85
|
+
:style, UINT,
|
86
|
+
:lpfnWndProc, WNDPROC,
|
87
|
+
:cbClsExtra, INT,
|
88
|
+
:cbWndExtra, INT,
|
89
|
+
:hInstance, HANDLE,
|
90
|
+
:hIcon, HICON,
|
91
|
+
:hCursor, HCURSOR,
|
92
|
+
:hbrBackground, HBRUSH,
|
93
|
+
:lpszMenuName, LPCTSTR,
|
94
|
+
:lpszClassName, LPCTSTR,
|
95
|
+
:hIconSm, HICON
|
96
|
+
|
97
|
+
def initialize(*args)
|
98
|
+
super
|
99
|
+
self[:cbSize] = self.size
|
100
|
+
@atom = 0
|
101
|
+
end
|
102
|
+
|
103
|
+
# Register class with Windows.
|
104
|
+
def register_class_ex
|
105
|
+
# According to MSDN, you must add 1 to this value before
|
106
|
+
# registering. We shouldn't expect client code to remember to
|
107
|
+
# always do this.
|
108
|
+
if self[:hbrBackground] > 0
|
109
|
+
self[:hbrBackground] = self[:hbrBackground] + 1
|
110
|
+
end
|
111
|
+
|
112
|
+
(@atom = Win32::RegisterClassEx(self)) != 0 ?
|
113
|
+
@atom : raise("RegisterClassEx Error")
|
114
|
+
end
|
115
|
+
|
116
|
+
# @!attribute [r] handle
|
117
|
+
#
|
118
|
+
# Returns a handle to use the windo class with CreateWindowEx()
|
119
|
+
def handle
|
120
|
+
@atom != 0 ? @atom : register_class_ex
|
121
|
+
end
|
122
|
+
end # WNDCLASSEX
|
123
|
+
|
124
|
+
# @see http://msdn.microsoft.com/en-us/library/windows/desktop/dd162805.aspx
|
125
|
+
class POINT < FFI::Struct
|
126
|
+
layout :x, LONG,
|
127
|
+
:y, LONG
|
128
|
+
end
|
129
|
+
|
130
|
+
# @see http://msdn.microsoft.com/en-us/library/windows/desktop/ms644958.aspx
|
131
|
+
class MSG < FFI::Struct
|
132
|
+
layout :hwnd, HWND,
|
133
|
+
:message, UINT,
|
134
|
+
:wParam, WPARAM,
|
135
|
+
:lParam, LPARAM,
|
136
|
+
:time, DWORD,
|
137
|
+
:pt, POINT
|
138
|
+
end
|
139
|
+
|
140
|
+
# @see http://msdn.microsoft.com/en-us/library/windows/desktop/ms649010.aspx
|
141
|
+
class COPYDATASTRUCT < FFI::Struct
|
142
|
+
layout :dwData, ULONG_PTR,
|
143
|
+
:cbData, DWORD,
|
144
|
+
:lpData, PVOID
|
145
|
+
end
|
146
|
+
|
147
|
+
# @!method RegisterWindowMessage(message_name)
|
148
|
+
#
|
149
|
+
# Registers a Window Message with Windows or returns a handle to an
|
150
|
+
# existing message.
|
151
|
+
#
|
152
|
+
# @param [String] message_name The name of the message to register
|
153
|
+
# @return [Handle]
|
154
|
+
# @see http://msdn.microsoft.com/en-us/library/windows/desktop/ms644947.aspx MSDN
|
155
|
+
_func(:RegisterWindowMessage, :RegisterWindowMessageA, [LPCTSTR], UINT)
|
156
|
+
|
157
|
+
# @!method GetModuleHandle(module_name)
|
158
|
+
#
|
159
|
+
# Used to obtain a handle to a module loaded by the application. If
|
160
|
+
# passed DL::NULL then returns a handle to the current module.
|
161
|
+
#
|
162
|
+
# @param [String|DL::NULL] module_name The name of the module to return
|
163
|
+
# a handle to.
|
164
|
+
# @return [ModuleHandle]
|
165
|
+
# @see http://msdn.microsoft.com/en-us/library/windows/desktop/ms683199.aspx MSDN
|
166
|
+
_func(:GetModuleHandle, :GetModuleHandleA, [LPCTSTR], HMODULE)
|
167
|
+
|
168
|
+
# @!method RegisterClassEx(class_definition)
|
169
|
+
#
|
170
|
+
# Registers a Window Class for use with CreateWindowEx.
|
171
|
+
#
|
172
|
+
# @param [WindowClass] class_definition
|
173
|
+
# @return [Handle]
|
174
|
+
# @see http://msdn.microsoft.com/en-us/library/windows/desktop/ms633587.aspx MSDN
|
175
|
+
_func(:RegisterClassEx, :RegisterClassExA, [LPVOID], ATOM)
|
176
|
+
|
177
|
+
# @!method CreateWindowEx(extended_style, window_class, window_name, style, x, y, width, height, parent, menu, instance)
|
178
|
+
#
|
179
|
+
# Creates a new window.
|
180
|
+
#
|
181
|
+
# @param [Integer] extended_style A combination of WS_EX_* constant
|
182
|
+
# values defining the extended style for this window.
|
183
|
+
# @param [String] window_class This matches up with a registered
|
184
|
+
# WindowClass's lpszClassName parameter.
|
185
|
+
# @param [String] window_name This is the title of the newly created
|
186
|
+
# window.
|
187
|
+
# @param [Integer] style A combination of the WS_* constant values
|
188
|
+
# defining the style for this window.
|
189
|
+
# @param [Integer] x The horizontal position of the window on the
|
190
|
+
# screen.
|
191
|
+
# @param [Integer] y The vertical position of the window on the screen.
|
192
|
+
# @param [Integer] width The width of the window to create.
|
193
|
+
# @param [Integer] height The height of the window to create.
|
194
|
+
# @param [WindowHandle] parent A parent window for this one.
|
195
|
+
# @param [MenuHandle] menu The menu for this window.
|
196
|
+
# @param [InstanceHandle] instance
|
197
|
+
# @return [WindowHandle]
|
198
|
+
# @see http://msdn.microsoft.com/en-us/library/windows/desktop/ms632680.aspx MSDN
|
199
|
+
_func(:CreateWindowEx, :CreateWindowExA, [DWORD, LPCTSTR, LPCTSTR,
|
200
|
+
DWORD, INT, INT, INT, INT,
|
201
|
+
HWND, HMENU, HINSTANCE,
|
202
|
+
LPVOID],
|
203
|
+
HWND)
|
204
|
+
|
205
|
+
# @!method GetMessage(message, window, filter_min, filter_max)
|
206
|
+
#
|
207
|
+
# Get a message from the message queue. Blocks until there is one to
|
208
|
+
# return. Compare with PeekMessage().
|
209
|
+
#
|
210
|
+
# @param [MSG] message **[out]** A message structure to output the
|
211
|
+
# incoming message to.
|
212
|
+
# @param [WindowHandle] window Which window to get messages for.
|
213
|
+
# @param [Integer] filter_min The first message to return, numerically.
|
214
|
+
# For suggestions, see MSDN.
|
215
|
+
# @param [Integer] filter_max The last message to return, numerically.
|
216
|
+
# If min and max are both 0 then all messages are returned. For
|
217
|
+
# suggestions, see MSDN.
|
218
|
+
# @return [Integer] -1 on error, otherwise 0 or 1 indicating whether to
|
219
|
+
# keep processing.
|
220
|
+
# @see http://msdn.microsoft.com/en-us/library/windows/desktop/ms644936.aspx MSDN
|
221
|
+
_func(:GetMessage, :GetMessageA, [LPMSG, HWND, UINT, UINT], BOOL)
|
222
|
+
|
223
|
+
# @!method PeekMessage(message, window, filter_min, filter_max, remove_message)
|
224
|
+
#
|
225
|
+
# Peek at a message from the message queue and optionally remove it.
|
226
|
+
# Never blocks. Compare with GetMessage().
|
227
|
+
#
|
228
|
+
# @param [MSG] message **[out]** A message structure to output the
|
229
|
+
# incoming message to.
|
230
|
+
# @param [WindowHandle] window Which window to get messages for.
|
231
|
+
# @param [Integer] filter_min The first message to return, numerically.
|
232
|
+
# For suggestions, see MSDN.
|
233
|
+
# @param [Integer] filter_max The last message to return, numerically.
|
234
|
+
# If min and max are both 0 then all messages are returned. For
|
235
|
+
# suggestions, see MSDN.
|
236
|
+
# @param [Integer] remove_message One of the PM_* values.
|
237
|
+
# @return [Integer] 0 or 1 indicating whether to keep processing.
|
238
|
+
# @see http://msdn.microsoft.com/en-us/library/windows/desktop/ms644943.aspx MSDN
|
239
|
+
_func(:PeekMessage, :PeekMessageA, [LPMSG, HWND, UINT, UINT, UINT], BOOL)
|
240
|
+
|
241
|
+
# @!method SendMessage(window, message, wParam, lParam)
|
242
|
+
#
|
243
|
+
# Send a message to another window.
|
244
|
+
#
|
245
|
+
# @param [WindowHandle] window Which window to send the message to.
|
246
|
+
# @param [Integer] message WM_* message to send.
|
247
|
+
# @see http://msdn.microsoft.com/en-us/library/windows/desktop/ms644950.aspx MSDN
|
248
|
+
_func(:SendMessage, :SendMessageA, [HWND, UINT, WPARAM, LPARAM], LRESULT)
|
249
|
+
|
250
|
+
# @!method PostMessage(window, message, wParam, lParam)
|
251
|
+
#
|
252
|
+
# Send a message to another window and return without waiting for a reply.
|
253
|
+
#
|
254
|
+
# @param [WindowHandle] window Which window to send the message to.
|
255
|
+
# @param [Integer] message WM_* message to send.
|
256
|
+
# @see http://msdn.microsoft.com/en-us/library/windows/desktop/ms644950.aspx MSDN
|
257
|
+
_func(:PostMessage, :PostMessageA, [HWND, UINT, WPARAM, LPARAM], LRESULT)
|
258
|
+
|
259
|
+
# @!method TranslateMessage(message)
|
260
|
+
# @see http://msdn.microsoft.com/en-us/library/windows/desktop/ms644955.aspx MSDN
|
261
|
+
_func(:TranslateMessage, [LPVOID], BOOL)
|
262
|
+
|
263
|
+
# @!method DispatchMessage(message)
|
264
|
+
# @see http://msdn.microsoft.com/en-us/library/windows/desktop/ms644934.aspx MSDN
|
265
|
+
_func(:DispatchMessage, :DispatchMessageA, [LPVOID], BOOL)
|
266
|
+
|
267
|
+
# @!method DefWindowProc(message, window, filter_min, filter_max)
|
268
|
+
# @return [Object] Return value is dependant on message type
|
269
|
+
# @see http://msdn.microsoft.com/en-us/library/windows/desktop/ms633572.aspx MSDN
|
270
|
+
_func(:DefWindowProc, :DefWindowProcA, [HWND, UINT, WPARAM, LPARAM], LRESULT)
|
271
|
+
|
272
|
+
# @!group Predefined WindowHandle's
|
273
|
+
#
|
274
|
+
# These are WindowHandle's provided by the Win32 API for special
|
275
|
+
# purposes.
|
276
|
+
|
277
|
+
# Target for SendMessage(). Broadcast to all windows.
|
278
|
+
HWND_BROADCAST = 0xffff
|
279
|
+
# Used as a parent in CreateWindow(). Signifies that this should be a
|
280
|
+
# message-only window.
|
281
|
+
HWND_MESSAGE = -3
|
282
|
+
|
283
|
+
# @!endgroup
|
284
|
+
|
285
|
+
# CreateWindow Use Default Value
|
286
|
+
CW_USEDEFAULT = 0x80000000
|
287
|
+
|
288
|
+
#@!group HBRUSH colours
|
289
|
+
|
290
|
+
# System window colour
|
291
|
+
COLOR_WINDOW = 5
|
292
|
+
|
293
|
+
# @!group Class Style contants.
|
294
|
+
# @see http://msdn.microsoft.com/en-us/library/windows/desktop/ff729176.aspx MSDN
|
295
|
+
|
296
|
+
# Redraws the entire window if a movement or size adjustment changes
|
297
|
+
# the height of the client area.
|
298
|
+
CS_VREDRAW = 0x0001
|
299
|
+
# Redraws the entire window if a movement or size adjustment changes
|
300
|
+
# the width of the client area.
|
301
|
+
CS_HREDRAW = 0x0002
|
302
|
+
|
303
|
+
# @!group Window Message constants
|
304
|
+
# @see http://msdn.microsoft.com/en-us/library/windows/desktop/ms644927.aspx#system_defined MSDN
|
305
|
+
|
306
|
+
# An application sends the WM_COPYDATA message to pass data to another
|
307
|
+
# application.
|
308
|
+
# @see http://msdn.microsoft.com/en-us/library/windows/desktop/ms649011.aspx MSDN
|
309
|
+
WM_COPYDATA = 0x004A
|
310
|
+
|
311
|
+
# @!group PeekMessage constants
|
312
|
+
|
313
|
+
# Messages are not removed from the queue after processing by
|
314
|
+
# #PeekMessage().
|
315
|
+
PM_NOREMOVE = 0
|
316
|
+
# Messages are removed from the queue after processing by
|
317
|
+
# #PeekMessage().
|
318
|
+
PM_REMOVE = 1
|
319
|
+
|
320
|
+
# @!group Window Style constants
|
321
|
+
#
|
322
|
+
# This is only a subset.
|
323
|
+
# @see http://msdn.microsoft.com/en-us/library/windows/desktop/ms632600.aspx
|
324
|
+
|
325
|
+
# The window has a thin-line border.
|
326
|
+
WS_BORDER = 0x00800000
|
327
|
+
# The window has a title bar
|
328
|
+
WS_CAPTION = 0x00C00000
|
329
|
+
# The window is initially disabled. A disabled window cannot receive
|
330
|
+
# input from the user.
|
331
|
+
WS_DISABLED = 0x08000000
|
332
|
+
# The window is an overlapped window. An overlapped window has a title
|
333
|
+
# bar and a border.
|
334
|
+
WS_OVERLAPPED = 0x00000000
|
335
|
+
# The windows is a pop-up window.
|
336
|
+
WS_POPUP = 0x80000000
|
337
|
+
# The window has a sizing border.
|
338
|
+
WS_SIZEBOX = 0x00040000
|
339
|
+
# The window has a window menu on its title bar.
|
340
|
+
WS_SYSMENU = 0x00080000
|
341
|
+
# The window has a sizing border.
|
342
|
+
WS_THICKFRAME = 0x00040000
|
343
|
+
# The window has a maximize button.
|
344
|
+
WS_MAXIMIZEBOX = 0x00010000
|
345
|
+
# The window has a minimize button.
|
346
|
+
WS_MINIMIZEBOX = 0x00020000
|
347
|
+
# The window is an overlapped window.
|
348
|
+
WS_OVERLAPPEDWINDOW = WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU |
|
349
|
+
WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX
|
350
|
+
# The window is a pop-up window.
|
351
|
+
WS_POPUPWINDOW = WS_POPUP | WS_BORDER | WS_SYSMENU
|
352
|
+
|
353
|
+
# @!group Window Extended Style constants
|
354
|
+
#
|
355
|
+
# This is only a subset.
|
356
|
+
# @see http://msdn.microsoft.com/en-us/library/windows/desktop/ff700543.aspx
|
357
|
+
|
358
|
+
# The window has generic left-aligned properties. This is the default.
|
359
|
+
WS_EX_LEFT = 0
|
360
|
+
|
361
|
+
# @!endgroup
|
362
|
+
end
|
363
|
+
end
|
364
|
+
end
|
365
|
+
end
|
@@ -0,0 +1,200 @@
|
|
1
|
+
|
2
|
+
require 'skype/communication/protocol'
|
3
|
+
require 'skype/communication/windows/win32'
|
4
|
+
|
5
|
+
class Skype
|
6
|
+
module Communication
|
7
|
+
# Utilises the Windows API to send and receive Window Messages to/from
|
8
|
+
# Skype.
|
9
|
+
#
|
10
|
+
# This protocol is only available on Windows and Cygwin.
|
11
|
+
class Windows
|
12
|
+
include Skype::Communication::Protocol
|
13
|
+
|
14
|
+
# Sets up access to Skype
|
15
|
+
#
|
16
|
+
# @see http://msdn.microsoft.com/en-us/library/bb384843.aspx Creating
|
17
|
+
# Win32-Based Applications
|
18
|
+
def initialize(application_name)
|
19
|
+
@application_name = application_name
|
20
|
+
|
21
|
+
# Get the message id's for the Skype Control messages
|
22
|
+
@api_discover_message_id =
|
23
|
+
Win32::RegisterWindowMessage('SkypeControlAPIDiscover')
|
24
|
+
@api_attach_message_id =
|
25
|
+
Win32::RegisterWindowMessage('SkypeControlAPIAttach')
|
26
|
+
|
27
|
+
instance = Win32::GetModuleHandle(nil)
|
28
|
+
|
29
|
+
@window_class = Win32::WNDCLASSEX.new
|
30
|
+
@window_class[:style] = Win32::CS_HREDRAW | Win32::CS_VREDRAW
|
31
|
+
@window_class[:lpfnWndProc] = method(:message_pump)
|
32
|
+
@window_class[:hInstance] = instance
|
33
|
+
@window_class[:hbrBackground] = Win32::COLOR_WINDOW
|
34
|
+
@window_class[:lpszClassName] =
|
35
|
+
FFI::MemoryPointer.from_string 'ruby-skype'
|
36
|
+
|
37
|
+
@window = Win32::CreateWindowEx(Win32::WS_EX_LEFT,
|
38
|
+
FFI::Pointer.new(@window_class.handle),
|
39
|
+
'ruby-skype',
|
40
|
+
Win32::WS_OVERLAPPEDWINDOW,
|
41
|
+
0, 0, 0, 0, Win32::NULL, Win32::NULL,
|
42
|
+
instance, nil)
|
43
|
+
end
|
44
|
+
|
45
|
+
# Connects to Skype.
|
46
|
+
#
|
47
|
+
# @return [void]
|
48
|
+
def connect
|
49
|
+
# Do setup before sending message as Windows will process messages as
|
50
|
+
# well while in SendMessage()
|
51
|
+
@msg = Win32::MSG.new
|
52
|
+
@authorized = nil
|
53
|
+
@message_counter = 0
|
54
|
+
@replies = {}
|
55
|
+
|
56
|
+
Win32::PostMessage(Win32::HWND_BROADCAST, @api_discover_message_id,
|
57
|
+
@window, 0)
|
58
|
+
end
|
59
|
+
|
60
|
+
# Update processing.
|
61
|
+
#
|
62
|
+
# This executes a Windows event loop while there are messages still
|
63
|
+
# pending, then dumps back out to let other things take over and do their
|
64
|
+
# thing.
|
65
|
+
#
|
66
|
+
# @return [void]
|
67
|
+
def tick
|
68
|
+
while Win32::PeekMessage(@msg, Win32::NULL, 0, 0, Win32::PM_REMOVE) > 0
|
69
|
+
Win32::TranslateMessage(@msg)
|
70
|
+
Win32::DispatchMessage(@msg)
|
71
|
+
end
|
72
|
+
|
73
|
+
# Don't simplify this as we rely on false != nil for tribool values
|
74
|
+
#noinspection RubySimplifyBooleanInspection
|
75
|
+
if @authorized == false
|
76
|
+
Skype::Errors::ExceptionFactory.generate_exception("ERROR 68")
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
# Sends a message to Skype.
|
81
|
+
#
|
82
|
+
# @param [string] message The message to send to Skype
|
83
|
+
# @return [string] The direct response from Skype
|
84
|
+
def send(message)
|
85
|
+
puts "-> #{message}" if Skype.DEBUG
|
86
|
+
|
87
|
+
counter = next_message_counter
|
88
|
+
message = "##{counter} #{message}"
|
89
|
+
|
90
|
+
data = Win32::COPYDATASTRUCT.new
|
91
|
+
data[:dwData] = 0
|
92
|
+
data[:cbData] = message.length + 1
|
93
|
+
data[:lpData] = FFI::MemoryPointer.from_string(message + "\0")
|
94
|
+
|
95
|
+
Win32::SendMessage(@skype_window, Win32::WM_COPYDATA, @window,
|
96
|
+
pointer_to_long(data.to_ptr))
|
97
|
+
|
98
|
+
while @replies[counter].nil?
|
99
|
+
tick
|
100
|
+
sleep(0.1)
|
101
|
+
end
|
102
|
+
|
103
|
+
ret = @replies[counter]
|
104
|
+
@replies.delete(counter)
|
105
|
+
ret
|
106
|
+
end
|
107
|
+
|
108
|
+
# Attached to Skype successfully.
|
109
|
+
API_ATTACH_SUCCESS = 0
|
110
|
+
# Skype indicated that we should hold on.
|
111
|
+
API_ATTACH_PENDING = 1
|
112
|
+
# Attachment to Skype was refused.
|
113
|
+
API_ATTACH_REFUSED = 2
|
114
|
+
# Attachment to Skype isn't available currently. Typically there is no
|
115
|
+
# user logged in.
|
116
|
+
API_ATTACH_NOT_AVAILABLE = 3
|
117
|
+
|
118
|
+
private
|
119
|
+
|
120
|
+
def next_message_counter
|
121
|
+
@message_counter += 1
|
122
|
+
end
|
123
|
+
|
124
|
+
LPARAM_BITS = Win32::LPARAM.size * 8
|
125
|
+
|
126
|
+
# Convert a ulong pointer value to a long int for use as a LPARAM because
|
127
|
+
# someone at Microsoft thought it'd be a good idea to pass around
|
128
|
+
# pointers as signed values.
|
129
|
+
def pointer_to_long(pointer)
|
130
|
+
pointer = pointer.to_i
|
131
|
+
pointer > (2 ** (LPARAM_BITS - 1)) ?
|
132
|
+
pointer - (2 ** LPARAM_BITS) : pointer
|
133
|
+
end
|
134
|
+
|
135
|
+
# Allows us to unwrap a pointer from a long. See #pointer_to_long
|
136
|
+
def long_to_pointer(long)
|
137
|
+
long < 0 ? long + (2 ** LPARAM_BITS) : long
|
138
|
+
end
|
139
|
+
|
140
|
+
# This is our message pump that receives messages from Windows.
|
141
|
+
#
|
142
|
+
# The return value from DefWindowProc is important and must be returned
|
143
|
+
# somehow.
|
144
|
+
#
|
145
|
+
# @see
|
146
|
+
# http://msdn.microsoft.com/en-us/library/windows/desktop/ms633573.aspx
|
147
|
+
# MSDN
|
148
|
+
def message_pump(window_handle, message_id, wParam, lParam)
|
149
|
+
case message_id
|
150
|
+
when @api_discover_message_id
|
151
|
+
# Drop WM_API_DISCOVER messages on the floor
|
152
|
+
when @api_attach_message_id
|
153
|
+
case lParam
|
154
|
+
when API_ATTACH_SUCCESS
|
155
|
+
@skype_window = wParam
|
156
|
+
send("NAME " + @application_name)
|
157
|
+
@protocol_version = send("PROTOCOL 8").sub(/^PROTOCOL\s+/, '').
|
158
|
+
to_i
|
159
|
+
@authorized = true
|
160
|
+
|
161
|
+
when API_ATTACH_REFUSED
|
162
|
+
# Signal to the message pump that we were deauthorised
|
163
|
+
@authorized = false
|
164
|
+
|
165
|
+
else
|
166
|
+
# Ignore pending signal
|
167
|
+
puts "WM: Ignoring WM_API_ATTACH response: #{lParam}" if
|
168
|
+
Skype.DEBUG
|
169
|
+
|
170
|
+
end
|
171
|
+
when Win32::WM_COPYDATA
|
172
|
+
pointer = FFI::Pointer.new(long_to_pointer(lParam))
|
173
|
+
data = Win32::COPYDATASTRUCT.new pointer
|
174
|
+
|
175
|
+
input = data[:lpData].read_string(data[:cbData] - 1)
|
176
|
+
if input[0] == '#'
|
177
|
+
(counter, input) = input.split(/\s+/, 2)
|
178
|
+
counter = counter.gsub(/^#/, '').to_i
|
179
|
+
else
|
180
|
+
counter = nil
|
181
|
+
end
|
182
|
+
|
183
|
+
if counter.nil?
|
184
|
+
receive(input)
|
185
|
+
else
|
186
|
+
@replies[counter] = input
|
187
|
+
puts "<- #{input}" if Skype.DEBUG
|
188
|
+
end
|
189
|
+
|
190
|
+
# Let Skype know we got it successfully
|
191
|
+
return 1
|
192
|
+
else
|
193
|
+
puts "Unhandled WM: #{sprintf("0x%04x", message_id)}" if Skype.DEBUG
|
194
|
+
return Win32::DefWindowProc(
|
195
|
+
window_handle, message_id, wParam, lParam)
|
196
|
+
end
|
197
|
+
end
|
198
|
+
end
|
199
|
+
end
|
200
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
|
2
|
+
class Skype
|
3
|
+
module DataMaps
|
4
|
+
# Data mapping between user visibilities as returned by Skype and the
|
5
|
+
# symbols that this API cares about.
|
6
|
+
USER_VISIBILITY = {
|
7
|
+
:unknown => 'UNKNOWN',
|
8
|
+
:online => 'ONLINE',
|
9
|
+
:offline => 'OFFLINE',
|
10
|
+
:skype_me => 'SKYPEME',
|
11
|
+
:away => 'AWAY',
|
12
|
+
:not_available => 'NA',
|
13
|
+
:do_not_disturb => 'DND',
|
14
|
+
:invisible => 'INVISIBLE',
|
15
|
+
:logged_out => 'LOGGEDOUT',
|
16
|
+
}
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
|
2
|
+
class Skype
|
3
|
+
module Errors
|
4
|
+
# This is a map of error codes to error messages used by ExceptionFactory
|
5
|
+
# to generate it's messages.
|
6
|
+
#
|
7
|
+
# @see http://developer.skype.com/public-api-reference#ERRORS Skype Public
|
8
|
+
# API error codes
|
9
|
+
ERROR_MESSAGES = {
|
10
|
+
68 => 'Access denied',
|
11
|
+
}
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
|
2
|
+
require 'skype/errors/error_messages'
|
3
|
+
require 'skype/errors/general_error'
|
4
|
+
|
5
|
+
class Skype
|
6
|
+
# You shouldn't try including this module, it is used purely for organisation.
|
7
|
+
# @private
|
8
|
+
module Errors
|
9
|
+
# This class allows the library to generate exceptions based on error codes
|
10
|
+
# from Skype easily.
|
11
|
+
class ExceptionFactory
|
12
|
+
# This function will raise an exception. It never returns.
|
13
|
+
#
|
14
|
+
# @param [String] error_message The ERROR #error_id response from Skype.
|
15
|
+
def self.generate_exception(error_message)
|
16
|
+
data = error_message.match(/^ERROR\s+(\d+)(?:\s+(.+))?$/)
|
17
|
+
error_code = data[1].to_i
|
18
|
+
exception_message = ERROR_MESSAGES[error_code] || data[2] ||
|
19
|
+
"Unknown error: #{error_code}"
|
20
|
+
|
21
|
+
raise ::Skype::Errors::GeneralError.new(error_code), exception_message,
|
22
|
+
caller
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
|
2
|
+
class Skype
|
3
|
+
module Errors
|
4
|
+
# A generalised Exception. This adds an error code field which holds the
|
5
|
+
# numeric error code as returned by Skype. The Exception's message is a
|
6
|
+
# plain text version of the error code, usually suitable for display to
|
7
|
+
# users.
|
8
|
+
class GeneralError < Exception
|
9
|
+
# This is the error code reported by Skype.
|
10
|
+
attr_reader :code
|
11
|
+
|
12
|
+
# Create a new GeneralError with the specified code. The error message
|
13
|
+
# should be provided to raise from ERROR_MESSAGES
|
14
|
+
def initialize(error_code)
|
15
|
+
@code = error_code
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
data/lib/skype.rb
ADDED
@@ -0,0 +1,213 @@
|
|
1
|
+
|
2
|
+
require 'skype/command_manager'
|
3
|
+
require 'skype/errors/exception_factory'
|
4
|
+
require 'skype/data_maps/user_visibility'
|
5
|
+
|
6
|
+
# This class is the main interface between Ruby and Skype.
|
7
|
+
class Skype
|
8
|
+
private
|
9
|
+
|
10
|
+
def platform
|
11
|
+
@platform ||=
|
12
|
+
case RbConfig::CONFIG['host_os']
|
13
|
+
when /mingw|cygwin|mswin/
|
14
|
+
:windows
|
15
|
+
when /linux/
|
16
|
+
:linux
|
17
|
+
else
|
18
|
+
:unknown
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
public
|
23
|
+
|
24
|
+
# Initialises the Skype library and sets up a communication protocol, but
|
25
|
+
# doesn't connect yet.
|
26
|
+
#
|
27
|
+
# @param [String] application_name Name to use when identifying to Skype. Not
|
28
|
+
# all platforms use this value as Skype will automatically assign a name.
|
29
|
+
def initialize(application_name, communication_protocol = nil)
|
30
|
+
if communication_protocol.nil?
|
31
|
+
case platform
|
32
|
+
when :windows
|
33
|
+
require 'skype/communication/windows'
|
34
|
+
@skype = Skype::Communication::Windows.new(application_name)
|
35
|
+
when :linux
|
36
|
+
require 'skype/communication/dbus'
|
37
|
+
@skype = Skype::Communication::DBus.new(application_name)
|
38
|
+
else
|
39
|
+
puts "Unfortunately, we don't support your platform currently."
|
40
|
+
puts "Please file an issue if you think this is incorrect."
|
41
|
+
exit 1
|
42
|
+
end
|
43
|
+
else
|
44
|
+
@skype = communication_protocol
|
45
|
+
end
|
46
|
+
|
47
|
+
@command_manager = CommandManager.new(self)
|
48
|
+
@skype.add_observer(self, :received_command)
|
49
|
+
end
|
50
|
+
|
51
|
+
# Controls whether the library should output extra debugging information or
|
52
|
+
# not. Currently controls whether we should output all network throughput.
|
53
|
+
#
|
54
|
+
# @return [Boolean]
|
55
|
+
def self.DEBUG
|
56
|
+
@debug_mode
|
57
|
+
end
|
58
|
+
|
59
|
+
# (see DEBUG)
|
60
|
+
def self.DEBUG=(value)
|
61
|
+
@debug_mode = value
|
62
|
+
end
|
63
|
+
|
64
|
+
# Returns the currently in use version of the ruby-skype library.
|
65
|
+
#
|
66
|
+
# @return [String]
|
67
|
+
def self.VERSION
|
68
|
+
@version ||=
|
69
|
+
IO.read(File.join(File.dirname(__FILE__), '..', 'VERSION')).chomp
|
70
|
+
end
|
71
|
+
|
72
|
+
# Connect to Skype and negotiate a communication channel. Blocks till the
|
73
|
+
# connection is fully established.
|
74
|
+
#
|
75
|
+
# @return [Symbol] Initial value for user_status.
|
76
|
+
def connect
|
77
|
+
@skype.connect
|
78
|
+
|
79
|
+
until @user_status
|
80
|
+
tick
|
81
|
+
sleep(0.1)
|
82
|
+
end
|
83
|
+
@user_status
|
84
|
+
end
|
85
|
+
|
86
|
+
# Are we connected to Skype?
|
87
|
+
#
|
88
|
+
# @return [Boolean]
|
89
|
+
def connected?
|
90
|
+
@skype.connected?
|
91
|
+
end
|
92
|
+
|
93
|
+
# Execute a single run of the Skype event loop
|
94
|
+
#
|
95
|
+
# @return [void]
|
96
|
+
def tick
|
97
|
+
@skype.tick
|
98
|
+
nil
|
99
|
+
end
|
100
|
+
|
101
|
+
# Executes the Skype event loops. Doesn't return unless #quit is called.
|
102
|
+
#
|
103
|
+
# @return [void]
|
104
|
+
def run
|
105
|
+
@finished = false
|
106
|
+
until @finished
|
107
|
+
tick
|
108
|
+
sleep(0.1)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
# Stops the Skype event loop from running.
|
113
|
+
#
|
114
|
+
# @return [void]
|
115
|
+
def quit
|
116
|
+
@finished = true
|
117
|
+
end
|
118
|
+
|
119
|
+
# Sends a raw command to Skype
|
120
|
+
#
|
121
|
+
# @param [String] command The command to send
|
122
|
+
# @return [String] The value returned by Skype
|
123
|
+
# @api private
|
124
|
+
def send_raw_command(command)
|
125
|
+
send_message(command)
|
126
|
+
end
|
127
|
+
|
128
|
+
#######################
|
129
|
+
### ###
|
130
|
+
### BEGIN SKYPE API ###
|
131
|
+
### ###
|
132
|
+
#######################
|
133
|
+
|
134
|
+
# Network connection status.
|
135
|
+
#
|
136
|
+
# Valid values:
|
137
|
+
#
|
138
|
+
# * `:offline`
|
139
|
+
# * `:connecting`
|
140
|
+
# * `:pausing`
|
141
|
+
# * `:online`
|
142
|
+
#
|
143
|
+
# @return [Symbol]
|
144
|
+
# @api skype
|
145
|
+
attr_reader :connection_status
|
146
|
+
|
147
|
+
# @!attribute [rw] user_status
|
148
|
+
# User visibility for the current user.
|
149
|
+
#
|
150
|
+
# Valid values:
|
151
|
+
#
|
152
|
+
# * `:unknown`
|
153
|
+
# * `:online`
|
154
|
+
# * `:offline`
|
155
|
+
# * `:skype_me`
|
156
|
+
# * `:away`
|
157
|
+
# * `:not_available`
|
158
|
+
# * `:do_not_disturb`
|
159
|
+
# * `:invisible`
|
160
|
+
#
|
161
|
+
# @return [Symbol]
|
162
|
+
# @api skype
|
163
|
+
def user_status
|
164
|
+
@user_status
|
165
|
+
end
|
166
|
+
|
167
|
+
# (see #user_status)
|
168
|
+
def user_status=(value)
|
169
|
+
send_message("SET USERSTATUS " + DataMaps::USER_VISIBILITY[value])
|
170
|
+
nil
|
171
|
+
end
|
172
|
+
|
173
|
+
# The protocol version in use for the connection with Skype. This value is
|
174
|
+
# only reliable once connected.
|
175
|
+
#
|
176
|
+
# @return [Integer] The version number of the protocol in use.
|
177
|
+
def protocol_version
|
178
|
+
@skype.protocol_version
|
179
|
+
end
|
180
|
+
|
181
|
+
protected
|
182
|
+
|
183
|
+
# Callback for receiving updates from Skype.
|
184
|
+
#
|
185
|
+
# @param [String] command The command string to process.
|
186
|
+
# @return [void]
|
187
|
+
def received_command(command)
|
188
|
+
@command_manager.process_command(command)
|
189
|
+
puts "<= #{command}" if ::Skype.DEBUG
|
190
|
+
end
|
191
|
+
|
192
|
+
def update_user_status(status)
|
193
|
+
@user_status = status
|
194
|
+
end
|
195
|
+
|
196
|
+
def update_connection_status(status)
|
197
|
+
@connection_status = status
|
198
|
+
end
|
199
|
+
|
200
|
+
private
|
201
|
+
|
202
|
+
# Handles sending messages and handling possible errors returned by Skype
|
203
|
+
#
|
204
|
+
# @param [String] message The message to send to Skype
|
205
|
+
# @return [String] The reply from Skype or throws an exception on an error
|
206
|
+
def send_message(message)
|
207
|
+
ret = @skype.send(message)
|
208
|
+
if ret[0,6] == "ERROR "
|
209
|
+
Errors::ExceptionFactory.generate_exception(ret)
|
210
|
+
end
|
211
|
+
ret
|
212
|
+
end
|
213
|
+
end
|
metadata
ADDED
@@ -0,0 +1,128 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: ruby-skype
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0.pre.1
|
5
|
+
prerelease: 6
|
6
|
+
platform: mswin32
|
7
|
+
authors:
|
8
|
+
- Matthew Scharley
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-08-15 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: yard
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: rake
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
type: :development
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: cane
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
type: :development
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: ffi
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ! '>='
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0'
|
70
|
+
type: :runtime
|
71
|
+
prerelease: false
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ! '>='
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '0'
|
78
|
+
description: ! " ruby-skype is a binding to the Skype Public API. The Public API
|
79
|
+
is a method\n to talk to the official Skype client running on the local computer
|
80
|
+
and\n allows for automation of the client.\n"
|
81
|
+
email: matt.scharley@gmail.com
|
82
|
+
executables: []
|
83
|
+
extensions: []
|
84
|
+
extra_rdoc_files: []
|
85
|
+
files:
|
86
|
+
- lib/skype/errors/exception_factory.rb
|
87
|
+
- lib/skype/errors/error_messages.rb
|
88
|
+
- lib/skype/errors/general_error.rb
|
89
|
+
- lib/skype/data_maps.rb
|
90
|
+
- lib/skype/data_maps/user_visibility.rb
|
91
|
+
- lib/skype/command_manager.rb
|
92
|
+
- lib/skype/communication/protocol.rb
|
93
|
+
- lib/skype/communication/windows/win32.rb
|
94
|
+
- lib/skype/communication/dbus.rb
|
95
|
+
- lib/skype/communication/windows.rb
|
96
|
+
- lib/skype.rb
|
97
|
+
- VERSION
|
98
|
+
- README.md
|
99
|
+
- LICENSE
|
100
|
+
- examples/call
|
101
|
+
- examples/listen
|
102
|
+
homepage: https://github.com/mscharley/ruby-skype
|
103
|
+
licenses:
|
104
|
+
- MIT
|
105
|
+
post_install_message:
|
106
|
+
rdoc_options: []
|
107
|
+
require_paths:
|
108
|
+
- lib
|
109
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
110
|
+
none: false
|
111
|
+
requirements:
|
112
|
+
- - ~>
|
113
|
+
- !ruby/object:Gem::Version
|
114
|
+
version: 1.9.0
|
115
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
116
|
+
none: false
|
117
|
+
requirements:
|
118
|
+
- - ! '>'
|
119
|
+
- !ruby/object:Gem::Version
|
120
|
+
version: 1.3.1
|
121
|
+
requirements: []
|
122
|
+
rubyforge_project:
|
123
|
+
rubygems_version: 1.8.24
|
124
|
+
signing_key:
|
125
|
+
specification_version: 3
|
126
|
+
summary: Ruby binding to the Skype Public API.
|
127
|
+
test_files: []
|
128
|
+
has_rdoc:
|