├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── doc └── sara.1 ├── examples ├── .xinitrc ├── sara-tags.sh └── sxhkdrc ├── patches ├── tomonabs.patch └── xinerama.patch └── src ├── common.c ├── common.h ├── config.def.h ├── sara.c └── sarasock.c /.gitignore: -------------------------------------------------------------------------------- 1 | config.h 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Cuty And Tiny Window Manager (catwm) 2 | ______________________________________________________________________________ 3 | 4 | Copyright (c) 2010, Rinaldini Julien, julien.rinaldini@heig-vd.ch 5 | 6 | sara Window Manager 7 | ______________________________________________________________________________ 8 | 9 | Copyright (c) 2019-2021, This Fackin Guy, gitluin on github (no email for you!) 10 | 11 | Dynamic Window Manager (dwm) 12 | ______________________________________________________________________________ 13 | 14 | MIT/X Consortium License 15 | 16 | © 2006-2020 Anselm R Garbe 17 | © 2006-2009 Jukka Salmi 18 | © 2006-2007 Sander van Dijk 19 | © 2007-2011 Peter Hartlich 20 | © 2007-2009 Szabolcs Nagy 21 | © 2007-2009 Christof Musik 22 | © 2007-2009 Premysl Hruby 23 | © 2007-2008 Enno Gottox Boland 24 | © 2008 Martin Hurton 25 | © 2008 Neale Pickett 26 | © 2009 Mate Nagy 27 | © 2010-2016 Hiltjo Posthuma 28 | © 2010-2012 Connor Lane Smith 29 | © 2011 Christoph Lohmann <20h@r-36.net> 30 | © 2015-2016 Quentin Rameau 31 | © 2015-2016 Eric Pruitt 32 | © 2016-2017 Markus Teich 33 | 34 | Permission is hereby granted, free of charge, to any person obtaining a 35 | copy of this software and associated documentation files (the "Software"), 36 | to deal in the Software without restriction, including without limitation 37 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 38 | and/or sell copies of the Software, and to permit persons to whom the 39 | Software is furnished to do so, subject to the following conditions: 40 | 41 | The above copyright notice and this permission notice shall be included in 42 | all copies or substantial portions of the Software. 43 | 44 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 45 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 46 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 47 | THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 48 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 49 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 50 | DEALINGS IN THE SOFTWARE. 51 | 52 | Binary Space Partioning Window Manager (bspwm) 53 | ______________________________________________________________________________ 54 | 55 | Copyright (c) 2012, Bastien Dejean 56 | All rights reserved. 57 | 58 | Redistribution and use in source and binary forms, with or without 59 | modification, are permitted provided that the following conditions are met: 60 | 61 | 1. Redistributions of source code must retain the above copyright notice, this 62 | list of conditions and the following disclaimer. 63 | 2. Redistributions in binary form must reproduce the above copyright notice, 64 | this list of conditions and the following disclaimer in the documentation 65 | and/or other materials provided with the distribution. 66 | 67 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 68 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 69 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 70 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 71 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 72 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 73 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 74 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 75 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 76 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 77 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | PREFIX?= /usr/local 2 | BINDIR?= $(PREFIX)/bin 3 | MANDIR?= ${PREFIX}/share/man 4 | DOCDIR?= doc 5 | 6 | CFLAGS= -std=c99 -Wall -Wno-deprecated-declarations -D_POSIX_C_SOURCE=200809L -DXINERAMA -Os 7 | INCFLAGS= -I/usr/include/freetype2 8 | LIBS= -lX11 -lXft -lXinerama -lXext 9 | 10 | SARASRC= sara.c common.c 11 | SARAOBJ= ${SARASRC:.c=.o} 12 | 13 | SOCKSRC= sarasock.c common.c 14 | SOCKOBJ= ${SOCKSRC:.c=.o} 15 | 16 | all: sara sarasock man 17 | 18 | VPATH=src 19 | 20 | config.h: 21 | cp src/config.def.h src/config.h 22 | 23 | .c.o: 24 | ${CC} -c ${CFLAGS} ${INCFLAGS} $< 25 | 26 | ${SARAOBJ}: config.h common.h 27 | ${SOCKOBJ}: common.h 28 | 29 | sara: ${SARAOBJ} 30 | ${CC} -o $@ ${SARAOBJ} ${LIBS} 31 | 32 | sarasock: ${SOCKOBJ} 33 | ${CC} -o $@ ${SOCKOBJ} ${LIBS} 34 | 35 | man: 36 | install -Dm 644 $(DOCDIR)/sara.1 $(MANDIR)/man1 37 | 38 | install: all 39 | install -Dsm 755 sara $(BINDIR)/sara 40 | install -Dsm 755 sarasock $(BINDIR)/sarasock 41 | 42 | uninstall: 43 | rm -f $(BINDIR)/sara 44 | rm -f $(BINDIR)/sarasock 45 | rm -f $(MANDIR)/man1/sara.1 46 | 47 | clean: 48 | rm -f sara sarasock *.o 49 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | sara 2 | ===== 3 | NOTE: This project is no longer actively maintained and has been archived. Thanks for all the fish! I might return to this in the future. 4 | 5 | Description 6 | ------- 7 | sara is a simple and lightweight dynamic window manager. What started out as an attempt to make catwm into a small and fully-functional window manager has turned into my primary project in C. At the moment, sara combines aspects of dwm and bspwm with some custom features. As my needs/wants change, so too will the program, and probably how much you like it. 8 | 9 | If things like [dwm-ipc](https://github.com/mihirlad55/dwm-ipc) scare the hell out of you because they're so big, this window manager might be for you. You want a window manager that works like a souped-up dwm with sane defaults that is also opinionated like all the cool kids. I think the combination of the tag system, the flexibility of sxhkd and IPC interaction, and the ease of parsing the window manager's internal state are good selling points. 10 | 11 | Features 12 | ----- 13 | ### Default Modes 14 | * Tiling mode (master window with right-hand vertical stack). 15 | * Monocle mode (fullscreen, but bar area still visible). 16 | * Floating mode (clients freely set coordinates). 17 | * Fullscreen mode (see Design Limitations). 18 | 19 | ### Behavior/Traits 20 | * dwm-like: 21 | * tags. 22 | * Multihead support through Xinerama. 23 | * patch-like: 24 | * attachaside 25 | * movestack 26 | * pertag-like: layouts, master_size 27 | * restartsig 28 | * singularborders 29 | * vanitygaps 30 | * bspwm-like: 31 | * IPC-based interaction via a client program, `sarasock`. 32 | * keyboard event handling via external program (like `sxhkd`). 33 | * output desktop information to the X server for external parsing (like with shell script + `lemonbar`/`polybar`). 34 | * no window borders. 35 | * no window titles. 36 | 37 | ### Design Limitations 38 | * No support for urgency, because nothing I do is urgent. 39 | * No support for iconified (i.e. "minimized") clients. 40 | * No [ICCCM](https://web.archive.org/web/20190617214524/https://raw.githubusercontent.com/kfish/xsel/1a1c5edf0dc129055f7764c666da2dd468df6016/rant.txt). This is mostly felt in the lack of the applysizehints behavior that dwm has (ex. cmatrix won't redraw using larger window bounds if you give it more space via togglefs, changemsize, etc.). 41 | * No EWMH support. Fullscreening is done manually (see examples/sxhkdrc). 42 | 43 | 44 | Help Me (Keybindings, Installation, Etc.)! 45 | ------------------------------------------- 46 | `man sara` 47 | 48 | If that doesn't answer your question, check out the [wiki](https://github.com/gitluin/sara/wiki)! 49 | 50 | Version History 51 | ------ 52 | * v4.2 - `config.h` now supports running anything via shell script on startup like a traditional `rc` file: just add new lines to the `const char* progs[]` array in `config.h`! `sara` also now supports reloading this config file on-the-fly, without restarting, thanks to bringing in the `dwm` approach to "adopting" unmanaged windows and much of the `restartsig` patch (anything specified in `progs[]` will be re-run, FYI!). And `sarasock` no longer requires quoting all its arguments, though you can still do so! Updated directory structure for the repository so it's less messy. 53 | * v4.1 - Now sources a config file, `$XDG_CONFIG_HOME/sara/sararc` or `$HOME/.config/sara/sararc`, which is a shell script that specifies programs to start à la `bspwm`. My plan is to update `sarasock` to configure some variables at runtime in a similar fashion. Also including a `polybar` script for tag information that has support for clickable areas. 54 | * v4.0 - A man page! Lots of """"bloat""""; `polybar` is now the suggested default! Huge shoutout to [Jonas](https://jonas-langlotz.de/2020/10/05/polybar-on-dwm) for the only post on his blog single-handedly making my `peachbar` problems obsolete. 55 | * v3.0 - Internal bar removed and bar scripts created. Floating layout. More Zoom-friendly client handling (nothing is sacred). Fixed longstanding math issues with `tile()`. 56 | * v2.0 - Keybinds are controlled by sxhkd now! A little bit of bspwm never hurt anyone. Pointer events (click into window, move and resize window) still handled internally. 57 | * v1.0 - Finished it enough to share with the class. At this stage, very like dwm with some patches (see above) built-in, some cleaned-up code (and some not), some cut corners (not as much support for Atoms), but all is well and functional. 58 | 59 | Bugs 60 | ---- 61 | * Reinitializing monitors should update focus to a client and reset enternotify status. 62 | * Sometimes after using physlock + betterlockscreen, clients need a redraw and don't do it until toggled/moved around. 63 | * Can't round corners on some clients like `chromium`. 64 | 65 | To Do 66 | ---- 67 | * Fix bugs. 68 | * External pointer management: can `sxhkd` reasonably do this? 69 | * Partial standards compliance so things like `rofi -m -4` works. 70 | * Convert to XCB, starting with where it will improve performance. 71 | 72 | Please submit bug reports! I've only tested this on my own system. 73 | -------------------------------------------------------------------------------- /doc/sara.1: -------------------------------------------------------------------------------- 1 | .TH sara 1 "" "" "User Commands" 2 | .SH NAME 3 | \fBsara\fR - Another X11 dynamic window manager. Doesn't even use XCB (yet). Also not EWMH/ICCCM compliant. 4 | 5 | fight me 6 | 7 | .SH SYNOPSIS 8 | \fBsara\fR 9 | 10 | Dynamically manages windows à la \fBdwm\fR, with a dash of \fBbspwm\fR/\fBmonsterwm\fR flavor for interacting with it. \fBsara\fR receives commands on a dedicated socket set in \fBconfig.h\fR, which defaults to \fB/tmp/sara.sock\fR. Commands are written to the socket using the companion program \fBsarasock\fR. Despite the nomenclature, "desktops" behave like tags. 11 | 12 | \fBsarasock\fR [\fIcommand\fR] 13 | 14 | Communicates commands to \fBsara\fR via a socket. 15 | 16 | .SH COMMANDS 17 | \fBsara\fR takes no command line arguments at start, but does output state information as Xatom strings. Use \fBxprop -spy -root\fR to see them! This is useful for bar scripts, and provides an easy way to \fBwhile read line\fR. 18 | 19 | \fBsarasock\fR passes arbitrary text to \fBsara\fR, but \fBsara\fR will only accept a predefined set of commands (defined in the \fBconversions\fR enum) and will only copy \fBMAXBUFF\fR characters of the message (defaults to 18, set at compile time). \fBsara\fR checks that a message has a function and an argument, and will not execute it otherwise, which is why commands like \fBkillclient\fR require a passthrough value. 20 | 21 | .TP 22 | \fBchangemsize\fR \fI+/-[0.05,0.95]\fR 23 | Increment the horizontal size of the master area by the amount given. Can be either positive or negative. If the result would lead to a value greater than 0.95 or less than 0.05 of the screen width, it will not be changed. 24 | .TP 25 | \fBfocusmon\fR \fI+/-1\fR 26 | Change focus to the next (+) or previous (-) monitor. Will not move the cursor. 27 | .TP 28 | \fBkillclient\fR \fI0\fR 29 | Kill the currently selected client. 0 is used as a passthrough. 30 | .TP 31 | \fBmoveclient\fR \fI+/-1\fR 32 | Move the currently selected client up (+) or down (-) the client list. 33 | .TP 34 | \fBmovefocus\fR \fI+/-1\fR 35 | Move focus up (+) or down (-) the client list. 36 | .TP 37 | \fBquit\fR \fI{0,1}\fR 38 | Safely quit (0) or restart (1) \fBsara\fR. 39 | .TP 40 | \fBtodesktop\fR \fI[0-8]\fR 41 | Send the currently selected client to only the specified tag. The number range will depend on how many tags you have allowed in \fBconfig.h\fR. 42 | .TP 43 | \fBtoggledesktop\fR \fI[0-8]/-1\fR 44 | Tag the currently selected client with the specified tag. If the client has already been tagged with that number, it will untag it (hence "toggle"). The number range will depend on how many tags you have allowed in \fBconfig.h\fR. The sepcial value \fI-1\fR can also be used to add the client to all tags (note that this will \fInot\fR toggle the state of any tags already set for the client). 45 | .TP 46 | \fBtogglefloat\fR \fI0\fR 47 | Toggle the floating state of the currently selected client. 0 is used as a passthrough. 48 | .TP 49 | \fBtogglefs\fR \fI0\fR 50 | Toggle the fullscreen state of the currently selected client. 0 is used as a passthrough. 51 | .TP 52 | \fBtoggleview\fR \fI[0-8]/-1\fR 53 | Toggle the specified tag into/out of view. The number range will depend on how many tags you have allowed in \fBconfig.h\fR. The special value of \fI-1\fR can also be used to view all tags. 54 | .TP 55 | \fBtomon\fR \fI+/-1\fR 56 | Send the currently selected client to the next (+) or the previous (-) monitor. If on the first monitor, previous will loop around and send it to the last monitor. 57 | .TP 58 | \fBsetlayout\fR \fIname\fR 59 | Set the layout for the currently selected tag to the specified layout. Layouts must be identified at compile time in \fBconfig.h\fR and have a corresponding function in \fBsara.c\fR. This mirrors the behavior of the \fBdwm's pertag\fR patch. 60 | .TP 61 | \fBview\fR \fI[0-8]\fR 62 | View the specified tag. The number range will depend on how many tags you have allowed in \fBconfig.h\fR. 63 | .TP 64 | \fBzoom\fR \fI0\fR 65 | Promote the currently selected client to the master area. 0 is used as a passthrough. 66 | 67 | .SH EXAMPLE 68 | .B sarasock changemsize +0.05 69 | 70 | Tell \fBsarasock\fR to write to the compile-time specified socket, telling \fBsara\fR to increase the width of the master area by 5% of the screen width. 71 | 72 | .SH AUTHOR 73 | \fBsara\fR and \fBsarasock\fR were written by \fBgitluin\fR. Visit \fIhttps://github.com/gitluin/sara\fR for more detailed licensing information, as this program would not exist without several others! 74 | -------------------------------------------------------------------------------- /examples/.xinitrc: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | exec sara 4 | -------------------------------------------------------------------------------- /examples/sara-tags.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Characters are from IcoMoon 4 | 5 | #TAGS="I:II:III:IV:V:VI:VII:VIII:IX" 6 | #OTAGS="$TAGS" 7 | #STAGS="$TAGS" 8 | TAGS="\uea56:\uea56:\uea56:\uea56:\uea56:\uea56:\uea56:\uea56:\uea56" 9 | OTAGS="\uea54:\uea54:\uea54:\uea54:\uea54:\uea54:\uea54:\uea54:\uea54" 10 | STAGS="\uea55:\uea55:\uea55:\uea55:\uea55:\uea55:\uea55:\uea55:\uea55" 11 | NTAGS="$TAGS" 12 | OCCFG="#CBC19C" 13 | SELFG="#CBC19C" 14 | TAGDELIMF=" " 15 | TAGDELIMB="$TAGDELIMF" 16 | LTDELIMF=" " 17 | LTDELIMB="$LTDELIMF" 18 | 19 | LTBUTTONSTART="%{A1:sarasock setlayout tile:}%{A3:sarasock setlayout monocle:}" 20 | LTBUTTONEND="%{A}%{A}" 21 | 22 | # This man is a god 23 | # https://jonas-langlotz.de/2020/10/05/polybar-on-dwm 24 | xprop -spy -root "SARA_MONSTATE_$MONITOR" 2>/dev/null | { 25 | while read line; do 26 | # SARA_MONSTATE_0 = "SONNNNNNN:T" 27 | MONLINE="$(echo $line | cut -d' ' -f3 | sed 's/\"//g')" 28 | # DeskState:LayoutSymbol 29 | DESKSTATE="$(echo $MONLINE | cut -d':' -f1)" 30 | LAYOUTSYM="$(echo $MONLINE | cut -d':' -f2)" 31 | 32 | # Generate call to insert correct tags by using line numbers 33 | # (have to subtract 1, though) 34 | TAGSTR="$(echo $DESKSTATE | \ 35 | sed 's/\(.\)/\1\n/g' | \ 36 | sed '/^$/d' | \ 37 | cat -n | \ 38 | sed "s/^ \([0-9]\).\(.*\)/{{%{A1:sarasock view \$((\1 - 1)):}%{A3:sarasock toggleview \$((\1 - 1)):}${TAGDELIMF}\$(echo $\\2TAGS \| cut -d":" -f\1)${TAGDELIMB}%{A}%{A}}}/g")" 39 | 40 | # Insert colors 41 | TAGSTR="$(echo "$TAGSTR" | sed 's/{{\([^}}]*STAGS[^}}]*\)}}/%{F$SELFG}%{B$SELBG}{{\1}}%{F-}%{B-}/g')" 42 | TAGSTR="$(echo "$TAGSTR" | sed 's/{{\([^}}]*OTAGS[^}}]*\)}}/%{F$OCCFG}%{B$OCCBG}{{\1}}%{F-}%{B-}/g')" 43 | TAGSTR="$(echo "$TAGSTR" | sed 's/{{\([^}}]*NTAGS[^}}]*\)}}/%{F-}%{B-}{{\1}}/g')" 44 | 45 | # Remove newlines 46 | TAGSTR="$(echo $TAGSTR | sed 's/}} {{//g' | sed 's/{{//g' | sed 's/}}//g')" 47 | 48 | # Perform cut operations, set correct view numbers 49 | TAGSTR="$(eval echo "$TAGSTR")" 50 | 51 | # Layout stuff 52 | if test "$LAYOUTSYM" = "T"; then 53 | LAYOUTSYM="\ue964" 54 | elif test "$LAYOUTSYM" = "M"; then 55 | LAYOUTSYM="\ue91f" 56 | elif test "$LAYOUTSYM" = "F"; then 57 | LAYOUTSYM="\ue9c1" 58 | fi 59 | TAGSTR="${TAGSTR}${LTBUTTONSTART}${LTDELIMF}$LAYOUTSYM${LTDELIMB}${LTBUTTONEND}%{B$BARBG}%{F$BARFG}" 60 | 61 | echo -e "${TAGSTR}" 62 | done 63 | } 64 | -------------------------------------------------------------------------------- /examples/sxhkdrc: -------------------------------------------------------------------------------- 1 | # ############### 2 | # general hotkeys 3 | # ############### 4 | 5 | # ############### 6 | # spawn programs 7 | 8 | super + t 9 | $TERMINAL 10 | 11 | super + w 12 | $BROWSER 13 | 14 | super + d 15 | dmenu_run -m "-1" -b -l 4 -p "Run" -fn "Misc Termsyn:size=10" -nb "#282a36" -nf "#f8f8f2" -sb "#1E88E5" -sf "#202020" 16 | 17 | # ############### 18 | # sara hotkeys 19 | # ############### 20 | 21 | # ############### 22 | # sara interfacing 23 | 24 | # quit sara (kill X) 25 | super + shift + e 26 | sarasock quit 0 27 | 28 | # restart sara - will re-run progs 29 | super + shift + e 30 | sarasock quit 1 31 | 32 | # ############### 33 | # Client interfacing 34 | 35 | # kill client 36 | super + shift + q 37 | sarasock killclient 0 38 | 39 | # move focus down/up 40 | super + {j,k} 41 | sarasock movefocus {-,+}1 42 | 43 | # move client down/up 44 | super + shift + {j,k} 45 | sarasock moveclient {-,+}1 46 | 47 | # toggle client to floating 48 | super + shift + space 49 | sarasock togglefloat 0 50 | 51 | # toggle client to fullscreen 52 | super + shift + Return 53 | sarasock togglefs 0 54 | 55 | # promote client to master 56 | super + Return 57 | sarasock zoom 0 58 | 59 | # ############### 60 | # Desktop interfacing 61 | 62 | # view desktop 63 | super + {1-9} 64 | sarasock view {0-8} 65 | 66 | # add client to desktop 67 | super + shift + {1-9} 68 | sarasock toggledesktop {0-8} 69 | 70 | # add desktop to current view 71 | super + control + {1-9} 72 | sarasock toggleview {0-8} 73 | 74 | # send client to just desktop 75 | super + shift + control + {1-9} 76 | sarasock todesktop {0-8} 77 | 78 | # view all desktops 79 | super + 0 80 | sarasock toggleview -1 81 | 82 | # decrease/increase master area size 83 | super + {h,l} 84 | sarasock changemsize {-,+}0.05 85 | 86 | # set layouts 87 | super + control + {t,m} 88 | sarasock setlayout {tile,monocle} 89 | 90 | # ############### 91 | # Monitor interfacing 92 | 93 | # focus left/right monitor 94 | super + {comma,period} 95 | sarasock focusmon {-,+}1 96 | 97 | # send client to left/right monitor 98 | super + shift + {comma,period} 99 | sarasock tomon {-,+}1 100 | -------------------------------------------------------------------------------- /patches/tomonabs.patch: -------------------------------------------------------------------------------- 1 | diff --git a/sara.c b/sara.c 2 | index f8295ef..919af7c 100644 3 | --- a/sara.c 4 | +++ b/sara.c 5 | @@ -213,6 +213,7 @@ static void toggledesktop(const Arg arg); 6 | static void togglefloat(const Arg arg); 7 | static void togglefs(const Arg arg); 8 | static void tomon(const Arg arg); 9 | +static void tomonabs(const Arg arg); 10 | static void unmanage(client* c); 11 | static void updatefocus(monitor* m); 12 | static void zoom(const Arg arg); 13 | @@ -222,8 +223,10 @@ static void cleanupmon(monitor* m); 14 | static monitor* coordstomon(int x, int y); 15 | static monitor* createmon(int num, int x, int y, int w, int h); 16 | static monitor* dirtomon(int dir); 17 | +static monitor* numtomon(int num); 18 | static monitor* findmon(Window w); 19 | static void focusmon(const Arg arg); 20 | +static void focusmonabs(const Arg arg); 21 | static void updategeom(); 22 | /* Client Interfacing */ 23 | static client* findclient(Window w); 24 | @@ -257,6 +260,7 @@ struct { 25 | } conversions [] = { 26 | {changemsize, "changemsize"}, 27 | {focusmon, "focusmon"}, 28 | + {focusmonabs, "focusmonabs"}, 29 | {killclient, "killclient"}, 30 | {moveclient, "moveclient"}, 31 | {movefocus, "movefocus"}, 32 | @@ -267,6 +271,7 @@ struct { 33 | {togglefs, "togglefs"}, 34 | {toggleview, "toggleview"}, 35 | {tomon, "tomon"}, 36 | + {tomonabs, "tomonabs"}, 37 | {setlayout, "setlayout"}, 38 | {view, "view"}, 39 | {zoom, "zoom"}, 40 | @@ -1001,6 +1006,18 @@ tomon(const Arg arg){ 41 | outputstats(); 42 | } 43 | 44 | +void 45 | +tomonabs(const Arg arg){ 46 | + if (!curmon->current || !mhead->next) 47 | + return; 48 | + 49 | + parser[WantInt](arg.s, &parg); 50 | + 51 | + sendmon(curmon->current, numtomon(parg.i)); 52 | + outputstats(); 53 | +} 54 | + 55 | + 56 | void 57 | unmanage(client* c){ 58 | monitor* m = c->mon; 59 | @@ -1114,6 +1131,16 @@ dirtomon(int dir){ 60 | return m; 61 | } 62 | 63 | +monitor* 64 | +numtomon(int num){ 65 | + for EACHMON(mhead) 66 | + if (im->num == num) 67 | + return im; 68 | + 69 | + return NULL; 70 | +} 71 | + 72 | + 73 | monitor* 74 | findmon(Window w){ 75 | for EACHMON(mhead) 76 | @@ -1138,6 +1165,20 @@ focusmon(const Arg arg){ 77 | changemon(m, YesFocus); 78 | } 79 | 80 | +void 81 | +focusmonabs(const Arg arg){ 82 | + monitor* m; 83 | + 84 | + if (!mhead->next) 85 | + return; 86 | + 87 | + parser[WantInt](arg.s, &parg); 88 | + 89 | + if ( (m = numtomon(parg.i)) && m == curmon ) 90 | + return; 91 | + changemon(m, YesFocus); 92 | +} 93 | + 94 | #ifdef XINERAMA 95 | static int isuniquegeom(XineramaScreenInfo* unique, size_t n, XineramaScreenInfo* info){ 96 | while (n--) 97 | -------------------------------------------------------------------------------- /patches/xinerama.patch: -------------------------------------------------------------------------------- 1 | --- sara_b.c 2020-04-07 22:01:53.994052589 -0400 2 | +++ sara_a.c 2020-04-07 22:01:06.520719786 -0400 3 | @@ -18,6 +18,9 @@ 4 | /* Xlib */ 5 | #include 6 | #include 7 | +#ifdef XINERAMA 8 | +#include 9 | +#endif 10 | 11 | #define BUTTONMASK (ButtonPressMask|ButtonReleaseMask) 12 | #define MOUSEMASK (BUTTONMASK|PointerMotionMask) 13 | @@ -1133,6 +1136,15 @@ 14 | changemon(m, YesFocus); 15 | } 16 | 17 | +#ifdef XINERAMA 18 | +static int isuniquegeom(XineramaScreenInfo* unique, size_t n, XineramaScreenInfo* info){ 19 | + while (n--) 20 | + if (unique[n].x_org == info->x_org && unique[n].y_org == info->y_org) 21 | + return 0; 22 | + return 1; 23 | +} 24 | +#endif 25 | + 26 | /* a la dwm 6.1 */ 27 | void 28 | updategeom(){ 29 | @@ -1140,6 +1152,31 @@ 30 | client* c; 31 | monitor* m, * oldmhead = mhead; 32 | 33 | +#ifdef XINERAMA 34 | + if (XineramaIsActive(dis)){ 35 | + int i, j, ns; 36 | + XineramaScreenInfo* unique; 37 | + XineramaScreenInfo* info = XineramaQueryScreens(dis, &ns); 38 | + 39 | + /* only consider unique geometries as separate screens */ 40 | + unique = ecalloc(ns, sizeof(XineramaScreenInfo)); 41 | + for (i=0, j=0;i < ns;i++) 42 | + if (isuniquegeom(unique, j, &info[i])) 43 | + memcpy(&unique[j++], &info[i], sizeof(XineramaScreenInfo)); 44 | + XFree(info); 45 | + 46 | + mhead = m = createmon(0, unique[0].x_org, unique[0].y_org, 47 | + unique[0].width, unique[0].height); 48 | + for (i=1;i < j;i++){ 49 | + m->next = createmon(i, unique[i].x_org, unique[i].y_org, 50 | + unique[i].width, unique[i].height); 51 | + m = m->next; 52 | + } 53 | + 54 | + free(unique); 55 | + 56 | + } else 57 | +#endif 58 | { 59 | mhead = createmon(0, 0, 0, sw, sh); 60 | } 61 | -------------------------------------------------------------------------------- /src/common.c: -------------------------------------------------------------------------------- 1 | /* 2 | * sara Window Manager 3 | * ______________________________________________________________________________ 4 | * 5 | * Please refer to the complete LICENSE file that should accompany this software. 6 | * Please refer to the MIT license for details on usage: https://mit-license.org/ 7 | */ 8 | 9 | #include 10 | #include 11 | 12 | void 13 | die(const char* e, ...){ 14 | fprintf(stdout, "sara: %s\n", e); 15 | exit(1); 16 | } 17 | 18 | int 19 | slen(const char* str){ 20 | int i = 0; 21 | 22 | for (;*str;str++,i++); 23 | 24 | return i; 25 | } 26 | -------------------------------------------------------------------------------- /src/common.h: -------------------------------------------------------------------------------- 1 | /* 2 | * sara Window Manager 3 | * ______________________________________________________________________________ 4 | * 5 | * Please refer to the complete LICENSE file that should accompany this software. 6 | * Please refer to the MIT license for details on usage: https://mit-license.org/ 7 | */ 8 | 9 | #ifndef COMMON_H 10 | #define COMMON_H 11 | 12 | #define INPUTSOCK "/tmp/sara.sock" 13 | /* longest is "changemsize -0.05" at 17, +1 for '\0' */ 14 | #define MAXBUFF 18 15 | /* max length of a progs command */ 16 | #define MAXLEN 256 17 | 18 | void die(const char* e, ...); 19 | int slen(const char* str); 20 | 21 | #endif 22 | -------------------------------------------------------------------------------- /src/config.def.h: -------------------------------------------------------------------------------- 1 | /* 2 | * sara Window Manager 3 | * ______________________________________________________________________________ 4 | * 5 | * Please refer to the complete LICENSE file that should accompany this software. 6 | * Please refer to the MIT license for details on usage: https://mit-license.org/ 7 | */ 8 | 9 | #ifndef CONFIG_H 10 | #define CONFIG_H 11 | 12 | /* --------------------------------------- 13 | * Defines 14 | * --------------------------------------- 15 | */ 16 | 17 | #define NUMTAGS 9 18 | #define MOUSEMOD Mod4Mask 19 | #define MASTER_SIZE 0.55 20 | 21 | 22 | /* vertical space allotted for your bar of choice */ 23 | static const int barpx = 20; 24 | static const int bottombar = 0; 25 | static const int gappx = 10; 26 | static const int corner_radius = 10; 27 | /* once within snappx of a monitor edge, snap to the edge */ 28 | static const unsigned int snappx = 32; 29 | 30 | 31 | /* commands to be executed at startup. will be run in-order with /bin/sh 32 | * each entry can only be common.h::MAXLEN chars by default 33 | */ 34 | static const char* progs[] = { 35 | "pgrep -x sxhkd > /dev/null || sxhkd &", 36 | }; 37 | 38 | 39 | static const rule rules[] = { 40 | /* WM_CLASS(STRING) = instance, class 41 | * WM_NAME(STRING) = title 42 | * tags mask 0 means it takes whatever the current tags are 43 | * else, go to that tag 44 | * monitor of -1 means spawn on focused monitor 45 | */ 46 | 47 | /* class instance title tags mask isfloat isfull monitor */ 48 | { "st", NULL, "cal", 0, 1, 1, -1 }, 49 | }; 50 | 51 | /* Layouts */ 52 | static const layout layouts[] = { 53 | /* letter function name */ 54 | { 'T', tile, "tile" }, /* first entry is default */ 55 | { 'M', monocle, "monocle" }, 56 | { 'F', floaty, "floaty" }, 57 | }; 58 | 59 | static button buttons[] = { 60 | /* event mask button function argument */ 61 | { MOUSEMOD, Button1, manipulate, {.i = WantMove} }, 62 | { MOUSEMOD, Button3, manipulate, {.i = WantResize} }, 63 | }; 64 | 65 | #endif 66 | -------------------------------------------------------------------------------- /src/sara.c: -------------------------------------------------------------------------------- 1 | /* 2 | * sara Window Manager 3 | * ______________________________________________________________________________ 4 | * 5 | * Please refer to the complete LICENSE file that should accompany this software. 6 | * Please refer to the MIT license for details on usage: https://mit-license.org/ 7 | */ 8 | 9 | /* general */ 10 | #include 11 | #include 12 | /* sockets */ 13 | #include 14 | #include 15 | #include 16 | /* Xlib */ 17 | #include 18 | #include 19 | #include 20 | #include 21 | #ifdef XINERAMA 22 | #include 23 | #endif 24 | 25 | #include "common.h" 26 | 27 | 28 | #define SAFEPARG(A,B) ((A <= parg.i && parg.i <= B)) 29 | #define BUTTONMASK (ButtonPressMask|ButtonReleaseMask) 30 | #define MOUSEMASK (BUTTONMASK|PointerMotionMask) 31 | #define EACHCLIENT(I) (ic=I;ic;ic=ic->next) /* ic is a global */ 32 | #define EACHMON(M) (im=M;im;im=im->next) /* im is a global */ 33 | #define ISOUTSIDE(PX,PY,X,Y,W,H) ((PX > X + W || PX < X || PY > Y + H || PY < Y)) 34 | #define ISVISIBLE(C) ((C->desks & C->mon->seldesks)) 35 | #define MAX(A,B) ((A) > (B) ? (A) : (B)) 36 | #define STREQ(A,B) ((strcmp(A,B) == 0)) 37 | #define TABLENGTH(X) (sizeof(X)/sizeof(*X)) 38 | 39 | 40 | enum { AnyVis, OnlyVis }; 41 | enum { NoZoom, YesZoom }; 42 | enum { NoFocus, YesFocus }; 43 | enum { WantMove, WantResize }; 44 | enum { WantFloating, WantTiled, WantDummy}; 45 | enum { WantInt, WantFloat, NumTypes}; 46 | 47 | 48 | /* --------------------------------------- 49 | * Typedefs 50 | * --------------------------------------- 51 | */ 52 | 53 | typedef struct client client; 54 | typedef struct desktop desktop; 55 | typedef struct monitor monitor; 56 | typedef struct rule rule; 57 | 58 | typedef union { 59 | int i; 60 | float f; 61 | /* received from sarasock */ 62 | const char* s; 63 | } Arg; 64 | 65 | typedef struct { 66 | unsigned int mask; 67 | unsigned int btn; 68 | void (*func)(const Arg arg); 69 | const Arg arg; 70 | } button; 71 | 72 | typedef struct { 73 | const char letter; 74 | void (*arrange)(monitor*); 75 | /* for external layout setting */ 76 | const char* name; 77 | } layout; 78 | 79 | struct client { 80 | int x, y, w, h; 81 | /* being in monocle is not considered floating */ 82 | int isfloat; 83 | /* prior to togglefs */ 84 | int oldfloat; 85 | int isfull; 86 | unsigned int desks; 87 | unsigned int iscur; 88 | client* next; 89 | monitor* mon; 90 | Window win; 91 | }; 92 | 93 | struct desktop { 94 | float msize; 95 | layout* curlayout; 96 | }; 97 | 98 | struct monitor { 99 | float msize; 100 | int curdesk; 101 | int mx, my, mh, mw, wy, wh; 102 | int num; 103 | unsigned int seldesks; 104 | client* current; 105 | //client* prev; 106 | client* head; 107 | desktop* desks; 108 | layout* curlayout; 109 | monitor* next; 110 | }; 111 | 112 | struct rule { 113 | char* class; 114 | char* instance; 115 | char* title; 116 | unsigned int desks; 117 | int isfloat; 118 | int isfull; 119 | int monitor; 120 | }; 121 | 122 | 123 | /* --------------------------------------- 124 | * Util Functions 125 | * --------------------------------------- 126 | */ 127 | 128 | void* 129 | ecalloc(size_t nmemb, size_t size){ 130 | void* p; 131 | 132 | if ( !(p = calloc(nmemb, size)) ) 133 | die("ecalloc failed"); 134 | 135 | return p; 136 | } 137 | 138 | void 139 | estrtoi(const char* s, Arg* arg){ 140 | arg->i = (int) strtol(s, (char**) NULL, 10); 141 | } 142 | 143 | void 144 | estrtof(const char* s, Arg* arg){ 145 | arg->f = (float) strtof(s, (char**) NULL); 146 | } 147 | 148 | /* convert 11011110 to "01111011" 149 | * for this example, len = 8 150 | * dest must be a calloc'd char* that you free() afterwards 151 | */ 152 | void 153 | uitos(unsigned int ui, int len, char* dest){ 154 | int i, j, res; 155 | int bytearray[len]; 156 | char bytestr[len + 1]; 157 | 158 | /* reverse the array, as tags are printed left to right, not right to left */ 159 | for (i=0;i < len;i++) 160 | bytearray[i] = ui >> i & 1; 161 | 162 | for (i=0, j=0; 163 | (i < (len + 1)) && (res = snprintf(bytestr + j, (len + 1) - j, "%d", bytearray[i])) > 0; 164 | i++) 165 | j += res; 166 | 167 | snprintf(dest, len + 1, "%s", bytestr); 168 | } 169 | 170 | 171 | /* --------------------------------------- 172 | * Main Function Declarations 173 | * --------------------------------------- 174 | */ 175 | 176 | /* Clients */ 177 | static void adjustcoords(client* c); 178 | static void applyrules(client* c); 179 | static void attach(client* c, int aside); 180 | static void changecurrent(client* c, monitor* m, int desk, int refocused); 181 | static void configure(client* c); 182 | static void detach(client* c, int refocus_override); 183 | static client* findclient(Window w); 184 | static client* findcurrent(monitor* m); 185 | static client* findprevclient(client* c, int onlyvis, int onlytiled); 186 | static client* findvisclient(client* c, int wantfloat); 187 | static void killclient(const Arg arg); 188 | static void manage(Window parent, XWindowAttributes* wa); 189 | static void manipulate(const Arg arg); 190 | static void moveclient(const Arg arg); 191 | static void moveclientup(client* c); 192 | static void movefocus(const Arg arg); 193 | static void resizeclient(client* c, int x, int y, int w, int h); 194 | static void restack(monitor* m); 195 | static void sendmon(client* c, monitor* m); 196 | static void showhide(monitor* m); 197 | static void todesktop(const Arg arg); 198 | static void toggledesktop(const Arg arg); 199 | static void togglefloat(const Arg arg); 200 | static void togglefs(const Arg arg); 201 | static void tomon(const Arg arg); 202 | static void unmanage(client* c, int destroyed); 203 | static void updatefocus(monitor* m); 204 | static void zoom(const Arg arg); 205 | /* Desktops */ 206 | static void arrange(monitor* m); 207 | static void changemsize(const Arg arg); 208 | static void floaty(monitor* m); 209 | static void loaddesktop(int i); 210 | static void monocle(monitor* m); 211 | static void setlayout(const Arg arg); 212 | static void tile(monitor* m); 213 | static void toggleview(const Arg arg); 214 | static void view(const Arg arg); 215 | /* Monitor Manipulation */ 216 | static void changemon(monitor* m, int wantfocus); 217 | static void cleanupmon(monitor* m); 218 | static monitor* coordstomon(int x, int y); 219 | static monitor* createmon(int num, int x, int y, int w, int h); 220 | static monitor* dirtomon(int dir); 221 | static monitor* findmon(Window w); 222 | static void focusmon(const Arg arg); 223 | static void updategeom(); 224 | /* Backend */ 225 | static void cleanup(); 226 | static int getptrcoords(int* x, int* y); 227 | static void grabbuttons(client* c, int focused); 228 | static void outputstats(); 229 | static void runconfig(); 230 | static void setrootstats(); 231 | static void setup(); 232 | static void start(); 233 | static int xerror(Display* dis, XErrorEvent* e); 234 | static int xsendkill(Window w); 235 | static void quit(const Arg arg); 236 | #ifdef _SHAPE_H_ 237 | static void roundcorners(client* c); 238 | static void unroundcorners(client* c); 239 | #endif 240 | /* sarasock interfacing */ 241 | static void handlemsg(char* msg); 242 | static void (*str2func(const char* str))(Arg); 243 | /* X */ 244 | static void adopt(); 245 | static void buttonpress(XEvent* e); 246 | static void configurenotify(XEvent* e); 247 | static void configurerequest(XEvent* e); 248 | static void destroynotify(XEvent* e); 249 | static void enternotify(XEvent* e); 250 | static void focusin(XEvent* e); 251 | static void maprequest(XEvent* e); 252 | static void motionnotify(XEvent* e); 253 | static void unmapnotify(XEvent* e); 254 | 255 | void (*events[LASTEvent])(XEvent* e) = { 256 | [ButtonPress] = buttonpress, 257 | [ConfigureNotify] = configurenotify, 258 | [ConfigureRequest] = configurerequest, 259 | [DestroyNotify] = destroynotify, 260 | [EnterNotify] = enternotify, 261 | [FocusIn] = focusin, 262 | [MapRequest] = maprequest, 263 | [MotionNotify] = motionnotify, 264 | [UnmapNotify] = unmapnotify 265 | }; 266 | 267 | /* Check assigned fields in arg when you use parser for safety. 268 | * If range is being used, SAFEPARG is suggested. 269 | */ 270 | void (*parser[NumTypes])(const char* s, Arg* arg) = { 271 | [WantInt] = estrtoi, 272 | [WantFloat] = estrtof 273 | }; 274 | 275 | 276 | /* callable functions from outside */ 277 | struct { 278 | void (*func)(Arg); 279 | const char* str; 280 | } conversions [] = { 281 | {changemsize, "changemsize"}, 282 | {focusmon, "focusmon"}, 283 | {killclient, "killclient"}, 284 | {moveclient, "moveclient"}, 285 | {movefocus, "movefocus"}, 286 | {quit, "quit"}, 287 | {todesktop, "todesktop"}, 288 | {toggledesktop, "toggledesktop"}, 289 | {togglefloat, "togglefloat"}, 290 | {togglefs, "togglefs"}, 291 | {toggleview, "toggleview"}, 292 | {tomon, "tomon"}, 293 | {setlayout, "setlayout"}, 294 | {view, "view"}, 295 | {zoom, "zoom"}, 296 | }; 297 | 298 | 299 | /* Make the above known */ 300 | #include "config.h" 301 | 302 | 303 | /* --------------------------------------- 304 | * Globals 305 | * --------------------------------------- 306 | */ 307 | 308 | /* X */ 309 | static int screen; 310 | static int sh; 311 | static int sw; 312 | static Cursor cursor; 313 | static Display* dis; 314 | static Window root; 315 | /* for EACHCLIENT/EACHMON iterating */ 316 | client* ic; 317 | monitor* im; 318 | Arg parg; /* for parser */ 319 | /* Monitor Interfacing */ 320 | static monitor* curmon; 321 | static monitor* mhead; 322 | /* Backend */ 323 | static int restart; 324 | static int running; 325 | static const Arg dumbarg; /* passthrough for function calls like togglefs */ 326 | static long w_data[] = { WithdrawnState, None }; /* for unmanage and unmapnotify */ 327 | static Atom w_atom; /* for unmanage and unmapnotify */ 328 | static XEvent dumbev; /* for XCheckMasking */ 329 | 330 | 331 | /* --------------------------------------- 332 | * Clients 333 | * --------------------------------------- 334 | */ 335 | 336 | void 337 | adjustcoords(client* c){ 338 | monitor* m; 339 | 340 | if ( (m = coordstomon(c->x, c->y)) && m != c->mon ){ 341 | c->x += (m->mx < c->mon->mx) ? c->mon->mx : -m->mx; 342 | c->y += (m->my < c->mon->my) ? c->mon->my : -m->my; 343 | } 344 | } 345 | 346 | void 347 | applyrules(client* c){ 348 | const char* class, * instance; 349 | int i; 350 | const rule* r; 351 | XTextProperty tp; 352 | XClassHint ch = { NULL, NULL }; 353 | 354 | c->isfloat = c->desks = 0; 355 | 356 | XGetWMName(dis, c->win, &tp); 357 | XGetClassHint(dis, c->win, &ch); 358 | class = ch.res_class ? ch.res_class : "broken"; 359 | instance = ch.res_name ? ch.res_name : "NULL"; 360 | 361 | for (i=0;i < TABLENGTH(rules);i++){ 362 | r = &rules[i]; 363 | 364 | if ((!r->title || (tp.value && strstr(r->title, (const char*) tp.value))) 365 | && (!r->class || strstr(class, r->class)) 366 | && (!r->instance || strstr(instance, r->instance))){ 367 | c->isfloat = r->isfloat; 368 | c->isfull = r->isfull; 369 | c->desks |= r->desks; 370 | 371 | for EACHMON(mhead){ 372 | if (im->num == r->monitor){ 373 | c->mon = im; 374 | break; 375 | } 376 | } 377 | } 378 | } 379 | 380 | if (ch.res_class) 381 | XFree(ch.res_class); 382 | if (ch.res_name) 383 | XFree(ch.res_name); 384 | 385 | c->desks = c->desks ? c->desks : c->mon->seldesks; 386 | } 387 | 388 | void 389 | attach(client* c, int aside){ 390 | client* l; 391 | 392 | if (!aside || !c->mon->head){ 393 | c->next = c->mon->head; 394 | c->mon->head = c; 395 | 396 | } else { 397 | /* If not the first on this desktop */ 398 | if (c->mon->current){ 399 | c->next = c->mon->current->next; 400 | c->mon->current->next = c; 401 | 402 | } else { 403 | for (l=c->mon->head;l && l->next;l=l->next); 404 | if (l) 405 | l->next = c; 406 | } 407 | } 408 | } 409 | 410 | // TODO: 411 | // if (m->prev && m->prev != c) 412 | // m->current = m->prev 413 | void 414 | changecurrent(client* c, monitor* m, int desk, int refocused){ 415 | client* vis; 416 | 417 | if (c){ 418 | c->iscur ^= 1 << desk; 419 | grabbuttons(c, 1); 420 | } 421 | 422 | for EACHCLIENT(m->head){ 423 | if (ic != c && (ic->iscur & 1 << desk)){ 424 | ic->iscur ^= 1 << desk; 425 | grabbuttons(ic, 0); 426 | } 427 | } 428 | 429 | m->current = c; 430 | 431 | /* move focus down if possible, else move up */ 432 | if (m->current && refocused){ 433 | vis = (vis = findvisclient(c->next, WantFloating)) 434 | ? vis : findprevclient(c, OnlyVis, WantFloating); 435 | changecurrent(vis, m, m->curdesk, 0); 436 | } 437 | 438 | if (c) 439 | updatefocus(m); 440 | } 441 | 442 | void 443 | configure(client* c){ 444 | XConfigureEvent ce = { 445 | .type = ConfigureNotify, 446 | .display = dis, 447 | .event = c->win, 448 | .window = c->win, 449 | .x = c->x, 450 | .y = c->y, 451 | .width = c->w, 452 | .height = c->h, 453 | .above = None, 454 | .override_redirect = False 455 | }; 456 | 457 | XSendEvent(dis, c->win, False, StructureNotifyMask, (XEvent *)&ce); 458 | } 459 | 460 | void 461 | detach(client* c, int refocus_override){ 462 | client** t; 463 | 464 | /* refocus only as necessary */ 465 | if (c == c->mon->current && !refocus_override) 466 | changecurrent(c, c->mon, c->mon->curdesk, 1); 467 | 468 | for (t=&(c->mon->head);*t && *t != c;t=&(*t)->next); 469 | *t = c->next; 470 | } 471 | 472 | client* 473 | findclient(Window w){ 474 | for EACHMON(mhead) 475 | for EACHCLIENT(im->head) 476 | if (ic->win == w) 477 | return ic; 478 | 479 | return NULL; 480 | } 481 | 482 | client* 483 | findcurrent(monitor* m){ 484 | for EACHCLIENT(m->head) 485 | if (ISVISIBLE(ic) && (ic->iscur & 1 << m->curdesk)) 486 | return ic; 487 | 488 | return NULL; 489 | } 490 | 491 | client* 492 | findprevclient(client* c, int onlyvis, int onlytiled){ 493 | client* ret = NULL; 494 | 495 | for EACHCLIENT(c->mon->head){ 496 | if (ic == c) 497 | break; 498 | 499 | if (onlyvis && ISVISIBLE(ic)) 500 | if ((onlytiled && !ic->isfloat) || !onlytiled) 501 | ret = ic; 502 | 503 | if (ic->next == c) 504 | return (onlyvis) ? ret : ic; 505 | } 506 | 507 | return NULL; 508 | } 509 | 510 | client* 511 | findvisclient(client* c, int wantfloat){ 512 | for EACHCLIENT(c) 513 | if (ISVISIBLE(ic)) 514 | if ((!wantfloat && !ic->isfloat) || wantfloat) 515 | return ic; 516 | 517 | return NULL; 518 | } 519 | 520 | void 521 | killclient(const Arg arg){ 522 | if (!curmon->current) 523 | return; 524 | 525 | if (!xsendkill(curmon->current->win)){ 526 | XGrabServer(dis); 527 | XSetCloseDownMode(dis, DestroyAll); 528 | XKillClient(dis, curmon->current->win); 529 | XSync(dis, False); 530 | XUngrabServer(dis); 531 | } 532 | } 533 | 534 | void 535 | manage(Window parent, XWindowAttributes* wa){ 536 | client* c, * t; 537 | Window trans = None; 538 | 539 | c = ecalloc(1, sizeof(client)); 540 | 541 | if (curmon->current && curmon->current->isfull) 542 | togglefs(dumbarg); 543 | 544 | c->win = parent; 545 | c->isfloat = c->oldfloat = c->isfull = c->iscur = 0; 546 | c->x = wa->x; 547 | c->y = wa->y; 548 | c->w = wa->width; 549 | c->h = wa->height; 550 | 551 | if (XGetTransientForHint(dis, parent, &trans) && (t = findclient(trans))){ 552 | c->desks = t->desks; 553 | c->mon = t->mon; 554 | 555 | } else { 556 | c->mon = curmon; 557 | applyrules(c); 558 | } 559 | if (!c->isfloat) 560 | c->isfloat = c->oldfloat = (trans != None); 561 | 562 | adjustcoords(c); 563 | c->y = (c->y < c->mon->wy) ? c->mon->wy : c->y; 564 | 565 | configure(c); 566 | XSelectInput(dis, c->win, EnterWindowMask|FocusChangeMask|PropertyChangeMask 567 | |StructureNotifyMask); 568 | grabbuttons(c, 0); 569 | 570 | attach(c, 1); 571 | 572 | /* move out of the way until told otherwise */ 573 | XMoveResizeWindow(dis, c->win, c->x + 2*sw, c->y, c->w, c->h); 574 | arrange(c->mon); 575 | #ifdef _SHAPE_H_ 576 | roundcorners(c); 577 | #endif 578 | XMapWindow(dis, c->win); 579 | 580 | if (c->desks & c->mon->seldesks){ 581 | changecurrent(c, c->mon, c->mon->curdesk, 0); 582 | restack(c->mon); 583 | 584 | /* applyrules */ 585 | if (c->isfull){ 586 | c->isfull = !c->isfull; 587 | togglefs(dumbarg); 588 | } 589 | } 590 | 591 | outputstats(); 592 | } 593 | 594 | void 595 | moveclient(const Arg arg){ 596 | client* c; 597 | 598 | if (!curmon->current || curmon->current->isfull) 599 | return; 600 | 601 | parser[WantInt](arg.s, &parg); 602 | 603 | if (!(SAFEPARG(-1,1))) 604 | return; 605 | 606 | /* move current up, or move next visible up (i.e. move current down) */ 607 | c = (parg.i > 0) ? curmon->current : findvisclient(curmon->current->next, WantTiled); 608 | 609 | if (c){ 610 | moveclientup(c); 611 | arrange(c->mon); 612 | } 613 | } 614 | 615 | void 616 | moveclientup(client* c){ 617 | client* p = NULL, * target = NULL; 618 | client** t = NULL; 619 | 620 | if (!c) 621 | return; 622 | 623 | target = findprevclient(c, OnlyVis, WantTiled); 624 | p = findprevclient(c, AnyVis, WantDummy); 625 | 626 | /* Go up only if not highest visible */ 627 | if (!target || !p) 628 | return; 629 | 630 | for (t=&c->mon->head;*t && *t != target;t=&(*t)->next); 631 | 632 | /* if p == target, then we're still okay */ 633 | p->next = c->next; 634 | c->next = target; 635 | *t = c; 636 | } 637 | 638 | void 639 | movefocus(const Arg arg){ 640 | client* j, * c = NULL; 641 | 642 | if (!curmon->current || curmon->current->isfull) 643 | return; 644 | 645 | parser[WantInt](arg.s, &parg); 646 | 647 | /* up stack */ 648 | if (parg.i > 0){ 649 | for (j=curmon->head;j && j != curmon->current;j=j->next) 650 | if (ISVISIBLE(j)) 651 | c = j; 652 | 653 | /* if curmon->current was highest, go to the bottom */ 654 | if (!c) 655 | for (;j;j=j->next) 656 | if (ISVISIBLE(j)) 657 | c = j; 658 | 659 | /* down stack, wrap around */ 660 | } else if (parg.i < 0) { 661 | if ( !(c = findvisclient(curmon->current->next, WantFloating)) ) 662 | c = findvisclient(curmon->head, WantFloating); 663 | } 664 | 665 | if (c && c != curmon->current){ 666 | changecurrent(c, curmon, curmon->curdesk, 0); 667 | restack(curmon); 668 | } 669 | } 670 | 671 | void 672 | manipulate(const Arg arg){ 673 | int x, y, ocx, ocy, nx, ny, nw, nh; 674 | client* c; 675 | XEvent ev; 676 | int doresize = (arg.i == WantResize), trytoggle = 0; 677 | monitor* m = NULL; 678 | Time lasttime = 0; 679 | 680 | if ( !(c = curmon->current) || c->isfull ) 681 | return; 682 | 683 | restack(curmon); 684 | 685 | if (XGrabPointer(dis, root, False, MOUSEMASK, GrabModeAsync, GrabModeAsync, 686 | None, cursor, CurrentTime) != GrabSuccess) 687 | return; 688 | 689 | if (!getptrcoords(&x, &y)) 690 | return; 691 | 692 | if (doresize){ 693 | if ( !(m = c->mon) ) 694 | return; 695 | XWarpPointer(dis, None, c->win, 0, 0, 0, 0, c->w + 1, c->h + 1); 696 | } 697 | 698 | ocx = c->x; 699 | ocy = c->y; 700 | do { 701 | XMaskEvent(dis, MOUSEMASK|ExposureMask|SubstructureRedirectMask, &ev); 702 | switch(ev.type){ 703 | case ConfigureRequest: 704 | case Expose: 705 | case MapRequest: 706 | events[ev.type](&ev); 707 | break; 708 | case MotionNotify: 709 | if ((ev.xmotion.time - lasttime) <= (1000 / 60)) 710 | continue; 711 | lasttime = ev.xmotion.time; 712 | 713 | /* Only adjust if moving */ 714 | nx = doresize ? c->x : ocx + (ev.xmotion.x - x); 715 | ny = doresize ? c->y : ocy + (ev.xmotion.y - y); 716 | /* Only adjust if resizing */ 717 | nw = doresize ? MAX(ev.xmotion.x - ocx + 1, 1) : c->w; 718 | nh = doresize ? MAX(ev.xmotion.y - ocy + 1, 1) : c->h; 719 | 720 | /* if c extends beyond the boundaries of its monitor, make it a float */ 721 | if (doresize && (m->mx + nw >= curmon->mx 722 | && m->mx + nw <= curmon->mx + curmon->mw 723 | && m->wy + nh >= curmon->wy 724 | && m->wy + nh <= curmon->wy + curmon->wh)){ 725 | trytoggle = 1; 726 | 727 | } else { 728 | /* if c is within snappx of the monitor borders, 729 | * then snap and make it a float 730 | */ 731 | nx = (abs(curmon->mx - nx) < snappx) ? curmon->mx : nx; 732 | nx = (abs((curmon->mx + curmon->mw) - (nx + c->w)) < snappx) 733 | ? curmon->mx + curmon->mw - c->w : nx; 734 | ny = (abs(curmon->wy - ny) < snappx) ? curmon->wy : ny; 735 | ny = (abs((curmon->wy + curmon->wh) - (ny + c->h)) < snappx) 736 | ? curmon->wy + curmon->wh - c->h : ny; 737 | trytoggle = 1; 738 | } 739 | /* don't toggle if floating layout, do resize if floating */ 740 | if (!c->isfloat && trytoggle && !(curmon->curlayout->arrange == &floaty)) 741 | togglefloat(dumbarg); 742 | if (c->isfloat || (curmon->curlayout->arrange == &floaty)) 743 | resizeclient(c, nx, ny, nw, nh); 744 | XFlush(dis); 745 | break; 746 | } 747 | } while (ev.type != ButtonRelease); 748 | 749 | if (doresize) 750 | XWarpPointer(dis, None, c->win, 0, 0, 0, 0, c->w + 1, c->h + 1); 751 | XUngrabPointer(dis, CurrentTime); 752 | 753 | if (doresize) 754 | while (XCheckMaskEvent(dis, EnterWindowMask, &ev)); 755 | 756 | if ( (m = coordstomon(c->x, c->y)) && m != curmon){ 757 | sendmon(c, m); 758 | changemon(m, YesFocus); 759 | outputstats(); 760 | } 761 | #ifdef _SHAPE_H_ 762 | roundcorners(c); 763 | #endif 764 | } 765 | 766 | void 767 | resizeclient(client* c, int x, int y, int w, int h){ 768 | XWindowChanges wc; 769 | 770 | c->x = wc.x = x; 771 | c->y = wc.y = y; 772 | c->w = wc.width = w; 773 | c->h = wc.height = h; 774 | XConfigureWindow(dis, c->win, CWX|CWY|CWWidth|CWHeight, &wc); 775 | XSync(dis, False); 776 | #ifdef _SHAPE_H_ 777 | roundcorners(c); 778 | #endif 779 | } 780 | 781 | void 782 | restack(monitor* m){ 783 | XWindowChanges wc; 784 | 785 | if (!m->current) 786 | return; 787 | 788 | wc.stack_mode = Below; 789 | wc.sibling = m->current->win; 790 | 791 | for EACHCLIENT(m->head){ 792 | //if (ic != m->current && !ic->isfloat && ISVISIBLE(ic)){ 793 | // if not current, and visible, and both are floating or neither are floating 794 | if (ic != m->current && ISVISIBLE(ic) && (ic->isfloat == m->current->isfloat)){ 795 | XConfigureWindow(dis, ic->win, CWSibling|CWStackMode, &wc); 796 | wc.sibling = ic->win; 797 | } 798 | } 799 | 800 | XSync(dis, False); 801 | while (XCheckMaskEvent(dis, EnterWindowMask, &dumbev)); 802 | } 803 | 804 | void 805 | showhide(monitor* m){ 806 | for EACHCLIENT(m->head){ 807 | if (ISVISIBLE(ic)){ 808 | XMoveWindow(dis, ic->win, ic->x, ic->y); 809 | if (ic->isfloat && !ic->isfull){ 810 | resizeclient(ic, ic->x, ic->y, ic->w, ic->h); 811 | XRaiseWindow(dis, ic->win); 812 | } 813 | 814 | } else { 815 | XMoveWindow(dis, ic->win, -2*ic->w, ic->y); 816 | } 817 | } 818 | } 819 | 820 | void 821 | sendmon(client* c, monitor* m){ 822 | if (c->mon == m || c->isfull) 823 | return; 824 | 825 | detach(c, 0); 826 | c->mon = m; 827 | c->desks = m->seldesks; 828 | 829 | c->next = NULL; 830 | attach(c, 1); 831 | c->iscur = 0; 832 | changecurrent(c, c->mon, c->mon->curdesk, 0); 833 | if (c->isfloat) 834 | adjustcoords(c); 835 | 836 | curmon->current = findcurrent(curmon); 837 | for EACHMON(mhead) 838 | arrange(im); 839 | changemon(c->mon, YesFocus); 840 | } 841 | 842 | void 843 | todesktop(const Arg arg){ 844 | if (!curmon->current) 845 | return; 846 | 847 | parser[WantInt](arg.s, &parg); 848 | 849 | if (!SAFEPARG(0,NUMTAGS)) 850 | return; 851 | 852 | if (curmon->current->desks == (1 << parg.i)) 853 | return; 854 | 855 | curmon->current->desks = 1 << parg.i; 856 | curmon->current->iscur = 0; 857 | changecurrent(curmon->current, curmon, 1 << parg.i, 1); 858 | 859 | arrange(curmon); 860 | outputstats(); 861 | } 862 | 863 | void 864 | toggledesktop(const Arg arg){ 865 | unsigned int newdesks; 866 | 867 | if (!curmon->current) 868 | return; 869 | 870 | parser[WantInt](arg.s, &parg); 871 | 872 | if (!SAFEPARG(-1,NUMTAGS)) 873 | return; 874 | 875 | if (parg.i < 0) 876 | newdesks = curmon->current->desks | ~(curmon->current->desks); 877 | else 878 | newdesks = curmon->current->desks ^ (1 << parg.i); 879 | 880 | if (newdesks){ 881 | curmon->current->desks = newdesks; 882 | /* set current to be current on new desktop 883 | * if it will no longer be visible, adjust current 884 | */ 885 | changecurrent(curmon->current, curmon, 1 << parg.i, 886 | (curmon->current->desks & curmon->seldesks) ? 0 : 1); 887 | 888 | arrange(curmon); 889 | outputstats(); 890 | } 891 | } 892 | 893 | void 894 | togglefloat(const Arg arg){ 895 | if (!curmon->current || curmon->current->isfull) 896 | return; 897 | 898 | if (curmon->curlayout->arrange == &floaty) 899 | return; 900 | 901 | curmon->current->isfloat = !curmon->current->isfloat; 902 | arrange(curmon); 903 | } 904 | 905 | void 906 | togglefs(const Arg arg){ 907 | if (!curmon->current) 908 | return; 909 | 910 | if ( (curmon->current->isfull = !curmon->current->isfull) ){ 911 | curmon->current->oldfloat = curmon->current->isfloat; 912 | curmon->current->isfloat = 0; 913 | 914 | resizeclient(curmon->current, curmon->mx, curmon->my, 915 | curmon->mw, curmon->mh); 916 | #ifdef _SHAPE_H_ 917 | unroundcorners(curmon->current); 918 | #endif 919 | XRaiseWindow(dis, curmon->current->win); 920 | 921 | } else { 922 | curmon->current->isfloat = curmon->current->oldfloat; 923 | arrange(curmon); 924 | } 925 | } 926 | 927 | void 928 | tomon(const Arg arg){ 929 | if (!curmon->current || !mhead->next) 930 | return; 931 | 932 | parser[WantInt](arg.s, &parg); 933 | 934 | sendmon(curmon->current, dirtomon(parg.i)); 935 | outputstats(); 936 | } 937 | 938 | void 939 | unmanage(client* c, int destroyed){ 940 | monitor* m = c->mon; 941 | 942 | detach(c, 0); 943 | if (!destroyed){ 944 | XGrabServer(dis); 945 | XUngrabButton(dis, AnyButton, AnyModifier, c->win); 946 | XChangeProperty(dis, c->win, w_atom, w_atom, 32, 947 | PropModeReplace, (unsigned char*) w_data, 2); 948 | XSync(dis, False); 949 | XUngrabServer(dis); 950 | } 951 | free(c); 952 | arrange(m); 953 | outputstats(); 954 | } 955 | 956 | void 957 | updatefocus(monitor* m){ 958 | if (!m) 959 | return; 960 | 961 | if (m->current) 962 | XSetInputFocus(dis, m->current->win, RevertToPointerRoot, CurrentTime); 963 | else 964 | XSetInputFocus(dis, root, RevertToPointerRoot, CurrentTime); 965 | 966 | XSync(dis, False); 967 | } 968 | 969 | void 970 | zoom(const Arg arg){ 971 | client* c; 972 | 973 | if (!(c = curmon->current)) 974 | return; 975 | 976 | detach(c, 1); 977 | attach(c, 0); 978 | arrange(c->mon); 979 | } 980 | 981 | 982 | /* --------------------------------------- 983 | * Desktops 984 | * --------------------------------------- 985 | */ 986 | 987 | void 988 | arrange(monitor* m){ 989 | showhide(m); 990 | m->curlayout->arrange(m); 991 | restack(m); 992 | }; 993 | 994 | void 995 | changemsize(const Arg arg){ 996 | parser[WantFloat](arg.s, &parg); 997 | curmon->msize += ( ((curmon->msize < 0.95 * curmon->mw) && (parg.f > 0)) 998 | || ((curmon->msize > 0.05 * curmon->mw) && (parg.f < 0)) ) 999 | ? parg.f * curmon->mw : 0; 1000 | 1001 | arrange(curmon); 1002 | } 1003 | 1004 | /* don't toggle isfloat, so that everyone snaps back when you tile, etc. */ 1005 | void 1006 | floaty(monitor* m){ 1007 | for EACHCLIENT(m->head) 1008 | if (ISVISIBLE(ic)) 1009 | resizeclient(ic, ic->x, ic->y, ic->w, ic->h); 1010 | } 1011 | 1012 | void 1013 | loaddesktop(int i){ 1014 | curmon->desks[curmon->curdesk].msize = curmon->msize; 1015 | curmon->desks[curmon->curdesk].curlayout = curmon->curlayout; 1016 | 1017 | curmon->msize = curmon->desks[i].msize; 1018 | curmon->curlayout = curmon->desks[i].curlayout; 1019 | } 1020 | 1021 | void 1022 | monocle(monitor* m){ 1023 | int x = m->mx + gappx, y = (bottombar ? (m->my + gappx) : (m->wy + barpx)), max_h = (bottombar ? (m->wh - barpx) : (m->mh - gappx)); 1024 | for EACHCLIENT(m->head) 1025 | if (ISVISIBLE(ic) && !ic->isfloat && !ic->isfull) 1026 | resizeclient(ic, x, y, m->mw - 2*gappx, max_h - y); 1027 | } 1028 | 1029 | void 1030 | setlayout(const Arg arg){ 1031 | int i; 1032 | for (i=0;i < TABLENGTH(layouts);i++) 1033 | if (STREQ(arg.s, layouts[i].name)) 1034 | curmon->curlayout = (layout*) &layouts[i]; 1035 | 1036 | arrange(curmon); 1037 | outputstats(); 1038 | } 1039 | 1040 | void 1041 | tile(monitor* m){ 1042 | int n = 0, i = 0, h = 0, x = m->mx + gappx, y = (bottombar ? (m->my + gappx) : (m->wy + barpx)), max_h = (bottombar ? (m->wh - barpx) : (m->mh - gappx)); 1043 | client* nf = NULL; 1044 | 1045 | /* Find the first non-floating, visible window and tally non-floating, visible windows */ 1046 | for EACHCLIENT(m->head){ 1047 | if (!ic->isfloat && ISVISIBLE(ic)){ 1048 | nf = (!nf) ? ic : nf; 1049 | n++; 1050 | } 1051 | } 1052 | 1053 | if (nf && n == 1){ 1054 | if (!nf->isfull) 1055 | resizeclient(nf, x, y, m->mw - 2*gappx, max_h - y); 1056 | 1057 | } else if (nf){ 1058 | /* so having a master doesn't affect stack splitting */ 1059 | n--; 1060 | 1061 | /* Master window */ 1062 | if (!nf->isfull) 1063 | resizeclient(nf, x, y, m->msize - gappx, max_h - y); 1064 | 1065 | /* Stack */ 1066 | for EACHCLIENT(nf->next){ 1067 | if (ISVISIBLE(ic) && !ic->isfloat && !ic->isfull){ 1068 | h = (max_h - y) / (n - i); 1069 | 1070 | resizeclient(ic, x + m->msize, y, m->mw - m->msize - 2*gappx, h); 1071 | 1072 | if (y + h < max_h) 1073 | y += h + gappx; 1074 | 1075 | i++; 1076 | } 1077 | } 1078 | } 1079 | } 1080 | 1081 | void 1082 | toggleview(const Arg arg){ 1083 | int i; 1084 | unsigned int tagmask; 1085 | 1086 | parser[WantInt](arg.s, &parg); 1087 | 1088 | if (!SAFEPARG(-1,NUMTAGS)) 1089 | return; 1090 | 1091 | if (parg.i < 0) 1092 | tagmask = ~(curmon->seldesks); 1093 | else 1094 | tagmask = 1 << parg.i; 1095 | 1096 | /* if this would leave nothing visible */ 1097 | if ((curmon->seldesks ^ tagmask) == 0) 1098 | return; 1099 | 1100 | if (curmon->current && curmon->current->isfull) 1101 | togglefs(dumbarg); 1102 | 1103 | curmon->seldesks ^= tagmask; 1104 | 1105 | if (!(curmon->seldesks & 1 << curmon->curdesk)){ 1106 | for (i=0;i < NUMTAGS;i++){ 1107 | if (curmon->seldesks & 1 << i){ 1108 | loaddesktop(i); 1109 | curmon->curdesk = i; 1110 | break; 1111 | } 1112 | } 1113 | } 1114 | 1115 | /* refocuses, toggles off curmon->current's currentness */ 1116 | if (curmon->current && !ISVISIBLE(curmon->current)) 1117 | changecurrent(curmon->current, curmon, curmon->curdesk, 1); 1118 | 1119 | arrange(curmon); 1120 | outputstats(); 1121 | } 1122 | 1123 | void 1124 | view(const Arg arg){ 1125 | client* c; 1126 | 1127 | parser[WantInt](arg.s, &parg); 1128 | 1129 | if (!SAFEPARG(0,NUMTAGS)) 1130 | return; 1131 | 1132 | if (curmon->current && curmon->current->isfull) 1133 | togglefs(dumbarg); 1134 | 1135 | loaddesktop(parg.i); 1136 | curmon->seldesks = 1 << parg.i; 1137 | curmon->curdesk = parg.i; 1138 | 1139 | if ( (c = findcurrent(curmon)) ) 1140 | /* rezero, so it can be set and everyone else unset */ 1141 | c->iscur ^= 1 << curmon->curdesk; 1142 | else 1143 | c = findvisclient(curmon->head, WantFloating); 1144 | 1145 | changecurrent(c, curmon, curmon->curdesk, 0); 1146 | 1147 | arrange(curmon); 1148 | outputstats(); 1149 | } 1150 | 1151 | 1152 | /* --------------------------------------- 1153 | * Monitor Manipulation 1154 | * --------------------------------------- 1155 | */ 1156 | 1157 | void 1158 | changemon(monitor* m, int wantfocus){ 1159 | if (curmon && curmon->current) 1160 | grabbuttons(curmon->current, 0); 1161 | curmon = m; 1162 | if (wantfocus) 1163 | updatefocus(curmon); 1164 | } 1165 | 1166 | void 1167 | cleanupmon(monitor* m){ 1168 | free(m->desks); 1169 | free(m); 1170 | } 1171 | 1172 | monitor* 1173 | coordstomon(int x, int y){ 1174 | for EACHMON(mhead) 1175 | if (!ISOUTSIDE(x, y, im->mx, im->my, im->mw, im->mh)) 1176 | return im; 1177 | 1178 | return NULL; 1179 | } 1180 | 1181 | monitor* 1182 | createmon(int num, int x, int y, int w, int h){ 1183 | int i; 1184 | monitor* m = ecalloc(1, sizeof(monitor)); 1185 | 1186 | m->num = num; 1187 | m->mx = x; 1188 | m->my = y; 1189 | m->mw = w; 1190 | m->mh = h; 1191 | 1192 | m->wy = m->my + (bottombar ? 0 : barpx ); 1193 | m->wh = m->mh - barpx; 1194 | 1195 | /* Default to first layout */ 1196 | m->curlayout = (layout*) &layouts[0]; 1197 | m->msize = m->mw * MASTER_SIZE; 1198 | 1199 | m->desks = ecalloc(NUMTAGS, sizeof(desktop)); 1200 | for (i=0;i < NUMTAGS;i++){ 1201 | m->desks[i].curlayout = m->curlayout; 1202 | m->desks[i].msize = m->msize; 1203 | } 1204 | 1205 | /* Default to first desktop */ 1206 | m->seldesks = 1 << 0; 1207 | m->curdesk = 0; 1208 | m->head = NULL; 1209 | m->current = NULL; 1210 | 1211 | return m; 1212 | } 1213 | 1214 | monitor* 1215 | dirtomon(int dir){ 1216 | monitor* m; 1217 | 1218 | if (dir > 0){ 1219 | if ( !(m = curmon->next) ) 1220 | m = mhead; 1221 | 1222 | } else if (curmon == mhead){ 1223 | for (m=mhead;m->next;m=m->next); 1224 | 1225 | } else { 1226 | for (m=mhead;m->next != curmon;m=m->next); 1227 | } 1228 | 1229 | return m; 1230 | } 1231 | 1232 | monitor* 1233 | findmon(Window w){ 1234 | for EACHMON(mhead) 1235 | for EACHCLIENT(im->head) 1236 | if (ic->win == w) 1237 | return im; 1238 | 1239 | return curmon; 1240 | } 1241 | 1242 | void 1243 | focusmon(const Arg arg){ 1244 | monitor* m; 1245 | 1246 | if (!mhead->next) 1247 | return; 1248 | 1249 | parser[WantInt](arg.s, &parg); 1250 | 1251 | if ( (m = dirtomon(parg.i)) && m == curmon ) 1252 | return; 1253 | changemon(m, YesFocus); 1254 | } 1255 | 1256 | #ifdef XINERAMA 1257 | static int isuniquegeom(XineramaScreenInfo* unique, size_t n, XineramaScreenInfo* info){ 1258 | while (n--) 1259 | if (unique[n].x_org == info->x_org && unique[n].y_org == info->y_org) 1260 | return 0; 1261 | return 1; 1262 | } 1263 | #endif 1264 | 1265 | // TODO: 6.2 approach to be less intrusive? 1266 | /* a la dwm 6.1 */ 1267 | void 1268 | updategeom(){ 1269 | int x, y; 1270 | client* c; 1271 | monitor* m, * oldmhead = mhead; 1272 | 1273 | #ifdef XINERAMA 1274 | if (XineramaIsActive(dis)){ 1275 | int i, j, ns; 1276 | XineramaScreenInfo* unique; 1277 | XineramaScreenInfo* info = XineramaQueryScreens(dis, &ns); 1278 | 1279 | // TODO: can i just use info[i] directly? 1280 | /* only consider unique geometries as separate screens */ 1281 | unique = ecalloc(ns, sizeof(XineramaScreenInfo)); 1282 | for (i=0, j=0;i < ns;i++) 1283 | if (isuniquegeom(unique, j, &info[i])) 1284 | memcpy(&unique[j++], &info[i], sizeof(XineramaScreenInfo)); 1285 | XFree(info); 1286 | 1287 | mhead = m = createmon(0, unique[0].x_org, unique[0].y_org, 1288 | unique[0].width, unique[0].height); 1289 | for (i=1;i < j;i++){ 1290 | m->next = createmon(i, unique[i].x_org, unique[i].y_org, 1291 | unique[i].width, unique[i].height); 1292 | m = m->next; 1293 | } 1294 | 1295 | free(unique); 1296 | 1297 | } else 1298 | #endif 1299 | { 1300 | mhead = createmon(0, 0, 0, sw, sh); 1301 | } 1302 | 1303 | /* if updating, reattach any old clients to the new mhead */ 1304 | while ( (m = oldmhead) ){ 1305 | while ( (c = m->head) ){ 1306 | m->head = c->next; 1307 | detach(c, 0); 1308 | c->mon = mhead; 1309 | c->next = NULL; 1310 | attach(c, 1); 1311 | } 1312 | mhead->seldesks |= m->seldesks; 1313 | oldmhead = m->next; 1314 | cleanupmon(m); 1315 | } 1316 | 1317 | getptrcoords(&x, &y); 1318 | if ( (m = coordstomon(x, y)) ) 1319 | changemon(m, YesFocus); 1320 | if (!curmon) 1321 | changemon(mhead, YesFocus); 1322 | 1323 | outputstats(); 1324 | } 1325 | 1326 | 1327 | /* --------------------------------------- 1328 | * Backend 1329 | * --------------------------------------- 1330 | */ 1331 | 1332 | /* Kill off any remaining clients 1333 | * Free all the things 1334 | */ 1335 | void 1336 | cleanup(){ 1337 | monitor* m, * tm = mhead; 1338 | const Arg arg = {.s = "-1"}; 1339 | 1340 | /* for an unknown reason, using EACHMON segfaults this */ 1341 | for (m=mhead;m;m=m->next){ 1342 | changemon(m, NoFocus); 1343 | /* make everything visible */ 1344 | toggleview(arg); 1345 | while (curmon->current) 1346 | unmanage(curmon->current, 0); 1347 | } 1348 | 1349 | XUngrabKey(dis, AnyKey, AnyModifier, root); 1350 | 1351 | while ( (m = tm) ){ 1352 | tm = m->next; 1353 | cleanupmon(m); 1354 | } 1355 | 1356 | XFreeCursor(dis, cursor); 1357 | 1358 | XSync(dis, False); 1359 | XSetInputFocus(dis, PointerRoot, RevertToPointerRoot, CurrentTime); 1360 | } 1361 | 1362 | int 1363 | getptrcoords(int* x, int* y){ 1364 | int di; 1365 | unsigned int dui; 1366 | Window dummy; 1367 | 1368 | return XQueryPointer(dis, root, &dummy, &dummy, x, y, &di, &di, &dui); 1369 | } 1370 | 1371 | void 1372 | grabbuttons(client* c, int focused){ 1373 | int i, j; 1374 | unsigned int modifiers[] = { 0, LockMask }; 1375 | 1376 | XUngrabButton(dis, AnyButton, AnyModifier, c->win); 1377 | if (!focused) 1378 | XGrabButton(dis, AnyButton, AnyModifier, c->win, False, 1379 | BUTTONMASK, GrabModeSync, GrabModeSync, None, None); 1380 | 1381 | /* "all" our clicks are ClkWin */ 1382 | for (i=0;i < TABLENGTH(buttons);i++) 1383 | for (j=0;j < TABLENGTH(modifiers);j++) 1384 | XGrabButton(dis, buttons[i].btn, 1385 | buttons[i].mask | modifiers[j], 1386 | c->win, False, BUTTONMASK, 1387 | GrabModeAsync, GrabModeSync, None, None); 1388 | } 1389 | 1390 | void 1391 | outputstats(){ 1392 | char* isdeskocc, * isdesksel, monstate[NUMTAGS+3]; 1393 | int i; 1394 | unsigned int occ, sel; 1395 | 1396 | /* output: 1397 | * "0:SONNNNNNN:Y" 1398 | * im->num:SEL/OCC/EMPTY:curlayout->symbol 1399 | */ 1400 | for EACHMON(mhead){ 1401 | occ = sel = 0; 1402 | isdeskocc = ecalloc(NUMTAGS, sizeof(char)); 1403 | isdesksel = ecalloc(NUMTAGS, sizeof(char)); 1404 | sel = im->seldesks; 1405 | 1406 | for EACHCLIENT(im->head) 1407 | occ |= ic->desks; 1408 | 1409 | /* uis get reordered in the dest string so they are left-to-right */ 1410 | uitos(occ, NUMTAGS, isdeskocc); 1411 | uitos(sel, NUMTAGS, isdesksel); 1412 | 1413 | for (i=0;icurlayout->letter; 1424 | monstate[NUMTAGS+2] = '\0'; 1425 | 1426 | setrootstats(monstate, im->num); 1427 | 1428 | free(isdeskocc); 1429 | free(isdesksel); 1430 | } 1431 | 1432 | fflush(stdout); 1433 | } 1434 | 1435 | /* à la BSPWM */ 1436 | void 1437 | runconfig(){ 1438 | int i; 1439 | char shcmd[MAXLEN]; 1440 | 1441 | for (i=0;i < TABLENGTH(progs);i++){ 1442 | snprintf(shcmd, slen(progs[i])+1, "%s", progs[i]); 1443 | 1444 | if (fork() == 0) { 1445 | if (dis) 1446 | close(ConnectionNumber(dis)); 1447 | setsid(); 1448 | execl("/bin/sh", "/bin/sh", "-c", shcmd, (char *) NULL); 1449 | fprintf(stderr, "sara: failed to execute startup cmd\n"); 1450 | exit(EXIT_SUCCESS); 1451 | } 1452 | } 1453 | } 1454 | 1455 | 1456 | /* This man is a god 1457 | * https://jonas-langlotz.de/2020/10/05/polybar-on-dwm 1458 | */ 1459 | void 1460 | setrootstats(char* monstate, int monnum){ 1461 | char* atom = malloc(sizeof(char) * 40); 1462 | char* num = malloc(sizeof(char) * 12); 1463 | sprintf(atom, "%s", ""); 1464 | sprintf(num, "%d", monnum); 1465 | strcat(atom, "SARA_MONSTATE_"); 1466 | strcat(atom, num); 1467 | strcat(atom, "\0"); 1468 | XChangeProperty(dis, root, XInternAtom(dis, atom, False), 1469 | XA_STRING, 8, PropModeReplace, (unsigned char*) monstate, 1470 | NUMTAGS+3); 1471 | } 1472 | 1473 | void 1474 | setup(){ 1475 | XSetWindowAttributes wa; 1476 | 1477 | screen = DefaultScreen(dis); 1478 | root = RootWindow(dis, screen); 1479 | 1480 | sw = XDisplayWidth(dis, screen); 1481 | sh = XDisplayHeight(dis, screen); 1482 | 1483 | cursor = XCreateFontCursor(dis, 68); 1484 | 1485 | running = 1; 1486 | 1487 | mhead = NULL; 1488 | curmon = NULL; 1489 | 1490 | updategeom(); 1491 | loaddesktop(0); 1492 | outputstats(); 1493 | 1494 | w_atom = XInternAtom(dis, "WM_STATE", False); 1495 | 1496 | wa.cursor = cursor; 1497 | wa.event_mask = SubstructureRedirectMask|SubstructureNotifyMask 1498 | |ButtonPressMask|PointerMotionMask|EnterWindowMask 1499 | |StructureNotifyMask|PropertyChangeMask; 1500 | XChangeWindowAttributes(dis, root, CWEventMask|CWCursor, &wa); 1501 | XSelectInput(dis, root, wa.event_mask); 1502 | } 1503 | 1504 | /* many thanks to bspwm, geeksforgeeks, Beej for sockets */ 1505 | void 1506 | start(){ 1507 | int nbytes; 1508 | char msg[MAXBUFF]; 1509 | fd_set desc; 1510 | XEvent ev; 1511 | int cfd, max_fd, sfd, xfd = ConnectionNumber(dis); 1512 | struct sockaddr saddress = {AF_UNIX, INPUTSOCK}; 1513 | 1514 | if ( (sfd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0 ) 1515 | die("couldn't create socket!"); 1516 | 1517 | unlink(INPUTSOCK); 1518 | 1519 | if (bind(sfd, &saddress, sizeof(saddress)) < 0) 1520 | die("couldn't bind socket!"); 1521 | 1522 | if (listen(sfd, SOMAXCONN) < 0) 1523 | die("couldn't listen to socket!"); 1524 | 1525 | runconfig(); 1526 | 1527 | while (running){ 1528 | XFlush(dis); 1529 | 1530 | FD_ZERO(&desc); 1531 | FD_SET(sfd, &desc); 1532 | FD_SET(xfd, &desc); 1533 | max_fd = MAX(sfd, xfd); 1534 | 1535 | if (select(max_fd + 1, &desc, NULL, NULL, NULL) > 0){ 1536 | /* Check for socket connections */ 1537 | if (FD_ISSET(sfd, &desc)){ 1538 | cfd = accept(sfd, NULL, NULL); 1539 | if (cfd > 0 && (nbytes = recv(cfd, msg, sizeof(msg)-1, 0)) > 0){ 1540 | msg[nbytes] = '\0'; 1541 | handlemsg(msg); 1542 | close(cfd); 1543 | } 1544 | } 1545 | 1546 | /* Check for an X event manually - XNextEvent blocks until an event occurs */ 1547 | if (FD_ISSET(xfd, &desc)){ 1548 | while (XCheckMaskEvent(dis, ~0, &ev)) 1549 | if (events[ev.type]) 1550 | events[ev.type](&ev); 1551 | } 1552 | } 1553 | } 1554 | 1555 | close(sfd); 1556 | unlink(INPUTSOCK); 1557 | } 1558 | 1559 | int 1560 | xerror(Display* dis, XErrorEvent* e){ 1561 | return 0; 1562 | } 1563 | 1564 | int 1565 | xsendkill(Window w){ 1566 | int n; 1567 | Atom* protocols; 1568 | XEvent ev; 1569 | int exists = 0; 1570 | Atom destproto = XInternAtom(dis, "WM_DELETE_WINDOW", False); 1571 | 1572 | if (XGetWMProtocols(dis, w, &protocols, &n)){ 1573 | while (!exists && n--) 1574 | exists = (protocols[n] == destproto); 1575 | XFree(protocols); 1576 | } 1577 | 1578 | if (exists){ 1579 | ev.type = ClientMessage; 1580 | ev.xclient.window = w; 1581 | ev.xclient.message_type = XInternAtom(dis, "WM_PROTOCOLS", True); 1582 | ev.xclient.format = 32; 1583 | ev.xclient.data.l[0] = destproto; 1584 | ev.xclient.data.l[1] = CurrentTime; 1585 | XSendEvent(dis, w, False, NoEventMask, &ev); 1586 | } 1587 | 1588 | return exists; 1589 | } 1590 | 1591 | void 1592 | quit(const Arg arg){ 1593 | 1594 | parser[WantInt](arg.s, &parg); 1595 | 1596 | if (!SAFEPARG(0,1)) 1597 | return; 1598 | 1599 | if (parg.i > 0) 1600 | restart = 1; 1601 | 1602 | running = 0; 1603 | } 1604 | 1605 | #ifdef _SHAPE_H_ 1606 | // TODO: XCB 1607 | void 1608 | roundcorners(client *c) 1609 | { 1610 | int diam; 1611 | Pixmap mask; 1612 | GC shapegc; 1613 | 1614 | if (corner_radius < 0) 1615 | return; 1616 | 1617 | if (!c || c->isfull) 1618 | return; 1619 | 1620 | diam = 2 * corner_radius; 1621 | if (c->w < diam || c->h < diam) 1622 | return; 1623 | 1624 | if (!(mask = XCreatePixmap(dis, c->win, c->w, c->h, 1))) 1625 | return; 1626 | 1627 | if (!(shapegc = XCreateGC(dis, mask, 0, NULL))){ 1628 | XFreePixmap(dis, mask); 1629 | free(shapegc); 1630 | return; 1631 | } 1632 | 1633 | XFillRectangle(dis, mask, shapegc, 0, 0, c->w, c->h); 1634 | XSetForeground(dis, shapegc, 1); 1635 | 1636 | /* topleft, topright, bottomleft, bottomright 1637 | * man XArc - positive is counterclockwise 1638 | */ 1639 | XFillArc(dis, mask, shapegc, 0, 0, diam, diam, 90 * 64, 90 * 64); 1640 | XFillArc(dis, mask, shapegc, c->w - diam - 1, 0, diam, diam, 0 * 64, 90 * 64); 1641 | XFillArc(dis, mask, shapegc, 0, c->h - diam - 1, diam, diam, -90 * 64, -90 * 64); 1642 | XFillArc(dis, mask, shapegc, c->w - diam - 1, c->h - diam - 1, diam, diam, 0 * 64, -90 * 64); 1643 | 1644 | XFillRectangle(dis, mask, shapegc, corner_radius, 0, c->w - diam, c->h); 1645 | XFillRectangle(dis, mask, shapegc, 0, corner_radius, c->w, c->h - diam); 1646 | XShapeCombineMask(dis, c->win, ShapeBounding, 0, 0, mask, ShapeSet); 1647 | XFreePixmap(dis, mask); 1648 | XFreeGC(dis, shapegc); 1649 | } 1650 | 1651 | // TODO: XCB 1652 | void 1653 | unroundcorners(client *c) 1654 | { 1655 | Pixmap mask; 1656 | GC shapegc; 1657 | 1658 | if (corner_radius < 0 || !c) 1659 | return; 1660 | 1661 | if (!(mask = XCreatePixmap(dis, c->win, c->w, c->h, 1))) 1662 | return; 1663 | 1664 | if (!(shapegc = XCreateGC(dis, mask, 0, NULL))){ 1665 | XFreePixmap(dis, mask); 1666 | free(shapegc); 1667 | return; 1668 | } 1669 | 1670 | XSetForeground(dis, shapegc, 1); 1671 | XFillRectangle(dis, mask, shapegc, 0, 0, c->w, c->h); 1672 | XShapeCombineMask(dis, c->win, ShapeBounding, 0, 0, mask, ShapeSet); 1673 | XFreePixmap(dis, mask); 1674 | XFreeGC(dis, shapegc); 1675 | } 1676 | #endif 1677 | 1678 | 1679 | /* --------------------------------------- 1680 | * sarasock interfacing 1681 | * --------------------------------------- 1682 | */ 1683 | 1684 | /* Thanks to bspwm's handle_message and process_message for some inspiration */ 1685 | void 1686 | handlemsg(char* msg){ 1687 | char* funcstr, * argstr; 1688 | void (*func)(Arg); 1689 | Arg arg; 1690 | 1691 | funcstr = strtok(msg, " "); 1692 | argstr = strtok(NULL, " "); 1693 | 1694 | if (argstr){ 1695 | arg.s = argstr; 1696 | 1697 | if ( (func = str2func(funcstr)) ) 1698 | func(arg); 1699 | } 1700 | } 1701 | 1702 | /* thanks to StackOverflow's wallyk for analagous str2enum */ 1703 | void (*str2func(const char* str))(Arg){ 1704 | int i; 1705 | for (i=0;i < TABLENGTH(conversions);i++) 1706 | if (STREQ(str, conversions[i].str)) 1707 | return conversions[i].func; 1708 | 1709 | return NULL; 1710 | } 1711 | 1712 | 1713 | /* --------------------------------------- 1714 | * X 1715 | * --------------------------------------- 1716 | */ 1717 | 1718 | 1719 | // TODO: XCB 1720 | /* dwm */ 1721 | void 1722 | adopt(){ 1723 | unsigned int i, num; 1724 | Window d1, d2, * wins = NULL; 1725 | XWindowAttributes wa; 1726 | 1727 | if (XQueryTree(dis, root, &d1, &d2, &wins, &num)) { 1728 | for (i = 0; i < num; i++) { 1729 | if (!XGetWindowAttributes(dis, wins[i], &wa) 1730 | || wa.override_redirect || XGetTransientForHint(dis, wins[i], &d1)) 1731 | continue; 1732 | if (wa.map_state == IsViewable) 1733 | manage(wins[i], &wa); 1734 | } 1735 | 1736 | for (i = 0; i < num; i++) { /* now the transients */ 1737 | if (!XGetWindowAttributes(dis, wins[i], &wa)) 1738 | continue; 1739 | if (XGetTransientForHint(dis, wins[i], &d1) 1740 | && (wa.map_state == IsViewable)) 1741 | manage(wins[i], &wa); 1742 | } 1743 | 1744 | if (wins) 1745 | XFree(wins); 1746 | } 1747 | } 1748 | 1749 | void 1750 | buttonpress(XEvent* e){ 1751 | int i; 1752 | client* c; 1753 | monitor* m; 1754 | XButtonPressedEvent* ev = &e->xbutton; 1755 | 1756 | if ( (m = findmon(ev->window)) && m != curmon ) 1757 | changemon(m, NoFocus); 1758 | 1759 | if ( (c = findclient(ev->window)) ){ 1760 | if (c != c->mon->current) 1761 | changecurrent(c, c->mon, c->mon->curdesk, 0); 1762 | 1763 | restack(c->mon); 1764 | XAllowEvents(dis, ReplayPointer, CurrentTime); 1765 | 1766 | /* "all" our button functions are performed on a client */ 1767 | for (i=0;i < TABLENGTH(buttons);i++) 1768 | if (buttons[i].func && buttons[i].btn == ev->button 1769 | && buttons[i].mask == ev->state) 1770 | buttons[i].func(buttons[i].arg); 1771 | } 1772 | } 1773 | 1774 | void 1775 | configurenotify(XEvent* e){ 1776 | XConfigureEvent* ev = &e->xconfigure; 1777 | 1778 | if (ev->window == root){ 1779 | sw = ev->width; sh = ev->height; 1780 | updategeom(); 1781 | 1782 | for EACHMON(mhead){ 1783 | for EACHCLIENT(im->head) 1784 | if (ic->isfull) 1785 | resizeclient(ic, im->mx, im->my, im->mw, im->mh); 1786 | 1787 | arrange(im); 1788 | } 1789 | } 1790 | } 1791 | 1792 | void 1793 | configurerequest(XEvent* e){ 1794 | client* c; 1795 | monitor* m; 1796 | XWindowChanges wc; 1797 | XConfigureRequestEvent* ev = &e->xconfigurerequest; 1798 | 1799 | if ( (c = findclient(ev->window)) ){ 1800 | if (c->isfloat){ 1801 | m = c->mon; 1802 | if (ev->value_mask & CWX) 1803 | c->x = m->mx + ev->x; 1804 | if (ev->value_mask & CWY) 1805 | c->y = (ev->y < barpx) ? barpx : ev->y; 1806 | if (ev->value_mask & CWWidth) 1807 | c->w = ev->width; 1808 | if (ev->value_mask & CWHeight) 1809 | c->h = ev->height; 1810 | if ((c->x + c->w) > m->mx + m->mw) 1811 | c->x = m->mx + (m->mw / 2 - c->w / 2); 1812 | if ((c->y + c->h) > m->wy + m->wh) 1813 | c->y = m->wy + (m->wh / 2 - c->h / 2); 1814 | if ((ev->value_mask & (CWX|CWY)) && !(ev->value_mask & (CWWidth|CWHeight))) 1815 | configure(c); 1816 | if (ISVISIBLE(c)) 1817 | XMoveResizeWindow(dis, c->win, c->x, c->y, c->w, c->h); 1818 | 1819 | } else { 1820 | configure(c); 1821 | } 1822 | 1823 | } else { 1824 | wc.x = ev->x; 1825 | wc.y = ev->y; 1826 | wc.width = ev->width; 1827 | wc.height = ev->height; 1828 | wc.sibling = ev->above; 1829 | wc.stack_mode = ev->detail; 1830 | XConfigureWindow(dis, ev->window, ev->value_mask, &wc); 1831 | } 1832 | 1833 | XSync(dis, False); 1834 | } 1835 | 1836 | void 1837 | destroynotify(XEvent* e){ 1838 | client* c; 1839 | XDestroyWindowEvent* ev = &e->xdestroywindow; 1840 | 1841 | if ( (c = findclient(ev->window)) ) 1842 | unmanage(c, 1); 1843 | } 1844 | 1845 | void 1846 | enternotify(XEvent* e){ 1847 | client* c; 1848 | monitor* m; 1849 | XCrossingEvent* ev = &e->xcrossing; 1850 | 1851 | if ((ev->mode != NotifyNormal || ev->detail == NotifyInferior) && ev->window != root) 1852 | return; 1853 | 1854 | if ( !(c = findclient(ev->window)) || c == curmon->current ) 1855 | return; 1856 | 1857 | if ( (m = c->mon) && m != curmon ) 1858 | changemon(m, YesFocus); 1859 | 1860 | changecurrent(c, c->mon, c->mon->curdesk, 0); 1861 | } 1862 | 1863 | void 1864 | focusin(XEvent* e){ 1865 | XFocusChangeEvent* ev = &e->xfocus; 1866 | 1867 | if (curmon->current && ev->window != curmon->current->win) 1868 | updatefocus(curmon); 1869 | } 1870 | 1871 | void 1872 | maprequest(XEvent* e){ 1873 | XWindowAttributes wa; 1874 | XMapRequestEvent* ev = &e->xmaprequest; 1875 | 1876 | if (!XGetWindowAttributes(dis, ev->window, &wa) || wa.override_redirect) 1877 | return; 1878 | 1879 | if (!findclient(ev->window)) 1880 | manage(ev->window, &wa); 1881 | } 1882 | 1883 | void 1884 | motionnotify(XEvent* e){ 1885 | monitor* m; 1886 | XMotionEvent* ev = &e->xmotion; 1887 | 1888 | if (ev->window != root) 1889 | return; 1890 | 1891 | if ( (m = coordstomon(ev->x_root, ev->y_root)) && m != curmon) 1892 | changemon(im, YesFocus); 1893 | } 1894 | 1895 | void 1896 | unmapnotify(XEvent* e){ 1897 | client* c; 1898 | XUnmapEvent* ev = &e->xunmap; 1899 | 1900 | if ( (c = findclient(ev->window)) ) 1901 | unmanage(c, 0); 1902 | } 1903 | 1904 | 1905 | int 1906 | main(){ 1907 | if ( !(dis = XOpenDisplay(NULL)) ) 1908 | die("Cannot open display!"); 1909 | XSetErrorHandler(xerror); 1910 | setup(); 1911 | 1912 | adopt(); 1913 | start(); 1914 | 1915 | if (restart) 1916 | execlp("sara", "sara"); 1917 | 1918 | cleanup(); 1919 | XCloseDisplay(dis); 1920 | 1921 | return 0; 1922 | } 1923 | -------------------------------------------------------------------------------- /src/sarasock.c: -------------------------------------------------------------------------------- 1 | /* 2 | * sara Window Manager 3 | * ______________________________________________________________________________ 4 | * 5 | * Please refer to the complete LICENSE file that should accompany this software. 6 | * Please refer to the MIT license for details on usage: https://mit-license.org/ 7 | */ 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | #include "common.h" 18 | 19 | 20 | int 21 | main(int argc, char* argv[]){ 22 | int i, sfd, left = MAXBUFF; 23 | char msg[MAXBUFF]; 24 | struct sockaddr saddress = {AF_UNIX, INPUTSOCK}; 25 | 26 | if (argc < 2) 27 | die("please provide at least one argument!"); 28 | 29 | for (i=1;i < argc && left > 0;i++){ 30 | if (i != 1){ 31 | strncat(msg, " ", 2); 32 | left--; 33 | } 34 | 35 | strncat(msg, argv[i], slen(argv[i]) > left ? left : slen(argv[i]) ); 36 | left -= slen(argv[i]); 37 | } 38 | 39 | msg[MAXBUFF-1] = '\0'; 40 | 41 | if ( (sfd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) 42 | die("failed to create socket!"); 43 | if (connect(sfd, &saddress, sizeof(saddress)) < 0) 44 | die("failed to connect to socket!"); 45 | if (send(sfd, msg, MAXBUFF, 0) < 0) 46 | die("failed to send to socket!"); 47 | 48 | close(sfd); 49 | 50 | return 0; 51 | } 52 | --------------------------------------------------------------------------------