Ajout support WiFi 1-3, icônes officielles Wi-Fi Alliance et affichage par icône dans le panel

- Ajout des générations WiFi 1 (802.11b), 2 (802.11a) et 3 (802.11g) dans les types et la détection legacy (fréquence + débit)
- Remplacement des icônes SVG WiFi 4-7 par les PNG officiels Wi-Fi Alliance (reverse signature, blanc sur transparent)
- Conservation des icônes SVG custom pour WiFi 1-3 (non fournies par la Wi-Fi Alliance)
- Remplacement de St.Label par St.Icon dans le panel avec la classe system-status-icon pour un dimensionnement adaptatif
- Fallback sur St.Label texte quand aucune icône n'est disponible (génération inconnue)
- Mise à jour du build pour copier les icônes dans dist/
This commit is contained in:
Jalil Arfaoui 2026-02-12 15:46:43 +01:00
parent ddb6b4ecfb
commit b9c51fd010
12 changed files with 226 additions and 7 deletions

View file

@ -5,7 +5,7 @@
"type": "module", "type": "module",
"scripts": { "scripts": {
"build": "tsc && npm run copy-assets", "build": "tsc && npm run copy-assets",
"copy-assets": "cp metadata.json stylesheet.css dist/", "copy-assets": "cp metadata.json stylesheet.css dist/ && cp -r src/icons dist/",
"install-extension": "npm run build && rm -rf ~/.local/share/gnome-shell/extensions/wifi-signal-plus@music-music.music && cp -r dist ~/.local/share/gnome-shell/extensions/wifi-signal-plus@music-music.music", "install-extension": "npm run build && rm -rf ~/.local/share/gnome-shell/extensions/wifi-signal-plus@music-music.music && cp -r dist ~/.local/share/gnome-shell/extensions/wifi-signal-plus@music-music.music",
"nested": "npm run install-extension && MUTTER_DEBUG_DUMMY_MODE_SPECS=1920x1080 dbus-run-session gnome-shell --devkit --wayland", "nested": "npm run install-extension && MUTTER_DEBUG_DUMMY_MODE_SPECS=1920x1080 dbus-run-session gnome-shell --devkit --wayland",
"watch": "tsc --watch", "watch": "tsc --watch",

View file

@ -5,6 +5,7 @@
*/ */
import Clutter from 'gi://Clutter'; import Clutter from 'gi://Clutter';
import Gio from 'gi://Gio';
import GLib from 'gi://GLib'; import GLib from 'gi://GLib';
import St from 'gi://St'; import St from 'gi://St';
import { Extension } from 'resource:///org/gnome/shell/extensions/extension.js'; import { Extension } from 'resource:///org/gnome/shell/extensions/extension.js';
@ -24,6 +25,7 @@ import {
GENERATION_CSS_CLASSES, GENERATION_CSS_CLASSES,
getGenerationLabel, getGenerationLabel,
getGenerationDescription, getGenerationDescription,
getGenerationIconFilename,
} from './wifiGeneration.js'; } from './wifiGeneration.js';
import type { GenerationCssClass, ChannelWidthMHz, SignalDbm } from './types.js'; import type { GenerationCssClass, ChannelWidthMHz, SignalDbm } from './types.js';
@ -69,6 +71,7 @@ const MENU_STRUCTURE: readonly MenuItemConfig[][] = [
export default class WifiSignalPlusExtension extends Extension { export default class WifiSignalPlusExtension extends Extension {
private indicator: PanelMenu.Button | null = null; private indicator: PanelMenu.Button | null = null;
private icon: St.Icon | null = null;
private label: St.Label | null = null; private label: St.Label | null = null;
private wifiService: WifiInfoService | null = null; private wifiService: WifiInfoService | null = null;
private refreshTimeout: number | null = null; private refreshTimeout: number | null = null;
@ -95,6 +98,7 @@ export default class WifiSignalPlusExtension extends Extension {
this.indicator = null; this.indicator = null;
this.wifiService = null; this.wifiService = null;
this.icon = null;
this.label = null; this.label = null;
this.menuItems.clear(); this.menuItems.clear();
} }
@ -103,12 +107,18 @@ export default class WifiSignalPlusExtension extends Extension {
this.indicator = new PanelMenu.Button(0.0, this.metadata.name, false); this.indicator = new PanelMenu.Button(0.0, this.metadata.name, false);
this.indicator.add_style_class_name('wifi-signal-plus-indicator'); this.indicator.add_style_class_name('wifi-signal-plus-indicator');
this.icon = new St.Icon({
style_class: 'system-status-icon',
y_align: Clutter.ActorAlign.CENTER,
});
this.label = new St.Label({ this.label = new St.Label({
text: 'WiFi', text: 'WiFi',
y_align: Clutter.ActorAlign.CENTER, y_align: Clutter.ActorAlign.CENTER,
style_class: 'wifi-signal-plus-label', style_class: 'wifi-signal-plus-label',
}); });
this.indicator.add_child(this.icon);
this.indicator.add_child(this.label); this.indicator.add_child(this.label);
this.buildMenu(); this.buildMenu();
@ -153,17 +163,31 @@ export default class WifiSignalPlusExtension extends Extension {
} }
private updateIndicatorLabel(info: WifiConnectionInfo): void { private updateIndicatorLabel(info: WifiConnectionInfo): void {
if (!this.label) return; if (!this.icon || !this.label) return;
this.clearGenerationStyles(); this.clearGenerationStyles();
if (!isConnected(info)) { if (!isConnected(info)) {
this.icon.visible = false;
this.label.visible = true;
this.label.set_text('WiFi --'); this.label.set_text('WiFi --');
this.label.add_style_class_name(GENERATION_CSS_CLASSES[WIFI_GENERATIONS.UNKNOWN]); this.label.add_style_class_name(GENERATION_CSS_CLASSES[WIFI_GENERATIONS.UNKNOWN]);
return; return;
} }
const iconFilename = getGenerationIconFilename(info.generation);
if (iconFilename) {
const iconPath = GLib.build_filenamev([this.path, 'icons', iconFilename]);
const file = Gio.File.new_for_path(iconPath);
this.icon.gicon = new Gio.FileIcon({ file });
this.icon.visible = true;
this.label.visible = false;
} else {
this.icon.visible = false;
this.label.visible = true;
this.label.set_text(getGenerationLabel(info.generation)); this.label.set_text(getGenerationLabel(info.generation));
}
this.label.add_style_class_name(GENERATION_CSS_CLASSES[info.generation]); this.label.add_style_class_name(GENERATION_CSS_CLASSES[info.generation]);
} }

1
src/icons/wifi-1.svg Normal file
View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" shape-rendering="geometricPrecision" text-rendering="geometricPrecision" image-rendering="optimizeQuality" fill-rule="evenodd" clip-rule="evenodd" viewBox="0 0 512 351.257"><path fill="#2B2A29" d="M236.563 284.97c18.304 0 33.144 14.839 33.144 33.143 0 18.303-14.84 33.144-33.144 33.144-18.302 0-33.143-14.841-33.143-33.144 0-18.304 14.841-33.143 33.143-33.143zM29.48 117.684c-7.496 6.414-18.774 5.537-25.188-1.959-6.415-7.496-5.538-18.774 1.958-25.189C76.588 30.265 156.239-.682 236.97.012c78.728.675 157.969 31.439 230.049 94.426a17.777 17.777 0 014.839 6.842c-23.099-15.127-50.711-23.935-80.38-23.935-2.837 0-5.655.09-8.451.248-47.7-27.728-97.255-41.476-146.339-41.898-71.983-.617-143.514 27.41-207.208 81.989zm66.973 71.863c-7.379 6.53-18.66 5.841-25.189-1.538-6.53-7.38-5.841-18.66 1.538-25.189 52.587-46.528 108.414-70.31 165.08-69.359 22.193.374 44.361 4.531 66.356 12.588a147.915 147.915 0 00-28.978 28.332c-12.678-3.288-25.336-5.025-37.937-5.236-47.456-.797-95.11 19.913-140.87 60.402zm63.574 63.823c-7.652 6.221-18.898 5.062-25.12-2.59-6.221-7.651-5.062-18.898 2.59-25.119 31.156-25.297 64.978-38.468 99.403-38.57 4.086-.011 8.171.163 12.254.52-2.917 11.341-4.501 23.214-4.602 35.442a98.454 98.454 0 00-7.652-.278c-25.994.076-52.155 10.524-76.873 30.595z"/><path fill="#2B2A29" d="M391.477 103.761c66.558 0 120.523 53.965 120.523 120.525 0 66.558-53.965 120.522-120.523 120.522-66.56 0-120.524-53.964-120.524-120.522 0-66.56 53.964-120.525 120.524-120.525z"/><text x="391" y="232" font-family="Arial,Helvetica,sans-serif" font-size="235" font-weight="bold" fill="#fff" text-anchor="middle" dominant-baseline="central">1</text></svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

1
src/icons/wifi-2.svg Normal file
View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" shape-rendering="geometricPrecision" text-rendering="geometricPrecision" image-rendering="optimizeQuality" fill-rule="evenodd" clip-rule="evenodd" viewBox="0 0 512 351.257"><path fill="#2B2A29" d="M236.563 284.97c18.304 0 33.144 14.839 33.144 33.143 0 18.303-14.84 33.144-33.144 33.144-18.302 0-33.143-14.841-33.143-33.144 0-18.304 14.841-33.143 33.143-33.143zM29.48 117.684c-7.496 6.414-18.774 5.537-25.188-1.959-6.415-7.496-5.538-18.774 1.958-25.189C76.588 30.265 156.239-.682 236.97.012c78.728.675 157.969 31.439 230.049 94.426a17.777 17.777 0 014.839 6.842c-23.099-15.127-50.711-23.935-80.38-23.935-2.837 0-5.655.09-8.451.248-47.7-27.728-97.255-41.476-146.339-41.898-71.983-.617-143.514 27.41-207.208 81.989zm66.973 71.863c-7.379 6.53-18.66 5.841-25.189-1.538-6.53-7.38-5.841-18.66 1.538-25.189 52.587-46.528 108.414-70.31 165.08-69.359 22.193.374 44.361 4.531 66.356 12.588a147.915 147.915 0 00-28.978 28.332c-12.678-3.288-25.336-5.025-37.937-5.236-47.456-.797-95.11 19.913-140.87 60.402zm63.574 63.823c-7.652 6.221-18.898 5.062-25.12-2.59-6.221-7.651-5.062-18.898 2.59-25.119 31.156-25.297 64.978-38.468 99.403-38.57 4.086-.011 8.171.163 12.254.52-2.917 11.341-4.501 23.214-4.602 35.442a98.454 98.454 0 00-7.652-.278c-25.994.076-52.155 10.524-76.873 30.595z"/><path fill="#2B2A29" d="M391.477 103.761c66.558 0 120.523 53.965 120.523 120.525 0 66.558-53.965 120.522-120.523 120.522-66.56 0-120.524-53.964-120.524-120.522 0-66.56 53.964-120.525 120.524-120.525z"/><text x="391" y="232" font-family="Arial,Helvetica,sans-serif" font-size="235" font-weight="bold" fill="#fff" text-anchor="middle" dominant-baseline="central">2</text></svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

1
src/icons/wifi-3.svg Normal file
View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" shape-rendering="geometricPrecision" text-rendering="geometricPrecision" image-rendering="optimizeQuality" fill-rule="evenodd" clip-rule="evenodd" viewBox="0 0 512 351.257"><path fill="#2B2A29" d="M236.563 284.97c18.304 0 33.144 14.839 33.144 33.143 0 18.303-14.84 33.144-33.144 33.144-18.302 0-33.143-14.841-33.143-33.144 0-18.304 14.841-33.143 33.143-33.143zM29.48 117.684c-7.496 6.414-18.774 5.537-25.188-1.959-6.415-7.496-5.538-18.774 1.958-25.189C76.588 30.265 156.239-.682 236.97.012c78.728.675 157.969 31.439 230.049 94.426a17.777 17.777 0 014.839 6.842c-23.099-15.127-50.711-23.935-80.38-23.935-2.837 0-5.655.09-8.451.248-47.7-27.728-97.255-41.476-146.339-41.898-71.983-.617-143.514 27.41-207.208 81.989zm66.973 71.863c-7.379 6.53-18.66 5.841-25.189-1.538-6.53-7.38-5.841-18.66 1.538-25.189 52.587-46.528 108.414-70.31 165.08-69.359 22.193.374 44.361 4.531 66.356 12.588a147.915 147.915 0 00-28.978 28.332c-12.678-3.288-25.336-5.025-37.937-5.236-47.456-.797-95.11 19.913-140.87 60.402zm63.574 63.823c-7.652 6.221-18.898 5.062-25.12-2.59-6.221-7.651-5.062-18.898 2.59-25.119 31.156-25.297 64.978-38.468 99.403-38.57 4.086-.011 8.171.163 12.254.52-2.917 11.341-4.501 23.214-4.602 35.442a98.454 98.454 0 00-7.652-.278c-25.994.076-52.155 10.524-76.873 30.595z"/><path fill="#2B2A29" d="M391.477 103.761c66.558 0 120.523 53.965 120.523 120.525 0 66.558-53.965 120.522-120.523 120.522-66.56 0-120.524-53.964-120.524-120.522 0-66.56 53.964-120.525 120.524-120.525z"/><text x="391" y="232" font-family="Arial,Helvetica,sans-serif" font-size="235" font-weight="bold" fill="#fff" text-anchor="middle" dominant-baseline="central">3</text></svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

BIN
src/icons/wifi-4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

BIN
src/icons/wifi-5.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.7 KiB

BIN
src/icons/wifi-6.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

BIN
src/icons/wifi-7.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

View file

@ -17,6 +17,9 @@ export type GuardIntervalUs = Brand<number, 'GuardIntervalUs'>;
export const WIFI_GENERATIONS = { export const WIFI_GENERATIONS = {
UNKNOWN: 0, UNKNOWN: 0,
WIFI_1: 1,
WIFI_2: 2,
WIFI_3: 3,
WIFI_4: 4, WIFI_4: 4,
WIFI_5: 5, WIFI_5: 5,
WIFI_6: 6, WIFI_6: 6,
@ -25,11 +28,14 @@ export const WIFI_GENERATIONS = {
export type WifiGeneration = (typeof WIFI_GENERATIONS)[keyof typeof WIFI_GENERATIONS]; export type WifiGeneration = (typeof WIFI_GENERATIONS)[keyof typeof WIFI_GENERATIONS];
export function isKnownGeneration(gen: WifiGeneration): gen is 4 | 5 | 6 | 7 { export function isKnownGeneration(gen: WifiGeneration): gen is 1 | 2 | 3 | 4 | 5 | 6 | 7 {
return gen >= WIFI_GENERATIONS.WIFI_4 && gen <= WIFI_GENERATIONS.WIFI_7; return gen >= WIFI_GENERATIONS.WIFI_1 && gen <= WIFI_GENERATIONS.WIFI_7;
} }
export const IEEE_STANDARDS = { export const IEEE_STANDARDS = {
[WIFI_GENERATIONS.WIFI_1]: '802.11b',
[WIFI_GENERATIONS.WIFI_2]: '802.11a',
[WIFI_GENERATIONS.WIFI_3]: '802.11g',
[WIFI_GENERATIONS.WIFI_4]: '802.11n', [WIFI_GENERATIONS.WIFI_4]: '802.11n',
[WIFI_GENERATIONS.WIFI_5]: '802.11ac', [WIFI_GENERATIONS.WIFI_5]: '802.11ac',
[WIFI_GENERATIONS.WIFI_6]: '802.11ax', [WIFI_GENERATIONS.WIFI_6]: '802.11ax',
@ -64,10 +70,13 @@ export const SECURITY_PROTOCOLS = [
export type SecurityProtocol = (typeof SECURITY_PROTOCOLS)[number]; export type SecurityProtocol = (typeof SECURITY_PROTOCOLS)[number];
export type GenerationCssClass = `wifi-gen-${4 | 5 | 6 | 7}` | 'wifi-disconnected'; export type GenerationCssClass = `wifi-gen-${1 | 2 | 3 | 4 | 5 | 6 | 7}` | 'wifi-disconnected';
export type SignalCssClass = `wifi-signal-${Lowercase<Exclude<SignalQuality, 'Unknown'>>}` | ''; export type SignalCssClass = `wifi-signal-${Lowercase<Exclude<SignalQuality, 'Unknown'>>}` | '';
export const GENERATION_CSS_CLASSES = { export const GENERATION_CSS_CLASSES = {
[WIFI_GENERATIONS.WIFI_1]: 'wifi-gen-1',
[WIFI_GENERATIONS.WIFI_2]: 'wifi-gen-2',
[WIFI_GENERATIONS.WIFI_3]: 'wifi-gen-3',
[WIFI_GENERATIONS.WIFI_4]: 'wifi-gen-4', [WIFI_GENERATIONS.WIFI_4]: 'wifi-gen-4',
[WIFI_GENERATIONS.WIFI_5]: 'wifi-gen-5', [WIFI_GENERATIONS.WIFI_5]: 'wifi-gen-5',
[WIFI_GENERATIONS.WIFI_6]: 'wifi-gen-6', [WIFI_GENERATIONS.WIFI_6]: 'wifi-gen-6',

View file

@ -7,6 +7,7 @@ import {
GENERATION_CSS_CLASSES, GENERATION_CSS_CLASSES,
getGenerationLabel, getGenerationLabel,
getGenerationDescription, getGenerationDescription,
getGenerationIconFilename,
isKnownGeneration, isKnownGeneration,
} from './wifiGeneration'; } from './wifiGeneration';
import { GUARD_INTERVALS } from './types'; import { GUARD_INTERVALS } from './types';
@ -37,6 +38,9 @@ describe('createEmptyIwLinkInfo', () => {
describe('isKnownGeneration', () => { describe('isKnownGeneration', () => {
it('should return true for known generations', () => { it('should return true for known generations', () => {
expect(isKnownGeneration(WIFI_GENERATIONS.WIFI_1)).toBe(true);
expect(isKnownGeneration(WIFI_GENERATIONS.WIFI_2)).toBe(true);
expect(isKnownGeneration(WIFI_GENERATIONS.WIFI_3)).toBe(true);
expect(isKnownGeneration(WIFI_GENERATIONS.WIFI_4)).toBe(true); expect(isKnownGeneration(WIFI_GENERATIONS.WIFI_4)).toBe(true);
expect(isKnownGeneration(WIFI_GENERATIONS.WIFI_5)).toBe(true); expect(isKnownGeneration(WIFI_GENERATIONS.WIFI_5)).toBe(true);
expect(isKnownGeneration(WIFI_GENERATIONS.WIFI_6)).toBe(true); expect(isKnownGeneration(WIFI_GENERATIONS.WIFI_6)).toBe(true);
@ -226,8 +230,118 @@ describe('parseIwLinkOutput', () => {
}); });
}); });
describe('legacy WiFi detection', () => {
describe('WiFi 2 (802.11a) - 5 GHz legacy', () => {
it('should detect WiFi 2 for 5 GHz without generation markers', () => {
const iwOutput = `Connected to aa:bb:cc:dd:ee:ff (on wlan0)
SSID: LegacyNetwork
freq: 5180
signal: -60 dBm
tx bitrate: 54.0 MBit/s
rx bitrate: 48.0 MBit/s`;
const result = parseIwLinkOutput(iwOutput);
expect(result.generation).toBe(WIFI_GENERATIONS.WIFI_2);
expect(result.standard).toBe('802.11a');
});
});
describe('WiFi 1 (802.11b) - 2.4 GHz low bitrate', () => {
it('should detect WiFi 1 for 2.4 GHz with bitrate <= 11 Mbps', () => {
const iwOutput = `Connected to aa:bb:cc:dd:ee:ff (on wlan0)
SSID: VeryOldNetwork
freq: 2437
signal: -70 dBm
tx bitrate: 11.0 MBit/s
rx bitrate: 5.5 MBit/s`;
const result = parseIwLinkOutput(iwOutput);
expect(result.generation).toBe(WIFI_GENERATIONS.WIFI_1);
expect(result.standard).toBe('802.11b');
});
it('should detect WiFi 1 for 2.4 GHz with 1 Mbps bitrate', () => {
const iwOutput = `Connected to aa:bb:cc:dd:ee:ff (on wlan0)
freq: 2412
signal: -80 dBm
tx bitrate: 1.0 MBit/s`;
const result = parseIwLinkOutput(iwOutput);
expect(result.generation).toBe(WIFI_GENERATIONS.WIFI_1);
});
});
describe('WiFi 3 (802.11g) - 2.4 GHz high bitrate', () => {
it('should detect WiFi 3 for 2.4 GHz with bitrate > 11 Mbps', () => {
const iwOutput = `Connected to aa:bb:cc:dd:ee:ff (on wlan0)
SSID: OlderNetwork
freq: 2437
signal: -55 dBm
tx bitrate: 54.0 MBit/s
rx bitrate: 36.0 MBit/s`;
const result = parseIwLinkOutput(iwOutput);
expect(result.generation).toBe(WIFI_GENERATIONS.WIFI_3);
expect(result.standard).toBe('802.11g');
});
it('should detect WiFi 3 for 2.4 GHz with 12 Mbps bitrate', () => {
const iwOutput = `Connected to aa:bb:cc:dd:ee:ff (on wlan0)
freq: 2462
signal: -65 dBm
tx bitrate: 12.0 MBit/s`;
const result = parseIwLinkOutput(iwOutput);
expect(result.generation).toBe(WIFI_GENERATIONS.WIFI_3);
});
});
describe('no legacy detection when generation already known', () => {
it('should not override WiFi 4 detection with legacy', () => {
const iwOutput = `Connected to aa:bb:cc:dd:ee:ff (on wlan0)
freq: 2437
signal: -65 dBm
tx bitrate: 72.2 MBit/s MCS 7 20MHz short GI`;
const result = parseIwLinkOutput(iwOutput);
expect(result.generation).toBe(WIFI_GENERATIONS.WIFI_4);
});
});
describe('no legacy detection without enough info', () => {
it('should remain UNKNOWN without frequency', () => {
const iwOutput = `Connected to aa:bb:cc:dd:ee:ff (on wlan0)
signal: -65 dBm
tx bitrate: 54.0 MBit/s`;
const result = parseIwLinkOutput(iwOutput);
expect(result.generation).toBe(WIFI_GENERATIONS.UNKNOWN);
});
it('should remain UNKNOWN without bitrate on 2.4 GHz', () => {
const iwOutput = `Connected to aa:bb:cc:dd:ee:ff (on wlan0)
freq: 2437
signal: -65 dBm`;
const result = parseIwLinkOutput(iwOutput);
expect(result.generation).toBe(WIFI_GENERATIONS.UNKNOWN);
});
});
});
describe('getGenerationLabel', () => { describe('getGenerationLabel', () => {
it('should return "WiFi X" for known generations', () => { it('should return "WiFi X" for known generations', () => {
expect(getGenerationLabel(WIFI_GENERATIONS.WIFI_1)).toBe('WiFi 1');
expect(getGenerationLabel(WIFI_GENERATIONS.WIFI_2)).toBe('WiFi 2');
expect(getGenerationLabel(WIFI_GENERATIONS.WIFI_3)).toBe('WiFi 3');
expect(getGenerationLabel(WIFI_GENERATIONS.WIFI_4)).toBe('WiFi 4'); expect(getGenerationLabel(WIFI_GENERATIONS.WIFI_4)).toBe('WiFi 4');
expect(getGenerationLabel(WIFI_GENERATIONS.WIFI_5)).toBe('WiFi 5'); expect(getGenerationLabel(WIFI_GENERATIONS.WIFI_5)).toBe('WiFi 5');
expect(getGenerationLabel(WIFI_GENERATIONS.WIFI_6)).toBe('WiFi 6'); expect(getGenerationLabel(WIFI_GENERATIONS.WIFI_6)).toBe('WiFi 6');
@ -241,6 +355,9 @@ describe('getGenerationLabel', () => {
describe('getGenerationDescription', () => { describe('getGenerationDescription', () => {
it('should return full description with IEEE standard', () => { it('should return full description with IEEE standard', () => {
expect(getGenerationDescription(WIFI_GENERATIONS.WIFI_1)).toBe('WiFi 1 (802.11b)');
expect(getGenerationDescription(WIFI_GENERATIONS.WIFI_2)).toBe('WiFi 2 (802.11a)');
expect(getGenerationDescription(WIFI_GENERATIONS.WIFI_3)).toBe('WiFi 3 (802.11g)');
expect(getGenerationDescription(WIFI_GENERATIONS.WIFI_4)).toBe('WiFi 4 (802.11n)'); expect(getGenerationDescription(WIFI_GENERATIONS.WIFI_4)).toBe('WiFi 4 (802.11n)');
expect(getGenerationDescription(WIFI_GENERATIONS.WIFI_5)).toBe('WiFi 5 (802.11ac)'); expect(getGenerationDescription(WIFI_GENERATIONS.WIFI_5)).toBe('WiFi 5 (802.11ac)');
expect(getGenerationDescription(WIFI_GENERATIONS.WIFI_6)).toBe('WiFi 6 (802.11ax)'); expect(getGenerationDescription(WIFI_GENERATIONS.WIFI_6)).toBe('WiFi 6 (802.11ax)');
@ -252,8 +369,30 @@ describe('getGenerationDescription', () => {
}); });
}); });
describe('getGenerationIconFilename', () => {
it('should return PNG filename for WiFi 4-7', () => {
expect(getGenerationIconFilename(WIFI_GENERATIONS.WIFI_4)).toBe('wifi-4.png');
expect(getGenerationIconFilename(WIFI_GENERATIONS.WIFI_5)).toBe('wifi-5.png');
expect(getGenerationIconFilename(WIFI_GENERATIONS.WIFI_6)).toBe('wifi-6.png');
expect(getGenerationIconFilename(WIFI_GENERATIONS.WIFI_7)).toBe('wifi-7.png');
});
it('should return SVG filename for WiFi 1-3', () => {
expect(getGenerationIconFilename(WIFI_GENERATIONS.WIFI_1)).toBe('wifi-1.svg');
expect(getGenerationIconFilename(WIFI_GENERATIONS.WIFI_2)).toBe('wifi-2.svg');
expect(getGenerationIconFilename(WIFI_GENERATIONS.WIFI_3)).toBe('wifi-3.svg');
});
it('should return null for UNKNOWN', () => {
expect(getGenerationIconFilename(WIFI_GENERATIONS.UNKNOWN)).toBeNull();
});
});
describe('IEEE_STANDARDS', () => { describe('IEEE_STANDARDS', () => {
it('should map all generations to their IEEE standards', () => { it('should map all generations to their IEEE standards', () => {
expect(IEEE_STANDARDS[WIFI_GENERATIONS.WIFI_1]).toBe('802.11b');
expect(IEEE_STANDARDS[WIFI_GENERATIONS.WIFI_2]).toBe('802.11a');
expect(IEEE_STANDARDS[WIFI_GENERATIONS.WIFI_3]).toBe('802.11g');
expect(IEEE_STANDARDS[WIFI_GENERATIONS.WIFI_4]).toBe('802.11n'); expect(IEEE_STANDARDS[WIFI_GENERATIONS.WIFI_4]).toBe('802.11n');
expect(IEEE_STANDARDS[WIFI_GENERATIONS.WIFI_5]).toBe('802.11ac'); expect(IEEE_STANDARDS[WIFI_GENERATIONS.WIFI_5]).toBe('802.11ac');
expect(IEEE_STANDARDS[WIFI_GENERATIONS.WIFI_6]).toBe('802.11ax'); expect(IEEE_STANDARDS[WIFI_GENERATIONS.WIFI_6]).toBe('802.11ax');
@ -264,6 +403,9 @@ describe('IEEE_STANDARDS', () => {
describe('GENERATION_CSS_CLASSES', () => { describe('GENERATION_CSS_CLASSES', () => {
it('should map all generations to CSS classes', () => { it('should map all generations to CSS classes', () => {
expect(GENERATION_CSS_CLASSES[WIFI_GENERATIONS.WIFI_1]).toBe('wifi-gen-1');
expect(GENERATION_CSS_CLASSES[WIFI_GENERATIONS.WIFI_2]).toBe('wifi-gen-2');
expect(GENERATION_CSS_CLASSES[WIFI_GENERATIONS.WIFI_3]).toBe('wifi-gen-3');
expect(GENERATION_CSS_CLASSES[WIFI_GENERATIONS.WIFI_4]).toBe('wifi-gen-4'); expect(GENERATION_CSS_CLASSES[WIFI_GENERATIONS.WIFI_4]).toBe('wifi-gen-4');
expect(GENERATION_CSS_CLASSES[WIFI_GENERATIONS.WIFI_5]).toBe('wifi-gen-5'); expect(GENERATION_CSS_CLASSES[WIFI_GENERATIONS.WIFI_5]).toBe('wifi-gen-5');
expect(GENERATION_CSS_CLASSES[WIFI_GENERATIONS.WIFI_6]).toBe('wifi-gen-6'); expect(GENERATION_CSS_CLASSES[WIFI_GENERATIONS.WIFI_6]).toBe('wifi-gen-6');
@ -272,8 +414,10 @@ describe('GENERATION_CSS_CLASSES', () => {
}); });
it('should use template literal type pattern', () => { it('should use template literal type pattern', () => {
// Type check: all values should match the GenerationCssClass type
const classes = Object.values(GENERATION_CSS_CLASSES); const classes = Object.values(GENERATION_CSS_CLASSES);
expect(classes).toContain('wifi-gen-1');
expect(classes).toContain('wifi-gen-2');
expect(classes).toContain('wifi-gen-3');
expect(classes).toContain('wifi-gen-4'); expect(classes).toContain('wifi-gen-4');
expect(classes).toContain('wifi-gen-5'); expect(classes).toContain('wifi-gen-5');
expect(classes).toContain('wifi-gen-6'); expect(classes).toContain('wifi-gen-6');

View file

@ -81,6 +81,8 @@ export function parseIwLinkOutput(iwOutput: string): IwLinkInfo {
parseLine(line.trim(), result); parseLine(line.trim(), result);
} }
detectLegacyGeneration(result);
return freezeResult(result); return freezeResult(result);
} }
@ -101,6 +103,28 @@ function createMutableResult(): MutableParseResult {
}; };
} }
const WIFI_1_MAX_BITRATE = 11;
const FREQ_5GHZ_START = 5000;
function detectLegacyGeneration(result: MutableParseResult): void {
if (result.generation !== WIFI_GENERATIONS.UNKNOWN) return;
if (result.frequency === null) return;
if (result.frequency >= FREQ_5GHZ_START) {
result.generation = WIFI_GENERATIONS.WIFI_2;
return;
}
const maxBitrate = Math.max(
(result.txBitrate as number | null) ?? 0,
(result.rxBitrate as number | null) ?? 0
);
if (maxBitrate === 0) return;
result.generation =
maxBitrate <= WIFI_1_MAX_BITRATE ? WIFI_GENERATIONS.WIFI_1 : WIFI_GENERATIONS.WIFI_3;
}
function freezeResult(result: MutableParseResult): IwLinkInfo { function freezeResult(result: MutableParseResult): IwLinkInfo {
if (isKnownGeneration(result.generation)) { if (isKnownGeneration(result.generation)) {
result.standard = IEEE_STANDARDS[result.generation]; result.standard = IEEE_STANDARDS[result.generation];
@ -285,3 +309,18 @@ export function getGenerationDescription(generation: WifiGeneration): string {
? `WiFi ${generation} (${IEEE_STANDARDS[generation]})` ? `WiFi ${generation} (${IEEE_STANDARDS[generation]})`
: 'WiFi'; : 'WiFi';
} }
const GENERATION_ICON_FILENAMES: Record<WifiGeneration, string | null> = {
[WIFI_GENERATIONS.WIFI_1]: 'wifi-1.svg',
[WIFI_GENERATIONS.WIFI_2]: 'wifi-2.svg',
[WIFI_GENERATIONS.WIFI_3]: 'wifi-3.svg',
[WIFI_GENERATIONS.WIFI_4]: 'wifi-4.png',
[WIFI_GENERATIONS.WIFI_5]: 'wifi-5.png',
[WIFI_GENERATIONS.WIFI_6]: 'wifi-6.png',
[WIFI_GENERATIONS.WIFI_7]: 'wifi-7.png',
[WIFI_GENERATIONS.UNKNOWN]: null,
} as const;
export function getGenerationIconFilename(generation: WifiGeneration): string | null {
return GENERATION_ICON_FILENAMES[generation];
}