summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--krebs/2configs/news.nix62
-rw-r--r--krebs/3modules/external/default.nix25
-rw-r--r--krebs/5pkgs/haskell/X11-aeson.nix13
-rw-r--r--krebs/5pkgs/haskell/hack.nix22
-rw-r--r--krebs/5pkgs/haskell/xmonad-aeson.nix13
-rw-r--r--krebs/5pkgs/simple/realwallpaper/default.nix8
-rw-r--r--krebs/nixpkgs-unstable.json8
-rw-r--r--krebs/nixpkgs.json8
-rw-r--r--lass/2configs/realwallpaper.nix2
-rw-r--r--lass/2configs/syncthing.nix2
-rw-r--r--lib/default.nix4
-rw-r--r--tv/2configs/gitrepos.nix6
-rw-r--r--tv/2configs/hw/w110er.nix2
-rw-r--r--tv/5pkgs/haskell/xmonad-tv/src/Paths.hs3
-rw-r--r--tv/5pkgs/haskell/xmonad-tv/src/XMonad/Extra.hs14
-rw-r--r--tv/5pkgs/haskell/xmonad-tv/src/main.hs28
-rw-r--r--tv/5pkgs/simple/mpvterm/default.nix8
-rw-r--r--tv/5pkgs/simple/mpvterm/mpvterm.patch146
18 files changed, 351 insertions, 23 deletions
diff --git a/krebs/2configs/news.nix b/krebs/2configs/news.nix
index ce4e83408..410beb041 100644
--- a/krebs/2configs/news.nix
+++ b/krebs/2configs/news.nix
@@ -19,12 +19,46 @@
root /var/lib/brockman;
index brockman.json;
'';
+ extraConfig = ''
+ add_header 'Access-Control-Allow-Origin' '*';
+ add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
+ '';
};
};
systemd.tmpfiles.rules = [
"d /var/lib/brockman 1750 brockman nginx -"
];
+ systemd.services.brockman-graph = {
+ path = [
+ pkgs.graphviz
+ pkgs.jq
+ pkgs.inotifyTools
+ ];
+ serviceConfig = {
+ ExecStart = pkgs.writers.writeDash "brockman-graph" ''
+
+ while :; do
+ graphviz="$(cat /var/lib/brockman/brockman.json \
+ | jq -r '
+ .bots |
+ to_entries |
+ map(select(.value.extraChannels|length > 1 )) |
+ .[] |
+ "\"\(.key)\" -> {\(.value.extraChannels|map("\""+.+"\"")|join(" "))}"
+ ')"
+ echo "digraph news { $graphviz }" | circo -Tsvg > /var/lib/brockman/graph.svg
+
+ inotifywait -q -e MODIFY /var/lib/brockman/brockman.json
+ done
+ '';
+ User = "brockman";
+ };
+ wantedBy = [ "multi-user.target" ];
+ };
+
+ systemd.services.brockman.serviceConfig.LimitNOFILE = 16384;
+ systemd.services.brockman.environment.BROCKMAN_LOG_LEVEL = "DEBUG";
krebs.brockman = {
enable = true;
config = {
@@ -53,6 +87,7 @@
"#all"
"#aluhut"
"#news"
+ "#lasstube"
];
};
}
@@ -87,13 +122,36 @@
add-youtube.filename = pkgs.writeDash "add-youtube" ''
set -euf
if [ "$#" -ne 1 ]; then
- echo 'usage: ${name}: add-youtube $nick $channelid'
+ echo 'usage: ${name}: add-youtube $nick $channel/video/stream/id'
exit 1
fi
youtube_nick=$(echo "$1" | ${pkgs.jq}/bin/jq -Rr '[match("(\\S+)\\s*";"g").captures[].string][0]')
- youtube_id=$(echo "$1" | ${pkgs.jq}/bin/jq -Rr '[match("(\\S+)\\s*";"g").captures[].string][1]')
+ youtube_url=$(echo "$1" | ${pkgs.jq}/bin/jq -Rr '[match("(\\S+)\\s*";"g").captures[].string][1]')
+ if [ ''${#youtube_url} -eq 24 ]; then
+ youtube_id=$youtube_url
+ else
+ youtube_id=$(${pkgs.youtube-dl}/bin/youtube-dl --max-downloads 1 -j "$youtube_url" | ${pkgs.jq}/bin/jq -r '.channel_id')
+ fi
echo "brockman: add yt_$youtube_nick http://rss.r/?action=display&bridge=Youtube&context=By+channel+id&c=$youtube_id&duration_min=&duration_max=&format=Mrss"
'';
+ add-twitch.filename = pkgs.writeDash "add-twitch" ''
+ set -euf
+ if [ "$#" -ne 1 ]; then
+ echo 'usage: ${name}: add-twitch $handle'
+ exit 1
+ fi
+ twitch_nick=$(echo "$1" | ${pkgs.jq}/bin/jq -Rr '[match("(\\S+)\\s*";"g").captures[].string][0]')
+ echo "brockman: add twitch_$twitch_nick http://rss.r/?action=display&bridge=Twitch&channel=$twitch_nick&type=all&format=Atom"
+ '';
+ add-twitter.filename = pkgs.writeDash "add-twitter" ''
+ set -euf
+ if [ "$#" -ne 1 ]; then
+ echo 'usage: ${name}: add-twitter $handle'
+ exit 1
+ fi
+ twitter_nick=$(echo "$1" | ${pkgs.jq}/bin/jq -Rr '[match("(\\S+)\\s*";"g").captures[].string][0]')
+ echo "brockman: add tw_$twitter_nick http://rss.r/?action=display&bridge=Twitch&channel=$twitter_nick&type=all&format=Atom"
+ '';
search.filename = pkgs.writeDash "search" ''
set -euf
if [ "$#" -ne 1 ]; then
diff --git a/krebs/3modules/external/default.nix b/krebs/3modules/external/default.nix
index efe3ace0f..c8e360a1e 100644
--- a/krebs/3modules/external/default.nix
+++ b/krebs/3modules/external/default.nix
@@ -603,6 +603,30 @@ in {
};
};
};
+ hydrogen = {
+ owner = config.krebs.users.sandro;
+ nets = {
+ retiolum = {
+ ip4.addr = "10.243.54.54";
+ aliases = [ "hydrogen.r" ];
+ tinc.pubkey = ''
+ -----BEGIN RSA PUBLIC KEY-----
+ MIICCgKCAgEA6bnwRKS+oWNc+ExOnJdqGHR1bnCEE9zkCqyONK3+xLg8QQIz6SvZ
+ Nt5cO9kdZw9rA+4Dshg5m0RSnaP2TVtiDxcyv3zAW4TSOTGaBRp9WZmwhcKyxiON
+ hvTBirsC1Domorew6++qHoZxtmHMKaF/uPoFLBqSvw3RHVoGzEAhGW9/LqZVa9k2
+ D8dDRg7jTqH2oiIHct33Up5+zeFbmCo5xyDzl+pcwHxldi8sAupvpHx4KxXH1zMX
+ YQATS2Rp8b52bGEQ52UKAbSOpJqyt9/o5vb+z2JZ52N1dDrphWHGfIcJa8DVt2VO
+ n/V0kWuUhDh3Wwt7aIFcY0bUq4OurcVQQ5eMgwGbOEthdjLf1ou2gUJhf0zAeVQp
+ IiaqeHTNn1+mtxBjl4+v+b0H/lhZPSgO74Jo28fAse+/itjM3kgkIKV0ldD5tWpv
+ MHaKMB5Ui6swHZDV6nUxf5vlKPAM71/14cy1e/0ANFo5JvE66jWn7m5wn60HYwpB
+ XnTOgIxue2rJ4F+Xtm8CTgS5TcV1AKR0cnmlU7WfVOVJHD2/4QTRYYTCR53/RoVq
+ 1T6DILDF71H92PNylujKSPA0CKI160xJ61Xy/T6MYl5u0+RblAgYr77o5HJwmXCe
+ jFrCu3SKUIlJWYHWE8yNoR+VVYeXakbDFYE3KpVyBDG+ljUbia+Oel8CAwEAAQ==
+ -----END RSA PUBLIC KEY-----
+ '';
+ };
+ };
+ };
};
users = {
@@ -633,6 +657,7 @@ in {
mail = "joerg@thalheim.io";
pubkey = ssh-for "mic92";
};
+ sandro = {};
shannan = {
mail = "shannan@lekwati.com";
pubkey = ssh-for "shannan";
diff --git a/krebs/5pkgs/haskell/X11-aeson.nix b/krebs/5pkgs/haskell/X11-aeson.nix
new file mode 100644
index 000000000..7db6c28a8
--- /dev/null
+++ b/krebs/5pkgs/haskell/X11-aeson.nix
@@ -0,0 +1,13 @@
+{ mkDerivation, aeson, base, fetchgit, stdenv, X11 }:
+mkDerivation {
+ pname = "X11-aeson";
+ version = "1.0.0";
+ src = fetchgit {
+ url = "https://cgit.krebsco.de/X11-aeson";
+ sha256 = "0y9nvssqpvqgl46g7nz9738l8jmpa7an8r3am3qaqcvmvzgwxh0d";
+ rev = "c0a70a62513baf2b437db4ebe3e5a32e3cfa5905";
+ fetchSubmodules = true;
+ };
+ libraryHaskellDepends = [ aeson base X11 ];
+ license = stdenv.lib.licenses.mit;
+}
diff --git a/krebs/5pkgs/haskell/hack.nix b/krebs/5pkgs/haskell/hack.nix
new file mode 100644
index 000000000..4e157107e
--- /dev/null
+++ b/krebs/5pkgs/haskell/hack.nix
@@ -0,0 +1,22 @@
+{ mkDerivation, base, blessings, containers, data-default, fetchgit
+, lens, mtl, old-locale, process, scanner, stdenv, time, unix
+, zippers
+}:
+mkDerivation {
+ pname = "hack";
+ version = "1.0.0";
+ src = fetchgit {
+ url = "https://cgit.krebsco.de/hack";
+ sha256 = "0hi6frpnxbg3h6s7gd48ri57jc226qycy4rnhmpzpq195xf8y3pf";
+ rev = "cb004b2e5f0fce6cea8d54e60558a1c1904dbe39";
+ fetchSubmodules = true;
+ };
+ isLibrary = true;
+ isExecutable = true;
+ libraryHaskellDepends = [ base ];
+ executableHaskellDepends = [
+ base blessings containers data-default lens mtl old-locale process
+ scanner time unix zippers
+ ];
+ license = stdenv.lib.licenses.mit;
+}
diff --git a/krebs/5pkgs/haskell/xmonad-aeson.nix b/krebs/5pkgs/haskell/xmonad-aeson.nix
new file mode 100644
index 000000000..3fccab469
--- /dev/null
+++ b/krebs/5pkgs/haskell/xmonad-aeson.nix
@@ -0,0 +1,13 @@
+{ mkDerivation, aeson, base, fetchgit, stdenv, X11-aeson, xmonad }:
+mkDerivation {
+ pname = "xmonad-aeson";
+ version = "1.0.0";
+ src = fetchgit {
+ url = "https://cgit.krebsco.de/xmonad-aeson";
+ sha256 = "0l1gna6p1498vzm6kj0ywj0i7775mz5n7k9nymwggvfb1pyxv3h9";
+ rev = "a95f652b150f17db3f2439214a6346335d6d8d89";
+ fetchSubmodules = true;
+ };
+ libraryHaskellDepends = [ aeson base X11-aeson xmonad ];
+ license = stdenv.lib.licenses.mit;
+}
diff --git a/krebs/5pkgs/simple/realwallpaper/default.nix b/krebs/5pkgs/simple/realwallpaper/default.nix
index e55454a08..04a2a6718 100644
--- a/krebs/5pkgs/simple/realwallpaper/default.nix
+++ b/krebs/5pkgs/simple/realwallpaper/default.nix
@@ -100,11 +100,11 @@ pkgs.writers.writeDashBin "generate-wallpaper" ''
fetch_older_min 720 ice-raw.jpg $(get_neo_url \
'https://neo.sci.gsfc.nasa.gov/view.php?datasetId=NISE_D') &
- fetch_older_days 3 snow-raw.jpg $(get_neo_url \
+ fetch_older_days 1 snow-raw.jpg $(get_neo_url \
'https://neo.sci.gsfc.nasa.gov/view.php?datasetId=MOD10C1_E_SNOW') &
- fetch_older_days 7 chlora-raw.jpg $(get_neo_url \
+ fetch_older_days 1 chlora-raw.jpg $(get_neo_url \
'https://neo.sci.gsfc.nasa.gov/view.php?datasetId=MY1DMM_CHLORA') &
- fetch_older_days 7 fire-raw.jpg $(get_neo_url \
+ fetch_older_days 1 fire-raw.jpg $(get_neo_url \
'https://neo.sci.gsfc.nasa.gov/view.php?datasetId=MOD14A1_E_FIRE') &
# regular fetches
@@ -239,7 +239,7 @@ pkgs.writers.writeDashBin "generate-wallpaper" ''
''}
if [ -s marker.json ]; then
- jq -r 'to_entries[] | @json "\(.value.latitude) \(.value.longitude) image=krebs.png"' marker.json >> marker_file
+ jq -r 'to_entries[] | select(.value.latitude != null) | @json "\(.value.latitude) \(.value.longitude) image=krebs.png"' marker.json >> marker_file
fi
xplanet --num_times 1 --geometry $xplanet_out_size \
diff --git a/krebs/nixpkgs-unstable.json b/krebs/nixpkgs-unstable.json
index 57d30799b..c0ffcf3e3 100644
--- a/krebs/nixpkgs-unstable.json
+++ b/krebs/nixpkgs-unstable.json
@@ -1,9 +1,9 @@
{
"url": "https://github.com/NixOS/nixpkgs",
- "rev": "8c8731330b53ba0061686f36f10f101e662a4717",
- "date": "2021-02-08T20:46:59+01:00",
- "path": "/nix/store/agilvsqqdsqx36wf4zkq5gnhnab47qpd-nixpkgs",
- "sha256": "0ak4d254myq6cl3d7jkq6n0apxabvwjz62zdw9habnrqg8asl8gk",
+ "rev": "266dc8c3d052f549826ba246d06787a219533b8f",
+ "date": "2021-03-15T09:37:03+01:00",
+ "path": "/nix/store/dkim3k1b5bdga370xpw0r52w0ac5y3fn-nixpkgs",
+ "sha256": "09ydqx2lznixmw8z4cfz1j3k137mh8n3cdpygwqymknhfdjq7lg4",
"fetchSubmodules": false,
"deepClone": false,
"leaveDotGit": false
diff --git a/krebs/nixpkgs.json b/krebs/nixpkgs.json
index 8670999e0..837782ff6 100644
--- a/krebs/nixpkgs.json
+++ b/krebs/nixpkgs.json
@@ -1,9 +1,9 @@
{
"url": "https://github.com/NixOS/nixpkgs",
- "rev": "2394284537b89471c87065b040d3dedd8b5907fe",
- "date": "2021-02-10T23:24:22+01:00",
- "path": "/nix/store/rqgraycidchn5wc5mki5sqj8bl5cpx78-nixpkgs",
- "sha256": "1j7vp735is5d32mbrgavpxi3fbnsm6d99a01ap8gn30n5ysd14sl",
+ "rev": "36e15cd6e7d55ba143caf3dc930467ace573d85c",
+ "date": "2021-03-16T08:18:29+01:00",
+ "path": "/nix/store/rsh8kmy9jiwdhsm390zw0mq1p256xzrk-nixpkgs",
+ "sha256": "15dwscz9s71n6hn1wml95il8hl8aza16jj9qwywps8bsdamgymfq",
"fetchSubmodules": false,
"deepClone": false,
"leaveDotGit": false
diff --git a/lass/2configs/realwallpaper.nix b/lass/2configs/realwallpaper.nix
index c3054d3af..7a2f6e916 100644
--- a/lass/2configs/realwallpaper.nix
+++ b/lass/2configs/realwallpaper.nix
@@ -9,7 +9,7 @@ let
in {
krebs.realwallpaper.enable = true;
- system.activationScripts.user-shadow = ''
+ system.activationScripts.wallpaper-chmod = ''
${pkgs.coreutils}/bin/chmod +x /var/realwallpaper
'';
services.nginx.virtualHosts.wallpaper = {
diff --git a/lass/2configs/syncthing.nix b/lass/2configs/syncthing.nix
index e288df68a..7801c758c 100644
--- a/lass/2configs/syncthing.nix
+++ b/lass/2configs/syncthing.nix
@@ -13,7 +13,7 @@
{ predicate = "-p udp --dport 21027"; target = "ACCEPT";}
];
- system.activationScripts.syncthing-home = ''
+ system.activationScripts.syncthing-home = mkDefault ''
${pkgs.coreutils}/bin/chmod a+x /home/lass
'';
diff --git a/lib/default.nix b/lib/default.nix
index 2efeec078..4190f8f5f 100644
--- a/lib/default.nix
+++ b/lib/default.nix
@@ -15,6 +15,10 @@ let
uri = import ./uri.nix { inherit lib; };
xml = import ./xml.nix { inherit lib; };
+ # compose a list of functions to be applied from left to right, i.e.
+ # compose :: [ (xm -> xn) ... (x1 -> x2) (x0 -> x1) ] -> x0 -> xn
+ compose = foldl' (f: g: x: f (g x)) id;
+
eq = x: y: x == y;
ne = x: y: x != y;
mod = x: y: x - y * (x / y);
diff --git a/tv/2configs/gitrepos.nix b/tv/2configs/gitrepos.nix
index 5b6a70b27..2e73640ff 100644
--- a/tv/2configs/gitrepos.nix
+++ b/tv/2configs/gitrepos.nix
@@ -96,6 +96,8 @@ let {
nix-writers = {
cgit.desc = "collection of package builders";
};
+ pager = {
+ };
populate = {
cgit.desc = "source code installer";
};
@@ -113,7 +115,9 @@ let {
ni = {
};
} // mapAttrs (_: recursiveUpdate { cgit.section = "3. Haskell libraries"; }) {
+ X11-aeson = {};
blessings = {};
+ hack = {};
hc = {};
mime = {};
quipper = {};
@@ -121,7 +125,9 @@ let {
wai-middleware-time = {};
web-routes-wai-custom = {};
xintmap = {};
+ xmonad-aeson = {};
xmonad-stockholm = {};
+ xmonad-web = {};
} // mapAttrs (_: recursiveUpdate { cgit.section = "4. museum"; }) {
cac-api = {
cgit.desc = "CloudAtCost API command line interface";
diff --git a/tv/2configs/hw/w110er.nix b/tv/2configs/hw/w110er.nix
index 693cef5ac..818d1aca6 100644
--- a/tv/2configs/hw/w110er.nix
+++ b/tv/2configs/hw/w110er.nix
@@ -28,6 +28,8 @@ with import <stockholm/lib>;
boot.loader.systemd-boot.enable = true;
boot.loader.efi.canTouchEfiVariables = true;
+ hardware.enableRedistributableFirmware = true;
+
hardware.opengl.driSupport32Bit = true;
hardware.opengl.extraPackages = [ pkgs.vaapiIntel ];
diff --git a/tv/5pkgs/haskell/xmonad-tv/src/Paths.hs b/tv/5pkgs/haskell/xmonad-tv/src/Paths.hs
index b2ad01ae7..2569b60c3 100644
--- a/tv/5pkgs/haskell/xmonad-tv/src/Paths.hs
+++ b/tv/5pkgs/haskell/xmonad-tv/src/Paths.hs
@@ -32,3 +32,6 @@ xcalib = findExecutable "xcalib"
xdpychvt :: FilePath
xdpychvt = findExecutable "xdpychvt"
+
+xterm :: FilePath
+xterm = findExecutable "xterm"
diff --git a/tv/5pkgs/haskell/xmonad-tv/src/XMonad/Extra.hs b/tv/5pkgs/haskell/xmonad-tv/src/XMonad/Extra.hs
new file mode 100644
index 000000000..74222712d
--- /dev/null
+++ b/tv/5pkgs/haskell/xmonad-tv/src/XMonad/Extra.hs
@@ -0,0 +1,14 @@
+module XMonad.Extra where
+
+import XMonad
+import qualified Data.Map as Map
+import qualified XMonad.StackSet as W
+
+
+isFloating :: Window -> WindowSet -> Bool
+isFloating w =
+ Map.member w . W.floating
+
+isFloatingX :: Window -> X Bool
+isFloatingX w =
+ isFloating w <$> gets windowset
diff --git a/tv/5pkgs/haskell/xmonad-tv/src/main.hs b/tv/5pkgs/haskell/xmonad-tv/src/main.hs
index e5a4473fe..48127a594 100644
--- a/tv/5pkgs/haskell/xmonad-tv/src/main.hs
+++ b/tv/5pkgs/haskell/xmonad-tv/src/main.hs
@@ -7,11 +7,12 @@ module Main (main) where
import System.Exit (exitFailure)
import Control.Exception
-import Control.Monad.Extra (whenJustM)
+import Control.Monad.Extra (ifM, whenJustM)
import qualified Data.List
import Graphics.X11.ExtraTypes.XF86
import Text.Read (readEither)
import XMonad
+import XMonad.Extra (isFloatingX)
import System.IO (hPutStrLn, stderr)
import System.Environment (getArgs, getEnv, getEnvironment, lookupEnv)
import System.Posix.Process (executeFile)
@@ -60,9 +61,8 @@ main = getArgs >>= \case
args -> hPutStrLn stderr ("bad arguments: " <> show args) >> exitFailure
-queryPrefix :: Query String -> String -> Query Bool
-queryPrefix query prefix =
- fmap (Data.List.isPrefixOf prefix) query
+(=??) :: Query a -> (a -> Bool) -> Query Bool
+(=??) x p = fmap p x
mainNoArgs :: IO ()
@@ -88,7 +88,8 @@ mainNoArgs = do
, manageHook =
composeAll
[ appName =? "fzmenu-urxvt" --> doCenterFloat
- , appName `queryPrefix` "pinentry" --> doCenterFloat
+ , appName =?? Data.List.isPrefixOf "pinentry" --> doCenterFloat
+ , appName =?? Data.List.isInfixOf "Float" --> doCenterFloat
, title =? "Upload to Imgur" -->
doRectFloat (W.RationalRect 0 0 (1 % 8) (1 % 8))
, placeHook (smart (1,0))
@@ -154,8 +155,8 @@ myKeys conf = Map.fromList $
, ((_S , xK_Menu ), gets windowset >>= allWorkspaceNames >>= pager pagerConfig (windows . W.shift) )
, ((_C , xK_Menu ), toggleWS)
- , ((_4 , xK_space ), sendMessage NextLayout)
- , ((_4M , xK_space ), resetLayout)
+ , ((_4 , xK_space ), withFocused $ \w -> ifM (isFloatingX w) xdeny $ sendMessage NextLayout)
+ , ((_4M , xK_space ), withFocused $ \w -> ifM (isFloatingX w) xdeny $ resetLayout)
, ((_4 , xK_m ), windows W.focusMaster)
, ((_4 , xK_j ), windows W.focusDown)
@@ -218,6 +219,19 @@ myKeys conf = Map.fromList $
def { XMonad.Prompt.font = myFont }
+xdeny :: X ()
+xdeny =
+ forkFile
+ Paths.xterm
+ [ "-fn", myFont
+ , "-geometry", "300x100"
+ , "-name", "AlertFloat"
+ , "-bg", "#E4002B"
+ , "-e", "sleep", "0.05"
+ ]
+ Nothing
+
+
pagerConfig :: PagerConfig
pagerConfig = def
{ pc_font = myFont
diff --git a/tv/5pkgs/simple/mpvterm/default.nix b/tv/5pkgs/simple/mpvterm/default.nix
new file mode 100644
index 000000000..66ad08a78
--- /dev/null
+++ b/tv/5pkgs/simple/mpvterm/default.nix
@@ -0,0 +1,8 @@
+{ pkgs }:
+
+pkgs.mpv-unwrapped.overrideAttrs (old: rec {
+ pname = "mpvterm";
+ patches = old.patches or [] ++ [
+ ./mpvterm.patch
+ ];
+})
diff --git a/tv/5pkgs/simple/mpvterm/mpvterm.patch b/tv/5pkgs/simple/mpvterm/mpvterm.patch
new file mode 100644
index 000000000..12636880d
--- /dev/null
+++ b/tv/5pkgs/simple/mpvterm/mpvterm.patch
@@ -0,0 +1,146 @@
+commit 5ded4dac370ce5d8d727c5d3891448f942edbfdf
+Author: tv <tv@krebsco.de>
+Date: Sat Feb 27 22:54:55 2021 +0100
+
+ x11: add input forwarding support
+
+diff --git a/video/out/x11_common.c b/video/out/x11_common.c
+index ac551fae8e..2e95451d7f 100644
+--- a/video/out/x11_common.c
++++ b/video/out/x11_common.c
+@@ -25,6 +25,10 @@
+ #include <string.h>
+ #include <assert.h>
+
++#include <stdarg.h>
++#include <sys/socket.h>
++#include <sys/un.h>
++
+ #include <X11/Xmd.h>
+ #include <X11/Xlib.h>
+ #include <X11/Xutil.h>
+@@ -1097,6 +1101,73 @@ static void release_all_keys(struct vo *vo)
+ x11->win_drag_button1_down = false;
+ }
+
++
++#define FORWARD_START 1
++#define FORWARD_READY 2
++#define FORWARD_ERROR 3
++static int forward_state = FORWARD_START;
++static int forward_fd, forward_len;
++static struct sockaddr_un forward_un;
++static char forward_buf[BUFSIZ];
++
++static void forward_start(void) {
++ const char *socket_path = getenv("FORWARD_SOCKET");
++ if (socket_path == NULL) {
++ fprintf(stderr, "forward_start: environment variable FORWARD_SOCKET not set\n");
++ forward_state = FORWARD_ERROR;
++ return;
++ }
++
++ if ((forward_fd = socket(AF_UNIX, SOCK_DGRAM, 0)) < 0) {
++ perror("socket");
++ } else {
++ memset(&forward_un, 0, sizeof(forward_un));
++ forward_un.sun_family = AF_UNIX;
++ strcpy(forward_un.sun_path, socket_path);
++ forward_len = offsetof(struct sockaddr_un, sun_path) + strlen(socket_path);
++ forward_state = FORWARD_READY;
++ }
++}
++static void forward_send(const char *fmt, ...) {
++ if (forward_state != FORWARD_READY) return;
++
++ va_list argp;
++ va_start(argp, fmt);
++ int n1 = vsnprintf(forward_buf, BUFSIZ, fmt, argp);
++ if (n1 < BUFSIZ + 1) {
++ forward_buf[n1++] = '\n';
++ forward_buf[n1] = '\0';
++ int n2 = sendto(forward_fd, forward_buf, n1, 0, (struct sockaddr *)&forward_un, forward_len);
++ if (n2 < 0) {
++ perror("sendto");
++ }
++ }
++}
++static const char *forward_keyname(KeySym keySym) {
++ const char *name;
++ if (keySym == NoSymbol) {
++ name = "NoSymbol";
++ } else if (!(name = XKeysymToString(keySym))) {
++ name = "NoName";
++ }
++ return name;
++}
++static void forward_keydown(KeySym keySym) {
++ forward_send("xdotool keydown %s", forward_keyname(keySym));
++}
++static void forward_keyup(KeySym keySym) {
++ forward_send("xdotool keyup %s", forward_keyname(keySym));
++}
++static void forward_mousedown(int button) {
++ forward_send("xdotool mousedown %d", button);
++}
++static void forward_mouseup(int button) {
++ forward_send("xdotool mouseup %d", button);
++}
++static void forward_mousemove(int x, int y) {
++ forward_send("xdotool mousemove %d %d", x, y);
++}
++
+ void vo_x11_check_events(struct vo *vo)
+ {
+ struct vo_x11_state *x11 = vo->x11;
+@@ -1105,6 +1176,10 @@ void vo_x11_check_events(struct vo *vo)
+
+ xscreensaver_heartbeat(vo->x11);
+
++ if (forward_state == FORWARD_START) {
++ forward_start();
++ }
++
+ while (XPending(display)) {
+ XNextEvent(display, &Event);
+ MP_TRACE(x11, "XEvent: %d\n", Event.type);
+@@ -1146,6 +1221,7 @@ void vo_x11_check_events(struct vo *vo)
+ if (mpkey)
+ mp_input_put_key(x11->input_ctx, mpkey | modifiers);
+ }
++ forward_keydown(XLookupKeysym(&Event.xkey, 0));
+ break;
+ }
+ case FocusIn:
+@@ -1161,6 +1237,7 @@ void vo_x11_check_events(struct vo *vo)
+ break;
+ case KeyRelease:
+ release_all_keys(vo);
++ forward_keyup(XLookupKeysym(&Event.xkey, 0));
+ break;
+ case MotionNotify:
+ if (x11->win_drag_button1_down && !x11->fs &&
+@@ -1182,6 +1259,7 @@ void vo_x11_check_events(struct vo *vo)
+ Event.xmotion.y);
+ }
+ x11->win_drag_button1_down = false;
++ forward_mousemove(Event.xmotion.x, Event.xmotion.y);
+ break;
+ case LeaveNotify:
+ if (Event.xcrossing.mode != NotifyNormal)
+@@ -1204,6 +1282,7 @@ void vo_x11_check_events(struct vo *vo)
+ get_mods(Event.xbutton.state) | MP_KEY_STATE_DOWN);
+ long msg[4] = {XEMBED_REQUEST_FOCUS};
+ vo_x11_xembed_send_message(x11, msg);
++ forward_mousedown(Event.xbutton.button);
+ break;
+ case ButtonRelease:
+ if (Event.xbutton.button - 1 >= MP_KEY_MOUSE_BTN_COUNT)
+@@ -1213,6 +1292,7 @@ void vo_x11_check_events(struct vo *vo)
+ mp_input_put_key(x11->input_ctx,
+ (MP_MBTN_BASE + Event.xbutton.button - 1) |
+ get_mods(Event.xbutton.state) | MP_KEY_STATE_UP);
++ forward_mouseup(Event.xbutton.button);
+ break;
+ case MapNotify:
+ x11->window_hidden = false;