प्रकाशित सोर्स
सोर्स ZIP डाउनलोड करें
CH-J Tools
किसी विशेष ऐप्लिकेशन रिलीज़ की डायरेक्टरी और फ़ाइलें देखें।
CH-J / Server Manager
1704 फ़ाइलें
0.1.2-beta
जोड़ा गया: · बिल्ड किया गया:
1
//! System entries of the helper, all per user and removable (no admin rights, no system folders):2
//! * macOS: Finder Quick Actions in `~/Library/Services/*.workflow`, login item `~/Library/LaunchAgents/*.plist`.3
//! * Windows: Explorer context menu `HKCU\Software\Classes\*\shell\CHJ.*` (in Windows 11 under "Show more4
//! options"), login item `HKCU\Software\Microsoft\Windows\CurrentVersion\Run\CH-J-Standby-Helper`.5
//! * Linux: Nautilus and Nemo scripts, Dolphin service menu, XDG autostart entry.6
//! Menu entries only run `ch-j-standby-helper verify|sign <file>`; login items run the helper with `run`.7
use crate::settings::{Context, Settings};8
use serde_json::{json, Value};9
use std::path::{Path, PathBuf};11
pub const ACTIONS: [&str; 2] = ["verify", "sign"];12
#[cfg_attr(not(any(target_os = "macos", test)), allow(dead_code))]13
/// LaunchAgent label, also the CFBundleIdentifier of the macOS helper bundle (`tools/mac-signing.mjs` `HELPER_BUNDLE_ID`).14
const AGENT_ID: &str = "de.ch-j.standby-helper";16
#[cfg_attr(not(any(target_os = "macos", target_os = "linux", test)), allow(dead_code))]17
fn write(path: &Path, content: &str, executable: bool) -> Result<(), String> {18
if let Some(parent) = path.parent() {19
std::fs::create_dir_all(parent).map_err(|e| format!("{}: {e}", parent.display()))?;20
}21
if std::fs::read_to_string(path).ok().as_deref() != Some(content) {22
let temp = path.with_extension("chj-tmp");23
std::fs::write(&temp, content).map_err(|e| format!("{}: {e}", path.display()))?;24
std::fs::rename(&temp, path).map_err(|e| format!("{}: {e}", path.display()))?;25
}26
#[cfg(unix)]27
{28
use std::os::unix::fs::PermissionsExt;29
let mode = if executable { 0o755 } else { 0o644 };30
std::fs::set_permissions(path, std::fs::Permissions::from_mode(mode)).map_err(|e| format!("{}: {e}", path.display()))?;31
}32
#[cfg(not(unix))]33
let _ = executable;34
Ok(())35
}37
fn remove(path: &Path) -> Result<(), String> {38
let result = if path.is_dir() { std::fs::remove_dir_all(path) } else { std::fs::remove_file(path) };39
match result {40
Ok(()) => Ok(()),41
Err(e) if e.kind() == std::io::ErrorKind::NotFound => Ok(()),42
Err(e) => Err(format!("{}: {e}", path.display())),43
}44
}46
pub fn xml(text: &str) -> String {47
text.replace('&', "&").replace('<', "<").replace('>', ">").replace('"', """)48
}50
/// POSIX shell single-quoted literal.51
pub fn sh_quote(text: &str) -> String {52
format!("'{}'", text.replace('\'', "'\\''"))53
}55
// ---------- macOS ----------57
#[cfg_attr(not(any(target_os = "macos", test)), allow(dead_code))]58
fn workflow_path(ctx: &Context, action: &str) -> PathBuf {59
// Stable file names; the visible title comes from Info.plist (NSMenuItem), so a language change needs no rename.60
ctx.home.join("Library").join("Services").join(format!("CH-J {}.workflow", if action == "verify" { "Verify" } else { "Sign" }))61
}63
#[cfg_attr(not(any(target_os = "macos", test)), allow(dead_code))]64
fn workflow_files(ctx: &Context, action: &str) -> (String, String) {65
let info = format!(66
r#"<?xml version="1.0" encoding="UTF-8"?>67
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">68
<plist version="1.0">69
<dict>70
<key>NSServices</key>71
<array>72
<dict>73
<key>NSMenuItem</key>74
<dict>75
<key>default</key>76
<string>{label}</string>77
</dict>78
<key>NSMessage</key>79
<string>runWorkflowAsService</string>80
<key>NSRequiredContext</key>81
<dict>82
<key>NSApplicationIdentifier</key>83
<string>com.apple.finder</string>84
</dict>85
<key>NSSendFileTypes</key>86
<array>87
<string>public.item</string>88
</array>89
</dict>90
</array>91
</dict>92
</plist>93
"#,94
label = xml(ctx.label(action))95
);96
// Only the first selected file: the quick window handles one file at a time.97
let command = format!("[ -n \"$1\" ] && exec {} {} \"$1\"", sh_quote(&ctx.helper.to_string_lossy()), action);98
let document = format!(99
r#"<?xml version="1.0" encoding="UTF-8"?>100
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">101
<plist version="1.0">102
<dict>103
<key>AMApplicationBuild</key>104
<string>523</string>105
<key>AMApplicationVersion</key>106
<string>2.10</string>107
<key>AMDocumentVersion</key>108
<string>2</string>109
<key>actions</key>110
<array>111
<dict>112
<key>action</key>113
<dict>114
<key>AMAccepts</key>115
<dict>116
<key>Container</key>117
<string>List</string>118
<key>Optional</key>119
<true/>120
<key>Types</key>121
<array>122
<string>com.apple.cocoa.path</string>123
</array>124
</dict>125
<key>AMActionVersion</key>126
<string>2.0.3</string>127
<key>AMApplication</key>128
<array>129
<string>Automator</string>130
</array>131
<key>AMParameterProperties</key>132
<dict>133
<key>COMMAND_STRING</key>134
<dict/>135
<key>CheckedForUserDefaultShell</key>136
<dict/>137
<key>inputMethod</key>138
<dict/>139
<key>shell</key>140
<dict/>141
<key>source</key>142
<dict/>143
</dict>144
<key>AMProvides</key>145
<dict>146
<key>Container</key>147
<string>List</string>148
<key>Types</key>149
<array>150
<string>com.apple.cocoa.string</string>151
</array>152
</dict>153
<key>ActionBundlePath</key>154
<string>/System/Library/Automator/Run Shell Script.action</string>155
<key>ActionName</key>156
<string>Run Shell Script</string>157
<key>ActionParameters</key>158
<dict>159
<key>COMMAND_STRING</key>160
<string>{command}</string>161
<key>CheckedForUserDefaultShell</key>162
<true/>163
<key>inputMethod</key>164
<integer>1</integer>165
<key>shell</key>166
<string>/bin/sh</string>167
<key>source</key>168
<string></string>169
</dict>170
<key>BundleIdentifier</key>171
<string>com.apple.RunShellScript</string>172
<key>CFBundleVersion</key>173
<string>2.0.3</string>174
<key>CanShowSelectedItemsWhenRun</key>175
<false/>176
<key>CanShowWhenRun</key>177
<true/>178
<key>Category</key>179
<array>180
<string>AMCategoryUtilities</string>181
</array>182
<key>Class Name</key>183
<string>RunShellScriptAction</string>184
<key>InputUUID</key>185
<string>6E1E1D0A-3C5B-4C1E-9C40-{n}0000000001</string>186
<key>Keywords</key>187
<array>188
<string>Shell</string>189
<string>Script</string>190
</array>191
<key>OutputUUID</key>192
<string>6E1E1D0A-3C5B-4C1E-9C40-{n}0000000002</string>193
<key>UUID</key>194
<string>6E1E1D0A-3C5B-4C1E-9C40-{n}0000000003</string>195
<key>UnlocalizedApplications</key>196
<array>197
<string>Automator</string>198
</array>199
<key>arguments</key>200
<dict/>201
<key>isViewVisible</key>202
<integer>1</integer>203
<key>location</key>204
<string>309.000000:253.000000</string>205
<key>nibPath</key>206
<string>/System/Library/Automator/Run Shell Script.action/Contents/Resources/Base.lproj/main.nib</string>207
</dict>208
<key>isViewVisible</key>209
<integer>1</integer>210
</dict>211
</array>212
<key>connectors</key>213
<dict/>214
<key>workflowMetaData</key>215
<dict>216
<key>applicationBundleIDsByPath</key>217
<dict/>218
<key>applicationPaths</key>219
<array/>220
<key>inputTypeIdentifier</key>221
<string>com.apple.Automator.fileSystemObject</string>222
<key>outputTypeIdentifier</key>223
<string>com.apple.Automator.nothing</string>224
<key>presentationMode</key>225
<integer>15</integer>226
<key>processesInput</key>227
<false/>228
<key>serviceInputTypeIdentifier</key>229
<string>com.apple.Automator.fileSystemObject</string>230
<key>serviceOutputTypeIdentifier</key>231
<string>com.apple.Automator.nothing</string>232
<key>serviceProcessesInput</key>233
<false/>234
<key>systemImageName</key>235
<string>NSActionTemplate</string>236
<key>useAutomaticInputType</key>237
<false/>238
<key>workflowTypeIdentifier</key>239
<string>com.apple.Automator.servicesMenu</string>240
</dict>241
</dict>242
</plist>243
"#,244
command = xml(&command),245
n = if action == "verify" { "1" } else { "2" }246
);247
(info, document)248
}250
#[cfg_attr(not(any(target_os = "macos", test)), allow(dead_code))]251
fn agent_path(ctx: &Context) -> PathBuf {252
ctx.home.join("Library").join("LaunchAgents").join(format!("{AGENT_ID}.plist"))253
}255
#[cfg_attr(not(any(target_os = "macos", test)), allow(dead_code))]256
fn agent_plist(ctx: &Context) -> String {257
format!(258
r#"<?xml version="1.0" encoding="UTF-8"?>259
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">260
<plist version="1.0">261
<dict>262
<key>Label</key>263
<string>{AGENT_ID}</string>264
<key>AssociatedBundleIdentifiers</key>265
<string>{AGENT_ID}</string>266
<key>ProgramArguments</key>267
<array>268
<string>{helper}</string>269
<string>run</string>270
</array>271
<key>RunAtLoad</key>272
<true/>273
<key>KeepAlive</key>274
<false/>275
<key>ProcessType</key>276
<string>Background</string>277
<key>LimitLoadToSessionType</key>278
<string>Aqua</string>279
</dict>280
</plist>281
"#,282
helper = xml(&ctx.helper.to_string_lossy())283
)284
}286
/// The LaunchAgent as it is on disk now (to see whether `apply` changed it).287
#[cfg(target_os = "macos")]288
pub fn agent_text(ctx: &Context) -> Option<String> {289
std::fs::read_to_string(agent_path(ctx)).ok()290
}292
/// Lets launchd own the background process, so System Settings → Login Items lists it as running (a process the293
/// app starts itself shows as "not running"). Loads the agent when it is wanted and not loaded, reloads it when294
/// `apply` changed it (e.g. new helper path after an update), unloads it otherwise. Returns true when launchd now295
/// runs the helper. `before_bootstrap` runs right before loading (stops a helper the app started directly).296
#[cfg(target_os = "macos")]297
pub fn launchd_sync(ctx: &Context, settings: &Settings, changed: bool, running: bool, before_bootstrap: impl FnOnce()) -> bool {298
use std::os::unix::fs::MetadataExt;299
let Ok(uid) = std::fs::metadata(&ctx.home).map(|m| m.uid()) else { return false };300
let launchctl = |args: &[&str]| {301
std::process::Command::new("/bin/launchctl").args(args).stdin(std::process::Stdio::null()).stdout(std::process::Stdio::null()).stderr(std::process::Stdio::null()).status().is_ok_and(|s| s.success())302
};303
let (domain, service) = (format!("gui/{uid}"), format!("gui/{uid}/{AGENT_ID}"));304
let mut loaded = launchctl(&["print", &service]);305
if settings.wants_login() {306
if loaded && changed {307
launchctl(&["bootout", &service]);308
loaded = false;309
}310
if !loaded {311
before_bootstrap();312
loaded = launchctl(&["bootstrap", &domain, &agent_path(ctx).to_string_lossy()]);313
} else if !running {314
// Registered but not running (crashed or stopped; KeepAlive is off): start it again.315
return launchctl(&["kickstart", &service]);316
}317
loaded318
} else {319
if loaded {320
launchctl(&["bootout", &service]);321
}322
false323
}324
}326
// ---------- Linux ----------328
#[cfg_attr(not(any(target_os = "linux", test)), allow(dead_code))]329
fn data_home(ctx: &Context) -> PathBuf {330
std::env::var_os("XDG_DATA_HOME").map(PathBuf::from).filter(|p| p.is_absolute()).unwrap_or_else(|| ctx.home.join(".local").join("share"))331
}333
#[cfg_attr(not(any(target_os = "linux", test)), allow(dead_code))]334
fn linux_script_paths(ctx: &Context, action: &str) -> Vec<PathBuf> {335
let name = ctx.label(action);336
vec![data_home(ctx).join("nautilus").join("scripts").join(name), data_home(ctx).join("nemo").join("scripts").join(name)]337
}339
#[cfg_attr(not(any(target_os = "linux", test)), allow(dead_code))]340
fn linux_script(ctx: &Context, action: &str) -> String {341
// Nautilus/Nemo pass the selected names relative to the current folder; the helper resolves them.342
format!("#!/bin/sh\n# CH-J Standby Helper\n[ -n \"$1\" ] && exec {} {} \"$1\"\n", sh_quote(&ctx.helper.to_string_lossy()), action)343
}345
#[cfg_attr(not(any(target_os = "linux", test)), allow(dead_code))]346
fn dolphin_path(ctx: &Context) -> PathBuf {347
data_home(ctx).join("kio").join("servicemenus").join("ch-j-standby-helper.desktop")348
}350
/// Desktop Entry `Exec` argument quoting.351
fn exec_quote(text: &str) -> String {352
format!("\"{}\"", text.replace('\\', "\\\\").replace('"', "\\\"").replace('`', "\\`").replace('$', "\\$"))353
}355
#[cfg_attr(not(any(target_os = "linux", test)), allow(dead_code))]356
fn dolphin_menu(ctx: &Context, settings: &Settings) -> String {357
let actions: Vec<&str> = ACTIONS.iter().copied().filter(|a| if *a == "verify" { settings.wants_verify() } else { settings.wants_sign() }).collect();358
let mut out = format!(359
"[Desktop Entry]\nType=Service\nMimeType=application/octet-stream;\nX-KDE-ServiceTypes=KonqPopupMenu/Plugin\nActions={};\n",360
actions.iter().map(|a| format!("chj_{a}")).collect::<Vec<_>>().join(";")361
);362
for action in actions {363
out.push_str(&format!("\n[Desktop Action chj_{action}]\nName={}\nIcon=ch-j-tools\nExec={} {action} %f\n", ctx.label(action), exec_quote(&ctx.helper.to_string_lossy())));364
}365
out366
}368
#[cfg_attr(not(any(target_os = "linux", test)), allow(dead_code))]369
fn autostart_path(ctx: &Context) -> PathBuf {370
let config = std::env::var_os("XDG_CONFIG_HOME").map(PathBuf::from).filter(|p| p.is_absolute()).unwrap_or_else(|| ctx.home.join(".config"));371
config.join("autostart").join("ch-j-standby-helper.desktop")372
}374
#[cfg_attr(not(any(target_os = "linux", test)), allow(dead_code))]375
fn autostart_entry(ctx: &Context) -> String {376
format!(377
"[Desktop Entry]\nType=Application\nName=CH-J-Standby-Helper\nExec={} run\nIcon=ch-j-tools\nNoDisplay=true\nTerminal=false\nX-GNOME-Autostart-enabled=true\n",378
exec_quote(&ctx.helper.to_string_lossy())379
)380
}382
// ---------- Windows ----------384
#[cfg(windows)]385
const WINDOWS_RUN_KEY: &str = r"HKCU\Software\Microsoft\Windows\CurrentVersion\Run";386
#[cfg(windows)]387
const WINDOWS_RUN_VALUE: &str = "CH-J-Standby-Helper";389
#[cfg_attr(not(any(windows, test)), allow(dead_code))]390
fn windows_login_command(ctx: &Context) -> String {391
format!("\"{}\" run", ctx.helper.display())392
}394
#[cfg(windows)]395
fn reg(args: &[&str]) -> Result<bool, String> {396
use std::os::windows::process::CommandExt;397
let status = std::process::Command::new("reg.exe")398
.args(args)399
.creation_flags(0x0800_0000 /* CREATE_NO_WINDOW */)400
.stdout(std::process::Stdio::null())401
.stderr(std::process::Stdio::null())402
.status()403
.map_err(|e| format!("reg.exe: {e}"))?;404
Ok(status.success())405
}407
#[cfg(windows)]408
fn windows_key(action: &str) -> String {409
format!(r"HKCU\Software\Classes\*\shell\CHJ.{}", if action == "verify" { "Verify" } else { "Sign" })410
}412
// ---------- shared ----------414
/// Brings every entry in line with `settings` (writes wanted ones, removes the others).415
pub fn apply(ctx: &Context, settings: &Settings) -> Result<(), String> {416
let mut errors = Vec::new();417
let mut note = |r: Result<(), String>| {418
if let Err(e) = r {419
errors.push(e)420
}421
};422
for action in ACTIONS {423
let wanted = if action == "verify" { settings.wants_verify() } else { settings.wants_sign() };424
#[cfg(target_os = "macos")]425
{426
let dir = workflow_path(ctx, action);427
if wanted {428
let (info, document) = workflow_files(ctx, action);429
note(write(&dir.join("Contents").join("Info.plist"), &info, false));430
note(write(&dir.join("Contents").join("document.wflow"), &document, false));431
} else {432
note(remove(&dir));433
}434
}435
#[cfg(target_os = "linux")]436
for path in linux_script_paths(ctx, action) {437
// Remove the other languages' names too, so a language change leaves no stale entry.438
if let Some(dir) = path.parent() {439
for other in ["cs", "de", "en"] {440
let label = Context { language: other, home: ctx.home.clone(), helper: ctx.helper.clone(), app: ctx.app.clone() }.label(action);441
if label != ctx.label(action) {442
note(remove(&dir.join(label)));443
}444
}445
}446
note(if wanted { write(&path, &linux_script(ctx, action), true) } else { remove(&path) });447
}448
#[cfg(windows)]449
{450
let key = windows_key(action);451
if wanted {452
let command = format!("\"{}\" {action} \"%1\"", ctx.helper.display());453
let icon = format!("\"{}\",0", ctx.app.display());454
let ok = reg(&["add", &key, "/v", "MUIVerb", "/d", ctx.label(action), "/f"])?455
&& reg(&["add", &key, "/v", "Icon", "/d", &icon, "/f"])?456
&& reg(&["add", &format!(r"{key}\command"), "/ve", "/d", &command, "/f"])?;457
if !ok {458
errors.push(format!("{key}: reg add failed"));459
}460
} else {461
let _ = reg(&["delete", &key, "/f"]);462
}463
}464
#[cfg(not(any(target_os = "macos", target_os = "linux", windows)))]465
let _ = wanted;466
}467
#[cfg(target_os = "macos")]468
note(if settings.wants_login() { write(&agent_path(ctx), &agent_plist(ctx), false) } else { remove(&agent_path(ctx)) });469
#[cfg(target_os = "linux")]470
{471
let wanted = settings.wants_verify() || settings.wants_sign();472
note(if wanted { write(&dolphin_path(ctx), &dolphin_menu(ctx, settings), true) } else { remove(&dolphin_path(ctx)) });473
note(if settings.wants_login() { write(&autostart_path(ctx), &autostart_entry(ctx), false) } else { remove(&autostart_path(ctx)) });474
}475
#[cfg(target_os = "macos")]476
refresh_services();477
#[cfg(windows)]478
if settings.wants_login() {479
if !reg(&["add", WINDOWS_RUN_KEY, "/v", WINDOWS_RUN_VALUE, "/t", "REG_SZ", "/d", &windows_login_command(ctx), "/f"])? {480
errors.push("standby helper login: reg add failed".into());481
}482
} else {483
// Missing values are normal, including on first install and repeated removal.484
let _ = reg(&["delete", WINDOWS_RUN_KEY, "/v", WINDOWS_RUN_VALUE, "/f"]);485
}486
if errors.is_empty() {487
Ok(())488
} else {489
Err(errors.join("; "))490
}491
}493
/// Finder reads new Quick Actions only after the services cache is rebuilt.494
#[cfg(target_os = "macos")]495
fn refresh_services() {496
let _ = std::process::Command::new("/System/Library/CoreServices/pbs").arg("-update").stdout(std::process::Stdio::null()).stderr(std::process::Stdio::null()).status();497
}499
pub fn remove_all(ctx: &Context) -> Result<(), String> {500
apply(ctx, &Settings { enabled: false, ..Settings::default() })501
}503
/// Which entries exist now (read back from the system, not from the settings).504
pub fn installed(ctx: &Context) -> Value {505
let _ = ctx;506
#[cfg(target_os = "macos")]507
return json!({508
"kind": "finder-quick-actions",509
"verify": workflow_path(ctx, "verify").join("Contents/document.wflow").is_file(),510
"sign": workflow_path(ctx, "sign").join("Contents/document.wflow").is_file(),511
"login": agent_path(ctx).is_file(),512
});513
#[cfg(target_os = "linux")]514
return json!({515
"kind": "nautilus-nemo-dolphin",516
"verify": linux_script_paths(ctx, "verify").iter().any(|p| p.is_file()),517
"sign": linux_script_paths(ctx, "sign").iter().any(|p| p.is_file()),518
"dolphin": dolphin_path(ctx).is_file(),519
"login": autostart_path(ctx).is_file(),520
});521
#[cfg(windows)]522
return json!({523
"kind": "explorer-context-menu",524
"verify": reg(&["query", &windows_key("verify")]).unwrap_or(false),525
"sign": reg(&["query", &windows_key("sign")]).unwrap_or(false),526
"login": reg(&["query", WINDOWS_RUN_KEY, "/v", WINDOWS_RUN_VALUE]).unwrap_or(false),527
});528
#[cfg(not(any(target_os = "macos", target_os = "linux", windows)))]529
return json!({"kind": "unsupported"});530
}532
#[cfg(test)]533
mod tests {534
use super::*;536
fn ctx(home: &Path, helper: &str) -> Context {537
Context { home: home.to_path_buf(), helper: PathBuf::from(helper), app: PathBuf::from("/Applications/CH-J Tools.app/Contents/MacOS/ch-j-tools"), language: "cs" }538
}540
#[test]541
fn quoting_keeps_spaces_and_quotes_literal() {542
assert_eq!(sh_quote("/A B/it's"), r"'/A B/it'\''s'");543
assert_eq!(exec_quote("/A B/\"x\"$y"), "\"/A B/\\\"x\\\"\\$y\"");544
assert_eq!(xml("a<b>&\""), "a<b>&"");545
}547
#[test]548
fn windows_login_quotes_the_helper_path_before_the_run_argument() {549
let c = ctx(Path::new("unused"), r"C:\Program Files\CH-J Tools\ch-j-standby-helper.exe");550
assert_eq!(windows_login_command(&c), r#""C:\Program Files\CH-J Tools\ch-j-standby-helper.exe" run"#);551
}553
#[test]554
fn generated_entries_only_call_the_helper_with_the_selected_file() {555
let home = std::env::temp_dir().join(format!("chj-standby-gen-{}", std::process::id()));556
let c = ctx(&home, "/Applications/CH-J Tools.app/Contents/MacOS/ch-j-standby-helper");557
let (info, document) = workflow_files(&c, "sign");558
assert!(info.contains("<string>CH-J – Podepsat (OpenPGP)</string>") && info.contains("public.item"));559
assert!(document.contains("exec '/Applications/CH-J Tools.app/Contents/MacOS/ch-j-standby-helper' sign"));560
assert!(document.contains("com.apple.Automator.servicesMenu"));561
assert!(agent_plist(&c).contains("<string>run</string>"));562
// Login items show the helper bundle's name ("CH-J-Standby-Helper"), not the certificate's developer name.563
assert!(agent_plist(&c).contains("<key>AssociatedBundleIdentifiers</key>\n\t<string>de.ch-j.standby-helper</string>"));564
assert!(autostart_entry(&ctx(&home, "/usr/bin/ch-j-standby-helper")).contains("\nName=CH-J-Standby-Helper\n"));565
let script = linux_script(&ctx(&home, "/usr/bin/ch-j-standby-helper"), "verify");566
assert!(script.starts_with("#!/bin/sh\n") && script.contains("exec '/usr/bin/ch-j-standby-helper' verify \"$1\""));567
let only_verify = Settings { enabled: true, start_at_login: false, menu_verify: true, menu_sign: false };568
let menu = dolphin_menu(&ctx(&home, "/usr/bin/ch-j-standby-helper"), &only_verify);569
assert!(menu.contains("Actions=chj_verify;") && !menu.contains("chj_sign") && menu.contains("Exec=\"/usr/bin/ch-j-standby-helper\" verify %f"));570
assert!(autostart_entry(&ctx(&home, "/usr/bin/ch-j-standby-helper")).contains("Exec=\"/usr/bin/ch-j-standby-helper\" run"));571
}573
#[cfg(any(target_os = "macos", target_os = "linux"))]574
#[test]575
fn apply_writes_wanted_entries_and_removes_the_rest() {576
let home = std::env::temp_dir().join(format!("chj-standby-apply-{}", std::process::id()));577
let _ = std::fs::remove_dir_all(&home);578
let c = ctx(&home, "/opt/chj/ch-j-standby-helper");579
let all = Settings { enabled: true, start_at_login: true, menu_verify: true, menu_sign: true };580
apply(&c, &all).unwrap();581
let state = installed(&c);582
assert_eq!((state["verify"].as_bool(), state["sign"].as_bool(), state["login"].as_bool()), (Some(true), Some(true), Some(true)), "{state}");583
apply(&c, &Settings { menu_sign: false, start_at_login: false, ..all.clone() }).unwrap();584
let state = installed(&c);585
assert_eq!((state["verify"].as_bool(), state["sign"].as_bool(), state["login"].as_bool()), (Some(true), Some(false), Some(false)), "{state}");586
remove_all(&c).unwrap();587
let state = installed(&c);588
assert_eq!((state["verify"].as_bool(), state["sign"].as_bool()), (Some(false), Some(false)));589
std::fs::remove_dir_all(&home).unwrap();590
}591
}SHA-256
be6c9267103f0fabf29195a92aa47bb9436e8827e1cb0f49f8e2099e28a29d57CH-J Proprietary Software License 1.14 · सोर्स CH-J Proprietary Software License 1.14 के तहत उपलब्ध कराया जाता है। इसकी उपलब्धता लाइसेंस की शर्तें नहीं बदलती और न ही अतिरिक्त अधिकार देती है।
आर्काइव SHA-256
ac8fe790f4ba60fc098f8c985fc2656cfcc6cdc21fb34b30e308a2b54e62258f