├── .gitignore ├── .travis.yml ├── AUTHORS ├── ChangeLog.md ├── LICENSE ├── Makefile.am ├── README.md ├── autogen.sh ├── configure.ac ├── debian ├── .gitignore ├── changelog ├── compat ├── control ├── copyright ├── docs ├── rules ├── source │ ├── format │ └── options └── watch ├── indent.sh ├── man ├── Makefile.am └── xplugd.1 ├── src ├── Makefile.am ├── edid.c ├── edid.h ├── exec.c ├── input.c ├── randr.c ├── xplugd.c └── xplugd.h └── xplugrc /.gitignore: -------------------------------------------------------------------------------- 1 | .hg 2 | *~ 3 | *.o 4 | *.d 5 | .deps 6 | .libs 7 | compile 8 | config.* 9 | configure 10 | depcomp 11 | install-sh 12 | missing 13 | stamp-h1 14 | Makefile 15 | Makefile.in 16 | aclocal.m4 17 | autom4te.cache 18 | xplugd 19 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # Travis CI integration 2 | # Defaults to GNU GCC and autotools: ./configure && make && make test 3 | language: c 4 | 5 | # We don't need to install packages, use dockerized build, quicker 6 | sudo: false 7 | 8 | # Test build with both GCC and Clang (LLVM) 9 | compiler: 10 | - gcc 11 | - clang 12 | 13 | env: 14 | global: 15 | # The next declaration is the encrypted COVERITY_SCAN_TOKEN, created 16 | # via the "travis encrypt" command using the project repo's public key 17 | - secure: "OW8+ZttgiqxdsiqrQebn7sEZk6g4PqhtwT5ggrO3H98I/l8lL3FjcN5CxJfJI2NIcInxUd91fOHUvpzuVjJpbEH4m33ao8HIYkSlB6SDMjcXXwMabBDkHwdGiLuI0Pcuv2aSMC4lVFd75Q7/hBOJySjSPf/aDQZBuiyORgRKfHHhLZgOdWGdTbsRbh0cxhoKhZckHTXGJpXh4h2DBfVsdh28vI69Pmv8sbfvRKUYjdI04p9lmE/7TXsTmrSyJXKQhZ/HOyOxUZX5XMlPhTDkdepX9RKUCQVfLyRMQz3dDoijHGT19vFwx+M6b0um7lwh+o8bIrkIkDX02o4bpnaF/mu6nMcxso8Ob40l7JH/DNFwfAwrhC+5ojO42Ewy8ZFMdK3WQmaTf89Oj7iRgpL4gtv825VYeI4UoTnXf/gBSdt2VCSytj4C/jLmxtRZNSkDGAMsALbIm1vE/VA/N8R8NxEEXD0e3yvLwRtzlNYcR4x3g5uGKooT1Yj/guobcBZtXRKgLDvTcnYQKBVOfnYgmVbW82J4TBAtJ9GOA+RCxhff9vvrj6DDmZKdFv7oNYSMaqOe2DxD8vaQ2LrP53s5PD2vXv6bE7gSWVKcNK7ND+1EBYmVTpWVVEtEUAZmSKkypAzS+gDf/Ugzc0ODeLmFPR7WKzW+QXgDvlxylNR2akU=" 18 | 19 | addons: 20 | apt: 21 | packages: 22 | - tree 23 | - libxi-dev 24 | - libxrandr-dev 25 | coverity_scan: 26 | project: 27 | name: "troglobit/xplugd" 28 | description: "Monitor, keyboard, and mouse plug/unplug helper for X" 29 | notification_email: troglobit@gmail.com 30 | build_command_prepend: "./autogen.sh && ./configure --prefix=/tmp && make clean" 31 | build_command: "make" 32 | branch_pattern: dev 33 | 34 | # We don't store generated files (configure and Makefile) in GIT, 35 | # so we must customize the default build script to run ./autogen.sh 36 | script: 37 | - ./autogen.sh 38 | - ./configure --prefix=/tmp 39 | - make V=1 40 | - make install-strip 41 | - tree /tmp 42 | - ldd /tmp/bin/xplugd 43 | - /tmp/bin/xplugd -h 44 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | xplugd is composed of parts from two other programs, as well as the 2 | usual amount of frog DNA needed to glue it all together. 3 | 4 | - Stefan Bolte wrote srandrd 5 | - Andrew Shadura wrote inputplug 6 | - Joachim Wiberg a.k.a Dr Frankenstein 7 | - Magnus Malm added EDID support 8 | -------------------------------------------------------------------------------- /ChangeLog.md: -------------------------------------------------------------------------------- 1 | Change Log 2 | ========== 3 | 4 | All notable changes to the project are documented in this file. 5 | 6 | 7 | [v1.4][] - 2020-07-08 8 | --------------------- 9 | 10 | Huge props to Magnus Malm for helping out with many of the fixes 11 | and features added in this release. Thank you! 12 | 13 | ### Changes 14 | - Support for EDID matching, i.e. monitor description information, 15 | by Magnus Malm 16 | - Support for querying currently attached monitors for EDID info 17 | using `xplugd -p`, by Magnus Malm 18 | - Support XDG Base Directory, xplugd now first looks for xplugrc 19 | in `~/.config/xplugrc`, with compat fallback to `~/.xplugrc`. 20 | Initial patch by hydraz 21 | - Debian package support, use `make package` to build, or use the 22 | pre-built x86_64 package from https://deb.troglobit.com 23 | 24 | ### Fixes 25 | - Support building on older musl libc based systems that do not yet 26 | support `GLOB_TILDE`. Used when looking for xplugrc file 27 | 28 | 29 | [v1.3][] - 20018-02-01 30 | ---------------------- 31 | 32 | ### Changes 33 | - Portability, replace `__progname` with a small function 34 | - Make script argument optional, default to `~/.xplugrc` 35 | - Add support for monitor description using EDID, by Magnus Malm 36 | 37 | ### Fixes 38 | - Fix SynPS/2 matching, `conntected` vs `connected` 39 | 40 | 41 | [v1.2][] - 20016-06-25 42 | ---------------------- 43 | 44 | ### Changes 45 | - Converted to GNU configure and build system 46 | - Add support for Travis-CI 47 | 48 | ### Fixes 49 | - Issue #1: Use `sigaction()` instead of ambiguous SysV `signal()` API 50 | - Fix missing keyboard/mouse disconnect events 51 | - Portability fix, use `__progname` as identity in `openlog()`, gives 52 | clearer log messages on non-GLIBC systems 53 | 54 | 55 | [v1.1][] - 20016-04-03 56 | ---------------------- 57 | 58 | Now with `xinput(1)` support -- one daemon to detect both display and 59 | keyboard/mouse events! Ideas and code from [inputplug][]. 60 | 61 | 62 | [v1.0][] - 20016-04-01 63 | ---------------------- 64 | 65 | First official release after fork from [srandrd][] 66 | 67 | - Handles monitor plug/unplug events 68 | - Can be used to seamlessly dock/undock 69 | - Prepared for future support for xinput(1) 70 | 71 | [UNRELEASED]: https://github.com/troglobit/xplugd/compare/v1.4...HEAD 72 | [v1.4]: https://github.com/troglobit/xplugd/compare/v1.3...v1.4 73 | [v1.3]: https://github.com/troglobit/xplugd/compare/v1.2...v1.3 74 | [v1.2]: https://github.com/troglobit/xplugd/compare/v1.1...v1.2 75 | [v1.1]: https://github.com/troglobit/xplugd/compare/v1.0...v1.1 76 | [v1.0]: https://github.com/troglobit/xplugd/compare/v0.5...v1.0 77 | [srandrd]: https://bitbucket.org/portix/srandrd 78 | [inputplug]: https://bitbucket.org/andrew_shadura/inputplug 79 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT/X Consortium License 2 | 3 | Copyright (C) 2012-2015 Stefan Bolte 4 | Copyright (C) 2013-2015 Andrew Shadura 5 | Copyright (C) 2016-2023 Joachim Wiberg 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy of 8 | this software and associated documentation files (the "Software"), to deal in 9 | the Software without restriction, including without limitation the rights to 10 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 11 | the Software, and to permit persons to whom the Software is furnished to do so, 12 | subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in all 15 | copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 19 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 20 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 21 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 22 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /Makefile.am: -------------------------------------------------------------------------------- 1 | SUBDIRS = man src 2 | doc_DATA = README.md LICENSE xplugrc 3 | EXTRA_DIST = README.md LICENSE xplugrc 4 | DISTCLEANFILES = *~ DEADJOE semantic.cache *.gdb *.elf core core.* *.d 5 | 6 | package: 7 | @debuild -uc -us -B --lintian-opts --profile debian -i -I --show-overrides 8 | 9 | ## Target to run when building a release 10 | release: distcheck package 11 | @for file in $(DIST_ARCHIVES); do \ 12 | md5sum $$file > ../$$file.md5; \ 13 | done 14 | @mv $(DIST_ARCHIVES) ../ 15 | @echo 16 | @echo "Resulting release files in ../" 17 | @echo "=================================================================" 18 | @for file in $(DIST_ARCHIVES); do \ 19 | printf "%-32s Distribution tarball\n" $$file; \ 20 | printf "%-32s " $$file.md5; cat ../$$file.md5 | cut -f1 -d' '; \ 21 | done 22 | @for file in `cd ..; ls $(PACKAGE)_$(VERSION)*`; do \ 23 | printf "%-32s Debian/Ubuntu file\n" $$file; \ 24 | done 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | xplugd - X plug daemon 2 | ====================== 3 | [![Travis Status][]][Travis] [![Coverity Status]][Coverity Scan] 4 | 5 | `xplugd` is a UNIX daemon that executes a script on X input and RandR 6 | changes, i.e., when a, keyboard, mouse. or a monitor is plugged in or 7 | unplugged. Useful in combination with lightweight setups, e.g. when 8 | running an X window manager like [Awesome][1], Fluxbox, or similar to 9 | detect when docking or undocking a laptop. 10 | 11 | 12 | Usage 13 | ----- 14 | 15 | xplugd [-hnpsv] [-l LEVEL] [FILE] 16 | 17 | -h Show help text and exit 18 | -l LEVEL Set log level: none, err, info, notice*, debug 19 | -n Run in foreground, do not fork to background 20 | -p Probe currently connected outputs and output EDID info. 21 | -s Use syslog, even if running in foreground, default w/o -n 22 | -v Show version info and exit 23 | 24 | FILE Optional script argument, default $XDG_CONFIG_HOME/xplugrc 25 | Fallback also checks for ~/.config/xplugrc and ~/.xplugrc 26 | 27 | When `FILE` is omitted `xplugd` uses `$XDG_CONFIG_HOME/xplugrc`, if that 28 | cannot be found `~/.config/xplugrc` is tried, and finally `~/.xplugrc`. 29 | The file is called as a shell script on plug events as follows: 30 | 31 | xplugrc TYPE DEVICE STATUS ["Optional Description"] 32 | | | | 33 | | | `---- connected or disconnected 34 | | `----------- HDMI3, LVDS1, VGA1, etc. 35 | `---------------- keyboard, pointer, display 36 | 37 | The script may be called like this, notice how the description is not 38 | included for displays: 39 | 40 | xplugrc display HDMI3 disconnected 41 | xplugrc keyboard 3 connected "Topre Corporation Realforce 87" 42 | 43 | The keyboard or pointer is always the X slave keyboard or pointer, and 44 | the status encoding for `XIStatusEnabled` and `XIStatusDisabled` is 45 | forwarded to the script as connected and disconnected, respectively. 46 | 47 | If EDID data is available from a connected display, the monitor model is 48 | passed in as fourth argument ("Optional Description") to the script. 49 | 50 | 51 | ### Example ~/.config/xplugrc 52 | 53 | ```sh 54 | #!/bin/sh 55 | LAPTOP=LVDS1 56 | DOCK=HDMI3 57 | DESKPOS=--left-of 58 | PRESPOS=--right-of 59 | 60 | if [ "$1" != "display" ]; then 61 | case "$1,$3,$4" in 62 | pointer,conntected,"SynPS/2 Synaptics TouchPad") 63 | xinput set-prop $2 'Synaptics Off' 1 64 | ;; 65 | keyboard,connected,*) 66 | setxkbmap -option ctrl:nocaps 67 | ;; 68 | esac 69 | exit 0 70 | fi 71 | 72 | if [ "$3" = "disconnected" ]; then 73 | xrandr --output $2 --off 74 | exit 0 75 | fi 76 | 77 | if [ "$2" = "${DOCK}" ]; then 78 | xrandr --output $2 --auto --primary ${DESKPOS} ${LAPTOP} 79 | elif [ "$1" != "${LAPTOP}" ]; then 80 | xrandr --output $2 --auto ${PRESPOS} ${LAPTOP} --primary 81 | else 82 | xrandr --auto 83 | fi 84 | ``` 85 | 86 | 87 | Build & Install 88 | --------------- 89 | 90 | To build `xplugd` you need the standard libraries and header files for 91 | X11, X11 input, and Xrandr. On a Debian/Ubuntu system these files can 92 | be installed with: 93 | 94 | sudo apt install libx11-dev libxi-dev libxrandr-dev 95 | 96 | Then run the configure script and make: 97 | 98 | ./configure && make 99 | 100 | Unless building from the GIT sources, in which case `./autogen.sh` first 101 | must be called to create the configure script. With relased tarballs this 102 | is not necessary. 103 | 104 | To change the default installation prefix from `/usr/local`, use the 105 | 106 | ./configure --prefix=/some/other/path 107 | 108 | Followed by 109 | 110 | make all && sudo make install-strip 111 | 112 | 113 | Origin & References 114 | ------------------- 115 | 116 | [`xplugd`][2] is composed from pieces of Stefan Bolte's [`srandrd`][3] 117 | and Andrew Shadura's [`inputplug`][4]. Please report bugs and problems 118 | to the `xplugd` project. 119 | 120 | [1]: https://awesome.naquadah.org 121 | [2]: https://github.com/troglobit/xplugd 122 | [3]: https://bitbucket.org/portix/srandrd 123 | [4]: https://bitbucket.org/andrew_shadura/inputplug 124 | [Travis]: https://travis-ci.org/troglobit/xplugd 125 | [Travis Status]: https://travis-ci.org/troglobit/xplugd.png?branch=master 126 | [Coverity Scan]: https://scan.coverity.com/projects/10739 127 | [Coverity Status]: https://scan.coverity.com/projects/10739/badge.svg 128 | -------------------------------------------------------------------------------- /autogen.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | autoreconf -W portability -visfm 4 | -------------------------------------------------------------------------------- /configure.ac: -------------------------------------------------------------------------------- 1 | AC_INIT([xplugd], [1.4], [https://github.com/troglobit/xplugd/issues]) 2 | AM_INIT_AUTOMAKE([1.11 foreign no-dist-gzip dist-xz]) 3 | AM_SILENT_RULES([yes]) 4 | 5 | AC_CONFIG_SRCDIR([src/xplugd.c]) 6 | AC_CONFIG_HEADER([config.h]) 7 | AC_CONFIG_FILES([Makefile man/Makefile src/Makefile]) 8 | 9 | AC_PROG_CC 10 | AC_HEADER_STDC 11 | AC_PROG_INSTALL 12 | 13 | # Check for required libraries 14 | AC_SEARCH_LIBS([pow], [m]) 15 | PKG_CHECK_MODULES([X11], [x11]) 16 | PKG_CHECK_MODULES([Xrandr], [xrandr]) 17 | PKG_CHECK_MODULES([Xi], [xi]) 18 | 19 | AC_OUTPUT 20 | -------------------------------------------------------------------------------- /debian/.gitignore: -------------------------------------------------------------------------------- 1 | *.log 2 | *.substvars 3 | autoreconf.* 4 | debhelper-build-stamp 5 | files 6 | -------------------------------------------------------------------------------- /debian/changelog: -------------------------------------------------------------------------------- 1 | xplugd (1.4) stable; urgency=medium 2 | 3 | * Support EDID matching in xplugrc and probing from command line 4 | * Support XDG Base Directory, look for .xplugrc in ~/.config 5 | * Initial Debian packaging 6 | 7 | -- Joachim Nilsson Wed, 08 Jul 2020 11:01:03 +0200 8 | -------------------------------------------------------------------------------- /debian/compat: -------------------------------------------------------------------------------- 1 | 10 2 | -------------------------------------------------------------------------------- /debian/control: -------------------------------------------------------------------------------- 1 | Source: xplugd 2 | Section: x11 3 | Priority: optional 4 | Maintainer: Joachim Wiberg 5 | Homepage: https://github.com/troglobit/xplugd 6 | Build-Depends: debhelper (>= 10), libx11-dev, libxi-dev, libxrandr-dev 7 | Standards-Version: 4.3.0 8 | Vcs-Git: https://github.com/troglobit/xplugd.git 9 | Vcs-Browser: https://github.com/troglobit/xplugd/commits/ 10 | 11 | Package: xplugd 12 | Architecture: any 13 | Depends: ${shlibs:Depends}, ${misc:Depends} 14 | Description: X plug daemon 15 | xplugd is a UNIX daemon that executes a script on X input and RandR 16 | changes, i.e., when a, keyboard, mouse. or a monitor is plugged in or 17 | unplugged. Useful in combination with lightweight setups, e.g. when 18 | running an X window manager like Awesome, Fluxbox, or similar to detect 19 | when docking or undocking a laptop. 20 | -------------------------------------------------------------------------------- /debian/copyright: -------------------------------------------------------------------------------- 1 | Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ 2 | Upstream-Name: xplugd 3 | Upstream-Contact: troglobit@gmail.com 4 | Source: https://github.com/troglobit/xplugd 5 | 6 | Files: * 7 | Copyright: 2012-2015 Stefan Bolte 8 | 2013-2015 Andrew Shadura 9 | 2016-2023 Joachim Wiberg 10 | License: MIT/X11 11 | 12 | Files: debian/* 13 | Copyright: 2020-2023 Joachim Wiberg 14 | License: MIT/X11 15 | Permission is hereby granted, free of charge, to any person obtaining a copy of 16 | this software and associated documentation files (the "Software"), to deal in 17 | the Software without restriction, including without limitation the rights to 18 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 19 | the Software, and to permit persons to whom the Software is furnished to do so, 20 | subject to the following conditions: 21 | . 22 | The above copyright notice and this permission notice shall be included in all 23 | copies or substantial portions of the Software. 24 | . 25 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 26 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 27 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 28 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 29 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 30 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 31 | -------------------------------------------------------------------------------- /debian/docs: -------------------------------------------------------------------------------- 1 | README.md 2 | -------------------------------------------------------------------------------- /debian/rules: -------------------------------------------------------------------------------- 1 | #!/usr/bin/make -f 2 | # export DH_VERBOSE=1 3 | export DEB_BUILD_MAINT_OPTIONS = hardening=+all 4 | 5 | %: 6 | dh $@ --with autoreconf 7 | 8 | override_dh_installchangelogs: 9 | dh_installchangelogs ChangeLog.md 10 | 11 | override_dh_auto_install: 12 | dh_auto_install 13 | find debian/ -name LICENSE -delete 14 | -------------------------------------------------------------------------------- /debian/source/format: -------------------------------------------------------------------------------- 1 | 3.0 (native) 2 | -------------------------------------------------------------------------------- /debian/source/options: -------------------------------------------------------------------------------- 1 | compression=xz 2 | -------------------------------------------------------------------------------- /debian/watch: -------------------------------------------------------------------------------- 1 | version=3 2 | opts=filenamemangle=s/.+\/v?(\d\S*)\.tar\.xz/xplugd-$1\.tar\.xz/ \ 3 | https://github.com/troglobit/xplugd/releases .*/xplugd-?(\d\S*)\.tar\.xz 4 | -------------------------------------------------------------------------------- /indent.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # Don't touch comment content/formatting, only placement. 4 | # ignore any ~/.indent.pro 5 | # 6 | # With the -T we can inform indent about non-ansi types 7 | # that we've added, so indent doesn't insert spaces in odd places. 8 | # 9 | indent -linux \ 10 | --ignore-profile \ 11 | --preserve-mtime \ 12 | --break-after-boolean-operator \ 13 | --blank-lines-after-procedures \ 14 | --blank-lines-after-declarations \ 15 | --dont-break-function-decl-args \ 16 | --dont-break-procedure-type \ 17 | --leave-preprocessor-space \ 18 | --line-length132 \ 19 | --honour-newlines \ 20 | --space-after-if \ 21 | --space-after-for \ 22 | --space-after-while \ 23 | --leave-optional-blank-lines \ 24 | --dont-format-comments \ 25 | --no-blank-lines-after-commas \ 26 | --no-space-after-parentheses \ 27 | --no-space-after-casts \ 28 | -T size_t -T timeval_t -T pid_t -T pthread_t -T FILE \ 29 | -T time_t -T uint32_t -T uint16_t -T uint8_t -T uchar -T uint -T ulong \ 30 | -T Display -T XRRScreenResources -T XRROutputChangeNotifyEvent -T XEvent \ 31 | $* 32 | -------------------------------------------------------------------------------- /man/Makefile.am: -------------------------------------------------------------------------------- 1 | dist_man1_MANS = xplugd.1 2 | -------------------------------------------------------------------------------- /man/xplugd.1: -------------------------------------------------------------------------------- 1 | .\" Hey, EMACS: -*- nroff -*- 2 | .\" First parameter, NAME, should be all caps 3 | .\" Second parameter, SECTION, should be 1-8, maybe w/ subsection 4 | .\" other parameters are allowed: see man(7), man(1) 5 | .Dd Jan 29, 2023 6 | .\" Please adjust this date whenever revising the manpage. 7 | .Dt XPLUGD 1 URM 8 | .Os 9 | .Sh NAME 10 | .Nm xplugd 11 | .Nd an X input/output plug in/out helper 12 | .Sh SYNOPSIS 13 | .Nm 14 | .Op Fl hnpsv 15 | .Op Fl l Ar LEVEL 16 | .Ar [FILE] 17 | .Sh DESCRIPTION 18 | .Nm 19 | is a daemon that executes a script on X input and RandR changes, i.e., 20 | if a keyboard, mouse or monitor is plugged in or unplugged. Useful if 21 | you want to run 22 | .Xr xrandr 1 , 23 | .Xr xinput 1 , 24 | or 25 | .Xr setxkbmap 1 26 | when docking or undocking a laptop. 27 | .Pp 28 | By default 29 | .Nm 30 | forks to the background and exits when the X server exits. 31 | .Sh OPTIONS 32 | .Pp 33 | .Bl -tag -width Ds 34 | .It Fl h 35 | Print help and exit 36 | .It Fl l Ar LVL 37 | Set log level for syslog messages, where 38 | .Ar LVL 39 | is one of: none, err, info, 40 | .Ar notice , 41 | debug. Use 42 | .Fl l Ar debug 43 | to enable debug messages. Default: 44 | .Ar notice 45 | .It Fl n 46 | Run in foreground, do not detach from calling terminal and fork to background 47 | .It Fl p 48 | Probe currently connected outputs and output EDID info 49 | .It Fl s 50 | Use syslog, even if running in foreground, default w/o 51 | .Fl n 52 | .It Fl v 53 | Show version information and exit 54 | .El 55 | .Pp 56 | The optional 57 | .Pa FILE 58 | argument defaults to 59 | .Pa $XDG_CONFIG_HOME/xplugrc , 60 | with fallbacks to 61 | .Pa ~/.config/xplugrc 62 | and 63 | .Pa ~/.xplugrc . 64 | The file is called as a script on plug events with the following 65 | arguments: 66 | .Bl -tag -width Ds -offset indent 67 | .It $1 = Ar TYPE 68 | One of 69 | .Ar display | keyboard | pointer 70 | .It $2 = Ar DEVICE 71 | Usually HDMI3, LVDS1, VGA1, or an 72 | .Xr xinput 1 73 | device number. 74 | .It $3 = Ar STATUS 75 | One of 76 | .Ar connected | disconnected | unknown 77 | .It $4 = Ar DESCRIPTION 78 | An optional description enclosed in double quotes, e.g., keyboard 79 | (manufacturer and) model name, or if EDID data is available from a 80 | connected display, the monitor model 81 | .El 82 | .Sh EXAMPLE 83 | Here is an example of how to use 84 | .Nm : 85 | .Bd -literal -offset indent 86 | xplugd 87 | .Ed 88 | .Pp 89 | When 90 | .Nm 91 | starts it looks for the file 92 | .Pa ~/.config/xplugrc , 93 | which may look something like this: 94 | .Bd -literal -offset indent 95 | #!/bin/sh 96 | 97 | type=$1 98 | device=$2 99 | status=$3 100 | desc=$4 101 | 102 | case "$type,$device,$status,$desc" in 103 | display,DVI-0,connected,*) 104 | xrandr --output DVI-0 --auto --right-of LVDS 105 | ;; 106 | display,VGA-0,connected,*) 107 | xrandr --output VGA-0 --auto --left-of LVDS 108 | ;; 109 | pointer,*,connected,"SynPS/2 Synaptics TouchPad") 110 | xinput set-prop $device 'Synaptics Off' 1 111 | ;; 112 | keyboard,*connected,*) 113 | setxkbmap -option ctrl:nocaps 114 | ;; 115 | esac 116 | .Ed 117 | .Sh FILES 118 | .Bl -tag -width $XDG_CONFIG_HOME/xplugrc -compact 119 | .It Pa $XDG_CONFIG_HOME/xplugrc 120 | Primary path 121 | .It Pa ~/.config/xplugrc 122 | Secondary path 123 | .It Pa ~/.xplugrc 124 | Fallback path, for compat with earlier releases 125 | .El 126 | .Sh SEE ALSO 127 | .Bl -tag -compact 128 | .It Aq http://bitbucket.org/portix/srandrd 129 | .It Aq https://bitbucket.org/andrew_shadura/inputplug 130 | .It Aq https://github.com/troglobit/xplugd 131 | .El 132 | .Sh AUTHORS 133 | .Nm 134 | was created by Joachim Wiberg in 2016 for use with window managers like 135 | .Xr awesome 1 136 | and 137 | .Xr i3 1 . 138 | It would however not exist if not for the hard work of Stefan Bolte with 139 | .Nm srandrd 140 | and Andrew Shadura with 141 | .Nm inputplug . 142 | -------------------------------------------------------------------------------- /src/Makefile.am: -------------------------------------------------------------------------------- 1 | bin_PROGRAMS = xplugd 2 | 3 | xplugd_SOURCES = xplugd.c xplugd.h exec.c input.c randr.c edid.c edid.h 4 | xplugd_CFLAGS = -W -Wall -Wextra -std=c99 -Wno-unused-parameter 5 | xplugd_CFLAGS += -D_POSIX_C_SOURCE=200809L -D_BSD_SOURCE -D_DEFAULT_SOURCE 6 | xplugd_CFLAGS += $(X11_CFLAGS) $(Xi_CFLAGS) $(Xrandr_CFLAGS) 7 | xplugd_LDADD = $(X11_LIBS) $(Xi_LIBS) $(Xrandr_LIBS) 8 | -------------------------------------------------------------------------------- /src/edid.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2007 Red Hat, Inc. 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a 5 | * copy of this software and associated documentation files (the "Software"), 6 | * to deal in the Software without restriction, including without limitation 7 | * on the rights to use, copy, modify, merge, publish, distribute, sub 8 | * license, and/or sell copies of the Software, and to permit persons to whom 9 | * the Software is furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice (including the next 12 | * paragraph) shall be included in all copies or substantial portions of the 13 | * Software. 14 | * 15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL 18 | * THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | */ 22 | 23 | /* Author: Soren Sandmann */ 24 | 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | 31 | #include "edid.h" 32 | 33 | #define TAB_LEN 8 34 | #define KEYWORD_LEN 13 // Max length of longest keyword (Currently Aspect Ratio) 35 | 36 | 37 | static int get_bit(int in, int bit) 38 | { 39 | return (in & (1 << bit)) >> bit; 40 | } 41 | 42 | static int get_bits(int in, int begin, int end) 43 | { 44 | int mask = (1 << (end - begin + 1)) - 1; 45 | 46 | return (in >> begin) & mask; 47 | } 48 | 49 | static int is_edid_header(const unsigned char *edid) 50 | { 51 | if (memcmp(edid, "\x00\xff\xff\xff\xff\xff\xff\x00", 8) == 0) 52 | return 1; 53 | 54 | return 0; 55 | } 56 | 57 | static int decode_vendor_and_product_identification(const unsigned char *edid, 58 | struct monitor_info *info) 59 | { 60 | int is_model_year; 61 | 62 | /* Manufacturer Code */ 63 | info->manufacturer_code[0] = get_bits(edid[0x08], 2, 6); 64 | info->manufacturer_code[1] = get_bits(edid[0x08], 0, 1) << 3; 65 | info->manufacturer_code[1] |= get_bits(edid[0x09], 5, 7); 66 | info->manufacturer_code[2] = get_bits(edid[0x09], 0, 4); 67 | info->manufacturer_code[3] = '\0'; 68 | 69 | info->manufacturer_code[0] += 'A' - 1; 70 | info->manufacturer_code[1] += 'A' - 1; 71 | info->manufacturer_code[2] += 'A' - 1; 72 | 73 | /* Product Code */ 74 | info->product_code = edid[0x0b] << 8 | edid[0x0a]; 75 | 76 | /* Serial Number */ 77 | info->serial_number = edid[0x0c] | edid[0x0d] << 8 | edid[0x0e] << 16 | edid[0x0f] << 24; 78 | 79 | /* Week and Year */ 80 | is_model_year = 0; 81 | switch (edid[0x10]) { 82 | case 0x00: 83 | info->production_week = -1; 84 | break; 85 | 86 | case 0xff: 87 | info->production_week = -1; 88 | is_model_year = 1; 89 | break; 90 | 91 | default: 92 | info->production_week = edid[0x10]; 93 | break; 94 | } 95 | 96 | if (is_model_year) { 97 | info->production_year = -1; 98 | info->model_year = 1990 + edid[0x11]; 99 | } else { 100 | info->production_year = 1990 + edid[0x11]; 101 | info->model_year = -1; 102 | } 103 | 104 | return 1; 105 | } 106 | 107 | static int decode_edid_version(const unsigned char *edid, struct monitor_info *info) 108 | { 109 | info->major_version = edid[0x12]; 110 | info->minor_version = edid[0x13]; 111 | 112 | return 1; 113 | } 114 | 115 | static int decode_display_parameters(const unsigned char *edid, struct monitor_info *info) 116 | { 117 | /* Digital vs Analog */ 118 | info->is_digital = get_bit(edid[0x14], 7); 119 | 120 | if (info->is_digital) { 121 | int bits; 122 | 123 | static const int bit_depth[8] = { 124 | -1, 6, 8, 10, 12, 14, 16, -1 125 | }; 126 | 127 | static const enum interface interfaces[6] = { 128 | UNDEFINED, DVI, HDMI_A, HDMI_B, MDDI, DISPLAY_PORT 129 | }; 130 | 131 | bits = get_bits(edid[0x14], 4, 6); 132 | info->digital.bits_per_primary = bit_depth[bits]; 133 | 134 | bits = get_bits(edid[0x14], 0, 3); 135 | 136 | if (bits <= 5) 137 | info->digital.interface = interfaces[bits]; 138 | else 139 | info->digital.interface = UNDEFINED; 140 | } else { 141 | int bits = get_bits(edid[0x14], 5, 6); 142 | 143 | static const double levels[][3] = { 144 | { 0.7, 0.3, 1.0 }, 145 | { 0.714, 0.286, 1.0 }, 146 | { 1.0, 0.4, 1.4 }, 147 | { 0.7, 0.0, 0.7 }, 148 | }; 149 | 150 | info->analog.video_signal_level = levels[bits][0]; 151 | info->analog.sync_signal_level = levels[bits][1]; 152 | info->analog.total_signal_level = levels[bits][2]; 153 | 154 | info->analog.blank_to_black = get_bit(edid[0x14], 4); 155 | 156 | info->analog.separate_hv_sync = get_bit(edid[0x14], 3); 157 | info->analog.composite_sync_on_h = get_bit(edid[0x14], 2); 158 | info->analog.composite_sync_on_green = get_bit(edid[0x14], 1); 159 | 160 | info->analog.serration_on_vsync = get_bit(edid[0x14], 0); 161 | } 162 | 163 | /* Screen Size / Aspect Ratio */ 164 | if (edid[0x15] == 0 && edid[0x16] == 0) { 165 | info->width_mm = -1; 166 | info->height_mm = -1; 167 | info->aspect_ratio = -1.0; 168 | } else if (edid[0x16] == 0) { 169 | info->width_mm = -1; 170 | info->height_mm = -1; 171 | info->aspect_ratio = 100.0 / (edid[0x15] + 99); 172 | } else if (edid[0x15] == 0) { 173 | info->width_mm = -1; 174 | info->height_mm = -1; 175 | info->aspect_ratio = 100.0 / (edid[0x16] + 99); 176 | info->aspect_ratio = 1 / info->aspect_ratio; /* portrait */ 177 | } else { 178 | info->width_mm = 10 * edid[0x15]; 179 | info->height_mm = 10 * edid[0x16]; 180 | } 181 | 182 | /* Gamma */ 183 | if (edid[0x17] == 0xFF) 184 | info->gamma = -1.0; 185 | else 186 | info->gamma = (edid[0x17] + 100.0) / 100.0; 187 | 188 | /* Features */ 189 | info->standby = get_bit(edid[0x18], 7); 190 | info->suspend = get_bit(edid[0x18], 6); 191 | info->active_off = get_bit(edid[0x18], 5); 192 | 193 | if (info->is_digital) { 194 | info->digital.rgb444 = 1; 195 | if (get_bit(edid[0x18], 3)) 196 | info->digital.ycrcb444 = 1; 197 | if (get_bit(edid[0x18], 4)) 198 | info->digital.ycrcb422 = 1; 199 | } else { 200 | int bits = get_bits(edid[0x18], 3, 4); 201 | 202 | enum color_type color_type[4] = { 203 | MONOCHROME, RGB, OTHER_COLOR, UNDEFINED_COLOR 204 | }; 205 | 206 | info->analog.color_type = color_type[bits]; 207 | } 208 | 209 | info->srgb_is_standard = get_bit(edid[0x18], 2); 210 | 211 | /* In 1.3 this is called "has preferred timing" */ 212 | info->preferred_timing_includes_native = get_bit(edid[0x18], 1); 213 | 214 | /* FIXME: In 1.3 this indicates whether the monitor accepts GTF */ 215 | info->continuous_frequency = get_bit(edid[0x18], 0); 216 | 217 | return 1; 218 | } 219 | 220 | static double decode_fraction(int high, int low) 221 | { 222 | double result = 0.0; 223 | int i; 224 | 225 | high = (high << 2) | low; 226 | 227 | for (i = 0; i < 10; ++i) 228 | result += get_bit(high, i) * pow(2, i - 10); 229 | 230 | return result; 231 | } 232 | 233 | static int decode_color_characteristics(const unsigned char *edid, struct monitor_info *info) 234 | { 235 | info->red_x = decode_fraction(edid[0x1b], get_bits(edid[0x19], 6, 7)); 236 | info->red_y = decode_fraction(edid[0x1c], get_bits(edid[0x19], 5, 4)); 237 | info->green_x = decode_fraction(edid[0x1d], get_bits(edid[0x19], 2, 3)); 238 | info->green_y = decode_fraction(edid[0x1e], get_bits(edid[0x19], 0, 1)); 239 | info->blue_x = decode_fraction(edid[0x1f], get_bits(edid[0x1a], 6, 7)); 240 | info->blue_y = decode_fraction(edid[0x20], get_bits(edid[0x1a], 4, 5)); 241 | info->white_x = decode_fraction(edid[0x21], get_bits(edid[0x1a], 2, 3)); 242 | info->white_y = decode_fraction(edid[0x22], get_bits(edid[0x1a], 0, 1)); 243 | 244 | return 1; 245 | } 246 | 247 | static int decode_established_timings(const unsigned char *edid, struct monitor_info *info) 248 | { 249 | static const struct timing established[][8] = { 250 | { 251 | { 800, 600, 60 }, 252 | { 800, 600, 56 }, 253 | { 640, 480, 75 }, 254 | { 640, 480, 72 }, 255 | { 640, 480, 67 }, 256 | { 640, 480, 60 }, 257 | { 720, 400, 88 }, 258 | { 720, 400, 70 } 259 | }, 260 | { 261 | { 1280, 1024, 75 }, 262 | { 1024, 768, 75 }, 263 | { 1024, 768, 70 }, 264 | { 1024, 768, 60 }, 265 | { 1024, 768, 87 }, 266 | { 832, 624, 75 }, 267 | { 800, 600, 75 }, 268 | { 800, 600, 72 } 269 | }, 270 | { 271 | { 0, 0, 0 }, 272 | { 0, 0, 0 }, 273 | { 0, 0, 0 }, 274 | { 0, 0, 0 }, 275 | { 0, 0, 0 }, 276 | { 0, 0, 0 }, 277 | { 0, 0, 0 }, 278 | { 1152, 870, 75 } 279 | }, 280 | }; 281 | 282 | int i, j, idx; 283 | 284 | idx = 0; 285 | for (i = 0; i < 3; ++i) { 286 | for (j = 0; j < 8; ++j) { 287 | int byte = edid[0x23 + i]; 288 | 289 | if (get_bit(byte, j) && established[i][j].frequency != 0) 290 | info->established[idx++] = established[i][j]; 291 | } 292 | } 293 | 294 | return 1; 295 | } 296 | 297 | static int decode_standard_timings(const unsigned char *edid, struct monitor_info *info) 298 | { 299 | int i; 300 | 301 | for (i = 0; i < 8; i++) { 302 | int first = edid[0x26 + 2 * i]; 303 | int second = edid[0x27 + 2 * i]; 304 | 305 | if (first != 0x01 && second != 0x01) { 306 | int w = 8 * (first + 31); 307 | int h; 308 | 309 | switch (get_bits(second, 6, 7)) { 310 | case 0x00: 311 | h = (w / 16) * 10; 312 | break; 313 | case 0x01: 314 | h = (w / 4) * 3; 315 | break; 316 | case 0x02: 317 | h = (w / 5) * 4; 318 | break; 319 | case 0x03: 320 | h = (w / 16) * 9; 321 | break; 322 | } 323 | 324 | info->standard[i].width = w; 325 | info->standard[i].height = h; 326 | info->standard[i].frequency = get_bits(second, 0, 5) + 60; 327 | } 328 | } 329 | 330 | return 1; 331 | } 332 | 333 | static void decode_lf_string(const unsigned char *s, int n_chars, char *result) 334 | { 335 | for (int i = 0; i < n_chars; ++i) { 336 | if (s[i] == 0x0a) { 337 | *result++ = '\0'; 338 | break; 339 | } 340 | 341 | if (s[i] == 0x00) 342 | /* Convert embedded 0's to spaces */ 343 | *result++ = ' '; 344 | else 345 | *result++ = s[i]; 346 | } 347 | } 348 | 349 | static void decode_display_descriptor(const unsigned char *desc, struct monitor_info *info) 350 | { 351 | switch (desc[0x03]) { 352 | case 0xFC: 353 | decode_lf_string(desc + 5, 13, info->dsc_product_name); 354 | break; 355 | 356 | case 0xFF: 357 | decode_lf_string(desc + 5, 13, info->dsc_serial_number); 358 | break; 359 | 360 | case 0xFE: 361 | decode_lf_string(desc + 5, 13, info->dsc_string); 362 | break; 363 | 364 | case 0xFD: 365 | /* Range Limits */ 366 | break; 367 | 368 | case 0xFB: 369 | /* Color Point */ 370 | break; 371 | 372 | case 0xFA: 373 | /* Timing Identifications */ 374 | break; 375 | 376 | case 0xF9: 377 | /* Color Management */ 378 | break; 379 | 380 | case 0xF8: 381 | /* Timing Codes */ 382 | break; 383 | 384 | case 0xF7: 385 | /* Established Timings */ 386 | break; 387 | 388 | case 0x10: 389 | break; 390 | } 391 | } 392 | 393 | static void decode_detailed_timing(const unsigned char *timing, struct detailed_timing * detailed) 394 | { 395 | int bits; 396 | 397 | enum stereo_type stereo[] = { 398 | NO_STEREO, NO_STEREO, FIELD_RIGHT, FIELD_LEFT, 399 | TWO_WAY_RIGHT_ON_EVEN, TWO_WAY_LEFT_ON_EVEN, 400 | FOUR_WAY_INTERLEAVED, SIDE_BY_SIDE 401 | }; 402 | 403 | detailed->pixel_clock = (timing[0x00] | timing[0x01] << 8) * 10000; 404 | detailed->h_addr = timing[0x02] | ((timing[0x04] & 0xf0) << 4); 405 | detailed->h_blank = timing[0x03] | ((timing[0x04] & 0x0f) << 8); 406 | detailed->v_addr = timing[0x05] | ((timing[0x07] & 0xf0) << 4); 407 | detailed->v_blank = timing[0x06] | ((timing[0x07] & 0x0f) << 8); 408 | detailed->h_front_porch = timing[0x08] | get_bits(timing[0x0b], 6, 7) << 8; 409 | detailed->h_sync = timing[0x09] | get_bits(timing[0x0b], 4, 5) << 8; 410 | detailed->v_front_porch = get_bits(timing[0x0a], 4, 7) | get_bits(timing[0x0b], 2, 3) << 4; 411 | detailed->v_sync = get_bits(timing[0x0a], 0, 3) | get_bits(timing[0x0b], 0, 1) << 4; 412 | detailed->width_mm = timing[0x0c] | get_bits(timing[0x0e], 4, 7) << 8; 413 | detailed->height_mm = timing[0x0d] | get_bits(timing[0x0e], 0, 3) << 8; 414 | detailed->right_border = timing[0x0f]; 415 | detailed->top_border = timing[0x10]; 416 | detailed->interlaced = get_bit(timing[0x11], 7); 417 | 418 | /* Stereo */ 419 | bits = get_bits(timing[0x11], 5, 6) << 1 | get_bit(timing[0x11], 0); 420 | detailed->stereo = stereo[bits]; 421 | 422 | /* Sync */ 423 | bits = timing[0x11]; 424 | 425 | detailed->digital_sync = get_bit(bits, 4); 426 | if (detailed->digital_sync) { 427 | detailed->digital.composite = !get_bit(bits, 3); 428 | 429 | if (detailed->digital.composite) { 430 | detailed->digital.serrations = get_bit(bits, 2); 431 | detailed->digital.negative_vsync = 0; 432 | } else { 433 | detailed->digital.serrations = 0; 434 | detailed->digital.negative_vsync = !get_bit(bits, 2); 435 | } 436 | 437 | detailed->digital.negative_hsync = !get_bit(bits, 0); 438 | } else { 439 | detailed->analog.bipolar = get_bit(bits, 3); 440 | detailed->analog.serrations = get_bit(bits, 2); 441 | detailed->analog.sync_on_green = !get_bit(bits, 1); 442 | } 443 | } 444 | 445 | static int decode_descriptors(const unsigned char *edid, struct monitor_info *info) 446 | { 447 | int i; 448 | int timing_idx; 449 | 450 | timing_idx = 0; 451 | 452 | for (i = 0; i < 4; ++i) { 453 | int index = 0x36 + i * 18; 454 | 455 | if (edid[index + 0] == 0x00 && edid[index + 1] == 0x00) { 456 | decode_display_descriptor(edid + index, info); 457 | } else { 458 | decode_detailed_timing(edid + index, &(info->detailed_timings[timing_idx++])); 459 | } 460 | } 461 | 462 | info->n_detailed_timings = timing_idx; 463 | 464 | return 1; 465 | } 466 | 467 | static void decode_checksum(const unsigned char *edid, struct monitor_info *info) 468 | { 469 | unsigned char check = 0; 470 | 471 | for (int i = 0; i < 128; ++i) 472 | check += edid[i]; 473 | 474 | info->checksum = check; 475 | } 476 | 477 | struct monitor_info *edid_decode(const unsigned char *edid) 478 | { 479 | if (!edid) { 480 | errno = EINVAL; 481 | return NULL; 482 | } 483 | 484 | struct monitor_info *info; 485 | 486 | info = calloc(1, sizeof(struct monitor_info)); 487 | if (!info) 488 | return NULL; 489 | 490 | decode_checksum(edid, info); 491 | 492 | if (!is_edid_header(edid)) 493 | goto error; 494 | 495 | if (!decode_vendor_and_product_identification(edid, info)) 496 | goto error; 497 | 498 | if (!decode_edid_version(edid, info)) 499 | goto error; 500 | 501 | if (!decode_display_parameters(edid, info)) 502 | goto error; 503 | 504 | if (!decode_color_characteristics(edid, info)) 505 | goto error; 506 | 507 | if (!decode_established_timings(edid, info)) 508 | goto error; 509 | 510 | if (!decode_standard_timings(edid, info)) 511 | goto error; 512 | 513 | if (!decode_descriptors(edid, info)) 514 | goto error; 515 | 516 | return info; 517 | 518 | error: 519 | free(info); 520 | errno = ENOENT; 521 | 522 | return NULL; 523 | } 524 | 525 | /** 526 | * Local Variables: 527 | * indent-tabs-mode: t 528 | * c-file-style: "linux" 529 | * End: 530 | */ 531 | -------------------------------------------------------------------------------- /src/edid.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2007 Red Hat, Inc. 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a 5 | * copy of this software and associated documentation files (the "Software"), 6 | * to deal in the Software without restriction, including without limitation 7 | * on the rights to use, copy, modify, merge, publish, distribute, sub 8 | * license, and/or sell copies of the Software, and to permit persons to whom 9 | * the Software is furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice (including the next 12 | * paragraph) shall be included in all copies or substantial portions of the 13 | * Software. 14 | * 15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL 18 | * THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | */ 22 | 23 | /* Author: Soren Sandmann */ 24 | 25 | enum interface { 26 | UNDEFINED, 27 | DVI, 28 | HDMI_A, 29 | HDMI_B, 30 | MDDI, 31 | DISPLAY_PORT 32 | }; 33 | 34 | enum color_type { 35 | UNDEFINED_COLOR, 36 | MONOCHROME, 37 | RGB, 38 | OTHER_COLOR 39 | }; 40 | 41 | enum stereo_type { 42 | NO_STEREO, 43 | FIELD_RIGHT, 44 | FIELD_LEFT, 45 | TWO_WAY_RIGHT_ON_EVEN, 46 | TWO_WAY_LEFT_ON_EVEN, 47 | FOUR_WAY_INTERLEAVED, 48 | SIDE_BY_SIDE 49 | }; 50 | 51 | struct timing { 52 | int width; 53 | int height; 54 | int frequency; 55 | }; 56 | 57 | struct detailed_timing { 58 | int pixel_clock; 59 | int h_addr; 60 | int h_blank; 61 | int h_sync; 62 | int h_front_porch; 63 | int v_addr; 64 | int v_blank; 65 | int v_sync; 66 | int v_front_porch; 67 | int width_mm; 68 | int height_mm; 69 | int right_border; 70 | int top_border; 71 | int interlaced; 72 | enum stereo_type stereo; 73 | 74 | int digital_sync; 75 | union { 76 | struct { 77 | int bipolar; 78 | int serrations; 79 | int sync_on_green; 80 | } analog; 81 | 82 | struct { 83 | int composite; 84 | int serrations; 85 | int negative_vsync; 86 | int negative_hsync; 87 | } digital; 88 | }; 89 | }; 90 | 91 | struct monitor_info { 92 | int checksum; 93 | char manufacturer_code[4]; 94 | int product_code; 95 | unsigned int serial_number; 96 | 97 | int production_week; /* -1 if not specified */ 98 | int production_year; /* -1 if not specified */ 99 | int model_year; /* -1 if not specified */ 100 | 101 | int major_version; 102 | int minor_version; 103 | 104 | int is_digital; 105 | 106 | union { 107 | struct { 108 | int bits_per_primary; 109 | enum interface interface; 110 | int rgb444; 111 | int ycrcb444; 112 | int ycrcb422; 113 | } digital; 114 | 115 | struct { 116 | double video_signal_level; 117 | double sync_signal_level; 118 | double total_signal_level; 119 | 120 | int blank_to_black; 121 | 122 | int separate_hv_sync; 123 | int composite_sync_on_h; 124 | int composite_sync_on_green; 125 | int serration_on_vsync; 126 | enum color_type color_type; 127 | } analog; 128 | }; 129 | 130 | int width_mm; /* -1 if not specified */ 131 | int height_mm; /* -1 if not specified */ 132 | double aspect_ratio; /* -1.0 if not specififed */ 133 | 134 | double gamma; /* -1.0 if not specified */ 135 | 136 | int standby; 137 | int suspend; 138 | int active_off; 139 | 140 | int srgb_is_standard; 141 | int preferred_timing_includes_native; 142 | int continuous_frequency; 143 | 144 | double red_x; 145 | double red_y; 146 | double green_x; 147 | double green_y; 148 | double blue_x; 149 | double blue_y; 150 | double white_x; 151 | double white_y; 152 | 153 | struct timing established[24]; /* Terminated by 0x0x0 */ 154 | struct timing standard[8]; 155 | 156 | /* 157 | * If monitor has a preferred mode, it is the first one (whether it has, 158 | * is determined by the preferred_timing_includes bit. 159 | */ 160 | int n_detailed_timings; 161 | struct detailed_timing detailed_timings[4]; 162 | 163 | /* Optional product description */ 164 | char dsc_serial_number[14]; 165 | char dsc_product_name[14]; 166 | char dsc_string[14]; /* Unspecified ASCII data */ 167 | }; 168 | 169 | struct monitor_info *edid_decode(const unsigned char *data); 170 | 171 | /** 172 | * Local Variables: 173 | * indent-tabs-mode: t 174 | * c-file-style: "linux" 175 | * End: 176 | */ 177 | -------------------------------------------------------------------------------- /src/exec.c: -------------------------------------------------------------------------------- 1 | /* Script exec handling 2 | * 3 | * Copyright (C) 2016-2023 Joachim Wiberg 4 | * 5 | * Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | * this software and associated documentation files (the "Software"), to deal in 7 | * the Software without restriction, including without limitation the rights to 8 | * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | * the Software, and to permit persons to whom the Software is furnished to do so, 10 | * subject to the following conditions: 11 | * 12 | * The above copyright notice and this permission notice shall be included in all 13 | * copies or substantial portions of the Software. 14 | * 15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | */ 22 | 23 | #include "xplugd.h" 24 | 25 | static Display *display = NULL; 26 | 27 | static void catch_child(int sig) 28 | { 29 | pid_t pid; 30 | 31 | (void)sig; 32 | while ((pid = waitpid(-1, NULL, WNOHANG)) > 0) 33 | syslog(LOG_DEBUG, "Collected PID %d", pid); 34 | } 35 | 36 | int exec_init(Display *dpy) 37 | { 38 | struct sigaction sa = { 39 | .sa_flags = SA_RESTART, 40 | .sa_handler = catch_child, 41 | }; 42 | 43 | display = dpy; 44 | sigaction(SIGCHLD, &sa, NULL); 45 | 46 | return 0; 47 | } 48 | 49 | int exec(char *type, char *device, char *status, char *name) 50 | { 51 | pid_t pid; 52 | 53 | syslog(LOG_DEBUG, "Calling %s %s %s %s %s", cmd, type, device, status, name ? name : ""); 54 | 55 | pid = fork(); 56 | if (!pid) { 57 | char *args[] = { 58 | cmd, 59 | type, 60 | device, 61 | status, 62 | name ? name : "", 63 | NULL 64 | }; 65 | 66 | setsid(); 67 | if (display) 68 | close(ConnectionNumber(display)); 69 | 70 | execv(args[0], args); 71 | syslog(LOG_ERR, "Failed calling %s: %s", cmd, strerror(errno)); 72 | exit(0); 73 | } 74 | 75 | syslog(LOG_DEBUG, "Started %s as PID %d", cmd, pid); 76 | 77 | return 0; 78 | } 79 | 80 | /** 81 | * Local Variables: 82 | * indent-tabs-mode: t 83 | * c-file-style: "linux" 84 | * End: 85 | */ 86 | -------------------------------------------------------------------------------- /src/input.c: -------------------------------------------------------------------------------- 1 | /* x input handler 2 | * 3 | * Copyright (C) 2013-2015 Andrew Shadura 4 | * 5 | * Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | * this software and associated documentation files (the "Software"), to deal in 7 | * the Software without restriction, including without limitation the rights to 8 | * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | * the Software, and to permit persons to whom the Software is furnished to do so, 10 | * subject to the following conditions: 11 | * 12 | * The above copyright notice and this permission notice shall be included in all 13 | * copies or substantial portions of the Software. 14 | * 15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | */ 22 | 23 | #include "xplugd.h" 24 | 25 | #define T(x) {x, #x} 26 | #define T_END {0, NULL} 27 | 28 | #define STRINGIFY(x) #x 29 | #define EXPAND_STRINGIFY(x) STRINGIFY(x) 30 | #define UINT_MAX_STRING EXPAND_STRINGIFY(UINT_MAX) 31 | 32 | struct pair { 33 | int key; 34 | char *value; 35 | }; 36 | 37 | const struct pair device_types[] = { 38 | T(XIMasterPointer), 39 | T(XIMasterKeyboard), 40 | {XISlavePointer, "pointer"}, 41 | {XISlaveKeyboard, "keyboard"}, 42 | T(XIFloatingSlave), 43 | T_END 44 | }; 45 | 46 | const struct pair changes[] = { 47 | T(XIMasterAdded), 48 | T(XIMasterRemoved), 49 | T(XISlaveAdded), 50 | T(XISlaveRemoved), 51 | T(XISlaveAttached), 52 | T(XISlaveDetached), 53 | {XIDeviceEnabled, "connected"}, 54 | {XIDeviceDisabled, "disconnected"}, 55 | T_END 56 | }; 57 | 58 | static int xi_opcode = -1; 59 | 60 | static const struct pair *map(int key, const struct pair *table, bool strict) 61 | { 62 | if (!table) 63 | return NULL; 64 | 65 | while (table->value) { 66 | if ((!strict && (table->key & key)) || (table->key == key)) 67 | return table; 68 | 69 | table++; 70 | } 71 | 72 | return NULL; 73 | } 74 | 75 | static int handle_device(int id, int type, int flags, char *name) 76 | { 77 | const struct pair *use = map(type, device_types, true); 78 | const struct pair *change = map(flags, changes, false); 79 | 80 | if (change) { 81 | char deviceid[strlen(UINT_MAX_STRING) + 1]; 82 | 83 | if (type != XISlavePointer && type != XISlaveKeyboard) { 84 | syslog(LOG_DEBUG, "Skipping dev %d type %s flags %s name %s", id, use ? use->value : "", change->value, name ? name : ""); 85 | return 0; 86 | } 87 | if (flags != XIDeviceEnabled && flags != XIDeviceDisabled) { 88 | syslog(LOG_DEBUG, "Skipping dev %d type %s flags %s name %s", id, use ? use->value : "", change->value, name ? name : ""); 89 | return 0; 90 | } 91 | 92 | snprintf(deviceid, sizeof(deviceid), "%d", id); 93 | exec(use->value, deviceid, change->value, name); 94 | 95 | return change->key; 96 | } 97 | 98 | return -1; 99 | } 100 | 101 | static char *get_device_name(Display *display, int deviceid) 102 | { 103 | XIDeviceInfo *info; 104 | int i, num_devices; 105 | char *name = NULL; 106 | 107 | info = XIQueryDevice(display, XIAllDevices, &num_devices); 108 | if (!info) 109 | return NULL; 110 | 111 | for (i = 0; i < num_devices; i++) { 112 | if (info[i].deviceid == deviceid) { 113 | name = strdup(info[i].name); 114 | break; 115 | } 116 | } 117 | XIFreeDeviceInfo(info); 118 | 119 | return name; 120 | } 121 | 122 | static void handle_event(XIHierarchyEvent *event) 123 | { 124 | int i; 125 | 126 | for (i = 0; i < event->num_info; i++) { 127 | int flags = event->info[i].flags; 128 | int j = 16; 129 | 130 | while (flags && j) { 131 | int ret = 0; 132 | char *name; 133 | 134 | name = get_device_name(event->display, event->info[i].deviceid); 135 | ret = handle_device(event->info[i].deviceid, event->info[i].use, flags, name); 136 | free(name); 137 | if (ret == -1) 138 | break; 139 | 140 | j--; 141 | flags -= ret; 142 | } 143 | } 144 | } 145 | 146 | int input_init(Display *dpy) 147 | { 148 | XIEventMask mask; 149 | int event, error; 150 | 151 | if (!XQueryExtension(dpy, "XInputExtension", &xi_opcode, &event, &error)) { 152 | syslog(LOG_ERR, "X Input extension not available\n"); 153 | exit(1); 154 | } 155 | 156 | mask.deviceid = XIAllDevices; 157 | mask.mask_len = XIMaskLen(XI_LASTEVENT); 158 | mask.mask = calloc(mask.mask_len, sizeof(char)); 159 | if (!mask.mask) { 160 | syslog(LOG_ERR, "Failed initializing X input module: %s", strerror(errno)); 161 | exit(1); 162 | } 163 | 164 | XISetMask(mask.mask, XI_HierarchyChanged); 165 | XISelectEvents(dpy, DefaultRootWindow(dpy), &mask, 1); 166 | 167 | return 0; 168 | } 169 | 170 | int is_input_event(Display *dpy, XEvent *ev) 171 | { 172 | XGenericEventCookie *c = (XGenericEventCookie*)&ev->xcookie; 173 | 174 | if (!XGetEventData(dpy, c)) 175 | return 0; 176 | 177 | if (c->type == GenericEvent && c->extension == xi_opcode && c->evtype == XI_HierarchyChanged) 178 | return 1; 179 | 180 | XFreeEventData(dpy, c); 181 | 182 | return 0; 183 | } 184 | 185 | int input_event(Display *dpy, XEvent *ev) 186 | { 187 | XGenericEventCookie *c = (XGenericEventCookie*)&ev->xcookie; 188 | 189 | handle_event(c->data); 190 | XFreeEventData(dpy, c); 191 | 192 | return 0; 193 | } 194 | 195 | /** 196 | * Local Variables: 197 | * indent-tabs-mode: t 198 | * c-file-style: "linux" 199 | * End: 200 | */ 201 | -------------------------------------------------------------------------------- /src/randr.c: -------------------------------------------------------------------------------- 1 | /* x randr handler 2 | * 3 | * Copyright (C) 2012-2015 Stefan Bolte 4 | * Copyright (C) 2016-2023 Joachim Wiberg 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy of 7 | * this software and associated documentation files (the "Software"), to deal in 8 | * the Software without restriction, including without limitation the rights to 9 | * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 10 | * the Software, and to permit persons to whom the Software is furnished to do so, 11 | * subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 18 | * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 19 | * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 20 | * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 21 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | */ 23 | 24 | #include "xplugd.h" 25 | #include "edid.h" 26 | 27 | static char *con_actions[] = { "connected", "disconnected", "unknown" }; 28 | 29 | static char color_type_names[4][16] = { 30 | "Undefined", 31 | "Monochrome", 32 | "RGB", 33 | "Other" 34 | }; 35 | 36 | static char iface_type_names[6][16] = { 37 | "Undefined", 38 | "DVI", 39 | "HDMI-A", 40 | "HDMI-B", 41 | "MDDI", 42 | "Display Port" 43 | }; 44 | 45 | static struct monitor_info *edid_info(Display *dpy, XID output, Atom prop) 46 | { 47 | unsigned long nitems, bytes_after; 48 | unsigned char *data; 49 | Atom actual_type; 50 | int actual_format; 51 | 52 | XRRGetOutputProperty(dpy, output, prop, 0, 128, False, False, 53 | AnyPropertyType, &actual_type, &actual_format, &nitems, &bytes_after, &data); 54 | 55 | if (nitems < 128) { 56 | syslog(LOG_INFO, "Not enough EDID data found. Need at least 128 bytes, got %lu bytes", nitems); 57 | return NULL; 58 | } 59 | 60 | return edid_decode(data); 61 | } 62 | 63 | static void edid_desc(Display *dpy, XRRScreenResources *res, const char *output, char *buf, size_t len) 64 | { 65 | struct monitor_info *info = NULL; 66 | Atom *p; 67 | int i; 68 | 69 | for (i = 0; i < res->noutput; ++i) { 70 | XRROutputInfo *output_info; 71 | int j, np; 72 | 73 | output_info = XRRGetOutputInfo(dpy, res, res->outputs[i]); 74 | if (!output_info) 75 | continue; 76 | 77 | if (output_info->connection != RR_Connected) 78 | continue; 79 | 80 | if (strcmp(output_info->name, output)) 81 | continue; 82 | 83 | p = XRRListOutputProperties(dpy, res->outputs[i], &np); 84 | for (j = 0; j < np; ++j) { 85 | if (strcmp(XGetAtomName(dpy, p[j]), "EDID")) 86 | continue; 87 | 88 | info = edid_info(dpy, res->outputs[i], p[j]); 89 | } 90 | break; 91 | } 92 | 93 | if (!info) { 94 | syslog(LOG_INFO, "Failed decoding EDID data: %s", strerror(errno)); 95 | return; 96 | } 97 | 98 | syslog(LOG_DEBUG, "MODEL: %s S/N: %s EXTRA: %s", 99 | info->dsc_product_name, info->dsc_serial_number, info->dsc_string); 100 | strncpy(buf, info->dsc_product_name, len); 101 | free(info); 102 | } 103 | 104 | static void handle_event(Display *dpy, XRROutputChangeNotifyEvent *ev) 105 | { 106 | static char old_msg[MSG_LEN] = ""; 107 | XRRScreenResources *res; 108 | XRROutputInfo *info; 109 | char desc[14] = { 0 }; 110 | char msg[MSG_LEN]; 111 | 112 | res = XRRGetScreenResources(ev->display, ev->window); 113 | if (!res) { 114 | syslog(LOG_ERR, "Could not get screen resources"); 115 | return; 116 | } 117 | 118 | info = XRRGetOutputInfo(ev->display, res, ev->output); 119 | if (!info) { 120 | syslog(LOG_ERR, "Could not get output info"); 121 | XRRFreeScreenResources(res); 122 | return; 123 | } 124 | 125 | /* Check for duplicate plug events */ 126 | snprintf(msg, sizeof(msg), "%s %s", info->name, con_actions[info->connection]); 127 | if (!strcmp(msg, old_msg)) { 128 | if (loglevel == LOG_DEBUG) 129 | syslog(LOG_DEBUG, "Same message as last time, time %lu, skipping ...", info->timestamp); 130 | goto done; 131 | } 132 | strcpy(old_msg, msg); 133 | 134 | if (loglevel == LOG_DEBUG) { 135 | syslog(LOG_DEBUG, "Event: %s %s", info->name, con_actions[info->connection]); 136 | syslog(LOG_DEBUG, "Time: %lu", info->timestamp); 137 | if (info->crtc == 0) { 138 | syslog(LOG_DEBUG, "Size: %lumm x %lumm", info->mm_width, info->mm_height); 139 | } else { 140 | XRRCrtcInfo *crtc; 141 | 142 | syslog(LOG_DEBUG, "CRTC: %lu", info->crtc); 143 | 144 | crtc = XRRGetCrtcInfo(dpy, res, info->crtc); 145 | if (crtc) { 146 | syslog(LOG_DEBUG, "Size: %dx%d", crtc->width, crtc->height); 147 | XRRFreeCrtcInfo(crtc); 148 | } 149 | } 150 | } 151 | 152 | if (!strcmp(con_actions[info->connection], "connected")) 153 | edid_desc(dpy, res, info->name, desc, sizeof(desc)); 154 | 155 | exec("display", info->name, con_actions[info->connection], desc); 156 | done: 157 | XRRFreeOutputInfo(info); 158 | XRRFreeScreenResources(res); 159 | } 160 | 161 | int randr_init(Display *dpy) 162 | { 163 | XRRSelectInput(dpy, DefaultRootWindow(dpy), RROutputChangeNotifyMask); 164 | return 0; 165 | } 166 | 167 | int randr_event(Display *dpy, XEvent *ev) 168 | { 169 | handle_event(dpy, (XRROutputChangeNotifyEvent *)ev); 170 | return 0; 171 | } 172 | 173 | #define NA "N/A" 174 | #define PRINT_STR(str) printf("%s\n", str ? strlen(str) > 0 ? str : NA : NA) 175 | #define PRINT_BOOL(val) printf("%s\n", val ? "Yes" : "No") 176 | #define PRINT_INT(val) if (val > 0) printf("%d\n", val); else printf("%s\n", NA) 177 | #define PRINT_FLOAT(val) if (val > 0.0) printf("%G\n", val); else printf("%s\n", NA) 178 | 179 | int randr_probe(Display *dpy) 180 | { 181 | struct monitor_info *info; 182 | XRRScreenResources *res; 183 | Window root; 184 | Atom *p; 185 | int i; 186 | 187 | root = RootWindow(dpy, DefaultScreen(dpy)); 188 | res = XRRGetScreenResources(dpy, root); 189 | if (!res) 190 | return 1; 191 | 192 | for (i = 0; i < res->noutput; ++i) { 193 | XRROutputInfo *output_info; 194 | int j, np; 195 | 196 | output_info = XRRGetOutputInfo(dpy, res, res->outputs[i]); 197 | if (!output_info) 198 | continue; 199 | 200 | if (output_info->connection != RR_Connected) 201 | continue; 202 | 203 | p = XRRListOutputProperties(dpy, res->outputs[i], &np); 204 | for (j = 0; j < np; ++j) { 205 | if (strcmp(XGetAtomName(dpy, p[j]), "EDID")) 206 | continue; 207 | 208 | info = edid_info(dpy, res->outputs[i], p[j]); 209 | if (!info) { 210 | printf("No EDID info for output %s\n", output_info->name); 211 | break; 212 | } 213 | 214 | printf("%s\n", output_info->name); 215 | printf(" Model : "); PRINT_STR(info->dsc_product_name); 216 | printf(" Serial Nr. : "); PRINT_STR(info->dsc_serial_number); 217 | printf(" Width : "); PRINT_INT(info->width_mm); 218 | printf(" Height : "); PRINT_INT(info->height_mm); 219 | printf(" Aspect Ratio : "); PRINT_FLOAT(info->aspect_ratio); 220 | printf(" Gamma : "); PRINT_FLOAT(info->gamma); 221 | printf(" Prod. Year : "); PRINT_INT(info->production_year); 222 | printf(" Prod. Week : "); PRINT_INT(info->production_week); 223 | printf(" Model Year : "); PRINT_INT(info->model_year); 224 | printf(" Extra : "); PRINT_STR(info->dsc_string); 225 | 226 | printf(" DPMS\n"); 227 | printf(" Standby : "); PRINT_BOOL(info->standby); 228 | printf(" Suspend : "); PRINT_BOOL(info->suspend); 229 | printf(" Active Off : "); PRINT_BOOL(info->active_off); 230 | 231 | if (info->is_digital) { 232 | printf(" Interface : "); PRINT_STR(iface_type_names[info->digital.interface]); 233 | printf(" Display Type : (digital)\n"); 234 | printf(" RGB 4:4:4 : "); PRINT_BOOL(info->digital.rgb444); 235 | printf(" YCrCb 4:4:4 : "); PRINT_BOOL(info->digital.ycrcb444); 236 | printf(" YCrCb 4:2:2 : "); PRINT_BOOL(info->digital.ycrcb422); 237 | } else { 238 | printf(" Display Type : (analog)\n"); 239 | printf(" : "); PRINT_STR(color_type_names[info->analog.color_type]); 240 | } 241 | 242 | printf(" EDID Version : %d.%d\n", info->major_version, info->minor_version); 243 | break; 244 | } 245 | } 246 | 247 | return 0; 248 | } 249 | 250 | /** 251 | * Local Variables: 252 | * indent-tabs-mode: t 253 | * c-file-style: "linux" 254 | * End: 255 | */ 256 | -------------------------------------------------------------------------------- /src/xplugd.c: -------------------------------------------------------------------------------- 1 | /* xplugd - monitor/keyboard/mouse plug/unplug helper 2 | * 3 | * Copyright (C) 2012-2015 Stefan Bolte 4 | * Copyright (C) 2013-2015 Andrew Shadura 5 | * Copyright (C) 2016-2023 Joachim Wiberg 6 | * 7 | * Permission is hereby granted, free of charge, to any person obtaining a copy of 8 | * this software and associated documentation files (the "Software"), to deal in 9 | * the Software without restriction, including without limitation the rights to 10 | * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 11 | * the Software, and to permit persons to whom the Software is furnished to do so, 12 | * subject to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be included in all 15 | * copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 19 | * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 20 | * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 21 | * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 22 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | */ 24 | 25 | #define SYSLOG_NAMES 26 | #include 27 | #ifndef GLOB_TILDE 28 | # include 29 | #endif 30 | #include "xplugd.h" 31 | 32 | int loglevel = LOG_NOTICE; 33 | char *cmd; 34 | char *prognm; 35 | 36 | static char *tilde_expand(char *path) 37 | { 38 | glob_t gl; 39 | char *rc; 40 | int flags = GLOB_ERR; 41 | 42 | #ifdef GLOB_TILDE 43 | /* E.g. musl libc < 1.1.21 does not have this GNU LIBC extension */ 44 | flags |= GLOB_TILDE; 45 | #else 46 | /* Simple homegrown replacement that at least handles leading ~/ */ 47 | if (!strncmp(path, "~/", 2)) { 48 | size_t len; 49 | char *buf, *home; 50 | 51 | home = getenv("HOME"); 52 | if (!home) 53 | return NULL; 54 | 55 | len = strlen(home) + strlen(path); 56 | buf = alloca(len); 57 | 58 | snprintf(buf, len, "%s/%s", home, &path[2]); 59 | path = buf; 60 | } 61 | #endif 62 | 63 | if (glob(path, flags, NULL, &gl)) 64 | return NULL; 65 | 66 | if (gl.gl_pathc < 1) 67 | return NULL; 68 | 69 | rc = strdup(gl.gl_pathv[0]); 70 | globfree(&gl); 71 | 72 | return rc; 73 | } 74 | 75 | /* 76 | * Returns the ~/ expanded path to the rc file. If no user supplied 77 | * path exists the following paths are checked: 78 | * - $XDG_CONFIG_HOME/xplugrc 79 | * - ~/.config/xplugrc 80 | * - ~/.xplugrc 81 | */ 82 | static char *rcfile(char *arg) 83 | { 84 | char *home; 85 | 86 | if (arg) 87 | return tilde_expand(arg); 88 | 89 | home = getenv("XDG_CONFIG_HOME"); 90 | if (home) { 91 | size_t len = strlen(home) + 9; 92 | char *path; 93 | 94 | path = malloc(len); 95 | if (!path) 96 | return NULL; 97 | 98 | snprintf(path, len, "%s/xplugrc", home); 99 | arg = tilde_expand(path); 100 | } 101 | 102 | if (!arg) 103 | arg = tilde_expand(XPLUGRC); 104 | 105 | if (!arg) 106 | arg = tilde_expand(XPLUGRC_FALLBACK); 107 | 108 | return arg; 109 | } 110 | 111 | static int loglvl(char *level) 112 | { 113 | for (int i = 0; prioritynames[i].c_name; i++) { 114 | if (!strcmp(prioritynames[i].c_name, level)) 115 | return prioritynames[i].c_val; 116 | } 117 | 118 | return atoi(level); 119 | } 120 | 121 | static int error_handler(Display *display) 122 | { 123 | exit(1); 124 | } 125 | 126 | static int usage(int status) 127 | { 128 | printf("Usage: %s [-hnpsv] [-l LEVEL] [FILE]\n\n" 129 | "Options:\n" 130 | " -h Print this help text and exit\n" 131 | " -l LEVEL Set log level: none, err, info, notice*, debug\n" 132 | " -n Run in foreground, do not fork to background\n" 133 | " -p Probe currently connected outputs and output EDID info\n" 134 | " -s Use syslog, even if running in foreground, default w/o -n\n" 135 | " -v Show program version\n" 136 | "\n" 137 | " FILE Optional script argument, default $XDG_CONFIG_HOME/xplugrc\n" 138 | " Fallback also checks for ~/.config/xplugrc and ~/.xplugrc\n" 139 | "\n" 140 | "Copyright (C) 2012-2015 Stefan Bolte\n" 141 | "Copyright (C) 2016-2023 Joachim Wiberg\n\n" 142 | "Bug report address: %s\n", prognm, PACKAGE_BUGREPORT); 143 | return status; 144 | } 145 | 146 | static int version(void) 147 | { 148 | printf("v%s\n", PACKAGE_VERSION); 149 | return 0; 150 | } 151 | 152 | static char *progname(char *arg0) 153 | { 154 | char *nm; 155 | 156 | nm = strrchr(arg0, '/'); 157 | if (nm) 158 | nm++; 159 | else 160 | nm = arg0; 161 | 162 | return nm; 163 | } 164 | 165 | int main(int argc, char *argv[]) 166 | { 167 | Display *dpy; 168 | XEvent ev; 169 | char *arg = NULL; 170 | int background = 1; 171 | int log_opts = LOG_CONS | LOG_PID; 172 | int logcons = 0; 173 | int mode = 0; 174 | int c; 175 | 176 | prognm = progname(argv[0]); 177 | while ((c = getopt(argc, argv, "hl:npsv")) != EOF) { 178 | switch (c) { 179 | case 'h': 180 | return usage(0); 181 | 182 | case 'l': 183 | loglevel = loglvl(optarg); 184 | break; 185 | 186 | case 'n': 187 | background = 0; 188 | logcons++; 189 | break; 190 | 191 | case 'p': 192 | mode = 1; 193 | break; 194 | 195 | case 's': 196 | logcons--; 197 | break; 198 | 199 | case 'v': 200 | return version(); 201 | 202 | default: 203 | return usage(1); 204 | } 205 | } 206 | 207 | dpy = XOpenDisplay(NULL); 208 | if (dpy == NULL) { 209 | fprintf(stderr, "Cannot open display\n"); 210 | exit(1); 211 | } 212 | 213 | if (mode) 214 | return randr_probe(dpy); 215 | 216 | if (optind < argc) 217 | arg = argv[optind]; 218 | 219 | cmd = rcfile(arg); 220 | if (!cmd) 221 | return usage(1); 222 | 223 | if (background) { 224 | if (daemon(0, 0)) { 225 | fprintf(stderr, "Failed backgrounding %s: %s", prognm, strerror(errno)); 226 | exit(1); 227 | } 228 | } 229 | if (logcons > 0) 230 | log_opts |= LOG_PERROR; 231 | 232 | openlog(prognm, log_opts, LOG_USER); 233 | setlogmask(LOG_UPTO(loglevel)); 234 | 235 | exec_init(dpy); 236 | input_init(dpy); 237 | randr_init(dpy); 238 | XSync(dpy, False); 239 | XSetIOErrorHandler((XIOErrorHandler)error_handler); 240 | 241 | while (1) { 242 | XNextEvent(dpy, &ev); 243 | 244 | if (is_input_event(dpy, &ev)) 245 | input_event(dpy, &ev); 246 | else 247 | randr_event(dpy, &ev); 248 | } 249 | 250 | return 0; 251 | } 252 | 253 | /** 254 | * Local Variables: 255 | * indent-tabs-mode: t 256 | * c-file-style: "linux" 257 | * End: 258 | */ 259 | -------------------------------------------------------------------------------- /src/xplugd.h: -------------------------------------------------------------------------------- 1 | /* xplugd - monitor/keyboard/mouse plug/unplug helper 2 | * 3 | * Copyright (C) 2012-2015 Stefan Bolte 4 | * Copyright (C) 2013-2015 Andrew Shadura 5 | * Copyright (C) 2016-2023 Joachim Wiberg 6 | * 7 | * Permission is hereby granted, free of charge, to any person obtaining a copy of 8 | * this software and associated documentation files (the "Software"), to deal in 9 | * the Software without restriction, including without limitation the rights to 10 | * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 11 | * the Software, and to permit persons to whom the Software is furnished to do so, 12 | * subject to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be included in all 15 | * copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 19 | * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 20 | * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 21 | * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 22 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | */ 24 | 25 | #ifndef XPLUGD_H_ 26 | #define XPLUGD_H_ 27 | 28 | #include "config.h" 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | #include 43 | 44 | #define MSG_LEN 128 45 | #define XPLUGRC "~/.config/xplugrc" 46 | #define XPLUGRC_FALLBACK "~/.xplugrc" 47 | 48 | extern int loglevel; 49 | extern char *cmd; 50 | extern char *prognm; 51 | 52 | int exec_init (Display *dpy); 53 | int exec (char *type, char *device, char *status, char *name); 54 | 55 | int input_init (Display *dpy); 56 | int is_input_event (Display *dpy, XEvent *ev); 57 | int input_event (Display *dpy, XEvent *ev); 58 | 59 | int randr_init (Display *dpy); 60 | int randr_event (Display *dpy, XEvent *ev); 61 | int randr_probe (Display *dpy); 62 | 63 | #endif /* XPLUGD_H_ */ 64 | 65 | /** 66 | * Local Variables: 67 | * indent-tabs-mode: t 68 | * c-file-style: "linux" 69 | * End: 70 | */ 71 | -------------------------------------------------------------------------------- /xplugrc: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # If not DOCK'ed output or LAPTOP, then it's likely VGA or Display Port 3 | # used for presenation. We assume the primary desktop screen (HDMI3) is 4 | # placed left of the internal laptop screen, while during presentations 5 | # the projector is on our right and the laptop screen is the primary. 6 | 7 | LAPTOP=LVDS1 8 | if [ `hostname` = "carbon" ]; then 9 | DOCK=DVI-I-1 10 | DESKPOS=--right-of 11 | PRESPOS=--left-of 12 | else 13 | DOCK=HDMI3 14 | DESKPOS=--left-of 15 | PRESPOS=--right-of 16 | fi 17 | 18 | TYPE=$1 19 | DEVICE=$2 20 | STATUS=$3 21 | shift 3 22 | DESC=$* 23 | 24 | if [ "$TYPE" != "display" ]; then 25 | case "$TYPE,$STATUS,$DESC" in 26 | pointer,connected,"SynPS/2 Synaptics TouchPad") 27 | xinput set-prop $DEVICE 'Synaptics Off' 1 28 | ;; 29 | keyboard,connected,*) 30 | setxkbmap -option ctrl:nocaps 31 | ;; 32 | esac 33 | exit 0 34 | fi 35 | 36 | if [ "$STATUS" = "disconnected" ]; then 37 | xrandr --output $DEVICE --off 38 | exit 0 39 | fi 40 | 41 | if [ "$DEVICE" = "${DOCK}" ]; then 42 | xrandr --output $DEVICE --auto --primary ${DESKPOS} ${LAPTOP} 43 | elif [ "$DEVICE" != "${LAPTOP}" ]; then 44 | xrandr --output $DEVICE --auto ${PRESPOS} ${LAPTOP} --primary 45 | else 46 | xrandr --auto 47 | fi 48 | 49 | --------------------------------------------------------------------------------