Compare commits
64 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 34579d8f31 | |||
| 21bf6758c5 | |||
| 006fab8cb2 | |||
| 2a70f71ae7 | |||
| 4eb82c9f29 | |||
| 0ff5c32c51 | |||
| 7e3109f758 | |||
| f5a0b763d5 | |||
| 4f813a2de7 | |||
| 7784cfd99b | |||
| 7e5b5ffed5 | |||
| 3f969d9447 | |||
| b68c139d8d | |||
| 175c3463f7 | |||
| d446be5fbd | |||
| e31ff0aa27 | |||
| a0b552e796 | |||
| b9a590be69 | |||
| 16642e7d02 | |||
| b8d825843d | |||
| 62027782a7 | |||
| 7d8037a82c | |||
| cf55c79855 | |||
| e9fab71e9d | |||
| 5318d3897b | |||
| 8967e3a1f5 | |||
| 089a5f7a49 | |||
| 4acc1556b2 | |||
| 9dd1a5430d | |||
| dbcde131aa | |||
| 2f8a3e98a5 | |||
| 03adbbd38f | |||
| bdccb9e0aa | |||
| 010643ee23 | |||
| 7fa9f85856 | |||
| 1a39b63b20 | |||
| da07682764 | |||
| b20768f64c | |||
| 2663d706b8 | |||
| c57e90d65e | |||
| cbd67c3c34 | |||
| 09d5484f52 | |||
| fc737d6edc | |||
| 7c67b41a7b | |||
| 2b16c6a612 | |||
| c6277a4a2a | |||
| ab2b3ce0a8 | |||
| 1d70410331 | |||
| 49c49e2e90 | |||
| 5dad7c0769 | |||
| 319a8ce00f | |||
| 74692b2b97 | |||
| 35522f5c3c | |||
| 5d0272b9bd | |||
| f2ac47cd88 | |||
| e7f2e43e52 | |||
| 0718d25887 | |||
| 52bc58da18 | |||
| 6911476077 | |||
| 270a7c0e4e | |||
| ae32389764 | |||
| 89962477c0 | |||
| ca365460af | |||
| e7cebf3092 |
+21
@@ -0,0 +1,21 @@
|
||||
# Generated by Cargo
|
||||
# will have compiled files and executables
|
||||
debug
|
||||
target
|
||||
|
||||
# These are backup files generated by rustfmt
|
||||
**/*.rs.bk
|
||||
|
||||
# MSVC Windows builds of rustc generate these, which store debugging information
|
||||
*.pdb
|
||||
|
||||
# Generated by cargo mutants
|
||||
# Contains mutation testing data
|
||||
**/mutants.out*/
|
||||
|
||||
# RustRover
|
||||
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
||||
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
||||
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
||||
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||
#.idea/
|
||||
Generated
+16
@@ -0,0 +1,16 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "ideskpet-installer"
|
||||
version = "1.0.0"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.183"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d"
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
[package]
|
||||
name = "ideskpet-installer"
|
||||
version = "1.0.0"
|
||||
edition = "2021"
|
||||
description = "Installer and CLI for I-DeskPet desktop pet application"
|
||||
authors = ["InoriShio"]
|
||||
license = "MIT"
|
||||
|
||||
[[bin]]
|
||||
name = "ideskpet-installer"
|
||||
path = "src/main.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "ideskpet"
|
||||
path = "src/bin/ideskpet.rs"
|
||||
|
||||
[target.'cfg(unix)'.dependencies]
|
||||
libc = "0.2"
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 298 KiB |
@@ -1 +1,63 @@
|
||||
### Pet March (Evernight)
|
||||
<div align="Center">
|
||||
<h3> Pet March (Evernight) </h3>
|
||||
<p> My selfmade desktop pet using QT </p>
|
||||
<img src=./Assets/Evernight.gif style="margin: 0px 30px 0px 0px;" />
|
||||
</div>
|
||||
|
||||
## Installation
|
||||
|
||||
```zsh
|
||||
cargo run --bin ideskpet-installer
|
||||
```
|
||||
|
||||
## Feature list
|
||||
|
||||
- [x] Binary for I-DeskPet (Branch Main)
|
||||
|
||||
# Config
|
||||
|
||||
Configuration is found at:
|
||||
|
||||
```zsh
|
||||
~/.config/I-DeskPet
|
||||
```
|
||||
|
||||
Options:
|
||||
|
||||
- gifFolder
|
||||
- maxScaling
|
||||
|
||||
Example for config.json:
|
||||
|
||||
```json
|
||||
{
|
||||
"gifFolder": "/home/inorishio/Pictures/Pets",
|
||||
"maxScaling": 1
|
||||
}
|
||||
```
|
||||
|
||||
# Hyprland keybinds
|
||||
|
||||
Toggle click through
|
||||
|
||||
```zsh
|
||||
bind = CTRL, mouse:274, global, I-DeskPet:toggle-Region
|
||||
```
|
||||
|
||||
Toggle between having your gif on your background vs foreground
|
||||
|
||||
```zsh
|
||||
bind = SHIFT, mouse:274, global, I-DeskPet:toggle-Layer
|
||||
```
|
||||
|
||||
Keybind for cycling through gif layering.
|
||||
Hover over which gif you want to cycle it's layer for and use the keybind.
|
||||
|
||||
```zsh
|
||||
bind = $mainMod, Z, global, I-DeskPet:cycle-zIndex
|
||||
```
|
||||
|
||||
# Other keybinds
|
||||
|
||||
- Double click = Reset gif size to original
|
||||
- Scroll = Scales the gif up and or down
|
||||
|
||||
@@ -1,56 +0,0 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Wayland
|
||||
//import qs.Gifs
|
||||
|
||||
PanelWindow {
|
||||
id: mainWindow
|
||||
WlrLayershell.layer: WlrLayer.Top
|
||||
color: "transparent"
|
||||
anchors {
|
||||
bottom: true
|
||||
left: true
|
||||
}
|
||||
surfaceFormat.opaque: false
|
||||
implicitWidth: 320
|
||||
implicitHeight: 293
|
||||
margins {
|
||||
left: 0
|
||||
bottom: 5
|
||||
}
|
||||
|
||||
property bool onTop: true
|
||||
|
||||
function toggleLayer() {
|
||||
if (onTop) {
|
||||
WlrLayershell.layer = WlrLayer.Bottom
|
||||
onTop = false
|
||||
} else {
|
||||
WlrLayershell.layer = WlrLayer.Top
|
||||
onTop = true
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: "transparent"
|
||||
|
||||
id: petContainer
|
||||
AnimatedImage {
|
||||
anchors.fill: parent
|
||||
source: "Gifs/evernight.gif"
|
||||
fillMode: Image.PreserveAspectFit
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
acceptedButtons: Qt.MiddleButton
|
||||
onClicked: (mouse) => {
|
||||
if (mouse.button === Qt.MiddleButton) {
|
||||
mainWindow.toggleLayer()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,492 @@
|
||||
//! I-DeskPet CLI Tool
|
||||
//!
|
||||
//! A command-line interface for managing the I-DeskPet desktop pet application.
|
||||
//!
|
||||
//! Usage:
|
||||
//! ideskpet <command> [options]
|
||||
//!
|
||||
//! Commands:
|
||||
//! start, stop, restart, log, update, toggle-layer, toggle-region, cycle-zindex
|
||||
|
||||
#[cfg(unix)]
|
||||
use std::env;
|
||||
#[cfg(unix)]
|
||||
use std::fs;
|
||||
#[cfg(unix)]
|
||||
use std::io::{BufRead, BufReader};
|
||||
#[cfg(unix)]
|
||||
use std::path::PathBuf;
|
||||
#[cfg(unix)]
|
||||
use std::process::{Command, Stdio};
|
||||
#[cfg(unix)]
|
||||
use std::thread;
|
||||
#[cfg(unix)]
|
||||
use std::time::Duration;
|
||||
|
||||
#[cfg(unix)]
|
||||
const VERSION: &str = "1.0.0";
|
||||
#[cfg(unix)]
|
||||
const APP_ID: &str = "I-DeskPet";
|
||||
#[cfg(unix)]
|
||||
const INSTALL_DIR: &str = "/etc/xdg/quickshell/I-DeskPet";
|
||||
#[cfg(unix)]
|
||||
const REPO_URL: &str = "https://github.com/InoriShio/I-DeskPet";
|
||||
|
||||
// ANSI color codes
|
||||
#[cfg(unix)]
|
||||
const RED: &str = "\x1b[31m";
|
||||
#[cfg(unix)]
|
||||
const GREEN: &str = "\x1b[32m";
|
||||
#[cfg(unix)]
|
||||
const YELLOW: &str = "\x1b[33m";
|
||||
#[cfg(unix)]
|
||||
const BLUE: &str = "\x1b[34m";
|
||||
#[cfg(unix)]
|
||||
const BOLD: &str = "\x1b[1m";
|
||||
#[cfg(unix)]
|
||||
const NC: &str = "\x1b[0m";
|
||||
|
||||
fn main() {
|
||||
#[cfg(not(unix))]
|
||||
{
|
||||
eprintln!("\x1b[31m[ERROR]\x1b[0m ideskpet only works on Linux/Unix systems.");
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
{
|
||||
let args: Vec<String> = env::args().collect();
|
||||
run_cli(&args);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
fn run_cli(args: &[String]) {
|
||||
if args.len() < 2 {
|
||||
print_help();
|
||||
return;
|
||||
}
|
||||
|
||||
let command = args[1].as_str();
|
||||
|
||||
match command {
|
||||
"start" => cmd_start(),
|
||||
"stop" => cmd_stop(),
|
||||
"restart" => cmd_restart(),
|
||||
"log" => cmd_log(&args[2..]),
|
||||
"update" => cmd_update(),
|
||||
"toggle-layer" => cmd_shortcut("toggle-Layer"),
|
||||
"toggle-region" => cmd_shortcut("toggle-Region"),
|
||||
"cycle-zindex" => cmd_shortcut("cycle-zIndex"),
|
||||
"-h" | "--help" | "help" => print_help(),
|
||||
"-v" | "--version" | "version" => println!("ideskpet v{VERSION}"),
|
||||
_ => {
|
||||
eprintln!("{RED}[ERROR]{NC} Unknown command: {command}");
|
||||
eprintln!("Run 'ideskpet --help' for usage information.");
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Helper Functions
|
||||
// =============================================================================
|
||||
|
||||
#[cfg(unix)]
|
||||
fn get_log_dir() -> PathBuf {
|
||||
let home = env::var("HOME").unwrap_or_else(|_| "/tmp".to_string());
|
||||
PathBuf::from(home).join(".local/state/ideskpet")
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
fn get_log_file() -> PathBuf {
|
||||
get_log_dir().join("ideskpet.log")
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
fn ensure_log_dir() {
|
||||
let log_dir = get_log_dir();
|
||||
if !log_dir.exists() {
|
||||
if let Err(e) = fs::create_dir_all(&log_dir) {
|
||||
eprintln!("{YELLOW}[WARN]{NC} Failed to create log directory: {e}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
fn is_running() -> bool {
|
||||
get_pid().is_some()
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
fn get_pid() -> Option<u32> {
|
||||
let output = Command::new("pgrep")
|
||||
.args(["-f", &format!("quickshell.*{APP_ID}")])
|
||||
.output()
|
||||
.ok()?;
|
||||
|
||||
if output.status.success() {
|
||||
let stdout = String::from_utf8_lossy(&output.stdout);
|
||||
stdout.lines().next()?.trim().parse().ok()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
fn check_dependency(name: &str) -> bool {
|
||||
Command::new("which")
|
||||
.arg(name)
|
||||
.stdout(Stdio::null())
|
||||
.stderr(Stdio::null())
|
||||
.status()
|
||||
.map(|s| s.success())
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
fn check_dependencies() -> bool {
|
||||
let mut ok = true;
|
||||
let deps = ["quickshell", "hyprctl", "git"];
|
||||
|
||||
for dep in deps {
|
||||
if !check_dependency(dep) {
|
||||
eprintln!("{RED}[ERROR]{NC} Missing dependency: {dep}");
|
||||
ok = false;
|
||||
}
|
||||
}
|
||||
|
||||
if !ok {
|
||||
eprintln!("\nPlease install the missing dependencies and try again.");
|
||||
}
|
||||
|
||||
ok
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Commands
|
||||
// =============================================================================
|
||||
|
||||
#[cfg(unix)]
|
||||
fn cmd_start() {
|
||||
if !check_dependency("quickshell") {
|
||||
eprintln!("{RED}[ERROR]{NC} quickshell is not installed");
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
if is_running() {
|
||||
let pid = get_pid().unwrap();
|
||||
eprintln!("{YELLOW}[WARN]{NC} I-DeskPet is already running (PID: {pid})");
|
||||
eprintln!("Use 'ideskpet restart' to restart, or 'ideskpet stop' to stop.");
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
// Check if install directory exists
|
||||
if !std::path::Path::new(INSTALL_DIR).exists() {
|
||||
eprintln!("{RED}[ERROR]{NC} I-DeskPet is not installed at {INSTALL_DIR}");
|
||||
eprintln!("Please run the installer first: cargo run");
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
ensure_log_dir();
|
||||
let log_file = get_log_file();
|
||||
|
||||
println!("{GREEN}[INFO]{NC} Starting I-DeskPet...");
|
||||
|
||||
// Open log file for appending
|
||||
let log_handle = fs::OpenOptions::new()
|
||||
.create(true)
|
||||
.append(true)
|
||||
.open(&log_file);
|
||||
|
||||
let (stdout_file, stderr_file) = match log_handle {
|
||||
Ok(f) => {
|
||||
let f2 = f.try_clone().unwrap_or_else(|_| {
|
||||
fs::OpenOptions::new()
|
||||
.create(true)
|
||||
.append(true)
|
||||
.open(&log_file)
|
||||
.unwrap()
|
||||
});
|
||||
(Stdio::from(f), Stdio::from(f2))
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("{YELLOW}[WARN]{NC} Could not open log file: {e}");
|
||||
(Stdio::null(), Stdio::null())
|
||||
}
|
||||
};
|
||||
|
||||
// Spawn quickshell process
|
||||
let result = Command::new("nohup")
|
||||
.args(["quickshell", "-p", INSTALL_DIR])
|
||||
.stdout(stdout_file)
|
||||
.stderr(stderr_file)
|
||||
.stdin(Stdio::null())
|
||||
.spawn();
|
||||
|
||||
match result {
|
||||
Ok(_child) => {
|
||||
// Wait a moment to check if it started
|
||||
thread::sleep(Duration::from_secs(1));
|
||||
|
||||
if is_running() {
|
||||
let pid = get_pid().unwrap_or(0);
|
||||
println!("{GREEN}[OK]{NC} I-DeskPet started successfully (PID: {pid})");
|
||||
println!("Use 'ideskpet log -f' to view logs");
|
||||
} else {
|
||||
eprintln!("{RED}[ERROR]{NC} I-DeskPet failed to start");
|
||||
eprintln!("Check logs with 'ideskpet log' for details");
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("{RED}[ERROR]{NC} Failed to start quickshell: {e}");
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
fn cmd_stop() {
|
||||
if !is_running() {
|
||||
println!("{YELLOW}[WARN]{NC} I-DeskPet is not running");
|
||||
return;
|
||||
}
|
||||
|
||||
let pid = get_pid().unwrap();
|
||||
println!("{GREEN}[INFO]{NC} Stopping I-DeskPet (PID: {pid})...");
|
||||
|
||||
// Send SIGTERM
|
||||
let _ = Command::new("kill").arg(pid.to_string()).status();
|
||||
|
||||
// Wait for graceful shutdown (up to 5 seconds)
|
||||
for _ in 0..10 {
|
||||
thread::sleep(Duration::from_millis(500));
|
||||
if !is_running() {
|
||||
println!("{GREEN}[OK]{NC} I-DeskPet stopped successfully");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Force kill if still running
|
||||
println!("{YELLOW}[WARN]{NC} Graceful shutdown failed, force killing...");
|
||||
let _ = Command::new("kill").args(["-9", &pid.to_string()]).status();
|
||||
|
||||
thread::sleep(Duration::from_millis(500));
|
||||
|
||||
if is_running() {
|
||||
eprintln!("{RED}[ERROR]{NC} Failed to stop I-DeskPet");
|
||||
std::process::exit(1);
|
||||
} else {
|
||||
println!("{GREEN}[OK]{NC} I-DeskPet stopped successfully");
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
fn cmd_restart() {
|
||||
println!("{GREEN}[INFO]{NC} Restarting I-DeskPet...");
|
||||
|
||||
if is_running() {
|
||||
cmd_stop();
|
||||
}
|
||||
|
||||
thread::sleep(Duration::from_millis(500));
|
||||
cmd_start();
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
fn cmd_log(args: &[String]) {
|
||||
let log_file = get_log_file();
|
||||
|
||||
if !log_file.exists() {
|
||||
eprintln!(
|
||||
"{YELLOW}[WARN]{NC} No log file found at {}",
|
||||
log_file.display()
|
||||
);
|
||||
eprintln!("Start I-DeskPet first with 'ideskpet start'");
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
let mut follow = false;
|
||||
let mut lines: u32 = 50;
|
||||
|
||||
// Parse arguments
|
||||
let mut i = 0;
|
||||
while i < args.len() {
|
||||
match args[i].as_str() {
|
||||
"-f" | "--follow" => {
|
||||
follow = true;
|
||||
}
|
||||
"-n" | "--lines" => {
|
||||
if i + 1 < args.len() {
|
||||
if let Ok(n) = args[i + 1].parse() {
|
||||
lines = n;
|
||||
i += 1;
|
||||
} else {
|
||||
eprintln!("{RED}[ERROR]{NC} Invalid number for -n option");
|
||||
std::process::exit(1);
|
||||
}
|
||||
} else {
|
||||
eprintln!("{RED}[ERROR]{NC} -n option requires a number");
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
eprintln!("{RED}[ERROR]{NC} Unknown log option: {}", args[i]);
|
||||
eprintln!("Usage: ideskpet log [-f] [-n <lines>]");
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
|
||||
if follow {
|
||||
println!("{GREEN}[INFO]{NC} Following logs (Ctrl+C to exit)...");
|
||||
println!("---");
|
||||
|
||||
let status = Command::new("tail")
|
||||
.args(["-n", &lines.to_string(), "-f", log_file.to_str().unwrap()])
|
||||
.status();
|
||||
|
||||
if let Err(e) = status {
|
||||
eprintln!("{RED}[ERROR]{NC} Failed to tail log file: {e}");
|
||||
std::process::exit(1);
|
||||
}
|
||||
} else {
|
||||
let status = Command::new("tail")
|
||||
.args(["-n", &lines.to_string(), log_file.to_str().unwrap()])
|
||||
.status();
|
||||
|
||||
if let Err(e) = status {
|
||||
eprintln!("{RED}[ERROR]{NC} Failed to read log file: {e}");
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
fn cmd_update() {
|
||||
if !check_dependency("git") {
|
||||
eprintln!("{RED}[ERROR]{NC} git is not installed");
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
if !std::path::Path::new(INSTALL_DIR).exists() {
|
||||
eprintln!("{RED}[ERROR]{NC} I-DeskPet is not installed at {INSTALL_DIR}");
|
||||
eprintln!("Please run the installer first: cargo run");
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
println!("{GREEN}[INFO]{NC} Updating I-DeskPet from GitHub...");
|
||||
|
||||
let status = Command::new("sudo")
|
||||
.args(["git", "-C", INSTALL_DIR, "pull"])
|
||||
.status();
|
||||
|
||||
match status {
|
||||
Ok(s) if s.success() => {
|
||||
println!("{GREEN}[OK]{NC} Update completed successfully");
|
||||
|
||||
if is_running() {
|
||||
println!();
|
||||
println!("{YELLOW}[WARN]{NC} I-DeskPet is currently running.");
|
||||
println!("Run 'ideskpet restart' to apply changes.");
|
||||
}
|
||||
}
|
||||
Ok(_) => {
|
||||
eprintln!("{RED}[ERROR]{NC} Git pull failed");
|
||||
std::process::exit(1);
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("{RED}[ERROR]{NC} Failed to run git: {e}");
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
fn cmd_shortcut(shortcut: &str) {
|
||||
if !check_dependency("hyprctl") {
|
||||
eprintln!("{RED}[ERROR]{NC} hyprctl not found. Are you running Hyprland?");
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
if !is_running() {
|
||||
eprintln!("{YELLOW}[WARN]{NC} I-DeskPet is not running");
|
||||
eprintln!("Start it first with 'ideskpet start'");
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
let shortcut_full = format!("{APP_ID}:{shortcut}");
|
||||
println!("{GREEN}[INFO]{NC} Triggering shortcut: {shortcut_full}");
|
||||
|
||||
let dispatch_arg = format!("hl.dsp.global(\"{shortcut_full}\")");
|
||||
|
||||
let status = Command::new("hyprctl")
|
||||
.args(["dispatch", &dispatch_arg])
|
||||
.status();
|
||||
|
||||
match status {
|
||||
Ok(s) if s.success() => {
|
||||
println!("{GREEN}[OK]{NC} Shortcut triggered");
|
||||
}
|
||||
Ok(_) => {
|
||||
eprintln!("{RED}[ERROR]{NC} Failed to trigger shortcut");
|
||||
std::process::exit(1);
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("{RED}[ERROR]{NC} Failed to run hyprctl: {e}");
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
fn print_help() {
|
||||
println!(
|
||||
"{BOLD}ideskpet{NC} - CLI tool for I-DeskPet desktop pet application (v{VERSION})
|
||||
|
||||
{BOLD}USAGE:{NC}
|
||||
ideskpet <COMMAND> [OPTIONS]
|
||||
|
||||
{BOLD}COMMANDS:{NC}
|
||||
{GREEN}start{NC} Start the I-DeskPet application
|
||||
{GREEN}stop{NC} Stop the running I-DeskPet instance
|
||||
{GREEN}restart{NC} Restart the I-DeskPet application
|
||||
{GREEN}log{NC} Show application logs
|
||||
{GREEN}update{NC} Update I-DeskPet from GitHub
|
||||
|
||||
{BOLD}HYPRLAND SHORTCUTS:{NC}
|
||||
{BLUE}toggle-layer{NC} Toggle pet between overlay/bottom layer
|
||||
{BLUE}toggle-region{NC} Toggle click-through mode
|
||||
{BLUE}cycle-zindex{NC} Cycle z-index of hovered gif
|
||||
|
||||
{BOLD}LOG OPTIONS:{NC}
|
||||
ideskpet log Show last 50 lines of logs
|
||||
ideskpet log -f Follow logs in real-time (Ctrl+C to exit)
|
||||
ideskpet log -n <N> Show last N lines of logs
|
||||
|
||||
{BOLD}OTHER:{NC}
|
||||
-h, --help Show this help message
|
||||
-v, --version Show version
|
||||
|
||||
{BOLD}CONFIGURATION:{NC}
|
||||
User config: ~/.config/I-DeskPet/config.json
|
||||
|
||||
Example config.json:
|
||||
{{
|
||||
\"gifFolder\": \"/home/user/Pictures/Pets\",
|
||||
\"maxScaling\": 1
|
||||
}}
|
||||
|
||||
{BOLD}HYPRLAND KEYBIND EXAMPLES:{NC}
|
||||
bind = CTRL, mouse:274, global, I-DeskPet:toggle-Region
|
||||
bind = SHIFT, mouse:274, global, I-DeskPet:toggle-Layer
|
||||
bind = $mainMod, Z, global, I-DeskPet:cycle-zIndex
|
||||
|
||||
{BOLD}OTHER KEYBINDS:{NC}
|
||||
Double click Reset gif size to original
|
||||
Scroll Scale the gif up or down
|
||||
"
|
||||
);
|
||||
}
|
||||
+186
@@ -0,0 +1,186 @@
|
||||
//! I-DeskPet Installer
|
||||
//!
|
||||
//! This installer builds the ideskpet CLI binary, clones/updates the repo,
|
||||
//! and installs everything to the appropriate system locations.
|
||||
//!
|
||||
//! Run with `cargo run` on your Arch Linux machine.
|
||||
|
||||
#[cfg(unix)]
|
||||
use std::path::Path;
|
||||
#[cfg(unix)]
|
||||
use std::process::{Command, ExitStatus};
|
||||
|
||||
#[cfg(unix)]
|
||||
const REPO_URL: &str = "https://github.com/InoriShio/I-DeskPet";
|
||||
#[cfg(unix)]
|
||||
const INSTALL_DIR: &str = "/etc/xdg/quickshell/I-DeskPet";
|
||||
#[cfg(unix)]
|
||||
const BINARY_DEST: &str = "/usr/bin/ideskpet";
|
||||
|
||||
// ANSI color codes
|
||||
#[cfg(unix)]
|
||||
const RED: &str = "\x1b[31m";
|
||||
#[cfg(unix)]
|
||||
const GREEN: &str = "\x1b[32m";
|
||||
#[cfg(unix)]
|
||||
const YELLOW: &str = "\x1b[33m";
|
||||
#[cfg(unix)]
|
||||
const BLUE: &str = "\x1b[34m";
|
||||
#[cfg(unix)]
|
||||
const BOLD: &str = "\x1b[1m";
|
||||
#[cfg(unix)]
|
||||
const NC: &str = "\x1b[0m";
|
||||
|
||||
fn main() {
|
||||
#[cfg(not(unix))]
|
||||
{
|
||||
eprintln!("\x1b[31m[ERROR]\x1b[0m This installer only works on Linux/Unix systems.");
|
||||
eprintln!("Please run this on your Arch Linux machine.");
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
run_installer();
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
fn run_installer() {
|
||||
println!("{BOLD}=== I-DeskPet Installer ==={NC}\n");
|
||||
|
||||
// Step 1: Build the ideskpet binary
|
||||
if !build_binary() {
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
// Step 2: Clone or update the repository
|
||||
if !setup_repository() {
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
// Step 3: Install the binary to /usr/bin
|
||||
if !install_binary() {
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
// Success!
|
||||
println!("\n{GREEN}{BOLD}=== Installation Complete ==={NC}\n");
|
||||
print_usage();
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
fn build_binary() -> bool {
|
||||
println!("{BLUE}[1/3]{NC} Building ideskpet binary...");
|
||||
|
||||
let status = Command::new("cargo")
|
||||
.args(["build", "--release", "--bin", "ideskpet"])
|
||||
.status();
|
||||
|
||||
match status {
|
||||
Ok(s) if s.success() => {
|
||||
println!("{GREEN}[OK]{NC} Binary built successfully\n");
|
||||
true
|
||||
}
|
||||
Ok(_) => {
|
||||
eprintln!("{RED}[ERROR]{NC} Failed to build binary");
|
||||
false
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("{RED}[ERROR]{NC} Failed to run cargo: {e}");
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
fn setup_repository() -> bool {
|
||||
println!("{BLUE}[2/3]{NC} Setting up repository at {INSTALL_DIR}...");
|
||||
|
||||
let install_path = Path::new(INSTALL_DIR);
|
||||
let parent_dir = install_path
|
||||
.parent()
|
||||
.unwrap_or(Path::new("/etc/xdg/quickshell"));
|
||||
|
||||
// Check if parent directory exists, create if not
|
||||
if !parent_dir.exists() {
|
||||
println!(" Creating directory {parent_dir:?} (requires sudo)...");
|
||||
if !run_sudo(&["mkdir", "-p", parent_dir.to_str().unwrap()]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if install_path.exists() {
|
||||
// Repository exists, do git pull
|
||||
println!(" Repository exists, updating with git pull...");
|
||||
if !run_sudo(&["git", "-C", INSTALL_DIR, "pull"]) {
|
||||
eprintln!("{YELLOW}[WARN]{NC} Git pull failed, continuing anyway...");
|
||||
} else {
|
||||
println!("{GREEN}[OK]{NC} Repository updated\n");
|
||||
}
|
||||
} else {
|
||||
// Clone the repository
|
||||
println!(" Cloning from {REPO_URL}...");
|
||||
if !run_sudo(&["git", "clone", REPO_URL, INSTALL_DIR]) {
|
||||
eprintln!("{RED}[ERROR]{NC} Failed to clone repository");
|
||||
return false;
|
||||
}
|
||||
println!("{GREEN}[OK]{NC} Repository cloned\n");
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
fn install_binary() -> bool {
|
||||
println!("{BLUE}[3/3]{NC} Installing binary to {BINARY_DEST}...");
|
||||
|
||||
// Get the path to the built binary
|
||||
let binary_src = "target/release/ideskpet";
|
||||
|
||||
if !Path::new(binary_src).exists() {
|
||||
eprintln!("{RED}[ERROR]{NC} Built binary not found at {binary_src}");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Copy binary to /usr/bin
|
||||
println!(" Copying binary (requires sudo)...");
|
||||
if !run_sudo(&["cp", binary_src, BINARY_DEST]) {
|
||||
eprintln!("{RED}[ERROR]{NC} Failed to copy binary");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Set executable permissions
|
||||
println!(" Setting executable permissions...");
|
||||
if !run_sudo(&["chmod", "+x", BINARY_DEST]) {
|
||||
eprintln!("{RED}[ERROR]{NC} Failed to set permissions");
|
||||
return false;
|
||||
}
|
||||
|
||||
println!("{GREEN}[OK]{NC} Binary installed to {BINARY_DEST}");
|
||||
true
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
fn run_sudo(args: &[&str]) -> bool {
|
||||
let status = Command::new("sudo").args(args).status();
|
||||
|
||||
matches!(status, Ok(s) if s.success())
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
fn print_usage() {
|
||||
println!("{BOLD}Usage:{NC}");
|
||||
println!(" ideskpet start Start the desktop pet");
|
||||
println!(" ideskpet stop Stop the desktop pet");
|
||||
println!(" ideskpet restart Restart the desktop pet");
|
||||
println!(" ideskpet log View last 50 lines of logs");
|
||||
println!(" ideskpet log -f Follow logs in real-time");
|
||||
println!(" ideskpet log -n <N> View last N lines of logs");
|
||||
println!(" ideskpet update Update from GitHub");
|
||||
println!(" ideskpet toggle-layer Toggle overlay/bottom layer");
|
||||
println!(" ideskpet toggle-region Toggle click-through mode");
|
||||
println!(" ideskpet cycle-zindex Cycle z-index of hovered gif");
|
||||
println!(" ideskpet --help Show full help");
|
||||
println!();
|
||||
println!("{BOLD}Get started:{NC}");
|
||||
println!(" Run {GREEN}ideskpet start{NC} to launch your desktop pet!");
|
||||
}
|
||||
Reference in New Issue
Block a user