@@ 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();
@@ 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},
};