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