commit 7b2650b64615517cc37952a59e31540a82150216 Author: Marcin Woźniak Date: Mon Sep 14 11:00:29 2020 +0200 Added Signed-off-by: Marcin Woźniak diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..1362d41 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,57 @@ +Contributing +============ + +All kinds of contributions to Mons are greatly appreciated. For someone +unfamiliar with the code base, the most efficient way to contribute is usually +to submit a [feature request](#feature-requests) or [bug report](#bug-reports). +If you want to dive into the source code, you can submit a [patch](#patches) as +well, either working on your own ideas or [existing issues][issues]. + +Feature Requests +---------------- + +Do you have an idea for an awesome new feature for Mons? Please [submit a +feature request][issue]. It's great to hear about new ideas. + +If you are inclined to do so, you're welcome to [fork][fork] Mons, work on +implementing the feature yourself, and submit a patch. In this case, it's +*highly recommended* that you first [open an issue][issue] describing your +enhancement to get early feedback on the new feature that you are implementing. +This will help avoid wasted efforts and ensure that your work is incorporated +into the code base. + +Bug Reports +----------- + +Did something go wrong with Mons? Sorry about that! Bug reports are greatly +appreciated! + +When you [submit a bug report][issue], please include relevant information such +as Mons version, operating system, configuration file, error messages, and +steps to reproduce the bug. The more details you can include, the easier it is +to find and fix the bug. + +Patches +------- + +If there are [open issues][issues], you're more than welcome to work on those - +this is probably the best way to contribute to Mons. If you have your own +ideas, that's great too! In that case, before working on substantial changes to +the code base, it is *highly recommended* that you first [open an issue][issue] +describing what you intend to work on. + +**Patches are generally submitted as pull requests.** Patches are also +[accepted over email][email]. + +The version history should be clean, and commit messages should be descriptive +and [properly formatted][commit-messages]. + +--- + +If you have any questions about anything, feel free to [ask][email]! + +[issue]: https://github.com/ventto/mons/issues/new +[issues]: https://github.com/ventto/mons/issues +[fork]: https://github.com/ventto/mons/fork +[email]: mailto:thomas.venries@gmail.com +[commit-messages]: http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..4b2486c --- /dev/null +++ b/LICENSE @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2016-2017 Thomas "Ventto" Venriès + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..b9ce84d --- /dev/null +++ b/Makefile @@ -0,0 +1,37 @@ +PKGNAME = mons +PKGDESC = POSIX Shell script to quickly manage 2-monitors display. + +LICENSEDIR = $(DESTDIR)/usr/share/licenses/$(PKGNAME) +MANDIR = $(DESTDIR)/usr/share/man/man1 +BINDIR = $(DESTDIR)/usr/bin +LIBDIR = $(DESTDIR)/usr/lib/libshlist +LIB = libshlist/liblist.sh + +install: + @if ! [ -r "$(LIB)" ]; then \ + echo "$(LIB): missing file"; \ + exit 1; \ + fi + help2man -N -n "$(PKGDESC)" -h -h -v -v ./$(PKGNAME) | gzip - > $(PKGNAME).1.gz + @if ! [ -r "$(PKGNAME).1.gz" ]; then \ + echo "$(PKGNAME).1.gz: missing manpage"; \ + exit 1; \ + fi + mkdir -p $(MANDIR) + mkdir -p $(LICENSEDIR) + mkdir -p $(LIBDIR) + mkdir -p $(BINDIR) + chmod 644 $(PKGNAME).1.gz + chmod 644 LICENSE + chmod 644 $(LIB) + chmod 755 mons + cp $(PKGNAME).1.gz $(MANDIR)/$(PKGNAME).1.gz + cp LICENSE $(LICENSEDIR)/LICENSE + cp $(LIB) $(LIBDIR)/liblist.sh + cp mons $(BINDIR)/mons + +uninstall: + $(RM) -r $(LIBDIR) + $(RM) $(BINDIR)/mons + +.PHONY: install uninstall diff --git a/README.md b/README.md new file mode 100644 index 0000000..821b756 --- /dev/null +++ b/README.md @@ -0,0 +1,200 @@ +Mons +=================== +[![License](https://img.shields.io/badge/license-MIT-blue.svg?style=flat)](https://github.com/Ventto/mons/blob/master/LICENSE) +[![Language (XRandR)](https://img.shields.io/badge/powered_by-XRandR-brightgreen.svg)](https://www.x.org/archive/X11R7.5/doc/man/man1/xrandr.1.html) +[![Vote for mons](https://img.shields.io/badge/AUR-Vote_for-yellow.svg)](https://aur.archlinux.org/packages/mons/) + + + +*"Mons is a Shell script to quickly manage 2-monitors display using xrandr."* + +## Perks + +* [x] **No requirement**: POSIX-compliant (minimal: *xorg-xrandr*) +* [x] **Useful**: Perfectly fit for laptops, quick and daily use +* [x] **Well known**: Laptop mode, projector mode, duplicate, mirror and extend +* [x] **More**: Select one or two monitors over several others +* [x] **Extra**: Cycle through every mode with only one shortcut +* [x] **Auto**: Deamon mode to automatically reset display + +# Installation + +* Package (AUR) + +``` +$ pacaur -S mons +``` + +* Manual + +``` +$ git clone --recursive https://github.com/Ventto/mons.git +$ cd mons +$ sudo make install +``` +> Note: `--recursive` is needed for git submodule + +# Usage + +``` +Without argument, it prints connected monitors list with their names and ids. +Options are exclusive and can be used in conjunction with extra options. + +Information: + -h Prints this help and exits. + -v Prints version and exits. + +Two monitors: + -o Primary monitor only. + -s Second monitor only. + -d Duplicates the primary monitor. + -m Mirrors the primary monitor. + -e + Extends the primary monitor to the selected side + [ top | left | right | bottom ]. + -n + This mode selects the previous ones, one after another. The argument + sets the side for the extend mode. + +More monitors: + -O + Only enables the monitor with a specified id. + -S ,: + Only enables two monitors with specified ids. The specified position + places the second monitor on the right (R) or at the top (T). + +Extra (in-conjunction options): + --dpi + Set the DPI, a strictly positive value within the range [0 ; 27432]. + --primary + Select a connected monitor as the primary output. Run the script + without argument to print monitors information, the names are in the + second column between ids and status. The primary monitor is marked + by an asterisk. + +Daemon mode: + -a Performs an automatic display if it detects only one monitor. +``` + +# Examples + +## Two monitors + +Displays monitor list: + +``` +$ mons +0: LVDS-1 (enabled) +5: VGA-1 +``` + +You have an enabled one, you want to extends the second one on the right: + +``` +$ mons -e right +``` + +You want to only display the second one: + +``` +$ mons -s +``` + +With the `-n` option, go through every 2-mons mode consecutively: + +1. Primary monitor only +1. Second monitor only +1. Extend mode whose the side is set with `-n ` +1. Mirror +1. Duplicate + +This mode is useful if you want to switch to every mode with only one shortcut. + +![alt 2-monitors modes](img/raw-body.png) + +``` +# Now in 'Second monitor mode' +$ mons -n right # -> 'Extend mode' +# Now in 'Extend mode' +$ mons -n right # -> 'Mirror mode' +``` + +## Three monitors (selection mode) + + +Displays monitor list: + +``` +$ mons +Monitors: 3 +Mode: Selection +0:* LVDS-1 (enabled) +1: DP-1 (enabled) +5: VGA-1 +``` + +You may need to display only the third one: + +``` +$ mons -O 5 +``` + +You may need to display the first and the third one on the right: + +``` +$ mons -S 0,5:R +``` + +Like above but you want to inverse the placement: + +``` +$ mons -S 5,0:R +``` + +## DPI value + +You might want to switch mode and set the DPI value. +Use the `--dpi ` option in conjunction with all others options. + +``` +$ mons [OPTIONS] --dpi +``` + +## Primary monitor + +You might choose one of your monitors as the main one. +You can use the `--primary ` option alone or in conjunction with all +others options. +`` refers to the monitor name that appears in the list of connected +monitors (ex: `LVDS-1` or `VGA-1`): + +``` +$ mons +Monitors: 3 +Mode: Primary +0:* LVDS-1 (enabled) +5: VGA-1 +``` + +The '*' character means that the monitor is the primary one: + +``` +$ mons --primary VGA-1 +Monitors: 3 +Mode: Primary +0: LVDS-1 (enabled) +5:* VGA-1 +``` + +## Daemon mode + +Especially for laptops, after unplugging the additional monitors, it might be +convenient to reset automatically the display for the remaining one. + +Run *mons* in background as follow: + +``` +$ nohup mons -a > /dev/null 2>&1 & (all shells) +$ mons -a &! (zsh) +$ mons -a &; disown (bash) +``` diff --git a/_gitignore b/_gitignore new file mode 100644 index 0000000..3c532da --- /dev/null +++ b/_gitignore @@ -0,0 +1,19 @@ +/* + +!libshlist + +!img +img/* +!img/raw-body.png + +!test +test/* +!test/*.sh + +!mons +!CONTRIBUTING.md +!LICENSE +!Makefile +!README.md +!.gitignore +!.gitmodules diff --git a/_gitmodules b/_gitmodules new file mode 100644 index 0000000..136cc85 --- /dev/null +++ b/_gitmodules @@ -0,0 +1,3 @@ +[submodule "libshlist"] + path = libshlist + url = https://github.com/Ventto/libshlist.git diff --git a/img/raw-body.png b/img/raw-body.png new file mode 100644 index 0000000..0dfbb8d Binary files /dev/null and b/img/raw-body.png differ diff --git a/libshlist/LICENSE b/libshlist/LICENSE new file mode 100644 index 0000000..335387b --- /dev/null +++ b/libshlist/LICENSE @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2017-2018 Thomas "Ventto" Venriès + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/libshlist/README.md b/libshlist/README.md new file mode 100644 index 0000000..dd7f70a --- /dev/null +++ b/libshlist/README.md @@ -0,0 +1,111 @@ +POSIX Shell List +================ + +[![License](https://img.shields.io/badge/license-MIT-blue.svg?style=flat)](https://github.com/Ventto/posix-shell-list/blob/master/LICENSE) + +*"This is a POSIX Shell list implementation."* + +## Perks + +* [x] **No requirement**: POSIX-compliant +* [x] **Lightweight**: ~100 lines +* [x] **Extra**: Additional functions to fit your needs +* [x] **Useful**: No internal independency +* [x] **Both**: Safe and unsafe (passing argument by reference) versions + +# Installation + +* Download it: + +```bash +$ wget https://raw.githubusercontent.com/Ventto/posix-shell-list/master/liblist.sh +``` + +* Source the library for including the functions into your Shell script: + +```bash +. liblist.sh (or) +. liblist_unsafe.sh +``` + +* Each function is independent. So you can copy some functions into your +script without sourcing + +# Functions + +The whole function documentation is in the following library scripts: + +* `liblist.sh`: the variable assignation is required for conserving changes +* `liblist_unsafe.sh`: uses `eval` special shell builtin for passing argument + by reference and set the list variable + +The following list enumerates all available functions: + +``` + SAFE | UNSAFE +____________________________________|_______________________________ +list ... | list ... +list_back | list_back +list_contains | list_contains +list_count | list_count +list_empty | list_empty +list_erase | list_erase +list_erase_from | list_erase_from +list_erase_range | list_erase_range +list_eraseat | list_eraseat +list_extract | list_extract +list_front | list_front +list_get | list_get +list_indexof | list_indexof +list_insert | list_insert +list_maps | list_maps +list_pop_back | list_pop_back +list_pop_front | list_pop_front +list_push_back | list_push_back +list_push_front | list_push_front +list_remove | list_remove +list_replace | list_replace +list_reverse | list_reverse +list_set | list_set +list_size | list_size +list_sort | list_sort +list_sort_reverse | list_sort_reverse +``` + +# Example + +Scripts in `test/` offer an exhaustive usage of both libraries. + +* Quick start (safe version): + +```bash +lst="$(list 'A' 'B' 'C')" +lst="$(list_sort "$lst")" # { C, B, A } + +if list_empty "$lst"; then + echo 'The list is empty.' +fi + +index="$(list_indexof 'D' "$lst")" # '', empty string + +if [ "$?" -ne 0 ]; then + echo 'Element not found.' +fi +``` + +* Quick start (unsafe version): + +```bash +lst="$(list 'A' 'B' 'C')" +list_sort lst # { C, B, A } + +if list_empty lst; then + echo 'The list is empty.' +fi + +index="$(list_indexof lst 'D')" # '', empty string + +if [ "$?" -ne 0 ]; then + echo 'Element not found.' +fi +``` diff --git a/libshlist/_git b/libshlist/_git new file mode 100644 index 0000000..6e67b0c --- /dev/null +++ b/libshlist/_git @@ -0,0 +1 @@ +gitdir: ../.git/modules/libshlist diff --git a/libshlist/liblist.sh b/libshlist/liblist.sh new file mode 100644 index 0000000..d3a2144 --- /dev/null +++ b/libshlist/liblist.sh @@ -0,0 +1,271 @@ +#!/bin/sh +# +# The MIT License (MIT) +# +# Copyright (c) 2017-2018 Thomas "Ventto" Venriès +# +# Permission is hereby granted, free of charge, to any person obtaining a copy of +# this software and associated documentation files (the "Software"), to deal in +# the Software without restriction, including without limitation the rights to +# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +# the Software, and to permit persons to whom the Software is furnished to do so, +# subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +# FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +# IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +## +# @brief Return a list from argument strings +# @usage list ... +# @print The created list +# +list () { + for e; do [ -n "$e" ] && echo "$e"; done +} + +## +# @brief Prints the number of elements in the list +# @usage list_size +# @print The size of the list as positive integer +# +list_size () { + if [ -z "$1" ]; then echo '0'; else echo "$@" | wc -l; fi +} + +## +# @brief Returns whether the list is empty(1) or not(0) +# @usage list_empty +# @return The exit code of the command +# +list_empty () { + test -z "$1" +} + +## +# @brief Adds a new element at the beginning of the list +# @usage list_push_front +# @print The list result +# +list_push_front () { + test "$#" -ne 2 && return 1 + if [ "$2" = '' ]; then echo "$1"; else printf '%s\n%s' "$1" "$2"; fi +} + +## +# @brief Adds a new element at the end of the list +# @usage list_push_back +# @print The list result +# +list_push_back () { + test "$#" -ne 2 && return 1 + if [ "$2" = '' ]; then echo "$1"; else printf '%s\n%s' "$2" "$1"; fi +} + +## +# @brief Inserts new elements in the list before a specified position +# @usage list_insert +# @print The list result +# +list_insert () { + test "$#" -ne 3 && return 1 + i="$2"; [ "$i" != '$' ] && i=$((i+1)); echo "$3" | sed "${i}i${1}" +} + +## +# @brief Modifies an element from the list at a specified position +# @usage list_set +# @print The list result +# +list_set () { + test "$#" -ne 3 && return 1 + i="$2"; i=$((i+1)); echo "$3" | sed -e "${i}s/.*/$1/" +} + +## +# @brief Extracts a range of elements from the list between two specified +# positions +# @usage list_extract +# @print The list result +# +list_extract () { + test "$#" -ne 3 && return 1 + i="$1"; j="$2"; i=$((i+1)); j=$((j+1)); echo "$3" | sed -n "${i},${j}p" +} + +## +# @brief Replaces all elements from the list with a specified element +# @usage list_replace +# @print The list result +# +list_replace () { + test "$#" -ne 3 && return 1; echo "$3" | sed -e "s/^$1$/$2/g" +} + +## +# @brief Prints the element at a specified position +# @usage list_get +# @print The element found +# +list_get () { + test "$#" -ne 2 && return 1; i="$1"; i=$((i+1)); echo "$2" | sed -n "${i}p" +} + +## +# @brief Prints the head of the list +# @usage list_front +# @print The element found +# +list_front () { + test "$#" -ne 1 && return 1; echo "$@" | sed -n '1p' +} + +## +# @brief Prints the queue of the list +# @usage list_back +# @print The element found +# +list_back () { + test "$#" -ne 1 && return 1; echo "$@" | sed -n '$p' +} + +## +# @brief Removes the first-hit element from a list +# @usage list_erase +# @print The list result +# +list_erase () { + test "$#" -ne 2 && return 1; echo "$2" | sed -e "0,/^$1$/ s///" -e '/^$/d' +} + +## +# @brief Removes a range of elements from a list between two specified +# positions +# @usage list_erase_range +# @print The list result +# +list_erase_range () { + test "$#" -ne 3 && return 1 + i="$1"; j="$2"; i=$((i+1)); j=$((j+1)); echo "$3" | sed "${i},${j}d" +} + +## +# @brief Removes all elements from a specified position +# @usage list_erase_from +# @print The list result +# +list_erase_from () { + test "$#" -ne 2 && return 1; i="$1"; i=$((i+1)); echo "$2" | sed "${i},\$d" +} + +## +# @brief Removes the element at a specified position +# @usage list_eraseat +# @print The list result +# +list_eraseat () { + test "$#" -ne 2 && return 1; i="$1"; i=$((i+1)); echo "$2" | sed "${i}d" +} + +## +# @brief Removes all the elements from the list, which are equal to given +# element +# @usage list_remove +# @print The list result +# +list_remove () { + test "$#" -ne 2 && return 1; echo "$2" | sed -e "/^$1$/d" +} + +## +# @brief Removes the first element of the list +# @usage list_pop_front +# @print The list result +# +list_pop_front () { + test "$#" -ne 1 && return 1; echo "$1" | sed '1d' +} + +## +# @brief Removes the last element of the list +# @usage list_pop_back +# @print The list result +# +list_pop_back () { + test "$#" -ne 1 && return 1; echo "$1" | sed '$d' +} + +## +# @brief Prints the index of a specified element +# @usage list_indexof +# @print The index or empty string if the element is not found +# +list_indexof () { + test "$#" -ne 2 && return 1; i=0 + for e in $2; do + [ "$e" = "$1" ] && { echo "$i"; return 0; }; i=$((i+1)); + done + return 1 +} + +## +# @brief Returns whether the list contains a specified element(0) or not(1) +# @usage list_contains +# @return 0 or 1 +# +list_contains () { + test "$#" -ne 2 && return 1 + for e in $2; do [ "$e" = "$1" ] && return 0; done; return 1 +} + +## +# @brief Counts the number of a specified element in the list +# @usage list_count +# @print The number of elements as positive integer +# +list_count () { + test "$#" -ne 2 && return 1 + i=0; for e in $2; do [ "$e" = "$1" ] && { i=$((i+1)); }; done; echo "$i" +} + +## +# @brief Maps every element of the list +# @usage list_maps +# @print The list result +# +list_map () { + test "$#" -ne 2 && return 1; for e in $2; do eval "$1 $e"; done +} + +## +# @brief Reverses the list +# @usage list_reverse +# @print The list result +# +list_reverse() { + test "$#" -ne 1 && return 1; echo "$1" | sed '1!x;H;1h;$!d;g' +} + +## +# @brief Sorts the list +# @usage list_sort +# @print The list result +# +list_sort () { + test "$#" -ne 1 && return 1; echo "$1" | sort -n +} + +## +# @brief Sorts and reverses the sense of the list +# @usage list_sort_reverse +# @print The list result +# +list_sort_reverse () { + test "$#" -ne 1 && return 1; echo "$1" | sort -nr +} diff --git a/libshlist/liblist_unsafe.sh b/libshlist/liblist_unsafe.sh new file mode 100644 index 0000000..af2625d --- /dev/null +++ b/libshlist/liblist_unsafe.sh @@ -0,0 +1,286 @@ +#!/bin/sh +# +# The MIT License (MIT) +# +# Copyright (c) 2017-2018 Thomas "Ventto" Venriès +# +# Permission is hereby granted, free of charge, to any person obtaining a copy of +# this software and associated documentation files (the "Software"), to deal in +# the Software without restriction, including without limitation the rights to +# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +# the Software, and to permit persons to whom the Software is furnished to do so, +# subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +# FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +# IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +## +# @brief Prints a list from arguments +# @usage list ... +# @print The list result +# +list () { + for e; do [ -n "$e" ] && echo "$e"; done +} + +## +# @brief Prints the number of elements in the list +# @usage list_size +# @print The size of the list as positive integer +# +list_size () { + test "$#" -ne 1 && { echo "0"; return; } + eval "test -n \"\$$1\"" && { eval "echo \"\$$1\" | wc -l"; return; } + echo '0' +} + +## +# @brief Returns whether the list is empty(1) or not(0) +# @usage list_empty +# @return 0 or 1 +# +list_empty () { + eval "test -z \"\$$1\"" +} + +## +# @brief Adds a new element at the beginning of the list +# @usage list_push_front +# @out Set the variable passed by reference +# +list_push_front () { + test "$#" -ne 2 && return 1 + eval "test -n \"\$$1\"" || { eval "$1='$2'"; return; } + eval "$1=\"\$(printf '%s\n%s' '$2' \"\$$1\")\""; +} + +## +# @brief Adds a new element at the end of the list +# @usage list_push_back +# @out Set the variable passed by reference +# +list_push_back () { + test "$#" -ne 2 && return 1 + eval "test -n \"\$$1\"" || { eval "$1='$2'"; return; } + eval "test -n \"\$$1\"" || { eval "$1='$2'"; return; } + eval "$1=\"\$(printf '%s\n%s' \"\$$1\" '$2')\""; +} + +## +# @brief Inserts new elements in the list before a specified position +# @usage list_insert +# @out Set the variable passed by reference +# +list_insert () { + test "$#" -ne 3 && return 1; i="$3"; [ "$i" != '$' ] && i=$((i+1)) + eval "$1=\"\$(echo \"\$$1\" | sed \"${i}i${2}\")\"" +} + +## +# @brief Modifies an element from the list at a specified position +# @usage list_set +# @out Set the variable passed by reference +# +list_set () { + test "$#" -ne 3 && return 1 + i="$3"; i=$((i+1)); eval "$1=\"\$(echo \"\$$1\" | sed \"${i}s/.*/$2/\")\"" +} + +## +# @brief Extracts a range of elements from the list between two specified +# positions +# @usage list_extract +# @out Set the variable passed by reference +# +list_extract () { + test "$#" -ne 3 && return 1; i="$2"; j="$3"; i=$((i+1)); j=$((j+1)) + eval "$1=\"\$(echo \"\$$1\" | sed -n \"${i},${j}p\")\"" +} + +## +# @brief Replaces all elements from the list with a specified element +# @usage list_replace +# @out Set the variable passed by reference +# +list_replace () { + test "$#" -ne 3 && return 1 + eval "$1=\"\$(echo \"\$$1\" | sed -e \"s/^$2$/$3/g\")\"" +} + +## +# @brief Prints the element at a specified position +# @usage list_get +# @print The element found +# +list_get () { + test "$#" -ne 2 && return 1; i="$2"; i=$((i+1)); + eval "echo \"\$$1\" | sed -n \"${i}p\"" +} + +## +# @brief Prints the head of the list +# @usage list_front +# @print The element found +# +list_front () { + test "$#" -ne 1 && return 1; eval "echo \"\$$1\" | sed -n '1p'" +} + +## +# @brief Prints the queue of the list +# @usage list_back +# @print The element found +# +list_back () { + test "$#" -ne 1 && return 1; eval "echo \"\$$1\" | sed -n '\$p'" +} + +## +# @brief Removes the first-hit element from a list +# @usage list_erase +# @out Set the variable passed by reference +# +list_erase () { + test "$#" -ne 2 && return 1 + eval "$1=\"\$(echo \"\$$1\" | sed -e \"0,/^$2$/ s///\" -e '/^$/d')\"" +} + +## +# @brief Removes a range of elements from a list between two specified +# positions +# @usage list_erase_range +# @out Set the variable passed by reference +# +list_erase_range () { + test "$#" -ne 3 && return 1 + i="$2"; j="$3"; i=$((i+1)); j=$((j+1)); + eval "$1=\"\$(echo \"\$$1\" | sed \"${i},${j}d\")\"" +} + +## +# @brief Removes all elements from a specified position +# @usage list_erase_from +# @out Set the variable passed by reference +# +list_erase_from () { + test "$#" -ne 2 && return 1 + i="$2"; i=$((i+1)); eval "$1=\"\$(echo \"\$$1\" | sed \"${i},\\\$d\")\"" +} + +## +# @brief Removes the element at a specified position +# @usage list_eraseat +# @out Set the variable passed by reference +# +list_eraseat () { + test "$#" -ne 2 && return 1 + i="$2"; i=$((i+1)); eval "$1=\"\$(echo \"\$$1\" | sed \"${i}d\")\"" +} + +## +# @brief Removes all the elements from the list, which are equal to given +# element +# @usage list_remove +# @out Set the variable passed by reference +# +list_remove () { + test "$#" -ne 2 && return 1 + eval "$1=\"\$(echo \"\$$1\" | sed -e \"/^$2$/d\")\"" +} + +## +# @brief Removes the first element of the list +# @usage list_pop_front +# @out Set the variable passed by reference +# +list_pop_front () { + test "$#" -ne 1 && return 1; eval "$1=\"\$(echo \"\$$1\" | sed '1d')\"" +} + +## +# @brief Removes the last element of the list +# @usage list_pop_back +# @out Set the variable passed by reference +# +list_pop_back () { + test "$#" -ne 1 && return 1; eval "$1=\"\$(echo \"\$$1\" | sed '\$d')\"" +} + +## +# @brief Prints the index of a specified element +# @usage list_indexof +# @print positive integer or -1 if not found +# +list_indexof () { + test "$#" -ne 2 && return 1; i=0 + eval "for e in \$$1; do + [ \"\$e\" = '$2' ] && { echo \"\$i\"; return 0; }; i=\$((i+1)); + done" + return 1 +} + +## +# @brief Returns whether the list contains a specified element(0) or not(1) +# @usage list_contains +# @return 0 or 1 +# +list_contains () { + test "$#" -ne 2 && return 1 + eval "for e in \$$1; do [ \"\$e\" = '$2' ] && return 0; done; return 1" +} + +## +# @brief Prints the number of a specified element in the list +# @usage list_count +# @print The number of elements as positive integer +# +list_count () { + test "$#" -ne 2 && { echo '0'; return; } + eval "i=0; for e in \$$1; do [ \"\$e\" = '$2' ] && { i=\$((i+1)); };done;" + echo "$i" +} + +## +# @brief Maps every element of the list +# @usage list_maps +# @out Set the variable passed by reference +# +list_map () { + test "$#" -ne 2 && return 1 + eval "$1=\"\$(for e in \$$1; do eval \"$2 \$e\"; done)\"" +} + +## +# @brief Reverses the list +# @usage list_reverse +# @out Set the variable passed by reference +# +list_reverse() { + test "$#" -ne 1 && return 1 + eval "$1=\"\$(echo \"\$$1\" | sed '1!x;H;1h;\$!d;g')\"" +} + +## +# @brief Sorts the list +# @usage list_sort +# @out Set the variable passed by reference +# +list_sort () { + test "$#" -ne 1 && return 1; eval "$1=\"\$(echo \"\$$1\" | sort -n)\"" +} + +## +# @brief Sorts and reverses the sense of the list +# @usage list_sort_reverse +# @out Set the variable passed by reference +# +list_sort_reverse () { + test "$#" -ne 1 && return 1; eval "$1=\"\$(echo \"\$$1\" | sort -nr)\"" +} diff --git a/libshlist/test/test.sh b/libshlist/test/test.sh new file mode 100644 index 0000000..c08334c --- /dev/null +++ b/libshlist/test/test.sh @@ -0,0 +1,85 @@ +#!/bin/sh + +. ./liblist.sh + +print_list () { + echo '==========List============' + printf "%s\n" "$1" + echo '--------------------------' + printf "Size: %s\t\n\n" "$(list_size "$1")" +} + +Test_Delete () { + lst="$(list '1' '12' '23' \ + '33' '215' '-456' \ + '1236' '1' '12' \ + '3' '-3' '33' \ + '1' '12' '-55' \ + '123' '-1002' '-1' )" + + printf "TEST: Deletion\n\n" + echo 'test: Initialization'; print_list "$lst" + + lst="$(list_remove '1' "$lst")" ; echo 'test:' ; print_list "$lst" + lst="$(list_erase '33' "$lst")" ; echo 'test:' ; print_list "$lst" + lst="$(list_eraseat '3' "$lst")" ; echo 'test:' ; print_list "$lst" + lst="$(list_pop_front "$lst")" ; echo 'test:' ; print_list "$lst" + lst="$(list_pop_back "$lst")" ; echo 'test:' ; print_list "$lst" + lst="$(list_erase_from '8' "$lst")" ; echo 'test:' ; print_list "$lst" + lst="$(list_erase_range '1' '2' "$lst")"; echo 'test:' ; print_list "$lst" + lst="$(list_erase_range '0' '0' "$lst")"; echo 'test:' ; print_list "$lst" + lst="$(list_extract '1' '3' "$lst")" ; echo 'test:' ; print_list "$lst" + lst="$(list_extract '1' '30' "$lst")" ; echo 'test:' ; print_list "$lst" + lst="$(list_extract '1' '0' "$lst")" ; echo 'test:' ; print_list "$lst" +} + +Test_Addition () { + lst= + + printf "TEST: Addition\n\n" + echo 'test: Initialization'; print_list "$lst" + + lst="$(list_push_front '12' "$lst")" ; echo 'test:' ; print_list "$lst" + lst="$(list_push_back '33' "$lst")" ; echo 'test:' ; print_list "$lst" + lst="$(list_push_front '1' "$lst")" ; echo 'test:' ; print_list "$lst" + lst="$(list_insert '23' '2' "$lst")" ; echo 'test:' ; print_list "$lst" + lst="$(list_push_back '215' "$lst")" ; echo 'test:' ; print_list "$lst" +} + +inc () { i="$1"; i=$((i+1)); echo "${i}"; } +Test_Set() { + lst="$(list '1' '12' '23' \ + '33' '215' '-456' \ + '1236' '1' '12' )" + + printf "TEST: Set\n\n" + echo 'test: Initialization'; print_list "$lst" + + lst="$(list_reverse "$lst")" ; echo 'test:' ; print_list "$lst" + lst="$(list_sort "$lst")" ; echo 'test:' ; print_list "$lst" + lst="$(list_sort_reverse "$lst")" ; echo 'test:' ; print_list "$lst" + lst="$(list_map inc "$lst")" ; echo 'test:' ; print_list "$lst" + lst="$(list_replace '2' '999' "$lst")" ; echo 'test:' ; print_list "$lst" + lst="$(list_set '999' '3' "$lst")" ; echo 'test:' ; print_list "$lst" + lst="$(list_set '999' '6' "$lst")" ; echo 'test:' ; print_list "$lst" +} + +Test_Get() { + lst="$(list '1' '12' '23' \ + '33' '215' '-456' \ + '1236' '1' '12' )" + + printf "TEST: Get\n\n" + echo 'test: Initialization'; print_list "$lst" + + printf 'test: elt=' ; list_front "$lst" + printf 'test: elt=' ; list_back "$lst" + printf 'test: elt=' ; list_get '2' "$lst" + printf 'test: index=' ; list_indexof '1' "$lst" | grep -E '^[0-9]+$' || echo + printf 'test: index=' ; list_indexof '-456' "$lst" | grep -E '^[0-9]+$' || echo + printf 'test: contains='; list_contains '1236' "$lst" && echo 'yes' || echo 'no' + printf 'test: contains='; list_contains '999' "$lst" && echo 'yes' || echo 'no' + printf 'test: count=' ; list_count '1' "$lst" + printf 'test: count=' ; list_count '215' "$lst" + printf 'test: empty=' ; list_empty "$lst" && echo 'yes' || echo 'no' +} diff --git a/libshlist/test/test_unsafe.sh b/libshlist/test/test_unsafe.sh new file mode 100644 index 0000000..f0f1920 --- /dev/null +++ b/libshlist/test/test_unsafe.sh @@ -0,0 +1,210 @@ +#!/bin/sh + +. ./liblist_unsafe.sh + +print_list () { + echo '==========List============' + eval "printf \"%s\\n\" \"\$$1\"" + echo '--------------------------' + eval "printf \"Size: %s\t\n\n\" \"\$(list_size $1)\"" +} + +Test_Delete () { + lst="$(list '1' '12' '23' \ + '33' '215' '-456' \ + '1236' '1' '12' \ + '3' '-3' '33' \ + '1' '12' '-55' \ + '123' '-1002' '-1' )" + + printf "TEST: Deletion\n\n" + echo 'test: Initialization'; print_list lst + + list_remove lst '1' ; echo 'test:' ; print_list lst + list_erase lst '33' ; echo 'test:' ; print_list lst + list_eraseat lst 3 ; echo 'test:' ; print_list lst + list_pop_front lst ; echo 'test:' ; print_list lst + list_pop_back lst ; echo 'test:' ; print_list lst + list_erase_from lst 8 ; echo 'test:' ; print_list lst + list_erase_range lst 1 2 ; echo 'test:' ; print_list lst + list_erase_range lst 0 0 ; echo 'test:' ; print_list lst + list_extract lst 1 3 ; echo 'test:' ; print_list lst + list_extract lst 1 30 ; echo 'test:' ; print_list lst + list_extract lst 1 0 ; echo 'test:' ; print_list lst +} + +Test_Addition () { + lst= + + printf "TEST: Addition\n\n" + echo 'test: Initialization'; print_list lst + + list_push_front lst '12' ; echo 'test:' ; print_list lst + list_push_back lst '33' ; echo 'test:' ; print_list lst + list_push_front lst '1' ; echo 'test:' ; print_list lst + list_insert lst '23' 2 ; echo 'test:' ; print_list lst + list_push_back lst '215' ; echo 'test:' ; print_list lst +} + +inc () { i="$1"; i=$((i+1)); echo "${i}"; } +Test_Set() { + lst="$(list '1' '12' '23' \ + '33' '215' '-456' \ + '1236' '1' '12' )" + + printf "TEST: Set\n\n" + echo 'test: Initialization'; print_list lst + + list_reverse lst ; echo 'test:' ; print_list lst + list_sort lst ; echo 'test:' ; print_list lst + list_sort_reverse lst ; echo 'test:' ; print_list lst + list_map lst inc ; echo 'test:' ; print_list lst + list_replace lst '2' '999' ; echo 'test:' ; print_list lst + list_set lst '999' 3 ; echo 'test:' ; print_list lst + list_set lst '999' 6 ; echo 'test:' ; print_list lst +} + +Test_Get() { + lst="$(list '1' '12' '23' \ + '33' '215' '-456' \ + '1236' '1' '12' )" + + printf "TEST: Set\n\n" + echo 'test: Initialization'; print_list lst + + printf 'test: elt=' ; list_front lst + printf 'test: elt=' ; list_back lst + printf 'test: elt=' ; list_get lst 2 + printf 'test: index=' ; list_indexof lst '1' | grep -E '^[0-9]+$' || echo + printf 'test: index=' ; list_indexof lst '-456' | grep -E '^[0-9]+$' || echo + printf 'test: contains='; list_contains lst '1236' && echo 'yes' || echo 'no' + printf 'test: contains='; list_contains lst '2' && echo 'yes' || echo 'no' + printf 'test: count=' ; list_count lst '1' + printf 'test: count=' ; list_count lst '215' + printf 'test: empty=' ; list_empty lst && echo 'yes' || echo 'no' +} + +Test_Void() { + lst="$(list '' '' '')" + + printf "TEST: Void\n\n" + echo 'test: Initialization'; print_list lst + + printf 'test (void): empty=' ; list_empty lst && echo 'yes' || echo 'no' + printf 'test (void): size=' ; list_size lst + + list_reverse lst ; echo 'test:' ; print_list lst + list_sort lst ; echo 'test:' ; print_list lst + list_sort_reverse lst ; echo 'test:' ; print_list lst + list_replace lst '2' '999' ; echo 'test:' ; print_list lst + list_set lst '999' 3 ; echo 'test:' ; print_list lst + list_set lst '999' 6 ; echo 'test:' ; print_list lst + list_map lst inc ; echo 'test:' ; print_list lst + list_remove lst '1' ; echo 'test:' ; print_list lst + list_erase lst '33' ; echo 'test:' ; print_list lst + list_eraseat lst 3 ; echo 'test:' ; print_list lst + list_pop_front lst ; echo 'test:' ; print_list lst + list_pop_back lst ; echo 'test:' ; print_list lst + list_erase_from lst 8 ; echo 'test:' ; print_list lst + list_erase_range lst 1 2 ; echo 'test:' ; print_list lst + list_erase_range lst 0 0 ; echo 'test:' ; print_list lst + list_extract lst 1 3 ; echo 'test:' ; print_list lst + list_extract lst 1 30 ; echo 'test:' ; print_list lst + list_extract lst 1 0 ; echo 'test:' ; print_list lst + list_push_front lst '2' ; echo 'test:' ; print_list lst + list_push_back lst '4' ; echo 'test:' ; print_list lst + list_push_front lst '1' ; echo 'test:' ; print_list lst + list_insert lst '3' 2 ; echo 'test:' ; print_list lst + list_push_back lst '5' ; echo 'test:' ; print_list lst +} + +Test_BadArg() { + lst="$1" + printf "TEST: BadArg\n\n" + echo 'test: Initialization'; print_list lst + + list_push_front lst ; echo 'test:' ; print_list lst + list_push_front '' ; echo 'test:' ; print_list lst + list_push_front ; echo 'test:' ; print_list lst + + list_push_back lst ; echo 'test:' ; print_list lst + list_push_back '' ; echo 'test:' ; print_list lst + list_push_back ; echo 'test:' ; print_list lst + + list_insert lst '' ; echo 'test:' ; print_list lst + list_insert lst ; echo 'test:' ; print_list lst + list_insert 2 ; echo 'test:' ; print_list lst + + list_reverse ; echo 'test:' ; print_list lst + list_sort ; echo 'test:' ; print_list lst + list_sort_reverse ; echo 'test:' ; print_list lst + + list_replace lst '2' ; echo 'test:' ; print_list lst + list_replace lst ; echo 'test:' ; print_list lst + list_replace ; echo 'test:' ; print_list lst + + list_set lst '999' ; echo 'test:' ; print_list lst + list_set lst ; echo 'test:' ; print_list lst + list_set ; echo 'test:' ; print_list lst + + list_map lst ; echo 'test:' ; print_list lst + list_map inc ; echo 'test:' ; print_list lst + list_map ; echo 'test:' ; print_list lst + + list_remove lst ; echo 'test:' ; print_list lst + list_remove '1' ; echo 'test:' ; print_list lst + list_remove ; echo 'test:' ; print_list lst + + list_erase lst ; echo 'test:' ; print_list lst + list_erase '33' ; echo 'test:' ; print_list lst + list_erase ; echo 'test:' ; print_list lst + + list_eraseat lst ; echo 'test:' ; print_list lst + list_eraseat 3 ; echo 'test:' ; print_list lst + list_eraseat ; echo 'test:' ; print_list lst + + list_pop_front ; echo 'test:' ; print_list lst + list_pop_back ; echo 'test:' ; print_list lst + + list_erase_from lst ; echo 'test:' ; print_list lst + list_erase_from 8 ; echo 'test:' ; print_list lst + list_erase_from ; echo 'test:' ; print_list lst + + list_erase_range lst 0 ; echo 'test:' ; print_list lst + list_erase_range lst ; echo 'test:' ; print_list lst + list_erase_range ; echo 'test:' ; print_list lst + + list_extract 0 1 ; echo 'test:' ; print_list lst + list_extract lst 1 ; echo 'test:' ; print_list lst + list_extract lst ; echo 'test:' ; print_list lst + list_extract ; echo 'test:' ; print_list lst + + printf 'test: elt=' ; list_front || echo + printf 'test: elt=' ; list_back || echo + + printf 'test: elt=' ; list_get lst || echo + printf 'test: elt=' ; list_get 2 || echo + printf 'test: elt=' ; list_get || echo + + printf 'test: index=' ; list_indexof lst | grep -E '^[0-9]+$' || echo + printf 'test: index=' ; list_indexof '-456' | grep -E '^[0-9]+$' || echo + printf 'test: index=' ; list_indexof | grep -E '^[0-9]+$' || echo + + printf 'test: contains='; list_contains lst && echo 'yes' || echo 'no' + printf 'test: contains='; list_contains '2' && echo 'yes' || echo 'no' + printf 'test: contains='; list_contains && echo 'yes' || echo 'no' + + printf 'test: count=' ; list_count lst + printf 'test: count=' ; list_count '215' + printf 'test: count=' ; list_count + + printf 'test: empty=' ; list_empty && echo 'yes' || echo 'no' +} + +Test_VoidBadArg() { + Test_BadArg "$(list '' '' '' '')" +} + +Test_WithBadArg() { + Test_BadArg "$(list 1 2 3)" +} diff --git a/mons b/mons new file mode 100644 index 0000000..a509c7d --- /dev/null +++ b/mons @@ -0,0 +1,448 @@ +#!/bin/sh +# +# The MIT License (MIT) +# +# Copyright (c) 2017-2018 Thomas "Ventto" Venriès +# +# Permission is hereby granted, free of charge, to any person obtaining a copy of +# this software and associated documentation files (the "Software"), to deal in +# the Software without restriction, including without limitation the rights to +# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +# the Software, and to permit persons to whom the Software is furnished to do so, +# subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +# FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +# IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +usage() { + echo 'Usage: mons [OPTION]... + +Without argument, it prints connected monitors list with their names and ids. +Options are exclusive and can be used in conjunction with extra options. + +Information: + -h Prints this help and exits. + -v Prints version and exits. + +Two monitors: + -o Primary monitor only. + -s Second monitor only. + -d Duplicates the primary monitor. + -m Mirrors the primary monitor. + -e + Extends the primary monitor to the selected side + [ top | left | right | bottom ]. + -n + This mode selects the previous ones, one after another. The argument + sets the side for the extend mode. + +More monitors: + -O + Only enables the monitor with a specified id. + -S ,: + Only enables two monitors with specified ids. The specified position + places the second monitor on the right (R) or at the top (T). + +Extra (in-conjunction or alone): + --dpi + Set the DPI, a strictly positive value within the range [0 ; 27432]. + --primary + Select a connected monitor as the primary output. Run the script + without argument to print monitors information, the names are in the + second column between ids and status. The primary monitor is marked + by an asterisk. + +Daemon mode: + -a Performs an automatic display if it detects only one monitor. +' +} + +version() { + echo 'Mons 0.8.2 +Copyright (C) 2017 Thomas "Ventto" Venries. + +License MIT: . + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +' +} + +# Helps to generate manpage with help2man before installing the library +[ "$1" = '-h' ] && { usage; exit; } +[ "$1" = '-v' ] && { version; exit; } +lib='/usr/lib/libshlist/liblist.sh' +[ ! -r "$lib" ] && { "$lib: library not found."; exit 1; } +. "${lib}" + +arg_err() { + usage ; exit 2 +} + +enable_mon() { + "${XRANDR}" --output "${1}" --auto --dpi "${dpi}" +} + +disable_mons() { + for mon in $@; do "${XRANDR}" --output "${mon}" --off ; done +} + +arg2xrandr() { + case $1 in + left) echo '--left-of' ;; + right) echo '--right-of' ;; + bottom) echo '--below' ;; + top) echo '--above' ;; + esac +} + +whichmode() { + if [ "$(list_size "${disp_mons}")" -eq 1 ]; then + if echo "${enabled_out}" | grep prima > /dev/null 2>&1; then + echo 'primary' + else + echo 'second' + fi + else + if [ "$(list_size "${plug_mons}")" -gt 2 ] ; then + echo 'selection'; return 0 + fi + + enabled_out="$(echo "${enabled_out}" | \ + sed 's/^.*\( [0-9]\+\x[0-9]\++[0-9]\++[0-9]\+\).*/\1/')" + + echo "${enabled_out}" | head -n1 | sed -e 's/+/ /g' | \ + while read -r trash x1 y1; do + echo "${enabled_out}" | tail -n1 | sed -e 's/x/ /' -e 's/+/ /g' | \ + while read -r w2 h2 x2 y2; do + echo "${xrandr_out}" | \ + awk "/^$(list_get 1 "${plug_mons}")/{nr[NR+1]}; NR in nr" | \ + awk '{print $1;}' | sed -e 's/x/ /' | \ + while read -r wi2 hi2; do + if [ "$x1" = "$x2" ] && [ "$y1" = "$y2" ]; then + if [ "$w2" != "$wi2" ] || [ "$h2" != "$hi2" ]; then + echo 'mirror' + else + echo 'duplicate' + fi + else + echo 'extend' + fi + done + done + done + fi +} + +main() { + aFlag=false + dFlag=false + eFlag=false + mFlag=false + nFlag=false + oFlag=false + sFlag=false + OFlag=false + SFlag=false + pFlag=false + iFlag=false + is_flag=false + # X has assumed 96 DPI and this is fine for many traditional monitors. + dpi=96 + primary= + + # getopts does not support long options. We convert them to short one. + for arg in "$@"; do + shift + case "$arg" in + --dpi) set -- "$@" '-i' ;; + --primary) set -- "$@" '-p' ;; + *) set -- "$@" "$arg" + esac + done + + while getopts 'hvamosde:n:O:S:i:p:' opt; do + case $opt in + # Long options + i) + if ! echo "${OPTARG}" | \ + grep -E '^[1-9][0-9]*$' > /dev/null 2>&1; then + arg_err + fi + iFlag=true; dpi="$OPTARG" + ;; + p) if ! echo "${OPTARG}" | \ + grep -E '^[a-zA-Z][a-zA-Z0-9\-]+' > /dev/null 2>&1; then + arg_err + fi + pFlag=true; primary="$OPTARG" + ;; + # Short options + a) $is_flag && arg_err + aFlag=true ; is_flag=true + ;; + m) $is_flag && arg_err + mFlag=true ; is_flag=true + ;; + o) $is_flag && arg_err + oFlag=true ; is_flag=true + ;; + s) $is_flag && arg_err + sFlag=true ; is_flag=true + ;; + d) $is_flag && arg_err + dFlag=true ; is_flag=true + ;; + e|n) $is_flag && arg_err + case ${OPTARG} in + left | right | bottom | top) ;; + *) arg_err ;; + esac + eArg=$OPTARG + [ "$opt" = "e" ] && eFlag=true || nFlag=true ; is_flag=true + ;; + O) $is_flag && arg_err + ! echo "${OPTARG}" | grep -E '^[0-9]+$' > /dev/null && arg_err + OArg=$OPTARG + OFlag=true ; is_flag=true + ;; + S) $is_flag && arg_err + idx1="$(echo "${OPTARG}" | cut -d',' -f1)" + idx2="$(echo "${OPTARG}" | cut -d',' -f2)" + area="$(echo "${idx2}" | cut -d ':' -f2)" + idx2="$(echo "${idx2}" | cut -d ':' -f1)" + ! echo "${idx1}" | grep -E '^[0-9]+$' > /dev/null && arg_err + ! echo "${idx2}" | grep -E '^[0-9]+$' > /dev/null && arg_err + ! echo "${area}" | grep -E '^[RT]$' > /dev/null && arg_err + [ "${idx1}" = "${idx2}" ] && arg_err + SFlag=true ; is_flag=true + ;; + h) usage ; exit ;; + v) version ; exit ;; + \?) arg_err ;; + :) arg_err ;; + esac + done + + [ -z "${DISPLAY}" ] && { echo 'DISPLAY: no variable set.'; exit 1; } + + XRANDR="$(command -v xrandr)" + [ "$?" -ne 0 ] && { echo 'xrandr: command not found.'; exit 1; } + + # DPI set + $iFlag && [ "$#" -eq 2 ] && { "${XRANDR}" --dpi "$dpi"; exit; } + + # Daemon mode + if $aFlag ; then + prev=0; i=0 + while true; do + for status in /sys/class/drm/*/status; do + [ "$(<"$status")" = 'connected' ] && i=$((i+1)) + done + if [ "$i" -eq 1 ] && [ "$i" != "$prev" ]; then + "${XRANDR}" --auto --dpi "${dpi}" + fi + prev="$i"; i=0 + sleep 2 + done + fi + + # List all outputs (except primary one) + xrandr_out="$("${XRANDR}")" + enabled_out="$(echo "${xrandr_out}" | grep 'connect')" + [ -z "${enabled_out}" ] && { echo 'No monitor output detected.'; exit; } + mons="$(echo "${enabled_out}" | cut -d' ' -f1)" + + # List plugged-in and turned-on outputs + enabled_out="$(echo "${enabled_out}" | grep ' connect')" + [ -z "${enabled_out}" ] && { echo 'No plugged-in monitor detected.'; exit 1; } + plug_mons="$(echo "${enabled_out}" | cut -d' ' -f1)" + + if [ "$(list_size "${plug_mons}")" -eq 0 ]; then + echo "No monitor plugged-in." + exit 0 + fi + + # Set primary output + if $pFlag; then + if ! list_contains "${primary}" "${plug_mons}"; then + echo "${primary}: output not connected." + exit 1 + fi + "${XRANDR}" --output "${primary}" --primary + [ "$#" -eq 2 ] && exit + else + primary="$(echo "${enabled_out}" | grep 'primary' | cut -d' ' -f1)" + fi + + # Move the primary monitor to the head if connected otherwise the first + # connected monitor that appears in the xrandr output is considerate as + # the primary one. + if [ -n "${primary}" ]; then + plug_mons="$(list_erase "${primary}" "${plug_mons}")" + plug_mons="$(list_insert "${primary}" 0 "${plug_mons}")" + fi + + enabled_out="$(echo "${enabled_out}" | grep -E '\+[0-9]{1,4}\+[0-9]{1,4}')" + disp_mons="$(echo "${enabled_out}" | cut -d' ' -f1)" + + if [ "$#" -eq 0 ]; then + echo "Monitors: $(list_size "${plug_mons}")" + echo "Mode: $(whichmode)" + + i=0 + for mon in ${mons}; do + if echo "${plug_mons}" | grep "^${mon}$" > /dev/null; then + if echo "${disp_mons}" | grep "^${mon}$" > /dev/null; then + state='(enabled)' + fi + if [ "${mon}" = "${primary}" ]; then + printf '%-4s %-8s %-8s %-8s\n' "${i}:*" "${mon}" "${state}" + else + printf '%-4s %-8s %-8s\n' "${i}:" "${mon}" "${state}" + fi + fi + i=$((i+1)) + state= + done + exit + fi + + if $nFlag ; then + case "$(whichmode)" in + primary) sFlag=true;; + second) eFlag=true;; + extend) mFlag=true;; + mirror) dFlag=true;; + duplicate) oFlag=true;; + esac + fi + + if [ "$(list_size "${plug_mons}")" -eq 1 ] ; then + if $oFlag ; then + # After unplugging each monitor, the last preferred one might be + # still turned off or the window manager might need the monitor + # reset to cause the reconfiguration of the layout placement. + "${XRANDR}" --auto --dpi "${dpi}" + else + echo 'Only one monitor detected.' + fi + exit + fi + + if $oFlag ; then + if [ "$(list_size "${disp_mons}")" -eq 1 ]; then + if [ "$(list_front "${disp_mons}")" = "$(list_front "${plug_mons}")" ]; then + exit + fi + fi + disp_mons="$(list_erase "$(list_front "${plug_mons}")" "$disp_mons")" + disable_mons "${disp_mons}" + enable_mon "$(list_front "${plug_mons}")" + exit + fi + + if $OFlag ; then + if [ "${OArg}" -ge "$(list_size "${mons}")" ] ; then + echo "Monitor ID '${OArg}' does not exist." + echo 'Try without option to get monitor ID list.' + exit 2 + fi + mons_elt="$(list_get "${OArg}" "${mons}")" + if ! list_contains "${mons_elt}" "${plug_mons}"; then + echo "Monitor ID '${OArg}' not plugged in." + echo 'Try without option to get monitor ID list.' + exit 2 + fi + + disp_mons="$(list_erase "${mons_elt}" "${disp_mons}")" + disable_mons "${disp_mons}" + enable_mon "${mons_elt}" + exit + fi + + if $SFlag ; then + if [ "${idx1}" -ge "$(list_size "${mons}")" ] || \ + [ "${idx2}" -ge "$(list_size "${mons}")" ]; then + echo 'One or both monitor IDs do not exist.' + echo 'Try without option to get monitor ID list.' + exit 2 + fi + if ! list_contains "$(list_get "${idx1}" "${mons}")" "${plug_mons}" || \ + ! list_contains "$(list_get "${idx2}" "${mons}")" "${plug_mons}" ; then + echo 'One or both monitor IDs are not plugged in.' + echo 'Try without option to get monitor ID list.' + exit 2 + fi + + [ "${area}" = 'R' ] && area="--right-of" || area="--above" + + mon1="$(list_get "${idx1}" "${mons}")" + mon2="$(list_get "${idx2}" "${mons}")" + disp_mons="$(list_erase "${mon1}" "${disp_mons}")" + disp_mons="$(list_erase "${mon2}" "${disp_mons}")" + disable_mons "${disp_mons}" + enable_mon "${mon1}" + enable_mon "${mon2}" + "${XRANDR}" --output "${mon2}" "${area}" "${mon1}" + exit + fi + + if [ "$(list_size "${plug_mons}")" -eq 2 ]; then + if $sFlag ; then + if [ "$(list_size "${disp_mons}")" -eq 1 ] ; then + if [ "$(list_front "${disp_mons}")" = "$(list_get 1 "${plug_mons}")" ] ; then + enable_mon "$(list_get 1 "${plug_mons}")" + exit + fi + fi + enable_mon "$(list_get 1 "${plug_mons}")" + disable_mons "$(list_front "${disp_mons}")" + exit + fi + + # Resets the screen configuration + disable_mons "$(list_get 1 "${plug_mons}")" + "${XRANDR}" --auto --dpi "${dpi}" + + if $dFlag ; then + "${XRANDR}" --output "$(list_get 1 "${plug_mons}")" \ + --same-as "$(list_front "${plug_mons}")" + exit $? + fi + + if $mFlag ; then + xrandr_out="$(echo "${xrandr_out}" | \ + awk "/primary/{nr[NR]; nr[NR+1]}; NR in nr")" + if echo "${xrandr_out}" | \ + grep -E 'primary [0-9]+x[0-9]+' >/dev/null 2>&1; then + size="$(echo "${xrandr_out}" | head -n1 | cut -d' ' -f4 | \ + cut -d'+' -f1)" + else + size="$(echo "${xrandr_out}" | tail -n1 | awk '{ print $1 }')" + fi + + "${XRANDR}" --output "$(list_get 1 "${plug_mons}")" \ + --auto --scale-from "${size}" \ + --output "$(list_front "${plug_mons}")" + exit $? + fi + + if $eFlag ; then + "${XRANDR}" --output "$(list_get 1 "${plug_mons}")" \ + "$(arg2xrandr "$eArg")" "$(list_front "${plug_mons}")" + exit $? + fi + else + echo 'At most two plugged monitors for this option.' + fi +} + +main "$@" diff --git a/mons.1.gz b/mons.1.gz new file mode 100644 index 0000000..0b5d8de Binary files /dev/null and b/mons.1.gz differ diff --git a/test/test-args.sh b/test/test-args.sh new file mode 100644 index 0000000..8b37179 --- /dev/null +++ b/test/test-args.sh @@ -0,0 +1,77 @@ +#!/bin/sh + +if [ "$#" -ne 3 ]; then + echo 'Usage: test-args.sh MON_ID1 MON_ID2 MON_ID3' + echo + echo 'Three monitor ids are required. Ids are given by `mons`.' + exit 1 +fi + +echo 'TEST: Bad Arguments' +echo '#=========================#' +./mons +echo '#=========================#' +echo 'test: common' + +n=1 +./mons --s -o >/dev/null 2>&1 && echo "failed: $n" ; n=$((n+1)) +./mons --o >/dev/null 2>&1 && echo "failed: $n" ; n=$((n+1)) +./mons -e fekl56 >/dev/null 2>&1 && echo "failed: $n" ; n=$((n+1)) +./mons -e 2 >/dev/null 2>&1 && echo "failed: $n" ; n=$((n+1)) +./mons -e pot >/dev/null 2>&1 && echo "failed: $n" ; n=$((n+1)) +./mons -d 2 >/dev/null 2>&1 || echo "failed: $n" ; n=$((n+1)) +./mons -m 2 >/dev/null 2>&1 || echo "failed: $n" ; n=$((n+1)) + +echo 'test: single selection' + +n=1 +./mons -O 5 "$1" >/dev/null 2>&1 && echo "failed: $n" ; n=$((n+1)) +./mons -O abc "$2" >/dev/null 2>&1 && echo "failed: $n" ; n=$((n+1)) +./mons --O "$3" >/dev/null 2>&1 && echo "failed: $n" ; n=$((n+1)) +./mons -O >/dev/null 2>&1 && echo "failed: $n" ; n=$((n+1)) + +echo 'test: multi selection' + +n=1 +./mons -S >/dev/null 2>&1 && echo "failed: $n" ; n=$((n+1)) +./mons -S "$1,$2:P" >/dev/null 2>&1 && echo "failed: $n" ; n=$((n+1)) +./mons -S "$3,$2:5" >/dev/null 2>&1 && echo "failed: $n" ; n=$((n+1)) +./mons -S "$2:R" >/dev/null 2>&1 && echo "failed: $n" ; n=$((n+1)) +./mons -S "$3,$1" >/dev/null 2>&1 && echo "failed: $n" ; n=$((n+1)) +./mons -S "A,B:R" >/dev/null 2>&1 && echo "failed: $n" ; n=$((n+1)) + +echo 'test: dpi' + +n=1 +./mons --dpi >/dev/null 2>&1 && echo "failed: $n" ; n=$((n+1)) +./mons --dpi A >/dev/null 2>&1 && echo "failed: $n" ; n=$((n+1)) +./mons --dpi 0 >/dev/null 2>&1 && echo "failed: $n" ; n=$((n+1)) +./mons --dpi -0 >/dev/null 2>&1 && echo "failed: $n" ; n=$((n+1)) +./mons --dpi -5 >/dev/null 2>&1 && echo "failed: $n" ; n=$((n+1)) +./mons --dpi -3000 >/dev/null 2>&1 && echo "failed: $n" ; n=$((n+1)) +./mons --dpi -s 1 >/dev/null 2>&1 && echo "failed: $n" ; n=$((n+1)) +# Weird configuration +./mons -e --dpi 96 left >/dev/null 2>&1 || echo "failed: $n" ; n=$((n+1)) +./mons -n --dpi 72 right >/dev/null 2>&1 || echo "failed: $n" ; n=$((n+1)) + +echo 'test: primary' +n=1 +./mons --primary >/dev/null 2>&1 && echo "failed: $n" ; n=$((n+1)) +./mons --primary -HDMI1 >/dev/null 2>&1 && echo "failed: $n" ; n=$((n+1)) +./mons --primary -HDMI1 >/dev/null 2>&1 && echo "failed: $n" ; n=$((n+1)) +./mons --primary 1HDMI >/dev/null 2>&1 && echo "failed: $n" ; n=$((n+1)) +./mons --primary -s 1 >/dev/null 2>&1 && echo "failed: $n" ; n=$((n+1)) +./mons -n --primary HDMI1 >/dev/null 2>&1 && echo "failed: $n" ; n=$((n+1)) +./mons -n --primary HDMI1 left >/dev/null 2>&1 || echo "failed: $n" ; n=$((n+1)) +./mons -n --primary HDMI1 left -s >/dev/null 2>&1 && echo "failed: $n" ; n=$((n+1)) + +echo 'test: dpi + primary' +n=1 +./mons --primary --dpi >/dev/null 2>&1 && echo "failed: $n" ; n=$((n+1)) +./mons --primary --dpi 75 >/dev/null 2>&1 && echo "failed: $n" ; n=$((n+1)) +./mons --primary HDMI1 --dpi >/dev/null 2>&1 && echo "failed: $n" ; n=$((n+1)) +./mons --primary HDMI1 --dpi 0 >/dev/null 2>&1 && echo "failed: $n" ; n=$((n+1)) +./mons --primary 1HDMI --dpi 0 >/dev/null 2>&1 && echo "failed: $n" ; n=$((n+1)) +./mons --primary HDMI1 --dpi HDMI1 >/dev/null 2>&1 && echo "failed: $n" ; n=$((n+1)) +./mons --primary HDMI1 --dpi 75 >/dev/null 2>&1 && echo "failed: $n" ; n=$((n+1)) +./mons -n --primary HDMI1 --dpi 75 left >/dev/null 2>&1 || echo "failed: $n" ; n=$((n+1)) diff --git a/test/test-common.sh b/test/test-common.sh new file mode 100644 index 0000000..d5db31d --- /dev/null +++ b/test/test-common.sh @@ -0,0 +1,36 @@ +#!/bin/sh + +if [ "$#" -ne 2 ]; then + echo 'Usage: test-common.sh CURR_MON_NAME MON_NAME' + echo + echo 'Monitor names are required. Names are given by "mons". +CURR_MON_NAME is your current monitor and the second one is reserved +for primary test.' + exit 1 +fi + +Test_Common () { + echo 'TEST: Common Modes' + echo '#=========================#' + ./mons + echo '#=========================#' + + ./mons -o ; echo "test: primary" ; sleep 8 + ./mons -s ; echo "test: second" ; sleep 8 + ./mons -e left ; echo "test: extend left" ; sleep 8 + ./mons -d ; echo "test: duplicate" ; sleep 8 + ./mons -m ; echo "test: mirror" ; sleep 8 + ./mons -e right ; echo "test: extend right" ; sleep 8 + ./mons -e top ; echo "test: extend top" ; sleep 8 + ./mons -e bottom ; echo "test: extend bottom" ; sleep 8 + ./mons -o ; echo "test: primary" ; sleep 8 + ./mons -s ; echo "test: second" ; sleep 8 + ./mons -o ; echo "test: primary" +} + +./mons -o --primary "$1" +Test_Common +./mons -o --primary "$2" +echo "[ Primary has been set: $1 ]" +Test_Common +./mons -o --primary "$1" diff --git a/test/test-select.sh b/test/test-select.sh new file mode 100644 index 0000000..151c79f --- /dev/null +++ b/test/test-select.sh @@ -0,0 +1,24 @@ +#!/bin/sh + +if [ "$#" -ne 3 ]; then + echo 'Usage: test-select.sh MON_ID1 MON_ID2 MON_ID3' + echo + echo 'Three monitor ids are required. Ids are given by `mons`.' + exit 1 +fi + +echo 'TEST: Selections' +echo '#=========================#' +./mons +echo '#=========================#' + +./mons -O "$1" ; echo "test: single select $1" ; sleep 8 +./mons -O "$2" ; echo "test: single select $2" ; sleep 8 +./mons -O "$3" ; echo "test: single select $3" ; sleep 8 +./mons -O "$1" ; echo "test: single select $1" ; sleep 8 +./mons -S "$1,$2:R" ; echo "test: single select $1,$2:R" ; sleep 8 +./mons -S "$1,$2:T" ; echo "test: multi select $1,$2:T" ; sleep 8 +./mons -S "$3,$2:T" ; echo "test: multi select $3,$2:T" ; sleep 8 +./mons -S "$2,$3:R" ; echo "test: multi select $2,$3:R" ; sleep 8 +./mons -S "$3,$1:T" ; echo "test: multi select $3,$1:T" ; sleep 8 +./mons -O "$1" ; echo "test: single select $1"