Class: MacWifi::MacOsModel
- Defined in:
- lib/mac-wifi/mac_os_model.rb
Constant Summary collapse
- AIRPORT_CMD =
'/System/Library/PrivateFrameworks/Apple80211.framework/Versions/Current/Resources/airport'
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
-
#current_network ⇒ Object
Returns the network currently connected to, or nil if none.
-
#disconnect ⇒ Object
Disconnects from the currently connected network.
-
#initialize(verbose = false) ⇒ MacOsModel
constructor
A new instance of MacOsModel.
-
#ip_address ⇒ Object
Returns the IP address assigned to the wifi port, or nil if none.
-
#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
-
#vpn_running? ⇒ Boolean
This is determined by whether or not a line like the following appears in the output of ‘netstat -nr`: 0/1 10.137.0.41 UGSc 15 0 utun1.
-
#wifi_hardware_port ⇒ Object
Identifies the (first) wireless network hardware port in the system, e.g.
-
#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, #preferred_network_password, #remove_preferred_networks, #till, #try_os_command_until
Constructor Details
#initialize(verbose = false) ⇒ MacOsModel
Returns a new instance of MacOsModel.
11 12 13 |
# File 'lib/mac-wifi/mac_os_model.rb', line 11 def initialize(verbose = false) 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.
51 52 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 |
# File 'lib/mac-wifi/mac_os_model.rb', line 51 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 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.
114 115 116 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 |
# File 'lib/mac-wifi/mac_os_model.rb', line 114 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
191 192 193 |
# File 'lib/mac-wifi/mac_os_model.rb', line 191 def connected_network_name wifi_info['SSID'] end |
#current_network ⇒ Object
Returns the network currently connected to, or nil if none.
248 249 250 251 252 |
# File 'lib/mac-wifi/mac_os_model.rb', line 248 def current_network 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 |
#disconnect ⇒ Object
Disconnects from the currently connected network. Does not turn off wifi.
256 257 258 259 |
# File 'lib/mac-wifi/mac_os_model.rb', line 256 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.
227 228 229 230 231 232 233 234 235 236 237 |
# File 'lib/mac-wifi/mac_os_model.rb', line 227 def ip_address begin run_os_command("ipconfig getifaddr #{wifi_hardware_port}").chomp rescue OsCommandError => error if error.exitstatus == 1 nil else raise end end end |
#os_level_connect(network_name, password = nil) ⇒ Object
This method is called by BaseModel#connect to do the OS-specific connection logic.
197 198 199 200 201 202 203 |
# File 'lib/mac-wifi/mac_os_model.rb', line 197 def os_level_connect(network_name, password = nil) command = "networksetup -setairportnetwork #{wifi_hardware_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
212 213 214 215 216 217 218 219 220 221 222 223 |
# File 'lib/mac-wifi/mac_os_model.rb', line 212 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
88 89 90 91 92 93 94 95 96 97 |
# File 'lib/mac-wifi/mac_os_model.rb', line 88 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.
154 155 156 157 158 159 160 161 162 163 164 165 |
# File 'lib/mac-wifi/mac_os_model.rb', line 154 def preferred_networks lines = run_os_command("networksetup -listpreferredwirelessnetworks #{wifi_hardware_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
240 241 242 243 244 |
# File 'lib/mac-wifi/mac_os_model.rb', line 240 def remove_preferred_network(network_name) network_name = network_name.to_s run_os_command("sudo networksetup -removepreferredwirelessnetwork " + "#{wifi_hardware_port} #{Shellwords.shellescape(network_name)}") end |
#vpn_running? ⇒ Boolean
This is determined by whether or not a line like the following appears in the output of ‘netstat -nr`: 0/1 10.137.0.41 UGSc 15 0 utun1
18 19 20 |
# File 'lib/mac-wifi/mac_os_model.rb', line 18 def vpn_running? run_os_command('netstat -nr').split("\n").grep(/^0\/1.*utun1/).any? end |
#wifi_hardware_port ⇒ Object
Identifies the (first) wireless network hardware port in the system, e.g. en0 or en1
24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
# File 'lib/mac-wifi/mac_os_model.rb', line 24 def wifi_hardware_port @wifi_hardware_port ||= begin 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 end |
#wifi_info ⇒ Object
Returns some useful wifi-related information.
263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 |
# File 'lib/mac-wifi/mac_os_model.rb', line 263 def wifi_info info = { wifi_on: wifi_on?, internet_on: connected_to_internet?, vpn_on: vpn_running?, port: wifi_hardware_port, network: current_network, ip_address: ip_address, 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 info end |
#wifi_off ⇒ Object
Turns wifi off.
184 185 186 187 188 |
# File 'lib/mac-wifi/mac_os_model.rb', line 184 def wifi_off return unless wifi_on? run_os_command("networksetup -setairportpower #{wifi_hardware_port} off") wifi_on? ? raise("Wifi could not be disabled.") : nil end |
#wifi_on ⇒ Object
Turns wifi on.
176 177 178 179 180 |
# File 'lib/mac-wifi/mac_os_model.rb', line 176 def wifi_on return if wifi_on? run_os_command("networksetup -setairportpower #{wifi_hardware_port} on") wifi_on? ? nil : raise("Wifi could not be enabled.") end |
#wifi_on? ⇒ Boolean
Returns true if wifi is on, else false.
169 170 171 172 |
# File 'lib/mac-wifi/mac_os_model.rb', line 169 def wifi_on? lines = run_os_command("#{AIRPORT_CMD} -I").split("\n") lines.grep("AirPort: Off").none? end |