Merge branch 'next'

This commit is contained in:
Michael Stapelberg
2012-12-12 00:18:23 +01:00
151 changed files with 6960 additions and 1271 deletions

View File

@ -56,6 +56,7 @@ EOL (\r?\n)
%s OUTPUT_COND
%s FOR_WINDOW_COND
%s EAT_WHITESPACE
%s BORDER_WIDTH
%x BUFFER_LINE
%x BAR
@ -171,6 +172,7 @@ EOL (\r?\n)
}
<ASSIGN_TARGET_COND>[ \t]*→[ \t]* { BEGIN(WANT_STRING); }
<ASSIGN_TARGET_COND>[ \t]+ { BEGIN(WANT_STRING); }
<BORDER_WIDTH>[^\n][0-9]+ { printf("Border width set to: %s\n", yytext); yylval.number = atoi(yytext); return NUMBER;}
<EXEC>--no-startup-id { printf("no startup id\n"); yy_pop_state(); return TOK_NO_STARTUP_ID; }
<EXEC>. { printf("anything else: *%s*\n", yytext); yyless(0); yy_pop_state(); yy_pop_state(); }
<OPTRELEASE>--release { printf("--release\n"); yy_pop_state(); return TOK_RELEASE; }
@ -200,9 +202,10 @@ auto { return TOK_AUTO; }
workspace_layout { return TOK_WORKSPACE_LAYOUT; }
new_window { return TOKNEWWINDOW; }
new_float { return TOKNEWFLOAT; }
normal { return TOK_NORMAL; }
normal { yy_push_state(BORDER_WIDTH); return TOK_NORMAL; }
none { return TOK_NONE; }
1pixel { return TOK_1PIXEL; }
pixel { yy_push_state(BORDER_WIDTH); return TOK_PIXEL; }
hide_edge_borders { return TOK_HIDE_EDGE_BORDERS; }
both { return TOK_BOTH; }
focus_follows_mouse { return TOKFOCUSFOLLOWSMOUSE; }
@ -212,6 +215,8 @@ force-xinerama { return TOK_FORCE_XINERAMA; }
fake_outputs { WS_STRING; return TOK_FAKE_OUTPUTS; }
fake-outputs { WS_STRING; return TOK_FAKE_OUTPUTS; }
workspace_auto_back_and_forth { return TOK_WORKSPACE_AUTO_BAF; }
force_display_urgency_hint { return TOK_WORKSPACE_URGENCY_TIMER; }
ms { return TOK_TIME_MS; }
workspace_bar { return TOKWORKSPACEBAR; }
popup_during_fullscreen { return TOK_POPUP_DURING_FULLSCREEN; }
ignore { return TOK_IGNORE; }

View File

@ -13,6 +13,8 @@
#include "all.h"
bool force_old_config_parser = false;
static pid_t configerror_pid = -1;
static Match current_match;
@ -108,6 +110,7 @@ static int detect_version(char *buf) {
strncasecmp(bind, "focus down", strlen("focus down")) == 0 ||
strncasecmp(bind, "border normal", strlen("border normal")) == 0 ||
strncasecmp(bind, "border 1pixel", strlen("border 1pixel")) == 0 ||
strncasecmp(bind, "border pixel", strlen("border pixel")) == 0 ||
strncasecmp(bind, "border borderless", strlen("border borderless")) == 0 ||
strncasecmp(bind, "--no-startup-id", strlen("--no-startup-id")) == 0 ||
strncasecmp(bind, "bar", strlen("bar")) == 0) {
@ -624,15 +627,20 @@ void parse_file(const char *f) {
}
}
/* now lex/parse it */
yy_scan_string(new);
context = scalloc(sizeof(struct context));
context->filename = f;
if (yyparse() != 0) {
fprintf(stderr, "Could not parse configfile\n");
exit(1);
if (force_old_config_parser) {
/* now lex/parse it */
yy_scan_string(new);
if (yyparse() != 0) {
fprintf(stderr, "Could not parse configfile\n");
exit(1);
}
} else {
struct ConfigResult *config_output = parse_config(new, context);
yajl_gen_free(config_output->json_gen);
}
check_for_duplicate_bindings(context);
@ -668,7 +676,8 @@ void parse_file(const char *f) {
start_configerror_nagbar(f);
}
yylex_destroy();
if (force_old_config_parser)
yylex_destroy();
FREE(context->line_copy);
free(context);
FREE(font_pattern);
@ -728,6 +737,7 @@ void parse_file(const char *f) {
%token <color> TOKCOLOR
%token TOKARROW "→"
%token TOKMODE "mode"
%token TOK_TIME_MS "ms"
%token TOK_BAR "bar"
%token TOK_ORIENTATION "default_orientation"
%token TOK_HORIZ "horizontal"
@ -738,6 +748,7 @@ void parse_file(const char *f) {
%token TOKNEWFLOAT "new_float"
%token TOK_NORMAL "normal"
%token TOK_NONE "none"
%token TOK_PIXEL "pixel"
%token TOK_1PIXEL "1pixel"
%token TOK_HIDE_EDGE_BORDERS "hide_edge_borders"
%token TOK_BOTH "both"
@ -746,6 +757,7 @@ void parse_file(const char *f) {
%token TOK_FORCE_XINERAMA "force_xinerama"
%token TOK_FAKE_OUTPUTS "fake_outputs"
%token TOK_WORKSPACE_AUTO_BAF "workspace_auto_back_and_forth"
%token TOK_WORKSPACE_URGENCY_TIMER "force_display_urgency_hint"
%token TOKWORKSPACEBAR "workspace_bar"
%token TOK_DEFAULT "default"
%token TOK_STACKING "stacking"
@ -816,9 +828,11 @@ void parse_file(const char *f) {
%type <number> bar_mode_mode
%type <number> bar_modifier_modifier
%type <number> optional_no_startup_id
%type <number> optional_border_width
%type <number> optional_release
%type <string> command
%type <string> word_or_number
%type <string> duration
%type <string> qstring_or_number
%type <string> optional_workspace_name
%type <string> workspace_name
@ -848,6 +862,7 @@ line:
| force_focus_wrapping
| force_xinerama
| fake_outputs
| force_display_urgency_hint
| workspace_back_and_forth
| workspace_bar
| workspace
@ -1052,6 +1067,11 @@ word_or_number:
}
;
duration:
NUMBER { sasprintf(&$$, "%d", $1); }
| NUMBER TOK_TIME_MS { sasprintf(&$$, "%d", $1); }
;
mode:
TOKMODE QUOTEDSTRING '{' modelines '}'
{
@ -1471,9 +1491,27 @@ new_float:
;
border_style:
TOK_NORMAL { $$ = BS_NORMAL; }
| TOK_NONE { $$ = BS_NONE; }
| TOK_1PIXEL { $$ = BS_1PIXEL; }
TOK_NORMAL optional_border_width
{
/* FIXME: the whole border_style thing actually screws up when new_float is used because it overwrites earlier values :-/ */
config.default_border_width = $2;
$$ = BS_NORMAL;
}
| TOK_1PIXEL
{
config.default_border_width = 1;
$$ = BS_PIXEL;
}
| TOK_NONE
{
config.default_border_width = 0;
$$ = BS_NONE;
}
| TOK_PIXEL optional_border_width
{
config.default_border_width = $2;
$$ = BS_PIXEL;
}
;
bool:
@ -1548,6 +1586,14 @@ workspace_back_and_forth:
}
;
force_display_urgency_hint:
TOK_WORKSPACE_URGENCY_TIMER duration
{
DLOG("workspace urgency_timer = %f\n", atoi($2) / 1000.0);
config.workspace_urgency_timer = atoi($2) / 1000.0;
}
;
workspace_bar:
TOKWORKSPACEBAR bool
{
@ -1736,6 +1782,11 @@ exec_always:
}
;
optional_border_width:
/* empty */ { $$ = 2; } // 2 pixels is the default value for any type of border
| NUMBER { $$ = $1; }
;
optional_no_startup_id:
/* empty */ { $$ = false; }
| TOK_NO_STARTUP_ID { $$ = true; }

View File

@ -179,6 +179,22 @@ static int route_click(Con *con, xcb_button_press_event_t *event, const bool mod
DLOG("--> OUTCOME = %p\n", con);
DLOG("type = %d, name = %s\n", con->type, con->name);
/* Any click in a workspace should focus that workspace. If the
* workspace is on another output we need to do a workspace_show in
* order for i3bar (and others) to notice the change in workspace. */
Con *ws = con_get_workspace(con);
Con *focused_workspace = con_get_workspace(focused);
if (!ws) {
ws = TAILQ_FIRST(&(output_get_content(con_get_output(con))->focus_head));
if (!ws)
goto done;
}
if (ws != focused_workspace)
workspace_show(ws);
focused_id = XCB_NONE;
/* dont handle dockarea cons, they must not be focused */
if (con->parent->type == CT_DOCKAREA)
goto done;
@ -207,21 +223,13 @@ static int route_click(Con *con, xcb_button_press_event_t *event, const bool mod
goto done;
}
/* 2: focus this con. If the workspace is on another output we need to
* do a workspace_show in order for i3bar (and others) to notice the
* change in workspace. */
Con *ws = con_get_workspace(con);
Con *focused_workspace = con_get_workspace(focused);
if (ws != focused_workspace)
workspace_show(ws);
focused_id = XCB_NONE;
/* 2: focus this con. */
con_focus(con);
/* 3: For floating containers, we also want to raise them on click.
* We will skip handling events on floating cons in fullscreen mode */
Con *fs = (ws ? con_get_fullscreen_con(ws, CF_OUTPUT) : NULL);
if (floatingcon != NULL && fs == NULL) {
if (floatingcon != NULL && fs != con) {
floating_raise_con(floatingcon);
/* 4: floating_modifier plus left mouse button drags */
@ -309,6 +317,25 @@ int handle_button_press(xcb_button_press_event_t *event) {
return route_click(con, event, mod_pressed, CLICK_INSIDE);
if (!(con = con_by_frame_id(event->event))) {
/* If the root window is clicked, find the relevant output from the
* click coordinates and focus the output's active workspace. */
if (event->event == root) {
Con *output, *ws;
TAILQ_FOREACH(output, &(croot->nodes_head), nodes) {
if (con_is_internal(output) ||
!rect_contains(output->rect, event->event_x, event->event_y))
continue;
ws = TAILQ_FIRST(&(output_get_content(output)->focus_head));
if (ws != con_get_workspace(focused)) {
workspace_show(ws);
tree_render();
}
return 1;
}
return 0;
}
ELOG("Clicked into unknown window?!\n");
xcb_allow_events(conn, XCB_ALLOW_REPLAY_POINTER, event->time);
xcb_flush(conn);

View File

@ -38,7 +38,6 @@
} \
} while (0)
static owindows_head owindows;
/*
* Returns true if a is definitely greater than b (using the given epsilon)
@ -57,19 +56,19 @@ static Output *get_output_from_string(Output *current_output, const char *output
Output *output;
if (strcasecmp(output_str, "left") == 0) {
output = get_output_next(D_LEFT, current_output);
output = get_output_next(D_LEFT, current_output, CLOSEST_OUTPUT);
if (!output)
output = get_output_most(D_RIGHT, current_output);
} else if (strcasecmp(output_str, "right") == 0) {
output = get_output_next(D_RIGHT, current_output);
output = get_output_next(D_RIGHT, current_output, CLOSEST_OUTPUT);
if (!output)
output = get_output_most(D_LEFT, current_output);
} else if (strcasecmp(output_str, "up") == 0) {
output = get_output_next(D_UP, current_output);
output = get_output_next(D_UP, current_output, CLOSEST_OUTPUT);
if (!output)
output = get_output_most(D_DOWN, current_output);
} else if (strcasecmp(output_str, "down") == 0) {
output = get_output_next(D_DOWN, current_output);
output = get_output_next(D_DOWN, current_output, CLOSEST_OUTPUT);
if (!output)
output = get_output_most(D_UP, current_output);
} else output = get_output_by_name(output_str);
@ -99,6 +98,29 @@ static bool maybe_back_and_forth(struct CommandResult *cmd_output, char *name) {
return true;
}
/*
* Return the passed workspace unless it is the current one and auto back and
* forth is enabled, in which case the back_and_forth workspace is returned.
*/
static Con *maybe_auto_back_and_forth_workspace(Con *workspace) {
Con *current, *baf;
if (!config.workspace_auto_back_and_forth)
return workspace;
current = con_get_workspace(focused);
if (current == workspace) {
baf = workspace_back_and_forth_get();
if (baf != NULL) {
DLOG("Substituting workspace with back_and_forth, as it is focused.\n");
return baf;
}
}
return workspace;
}
// This code is commented out because we might recycle it for popping up error
// messages on parser errors.
#if 0
@ -199,6 +221,20 @@ void cmd_MIGRATION_start_nagbar(void) {
* Criteria functions.
******************************************************************************/
/*
* Helper data structure for an operation window (window on which the operation
* will be performed). Used to build the TAILQ owindows.
*
*/
typedef struct owindow {
Con *con;
TAILQ_ENTRY(owindow) owindows;
} owindow;
typedef TAILQ_HEAD(owindows_head, owindow) owindows_head;
static owindows_head owindows;
/*
* Initializes the specified 'Match' data structure and the initial state of
* commands.c for matching target windows of a command.
@ -365,7 +401,8 @@ void cmd_move_con_to_workspace(I3_CMD, char *which) {
* when criteria was specified but didn't match any window or
* when criteria wasn't specified and we don't have any window focused. */
if ((!match_is_empty(current_match) && TAILQ_EMPTY(&owindows)) ||
(match_is_empty(current_match) && focused->type == CT_WORKSPACE)) {
(match_is_empty(current_match) && focused->type == CT_WORKSPACE &&
!con_has_children(focused))) {
ysuccess(false);
return;
}
@ -400,6 +437,38 @@ void cmd_move_con_to_workspace(I3_CMD, char *which) {
ysuccess(true);
}
/**
* Implementation of 'move [window|container] [to] workspace back_and_forth'.
*
*/
void cmd_move_con_to_workspace_back_and_forth(I3_CMD) {
owindow *current;
Con *ws;
ws = workspace_back_and_forth_get();
if (ws == NULL) {
y(map_open);
ystr("success");
y(bool, false);
ystr("error");
ystr("No workspace was previously active.");
y(map_close);
return;
}
HANDLE_EMPTY_MATCH;
TAILQ_FOREACH(current, &owindows, owindows) {
DLOG("matching: %p / %s\n", current->con, current->con->name);
con_move_to_workspace(current->con, ws, true, false);
}
cmd_output->needs_tree_render = true;
// XXX: default reply for now, make this a better reply
ysuccess(true);
}
/*
* Implementation of 'move [window|container] [to] workspace <name>'.
*
@ -421,9 +490,8 @@ void cmd_move_con_to_workspace_name(I3_CMD, char *name) {
ysuccess(false);
return;
}
if (match_is_empty(current_match) && focused->type == CT_WORKSPACE) {
ELOG("No window to move, you have focused a workspace.\n");
else if (match_is_empty(current_match) && focused->type == CT_WORKSPACE &&
!con_has_children(focused)) {
ysuccess(false);
return;
}
@ -432,6 +500,8 @@ void cmd_move_con_to_workspace_name(I3_CMD, char *name) {
/* get the workspace */
Con *ws = workspace_get(name, NULL);
ws = maybe_auto_back_and_forth_workspace(ws);
HANDLE_EMPTY_MATCH;
TAILQ_FOREACH(current, &owindows, owindows) {
@ -445,7 +515,7 @@ void cmd_move_con_to_workspace_name(I3_CMD, char *name) {
}
/*
* Implementation of 'move [window|container] [to] workspace number <number>'.
* Implementation of 'move [window|container] [to] workspace number <name>'.
*
*/
void cmd_move_con_to_workspace_number(I3_CMD, char *which) {
@ -455,7 +525,8 @@ void cmd_move_con_to_workspace_number(I3_CMD, char *which) {
* when criteria was specified but didn't match any window or
* when criteria wasn't specified and we don't have any window focused. */
if ((!match_is_empty(current_match) && TAILQ_EMPTY(&owindows)) ||
(match_is_empty(current_match) && focused->type == CT_WORKSPACE)) {
(match_is_empty(current_match) && focused->type == CT_WORKSPACE &&
!con_has_children(focused))) {
ysuccess(false);
return;
}
@ -469,8 +540,8 @@ void cmd_move_con_to_workspace_number(I3_CMD, char *which) {
if (parsed_num == LONG_MIN ||
parsed_num == LONG_MAX ||
parsed_num < 0 ||
*endptr != '\0') {
LOG("Could not parse \"%s\" as a number.\n", which);
endptr == which) {
LOG("Could not parse initial part of \"%s\" as a number.\n", which);
y(map_open);
ystr("success");
y(bool, false);
@ -489,6 +560,8 @@ void cmd_move_con_to_workspace_number(I3_CMD, char *which) {
workspace = workspace_get(which, NULL);
}
workspace = maybe_auto_back_and_forth_workspace(workspace);
HANDLE_EMPTY_MATCH;
TAILQ_FOREACH(current, &owindows, owindows) {
@ -503,23 +576,35 @@ void cmd_move_con_to_workspace_number(I3_CMD, char *which) {
static void cmd_resize_floating(I3_CMD, char *way, char *direction, Con *floating_con, int px) {
LOG("floating resize\n");
Rect old_rect = floating_con->rect;
if (strcmp(direction, "up") == 0) {
floating_con->rect.y -= px;
floating_con->rect.height += px;
} else if (strcmp(direction, "down") == 0 || strcmp(direction, "height") == 0) {
floating_con->rect.height += px;
} else if (strcmp(direction, "left") == 0) {
floating_con->rect.x -= px;
floating_con->rect.width += px;
} else {
floating_con->rect.width += px;
}
floating_check_size(floating_con);
/* Did we actually resize anything or did the size constraints prevent us?
* If we could not resize, exit now to not move the window. */
if (memcmp(&old_rect, &(floating_con->rect), sizeof(Rect)) == 0)
return;
if (strcmp(direction, "up") == 0) {
floating_con->rect.y -= px;
} else if (strcmp(direction, "left") == 0) {
floating_con->rect.x -= px;
}
}
static bool cmd_resize_tiling_direction(I3_CMD, char *way, char *direction, int ppt) {
static bool cmd_resize_tiling_direction(I3_CMD, Con *current, char *way, char *direction, int ppt) {
LOG("tiling resize\n");
/* get the appropriate current container (skip stacked/tabbed cons) */
Con *current = focused;
Con *other = NULL;
double percentage = 0;
while (current->parent->layout == L_STACKED ||
@ -599,10 +684,9 @@ static bool cmd_resize_tiling_direction(I3_CMD, char *way, char *direction, int
return true;
}
static bool cmd_resize_tiling_width_height(I3_CMD, char *way, char *direction, int ppt) {
static bool cmd_resize_tiling_width_height(I3_CMD, Con *current, char *way, char *direction, int ppt) {
LOG("width/height resize\n");
/* get the appropriate current container (skip stacked/tabbed cons) */
Con *current = focused;
while (current->parent->layout == L_STACKED ||
current->parent->layout == L_TABBED)
current = current->parent;
@ -697,17 +781,22 @@ void cmd_resize(I3_CMD, char *way, char *direction, char *resize_px, char *resiz
ppt *= -1;
}
Con *floating_con;
if ((floating_con = con_inside_floating(focused))) {
cmd_resize_floating(current_match, cmd_output, way, direction, floating_con, px);
} else {
if (strcmp(direction, "width") == 0 ||
strcmp(direction, "height") == 0) {
if (!cmd_resize_tiling_width_height(current_match, cmd_output, way, direction, ppt))
return;
HANDLE_EMPTY_MATCH;
owindow *current;
TAILQ_FOREACH(current, &owindows, owindows) {
Con *floating_con;
if ((floating_con = con_inside_floating(current->con))) {
cmd_resize_floating(current_match, cmd_output, way, direction, floating_con, px);
} else {
if (!cmd_resize_tiling_direction(current_match, cmd_output, way, direction, ppt))
return;
if (strcmp(direction, "width") == 0 ||
strcmp(direction, "height") == 0) {
if (!cmd_resize_tiling_width_height(current_match, cmd_output, current->con, way, direction, ppt))
return;
} else {
if (!cmd_resize_tiling_direction(current_match, cmd_output, current->con, way, direction, ppt))
return;
}
}
}
@ -717,11 +806,11 @@ void cmd_resize(I3_CMD, char *way, char *direction, char *resize_px, char *resiz
}
/*
* Implementation of 'border normal|none|1pixel|toggle'.
* Implementation of 'border normal|none|1pixel|toggle|pixel'.
*
*/
void cmd_border(I3_CMD, char *border_style_str) {
DLOG("border style should be changed to %s\n", border_style_str);
void cmd_border(I3_CMD, char *border_style_str, char *border_width ) {
DLOG("border style should be changed to %s with border width %s\n", border_style_str, border_width);
owindow *current;
HANDLE_EMPTY_MATCH;
@ -729,23 +818,39 @@ void cmd_border(I3_CMD, char *border_style_str) {
TAILQ_FOREACH(current, &owindows, owindows) {
DLOG("matching: %p / %s\n", current->con, current->con->name);
int border_style = current->con->border_style;
char *end;
int tmp_border_width = -1;
tmp_border_width = strtol(border_width, &end, 10);
if (end == border_width) {
/* no valid digits found */
tmp_border_width = -1;
}
if (strcmp(border_style_str, "toggle") == 0) {
border_style++;
border_style %= 3;
if (border_style == BS_NORMAL)
tmp_border_width = 2;
else if (border_style == BS_NONE)
tmp_border_width = 0;
else if (border_style == BS_PIXEL)
tmp_border_width = 1;
} else {
if (strcmp(border_style_str, "normal") == 0)
border_style = BS_NORMAL;
else if (strcmp(border_style_str, "none") == 0)
else if (strcmp(border_style_str, "pixel") == 0)
border_style = BS_PIXEL;
else if (strcmp(border_style_str, "1pixel") == 0){
border_style = BS_PIXEL;
tmp_border_width = 1;
} else if (strcmp(border_style_str, "none") == 0)
border_style = BS_NONE;
else if (strcmp(border_style_str, "1pixel") == 0)
border_style = BS_1PIXEL;
else {
ELOG("BUG: called with border_style=%s\n", border_style_str);
ysuccess(false);
return;
}
}
con_set_border_style(current->con, border_style);
con_set_border_style(current->con, border_style, tmp_border_width);
}
cmd_output->needs_tree_render = true;
@ -807,7 +912,7 @@ void cmd_workspace(I3_CMD, char *which) {
}
/*
* Implementation of 'workspace number <number>'
* Implementation of 'workspace number <name>'
*
*/
void cmd_workspace_number(I3_CMD, char *which) {
@ -818,8 +923,8 @@ void cmd_workspace_number(I3_CMD, char *which) {
if (parsed_num == LONG_MIN ||
parsed_num == LONG_MAX ||
parsed_num < 0 ||
*endptr != '\0') {
LOG("Could not parse \"%s\" as a number.\n", which);
endptr == which) {
LOG("Could not parse initial part of \"%s\" as a number.\n", which);
y(map_open);
ystr("success");
y(bool, false);
@ -838,8 +943,6 @@ void cmd_workspace_number(I3_CMD, char *which) {
if (!workspace) {
LOG("There is no workspace with number %ld, creating a new one.\n", parsed_num);
ysuccess(true);
/* terminate the which string after the endposition of the number */
*endptr = '\0';
workspace_show_by_name(which);
cmd_output->needs_tree_render = true;
return;
@ -949,13 +1052,13 @@ void cmd_move_con_to_output(I3_CMD, char *name) {
// TODO: clean this up with commands.spec as soon as we switched away from the lex/yacc command parser
if (strcasecmp(name, "up") == 0)
output = get_output_next(D_UP, current_output);
output = get_output_next(D_UP, current_output, CLOSEST_OUTPUT);
else if (strcasecmp(name, "down") == 0)
output = get_output_next(D_DOWN, current_output);
output = get_output_next(D_DOWN, current_output, CLOSEST_OUTPUT);
else if (strcasecmp(name, "left") == 0)
output = get_output_next(D_LEFT, current_output);
output = get_output_next(D_LEFT, current_output, CLOSEST_OUTPUT);
else if (strcasecmp(name, "right") == 0)
output = get_output_next(D_RIGHT, current_output);
output = get_output_next(D_RIGHT, current_output, CLOSEST_OUTPUT);
else
output = get_output_by_name(name);
@ -1042,8 +1145,12 @@ void cmd_move_workspace_to_output(I3_CMD, char *name) {
Con *content = output_get_content(output->con);
LOG("got output %p with content %p\n", output, content);
Con *previously_visible_ws = TAILQ_FIRST(&(content->nodes_head));
LOG("Previously visible workspace = %p / %s\n", previously_visible_ws, previously_visible_ws->name);
Con *ws = con_get_workspace(current->con);
LOG("should move workspace %p / %s\n", ws, ws->name);
bool workspace_was_visible = workspace_is_visible(ws);
if (con_num_children(ws->parent) == 1) {
LOG("Creating a new workspace to replace \"%s\" (last on its output).\n", ws->name);
@ -1078,9 +1185,9 @@ void cmd_move_workspace_to_output(I3_CMD, char *name) {
/* notify the IPC listeners */
ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"init\"}");
}
DLOG("Detaching\n");
/* detach from the old output and attach to the new output */
bool workspace_was_visible = workspace_is_visible(ws);
Con *old_content = ws->parent;
con_detach(ws);
if (workspace_was_visible) {
@ -1102,6 +1209,22 @@ void cmd_move_workspace_to_output(I3_CMD, char *name) {
/* Focus the moved workspace on the destination output. */
workspace_show(ws);
}
/* NB: We cannot simply work with previously_visible_ws since it might
* have been cleaned up by workspace_show() already, depending on the
* focus order/number of other workspaces on the output.
* Instead, we loop through the available workspaces and only work with
* previously_visible_ws if we still find it. */
TAILQ_FOREACH(ws, &(content->nodes_head), nodes) {
if (ws != previously_visible_ws)
continue;
/* Call the on_remove_child callback of the workspace which previously
* was visible on the destination output. Since it is no longer
* visible, it might need to get cleaned up. */
CALL(previously_visible_ws, on_remove_child);
break;
}
}
cmd_output->needs_tree_render = true;
@ -1132,7 +1255,7 @@ void cmd_split(I3_CMD, char *direction) {
}
/*
* Implementaiton of 'kill [window|client]'.
* Implementation of 'kill [window|client]'.
*
*/
void cmd_kill(I3_CMD, char *kill_mode_str) {
@ -1187,14 +1310,6 @@ void cmd_exec(I3_CMD, char *nosn, char *command) {
*
*/
void cmd_focus_direction(I3_CMD, char *direction) {
if (focused &&
focused->type != CT_WORKSPACE &&
focused->fullscreen_mode != CF_NONE) {
LOG("Cannot change focus while in fullscreen mode.\n");
ysuccess(false);
return;
}
DLOG("direction = *%s*\n", direction);
if (strcmp(direction, "left") == 0)
@ -1221,14 +1336,6 @@ void cmd_focus_direction(I3_CMD, char *direction) {
*
*/
void cmd_focus_window_mode(I3_CMD, char *window_mode) {
if (focused &&
focused->type != CT_WORKSPACE &&
focused->fullscreen_mode != CF_NONE) {
LOG("Cannot change focus while in fullscreen mode.\n");
ysuccess(false);
return;
}
DLOG("window_mode = %s\n", window_mode);
Con *ws = con_get_workspace(focused);
@ -1478,7 +1585,7 @@ void cmd_layout_toggle(I3_CMD, char *toggle_mode) {
}
/*
* Implementaiton of 'exit'.
* Implementation of 'exit'.
*
*/
void cmd_exit(I3_CMD) {
@ -1490,7 +1597,7 @@ void cmd_exit(I3_CMD) {
}
/*
* Implementaiton of 'reload'.
* Implementation of 'reload'.
*
*/
void cmd_reload(I3_CMD) {
@ -1507,7 +1614,7 @@ void cmd_reload(I3_CMD) {
}
/*
* Implementaiton of 'restart'.
* Implementation of 'restart'.
*
*/
void cmd_restart(I3_CMD) {
@ -1519,7 +1626,7 @@ void cmd_restart(I3_CMD) {
}
/*
* Implementaiton of 'open'.
* Implementation of 'open'.
*
*/
void cmd_open(I3_CMD) {
@ -1708,16 +1815,24 @@ void cmd_scratchpad_show(I3_CMD) {
}
/*
* Implementation of 'rename workspace <name> to <name>'
* Implementation of 'rename workspace [<name>] to <name>'
*
*/
void cmd_rename_workspace(I3_CMD, char *old_name, char *new_name) {
LOG("Renaming workspace \"%s\" to \"%s\"\n", old_name, new_name);
if (old_name) {
LOG("Renaming workspace \"%s\" to \"%s\"\n", old_name, new_name);
} else {
LOG("Renaming current workspace to \"%s\"\n", new_name);
}
Con *output, *workspace = NULL;
TAILQ_FOREACH(output, &(croot->nodes_head), nodes)
GREP_FIRST(workspace, output_get_content(output),
!strcasecmp(child->name, old_name));
if (old_name) {
TAILQ_FOREACH(output, &(croot->nodes_head), nodes)
GREP_FIRST(workspace, output_get_content(output),
!strcasecmp(child->name, old_name));
} else {
workspace = con_get_workspace(focused);
}
if (!workspace) {
// TODO: we should include the old workspace name here and use yajl for

View File

@ -46,7 +46,7 @@
* input parser-specs/commands.spec.
******************************************************************************/
#include "GENERATED_enums.h"
#include "GENERATED_command_enums.h"
typedef struct token {
char *name;
@ -63,7 +63,7 @@ typedef struct tokenptr {
int n;
} cmdp_token_ptr;
#include "GENERATED_tokens.h"
#include "GENERATED_command_tokens.h"
/*******************************************************************************
* The (small) stack where identified literals are stored during the parsing
@ -182,7 +182,7 @@ static Match current_match;
static struct CommandResult subcommand_output;
static struct CommandResult command_output;
#include "GENERATED_call.h"
#include "GENERATED_command_call.h"
static void next_state(const cmdp_token *token) {
@ -190,6 +190,7 @@ static void next_state(const cmdp_token *token) {
subcommand_output.json_gen = command_output.json_gen;
subcommand_output.needs_tree_render = false;
GENERATED_call(token->extra.call_identifier, &subcommand_output);
state = subcommand_output.next_state;
/* If any subcommand requires a tree_render(), we need to make the
* whole parser result request a tree_render(). */
if (subcommand_output.needs_tree_render)

335
src/con.c
View File

@ -28,6 +28,20 @@ char *colors[] = {
static void con_on_remove_child(Con *con);
/*
* force parent split containers to be redrawn
*
*/
static void con_force_split_parents_redraw(Con *con) {
Con *parent = con;
while (parent && parent->type != CT_WORKSPACE && parent->type != CT_DOCKAREA) {
if (!con_is_leaf(parent))
FREE(parent->deco_render_params);
parent = parent->parent;
}
}
/*
* Create a new container (and attach it to the given parent, if not NULL).
* This function initializes the data structures and creates the appropriate
@ -41,6 +55,7 @@ Con *con_new(Con *parent, i3Window *window) {
new->type = CT_CON;
new->window = window;
new->border_style = config.default_border;
new->current_border_width = -1;
static int cnt = 0;
DLOG("opening window %d\n", cnt);
@ -162,6 +177,7 @@ add_to_focus_head:
* This way, we have the option to insert Cons without having
* to focus them. */
TAILQ_INSERT_TAIL(focus_head, con, focused);
con_force_split_parents_redraw(con);
}
/*
@ -169,6 +185,7 @@ add_to_focus_head:
*
*/
void con_detach(Con *con) {
con_force_split_parents_redraw(con);
if (con->type == CT_FLOATING_CON) {
TAILQ_REMOVE(&(con->parent->floating_head), con, floating_windows);
TAILQ_REMOVE(&(con->parent->focus_head), con, focused);
@ -195,8 +212,14 @@ void con_focus(Con *con) {
con_focus(con->parent);
focused = con;
if (con->urgent) {
/* We can't blindly reset non-leaf containers since they might have
* other urgent children. Therefore we only reset leafs and propagate
* the changes upwards via con_update_parents_urgency() which does proper
* checks before resetting the urgency.
*/
if (con->urgent && con_is_leaf(con)) {
con->urgent = false;
con_update_parents_urgency(con);
workspace_update_urgent_flag(con_get_workspace(con));
}
}
@ -209,6 +232,32 @@ bool con_is_leaf(Con *con) {
return TAILQ_EMPTY(&(con->nodes_head));
}
/**
* Returns true if this node has regular or floating children.
*
*/
bool con_has_children(Con *con) {
return (!con_is_leaf(con) || !TAILQ_EMPTY(&(con->floating_head)));
}
/*
* Returns true if a container should be considered split.
*
*/
bool con_is_split(Con *con) {
if (con_is_leaf(con))
return false;
switch (con->layout) {
case L_DOCKAREA:
case L_OUTPUT:
return false;
default:
return true;
}
}
/*
* Returns true if this node accepts a window (if the node swallows windows,
* it might already have swallowed enough and cannot hold any more).
@ -219,7 +268,7 @@ bool con_accepts_window(Con *con) {
if (con->type == CT_WORKSPACE)
return false;
if (con->split) {
if (con_is_split(con)) {
DLOG("container %p does not accept windows, it is a split container.\n", con);
return false;
}
@ -337,6 +386,14 @@ Con *con_get_fullscreen_con(Con *con, int fullscreen_mode) {
return NULL;
}
/**
* Returns true if the container is internal, such as __i3_scratch
*
*/
bool con_is_internal(Con *con) {
return (con->name[0] == '_' && con->name[1] == '_');
}
/*
* Returns true if the node is floating.
*
@ -576,11 +633,6 @@ void con_toggle_fullscreen(Con *con, int fullscreen_mode) {
*
*/
void con_move_to_workspace(Con *con, Con *workspace, bool fix_coordinates, bool dont_warp) {
if (con->type == CT_WORKSPACE) {
DLOG("Moving workspaces is not yet implemented.\n");
return;
}
/* Prevent moving if this would violate the fullscreen focus restrictions. */
if (!con_fullscreen_permits_focusing(workspace)) {
LOG("Cannot move out of a fullscreen container");
@ -598,6 +650,25 @@ void con_move_to_workspace(Con *con, Con *workspace, bool fix_coordinates, bool
return;
}
if (con->type == CT_WORKSPACE) {
/* Re-parent all of the old workspace's floating windows. */
Con *child;
while (!TAILQ_EMPTY(&(source_ws->floating_head))) {
child = TAILQ_FIRST(&(source_ws->floating_head));
con_move_to_workspace(child, workspace, true, true);
}
/* If there are no non-floating children, ignore the workspace. */
if (con_is_leaf(con))
return;
con = workspace_encapsulate(con);
if (con == NULL) {
ELOG("Workspace failed to move its contents into a container!\n");
return;
}
}
/* Save the current workspace. So we can call workspace_show() by the end
* of this function. */
Con *current_ws = con_get_workspace(focused);
@ -655,6 +726,14 @@ void con_move_to_workspace(Con *con, Con *workspace, bool fix_coordinates, bool
}
}
/* If moving a fullscreen container and the destination already has a
* fullscreen window on it, un-fullscreen the target's fullscreen con. */
Con *fullscreen = con_get_fullscreen_con(workspace, CF_OUTPUT);
if (con->fullscreen_mode != CF_NONE && fullscreen != NULL) {
con_toggle_fullscreen(fullscreen, CF_OUTPUT);
fullscreen = NULL;
}
DLOG("Re-attaching container to %p / %s\n", next, next->name);
/* 5: re-attach the con to the parent of this focused container */
Con *parent = con->parent;
@ -671,14 +750,16 @@ void con_move_to_workspace(Con *con, Con *workspace, bool fix_coordinates, bool
* invisible.
* We dont focus the con for i3 pseudo workspaces like __i3_scratch and
* we dont focus when there is a fullscreen con on that workspace. */
if ((workspace->name[0] != '_' || workspace->name[1] != '_') &&
con_get_fullscreen_con(workspace, CF_OUTPUT) == NULL) {
/* We need to save focus on workspace level and restore it afterwards.
* Otherwise, we might focus a different workspace without actually
* switching workspaces. */
if (!con_is_internal(workspace) && !fullscreen) {
/* We need to save the focused workspace on the output in case the
* new workspace is hidden and it's necessary to immediately switch
* back to the originally-focused workspace. */
Con *old_focus = TAILQ_FIRST(&(output_get_content(dest_output)->focus_head));
con_focus(con_descend_focused(con));
con_focus(old_focus);
/* Restore focus if the output's focused workspace has changed. */
if (con_get_workspace(focused) != old_focus)
con_focus(old_focus);
}
/* 8: when moving to a visible workspace on a different output, we keep the
@ -686,8 +767,7 @@ void con_move_to_workspace(Con *con, Con *workspace, bool fix_coordinates, bool
* dont want to focus invisible workspaces */
if (source_output != dest_output &&
workspace_is_visible(workspace) &&
workspace->name[0] != '_' &&
workspace->name[1] != '_') {
!con_is_internal(workspace)) {
DLOG("Moved to a different output, focusing target\n");
} else {
/* Descend focus stack in case focus_next is a workspace which can
@ -701,6 +781,38 @@ void con_move_to_workspace(Con *con, Con *workspace, bool fix_coordinates, bool
con_focus(con_descend_focused(focus_next));
}
/* If anything within the container is associated with a startup sequence,
* delete it so child windows won't be created on the old workspace. */
struct Startup_Sequence *sequence;
xcb_get_property_cookie_t cookie;
xcb_get_property_reply_t *startup_id_reply;
if (!con_is_leaf(con)) {
Con *child;
TAILQ_FOREACH(child, &(con->nodes_head), nodes) {
if (!child->window)
continue;
cookie = xcb_get_property(conn, false, child->window->id,
A__NET_STARTUP_ID, XCB_GET_PROPERTY_TYPE_ANY, 0, 512);
startup_id_reply = xcb_get_property_reply(conn, cookie, NULL);
sequence = startup_sequence_get(child->window, startup_id_reply, true);
if (sequence != NULL)
startup_sequence_delete(sequence);
}
}
if (con->window) {
cookie = xcb_get_property(conn, false, con->window->id,
A__NET_STARTUP_ID, XCB_GET_PROPERTY_TYPE_ANY, 0, 512);
startup_id_reply = xcb_get_property_reply(conn, cookie, NULL);
sequence = startup_sequence_get(con->window, startup_id_reply, true);
if (sequence != NULL)
startup_sequence_delete(sequence);
}
CALL(parent, on_remove_child);
}
@ -893,6 +1005,7 @@ Con *con_descend_tiling_focused(Con *con) {
*/
Con *con_descend_direction(Con *con, direction_t direction) {
Con *most = NULL;
Con *current;
int orientation = con_orientation(con);
DLOG("con_descend_direction(%p, orientation %d, direction %d)\n", con, orientation, direction);
if (direction == D_LEFT || direction == D_RIGHT) {
@ -906,7 +1019,12 @@ Con *con_descend_direction(Con *con, direction_t direction) {
/* Wrong orientation. We use the last focused con. Within that con,
* we recurse to chose the left/right con or at least the last
* focused one. */
most = TAILQ_FIRST(&(con->focus_head));
TAILQ_FOREACH(current, &(con->focus_head), focused) {
if (current->type != CT_FLOATING_CON) {
most = current;
break;
}
}
} else {
/* If the con has no orientation set, its not a split container
* but a container with a client window, so stop recursing */
@ -925,7 +1043,12 @@ Con *con_descend_direction(Con *con, direction_t direction) {
/* Wrong orientation. We use the last focused con. Within that con,
* we recurse to chose the top/bottom con or at least the last
* focused one. */
most = TAILQ_FIRST(&(con->focus_head));
TAILQ_FOREACH(current, &(con->focus_head), focused) {
if (current->type != CT_FLOATING_CON) {
most = current;
break;
}
}
} else {
/* If the con has no orientation set, its not a split container
* but a container with a client window, so stop recursing */
@ -946,52 +1069,38 @@ Con *con_descend_direction(Con *con, direction_t direction) {
*/
Rect con_border_style_rect(Con *con) {
adjacent_t borders_to_hide = ADJ_NONE;
int border_width = con->current_border_width;
DLOG("The border width for con is set to: %d\n", con->current_border_width);
Rect result;
if (con->current_border_width < 0)
border_width = config.default_border_width;
DLOG("Effective border width is set to: %d\n", border_width);
/* Shortcut to avoid calling con_adjacent_borders() on dock containers. */
int border_style = con_border_style(con);
if (border_style == BS_NONE)
return (Rect){ 0, 0, 0, 0 };
borders_to_hide = con_adjacent_borders(con) & config.hide_edge_borders;
switch (border_style) {
case BS_NORMAL:
result = (Rect){2, 0, -(2 * 2), -2};
if (borders_to_hide & ADJ_LEFT_SCREEN_EDGE) {
result.x -= 2;
result.width += 2;
}
if (borders_to_hide & ADJ_RIGHT_SCREEN_EDGE) {
result.width += 2;
}
/* With normal borders we never hide the upper border */
if (borders_to_hide & ADJ_LOWER_SCREEN_EDGE) {
result.height += 2;
}
return result;
case BS_1PIXEL:
result = (Rect){1, 1, -2, -2};
if (borders_to_hide & ADJ_LEFT_SCREEN_EDGE) {
result.x -= 1;
result.width += 1;
}
if (borders_to_hide & ADJ_RIGHT_SCREEN_EDGE) {
result.width += 1;
}
if (borders_to_hide & ADJ_UPPER_SCREEN_EDGE) {
result.y -= 1;
result.height += 1;
}
if (borders_to_hide & ADJ_LOWER_SCREEN_EDGE) {
result.height += 1;
}
return result;
case BS_NONE:
return (Rect){0, 0, 0, 0};
default:
assert(false);
if (border_style == BS_NORMAL) {
result = (Rect){border_width, 0 , -(2 * border_width), -(border_width)};
} else {
result = (Rect){border_width, border_width, -(2 * border_width), -(2 * border_width)};
}
if (borders_to_hide & ADJ_LEFT_SCREEN_EDGE) {
result.x -= border_width;
result.width += border_width;
}
if (borders_to_hide & ADJ_RIGHT_SCREEN_EDGE) {
result.width += border_width;
}
if (borders_to_hide & ADJ_UPPER_SCREEN_EDGE && (border_style != BS_NORMAL)) {
result.y -= border_width;
result.height += border_width;
}
if (borders_to_hide & ADJ_LOWER_SCREEN_EDGE) {
result.height += border_width;
}
return result;
}
/*
@ -1046,10 +1155,11 @@ int con_border_style(Con *con) {
* floating window.
*
*/
void con_set_border_style(Con *con, int border_style) {
void con_set_border_style(Con *con, int border_style, int border_width) {
/* Handle the simple case: non-floating containerns */
if (!con_is_floating(con)) {
con->border_style = border_style;
con->current_border_width = border_width;
return;
}
@ -1068,6 +1178,7 @@ void con_set_border_style(Con *con, int border_style) {
/* Change the border style, get new border/decoration values. */
con->border_style = border_style;
con->current_border_width = border_width;
bsr = con_border_style_rect(con);
int deco_height =
(con->border_style == BS_NORMAL ? config.font.height + 5 : 0);
@ -1125,7 +1236,6 @@ void con_set_layout(Con *con, int layout) {
* split. */
new->layout = layout;
new->last_split_layout = con->last_split_layout;
new->split = true;
Con *old_focused = TAILQ_FIRST(&(con->focus_head));
if (old_focused == TAILQ_END(&(con->focus_head)))
@ -1149,6 +1259,7 @@ void con_set_layout(Con *con, int layout) {
tree_flatten(croot);
}
con_force_split_parents_redraw(con);
return;
}
@ -1169,6 +1280,7 @@ void con_set_layout(Con *con, int layout) {
} else {
con->layout = layout;
}
con_force_split_parents_redraw(con);
}
/*
@ -1248,6 +1360,8 @@ static void con_on_remove_child(Con *con) {
return;
}
con_force_split_parents_redraw(con);
/* TODO: check if this container would swallow any other client and
* dont close it automatically. */
int children = con_num_children(con);
@ -1294,7 +1408,7 @@ Rect con_minimum_size(Con *con) {
/* For horizontal/vertical split containers we sum up the width (h-split)
* or height (v-split) and use the maximum of the height (h-split) or width
* (v-split) as minimum size. */
if (con->split) {
if (con_is_split(con)) {
uint32_t width = 0, height = 0;
Con *child;
TAILQ_FOREACH(child, &(con->nodes_head), nodes) {
@ -1312,7 +1426,7 @@ Rect con_minimum_size(Con *con) {
}
ELOG("Unhandled case, type = %d, layout = %d, split = %d\n",
con->type, con->layout, con->split);
con->type, con->layout, con_is_split(con));
assert(false);
}
@ -1378,3 +1492,104 @@ bool con_fullscreen_permits_focusing(Con *con) {
/* Focusing con would hide it behind a fullscreen window, disallow it. */
return false;
}
/*
*
* Checks if the given container has an urgent child.
*
*/
bool con_has_urgent_child(Con *con) {
Con *child;
if (con_is_leaf(con))
return con->urgent;
/* We are not interested in floating windows since they can only be
* attached to a workspace → nodes_head instead of focus_head */
TAILQ_FOREACH(child, &(con->nodes_head), nodes) {
if (con_has_urgent_child(child))
return true;
}
return false;
}
/*
* Make all parent containers urgent if con is urgent or clear the urgent flag
* of all parent containers if there are no more urgent children left.
*
*/
void con_update_parents_urgency(Con *con) {
Con *parent = con->parent;
bool new_urgency_value = con->urgent;
while (parent && parent->type != CT_WORKSPACE && parent->type != CT_DOCKAREA) {
if (new_urgency_value) {
parent->urgent = true;
} else {
/* We can only reset the urgency when the parent
* has no other urgent children */
if (!con_has_urgent_child(parent))
parent->urgent = false;
}
parent = parent->parent;
}
}
/*
* Create a string representing the subtree under con.
*
*/
char *con_get_tree_representation(Con *con) {
/* this code works as follows:
* 1) create a string with the layout type (D/V/H/T/S) and an opening bracket
* 2) append the tree representation of the children to the string
* 3) add closing bracket
*
* The recursion ends when we hit a leaf, in which case we return the
* class_instance of the contained window.
*/
/* end of recursion */
if (con_is_leaf(con)) {
if (!con->window)
return sstrdup("nowin");
if (!con->window->class_instance)
return sstrdup("noinstance");
return sstrdup(con->window->class_instance);
}
char *buf;
/* 1) add the Layout type to buf */
if (con->layout == L_DEFAULT)
buf = sstrdup("D[");
else if (con->layout == L_SPLITV)
buf = sstrdup("V[");
else if (con->layout == L_SPLITH)
buf = sstrdup("H[");
else if (con->layout == L_TABBED)
buf = sstrdup("T[");
else if (con->layout == L_STACKED)
buf = sstrdup("S[");
/* 2) append representation of children */
Con *child;
TAILQ_FOREACH(child, &(con->nodes_head), nodes) {
char *child_txt = con_get_tree_representation(child);
char *tmp_buf;
sasprintf(&tmp_buf, "%s%s%s", buf,
(TAILQ_FIRST(&(con->nodes_head)) == child ? "" : " "), child_txt);
free(buf);
buf = tmp_buf;
}
/* 3) close the brackets */
char *complete_buf;
sasprintf(&complete_buf, "%s]", buf);
free(buf);
return complete_buf;
}

View File

@ -46,6 +46,7 @@ static void grab_keycode_for_binding(xcb_connection_t *conn, Binding *bind, uint
}
GRAB_KEY(mods);
GRAB_KEY(mods | xcb_numlock_mask);
GRAB_KEY(mods | XCB_MOD_MASK_LOCK);
GRAB_KEY(mods | xcb_numlock_mask | XCB_MOD_MASK_LOCK);
}
@ -194,6 +195,13 @@ void switch_mode(const char *new_mode) {
bindings = mode->bindings;
translate_keysyms();
grab_all_keys(conn, false);
char *event_msg;
sasprintf(&event_msg, "{\"change\":\"%s\"}", mode->name);
ipc_send_event("mode", I3_IPC_EVENT_MODE, event_msg);
FREE(event_msg);
return;
}
@ -410,9 +418,14 @@ void load_configuration(xcb_connection_t *conn, const char *override_configpath,
config.default_border = BS_NORMAL;
config.default_floating_border = BS_NORMAL;
config.default_border_width = 2;
/* Set default_orientation to NO_ORIENTATION for auto orientation. */
config.default_orientation = NO_ORIENTATION;
/* Set default urgency reset delay to 500ms */
if (config.workspace_urgency_timer == 0)
config.workspace_urgency_timer = 0.5;
parse_configuration(override_configpath);
if (reload) {

564
src/config_directives.c Normal file
View File

@ -0,0 +1,564 @@
#undef I3__FILE__
#define I3__FILE__ "config_directives.c"
/*
* vim:ts=4:sw=4:expandtab
*
* i3 - an improved dynamic tiling window manager
* © 2009-2012 Michael Stapelberg and contributors (see also: LICENSE)
*
* config_directives.c: all config storing functions (see config_parser.c)
*
*/
#include <float.h>
#include <stdarg.h>
#include "all.h"
// Macros to make the YAJL API a bit easier to use.
#define y(x, ...) yajl_gen_ ## x (cmd_output->json_gen, ##__VA_ARGS__)
#define ystr(str) yajl_gen_string(cmd_output->json_gen, (unsigned char*)str, strlen(str))
#define ysuccess(success) do { \
y(map_open); \
ystr("success"); \
y(bool, success); \
y(map_close); \
} while (0)
/*******************************************************************************
* Criteria functions.
******************************************************************************/
static int criteria_next_state;
/*
* Initializes the specified 'Match' data structure and the initial state of
* commands.c for matching target windows of a command.
*
*/
CFGFUN(criteria_init, int _state) {
criteria_next_state = _state;
DLOG("Initializing criteria, current_match = %p, state = %d\n", current_match, _state);
match_init(current_match);
}
CFGFUN(criteria_pop_state) {
result->next_state = criteria_next_state;
}
/*
* Interprets a ctype=cvalue pair and adds it to the current match
* specification.
*
*/
CFGFUN(criteria_add, const char *ctype, const char *cvalue) {
DLOG("ctype=*%s*, cvalue=*%s*\n", ctype, cvalue);
if (strcmp(ctype, "class") == 0) {
current_match->class = regex_new(cvalue);
return;
}
if (strcmp(ctype, "instance") == 0) {
current_match->instance = regex_new(cvalue);
return;
}
if (strcmp(ctype, "window_role") == 0) {
current_match->role = regex_new(cvalue);
return;
}
if (strcmp(ctype, "con_id") == 0) {
char *end;
long parsed = strtol(cvalue, &end, 10);
if (parsed == LONG_MIN ||
parsed == LONG_MAX ||
parsed < 0 ||
(end && *end != '\0')) {
ELOG("Could not parse con id \"%s\"\n", cvalue);
} else {
current_match->con_id = (Con*)parsed;
printf("id as int = %p\n", current_match->con_id);
}
return;
}
if (strcmp(ctype, "id") == 0) {
char *end;
long parsed = strtol(cvalue, &end, 10);
if (parsed == LONG_MIN ||
parsed == LONG_MAX ||
parsed < 0 ||
(end && *end != '\0')) {
ELOG("Could not parse window id \"%s\"\n", cvalue);
} else {
current_match->id = parsed;
printf("window id as int = %d\n", current_match->id);
}
return;
}
if (strcmp(ctype, "con_mark") == 0) {
current_match->mark = regex_new(cvalue);
return;
}
if (strcmp(ctype, "title") == 0) {
current_match->title = regex_new(cvalue);
return;
}
if (strcmp(ctype, "urgent") == 0) {
if (strcasecmp(cvalue, "latest") == 0 ||
strcasecmp(cvalue, "newest") == 0 ||
strcasecmp(cvalue, "recent") == 0 ||
strcasecmp(cvalue, "last") == 0) {
current_match->urgent = U_LATEST;
} else if (strcasecmp(cvalue, "oldest") == 0 ||
strcasecmp(cvalue, "first") == 0) {
current_match->urgent = U_OLDEST;
}
return;
}
ELOG("Unknown criterion: %s\n", ctype);
}
/* TODO: refactor the above criteria code into a single file (with src/commands.c). */
/*******************************************************************************
* Utility functions
******************************************************************************/
static bool eval_boolstr(const char *str) {
return (strcasecmp(str, "1") == 0 ||
strcasecmp(str, "yes") == 0 ||
strcasecmp(str, "true") == 0 ||
strcasecmp(str, "on") == 0 ||
strcasecmp(str, "enable") == 0 ||
strcasecmp(str, "active") == 0);
}
static uint32_t modifiers_from_str(const char *str) {
/* It might be better to use strtok() here, but the simpler strstr() should
* do for now. */
uint32_t result = 0;
if (str == NULL)
return result;
if (strstr(str, "Mod1") != NULL)
result |= BIND_MOD1;
if (strstr(str, "Mod2") != NULL)
result |= BIND_MOD2;
if (strstr(str, "Mod3") != NULL)
result |= BIND_MOD3;
if (strstr(str, "Mod4") != NULL)
result |= BIND_MOD4;
if (strstr(str, "Mod5") != NULL)
result |= BIND_MOD5;
if (strstr(str, "Control") != NULL ||
strstr(str, "Ctrl") != NULL)
result |= BIND_CONTROL;
if (strstr(str, "Shift") != NULL)
result |= BIND_SHIFT;
if (strstr(str, "Mode_switch") != NULL)
result |= BIND_MODE_SWITCH;
return result;
}
static char *font_pattern;
CFGFUN(font, const char *font) {
config.font = load_font(font, true);
set_font(&config.font);
/* Save the font pattern for using it as bar font later on */
FREE(font_pattern);
font_pattern = sstrdup(font);
}
// TODO: refactor with mode_binding
CFGFUN(binding, const char *bindtype, const char *modifiers, const char *key, const char *release, const char *command) {
Binding *new_binding = scalloc(sizeof(Binding));
DLOG("bindtype %s, modifiers %s, key %s, release %s\n", bindtype, modifiers, key, release);
new_binding->release = (release != NULL ? B_UPON_KEYRELEASE : B_UPON_KEYPRESS);
if (strcmp(bindtype, "bindsym") == 0) {
new_binding->symbol = sstrdup(key);
} else {
// TODO: strtol with proper error handling
new_binding->keycode = atoi(key);
if (new_binding->keycode == 0) {
ELOG("Could not parse \"%s\" as a keycode, ignoring this binding.\n", key);
return;
}
}
new_binding->mods = modifiers_from_str(modifiers);
new_binding->command = sstrdup(command);
TAILQ_INSERT_TAIL(bindings, new_binding, bindings);
}
/*******************************************************************************
* Mode handling
******************************************************************************/
static struct bindings_head *current_bindings;
CFGFUN(mode_binding, const char *bindtype, const char *modifiers, const char *key, const char *release, const char *command) {
Binding *new_binding = scalloc(sizeof(Binding));
DLOG("bindtype %s, modifiers %s, key %s, release %s\n", bindtype, modifiers, key, release);
new_binding->release = (release != NULL ? B_UPON_KEYRELEASE : B_UPON_KEYPRESS);
if (strcmp(bindtype, "bindsym") == 0) {
new_binding->symbol = sstrdup(key);
} else {
// TODO: strtol with proper error handling
new_binding->keycode = atoi(key);
if (new_binding->keycode == 0) {
ELOG("Could not parse \"%s\" as a keycode, ignoring this binding.\n", key);
return;
}
}
new_binding->mods = modifiers_from_str(modifiers);
new_binding->command = sstrdup(command);
TAILQ_INSERT_TAIL(current_bindings, new_binding, bindings);
}
CFGFUN(enter_mode, const char *modename) {
if (strcasecmp(modename, "default") == 0) {
ELOG("You cannot use the name \"default\" for your mode\n");
exit(1);
}
DLOG("\t now in mode %s\n", modename);
struct Mode *mode = scalloc(sizeof(struct Mode));
mode->name = sstrdup(modename);
mode->bindings = scalloc(sizeof(struct bindings_head));
TAILQ_INIT(mode->bindings);
current_bindings = mode->bindings;
SLIST_INSERT_HEAD(&modes, mode, modes);
}
CFGFUN(exec, const char *exectype, const char *no_startup_id, const char *command) {
struct Autostart *new = smalloc(sizeof(struct Autostart));
new->command = sstrdup(command);
new->no_startup_id = (no_startup_id != NULL);
if (strcmp(exectype, "exec") == 0) {
TAILQ_INSERT_TAIL(&autostarts, new, autostarts);
} else {
TAILQ_INSERT_TAIL(&autostarts_always, new, autostarts_always);
}
}
CFGFUN(for_window, const char *command) {
if (match_is_empty(current_match)) {
ELOG("Match is empty, ignoring this for_window statement\n");
return;
}
DLOG("\t should execute command %s for the criteria mentioned above\n", command);
Assignment *assignment = scalloc(sizeof(Assignment));
assignment->type = A_COMMAND;
match_copy(&(assignment->match), current_match);
assignment->dest.command = sstrdup(command);
TAILQ_INSERT_TAIL(&assignments, assignment, assignments);
}
CFGFUN(floating_minimum_size, const long width, const long height) {
config.floating_minimum_width = width;
config.floating_minimum_height = height;
}
CFGFUN(floating_maximum_size, const long width, const long height) {
config.floating_maximum_width = width;
config.floating_maximum_height = height;
}
CFGFUN(floating_modifier, const char *modifiers) {
config.floating_modifier = modifiers_from_str(modifiers);
}
CFGFUN(default_orientation, const char *orientation) {
if (strcmp(orientation, "horizontal") == 0)
config.default_orientation = HORIZ;
else if (strcmp(orientation, "vertical") == 0)
config.default_orientation = VERT;
else config.default_orientation = NO_ORIENTATION;
}
CFGFUN(workspace_layout, const char *layout) {
if (strcmp(layout, "default") == 0)
config.default_layout = L_DEFAULT;
else if (strcmp(layout, "stacking") == 0 ||
strcmp(layout, "stacked") == 0)
config.default_layout = L_STACKED;
else config.default_layout = L_TABBED;
}
CFGFUN(new_window, const char *windowtype, const char *border, const long width) {
// FIXME: when using new_float *and* new_window with different border
// types, this breaks because default_border_width gets overwritten.
int border_style;
int border_width;
if (strcmp(border, "1pixel") == 0) {
border_style = BS_PIXEL;
border_width = 1;
} else if (strcmp(border, "none") == 0) {
border_style = BS_NONE;
border_width = 0;
} else if (strcmp(border, "pixel") == 0) {
border_style = BS_PIXEL;
border_width = width;
} else {
border_style = BS_NORMAL;
border_width = width;
}
if (strcmp(windowtype, "new_window") == 0) {
config.default_border = border_style;
} else {
config.default_floating_border = border_style;
}
config.default_border_width = border_width;
}
CFGFUN(hide_edge_borders, const char *borders) {
if (strcmp(borders, "vertical") == 0)
config.hide_edge_borders = ADJ_LEFT_SCREEN_EDGE | ADJ_RIGHT_SCREEN_EDGE;
else if (strcmp(borders, "horizontal") == 0)
config.hide_edge_borders = ADJ_UPPER_SCREEN_EDGE | ADJ_LOWER_SCREEN_EDGE;
else if (strcmp(borders, "both") == 0)
config.hide_edge_borders = ADJ_LEFT_SCREEN_EDGE | ADJ_RIGHT_SCREEN_EDGE | ADJ_UPPER_SCREEN_EDGE | ADJ_LOWER_SCREEN_EDGE;
else if (strcmp(borders, "none") == 0)
config.hide_edge_borders = ADJ_NONE;
else if (eval_boolstr(borders))
config.hide_edge_borders = ADJ_LEFT_SCREEN_EDGE | ADJ_RIGHT_SCREEN_EDGE;
else config.hide_edge_borders = ADJ_NONE;
}
CFGFUN(focus_follows_mouse, const char *value) {
config.disable_focus_follows_mouse = !eval_boolstr(value);
}
CFGFUN(force_xinerama, const char *value) {
config.force_xinerama = eval_boolstr(value);
}
CFGFUN(force_focus_wrapping, const char *value) {
config.force_focus_wrapping = eval_boolstr(value);
}
CFGFUN(workspace_back_and_forth, const char *value) {
config.workspace_auto_back_and_forth = eval_boolstr(value);
}
CFGFUN(fake_outputs, const char *outputs) {
config.fake_outputs = sstrdup(outputs);
}
CFGFUN(force_display_urgency_hint, const long duration_ms) {
config.workspace_urgency_timer = duration_ms / 1000.0;
}
CFGFUN(workspace, const char *workspace, const char *output) {
DLOG("Assigning workspace \"%s\" to output \"%s\"\n", workspace, output);
/* Check for earlier assignments of the same workspace so that we
* dont have assignments of a single workspace to different
* outputs */
struct Workspace_Assignment *assignment;
bool duplicate = false;
TAILQ_FOREACH(assignment, &ws_assignments, ws_assignments) {
if (strcasecmp(assignment->name, workspace) == 0) {
ELOG("You have a duplicate workspace assignment for workspace \"%s\"\n",
workspace);
assignment->output = sstrdup(output);
duplicate = true;
}
}
if (!duplicate) {
assignment = scalloc(sizeof(struct Workspace_Assignment));
assignment->name = sstrdup(workspace);
assignment->output = sstrdup(output);
TAILQ_INSERT_TAIL(&ws_assignments, assignment, ws_assignments);
}
}
CFGFUN(ipc_socket, const char *path) {
config.ipc_socket_path = sstrdup(path);
}
CFGFUN(restart_state, const char *path) {
config.restart_state_path = sstrdup(path);
}
CFGFUN(popup_during_fullscreen, const char *value) {
config.popup_during_fullscreen =
(strcmp(value, "ignore") == 0 ? PDF_IGNORE : PDF_LEAVE_FULLSCREEN);
}
CFGFUN(color_single, const char *colorclass, const char *color) {
/* used for client.background only currently */
config.client.background = get_colorpixel(color);
}
CFGFUN(color, const char *colorclass, const char *border, const char *background, const char *text, const char *indicator) {
#define APPLY_COLORS(classname) \
do { \
if (strcmp(colorclass, "client." #classname) == 0) { \
config.client.classname.border = get_colorpixel(border); \
config.client.classname.background = get_colorpixel(background); \
config.client.classname.text = get_colorpixel(text); \
if (indicator != NULL) { \
config.client. classname .indicator = get_colorpixel(indicator); \
} \
} \
} while (0)
APPLY_COLORS(focused_inactive);
APPLY_COLORS(focused);
APPLY_COLORS(unfocused);
APPLY_COLORS(urgent);
#undef APPLY_COLORS
}
CFGFUN(assign, const char *workspace) {
if (match_is_empty(current_match)) {
ELOG("Match is empty, ignoring this assignment\n");
return;
}
DLOG("new assignment, using above criteria, to workspace %s\n", workspace);
Assignment *assignment = scalloc(sizeof(Assignment));
match_copy(&(assignment->match), current_match);
assignment->type = A_TO_WORKSPACE;
assignment->dest.workspace = sstrdup(workspace);
TAILQ_INSERT_TAIL(&assignments, assignment, assignments);
}
/*******************************************************************************
* Bar configuration (i3bar)
******************************************************************************/
static Barconfig current_bar;
CFGFUN(bar_font, const char *font) {
FREE(current_bar.font);
current_bar.font = sstrdup(font);
}
CFGFUN(bar_mode, const char *mode) {
current_bar.mode = (strcmp(mode, "hide") == 0 ? M_HIDE : M_DOCK);
}
CFGFUN(bar_output, const char *output) {
int new_outputs = current_bar.num_outputs + 1;
current_bar.outputs = srealloc(current_bar.outputs, sizeof(char*) * new_outputs);
current_bar.outputs[current_bar.num_outputs] = sstrdup(output);
current_bar.num_outputs = new_outputs;
}
CFGFUN(bar_verbose, const char *verbose) {
current_bar.verbose = eval_boolstr(verbose);
}
CFGFUN(bar_modifier, const char *modifier) {
if (strcmp(modifier, "Mod1") == 0)
current_bar.modifier = M_MOD1;
else if (strcmp(modifier, "Mod2") == 0)
current_bar.modifier = M_MOD2;
else if (strcmp(modifier, "Mod3") == 0)
current_bar.modifier = M_MOD3;
else if (strcmp(modifier, "Mod4") == 0)
current_bar.modifier = M_MOD4;
else if (strcmp(modifier, "Mod5") == 0)
current_bar.modifier = M_MOD5;
else if (strcmp(modifier, "Control") == 0 ||
strcmp(modifier, "Ctrl") == 0)
current_bar.modifier = M_CONTROL;
else if (strcmp(modifier, "Shift") == 0)
current_bar.modifier = M_SHIFT;
}
CFGFUN(bar_position, const char *position) {
current_bar.position = (strcmp(position, "top") == 0 ? P_TOP : P_BOTTOM);
}
CFGFUN(bar_i3bar_command, const char *i3bar_command) {
FREE(current_bar.i3bar_command);
current_bar.i3bar_command = sstrdup(i3bar_command);
}
CFGFUN(bar_color, const char *colorclass, const char *border, const char *background, const char *text) {
#define APPLY_COLORS(classname) \
do { \
if (strcmp(colorclass, #classname) == 0) { \
if (text != NULL) { \
/* New syntax: border, background, text */ \
current_bar.colors. classname ## _border = sstrdup(border); \
current_bar.colors. classname ## _bg = sstrdup(background); \
current_bar.colors. classname ## _text = sstrdup(text); \
} else { \
/* Old syntax: text, background */ \
current_bar.colors. classname ## _bg = sstrdup(background); \
current_bar.colors. classname ## _text = sstrdup(border); \
} \
} \
} while (0)
APPLY_COLORS(focused_workspace);
APPLY_COLORS(active_workspace);
APPLY_COLORS(inactive_workspace);
APPLY_COLORS(urgent_workspace);
#undef APPLY_COLORS
}
CFGFUN(bar_socket_path, const char *socket_path) {
FREE(current_bar.socket_path);
current_bar.socket_path = sstrdup(socket_path);
}
CFGFUN(bar_tray_output, const char *output) {
FREE(current_bar.tray_output);
current_bar.tray_output = sstrdup(output);
}
CFGFUN(bar_color_single, const char *colorclass, const char *color) {
if (strcmp(colorclass, "background") == 0)
current_bar.colors.background = sstrdup(color);
else current_bar.colors.statusline = sstrdup(color);
}
CFGFUN(bar_status_command, const char *command) {
FREE(current_bar.status_command);
current_bar.status_command = sstrdup(command);
}
CFGFUN(bar_workspace_buttons, const char *value) {
current_bar.hide_workspace_buttons = !eval_boolstr(value);
}
CFGFUN(bar_finish) {
DLOG("\t new bar configuration finished, saving.\n");
/* Generate a unique ID for this bar */
current_bar.id = sstrdup("bar-XXXXXX");
/* This works similar to mktemp in that it replaces the last six X with
* random letters, but without the restriction that the given buffer
* has to contain a valid path name. */
char *x = current_bar.id + strlen("bar-");
while (*x != '\0') {
*(x++) = (rand() % 26) + 'a';
}
/* If no font was explicitly set, we use the i3 font as default */
if (!current_bar.font && font_pattern)
current_bar.font = sstrdup(font_pattern);
/* Copy the current (static) structure into a dynamically allocated
* one, then cleanup our static one. */
Barconfig *bar_config = scalloc(sizeof(Barconfig));
memcpy(bar_config, &current_bar, sizeof(Barconfig));
TAILQ_INSERT_TAIL(&barconfigs, bar_config, configs);
memset(&current_bar, '\0', sizeof(Barconfig));
}

652
src/config_parser.c Normal file
View File

@ -0,0 +1,652 @@
#undef I3__FILE__
#define I3__FILE__ "config_parser.c"
/*
* vim:ts=4:sw=4:expandtab
*
* i3 - an improved dynamic tiling window manager
* © 2009-2012 Michael Stapelberg and contributors (see also: LICENSE)
*
* config_parser.c: hand-written parser to parse configuration directives.
*
* See also src/commands_parser.c for rationale on why we use a custom parser.
*
* This parser works VERY MUCH like src/commands_parser.c, so read that first.
* The differences are:
*
* 1. config_parser supports the 'number' token type (in addition to 'word' and
* 'string'). Numbers are referred to using &num (like $str).
*
* 2. Criteria are not executed immediately, they are just stored.
*
* 3. config_parser recognizes \n and \r as 'end' token, while commands_parser
* ignores them.
*
* 4. config_parser skips the current line on invalid inputs and follows the
* nearest <error> token.
*
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <stdbool.h>
#include <stdint.h>
#include "all.h"
// Macros to make the YAJL API a bit easier to use.
#define y(x, ...) yajl_gen_ ## x (command_output.json_gen, ##__VA_ARGS__)
#define ystr(str) yajl_gen_string(command_output.json_gen, (unsigned char*)str, strlen(str))
/*******************************************************************************
* The data structures used for parsing. Essentially the current state and a
* list of tokens for that state.
*
* The GENERATED_* files are generated by generate-commands-parser.pl with the
* input parser-specs/configs.spec.
******************************************************************************/
#include "GENERATED_config_enums.h"
typedef struct token {
char *name;
char *identifier;
/* This might be __CALL */
cmdp_state next_state;
union {
uint16_t call_identifier;
} extra;
} cmdp_token;
typedef struct tokenptr {
cmdp_token *array;
int n;
} cmdp_token_ptr;
#include "GENERATED_config_tokens.h"
/*******************************************************************************
* The (small) stack where identified literals are stored during the parsing
* of a single command (like $workspace).
******************************************************************************/
struct stack_entry {
/* Just a pointer, not dynamically allocated. */
const char *identifier;
enum {
STACK_STR = 0,
STACK_LONG = 1,
} type;
union {
char *str;
long num;
} val;
};
/* 10 entries should be enough for everybody. */
static struct stack_entry stack[10];
/*
* Pushes a string (identified by 'identifier') on the stack. We simply use a
* single array, since the number of entries we have to store is very small.
*
*/
static void push_string(const char *identifier, const char *str) {
for (int c = 0; c < 10; c++) {
if (stack[c].identifier != NULL &&
strcmp(stack[c].identifier, identifier) != 0)
continue;
if (stack[c].identifier == NULL) {
/* Found a free slot, lets store it here. */
stack[c].identifier = identifier;
stack[c].val.str = sstrdup(str);
stack[c].type = STACK_STR;
} else {
/* Append the value. */
char *prev = stack[c].val.str;
sasprintf(&(stack[c].val.str), "%s,%s", prev, str);
free(prev);
}
return;
}
/* When we arrive here, the stack is full. This should not happen and
* means theres either a bug in this parser or the specification
* contains a command with more than 10 identified tokens. */
fprintf(stderr, "BUG: commands_parser stack full. This means either a bug "
"in the code, or a new command which contains more than "
"10 identified tokens.\n");
exit(1);
}
static void push_long(const char *identifier, long num) {
for (int c = 0; c < 10; c++) {
if (stack[c].identifier != NULL)
continue;
/* Found a free slot, lets store it here. */
stack[c].identifier = identifier;
stack[c].val.num = num;
stack[c].type = STACK_LONG;
return;
}
/* When we arrive here, the stack is full. This should not happen and
* means theres either a bug in this parser or the specification
* contains a command with more than 10 identified tokens. */
fprintf(stderr, "BUG: commands_parser stack full. This means either a bug "
"in the code, or a new command which contains more than "
"10 identified tokens.\n");
exit(1);
}
static const char *get_string(const char *identifier) {
for (int c = 0; c < 10; c++) {
if (stack[c].identifier == NULL)
break;
if (strcmp(identifier, stack[c].identifier) == 0)
return stack[c].val.str;
}
return NULL;
}
static const long get_long(const char *identifier) {
for (int c = 0; c < 10; c++) {
if (stack[c].identifier == NULL)
break;
if (strcmp(identifier, stack[c].identifier) == 0)
return stack[c].val.num;
}
return 0;
}
static void clear_stack(void) {
for (int c = 0; c < 10; c++) {
if (stack[c].type == STACK_STR && stack[c].val.str != NULL)
free(stack[c].val.str);
stack[c].identifier = NULL;
stack[c].val.str = NULL;
stack[c].val.num = 0;
}
}
// TODO: remove this if it turns out we dont need it for testing.
#if 0
/*******************************************************************************
* A dynamically growing linked list which holds the criteria for the current
* command.
******************************************************************************/
typedef struct criterion {
char *type;
char *value;
TAILQ_ENTRY(criterion) criteria;
} criterion;
static TAILQ_HEAD(criteria_head, criterion) criteria =
TAILQ_HEAD_INITIALIZER(criteria);
/*
* Stores the given type/value in the list of criteria.
* Accepts a pointer as first argument, since it is 'call'ed by the parser.
*
*/
static void push_criterion(void *unused_criteria, const char *type,
const char *value) {
struct criterion *criterion = malloc(sizeof(struct criterion));
criterion->type = strdup(type);
criterion->value = strdup(value);
TAILQ_INSERT_TAIL(&criteria, criterion, criteria);
}
/*
* Clears the criteria linked list.
* Accepts a pointer as first argument, since it is 'call'ed by the parser.
*
*/
static void clear_criteria(void *unused_criteria) {
struct criterion *criterion;
while (!TAILQ_EMPTY(&criteria)) {
criterion = TAILQ_FIRST(&criteria);
free(criterion->type);
free(criterion->value);
TAILQ_REMOVE(&criteria, criterion, criteria);
free(criterion);
}
}
#endif
/*******************************************************************************
* The parser itself.
******************************************************************************/
static cmdp_state state;
static Match current_match;
static struct ConfigResult subcommand_output;
static struct ConfigResult command_output;
/* A list which contains the states that lead to the current state, e.g.
* INITIAL, WORKSPACE_LAYOUT.
* When jumping back to INITIAL, statelist_idx will simply be set to 1
* (likewise for other states, e.g. MODE or BAR).
* This list is used to process the nearest error token. */
static cmdp_state statelist[10] = { INITIAL };
/* NB: statelist_idx points to where the next entry will be inserted */
static int statelist_idx = 1;
#include "GENERATED_config_call.h"
static void next_state(const cmdp_token *token) {
cmdp_state _next_state = token->next_state;
//printf("token = name %s identifier %s\n", token->name, token->identifier);
//printf("next_state = %d\n", token->next_state);
if (token->next_state == __CALL) {
subcommand_output.json_gen = command_output.json_gen;
GENERATED_call(token->extra.call_identifier, &subcommand_output);
_next_state = subcommand_output.next_state;
clear_stack();
}
state = _next_state;
if (state == INITIAL) {
clear_stack();
}
/* See if we are jumping back to a state in which we were in previously
* (statelist contains INITIAL) and just move statelist_idx accordingly. */
for (int i = 0; i < statelist_idx; i++) {
if (statelist[i] != _next_state)
continue;
statelist_idx = i+1;
return;
}
/* Otherwise, the state is new and we add it to the list */
statelist[statelist_idx++] = _next_state;
}
/*
* Returns a pointer to the start of the line (one byte after the previous \r,
* \n) or the start of the input, if this is the first line.
*
*/
static const char *start_of_line(const char *walk, const char *beginning) {
while (*walk != '\n' && *walk != '\r' && walk >= beginning) {
walk--;
}
return walk + 1;
}
/*
* Copies the line and terminates it at the next \n, if any.
*
* The caller has to free() the result.
*
*/
static char *single_line(const char *start) {
char *result = sstrdup(start);
char *end = strchr(result, '\n');
if (end != NULL)
*end = '\0';
return result;
}
struct ConfigResult *parse_config(const char *input, struct context *context) {
/* Dump the entire config file into the debug log. We cannot just use
* DLOG("%s", input); because one log message must not exceed 4 KiB. */
const char *dumpwalk = input;
int linecnt = 1;
while (*dumpwalk != '\0') {
char *next_nl = strchr(dumpwalk, '\n');
if (next_nl != NULL) {
DLOG("CONFIG(line %3d): %.*s\n", linecnt, (int)(next_nl - dumpwalk), dumpwalk);
dumpwalk = next_nl + 1;
} else {
DLOG("CONFIG(line %3d): %s\n", linecnt, dumpwalk);
break;
}
linecnt++;
}
state = INITIAL;
statelist_idx = 1;
/* A YAJL JSON generator used for formatting replies. */
#if YAJL_MAJOR >= 2
command_output.json_gen = yajl_gen_alloc(NULL);
#else
command_output.json_gen = yajl_gen_alloc(NULL, NULL);
#endif
y(array_open);
const char *walk = input;
const size_t len = strlen(input);
int c;
const cmdp_token *token;
bool token_handled;
linecnt = 1;
// TODO: make this testable
#ifndef TEST_PARSER
cfg_criteria_init(&current_match, &subcommand_output, INITIAL);
#endif
/* The "<=" operator is intentional: We also handle the terminating 0-byte
* explicitly by looking for an 'end' token. */
while ((walk - input) <= len) {
/* Skip whitespace before every token, newlines are relevant since they
* separate configuration directives. */
while ((*walk == ' ' || *walk == '\t') && *walk != '\0')
walk++;
//printf("remaining input: %s\n", walk);
cmdp_token_ptr *ptr = &(tokens[state]);
token_handled = false;
for (c = 0; c < ptr->n; c++) {
token = &(ptr->array[c]);
/* A literal. */
if (token->name[0] == '\'') {
if (strncasecmp(walk, token->name + 1, strlen(token->name) - 1) == 0) {
if (token->identifier != NULL)
push_string(token->identifier, token->name + 1);
walk += strlen(token->name) - 1;
next_state(token);
token_handled = true;
break;
}
continue;
}
if (strcmp(token->name, "number") == 0) {
/* Handle numbers. We only accept decimal numbers for now. */
char *end = NULL;
errno = 0;
long int num = strtol(walk, &end, 10);
if ((errno == ERANGE && (num == LONG_MIN || num == LONG_MAX)) ||
(errno != 0 && num == 0))
continue;
/* No valid numbers found */
if (end == walk)
continue;
if (token->identifier != NULL)
push_long(token->identifier, num);
/* Set walk to the first non-number character */
walk = end;
next_state(token);
token_handled = true;
break;
}
if (strcmp(token->name, "string") == 0 ||
strcmp(token->name, "word") == 0) {
const char *beginning = walk;
/* Handle quoted strings (or words). */
if (*walk == '"') {
beginning++;
walk++;
while (*walk != '\0' && (*walk != '"' || *(walk-1) == '\\'))
walk++;
} else {
if (token->name[0] == 's') {
while (*walk != '\0' && *walk != '\r' && *walk != '\n')
walk++;
} else {
/* For a word, the delimiters are white space (' ' or
* '\t'), closing square bracket (]), comma (,) and
* semicolon (;). */
while (*walk != ' ' && *walk != '\t' &&
*walk != ']' && *walk != ',' &&
*walk != ';' && *walk != '\r' &&
*walk != '\n' && *walk != '\0')
walk++;
}
}
if (walk != beginning) {
char *str = scalloc(walk-beginning + 1);
/* We copy manually to handle escaping of characters. */
int inpos, outpos;
for (inpos = 0, outpos = 0;
inpos < (walk-beginning);
inpos++, outpos++) {
/* We only handle escaped double quotes to not break
* backwards compatibility with people using \w in
* regular expressions etc. */
if (beginning[inpos] == '\\' && beginning[inpos+1] == '"')
inpos++;
str[outpos] = beginning[inpos];
}
if (token->identifier)
push_string(token->identifier, str);
free(str);
/* If we are at the end of a quoted string, skip the ending
* double quote. */
if (*walk == '"')
walk++;
next_state(token);
token_handled = true;
break;
}
}
if (strcmp(token->name, "end") == 0) {
//printf("checking for end: *%s*\n", walk);
if (*walk == '\0' || *walk == '\n' || *walk == '\r') {
next_state(token);
token_handled = true;
/* To make sure we start with an appropriate matching
* datastructure for commands which do *not* specify any
* criteria, we re-initialize the criteria system after
* every command. */
// TODO: make this testable
#ifndef TEST_PARSER
cfg_criteria_init(&current_match, &subcommand_output, INITIAL);
#endif
linecnt++;
walk++;
break;
}
}
}
if (!token_handled) {
/* Figure out how much memory we will need to fill in the names of
* all tokens afterwards. */
int tokenlen = 0;
for (c = 0; c < ptr->n; c++)
tokenlen += strlen(ptr->array[c].name) + strlen("'', ");
/* Build up a decent error message. We include the problem, the
* full input, and underline the position where the parser
* currently is. */
char *errormessage;
char *possible_tokens = smalloc(tokenlen + 1);
char *tokenwalk = possible_tokens;
for (c = 0; c < ptr->n; c++) {
token = &(ptr->array[c]);
if (token->name[0] == '\'') {
/* A literal is copied to the error message enclosed with
* single quotes. */
*tokenwalk++ = '\'';
strcpy(tokenwalk, token->name + 1);
tokenwalk += strlen(token->name + 1);
*tokenwalk++ = '\'';
} else {
/* Skip error tokens in error messages, they are used
* internally only and might confuse users. */
if (strcmp(token->name, "error") == 0)
continue;
/* Any other token is copied to the error message enclosed
* with angle brackets. */
*tokenwalk++ = '<';
strcpy(tokenwalk, token->name);
tokenwalk += strlen(token->name);
*tokenwalk++ = '>';
}
if (c < (ptr->n - 1)) {
*tokenwalk++ = ',';
*tokenwalk++ = ' ';
}
}
*tokenwalk = '\0';
sasprintf(&errormessage, "Expected one of these tokens: %s",
possible_tokens);
free(possible_tokens);
/* Go back to the beginning of the line */
const char *error_line = start_of_line(walk, input);
/* Contains the same amount of characters as 'input' has, but with
* the unparseable part highlighted using ^ characters. */
char *position = scalloc(strlen(error_line) + 1);
const char *copywalk;
for (copywalk = error_line;
*copywalk != '\n' && *copywalk != '\r' && *copywalk != '\0';
copywalk++)
position[(copywalk - error_line)] = (copywalk >= walk ? '^' : (*copywalk == '\t' ? '\t' : ' '));
position[(copywalk - error_line)] = '\0';
ELOG("CONFIG: %s\n", errormessage);
ELOG("CONFIG: (in file %s)\n", context->filename);
char *error_copy = single_line(error_line);
/* Print context lines *before* the error, if any. */
if (linecnt > 1) {
const char *context_p1_start = start_of_line(error_line-2, input);
char *context_p1_line = single_line(context_p1_start);
if (linecnt > 2) {
const char *context_p2_start = start_of_line(context_p1_start-2, input);
char *context_p2_line = single_line(context_p2_start);
ELOG("CONFIG: Line %3d: %s\n", linecnt - 2, context_p2_line);
free(context_p2_line);
}
ELOG("CONFIG: Line %3d: %s\n", linecnt - 1, context_p1_line);
free(context_p1_line);
}
ELOG("CONFIG: Line %3d: %s\n", linecnt, error_copy);
ELOG("CONFIG: %s\n", position);
free(error_copy);
/* Print context lines *after* the error, if any. */
for (int i = 0; i < 2; i++) {
char *error_line_end = strchr(error_line, '\n');
if (error_line_end != NULL && *(error_line_end + 1) != '\0') {
error_line = error_line_end + 1;
error_copy = single_line(error_line);
ELOG("CONFIG: Line %3d: %s\n", linecnt + i + 1, error_copy);
free(error_copy);
}
}
context->has_errors = true;
/* Format this error message as a JSON reply. */
y(map_open);
ystr("success");
y(bool, false);
/* We set parse_error to true to distinguish this from other
* errors. i3-nagbar is spawned upon keypresses only for parser
* errors. */
ystr("parse_error");
y(bool, true);
ystr("error");
ystr(errormessage);
ystr("input");
ystr(input);
ystr("errorposition");
ystr(position);
y(map_close);
/* Skip the rest of this line, but continue parsing. */
while ((walk - input) <= len && *walk != '\n')
walk++;
free(position);
free(errormessage);
clear_stack();
/* To figure out in which state to go (e.g. MODE or INITIAL),
* we find the nearest state which contains an <error> token
* and follow that one. */
bool error_token_found = false;
for (int i = statelist_idx-1; (i >= 0) && !error_token_found; i--) {
cmdp_token_ptr *errptr = &(tokens[statelist[i]]);
for (int j = 0; j < errptr->n; j++) {
if (strcmp(errptr->array[j].name, "error") != 0)
continue;
next_state(&(errptr->array[j]));
error_token_found = true;
break;
}
}
assert(error_token_found);
}
}
y(array_close);
return &command_output;
}
/*******************************************************************************
* Code for building the stand-alone binary test.commands_parser which is used
* by t/187-commands-parser.t.
******************************************************************************/
#ifdef TEST_PARSER
/*
* Logs the given message to stdout while prefixing the current time to it,
* but only if debug logging was activated.
* This is to be called by DLOG() which includes filename/linenumber
*
*/
void debuglog(char *fmt, ...) {
va_list args;
va_start(args, fmt);
fprintf(stdout, "# ");
vfprintf(stdout, fmt, args);
va_end(args);
}
void errorlog(char *fmt, ...) {
va_list args;
va_start(args, fmt);
vfprintf(stderr, fmt, args);
va_end(args);
}
static int criteria_next_state;
void cfg_criteria_init(I3_CFG, int _state) {
criteria_next_state = _state;
}
void cfg_criteria_add(I3_CFG, const char *ctype, const char *cvalue) {
}
void cfg_criteria_pop_state(I3_CFG) {
result->next_state = criteria_next_state;
}
int main(int argc, char *argv[]) {
if (argc < 2) {
fprintf(stderr, "Syntax: %s <command>\n", argv[0]);
return 1;
}
struct context context;
context.filename = "<stdin>";
parse_config(argv[1], &context);
}
#endif

View File

@ -28,6 +28,51 @@ static Rect total_outputs_dimensions(void) {
return outputs_dimensions;
}
/**
* Called when a floating window is created or resized.
* This function resizes the window if its size is higher or lower than the
* configured maximum/minimum size, respectively.
*
*/
void floating_check_size(Con *floating_con) {
/* Define reasonable minimal and maximal sizes for floating windows */
const int floating_sane_min_height = 50;
const int floating_sane_min_width = 75;
Rect floating_sane_max_dimensions;
/* Unless user requests otherwise (-1), ensure width/height do not exceed
* configured maxima or, if unconfigured, limit to combined width of all
* outputs */
if (config.floating_minimum_height != -1) {
if (config.floating_minimum_height == 0)
floating_con->rect.height = max(floating_con->rect.height, floating_sane_min_height);
else
floating_con->rect.height = max(floating_con->rect.height, config.floating_minimum_height);
}
if (config.floating_minimum_width != -1) {
if (config.floating_minimum_width == 0)
floating_con->rect.width = max(floating_con->rect.width, floating_sane_min_width);
else
floating_con->rect.width = max(floating_con->rect.width, config.floating_minimum_width);
}
/* Unless user requests otherwise (-1), raise the width/height to
* reasonable minimum dimensions */
floating_sane_max_dimensions = total_outputs_dimensions();
if (config.floating_maximum_height != -1) {
if (config.floating_maximum_height == 0)
floating_con->rect.height = min(floating_con->rect.height, floating_sane_max_dimensions.height);
else
floating_con->rect.height = min(floating_con->rect.height, config.floating_maximum_height);
}
if (config.floating_maximum_width != -1) {
if (config.floating_maximum_width == 0)
floating_con->rect.width = min(floating_con->rect.width, floating_sane_max_dimensions.width);
else
floating_con->rect.width = min(floating_con->rect.width, config.floating_maximum_width);
}
}
void floating_enable(Con *con, bool automatic) {
bool set_focus = (con == focused);
@ -99,7 +144,6 @@ void floating_enable(Con *con, bool automatic) {
* otherwise. */
Con *ws = con_get_workspace(con);
nc->parent = ws;
nc->split = true;
nc->type = CT_FLOATING_CON;
nc->layout = L_SPLITH;
/* We insert nc already, even though its rect is not yet calculated. This
@ -139,43 +183,7 @@ void floating_enable(Con *con, bool automatic) {
}
}
/* Define reasonable minimal and maximal sizes for floating windows */
const int floating_sane_min_height = 50;
const int floating_sane_min_width = 75;
Rect floating_sane_max_dimensions;
floating_sane_max_dimensions = total_outputs_dimensions();
/* Unless user requests otherwise (-1), ensure width/height do not exceed
* configured maxima or, if unconfigured, limit to combined width of all
* outputs */
if (config.floating_maximum_height != -1) {
if (config.floating_maximum_height == 0)
nc->rect.height = min(nc->rect.height, floating_sane_max_dimensions.height);
else
nc->rect.height = min(nc->rect.height, config.floating_maximum_height);
}
if (config.floating_maximum_width != -1) {
if (config.floating_maximum_width == 0)
nc->rect.width = min(nc->rect.width, floating_sane_max_dimensions.width);
else
nc->rect.width = min(nc->rect.width, config.floating_maximum_width);
}
/* Unless user requests otherwise (-1), raise the width/height to
* reasonable minimum dimensions */
if (config.floating_minimum_height != -1) {
if (config.floating_minimum_height == 0)
nc->rect.height = max(nc->rect.height, floating_sane_min_height);
else
nc->rect.height = max(nc->rect.height, config.floating_minimum_height);
}
if (config.floating_minimum_width != -1) {
if (config.floating_minimum_width == 0)
nc->rect.width = max(nc->rect.width, floating_sane_min_width);
else
nc->rect.width = max(nc->rect.width, config.floating_minimum_width);
}
floating_check_size(nc);
/* 3: attach the child to the new parent container. We need to do this
* because con_border_style_rect() needs to access con->parent. */
@ -220,17 +228,22 @@ void floating_enable(Con *con, bool automatic) {
/* Sanity check: Are the coordinates on the appropriate output? If not, we
* need to change them */
Output *current_output = get_output_containing(nc->rect.x, nc->rect.y);
Output *current_output = get_output_containing(nc->rect.x +
(nc->rect.width / 2), nc->rect.y + (nc->rect.height / 2));
Con *correct_output = con_get_output(ws);
if (!current_output || current_output->con != correct_output) {
DLOG("This floating window is on the wrong output, fixing coordinates (currently (%d, %d))\n",
nc->rect.x, nc->rect.y);
/* Take the relative coordinates of the current output, then add them
* to the coordinate space of the correct output */
uint32_t rel_x = (nc->rect.x - (current_output ? current_output->con->rect.x : 0));
uint32_t rel_y = (nc->rect.y - (current_output ? current_output->con->rect.y : 0));
nc->rect.x = correct_output->rect.x + rel_x;
nc->rect.y = correct_output->rect.y + rel_y;
/* If moving from one output to another, keep the relative position
* consistent (e.g. a centered dialog will remain centered). */
if (current_output)
floating_fix_coordinates(nc, &current_output->con->rect, &correct_output->rect);
else {
nc->rect.x = correct_output->rect.x;
nc->rect.y = correct_output->rect.y;
}
}
DLOG("Floating rect: (%d, %d) with %d x %d\n", nc->rect.x, nc->rect.y, nc->rect.width, nc->rect.height);
@ -395,7 +408,7 @@ void floating_drag_window(Con *con, const xcb_button_press_event_t *event) {
tree_render();
/* Drag the window */
drag_pointer(con, event, XCB_NONE, BORDER_TOP /* irrelevant */, drag_window_callback, event);
drag_pointer(con, event, XCB_NONE, BORDER_TOP /* irrelevant */, XCURSOR_CURSOR_MOVE, drag_window_callback, event);
tree_render();
}
@ -479,13 +492,21 @@ void floating_resize_window(Con *con, const bool proportional,
corner |= BORDER_LEFT;
else corner |= BORDER_RIGHT;
if (event->event_y <= (con->rect.height / 2))
int cursor = 0;
if (event->event_y <= (con->rect.height / 2)) {
corner |= BORDER_TOP;
else corner |= BORDER_BOTTOM;
cursor = (corner & BORDER_LEFT) ?
XCURSOR_CURSOR_TOP_LEFT_CORNER : XCURSOR_CURSOR_TOP_RIGHT_CORNER;
}
else {
corner |= BORDER_BOTTOM;
cursor = (corner & BORDER_LEFT) ?
XCURSOR_CURSOR_BOTTOM_LEFT_CORNER : XCURSOR_CURSOR_BOTTOM_RIGHT_CORNER;
}
struct resize_window_callback_params params = { corner, proportional, event };
drag_pointer(con, event, XCB_NONE, BORDER_TOP /* irrelevant */, resize_window_callback, &params);
drag_pointer(con, event, XCB_NONE, BORDER_TOP /* irrelevant */, cursor, resize_window_callback, &params);
}
/*
@ -497,13 +518,16 @@ void floating_resize_window(Con *con, const bool proportional,
*
*/
void drag_pointer(Con *con, const xcb_button_press_event_t *event, xcb_window_t
confine_to, border_t border, callback_t callback, const void *extra)
confine_to, border_t border, int cursor, callback_t callback, const void *extra)
{
uint32_t new_x, new_y;
Rect old_rect = { 0, 0, 0, 0 };
if (con != NULL)
memcpy(&old_rect, &(con->rect), sizeof(Rect));
Cursor xcursor = (cursor && xcursor_supported) ?
xcursor_get_cursor(cursor) : XCB_NONE;
/* Grab the pointer */
xcb_grab_pointer_cookie_t cookie;
xcb_grab_pointer_reply_t *reply;
@ -514,7 +538,7 @@ void drag_pointer(Con *con, const xcb_button_press_event_t *event, xcb_window_t
XCB_GRAB_MODE_ASYNC, /* pointer events should continue as normal */
XCB_GRAB_MODE_ASYNC, /* keyboard mode */
confine_to, /* confine_to = in which window should the cursor stay */
XCB_NONE, /* dont display a special cursor */
xcursor, /* possibly display a special cursor */
XCB_CURRENT_TIME);
if ((reply = xcb_grab_pointer_reply(conn, cookie, NULL)) == NULL) {
@ -623,16 +647,18 @@ void floating_fix_coordinates(Con *con, Rect *old_rect, Rect *new_rect) {
new_rect->x, new_rect->y, new_rect->width, new_rect->height);
/* First we get the x/y coordinates relative to the x/y coordinates
* of the output on which the window is on */
int32_t rel_x = (con->rect.x - old_rect->x);
int32_t rel_y = (con->rect.y - old_rect->y);
int32_t rel_x = con->rect.x - old_rect->x + (int32_t)(con->rect.width / 2);
int32_t rel_y = con->rect.y - old_rect->y + (int32_t)(con->rect.height / 2);
/* Then we calculate a fraction, for example 0.63 for a window
* which is at y = 1212 of a 1920 px high output */
DLOG("rel_x = %d, rel_y = %d, fraction_x = %f, fraction_y = %f, output->w = %d, output->h = %d\n",
rel_x, rel_y, (double)rel_x / old_rect->width, (double)rel_y / old_rect->height,
old_rect->width, old_rect->height);
/* Here we have to multiply at first. Or we will lose precision when not compiled with -msse2 */
con->rect.x = (int32_t)new_rect->x + (double)(rel_x * (int32_t)new_rect->width) / (int32_t)old_rect->width;
con->rect.y = (int32_t)new_rect->y + (double)(rel_y * (int32_t)new_rect->height) / (int32_t)old_rect->height;
con->rect.x = (int32_t)new_rect->x + (double)(rel_x * (int32_t)new_rect->width)
/ (int32_t)old_rect->width - (int32_t)(con->rect.width / 2);
con->rect.y = (int32_t)new_rect->y + (double)(rel_y * (int32_t)new_rect->height)
/ (int32_t)old_rect->height - (int32_t)(con->rect.height / 2);
DLOG("Resulting coordinates: x = %d, y = %d\n", con->rect.x, con->rect.y);
}

View File

@ -836,7 +836,13 @@ static bool handle_hints(void *data, xcb_connection_t *conn, uint8_t state, xcb_
}
/* Update the flag on the client directly */
con->urgent = (xcb_icccm_wm_hints_get_urgency(&hints) != 0);
bool hint_urgent = (xcb_icccm_wm_hints_get_urgency(&hints) != 0);
if (con->urgency_timer == NULL) {
con->urgent = hint_urgent;
} else
DLOG("Discarding urgency WM_HINT because timer is running\n");
//CLIENT_LOG(con);
if (con->window) {
if (con->urgent) {
@ -846,6 +852,9 @@ static bool handle_hints(void *data, xcb_connection_t *conn, uint8_t state, xcb_
con->window->urgent.tv_usec = 0;
}
}
con_update_parents_urgency(con);
LOG("Urgency flag changed to %d\n", con->urgent);
Con *ws;

View File

@ -50,12 +50,25 @@ src/cfgparse.tab.c: src/cfgparse.y $(i3_HEADERS_DEP)
# and once as an object file for i3.
src/commands_parser.o: src/commands_parser.c $(i3_HEADERS_DEP) i3-command-parser.stamp
echo "[i3] CC $<"
$(CC) $(I3_CPPFLAGS) $(XCB_CPPFLAGS) $(CPPFLAGS) $(i3_CFLAGS) $(I3_CFLAGS) $(CFLAGS) $(I3_LDFLAGS) $(LDFLAGS) -DTEST_PARSER -o test.commands_parser $< $(LIBS) $(i3_LIBS)
$(CC) $(I3_CPPFLAGS) $(XCB_CPPFLAGS) $(CPPFLAGS) $(i3_CFLAGS) $(I3_CFLAGS) $(CFLAGS) $(I3_LDFLAGS) $(LDFLAGS) -DTEST_PARSER -g -o test.commands_parser $< $(LIBS) $(i3_LIBS)
$(CC) $(I3_CPPFLAGS) $(XCB_CPPFLAGS) $(CPPFLAGS) $(i3_CFLAGS) $(I3_CFLAGS) $(CFLAGS) -c -o $@ ${canonical_path}/$<
# This target compiles the command parser twice:
# Once with -DTEST_PARSER, creating a stand-alone executable used for tests,
# and once as an object file for i3.
src/config_parser.o: src/config_parser.c $(i3_HEADERS_DEP) i3-config-parser.stamp
echo "[i3] CC $<"
$(CC) $(I3_CPPFLAGS) $(XCB_CPPFLAGS) $(CPPFLAGS) $(i3_CFLAGS) $(I3_CFLAGS) $(CFLAGS) $(I3_LDFLAGS) $(LDFLAGS) -DTEST_PARSER -g -o test.config_parser $< $(LIBS) $(i3_LIBS)
$(CC) $(I3_CPPFLAGS) $(XCB_CPPFLAGS) $(CPPFLAGS) $(i3_CFLAGS) $(I3_CFLAGS) $(CFLAGS) -c -o $@ ${canonical_path}/$<
i3-command-parser.stamp: generate-command-parser.pl parser-specs/commands.spec
echo "[i3] Generating command parser"
(cd include; ../generate-command-parser.pl)
(cd include; ../generate-command-parser.pl --input=../parser-specs/commands.spec --prefix=command)
touch $@
i3-config-parser.stamp: generate-command-parser.pl parser-specs/config.spec
echo "[i3] Generating config parser"
(cd include; ../generate-command-parser.pl --input=../parser-specs/config.spec --prefix=config)
touch $@
i3: libi3.a $(i3_OBJECTS)
@ -74,6 +87,7 @@ install-i3: i3
$(INSTALL) -m 0755 i3-sensible-editor $(DESTDIR)$(PREFIX)/bin/
$(INSTALL) -m 0755 i3-sensible-pager $(DESTDIR)$(PREFIX)/bin/
$(INSTALL) -m 0755 i3-sensible-terminal $(DESTDIR)$(PREFIX)/bin/
$(INSTALL) -m 0755 i3-dmenu-desktop $(DESTDIR)$(PREFIX)/bin/
test -e $(DESTDIR)$(SYSCONFDIR)/i3/config || $(INSTALL) -m 0644 i3.config $(DESTDIR)$(SYSCONFDIR)/i3/config
test -e $(DESTDIR)$(SYSCONFDIR)/i3/config.keycodes || $(INSTALL) -m 0644 i3.config.keycodes $(DESTDIR)$(SYSCONFDIR)/i3/config.keycodes
$(INSTALL) -m 0644 i3.xsession.desktop $(DESTDIR)$(PREFIX)/share/xsessions/i3.desktop
@ -82,4 +96,4 @@ install-i3: i3
clean-i3:
echo "[i3] Clean"
rm -f $(i3_OBJECTS) $(i3_SOURCES_GENERATED) $(i3_HEADERS_CMDPARSER) include/loglevels.h loglevels.tmp include/all.h.pch i3-command-parser.stamp i3 src/*.gcno src/cfgparse.{output,dot,tab.h,y.o} src/cmdparse.*
rm -f $(i3_OBJECTS) $(i3_SOURCES_GENERATED) $(i3_HEADERS_CMDPARSER) include/loglevels.h loglevels.tmp include/all.h.pch i3-command-parser.stamp i3-config-parser.stamp i3 src/*.gcno src/cfgparse.{output,dot,tab.h,y.o} src/cmdparse.*

119
src/ipc.c
View File

@ -10,6 +10,7 @@
*
*/
#include "all.h"
#include "yajl_utils.h"
#include <sys/socket.h>
#include <sys/un.h>
@ -18,14 +19,9 @@
#include <ev.h>
#include <yajl/yajl_gen.h>
#include <yajl/yajl_parse.h>
#include <yajl/yajl_version.h>
char *current_socketpath = NULL;
/* Shorter names for all those yajl_gen_* functions */
#define y(x, ...) yajl_gen_ ## x (gen, ##__VA_ARGS__)
#define ystr(str) yajl_gen_string(gen, (unsigned char*)str, strlen(str))
TAILQ_HEAD(ipc_client_head, ipc_client) all_clients = TAILQ_HEAD_INITIALIZER(all_clients);
/*
@ -52,7 +48,7 @@ static bool mkdirp(const char *path) {
ELOG("mkdir(%s) failed: %s\n", path, strerror(errno));
return false;
}
char *copy = strdup(path);
char *copy = sstrdup(path);
/* strip trailing slashes, if any */
while (copy[strlen(copy)-1] == '/')
copy[strlen(copy)-1] = '\0';
@ -128,11 +124,7 @@ IPC_HANDLER(command) {
tree_render();
const unsigned char *reply;
#if YAJL_MAJOR >= 2
size_t length;
#else
unsigned int length;
#endif
ylength length;
yajl_gen_get_buf(command_output->json_gen, &reply, &length);
ipc_send_message(fd, length, I3_IPC_REPLY_TYPE_COMMAND,
@ -165,7 +157,7 @@ void dump_node(yajl_gen gen, struct Con *con, bool inplace_restart) {
/* provided for backwards compatibility only. */
ystr("orientation");
if (!con->split)
if (!con_is_split(con))
ystr("none");
else {
if (con_orientation(con) == HORIZ)
@ -202,9 +194,6 @@ void dump_node(yajl_gen gen, struct Con *con, bool inplace_restart) {
ystr("focused");
y(bool, (con == focused));
ystr("split");
y(bool, con->split);
ystr("layout");
switch (con->layout) {
case L_DEFAULT:
@ -266,11 +255,14 @@ void dump_node(yajl_gen gen, struct Con *con, bool inplace_restart) {
case BS_NONE:
ystr("none");
break;
case BS_1PIXEL:
ystr("1pixel");
case BS_PIXEL:
ystr("pixel");
break;
}
ystr("current_border_width");
y(integer, con->current_border_width);
dump_rect(gen, "rect", con->rect);
dump_rect(gen, "window_rect", con->window_rect);
dump_rect(gen, "geometry", con->geometry);
@ -367,20 +359,12 @@ void dump_node(yajl_gen gen, struct Con *con, bool inplace_restart) {
IPC_HANDLER(tree) {
setlocale(LC_NUMERIC, "C");
#if YAJL_MAJOR >= 2
yajl_gen gen = yajl_gen_alloc(NULL);
#else
yajl_gen gen = yajl_gen_alloc(NULL, NULL);
#endif
yajl_gen gen = ygenalloc();
dump_node(gen, croot, false);
setlocale(LC_NUMERIC, "");
const unsigned char *payload;
#if YAJL_MAJOR >= 2
size_t length;
#else
unsigned int length;
#endif
ylength length;
y(get_buf, &payload, &length);
ipc_send_message(fd, length, I3_IPC_REPLY_TYPE_TREE, payload);
@ -394,18 +378,14 @@ IPC_HANDLER(tree) {
*
*/
IPC_HANDLER(get_workspaces) {
#if YAJL_MAJOR >= 2
yajl_gen gen = yajl_gen_alloc(NULL);
#else
yajl_gen gen = yajl_gen_alloc(NULL, NULL);
#endif
yajl_gen gen = ygenalloc();
y(array_open);
Con *focused_ws = con_get_workspace(focused);
Con *output;
TAILQ_FOREACH(output, &(croot->nodes_head), nodes) {
if (output->name[0] == '_' && output->name[1] == '_')
if (con_is_internal(output))
continue;
Con *ws;
TAILQ_FOREACH(ws, &(output_get_content(output)->nodes_head), nodes) {
@ -451,11 +431,7 @@ IPC_HANDLER(get_workspaces) {
y(array_close);
const unsigned char *payload;
#if YAJL_MAJOR >= 2
size_t length;
#else
unsigned int length;
#endif
ylength length;
y(get_buf, &payload, &length);
ipc_send_message(fd, length, I3_IPC_REPLY_TYPE_WORKSPACES, payload);
@ -468,11 +444,7 @@ IPC_HANDLER(get_workspaces) {
*
*/
IPC_HANDLER(get_outputs) {
#if YAJL_MAJOR >= 2
yajl_gen gen = yajl_gen_alloc(NULL);
#else
yajl_gen gen = yajl_gen_alloc(NULL, NULL);
#endif
yajl_gen gen = ygenalloc();
y(array_open);
Output *output;
@ -512,11 +484,7 @@ IPC_HANDLER(get_outputs) {
y(array_close);
const unsigned char *payload;
#if YAJL_MAJOR >= 2
size_t length;
#else
unsigned int length;
#endif
ylength length;
y(get_buf, &payload, &length);
ipc_send_message(fd, length, I3_IPC_REPLY_TYPE_OUTPUTS, payload);
@ -529,11 +497,7 @@ IPC_HANDLER(get_outputs) {
*
*/
IPC_HANDLER(get_marks) {
#if YAJL_MAJOR >= 2
yajl_gen gen = yajl_gen_alloc(NULL);
#else
yajl_gen gen = yajl_gen_alloc(NULL, NULL);
#endif
yajl_gen gen = ygenalloc();
y(array_open);
Con *con;
@ -544,11 +508,7 @@ IPC_HANDLER(get_marks) {
y(array_close);
const unsigned char *payload;
#if YAJL_MAJOR >= 2
size_t length;
#else
unsigned int length;
#endif
ylength length;
y(get_buf, &payload, &length);
ipc_send_message(fd, length, I3_IPC_REPLY_TYPE_MARKS, payload);
@ -560,11 +520,7 @@ IPC_HANDLER(get_marks) {
*
*/
IPC_HANDLER(get_version) {
#if YAJL_MAJOR >= 2
yajl_gen gen = yajl_gen_alloc(NULL);
#else
yajl_gen gen = yajl_gen_alloc(NULL, NULL);
#endif
yajl_gen gen = ygenalloc();
y(map_open);
ystr("major");
@ -582,11 +538,7 @@ IPC_HANDLER(get_version) {
y(map_close);
const unsigned char *payload;
#if YAJL_MAJOR >= 2
size_t length;
#else
unsigned int length;
#endif
ylength length;
y(get_buf, &payload, &length);
ipc_send_message(fd, length, I3_IPC_REPLY_TYPE_VERSION, payload);
@ -599,11 +551,7 @@ IPC_HANDLER(get_version) {
*
*/
IPC_HANDLER(get_bar_config) {
#if YAJL_MAJOR >= 2
yajl_gen gen = yajl_gen_alloc(NULL);
#else
yajl_gen gen = yajl_gen_alloc(NULL, NULL);
#endif
yajl_gen gen = ygenalloc();
/* If no ID was passed, we return a JSON array with all IDs */
if (message_size == 0) {
@ -615,11 +563,7 @@ IPC_HANDLER(get_bar_config) {
y(array_close);
const unsigned char *payload;
#if YAJL_MAJOR >= 2
size_t length;
#else
unsigned int length;
#endif
ylength length;
y(get_buf, &payload, &length);
ipc_send_message(fd, length, I3_IPC_REPLY_TYPE_BAR_CONFIG, payload);
@ -753,11 +697,7 @@ IPC_HANDLER(get_bar_config) {
y(map_close);
const unsigned char *payload;
#if YAJL_MAJOR >= 2
size_t length;
#else
unsigned int length;
#endif
ylength length;
y(get_buf, &payload, &length);
ipc_send_message(fd, length, I3_IPC_REPLY_TYPE_BAR_CONFIG, payload);
@ -768,13 +708,8 @@ IPC_HANDLER(get_bar_config) {
* Callback for the YAJL parser (will be called when a string is parsed).
*
*/
#if YAJL_MAJOR < 2
static int add_subscription(void *extra, const unsigned char *s,
unsigned int len) {
#else
static int add_subscription(void *extra, const unsigned char *s,
size_t len) {
#endif
ylength len) {
ipc_client *client = extra;
DLOG("should add subscription to extra %p, sub %.*s\n", client, (int)len, s);
@ -824,11 +759,7 @@ IPC_HANDLER(subscribe) {
memset(&callbacks, 0, sizeof(yajl_callbacks));
callbacks.yajl_string = add_subscription;
#if YAJL_MAJOR >= 2
p = yajl_alloc(&callbacks, NULL, (void*)client);
#else
p = yajl_alloc(&callbacks, NULL, NULL, (void*)client);
#endif
p = yalloc(&callbacks, (void*)client);
stat = yajl_parse(p, (const unsigned char*)message, message_size);
if (stat != yajl_status_ok) {
unsigned char *err;

View File

@ -172,19 +172,17 @@ static int json_string(void *ctx, const unsigned char *val, unsigned int len) {
else if (strcasecmp(buf, "vertical") == 0)
json_node->last_split_layout = L_SPLITV;
else LOG("Unhandled orientation: %s\n", buf);
/* What used to be an implicit check whether orientation !=
* NO_ORIENTATION is now a proper separate flag. */
if (strcasecmp(buf, "none") != 0)
json_node->split = true;
free(buf);
} else if (strcasecmp(last_key, "border") == 0) {
char *buf = NULL;
sasprintf(&buf, "%.*s", (int)len, val);
if (strcasecmp(buf, "none") == 0)
json_node->border_style = BS_NONE;
else if (strcasecmp(buf, "1pixel") == 0)
json_node->border_style = BS_1PIXEL;
else if (strcasecmp(buf, "1pixel") == 0) {
json_node->border_style = BS_PIXEL;
json_node->current_border_width = 1;
} else if (strcasecmp(buf, "pixel") == 0)
json_node->border_style = BS_PIXEL;
else if (strcasecmp(buf, "normal") == 0)
json_node->border_style = BS_NORMAL;
else LOG("Unhandled \"border\": %s\n", buf);
@ -199,11 +197,9 @@ static int json_string(void *ctx, const unsigned char *val, unsigned int len) {
json_node->layout = L_STACKED;
else if (strcasecmp(buf, "tabbed") == 0)
json_node->layout = L_TABBED;
else if (strcasecmp(buf, "dockarea") == 0) {
else if (strcasecmp(buf, "dockarea") == 0)
json_node->layout = L_DOCKAREA;
/* Necessary for migrating from older versions of i3. */
json_node->split = false;
} else if (strcasecmp(buf, "output") == 0)
else if (strcasecmp(buf, "output") == 0)
json_node->layout = L_OUTPUT;
else if (strcasecmp(buf, "splith") == 0)
json_node->layout = L_SPLITH;
@ -278,6 +274,9 @@ static int json_int(void *ctx, long val) {
if (strcasecmp(last_key, "num") == 0)
json_node->num = val;
if (strcasecmp(last_key, "current_border_width") == 0)
json_node->current_border_width = val;
if (!parsing_swallows && strcasecmp(last_key, "id") == 0)
json_node->old_id = val;
@ -327,9 +326,6 @@ static int json_bool(void *ctx, int val) {
to_focus = json_node;
}
if (strcasecmp(last_key, "split") == 0)
json_node->split = val;
if (parsing_swallows) {
if (strcasecmp(last_key, "restart_mode") == 0)
current_swallow->restart_mode = val;
@ -350,11 +346,22 @@ void tree_append_json(const char *filename) {
/* TODO: percent of other windows are not correctly fixed at the moment */
FILE *f;
if ((f = fopen(filename, "r")) == NULL) {
LOG("Cannot open file\n");
LOG("Cannot open file \"%s\"\n", filename);
return;
}
struct stat stbuf;
if (fstat(fileno(f), &stbuf) != 0) {
LOG("Cannot fstat() the file\n");
fclose(f);
return;
}
char *buf = smalloc(stbuf.st_size);
int n = fread(buf, 1, stbuf.st_size, f);
if (n != stbuf.st_size) {
LOG("File \"%s\" could not be read entirely, not loading.\n", filename);
fclose(f);
return;
}
char *buf = malloc(65535); /* TODO */
int n = fread(buf, 1, 65535, f);
LOG("read %d bytes\n", n);
yajl_gen g;
yajl_handle hand;

View File

@ -108,7 +108,7 @@ void init_logging(void) {
#endif
logbuffer_size = min(physical_mem_bytes * 0.01, shmlog_size);
sasprintf(&shmlogname, "/i3-log-%d", getpid());
logbuffer_shm = shm_open(shmlogname, O_RDWR | O_CREAT | O_TRUNC, S_IREAD | S_IWRITE);
logbuffer_shm = shm_open(shmlogname, O_RDWR | O_CREAT, S_IREAD | S_IWRITE);
if (logbuffer_shm == -1) {
ELOG("Could not shm_open SHM segment for the i3 log: %s\n", strerror(errno));
return;

View File

@ -233,12 +233,9 @@ static void i3_exit(void) {
*
*/
static void handle_signal(int sig, siginfo_t *info, void *data) {
fprintf(stderr, "Received signal %d, terminating\n", sig);
if (*shmlogname != '\0') {
fprintf(stderr, "Closing SHM log \"%s\"\n", shmlogname);
shm_unlink(shmlogname);
}
fflush(stderr);
raise(sig);
}
@ -272,6 +269,7 @@ int main(int argc, char *argv[]) {
{"get_socketpath", no_argument, 0, 0},
{"fake_outputs", required_argument, 0, 0},
{"fake-outputs", required_argument, 0, 0},
{"force-old-config-parser-v4.4-only", no_argument, 0, 0},
{0, 0, 0, 0}
};
int option_index = 0, opt;
@ -375,6 +373,10 @@ int main(int argc, char *argv[]) {
LOG("Initializing fake outputs: %s\n", optarg);
fake_outputs = sstrdup(optarg);
break;
} else if (strcmp(long_options[option_index].name, "force-old-config-parser-v4.4-only") == 0) {
LOG("FORCING OLD CONFIG PARSER!\n");
force_old_config_parser = true;
break;
}
/* fall-through */
default:
@ -542,6 +544,7 @@ int main(int argc, char *argv[]) {
uint32_t mask = XCB_CW_EVENT_MASK;
uint32_t values[] = { XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT |
XCB_EVENT_MASK_BUTTON_PRESS |
XCB_EVENT_MASK_STRUCTURE_NOTIFY | /* when the user adds a screen (e.g. video
projector), the root window gets a
ConfigureNotify */

View File

@ -172,9 +172,13 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki
xcb_grab_button(conn, false, window, XCB_EVENT_MASK_BUTTON_PRESS,
XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC, root, XCB_NONE,
3 /* right mouse button */,
2 /* middle mouse button */,
XCB_BUTTON_MASK_ANY /* dont filter for any modifiers */);
xcb_grab_button(conn, false, window, XCB_EVENT_MASK_BUTTON_PRESS,
XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC, root, XCB_NONE,
3 /* right mouse button */,
XCB_BUTTON_MASK_ANY /* dont filter for any modifiers */);
/* update as much information as possible so far (some replies may be NULL) */
window_update_class(cwindow, xcb_get_property_reply(conn, class_cookie, NULL), true);
@ -343,6 +347,12 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki
fs != NULL) {
LOG("There is a fullscreen window, leaving fullscreen mode\n");
con_toggle_fullscreen(fs, CF_OUTPUT);
} else if (config.popup_during_fullscreen == PDF_SMART &&
fs != NULL &&
fs->window != NULL &&
fs->window->id == cwindow->transient_for) {
LOG("This floating window belongs to the fullscreen window (popup_during_fullscreen == smart)\n");
con_focus(nc);
}
}

View File

@ -69,7 +69,7 @@ Output *get_first_output(void) {
if (output->active)
return output;
return NULL;
die("No usable outputs available.\n");
}
/*
@ -101,89 +101,76 @@ Output *get_output_containing(int x, int y) {
*
*/
Output *get_output_most(direction_t direction, Output *current) {
Output *output, *candidate = NULL;
int position = 0;
TAILQ_FOREACH(output, &outputs, outputs) {
if (!output->active)
continue;
/* Repeated calls of WIN determine the winner of the comparison */
#define WIN(variable, condition) \
if (variable condition) { \
candidate = output; \
position = variable; \
} \
break;
if (((direction == D_UP) || (direction == D_DOWN)) &&
(current->rect.x != output->rect.x))
continue;
if (((direction == D_LEFT) || (direction == D_RIGHT)) &&
(current->rect.y != output->rect.y))
continue;
switch (direction) {
case D_UP:
WIN(output->rect.y, <= position);
case D_DOWN:
WIN(output->rect.y, >= position);
case D_LEFT:
WIN(output->rect.x, <= position);
case D_RIGHT:
WIN(output->rect.x, >= position);
}
}
assert(candidate != NULL);
return candidate;
Output *best = get_output_next(direction, current, FARTHEST_OUTPUT);
if (!best)
best = current;
DLOG("current = %s, best = %s\n", current->name, best->name);
return best;
}
/*
* Gets the output which is the next one in the given direction.
*
*/
Output *get_output_next(direction_t direction, Output *current) {
Output *output, *candidate = NULL;
Output *get_output_next(direction_t direction, Output *current, output_close_far_t close_far) {
Rect *cur = &(current->rect),
*other;
Output *output,
*best = NULL;
TAILQ_FOREACH(output, &outputs, outputs) {
if (!output->active)
continue;
if (((direction == D_UP) || (direction == D_DOWN)) &&
(current->rect.x != output->rect.x))
other = &(output->rect);
if ((direction == D_RIGHT && other->x > cur->x) ||
(direction == D_LEFT && other->x < cur->x)) {
/* Skip the output when it doesnt overlap the other ones y
* coordinate at all. */
if ((other->y + other->height) <= cur->y ||
(cur->y + cur->height) <= other->y)
continue;
} else if ((direction == D_DOWN && other->y > cur->y) ||
(direction == D_UP && other->y < cur->y)) {
/* Skip the output when it doesnt overlap the other ones x
* coordinate at all. */
if ((other->x + other->width) <= cur->x ||
(cur->x + cur->width) <= other->x)
continue;
} else
continue;
if (((direction == D_LEFT) || (direction == D_RIGHT)) &&
(current->rect.y != output->rect.y))
/* No candidate yet? Start with this one. */
if (!best) {
best = output;
continue;
}
switch (direction) {
case D_UP:
if (output->rect.y < current->rect.y &&
(!candidate || output->rect.y < candidate->rect.y))
candidate = output;
break;
case D_DOWN:
if (output->rect.y > current->rect.y &&
(!candidate || output->rect.y > candidate->rect.y))
candidate = output;
break;
case D_LEFT:
if (output->rect.x < current->rect.x &&
(!candidate || output->rect.x > candidate->rect.x))
candidate = output;
break;
case D_RIGHT:
if (output->rect.x > current->rect.x &&
(!candidate || output->rect.x < candidate->rect.x))
candidate = output;
break;
if (close_far == CLOSEST_OUTPUT) {
/* Is this output better (closer to the current output) than our
* current best bet? */
if ((direction == D_RIGHT && other->x < best->rect.x) ||
(direction == D_LEFT && other->x > best->rect.x) ||
(direction == D_DOWN && other->y < best->rect.y) ||
(direction == D_UP && other->y > best->rect.y)) {
best = output;
continue;
}
} else {
/* Is this output better (farther to the current output) than our
* current best bet? */
if ((direction == D_RIGHT && other->x > best->rect.x) ||
(direction == D_LEFT && other->x < best->rect.x) ||
(direction == D_DOWN && other->y > best->rect.y) ||
(direction == D_UP && other->y < best->rect.y)) {
best = output;
continue;
}
}
}
return candidate;
DLOG("current = %s, best = %s\n", current->name, (best ? best->name : "NULL"));
return best;
}
/*
@ -660,8 +647,7 @@ void randr_query_outputs(void) {
output->active = false;
DLOG("Output %s disabled, re-assigning workspaces/docks\n", output->name);
if ((first = get_first_output()) == NULL)
die("No usable outputs available\n");
first = get_first_output();
/* TODO: refactor the following code into a nice function. maybe
* use an on_destroy callback which is implement differently for
@ -749,6 +735,9 @@ void randr_query_outputs(void) {
disable_randr(conn);
}
/* Verifies that there is at least one active output as a side-effect. */
get_first_output();
/* Just go through each active output and assign one workspace */
TAILQ_FOREACH(output, &outputs, outputs) {
if (!output->active)

View File

@ -225,7 +225,7 @@ void render_con(Con *con, bool render_fullscreen) {
if (con->layout == L_OUTPUT) {
/* Skip i3-internal outputs */
if (con->name[0] == '_' && con->name[1] == '_')
if (con_is_internal(con))
return;
render_l_output(con);
} else if (con->type == CT_ROOT) {
@ -240,36 +240,34 @@ void render_con(Con *con, bool render_fullscreen) {
* windows/containers so that they overlap on another output. */
DLOG("Rendering floating windows:\n");
TAILQ_FOREACH(output, &(con->nodes_head), nodes) {
if (output->name[0] == '_' && output->name[1] == '_')
if (con_is_internal(output))
continue;
/* Get the active workspace of that output */
Con *content = output_get_content(output);
Con *workspace = TAILQ_FIRST(&(content->focus_head));
/* Check for (floating!) fullscreen nodes */
/* XXX: This code duplication is unfortunate. Keep in mind to fix
* this when we clean up the whole render.c */
Con *fullscreen = NULL;
fullscreen = con_get_fullscreen_con(workspace, CF_OUTPUT);
if (fullscreen) {
/* Either the fullscreen window is inside the floating
* container, then we need to render and raise it now… */
if (con_inside_floating(fullscreen)) {
fullscreen->rect = output->rect;
x_raise_con(fullscreen);
render_con(fullscreen, true);
continue;
} else {
/* …or its a tiling window, in which case the floating
* windows should not overlap it, so we skip rendering this
* output. */
continue;
}
}
Con *fullscreen = con_get_fullscreen_con(workspace, CF_OUTPUT);
Con *child;
TAILQ_FOREACH(child, &(workspace->floating_head), floating_windows) {
DLOG("floating child at (%d,%d) with %d x %d\n", child->rect.x, child->rect.y, child->rect.width, child->rect.height);
/* Dont render floating windows when there is a fullscreen window
* on that workspace. Necessary to make floating fullscreen work
* correctly (ticket #564). */
if (fullscreen != NULL) {
Con *floating_child = con_descend_focused(child);
/* Exception to the above rule: smart
* popup_during_fullscreen handling (popups belonging to
* the fullscreen app will be rendered). */
if (floating_child->window == NULL ||
fullscreen->window == NULL ||
floating_child->window->transient_for != fullscreen->window->id)
continue;
else {
DLOG("Rendering floating child even though in fullscreen mode: "
"floating->transient_for (0x%08x) == fullscreen->id (0x%08x)\n",
floating_child->window->transient_for, fullscreen->window->id);
}
}
DLOG("floating child at (%d,%d) with %d x %d\n",
child->rect.x, child->rect.y, child->rect.width, child->rect.height);
x_raise_con(child);
render_con(child, false);
}
@ -324,7 +322,7 @@ void render_con(Con *con, bool render_fullscreen) {
child->deco_rect.width = child->rect.width;
child->deco_rect.height = deco_height;
if (children > 1 || (child->border_style != BS_1PIXEL && child->border_style != BS_NONE)) {
if (children > 1 || (child->border_style != BS_PIXEL && child->border_style != BS_NONE)) {
child->rect.y += (deco_height * children);
child->rect.height -= (deco_height * children);
}
@ -341,12 +339,12 @@ void render_con(Con *con, bool render_fullscreen) {
child->deco_rect.x = x - con->rect.x + i * child->deco_rect.width;
child->deco_rect.y = y - con->rect.y;
if (children > 1 || (child->border_style != BS_1PIXEL && child->border_style != BS_NONE)) {
if (children > 1 || (child->border_style != BS_PIXEL && child->border_style != BS_NONE)) {
child->rect.y += deco_height;
child->rect.height -= deco_height;
child->deco_rect.height = deco_height;
} else {
child->deco_rect.height = (child->border_style == BS_1PIXEL ? 1 : 0);
child->deco_rect.height = (child->border_style == BS_PIXEL ? 1 : 0);
}
}

View File

@ -106,7 +106,7 @@ int resize_graphical_handler(Con *first, Con *second, orientation_t orientation,
const struct callback_params params = { orientation, output, helpwin, &new_position };
drag_pointer(NULL, event, grabwin, BORDER_TOP, resize_callback, &params);
drag_pointer(NULL, event, grabwin, BORDER_TOP, 0, resize_callback, &params);
xcb_destroy_window(conn, helpwin);
xcb_destroy_window(conn, grabwin);

View File

@ -39,11 +39,17 @@ void scratchpad_move(Con *con) {
return;
}
/* 1: Ensure the window is floating. From now on, we deal with the
* CT_FLOATING_CON. We use automatic == false because the user made the
* choice that this window should be a scratchpad (and floating). */
floating_enable(con, false);
con = con->parent;
/* 1: Ensure the window or any parent is floating. From now on, we deal
* with the CT_FLOATING_CON. We use automatic == false because the user
* made the choice that this window should be a scratchpad (and floating).
*/
Con *maybe_floating_con = con_inside_floating(con);
if (maybe_floating_con == NULL) {
floating_enable(con, false);
con = con->parent;
} else {
con = maybe_floating_con;
}
/* 2: Send the window to the __i3_scratch workspace, mainting its
* coordinates and not warping the pointer. */

View File

@ -58,7 +58,7 @@ static void startup_timeout(EV_P_ ev_timer *w, int revents) {
}
/*
* Some applications (such as Firefox) mark a startup sequence as completede
* Some applications (such as Firefox) mark a startup sequence as completed
* *before* they even map a window. Therefore, we cannot entirely delete the
* startup sequence once its marked as complete. Instead, well mark it for
* deletion in 30 seconds and use that chance to delete old sequences.
@ -68,15 +68,10 @@ static void startup_timeout(EV_P_ ev_timer *w, int revents) {
* the root window cursor.
*
*/
static int _delete_startup_sequence(struct Startup_Sequence *sequence) {
static int _prune_startup_sequences(void) {
time_t current_time = time(NULL);
int active_sequences = 0;
/* Mark the given sequence for deletion in 30 seconds. */
sequence->delete_at = current_time + 30;
DLOG("Will delete startup sequence %s at timestamp %ld\n",
sequence->id, sequence->delete_at);
/* Traverse the list and delete everything which was marked for deletion 30
* seconds ago or earlier. */
struct Startup_Sequence *current, *next;
@ -94,20 +89,35 @@ static int _delete_startup_sequence(struct Startup_Sequence *sequence) {
if (current_time <= current->delete_at)
continue;
DLOG("Deleting startup sequence %s, delete_at = %ld, current_time = %ld\n",
current->id, current->delete_at, current_time);
/* Unref the context, will be free()d */
sn_launcher_context_unref(current->context);
/* Delete our internal sequence */
TAILQ_REMOVE(&startup_sequences, current, sequences);
startup_sequence_delete(current);
}
return active_sequences;
}
/**
* Deletes a startup sequence, ignoring whether its timeout has elapsed.
* Useful when e.g. a window is moved between workspaces and its children
* shouldn't spawn on the original workspace.
*
*/
void startup_sequence_delete(struct Startup_Sequence *sequence) {
assert(sequence != NULL);
DLOG("Deleting startup sequence %s, delete_at = %ld, current_time = %ld\n",
sequence->id, sequence->delete_at, time(NULL));
/* Unref the context, will be free()d */
sn_launcher_context_unref(sequence->context);
/* Delete our internal sequence */
TAILQ_REMOVE(&startup_sequences, sequence, sequences);
free(sequence->id);
free(sequence->workspace);
FREE(sequence);
}
/*
* Starts the given application by passing it through a shell. We use double fork
* to avoid zombie processes. As the started applications parent exits (immediately),
@ -233,7 +243,13 @@ void startup_monitor_event(SnMonitorEvent *event, void *userdata) {
case SN_MONITOR_EVENT_COMPLETED:
DLOG("startup sequence %s completed\n", sn_startup_sequence_get_id(snsequence));
if (_delete_startup_sequence(sequence) == 0) {
/* Mark the given sequence for deletion in 30 seconds. */
time_t current_time = time(NULL);
sequence->delete_at = current_time + 30;
DLOG("Will delete startup sequence %s at timestamp %ld\n",
sequence->id, sequence->delete_at);
if (_prune_startup_sequences() == 0) {
DLOG("No more startup sequences running, changing root window cursor to default pointer.\n");
/* Change the pointer of the root window to indicate progress */
if (xcursor_supported)
@ -247,32 +263,44 @@ void startup_monitor_event(SnMonitorEvent *event, void *userdata) {
}
}
/*
* Checks if the given window belongs to a startup notification by checking if
* the _NET_STARTUP_ID property is set on the window (or on its leader, if its
* unset).
*
* If so, returns the workspace on which the startup was initiated.
* Returns NULL otherwise.
/**
* Gets the stored startup sequence for the _NET_STARTUP_ID of a given window.
*
*/
char *startup_workspace_for_window(i3Window *cwindow, xcb_get_property_reply_t *startup_id_reply) {
struct Startup_Sequence *startup_sequence_get(i3Window *cwindow,
xcb_get_property_reply_t *startup_id_reply, bool ignore_mapped_leader) {
/* The _NET_STARTUP_ID is only needed during this function, so we get it
* here and dont save it in the 'cwindow'. */
if (startup_id_reply == NULL || xcb_get_property_value_length(startup_id_reply) == 0) {
FREE(startup_id_reply);
DLOG("No _NET_STARTUP_ID set on this window\n");
DLOG("No _NET_STARTUP_ID set on window 0x%08x\n", cwindow->id);
if (cwindow->leader == XCB_NONE)
return NULL;
xcb_get_property_cookie_t cookie;
cookie = xcb_get_property(conn, false, cwindow->leader, A__NET_STARTUP_ID, XCB_GET_PROPERTY_TYPE_ANY, 0, 512);
/* This is a special case that causes the leader's startup sequence
* to only be returned if it has never been mapped, useful primarily
* when trying to delete a sequence.
*
* It's generally inappropriate to delete a leader's sequence when
* moving a child window, but if the leader has no container, it's
* likely permanently unmapped and the child is the "real" window. */
if (ignore_mapped_leader && con_by_window_id(cwindow->leader) != NULL) {
DLOG("Ignoring leader window 0x%08x\n", cwindow->leader);
return NULL;
}
DLOG("Checking leader window 0x%08x\n", cwindow->leader);
xcb_get_property_cookie_t cookie;
cookie = xcb_get_property(conn, false, cwindow->leader,
A__NET_STARTUP_ID, XCB_GET_PROPERTY_TYPE_ANY, 0, 512);
startup_id_reply = xcb_get_property_reply(conn, cookie, NULL);
if (startup_id_reply == NULL || xcb_get_property_value_length(startup_id_reply) == 0) {
DLOG("No _NET_STARTUP_ID set on the leader either\n");
if (startup_id_reply == NULL ||
xcb_get_property_value_length(startup_id_reply) == 0) {
FREE(startup_id_reply);
DLOG("No _NET_STARTUP_ID set on the leader either\n");
return NULL;
}
}
@ -304,5 +332,31 @@ char *startup_workspace_for_window(i3Window *cwindow, xcb_get_property_reply_t *
free(startup_id);
free(startup_id_reply);
return sequence;
}
/*
* Checks if the given window belongs to a startup notification by checking if
* the _NET_STARTUP_ID property is set on the window (or on its leader, if its
* unset).
*
* If so, returns the workspace on which the startup was initiated.
* Returns NULL otherwise.
*
*/
char *startup_workspace_for_window(i3Window *cwindow, xcb_get_property_reply_t *startup_id_reply) {
struct Startup_Sequence *sequence = startup_sequence_get(cwindow, startup_id_reply, false);
if (sequence == NULL)
return NULL;
/* If the startup sequence's time span has elapsed, delete it. */
time_t current_time = time(NULL);
if (sequence->delete_at > 0 && current_time > sequence->delete_at) {
DLOG("Deleting expired startup sequence %s\n", sequence->id);
startup_sequence_delete(sequence);
return NULL;
}
return sequence->workspace;
}

View File

@ -255,6 +255,15 @@ bool tree_close(Con *con, kill_window_t kill_window, bool dont_kill_parent, bool
x_con_kill(con);
con_detach(con);
/* disable urgency timer, if needed */
if (con->urgency_timer != NULL) {
DLOG("Removing urgency timer of con %p\n", con);
workspace_update_urgent_flag(con_get_workspace(con));
ev_timer_stop(main_loop, con->urgency_timer);
FREE(con->urgency_timer);
}
if (con->type != CT_FLOATING_CON) {
/* If the container is *not* floating, we might need to re-distribute
* percentage values for the resized containers. */
@ -347,6 +356,10 @@ void tree_split(Con *con, orientation_t orientation) {
con->layout = (orientation == HORIZ) ? L_SPLITH : L_SPLITV;
return;
}
else if (con->type == CT_FLOATING_CON) {
DLOG("Floating containers can't be split.\n");
return;
}
Con *parent = con->parent;
@ -373,7 +386,6 @@ void tree_split(Con *con, orientation_t orientation) {
TAILQ_REPLACE(&(parent->focus_head), con, new, focused);
new->parent = parent;
new->layout = (orientation == HORIZ) ? L_SPLITH : L_SPLITV;
new->split = true;
/* 3: swap 'percent' (resize factor) */
new->percent = con->percent;
@ -388,9 +400,16 @@ void tree_split(Con *con, orientation_t orientation) {
*
*/
bool level_up(void) {
/* Skip over floating containers and go directly to the grandparent
* (which should always be a workspace) */
if (focused->parent->type == CT_FLOATING_CON) {
con_focus(focused->parent->parent);
return true;
}
/* We can focus up to the workspace, but not any higher in the tree */
if ((focused->parent->type != CT_CON &&
focused->parent->type != CT_WORKSPACE) ||
focused->parent->type != CT_WORKSPACE) ||
focused->type == CT_WORKSPACE) {
ELOG("'focus parent': Focus is already on the workspace, cannot go higher than that.\n");
return false;
@ -407,9 +426,21 @@ bool level_down(void) {
/* Go down the focus stack of the current node */
Con *next = TAILQ_FIRST(&(focused->focus_head));
if (next == TAILQ_END(&(focused->focus_head))) {
printf("cannot go down\n");
DLOG("cannot go down\n");
return false;
}
else if (next->type == CT_FLOATING_CON) {
/* Floating cons shouldn't be directly focused; try immediately
* going to the grandchild of the focused con. */
Con *child = TAILQ_FIRST(&(next->focus_head));
if (child == TAILQ_END(&(next->focus_head))) {
DLOG("cannot go down\n");
return false;
}
else
next = TAILQ_FIRST(&(next->focus_head));
}
con_focus(next);
return true;
}
@ -454,9 +485,20 @@ void tree_render(void) {
*
*/
static bool _tree_next(Con *con, char way, orientation_t orientation, bool wrap) {
/* When dealing with fullscreen containers, it's necessary to go up to the
* workspace level, because 'focus $dir' will start at the con's real
* position in the tree, and it may not be possible to get to the edge
* normally due to fullscreen focusing restrictions. */
if (con->fullscreen_mode == CF_OUTPUT && con->type != CT_WORKSPACE)
con = con_get_workspace(con);
/* Stop recursing at workspaces after attempting to switch to next
* workspace if possible. */
if (con->type == CT_WORKSPACE) {
if (con_get_fullscreen_con(con, CF_GLOBAL)) {
DLOG("Cannot change workspace while in global fullscreen mode.\n");
return false;
}
Output *current_output = get_output_containing(con->rect.x, con->rect.y);
Output *next_output;
@ -477,7 +519,7 @@ static bool _tree_next(Con *con, char way, orientation_t orientation, bool wrap)
else
return false;
next_output = get_output_next(direction, current_output);
next_output = get_output_next(direction, current_output, CLOSEST_OUTPUT);
if (!next_output)
return false;
DLOG("Next output is %s\n", next_output->name);
@ -491,6 +533,13 @@ static bool _tree_next(Con *con, char way, orientation_t orientation, bool wrap)
return false;
workspace_show(workspace);
/* If a workspace has an active fullscreen container, one of its
* children should always be focused. The above workspace_show()
* should be adequate for that, so return. */
if (con_get_fullscreen_con(workspace, CF_OUTPUT))
return true;
Con *focus = con_descend_direction(workspace, direction);
if (focus) {
con_focus(focus);
@ -617,8 +666,8 @@ void tree_flatten(Con *con) {
/* The child must have a different orientation than the con but the same as
* the cons parent to be redundant */
if (!con->split ||
!child->split ||
if (!con_is_split(con) ||
!con_is_split(child) ||
con_orientation(con) == con_orientation(child) ||
con_orientation(child) != con_orientation(parent))
goto recurse;

View File

@ -11,6 +11,9 @@
*
*/
#include "all.h"
#include "yajl_utils.h"
#include <yajl/yajl_gen.h>
/* Stores a copy of the name of the last used workspace for the workspace
* back-and-forth switching. */
@ -312,12 +315,63 @@ static void workspace_reassign_sticky(Con *con) {
workspace_reassign_sticky(current);
}
/*
* Callback to reset the urgent flag of the given con to false. May be started by
* _workspace_show to avoid urgency hints being lost by switching to a workspace
* focusing the con.
*
*/
static void workspace_defer_update_urgent_hint_cb(EV_P_ ev_timer *w, int revents) {
Con *con = w->data;
DLOG("Resetting urgency flag of con %p by timer\n", con);
con->urgent = false;
con_update_parents_urgency(con);
workspace_update_urgent_flag(con_get_workspace(con));
tree_render();
ev_timer_stop(main_loop, con->urgency_timer);
FREE(con->urgency_timer);
}
/*
* For the "focus" event we send, along the usual "change" field, also the
* current and previous workspace, in "current" and "old" respectively.
*/
static void ipc_send_workspace_focus_event(Con *current, Con *old) {
setlocale(LC_NUMERIC, "C");
yajl_gen gen = ygenalloc();
y(map_open);
ystr("change");
ystr("focus");
ystr("current");
dump_node(gen, current, false);
ystr("old");
if (old == NULL)
y(null);
else
dump_node(gen, old, false);
y(map_close);
const unsigned char *payload;
ylength length;
y(get_buf, &payload, &length);
ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, (const char *)payload);
y(free);
setlocale(LC_NUMERIC, "");
}
static void _workspace_show(Con *workspace) {
Con *current, *old = NULL;
/* safe-guard against showing i3-internal workspaces like __i3_scratch */
if (workspace->name[0] == '_' && workspace->name[1] == '_')
if (con_is_internal(workspace))
return;
/* disable fullscreen for the other workspaces and get the workspace we are
@ -353,9 +407,49 @@ static void _workspace_show(Con *workspace) {
workspace_reassign_sticky(workspace);
LOG("switching to %p\n", workspace);
DLOG("switching to %p / %s\n", workspace, workspace->name);
Con *next = con_descend_focused(workspace);
/* Memorize current output */
Con *old_output = con_get_output(focused);
/* Display urgency hint for a while if the newly visible workspace would
* focus and thereby immediately destroy it */
if (next->urgent && (int)(config.workspace_urgency_timer * 1000) > 0) {
/* focus for now… */
con_focus(next);
/* … but immediately reset urgency flags; they will be set to false by
* the timer callback in case the container is focused at the time of
* its expiration */
focused->urgent = true;
workspace->urgent = true;
if (focused->urgency_timer == NULL) {
DLOG("Deferring reset of urgency flag of con %p on newly shown workspace %p\n",
focused, workspace);
focused->urgency_timer = scalloc(sizeof(struct ev_timer));
/* use a repeating timer to allow for easy resets */
ev_timer_init(focused->urgency_timer, workspace_defer_update_urgent_hint_cb,
config.workspace_urgency_timer, config.workspace_urgency_timer);
focused->urgency_timer->data = focused;
ev_timer_start(main_loop, focused->urgency_timer);
} else {
DLOG("Resetting urgency timer of con %p on workspace %p\n",
focused, workspace);
ev_timer_again(main_loop, focused->urgency_timer);
}
} else
con_focus(next);
ipc_send_workspace_focus_event(workspace, old);
DLOG("old = %p / %s\n", old, (old ? old->name : "(null)"));
/* Close old workspace if necessary. This must be done *after* doing
* urgency handling, because tree_close() will do a con_focus() on the next
* client, which will clear the urgency flag too early. Also, there is no
* way for con_focus() to know about when to clear urgency immediately and
* when to defer it. */
if (old && TAILQ_EMPTY(&(old->nodes_head)) && TAILQ_EMPTY(&(old->floating_head))) {
/* check if this workspace is currently visible */
if (!workspace_is_visible(old)) {
@ -365,10 +459,6 @@ static void _workspace_show(Con *workspace) {
}
}
/* Memorize current output */
Con *old_output = con_get_output(focused);
con_focus(next);
workspace->fullscreen_mode = CF_OUTPUT;
LOG("focused now = %p / %s\n", focused, focused->name);
@ -380,8 +470,6 @@ static void _workspace_show(Con *workspace) {
/* Update the EWMH hints */
ewmh_update_current_desktop();
ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"focus\"}");
}
/*
@ -398,8 +486,7 @@ void workspace_show(Con *workspace) {
*/
void workspace_show_by_name(const char *num) {
Con *workspace;
bool changed_num_workspaces;
workspace = workspace_get(num, &changed_num_workspaces);
workspace = workspace_get(num, NULL);
_workspace_show(workspace);
}
@ -419,7 +506,7 @@ Con* workspace_next(void) {
/* If currently a numbered workspace, find next numbered workspace. */
TAILQ_FOREACH(output, &(croot->nodes_head), nodes) {
/* Skip outputs starting with __, they are internal. */
if (output->name[0] == '_' && output->name[1] == '_')
if (con_is_internal(output))
continue;
NODES_FOREACH(output_get_content(output)) {
if (child->type != CT_WORKSPACE)
@ -440,7 +527,7 @@ Con* workspace_next(void) {
bool found_current = false;
TAILQ_FOREACH(output, &(croot->nodes_head), nodes) {
/* Skip outputs starting with __, they are internal. */
if (output->name[0] == '_' && output->name[1] == '_')
if (con_is_internal(output))
continue;
NODES_FOREACH(output_get_content(output)) {
if (child->type != CT_WORKSPACE)
@ -459,7 +546,7 @@ Con* workspace_next(void) {
if (!next) {
TAILQ_FOREACH(output, &(croot->nodes_head), nodes) {
/* Skip outputs starting with __, they are internal. */
if (output->name[0] == '_' && output->name[1] == '_')
if (con_is_internal(output))
continue;
NODES_FOREACH(output_get_content(output)) {
if (child->type != CT_WORKSPACE)
@ -491,7 +578,7 @@ Con* workspace_prev(void) {
/* If numbered workspace, find previous numbered workspace. */
TAILQ_FOREACH_REVERSE(output, &(croot->nodes_head), nodes_head, nodes) {
/* Skip outputs starting with __, they are internal. */
if (output->name[0] == '_' && output->name[1] == '_')
if (con_is_internal(output))
continue;
NODES_FOREACH_REVERSE(output_get_content(output)) {
if (child->type != CT_WORKSPACE || child->num == -1)
@ -510,7 +597,7 @@ Con* workspace_prev(void) {
bool found_current = false;
TAILQ_FOREACH_REVERSE(output, &(croot->nodes_head), nodes_head, nodes) {
/* Skip outputs starting with __, they are internal. */
if (output->name[0] == '_' && output->name[1] == '_')
if (con_is_internal(output))
continue;
NODES_FOREACH_REVERSE(output_get_content(output)) {
if (child->type != CT_WORKSPACE)
@ -529,7 +616,7 @@ Con* workspace_prev(void) {
if (!prev) {
TAILQ_FOREACH_REVERSE(output, &(croot->nodes_head), nodes_head, nodes) {
/* Skip outputs starting with __, they are internal. */
if (output->name[0] == '_' && output->name[1] == '_')
if (con_is_internal(output))
continue;
NODES_FOREACH_REVERSE(output_get_content(output)) {
if (child->type != CT_WORKSPACE)
@ -670,6 +757,22 @@ void workspace_back_and_forth(void) {
workspace_show_by_name(previous_workspace_name);
}
/*
* Returns the previously focused workspace con, or NULL if unavailable.
*
*/
Con *workspace_back_and_forth_get(void) {
if (!previous_workspace_name) {
DLOG("no previous workspace name set.");
return NULL;
}
Con *workspace;
workspace = workspace_get(previous_workspace_name, NULL);
return workspace;
}
static bool get_urgency_flag(Con *con) {
Con *child;
TAILQ_FOREACH(child, &(con->nodes_head), nodes)
@ -706,7 +809,6 @@ void ws_force_orientation(Con *ws, orientation_t orientation) {
/* 1: create a new split container */
Con *split = con_new(NULL, NULL);
split->parent = ws;
split->split = true;
/* 2: copy layout from workspace */
split->layout = ws->layout;
@ -758,7 +860,6 @@ Con *workspace_attach_to(Con *ws) {
/* 1: create a new split container */
Con *new = con_new(NULL, NULL);
new->parent = ws;
new->split = true;
/* 2: set the requested layout on the split con */
new->layout = ws->workspace_layout;
@ -769,3 +870,34 @@ Con *workspace_attach_to(Con *ws) {
return new;
}
/**
* Creates a new container and re-parents all of children from the given
* workspace into it.
*
* The container inherits the layout from the workspace.
*/
Con *workspace_encapsulate(Con *ws) {
if (TAILQ_EMPTY(&(ws->nodes_head))) {
ELOG("Workspace %p / %s has no children to encapsulate\n", ws, ws->name);
return NULL;
}
Con *new = con_new(NULL, NULL);
new->parent = ws;
new->layout = ws->layout;
DLOG("Moving children of workspace %p / %s into container %p\n",
ws, ws->name, new);
Con *child;
while (!TAILQ_EMPTY(&(ws->nodes_head))) {
child = TAILQ_FIRST(&(ws->nodes_head));
con_detach(child);
con_attach(child, new, true);
}
con_attach(new, ws, true);
return new;
}

33
src/x.c
View File

@ -432,7 +432,7 @@ void x_draw_decoration(Con *con) {
xcb_poly_fill_rectangle(conn, con->pixmap, con->pm_gc, 1, &bottomline);
}
/* 1pixel border needs an additional line at the top */
if (p->border_style == BS_1PIXEL && !(borders_to_hide & ADJ_UPPER_SCREEN_EDGE)) {
if (p->border_style == BS_PIXEL && !(borders_to_hide & ADJ_UPPER_SCREEN_EDGE)) {
xcb_rectangle_t topline = { br.x, 0, con->rect.width + br.width + br.x, br.y };
xcb_poly_fill_rectangle(conn, con->pixmap, con->pm_gc, 1, &topline);
}
@ -468,12 +468,20 @@ void x_draw_decoration(Con *con) {
/* 5: draw two unconnected lines in border color */
xcb_change_gc(conn, parent->pm_gc, XCB_GC_FOREGROUND, (uint32_t[]){ p->color->border });
Rect *dr = &(con->deco_rect);
int deco_diff_l = 2;
int deco_diff_r = 2;
if (parent->layout == L_TABBED) {
if (TAILQ_PREV(con, nodes_head, nodes) != NULL)
deco_diff_l = 0;
if (TAILQ_NEXT(con, nodes) != NULL)
deco_diff_r = 0;
}
xcb_segment_t segments[] = {
{ dr->x, dr->y,
dr->x + dr->width - 1, dr->y },
{ dr->x + 2, dr->y + dr->height - 1,
dr->x + dr->width - 3, dr->y + dr->height - 1 }
{ dr->x + deco_diff_l, dr->y + dr->height - 1,
dr->x - deco_diff_r + dr->width - 1, dr->y + dr->height - 1 }
};
xcb_poly_segment(conn, parent->pixmap, parent->pm_gc, 2, segments);
@ -482,16 +490,27 @@ void x_draw_decoration(Con *con) {
int text_offset_y = (con->deco_rect.height - config.font.height) / 2;
struct Window *win = con->window;
if (win == NULL || win->name == NULL) {
/* this is a non-leaf container, we need to make up a good description */
// TODO: use a good description instead of just "another container"
draw_text_ascii("another container",
if (win == NULL) {
/* we have a split container which gets a representation
* of its children as title
*/
char *title;
char *tree = con_get_tree_representation(con);
sasprintf(&title, "i3: %s", tree);
free(tree);
draw_text_ascii(title,
parent->pixmap, parent->pm_gc,
con->deco_rect.x + 2, con->deco_rect.y + text_offset_y,
con->deco_rect.width - 2);
free(title);
goto copy_pixmaps;
}
if (win->name == NULL)
goto copy_pixmaps;
int indent_level = 0,
indent_mult = 0;
Con *il_parent = parent;

View File

@ -34,10 +34,15 @@ static Cursor load_cursor(const char *name) {
}
void xcursor_load_cursors(void) {
cursors[XCURSOR_CURSOR_POINTER] = load_cursor("left_ptr");
cursors[XCURSOR_CURSOR_RESIZE_HORIZONTAL] = load_cursor("sb_h_double_arrow");
cursors[XCURSOR_CURSOR_RESIZE_VERTICAL] = load_cursor("sb_v_double_arrow");
cursors[XCURSOR_CURSOR_WATCH] = load_cursor("watch");
cursors[XCURSOR_CURSOR_POINTER] = load_cursor("left_ptr");
cursors[XCURSOR_CURSOR_RESIZE_HORIZONTAL] = load_cursor("sb_h_double_arrow");
cursors[XCURSOR_CURSOR_RESIZE_VERTICAL] = load_cursor("sb_v_double_arrow");
cursors[XCURSOR_CURSOR_WATCH] = load_cursor("watch");
cursors[XCURSOR_CURSOR_MOVE] = load_cursor("fleur");
cursors[XCURSOR_CURSOR_TOP_LEFT_CORNER] = load_cursor("top_left_corner");
cursors[XCURSOR_CURSOR_TOP_RIGHT_CORNER] = load_cursor("top_right_corner");
cursors[XCURSOR_CURSOR_BOTTOM_LEFT_CORNER] = load_cursor("bottom_left_corner");
cursors[XCURSOR_CURSOR_BOTTOM_RIGHT_CORNER] = load_cursor("bottom_right_corner");
}
/*