From 91a2d139fc7a152f80027e5f841c3cb77190343d Mon Sep 17 00:00:00 2001 From: Ryan Rueger Date: Wed, 10 Apr 2024 14:48:49 +0200 Subject: Initial commit --- README.md | 517 +++++++++++++++ desktopctl | 2065 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 2582 insertions(+) create mode 100644 README.md create mode 100755 desktopctl diff --git a/README.md b/README.md new file mode 100644 index 0000000..544c6a7 --- /dev/null +++ b/README.md @@ -0,0 +1,517 @@ +# desktopctl + +*"A desktop environment for window managers"* + +Using a window manager (i3/sway/awesome/etc) should not mean that one has to +completely reinvent the wheel and manage a strange collection of one-liners to +get wifi status or to see if headphones are connected. This project aims to be +a solution to this problem. + +Some of these tools are simple one-liners, some are more complex. The idea is to +create a suite of platform-independent (Distro/Desktop Environment independent) +tools to get and set system information. + +Similar in nature to [neofetch](https://github.com/dylanaraps/neofetch), but +also allows for *setting* information too (not just *getting*). + +### Project status + +The code is not particularly robust. It can and will fail in non-obvious ways if +certain utility programs are not installed. + +Notably, this program assumes certain permissions are granted to the user. For +example, to change the brightness in Archlinux, the user must be part of the +`video` group; or to non-interactively change the time zone with `timedatectl` +one needs a polkit rule. + +### Features + +* **Session Authentication** Show session authentication status +* **Backlight Manager** Get and set backlight +* **Battery Manager** Get battery status +* **Bluetooth Manager** Get and set Bluetooth +* **Colour picker** Colour picker +* **CPU frequency** Get CPU frequency +* **Headphone indicator** Get whether headpones are plugged in +* **IP Address** Get local IP address +* **Media Watch Time** Print total media time of listed files +* **Memory** Get memory usage +* **Monitor Directories/Files** Monitor directories/files +* **Check if online** Get current internet connection status +* **Screenshots** Take screenshots +* **Sound Manager** Get and set sound +* **Internet speedtest** Show current internet speed +* **SSH** List ssh clients on local network +* **Time zone** Get and set current time zone +* **Wifi Status** Set wifi on/off + +Limited features + +* **Configuration file differences** Show configuration file differences (Archlinux only) +* **Power Manager** Set power and charging profiles (Dell only) +* **Reboot check** List programs that need to be restarted (Archlinux only) +* **Xwayland clients** List number of xwayland clients (Sway only) + +--- + +### Configuring + +The configuration is read from `~/.config/desktopctl/desktopctlrc`. + +--- + +#### Session Authentication + +Show session + +Prints whether the session is [correctly +authenticated](https://wiki.archlinux.org/title/General_troubleshooting#Session_permissions) +using `loginctl`. + +Arguments: None + +#### Backlight Manager + +Get and set backlight + +Is configured to give non-linear brightness changes with a very simple +interface. Can easily be bound to keyboard brightness keys. + +``` +Arguments: + NUMBER, Set backlight to NUMBER percentage. + +NUMBER, Increase backlight percentage by NUMBER. + -NUMBER, Decrease backlight percentage by NUMBER. + + +, Go to the next backlight intensity level. + -, Go to the previous backlight intensity level. + + -p, --print, Print current backlight percentages. + -n, --notify, Send desktop notification with current backlight intensity. + -h, --help, Display this help. +``` + +Changes are made with `xbacklight` under the hood. + +The brightness profile can be configured by setting `$BACKLIGHT_LEVELS` in the +configuration file. + +The default is + +``` +BACKLIGHT_LEVELS=(0 1 2 3 4 5 6 7 8 9 10 15 20 25 30 40 50 60 70 80 90 100) +``` + +(Small steps at lower brightnesses and larger steps at higher brightnesses) + +#### Battery Manager + +Get battery status + +Gives very detailed statistics about the battery usage rate. Useful for building +a status line. Comes shipped with a default status line. + +``` +Arguments: + power, Current power usage (in W) + voltage, Current battery voltage (in uV) + current, Current battery current (in uA) + capacity, Current battery capacity (in %) + charge, Current battery capacity (in C) + charging, Exit value 0 if charging + charged, Exit value 0 if fully charged + + remaining, If on battery: remaining time until battery depleted + If charging: remaining time until battery full + (Human readable string) + + info, Statusline like information about battery + (May need correct font configuration) +``` + +Configure battery icons with `$BATTERY_LEVELS_CHARGING` and `$BATTERY_LEVELS`. + +The defaults are set using [Overpass Mono Nerd +Fonts](https://github.com/ryanoasis/nerd-fonts). + +``` +BATTERY_LEVELS_CHARGING=(󰂆 󰂆 󰂇 󰂈 󰂉 󰂊 󰂊 󰂋 󰂋 󰂅 ) +BATTERY_LEVELS=(󰁺 󰁻 󰁼 󰁽 󰁾 󰁿 󰂀 󰂁 󰂂 󰁹 ) +``` + +(You may not be able to see these in browser) + +#### Bluetooth Manager + +Get and set Bluetooth + +``` +Arguments: + on, Power bluetooth on + off, Power bluetooth off + --daemon, daemon, Interact with the bluetooth daemon + -i, --indicator, Print current bluetooth connection indicator + -l, --list, list, List connected bluetooth devicees + -r, --restart, restart, Restart bluetooth + -c, --connect, connect, Connect to bluetooth device + -d, --disconnect, disconnect, Connect to bluetooth device + -h, --help, help, Print this help +``` + +To display battery status of bluetooth devices, experimental features must be +enabled in `/etc/bluetooth/main.conf` in the `[General]` section by setting + +``` +Experimental = true +``` + +Allows configuring bluetooth from the command line whilst aliasing devices with +nice names. For example + +``` +desktopctl bluetooth connect sony-headphones +``` + +Instead of + +``` +bluetooth on +bluetoothctl agent on +bluetoothctl default-agent +# Find out the MAC address of your Bluetooth device +# Suppose it is 00:23:02:C0:A9:6F +bluetoothctl connect 00:23:02:C0:A9:6F +``` + +These aliases are defined in the configuration file with a function which must +be called `desktopctl.bt.config` and should return from an alias the correct MAC +address. + +Here is an example + +``` +# Inside ~/.config/desktopctl/desktopctlrc +desktopctl.bt.config () { + case "$1" in + aukey) ID=00:23:02:C0:A9:6F ;; + soundcore) ID=08:EB:ED:5F:37:28 ;; + dell-keyboard) ID=ED:DD:9A:5F:57:DB ;; + dell-mouse) ID=EE:79:C0:96:C5:C3 ;; + hr) ID=DA:80:4B:56:CE:4E ;; + sony) ID=38:18:4C:BE:BB:00 ;; + jlab) ID=00:23:10:0C:74:47 ;; + wacom) ID=DC:2C:26:09:C7:70 ;; + shokz) ID=C0:86:B3:57:39:67 ;; + *) ID=$1 ;; + esac +} +```` + +Bluetooth is also quite fragile. Often one needs to restart to make things work. +This script handles this automatically. + +May require polkit rule to allow restarting bluetooth without password + +This rule will allow any user in the `wheel` group to restart the systemd +`bluetooth.service`. + +``` +polkit.addRule(function(action, subject) { + if (action.id == "org.freedesktop.systemd1.manage-units" && + subject.isInGroup("wheel") && + action.lookup("unit") == "bluetooth.service") { + return polkit.Result.YES; + }; +}); +``` + +#### Colour picker + +Simple colour picker. Copies value to clipboard in RBG, Hex and SRGB values. + +Compatible with wayland and X11. + +#### CPU frequency + +Prints `/` in human readable form + +Data as reported by: `/sys/devices/system/cpu/cpu*/cpufreq/scaling_cur_freq` + +``` +Arguments: + -a, --avg, avg, Only print average CPU clock speed over all cores + -m, --max, max, Only print maximum CPU clock speed over all cores + -r, --raw, raw, Do not make human readable + + -h, --help, help, Display this help +``` + +Useful for status lines. + +#### Headphone indicator + +Prints indicator if headphones are plugged in + +Uses `pactl` under the hood + +Configure the icon in the configuration file by setting `HEADPHONE_INDICATOR`. + +Useful for status lines. + +#### IP Address + +Get local IP address. + +#### Media Watch Time + +Prints watch times of media with remaining time and percentage. + +Requires `column` from `util-linux`. + +``` +Arguments: + A list of media files (mp4) +``` + +Example: + +``` +$ cd tv-shows/planet-earth +$ desktopctl mediawatchtime *mp4 +Item | Title | Duration | Watched | % Watched | Remaining +1 | S01E01-planet-earth-from-pole-to-pole.mp4: | 0:59 | 0:59 | 9.00 | 9:44 +2 | S01E02-planet-earth-mountains.mp4: | 0:57 | 1:56 | 18.00 | 8:47 +3 | S01E03-planet-earth-fresh-water.mp4: | 0:58 | 2:55 | 27.00 | 7:48 +4 | S01E04-planet-earth-caves.mp4: | 0:57 | 3:52 | 36.00 | 6:51 +5 | S01E05-planet-earth-deserts.mp4: | 0:57 | 4:50 | 45.00 | 5:53 +6 | S01E06-planet-earth-ice-worlds.mp4: | 0:58 | 5:49 | 54.00 | 4:54 +7 | S01E07-planet-earth-great-plains.mp4: | 0:58 | 6:48 | 63.00 | 3:55 +8 | S01E08-planet-earth-jungles.mp4: | 0:58 | 7:46 | 72.00 | 2:56 +9 | S01E09-planet-earth-shallow-seas.mp4: | 0:58 | 8:45 | 81.00 | 1:57 +10 | S01E10-planet-earth-seasonal-forests.mp4: | 0:58 | 9:44 | 90.00 | 0:58 +11 | S01E11-planet-earth-ocean-deep.mp4: | 0:58 | 10:43 | 100.00 | 0:00 +``` + +#### Memory + +Print memory usage (human readable). + +``` +Options + -r, --raw, raw, Display in raw bytes (not human readable). +``` + +Useful for status lines + +#### Monitor Directories/Files + +Prints size, number of files in directory, and changes since last polling. + +Argument: Directory or file to monitor + +``` +Arguments: + dir, Directory or file to monitor + -i, --interval, Time interval to re-compute file sizes and changes in seconds + (Default 60) +``` + +E.g. + +``` +$ rsync /path/to/src/dir /path/to/dest/dir +``` + +Forgot to add progress options! ... instead of cancelling the transfer, just +monitor the destination directory directly + +``` +$ desktopctl mondir -i 5 /path/to/dest/dir +mondir: Monitoring '/path/to/dest/dir' with interval 5s. + +2024-03-30-15:23:14 Total: 188G Files: 349222 +2024-03-30-15:23:20 Total: 188G Files: 349266 Diff: 500MiB Files diff: 44 Speed: 100MiB/s +2024-03-30-15:23:26 Total: 189G Files: 349339 Diff: 525MiB Files diff: 73 Speed: 105MiB/s +... +``` + +Of course this is also useful for tools that do not offer progress statistics. + +#### Check if online + +Get current internet connection status + +``` +Arguments: + -t, --timeout, Set timeout in seconds + -q, --quiet, Do not print. Only uses exit value +``` + +Note: Options must be specified separately i.e. `-q -t 30` and not `-qt30` or +`-qt 30`. + +Useful in scripts, to start backups or synchronise emails only when connected to +the internet. + +#### Screenshots + +Take screenshots + +Supports taking a partial selection of the screen + +Supports wayland/x11 + +Screenshots are copied to the clipboard + +``` +Arguments: + --select, Will ask user to make selection of area to screenshot + --blurred, (x11 only) Will blur screenshot +``` + +Blurred screenshots are nice for creating a lockscreen with current screen +contents blurred. + +#### Sound Manager + +Get and set sound + +``` +Arguments: + +, Increase volume + -, Decrease volume + sink, Print current default output sink + ismuted, Print "yes" if muted all devices are muted, else no + notify, Send a desktop notification of current volume + get-volume, get Print current volume + mute, Mute current default sink + toggle, Toggle mute status +``` + +Uses `pactl` under the hood + +Useful for displaying current volume in status line or when binding keyboard +buttons. + +#### Internet speedtest + +Show current internet speed + +`curl`s a file from Linode + +``` +http://eu-central-1.linodeobjects.com/speedtest/100MB-speedtest +``` + +#### SSH + +List ssh clients on local network + +Useful for finding a headless Raspberry Pi after setting it up. + +#### Time zone + +Get and set current time zone. + +Uses [ip-api.com](http://ip-api.com/) to get the time zone based on IP address. + +Uses systemd's `timedatectl` to set the time zone. To do this non-interactively +one needs the following polkit rule + +``` +# /etc/polkit-1/rules.d/timedate.rules +polkit.addRule(function(action, subject) { + if (action.id == "org.freedesktop.timedate1.set-time") { + return polkit.Result.YES; + } +}); +``` + +#### Wifi Status + +Set wifi on/off. Show wifi connection status, signal strength. Useful for status +lines. + +``` +Arguments: + ssid, Print connected ssid + icons, Print ssid with icons + only-icons Only print icons + off, Turn wifi off + on, Turn wifi on +``` + +Configuration values + +``` +WIFI_ICON_CONFIGURING=󰤫 +WIFI_ICON_LOW_STRENGTH=󰤟 +WIFI_ICON_MED_STRENGTH=󰤢 +WIFI_ICON_HIG_STRENGTH=󰤨 +WIFI_ICON_DISCONNECTED=󰤯 +WIFI_ICON_OFF=󰤮 +# nm for Network Manager and iwd for IWD +WIFI_BACKEND=nm +WIFI_DEVICE=wlan0 +``` + +--- + +### Limited Features + +#### Config Diff + +Uses configured `DIFFTOOL` (from configuration file) to display changes of files +that have been modified on disk from how they were originally shipped with the +package manager. + +``` +Arguments: + path, Path to configuration file +``` + +Example: See changes from the default in the bluetooth configuration file, call + +``` +desktopctl config_diff /etc/bluetooth/main.conf +``` + +Only works on Archlinux. + +#### Power Manager + +``` +Charging Profiles: + ac, Primarily AC Charging, Auto performance + express, Express charging, Low performance + +Performance: + auto, Automatically adjust performance + cool, Set performance profile to cool + desktop, Highest performance settings + laptop, Laptop performance settings + low, Set perforamnce to power saving + quiet, Set performance profile to quiet +``` + +#### Reboot check + +Prints information about what services need to be restarted, and whether the +kernel is old + +Used to generally inform, whether the system should be rebooted + +Can be wrapped with pacman hook to give information after update + +``` +Options: + -d, --detailed, Print more detailed information +``` + +#### Xwayland clients + +Count xwayland clients + +Arguemnts: + format, A format string in which '%n' will replace number of Xwayland clients + if there are any. If there are none, nothing will be printed. diff --git a/desktopctl b/desktopctl new file mode 100755 index 0000000..bac460b --- /dev/null +++ b/desktopctl @@ -0,0 +1,2065 @@ +#!/bin/bash + +# dsektopctl +# +# A desktop environment for window managers +# +# Ryan Rueger 2024 + +shopt -s extglob + +# Configuration {{{{{ +if [[ -r "$HOME/.config/desktopctl/desktopctlrc" ]] +then + source "$HOME/.config/desktopctl/desktopctlrc" +fi + +# Battery icons +if [[ -z BATTERY_LEVELS_CHARGING ]] +then + BATTERY_LEVELS_CHARGING=(󰂆 󰂆 󰂇 󰂈 󰂉 󰂊 󰂊 󰂋 󰂋 󰂅 ) +fi + +if [[ -z BATTERY_LEVELS ]] +then + BATTERY_LEVELS=(󰁺 󰁻 󰁼 󰁽 󰁾 󰁿 󰂀 󰂁 󰂂 󰁹 ) +fi + +# Window manager +WINDOWING=${XDG_SESSION_TYPE:-x11} +XAUTHORITY=${XAUTHORITY:-$HOME/.Xauthority} + +# Screenshots +SCREENSHOTS=${SCREENSHOTS:-$HOME/.cache/screenshots} +SCREENSHOT_SIZE=${SCREENSHOT_SZE:-1920x1080} + +# General +DIFFTOOL=${DIFFTOOL:-diff} + +# Wifi configuration +WIFI_ICON_OFF=${WIFI_ICON_OFF:-Off} +WIFI_ICON_ON=${WIFI_ICON_ON:-Connected:} +WIFI_BACKEND=${WIFI_BACKEND:-nm} +WIFI_DEVICE=${WIFI_DEVICE:-wlan0} + +# Headphone indicator +HEADPHONE_INDICATOR=${HEADPHONE_INDICATOR:-"H"} + +# Power statistics +UEVENT=${UEVENT:-/sys/class/power_supply/BAT0/uevent} +POWERSTATS=${POWERSTATS:-$HOME/.powerstats} + +# Backlight power levels +# Cannot use default values syntax for arrays +if [[ -z $BACKLIGHT_LEVELS ]] +then + BACKLIGHT_LEVELS=(0 1 2 3 4 5 6 7 8 9 10 15 20 25 30 40 50 60 70 80 90 100) +fi + +# Bluetooth +BLUETOOTH_ICON_ON=${BLUETOOTH_ICON_ON:-B} +BLUETOOTH_ICON_CONNECTED=${BLUETOOTH_ICON_CONNECTED:-Bc} + +# export DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/$UID/bus +# export DISPLAY=:0 +# }}}}} + +# Logging {{{{{ +desktopctl.log () { + 2>&1 echo "desktopctl: $*" +} +# }}}}} + +# Dependencycheck {{{{{ +desktopctl.dependencycheck () { + for DEPENDENCY in "$@" + do + if ! command -v "$DEPENDENCY" > /dev/null 2>&1 + then + desktopctl.log "dependencycheck: Requires executable '$DEPENDENCY'" + exit 2 + fi + done +} +# }}}}} +# Sudocheck {{{{{ +desktopctl.sudocheck () { + for COMMAND in "$@" + do + if ! sudo -n "$@" > /dev/null 2>&1 + then + desktopctl.log "sudocheck: Error: You don't have sufficient permissions to execute '$COMMAND'." + fi +done +} +# }}}}} +# Rounding calculator {{{{{ +desktopctl.bcr () { + bc -l <0)-(t<0))/2)/10^scale +EOF +} +# }}}}} + +# Session Authentication {{{{{ +desktopctl.session_auth.usage () { +cat < /dev/null 2>&1 + then + bc -l <<< "scale=1; $(paste -d'*' /sys/class/power_supply/BAT0/{current_now,voltage_now})/10^12" + else + echo ?? + fi +} + +desktopctl.battery.get_capacity () { + # Get the current battery capacity from the UEVENT file as a percentage of + # the last full charge + # + # Arguments: None + + awk -F= '$1 == "POWER_SUPPLY_CAPACITY" {print $2}' "$UEVENT" +} + +desktopctl.battery.get_charge () { + # Get the total charge (in coulombs) that the battery currently contains as + # reported by the UEVENT file + # + # Arguments: None + + awk -F= '$1 == "POWER_SUPPLY_CHARGE_NOW" {print $2}' "$UEVENT" +} + +desktopctl.battery.is_charging () { + # True if battery is charging (but not full), false otherwise. + # + # Arguments: None + + grep -qF Charging "$UEVENT" +} + +desktopctl.battery.is_charged () { + # True if battery is full, false otherwise. + # + # Arguments: None + grep -qF Charging "$UEVENT" +} + +desktopctl.battery.remaining () { + # Return (in human readable format) the current time remaining time left as + # calculated by acpi + # If charging, the time until the battery is full will be displayed + # + # Arguments: None + + acpi -b | awk '{print $5}' | awk -F: '{print $1 ":" $2}' +} + +desktopctl.battery.info () { + bcr () { + # Rounding calculator + bc -l <<< "t=$1; scale=${2:-2}; (t*10^scale+((t>0)-(t<0))/2)/10^scale" + } + + get_hist () { + # Crude check, to see if the battery is currently properly understood by the + # kernel + # (After plugging a laptop in, and waiting for it to charge, there can be a + # few seconds, where everything is confused --- noticable on usb-c machines + # that take 10-15 seconds to negotiate charging voltages/currents with the + # charging block) + if cat /sys/class/power_supply/BAT0/{current_now,voltage_now} > /dev/null 2>&1 + then + # Read file backwards, because we want the most recent entries + tac "$POWERSTATS" | awk -v n=$(($1*2)) '{if ($2 !=0) {c+=$2;s+=1}; if (s==n) {printf "%.1f\n", c/n; exit}}' + else + echo ?? + fi + } + + PERCENTAGE=$(cat /sys/class/power_supply/BAT0/capacity) + BATTERY_STATUS=$(cat /sys/class/power_supply/BAT0/status) + + if [[ "$BATTERY_STATUS" == "Full" ]] + then + ICON="" + elif [[ "$BATTERY_STATUS" == "Charging" ]] + then + ICON="${BATTERY_LEVELS_CHARGING[$((PERCENTAGE/10))]}" + + BATTERY_INFO="${PERCENTAGE}%${ICON} $(desktopctl.battery.calc_power)W" + else + ICON="${BATTERY_LEVELS[$((PERCENTAGE/10))]}" + + NOW=$(get_hist 5) + SHORT_TERM=$(get_hist 600) + BATTERY_INFO="${NOW}/${SHORT_TERM} ${PERCENTAGE}% ${ICON}" + fi + + echo "$BATTERY_INFO" | sed 's/\s*$//' +} + +desktopctl.battery.daemon () { + while true + do + TIME=$(date '+%F-%H%M%S.%N') + POWER=$(_power) + echo "$TIME $POWER" >> "$POWERSTATS" + sleep 1 + done +} + +desktopctl.battery () { + if [[ ! -r "$UEVENT" ]] ; then + desktopctl.log "stats: Error: Cannot read uevent file '$UEVENT'" + exit 1 + fi + + case "$1" in + voltage) desktopctl.battery.get_voltage ;; + current) desktopctl.battery.get_current ;; + power) desktopctl.battery.calc_power ;; + capacity) desktopctl.battery.get_capacity ;; + charge) desktopctl.battery.get_charge ;; + charging) desktopctl.battery.is_charging ;; + charged) desktopctl.battery.is_charged ;; + remaining) desktopctl.battery.remaining ;; + info) desktopctl.battery.info ;; + *) desktopctl.battery.usage ;; + esac +} +# }}}}} +# Bluetooth {{{{{ +desktopctl.bt.usage () { +cat < /dev/null 2>&1 +} + +desktopctl.bt.power_on () { + sudo systemctl start bluetooth + # Uses rfkill under the hood + # 'bluetooth' command is supplied by package 'tlp' + bluetooth on > /dev/null 2>&1 + sleep 0.5 + if ! bluetoothctl power on > /dev/null 2>&1 + then + echo "desktopctl: bt: Bluez not ready" + fi +} + +desktopctl.bt.list_connected_devices () { + if [[ $1 == '-1' ]] + then + if ! desktopctl.bt.is_powered + then + exit 1 + fi + bluetoothctl devices \ + | awk '{print $2}' \ + | while read -r UUID; do bluetoothctl info "$UUID"; done \ + | awk '/Name|Connected|Battery/ {print $2}' \ + | paste - - - \ + | awk '$2 == "yes" {print $1}' \ + | paste -sd' ' + else + if ! desktopctl.bt.is_powered -v + then + exit 1 + fi + echo + bluetoothctl devices \ + | awk '{print $2}' \ + | while read -r UUID; do bluetoothctl info "$UUID"; done \ + | awk '/Name|Connected/ {print $2}' \ + | paste - - \ + | column -t -N Device,Connected \ + | sed 's/^/ /' + + echo + fi +} + +desktopctl.bt.restart () { + echo -n "Restarting bluetooth ..." + systemctl restart bluetooth --quiet + echo -e "\r[Done] Restarting bluetooth." + + # Unblock bluetooth availablility + desktopctl.bt.power_on + + echo -n "Powering on bluetooth radio..." + bluetoothctl power on > /dev/null 2>&1 + echo -e "\r[Done] Powering on bluetooth radio." + + echo -n "Starting agent..." + bluetoothctl agent on > /dev/null 2>&1 + bluetoothctl default-agent > /dev/null 2>&1 + echo -e "\r[Done] Starting agent..." +} + +desktopctl.bt.daemon () { + # Automatically turn off bluetooth if no devices are connected for 5 minutes + + if (($(pgrep -u "$UID" -cxf '^.*/de bt --daemon.*') > 1)) + then + echo "bt: Daemon already running." + exit 1 + fi + + NOT_CONNECTED=0 + THRESHOLD=5 + NOTIFIED=false + + while true + do + if desktopctl.bt.is_powered + then + if (($(desktopctl.bt.list_connected_devices | awk 'c=0; $2 == "yes" {c++} END {print c}') == 0)) + then + ((NOT_CONNECTED++)) + + if ((NOT_CONNECTED > "$THRESHOLD")) + then + echo "bt: Powering off bluetooth after $THRESHOLD minutes inactivity" + desktopctl.bt.power_off + notify "desktopctl: bt: Powering off bluetooth" + NOT_CONNECTED=0 + fi + NOTIFIED=false + else + NOT_CONNECTED=0 + if [[ $NOTIFIED == false ]] + then + echo "t: Bluetooth is powered on again, and connected to a device" + fi + NOTIFIED=true + fi + else + NOT_CONNECTED=0 + NOTIFIED=false + fi + + sleep 60 + done & + exit +} + +desktopctl.bt.connect () { + desktopctl.bt.config "$1" + + echo -n "Connecting to device..." + + attempt=1 + until grep -q 'Connected: yes' <(bluetoothctl info "$ID") + do + if ((attempt % 10 == 0)) + then + desktopctl.bt.restart + else + echo -en "\rConnection attempt $attempt" + bluetoothctl connect "$ID" > /dev/null 2>&1 + sleep 1 + ((attempt++)) + fi + done + # Need trailing spaces to clean up the + # Connecting to device... + # string + echo -e "\rConnected " +} + +desktopctl.bt.disconnect () { + desktopctl.bt.config "$1" + bluetoothctl disconnect "$ID" +} + +desktopctl.bt.indicator () { + if (( $(desktopctl.bt.list_connected_devices | grep -c 'yes$') != 0 )) + then + echo "$BLUETOOTH_ICON_CONNECTED" + elif desktopctl.bt.is_powered + then + echo "$BLUETOOTH_ICON_ON" + else + echo "" + fi +} + +desktopctl.bt () { + case "$1" in + on) shift; desktopctl.bt.power_on ;; + off) shift; desktopctl.bt.power_off ;; + --daemon|daemon) shift; desktopctl.bt.daemon ;; + -i|--indicator) shift; desktopctl.bt.indicator ;; + -l|--list|list) shift; desktopctl.bt.list_connected_devices "$@" ;; + -r|--restart|restart) shift; desktopctl.bt.restart ;; + -c|--connect|connect) shift; desktopctl.bt.power_on && desktopctl.bt.connect "$@" ;; + -d|--disconnect|disconnect) shift; desktopctl.bt.disconnect "$@" ;; + -h|--help|help|*) shift; desktopctl.bt.usage ;; + esac +} +# }}}}} +# Colour picker {{{{{ +desktopctl.colour_picker.usage () { +cat < /tmp/config + $DIFFTOOL "/tmp/config" "$config" +} + +desktopctl.config_diff () { + if [[ ! -r $1 ]] + then + echo "desktopctl: Error: Cannot read file '$1'" + desktopctl.config_diff.usage + exit 1 + else + desktopctl.config_diff.diff "$1" + fi +} +# }}}}} +# CPU {{{{{ + +desktopctl.cpu.usage () { +cat </' in human readable form + +Data as reported by: '/sys/devices/system/cpu/cpu*/cpufreq/scaling_cur_freq' + +Arguments: + -a, --avg, avg, Only print average CPU clock speed over all cores + -m, --max, max, Only print maximum CPU clock speed over all cores + -r, --raw, raw, Do not make human readable + + -h, --help, help, Display this help +EOF +} + +desktopctl.cpu.avg () { + cat /sys/devices/system/cpu/cpu*/cpufreq/scaling_cur_freq | awk '{total+=$1;n+=1} END {printf "%.0f\n", total/n}' +} + +desktopctl.cpu.max () { + cat /sys/devices/system/cpu/cpu*/cpufreq/scaling_cur_freq | sort -n | tail -1 +} + +desktopctl.cpu () { + case "$1" in + -h|--help|help) desktopctl.cpu.usage ;; + -r|--raw|raw) echo "$(desktopctl.cpu.avg)/$(desktopctl.cpu.max)" ;; + -a|--avg|avg) desktopctl.cpu.avg | numfmt --from-unit=1024 --to=iec --suffix=Hz ;; + -m|--max|max) desktopctl.cpu.max | numfmt --from-unit=1024 --to=iec --suffix=Hz ;; + '') echo "$(desktopctl.cpu.avg | numfmt --from-unit=1024 --to=iec --suffix=Hz)/$(desktopctl.cpu.max | numfmt --from-unit=1024 --to=iec --suffix=Hz)" ;; + *) desktopctl.cpu.usage ;; + esac +} +# }}}}}} +# Headphone indicator {{{{{ +desktopctl.headphones.usage () { +cat <&1 | grep -o 'Duration: [^,]*' + done > "$TMP" + + TOTAL_HOURS=$(awk '{print $3}' "$TMP" | to_dec | paste -sd+ | bc -l) + watched=0 + n=0 + + while read -r title _ duration + do + duration=$(to_dec <<< "$duration") + watched=$(bcr "$watched + $duration") + remaining=$(bcr "$TOTAL_HOURS - $watched") + percentage=$(bcr "$watched/$TOTAL_HOURS*100") + + duration=$(to_hrs <<< "$duration") + remaining=$(to_hrs <<< "$remaining") + ((n++)) + + echo "$n $title $duration $(to_hrs <<< "$watched") $percentage $remaining" + done < "$TMP" | column -t -o" | " -N "Item, Title, Duration, Watched, % Watched, Remaining" + + rm "$TMP" +} + +desktopctl.mediawatchtime () { + case "$1" in + ''|-h|--help|help) desktopctl.mediawatchtime.usage "$@" ;; + *) desktopctl.mediawatchtime.list "$@" ;; + esac +} +# }}}}} +# Memory {{{{{ +desktopctl.memory.usage () { +cat < -1)) + do + # https://unix.stackexchange.com/a/190610 + if nc -zw1 google.com 443 2>/dev/null \ + && awk '$1 " " $2 == "SSL handshake" {getline; exit $2 != "OK"}' \ + <(echo | openssl s_client -connect "google.com:443" 2>&1) + then + [[ $quiet == false ]] && echo "desktopctl.online: Connection was succesful!" + return 0 + fi + if (( time > 0 )) + then + sleep 1 + fi + time=$((time-1)) + done + [[ $quiet == false ]] && echo "desktopctl.online: Could not connect to the internet." + return 1 +} + +desktopctl.online () { + case "$1" in + ''|-q|--quiet|-t|--timeout) desktopctl.online.check "$@" ;; + *) desktopctl.online.usage "$@" ;; + esac +} +# }}}}} +# Power Management (Dell) {{{{{ +desktopctl.power.usage () { +cat < /dev/null 2>&1 +} +desktopctl.power.quiet () { + echo "desktopctl: Setting dell thermal profile to quiet..." + sudo cctk --ThermalManagement=Quiet > /dev/null 2>&1 +} +desktopctl.power.ultraperformance () { + echo "desktopctl: Setting dell thermal profile to UltraPerformance..." + sudo cctk --ThermalManagement=UltraPerformance > /dev/null 2>&1 +} +desktopctl.power.express () { + echo "desktopctl: Setting dell charging profile to express..." + sudo cctk --PrimaryBattChargeCfg=Express > /dev/null 2>&1 +} +desktopctl.power.ac () { + echo "desktopctl: Setting dell charging profile to AC..." + sudo cctk --PrimaryBattChargeCfg=AC > /dev/null 2>&1 +} + +desktopctl.tlp.start () { + echo "desktopctl: Starting TLP in automatic mode..." + sudo tlp start > /dev/null 2>&1 +} +desktopctl.tlp.ac () { + echo "desktopctl: Starting TLP in AC mode..." + sudo tlp ac > /dev/null 2>&1 +} +desktopctl.tlp.bat () { + echo "desktopctl: Starting TLP in bat mode..." + sudo tlp bat > /dev/null 2>&1 +} + +desktopctl.governor.powersave () { + echo "desktopctl: Setting CPU governor to powersave..." + sudo cpupower frequency-set --governor powersave > /dev/null 2>&1 +} +desktopctl.governor.performance () { + echo "desktopctl: Setting CPU governor to performance..." + sudo cpupower frequency-set --governor performance > /dev/null 2>&1 +} + +desktopctl.frequency.set () { + hr=$(bc -l <<< "scale=1;$1/1000000")GHz + echo "desktopctl: Setting CPU frequency to $hr..." + sudo cpupower frequency-set --max "$1" > /dev/null 2>&1 +} + +desktopctl.power.desktop () { + desktopctl.tlp.ac + desktopctl.power.ultraperformance + desktopctl.governor.performance + desktopctl.frequency.set 4800000 +} + +desktopctl.power.laptop () { + desktopctl.tlp.bat + desktopctl.power.quiet + desktopctl.governor.powersave + desktopctl.frequency.set 2000000 +} + +desktopctl.power.low_power () { + desktopctl.tlp.bat + desktopctl.power.quiet + desktopctl.governor.powersave + desktopctl.frequency.set 1000000 +} + +desktopctl.power.auto () { + desktopctl.tlp.start + if [[ $(cat /sys/class/power_supply/AC/online) == 1 ]] + then + dunstify --replace=2393 --urgency=low "Being charged" + desktopctl.power.ultraperformance + desktopctl.governor.performance + desktopctl.frequency.set 4800000 + else + dunstify --replace=2393 --urgency=low "On battery" + desktopctl.power.quiet + desktopctl.governor.powersave + desktopctl.frequency.set 2000000 + fi +} + +desktopctl.power () { + while true + do + case $1 + in + ac) shift; desktopctl.power.ac "$@" ;; + auto) shift; desktopctl.power.auto "$@" ;; + cool) shift; desktopctl.power.cool "$@" ;; + desktop) shift; desktopctl.power.desktop "$@" ;; + express) shift; desktopctl.power.low_power; desktopctl.power.express "$@" ;; + laptop) shift; desktopctl.power.laptop "$@" ;; + low|low-power) shift; desktopctl.power.low_power "$@" ;; + quiet) shift; desktopctl.power.quiet "$@" ;; + '') break ;; + *) shift; desktopctl.power.usage "$@" ; break ;; + esac + done +} +# }}}}} +# Reboot required (Archlinux Only) {{{{{ +desktopctl.reboot-required.usage () { +cat < Running kernel: $running_kernel" + echo + else + echo "Running kernel is up to date" + fi + + if [[ $1 == --d ]] || [[ $1 == --detailed ]] + then + declare -A affected_executables + while read -r executable vlibrary + do + affected_executables[$vlibrary]+=" $executable" + done < <(sudo lsof -n +c 0 2>/dev/null | awk '/DEL.*\/usr\/lib/ {print $1 " " $NF}' | sort -u) + + if (( ${#affected_executables[@]} > 0 )) + then + echo -e "\e[1m:: Out of date libraries\e[0m\n" + { + echo "Library| Affected executables" + echo "-------| --------------------" + for vlibrary in "${!affected_executables[@]}" + do + library=$(basename "$vlibrary" | sed 's/.so.*$/.so/') + executables=${affected_executables[$vlibrary]} + executables=${executables## } + echo "$library| $executables" + done | sort -k2 -t\| + } | column -c 80 -Lto ' | ' -s \| -c 80 | sed 's/^/ /' + echo + else + echo "No libraries out of date" + fi + else + executables=$(sudo lsof -n +c 0 2>/dev/null | awk '/DEL.*\/usr\/lib/ {print $1}' | sort -u) + if [[ -n $executables ]] + then + echo -e "\e[1m:: Out of date programs\e[0m\n" + sed 's/^/ /' <<< "$executables" + else + echo "No programs out of date" + fi + fi + echo -n "Uptime: " + uptime -p +} + +desktopctl.reboot-required () { + case "$1" in + ''|-d|--detailed) desktopctl.reboot-required.check "$@" ;; + *) desktopctl.reboot-required.usage "$@" ;; + esac +} +# }}}}} +# Screenshot {{{{{ +desktopctl.screenshot.usage () { +cat < /dev/null + then + dunstify --replace=1 --urgency=low "$(desktopctl.sound.volume)" + else + notify-send "$(desktopctl.sound.volume)" + fi +} + +desktopctl.sound._volume () { + pactl --format json list | jq -r ".sinks[] | select(.name == \"$(pactl get-default-sink)\") | .volume[] | .value_percent" | head -1 +} + +desktopctl.sound._equalise_volume () { + pactl set-sink-volume @DEFAULT_SINK@ "$(desktopctl.sound._volume)" +} + +desktopctl.sound.volume () { + if [[ $1 == --status ]] + then + if ! desktopctl.sound.ismuted + then + desktopctl.sound._volume + fi + else + if desktopctl.sound.ismuted + then + echo "Muted" + else + echo "Volume: $(desktopctl.sound._volume)" + fi + fi +} + +desktopctl.sound.sink () { + ALSA_DEFAULT_SINK=$(pactl get-default-sink) + DEFAULT_SINK=$(pactl list sinks | awk -v DefaultSink="$ALSA_DEFAULT_SINK" '$0 ~ DefaultSink {getline; print}' | awk -F'\\s*:\\s*' '/Description/ {print $2}') + + BT_ID=$(sed 's/.*bluez_output.\(..\)_\(..\)_\(..\)_\(..\)_\(..\)_\(..\)\..*/\1:\2:\3:\4:\5:\6/' <<< "$ALSA_DEFAULT_SINK") + + if ! [[ "$BT_ID" == "" ]] && [[ $(systemctl is-active bluetooth) == active ]] + then + DEFAULT_SINK="$DEFAULT_SINK ($(bluetoothctl info "$BT_ID" | awk -F: '/Battery Percentage/ {print $2}' | sed 's/.*(\(.*\))$/\1/')%)" + fi + + # Requires pulsemixer dependency + # DEFAULT_SINK=$(pulsemixer --list-sinks | sed -n 's/.*Name: \([^, ]*\).*Default/\1/p') + + if [[ $DEFAULT_SINK =~ (Built-in *|Tiger Lake-LP) ]] + then + # Need to print some output + echo + else + echo "$DEFAULT_SINK" + fi +} + +desktopctl.sound.daemon () { + # Automatically mute sound if nothing is playing for a while + + if (($(pgrep -u "$UID" -cxf '^.*/de sound --daemon.*') > 1)) + then + desktopctl.log "sound: Daemon already running." + exit 1 + fi + + NOT_CONNECTED=0 + THRESHOLD=10 + NOTIFIED=false + + while true + do + if ! desktopctl.sound.ismuted && (($(grep -c 'Corked: no' < <(pactl list sink-inputs)) == 0)) + then + ((NOT_CONNECTED++)) + + if ((NOT_CONNECTED > "$THRESHOLD")) + then + desktopctl.log "sound: Muting sound" + NOT_CONNECTED=0 + desktopctl.sound mute + fi + NOTIFIED=false + else + NOT_CONNECTED=0 + NOTIFIED=true + fi + + sleep 60 + done & + exit +} + +desktopctl.sound () { + case $1 in + "+") + if ! desktopctl.sound.ismuted || [[ $(desktopctl.sound.volume) = "Volume: 0%" ]] + then + if (($(desktopctl.sound._volume | sed 's/%//') > 90)) + then + pactl set-sink-volume @DEFAULT_SINK@ 100% + else + pactl set-sink-volume @DEFAULT_SINK@ +5% + fi + fi + + desktopctl.sound.unmute + desktopctl.sound.volume.notify + ;; + "-") + if [[ $(desktopctl.sound.volume) = "Volume: 5%" ]] + then + desktopctl.sound.mute + fi + + pactl set-sink-volume @DEFAULT_SINK@ -5% + desktopctl.sound.volume.notify + ;; + "toggle") + pactl set-sink-mute @DEFAULT_SINK@ toggle + desktopctl.sound.volume.notify + ;; + "mute") + pactl set-sink-mute @DEFAULT_SINK@ true + desktopctl.sound.volume.notify + ;; + "volume"|"get-volume"|"get") + shift + desktopctl.sound.volume "$@" + ;; + "sink") + desktopctl.sound.sink + ;; + "ismuted") + if desktopctl.sound.ismuted + then + echo "True" + else + echo "False" + exit 2 + fi + ;; + "playing") + if grep -q playing$ <(playerctl metadata -af '{{lc(status)}}' 2>/dev/null) + then + echo "" + elif grep -q paused$ <(playerctl metadata -af '{{lc(status)}}' 2>/dev/null) + then + echo "" + fi + ;; + "--daemon") + shift + desktopctl.sound.daemon + ;; + "notify") + desktopctl.sound.volume.notify + ;; + *) + desktopctl.sound.usage + exit 1 + ;; + esac +} +# }}}}} +# Speedtest {{{{{ +desktopctl.speedtest.usage () { +cat < /dev/null 2>&1 + then + curl --silent http://eu-central-1.linodeobjects.com/speedtest/100MB-speedtest \ + | pv --bits --wait --format 'Curr: %r Avg: %a' > /dev/null + else + curl --silent http://eu-central-1.linodeobjects.com/speedtest/100MB-speedtest \ + | pv --wait --format 'Curr: %r Avg: %a' > /dev/null + fi +} + +desktopctl.speedtest () { + case "$1" in + '') desktopctl.speedtest.test "$@" ;; + *) desktopctl.speedtest.usage "$@" ;; + esac +} +# }}}}} +# SSH devices {{{{{ +desktopctl.ssh_devices.usage () { +cat </dev/null +} + +desktopctl.timezone.update () { + tz=$(desktopctl.timezone) + if [[ -n $tz ]] + then + timedatectl set-timezone "$tz" + fi +} + +desktopctl.timezone () { + case "$1" in + '') desktopctl.timezone.get "$@" ;; + update) shift; desktopctl.timezone.update "$@" ;; + *) desktopctl.timezone.usage "$@" ;; + esac +} +# }}}}} +# Wifi {{{{{ +desktopctl.wifi.usage () { +cat <100?100:a;print int(100-(a-40)/60*100)}' + ;; + nm) + nmcli -t -f IN-USE,SIGNAL device wifi | awk -F: '/*/ {print $2}' | head -1 + ;; + generic) + # Link quality is apparently maximal at 70 + awk '$1 ~ /wlan0/ {gsub("\\.", "", $3); printf "%.0f\n", $3/70*100}' /proc/net/wireless + ;; + esac +} + +desktopctl.wifi () { + info=$(desktopctl.wifi.info) + ssid=$(desktopctl.wifi.ssid) + + _ssid=$ssid + + for KNOWN_NETWORK in "${KNOWN_NETWORKS[@]}" + do + echo -n + if [[ "$KNOWN_NETWORK" == "$ssid" ]] + then + _ssid="" + fi + done + + strength=$(desktopctl.wifi.strength) + + if [[ "$strength" == "" ]] + then + WIFI_ICON_ON=$WIFI_ICON_DISCONNECTED + elif ((strength < 26)) + then + WIFI_ICON_ON=$WIFI_ICON_LOW_STRENGTH + elif ((strength < 50)) + then + WIFI_ICON_ON=$WIFI_ICON_MED_STRENGTH + else + WIFI_ICON_ON=$WIFI_ICON_HIG_STRENGTH + fi + + case "$1" in + ssid) + echo "$ssid" + ;; + icons) + case "$info" in + off) echo "$WIFI_ICON_OFF" ;; + disconnected) echo "$WIFI_ICON_DISCONNECTED" ;; + configuring) echo "$WIFI_ICON_CONFIGURING $ssid" ;; + connected) echo "$WIFI_ICON_ON $ssid" ;; + esac + ;; + only-icons) + case "$info" in + off) echo "$WIFI_ICON_OFF" ;; + disconnected) echo "$WIFI_ICON_DISCONNECTED" ;; + configuring) echo "$ssid $WIFI_ICON_CONFIGURING" ;; + connected) echo "$_ssid $WIFI_ICON_ON" ;; + esac + ;; + off) + wifi off ;; + on) + wifi on ;; + -h|--help|help) + desktopctl.wifi.usage "$@" ;; + *) + case "$info" in + off) echo "Off" ;; + disconnected) echo "Disconnected" ;; + configuring) echo "Configuring: $ssid" ;; + connected) echo "Connected: $ssid" ;; + esac + ;; + esac +} +# }}}}} +# Xwayland windows (sway only) {{{{{ +desktopctl.xwayland.usage () { +cat < /dev/null + then + xwayland_clients=$(swaymsg -t get_tree | grep -c xwayland) + else + echo "swaymsg not running..." + exit 2 + fi + + if [[ $1 == -v ]] + then + shift + verbose=true + fi + + if [[ -z $1 ]] + then + fmt="Active xwayland clients: %n" + else + fmt=$1 + fi + + if ((xwayland_clients > 0)) || [[ $verbose == true ]] + then + echo "${fmt/\%n/$xwayland_clients}" + fi +} + +desktopctl.xwayland () { + case "$1" in + -h|--help|help) desktopctl.xwayland.usage "$@" ;; + *) desktopctl.xwayland.count "$@" ;; + esac +} +# }}}}} + +# Help {{{{{ +desktopctl.help () { + echo "desktopctl: available commands" + echo + { + echo "auth - Show session authentication status" + echo "backlight - Get and set backlight" + echo "battery - Get battery status (Percentage, Current consumption, etc)" + echo "bt|bluetooth - Get and set Bluetooth" + echo "color|colour - Colour picker" + echo "config_diff - Show configuration file differences (Archlinux only)" + echo "cpu - Get CPU frequency" + echo "headphones - Get whether headpones are plugged in" + echo "ip - Get local IP address" + echo "mediawatchtime - Print total media time of listed files" + echo "memory - Get memory usage" + echo "mondir - Monitor directories/files" + echo "online - Get current internet connection status" + echo "power - Set power and charging profiles (Dell only)" + echo "reboot-required - List programs that need to be restarted (Archlinux only)" + echo "screenshot - Take screenshots" + echo "sound - Get and set sound" + echo "speedtest - Show current internet speed" + echo "ssh - List ssh clients on local network" + echo "timezone|gettz - Get current timezone" + echo "wifi - Set wifi on/off" + echo "xwayland - List number of xwayland clients (sway only)" + } | column -xc80 + echo + echo "Configuration file is at ~/.config/desktopctl/desktopctlrc" +} +# }}}}} +# Longhelp {{{{{ +desktopctl.longhelp () { + { + desktopctl.help + echo -e '\n================================================================================' + echo "Individual help pages" + echo -e '================================================================================\n' + + echo -e '--------------------------------------------------------------------------------\n' + desktopctl.session_auth.usage + echo -e '\n--------------------------------------------------------------------------------\n' + desktopctl.backlight.usage + echo -e '\n--------------------------------------------------------------------------------\n' + desktopctl.battery.usage + echo -e '\n--------------------------------------------------------------------------------\n' + desktopctl.bt.usage + echo -e '\n--------------------------------------------------------------------------------\n' + desktopctl.cpu.usage + echo -e '\n--------------------------------------------------------------------------------\n' + desktopctl.colour_picker.usage + echo -e '\n--------------------------------------------------------------------------------\n' + desktopctl.config_diff.usage + echo -e '\n--------------------------------------------------------------------------------\n' + desktopctl.headphones.usage + echo -e '\n--------------------------------------------------------------------------------\n' + desktopctl.online.usage + echo -e '\n--------------------------------------------------------------------------------\n' + desktopctl.local_ip.usage + echo -e '\n--------------------------------------------------------------------------------\n' + desktopctl.mediawatchtime.usage + echo -e '\n--------------------------------------------------------------------------------\n' + desktopctl.memory.usage + echo -e '\n--------------------------------------------------------------------------------\n' + desktopctl.mondir.usage + echo -e '\n--------------------------------------------------------------------------------\n' + desktopctl.power.usage + echo -e '\n--------------------------------------------------------------------------------\n' + desktopctl.reboot-required.usage + echo -e '\n--------------------------------------------------------------------------------\n' + desktopctl.screenshot.usage + echo -e '\n--------------------------------------------------------------------------------\n' + desktopctl.sound.usage + echo -e '\n--------------------------------------------------------------------------------\n' + desktopctl.speedtest.usage + echo -e '\n--------------------------------------------------------------------------------\n' + desktopctl.ssh_devices.usage + echo -e '\n--------------------------------------------------------------------------------\n' + desktopctl.timezone.usage + echo -e '\n--------------------------------------------------------------------------------\n' + desktopctl.wifi.usage + echo -e '\n--------------------------------------------------------------------------------\n' + desktopctl.xwayland.usage + + } | less +} +# }}}}} + +case $1 in + auth) shift; desktopctl.session_auth "$@" ;; + backlight) shift; desktopctl.backlight "$@" ;; + battery) shift; desktopctl.battery "$@" ;; + bt|bluetooth) shift; desktopctl.bt "$@" ;; + color|colour) shift; desktopctl.colour_picker "$@" ;; + config_diff) shift; desktopctl.config_diff "$@" ;; + cpu) shift; desktopctl.cpu "$@" ;; + headphones) shift; desktopctl.headphones "$@" ;; + ip) shift; desktopctl.local_ip "$@" ;; + mediawatchtime) shift; desktopctl.mediawatchtime "$@" ;; + memory) shift; desktopctl.memory "$@" ;; + mondir) shift; desktopctl.mondir "$@" ;; + online) shift; desktopctl.online "$@" ;; + power) shift; desktopctl.power "$@" ;; + reboot-required) shift; desktopctl.reboot-required "$@" ;; + screenshot) shift; desktopctl.screenshot "$@" ;; + sound) shift; desktopctl.sound "$@" ;; + speedtest) shift; desktopctl.speedtest "$@" ;; + ssh) shift; desktopctl.ssh_devices "$@" ;; + timezone|gettz) shift; desktopctl.timezone "$@" ;; + wifi) shift; desktopctl.wifi "$@" ;; + xwayland) shift; desktopctl.xwayland "$@" ;; + + help) shift; desktopctl.longhelp "$@" ;; + *) echo "desktopctl: '$*' is not a valid command"; desktopctl.help "$@" ;; +esac + -- cgit v1.2.3-89-gcfd4