changeset 3:3d7fc94a404a

a few more inputs; daemonize
author drewp@bigasterisk.com
date Wed, 08 Mar 2023 10:57:11 -0800
parents 0ecb388a0b90
children 5ad799e534a5
files current_window_title_linux.py current_window_title_osx.py pdm.lock power_linux.py power_osx.py progs_all.py pyproject.toml racc.py run.sh
diffstat 9 files changed, 374 insertions(+), 70 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/current_window_title_linux.py	Wed Mar 08 10:57:11 2023 -0800
@@ -0,0 +1,13 @@
+import Xlib
+import Xlib.display
+
+_disp = None
+
+
+def get_current_window_title():
+    global _disp
+    if _disp is None:
+        _disp = Xlib.display.Display()
+    window = _disp.get_input_focus().focus
+    # get_wm_name returns '': https://github.com/python-xlib/python-xlib/issues/212
+    return window.get_property(_disp.get_atom("WM_NAME"), _disp.get_atom("UTF8_STRING"), 0, 1024).value.decode('utf8')
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/current_window_title_osx.py	Wed Mar 08 10:57:11 2023 -0800
@@ -0,0 +1,15 @@
+from osascript import osascript
+#https://stackoverflow.com/questions/5292204/macosx-get-foremost-window-title
+
+
+def get_current_window_title():
+    _, title, _ = osascript('''
+    tell application "System Events"
+        set frontApp to name of first application process whose frontmost is true
+    end tell
+
+    tell application frontApp
+        set window_name to name of front window
+    end tell
+    ''')
+    return title
\ No newline at end of file
--- a/pdm.lock	Sat Mar 04 12:10:36 2023 -0800
+++ b/pdm.lock	Wed Mar 08 10:57:11 2023 -0800
@@ -1,3 +1,6 @@
+# This file is @generated by PDM.
+# It is not intended for manual editing.
+
 [[package]]
 name = "anyio"
 version = "3.6.2"
@@ -9,6 +12,24 @@
 ]
 
 [[package]]
+name = "appnope"
+version = "0.1.3"
+summary = "Disable App Nap on macOS >= 10.9"
+
+[[package]]
+name = "asttokens"
+version = "2.2.1"
+summary = "Annotate AST trees with source code positions"
+dependencies = [
+    "six",
+]
+
+[[package]]
+name = "backcall"
+version = "0.2.0"
+summary = "Specifications for callback functions passed in to an API"
+
+[[package]]
 name = "background-loop"
 version = "1.7.0"
 requires_python = ">=3.9"
@@ -41,6 +62,27 @@
 summary = "Cross-platform colored terminal text."
 
 [[package]]
+name = "daemonocle"
+version = "1.2.3"
+requires_python = ">=3.6"
+summary = "A Python library for creating super fancy Unix daemons"
+dependencies = [
+    "click",
+    "psutil",
+]
+
+[[package]]
+name = "decorator"
+version = "5.1.1"
+requires_python = ">=3.5"
+summary = "Decorators for Humans"
+
+[[package]]
+name = "executing"
+version = "1.2.0"
+summary = "Get the currently executing AST node of a frame, and other information"
+
+[[package]]
 name = "h11"
 version = "0.14.0"
 requires_python = ">=3.7"
@@ -53,6 +95,44 @@
 summary = "Internationalized Domain Names in Applications (IDNA)"
 
 [[package]]
+name = "ipython"
+version = "8.11.0"
+requires_python = ">=3.8"
+summary = "IPython: Productive Interactive Computing"
+dependencies = [
+    "appnope; sys_platform == \"darwin\"",
+    "backcall",
+    "colorama; sys_platform == \"win32\"",
+    "decorator",
+    "jedi>=0.16",
+    "matplotlib-inline",
+    "pexpect>4.3; sys_platform != \"win32\"",
+    "pickleshare",
+    "prompt-toolkit!=3.0.37,<3.1.0,>=3.0.30",
+    "pygments>=2.4.0",
+    "stack-data",
+    "traitlets>=5",
+]
+
+[[package]]
+name = "jedi"
+version = "0.18.2"
+requires_python = ">=3.6"
+summary = "An autocompletion tool for Python that can be used for text editors."
+dependencies = [
+    "parso<0.9.0,>=0.8.0",
+]
+
+[[package]]
+name = "matplotlib-inline"
+version = "0.1.6"
+requires_python = ">=3.5"
+summary = "Inline Matplotlib backend for Jupyter"
+dependencies = [
+    "traitlets",
+]
+
+[[package]]
 name = "osascript"
 version = "2020.12.3"
 summary = ""
@@ -61,18 +141,51 @@
 ]
 
 [[package]]
+name = "parso"
+version = "0.8.3"
+requires_python = ">=3.6"
+summary = "A Python Parser"
+
+[[package]]
+name = "pexpect"
+version = "4.8.0"
+summary = "Pexpect allows easy control of interactive console applications."
+dependencies = [
+    "ptyprocess>=0.5",
+]
+
+[[package]]
+name = "pickleshare"
+version = "0.7.5"
+summary = "Tiny 'shelve'-like database with concurrency support"
+
+[[package]]
 name = "prometheus-client"
 version = "0.16.0"
 requires_python = ">=3.6"
 summary = "Python client for the Prometheus monitoring system."
 
 [[package]]
+name = "prompt-toolkit"
+version = "3.0.38"
+requires_python = ">=3.7.0"
+summary = "Library for building powerful interactive command lines in Python"
+dependencies = [
+    "wcwidth",
+]
+
+[[package]]
 name = "psutil"
 version = "5.9.4"
 requires_python = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
 summary = "Cross-platform lib for process and system monitoring in Python."
 
 [[package]]
+name = "ptyprocess"
+version = "0.7.0"
+summary = "Run a subprocess in a pseudo terminal"
+
+[[package]]
 name = "pulsectl"
 version = "22.3.2"
 summary = "Python high-level interface and ctypes-based bindings for PulseAudio (libpulse)"
@@ -87,12 +200,23 @@
 ]
 
 [[package]]
+name = "pure-eval"
+version = "0.2.2"
+summary = "Safely evaluate AST nodes without side effects"
+
+[[package]]
 name = "pycparser"
 version = "2.21"
 requires_python = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
 summary = "C parser in Python"
 
 [[package]]
+name = "pygments"
+version = "2.14.0"
+requires_python = ">=3.6"
+summary = "Pygments is a syntax highlighting package written in Python."
+
+[[package]]
 name = "python-xlib"
 version = "0.33"
 summary = "Python X Library"
@@ -101,11 +225,6 @@
 ]
 
 [[package]]
-name = "quartz"
-version = "0.0.1.dev0"
-summary = ""
-
-[[package]]
 name = "runcmd"
 version = "2020.12.3"
 summary = ""
@@ -126,6 +245,16 @@
 summary = "Sniff out which async library your code is running under"
 
 [[package]]
+name = "stack-data"
+version = "0.6.2"
+summary = "Extract data from python stack frames and tracebacks for informative displays"
+dependencies = [
+    "asttokens>=2.1.0",
+    "executing>=1.2.0",
+    "pure-eval",
+]
+
+[[package]]
 name = "starlette"
 version = "0.23.1"
 requires_python = ">=3.7"
@@ -144,6 +273,12 @@
 ]
 
 [[package]]
+name = "traitlets"
+version = "5.9.0"
+requires_python = ">=3.7"
+summary = "Traitlets Python configuration system"
+
+[[package]]
 name = "uvicorn"
 version = "0.20.0"
 requires_python = ">=3.7"
@@ -154,6 +289,11 @@
 ]
 
 [[package]]
+name = "wcwidth"
+version = "0.2.6"
+summary = "Measures the displayed width of unicode strings in a terminal"
+
+[[package]]
 name = "xprintidle"
 version = "0.2"
 summary = "A cffi wrapper around xprintidle."
@@ -162,14 +302,26 @@
 ]
 
 [metadata]
-lock_version = "4.0"
-content_hash = "sha256:86695308ac1a377a8f1950ba4dce042957aee65d99007a48771f71b52557f734"
+lock_version = "4.1"
+content_hash = "sha256:5f775ca5777bad86a2fa325b5d6912065c225f8c4afcbaa5b15a741a89d44ba7"
 
 [metadata.files]
 "anyio 3.6.2" = [
     {url = "https://files.pythonhosted.org/packages/77/2b/b4c0b7a3f3d61adb1a1e0b78f90a94e2b6162a043880704b7437ef297cad/anyio-3.6.2-py3-none-any.whl", hash = "sha256:fbbe32bd270d2a2ef3ed1c5d45041250284e31fc0a4df4a5a6071842051a51e3"},
     {url = "https://files.pythonhosted.org/packages/8b/94/6928d4345f2bc1beecbff03325cad43d320717f51ab74ab5a571324f4f5a/anyio-3.6.2.tar.gz", hash = "sha256:25ea0d673ae30af41a0c442f81cf3b38c7e79fdc7b60335a4c14e05eb0947421"},
 ]
+"appnope 0.1.3" = [
+    {url = "https://files.pythonhosted.org/packages/41/4a/381783f26df413dde4c70c734163d88ca0550a1361cb74a1c68f47550619/appnope-0.1.3-py2.py3-none-any.whl", hash = "sha256:265a455292d0bd8a72453494fa24df5a11eb18373a60c7c0430889f22548605e"},
+    {url = "https://files.pythonhosted.org/packages/6a/cd/355842c0db33192ac0fc822e2dcae835669ef317fe56c795fb55fcddb26f/appnope-0.1.3.tar.gz", hash = "sha256:02bd91c4de869fbb1e1c50aafc4098827a7a54ab2f39d9dcba6c9547ed920e24"},
+]
+"asttokens 2.2.1" = [
+    {url = "https://files.pythonhosted.org/packages/c8/e3/b0b4f32162621126fbdaba636c152c6b6baec486c99f48686e66343d638f/asttokens-2.2.1.tar.gz", hash = "sha256:4622110b2a6f30b77e1473affaa97e711bc2f07d3f10848420ff1898edbe94f3"},
+    {url = "https://files.pythonhosted.org/packages/f3/e1/64679d9d0759db5b182222c81ff322c2fe2c31e156a59afd6e9208c960e5/asttokens-2.2.1-py2.py3-none-any.whl", hash = "sha256:6b0ac9e93fb0335014d382b8fa9b3afa7df546984258005da0b9e7095b3deb1c"},
+]
+"backcall 0.2.0" = [
+    {url = "https://files.pythonhosted.org/packages/4c/1c/ff6546b6c12603d8dd1070aa3c3d273ad4c07f5771689a7b69a550e8c951/backcall-0.2.0-py2.py3-none-any.whl", hash = "sha256:fbbce6a29f263178a1f7915c1940bde0ec2b2a967566fe1c65c1dfb7422bd255"},
+    {url = "https://files.pythonhosted.org/packages/a2/40/764a663805d84deee23043e1426a9175567db89c8b3287b5c2ad9f71aa93/backcall-0.2.0.tar.gz", hash = "sha256:5cbdbf27be5e7cfadb448baf0aa95508f91f2bbc6c6437cd9cd06e2a4c215e1e"},
+]
 "background-loop 1.7.0" = [
     {url = "https://projects.bigasterisk.com/background-loop/background-loop-1.7.0.tar.gz", hash = "sha256:0f21badaeb68d4c9ef8742b265f5498cc961f79b02dd116f04b8fb2b841bf975"},
 ]
@@ -247,6 +399,17 @@
     {url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"},
     {url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
 ]
+"daemonocle 1.2.3" = [
+    {url = "https://files.pythonhosted.org/packages/ad/df/37c5b547fceba26e623cc41cc8416d9eff1cb8bcb57ed063ef5f004b692d/daemonocle-1.2.3.tar.gz", hash = "sha256:741ce6abc37c85ca396379a0d45710ac27ab5f17eeaadbc64a0f5cc4c4f666ea"},
+]
+"decorator 5.1.1" = [
+    {url = "https://files.pythonhosted.org/packages/66/0c/8d907af351aa16b42caae42f9d6aa37b900c67308052d10fdce809f8d952/decorator-5.1.1.tar.gz", hash = "sha256:637996211036b6385ef91435e4fae22989472f9d571faba8927ba8253acbc330"},
+    {url = "https://files.pythonhosted.org/packages/d5/50/83c593b07763e1161326b3b8c6686f0f4b0f24d5526546bee538c89837d6/decorator-5.1.1-py3-none-any.whl", hash = "sha256:b8c3f85900b9dc423225913c5aace94729fe1fa9763b38939a95226f02d37186"},
+]
+"executing 1.2.0" = [
+    {url = "https://files.pythonhosted.org/packages/28/3c/bc3819dd8b1a1588c9215a87271b6178cc5498acaa83885211f5d4d9e693/executing-1.2.0-py2.py3-none-any.whl", hash = "sha256:0314a69e37426e3608aada02473b4161d4caf5a4b244d1d0c48072b8fee7bacc"},
+    {url = "https://files.pythonhosted.org/packages/8f/ac/89ff37d8594b0eef176b7cec742ac868fef853b8e18df0309e3def9f480b/executing-1.2.0.tar.gz", hash = "sha256:19da64c18d2d851112f09c287f8d3dbbdf725ab0e569077efb6cdcbd3497c107"},
+]
 "h11 0.14.0" = [
     {url = "https://files.pythonhosted.org/packages/95/04/ff642e65ad6b90db43e668d70ffb6736436c7ce41fcc549f4e9472234127/h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"},
     {url = "https://files.pythonhosted.org/packages/f5/38/3af3d3633a34a3316095b39c8e8fb4853a28a536e55d347bd8d8e9a14b03/h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"},
@@ -255,13 +418,41 @@
     {url = "https://files.pythonhosted.org/packages/8b/e1/43beb3d38dba6cb420cefa297822eac205a277ab43e5ba5d5c46faf96438/idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"},
     {url = "https://files.pythonhosted.org/packages/fc/34/3030de6f1370931b9dbb4dad48f6ab1015ab1d32447850b9fc94e60097be/idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"},
 ]
+"ipython 8.11.0" = [
+    {url = "https://files.pythonhosted.org/packages/a7/e1/7c277c1dd0b0520bf07070c3921ff1986a2eb3db704ad91f0ac46fb42d8f/ipython-8.11.0.tar.gz", hash = "sha256:735cede4099dbc903ee540307b9171fbfef4aa75cfcacc5a273b2cda2f02be04"},
+    {url = "https://files.pythonhosted.org/packages/ac/91/23e08c442657cf493598b0222008437c9e0aef0709a8fd65a5d5d68ffa21/ipython-8.11.0-py3-none-any.whl", hash = "sha256:5b54478e459155a326bf5f42ee4f29df76258c0279c36f21d71ddb560f88b156"},
+]
+"jedi 0.18.2" = [
+    {url = "https://files.pythonhosted.org/packages/15/02/afd43c5066de05f6b3188f3aa74136a3289e6c30e7a45f351546cab0928c/jedi-0.18.2.tar.gz", hash = "sha256:bae794c30d07f6d910d32a7048af09b5a39ed740918da923c6b780790ebac612"},
+    {url = "https://files.pythonhosted.org/packages/6d/60/4acda63286ef6023515eb914543ba36496b8929cb7af49ecce63afde09c6/jedi-0.18.2-py2.py3-none-any.whl", hash = "sha256:203c1fd9d969ab8f2119ec0a3342e0b49910045abe6af0a3ae83a5764d54639e"},
+]
+"matplotlib-inline 0.1.6" = [
+    {url = "https://files.pythonhosted.org/packages/d9/50/3af8c0362f26108e54d58c7f38784a3bdae6b9a450bab48ee8482d737f44/matplotlib-inline-0.1.6.tar.gz", hash = "sha256:f887e5f10ba98e8d2b150ddcf4702c1e5f8b3a20005eb0f74bfdbd360ee6f304"},
+    {url = "https://files.pythonhosted.org/packages/f2/51/c34d7a1d528efaae3d8ddb18ef45a41f284eacf9e514523b191b7d0872cc/matplotlib_inline-0.1.6-py3-none-any.whl", hash = "sha256:f1f41aab5328aa5aaea9b16d083b128102f8712542f819fe7e6a420ff581b311"},
+]
 "osascript 2020.12.3" = [
     {url = "https://files.pythonhosted.org/packages/15/21/36f43fde613eefa23e8f569a4020964c5417cd8fad7b069383717d2a57b0/osascript-2020.12.3.tar.gz", hash = "sha256:36d3f30fb6c43c868c70d1fb06e4c970844a5cbe0a481b9fbd5059398916e069"},
 ]
+"parso 0.8.3" = [
+    {url = "https://files.pythonhosted.org/packages/05/63/8011bd08a4111858f79d2b09aad86638490d62fbf881c44e434a6dfca87b/parso-0.8.3-py2.py3-none-any.whl", hash = "sha256:c001d4636cd3aecdaf33cbb40aebb59b094be2a74c556778ef5576c175e19e75"},
+    {url = "https://files.pythonhosted.org/packages/a2/0e/41f0cca4b85a6ea74d66d2226a7cda8e41206a624f5b330b958ef48e2e52/parso-0.8.3.tar.gz", hash = "sha256:8c07be290bb59f03588915921e29e8a50002acaf2cdc5fa0e0114f91709fafa0"},
+]
+"pexpect 4.8.0" = [
+    {url = "https://files.pythonhosted.org/packages/39/7b/88dbb785881c28a102619d46423cb853b46dbccc70d3ac362d99773a78ce/pexpect-4.8.0-py2.py3-none-any.whl", hash = "sha256:0b48a55dcb3c05f3329815901ea4fc1537514d6ba867a152b581d69ae3710937"},
+    {url = "https://files.pythonhosted.org/packages/e5/9b/ff402e0e930e70467a7178abb7c128709a30dfb22d8777c043e501bc1b10/pexpect-4.8.0.tar.gz", hash = "sha256:fc65a43959d153d0114afe13997d439c22823a27cefceb5ff35c2178c6784c0c"},
+]
+"pickleshare 0.7.5" = [
+    {url = "https://files.pythonhosted.org/packages/9a/41/220f49aaea88bc6fa6cba8d05ecf24676326156c23b991e80b3f2fc24c77/pickleshare-0.7.5-py2.py3-none-any.whl", hash = "sha256:9649af414d74d4df115d5d718f82acb59c9d418196b7b4290ed47a12ce62df56"},
+    {url = "https://files.pythonhosted.org/packages/d8/b6/df3c1c9b616e9c0edbc4fbab6ddd09df9535849c64ba51fcb6531c32d4d8/pickleshare-0.7.5.tar.gz", hash = "sha256:87683d47965c1da65cdacaf31c8441d12b8044cdec9aca500cd78fc2c683afca"},
+]
 "prometheus-client 0.16.0" = [
     {url = "https://files.pythonhosted.org/packages/5b/8e/6a546e439b4366ab9eab0a736876eb1e1916dd93b4a1fa560ef711d24f8c/prometheus_client-0.16.0-py3-none-any.whl", hash = "sha256:0836af6eb2c8f4fed712b2f279f6c0a8bbab29f9f4aa15276b91c7cb0d1616ab"},
     {url = "https://files.pythonhosted.org/packages/d0/55/9e34c73e1e490b105b4cd13d08497b1f7cb086a260e4161b7b7c2928b196/prometheus_client-0.16.0.tar.gz", hash = "sha256:a03e35b359f14dd1630898543e2120addfdeacd1a6069c1367ae90fd93ad3f48"},
 ]
+"prompt-toolkit 3.0.38" = [
+    {url = "https://files.pythonhosted.org/packages/4b/bb/75cdcd356f57d17b295aba121494c2333d26bfff1a837e6199b8b83c415a/prompt_toolkit-3.0.38.tar.gz", hash = "sha256:23ac5d50538a9a38c8bde05fecb47d0b403ecd0662857a86f886f798563d5b9b"},
+    {url = "https://files.pythonhosted.org/packages/87/3f/1f5a0ff475ae6481f4b0d45d4d911824d3218b94ee2a97a8cb84e5569836/prompt_toolkit-3.0.38-py3-none-any.whl", hash = "sha256:45ea77a2f7c60418850331366c81cf6b5b9cf4c7fd34616f733c5427e6abbb1f"},
+]
 "psutil 5.9.4" = [
     {url = "https://files.pythonhosted.org/packages/1d/80/e1502ba4ff65390bd17b4612010762075f64f5a0e7c28e889c4820bd95a9/psutil-5.9.4-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:3ff89f9b835100a825b14c2808a106b6fdcc4b15483141482a12c725e7f78549"},
     {url = "https://files.pythonhosted.org/packages/25/6e/ba97809175c90cbdcd33b470e466ebf0854d15d1506e605cc0ddd284d5b6/psutil-5.9.4-cp36-abi3-win_amd64.whl", hash = "sha256:fd8522436a6ada7b4aad6638662966de0d61d241cb821239b2ae7013d41a43d4"},
@@ -278,6 +469,10 @@
     {url = "https://files.pythonhosted.org/packages/a5/73/35cea01aad1baf901c915dc95ea33a2f271c8ff8cf2f1c73b7f591f1bdf1/psutil-5.9.4-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:54d5b184728298f2ca8567bf83c422b706200bcbbfafdc06718264f9393cfeb7"},
     {url = "https://files.pythonhosted.org/packages/ec/be/b8df2071eda861e65a1b2cec35770bb1f4523737e84a10aa41c53e39e9bc/psutil-5.9.4-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:6b92c532979bafc2df23ddc785ed116fced1f492ad90a6830cf24f4d1ea27d24"},
 ]
+"ptyprocess 0.7.0" = [
+    {url = "https://files.pythonhosted.org/packages/20/e5/16ff212c1e452235a90aeb09066144d0c5a6a8c0834397e03f5224495c4e/ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220"},
+    {url = "https://files.pythonhosted.org/packages/22/a6/858897256d0deac81a172289110f31629fc4cee19b6f01283303e18c8db3/ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35"},
+]
 "pulsectl 22.3.2" = [
     {url = "https://files.pythonhosted.org/packages/38/b5/7fb041271e9db6ad54a74eecbc84f8c9a9faf696c267b63dfc9fbbf63d49/pulsectl-22.3.2.tar.gz", hash = "sha256:cc174e1ceebd4e6222c5b78f4f415f12e80753c9ab75ab354159b4cb5950b084"},
     {url = "https://files.pythonhosted.org/packages/e7/dc/53454b4d9f52f53aa5d95b0c5aa1adaf80e324f760e9086db4e2de2dab24/pulsectl-22.3.2-py2.py3-none-any.whl", hash = "sha256:6fded96a8e6a9ac6c387ed3a9bdb1afb20d80256b0c9df4b98b58461d74ba0c4"},
@@ -286,17 +481,22 @@
     {url = "https://files.pythonhosted.org/packages/02/98/b5618e70db2279c9d7f604283df21eadd0d0a5e878bc5a0b9fd85e14fc96/pulsectl-asyncio-1.0.0.tar.gz", hash = "sha256:46ef1573a17fb064196e8d8f0d9031a9377f717cde26769d97959ce4b9395af0"},
     {url = "https://files.pythonhosted.org/packages/41/4d/b973e74ed465735fc230089cc9167152f692d7f6221ffccf35a3b3156e7f/pulsectl_asyncio-1.0.0-py3-none-any.whl", hash = "sha256:5842fe428da9ede62077469f15dedd7c19f1e0085a641f22e2266449fd394dd2"},
 ]
+"pure-eval 0.2.2" = [
+    {url = "https://files.pythonhosted.org/packages/2b/27/77f9d5684e6bce929f5cfe18d6cfbe5133013c06cb2fbf5933670e60761d/pure_eval-0.2.2-py3-none-any.whl", hash = "sha256:01eaab343580944bc56080ebe0a674b39ec44a945e6d09ba7db3cb8cec289350"},
+    {url = "https://files.pythonhosted.org/packages/97/5a/0bc937c25d3ce4e0a74335222aee05455d6afa2888032185f8ab50cdf6fd/pure_eval-0.2.2.tar.gz", hash = "sha256:2b45320af6dfaa1750f543d714b6d1c520a1688dec6fd24d339063ce0aaa9ac3"},
+]
 "pycparser 2.21" = [
     {url = "https://files.pythonhosted.org/packages/5e/0b/95d387f5f4433cb0f53ff7ad859bd2c6051051cebbb564f139a999ab46de/pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"},
     {url = "https://files.pythonhosted.org/packages/62/d5/5f610ebe421e85889f2e55e33b7f9a6795bd982198517d912eb1c76e1a53/pycparser-2.21-py2.py3-none-any.whl", hash = "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9"},
 ]
+"pygments 2.14.0" = [
+    {url = "https://files.pythonhosted.org/packages/0b/42/d9d95cc461f098f204cd20c85642ae40fbff81f74c300341b8d0e0df14e0/Pygments-2.14.0-py3-none-any.whl", hash = "sha256:fa7bd7bd2771287c0de303af8bfdfc731f51bd2c6a47ab69d117138893b82717"},
+    {url = "https://files.pythonhosted.org/packages/da/6a/c427c06913204e24de28de5300d3f0e809933f376e0b7df95194b2bb3f71/Pygments-2.14.0.tar.gz", hash = "sha256:b3ed06a9e8ac9a9aae5a6f5dbe78a8a58655d17b43b93c078f094ddc476ae297"},
+]
 "python-xlib 0.33" = [
     {url = "https://files.pythonhosted.org/packages/86/f5/8c0653e5bb54e0cbdfe27bf32d41f27bc4e12faa8742778c17f2a71be2c0/python-xlib-0.33.tar.gz", hash = "sha256:55af7906a2c75ce6cb280a584776080602444f75815a7aff4d287bb2d7018b32"},
     {url = "https://files.pythonhosted.org/packages/fc/b8/ff33610932e0ee81ae7f1269c890f697d56ff74b9f5b2ee5d9b7fa2c5355/python_xlib-0.33-py2.py3-none-any.whl", hash = "sha256:c3534038d42e0df2f1392a1b30a15a4ff5fdc2b86cfa94f072bf11b10a164398"},
 ]
-"quartz 0.0.1.dev0" = [
-    {url = "https://files.pythonhosted.org/packages/31/f2/4fac785ec7690dd2b9f87506aa52e702ed5d98dedc06a2e940c4a076136a/quartz-0.0.1.dev0.tar.gz", hash = "sha256:95d6adb994aa2713dc51fa9f1799227e247c826b71216ebf25cecab884685d35"},
-]
 "runcmd 2020.12.3" = [
     {url = "https://files.pythonhosted.org/packages/41/b7/08c18bc2944c0443aa77672e5610e861c6946f93f247bef0aa8d7737b248/runcmd-2020.12.3.tar.gz", hash = "sha256:1f2652c2e51404bb0faaa83610f23fdafd4c246b46e361a5dd6d4334d013fc24"},
 ]
@@ -308,6 +508,10 @@
     {url = "https://files.pythonhosted.org/packages/c3/a0/5dba8ed157b0136607c7f2151db695885606968d1fae123dc3391e0cfdbf/sniffio-1.3.0-py3-none-any.whl", hash = "sha256:eecefdce1e5bbfb7ad2eeaabf7c1eeb404d7757c379bd1f7e5cce9d8bf425384"},
     {url = "https://files.pythonhosted.org/packages/cd/50/d49c388cae4ec10e8109b1b833fd265511840706808576df3ada99ecb0ac/sniffio-1.3.0.tar.gz", hash = "sha256:e60305c5e5d314f5389259b7f22aaa33d8f7dee49763119234af3755c55b9101"},
 ]
+"stack-data 0.6.2" = [
+    {url = "https://files.pythonhosted.org/packages/6a/81/aa96c25c27f78cdc444fec27d80f4c05194c591465e491a1358d8a035bc1/stack_data-0.6.2-py3-none-any.whl", hash = "sha256:cbb2a53eb64e5785878201a97ed7c7b94883f48b87bfb0bbe8b623c74679e4a8"},
+    {url = "https://files.pythonhosted.org/packages/db/18/aa7f2b111aeba2cd83503254d9133a912d7f61f459a0c8561858f0d72a56/stack_data-0.6.2.tar.gz", hash = "sha256:32d2dd0376772d01b6cb9fc996f3c8b57a357089dec328ed4b6553d037eaf815"},
+]
 "starlette 0.23.1" = [
     {url = "https://files.pythonhosted.org/packages/0c/d2/a2898deb36d12e40e84e83b7728628a04013cb0cfc545932c4605185bf2d/starlette-0.23.1.tar.gz", hash = "sha256:8510e5b3d670326326c5c1d4cb657cc66832193fe5d5b7015a51c7b1e1b1bf42"},
     {url = "https://files.pythonhosted.org/packages/a3/1d/b23984c05e39ddab35bbba33a3828dc4f896250220dcbd946c0fcad1e934/starlette-0.23.1-py3-none-any.whl", hash = "sha256:ec69736c90be8dbfc6ec6800ba6feb79c8c44f9b1706c0b2bb27f936bcf362cc"},
@@ -316,10 +520,18 @@
     {url = "https://files.pythonhosted.org/packages/0c/28/5ef1e05bd24984d508e93d4a0319b7f63f8ce5d3c33dd1515507c024f76e/starlette_exporter-0.15.1-py3-none-any.whl", hash = "sha256:24eeaef01f05ef973984704427f6e6a93d468f487b8b26ad77548d963affc9fe"},
     {url = "https://files.pythonhosted.org/packages/7c/6d/72fbe02fd9dec57c8990f466255082a26a7b298ccf8a6960f79f3286b746/starlette_exporter-0.15.1.tar.gz", hash = "sha256:3bde5d863effb26684210fe016a1ebf2b383efedf21a4b2f28585ec5064f4033"},
 ]
+"traitlets 5.9.0" = [
+    {url = "https://files.pythonhosted.org/packages/39/c3/205e88f02959712b62008502952707313640369144a7fded4cbc61f48321/traitlets-5.9.0.tar.gz", hash = "sha256:f6cde21a9c68cf756af02035f72d5a723bf607e862e7be33ece505abf4a3bad9"},
+    {url = "https://files.pythonhosted.org/packages/77/75/c28e9ef7abec2b7e9ff35aea3e0be6c1aceaf7873c26c95ae1f0d594de71/traitlets-5.9.0-py3-none-any.whl", hash = "sha256:9e6ec080259b9a5940c797d58b613b5e31441c2257b87c2e795c5228ae80d2d8"},
+]
 "uvicorn 0.20.0" = [
     {url = "https://files.pythonhosted.org/packages/95/3c/9f4650ff609370456f796bb96a355dcddef1ded67e05d1b4eb3481088329/uvicorn-0.20.0.tar.gz", hash = "sha256:a4e12017b940247f836bc90b72e725d7dfd0c8ed1c51eb365f5ba30d9f5127d8"},
     {url = "https://files.pythonhosted.org/packages/96/f3/f39ac8ac3bdf356b4934b8f7e56173e96681f67ef0cd92bd33a5059fae9e/uvicorn-0.20.0-py3-none-any.whl", hash = "sha256:c3ed1598a5668208723f2bb49336f4509424ad198d6ab2615b7783db58d919fd"},
 ]
+"wcwidth 0.2.6" = [
+    {url = "https://files.pythonhosted.org/packages/20/f4/c0584a25144ce20bfcf1aecd041768b8c762c1eb0aa77502a3f0baa83f11/wcwidth-0.2.6-py2.py3-none-any.whl", hash = "sha256:795b138f6875577cd91bba52baf9e445cd5118fd32723b460e30a0af30ea230e"},
+    {url = "https://files.pythonhosted.org/packages/5e/5f/1e4bd82a9cc1f17b2c2361a2d876d4c38973a997003ba5eb400e8a932b6c/wcwidth-0.2.6.tar.gz", hash = "sha256:a5220780a404dbe3353789870978e472cfe477761f06ee55077256e509b156d0"},
+]
 "xprintidle 0.2" = [
     {url = "https://files.pythonhosted.org/packages/19/64/5ca302f96fd499e65f3b3d49ca5d41b8d9fac7a70ae5a7094baf93db380a/xprintidle-0.2.tar.gz", hash = "sha256:86695d81a4b2399752946f7f79f4a5d48abbac6c325c0f34b3e82a9bff063453"},
 ]
--- a/power_linux.py	Sat Mar 04 12:10:36 2023 -0800
+++ b/power_linux.py	Wed Mar 08 10:57:11 2023 -0800
@@ -8,7 +8,7 @@
 
 def is_screen_on():
     _display.sync()
-    return _display.dpms_info().state== 1  # untested
+    return _display.dpms_info().state == 1  # untested
 
 
 def go_lock_screen():
@@ -23,4 +23,3 @@
 
 def go_sleep_mode():
     subprocess.run(['systemctl', 'suspend'])
-
--- a/power_osx.py	Sat Mar 04 12:10:36 2023 -0800
+++ b/power_osx.py	Wed Mar 08 10:57:11 2023 -0800
@@ -1,10 +1,18 @@
 import subprocess
-import Quartz
+import ast
+
 
 def is_screen_on():
-    d=Quartz.CGSessionCopyCurrentDictionary()
-    print(d)
-
+    screen_is_locked = ast.literal_eval(
+        subprocess.check_output(
+            [
+                '/usr/bin/python',  # system one with Quartz available
+                '-sc',
+                'import Quartz; ' +
+                'print Quartz.CGSessionCopyCurrentDictionary().get("CGSSessionScreenIsLocked", False)'
+            ],
+            encoding='ascii'))
+    return not screen_is_locked
 
 
 def go_lock_screen():
@@ -18,4 +26,5 @@
 def go_sleep_mode():
     subprocess.run(['pmset', 'sleepnow'])
 
+
 is_screen_on()
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/progs_all.py	Wed Mar 08 10:57:11 2023 -0800
@@ -0,0 +1,29 @@
+import psutil
+from typing import Dict, List, Optional
+
+
+def progname(cmdline: List[str]) -> Optional[str]:
+    if len(cmdline) < 1:
+        return None
+    if cmdline[-1].endswith('/steam'):
+        return 'steam'
+    if cmdline[0].endswith('/minecraft-launcher'):
+        return 'minecraft-launcher'
+    if cmdline[0].endswith('/java') and '--versionType' in cmdline:
+        return 'minecraft'
+    if cmdline[0].endswith('/RobloxPlayer'):
+        return 'roblox'
+
+
+def get_running_progs() -> Dict[str, int]:
+    progs = set()
+    for proc in psutil.process_iter(['pid', 'name']):
+        try:
+            prog = progname(proc.cmdline())
+            if prog:
+                progs.add(prog)
+        except (psutil.AccessDenied, psutil.NoSuchProcess):
+            pass
+    return dict(
+        (p, p in progs)
+        for p in ['steam', 'minecraft-launcher', 'minecraft', 'roblox'])
--- a/pyproject.toml	Sat Mar 04 12:10:36 2023 -0800
+++ b/pyproject.toml	Wed Mar 08 10:57:11 2023 -0800
@@ -11,11 +11,16 @@
     "psutil>=5.9.4",
     "background-loop>=1.7.0",
     "starlette-exporter>=0.15.1",
+    "daemonocle>=1.2.3",
+    "python-xlib>=0.33",
 ]
 requires-python = ">=3.10"
 license = {text = "MIT"}
 
 [project.optional-dependencies]
+dev = [
+    "ipython>=8.11.0",
+]
 linux = [
     "xprintidle>=0.2",
     "pulsectl-asyncio>=1.0.0",
@@ -23,7 +28,7 @@
 ]
 osx = [
     "osascript>=2020.12.3",
-    "quartz>=0.0.1.dev0"
+    # "quartz>=0.0.1.dev0"
 ]
 
 [tool.pdm]
--- a/racc.py	Sat Mar 04 12:10:36 2023 -0800
+++ b/racc.py	Wed Mar 08 10:57:11 2023 -0800
@@ -1,70 +1,80 @@
-from starlette.applications import Starlette
-from starlette.exceptions import HTTPException
-from starlette.requests import Request
-from starlette.responses import JSONResponse, HTMLResponse
-from starlette.staticfiles import StaticFiles
-from starlette.routing import Route, Mount
-from starlette.templating import Jinja2Templates
-import uvicorn
-import psutil
-from starlette_exporter import PrometheusMiddleware, handle_metrics
-from prometheus_client import Summary, Gauge
-import background_loop
-from typing import List, Optional
 import logging
+import os
 import socket
 import sys
+
+import background_loop
+import daemonocle
+import psutil
+import uvicorn
+from prometheus_client import Gauge
+from starlette.applications import Starlette
+from starlette.requests import Request
+from starlette.responses import HTMLResponse
+from starlette.routing import Route
+from starlette.staticfiles import StaticFiles
+from starlette_exporter import PrometheusMiddleware, handle_metrics
+''
+import progs_all as progs
+
 if psutil.OSX:
+    import current_window_title_osx as current_window_title
     import idle_osx as idle
-    import volume_osx as volume
     import power_osx as power
+    import volume_osx as volume
 elif psutil.LINUX:
+    import current_window_title_linux as current_window_title
     import idle_linux as idle
+    import power_linux as power
     import volume_linux as volume
-    import power_linux as power
 else:
     raise NotImplementedError(repr(sys.implementation))
 
-hostname = socket.gethostname().split('.')[0]
+DEBUG = os.environ.get('RACC_DEBUG', False)
 logging.basicConfig(level=logging.INFO)
 log = logging.getLogger()
-
-
-def progname(cmdline: List[str]) -> Optional[str]:
-    if len(cmdline) < 1:
-        return None
-    if cmdline[-1].endswith('/steam'):
-        return 'steam'
-    if cmdline[0].endswith('/minecraft-launcher'):
-        return 'minecraft-launcher'
-    if cmdline[0].endswith('/java') and '--versionType' in cmdline:
-        return 'minecraft'
-
-
+hostname = socket.gethostname().split('.')[0]
 RACC_RUNNING = Gauge("racc_running", "program is running", ['host', 'prog'])
 RACC_IDLE = Gauge("racc_idle", "desktop mouse/kb idle seconds", ['host'])
-RACC_SCREEN = Gauge("racc_screen", "screen in unlocked/on mode", ['host'])
+RACC_SCREEN = Gauge("racc_screen_on", "screen in unlocked/on mode", ['host'])
+RACC_CURRENT_WINDOW = Gauge("racc_current_window",
+                            "label carries title; site is last part",
+                            ['host', 'title', 'site'])
 
 
-def update_progs(first_run):
-    out = []
-    progs = set()
-    for proc in psutil.process_iter(['pid', 'name']):
-        try:
-            prog = progname(proc.cmdline())
-            if prog:
-                progs.add(prog)
-        except (psutil.AccessDenied, psutil.NoSuchProcess):
-            pass
+def update(first_run):
+    try:
+        for p, val in progs.get_running_progs().items():
+            RACC_RUNNING.labels(host=hostname, prog=p).set(val)
+    except Exception:
+        if DEBUG: raise
+
+    try:
+        RACC_IDLE.labels(host=hostname).set(idle.get_idle_seconds())
+    except Exception:
+        if DEBUG: raise
+
+    try:
+        RACC_SCREEN.labels(host=hostname).set(power.is_screen_on())
+    except Exception:
+        if DEBUG: raise
 
-    for p in [
-            'minecraft',
-            'minecraft-launcher',
-            'steam',
-    ]:
-        RACC_RUNNING.labels(host=hostname, prog=p).set(p in progs)
-    RACC_IDLE.labels(host=hostname).set(idle.get_idle_seconds())
-    RACC_SCREEN.labels(host=hostname).set(power.is_screen_on())
+    try:
+        title = current_window_title.get_current_window_title()
+        # chrome, at least on osx, adds icon to window title when it's playing audio
+        title = title.rstrip('🔊').strip()
+        # some websites choose to title like '<something> - <site name>'
+        last_section = title.split(' - ')[-1].strip()
+        if last_section in {'YouTube', 'Google Search', 'Google Chrome', 'Visual Studio Code', 'Roblox'}:
+            site = last_section.lower().replace(' ', '_')
+        else:
+            site = ''
+        RACC_CURRENT_WINDOW.clear()
+        RACC_CURRENT_WINDOW.labels(host=hostname, title=title,
+                                   site=site).set(1)
+    except Exception:
+        if DEBUG: raise
+
 
 async def root(req: Request) -> HTMLResponse:
     vol = await volume.get_volume()
@@ -77,13 +87,23 @@
                         Route('/', root),
                     ],
                     on_startup=[
-                        lambda: background_loop.loop_forever(update_progs, 3),
+                        lambda: background_loop.loop_forever(update, 5),
                     ])
 
     app.add_middleware(PrometheusMiddleware, app_name='racc')
     app.add_route("/metrics", handle_metrics)
-    return app
+    uvicorn.run(app, host='0.0.0.0', port=5150)
 
 
 if __name__ == "__main__":
-    uvicorn.run(main(), host='0.0.0.0', port=5150)
+    d = daemonocle.Daemon(
+        worker=main,
+        work_dir='.',
+        pid_file='/tmp/racc.pid',
+        detach=True,
+    )
+    d.stop(timeout=1, force=True)
+    if DEBUG:
+        main()
+    else:
+        d.cli()
--- a/run.sh	Sat Mar 04 12:10:36 2023 -0800
+++ b/run.sh	Wed Mar 08 10:57:11 2023 -0800
@@ -1,9 +1,11 @@
 #!/bin/sh
 
+cd `dirname $0`
+
 if [ `uname` = Linux ]; then
-  pdm run python racc.py
+  pdm run python racc.py restart
 elif [ `uname` = Darwin ]; then
-  ${HOME}/Library/Python/3.10/bin/pdm run python racc.py
+  ${HOME}/Library/Python/3.10/bin/pdm run python racc.py restart
 else
   echo `uname` unknown
   exit 1