fixed qute-bitwarden

This commit is contained in:
opal
2022-10-29 22:48:05 -07:00
parent 887ac20344
commit c1c6874078
11 changed files with 1692 additions and 2702 deletions

View File

@@ -63,6 +63,7 @@ export LC_WHO=rorlando
# Scripts Path
PATH=$PATH:~/scripts
PATH=$PATH:~/.local/bin
PATH=$PATH:~/bin
# Ruby Path
PATH=$PATH:~/.local/share/gem/ruby/2.7.0/bin
@@ -73,7 +74,7 @@ PATH=$PATH:~/.local/share/gem/ruby/2.7.0/bin
# EG: the ls command is aliased, but to use the normal ls command you would type \ls
# Rsync push music to jellyfin
alias music-push='rsync -r -e 'ssh -p 46668' --info=progress2 /home/opal/music/to-push/* cleric:/data/container_storage/jellyfin/media/music/to-sort/'
alias music-push="rsync -r -e 'ssh -p 46668' --info=progress2 /home/opal/music/to-push/* cleric:/data/container_storage/jellyfin/media/music/to-sort/"
# Add an "alert" alias for long running commands. Use like so:
# sleep 10; alert
@@ -86,7 +87,6 @@ alias da='date "+%Y-%m-%d %A %T %Z"'
alias cp='cp -i'
alias mv='mv -i'
alias rm='rm -iv'
alias trash='mv $1 ~/.trash/'
alias mkdir='mkdir -p'
alias ps='ps auxf'
alias ping='ping -c 10'

View File

@@ -207,6 +207,11 @@
;; ("/work/trash" . ?t)
;; ("/work/drafts" . ?d))))
(use-package vterm
:commands vterm
:config
(setq vterm-shell "bash"))
(setq rmh-elfeed-org-files (list "~/sync/elfeed/feeds.org"))
(add-hook! 'elfeed-search-mode-hook 'elfeed-update)
(after! elfeed

View File

@@ -94,7 +94,7 @@
;;taskrunner ; taskrunner for all your projects
;;terraform ; infrastructure as code
;;tmux ; an API for interacting with tmux
;;upload ; map local to remote projects via ssh/ftp
r;;upload ; map local to remote projects via ssh/ftp
:os
(:if IS-MAC macos) ; improve compatibility with macOS

View File

@@ -1,8 +1,3 @@
(use-package vterm
:commands vterm
:config
(setq vterm-shell "zsh"))
(package! org-bullets)
(package! visual-fill-column)
(package! org-make-toc)

View File

@@ -49,4 +49,5 @@ settings:
'*://*.www.vultr.com/*': true
'*://*.www.xfinity.com/*': true
'*://*.www.youtube.com/*': true
'*://jf.opal.sh/*': true
'*://www.newegg.com/*': true

View File

@@ -6,39 +6,62 @@ import dracula.draw
# Load existing settings made via :set
config.load_autoconfig()
dracula.draw.blood(c, {"spacing": {"vertical": 6, "horizontal": 8}})
dracula.draw.blood(c, {
'spacing': {
'vertical': 6,
'horizontal': 8
}
})
def intercept(info: interceptor.Request):
if info.request_url.host() == 'youtube.com':
if info.request_url.host() == "youtube.com":
new_url = QUrl(info.request_url)
new_url.setHost('vid.puffyan.us')
new_url.setHost("vid.puffyan.us")
try:
info.redirect(new_url)
except interceptor.interceptors.RedirectException:
pass
interceptor.register(intercept)
c.qt.highdpi = True
c.fonts.default_size = '15pt'
c.zoom.default = "150%"
c.fonts.default_size = "15pt"
c.fonts.tabs.selected = "13pt default_family"
c.fonts.tabs.unselected = "13pt default_family"
c.zoom.default = "130%"
c.content.javascript.enabled = False
#c.content.user_stylesheets = [ "stylesheet.css" ]
c.downloads.location.directory = "~/downloads"
c.editor.command = [ "alacritty", "-e", "vim", "{}" ]
c.editor.command = ["alacritty", "-e", "vim", "{}"]
c.editor.encoding = "utf-8"
c.scrolling.smooth = True
c.auto_save.session = True
c.colors.webpage.preferred_color_scheme = "dark"
# privacy
c.content.cookies.accept = "no-3rdparty"
c.content.webrtc_ip_handling_policy = "default-public-interface-only"
c.content.site_specific_quirks.enabled = False
c.content.headers.user_agent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.5112.79 Safari/537.36"
config.bind("j", "scroll-px 0 100")
config.bind("k", "scroll-px 0 -100")
config.bind("K", "tab-next")
config.bind("J", "tab-prev")
c.url.searchengines = {
"DEFAULT" : "https://duckduckgo.com/?q={}",
"DEFAULT": "https://www.startpage.com/sp/search?query={}",
}
c.url.start_pages = [ "https://html.duckduckgo.com" ]
c.url.start_pages = ["https://www.startpage.com"]
config.bind('<Ctrl-Shift-y>', 'hint links spawn --detach mpv --force-window yes {hint-url}')
# Bitwarden Password Management
config.bind(
",p",
"spawn --userscript qute-bitwarden --dmenu-invocation 'bemenu -b -x --prompt=Pass'",
)
config.bind(
",u",
"spawn --userscript qute-bitwarden -e --dmenu-invocation 'bemenu -b -p 'Username Only''",
)
config.bind(
",P",
"spawn --userscript qute-bitwarden --dmenu-invocation 'bemenu -b -x -p 'Password Only''",
)

View File

@@ -0,0 +1,343 @@
#!/usr/bin/env python3
#!/usr/bin/env python3
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2014-2021 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# Copyright 2014-2018 Claude (longneck) <longneck@scratchbook.ch>
# This file is part of qutebrowser.
#
# qutebrowser is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# qutebrowser is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with qutebrowser. If not, see <https://www.gnu.org/licenses/>.
"""Tool to import data from other browsers.
Currently importing bookmarks from Netscape HTML Bookmark files, Chrome
profiles, and Mozilla profiles is supported.
"""
import argparse
import textwrap
import sqlite3
import os
import urllib.parse
import json
import string
def main():
args = get_args()
bookmark_types = []
output_format = None
input_format = args.input_format
if args.search_output:
bookmark_types = ["search"]
if args.oldconfig:
output_format = "oldsearch"
else:
output_format = "search"
else:
if args.bookmark_output:
output_format = "bookmark"
elif args.quickmark_output:
output_format = "quickmark"
if args.import_bookmarks:
bookmark_types.append("bookmark")
if args.import_keywords:
bookmark_types.append("keyword")
if not bookmark_types:
bookmark_types = ["bookmark", "keyword"]
if not output_format:
output_format = "quickmark"
import_function = {
"html": import_html_bookmarks,
"mozilla": import_moz_places,
"chrome": import_chrome,
}
import_function[input_format](args.bookmarks, bookmark_types, output_format)
def get_args():
"""Get the argparse parser."""
parser = argparse.ArgumentParser(
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog=textwrap.dedent(
"""
To import bookmarks, you'll need the path to your profile or an
exported HTML file from your browser's bookmark manager. Redirect
the output from this script to the appropriate file in your
qutebrowser config directory (listed in the output of :version),
usually done with the '>' operator; for example,
./importer.py -i mozilla your_profile_path > ~/.config/qutebrowser/quickmarks
Common browsers with native input format support:
chrome: Chrome, Chromium, Edge
mozilla: Firefox, SeaMonkey, Pale Moon
"""
),
)
parser.add_argument(
"-i",
"--input-format",
help="Which input format? Defaults to html",
choices=["html", "mozilla", "chrome"],
default="html",
required=False,
)
parser.add_argument(
"-b",
"--bookmark-output",
help="Output in bookmark format.",
action="store_true",
default=False,
required=False,
)
parser.add_argument(
"-q",
"--quickmark-output",
help="Output in quickmark format (default).",
action="store_true",
default=False,
required=False,
)
parser.add_argument(
"-s",
"--search-output",
help="Output config.py search engine format (negates -B and -K)",
action="store_true",
default=False,
required=False,
)
parser.add_argument(
"--oldconfig",
help="Output search engine format for old qutebrowser.conf format",
default=False,
action="store_true",
required=False,
)
parser.add_argument(
"-B",
"--import-bookmarks",
help="Import plain bookmarks (can be combiend with -K)",
action="store_true",
default=False,
required=False,
)
parser.add_argument(
"-K",
"--import-keywords",
help="Import keywords (can be combined with -B)",
action="store_true",
default=False,
required=False,
)
parser.add_argument(
"bookmarks",
help="Bookmarks file (html format) or " "profile folder (Mozilla format)",
)
args = parser.parse_args()
return args
def search_escape(url):
"""Escape URLs such that preexisting { and } are handled properly.
Will obviously trash a properly-formatted qutebrowser URL.
"""
return url.replace("{", "{{").replace("}", "}}")
def opensearch_convert(url):
"""Convert a basic OpenSearch URL into something qutebrowser can use.
Exceptions:
KeyError:
An unknown and required parameter is present in the URL. This
usually means there's browser/addon specific functionality needed
to build the URL (I'm looking at you and your browser, Google) that
obviously won't be present here.
"""
subst = {
"searchTerms": "%s", # for proper escaping later
"language": "*",
"inputEncoding": "UTF-8",
"outputEncoding": "UTF-8",
}
# remove optional parameters (even those we don't support)
for param in string.Formatter().parse(url):
if param[1]:
if param[1].endswith("?"):
url = url.replace("{" + param[1] + "}", "")
elif param[2] and param[2].endswith("?"):
url = url.replace("{" + param[1] + ":" + param[2] + "}", "")
return search_escape(url.format(**subst)).replace("%s", "{}")
def import_html_bookmarks(bookmarks_file, bookmark_types, output_format):
"""Import bookmarks from a NETSCAPE-Bookmark-file v1.
Generated by Chromium, Firefox, IE and possibly more browsers. Not all
export all possible bookmark types:
- Firefox mostly works with everything
- Chrome doesn't support keywords at all; searches are a separate
database
"""
import bs4
with open(bookmarks_file, encoding="utf-8") as f:
soup = bs4.BeautifulSoup(f, "html.parser")
bookmark_query = {
"search": lambda tag: (
(tag.name == "a") and ("shortcuturl" in tag.attrs) and ("%s" in tag["href"])
),
"keyword": lambda tag: (
(tag.name == "a")
and ("shortcuturl" in tag.attrs)
and ("%s" not in tag["href"])
),
"bookmark": lambda tag: (
(tag.name == "a") and ("shortcuturl" not in tag.attrs) and (tag.string)
),
}
output_template = {
"search": {
"search": "c.url.searchengines['{tag[shortcuturl]}'] = "
"'{tag[href]}' #{tag.string}"
},
"oldsearch": {
"search": "{tag[shortcuturl]} = {tag[href]} #{tag.string}",
},
"bookmark": {
"bookmark": "{tag[href]} {tag.string}",
"keyword": "{tag[href]} {tag.string}",
},
"quickmark": {
"bookmark": "{tag.string} {tag[href]}",
"keyword": "{tag[shortcuturl]} {tag[href]}",
},
}
bookmarks = []
for typ in bookmark_types:
tags = soup.findAll(bookmark_query[typ])
for tag in tags:
if typ == "search":
tag["href"] = search_escape(tag["href"]).replace("%s", "{}")
if tag["href"] not in bookmarks:
bookmarks.append(output_template[output_format][typ].format(tag=tag))
for bookmark in bookmarks:
print(bookmark)
def import_moz_places(profile, bookmark_types, output_format):
"""Import bookmarks from a Mozilla profile's places.sqlite database."""
place_query = {
"bookmark": (
"SELECT DISTINCT moz_bookmarks.title,moz_places.url "
"FROM moz_bookmarks,moz_places "
"WHERE moz_places.id=moz_bookmarks.fk "
"AND moz_places.id NOT IN (SELECT place_id FROM moz_keywords) "
"AND moz_places.url NOT LIKE 'place:%';"
), # Bookmarks with no keywords assigned
"keyword": (
"SELECT moz_keywords.keyword,moz_places.url "
"FROM moz_keywords,moz_places,moz_bookmarks "
"WHERE moz_places.id=moz_bookmarks.fk "
"AND moz_places.id=moz_keywords.place_id "
"AND moz_places.url NOT LIKE '%!%s%' ESCAPE '!';"
), # Bookmarks with keywords assigned but no %s substitution
"search": (
"SELECT moz_keywords.keyword, "
" moz_bookmarks.title, "
" search_conv(moz_places.url) AS url "
"FROM moz_keywords,moz_places,moz_bookmarks "
"WHERE moz_places.id=moz_bookmarks.fk "
"AND moz_places.id=moz_keywords.place_id "
"AND moz_places.url LIKE '%!%s%' ESCAPE '!';"
), # bookmarks with keyword and %s substitution
}
out_template = {
"bookmark": {"bookmark": "{url} {title}", "keyword": "{url} {keyword}"},
"quickmark": {"bookmark": "{title} {url}", "keyword": "{keyword} {url}"},
"oldsearch": {"search": "{keyword} {url} #{title}"},
"search": {"search": "c.url.searchengines['{keyword}'] = '{url}' #{title}"},
}
def search_conv(url):
return search_escape(url).replace("%s", "{}")
places = sqlite3.connect(os.path.join(profile, "places.sqlite"))
places.create_function("search_conv", 1, search_conv)
places.row_factory = sqlite3.Row
c = places.cursor()
for typ in bookmark_types:
c.execute(place_query[typ])
for row in c:
print(out_template[output_format][typ].format(**row))
def import_chrome(profile, bookmark_types, output_format):
"""Import bookmarks and search keywords from Chrome-type profiles.
On Chrome, keywords and search engines are the same thing and handled in
their own database table; bookmarks cannot have associated keywords. This
is why the dictionary lookups here are much simpler.
"""
out_template = {
"bookmark": "{url} {name}",
"quickmark": "{name} {url}",
"search": "c.url.searchengines['{keyword}'] = '{url}'",
"oldsearch": "{keyword} {url}",
}
if "search" in bookmark_types:
webdata = sqlite3.connect(os.path.join(profile, "Web Data"))
c = webdata.cursor()
c.execute("SELECT keyword,url FROM keywords;")
for keyword, url in c:
try:
url = opensearch_convert(url)
print(out_template[output_format].format(keyword=keyword, url=url))
except KeyError:
print(
"# Unsupported parameter in url for {}; skipping....".format(
keyword
)
)
else:
with open(os.path.join(profile, "Bookmarks"), encoding="utf-8") as f:
bookmarks = json.load(f)
def bm_tree_walk(bm, template):
"""Recursive function to walk through bookmarks."""
if not isinstance(bm, dict):
return
assert "type" in bm, bm
if bm["type"] == "url":
if urllib.parse.urlparse(bm["url"]).scheme != "chrome":
print(template.format(**bm))
elif bm["type"] == "folder":
for child in bm["children"]:
bm_tree_walk(child, template)
for root in bookmarks["roots"].values():
bm_tree_walk(root, out_template[output_format])
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,355 @@
#!/usr/bin/env python3
# Copyright 2017 Chris Braun (cryzed) <cryzed@googlemail.com>
# Adapted for Bitwarden by Jonathan Haylett (JonnyHaystack) <jonathan@haylett.dev>
#
# This file is part of qutebrowser.
#
# qutebrowser is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# qutebrowser is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with qutebrowser. If not, see <https://www.gnu.org/licenses/>.
"""
Insert login information using Bitwarden CLI and a dmenu-compatible application
(e.g. dmenu, rofi -dmenu, ...).
"""
USAGE = """The domain of the site has to be in the name of the Bitwarden entry, for example: "github.com/cryzed" or
"websites/github.com". The login information is inserted by emulating key events using qutebrowser's fake-key command in this manner:
[USERNAME]<Tab>[PASSWORD], which is compatible with almost all login forms.
If enabled, with the `--totp` flag, it will also move the TOTP code to the
clipboard, much like the Firefox add-on.
You must log into Bitwarden CLI using `bw login` prior to use of this script.
The session key will be stored using keyctl for the number of seconds passed to
the --auto-lock option.
To use in qutebrowser, run: `spawn --userscript qute-bitwarden`
"""
EPILOG = """Dependencies: tldextract (Python 3 module), pyperclip (optional
Python module, used for TOTP codes), Bitwarden CLI (1.7.4 is known to work
but older versions may well also work)
WARNING: The login details are viewable as plaintext in qutebrowser's debug log
(qute://log) and might be shared if you decide to submit a crash report!"""
import argparse
import enum
import functools
import os
import shlex
import subprocess
import sys
import json
import tldextract
argument_parser = argparse.ArgumentParser(
description=__doc__,
usage=USAGE,
epilog=EPILOG,
)
argument_parser.add_argument("url", nargs="?", default=os.getenv("QUTE_URL"))
argument_parser.add_argument(
"--dmenu-invocation",
"-d",
default="bemenu -x -b --prompt=Pass",
help="Invocation used to execute a dmenu-provider",
)
argument_parser.add_argument(
"--password-prompt-invocation",
"-p",
default='bemenu -x -b -p "Master Password"',
help="Invocation used to prompt the user for their Bitwarden password",
)
argument_parser.add_argument(
"--no-insert-mode",
"-n",
dest="insert_mode",
action="store_false",
help="Don't automatically enter insert mode",
)
argument_parser.add_argument(
"--totp", "-t", action="store_true", help="Copy TOTP key to clipboard"
)
argument_parser.add_argument(
"--io-encoding",
"-i",
default="UTF-8",
help="Encoding used to communicate with subprocesses",
)
argument_parser.add_argument(
"--merge-candidates",
"-m",
action="store_true",
help="Merge pass candidates for fully-qualified and registered domain name",
)
argument_parser.add_argument(
"--auto-lock",
type=int,
default=900,
help="Automatically lock the vault after this many seconds",
)
group = argument_parser.add_mutually_exclusive_group()
group.add_argument(
"--username-only", "-e", action="store_true", help="Only insert username"
)
group.add_argument(
"--password-only", "-w", action="store_true", help="Only insert password"
)
group.add_argument(
"--totp-only", "-T", action="store_true", help="Only insert totp code"
)
stderr = functools.partial(print, file=sys.stderr)
class ExitCodes(enum.IntEnum):
SUCCESS = 0
FAILURE = 1
# 1 is automatically used if Python throws an exception
NO_PASS_CANDIDATES = 2
COULD_NOT_MATCH_USERNAME = 3
COULD_NOT_MATCH_PASSWORD = 4
def qute_command(command):
with open(os.environ["QUTE_FIFO"], "w") as fifo:
fifo.write(command + "\n")
fifo.flush()
def ask_password(password_prompt_invocation):
process = subprocess.run(
shlex.split(password_prompt_invocation),
text=True,
stdout=subprocess.PIPE,
)
if process.returncode > 0:
raise Exception("Could not unlock vault")
master_pass = process.stdout.strip()
return subprocess.check_output(
["bw", "unlock", "--raw", master_pass],
text=True,
).strip()
def get_session_key(auto_lock, password_prompt_invocation):
if auto_lock == 0:
subprocess.call(["keyctl", "purge", "user", "bw_session"])
return ask_password(password_prompt_invocation)
else:
process = subprocess.run(
["keyctl", "request", "user", "bw_session"],
text=True,
stdout=subprocess.PIPE,
)
key_id = process.stdout.strip()
if process.returncode > 0:
session = ask_password(password_prompt_invocation)
if not session:
raise Exception("Could not unlock vault")
key_id = subprocess.check_output(
["keyctl", "add", "user", "bw_session", session, "@u"],
text=True,
).strip()
if auto_lock > 0:
subprocess.call(["keyctl", "timeout", str(key_id), str(auto_lock)])
return subprocess.check_output(
["keyctl", "pipe", str(key_id)],
text=True,
).strip()
def pass_(domain, encoding, auto_lock, password_prompt_invocation):
session_key = get_session_key(auto_lock, password_prompt_invocation)
process = subprocess.run(
["bw", "list", "items", "--session", session_key, "--url", domain],
capture_output=True,
)
err = process.stderr.decode(encoding).strip()
if err:
msg = "Bitwarden CLI returned for {:s} - {:s}".format(domain, err)
stderr(msg)
if process.returncode:
return "[]"
out = process.stdout.decode(encoding).strip()
return out
def get_totp_code(
selection_id, domain_name, encoding, auto_lock, password_prompt_invocation
):
session_key = get_session_key(auto_lock, password_prompt_invocation)
process = subprocess.run(
["bw", "get", "totp", "--session", session_key, selection_id],
capture_output=True,
)
err = process.stderr.decode(encoding).strip()
if err:
# domain_name instead of selection_id to make it more user-friendly
msg = "Bitwarden CLI returned for {:s} - {:s}".format(domain_name, err)
stderr(msg)
if process.returncode:
return "[]"
out = process.stdout.decode(encoding).strip()
return out
def dmenu(items, invocation, encoding):
command = shlex.split(invocation)
process = subprocess.run(
command, input="\n".join(items).encode(encoding), stdout=subprocess.PIPE
)
return process.stdout.decode(encoding).strip()
def fake_key_raw(text):
for character in text:
# Escape all characters by default, space requires special handling
sequence = '" "' if character == " " else r"\{}".format(character)
qute_command("fake-key {}".format(sequence))
def main(arguments):
if not arguments.url:
argument_parser.print_help()
return ExitCodes.FAILURE
extract_result = tldextract.extract(arguments.url)
# Try to find candidates using targets in the following order: fully-qualified domain name (includes subdomains),
# the registered domain name and finally: the IPv4 address if that's what
# the URL represents
candidates = []
for target in filter(
None,
[
extract_result.fqdn,
extract_result.registered_domain,
extract_result.subdomain + "." + extract_result.domain,
extract_result.domain,
extract_result.ipv4,
],
):
target_candidates = json.loads(
pass_(
target,
arguments.io_encoding,
arguments.auto_lock,
arguments.password_prompt_invocation,
)
)
if not target_candidates:
continue
candidates = candidates + target_candidates
if not arguments.merge_candidates:
break
else:
if not candidates:
stderr("No pass candidates for URL {!r} found!".format(arguments.url))
return ExitCodes.NO_PASS_CANDIDATES
if len(candidates) == 1:
selection = candidates.pop()
else:
choices = []
for c in candidates:
if c["login"]["username"] is None:
c["login"]["username"] = ""
choices.append("{:s} | {:s}".format(c["name"], c["login"]["username"]))
# choices = [
# "{:s} | {:s}".format(c["name"], c["login"]["username"]) for c in candidates
# ]
choice = dmenu(choices, arguments.dmenu_invocation, arguments.io_encoding)
choice_tokens = choice.split("|")
choice_name = choice_tokens[0].strip()
choice_username = choice_tokens[1].strip()
selection = next(
(
c
for (i, c) in enumerate(candidates)
if c["name"] == choice_name
and c["login"]["username"] == choice_username
),
None,
)
# Nothing was selected, simply return
if not selection:
return ExitCodes.SUCCESS
username = selection["login"]["username"]
password = selection["login"]["password"]
totp = selection["login"]["totp"]
if arguments.username_only:
fake_key_raw(username)
elif arguments.password_only:
fake_key_raw(password)
elif arguments.totp_only:
# No point in moving it to the clipboard in this case
fake_key_raw(
get_totp_code(
selection["id"],
selection["name"],
arguments.io_encoding,
arguments.auto_lock,
arguments.password_prompt_invocation,
)
)
else:
# Enter username and password using fake-key and <Tab> (which seems to work almost universally), then switch
# back into insert-mode, so the form can be directly submitted by
# hitting enter afterwards
fake_key_raw(username)
qute_command("fake-key <Tab>")
fake_key_raw(password)
if arguments.insert_mode:
qute_command("mode-enter insert")
# If it finds a TOTP code, it copies it to the clipboard,
# which is the same behavior as the Firefox add-on.
if not arguments.totp_only and totp and arguments.totp:
# The import is done here, to make pyperclip an optional dependency
import pyperclip
pyperclip.copy(
get_totp_code(
selection["id"],
selection["name"],
arguments.io_encoding,
arguments.auto_lock,
arguments.password_prompt_invocation,
)
)
return ExitCodes.SUCCESS
if __name__ == "__main__":
arguments = argument_parser.parse_args()
sys.exit(main(arguments))

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -257,11 +257,11 @@ Not using this much atm.
;; ("/work/drafts" . ?d))))
#+end_src
** Terminals
#+begin_src emacs-lisp :tangle ~/opalfiles/.config/doom/packages.el
#+begin_src emacs-lisp :tangle ~/opalfiles/.config/doom/config.el
(use-package vterm
:commands vterm
:config
(setq vterm-shell "zsh"))
(setq vterm-shell "bash"))
#+end_src
** Elfeed
Rss reader