From de7855905a1b207c2b86b0e39c994b19c6ead823 Mon Sep 17 00:00:00 2001 From: Solomon Laing Date: Thu, 25 May 2023 16:36:19 +0930 Subject: [PATCH] updates --- .config/bash/.bashrc | 2 +- .config/mpv/input.conf | 5 + .config/ncmpcpp/bindings | 24 + .config/ncmpcpp/config | 1 + .../nvim/lua/lazyvim/plugins/lsp/format.lua | 52 +- .config/nvim/lua/lazyvim/plugins/neorg.lua | 60 +- .config/pipewire/pipewire.conf | 247 +++++ .config/shell/aliasrc | 10 + .config/wget/wgetrc | 0 .config/x11/xprofile | 4 +- .config/zathura/zathurarc | 15 + .dwm/autostart.sh | 12 +- .local/bin/cht.sh | 26 +- .local/bin/displayselect | 41 +- .local/bin/dmenumount | 67 -- .local/bin/dmenuumount | 21 - .local/bin/mounter | 114 +++ .local/bin/networkmanager_dmenu | 924 ------------------ .local/bin/status-mem | 2 +- .local/bin/unmounter | 28 + 20 files changed, 543 insertions(+), 1112 deletions(-) create mode 100644 .config/mpv/input.conf create mode 100644 .config/ncmpcpp/bindings create mode 100644 .config/ncmpcpp/config create mode 100644 .config/pipewire/pipewire.conf create mode 100644 .config/wget/wgetrc create mode 100644 .config/zathura/zathurarc delete mode 100755 .local/bin/dmenumount delete mode 100755 .local/bin/dmenuumount create mode 100755 .local/bin/mounter delete mode 100755 .local/bin/networkmanager_dmenu create mode 100755 .local/bin/unmounter diff --git a/.config/bash/.bashrc b/.config/bash/.bashrc index 7253679..59dbb8c 100644 --- a/.config/bash/.bashrc +++ b/.config/bash/.bashrc @@ -33,7 +33,7 @@ export GCC_COLORS='error=01;31:warning=01;35:note=01;36:caret=01;32:locus=01:quo bind '"\C-o":"lfcd\n"' bind '"\C-t":"tms\n"' -bind '"\C-T":"tms $(pwd)\n"' +bind '"\C-w":"tms $(pwd)\n"' bind '"\C-n":"zk edit --interactive --sort modified-\n"' bind '"\C-f":"cd $(dirname "$(fzf)")\n"' diff --git a/.config/mpv/input.conf b/.config/mpv/input.conf new file mode 100644 index 0000000..d614b15 --- /dev/null +++ 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/ncmpcpp/bindings b/.config/ncmpcpp/bindings new file mode 100644 index 0000000..bbfb180 --- /dev/null +++ b/.config/ncmpcpp/bindings @@ -0,0 +1,24 @@ +def_key "j" + scroll_down +def_key "k" + scroll_up +def_key "h" + previous_column +def_key "l" + next_column +def_key "ctrl-b" + page_up +def_key "ctrl-u" + page_up +def_key "ctrl-f" + page_down +def_key "ctrl-d" + page_down +def_key "g" + move_home +def_key "G" + move_end +def_key "n" + next_found_item +def_key "N" + previous_found_item diff --git a/.config/ncmpcpp/config b/.config/ncmpcpp/config new file mode 100644 index 0000000..d6a55a2 --- /dev/null +++ b/.config/ncmpcpp/config @@ -0,0 +1 @@ +execute_on_song_change = notify-send "Now Playing" "$(mpc --format '%title% \n%artist% - %album%' current)" diff --git a/.config/nvim/lua/lazyvim/plugins/lsp/format.lua b/.config/nvim/lua/lazyvim/plugins/lsp/format.lua index 01dabf4..2541beb 100644 --- a/.config/nvim/lua/lazyvim/plugins/lsp/format.lua +++ b/.config/nvim/lua/lazyvim/plugins/lsp/format.lua @@ -1,40 +1,40 @@ local M = {} -M.autoformat = true +M.autoformat = false function M.toggle() - M.autoformat = not M.autoformat - vim.notify(M.autoformat and "Enabled format on save" or "Disabled format on save") + M.autoformat = not M.autoformat + vim.notify(M.autoformat and "Enabled format on save" or "Disabled format on save") end function M.format() - local buf = vim.api.nvim_get_current_buf() - local ft = vim.bo[buf].filetype - local have_nls = #require("null-ls.sources").get_available(ft, "NULL_LS_FORMATTING") > 0 + local buf = vim.api.nvim_get_current_buf() + local ft = vim.bo[buf].filetype + local have_nls = #require("null-ls.sources").get_available(ft, "NULL_LS_FORMATTING") > 0 - vim.lsp.buf.format({ - bufnr = buf, - filter = function(client) - if have_nls then - return client.name == "null-ls" - end - return client.name ~= "null-ls" - end, - }) + vim.lsp.buf.format({ + bufnr = buf, + filter = function(client) + if have_nls then + return client.name == "null-ls" + end + return client.name ~= "null-ls" + end, + }) end function M.on_attach(client, buf) - if client.supports_method("textDocument/formatting") then - vim.api.nvim_create_autocmd("BufWritePre", { - group = vim.api.nvim_create_augroup("LspFormat." .. buf, {}), - buffer = buf, - callback = function() - if M.autoformat then - M.format() - end - end, - }) - end + if client.supports_method("textDocument/formatting") then + vim.api.nvim_create_autocmd("BufWritePre", { + group = vim.api.nvim_create_augroup("LspFormat." .. buf, {}), + buffer = buf, + callback = function() + if M.autoformat then + M.format() + end + end, + }) + end end return M diff --git a/.config/nvim/lua/lazyvim/plugins/neorg.lua b/.config/nvim/lua/lazyvim/plugins/neorg.lua index 035aa4a..9386bd1 100644 --- a/.config/nvim/lua/lazyvim/plugins/neorg.lua +++ b/.config/nvim/lua/lazyvim/plugins/neorg.lua @@ -1,32 +1,32 @@ return { - "nvim-neorg/neorg", - -- event = "BufEnter *.norg", - event = "VeryLazy", - dependencies = { { "nvim-treesitter/nvim-treesitter" }, { "nvim-lua/plenary.nvim" } }, - build = ":Neorg sync-parsers", - opts = { - load = { - ["core.defaults"] = {}, - ["core.concealer"] = {}, - ["core.dirman"] = { - config = { - workspaces = { - work = "~/work/neorg/", - personal = "~/notes/neorg/", - journal = "~/notes/neorg/journal/", - }, - }, - }, - ["core.completion"] = { - config = { - engine = "nvim-cmp", - }, - }, - ["core.presenter"] = { - config = { - zen_mode = "zen-mode", - }, - }, - }, - }, + -- "nvim-neorg/neorg", + -- -- event = "BufEnter *.norg", + -- event = "VeryLazy", + -- dependencies = { { "nvim-treesitter/nvim-treesitter" }, { "nvim-lua/plenary.nvim" } }, + -- build = ":Neorg sync-parsers", + -- opts = { + -- load = { + -- ["core.defaults"] = {}, + -- ["core.concealer"] = {}, + -- ["core.dirman"] = { + -- config = { + -- workspaces = { + -- work = "~/work/neorg/", + -- personal = "~/notes/neorg/", + -- journal = "~/notes/neorg/journal/", + -- }, + -- }, + -- }, + -- ["core.completion"] = { + -- config = { + -- engine = "nvim-cmp", + -- }, + -- }, + -- ["core.presenter"] = { + -- config = { + -- zen_mode = "zen-mode", + -- }, + -- }, + -- }, + -- }, } diff --git a/.config/pipewire/pipewire.conf b/.config/pipewire/pipewire.conf new file mode 100644 index 0000000..b8a3672 --- /dev/null +++ b/.config/pipewire/pipewire.conf @@ -0,0 +1,247 @@ +# Daemon config file for PipeWire version "0.3.40" # +# +# Copy and edit this file in /etc/pipewire for system-wide changes +# or in ~/.config/pipewire for local changes. + +context.properties = { + ## Configure properties in the system. + #library.name.system = support/libspa-support + #context.data-loop.library.name.system = support/libspa-support + #support.dbus = true + #link.max-buffers = 64 + link.max-buffers = 16 # version < 3 clients can't handle more + #mem.warn-mlock = false + #mem.allow-mlock = true + #mem.mlock-all = false + #clock.power-of-two-quantum = true + #log.level = 2 + #cpu.zero.denormals = true + + core.daemon = true # listening for socket connections + core.name = pipewire-0 # core name and socket name + + ## Properties for the DSP configuration. + #default.clock.rate = 48000 + #default.clock.allowed-rates = [ 48000 ] + #default.clock.quantum = 1024 + #default.clock.min-quantum = 32 + #default.clock.max-quantum = 8192 + #default.video.width = 640 + #default.video.height = 480 + #default.video.rate.num = 25 + #default.video.rate.denom = 1 + # + # These overrides are only applied when running in a vm. + vm.overrides = { + default.clock.min-quantum = 1024 + } +} + +context.spa-libs = { + # = + # + # Used to find spa factory names. It maps an spa factory name + # regular expression to a library name that should contain + # that factory. + # + 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 + #videotestsrc = videotestsrc/libspa-videotestsrc + #audiotestsrc = audiotestsrc/libspa-audiotestsrc +} + +context.modules = [ + #{ name = + # [ args = { = ... } ] + # [ flags = [ [ ifexists ] [ nofail ] ] + #} + # + # Loads a module with the given parameters. + # If ifexists is given, the module is ignored when it is not found. + # If nofail is given, module initialization failures are ignored. + # + + # Uses RTKit to boost the data thread priority. + { name = libpipewire-module-rtkit + args = { + #nice.level = -11 + #rt.prio = 88 + #rt.time.soft = 2000000 + #rt.time.hard = 2000000 + } + flags = [ ifexists nofail ] + } + + # Set thread priorities without using RTKit. + #{ name = libpipewire-module-rt + # args = { + # nice.level = -11 + # rt.prio = 88 + # rt.time.soft = 2000000 + # rt.time.hard = 2000000 + # } + # flags = [ ifexists nofail ] + #} + + # The native communication protocol. + { name = libpipewire-module-protocol-native } + + # The profile module. Allows application to access profiler + # and performance data. It provides an interface that is used + # by pw-top and pw-profiler. + { name = libpipewire-module-profiler } + + # Allows applications to create metadata objects. It creates + # a factory for Metadata objects. + { name = libpipewire-module-metadata } + + # Creates a factory for making devices that run in the + # context of the PipeWire server. + { name = libpipewire-module-spa-device-factory } + + # Creates a factory for making nodes that run in the + # context of the PipeWire server. + { name = libpipewire-module-spa-node-factory } + + # Allows creating nodes that run in the context of the + # client. Is used by all clients that want to provide + # data to PipeWire. + { name = libpipewire-module-client-node } + + # Allows creating devices that run in the context of the + # client. Is used by the session manager. + { name = libpipewire-module-client-device } + + # The portal module monitors the PID of the portal process + # and tags connections with the same PID as portal + # connections. + { name = libpipewire-module-portal + flags = [ ifexists nofail ] + } + + # The access module can perform access checks and block + # new clients. + { name = libpipewire-module-access + args = { + # access.allowed to list an array of paths of allowed + # apps. + #access.allowed = [ + # /usr/bin/pipewire-media-session + #] + + # An array of rejected paths. + #access.rejected = [ ] + + # An array of paths with restricted access. + #access.restricted = [ ] + + # Anything not in the above lists gets assigned the + # access.force permission. + #access.force = flatpak + } + } + + # Makes a factory for wrapping nodes in an adapter with a + # converter and resampler. + { name = libpipewire-module-adapter } + + # Makes a factory for creating links between ports. + { name = libpipewire-module-link-factory } + + # Provides factories to make session manager objects. + { name = libpipewire-module-session-manager } +] + +context.objects = [ + #{ factory = + # [ args = { = ... } ] + # [ flags = [ [ nofail ] ] + #} + # + # Creates an object from a PipeWire factory with the given parameters. + # If nofail is given, errors are ignored (and no object is created). + # + #{ factory = spa-node-factory args = { factory.name = videotestsrc node.name = videotestsrc Spa:Pod:Object:Param:Props:patternType = 1 } } + #{ factory = spa-device-factory args = { factory.name = api.jack.device foo=bar } flags = [ nofail ] } + #{ factory = spa-device-factory args = { factory.name = api.alsa.enum.udev } } + #{ factory = spa-node-factory args = { factory.name = api.alsa.seq.bridge node.name = Internal-MIDI-Bridge } } + #{ factory = adapter args = { factory.name = audiotestsrc node.name = my-test } } + #{ factory = spa-node-factory args = { factory.name = api.vulkan.compute.source node.name = my-compute-source } } + + # A default dummy driver. This handles nodes marked with the "node.always-driver" + # property when no other driver is currently active. JACK clients need this. + { 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 + } + } + # This creates a new Source node. It will have input ports + # that you can link, to provide audio for this source. + #{ factory = adapter + # args = { + # factory.name = support.null-audio-sink + # node.name = "my-mic" + # node.description = "Microphone" + # media.class = "Audio/Source/Virtual" + # audio.position = "FL,FR" + # } + #} + + # This creates a single PCM source device for the given + # alsa device path hw:0. You can change source to sink + # to make a sink in the same way. + #{ factory = adapter + # args = { + # factory.name = api.alsa.pcm.source + # node.name = "alsa-source" + # node.description = "PCM Source" + # media.class = "Audio/Source" + # api.alsa.path = "hw:0" + # api.alsa.period-size = 1024 + # api.alsa.headroom = 0 + # api.alsa.disable-mmap = false + # api.alsa.disable-batch = false + # audio.format = "S16LE" + # audio.rate = 48000 + # audio.channels = 2 + # audio.position = "FL,FR" + # } + #} +] + +context.exec = [ + #{ path = [ args = "" ] } + # + # Execute the given program with arguments. + # + # You can optionally start the session manager here, + # but it is better to start it as a systemd service. + # Run the session manager with -h for options. + # + { path = "/usr/bin/wireplumber" args = "" } + # + # You can optionally start the pulseaudio-server here as well + # but it is better to start it as a systemd service. + # It can be interesting to start another daemon here that listens + # on another address with the -a option (eg. -a tcp:4713). + # + { path = "/usr/bin/pipewire" args = "-c pipewire-pulse.conf" } +] diff --git a/.config/shell/aliasrc b/.config/shell/aliasrc index b6c13a3..3a465ad 100644 --- a/.config/shell/aliasrc +++ b/.config/shell/aliasrc @@ -62,3 +62,13 @@ alias \ cls="clear" \ c="clear" \ cdc="cd && clear" + +# git aliases +alias \ + gst="git status" \ + ga.="git add ." \ + gca="git commit -a" \ + gcam="git commit -am" \ + gsta="git stash" \ + gstac="git stash clear" \ + gstaa="git stash apply" diff --git a/.config/wget/wgetrc b/.config/wget/wgetrc new file mode 100644 index 0000000..e69de29 diff --git a/.config/x11/xprofile b/.config/x11/xprofile index b51bed1..5ede9c8 100755 --- a/.config/x11/xprofile +++ b/.config/x11/xprofile @@ -13,8 +13,10 @@ numlockx # set default desktop window layout "$HOME/.screenlayout/default.sh" +nitrogen --restore & + # I don't use the following but I like it so I'll leave it for now. -autostart="pipewire pipewire-pulse wireplumber" +autostart="pipewire pipewire-pulse wireplumber dunst mpd nm-applet picom" for program in $autostart; do pidof -s "$program" || "$program" & diff --git a/.config/zathura/zathurarc b/.config/zathura/zathurarc new file mode 100644 index 0000000..452e116 --- /dev/null +++ b/.config/zathura/zathurarc @@ -0,0 +1,15 @@ +set sandbox none +set statusbar-h-padding 0 +set statusbar-v-padding 0 +set page-padding 1 +set selection-clipboard clipboard +map u scroll half-up +map d scroll half-down +map D toggle_page_mode +map r reload +map R rotate +map K zoom in +map J zoom out +map i recolor +map p print +map g goto top diff --git a/.dwm/autostart.sh b/.dwm/autostart.sh index 39768c6..6d17716 100755 --- a/.dwm/autostart.sh +++ b/.dwm/autostart.sh @@ -7,17 +7,7 @@ # System Applications ############################################################################### -# Dunst - notifications -dunst & - -# Network Manager Applet -nm-applet & - -# Compostitor with special stuff -picom & - -# wallpaper -nitrogen --restore & +# conflicted about using this and/or xprofile... ############################################################################### # Startup Applications diff --git a/.local/bin/cht.sh b/.local/bin/cht.sh index 2915811..677f4cf 100755 --- a/.local/bin/cht.sh +++ b/.local/bin/cht.sh @@ -1,15 +1,23 @@ #!/bin/bash -commands="rsync find man tldr sed awk tr cp ls grep xargs rg ps mv kill lsof less head tail tar cp rm rename jq cat ssh cargo git git-worktree git-status git-commit git-rebase docker docker-compose stow chmod chown make" +commands="ncmpcpp rsync find man tldr sed awk tr cp ls grep xargs rg ps mv kill lsof less head tail tar cp rm rename jq cat ssh cargo git git-worktree git-status git-commit git-rebase docker docker-compose stow chmod chown make" languages="golang solidity vlang v nodejs javascript tmux typescript zsh cpp c lua rust python bash php haskell ArnoldC css html gdb" -selected=$(echo "$commands $languages" | tr ' ' '\n' | fzf) -if [[ -z $selected ]]; then - exit 0 +if [[ -z $@ ]]; then + + selected=$(echo "$commands $languages" | tr ' ' '\n' | fzf) + if [[ -z $selected ]]; then + exit 0 + fi + + read -rp "Enter Query: " query + + query=$(echo "$query" | tr ' ' '+') + tmux neww bash -c "curl cht.sh/$selected/$query & while [ : ]; do sleep 1; done" + +else + + tmux neww bash -c "curl cht.sh/$@ & while [ : ]; do sleep 1; done" + fi - -read -rp "Enter Query: " query - -query=$(echo "$query" | tr ' ' '+') -tmux neww bash -c "curl cht.sh/$selected/$query & while [ : ]; do sleep 1; done" diff --git a/.local/bin/displayselect b/.local/bin/displayselect index f6f16d4..0227a32 100755 --- a/.local/bin/displayselect +++ b/.local/bin/displayselect @@ -36,31 +36,30 @@ twoscreen() { # If multi-monitor is selected and there are two screens. direction=$(printf "left\\nright" | dmenu -i -p "What side of $primary should $secondary be on?") xrandr --output "$primary" --auto --scale 1.0x1.0 --output "$secondary" --"$direction"-of "$primary" --auto --scale 1.0x1.0 fi -} + } morescreen() { # If multi-monitor is selected and there are more than two screens. - primary=$(echo "$screens" | dmenu -i -p "Select primary display:") - secondary=$(echo "$screens" | grep -v "$primary" | dmenu -i -p "Select secondary display:") - direction=$(printf "left\\nright" | dmenu -i -p "What side of $primary should $secondary be on?") - tertiary=$(echo "$screens" | grep -v "$primary" | grep -v "$secondary" | dmenu -i -p "Select third display:") - xrandr --output "$primary" --auto --output "$secondary" --"$direction"-of "$primary" --auto --output "$tertiary" --"$(printf "left\\nright" | grep -v "$direction")"-of "$primary" --auto -} + primary=$(echo "$screens" | dmenu -i -p "Select primary display:") + secondary=$(echo "$screens" | grep -v "$primary" | dmenu -i -p "Select secondary display:") + direction=$(printf "left\\nright" | dmenu -i -p "What side of $primary should $secondary be on?") + tertiary=$(echo "$screens" | grep -v "$primary" | grep -v "$secondary" | dmenu -i -p "Select third display:") + xrandr --output "$primary" --auto --output "$secondary" --"$direction"-of "$primary" --auto --output "$tertiary" --"$(printf "left\\nright" | grep -v "$direction")"-of "$primary" --auto + } multimon() { # Multi-monitor handler. - case "$(echo "$screens" | wc -l)" in - 2) twoscreen ;; - *) morescreen ;; -esac ;} + case "$(echo "$screens" | wc -l)" in + 2) twoscreen ;; + *) morescreen ;; + esac ;} onescreen() { # If only one output available or chosen. - xrandr --output "$1" --auto --scale 1.0x1.0 "$(echo "$allposs" | grep -v "\b$1" | awk '{print "--output", $1, "--off"}' | paste -sd ' ' -)" -} + xrandr --output "$1" --auto --scale 1.0x1.0 $(echo "$allposs" | grep -v "\b$1" | awk '{print "--output", $1, "--off"}' | paste -sd ' ' -) + } postrun() { # Stuff to run to clean up. - setbg # Fix background if screen size/arangement has changed. - remaps # Re-remap keys if keyboard added (for laptop bases) - { killall dunst ; setsid -f dunst ;} >/dev/null 2>&1 # Restart dunst to ensure proper location on screen -} + setbg # Fix background if screen size/arangement has changed. + { killall dunst ; setsid -f dunst ;} >/dev/null 2>&1 # Restart dunst to ensure proper location on screen + } # Get all possible displays allposs=$(xrandr -q | grep "connected") @@ -70,14 +69,14 @@ screens=$(echo "$allposs" | awk '/ connected/ {print $1}') # If there's only one screen [ "$(echo "$screens" | wc -l)" -lt 2 ] && -{ onescreen "$screens"; postrun; notify-send "💻 Only one screen detected." "Using it in its optimal settings..."; exit ;} + { onescreen "$screens"; postrun; notify-send "💻 Only one screen detected." "Using it in its optimal settings..."; exit ;} # Get user choice including multi-monitor and manual selection: chosen=$(printf "%s\\nmulti-monitor\\nmanual selection" "$screens" | dmenu -i -p "Select display arangement:") && case "$chosen" in - "manual selection") arandr ; exit ;; - "multi-monitor") multimon ;; - *) onescreen "$chosen" ;; + "manual selection") arandr ; exit ;; + "multi-monitor") multimon ;; + *) onescreen "$chosen" ;; esac postrun diff --git a/.local/bin/dmenumount b/.local/bin/dmenumount deleted file mode 100755 index a887b6b..0000000 --- a/.local/bin/dmenumount +++ /dev/null @@ -1,67 +0,0 @@ -#!/bin/sh - -# Gives a dmenu prompt to mount unmounted drives and Android phones. If -# they're in /etc/fstab, they'll be mounted automatically. Otherwise, you'll -# be prompted to give a mountpoint from already existsing directories. If you -# input a novel directory, it will prompt you to create that directory. - -getmount() { \ - [ -z "$chosen" ] && exit 1 - # shellcheck disable=SC2086 - mp="$(find $1 2>/dev/null | dmenu -i -p "Type in mount point.")" || exit 1 - test -z "$mp" && exit 1 - if [ ! -d "$mp" ]; then - mkdiryn=$(printf "No\\nYes" | dmenu -i -p "$mp does not exist. Create it?") || exit 1 - [ "$mkdiryn" = "Yes" ] && (mkdir -p "$mp" || sudo -A mkdir -p "$mp") - fi -} - -mountusb() { \ - chosen="$(echo "$usbdrives" | dmenu -i -p "Mount which drive?")" || exit 1 - chosen="$(echo "$chosen" | awk '{print $1}')" - sudo -A mount "$chosen" 2>/dev/null && notify-send "USB mounting" "$chosen mounted." && exit 0 - alreadymounted=$(lsblk -nrpo "name,type,mountpoint" | awk '$3!~/\/boot|\/home$|SWAP/&&length($3)>1{printf "-not ( -path *%s -prune ) ",$3}') - getmount "/mnt /media /mount /home -maxdepth 5 -type d $alreadymounted" - partitiontype="$(lsblk -no "fstype" "$chosen")" - case "$partitiontype" in - "vfat") sudo -A mount -t vfat "$chosen" "$mp" -o rw,umask=0000 ;; - "exfat") sudo -A mount "$chosen" "$mp" -o uid="$(id -u)",gid="$(id -g)" ;; - *) sudo -A mount "$chosen" "$mp"; user="$(whoami)"; ug="$(groups | awk '{print $1}')"; sudo -A chown "$user":"$ug" "$mp" ;; - esac && notify-send "USB mounting" "$chosen mounted to $mp." || - notify-send "Drive failed to mount." "Probably a permissions issue or drive is already mounted." -} - -mountandroid() { \ - chosen="$(echo "$anddrives" | dmenu -i -p "Which Android device?")" || exit 1 - chosen="$(echo "$chosen" | cut -d : -f 1)" - getmount "$HOME -maxdepth 3 -type d" - echo "OK" | dmenu -i -p "Tap Allow on your phone if it asks for permission and then press enter" || exit 1 - simple-mtpfs --device "$chosen" "$mp" && - notify-send "Android Mounting" "Android device mounted to $mp." || - notify-send "Android failed mounting." "Probably a permissions issue or phone is already mounted." -} - -asktype() { \ - choice="$(printf "USB\\nAndroid" | dmenu -i -p "Mount a USB drive or Android device?")" || exit 1 - case $choice in - USB) mountusb ;; - Android) mountandroid ;; - esac -} - -anddrives=$(simple-mtpfs -l 2>/dev/null) -usbdrives="$(lsblk -rpo "name,type,size,label,mountpoint,fstype" | grep -v crypto_LUKS | grep 'part\|rom' | sed 's/ /:/g' | awk -F':' '$5==""{printf "%s (%s) %s\n",$1,$3,$4}')" - -if [ -z "$usbdrives" ]; then - [ -z "$anddrives" ] && echo "No USB drive or Android device detected" && exit - echo "Android device(s) detected." - mountandroid -else - if [ -z "$anddrives" ]; then - echo "USB drive(s) detected." - mountusb - else - echo "Mountable USB drive(s) and Android device(s) detected." - asktype - fi -fi diff --git a/.local/bin/dmenuumount b/.local/bin/dmenuumount deleted file mode 100755 index 9ddf605..0000000 --- a/.local/bin/dmenuumount +++ /dev/null @@ -1,21 +0,0 @@ -#!/bin/sh - -# A dmenu prompt to unmount drives. -# Provides you with mounted partitions, select one to unmount. -# Drives mounted at /, /boot and /home will not be options to unmount. - -drives="$(lsblk -nrpo "name,type,size,mountpoint,label" | awk -F':' '{gsub(/ /,":")}$4!~/\/boot|\/efi|\/home$|SWAP/&&length($4)>1{printf "%s (%s) %s\n",$4,$3,$5}'; awk '/simple-mtpfs/ { print "📱", $2; }' /etc/mtab)" - -chosen="$(echo "$drives" | dmenu -i -p "Unmount which drive?")" || exit 1 - -case "$chosen" in - 📱*) - chosen="${chosen#📱 }" - sudo -A umount -l "$chosen" - ;; - *) - chosen="${chosen% (*}" - sudo -A umount -l "$chosen" - ;; -esac && notify-send "Drive unmounted." "$chosen successfully unmounted." || -notify-send "Drive failed to unmount." "Possibly a permissions or I/O issue." diff --git a/.local/bin/mounter b/.local/bin/mounter new file mode 100755 index 0000000..b532e08 --- /dev/null +++ b/.local/bin/mounter @@ -0,0 +1,114 @@ +#!/bin/bash + +# Mounts Android Phones and USB drives (encrypted or not). This script will +# replace the older `dmenumount` which had extra steps and couldn't handle +# encrypted drives. +# TODO: Try decrypt for drives in crtypttab +# TODO: Add some support for connecting iPhones (although they are annoying). + +IFS=' +' +# Function for escaping cell-phone names. +escape(){ echo "$@" | iconv -cf UTF-8 -t ASCII//TRANSLIT | tr -d '[:punct:]' | tr '[:upper:]' '[:lower:]' | tr ' ' '-' | sed "s/-\+/-/g;s/\(^-\|-\$\)//g" ;} + +# Check for phones. +phones="$(simple-mtpfs -l 2>/dev/null | sed "s/^/📱/")" +mountedphones="$(grep "simple-mtpfs" /etc/mtab)" +# If there are already mounted phones, remove them from the list of mountables. +[ -n "$mountedphones" ] && phones="$(for phone in $phones; do + for mounted in $mountedphones; do + escphone="$(escape "$phone")" + [[ "$mounted" =~ "$escphone" ]] && break 1 + done && continue 1 + echo "$phone" +done)" + +# Check for drives. +lsblkoutput="$(lsblk -rpo "uuid,name,type,size,label,mountpoint,fstype")" +# Get all LUKS drives +allluks="$(echo "$lsblkoutput" | grep crypto_LUKS)" +# Get a list of the LUKS drive UUIDs already decrypted. +decrypted="$(find /dev/disk/by-id/dm-uuid-CRYPT-LUKS2-* | sed "s|.*LUKS2-||;s|-.*||")" +# Functioning for formatting drives correctly for dmenu: +filter() { sed "s/ /:/g" | awk -F':' '$7==""{printf "%s%s (%s) %s\n",$1,$3,$5,$6}' ; } + +# Get only LUKS drives that are not decrypted. +unopenedluks="$(for drive in $allluks; do + uuid="${drive%% *}" + uuid="${uuid//-}" # This is a bashism. + for open in $decrypted; do + [ "$uuid" = "$open" ] && break 1 + done && continue 1 + echo "🔒 $drive" +done | filter)" + +# Get all normal, non-encrypted or decrypted partitions that are not mounted. +normalparts="$(echo "$lsblkoutput"| grep -v crypto_LUKS | grep 'part\|rom\|crypt' | sed "s/^/💾 /" | filter )" + +# Add all to one variable. If no mountable drives found, exit. +alldrives="$(echo "$phones +$unopenedluks +$normalparts" | sed "/^$/d;s/ *$//")" + +# Quit the script if a sequential command fails. +set -e + +test -n "$alldrives" + +# Feed all found drives to dmenu and get user choice. +chosen="$(echo "$alldrives" | dmenu -p "Mount which drive?" -i)" + +# Function for prompting user for a mountpoint. +getmount(){ + mp="$(find /mnt /media /mount /home -maxdepth 1 -type d 2>/dev/null | dmenu -i -p "Mount this drive where?")" + test -n "$mp" + if [ ! -d "$mp" ]; then + mkdiryn=$(printf "No\\nYes" | dmenu -i -p "$mp does not exist. Create it?") + [ "$mkdiryn" = "Yes" ] && (mkdir -p "$mp" || sudo -A mkdir -p "$mp") + fi +} + +attemptmount(){ + # Attempt to mount without a mountpoint, to see if drive is in fstab. + sudo -A mount "$chosen" || return 1 + notify-send "💾Drive Mounted." "$chosen mounted." + exit +} + +case "$chosen" in + 💾*) + chosen="${chosen%% *}" + chosen="${chosen:1}" # This is a bashism. + attemptmount || getmount + sudo -A mount "$chosen" "$mp" -o uid="$(id -u)",gid="$(id -g)" + notify-send "💾Drive Mounted." "$chosen mounted to $mp." + ;; + + 🔒*) + chosen="${chosen%% *}" + chosen="${chosen:1}" # This is a bashism. + # Number the drive. + while true; do + [ -f "/dev/mapper/usb$num" ] || break + num="$(printf "%02d" "$((num +1))")" + done + + # Decrypt in a terminal window + ${TERMINAL:-st} -n floatterm -g 60x1 -e sudo cryptsetup open "$chosen" "usb$num" + # Check if now decrypted. + test -b "/dev/mapper/usb$num" + + attemptmount || getmount + sudo -A mount "/dev/mapper/usb$num" "$mp" -o uid="$(id -u)",gid="$(id -g)" + notify-send "🔓Decrypted drive Mounted." "$chosen decrypted and mounted to $mp." + ;; + + 📱*) + notify-send "❗Note" "Remember to allow file access on your phone now." + getmount + number="${chosen%%:*}" + number="${chosen:1}" # This is a bashism. + sudo -A simple-mtpfs -o allow_other -o fsname="simple-mtpfs-$(escape "$chosen")" --device "$number" "$mp" + notify-send "🤖 Android Mounted." "Android device mounted to $mp." + ;; +esac diff --git a/.local/bin/networkmanager_dmenu b/.local/bin/networkmanager_dmenu deleted file mode 100755 index 137c968..0000000 --- a/.local/bin/networkmanager_dmenu +++ /dev/null @@ -1,924 +0,0 @@ -#!/usr/bin/env python3 -# encoding:utf8 -"""NetworkManager command line dmenu script. - -To add new connections or enable/disable networking requires policykit -permissions setup per: -https://wiki.archlinux.org/index.php/NetworkManager#Set_up_PolicyKit_permissions - -OR running the script as root - -Add dmenu options and default terminal if desired to -~/.config/networkmanager-dmenu/config.ini - -""" -import pathlib -import struct -import configparser -import locale -import os -from os.path import expanduser -import shlex -from shutil import which -import sys -from time import sleep -import uuid -import subprocess - -# pylint: disable=import-error -import gi -gi.require_version('NM', '1.0') -from gi.repository import GLib, NM # noqa pylint: disable=wrong-import-position -# pylint: enable=import-error - -ENV = os.environ.copy() -ENV['LC_ALL'] = 'C' -ENC = locale.getpreferredencoding() - -CONF = configparser.ConfigParser() -CONF.read(expanduser("~/.config/networkmanager-dmenu/config.ini")) - - -def cli_args(): - """ Don't override dmenu_cmd function arguments with CLI args. Removes -l - and -p if those are passed on the command line. - - Exception: if -l is passed and dmenu_command is not defined, assume that the - user wants to switch dmenu to the vertical layout and include -l. - - Returns: List of additional CLI arguments - - """ - args = sys.argv[1:] - cmd = CONF.get('dmenu', 'dmenu_command', fallback=False) - if "-l" in args or "-p" in args: - for nope in ['-l', '-p'] if cmd is not False else ['-p']: - try: - nope_idx = args.index(nope) - del args[nope_idx] - del args[nope_idx] - except ValueError: - pass - return args - - -def dmenu_pass(command, color): - """Check if dmenu passphrase patch is applied and return the correct command - line arg list - - Args: command - string - color - obscure color string - Returns: list or None - - """ - if command != 'dmenu': - return None - try: - # Check for dmenu password patch - dm_patch = b'P' in subprocess.run(["dmenu", "-h"], - capture_output=True, - check=False).stderr - except FileNotFoundError: - dm_patch = False - return ["-P"] if dm_patch else ["-nb", color, "-nf", color] - - -def dmenu_cmd(num_lines, prompt="Networks", active_lines=None): - """Parse config.ini for menu options - - Args: args - num_lines: number of lines to display - prompt: prompt to show - active_lines: list of line numbers to tag as active - Returns: command invocation (as a list of strings) for example - ["dmenu", "-l", "", "-p", "", "-i"] - - """ - # Create command string - commands = {"dmenu": ["-p", str(prompt)], - "rofi": ["-dmenu", "-p", str(prompt), "-l", str(num_lines)], - "bemenu": ["-p", str(prompt)], - "wofi": ["-p", str(prompt)]} - command = shlex.split(CONF.get('dmenu', 'dmenu_command', fallback="dmenu")) - command.extend(cli_args()) - command.extend(commands.get(command[0], [])) - # Rofi Highlighting - rofi_highlight = CONF.getboolean('dmenu', 'rofi_highlight', fallback=False) - if rofi_highlight is True and command[0] == "rofi" and active_lines: - command.extend(["-a", ",".join([str(num) for num in active_lines])]) - # Passphrase prompts - obscure = CONF.getboolean('dmenu_passphrase', 'obscure', fallback=False) - if prompt == "Passphrase" and obscure is True: - obscure_color = CONF.get('dmenu_passphrase', 'obscure_color', fallback='#222222') - pass_prompts = {"dmenu": dmenu_pass(command[0], obscure_color), - "rofi": ['-password'], - "bemenu": ['-x'], - "wofi": ['-P']} - command.extend(pass_prompts.get(command[0], [])) - return command - - -def choose_adapter(client): - """If there is more than one wifi adapter installed, ask which one to use - - """ - devices = client.get_devices() - devices = [i for i in devices if i.get_device_type() == NM.DeviceType.WIFI] - if not devices: - return None - if len(devices) == 1: - return devices[0] - device_names = "\n".join([d.get_iface() for d in devices]) - sel = subprocess.run(dmenu_cmd(len(devices), "CHOOSE ADAPTER:"), - capture_output=True, - check=False, - env=ENV, - input=device_names, - encoding=ENC).stdout - if not sel.strip(): - sys.exit() - devices = [i for i in devices if i.get_iface() == sel.strip()] - assert len(devices) == 1 - return devices[0] - - -def is_installed(cmd): - """Check if a utility is installed""" - return which(cmd) is not None - - -def bluetooth_get_enabled(): - """Check if bluetooth is enabled via rfkill. - - Returns None if no bluetooth device was found. - """ - # See https://www.kernel.org/doc/Documentation/ABI/stable/sysfs-class-rfkill - for path in pathlib.Path('/sys/class/rfkill/').glob('rfkill*'): - if (path / 'type').read_text().strip() == 'bluetooth': - return (path / 'soft').read_text().strip() == '0' - return None - - -def create_other_actions(client): - """Return list of other actions that can be taken - - """ - networking_enabled = client.networking_get_enabled() - networking_action = "Disable" if networking_enabled else "Enable" - - wifi_enabled = client.wireless_get_enabled() - wifi_action = "Disable" if wifi_enabled else "Enable" - - bluetooth_enabled = bluetooth_get_enabled() - bluetooth_action = "Disable" if bluetooth_enabled else "Enable" - - actions = [Action(f"{wifi_action} Wifi", toggle_wifi, - not wifi_enabled), - Action(f"{networking_action} Networking", - toggle_networking, not networking_enabled)] - if bluetooth_enabled is not None: - actions.append(Action(f"{bluetooth_action} Bluetooth", - toggle_bluetooth, not bluetooth_enabled)) - actions += [Action("Launch Connection Manager", launch_connection_editor), - Action("Delete a Connection", delete_connection)] - if wifi_enabled: - actions.append(Action("Rescan Wifi Networks", rescan_wifi)) - return actions - - -def rescan_wifi(): - """ - Rescan Wifi Access Points - """ - delay = CONF.getint('nmdm', 'rescan_delay', fallback=5) - for dev in CLIENT.get_devices(): - if gi.repository.NM.DeviceWifi == type(dev): - try: - dev.request_scan_async(None, rescan_cb, None) - LOOP.run() - sleep(delay) - notify("Wifi scan complete") - main() - except gi.repository.GLib.Error as err: - # Too frequent rescan error - notify("Wifi rescan failed", urgency="critical") - if not err.code == 6: # pylint: disable=no-member - raise err - - -def rescan_cb(dev, res, data): - """Callback for rescan_wifi. Just for notifications - - """ - if dev.request_scan_finish(res) is True: - notify("Wifi scan running...") - else: - notify("Wifi scan failed", urgency="critical") - LOOP.quit() - - -def ssid_to_utf8(nm_ap): - """ Convert binary ssid to utf-8 """ - ssid = nm_ap.get_ssid() - if not ssid: - return "" - ret = NM.utils_ssid_to_utf8(ssid.get_data()) - return ret - - -def prompt_saved(saved_cons): - """Prompt for a saved connection.""" - actions = create_saved_actions(saved_cons) - sel = get_selection(actions) - sel() - - -def ap_security(nm_ap): - """Parse the security flags to return a string with 'WPA2', etc. """ - flags = nm_ap.get_flags() - wpa_flags = nm_ap.get_wpa_flags() - rsn_flags = nm_ap.get_rsn_flags() - sec_str = "" - if ((flags & getattr(NM, '80211ApFlags').PRIVACY) and - (wpa_flags == 0) and (rsn_flags == 0)): - sec_str = " WEP" - if wpa_flags: - sec_str = " WPA1" - if rsn_flags & getattr(NM, '80211ApSecurityFlags').KEY_MGMT_PSK: - sec_str += " WPA2" - if rsn_flags & getattr(NM, '80211ApSecurityFlags').KEY_MGMT_SAE: - sec_str += " WPA3" - if ((wpa_flags & getattr(NM, '80211ApSecurityFlags').KEY_MGMT_802_1X) or - (rsn_flags & getattr(NM, '80211ApSecurityFlags').KEY_MGMT_802_1X)): - sec_str += " 802.1X" - if ((wpa_flags & getattr(NM, '80211ApSecurityFlags').KEY_MGMT_OWE) or - (rsn_flags & getattr(NM, '80211ApSecurityFlags').KEY_MGMT_OWE)): - sec_str += " OWE" - - # If there is no security use "--" - if sec_str == "": - sec_str = "--" - return sec_str.lstrip() - - -class Action(): # pylint: disable=too-few-public-methods - """Helper class to execute functions from a string variable""" - def __init__(self, - name, - func, - args=None, - active=False): - self.name = name - self.func = func - self.is_active = active - if args is None: - self.args = None - elif isinstance(args, list): - self.args = args - else: - self.args = [args] - - def __str__(self): - return self.name - - def __call__(self): - if self.args is None: - self.func() - else: - self.func(*self.args) - - -def conn_matches_adapter(conn, adapter): - """Return True if the connection is applicable for the given adapter. - - There seem to be two ways for a connection specify what interface it belongs - to: - - - By setting 'mac-address' in [wifi] to the adapter's MAC - - By setting 'interface-name` in [connection] to the adapter's name. - - Depending on how the connection was added, it seems like either - 'mac-address', 'interface-name' or neither of both is set. - """ - # [wifi] mac-address - setting_wireless = conn.get_setting_wireless() - mac = setting_wireless.get_mac_address() - if mac is not None: - return mac == adapter.get_permanent_hw_address() - - # [connection] interface-name - setting_connection = conn.get_setting_connection() - interface = setting_connection.get_interface_name() - if interface is not None: - return interface == adapter.get_iface() - - # Neither is set, let's assume this connection is for multiple/all adapters. - return True - - -def process_ap(nm_ap, is_active, adapter): - """Activate/Deactivate a connection and get password if required""" - if is_active: - CLIENT.deactivate_connection_async(nm_ap, None, deactivate_cb, nm_ap) - LOOP.run() - else: - conns_cur = [i for i in CONNS if - i.get_setting_wireless() is not None and - conn_matches_adapter(i, adapter)] - con = nm_ap.filter_connections(conns_cur) - if len(con) > 1: - raise ValueError("There are multiple connections possible") - - if len(con) == 1: - CLIENT.activate_connection_async(con[0], adapter, nm_ap.get_path(), - None, activate_cb, nm_ap) - LOOP.run() - else: - if ap_security(nm_ap) != "--": - password = get_passphrase() - else: - password = "" - set_new_connection(nm_ap, password, adapter) - - -def activate_cb(dev, res, data): - """Notification if activate connection completed successfully - - """ - try: - conn = dev.activate_connection_finish(res) - except GLib.Error: - conn = None - if conn is not None: - notify(f"Activated {conn.get_id()}") - else: - notify(f"Problem activating {data.get_id()}", urgency="critical") - LOOP.quit() - - -def deactivate_cb(dev, res, data): - """Notification if deactivate connection completed successfully - - """ - if dev.deactivate_connection_finish(res) is True: - notify(f"Deactivated {data.get_id()}") - else: - notify(f"Problem deactivating {data.get_id()}", urgency="critical") - LOOP.quit() - - -def process_vpngsm(con, activate): - """Activate/deactive VPN or GSM connections""" - if activate: - CLIENT.activate_connection_async(con, None, None, - None, activate_cb, con) - else: - CLIENT.deactivate_connection_async(con, None, deactivate_cb, con) - LOOP.run() - - -def create_ap_actions(aps, active_ap, active_connection, adapter): # noqa pylint: disable=too-many-locals,line-too-long - """For each AP in a list, create the string and its attached function - (activate/deactivate) - - """ - active_ap_bssid = active_ap.get_bssid() if active_ap is not None else "" - - names = [ssid_to_utf8(ap) for ap in aps] - max_len_name = max([len(name) for name in names]) if names else 0 - secs = [ap_security(ap) for ap in aps] - max_len_sec = max([len(sec) for sec in secs]) if secs else 0 - - ap_actions = [] - - for nm_ap, name, sec in zip(aps, names, secs): - bars = NM.utils_wifi_strength_bars(nm_ap.get_strength()) - wifi_chars = CONF.get("dmenu", "wifi_chars", fallback=False) - if wifi_chars: - bars = "".join([wifi_chars[i] for i, j in enumerate(bars) if j == '*']) - is_active = nm_ap.get_bssid() == active_ap_bssid - compact = CONF.getboolean("dmenu", "compact", fallback=False) - if compact: - action_name = f"{name} {sec} {bars}" - else: - action_name = f"{name:<{max_len_name}s} {sec:<{max_len_sec}s} {bars:>4}" - if is_active: - ap_actions.append(Action(action_name, process_ap, - [active_connection, True, adapter], - active=True)) - else: - ap_actions.append(Action(action_name, process_ap, - [nm_ap, False, adapter])) - return ap_actions - - -def create_vpn_actions(vpns, active): - """Create the list of strings to display with associated function - (activate/deactivate) for VPN connections. - - """ - active_vpns = [i for i in active if i.get_vpn()] - return _create_vpngsm_actions(vpns, active_vpns, "VPN") - - -def create_wireguard_actions(wgs, active): - """Create the list of strings to display with associated function - (activate/deactivate) for Wireguard connections. - - """ - active_wgs = [i for i in active if i.get_connection_type() == "wireguard"] - return _create_vpngsm_actions(wgs, active_wgs, "Wireguard") - - -def create_eth_actions(eths, active): - """Create the list of strings to display with associated function - (activate/deactivate) for Ethernet connections. - - """ - active_eths = [i for i in active if 'ethernet' in i.get_connection_type()] - return _create_vpngsm_actions(eths, active_eths, "Eth") - - -def create_gsm_actions(gsms, active): - """Create the list of strings to display with associated function - (activate/deactivate) GSM connections.""" - active_gsms = [i for i in active if - i.get_connection() is not None and - i.get_connection().is_type(NM.SETTING_GSM_SETTING_NAME)] - return _create_vpngsm_actions(gsms, active_gsms, "GSM") - - -def create_blue_actions(blues, active): - """Create the list of strings to display with associated function - (activate/deactivate) Bluetooth connections.""" - active_blues = [i for i in active if - i.get_connection() is not None and - i.get_connection().is_type(NM.SETTING_BLUETOOTH_SETTING_NAME)] - return _create_vpngsm_actions(blues, active_blues, "Bluetooth") - - -def create_saved_actions(saved): - """Create the list of strings to display with associated function - (activate/deactivate) for VPN connections. - - """ - return _create_vpngsm_actions(saved, [], "SAVED") - - -def _create_vpngsm_actions(cons, active_cons, label): - active_con_ids = [a.get_id() for a in active_cons] - actions = [] - for con in cons: - is_active = con.get_id() in active_con_ids - action_name = f"{con.get_id()}:{label}" - if is_active: - active_connection = [a for a in active_cons - if a.get_id() == con.get_id()] - if len(active_connection) != 1: - raise ValueError(f"Multiple active connections match {con.get_id()}") - active_connection = active_connection[0] - - actions.append(Action(action_name, process_vpngsm, - [active_connection, False], active=True)) - else: - actions.append(Action(action_name, process_vpngsm, - [con, True])) - return actions - - -def create_wwan_actions(client): - """Create WWWAN actions - - """ - wwan_enabled = client.wwan_get_enabled() - wwan_action = "Disable" if wwan_enabled else "Enable" - return [Action(f"{wwan_action} WWAN", toggle_wwan, not wwan_enabled)] - - -def combine_actions(eths, aps, vpns, wgs, gsms, blues, wwan, others, saved): - # pylint: disable=too-many-arguments - """Combine all given actions into a list of actions. - - Args: args - eths: list of Actions - aps: list of Actions - vpns: list of Actions - gsms: list of Actions - blues: list of Actions - wwan: list of Actions - others: list of Actions - """ - compact = CONF.getboolean("dmenu", "compact", fallback=False) - empty_action = [Action('', None)] if not compact else [] - all_actions = [] - all_actions += eths + empty_action if eths else [] - all_actions += aps + empty_action if aps else [] - all_actions += vpns + empty_action if vpns else [] - all_actions += wgs + empty_action if wgs else [] - all_actions += gsms + empty_action if (gsms and wwan) else [] - all_actions += blues + empty_action if blues else [] - all_actions += wwan + empty_action if wwan else [] - all_actions += others + empty_action if others else [] - all_actions += saved + empty_action if saved else [] - return all_actions - - -def get_selection(all_actions): - """Spawn dmenu for selection and execute the associated action.""" - rofi_highlight = CONF.getboolean('dmenu', 'rofi_highlight', fallback=False) - inp = [] - - if rofi_highlight is True: - inp = [str(action) for action in all_actions] - else: - inp = [('== ' if action.is_active else ' ') + str(action) - for action in all_actions] - active_lines = [index for index, action in enumerate(all_actions) - if action.is_active] - - command = dmenu_cmd(len(inp), active_lines=active_lines) - sel = subprocess.run(command, - capture_output=True, - check=False, - input="\n".join(inp), - encoding=ENC, - env=ENV).stdout - - if not sel.rstrip(): - sys.exit() - - if rofi_highlight is False: - action = [i for i in all_actions - if ((str(i).strip() == str(sel.strip()) - and not i.is_active) or - ('== ' + str(i) == str(sel.rstrip('\n')) - and i.is_active))] - else: - action = [i for i in all_actions if str(i).strip() == sel.strip()] - assert len(action) == 1, f"Selection was ambiguous: '{str(sel.strip())}'" - return action[0] - - -def toggle_networking(enable): - """Enable/disable networking - - Args: enable - boolean - - """ - toggle = GLib.Variant.new_tuple(GLib.Variant.new_boolean(enable)) - try: - CLIENT.dbus_call(NM.DBUS_PATH, NM.DBUS_INTERFACE, "Enable", toggle, - None, -1, None, None, None) - except AttributeError: - # Workaround for older versions of python-gobject - CLIENT.networking_set_enabled(enable) - notify(f"Networking {'enabled' if enable is True else 'disabled'}") - - -def toggle_wifi(enable): - """Enable/disable Wifi - - Args: enable - boolean - - """ - toggle = GLib.Variant.new_boolean(enable) - try: - CLIENT.dbus_set_property(NM.DBUS_PATH, NM.DBUS_INTERFACE, "WirelessEnabled", toggle, - -1, None, None, None) - except AttributeError: - # Workaround for older versions of python-gobject - CLIENT.wireless_set_enabled(enable) - notify(f"Wifi {'enabled' if enable is True else 'disabled'}") - - -def toggle_wwan(enable): - """Enable/disable WWAN - - Args: enable - boolean - - """ - toggle = GLib.Variant.new_boolean(enable) - try: - CLIENT.dbus_set_property(NM.DBUS_PATH, NM.DBUS_INTERFACE, "WwanEnabled", toggle, - -1, None, None, None) - except AttributeError: - # Workaround for older versions of python-gobject - CLIENT.wwan_set_enabled(enable) - notify(f"Wwan {'enabled' if enable is True else 'disabled'}") - - -def toggle_bluetooth(enable): - """Enable/disable Bluetooth - - Args: enable - boolean - - References: - https://github.com/blueman-project/blueman/blob/master/blueman/plugins/mechanism/RfKill.py - https://www.kernel.org/doc/html/latest/driver-api/rfkill.html - https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/tree/include/uapi/linux/rfkill.h?h=v5.8.9 - - """ - type_bluetooth = 2 - op_change_all = 3 - idx = 0 - soft_state = 0 if enable else 1 - hard_state = 0 - - data = struct.pack("IBBBB", idx, type_bluetooth, op_change_all, - soft_state, hard_state) - - try: - with open('/dev/rfkill', 'r+b', buffering=0) as rff: - rff.write(data) - except PermissionError: - notify("Lacking permission to write to /dev/rfkill.", - "Check README for configuration options.", - urgency="critical") - else: - notify(f"Bluetooth {'enabled' if enable else 'disabled'}") - - -def launch_connection_editor(): - """Launch nmtui or the gui nm-connection-editor - - """ - terminal = CONF.get("editor", "terminal", fallback="xterm") - gui_if_available = CONF.getboolean("editor", "gui_if_available", fallback=True) - guis = ["gnome-control-center", "nm-connection-editor"] - if gui_if_available is True: - for gui in guis: - if is_installed(gui): - subprocess.run(gui, check=False) - return - if is_installed("nmtui"): - subprocess.run([terminal, "-e", "nmtui"], check=False) - return - notify("No network connection editor installed", urgency="critical") - - -def get_passphrase(): - """Get a password - - Returns: string - - """ - pinentry = CONF.get("dmenu", "pinentry", fallback=None) - if pinentry: - pin = "" - out = subprocess.run(pinentry, - capture_output=True, - check=False, - encoding=ENC, - input='setdesc Get network password\ngetpin\n').stdout - if out: - res = out.split("\n")[2] - if res.startswith("D "): - pin = res.split("D ")[1] - return pin - return subprocess.run(dmenu_cmd(0, "Passphrase"), - stdin=subprocess.DEVNULL, - capture_output=True, - check=False, - encoding=ENC).stdout - - -def delete_connection(): - """Display list of NM connections and delete the selected one - - """ - conn_acts = [Action(i.get_id(), i.delete_async, args=[None, delete_cb, None]) for i in CONNS] - conn_names = "\n".join([str(i) for i in conn_acts]) - sel = subprocess.run(dmenu_cmd(len(conn_acts), "CHOOSE CONNECTION TO DELETE:"), - capture_output=True, - check=False, - input=conn_names, - encoding=ENC, - env=ENV).stdout - if not sel.strip(): - sys.exit() - action = [i for i in conn_acts if str(i) == sel.rstrip("\n")] - assert len(action) == 1, f"Selection was ambiguous: {str(sel)}" - action[0]() - LOOP.run() - - -def delete_cb(dev, res, data): - """Notification if delete completed successfully - - """ - if dev.delete_finish(res) is True: - notify(f"Deleted {dev.get_id()}") - else: - notify(f"Problem deleting {dev.get_id()}", urgency="critical") - LOOP.quit() - - -def set_new_connection(nm_ap, nm_pw, adapter): - """Setup a new NetworkManager connection - - Args: ap - NM.AccessPoint - pw - string - - """ - nm_pw = str(nm_pw).strip() - profile = create_wifi_profile(nm_ap, nm_pw, adapter) - CLIENT.add_and_activate_connection_async(profile, adapter, nm_ap.get_path(), - None, verify_conn, profile) - LOOP.run() - - -def create_wifi_profile(nm_ap, password, adapter): - # pylint: disable=line-too-long - # noqa From https://cgit.freedesktop.org/NetworkManager/NetworkManager/tree/examples/python/gi/add_connection.py - # noqa and https://cgit.freedesktop.org/NetworkManager/NetworkManager/tree/examples/python/dbus/add-wifi-psk-connection.py - # pylint: enable=line-too-long - """Create the NM profile given the AP and passphrase""" - ap_sec = ap_security(nm_ap) - profile = NM.SimpleConnection.new() - - s_con = NM.SettingConnection.new() - s_con.set_property(NM.SETTING_CONNECTION_ID, ssid_to_utf8(nm_ap)) - s_con.set_property(NM.SETTING_CONNECTION_UUID, str(uuid.uuid4())) - s_con.set_property(NM.SETTING_CONNECTION_TYPE, "802-11-wireless") - profile.add_setting(s_con) - - s_wifi = NM.SettingWireless.new() - s_wifi.set_property(NM.SETTING_WIRELESS_SSID, nm_ap.get_ssid()) - s_wifi.set_property(NM.SETTING_WIRELESS_MODE, 'infrastructure') - s_wifi.set_property(NM.SETTING_WIRELESS_MAC_ADDRESS, adapter.get_permanent_hw_address()) - profile.add_setting(s_wifi) - - s_ip4 = NM.SettingIP4Config.new() - s_ip4.set_property(NM.SETTING_IP_CONFIG_METHOD, "auto") - profile.add_setting(s_ip4) - - s_ip6 = NM.SettingIP6Config.new() - s_ip6.set_property(NM.SETTING_IP_CONFIG_METHOD, "auto") - profile.add_setting(s_ip6) - - if ap_sec != "--": - s_wifi_sec = NM.SettingWirelessSecurity.new() - if "WPA" in ap_sec: - if "WPA3" in ap_sec: - s_wifi_sec.set_property(NM.SETTING_WIRELESS_SECURITY_KEY_MGMT, - "sae") - else: - s_wifi_sec.set_property(NM.SETTING_WIRELESS_SECURITY_KEY_MGMT, - "wpa-psk") - s_wifi_sec.set_property(NM.SETTING_WIRELESS_SECURITY_AUTH_ALG, - "open") - s_wifi_sec.set_property(NM.SETTING_WIRELESS_SECURITY_PSK, password) - elif "WEP" in ap_sec: - s_wifi_sec.set_property(NM.SETTING_WIRELESS_SECURITY_KEY_MGMT, - "None") - s_wifi_sec.set_property(NM.SETTING_WIRELESS_SECURITY_WEP_KEY_TYPE, - NM.WepKeyType.PASSPHRASE) - s_wifi_sec.set_wep_key(0, password) - profile.add_setting(s_wifi_sec) - - return profile - - -def verify_conn(client, result, data): - """Callback function for add_and_activate_connection_async - - Check if connection completes successfully. Delete the connection if there - is an error. - - """ - try: - act_conn = client.add_and_activate_connection_finish(result) - conn = act_conn.get_connection() - if not all([conn.verify(), - conn.verify_secrets(), - data.verify(), - data.verify_secrets()]): - raise GLib.Error - notify(f"Added {conn.get_id()}") - except GLib.Error: - try: - notify(f"Connection to {conn.get_id()} failed", - urgency="critical") - conn.delete_async(None, None, None) - except UnboundLocalError: - pass - finally: - LOOP.quit() - - -def create_ap_list(adapter, active_connections): - """Generate list of access points. Remove duplicate APs , keeping strongest - ones and the active AP - - Args: adapter - active_connections - list of all active connections - Returns: aps - list of access points - active_ap - active AP - active_ap_con - active Connection - adapter - - """ - aps = [] - ap_names = [] - active_ap = adapter.get_active_access_point() - aps_all = sorted(adapter.get_access_points(), - key=lambda a: a.get_strength(), reverse=True) - conns_cur = [i for i in CONNS if - i.get_setting_wireless() is not None and - conn_matches_adapter(i, adapter)] - try: - ap_conns = active_ap.filter_connections(conns_cur) - active_ap_name = ssid_to_utf8(active_ap) - active_ap_con = [active_conn for active_conn in active_connections - if active_conn.get_connection() in ap_conns] - except AttributeError: - active_ap_name = None - active_ap_con = [] - if len(active_ap_con) > 1: - raise ValueError("Multiple connection profiles match" - " the wireless AP") - active_ap_con = active_ap_con[0] if active_ap_con else None - for nm_ap in aps_all: - ap_name = ssid_to_utf8(nm_ap) - if nm_ap != active_ap and ap_name == active_ap_name: - # Skip adding AP if it's not active but same name as active AP - continue - if ap_name not in ap_names: - ap_names.append(ap_name) - aps.append(nm_ap) - return aps, active_ap, active_ap_con, adapter - - -def notify(message, details=None, urgency="low"): - """Use notify-send if available for notifications - - """ - delay = CONF.getint('nmdm', 'rescan_delay', fallback=5) - args = ["-u", urgency, "-a", "networkmanager-dmenu", - "-t", str(delay * 1000), message] - if details is not None: - args.append(details) - if is_installed("notify-send"): - subprocess.run(["notify-send"] + args, check=False) - - -def run(): # pylint: disable=too-many-locals - """Main script entrypoint""" - active = CLIENT.get_active_connections() - adapter = choose_adapter(CLIENT) - if adapter: - ap_actions = create_ap_actions(*create_ap_list(adapter, active)) - else: - ap_actions = [] - - vpns = [i for i in CONNS if i.is_type(NM.SETTING_VPN_SETTING_NAME)] - try: - wgs = [i for i in CONNS if i.is_type(NM.SETTING_WIREGUARD_SETTING_NAME)] - except AttributeError: - # Workaround for older versions of python-gobject with no wireguard support - wgs = [] - eths = [i for i in CONNS if i.is_type(NM.SETTING_WIRED_SETTING_NAME)] - blues = [i for i in CONNS if i.is_type(NM.SETTING_BLUETOOTH_SETTING_NAME)] - - vpn_actions = create_vpn_actions(vpns, active) - wg_actions = create_wireguard_actions(wgs, active) - eth_actions = create_eth_actions(eths, active) - blue_actions = create_blue_actions(blues, active) - other_actions = create_other_actions(CLIENT) - wwan_installed = is_installed("ModemManager") - if wwan_installed: - gsms = [i for i in CONNS if i.is_type(NM.SETTING_GSM_SETTING_NAME)] - gsm_actions = create_gsm_actions(gsms, active) - wwan_actions = create_wwan_actions(CLIENT) - else: - gsm_actions = [] - wwan_actions = [] - - list_saved = CONF.getboolean('dmenu', 'list_saved', fallback=False) - saved_cons = [i for i in CONNS if i not in vpns + wgs + eths + blues] - if list_saved: - saved_actions = create_saved_actions(saved_cons) - else: - saved_actions = [Action("Saved connections", prompt_saved, [saved_cons])] - - actions = combine_actions(eth_actions, ap_actions, vpn_actions, wg_actions, - gsm_actions, blue_actions, wwan_actions, - other_actions, saved_actions) - sel = get_selection(actions) - sel() - - -def main(): - """Main. Enables script to be re-run after a wifi rescan - - """ - global CLIENT, CONNS, LOOP # noqa pylint: disable=global-variable-undefined - CLIENT = NM.Client.new(None) - LOOP = GLib.MainLoop() - CONNS = CLIENT.get_connections() - - run() - - -if __name__ == '__main__': - main() - -# vim: set et ts=4 sw=4 : diff --git a/.local/bin/status-mem b/.local/bin/status-mem index 8e12f9a..29f06f3 100755 --- a/.local/bin/status-mem +++ b/.local/bin/status-mem @@ -2,7 +2,7 @@ case $BUTTON in 1) notify-send "Memory hogs" "$(ps axch -o cmd:15,%mem --sort=-%mem | head)" ;; - 2) setsid -f "$TERMINAL" -e htop ;; + 2) setsid -f "$TERMINAL" -e gotop ;; 3) notify-send "Memory module" "Shows Memory Used/Total. - Click to show memory hogs. - Middle click to open htop." ;; diff --git a/.local/bin/unmounter b/.local/bin/unmounter new file mode 100755 index 0000000..7f1dbf5 --- /dev/null +++ b/.local/bin/unmounter @@ -0,0 +1,28 @@ +#!/bin/sh + +# Unmount USB drives or Android phones. Replaces the older `dmenuumount`. Fewer +# prompt and also de-decrypts LUKS drives that are unmounted. + +set -e + +mounteddroids="$(grep simple-mtpfs /etc/mtab | awk '{print "📱" $2}')" +lsblkoutput="$(lsblk -nrpo "name,type,size,mountpoint")" +mounteddrives="$(echo "$lsblkoutput" | awk '($2=="part"||$2="crypt")&&$4!~/\/boot|\/home$|SWAP/&&length($4)>1{printf "💾%s (%s)\n",$4,$3}')" + +allunmountable="$(echo "$mounteddroids +$mounteddrives" | sed "/^$/d;s/ *$//")" +test -n "$allunmountable" + +chosen="$(echo "$allunmountable" | dmenu -i -p "Unmount which drive?")" +chosen="${chosen%% *}" +test -n "$chosen" + +sudo -A umount -l "/${chosen#*/}" +notify-send "Device unmounted." "$chosen has been unmounted." + +# Close the chosen drive if decrypted. +cryptid="$(echo "$lsblkoutput" | grep "/${chosen#*/}$")" +cryptid="${cryptid%% *}" +test -b /dev/mapper/"${cryptid##*/}" +sudo -A cryptsetup close "$cryptid" +notify-send "🔒Device dencryption closed." "Drive is now securely locked again."