Files

393 lines
6.4 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""qs-launch — wofi-based launcher modes: emoji pick, calculator, DuckDuckGo search.
Subcommands:
emoji feed a curated emoji list into wofi --dmenu, copy the picked emoji
calc type an expression in wofi, evaluate with qalc, copy + toast result
search type a query in wofi, open DuckDuckGo in the default browser
All binary paths are injected via $QS_* env vars by the .local/bin/qs-launch
wrapper so execution works regardless of PATH (same pattern as qs-bt's
$BLUECTL). When no DISPLAY/WAYLAND_DISPLAY is present, wofi can't open, so each
mode exits silently with code 1.
"""
import os
import subprocess
import sys
import urllib.parse
# Curated emoji list: "<char> <shortcode>". Searchable by name, output splits
# on the first whitespace so only the emoji character gets copied.
EMOJI = """😀 grin
😃 smiley
😁 beam
😂 joy
🤣 rofl
😊 blush
🙂 slight smile
😉 wink
😍 heart eyes
😘 kiss
🤩 star struck
🥳 party
😎 cool
🤓 nerd
😇 halo
😏 smirk
😜 winky tongue
🥺 plead
😬 grimace
🤔 think
🤯 mind blown
😴 sleepy
😭 cry
😢 sad
😞 disappointed
😠 angry
😤 triumph
🤬 swear
🙄 roll eyes
😐 neutral
😑 expressionless
🙂🙃 upside down
😱 scream
🤒 sick
🤕 hurt
💀 skull
👻 ghost
👽 alien
🤖 robot
🎃 pumpkin
💩 poo
🙌 yes
👏 clap
🙏 pray
🤝 handshake
👍 thumbs up
👎 thumbs down
👊 fist
✊ raised fist
🤞 crossed fingers
✌️ peace
👋 hello wave
🖐️ hand
✍️ writing
💪 muscle
🫶 heart hands
🦾 bionic arm
🧠 brain
👀 eyes
👁️ eye
👄 lips
👅 taste tongue
🧛 vampire
🧟 zombie
❤️ heart
🧡 orange heart
💛 yellow heart
💚 green heart
💙 blue heart
💜 purple heart
🖤 black heart
🤍 white heart
🤎 brown heart
💔 broken heart
❤️‍🔥 heart fire
💯 hundred
💥 boom
💫 dizzy
✨ sparkles
🔥 fire
🌟 star
⭐ star white
☀️ sun
🌙 moon
🌑 new moon
🌈 rainbow
☁️ cloud
⛈️ storm
❄️ snow
🌊 ocean wave
🌍 earth
🌺 hibiscus
🌸 cherry blossom
🌹 rose
🌷 tulip
🌵 cactus
🌴 palm
🍀 clover
🍄 mushroom
🐶 dog
🐱 cat
🦊 fox
🐻 bear
🐼 panda
🐨 koala
🦁 lion
🐯 tiger
🐸 frog
🐵 monkey
🐧 penguin
🦄 unicorn
🐝 bee
🦋 butterfly
🐢 turtle
🐙 octopus
🦈 shark
🐬 dolphin
🍍 pineapple
🥭 mango
🍒 cherries
🍓 strawberry
🍑 peach
🥑 avocado
🍕 pizza
🍔 burger
🍟 fries
🌭 hotdog
🍿 popcorn
🌮 taco
🍰 cake
🍩 donut
🥐 croissant
☕ coffee
🍵 tea
🍺 beer
🍻 cheers
⚽ football
🏀 basketball
🎾 tennis
⚾ baseball
🏈 american football
🎮 game
🎲 dice
🎯 target
🎵 music
🎶 notes
🎤 mic
🎧 headphones
📻 radio
🎬 movie
📺 tv
💻 laptop
📱 phone
🖥️ computer
🖱️ mouse
💾 floppy
💿 disc
🔋 battery
🔌 plug
💡 bulb
🔦 flashlight
🗑️ trash
🔒 lock
🔓 unlocked
🔑 key
✂️ scissors
📎 paperclip
📌 pin
📍 location
🧲 magnet
🛠️ tools
⚙️ gear
🔧 wrench
✏️ pencil
🖊️ pen
📖 book
📚 books
📄 doc
📁 folder
📦 package
✉️ mail
📝 memo
📊 chart
💰 money
💳 card
🧾 receipt
🏆 trophy
🥇 gold
🥈 silver
🎁 gift
🎈 balloon
🎉 party popper
🥂 toast
🎊 confetti
🧹 clean
🧽 sponge
⏰ alarm
⏱️ stopwatch
🕰️ clock
📅 calendar
📆 date
🔔 bell
🔕 bell off
❌ cross
✅ check
⚠️ warning
🚫 no entry
plus
minus
➗ divide
✖️ multiply
🔄 refresh
⬆️ up
⬇️ down
⬅️ left
➡️ right
↩️ return
🔙 back
🔜 soon
🌐 globe
🚀 rocket
🛸 saucer
✈️ plane
🚗 car
🚕 taxi
🚌 bus
🚂 train
🚲 bike
🏍️ motorcycle
👻 spooky
☠️ skull crossbones
💣 bomb
🚁 chopper
🚢 ship
🚄 bullet train
🚦 traffic light
⛽ fuel
🅿️ parking
🚧 roadwork
🌉 bridge
🏰 castle
⛺ camp
🌋 volcano
🗼 tower
🏯 pagoda
🌆 city dusk
🌃 city night
🏙️ skyline
🖼️ frame
🎨 palette
🖌️ brush
🧶 yarn
🧵 thread
💍 ring
👟 sneaker
👠 heel
🧢 cap
🎓 graduate
👒 hat
🧣 scarf
🧤 gloves
🧥 coat
👔 tie
👕 shirt
👖 jeans
💭 thought
💬 speech
🗯️ anger
⌨️ keyboard
🖨️ printer
🧮 abacus
🕹️ joystick
🎳 bowling
🏓 ping pong
🏸 badminton
🥊 boxing
🥋 martial arts
🎽 running shirt
🚴 cyclist
🧗 climber
🏊 swimmer
🎪 circus
🎠 carousel
🗿 moai
🏛️ classical
🏟️ stadium
🏫 school
🏥 hospital
🏦 bank
🏪 store
🏭 factory
🏝️ island
🏖️ beach
⛱️ umbrella
🧭 compass"""
def run(command, **kwargs):
return subprocess.run(command, capture_output=True, text=True, **kwargs)
def wofi(entries, prompt, exec_search=False):
cmd = [os.environ.get("QS_WOFI", "wofi"), "--dmenu", "--prompt", prompt]
if exec_search:
cmd.append("--exec-search")
result = run(cmd, input=entries)
if result.returncode != 0:
return None
return result.stdout.rstrip("\n")
def cmd_mode():
if "WAYLAND_DISPLAY" not in os.environ and "DISPLAY" not in os.environ:
return 1
sub = sys.argv[1] if len(sys.argv) > 1 else ""
if sub == "emoji":
return emoji_mode()
if sub == "calc":
return calc_mode()
if sub == "search":
return search_mode()
print(__doc__, file=sys.stderr)
return 2
def emoji_mode():
picked = wofi(EMOJI, "\U0001f600 Emoji")
if not picked:
return 0
char = picked.split(" ", 1)[0]
if not char:
return 0
wl_copy = os.environ.get("QS_WLCOPY", "wl-copy")
run([wl_copy, char])
return 0
def calc_mode():
expr = wofi("", "=? \u2014 1+2 then Enter", exec_search=True)
if not expr:
return 0
qalc = os.environ.get("QS_QALC", "qalc")
result = run([qalc, "-t", expr])
if result.returncode != 0:
return 0
answer = result.stdout.strip()
wl_copy = os.environ.get("QS_WLCOPY", "wl-copy")
run([wl_copy, answer])
notify(os.environ.get("QS_NOTIFY", "notify-send"),
"\U0001f4d0 " + expr, "= " + answer + " (copied)")
return 0
def search_mode():
query = wofi("", "Search the web", exec_search=True)
if not query:
return 0
url = "https://duckduckgo.com/?q=" + urllib.parse.quote_plus(query)
xdg_open = os.environ.get("QS_XDGO", "xdg-open")
subprocess.Popen([xdg_open, url])
return 0
def notify(sender, title, body):
try:
run([sender, "-a", "qs-launch", title, body])
except OSError:
pass
if __name__ == "__main__":
raise SystemExit(cmd_mode())