destroyed repo, recreated with new nimble packaging

This commit is contained in:
Paul Wilde 2022-07-16 23:07:51 +01:00
commit f69b202da8
46 changed files with 5995 additions and 0 deletions

8
.gitignore vendored Executable file
View file

@ -0,0 +1,8 @@
# ignore all
*
# unignore all with extensions
!*.*
# unignore directories
!*/

70
README.MD Executable file
View file

@ -0,0 +1,70 @@
# A selection of information output tools for dmenu
These are a selection of independant tools for displaying various information
about system status in dmenu. Some of them i.e. `volume` have options (up, down, mute...)
which are selectable options in dmenu.
## Tools
- `pingclock` performs a single `ping` to a server and returns the response time
- `battery` shows the current battery level
- `brightness` shows the current backlight level and gives options to adjust it
- `volume` shows the current volume level and gives options to adjust and manage it
- `date` shows the date
- `fuzzytime` shows the fuzzytime clock
- `wlan` shows the state of the wireless network interface. SSID connected to and signal level.
- `nic` shows the status and/or the ip address of the network interface card
- `temperature` shows the current CPU temperature
- `notes` a simple one liner note taking tool, displaying notes in `dmenu`/`rofi`
- `calculate` a calculator, utilising `qalculate` - inspired by [@fedops](https://codeberg.org/fedops/scripts)
- `emoji` an emoji picker
- `remmina` reads the files in your remmina config directory and allows you to connect to and edit them
- `translate` utilises libretranslate (you'll need and API key or your own instance) to translate test. Prefix the text with `en>de`, `de>en`, `en>fr`, etc. as you need. Must be compiled with `-d:ssl`
The next two do not work with `rofi` unless you have `alias dmenu=rofi` set, but they're pretty nice tools
- `passmenu_wrapper` a wrapper for passmenu. It basically just styles `passmenu` with no other features
- `command_wrapper` inspired by passmenu_wrapper, a basic tool to run other `dmenu` related tools with uniform styling.
- For example: `dmenu_run`, `clipmenu`, `passmenu` etc.
### Example in `dmenu`:
![dmenu_tools](https://user-images.githubusercontent.com/31094984/167123173-ee8092a2-d5ab-47b4-b207-ced328072cc0.gif)
### Example of `command_wrapper` with `clipmenu`
![command_wrap](https://user-images.githubusercontent.com/31094984/167122436-eea0be88-a929-46e8-9b4d-cb677dcb129c.gif)
## How to compile
There are some configuration variables explicit to me, you'll need to change them for you for them to be useful I imagine.
Configuration variables are compile - there are no config files or runtime parameters (except for "rofi")
Each tool is compiled separately, for example:
```nim
nim c pingclock
```
and then run with
```sh
./pingclock
or
./pingclock rofi
```
## How to use
Personally, I have these bound to key combinations in i3.
In fact, I have a seperate `bindsym` mode in which all these
tools are accessible i.e. `$mod+i` to get to "info" mode then `p` to show pingclock.
It's completely up to you how to run them, they're just simple CLI tools really.
### You can also set the volume and brightness levels by typing a numeric figure into the dmenu/rofi input box
## Dependencies
- `dmenu` or `rofi`
- `yad` for calendar
- `passmenu` for passmenu_wrapper
- basically any tool that's used to gather the information.
- "tools" for audio etc. (`pamixer`, `ncpamixer`, etc.) can be set in the source
## Full disclosure
I'm aware my code is messy.
I'm aware my code is mostly undocumented.
But hopefully these things are simple enough to work out.

293
base.nim Normal file
View file

@ -0,0 +1,293 @@
import std/[os,osproc,strutils,json,rdstdin,marshal]
type
Info* = object
title*: string
selected_fg*: string
selected_bg*: string
unselected_fg*: string
unselected_bg*: string
full_text*: string
# next few are for i3bar use
border*: string
background*: string
color*: string
html_text*: string
short_text*: string
args*: seq[string]
Menu = object
command: string
bottom: string
grab_kb: string
i_case: string
lines_shown: string
monitor: string
prompt: string
font: string
norm_bg: string
norm_fg: string
sel_bg: string
sel_fg: string
extra_cmd: string
i3BarInput* = object
button*: int
x*: int
y*: int
const WM_TOOLS_DIR* = getHomeDir() & "Nextcloud/.wm_tools/"
const background* = "#000000"
const backgroundalt* = "#bb222222"
const backgroundalt2* = "#bb333333"
const foreground* = "#dfdfdf"
const foregroundalt* = "#777"
const foregroundalt2* = "#ccc"
const black* = "#000000"
const white* = "#FFFFFF"
const yellow* = "#ffb52a"
const red* = "#e60053"
const purple* = "#9f78e1"
const blue* = "#0a6cf5"
const lightblue* = "#7296EF"
const lighterblue* = "#B5DDF7"
const green* = "#4b9901"
const lightgreen* = "#00ff00"
const grey* = "#dfdfdf"
const darkgrey* = "#444"
const primary* = yellow
const secondary* = red
const alert* = "#bd2c40"
const MAX_LINES = 20
const font = "Hermit-12"
const WL_DMENU = "dmenu"
const WL_ROFI = "wofi --dmenu"
var loop* = false
var stoploop* = true
var tool* = "dmenu"
var passmenu* = false
var command_wrapper* = false
var run_command* = ""
var wayland* = false
proc newInfo*(str: string = "Info"): Info =
var title = str
if tool == "rofi":
title = title & " : "
return Info(
title: title,
selected_fg: black,
selected_bg: white,
unselected_fg: white,
unselected_bg: black,
# next few are for i3bar use
border: white,
background: black,
color: foreground,
)
proc newMenuConfig(cmd: string = "dmenu"): Menu =
var run = cmd
if wayland and cmd == "dmenu":
run = WL_DMENU
var menu = Menu()
menu.command = run
menu.prompt = "-p"
menu.i_case = "-i"
menu.lines_shown = "-l"
return menu
proc newRofiConfig(cmd: string = "rofi -dmenu"): Menu =
var run = cmd
if wayland and cmd == "rofi -dmenu":
run = WL_ROFI
var menu = newMenuConfig(run)
menu.extra_cmd = "-markup-rows -kb-row-select \"Tab\" -kb-row-tab \"\""
return menu
proc newDmenuConfig(cmd: string = "dmenu"): Menu =
var run = cmd
if wayland and cmd == "dmenu":
run = WL_DMENU
var menu = newMenuConfig(run)
menu.bottom = "-b"
menu.grabkb = "-f"
menu.monitor = "-m"
menu.font = "-fn"
menu.norm_bg = "-nb"
menu.norm_fg = "-nf"
menu.sel_bg = "-sb"
menu.sel_fg = "-sf"
return menu
proc newMenu(): Menu =
if passmenu:
return newDmenuConfig("passmenu")
elif command_wrapper:
return newDmenuConfig(run_command)
elif tool == "rofi":
return newRofiConfig()
elif tool == "dmenu":
return newDmenuConfig()
return newMenuConfig()
proc debugLog*(str: string) =
let f = open("/tmp/debug.txt",fmAppend)
defer: f.close()
f.writeLine(str)
proc switchTwmMode*(mode: string = "default") =
# I intend to add support for more twm as time goes on (I switch around a lot)
# Switch out of an i3 bindsym mode if set
if wayland:
discard execCmd("sway mode \"" & mode & "\"")
else:
discard execCmd("i3-msg mode \"" & mode & "\"")
proc checkWayland() =
if getEnv("XDG_SESSION_TYPE") == "wayland":
wayland = true
proc parseInput*(): i3BarInput =
let input = readLineFromStdin("")
try:
let jsonNode = parseJson(input)
let i3input = to(jsonNode, i3BarInput)
return i3input
except:
return i3BarInput()
proc clearInput*(count: int = 1) =
for x in countup(1, count):
discard readLineFromStdin("")
proc getArguments*(): seq[string] =
let args = commandLineParams()
return args
proc stripQuotes*(str: string): string =
return replace(str,"\"",""")
proc quote*(str: string): string =
var text = str
# May need to put some further work to escape some special chars here
text = stripQuotes(text)
# Put leading and ending quote marks in
return " \"" & text & "\" "
# ^ Add a spaces ^ so the previous flag isn't touching
#
#
proc markup(str: string): string =
var text = str
return text
proc genMenuCmd*(data: Info, opts: varargs[string], rofi: bool = false): string =
# Build dmenu/rofi command
var cmd = ""
# if the text is empty, we don't want to create a menu item of it
if data.full_text != "":
let text = markup(data.full_text)
cmd &= text & "\n"
for opt in opts:
let text = markup(opt)
cmd = cmd & text & "\n"
cmd.removeSuffix("\n")
cmd = "echo -e" & quote(cmd) & " | "
var menu = newMenu()
cmd = cmd & menu.command & " "
cmd = cmd & menu.extra_cmd & " "
cmd = cmd & menu.i_case & " "
cmd = cmd & menu.lines_shown & " " & $MAX_LINES & " "
cmd = cmd & menu.prompt & quote(data.title)
cmd = cmd & menu.norm_bg & quote(data.unselected_bg)
cmd = cmd & menu.norm_fg & quote(data.unselected_fg)
cmd = cmd & menu.sel_bg & quote(data.selected_bg)
cmd = cmd & menu.sel_fg & quote(data.selected_fg)
cmd = cmd & menu.font & quote(font)
echo cmd
return cmd
proc runMenu*(data: Info, opts: varargs[string], rofi: bool = false): string =
let cmd = genMenuCmd(data, opts, rofi)
#echo cmd
#
# Run command and get output
var output = execCmdEx(cmd)
output.output.stripLineEnd()
return output.output
proc copyToClipboard*(str: string) =
if wayland:
discard execCmd("wl-copy " & str)
else:
discard execCmd("echo -n " & quote(str) & " | xclip -selection clipboard")
proc getCurrentClipboardContent*(): string =
var str = ""
if wayland:
let cur = execCmdEx("wl-paste")
if cur.exitcode == 0:
str = cur[0]
else:
echo cur
else:
let cur = execCmdEx("xsel -o -b")
if cur.exitcode == 0:
str = cur[0]
else:
echo cur
return strip(str)
proc outputData*(data: Info, args: varargs[string]): string {.discardable.} =
var output = ""
if tool == "rofi":
output = runMenu(data,args, rofi = true)
elif loop:
# mainly for i3bar/i3blocks compatible output
var j_data = data
if j_data.html_text != "":
j_data.full_text = j_data.html_text
echo $$j_data
else:
# if all else fails, use dmenu (default)
output = runMenu(data,args)
return output
proc checkCacheDir() =
if not dirExists(WM_TOOLS_DIR):
createDir(WM_TOOLS_DIR)
# At Start up:
checkCacheDir()
checkWayland()
# Switch bindsym mode back to default as it could be being used.
switchTwmMode()
let args* = getArguments()
for idx, arg in args:
case arg:
of "noloop":
stoploop = true
of "i3bar":
# I've kind of changed from using an i3bar to using #nobar so i3bar
# isn't really supported any more but this is here for backwards compatibility
loop = true
stoploop = false
of "dmenu":
stoploop = true
tool = "dmenu"
of "rofi":
stoploop = true
tool = "rofi"
of ["pass","passmenu"]:
passmenu = true
break

13
battery/battery.nimble Normal file
View file

@ -0,0 +1,13 @@
# Package
version = "0.1.0"
author = "Paul Wilde"
description = "Shows battery percentage using dmenu"
license = "MIT"
srcDir = "src"
bin = @["battery"]
# Dependencies
requires "nim >= 1.6.6"

126
battery/src/battery.nim Normal file
View file

@ -0,0 +1,126 @@
import ../../base
import std/[strutils,os]
const battery = "BAT0"
const ok_fg = lightgreen
const default_fg = white
const default_bg = black
const warning_fg = black
const warning_bg = red
const low_bg = black
const low_fg = red
const alert_fg = black
const alert_bg = yellow
const med_fg = green
const med_bg = black
proc batteryExists(): bool =
try:
let state = strip(readFile("/sys/class/power_supply/" & battery & "/present"))
if state == "1":
return true
except:
echo "Error getting battery : " & getCurrentExceptionMsg()
return false
proc isCharging(): bool =
try:
let state = strip(readFile("/sys/class/power_supply/" & battery & "/status"))
if state == "Charging":
return true
except:
echo "Error getting charging status : " & getCurrentExceptionMsg()
return false
proc getCharge(): int =
var charge = 0
try:
let chg = strip(readFile("/sys/class/power_supply/" & battery & "/capacity"))
if chg != "":
charge = parseInt(chg)
except:
echo "Error getting battery level : " & getCurrentExceptionMsg()
return charge
proc getDesign(charge: int, state: bool): (string, string, string, string, string) =
var icon = ""
var icon_colour = ok_fg
var col = default_fg
var bg = default_bg
var border = ok_fg
if isCharging():
icon = ""
else:
case charge:
of 0..5:
icon_colour = warning_fg
col = warning_fg
bg = warning_bg
of 6..19:
icon_colour = low_fg
border = low_bg
bg = default_bg
of 20..39:
icon_colour = alert_fg
border = alert_bg
icon = ""
of 40..59:
icon_colour = med_fg
border= med_fg
icon = ""
of 60..79:
icon_colour = med_fg
border = med_fg
icon = ""
of 80..100:
icon_colour = ok_fg
border = ok_fg
icon = ""
else:
icon = "x "
let main_text = icon & " " & $charge & "%"
# This next line is here for i3bar purposes
let html_text = "<span foreground=\"" & icon_colour & "\">" & icon & "</span>" & $charge & "%"
return (html_text,main_text, col, bg, border)
proc getOutput(charge: int, state: bool): Info =
let (html_text,main_text,col,bg_col,highlight_col) = get_design(charge, state)
var data = newInfo("Battery")
data.full_text = main_text
data.selected_bg = highlight_col
data.selected_fg = col # may just want `black` here
# i3bar stuff
data.html_text = html_text
data.color = col
data.border = highlight_col
data.background = bg_col
return data
proc getBatteryInfo() =
var last_charge = -1
var last_state = false
while true:
let charge = getCharge()
let state = isCharging()
if charge != last_charge or state != last_state:
let data = getoutput(charge, state)
outputData(data)
last_charge = charge
last_state = state
if stoploop:
break
sleep(1000)
proc main() =
if batteryExists():
getBatteryInfo()
else:
switchTwmMode()
if isMainModule:
main()

View file

@ -0,0 +1,13 @@
# Package
version = "0.1.0"
author = "Paul Wilde"
description = "Shows and controls laptop brightness"
license = "MIT"
srcDir = "src"
bin = @["brightness"]
# Dependencies
requires "nim >= 1.6.6"

View file

@ -0,0 +1,116 @@
import ../../base
import std/[os,strutils,osproc,math]
const backlight = "intel_backlight"
const default_bg = yellow
const default_fg = black
const BACKLIGHT_CMD = "xbacklight"
const UP_X = BACKLIGHT_CMD & " -inc %v" # %v is amount by
const DOWN_X = BACKLIGHT_CMD & " -dec %v" # %v is amount by
const SET_X = BACKLIGHT_CMD & " -set %v" # %v is amount by
const BACKLIGHT_CMD_WL = "brightnessctl"
const UP_WL = BACKLIGHT_CMD_WL & " set %v%+"
const DOWN_WL = BACKLIGHT_CMD_WL & " set %v%-"
const SET_WL = BACKLIGHT_CMD_WL & " set %v%"
var CMD = BACKLIGHT_CMD
var UP = UP_X
var DOWN = DOWN_X
var SET = SET_X
const default_value = "5"
proc getLimit(): int =
try:
let back_l = readFile("/sys/class/backlight/" & backlight & "/max_brightness")
return parseInt(strip(back_l))
except:
echo "Error getting backlight max : ", getCurrentExceptionMsg()
return -1
let limit = getLimit()
proc getDesign(pcnt: float): string =
var icon = "🌑"
case pcnt:
of 85..100:
icon = "🌕"
of 75..85:
icon = "🌖"
of 50..75:
icon = "🌗"
of 35..50:
icon = "🌘"
else:
icon = "🌑"
let percent = toInt(round(pcnt,0))
let text = icon & " " & $percent & "%"
return text
proc brightnessUp() =
let cmd = replace(UP,"%v",default_value)
discard execCmd(cmd)
proc brightnessDown() =
let cmd = replace(DOWN,"%v",default_value)
discard execCmd(cmd)
proc getBrightness*(run_once: bool = false) =
var last_pcnt: float = 0
while true:
let current = parseInt(strip(readFile("/sys/class/backlight/" & backlight & "/actual_brightness")))
let pcnt = (current/limit)*100
if pcnt != last_pcnt:
let text = getDesign(pcnt)
var data = newInfo("Brightness")
data.full_text = text
data.selected_bg = default_fg
data.selected_fg = default_bg
# i3bar stuff
data.border = default_fg
let args = @["up", "down"]
let option = outputData(data,args)
if option in args:
case option:
of "up":
brightnessUp()
getBrightness(true)
of "down":
brightnessDown()
getBrightness(true)
else:
try:
let i = parseInt(option)
let cmd = replace(SET,"%v",$i)
discard execCmd(cmd)
getBrightness(true)
except:
echo getCurrentExceptionMsg()
if run_once:
break
if stoploop:
break
last_pcnt = pcnt
sleep(1000)
proc main() =
if limit == -1:
switchTwmMode()
return
getBrightness()
if isMainModule:
block start:
if wayland:
CMD = BACKLIGHT_CMD_WL
UP = UP_WL
DOWN = DOWN_WL
SET = SET_WL
for arg in args:
case arg:
of "up":
brightnessUp()
break start
of "down":
brightnessDown()
break start
main()

View file

@ -0,0 +1,5 @@
# This is just an example to get you started. A typical binary package
# uses this file as the main entry point of the application.
when isMainModule:
echo("Hello, World!")

View file

@ -0,0 +1,13 @@
# Package
version = "0.1.0"
author = "Paul Wilde"
description = "A simple dmenu calculator"
license = "MIT"
srcDir = "src"
bin = @["calculate"]
# Dependencies
requires "nim >= 1.6.6"

View file

@ -0,0 +1,35 @@
import ../../base
import std/[osproc,strutils,sequtils]
#using qalc as it has a lot of nice inbuilt features
#
proc doCalculation(calc: string) =
var info = newInfo("Calculator")
let ans_cmd_terse = "echo \"" & calc & "\" | qalc +u8 -terse -color=never | awk '!/^>/ && !/^$/ {gsub(/^[ \\t]+|[ \\t]+$/, \"\", $0); print}'"
let ans_cmd_full = "echo \"" & calc & "\" | qalc +u8 -color=never | awk '!/^>/ && !/^$/ {gsub(/^[ \\t]+|[ \\t]+$/, \"\", $0); print}'"
var ans_full = execCmdEx(ans_cmd_full)
ans_full.output.stripLineEnd()
var ans_terse = execCmdEx(ans_cmd_terse)
ans_terse.output.stripLineEnd()
let answers = @[strip(ans_full.output),
strip(ans_terse.output)]
let args = concat(answers,@["exit"])
var cmd = outputData(info, args)
cmd.stripLineEnd()
if cmd in answers:
copyToClipboard(cmd)
elif cmd == "exit" or cmd == "":
return
else:
doCalculation(cmd)
proc main() =
var info = newInfo("Calculator")
let args = @["exit"]
let cmd = outputData(info, args)
if cmd != "":
doCalculation(cmd)
return
if isMainModule:
main()

13
clipurr/clipurr.nimble Normal file
View file

@ -0,0 +1,13 @@
# Package
version = "0.1.0"
author = "Paul Wilde"
description = "Clipboard manager for X11 or Wayland"
license = "MIT"
srcDir = "src"
bin = @["clipurr"]
# Dependencies
requires "nim >= 1.6.6"

135
clipurr/src/clipurr.nim Normal file
View file

@ -0,0 +1,135 @@
import ../../base
import std/[strutils,os,db_sqlite,osproc]
const CLIP_DB = WM_TOOLS_DIR & ".clipurr_cache.sqlite"
const KEEP_ITEMS = 20
proc addClip(str: var string)
proc killOldRunningProcesses() =
let x = execCmdEx("killall wl-paste clipnotify")
echo x
proc runDaemon() =
echo "Starting Daemon..."
if wayland:
echo "Using Wl-paste"
let cwd = getAppDir()
let outp = execProcess("wl-paste -n -w " & cwd & "/clipurr set")
else:
var run = true
while run:
echo "Using Clipnotify"
let outp = execCmdEx("clipnotify")
if outp.exitcode == 0:
var content = getCurrentClipboardContent()
addClip(content)
echo "Exiting Daemon..."
proc openDBConn(): DBConn =
let db: DBconn = open(CLIP_DB,"","","")
try:
db.exec(sql"""create table if not exists
clip_items (
timestamp DATETIME NOT NULL,
clip NVARCHAR(500) NOT NULL
)
""")
except:
echo getCurrentExceptionMsg()
return db
proc clearHistory() =
let db = openDBConn()
defer: db.close()
try:
db.exec(sql"drop table if exists clip_items")
except:
echo getCurrentExceptionMsg()
proc maintainDB() =
let db = openDBConn()
try:
db.exec(sql"""BEGIN""")
db.exec(sql"delete from clip_items order by timestamp desc offset ?", KEEP_ITEMS)
db.exec(sql"""COMMIT""")
except:
echo "Error cleaning DB : " & getCurrentExceptionMsg()
proc escapeClip(str: string): string =
var clip = str
clip = clip.replace("`","\\`")
clip = escape(clip)
return strip(clip)
proc unescapeClip(str: string): string =
var clip = str
try:
clip = unescape(clip)
except:
echo getCurrentExceptionMsg()
return strip(clip)
proc readClipFile(): seq[string] =
var clips: seq[string] = @[]
let db = openDBConn()
defer: db.close()
try:
for row in db.fastRows(sql"select distinct(clip) from clip_items order by timestamp desc"):
var str = unescapeClip(row[0])
clips.add(str)
except:
echo "Error Reading Clip File : " & getCurrentExceptionMsg()
return clips
proc addClip(str: var string) =
if str == "":
return
elif str[0] == '\x89':
var t = str[1..3]
echo "Is a ", $t, " file , not storing"
str = "[" & t & " Image] (not stored)"
let db = openDBConn()
defer: db.close()
try:
db.exec(sql"""BEGIN""")
db.exec(sql"""insert into clip_items (timestamp, clip)
values (CURRENT_TIMESTAMP, ?)
""", escapeClip(str))
db.exec(sql"""COMMIT""")
except:
echo getCurrentExceptionMsg()
return
proc showClips() =
let clips = readClipFile()
let info = newInfo("Clipurr")
let option = outputData(info, clips)
if option != "":
copyToClipboard(option)
return
proc main() =
for idx, arg in args:
if arg == "daemon":
killOldRunningProcesses()
runDaemon()
return
if arg == "set":
var content = getCurrentClipboardContent()
addClip(content)
return
if arg == "clear":
clearHistory()
return
showClips()
return
block start:
if isMainModule:
main()
maintainDB()

View file

@ -0,0 +1,13 @@
# Package
version = "0.1.0"
author = "Paul Wilde"
description = "A Command wrapper thing - i.e. run clipmenu/passmenu into dmenu with styling, sucks really"
license = "MIT"
srcDir = "src"
bin = @["command_wrapper"]
# Dependencies
requires "nim >= 1.6.6"

View file

@ -0,0 +1,22 @@
import ../../base
import std/[strutils,osproc]
# Basically just a wrapper to style passmenu nicely
proc main() =
var info = newInfo(capitalizeAscii(run_command))
let cmd = genMenuCmd(info)
discard execCmd(cmd)
return
if isMainModule:
base.command_wrapper = true
for idx, arg in args:
case arg:
of "-r", "--run":
run_command = args[idx + 1]
break
else:
echo "No command given, please run again with `[-r|--run] __cmd__`"
if run_command != "":
main()

11
compile_all.sh Executable file
View file

@ -0,0 +1,11 @@
#!/bin/bash
for dir in ./*; do
if [ -d $dir ]; then
cd $dir
f=$(echo $dir | sed 's/\.\///')
nimble install -y
sudo cp -v $f /usr/local/bin/$f
cd ../
fi
done

13
emoji/emoji.nimble Normal file
View file

@ -0,0 +1,13 @@
# Package
version = "0.1.0"
author = "Paul Wilde"
description = "An emoji picker for dmenu/rofi"
license = "MIT"
srcDir = "src"
bin = @["emoji"]
# Dependencies
requires "nim >= 1.6.6"

20
emoji/src/emoji.nim Normal file
View file

@ -0,0 +1,20 @@
import ../../base
import lib/emojilist
import std/[re]
proc main() =
var info = newInfo("Emoji Picker")
var args = getEmoji()
args.add("exit")
let output = outputData(info,args)
if output == "exit" or output == "":
return
else:
let e = re.findAll(output,re(".+ :"))
if len(e) > 0:
let emoji = re.replace(e[0], re(" :"),"")
copyToClipboard(emoji)
return
if isMainModule:
main()

3572
emoji/src/lib/emojilist.nim Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,13 @@
# Package
version = "0.1.0"
author = "Paul Wilde"
description = "Displays the fuzzy time in dmenu"
license = "MIT"
srcDir = "src"
bin = @["fuzzytime"]
# Dependencies
requires "nim >= 1.6.6"

View file

@ -0,0 +1,92 @@
import ../../base
import std/[times]
const default_bg = lightblue
const default_fg = black
proc getHour(hr: int): string
proc getMinute(min: int): string
proc getFuzzyTime(): string =
let tm = now()
var hr = tm.hour()
let min = tm.minute()
var link = "past"
if min > 32 :
link = "to"
case hr:
of 23:
hr = 0
else:
hr = hr + 1
if min >= 58 or min <= 02:
return getHour(hr) & " " & getMinute(min)
else:
return getMinute(min) & " " & link & " " & getHour(hr)
proc getHour(hr: int): string =
case hr:
of 1, 13:
return "one"
of 2, 14:
return "two"
of 3, 15:
return "three"
of 4, 16:
return "four"
of 5, 17:
return "five"
of 6, 18:
return "six"
of 7, 19:
return "seven"
of 8, 20:
return "eight"
of 9, 21:
return "nine"
of 10, 22:
return "ten"
of 11, 23:
return "eleven"
of 0, 12, 24:
return "twelve"
else:
return "error"
proc getMinute(min: int): string =
case min:
of 58,59,0,1,2:
return "o'clock"
of 3,4,5,6,7,53,54,55,56,57:
return "five"
of 8,9,10,11,12,48,49,50,51,52:
return "ten"
of 13,14,15,16,17,43,44,45,46,47:
return "quarter"
of 18,19,20,21,22,38,39,40,41,42:
return "twenty"
of 23,24,25,26,27,33,34,35,36,37:
return "twenty-five"
of 28,29,30,31,32:
return "half"
else:
return "error"
proc getObject(time: string): Info =
var data = newInfo("Fuzzy Time")
data.full_text = time
data.selected_bg = default_bg
data.selected_fg = default_fg
#i3bar stuff
data.color = default_fg
data.border = default_fg
return data
proc main() =
let time = getFuzzyTime()
let data = getObject(time)
outputData(data)
if isMainModule:
main()

View file

@ -0,0 +1,13 @@
# Package
version = "0.1.0"
author = "Paul Wilde"
description = "Displays open windows in i3 workspaces"
license = "MIT"
srcDir = "src"
bin = @["i3_workspaces"]
# Dependencies
requires "nim >= 1.6.6"

View file

@ -0,0 +1,200 @@
import ../../base
import std/[osproc,json,strutils]
const I3_WORKSPACES = "i3-msg -t get_workspaces"
const I3_TREE = "i3-msg -t get_tree"
const VISIBLE = "#"
const URGENT = "!"
const FOCUSED = "%"
type
Workspace = object
num: int
name: string
focused: bool
visible: bool
output: string
urgent: bool
display_string: string
apps: seq[string]
applications: seq[Application]
application: Application
Application = object
title: string
class: string
focused: bool
urgent: bool
var my_workspaces: seq[Workspace]
var current_workspace: int = 0
proc showWorkspaces()
proc buildString(ws: Workspace): string =
var str = $ws.num & " |"
# if ws.urgent or ws.application.urgent:
if ws.application.urgent:
str &= URGENT
else:
str &= " "
if ws.focused or ws.application.focused:
current_workspace = ws.num
str &= FOCUSED
# elif ws.visible:
# str &= VISIBLE
else:
str &= " "
str &= "| " & ws.output & " | "
if ws.application.class != "":
str &= ws.application.class & " " & ws.application.title & " | "
else:
str = ""
# for app in ws.applications:
# str &= app.class & " " & app.title & " | "
return str
proc findWorkspace(workspace: string): Workspace =
for ws in my_workspaces:
if workspace == ws.display_string:
return ws
proc switchWorkspace(workspace: string) =
if workspace.contains("%"):
return
let ws = findWorkspace(workspace)
if ws.num == current_workspace:
return
let cmd = "i3-msg workspace " & $ws.num
discard execCmd(cmd)
showWorkspaces()
# proc getApplications(node: JsonNode): (seq[string],bool) =
# var apps: seq[string] = @[]
# var focused = false
# for item in node["nodes"].getElems():
# var name = ""
# if item["focused"].getBool():
# focused = true
# if item{"window_properties"} != nil :
# let prop = item["window_properties"]
# name = prop["class"].getStr()
# name &= " " & prop["title"].getStr()
# echo "Adding application : " & name
# apps.add(name)
# return (apps, focused)
#
proc getApplication(node: JsonNode, ws: Workspace = Workspace()): Application =
var app = Application()
let window = node["window_properties"]
app.title = window["title"].getStr()
app.class = window["class"].getStr()
app.focused = node["focused"].getBool()
app.urgent = node["urgent"].getBool()
#echo ws.num
#echo app.title & " " & app.class
return app
# proc findWorkspaces(node: JsonNode) =
# for channel in node["nodes"].getElems():
# case channel["type"].getStr():
# of "workspace":
# if channel["output"].getStr() == "__i3":
# continue
# let w = channel
# let apps = getApplications(channel)
# var space = Workspace(
# num: w["num"].getInt(),
# name: w["name"].getStr(),
# focused: apps[1],
# #visible: w["visible"].getBool(),
# urgent: w["urgent"].getBool(),
# output: w["output"].getStr(),
# apps: apps[0]
# )
# space.display_string = buildString(space)
# my_workspaces.add(space)
# else:
# echo "finding workspaces..."
# findWorkspaces(channel)
# return
proc newWorkspace(node: JsonNode): Workspace =
return Workspace(
num: node["num"].getInt(),
name: node["name"].getStr(),
focused: node["focused"].getBool(),
#visible: w["visible"].getBool(),
urgent: node["urgent"].getBool(),
output: node["output"].getStr(),
)
proc findWorkspacesTree(node: JsonNode, parent: Workspace = Workspace()) =
for channel in node["nodes"].getElems():
### move this into for loop if want separate entry per window
var ws: Workspace = Workspace()
if parent.num > 0:
ws = parent
elif node{"type"}.getStr() == "workspace":
if node["output"].getStr() == "__i3":
return
ws = newWorkspace(node)
###
if channel{"window_properties"} != nil:
let app = getApplication(channel,ws)
if ws.name != "":
#if app.focused:
# ws.focused = true
ws.applications.add(app)
ws.application = app
elif ws.num > 0 and len(channel{"nodes"}) > 0:
findWorkspacesTree(channel,ws)
else:
findWorkspacesTree(channel)
### move this into for loop if want separate entry per window
if ws.name != "":
ws.display_string = ws.buildString()
if ws.display_string != "":
my_workspaces.add(ws)
###
return
proc getTree() =
let cur_workspaces = execCmdEx(I3_TREE)
if cur_workspaces.output != "":
let root = parseJson(cur_workspaces.output)
findWorkspacesTree(root)
return
proc getWorkspaces(): seq[Workspace] =
let cur_workspaces = execCmdEx(I3_WORKSPACES)
if cur_workspaces.output != "":
let ws = parseJson(cur_workspaces.output)
for w in ws:
var space = Workspace(
num: w["num"].getInt(),
name: w["name"].getStr(),
focused: w["focused"].getBool(),
visible: w["visible"].getBool(),
urgent: w["urgent"].getBool(),
output: w["output"].getStr()
)
space.display_string = buildString(space)
my_workspaces.add(space)
return my_workspaces
proc showWorkspaces() =
my_workspaces = @[]
getTree()
var info = newInfo("Workspaces")
var args: seq[string] = @[]
for ws in my_workspaces:
args.add(ws.display_string)
let output = outputData(info,args)
if output in args:
switchWorkspace(output)
proc main() =
showWorkspaces()
if isMainModule:
main()

13
nic/nic.nimble Normal file
View file

@ -0,0 +1,13 @@
# Package
version = "0.1.0"
author = "Paul Wilde"
description = "Display NIC IP address and link to manage NICs"
license = "GPL-3.0-or-later"
srcDir = "src"
bin = @["nic"]
# Dependencies
requires "nim >= 1.6.6"

66
nic/src/nic.nim Normal file
View file

@ -0,0 +1,66 @@
import ../../base
import std/[os,osproc,strutils,sequtils]
const default_fg = black
const default_bg = purple
const mng_cmd = "alacritty -e nmtui-connect"
const nics: seq[string] = @["wlan0","bridge0", "enp3s0","wlp2s0","enp0s20f0u3"]
proc getIP(nic: string): string =
let cmd = "ifconfig " & nic & " | grep inet | awk -F\" \" '{print $2}' | head -1 | awk '{print $1}'"
let ip = execCmdEx(cmd)
return strip(ip.output)
proc getOnlineState(nic: string): string =
try:
let oper = readFile("/sys/class/net/" & nic & "/operstate")
let state = strip(oper)
return "[" & state & "]"
except:
echo "Error getting operstate for " & nic & " : ", getCurrentExceptionMsg()
return ""
proc getConnState(nic: string): (string, string) =
let state = getOnlineState(nic)
let ip = getIP(nic)
if state == "[down]" or ip == "":
return ("disconnected", state)
return (ip, state)
proc getObject(): Info =
var data = newInfo("IP")
data.selected_bg = default_bg
data.selected_fg = default_fg
# i3bar stuff
data.border = default_bg
return data
proc getNetInfo*(my_nics: seq[string]) =
var my_nic_states: seq[string] = @[]
for nic in my_nics:
let (ip, state) = getConnState(nic)
my_nic_states.add(nic & ":" & state & " " & ip)
let data = getObject()
let args = concat(my_nic_states,@["manage"])
let option = outputData(data, args)
if option in my_nic_states:
discard execCmd(mng_cmd)
case option:
of "manage":
discard execCmd(mng_cmd)
proc getNics*(): seq[string] =
var my_nics: seq[string] = @[]
for nic in os.walkDir("/sys/class/net/"):
let n = replace(nic.path, "/sys/class/net/", "")
my_nics.add(n)
if len(my_nics) > 0:
return my_nics
return @["no-nic"]
proc main() =
getNetInfo(getNics())
if isMainModule:
main()

13
notes/notes.nimble Normal file
View file

@ -0,0 +1,13 @@
# Package
version = "0.1.0"
author = "Paul Wilde"
description = "A notes app for dmenu"
license = "GPL-3.0-or-later"
srcDir = "src"
bin = @["notes"]
# Dependencies
requires "nim >= 1.6.6"

167
notes/src/notes.nim Normal file
View file

@ -0,0 +1,167 @@
import ../../base
import std/[os,strutils,sequtils,times]
const note_dir = WM_TOOLS_DIR & ".notes.dmenu/" # Putting it in Nextcloud so it can sync :-)
const default_bg = white
const default_fg = black
type
Note = object
id: string
text: string
proc startNotes()
proc displayOptionMenu(option: string)
var notes: seq[Note] = @[]
proc readNotes(): seq[Note] =
if len(notes) == 0:
if existsOrCreateDir(note_dir):
for note_file in walkDir(note_dir):
try:
var note = Note()
note.id = note_file[1]
note.text = stripQuotes(readFile(note_file[1]))
notes.add(note)
except:
echo "Unable to read note : ", note_file, " : ", getCurrentExceptionMsg()
return notes
proc removeNote(note: Note) =
try:
removeFile(note.id)
for idx, a_note in notes:
if note.id == a_note.id:
notes.delete(idx)
except:
echo "Unable to delete file : ", note.id, " : ", getCurrentExceptionMsg()
proc getNoteStrings(): seq[string] =
var note_list: seq[string] = @[]
if len(notes) == 0:
discard readNotes()
for note in notes:
note_list.add(note.text)
return note_list
proc writeNote(note: Note, is_new: bool = false) =
if note.text == "":
return
try:
writeFile(note.id, stripQuotes(note.text))
if is_new:
var new_note = note
new_note.text = stripQuotes(new_note.text)
notes.add(note)
else:
for idx, a_note in notes:
if a_note.id == note.id:
notes[idx].text = stripQuotes(note.text)
except:
echo "write_note, Unable to write note : ", note.id, " : ", getCurrentExceptionMsg()
proc getNotes(): (Info, seq[string]) =
var info = newInfo("Notes")
info.selected_bg = default_bg
info.selected_fg = default_fg
let notes = getNoteStrings()
return (info,notes)
proc getNote(text: string): Note =
for note in notes:
if note.text == text:
return note
return Note()
proc displayDeleteConfirmationMenu(note: string) =
var yes_no = newInfo("Confirm Delete note : " & note)
yes_no.selected_bg = default_bg
yes_no.selected_fg = default_fg
let args = @["yes", "no"]
let choice = outputData(yes_no, args)
case choice:
of "yes":
let rm_note = getNote(note)
if rm_note.text == note:
removeNote(rm_note)
of "no":
displayOptionMenu(note)
proc replaceNoteConfirmationMenu(note_idx: int, new_text: string): bool =
let old_note = notes[note_idx]
var diag = newInfo("Replace Note : '" & old_note.text & "', with '" & new_text & "' ?")
diag.selected_bg = default_bg
diag.selected_fg = default_fg
let args = @["yes", "no"]
let choice = outputData(diag,args)
case choice:
of "yes":
echo "here"
return true
else:
return false
proc replaceNote(new_text: string, old_text: string) =
for idx, note in notes:
if note.text == old_text:
if replaceNoteConfirmationMenu(idx,new_text):
var new_note = note
new_note.text = new_text
notes[idx] = new_note
writeNote(new_note)
return
proc displayOptionMenu(option: string) =
var select = newInfo("Note")
select.selected_bg = default_bg
select.selected_fg = default_fg
let note_choices = @["rm","back"]
let args = concat(@[option],note_choices)
let chosen = outputData(select,args)
if chosen in note_choices:
case chosen:
of "rm":
displayDeleteConfirmationMenu(option)
startNotes()
of "back":
startNotes()
elif chosen != "" and chosen != option:
replaceNote(chosen, option)
displayOptionMenu(chosen)
else:
displayOptionMenu(option)
proc newNote(text: string) =
var new_note = Note()
let note_id = times.now().format("'{'yyyyMMdd-HHmmss-ffffff'}'")
new_note.id = note_dir & note_id
new_note.text = text
writeNote(new_note, is_new = true)
proc startNotes() =
let (info,all_notes) = getNotes()
let all_choices = @["exit"]
let args = concat(all_notes, all_choices)
let option = outputData(info, args)
if option in all_choices:
case option:
of "exit":
return
if option in all_notes:
displayOptionMenu(option)
elif option != "":
newNote(option)
startNotes()
proc main() =
echo "Note dir : ", note_dir
if tool != "dmenu" and tool != "rofi":
echo "Can only be run in dmenu or rofi mode. Exiting..."
return
startNotes()
if isMainModule:
main()

View file

@ -0,0 +1,13 @@
# Package
version = "0.1.0"
author = "Paul Wilde"
description = "A ping clock - display current ping time"
license = "GPL-3.0-or-later"
srcDir = "src"
bin = @["pingclock"]
# Dependencies
requires "nim >= 1.6.6"

View file

@ -0,0 +1,85 @@
import ../../base
import std/[osproc, re, strutils]
const host: string = "9.9.9.9"
const default_bg = blue
const default_fg = white
const medium_bg = alert
const medium_fg = black
const alert_bg = alert
const alert_fg = black
const warning_bg = red
const warning_fg = white
let ping_re = re(r"time=[0-9.]+")
const ping_cmd: string = "ping -4 -c 1 " & host
proc getPing(): float =
var ping: float = -1
let cmdOut = execCmdEx(ping_cmd)
let lines = splitLines(cmdOut.output)
let ping_line = lines[1]
let bounds = findBounds(ping_line, ping_re)
if bounds.first > 0:
let png = ping_line[bounds.first+5..bounds.last]
ping = parseFloat(png)
return ping
proc getObject(ping: float): Info =
let pingstr = split($ping,".")
let niceping = pingstr[0] & "." & pingstr[1][0]
var text = "🏓 " & niceping & " ms"
var state = 0
if ping < 0:
text = "❌ No Pong"
state = 1
else:
case ping:
of 0..100:
state = 0
of 101..400:
state = 1
of 401..1000:
state = 2
else:
state = 9
var data = newInfo("Ping Clock")
data.full_text = text
# i3bar stuff
data.color = default_fg
data.border = default_bg
data.background = black
data.selected_bg = default_bg
data.selected_fg = default_fg
case state:
of 1:
data.selected_bg = medium_bg
data.selected_fg = medium_fg
# i3bar stuff
data.color = medium_bg
of 2:
data.selected_bg = alert_bg
data.selected_fg = alert_fg
# i3bar stuff
data.color = alert_bg
of 9:
data.selected_bg = warning_bg
data.selected_fg = warning_fg
# i3bar stuff
data.color = warning_bg
else:
#default options already set
let ok = true
return data
proc main() =
let ping = get_ping()
let data = getObject(ping)
let output = outputData(data)
if output == data.full_text:
main()
if isMainModule:
main()

14
remmina/remmina.nimble Normal file
View file

@ -0,0 +1,14 @@
# Package
version = "0.1.0"
author = "Paul Wilde"
description = "A Remmina client tool and opener"
license = "GPL-3.0-or-later"
srcDir = "src"
bin = @["remmina"]
# Dependencies
requires "nim >= 1.6.6"
requires "configparser >= 0.1.0"

74
remmina/src/remmina.nim Normal file
View file

@ -0,0 +1,74 @@
import ../../base
import std/[os,osproc,tables,algorithm]
import configparser
const REMMINA_DIR = getHomeDir() & ".local/share/remmina"
const REMMINA_WS = "4"
var sessions = initTable[string,string]()
var names: seq[string] = @[]
proc main()
proc switchWorkspace() =
if REMMINA_WS != "":
discard execCmd("i3-msg workspace number " & REMMINA_WS)
discard execCmd("swaymsg workspace number " & REMMINA_WS)
proc getRemminaFiles(): seq[string] =
if len(names) < 1:
for file in walkFiles(REMMINA_DIR & "/*.remmina"):
let content = readFile(file)
let ini = parseIni(content)
let group = ini.getProperty("remmina","group")
let name = ini.getProperty("remmina","name")
let server = ini.getProperty("remmina","server")
if name != "" and server != "":
let slug = group & " : " & name & " : (" & server & ")"
sessions[slug] = file
names.add(slug)
names.sort()
return names
proc editRemmina(conn: string) =
let session = sessions[conn]
discard execCmd("remmina -e " & quote(session))
proc startRemmina(conn: string) =
let session = sessions[conn]
discard execCmd("remmina -c " & quote(session))
proc selectRemmina(conn: string) =
var info = newInfo("Remmina : " & conn)
let args = @["connect", "edit", "back"]
let output = outputData(info,args)
if output in args:
case output:
of "connect":
startRemmina(conn)
switchWorkspace()
of "edit":
editRemmina(conn)
switchWorkspace()
of "back":
main()
proc main() =
var info = newInfo("Remmina")
var args: seq[string] = getRemminaFiles()
args.add("new")
args.add("exit")
let output = outputData(info,args)
if output == "exit" or output == "":
return
elif output == "new":
discard execCmd("remmina --new")
elif output in names:
selectRemmina(output)
return
return
if isMainModule:
main()

View file

@ -0,0 +1,13 @@
# Package
version = "0.1.0"
author = "Paul Wilde"
description = "A screenshot client, works on both X11 and Wayland"
license = "GPL-3.0-or-later"
srcDir = "src"
bin = @["screenshot"]
# Dependencies
requires "nim >= 1.6.6"

View file

@ -0,0 +1,102 @@
import ../../base
import std/[times,os,osproc,strutils,sequtils]
var screenshot_type = ""
const TYPES = @["region", "fullscreen", "window"]
const DATE_FORMAT = "yyyyMMdd-hhmmss"
const FILENAME = "Screenshot-%d.png"
const TEMP_DIR = "/tmp/"
const SCREENSHOT_CMD = "maim -u %s --format png %f"
const SCREENSHOT_CMD_WL = "grim %s %f"
var RUN_CMD = SCREENSHOT_CMD
let DATE_STR = now().format(DATE_FORMAT)
# where %s is an extra flag or process, i.e. xdotool for getting active window
const ACTIVE_WINDOW_CMD = "-i $(xdotool getactivewindow)"
const REGION_FLAG = "-s"
const REGION_FLAG_WL = "-g \"$(slurp)\""
var REGION_CMD = REGION_FLAG
const CLIPBOARD_CMD = "xclip -selection clipboard -t image/png"
const CLIPBOARD_CMD_WL = "wl-copy"
var CLIP_CMD = CLIPBOARD_CMD
proc saveToClipboard(filename: string) =
let cmd = "cat " & filename & " | " & CLIP_CMD
let status = execCmd(cmd)
if status == 0 and fileExists(filename):
removeFile(filename)
return
proc saveToFile(filename: string) =
if fileExists(filename):
let new_filename = filename.replace("/tmp/", getHomeDir() & "Screenshots/")
copyFile(filename, new_filename)
if fileExists(new_filename):
removeFile(filename)
return
proc openFile(filename: string) =
let cmd = "xdg-open " & filename
discard execCmd(cmd)
return
proc showScreenshotSaveSel(filename: string) =
let info = newInfo("Save Screenshot")
let args = @["clipboard", "save", "open", "---", "exit"]
let choice = outputData(info,args)
if choice == "---":
showScreenshotSaveSel(filename)
elif choice == "exit":
return
elif choice in args:
case choice:
of "clipboard":
saveToClipboard(filename)
of "save":
saveToFile(filename)
of "open":
openFile(filename)
return
proc showScreenshotTypeSel() =
let info = newInfo("Screenshot type")
let args = concat(TYPES,@["---","exit"])
let choice = outputData(info,args)
if choice in TYPES:
screenshot_type = choice
elif choice == "---":
showScreenshotTypeSel()
elif choice == "exit":
return
return
proc takeScreenshot() =
let filename = TEMP_DIR & FILENAME.replace("%d",DATE_STR)
var cmd = RUN_CMD.replace("%f",filename)
case screenshot_type:
of "window":
cmd = cmd.replace("%s",ACTIVE_WINDOW_CMD)
of "region":
cmd = cmd.replace("%s",REGION_CMD)
else: #fullscreen
cmd = cmd.replace("%s","")
# sleep for a bit otherwise the screen shot grabs dmenu as well
sleep(1*500)
let status = execCmd(cmd)
if status == 0:
showScreenshotSaveSel(filename)
return
if isMainModule:
if wayland:
RUN_CMD = SCREENSHOT_CMD_WL
REGION_CMD = REGION_FLAG_WL
CLIP_CMD = CLIPBOARD_CMD_WL
for arg in args:
if arg in TYPES:
screenshot_type = arg
break
if screenshot_type == "":
showScreenshotTypeSel()
if screenshot_type != "":
takeScreenshot()

View file

@ -0,0 +1,13 @@
# Package
version = "0.1.0"
author = "Paul Wilde"
description = "Displays the date in dmenu"
license = "MIT"
srcDir = "src"
bin = @["date"]
# Dependencies
requires "nim >= 1.6.6"

View file

@ -0,0 +1,52 @@
import ../../base
import std/[times,osproc,re]
const default_bg = blue
const default_fg = white
const default_format = "yyyy-MM-dd"
const cal_pos_x = "20"
const cal_pos_y = "20"
proc getObject(date: string): Info =
var data = newInfo("Date")
data.full_text = date
data.border = default_bg
data.selected_bg = default_bg
data.selected_fg = default_fg
return data
proc newCalendar(): string =
var c = """yad --calendar \
--undecorated --fixed --close-on-unfocus --no-buttons \
--width="222" --height="193" \
--posx="%pos_x" --posy="%pos_y" \
--title="yad-calendar" --borders 0 > /dev/null
"""
return c
proc openCalendar*(input: i3barInput) =
var c = newCalendar()
c = replace(c,re"%pos_x", $(input.x - 111))
c = replace(c,re"%pos_y", $input.y)
discard execCmd(c)
proc dmenuCalendar() =
var c = newCalendar()
c = replace(c,re"%pos_x", cal_pos_x)
c = replace(c,re"%pos_y", cal_pos_y)
discard execCmd(c)
proc getDate*() =
let date_today = times.now().format(default_format)
let data = getObject(date_today)
let output = outputData(data)
if output == date_today:
dmenuCalendar()
proc main() =
getDate()
if isMainModule:
main()

View file

@ -0,0 +1,72 @@
import ../../base
import std/[os,re,math,strutils]
const default_bg = green
const default_fg = black
const alert_bg = yellow
const alert_fg = black
const warning_bg = red
const warning_fg = black
proc getThermalZones(): seq[string] =
var zones: seq[string] = @[]
let dirname = re("thermal_zone[\\d]+")
for file in walkDir("/sys/class/thermal/"):
if contains(file.path,dirname):
let state = readFile(file.path & "/mode")
if contains(state,re"enabled"):
zones.add(file.path)
return zones
proc getTemp(zone: string): int =
let temp = strip(readFile(zone & "/temp"))
return parseInt(temp)
proc getAverageTemp(zones: seq[string]): int =
var temps: int = 0
for zone in zones:
let temp = getTemp(zone)
temps += temp
let avgtemp = ceil((temps / len(zones))/1000)
return toInt(round(avgtemp,0))
proc getObject(temp: int): Info =
var icon = ""
var bg_col = default_bg
var fg_col = default_fg
case temp:
of 40..59:
bg_col = alert_bg
fg_col = alert_fg
icon = ""
of 60.. 200:
bg_col = warning_bg
fg_col = warning_fg
icon = ""
else:
bg_col = default_bg
fg_col = default_fg
icon = ""
let text = "<span foreground='" & bg_col & "'>" & icon & "</span> " & $temp & "°C"
let main_text = icon & " " & $temp & "°C"
var data = newInfo("Temperature")
data.full_text = main_text
data.selected_bg = bg_col
data.selected_fg = fg_col
# i3bar stuff
data.html_text = text
data.color = bg_col
data.border = bg_col
return data
proc main() =
let zones = getThermalZones()
let temp = getAverageTemp(zones)
let data = getObject(temp)
let option = outputData(data)
if option == data.full_text:
# Refresh
main()
if isMainModule:
main()

View file

@ -0,0 +1,13 @@
# Package
version = "0.1.0"
author = "Paul Wilde"
description = "Display temp in dmenu"
license = "GPL-3.0-or-later"
srcDir = "src"
bin = @["temperature"]
# Dependencies
requires "nim >= 1.6.6"

1
tides/nim.cfg Normal file
View file

@ -0,0 +1 @@
-d:ssl

106
tides/src/tides.nim Normal file
View file

@ -0,0 +1,106 @@
#curl https://www.tidetimes.org.uk/exmouth-dock-tide-times-20190101 | grep -E -o ">((High|Low)|([0-9]+:[0-9]+)|([0-9]+\.[0-9]+m))"
import ../../base
import std/[re,httpclient,times,osproc,sequtils]
# TODO:
# Pass location in as variable
const url* = "https://www.tidetimes.org.uk/%LOC-tide-times"
const loc* = "exmouth-dock"
const icon: string = "🌊 "
type
Tide = ref object
State: string
Time: string
Height: string
Tomorrow: bool
TideList = ref object
Tides: seq[Tide]
LastUpdated: DateTime
proc sortTides(tides: seq[Tide], is_tomorrow: bool = false): seq[Tide] =
let timenow = now()
var reltides: seq[Tide]
for tide in tides:
if timenow.format("HH:MM") <= tide.Time and not is_tomorrow:
reltides.add(tide)
elif is_tomorrow:
reltides.add(tide)
return reltides
proc getTideData(get_tomorrow: bool = false): seq[Tide] =
var tides: seq[Tide]
let fnd = re">((High|Low)|([0-9]+:[0-9]+)|([0-9]+\.[0-9]+m))"
var client = newHttpClient()
var link = replace(url,re"\%LOC",loc)
if get_tomorrow:
let tomdate = now() + initTimeInterval(days = 1)
link &= "-" & tomdate.format("yyyyMMdd")
try:
# Remember to compile with -d:ssl else this won't work
let data = client.getContent(link)
let times = findAll(data,fnd)
var tide: Tide
var column = 0
for idx,time in times:
if idx == 12:
break
let l = len(time) - 1
if time == ">High" or time == ">Low":
tide = Tide()
tide.State = time[1..l]
column = 1
continue
elif column == 1:
tide.Time = time[1..l]
column = 2
continue
elif column == 2:
tide.Height = time[1..l]
tides.add(tide)
column = 0
continue
except:
echo "Unable to get Tide Data : " & getCurrentExceptionMsg()
return tides
proc getDesign(tides: seq[Tide]): Info =
var my_tides: seq[string] = @[]
for tide in tides:
let str = icon & tide.State[0] & " " & tide.Time & " " & tide.Height
my_tides.add(str)
var data = newInfo("Tides")
data.border = black
data.args = my_tides
return data
proc getTides*(get_tomorrow: bool = false) =
var mytides = TideList()
mytides.Tides = getTideData(get_tomorrow)
mytides.Tides = sortTides(mytides.Tides, get_tomorrow)
if len(mytides.Tides) == 0:
getTides(true)
return
let data = getDesign(mytides.Tides)
var opt_tomorrow = "tomorrow"
if get_tomorrow:
opt_tomorrow = "back"
let args = concat(data.args,@["---",opt_tomorrow])
let output = outputData(data,args)
if output == "tomorrow":
getTides(true)
elif output == "back":
getTides()
elif output == "---":
return
elif output in args:
let url = replace(url,re"\%LOC",loc)
discard execCmd("xdg-open " & url)
proc main() =
getTides()
if isMainModule:
main()

13
tides/tides.nimble Normal file
View file

@ -0,0 +1,13 @@
# Package
version = "0.1.0"
author = "Paul Wilde"
description = "Display local tides in dmenu"
license = "GPL-3.0-or-later"
srcDir = "src"
bin = @["tides"]
# Dependencies
requires "nim >= 1.6.6"

1
translate/nim.cfg Normal file
View file

@ -0,0 +1 @@
-d:ssl

126
translate/src/translate.nim Normal file
View file

@ -0,0 +1,126 @@
import ../../base
import std/[re,httpclient,json,strutils,tables]
# TODO:
# Query available languages, maybe have selection panel
# Maybe store last used so future translations are more fluent
const LIBRETRANSLATE_URL = "https://libretranslate.pussthecat.org/"
const HOME = "en"
let LANG_RE = re"\w+>\w+"
let DETECT_RE = re"\bd(etect)?\b"
var current = @["de",HOME]
var detected = {"detected":"0","confidence":"0","language":"en"}.toTable
type
Request = object
source: string
target: string
q: string
format: string
proc main(messages: varargs[string] = @[])
proc newRequest(): Request =
return Request(source:current[0],
target:current[1],
q: "",
format: "text",)
proc parseReq(req: string): Request =
var langs = findAll(req,LANG_RE)
var r = newRequest()
let query = replace(req,LANG_RE,"")
r.q = query
if len(langs) > 0:
let lang = langs[0]
langs = lang.split(re">")
current[0] = langs[0].toLowerAscii()
current[1] = langs[1].toLowerAscii()
r.source = current[0].toLowerAscii()
r.target = current[1].toLowerAscii()
if query == "":
main()
return r
proc answerTranslate(response: string, req: Request) =
let r = parseJson(response)
try:
let ans = r["translatedText"].getStr()
main(ans)
except:
main("Error : " & r["error"].getStr())
proc goTranslate(req: string) =
let r = parseReq(req)
if r.q != "":
var client = newHTTPClient()
client.headers = newHttpHeaders({"Content-Type":"application/json"})
let body = %*r
let response = client.request(LIBRETRANSLATE_URL & "translate", httpMethod = HttpPost, body = $body)
if response.status != "":
answerTranslate(response.body, r)
proc flipCurrent() =
current = @[current[1],current[0]]
main()
proc detectLanguage(str: string) =
let term = replace(str,DETECT_RE,"")
echo "detecting ", term
if term != "":
var client = newHTTPClient()
client.headers = newHttpHeaders({"Content-Type":"application/json"})
let body = %*Request(q:term)
let response = client.request(LIBRETRANSLATE_URL & "detect", httpMethod = HttpPost, body = $body)
if response.status != "":
let r = parseJson(response.body)
echo r
try:
detected["confidence"] = $r[0]["confidence"].getFloat()
detected["language"] = r[0]["language"].getStr()
detected["detected"] = "1"
current[0] = detected["language"]
current[1] = HOME
goTranslate(term)
except:
try:
main("Error : " & r["error"].getStr())
except:
main("Error Parsing Json")
return
proc main(messages:varargs[string] = @[]) =
var info = newInfo("Translate")
info.selected_bg = green
var args: seq[string] = @[]
for msg in messages:
if msg != "":
args.add(msg)
args.add(current[0] & " > " & current[1])
if detected["detected"] == "1":
args.add("detected language : " & detected["language"])
args.add("detected confidence : " & detected["confidence"])
detected["detected"] = "0"
if len(messages) > 0:
args.add("back")
args.add("exit")
let output = outputData(info,args)
if output == "exit" or output == "":
return
elif output == "back":
main()
elif output == current[0] & " > " & current[1]:
flipCurrent()
elif re.startsWith(output,DETECT_RE):
detectLanguage(output)
elif output in messages:
copyToClipboard(output)
else:
goTranslate(output)
return
if isMainModule:
main()

View file

@ -0,0 +1,13 @@
# Package
version = "0.1.0"
author = "Paul Wilde"
description = "Query libretranslate with dmenu"
license = "GPL-3.0-or-later"
srcDir = "src"
bin = @["translate"]
# Dependencies
requires "nim >= 1.6.6"

126
volume/src/volume.nim Normal file
View file

@ -0,0 +1,126 @@
import ../../base
import std/[os,strutils,sequtils,osproc]
const audio_tools = @["ncpamixer", "pavucontrol"]
const vol_cmd = "pamixer"
const vol_up = vol_cmd & " -i %v" # where %v is amount by
const vol_down = vol_cmd & " -d %v" # where %v is amount by
const vol_set = vol_cmd & " --set-volume %v" # where %v is amount by
const vol_mute = vol_cmd & " -t"
const vol_default_by = "5"
const vol_get = vol_cmd & " --get-volume"
const vol_get_mute = vol_cmd & " --get-mute"
const default_bg = green
const default_fg = black
proc getCurrentVolume(): string {.gcsafe.}
proc checkVolume(volume: string): string =
var vol = volume
if strip(vol) == "Connection error":
sleep(1000)
vol = getCurrentVolume()
vol = checkVolume(vol)
return vol
proc getDesign(volume: string): (string,string) =
let vol = checkVolume(volume)
var icon = ""
if vol == "muted":
return (icon & "muted", icon & "muted")
let pcnt = parseInt(strip(vol))
case pcnt:
of 85..100:
icon = ""
of 55..84:
icon = ""
of 35..54:
icon = ""
of 10..34:
icon = ""
else:
icon = ""
let main_text = icon & $pcnt & "%"
let text = "<span size=\"x-large\">" & icon & "</span>" & $pcnt & "%"
return (text, main_text)
proc getCurrentVolume(): string =
let mute = execCmdEx(vol_get_mute)
if strip(mute.output) == "true":
return "muted"
let vol = execCmdEx(vol_get)
return vol.output
proc volumeUp() =
let cmd = replace(vol_up, "%v", vol_default_by)
discard execCmd(cmd)
proc volumeDown() =
let cmd = replace(vol_down, "%v", vol_default_by)
discard execCmd(cmd)
proc volumeMute() =
discard execCmd(vol_mute)
proc getVolume*(run_once: bool = false) =
let vol = getCurrentVolume()
let (text, main_text) = getDesign(vol)
var data = newInfo("Volume")
data.full_text = main_text
data.selected_bg = default_bg
data.selected_fg = default_fg
# i3bar stuff
data.html_text = text
data.color = foreground
data.border = green
data.background = black
let args = concat(@["up", "down", "mute", "---", "exit", "---"],audio_tools)
let option = outputData(data,args)
if option == "":
return
elif option in args:
if option in audio_tools:
discard(execCmd(option))
return
case option:
of "up":
volumeUp()
getVolume()
of "down":
volumeDown()
getVolume()
of "mute":
volumeMute()
getVolume()
of "---":
getVolume()
of "exit":
return
else:
try:
let vol = parseInt(option)
let cmd = replace(vol_set, "%v", $vol)
let x = execCmd(cmd)
getVolume()
except:
echo getCurrentExceptionMsg()
getVolume()
proc main() =
getVolume()
if isMainModule:
block start:
for arg in args:
case arg:
of "up":
volumeUp()
break start
of "down":
volumeDown()
break start
of "mute":
volumeMute()
break start
main()

13
volume/volume.nimble Normal file
View file

@ -0,0 +1,13 @@
# Package
version = "0.1.0"
author = "Paul Wilde"
description = "Display and control volume with dmenu"
license = "GPL-3.0-or-later"
srcDir = "src"
bin = @["volume"]
# Dependencies
requires "nim >= 1.6.6"

64
wlan/src/wlan.nim Normal file
View file

@ -0,0 +1,64 @@
import ../../base
import std/[os,osproc,strutils,sequtils]
const default_bg = purple
const default_fg = white
const wlan_nics: seq[string] = @["wlan0"]
const get_ssid_cmd = "iwgetid -r"
const mng_cmd = "alacritty -e nmtui-connect"
proc getSsid(): string =
let ssid = execCmdEx(get_ssid_cmd)
return strip(ssid.output)
proc getSignalQuality(): string =
let wl = readFile("/proc/net/wireless")
let ln = splitLines(wl)[2]
let links = split(ln," ")
var qual = strip(links[1])
qual = replace(qual,".","")
return "[" & qual & "]"
proc getWifi(nic: string): (string, string) =
let ssid = getSsid()
if ssid == "":
return ("disconnected", "disconnected")
let quality = getSignalQuality()
return (ssid, quality)
proc getObject(): Info =
var data = newInfo("WiFi")
data.border = purple
data.selected_bg = default_bg
data.selected_fg = default_fg
return data
proc getWifiInfo*(nics: seq[string]) =
var lst: seq[string] = @[]
for nic in nics:
let (essid, quality) = getWifi(nic)
lst.add(nic & ":" & quality & " " & essid)
let data = getObject()
let args = concat(lst,@["---", "manage","exit"])
let output = outputData(data, args)
case output:
of "manage":
discard execCmd(mng_cmd)
of "exit":
return
of "---":
return
proc main() =
var my_nics: seq[string] = @[]
for nic in wlan_nics:
if dirExists("/sys/class/net/" & nic):
my_nics.add(nic)
if len(my_nics) > 0:
getWifiInfo(my_nics)
else:
switchTwmMode()
echo "No WLAN"
if isMainModule:
main()

13
wlan/wlan.nimble Normal file
View file

@ -0,0 +1,13 @@
# Package
version = "0.1.0"
author = "Paul Wilde"
description = "Display and control WLAN state with dmenu"
license = "GPL-3.0-or-later"
srcDir = "src"
bin = @["wlan"]
# Dependencies
requires "nim >= 1.6.6"