Color AP bitrate with 6-level speed quality scale

Separate maxBitrate from the grey details label into its own colored label in AP rows. Speed quality uses a dedicated palette: purple (>=1000), blue (>=300), green (>=100), yellow (>=50), orange (>=20), red (<20 Mbit/s).
This commit is contained in:
Jalil Arfaoui 2026-02-27 11:43:19 +01:00
parent fffe358a3c
commit 913d63a0c4
4 changed files with 106 additions and 4 deletions

View file

@ -30,10 +30,12 @@ import {
} from './wifiGeneration.js'; } from './wifiGeneration.js';
import { import {
getSignalQualityFromPercent, getSignalQualityFromPercent,
getSpeedQuality,
type GenerationCssClass, type GenerationCssClass,
type ChannelWidthMHz, type ChannelWidthMHz,
type SignalDbm, type SignalDbm,
type FrequencyBand, type FrequencyBand,
type SpeedQuality,
type WifiGeneration, type WifiGeneration,
} from './types.js'; } from './types.js';
@ -63,6 +65,15 @@ const SIGNAL_QUALITY_BAR_COLORS: Readonly<Record<string, string>> = {
Poor: '#e01b24', Poor: '#e01b24',
}; };
const SPEED_QUALITY_COLORS: Readonly<Record<SpeedQuality, string>> = {
Excellent: '#c061cb',
VeryGood: '#62a0ea',
Good: '#33d17a',
OK: '#f6d32d',
Weak: '#ff7800',
Poor: '#e01b24',
};
type MenuItemId = type MenuItemId =
| 'bitrate' | 'bitrate'
| 'channelWidth' | 'channelWidth'
@ -851,9 +862,6 @@ export default class WifiSignalPlusExtension extends Extension {
if ((ap.bandwidth as number) > 20) { if ((ap.bandwidth as number) > 20) {
detailParts.push(`${ap.bandwidth} MHz`); detailParts.push(`${ap.bandwidth} MHz`);
} }
if ((ap.maxBitrate as number) > 0) {
detailParts.push(`${ap.maxBitrate} Mbit/s`);
}
const detailsLabel = new St.Label({ const detailsLabel = new St.Label({
text: detailParts.join(' · '), text: detailParts.join(' · '),
@ -863,6 +871,17 @@ export default class WifiSignalPlusExtension extends Extension {
}); });
infoRow.add_child(detailsLabel); infoRow.add_child(detailsLabel);
if ((ap.maxBitrate as number) > 0) {
const speedQuality = getSpeedQuality(ap.maxBitrate);
const speedLabel = new St.Label({
text: `${ap.maxBitrate} Mbit/s`,
style_class: 'wifi-nearby-ap-speed',
y_align: Clutter.ActorAlign.CENTER,
});
speedLabel.set_style(`color: ${SPEED_QUALITY_COLORS[speedQuality]};`);
infoRow.add_child(speedLabel);
}
const quality = getSignalQualityFromPercent(ap.signalPercent); const quality = getSignalQualityFromPercent(ap.signalPercent);
const signalColor = SIGNAL_QUALITY_BAR_COLORS[quality] ?? '#ffffff'; const signalColor = SIGNAL_QUALITY_BAR_COLORS[quality] ?? '#ffffff';

View file

@ -51,6 +51,9 @@ export type FrequencyBand = (typeof FREQUENCY_BANDS)[number];
export const SIGNAL_QUALITIES = ['Excellent', 'Good', 'Fair', 'Weak', 'Poor', 'Unknown'] as const; export const SIGNAL_QUALITIES = ['Excellent', 'Good', 'Fair', 'Weak', 'Poor', 'Unknown'] as const;
export type SignalQuality = (typeof SIGNAL_QUALITIES)[number]; export type SignalQuality = (typeof SIGNAL_QUALITIES)[number];
export const SPEED_QUALITIES = ['Excellent', 'VeryGood', 'Good', 'OK', 'Weak', 'Poor'] as const;
export type SpeedQuality = (typeof SPEED_QUALITIES)[number];
export const SIGNAL_THRESHOLDS = { export const SIGNAL_THRESHOLDS = {
Excellent: -50, Excellent: -50,
Good: -60, Good: -60,
@ -186,6 +189,24 @@ export function getSignalQualityFromPercent(signalPercent: SignalPercent): Signa
return 'Poor'; return 'Poor';
} }
const SPEED_THRESHOLDS = {
Excellent: 1000,
VeryGood: 300,
Good: 100,
OK: 50,
Weak: 20,
} as const;
export function getSpeedQuality(bitrate: BitrateMbps): SpeedQuality {
const mbps = bitrate as number;
if (mbps >= SPEED_THRESHOLDS.Excellent) return 'Excellent';
if (mbps >= SPEED_THRESHOLDS.VeryGood) return 'VeryGood';
if (mbps >= SPEED_THRESHOLDS.Good) return 'Good';
if (mbps >= SPEED_THRESHOLDS.OK) return 'OK';
if (mbps >= SPEED_THRESHOLDS.Weak) return 'Weak';
return 'Poor';
}
export function createEmptyIwLinkInfo(): IwLinkInfo { export function createEmptyIwLinkInfo(): IwLinkInfo {
return Object.freeze({ return Object.freeze({
generation: WIFI_GENERATIONS.UNKNOWN, generation: WIFI_GENERATIONS.UNKNOWN,

View file

@ -11,7 +11,13 @@ import {
getGenerationIconFilename, getGenerationIconFilename,
isKnownGeneration, isKnownGeneration,
} from './wifiGeneration'; } from './wifiGeneration';
import { GUARD_INTERVALS, asSignalPercent, getSignalQualityFromPercent } from './types'; import {
GUARD_INTERVALS,
asBitrateMbps,
asSignalPercent,
getSignalQualityFromPercent,
getSpeedQuality,
} from './types';
describe('createEmptyIwLinkInfo', () => { describe('createEmptyIwLinkInfo', () => {
it('should create an object with all null values and UNKNOWN generation', () => { it('should create an object with all null values and UNKNOWN generation', () => {
@ -645,3 +651,53 @@ describe('getSignalQualityFromPercent', () => {
expect(getSignalQualityFromPercent(asSignalPercent(100))).toBe('Excellent'); expect(getSignalQualityFromPercent(asSignalPercent(100))).toBe('Excellent');
}); });
}); });
describe('getSpeedQuality', () => {
it('should return Poor for 0 Mbit/s', () => {
expect(getSpeedQuality(asBitrateMbps(0))).toBe('Poor');
});
it('should return Poor for 19 Mbit/s', () => {
expect(getSpeedQuality(asBitrateMbps(19))).toBe('Poor');
});
it('should return Weak for 20 Mbit/s', () => {
expect(getSpeedQuality(asBitrateMbps(20))).toBe('Weak');
});
it('should return Weak for 49 Mbit/s', () => {
expect(getSpeedQuality(asBitrateMbps(49))).toBe('Weak');
});
it('should return OK for 50 Mbit/s', () => {
expect(getSpeedQuality(asBitrateMbps(50))).toBe('OK');
});
it('should return OK for 99 Mbit/s', () => {
expect(getSpeedQuality(asBitrateMbps(99))).toBe('OK');
});
it('should return Good for 100 Mbit/s', () => {
expect(getSpeedQuality(asBitrateMbps(100))).toBe('Good');
});
it('should return Good for 299 Mbit/s', () => {
expect(getSpeedQuality(asBitrateMbps(299))).toBe('Good');
});
it('should return VeryGood for 300 Mbit/s', () => {
expect(getSpeedQuality(asBitrateMbps(300))).toBe('VeryGood');
});
it('should return VeryGood for 999 Mbit/s', () => {
expect(getSpeedQuality(asBitrateMbps(999))).toBe('VeryGood');
});
it('should return Excellent for 1000 Mbit/s', () => {
expect(getSpeedQuality(asBitrateMbps(1000))).toBe('Excellent');
});
it('should return Excellent for 2400 Mbit/s', () => {
expect(getSpeedQuality(asBitrateMbps(2400))).toBe('Excellent');
});
});

View file

@ -211,6 +211,12 @@
color: rgba(255, 255, 255, 0.6); color: rgba(255, 255, 255, 0.6);
} }
.wifi-nearby-ap-speed {
font-weight: bold;
font-size: 0.85em;
margin-right: 8px;
}
.wifi-nearby-ap-signal { .wifi-nearby-ap-signal {
font-weight: bold; font-weight: bold;
font-size: 0.85em; font-size: 0.85em;