Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion src/caelestia/parser.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import argparse

from caelestia.subcommands import clipboard, emoji, record, resizer, scheme, screenshot, shell, toggle, wallpaper
from caelestia.subcommands import clipboard, emoji, record, resizer, rshell, scheme, screenshot, shell, timer, toggle, wallpaper
from caelestia.utils.paths import wallpapers_dir
from caelestia.utils.scheme import get_scheme_names, scheme_variants
from caelestia.utils.wallpaper import get_wallpaper
Expand Down Expand Up @@ -126,4 +126,16 @@ def parse_args() -> (argparse.ArgumentParser, argparse.Namespace):
resizer_parser.add_argument("height", nargs="?", help="height to resize to")
resizer_parser.add_argument("actions", nargs="?", help="comma-separated actions to apply (float,center,pip)")

# Create parser for timer opts
timer_parser = command_parser.add_parser("timer", help="manage timers")
timer_parser.set_defaults(cls=timer.Command)
timer_parser.add_argument("duration", nargs="?", help="set a timer for DURATION minutes")
timer_parser.add_argument("-l", "--list", action="store_true", help="list all running timers")
timer_parser.add_argument("-s", "--show", type=int, metavar="INDEX", help="show remaining time for timer at INDEX")
timer_parser.add_argument("-q", "--quit", type=int, metavar="INDEX", help="quit timer at INDEX")

# Create parser for rshell opts
rshell_parser = command_parser.add_parser("rshell", help="restart the caelestia shell")
rshell_parser.set_defaults(cls=rshell.Command)

return parser, parser.parse_args()
25 changes: 25 additions & 0 deletions src/caelestia/subcommands/rshell.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import subprocess
import os
from argparse import Namespace


class Command:
args: Namespace

def __init__(self, args: Namespace) -> None:
self.args = args

def run(self) -> None:
script_path = os.path.join(os.path.dirname(__file__), "scripts", "rshell")

# Execute the rshell script
try:
result = subprocess.run([script_path], capture_output=True, text=True)
if result.stdout:
print(result.stdout, end='')
if result.stderr:
print(result.stderr, end='')
exit(result.returncode)
except Exception as e:
print(f"Error executing rshell script: {e}")
exit(1)
5 changes: 5 additions & 0 deletions src/caelestia/subcommands/scripts/rshell
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#!/bin/bash
echo "Starting shell restart"
qs -c caelestia kill
sleep 3
caelestia shell -d
192 changes: 192 additions & 0 deletions src/caelestia/subcommands/scripts/timer
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
#!/bin/bash

# Directory to store timer information
TIMER_DIR="$HOME/.timers"
mkdir -p "$TIMER_DIR"

# File to store timer PIDs, start times, and durations
TIMER_FILE="$TIMER_DIR/timers"

# Function to display help message
show_help() {
echo "Usage: ti [OPTION] [ARGUMENT]"
echo "A simple timer script that sets timers and sends notifications."
echo ""
echo "Options:"
echo " -h, --help Display this help message and exit"
echo " -l, --list List all running timers"
echo " -s, --show INDEX Show remaining time for timer at INDEX"
echo " -q, --quit INDEX Quit timer at INDEX"
echo " MINUTES Set a timer for MINUTES"
echo ""
echo "Examples:"
echo " ti 30 Set a 30-minute timer"
echo " ti -l List all running timers"
echo " ti -s 0 Show remaining time for timer at index 0"
echo " ti -q 0 Quit timer at index 0"
echo " ti Display this help message"
exit 0
}

# Function to clean up finished timers
clean_timers() {
if [ ! -s "$TIMER_FILE" ]; then
return
fi

# Create a temporary file for active timers
temp_file=$(mktemp)
while IFS=: read -r pid start duration; do
if ps -p "$pid" > /dev/null; then
echo "$pid:$start:$duration" >> "$temp_file"
fi
done < "$TIMER_FILE"

# Replace the original file with the cleaned-up version
mv "$temp_file" "$TIMER_FILE"
}

# Function to quit a specific timer
quit_timer() {
local index="$1"
local count=0
local found=0
local temp_file=$(mktemp)

# Clean up finished timers before quitting
clean_timers

# Read the timer file and process the specified index
while IFS=: read -r pid start duration; do
if [ "$count" -eq "$index" ] && ps -p "$pid" > /dev/null; then
found=1
kill "$pid" 2>/dev/null
echo "Timer $index has been terminated."
else
echo "$pid:$start:$duration" >> "$temp_file"
fi
((count++))
done < "$TIMER_FILE"

# Update the timer file
mv "$temp_file" "$TIMER_FILE"

if [ "$found" -eq 0 ]; then
echo "Error: No timer found at index $index or timer has already finished."
exit 1
fi
}

# Function to display remaining time for a specific timer
show_timer() {
local index="$1"
local count=0
local found=0

# Clean up finished timers before showing
clean_timers

# Read the timer file and process the specified index
while IFS=: read -r pid start duration; do
if [ "$count" -eq "$index" ] && ps -p "$pid" > /dev/null; then
found=1
current_time=$(date +%s)
elapsed=$(echo "$current_time - $start" | bc -l)
remaining=$(echo "$duration - $elapsed" | bc -l)
if [ "$(echo "$remaining > 0" | bc -l)" -eq 1 ]; then
minutes=$(echo "$remaining / 60" | bc -l | awk '{printf "%.2f", $0}')
echo "Timer $index: $minutes minutes remaining"
else
echo "Timer $index: Already finished"
fi
break
fi
((count++))
done < "$TIMER_FILE"

if [ "$found" -eq 0 ]; then
echo "Error: No timer found at index $index or timer has already finished."
exit 1
fi
}

# Function to list all running timers
list_timers() {
# Clean up finished timers before listing
clean_timers

if [ ! -s "$TIMER_FILE" ]; then
echo "No timers are currently running."
exit 0
fi

local count=0
while IFS=: read -r pid start duration; do
if ps -p "$pid" > /dev/null; then
minutes=$(echo "$duration / 60" | bc -l | awk '{printf "%.2f", $0}')
echo "Timer $count: Set for $minutes minutes"
fi
((count++))
done < "$TIMER_FILE"
}

# Check for flags or no arguments
case "$1" in
-h|--help)
show_help
;;
-l|--list)
list_timers
;;
-s|--show)
if [ -z "$2" ]; then
echo "Error: Please provide a timer index with -s or --show."
exit 1
fi
if ! [[ "$2" =~ ^[0-9]+$ ]]; then
echo "Error: Timer index must be a non-negative integer."
exit 1
fi
show_timer "$2"
;;
-q|--quit)
if [ -z "$2" ]; then
echo "Error: Please provide a timer index with -q or --quit."
exit 1
fi
if ! [[ "$2" =~ ^[0-9]+$ ]]; then
echo "Error: Timer index must be a non-negative integer."
exit 1
fi
quit_timer "$2"
;;
"")
show_help
;;
*)
# Validate input is a positive number (integer or float)
minutes="$1"
if ! [[ "$minutes" =~ ^[0-9]*\.?[0-9]+$ ]] || [ "$(echo "$minutes <= 0" | bc -l)" -eq 1 ]; then
echo "Error: Please provide a positive number for minutes (e.g., 60 or 0.5)."
exit 1
fi

# Convert minutes to seconds for sleep (handles floats)
seconds=$(echo "$minutes * 60" | bc -l)

# Command to execute when timer finishes
notify_cmd="notify-send -u low -i dialog-information-symbolic 'Timer' '$minutes minute timer done!'"
sound_cmd="paplay /usr/share/sounds/freedesktop/stereo/complete.oga"

# Get current timestamp
start_time=$(date +%s)

# Run sleep in the background and trigger notification and sound
(sleep "$seconds" && eval "$notify_cmd" && eval "$sound_cmd") &

# Store PID, start time, and duration in the timer file
echo "$!:$start_time:$seconds" >> "$TIMER_FILE"

echo "Timer set for $minutes minutes ($seconds seconds). Notification and sound will trigger when done."
;;
esac
38 changes: 38 additions & 0 deletions src/caelestia/subcommands/timer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import subprocess
import os
from argparse import Namespace


class Command:
args: Namespace

def __init__(self, args: Namespace) -> None:
self.args = args

def run(self) -> None:
script_path = os.path.join(os.path.dirname(__file__), "scripts", "timer")

# Build the command arguments
cmd = [script_path]

# Pass through all arguments from argparse to the script
if hasattr(self.args, 'list') and self.args.list:
cmd.extend(['-l'])
elif hasattr(self.args, 'show') and self.args.show is not None:
cmd.extend(['-s', str(self.args.show)])
elif hasattr(self.args, 'quit') and self.args.quit is not None:
cmd.extend(['-q', str(self.args.quit)])
elif hasattr(self.args, 'duration') and self.args.duration is not None:
cmd.append(str(self.args.duration))

# Execute the timer script
try:
result = subprocess.run(cmd, capture_output=True, text=True)
if result.stdout:
print(result.stdout, end='')
if result.stderr:
print(result.stderr, end='')
exit(result.returncode)
except Exception as e:
print(f"Error executing timer script: {e}")
exit(1)