-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathNetivity.py
137 lines (117 loc) · 4.49 KB
/
Netivity.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
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
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
import bandwidth
import rumps
import subprocess
import re
import prefs
class Netivity(prefs.Prefs):
def __init__(self):
self.app = rumps.App("Netitivy", "👾")
self.timer = rumps.Timer(self.tick, 1)
self.init_interfaces()
self.init_menu()
self.load_prefs()
self.apply_prefs(self.app)
self.params = bandwidth.Params(0, 0, 0, 0, self.interface.encode())
self.timer.start()
self.app.run()
def init_interfaces(self):
"""Initializes the interface list."""
self.interface = None
self.load_interfaces()
def init_menu(self):
"""Constructs and sets the initial menu for the app."""
items = ["Paused while open", rumps.separator]
items.append(
(
"Interfaces",
[rumps.separator], # placehold with a separator
)
)
items.append(
(
"Settings",
[
rumps.MenuItem(
"Shorten `utun` to `t` in the menu bar",
callback=self.shorten_utun,
),
rumps.MenuItem(
"Shorten `en` to `e` in the menu bar",
callback=self.shorten_en,
),
],
),
)
items.append(rumps.separator)
self.app.menu = items
self.set_interface_menu_items()
def set_interface_menu_items(self):
"""Rebuilds the items in the Interfaces menu."""
interfaces_menu = self.app.menu["Interfaces"]
interfaces_menu.clear()
for interface in self.interfaces:
interfaces_menu.add(
rumps.MenuItem(
title=interface,
callback=self.switch_interface,
)
)
for item in interfaces_menu:
if item == self.interface:
interfaces_menu[item].state = True
def load_interfaces(self):
"""Loads all interfaces with active routes and ensures an active interface is selected."""
# interfaces = sorted([name for _, name in socket.if_nameindex()])
self.active_interfaces = dict()
# TOOD: python/C mechanism to get the interfaces with active routes
# NOTE: this parsing is silly I know
inet_routes = subprocess.check_output(["netstat", "-rnf", "inet"])
lines = inet_routes.decode().splitlines()
for line in lines:
parts = re.split(r"\s+", line)
if parts[0] == "Destination" or len(parts) < 4:
continue
iface = parts[3]
# default route is always the first in the table
if self.interface is None:
self.interface = iface
self.active_interfaces[iface] = True
self.interfaces = sorted([iface for iface, _ in self.active_interfaces.items()])
if self.interface not in self.interfaces:
self.set_interface(self.interfaces[0])
def shorten_utun(self, sender):
"""Click handler for the shorten utun menu item."""
sender.state = not sender.state
self.write_pref("shorten_utun", sender.state)
def shorten_en(self, sender):
"""Click handler for the shorten en menu item."""
sender.state = not sender.state
self.write_pref("shorten_en", sender.state)
def switch_interface(self, sender):
"""Click handler for the interface menu items."""
sender.state = True
interfaces_menu = self.app.menu["Interfaces"]
for item in interfaces_menu:
if item != sender.title:
interfaces_menu[item].state = False
self.set_interface(sender.title)
def set_interface(self, interface):
"""Sets the current interface and updates the Params struct."""
self.interface = interface
self.params.last_out = 0
self.params.last_in = 0
self.params.net_in = 0
self.params.net_out = 0
self.params.ifa_name = self.interface.encode()
def tick(self, *_):
"""Updates the title of the app with the current traffic."""
self.load_interfaces()
self.set_interface_menu_items()
title = bandwidth.retrieve(self.params)
if self.prefs["shorten_en"]:
title = title.replace("en", "e")
if self.prefs["shorten_utun"]:
title = title.replace("utun", "t")
self.app.title = title
if __name__ == "__main__":
Netivity()