Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 34579d8f31 | |||
| 21bf6758c5 | |||
| 006fab8cb2 | |||
| 2a70f71ae7 | |||
| 4eb82c9f29 |
+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/
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 294 KiB |
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"
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
|
|
||||||
FileView {
|
|
||||||
id: watcher
|
|
||||||
path: root.configPath
|
|
||||||
|
|
||||||
watchChanges: true
|
|
||||||
|
|
||||||
onFileChanged: reload()
|
|
||||||
|
|
||||||
onAdapterUpdated: writeAdapter()
|
|
||||||
|
|
||||||
JsonAdapter {
|
|
||||||
id: adapter
|
|
||||||
|
|
||||||
property string gifFolder: Quickshell.shellDir + "/Gifs"
|
|
||||||
property real maxScaling: 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
|
|
||||||
FileView {
|
|
||||||
id: watcher
|
|
||||||
path: configPath
|
|
||||||
|
|
||||||
property string name: gifItem.fileBaseName + ".json"
|
|
||||||
property string configDir: Quickshell.env("HOME") + "/.config/I-DeskPet/"
|
|
||||||
property string configPath: configDir + name
|
|
||||||
|
|
||||||
onLoaded: {
|
|
||||||
if ( gifSaved.zIndex === -1 ) gifSaved.zIndex = gifItem.index
|
|
||||||
gifItem.x = gifSaved.positionX
|
|
||||||
gifItem.y = gifSaved.positionY
|
|
||||||
gifItem.loaded = true
|
|
||||||
}
|
|
||||||
|
|
||||||
onLoadFailed: {
|
|
||||||
gifSaved.zIndex = gifItem.index
|
|
||||||
writeAdapter()
|
|
||||||
gifItem.loaded = true
|
|
||||||
}
|
|
||||||
|
|
||||||
watchChanges: true
|
|
||||||
|
|
||||||
onFileChanged: reload()
|
|
||||||
|
|
||||||
onAdapterUpdated: writeAdapter()
|
|
||||||
|
|
||||||
JsonAdapter {
|
|
||||||
id: gifSaved
|
|
||||||
|
|
||||||
property real scaling: 1
|
|
||||||
property int positionX: 0
|
|
||||||
property int positionY: 0
|
|
||||||
property int zIndex: -1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-38
@@ -1,38 +0,0 @@
|
|||||||
|
|
||||||
FileView {
|
|
||||||
id: watcher
|
|
||||||
path: configPath
|
|
||||||
|
|
||||||
property string name: gifItem.fileBaseName + ".json"
|
|
||||||
property string configDir: Quickshell.env("HOME") + "/.config/I-DeskPet/"
|
|
||||||
property string configPath: configDir + name
|
|
||||||
|
|
||||||
onLoaded: {
|
|
||||||
if ( gifSaved.zIndex === -1 ) gifSaved.zIndex = gifItem.index
|
|
||||||
gifItem.x = gifSaved.positionX
|
|
||||||
gifItem.y = gifSaved.positionY
|
|
||||||
gifItem.loaded = true
|
|
||||||
}
|
|
||||||
|
|
||||||
onLoadFailed: {
|
|
||||||
gifSaved.zIndex = gifItem.index
|
|
||||||
writeAdapter()
|
|
||||||
gifItem.loaded = true
|
|
||||||
}
|
|
||||||
|
|
||||||
watchChanges: true
|
|
||||||
|
|
||||||
onFileChanged: reload()
|
|
||||||
|
|
||||||
onAdapterUpdated: writeAdapter()
|
|
||||||
|
|
||||||
JsonAdapter {
|
|
||||||
id: gifSaved
|
|
||||||
|
|
||||||
property real scaling: 1
|
|
||||||
property int positionX: 0
|
|
||||||
property int positionY: 0
|
|
||||||
property int zIndex: -1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 294 KiB |
@@ -1,56 +0,0 @@
|
|||||||
pragma Singleton
|
|
||||||
|
|
||||||
import QtQuick
|
|
||||||
import Quickshell.Io
|
|
||||||
import Quickshell
|
|
||||||
|
|
||||||
Singleton {
|
|
||||||
id: root
|
|
||||||
|
|
||||||
property string configDir: Quickshell.env("HOME") + "/.config/I-DeskPet"
|
|
||||||
property string configPath: configDir + "/config.json"
|
|
||||||
property alias gifFolder: adapter.gifFolder
|
|
||||||
property alias maxScaling: adapter.maxScale
|
|
||||||
|
|
||||||
Process {
|
|
||||||
id: dirCheck
|
|
||||||
|
|
||||||
command: ["test", "-d", root.configDir]
|
|
||||||
running: true
|
|
||||||
|
|
||||||
onExited: function (exitCode) {
|
|
||||||
if (exitCode !== 0) {
|
|
||||||
console.log("creating dir");
|
|
||||||
dirCreate.running = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Process {
|
|
||||||
id: dirCreate
|
|
||||||
|
|
||||||
command: ["mkdir", "-p", root.configDir]
|
|
||||||
running: false
|
|
||||||
|
|
||||||
onExited: function (): void {
|
|
||||||
console.log("Created config directory:", root.configDir);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
FileView {
|
|
||||||
id: watcher
|
|
||||||
|
|
||||||
path: root.configPath
|
|
||||||
watchChanges: true
|
|
||||||
|
|
||||||
onAdapterUpdated: writeAdapter()
|
|
||||||
onFileChanged: reload()
|
|
||||||
|
|
||||||
JsonAdapter {
|
|
||||||
id: adapter
|
|
||||||
|
|
||||||
property string gifFolder: Quickshell.shellDir + "/Gifs"
|
|
||||||
property real maxScale: 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
import QtQuick
|
|
||||||
import Qt.labs.folderlistmodel
|
|
||||||
|
|
||||||
Item {
|
|
||||||
id: root
|
|
||||||
|
|
||||||
property alias count: folderModel.count
|
|
||||||
required property string gifFolder
|
|
||||||
property alias gifsModel: folderModel
|
|
||||||
|
|
||||||
FolderListModel {
|
|
||||||
id: folderModel
|
|
||||||
|
|
||||||
folder: "file://" + root.gifFolder
|
|
||||||
nameFilters: ["*.gif"]
|
|
||||||
showDirs: false
|
|
||||||
showHidden: false
|
|
||||||
sortField: FolderListModel.Name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,88 +0,0 @@
|
|||||||
pragma ComponentBehavior: Bound
|
|
||||||
|
|
||||||
import QtQuick
|
|
||||||
import Quickshell
|
|
||||||
import Quickshell.Io
|
|
||||||
import Qt.labs.folderlistmodel
|
|
||||||
import qs.Modules
|
|
||||||
|
|
||||||
Repeater {
|
|
||||||
id: gifRepeater
|
|
||||||
|
|
||||||
required property FolderListModel gifsModel
|
|
||||||
|
|
||||||
model: gifsModel
|
|
||||||
|
|
||||||
Item {
|
|
||||||
id: gifItem
|
|
||||||
|
|
||||||
required property string fileBaseName
|
|
||||||
required property url fileUrl
|
|
||||||
property alias hovered: mouse.containsMouse
|
|
||||||
required property int index
|
|
||||||
property bool loaded: false
|
|
||||||
property alias zIndex: gifSaved.zIndex
|
|
||||||
|
|
||||||
height: Math.floor(gif.sourceSize.height / gifSaved.scaling)
|
|
||||||
visible: gifItem.loaded
|
|
||||||
width: Math.floor(gif.sourceSize.width / gifSaved.scaling)
|
|
||||||
z: gifSaved.zIndex
|
|
||||||
|
|
||||||
onXChanged: if (gifItem.loaded)
|
|
||||||
gifSaved.positionX = gifItem.x
|
|
||||||
onYChanged: if (gifItem.loaded)
|
|
||||||
gifSaved.positionY = gifItem.y
|
|
||||||
|
|
||||||
AnimatedImage {
|
|
||||||
id: gif
|
|
||||||
|
|
||||||
anchors.fill: parent
|
|
||||||
fillMode: Image.PreserveAspectFit
|
|
||||||
source: gifItem.fileUrl
|
|
||||||
}
|
|
||||||
|
|
||||||
Mouse {
|
|
||||||
id: mouse
|
|
||||||
|
|
||||||
onDoubleClicked: gifSaved.scaling = 1
|
|
||||||
onWheel: wheel => {
|
|
||||||
gifSaved.scaling = Math.max(ConfigLoader.maxScaling, (gifSaved.scaling + 0.1 * (wheel.angleDelta.y / 120)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
FileView {
|
|
||||||
id: watcher
|
|
||||||
|
|
||||||
property string configDir: Quickshell.env("HOME") + "/.config/I-DeskPet/"
|
|
||||||
property string configPath: configDir + name
|
|
||||||
property string name: gifItem.fileBaseName + ".json"
|
|
||||||
|
|
||||||
path: configPath
|
|
||||||
watchChanges: true
|
|
||||||
|
|
||||||
onAdapterUpdated: writeAdapter()
|
|
||||||
onFileChanged: reload()
|
|
||||||
onLoadFailed: {
|
|
||||||
gifSaved.zIndex = gifItem.index;
|
|
||||||
writeAdapter();
|
|
||||||
gifItem.loaded = true;
|
|
||||||
}
|
|
||||||
onLoaded: {
|
|
||||||
if (gifSaved.zIndex === -1)
|
|
||||||
gifSaved.zIndex = gifItem.index;
|
|
||||||
gifItem.x = gifSaved.positionX;
|
|
||||||
gifItem.y = gifSaved.positionY;
|
|
||||||
gifItem.loaded = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
JsonAdapter {
|
|
||||||
id: gifSaved
|
|
||||||
|
|
||||||
property int positionX: 0
|
|
||||||
property int positionY: 0
|
|
||||||
property real scaling: 1
|
|
||||||
property int zIndex: -1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
import QtQuick
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
acceptedButtons: Qt.LeftButton
|
|
||||||
anchors.fill: parent
|
|
||||||
drag.axis: Drag.XAndYAxis
|
|
||||||
drag.maximumX: Screen.width - parent.width
|
|
||||||
drag.maximumY: Screen.height - parent.height
|
|
||||||
drag.minimumX: 0
|
|
||||||
drag.minimumY: 0
|
|
||||||
drag.target: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
}
|
|
||||||
@@ -1,18 +1,18 @@
|
|||||||
<div align="Center">
|
<div align="Center">
|
||||||
<h3> Pet March (Evernight) </h3>
|
<h3> Pet March (Evernight) </h3>
|
||||||
<p>My selfmade desktop pet using QT </p>
|
<p> My selfmade desktop pet using QT </p>
|
||||||
<img src=./Assets/Evernight.gif style="margin: 0px 30px 0px 0px;" />
|
<img src=./Assets/Evernight.gif style="margin: 0px 30px 0px 0px;" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
```zsh
|
||||||
|
cargo run --bin ideskpet-installer
|
||||||
|
```
|
||||||
|
|
||||||
## Feature list
|
## Feature list
|
||||||
|
|
||||||
- [x] Hyprland keybind support
|
- [x] Binary for I-DeskPet (Branch Main)
|
||||||
- [x] Toggle layer ontop/bottom
|
|
||||||
- [x] Toggle active mouse area
|
|
||||||
- [x] Dynamic path + live update
|
|
||||||
- [x] Supports multiple gifs
|
|
||||||
- [x] User config options
|
|
||||||
- [x] Evernight base gif img
|
|
||||||
|
|
||||||
# Config
|
# Config
|
||||||
|
|
||||||
|
|||||||
@@ -1,379 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
# =============================================================================
|
|
||||||
# ideskpet - CLI tool for I-DeskPet Quickshell application
|
|
||||||
# =============================================================================
|
|
||||||
|
|
||||||
VERSION="1.0.0"
|
|
||||||
REPO_URL="https://github.com/InoriShio/I-DeskPet"
|
|
||||||
INSTALL_DIR="/etc/xdg/quickshell/I-DeskPet"
|
|
||||||
LOG_DIR="$HOME/.local/state/ideskpet"
|
|
||||||
LOG_FILE="$LOG_DIR/ideskpet.log"
|
|
||||||
APP_ID="I-DeskPet"
|
|
||||||
|
|
||||||
# Colors
|
|
||||||
RED='\033[0;31m'
|
|
||||||
GREEN='\033[0;32m'
|
|
||||||
YELLOW='\033[1;33m'
|
|
||||||
BLUE='\033[0;34m'
|
|
||||||
BOLD='\033[1m'
|
|
||||||
NC='\033[0m'
|
|
||||||
|
|
||||||
# =============================================================================
|
|
||||||
# Helper Functions
|
|
||||||
# =============================================================================
|
|
||||||
|
|
||||||
print_help() {
|
|
||||||
cat << EOF
|
|
||||||
${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}INSTALLATION:${NC}
|
|
||||||
sudo cp ideskpet /usr/bin/ideskpet
|
|
||||||
sudo chmod +x /usr/bin/ideskpet
|
|
||||||
|
|
||||||
EOF
|
|
||||||
}
|
|
||||||
|
|
||||||
print_version() {
|
|
||||||
echo "ideskpet v${VERSION}"
|
|
||||||
}
|
|
||||||
|
|
||||||
info() {
|
|
||||||
echo -e "${GREEN}[INFO]${NC} $1"
|
|
||||||
}
|
|
||||||
|
|
||||||
warn() {
|
|
||||||
echo -e "${YELLOW}[WARN]${NC} $1"
|
|
||||||
}
|
|
||||||
|
|
||||||
error() {
|
|
||||||
echo -e "${RED}[ERROR]${NC} $1" >&2
|
|
||||||
}
|
|
||||||
|
|
||||||
is_running() {
|
|
||||||
pgrep -f "quickshell.*${APP_ID}" > /dev/null 2>&1
|
|
||||||
}
|
|
||||||
|
|
||||||
get_pid() {
|
|
||||||
pgrep -f "quickshell.*${APP_ID}" 2>/dev/null || true
|
|
||||||
}
|
|
||||||
|
|
||||||
ensure_log_dir() {
|
|
||||||
if [[ ! -d "$LOG_DIR" ]]; then
|
|
||||||
mkdir -p "$LOG_DIR"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
ensure_installed() {
|
|
||||||
if [[ ! -d "$INSTALL_DIR" ]]; then
|
|
||||||
warn "I-DeskPet is not installed at ${INSTALL_DIR}"
|
|
||||||
info "Cloning from ${REPO_URL}..."
|
|
||||||
|
|
||||||
# Check if /etc/xdg/quickshell exists
|
|
||||||
if [[ ! -d "/etc/xdg/quickshell" ]]; then
|
|
||||||
info "Creating /etc/xdg/quickshell directory (requires sudo)..."
|
|
||||||
sudo mkdir -p /etc/xdg/quickshell
|
|
||||||
fi
|
|
||||||
|
|
||||||
info "Cloning repository (requires sudo)..."
|
|
||||||
sudo git clone "$REPO_URL" "$INSTALL_DIR"
|
|
||||||
|
|
||||||
if [[ $? -eq 0 ]]; then
|
|
||||||
info "Successfully installed I-DeskPet to ${INSTALL_DIR}"
|
|
||||||
else
|
|
||||||
error "Failed to clone repository"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
check_dependencies() {
|
|
||||||
local missing=()
|
|
||||||
|
|
||||||
if ! command -v quickshell &> /dev/null; then
|
|
||||||
missing+=("quickshell")
|
|
||||||
fi
|
|
||||||
|
|
||||||
if ! command -v hyprctl &> /dev/null; then
|
|
||||||
missing+=("hyprctl (hyprland)")
|
|
||||||
fi
|
|
||||||
|
|
||||||
if ! command -v git &> /dev/null; then
|
|
||||||
missing+=("git")
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ ${#missing[@]} -gt 0 ]]; then
|
|
||||||
error "Missing dependencies: ${missing[*]}"
|
|
||||||
echo "Please install the missing packages and try again."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# =============================================================================
|
|
||||||
# Command Implementations
|
|
||||||
# =============================================================================
|
|
||||||
|
|
||||||
cmd_start() {
|
|
||||||
check_dependencies
|
|
||||||
ensure_installed
|
|
||||||
ensure_log_dir
|
|
||||||
|
|
||||||
if is_running; then
|
|
||||||
warn "I-DeskPet is already running (PID: $(get_pid))"
|
|
||||||
echo "Use 'ideskpet restart' to restart, or 'ideskpet stop' to stop."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
info "Starting I-DeskPet..."
|
|
||||||
|
|
||||||
# Start quickshell in background, redirect output to log file
|
|
||||||
nohup quickshell -p "$INSTALL_DIR" >> "$LOG_FILE" 2>&1 &
|
|
||||||
disown
|
|
||||||
|
|
||||||
# Wait a moment and check if it started successfully
|
|
||||||
sleep 1
|
|
||||||
|
|
||||||
if is_running; then
|
|
||||||
info "I-DeskPet started successfully (PID: $(get_pid))"
|
|
||||||
echo "Use 'ideskpet log -f' to view logs"
|
|
||||||
else
|
|
||||||
error "Failed to start I-DeskPet"
|
|
||||||
echo "Check logs with 'ideskpet log' for details"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd_stop() {
|
|
||||||
if ! is_running; then
|
|
||||||
warn "I-DeskPet is not running"
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
local pid
|
|
||||||
pid=$(get_pid)
|
|
||||||
|
|
||||||
info "Stopping I-DeskPet (PID: ${pid})..."
|
|
||||||
|
|
||||||
# Try graceful shutdown first
|
|
||||||
kill "$pid" 2>/dev/null || true
|
|
||||||
|
|
||||||
# Wait up to 5 seconds for graceful shutdown
|
|
||||||
local count=0
|
|
||||||
while is_running && [[ $count -lt 10 ]]; do
|
|
||||||
sleep 0.5
|
|
||||||
((count++))
|
|
||||||
done
|
|
||||||
|
|
||||||
# Force kill if still running
|
|
||||||
if is_running; then
|
|
||||||
warn "Graceful shutdown failed, force killing..."
|
|
||||||
kill -9 "$pid" 2>/dev/null || true
|
|
||||||
sleep 0.5
|
|
||||||
fi
|
|
||||||
|
|
||||||
if is_running; then
|
|
||||||
error "Failed to stop I-DeskPet"
|
|
||||||
exit 1
|
|
||||||
else
|
|
||||||
info "I-DeskPet stopped successfully"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd_restart() {
|
|
||||||
info "Restarting I-DeskPet..."
|
|
||||||
|
|
||||||
if is_running; then
|
|
||||||
cmd_stop
|
|
||||||
fi
|
|
||||||
|
|
||||||
sleep 0.5
|
|
||||||
cmd_start
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd_log() {
|
|
||||||
ensure_log_dir
|
|
||||||
|
|
||||||
if [[ ! -f "$LOG_FILE" ]]; then
|
|
||||||
warn "No log file found at ${LOG_FILE}"
|
|
||||||
echo "Start I-DeskPet first with 'ideskpet start'"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
local follow=false
|
|
||||||
local lines=50
|
|
||||||
|
|
||||||
# Parse arguments
|
|
||||||
while [[ $# -gt 0 ]]; do
|
|
||||||
case "$1" in
|
|
||||||
-f|--follow)
|
|
||||||
follow=true
|
|
||||||
shift
|
|
||||||
;;
|
|
||||||
-n|--lines)
|
|
||||||
if [[ -n "${2:-}" ]] && [[ "$2" =~ ^[0-9]+$ ]]; then
|
|
||||||
lines="$2"
|
|
||||||
shift 2
|
|
||||||
else
|
|
||||||
error "Option -n requires a numeric argument"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
error "Unknown log option: $1"
|
|
||||||
echo "Usage: ideskpet log [-f] [-n <lines>]"
|
|
||||||
exit 1
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
done
|
|
||||||
|
|
||||||
if [[ "$follow" == true ]]; then
|
|
||||||
info "Following logs (Ctrl+C to exit)..."
|
|
||||||
echo "---"
|
|
||||||
tail -n "$lines" -f "$LOG_FILE"
|
|
||||||
else
|
|
||||||
tail -n "$lines" "$LOG_FILE"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd_update() {
|
|
||||||
check_dependencies
|
|
||||||
|
|
||||||
if [[ ! -d "$INSTALL_DIR" ]]; then
|
|
||||||
error "I-DeskPet is not installed at ${INSTALL_DIR}"
|
|
||||||
echo "Run 'ideskpet start' to install it first."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
info "Updating I-DeskPet from GitHub..."
|
|
||||||
|
|
||||||
cd "$INSTALL_DIR"
|
|
||||||
|
|
||||||
# Check for local changes
|
|
||||||
if ! sudo git diff --quiet 2>/dev/null; then
|
|
||||||
warn "Local changes detected. They may be overwritten."
|
|
||||||
fi
|
|
||||||
|
|
||||||
sudo git pull
|
|
||||||
|
|
||||||
if [[ $? -eq 0 ]]; then
|
|
||||||
info "Update completed successfully"
|
|
||||||
|
|
||||||
if is_running; then
|
|
||||||
echo ""
|
|
||||||
warn "I-DeskPet is currently running."
|
|
||||||
echo "Run 'ideskpet restart' to apply changes."
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
error "Update failed"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd_shortcut() {
|
|
||||||
local shortcut="$1"
|
|
||||||
|
|
||||||
if ! command -v hyprctl &> /dev/null; then
|
|
||||||
error "hyprctl not found. Are you running Hyprland?"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
if ! is_running; then
|
|
||||||
warn "I-DeskPet is not running"
|
|
||||||
echo "Start it first with 'ideskpet start'"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
info "Triggering shortcut: ${APP_ID}:${shortcut}"
|
|
||||||
hyprctl dispatch global "${APP_ID}:${shortcut}"
|
|
||||||
}
|
|
||||||
|
|
||||||
# =============================================================================
|
|
||||||
# Main Entry Point
|
|
||||||
# =============================================================================
|
|
||||||
|
|
||||||
main() {
|
|
||||||
local cmd="${1:-}"
|
|
||||||
|
|
||||||
case "$cmd" in
|
|
||||||
start)
|
|
||||||
cmd_start
|
|
||||||
;;
|
|
||||||
stop)
|
|
||||||
cmd_stop
|
|
||||||
;;
|
|
||||||
restart)
|
|
||||||
cmd_restart
|
|
||||||
;;
|
|
||||||
log)
|
|
||||||
shift
|
|
||||||
cmd_log "$@"
|
|
||||||
;;
|
|
||||||
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)
|
|
||||||
print_version
|
|
||||||
;;
|
|
||||||
"")
|
|
||||||
print_help
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
error "Unknown command: ${cmd}"
|
|
||||||
echo "Run 'ideskpet --help' for usage information."
|
|
||||||
exit 1
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
}
|
|
||||||
|
|
||||||
main "$@"
|
|
||||||
@@ -1,183 +0,0 @@
|
|||||||
pragma ComponentBehavior: Bound
|
|
||||||
|
|
||||||
import QtQuick
|
|
||||||
import Quickshell
|
|
||||||
import Quickshell.Io
|
|
||||||
import Quickshell.Wayland
|
|
||||||
import Quickshell.Hyprland
|
|
||||||
import qs.Modules
|
|
||||||
|
|
||||||
PanelWindow {
|
|
||||||
id: mainWindow
|
|
||||||
|
|
||||||
property var noMove: Region {
|
|
||||||
}
|
|
||||||
property bool onTop: true
|
|
||||||
property var petMove: Region {
|
|
||||||
id: pets
|
|
||||||
|
|
||||||
height: Screen.height
|
|
||||||
intersection: Intersection.Xor
|
|
||||||
regions: maskVariants.instances
|
|
||||||
width: Screen.width
|
|
||||||
}
|
|
||||||
property list<Item> repeaterItems: []
|
|
||||||
property bool setMask: true
|
|
||||||
|
|
||||||
function petRegion(itemObject) {
|
|
||||||
let newregion = regionComponent.createObject(pets, {
|
|
||||||
"item": itemObject
|
|
||||||
});
|
|
||||||
pets.regions.push(newregion);
|
|
||||||
}
|
|
||||||
|
|
||||||
WlrLayershell.exclusionMode: ExclusionMode.Ignore
|
|
||||||
WlrLayershell.layer: WlrLayer.Overlay
|
|
||||||
WlrLayershell.namespace: "I-DeskPet"
|
|
||||||
color: "transparent"
|
|
||||||
surfaceFormat.opaque: false
|
|
||||||
|
|
||||||
mask: Region {
|
|
||||||
height: Screen.height
|
|
||||||
intersection: Intersection.Xor
|
|
||||||
regions: maskVariants.instances
|
|
||||||
width: Screen.width
|
|
||||||
}
|
|
||||||
|
|
||||||
anchors {
|
|
||||||
bottom: true
|
|
||||||
left: true
|
|
||||||
right: true
|
|
||||||
top: true
|
|
||||||
}
|
|
||||||
|
|
||||||
margins {
|
|
||||||
bottom: 0
|
|
||||||
left: 0
|
|
||||||
right: 0
|
|
||||||
top: 0
|
|
||||||
}
|
|
||||||
|
|
||||||
GetGifs {
|
|
||||||
id: getGifs
|
|
||||||
|
|
||||||
gifFolder: ConfigLoader.gifFolder
|
|
||||||
}
|
|
||||||
|
|
||||||
GifsLoader {
|
|
||||||
id: gifLoader
|
|
||||||
|
|
||||||
gifsModel: getGifs.gifsModel
|
|
||||||
|
|
||||||
onItemAdded: function (index, item) {
|
|
||||||
mainWindow.repeaterItems = Array.from({
|
|
||||||
length: gifLoader.count
|
|
||||||
}, (_, i) => gifLoader.itemAt(i)).filter(v => v !== null);
|
|
||||||
}
|
|
||||||
onItemRemoved: function (index, item) {
|
|
||||||
mainWindow.repeaterItems = Array.from({
|
|
||||||
length: gifLoader.count
|
|
||||||
}, (_, i) => gifLoader.itemAt(i)).filter(v => v !== null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Variants {
|
|
||||||
id: maskVariants
|
|
||||||
|
|
||||||
model: [...mainWindow.repeaterItems]
|
|
||||||
|
|
||||||
Region {
|
|
||||||
required property Item modelData
|
|
||||||
|
|
||||||
height: modelData.height
|
|
||||||
intersection: Intersection.Subtract
|
|
||||||
width: modelData.width
|
|
||||||
x: modelData.x
|
|
||||||
y: modelData.y
|
|
||||||
|
|
||||||
Component.onCompleted: {
|
|
||||||
console.log(modelData);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Component {
|
|
||||||
id: regionComponent
|
|
||||||
|
|
||||||
Region {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
GlobalShortcut {
|
|
||||||
appid: "I-DeskPet"
|
|
||||||
name: "toggle-Layer"
|
|
||||||
|
|
||||||
onPressed: {
|
|
||||||
if (!mainWindow.onTop) {
|
|
||||||
mainWindow.WlrLayershell.layer = WlrLayer.Overlay;
|
|
||||||
mainWindow.onTop = true;
|
|
||||||
} else {
|
|
||||||
mainWindow.WlrLayershell.layer = WlrLayer.Bottom;
|
|
||||||
mainWindow.onTop = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
GlobalShortcut {
|
|
||||||
appid: "I-DeskPet"
|
|
||||||
name: "toggle-Region"
|
|
||||||
|
|
||||||
onPressed: {
|
|
||||||
if (!mainWindow.setMask) {
|
|
||||||
mainWindow.mask = mainWindow.petMove;
|
|
||||||
mainWindow.setMask = true;
|
|
||||||
} else {
|
|
||||||
mainWindow.mask = mainWindow.noMove;
|
|
||||||
mainWindow.setMask = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
GlobalShortcut {
|
|
||||||
appid: "I-DeskPet"
|
|
||||||
name: "cycle-zIndex"
|
|
||||||
|
|
||||||
onPressed: {
|
|
||||||
let items = mainWindow.repeaterItems;
|
|
||||||
if (items.length < 2)
|
|
||||||
return;
|
|
||||||
|
|
||||||
// Find the hovered GIF
|
|
||||||
let hovered = null;
|
|
||||||
for (let i = 0; i < items.length; i++) {
|
|
||||||
if (items[i].hovered) {
|
|
||||||
hovered = items[i];
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!hovered)
|
|
||||||
return;
|
|
||||||
let currentZ = hovered.zIndex;
|
|
||||||
let maxZ = items.length - 1;
|
|
||||||
|
|
||||||
if (currentZ >= maxZ) {
|
|
||||||
// Already on top, wrap to bottom: shift everyone else up by 1
|
|
||||||
for (let i = 0; i < items.length; i++) {
|
|
||||||
if (items[i] !== hovered) {
|
|
||||||
items[i].zIndex += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
hovered.zIndex = 0;
|
|
||||||
} else {
|
|
||||||
// Swap with the item directly above
|
|
||||||
for (let i = 0; i < items.length; i++) {
|
|
||||||
if (items[i] !== hovered && items[i].zIndex === currentZ + 1) {
|
|
||||||
items[i].zIndex = currentZ;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
hovered.zIndex = currentZ + 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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