Add transient settings OSD for Sway
This commit is contained in:
parent
195b777b84
commit
2bafbcd2be
14 changed files with 412 additions and 3 deletions
BIN
docs/assets/screenshots/quickshell-osd-synthetic-brightness.png
Normal file
BIN
docs/assets/screenshots/quickshell-osd-synthetic-brightness.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 6.8 KiB |
|
|
@ -27,6 +27,7 @@ PanelWindow {
|
|||
required property var applicationCoordinator
|
||||
required property bool authenticationActive
|
||||
required property var keepAwakeController
|
||||
required property var osdService
|
||||
screen: modelData
|
||||
|
||||
// INVARIANT: une seule surface de barre porte l'inhibiteur, même avec
|
||||
|
|
@ -454,6 +455,7 @@ PanelWindow {
|
|||
adapter: controlCenter.adapter
|
||||
controlCenterOpen: controlCenter.visible
|
||||
audioBrightnessController: controlCenter
|
||||
onAdjustedFromBar: kind => bar.osdService.barAction(kind, bar.screen)
|
||||
keepAwakeController: bar.keepAwakeController
|
||||
clipboardOpen: clipboardLoader.active && !!clipboardLoader.item && !clipboardLoader.item.closing
|
||||
onOpenPageRequested: page => {
|
||||
|
|
@ -496,6 +498,27 @@ PanelWindow {
|
|||
barWindow: bar
|
||||
keepAwakeController: bar.keepAwakeController
|
||||
onThresholdApplied: rightStatusArea.refreshBatteryThreshold()
|
||||
onVisibleChanged: bar.osdService.setPanel(bar.screen, visible, currentPage)
|
||||
onCurrentPageChanged: bar.osdService.setPanel(bar.screen, visible, currentPage)
|
||||
}
|
||||
Connections {
|
||||
target: controlCenter
|
||||
function snapshot() {
|
||||
if (!bar.startupHost) return;
|
||||
bar.osdService.sampleSink(controlCenter.volumeIdentity,
|
||||
controlCenter.volumeAvailable, controlCenter.volumeRaw,
|
||||
controlCenter.volumePercent, controlCenter.volumeMuted);
|
||||
bar.osdService.sampleBrightness(controlCenter.brightnessIdentity,
|
||||
controlCenter.brightnessAvailable, controlCenter.brightnessRaw,
|
||||
controlCenter.brightnessPercent);
|
||||
}
|
||||
function onVolumeIdentityChanged() { snapshot(); }
|
||||
function onVolumeAvailableChanged() { snapshot(); }
|
||||
function onVolumeRawChanged() { snapshot(); }
|
||||
function onVolumeMutedChanged() { snapshot(); }
|
||||
function onBrightnessAvailableChanged() { snapshot(); }
|
||||
function onBrightnessRawChanged() { snapshot(); }
|
||||
Component.onCompleted: snapshot()
|
||||
}
|
||||
|
||||
DateCenter {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,30 @@
|
|||
import Quickshell
|
||||
import Quickshell.Wayland
|
||||
import "osd"
|
||||
import "theme"
|
||||
|
||||
// Données de démonstration uniquement : aucun backend audio ni rétroéclairage.
|
||||
ShellRoot {
|
||||
// Fond de démonstration neutre sous la carte translucide : la capture ne
|
||||
// révèle aucun pixel du bureau ni d'une application réelle.
|
||||
PanelWindow {
|
||||
screen: Quickshell.screens[0]
|
||||
anchors { left: true; bottom: true }
|
||||
margins.left: Math.max(0, Math.round((screen.width - implicitWidth) / 2))
|
||||
margins.bottom: Math.min(40, Math.max(8, Math.round(screen.height / 20)))
|
||||
implicitWidth: Math.min(320, Math.max(1, screen.width - 16))
|
||||
implicitHeight: 80
|
||||
exclusionMode: ExclusionMode.Ignore
|
||||
WlrLayershell.layer: WlrLayer.Top
|
||||
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
|
||||
WlrLayershell.namespace: "labfy-osd-visual-backdrop"
|
||||
mask: Region { width: 0; height: 0 }
|
||||
color: Theme.surface0
|
||||
}
|
||||
OsdWindow {
|
||||
hostScreen: Quickshell.screens[0]
|
||||
kind: "brightness"
|
||||
percent: 42
|
||||
muted: false
|
||||
}
|
||||
}
|
||||
|
|
@ -211,6 +211,10 @@ Control Center, avec un minimum sûr de 10 % et un maximum de 100 %. Ses
|
|||
écriture, y compris si le pilote arrondit. L'absence de sortie audio ou de
|
||||
rétroéclairage désactive l'icône concernée.
|
||||
|
||||
L'OSD des réglages est décrit dans [osd/README.md](osd/README.md). Il observe
|
||||
ces mêmes valeurs confirmées, ainsi que la sourdine de la source audio par
|
||||
défaut, sans modifier les commandes des touches matérielles.
|
||||
|
||||
Le libellé `RESIZE` en Catppuccin Peach apparaît à gauche des workspaces
|
||||
quand l'événement IPC Sway `mode` annonce `resize`. Un seul abonnement
|
||||
`I3IpcListener` partage cet état entre toutes les sorties. Une requête
|
||||
|
|
|
|||
|
|
@ -27,8 +27,12 @@ PopupWindow {
|
|||
readonly property bool volumeAvailable: volumeSlider.sinkAudio !== null
|
||||
readonly property bool volumeMuted: volumeSlider.muted
|
||||
readonly property int volumePercent: volumeSlider.percent
|
||||
readonly property var volumeIdentity: volumeSlider.sink
|
||||
readonly property real volumeRaw: volumeSlider.sinkAudio ? volumeSlider.sinkAudio.volume : NaN
|
||||
readonly property bool brightnessAvailable: brightnessSlider.available
|
||||
readonly property int brightnessPercent: brightnessSlider.percent
|
||||
readonly property string brightnessIdentity: brightnessSlider.backlightPath
|
||||
readonly property real brightnessRaw: brightnessSlider.brightness
|
||||
function adjustVolume(steps) { volumeSlider.adjustBy(steps); }
|
||||
function adjustBrightness(steps) { brightnessSlider.adjustBy(steps); }
|
||||
readonly property var pendingAction: sessionExitGate.pendingAction
|
||||
|
|
|
|||
36
quickshell/.config/quickshell/labfy-sway/osd/Observation.js
Normal file
36
quickshell/.config/quickshell/labfy-sway/osd/Observation.js
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
.pragma library
|
||||
|
||||
// CONTRACT: chaque identité de périphérique a une première valeur de référence,
|
||||
// qui ne représente jamais une action. Les valeurs brutes détectent aussi les
|
||||
// changements arrondis vers le même pourcentage affiché.
|
||||
function observe(previous, identity, available, raw, muted) {
|
||||
if (!identity || !available || !Number.isFinite(raw))
|
||||
return { state: null, changed: false, volumeChanged: false, muteChanged: false };
|
||||
const state = { identity: identity, raw: raw, muted: !!muted };
|
||||
if (!previous || previous.identity !== identity)
|
||||
return { state: state, changed: false, volumeChanged: false, muteChanged: false };
|
||||
const volumeChanged = previous.raw !== raw;
|
||||
const muteChanged = previous.muted !== !!muted;
|
||||
return { state: state, changed: volumeChanged || muteChanged,
|
||||
volumeChanged: volumeChanged, muteChanged: muteChanged };
|
||||
}
|
||||
|
||||
function nextCard(card, kind, value, muted, screen, now) {
|
||||
return { kind: kind, value: value, muted: muted, screen: screen, deadline: now + 1500,
|
||||
generation: card ? card.generation + 1 : 1 };
|
||||
}
|
||||
|
||||
function expire(card, generation, now) {
|
||||
return !!card && card.generation === generation && now >= card.deadline;
|
||||
}
|
||||
|
||||
function screenForEvent(screens, focusedName, origin, kind, now) {
|
||||
if (origin && origin.kind === kind && origin.until >= now
|
||||
&& screens.indexOf(origin.screen) >= 0) return origin.screen;
|
||||
return screens.find(screen => screen.name === focusedName) || screens[0] || null;
|
||||
}
|
||||
|
||||
function pageShows(kind, page) {
|
||||
return kind === "brightness" ? page === 0
|
||||
: kind === "microphone" ? page === 15 : page === 0 || page === 15;
|
||||
}
|
||||
108
quickshell/.config/quickshell/labfy-sway/osd/OsdService.qml
Normal file
108
quickshell/.config/quickshell/labfy-sway/osd/OsdService.qml
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.I3
|
||||
import Quickshell.Services.Pipewire
|
||||
import "Observation.js" as Observation
|
||||
|
||||
Item {
|
||||
id: service
|
||||
required property bool authenticationActive
|
||||
property var sinkState: null
|
||||
property var brightnessState: null
|
||||
property var sourceState: null
|
||||
property var card: null
|
||||
property var panels: ({})
|
||||
property var origin: null
|
||||
readonly property var source: Pipewire.defaultAudioSource
|
||||
readonly property var sourceAudio: source && source.ready && source.audio ? source.audio : null
|
||||
|
||||
// WHY: AudioMixerPage ne suit les nœuds que pendant son ouverture. Ce
|
||||
// tracker léger permet de lire la sourdine du défaut sans ouvrir de flux.
|
||||
PwObjectTracker { objects: [service.source] }
|
||||
onSourceAudioChanged: sampleSource()
|
||||
Connections {
|
||||
target: service.sourceAudio
|
||||
function onMutedChanged() { service.sampleSource(); }
|
||||
}
|
||||
function sampleSource() {
|
||||
const audio = sourceAudio;
|
||||
const result = Observation.observe(sourceState, source, !!audio,
|
||||
audio ? audio.volume : NaN, audio ? audio.muted : false);
|
||||
sourceState = result.state;
|
||||
if (result.muteChanged) publish("microphone", 0, audio.muted);
|
||||
}
|
||||
function sampleSink(identity, available, raw, percent, muted) {
|
||||
const result = Observation.observe(sinkState, identity, available, raw, muted);
|
||||
sinkState = result.state;
|
||||
if (result.muteChanged || result.volumeChanged)
|
||||
publish("volume", percent, muted);
|
||||
}
|
||||
function sampleBrightness(identity, available, raw, percent) {
|
||||
const result = Observation.observe(brightnessState, identity, available, raw, false);
|
||||
brightnessState = result.state;
|
||||
if (result.changed) publish("brightness", percent, false);
|
||||
}
|
||||
function barAction(kind, screen) {
|
||||
// L'indice de provenance n'est consommé que par une valeur confirmée ;
|
||||
// il expire afin qu'une modification externe ultérieure suive le focus.
|
||||
origin = { kind: kind, screen: screen, until: Date.now() + 700 };
|
||||
}
|
||||
function setPanel(screen, visible, page) {
|
||||
const next = Object.assign({}, panels);
|
||||
if (screen) next[screen.name] = visible ? page : -1;
|
||||
panels = next;
|
||||
}
|
||||
function panelShows(kind) {
|
||||
// Tous les Control Centers montrent les mêmes réglages globaux ; une
|
||||
// page visible sur une autre sortie suffit donc à rendre l'OSD redondant.
|
||||
return Object.keys(panels).some(name => Observation.pageShows(kind, panels[name]));
|
||||
}
|
||||
function focusedScreen() {
|
||||
const monitor = I3.focusedMonitor;
|
||||
const screens = Quickshell.screens;
|
||||
return Observation.screenForEvent(screens, monitor ? monitor.name : "",
|
||||
null, "", Date.now());
|
||||
}
|
||||
function publish(kind, percent, muted) {
|
||||
if (authenticationActive) return;
|
||||
const now = Date.now();
|
||||
const monitor = I3.focusedMonitor;
|
||||
const screen = Observation.screenForEvent(Quickshell.screens,
|
||||
monitor ? monitor.name : "", origin, kind, now);
|
||||
if (origin && (origin.kind === kind || origin.until < now)) origin = null;
|
||||
if (!screen || panelShows(kind)) return;
|
||||
// INVARIANT: une seule carte et une seule échéance courante ; un ancien
|
||||
// callback ne peut fermer une génération plus récente.
|
||||
card = Observation.nextCard(card, kind, percent, muted, screen, now);
|
||||
expiry.restart();
|
||||
}
|
||||
onAuthenticationActiveChanged: if (authenticationActive) {
|
||||
card = null;
|
||||
origin = null;
|
||||
expiry.stop();
|
||||
}
|
||||
Timer {
|
||||
id: expiry
|
||||
interval: 1500
|
||||
repeat: false
|
||||
property int generation: 0
|
||||
onTriggered: {
|
||||
if (Observation.expire(service.card, generation, Date.now())) service.card = null;
|
||||
}
|
||||
function restart() {
|
||||
generation = service.card.generation;
|
||||
stop();
|
||||
start();
|
||||
}
|
||||
}
|
||||
LazyLoader {
|
||||
active: !!service.card && !service.authenticationActive
|
||||
OsdWindow {
|
||||
// LazyLoader peut garder l'objet pendant une frame de destruction.
|
||||
hostScreen: service.card ? service.card.screen : service.focusedScreen()
|
||||
kind: service.card ? service.card.kind : "volume"
|
||||
percent: service.card ? service.card.value : 0
|
||||
muted: service.card ? service.card.muted : false
|
||||
}
|
||||
}
|
||||
}
|
||||
93
quickshell/.config/quickshell/labfy-sway/osd/OsdWindow.qml
Normal file
93
quickshell/.config/quickshell/labfy-sway/osd/OsdWindow.qml
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Wayland
|
||||
import "../components"
|
||||
import "../theme"
|
||||
|
||||
PanelWindow {
|
||||
id: window
|
||||
required property var hostScreen
|
||||
required property string kind
|
||||
required property int percent
|
||||
required property bool muted
|
||||
screen: hostScreen
|
||||
anchors { left: true; bottom: true }
|
||||
margins.left: Math.max(0, Math.round((hostScreen.width - implicitWidth) / 2))
|
||||
margins.bottom: Math.min(40, Math.max(8, Math.round(hostScreen.height / 20)))
|
||||
implicitWidth: Math.min(320, Math.max(1, hostScreen.width - 16))
|
||||
implicitHeight: 80
|
||||
exclusionMode: ExclusionMode.Ignore
|
||||
exclusiveZone: 0
|
||||
aboveWindows: true
|
||||
WlrLayershell.layer: WlrLayer.Overlay
|
||||
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
|
||||
WlrLayershell.namespace: "labfy-setting-osd"
|
||||
color: "transparent"
|
||||
// CONTRACT: Region vide au niveau de la fenêtre Wayland : ni clic, ni
|
||||
// défilement, ni glisser-déposer ne sont dirigés vers la carte.
|
||||
mask: Region { width: 0; height: 0 }
|
||||
|
||||
readonly property color emphasis: kind === "brightness" ? Theme.yellow
|
||||
: muted ? Theme.danger : Theme.mauve
|
||||
readonly property string label: kind === "brightness" ? "Luminosité"
|
||||
: kind === "microphone" ? "Microphone" : "Volume"
|
||||
readonly property string glyph: kind === "brightness" ? ""
|
||||
: kind === "microphone" ? (muted ? "" : "")
|
||||
: (muted ? "" : "")
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
radius: 4
|
||||
// WHY: un léger aperçu du contenu reste visible sans changer de palette.
|
||||
color: Qt.rgba(Theme.popupBackground.r, Theme.popupBackground.g,
|
||||
Theme.popupBackground.b, 0.92)
|
||||
border.color: Theme.outline
|
||||
border.width: 1
|
||||
NerdIcon {
|
||||
id: icon
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 16
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: 13
|
||||
text: window.glyph
|
||||
color: window.emphasis
|
||||
font.pixelSize: 26
|
||||
}
|
||||
Text {
|
||||
anchors.left: icon.right
|
||||
anchors.leftMargin: 12
|
||||
anchors.verticalCenter: icon.verticalCenter
|
||||
text: window.label
|
||||
color: Theme.foreground
|
||||
font.pixelSize: 15
|
||||
font.bold: true
|
||||
}
|
||||
Text {
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 16
|
||||
anchors.verticalCenter: icon.verticalCenter
|
||||
text: window.kind === "microphone" ? (window.muted ? "Muet" : "Non muet")
|
||||
: window.muted ? "Muet · " + window.percent + "%" : window.percent + "%"
|
||||
color: window.muted ? Theme.danger : Theme.foreground
|
||||
font.pixelSize: 14
|
||||
}
|
||||
Rectangle {
|
||||
visible: window.kind !== "microphone"
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.leftMargin: 16
|
||||
anchors.rightMargin: 16
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottomMargin: 15
|
||||
height: 7
|
||||
radius: 3
|
||||
color: Theme.buttonBackground
|
||||
Rectangle {
|
||||
width: Math.max(0, Math.min(1, window.percent / 100)) * parent.width
|
||||
height: parent.height
|
||||
radius: parent.radius
|
||||
color: window.emphasis
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
43
quickshell/.config/quickshell/labfy-sway/osd/README.md
Normal file
43
quickshell/.config/quickshell/labfy-sway/osd/README.md
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
# OSD volume, luminosité et microphone
|
||||
|
||||
`OsdService` observe le sink PipeWire par défaut via le `VolumeSlider` déjà
|
||||
partagé entre la barre et le Control Center. Il observe le rétroéclairage via
|
||||
le `BrightnessSlider` existant, et suit en plus le nœud source PipeWire par
|
||||
défaut avec `PwObjectTracker`, même si la page Audio n'a jamais été ouverte.
|
||||
Ce dernier suivi ne crée aucun flux de capture. Les touches Sway gardent leurs
|
||||
commandes `pactl` et `brightnessctl`, leurs pas et leurs bornes. Le défilement
|
||||
de la barre utilise toujours les mêmes curseurs. Un changement externe des
|
||||
mêmes valeurs est également détecté. Les flux d'applications du mixeur ne
|
||||
déclenchent rien.
|
||||
|
||||
Chaque nouvelle identité de périphérique et chaque reconnexion établissent
|
||||
une référence sans affichage. Une valeur brute identique est ignorée ; la
|
||||
carte présente la valeur observée après confirmation, y compris un volume
|
||||
externe supérieur à 100 %. Seule la jauge est plafonnée. Une valeur
|
||||
indisponible ne devient pas `0 %`. Le microphone indique `Muet` ou `Non muet`,
|
||||
sans prétendre détecter une capture active. Lorsqu'un contrôle correspondant
|
||||
est visible dans un Control Center, l'OSD est supprimé. Toute carte active est
|
||||
fermée dès le début d'une authentification Polkit, sans rejeu ultérieur.
|
||||
|
||||
La surface `labfy-setting-osd` est une seule `PanelWindow` de 320 × 80 pixels
|
||||
logiques au maximum, centrée en bas à environ 40 pixels de la sortie choisie.
|
||||
Son fond reprend `Theme.popupBackground` à 92 % d'opacité.
|
||||
Une action de barre privilégie sa sortie ; les autres changements suivent le
|
||||
moniteur focalisé de `I3`, avec repli sur la première sortie disponible.
|
||||
Elle est sur la couche `Overlay`, au-dessus d'un contenu plein écran, avec
|
||||
`WlrKeyboardFocus.None`, zone d'exclusion ignorée et région d'entrée Wayland
|
||||
vide. Elle ne prend donc ni focus ni entrée pointeur. Sa création n'ouvre ni
|
||||
panneau, ni notification, ni `IdleInhibitor` ; la tasse et le pont Firefox ne
|
||||
sont pas liés à cette surface.
|
||||
|
||||
Chaque événement pertinent remplace le contenu de la carte et relance son
|
||||
délai de 1500 ms. Une génération de minuteur empêche une ancienne échéance
|
||||
de fermer une carte plus récente. La surface est détruite à l'expiration.
|
||||
L'Overview évite de capturer cette surface sans supprimer ses miniatures
|
||||
existantes.
|
||||
|
||||
`node tests/quickshell/test_osd_observation.js` vérifie les références, les
|
||||
changements, les rafales, les échéances, la sourdine synthétique et le choix de
|
||||
sortie. `OsdVisualFixture.qml` montre une luminosité fictive de 42 % ; sa
|
||||
[capture recadrée](../../../../../docs/assets/screenshots/quickshell-osd-synthetic-brightness.png)
|
||||
ne contient aucune donnée personnelle.
|
||||
|
|
@ -9,6 +9,9 @@ utiles sur `eDP-1`, sans défilement. Sur les petits écrans, le défilement res
|
|||
disponible si la grille dépasse réellement la hauteur utile. La surface
|
||||
layer-shell `Overlay` prend le focus
|
||||
clavier exclusivement pendant l'ouverture et libère ce focus en se fermant.
|
||||
La surface `labfy-setting-osd` suspend seulement une nouvelle capture
|
||||
automatique tant qu'elle est visible ; les miniatures déjà validées restent
|
||||
disponibles.
|
||||
|
||||
## Provenance des miniatures
|
||||
|
||||
|
|
|
|||
|
|
@ -173,7 +173,8 @@ def state():
|
|||
output_info = {item["name"]: item for item in outputs if item.get("active")}
|
||||
overlay_outputs = [item["name"] for item in outputs if item.get("active")
|
||||
and any(surface.get("namespace") in {
|
||||
"labfy-workspace-overview", "labfy-applications-menu"}
|
||||
"labfy-workspace-overview", "labfy-applications-menu",
|
||||
"labfy-setting-osd"}
|
||||
for surface in item.get("layer_shell_surfaces") or [])]
|
||||
workspace_nodes = {}
|
||||
|
||||
|
|
|
|||
|
|
@ -8,12 +8,14 @@ import "notifications"
|
|||
import "idlebridge"
|
||||
import "theme"
|
||||
import "polkit"
|
||||
import "osd"
|
||||
|
||||
ShellRoot {
|
||||
id: shell
|
||||
// CONTRACT: one PolkitAgent owns the native request queue for the entire
|
||||
// user session. A dialog is constructed only for its current flow.
|
||||
PolkitAgent { id: polkitAgent }
|
||||
OsdService { id: shellOsdService; authenticationActive: shell.authenticationActive }
|
||||
property var polkitScreen: null
|
||||
readonly property bool authenticationActive: polkitAgent.flow !== null
|
||||
signal authenticationOpening()
|
||||
|
|
@ -200,6 +202,7 @@ ShellRoot {
|
|||
model: Quickshell.screens
|
||||
|
||||
Bar {
|
||||
osdService: shellOsdService
|
||||
applicationCoordinator: shell
|
||||
authenticationActive: shell.authenticationActive
|
||||
keepAwakeController: shell
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ Row {
|
|||
signal toggleControlCenterRequested()
|
||||
signal toggleRemovableMediaRequested()
|
||||
signal toggleClipboardRequested()
|
||||
signal adjustedFromBar(string kind)
|
||||
function refreshBatteryThreshold() { batteryIndicator.refreshThreshold() }
|
||||
function closeRemovableMedia() { removableMediaIndicator.popupOpen = false }
|
||||
function setRemovableMediaOpen(open) { removableMediaIndicator.popupOpen = open }
|
||||
|
|
@ -42,7 +43,10 @@ Row {
|
|||
available: area.audioBrightnessController.volumeAvailable
|
||||
clickable: true
|
||||
onActivated: area.openPageRequested(15)
|
||||
onAdjusted: steps => area.audioBrightnessController.adjustVolume(steps)
|
||||
onAdjusted: steps => {
|
||||
area.adjustedFromBar("volume");
|
||||
area.audioBrightnessController.adjustVolume(steps);
|
||||
}
|
||||
}
|
||||
ScrollIcon {
|
||||
glyph: ""
|
||||
|
|
@ -51,7 +55,10 @@ Row {
|
|||
label: "Luminosité"
|
||||
percent: area.audioBrightnessController.brightnessPercent
|
||||
available: area.audioBrightnessController.brightnessAvailable
|
||||
onAdjusted: steps => area.audioBrightnessController.adjustBrightness(steps)
|
||||
onAdjusted: steps => {
|
||||
area.adjustedFromBar("brightness");
|
||||
area.audioBrightnessController.adjustBrightness(steps);
|
||||
}
|
||||
}
|
||||
}
|
||||
Rectangle {
|
||||
|
|
|
|||
54
tests/quickshell/test_osd_observation.js
Normal file
54
tests/quickshell/test_osd_observation.js
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
const assert = require("node:assert/strict");
|
||||
const fs = require("node:fs");
|
||||
const path = require("node:path");
|
||||
const vm = require("node:vm");
|
||||
|
||||
const source = fs.readFileSync(path.resolve(__dirname,
|
||||
"../../quickshell/.config/quickshell/labfy-sway/osd/Observation.js"), "utf8")
|
||||
.replace(/^\.pragma library\s*/, "");
|
||||
const model = {};
|
||||
vm.createContext(model);
|
||||
vm.runInContext(source, model);
|
||||
|
||||
const sink = {};
|
||||
let result = model.observe(null, sink, true, 0.57, false);
|
||||
assert.equal(result.changed, false); // Première lecture : référence seulement.
|
||||
result = model.observe(result.state, sink, true, 0.57, false);
|
||||
assert.equal(result.changed, false);
|
||||
result = model.observe(result.state, sink, true, 0.571, false);
|
||||
assert.equal(result.volumeChanged, true); // Même pourcentage arrondi, autre valeur brute.
|
||||
result = model.observe(result.state, sink, true, 0.571, true);
|
||||
assert.equal(result.muteChanged, true);
|
||||
let microphone = model.observe(null, "source A", true, 0.36, false);
|
||||
assert.equal(microphone.changed, false); // Page Audio jamais ouverte.
|
||||
microphone = model.observe(microphone.state, "source A", true, 0.36, true);
|
||||
assert.equal(microphone.muteChanged, true);
|
||||
microphone = model.observe(microphone.state, "source B", true, 0.2, false);
|
||||
assert.equal(microphone.changed, false);
|
||||
result = model.observe(result.state, {}, true, 0.25, false);
|
||||
assert.equal(result.changed, false); // Nouveau défaut : état initial ignoré.
|
||||
result = model.observe(result.state, null, false, NaN, false);
|
||||
assert.equal(result.state, null);
|
||||
result = model.observe(result.state, sink, true, 0.57, false);
|
||||
assert.equal(result.changed, false); // Reconnexion : nouvelle référence.
|
||||
|
||||
const screen = { name: "sortie-test" };
|
||||
let card = model.nextCard(null, "volume", 57, false, screen, 0);
|
||||
card = model.nextCard(card, "volume", 58, false, screen, 500);
|
||||
assert.equal(card.deadline, 2000);
|
||||
assert.equal(model.expire(card, 1, 1500), false);
|
||||
card = model.nextCard(card, "brightness", 42, false, screen, 800);
|
||||
assert.equal(card.kind, "brightness");
|
||||
assert.equal(model.expire(card, 2, 2000), false); // Ancien timer.
|
||||
assert.equal(model.expire(card, 3, 2299), false);
|
||||
assert.equal(model.expire(card, 3, 2300), true);
|
||||
const other = { name: "autre-sortie" };
|
||||
assert.equal(model.screenForEvent([screen, other], other.name, null, "volume", 0), other);
|
||||
assert.equal(model.screenForEvent([screen, other], other.name,
|
||||
{ kind: "volume", screen, until: 100 }, "volume", 50), screen);
|
||||
assert.equal(model.screenForEvent([screen, other], other.name,
|
||||
{ kind: "volume", screen, until: 100 }, "volume", 101), other);
|
||||
assert.equal(model.pageShows("brightness", 0), true);
|
||||
assert.equal(model.pageShows("microphone", 0), false);
|
||||
assert.equal(model.pageShows("microphone", 15), true);
|
||||
console.log("OSD observation et minuteur: PASS");
|
||||
Loading…
Reference in a new issue