Merge branch 'next'
This commit is contained in:
@ -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; }
|
||||
|
@ -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; }
|
||||
|
47
src/click.c
47
src/click.c
@ -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;
|
||||
|
||||
/* don’t 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);
|
||||
|
259
src/commands.c
259
src/commands.c
@ -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
|
||||
|
@ -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
335
src/con.c
@ -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 don’t focus the con for i3 pseudo workspaces like __i3_scratch and
|
||||
* we don’t 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
|
||||
* don’t 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, it’s 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, it’s 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
|
||||
* don’t 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;
|
||||
}
|
||||
|
13
src/config.c
13
src/config.c
@ -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
564
src/config_directives.c
Normal 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
|
||||
* don’t 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, ¤t_bar, sizeof(Barconfig));
|
||||
TAILQ_INSERT_TAIL(&barconfigs, bar_config, configs);
|
||||
|
||||
memset(¤t_bar, '\0', sizeof(Barconfig));
|
||||
}
|
652
src/config_parser.c
Normal file
652
src/config_parser.c
Normal 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, let’s 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 there’s 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, let’s 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 there’s 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 don’t 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(¤t_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(¤t_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
|
136
src/floating.c
136
src/floating.c
@ -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, ¤t_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, ¶ms);
|
||||
drag_pointer(con, event, XCB_NONE, BORDER_TOP /* irrelevant */, cursor, resize_window_callback, ¶ms);
|
||||
}
|
||||
|
||||
/*
|
||||
@ -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, /* don’t 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);
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
20
src/i3.mk
20
src/i3.mk
@ -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
119
src/ipc.c
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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 */
|
||||
|
12
src/manage.c
12
src/manage.c
@ -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 /* don’t 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 /* don’t 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);
|
||||
}
|
||||
}
|
||||
|
||||
|
127
src/randr.c
127
src/randr.c
@ -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 doesn’t overlap the other one’s 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 doesn’t overlap the other one’s 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)
|
||||
|
54
src/render.c
54
src/render.c
@ -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 it’s 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);
|
||||
/* Don’t 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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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, ¶ms);
|
||||
drag_pointer(NULL, event, grabwin, BORDER_TOP, 0, resize_callback, ¶ms);
|
||||
|
||||
xcb_destroy_window(conn, helpwin);
|
||||
xcb_destroy_window(conn, grabwin);
|
||||
|
@ -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. */
|
||||
|
112
src/startup.c
112
src/startup.c
@ -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 it’s marked as complete. Instead, we’ll 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 application’s 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 it’s
|
||||
* 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 don’t 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 it’s
|
||||
* 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;
|
||||
}
|
||||
|
61
src/tree.c
61
src/tree.c
@ -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 con’s 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;
|
||||
|
168
src/workspace.c
168
src/workspace.c
@ -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
33
src/x.c
@ -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;
|
||||
|
@ -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");
|
||||
}
|
||||
|
||||
/*
|
||||
|
Reference in New Issue
Block a user