64 Commits

Author SHA1 Message Date
Inorishio 34579d8f31 Fixed hyprland shortcuts 2026-05-16 12:52:09 +02:00
Inorishio 21bf6758c5 Readme update 2026-03-23 15:57:02 +01:00
Inorishio 006fab8cb2 Clean up 2026-03-23 13:45:26 +01:00
Inorishio 2a70f71ae7 binary 2026-03-23 13:28:34 +01:00
Inorishio 4eb82c9f29 binary 2026-03-23 13:27:53 +01:00
InoriShio 0ff5c32c51 Add files via upload 2026-03-19 15:29:32 +01:00
Inorishio 7e3109f758 zach's nutty qmlformat 2026-02-25 23:01:52 +01:00
Inorishio f5a0b763d5 Edit read.md 2026-02-16 13:25:42 +01:00
Inorishio 4f813a2de7 Edit read.md 2026-02-16 13:25:03 +01:00
Inorishio 7784cfd99b Edit read.md 2026-02-16 13:23:06 +01:00
Inorishio 7e5b5ffed5 Finished. 2026-02-16 12:40:29 +01:00
Inorishio 3f969d9447 It's poppin' 2026-02-14 18:24:01 +01:00
Inorishio b68c139d8d before it goes to .... 2026-02-14 17:20:10 +01:00
Inorishio 175c3463f7 screenYposGae 2026-02-12 19:46:25 +01:00
Inorishio d446be5fbd xy 2026-02-12 19:27:51 +01:00
Inorishio e31ff0aa27 can't be working on this at work lmao 2026-02-11 12:00:12 +01:00
Inorishio a0b552e796 Fileview 2026-02-09 23:45:59 +01:00
Inorishio b9a590be69 edits 2026-02-09 22:33:24 +01:00
Inorishio 16642e7d02 Read.md 2025-12-04 22:57:28 +01:00
Inorishio b8d825843d Read.md 2025-12-04 22:54:45 +01:00
Inorishio 62027782a7 Read.md 2025-12-04 22:52:41 +01:00
Inorishio 7d8037a82c Fix / move bug 2025-12-04 19:39:45 +01:00
Inorishio cf55c79855 Zach: I know everything about your life 2025-12-03 23:22:59 +01:00
InoriShio e9fab71e9d Merge pull request #2 from Zacharias-Brohn/Repeater
Zachjittery pog
2025-12-03 15:57:31 +00:00
Zacharias-Brohn 5318d3897b fixes 2025-12-03 15:33:29 +01:00
Inorishio 8967e3a1f5 Claude pog? 2025-12-03 13:53:58 +01:00
Inorishio 089a5f7a49 finished update 2025-11-09 02:37:11 +01:00
Inorishio 4acc1556b2 More pets 2025-11-04 08:02:03 +01:00
Inorishio 9dd1a5430d repeater 2025-10-30 14:39:51 +01:00
Inorishio dbcde131aa MOVEMENT 2025-10-27 00:14:05 +01:00
Inorishio 2f8a3e98a5 REGIONS 2025-10-26 22:28:03 +01:00
Inorishio 03adbbd38f REGIONS 2025-10-23 00:47:29 +02:00
Inorishio bdccb9e0aa read me 2025-10-22 22:32:56 +02:00
Inorishio 010643ee23 Click through it... 2025-10-22 22:06:14 +02:00
Inorishio 7fa9f85856 clean up 2025-10-21 22:48:57 +02:00
Inorishio 1a39b63b20 clean up 2025-10-21 22:29:38 +02:00
Inorishio da07682764 Evernight & Caelestia 2025-10-20 10:53:55 +02:00
Inorishio b20768f64c Git 2025-10-19 23:35:08 +02:00
Inorishio 2663d706b8 Git 2025-10-19 23:34:47 +02:00
Inorishio c57e90d65e Git 2025-10-19 23:34:16 +02:00
Inorishio cbd67c3c34 Git ignore 2025-10-15 22:46:31 +02:00
Inorishio 09d5484f52 Git ignore 2025-10-15 22:44:57 +02:00
Inorishio fc737d6edc Update text 2025-10-15 16:01:50 +02:00
Inorishio 7c67b41a7b Update text 2025-10-15 15:54:16 +02:00
Inorishio 2b16c6a612 Update text 2025-10-15 15:52:36 +02:00
Inorishio c6277a4a2a Update text 2025-10-15 15:21:36 +02:00
Inorishio ab2b3ce0a8 Update text 2025-10-15 15:21:09 +02:00
Inorishio 1d70410331 Update text 2025-10-15 15:20:28 +02:00
Inorishio 49c49e2e90 Update text 2025-10-15 15:17:09 +02:00
Inorishio 5dad7c0769 Update text 2025-10-15 14:45:16 +02:00
Inorishio 319a8ce00f Update text 2025-10-15 14:44:10 +02:00
Inorishio 74692b2b97 Update text 2025-10-15 14:29:47 +02:00
Inorishio 35522f5c3c Update text 2025-10-15 14:09:07 +02:00
Inorishio 5d0272b9bd Read me 2025-10-15 00:52:30 +02:00
Inorishio f2ac47cd88 Read me 2025-10-15 00:52:08 +02:00
Inorishio e7f2e43e52 Read me 2025-10-15 00:51:29 +02:00
Inorishio 0718d25887 Read me 2025-10-15 00:47:27 +02:00
Inorishio 52bc58da18 Read me 2025-10-15 00:43:23 +02:00
Inorishio 6911476077 Read me 2025-10-15 00:41:46 +02:00
Inorishio 270a7c0e4e Read me 2025-10-15 00:39:00 +02:00
Inorishio ae32389764 Read me 2025-10-15 00:27:34 +02:00
Inorishio 89962477c0 Read me 2025-10-15 00:26:21 +02:00
Inorishio ca365460af FUNCTIONS SU-.. 2025-10-15 00:23:11 +02:00
Inorishio e7cebf3092 expansion 2025-10-14 16:05:58 +02:00
8 changed files with 796 additions and 57 deletions
+21
View File
@@ -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
View File
@@ -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
View File
@@ -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

+63 -1
View File
@@ -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
-56
View File
@@ -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()
}
}
}
}
}
+492
View File
@@ -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
View File
@@ -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!");
}