#!/usr/bin/env ruby require 'open3' PASS_ROOT = "#{ENV['HOME']}/.password-store/".freeze ROFI_ARGS = ['-matching', 'fuzzy', '-sort', '-sorting-method', 'fzf', '-kb-accept-entry', '!Return'].freeze DZEN2_ARGS = ['-p', '5', '-h', '16', '-fg', '#d75f00', '-bg', '#080808'].freeze PW_CLEAR_TIMEOUT = 15 XDOTOOL_DELAY = 0.1 DEFAULT_ACTION = 0 NO_ACTION = 1 AUTOTYPE_ACTION = 10 COPY_NAME_ACTION = 11 COPY_PASS_ACTION = 12 def run(args, stdin_data = nil) Open3.popen2(*args) do |stdin, _stdout, wait| stdin.print(stdin_data) if stdin_data stdin.close wait.value end end def capture(args, stdin = nil) Open3.capture2(*args, stdin_data: stdin) end def rofi(args, stdin) capture(['rofi'] + ROFI_ARGS + args, stdin) end def parse_credential(name) pw, *other = capture(['pass', name])[0].lines.map(&:chomp) (other.map { |l| l.split(': ', 2) } + [['pw', pw]]).to_h end def nag(message) run(['dzen2'] + DZEN2_ARGS, message + "\n") exit(1) end def autotype_field(meta, field) value = meta[field] || nag("Unknown field: #{field}") run(['xdotool', 'type', '--clearmodifiers', '--file', '-'], value) end def guess_autotype_fields(meta) if meta['autotype'] meta['autotype'].split elsif meta['user'] ['user', ':tab', 'pass'] else ['pass'] end end def autotype(meta) guess_autotype_fields(meta).each do |word| case word when ':tab' then run(%w[xdotool key Tab]) when ':space' then run(%w[xdotool key space]) when ':delay' then sleep(XDOTOOL_DELAY) when 'pass' then autotype_field(meta, 'pw') else autotype_field(meta, word) end end end def copy(meta, field) value = meta[field] || nag("Missing field: #{field}") cmd = ['xclip', '-selection', 'clipboard'] run(cmd, value) sleep(PW_CLEAR_TIMEOUT) run(cmd, '') end creds_paths = Dir.glob(File.join(PASS_ROOT, '**/*.gpg')) creds = creds_paths.map { |p| p.sub(PASS_ROOT, '').sub(/\.gpg$/, '') } selection, status = rofi(['-dmenu', '-p', 'pass-rofi > ', '-kb-custom-1', 'Alt+a', '-kb-custom-2', 'Alt+u', '-kb-custom-3', 'Alt+p'], creds.join("\n")) exit(0) if status.exitstatus == NO_ACTION meta = parse_credential(selection.chomp) case status.exitstatus when DEFAULT_ACTION, AUTOTYPE_ACTION then autotype(meta) when COPY_NAME_ACTION then copy(meta, 'user') when COPY_PASS_ACTION then copy(meta, 'pw') end