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
58 changes: 55 additions & 3 deletions frontends/desktop_pet.pyw
Original file line number Diff line number Diff line change
@@ -1,10 +1,47 @@
"""Desktop Pet with HTTP Toast — ~90 lines"""
import tkinter as tk, threading, random, os, sys
import tkinter as tk, threading, random, os, sys, socket
from http.server import HTTPServer, BaseHTTPRequestHandler
from urllib.parse import urlparse, parse_qs

PORT = 51983
GIF = os.path.join(os.path.dirname(os.path.abspath(sys.argv[0])), 'pet.gif')
DEFAULT_PORT = 51983
SCRIPT_DIR = os.path.dirname(os.path.abspath(sys.argv[0]))
PROJECT_DIR = os.path.dirname(SCRIPT_DIR)
PORT_FILE = os.path.join(PROJECT_DIR, 'temp', 'desktop_pet_port.txt')
PORT = DEFAULT_PORT
GIF = os.path.join(SCRIPT_DIR, 'pet.gif')


def _read_saved_port():
try:
return int(open(PORT_FILE, encoding='utf-8').read().strip())
except Exception:
return None


def _write_saved_port(port):
os.makedirs(os.path.dirname(PORT_FILE), exist_ok=True)
with open(PORT_FILE, 'w', encoding='utf-8') as f:
f.write(str(port))


def _can_bind(port):
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try:
sock.bind(('127.0.0.1', port))
return True
except OSError:
return False
finally:
sock.close()


def _pick_port(preferred=DEFAULT_PORT, lo=45183, hi=45283):
if _can_bind(preferred):
return preferred
for port in range(lo, hi + 1):
if _can_bind(port):
return port
raise OSError('No usable local port found for desktop pet')

class Pet:
def __init__(self):
Expand Down Expand Up @@ -85,9 +122,24 @@ class Pet:
def log_message(self, *a): pass
HTTPServer.allow_reuse_address = False
srv = HTTPServer(('127.0.0.1', PORT), H)
_write_saved_port(PORT)
t = threading.Thread(target=srv.serve_forever, daemon=True)
t.start()
print(f'Toast server: http://127.0.0.1:{PORT}/?msg=hello')

if __name__ == '__main__':
existing_port = _read_saved_port()
if existing_port:
_s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try:
_s.connect(('127.0.0.1', existing_port))
_s.close()
print(f'⚠ Pet already running on port {existing_port}, exiting.')
sys.exit(0)
except OSError:
pass
finally:
_s.close()
PORT = _pick_port()
print(f'Using pet port: {PORT}')
Pet()
66 changes: 54 additions & 12 deletions frontends/desktop_pet_v2.pyw
Original file line number Diff line number Diff line change
@@ -1,13 +1,48 @@
"""Desktop Pet with Skin System — Cross-platform with True Transparency"""
import os, re, sys, json, threading, io
import os, re, sys, json, threading, io, socket
from http.server import HTTPServer, BaseHTTPRequestHandler
from urllib.parse import urlparse, parse_qs
from PIL import Image, ImageDraw, ImageFont, ImageOps

PORT = 51983
DEFAULT_PORT = 51983
PORT = DEFAULT_PORT
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
PROJECT_DIR = os.path.dirname(SCRIPT_DIR)
SKINS_DIR = os.path.join(SCRIPT_DIR, 'skins')
PORT_FILE = os.path.join(PROJECT_DIR, 'temp', 'desktop_pet_port.txt')


def _read_saved_port():
try:
return int(open(PORT_FILE, encoding='utf-8').read().strip())
except Exception:
return None


def _write_saved_port(port):
os.makedirs(os.path.dirname(PORT_FILE), exist_ok=True)
with open(PORT_FILE, 'w', encoding='utf-8') as f:
f.write(str(port))


def _can_bind(port):
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try:
sock.bind(('127.0.0.1', port))
return True
except OSError:
return False
finally:
sock.close()


def _pick_port(preferred=DEFAULT_PORT, lo=45183, hi=45283):
if _can_bind(preferred):
return preferred
for port in range(lo, hi + 1):
if _can_bind(port):
return port
raise OSError('No usable local port found for desktop pet')

class SkinLoader:
"""Load and parse skin configuration"""
Expand Down Expand Up @@ -277,6 +312,7 @@ class PetBase:
try:
HTTPServer.allow_reuse_address = True
srv = HTTPServer(('127.0.0.1', PORT), Handler)
_write_saved_port(PORT)
threading.Thread(target=srv.serve_forever, daemon=True).start()
print(f'✓ Server: http://127.0.0.1:{PORT}/?state=walk')
except OSError as e:
Expand Down Expand Up @@ -767,16 +803,22 @@ else:
pass

if __name__ == '__main__':
# Singleton: if port already in use, another instance is running
import socket
_s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try:
_s.connect(('127.0.0.1', PORT))
_s.close()
print(f'⚠ Pet already running on port {PORT}, exiting.')
sys.exit(0)
except ConnectionRefusedError:
pass
# Singleton: if saved port already in use, another instance is running
existing_port = _read_saved_port()
if existing_port:
_s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try:
_s.connect(('127.0.0.1', existing_port))
_s.close()
print(f'⚠ Pet already running on port {existing_port}, exiting.')
sys.exit(0)
except OSError:
pass
finally:
_s.close()

PORT = _pick_port()
print(f'Using pet port: {PORT}')

if sys.platform == 'darwin':
pet = MacPet('vita')
Expand Down
14 changes: 13 additions & 1 deletion frontends/stapp.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,16 @@
import time, json, re, threading, queue
from agentmain import GeneraticAgent

PROJECT_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))
PET_PORT_FILE = os.path.join(PROJECT_DIR, 'temp', 'desktop_pet_port.txt')


def read_pet_port():
try:
return int(open(PET_PORT_FILE, encoding='utf-8').read().strip())
except Exception:
return None

st.set_page_config(page_title="Cowork", layout="wide")

@st.cache_resource
Expand Down Expand Up @@ -50,7 +60,9 @@ def render_sidebar():
subprocess.Popen([sys.executable, pet_script], **kwargs)
def _pet_req(q):
def _do():
try: urlopen(f'http://127.0.0.1:51983/?{q}', timeout=2)
port = read_pet_port()
if not port: return
try: urlopen(f'http://127.0.0.1:{port}/?{q}', timeout=2)
except Exception: pass
threading.Thread(target=_do, daemon=True).start()
agent._pet_req = _pet_req
Expand Down