From 7b2650b64615517cc37952a59e31540a82150216 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcin=20Wo=C5=BAniak?= Date: Mon, 14 Sep 2020 11:00:29 +0200 Subject: [PATCH] Added MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Marcin Woźniak --- CONTRIBUTING.md | 57 +++++ LICENSE | 20 ++ Makefile | 37 +++ README.md | 200 +++++++++++++++ _gitignore | 19 ++ _gitmodules | 3 + img/raw-body.png | Bin 0 -> 10050 bytes libshlist/LICENSE | 20 ++ libshlist/README.md | 111 +++++++++ libshlist/_git | 1 + libshlist/liblist.sh | 271 ++++++++++++++++++++ libshlist/liblist_unsafe.sh | 286 ++++++++++++++++++++++ libshlist/test/test.sh | 85 +++++++ libshlist/test/test_unsafe.sh | 210 ++++++++++++++++ mons | 448 ++++++++++++++++++++++++++++++++++ mons.1.gz | Bin 0 -> 1064 bytes test/test-args.sh | 77 ++++++ test/test-common.sh | 36 +++ test/test-select.sh | 24 ++ 19 files changed, 1905 insertions(+) create mode 100644 CONTRIBUTING.md create mode 100644 LICENSE create mode 100644 Makefile create mode 100644 README.md create mode 100644 _gitignore create mode 100644 _gitmodules create mode 100644 img/raw-body.png create mode 100644 libshlist/LICENSE create mode 100644 libshlist/README.md create mode 100644 libshlist/_git create mode 100644 libshlist/liblist.sh create mode 100644 libshlist/liblist_unsafe.sh create mode 100644 libshlist/test/test.sh create mode 100644 libshlist/test/test_unsafe.sh create mode 100644 mons create mode 100644 mons.1.gz create mode 100644 test/test-args.sh create mode 100644 test/test-common.sh create mode 100644 test/test-select.sh 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 0000000000000000000000000000000000000000..0dfbb8d3f5d731331350e5bf1a3f7f26f5b3eb39 GIT binary patch literal 10050 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 LO@%r+i% + +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 0000000000000000000000000000000000000000..0b5d8de81e5f719e15a9e14b34ff0f8b936a9584 GIT binary patch literal 1064 zcmV+@1lRi?iwFP!0000019eqhZ{s!)eAlno`Y8b%C3X);n+5?KOFk2|B|%ayXT*ns zk?EK{NmNNHiGlw3?&{;Brs<0aQM0qV!{N%^j=&2c2vb;x9$Rc6U9togY~}w1z%0CQ z15ct9np?4h$0Hm>UQG*L!o>Y_=}soDcAT(E6yXRRw$-q$=N~N=fuW?Rv&Osd$#EHmHCwV|oJL zTXZ}x6@J$)q=@Q$<`~h%5ijhi$^)5(x@gF|^I0qO((r)#s=}r!{L&T`qV6C{{+M?v z_suu<;$SL~$-N}|?FPLiJbg?qBhGNb?}O2T}kG> zRhr#a`IGn*aF5$&kB;UfJ?%8`o6B(`%d_FVA#{#1pAJ<)$5pcF3D>rZM6{e<#Kt6q z)iSf#SX&|9&u5<3qG?HX74Z@$&BF!!2Gh$=KTn5d*q(Ln>V^A*We-}FkM?DTKZqUEdeD99I;{uV5Ifs*iH?KCHQpesFzY8Y z$UTkKov_Vqp%KyCkGETmQ+Xano0xrGrp{bd$JP|vlT6XCK)v0%Qs z^H}g1ZZVF2tt*BZku(i~5_VL~Pl#;o$MYq=&TiR?rJJ#{U}->{i!g>6M6)<$^Sjk7 ihUhNFAEb}zJ){j-u!u2>zxIRFb^Zal+JR}x2LJ%gr4*z9 literal 0 HcmV?d00001 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"