Start Gyre from an icon: launch / quit / install-desktop
- python3 -m sandtable install-desktop puts a Gyre icon in the app grid and on the desktop (freedesktop .desktop entry with a "Quit Gyre" action). - The icon runs `launch`: starts the web page in the background (no terminal, own session, log in .cache/server.log) if it isn't running, then opens it. A dialog shows the reason if it can't start. - Quit link on the page (local computer only) and `quit`: stop any design, motors off, close the page. - Gyre icon (sandtable/static/gyre.svg), also the page's tab icon. - Simulator takes several lines per tick, so streaming tests aren't slowed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
8a377af116
commit
db78b1f2e6
9 files changed
+195
-13
No files matched your search
@@ -0,0 +1,112 @@
|
||||
"""Start Gyre from a desktop icon: run the web page in the background (no terminal window) and
|
||||
open it in the browser. Used by the Gyre icon (`install-desktop` puts it in the app grid and on
|
||||
the desktop):
|
||||
python3 -m sandtable launch start if needed, then open the page
|
||||
python3 -m sandtable quit stop any design, motors off, close Gyre
|
||||
"""
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
import urllib.request
|
||||
import webbrowser
|
||||
|
||||
SOFTWARE = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
ICON = os.path.join(SOFTWARE, 'sandtable', 'static', 'gyre.svg')
|
||||
LOG = os.path.join(SOFTWARE, '.cache', 'server.log')
|
||||
|
||||
|
||||
def status(port):
|
||||
"""The running Gyre page's status, or None if nothing (or something else) answers."""
|
||||
try:
|
||||
with urllib.request.urlopen(f'http://127.0.0.1:{port}/api/status', timeout=1.5) as r:
|
||||
d = json.load(r)
|
||||
return d if 'machine' in d else None
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
|
||||
def say(title, msg, error=False):
|
||||
"""Tell the person at the desktop: a dialog if possible, else a notification."""
|
||||
print(f'{title}: {msg}', flush=True)
|
||||
if shutil.which('zenity'):
|
||||
subprocess.run(['zenity', '--error' if error else '--info', '--title', title, '--width', '420',
|
||||
'--text', msg], stderr=subprocess.DEVNULL)
|
||||
elif shutil.which('notify-send'):
|
||||
subprocess.run(['notify-send', '-a', 'Gyre', title, msg])
|
||||
|
||||
|
||||
def launch(port=8765):
|
||||
if status(port) is None:
|
||||
os.makedirs(os.path.dirname(LOG), exist_ok=True)
|
||||
log = open(LOG, 'a')
|
||||
log.write(time.strftime('\n=== %Y-%m-%d %H:%M:%S starting from the Gyre icon\n'))
|
||||
log.flush()
|
||||
subprocess.Popen([sys.executable, '-m', 'sandtable', 'web', '--no-browser', '--http-port', str(port)],
|
||||
cwd=SOFTWARE, stdout=log, stderr=subprocess.STDOUT, stdin=subprocess.DEVNULL,
|
||||
start_new_session=True) # keeps running after this launcher exits
|
||||
t_end = time.time() + 25
|
||||
while time.time() < t_end and status(port) is None:
|
||||
time.sleep(0.4)
|
||||
if status(port) is None:
|
||||
with open(LOG) as f:
|
||||
tail = ''.join(f.readlines()[-12:])
|
||||
say('Gyre could not start', f'The page did not come up. The last lines of its log:\n\n{tail}', error=True)
|
||||
return 1
|
||||
webbrowser.open(f'http://127.0.0.1:{port}/')
|
||||
return 0
|
||||
|
||||
|
||||
def quit_gyre(port=8765):
|
||||
try:
|
||||
req = urllib.request.Request(f'http://127.0.0.1:{port}/api/quit', data=b'{}', method='POST',
|
||||
headers={'Content-Type': 'application/json'})
|
||||
with urllib.request.urlopen(req, timeout=40) as r:
|
||||
ok = json.load(r).get('ok')
|
||||
except Exception:
|
||||
ok = False
|
||||
if not ok:
|
||||
say('Gyre', 'Gyre is not running.')
|
||||
return 0
|
||||
|
||||
|
||||
def install_desktop():
|
||||
"""Put a Gyre icon in the app grid and on the desktop (Linux desktops that follow the
|
||||
freedesktop.org standard: GNOME, KDE, Cinnamon, Xfce...)."""
|
||||
entry = f"""[Desktop Entry]
|
||||
Type=Application
|
||||
Name=Gyre
|
||||
GenericName=Sand art table
|
||||
Comment=Draw designs on the Gyre sand table
|
||||
Exec={sys.executable} -m sandtable launch
|
||||
Path={SOFTWARE}
|
||||
Icon={ICON}
|
||||
Terminal=false
|
||||
Categories=Utility;
|
||||
StartupNotify=false
|
||||
Actions=quit;
|
||||
|
||||
[Desktop Action quit]
|
||||
Name=Quit Gyre
|
||||
Exec={sys.executable} -m sandtable quit
|
||||
"""
|
||||
apps = os.path.expanduser('~/.local/share/applications')
|
||||
os.makedirs(apps, exist_ok=True)
|
||||
places = [os.path.join(apps, 'gyre.desktop')]
|
||||
desktop = subprocess.run(['xdg-user-dir', 'DESKTOP'], capture_output=True, text=True).stdout.strip() \
|
||||
if shutil.which('xdg-user-dir') else os.path.expanduser('~/Desktop')
|
||||
if desktop and os.path.isdir(desktop) and desktop != os.path.expanduser('~'):
|
||||
places.append(os.path.join(desktop, 'Gyre.desktop'))
|
||||
for p in places:
|
||||
with open(p, 'w') as f:
|
||||
f.write(entry)
|
||||
os.chmod(p, 0o755)
|
||||
if shutil.which('gio'): # GNOME: allow launching from the desktop
|
||||
subprocess.run(['gio', 'set', p, 'metadata::trusted', 'true'], stderr=subprocess.DEVNULL)
|
||||
if shutil.which('update-desktop-database'):
|
||||
subprocess.run(['update-desktop-database', apps], stderr=subprocess.DEVNULL)
|
||||
for p in places:
|
||||
print(' wrote', p)
|
||||
return places
|
||||
Reference in new issue
Block a user