updates
This commit is contained in:
parent
dd31193cef
commit
de7855905a
@ -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-o":"lfcd\n"'
|
||||||
bind '"\C-t":"tms\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-n":"zk edit --interactive --sort modified-\n"'
|
||||||
bind '"\C-f":"cd $(dirname "$(fzf)")\n"'
|
bind '"\C-f":"cd $(dirname "$(fzf)")\n"'
|
||||||
|
|
||||||
|
|||||||
5
.config/mpv/input.conf
Normal file
5
.config/mpv/input.conf
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
l seek 5
|
||||||
|
h seek -5
|
||||||
|
j seek -60
|
||||||
|
k seek 60
|
||||||
|
S cycle sub
|
||||||
24
.config/ncmpcpp/bindings
Normal file
24
.config/ncmpcpp/bindings
Normal file
@ -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
|
||||||
1
.config/ncmpcpp/config
Normal file
1
.config/ncmpcpp/config
Normal file
@ -0,0 +1 @@
|
|||||||
|
execute_on_song_change = notify-send "Now Playing" "$(mpc --format '%title% \n%artist% - %album%' current)"
|
||||||
@ -1,6 +1,6 @@
|
|||||||
local M = {}
|
local M = {}
|
||||||
|
|
||||||
M.autoformat = true
|
M.autoformat = false
|
||||||
|
|
||||||
function M.toggle()
|
function M.toggle()
|
||||||
M.autoformat = not M.autoformat
|
M.autoformat = not M.autoformat
|
||||||
|
|||||||
@ -1,32 +1,32 @@
|
|||||||
return {
|
return {
|
||||||
"nvim-neorg/neorg",
|
-- "nvim-neorg/neorg",
|
||||||
-- event = "BufEnter *.norg",
|
-- -- event = "BufEnter *.norg",
|
||||||
event = "VeryLazy",
|
-- event = "VeryLazy",
|
||||||
dependencies = { { "nvim-treesitter/nvim-treesitter" }, { "nvim-lua/plenary.nvim" } },
|
-- dependencies = { { "nvim-treesitter/nvim-treesitter" }, { "nvim-lua/plenary.nvim" } },
|
||||||
build = ":Neorg sync-parsers",
|
-- build = ":Neorg sync-parsers",
|
||||||
opts = {
|
-- opts = {
|
||||||
load = {
|
-- load = {
|
||||||
["core.defaults"] = {},
|
-- ["core.defaults"] = {},
|
||||||
["core.concealer"] = {},
|
-- ["core.concealer"] = {},
|
||||||
["core.dirman"] = {
|
-- ["core.dirman"] = {
|
||||||
config = {
|
-- config = {
|
||||||
workspaces = {
|
-- workspaces = {
|
||||||
work = "~/work/neorg/",
|
-- work = "~/work/neorg/",
|
||||||
personal = "~/notes/neorg/",
|
-- personal = "~/notes/neorg/",
|
||||||
journal = "~/notes/neorg/journal/",
|
-- journal = "~/notes/neorg/journal/",
|
||||||
},
|
-- },
|
||||||
},
|
-- },
|
||||||
},
|
-- },
|
||||||
["core.completion"] = {
|
-- ["core.completion"] = {
|
||||||
config = {
|
-- config = {
|
||||||
engine = "nvim-cmp",
|
-- engine = "nvim-cmp",
|
||||||
},
|
-- },
|
||||||
},
|
-- },
|
||||||
["core.presenter"] = {
|
-- ["core.presenter"] = {
|
||||||
config = {
|
-- config = {
|
||||||
zen_mode = "zen-mode",
|
-- zen_mode = "zen-mode",
|
||||||
},
|
-- },
|
||||||
},
|
-- },
|
||||||
},
|
-- },
|
||||||
},
|
-- },
|
||||||
}
|
}
|
||||||
|
|||||||
247
.config/pipewire/pipewire.conf
Normal file
247
.config/pipewire/pipewire.conf
Normal file
@ -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 = {
|
||||||
|
#<factory-name regex> = <library-name>
|
||||||
|
#
|
||||||
|
# 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 = <module-name>
|
||||||
|
# [ args = { <key> = <value> ... } ]
|
||||||
|
# [ 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 = <factory-name>
|
||||||
|
# [ args = { <key> = <value> ... } ]
|
||||||
|
# [ 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 = <program-name> [ args = "<arguments>" ] }
|
||||||
|
#
|
||||||
|
# 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" }
|
||||||
|
]
|
||||||
@ -62,3 +62,13 @@ alias \
|
|||||||
cls="clear" \
|
cls="clear" \
|
||||||
c="clear" \
|
c="clear" \
|
||||||
cdc="cd && 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"
|
||||||
|
|||||||
0
.config/wget/wgetrc
Normal file
0
.config/wget/wgetrc
Normal file
@ -13,8 +13,10 @@ numlockx
|
|||||||
# set default desktop window layout
|
# set default desktop window layout
|
||||||
"$HOME/.screenlayout/default.sh"
|
"$HOME/.screenlayout/default.sh"
|
||||||
|
|
||||||
|
nitrogen --restore &
|
||||||
|
|
||||||
# I don't use the following but I like it so I'll leave it for now.
|
# 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
|
for program in $autostart; do
|
||||||
pidof -s "$program" || "$program" &
|
pidof -s "$program" || "$program" &
|
||||||
|
|||||||
15
.config/zathura/zathurarc
Normal file
15
.config/zathura/zathurarc
Normal file
@ -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
|
||||||
@ -7,17 +7,7 @@
|
|||||||
# System Applications
|
# System Applications
|
||||||
###############################################################################
|
###############################################################################
|
||||||
|
|
||||||
# Dunst - notifications
|
# conflicted about using this and/or xprofile...
|
||||||
dunst &
|
|
||||||
|
|
||||||
# Network Manager Applet
|
|
||||||
nm-applet &
|
|
||||||
|
|
||||||
# Compostitor with special stuff
|
|
||||||
picom &
|
|
||||||
|
|
||||||
# wallpaper
|
|
||||||
nitrogen --restore &
|
|
||||||
|
|
||||||
###############################################################################
|
###############################################################################
|
||||||
# Startup Applications
|
# Startup Applications
|
||||||
|
|||||||
@ -1,15 +1,23 @@
|
|||||||
#!/bin/bash
|
#!/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"
|
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 $@ ]]; then
|
||||||
if [[ -z $selected ]]; then
|
|
||||||
|
selected=$(echo "$commands $languages" | tr ' ' '\n' | fzf)
|
||||||
|
if [[ -z $selected ]]; then
|
||||||
exit 0
|
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
|
fi
|
||||||
|
|
||||||
read -rp "Enter Query: " query
|
|
||||||
|
|
||||||
query=$(echo "$query" | tr ' ' '+')
|
|
||||||
tmux neww bash -c "curl cht.sh/$selected/$query & while [ : ]; do sleep 1; done"
|
|
||||||
|
|||||||
@ -36,7 +36,7 @@ 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?")
|
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
|
xrandr --output "$primary" --auto --scale 1.0x1.0 --output "$secondary" --"$direction"-of "$primary" --auto --scale 1.0x1.0
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
morescreen() { # If multi-monitor is selected and there are more than two screens.
|
morescreen() { # If multi-monitor is selected and there are more than two screens.
|
||||||
primary=$(echo "$screens" | dmenu -i -p "Select primary display:")
|
primary=$(echo "$screens" | dmenu -i -p "Select primary display:")
|
||||||
@ -44,23 +44,22 @@ morescreen() { # If multi-monitor is selected and there are more than two screen
|
|||||||
direction=$(printf "left\\nright" | dmenu -i -p "What side of $primary should $secondary be on?")
|
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:")
|
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
|
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.
|
multimon() { # Multi-monitor handler.
|
||||||
case "$(echo "$screens" | wc -l)" in
|
case "$(echo "$screens" | wc -l)" in
|
||||||
2) twoscreen ;;
|
2) twoscreen ;;
|
||||||
*) morescreen ;;
|
*) morescreen ;;
|
||||||
esac ;}
|
esac ;}
|
||||||
|
|
||||||
onescreen() { # If only one output available or chosen.
|
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.
|
postrun() { # Stuff to run to clean up.
|
||||||
setbg # Fix background if screen size/arangement has changed.
|
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
|
{ killall dunst ; setsid -f dunst ;} >/dev/null 2>&1 # Restart dunst to ensure proper location on screen
|
||||||
}
|
}
|
||||||
|
|
||||||
# Get all possible displays
|
# Get all possible displays
|
||||||
allposs=$(xrandr -q | grep "connected")
|
allposs=$(xrandr -q | grep "connected")
|
||||||
@ -70,7 +69,7 @@ screens=$(echo "$allposs" | awk '/ connected/ {print $1}')
|
|||||||
|
|
||||||
# If there's only one screen
|
# If there's only one screen
|
||||||
[ "$(echo "$screens" | wc -l)" -lt 2 ] &&
|
[ "$(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:
|
# Get user choice including multi-monitor and manual selection:
|
||||||
chosen=$(printf "%s\\nmulti-monitor\\nmanual selection" "$screens" | dmenu -i -p "Select display arangement:") &&
|
chosen=$(printf "%s\\nmulti-monitor\\nmanual selection" "$screens" | dmenu -i -p "Select display arangement:") &&
|
||||||
|
|||||||
@ -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
|
|
||||||
@ -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."
|
|
||||||
114
.local/bin/mounter
Executable file
114
.local/bin/mounter
Executable file
@ -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
|
||||||
@ -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", "<num_lines>", "-p", "<prompt>", "-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 :
|
|
||||||
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
case $BUTTON in
|
case $BUTTON in
|
||||||
1) notify-send "Memory hogs" "$(ps axch -o cmd:15,%mem --sort=-%mem | head)" ;;
|
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.
|
3) notify-send "Memory module" "Shows Memory Used/Total.
|
||||||
- Click to show memory hogs.
|
- Click to show memory hogs.
|
||||||
- Middle click to open htop." ;;
|
- Middle click to open htop." ;;
|
||||||
|
|||||||
28
.local/bin/unmounter
Executable file
28
.local/bin/unmounter
Executable file
@ -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."
|
||||||
Loading…
Reference in New Issue
Block a user