Dup Ver Goto 📝

Simple Launcher 1

To
188 lines, 688 words, 5128 chars Page 'SimpleLauncher_01' does not exist.

A very poor mans launcher thing. I bind it to A-C-M-space and then define shorthands in ~/.schmerp.json. This is for things that aren't quite so common that I want to bind an actual key combo to them, but common enough that I want to be able to get them slightly quicker than launching an terminal and using that. The problem was that I would launch a throwaway terminal to run something, not close it, and be left with 100 open terminals that I had to go through to find which ones I wanted to close.

If the shorthand is not found, we attempt to run as command, so this also doubles as a quick command prompt. We split ignoring quotes, so "o 'hello world'" would split to ["o","'hello","world'"] — if you want full shell stuff, launch a terminal.

The first item is, after expansion, given to shutil.which() to see if it is a command, and if so, then we execv that command with the given arguments.

We append the result of the items to the result, so that we could then append args to the entry. E.g. "x y z" would expand to ["run_something_with_no_args","y","z"]

Example ~/.schmerp.json

{
  "x": "run_something_with_no_args",
  "y": [ "run", "something", "with", "args" ]
}

what actually happens is in the DoSomething class supplied via the delegate= parameter to the Main() constructor, which can be replaced so that e.g. we can run Python code instead of just running an external program.

#!/usr/bin/env python

"""actions defined by ~/.schmerp.json

A very poor mans launcher thing.
I bind it to A-C-M-space and then define
shorthands in ~/.schmerp.json.
This is for things that aren't quite so common
that I want to bind an actual key combo to them,
but common enough that I want to be able to get them
slightly quicker than launching an terminal and using
that. The problem was that I would launch a throwaway
terminal to run something, not close it, and be left
with 100 open terminals that I had to go through to
find which ones I wanted to close.

If the shorthand is not found, we attempt to run as command,
so this also doubles as a quick command prompt.
We split ignoring quotes, so "o 'hello world'"
would split to ["o","'hello","world'"] -- if you want
full shell stuff, launch a terminal.

The first item is, after expansion, given to shutil.which()
to see if it is a command, and if so, then we execv that
command with the given arguments.

We append the result of the items to the result,
so that we could then append args to the entry. 
E.g. "x y z" would expand to
["run_something_with_no_args","y","z"]

Example ~/.schmerp.json
{
  "x": "run_something_with_no_args",
  "y": [ "run", "something", "with", "args" ]
}

what actually happens is in the DoSomething class,
which can be replaced so that e.g. we can run Python
code instead of just running an external program.

Keys:
    Escape: exit
    Shift-BackSpace: clear
"""

import tkinter as tk
import tkinter.messagebox
from subprocess import run
import shutil
import os
import json
import re

class Main:
  def __init__(self, delegate=None):
    self.delegate = delegate

  def main(self):
    root = tk.Tk()

    label = tk.Label(root, text="Schmerp:", font=("Optima",30))
    label.grid(row=0)
    textinput = tk.Entry(root,font=("Hack Nerd Font Mono",30))
    textinput.grid(row=0,column=1)
    textinput.focus()

    root.bind('<Return>',self.return_handler)
    root.bind('<Escape>',self.escape_handler)
    root.bind('<Shift-BackSpace>',self.sbackspace_handler)

    self.label = label
    self.textinput = textinput
    self.root = root

    root.mainloop()

  def __call__(self,*xs,**kw):
    return self.main(*xs,**kw)

  def sbackspace_handler(self,e):
    self.textinput.delete(0,tk.END)

  def escape_handler(self,e):
    print("Escape")
    exit()

  def return_handler(self,e):
    value = self.textinput.get()
    if self.delegate:
      try:
        if self.delegate(value):
          exit()
      except Exception as e:
        self.delegate.do_error(e)

class DoSomething:
  def __init__(self):
    ifn = os.path.expanduser("~/.schmerp.json")
    try:
      with open(ifn) as f:
        self.schmerp = json.load(f)
    except Exception as e:
      print("No ~/.schmerp.json",e)
      self.schmerp = {
      }

  def __call__(self,x):
    xs = re.split(r"\s+",x)
    x = xs[0]
    if x in self.schmerp:
      y = self.schmerp[x]
      xs.pop(0)
      if type(y) is str:
        xs.insert(0,y)
      elif type(y) is list:
        xs = y + xs
      else:
        self.do_error(ValueError("Must be string or list"))
    cmd, *args = xs
    try:
      self.do(cmd,*args)
      return True
    except Exception as e:
      self.do_error(e)
      return False

  def do(self,cmd,*args):
    wcmd = shutil.which(cmd)
    if wcmd is not None:
      return os.execv(wcmd,[cmd,*args])
    else:
      raise FileNotFoundError(f"Cannot do {cmd}")

  def do_error(self,e):
    print("do_error",e)
    txt = repr(e)
    tk.messagebox.showinfo(message=txt,icon=tkinter.messagebox.ERROR)

if __name__ == "__main__":
  main = Main(delegate=DoSomething())
  main()
  exit()