summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2025-07-19 16:37:04 +0300
committerPaul Buetow <paul@buetow.org>2025-07-19 16:37:04 +0300
commit77538381c28e4de8fa7d3f3731412c97699bb889 (patch)
tree6abc09587e3dcb279f86f9c63c848ebfbee7b926
parent14357aee5c0db8665bea2da817d67f12d0499451 (diff)
feat: add app icon and change default output directory
- Add custom app icon with memory/brain theme - Icon now displays in GNOME app menu, window title bar, and process list - Change default output directory to ~/.local/state/totalrecall/ - Make output directory configurable via CLI flag, config file, or env var - Add desktop entry file for GNOME integration - Add install script for easy icon installation - Update README with icon display and default directory information - Bump version to 0.5.1 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
-rw-r--r--.gitignore5
-rw-r--r--README.md52
-rw-r--r--assets/icons/totalrecall.svg66
-rw-r--r--assets/icons/totalrecall_128.pngbin0 -> 26276 bytes
-rw-r--r--assets/icons/totalrecall_16.pngbin0 -> 1700 bytes
-rw-r--r--assets/icons/totalrecall_256.pngbin0 -> 62217 bytes
-rw-r--r--assets/icons/totalrecall_32.pngbin0 -> 4560 bytes
-rw-r--r--assets/icons/totalrecall_48.pngbin0 -> 7238 bytes
-rw-r--r--assets/icons/totalrecall_512.pngbin0 -> 40153 bytes
-rw-r--r--assets/icons/totalrecall_64.pngbin0 -> 10653 bytes
-rw-r--r--cmd/totalrecall/main.go11
-rwxr-xr-xinstall-icon.sh66
-rw-r--r--internal/gui/app.go6
-rw-r--r--internal/gui/icon.go17
-rw-r--r--internal/gui/totalrecall_256.pngbin0 -> 62217 bytes
-rw-r--r--internal/version.go2
-rw-r--r--totalrecall.desktop13
17 files changed, 232 insertions, 6 deletions
diff --git a/.gitignore b/.gitignore
index 20e144e..4a70ff3 100644
--- a/.gitignore
+++ b/.gitignore
@@ -44,3 +44,8 @@ anki_cards
# Configuration with API keys
.totalrecall.yaml
.image_cache/
+
+# Test output directories
+test_output/
+test_output2/
+test_phonetic/
diff --git a/README.md b/README.md
index 24bfbf3..828d991 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,9 @@
# totalrecall - Bulgarian Anki Flashcard Generator
+<p align="center">
+ <img src="assets/icons/totalrecall_512.png" alt="TotalRecall Icon" width="256" height="256">
+</p>
+
`totalrecall` is a versatile tool for generating Anki flashcard materials from Bulgarian words. It offers both a command-line interface (CLI) and a graphical user interface (GUI) for creating audio pronunciation files and AI-generated images.
It has mainly been vibe coded using Claude Code CLI.
@@ -22,6 +26,7 @@ It has mainly been vibe coded using Claude Code CLI.
- Anki-compatible CSV export with translations
- Random voice variants and speech speed
- Audio caching to save API costs
+- **Default output directory**: `~/.local/state/totalrecall/` (configurable)
## Installation
@@ -42,12 +47,45 @@ cd totalrecall
go build -o totalrecall ./cmd/totalrecall
```
-Or install directly:
+### Installing to Go Bin Directory
+
+Using Task (recommended):
+```bash
+cd totalrecall
+task install
+```
+
+Or using go install directly:
+```bash
+cd totalrecall
+go install ./cmd/totalrecall
+```
+Or install from remote repository:
```bash
go install codeberg.org/snonux/totalrecall/cmd/totalrecall@latest
```
+This will install the binary to `~/go/bin/totalrecall`, which should be in your PATH.
+
+### Desktop Icon Installation (GNOME/Fedora)
+
+TotalRecall includes a desktop icon for GNOME integration. To install:
+
+**For current user only:**
+```bash
+cd totalrecall
+./install-icon.sh
+```
+
+**System-wide installation:**
+```bash
+cd totalrecall
+sudo ./install-icon.sh
+```
+
+After installation, you may need to log out and log back in for the icon to appear in GNOME's application menu. The icon will show up as "TotalRecall" in the Education category.
+
## Quick Start
**Note:** By default, totalrecall uses OpenAI for both audio and images. Make sure to set your OpenAI API key:
@@ -92,7 +130,13 @@ Launch the interactive graphical interface:
totalrecall --gui
```
-Then use keyboard shortcuts or buttons to generate and manage flashcards interactively.
+The GUI is best navigated using keyboard shortcuts for efficient workflow. Press **`h`** at any time to display a complete list of all available keyboard shortcuts.
+
+Key features:
+- Fast keyboard-driven interface
+- Real-time audio playback
+- Batch processing support
+- Visual feedback for all operations
## Configuration
@@ -123,7 +167,7 @@ image:
openai_style: "natural" # Style: natural or vivid (dall-e-3 only)
output:
- directory: ./anki_cards
+ directory: ~/.local/state/totalrecall # Default location (can be overridden)
naming: "{word}_{type}"
```
@@ -174,6 +218,8 @@ When translations are provided, they are used directly without calling the trans
### Output Files
+By default, all files are saved to `~/.local/state/totalrecall/`. You can override this with the `-o` flag or the `output.directory` config option.
+
For each word, the tool generates:
- `word.mp3` - Audio pronunciation (random voice)
- `word_translation.txt` - English translation
diff --git a/assets/icons/totalrecall.svg b/assets/icons/totalrecall.svg
new file mode 100644
index 0000000..145018e
--- /dev/null
+++ b/assets/icons/totalrecall.svg
@@ -0,0 +1,66 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="512" height="512" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg">
+ <!-- Background circle with gradient -->
+ <defs>
+ <linearGradient id="bgGradient" x1="0%" y1="0%" x2="100%" y2="100%">
+ <stop offset="0%" style="stop-color:#1565C0;stop-opacity:1" />
+ <stop offset="100%" style="stop-color:#0D47A1;stop-opacity:1" />
+ </linearGradient>
+ <linearGradient id="brainGradient" x1="0%" y1="0%" x2="100%" y2="100%">
+ <stop offset="0%" style="stop-color:#E91E63;stop-opacity:1" />
+ <stop offset="50%" style="stop-color:#9C27B0;stop-opacity:1" />
+ <stop offset="100%" style="stop-color:#673AB7;stop-opacity:1" />
+ </linearGradient>
+ </defs>
+
+ <!-- Background circle -->
+ <circle cx="256" cy="256" r="240" fill="url(#bgGradient)"/>
+
+ <!-- Brain outline with memory circuits -->
+ <g transform="translate(256, 256)">
+ <!-- Brain shape -->
+ <path d="M -80,-60 C -80,-100 -40,-120 0,-120 C 40,-120 80,-100 80,-60 C 80,-40 70,-20 60,0 C 70,20 80,40 80,60 C 80,100 40,120 0,120 C -40,120 -80,100 -80,60 C -80,40 -70,20 -60,0 C -70,-20 -80,-40 -80,-60 Z"
+ fill="url(#brainGradient)" stroke="#FFFFFF" stroke-width="6"/>
+
+ <!-- Central dividing line -->
+ <path d="M 0,-120 C -20,-80 20,-40 0,0 C -20,40 20,80 0,120"
+ stroke="#FFFFFF" stroke-width="4" fill="none"/>
+
+ <!-- Memory circuit patterns on left hemisphere -->
+ <g opacity="0.8">
+ <circle cx="-40" cy="-40" r="8" fill="#FFFFFF"/>
+ <circle cx="-50" cy="0" r="8" fill="#FFFFFF"/>
+ <circle cx="-40" cy="40" r="8" fill="#FFFFFF"/>
+ <path d="M -40,-40 L -50,0 L -40,40" stroke="#FFFFFF" stroke-width="3" fill="none"/>
+ <path d="M -40,-40 L -20,-60" stroke="#FFFFFF" stroke-width="2" fill="none"/>
+ <path d="M -50,0 L -70,0" stroke="#FFFFFF" stroke-width="2" fill="none"/>
+ <path d="M -40,40 L -20,60" stroke="#FFFFFF" stroke-width="2" fill="none"/>
+ </g>
+
+ <!-- Memory circuit patterns on right hemisphere -->
+ <g opacity="0.8">
+ <circle cx="40" cy="-40" r="8" fill="#FFFFFF"/>
+ <circle cx="50" cy="0" r="8" fill="#FFFFFF"/>
+ <circle cx="40" cy="40" r="8" fill="#FFFFFF"/>
+ <path d="M 40,-40 L 50,0 L 40,40" stroke="#FFFFFF" stroke-width="3" fill="none"/>
+ <path d="M 40,-40 L 20,-60" stroke="#FFFFFF" stroke-width="2" fill="none"/>
+ <path d="M 50,0 L 70,0" stroke="#FFFFFF" stroke-width="2" fill="none"/>
+ <path d="M 40,40 L 20,60" stroke="#FFFFFF" stroke-width="2" fill="none"/>
+ </g>
+
+ <!-- Glowing effect dots representing memories -->
+ <circle cx="-30" cy="-20" r="4" fill="#FFD700" opacity="0.9"/>
+ <circle cx="30" cy="-20" r="4" fill="#FFD700" opacity="0.9"/>
+ <circle cx="0" cy="20" r="4" fill="#FFD700" opacity="0.9"/>
+ <circle cx="-25" cy="30" r="3" fill="#FFD700" opacity="0.7"/>
+ <circle cx="25" cy="30" r="3" fill="#FFD700" opacity="0.7"/>
+
+ <!-- Flashcard hint in bottom corner -->
+ <g transform="translate(100, 100)">
+ <rect x="-30" y="-20" width="60" height="40" rx="4" fill="#FFFFFF" stroke="#333333" stroke-width="2" opacity="0.9"/>
+ <line x1="-20" y1="-10" x2="20" y2="-10" stroke="#333333" stroke-width="1" opacity="0.5"/>
+ <line x1="-20" y1="0" x2="20" y2="0" stroke="#333333" stroke-width="1" opacity="0.5"/>
+ <line x1="-20" y1="10" x2="20" y2="10" stroke="#333333" stroke-width="1" opacity="0.5"/>
+ </g>
+ </g>
+</svg> \ No newline at end of file
diff --git a/assets/icons/totalrecall_128.png b/assets/icons/totalrecall_128.png
new file mode 100644
index 0000000..cce572f
--- /dev/null
+++ b/assets/icons/totalrecall_128.png
Binary files differ
diff --git a/assets/icons/totalrecall_16.png b/assets/icons/totalrecall_16.png
new file mode 100644
index 0000000..381f124
--- /dev/null
+++ b/assets/icons/totalrecall_16.png
Binary files differ
diff --git a/assets/icons/totalrecall_256.png b/assets/icons/totalrecall_256.png
new file mode 100644
index 0000000..1d5c297
--- /dev/null
+++ b/assets/icons/totalrecall_256.png
Binary files differ
diff --git a/assets/icons/totalrecall_32.png b/assets/icons/totalrecall_32.png
new file mode 100644
index 0000000..1611629
--- /dev/null
+++ b/assets/icons/totalrecall_32.png
Binary files differ
diff --git a/assets/icons/totalrecall_48.png b/assets/icons/totalrecall_48.png
new file mode 100644
index 0000000..e33f4ec
--- /dev/null
+++ b/assets/icons/totalrecall_48.png
Binary files differ
diff --git a/assets/icons/totalrecall_512.png b/assets/icons/totalrecall_512.png
new file mode 100644
index 0000000..724aaf3
--- /dev/null
+++ b/assets/icons/totalrecall_512.png
Binary files differ
diff --git a/assets/icons/totalrecall_64.png b/assets/icons/totalrecall_64.png
new file mode 100644
index 0000000..824dc55
--- /dev/null
+++ b/assets/icons/totalrecall_64.png
Binary files differ
diff --git a/cmd/totalrecall/main.go b/cmd/totalrecall/main.go
index 7fffbaf..f8039a8 100644
--- a/cmd/totalrecall/main.go
+++ b/cmd/totalrecall/main.go
@@ -73,11 +73,15 @@ func init() {
// Initialize random number generator
rand.Seed(time.Now().UnixNano())
+ // Set default output directory
+ home, _ := os.UserHomeDir()
+ defaultOutputDir := filepath.Join(home, ".local", "state", "totalrecall")
+
// Global flags
rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.totalrecall.yaml)")
// Local flags
- rootCmd.Flags().StringVarP(&outputDir, "output", "o", "./anki_cards", "Output directory")
+ rootCmd.Flags().StringVarP(&outputDir, "output", "o", defaultOutputDir, "Output directory")
rootCmd.Flags().StringVarP(&audioFormat, "format", "f", "mp3", "Audio format (wav or mp3)")
rootCmd.Flags().StringVar(&imageAPI, "image-api", "openai", "Image source (only openai supported)")
rootCmd.Flags().StringVar(&batchFile, "batch", "", "Process words from file (one per line)")
@@ -151,6 +155,11 @@ func initConfig() {
}
func runCommand(cmd *cobra.Command, args []string) error {
+ // Check if output directory was set in config file
+ if !cmd.Flags().Changed("output") && viper.IsSet("output.directory") {
+ outputDir = viper.GetString("output.directory")
+ }
+
// Handle --list-models flag
if listModels {
return listAvailableModels()
diff --git a/install-icon.sh b/install-icon.sh
new file mode 100755
index 0000000..dd5c224
--- /dev/null
+++ b/install-icon.sh
@@ -0,0 +1,66 @@
+#!/bin/bash
+# Script to install TotalRecall icon for GNOME on Fedora Linux
+# Run with sudo for system-wide installation
+
+set -e
+
+# Check if running as root for system-wide install
+if [[ $EUID -eq 0 ]]; then
+ echo "Installing TotalRecall icon system-wide..."
+ ICON_BASE="/usr/share/icons/hicolor"
+ APP_DIR="/usr/share/applications"
+ BINARY_PATH="/usr/local/bin/totalrecall"
+else
+ echo "Installing TotalRecall icon for current user..."
+ ICON_BASE="$HOME/.local/share/icons/hicolor"
+ APP_DIR="$HOME/.local/share/applications"
+ BINARY_PATH="$HOME/go/bin/totalrecall"
+fi
+
+# Create directories
+mkdir -p "$ICON_BASE"/{16x16,32x32,48x48,64x64,128x128,256x256,512x512}/apps
+mkdir -p "$APP_DIR"
+
+# Copy icons
+cp assets/icons/totalrecall_16.png "$ICON_BASE/16x16/apps/totalrecall.png"
+cp assets/icons/totalrecall_32.png "$ICON_BASE/32x32/apps/totalrecall.png"
+cp assets/icons/totalrecall_48.png "$ICON_BASE/48x48/apps/totalrecall.png"
+cp assets/icons/totalrecall_64.png "$ICON_BASE/64x64/apps/totalrecall.png"
+cp assets/icons/totalrecall_128.png "$ICON_BASE/128x128/apps/totalrecall.png"
+cp assets/icons/totalrecall_256.png "$ICON_BASE/256x256/apps/totalrecall.png"
+cp assets/icons/totalrecall_512.png "$ICON_BASE/512x512/apps/totalrecall.png"
+
+# Copy scalable icon
+mkdir -p "$ICON_BASE/scalable/apps"
+cp assets/icons/totalrecall.svg "$ICON_BASE/scalable/apps/totalrecall.svg"
+
+# Copy and update desktop file with correct binary path
+# First check if totalrecall is in standard locations
+if command -v totalrecall &> /dev/null; then
+ TOTALRECALL_PATH=$(command -v totalrecall)
+elif [[ -x "$HOME/go/bin/totalrecall" ]]; then
+ TOTALRECALL_PATH="$HOME/go/bin/totalrecall"
+elif [[ -x "/usr/local/bin/totalrecall" ]]; then
+ TOTALRECALL_PATH="/usr/local/bin/totalrecall"
+elif [[ -x "/usr/bin/totalrecall" ]]; then
+ TOTALRECALL_PATH="/usr/bin/totalrecall"
+else
+ echo "Warning: totalrecall binary not found in standard locations"
+ echo "Please install totalrecall first with 'task install' or 'go install ./cmd/totalrecall'"
+ TOTALRECALL_PATH="totalrecall"
+fi
+
+# Create desktop file with proper exec path
+sed "s|Exec=totalrecall|Exec=$TOTALRECALL_PATH|g" totalrecall.desktop > "$APP_DIR/totalrecall.desktop"
+
+# Update caches
+if command -v gtk-update-icon-cache &> /dev/null; then
+ gtk-update-icon-cache -f -t "$ICON_BASE" 2>/dev/null || true
+fi
+
+if command -v update-desktop-database &> /dev/null; then
+ update-desktop-database "$APP_DIR" 2>/dev/null || true
+fi
+
+echo "TotalRecall icon installed successfully!"
+echo "You may need to log out and log back in to see the icon in GNOME." \ No newline at end of file
diff --git a/internal/gui/app.go b/internal/gui/app.go
index 31d2460..2226eb1 100644
--- a/internal/gui/app.go
+++ b/internal/gui/app.go
@@ -112,8 +112,11 @@ func New(config *Config) *Application {
ctx, cancel := context.WithCancel(context.Background())
+ myApp := app.New()
+ myApp.SetIcon(GetAppIcon())
+
app := &Application{
- app: app.New(),
+ app: myApp,
config: config,
ctx: ctx,
cancel: cancel,
@@ -152,6 +155,7 @@ func New(config *Config) *Application {
// setupUI creates the main user interface
func (a *Application) setupUI() {
a.window = a.app.NewWindow(fmt.Sprintf("TotalRecall v%s - Bulgarian Flashcard Generator", internal.Version))
+ a.window.SetIcon(GetAppIcon())
a.window.Resize(fyne.NewSize(800, 700))
// Create input section with navigation
diff --git a/internal/gui/icon.go b/internal/gui/icon.go
new file mode 100644
index 0000000..f778225
--- /dev/null
+++ b/internal/gui/icon.go
@@ -0,0 +1,17 @@
+package gui
+
+import (
+ _ "embed"
+ "fyne.io/fyne/v2"
+)
+
+//go:embed totalrecall_256.png
+var iconData []byte
+
+// GetAppIcon returns the application icon as a Fyne resource
+func GetAppIcon() fyne.Resource {
+ return &fyne.StaticResource{
+ StaticName: "totalrecall.png",
+ StaticContent: iconData,
+ }
+} \ No newline at end of file
diff --git a/internal/gui/totalrecall_256.png b/internal/gui/totalrecall_256.png
new file mode 100644
index 0000000..1d5c297
--- /dev/null
+++ b/internal/gui/totalrecall_256.png
Binary files differ
diff --git a/internal/version.go b/internal/version.go
index c658e16..360eeb8 100644
--- a/internal/version.go
+++ b/internal/version.go
@@ -1,3 +1,3 @@
package internal
-const Version = "0.5.0"
+const Version = "0.5.1"
diff --git a/totalrecall.desktop b/totalrecall.desktop
new file mode 100644
index 0000000..41be16e
--- /dev/null
+++ b/totalrecall.desktop
@@ -0,0 +1,13 @@
+[Desktop Entry]
+Version=1.0
+Type=Application
+Name=TotalRecall
+GenericName=Bulgarian Flashcard Generator
+Comment=Generate Anki flashcard materials from Bulgarian words
+Exec=totalrecall --gui
+Icon=totalrecall
+Terminal=false
+Categories=Education;Languages;
+Keywords=bulgarian;flashcards;anki;language;learning;
+StartupNotify=true
+StartupWMClass=totalrecall \ No newline at end of file