Recover Rime faults, persist window modes and fix GPU allocation
This commit is contained in:
1 parent
8e9a5838ab
commit
6f34fd080f
27 files changed
+1260
-34
No files matched your search
@@ -0,0 +1,185 @@
|
||||
//! Window-local mode/schema preferences, with application defaults for reopening.
|
||||
//! Persist preferences only: never composition, surrounding text or window titles.
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::BTreeMap;
|
||||
use std::io::Write;
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
|
||||
pub struct Profile {
|
||||
pub schema: String,
|
||||
pub ascii: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
pub struct Context {
|
||||
pub session: String,
|
||||
pub window: u64,
|
||||
pub app_id: String,
|
||||
}
|
||||
impl Context {
|
||||
fn key(&self) -> String {
|
||||
format!("{}:{}", self.session, self.window)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Deserialize, Serialize)]
|
||||
struct Stored {
|
||||
#[serde(default)]
|
||||
applications: BTreeMap<String, Profile>,
|
||||
#[serde(default)]
|
||||
windows: BTreeMap<String, Profile>,
|
||||
}
|
||||
|
||||
pub struct Profiles {
|
||||
path: PathBuf,
|
||||
default: Profile,
|
||||
stored: Stored,
|
||||
dirty: bool,
|
||||
pub context: Option<Context>,
|
||||
pub current: Profile,
|
||||
}
|
||||
impl Profiles {
|
||||
pub fn open(path: &Path, schema: String) -> Result<Self, String> {
|
||||
let stored = match std::fs::read(path) {
|
||||
Ok(bytes) => serde_json::from_slice(&bytes)
|
||||
.map_err(|e| format!("invalid IME preferences: {e}"))?,
|
||||
Err(e) if e.kind() == std::io::ErrorKind::NotFound => Stored::default(),
|
||||
Err(e) => return Err(e.to_string()),
|
||||
};
|
||||
let default = Profile {
|
||||
schema,
|
||||
ascii: false,
|
||||
};
|
||||
Ok(Self {
|
||||
path: path.to_owned(),
|
||||
stored,
|
||||
dirty: false,
|
||||
context: None,
|
||||
current: default.clone(),
|
||||
default,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn focus(&mut self, context: Option<Context>) -> Profile {
|
||||
if let Some(context) = &context {
|
||||
let prefix = format!("{}:", context.session);
|
||||
self.stored
|
||||
.windows
|
||||
.retain(|key, _| key.starts_with(&prefix));
|
||||
}
|
||||
let profile = context
|
||||
.as_ref()
|
||||
.and_then(|c| {
|
||||
self.stored
|
||||
.windows
|
||||
.get(&c.key())
|
||||
.or_else(|| self.stored.applications.get(&c.app_id))
|
||||
})
|
||||
.unwrap_or(&self.default)
|
||||
.clone();
|
||||
self.context = context;
|
||||
self.current = profile.clone();
|
||||
// Pin even the initial default. A second window of the same application
|
||||
// can change its mode without changing this already-open window.
|
||||
if let Some(context) = &self.context {
|
||||
self.dirty |= !self.stored.windows.contains_key(&context.key());
|
||||
self.stored
|
||||
.windows
|
||||
.entry(context.key())
|
||||
.or_insert_with(|| profile.clone());
|
||||
}
|
||||
profile
|
||||
}
|
||||
|
||||
pub fn remember(&mut self, profile: Profile) -> Result<(), String> {
|
||||
if self.current == profile {
|
||||
return self.flush();
|
||||
}
|
||||
self.current = profile.clone();
|
||||
let Some(context) = &self.context else {
|
||||
return Ok(());
|
||||
};
|
||||
self.stored.windows.insert(context.key(), profile.clone());
|
||||
if !context.app_id.is_empty() {
|
||||
self.stored
|
||||
.applications
|
||||
.insert(context.app_id.clone(), profile);
|
||||
}
|
||||
self.dirty = true;
|
||||
self.flush()
|
||||
}
|
||||
|
||||
pub fn flush(&mut self) -> Result<(), String> {
|
||||
if self.dirty {
|
||||
self.save()?;
|
||||
self.dirty = false;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn save(&self) -> Result<(), String> {
|
||||
let parent = self.path.parent().ok_or("preferences path has no parent")?;
|
||||
std::fs::create_dir_all(parent).map_err(|e| e.to_string())?;
|
||||
let mut temp = tempfile::NamedTempFile::new_in(parent).map_err(|e| e.to_string())?;
|
||||
temp.as_file()
|
||||
.set_permissions(std::fs::Permissions::from_mode(0o600))
|
||||
.map_err(|e| e.to_string())?;
|
||||
serde_json::to_writer_pretty(&mut temp, &self.stored).map_err(|e| e.to_string())?;
|
||||
temp.write_all(b"\n")
|
||||
.and_then(|()| temp.as_file().sync_all())
|
||||
.map_err(|e| e.to_string())?;
|
||||
temp.persist(&self.path).map_err(|e| e.to_string())?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
fn context(window: u64, app: &str) -> Option<Context> {
|
||||
Some(Context {
|
||||
session: "one".into(),
|
||||
window,
|
||||
app_id: app.into(),
|
||||
})
|
||||
}
|
||||
#[test]
|
||||
fn isolates_windows_restores_closed_apps_and_survives_service_restart() {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
let path = dir.path().join("preferences.json");
|
||||
let rime = Profile {
|
||||
schema: "luna_pinyin_simp".into(),
|
||||
ascii: false,
|
||||
};
|
||||
let english = Profile {
|
||||
ascii: true,
|
||||
..rime.clone()
|
||||
};
|
||||
let other = Profile {
|
||||
schema: "luna_pinyin".into(),
|
||||
ascii: false,
|
||||
};
|
||||
let mut p = Profiles::open(&path, rime.schema.clone()).unwrap();
|
||||
assert_eq!(p.focus(context(1, "QQ")), rime);
|
||||
p.remember(english.clone()).unwrap();
|
||||
assert_eq!(p.focus(context(2, "QQ")), english);
|
||||
p.remember(other.clone()).unwrap();
|
||||
assert_eq!(p.focus(context(1, "QQ")), english);
|
||||
assert_eq!(p.focus(context(3, "teams")), rime);
|
||||
drop(p);
|
||||
let mut p = Profiles::open(&path, rime.schema.clone()).unwrap();
|
||||
assert_eq!(p.focus(context(1, "QQ")), english);
|
||||
assert_eq!(p.focus(context(2, "QQ")), other);
|
||||
assert_eq!(p.focus(context(4, "QQ")), other);
|
||||
let mut fresh = context(1, "QQ").unwrap();
|
||||
fresh.session = "two".into();
|
||||
assert_eq!(p.focus(Some(fresh)), other);
|
||||
assert_eq!(p.focus(None), rime);
|
||||
assert_eq!(
|
||||
std::fs::metadata(path).unwrap().permissions().mode() & 0o777,
|
||||
0o600
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user