A tiny pixel-art desktop pet for macOS
It types along when you type, follows your cursor with its eyes, and occasionally sips coffee or dozes off and wakes up startled.
English Β· νκ΅μ΄
Grab the latest .dmg from Releases,
drag DeskPet into Applications, then right-click β Open the first time
(it's ad-hoc signed, so a plain double-click is blocked once).
Or build it yourself:
git clone https://github.com/chaeyeon-2/DeskPet.git
cd DeskPet
./Scripts/build_app.sh
open build/DeskPet.appThere's no Dock icon β look for the little face in the menu bar. The character appears in the bottom-right; drag it anywhere and the position sticks.
To make it type along with you, turn on one permission:
System Settings β Privacy & Security β Accessibility β enable
DeskPet(You'll be prompted on first launch. Everything else works without it.)
- π₯οΈ Fully local Β· zero network requests Β· no external APIs
- π Never reads what you type β only that a key was pressed, and when
- π¨ Pixel art drawn in code β no image files, crisp at every scale
- π 4 outfits Β· π¬ 11 states
- π English & Korean β menus and speech bubbles both
- π±οΈ Only the character's own pixels take clicks, so it never blocks the app underneath
Drag to move it, click to startle it, click its head 3Γ in a row to make it sulk.
From the menu-bar face icon:
| Item | What it does |
|---|---|
| Hide / Show Character | Hiding also stops the animation timer, so it uses no CPU |
| Size | Small (96pt) / Medium (144pt) / Large (192pt) |
| Outfit | Blue Check Shirt / Orange Puffer / I β₯ KIXLAB Tee / LG Twins Jersey |
| Speech Bubbles Β· Frequency | Off / Rare Β· Normal Β· Often |
| Language | Match System / νκ΅μ΄ / English β switches menus and speech lines |
| Sound | Typing clicks (off by default) |
| Launch at Login | |
| Typing access | Shows current permission state, opens System Settings |
| Reset Position Β· Quit |
All 11 states
| State | When |
|---|---|
idle |
Default β slow breathing |
blink |
Every 3β7 seconds |
lookLeft / lookRight |
Follows a nearby cursor with eyes and a head tilt |
adjustGlasses |
Occasionally pushes the glasses back up |
typingSlow / typingFast |
While you type (4+ keys/sec switches to fast hands) |
drinkCoffee |
Occasionally lifts a mug |
sleepy |
Dozes off after a long quiet spell β wakes up as surprised |
surprised |
When you click it (glasses go slightly crooked) |
sulking |
After 3 quick clicks on its head |
Only one special action runs at a time, with at least 9 seconds between them.
Everything lives in Sources/DeskPetCore/SpeechLibrary.swift.
public static let lines: [SpeechLine] = [
SpeechLine(id: "hmm", ko: "ν β¦", en: "Hmmβ¦", weight: 3),
SpeechLine(id: "coffee", ko: "μ»€νΌ λ§μ€λ?", en: "Coffee?", weight: 2),
SpeechLine(id: "lunch", ko: "μ€λ μ μ¬μ?", en: "What's for lunch?", weight: 4, hours: [11, 12, 13]),
]ko/enβ write both; the app picks one based on the Language settingweightβ higher shows up more often (Slacking off?is 0.4, the rarest)hoursβ restrict a line to certain hours of the day (omit for all day)pokeLinesin the same file are the short replies when you click the characterSpeechFrequencyholds the Rare / Normal / Often timing presets
The character is drawn in code, so there are no image files to edit. Two ways to change it:
Swap in PNGs (no rebuild) β the source canvas is 64 Γ 48 pixels.
# 1. Export what's there now
./build/DeskPet.app/Contents/MacOS/DeskPet --export-sprites ~/Desktop/Sprites --scale 4
# 2. Drop edited PNGs here under the same names
# ~/Library/Application Support/DeskPet/Sprites/
# idle_0.png, typingFast_2.png, orangePuffer_idle_0.png ...Names are <state>_<frame>.png, prefixed with the outfit for non-default outfits.
Anything missing falls back to the built-in art. (Sources/DeskPetApp/SpriteImageProvider.swift)
Edit the code
| File | What's in it |
|---|---|
DeskPetCore/Palette.swift |
Colors |
DeskPetCore/CharacterSprite.swift |
Hair, glasses, outfits, keyboard, desk, mug |
DeskPetCore/Pose.swift |
Per-frame pose parameters + Outfit |
DeskPetCore/AnimationLibrary.swift |
Frames and durations per state |
DeskPetCore/MicroFont.swift |
3Γ5 pixel font used to print text on clothes |
You never hand-draw frames β a new animation is just new Pose values.
Scaling is always nearest-neighbor, so pixels stay sharp at any size.
swift build
swift run DeskPetTests # 78 unit tests
./.build/debug/DeskPet --selftest ./selftest # 42 checks against a real window (+ PNG captures)Sources/
ββ DeskPetCore/ Pure logic, no AppKit β all of it testable
β CharacterSprite Β· Pose Β· AnimationLibrary Β· PetState drawing & animation
β PetBrain Β· TypingTracker Β· GazeSolver state machine
β SpeechLibrary Β· SpeechDirector speech bubbles
β PixelCanvas Β· Palette Β· MicroFont Β· AppIcon pixel engine
β L10n English / Korean strings
β ScreenPlacement Β· PetSize Β· SpriteExport
ββ DeskPetApp/ AppKit / SwiftUI
PetPanel Β· PetWindowController Β· PetView Β· PetViewModel
KeyActivityMonitor Β· MenuBarController Β· PermissionOnboarding
LaunchAtLogin Β· SoundPlayer Β· Preferences Β· SelfTest Β· Diagnose
Anything that doesn't need the screen goes in DeskPetCore β that's what keeps it testable.
The --selftest mode opens the real window and verifies transparency, dragging,
position persistence, typing reaction, gaze tracking and speech bubbles, then saves
PNG captures of the window. Attach those to a PR when you change the artwork.
Without full Xcode,
XCTestisn't available, soSources/TinyTestprovides a tiny runner with the same API (XCTestCase+XCTAssert*). With Xcode installed you can switchimport TinyTestback toimport XCTestand restore the.testTargetinPackage.swiftto useswift test.
./Scripts/package.shProduces a universal binary (Apple Silicon + Intel) as a DMG and ZIP in dist/,
and verifies the signature inside the mounted DMG β a step that matters, because
hdiutil adds a com.apple.FinderInfo xattr that silently breaks signature
validation, and macOS then ignores the Accessibility permission entirely.
With an Apple Developer account it signs and notarizes too:
xcrun notarytool store-credentials deskpet-notary \
--apple-id you@example.com --team-id TEAMID --password app-specific-password
DESKPET_SIGN_ID="Developer ID Application: Name (TEAMID)" \
DESKPET_NOTARY_PROFILE="deskpet-notary" ./Scripts/package.shWithout one it falls back to ad-hoc signing, and recipients need right-click β Open once.
It doesn't type along
/Applications/DeskPet.app/Contents/MacOS/DeskPet --diagnose /tmp/deskpet.txt
# type for 20 seconds, then
cat /tmp/deskpet.txtAXIsProcessTrusted: false means the Accessibility permission isn't active.
If you're sure you enabled it and it's still false, the signature is probably broken:
codesign --verify --strict /Applications/DeskPet.appresource fork, Finder information, or similar detritus not allowed means a
Finder-added extended attribute broke it β and macOS then ignores the permission
toggle even though it looks enabled.
xattr -cr /Applications/DeskPet.app # strips xattrs only; the signature hash is untouched, so the permission survivesRebuilding the app changes its ad-hoc signature hash and revokes the permission.
Remove DeskPet from the Accessibility list with β and add it back with +.
This is the app's most important promise.
- It only uses that a key was pressed and when.
- It never reads or stores which key (
characters,keyCode), your passwords, which app you're in, or any document contents. There is no code path for it βPetBrain.registerKeystroke(at:)takes a timestamp and nothing else. - It makes no network requests at all.
- When macOS turns on Secure Input (password fields), it doesn't hang β it just falls back to idle animations.
New outfits, animations and speech lines are all welcome. See CONTRIBUTING.md.
| Want to | Touch |
|---|---|
| Add a speech line | One line in SpeechLibrary.swift (write both ko and en) |
| Add an outfit | One Outfit case + one draw function β the menu picks it up automatically |
| Add an animation | A PetState case + frames (Pose values) in AnimationLibrary |
MIT β use it, change it, ship it.
Two things sit outside the code license, though:
- The character's face is a pixel character based on a real person's photo. The original photo isn't in this repo, but please don't use a recognizable likeness commercially or to impersonate anyone.
- The LG Twins jersey is fan-made pixel art. The team name and uniform design belong to the club β personal, non-commercial use only.
Reference photos were used for design only and are not included in this repository. No images of unclear provenance were used.
