Adding dwm patch for systray. - dotfiles - Unnamed repository; edit this file '… | |
Log | |
Files | |
Refs | |
README | |
--- | |
commit 3d5e193eadca503c9e6d6a5f03ae91f674e1985a | |
parent 0af9eacb1337a96136452b728bd04700cebb72b9 | |
Author: Jay Scott <[email protected]> | |
Date: Sat, 28 Oct 2023 09:06:01 +0100 | |
Adding dwm patch for systray. | |
Diffstat: | |
M bashrc | 3 +++ | |
M bin/dwmstatus.sh | 13 +++++++------ | |
D bin/fet.sh | 281 -----------------------------… | |
M suckless/dwm/Makefile | 8 +++++++- | |
M suckless/dwm/config.h | 6 ++++++ | |
A suckless/dwm/patches/01-dwm-systra… | 746 +++++++++++++++++++++++++++… | |
6 files changed, 769 insertions(+), 288 deletions(-) | |
--- | |
diff --git a/bashrc b/bashrc | |
@@ -42,6 +42,9 @@ alias rm='rm -i' | |
# random alias | |
alias weather='curl wttr.in/?1QF' | |
alias ls='ls --color=auto' | |
+alias voff='mullvad lockdown-mode set off; mullvad disconnect' | |
+alias von='mullvad lockdown-mode set on; mullvad connect' | |
+alias vcheck='curl https://am.i.mullvad.net/connected' | |
# git alias | |
alias ga='git add -A' | |
diff --git a/bin/dwmstatus.sh b/bin/dwmstatus.sh | |
@@ -1,17 +1,18 @@ | |
#!/bin/sh | |
-maildir="$HOME/mail/jay/Inbox/new/" | |
- | |
while true; do | |
localtime=$(date +"%T") | |
vol=$(pactl list sinks | tr ' ' '\n' | grep -m1 '%') | |
+ vstatus=$(mullvad status) | |
- #mailcount="$(find "$maildir" -type f | wc -l)" | |
- #rsscount=$(newsboat -x print-unread | awk '{print $1}') | |
+ if [ "$vstatus" != "Disconnected" ]; then | |
+ vstatus="On" | |
+ else | |
+ vstatus="Off" | |
+ fi | |
- #xsetroot -name " RSS: $rsscount | MAIL: $mailcount | VOL: $vol | $loc… | |
- xsetroot -name " VOL: $vol | $localtime" | |
+ xsetroot -name " VPN: $vstatus | VOL: $vol | $localtime | " | |
sleep 10 | |
done | |
diff --git a/bin/fet.sh b/bin/fet.sh | |
@@ -1,281 +0,0 @@ | |
-#!/bin/sh | |
-# | |
-# fet.sh | |
-# a fetch in pure POSIX shell | |
-# | |
- | |
-# supress errors | |
-exec 2>/dev/null | |
-set -- | |
-eq() { # equals | [ a = b ] with globbing | |
- case $1 in | |
- $2) ;; | |
- *) return 1 ;; | |
- esac | |
-} | |
- | |
-## DE | |
-wm=$XDG_CURRENT_DESKTOP | |
-[ "$wm" ] || wm=$DESKTOP_SESSION | |
- | |
-## Distro | |
-# freedesktop.org/software/systemd/man/os-release.html | |
-# a common file that has variables about the distro | |
-for os in /etc/os-release /usr/lib/os-release; do | |
- # some POSIX shells exit when trying to source a file that doesn't exi… | |
- [ -f $os ] && . $os && break | |
-done | |
- | |
-if [ -e /proc/$$/comm ]; then | |
- ## Terminal | |
- while [ ! "$term" ]; do | |
- # loop over lines in /proc/pid/status until it reaches PPid | |
- # then save that to a variable and exit the file | |
- while read -r line; do | |
- eq "$line" 'PPid*' && ppid=${line##*:?} && break | |
- done <"/proc/${ppid:-$PPID}/status" | |
- | |
- # Make sure not to do an infinite loop | |
- [ "$pppid" = "$ppid" ] && break | |
- pppid=$ppid | |
- | |
- # get name of binary | |
- read -r name <"/proc/$ppid/comm" | |
- | |
- case $name in | |
- *sh | "${0##*/}") ;; # skip shells | |
- *[Ll]ogin* | *init | *systemd*) break ;; # exit when the top i… | |
- # anything else can be assumed to be the terminal | |
- # this has the side affect of catching tmux, but tmux | |
- # detaches from the terminal and therefore ignoring that | |
- # will just make the init the term | |
- *) term=$name ;; | |
- esac | |
- done | |
- | |
- ## WM/DE | |
- [ "$wm" ] || | |
- # loop over all processes and check the binary name | |
- for i in /proc/*/comm; do | |
- read -r c <"$i" | |
- case $c in | |
- *bar* | *rc) ;; | |
- awesome | xmonad* | qtile | sway | i3 | [bfo]*box | *w… | |
- wm=${c%%-*} | |
- break | |
- ;; | |
- esac | |
- done | |
- | |
- ## Memory | |
- # loop over lines in /proc/meminfo until it reaches MemTotal, | |
- # then convert the amount (second word) from KB to MB | |
- while read -r line; do | |
- eq "$line" 'MemTotal*' && set -- $line && break | |
- done </proc/meminfo | |
- mem="$(($2 / 1000))MB" | |
- | |
- ## Processor | |
- while read -r line; do | |
- case $line in | |
- vendor_id*) vendor="${line##*: } " ;; | |
- model\ name*) | |
- cpu=${line##*: } | |
- break | |
- ;; | |
- esac | |
- done </proc/cpuinfo | |
- | |
- ## Uptime | |
- # the simple math is shamefully stolen from aosync | |
- IFS=. read -r uptime _ </proc/uptime | |
- d=$((uptime / 60 / 60 / 24)) | |
- up=$(printf %02d:%02d $((uptime / 60 / 60 % 24)) $((uptime / 60 % 60))) | |
- [ "$d" -gt 0 ] && up="${d}d $up" | |
- | |
- ## Kernel | |
- read -r _ _ version _ </proc/version | |
- kernel=${version%%-*} | |
- eq "$version" '*Microsoft*' && ID="fake $ID" | |
- | |
- ## Motherboard // laptop | |
- read -r model </sys/devices/virtual/dmi/id/product_name | |
- # invalid model handling | |
- case $model in | |
- # alternate file with slightly different info | |
- # on my laptop it has the device model (instead of 'hp notebook') | |
- # on my desktop it has the extended motherboard model | |
- 'System '* | 'Default '* | 'To Be Filled'*) | |
- read -r model </sys/devices/virtual/dmi/id/board_name | |
- ;; | |
- esac | |
- | |
- ## Packages | |
- # clean environment, then make every file in the dir an argument, | |
- # then save the argument count to $pkgs | |
- set -- | |
- # kiss, arch, debian, void, gentoo | |
- for i in '/var/db/kiss/installed/*' '/var/lib/pacman/local/[0-9a-z]*' \ | |
- '/var/lib/dpkg/info/*.list' '/var/db/xbps/.*' '/var/db/pkg/*/*… | |
- set -- $i | |
- [ $# -gt 1 ] && pkgs=$# && break | |
- done | |
- | |
- read -r host </proc/sys/kernel/hostname | |
-elif [ -f /var/run/dmesg.boot ]; then | |
- # Both OpenBSD and FreeBSD use this file, however they're formatted di… | |
- read -r bsd </var/run/dmesg.boot | |
- case $bsd in | |
- Open*) | |
- ## OpenBSD cpu/mem/name | |
- while read -r line; do | |
- case $line in | |
- 'real mem'*) | |
- # use the pre-formatted value which is in brac… | |
- mem=${line##*\(} | |
- mem=${mem%\)*} | |
- ;; | |
- # set $cpu to everything before a comma and after the … | |
- cpu0:*) | |
- cpu=${line#cpu0: } | |
- # Remove excess info after the actual CPU name | |
- cpu=${cpu%%,*} | |
- # Set the CPU Manufacturer to the first word o… | |
- # variable [separated by '(' or ' '] | |
- vendor=${cpu%%[\( ]*} | |
- # We got all the info we want, stop reading | |
- break | |
- ;; | |
- # First 2 words in the file are OpenBSD <version> | |
- *) [ "$ID" ] || { | |
- set -- $line | |
- ID="$1 $2" | |
- } ;; | |
- esac | |
- done </var/run/dmesg.boot | |
- [ -d /var/db/pkg ] && set -- /var/db/pkg/* && pkgs=$# | |
- read -r host </etc/myname | |
- host=${host%.*} | |
- ;; | |
- # Everything else, assume FreeBSD (first line is ---<<BOOT>> or someth… | |
- *) | |
- # shellcheck source=/dev/null | |
- . /etc/rc.conf | |
- # shut shellcheck up without disabling the warning | |
- host=${hostname:-} | |
- | |
- while read -r line; do | |
- case $line in | |
- # os version | |
- FreeBSD*) | |
- # If the OS is already set, no need to set it … | |
- [ "$ID" ] && continue | |
- ID=${line%%-R*} | |
- ;; | |
- | |
- CPU:*) | |
- cpu=${cpu#CPU: } | |
- # Remove excess info from after the actual CPU… | |
- cpu=${line%\(*} | |
- ;; | |
- *Origin=*) | |
- # CPU Manufacturer | |
- vendor=${line#*Origin=\"} | |
- vendor="${vendor%%\"*} " | |
- ;; | |
- | |
- 'real memory'*) | |
- # Get the pre-formatted amount which is inside… | |
- mem=${line##*\(} | |
- mem=${mem%\)*} | |
- # This appears to be the final thing we need f… | |
- # no need to read it more. | |
- break | |
- ;; | |
- esac | |
- done </var/run/dmesg.boot | |
- ;; | |
- esac | |
-elif | |
- v=/System/Library/CoreServices/SystemVersion.plist | |
- [ -f "$v" ] | |
-then | |
- ## Macos | |
- # make sure this variable is empty as to not break the following loop | |
- temp= | |
- while read -r line; do | |
- case $line in | |
- # set a variable so the script knows it's on the correct line | |
- # (the line after this one is the important one) | |
- *ProductVersion*) temp=. ;; | |
- *) | |
- # check if the script is reading the derired line, if … | |
- # don't do anything | |
- [ "$temp" ] || continue | |
- # Remove everything before and including the first '>' | |
- ID=${line#*>} | |
- # Remove the other side of the XML tag, and insert the… | |
- ID="MacOS ${ID%<*}" | |
- # We got the info we want, end the loop. | |
- break | |
- ;; | |
- esac | |
- done <"$v" | |
-fi | |
- | |
-eq "$0" '*fetish' && printf 'Step on me daddy\n' && exit | |
- | |
-# help i dont know if it's a capital consistently | |
-eq "$wm" '*[Gg][Nn][Oo][Mm][Ee]*' && wm='foot DE' | |
- | |
-## GTK | |
-while read -r line; do | |
- eq "$line" 'gtk-theme*' && gtk=${line##*=} && break | |
-done <"${XDG_CONFIG_HOME:=$HOME/.config}/gtk-3.0/settings.ini" | |
- | |
-# Shorten $cpu and $vendor | |
-# this is so messy due to so many inconsistencies in the model names | |
-vendor=${vendor##*Authentic} | |
-vendor=${vendor##*Genuine} | |
-cpu=${cpu##*) } | |
-cpu=${cpu%% @*} | |
-cpu=${cpu%% CPU} | |
-cpu=${cpu##CPU } | |
-cpu=${cpu##*AMD } | |
-cpu=${cpu%% with*} | |
-cpu=${cpu% *-Core*} | |
- | |
-col() { | |
- printf ' ' | |
- for i in 1 2 3 4 5 6; do | |
- printf '\033[9%sm%s' "$i" "${colourblocks:-▅▅}" | |
- done | |
- printf '\033[0m\n' | |
-} | |
- | |
-print() { | |
- [ "$2" ] && printf '\033[9%sm%6s\033[0m%b%s\n' \ | |
- "${accent:-4}" "$1" "${separator:- ~ }" "$2" | |
-} | |
- | |
-# default value | |
-: "${info:=n user os sh wm up gtk cpu mem host kern pkgs term col n}" | |
- | |
-for i in $info; do | |
- case $i in | |
- n) echo ;; | |
- os) print os "$ID" ;; | |
- sh) print sh "${SHELL##*/}" ;; | |
- wm) print wm "${wm##*/}" ;; | |
- up) print up "$up" ;; | |
- gtk) print gtk "${gtk# }" ;; | |
- cpu) print cpu "$vendor$cpu" ;; | |
- mem) print mem "$mem" ;; | |
- host) print host "$model" ;; | |
- kern) print kern "$kernel" ;; | |
- pkgs) print pkgs "$pkgs" ;; | |
- term) print term "$term" ;; | |
- user) printf '%7s@%s\n' "$USER" "$host" ;; | |
- col) col ;; | |
- esac | |
-done | |
diff --git a/suckless/dwm/Makefile b/suckless/dwm/Makefile | |
@@ -1,6 +1,7 @@ | |
REPOSITORY = http://git.suckless.org/dwm | |
SRC_DIR = src | |
PINNED_REVISION = HEAD | |
+PATCH_DIR = patches | |
all: $(SRC_DIR) | |
@@ -11,7 +12,7 @@ clean: reset | |
git clean -f; \ | |
fi | |
-$(SRC_DIR): clone reset | |
+$(SRC_DIR): clone reset patch | |
@cp config.h $@ | |
@cd $@ && $(MAKE) -s | |
@@ -20,6 +21,11 @@ reset: | |
cd $(SRC_DIR) && git reset --hard $(PINNED_REVISION); \ | |
fi | |
+patch: $(PATCH_DIR)/* | |
+ @for file in $^ ; do \ | |
+ patch -d "${SRC_DIR}" < $${file}; \ | |
+ done | |
+ | |
clone: | |
@if ! test -d $(SRC_DIR); then \ | |
git clone $(REPOSITORY) $(SRC_DIR); \ | |
diff --git a/suckless/dwm/config.h b/suckless/dwm/config.h | |
@@ -6,6 +6,12 @@ static const unsigned int snap = 32; | |
static const int lockfullscreen = 1; | |
static const int showbar = 1; | |
static const int topbar = 1; | |
+static const unsigned int systraypinning = 0; | |
+static const unsigned int systrayonleft = 0; | |
+static const unsigned int systrayspacing = 2; | |
+static const int systraypinningfailfirst = 1; | |
+static const int showsystray = 1; | |
+ | |
static const char *fonts[] = { "Hack:size=10" }; | |
static const char dmenufont[] = "Hack:size=10"; | |
static const char col_gray1[] = "#222222"; | |
diff --git a/suckless/dwm/patches/01-dwm-systray-6.4.diff b/suckless/dwm/patche… | |
@@ -0,0 +1,746 @@ | |
+diff --git a/config.def.h b/config.def.h | |
+index 9efa774..750529d 100644 | |
+--- a/config.def.h | |
++++ b/config.def.h | |
+@@ -3,6 +3,11 @@ | |
+ /* appearance */ | |
+ static const unsigned int borderpx = 1; /* border pixel of windows */ | |
+ static const unsigned int snap = 32; /* snap pixel */ | |
++static const unsigned int systraypinning = 0; /* 0: sloppy systray follows … | |
++static const unsigned int systrayonleft = 0; /* 0: systray in the right co… | |
++static const unsigned int systrayspacing = 2; /* systray spacing */ | |
++static const int systraypinningfailfirst = 1; /* 1: if pinning fails, displ… | |
++static const int showsystray = 1; /* 0 means no systray */ | |
+ static const int showbar = 1; /* 0 means no bar */ | |
+ static const int topbar = 1; /* 0 means bottom bar */ | |
+ static const char *fonts[] = { "monospace:size=10" }; | |
+@@ -101,8 +106,8 @@ static const Key keys[] = { | |
+ /* click can be ClkTagBar, ClkLtSymbol, ClkStatusText, ClkWinTitle, ClkClient… | |
+ static const Button buttons[] = { | |
+ /* click event mask button function … | |
+- { ClkLtSymbol, 0, Button1, setlayout, … | |
+- { ClkLtSymbol, 0, Button3, setlayout, … | |
++ { ClkTagBar, MODKEY, Button1, tag, … | |
++ { ClkTagBar, MODKEY, Button3, toggletag, … | |
+ { ClkWinTitle, 0, Button2, zoom, … | |
+ { ClkStatusText, 0, Button2, spawn, … | |
+ { ClkClientWin, MODKEY, Button1, movemouse, … | |
+diff --git a/dwm.c b/dwm.c | |
+index 03baf42..4611a03 100644 | |
+--- a/dwm.c | |
++++ b/dwm.c | |
+@@ -57,12 +57,27 @@ | |
+ #define TAGMASK ((1 << LENGTH(tags)) - 1) | |
+ #define TEXTW(X) (drw_fontset_getwidth(drw, (X)) + lrpad) | |
+ | |
++#define SYSTEM_TRAY_REQUEST_DOCK 0 | |
++/* XEMBED messages */ | |
++#define XEMBED_EMBEDDED_NOTIFY 0 | |
++#define XEMBED_WINDOW_ACTIVATE 1 | |
++#define XEMBED_FOCUS_IN 4 | |
++#define XEMBED_MODALITY_ON 10 | |
++#define XEMBED_MAPPED (1 << 0) | |
++#define XEMBED_WINDOW_ACTIVATE 1 | |
++#define XEMBED_WINDOW_DEACTIVATE 2 | |
++#define VERSION_MAJOR 0 | |
++#define VERSION_MINOR 0 | |
++#define XEMBED_EMBEDDED_VERSION (VERSION_MAJOR << 16) | VERSION_MINOR | |
++ | |
+ /* enums */ | |
+ enum { CurNormal, CurResize, CurMove, CurLast }; /* cursor */ | |
+ enum { SchemeNorm, SchemeSel }; /* color schemes */ | |
+ enum { NetSupported, NetWMName, NetWMState, NetWMCheck, | |
++ NetSystemTray, NetSystemTrayOP, NetSystemTrayOrientation, NetSystemTra… | |
+ NetWMFullscreen, NetActiveWindow, NetWMWindowType, | |
+ NetWMWindowTypeDialog, NetClientList, NetLast }; /* EWMH atoms */ | |
++enum { Manager, Xembed, XembedInfo, XLast }; /* Xembed atoms */ | |
+ enum { WMProtocols, WMDelete, WMState, WMTakeFocus, WMLast }; /* default atom… | |
+ enum { ClkTagBar, ClkLtSymbol, ClkStatusText, ClkWinTitle, | |
+ ClkClientWin, ClkRootWin, ClkLast }; /* clicks */ | |
+@@ -141,6 +156,12 @@ typedef struct { | |
+ int monitor; | |
+ } Rule; | |
+ | |
++typedef struct Systray Systray; | |
++struct Systray { | |
++ Window win; | |
++ Client *icons; | |
++}; | |
++ | |
+ /* function declarations */ | |
+ static void applyrules(Client *c); | |
+ static int applysizehints(Client *c, int *x, int *y, int *w, int *h, int inte… | |
+@@ -172,6 +193,7 @@ static void focusstack(const Arg *arg); | |
+ static Atom getatomprop(Client *c, Atom prop); | |
+ static int getrootptr(int *x, int *y); | |
+ static long getstate(Window w); | |
++static unsigned int getsystraywidth(); | |
+ static int gettextprop(Window w, Atom atom, char *text, unsigned int size); | |
+ static void grabbuttons(Client *c, int focused); | |
+ static void grabkeys(void); | |
+@@ -189,13 +211,16 @@ static void pop(Client *c); | |
+ static void propertynotify(XEvent *e); | |
+ static void quit(const Arg *arg); | |
+ static Monitor *recttomon(int x, int y, int w, int h); | |
++static void removesystrayicon(Client *i); | |
+ static void resize(Client *c, int x, int y, int w, int h, int interact); | |
++static void resizebarwin(Monitor *m); | |
+ static void resizeclient(Client *c, int x, int y, int w, int h); | |
+ static void resizemouse(const Arg *arg); | |
++static void resizerequest(XEvent *e); | |
+ static void restack(Monitor *m); | |
+ static void run(void); | |
+ static void scan(void); | |
+-static int sendevent(Client *c, Atom proto); | |
++static int sendevent(Window w, Atom proto, int m, long d0, long d1, long d2, … | |
+ static void sendmon(Client *c, Monitor *m); | |
+ static void setclientstate(Client *c, long state); | |
+ static void setfocus(Client *c); | |
+@@ -207,6 +232,7 @@ static void seturgent(Client *c, int urg); | |
+ static void showhide(Client *c); | |
+ static void sigchld(int unused); | |
+ static void spawn(const Arg *arg); | |
++static Monitor *systraytomon(Monitor *m); | |
+ static void tag(const Arg *arg); | |
+ static void tagmon(const Arg *arg); | |
+ static void tile(Monitor *m); | |
+@@ -224,18 +250,23 @@ static int updategeom(void); | |
+ static void updatenumlockmask(void); | |
+ static void updatesizehints(Client *c); | |
+ static void updatestatus(void); | |
++static void updatesystray(void); | |
++static void updatesystrayicongeom(Client *i, int w, int h); | |
++static void updatesystrayiconstate(Client *i, XPropertyEvent *ev); | |
+ static void updatetitle(Client *c); | |
+ static void updatewindowtype(Client *c); | |
+ static void updatewmhints(Client *c); | |
+ static void view(const Arg *arg); | |
+ static Client *wintoclient(Window w); | |
+ static Monitor *wintomon(Window w); | |
++static Client *wintosystrayicon(Window w); | |
+ static int xerror(Display *dpy, XErrorEvent *ee); | |
+ static int xerrordummy(Display *dpy, XErrorEvent *ee); | |
+ static int xerrorstart(Display *dpy, XErrorEvent *ee); | |
+ static void zoom(const Arg *arg); | |
+ | |
+ /* variables */ | |
++static Systray *systray = NULL; | |
+ static const char broken[] = "broken"; | |
+ static char stext[256]; | |
+ static int screen; | |
+@@ -258,9 +289,10 @@ static void (*handler[LASTEvent]) (XEvent *) = { | |
+ [MapRequest] = maprequest, | |
+ [MotionNotify] = motionnotify, | |
+ [PropertyNotify] = propertynotify, | |
++ [ResizeRequest] = resizerequest, | |
+ [UnmapNotify] = unmapnotify | |
+ }; | |
+-static Atom wmatom[WMLast], netatom[NetLast]; | |
++static Atom wmatom[WMLast], netatom[NetLast], xatom[XLast]; | |
+ static int running = 1; | |
+ static Cur *cursor[CurLast]; | |
+ static Clr **scheme; | |
+@@ -442,7 +474,7 @@ buttonpress(XEvent *e) | |
+ arg.ui = 1 << i; | |
+ } else if (ev->x < x + TEXTW(selmon->ltsymbol)) | |
+ click = ClkLtSymbol; | |
+- else if (ev->x > selmon->ww - (int)TEXTW(stext)) | |
++ else if (ev->x > selmon->ww - (int)TEXTW(stext) - getsystrayw… | |
+ click = ClkStatusText; | |
+ else | |
+ click = ClkWinTitle; | |
+@@ -485,6 +517,13 @@ cleanup(void) | |
+ XUngrabKey(dpy, AnyKey, AnyModifier, root); | |
+ while (mons) | |
+ cleanupmon(mons); | |
++ | |
++ if (showsystray) { | |
++ XUnmapWindow(dpy, systray->win); | |
++ XDestroyWindow(dpy, systray->win); | |
++ free(systray); | |
++ } | |
++ | |
+ for (i = 0; i < CurLast; i++) | |
+ drw_cur_free(drw, cursor[i]); | |
+ for (i = 0; i < LENGTH(colors); i++) | |
+@@ -516,9 +555,58 @@ cleanupmon(Monitor *mon) | |
+ void | |
+ clientmessage(XEvent *e) | |
+ { | |
++ XWindowAttributes wa; | |
++ XSetWindowAttributes swa; | |
+ XClientMessageEvent *cme = &e->xclient; | |
+ Client *c = wintoclient(cme->window); | |
+ | |
++ if (showsystray && cme->window == systray->win && cme->message_type =… | |
++ /* add systray icons */ | |
++ if (cme->data.l[1] == SYSTEM_TRAY_REQUEST_DOCK) { | |
++ if (!(c = (Client *)calloc(1, sizeof(Client)))) | |
++ die("fatal: could not malloc() %u bytes\n", s… | |
++ if (!(c->win = cme->data.l[2])) { | |
++ free(c); | |
++ return; | |
++ } | |
++ c->mon = selmon; | |
++ c->next = systray->icons; | |
++ systray->icons = c; | |
++ if (!XGetWindowAttributes(dpy, c->win, &wa)) { | |
++ /* use sane defaults */ | |
++ wa.width = bh; | |
++ wa.height = bh; | |
++ wa.border_width = 0; | |
++ } | |
++ c->x = c->oldx = c->y = c->oldy = 0; | |
++ c->w = c->oldw = wa.width; | |
++ c->h = c->oldh = wa.height; | |
++ c->oldbw = wa.border_width; | |
++ c->bw = 0; | |
++ c->isfloating = True; | |
++ /* reuse tags field as mapped status */ | |
++ c->tags = 1; | |
++ updatesizehints(c); | |
++ updatesystrayicongeom(c, wa.width, wa.height); | |
++ XAddToSaveSet(dpy, c->win); | |
++ XSelectInput(dpy, c->win, StructureNotifyMask | Prope… | |
++ XReparentWindow(dpy, c->win, systray->win, 0, 0); | |
++ /* use parents background color */ | |
++ swa.background_pixel = scheme[SchemeNorm][ColBg].pix… | |
++ XChangeWindowAttributes(dpy, c->win, CWBackPixel, &sw… | |
++ sendevent(c->win, netatom[Xembed], StructureNotifyMas… | |
++ /* FIXME not sure if I have to send these events, too… | |
++ sendevent(c->win, netatom[Xembed], StructureNotifyMas… | |
++ sendevent(c->win, netatom[Xembed], StructureNotifyMas… | |
++ sendevent(c->win, netatom[Xembed], StructureNotifyMas… | |
++ XSync(dpy, False); | |
++ resizebarwin(selmon); | |
++ updatesystray(); | |
++ setclientstate(c, NormalState); | |
++ } | |
++ return; | |
++ } | |
++ | |
+ if (!c) | |
+ return; | |
+ if (cme->message_type == netatom[NetWMState]) { | |
+@@ -571,7 +659,7 @@ configurenotify(XEvent *e) | |
+ for (c = m->clients; c; c = c->next) | |
+ if (c->isfullscreen) | |
+ resizeclient(c, m->mx, m->my,… | |
+- XMoveResizeWindow(dpy, m->barwin, m->wx, m->b… | |
++ resizebarwin(m); | |
+ } | |
+ focus(NULL); | |
+ arrange(NULL); | |
+@@ -656,6 +744,11 @@ destroynotify(XEvent *e) | |
+ | |
+ if ((c = wintoclient(ev->window))) | |
+ unmanage(c, 1); | |
++ else if ((c = wintosystrayicon(ev->window))) { | |
++ removesystrayicon(c); | |
++ resizebarwin(selmon); | |
++ updatesystray(); | |
++ } | |
+ } | |
+ | |
+ void | |
+@@ -699,7 +792,7 @@ dirtomon(int dir) | |
+ void | |
+ drawbar(Monitor *m) | |
+ { | |
+- int x, w, tw = 0; | |
++ int x, w, tw = 0, stw = 0; | |
+ int boxs = drw->fonts->h / 9; | |
+ int boxw = drw->fonts->h / 6 + 2; | |
+ unsigned int i, occ = 0, urg = 0; | |
+@@ -708,13 +801,17 @@ drawbar(Monitor *m) | |
+ if (!m->showbar) | |
+ return; | |
+ | |
++ if(showsystray && m == systraytomon(m) && !systrayonleft) | |
++ stw = getsystraywidth(); | |
++ | |
+ /* draw status first so it can be overdrawn by tags later */ | |
+ if (m == selmon) { /* status is only drawn on selected monitor */ | |
+ drw_setscheme(drw, scheme[SchemeNorm]); | |
+- tw = TEXTW(stext) - lrpad + 2; /* 2px right padding */ | |
+- drw_text(drw, m->ww - tw, 0, tw, bh, 0, stext, 0); | |
++ tw = TEXTW(stext) - lrpad / 2 + 2; /* 2px extra right padding… | |
++ drw_text(drw, m->ww - tw - stw, 0, tw, bh, lrpad / 2 - 2, ste… | |
+ } | |
+ | |
++ resizebarwin(m); | |
+ for (c = m->clients; c; c = c->next) { | |
+ occ |= c->tags; | |
+ if (c->isurgent) | |
+@@ -735,7 +832,7 @@ drawbar(Monitor *m) | |
+ drw_setscheme(drw, scheme[SchemeNorm]); | |
+ x = drw_text(drw, x, 0, w, bh, lrpad / 2, m->ltsymbol, 0); | |
+ | |
+- if ((w = m->ww - tw - x) > bh) { | |
++ if ((w = m->ww - tw - stw - x) > bh) { | |
+ if (m->sel) { | |
+ drw_setscheme(drw, scheme[m == selmon ? SchemeSel : S… | |
+ drw_text(drw, x, 0, w, bh, lrpad / 2, m->sel->name, 0… | |
+@@ -746,7 +843,7 @@ drawbar(Monitor *m) | |
+ drw_rect(drw, x, 0, w, bh, 1, 1); | |
+ } | |
+ } | |
+- drw_map(drw, m->barwin, 0, 0, m->ww, bh); | |
++ drw_map(drw, m->barwin, 0, 0, m->ww - stw, bh); | |
+ } | |
+ | |
+ void | |
+@@ -783,8 +880,11 @@ expose(XEvent *e) | |
+ Monitor *m; | |
+ XExposeEvent *ev = &e->xexpose; | |
+ | |
+- if (ev->count == 0 && (m = wintomon(ev->window))) | |
++ if (ev->count == 0 && (m = wintomon(ev->window))) { | |
+ drawbar(m); | |
++ if (m == selmon) | |
++ updatesystray(); | |
++ } | |
+ } | |
+ | |
+ void | |
+@@ -870,14 +970,32 @@ getatomprop(Client *c, Atom prop) | |
+ unsigned char *p = NULL; | |
+ Atom da, atom = None; | |
+ | |
+- if (XGetWindowProperty(dpy, c->win, prop, 0L, sizeof atom, False, XA_… | |
++ /* FIXME getatomprop should return the number of items and a pointer … | |
++ * the stored data instead of this workaround */ | |
++ Atom req = XA_ATOM; | |
++ if (prop == xatom[XembedInfo]) | |
++ req = xatom[XembedInfo]; | |
++ | |
++ if (XGetWindowProperty(dpy, c->win, prop, 0L, sizeof atom, False, req, | |
+ &da, &di, &dl, &dl, &p) == Success && p) { | |
+ atom = *(Atom *)p; | |
++ if (da == xatom[XembedInfo] && dl == 2) | |
++ atom = ((Atom *)p)[1]; | |
+ XFree(p); | |
+ } | |
+ return atom; | |
+ } | |
+ | |
++unsigned int | |
++getsystraywidth() | |
++{ | |
++ unsigned int w = 0; | |
++ Client *i; | |
++ if(showsystray) | |
++ for(i = systray->icons; i; w += i->w + systrayspacing, i = i-… | |
++ return w ? w + systrayspacing : 1; | |
++} | |
++ | |
+ int | |
+ getrootptr(int *x, int *y) | |
+ { | |
+@@ -1018,7 +1136,8 @@ killclient(const Arg *arg) | |
+ { | |
+ if (!selmon->sel) | |
+ return; | |
+- if (!sendevent(selmon->sel, wmatom[WMDelete])) { | |
++ | |
++ if (!sendevent(selmon->sel->win, wmatom[WMDelete], NoEventMask, wmato… | |
+ XGrabServer(dpy); | |
+ XSetErrorHandler(xerrordummy); | |
+ XSetCloseDownMode(dpy, DestroyAll); | |
+@@ -1105,6 +1224,13 @@ maprequest(XEvent *e) | |
+ static XWindowAttributes wa; | |
+ XMapRequestEvent *ev = &e->xmaprequest; | |
+ | |
++ Client *i; | |
++ if ((i = wintosystrayicon(ev->window))) { | |
++ sendevent(i->win, netatom[Xembed], StructureNotifyMask, Curre… | |
++ resizebarwin(selmon); | |
++ updatesystray(); | |
++ } | |
++ | |
+ if (!XGetWindowAttributes(dpy, ev->window, &wa) || wa.override_redire… | |
+ return; | |
+ if (!wintoclient(ev->window)) | |
+@@ -1226,6 +1352,17 @@ propertynotify(XEvent *e) | |
+ Window trans; | |
+ XPropertyEvent *ev = &e->xproperty; | |
+ | |
++ if ((c = wintosystrayicon(ev->window))) { | |
++ if (ev->atom == XA_WM_NORMAL_HINTS) { | |
++ updatesizehints(c); | |
++ updatesystrayicongeom(c, c->w, c->h); | |
++ } | |
++ else | |
++ updatesystrayiconstate(c, ev); | |
++ resizebarwin(selmon); | |
++ updatesystray(); | |
++ } | |
++ | |
+ if ((ev->window == root) && (ev->atom == XA_WM_NAME)) | |
+ updatestatus(); | |
+ else if (ev->state == PropertyDelete) | |
+@@ -1276,6 +1413,19 @@ recttomon(int x, int y, int w, int h) | |
+ return r; | |
+ } | |
+ | |
++void | |
++removesystrayicon(Client *i) | |
++{ | |
++ Client **ii; | |
++ | |
++ if (!showsystray || !i) | |
++ return; | |
++ for (ii = &systray->icons; *ii && *ii != i; ii = &(*ii)->next); | |
++ if (ii) | |
++ *ii = i->next; | |
++ free(i); | |
++} | |
++ | |
+ void | |
+ resize(Client *c, int x, int y, int w, int h, int interact) | |
+ { | |
+@@ -1283,6 +1433,14 @@ resize(Client *c, int x, int y, int w, int h, int inter… | |
+ resizeclient(c, x, y, w, h); | |
+ } | |
+ | |
++void | |
++resizebarwin(Monitor *m) { | |
++ unsigned int w = m->ww; | |
++ if (showsystray && m == systraytomon(m) && !systrayonleft) | |
++ w -= getsystraywidth(); | |
++ XMoveResizeWindow(dpy, m->barwin, m->wx, m->by, w, bh); | |
++} | |
++ | |
+ void | |
+ resizeclient(Client *c, int x, int y, int w, int h) | |
+ { | |
+@@ -1298,6 +1456,19 @@ resizeclient(Client *c, int x, int y, int w, int h) | |
+ XSync(dpy, False); | |
+ } | |
+ | |
++void | |
++resizerequest(XEvent *e) | |
++{ | |
++ XResizeRequestEvent *ev = &e->xresizerequest; | |
++ Client *i; | |
++ | |
++ if ((i = wintosystrayicon(ev->window))) { | |
++ updatesystrayicongeom(i, ev->width, ev->height); | |
++ resizebarwin(selmon); | |
++ updatesystray(); | |
++ } | |
++} | |
++ | |
+ void | |
+ resizemouse(const Arg *arg) | |
+ { | |
+@@ -1444,26 +1615,37 @@ setclientstate(Client *c, long state) | |
+ } | |
+ | |
+ int | |
+-sendevent(Client *c, Atom proto) | |
++sendevent(Window w, Atom proto, int mask, long d0, long d1, long d2, long d3,… | |
+ { | |
+ int n; | |
+- Atom *protocols; | |
++ Atom *protocols, mt; | |
+ int exists = 0; | |
+ XEvent ev; | |
+ | |
+- if (XGetWMProtocols(dpy, c->win, &protocols, &n)) { | |
+- while (!exists && n--) | |
+- exists = protocols[n] == proto; | |
+- XFree(protocols); | |
++ if (proto == wmatom[WMTakeFocus] || proto == wmatom[WMDelete]) { | |
++ mt = wmatom[WMProtocols]; | |
++ if (XGetWMProtocols(dpy, w, &protocols, &n)) { | |
++ while (!exists && n--) | |
++ exists = protocols[n] == proto; | |
++ XFree(protocols); | |
++ } | |
++ } | |
++ else { | |
++ exists = True; | |
++ mt = proto; | |
+ } | |
++ | |
+ if (exists) { | |
+ ev.type = ClientMessage; | |
+- ev.xclient.window = c->win; | |
+- ev.xclient.message_type = wmatom[WMProtocols]; | |
++ ev.xclient.window = w; | |
++ ev.xclient.message_type = mt; | |
+ ev.xclient.format = 32; | |
+- ev.xclient.data.l[0] = proto; | |
+- ev.xclient.data.l[1] = CurrentTime; | |
+- XSendEvent(dpy, c->win, False, NoEventMask, &ev); | |
++ ev.xclient.data.l[0] = d0; | |
++ ev.xclient.data.l[1] = d1; | |
++ ev.xclient.data.l[2] = d2; | |
++ ev.xclient.data.l[3] = d3; | |
++ ev.xclient.data.l[4] = d4; | |
++ XSendEvent(dpy, w, False, mask, &ev); | |
+ } | |
+ return exists; | |
+ } | |
+@@ -1477,7 +1659,7 @@ setfocus(Client *c) | |
+ XA_WINDOW, 32, PropModeReplace, | |
+ (unsigned char *) &(c->win), 1); | |
+ } | |
+- sendevent(c, wmatom[WMTakeFocus]); | |
++ sendevent(c->win, wmatom[WMTakeFocus], NoEventMask, wmatom[WMTakeFocu… | |
+ } | |
+ | |
+ void | |
+@@ -1566,6 +1748,10 @@ setup(void) | |
+ wmatom[WMTakeFocus] = XInternAtom(dpy, "WM_TAKE_FOCUS", False); | |
+ netatom[NetActiveWindow] = XInternAtom(dpy, "_NET_ACTIVE_WINDOW", Fal… | |
+ netatom[NetSupported] = XInternAtom(dpy, "_NET_SUPPORTED", False); | |
++ netatom[NetSystemTray] = XInternAtom(dpy, "_NET_SYSTEM_TRAY_S0", Fals… | |
++ netatom[NetSystemTrayOP] = XInternAtom(dpy, "_NET_SYSTEM_TRAY_OPCODE"… | |
++ netatom[NetSystemTrayOrientation] = XInternAtom(dpy, "_NET_SYSTEM_TRA… | |
++ netatom[NetSystemTrayOrientationHorz] = XInternAtom(dpy, "_NET_SYSTEM… | |
+ netatom[NetWMName] = XInternAtom(dpy, "_NET_WM_NAME", False); | |
+ netatom[NetWMState] = XInternAtom(dpy, "_NET_WM_STATE", False); | |
+ netatom[NetWMCheck] = XInternAtom(dpy, "_NET_SUPPORTING_WM_CHECK", Fa… | |
+@@ -1573,6 +1759,9 @@ setup(void) | |
+ netatom[NetWMWindowType] = XInternAtom(dpy, "_NET_WM_WINDOW_TYPE", Fa… | |
+ netatom[NetWMWindowTypeDialog] = XInternAtom(dpy, "_NET_WM_WINDOW_TYP… | |
+ netatom[NetClientList] = XInternAtom(dpy, "_NET_CLIENT_LIST", False); | |
++ xatom[Manager] = XInternAtom(dpy, "MANAGER", False); | |
++ xatom[Xembed] = XInternAtom(dpy, "_XEMBED", False); | |
++ xatom[XembedInfo] = XInternAtom(dpy, "_XEMBED_INFO", False); | |
+ /* init cursors */ | |
+ cursor[CurNormal] = drw_cur_create(drw, XC_left_ptr); | |
+ cursor[CurResize] = drw_cur_create(drw, XC_sizing); | |
+@@ -1581,6 +1770,8 @@ setup(void) | |
+ scheme = ecalloc(LENGTH(colors), sizeof(Clr *)); | |
+ for (i = 0; i < LENGTH(colors); i++) | |
+ scheme[i] = drw_scm_create(drw, colors[i], 3); | |
++ /* init system tray */ | |
++ updatesystray(); | |
+ /* init bars */ | |
+ updatebars(); | |
+ updatestatus(); | |
+@@ -1711,7 +1902,18 @@ togglebar(const Arg *arg) | |
+ { | |
+ selmon->showbar = !selmon->showbar; | |
+ updatebarpos(selmon); | |
+- XMoveResizeWindow(dpy, selmon->barwin, selmon->wx, selmon->by, selmon… | |
++ resizebarwin(selmon); | |
++ if (showsystray) { | |
++ XWindowChanges wc; | |
++ if (!selmon->showbar) | |
++ wc.y = -bh; | |
++ else if (selmon->showbar) { | |
++ wc.y = 0; | |
++ if (!selmon->topbar) | |
++ wc.y = selmon->mh - bh; | |
++ } | |
++ XConfigureWindow(dpy, systray->win, CWY, &wc); | |
++ } | |
+ arrange(selmon); | |
+ } | |
+ | |
+@@ -1807,11 +2009,18 @@ unmapnotify(XEvent *e) | |
+ else | |
+ unmanage(c, 0); | |
+ } | |
++ else if ((c = wintosystrayicon(ev->window))) { | |
++ /* KLUDGE! sometimes icons occasionally unmap their windows, … | |
++ * _not_ destroy them. We map those windows back */ | |
++ XMapRaised(dpy, c->win); | |
++ updatesystray(); | |
++ } | |
+ } | |
+ | |
+ void | |
+ updatebars(void) | |
+ { | |
++ unsigned int w; | |
+ Monitor *m; | |
+ XSetWindowAttributes wa = { | |
+ .override_redirect = True, | |
+@@ -1822,10 +2031,15 @@ updatebars(void) | |
+ for (m = mons; m; m = m->next) { | |
+ if (m->barwin) | |
+ continue; | |
+- m->barwin = XCreateWindow(dpy, root, m->wx, m->by, m->ww, bh,… | |
++ w = m->ww; | |
++ if (showsystray && m == systraytomon(m)) | |
++ w -= getsystraywidth(); | |
++ m->barwin = XCreateWindow(dpy, root, m->wx, m->by, w, bh, 0, … | |
+ CopyFromParent, DefaultVisual(dpy, screen), | |
+ CWOverrideRedirect|CWBackPixmap|CWEventMask, … | |
+ XDefineCursor(dpy, m->barwin, cursor[CurNormal]->cursor); | |
++ if (showsystray && m == systraytomon(m)) | |
++ XMapRaised(dpy, systray->win); | |
+ XMapRaised(dpy, m->barwin); | |
+ XSetClassHint(dpy, m->barwin, &ch); | |
+ } | |
+@@ -2002,6 +2216,125 @@ updatestatus(void) | |
+ if (!gettextprop(root, XA_WM_NAME, stext, sizeof(stext))) | |
+ strcpy(stext, "dwm-"VERSION); | |
+ drawbar(selmon); | |
++ updatesystray(); | |
++} | |
++ | |
++ | |
++void | |
++updatesystrayicongeom(Client *i, int w, int h) | |
++{ | |
++ if (i) { | |
++ i->h = bh; | |
++ if (w == h) | |
++ i->w = bh; | |
++ else if (h == bh) | |
++ i->w = w; | |
++ else | |
++ i->w = (int) ((float)bh * ((float)w / (float)h)); | |
++ applysizehints(i, &(i->x), &(i->y), &(i->w), &(i->h), False); | |
++ /* force icons into the systray dimensions if they don't want… | |
++ if (i->h > bh) { | |
++ if (i->w == i->h) | |
++ i->w = bh; | |
++ else | |
++ i->w = (int) ((float)bh * ((float)i->w / (flo… | |
++ i->h = bh; | |
++ } | |
++ } | |
++} | |
++ | |
++void | |
++updatesystrayiconstate(Client *i, XPropertyEvent *ev) | |
++{ | |
++ long flags; | |
++ int code = 0; | |
++ | |
++ if (!showsystray || !i || ev->atom != xatom[XembedInfo] || | |
++ !(flags = getatomprop(i, xatom[XembedInfo]))) | |
++ return; | |
++ | |
++ if (flags & XEMBED_MAPPED && !i->tags) { | |
++ i->tags = 1; | |
++ code = XEMBED_WINDOW_ACTIVATE; | |
++ XMapRaised(dpy, i->win); | |
++ setclientstate(i, NormalState); | |
++ } | |
++ else if (!(flags & XEMBED_MAPPED) && i->tags) { | |
++ i->tags = 0; | |
++ code = XEMBED_WINDOW_DEACTIVATE; | |
++ XUnmapWindow(dpy, i->win); | |
++ setclientstate(i, WithdrawnState); | |
++ } | |
++ else | |
++ return; | |
++ sendevent(i->win, xatom[Xembed], StructureNotifyMask, CurrentTime, co… | |
++ systray->win, XEMBED_EMBEDDED_VERSION); | |
++} | |
++ | |
++void | |
++updatesystray(void) | |
++{ | |
++ XSetWindowAttributes wa; | |
++ XWindowChanges wc; | |
++ Client *i; | |
++ Monitor *m = systraytomon(NULL); | |
++ unsigned int x = m->mx + m->mw; | |
++ unsigned int sw = TEXTW(stext) - lrpad + systrayspacing; | |
++ unsigned int w = 1; | |
++ | |
++ if (!showsystray) | |
++ return; | |
++ if (systrayonleft) | |
++ x -= sw + lrpad / 2; | |
++ if (!systray) { | |
++ /* init systray */ | |
++ if (!(systray = (Systray *)calloc(1, sizeof(Systray)))) | |
++ die("fatal: could not malloc() %u bytes\n", sizeof(Sy… | |
++ systray->win = XCreateSimpleWindow(dpy, root, x, m->by, w, bh… | |
++ wa.event_mask = ButtonPressMask | ExposureMask; | |
++ wa.override_redirect = True; | |
++ wa.background_pixel = scheme[SchemeNorm][ColBg].pixel; | |
++ XSelectInput(dpy, systray->win, SubstructureNotifyMask); | |
++ XChangeProperty(dpy, systray->win, netatom[NetSystemTrayOrien… | |
++ PropModeReplace, (unsigned char *)&netatom[Ne… | |
++ XChangeWindowAttributes(dpy, systray->win, CWEventMask|CWOver… | |
++ XMapRaised(dpy, systray->win); | |
++ XSetSelectionOwner(dpy, netatom[NetSystemTray], systray->win,… | |
++ if (XGetSelectionOwner(dpy, netatom[NetSystemTray]) == systra… | |
++ sendevent(root, xatom[Manager], StructureNotifyMask, … | |
++ XSync(dpy, False); | |
++ } | |
++ else { | |
++ fprintf(stderr, "dwm: unable to obtain system tray.\n… | |
++ free(systray); | |
++ systray = NULL; | |
++ return; | |
++ } | |
++ } | |
++ for (w = 0, i = systray->icons; i; i = i->next) { | |
++ /* make sure the background color stays the same */ | |
++ wa.background_pixel = scheme[SchemeNorm][ColBg].pixel; | |
++ XChangeWindowAttributes(dpy, i->win, CWBackPixel, &wa); | |
++ XMapRaised(dpy, i->win); | |
++ w += systrayspacing; | |
++ i->x = w; | |
++ XMoveResizeWindow(dpy, i->win, i->x, 0, i->w, i->h); | |
++ w += i->w; | |
++ if (i->mon != m) | |
++ i->mon = m; | |
++ } | |
++ w = w ? w + systrayspacing : 1; | |
++ x -= w; | |
++ XMoveResizeWindow(dpy, systray->win, x, m->by, w, bh); | |
++ wc.x = x; wc.y = m->by; wc.width = w; wc.height = bh; | |
++ wc.stack_mode = Above; wc.sibling = m->barwin; | |
++ XConfigureWindow(dpy, systray->win, CWX|CWY|CWWidth|CWHeight|CWSiblin… | |
++ XMapWindow(dpy, systray->win); | |
++ XMapSubwindows(dpy, systray->win); | |
++ /* redraw background */ | |
++ XSetForeground(dpy, drw->gc, scheme[SchemeNorm][ColBg].pixel); | |
++ XFillRectangle(dpy, systray->win, drw->gc, 0, 0, w, bh); | |
++ XSync(dpy, False); | |
+ } | |
+ | |
+ void | |
+@@ -2069,6 +2402,16 @@ wintoclient(Window w) | |
+ return NULL; | |
+ } | |
+ | |
++Client * | |
++wintosystrayicon(Window w) { | |
++ Client *i = NULL; | |
++ | |
++ if (!showsystray || !w) | |
++ return i; | |
++ for (i = systray->icons; i && i->win != w; i = i->next) ; | |
++ return i; | |
++} | |
++ | |
+ Monitor * | |
+ wintomon(Window w) | |
+ { | |
+@@ -2122,6 +2465,22 @@ xerrorstart(Display *dpy, XErrorEvent *ee) | |
+ return -1; | |
+ } | |
+ | |
++Monitor * | |
++systraytomon(Monitor *m) { | |
++ Monitor *t; | |
++ int i, n; | |
++ if(!systraypinning) { | |
++ if(!m) | |
++ return selmon; | |
++ return m == selmon ? m : NULL; | |
++ } | |
++ for(n = 1, t = mons; t && t->next; n++, t = t->next) ; | |
++ for(i = 1, t = mons; t && t->next && i < systraypinning; i++, t = t->… | |
++ if(systraypinningfailfirst && n < systraypinning) | |
++ return mons; | |
++ return t; | |
++} | |
++ | |
+ void | |
+ zoom(const Arg *arg) | |
+ { |