State four fires a notification on one API and means something else on another, and the code shows no difference at all. Every state value in NetworkManager belongs to a named enumeration, and the enumerations do not agree on what a given number means. Copying an integer from a working snippet into a different callback is therefore not a transfer of behaviour; it is a coincidence waiting to break. Find the type first, and the number becomes readable.

A number without a type is a rumour

Two enumerations matter here. One describes an active connection in general: unknown, activating, activated, deactivating, deactivated. The other describes a VPN connection specifically, and it has more stops along the way, including preparation, connection setup, IP configuration and an authentication request before anything is activated. Their numbering starts at zero in both cases and then diverges, so the value meaning deactivated in the general enumeration lands somewhere in the middle of the VPN sequence. Neither header is wrong; they answer different questions.

Use the symbolic constant from the installed header or binding rather than a literal. The name survives a renumbering; the integer does not.

Watch one connection, not the machine

Start from the profile UUID you care about and the active connection object created by that activation. A device state, or another tunnel's state, is not a substitute, even when both change at the same moment. Keep the object identity with the observation, so a later report can say which profile produced it. The machine has many states; the notice is about one of them.

Then decide what the notice should mean. A connection that never reached activation is a setup failure, and a session that was active and then disappeared is a loss. Those deserve different words.

Say which transition you are reporting

Store the previous state and compare it with the new one, instead of testing the new value in isolation. A transition into a failed state and a transition out of an activated state are different events even when both end with nothing connected. Record the reason string alongside, because it is often the only part a user can act on.

An intentional disconnect is not a fault. If the notice cannot tell the difference, it will train people to ignore it.

Keep the observer alive

Repeated polling must not raise another alert for a state that has not changed. That single rule removes most of the noise these notifiers generate.

Also handle the case where the manager restarts or the observed object is replaced. A watcher bound to a stale identity goes quiet at exactly the moment something interesting happens, and quiet looks like health. Rediscover the current object after a change in the manager's presence, and record when the rediscovery happened. Otherwise the log shows a tunnel that simply stopped reporting.

Deliver the notice where the user is

A privileged system callback does not inherit a desktop session's notification service. A message sent from a root-level handler can vanish without an error, which produces a notifier that works when tested by hand and never in practice. Deliver into the session that owns the desktop, or let the desktop's own client raise the notice.

Prefer the client's existing notification if it already covers the case, since two notifiers for one event is a support problem rather than redundancy. Keep the event time, the profile and the reason in the support record, and leave passwords and full profiles out of it. Remember what the notice is: a report of an observed state, not an enforcement mechanism, because blocking traffic while the tunnel is absent is a separate policy with its own implementation.

ဆက်စပ်ဆောင်းပါးများ