destroyed repo, recreated with new nimble packaging
This commit is contained in:
commit
f69b202da8
46 changed files with 5995 additions and 0 deletions
8
.gitignore
vendored
Executable file
8
.gitignore
vendored
Executable file
|
@ -0,0 +1,8 @@
|
|||
# ignore all
|
||||
*
|
||||
|
||||
# unignore all with extensions
|
||||
!*.*
|
||||
|
||||
# unignore directories
|
||||
!*/
|
70
README.MD
Executable file
70
README.MD
Executable 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
293
base.nim
Normal 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
13
battery/battery.nimble
Normal 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
126
battery/src/battery.nim
Normal 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()
|
||||
|
13
brightness/brightness.nimble
Normal file
13
brightness/brightness.nimble
Normal 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"
|
116
brightness/src/brightness.nim
Normal file
116
brightness/src/brightness.nim
Normal 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()
|
5
brightness/src/src/thingy.nim
Normal file
5
brightness/src/src/thingy.nim
Normal 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!")
|
13
calculate/calculate.nimble
Normal file
13
calculate/calculate.nimble
Normal 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"
|
35
calculate/src/calculate.nim
Normal file
35
calculate/src/calculate.nim
Normal 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
13
clipurr/clipurr.nimble
Normal 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
135
clipurr/src/clipurr.nim
Normal 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()
|
13
command_wrapper/command_wrapper.nimble
Normal file
13
command_wrapper/command_wrapper.nimble
Normal 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"
|
22
command_wrapper/src/command_wrapper.nim
Normal file
22
command_wrapper/src/command_wrapper.nim
Normal 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
11
compile_all.sh
Executable 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
13
emoji/emoji.nimble
Normal 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
20
emoji/src/emoji.nim
Normal 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
3572
emoji/src/lib/emojilist.nim
Normal file
File diff suppressed because it is too large
Load diff
13
fuzzytime/fuzzytime.nimble
Normal file
13
fuzzytime/fuzzytime.nimble
Normal 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"
|
92
fuzzytime/src/fuzzytime.nim
Normal file
92
fuzzytime/src/fuzzytime.nim
Normal 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()
|
13
i3_workspaces/i3_workspaces.nimble
Normal file
13
i3_workspaces/i3_workspaces.nimble
Normal 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"
|
200
i3_workspaces/src/i3_workspaces.nim
Normal file
200
i3_workspaces/src/i3_workspaces.nim
Normal 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
13
nic/nic.nimble
Normal 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
66
nic/src/nic.nim
Normal 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
13
notes/notes.nimble
Normal 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
167
notes/src/notes.nim
Normal 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()
|
13
pingclock/pingclock.nimble
Normal file
13
pingclock/pingclock.nimble
Normal 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"
|
85
pingclock/src/pingclock.nim
Normal file
85
pingclock/src/pingclock.nim
Normal 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
14
remmina/remmina.nimble
Normal 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
74
remmina/src/remmina.nim
Normal 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()
|
13
screenshot/screenshot.nimble
Normal file
13
screenshot/screenshot.nimble
Normal 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"
|
102
screenshot/src/screenshot.nim
Normal file
102
screenshot/src/screenshot.nim
Normal 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()
|
13
show_date/show_date.nimble
Normal file
13
show_date/show_date.nimble
Normal 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"
|
52
show_date/src/show_date.nim
Normal file
52
show_date/src/show_date.nim
Normal 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()
|
||||
|
72
temperature/src/temperature.nim
Normal file
72
temperature/src/temperature.nim
Normal 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()
|
13
temperature/temperature.nimble
Normal file
13
temperature/temperature.nimble
Normal 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
1
tides/nim.cfg
Normal file
|
@ -0,0 +1 @@
|
|||
-d:ssl
|
106
tides/src/tides.nim
Normal file
106
tides/src/tides.nim
Normal 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
13
tides/tides.nimble
Normal 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
1
translate/nim.cfg
Normal file
|
@ -0,0 +1 @@
|
|||
-d:ssl
|
126
translate/src/translate.nim
Normal file
126
translate/src/translate.nim
Normal 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()
|
||||
|
13
translate/translate.nimble
Normal file
13
translate/translate.nimble
Normal 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
126
volume/src/volume.nim
Normal 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
13
volume/volume.nimble
Normal 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
64
wlan/src/wlan.nim
Normal 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
13
wlan/wlan.nimble
Normal 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"
|
Loading…
Reference in a new issue