Add Access Points section showing all APs of the connected SSID
Display all access points sharing the connected network's SSID in a dedicated section between Signal and Nearby Networks. The connected AP is marked with a green checkmark icon, other APs get a spacer to keep BSSIDs aligned. Section is hidden when only one AP exists. NM duplicate BSSIDs are deduplicated by keeping the strongest signal.
This commit is contained in:
parent
7fbced9d25
commit
fffe358a3c
3 changed files with 138 additions and 2 deletions
|
|
@ -114,11 +114,16 @@ export default class WifiSignalPlusExtension extends Extension {
|
|||
private headerGenerationLabel: St.Label | null = null;
|
||||
private headerBandLabel: St.Label | null = null;
|
||||
private headerIcon: St.Icon | null = null;
|
||||
private accessPointsSeparator: PopupMenu.PopupSeparatorMenuItem | null = null;
|
||||
private accessPointsSection: PopupMenu.PopupMenuSection | null = null;
|
||||
private accessPointsItems: PopupMenu.PopupBaseMenuItem[] = [];
|
||||
private accessPointsUpdatePending = false;
|
||||
private nearbySeparator: PopupMenu.PopupSeparatorMenuItem | null = null;
|
||||
private nearbySection: PopupMenu.PopupMenuSection | null = null;
|
||||
private nearbyItems: NearbyNetworkCard[] = [];
|
||||
private nearbyUpdatePending = false;
|
||||
private currentConnectedSsid: string | undefined;
|
||||
private currentConnectedBssid: string | undefined;
|
||||
private isMenuOpen = false;
|
||||
private enableEpoch = 0;
|
||||
|
||||
|
|
@ -149,6 +154,7 @@ export default class WifiSignalPlusExtension extends Extension {
|
|||
this.stopBackgroundScanTimer();
|
||||
this.stopRefreshTimer();
|
||||
this.wifiService?.unwatchDeviceSignals();
|
||||
this.clearAccessPointsItems();
|
||||
this.clearNearbyItems();
|
||||
this.indicator?.destroy();
|
||||
this.wifiService?.destroy();
|
||||
|
|
@ -164,12 +170,17 @@ export default class WifiSignalPlusExtension extends Extension {
|
|||
this.headerGenerationLabel = null;
|
||||
this.headerBandLabel = null;
|
||||
this.headerIcon = null;
|
||||
this.accessPointsSeparator = null;
|
||||
this.accessPointsSection = null;
|
||||
this.accessPointsItems = [];
|
||||
this.accessPointsUpdatePending = false;
|
||||
this.nearbySeparator = null;
|
||||
this.nearbySection = null;
|
||||
this.nearbyItems = [];
|
||||
this.refreshPending = false;
|
||||
this.nearbyUpdatePending = false;
|
||||
this.currentConnectedSsid = undefined;
|
||||
this.currentConnectedBssid = undefined;
|
||||
this.isMenuOpen = false;
|
||||
}
|
||||
|
||||
|
|
@ -231,6 +242,13 @@ export default class WifiSignalPlusExtension extends Extension {
|
|||
}
|
||||
});
|
||||
|
||||
this.accessPointsSeparator = new PopupMenu.PopupSeparatorMenuItem('Access Points');
|
||||
this.accessPointsSeparator.visible = false;
|
||||
menu.addMenuItem(this.accessPointsSeparator);
|
||||
this.accessPointsSection = new PopupMenu.PopupMenuSection();
|
||||
this.accessPointsSection.actor.visible = false;
|
||||
menu.addMenuItem(this.accessPointsSection);
|
||||
|
||||
this.nearbySeparator = new PopupMenu.PopupSeparatorMenuItem('Nearby Networks');
|
||||
menu.addMenuItem(this.nearbySeparator);
|
||||
|
||||
|
|
@ -447,10 +465,12 @@ export default class WifiSignalPlusExtension extends Extension {
|
|||
if (!this.wifiService) return;
|
||||
|
||||
this.currentConnectedSsid = isConnected(info) ? info.ssid : undefined;
|
||||
this.currentConnectedBssid = isConnected(info) ? info.bssid : undefined;
|
||||
this.updateIndicatorLabel(info);
|
||||
this.updateMenuContent(info);
|
||||
|
||||
if (this.isMenuOpen) {
|
||||
await this.updateAccessPoints();
|
||||
await this.updateNearbyNetworks();
|
||||
}
|
||||
} finally {
|
||||
|
|
@ -518,6 +538,9 @@ export default class WifiSignalPlusExtension extends Extension {
|
|||
}
|
||||
}
|
||||
|
||||
this.clearAccessPointsItems();
|
||||
this.setAccessPointsVisible(false);
|
||||
|
||||
this.signalHistory.length = 0;
|
||||
this.signalGraph?.queue_repaint();
|
||||
}
|
||||
|
|
@ -606,6 +629,56 @@ export default class WifiSignalPlusExtension extends Extension {
|
|||
return `${signalStrength} dBm (${quality})`;
|
||||
}
|
||||
|
||||
private async updateAccessPoints(): Promise<void> {
|
||||
if (!this.wifiService || !this.accessPointsSection || this.accessPointsUpdatePending) return;
|
||||
|
||||
if (!this.currentConnectedSsid) {
|
||||
this.clearAccessPointsItems();
|
||||
this.setAccessPointsVisible(false);
|
||||
return;
|
||||
}
|
||||
|
||||
this.accessPointsUpdatePending = true;
|
||||
let accessPoints: ScannedNetwork[];
|
||||
try {
|
||||
accessPoints = await this.wifiService.getAccessPointsForSsid(this.currentConnectedSsid);
|
||||
} finally {
|
||||
this.accessPointsUpdatePending = false;
|
||||
}
|
||||
|
||||
this.clearAccessPointsItems();
|
||||
|
||||
if (accessPoints.length <= 1) {
|
||||
this.setAccessPointsVisible(false);
|
||||
return;
|
||||
}
|
||||
|
||||
this.setAccessPointsVisible(true);
|
||||
|
||||
for (const ap of accessPoints) {
|
||||
const isActive = ap.bssid === this.currentConnectedBssid?.toLowerCase();
|
||||
const row = this.createApRow(ap, isActive ? 'connected' : 'spacer');
|
||||
this.accessPointsSection.addMenuItem(row);
|
||||
this.accessPointsItems.push(row);
|
||||
}
|
||||
}
|
||||
|
||||
private setAccessPointsVisible(visible: boolean): void {
|
||||
if (this.accessPointsSeparator) {
|
||||
this.accessPointsSeparator.visible = visible;
|
||||
}
|
||||
if (this.accessPointsSection) {
|
||||
this.accessPointsSection.actor.visible = visible;
|
||||
}
|
||||
}
|
||||
|
||||
private clearAccessPointsItems(): void {
|
||||
for (const item of this.accessPointsItems) {
|
||||
item.destroy();
|
||||
}
|
||||
this.accessPointsItems = [];
|
||||
}
|
||||
|
||||
private async updateNearbyNetworks(): Promise<void> {
|
||||
if (!this.wifiService || !this.nearbySection || this.nearbyUpdatePending) return;
|
||||
|
||||
|
|
@ -744,10 +817,23 @@ export default class WifiSignalPlusExtension extends Extension {
|
|||
return box;
|
||||
}
|
||||
|
||||
private createApRow(ap: ScannedNetwork): PopupMenu.PopupBaseMenuItem {
|
||||
private createApRow(ap: ScannedNetwork, connectedIndicator: 'connected' | 'spacer' | 'none' = 'none'): PopupMenu.PopupBaseMenuItem {
|
||||
const item = new PopupMenu.PopupBaseMenuItem({ reactive: false });
|
||||
item.add_style_class_name('wifi-nearby-ap');
|
||||
|
||||
if (connectedIndicator === 'connected') {
|
||||
const connectedIcon = new St.Icon({
|
||||
icon_name: 'emblem-ok-symbolic',
|
||||
icon_size: 12,
|
||||
style_class: 'wifi-ap-connected-icon',
|
||||
y_align: Clutter.ActorAlign.CENTER,
|
||||
});
|
||||
item.add_child(connectedIcon);
|
||||
} else if (connectedIndicator === 'spacer') {
|
||||
const spacer = new St.Widget({ style_class: 'wifi-ap-icon-spacer' });
|
||||
item.add_child(spacer);
|
||||
}
|
||||
|
||||
const outerBox = new St.BoxLayout({ vertical: true, x_expand: true });
|
||||
|
||||
// Info row: BSSID + details + signal%
|
||||
|
|
|
|||
|
|
@ -139,6 +139,46 @@ export class WifiInfoService {
|
|||
});
|
||||
}
|
||||
|
||||
async getAccessPointsForSsid(ssid: string): Promise<ScannedNetwork[]> {
|
||||
if (!this.client) return [];
|
||||
|
||||
const wifiDevice = this.findWifiDevice();
|
||||
if (!wifiDevice) return [];
|
||||
|
||||
const accessPoints = wifiDevice.get_access_points();
|
||||
const bestByBssid = new Map<string, ScannedNetwork>();
|
||||
|
||||
for (const ap of accessPoints) {
|
||||
const apSsid = this.decodeSsid(ap.get_ssid());
|
||||
if (apSsid !== ssid) continue;
|
||||
|
||||
const bssid = (ap.get_bssid() ?? '').toLowerCase();
|
||||
if (!bssid) continue;
|
||||
|
||||
const strength = ap.get_strength();
|
||||
const existing = bestByBssid.get(bssid);
|
||||
if (existing && (existing.signalPercent as number) >= strength) continue;
|
||||
|
||||
const frequency = asFrequencyMHz(ap.get_frequency());
|
||||
const generation = this.generationMap.get(bssid) ?? WIFI_GENERATIONS.UNKNOWN;
|
||||
|
||||
bestByBssid.set(bssid, Object.freeze({
|
||||
ssid: apSsid,
|
||||
bssid,
|
||||
frequency,
|
||||
channel: frequencyToChannel(frequency),
|
||||
band: frequencyToBand(frequency),
|
||||
bandwidth: getApBandwidth(ap),
|
||||
maxBitrate: asBitrateMbps(ap.get_max_bitrate() / 1000),
|
||||
signalPercent: asSignalPercent(strength),
|
||||
security: getSecurityProtocol(ap),
|
||||
generation,
|
||||
}));
|
||||
}
|
||||
|
||||
return sortBySignalStrength([...bestByBssid.values()]);
|
||||
}
|
||||
|
||||
async getAvailableNetworks(excludeSsid?: string): Promise<Map<string, ScannedNetwork[]>> {
|
||||
if (!this.client) return new Map();
|
||||
|
||||
|
|
|
|||
|
|
@ -184,10 +184,20 @@
|
|||
color: #e01b24;
|
||||
}
|
||||
|
||||
/* Access Points - connected AP icon */
|
||||
.wifi-ap-connected-icon {
|
||||
color: #33d17a;
|
||||
}
|
||||
|
||||
/* Access Points - spacer matching icon width for non-connected rows */
|
||||
.wifi-ap-icon-spacer {
|
||||
width: 12px;
|
||||
}
|
||||
|
||||
/* Nearby networks - AP sub-rows */
|
||||
.wifi-nearby-ap {
|
||||
min-height: 0;
|
||||
padding: 3px 8px 3px 30px;
|
||||
padding: 3px 8px 3px 12px;
|
||||
}
|
||||
|
||||
.wifi-nearby-ap-bssid {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue