mirror of
https://gitlab.steamos.cloud/holo/steamos-manager.git
synced 2025-07-18 04:06:47 -04:00
Merge branch 'work/whiting/screenreader' into 'master'
Add screenreader support to steamos-manager. See merge request holo/steamos-manager!1
This commit is contained in:
commit
334fc09a0b
14 changed files with 1015 additions and 5 deletions
|
@ -8,7 +8,7 @@ test:
|
|||
tags:
|
||||
- x86_64-linux-kvm-docker
|
||||
script:
|
||||
- pacman -Sy --noconfirm --needed dbus rust
|
||||
- pacman -Sy --noconfirm --needed dbus rust gsettings-desktop-schemas
|
||||
- dbus-run-session cargo test
|
||||
|
||||
workflow:
|
||||
|
|
218
Cargo.lock
generated
218
Cargo.lock
generated
|
@ -105,6 +105,16 @@ version = "1.10.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-expr"
|
||||
version = "0.20.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e34e221e91c7eb5e8315b5c9cf1a61670938c0626451f954a51693ed44b37f45"
|
||||
dependencies = [
|
||||
"smallvec",
|
||||
"target-lexicon",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
|
@ -295,12 +305,32 @@ version = "2.3.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
|
||||
|
||||
[[package]]
|
||||
name = "futures-channel"
|
||||
version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-core"
|
||||
version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e"
|
||||
|
||||
[[package]]
|
||||
name = "futures-executor"
|
||||
version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-task",
|
||||
"futures-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-io"
|
||||
version = "0.3.31"
|
||||
|
@ -320,12 +350,43 @@ dependencies = [
|
|||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-macro"
|
||||
version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-sink"
|
||||
version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7"
|
||||
|
||||
[[package]]
|
||||
name = "futures-task"
|
||||
version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988"
|
||||
|
||||
[[package]]
|
||||
name = "futures-util"
|
||||
version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-macro",
|
||||
"futures-task",
|
||||
"pin-project-lite",
|
||||
"pin-utils",
|
||||
"slab",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.16"
|
||||
|
@ -355,6 +416,91 @@ version = "0.31.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"
|
||||
|
||||
[[package]]
|
||||
name = "gio"
|
||||
version = "0.20.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d2a5c3829f5794cb15120db87707b2ec03720edff7ad09eb7b711b532e3fe747"
|
||||
dependencies = [
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"futures-io",
|
||||
"futures-util",
|
||||
"gio-sys",
|
||||
"glib",
|
||||
"libc",
|
||||
"pin-project-lite",
|
||||
"smallvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gio-sys"
|
||||
version = "0.20.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "521e93a7e56fc89e84aea9a52cfc9436816a4b363b030260b699950ff1336c83"
|
||||
dependencies = [
|
||||
"glib-sys",
|
||||
"gobject-sys",
|
||||
"libc",
|
||||
"system-deps",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "glib"
|
||||
version = "0.20.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c501c495842c2b23cdacead803a5a343ca2a5d7a7ddaff14cc5f6cf22cfb92c2"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"futures-executor",
|
||||
"futures-task",
|
||||
"futures-util",
|
||||
"gio-sys",
|
||||
"glib-macros",
|
||||
"glib-sys",
|
||||
"gobject-sys",
|
||||
"libc",
|
||||
"memchr",
|
||||
"smallvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "glib-macros"
|
||||
version = "0.20.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ebe6dc9ce29887c4b3b74d78d5ba473db160a258ae7ed883d23632ac7fed7bc9"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro-crate",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "glib-sys"
|
||||
version = "0.20.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ab79e1ed126803a8fb827e3de0e2ff95191912b8db65cee467edb56fc4cc215"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"system-deps",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gobject-sys"
|
||||
version = "0.20.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec9aca94bb73989e3cfdbf8f2e0f1f6da04db4d291c431f444838925c4c63eda"
|
||||
dependencies = [
|
||||
"glib-sys",
|
||||
"libc",
|
||||
"system-deps",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.14.5"
|
||||
|
@ -437,6 +583,12 @@ dependencies = [
|
|||
"either",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.5.0"
|
||||
|
@ -596,6 +748,12 @@ version = "0.2.16"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b"
|
||||
|
||||
[[package]]
|
||||
name = "pin-utils"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
|
||||
|
||||
[[package]]
|
||||
name = "pkg-config"
|
||||
version = "0.3.32"
|
||||
|
@ -725,6 +883,12 @@ version = "1.0.20"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2"
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.219"
|
||||
|
@ -745,6 +909,18 @@ dependencies = [
|
|||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.140"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"memchr",
|
||||
"ryu",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_repr"
|
||||
version = "0.1.20"
|
||||
|
@ -783,6 +959,21 @@ dependencies = [
|
|||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "slab"
|
||||
version = "0.4.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "1.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9"
|
||||
|
||||
[[package]]
|
||||
name = "socket2"
|
||||
version = "0.5.9"
|
||||
|
@ -807,6 +998,7 @@ dependencies = [
|
|||
"async-trait",
|
||||
"clap",
|
||||
"config",
|
||||
"gio",
|
||||
"inotify",
|
||||
"itertools",
|
||||
"lazy_static",
|
||||
|
@ -815,6 +1007,7 @@ dependencies = [
|
|||
"num_enum",
|
||||
"regex",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"strum",
|
||||
"tempfile",
|
||||
"tokio",
|
||||
|
@ -862,6 +1055,25 @@ dependencies = [
|
|||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "system-deps"
|
||||
version = "7.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e4be53aa0cba896d2dc615bd42bbc130acdcffa239e0a2d965ea5b3b2a86ffdb"
|
||||
dependencies = [
|
||||
"cfg-expr",
|
||||
"heck",
|
||||
"pkg-config",
|
||||
"toml",
|
||||
"version-compare",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "target-lexicon"
|
||||
version = "0.13.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e502f78cdbb8ba4718f566c418c52bc729126ffd16baee5baa718cf25dd5a69a"
|
||||
|
||||
[[package]]
|
||||
name = "tempfile"
|
||||
version = "3.20.0"
|
||||
|
@ -1069,6 +1281,12 @@ version = "1.0.18"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
|
||||
|
||||
[[package]]
|
||||
name = "version-compare"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "852e951cb7832cb45cb1169900d19760cfa39b82bc0ea9c0e5a14ae88411c98b"
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.11.0+wasi-snapshot-preview1"
|
||||
|
|
|
@ -11,6 +11,7 @@ anyhow = "1"
|
|||
async-trait = "0.1"
|
||||
clap = { version = "4.5", default-features = false, features = ["derive", "help", "std", "usage"] }
|
||||
config = { version = "0.15", default-features = false, features = ["async", "ini", "toml"] }
|
||||
gio = "0.20"
|
||||
inotify = { version = "0.11", default-features = false, features = ["stream"] }
|
||||
itertools = "0.14"
|
||||
lazy_static = "1"
|
||||
|
@ -19,6 +20,7 @@ nix = { version = "0.30", default-features = false, features = ["fs", "poll", "s
|
|||
num_enum = "0.7"
|
||||
regex = "1"
|
||||
serde = { version = "1.0", default-features = false, features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
strum = { version = "0.27", features = ["derive"] }
|
||||
tempfile = "3"
|
||||
tokio = { version = "1", default-features = false, features = ["fs", "io-std", "io-util", "macros", "process", "rt-multi-thread", "signal", "sync"] }
|
||||
|
|
1
Makefile
1
Makefile
|
@ -33,3 +33,4 @@ install: target/release/steamos-manager target/release/steamosctl
|
|||
|
||||
install -m644 "data/user/com.steampowered.SteamOSManager1.service" "$(DESTDIR)/usr/share/dbus-1/services/"
|
||||
install -m644 "data/user/steamos-manager.service" "$(DESTDIR)/usr/lib/systemd/user/"
|
||||
install -m644 "data/user/orca.service" "$(DESTDIR)/usr/lib/systemd/user/"
|
||||
|
|
|
@ -298,6 +298,43 @@
|
|||
|
||||
</interface>
|
||||
|
||||
<!--
|
||||
com.steampowered.SteamOSManager1.ScreenReader1
|
||||
@short_description: Optional interface for managing a screen reader.
|
||||
|
||||
This interface is considered unstable and may change between verisons.
|
||||
Once it is considered stable it will be renamed to ScreenReader1
|
||||
-->
|
||||
<interface name="com.steampowered.SteamOSManager1.ScreenReader0">
|
||||
<!--
|
||||
Enabled
|
||||
|
||||
True if screen reader is enabled, false otherwise.
|
||||
-->
|
||||
<property name="Enabled" type="b" access="readwrite"/>
|
||||
|
||||
<!--
|
||||
Rate
|
||||
|
||||
The rate of speech output. Valid values are 0 for slowest to 100 for fastest.
|
||||
-->
|
||||
<property name="Rate" type="d" access="readwrite"/>
|
||||
|
||||
<!--
|
||||
Pitch
|
||||
|
||||
The pitch for speech output. Valid values are 0.0 for lowest, and 10.0 for highest.
|
||||
-->
|
||||
<property name="Pitch" type="d" access="readwrite"/>
|
||||
|
||||
<!--
|
||||
Volume
|
||||
|
||||
The volume for speech output. Valid values ar 0.0 for off, 10.0 for highest.
|
||||
-->
|
||||
<property name="Volume" type="d" access="readwrite"/>
|
||||
</interface>
|
||||
|
||||
<!--
|
||||
com.steampowered.SteamOSManager1.Storage1
|
||||
@short_description: Optional interface for managing storage devices
|
||||
|
|
150
data/test-orca-settings.conf
Normal file
150
data/test-orca-settings.conf
Normal file
|
@ -0,0 +1,150 @@
|
|||
{
|
||||
"general": {
|
||||
"orcaModifierKeys": [
|
||||
"Insert",
|
||||
"KP_Insert"
|
||||
],
|
||||
"enableSpeech": true,
|
||||
"onlySpeakDisplayedText": false,
|
||||
"speechServerFactory": "speechdispatcherfactory",
|
||||
"speechServerInfo": null,
|
||||
"voices": {
|
||||
"default": {
|
||||
"established": false
|
||||
},
|
||||
"uppercase": {
|
||||
"average-pitch": 7.0
|
||||
},
|
||||
"hyperlink": {
|
||||
"established": false
|
||||
},
|
||||
"system": {
|
||||
"established": false
|
||||
}
|
||||
},
|
||||
"speechVerbosityLevel": 1,
|
||||
"readFullRowInGUITable": true,
|
||||
"readFullRowInDocumentTable": true,
|
||||
"readFullRowInSpreadSheet": false,
|
||||
"enableSpeechIndentation": false,
|
||||
"enableEchoByCharacter": false,
|
||||
"enableEchoByWord": false,
|
||||
"enableEchoBySentence": false,
|
||||
"enableKeyEcho": true,
|
||||
"enableAlphabeticKeys": true,
|
||||
"enableNumericKeys": true,
|
||||
"enablePunctuationKeys": true,
|
||||
"enableSpace": true,
|
||||
"enableModifierKeys": true,
|
||||
"enableFunctionKeys": true,
|
||||
"enableActionKeys": true,
|
||||
"enableNavigationKeys": false,
|
||||
"enableDiacriticalKeys": false,
|
||||
"enablePauseBreaks": true,
|
||||
"enableTutorialMessages": true,
|
||||
"enableMnemonicSpeaking": false,
|
||||
"enablePositionSpeaking": false,
|
||||
"enableBraille": true,
|
||||
"disableBrailleEOL": false,
|
||||
"brailleVerbosityLevel": 1,
|
||||
"brailleRolenameStyle": 1,
|
||||
"brailleSelectorIndicator": 192,
|
||||
"brailleLinkIndicator": 192,
|
||||
"enableSound": true,
|
||||
"soundVolume": 0.5,
|
||||
"playSoundForRole": false,
|
||||
"playSoundForState": false,
|
||||
"playSoundForPositionInSet": false,
|
||||
"playSoundForValue": false,
|
||||
"verbalizePunctuationStyle": 1,
|
||||
"presentToolTips": false,
|
||||
"sayAllStyle": 1,
|
||||
"keyboardLayout": 1,
|
||||
"speakBlankLines": true,
|
||||
"speakNumbersAsDigits": false,
|
||||
"speakMisspelledIndicator": true,
|
||||
"textAttributesToSpeak": [],
|
||||
"textAttributesToBraille": [],
|
||||
"textAttributesBrailleIndicator": 0,
|
||||
"profile": [
|
||||
"Default",
|
||||
"default"
|
||||
],
|
||||
"speakProgressBarUpdates": true,
|
||||
"brailleProgressBarUpdates": false,
|
||||
"beepProgressBarUpdates": false,
|
||||
"progressBarUpdateInterval": 10,
|
||||
"progressBarVerbosity": 1,
|
||||
"ignoreStatusBarProgressBars": true,
|
||||
"enableBrailleWordWrap": false,
|
||||
"enableContractedBraille": false,
|
||||
"brailleContractionTable": "",
|
||||
"enableMouseReview": false,
|
||||
"speakCellCoordinates": true,
|
||||
"speakSpreadsheetCoordinates": true,
|
||||
"alwaysSpeakSelectedSpreadsheetRange": false,
|
||||
"speakCellSpan": true,
|
||||
"speakCellHeaders": true,
|
||||
"skipBlankCells": false,
|
||||
"largeObjectTextLength": 75,
|
||||
"structuralNavigationEnabled": true,
|
||||
"wrappedStructuralNavigation": true,
|
||||
"chatMessageVerbosity": 0,
|
||||
"chatSpeakRoomName": false,
|
||||
"chatAnnounceBuddyTyping": false,
|
||||
"chatRoomHistories": false,
|
||||
"enableFlashMessages": true,
|
||||
"brailleFlashTime": 5000,
|
||||
"flashIsPersistent": false,
|
||||
"flashIsDetailed": true,
|
||||
"messagesAreDetailed": true,
|
||||
"presentDateFormat": "%x",
|
||||
"presentTimeFormat": "%X",
|
||||
"activeProfile": [
|
||||
"Default",
|
||||
"default"
|
||||
],
|
||||
"startingProfile": [
|
||||
"Default",
|
||||
"default"
|
||||
],
|
||||
"spellcheckSpellError": true,
|
||||
"spellcheckSpellSuggestion": true,
|
||||
"spellcheckPresentContext": true,
|
||||
"useColorNames": true,
|
||||
"capitalizationStyle": "none",
|
||||
"findResultsVerbosity": 2,
|
||||
"findResultsMinimumLength": 4,
|
||||
"structNavTriggersFocusMode": false,
|
||||
"caretNavTriggersFocusMode": false,
|
||||
"layoutMode": true,
|
||||
"nativeNavTriggersFocusMode": true,
|
||||
"rewindAndFastForwardInSayAll": false,
|
||||
"structNavInSayAll": false,
|
||||
"speakDescription": true,
|
||||
"speakContextBlockquote": true,
|
||||
"speakContextPanel": true,
|
||||
"speakContextLandmark": true,
|
||||
"speakContextNonLandmarkForm": true,
|
||||
"speakContextList": true,
|
||||
"speakContextTable": true,
|
||||
"sayAllContextBlockquote": true,
|
||||
"sayAllContextPanel": true,
|
||||
"sayAllContextLandmark": true,
|
||||
"sayAllContextNonLandmarkForm": true,
|
||||
"sayAllContextList": true,
|
||||
"sayAllContextTable": true
|
||||
},
|
||||
"profiles": {
|
||||
"default": {
|
||||
"profile": [
|
||||
"Default",
|
||||
"default"
|
||||
],
|
||||
"pronunciations": {},
|
||||
"keybindings": {}
|
||||
}
|
||||
},
|
||||
"pronunciations": {},
|
||||
"keybindings": {}
|
||||
}
|
9
data/user/orca.service
Normal file
9
data/user/orca.service
Normal file
|
@ -0,0 +1,9 @@
|
|||
[Unit]
|
||||
Description=Orca Screen Reader
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
ExecStart=/usr/bin/orca
|
||||
ExecReload=/usr/bin/orca --replace
|
||||
Restart=on-failure
|
||||
EnvironmentFile=%t/gamescope-environment
|
|
@ -6,7 +6,7 @@
|
|||
*/
|
||||
|
||||
use anyhow::Result;
|
||||
use clap::{Parser, Subcommand};
|
||||
use clap::{ArgAction, Parser, Subcommand};
|
||||
use itertools::Itertools;
|
||||
use std::collections::HashMap;
|
||||
use std::io::Cursor;
|
||||
|
@ -16,8 +16,8 @@ use steamos_manager::power::{CPUScalingGovernor, GPUPerformanceLevel, GPUPowerPr
|
|||
use steamos_manager::proxy::{
|
||||
AmbientLightSensor1Proxy, BatteryChargeLimit1Proxy, CpuScaling1Proxy, FactoryReset1Proxy,
|
||||
FanControl1Proxy, GpuPerformanceLevel1Proxy, GpuPowerProfile1Proxy, HdmiCec1Proxy,
|
||||
LowPowerMode1Proxy, Manager2Proxy, PerformanceProfile1Proxy, Storage1Proxy, TdpLimit1Proxy,
|
||||
UpdateBios1Proxy, UpdateDock1Proxy, WifiDebug1Proxy, WifiDebugDump1Proxy,
|
||||
LowPowerMode1Proxy, Manager2Proxy, PerformanceProfile1Proxy, ScreenReader0Proxy, Storage1Proxy,
|
||||
TdpLimit1Proxy, UpdateBios1Proxy, UpdateDock1Proxy, WifiDebug1Proxy, WifiDebugDump1Proxy,
|
||||
WifiPowerManagement1Proxy,
|
||||
};
|
||||
use steamos_manager::wifi::{WifiBackend, WifiDebugMode, WifiPowerManagement};
|
||||
|
@ -206,6 +206,42 @@ enum Commands {
|
|||
|
||||
/// Get the model and variant of this device, if known
|
||||
GetDeviceModel,
|
||||
|
||||
/// Get whether screen reader is enabled or not.
|
||||
GetScreenReaderEnabled,
|
||||
|
||||
/// Enable or disable the screen reader
|
||||
SetScreenReaderEnabled {
|
||||
#[arg(action = ArgAction::Set, required = true)]
|
||||
enable: bool,
|
||||
},
|
||||
|
||||
/// Get screen reader rate
|
||||
GetScreenReaderRate,
|
||||
|
||||
/// Set screen reader rate
|
||||
SetScreenReaderRate {
|
||||
/// Valid rates between 0.0 for slowest and 100.0 for fastest.
|
||||
rate: f64,
|
||||
},
|
||||
|
||||
/// Get screen reader pitch
|
||||
GetScreenReaderPitch,
|
||||
|
||||
/// Set screen reader pitch
|
||||
SetScreenReaderPitch {
|
||||
/// Valid pitches between 0.0 for lowest and 10.0 for highest.
|
||||
pitch: f64,
|
||||
},
|
||||
|
||||
/// Get screen reader volume
|
||||
GetScreenReaderVolume,
|
||||
|
||||
/// Set screen reader volume
|
||||
SetScreenReaderVolume {
|
||||
/// Valid volume between 0.0 for off, and 10.0 for loudest.
|
||||
volume: f64,
|
||||
},
|
||||
}
|
||||
|
||||
async fn get_all_properties(conn: &Connection) -> Result<()> {
|
||||
|
@ -519,6 +555,42 @@ async fn main() -> Result<()> {
|
|||
println!("Model: {device}");
|
||||
println!("Variant: {variant}");
|
||||
}
|
||||
Commands::GetScreenReaderEnabled => {
|
||||
let proxy = ScreenReader0Proxy::new(&conn).await?;
|
||||
let enabled = proxy.enabled().await?;
|
||||
println!("Enabled: {enabled}");
|
||||
}
|
||||
Commands::SetScreenReaderEnabled { enable } => {
|
||||
let proxy = ScreenReader0Proxy::new(&conn).await?;
|
||||
proxy.set_enabled(*enable).await?;
|
||||
}
|
||||
Commands::GetScreenReaderRate => {
|
||||
let proxy = ScreenReader0Proxy::new(&conn).await?;
|
||||
let rate = proxy.rate().await?;
|
||||
println!("Rate: {rate}");
|
||||
}
|
||||
Commands::SetScreenReaderRate { rate } => {
|
||||
let proxy = ScreenReader0Proxy::new(&conn).await?;
|
||||
proxy.set_rate(*rate).await?;
|
||||
}
|
||||
Commands::GetScreenReaderPitch => {
|
||||
let proxy = ScreenReader0Proxy::new(&conn).await?;
|
||||
let pitch = proxy.pitch().await?;
|
||||
println!("Pitch: {pitch}");
|
||||
}
|
||||
Commands::SetScreenReaderPitch { pitch } => {
|
||||
let proxy = ScreenReader0Proxy::new(&conn).await?;
|
||||
proxy.set_pitch(*pitch).await?;
|
||||
}
|
||||
Commands::GetScreenReaderVolume => {
|
||||
let proxy = ScreenReader0Proxy::new(&conn).await?;
|
||||
let volume = proxy.volume().await?;
|
||||
println!("Volume: {volume}");
|
||||
}
|
||||
Commands::SetScreenReaderVolume { volume } => {
|
||||
let proxy = ScreenReader0Proxy::new(&conn).await?;
|
||||
proxy.set_volume(*volume).await?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
|
|
@ -25,6 +25,7 @@ mod job;
|
|||
mod manager;
|
||||
mod platform;
|
||||
mod process;
|
||||
mod screenreader;
|
||||
mod sls;
|
||||
mod systemd;
|
||||
mod udev;
|
||||
|
|
|
@ -656,7 +656,7 @@ mod test {
|
|||
fake_model(SteamDeckVariant::Unknown)
|
||||
.await
|
||||
.expect("fake_model");
|
||||
assert_eq!(proxy.als_calibration_gain().await.unwrap(), &[]);
|
||||
assert!(proxy.als_calibration_gain().await.unwrap().is_empty());
|
||||
|
||||
fake_model(SteamDeckVariant::Jupiter)
|
||||
.await
|
||||
|
|
|
@ -31,6 +31,7 @@ use crate::power::{
|
|||
get_gpu_clocks, get_gpu_clocks_range, get_gpu_performance_level, get_gpu_power_profile,
|
||||
get_max_charge_level, get_platform_profile, tdp_limit_manager, TdpManagerCommand,
|
||||
};
|
||||
use crate::screenreader::OrcaManager;
|
||||
use crate::wifi::{
|
||||
get_wifi_backend, get_wifi_power_management_state, list_wifi_interfaces, WifiBackend,
|
||||
};
|
||||
|
@ -153,6 +154,10 @@ struct PerformanceProfile1 {
|
|||
tdp_limit_manager: UnboundedSender<TdpManagerCommand>,
|
||||
}
|
||||
|
||||
struct ScreenReader0 {
|
||||
screen_reader: OrcaManager<'static>,
|
||||
}
|
||||
|
||||
struct Storage1 {
|
||||
proxy: Proxy<'static>,
|
||||
job_manager: UnboundedSender<JobManagerCommand>,
|
||||
|
@ -618,6 +623,68 @@ impl PerformanceProfile1 {
|
|||
}
|
||||
}
|
||||
|
||||
impl ScreenReader0 {
|
||||
async fn new(connection: &Connection) -> Result<ScreenReader0> {
|
||||
let screen_reader = OrcaManager::new(connection).await?;
|
||||
Ok(ScreenReader0 { screen_reader })
|
||||
}
|
||||
}
|
||||
|
||||
#[interface(name = "com.steampowered.SteamOSManager1.ScreenReader0")]
|
||||
impl ScreenReader0 {
|
||||
#[zbus(property)]
|
||||
async fn enabled(&self) -> bool {
|
||||
self.screen_reader.enabled()
|
||||
}
|
||||
|
||||
#[zbus(property)]
|
||||
async fn set_enabled(&mut self, enabled: bool) -> fdo::Result<()> {
|
||||
self.screen_reader
|
||||
.set_enabled(enabled)
|
||||
.await
|
||||
.map_err(to_zbus_fdo_error)
|
||||
}
|
||||
|
||||
#[zbus(property)]
|
||||
async fn rate(&self) -> f64 {
|
||||
self.screen_reader.rate()
|
||||
}
|
||||
|
||||
#[zbus(property)]
|
||||
async fn set_rate(&mut self, rate: f64) -> fdo::Result<()> {
|
||||
self.screen_reader
|
||||
.set_rate(rate)
|
||||
.await
|
||||
.map_err(to_zbus_fdo_error)
|
||||
}
|
||||
|
||||
#[zbus(property)]
|
||||
async fn pitch(&self) -> f64 {
|
||||
self.screen_reader.pitch()
|
||||
}
|
||||
|
||||
#[zbus(property)]
|
||||
async fn set_pitch(&mut self, pitch: f64) -> fdo::Result<()> {
|
||||
self.screen_reader
|
||||
.set_pitch(pitch)
|
||||
.await
|
||||
.map_err(to_zbus_fdo_error)
|
||||
}
|
||||
|
||||
#[zbus(property)]
|
||||
async fn volume(&self) -> f64 {
|
||||
self.screen_reader.volume()
|
||||
}
|
||||
|
||||
#[zbus(property)]
|
||||
async fn set_volume(&mut self, volume: f64) -> fdo::Result<()> {
|
||||
self.screen_reader
|
||||
.set_volume(volume)
|
||||
.await
|
||||
.map_err(to_zbus_fdo_error)
|
||||
}
|
||||
}
|
||||
|
||||
#[interface(name = "com.steampowered.SteamOSManager1.Storage1")]
|
||||
impl Storage1 {
|
||||
async fn format_device(
|
||||
|
@ -919,6 +986,7 @@ pub(crate) async fn create_interfaces(
|
|||
proxy: proxy.clone(),
|
||||
channel: daemon,
|
||||
};
|
||||
let screen_reader = ScreenReader0::new(&session).await?;
|
||||
let wifi_debug = WifiDebug1 {
|
||||
proxy: proxy.clone(),
|
||||
};
|
||||
|
@ -971,6 +1039,8 @@ pub(crate) async fn create_interfaces(
|
|||
|
||||
object_server.at(MANAGER_PATH, manager2).await?;
|
||||
|
||||
object_server.at(MANAGER_PATH, screen_reader).await?;
|
||||
|
||||
if steam_deck_variant().await.unwrap_or_default() == SteamDeckVariant::Galileo {
|
||||
object_server.at(MANAGER_PATH, wifi_debug).await?;
|
||||
}
|
||||
|
|
|
@ -25,6 +25,7 @@ mod hdmi_cec1;
|
|||
mod low_power_mode1;
|
||||
mod manager2;
|
||||
mod performance_profile1;
|
||||
mod screenreader0;
|
||||
mod storage1;
|
||||
mod tdp_limit1;
|
||||
mod update_bios1;
|
||||
|
@ -43,6 +44,7 @@ pub use crate::proxy::hdmi_cec1::HdmiCec1Proxy;
|
|||
pub use crate::proxy::low_power_mode1::LowPowerMode1Proxy;
|
||||
pub use crate::proxy::manager2::Manager2Proxy;
|
||||
pub use crate::proxy::performance_profile1::PerformanceProfile1Proxy;
|
||||
pub use crate::proxy::screenreader0::ScreenReader0Proxy;
|
||||
pub use crate::proxy::storage1::Storage1Proxy;
|
||||
pub use crate::proxy::tdp_limit1::TdpLimit1Proxy;
|
||||
pub use crate::proxy::update_bios1::UpdateBios1Proxy;
|
||||
|
|
45
src/proxy/screenreader0.rs
Normal file
45
src/proxy/screenreader0.rs
Normal file
|
@ -0,0 +1,45 @@
|
|||
//! # D-Bus interface proxy for: `com.steampowered.SteamOSManager1.ScreenReader0`
|
||||
//!
|
||||
//! This code was generated by `zbus-xmlgen` `5.1.0` from D-Bus introspection data.
|
||||
//! Source: `com.steampowered.SteamOSManager1.xml`.
|
||||
//!
|
||||
//! You may prefer to adapt it, instead of using it verbatim.
|
||||
//!
|
||||
//! More information can be found in the [Writing a client proxy] section of the zbus
|
||||
//! documentation.
|
||||
//!
|
||||
//!
|
||||
//! [Writing a client proxy]: https://dbus2.github.io/zbus/client.html
|
||||
//! [D-Bus standard interfaces]: https://dbus.freedesktop.org/doc/dbus-specification.html#standard-interfaces,
|
||||
use zbus::proxy;
|
||||
#[proxy(
|
||||
interface = "com.steampowered.SteamOSManager1.ScreenReader0",
|
||||
default_service = "com.steampowered.SteamOSManager1",
|
||||
default_path = "/com/steampowered/SteamOSManager1",
|
||||
assume_defaults = true
|
||||
)]
|
||||
pub trait ScreenReader0 {
|
||||
/// Enabled property
|
||||
#[zbus(property)]
|
||||
fn enabled(&self) -> zbus::Result<bool>;
|
||||
#[zbus(property)]
|
||||
fn set_enabled(&self, value: bool) -> zbus::Result<()>;
|
||||
|
||||
/// Pitch property
|
||||
#[zbus(property)]
|
||||
fn pitch(&self) -> zbus::Result<f64>;
|
||||
#[zbus(property)]
|
||||
fn set_pitch(&self, value: f64) -> zbus::Result<()>;
|
||||
|
||||
/// Rate property
|
||||
#[zbus(property)]
|
||||
fn rate(&self) -> zbus::Result<f64>;
|
||||
#[zbus(property)]
|
||||
fn set_rate(&self, value: f64) -> zbus::Result<()>;
|
||||
|
||||
/// Volume property
|
||||
#[zbus(property)]
|
||||
fn volume(&self) -> zbus::Result<f64>;
|
||||
#[zbus(property)]
|
||||
fn set_volume(&self, value: f64) -> zbus::Result<()>;
|
||||
}
|
403
src/screenreader.rs
Normal file
403
src/screenreader.rs
Normal file
|
@ -0,0 +1,403 @@
|
|||
/*
|
||||
* Copyright © 2025 Collabora Ltd.
|
||||
* Copyright © 2025 Valve Software
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
use anyhow::{anyhow, bail, ensure, Result};
|
||||
use gio::{prelude::SettingsExt, Settings};
|
||||
use lazy_static::lazy_static;
|
||||
use serde_json::{Map, Value};
|
||||
use std::collections::HashMap;
|
||||
use std::ops::RangeInclusive;
|
||||
use std::path::PathBuf;
|
||||
use tokio::fs::{read_to_string, write};
|
||||
use tracing::{debug, error, info, trace, warn};
|
||||
#[cfg(not(test))]
|
||||
use xdg::BaseDirectories;
|
||||
use zbus::Connection;
|
||||
|
||||
#[cfg(test)]
|
||||
use crate::path;
|
||||
use crate::systemd::SystemdUnit;
|
||||
|
||||
#[cfg(test)]
|
||||
const TEST_ORCA_SETTINGS: &str = "data/test-orca-settings.conf";
|
||||
#[cfg(test)]
|
||||
const ORCA_SETTINGS: &str = "orca-settings.conf";
|
||||
|
||||
#[cfg(not(test))]
|
||||
const ORCA_SETTINGS: &str = "orca/user-settings.conf";
|
||||
const PITCH_SETTING: &str = "average-pitch";
|
||||
const RATE_SETTING: &str = "rate";
|
||||
const VOLUME_SETTING: &str = "gain";
|
||||
const ENABLE_SETTING: &str = "enableSpeech";
|
||||
|
||||
const A11Y_SETTING: &str = "org.gnome.desktop.a11y.applications";
|
||||
const SCREEN_READER_SETTING: &str = "screen-reader-enabled";
|
||||
|
||||
const PITCH_DEFAULT: f64 = 5.0;
|
||||
const RATE_DEFAULT: f64 = 50.0;
|
||||
const VOLUME_DEFAULT: f64 = 10.0;
|
||||
|
||||
lazy_static! {
|
||||
static ref VALID_SETTINGS: HashMap<&'static str, RangeInclusive<f64>> = HashMap::from_iter([
|
||||
(PITCH_SETTING, (0.0..=10.0)),
|
||||
(RATE_SETTING, (0.0..=100.0)),
|
||||
(VOLUME_SETTING, (0.0..=10.0)),
|
||||
]);
|
||||
}
|
||||
|
||||
pub(crate) struct OrcaManager<'dbus> {
|
||||
orca_unit: SystemdUnit<'dbus>,
|
||||
rate: f64,
|
||||
pitch: f64,
|
||||
volume: f64,
|
||||
enabled: bool,
|
||||
}
|
||||
|
||||
impl<'dbus> OrcaManager<'dbus> {
|
||||
pub async fn new(connection: &Connection) -> Result<OrcaManager<'dbus>> {
|
||||
let mut manager = OrcaManager {
|
||||
orca_unit: SystemdUnit::new(connection.clone(), "orca.service").await?,
|
||||
rate: RATE_DEFAULT,
|
||||
pitch: PITCH_DEFAULT,
|
||||
volume: VOLUME_DEFAULT,
|
||||
enabled: true,
|
||||
};
|
||||
let _ = manager
|
||||
.load_values()
|
||||
.await
|
||||
.inspect_err(|e| warn!("Failed to load orca configuration: {e}"));
|
||||
let a11ysettings = Settings::new(A11Y_SETTING);
|
||||
manager.enabled = a11ysettings.boolean(SCREEN_READER_SETTING);
|
||||
Ok(manager)
|
||||
}
|
||||
|
||||
#[cfg(not(test))]
|
||||
fn settings_path(&self) -> Result<PathBuf> {
|
||||
let xdg_base = BaseDirectories::new();
|
||||
Ok(xdg_base
|
||||
.get_data_home()
|
||||
.ok_or(anyhow!("No XDG_DATA_HOME found"))?
|
||||
.join(ORCA_SETTINGS))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn settings_path(&self) -> Result<PathBuf> {
|
||||
Ok(path(ORCA_SETTINGS))
|
||||
}
|
||||
|
||||
pub fn enabled(&self) -> bool {
|
||||
self.enabled
|
||||
}
|
||||
|
||||
pub async fn set_enabled(&mut self, enable: bool) -> Result<()> {
|
||||
if self.enabled == enable {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
#[cfg(not(test))]
|
||||
{
|
||||
let a11ysettings = Settings::new(A11Y_SETTING);
|
||||
a11ysettings
|
||||
.set_boolean(SCREEN_READER_SETTING, enable)
|
||||
.map_err(|e| anyhow!("Unable to set screen reader enabled gsetting, {e}"))?;
|
||||
}
|
||||
self.set_orca_enabled(enable).await?;
|
||||
if enable {
|
||||
self.restart_orca().await?;
|
||||
} else {
|
||||
self.stop_orca().await?;
|
||||
}
|
||||
self.enabled = enable;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn pitch(&self) -> f64 {
|
||||
self.pitch
|
||||
}
|
||||
|
||||
pub async fn set_pitch(&mut self, pitch: f64) -> Result<()> {
|
||||
trace!("set_pitch called with {pitch}");
|
||||
|
||||
self.set_orca_option(PITCH_SETTING, pitch).await?;
|
||||
self.pitch = pitch;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn rate(&self) -> f64 {
|
||||
self.rate
|
||||
}
|
||||
|
||||
pub async fn set_rate(&mut self, rate: f64) -> Result<()> {
|
||||
trace!("set_rate called with {rate}");
|
||||
|
||||
self.set_orca_option(RATE_SETTING, rate).await?;
|
||||
self.rate = rate;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn volume(&self) -> f64 {
|
||||
self.volume
|
||||
}
|
||||
|
||||
pub async fn set_volume(&mut self, volume: f64) -> Result<()> {
|
||||
trace!("set_volume called with {volume}");
|
||||
|
||||
self.set_orca_option(VOLUME_SETTING, volume).await?;
|
||||
self.volume = volume;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn set_orca_enabled(&mut self, enabled: bool) -> Result<()> {
|
||||
// Change json file
|
||||
let data = read_to_string(self.settings_path()?).await?;
|
||||
let mut json: Value = serde_json::from_str(&data)?;
|
||||
|
||||
let general = json
|
||||
.as_object_mut()
|
||||
.ok_or(anyhow!("orca user-settings.conf json is not an object"))?
|
||||
.entry("general")
|
||||
.or_insert(Value::Object(Map::new()));
|
||||
general
|
||||
.as_object_mut()
|
||||
.ok_or(anyhow!("orca user-settings.conf general is not an object"))?
|
||||
.insert(ENABLE_SETTING.to_string(), Value::Bool(enabled));
|
||||
|
||||
let data = serde_json::to_string_pretty(&json)?;
|
||||
Ok(write(self.settings_path()?, data.as_bytes()).await?)
|
||||
}
|
||||
|
||||
async fn load_values(&mut self) -> Result<()> {
|
||||
debug!("Loading orca values from user-settings.conf");
|
||||
let data = read_to_string(self.settings_path()?).await?;
|
||||
let json: Value = serde_json::from_str(&data)?;
|
||||
|
||||
let Some(default_voice) = json
|
||||
.get("profiles")
|
||||
.and_then(|profiles| profiles.get("default"))
|
||||
.and_then(|default_profile| default_profile.get("voices"))
|
||||
.and_then(|voices| voices.get("default"))
|
||||
else {
|
||||
warn!("Orca user-settings.conf missing default voice");
|
||||
self.pitch = PITCH_DEFAULT;
|
||||
self.rate = RATE_DEFAULT;
|
||||
self.volume = VOLUME_DEFAULT;
|
||||
return Ok(());
|
||||
};
|
||||
if let Some(pitch) = default_voice.get(PITCH_SETTING) {
|
||||
self.pitch = pitch.as_f64().unwrap_or_else(|| {
|
||||
error!("Unable to convert orca pitch setting to float value");
|
||||
PITCH_DEFAULT
|
||||
});
|
||||
} else {
|
||||
warn!("Unable to load default pitch from orca user-settings.conf");
|
||||
self.pitch = PITCH_DEFAULT;
|
||||
}
|
||||
|
||||
if let Some(rate) = default_voice.get(RATE_SETTING) {
|
||||
self.rate = rate.as_f64().unwrap_or_else(|| {
|
||||
error!("Unable to convert orca rate setting to float value");
|
||||
RATE_DEFAULT
|
||||
});
|
||||
} else {
|
||||
warn!("Unable to load default voice rate from orca user-settings.conf");
|
||||
}
|
||||
|
||||
if let Some(volume) = default_voice.get(VOLUME_SETTING) {
|
||||
self.volume = volume.as_f64().unwrap_or_else(|| {
|
||||
error!("Unable to convert orca volume value to float value");
|
||||
VOLUME_DEFAULT
|
||||
});
|
||||
} else {
|
||||
warn!("Unable to load default voice volume from orca user-settings.conf");
|
||||
}
|
||||
info!(
|
||||
"Done loading orca user-settings.conf, values: Rate: {}, Pitch: {}, Volume: {}",
|
||||
self.rate, self.pitch, self.volume
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn set_orca_option(&self, option: &str, value: f64) -> Result<()> {
|
||||
if let Some(range) = VALID_SETTINGS.get(option) {
|
||||
ensure!(
|
||||
range.contains(&value),
|
||||
"orca option {option} value {value} out of range"
|
||||
);
|
||||
} else {
|
||||
bail!("Invalid orca option {option}");
|
||||
}
|
||||
let data = read_to_string(self.settings_path()?).await?;
|
||||
let mut json: Value = serde_json::from_str(&data)?;
|
||||
|
||||
let profiles = json
|
||||
.as_object_mut()
|
||||
.ok_or(anyhow!("orca user-settings.conf json is not an object"))?
|
||||
.entry("profiles")
|
||||
.or_insert(Value::Object(Map::new()));
|
||||
let default_profile = profiles
|
||||
.as_object_mut()
|
||||
.ok_or(anyhow!("orca user-settings.conf profiles is not an object"))?
|
||||
.entry("default")
|
||||
.or_insert(Value::Object(Map::new()));
|
||||
let voices = default_profile
|
||||
.as_object_mut()
|
||||
.ok_or(anyhow!(
|
||||
"orca user-settings.conf default profile is not an object"
|
||||
))?
|
||||
.entry("voices")
|
||||
.or_insert(Value::Object(Map::new()));
|
||||
let default_voice = voices
|
||||
.as_object_mut()
|
||||
.ok_or(anyhow!("orca user-settings.conf voices is not an object"))?
|
||||
.entry("default")
|
||||
.or_insert(Value::Object(Map::new()));
|
||||
default_voice
|
||||
.as_object_mut()
|
||||
.ok_or(anyhow!(
|
||||
"orca user-settings.conf default voice is not an object"
|
||||
))?
|
||||
.insert(option.to_string(), value.into());
|
||||
|
||||
let data = serde_json::to_string_pretty(&json)?;
|
||||
Ok(write(self.settings_path()?, data.as_bytes()).await?)
|
||||
}
|
||||
|
||||
#[cfg(not(test))]
|
||||
async fn restart_orca(&self) -> Result<()> {
|
||||
trace!("Restarting orca...");
|
||||
self.orca_unit.restart().await
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
async fn restart_orca(&self) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(not(test))]
|
||||
async fn stop_orca(&self) -> Result<()> {
|
||||
trace!("Stopping orca...");
|
||||
self.orca_unit.stop().await
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
async fn stop_orca(&self) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use crate::testing;
|
||||
use tokio::fs::{copy, remove_file};
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_enable_disable() {
|
||||
let mut h = testing::start();
|
||||
copy(TEST_ORCA_SETTINGS, h.test.path().join(ORCA_SETTINGS))
|
||||
.await
|
||||
.unwrap();
|
||||
let mut manager = OrcaManager::new(&h.new_dbus().await.expect("new_dbus"))
|
||||
.await
|
||||
.expect("OrcaManager::new");
|
||||
let enable_result = manager.set_enabled(true).await;
|
||||
assert!(enable_result.is_ok());
|
||||
assert_eq!(manager.enabled(), true);
|
||||
|
||||
let disable_result = manager.set_enabled(false).await;
|
||||
assert!(disable_result.is_ok());
|
||||
assert_eq!(manager.enabled(), false);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_pitch() {
|
||||
let mut h = testing::start();
|
||||
copy(TEST_ORCA_SETTINGS, h.test.path().join(ORCA_SETTINGS))
|
||||
.await
|
||||
.unwrap();
|
||||
let mut manager = OrcaManager::new(&h.new_dbus().await.expect("new_dbus"))
|
||||
.await
|
||||
.expect("OrcaManager::new");
|
||||
let set_result = manager.set_pitch(5.0).await;
|
||||
assert!(set_result.is_ok());
|
||||
assert_eq!(manager.pitch(), 5.0);
|
||||
|
||||
let too_low_result = manager.set_pitch(-1.0).await;
|
||||
assert!(too_low_result.is_err());
|
||||
assert_eq!(manager.pitch(), 5.0);
|
||||
|
||||
let too_high_result = manager.set_pitch(12.0).await;
|
||||
assert!(too_high_result.is_err());
|
||||
assert_eq!(manager.pitch(), 5.0);
|
||||
|
||||
remove_file(h.test.path().join(ORCA_SETTINGS))
|
||||
.await
|
||||
.unwrap();
|
||||
let nofile_result = manager.set_pitch(7.0).await;
|
||||
assert_eq!(manager.pitch(), 5.0);
|
||||
assert!(nofile_result.is_err());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_rate() {
|
||||
let mut h = testing::start();
|
||||
copy(TEST_ORCA_SETTINGS, h.test.path().join(ORCA_SETTINGS))
|
||||
.await
|
||||
.unwrap();
|
||||
let mut manager = OrcaManager::new(&h.new_dbus().await.expect("new_dbus"))
|
||||
.await
|
||||
.expect("OrcaManager::new");
|
||||
let set_result = manager.set_rate(5.0).await;
|
||||
assert!(set_result.is_ok());
|
||||
assert_eq!(manager.rate(), 5.0);
|
||||
|
||||
let too_low_result = manager.set_rate(-1.0).await;
|
||||
assert!(too_low_result.is_err());
|
||||
assert_eq!(manager.rate(), 5.0);
|
||||
|
||||
let too_high_result = manager.set_rate(101.0).await;
|
||||
assert!(too_high_result.is_err());
|
||||
assert_eq!(manager.rate(), 5.0);
|
||||
|
||||
remove_file(h.test.path().join(ORCA_SETTINGS))
|
||||
.await
|
||||
.unwrap();
|
||||
let nofile_result = manager.set_rate(7.0).await;
|
||||
assert_eq!(manager.rate(), 5.0);
|
||||
assert!(nofile_result.is_err());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_volume() {
|
||||
let mut h = testing::start();
|
||||
copy(TEST_ORCA_SETTINGS, h.test.path().join(ORCA_SETTINGS))
|
||||
.await
|
||||
.unwrap();
|
||||
let mut manager = OrcaManager::new(&h.new_dbus().await.expect("new_dbus"))
|
||||
.await
|
||||
.expect("OrcaManager::new");
|
||||
let set_result = manager.set_volume(5.0).await;
|
||||
assert!(set_result.is_ok());
|
||||
assert_eq!(manager.volume(), 5.0);
|
||||
|
||||
let too_low_result = manager.set_volume(-1.0).await;
|
||||
assert!(too_low_result.is_err());
|
||||
assert_eq!(manager.volume(), 5.0);
|
||||
|
||||
let too_high_result = manager.set_volume(12.0).await;
|
||||
assert!(too_high_result.is_err());
|
||||
assert_eq!(manager.volume(), 5.0);
|
||||
|
||||
remove_file(h.test.path().join(ORCA_SETTINGS))
|
||||
.await
|
||||
.unwrap();
|
||||
let nofile_result = manager.set_volume(7.0).await;
|
||||
assert_eq!(manager.volume(), 5.0);
|
||||
assert!(nofile_result.is_err());
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue