Skip to content

Commit

Permalink
feat: Implement System Tray Icon (#300)
Browse files Browse the repository at this point in the history
* add tray icon

* move to tray.py

* add notifications & guards

* pyinstaller currently broken

* Update tray.py

* Update cards.py

* hide icon in taskbar + minor improvements

* add dropdown for visualize

* Update tray.py

* Update tray.py

* add py6tray + notifier

* BYE BYE PYSTRAY !!!

hello dependency inversion?

* os.sep + fix console + other

* use path.join

* run -> _run

* add replays

* fix conflicting action items

* fix database is locked + threading

* type annotations

* Update cards.py

* Create visualize1.py

* Revert "Create visualize1.py"

This reverts commit 753f691.

* fix typo

* fix merge conflicts : still broken

* fix

* Revert "fix"

This reverts commit 9d84446.

* Revert "fix merge conflicts : still broken"

This reverts commit 82e62d9.

* Update pyproject.toml

* styling / linting

* Update tray.py

* Update build.py

* return vals

* Update build.py

* show app

* changes
  • Loading branch information
0dm authored Aug 10, 2023
1 parent 7b57505 commit 9f2a04e
Show file tree
Hide file tree
Showing 12 changed files with 610 additions and 171 deletions.
Binary file added openadapt/app/assets/logo_inverted.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
13 changes: 8 additions & 5 deletions openadapt/app/build.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,22 +7,23 @@
"""

from pathlib import Path
import os
import subprocess

import nicegui

spec = [
"pyi-makespec",
f"{Path(__file__).parent}/main.py",
f"--icon={Path(__file__).parent}/assets/logo.ico",
f"{Path(__file__).parent / 'tray.py'}",
f"--icon={Path(__file__).parent / 'assets' / 'logo.ico'}",
"--name",
"OpenAdapt", # name
# "--onefile", # trade startup speed for smaller file size
"--onedir",
"--windowed", # prevent console appearing, only use with ui.run(native=True, ...)
"--add-data",
f"{Path(nicegui.__file__).parent}{os.pathsep}nicegui",
f"{Path(nicegui.__file__).parent}:{Path('nicegui')}",
"--add-data",
f"{Path(__file__).parent}:{Path('assets')}",
]

subprocess.call(spec)
Expand All @@ -36,4 +37,6 @@
f.truncate()
f.writelines(lines)

subprocess.call(["pyinstaller", "OpenAdapt.spec"])
# building
proc = subprocess.Popen("pyinstaller OpenAdapt.spec", shell=True)
proc.wait()
54 changes: 42 additions & 12 deletions openadapt/app/cards.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,17 @@
This module provides functions for managing UI cards in the OpenAdapt application.
"""

from datetime import datetime
from subprocess import Popen
import signal

from nicegui import ui

from openadapt.app.objects.local_file_picker import LocalFilePicker
from openadapt.app.util import get_scrub, set_dark, set_scrub, sync_switch
from openadapt.crud import new_session

PROC = None
record_proc = None


def settings(dark_mode: bool) -> None:
Expand Down Expand Up @@ -63,14 +65,36 @@ async def pick_file() -> None:
import_dialog.open()


def stop_record() -> None:
"""Stop the current recording session."""
global record_proc
if record_proc is not None:
record_proc.send_signal(signal.SIGINT)

# wait for process to terminate
record_proc.wait()
record_proc = None


def quick_record() -> None:
"""Run a recording session with no option for recording name (uses date instead)."""
global record_proc
new_session()
now = datetime.now().strftime("%d/%m/%Y %H:%M:%S")
record_proc = Popen(
f"python -m openadapt.record '{now}'",
shell=True,
)


def recording_prompt(options: list[str], record_button: ui.button) -> None:
"""Display the recording prompt dialog.
Args:
options (list): List of autocomplete options.
record_button (nicegui.widgets.Button): Record button widget.
"""
if PROC is None:
if record_proc is None:
with ui.dialog() as dialog, ui.card():
ui.label("Enter a name for the recording: ")
ui.input(
Expand All @@ -88,28 +112,34 @@ def recording_prompt(options: list[str], record_button: ui.button) -> None:
dialog.open()

def terminate() -> None:
global process
process.send_signal(signal.SIGINT)
global record_proc
record_proc.send_signal(signal.SIGINT)

# Wait for process to terminate
process.wait()
# wait for process to terminate
record_proc.wait()
ui.notify("Stopped recording")
record_button._props["name"] = "radio_button_checked"
record_button.on("click", lambda: recording_prompt(options, record_button))

process = None
record_proc = None

def begin() -> None:
name = result.text.__getattribute__("value")

ui.notify(f"Recording {name}... Press CTRL + C in terminal window to cancel")
PROC = Popen("python3 -m openadapt.record " + name, shell=True)
ui.notify(
f"Recording {name}... Press CTRL + C in terminal window to cancel",
)
new_session()
proc = Popen(
"python -m openadapt.record " + name,
shell=True,
)
record_button._props["name"] = "stop"
record_button.on("click", lambda: terminate())
record_button.update()
return PROC
return proc

def on_record() -> None:
global PROC
global record_proc
dialog.close()
PROC = begin()
record_proc = begin()
80 changes: 42 additions & 38 deletions openadapt/app/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,45 +19,44 @@
from openadapt.app.util import clear_db, on_export, on_import

SERVER = "127.0.0.1:8000/upload"
FPATH = os.path.dirname(__file__)

# Recording description autocomplete
OPTIONS = ["test"]

def run_app() -> None:
"""Run the OpenAdapt application."""
file = os.path.dirname(__file__)
app.native.window_args["resizable"] = False # too many issues with resizing
app.native.start_args["debug"] = False

dark = ui.dark_mode()
dark.value = config.APP_DARK_MODE

logger = None

# Add logo
# Right-align icon
with ui.row().classes("w-full justify-right"):
# settings
with ui.avatar(color="white" if dark else "black", size=128):
logo_base64 = base64.b64encode(open(f"{file}/assets/logo.png", "rb").read())
img = bytes(
f"data:image/png;base64,{(logo_base64.decode('utf-8'))}",
encoding="utf-8",
)
ui.image(img.decode("utf-8"))
ui.icon("settings").tooltip("Settings").on("click", lambda: settings(dark))
ui.icon("delete").on("click", lambda: clear_db(log=logger)).tooltip(
"Clear all recorded data"
)
ui.icon("upload").tooltip("Export Data").on("click", lambda: on_export(SERVER))
ui.icon("download").tooltip("Import Data").on(
"click", lambda: select_import(on_import)
app.native.start_args["debug"] = False

dark = ui.dark_mode()
dark.value = config.APP_DARK_MODE

logger = None

# Add logo
# right align icon
with ui.row().classes("w-full justify-right"):
# settings

# alignment trick
with ui.avatar(color="white" if dark else "black", size=128):
logo_base64 = base64.b64encode(
open(os.path.join(FPATH, "assets/logo.png"), "rb").read()
)
ui.icon("share").tooltip("Share").on(
"click",
lambda: (_ for _ in ()).throw(Exception(NotImplementedError)),
img = bytes(
f"data:image/png;base64,{(logo_base64.decode('utf-8'))}",
encoding="utf-8",
)

# Recording description autocomplete
options = ["test"]
ui.image(img.decode("utf-8"))
ui.icon("settings").tooltip("Settings").on("click", lambda: settings(dark))
ui.icon("delete").on("click", lambda: clear_db(log=logger)).tooltip(
"Clear all recorded data"
)
ui.icon("upload").tooltip("Export Data").on("click", lambda: on_export(SERVER))
ui.icon("download").tooltip("Import Data").on(
"click", lambda: select_import(on_import)
)
ui.icon("share").tooltip("Share").on(
"click", lambda: (_ for _ in ()).throw(Exception(NotImplementedError))
)

with ui.splitter(value=20) as splitter:
splitter.classes("w-full h-full")
Expand All @@ -67,7 +66,7 @@ def run_app() -> None:
ui.icon("radio_button_checked", size="64px")
.on(
"click",
lambda: recording_prompt(options, record_button),
lambda: recording_prompt(OPTIONS, record_button),
)
.tooltip("Record a new replay / Stop recording")
)
Expand All @@ -82,16 +81,21 @@ def run_app() -> None:
with splitter.after:
logger = Console()
logger.log.style("height: 250px;, width: 300px;")

splitter.enabled = False


def start(fullscreen: bool = False) -> None:
"""Start the OpenAdapt application."""
ui.run(
title="OpenAdapt Client",
native=True,
window_size=(400, 400),
fullscreen=False,
fullscreen=fullscreen,
reload=False,
show=False,
)


if __name__ == "__main__":
run_app()
start()
4 changes: 3 additions & 1 deletion openadapt/app/objects/console.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ def __init__(self) -> None:
"""Initialize the Console object."""
self.log = ui.log().classes("w-full h-20")
self.old_stderr = sys.stderr
sys.stderr = self
self.buffer = ""

def write(self, data: str) -> None:
"""Write data to the log.
Expand All @@ -29,10 +29,12 @@ def write(self, data: str) -> None:
"""
self.log.push(data[:-1])
self.log.update()
self.old_stderr.write(data)

def flush(self) -> None:
"""Flush the log."""
self.log.update()
self.old_stderr.flush()

def reset(self) -> None:
"""Reset the log and restore stderr."""
Expand Down
Loading

0 comments on commit 9f2a04e

Please sign in to comment.