Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 81 additions & 3 deletions networkmanager_dmenu
Original file line number Diff line number Diff line change
Expand Up @@ -810,6 +810,7 @@ def process_ap(nm_ap, is_active, adapter):
nm_ap,
)
LOOP.run()
wait_for_activation(adapter)
else:
sec = ap_security(nm_ap)
if "802.1X" in sec:
Expand All @@ -831,9 +832,11 @@ def activate_cb(dev, res, data):
Finishing here only means NM took the request, the activation itself
settles long after nmdm has exited, so do not claim it succeeded.
"""
global ACTIVE_CONN
try:
conn = dev.activate_connection_finish(res)
if conn is not None:
ACTIVE_CONN = conn
notify(f"Connecting to {conn.get_id()}")
else:
notify(
Expand All @@ -848,6 +851,73 @@ def activate_cb(dev, res, data):
LOOP.quit()


def wait_for_device_activated(device, timeout=30):
"""Block until the device has finished activating

NetworkManager ties the activation of a connection's secondaries (the VPN
profiles listed in `connection.secondaries`) to the D-Bus client that
asked for it, so returning as soon as the activation request is accepted
leaves the VPN to be torn down again the moment nmdm exits. Stay on the
bus until the device reports ACTIVATED, the state it only reaches once
its secondaries are up.

Args: device - NM.Device
timeout - seconds to wait before giving up

"""
if device.get_state() == NM.DeviceState.ACTIVATED:
return

timed_out = False

def on_state_changed(dev, new_state, old_state, reason):
if new_state == NM.DeviceState.ACTIVATED:
LOOP.quit()

def on_timeout():
nonlocal timed_out
timed_out = True
LOOP.quit()
return GLib.SOURCE_REMOVE

handler_id = device.connect("state-changed", on_state_changed)
timeout_id = GLib.timeout_add_seconds(timeout, on_timeout)
LOOP.run()
if not timed_out:
GLib.source_remove(timeout_id)
# NM.Device shadows GObject's `disconnect`, and its version disconnects
# the device from the network instead of the signal handler
device.handler_disconnect(handler_id)
if timed_out:
notify(
f"{device.get_iface()} did not finish activating",
f"Gave up waiting after {timeout} seconds",
urgency="critical",
)


def wait_for_activation(device=None):
"""Block until the activation just requested has finished

`process_ap` and `set_new_connection` activate against an adapter and
pass it in. `process_vpngsm` has no adapter to pass, so take the device
from the connection NetworkManager just activated instead.

Args: device - NM.Device to wait on, or None to use the device of the
connection that was activated

"""
if ACTIVE_CONN is None:
# The request itself failed, there is nothing left to wait for
return
if device is None:
devices = ACTIVE_CONN.get_devices()
if not devices:
return
device = devices[0]
wait_for_device_activated(device)


def deactivate_cb(dev, res, data):
"""Notification if deactivate connection completed successfully"""
try:
Expand All @@ -872,8 +942,12 @@ def process_vpngsm(con, activate):
CLIENT.activate_connection_async(
con, None, None, None, activate_cb, con
)
else:
CLIENT.deactivate_connection_async(con, None, deactivate_cb, con)
LOOP.run()
# Saved connections are activated without an adapter, so the device
# to wait on is whichever one NetworkManager picked
wait_for_activation()
return
CLIENT.deactivate_connection_async(con, None, deactivate_cb, con)
LOOP.run()


Expand Down Expand Up @@ -1432,6 +1506,7 @@ def set_new_connection(nm_ap, nm_pw, adapter):
profile, adapter, nm_ap.get_path(), None, verify_conn, profile
)
LOOP.run()
wait_for_activation(adapter)


def create_wifi_profile(nm_ap, password, adapter):
Expand Down Expand Up @@ -1499,6 +1574,7 @@ def verify_conn(client, result, data):
is an error.

"""
global ACTIVE_CONN
conn = None
try:
act_conn = client.add_and_activate_connection_finish(result)
Expand All @@ -1524,6 +1600,7 @@ def verify_conn(client, result, data):
conn.delete_async(None, cleanup_cb, None)
return
else:
ACTIVE_CONN = act_conn
notify(f"Connecting to {conn.get_id()}")
LOOP.quit()

Expand Down Expand Up @@ -1709,7 +1786,8 @@ def run(): # pylint: disable=too-many-locals

def main():
"""Main. Enables script to be re-run after a WiFi rescan"""
global CLIENT, CONNS, LOOP
global CLIENT, CONNS, LOOP, ACTIVE_CONN
ACTIVE_CONN = None
CLIENT = NM.Client.new(None)
LOOP = GLib.MainLoop()
CONNS = CLIENT.get_connections()
Expand Down