~zenomat/wm

dd356bdb27a62c88b0feaece9692dea53538711b — zenomat 10 months ago 4a18dfb
update documentation
3 files changed, 155 insertions(+), 60 deletions(-)

M c-wm.c
M c-wm.h
M config.h
M c-wm.c => c-wm.c +145 -50
@@ 18,34 18,41 @@
#define PDEBUG(s) "\033[33;1m"s \
				  "\033[0m\n"

// TODO:
// 		- [ ] inner, outer gaps
// 		- [x] window unmapping
// 		- [x] keymapps
// 		- [x] rotating windows
// 		- [x] borders
// 			- [x] focus border color
// 			- NOTE: check fixme note in updateDisplay function
// 		- [x]  focus
// 		- [x] handle multiple screens
// 			- [ ] FIXME: figure out case, where it fucks up the arrangement, when unmapping on display change
// 		- [ ] handle workspaces
// 		- [ ] don't send keymap keypresses to window
// 		- [ ] support for floating windows, like popups (browser addons, signing key entry window)
/*
 * TODO:
 * 		- [ ] inner, outer gaps
 * 		- [x] window unmapping
 * 		- [x] keymapps
 * 		- [x] rotating windows
 * 		- [x] borders
 * 			- [x] focus border color
 * 			- check fixme note in updateDisplay function
 * 		- [x]  focus
 * 		- [x] handle multiple screens
 * 			- [x] figure out case, where it fucks up the arrangement, when unmapping on display change
 * 				- this is irrelevant, because we can't accidantaly unmap windows on display change
 * 		- [ ] move windows between monitors
 * 		- [ ] handle workspaces
 * 		- [ ] don't send keymap keypresses to window
 * 		- [ ] support for floating windows, like popups (browser addons, signing key entry window)
 */

xcb_connection_t* connection;
xcb_screen_t* screen;
WmState* wm_state;

// source: https://stackoverflow.com/a/19288271
// NOTE(zeno): we need this, because the % operator in C is the remainder, not the modulo, because
// NOTE: the division with integers truncates towards zero and not negativ infinitive
/*
 * source: https://stackoverflow.com/a/19288271
 * we need this, because the % operator in C is the remainder, not the modulo, because
 * the division with integers truncates towards zero and not negativ infinitive
 */
int mod(int a, int b)
{
	int r = a % b;
	return r < 0 ? r + b : r;
}

// query the window tree and return the length
int getWindowNum()
{
	xcb_generic_error_t* error;


@@ 55,7 62,6 @@ int getWindowNum()
	return num_of_wins;
}


// code adapted from the following source: https://github.com/Airblader/xedgewarp/blob/master/src/randr.c
static void randr_handle_output(xcb_randr_output_t id, xcb_randr_get_output_info_reply_t* output,
	xcb_timestamp_t timestamp)


@@ 64,6 70,7 @@ static void randr_handle_output(xcb_randr_output_t id, xcb_randr_get_output_info
	printf(PDEBUG("Handling output %d"), id);
#endif

	// if the output is disabled or disconnected, we don't care about it
	if (output->crtc == XCB_NONE || output->connection == XCB_RANDR_CONNECTION_DISCONNECTED) {
#ifdef DEBUG
		printf(PDEBUG("Output %d seems to be disabled / disconnected, skipping it."), id);


@@ 71,6 78,7 @@ static void randr_handle_output(xcb_randr_output_t id, xcb_randr_get_output_info
		return;
	}

	// if we find a connected monitor, get information about it
	xcb_randr_get_crtc_info_reply_t* crtc = xcb_randr_get_crtc_info_reply(connection,
		xcb_randr_get_crtc_info(connection, output->crtc, timestamp), NULL);
	if (crtc == NULL) {


@@ 78,19 86,17 @@ static void randr_handle_output(xcb_randr_output_t id, xcb_randr_get_output_info
		return;
	}

	Output* new = calloc(sizeof(Output), 1);
	if (new == NULL) {
		fprintf(stderr, "Could not alloc space for output %d, skipping it.", id);
		return;
	}

	new->id = id;
	new->x = crtc->x;
	new->y = crtc->y;
	new->width = crtc->width;
	new->height = crtc->height;
	// construct an output with all the information
	Output new = {
		.id = id,
		.x = crtc->x,
		.y = crtc->y,
		.width = crtc->width,
		.height = crtc->height,
	};

	addOutputToOutputs(*new);
	// add that output to our wm_state output array
	addOutputToOutputs(new);

#ifdef DEBUG
	printf(PDEBUG("Added output %d to list of outputs."), id);


@@ 98,6 104,7 @@ static void randr_handle_output(xcb_randr_output_t id, xcb_randr_get_output_info
	free(crtc);
}

// code copied and adopted from the following source: https://github.com/Airblader/xedgewarp/blob/master/src/randr.c
void populateOutputs()
{
	xcb_randr_get_screen_resources_current_reply_t* reply = xcb_randr_get_screen_resources_current_reply(


@@ 106,7 113,7 @@ void populateOutputs()
		fprintf(stderr, "Could not receive RandR outputs, bailing out.");
		exit(errno);
	}
	/* This allows us to ensure that we get consistent information from the server. */
	// This allows us to ensure that we get consistent information from the server.
	xcb_timestamp_t timestamp = reply->config_timestamp;

	int len = xcb_randr_get_screen_resources_current_outputs_length(reply);


@@ 142,6 149,10 @@ void populateOutputs()
	free(reply);
}

/*
 * find the index for the given window in the active workspace
 * on the active output
 */
int findWindowIndex(xcb_window_t window)
{
	WorkSpace* ws = &wm_state->outputs[wm_state->active_output_idx].workspace[wm_state->outputs[wm_state->active_output_idx].active_workspace_idx];


@@ 162,6 173,7 @@ xcb_keysym_t xcb_get_keysym(xcb_keycode_t keycode)
	xcb_key_symbols_free(keysyms);
	return keysym;
}

xcb_keycode_t* xcb_get_keycodes(xcb_keysym_t keysym)
{
	xcb_key_symbols_t* keysyms = xcb_key_symbols_alloc(connection);


@@ 171,8 183,10 @@ xcb_keycode_t* xcb_get_keycodes(xcb_keysym_t keysym)
	return keycode;
}

// adds the given output to the wm_state output array
void addOutputToOutputs(Output output)
{
	// realloc, because we have to accomodate one addtional entry in the array
	Output* new_output_arr = realloc(wm_state->outputs, (wm_state->output_num + 1) * sizeof(Output));
	if (new_output_arr == NULL) {
		fprintf(stderr, "ERROR: Reallocating memory for Outputs failed: %s\n", strerror(errno));


@@ 185,6 199,7 @@ void addOutputToOutputs(Output output)
	wm_state->output_num++;
}

// adds the given window to the active workspace
void addWindowToWorkSpace(xcb_window_t window)
{
	WorkSpace* ws = &wm_state->outputs[wm_state->active_output_idx].workspace[wm_state->outputs[wm_state->active_output_idx].active_workspace_idx];


@@ 201,22 216,29 @@ void addWindowToWorkSpace(xcb_window_t window)
	ws->winnum++;
}

// removes the given window from the active workspace
void removeWindowFromWorkSpace(xcb_window_t window)
{
	WorkSpace* ws = &wm_state->outputs[wm_state->active_output_idx].workspace[wm_state->outputs[wm_state->active_output_idx].active_workspace_idx];
	bool found = false;
	int amount;
	// check how many elements we need to allocate memeory for
	if (ws->winnum == 0) {
		amount = 0;
	} else {
		amount = ws->winnum - 1;
	}
	// no realloc, because we don't want to copy the previous array
	xcb_window_t* new_windows_arr = malloc(amount * sizeof(xcb_window_t));
	xcb_window_t* new_windows_arr = calloc(amount, sizeof(xcb_window_t));
	if (new_windows_arr == NULL) {
		fprintf(stderr, "ERROR: Could not allocate memory for new array: %s\n", strerror(errno));
		exit(errno);
	}
	/*
	 * go through all windows in the original array
	 * if the window we want to remove is the one in the current iteration,
	 * skip it, because we don't want it in the new array
	 */
	for (int i = 0; i < ws->winnum; i++) {
		if (ws->windows[i] == window) {
			found = true;


@@ 231,11 253,22 @@ void removeWindowFromWorkSpace(xcb_window_t window)
	ws->winnum--;
}

/*
 * recolors all the borders of all windows on all outputs according to
 * if they are focused or not
 * we only color the focused window on the focused output with the focused border
 * color
 */
void colorBorders()
{
	int active_output = wm_state->active_output_idx;
	int active_workspace = wm_state->outputs[active_output].active_workspace_idx;
	uint32_t values[1];
	/*
	 * go through all monitors and all windows on the active workspace
	 * check if window is the focused one, then color with focused color, else
	 * unfocused color
	 */
	for (int i = 0; i < wm_state->output_num; i++) {
		for (int j = 0; j < wm_state->outputs[i].workspace[active_workspace].winnum; j++) {
			if (i == wm_state->active_output_idx && wm_state->outputs[i].workspace[active_workspace].windows[j] == wm_state->outputs[i].workspace[active_workspace].active_window) {


@@ 249,36 282,52 @@ void colorBorders()
	xcb_flush(connection);
}

// sets the active display
void setActiveDisplay(int display_num)
{
	wm_state->active_output_idx = display_num;
	// figure out how to make this instantanious
	// recolor borders, because we only want the active border color on the
	// active window
	colorBorders();
}

// focused the next display, wraps around
void setFocusNextDisplay()
{
	// find the next display
	// we wrap around on the end, using mod
	int offset = (wm_state->active_output_idx == wm_state->output_num - 1) ? wm_state->output_num : 0;
	int next_output_idx = mod(wm_state->active_output_idx + 1, wm_state->output_num + 1) - offset;
	setActiveDisplay(next_output_idx);
}

// focused the prev display, wraps around
void setFocusPrevDisplay()
{
	// find the prev display
	// we wrap around on the end, using mod
	int offset = (wm_state->active_output_idx == 0) ? wm_state->output_num - 1 : 0;
	int prev_output_idx = mod(wm_state->active_output_idx - 1, wm_state->output_num + 1) - offset;
	setActiveDisplay(prev_output_idx);
}

// sets the focus and the input focus to the given window on the active
// workspace on the active output
void setFocus(xcb_window_t window)
{
	WorkSpace* ws = &wm_state->outputs[wm_state->active_output_idx].workspace[wm_state->outputs[wm_state->active_output_idx].active_workspace_idx];
	ws->active_window = window;
	// set input focus to the window, so it receives the keyboard input
	xcb_set_input_focus(connection, XCB_INPUT_FOCUS_POINTER_ROOT, ws->active_window, XCB_CURRENT_TIME);
	/* recolor all borders, because we want our main window to have the active
	 * focus color.
	 * we can't just repaint the previous focused window, because we don't have
	 * that information
	 */
	colorBorders();
	xcb_flush(connection);
}

// focues the next window, wraps around
void setFocusNext()
{
	WorkSpace* ws = &wm_state->outputs[wm_state->active_output_idx].workspace[wm_state->outputs[wm_state->active_output_idx].active_workspace_idx];


@@ 295,6 344,7 @@ void setFocusNext()
	setFocus(next_win);
}

// focues the prev window, wraps around
void setFocusPrev()
{
	WorkSpace* ws = &wm_state->outputs[wm_state->active_output_idx].workspace[wm_state->outputs[wm_state->active_output_idx].active_workspace_idx];


@@ 311,8 361,19 @@ void setFocusPrev()
	setFocus(prev_win);
}

/*
 * spawns the given program with the given args
 * takes a null terminated array, where the first index is the program
 * to execute and the other indexes are the args
 */
void spawn(char** prg)
{
	/*
	 * fork twice, once in the main program, once in the child, because
	 * then we don't have to wait for the child process to exit.
	 * the execute program gets assigned to the init process, which handles
	 * the waiting for the end and reaping
	 */
	if (fork() == 0) {
		setsid();
		if (fork() != 0) {


@@ 324,6 385,7 @@ void spawn(char** prg)
	wait(NULL);
}

// kills the focused window on the focused workspace on the focused output
void killActiveWindow()
{
	WorkSpace* ws = &wm_state->outputs[wm_state->active_output_idx].workspace[wm_state->outputs[wm_state->active_output_idx].active_workspace_idx];


@@ 332,26 394,43 @@ void killActiveWindow()
}

// source: https://www.geeksforgeeks.org/c-program-cyclically-rotate-array-one/
// rotates the window stack towards the end, wraps arround
void rotateToNext()
{
	WorkSpace* ws = &wm_state->outputs[wm_state->active_output_idx].workspace[wm_state->outputs[wm_state->active_output_idx].active_workspace_idx];
	// store the last window in tmp, so we can later assign that to the first one
	// (wrap around)
	int tmp = ws->windows[ws->winnum - 1];
	/*
	 * got through all windows and map the window to the previous,
	 * except the first window, because here we have to assign the last,
	 * because we want to wrap around
	 */
	for (int i = ws->winnum - 1; i > 0; i--)
		ws->windows[i] = ws->windows[i - 1];
	ws->windows[0] = tmp;
	updateDisplay();
}

// rotates the window stack towards the beginning, wraps around
void rotateToPrev()
{
	WorkSpace* ws = &wm_state->outputs[wm_state->active_output_idx].workspace[wm_state->outputs[wm_state->active_output_idx].active_workspace_idx];
	// store the first window in tmp, so we can later assign that to the last one
	// (wrap around)
	int tmp = ws->windows[0];
	/*
	 * got through all windows and map the window to the next,
	 * except the last window, because here we have to assign the first,
	 * because we want to wrap around
	 */
	for (int i = 0; i < ws->winnum - 1; i++)
		ws->windows[i] = ws->windows[i + 1];
	ws->windows[ws->winnum - 1] = tmp;
	updateDisplay();
}

// repositions all the windows only on the focused output and workspace
void updateDisplay()
{
	int active_output = wm_state->active_output_idx;


@@ 371,6 450,11 @@ void updateDisplay()
	for (int window_idx = 0; window_idx < winnum; window_idx++) {
		// the master window
		if (window_idx == 0) {
			/*
			 * we add the x and y of the current monitor to all x and y coordinates of the window,
			 * because X has one big screen, where the physical screens are mapped into, using x and y
			 * to specify their top left corner in the big window
			 */
			x = wm_state->outputs[active_output].x + gaps;
			y = wm_state->outputs[active_output].y + gaps;
			// if only one window, take whole width,


@@ 410,16 494,19 @@ void updateDisplay()
	xcb_flush(connection);
}

// handles the map request send by the X server
void handleMapRequest(xcb_generic_event_t* event)
{
	xcb_map_request_event_t* map = (xcb_map_request_event_t*)event;
	xcb_map_window(connection, map->window);

	// events we want to get notifications about
	uint32_t events[] = {
		XCB_EVENT_MASK_KEY_PRESS | XCB_EVENT_MASK_FOCUS_CHANGE
	};
	xcb_change_window_attributes(connection, map->window, XCB_CW_EVENT_MASK,
		events);
	// set border width
	uint32_t values[] = { border_width };
	xcb_configure_window(connection, map->window, XCB_CONFIG_WINDOW_BORDER_WIDTH, values);
	xcb_flush(connection);


@@ 428,13 515,14 @@ void handleMapRequest(xcb_generic_event_t* event)
	// so we can keep track of it
	addWindowToWorkSpace(map->window);

	updateDisplay();

	// set focus to the window, because we want to focus the newest window
	setFocus(map->window);

	xcb_flush(connection);
	// updateDisplay, because new window
	updateDisplay();
}

// handles the window destroy event send by the X server
void handleWindowDestroy(xcb_generic_event_t* event)
{
	WorkSpace* ws = &wm_state->outputs[wm_state->active_output_idx].workspace[wm_state->outputs[wm_state->active_output_idx].active_workspace_idx];


@@ 442,18 530,21 @@ void handleWindowDestroy(xcb_generic_event_t* event)

	xcb_kill_client(connection, dest->window);

	// if we kill the last window, set the focus to root, because there
	// will be no next window
	if (ws->winnum == 0 || ws->winnum == 1) {
		setFocus(screen->root);
	} else {
		setFocusNext();
		removeWindowFromWorkSpace(dest->window);
	}

	updateDisplay();
	removeWindowFromWorkSpace(dest->window);

	xcb_flush(connection);
	// we have to update the display, because one window got killed
	updateDisplay();
}

// handles the keypress event send by the X server
void handleKeyPress(xcb_generic_event_t* event)
{
	xcb_key_press_event_t* kp = (xcb_key_press_event_t*)event;


@@ 462,6 553,8 @@ void handleKeyPress(xcb_generic_event_t* event)
	printf(PDEBUG("keycode(%d) being pressed\nMod: %d"),
		xcb_get_keysym(kp->detail), kp->state);
#endif
	// go through all keybindings in the config and check if they match the
	// pressed keycombo and execute the specified function
	for (int i = 0; (long unsigned int)i < (sizeof(keymap) / sizeof(keymap[0])); i++) {
		if (keymap[i].mod == kp->state && keymap[i].key == (int)key) {
			keymap[i].func(keymap[i].args);


@@ 485,6 578,10 @@ void handleEventLoop()
			handleWindowDestroy(event);
			break;
		}
		case XCB_CONFIGURE_REQUEST: {
			printf(PDEBUG("COnfigure request"));
			break;
		}
		case XCB_KEY_PRESS: {
			handleKeyPress(event);
			break;


@@ 506,26 603,28 @@ void wm_setup()
	// all the events we want to get notifications about
	const uint32_t values[] = {
		XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT | XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY | XCB_EVENT_MASK_ENTER_WINDOW | XCB_EVENT_MASK_LEAVE_WINDOW | XCB_EVENT_MASK_STRUCTURE_NOTIFY | XCB_EVENT_MASK_PROPERTY_CHANGE | XCB_EVENT_MASK_KEY_PRESS | XCB_EVENT_MASK_FOCUS_CHANGE

	};

	xcb_change_window_attributes(connection, screen->root, XCB_CW_EVENT_MASK, values);

	// grab all keys from our keybindings table, so they don't get send to
	// the applications in which they occur
	for (int i = 0; i < (sizeof(keymap) / sizeof(keymap[0])); i++) {
		xcb_keycode_t* keycode = xcb_get_keycodes(keymap[i].key);
		if (keycode != NULL) {
			xcb_grab_key(connection, 1, screen->root, keymap[i].mod, *keycode, XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC);
		}
	}
	xcb_change_window_attributes(connection, screen->root, XCB_CW_EVENT_MASK,
		values);
	xcb_flush(connection);

	// allocate out memory for the wm_state, initallize to 0
	wm_state = calloc(sizeof(WmState), 1);

	wm_state->output_num = 0;

	// TODO(zeno): figure out a way to find primary output
	wm_state->active_output_idx = 0;
	// allocate memory for the the output struct array, because it is on heap
	// allocate the memeory for our outputs, initalize to zero
	Output* outputs = calloc(sizeof(Output), 1);

	if (outputs == NULL) {


@@ 535,12 634,11 @@ void wm_setup()

	wm_state->outputs = outputs;

	// get all the outputs and put the information in our wm_state struct
	populateOutputs();

	// allocate enough memory on the heap to hold the windows
	// we allocate on the heap, so we can expend it, if new windows get spawned
	// go through all the workspaces on all outputs and assing to memory
	for (int i = 0; i < wm_state->output_num; i++) {
		// printf("in iteration: %d, %d\n", i, j);
		for (int j = 0; j < 9; j++) {
			wm_state->outputs[i].workspace[j].winnum = 0;
			xcb_window_t* windows = calloc(1, sizeof(xcb_window_t));


@@ 566,9 664,6 @@ int main()
	xcb_screen_iterator_t iter = xcb_setup_roots_iterator(setup);
	screen = iter.data;

	// get connected monitors
	// randr_query_outputs();

	// setup the wm and get the tag
	wm_setup();


M c-wm.h => c-wm.h +1 -1
@@ 29,7 29,7 @@ typedef struct Keymap {

int mod(int, int);
int getWindowNum(); 
void populateOutputs(); // FIXME: everything
void populateOutputs();
int findWindowIndex(xcb_window_t);
xcb_keysym_t xcb_get_keysym(xcb_keycode_t);
xcb_keycode_t* xcb_get_keycodes(xcb_keysym_t);

M config.h => config.h +9 -9
@@ 7,13 7,13 @@ static char* TermCmd[] = { "st", NULL };
static char* MenuCmd[] = { "dmenu_run", NULL };

static Keymap keymap[] = {
	{ MODKEY,							Key_d,				spawn, 					MenuCmd},
	{ MODKEY | SHIFTKEY, 				Key_Return,			spawn, 					TermCmd},
	{ MODKEY,							Key_j,				setFocusPrev,			NULL},
	{ MODKEY,							Key_k,				setFocusNext,			NULL},
	{ MODKEY,							Key_w,				killActiveWindow, 		NULL},
	{ MODKEY | SHIFTKEY,				Key_j,				rotateToPrev,			NULL},
	{ MODKEY | SHIFTKEY,				Key_k,				rotateToNext,			NULL},
	{ MODKEY,							Key_Comma,			setFocusPrevDisplay, 	NULL},
	{ MODKEY,							Key_Period,			setFocusNextDisplay, 	NULL},
	{ ALTKEY,							Key_d,				spawn, 					MenuCmd},
	{ ALTKEY | SHIFTKEY, 				Key_Return,			spawn, 					TermCmd},
	{ ALTKEY,							Key_j,				setFocusPrev,			NULL},
	{ ALTKEY,							Key_k,				setFocusNext,			NULL},
	{ ALTKEY,							Key_w,				killActiveWindow, 		NULL},
	{ ALTKEY | SHIFTKEY,				Key_j,				rotateToPrev,			NULL},
	{ ALTKEY | SHIFTKEY,				Key_k,				rotateToNext,			NULL},
	{ ALTKEY,							Key_Comma,			setFocusPrevDisplay, 	NULL},
	{ ALTKEY,							Key_Period,			setFocusNextDisplay, 	NULL},
};