Class: WifiWand::MacOsModel
- Defined in:
- lib/wifi-wand/models/mac_os_model.rb
Constant Summary collapse
- AIRPORT_CMD =
'/System/Library/PrivateFrameworks/Apple80211.framework/Versions/Current/Resources/airport'
Instance Attribute Summary
Attributes inherited from BaseModel
Instance Method Summary collapse
-
#available_network_info ⇒ Object
Returns data pertaining to available wireless networks.
-
#available_network_names ⇒ Object
Kludge alert: the tabular data does not differentiate between strings with and without leading whitespace Therefore, we get the data once in tabular format, and another time in XML format.
-
#connected_network_name ⇒ Object
Returns the network currently connected to, or nil if none.
-
#detect_wifi_port ⇒ Object
Identifies the (first) wireless network hardware port in the system, e.g.
-
#disconnect ⇒ Object
Disconnects from the currently connected network.
-
#initialize(options = OpenStruct.new) ⇒ MacOsModel
constructor
Takes an OpenStruct containing options such as verbose mode and port name.
-
#ip_address ⇒ Object
Returns the IP address assigned to the wifi port, or nil if none.
-
#is_wifi_port?(port) ⇒ Boolean
Returns whether or not the specified interface is a WiFi interfae.
- #open_resource(resource_url) ⇒ Object
-
#os_level_connect(network_name, password = nil) ⇒ Object
This method is called by BaseModel#connect to do the OS-specific connection logic.
-
#os_level_preferred_network_password(preferred_network_name) ⇒ Object
@return: If the network is in the preferred networks list If a password is associated w/this network, return the password If not, return nil else raise an error.
- #parse_network_names(info) ⇒ Object
-
#preferred_networks ⇒ Object
Returns data pertaining to “preferred” networks, many/most of which will probably not be available.
- #remove_preferred_network(network_name) ⇒ Object
-
#wifi_info ⇒ Object
Returns some useful wifi-related information.
-
#wifi_off ⇒ Object
Turns wifi off.
-
#wifi_on ⇒ Object
Turns wifi on.
-
#wifi_on? ⇒ Boolean
Returns true if wifi is on, else false.
Methods inherited from BaseModel
#connect, #connected_to?, #connected_to_internet?, #cycle_network, #nameservers_using_resolv_conf, #nameservers_using_scutil, #preferred_network_password, #public_ip_address_info, #remove_preferred_networks, #run_os_command, #till, #try_os_command_until
Constructor Details
#initialize(options = OpenStruct.new) ⇒ MacOsModel
Takes an OpenStruct containing options such as verbose mode and port name.
13 14 15 |
# File 'lib/wifi-wand/models/mac_os_model.rb', line 13 def initialize( = OpenStruct.new) super end |
Instance Method Details
#available_network_info ⇒ Object
Returns data pertaining to available wireless networks. For some reason, this often returns no results, so I’ve put the operation in a loop. I was unable to detect a sort strategy in the airport utility’s output, so I sort the lines alphabetically, to show duplicates and for easier lookup.
Sample Output:
> [“SSID BSSID RSSI CHANNEL HT CC SECURITY (auth/unicast/group)”,
"ByCO-U00tRzUzMEg 64:6c:b2:db:f3:0c -56 6 Y -- NONE",
"Chancery 0a:18:d6:0b:b9:c3 -82 11 Y -- NONE",
"Chancery 2a:a4:3c:03:33:99 -59 60,+1 Y -- NONE",
"DIRECT-sq-BRAVIA 02:71:cc:87:4a:8c -76 6 Y -- WPA2(PSK/AES/AES) ", #
53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 |
# File 'lib/wifi-wand/models/mac_os_model.rb', line 53 def available_network_info return nil unless wifi_on? # no need to try command = "#{AIRPORT_CMD} -s" max_attempts = 50 reformat_line = ->(line) do ssid = line[0..31].strip "%-32.32s%s" % [ssid, line[32..-1]] end process_tabular_data = ->(output) do lines = output.split("\n") header_line = lines[0] data_lines = lines[1..-1] data_lines.map! do |line| # Reformat the line so that the name is left instead of right justified reformat_line.(line) end # TODO: Need to sort case insensitively?: data_lines.sort! [reformat_line.(header_line)] + data_lines end output = try_os_command_until(command, ->(output) do ! ([nil, ''].include?(output)) end) if output process_tabular_data.(output) else raise "Unable to get available network information after #{max_attempts} attempts." end end |
#available_network_names ⇒ Object
Kludge alert: the tabular data does not differentiate between strings with and without leading whitespace Therefore, we get the data once in tabular format, and another time in XML format. The XML element will include any leading whitespace. However, it includes all <string> elements, many of which are not network names. As an improved approximation of the correct result, for each network name found in tabular mode, we look to see if there is a corresponding string element with leading whitespace, and, if so, replace it.
This will not behave correctly if a given name has occurrences with different amounts of whitespace, e.g. ‘ x’ and ‘ x’.
The reason we don’t use an XML parser to get the exactly correct result is that we don’t want users to need to install any external dependencies in order to run this script.
117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 |
# File 'lib/wifi-wand/models/mac_os_model.rb', line 117 def available_network_names # Parses the XML text (using grep, not XML parsing) to find # <string> elements, and extracts the network name candidates # containing leading spaces from it. get_leading_space_names = ->(text) do text.split("\n") \ .grep(%r{<string>}) \ .sort \ .uniq \ .map { |line| line.gsub("<string>", '').gsub('</string>', '').gsub("\t", '') } \ .select { |s| s[0] == ' ' } end output_is_valid = ->(output) { ! ([nil, ''].include?(output)) } tabular_data = try_os_command_until("#{AIRPORT_CMD} -s", output_is_valid) xml_data = try_os_command_until("#{AIRPORT_CMD} -s -x", output_is_valid) if tabular_data.nil? || xml_data.nil? raise "Unable to get available network information; please try again." end tabular_data_lines = tabular_data[1..-1] # omit header line names_no_spaces = parse_network_names(tabular_data_lines.split("\n")).map(&:strip) names_maybe_spaces = get_leading_space_names.(xml_data) names = names_no_spaces.map do |name_no_spaces| match = names_maybe_spaces.detect do |name_maybe_spaces| %r{[ \t]?#{name_no_spaces}$}.match(name_maybe_spaces) end match ? match : name_no_spaces end names.sort { |s1, s2| s1.casecmp(s2) } # sort alphabetically, case insensitively end |
#connected_network_name ⇒ Object
Returns the network currently connected to, or nil if none.
254 255 256 257 258 |
# File 'lib/wifi-wand/models/mac_os_model.rb', line 254 def connected_network_name lines = run_os_command("#{AIRPORT_CMD} -I").split("\n") ssid_lines = lines.grep(/ SSID:/) ssid_lines.empty? ? nil : ssid_lines.first.split('SSID: ').last.strip end |
#detect_wifi_port ⇒ Object
Identifies the (first) wireless network hardware port in the system, e.g. en0 or en1 This may not detect wifi ports with nonstandard names, such as USB wifi devices.
20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
# File 'lib/wifi-wand/models/mac_os_model.rb', line 20 def detect_wifi_port lines = run_os_command("networksetup -listallhardwareports").split("\n") # Produces something like this: # Hardware Port: Wi-Fi # Device: en0 # Ethernet Address: ac:bc:32:b9:a9:9d # # Hardware Port: Bluetooth PAN # Device: en3 # Ethernet Address: ac:bc:32:b9:a9:9e wifi_port_line_num = (0...lines.size).detect do |index| /: Wi-Fi$/.match(lines[index]) end if wifi_port_line_num.nil? raise %Q{Wifi port (e.g. "en0") not found in output of: networksetup -listallhardwareports} else lines[wifi_port_line_num + 1].split(': ').last end end |
#disconnect ⇒ Object
Disconnects from the currently connected network. Does not turn off wifi.
262 263 264 265 |
# File 'lib/wifi-wand/models/mac_os_model.rb', line 262 def disconnect run_os_command("sudo #{AIRPORT_CMD} -z") nil end |
#ip_address ⇒ Object
Returns the IP address assigned to the wifi port, or nil if none.
233 234 235 236 237 238 239 240 241 242 243 |
# File 'lib/wifi-wand/models/mac_os_model.rb', line 233 def ip_address begin run_os_command("ipconfig getifaddr #{wifi_port}").chomp rescue OsCommandError => error if error.exitstatus == 1 nil else raise end end end |
#is_wifi_port?(port) ⇒ Boolean
Returns whether or not the specified interface is a WiFi interfae.
172 173 174 175 176 |
# File 'lib/wifi-wand/models/mac_os_model.rb', line 172 def is_wifi_port?(port) run_os_command("networksetup -listpreferredwirelessnetworks #{port} 2>/dev/null") exit_status = $?.exitstatus exit_status != 10 end |
#open_resource(resource_url) ⇒ Object
303 304 305 |
# File 'lib/wifi-wand/models/mac_os_model.rb', line 303 def open_resource(resource_url) run_os_command('open ' + resource_url) end |
#os_level_connect(network_name, password = nil) ⇒ Object
This method is called by BaseModel#connect to do the OS-specific connection logic.
203 204 205 206 207 208 209 |
# File 'lib/wifi-wand/models/mac_os_model.rb', line 203 def os_level_connect(network_name, password = nil) command = "networksetup -setairportnetwork #{wifi_port} " + "#{Shellwords.shellescape(network_name)}" if password command << ' ' << Shellwords.shellescape(password) end run_os_command(command) end |
#os_level_preferred_network_password(preferred_network_name) ⇒ Object
@return:
If the network is in the preferred networks list
If a password is associated w/this network, return the password
If not, return nil
else
raise an error
218 219 220 221 222 223 224 225 226 227 228 229 |
# File 'lib/wifi-wand/models/mac_os_model.rb', line 218 def os_level_preferred_network_password(preferred_network_name) command = %Q{security find-generic-password -D "AirPort network password" -a "#{preferred_network_name}" -w 2>&1} begin return run_os_command(command).chomp rescue OsCommandError => error if error.exitstatus == 44 # network has no password stored nil else raise end end end |
#parse_network_names(info) ⇒ Object
91 92 93 94 95 96 97 98 99 100 |
# File 'lib/wifi-wand/models/mac_os_model.rb', line 91 def parse_network_names(info) if info.nil? nil else info[1..-1] \ .map { |line| line[0..32].rstrip } \ .uniq \ .sort { |s1, s2| s1.casecmp(s2) } end end |
#preferred_networks ⇒ Object
Returns data pertaining to “preferred” networks, many/most of which will probably not be available.
157 158 159 160 161 162 163 164 165 166 167 168 |
# File 'lib/wifi-wand/models/mac_os_model.rb', line 157 def preferred_networks lines = run_os_command("networksetup -listpreferredwirelessnetworks #{wifi_port}").split("\n") # Produces something like this, unsorted, and with leading tabs: # Preferred networks on en0: # LibraryWiFi # @thePAD/Magma lines.delete_at(0) # remove title line lines.map! { |line| line.gsub("\t", '') } # remove leading tabs lines.sort! { |s1, s2| s1.casecmp(s2) } # sort alphabetically, case insensitively lines end |
#remove_preferred_network(network_name) ⇒ Object
246 247 248 249 250 |
# File 'lib/wifi-wand/models/mac_os_model.rb', line 246 def remove_preferred_network(network_name) network_name = network_name.to_s run_os_command("sudo networksetup -removepreferredwirelessnetwork " + "#{wifi_port} #{Shellwords.shellescape(network_name)}") end |
#wifi_info ⇒ Object
Returns some useful wifi-related information.
269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 |
# File 'lib/wifi-wand/models/mac_os_model.rb', line 269 def wifi_info connected = begin connected_to_internet? rescue false end info = { 'wifi_on' => wifi_on?, 'internet_on' => connected, 'port' => wifi_port, 'network' => connected_network_name, 'ip_address' => ip_address, 'nameservers' => nameservers_using_scutil, 'timestamp' => Time.now, } more_output = run_os_command(AIRPORT_CMD + " -I") more_info = colon_output_to_hash(more_output) info.merge!(more_info) info.delete('AirPort') # will be here if off, but info is already in wifi_on key if info['internet_on'] begin info['public_ip'] = public_ip_address_info rescue => e puts "Error obtaining public IP address info, proceeding with everything else:" puts e.to_s end end info end |
#wifi_off ⇒ Object
Turns wifi off.
195 196 197 198 199 |
# File 'lib/wifi-wand/models/mac_os_model.rb', line 195 def wifi_off return unless wifi_on? run_os_command("networksetup -setairportpower #{wifi_port} off") wifi_on? ? raise("Wifi could not be disabled.") : nil end |
#wifi_on ⇒ Object
Turns wifi on.
187 188 189 190 191 |
# File 'lib/wifi-wand/models/mac_os_model.rb', line 187 def wifi_on return if wifi_on? run_os_command("networksetup -setairportpower #{wifi_port} on") wifi_on? ? nil : raise("Wifi could not be enabled.") end |
#wifi_on? ⇒ Boolean
Returns true if wifi is on, else false.
180 181 182 183 |
# File 'lib/wifi-wand/models/mac_os_model.rb', line 180 def wifi_on? lines = run_os_command("#{AIRPORT_CMD} -I").split("\n") lines.grep("AirPort: Off").none? end |