extras: add PowerShell greeting port, Rust gm-switch and Windows installers
- powershell_greeting: PowerShell port of fish_greeting (fastfetch random logo, sixel/kitty-direct per terminal, logs, modes) - win-gm-switch: Rust port of gm-switch.cpp (cross-platform mode file, --status, Start Menu shortcuts installer) - install-windows.ps1 umbrella installer; README pointers
This commit is contained in:
@@ -0,0 +1 @@
|
||||
/target
|
||||
Generated
+7
@@ -0,0 +1,7 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "gm-switch"
|
||||
version = "0.1.0"
|
||||
@@ -0,0 +1,10 @@
|
||||
[package]
|
||||
name = "gm-switch"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
description = "Set the J621 shell greeting rating mode (NSFW / SFW / Questionable)"
|
||||
publish = false
|
||||
|
||||
[profile.release]
|
||||
strip = true
|
||||
lto = true
|
||||
@@ -0,0 +1,54 @@
|
||||
# win-gm-switch (Rust)
|
||||
|
||||
Rust port of the old `gm-switch.cpp` from
|
||||
`J621-Django/extras/fish_greeting system/`: a tiny CLI that sets the J621
|
||||
shell greeting rating mode (0 = NSFW, 1 = SFW, 2 = Questionable).
|
||||
|
||||
## Usage
|
||||
|
||||
```
|
||||
gm-switch --mode 0 # NSFW (explicit)
|
||||
gm-switch --mode 1 # SFW (safe)
|
||||
gm-switch --mode 2 # Questionable
|
||||
gm-switch --status # print the current mode
|
||||
```
|
||||
|
||||
`--path <file>` overrides the mode file (also via the `J621_MODE_FILE`
|
||||
environment variable). Invalid input prints the same usage/errors as the C++
|
||||
tool and exits 1.
|
||||
|
||||
Mode file locations:
|
||||
|
||||
- **Windows**: `%APPDATA%\j621Greeting\greeting_mode` - read by
|
||||
[`../powershell_greeting/`](../powershell_greeting/).
|
||||
- **Linux/macOS**: `~/.config/fish/greeting_mode` - read by
|
||||
[`../fish_greeting/`](../fish_greeting/), so this binary can stand in for
|
||||
the C++ tool there.
|
||||
|
||||
## Install (Windows)
|
||||
|
||||
```powershell
|
||||
cd extras\win-gm-switch
|
||||
.\install.ps1
|
||||
```
|
||||
|
||||
The installer builds with `cargo build --release`, installs `gm-switch.exe`
|
||||
to `%LOCALAPPDATA%\J621\bin`, adds that folder to your user PATH (new
|
||||
terminals pick it up) and creates Start Menu shortcuts under **J621
|
||||
Greeting** (NSFW / SFW / Questionable). Each shortcut opens a small console
|
||||
showing the confirmation; close it with `exit` or the window button.
|
||||
|
||||
Uninstall:
|
||||
|
||||
```powershell
|
||||
.\install.ps1 -Uninstall
|
||||
```
|
||||
|
||||
## Build and test without installing
|
||||
|
||||
```powershell
|
||||
cargo build --release # -> target\release\gm-switch.exe
|
||||
cargo test
|
||||
```
|
||||
|
||||
Zero runtime dependencies (std only). The `target\` folder is gitignored.
|
||||
@@ -0,0 +1,105 @@
|
||||
# Build and install gm-switch:
|
||||
# * cargo build --release (needs the Rust toolchain on PATH)
|
||||
# * copies gm-switch.exe to %LOCALAPPDATA%\J621\bin
|
||||
# * adds that folder to your user PATH (if missing)
|
||||
# * creates Start Menu shortcuts for each mode under "J621 Greeting"
|
||||
#
|
||||
# Usage:
|
||||
# .\install.ps1
|
||||
# .\install.ps1 -Uninstall
|
||||
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[switch]$Uninstall
|
||||
)
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
$here = $PSScriptRoot
|
||||
if (-not $here) { $here = Split-Path -Parent $MyInvocation.MyCommand.Path }
|
||||
$binDir = Join-Path $env:LOCALAPPDATA 'J621\bin'
|
||||
$exe = Join-Path $binDir 'gm-switch.exe'
|
||||
$startMenuDir = Join-Path $env:APPDATA 'Microsoft\Windows\Start Menu\Programs\J621 Greeting'
|
||||
|
||||
if ($Uninstall) {
|
||||
if (Test-Path -LiteralPath $startMenuDir) {
|
||||
Remove-Item -LiteralPath $startMenuDir -Recurse -Force
|
||||
Write-Host "Removed $startMenuDir"
|
||||
}
|
||||
if (Test-Path -LiteralPath $exe) {
|
||||
Remove-Item -LiteralPath $exe -Force
|
||||
Write-Host "Removed $exe"
|
||||
}
|
||||
$userPath = [Environment]::GetEnvironmentVariable('Path', 'User')
|
||||
if ($userPath) {
|
||||
$parts = @($userPath -split ';' | Where-Object { $_ -and $_ -ne $binDir })
|
||||
[Environment]::SetEnvironmentVariable('Path', ($parts -join ';'), 'User')
|
||||
Write-Host "Removed $binDir from your user PATH"
|
||||
}
|
||||
exit 0
|
||||
}
|
||||
|
||||
# --- Build ---------------------------------------------------------------------
|
||||
if (-not (Get-Command cargo -ErrorAction SilentlyContinue)) {
|
||||
Write-Host 'cargo was not found on PATH - install the Rust toolchain (https://rustup.rs) first.'
|
||||
exit 1
|
||||
}
|
||||
Push-Location $here
|
||||
try {
|
||||
Write-Host 'Building gm-switch (cargo build --release) ...'
|
||||
# cargo writes progress to stderr; keep that from tripping $ErrorActionPreference.
|
||||
$previousPreference = $ErrorActionPreference
|
||||
$ErrorActionPreference = 'Continue'
|
||||
cargo build --release 2>&1 | Write-Host
|
||||
$buildExit = $LASTEXITCODE
|
||||
$ErrorActionPreference = $previousPreference
|
||||
if ($buildExit -ne 0) {
|
||||
Write-Host 'cargo build failed.'
|
||||
exit 1
|
||||
}
|
||||
} finally {
|
||||
Pop-Location
|
||||
}
|
||||
|
||||
$built = Join-Path $here 'target\release\gm-switch.exe'
|
||||
if (-not (Test-Path -LiteralPath $built)) {
|
||||
Write-Host "Build output not found: $built"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# --- Install -------------------------------------------------------------------
|
||||
New-Item -ItemType Directory -Path $binDir -Force | Out-Null
|
||||
Copy-Item -LiteralPath $built -Destination $exe -Force
|
||||
Write-Host "Installed $exe"
|
||||
|
||||
$userPath = [Environment]::GetEnvironmentVariable('Path', 'User')
|
||||
if (-not $userPath) { $userPath = '' }
|
||||
if (-not (($userPath -split ';') -contains $binDir)) {
|
||||
$newPath = ($userPath.TrimEnd(';') + ';' + $binDir).TrimStart(';')
|
||||
[Environment]::SetEnvironmentVariable('Path', $newPath, 'User')
|
||||
Write-Host "Added $binDir to your user PATH (open a new terminal to pick it up)"
|
||||
}
|
||||
|
||||
# --- Shortcuts ------------------------------------------------------------------
|
||||
New-Item -ItemType Directory -Path $startMenuDir -Force | Out-Null
|
||||
$modes = @(
|
||||
@{ Value = '0'; Name = 'NSFW' },
|
||||
@{ Value = '1'; Name = 'SFW' },
|
||||
@{ Value = '2'; Name = 'Questionable' }
|
||||
)
|
||||
$shell = New-Object -ComObject WScript.Shell
|
||||
foreach ($m in $modes) {
|
||||
$lnkPath = Join-Path $startMenuDir ('J621 Greeting - ' + $m.Name + '.lnk')
|
||||
$shortcut = $shell.CreateShortcut($lnkPath)
|
||||
$shortcut.TargetPath = "$env:SystemRoot\System32\cmd.exe"
|
||||
$shortcut.Arguments = '/k ""' + $exe + '" --mode ' + $m.Value + '"'
|
||||
$shortcut.WorkingDirectory = $env:USERPROFILE
|
||||
$shortcut.Description = 'Set the J621 shell greeting to ' + $m.Name + ' mode'
|
||||
$shortcut.Save()
|
||||
Write-Host ('Created shortcut: ' + $lnkPath)
|
||||
}
|
||||
|
||||
Write-Host ''
|
||||
Write-Host 'Done. Try: gm-switch --status'
|
||||
Write-Host 'The Start Menu shortcuts open a small console showing the confirmation.'
|
||||
exit 0
|
||||
@@ -0,0 +1,188 @@
|
||||
//! gm-switch - set the J621 shell greeting rating mode.
|
||||
//!
|
||||
//! Rust port of the old gm-switch.cpp (J621-Django/extras/fish_greeting
|
||||
//! system). On Windows it writes the mode file used by the PowerShell
|
||||
//! greeting (%APPDATA%\j621Greeting\greeting_mode); on Linux/macOS it writes
|
||||
//! ~/.config/fish/greeting_mode, so it can stand in for the C++ tool there.
|
||||
//!
|
||||
//! Usage:
|
||||
//! gm-switch --mode <0|1|2> [--path <file>]
|
||||
//! gm-switch --status [--path <file>]
|
||||
|
||||
use std::env;
|
||||
use std::fs;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::exit;
|
||||
|
||||
const USAGE: &str = "\
|
||||
Usage: gm-switch --mode <0|1|2>
|
||||
--mode 0 Set to NSFW mode (explicit)
|
||||
--mode 1 Set to SFW mode (safe)
|
||||
--mode 2 Set to Questionable mode
|
||||
--status Print the current greeting mode
|
||||
--path <file> Override the mode file location
|
||||
";
|
||||
|
||||
fn mode_name(mode: &str) -> Option<&'static str> {
|
||||
match mode {
|
||||
"0" => Some("NSFW"),
|
||||
"1" => Some("SFW"),
|
||||
"2" => Some("Questionable"),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn default_mode_file() -> PathBuf {
|
||||
if let Ok(path) = env::var("J621_MODE_FILE") {
|
||||
if !path.is_empty() {
|
||||
return PathBuf::from(path);
|
||||
}
|
||||
}
|
||||
#[cfg(windows)]
|
||||
{
|
||||
if let Ok(appdata) = env::var("APPDATA") {
|
||||
return Path::new(&appdata).join("j621Greeting").join("greeting_mode");
|
||||
}
|
||||
}
|
||||
#[cfg(not(windows))]
|
||||
{
|
||||
if let Ok(home) = env::var("HOME") {
|
||||
return Path::new(&home)
|
||||
.join(".config")
|
||||
.join("fish")
|
||||
.join("greeting_mode");
|
||||
}
|
||||
}
|
||||
PathBuf::from("greeting_mode")
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let args: Vec<String> = env::args().skip(1).collect();
|
||||
|
||||
let mut mode: Option<String> = None;
|
||||
let mut status = false;
|
||||
let mut path_override: Option<String> = None;
|
||||
|
||||
let mut i = 0;
|
||||
while i < args.len() {
|
||||
match args[i].as_str() {
|
||||
"--mode" => {
|
||||
if i + 1 < args.len() {
|
||||
mode = Some(args[i + 1].clone());
|
||||
i += 2;
|
||||
} else {
|
||||
eprintln!("Error: --mode requires a value");
|
||||
print!("{USAGE}");
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
"--status" => {
|
||||
status = true;
|
||||
i += 1;
|
||||
}
|
||||
"--path" => {
|
||||
if i + 1 < args.len() {
|
||||
path_override = Some(args[i + 1].clone());
|
||||
i += 2;
|
||||
} else {
|
||||
eprintln!("Error: --path requires a value");
|
||||
print!("{USAGE}");
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
"-h" | "--help" => {
|
||||
print!("{USAGE}");
|
||||
exit(0);
|
||||
}
|
||||
other => {
|
||||
eprintln!("Error: unknown argument: {other}");
|
||||
print!("{USAGE}");
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mode_file = match &path_override {
|
||||
Some(p) => PathBuf::from(p),
|
||||
None => default_mode_file(),
|
||||
};
|
||||
|
||||
if status {
|
||||
match fs::read_to_string(&mode_file) {
|
||||
Ok(content) => {
|
||||
let value = content.trim().to_string();
|
||||
match mode_name(&value) {
|
||||
Some(name) => println!("Current greeting mode: {name} ({value})"),
|
||||
None => println!(
|
||||
"Current greeting mode: {value} (unrecognized - the greeting falls back to NSFW)"
|
||||
),
|
||||
}
|
||||
}
|
||||
Err(_) => {
|
||||
println!("Current greeting mode: 2 (Questionable, default - no mode file yet)")
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
let value = match mode {
|
||||
Some(v) => v,
|
||||
None => {
|
||||
print!("{USAGE}");
|
||||
exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
let name = match mode_name(&value) {
|
||||
Some(name) => name,
|
||||
None => {
|
||||
eprintln!("Error: Mode must be 0, 1, or 2");
|
||||
print!("{USAGE}");
|
||||
exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(parent) = mode_file.parent() {
|
||||
if !parent.as_os_str().is_empty() {
|
||||
if let Err(err) = fs::create_dir_all(parent) {
|
||||
eprintln!("Error: {err}");
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Err(err) = fs::write(&mode_file, &value) {
|
||||
eprintln!(
|
||||
"Error: Could not open file for writing: {}",
|
||||
mode_file.display()
|
||||
);
|
||||
eprintln!("Error: {err}");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
println!("Successfully set greeting mode to: {name}");
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn maps_modes_to_names() {
|
||||
assert_eq!(mode_name("0"), Some("NSFW"));
|
||||
assert_eq!(mode_name("1"), Some("SFW"));
|
||||
assert_eq!(mode_name("2"), Some("Questionable"));
|
||||
assert_eq!(mode_name("x"), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn writes_and_reads_mode_file() {
|
||||
let dir = env::temp_dir().join("gm-switch-test");
|
||||
let _ = fs::remove_dir_all(&dir);
|
||||
fs::create_dir_all(&dir).unwrap();
|
||||
let file = dir.join("greeting_mode");
|
||||
fs::write(&file, "1").unwrap();
|
||||
assert_eq!(fs::read_to_string(&file).unwrap().trim(), "1");
|
||||
let _ = fs::remove_dir_all(&dir);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user