rj1
log | files | refs
commit ccbea2d61b4ab3673f368a0502f13be747c6a99d
author: rj1 <[email protected]>
date:   Sat, 27 Apr 2024 10:12:42 -0600

init

Diffstat:
A.config/etc/evremap/evremap-laptop.service | 8++++++++
A.config/etc/evremap/evremap-laptop.yml | 21+++++++++++++++++++++
A.config/etc/pacman.conf | 18++++++++++++++++++
A.config/etc/sudoers | 6++++++
A.config/firefox/user.js | 133+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A.config/firefox/userChrome.css | 104+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A.config/firefox/vimium_c.json | 43+++++++++++++++++++++++++++++++++++++++++++
A.config/firejail/chromium.profile | 2++
A.config/firejail/firefox.profile | 16++++++++++++++++
A.config/firejail/qutebrowser.profile | 13+++++++++++++
A.config/fontconfig/fonts.conf | 12++++++++++++
A.config/foot/foot.ini | 54++++++++++++++++++++++++++++++++++++++++++++++++++++++
A.config/git/config | 46++++++++++++++++++++++++++++++++++++++++++++++
A.config/imv/config | 8++++++++
A.config/mako/config | 14++++++++++++++
A.config/mako/notify.ogg | 0
A.config/mimeapps.list | 27+++++++++++++++++++++++++++
A.config/mitmproxy/config.yaml | 1+
A.config/mpd/mpd.conf | 11+++++++++++
A.config/mpv/input.conf | 5+++++
A.config/mpv/mpv.conf | 12++++++++++++
A.config/mpv/scripts/subs.lua | 5+++++
A.config/newsboat/config | 32++++++++++++++++++++++++++++++++
A.config/nvim/init.lua | 18++++++++++++++++++
A.config/nvim/lua/rj1/ai.lua | 58++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A.config/nvim/lua/rj1/bufline.lua | 83+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A.config/nvim/lua/rj1/colorpicker.lua | 26++++++++++++++++++++++++++
A.config/nvim/lua/rj1/filetypes.lua | 22++++++++++++++++++++++
A.config/nvim/lua/rj1/formatter.lua | 21+++++++++++++++++++++
A.config/nvim/lua/rj1/git.lua | 8++++++++
A.config/nvim/lua/rj1/keymaps.lua | 204+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A.config/nvim/lua/rj1/lsp.lua | 420+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A.config/nvim/lua/rj1/lualine.lua | 118+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A.config/nvim/lua/rj1/notes.lua | 134+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A.config/nvim/lua/rj1/plugins.lua | 157+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A.config/nvim/lua/rj1/sessions.lua | 33+++++++++++++++++++++++++++++++++
A.config/nvim/lua/rj1/settings.lua | 98+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A.config/nvim/lua/rj1/telescope.lua | 43+++++++++++++++++++++++++++++++++++++++++++
A.config/nvim/lua/rj1/treesitter.lua | 34++++++++++++++++++++++++++++++++++
A.config/pipewire/pipewire.conf | 75+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A.config/qutebrowser/config.py | 161+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A.config/qutebrowser/redirect.py | 23+++++++++++++++++++++++
A.config/rofi/config.rasi | 8++++++++
A.config/rofi/onedark.rasi | 78++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A.config/swappy/config | 7+++++++
A.config/sway/config##hostname.empy | 213+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A.config/sway/config##hostname.splitty | 211+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A.config/sway/mic-toggle.sh | 11+++++++++++
A.config/tessen/config | 5+++++
A.config/tmux/tmux.conf | 101+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A.config/user-dirs.dirs | 15+++++++++++++++
A.config/visidata/config.py | 2++
A.config/waybar/config | 138+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A.config/waybar/style.css | 61+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A.config/waybar/task.sh | 3+++
A.config/waybar/temp.sh | 4++++
A.config/wob/wob.ini | 10++++++++++
A.config/zathura/zathurarc | 20++++++++++++++++++++
A.config/zsh/.zprofile | 81+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A.config/zsh/.zshrc | 49+++++++++++++++++++++++++++++++++++++++++++++++++
A.config/zsh/aliases.zsh | 184+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A.config/zsh/completion.zsh | 14++++++++++++++
A.config/zsh/history.zsh | 28++++++++++++++++++++++++++++
A.config/zsh/vi.zsh | 25+++++++++++++++++++++++++
A.local/share/applications/imv.desktop | 4++++
A.local/share/applications/mpv.desktop | 4++++
A.local/share/applications/nvim.desktop | 4++++
A.local/share/gnupg/gpg-agent.conf | 4++++
A.zshenv | 2++
Abin/brightness-adjust | 5+++++
Abin/cert-gen | 34++++++++++++++++++++++++++++++++++
Abin/chromium-rob | 3+++
Abin/chromium-tuio | 3+++
Abin/edit | 117+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Abin/email-check | 45+++++++++++++++++++++++++++++++++++++++++++++
Abin/emoji | 13+++++++++++++
Abin/launch-terminal | 8++++++++
Abin/miniircd | 1250+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Abin/pinentry-fuzzel | 87+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Abin/pinentry-rofi | 90+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Abin/rofi-bluetooth | 317+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Abin/screen-lock | 28++++++++++++++++++++++++++++
Abin/screen-record | 32++++++++++++++++++++++++++++++++
Abin/snippet-rofi | 10++++++++++
Abin/startw | 4++++
Abin/tessen | 870+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Abin/wallpaper | 6++++++
Abin/ytmpd | 29+++++++++++++++++++++++++++++
88 files changed, 6564 insertions(+), 0 deletions(-)

diff --git a/.config/etc/evremap/evremap-laptop.service b/.config/etc/evremap/evremap-laptop.service @@ -0,0 +1,8 @@ +# /usr/lib/systemd/system/evremap-laptop.service +[Service] +WorkingDirectory=/ +ExecStart=bash -c "/usr/bin/evremap remap /etc/evremap-laptop.toml -d 0" +Restart=always + +[Install] +WantedBy=multi-user.target diff --git a/.config/etc/evremap/evremap-laptop.yml b/.config/etc/evremap/evremap-laptop.yml @@ -0,0 +1,21 @@ +device_name = "AT Translated Set 2 keyboard" + +[[remap]] +input = ["KEY_CAPSLOCK"] +output = ["KEY_ESC"] + +[[remap]] +input = ["KEY_LEFTALT", "KEY_K"] +output = ["KEY_UP"] + +[[remap]] +input = ["KEY_LEFTALT", "KEY_H"] +output = ["KEY_LEFT"] + +[[remap]] +input = ["KEY_LEFTALT", "KEY_J"] +output = ["KEY_DOWN"] + +[[remap]] +input = ["KEY_LEFTALT", "KEY_L"] +output = ["KEY_RIGHT"] diff --git a/.config/etc/pacman.conf b/.config/etc/pacman.conf @@ -0,0 +1,18 @@ +[options] +HoldPkg = pacman glibc +Architecture = auto +Color +CheckSpace +VerbosePkgLists +ParallelDownloads = 10 +SigLevel = Required DatabaseOptional +LocalFileSigLevel = Optional + +[core] +Include = /etc/pacman.d/mirrorlist + +[extra] +Include = /etc/pacman.d/mirrorlist + +[community] +Include = /etc/pacman.d/mirrorlist diff --git a/.config/etc/sudoers b/.config/etc/sudoers @@ -0,0 +1,6 @@ +root ALL=(ALL:ALL) ALL +%wheel ALL=(ALL:ALL) ALL +rj1 ALL=NOPASSWD:/usr/bin/reboot +rj1 ALL=NOPASSWD:/usr/bin/shutdown +rj1 ALL=NOPASSWD:/usr/bin/poweroff +Defaults passprompt="[sudo] password: " diff --git a/.config/firefox/user.js b/.config/firefox/user.js @@ -0,0 +1,133 @@ +// allow vimium_c (and other extensions) to be used on mozilla.org & other sites restricted by default +user_pref('privacy.resistFingerprinting.block_mozAddonManager', true); +user_pref('extensions.webextensions.restrictedDomains', ''); + +// load userChrome.css customizations +user_pref('toolkit.legacyUserProfileCustomizations.stylesheets', true); + +// scaling +user_pref('layout.css.devPixelsPerPx', 1.5); + +// disable pasting w/ middlemouse button +user_pref('middlemouse.paste', false) + +// hardware acceleration +user_pref('media.ffmpeg.vaapi.enabled', true); +user_pref('media.ffvpx.enabled', false); +user_pref('media.rdd-vpx.enabled', false); +user_pref('media.navigator.mediadatadecoder_vpx_enabled', true); + +// disable alt key popping top menu +user_pref('ui.key.menuAccessKeyFocuses', false); + +// etc +user_pref("general.warnOnAboutConfig", false); +user_pref("browser.newtabpage.activity-stream.showSponsoredTopSites", false); +user_pref("pdfjs.enableScripting", false); +user_pref("browser.urlbar.suggest.quicksuggest", false); +user_pref("browser.urlbar.suggest.quicksuggest.sponsored", false); +user_pref("browser.search.suggest.enabled", false); +user_pref("browser.onboarding.enabled", false); +user_pref("browser.newtabpage.activity-stream.asrouter.userprefs.cfr", false); +user_pref("browser.newtabpage.activity-stream.asrouter.userprefs.cfr.addons", false); +user_pref("browser.newtabpage.activity-stream.asrouter.userprefs.cfr.features", false); +user_pref("extensions.pocket.enabled", false); +user_pref("view_source.wrap_long_lines", true); +user_pref("network.prefetch-next", false); +user_pref("media.video_stats.enabled", false); +user_pref("network.dns.disablePrefetch", true); +user_pref("geo.enabled", false); +user_pref("browser.newtab.preload", false); +user_pref("browser.newtabpage.activity-stream.disableSnippets", true); +user_pref("browser.newtabpage.activity-stream.enabled", false); +user_pref("browser.newtabpage.activity-stream.feeds.section.highlights", false); +user_pref("browser.newtabpage.activity-stream.feeds.section.topstories", false); +user_pref("browser.newtabpage.activity-stream.feeds.snippets", false); +user_pref("browser.newtabpage.activity-stream.feeds.telemetry", false); +user_pref("browser.newtabpage.activity-stream.feeds.topsites", false); +user_pref("browser.newtabpage.activity-stream.migrationExpired", true); +user_pref("browser.newtabpage.activity-stream.prerender", false); +user_pref("browser.newtabpage.activity-stream.showSearch", false); +user_pref("browser.newtabpage.activity-stream.showTopSites", false); +user_pref("browser.newtabpage.activity-stream.telemetry", false); +user_pref("browser.newtabpage.directory.source", ""); +user_pref("browser.newtabpage.directory.ping", ""); +user_pref("browser.newtabpage.enabled", false); +user_pref("browser.newtabpage.enhanced", false); +user_pref("browser.newtabpage.introShown", true); +user_pref("browser.search.region", "EN"); +user_pref("toolkit.telemetry.enabled", false); +user_pref("toolkit.telemetry.coverage.opt-out", true); +user_pref("toolkit.telemetry.server", ""); +user_pref("datareporting.healthreport.service.enabled", false); +user_pref("datareporting.healthreport.uploadEnabled", false); +user_pref("datareporting.policy.dataSubmissionEnabled", false); +user_pref("browser.selfsupport.url", ""); +user_pref("browser.send_pings", false); +user_pref("browser.shell.checkDefaultBrowser", false); +user_pref("browser.startup.homepage_override.mstone", "ignore"); +user_pref("beacon.enabled", false); +user_pref("loop.logDomains", false); +user_pref("app.normandy.enabled", false); +user_pref("app.normandy.api_url", ""); +user_pref("extensions.shield-recipe-client.enabled", false); +user_pref("app.shield.optoutstudies.enabled", false); +user_pref("network.allow-experiments", false); +user_pref("toolkit.telemetry.enabled", false); +user_pref("toolkit.telemetry.unified", false); +user_pref("toolkit.telemetry.archive.enabled", false); +user_pref("experiments.supported", false); +user_pref("experiments.enabled", false); +user_pref("experiments.manifest.uri", ""); +user_pref("media.eme.enabled", false); +user_pref("media.gmp-widevinecdm.enabled", false); +user_pref("security.ssl.disable_session_identifiers", true); +user_pref("signon.rememberSignons", false); +user_pref("browser.formfill.enable", false); +user_pref("browser.download.manager.retention", 0); +user_pref("browser.safebrowsing.downloads.remote.enabled",false); +user_pref("general.buildID.override", "20100101"); +user_pref("browser.startup.homepage_override.buildID", "20100101"); +user_pref("gfx.font_rendering.opentype_svg.enabled", false); + +// dns over http - https://wiki.mozilla.org/Trusted_Recursive_Resolver +user_pref("network.trr.mode", 5); +user_pref("network.trr.default_provider_uri", "https://mozilla.cloudflare-dns.com/dns-query"); +user_pref("network.trr.uri", "https://mozilla.cloudflare-dns.com/dns-query"); +user_pref("network.trr.custom_uri", "https://mozilla.cloudflare-dns.com/dns-query"); +user_pref("network.trr.bootstrapAddress", "1.1.1.1"); + +// don't allow websites to highjack rightclick context menu +// user_pref("dom.event.contextmenu.enabled", false); + +// oscp +user_pref("security.ssl.enable_ocsp_stapling", false); +user_pref("security.OCSP.enabled", 0); +user_pref("security.OCSP.require", false); + +// disable recommendation pane in about:addons (uses google analytics) +user_pref("extensions.getAddons.showPane", false); +user_pref("extensions.htmlaboutaddons.recommendations.enabled", false); +user_pref("browser.discovery.enabled", false); + +// more telemetry (some duplicates) +user_pref("toolkit.telemetry.unified", false); +user_pref("toolkit.telemetry.enabled", false); +user_pref("toolkit.telemetry.server", "data:,"); +user_pref("toolkit.telemetry.archive.enabled", false); +user_pref("toolkit.telemetry.newProfilePing.enabled", false); +user_pref("toolkit.telemetry.shutdownPingSender.enabled", false); +user_pref("toolkit.telemetry.updatePing.enabled", false); +user_pref("toolkit.telemetry.bhrPing.enabled", false); +user_pref("toolkit.telemetry.firstShutdownPing.enabled", false); +user_pref("toolkit.telemetry.coverage.opt-out", true); +user_pref("toolkit.coverage.opt-out", true); +user_pref("toolkit.coverage.endpoint.base", ""); +user_pref("browser.ping-centre.telemetry", false); +user_pref("browser.newtabpage.activity-stream.feeds.telemetry", false); +user_pref("browser.newtabpage.activity-stream.telemetry", false); + +// crash reports +user_pref("breakpad.reportURL", ""); +user_pref("browser.tabs.crashReporting.sendReport", false); +user_pref("browser.crashReports.unsubmittedCheck.autoSubmit2", false); diff --git a/.config/firefox/userChrome.css b/.config/firefox/userChrome.css @@ -0,0 +1,104 @@ +:root { + /* Tabbar: reduce tab margin */ + --tab-block-margin: 4px 3px !important; +} + +/* Tab: Reduce height */ +.tabbrowser-tab { + min-height: 24px !important; +} + +/* Tab: Ensure tab height doesn't augment when arrowscrollbox is visible */ +#tabbrowser-arrowscrollbox { + --tab-min-height: 31px !important; + max-height: var(--tab-min-height); +} + +/* Tab: Attention icon */ +.tabbrowser-tab:is([image], [pinned]) + > .tab-stack + > .tab-content[attention]:not([selected="true"]), +.tabbrowser-tab + > .tab-stack + > .tab-content[pinned][titlechanged]:not([selected="true"]) { + background-position-x: left 2px !important; + background-position-y: bottom 12.5px !important; +} + +/* URLBar: Fix vertical alignment */ +#urlbar[breakout="true"]:not([open="true"]) { + --urlbar-height: 20px !important; + --urlbar-toolbar-height: 24px !important; +} + +/* URLBar: Fix URL address vertical aligment when megabar is open */ +#urlbar[breakout="true"][open="true"] { + --urlbar-toolbar-height: 30px !important; +} + +/* URLBar: Reduce row items padding */ +.urlbarView-row-inner { + padding-inline: var(--urlbarView-item-inline-padding); + padding-block: 2px !important; +} + +/* URLBar: Reduce and realign row bookmark icons */ +.urlbarView-type-icon { + width: 10px !important; + height: 10px !important; + margin-bottom: 0 !important; + margin-inline-start: 10px !important; +} + +/* URLBar: Reduce "This time, serach with" padding */ +#urlbar .search-one-offs:not([hidden]) { + padding-block: 4px !important; +} + +/* Searchbar: Ensure toolbar height doesn't augment when searchbar is visible */ +#urlbar-container, +#search-container { + padding-block: 0 !important; +} + +/* Searchbar: Make sure the min-height of the input is the same as the popup */ +#search-container { + min-width: 192px !important; +} + +/* Toolbar: Reduce spacing */ +#urlbar-container { + --urlbar-container-height: 30px !important; + margin-top: 0 !important; +} + +/* Reload Button: Fix vertical alignment */ +#reload-button { + margin-block-start: -2px !important; +} + +/* AppMenu: Header */ +.panel-header { + padding: 4px 0 0 4px !important; +} + +/* AppMenu: Header button */ +.panel-header > .subviewbutton-back { + padding: 4px !important; +} + +.tabbrowser-tab[usercontextid] + > .tab-stack + > .tab-background + > .tab-context-line { + height: 1px !important; +} + +.tabbrowser-tab[usercontextid] + > .tab-stack + > .tab-background + > .tab-context-line + + .tab-loading-burst { + background-color: var(--identity-tab-color); + opacity: 0.1; +} diff --git a/.config/firefox/vimium_c.json b/.config/firefox/vimium_c.json @@ -0,0 +1,43 @@ +{ + "name": "Vimium C", + "@time": "9/4/2023, 10:21:35 AM", + "time": 1693844495681, + "environment": { + "extension": "1.99.995", + "platform": "linux", + "firefox": 117 + }, + "clipSub": [ + "p=^git@([^/:]+):=https://$1/=", + "s@^https://(?:www\\.)?google\\.com(?:\\.[^/]+)?/url\\?(?:[^&#]+&)*?url=([^&#]+)@$1@,matched,decodecomp", + "" + ], + "extAllowList": "# extension id or hostname", + "grabBackFocus": true, + "keyMappings": [ + "#!no-check", + "#map K previousTab", + "#map J nextTab", + "map <c-s-h> zoomReset", + "map <c-s-k> zoomIn", + "map <c-s-j> zoomOut", + "map U LinkHints.activateCopyLinkUrl", + "" + ], + "nextPatterns": "next,more,newer,>,›,→,»,≫,>>", + "previousPatterns": "prev,previous,back,older,<,‹,←,«,≪,<<", + "searchEngines": [ + ":sx|searx: https://paulgo.io/search?q=$s", + ":qw|qwant: https://lite.qwant.com/?q=$s", + ":g|google: https://google.com/search?q=$s", + ":gi|google img: https://www.google.com/search?q=$s&source=lnms&tbm=isch", + ":yi|yahoo img: https://images.search.yahoo.com/search/images?p=$s", + ":w|wikpedia: https://en.wikipedia.org/?search=$s", + ":gh|github: https://github.com/search?q=$s", + ":yt|youtube: https://www.youtube.com/results?search_query=$s", + ":pb|pirate bay:https://thepiratebay.org/search.php?q=$s&all=on&search=Pirate+Search&page=0&orderby=", + "" + ], + "searchUrl": "https://paulgo.io/search?q=$s paulgo", + "vimSync": true +} diff --git a/.config/firejail/chromium.profile b/.config/firejail/chromium.profile @@ -0,0 +1,2 @@ +include /etc/firejail/chromium.profile +whitelist ${HOME}/dl diff --git a/.config/firejail/firefox.profile b/.config/firejail/firefox.profile @@ -0,0 +1,16 @@ +noblacklist ${HOME}/.config/firefox +mkdir ${HOME}/.config/firefox +whitelist ${HOME}/.config/firefox +noblacklist ${HOME}/.config/foot +mkdir ${HOME}/.config/foot +whitelist ${HOME}/.config/foot +noblacklist ${HOME}/.config/nvim +mkdir ${HOME}/.config/nvim +whitelist ${HOME}/.config/nvim +noblacklist /usr/share/nvim +whitelist /usr/share/nvim +ignore private-tmp +whitelist ${HOME}/dl +include /etc/firejail/allow-lua.inc +include /etc/firejail/firefox.profile +dbus-user.talk org.freedesktop.Notifications diff --git a/.config/firejail/qutebrowser.profile b/.config/firejail/qutebrowser.profile @@ -0,0 +1,13 @@ +noblacklist ${HOME}/.config/foot +mkdir ${HOME}/.config/foot +whitelist ${HOME}/.config/foot +noblacklist ${HOME}/.config/mpv +mkdir ${HOME}/.config/mpv +whitelist ${HOME}/.config/mpv +noblacklist ${HOME}/.config/nvim +mkdir ${HOME}/.config/nvim +whitelist ${HOME}/.config/nvim + +whitelist ${HOME}/dl +include /etc/firejail/allow-lua.inc +include /etc/firejail/qutebrowser.profile diff --git a/.config/fontconfig/fonts.conf b/.config/fontconfig/fonts.conf @@ -0,0 +1,12 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE fontconfig SYSTEM "fonts.dtd"> +<fontconfig> + <match target="pattern"> + <test name="family" compare="contains"><string>Hack Nerd Font</string></test> + <edit name="family" mode="append"><string>Noto Color Emoji</string></edit> + </match> + <alias> + <family>monospace</family> + <prefer><family>Hack Nerd Font Mono</family></prefer> + </alias> +</fontconfig> diff --git a/.config/foot/foot.ini b/.config/foot/foot.ini @@ -0,0 +1,54 @@ +font=Hack Nerd Font Mono:size=15, Noto Color Emoji:size=12 + +[bell] +urgent=no +notify=no + +[scrollback] +lines=50000 + +[url] +launch=xdg-open ${url} +label-letters=sadfjklewcmpgh +osc8-underline=url-mode +protocols=http, https, ftp, ftps, file, gemini, gopher, magnet +uri-characters=abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.,~:;/?#@!$&%*+="' + +[cursor] +style=beam +color=111111 dcdccc +blink=yes +beam-thickness=1.2 + +[colors] +alpha=0.99 +foreground=abb2bf +background=282c34 +regular0=282c34 # black +regular1=e06c75 # red +regular2=98c379 # green +regular3=e5c07b # yellow +regular4=61afef # blue +regular5=c678dd # magenta +regular6=56b6c2 # cyan +regular7=abb2bf # white +bright0=3e4451 # bright black +bright1=e9969d # bright red +bright2=b3d39c # bright green +bright3=edd4a6 # bright yellow +bright4=8fc6f4 # bright blue +bright5=d7a1e7 # bright magenta +bright6=7bc6d0 # bright cyan +bright7=c8cdd5 # bright white + +[key-bindings] +font-increase=Control+Shift+k +font-decrease=Control+Shift+j +font-reset=Control+Shift+H +show-urls-launch=Control+Shift+e +unicode-input=Control+Shift+o +show-urls-copy=Control+Shift+u + +[url-bindings] +cancel=Control+g Control+c Control+d Escape +toggle-url-visible=t diff --git a/.config/git/config b/.config/git/config @@ -0,0 +1,46 @@ +[init] + defaultBranch = master +[user] + useConfigOnly = true +[pretty] + better-oneline = "format:%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr)%Creset %Cblue[%cn]" +[alias] + id = "!f() { \ + git config user.name \"$(git config user.$1.name)\"; \ + git config user.email \"$(git config user.$1.email)\"; \ + if [ $(git config user.$1.signingkey) ]; then \ + git config user.signingkey \"$(git config user.$1.signingkey)\"; \ + git config commit.gpgsign true; \ + else \ + git config --unset user.signingkey; \ + git config --unset commit.gpgsign; \ + fi; \ + if [ $(git config user.$1.sshkey) ]; then \ + git config core.sshCommand \"ssh -i $HOME/.ssh/$(git config user.$1.sshkey)\"; \ + else \ + git config --unset core.sshCommand; \ + fi; \ + if [ $(git config user.$1.email) ]; then \ + git config sendemail.smtpserveroption \"--account=$(git config user.$1.email)\"; \ + else \ + git config --unset sendemail.smtpserveroption; \ + fi; \ + }; f" + history = log -p + lg = log --pretty=better-oneline + tree = log --pretty=better-oneline --all --graph + co = checkout + cob = checkout -b + br = branch --format='%(HEAD) %(color:yellow)%(refname:short)%(color:reset) - %(contents:subject) %(color:green)(%(committerdate:relative)) [%(authorname)]' --sort=-committerdate + undo = reset HEAD~1 --mixed + st = status -sb + sl = stash list --name-status --pretty='format:%gd [%ar]: %s' + leaderboard = shortlog -s -n + c = commit + p = add --patch +[sendemail] + smtpserver = /usr/bin/msmtp +[include] + path = ~/.config/git/identities +[diff] + tool = nvimdiff diff --git a/.config/imv/config b/.config/imv/config @@ -0,0 +1,8 @@ +[binds] +r = rotate by 90 +n = next +p = prev +k = zoom +5 +j = zoom -5 +h = zoom actual +yy = exec echo "$imv_current_file" | tr -d '\n' | wl-copy diff --git a/.config/mako/config b/.config/mako/config @@ -0,0 +1,14 @@ +anchor=bottom-right +sort=-time +layer=overlay +background-color=#2e3440 +width=200 +height=150 +border-size=1 +border-color=#98c379 +icons=0 +max-icon-size=64 +default-timeout=5000 +font=Hack Nerd Font Mono 16 +output=HDMI-A-1 +on-notify=exec mpv ~/.config/mako/notify.ogg diff --git a/.config/mako/notify.ogg b/.config/mako/notify.ogg Binary files differ. diff --git a/.config/mimeapps.list b/.config/mimeapps.list @@ -0,0 +1,27 @@ +[Default Applications] +text/plain=nvim.desktop +text/html=firefox.desktop +text/x-php=nvim.desktop +text/x-shellscript=nvim.desktop +text/x-script.python=nvim.desktop +text/x-perl=nvim.desktop +x-scheme-handler/http=firefox.desktop +x-scheme-handler/https=firefox.desktop +x-scheme-handler/about=firefox.desktop +x-scheme-handler/unknown=firefox.desktop +image/jpeg=imv.desktop +image/png=imv.desktop +image/webp=imv.desktop +image/gif=imv.desktop +video/mp4=mpv.desktop +video/x-msvideo=mpv.desktop +application/x-mpegURL=mpv.desktop +video/mp4=mpv.desktop +video/x-matroska=mpv.desktop +audio/x-wav=mpv.desktop +audio/x-aiff=mpv.desktop +audio/x-aiff=mpv.desktop +audio/mpeg=mpv.desktop +audio/mp4=mpv.desktop +audio/mpeg=mpv.desktop +application/ogg=mpv.desktop diff --git a/.config/mitmproxy/config.yaml b/.config/mitmproxy/config.yaml @@ -0,0 +1 @@ +validate_inbound_headers: False diff --git a/.config/mpd/mpd.conf b/.config/mpd/mpd.conf @@ -0,0 +1,11 @@ +music_directory "~/music" +playlist_directory "~/.local/share/mpd/playlists" +db_file "~/.local/share/mpd/database" +pid_file "~/.local/share/mpd/pid" +state_file "~/.local/share/state" +sticker_file "~/.local/share/mpd/sticker.sql" + +audio_output { + type "pulse" + name "pulse" +} diff --git a/.config/mpv/input.conf b/.config/mpv/input.conf @@ -0,0 +1,5 @@ +l seek 5 +h seek -5 +j seek -60 +k seek 60 +S cycle sub diff --git a/.config/mpv/mpv.conf b/.config/mpv/mpv.conf @@ -0,0 +1,12 @@ +fullscreen=no +border=no +load-scripts=yes +osd-font=Hack Nerd Font +cache=yes +volume=100 +screenshot-directory=~/img/sshot +sub-font=Hack Nerd Font +sub-auto=all + +script-opts=ytdl_hook-ytdl_path=yt-dlp +ytdl-format=bestvideo[height<=?1080]+bestaudio/best diff --git a/.config/mpv/scripts/subs.lua b/.config/mpv/scripts/subs.lua @@ -0,0 +1,5 @@ +mp.add_hook('on_load', 10, function () + sub_paths = {'Subs'} + sub_paths[#sub_paths+1]='Subs/' .. mp.get_property('filename/no-ext') + mp.set_property_native('sub-file-paths', sub_paths) +end) diff --git a/.config/newsboat/config b/.config/newsboat/config @@ -0,0 +1,32 @@ +# general +auto-reload yes +max-items 50 +browser "firefox %u" + +# unbind keys +unbind-key ENTER +unbind-key j +unbind-key k +unbind-key J +unbind-key K + +# bind vimkeys +bind-key j down +bind-key k up +bind-key l open +bind-key h quit + +# theme +color background default default +color listnormal default default +color listnormal_unread default default +color listfocus black cyan +color listfocus_unread black cyan +color info default black +color article default default + +# highlights +highlight article "^(Title):.*$" blue default +highlight article "https?://[^ ]+" red default +highlight article "\\[image\\ [0-9]+\\]" green default + diff --git a/.config/nvim/init.lua b/.config/nvim/init.lua @@ -0,0 +1,18 @@ +vim.g.mapleader = " " +vim.g.maplocalleader = " " + +require("rj1/plugins") +require("rj1/settings") +require("rj1/ai") +require("rj1/notes") +require("rj1/sessions") +require("rj1/lualine") +require("rj1/bufline") +require("rj1/telescope") +require("rj1/treesitter") +require("rj1/lsp") +require("rj1/formatter") +require("rj1/colorpicker") +require("rj1/git") +require("rj1/keymaps") +require("rj1/filetypes") diff --git a/.config/nvim/lua/rj1/ai.lua b/.config/nvim/lua/rj1/ai.lua @@ -0,0 +1,58 @@ +-- codeium +-- vim.g.codeium_enabled = false +-- vim.g.codeium_disable_bindings = 1 + +--[[ function Codeium_status() + local status = vim.fn["codeium#GetStatusString"]() + status = string.gsub(status, "^%s+", "") + status = string.lower(status) + return status +end ]] + +-- use <leader>ai to toggle codeium +--[[ vim.keymap.set("n", "<leader>ai", function() + if Codeium_status() == "on" then + vim.g.codeium_enabled = false + else + vim.g.codeium_enabled = true + end +end, { noremap = true }) ]] + + +-- openai +local function fetch_openai_key() + local handle = io.popen("pass dev/openai.com/openai_key") + if handle ~= nil then + local key = string.gsub(handle:read("*a"), "\n", "") + handle:close() + return key + end +end + +require("gp").setup({ + agents = { + { name ="ChatGPT4" }, + { name ="ChatGPT3-5" }, + { + name = "dev", + chat = true, + command = false, + -- model = { model = "gpt-3.5-turbo-16k", temperature = 1.1, top_p = 1 }, + model = { model = "gpt-3.5-turbo-1106", temperature = 1.1, top_p = 1 }, + system_prompt = "You are an AI assistant for a professional programmer.\n\n" + .. "The user provided the additional info about how they would like you to respond:\n\n" + .. "- If you're unsure don't guess and say you don't know instead.\n" + .. "- Ask question if you need clarification to provide a better answer.\n" + .. "- Think deeply and carefully from first principles, going through the problem step by step.\n" + .. "- Zoom out first to see the big picture and then zoom in to details.\n" + .. "- Use the Socratic method to improve your thinking and coding skills.\n" + .. "- Don't exclude any code from your output if the answer requires coding.\n" + .. "- Take a deep breath; You've got this!\n", + }, + }, + openai_api_key = fetch_openai_key(), + openai_api_endpoint = "https://api.openai.com/v1/chat/completions", + chat_dir = vim.fn.stdpath("data"):gsub("/$", "") .. "/chatgpt/chats", + chat_user_prefix = "## prompt", + chat_assistant_prefix = "## response", +}) diff --git a/.config/nvim/lua/rj1/bufline.lua b/.config/nvim/lua/rj1/bufline.lua @@ -0,0 +1,83 @@ +local colors = require("onedarkpro.helpers").get_colors() + +-- cokeline +require("cokeline").setup({ + default_hl = { + fg = function(buffer) + if buffer.is_modified then + return colors.blue + else + return buffer.is_focused and colors.purple or colors.white + end + end, + bg = colors.black, + style = function(buffer) + return buffer.is_focused and "bold" or nil + end, + }, + sidebar = { + filetype = "NvimTree", + components = { + { + text = " nvim-tree", + fg = colors.purple, + bg = colors.black, + style = "bold", + }, + }, + }, + + components = { + { text = " " }, + { + text = function(buffer) + return (buffer.index ~= 1) and "" or "" + end, + }, + { text = " " }, + { + text = function(buffer) + return buffer.devicon.icon + end, + fg = function(buffer) + return buffer.devicon.color + end, + }, + { text = " " }, + { + text = function(buffer) + return buffer.unique_prefix + end, + style = "italic", + fg = function(buffer) + if buffer.is_focused and buffer.is_modified then + return colors.blue + elseif buffer.is_focused then + return colors.purple + else + return colors.gray + end + end, + }, + { + text = function(buffer) + return buffer.filename .. " " + end, + style = function(buffer) + return buffer.is_focused and "bold" or nil + end, + }, + { + text = function(buffer) + -- don't show separator on the last tab + if buffer.index < #vim.fn.getbufinfo({ buflisted = 1 }) then + return "" + else + return "" + end + end, + fg = colors.white, + style = "bold", + }, + }, +}) diff --git a/.config/nvim/lua/rj1/colorpicker.lua b/.config/nvim/lua/rj1/colorpicker.lua @@ -0,0 +1,26 @@ +local function cmp_border(hl_name) + return { + { "🭽", hl_name }, + { "▔", hl_name }, + { "🭾", hl_name }, + { "▕", hl_name }, + { "🭿", hl_name }, + { "▁", hl_name }, + { "🭼", hl_name }, + { "▏", hl_name }, + } +end + +require("ccc").setup({ + bar_char = "󰋘", + point_char = "󰋙", + bar_len = 50, + highlighter = { + auto_enable = true, + }, + win_opts = { + border = cmp_border("CccBorder"), + }, +}) +vim.cmd("command! Color CccPick") +vim.keymap.set("n", "<leader>cc", ":Color<CR>", { silent = true }) diff --git a/.config/nvim/lua/rj1/filetypes.lua b/.config/nvim/lua/rj1/filetypes.lua @@ -0,0 +1,22 @@ +local filetype_map = { + [vim.fn.expand("$HOME/.ssh/servers")] = "sshconfig", + [vim.fn.expand("$XDG_CONFIG_HOME/yambar/config.yml##hostname.redstar")] = "yaml", + [vim.fn.expand("$XDG_CONFIG_HOME/yambar/config.yml##hostname.hackpad")] = "yaml", + [vim.fn.expand("$XDG_CONFIG_HOME/yambar/config.yml##hostname.empy")] = "yaml", + [vim.fn.expand("$XDG_CONFIG_HOME/yambar/config.yml##hostname.splitty")] = "yaml", + [vim.fn.expand("$XDG_CONFIG_HOME/alot/config")] = "toml", + [vim.fn.expand("$XDG_CONFIG_HOME/sway/config##hostname.redstar")] = "swayconfig", + [vim.fn.expand("$XDG_CONFIG_HOME/sway/config##hostname.hackpad")] = "swayconfig", + [vim.fn.expand("$XDG_CONFIG_HOME/sway/config##hostname.empy")] = "swayconfig", + [vim.fn.expand("$XDG_CONFIG_HOME/sway/config##hostname.splitty")] = "swayconfig", + [vim.fn.expand("$XDG_CONFIG_HOME/waybar/config*")] = "json", +} + +for filename, filetype in pairs(filetype_map) do + vim.api.nvim_create_autocmd("bufread", { + pattern = filename, + callback = function() + vim.api.nvim_buf_set_option(0, "filetype", filetype) + end, + }) +end diff --git a/.config/nvim/lua/rj1/formatter.lua b/.config/nvim/lua/rj1/formatter.lua @@ -0,0 +1,21 @@ +require("formatter").setup({ + filetype = { + html = { require("formatter.filetypes.html").prettier }, + css = { require("formatter.filetypes.css").prettier }, + javascript = { require("formatter.filetypes.javascript").prettier }, + javascriptreact = { require("formatter.filetypes.javascript").prettier }, + typescript = { require("formatter.filetypes.typescript").prettier }, + typescriptreact = { require("formatter.filetypes.typescript").prettier }, + python = { require("formatter.filetypes.python").black }, + rust = { require("formatter.filetypes.rust").rustfmt }, + sh = { require("formatter.filetypes.sh").shfmt }, + go = { require("formatter.filetypes.go").gofmt }, + json = { require("formatter.filetypes.json").jq }, + php = { require("formatter.filetypes.php").phpcbf }, + lua = { require("formatter.filetypes.lua").stylua }, + markdown = { require("formatter.filetypes.markdown").prettier }, + blade = { require("formatter.filetypes.html").prettier }, + c = { require("formatter.filetypes.c").clangformat }, + vue = { require("formatter.filetypes.vue").prettier }, + }, +}) diff --git a/.config/nvim/lua/rj1/git.lua b/.config/nvim/lua/rj1/git.lua @@ -0,0 +1,8 @@ +-- gitsigns +require("gitsigns").setup({ + current_line_blame = true, + attach_to_untracked = true, + yadm = { + enable = true, + }, +}) diff --git a/.config/nvim/lua/rj1/keymaps.lua b/.config/nvim/lua/rj1/keymaps.lua @@ -0,0 +1,204 @@ +local wk = require("which-key") +wk.register() + + +-- reset shortmess to default +vim.keymap.set("n", "<leader>rs", ":set shortmess=filnxtToOF<cr>", { silent = true }) + +-- buffer navigation +vim.keymap.set("n", "<c-k>", "<plug>(cokeline-focus-next)", { silent = true }) +vim.keymap.set("n", "<c-j>", "<plug>(cokeline-focus-prev)", { silent = true }) +vim.keymap.set("n", "<c-w>", ":bd!<cr>", { silent = true }) + +-- new file, save file +vim.keymap.set("n", "<c-n>", ":enew<cr>", { silent = true }) +vim.keymap.set("n", "<c-s>", ":update<cr>", { silent = false }) + +-- splits +vim.keymap.set("n", "ss", ":split<cr>", { silent = true }) +vim.keymap.set("n", "sv", ":vsplit<cr>", { silent = true }) +vim.keymap.set("n", "sc", ":close<cr>", { silent = true }) +vim.keymap.set("n", "sh", "<c-w>h", { silent = true }) +vim.keymap.set("n", "sj", "<c-w>j", { silent = true }) +vim.keymap.set("n", "sk", "<c-w>k", { silent = true }) +vim.keymap.set("n", "sl", "<c-w>l", { silent = true }) + +-- don't let fugitive remap our s key so wo maintain our split navigation +vim.api.nvim_create_autocmd("FileType", { + pattern = "fugitive", + callback = function() + vim.keymap.set("n", "s", "", { buffer = true, silent = true }) + end, +}) + +-- deal w/ word wrap (treat wrapped lines as their own) +vim.keymap.set("n", "k", "v:count == 0 ? 'gk' : 'k'", { expr = true, silent = true }) +vim.keymap.set("n", "j", "v:count == 0 ? 'gj' : 'j'", { expr = true, silent = true }) + +-- toggle highlight +vim.keymap.set("n", "<Left>", ":set hls!<cr>", { silent = true }) + +-- toggle file explorer +vim.keymap.set("n", "<c-b>", ":NvimTreeToggle<cr>") + +-- toggle highlight +vim.keymap.set("n", "<a-z>", ":set wrap!<cr>", { silent = true }) + +-- telescope +vim.keymap.set("n", "<c-p>", ":Telescope find_files<cr>") +vim.keymap.set("n", "<c-f>", ":Telescope live_grep<cr>") +vim.keymap.set("n", "<c-h>", ":Telescope buffers<cr>") +vim.keymap.set("n", "<leader>s", ":Telescope possession list<cr>") + +-- markdown preview +vim.keymap.set("n", "<leader>mp", ":MarkdownPreview<cr>") + +-- colorcolumn toggle +vim.keymap.set("n", "<a-c>", function() + vim.o.colorcolumn = (vim.o.colorcolumn == "") and "80" or (vim.o.colorcolumn == "80") and "120" or "" +end) + +-- format buffer w/ formatter.nvim +vim.keymap.set("n", "<leader>ff", ":Format<cr>") + +-- format selection w/ formatter.nvim +vim.keymap.set("v", "<leader>ff", ":Format<cr>") + +-- place filepath in clipboard +vim.keymap.set('n', '<leader>fp', "<cmd>let @+ = expand('%')<cr>", {noremap = true, silent = true}) + +-- git +vim.keymap.set("n", "<leader>gg", ":Git<cr>", { desc = "git: fugitive interface" }) +vim.keymap.set("n", "<leader>gb", ":Git blame<cr>", { desc = "git: blame" }) +vim.keymap.set("n", "<leader>gaf", ":Gitsigns stage_buffer<cr>", { desc = "git: stage this entire buffer" }) +vim.keymap.set("n", "<leader>grf", ":Gitsigns reset_buffer<cr>", { desc = "git: reset buffer" }) +vim.keymap.set({ "n", "v" }, "<leader>gah", ":Gitsigns stage_hunk<cr>", { desc = "git: stage this hunk" }) +vim.keymap.set({ "n", "v" }, "<leader>guh", ":Gitsigns undo_stage_hunk<cr>", { desc = "git: undo stage this hunk" }) +vim.keymap.set({ "n", "v" }, "<leader>grh", ":Gitsigns reset_hunk<cr>", { desc = "git: reset this hunk" }) +vim.keymap.set("n", "<leader>gph", ":Gitsigns preview_hunk<cr>", { desc = "git: preview this hunk" }) +vim.keymap.set("n", "<leader>gn", ":Gitsigns next_hunk<cr>", { desc = "git: browse to next hunk" }) +vim.keymap.set("n", "<leader>gp", ":Gitsigns prev_hunk<cr>", { desc = "git: browse to previous hunk" }) + +-- vimux +vim.keymap.set("n", "<leader>tt", ":VimuxTogglePane<cr>", { silent = true }) + +-- vimux executors +vim.keymap.set("n", "<leader>tr", ':echo "can\'t execute this file"<cr>', { desc = "vimux: run this file" }) +vim.api.nvim_create_augroup("vimux", { clear = true }) +local vimuxtable = { + ["php"] = "php", + ["python"] = "python", + ["go"] = "go run", + ["sh"] = "bash", + ["javascript"] = "node", + ["typescript"] = "node", + ["rust"] = "rust-script", +} +for ft, exec in pairs(vimuxtable) do + vim.api.nvim_create_autocmd("filetype", { + group = "vimux", + pattern = ft, + callback = function() + vim.api.nvim_buf_set_keymap( + 0, + "n", + "<leader>tr", + ':VimuxRunCommand "' .. exec .. " " .. vim.fn.expand("%:p") .. '"<cr>', + { noremap = true } + ) + end, + }) +end + +-- highlight on yank +local highlight_group = vim.api.nvim_create_augroup("YankHighlight", { clear = true }) +vim.api.nvim_create_autocmd("TextYankPost", { + callback = function() + vim.highlight.on_yank() + end, + group = highlight_group, + pattern = "*", +}) + +-- send visual selections as commands to execute in tmux pane +-- todo: turn this into lua, C-U wouldn't work w/ keymap.set for some reason +-- You can add range = true in the options argument of nvim_create_user_command, it will avoid an error if the user leaves the range in the command. +-- + +vim.cmd([[ +vn <silent> <leader>ts :<C-U>VimuxRunCommand(VisualSelection())<cr> +function! VisualSelection() + let [line_start, column_start] = getpos("'<")[1:2] + let [line_end, column_end] = getpos("'>")[1:2] + let lines = getline(line_start, line_end) + if len(lines) == 0 + return '' + endif + let lines[-1] = lines[-1][: column_end - (&selection == 'inclusive' ? 1 : 2)] + let lines[0] = lines[0][column_start - 1:] + return join(lines, "\n") +endfunction]]) + +-- send entire current line as command to execute in tmux pane, if line ends in \ then send next line too +vim.keymap.set("n", "<leader>tl", function() + local line = vim.fn.getline(".") + vim.cmd("VimuxRunCommand '" .. line .. "'") +end, { desc = "vimux: execute visual selection as shell command" }) + +-- todo +vim.keymap.set("n", "]t", function() + require("todo-comments").jump_next() +end) +vim.keymap.set("n", "[t", function() + require("todo-comments").jump_prev() +end) + +-- find digit + +vim.keymap.set("n", "<leader>fd", function() + vim.fn.search("\\d\\+") +end) + +-- trouble.nvim +vim.keymap.set("n", "<leader>tt", "<cmd>TroubleToggle workspace_diagnostics<cr>") + +-- bind :W to :write +vim.api.nvim_create_user_command("W", "write", {}) + +vim.keymap.set({ "n", "x", "o" }, "H", "0", { silent = true }) +vim.keymap.set({ "n", "x", "o" }, "L", "$", { silent = true }) + +-- keymap to toggle between relative and absolute line numbers +vim.keymap.set("n", "<c-l>", function() + if vim.o.relativenumber == true then + vim.wo.relativenumber = false + else + vim.wo.relativenumber = true + end +end) + +-- angular file switching +vim.api.nvim_create_autocmd("FileType", { + pattern = "html", + callback = function() + vim.keymap.set("n", "<leader>ngc", ":e %<.ts<cr>", { desc = "angular: switch to component", buffer = true, silent = true }) + vim.keymap.set("n", "<leader>ngs", ":e %<.scss<cr>", { desc = "angular: switch to stylesheet", buffer = true, silent = true }) + end, +}) + +vim.api.nvim_create_autocmd("FileType", { + pattern = "typescript", + callback = function() + vim.keymap.set("n", "<leader>ngt", ":e %<.html<cr>", { desc = "angular: switch to template", buffer = true, silent = true }) + vim.keymap.set("n", "<leader>ngs", ":e %<.scss<cr>", { desc = "angular: switch to stylesheet", buffer = true, silent = true }) + end, +}) + +-- openai +vim.keymap.set("n", "<leader>an", ":GpChatNew<cr>", { desc = "gpt: new chat buffer" }) +vim.keymap.set("n", "<leader>ap", ":GpChatToggle<cr>", { desc = "gpt: chat popup" }) +vim.keymap.set("n", "<leader>as", ":GpChatRespond<cr>", { desc = "gpt: send message" }) + +vim.keymap.set("v", "<leader>an", ":<C-u>'<,'>GpChatNew<cr>", { desc = "gpt: new chat buffer" }) +vim.keymap.set("v", "<leader>ap", ":<C-u>'<,'>GpChatPaste tabnew<cr>", { desc = "gpt: chat paste" }) +vim.keymap.set("v", "<leader>ar", ":<C-u>'<,'>GpRewrite<cr>", { desc = "gpt: rewrite selection" }) diff --git a/.config/nvim/lua/rj1/lsp.lua b/.config/nvim/lua/rj1/lsp.lua @@ -0,0 +1,420 @@ +local function cmp_border(hl_name) + return { + { "🭽", hl_name }, + { "▔", hl_name }, + { "🭾", hl_name }, + { "▕", hl_name }, + { "🭿", hl_name }, + { "▁", hl_name }, + { "🭼", hl_name }, + { "▏", hl_name }, + } +end +-- set lsp diagnostic icons for gutter +local signs = { Error = "󰅗 ", Warn = "󰅉 ", Information = "󰅍 ", Hint = "󰌵 " } +for type, icon in pairs(signs) do + local hl = "DiagnosticSign" .. type + vim.fn.sign_define(hl, { text = icon, texthl = hl, numhl = "" }) +end + +-- this runs when a buffer is using an lsp +local border = { + { "🭽", "FloatBorder" }, + { "▔", "FloatBorder" }, + { "🭾", "FloatBorder" }, + { "▕", "FloatBorder" }, + { "🭿", "FloatBorder" }, + { "▁", "FloatBorder" }, + { "🭼", "FloatBorder" }, + { "▏", "FloatBorder" }, +} +local on_attach = function(_, buffer) + -- goto definition + vim.keymap.set("n", "gd", vim.lsp.buf.definition, { buffer = buffer }) + + -- references + vim.keymap.set("n", "gr", vim.lsp.buf.references, { buffer = buffer }) + + -- hover code signature + vim.keymap.set("n", "K", vim.lsp.buf.hover, { buffer = buffer }) + + -- diagnostic keymaps + vim.keymap.set("n", "[e", vim.diagnostic.goto_prev, { buffer = buffer }) + vim.keymap.set("n", "]e", vim.diagnostic.goto_next, { buffer = buffer }) + + -- refactor + vim.keymap.set("n", "<leader>rv", vim.lsp.buf.rename, { buffer = buffer }) + + -- lsp restart + vim.keymap.set("n", "<leader>lr", ":LspRestart<cr>", { buffer = buffer }) + + -- disable inline error messages + vim.diagnostic.config({ virtual_text = false }) +end + +local orig_util_open_floating_preview = vim.lsp.util.open_floating_preview +function vim.lsp.util.open_floating_preview(contents, syntax, opts, ...) + opts = opts or {} + opts.border = opts.border or border + return orig_util_open_floating_preview(contents, syntax, opts, ...) +end + +-- nvim-cmp supports additional completion capabilities +local capabilities = require("cmp_nvim_lsp").default_capabilities() + +-- setup mason so it can manage installing lsps for us +require("mason").setup() + +local nvim_lsp = require("lspconfig") +local lspconfig = require("mason-lspconfig") + +-- manual lsp configuration (not available via mason) +nvim_lsp["dartls"].setup({ + on_attach = on_attach, + capabilities = capabilities, +}) + +-- tell mason to ensure these language servers are installed +lspconfig.setup({ + ensure_installed = { + "pyright", + "tsserver", + "eslint", + "intelephense", + "cssls", + "html", + "emmet_ls", + "vuels", + "lua_ls", + }, +}) + +-- this configures all language servers installed via mason +-- we can define optional settings for each one here if desired +lspconfig.setup_handlers({ + function(server_name) + nvim_lsp[server_name].setup({ + on_attach = on_attach, + capabilities = capabilities, + }) + end, + -- lua language server settings + ["lua_ls"] = function() + nvim_lsp.lua_ls.setup({ + on_attach = on_attach, + capabilities = capabilities, + root_dir = function() + return vim.loop.cwd() + end, + settings = { + Lua = { + runtime = { + version = "LuaJIT", + }, + diagnostics = { + globals = { "vim" }, + }, + telemetry = { + enable = false, + }, + }, + }, + }) + end, + -- javascript language server settings + ["tsserver"] = function() + nvim_lsp.tsserver.setup({ + on_attach = on_attach, + capabilities = capabilities, + root_dir = function() + return vim.loop.cwd() + end, + init_options = { + preferences = { + disableSuggestions = true, + }, + }, + }) + end, + -- php language server settings + ["intelephense"] = function() + nvim_lsp.intelephense.setup({ + on_attach = on_attach, + capabilities = capabilities, + init_options = { + globalStoragePath = os.getenv("HOME") .. "/.local/state/intelephense", + }, + settings = { + intelephense = { + environment = { + phpVersion = 8.3, + }, + stubs = { + "apache", + "bcmath", + "bz2", + "calendar", + "com_dotnet", + "Core", + "ctype", + "curl", + "date", + "dba", + "dom", + "enchant", + "exif", + "FFI", + "fileinfo", + "filter", + "fpm", + "ftp", + "gd", + "gettext", + "gmp", + "hash", + "iconv", + "imap", + "imagick", + "intl", + "json", + "ldap", + "libxml", + "mbstring", + "meta", + "mysqli", + "oci8", + "odbc", + "openssl", + "pcntl", + "pcre", + "PDO", + "pdo_ibm", + "pdo_mysql", + "pdo_pgsql", + "pdo_sqlite", + "pgsql", + "Phar", + "posix", + "pspell", + "readline", + "Reflection", + "session", + "shmop", + "SimpleXML", + "snmp", + "soap", + "sockets", + "sodium", + "SPL", + "sqlite3", + "standard", + "superglobals", + "sysvmsg", + "sysvsem", + "sysvshm", + "tidy", + "tokenizer", + "xml", + "xmlreader", + "xmlrpc", + "xmlwriter", + "xsl", + "Zend OPcache", + "zip", + "zlib", + "wordpress", + "random", + }, + }, + }, + }) + end, + -- python language server settings + ["pyright"] = function() + nvim_lsp.pyright.setup({ + on_attach = on_attach, + capabilities = capabilities, + settings = { + python = { + analysis = { + typeCheckingMode = "off", + }, + }, + }, + before_init = function(_, config) + local stubPath = vim.fn.stdpath("data") .. "lazy" .. "python-type-stubs" + config.settings.python.analysis.stubPath = stubPath + end, + }) + end, + -- html language server settings + ["html"] = function() + nvim_lsp.html.setup({ + on_attach = on_attach, + capabilities = capabilities, + settings = { + css = { + lint = { + validProperties = {}, + }, + }, + }, + }) + end, + -- rust language server settings + ["rust_analyzer"] = function() + nvim_lsp.rust_analyzer.setup({ + on_attach = on_attach, + capabilities = capabilities, + }) + end, +}) + +local kind_icons = { + Text = "", + Method = "󰆧", + Function = "󰊕", + Constructor = "", + Field = "󰇽", + Variable = "󰂡", + Class = "󰠱", + Interface = "", + Module = "", + Property = "󰜢", + Unit = "", + Value = "󰎠", + Enum = "", + Keyword = "󰌋", + Snippet = "", + Color = "󰏘", + File = "󰈙", + Reference = "", + Folder = "󰉋", + EnumMember = "", + Constant = "󰏿", + Struct = "", + Event = "", + Operator = "󰆕", + TypeParameter = "󰅲", +} + +require("luasnip.loaders.from_vscode").lazy_load() +require("luasnip").filetype_extend("javascript", { "javascriptreact" }) +require("luasnip").filetype_extend("typescript", { "javascript" }) +local cmp = require("cmp") +local luasnip = require("luasnip") +cmp.setup({ + preselect = cmp.PreselectMode.None, + formatting = { + fields = { "kind", "abbr", "menu" }, + format = function(entry, vim_item) + local icon = kind_icons[vim_item.kind] + local text = vim_item.kind + text = text .. string.rep(" ", 10 - #text) + text = string.lower(text) + + local source = ({ + nvim_lsp = "lsp", + luasnip = "luasnip", + path = "path", + buffer = "buffer", + tmux = "tmux", + })[entry.source.name] + + vim_item.kind = icon + vim_item.menu = text .. (source and " " .. source or "") + + return vim_item + end, + }, + snippet = { + expand = function(args) + luasnip.lsp_expand(args.body) + end, + }, + mapping = cmp.mapping.preset.insert({ + ["<c-u>"] = cmp.mapping.scroll_docs(-4), + ["<c-d>"] = cmp.mapping.scroll_docs(4), + ["<c-space>"] = cmp.mapping.complete({ reason = cmp.ContextReason.Auto }), + ["<cr>"] = cmp.mapping.confirm({ behavior = cmp.ConfirmBehavior.Replace, select = false }), + ["<tab>"] = cmp.mapping(function(fallback) + if cmp.visible() then + cmp.select_next_item() + elseif luasnip.expand_or_jumpable() then + luasnip.expand_or_jump() + else + fallback() + end + end, { "i", "s" }), + ["<s-tab>"] = cmp.mapping(function(fallback) + if cmp.visible() then + cmp.select_prev_item() + elseif luasnip.jumpable(-1) then + luasnip.jump(-1) + else + fallback() + end + end, { "i", "s" }), + }), + sources = { + { name = "luasnip" }, + { name = "nvim_lsp" }, + { name = "nvim_lsp_signature_help" }, + { name = "path" }, + { name = "buffer" }, + { name = "tmux" }, + }, + window = { + completion = { + border = cmp_border("CmpBorder"), + winhighlight = "Normal:CmpPmenu,CursorLine:PmenuSel,Search:None", + }, + documentation = { + border = cmp_border("CmpBorder"), + winhighlight = "Normal:CmpPmenu,CursorLine:PmenuSel,Search:None", + }, + }, +}) + +-- `/` cmdline setup. +cmp.setup.cmdline("/", { + formatting = { + fields = { "abbr" }, + format = function(entry, vim_item) + return vim_item + end, + }, + mapping = cmp.mapping.preset.cmdline(), + sources = { + { name = "buffer" }, + }, +}) + +-- `:` cmdline setup. +cmp.setup.cmdline(":", { + formatting = { + fields = { "abbr" }, + format = function(entry, vim_item) + return vim_item + end, + }, + mapping = cmp.mapping.preset.cmdline(), + sources = cmp.config.sources({ + { name = "path" }, + }, { + { + name = "cmdline", + option = { + ignore_cmds = { "Man", "!" }, + }, + }, + }), +}) + +-- autopairs +require("nvim-autopairs").setup({ + disable_filetype = { "TelescopePrompt", "vim" }, +}) + +local cmp_autopairs = require("nvim-autopairs.completion.cmp") +cmp.event:on("confirm_done", cmp_autopairs.on_confirm_done()) diff --git a/.config/nvim/lua/rj1/lualine.lua b/.config/nvim/lua/rj1/lualine.lua @@ -0,0 +1,118 @@ +local colors = require("onedarkpro.helpers").get_colors() + +local function diff_source() + local gitsigns = vim.b.gitsigns_status_dict + if gitsigns then + return { + added = gitsigns.added, + modified = gitsigns.changed, + removed = gitsigns.removed, + } + end +end + +local function random_icon() + local symbols = { "󰊠", "󰊮", "󰣐", "󰊓", "󰊔", "󰇥" } + local randomIndex = math.random(1, #symbols) + return symbols[randomIndex] +end + +local function lsp_servers() + local servers = {} + for _, server in pairs(vim.lsp.buf_get_clients()) do + table.insert(servers, server.name) + end + return table.concat(servers, ", ") +end + +-- lualine +require("lualine").setup({ + options = { + icons_enabled = true, + theme = "onedark", + section_separators = { left = "", right = "" }, + component_separators = { left = "", right = "" }, + disabled_filetypes = { "NvimTree" }, + }, + sections = { + lualine_a = { + { + "mode", + -- 󰊠 + icon = random_icon(), + color = { gui = "bold" }, + fmt = string.lower, + }, + }, + lualine_c = { + { + "branch", + color = { fg = colors.fg }, + }, + { + "diff", + source = diff_source, + diff_color = { + added = { fg = colors.green }, + modified = { fg = colors.yellow }, + removed = { fg = colors.red }, + }, + symbols = { added = " ", modified = "⊡ ", removed = " " }, + }, + { + "diagnostics", + symbols = { + error = "󰅗 ", + warn = "󰅉 ", + info = "󰅍 ", + hint = "󰌵 ", + }, + }, + { + lsp_servers, + icon = "", + + color = function() + if vim.lsp.util.get_progress_messages()[1] then + return { fg = colors.red } + end + return { fg = colors.purple } + end, + }, + --[[ { + Codeium_status, + icon = "󰧑 ai:", + + color = function() + if Codeium_status() == "on" then + return { fg = colors.green } + else + return { fg = colors.red } + end + end, + }, ]] + }, + lualine_b = { + { + Session_name, + icon = "", + color = { fg = colors.fg }, + }, + }, + lualine_x = { + { + "encoding", + color = { fg = colors.purple }, + + }, + { + "fileformat", + }, + { + "filetype", + color = { fg = colors.blue }, + }, + }, + lualine_y = { { "progress", color = { fg = colors.fg }, fmt = string.lower } }, + }, +}) diff --git a/.config/nvim/lua/rj1/notes.lua b/.config/nvim/lua/rj1/notes.lua @@ -0,0 +1,134 @@ +local notes_dir = vim.fn.expand("$HOME/notes") +local work_notes_dir = vim.fn.expand("$HOME/notes/work") +vim.api.nvim_create_autocmd({ "bufenter", "bufleave" }, { + pattern = { notes_dir .. "/**/*.md" }, + command = "setl noswapfile noundofile nobackup viminfo=", +}) + +--[[ require("mkdnflow").setup({ + perspective = { + priority = "current", + }, + links = { + transform_explicit = function(text) + text = text:gsub(" ", "-") + text = text:lower() + return text + end, + }, +}) ]] + +-- create daily journal entry command +local function daily_file(directory) + local date_string = os.date("%Y-%m-%d") + local dir = vim.fn.expand(directory .. "/daily/") + return dir .. date_string .. ".md" +end + +local function create_or_open_file(directory, template_path) + local file_path = daily_file(directory) + local f = io.open(file_path, "r") + if f ~= nil then + f:close() + else + local dir = vim.fn.expand(directory .. "/daily/") + local template = io.open(dir .. template_path, "r") + if template == nil then + vim.cmd("echo 'no daily template found in " .. dir .. "'") + return + end + local template_content = template:read("*all") + template:close() + local new_file = io.open(file_path, "w") + if new_file ~= nil then + new_file:write("# " .. os.date("%Y-%m-%d") .. "\n\n") + new_file:write(template_content) + new_file:close() + end + end + vim.cmd("edit " .. file_path) +end + +-- create daily journal entry command +vim.api.nvim_create_user_command("Today", function() + create_or_open_file(notes_dir, "template.md") +end, {}) + +vim.api.nvim_create_user_command("Yesterday", function() + local date_string = os.date("%Y-%m-%d", os.time() - 86400) + local file_path = vim.fn.expand(notes_dir .. "/daily/" .. date_string .. ".md") + if vim.fn.filereadable(file_path) == 1 then + vim.cmd("edit " .. file_path) + else + vim.cmd("echo 'no daily entry for " .. date_string .. "'") + end +end, {}) + +vim.api.nvim_create_user_command("WorkToday", function() + create_or_open_file(work_notes_dir, "template.md") +end, {}) + +vim.api.nvim_create_user_command("WorkYesterday", function() + local date_string = os.date("%Y-%m-%d", os.time() - 86400) + local file_path = vim.fn.expand(work_notes_dir .. "/daily/" .. date_string .. ".md") + if vim.fn.filereadable(file_path) == 1 then + vim.cmd("edit " .. file_path) + else + vim.cmd("echo 'no daily entry for " .. date_string .. "'") + end +end, {}) + +function Daily_status(directory) + if vim.fn.filereadable(daily_file(directory)) == 1 then + return "" + else + return "pending" + end +end + +-- function to get file path for tomorrow's file +local function tomorrow_file(directory, template_path) + local date_string = os.date("%Y-%m-%d", os.time() + 86400) + local dir = vim.fn.expand(directory .. "/daily/") + return dir .. date_string .. ".md" +end + +-- create or open file for tomorrow's entry +local function create_or_open_tomorrow_file(directory, template_path) + local file_path = tomorrow_file(directory, template_path) + local f = io.open(file_path, "r") + if f ~= nil then + f:close() + else + local dir = vim.fn.expand(directory .. "/daily/") + local template = io.open(dir .. template_path, "r") + if template == nil then + vim.cmd("echo 'no daily template found in " .. dir .. "'") + return + end + local template_content = template:read("*all") + template:close() + local new_file = io.open(file_path, "w") + if new_file then + new_file:write("# " .. os.date("%Y-%m-%d", os.time() + 86400) .. "\n\n") + new_file:write(template_content) + new_file:close() + end + end + vim.cmd("edit " .. file_path) +end + +-- create WorkTomorrow command +vim.api.nvim_create_user_command("WorkTomorrow", function() + create_or_open_tomorrow_file(work_notes_dir, "template.md") +end, {}) + +vim.api.nvim_create_user_command("QuickNote", function(opts) + local note = opts.args or nil + local file_path = vim.fn.expand(notes_dir .. "/quick-notes.md") + local f = io.open(file_path, "a") + if f ~= nil then + f:write("\n\n" .. note .. "\n\n") + f:close() + end +end, { nargs = "?" }) diff --git a/.config/nvim/lua/rj1/plugins.lua b/.config/nvim/lua/rj1/plugins.lua @@ -0,0 +1,157 @@ +local lazypath = vim.fn.stdpath("data") .. "/lazy/lazy.nvim" +if not vim.loop.fs_stat(lazypath) then + vim.fn.system({ + "git", + "clone", + "--filter=blob:none", + "https://github.com/folke/lazy.nvim.git", + "--branch=stable", + lazypath, + }) +end +vim.opt.rtp:prepend(lazypath) + +require("lazy").setup({ + "olimorris/onedarkpro.nvim", + -- lsp + "neovim/nvim-lspconfig", + "williamboman/mason.nvim", + "williamboman/mason-lspconfig.nvim", + { "hrsh7th/nvim-cmp", dependencies = "hrsh7th/cmp-nvim-lsp" }, + { "hrsh7th/cmp-path", dependencies = "hrsh7th/nvim-cmp" }, + { "hrsh7th/cmp-buffer", dependencies = "hrsh7th/nvim-cmp" }, + { "hrsh7th/cmp-nvim-lsp-signature-help", dependencies = "hrsh7th/nvim-cmp" }, + { "hrsh7th/cmp-cmdline", dependencies = "hrsh7th/nvim-cmp" }, + { "L3MON4D3/LuaSnip", dependencies = "saadparwaiz1/cmp_luasnip" }, + "andersevenrud/cmp-tmux", + "microsoft/python-type-stubs", + "rafamadriz/friendly-snippets", + + -- ai + { + "Exafunction/codeium.vim", + config = function() + vim.keymap.set("i", "<Right>", function() + return vim.fn["codeium#Accept"]() + end, { expr = true }) + vim.keymap.set("i", "<a-]>", function() + return vim.fn["codeium#CycleCompletions"](1) + end, { expr = true }) + vim.keymap.set("i", "<a-[>", function() + return vim.fn["codeium#CycleCompletions"](-1) + end, { expr = true }) + vim.keymap.set("i", "<a-x>", function() + return vim.fn["codeium#Clear"]() + end, { expr = true }) + end, + }, + "Robitx/gp.nvim", + + -- syntax / editing + { "nvim-treesitter/nvim-treesitter", build = ":TSUpdate" }, + "windwp/nvim-ts-autotag", + "windwp/nvim-autopairs", + "mhartington/formatter.nvim", + "Vimjas/vim-python-pep8-indent", + "jidn/vim-dbml", + "b3nj5m1n/kommentary", + "JoosepAlviste/nvim-ts-context-commentstring", + "kylechui/nvim-surround", + "leafOfTree/vim-matchtag", + "folke/todo-comments.nvim", + "junegunn/vim-easy-align", + "mbbill/undotree", + "jwalton512/vim-blade", + "NMAC427/guess-indent.nvim", + + -- git + { "lewis6991/gitsigns.nvim", dependencies = "nvim-lua/plenary.nvim" }, + "tpope/vim-fugitive", + "tpope/vim-rhubarb", + + -- visual + "olimorris/onedarkpro.nvim", + { "nvim-lualine/lualine.nvim", dependencies = "kyazdani42/nvim-web-devicons" }, + { "noib3/nvim-cokeline", dependencies = "kyazdani42/nvim-web-devicons" }, + { "nvim-tree/nvim-tree.lua", dependencies = "kyazdani42/nvim-web-devicons" }, + "karb94/neoscroll.nvim", + { "iamcco/markdown-preview.nvim", build = "cd app && npm install" }, + "uga-rosa/ccc.nvim", + "tversteeg/registers.nvim", + "jinh0/eyeliner.nvim", + "folke/which-key.nvim", + + -- "lukas-reineke/indent-blankline.nvim", + "folke/trouble.nvim", + + -- { "jakewvincent/mkdnflow.nvim", rocks = "luautf8" }, + + -- telescope + { + "nvim-telescope/telescope-fzf-native.nvim", + build = "make", + dependencies = { "nvim-telescope/telescope.nvim", "nvim-lua/plenary.nvim" }, + }, + + -- session management + { "jedrzejboczar/possession.nvim", dependencies = "nvim-lua/plenary.nvim" }, + + -- tmux integration + "preservim/vimux", + + -- Rename/Delete/Chmod/etc + "tpope/vim-eunuch", +}) + +-- kommentary +require("kommentary.config").configure_language("php", { + single_line_comment_string = "//", + multi_line_comment_strings = { "/*", "*/" }, +}) + +-- nvim-surround +require("nvim-surround").setup({}) + +-- nvim-tree +require("nvim-tree").setup({ + on_attach = function(bufnr) + local api = require("nvim-tree.api") + api.config.mappings.default_on_attach(bufnr) + vim.keymap.set("n", "s", "", { buffer = bufnr }) + vim.keymap.del("n", "s", { buffer = bufnr }) + end, + + view = { + relativenumber = true, + side = "right", + width = 70, + }, +}) + +-- smooth scrolling +require("neoscroll").setup({ + hide_cursor = false, + mappings = { + "<C-u>", + "<C-d>", + }, +}) + +-- todo highlighter +require("todo-comments").setup({}) + +-- registers preview popup +require("registers").setup({}) + +-- hotreload flutter on save +vim.api.nvim_create_autocmd("bufwritepost", { + pattern = "*.dart", + command = "silent execute '!kill -SIGUSR1 $(pgrep -f \"[f]lutter_tool.*run\")'", +}) + +-- eyeliner (quick-scope lua replacement) +require("eyeliner").setup({ + highlight_on_key = true, +}) + +require("guess-indent").setup({}) diff --git a/.config/nvim/lua/rj1/sessions.lua b/.config/nvim/lua/rj1/sessions.lua @@ -0,0 +1,33 @@ +function Session_name() + return require("possession.session").session_name or "no session" +end + +require("possession").setup({ + session_dir = vim.fn.stdpath("state") .. "/session", + plugins = { + delete_hidden_buffers = false, + }, + autosave = { + current = true, + }, + silent = true, + commands = { + save = "SessionSave", + load = "SessionLoad", + delete = "SessionDelete", + list = "SessionList", + }, + hooks = { + -- rename the tmux window to the name of the session we're loading + before_load = function(name) + return vim.fn.system("tmux rename-window " .. name) + end, + after_load = function(name) + local server_address = "/tmp/nvim-" .. name + if vim.fn.filereadable(server_address) == 1 then + vim.fn.serverstop(server_address) + end + vim.fn.serverstart(server_address) + end, + }, +}) diff --git a/.config/nvim/lua/rj1/settings.lua b/.config/nvim/lua/rj1/settings.lua @@ -0,0 +1,98 @@ +local color = require("onedarkpro.helpers") +require("onedarkpro").setup({ + options = { + cursorline = true, + bold = false, + italic = false, + transparency = true, + }, + highlights = { + Comment = { fg = color.lighten("comment", 15, "onedark") }, + }, +}) + +vim.o.termguicolors = true +vim.cmd("colorscheme onedark") + +-- disable start message +vim.o.shortmess = "I" + +-- relative line numbers +vim.wo.relativenumber = true + +-- mouse mode +vim.o.mouse = "a" + +-- break indent +vim.o.breakindent = true + +-- undo history +vim.o.undofile = true +vim.o.undodir = vim.fn.stdpath("state") .. "/undo" + +-- search settings +vim.o.ignorecase = true +vim.o.smartcase = true +vim.o.hlsearch = true + +-- quick update time +vim.o.updatetime = 100 +vim.wo.signcolumn = "yes" + +-- use system clipboard +vim.o.clipboard = "unnamedplus" + +-- set completeopt to have a better completion experience +vim.o.completeopt = "menuone,noselect" + +-- indenting +vim.o.tabstop = 2 +vim.o.shiftwidth = 2 +vim.o.expandtab = true + +-- word wrap +vim.wo.wrap = false + +-- backup +vim.opt.backup = false +vim.opt.writebackup = false + +-- textwidth +-- vim.o.textwidth = 80 +local textwidth = vim.api.nvim_create_augroup("textwidth", { clear = true }) +vim.api.nvim_create_autocmd("filetype", { + group = textwidth, + pattern = { "html" }, + callback = function() + vim.o.textwidth = 120 + end, +}) + +-- vimux +vim.g.vimuxheight = 32 + +-- open tmux panes in nvim's cwd +vim.api.nvim_create_autocmd("dirchanged", { + pattern = "*", + command = 'call chansend(v:stderr, printf("\\033]7;%s\\033", v:event.cwd))', +}) + +-- toggle relative line numbers when entering/leaving splits +local splitswitch = vim.api.nvim_create_augroup("splitswitch", { clear = true }) + +vim.api.nvim_create_autocmd("WinEnter", { + group = splitswitch, + pattern = "*", + callback = function() + vim.opt.relativenumber = true + end, +}) + +vim.api.nvim_create_autocmd("WinLeave", { + group = splitswitch, + pattern = "*", + callback = function() + vim.opt.relativenumber = false + vim.opt.number = true + end, +}) diff --git a/.config/nvim/lua/rj1/telescope.lua b/.config/nvim/lua/rj1/telescope.lua @@ -0,0 +1,43 @@ +require("telescope").setup({ + pickers = { + find_files = { + previewer = false, + hidden = true, + file_ignore_patterns = { ".git/" }, + }, + buffers = { + previewer = false, + }, + }, + extensions = { + fzf = { + fuzzy = true, + override_generic_sorter = true, + override_file_sorter = true, + case_mode = "smart_case", + }, + }, + defaults = { + borderchars = { "▔", "▕", "▁", "▏", "🭽", "🭾", "🭿", "🭼" }, + prompt_prefix = " 󰍉 ", + entry_prefix = " ", + selection_caret = " 󰅂 ", + layout_config = { + width = 0.8, + height = 0.5, + }, + mappings = { + i = { + -- close telescope by pressing esc only once + ["<esc>"] = require("telescope.actions").close, + ["<c-j>"] = require("telescope.actions").move_selection_next, + ["<c-k>"] = require("telescope.actions").move_selection_previous, + ["<c-u>"] = false, + ["<c-d>"] = false, + }, + }, + }, +}) + +require("telescope").load_extension("fzf") +require("telescope").load_extension("possession") diff --git a/.config/nvim/lua/rj1/treesitter.lua b/.config/nvim/lua/rj1/treesitter.lua @@ -0,0 +1,34 @@ +require("nvim-treesitter.configs").setup({ + ensure_installed = { + "lua", + "go", + "python", + "tsx", + "php", + "html", + "css", + "javascript", + "dart", + }, + highlight = { + enable = true, + disable = { "html", "typescript", "javascript", "bash" }, + }, + indent = { + enable = true, + disable = { "html", "python", "javascript", "typescript" }, + }, + autotag = { + enable = true, + }, +}) + +require('ts_context_commentstring').setup { + enable_autocmd = false, + languages = { + typescript = '// %s', + }, +} + +local parser_config = require("nvim-treesitter.parsers").get_parser_configs() +parser_config.tsx.filetype_to_parsername = { "javascript", "typescript.tsx" } diff --git a/.config/pipewire/pipewire.conf b/.config/pipewire/pipewire.conf @@ -0,0 +1,75 @@ +context.properties = { + link.max-buffers = 16 + core.daemon = true + core.name = pipewire-0 + default.clock.rate = 44100 + default.clock.allowed-rates = [ 44100 ] + default.clock.min-quantum = 16 + vm.overrides = { + default.clock.min-quantum = 1024 + } +} + +context.spa-libs = { + audio.convert.* = audioconvert/libspa-audioconvert + api.alsa.* = alsa/libspa-alsa + api.v4l2.* = v4l2/libspa-v4l2 + api.libcamera.* = libcamera/libspa-libcamera + api.bluez5.* = bluez5/libspa-bluez5 + api.vulkan.* = vulkan/libspa-vulkan + api.jack.* = jack/libspa-jack + support.* = support/libspa-support +} + +context.modules = [ + { name = libpipewire-module-rt + args = { + nice.level = -11 + } + flags = [ ifexists nofail ] + } + + { name = libpipewire-module-protocol-native } + { name = libpipewire-module-profiler } + { name = libpipewire-module-metadata } + { name = libpipewire-module-spa-device-factory } + { name = libpipewire-module-spa-node-factory } + { name = libpipewire-module-client-node } + { name = libpipewire-module-client-device } + { name = libpipewire-module-portal + flags = [ ifexists nofail ] + } + + { name = libpipewire-module-access + args = { + } + } + + { name = libpipewire-module-adapter } + { name = libpipewire-module-link-factory } + { name = libpipewire-module-session-manager } +] + +context.objects = [ + { factory = spa-node-factory + args = { + factory.name = support.node.driver + node.name = Dummy-Driver + node.group = pipewire.dummy + priority.driver = 20000 + } + } + { factory = spa-node-factory + args = { + factory.name = support.node.driver + node.name = Freewheel-Driver + priority.driver = 19000 + node.group = pipewire.freewheel + node.freewheel = true + } + } +] + +context.exec = [ + { path = "/usr/bin/pipewire-pulse" args = "" } +] diff --git a/.config/qutebrowser/config.py b/.config/qutebrowser/config.py @@ -0,0 +1,161 @@ +config.load_autoconfig() +config.source('redirect.py') + +c.tabs.position = 'top' +c.tabs.width = '15%' +c.fonts.default_family = "Hack Nerd Font Mono" +c.fonts.web.family.standard = "Hack Nerd Font Mono" +c.fonts.default_size = '12px' +c.hints.uppercase = True +c.completion.height = '20%' +c.downloads.location.directory = '~/dl' +c.downloads.prevent_mixed_content = False +c.editor.command = ['foot', 'nvim', '{file}'] +c.colors.webpage.darkmode.enabled = True +c.content.javascript.enabled = False +c.content.blocking.method = 'both' +c.auto_save.session = True +c.zoom.default = '100%' + +c.url.start_pages = 'https://paulgo.io' +c.url.default_page = 'https://paulgo.io' +c.url.searchengines = { + 'DEFAULT': 'https://paulgo.io/?q={}', + 'qw' : 'https://lite.qwant.com/?q={}', + 'g' : 'https://google.com/search?q={}', + 'gi' : 'https://www.google.com/search?q={}&source=lnms&tbm=isch', + 'gh' : 'https://github.com/search?q={}', + 'w' : 'https://en.wikipedia.org/?search={}', + 'yt' : 'https://www.youtube.com/results?search_query={}' +} + +c.aliases = { + 'g' : 'open -t g', + 'qw' : 'open -t qw', + 'gi' : 'open -t gi', + 'yt' : 'open -t yt', + 'gh' : 'open -t gh', + 'w' : 'open -t w', + 'private': 'open -p' +} + +c.bindings.commands['normal'] = { + '<shift-k>' : 'tab-next', + '<shift-j>' : 'tab-prev', + '<alt-shift-j>': 'zoom-out', + '<alt-shift-k>': 'zoom-in', + '<alt-shift-h>': 'zoom 100%', + 'd' : 'scroll-page 0 0.5', + 'u' : 'scroll-page 0 -0.5', + '<alt-v>' : 'spawn mpv {url}', + '<shift-u>' : 'hint links yank', + ',v' : 'spawn mpv {url}', + '<shift-i>' : 'hint links spawn mpv {hint-url}', + '<ctrl-b>' : 'config-cycle tabs.show always switching', +} + +# dracula theme - https://github.com/dracula/qutebrowser +palette = { + 'background': '#282a36', + 'background-alt': '#282a36', + 'background-attention': '#181920', + 'border': '#282a36', + 'current-line': '#44475a', + 'selection': '#44475a', + 'foreground': '#f8f8f2', + 'foreground-alt': '#e0e0e0', + 'foreground-attention': '#ffffff', + 'comment': '#6272a4', + 'cyan': '#8be9fd', + 'green': '#50fa7b', + 'orange': '#ffb86c', + 'pink': '#ff79c6', + 'purple': '#bd93f9', + 'red': '#ff5555', + 'yellow': '#f1fa8c' +} + +spacing = { + 'vertical': 5, + 'horizontal': 5 +} + +padding = { + 'top': spacing['vertical'], + 'right': spacing['horizontal'], + 'bottom': spacing['vertical'], + 'left': spacing['horizontal'] +} + +c.colors.completion.category.bg = palette['background'] +c.colors.completion.category.border.bottom = palette['border'] +c.colors.completion.category.border.top = palette['border'] +c.colors.completion.category.fg = palette['foreground'] +c.colors.completion.even.bg = palette['background'] +c.colors.completion.odd.bg = palette['background-alt'] +c.colors.completion.fg = palette['foreground'] +c.colors.completion.item.selected.bg = palette['selection'] +c.colors.completion.item.selected.border.bottom = palette['selection'] +c.colors.completion.item.selected.border.top = palette['selection'] +c.colors.completion.item.selected.fg = palette['foreground'] +c.colors.completion.match.fg = palette['orange'] +c.colors.completion.scrollbar.bg = palette['background'] +c.colors.completion.scrollbar.fg = palette['foreground'] +c.colors.downloads.bar.bg = palette['background'] +c.colors.downloads.error.bg = palette['background'] +c.colors.downloads.error.fg = palette['red'] +c.colors.downloads.stop.bg = palette['background'] +c.colors.downloads.system.bg = 'none' +c.colors.hints.match.fg = '#808080' +c.colors.messages.error.bg = palette['background'] +c.colors.messages.error.border = palette['background-alt'] +c.colors.messages.error.fg = palette['red'] +c.colors.messages.info.bg = palette['background'] +c.colors.messages.info.border = palette['background-alt'] +c.colors.messages.info.fg = palette['comment'] +c.colors.messages.warning.bg = palette['background'] +c.colors.messages.warning.border = palette['background-alt'] +c.colors.messages.warning.fg = palette['red'] +c.colors.prompts.bg = palette['background'] +c.colors.prompts.border = '1px solid ' + palette['background-alt'] +c.colors.prompts.fg = palette['cyan'] +c.colors.prompts.selected.bg = palette['selection'] +c.colors.statusbar.caret.bg = palette['background'] +c.colors.statusbar.caret.fg = palette['orange'] +c.colors.statusbar.caret.selection.bg = palette['background'] +c.colors.statusbar.caret.selection.fg = palette['orange'] +c.colors.statusbar.command.bg = palette['background'] +c.colors.statusbar.command.fg = palette['pink'] +c.colors.statusbar.command.private.bg = palette['background'] +c.colors.statusbar.command.private.fg = palette['foreground-alt'] +c.colors.statusbar.insert.bg = palette['background-attention'] +c.colors.statusbar.insert.fg = palette['foreground-attention'] +c.colors.statusbar.normal.bg = palette['background'] +c.colors.statusbar.normal.fg = palette['foreground'] +c.colors.statusbar.passthrough.bg = palette['background'] +c.colors.statusbar.passthrough.fg = palette['orange'] +c.colors.statusbar.private.bg = palette['background-alt'] +c.colors.statusbar.private.fg = palette['foreground-alt'] +c.colors.statusbar.progress.bg = palette['background'] +c.colors.statusbar.url.error.fg = palette['red'] +c.colors.statusbar.url.fg = palette['foreground'] +c.colors.statusbar.url.hover.fg = palette['cyan'] +c.colors.statusbar.url.success.http.fg = palette['green'] +c.colors.statusbar.url.success.https.fg = palette['green'] +c.colors.statusbar.url.warn.fg = palette['yellow'] +c.statusbar.padding = padding +c.colors.tabs.bar.bg = palette['selection'] +c.colors.tabs.even.bg = palette['selection'] +c.colors.tabs.even.fg = palette['foreground'] +c.colors.tabs.indicator.error = palette['red'] +c.colors.tabs.indicator.start = palette['orange'] +c.colors.tabs.indicator.stop = palette['green'] +c.colors.tabs.indicator.system = 'none' +c.colors.tabs.odd.bg = palette['selection'] +c.colors.tabs.odd.fg = palette['foreground'] +c.colors.tabs.selected.even.bg = palette['background'] +c.colors.tabs.selected.even.fg = palette['foreground'] +c.colors.tabs.selected.odd.bg = palette['background'] +c.colors.tabs.selected.odd.fg = palette['foreground'] +c.tabs.favicons.scale = 1 + diff --git a/.config/qutebrowser/redirect.py b/.config/qutebrowser/redirect.py @@ -0,0 +1,23 @@ +import operator, re, typing +from urllib.parse import urljoin +from qutebrowser.api import interceptor, message +from PyQt5.QtCore import QUrl + +# any return value other than a literal 'false' means we redirected +REDIRECT_MAP = { + "reddit.com": operator.methodcaller('setHost', 'libredd.it'), + "www.reddit.com": operator.methodcaller('setHost', 'libredd.it'), +} + +def int_fn(info: interceptor.Request): + """Block the given request if necessary.""" + if (info.resource_type != interceptor.ResourceType.main_frame or + info.request_url.scheme() in {"data", "blob"}): + return + url = info.request_url + redir = REDIRECT_MAP.get(url.host()) + if redir is not None and redir(url) is not False: + message.info("Redirecting to " + url.toString()) + info.redirect(url) + +interceptor.register(int_fn) diff --git a/.config/rofi/config.rasi b/.config/rofi/config.rasi @@ -0,0 +1,8 @@ +configuration { + display: "HDMI-A-1"; + font: "Hack Nerd Font Mono 15"; + modi: "run,ssh,combi"; + terminal: "/usr/bin/alacritty"; + display-run: "> "; +} +@theme "onedark" diff --git a/.config/rofi/onedark.rasi b/.config/rofi/onedark.rasi @@ -0,0 +1,78 @@ +/* + * ROFI One Dark + * + * Based on OneDark.vim (https://github.com/joshdick/onedark.vim) + * + * Author: Benjamin Stauss + * User: me-benni + * + */ + +* { + spacing: 0; + background-color: transparent; + text-color: #dfdfdf; +} + +window { + background-color: #282c34ee; + width: 1000px; + height: 1000px; + border: 1px; + border-color: #98c379; +} + +mainbox { + padding: 1% 1%; +} + +inputbar { + margin: 0px 0px 10px 0px; + children: [prompt, textbox-prompt-colon, entry, case-indicator]; +} + +prompt { + text-color: #eb6e67; +} + +textbox-prompt-colon { + expand: false; + str: ""; + text-color: #b2b2b2; +} + +listview { + spacing: 2px; + dynamic: true; + scrollbar: false; +} + +element { + padding: 2px; + text-color: #b2b2b2; + highlight: bold #95ee8f; + border-radius: 3px; +} + +element selected { + background-color: #50536b; + text-color: #dfdfdf; +} + +element urgent, element selected urgent { + text-color: #eb6e67; +} + +message { + text-color: #eb6e67; + padding: 5px; + margin: 0 0 10px 0; + border: 1px; + border-color: #95ee8f; +} + +button selected { + padding: 5px; + border-radius: 3px; + background-color: #50536b; +} diff --git a/.config/swappy/config b/.config/swappy/config @@ -0,0 +1,7 @@ +[Default] +save_dir=$HOME/img/sshot +save_filename_format=swappy-%Y%m%d-%H%M%S.png +show_panel=true +line_size=5 +text_size=20 +text_font=Hack Nerd Font Mono diff --git a/.config/sway/config##hostname.empy b/.config/sway/config##hostname.empy @@ -0,0 +1,213 @@ +# display settings +output eDP-1 pos 3840 1080 res 1920x1080 scale 1 +output DP-5 pos 0 0 res 3840x2160 scale 1 +output DP-6 pos 0 0 res 3840x2160 scale 1 + +# start xdg-desktop-portal (screen sharing) +exec --no-startup-id /usr/lib/xdg-desktop-portal -r & /usr/lib/xdg-desktop-portal-wlr + +# dynamic wallpaper +exec wallpaper + +# winkey +set $mod mod4 +set $left h +set $down j +set $up k +set $right l + +# keeb settings +input "type:keyboard" { + xkb_layout us + repeat_delay 250 + repeat_rate 60 +} + +# make trackpoint tolerable +input "2:10:TPPS/2_IBM_TrackPoint" { + accel_profile flat + pointer_accel 1 +} + +# disable touchpad +input "1739:0:Synaptics_TM3276-022" { + events disabled +} + +# sway theme +set $font 'Hack Nerd Font Mono' +set $cursor_theme 'Neutral' +set $cursor_size 16 +seat seat0 hide_cursor 5000 +seat seat0 xcursor_theme $cursor_theme $cursor_size + +exec_always { + gsettings set org.gnome.desktop.interface cursor-theme $cursor_theme + gsettings set org.gnome.desktop.interface cursor-size $cursor_size + gsettings set org.gnome.desktop.interface font-name $font +} + +font $font 12 +titlebar_padding 2 +default_border pixel 5 +gaps inner 5 + +# class border bg text indicator child_border +client.focused #98c379 #98c379 #000000 #98c379 #98c379 +client.focused_inactive #333333 #222222 #ffffff #484e50 #3e4452 +client.unfocused #333333 #222222 #eeeeee #292d2e #3e4452 +client.urgent #2f343a #900000 #ffffff #900000 #900000 +client.placeholder #000000 #0c0c0c #ffffff #000000 #0c0c0c + +# yambar +# exec yambar + +# restart bar +# bindsym $mod+shift+y exec "killall yambar; yambar" + +# waybar +exec waybar +bindsym $mod+shift+y exec "killall waybar; waybar" + +# lock settings +set $sleep screen-lock +bindsym ctrl+alt+delete exec $sleep + +exec swayidle timeout 900 $sleep \ + timeout 1200 'swaymsg "output * dpms off"' \ + resume 'swaymsg "output * dpms on"' + +# brightness control +bindsym XF86MonBrightnessUp exec xbacklight -inc 10 +bindsym XF86MonBrightnessDown exec xbacklight -dec 10 +bindsym $mod+shift+equal exec xbacklight -inc 10 +bindsym $mod+shift+minus exec xbacklight -dec 10 + +# terminal +bindsym $mod+return exec launch-terminal + +# kill focused window +bindsym $mod+shift+q kill + +# rofi +bindsym $mod+space exec rofi -show run + +# rofi+pass +bindsym $mod+p exec tessen + +# sshot +bindsym $mod+s exec grim -g "$(slurp)" - | swappy -f - + +# screencast +bindsym $mod+shift+s exec screen-record + +# snippet +bindsym $mod+n exec snippet-rofi + +# color picker +bindsym $mod+c exec hyprpicker --autocopy + +# toggle horizontal/vertical +bindsym $mod+d layout toggle split +bindsym $mod+apostrophe splitv +bindsym $mod+semicolon splith + +# toggle layout +bindsym $mod+t layout toggle all + +# fullscreen +bindsym $mod+f fullscreen + +# floating/tiled toggle +bindsym $mod+shift+f floating toggle + +# drag floating windows +floating_modifier $mod normal + +# notifications +exec mako + +# audio +set $WOBSOCK $XDG_RUNTIME_DIR/wob.sock +exec rm -f $WOBSOCK && mkfifo $WOBSOCK && tail -f $WOBSOCK | wob + +# volume control +bindsym $mod+equal exec pactl set-sink-volume 0 +2% && pactl get-sink-volume 0 | awk '{print $12}' | tr -d '%' | head -1 > $WOBSOCK +bindsym $mod+minus exec pactl set-sink-volume 0 -2% && pactl get-sink-volume 0 | awk '{print $12}' | tr -d '%' | head -1 > $WOBSOCK +bindsym $mod+0 exec pactl set-sink-mute 0 toggle && pactl get-sink-volume 0 | awk '{print $12}' | tr -d '%' | head -1 > $WOBSOCK +bindsym $mod+shift+0 exec pactl set-source-mute 0 toggle + + +# media control +bindsym $mod+m exec rofimpd +bindsym $mod+slash exec mpc toggle +bindsym $mod+period exec mpc next +bindsym $mod+comma exec mpc prev +bindsym $mod+shift+comma exec mpc seek -5 +bindsym $mod+shift+period exec mpc seek +5 + +# reload sway +bindsym $mod+shift+r reload + +# quit sway +bindsym $mod+shift+x exec swaynag -t warning \ + -m 'is you finished or is you done?' -b 'ye' 'swaymsg exit' + +# switch windows +bindsym $mod+$left focus left +bindsym $mod+$down focus down +bindsym $mod+$up focus up +bindsym $mod+$right focus right + +# move windows +bindsym $mod+shift+$left move left +bindsym $mod+shift+$down move down +bindsym $mod+shift+$up move up +bindsym $mod+shift+$right move right + +# switch workspaces +bindsym $mod+b workspace "main" +bindsym $mod+g workspace "goog" +bindsym $mod+1 workspace number 1 +bindsym $mod+2 workspace number 2 +bindsym $mod+3 workspace number 3 +bindsym $mod+4 workspace number 4 +bindsym $mod+5 workspace number 5 +bindsym $mod+6 workspace number 6 +bindsym $mod+7 workspace number 7 +bindsym $mod+8 workspace number 8 +bindsym $mod+9 workspace number 9 + +# move windows to workspaces +bindsym $mod+shift+b move container to workspace "main" +bindsym $mod+shift+g move container to workspace "goog" +bindsym $mod+shift+1 move container to workspace number 1 +bindsym $mod+shift+2 move container to workspace number 2 +bindsym $mod+shift+3 move container to workspace number 3 +bindsym $mod+shift+4 move container to workspace number 4 +bindsym $mod+shift+5 move container to workspace number 5 +bindsym $mod+shift+6 move container to workspace number 6 +bindsym $mod+shift+7 move container to workspace number 7 +bindsym $mod+shift+8 move container to workspace number 8 +bindsym $mod+shift+9 move container to workspace number 9 + +# resize mode +mode "resize" { + bindsym $left resize shrink width 10px + bindsym $down resize grow height 10px + bindsym $up resize shrink height 10px + bindsym $right resize grow width 10px + + # return to default mode + bindsym return mode "default" + bindsym escape mode "default" + bindsym $mod+r mode "default" +} + +bindsym $mod+r mode "resize" + +# wlsunset +exec brightness-adjust + +# noisetorch +exec "sleep 4 && noisetorch -i" diff --git a/.config/sway/config##hostname.splitty b/.config/sway/config##hostname.splitty @@ -0,0 +1,211 @@ +# display settings +output DP-1 res 3840x2160 scale 1 + +# start xdg-desktop-portal (screen sharing) +exec --no-startup-id /usr/lib/xdg-desktop-portal -r & /usr/lib/xdg-desktop-portal-wlr + +# dynamic wallpaper +exec wallpaper + +# winkey +set $mod mod4 +set $left h +set $down j +set $up k +set $right l + +# keeb settings +input "type:keyboard" { + xkb_layout us + repeat_delay 250 + repeat_rate 60 +} + +# make trackpoint tolerable +input "2:10:TPPS/2_IBM_TrackPoint" { + accel_profile flat + pointer_accel 1 +} + +# disable touchpad +input "1739:0:Synaptics_TM3276-022" { + events disabled +} + +# sway theme +set $font 'Hack Nerd Font Mono' +set $cursor_theme 'Neutral' +set $cursor_size 16 +seat seat0 hide_cursor 5000 +seat seat0 xcursor_theme $cursor_theme $cursor_size + +exec_always { + gsettings set org.gnome.desktop.interface cursor-theme $cursor_theme + gsettings set org.gnome.desktop.interface cursor-size $cursor_size + gsettings set org.gnome.desktop.interface font-name $font +} + +font $font 12 +titlebar_padding 2 +default_border pixel 5 +gaps inner 5 + +# class border bg text indicator child_border +client.focused #98c379 #98c379 #000000 #98c379 #98c379 +client.focused_inactive #333333 #222222 #ffffff #484e50 #3e4452 +client.unfocused #333333 #222222 #eeeeee #292d2e #3e4452 +client.urgent #2f343a #900000 #ffffff #900000 #900000 +client.placeholder #000000 #0c0c0c #ffffff #000000 #0c0c0c + +# yambar +# exec yambar +# bindsym $mod+shift+y exec "killall yambar; yambar" + +# waybar +exec waybar +bindsym $mod+shift+y exec "killall waybar; waybar" + +# lock settings +set $sleep screen-lock +bindsym ctrl+alt+delete exec $sleep + +exec swayidle timeout 900 $sleep \ + timeout 1200 'swaymsg "output * dpms off"' \ + resume 'swaymsg "output * dpms on"' + +# brightness control +bindsym XF86MonBrightnessUp exec xbacklight -inc 10 +bindsym XF86MonBrightnessDown exec xbacklight -dec 10 +bindsym $mod+shift+equal exec xbacklight -inc 10 +bindsym $mod+shift+minus exec xbacklight -dec 10 + +# terminal +bindsym $mod+return exec launch-terminal + +# kill focused window +bindsym $mod+shift+q kill + +# rofi +bindsym $mod+space exec rofi -show run + +# rofi+pass +bindsym $mod+p exec tessen + +# sshot +bindsym $mod+s exec grim -g "$(slurp)" - | swappy -f - + +# screencast +bindsym $mod+shift+s exec screen-record + +# snippet +bindsym $mod+n exec snippet-rofi + +# color picker +bindsym $mod+c exec hyprpicker --autocopy + +# bluetooth +bindsym $mod+w exec rofi-bluetooth + +# toggle horizontal/vertical +bindsym $mod+d layout toggle split +bindsym $mod+apostrophe splitv +bindsym $mod+semicolon splith + +# toggle layout +bindsym $mod+t layout toggle all + +# fullscreen +bindsym $mod+f fullscreen + +# floating/tiled toggle +bindsym $mod+shift+f floating toggle + +# drag floating windows +floating_modifier $mod normal + +# notifications +exec mako + +# audio +set $WOBSOCK $XDG_RUNTIME_DIR/wob.sock +exec rm -f $WOBSOCK && mkfifo $WOBSOCK && tail -f $WOBSOCK | wob + +# volume control +bindsym $mod+equal exec pactl set-sink-volume 0 +2% && pactl get-sink-volume 0 | awk '{print $12}' | tr -d '%' | head -1 > $WOBSOCK +bindsym $mod+minus exec pactl set-sink-volume 0 -2% && pactl get-sink-volume 0 | awk '{print $12}' | tr -d '%' | head -1 > $WOBSOCK +bindsym $mod+0 exec pactl set-sink-mute 0 toggle && pactl get-sink-volume 0 | awk '{print $12}' | tr -d '%' | head -1 > $WOBSOCK +bindsym $mod+shift+0 exec ~/.config/sway/mic-toggle.sh + +# media control +bindsym $mod+m exec rofimpd +bindsym $mod+slash exec mpc toggle +bindsym $mod+period exec mpc next +bindsym $mod+comma exec mpc prev +bindsym $mod+shift+comma exec mpc seek -5 +bindsym $mod+shift+period exec mpc seek +5 + +# reload sway +bindsym $mod+shift+r reload + +# quit sway +bindsym $mod+shift+x exec swaynag -t warning \ + -m 'is you finished or is you done?' -b 'ye' 'swaymsg exit' + +# switch windows +bindsym $mod+$left focus left +bindsym $mod+$down focus down +bindsym $mod+$up focus up +bindsym $mod+$right focus right + +# move windows +bindsym $mod+shift+$left move left +bindsym $mod+shift+$down move down +bindsym $mod+shift+$up move up +bindsym $mod+shift+$right move right + +# switch workspaces +bindsym $mod+b workspace "main" +bindsym $mod+g workspace "goog" +bindsym $mod+1 workspace number 1 +bindsym $mod+2 workspace number 2 +bindsym $mod+3 workspace number 3 +bindsym $mod+4 workspace number 4 +bindsym $mod+5 workspace number 5 +bindsym $mod+6 workspace number 6 +bindsym $mod+7 workspace number 7 +bindsym $mod+8 workspace number 8 +bindsym $mod+9 workspace number 9 + +# move windows to workspaces +bindsym $mod+shift+b move container to workspace "main" +bindsym $mod+shift+g move container to workspace "goog" +bindsym $mod+shift+1 move container to workspace number 1 +bindsym $mod+shift+2 move container to workspace number 2 +bindsym $mod+shift+3 move container to workspace number 3 +bindsym $mod+shift+4 move container to workspace number 4 +bindsym $mod+shift+5 move container to workspace number 5 +bindsym $mod+shift+6 move container to workspace number 6 +bindsym $mod+shift+7 move container to workspace number 7 +bindsym $mod+shift+8 move container to workspace number 8 +bindsym $mod+shift+9 move container to workspace number 9 + +# resize mode +mode "resize" { + bindsym $left resize shrink width 10px + bindsym $down resize grow height 10px + bindsym $up resize shrink height 10px + bindsym $right resize grow width 10px + + # return to default mode + bindsym return mode "default" + bindsym escape mode "default" + bindsym $mod+r mode "default" +} + +bindsym $mod+r mode "resize" + +# wlsunset +exec brightness-adjust + +# noisetorch +exec "sleep 4 && noisetorch -i" diff --git a/.config/sway/mic-toggle.sh b/.config/sway/mic-toggle.sh @@ -0,0 +1,11 @@ +#!/bin/bash + +sources=$(pactl list sources | grep -E 'Name:' | awk -F ': ' '{print $2}') +echo "${sources}" + + +for source in "NoiseTorch Microphone for Blue Microphones" "alsa_input.usb-Generic_Blue_Microphones_LT_201028234628D70F0609_111000-00.analog-stereo" +do + echo "$source" + pactl set-source-mute "${source}" toggle +done diff --git a/.config/tessen/config b/.config/tessen/config @@ -0,0 +1,5 @@ +userkey="user" +action="copy" +pass_backend="pass" +dmenu_backend="rofi" +rofi_config_file="$XDG_CONFIG_HOME/rofi/config.rasi" diff --git a/.config/tmux/tmux.conf b/.config/tmux/tmux.conf @@ -0,0 +1,101 @@ +set -g default-terminal "xterm-256color" +set -ag terminal-overrides ",xterm-256color:RGB" +set-option -g set-titles on +set-option -g set-titles-string '#S: #W' +set-window-option -g mode-keys vi + +# set split keys, splits open in same dir as current +bind-key "'" split-window -v -c '#{?pane_path,#{pane_path},#{pane_current_path}}' +bind-key ";" split-window -h -c '#{?pane_path,#{pane_path},#{pane_current_path}}' + +# switch panes w/ vimkeys +bind-key h select-pane -L +bind-key j select-pane -D +bind-key k select-pane -U +bind-key l select-pane -R + +# resize panes w/ vimkeys +bind-key -T copy-mode-vi C-h resize-pane -L 1 +bind-key -T copy-mode-vi C-j resize-pane -D 1 +bind-key -T copy-mode-vi C-k resize-pane -U 1 +bind-key -T copy-mode-vi C-l resize-pane -R 1 + +# fast window switching +bind w display-popup -E "tmux list-windows | cut -d ' ' -f 1-2 | grep -v \"^$(tmux display-message -p '#S')\$\" | fzf --reverse | cut -d \":\" -f 1 | xargs tmux select-window -t" + +# use C-a prefix +set-option -g prefix C-a +unbind-key C-a +bind-key C-a send-prefix + +# copy-mode +bind a copy-mode + +# kill window +bind X kill-window + +# 1 > 0 +set -g base-index 1 +set -g pane-base-index 1 + +# some good settings +set-option -g renumber-windows on # reindex when closing window +set -s escape-time 0 # fix vi mode delay +set -g history-limit 50000 # increase scrollback buffer size +set -g display-time 4000 # tmux messages are displayed for 4 seconds +setw -g aggressive-resize on # super useful when using "grouped sessions" and multi-monitor setup +set -g focus-events on # neovim likes this +set -g mouse on # enable mouse + +# tmux fingers +unbind-key f +set -g @fingers-key f +set -g @fingers-shift-action ':open:' +run-shell ~/.config/tmux/tmux-fingers/tmux-fingers.tmux + +# tmux theme +set-option -gq "status" "on" +set-option -gq "status-justify" "left" + +set-option -gq "status-left-length" "100" +set-option -gq "status-right-length" "100" +set-option -gq "status-right-attr" "none" + +set-option -gq "message-fg" "#aab2bf" +set-option -gq "message-bg" "#282c34" + +set-option -gq "message-command-fg" "#aab2bf" +set-option -gq "message-command-bg" "#282c34" + +set-option -gq "status-attr" "none" +set-option -gq "status-left-attr" "none" + +set-window-option -gq "window-status-fg" "#282c34" +set-window-option -gq "window-status-bg" "#282c34" +set-window-option -gq "window-status-attr" "none" + +set-window-option -gq "window-status-activity-bg" "#282c34" +set-window-option -gq "window-status-activity-fg" "#282c34" +set-window-option -gq "window-status-activity-attr" "none" + +set-window-option -gq "window-status-separator" "" + +set-option -gq "window-style" "fg=#aab2bf" +set-option -gq "window-active-style" "fg=#aab2bf" + +set-option -gq "pane-border-style" "fg=#282c34" +set-option -gq "pane-active-border-style" "bg=default,fg=#98c379" + +set-option -gq "display-panes-active-colour" "#e5c07b" +set-option -gq "display-panes-colour" "#61afef" + +set-option -gq "status-bg" "#282c34" +set-option -gq "status-fg" "#aab2bf" + +set-option -gq "status-right" "" +set-option -gq "status-left" "#[fg=#282c34,bg=#98c379,bold] #S #{prefix_highlight}#[fg=#98c379,bg=#282c34,nobold,nounderscore,noitalics]" + +set-option -gq "window-status-format" "#[fg=#282c34,bg=#282c34,nobold,nounderscore,noitalics]#[fg=#aab2bf,bg=#282c34] #I  #W #[fg=#282c34,bg=#282c34,nobold,nounderscore,noitalics]" +set-option -gq "window-status-current-format" "#[fg=#282c34,bg=#3e4452,nobold,nounderscore,noitalics]#[fg=#aab2bf,bg=#3e4452,nobold] #I  #W #[fg=#3e4452,bg=#282c34,nobold,nounderscore,noitalics]" + +set-window-option -g mode-style "bg=#98c379,fg=#282c34" diff --git a/.config/user-dirs.dirs b/.config/user-dirs.dirs @@ -0,0 +1,15 @@ +# This file is written by xdg-user-dirs-update +# If you want to change or add directories, just edit the line you're +# interested in. All local changes will be retained on the next run. +# Format is XDG_xxx_DIR="$HOME/yyy", where yyy is a shell-escaped +# homedir-relative path, or XDG_xxx_DIR="/yyy", where /yyy is an +# absolute path. No other format is supported. +# +XDG_DESKTOP_DIR="$HOME/" +XDG_DOWNLOAD_DIR="$HOME/dl" +XDG_TEMPLATES_DIR="$HOME/" +XDG_PUBLICSHARE_DIR="$HOME/" +XDG_DOCUMENTS_DIR="$HOME/docs" +XDG_MUSIC_DIR="$HOME/music" +XDG_PICTURES_DIR="$HOME/img" +XDG_VIDEOS_DIR="$HOME/vid" diff --git a/.config/visidata/config.py b/.config/visidata/config.py @@ -0,0 +1,2 @@ +options.clipboard_copy_cmd = "wl-copy" +options.clipboard_paste_cmd = "wl-paste" diff --git a/.config/waybar/config b/.config/waybar/config @@ -0,0 +1,138 @@ +{ + "position": "bottom", + "layer": "top", + "modules-left": [ + "sway/workspaces", + "sway/mode", + "custom/task" + ], + "modules-center": [ + ], + "modules-right": [ + "mpd", + "custom/recorder", + "pulseaudio", + "custom/temp", + "disk", + "clock#utc", + "clock#toronto", + "clock", + "battery" + ], + "battery": { + "format": "| bat: {capacity}% " + }, + "custom/task": { + "format": "{}", + "interval": 10, + "exec": "$XDG_CONFIG_HOME/waybar/task.sh" + }, + "clock": { + "format": "{:%a, %d %b %r} CT", + "format-alt": "{:%H:%M:%S}", + "interval": 1 + }, + "clock#toronto": { + "format": "{:%I:%M %p} ET |", + "interval": 1, + "timezone": "America/Toronto" + }, + "clock#utc": { + "format": "{:%H:%M} UTC |", + "interval": 1, + "timezone": "UTC" + }, + "network": { + "interface": "redstar", + "interval": 1, + "format-wifi": " {essid} ({signalStrength}%) {bandwidthUpBits} {bandwidthDownBits}", + "format-ethernet": "{bandwidthUpBits}  {bandwidthDownBits}  |", + "format-disconnected": "⚠ disconnected", + "tooltip-format": "{ifname}: {ipaddr}", + "tooltip": false + }, + "custom/temp": { + "exec": "$XDG_CONFIG_HOME/waybar/temp.sh", + "interval": 10 + }, + "memory": { + "interval": 5, + "format": "mem: {percentage}%" + }, + "pulseaudio": { + "format": "{volume}% {icon} | {format_source} |", + "format-muted": "muted  | {format_source} |", + "format-source": "mic on", + "format-source-muted": "mic off", + "format-icons": { + "default": [ + "", + "", + "" + ] + }, + "scroll-step": 1, + "on-click": "pactl set-sink-mute 0 toggle && pactl get-sink-volume 0", + "tooltip": false + }, + "custom/recorder": { + "format": " ", + "return-type": "json", + "interval": 1, + "exec": "echo '{\"class\": \"recording\"}'", + "exec-if": "pgrep wf-recorder" + }, + "mpd": { + "format": "{stateIcon} {artist} - {title} |", + "interval": 1, + "exec": "mpc current", + "exec-if": "pgrep mpd", + "on-click": "mpc toggle", + "escape": true, + "state-icons": { + "paused": "", + "playing": "" + }, + "tooltip": false + }, + "custom/weather": { + "format": "{}", + "tooltip": true, + "interval": 3600, + "exec": "python $HOME/.config/waybar/wttr.py", + "return-type": "json" + }, + "sway/language": { + "on-click": "swaymsg input '12815:20484:SONiX_USB_DEVICE' xkb_switch_layout next", + "format": "{short} {variant}" + }, + "custom/arrow1": { + "format": "", + "tooltip": false + }, + "custom/arrow2": { + "format": "", + "tooltip": false + }, + "custom/arrow3": { + "format": "", + "tooltip": false + }, + "custom/arrow4": { + "format": "", + "tooltip": false + }, + "custom/arrow5": { + "format": "", + "tooltip": false + }, + "custom/arrow6": { + "format": "", + "tooltip": false + }, + "disk": { + "interval": 120, + "format": " os: {free} |", + "path": "/" + } +} diff --git a/.config/waybar/style.css b/.config/waybar/style.css @@ -0,0 +1,61 @@ +* { + border: none; + border-radius: 0; + font-family: "Hack Nerd Font Mono"; + font-size: 22px; + min-height: 0; + color: #ffffff; +} + +*.DVI-D-1 * { + font-size: 14px; +} + +window#waybar { + background-color: rgba(29, 35, 48, 0.8); + color: #ffffff; + transition-property: background-color; + transition-duration: 0.5s; +} + +window#waybar.hidden { + opacity: 0.2; +} + +#workspaces button { + padding: 0 8px; + background-color: transparent; + color: #ffffff; +} + +#workspaces button.focused { + box-shadow: inset 0 -5px #98c379; +} + +#mode { + background-color: #eb4d4b; +} + +#clock, +#temperature, +#pulseaudio, +#tray, +#mode, +#mpd { + /* padding: 0 10px; */ + margin: 5px; + color: #ffffff; +} + +#window, +#workspaces { + margin: 0 4px; +} + +.modules-left > widget:first-child > #workspaces { + margin-left: 0; +} + +.modules-right > widget:last-child > #workspaces { + margin-right: 0; +} diff --git a/.config/waybar/task.sh b/.config/waybar/task.sh @@ -0,0 +1,3 @@ +#!/bin/sh +task=$(dstask show-active | jq -r 'if length == 0 then "task: none" else .[0] | "task \(.id): \(.summary)" end') +echo ${task} diff --git a/.config/waybar/temp.sh b/.config/waybar/temp.sh @@ -0,0 +1,4 @@ +#!/bin/sh +cpu=$(sensors | grep "Tctl" | awk '{print $2}' | sed 's/+//;s/\.0//;s/°C/°/') +gpu=$(sensors | grep "edge" | awk '{print $2}' | sed 's/+//;s/\.0//;s/°C/°/') +echo "cpu: ${cpu} gpu: ${gpu} |" diff --git a/.config/wob/wob.ini b/.config/wob/wob.ini @@ -0,0 +1,10 @@ +anchor = bottom +background_color = 1d2330dd +bar_color = 98c379cc +border_size = 0 +border_offset = 2 +bar_padding = 0 +overflow_background_color = 1d2330dd +overflow_bar_color = d19a66dd +height = 30 +margin = 400 diff --git a/.config/zathura/zathurarc b/.config/zathura/zathurarc @@ -0,0 +1,20 @@ +set sandbox none +set statusbar-h-padding 0 +set statusbar-v-padding 0 +set page-padding 1 +set selection-clipboard clipboard +set adjust-open width +map u scroll half-up +map d scroll half-down +map H navigate previous +map [fullscreen] H navigate previous +map L navigate next +map [fullscreen] L navigate next +map D toggle_page_mode +map r reload +map R rotate +map K zoom in +map J zoom out +map a recolor +map F toggle_fullscreen +map [fullscreen] F toggle_fullscreen diff --git a/.config/zsh/.zprofile b/.config/zsh/.zprofile @@ -0,0 +1,81 @@ +# xdg +export XDG_DATA_HOME=$HOME/.local/share +export XDG_CONFIG_HOME=$HOME/.config +export XDG_STATE_HOME=$HOME/.local/state +export XDG_CACHE_HOME=$HOME/.cache + +# dbus +export DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus + +# runit +export SVDIR=$XDG_CONFIG_HOME/runit/services + +# zsh config +export ZDOTDIR=$XDG_CONFIG_HOME/zsh + +# wayland / desktop +export QT_SCALE_FACTOR=1 +export QT_QPA_PLATFORM=wayland +export QT_WAYLAND_DISABLE_WINDOWDECORATION=1 +export QT_QPA_PLATFORMTHEME=qt5ct +export XDG_SESSION_TYPE=wayland +export MOZ_ENABLE_WAYLAND=1 +export GDK_BACKEND=wayland +export XDG_CURRENT_DESKTOP=sway +export SDL_VIDEODRIVER=wayland +export _JAVA_AWT_WM_NONREPARENTING=1 +export XDG_SESSION_DESKTOP=sway +export GTK_THEME=Adwaita:dark + +# etc +export EDITOR=nvim +export VISUAL=nvim +export GPG_TTY=$(tty) +export CHROME_EXECUTABLE=/usr/bin/chromium +export PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium +export GNUPGHOME=$XDG_DATA_HOME/gnupg +export DSTASK_GIT_REPO=$XDG_DATA_HOME/dstask +export PASSWORD_STORE_DIR=$XDG_DATA_HOME/pass +export PASSWORD_STORE_ENABLE_EXTENSIONS=true +export PASSWORD_STORE_GENERATED_LENGTH=34 +export PASSWORD_STORE_CHARACTER_SET='123450!@#$%6789qwertyQWERTYasdfgASDFGzxcvbZXCVB' +export NOTMUCH_CONFIG=$XDG_CONFIG_HOME/notmuch/config + +# userbin +export PATH=$PATH:$HOME/bin + +# go +export GOPATH=$XDG_DATA_HOME/go +export PATH=$PATH:$GOROOT/bin:$GOPATH/bin +export GOPROXY=direct +export GOSUMDB=off + +# python +export PATH=$PATH:$HOME/.local/bin + +# rust +export CARGO_HOME=$XDG_DATA_HOME/cargo +export PATH=$PATH:$CARGO_HOME/bin + +# php +export PATH=$PATH:$XDG_CONFIG_HOME/composer/vendor/bin + +# android-tools +# export ANDROID_HOME=$XDG_DATA_HOME/android +# export PATH=$PATH:$ANDROID_HOME/emulator +# export PATH=$PATH:$ANDROID_HOME/platform-tools +# export ANDROID_SDK_ROOT=/opt/android-sdk +# export PATH=$PATH:$ANDROID_SDK_ROOT/tools/bin/ +# export PATH=$PATH:$ANDROID_SDK_ROOT/tools/ +export PATH=$PATH:$ANDROID_HOME/cmdline-tools/latest/bin + +# node +# export NODE_OPTIONS=--openssl-legacy-provider + +# ruby +export PATH=$PATH:$XDG_DATA_HOME/gem/ruby/3.0.0/bin + +# js +export NPM_PACKAGES=$XDG_DATA_HOME/npm +export PATH=$PATH:$NPM_PACKAGES/bin +export NPM_CONFIG_USERCONFIG=$XDG_CONFIG_HOME/npm/config diff --git a/.config/zsh/.zshrc b/.config/zsh/.zshrc @@ -0,0 +1,49 @@ +export TERM=xterm-256color +eval "$(dircolors)" + +# zinit +ZINIT_HOME="${XDG_DATA_HOME:-${HOME}/.local/share}/zinit/zinit.git" +[ ! -d $ZINIT_HOME ] && mkdir -p "$(dirname $ZINIT_HOME)" +[ ! -d $ZINIT_HOME/.git ] && git clone https://github.com/zdharma-continuum/zinit.git "$ZINIT_HOME" +source "${ZINIT_HOME}/zinit.zsh" + +autoload -U url-quote-magic bracketed-paste-magic +zle -N self-insert url-quote-magic +zle -N bracketed-paste bracketed-paste-magic + +source $XDG_CONFIG_HOME/zsh/vi.zsh +source $XDG_CONFIG_HOME/zsh/completion.zsh +source $XDG_CONFIG_HOME/zsh/history.zsh +zinit load "zsh-users/zsh-autosuggestions" +bindkey '^l' autosuggest-accept +source $XDG_CONFIG_HOME/zsh/aliases.zsh + +# node version manager +# source /usr/share/nvm/init-nvm.sh + +# settings +export ZSH_AUTOSUGGEST_BUFFER_MAX_SIZE=20 +export FZF_DEFAULT_COMMAND='rg --files --follow --hidden -g "!{node_modules,.git,vendor}"' +export BAT_THEME="OneHalfDark" +export MANPAGER="nvim +Man!" + +# don't let url-quote-magic & autosuggestions conflict +pasteinit() { + OLD_SELF_INSERT=${${(s.:.)widgets[self-insert]}[2,3]} + zle -N self-insert url-quote-magic +} +pastefinish() { + zle -N self-insert $OLD_SELF_INSERT +} +zstyle :bracketed-paste-magic paste-init pasteinit +zstyle :bracketed-paste-magic paste-finish pastefinish +ZSH_AUTOSUGGEST_CLEAR_WIDGETS+=(bracketed-paste) + +# shell theme +export PURE_GIT_PULL=0 +fpath+=$XDG_CONFIG_HOME/zsh/pure +autoload -U promptinit; promptinit +prompt pure + +source ~/.config/zsh/zsh-artisan/artisan.plugin.zsh +zinit load "zsh-users/zsh-syntax-highlighting" diff --git a/.config/zsh/aliases.zsh b/.config/zsh/aliases.zsh @@ -0,0 +1,184 @@ +# config +alias mitmproxy="mitmproxy --set confdir=$XDG_CONFIG_HOME/mitmproxy" +alias mitmweb="mitmweb --set confdir=$XDG_CONFIG_HOME/mitmproxy" +alias mbsync="mbsync -c $XDG_CONFIG_HOME/mbsync/config" +alias wget="wget --hsts-file=/tmp/.wget-hsts" + +alias ..="cd .." +alias ...="cd ../.." +alias ....="cd ../../.." +alias .....="cd ../../../.." +alias g="git" +alias music="ncmpcpp" +alias rm="rm -i" +alias ls="ls --color" +alias l="ls -lah" +alias grep="grep --color" +alias task="dstask" +alias fd="fd --color=auto" +alias campv="mpv av://v4l2:/dev/video0" +alias ip='ip -color=auto' +alias busy="cat /dev/urandom | hexdump -C | grep '1a cc'" +alias tz="php ~/scripts/time.php" +alias checkencoding="ffprobe -v error -select_streams v:0 -show_entries stream=codec_name -of default=noprint_wrappers=1:nokey=1" +alias clip2qr="wl-paste | qrencode -t ANSI" +alias sshfs="sshfs -oauto_cache,reconnect" +alias studev="sshfs -oauto_cache,reconnect stu:/home/stu ~/mount/stu" +alias mp3='yt-dlp -f bestaudio --extract-audio --audio-format mp3 --add-metadata -o "%(title)s.%(ext)s"' +alias mp3tor='mp3 --proxy socks5://127.0.0.1:9050' +alias mp3album='yt-dlp -f bestaudio --extract-audio --audio-format mp3 --split-chapters -o "chapter:%(section_title)s.%(ext)s" --add-metadata --parse-metadata "title:%(artist)s - %(album)s"' +alias password="</dev/urandom tr -dc '123450!@#$%6789qwertQWERTasdfgASDFGzxcvbZXCVB' | head -c32" +alias passgen="pass generate -c" +alias sc="source $XDG_CONFIG_HOME/zsh/.zshrc" +alias diff="nvim -d" +alias d="sudo docker" +alias sctl='sudo systemctl' +alias uctl='systemctl --user' +alias a=artisan +alias nvimdiff="nvim -d" +alias c=clear +alias ta="transmission-remote -a" +alias tl="transmission-remote --list" +alias mpv="mpv --input-ipc-server=/tmp/mpv" +alias e=edit +alias playwright="npx playwright" + +urlencode() { + php -r "echo urlencode('$1');" +} +urldecode() { + php -r "echo urldecode('$1');" +} + +# xbps +alias xbi="sudo xbps-install" +alias xbr="sudo xbps-remove" +alias xbq="xbps-query" + +for method in GET HEAD POST PUT DELETE TRACE OPTIONS; do + alias "${method}"="curl -X '${method}'" +done + +ipi() { + curl -s https://ipinfo.io/$1 +} + +upload() { + if [ $# -lt 1 ]; then + echo "filename parameter required" + return + fi + + if [ $1 = "img" ]; then + file="$HOME/img/sshot/$(/usr/bin/ls -Art $HOME/img/sshot | tail -n 1)" + elif [ $1 = "vid" ]; then + file="$HOME/vid/screencast/$(/usr/bin/ls -Art $HOME/vid/screencast | tail -n 1 )" + else + file=$1 + fi + + filename=$(basename $file) + + curl -fsSL -F "file=@\"${file}\"" -F "url_len=5" https://u.rj1.su/upload +} +alias up=upload + +cpsshot() { + if [ $# -lt 1 ]; then + echo "filename parameter required" + return + fi + + file="$HOME/img/sshot/$(/usr/bin/ls -Art $HOME/img/sshot | tail -n 1)" + cp -f ${file} $1 + echo "copied ${file}" +} + +cpvid() { + if [ $# -lt 1 ]; then + echo "filename parameter required" + return + fi + + file="$HOME/vid/screencast/$(/usr/bin/ls -Art $HOME/vid/screencast | tail -n 1)" + cp -f ${file} $1 + echo "copied ${file}" +} + +androidproxy() { + if [ $# -lt 1 ]; then + echo "parameter required: on/off" + return + fi + + if [ $1 = "on" ]; then + adb shell settings put global http_proxy 192.168.0.10:8080 + elif [ $1 = "off" ]; then + adb shell settings put global http_proxy :0 + else + echo "parameter required: on/off" + fi +} +alias ap=androidproxy + +function calc() { + echo "$@" | bc -l +} + +function cur() { + php ~/scripts/cur.php $1 $2 $3 +} + +function wgetsite() { + wget \ + --recursive \ + --page-requisites \ + --html-extension \ + --convert-links \ + --restrict-file-names=windows \ + --domains $1 \ + --no-parent \ + https://$1 +} + +function wavs2mp3() { + for i in *.wav; do + ffmpeg -i "$i" -ab 320k -ac 2 -ar 44100 -joint_stereo 0 "${i%.*}.mp3"; + done +} + +function blurborder() { + convert $1 -bordercolor black -fill white \ + \( -clone 0 -colorize 100 -shave 10x10 -border 10x10 -blur 0x10 \) \ + -compose copyopacity -composite $2 +} + +function send2kindle() { + scp "${1}" kindle:/mnt/us/documents/ +} + +# ping - https://gist.github.com/schappim/1d958254a2907f073cf3b70091ab4b0f +function ping() { + local new_args=() + local url_found=0 + + for arg in "$@"; do + if [[ "$arg" =~ ^http ]] && [ $url_found -eq 0 ]; then + # Process the URL + local url="$arg" + url=${url#*://} # Remove protocol + url=${url%%/*} # Remove path + url=${url%%:*} # Remove port + url=${url%%@*} # Remove user info + new_args+=("$url") + url_found=1 + else + # Add the argument as is + new_args+=("$arg") + fi + done + + # Call the original ping command with the new argument list + command ping "${new_args[@]}" +} + diff --git a/.config/zsh/completion.zsh b/.config/zsh/completion.zsh @@ -0,0 +1,14 @@ +zmodload -i zsh/complist +zstyle :compinstall filename $XDG_CONFIG_HOME.'/zsh/.zshrc' +zstyle ':completion:*' menu select +zstyle ":completion:*:default" list-colors ${(s.:.)LS_COLORS} "ma=48;5;153;1" +bindkey -M menuselect '^[[Z' reverse-menu-complete + +autoload -Uz compinit +compinit + +# dstask completions +_dstask() { + compadd -- $(dstask _completions "${words[@]}") +} +compdef _dstask dstask diff --git a/.config/zsh/history.zsh b/.config/zsh/history.zsh @@ -0,0 +1,28 @@ +export HISTFILE=$XDG_DATA_HOME/zsh/history +export HISTSIZE=100000 +export SAVEHIST=100000 +setopt appendhistory +setopt sharehistory +setopt incappendhistory + +function historycomplete() { + setopt extended_glob + local history_cmd="fc -n -l -1 0 | awk '!seen[\$0]++'" + local fzf_cmd="fzf --height 10% +m +s -e -q \"$BUFFER\"" + local candidates="$(eval $history_cmd | eval $fzf_cmd)" + local ret="$?" + + if [ -n "$candidates" ]; then + BUFFER=$(echo $candidates | awk '{gsub(/\\n/,"\n")}1') + zle vi-fetch-history -n "$BUFFER" + zle end-of-line + fi + + zle reset-prompt + return $ret +} + +autoload historycomplete +zle -N historycomplete +bindkey ^r historycomplete + diff --git a/.config/zsh/vi.zsh b/.config/zsh/vi.zsh @@ -0,0 +1,25 @@ +# vi mode +bindkey -v +export KEYTIMEOUT=1 + +function zle-keymap-select { +if [[ ${KEYMAP} == vicmd ]] || + [[ $1 = 'block' ]]; then + echo -ne '\e[1 q' +elif [[ ${KEYMAP} == main ]] || + [[ ${KEYMAP} == viins ]] || + [[ ${KEYMAP} = '' ]] || + [[ $1 = 'beam' ]]; then + echo -ne '\e[5 q' +fi +} + +zle -N zle-keymap-select +zle-line-init() { +zle -K viins +echo -ne "\e[5 q" +} + +zle -N zle-line-init +echo -ne '\e[5 q' + diff --git a/.local/share/applications/imv.desktop b/.local/share/applications/imv.desktop @@ -0,0 +1,4 @@ +[Desktop Entry] +Type=Application +Name=imv +Exec=/usr/bin/imv diff --git a/.local/share/applications/mpv.desktop b/.local/share/applications/mpv.desktop @@ -0,0 +1,4 @@ +[Desktop Entry] +Type=Application +Name=mpv +Exec=/usr/bin/mpv diff --git a/.local/share/applications/nvim.desktop b/.local/share/applications/nvim.desktop @@ -0,0 +1,4 @@ +[Desktop Entry] +Type=Application +Name=nvim +Exec=/home/rj1/bin/edit diff --git a/.local/share/gnupg/gpg-agent.conf b/.local/share/gnupg/gpg-agent.conf @@ -0,0 +1,4 @@ +pinentry-program /home/rj1/bin/pinentry-rofi +default-cache-ttl 34560000 +max-cache-ttl 34560000 +auto-expand-secmem diff --git a/.zshenv b/.zshenv @@ -0,0 +1,2 @@ +export ZDOTDIR=~/.config/zsh + diff --git a/bin/brightness-adjust b/bin/brightness-adjust @@ -0,0 +1,5 @@ +#!/bin/bash +notify-send "wlsunset" "trying to launch.. (gpg required)" +lat=$(pass meta -a wlsunset 'latitude') +lon=$(pass meta -a wlsunset 'longitude') +/usr/bin/wlsunset -l ${lat} -L ${lon} & diff --git a/bin/cert-gen b/bin/cert-gen @@ -0,0 +1,34 @@ +#!/bin/bash + +# generate root ca: +# openssl genrsa -des3 -out rootCA.key 4096 +# openssl req -x509 -new -nodes -key rootCA.key -sha256 -days 1024 -out rootCA.crt + +if [ $# -lt 1 ]; then + echo "domain name parameter required" + exit 1 +fi + +# create dir for cert +cert_dir=$XDG_DATA_HOME/ssl/certs/$1 +mkdir -p $cert_dir + +# generate cert key +openssl genrsa -out $cert_dir/privkey.pem 2048 + +openssl req -new -sha256 \ + -key $cert_dir/privkey.pem \ + -subj "/C=RU/ST=CFD/O=KGB/OU=FSB/CN=${1}" \ + -reqexts SAN \ + -config <(cat /etc/ssl/openssl.cnf <(printf "\n[SAN]\nsubjectAltName=DNS:${1}")) \ + -out $cert_dir/csr.pem + + +openssl x509 -req -extfile <(printf "subjectAltName=DNS:${1}") \ + -days 365 \ + -in $cert_dir/csr.pem \ + -CA $XDG_DATA_HOME/ssl/root-ca/rootCA.crt \ + -CAkey $XDG_DATA_HOME/ssl/root-ca/rootCA.key \ + -CAcreateserial \ + -out $cert_dir/certificate.pem \ + -sha256 diff --git a/bin/chromium-rob b/bin/chromium-rob @@ -0,0 +1,3 @@ +#!/bin/sh +# /usr/bin/chromium --profile-directory="Default" --enable-features=UseOzonePlatform --ozone-platform=wayland --force-device-scale-factor=1.3 +/usr/bin/chromium --profile-directory="Default" --force-device-scale-factor=1.3 diff --git a/bin/chromium-tuio b/bin/chromium-tuio @@ -0,0 +1,3 @@ +#!/bin/sh +# /usr/bin/chromium --profile-directory="Profile 2" --enable-features=UseOzonePlatform --ozone-platform=wayland +/usr/bin/chromium --profile-directory="Profile 5" --force-device-scale-factor=1.3 diff --git a/bin/edit b/bin/edit @@ -0,0 +1,117 @@ +#!/usr/bin/env python +import libtmux +from pynvim import attach +from i3ipc import Connection as sway +import sys +import os +import glob +import json +import time + + +def in_dir(path, dir): + return os.path.realpath(path).startswith(os.path.realpath(dir) + os.sep) + + +def abs_filepath(filepath): + if os.path.isabs(filepath): + return filepath + else: + return os.getcwd() + "/" + filepath + + +server = libtmux.Server() + +# default session name is "edit" +tmux_session = server.sessions.filter(session_name="edit")[0] + +# check if 'edit' tmux session is open +if tmux_session is None: + print("you don't have the edit session opened, do this first") + quit() + +# check if file parameter is passed +if len(sys.argv) != 2: + print("missing file parameter, e.g. `edit <filename>`") + quit() + +# get list of windows in our tmux session +windows = tmux_session.windows + +open_windows = [] +for window in windows: + open_windows.append(window.name) + +# get list of all neovim sessions +session_dir = os.getenv("XDG_STATE_HOME") + "/nvim/session" +session_files = glob.glob(session_dir + "/*.json") + +# create our list of sessions and the data we need to use them +sessions = {} +for session in session_files: + # open json file + with open(session) as f: + data = json.load(f) + + # add sessions + basename = os.path.basename(session).replace(".json", "") + sessions[basename] = {} + sessions[basename]["name"] = basename + sessions[basename]["dir"] = data["cwd"] + sessions[basename]["open"] = False + +# reverse sort by string length of dir, so we can find the deepest dir that contains the file we're trying to open +# e.g. if we have sessions for ~/.config/nvim and ~/.config/nvim/plugins/some-plugin +sessions = dict(sorted(sessions.items(), key=lambda x: len(x[1]["dir"]), reverse=True)) + +# set open flag for sessions that are open in tmux +for session in sessions: + if session in open_windows: + sessions[session]["open"] = True + +# absolute filepath +file = abs_filepath(sys.argv[1]) + +# set default session +use_session = sessions["config"] + +# see if there's a non-default session available that we should use instead +for session in sessions.values(): + if in_dir(file, session["dir"]) is True: + use_session = session + break + +if use_session["open"] is False: + # create new tmux window + window = tmux_session.new_window(window_name=use_session["name"]) + + # open neovim in new tmux window + pane = window.attached_pane + pane.send_keys("nvim") + pane.send_keys(":LoadSession " + use_session["name"]) + + # wait for socket to be created + socket = "/tmp/nvim-" + use_session["name"] + while os.path.exists(socket) is False: + time.sleep(0.1) + +# connect to neovim socket and open file +# NOTE: this requires the creation of sockets per session in your neovim config +# e.g. au sessionloadpost * call serverstart('/tmp/nvim-' . SessionName()) +socket = "/tmp/nvim-" + use_session["name"] +nvim = attach("socket", path=socket) +nvim.command("edit " + file) + +# find tmux window and switch to it +find_window = tmux_session.windows.filter(window_name=use_session["name"])[0] +window = tmux_session.select_window(str(find_window).split(":")[0].split()[1]) + +# switch to first pane in session window (this is where i always have neovim) +pane = window.panes[0] +pane.select_pane() + +# find the edit window in sway and focus it +for window in sway().get_tree().leaves(): + if "edit: " in window.name: + window.command("focus") + window.workspace().command("focus") diff --git a/bin/email-check b/bin/email-check @@ -0,0 +1,45 @@ +#!/bin/zsh +# source env vars for cronjob +source ~/.config/zsh/.zprofile + +# get list of emails +source ~/.config/lieer/config.sh + +for gmail in "${gmails[@]}" +do + cd ~/mail/$gmail + echo "checking ${gmail}" + /usr/bin/gmi sync +done + +# sync smtp mail if there's a parameter +/usr/bin/mbsync -c /home/rj1/.config/mbsync/config -aV + +# add mail to notmuch db +/usr/bin/notmuch new + +# do some filtering +# source ~/.config/email/filter.sh + +# do some tagging +/usr/bin/notmuch tag +inbox -- tag:new +/usr/bin/notmuch tag +notify -- tag:new +/usr/bin/notmuch tag -new -- tag:new + +# notify if there are new mails and notifications aren't muted +if [ -z "$MUTE_NOTIFICATIONS" ] || [ "$MUTE_NOTIFICATIONS" -eq 0 ]; then + + SEARCH="tag:notify" + + NOTIFY_COUNT=$(/usr/bin/notmuch count "$SEARCH") + + if [ "$NOTIFY_COUNT" -gt 0 ]; then + RESULTS=$(/usr/bin/notmuch search --format=json --output=summary --limit=3 --sort="newest-first" "$SEARCH" | jq -r '.[] | "\(.authors): \(.subject)"') + /usr/bin/notify-send "$NOTIFY_COUNT new mails:" "$RESULTS" + fi + + /usr/bin/notmuch tag -notify -- tag:notify +fi + +# refresh alot buffer if it's open +pkill --signal SIGUSR1 alot diff --git a/bin/emoji b/bin/emoji @@ -0,0 +1,13 @@ +#!/bin/sh + +emojis="$XDG_CACHE_HOME/emojis.json" + +if ! [ -f "$emojis" ]; then + curl -o "$emojis" -L "https://github.com/github/gemoji/raw/master/db/emoji.json" +fi + +filter='.[] | (.emoji + " " + .description + " (" + (.aliases | join(", ")) + ")")' +emoji="$(jq -r "$filter" <"$emojis" | fzf | cut -d ' ' -f 1 | tr -d '\n')" +if [ -n "$emoji" ]; then + echo $emoji | tr -d '\n' | wl-copy +fi diff --git a/bin/launch-terminal b/bin/launch-terminal @@ -0,0 +1,8 @@ +#!/bin/bash +if pgrep foot &>/dev/null; then + footclient +else + foot -s & + sleep 0.1 + footclient +fi diff --git a/bin/miniircd b/bin/miniircd @@ -0,0 +1,1250 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2003-2021 Joel Rosdahl +# +# This program 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 2 of the License, or +# (at your option) any later version. +# +# This program 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 this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 +# USA +# +# Joel Rosdahl <[email protected]> + +import logging +import os +import re +import select +import socket +import string +import sys +import tempfile +import time + +from argparse import ArgumentParser, Namespace +from datetime import datetime +from logging.handlers import RotatingFileHandler +from typing import Any, Collection, Dict, List, Optional, Sequence, Set + +Socket = socket.socket + +VERSION = "2.1" + + +def create_directory(path: str) -> None: + if not os.path.isdir(path): + os.makedirs(path) + + +class Channel: + def __init__(self, server: "Server", name: bytes) -> None: + self.server = server + self.name = name + self.members: Set["Client"] = set() + self._topic = b"" + self._key: Optional[bytes] = None + self._state_path: Optional[str] + if self.server.state_dir: + fs_safe_name = ( + name.decode(errors="ignore") + .replace("_", "__") + .replace("/", "_") + ) + self._state_path = f"{self.server.state_dir}/{fs_safe_name}" + self._read_state() + else: + self._state_path = None + + def add_member(self, client: "Client") -> None: + self.members.add(client) + + @property + def topic(self) -> bytes: + return self._topic + + @topic.setter + def topic(self, value: bytes) -> None: + self._topic = value + self._write_state() + + @property + def key(self) -> Optional[bytes]: + return self._key + + @key.setter + def key(self, value: bytes) -> None: + self._key = value + self._write_state() + + def remove_client(self, client: "Client") -> None: + self.members.discard(client) + if not self.members: + self.server.remove_channel(self) + + def _read_state(self) -> None: + if not (self._state_path and os.path.exists(self._state_path)): + return + data: Dict[str, Any] = {} + + with open(self._state_path, "rb") as state_file: + exec(state_file.read(), {}, data) + + self._topic = data.get("topic", "") + self._key = data.get("key") + + def _write_state(self) -> None: + if not self._state_path: + return + fd, path = tempfile.mkstemp(dir=os.path.dirname(self._state_path)) + fp = os.fdopen(fd, "w") + fp.write("topic = %r\n" % self.topic) + fp.write("key = %r\n" % self.key) + fp.close() + os.replace(path, self._state_path) + + +class Client: + __linesep_regexp = re.compile(rb"\r?\n") + # The RFC limit for nicknames is 9 characters, but what the heck. + __valid_nickname_regexp = re.compile( + rb"^[][\`_^{|}A-Za-z][][\`_^{|}A-Za-z0-9-]{0,50}$" + ) + __valid_channelname_regexp = re.compile( + rb"^[&#+!][^\x00\x07\x0a\x0d ,:]{0,50}$" + ) + + def __init__(self, server: "Server", socket: Socket) -> None: + self.server = server + self.socket = socket + # irc_lower(Channel name) --> Channel + self.channels: Dict[bytes, Channel] = {} + self.nickname = b"" + self.user = b"" + self.realname = b"" + if self.server.ipv6: + host, port, _, _ = socket.getpeername() + else: + host, port = socket.getpeername() + self.host = host.encode() + self.port = port + if self.server.cloak: + self.host = self.server.cloak + self.__timestamp = time.time() + self.__readbuffer = b"" + self.__writebuffer = b"" + self.__sent_ping = False + if self.server.password: + self.__handle_command = self.__pass_handler + else: + self.__handle_command = self.__registration_handler + + @property + def prefix(self) -> bytes: + return b"%s!%s@%s" % (self.nickname, self.user, self.host) + + def check_aliveness(self) -> None: + now = time.time() + if self.__timestamp + 180 < now: + self.disconnect("ping timeout") + return + if not self.__sent_ping and self.__timestamp + 90 < now: + if self.__handle_command == self.__command_handler: + # Registered. + self.message(b"PING :%s" % self.server.name) + self.__sent_ping = True + else: + # Not registered. + self.disconnect("ping timeout") + + def write_queue_size(self) -> int: + return len(self.__writebuffer) + + def __parse_read_buffer(self) -> None: + lines = self.__linesep_regexp.split(self.__readbuffer) + self.__readbuffer = lines[-1] + lines = lines[:-1] + for line in lines: + if not line: + # Empty line. Ignore. + continue + x = line.split(b" ", 1) + command = x[0].upper() + if len(x) == 1: + arguments = [] + elif x[1].startswith(b":"): + arguments = [x[1][1:]] + else: + y = x[1].split(b" :", 1) + arguments = y[0].split() + if len(y) == 2: + arguments.append(y[1]) + self.__handle_command(command, arguments) + + def __pass_handler( + self, command: bytes, arguments: Sequence[bytes] + ) -> None: + server = self.server + if command == b"PASS": + if len(arguments) == 0: + self.reply_461(b"PASS") + elif arguments[0] == server.password.encode(): + self.__handle_command = self.__registration_handler + else: + self.reply(b"464 :Password incorrect") + elif command == b"QUIT": + self.disconnect("Client quit") + + def __registration_handler( + self, command: bytes, arguments: Sequence[bytes] + ) -> None: + server = self.server + if command == b"NICK": + if len(arguments) < 1: + self.reply(b"431 :No nickname given") + return + nick = arguments[0] + if server.get_client(nick): + self.reply(b"433 * %s :Nickname is already in use" % nick) + elif not self.__valid_nickname_regexp.match(nick): + self.reply(b"432 * %s :Erroneous nickname" % nick) + else: + self.nickname = nick + server.client_changed_nickname(self, None) + elif command == b"USER": + if len(arguments) < 4: + self.reply_461(b"USER") + return + self.user = arguments[0] + self.realname = arguments[3] + elif command == b"QUIT": + self.disconnect("Client quit") + return + if self.nickname and self.user: + self.reply(b"001 %s :Hi, welcome to IRC" % self.nickname) + self.reply( + b"002 %s :Your host is %s, running version miniircd-%s" + % (self.nickname, server.name, VERSION.encode()) + ) + self.reply( + b"003 %s :This server was created sometime" % self.nickname + ) + self.reply( + b"004 %s %s miniircd-%s o o" + % (self.nickname, server.name, VERSION.encode()) + ) + self.send_lusers() + self.send_motd() + self.__handle_command = self.__command_handler + + def __send_names( + self, arguments: Sequence[bytes], for_join: bool = False + ) -> None: + server = self.server + valid_channel_re = self.__valid_channelname_regexp + if len(arguments) > 0: + channelnames = arguments[0].split(b",") + else: + channelnames = sorted(self.channels.keys()) + if len(arguments) > 1: + keys = arguments[1].split(b",") + else: + keys = [] + for i, channelname in enumerate(channelnames): + if for_join and irc_lower(channelname) in self.channels: + continue + if not valid_channel_re.match(channelname): + self.reply_403(channelname) + continue + channel = server.get_channel(channelname) + if channel.key is not None and ( + len(keys) <= i or channel.key != keys[i] + ): + self.reply( + b"475 %s %s :Cannot join channel (+k) - bad key" + % (self.nickname, channelname) + ) + continue + + if for_join: + channel.add_member(self) + self.channels[irc_lower(channelname)] = channel + self.message_channel(channel, b"JOIN", channelname, True) + self.channel_log(channel, b"joined", meta=True) + if channel.topic: + self.reply( + b"332 %s %s :%s" + % (self.nickname, channel.name, channel.topic) + ) + else: + self.reply( + b"331 %s %s :No topic is set" + % (self.nickname, channel.name) + ) + names_prefix = b"353 %s = %s :" % (self.nickname, channelname) + names = b"" + # Max length: reply prefix ":server_name(space)" plus CRLF in + # the end. + names_max_len = 512 - (len(server.name) + 2 + 2) + for name in sorted(x.nickname for x in channel.members): + if not names: + names = names_prefix + name + # Using >= to include the space between "names" and "name". + elif len(names) + len(name) >= names_max_len: + self.reply(names) + names = names_prefix + name + else: + names += b" " + name + if names: + self.reply(names) + self.reply( + b"366 %s %s :End of NAMES list" % (self.nickname, channelname) + ) + + def __command_handler( + self, command: bytes, arguments: Sequence[bytes] + ) -> None: + def away_handler() -> None: + pass + + def ison_handler() -> None: + if len(arguments) < 1: + self.reply_461(b"ISON") + return + nicks = arguments + online = [n for n in nicks if server.get_client(n)] + self.reply(b"303 %s :%s" % (self.nickname, b" ".join(online))) + + def join_handler() -> None: + if len(arguments) < 1: + self.reply_461(b"JOIN") + return + if arguments[0] == b"0": + for (channelname, channel) in self.channels.items(): + self.message_channel(channel, b"PART", channelname, True) + self.channel_log(channel, b"left", meta=True) + server.remove_member_from_channel(self, channelname) + self.channels = {} + return + self.__send_names(arguments, for_join=True) + + def list_handler() -> None: + if len(arguments) < 1: + channels = list(server.channels.values()) + else: + channels = [] + for channelname in arguments[0].split(b","): + if server.has_channel(channelname): + channels.append(server.get_channel(channelname)) + + sorted_channels = sorted(channels, key=lambda x: x.name) + for channel in sorted_channels: + self.reply( + b"322 %s %s %d :%s" + % ( + self.nickname, + channel.name, + len(channel.members), + channel.topic, + ) + ) + self.reply(b"323 %s :End of LIST" % self.nickname) + + def lusers_handler() -> None: + self.send_lusers() + + def mode_handler() -> None: + if len(arguments) < 1: + self.reply_461(b"MODE") + return + targetname = arguments[0] + if server.has_channel(targetname): + channel = server.get_channel(targetname) + if len(arguments) < 2: + if channel.key: + modes = b"+k" + if irc_lower(channel.name) in self.channels: + modes += b" %s" % channel.key + else: + modes = b"+" + self.reply( + b"324 %s %s %s" % (self.nickname, targetname, modes) + ) + return + flag = arguments[1] + if flag == b"+k": + if len(arguments) < 3: + self.reply_461(b"MODE") + return + key = arguments[2] + if irc_lower(channel.name) in self.channels: + channel.key = key + self.message_channel( + channel, + b"MODE", + b"%s +k %s" % (channel.name, key), + True, + ) + self.channel_log( + channel, b"set channel key to %s" % key, meta=True + ) + else: + self.reply( + b"442 %s :You're not on that channel" % targetname + ) + elif flag == b"-k": + if irc_lower(channel.name) in self.channels: + channel.key = None + self.message_channel( + channel, b"MODE", b"%s -k" % channel.name, True + ) + self.channel_log( + channel, b"removed channel key", meta=True + ) + else: + self.reply( + b"442 %s :You're not on that channel" % targetname + ) + else: + self.reply( + b"472 %s %s :Unknown MODE flag" % (self.nickname, flag) + ) + elif targetname == self.nickname: + if len(arguments) == 1: + self.reply(b"221 %s +" % self.nickname) + else: + self.reply(b"501 %s :Unknown MODE flag" % self.nickname) + else: + self.reply_403(targetname) + + def motd_handler() -> None: + self.send_motd() + + def names_handler() -> None: + self.__send_names(arguments) + + def nick_handler() -> None: + if len(arguments) < 1: + self.reply(b"431 :No nickname given") + return + newnick = arguments[0] + client = server.get_client(newnick) + if newnick == self.nickname: + pass + elif client and client is not self: + self.reply( + b"433 %s %s :Nickname is already in use" + % (self.nickname, newnick) + ) + elif not self.__valid_nickname_regexp.match(newnick): + self.reply( + b"432 %s %s :Erroneous Nickname" % (self.nickname, newnick) + ) + else: + for x in self.channels.values(): + self.channel_log( + x, b"changed nickname to %s" % newnick, meta=True + ) + oldnickname = self.nickname + self.nickname = newnick + server.client_changed_nickname(self, oldnickname) + self.message_related( + b":%s!%s@%s NICK %s" + % (oldnickname, self.user, self.host, self.nickname), + True, + ) + + def notice_and_privmsg_handler() -> None: + if len(arguments) == 0: + self.reply( + b"411 %s :No recipient given (%s)" + % (self.nickname, command) + ) + return + if len(arguments) == 1: + self.reply(b"412 %s :No text to send" % self.nickname) + return + targetname = arguments[0] + message = arguments[1] + client = server.get_client(targetname) + if client: + client.message( + b":%s %s %s :%s" + % (self.prefix, command, targetname, message) + ) + elif server.has_channel(targetname): + channel = server.get_channel(targetname) + self.message_channel( + channel, command, b"%s :%s" % (channel.name, message) + ) + self.channel_log(channel, message) + else: + self.reply( + b"401 %s %s :No such nick/channel" + % (self.nickname, targetname) + ) + + def part_handler() -> None: + if len(arguments) < 1: + self.reply_461(b"PART") + return + if len(arguments) > 1: + partmsg = arguments[1] + else: + partmsg = self.nickname + for channelname in arguments[0].split(b","): + if not valid_channel_re.match(channelname): + self.reply_403(channelname) + elif not irc_lower(channelname) in self.channels: + self.reply( + b"442 %s %s :You're not on that channel" + % (self.nickname, channelname) + ) + else: + channel = self.channels[irc_lower(channelname)] + self.message_channel( + channel, + b"PART", + b"%s :%s" % (channelname, partmsg), + True, + ) + self.channel_log( + channel, b"left (%s)" % partmsg, meta=True + ) + del self.channels[irc_lower(channelname)] + server.remove_member_from_channel(self, channelname) + + def ping_handler() -> None: + if len(arguments) < 1: + self.reply(b"409 %s :No origin specified" % self.nickname) + return + self.reply(b"PONG %s :%s" % (server.name, arguments[0])) + + def pong_handler() -> None: + pass + + def quit_handler() -> None: + if len(arguments) < 1: + quitmsg = self.nickname + else: + quitmsg = arguments[0] + self.disconnect(quitmsg.decode(errors="ignore")) + + def topic_handler() -> None: + if len(arguments) < 1: + self.reply_461(b"TOPIC") + return + channelname = arguments[0] + channel = self.channels.get(irc_lower(channelname)) + if channel: + if len(arguments) > 1: + newtopic = arguments[1] + channel.topic = newtopic + self.message_channel( + channel, + b"TOPIC", + b"%s :%s" % (channelname, newtopic), + True, + ) + self.channel_log( + channel, b"set topic to %r" % newtopic, meta=True + ) + else: + if channel.topic: + self.reply( + b"332 %s %s :%s" + % (self.nickname, channel.name, channel.topic) + ) + else: + self.reply( + b"331 %s %s :No topic is set" + % (self.nickname, channel.name) + ) + else: + self.reply(b"442 %s :You're not on that channel" % channelname) + + def wallops_handler() -> None: + if len(arguments) < 1: + self.reply_461(b"WALLOPS") + return + message = arguments[0] + for client in server.clients.values(): + client.message( + b":%s NOTICE %s :Global notice: %s" + % (self.prefix, client.nickname, message) + ) + + def who_handler() -> None: + if len(arguments) < 1: + return + targetname = arguments[0] + if server.has_channel(targetname): + channel = server.get_channel(targetname) + for member in channel.members: + self.reply( + b"352 %s %s %s %s %s %s H :0 %s" + % ( + self.nickname, + targetname, + member.user, + member.host, + server.name, + member.nickname, + member.realname, + ) + ) + self.reply( + b"315 %s %s :End of WHO list" % (self.nickname, targetname) + ) + + def whois_handler() -> None: + if len(arguments) < 1: + return + username = arguments[0] + user = server.get_client(username) + if user: + self.reply( + b"311 %s %s %s %s * :%s" + % ( + self.nickname, + user.nickname, + user.user, + user.host, + user.realname, + ) + ) + self.reply( + b"312 %s %s %s :%s" + % (self.nickname, user.nickname, server.name, server.name) + ) + self.reply( + b"319 %s %s :%s" + % ( + self.nickname, + user.nickname, + b"".join(x + b" " for x in user.channels), + ) + ) + self.reply( + b"318 %s %s :End of WHOIS list" + % (self.nickname, user.nickname) + ) + else: + self.reply( + b"401 %s %s :No such nick" % (self.nickname, username) + ) + + handler_table = { + b"AWAY": away_handler, + b"ISON": ison_handler, + b"JOIN": join_handler, + b"LIST": list_handler, + b"LUSERS": lusers_handler, + b"MODE": mode_handler, + b"MOTD": motd_handler, + b"NAMES": names_handler, + b"NICK": nick_handler, + b"NOTICE": notice_and_privmsg_handler, + b"PART": part_handler, + b"PING": ping_handler, + b"PONG": pong_handler, + b"PRIVMSG": notice_and_privmsg_handler, + b"QUIT": quit_handler, + b"TOPIC": topic_handler, + b"WALLOPS": wallops_handler, + b"WHO": who_handler, + b"WHOIS": whois_handler, + } + server = self.server + valid_channel_re = self.__valid_channelname_regexp + try: + handler_table[command]() + except KeyError: + self.reply( + b"421 %s %s :Unknown command" % (self.nickname, command) + ) + + def socket_readable_notification(self) -> None: + try: + data = self.socket.recv(2 ** 10) + if self.server.debug: + host = self.host.decode(errors="ignore") + self.server.print_debug(f"[{host}:{self.port}] -> {data!r}") + quitmsg = "EOT" + except socket.error as e: + data = b"" + quitmsg = str(e) + if data: + self.__readbuffer += data + self.__parse_read_buffer() + self.__timestamp = time.time() + self.__sent_ping = False + else: + self.disconnect(quitmsg) + + def socket_writable_notification(self) -> None: + try: + sent = self.socket.send(self.__writebuffer) + if self.server.debug: + head = self.__writebuffer[:sent] + host = self.host.decode(errors="ignore") + self.server.print_debug(f"[{host}:{self.port}] <- {head!r}") + self.__writebuffer = self.__writebuffer[sent:] + except socket.error as x: + self.disconnect(str(x)) + + def disconnect(self, quitmsg: str) -> None: + self.message((f"ERROR :{quitmsg}").encode()) + host = self.host.decode(errors="ignore") + self.server.print_info( + f"Disconnected connection from {host}:{self.port} ({quitmsg})." + ) + self.socket.close() + self.server.remove_client(self, quitmsg.encode()) + + def message(self, msg: bytes) -> None: + self.__writebuffer += msg + b"\r\n" + + def reply(self, msg: bytes) -> None: + self.message(b":%s %s" % (self.server.name, msg)) + + def reply_403(self, channel: bytes) -> None: + self.reply(b"403 %s %s :No such channel" % (self.nickname, channel)) + + def reply_461(self, command: bytes) -> None: + nickname = self.nickname or b"*" + self.reply(b"461 %s %s :Not enough parameters" % (nickname, command)) + + def message_channel( + self, + channel: Channel, + command: bytes, + message: bytes, + include_self: bool = False, + ) -> None: + line = b":%s %s %s" % (self.prefix, command, message) + for client in channel.members: + if client != self or include_self: + client.message(line) + + def channel_log( + self, channel: Channel, message: bytes, meta: bool = False + ) -> None: + if not self.server.channel_log_dir: + return + if meta: + format_string = "[{}] * {} {}\n" + else: + format_string = "[{}] <{}> {}\n" + timestamp = datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S UTC") + channel_name = irc_lower(channel.name).decode(errors="ignore") + logname = channel_name.replace("_", "__").replace("/", "_") + logfile = f"{self.server.channel_log_dir}/{logname}.log" + logmsg = format_string.format(timestamp, self.nickname, message) + with open(logfile, "a") as fp: + fp.write(logmsg) + + def message_related(self, msg: bytes, include_self: bool = False) -> None: + clients = set() + if include_self: + clients.add(self) + for channel in self.channels.values(): + clients |= channel.members + if not include_self: + clients.discard(self) + for client in clients: + client.message(msg) + + def send_lusers(self) -> None: + self.reply( + b"251 %s :There are %d users and 0 services on 1 server" + % (self.nickname, len(self.server.clients)) + ) + + def send_motd(self) -> None: + server = self.server + motdlines = server.get_motd_lines() + if motdlines: + self.reply( + b"375 %s :- %s Message of the day -" + % (self.nickname, server.name) + ) + for line in motdlines: + self.reply( + b"372 %s :- %s" % (self.nickname, line.rstrip().encode()) + ) + self.reply(b"376 %s :End of /MOTD command" % self.nickname) + else: + self.reply(b"422 %s :MOTD File is missing" % self.nickname) + + +class Server: + def __init__(self, args: Namespace) -> None: + self.ports = args.ports + self.password: str = args.password + self.ssl_cert_file = args.ssl_cert_file + self.ssl_key_file = args.ssl_key_file + self.motdfile = args.motd + self.verbose = args.verbose + self.ipv6 = args.ipv6 + self.debug = args.debug + self.channel_log_dir = args.channel_log_dir + self.chroot = args.chroot + self.setuid = args.setuid + self.state_dir = args.state_dir + self.log_file = args.log_file + self.log_max_bytes = args.log_max_size * 1024 * 1024 + self.log_count = args.log_count + self.logger: Optional[logging.Logger] = None + self.cloak = args.cloak + self.name: bytes + + if args.password_file: + with open(args.password_file, "r") as fp: + self.password = fp.read().strip("\n") + + if self.ssl_key_file: + self.ssl = __import__("ssl") + + # Find key/cert files after daemonization if path is relative: + if self.ssl_cert_file and os.path.exists(self.ssl_cert_file): + self.ssl_cert_file = os.path.abspath(self.ssl_cert_file) + if self.ssl_key_file and os.path.exists(self.ssl_key_file): + self.ssl_key_file = os.path.abspath(self.ssl_key_file) + # else: might exist in the chroot jail, so just continue + + if args.listen and self.ipv6: + self.address = socket.getaddrinfo( + args.listen, None, proto=socket.IPPROTO_TCP + )[0][4][0] + elif args.listen: + self.address = socket.gethostbyname(args.listen) + else: + self.address = "" + server_name_limit = 63 # From the RFC. + self.name = socket.getfqdn(self.address)[:server_name_limit].encode() + + self.channels: Dict[bytes, Channel] = {} # key: irc_lower(channelname) + self.clients: Dict[Socket, Client] = {} + self.nicknames: Dict[bytes, Client] = {} # key: irc_lower(nickname) + if self.channel_log_dir: + create_directory(self.channel_log_dir) + if self.state_dir: + create_directory(self.state_dir) + + def make_pid_file(self, filename: str) -> None: + try: + fd = os.open(filename, os.O_RDWR | os.O_CREAT | os.O_EXCL, 0o644) + os.write(fd, b"%i\n" % os.getpid()) + os.close(fd) + except Exception: + self.print_error("Could not create PID file %r" % filename) + sys.exit(1) + + def remove_pid_file(self, filename: str) -> None: + try: + os.remove(filename) + except Exception: + self.print_error("Could not remove PID file %r" % filename) + + def daemonize(self) -> None: + try: + pid = os.fork() + if pid > 0: + sys.exit(0) + except OSError: + sys.exit(1) + os.setsid() + try: + pid = os.fork() + if pid > 0: + self.print_info("PID: %d" % pid) + sys.exit(0) + except OSError: + sys.exit(1) + os.chdir("/") + os.umask(0) + dev_null = open("/dev/null", "r+") + os.dup2(dev_null.fileno(), sys.stdout.fileno()) + os.dup2(dev_null.fileno(), sys.stderr.fileno()) + os.dup2(dev_null.fileno(), sys.stdin.fileno()) + + def get_client(self, nickname: bytes) -> Optional[Client]: + return self.nicknames.get(irc_lower(nickname)) + + def has_channel(self, name: bytes) -> bool: + return irc_lower(name) in self.channels + + def get_channel(self, channelname: bytes) -> Channel: + if irc_lower(channelname) in self.channels: + channel = self.channels[irc_lower(channelname)] + else: + channel = Channel(self, channelname) + self.channels[irc_lower(channelname)] = channel + return channel + + def get_motd_lines(self) -> Collection[str]: + if self.motdfile: + try: + return open(self.motdfile).readlines() + except IOError: + return ["Could not read MOTD file %r." % self.motdfile] + else: + return [] + + def print_info(self, msg: str) -> None: + if self.verbose: + print(msg) + sys.stdout.flush() + if self.logger: + self.logger.info(msg) + + def print_debug(self, msg: str) -> None: + if self.debug: + print(msg) + sys.stdout.flush() + if self.logger: + self.logger.debug(msg) + + def print_error(self, msg: str) -> None: + sys.stderr.write(f"{msg}\n") + if self.logger: + self.logger.error(msg) + + def client_changed_nickname( + self, client: Client, oldnickname: Optional[bytes] + ) -> None: + if oldnickname: + del self.nicknames[irc_lower(oldnickname)] + self.nicknames[irc_lower(client.nickname)] = client + + def remove_member_from_channel( + self, client: Client, channelname: bytes + ) -> None: + if irc_lower(channelname) in self.channels: + channel = self.channels[irc_lower(channelname)] + channel.remove_client(client) + + def remove_client(self, client: Client, quitmsg: bytes) -> None: + client.message_related(b":%s QUIT :%s" % (client.prefix, quitmsg)) + for x in client.channels.values(): + client.channel_log(x, b"quit (%s)" % quitmsg, meta=True) + x.remove_client(client) + if client.nickname and irc_lower(client.nickname) in self.nicknames: + del self.nicknames[irc_lower(client.nickname)] + del self.clients[client.socket] + + def remove_channel(self, channel: Channel) -> None: + del self.channels[irc_lower(channel.name)] + + def start(self) -> None: + serversockets = [] + for port in self.ports: + s = socket.socket( + socket.AF_INET6 if self.ipv6 else socket.AF_INET, + socket.SOCK_STREAM, + ) + s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + try: + s.bind((self.address, port)) + except socket.error as e: + self.print_error(f"Could not bind port {port}: {e}.") + sys.exit(1) + s.listen(5) + serversockets.append(s) + del s + self.print_info(f"Listening on port {port}.") + if self.chroot: + os.chdir(self.chroot) + os.chroot(self.chroot) + self.print_info(f"Changed root directory to {self.chroot}") + if self.setuid: + os.setgid(self.setuid[1]) + os.setuid(self.setuid[0]) + self.print_info( + f"Setting uid:gid to {self.setuid[0]}:{self.setuid[1]}" + ) + + self.init_logging() + try: + self.run(serversockets) + except Exception: + if self.logger: + self.logger.exception("Fatal exception") + raise + + def init_logging(self) -> None: + if not self.log_file: + return + + log_level = logging.INFO + if self.debug: + log_level = logging.DEBUG + self.logger = logging.getLogger("miniircd") + formatter = logging.Formatter( + "%(asctime)s - %(name)s[%(process)d] - %(levelname)s - %(message)s" + ) + fh = RotatingFileHandler( + self.log_file, + maxBytes=self.log_max_bytes, + backupCount=self.log_count, + ) + fh.setLevel(log_level) + fh.setFormatter(formatter) + self.logger.setLevel(log_level) + self.logger.addHandler(fh) + + def run(self, serversockets: List[Socket]) -> None: + last_aliveness_check = time.time() + while True: + iwtd, owtd, ewtd = select.select( + serversockets + [x.socket for x in self.clients.values()], + [ + x.socket + for x in self.clients.values() + if x.write_queue_size() > 0 + ], + [], + 10, + ) + for x in iwtd: + if x in self.clients: + self.clients[x].socket_readable_notification() + else: + conn, addr = x.accept() + if self.ssl_key_file: + try: + conn = self.ssl.wrap_socket( + conn, + server_side=True, + certfile=self.ssl_cert_file, + keyfile=self.ssl_key_file, + ) + except Exception as e: + self.print_error( + "SSL error for connection from" + f" {addr[0]}:{addr[1]}: {e}" + ) + continue + try: + self.clients[conn] = Client(self, conn) + self.print_info( + f"Accepted connection from {addr[0]}:{addr[1]}." + ) + except socket.error: + try: + conn.close() + except Exception: + pass + for x in owtd: + if x in self.clients: # client may have been disconnected + self.clients[x].socket_writable_notification() + now = time.time() + if last_aliveness_check + 10 < now: + for client in list(self.clients.values()): + client.check_aliveness() + last_aliveness_check = now + + +_ircstring_translation = bytes.maketrans( + (string.ascii_lowercase.upper() + "[]\\^").encode(), + (string.ascii_lowercase + "{}|~").encode(), +) + + +def irc_lower(s: bytes) -> bytes: + return s.translate(_ircstring_translation) + + +def main() -> None: + ap = ArgumentParser( + description="miniircd is a small and limited IRC server.", + ) + ap.add_argument("--version", action="version", version=VERSION) + ap.add_argument( + "--channel-log-dir", + metavar="X", + help="store channel log in directory X", + ) + ap.add_argument( + "-d", "--daemon", action="store_true", help="fork and become a daemon" + ) + ap.add_argument("--ipv6", action="store_true", help="use IPv6") + ap.add_argument( + "--debug", action="store_true", help="print debug messages to stdout" + ) + ap.add_argument( + "--listen", metavar="X", help="listen on specific IP address X" + ) + ap.add_argument( + "--log-count", + metavar="X", + default=10, + type=int, + help="keep X log files; default: %(default)s", + ) + ap.add_argument("--log-file", metavar="X", help="store log in file X") + ap.add_argument( + "--log-max-size", + metavar="X", + default=10, + type=int, + help="set maximum log file size to X MiB; default: %(default)s MiB", + ) + ap.add_argument( + "--motd", metavar="X", help="display file X as message of the day" + ) + ap.add_argument("--pid-file", metavar="X", help="write PID to file X") + ap.add_argument( + "-p", + "--password", + metavar="X", + help="require connection password X; default: no password", + ) + ap.add_argument( + "--password-file", + metavar="X", + help=( + "require connection password stored in file X;" + " default: no password" + ), + ) + ap.add_argument( + "--ports", + metavar="X", + help="listen to ports X (a list separated by comma or whitespace);" + " default: 6667 or 6697 if SSL is enabled", + ) + ap.add_argument( + "--ssl-cert-file", + metavar="FILE", + help="enable SSL with PEM certificate in FILE", + ) + ap.add_argument( + "--ssl-key-file", + metavar="FILE", + help="enable SSL with PEM key in FILE", + ) + ap.add_argument( + "-s", + "--ssl-pem-file", + metavar="FILE", + help="enable SSL with key and certificate combined in FILE", + ) + ap.add_argument( + "--state-dir", + metavar="X", + help="save persistent channel state (topic, key) in directory X", + ) + ap.add_argument( + "--verbose", + action="store_true", + help="be verbose (print some progress messages to stdout)", + ) + ap.add_argument( + "--cloak", metavar="X", help="report X as the host for all clients" + ) + if os.name == "posix": + ap.add_argument( + "--chroot", + metavar="X", + help="change filesystem root to directory X after startup" + " (requires root)", + ) + ap.add_argument( + "--setuid", + metavar="U[:G]", + help="change process user (and optionally group) after startup" + " (requires root)", + ) + + args = ap.parse_args() + + if bool(args.ssl_cert_file) != bool(args.ssl_key_file): + args.error("Must specify both --ssl-cert-file and --ssl-key-file") + if args.ssl_pem_file: + if args.ssl_cert_file: + args.error( + "Cannot specify both --ssl-pem-file and --ssl-cert-file" + ) + args.ssl_cert_file = args.ssl_pem_file + args.ssl_key_file = args.ssl_pem_file + + if os.name != "posix": + args.chroot = False + args.setuid = False + if args.debug: + args.verbose = True + if args.ports is None: + if args.ssl_key_file is None: + args.ports = "6667" + else: + args.ports = "6697" + if args.chroot and os.getuid() != 0: + ap.error("Must be root to use --chroot") + if args.setuid: + from pwd import getpwnam + from grp import getgrnam + + if os.getuid() != 0: + ap.error("Must be root to use --setuid") + matches = args.setuid.split(":") + if len(matches) == 2: + args.setuid = ( + getpwnam(matches[0]).pw_uid, + getgrnam(matches[1]).gr_gid, + ) + elif len(matches) == 1: + args.setuid = ( + getpwnam(matches[0]).pw_uid, + getpwnam(matches[0]).pw_gid, + ) + else: + ap.error( + "Specify a user, or user and group separated by a colon," + " e.g. --setuid daemon, --setuid nobody:nobody" + ) + if ( + os.name == "posix" + and not args.setuid + and (os.getuid() == 0 or os.getgid() == 0) + ): + ap.error( + "Running this service as root is not recommended. Use the" + " --setuid option to switch to an unprivileged account after" + " startup. If you really intend to run as root, use" + ' "--setuid root".' + ) + + ports = [] + for port in re.split(r"[,\s]+", args.ports): + try: + ports.append(int(port)) + except ValueError: + ap.error("bad port: %r" % port) + args.ports = ports + server = Server(args) + if args.pid_file: + args.pid_file = os.path.abspath(args.pid_file) + if args.daemon: + server.daemonize() + if args.pid_file: + server.make_pid_file(args.pid_file) + try: + server.start() + except KeyboardInterrupt: + server.print_error("Interrupted.") + finally: + if args.pid_file: + server.remove_pid_file(args.pid_file) + + +if __name__ == "__main__": + main() diff --git a/bin/pinentry-fuzzel b/bin/pinentry-fuzzel @@ -0,0 +1,87 @@ +#!/bin/sh + +FUZZEL="fuzzel --dmenu --password" +DESC="" +ERROR="" +PROMPT="" + +echo "OK Please go ahead" +while read cmd rest; do + + if [ -z "$cmd" ]; then + continue; + fi + + case "$cmd" in + \#*) + echo "OK" + ;; + + GETINFO) + case "$rest" in + flavor) + echo "D rofi" + echo "OK" + ;; + version) + echo "D 0.1" + echo "OK" + ;; + ttyinfo) + echo "D - - -" + echo "OK" + ;; + pid) + echo "D $$" + echo "OK" + ;; + esac + ;; + + SETDESC) + DESC=$rest + echo "OK" + ;; + + SETERROR) + ERROR=$( echo _ERO_${rest}_ERC_ ) + echo "OK" + ;; + + SETPROMPT) + PROMPT=$(echo $rest | tr -d ':') + echo "OK" + ;; + + GETPIN) + MESSAGE=$( echo "$ERROR$DESC" | sed -e "s|%0A||g" \ + -e "s|%22||g" \ + -e "s|key:|key:\n|g" \ + -e "s|>|>\n|g" \ + -e "s|<|\&lt;|g" \ + -e "s|>|\&gt;|g" \ + -e "s|,created|,\ncreated|g" \ + -e "s|_ERO_|<span fgcolor='#ab4642'>|g" \ + -e "s|_ERC_|</span>\n|g" ) + + # display gpg messages lowercase + MESSAGE=$( echo $MESSAGE | tr '[:upper:]' '[:lower:]') + + _PP=$($FUZZEL) + + if [ -n "$_PP" ]; then + echo "D $_PP" + fi + echo "OK" + ;; + + BYE) + echo "OK closing connection" + exit 0 + ;; + + *) + echo "OK" + ;; + esac + done diff --git a/bin/pinentry-rofi b/bin/pinentry-rofi @@ -0,0 +1,90 @@ +#!/bin/sh + +ROFI="rofi -dmenu -input /dev/null -password -lines 0" +DESC="" +ERROR="" +PROMPT="" + +echo "OK Please go ahead" +while read cmd rest; do + + if [ -z "$cmd" ]; then + continue + fi + + case "$cmd" in + \#*) + echo "OK" + ;; + + GETINFO) + case "$rest" in + flavor) + echo "D rofi" + echo "OK" + ;; + version) + echo "D 0.1" + echo "OK" + ;; + ttyinfo) + echo "D - - -" + echo "OK" + ;; + pid) + echo "D $$" + echo "OK" + ;; + esac + ;; + + SETDESC) + DESC=$rest + echo "OK" + ;; + + SETERROR) + ERROR=$(echo _ERO_${rest}_ERC_) + echo "OK" + ;; + + SETPROMPT) + PROMPT=$(echo $rest | tr -d ':') + echo "OK" + ;; + + GETPIN) + # this adds some markup flavor and changes the output so that rofi LOVES + # what we give it + MESSAGE=$(echo "$ERROR$DESC" | sed -e "s|%0A||g" \ + -e "s|%22||g" \ + -e "s|key:|key:\n|g" \ + -e "s|>|>\n|g" \ + -e "s|<|\&lt;|g" \ + -e "s|>|\&gt;|g" \ + -e "s|,created|,\ncreated|g" \ + -e "s|_ERO_|<span fgcolor='#ab4642'>|g" \ + -e "s|_ERC_|</span>\n|g") + + # lowercase + MESSAGE=$(echo $MESSAGE | tr '[:upper:]' '[:lower:]') + + rofi_cmd="$ROFI -p \"password: \" -mesg \"$MESSAGE\"" + _PP=$($ROFI -p "password: " -mesg "$MESSAGE") + + if [ -n "$_PP" ]; then + echo "D $_PP" + fi + echo "OK" + ;; + + BYE) + echo "OK closing connection" + exit 0 + ;; + + *) + echo "OK" + ;; + esac +done diff --git a/bin/rofi-bluetooth b/bin/rofi-bluetooth @@ -0,0 +1,317 @@ +#!/usr/bin/env bash +# __ _ _ _ _ _ _ +# _ __ ___ / _(_) | |__ | |_ _ ___| |_ ___ ___ | |_| |__ +# | '__/ _ \| |_| |_____| '_ \| | | | |/ _ \ __/ _ \ / _ \| __| '_ \ +# | | | (_) | _| |_____| |_) | | |_| | __/ || (_) | (_) | |_| | | | +# |_| \___/|_| |_| |_.__/|_|\__,_|\___|\__\___/ \___/ \__|_| |_| +# +# Author: Nick Clyde (clydedroid) +# +# A script that generates a rofi menu that uses bluetoothctl to +# connect to bluetooth devices and display status info. +# +# Inspired by networkmanager-dmenu (https://github.com/firecat53/networkmanager-dmenu) +# Thanks to x70b1 (https://github.com/polybar/polybar-scripts/tree/master/polybar-scripts/system-bluetooth-bluetoothctl) +# +# Depends on: +# Arch repositories: rofi, bluez-utils (contains bluetoothctl) + +# Constants +divider="---------" +goback="Back" + +# Checks if bluetooth controller is powered on +power_on() { + if bluetoothctl show | grep -q "Powered: yes"; then + return 0 + else + return 1 + fi +} + +# Toggles power state +toggle_power() { + if power_on; then + bluetoothctl power off + show_menu + else + if rfkill list bluetooth | grep -q 'blocked: yes'; then + rfkill unblock bluetooth && sleep 3 + fi + bluetoothctl power on + show_menu + fi +} + +# Checks if controller is scanning for new devices +scan_on() { + if bluetoothctl show | grep -q "Discovering: yes"; then + echo "Scan: on" + return 0 + else + echo "Scan: off" + return 1 + fi +} + +# Toggles scanning state +toggle_scan() { + if scan_on; then + kill $(pgrep -f "bluetoothctl scan on") + bluetoothctl scan off + show_menu + else + bluetoothctl scan on & + echo "Scanning..." + sleep 5 + show_menu + fi +} + +# Checks if controller is able to pair to devices +pairable_on() { + if bluetoothctl show | grep -q "Pairable: yes"; then + echo "Pairable: on" + return 0 + else + echo "Pairable: off" + return 1 + fi +} + +# Toggles pairable state +toggle_pairable() { + if pairable_on; then + bluetoothctl pairable off + show_menu + else + bluetoothctl pairable on + show_menu + fi +} + +# Checks if controller is discoverable by other devices +discoverable_on() { + if bluetoothctl show | grep -q "Discoverable: yes"; then + echo "Discoverable: on" + return 0 + else + echo "Discoverable: off" + return 1 + fi +} + +# Toggles discoverable state +toggle_discoverable() { + if discoverable_on; then + bluetoothctl discoverable off + show_menu + else + bluetoothctl discoverable on + show_menu + fi +} + +# Checks if a device is connected +device_connected() { + device_info=$(bluetoothctl info "$1") + if echo "$device_info" | grep -q "Connected: yes"; then + return 0 + else + return 1 + fi +} + +# Toggles device connection +toggle_connection() { + if device_connected "$1"; then + bluetoothctl disconnect "$1" + device_menu "$device" + else + bluetoothctl connect "$1" + device_menu "$device" + fi +} + +# Checks if a device is paired +device_paired() { + device_info=$(bluetoothctl info "$1") + if echo "$device_info" | grep -q "Paired: yes"; then + echo "Paired: yes" + return 0 + else + echo "Paired: no" + return 1 + fi +} + +# Toggles device paired state +toggle_paired() { + if device_paired "$1"; then + bluetoothctl remove "$1" + device_menu "$device" + else + bluetoothctl pair "$1" + device_menu "$device" + fi +} + +# Checks if a device is trusted +device_trusted() { + device_info=$(bluetoothctl info "$1") + if echo "$device_info" | grep -q "Trusted: yes"; then + echo "Trusted: yes" + return 0 + else + echo "Trusted: no" + return 1 + fi +} + +# Toggles device connection +toggle_trust() { + if device_trusted "$1"; then + bluetoothctl untrust "$1" + device_menu "$device" + else + bluetoothctl trust "$1" + device_menu "$device" + fi +} + +# Prints a short string with the current bluetooth status +# Useful for status bars like polybar, etc. +print_status() { + if power_on; then + printf '' + + paired_devices_cmd="devices Paired" + # Check if an outdated version of bluetoothctl is used to preserve backwards compatibility + if (( $(echo "$(bluetoothctl version | cut -d ' ' -f 2) < 5.65" | bc -l) )); then + paired_devices_cmd="paired-devices" + fi + + mapfile -t paired_devices < <(bluetoothctl $paired_devices_cmd | grep Device | cut -d ' ' -f 2) + counter=0 + + for device in "${paired_devices[@]}"; do + if device_connected "$device"; then + device_alias=$(bluetoothctl info "$device" | grep "Alias" | cut -d ' ' -f 2-) + + if [ $counter -gt 0 ]; then + printf ", %s" "$device_alias" + else + printf " %s" "$device_alias" + fi + + ((counter++)) + fi + done + printf "\n" + else + echo "" + fi +} + +# A submenu for a specific device that allows connecting, pairing, and trusting +device_menu() { + device=$1 + + # Get device name and mac address + device_name=$(echo "$device" | cut -d ' ' -f 3-) + mac=$(echo "$device" | cut -d ' ' -f 2) + + # Build options + if device_connected "$mac"; then + connected="Connected: yes" + else + connected="Connected: no" + fi + paired=$(device_paired "$mac") + trusted=$(device_trusted "$mac") + options="$connected\n$paired\n$trusted\n$divider\n$goback\nExit" + + # Open rofi menu, read chosen option + chosen="$(echo -e "$options" | $rofi_command "$device_name")" + + # Match chosen option to command + case "$chosen" in + "" | "$divider") + echo "No option chosen." + ;; + "$connected") + toggle_connection "$mac" + ;; + "$paired") + toggle_paired "$mac" + ;; + "$trusted") + toggle_trust "$mac" + ;; + "$goback") + show_menu + ;; + esac +} + +# Opens a rofi menu with current bluetooth status and options to connect +show_menu() { + # Get menu options + if power_on; then + power="Power: on" + + # Human-readable names of devices, one per line + # If scan is off, will only list paired devices + devices=$(bluetoothctl devices | grep Device | cut -d ' ' -f 3-) + + # Get controller flags + scan=$(scan_on) + pairable=$(pairable_on) + discoverable=$(discoverable_on) + + # Options passed to rofi + options="$devices\n$divider\n$power\n$scan\n$pairable\n$discoverable\nExit" + else + power="Power: off" + options="$power\nExit" + fi + + # Open rofi menu, read chosen option + chosen="$(echo -e "$options" | $rofi_command "Bluetooth")" + + # Match chosen option to command + case "$chosen" in + "" | "$divider") + echo "No option chosen." + ;; + "$power") + toggle_power + ;; + "$scan") + toggle_scan + ;; + "$discoverable") + toggle_discoverable + ;; + "$pairable") + toggle_pairable + ;; + *) + device=$(bluetoothctl devices | grep "$chosen") + # Open a submenu if a device is selected + if [[ $device ]]; then device_menu "$device"; fi + ;; + esac +} + +# Rofi command to pipe into, can add any options here +rofi_command="rofi -dmenu $* -p" + +case "$1" in + --status) + print_status + ;; + *) + show_menu + ;; +esac diff --git a/bin/screen-lock b/bin/screen-lock @@ -0,0 +1,28 @@ +#!/bin/bash + +# check if corrupter is installed +if ! command -v corrupter &>/dev/null; then + notify-send "swaylock" "corrupter could not be found" + exit +fi + +sshot="/tmp/swaylockscreen.png" +blurred="/tmp/swaylockscreen-blurred.png" +grim $sshot && + # ffmpeg -i $sshot -filter_complex boxblur=lr=8:lp=2 -y $blurred && \ + corrupter -mag 2 -boffset 20 $sshot $blurred && \ +swaylock -i $blurred \ + --font "Hack Nerd Font Mono" \ + --font-size=25 \ + --text-color ffffffff \ + --ring-color c678dd \ + --key-hl-color 6eaafb \ + --text-color abb2bf \ + --ring-ver-color 98c379 \ + --inside-ver-color 98c379 \ + --line-color 00000000 \ + --inside-color 282c34 \ + --separator-color 00000000 \ + --indicator-radius=100 + +rm -f $sshot $blurred diff --git a/bin/screen-record b/bin/screen-record @@ -0,0 +1,32 @@ +#!/bin/bash + +if [ $(pgrep screen-record) != 0 ]; then + dimensions=$(slurp) + audio=$(echo -e "none\ndesktop\nmicrophone" | rofi -dmenu -p 'screencast audio') + [ ! ${audio} ] && exit + file="$HOME/vid/screencast/screencast-$(date +%F-%H-%M-%S).mp4" + # cmd="wf-recorder -f \"${file}\" -g \"${dimensions}\" -c h264_vaapi -d /dev/dri/renderD128" + cmd="wf-recorder -f \"${file}\" -g \"${dimensions}\"" + + # pactl list | grep -A2 'Source #' | grep 'Name: ' | cut -d" " -f2 + + case $audio in + desktop) + cmd+=" --audio=\"bluez_output.AC_BF_71_0C_52_70.1.monitor\"" + bash -c "${cmd}" + ;; + microphone) + cmd+=" --audio=\"NoiseTorch\"" + bash -c "${cmd}" + ;; + none) + bash -c "${cmd}" + ;; + cancel) + exit + ;; + esac +else + pkill --signal SIGINT wf-recorder + notify-send "wf-recorder" "screencast saved!" +fi diff --git a/bin/snippet-rofi b/bin/snippet-rofi @@ -0,0 +1,10 @@ +#!/bin/env sh + +cd ~/notes/snippets + +selected_file=$(fd . ~/notes/snippets | sed 's|^./||' | rofi -dmenu -p "choose snippet to place in clipboard") + +if [ -n "$selected_file" ]; then + snippet_content=$(cat "$selected_file") + echo -n "$snippet_content" | wl-copy +fi diff --git a/bin/startw b/bin/startw @@ -0,0 +1,4 @@ +#!/bin/zsh +dbus-daemon --session --nofork --nopidfile --address=$DBUS_SESSION_BUS_ADDRESS & +/usr/bin/sway +# /usr/bin/sway -V > /tmp/sway.log 2>&1 diff --git a/bin/tessen b/bin/tessen @@ -0,0 +1,870 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: GPL-2.0-only +# Copyright (C) 2021-2022 Ayush Agarwal <ayushnix at fastmail dot com> +# +# vim: set expandtab ts=2 sw=2 sts=2: +# +# tessen - a data selection interface for pass and gopass on Wayland +# ------------------------------------------------------------------------------ + +# don't leak password data if debug mode is enabled +set +x + +# GLOBAL VARIABLES +readonly tsn_version="2.1.2" +declare pass_backend dmenu_backend tsn_action tsn_config +declare -a dmenu_backend_opts tmp_tofi_opts tmp_rofi_opts tmp_wofi_opts +declare tsn_userkey tsn_urlkey tsn_autokey tsn_delay tsn_web_browser +# show both actions, 'autotype' and 'copy', to choose from by default +tsn_action="default" +tsn_otp=false +# initialize default values for keys +tsn_userkey="user" +tsn_urlkey="url" +tsn_autokey="autotype" +tsn_delay=100 +# initialize the default location of the config file +tsn_config="${XDG_CONFIG_HOME:-$HOME/.config}"/tessen/config +# variables with sensitive data which will be manually unset using _clear +declare tsn_passfile tsn_username tsn_password tsn_url tsn_autotype chosen_key +declare -A tsn_passdata + +# FIRST MENU: generate a list of pass files, let the user select one +get_pass_files() { + local tmp_prefix="${PASSWORD_STORE_DIR:-$HOME/.password-store}" + if ! [[ -d $tmp_prefix ]]; then + _die "password store directory not found" + fi + + local -a tmp_pass_files + # temporarily enable globbing, get the list of all gpg files recursively, + # remove PASSWORD_STORE_DIR from the file names, and remove the '.gpg' suffix + shopt -s nullglob globstar + tmp_pass_files=("$tmp_prefix"/**/*.gpg) + tmp_pass_files=("${tmp_pass_files[@]#"$tmp_prefix"/}") + tmp_pass_files=("${tmp_pass_files[@]%.gpg}") + shopt -u nullglob globstar + + tsn_passfile="$(printf "%s\n" "${tmp_pass_files[@]}" \ + | "$dmenu_backend" "${dmenu_backend_opts[@]}")" + + if ! [[ -f "$tmp_prefix/$tsn_passfile".gpg ]]; then + _die + fi + + unset -v tmp_pass_files tmp_prefix +} + +# FIRST MENU: generate a list of gopass files, let the user select one +get_gopass_files() { + local line path_files file mount_name tmp_tsn_passfile + local -A tmp_gopass_files + local -a mount_name_arr + + # why not use `gopass config path` and `gopass ls -f`? + shopt -s nullglob globstar + while read -r line || [[ -n $line ]]; do + # we assume that we'll encounter `path: ...` only once and as soon as we + # do, we parse the list of all the files inside the dir and store them in + # an associative array with the name of the files as the index and the path + # as the value + if [[ $line == path* ]] && [[ -d ${line#* } ]]; then + path_files=("${line#* }"/**/*.gpg) + path_files=("${path_files[@]#"${line#* }"/}") + path_files=("${path_files[@]%.gpg}") + for file in "${path_files[@]}"; do + tmp_gopass_files["$file"]="${line#* }" + done + fi + # similarly, we go through the mount points, generate the list of files + # inside those mount points, add those files to the associative array with + # the file names as the index and the location of the mount point as the + # value + if [[ $line == mount* ]]; then + # remove the quotes from the parsed line + line="${line//\"/}" + # the mount name needs to be extracted to distinguish files with + # potentially identical names + mount_name="${line#mount *}" + mount_name="${mount_name% =>*}" + mount_name_arr+=("$mount_name") + if [[ -d ${line#*=> } ]]; then + path_files=("${line#*=> }"/**/*.gpg) + path_files=("${path_files[@]#"${line#*=> }"/}") + path_files=("${path_files[@]%.gpg}") + path_files=("${path_files[@]/#/"$mount_name/"}") + for file in "${path_files[@]}"; do + tmp_gopass_files["$file"]="${line#*=> }" + done + fi + fi + done < <(gopass config) + shopt -u nullglob globstar + + # the actual menu + tsn_passfile="$(printf "%s\n" "${!tmp_gopass_files[@]}" \ + | "$dmenu_backend" "${dmenu_backend_opts[@]}")" + + if [[ -z $tsn_passfile ]]; then + _die + fi + + # remove the mount name for the path check to be successful + # initialize the temp variable with the value of tsn_passfile in case an + # entry from the gopass path is chosen + tmp_tsn_passfile="$tsn_passfile" + for idx in "${mount_name_arr[@]}"; do + if [[ ${tsn_passfile%%/*} == "$idx" ]]; then + tmp_tsn_passfile="${tsn_passfile#*/}" + fi + done + + # we had to use an associative array to keep track of the absolute path of + # the selected file because it is possible to give invalid input to dmenu + # while making a selection and tessen should exit in that case + if [[ -n ${tmp_gopass_files["$tsn_passfile"]} ]]; then + if ! [[ -f "${tmp_gopass_files["$tsn_passfile"]}"/"$tmp_tsn_passfile".gpg ]]; then + _die "the selected file was not found" + fi + fi + + unset -v tmp_gopass_files line path_files file mount_name mount_name_arr tmp_tsn_passfile +} + +# parse the password store file for username, password, otp, custom autotype, +# and other key value pairs +get_pass_data() { + local -a passdata + local keyval_regex otp_regex idx key val + + if [[ $pass_backend == "pass" ]]; then + mapfile -t passdata < <(pass show "$tsn_passfile" 2> /dev/null) + if [[ ${#passdata[@]} -eq 0 ]]; then + _die "$tsn_passfile is empty" + fi + elif [[ $pass_backend == "gopass" ]]; then + # gopass show -n -f is weird because it emits a first line 'Secret: + # truncated-file-name' and that doesn't get assigned to a variable. but if + # I redirect stdout to /dev/null, that first line gets redirected as well. + # there doesn't seem to be any way to disable printing this first line. + mapfile -t passdata < <(gopass show -n -f "$tsn_passfile" 2> /dev/null) + if [[ ${#passdata[@]} -eq 0 ]]; then + _die "$tsn_passfile is empty" + fi + fi + + # the key can contain alphanumerics, spaces, hyphen, underscore, plus, at, + # and hash + # + # the value can contain anything but `key:` and `val` should be separated + # with a whitespace + keyval_regex='^[[:alnum:][:blank:]+#@_-]+:[[:blank:]].+$' + # parse the 'otpauth://' URI + # this regex is borrowed from pass-otp at commit 3ba564c + # + # note that OTP support in gopass has been deprecated and for good reasons + # I, for one, don't see how storing OTPs in the same place as storing your + # passwords is a sane idea + # https://github.com/gopasspw/gopass/blob/master/docs/features.md#adding-otp-secrets + otp_regex='^otpauth:\/\/(totp|hotp)(\/(([^:?]+)?(:([^:?]*))?)(:([0-9]+))?)?\?(.+)$' + + # the first line should contain the only the password + # this assumes the caveat highlighted earlier about gopass' behavior + tsn_password="${passdata[0]}" + + # each key should be unique + # if non-unique keys are present, the value of the first non-unique key will + # be considered + # in addition, the 'username', 'autotype', 'url', and 'password' keys are + # considered as case insensitive + for idx in "${passdata[@]:1}"; do + key="${idx%%:*}" + val="${idx#*: }" + # keys with the case insensitive name 'password' are ignored + if [[ ${key,,} == "password" ]]; then + continue + elif [[ ${key,,} =~ ^$tsn_userkey$ ]] && [[ -z ${tsn_username} ]]; then + tsn_userkey="${key,,}" + tsn_username="$val" + elif [[ ${key,,} =~ ^$tsn_autokey$ ]] && [[ -z ${tsn_autotype} ]]; then + tsn_autokey="${key,,}" + tsn_autotype="$val" + elif [[ ${key,,} =~ ^$tsn_urlkey$ ]] && [[ -z ${tsn_url} ]]; then + tsn_urlkey="${key,,}" + tsn_url="$val" + elif [[ $idx =~ $otp_regex ]] && [[ $tsn_otp == "false" ]]; then + tsn_otp=true + elif [[ $idx =~ $keyval_regex ]] && [[ -z ${tsn_passdata["$key"]} ]]; then + tsn_passdata["$key"]="$val" + fi + done + + # if $tsn_userkey isn't found, use the basename of file as username + # also set the value of the tsn_userkey to the default value + # this prevents the userkey from showing up as a regex in case a user has set + # it in the config file + # the same goes for other custom key variables + if [[ -z $tsn_username ]]; then + tsn_username="${tsn_passfile##*/}" + tsn_userkey="user" + fi + if [[ -z $tsn_autotype ]]; then + tsn_autokey="autotype" + fi + if [[ -z $tsn_url ]]; then + tsn_urlkey="url" + fi + + unset -v passdata keyval_regex otp_regex idx key val +} + +# SECOND MENU: show a list of possible keys to choose from for autotyping or +# copying, depending on the value of tsn_action +# THIRD MENU: optional, this will show up if tsn_action is blank +get_key() { + local -a key_arr + local ch flag=false + + # the 2nd menu for autotype, both, and the default actions will be the same + # and the autotype key will be present in these cases + # when tsn_action is set to copy, the autotype key shouldn't be shown in the 2nd menu + case "$tsn_action" in + autotype | both | default) + if [[ $1 == "key_list" ]]; then + if [[ $tsn_otp == "false" ]] && [[ -z $tsn_url ]]; then + key_arr=("$tsn_autokey" "$tsn_userkey" "password" "${!tsn_passdata[@]}") + elif [[ $tsn_otp == "false" ]] && [[ -n $tsn_url ]]; then + key_arr=("$tsn_autokey" "$tsn_userkey" "password" "$tsn_urlkey" "${!tsn_passdata[@]}") + elif [[ $tsn_otp == "true" ]] && [[ -z $tsn_url ]]; then + key_arr=("$tsn_autokey" "$tsn_userkey" "password" "otp" "${!tsn_passdata[@]}") + elif [[ $tsn_otp == "true" ]] && [[ -n $tsn_url ]]; then + key_arr=("$tsn_autokey" "$tsn_userkey" "password" "otp" "$tsn_urlkey" "${!tsn_passdata[@]}") + fi + fi + # the (optional) third menu, its appearance depends on tsn_action being default + if [[ $tsn_action == "default" ]] && [[ $1 == "option" ]]; then + key_arr=("$tsn_autokey" "copy") + # the (optional) third menu if tsn_urlkey is chosen, it depends on + # tsn_action being default + elif [[ $tsn_action == "default" ]] && [[ $1 == "$tsn_urlkey" ]]; then + key_arr=("open" "copy") + fi + ;; + copy) + if [[ $1 == "key_list" ]]; then + if [[ $tsn_otp == "false" ]] && [[ -z $tsn_url ]]; then + key_arr=("$tsn_userkey" "password" "${!tsn_passdata[@]}") + elif [[ $tsn_otp == "false" ]] && [[ -n $tsn_url ]]; then + key_arr=("$tsn_userkey" "password" "$tsn_urlkey" "${!tsn_passdata[@]}") + elif [[ $tsn_otp == "true" ]] && [[ -z $tsn_url ]]; then + key_arr=("$tsn_userkey" "password" "otp" "${!tsn_passdata[@]}") + elif [[ $tsn_otp == "true" ]] && [[ -n $tsn_url ]]; then + key_arr=("$tsn_userkey" "password" "otp" "$tsn_urlkey" "${!tsn_passdata[@]}") + fi + fi + ;; + esac + + # a global variable to hold the selected key for key_menu + chosen_key="$(printf "%s\n" "${key_arr[@]}" | "$dmenu_backend" "${dmenu_backend_opts[@]}")" + + # validate the chosen key, if it doesn't exist, exit + for ch in "${key_arr[@]}"; do + if [[ $chosen_key == "$ch" ]]; then + flag=true + break + fi + done + if [[ $flag == "false" ]]; then + _die + fi + + unset -v key_arr ch flag +} + +# SECOND MENU: use 'get_key()' to show a list of possible keys to choose from +key_menu() { + get_key key_list + + case "$chosen_key" in + "$tsn_autokey") auto_type_def ;; + "$tsn_userkey") key_action "$tsn_username" ;; + password) key_action "$tsn_password" ;; + otp) key_otp ;; + "$tsn_urlkey") key_action "$tsn_urlkey" ;; + *) key_action "${tsn_passdata["$chosen_key"]}" ;; + esac +} + +# this function checks the value of tsn_action and decides if the third menu +# should be presented or not +# in case it receives a parameter called "url", autotype becomes equivalent to +# opening the url in the web browser +key_action() { + local arg="$1" + + case "$tsn_action" in + autotype) + if [[ $arg == "$tsn_urlkey" ]]; then + key_open_url || _die + return 0 + fi + auto_type "$arg" + ;; + copy) + if [[ $arg == "$tsn_urlkey" ]]; then + wld_copy "$tsn_url" || _die + return 0 + fi + wld_copy "$arg" + ;; + both) + if [[ $arg == "$tsn_urlkey" ]]; then + key_open_url + wld_copy "$tsn_url" + else + printf "%s" "$arg" | wtype -s "$tsn_delay" - + wld_copy "$arg" + fi + ;; + default) + if [[ $arg == "$tsn_urlkey" ]]; then + get_key "$tsn_urlkey" + if [[ $chosen_key == "open" ]]; then + key_open_url || _die + return 0 + else + wld_copy "$tsn_url" + fi + else + get_key option + if [[ $chosen_key == "$tsn_autokey" ]]; then + auto_type "$arg" + else + wld_copy "$arg" + fi + fi + ;; + esac + + unset -v arg +} + +# THIRD MENU: optional, this function is used if an 'otpauth://' URI is found +# note that OTP support in gopass is deprecated and if they end up removing +# support for it, we'll have to make changes here as well +key_otp() { + local tmp_otp + + if [[ $pass_backend == "pass" ]] && ! pass otp -h > /dev/null 2>&1; then + _die "pass-otp is not installed" + fi + + if [[ $pass_backend == "pass" ]]; then + tmp_otp="$(pass otp "$tsn_passfile")" + elif [[ $pass_backend == "gopass" ]]; then + tmp_otp="$(gopass otp -o "$tsn_passfile")" + fi + + if ! [[ $tmp_otp =~ ^[[:digit:]]+$ ]]; then + _die "invalid OTP detected" + fi + key_action "$tmp_otp" + + unset -v tmp_otp +} + +# open the url using either xdg-open or tsn_web_browser +# if tsn_web_browser is defined, xdg-open won't be used +key_open_url() { + if [[ -n $tsn_web_browser ]]; then + "$tsn_web_browser" "$tsn_url" > /dev/null 2>&1 || { + printf "%s\n" "$tsn_web_browser was unable to open '$tsn_url'" >&2 + return 1 + } + elif is_installed xdg-open; then + xdg-open "$tsn_url" 2> /dev/null || { + printf "%s\n" "xdg-open was unable to open '$tsn_url'" >&2 + return 1 + } + else + _die "failed to open '$tsn_urlkey'" + fi +} + +# SECOND MENU: the default autotype function, either autotype the username and +# password or the custom autotype defined by the user +auto_type_def() { + local word tmp_otp + + if [[ -z $tsn_autotype ]]; then + printf "%s" "$tsn_username" | wtype -s "$tsn_delay" - + wtype -s "$tsn_delay" -k Tab -- + printf "%s" "$tsn_password" | wtype -s "$tsn_delay" - + else + for word in $tsn_autotype; do + case "$word" in + ":delay") sleep 1 ;; + ":tab") wtype -s "$tsn_delay" -k Tab -- ;; + ":space") wtype -s "$tsn_delay" -k space -- ;; + ":enter") wtype -s "$tsn_delay" -k Return -- ;; + ":otp") key_otp ;; + path | basename | filename) printf "%s" "${tsn_passfile##*/}" | wtype -s "$tsn_delay" - ;; + "$tsn_userkey") printf "%s" "$tsn_username" | wtype -s "$tsn_delay" - ;; + pass | password) printf "%s" "$tsn_password" | wtype -s "$tsn_delay" - ;; + *) + if [[ -n ${tsn_passdata["$word"]} ]]; then + printf "%s" "${tsn_passdata["$word"]}" | wtype -s "$tsn_delay" - + else + wtype -s "$tsn_delay" -k space -- + fi + ;; + esac + done + fi +} + +auto_type() { + printf "%s" "$1" | wtype -s "$tsn_delay" - +} + +# POTENTIAL IMPROVEMENT: We could restore the clipboard as it was before pass +# was used. This is done by default by pass. +wld_copy() { + local tsn_cliptime + + if [[ $pass_backend == "pass" ]]; then + tsn_cliptime="${PASSWORD_STORE_CLIP_TIME:-15}" + if ! are_digits "$tsn_cliptime"; then + printf "%s\n" "invalid clipboard timeout value in PASSWORD_STORE_CLIP_TIME" >&2 + return 1 + fi + elif [[ $pass_backend == "gopass" ]]; then + tsn_cliptime="$(gopass config cliptimeout)" + tsn_cliptime="${tsn_cliptime##*: }" + if ! are_digits "$tsn_cliptime"; then + printf "%s\n" "invalid clipboard timeout value in cliptimeout" >&2 + return 1 + fi + fi + # it would've been better to use, or at least provide an option, to paste + # only once using `wl-copy -o` but web browsers don't work well with this + # feature + # https://github.com/bugaevc/wl-clipboard/issues/107 + printf "%s" "$1" | wl-copy + if is_installed notify-send; then + notify-send -t $((tsn_cliptime * 1000)) \ + "pass" "data has been copied and will be cleared from the clipboard after $tsn_cliptime seconds" + fi + { + sleep "$tsn_cliptime" || kill 0 + wl-copy --clear + } > /dev/null 2>&1 & + + unset -v tsn_cliptime + unset -v tsn_passfile tsn_passdata tsn_username tsn_password chosen_key +} + +are_digits() { + if [[ $1 =~ ^[[:digit:]]+$ ]]; then + return 0 + else + return 1 + fi +} + +validate_pass_backend() { + if ! is_installed "$1"; then + _die "please install a valid password store backend: pass | gopass" + fi + if [[ $1 == "pass" ]] || [[ $1 == "gopass" ]]; then + pass_backend="$1" + else + _die "please specify a valid password store backend: pass | gopass" + fi +} + +# fuzzel and bemenu do not support config files so their backend options won't +# be changed after this function, rofi and wofi do support config files and +# those will be appended to the dmenu_backend_opts array by sourcing the config +# file of tessen +# +# of course, this limits customization when using fuzzel or bemenu and when +# multiple contexts are invovled, which I don't like myself, but I won't accept +# arbitrary input as arguments and use hacks like +# [this](https://github.com/ayushnix/tessen/pull/13) +# +# if [this](https://codeberg.org/dnkl/fuzzel/issues/3) issue gets resolved, +# fuzzel will be promoted as the recommended and the default dmenu backend for +# tessen +validate_dmenu_backend() { + if ! is_installed "$1"; then + _die "please install a valid dmenu backend: fuzzel | tofi | bemenu | wofi | rofi | dmenu" + fi + + local -a bemenu_opts + case "$1" in + rofi) + dmenu_backend="rofi" + dmenu_backend_opts=('-dmenu') + ;; + fuzzel) + dmenu_backend="fuzzel" + dmenu_backend_opts=('-d' '--log-level=warning') + ;; + tofi) + dmenu_backend="tofi" + dmenu_backend_opts=() + ;; + bemenu) + dmenu_backend="bemenu" + dmenu_backend_opts=() + bemenu_opts=('-l' '10' '-n') + if [[ -z ${BEMENU_OPTS[*]} ]]; then + export BEMENU_OPTS="${bemenu_opts[*]}" + fi + ;; + wofi) + dmenu_backend="wofi" + dmenu_backend_opts=('-d' '-k' '/dev/null') + ;; + dmenu) + dmenu_backend="dmenu" + dmenu_backend_opts=() + ;; + *) + _die "please install a valid dmenu backend: fuzzel | tofi | bemenu | wofi | rofi | dmenu" + ;; + esac + unset -v bemenu_opts +} + +validate_action() { + case "$1" in + autotype) + if ! is_installed "wtype"; then + _die "wtype is not installed, unable to autotype pass data" + fi + tsn_action="autotype" + ;; + copy) + if ! is_installed "wl-copy"; then + _die "wl-clipboard is not installed, unable to copy-paste pass data" + fi + tsn_action="copy" + ;; + both) + if ! is_installed "wtype"; then + _die "wtype is not installed, unable to autotype pass data" + elif ! is_installed "wl-copy"; then + _die "wl-clipboard is not installed, unable to copy-paste pass data" + fi + tsn_action="both" + ;; + default) + if is_installed "wtype" && is_installed "wl-copy"; then + tsn_action="default" + elif is_installed "wtype" && ! is_installed "wl-copy"; then + printf "%s\n" "wl-clipboard is not installed, unable to copy-paste pass data" >&2 + tsn_action="autotype" + elif ! is_installed "wtype" && is_installed "wl-copy"; then + printf "%s\n" "wtype is not installed, unable to autotype pass data" >&2 + tsn_action="copy" + elif ! is_installed "wtype" && ! is_installed "wl-copy"; then + _die "please install at least one the following backends to use tessen: wtype | wl-clipboard " + fi + ;; + *) _die "please specify a valid action: autotype | copy | both" ;; + esac +} + +find_pass_backend() { + local -a tmp_pass_arr=('pass' 'gopass') + local idx + + for idx in "${tmp_pass_arr[@]}"; do + if is_installed "$idx"; then + pass_backend="$idx" + break + fi + done + if [[ -z $pass_backend ]]; then + _die "please install a valid password store backend: pass | gopass" + fi + + unset -v idx tmp_pass_arr +} + +find_dmenu_backend() { + local -a tmp_dmenu_arr=('fuzzel' 'tofi' 'bemenu' 'wofi' 'rofi' 'dmenu') + local idx + + for idx in "${tmp_dmenu_arr[@]}"; do + if is_installed "$idx"; then + dmenu_backend="$idx" + break + fi + done + if [[ -z $dmenu_backend ]]; then + _die "please install a valid dmenu backend: fuzzel | tofi | bemenu | wofi | rofi | dmenu" + fi + unset -v idx tmp_dmenu_arr +} + +is_installed() { + if command -v "$1" > /dev/null 2>&1; then + return 0 + else + return 1 + fi +} + +_clear() { + unset -v tsn_passfile tsn_passdata tsn_username tsn_password tsn_url + unset -v tsn_autotype chosen_key +} + +_die() { + if [[ -n $1 ]]; then + printf "%s\n" "$1" >&2 + fi + exit 1 +} + +print_help() { + local prog="tessen" + + printf "%s" "\ +$prog - autotype and copy data from password-store and gopass on wayland + +usage: $prog [options] + + $prog find a dmenu and pass backend, look for a config + file in \$XDG_CONFIG_HOME/tessen/config, and either + autotype OR copy data + $prog -p gopass use gopass as the pass backend + $prog -d fuzzel use fuzzel as the dmenu backend + $prog -d fuzzel -a autotype use fuzzel and always autotype data + $prog -d fuzzel -a copy use fuzzel and always copy data + $prog -d fuzzel -a both use fuzzel and always autotype AND copy data + $prog -c \$HOME/tsncfg specify a custom location for the $prog config file + + -p, --pass, --pass= choose either 'pass' or 'gopass' + -d, --dmenu, --dmenu= specify a dmenu backend - 'fuzzel', 'tofi', + 'bemenu', 'wofi', 'rofi', and 'dmenu' are supported + -a, --action, --action= choose either 'autotype', 'copy', or 'both' + omit this option to use the default behavior + -c, --config, --config= use a config file on a custom path + -h, --help print this help menu + -v, --version print the version of $prog + +for more details and additional features, please read the man page of $prog(1) + +for reporting bugs or feedback, visit one of the following git forge providers +https://sr.ht/~ayushnix/tessen +https://codeberg.org/ayushnix/tessen +https://github.com/ayushnix/tessen +" + + unset -v prog +} + +# this block of code is needed because we can't source the file and execute +# arbitrary input +parse_config() { + local line idx key val + local -a config_arr + local config_regex='^[[:alpha:]_]+="[[:alnum:]~_./^$|()-]+"$' + # in case the user hasn't provided an explicit location, we'll have to check + # if the default file exists before we parse it + if [[ -s $tsn_config ]]; then + while read -r line || [[ -n $line ]]; do + if [[ $line == \#* ]]; then + continue + elif [[ $line =~ $config_regex ]]; then + config_arr+=("$line") + fi + done < "$tsn_config" + for idx in "${config_arr[@]}"; do + key="${idx%=*}" + val="${idx#*\"}" + val="${val%*\"}" + # here comes the ladder + # the -p, -d, and -a options will be parsed and set only if they're not + # already set, i.e., from the argparse + if [[ $key == "pass_backend" ]] && [[ -z $pass_backend ]]; then + validate_pass_backend "$val" + readonly pass_backend + elif [[ $key == "dmenu_backend" ]] && [[ -z $dmenu_backend ]]; then + validate_dmenu_backend "$val" + readonly dmenu_backend + elif [[ $key == "action" ]] && unset -v tsn_action 2> /dev/null; then + validate_action "$val" + readonly tsn_action + elif [[ $key == "tofi_config_file" ]] && [[ -f ${val@P} ]]; then + tmp_tofi_opts+=("--config" "${val@P}") + elif [[ $key == "rofi_config_file" ]] && [[ -f ${val@P} ]]; then + tmp_rofi_opts+=("-config" "${val@P}") + elif [[ $key == "wofi_config_file" ]] && [[ -f ${val@P} ]]; then + tmp_wofi_opts+=("-c" "${val@P}") + elif [[ $key == "wofi_style_file" ]] && [[ -f ${val@P} ]]; then + tmp_wofi_opts+=("-s" "${val@P}") + elif [[ $key == "wofi_color_file" ]] && [[ -f ${val@P} ]]; then + tmp_wofi_opts+=("-C" "${val@P}") + elif [[ $key == "userkey" ]]; then + tsn_userkey="$val" + elif [[ $key == "urlkey" ]]; then + tsn_urlkey="$val" + elif [[ $key == "autotype_key" ]]; then + tsn_autokey="$val" + elif [[ $key == "delay" ]]; then + tsn_delay="$val" + elif [[ $key == "web_browser" ]] && is_installed "$val"; then + tsn_web_browser="$val" + fi + done + fi + + unset -v line key val idx config_arr config_regex +} + +main() { + # parse arguments because they have the highest priority + # make the values supplied to -p, -d, and -a as readonly + local _opt + while [[ $# -gt 0 ]]; do + _opt="$1" + case "$_opt" in + -p | --pass) + if [[ $# -lt 2 ]]; then + _die "please specify a valid password store backend: pass | gopass" + fi + validate_pass_backend "$2" + readonly pass_backend + shift + ;; + --pass=*) + if [[ -z ${_opt##--pass=} ]]; then + _die "please specify a valid password store backend: pass | gopass" + fi + validate_pass_backend "${_opt##--pass=}" + readonly pass_backend + ;; + -d | --dmenu) + if [[ $# -lt 2 ]]; then + _die "please install a valid dmenu backend: fuzzel | tofi | bemenu | wofi | rofi | dmenu" + fi + validate_dmenu_backend "$2" + readonly dmenu_backend + # since there's a possibility that a user may mention config files for + # rofi and wofi, we will make dmenu_backend_opts readonly only if + # dmenu_backend is fuzzel and bemenu, the dmenu programs which don't + # support configuration files + if [[ $dmenu_backend == "fuzzel" ]] || [[ $dmenu_backend == "bemenu" ]] \ + || [[ $dmenu_backend == "dmenu" ]]; then + readonly dmenu_backend_opts + fi + shift + ;; + --dmenu=*) + if [[ -z ${_opt##--dmenu=} ]]; then + _die "please install a valid dmenu backend: fuzzel | tofi | bemenu | wofi | rofi | dmenu" + fi + validate_dmenu_backend "${_opt##--dmenu=}" + readonly dmenu_backend + if [[ $dmenu_backend == "fuzzel" ]] || [[ $dmenu_backend == "bemenu" ]] \ + || [[ $dmenu_backend == "dmenu" ]]; then + readonly dmenu_backend_opts + fi + ;; + -a | --action) + if [[ $# -lt 2 ]]; then + _die "please specify a valid action: autotype | copy | both" + fi + validate_action "$2" + readonly tsn_action + shift + ;; + --action=*) + if [[ -z ${_opt##--action=} ]]; then + _die "please specify a valid action: autotype | copy | both" + fi + validate_action "${_opt##--action=}" + readonly tsn_action + ;; + -c | --config) + if [[ $# -lt 2 ]] || ! [[ -f $2 ]]; then + _die "please specify a valid path for the configuration file of tessen" + fi + tsn_config="$2" + shift + ;; + --config=*) + if ! [[ -f ${_opt##--config=} ]]; then + _die "please specify a valid path for the configuration file of tessen" + fi + tsn_config="${_opt##--config=}" + ;; + -h | --help) + print_help + exit 0 + ;; + -v | --version) + printf "%s\n" "$tsn_version" + exit 0 + ;; + --) + shift + break + ;; + *) _die "invalid argument detected" ;; + esac + shift + done + unset -v _opt + + # parse the config file + # the config file comes AFTER the argparse because the config file has some + # options that argparse doesn't offer + # the options which are mutual between the argparse and the config file will + # be considered in the config file only if those options aren't already set + parse_config + if [[ $dmenu_backend == "tofi" ]]; then + dmenu_backend_opts+=("${tmp_tofi_opts[@]}") + readonly dmenu_backend_opts + elif [[ $dmenu_backend == "rofi" ]]; then + dmenu_backend_opts+=("${tmp_rofi_opts[@]}") + readonly dmenu_backend_opts + elif [[ $dmenu_backend == "wofi" ]]; then + dmenu_backend_opts+=("${tmp_wofi_opts[@]}") + readonly dmenu_backend_opts + fi + + # initialize basic options for users who expect sane defaults and don't use + # either the config file or args + if [[ -z $pass_backend ]]; then + find_pass_backend + readonly pass_backend + fi + if [[ -z $dmenu_backend ]]; then + find_dmenu_backend + validate_dmenu_backend "$dmenu_backend" + readonly dmenu_backend + fi + if unset -v tsn_action 2> /dev/null; then + validate_action "default" + readonly tsn_action + fi + + trap '_clear' EXIT TERM INT + if [[ $pass_backend == "pass" ]]; then + get_pass_files + elif [[ $pass_backend == "gopass" ]]; then + get_gopass_files + fi + get_pass_data + key_menu + trap - EXIT TERM INT +} + +main "$@" diff --git a/bin/wallpaper b/bin/wallpaper @@ -0,0 +1,6 @@ +#!/bin/bash +while true; do + pkill swaybg + swaybg -i $(find $XDG_DATA_HOME/wallpapers -type f -name "*.jpg" | shuf -n1) -m fill & + sleep 900 +done diff --git a/bin/ytmpd b/bin/ytmpd @@ -0,0 +1,29 @@ +#!/usr/bin/env python3 +import sys +from mpd import MPDClient +from yt_dlp import YoutubeDL + +if len(sys.argv) < 2: + print("ytmpd: missing url") + sys.exit(1) + +source_url = sys.argv[1] +mpd = MPDClient() + +with YoutubeDL({"format": "bestaudio/audio"}) as youtube: + info = youtube.extract_info(source_url, download=False) + url = info.get("url") + title = info.get("title") + source = info.get("extractor_key") + + mpd.connect("localhost", 6600) + song_id = mpd.addid(url) + mpd.addtagid(song_id, "title", title) + mpd.addtagid(song_id, "album", source) + + # cli parameter to play the song immediately + if len(sys.argv) > 2 and sys.argv[2] == "--play": + mpd.playid(song_id) + mpd.play() + + mpd.disconnect()