mirror of
https://gitlab.steamos.cloud/holo/steamos-manager.git
synced 2025-07-05 06:00:30 -04:00
Add voices api.
Get voice locales known to speech-dispatcher. Get possible voice list for a given locale. Added getting and setting the user's chosen voice. Tell orca to reload settings after changing. Initialize user's voice from orca settings.
This commit is contained in:
parent
36685ce630
commit
02a15c9295
5 changed files with 332 additions and 2 deletions
132
Cargo.lock
generated
132
Cargo.lock
generated
|
@ -93,6 +93,26 @@ dependencies = [
|
||||||
"windows-targets 0.52.6",
|
"windows-targets 0.52.6",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bindgen"
|
||||||
|
version = "0.72.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4f72209734318d0b619a5e0f5129918b848c416e122a3c4ce054e03cb87b726f"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags",
|
||||||
|
"cexpr",
|
||||||
|
"clang-sys",
|
||||||
|
"itertools 0.13.0",
|
||||||
|
"log",
|
||||||
|
"prettyplease",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"regex",
|
||||||
|
"rustc-hash",
|
||||||
|
"shlex",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bitflags"
|
name = "bitflags"
|
||||||
version = "2.9.1"
|
version = "2.9.1"
|
||||||
|
@ -105,6 +125,15 @@ version = "1.10.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a"
|
checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cexpr"
|
||||||
|
version = "0.6.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766"
|
||||||
|
dependencies = [
|
||||||
|
"nom",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cfg-expr"
|
name = "cfg-expr"
|
||||||
version = "0.18.0"
|
version = "0.18.0"
|
||||||
|
@ -127,6 +156,17 @@ version = "0.2.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
|
checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "clang-sys"
|
||||||
|
version = "1.8.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4"
|
||||||
|
dependencies = [
|
||||||
|
"glob",
|
||||||
|
"libc",
|
||||||
|
"libloading",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap"
|
name = "clap"
|
||||||
version = "4.5.40"
|
version = "4.5.40"
|
||||||
|
@ -490,6 +530,12 @@ dependencies = [
|
||||||
"system-deps",
|
"system-deps",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "glob"
|
||||||
|
version = "0.3.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "gobject-sys"
|
name = "gobject-sys"
|
||||||
version = "0.20.10"
|
version = "0.20.10"
|
||||||
|
@ -594,6 +640,15 @@ dependencies = [
|
||||||
"windows-sys 0.48.0",
|
"windows-sys 0.48.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "itertools"
|
||||||
|
version = "0.13.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186"
|
||||||
|
dependencies = [
|
||||||
|
"either",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "itertools"
|
name = "itertools"
|
||||||
version = "0.14.0"
|
version = "0.14.0"
|
||||||
|
@ -621,6 +676,16 @@ version = "0.2.174"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776"
|
checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libloading"
|
||||||
|
version = "0.8.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"windows-targets 0.52.6",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libudev-sys"
|
name = "libudev-sys"
|
||||||
version = "0.1.4"
|
version = "0.1.4"
|
||||||
|
@ -637,6 +702,12 @@ version = "0.9.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12"
|
checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "log"
|
||||||
|
version = "0.4.27"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "matchers"
|
name = "matchers"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
@ -661,6 +732,12 @@ dependencies = [
|
||||||
"autocfg",
|
"autocfg",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "minimal-lexical"
|
||||||
|
version = "0.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "miniz_oxide"
|
name = "miniz_oxide"
|
||||||
version = "0.8.9"
|
version = "0.8.9"
|
||||||
|
@ -706,6 +783,16 @@ dependencies = [
|
||||||
"memoffset",
|
"memoffset",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "nom"
|
||||||
|
version = "7.1.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
|
||||||
|
dependencies = [
|
||||||
|
"memchr",
|
||||||
|
"minimal-lexical",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ntapi"
|
name = "ntapi"
|
||||||
version = "0.4.1"
|
version = "0.4.1"
|
||||||
|
@ -820,6 +907,16 @@ version = "0.3.32"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
|
checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "prettyplease"
|
||||||
|
version = "0.2.34"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6837b9e10d61f45f987d50808f83d1ee3d206c66acf650c3e4ae2e1f6ddedf55"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro-crate"
|
name = "proc-macro-crate"
|
||||||
version = "3.3.0"
|
version = "3.3.0"
|
||||||
|
@ -924,6 +1021,12 @@ version = "0.1.25"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "989e6739f80c4ad5b13e0fd7fe89531180375b18520cc8c82080e4dc4035b84f"
|
checksum = "989e6739f80c4ad5b13e0fd7fe89531180375b18520cc8c82080e4dc4035b84f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustc-hash"
|
||||||
|
version = "2.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustix"
|
name = "rustix"
|
||||||
version = "1.0.7"
|
version = "1.0.7"
|
||||||
|
@ -1010,6 +1113,12 @@ dependencies = [
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "shlex"
|
||||||
|
version = "1.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "signal-hook-registry"
|
name = "signal-hook-registry"
|
||||||
version = "1.4.5"
|
version = "1.4.5"
|
||||||
|
@ -1041,6 +1150,26 @@ dependencies = [
|
||||||
"windows-sys 0.52.0",
|
"windows-sys 0.52.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "speech-dispatcher"
|
||||||
|
version = "0.16.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5727d53c474ba5ada07784ad7d203cf896a74854cfee0eb32376b00759eb2972"
|
||||||
|
dependencies = [
|
||||||
|
"lazy_static",
|
||||||
|
"libc",
|
||||||
|
"speech-dispatcher-sys",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "speech-dispatcher-sys"
|
||||||
|
version = "0.7.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6c3e8acdf2b1f4bb13f1813b40b52f3edf4cc94d8a55fe713a584f672a10388d"
|
||||||
|
dependencies = [
|
||||||
|
"bindgen",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "static_assertions"
|
name = "static_assertions"
|
||||||
version = "1.1.0"
|
version = "1.1.0"
|
||||||
|
@ -1058,7 +1187,7 @@ dependencies = [
|
||||||
"gio",
|
"gio",
|
||||||
"inotify",
|
"inotify",
|
||||||
"input-linux",
|
"input-linux",
|
||||||
"itertools",
|
"itertools 0.14.0",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
"libc",
|
"libc",
|
||||||
"nix 0.30.1",
|
"nix 0.30.1",
|
||||||
|
@ -1066,6 +1195,7 @@ dependencies = [
|
||||||
"regex",
|
"regex",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
"speech-dispatcher",
|
||||||
"strum",
|
"strum",
|
||||||
"sysinfo",
|
"sysinfo",
|
||||||
"tempfile",
|
"tempfile",
|
||||||
|
|
|
@ -18,11 +18,12 @@ input-linux = "0.7"
|
||||||
itertools = "0.14"
|
itertools = "0.14"
|
||||||
lazy_static = "1"
|
lazy_static = "1"
|
||||||
libc = "0.2"
|
libc = "0.2"
|
||||||
nix = { version = "0.30", default-features = false, features = ["fs", "poll", "signal", "time"] }
|
nix = { version = "0.30", default-features = false, features = ["fs", "poll", "signal", "time", "user"] }
|
||||||
num_enum = "0.7"
|
num_enum = "0.7"
|
||||||
regex = "1"
|
regex = "1"
|
||||||
serde = { version = "1.0", default-features = false, features = ["derive"] }
|
serde = { version = "1.0", default-features = false, features = ["derive"] }
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
|
speech-dispatcher = "0.16"
|
||||||
strum = { version = "0.27", features = ["derive"] }
|
strum = { version = "0.27", features = ["derive"] }
|
||||||
sysinfo = "0.35"
|
sysinfo = "0.35"
|
||||||
tempfile = "3"
|
tempfile = "3"
|
||||||
|
|
|
@ -343,6 +343,22 @@
|
||||||
-->
|
-->
|
||||||
<property name="Mode" type="u" access="readwrite"/>
|
<property name="Mode" type="u" access="readwrite"/>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Voice
|
||||||
|
|
||||||
|
The voice to use for screen reading.
|
||||||
|
|
||||||
|
Valid voices can be found from GetVoicesForLocale
|
||||||
|
-->
|
||||||
|
<property name="Voice" type="s" access="readwrite"/>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Voice Locales
|
||||||
|
|
||||||
|
The list of known voice locales
|
||||||
|
-->
|
||||||
|
<property name="VoiceLocales" type="as" access="read"/>
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
Trigger Action
|
Trigger Action
|
||||||
|
|
||||||
|
@ -369,6 +385,20 @@
|
||||||
<arg type="u" name="action" direction="in"/>
|
<arg type="u" name="action" direction="in"/>
|
||||||
<arg type="t" name="timestamp" direction="in"/>
|
<arg type="t" name="timestamp" direction="in"/>
|
||||||
</method>
|
</method>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Get the voices for a given locale
|
||||||
|
|
||||||
|
Get a list of voices for a given locale
|
||||||
|
|
||||||
|
@locale: The locale to get the voices for. e.g. en-US
|
||||||
|
@voices: A list of voice names to present to the user
|
||||||
|
-->
|
||||||
|
<method name="GetVoicesForLocale">
|
||||||
|
<arg type="s" name="locale" direction="in"/>
|
||||||
|
<arg type="as" name="voices" direction="out"/>
|
||||||
|
</method>
|
||||||
|
|
||||||
</interface>
|
</interface>
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
|
|
|
@ -711,6 +711,29 @@ impl ScreenReader0 {
|
||||||
self.mode_changed(&ctx).await.map_err(to_zbus_fdo_error)
|
self.mode_changed(&ctx).await.map_err(to_zbus_fdo_error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[zbus(property)]
|
||||||
|
async fn voice(&self) -> String {
|
||||||
|
self.screen_reader.voice()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[zbus(property)]
|
||||||
|
async fn set_voice(
|
||||||
|
&mut self,
|
||||||
|
voice: &str,
|
||||||
|
#[zbus(signal_emitter)] ctx: SignalEmitter<'_>,
|
||||||
|
) -> fdo::Result<()> {
|
||||||
|
self.screen_reader
|
||||||
|
.set_voice(voice)
|
||||||
|
.await
|
||||||
|
.map_err(to_zbus_fdo_error)?;
|
||||||
|
self.voice_changed(&ctx).await.map_err(to_zbus_fdo_error)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[zbus(property)]
|
||||||
|
async fn voice_locales(&self) -> Vec<String> {
|
||||||
|
self.screen_reader.get_voice_locales()
|
||||||
|
}
|
||||||
|
|
||||||
async fn trigger_action(&mut self, a: u32, timestamp: u64) -> fdo::Result<()> {
|
async fn trigger_action(&mut self, a: u32, timestamp: u64) -> fdo::Result<()> {
|
||||||
let action = match ScreenReaderAction::try_from(a) {
|
let action = match ScreenReaderAction::try_from(a) {
|
||||||
Ok(action) => action,
|
Ok(action) => action,
|
||||||
|
@ -721,6 +744,12 @@ impl ScreenReader0 {
|
||||||
.await
|
.await
|
||||||
.map_err(to_zbus_fdo_error)
|
.map_err(to_zbus_fdo_error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn get_voices(&self, locale: &str) -> fdo::Result<Vec<String>> {
|
||||||
|
self.screen_reader
|
||||||
|
.get_voices(locale)
|
||||||
|
.ok_or(fdo::Error::Failed(String::from("No voices found")))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[interface(name = "com.steampowered.SteamOSManager1.Storage1")]
|
#[interface(name = "com.steampowered.SteamOSManager1.Storage1")]
|
||||||
|
|
|
@ -12,8 +12,13 @@ use input_linux::Key;
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
use nix::sys::signal;
|
use nix::sys::signal;
|
||||||
use nix::unistd::Pid;
|
use nix::unistd::Pid;
|
||||||
|
#[cfg(not(test))]
|
||||||
|
use nix::unistd::{Uid, User};
|
||||||
use num_enum::TryFromPrimitive;
|
use num_enum::TryFromPrimitive;
|
||||||
use serde_json::{Map, Value};
|
use serde_json::{Map, Value};
|
||||||
|
use speech_dispatcher::Voice;
|
||||||
|
#[cfg(not(test))]
|
||||||
|
use speech_dispatcher::{Connection as SDConnection, Mode};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::io::ErrorKind;
|
use std::io::ErrorKind;
|
||||||
use std::ops::RangeInclusive;
|
use std::ops::RangeInclusive;
|
||||||
|
@ -40,6 +45,8 @@ const ORCA_SETTINGS: &str = "orca/user-settings.conf";
|
||||||
const PITCH_SETTING: &str = "average-pitch";
|
const PITCH_SETTING: &str = "average-pitch";
|
||||||
const RATE_SETTING: &str = "rate";
|
const RATE_SETTING: &str = "rate";
|
||||||
const VOLUME_SETTING: &str = "gain";
|
const VOLUME_SETTING: &str = "gain";
|
||||||
|
const FAMILY_SETTING: &str = "family";
|
||||||
|
const VOICE_NAME_SETTING: &str = "name";
|
||||||
const ENABLE_SETTING: &str = "enableSpeech";
|
const ENABLE_SETTING: &str = "enableSpeech";
|
||||||
|
|
||||||
const A11Y_SETTING: &str = "org.gnome.desktop.a11y.applications";
|
const A11Y_SETTING: &str = "org.gnome.desktop.a11y.applications";
|
||||||
|
@ -49,6 +56,7 @@ const KEYBOARD_NAME: &str = "steamos-manager";
|
||||||
const PITCH_DEFAULT: f64 = 5.0;
|
const PITCH_DEFAULT: f64 = 5.0;
|
||||||
const RATE_DEFAULT: f64 = 50.0;
|
const RATE_DEFAULT: f64 = 50.0;
|
||||||
const VOLUME_DEFAULT: f64 = 10.0;
|
const VOLUME_DEFAULT: f64 = 10.0;
|
||||||
|
const VOICE_NAME_DEFAULT: &str = "default";
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
static ref VALID_SETTINGS: HashMap<&'static str, RangeInclusive<f64>> = HashMap::from_iter([
|
static ref VALID_SETTINGS: HashMap<&'static str, RangeInclusive<f64>> = HashMap::from_iter([
|
||||||
|
@ -89,7 +97,10 @@ pub(crate) struct OrcaManager<'dbus> {
|
||||||
volume: f64,
|
volume: f64,
|
||||||
enabled: bool,
|
enabled: bool,
|
||||||
mode: ScreenReaderMode,
|
mode: ScreenReaderMode,
|
||||||
|
voice: String,
|
||||||
keyboard: UInputDevice,
|
keyboard: UInputDevice,
|
||||||
|
voices: HashMap<String, Voice>,
|
||||||
|
voices_by_language: HashMap<String, Vec<String>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'dbus> OrcaManager<'dbus> {
|
impl<'dbus> OrcaManager<'dbus> {
|
||||||
|
@ -102,7 +113,10 @@ impl<'dbus> OrcaManager<'dbus> {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
// Always start in browse mode for now, since we have no storage to remember this property
|
// Always start in browse mode for now, since we have no storage to remember this property
|
||||||
mode: ScreenReaderMode::Browse,
|
mode: ScreenReaderMode::Browse,
|
||||||
|
voice: String::new(),
|
||||||
keyboard: UInputDevice::new()?,
|
keyboard: UInputDevice::new()?,
|
||||||
|
voices: HashMap::new(),
|
||||||
|
voices_by_language: HashMap::new(),
|
||||||
};
|
};
|
||||||
let _ = manager
|
let _ = manager
|
||||||
.load_values()
|
.load_values()
|
||||||
|
@ -124,9 +138,46 @@ impl<'dbus> OrcaManager<'dbus> {
|
||||||
Key::Up,
|
Key::Up,
|
||||||
])?;
|
])?;
|
||||||
|
|
||||||
|
#[cfg(not(test))]
|
||||||
|
match manager.init_voice_list() {
|
||||||
|
Ok(()) => trace!("Voice list loaded"),
|
||||||
|
Err(e) => error!("Unable to init voice list. {e}"),
|
||||||
|
}
|
||||||
|
|
||||||
Ok(manager)
|
Ok(manager)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(not(test))]
|
||||||
|
fn init_voice_list(&mut self) -> Result<()> {
|
||||||
|
const CLIENT_NAME: &str = "steamos-manager";
|
||||||
|
const CONNECTION_NAME: &str = "steamos-manager";
|
||||||
|
let user_name = User::from_uid(Uid::current())?
|
||||||
|
.ok_or(anyhow!("Unable to get current user"))?
|
||||||
|
.name;
|
||||||
|
let connection =
|
||||||
|
SDConnection::open(CLIENT_NAME, CONNECTION_NAME, &user_name, Mode::Threaded)?;
|
||||||
|
let voices = connection.list_synthesis_voices()?.to_vec();
|
||||||
|
for v in voices.iter() {
|
||||||
|
let name = &v.name;
|
||||||
|
let lang = &v.language;
|
||||||
|
self.voices.insert(name.clone(), v.clone());
|
||||||
|
self.voices_by_language
|
||||||
|
.entry(lang.clone())
|
||||||
|
.or_default()
|
||||||
|
.push(name.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_voices(&self, locale: &str) -> Option<Vec<String>> {
|
||||||
|
self.voices_by_language.get(locale).cloned()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_voice_locales(&self) -> Vec<String> {
|
||||||
|
self.voices_by_language.keys().cloned().collect()
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(not(test))]
|
#[cfg(not(test))]
|
||||||
fn settings_path(&self) -> Result<PathBuf> {
|
fn settings_path(&self) -> Result<PathBuf> {
|
||||||
let xdg_base = BaseDirectories::new();
|
let xdg_base = BaseDirectories::new();
|
||||||
|
@ -170,6 +221,21 @@ impl<'dbus> OrcaManager<'dbus> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn voice(&self) -> String {
|
||||||
|
self.voice.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn set_voice(&mut self, voice: &str) -> Result<()> {
|
||||||
|
let properties = self
|
||||||
|
.voices
|
||||||
|
.get(voice)
|
||||||
|
.ok_or(anyhow!("Invalid voice specified"))?;
|
||||||
|
self.set_orca_voice(properties).await?;
|
||||||
|
self.voice = voice.to_string();
|
||||||
|
self.reload_orca().await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
pub fn pitch(&self) -> f64 {
|
pub fn pitch(&self) -> f64 {
|
||||||
self.pitch
|
self.pitch
|
||||||
}
|
}
|
||||||
|
@ -398,9 +464,83 @@ impl<'dbus> OrcaManager<'dbus> {
|
||||||
self.rate, self.pitch, self.volume
|
self.rate, self.pitch, self.volume
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if let Some(family) = default_voice.get(FAMILY_SETTING) {
|
||||||
|
if let Some(name) = family.get(VOICE_NAME_SETTING) {
|
||||||
|
self.voice = name
|
||||||
|
.as_str()
|
||||||
|
.unwrap_or_else(|| {
|
||||||
|
error!("Unable to convert orca default voice family name to string value");
|
||||||
|
VOICE_NAME_DEFAULT
|
||||||
|
})
|
||||||
|
.to_string();
|
||||||
|
} else {
|
||||||
|
warn!("Unable to load default voice family name from orca user-settings.conf");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
warn!("Unable to load default voice family from orca user-settings.conf");
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn set_orca_voice(&self, voice: &Voice) -> Result<()> {
|
||||||
|
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()));
|
||||||
|
let family = default_voice
|
||||||
|
.as_object_mut()
|
||||||
|
.ok_or(anyhow!("orca user-settings.conf family is not an object"))?
|
||||||
|
.entry("family")
|
||||||
|
.or_insert(Value::Object(Map::new()));
|
||||||
|
// If we have a dialect use it, otherwise leave it blank
|
||||||
|
let mut language = voice.language.clone();
|
||||||
|
let mut dialect = "";
|
||||||
|
if let Some((l, d)) = voice.language.split_once("-") {
|
||||||
|
language = l.to_string();
|
||||||
|
dialect = d;
|
||||||
|
}
|
||||||
|
let mut_family = family.as_object_mut().ok_or(anyhow!(
|
||||||
|
"orca user-settings.conf default voice family is not an object"
|
||||||
|
))?;
|
||||||
|
mut_family.insert("name".to_string(), voice.name.clone().into());
|
||||||
|
mut_family.insert("lang".to_string(), language.into());
|
||||||
|
mut_family.insert("variant".to_string(), voice.variant.clone().into());
|
||||||
|
mut_family.insert("dialect".to_string(), dialect.into());
|
||||||
|
|
||||||
|
// Set established property
|
||||||
|
default_voice
|
||||||
|
.as_object_mut()
|
||||||
|
.ok_or(anyhow!(
|
||||||
|
"orca user-settings.conf default voice is not an object"
|
||||||
|
))?
|
||||||
|
.insert("established".to_string(), true.into());
|
||||||
|
|
||||||
|
let data = serde_json::to_string_pretty(&json)?;
|
||||||
|
Ok(write(self.settings_path()?, data.as_bytes()).await?)
|
||||||
|
}
|
||||||
|
|
||||||
async fn set_orca_option(&self, option: &str, value: f64) -> Result<()> {
|
async fn set_orca_option(&self, option: &str, value: f64) -> Result<()> {
|
||||||
if let Some(range) = VALID_SETTINGS.get(option) {
|
if let Some(range) = VALID_SETTINGS.get(option) {
|
||||||
ensure!(
|
ensure!(
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue