Commit 140459fb authored by Tinu Weber's avatar Tinu Weber
Browse files

Restart from scratch.

parent 2efe0fee
/build
/config.mk
/build/
/karuiwm
/tags
/xinitrc.custom
/valgrind.log
This diff is collapsed.
# Config:
-include config.mk
# Compiler:
CC ?= gcc
VERBOSE ?= 0
# Application name:
APPNAME = karuiwm
......@@ -7,18 +11,11 @@ APPNAME = karuiwm
# Files:
SRCDIR = src
BUILDDIR = build
MODULEDIR = modules
SOURCES = $(shell find ${SRCDIR} -name '*.c')
SOURCES = $(shell find "${SRCDIR}" -name '*.c')
OBJECTS = $(SOURCES:${SRCDIR}/%.c=${BUILDDIR}/%.o)
DEPENDS = $(OBJECTS:%.o=%.d)
XINITRC = xinitrc
# Installation:
INSTALLDIR ?= /usr/local
BINDIR ?= ${INSTALLDIR}/bin
INCLUDEDIR ?= ${INSTALLDIR}/include/${APPNAME}
# ==============================================================================
# FLAGS ========================================================================
# Compilation flags:
_CFLAGS = -std=c99
......@@ -28,38 +25,29 @@ _CFLAGS += -Wlogical-op -Wpointer-arith -Wformat=2
_CFLAGS += -Winit-self -Wuninitialized -Wmaybe-uninitialized -Wshadow
_CFLAGS += -Wstrict-prototypes -Wmissing-declarations -Wmissing-prototypes
#_CFLAGS += -Wpadded
_CFLAGS += $(shell pkg-config --cflags x11)
_CFLAGS += -I${HOME}/.local/include
_CFLAGS += $(shell pkg-config --cflags x11 xinerama)
_CFLAGS_ASAN = -fsanitize=address -fno-omit-frame-pointer
_CFLAGS_DEBUG = -Werror -g -O1 -DMODE_DEBUG
_CFLAGS_RELEASE = -O2
_CFLAGS_XINERAMA = $(shell pkg-config --cflags xinerama) -DXINERAMA
# Libraries:
_LIBS = $(shell pkg-config --libs x11) -ldl
_LIBS = $(shell pkg-config --libs x11 xinerama) -ldl
_LIBS_ASAN =
_LIBS_DEBUG =
_LIBS_RELEASE =
_LIBS_XINERAMA = $(shell pkg-config --libs xinerama)
# Linker flags:
_LDFLAGS = -Wl,--export-dynamic
_LDFLAGS_ASAN = -fsanitize=address
_LDFLAGS_DEBUG =
_LDFLAGS_RELEASE =
_LDFLAGS_XINERAMA =
# ==============================================================================
# Configuration:
-include config.mk
# ==============================================================================
# COMPILATION ==================================================================
# Default: Release + Xinerama
.PHONY: all
all: release_xinerama
all: release
# Release:
.PHONY: release
......@@ -68,13 +56,6 @@ release: _LIBS += ${_LIBS_RELEASE}
release: _LDFLAGS += ${_LDFLAGS_RELEASE}
release: $(APPNAME)
# Release + Xinerama:
.PHONY: release_xinerama
release_xinerama: _CFLAGS += ${_CFLAGS_XINERAMA}
release_xinerama: _LIBS += ${_LIBS_XINERAMA}
release_xinerama: _LDFLAGS += ${_LDFLAGS_XINERAMA}
release_xinerama: release
# Debug:
.PHONY: debug
debug: _CFLAGS += ${_CFLAGS_DEBUG}
......@@ -82,13 +63,6 @@ debug: _LIBS += ${_LIBS_DEBUG}
debug: _LDFLAGS += ${_LDFLAGS_DEBUG}
debug: $(APPNAME)
# Debug + Xinerama:
.PHONY: debug_xinerama
debug_xinerama: _CFLAGS += ${_CFLAGS_XINERAMA}
debug_xinerama: _LIBS += ${_LIBS_XINERAMA}
debug_xinerama: _LDFLAGS += ${_LDFLAGS_XINERAMA}
debug_xinerama: debug
# Address Sanitizer:
.PHONY: asan
asan: _CFLAGS += ${_CFLAGS_ASAN}
......@@ -96,23 +70,13 @@ asan: _LIBS += ${_LIBS_ASAN}
asan: _LDFLAGS += ${_LDFLAGS_ASAN}
asan: debug
# Address Sanitizer + Xinerama:
.PHONY: asan_xinerama
asan_xinerama: _CFLAGS += ${_CFLAGS_XINERAMA}
asan_xinerama: _LIBS += ${_LIBS_XINERAMA}
asan_xinerama: _LDFLAGS += ${_LDFLAGS_XINERAMA}
asan_xinerama: asan
# ==============================================================================
# Build-dependencies:
-include ${DEPENDS}
# Compile:
$(BUILDDIR)/%.o: _CFLAGS += ${CFLAGS}
$(BUILDDIR)/%.o: ${SRCDIR}/%.c
@printf "compiling \033[1m%s\033[0m ...\n" $@
mkdir -p "$(shell dirname $@)"
@mkdir -p "$(shell dirname $@)"
$(CC) ${_CFLAGS} -c $< -o $@
$(CC) ${_CFLAGS} -MM -MT $@ $< > ${BUILDDIR}/$*.d
......@@ -120,47 +84,11 @@ $(BUILDDIR)/%.o: ${SRCDIR}/%.c
$(APPNAME): _LIBS += ${LIBS}
$(APPNAME): _LDFLAGS += ${LDFLAGS}
$(APPNAME): $(OBJECTS)
@printf "linking \033[1m%s\033[0m ...\n" $@
$(CC) ${_LDFLAGS} ${OBJECTS} ${_LIBS} -o $@
# ==============================================================================
.PHONY: modules modules-install
modules:
for m in ${MODULEDIR}/*/; do cd "$$m"; make; cd -; done
modules-install:
for m in ${MODULEDIR}/*/; do cd "$$m"; make install; cd -; done
# ==============================================================================
# CLEANUP ======================================================================
# Generic actions:
.PHONY: clean mrproper install uninstall
.PHONY: clean
clean:
rm -rf ${BUILDDIR}
mrproper: clean
rm -f ${APPNAME}
install:
install -D ${APPNAME} ${BINDIR}/${APPNAME}
install-headers:
mkdir -p ${INCLUDEDIR}
install -m 644 ${SRCDIR}/*.h ${INCLUDEDIR}/
uninstall:
rm -f ${BINDIR}/${APPNAME}
rm -rf ${INCLUDEDIR}
# Ctags:
.PHONY: ctags
ctags:
rm -f tags
find ${SRCDIR} -name '*.[ch]' | ctags --append -L -
# ==============================================================================
# karuiwm-specific actions:
.PHONY: run xephyr valphyr
run:
startx ${XINITRC} -- :1
xephyr:
xinit ${XINITRC} -- $(shell which Xephyr) :1
valphyr:
VALGRIND=1 xinit ${XINITRC} -- $(shell which Xephyr) :1
karuiwm
=======
karuiwm is a lightweight, modular, dynamically tiling window manager for X11.
This is an attempt to write a tiling window manager for X11.
The master branch holds the newest version, which is still in early development
phase. Check out the [legacy](https://github.com/ayekat/karuiwm/tree/legacy)
branch if you want to see what it might look like.
build
-----
make
will build karuiwm with the default settings. The targets `release_xinerama`
(default), `release`, `debug_xinerama`, `debug`, `asan_xinerama` and `asan` will
build karuiwm for release mode, debug mode, and debug mode with address
sanitizer, each with and without Xinerama support.
build modules
-------------
karuiwm on its own is pretty useless - the *modules* are the components doing
the real job. They are situated in the [modules](modules) directory, and each of
them can be compiled as easy as the main program:
cd modules/$modulename/
make
For building all modules at once, you can also simply run
make modules
from the project root.
install
-------
make install
will install karuiwm to /usr/local. Pass `INSTALLDIR=...` to install karuiwm to
a different location. The modules can be installed similarly:
cd modules/$modulename/
make install
Or for installing all modules at once:
make modules-install
from the project root.
for developers
--------------
For testing purposes, karuiwm can also be launched via Xephyr (X server in a
window):
make xephyr
To run it with valgrind in Xephyr, you can use the `valphyr` target:
make valphyr
Alternatively, `make run` will launch karuiwm normally, however it is
discouraged to run from within an existing X session, as it will likely cause an
X hickup.
See the [doc](doc) folder for the documentation.
configuration
-------------
The configuration happens through [X
resources](https://en.wikipedia.org/wiki/X_resources). Here is a sample X
resources configuration snippet that can be used and modified:
``` Xresources
karuiwm.client.border.width : 1
karuiwm.client.border.colour : #FF0000
karuiwm.client.border.colour_focus : #00FF00
karuiwm.monitor.gap.top : 15
karuiwm.layouts : rstack, monocle
karuiwm.modifier : A
karuiwm.keysym.M-k : stepclient:prev
karuiwm.keysym.M-j : stepclient:next
karuiwm.keysym.M-S-k : shiftclient:prev
karuiwm.keysym.M-S-j : shiftclient:next
karuiwm.keysym.M-Return : zoom
karuiwm.keysym.M-S-c : killclient
karuiwm.keysym.M-t : togglefloat
karuiwm.keysym.M-C-h : stepdesktop:left
karuiwm.keysym.M-C-j : stepdesktop:down
karuiwm.keysym.M-C-k : stepdesktop:up
karuiwm.keysym.M-C-l : stepdesktop:right
karuiwm.keysym.M-l : setmfact:+0.05
karuiwm.keysym.M-h : setmfact:-0.05
karuiwm.keysym.M-comma : setnmaster:+1
karuiwm.keysym.M-period : setnmaster:-1
karuiwm.keysym.M-space : steplayout:next
karuiwm.keysym.M-S-space : steplayout:prev
karuiwm.keysym.M-r : setlayout:rstack
karuiwm.keysym.M-m : setlayout:monocle
karuiwm.keysym.M-q : restart
karuiwm.keysym.M-S-q : stop
karuiwm.keysym.M-n : spawn:urxvt
karuiwm.button.M-1 : mousemove
karuiwm.button.M-3 : mouseresize
```
Documentation will follow.
bugs
----
Although karuiwm has been crafted with utmost care and love, some bugs may have
slipped my notice. Please feel free to file bug reports.
I won't bite.
A previous version (from 2013/2014) is available in the
[legacy](https://github.com/ayekat/karuiwm/tree/legacy) branch. I use it
productively on my computers, although I would not recommend it.
Frequently Asked Questions
==========================
This is a collection of questions that may arise when looking at the code. It's
primarly targeted at developers who may look at a piece of code and wonder:
"hey, this is weird, I'll fix that!", while in fact there is a deeper reason
*why* that code is so weird.
* **Why not start managing windows in `createnotify` instead of `mapnotify`?**
This is due to some applications not mapping their windows immediately after
creating them. This would cause awkward holes in the window layout, as we only
handle mapped windows.
* **Why not stop managing windows in `unmapnotify` instead of `destroynotify`?**
This is due to desktop switching: we unmap all windows from invisible
desktops, so if we would stop managing all unmapped windows there, we would
basically lose track of all desktops we leave. That's not nice.
* **Why do we catch some X errors?**
In a perfect world, each X error could be avoided by having a perfect program.
Unfortunately, some X errors cannot be avoided. Here a breakdown of all of
them, and why we catch them before they wreak havoc:
* `error_code == BadWindow`: This error is caused by clients, no matter
what. We don't know why. Anyway, it's beyond the control of karuiwm.
* `request_code == X_SetInputFocus && error_code == BadMatch`: This error is
caused if we focus a window after it has been unmapped/destroyed.
Sometimes, several windows get unmapped at the same time, but we cannot
know of the second unmapped window until we have handled the first one.
This is often the case when a dialog box asks for confirmation to close an
application.
Of course, catching an X error will hide whenever there *truly* is an issue in
our code. But we can't change that. If someone finds an elegant way to
circumvent catching any of the errors above, (s)he is welcome to solve that.
* **Why is there a `seltiled`?**
The purpose of keeping a pointer to the selected tiled client is to be able to
put it on the top of the stack when arranging tiled windows. This may seem
unnecessary, but when applying layouts that overlap clients (such as the
monocle layout), we would like to keep the focused client on top.
* **Why do we split module announcing from the regular module initialisation?**
The basic problem we are trying to solve here is that certain things in the
module initialisation should happen as early as possible, whereas other should
happen as late as possible.
Announcing a module has nearly no dependencies: a module simply states what
layouts and actions it provides, and may listen to bus events. This can happen
very early in the initialisation. Also, it is crucial for layouts to be
announced before the session initialisation, as desktops need to know what
layouts they can apply, and key/button bindings need to know what actions are
available.
On the other hand, a module may depend on the WM (especially the session) to
be fully initialised in order to fully operate. This step of module
initialisation must happen as late as possible (ideally right before `run()`
is called).
karuiwm coding
==============
In order to maintain easily readable, hackable, debuggable code, a consistent
coding style is necessary.
coding style
------------
First off, read the [Linux kernel coding
style](https://www.kernel.org/doc/Documentation/CodingStyle). Then, apply a few
"patches":
1. When implementing a function, the return type of a function is to be split
from the function name and to be put on a separate line *above*. This allows
grepping for function implementations more easily, looks nicer, and has the
additional benefit of saving columns (remember, there are only 80 of them).
```c
int
function(int x)
{
/* function body */
}
```
2. Use comments sparsely, avoid function annotatations. A reader should be able
to understand what a function does by looking at its name, its arguments, and
its code (remember, it is short and sweet).
3. Component functions are prefixed by the component's name (e.g. `client_`,
`workspace_`, etc). Function names should be relatively short but accurate
(no abbreviations!). Note that this can sometimes be tricky.
The prefixes are not required for static functions, as there is no
possibility of a namespace clash (unless you come up with a function name
occupied by an included library).
4. A component should *never* manage the structures that it defines (e.g. keep a
list of existing structures, define actions that affect all structures, ...).
Remember that karuiwm has a tree-like hierarchy of which components manage
which components (for example, clients are defined in `client.h`, but are
handled by desktops (in `desktop.c`)); there is no such thing as "Component
XY manages structures XY". At worst, it is directly handled by the top
component `karuiwm.c` (but you may want to open a discussion first).
git
---
Development happens in the
[development](https://github.com/ayekat/karuiwm/tree/development) branch. **You
should generally not commit directly to master.** The purpose of this is to keep
a stable master branch, and to be able to test the code before it goes "public"
(think of the master branch as the "release" version). Small, trivial bug fixes
may be done directly in the master branch, though.
For smaller projects, this allows to have stable software without a need for
maintaining releases.
**We do generally not accept merge requests to the master branch.** Add your
feature to the development branch (or a branch based on the development branch).
### commit messages
Commit messages should consist of a one-line, less than 72 column wide title
explaining what the commit is about, followed by an empty line, and a multi-line
explanation about what the commit does.
### features and merging
When writing a new feature, or working on a specific topic of the project,
create a new branch on top of what it will be merged into. When finished, merge
the branch back into its parent (with *fast-forward* to keep the history clean).
Note: if the feature can be added to the development branch with one commit, a
separate branch is of course not necessary.
When merging the development branch into the master branch, do *not* use
fast-forwarding, to keep the history separated.
/config.mk
/*/config.mk
/*/build
/*/*.so
base
====
The `base` module adds the basic functionality for handling windows, desktops
and workspaces through mouse and keyboard input; it is therefore strongly
recommended to enable the `base` module (or a module that provides equivalent
functionality).
actions
-------
* `killclient`: Close the currently focused client's window.
* `mousemove`: Detach client under mouse from tiled layout and move it around
freely.
* `mouseresize`: Detach client under mouse from tiled layout and resize it
freely.
* `restart`: Stop karuiwm and start it again.
* `setlayout(STRING)`: Set layout to the one named `STRING`.
* `setmfact(FLOAT)`: Increase/decrease master area size by factor `FLOAT`.
* `setnmaster(INT)`: Increase/decrease number of clients in master area by `INT`
clients.
* `shiftclient(prev/next)`: Swap client with previous/next client in layout.
* `exec(STRING)`: Fork and execute a program `STRING`.
* `stepclient(prev/next)`: Set focus to previous/next client in layout.
* `stepdesktop(up/right/down/left)`: Set focus to upper/right/lower/left
desktop.
* `steplayout(prev/next)`: Arrange tiled clients according to next/previous
layout.
* `stop`: Stop karuiwm.
* `togglefloat`: Toggle floating state for focus client.
* `zoom`: Swap client with first client in the master area, or swap with next
client if already first master.
layouts
-------
* `monocle`: The focused client covers the entire desktop.
* `rstack`: The desktop is split to a *master* area to the left and a *stack*
area to the right.
#include "karuiwm/karuiwm.h"
#include "karuiwm/desktop.h"
#include "karuiwm/util.h"
#include "karuiwm/cursor.h"
#include "karuiwm/core.h"
#include "karuiwm/input.h"
#include "karuiwm/config.h"
#include "karuiwm/list.h"
#include "karuiwm/client.h"
#include "karuiwm/layout.h"
#include "karuiwm/bus.h"
#include <unistd.h>
#include <string.h>
#include <errno.h>
#define BASE_BUFSIZE 512
void announce(struct module *mod);
void denounce(void);
static void action_exec(union argument *arg);
static void action_killclient(union argument *arg);
static void action_mousemove(union argument *arg);
static void action_mouseresize(union argument *arg);
static void action_restart(union argument *arg);
static void action_setlayout(union argument *arg);
static void action_setmfact(union argument *arg);
static void action_setnmaster(union argument *arg);
static void action_shiftclient(union argument *arg);
static void action_stepclient(union argument *arg);
static void action_stepdesktop(union argument *arg);
static void action_steplayout(union argument *arg);
static void action_stop(union argument *arg);
static void action_togglefloat(union argument *arg);
static void action_zoom(union argument *arg);
static void cycle_init(enum bus_event event);
static void cycle_term(void);
static void layout_monocle(struct client *clients, size_t nc,
size_t nmaster, float mfact,
int wx, int wy, int unsigned ww, int unsigned wh);
static void layout_rstack(struct client *clients, size_t nc,
size_t nmaster, float mfact,
int wx, int wy, int unsigned ww, int unsigned wh);
static int locate_layout(struct layout **l, char const *name);
static void mouse_move(struct client *c, int mx, int my);
static void mouse_moveresize(struct client *c, void (*mh)(struct client *, int, int));
static void mouse_resize(struct client *c, int mx, int my);
static struct {
struct layout *layouts;
size_t nlayouts;
} base;
void
announce(struct module *mod)
{
module_announce_action(mod, "killclient", action_killclient, ARGTYPE_NONE);
module_announce_action(mod, "mousemove", action_mousemove, ARGTYPE_MOUSE);
module_announce_action(mod, "mouseresize", action_mouseresize, ARGTYPE_MOUSE);
module_announce_action(mod, "restart", action_restart, ARGTYPE_NONE);
module_announce_action(mod, "setlayout", action_setlayout, ARGTYPE_LAYOUT);
module_announce_action(mod, "setmfact", action_setmfact, ARGTYPE_FLOATING);
module_announce_action(mod, "setnmaster", action_setnmaster, ARGTYPE_INTEGER);
module_announce_action(mod, "shiftclient", action_shiftclient, ARGTYPE_LIST_DIRECTION);
module_announce_action(mod, "exec", action_exec, ARGTYPE_STRING);
module_announce_action(mod, "stepclient", action_stepclient, ARGTYPE_LIST_DIRECTION);
module_announce_action(mod, "stepdesktop", action_stepdesktop, ARGTYPE_DIRECTION);
module_announce_action(mod, "steplayout", action_steplayout, ARGTYPE_LIST_DIRECTION);
module_announce_action(mod, "stop", action_stop, ARGTYPE_NONE);
module_announce_action(mod, "togglefloat", action_togglefloat, ARGTYPE_NONE);
module_announce_action(mod, "zoom", action_zoom, ARGTYPE_NONE);
module_announce_layout(mod, "monocle", layout_monocle);
module_announce_layout(mod, "rstack", layout_rstack);
bus_listen(EVENT_MODULES, cycle_init);
}
void
denounce(void)
{
cycle_term();
}
static void
action_exec(union argument *arg)
{
char const *cmd = (char const *) arg->v;
pid_t pid = fork();
if (pid == 0) {
execl("/bin/sh", "sh", "-c", cmd, (char *) NULL);
ERROR("execl(%s) failed: %s", cmd, strerror(errno));
exit(1);
} else if (pid < 0) {
ERROR("fork() failed with code %d", pid);
}
}
static void
action_killclient(union argument *arg)
{
(void) arg;
desktop_kill_client(karuiwm.focus->selmon->seldt);
}
static void
action_mousemove(union argument *arg)
{
struct client *c;
Window win = *((Window *) arg->v);
if (session_locate_window(karuiwm.session, &c, win) < 0) {
WARN("attempt to mouse-move unhandled window %lu", win);
return;
}
mouse_moveresize(karuiwm.focus->selmon->seldt->selcli, mouse_move);
}
static void
action_mouseresize(union argument *arg)