Merge branch 'next'

This commit is contained in:
Michael Stapelberg
2010-03-30 13:06:41 +02:00
88 changed files with 7702 additions and 2593 deletions

View File

@ -1,5 +1,7 @@
%option nounput
%option noinput
%option noyy_top_state
%option stack
%{
/*
@ -13,20 +15,59 @@
#include "data.h"
#include "config.h"
#include "log.h"
#include "util.h"
int yycolumn = 1;
#define YY_DECL int yylex (struct context *context)
#define YY_USER_ACTION { \
context->first_column = yycolumn; \
context->last_column = yycolumn+yyleng-1; \
yycolumn += yyleng; \
}
%}
%Start BIND_COND
%Start BINDSYM_COND
%Start BIND_AWS_COND
%Start BINDSYM_AWS_COND
%Start BIND_A2WS_COND
%Start ASSIGN_COND
%Start COLOR_COND
%Start SCREEN_COND
%Start SCREEN_AWS_COND
EOL (\r?\n)
%s BIND_COND
%s BINDSYM_COND
%s BIND_AWS_COND
%s BINDSYM_AWS_COND
%s BIND_A2WS_COND
%s ASSIGN_COND
%s COLOR_COND
%s OUTPUT_COND
%s OUTPUT_AWS_COND
%x BUFFER_LINE
%%
{
/* This is called when a new line is lexed. We only want the
* first line to match to go into state BUFFER_LINE */
if (context->line_number == 0) {
context->line_number = 1;
BEGIN(INITIAL);
yy_push_state(BUFFER_LINE);
}
}
<BUFFER_LINE>^[^\r\n]*/{EOL}? {
/* save whole line */
context->line_copy = strdup(yytext);
yyless(0);
yy_pop_state();
yy_set_bol(true);
yycolumn = 1;
}
<BIND_A2WS_COND>[^\n]+ { BEGIN(INITIAL); yylval.string = strdup(yytext); return STR; }
<OUTPUT_AWS_COND>[a-zA-Z0-9_-]+ { yylval.string = strdup(yytext); return OUTPUT; }
^[ \t]*#[^\n]* { return TOKCOMMENT; }
<COLOR_COND>[0-9a-fA-F]+ { yylval.string = strdup(yytext); return HEX; }
[0-9]+ { yylval.number = atoi(yytext); return NUMBER; }
@ -35,7 +76,14 @@ bind { BEGIN(BIND_COND); return TOKBIND; }
bindsym { BEGIN(BINDSYM_COND); return TOKBINDSYM; }
floating_modifier { BEGIN(INITIAL); return TOKFLOATING_MODIFIER; }
workspace { BEGIN(INITIAL); return TOKWORKSPACE; }
screen { BEGIN(SCREEN_COND); return TOKSCREEN; }
output { BEGIN(OUTPUT_COND); return TOKOUTPUT; }
screen {
/* for compatibility until v3.φ */
ELOG("Assignments to screens are DEPRECATED and will not work. " \
"Please replace them with assignments to outputs.\n");
BEGIN(OUTPUT_COND);
return TOKOUTPUT;
}
terminal { BEGIN(BIND_AWS_COND); return TOKTERMINAL; }
font { BEGIN(BIND_AWS_COND); return TOKFONT; }
assign { BEGIN(ASSIGN_COND); return TOKASSIGN; }
@ -44,6 +92,8 @@ ipc-socket { BEGIN(BIND_AWS_COND); return TOKIPCSOCKET; }
ipc_socket { BEGIN(BIND_AWS_COND); return TOKIPCSOCKET; }
new_container { return TOKNEWCONTAINER; }
new_window { return TOKNEWWINDOW; }
focus_follows_mouse { return TOKFOCUSFOLLOWSMOUSE; }
workspace_bar { return TOKWORKSPACEBAR; }
default { yylval.number = MODE_DEFAULT; return TOKCONTAINERMODE; }
stacking { yylval.number = MODE_STACK; return TOKCONTAINERMODE; }
tabbed { yylval.number = MODE_TABBED; return TOKCONTAINERMODE; }
@ -65,16 +115,21 @@ Mod4 { yylval.number = BIND_MOD4; return MODIFIER; }
Mod5 { yylval.number = BIND_MOD5; return MODIFIER; }
Mode_switch { yylval.number = BIND_MODE_SWITCH; return MODIFIER; }
control { return TOKCONTROL; }
ctrl { return TOKCONTROL; }
shift { return TOKSHIFT; }
→ { return TOKARROW; }
\n /* ignore end of line */;
<SCREEN_AWS_COND>x { return (int)yytext[0]; }
{EOL} {
FREE(context->line_copy);
context->line_number++;
BEGIN(INITIAL);
yy_push_state(BUFFER_LINE);
}
<BIND_COND>[ \t]+ { BEGIN(BIND_AWS_COND); return WHITESPACE; }
<BINDSYM_COND>[ \t]+ { BEGIN(BINDSYM_AWS_COND); return WHITESPACE; }
<BIND_AWS_COND>[ \t]+ { BEGIN(BIND_A2WS_COND); return WHITESPACE; }
<BINDSYM_AWS_COND>[ \t]+ { BEGIN(BIND_A2WS_COND); return WHITESPACE; }
<SCREEN_COND>[ \t]+ { BEGIN(SCREEN_AWS_COND); return WHITESPACE; }
<SCREEN_AWS_COND>[ \t]+ { BEGIN(BIND_A2WS_COND); return WHITESPACE; }
<OUTPUT_COND>[ \t]+ { BEGIN(OUTPUT_AWS_COND); return WHITESPACE; }
<OUTPUT_AWS_COND>[ \t]+ { BEGIN(BIND_A2WS_COND); return WHITESPACE; }
[ \t]+ { return WHITESPACE; }
\"[^\"]+\" {
/* if ASSIGN_COND then */
@ -89,4 +144,11 @@ shift { return TOKSHIFT; }
<BINDSYM_AWS_COND>[a-zA-Z0-9_]+ { yylval.string = strdup(yytext); return WORD; }
[a-zA-Z]+ { yylval.string = strdup(yytext); return WORD; }
. { return (int)yytext[0]; }
<<EOF>> {
while (yy_start_stack_ptr > 0)
yy_pop_state();
yyterminate();
}
%%

View File

@ -21,20 +21,35 @@
#include "table.h"
#include "workspace.h"
#include "xcb.h"
#include "log.h"
typedef struct yy_buffer_state *YY_BUFFER_STATE;
extern int yylex(void);
extern int yylex(struct context *context);
extern int yyparse(void);
extern FILE *yyin;
YY_BUFFER_STATE yy_scan_string(const char *);
static struct bindings_head *current_bindings;
static struct context *context;
int yydebug = 1;
/* We dont need yydebug for now, as we got decent error messages using
* yyerror(). Should you ever want to extend the parser, it might be handy
* to just comment it in again, so it stays here. */
//int yydebug = 1;
void yyerror(const char *str) {
fprintf(stderr,"error: %s\n",str);
void yyerror(const char *error_message) {
ELOG("\n");
ELOG("CONFIG: %s\n", error_message);
ELOG("CONFIG: in file \"%s\", line %d:\n",
context->filename, context->line_number);
ELOG("CONFIG: %s\n", context->line_copy);
ELOG("CONFIG: ");
for (int c = 1; c <= context->last_column; c++)
if (c >= context->first_column)
printf("^");
else printf(" ");
printf("\n");
ELOG("\n");
}
int yywrap() {
@ -55,7 +70,7 @@ void parse_file(const char *f) {
if (fstat(fd, &stbuf) == -1)
die("Could not fstat file: %s\n", strerror(errno));
buf = smalloc(stbuf.st_size * sizeof(char));
buf = scalloc((stbuf.st_size + 1) * sizeof(char));
while (read_bytes < stbuf.st_size) {
if ((ret = read(fd, buf + read_bytes, (stbuf.st_size - read_bytes))) < 0)
die("Could not read(): %s\n", strerror(errno));
@ -95,7 +110,7 @@ void parse_file(const char *f) {
new->key = sstrdup(v_key);
new->value = sstrdup(v_value);
SLIST_INSERT_HEAD(&variables, new, variables);
LOG("Got new variable %s = %s\n", v_key, v_value);
DLOG("Got new variable %s = %s\n", v_key, v_value);
continue;
}
}
@ -149,18 +164,33 @@ void parse_file(const char *f) {
yy_scan_string(new);
context = scalloc(sizeof(struct context));
context->filename = f;
if (yyparse() != 0) {
fprintf(stderr, "Could not parse configfile\n");
exit(1);
}
FREE(context->line_copy);
free(context);
free(new);
free(buf);
while (!SLIST_EMPTY(&variables)) {
current = SLIST_FIRST(&variables);
FREE(current->key);
FREE(current->value);
SLIST_REMOVE_HEAD(&variables, variables);
FREE(current);
}
}
%}
%expect 1
%error-verbose
%lex-param { struct context *context }
%union {
int number;
@ -170,40 +200,44 @@ void parse_file(const char *f) {
struct Binding *binding;
}
%token <number>NUMBER
%token <string>WORD
%token <string>STR
%token <string>STR_NG
%token <string>HEX
%token <number>NUMBER "<number>"
%token <string>WORD "<word>"
%token <string>STR "<string>"
%token <string>STR_NG "<string (non-greedy)>"
%token <string>HEX "<hex>"
%token <string>OUTPUT "<RandR output>"
%token TOKBIND
%token TOKTERMINAL
%token TOKCOMMENT
%token TOKFONT
%token TOKBINDSYM
%token MODIFIER
%token TOKCONTROL
%token TOKSHIFT
%token WHITESPACE
%token TOKFLOATING_MODIFIER
%token QUOTEDSTRING
%token TOKWORKSPACE
%token TOKSCREEN
%token TOKASSIGN
%token TOKCOMMENT "<comment>"
%token TOKFONT "font"
%token TOKBINDSYM "bindsym"
%token MODIFIER "<modifier>"
%token TOKCONTROL "control"
%token TOKSHIFT "shift"
%token WHITESPACE "<whitespace>"
%token TOKFLOATING_MODIFIER "floating_modifier"
%token QUOTEDSTRING "<quoted string>"
%token TOKWORKSPACE "workspace"
%token TOKOUTPUT "output"
%token TOKASSIGN "assign"
%token TOKSET
%token TOKIPCSOCKET
%token TOKEXEC
%token TOKIPCSOCKET "ipc_socket"
%token TOKEXEC "exec"
%token TOKCOLOR
%token TOKARROW
%token TOKMODE
%token TOKNEWCONTAINER
%token TOKNEWWINDOW
%token TOKCONTAINERMODE
%token TOKSTACKLIMIT
%token TOKARROW "→"
%token TOKMODE "mode"
%token TOKNEWCONTAINER "new_container"
%token TOKNEWWINDOW "new_window"
%token TOKFOCUSFOLLOWSMOUSE "focus_follows_mouse"
%token TOKWORKSPACEBAR "workspace_bar"
%token TOKCONTAINERMODE "default/stacking/tabbed"
%token TOKSTACKLIMIT "stack-limit"
%%
lines: /* empty */
| lines WHITESPACE line
| lines error
| lines line
;
@ -213,6 +247,8 @@ line:
| floating_modifier
| new_container
| new_window
| focus_follows_mouse
| workspace_bar
| workspace
| assign
| ipcsocket
@ -251,7 +287,7 @@ bind:
new->keycode = $<number>2;
new->mods = $<number>1;
new->command = sstrdup($<string>4);
new->command = $<string>4;
$<binding>$ = new;
}
@ -263,9 +299,9 @@ bindsym:
printf("\tFound symbolic mod%d with key %s and command %s\n", $<number>1, $<string>2, $<string>4);
Binding *new = scalloc(sizeof(Binding));
new->symbol = sstrdup($<string>2);
new->symbol = $<string>2;
new->mods = $<number>1;
new->command = sstrdup($<string>4);
new->command = $<string>4;
$<binding>$ = new;
}
@ -295,7 +331,7 @@ mode:
}
struct Mode *mode = scalloc(sizeof(struct Mode));
mode->name = strdup($<string>3);
mode->name = $<string>3;
mode->bindings = current_bindings;
current_bindings = NULL;
SLIST_INSERT_HEAD(&modes, mode, modes);
@ -325,7 +361,7 @@ modeline:
floating_modifier:
TOKFLOATING_MODIFIER WHITESPACE binding_modifiers
{
LOG("floating modifier = %d\n", $<number>3);
DLOG("floating modifier = %d\n", $<number>3);
config.floating_modifier = $<number>3;
}
;
@ -333,7 +369,7 @@ floating_modifier:
new_container:
TOKNEWCONTAINER WHITESPACE TOKCONTAINERMODE
{
LOG("new containers will be in mode %d\n", $<number>3);
DLOG("new containers will be in mode %d\n", $<number>3);
config.container_mode = $<number>3;
/* We also need to change the layout of the already existing
@ -355,7 +391,7 @@ new_container:
}
| TOKNEWCONTAINER WHITESPACE TOKSTACKLIMIT WHITESPACE TOKSTACKLIMIT WHITESPACE NUMBER
{
LOG("stack-limit %d with val %d\n", $<number>5, $<number>7);
DLOG("stack-limit %d with val %d\n", $<number>5, $<number>7);
config.container_stack_limit = $<number>5;
config.container_stack_limit_value = $<number>7;
@ -374,32 +410,69 @@ new_container:
new_window:
TOKNEWWINDOW WHITESPACE WORD
{
LOG("new windows should start in mode %s\n", $<string>3);
config.default_border = strdup($<string>3);
DLOG("new windows should start in mode %s\n", $<string>3);
config.default_border = sstrdup($<string>3);
}
;
bool:
NUMBER
{
$<number>$ = ($<number>1 == 1);
}
| WORD
{
DLOG("checking word \"%s\"\n", $<string>1);
$<number>$ = (strcasecmp($<string>1, "yes") == 0 ||
strcasecmp($<string>1, "true") == 0 ||
strcasecmp($<string>1, "on") == 0 ||
strcasecmp($<string>1, "enable") == 0 ||
strcasecmp($<string>1, "active") == 0);
}
;
focus_follows_mouse:
TOKFOCUSFOLLOWSMOUSE WHITESPACE bool
{
DLOG("focus follows mouse = %d\n", $<number>3);
config.disable_focus_follows_mouse = !($<number>3);
}
;
workspace_bar:
TOKWORKSPACEBAR WHITESPACE bool
{
DLOG("workspace bar = %d\n", $<number>3);
config.disable_workspace_bar = !($<number>3);
}
;
workspace:
TOKWORKSPACE WHITESPACE NUMBER WHITESPACE TOKSCREEN WHITESPACE screen optional_workspace_name
TOKWORKSPACE WHITESPACE NUMBER WHITESPACE TOKOUTPUT WHITESPACE OUTPUT optional_workspace_name
{
int ws_num = $<number>3;
if (ws_num < 1) {
LOG("Invalid workspace assignment, workspace number %d out of range\n", ws_num);
DLOG("Invalid workspace assignment, workspace number %d out of range\n", ws_num);
} else {
Workspace *ws = workspace_get(ws_num - 1);
ws->preferred_screen = sstrdup($<string>7);
if ($<string>8 != NULL)
ws->preferred_output = $<string>7;
if ($<string>8 != NULL) {
workspace_set_name(ws, $<string>8);
free($<string>8);
}
}
}
| TOKWORKSPACE WHITESPACE NUMBER WHITESPACE workspace_name
{
int ws_num = $<number>3;
if (ws_num < 1) {
LOG("Invalid workspace assignment, workspace number %d out of range\n", ws_num);
DLOG("Invalid workspace assignment, workspace number %d out of range\n", ws_num);
} else {
if ($<string>5 != NULL)
DLOG("workspace name to: %s\n", $<string>5);
if ($<string>5 != NULL) {
workspace_set_name(workspace_get(ws_num - 1), $<string>5);
free($<string>5);
}
}
}
;
@ -415,13 +488,6 @@ workspace_name:
| WORD { $<string>$ = $<string>1; }
;
screen:
NUMBER { asprintf(&$<string>$, "%d", $<number>1); }
| NUMBER 'x' { asprintf(&$<string>$, "%d", $<number>1); }
| NUMBER 'x' NUMBER { asprintf(&$<string>$, "%dx%d", $<number>1, $<number>3); }
| 'x' NUMBER { asprintf(&$<string>$, "x%d", $<number>2); }
;
assign:
TOKASSIGN WHITESPACE window_class WHITESPACE optional_arrow assign_target
{
@ -430,7 +496,7 @@ assign:
struct Assignment *new = $<assignment>6;
printf(" to %d\n", new->workspace);
printf(" floating = %d\n", new->floating);
new->windowclass_title = strdup($<string>3);
new->windowclass_title = $<string>3;
TAILQ_INSERT_TAIL(&assignments, new, assignments);
}
;
@ -471,7 +537,7 @@ optional_arrow:
ipcsocket:
TOKIPCSOCKET WHITESPACE STR
{
config.ipc_socket_path = sstrdup($<string>3);
config.ipc_socket_path = $<string>3;
}
;
@ -479,7 +545,7 @@ exec:
TOKEXEC WHITESPACE STR
{
struct Autostart *new = smalloc(sizeof(struct Autostart));
new->command = sstrdup($<string>3);
new->command = $<string>3;
TAILQ_INSERT_TAIL(&autostarts, new, autostarts);
}
;
@ -487,15 +553,15 @@ exec:
terminal:
TOKTERMINAL WHITESPACE STR
{
config.terminal = sstrdup($<string>3);
printf("terminal %s\n", config.terminal);
ELOG("The terminal option is DEPRECATED and has no effect. "
"Please remove it from your configuration file.\n");
}
;
font:
TOKFONT WHITESPACE STR
{
config.font = sstrdup($<string>3);
config.font = $<string>3;
printf("font %s\n", config.font);
}
;

View File

@ -3,7 +3,7 @@
*
* i3 - an improved dynamic tiling window manager
*
* © 2009 Michael Stapelberg and contributors
* © 2009-2010 Michael Stapelberg and contributors
*
* See file LICENSE for license information.
*
@ -36,6 +36,8 @@
#include "commands.h"
#include "floating.h"
#include "resize.h"
#include "log.h"
#include "randr.h"
static struct Stack_Window *get_stack_window(xcb_window_t window_id) {
struct Stack_Window *current;
@ -97,18 +99,18 @@ static bool button_press_stackwin(xcb_connection_t *conn, xcb_button_press_event
int wrap = ceil((float)num_clients / container->stack_limit_value);
int clicked_column = (event->event_x / (stack_win->rect.width / container->stack_limit_value));
int clicked_row = (event->event_y / decoration_height);
LOG("clicked on column %d, row %d\n", clicked_column, clicked_row);
DLOG("clicked on column %d, row %d\n", clicked_column, clicked_row);
destination = (wrap * clicked_column) + clicked_row;
} else {
int width = (stack_win->rect.width / ceil((float)num_clients / container->stack_limit_value));
int clicked_column = (event->event_x / width);
int clicked_row = (event->event_y / decoration_height);
LOG("clicked on column %d, row %d\n", clicked_column, clicked_row);
DLOG("clicked on column %d, row %d\n", clicked_column, clicked_row);
destination = (container->stack_limit_value * clicked_column) + clicked_row;
}
}
LOG("Click on stack_win for client %d\n", destination);
DLOG("Click on stack_win for client %d\n", destination);
CIRCLEQ_FOREACH(client, &(stack_win->container->clients), clients)
if (c++ == destination) {
set_focus(conn, client, true);
@ -124,26 +126,26 @@ static bool button_press_stackwin(xcb_connection_t *conn, xcb_button_press_event
*
*/
static bool button_press_bar(xcb_connection_t *conn, xcb_button_press_event_t *event) {
i3Screen *screen;
TAILQ_FOREACH(screen, virtual_screens, screens) {
if (screen->bar != event->event)
Output *output;
TAILQ_FOREACH(output, &outputs, outputs) {
if (output->bar != event->event)
continue;
LOG("Click on a bar\n");
DLOG("Click on a bar\n");
/* Check if the button was one of button4 or button5 (scroll up / scroll down) */
if (event->detail == XCB_BUTTON_INDEX_4 || event->detail == XCB_BUTTON_INDEX_5) {
Workspace *ws = c_ws;
if (event->detail == XCB_BUTTON_INDEX_5) {
while ((ws = TAILQ_NEXT(ws, workspaces)) != TAILQ_END(workspaces_head)) {
if (ws->screen == screen) {
if (ws->output == output) {
workspace_show(conn, ws->num + 1);
return true;
}
}
} else {
while ((ws = TAILQ_PREV(ws, workspaces_head, workspaces)) != TAILQ_END(workspaces)) {
if (ws->screen == screen) {
if (ws->output == output) {
workspace_show(conn, ws->num + 1);
return true;
}
@ -152,13 +154,13 @@ static bool button_press_bar(xcb_connection_t *conn, xcb_button_press_event_t *e
return true;
}
int drawn = 0;
/* Because workspaces can be on different screens, we need to loop
through all of them and decide to count it based on its ->screen */
/* Because workspaces can be on different outputs, we need to loop
through all of them and decide to count it based on its ->output */
Workspace *ws;
TAILQ_FOREACH(ws, workspaces, workspaces) {
if (ws->screen != screen)
if (ws->output != output)
continue;
LOG("Checking if click was on workspace %d with drawn = %d, tw = %d\n",
DLOG("Checking if click was on workspace %d with drawn = %d, tw = %d\n",
ws->num, drawn, ws->text_width);
if (event->event_x > (drawn + 1) &&
event->event_x <= (drawn + 1 + ws->text_width + 5 + 5)) {
@ -201,7 +203,7 @@ static bool floating_mod_on_tiled_client(xcb_connection_t *conn, Client *client,
Workspace *ws = con->workspace;
int first = 0, second = 0;
LOG("click was %d px to the right, %d px to the left, %d px to top, %d px to bottom\n",
DLOG("click was %d px to the right, %d px to the left, %d px to top, %d px to bottom\n",
to_right, to_left, to_top, to_bottom);
if (to_right < to_left &&
@ -209,7 +211,7 @@ static bool floating_mod_on_tiled_client(xcb_connection_t *conn, Client *client,
to_right < to_bottom) {
/* …right border */
first = con->col + (con->colspan - 1);
LOG("column %d\n", first);
DLOG("column %d\n", first);
if (!cell_exists(ws, first, con->row) ||
(first == (ws->cols-1)))
@ -251,7 +253,7 @@ static bool floating_mod_on_tiled_client(xcb_connection_t *conn, Client *client,
}
int handle_button_press(void *ignored, xcb_connection_t *conn, xcb_button_press_event_t *event) {
LOG("Button %d pressed\n", event->state);
DLOG("Button %d pressed\n", event->state);
/* This was either a focus for a clients parent (= titlebar)… */
Client *client = table_get(&by_child, event->event);
bool border_click = false;
@ -263,23 +265,28 @@ int handle_button_press(void *ignored, xcb_connection_t *conn, xcb_button_press_
* to move around the client if it was floating. if not, we just process
* as usual. */
if (config.floating_modifier != 0 &&
(event->state & config.floating_modifier) != 0) {
(event->state & config.floating_modifier) == config.floating_modifier) {
if (client == NULL) {
LOG("Not handling, floating_modifier was pressed and no client found\n");
DLOG("Not handling, floating_modifier was pressed and no client found\n");
xcb_allow_events(conn, XCB_ALLOW_REPLAY_POINTER, event->time);
xcb_flush(conn);
return 1;
}
if (client->fullscreen) {
LOG("Not handling, client is in fullscreen mode\n");
DLOG("Not handling, client is in fullscreen mode\n");
xcb_allow_events(conn, XCB_ALLOW_REPLAY_POINTER, event->time);
xcb_flush(conn);
return 1;
}
if (client_is_floating(client)) {
LOG("button %d pressed\n", event->detail);
DLOG("button %d pressed\n", event->detail);
if (event->detail == 1) {
LOG("left mouse button, dragging\n");
DLOG("left mouse button, dragging\n");
floating_drag_window(conn, client, event);
} else if (event->detail == 3) {
LOG("right mouse button\n");
floating_resize_window(conn, client, event);
bool proportional = (event->state & BIND_SHIFT);
DLOG("right mouse button\n");
floating_resize_window(conn, client, proportional, event);
}
return 1;
}
@ -301,7 +308,7 @@ int handle_button_press(void *ignored, xcb_connection_t *conn, xcb_button_press_
if (button_press_bar(conn, event))
return 1;
LOG("Could not handle this button press\n");
DLOG("Could not handle this button press\n");
return 1;
}
@ -309,19 +316,19 @@ int handle_button_press(void *ignored, xcb_connection_t *conn, xcb_button_press_
set_focus(conn, client, true);
/* Lets see if this was on the borders (= resize). If not, were done */
LOG("press button on x=%d, y=%d\n", event->event_x, event->event_y);
DLOG("press button on x=%d, y=%d\n", event->event_x, event->event_y);
resize_orientation_t orientation = O_VERTICAL;
Container *con = client->container;
int first, second;
if (client->dock) {
LOG("dock. done.\n");
DLOG("dock. done.\n");
xcb_allow_events(conn, XCB_ALLOW_REPLAY_POINTER, event->time);
xcb_flush(conn);
return 1;
}
LOG("event->event_x = %d, client->rect.width = %d\n", event->event_x, client->rect.width);
DLOG("event->event_x = %d, client->rect.width = %d\n", event->event_x, client->rect.width);
/* Some clients (xfontsel for example) seem to pass clicks on their
* window to the parent window, thus we receive an event here which in
@ -331,12 +338,12 @@ int handle_button_press(void *ignored, xcb_connection_t *conn, xcb_button_press_
event->event_x <= (client->child_rect.x + client->child_rect.width) &&
event->event_y >= client->child_rect.y &&
event->event_y <= (client->child_rect.y + client->child_rect.height)) {
LOG("Fixing border_click = false because of click in child\n");
DLOG("Fixing border_click = false because of click in child\n");
border_click = false;
}
if (!border_click) {
LOG("client. done.\n");
DLOG("client. done.\n");
xcb_allow_events(conn, XCB_ALLOW_REPLAY_POINTER, event->time);
/* Floating clients should be raised on click */
if (client_is_floating(client))
@ -348,7 +355,7 @@ int handle_button_press(void *ignored, xcb_connection_t *conn, xcb_button_press_
/* Dont handle events inside the titlebar, only borders are interesting */
i3Font *font = load_font(conn, config.font);
if (event->event_y >= 2 && event->event_y <= (font->height + 2 + 2)) {
LOG("click on titlebar\n");
DLOG("click on titlebar\n");
/* Floating clients can be dragged by grabbing their titlebar */
if (client_is_floating(client)) {
@ -392,7 +399,7 @@ int handle_button_press(void *ignored, xcb_connection_t *conn, xcb_button_press_
} else if (event->event_x > 2) {
/* …right border */
first = con->col + (con->colspan - 1);
LOG("column %d\n", first);
DLOG("column %d\n", first);
if (!cell_exists(ws, first, con->row) ||
(first == (ws->cols-1)))

View File

@ -3,7 +3,7 @@
*
* i3 - an improved dynamic tiling window manager
*
* © 2009 Michael Stapelberg and contributors
* © 2009-2010 Michael Stapelberg and contributors
*
* See file LICENSE for license information.
*
@ -13,6 +13,7 @@
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <limits.h>
#include <xcb/xcb.h>
#include <xcb/xcb_icccm.h>
@ -26,6 +27,8 @@
#include "client.h"
#include "table.h"
#include "workspace.h"
#include "config.h"
#include "log.h"
/*
* Removes the given client from the container, either because it will be inserted into another
@ -43,7 +46,7 @@ void client_remove_from_container(xcb_connection_t *conn, Client *client, Contai
if (CIRCLEQ_EMPTY(&(container->clients)) &&
(container->mode == MODE_STACK ||
container->mode == MODE_TABBED)) {
LOG("Unmapping stack window\n");
DLOG("Unmapping stack window\n");
struct Stack_Window *stack_win = &(container->stack_win);
stack_win->rect.height = 0;
xcb_unmap_window(conn, stack_win->window);
@ -150,44 +153,82 @@ bool client_matches_class_name(Client *client, char *to_class, char *to_title,
* and when moving a fullscreen client to another screen.
*
*/
void client_enter_fullscreen(xcb_connection_t *conn, Client *client) {
Workspace *workspace = client->workspace;
void client_enter_fullscreen(xcb_connection_t *conn, Client *client, bool global) {
Workspace *workspace;
Output *output;
Rect r;
if (workspace->fullscreen_client != NULL) {
LOG("Not entering fullscreen mode, there already is a fullscreen client.\n");
return;
if (global) {
TAILQ_FOREACH(output, &outputs, outputs) {
if (!output->active)
continue;
if (output->current_workspace->fullscreen_client == NULL)
continue;
LOG("Not entering global fullscreen mode, there already "
"is a fullscreen client on output %s.\n", output->name);
return;
}
r = (Rect) { UINT_MAX, UINT_MAX, 0,0 };
Output *output;
/* Set fullscreen_client for each active workspace.
* Expand the rectangle to contain all outputs. */
TAILQ_FOREACH(output, &outputs, outputs) {
if (!output->active)
continue;
output->current_workspace->fullscreen_client = client;
/* Temporarily abuse width/heigth as coordinates of the lower right corner */
if (r.x > output->rect.x)
r.x = output->rect.x;
if (r.y > output->rect.y)
r.y = output->rect.y;
if (r.x + r.width < output->rect.x + output->rect.width)
r.width = output->rect.x + output->rect.width;
if (r.y + r.height < output->rect.y + output->rect.height)
r.height = output->rect.y + output->rect.height;
}
/* Putting them back to their original meaning */
r.height -= r.x;
r.width -= r.y;
LOG("Entering global fullscreen mode...\n");
} else {
workspace = client->workspace;
if (workspace->fullscreen_client != NULL && workspace->fullscreen_client != client) {
LOG("Not entering fullscreen mode, there already is a fullscreen client.\n");
return;
}
workspace->fullscreen_client = client;
r = workspace->rect;
LOG("Entering fullscreen mode...\n");
}
client->fullscreen = true;
workspace->fullscreen_client = client;
LOG("Entering fullscreen mode...\n");
/* We just entered fullscreen mode, lets configure the window */
uint32_t mask = XCB_CONFIG_WINDOW_X |
XCB_CONFIG_WINDOW_Y |
XCB_CONFIG_WINDOW_WIDTH |
XCB_CONFIG_WINDOW_HEIGHT;
uint32_t values[4] = {workspace->rect.x,
workspace->rect.y,
workspace->rect.width,
workspace->rect.height};
DLOG("child itself will be at %dx%d with size %dx%d\n",
r.x, r.y, r.width, r.height);
LOG("child itself will be at %dx%d with size %dx%d\n",
values[0], values[1], values[2], values[3]);
xcb_configure_window(conn, client->frame, mask, values);
xcb_set_window_rect(conn, client->frame, r);
/* Childs coordinates are relative to the parent (=frame) */
values[0] = 0;
values[1] = 0;
xcb_configure_window(conn, client->child, mask, values);
r.x = 0;
r.y = 0;
xcb_set_window_rect(conn, client->child, r);
/* Raise the window */
values[0] = XCB_STACK_MODE_ABOVE;
uint32_t values[] = { XCB_STACK_MODE_ABOVE };
xcb_configure_window(conn, client->frame, XCB_CONFIG_WINDOW_STACK_MODE, values);
Rect child_rect = workspace->rect;
child_rect.x = child_rect.y = 0;
fake_configure_notify(conn, child_rect, client->child);
fake_configure_notify(conn, r, client->child);
xcb_flush(conn);
}
@ -234,34 +275,26 @@ void client_toggle_fullscreen(xcb_connection_t *conn, Client *client) {
/* dock clients cannot enter fullscreen mode */
assert(!client->dock);
Workspace *workspace = client->workspace;
if (!client->fullscreen) {
client_enter_fullscreen(conn, client, false);
} else {
client_leave_fullscreen(conn, client);
}
}
/*
* Like client_toggle_fullscreen(), but putting it in global fullscreen-mode.
*
*/
void client_toggle_fullscreen_global(xcb_connection_t *conn, Client *client) {
/* dock clients cannot enter fullscreen mode */
assert(!client->dock);
if (!client->fullscreen) {
client_enter_fullscreen(conn, client);
return;
}
LOG("leaving fullscreen mode\n");
client->fullscreen = false;
workspace->fullscreen_client = NULL;
if (client_is_floating(client)) {
/* For floating clients its enough if we just reconfigure that window (in fact,
* re-rendering the layout will not update the client.) */
reposition_client(conn, client);
resize_client(conn, client);
/* redecorate_window flushes */
redecorate_window(conn, client);
client_enter_fullscreen(conn, client, true);
} else {
client_set_below_floating(conn, client);
/* Because the coordinates of the window havent changed, it would not be
re-configured if we dont set the following flag */
client->force_reconfigure = true;
/* We left fullscreen mode, redraw the whole layout to ensure enternotify events are disabled */
render_layout(conn);
client_leave_fullscreen(conn, client);
}
xcb_flush(conn);
}
/*
@ -277,14 +310,14 @@ void client_set_below_floating(xcb_connection_t *conn, Client *client) {
if (first_floating == TAILQ_END(&(ws->floating_clients)))
return;
LOG("Setting below floating\n");
DLOG("Setting below floating\n");
uint32_t values[] = { first_floating->frame, XCB_STACK_MODE_BELOW };
xcb_configure_window(conn, client->frame, XCB_CONFIG_WINDOW_SIBLING | XCB_CONFIG_WINDOW_STACK_MODE, values);
if (client->workspace->fullscreen_client == NULL)
return;
LOG("(and below fullscreen)\n");
DLOG("(and below fullscreen)\n");
/* Ensure that the window is still below the fullscreen window */
values[0] = client->workspace->fullscreen_client->frame;
xcb_configure_window(conn, client->frame, XCB_CONFIG_WINDOW_SIBLING | XCB_CONFIG_WINDOW_STACK_MODE, values);
@ -407,3 +440,38 @@ void client_mark(xcb_connection_t *conn, Client *client, const char *mark) {
break;
}
}
/*
* Returns the minimum height of a specific window. The height is calculated
* by using 2 pixels (for the client window itself), possibly padding this to
* comply with the clients base_height and then adding the decoration height.
*
*/
uint32_t client_min_height(Client *client) {
uint32_t height = max(2, client->base_height);
i3Font *font = load_font(global_conn, config.font);
if (client->titlebar_position == TITLEBAR_OFF && client->borderless)
return height;
if (client->titlebar_position == TITLEBAR_OFF && !client->borderless)
return height + 2;
return height + font->height + 2 + 2;
}
/*
* See client_min_height.
*
*/
uint32_t client_min_width(Client *client) {
uint32_t width = max(2, client->base_width);
if (client->titlebar_position == TITLEBAR_OFF && client->borderless)
return width;
if (client->titlebar_position == TITLEBAR_OFF && !client->borderless)
return width + 2;
return width + 2 + 2;
}

View File

@ -3,7 +3,7 @@
*
* i3 - an improved dynamic tiling window manager
*
* © 2009 Michael Stapelberg and contributors
* © 2009-2010 Michael Stapelberg and contributors
*
* See file LICENSE for license information.
*
@ -22,7 +22,7 @@
#include "table.h"
#include "layout.h"
#include "i3.h"
#include "xinerama.h"
#include "randr.h"
#include "client.h"
#include "floating.h"
#include "xcb.h"
@ -30,6 +30,10 @@
#include "workspace.h"
#include "commands.h"
#include "resize.h"
#include "log.h"
#include "sighandler.h"
#include "manage.h"
#include "ipc.h"
bool focus_window_in_container(xcb_connection_t *conn, Container *container, direction_t direction) {
/* If this container is empty, were done */
@ -45,7 +49,7 @@ bool focus_window_in_container(xcb_connection_t *conn, Container *container, dir
else if (direction == D_DOWN) {
if ((candidate = CIRCLEQ_NEXT_OR_NULL(&(container->clients), container->currently_focused, clients)) == NULL)
candidate = CIRCLEQ_FIRST(&(container->clients));
} else LOG("Direction not implemented!\n");
} else ELOG("Direction not implemented!\n");
/* If we could not switch, the container contains exactly one client. We return false */
if (candidate == container->currently_focused)
@ -69,16 +73,16 @@ static void jump_to_mark(xcb_connection_t *conn, const char *mark) {
if (current->mark == NULL || strcmp(current->mark, mark) != 0)
continue;
workspace_show(conn, current->workspace->num + 1);
set_focus(conn, current, true);
workspace_show(conn, current->workspace->num + 1);
return;
}
LOG("No window with this mark found\n");
ELOG("No window with this mark found\n");
}
static void focus_thing(xcb_connection_t *conn, direction_t direction, thing_t thing) {
LOG("focusing direction %d\n", direction);
DLOG("focusing direction %d\n", direction);
int new_row = current_row,
new_col = current_col;
@ -86,19 +90,20 @@ static void focus_thing(xcb_connection_t *conn, direction_t direction, thing_t t
Workspace *t_ws = c_ws;
/* Makes sure new_col and new_row are within bounds of the new workspace */
void check_colrow_boundaries() {
if (new_col >= t_ws->cols)
new_col = (t_ws->cols - 1);
if (new_row >= t_ws->rows)
new_row = (t_ws->rows - 1);
}
#define CHECK_COLROW_BOUNDARIES \
do { \
if (new_col >= t_ws->cols) \
new_col = (t_ws->cols - 1); \
if (new_row >= t_ws->rows) \
new_row = (t_ws->rows - 1); \
} while (0)
/* There always is a container. If not, current_col or current_row is wrong */
assert(container != NULL);
if (container->workspace->fullscreen_client != NULL) {
LOG("You're in fullscreen mode. Won't switch focus\n");
return;
LOG("You're in fullscreen mode. Forcing focus to operate on whole screens\n");
thing = THING_SCREEN;
}
/* For focusing screens, situation is different: we get the rect
@ -106,7 +111,7 @@ static void focus_thing(xcb_connection_t *conn, direction_t direction, thing_t t
* right/left/bottom/top and just switch to the workspace on
* the target screen. */
if (thing == THING_SCREEN) {
i3Screen *cs = c_ws->screen;
Output *cs = c_ws->output;
assert(cs != NULL);
Rect bounds = cs->rect;
@ -118,20 +123,20 @@ static void focus_thing(xcb_connection_t *conn, direction_t direction, thing_t t
bounds.y -= bounds.height;
else bounds.y += bounds.height;
i3Screen *target = get_screen_containing(bounds.x, bounds.y);
Output *target = get_output_containing(bounds.x, bounds.y);
if (target == NULL) {
LOG("Target screen NULL\n");
DLOG("Target output NULL\n");
/* Wrap around if the target screen is out of bounds */
if (direction == D_RIGHT)
target = get_screen_most(D_LEFT, cs);
target = get_output_most(D_LEFT, cs);
else if (direction == D_LEFT)
target = get_screen_most(D_RIGHT, cs);
target = get_output_most(D_RIGHT, cs);
else if (direction == D_UP)
target = get_screen_most(D_DOWN, cs);
else target = get_screen_most(D_UP, cs);
target = get_output_most(D_DOWN, cs);
else target = get_output_most(D_UP, cs);
}
LOG("Switching to ws %d\n", target->current_workspace + 1);
DLOG("Switching to ws %d\n", target->current_workspace + 1);
workspace_show(conn, target->current_workspace->num + 1);
return;
}
@ -159,31 +164,48 @@ static void focus_thing(xcb_connection_t *conn, direction_t direction, thing_t t
}
} else {
/* Lets see if there is a screen down/up there to which we can switch */
LOG("container is at %d with height %d\n", container->y, container->height);
i3Screen *screen;
DLOG("container is at %d with height %d\n", container->y, container->height);
Output *output;
int destination_y = (direction == D_UP ? (container->y - 1) : (container->y + container->height + 1));
if ((screen = get_screen_containing(container->x, destination_y)) == NULL) {
LOG("Wrapping screen around vertically\n");
if ((output = get_output_containing(container->x, destination_y)) == NULL) {
DLOG("Wrapping screen around vertically\n");
/* No screen found? Then wrap */
screen = get_screen_most((direction == D_UP ? D_DOWN : D_UP), container->workspace->screen);
output = get_output_most((direction == D_UP ? D_DOWN : D_UP), container->workspace->output);
}
t_ws = screen->current_workspace;
t_ws = output->current_workspace;
new_row = (direction == D_UP ? (t_ws->rows - 1) : 0);
}
check_colrow_boundaries();
CHECK_COLROW_BOUNDARIES;
LOG("new_col = %d, new_row = %d\n", new_col, new_row);
DLOG("new_col = %d, new_row = %d\n", new_col, new_row);
if (t_ws->table[new_col][new_row]->currently_focused == NULL) {
LOG("Cell empty, checking for colspanned client above...\n");
DLOG("Cell empty, checking for colspanned client above...\n");
for (int cols = 0; cols < new_col; cols += t_ws->table[cols][new_row]->colspan) {
if (new_col > (cols + (t_ws->table[cols][new_row]->colspan - 1)))
continue;
new_col = cols;
DLOG("Fixed it to new col %d\n", new_col);
break;
}
}
if (t_ws->table[new_col][new_row]->currently_focused == NULL) {
DLOG("Cell still empty, checking for full cols above spanned width...\n");
DLOG("new_col = %d\n", new_col);
DLOG("colspan = %d\n", container->colspan);
for (int cols = new_col;
cols < container->col + container->colspan;
cols += t_ws->table[cols][new_row]->colspan) {
DLOG("candidate: new_row = %d, cols = %d\n", new_row, cols);
if (t_ws->table[cols][new_row]->currently_focused == NULL)
continue;
new_col = cols;
DLOG("Fixed it to new col %d\n", new_col);
break;
}
LOG("Fixed it to new col %d\n", new_col);
}
} else if (direction == D_LEFT || direction == D_RIGHT) {
if (direction == D_RIGHT && cell_exists(t_ws, current_col+1, current_row))
@ -202,37 +224,55 @@ static void focus_thing(xcb_connection_t *conn, direction_t direction, thing_t t
}
} else {
/* Lets see if there is a screen left/right here to which we can switch */
LOG("container is at %d with width %d\n", container->x, container->width);
i3Screen *screen;
DLOG("container is at %d with width %d\n", container->x, container->width);
Output *output;
int destination_x = (direction == D_LEFT ? (container->x - 1) : (container->x + container->width + 1));
if ((screen = get_screen_containing(destination_x, container->y)) == NULL) {
LOG("Wrapping screen around horizontally\n");
screen = get_screen_most((direction == D_LEFT ? D_RIGHT : D_LEFT), container->workspace->screen);
if ((output = get_output_containing(destination_x, container->y)) == NULL) {
DLOG("Wrapping screen around horizontally\n");
output = get_output_most((direction == D_LEFT ? D_RIGHT : D_LEFT), container->workspace->output);
}
t_ws = screen->current_workspace;
t_ws = output->current_workspace;
new_col = (direction == D_LEFT ? (t_ws->cols - 1) : 0);
}
check_colrow_boundaries();
CHECK_COLROW_BOUNDARIES;
LOG("new_col = %d, new_row = %d\n", new_col, new_row);
DLOG("new_col = %d, new_row = %d\n", new_col, new_row);
if (t_ws->table[new_col][new_row]->currently_focused == NULL) {
LOG("Cell empty, checking for rowspanned client above...\n");
DLOG("Cell empty, checking for rowspanned client above...\n");
for (int rows = 0; rows < new_row; rows += t_ws->table[new_col][rows]->rowspan) {
if (new_row > (rows + (t_ws->table[new_col][rows]->rowspan - 1)))
continue;
new_row = rows;
DLOG("Fixed it to new row %d\n", new_row);
break;
}
LOG("Fixed it to new row %d\n", new_row);
}
if (t_ws->table[new_col][new_row]->currently_focused == NULL) {
DLOG("Cell still empty, checking for full cols near full spanned height...\n");
DLOG("new_row = %d\n", new_row);
DLOG("rowspan = %d\n", container->rowspan);
for (int rows = new_row;
rows < container->row + container->rowspan;
rows += t_ws->table[new_col][rows]->rowspan) {
DLOG("candidate: new_col = %d, rows = %d\n", new_col, rows);
if (t_ws->table[new_col][rows]->currently_focused == NULL)
continue;
new_row = rows;
DLOG("Fixed it to new col %d\n", new_row);
break;
}
}
} else {
LOG("direction unhandled\n");
ELOG("direction unhandled\n");
return;
}
check_colrow_boundaries();
CHECK_COLROW_BOUNDARIES;
if (t_ws->table[new_col][new_row]->currently_focused != NULL)
set_focus(conn, t_ws->table[new_col][new_row]->currently_focused, true);
@ -254,7 +294,7 @@ static bool move_current_window_in_container(xcb_connection_t *conn, Client *cli
if (other == CIRCLEQ_END(&(client->container->clients)))
return false;
LOG("i can do that\n");
DLOG("i can do that\n");
/* We can move the client inside its current container */
CIRCLEQ_REMOVE(&(client->container->clients), client, clients);
if (direction == D_UP)
@ -357,7 +397,7 @@ static void move_current_window(xcb_connection_t *conn, direction_t direction) {
/* Fix colspan/rowspan if itd overlap */
fix_colrowspan(conn, workspace);
render_workspace(conn, workspace->screen, workspace);
render_workspace(conn, workspace->output, workspace);
xcb_flush(conn);
set_focus(conn, current_client, true);
@ -411,7 +451,7 @@ static void move_current_container(xcb_connection_t *conn, direction_t direction
return;
}
LOG("old = %d,%d and new = %d,%d\n", container->col, container->row, new->col, new->row);
DLOG("old = %d,%d and new = %d,%d\n", container->col, container->row, new->col, new->row);
/* Swap the containers */
int col = new->col;
@ -453,7 +493,7 @@ static void snap_current_container(xcb_connection_t *conn, direction_t direction
/* Snap to the left is actually a move to the left and then a snap right */
if (!cell_exists(container->workspace, container->col - 1, container->row) ||
CUR_TABLE[container->col-1][container->row]->currently_focused != NULL) {
LOG("cannot snap to left - the cell is already used\n");
ELOG("cannot snap to left - the cell is already used\n");
return;
}
@ -466,18 +506,18 @@ static void snap_current_container(xcb_connection_t *conn, direction_t direction
for (int i = 0; i < container->rowspan; i++)
if (!cell_exists(container->workspace, new_col, container->row + i) ||
CUR_TABLE[new_col][container->row + i]->currently_focused != NULL) {
LOG("cannot snap to right - the cell is already used\n");
ELOG("cannot snap to right - the cell is already used\n");
return;
}
/* Check if there are other cells with rowspan, which are in our way.
* If so, reduce their rowspan. */
for (int i = container->row-1; i >= 0; i--) {
LOG("we got cell %d, %d with rowspan %d\n",
DLOG("we got cell %d, %d with rowspan %d\n",
new_col, i, CUR_TABLE[new_col][i]->rowspan);
while ((CUR_TABLE[new_col][i]->rowspan-1) >= (container->row - i))
CUR_TABLE[new_col][i]->rowspan--;
LOG("new rowspan = %d\n", CUR_TABLE[new_col][i]->rowspan);
DLOG("new rowspan = %d\n", CUR_TABLE[new_col][i]->rowspan);
}
container->colspan++;
@ -486,7 +526,7 @@ static void snap_current_container(xcb_connection_t *conn, direction_t direction
case D_UP:
if (!cell_exists(container->workspace, container->col, container->row - 1) ||
CUR_TABLE[container->col][container->row-1]->currently_focused != NULL) {
LOG("cannot snap to top - the cell is already used\n");
ELOG("cannot snap to top - the cell is already used\n");
return;
}
@ -494,21 +534,21 @@ static void snap_current_container(xcb_connection_t *conn, direction_t direction
snap_current_container(conn, D_DOWN);
return;
case D_DOWN: {
LOG("snapping down\n");
DLOG("snapping down\n");
int new_row = container->row + container->rowspan;
for (int i = 0; i < container->colspan; i++)
if (!cell_exists(container->workspace, container->col + i, new_row) ||
CUR_TABLE[container->col + i][new_row]->currently_focused != NULL) {
LOG("cannot snap down - the cell is already used\n");
ELOG("cannot snap down - the cell is already used\n");
return;
}
for (int i = container->col-1; i >= 0; i--) {
LOG("we got cell %d, %d with colspan %d\n",
DLOG("we got cell %d, %d with colspan %d\n",
i, new_row, CUR_TABLE[i][new_row]->colspan);
while ((CUR_TABLE[i][new_row]->colspan-1) >= (container->col - i))
CUR_TABLE[i][new_row]->colspan--;
LOG("new colspan = %d\n", CUR_TABLE[i][new_row]->colspan);
DLOG("new colspan = %d\n", CUR_TABLE[i][new_row]->colspan);
}
@ -530,12 +570,12 @@ static void move_floating_window_to_workspace(xcb_connection_t *conn, Client *cl
LOG("moving floating\n");
workspace_initialize(t_ws, c_ws->screen);
workspace_initialize(t_ws, c_ws->output, false);
/* Check if there is already a fullscreen client on the destination workspace and
* stop moving if so. */
if (client->fullscreen && (t_ws->fullscreen_client != NULL)) {
LOG("Not moving: Fullscreen client already existing on destination workspace.\n");
ELOG("Not moving: Fullscreen client already existing on destination workspace.\n");
return;
}
@ -543,21 +583,26 @@ static void move_floating_window_to_workspace(xcb_connection_t *conn, Client *cl
/* If were moving it to an invisible screen, we need to unmap it */
if (!workspace_is_visible(t_ws)) {
LOG("This workspace is not visible, unmapping\n");
DLOG("This workspace is not visible, unmapping\n");
client_unmap(conn, client);
} else {
/* If this is not the case, we move the window to a workspace
* which is on another screen, so we also need to adjust its
* coordinates. */
LOG("before x = %d, y = %d\n", client->rect.x, client->rect.y);
DLOG("before x = %d, y = %d\n", client->rect.x, client->rect.y);
uint32_t relative_x = client->rect.x - old_ws->rect.x,
relative_y = client->rect.y - old_ws->rect.y;
LOG("rel_x = %d, rel_y = %d\n", relative_x, relative_y);
client->rect.x = t_ws->rect.x + relative_x;
client->rect.y = t_ws->rect.y + relative_y;
LOG("after x = %d, y = %d\n", client->rect.x, client->rect.y);
reposition_client(conn, client);
xcb_flush(conn);
DLOG("rel_x = %d, rel_y = %d\n", relative_x, relative_y);
if (client->fullscreen) {
client_enter_fullscreen(conn, client, false);
memcpy(&(client->rect), &(t_ws->rect), sizeof(Rect));
} else {
client->rect.x = t_ws->rect.x + relative_x;
client->rect.y = t_ws->rect.y + relative_y;
DLOG("after x = %d, y = %d\n", client->rect.x, client->rect.y);
reposition_client(conn, client);
xcb_flush(conn);
}
}
/* Configure the window above all tiling windows (or below a fullscreen
@ -576,12 +621,14 @@ static void move_floating_window_to_workspace(xcb_connection_t *conn, Client *cl
}
}
LOG("done\n");
DLOG("done\n");
render_layout(conn);
if (workspace_is_visible(t_ws))
if (workspace_is_visible(t_ws)) {
client_warp_pointer_into(conn, client);
set_focus(conn, client, true);
}
}
/*
@ -600,18 +647,18 @@ static void move_current_window_to_workspace(xcb_connection_t *conn, int workspa
Client *current_client = container->currently_focused;
if (current_client == NULL) {
LOG("No currently focused client in current container.\n");
ELOG("No currently focused client in current container.\n");
return;
}
Client *to_focus = CIRCLEQ_NEXT_OR_NULL(&(container->clients), current_client, clients);
if (to_focus == NULL)
to_focus = CIRCLEQ_PREV_OR_NULL(&(container->clients), current_client, clients);
workspace_initialize(t_ws, container->workspace->screen);
workspace_initialize(t_ws, container->workspace->output, false);
/* Check if there is already a fullscreen client on the destination workspace and
* stop moving if so. */
if (current_client->fullscreen && (t_ws->fullscreen_client != NULL)) {
LOG("Not moving: Fullscreen client already existing on destination workspace.\n");
ELOG("Not moving: Fullscreen client already existing on destination workspace.\n");
return;
}
@ -627,7 +674,7 @@ static void move_current_window_to_workspace(xcb_connection_t *conn, int workspa
CIRCLEQ_INSERT_TAIL(&(to_container->clients), current_client, clients);
SLIST_INSERT_HEAD(&(to_container->workspace->focus_stack), current_client, focus_clients);
LOG("Moved.\n");
DLOG("Moved.\n");
current_client->container = to_container;
current_client->workspace = to_container->workspace;
@ -636,12 +683,12 @@ static void move_current_window_to_workspace(xcb_connection_t *conn, int workspa
/* If were moving it to an invisible screen, we need to unmap it */
if (!workspace_is_visible(to_container->workspace)) {
LOG("This workspace is not visible, unmapping\n");
DLOG("This workspace is not visible, unmapping\n");
client_unmap(conn, current_client);
} else {
if (current_client->fullscreen) {
LOG("Calling client_enter_fullscreen again\n");
client_enter_fullscreen(conn, current_client);
DLOG("Calling client_enter_fullscreen again\n");
client_enter_fullscreen(conn, current_client, false);
}
}
@ -650,8 +697,10 @@ static void move_current_window_to_workspace(xcb_connection_t *conn, int workspa
render_layout(conn);
if (workspace_is_visible(to_container->workspace))
if (workspace_is_visible(to_container->workspace)) {
client_warp_pointer_into(conn, current_client);
set_focus(conn, current_client, true);
}
}
/*
@ -672,11 +721,12 @@ static void jump_to_window(xcb_connection_t *conn, const char *arguments) {
if ((client = get_matching_client(conn, classtitle, NULL)) == NULL) {
free(classtitle);
LOG("No matching client found.\n");
ELOG("No matching client found.\n");
return;
}
free(classtitle);
workspace_show(conn, client->workspace->num + 1);
set_focus(conn, client, true);
}
@ -694,7 +744,7 @@ static void jump_to_container(xcb_connection_t *conn, const char *arguments) {
/* No match? Either no arguments were specified, or no numbers */
if (result < 1) {
LOG("At least one valid argument required\n");
ELOG("At least one valid argument required\n");
return;
}
@ -704,7 +754,7 @@ static void jump_to_container(xcb_connection_t *conn, const char *arguments) {
if (result < 3)
return;
LOG("Boundary-checking col %d, row %d... (max cols %d, max rows %d)\n", col, row, c_ws->cols, c_ws->rows);
DLOG("Boundary-checking col %d, row %d... (max cols %d, max rows %d)\n", col, row, c_ws->cols, c_ws->rows);
/* Move to row/col */
if (row >= c_ws->rows)
@ -712,7 +762,7 @@ static void jump_to_container(xcb_connection_t *conn, const char *arguments) {
if (col >= c_ws->cols)
col = c_ws->cols - 1;
LOG("Jumping to col %d, row %d\n", col, row);
DLOG("Jumping to col %d, row %d\n", col, row);
if (c_ws->table[col][row]->currently_focused != NULL)
set_focus(conn, c_ws->table[col][row]->currently_focused, true);
}
@ -741,7 +791,7 @@ static void travel_focus_stack(xcb_connection_t *conn, const char *arguments) {
} else if (strcasecmp(arguments, "ft") == 0) {
Client *last_focused = SLIST_FIRST(&(c_ws->focus_stack));
if (last_focused == SLIST_END(&(c_ws->focus_stack))) {
LOG("Cannot select the next floating/tiling client because there is no client at all\n");
ELOG("Cannot select the next floating/tiling client because there is no client at all\n");
return;
}
@ -749,17 +799,17 @@ static void travel_focus_stack(xcb_connection_t *conn, const char *arguments) {
} else {
/* …or a number was specified */
if (sscanf(arguments, "%u", &times) != 1) {
LOG("No or invalid argument given (\"%s\"), using default of 1 times\n", arguments);
ELOG("No or invalid argument given (\"%s\"), using default of 1 times\n", arguments);
times = 1;
}
SLIST_FOREACH(current, &(CUR_CELL->workspace->focus_stack), focus_clients) {
if (++count < times) {
LOG("Skipping\n");
DLOG("Skipping\n");
continue;
}
LOG("Focussing\n");
DLOG("Focussing\n");
set_focus(conn, current, true);
break;
}
@ -774,29 +824,6 @@ static void travel_focus_stack(xcb_connection_t *conn, const char *arguments) {
}
}
/*
* Goes through the list of arguments (for exec()) and checks if the given argument
* is present. If not, it copies the arguments (because we cannot realloc it) and
* appends the given argument.
*
*/
static char **append_argument(char **original, char *argument) {
int num_args;
for (num_args = 0; original[num_args] != NULL; num_args++) {
LOG("original argument: \"%s\"\n", original[num_args]);
/* If the argument is already present we return the original pointer */
if (strcmp(original[num_args], argument) == 0)
return original;
}
/* Copy the original array */
char **result = smalloc((num_args+2) * sizeof(char*));
memcpy(result, original, num_args * sizeof(char*));
result[num_args] = argument;
result[num_args+1] = NULL;
return result;
}
/*
* Switch to next or previous existing workspace
*
@ -805,16 +832,32 @@ static void next_previous_workspace(xcb_connection_t *conn, int direction) {
Workspace *ws = c_ws;
if (direction == 'n') {
while ((ws = TAILQ_NEXT(ws, workspaces)) != TAILQ_END(workspaces_head)) {
if (ws->screen == NULL)
while (1) {
ws = TAILQ_NEXT(ws, workspaces);
if (ws == TAILQ_END(workspaces))
ws = TAILQ_FIRST(workspaces);
if (ws == c_ws)
return;
if (ws->output == NULL)
continue;
workspace_show(conn, ws->num + 1);
return;
}
} else if (direction == 'p') {
while ((ws = TAILQ_PREV(ws, workspaces_head, workspaces)) != TAILQ_END(workspaces)) {
if (ws->screen == NULL)
while (1) {
ws = TAILQ_PREV(ws, workspaces_head, workspaces);
if (ws == TAILQ_END(workspaces))
ws = TAILQ_LAST(workspaces, workspaces_head);
if (ws == c_ws)
return;
if (ws->output == NULL)
continue;
workspace_show(conn, ws->num + 1);
@ -827,7 +870,34 @@ static void parse_resize_command(xcb_connection_t *conn, Client *last_focused, c
int first, second;
resize_orientation_t orientation = O_VERTICAL;
Container *con = last_focused->container;
Workspace *ws = con->workspace;
Workspace *ws = last_focused->workspace;
if (client_is_floating(last_focused)) {
DLOG("Resizing a floating client\n");
if (STARTS_WITH(command, "left")) {
command += strlen("left");
last_focused->rect.width += atoi(command);
last_focused->rect.x -= atoi(command);
} else if (STARTS_WITH(command, "right")) {
command += strlen("right");
last_focused->rect.width += atoi(command);
} else if (STARTS_WITH(command, "top")) {
command += strlen("top");
last_focused->rect.height += atoi(command);
last_focused->rect.y -= atoi(command);
} else if (STARTS_WITH(command, "bottom")) {
command += strlen("bottom");
last_focused->rect.height += atoi(command);
} else {
ELOG("Syntax: resize <left|right|top|bottom> [+|-]<pixels>\n");
return;
}
/* resize_client flushes */
resize_client(conn, last_focused);
return;
}
if (STARTS_WITH(command, "left")) {
if (con->col == 0)
@ -837,7 +907,7 @@ static void parse_resize_command(xcb_connection_t *conn, Client *last_focused, c
command += strlen("left");
} else if (STARTS_WITH(command, "right")) {
first = con->col + (con->colspan - 1);
LOG("column %d\n", first);
DLOG("column %d\n", first);
if (!cell_exists(ws, first, con->row) ||
(first == (ws->cols-1)))
@ -862,7 +932,7 @@ static void parse_resize_command(xcb_connection_t *conn, Client *last_focused, c
orientation = O_HORIZONTAL;
command += strlen("bottom");
} else {
LOG("Syntax: resize <left|right|top|bottom> [+|-]<pixels>\n");
ELOG("Syntax: resize <left|right|top|bottom> [+|-]<pixels>\n");
return;
}
@ -898,14 +968,14 @@ void parse_command(xcb_connection_t *conn, const char *command) {
if (STARTS_WITH(command, "mark")) {
if (last_focused == NULL) {
LOG("There is no window to mark\n");
ELOG("There is no window to mark\n");
return;
}
const char *rest = command + strlen("mark");
while (*rest == ' ')
rest++;
if (*rest == '\0') {
LOG("interactive mark starting\n");
DLOG("interactive mark starting\n");
start_application("i3-input -p 'mark ' -l 1 -P 'Mark: '");
} else {
LOG("mark with \"%s\"\n", rest);
@ -919,7 +989,7 @@ void parse_command(xcb_connection_t *conn, const char *command) {
while (*rest == ' ')
rest++;
if (*rest == '\0') {
LOG("interactive go to mark starting\n");
DLOG("interactive go to mark starting\n");
start_application("i3-input -p 'goto ' -l 1 -P 'Goto: '");
} else {
LOG("go to \"%s\"\n", rest);
@ -930,7 +1000,7 @@ void parse_command(xcb_connection_t *conn, const char *command) {
if (STARTS_WITH(command, "stack-limit ")) {
if (last_focused == NULL || client_is_floating(last_focused)) {
LOG("No container focused\n");
ELOG("No container focused\n");
return;
}
const char *rest = command + strlen("stack-limit ");
@ -941,7 +1011,7 @@ void parse_command(xcb_connection_t *conn, const char *command) {
last_focused->container->stack_limit = STACK_LIMIT_COLS;
rest += strlen("cols ");
} else {
LOG("Syntax: stack-limit <cols|rows> <limit>\n");
ELOG("Syntax: stack-limit <cols|rows> <limit>\n");
return;
}
@ -969,28 +1039,28 @@ void parse_command(xcb_connection_t *conn, const char *command) {
/* Is it an <exit>? */
if (STARTS_WITH(command, "exit")) {
LOG("User issued exit-command, exiting without error.\n");
restore_geometry(global_conn);
ipc_shutdown();
exit(EXIT_SUCCESS);
}
/* Is it a <reload>? */
if (STARTS_WITH(command, "reload")) {
load_configuration(conn, NULL, true);
render_layout(conn);
/* Send an IPC event just in case the ws names have changed */
ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"reload\"}");
return;
}
/* Is it <restart>? Then restart in place. */
if (STARTS_WITH(command, "restart")) {
LOG("restarting \"%s\"...\n", start_argv[0]);
/* make sure -a is in the argument list or append it */
start_argv = append_argument(start_argv, "-a");
execvp(start_argv[0], start_argv);
/* not reached */
i3_restart();
}
if (STARTS_WITH(command, "kill")) {
if (last_focused == NULL) {
LOG("There is no window to kill\n");
ELOG("There is no window to kill\n");
return;
}
@ -1015,25 +1085,28 @@ void parse_command(xcb_connection_t *conn, const char *command) {
return;
}
/* Is it 'f' for fullscreen? */
/* Is it 'f' for fullscreen, or 'fg' for fullscreen_global? */
if (command[0] == 'f') {
if (last_focused == NULL)
return;
client_toggle_fullscreen(conn, last_focused);
if (command[1] == 'g')
client_toggle_fullscreen_global(conn, last_focused);
else
client_toggle_fullscreen(conn, last_focused);
return;
}
/* Is it just 's' for stacking or 'd' for default? */
if ((command[0] == 's' || command[0] == 'd' || command[0] == 'T') && (command[1] == '\0')) {
if (last_focused != NULL && client_is_floating(last_focused)) {
LOG("not switching, this is a floating client\n");
ELOG("not switching, this is a floating client\n");
return;
}
LOG("Switching mode for current container\n");
int new_mode = MODE_DEFAULT;
if (command[0] == 's')
if (command[0] == 's' && CUR_CELL->mode != MODE_STACK)
new_mode = MODE_STACK;
if (command[0] == 'T')
if (command[0] == 'T' && CUR_CELL->mode != MODE_TABBED)
new_mode = MODE_TABBED;
switch_layout_mode(conn, CUR_CELL, new_mode);
return;
@ -1043,7 +1116,7 @@ void parse_command(xcb_connection_t *conn, const char *command) {
/* or even 'bt' (toggle border: 'bp' -> 'bb' -> 'bn' ) */
if (command[0] == 'b') {
if (last_focused == NULL) {
LOG("No window focused, cannot change border type\n");
ELOG("No window focused, cannot change border type\n");
return;
}
@ -1084,7 +1157,7 @@ void parse_command(xcb_connection_t *conn, const char *command) {
with = WITH_SCREEN;
command++;
} else {
LOG("not yet implemented.\n");
ELOG("not yet implemented.\n");
return;
}
}
@ -1097,7 +1170,7 @@ void parse_command(xcb_connection_t *conn, const char *command) {
return;
}
if (last_focused == NULL) {
LOG("Cannot toggle tiling/floating: workspace empty\n");
ELOG("Cannot toggle tiling/floating: workspace empty\n");
return;
}
@ -1113,7 +1186,7 @@ void parse_command(xcb_connection_t *conn, const char *command) {
/* Fix colspan/rowspan if itd overlap */
fix_colrowspan(conn, ws);
render_workspace(conn, ws->screen, ws);
render_workspace(conn, ws->output, ws);
/* Re-focus the client because cleanup_table sets the focus to the last
* focused client inside a container only. */
@ -1134,7 +1207,7 @@ void parse_command(xcb_connection_t *conn, const char *command) {
direction_t direction;
int times = strtol(command, &rest, 10);
if (rest == NULL) {
LOG("Invalid command (\"%s\")\n", command);
ELOG("Invalid command (\"%s\")\n", command);
return;
}
@ -1152,7 +1225,7 @@ void parse_command(xcb_connection_t *conn, const char *command) {
int workspace = strtol(rest, &rest, 10);
if (rest == NULL) {
LOG("Invalid command (\"%s\")\n", command);
ELOG("Invalid command (\"%s\")\n", command);
return;
}
@ -1164,13 +1237,13 @@ void parse_command(xcb_connection_t *conn, const char *command) {
}
if (last_focused == NULL) {
LOG("Not performing (no window found)\n");
ELOG("Not performing (no window found)\n");
return;
}
if (client_is_floating(last_focused) &&
(action != ACTION_FOCUS && action != ACTION_MOVE)) {
LOG("Not performing (floating)\n");
ELOG("Not performing (floating)\n");
return;
}
@ -1185,7 +1258,7 @@ void parse_command(xcb_connection_t *conn, const char *command) {
else if (*rest == 'l')
direction = D_RIGHT;
else {
LOG("unknown direction: %c\n", *rest);
ELOG("unknown direction: %c\n", *rest);
return;
}
rest++;
@ -1208,7 +1281,7 @@ void parse_command(xcb_connection_t *conn, const char *command) {
/* TODO: this should swap the screens contents
* (e.g. all workspaces) with the next/previous/…
* screen */
LOG("Not yet implemented\n");
ELOG("Not yet implemented\n");
continue;
}
if (client_is_floating(last_focused)) {
@ -1223,7 +1296,7 @@ void parse_command(xcb_connection_t *conn, const char *command) {
if (action == ACTION_SNAP) {
if (with == WITH_SCREEN) {
LOG("You cannot snap a screen (it makes no sense).\n");
ELOG("You cannot snap a screen (it makes no sense).\n");
continue;
}
snap_current_container(conn, direction);

View File

@ -3,16 +3,23 @@
*
* i3 - an improved dynamic tiling window manager
*
* © 2009 Michael Stapelberg and contributors
* © 2009-2010 Michael Stapelberg and contributors
*
* See file LICENSE for license information.
*
* src/config.c: Contains all functions handling the configuration file (calling
* the parser (src/cfgparse.y) with the correct path, switching key bindings
* mode).
*
*/
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <stdlib.h>
#include <glob.h>
#include <wordexp.h>
#include <unistd.h>
/* We need Xlib for XStringToKeysym */
#include <X11/Xlib.h>
@ -25,48 +32,44 @@
#include "xcb.h"
#include "table.h"
#include "workspace.h"
/* prototype for src/cfgparse.y, will be cleaned up as soon as we completely
* switched to the new scanner/parser. */
void parse_file(const char *f);
#include "log.h"
Config config;
struct modes_head modes;
bool config_use_lexer = false;
/*
* This function resolves ~ in pathnames.
*
*/
static char *glob_path(const char *path) {
char *glob_path(const char *path) {
static glob_t globbuf;
if (glob(path, GLOB_NOCHECK | GLOB_TILDE, NULL, &globbuf) < 0)
die("glob() failed");
char *result = sstrdup(globbuf.gl_pathc > 0 ? globbuf.gl_pathv[0] : path);
globfree(&globbuf);
/* If the file does not exist yet, we still may need to resolve tilde,
* so call wordexp */
if (strcmp(result, path) == 0) {
wordexp_t we;
wordexp(path, &we, WRDE_NOCMD);
if (we.we_wordc > 0) {
free(result);
result = sstrdup(we.we_wordv[0]);
}
wordfree(&we);
}
return result;
}
/*
* This function does a very simple replacement of each instance of key with value.
* Checks if the given path exists by calling stat().
*
*/
static void replace_variable(char *buffer, const char *key, const char *value) {
char *pos;
/* To prevent endless recursions when the user makes an error configuring,
* we stop after 100 replacements. That should be vastly more than enough. */
int c = 0;
while ((pos = strcasestr(buffer, key)) != NULL && c++ < 100) {
char *rest = pos + strlen(key);
*pos = '\0';
char *replaced;
asprintf(&replaced, "%s%s%s", buffer, value, rest);
/* Hm, this is a bit ugly, but sizeof(buffer) = 4, as its just a pointer.
* So we need to hard-code the dimensions here. */
strncpy(buffer, replaced, 1026);
free(replaced);
}
bool path_exists(const char *path) {
struct stat buf;
return (stat(path, &buf) == 0);
}
/**
@ -75,57 +78,88 @@ static void replace_variable(char *buffer, const char *key, const char *value) {
*
*/
void ungrab_all_keys(xcb_connection_t *conn) {
LOG("Ungrabbing all keys\n");
DLOG("Ungrabbing all keys\n");
xcb_ungrab_key(conn, XCB_GRAB_ANY, root, XCB_BUTTON_MASK_ANY);
}
static void grab_keycode_for_binding(xcb_connection_t *conn, Binding *bind, uint32_t keycode) {
LOG("Grabbing %d\n", keycode);
if ((bind->mods & BIND_MODE_SWITCH) != 0)
xcb_grab_key(conn, 0, root, 0, keycode,
XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_SYNC);
else {
/* Grab the key in all combinations */
#define GRAB_KEY(modifier) xcb_grab_key(conn, 0, root, modifier, keycode, \
XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC)
GRAB_KEY(bind->mods);
GRAB_KEY(bind->mods | xcb_numlock_mask);
GRAB_KEY(bind->mods | xcb_numlock_mask | XCB_MOD_MASK_LOCK);
DLOG("Grabbing %d\n", keycode);
/* Grab the key in all combinations */
#define GRAB_KEY(modifier) \
do { \
xcb_grab_key(conn, 0, root, modifier, keycode, \
XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC); \
} while (0)
int mods = bind->mods;
if ((bind->mods & BIND_MODE_SWITCH) != 0) {
mods &= ~BIND_MODE_SWITCH;
if (mods == 0)
mods = XCB_MOD_MASK_ANY;
}
GRAB_KEY(mods);
GRAB_KEY(mods | xcb_numlock_mask);
GRAB_KEY(mods | xcb_numlock_mask | XCB_MOD_MASK_LOCK);
}
/*
* Grab the bound keys (tell X to send us keypress events for those keycodes)
* Returns a pointer to the Binding with the specified modifiers and keycode
* or NULL if no such binding exists.
*
*/
void grab_all_keys(xcb_connection_t *conn) {
Binding *get_binding(uint16_t modifiers, xcb_keycode_t keycode) {
Binding *bind;
TAILQ_FOREACH(bind, bindings, bindings) {
/* First compare the modifiers */
if (bind->mods != modifiers)
continue;
/* If a symbol was specified by the user, we need to look in
* the array of translated keycodes for the events keycode */
if (bind->symbol != NULL) {
if (memmem(bind->translated_to,
bind->number_keycodes * sizeof(xcb_keycode_t),
&keycode, sizeof(xcb_keycode_t)) != NULL)
break;
} else {
/* This case is easier: The user specified a keycode */
if (bind->keycode == keycode)
break;
}
}
return (bind == TAILQ_END(bindings) ? NULL : bind);
}
/*
* Translates keysymbols to keycodes for all bindings which use keysyms.
*
*/
void translate_keysyms() {
Binding *bind;
TAILQ_FOREACH(bind, bindings, bindings) {
/* The easy case: the user specified a keycode directly. */
if (bind->keycode > 0) {
grab_keycode_for_binding(conn, bind, bind->keycode);
if (bind->keycode > 0)
continue;
}
/* We need to translate the symbol to a keycode */
xcb_keysym_t keysym = XStringToKeysym(bind->symbol);
if (keysym == NoSymbol) {
LOG("Could not translate string to key symbol: \"%s\"\n", bind->symbol);
ELOG("Could not translate string to key symbol: \"%s\"\n", bind->symbol);
continue;
}
#ifdef OLD_XCB_KEYSYMS_API
bind->number_keycodes = 1;
xcb_keycode_t code = xcb_key_symbols_get_keycode(keysyms, keysym);
LOG("Translated symbol \"%s\" to 1 keycode (%d)\n", bind->symbol, code);
grab_keycode_for_binding(conn, bind, code);
DLOG("Translated symbol \"%s\" to 1 keycode (%d)\n", bind->symbol, code);
grab_keycode_for_binding(global_conn, bind, code);
bind->translated_to = smalloc(sizeof(xcb_keycode_t));
memcpy(bind->translated_to, &code, sizeof(xcb_keycode_t));
#else
uint32_t last_keycode = 0;
xcb_keycode_t *keycodes = xcb_key_symbols_get_keycode(keysyms, keysym);
if (keycodes == NULL) {
LOG("Could not translate symbol \"%s\"\n", bind->symbol);
DLOG("Could not translate symbol \"%s\"\n", bind->symbol);
continue;
}
@ -136,11 +170,10 @@ void grab_all_keys(xcb_connection_t *conn) {
* and skip them */
if (last_keycode == *walk)
continue;
grab_keycode_for_binding(conn, bind, *walk);
last_keycode = *walk;
bind->number_keycodes++;
}
LOG("Translated symbol \"%s\" to %d keycode\n", bind->symbol, bind->number_keycodes);
DLOG("Translated symbol \"%s\" to %d keycode\n", bind->symbol, bind->number_keycodes);
bind->translated_to = smalloc(bind->number_keycodes * sizeof(xcb_keycode_t));
memcpy(bind->translated_to, keycodes, bind->number_keycodes * sizeof(xcb_keycode_t));
free(keycodes);
@ -148,6 +181,29 @@ void grab_all_keys(xcb_connection_t *conn) {
}
}
/*
* Grab the bound keys (tell X to send us keypress events for those keycodes)
*
*/
void grab_all_keys(xcb_connection_t *conn, bool bind_mode_switch) {
Binding *bind;
TAILQ_FOREACH(bind, bindings, bindings) {
if ((bind_mode_switch && (bind->mods & BIND_MODE_SWITCH) == 0) ||
(!bind_mode_switch && (bind->mods & BIND_MODE_SWITCH) != 0))
continue;
/* The easy case: the user specified a keycode directly. */
if (bind->keycode > 0) {
grab_keycode_for_binding(conn, bind, bind->keycode);
continue;
}
xcb_keycode_t *walk = bind->translated_to;
for (int i = 0; i < bind->number_keycodes; i++)
grab_keycode_for_binding(conn, bind, *walk);
}
}
/*
* Switches the key bindings to the given mode, if the mode exists
*
@ -163,18 +219,89 @@ void switch_mode(xcb_connection_t *conn, const char *new_mode) {
ungrab_all_keys(conn);
bindings = mode->bindings;
grab_all_keys(conn);
translate_keysyms();
grab_all_keys(conn, false);
return;
}
LOG("ERROR: Mode not found\n");
ELOG("ERROR: Mode not found\n");
}
/*
* Reads the configuration from ~/.i3/config or /etc/i3/config if not found.
* Get the path of the first configuration file found. Checks the XDG folders
* first ($XDG_CONFIG_HOME, $XDG_CONFIG_DIRS), then the traditional paths.
*
* If you specify override_configpath, only this path is used to look for a
* configuration file.
*/
static char *get_config_path() {
/* 1: check for $XDG_CONFIG_HOME/i3/config */
char *xdg_config_home, *xdg_config_dirs, *config_path;
if ((xdg_config_home = getenv("XDG_CONFIG_HOME")) == NULL)
xdg_config_home = "~/.config";
xdg_config_home = glob_path(xdg_config_home);
if (asprintf(&config_path, "%s/i3/config", xdg_config_home) == -1)
die("asprintf() failed");
free(xdg_config_home);
if (path_exists(config_path))
return config_path;
free(config_path);
/* 2: check for $XDG_CONFIG_DIRS/i3/config */
if ((xdg_config_dirs = getenv("XDG_CONFIG_DIRS")) == NULL)
xdg_config_dirs = "/etc/xdg";
char *buf = strdup(xdg_config_dirs);
char *tok = strtok(buf, ":");
while (tok != NULL) {
tok = glob_path(tok);
if (asprintf(&config_path, "%s/i3/config", tok) == -1)
die("asprintf() failed");
free(tok);
if (path_exists(config_path)) {
free(buf);
return config_path;
}
free(config_path);
tok = strtok(NULL, ":");
}
free(buf);
/* 3: check traditional paths */
config_path = glob_path("~/.i3/config");
if (path_exists(config_path))
return config_path;
config_path = strdup("/etc/i3/config");
if (!path_exists(config_path))
die("Neither $XDG_CONFIG_HOME/i3/config, nor "
"$XDG_CONFIG_DIRS/i3/config, nor ~/.i3/config nor "
"/etc/i3/config exist.");
return config_path;
}
/*
* Finds the configuration file to use (either the one specified by
* override_configpath), the users one or the system default) and calls
* parse_file().
*
*/
static void parse_configuration(const char *override_configpath) {
if (override_configpath != NULL) {
parse_file(override_configpath);
return;
}
char *path = get_config_path();
DLOG("Parsing configfile %s\n", path);
parse_file(path);
free(path);
}
/*
* (Re-)loads the configuration file (sets useful defaults before).
*
*/
void load_configuration(xcb_connection_t *conn, const char *override_configpath, bool reload) {
@ -208,6 +335,11 @@ void load_configuration(xcb_connection_t *conn, const char *override_configpath,
TAILQ_REMOVE(&assignments, assign, assignments);
FREE(assign);
}
/* Clear workspace names */
Workspace *ws;
TAILQ_FOREACH(ws, workspaces, workspaces)
workspace_set_name(ws, NULL);
}
SLIST_INIT(&modes);
@ -220,388 +352,36 @@ void load_configuration(xcb_connection_t *conn, const char *override_configpath,
bindings = default_mode->bindings;
SLIST_HEAD(variables_head, Variable) variables;
#define OPTION_STRING(name) \
if (strcasecmp(key, #name) == 0) { \
config.name = sstrdup(value); \
continue; \
}
#define REQUIRED_OPTION(name) \
if (config.name == NULL) \
die("You did not specify required configuration option " #name "\n");
#define OPTION_COLORTRIPLE(opt, name) \
if (strcasecmp(key, opt) == 0) { \
char border[8], background[8], text[8]; \
memset(border, 0, sizeof(border)); \
memset(background, 0, sizeof(background)); \
memset(text, 0, sizeof(text)); \
border[0] = background[0] = text[0] = '#'; \
if (sscanf(value, "#%06[0-9a-fA-F] #%06[0-9a-fA-F] #%06[0-9a-fA-F]", \
border + 1, background + 1, text + 1) != 3 || \
strlen(border) != 7 || \
strlen(background) != 7 || \
strlen(text) != 7) \
die("invalid color code line: %s\n", value); \
config.name.border = get_colorpixel(conn, border); \
config.name.background = get_colorpixel(conn, background); \
config.name.text = get_colorpixel(conn, text); \
continue; \
}
/* Clear the old config or initialize the data structure */
memset(&config, 0, sizeof(config));
SLIST_INIT(&variables);
/* Initialize default colors */
config.client.focused.border = get_colorpixel(conn, "#4c7899");
config.client.focused.background = get_colorpixel(conn, "#285577");
config.client.focused.text = get_colorpixel(conn, "#ffffff");
#define INIT_COLOR(x, cborder, cbackground, ctext) \
do { \
x.border = get_colorpixel(conn, cborder); \
x.background = get_colorpixel(conn, cbackground); \
x.text = get_colorpixel(conn, ctext); \
} while (0)
config.client.focused_inactive.border = get_colorpixel(conn, "#333333");
config.client.focused_inactive.background = get_colorpixel(conn, "#5f676a");
config.client.focused_inactive.text = get_colorpixel(conn, "#ffffff");
INIT_COLOR(config.client.focused, "#4c7899", "#285577", "#ffffff");
INIT_COLOR(config.client.focused_inactive, "#333333", "#5f676a", "#ffffff");
INIT_COLOR(config.client.unfocused, "#333333", "#222222", "#888888");
INIT_COLOR(config.client.urgent, "#2f343a", "#900000", "#ffffff");
INIT_COLOR(config.bar.focused, "#4c7899", "#285577", "#ffffff");
INIT_COLOR(config.bar.unfocused, "#333333", "#222222", "#888888");
INIT_COLOR(config.bar.urgent, "#2f343a", "#900000", "#ffffff");
config.client.unfocused.border = get_colorpixel(conn, "#333333");
config.client.unfocused.background = get_colorpixel(conn, "#222222");
config.client.unfocused.text = get_colorpixel(conn, "#888888");
parse_configuration(override_configpath);
config.client.urgent.border = get_colorpixel(conn, "#2f343a");
config.client.urgent.background = get_colorpixel(conn, "#900000");
config.client.urgent.text = get_colorpixel(conn, "#ffffff");
config.bar.focused.border = get_colorpixel(conn, "#4c7899");
config.bar.focused.background = get_colorpixel(conn, "#285577");
config.bar.focused.text = get_colorpixel(conn, "#ffffff");
config.bar.unfocused.border = get_colorpixel(conn, "#333333");
config.bar.unfocused.background = get_colorpixel(conn, "#222222");
config.bar.unfocused.text = get_colorpixel(conn, "#888888");
config.bar.urgent.border = get_colorpixel(conn, "#2f343a");
config.bar.urgent.background = get_colorpixel(conn, "#900000");
config.bar.urgent.text = get_colorpixel(conn, "#ffffff");
if (config_use_lexer) {
/* Yes, this will be cleaned up soon. */
if (override_configpath != NULL) {
parse_file(override_configpath);
} else {
FILE *handle;
char *globbed = glob_path("~/.i3/config");
if ((handle = fopen(globbed, "r")) == NULL) {
if ((handle = fopen("/etc/i3/config", "r")) == NULL) {
die("Neither \"%s\" nor /etc/i3/config could be opened\n", globbed);
} else {
parse_file("/etc/i3/config");
}
} else {
parse_file(globbed);
}
}
if (reload)
grab_all_keys(conn);
} else {
FILE *handle;
if (override_configpath != NULL) {
if ((handle = fopen(override_configpath, "r")) == NULL)
die("Could not open configfile \"%s\".\n", override_configpath);
} else {
/* We first check for ~/.i3/config, then for /etc/i3/config */
char *globbed = glob_path("~/.i3/config");
if ((handle = fopen(globbed, "r")) == NULL)
if ((handle = fopen("/etc/i3/config", "r")) == NULL)
die("Neither \"%s\" nor /etc/i3/config could be opened\n", globbed);
free(globbed);
}
char key[512], value[512], buffer[1026];
while (!feof(handle)) {
if (fgets(buffer, 1024, handle) == NULL) {
/* fgets returns NULL on EOF and on error, so see which one it is. */
if (feof(handle))
break;
die("Could not read configuration file\n");
}
if (config.terminal != NULL)
replace_variable(buffer, "$terminal", config.terminal);
/* Replace all custom variables */
struct Variable *current;
SLIST_FOREACH(current, &variables, variables)
replace_variable(buffer, current->key, current->value);
/* sscanf implicitly strips whitespace. Also, we skip comments and empty lines. */
if (sscanf(buffer, "%s %[^\n]", key, value) < 1 ||
key[0] == '#' || strlen(key) < 3)
continue;
OPTION_STRING(terminal);
OPTION_STRING(font);
/* Colors */
OPTION_COLORTRIPLE("client.focused", client.focused);
OPTION_COLORTRIPLE("client.focused_inactive", client.focused_inactive);
OPTION_COLORTRIPLE("client.unfocused", client.unfocused);
OPTION_COLORTRIPLE("client.urgent", client.urgent);
OPTION_COLORTRIPLE("bar.focused", bar.focused);
OPTION_COLORTRIPLE("bar.unfocused", bar.unfocused);
OPTION_COLORTRIPLE("bar.urgent", bar.urgent);
/* exec-lines (autostart) */
if (strcasecmp(key, "exec") == 0) {
struct Autostart *new = smalloc(sizeof(struct Autostart));
new->command = sstrdup(value);
TAILQ_INSERT_TAIL(&autostarts, new, autostarts);
continue;
}
/* key bindings */
if (strcasecmp(key, "bind") == 0 || strcasecmp(key, "bindsym") == 0) {
#define CHECK_MODIFIER(name) \
if (strncasecmp(walk, #name, strlen(#name)) == 0) { \
modifiers |= BIND_##name; \
walk += strlen(#name) + 1; \
continue; \
}
char *walk = value, *rest;
uint32_t modifiers = 0;
while (*walk != '\0') {
/* Need to check for Mod1-5, Ctrl, Shift, Mode_switch */
CHECK_MODIFIER(SHIFT);
CHECK_MODIFIER(CONTROL);
CHECK_MODIFIER(MODE_SWITCH);
CHECK_MODIFIER(MOD1);
CHECK_MODIFIER(MOD2);
CHECK_MODIFIER(MOD3);
CHECK_MODIFIER(MOD4);
CHECK_MODIFIER(MOD5);
/* No modifier found? Then were done with this step */
break;
}
Binding *new = scalloc(sizeof(Binding));
/* Now check for the keycode or copy the symbol */
if (strcasecmp(key, "bind") == 0) {
int keycode = strtol(walk, &rest, 10);
if (!rest || *rest != ' ')
die("Invalid binding (keycode)\n");
new->keycode = keycode;
} else {
rest = walk;
char *sym = rest;
while (*rest != '\0' && *rest != ' ')
rest++;
if (*rest != ' ')
die("Invalid binding (keysym)\n");
#if defined(__OpenBSD__)
size_t len = strlen(sym);
if (len > (rest - sym))
len = (rest - sym);
new->symbol = smalloc(len + 1);
memcpy(new->symbol, sym, len+1);
new->symbol[len]='\0';
#else
new->symbol = strndup(sym, (rest - sym));
#endif
}
rest++;
LOG("keycode = %d, symbol = %s, modifiers = %d, command = *%s*\n", new->keycode, new->symbol, modifiers, rest);
new->mods = modifiers;
new->command = sstrdup(rest);
TAILQ_INSERT_TAIL(bindings, new, bindings);
continue;
}
if (strcasecmp(key, "floating_modifier") == 0) {
char *walk = value;
uint32_t modifiers = 0;
while (*walk != '\0') {
/* Need to check for Mod1-5, Ctrl, Shift, Mode_switch */
CHECK_MODIFIER(SHIFT);
CHECK_MODIFIER(CONTROL);
CHECK_MODIFIER(MODE_SWITCH);
CHECK_MODIFIER(MOD1);
CHECK_MODIFIER(MOD2);
CHECK_MODIFIER(MOD3);
CHECK_MODIFIER(MOD4);
CHECK_MODIFIER(MOD5);
/* No modifier found? Then were done with this step */
break;
}
LOG("Floating modifiers = %d\n", modifiers);
config.floating_modifier = modifiers;
continue;
}
/* workspace "workspace number" [screen <screen>] ["name of the workspace"]
* with screen := <number> | <position>, e.g. screen 1280 or screen 1 */
if (strcasecmp(key, "name") == 0 || strcasecmp(key, "workspace") == 0) {
LOG("workspace: %s\n",value);
char *ws_str = sstrdup(value);
char *end = strchr(ws_str, ' ');
if (end == NULL)
die("Malformed name, couln't find terminating space\n");
*end = '\0';
/* Strip trailing whitespace */
while (strlen(value) > 0 && value[strlen(value)-1] == ' ')
value[strlen(value)-1] = '\0';
int ws_num = atoi(ws_str);
if (ws_num < 1 || ws_num > 10)
die("Malformed name, invalid workspace number\n");
/* find the name */
char *name = value;
name += strlen(ws_str) + 1;
if (strncasecmp(name, "screen ", strlen("screen ")) == 0) {
char *screen = strdup(name + strlen("screen "));
if ((end = strchr(screen, ' ')) != NULL)
*end = '\0';
LOG("Setting preferred screen for workspace %d to \"%s\"\n", ws_num, screen);
workspace_get(ws_num-1)->preferred_screen = screen;
name += strlen("screen ") + strlen(screen);
}
/* Strip leading whitespace */
while (*name != '\0' && *name == ' ')
name++;
LOG("rest to parse = %s\n", name);
if (name == '\0') {
free(ws_str);
continue;
}
LOG("setting name to \"%s\"\n", name);
if (*name != '\0')
workspace_set_name(workspace_get(ws_num - 1), name);
free(ws_str);
continue;
}
/* assign window class[/window title] → workspace */
if (strcasecmp(key, "assign") == 0) {
LOG("assign: \"%s\"\n", value);
char *class_title;
char *target;
char *end;
/* If the window class/title is quoted we skip quotes */
if (value[0] == '"') {
class_title = sstrdup(value+1);
end = strchr(class_title, '"');
} else {
class_title = sstrdup(value);
/* If it is not quoted, we terminate it at the first space */
end = strchr(class_title, ' ');
}
if (end == NULL)
die("Malformed assignment, couldn't find terminating quote\n");
*end = '\0';
/* Strip trailing whitespace */
while (strlen(value) > 0 && value[strlen(value)-1] == ' ')
value[strlen(value)-1] = '\0';
/* Strip trailing whitespace */
while (strlen(value) > 0 && value[strlen(value)-1] == ' ')
value[strlen(value)-1] = '\0';
/* The target is the last argument separated by a space */
if ((target = strrchr(value, ' ')) == NULL)
die("Malformed assignment, couldn't find target (\"%s\")\n", value);
target++;
if (strchr(target, '~') == NULL && (atoi(target) < 1 || atoi(target) > 10))
die("Malformed assignment, invalid workspace number\n");
LOG("assignment parsed: \"%s\" to \"%s\"\n", class_title, target);
struct Assignment *new = scalloc(sizeof(struct Assignment));
new->windowclass_title = class_title;
if (strchr(target, '~') != NULL)
new->floating = ASSIGN_FLOATING_ONLY;
while (*target == '~')
target++;
if (atoi(target) >= 1) {
if (new->floating == ASSIGN_FLOATING_ONLY)
new->floating = ASSIGN_FLOATING;
new->workspace = atoi(target);
}
TAILQ_INSERT_TAIL(&assignments, new, assignments);
LOG("Assignment loaded: \"%s\":\n", class_title);
if (new->floating != ASSIGN_FLOATING_ONLY)
LOG(" to workspace %d\n", new->workspace);
if (new->floating != ASSIGN_FLOATING_NO)
LOG(" will be floating\n");
continue;
}
/* set a custom variable */
if (strcasecmp(key, "set") == 0) {
if (value[0] != '$')
die("Malformed variable assignment, name has to start with $\n");
/* get key/value for this variable */
char *v_key = value, *v_value;
if ((v_value = strstr(value, " ")) == NULL)
die("Malformed variable assignment, need a value\n");
*(v_value++) = '\0';
struct Variable *new = scalloc(sizeof(struct Variable));
new->key = sstrdup(v_key);
new->value = sstrdup(v_value);
SLIST_INSERT_HEAD(&variables, new, variables);
LOG("Got new variable %s = %s\n", v_key, v_value);
continue;
}
if (strcasecmp(key, "ipc-socket") == 0) {
config.ipc_socket_path = sstrdup(value);
continue;
}
die("Unknown configfile option: %s\n", key);
}
/* now grab all keys again */
if (reload)
grab_all_keys(conn);
fclose(handle);
while (!SLIST_EMPTY(&variables)) {
struct Variable *v = SLIST_FIRST(&variables);
SLIST_REMOVE_HEAD(&variables, variables);
free(v->key);
free(v->value);
free(v);
}
if (reload) {
translate_keysyms();
grab_all_keys(conn, false);
}
REQUIRED_OPTION(terminal);
REQUIRED_OPTION(font);
/* Set an empty name for every workspace which got no name */
@ -618,6 +398,4 @@ void load_configuration(xcb_connection_t *conn, const char *override_configpath,
workspace_set_name(ws, NULL);
}
return;
}

43
src/container.c Normal file
View File

@ -0,0 +1,43 @@
/*
* vim:ts=8:expandtab
*
* i3 - an improved dynamic tiling window manager
*
* © 2009 Michael Stapelberg and contributors
*
* See file LICENSE for license information.
*
*/
#include "data.h"
#include "log.h"
/*
* Returns the mode of the given container (or MODE_DEFAULT if a NULL pointer
* was passed in order to save a few explicit checks in other places). If
* for_frame was set to true, the special case of having exactly one client
* in a container is handled so that MODE_DEFAULT is returned. For some parts
* of the rendering, this is interesting, other parts need the real mode.
*
*/
int container_mode(Container *con, bool for_frame) {
int num_clients = 0;
Client *client;
if (con == NULL || con->mode == MODE_DEFAULT)
return MODE_DEFAULT;
if (!for_frame)
return con->mode;
CIRCLEQ_FOREACH(client, &(con->clients), clients)
num_clients++;
/* If the container contains only one client, mode is irrelevant */
if (num_clients == 1) {
DLOG("mode to default\n");
return MODE_DEFAULT;
}
return con->mode;
}

View File

@ -3,7 +3,7 @@
*
* i3 - an improved dynamic tiling window manager
*
* © 2009 Michael Stapelberg and contributors
* © 2009-2010 Michael Stapelberg and contributors
*
* See file LICENSE for license information.
*
@ -14,6 +14,8 @@
#include <stdio.h>
#include <xcb/xcb.h>
#include "log.h"
static const char *labelError[] = {
"Success",
"BadRequest",
@ -219,19 +221,21 @@ int format_event(xcb_generic_event_t *e) {
switch(e->response_type) {
case 0:
printf("Error %s on seqnum %d (%s).\n",
DLOG("Error %s on seqnum %d (%s).\n",
labelError[*((uint8_t *) e + 1)],
seqnum,
labelRequest[*((uint8_t *) e + 10)]);
break;
default:
printf("Event %s following seqnum %d%s.\n",
if (e->response_type > sizeof(labelEvent) / sizeof(char*))
break;
DLOG("Event %s following seqnum %d%s.\n",
labelEvent[e->response_type],
seqnum,
labelSendEvent[sendEvent]);
break;
case XCB_KEYMAP_NOTIFY:
printf("Event %s%s.\n",
DLOG("Event %s%s.\n",
labelEvent[e->response_type],
labelSendEvent[sendEvent]);
break;

103
src/ewmh.c Normal file
View File

@ -0,0 +1,103 @@
/*
* vim:ts=8:expandtab
*
* i3 - an improved dynamic tiling window manager
*
* © 2009 Michael Stapelberg and contributors
*
* See file LICENSE for license information.
*
* ewmh.c: Functions to get/set certain EWMH properties easily.
*
*/
#include <stdint.h>
#include <string.h>
#include <stdlib.h>
#include "data.h"
#include "table.h"
#include "i3.h"
#include "xcb.h"
#include "util.h"
#include "log.h"
/*
* Updates _NET_CURRENT_DESKTOP with the current desktop number.
*
* EWMH: The index of the current desktop. This is always an integer between 0
* and _NET_NUMBER_OF_DESKTOPS - 1.
*
*/
void ewmh_update_current_desktop() {
uint32_t current_desktop = c_ws->num;
xcb_change_property(global_conn, XCB_PROP_MODE_REPLACE, root,
atoms[_NET_CURRENT_DESKTOP], CARDINAL, 32, 1,
&current_desktop);
}
/*
* Updates _NET_ACTIVE_WINDOW with the currently focused window.
*
* EWMH: The window ID of the currently active window or None if no window has
* the focus.
*
*/
void ewmh_update_active_window(xcb_window_t window) {
xcb_change_property(global_conn, XCB_PROP_MODE_REPLACE, root,
atoms[_NET_ACTIVE_WINDOW], WINDOW, 32, 1, &window);
}
/*
* Updates the workarea for each desktop.
*
* EWMH: Contains a geometry for each desktop. These geometries specify an area
* that is completely contained within the viewport. Work area SHOULD be used by
* desktop applications to place desktop icons appropriately.
*
*/
void ewmh_update_workarea() {
Workspace *ws;
int num_workspaces = 0, count = 0;
Rect last_rect = {0, 0, 0, 0};
/* Get the number of workspaces */
TAILQ_FOREACH(ws, workspaces, workspaces) {
/* Check if we need to initialize last_rect. The case that the
* first workspace is all-zero may happen when the user
* assigned workspace 2 for his first screen, for example. Thus
* we need an initialized last_rect in the very first run of
* the following loop. */
if (last_rect.width == 0 && last_rect.height == 0 &&
ws->rect.width != 0 && ws->rect.height != 0) {
memcpy(&last_rect, &(ws->rect), sizeof(Rect));
}
num_workspaces++;
}
DLOG("Got %d workspaces\n", num_workspaces);
uint8_t *workarea = smalloc(sizeof(Rect) * num_workspaces);
TAILQ_FOREACH(ws, workspaces, workspaces) {
DLOG("storing %d: %dx%d with %d x %d\n", count, ws->rect.x,
ws->rect.y, ws->rect.width, ws->rect.height);
/* If a workspace is not yet initialized and thus its
* dimensions are zero, we will instead put the dimensions
* of the last workspace in the list. For example firefox
* intersects all workspaces and does not cope so well with
* an all-zero workspace. */
if (ws->rect.width == 0 || ws->rect.height == 0) {
DLOG("re-using last_rect (%dx%d, %d, %d)\n",
last_rect.x, last_rect.y, last_rect.width,
last_rect.height);
memcpy(workarea + (sizeof(Rect) * count++), &last_rect, sizeof(Rect));
continue;
}
memcpy(workarea + (sizeof(Rect) * count++), &(ws->rect), sizeof(Rect));
memcpy(&last_rect, &(ws->rect), sizeof(Rect));
}
xcb_change_property(global_conn, XCB_PROP_MODE_REPLACE, root,
atoms[_NET_WORKAREA], CARDINAL, 32,
num_workspaces * (sizeof(Rect) / sizeof(uint32_t)),
workarea);
free(workarea);
xcb_flush(global_conn);
}

View File

@ -3,7 +3,7 @@
*
* i3 - an improved dynamic tiling window manager
*
* © 2009 Michael Stapelberg and contributors
* © 2009-2010 Michael Stapelberg and contributors
*
* See file LICENSE for license information.
*
@ -27,6 +27,7 @@
#include "client.h"
#include "floating.h"
#include "workspace.h"
#include "log.h"
/*
* Toggles floating mode for the given client.
@ -42,12 +43,12 @@ void toggle_floating_mode(xcb_connection_t *conn, Client *client, bool automatic
i3Font *font = load_font(conn, config.font);
if (client->dock) {
LOG("Not putting dock client into floating mode\n");
DLOG("Not putting dock client into floating mode\n");
return;
}
if (con == NULL) {
LOG("This client is already in floating (container == NULL), re-inserting\n");
DLOG("This client is already in floating (container == NULL), re-inserting\n");
Client *next_tiling;
Workspace *ws = client->workspace;
SLIST_FOREACH(next_tiling, &(ws->focus_stack), focus_clients)
@ -62,7 +63,7 @@ void toggle_floating_mode(xcb_connection_t *conn, Client *client, bool automatic
/* Remove the client from the list of floating clients */
TAILQ_REMOVE(&(ws->floating_clients), client, floating_clients);
LOG("destination container = %p\n", con);
DLOG("destination container = %p\n", con);
Client *old_focused = con->currently_focused;
/* Preserve position/size */
memcpy(&(client->floating_rect), &(client->rect), sizeof(Rect));
@ -74,7 +75,7 @@ void toggle_floating_mode(xcb_connection_t *conn, Client *client, bool automatic
CIRCLEQ_INSERT_AFTER(&(con->clients), old_focused, client, clients);
else CIRCLEQ_INSERT_TAIL(&(con->clients), client, clients);
LOG("Re-inserted the client into the matrix.\n");
DLOG("Re-inserted the window.\n");
con->currently_focused = client;
client_set_below_floating(conn, client);
@ -85,7 +86,7 @@ void toggle_floating_mode(xcb_connection_t *conn, Client *client, bool automatic
return;
}
LOG("Entering floating for client %08x\n", client->child);
DLOG("Entering floating for client %08x\n", client->child);
/* Remove the client of its container */
client_remove_from_container(conn, client, con, false);
@ -95,7 +96,7 @@ void toggle_floating_mode(xcb_connection_t *conn, Client *client, bool automatic
TAILQ_INSERT_TAIL(&(client->workspace->floating_clients), client, floating_clients);
if (con->currently_focused == client) {
LOG("Need to re-adjust currently_focused\n");
DLOG("Need to re-adjust currently_focused\n");
/* Get the next client in the focus stack for this particular container */
con->currently_focused = get_last_focused_client(conn, con, NULL);
}
@ -118,11 +119,11 @@ void toggle_floating_mode(xcb_connection_t *conn, Client *client, bool automatic
client->rect.width = client->child_rect.width + 2 + 2;
client->rect.height = client->child_rect.height + (font->height + 2 + 2) + 2;
LOG("copying size from tiling (%d, %d) size (%d, %d)\n", client->floating_rect.x, client->floating_rect.y,
DLOG("copying size from tiling (%d, %d) size (%d, %d)\n", client->floating_rect.x, client->floating_rect.y,
client->floating_rect.width, client->floating_rect.height);
} else {
/* If the client was already in floating before we restore the old position / size */
LOG("using: (%d, %d) size (%d, %d)\n", client->floating_rect.x, client->floating_rect.y,
DLOG("using: (%d, %d) size (%d, %d)\n", client->floating_rect.x, client->floating_rect.y,
client->floating_rect.width, client->floating_rect.height);
memcpy(&(client->rect), &(client->floating_rect), sizeof(Rect));
}
@ -161,6 +162,69 @@ void floating_assign_to_workspace(Client *client, Workspace *new_workspace) {
client->workspace->fullscreen_client = client;
}
/*
* This is an ugly data structure which we need because there is no standard
* way of having nested functions (only available as a gcc extension at the
* moment, clang doesnt support it) or blocks (only available as a clang
* extension and only on Mac OS X systems at the moment).
*
*/
struct resize_callback_params {
border_t border;
xcb_button_press_event_t *event;
};
DRAGGING_CB(resize_callback) {
struct resize_callback_params *params = extra;
xcb_button_press_event_t *event = params->event;
switch (params->border) {
case BORDER_RIGHT: {
int new_width = old_rect->width + (new_x - event->root_x);
if ((new_width < 0) ||
(new_width < client_min_width(client) && client->rect.width >= new_width))
return;
client->rect.width = new_width;
break;
}
case BORDER_BOTTOM: {
int new_height = old_rect->height + (new_y - event->root_y);
if ((new_height < 0) ||
(new_height < client_min_height(client) && client->rect.height >= new_height))
return;
client->rect.height = old_rect->height + (new_y - event->root_y);
break;
}
case BORDER_TOP: {
int new_height = old_rect->height + (event->root_y - new_y);
if ((new_height < 0) ||
(new_height < client_min_height(client) && client->rect.height >= new_height))
return;
client->rect.y = old_rect->y + (new_y - event->root_y);
client->rect.height = new_height;
break;
}
case BORDER_LEFT: {
int new_width = old_rect->width + (event->root_x - new_x);
if ((new_width < 0) ||
(new_width < client_min_width(client) && client->rect.width >= new_width))
return;
client->rect.x = old_rect->x + (new_x - event->root_x);
client->rect.width = new_width;
break;
}
}
/* Push the new position/size to X11 */
reposition_client(conn, client);
resize_client(conn, client);
xcb_flush(conn);
}
/*
* Called whenever the user clicks on a border (not the titlebar!) of a floating window.
* Determines on which border the user clicked and launches the drag_pointer function
@ -168,59 +232,10 @@ void floating_assign_to_workspace(Client *client, Workspace *new_workspace) {
*
*/
int floating_border_click(xcb_connection_t *conn, Client *client, xcb_button_press_event_t *event) {
LOG("floating border click\n");
DLOG("floating border click\n");
border_t border;
void resize_callback(Rect *old_rect, uint32_t new_x, uint32_t new_y) {
switch (border) {
case BORDER_RIGHT: {
int new_width = old_rect->width + (new_x - event->root_x);
if ((new_width < 0) ||
(new_width < 50 && client->rect.width >= new_width))
return;
client->rect.width = new_width;
break;
}
case BORDER_BOTTOM: {
int new_height = old_rect->height + (new_y - event->root_y);
if ((new_height < 0) ||
(new_height < 20 && client->rect.height >= new_height))
return;
client->rect.height = old_rect->height + (new_y - event->root_y);
break;
}
case BORDER_TOP: {
int new_height = old_rect->height + (event->root_y - new_y);
if ((new_height < 0) ||
(new_height < 20 && client->rect.height >= new_height))
return;
client->rect.y = old_rect->y + (new_y - event->root_y);
client->rect.height = new_height;
break;
}
case BORDER_LEFT: {
int new_width = old_rect->width + (event->root_x - new_x);
if ((new_width < 0) ||
(new_width < 50 && client->rect.width >= new_width))
return;
client->rect.x = old_rect->x + (new_x - event->root_x);
client->rect.width = new_width;
break;
}
}
/* Push the new position/size to X11 */
reposition_client(conn, client);
resize_client(conn, client);
xcb_flush(conn);
}
if (event->event_y < 2)
border = BORDER_TOP;
else if (event->event_y >= (client->rect.height - 2))
@ -230,17 +245,31 @@ int floating_border_click(xcb_connection_t *conn, Client *client, xcb_button_pre
else if (event->event_x >= (client->rect.width - 2))
border = BORDER_RIGHT;
else {
LOG("Not on any border, not doing anything.\n");
DLOG("Not on any border, not doing anything.\n");
return 1;
}
LOG("border = %d\n", border);
DLOG("border = %d\n", border);
drag_pointer(conn, client, event, XCB_NONE, border, resize_callback);
struct resize_callback_params params = { border, event };
drag_pointer(conn, client, event, XCB_NONE, border, resize_callback, &params);
return 1;
}
DRAGGING_CB(drag_window_callback) {
struct xcb_button_press_event_t *event = extra;
/* Reposition the client correctly while moving */
client->rect.x = old_rect->x + (new_x - event->root_x);
client->rect.y = old_rect->y + (new_y - event->root_y);
reposition_client(conn, client);
/* Because reposition_client does not send a faked configure event (only resize does),
* we need to initiate that on our own */
fake_absolute_configure_notify(conn, client);
/* fake_absolute_configure_notify flushes */
}
/*
* Called when the user clicked on the titlebar of a floating window.
@ -248,47 +277,95 @@ int floating_border_click(xcb_connection_t *conn, Client *client, xcb_button_pre
*
*/
void floating_drag_window(xcb_connection_t *conn, Client *client, xcb_button_press_event_t *event) {
LOG("floating_drag_window\n");
DLOG("floating_drag_window\n");
void drag_window_callback(Rect *old_rect, uint32_t new_x, uint32_t new_y) {
/* Reposition the client correctly while moving */
client->rect.x = old_rect->x + (new_x - event->root_x);
client->rect.y = old_rect->y + (new_y - event->root_y);
reposition_client(conn, client);
/* Because reposition_client does not send a faked configure event (only resize does),
* we need to initiate that on our own */
fake_absolute_configure_notify(conn, client);
/* fake_absolute_configure_notify flushes */
}
drag_pointer(conn, client, event, XCB_NONE, BORDER_TOP /* irrelevant */, drag_window_callback);
drag_pointer(conn, client, event, XCB_NONE, BORDER_TOP /* irrelevant */, drag_window_callback, event);
}
/*
* Called when the user right-clicked on the titlebar of a floating window to
* resize it.
* This is an ugly data structure which we need because there is no standard
* way of having nested functions (only available as a gcc extension at the
* moment, clang doesnt support it) or blocks (only available as a clang
* extension and only on Mac OS X systems at the moment).
*
*/
struct resize_window_callback_params {
border_t corner;
bool proportional;
xcb_button_press_event_t *event;
};
DRAGGING_CB(resize_window_callback) {
struct resize_window_callback_params *params = extra;
xcb_button_press_event_t *event = params->event;
border_t corner = params->corner;
int32_t dest_x = client->rect.x;
int32_t dest_y = client->rect.y;
uint32_t dest_width;
uint32_t dest_height;
double ratio = (double) old_rect->width / old_rect->height;
/* First guess: We resize by exactly the amount the mouse moved,
* taking into account in which corner the client was grabbed */
if (corner & BORDER_LEFT)
dest_width = old_rect->width - (new_x - event->root_x);
else dest_width = old_rect->width + (new_x - event->root_x);
if (corner & BORDER_TOP)
dest_height = old_rect->height - (new_y - event->root_y);
else dest_height = old_rect->height + (new_y - event->root_y);
/* Obey minimum window size */
dest_width = max(dest_width, client_min_width(client));
dest_height = max(dest_height, client_min_height(client));
/* User wants to keep proportions, so we may have to adjust our values */
if (params->proportional) {
dest_width = max(dest_width, (int) (dest_height * ratio));
dest_height = max(dest_height, (int) (dest_width / ratio));
}
/* If not the lower right corner is grabbed, we must also reposition
* the client by exactly the amount we resized it */
if (corner & BORDER_LEFT)
dest_x = old_rect->x + (old_rect->width - dest_width);
if (corner & BORDER_TOP)
dest_y = old_rect->y + (old_rect->height - dest_height);
client->rect = (Rect) { dest_x, dest_y, dest_width, dest_height };
/* resize_client flushes */
resize_client(conn, client);
}
/*
* Called when the user clicked on a floating window while holding the
* floating_modifier and the right mouse button.
* Calls the drag_pointer function with the resize_window callback
*
*/
void floating_resize_window(xcb_connection_t *conn, Client *client, xcb_button_press_event_t *event) {
LOG("floating_resize_window\n");
void floating_resize_window(xcb_connection_t *conn, Client *client,
bool proportional, xcb_button_press_event_t *event) {
DLOG("floating_resize_window\n");
void resize_window_callback(Rect *old_rect, uint32_t new_x, uint32_t new_y) {
int32_t new_width = old_rect->width + (new_x - event->root_x);
int32_t new_height = old_rect->height + (new_y - event->root_y);
/* corner saves the nearest corner to the original click. It contains
* a bitmask of the nearest borders (BORDER_LEFT, BORDER_RIGHT, …) */
border_t corner = 0;
/* Obey minimum window size and reposition the client */
if (new_width >= 50)
client->rect.width = new_width;
if (event->event_x <= (client->rect.width / 2))
corner |= BORDER_LEFT;
else corner |= BORDER_RIGHT;
if (new_height >= 20)
client->rect.height = new_height;
if (event->event_y <= (client->rect.height / 2))
corner |= BORDER_TOP;
else corner |= BORDER_RIGHT;
/* resize_client flushes */
resize_client(conn, client);
}
struct resize_window_callback_params params = { corner, proportional, event };
drag_pointer(conn, client, event, XCB_NONE, BORDER_TOP /* irrelevant */, resize_window_callback);
drag_pointer(conn, client, event, XCB_NONE, BORDER_TOP /* irrelevant */, resize_window_callback, &params);
}
@ -301,7 +378,7 @@ void floating_resize_window(xcb_connection_t *conn, Client *client, xcb_button_p
*
*/
void drag_pointer(xcb_connection_t *conn, Client *client, xcb_button_press_event_t *event,
xcb_window_t confine_to, border_t border, callback_t callback) {
xcb_window_t confine_to, border_t border, callback_t callback, void *extra) {
xcb_window_t root = xcb_setup_roots_iterator(xcb_get_setup(conn)).data->root;
uint32_t new_x, new_y;
Rect old_rect;
@ -351,12 +428,12 @@ void drag_pointer(xcb_connection_t *conn, Client *client, xcb_button_press_event
break;
case XCB_UNMAP_NOTIFY:
LOG("Unmap-notify, aborting\n");
DLOG("Unmap-notify, aborting\n");
xcb_event_handle(&evenths, inside_event);
goto done;
default:
LOG("Passing to original handler\n");
DLOG("Passing to original handler\n");
/* Use original handler */
xcb_event_handle(&evenths, inside_event);
break;
@ -371,7 +448,7 @@ void drag_pointer(xcb_connection_t *conn, Client *client, xcb_button_press_event
new_x = ((xcb_motion_notify_event_t*)last_motion_notify)->root_x;
new_y = ((xcb_motion_notify_event_t*)last_motion_notify)->root_y;
callback(&old_rect, new_x, new_y);
callback(conn, client, &old_rect, new_x, new_y, extra);
FREE(last_motion_notify);
}
done:
@ -387,7 +464,7 @@ done:
*
*/
void floating_focus_direction(xcb_connection_t *conn, Client *currently_focused, direction_t direction) {
LOG("floating focus\n");
DLOG("floating focus\n");
if (direction == D_LEFT || direction == D_RIGHT) {
/* Go to the next/previous floating client */
@ -409,10 +486,15 @@ void floating_focus_direction(xcb_connection_t *conn, Client *currently_focused,
*
*/
void floating_move(xcb_connection_t *conn, Client *currently_focused, direction_t direction) {
LOG("floating move\n");
DLOG("floating move\n");
if (currently_focused->fullscreen) {
DLOG("Cannot move fullscreen windows\n");
return;
}
Rect destination = currently_focused->rect;
Rect *screen = &(currently_focused->workspace->screen->rect);
Rect *screen = &(currently_focused->workspace->output->rect);
switch (direction) {
case D_LEFT:
@ -437,7 +519,7 @@ void floating_move(xcb_connection_t *conn, Client *currently_focused, direction_
(int32_t)(destination.x + 5) >= (int32_t)(screen->x + screen->width) ||
(int32_t)(destination.y + destination.height - 5) <= (int32_t)screen->y ||
(int32_t)(destination.y + 5) >= (int32_t)(screen->y + screen->height)) {
LOG("boundary check failed, not moving\n");
DLOG("boundary check failed, not moving\n");
return;
}
@ -459,7 +541,7 @@ void floating_toggle_hide(xcb_connection_t *conn, Workspace *workspace) {
Client *client;
workspace->floating_hidden = !workspace->floating_hidden;
LOG("floating_hidden is now: %d\n", workspace->floating_hidden);
DLOG("floating_hidden is now: %d\n", workspace->floating_hidden);
TAILQ_FOREACH(client, &(workspace->floating_clients), floating_clients) {
if (workspace->floating_hidden)
client_unmap(conn, client);

View File

@ -3,7 +3,7 @@
*
* i3 - an improved dynamic tiling window manager
*
* © 2009 Michael Stapelberg and contributors
* © 2009-2010 Michael Stapelberg and contributors
*
* See file LICENSE for license information.
*
@ -17,6 +17,7 @@
#include <xcb/xcb.h>
#include <xcb/xcb_atom.h>
#include <xcb/xcb_icccm.h>
#include <xcb/randr.h>
#include <X11/XKBlib.h>
@ -28,7 +29,7 @@
#include "data.h"
#include "xcb.h"
#include "util.h"
#include "xinerama.h"
#include "randr.h"
#include "config.h"
#include "queue.h"
#include "resize.h"
@ -36,6 +37,9 @@
#include "manage.h"
#include "floating.h"
#include "workspace.h"
#include "log.h"
#include "container.h"
#include "ipc.h"
/* After mapping/unmapping windows, a notify event is generated. However, we dont want it,
since itd trigger an infinite loop of switching between the different windows when
@ -78,77 +82,45 @@ static bool event_is_ignored(const int sequence) {
return false;
}
/*
* Due to bindings like Mode_switch + <a>, we need to bind some keys in XCB_GRAB_MODE_SYNC.
* Therefore, we just replay all key presses.
*
*/
int handle_key_release(void *ignored, xcb_connection_t *conn, xcb_key_release_event_t *event) {
xcb_allow_events(conn, XCB_ALLOW_REPLAY_KEYBOARD, event->time);
xcb_flush(conn);
return 1;
}
/*
* There was a key press. We compare this key code with our bindings table and pass
* the bound action to parse_command().
*
*/
int handle_key_press(void *ignored, xcb_connection_t *conn, xcb_key_press_event_t *event) {
LOG("Keypress %d, state raw = %d\n", event->detail, event->state);
DLOG("Keypress %d, state raw = %d\n", event->detail, event->state);
/* Remove the numlock bit, all other bits are modifiers we can bind to */
uint16_t state_filtered = event->state & ~(xcb_numlock_mask | XCB_MOD_MASK_LOCK);
LOG("(removed numlock, state = %d)\n", state_filtered);
DLOG("(removed numlock, state = %d)\n", state_filtered);
/* Only use the lower 8 bits of the state (modifier masks) so that mouse
* button masks are filtered out */
state_filtered &= 0xFF;
LOG("(removed upper 8 bits, state = %d)\n", state_filtered);
DLOG("(removed upper 8 bits, state = %d)\n", state_filtered);
/* We need to get the keysym group (There are group 1 to group 4, each holding
two keysyms (without shift and with shift) using Xkb because X fails to
provide them reliably (it works in Xephyr, it does not in real X) */
XkbStateRec state;
if (XkbGetState(xkbdpy, XkbUseCoreKbd, &state) == Success && (state.group+1) == 2)
if (xkb_current_group == XkbGroup2Index)
state_filtered |= BIND_MODE_SWITCH;
LOG("(checked mode_switch, state %d)\n", state_filtered);
DLOG("(checked mode_switch, state %d)\n", state_filtered);
/* Find the binding */
Binding *bind;
TAILQ_FOREACH(bind, bindings, bindings) {
/* First compare the modifiers */
if (bind->mods != state_filtered)
continue;
Binding *bind = get_binding(state_filtered, event->detail);
/* If a symbol was specified by the user, we need to look in
* the array of translated keycodes for the events keycode */
if (bind->symbol != NULL) {
if (memmem(bind->translated_to,
bind->number_keycodes * sizeof(xcb_keycode_t),
&(event->detail), sizeof(xcb_keycode_t)) != NULL)
break;
} else {
/* This case is easier: The user specified a keycode */
if (bind->keycode == event->detail)
break;
/* No match? Then the user has Mode_switch enabled but does not have a
* specific keybinding. Fall back to the default keybindings (without
* Mode_switch). Makes it much more convenient for users of a hybrid
* layout (like us, ru). */
if (bind == NULL) {
state_filtered &= ~(BIND_MODE_SWITCH);
DLOG("no match, new state_filtered = %d\n", state_filtered);
if ((bind = get_binding(state_filtered, event->detail)) == NULL) {
ELOG("Could not lookup key binding (modifiers %d, keycode %d)\n",
state_filtered, event->detail);
return 1;
}
}
/* No match? Then it was an actively grabbed key, that is with Mode_switch, and
the user did not press Mode_switch, so just pass it… */
if (bind == TAILQ_END(bindings)) {
xcb_allow_events(conn, ReplayKeyboard, event->time);
xcb_flush(conn);
return 1;
}
parse_command(conn, bind->command);
if (state_filtered & BIND_MODE_SWITCH) {
LOG("Mode_switch -> allow_events(SyncKeyboard)\n");
xcb_allow_events(conn, SyncKeyboard, event->time);
xcb_flush(conn);
}
return 1;
}
@ -159,21 +131,39 @@ int handle_key_press(void *ignored, xcb_connection_t *conn, xcb_key_press_event_
*
*/
static void check_crossing_screen_boundary(uint32_t x, uint32_t y) {
i3Screen *screen;
Output *output;
if ((screen = get_screen_containing(x, y)) == NULL) {
LOG("ERROR: No such screen\n");
if ((output = get_output_containing(x, y)) == NULL) {
ELOG("ERROR: No such screen\n");
return;
}
if (screen == c_ws->screen)
if (output == c_ws->output)
return;
c_ws->current_row = current_row;
c_ws->current_col = current_col;
c_ws = screen->current_workspace;
c_ws = output->current_workspace;
current_row = c_ws->current_row;
current_col = c_ws->current_col;
LOG("We're now on virtual screen number %d\n", screen->num);
DLOG("We're now on output %p\n", output);
/* While usually this function is only called when the user switches
* to a different output using his mouse (and thus the output is
* empty), it may be that the following race condition occurs:
* 1) the user actives a new output (say VGA1).
* 2) the cursor is sent to the first pixel of the new VGA1, thus
* generating an enter_notify for the screen (the enter_notify
* is not yet received by i3).
* 3) i3 requeries screen configuration and maps a workspace onto the
* new output.
* 4) the enter_notify event arrives and c_ws is set to the new
* workspace but the existing windows on the new workspace are not
* focused.
*
* Therefore, we re-set the focus here to be sure its correct. */
Client *first_client = SLIST_FIRST(&(c_ws->focus_stack));
if (first_client != NULL)
set_focus(global_conn, first_client, true);
}
/*
@ -181,9 +171,9 @@ static void check_crossing_screen_boundary(uint32_t x, uint32_t y) {
*
*/
int handle_enter_notify(void *ignored, xcb_connection_t *conn, xcb_enter_notify_event_t *event) {
LOG("enter_notify for %08x, mode = %d, detail %d, serial %d\n", event->event, event->mode, event->detail, event->sequence);
DLOG("enter_notify for %08x, mode = %d, detail %d, serial %d\n", event->event, event->mode, event->detail, event->sequence);
if (event->mode != XCB_NOTIFY_MODE_NORMAL) {
LOG("This was not a normal notify, ignoring\n");
DLOG("This was not a normal notify, ignoring\n");
return 1;
}
/* Some events are not interesting, because they were not generated actively by the
@ -210,7 +200,7 @@ int handle_enter_notify(void *ignored, xcb_connection_t *conn, xcb_enter_notify_
/* If not, then the user moved his cursor to the root window. In that case, we adjust c_ws */
if (client == NULL) {
LOG("Getting screen at %d x %d\n", event->root_x, event->root_y);
DLOG("Getting screen at %d x %d\n", event->root_x, event->root_y);
check_crossing_screen_boundary(event->root_x, event->root_y);
return 1;
}
@ -220,19 +210,20 @@ int handle_enter_notify(void *ignored, xcb_connection_t *conn, xcb_enter_notify_
if (client->container != NULL &&
client->container->mode == MODE_STACK &&
client->container->currently_focused != client) {
LOG("Plausibility check says: no\n");
DLOG("Plausibility check says: no\n");
return 1;
}
if (client->workspace != c_ws && client->workspace->screen == c_ws->screen) {
if (client->workspace != c_ws && client->workspace->output == c_ws->output) {
/* This can happen when a client gets assigned to a different workspace than
* the current one (see src/mainx.c:reparent_window). Shortly after it was created,
* an enter_notify will follow. */
LOG("enter_notify for a client on a different workspace but the same screen, ignoring\n");
DLOG("enter_notify for a client on a different workspace but the same screen, ignoring\n");
return 1;
}
set_focus(conn, client, false);
if (!config.disable_focus_follows_mouse)
set_focus(conn, client, false);
return 1;
}
@ -264,13 +255,14 @@ int handle_mapping_notify(void *ignored, xcb_connection_t *conn, xcb_mapping_not
event->request != XCB_MAPPING_MODIFIER)
return 0;
LOG("Received mapping_notify for keyboard or modifier mapping, re-grabbing keys\n");
DLOG("Received mapping_notify for keyboard or modifier mapping, re-grabbing keys\n");
xcb_refresh_keyboard_mapping(keysyms, event);
xcb_get_numlock_mask(conn);
ungrab_all_keys(conn);
grab_all_keys(conn);
translate_keysyms();
grab_all_keys(conn, false);
return 0;
}
@ -284,7 +276,7 @@ int handle_map_request(void *prophs, xcb_connection_t *conn, xcb_map_request_eve
cookie = xcb_get_window_attributes_unchecked(conn, event->window);
LOG("window = 0x%08x, serial is %d.\n", event->window, event->sequence);
DLOG("window = 0x%08x, serial is %d.\n", event->window, event->sequence);
add_ignore_event(event->sequence);
manage_window(prophs, conn, event->window, cookie, false);
@ -298,7 +290,7 @@ int handle_map_request(void *prophs, xcb_connection_t *conn, xcb_map_request_eve
*
*/
int handle_configure_request(void *prophs, xcb_connection_t *conn, xcb_configure_request_event_t *event) {
LOG("window 0x%08x wants to be at %dx%d with %dx%d\n",
DLOG("window 0x%08x wants to be at %dx%d with %dx%d\n",
event->window, event->x, event->y, event->width, event->height);
Client *client = table_get(&by_child, event->window);
@ -328,7 +320,7 @@ int handle_configure_request(void *prophs, xcb_connection_t *conn, xcb_configure
}
if (client->fullscreen) {
LOG("Client is in fullscreen mode\n");
DLOG("Client is in fullscreen mode\n");
Rect child_rect = client->workspace->rect;
child_rect.x = child_rect.y = 0;
@ -389,7 +381,7 @@ int handle_configure_request(void *prophs, xcb_connection_t *conn, xcb_configure
}
}
LOG("Accepted new position/size for floating client: (%d, %d) size %d x %d\n",
DLOG("Accepted new position/size for floating client: (%d, %d) size %d x %d\n",
client->rect.x, client->rect.y, client->rect.width, client->rect.height);
/* Push the new position/size to X11 */
@ -402,22 +394,22 @@ int handle_configure_request(void *prophs, xcb_connection_t *conn, xcb_configure
/* Dock clients can be reconfigured in their height */
if (client->dock) {
LOG("Reconfiguring height of this dock client\n");
DLOG("Reconfiguring height of this dock client\n");
if (!(event->value_mask & XCB_CONFIG_WINDOW_HEIGHT)) {
LOG("Ignoring configure request, no height given\n");
DLOG("Ignoring configure request, no height given\n");
return 1;
}
client->desired_height = event->height;
render_workspace(conn, c_ws->screen, c_ws);
render_workspace(conn, c_ws->output, c_ws);
xcb_flush(conn);
return 1;
}
if (client->fullscreen) {
LOG("Client is in fullscreen mode\n");
DLOG("Client is in fullscreen mode\n");
Rect child_rect = client->container->workspace->rect;
child_rect.x = child_rect.y = 0;
@ -432,26 +424,30 @@ int handle_configure_request(void *prophs, xcb_connection_t *conn, xcb_configure
}
/*
* Configuration notifies are only handled because we need to set up ignore for the following
* enter notify events
* Configuration notifies are only handled because we need to set up ignore for
* the following enter notify events.
*
*/
int handle_configure_event(void *prophs, xcb_connection_t *conn, xcb_configure_notify_event_t *event) {
xcb_window_t root = xcb_setup_roots_iterator(xcb_get_setup(conn)).data->root;
/* We ignore this sequence twice because events for child and frame should be ignored */
add_ignore_event(event->sequence);
add_ignore_event(event->sequence);
if (event->event == root) {
LOG("event->x = %d, ->y = %d, ->width = %d, ->height = %d\n", event->x, event->y, event->width, event->height);
LOG("reconfigure of the root window, need to xinerama\n");
/* FIXME: Somehow, this is occuring too often. Therefore, we check for 0/0,
but is there a better way? */
if (event->x == 0 && event->y == 0)
xinerama_requery_screens(conn);
return 1;
}
return 1;
}
/*
* Gets triggered upon a RandR screen change event, that is when the user
* changes the screen configuration in any way (mode, position, …)
*
*/
int handle_screen_change(void *prophs, xcb_connection_t *conn,
xcb_generic_event_t *e) {
DLOG("RandR screen change\n");
randr_query_outputs(conn);
ipc_send_event("output", I3_IPC_EVENT_OUTPUT, "{\"change\":\"unspecified\"}");
return 1;
}
@ -474,10 +470,10 @@ int handle_unmap_notify_event(void *data, xcb_connection_t *conn, xcb_unmap_noti
return 1;
}
LOG("event->window = %08x, event->event = %08x\n", event->window, event->event);
LOG("UnmapNotify for 0x%08x (received from 0x%08x)\n", event->window, event->event);
DLOG("event->window = %08x, event->event = %08x\n", event->window, event->event);
DLOG("UnmapNotify for 0x%08x (received from 0x%08x)\n", event->window, event->event);
if (client == NULL) {
LOG("not a managed window. Ignoring.\n");
DLOG("not a managed window. Ignoring.\n");
/* This was most likely the destroyed frame of a client which is
* currently being unmapped, so we add this sequence (again!) to
@ -490,9 +486,14 @@ int handle_unmap_notify_event(void *data, xcb_connection_t *conn, xcb_unmap_noti
client = table_remove(&by_child, event->window);
/* If this was the fullscreen client, we need to unset it */
if (client->fullscreen)
client->workspace->fullscreen_client = NULL;
/* If this was the fullscreen client, we need to unset it from all
* workspaces it was on (global fullscreen) */
if (client->fullscreen) {
Workspace *ws;
TAILQ_FOREACH(ws, workspaces, workspaces)
if (ws->fullscreen_client == client)
ws->fullscreen_client = NULL;
}
/* Clients without a container are either floating or dock windows */
if (client->container != NULL) {
@ -508,17 +509,17 @@ int handle_unmap_notify_event(void *data, xcb_connection_t *conn, xcb_unmap_noti
if ((con->currently_focused != NULL) && ((con == CUR_CELL) || client->fullscreen))
set_focus(conn, con->currently_focused, true);
} else if (client_is_floating(client)) {
LOG("Removing from floating clients\n");
DLOG("Removing from floating clients\n");
TAILQ_REMOVE(&(client->workspace->floating_clients), client, floating_clients);
SLIST_REMOVE(&(client->workspace->focus_stack), client, Client, focus_clients);
}
if (client->dock) {
LOG("Removing from dock clients\n");
SLIST_REMOVE(&(client->workspace->screen->dock_clients), client, Client, dock_clients);
DLOG("Removing from dock clients\n");
SLIST_REMOVE(&(client->workspace->output->dock_clients), client, Client, dock_clients);
}
LOG("child of 0x%08x.\n", client->frame);
DLOG("child of 0x%08x.\n", client->frame);
xcb_reparent_window(conn, client->child, root, 0, 0);
client_unmap(conn, client);
@ -542,8 +543,10 @@ int handle_unmap_notify_event(void *data, xcb_connection_t *conn, xcb_unmap_noti
if (workspace_is_visible(client->workspace))
workspace_empty = false;
if (workspace_empty)
client->workspace->screen = NULL;
if (workspace_empty) {
client->workspace->output = NULL;
ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"empty\"}");
}
/* Remove the urgency flag if set */
client->urgent = false;
@ -564,7 +567,7 @@ int handle_unmap_notify_event(void *data, xcb_connection_t *conn, xcb_unmap_noti
if (to_focus != NULL)
set_focus(conn, to_focus, true);
else {
LOG("Restoring focus to root screen\n");
DLOG("Restoring focus to root screen\n");
xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, root, XCB_CURRENT_TIME);
xcb_flush(conn);
}
@ -573,6 +576,26 @@ int handle_unmap_notify_event(void *data, xcb_connection_t *conn, xcb_unmap_noti
return 1;
}
/*
* A destroy notify event is sent when the window is not unmapped, but
* immediately destroyed (for example when starting a window and immediately
* killing the program which started it).
*
* We just pass on the event to the unmap notify handler (by copying the
* important fields in the event data structure).
*
*/
int handle_destroy_notify_event(void *data, xcb_connection_t *conn, xcb_destroy_notify_event_t *event) {
DLOG("destroy notify for 0x%08x, 0x%08x\n", event->event, event->window);
xcb_unmap_notify_event_t unmap;
unmap.sequence = event->sequence;
unmap.event = event->event;
unmap.window = event->window;
return handle_unmap_notify_event(NULL, conn, &unmap);
}
/*
* Called when a window changes its title
*
@ -580,7 +603,7 @@ int handle_unmap_notify_event(void *data, xcb_connection_t *conn, xcb_unmap_noti
int handle_windowname_change(void *data, xcb_connection_t *conn, uint8_t state,
xcb_window_t window, xcb_atom_t atom, xcb_get_property_reply_t *prop) {
if (prop == NULL || xcb_get_property_value_length(prop) == 0) {
LOG("_NET_WM_NAME not specified, not changing\n");
DLOG("_NET_WM_NAME not specified, not changing\n");
return 1;
}
Client *client = table_get(&by_child, window);
@ -618,9 +641,11 @@ int handle_windowname_change(void *data, xcb_connection_t *conn, uint8_t state,
if (client->dock)
return 1;
if (client->container != NULL &&
(client->container->mode == MODE_STACK ||
client->container->mode == MODE_TABBED))
if (!workspace_is_visible(client->workspace))
return 1;
int mode = container_mode(client->container, true);
if (mode == MODE_STACK || mode == MODE_TABBED)
render_container(conn, client->container);
else decorate_window(conn, client, client->frame, client->titlegc, 0, 0);
xcb_flush(conn);
@ -642,7 +667,7 @@ int handle_windowname_change(void *data, xcb_connection_t *conn, uint8_t state,
int handle_windowname_change_legacy(void *data, xcb_connection_t *conn, uint8_t state,
xcb_window_t window, xcb_atom_t atom, xcb_get_property_reply_t *prop) {
if (prop == NULL || xcb_get_property_value_length(prop) == 0) {
LOG("prop == NULL\n");
DLOG("prop == NULL\n");
return 1;
}
Client *client = table_get(&by_child, window);
@ -657,7 +682,7 @@ int handle_windowname_change_legacy(void *data, xcb_connection_t *conn, uint8_t
char *new_name;
if (asprintf(&new_name, "%.*s", xcb_get_property_value_length(prop), (char*)xcb_get_property_value(prop)) == -1) {
perror("Could not get old name");
LOG("Could not get old name\n");
DLOG("Could not get old name\n");
return 1;
}
/* Convert it to UCS-2 here for not having to convert it later every time we want to pass it to X */
@ -685,6 +710,9 @@ int handle_windowname_change_legacy(void *data, xcb_connection_t *conn, uint8_t
if (client->dock)
return 1;
if (!workspace_is_visible(client->workspace))
return 1;
if (client->container != NULL &&
(client->container->mode == MODE_STACK ||
client->container->mode == MODE_TABBED))
@ -702,7 +730,7 @@ int handle_windowname_change_legacy(void *data, xcb_connection_t *conn, uint8_t
int handle_windowclass_change(void *data, xcb_connection_t *conn, uint8_t state,
xcb_window_t window, xcb_atom_t atom, xcb_get_property_reply_t *prop) {
if (prop == NULL || xcb_get_property_value_length(prop) == 0) {
LOG("prop == NULL\n");
DLOG("prop == NULL\n");
return 1;
}
Client *client = table_get(&by_child, window);
@ -736,7 +764,7 @@ int handle_expose_event(void *data, xcb_connection_t *conn, xcb_expose_event_t *
skip all events but the last one */
if (event->count != 0)
return 1;
LOG("window = %08x\n", event->window);
DLOG("window = %08x\n", event->window);
Client *client = table_get(&by_parent, event->window);
if (client == NULL) {
@ -750,9 +778,9 @@ int handle_expose_event(void *data, xcb_connection_t *conn, xcb_expose_event_t *
}
/* …or one of the bars? */
i3Screen *screen;
TAILQ_FOREACH(screen, virtual_screens, screens)
if (screen->bar == event->window)
Output *output;
TAILQ_FOREACH(output, &outputs, outputs)
if (output->bar == event->window)
render_layout(conn);
return 1;
}
@ -760,9 +788,7 @@ int handle_expose_event(void *data, xcb_connection_t *conn, xcb_expose_event_t *
if (client->dock)
return 1;
if (client->container == NULL ||
(client->container->mode != MODE_STACK &&
client->container->mode != MODE_TABBED))
if (container_mode(client->container, true) == MODE_DEFAULT)
decorate_window(conn, client, client->frame, client->titlegc, 0, 0);
else {
uint32_t background_color;
@ -787,7 +813,7 @@ int handle_expose_event(void *data, xcb_connection_t *conn, xcb_expose_event_t *
/* Draw a black background */
xcb_change_gc_single(conn, client->titlegc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#000000"));
if (client->titlebar_position == TITLEBAR_OFF) {
if (client->titlebar_position == TITLEBAR_OFF && !client->borderless) {
xcb_rectangle_t crect = {1, 0, client->rect.width - (1 + 1), client->rect.height - 1};
xcb_poly_fill_rectangle(conn, client->frame, client->titlegc, 1, &crect);
} else {
@ -821,7 +847,7 @@ int handle_client_message(void *data, xcb_connection_t *conn, xcb_client_message
event->data.data32[0] == _NET_WM_STATE_TOGGLE)))
client_toggle_fullscreen(conn, client);
} else {
LOG("unhandled clientmessage\n");
ELOG("unhandled clientmessage\n");
return 0;
}
@ -832,7 +858,7 @@ int handle_window_type(void *data, xcb_connection_t *conn, uint8_t state, xcb_wi
xcb_atom_t atom, xcb_get_property_reply_t *property) {
/* TODO: Implement this one. To do this, implement a little test program which sleep(1)s
before changing this property. */
LOG("_NET_WM_WINDOW_TYPE changed, this is not yet implemented.\n");
ELOG("_NET_WM_WINDOW_TYPE changed, this is not yet implemented.\n");
return 0;
}
@ -847,7 +873,7 @@ int handle_normal_hints(void *data, xcb_connection_t *conn, uint8_t state, xcb_w
xcb_atom_t name, xcb_get_property_reply_t *reply) {
Client *client = table_get(&by_child, window);
if (client == NULL) {
LOG("Received WM_SIZE_HINTS for unknown client\n");
DLOG("Received WM_SIZE_HINTS for unknown client\n");
return 1;
}
xcb_size_hints_t size_hints;
@ -862,27 +888,24 @@ int handle_normal_hints(void *data, xcb_connection_t *conn, uint8_t state, xcb_w
if ((size_hints.flags & XCB_SIZE_HINT_P_MIN_SIZE)) {
// TODO: Minimum size is not yet implemented
//LOG("Minimum size: %d (width) x %d (height)\n", size_hints.min_width, size_hints.min_height);
DLOG("Minimum size: %d (width) x %d (height)\n", size_hints.min_width, size_hints.min_height);
}
bool changed = false;
if ((size_hints.flags & XCB_SIZE_HINT_P_RESIZE_INC)) {
bool changed = false;
if (size_hints.width_inc > 0)
if (size_hints.width_inc > 0 && size_hints.width_inc < 0xFFFF)
if (client->width_increment != size_hints.width_inc) {
client->width_increment = size_hints.width_inc;
changed = true;
}
if (size_hints.height_inc > 0)
if (size_hints.height_inc > 0 && size_hints.height_inc < 0xFFFF)
if (client->height_increment != size_hints.height_inc) {
client->height_increment = size_hints.height_inc;
changed = true;
}
if (changed) {
resize_client(conn, client);
xcb_flush(conn);
}
if (changed)
DLOG("resize increments changed\n");
}
int base_width = 0, base_height = 0;
@ -890,10 +913,11 @@ int handle_normal_hints(void *data, xcb_connection_t *conn, uint8_t state, xcb_w
/* base_width/height are the desired size of the window.
We check if either the program-specified size or the program-specified
min-size is available */
if (size_hints.flags & XCB_SIZE_HINT_P_SIZE) {
if (size_hints.flags & XCB_SIZE_HINT_BASE_SIZE) {
base_width = size_hints.base_width;
base_height = size_hints.base_height;
} else if (size_hints.flags & XCB_SIZE_HINT_P_MIN_SIZE) {
/* TODO: is this right? icccm says not */
base_width = size_hints.min_width;
base_height = size_hints.min_height;
}
@ -902,11 +926,18 @@ int handle_normal_hints(void *data, xcb_connection_t *conn, uint8_t state, xcb_w
base_height != client->base_height) {
client->base_width = base_width;
client->base_height = base_height;
LOG("client's base_height changed to %d\n", base_height);
DLOG("client's base_height changed to %d\n", base_height);
DLOG("client's base_width changed to %d\n", base_width);
changed = true;
}
if (changed) {
if (client->fullscreen)
LOG("Not resizing client, it is in fullscreen mode\n");
else
DLOG("Not resizing client, it is in fullscreen mode\n");
else {
resize_client(conn, client);
xcb_flush(conn);
}
}
/* If no aspect ratio was set or if it was invalid, we ignore the hints */
@ -922,8 +953,8 @@ int handle_normal_hints(void *data, xcb_connection_t *conn, uint8_t state, xcb_w
double min_aspect = (double)size_hints.min_aspect_num / size_hints.min_aspect_den;
double max_aspect = (double)size_hints.max_aspect_num / size_hints.min_aspect_den;
LOG("Aspect ratio set: minimum %f, maximum %f\n", min_aspect, max_aspect);
LOG("width = %f, height = %f\n", width, height);
DLOG("Aspect ratio set: minimum %f, maximum %f\n", min_aspect, max_aspect);
DLOG("width = %f, height = %f\n", width, height);
/* Sanity checks, this is user-input, in a way */
if (max_aspect <= 0 || min_aspect <= 0 || height == 0 || (width / height) <= 0)
@ -940,7 +971,7 @@ int handle_normal_hints(void *data, xcb_connection_t *conn, uint8_t state, xcb_w
client->force_reconfigure = true;
if (client->container != NULL) {
if (client->container != NULL && workspace_is_visible(client->workspace)) {
render_container(conn, client->container);
xcb_flush(conn);
}
@ -956,7 +987,7 @@ int handle_hints(void *data, xcb_connection_t *conn, uint8_t state, xcb_window_t
xcb_atom_t name, xcb_get_property_reply_t *reply) {
Client *client = table_get(&by_child, window);
if (client == NULL) {
LOG("Received WM_HINTS for unknown client\n");
DLOG("Received WM_HINTS for unknown client\n");
return 1;
}
xcb_wm_hints_t hints;
@ -971,7 +1002,7 @@ int handle_hints(void *data, xcb_connection_t *conn, uint8_t state, xcb_window_t
Client *last_focused = SLIST_FIRST(&(c_ws->focus_stack));
if (!client->urgent && client == last_focused) {
LOG("Ignoring urgency flag for current client\n");
DLOG("Ignoring urgency flag for current client\n");
return 1;
}
@ -981,14 +1012,15 @@ int handle_hints(void *data, xcb_connection_t *conn, uint8_t state, xcb_window_t
LOG("Urgency flag changed to %d\n", client->urgent);
workspace_update_urgent_flag(client->workspace);
redecorate_window(conn, client);
/* If the workspace this client is on is not visible, we need to redraw
* the workspace bar */
if (!workspace_is_visible(client->workspace)) {
i3Screen *screen = client->workspace->screen;
render_workspace(conn, screen, screen->current_workspace);
Output *output = client->workspace->output;
render_workspace(conn, output, output->current_workspace);
xcb_flush(conn);
} else {
redecorate_window(conn, client);
}
return 1;
@ -1005,7 +1037,7 @@ int handle_transient_for(void *data, xcb_connection_t *conn, uint8_t state, xcb_
xcb_atom_t name, xcb_get_property_reply_t *reply) {
Client *client = table_get(&by_child, window);
if (client == NULL) {
LOG("No such client\n");
DLOG("No such client\n");
return 1;
}
@ -1021,7 +1053,7 @@ int handle_transient_for(void *data, xcb_connection_t *conn, uint8_t state, xcb_
}
if (client->floating == FLOATING_AUTO_OFF) {
LOG("This is a popup window, putting into floating\n");
DLOG("This is a popup window, putting into floating\n");
toggle_floating_mode(conn, client, true);
}
@ -1047,10 +1079,10 @@ int handle_clientleader_change(void *data, xcb_connection_t *conn, uint8_t state
return 1;
xcb_window_t *leader = xcb_get_property_value(prop);
if (leader == NULL || *leader == 0)
if (leader == NULL)
return 1;
LOG("Client leader changed to %08x\n", *leader);
DLOG("Client leader changed to %08x\n", *leader);
client->leader = *leader;

399
src/ipc.c
View File

@ -3,7 +3,7 @@
*
* i3 - an improved dynamic tiling window manager
*
* © 2009 Michael Stapelberg and contributors
* © 2009-2010 Michael Stapelberg and contributors
*
* See file LICENSE for license information.
*
@ -12,6 +12,7 @@
*/
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/un.h>
#include <fcntl.h>
#include <unistd.h>
@ -21,19 +22,24 @@
#include <stdlib.h>
#include <stdint.h>
#include <stdio.h>
#include <libgen.h>
#include <ev.h>
#include <yajl/yajl_gen.h>
#include <yajl/yajl_parse.h>
#include "queue.h"
#include "i3/ipc.h"
#include "ipc.h"
#include "i3.h"
#include "util.h"
#include "commands.h"
#include "log.h"
#include "table.h"
#include "randr.h"
#include "config.h"
typedef struct ipc_client {
int fd;
TAILQ_ENTRY(ipc_client) clients;
} ipc_client;
/* 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);
@ -50,49 +56,318 @@ static void set_nonblock(int sockfd) {
err(-1, "Could not set O_NONBLOCK");
}
#if 0
void broadcast(EV_P_ struct ev_timer *t, int revents) {
ipc_client *current;
TAILQ_FOREACH(current, &all_clients, clients) {
write(current->fd, "hi there!\n", strlen("hi there!\n"));
}
}
#endif
/*
* Decides what to do with the received message.
*
* message is the raw packet, as received from the UNIX domain socket. size
* is the remaining size of bytes for this packet.
*
* message_size is the size of the message as the sender specified it.
* message_type is the type of the message as the sender specified it.
* Emulates mkdir -p (creates any missing folders)
*
*/
static void ipc_handle_message(uint8_t *message, int size,
uint32_t message_size, uint32_t message_type) {
LOG("handling message of size %d\n", size);
LOG("sender specified size %d\n", message_size);
LOG("sender specified type %d\n", message_type);
LOG("payload as a string = %s\n", message);
static bool mkdirp(const char *path) {
if (mkdir(path, S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH) == 0)
return true;
if (errno != ENOENT) {
ELOG("mkdir(%s) failed: %s\n", path, strerror(errno));
return false;
}
char *copy = strdup(path);
/* strip trailing slashes, if any */
while (copy[strlen(copy)-1] == '/')
copy[strlen(copy)-1] = '\0';
switch (message_type) {
case I3_IPC_MESSAGE_TYPE_COMMAND: {
/* To get a properly terminated buffer, we copy
* message_size bytes out of the buffer */
char *command = scalloc(message_size);
strncpy(command, (const char*)message, message_size);
parse_command(global_conn, (const char*)command);
free(command);
char *sep = strrchr(copy, '/');
if (sep == NULL)
return false;
*sep = '\0';
bool result = false;
if (mkdirp(copy))
result = mkdirp(path);
free(copy);
break;
return result;
}
static void ipc_send_message(int fd, const unsigned char *payload,
int message_type, int message_size) {
int buffer_size = strlen("i3-ipc") + sizeof(uint32_t) +
sizeof(uint32_t) + message_size;
char msg[buffer_size];
char *walk = msg;
strcpy(walk, "i3-ipc");
walk += strlen("i3-ipc");
memcpy(walk, &message_size, sizeof(uint32_t));
walk += sizeof(uint32_t);
memcpy(walk, &message_type, sizeof(uint32_t));
walk += sizeof(uint32_t);
memcpy(walk, payload, message_size);
int sent_bytes = 0;
int bytes_to_go = buffer_size;
while (sent_bytes < bytes_to_go) {
int n = write(fd, msg + sent_bytes, bytes_to_go);
if (n == -1) {
DLOG("write() failed: %s\n", strerror(errno));
return;
}
default:
LOG("unhandled ipc message\n");
break;
sent_bytes += n;
bytes_to_go -= n;
}
}
/*
* Sends the specified event to all IPC clients which are currently connected
* and subscribed to this kind of event.
*
*/
void ipc_send_event(const char *event, uint32_t message_type, const char *payload) {
ipc_client *current;
TAILQ_FOREACH(current, &all_clients, clients) {
/* see if this client is interested in this event */
bool interested = false;
for (int i = 0; i < current->num_events; i++) {
if (strcasecmp(current->events[i], event) != 0)
continue;
interested = true;
break;
}
if (!interested)
continue;
ipc_send_message(current->fd, (const unsigned char*)payload,
message_type, strlen(payload));
}
}
/*
* Calls shutdown() on each socket and closes it. This function to be called
* when exiting or restarting only!
*
*/
void ipc_shutdown() {
ipc_client *current;
TAILQ_FOREACH(current, &all_clients, clients) {
shutdown(current->fd, SHUT_RDWR);
close(current->fd);
}
}
/*
* Executes the command and returns whether it could be successfully parsed
* or not (at the moment, always returns true).
*
*/
IPC_HANDLER(command) {
/* To get a properly terminated buffer, we copy
* message_size bytes out of the buffer */
char *command = scalloc(message_size);
strncpy(command, (const char*)message, message_size);
parse_command(global_conn, (const char*)command);
free(command);
/* For now, every command gets a positive acknowledge
* (will change with the new command parser) */
const char *reply = "{\"success\":true}";
ipc_send_message(fd, (const unsigned char*)reply,
I3_IPC_REPLY_TYPE_COMMAND, strlen(reply));
}
/*
* Formats the reply message for a GET_WORKSPACES request and sends it to the
* client
*
*/
IPC_HANDLER(get_workspaces) {
Workspace *ws;
Client *last_focused = SLIST_FIRST(&(c_ws->focus_stack));
if (last_focused == SLIST_END(&(c_ws->focus_stack)))
last_focused = NULL;
yajl_gen gen = yajl_gen_alloc(NULL, NULL);
y(array_open);
TAILQ_FOREACH(ws, workspaces, workspaces) {
if (ws->output == NULL)
continue;
y(map_open);
ystr("num");
y(integer, ws->num + 1);
ystr("name");
ystr(ws->utf8_name);
ystr("visible");
y(bool, ws->output->current_workspace == ws);
ystr("focused");
y(bool, c_ws == ws);
ystr("rect");
y(map_open);
ystr("x");
y(integer, ws->rect.x);
ystr("y");
y(integer, ws->rect.y);
ystr("width");
y(integer, ws->rect.width);
ystr("height");
y(integer, ws->rect.height);
y(map_close);
ystr("output");
ystr(ws->output->name);
ystr("urgent");
y(bool, ws->urgent);
y(map_close);
}
y(array_close);
const unsigned char *payload;
unsigned int length;
y(get_buf, &payload, &length);
ipc_send_message(fd, payload, I3_IPC_REPLY_TYPE_WORKSPACES, length);
y(free);
}
/*
* Formats the reply message for a GET_OUTPUTS request and sends it to the
* client
*
*/
IPC_HANDLER(get_outputs) {
Output *output;
yajl_gen gen = yajl_gen_alloc(NULL, NULL);
y(array_open);
TAILQ_FOREACH(output, &outputs, outputs) {
y(map_open);
ystr("name");
ystr(output->name);
ystr("active");
y(bool, output->active);
ystr("rect");
y(map_open);
ystr("x");
y(integer, output->rect.x);
ystr("y");
y(integer, output->rect.y);
ystr("width");
y(integer, output->rect.width);
ystr("height");
y(integer, output->rect.height);
y(map_close);
ystr("current_workspace");
if (output->current_workspace == NULL)
y(null);
else y(integer, output->current_workspace->num + 1);
y(map_close);
}
y(array_close);
const unsigned char *payload;
unsigned int length;
y(get_buf, &payload, &length);
ipc_send_message(fd, payload, I3_IPC_REPLY_TYPE_OUTPUTS, length);
y(free);
}
/*
* Callback for the YAJL parser (will be called when a string is parsed).
*
*/
static int add_subscription(void *extra, const unsigned char *s,
unsigned int len) {
ipc_client *client = extra;
DLOG("should add subscription to extra %p, sub %.*s\n", client, len, s);
int event = client->num_events;
client->num_events++;
client->events = realloc(client->events, client->num_events * sizeof(char*));
/* We copy the string because it is not null-terminated and strndup()
* is missing on some BSD systems */
client->events[event] = scalloc(len+1);
memcpy(client->events[event], s, len);
DLOG("client is now subscribed to:\n");
for (int i = 0; i < client->num_events; i++)
DLOG("event %s\n", client->events[i]);
DLOG("(done)\n");
return 1;
}
/*
* Subscribes this connection to the event types which were given as a JSON
* serialized array in the payload field of the message.
*
*/
IPC_HANDLER(subscribe) {
yajl_handle p;
yajl_callbacks callbacks;
yajl_status stat;
ipc_client *current, *client = NULL;
/* Search the ipc_client structure for this connection */
TAILQ_FOREACH(current, &all_clients, clients) {
if (current->fd != fd)
continue;
client = current;
break;
}
if (client == NULL) {
ELOG("Could not find ipc_client data structure for fd %d\n", fd);
return;
}
/* Setup the JSON parser */
memset(&callbacks, 0, sizeof(yajl_callbacks));
callbacks.yajl_string = add_subscription;
p = yajl_alloc(&callbacks, NULL, NULL, (void*)client);
stat = yajl_parse(p, (const unsigned char*)message, message_size);
if (stat != yajl_status_ok) {
unsigned char *err;
err = yajl_get_error(p, true, (const unsigned char*)message,
message_size);
ELOG("YAJL parse error: %s\n", err);
yajl_free_error(p, err);
const char *reply = "{\"success\":false}";
ipc_send_message(fd, (const unsigned char*)reply,
I3_IPC_REPLY_TYPE_SUBSCRIBE, strlen(reply));
yajl_free(p);
return;
}
yajl_free(p);
const char *reply = "{\"success\":true}";
ipc_send_message(fd, (const unsigned char*)reply,
I3_IPC_REPLY_TYPE_SUBSCRIBE, strlen(reply));
}
/* The index of each callback function corresponds to the numeric
* value of the message type (see include/i3/ipc.h) */
handler_t handlers[4] = {
handle_command,
handle_get_workspaces,
handle_subscribe,
handle_get_outputs
};
/*
* Handler for activity on a client connection, receives a message from a
* client.
@ -122,11 +397,13 @@ static void ipc_receive_message(EV_P_ struct ev_io *w, int revents) {
close(w->fd);
/* Delete the client from the list of clients */
struct ipc_client *current;
ipc_client *current;
TAILQ_FOREACH(current, &all_clients, clients) {
if (current->fd != w->fd)
continue;
for (int i = 0; i < current->num_events; i++)
free(current->events[i]);
/* We can call TAILQ_REMOVE because we break out of the
* TAILQ_FOREACH afterwards */
TAILQ_REMOVE(&all_clients, current, clients);
@ -135,7 +412,7 @@ static void ipc_receive_message(EV_P_ struct ev_io *w, int revents) {
ev_io_stop(EV_A_ w);
LOG("IPC: client disconnected\n");
DLOG("IPC: client disconnected\n");
return;
}
@ -144,18 +421,18 @@ static void ipc_receive_message(EV_P_ struct ev_io *w, int revents) {
/* Check if the message starts with the i3 IPC magic code */
if (n < strlen(I3_IPC_MAGIC)) {
LOG("IPC: message too short, ignoring\n");
DLOG("IPC: message too short, ignoring\n");
return;
}
if (strncmp(buf, I3_IPC_MAGIC, strlen(I3_IPC_MAGIC)) != 0) {
LOG("IPC: message does not start with the IPC magic\n");
DLOG("IPC: message does not start with the IPC magic\n");
return;
}
uint8_t *message = (uint8_t*)buf;
while (n > 0) {
LOG("IPC: n = %d\n", n);
DLOG("IPC: n = %d\n", n);
message += strlen(I3_IPC_MAGIC);
n -= strlen(I3_IPC_MAGIC);
@ -165,7 +442,7 @@ static void ipc_receive_message(EV_P_ struct ev_io *w, int revents) {
n -= sizeof(uint32_t);
if (message_size > n) {
LOG("IPC: Either the message size was wrong or the message was not read completely, dropping\n");
DLOG("IPC: Either the message size was wrong or the message was not read completely, dropping\n");
return;
}
@ -174,7 +451,12 @@ static void ipc_receive_message(EV_P_ struct ev_io *w, int revents) {
message += sizeof(uint32_t);
n -= sizeof(uint32_t);
ipc_handle_message(message, n, message_size, message_type);
if (message_type >= (sizeof(handlers) / sizeof(handler_t)))
DLOG("Unhandled message type: %d\n", message_type);
else {
handler_t h = handlers[message_type];
h(w->fd, message, n, message_size, message_type);
}
n -= message_size;
message += message_size;
}
@ -200,13 +482,13 @@ void ipc_new_client(EV_P_ struct ev_io *w, int revents) {
set_nonblock(client);
struct ev_io *package = calloc(sizeof(struct ev_io), 1);
struct ev_io *package = scalloc(sizeof(struct ev_io));
ev_io_init(package, ipc_receive_message, client, EV_READ);
ev_io_start(EV_A_ package);
LOG("IPC: new client connected\n");
DLOG("IPC: new client connected\n");
struct ipc_client *new = calloc(sizeof(struct ipc_client), 1);
ipc_client *new = scalloc(sizeof(ipc_client));
new->fd = client;
TAILQ_INSERT_TAIL(&all_clients, new, clients);
@ -220,11 +502,20 @@ void ipc_new_client(EV_P_ struct ev_io *w, int revents) {
int ipc_create_socket(const char *filename) {
int sockfd;
char *globbed = glob_path(filename);
DLOG("Creating IPC-socket at %s\n", globbed);
char *copy = sstrdup(globbed);
const char *dir = dirname(copy);
if (!path_exists(dir))
mkdirp(dir);
free(copy);
/* Unlink the unix domain socket before */
unlink(filename);
unlink(globbed);
if ((sockfd = socket(AF_LOCAL, SOCK_STREAM, 0)) < 0) {
perror("socket()");
free(globbed);
return -1;
}
@ -233,12 +524,14 @@ int ipc_create_socket(const char *filename) {
struct sockaddr_un addr;
memset(&addr, 0, sizeof(struct sockaddr_un));
addr.sun_family = AF_LOCAL;
strcpy(addr.sun_path, filename);
strcpy(addr.sun_path, globbed);
if (bind(sockfd, (struct sockaddr*)&addr, sizeof(struct sockaddr_un)) < 0) {
perror("bind()");
free(globbed);
return -1;
}
free(globbed);
set_nonblock(sockfd);
if (listen(sockfd, 5) < 0) {

View File

@ -3,7 +3,7 @@
*
* i3 - an improved dynamic tiling window manager
*
* © 2009 Michael Stapelberg and contributors
* © 2009-2010 Michael Stapelberg and contributors
*
* See file LICENSE for license information.
*
@ -22,23 +22,14 @@
#include "xcb.h"
#include "table.h"
#include "util.h"
#include "xinerama.h"
#include "randr.h"
#include "layout.h"
#include "client.h"
#include "floating.h"
#include "handlers.h"
#include "workspace.h"
/*
* Updates *destination with new_value and returns true if it was changed or false
* if it was the same
*
*/
static bool update_if_necessary(uint32_t *destination, const uint32_t new_value) {
uint32_t old_value = *destination;
return ((*destination = new_value) != old_value);
}
#include "log.h"
#include "container.h"
/*
* Gets the unoccupied space (= space which is available for windows which were resized by the user)
@ -50,16 +41,16 @@ int get_unoccupied_x(Workspace *workspace) {
double unoccupied = workspace->rect.width;
double default_factor = ((float)workspace->rect.width / workspace->cols) / workspace->rect.width;
LOG("get_unoccupied_x(), starting with %f, default_factor = %f\n", unoccupied, default_factor);
DLOG("get_unoccupied_x(), starting with %f, default_factor = %f\n", unoccupied, default_factor);
for (int cols = 0; cols < workspace->cols; cols++) {
LOG("width_factor[%d] = %f, unoccupied = %f\n", cols, workspace->width_factor[cols], unoccupied);
DLOG("width_factor[%d] = %f, unoccupied = %f\n", cols, workspace->width_factor[cols], unoccupied);
if (workspace->width_factor[cols] == 0)
unoccupied -= workspace->rect.width * default_factor;
}
LOG("unoccupied space: %f\n", unoccupied);
DLOG("unoccupied space: %f\n", unoccupied);
return unoccupied;
}
@ -69,15 +60,15 @@ int get_unoccupied_y(Workspace *workspace) {
double unoccupied = height;
double default_factor = ((float)height / workspace->rows) / height;
LOG("get_unoccupied_y(), starting with %f, default_factor = %f\n", unoccupied, default_factor);
DLOG("get_unoccupied_y(), starting with %f, default_factor = %f\n", unoccupied, default_factor);
for (int rows = 0; rows < workspace->rows; rows++) {
LOG("height_factor[%d] = %f, unoccupied = %f\n", rows, workspace->height_factor[rows], unoccupied);
DLOG("height_factor[%d] = %f, unoccupied = %f\n", rows, workspace->height_factor[rows], unoccupied);
if (workspace->height_factor[rows] == 0)
unoccupied -= height * default_factor;
}
LOG("unoccupied space: %f\n", unoccupied);
DLOG("unoccupied space: %f\n", unoccupied);
return unoccupied;
}
@ -140,16 +131,15 @@ void decorate_window(xcb_connection_t *conn, Client *client, xcb_drawable_t draw
- Draw two lines in a lighter color
- Draw the windows title
*/
int mode = container_mode(client->container, true);
/* Draw a rectangle in background color around the window */
if (client->borderless && (client->container == NULL ||
(client->container->mode != MODE_STACK &&
client->container->mode != MODE_TABBED)))
if (client->borderless && mode == MODE_DEFAULT)
xcb_change_gc_single(conn, gc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#000000"));
else xcb_change_gc_single(conn, gc, XCB_GC_FOREGROUND, color->background);
/* In stacking mode, we only render the rect for this specific decoration */
if (client->container != NULL && (client->container->mode == MODE_STACK || client->container->mode == MODE_TABBED)) {
if (mode == MODE_STACK || mode == MODE_TABBED) {
/* We need to use the containers width because it is the more recent value - when
in stacking mode, clients get reconfigured only on demand (the not active client
is not reconfigured), so the clients rect.width would be wrong */
@ -164,7 +154,10 @@ void decorate_window(xcb_connection_t *conn, Client *client, xcb_drawable_t draw
/* Draw the inner background to have a black frame around clients (such as mplayer)
which cannot be resized exactly in our frames and therefore are centered */
xcb_change_gc_single(conn, client->titlegc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#000000"));
if (client->titlebar_position == TITLEBAR_OFF) {
if (client->titlebar_position == TITLEBAR_OFF && client->borderless) {
xcb_rectangle_t crect = {0, 0, client->rect.width, client->rect.height};
xcb_poly_fill_rectangle(conn, client->frame, client->titlegc, 1, &crect);
} else if (client->titlebar_position == TITLEBAR_OFF && !client->borderless) {
xcb_rectangle_t crect = {1, 1, client->rect.width - (1 + 1), client->rect.height - (1 + 1)};
xcb_poly_fill_rectangle(conn, client->frame, client->titlegc, 1, &crect);
} else {
@ -174,22 +167,21 @@ void decorate_window(xcb_connection_t *conn, Client *client, xcb_drawable_t draw
}
}
mode = container_mode(client->container, false);
if (client->titlebar_position != TITLEBAR_OFF) {
/* Draw the lines */
xcb_draw_line(conn, drawable, gc, color->border, offset_x, offset_y, offset_x + client->rect.width, offset_y);
if ((client->container == NULL ||
(client->container->mode != MODE_STACK &&
client->container->mode != MODE_TABBED) ||
CIRCLEQ_NEXT_OR_NULL(&(client->container->clients), client, clients) == NULL))
xcb_draw_line(conn, drawable, gc, color->border,
offset_x + 2, /* x */
offset_y + font->height + 3, /* y */
offset_x + client->rect.width - 3, /* to_x */
offset_y + font->height + 3 /* to_y */);
xcb_draw_line(conn, drawable, gc, color->border,
offset_x + 2, /* x */
offset_y + font->height + 3, /* y */
offset_x + client->rect.width - 3, /* to_x */
offset_y + font->height + 3 /* to_y */);
}
/* If the client has a title, we draw it */
if (client->name != NULL && client->titlebar_position != TITLEBAR_OFF) {
if (client->name != NULL &&
(mode != MODE_DEFAULT || client->titlebar_position != TITLEBAR_OFF)) {
/* Draw the font */
uint32_t mask = XCB_GC_FOREGROUND | XCB_GC_BACKGROUND | XCB_GC_FONT;
uint32_t values[] = { color->text, color->background, font->id };
@ -212,9 +204,9 @@ void decorate_window(xcb_connection_t *conn, Client *client, xcb_drawable_t draw
*
*/
void reposition_client(xcb_connection_t *conn, Client *client) {
i3Screen *screen;
Output *output;
LOG("frame 0x%08x needs to be pushed to %dx%d\n", client->frame, client->rect.x, client->rect.y);
DLOG("frame 0x%08x needs to be pushed to %dx%d\n", client->frame, client->rect.x, client->rect.y);
/* Note: We can use a pointer to client->x like an array of uint32_ts
because it is followed by client->y by definition */
xcb_configure_window(conn, client->frame, XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y, &(client->rect.x));
@ -223,19 +215,25 @@ void reposition_client(xcb_connection_t *conn, Client *client) {
return;
/* If the client is floating, we need to check if we moved it to a different workspace */
screen = get_screen_containing(client->rect.x + (client->rect.width / 2),
output = get_output_containing(client->rect.x + (client->rect.width / 2),
client->rect.y + (client->rect.height / 2));
if (client->workspace->screen == screen)
if (client->workspace->output == output)
return;
if (screen == NULL) {
LOG("Boundary checking disabled, no screen found for (%d, %d)\n", client->rect.x, client->rect.y);
if (output == NULL) {
DLOG("Boundary checking disabled, no output found for (%d, %d)\n", client->rect.x, client->rect.y);
return;
}
LOG("Client is on workspace %p with screen %p\n", client->workspace, client->workspace->screen);
LOG("but screen at %d, %d is %p\n", client->rect.x, client->rect.y, screen);
floating_assign_to_workspace(client, screen->current_workspace);
if (output->current_workspace == NULL) {
DLOG("Boundary checking deferred, no current workspace on output\n");
client->force_reconfigure = true;
return;
}
DLOG("Client is on workspace %p with output %p\n", client->workspace, client->workspace->output);
DLOG("but output at %d, %d is %p\n", client->rect.x, client->rect.y, output);
floating_assign_to_workspace(client, output->current_workspace);
set_focus(conn, client, true);
}
@ -250,24 +248,15 @@ void reposition_client(xcb_connection_t *conn, Client *client) {
void resize_client(xcb_connection_t *conn, Client *client) {
i3Font *font = load_font(conn, config.font);
LOG("frame 0x%08x needs to be pushed to %dx%d\n", client->frame, client->rect.x, client->rect.y);
LOG("resizing client 0x%08x to %d x %d\n", client->frame, client->rect.width, client->rect.height);
xcb_configure_window(conn, client->frame,
XCB_CONFIG_WINDOW_X |
XCB_CONFIG_WINDOW_Y |
XCB_CONFIG_WINDOW_WIDTH |
XCB_CONFIG_WINDOW_HEIGHT,
&(client->rect.x));
DLOG("frame 0x%08x needs to be pushed to %dx%d\n", client->frame, client->rect.x, client->rect.y);
DLOG("resizing client 0x%08x to %d x %d\n", client->frame, client->rect.width, client->rect.height);
xcb_set_window_rect(conn, client->frame, client->rect);
/* Adjust the position of the child inside its frame.
* The coordinates of the child are relative to its frame, we
* add a border of 2 pixel to each value */
uint32_t mask = XCB_CONFIG_WINDOW_X |
XCB_CONFIG_WINDOW_Y |
XCB_CONFIG_WINDOW_WIDTH |
XCB_CONFIG_WINDOW_HEIGHT;
Rect *rect = &(client->child_rect);
switch ((client->container != NULL ? client->container->mode : MODE_DEFAULT)) {
switch (container_mode(client->container, true)) {
case MODE_STACK:
case MODE_TABBED:
rect->x = 2;
@ -301,7 +290,7 @@ void resize_client(xcb_connection_t *conn, Client *client) {
/* Obey the ratio, if any */
if (client->proportional_height != 0 &&
client->proportional_width != 0) {
LOG("proportional height = %d, width = %d\n", client->proportional_height, client->proportional_width);
DLOG("proportional height = %d, width = %d\n", client->proportional_height, client->proportional_width);
double new_height = rect->height + 1;
int new_width = rect->width;
@ -317,26 +306,26 @@ void resize_client(xcb_connection_t *conn, Client *client) {
rect->height = new_height;
rect->width = new_width;
LOG("new_height = %f, new_width = %d\n", new_height, new_width);
DLOG("new_height = %f, new_width = %d\n", new_height, new_width);
}
if (client->height_increment > 1) {
int old_height = rect->height;
rect->height -= (rect->height - client->base_height) % client->height_increment;
LOG("Lost %d pixel due to client's height_increment (%d px, base_height = %d)\n",
DLOG("Lost %d pixel due to client's height_increment (%d px, base_height = %d)\n",
old_height - rect->height, client->height_increment, client->base_height);
}
if (client->width_increment > 1) {
int old_width = rect->width;
rect->width -= (rect->width - client->base_width) % client->width_increment;
LOG("Lost %d pixel due to client's width_increment (%d px, base_width = %d)\n",
DLOG("Lost %d pixel due to client's width_increment (%d px, base_width = %d)\n",
old_width - rect->width, client->width_increment, client->base_width);
}
LOG("child will be at %dx%d with size %dx%d\n", rect->x, rect->y, rect->width, rect->height);
DLOG("child will be at %dx%d with size %dx%d\n", rect->x, rect->y, rect->width, rect->height);
xcb_configure_window(conn, client->child, mask, &(rect->x));
xcb_set_window_rect(conn, client->child, *rect);
/* After configuring a child window we need to fake a configure_notify_event (see ICCCM 4.2.3).
* This is necessary to inform the client of its position relative to the root window,
@ -364,6 +353,10 @@ void render_container(xcb_connection_t *conn, Container *container) {
num_clients++;
if (container->mode == MODE_DEFAULT) {
int height = (container->height / max(1, num_clients));
int rest_pixels = (container->height % max(1, num_clients));
DLOG("height per client = %d, rest = %d\n", height, rest_pixels);
CIRCLEQ_FOREACH(client, &(container->clients), clients) {
/* If the client is in fullscreen mode, it does not get reconfigured */
if (container->workspace->fullscreen_client == client) {
@ -371,6 +364,13 @@ void render_container(xcb_connection_t *conn, Container *container) {
continue;
}
/* If we have some pixels left to distribute, add one
* pixel to each client as long as possible. */
int this_height = height;
if (rest_pixels > 0) {
height++;
rest_pixels--;
}
/* Check if we changed client->x or client->y by updating it.
* Note the bitwise OR instead of logical OR to force evaluation of both statements */
if (client->force_reconfigure |
@ -378,7 +378,7 @@ void render_container(xcb_connection_t *conn, Container *container) {
update_if_necessary(&(client->rect.y), container->y +
(container->height / num_clients) * current_client) |
update_if_necessary(&(client->rect.width), container->width) |
update_if_necessary(&(client->rect.height), container->height / num_clients))
update_if_necessary(&(client->rect.height), this_height))
resize_client(conn, client);
/* TODO: vertical default layout */
@ -398,16 +398,16 @@ void render_container(xcb_connection_t *conn, Container *container) {
/* Check if we need to remap our stack title window, it gets unmapped when the container
is empty in src/handlers.c:unmap_notify() */
if (stack_win->rect.height == 0 && num_clients > 0) {
LOG("remapping stack win\n");
if (stack_win->rect.height == 0 && num_clients > 1) {
DLOG("remapping stack win\n");
xcb_map_window(conn, stack_win->window);
} else LOG("not remapping stackwin, height = %d, num_clients = %d\n",
} else DLOG("not remapping stackwin, height = %d, num_clients = %d\n",
stack_win->rect.height, num_clients);
if (container->mode == MODE_TABBED) {
/* By setting num_clients to 1 we force that the stack window will be only one line
* high. The rest of the code is useful in both cases. */
LOG("tabbed mode, setting num_clients = 1\n");
DLOG("tabbed mode, setting num_clients = 1\n");
if (stack_lines > 1)
stack_lines = 1;
}
@ -418,11 +418,21 @@ void render_container(xcb_connection_t *conn, Container *container) {
stack_lines = min(num_clients, container->stack_limit_value);
}
int height = decoration_height * stack_lines;
if (num_clients == 1) {
height = 0;
stack_win->rect.height = 0;
xcb_unmap_window(conn, stack_win->window);
DLOG("Just one client, setting height to %d\n", height);
}
/* Check if we need to reconfigure our stack title window */
if (update_if_necessary(&(stack_win->rect.x), container->x) |
update_if_necessary(&(stack_win->rect.y), container->y) |
update_if_necessary(&(stack_win->rect.width), container->width) |
update_if_necessary(&(stack_win->rect.height), decoration_height * stack_lines)) {
if (height > 0 && (
update_if_necessary(&(stack_win->rect.x), container->x) |
update_if_necessary(&(stack_win->rect.y), container->y) |
update_if_necessary(&(stack_win->rect.width), container->width) |
update_if_necessary(&(stack_win->rect.height), height))) {
/* Configuration can happen in two slightly different ways:
@ -456,7 +466,8 @@ void render_container(xcb_connection_t *conn, Container *container) {
}
/* Prepare the pixmap for usage */
cached_pixmap_prepare(conn, &(stack_win->pixmap));
if (num_clients > 1)
cached_pixmap_prepare(conn, &(stack_win->pixmap));
int current_row = 0, current_col = 0;
int wrap = 0;
@ -486,9 +497,9 @@ void render_container(xcb_connection_t *conn, Container *container) {
* Note the bitwise OR instead of logical OR to force evaluation of all statements */
if (client->force_reconfigure |
update_if_necessary(&(client->rect.x), container->x) |
update_if_necessary(&(client->rect.y), container->y + (decoration_height * stack_lines)) |
update_if_necessary(&(client->rect.y), container->y + height) |
update_if_necessary(&(client->rect.width), container->width) |
update_if_necessary(&(client->rect.height), container->height - (decoration_height * stack_lines)))
update_if_necessary(&(client->rect.height), container->height - height))
resize_client(conn, client);
client->force_reconfigure = false;
@ -520,13 +531,15 @@ void render_container(xcb_connection_t *conn, Container *container) {
current_client++;
} else if (container->mode == MODE_TABBED) {
if (container->stack_limit == STACK_LIMIT_ROWS) {
LOG("You limited this container in its rows. "
LOG("You limited a tabbed container in its rows. "
"This makes no sense in tabbing mode.\n");
}
offset_x = current_client++ * size_each;
}
decorate_window(conn, client, stack_win->pixmap.id, stack_win->pixmap.gc,
offset_x, offset_y);
if (stack_win->pixmap.id == XCB_NONE)
continue;
decorate_window(conn, client, stack_win->pixmap.id,
stack_win->pixmap.gc, offset_x, offset_y);
}
/* Check if we need to fill one column because of an uneven
@ -553,6 +566,8 @@ void render_container(xcb_connection_t *conn, Container *container) {
}
}
if (stack_win->pixmap.id == XCB_NONE)
return;
xcb_copy_area(conn, stack_win->pixmap.id, stack_win->window, stack_win->pixmap.gc,
0, 0, 0, 0, stack_win->rect.width, stack_win->rect.height);
}
@ -560,8 +575,8 @@ void render_container(xcb_connection_t *conn, Container *container) {
static void render_bars(xcb_connection_t *conn, Workspace *r_ws, int width, int *height) {
Client *client;
SLIST_FOREACH(client, &(r_ws->screen->dock_clients), dock_clients) {
LOG("client is at %d, should be at %d\n", client->rect.y, *height);
SLIST_FOREACH(client, &(r_ws->output->dock_clients), dock_clients) {
DLOG("client is at %d, should be at %d\n", client->rect.y, *height);
if (client->force_reconfigure |
update_if_necessary(&(client->rect.x), r_ws->rect.x) |
update_if_necessary(&(client->rect.y), *height))
@ -573,55 +588,55 @@ static void render_bars(xcb_connection_t *conn, Workspace *r_ws, int width, int
resize_client(conn, client);
client->force_reconfigure = false;
LOG("desired_height = %d\n", client->desired_height);
DLOG("desired_height = %d\n", client->desired_height);
*height += client->desired_height;
}
}
static void render_internal_bar(xcb_connection_t *conn, Workspace *r_ws, int width, int height) {
i3Font *font = load_font(conn, config.font);
i3Screen *screen = r_ws->screen;
Output *output = r_ws->output;
enum { SET_NORMAL = 0, SET_FOCUSED = 1 };
/* Fill the whole bar in black */
xcb_change_gc_single(conn, screen->bargc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#000000"));
xcb_change_gc_single(conn, output->bargc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#000000"));
xcb_rectangle_t rect = {0, 0, width, height};
xcb_poly_fill_rectangle(conn, screen->bar, screen->bargc, 1, &rect);
xcb_poly_fill_rectangle(conn, output->bar, output->bargc, 1, &rect);
/* Set font */
xcb_change_gc_single(conn, screen->bargc, XCB_GC_FONT, font->id);
xcb_change_gc_single(conn, output->bargc, XCB_GC_FONT, font->id);
int drawn = 0;
Workspace *ws;
TAILQ_FOREACH(ws, workspaces, workspaces) {
if (ws->screen != screen)
if (ws->output != output)
continue;
struct Colortriple *color;
if (screen->current_workspace == ws)
if (output->current_workspace == ws)
color = &(config.bar.focused);
else if (ws->urgent)
color = &(config.bar.urgent);
else color = &(config.bar.unfocused);
/* Draw the outer rect */
xcb_draw_rect(conn, screen->bar, screen->bargc, color->border,
xcb_draw_rect(conn, output->bar, output->bargc, color->border,
drawn, /* x */
1, /* y */
ws->text_width + 5 + 5, /* width = text width + 5 px left + 5px right */
height - 2 /* height = max. height - 1 px upper and 1 px bottom border */);
/* Draw the background of this rect */
xcb_draw_rect(conn, screen->bar, screen->bargc, color->background,
xcb_draw_rect(conn, output->bar, output->bargc, color->background,
drawn + 1,
2,
ws->text_width + 4 + 4,
height - 4);
xcb_change_gc_single(conn, screen->bargc, XCB_GC_FOREGROUND, color->text);
xcb_change_gc_single(conn, screen->bargc, XCB_GC_BACKGROUND, color->background);
xcb_image_text_16(conn, ws->name_len, screen->bar, screen->bargc, drawn + 5 /* X */,
xcb_change_gc_single(conn, output->bargc, XCB_GC_FOREGROUND, color->text);
xcb_change_gc_single(conn, output->bargc, XCB_GC_BACKGROUND, color->background);
xcb_image_text_16(conn, ws->name_len, output->bar, output->bargc, drawn + 5 /* X */,
font->height + 1 /* Y = baseline of font */,
(xcb_char2b_t*)ws->name);
drawn += ws->text_width + 12;
@ -662,18 +677,19 @@ void ignore_enter_notify_forall(xcb_connection_t *conn, Workspace *workspace, bo
* Renders the given workspace on the given screen
*
*/
void render_workspace(xcb_connection_t *conn, i3Screen *screen, Workspace *r_ws) {
void render_workspace(xcb_connection_t *conn, Output *output, Workspace *r_ws) {
i3Font *font = load_font(conn, config.font);
int width = r_ws->rect.width;
int height = r_ws->rect.height;
/* Reserve space for dock clients */
Client *client;
SLIST_FOREACH(client, &(screen->dock_clients), dock_clients)
SLIST_FOREACH(client, &(output->dock_clients), dock_clients)
height -= client->desired_height;
/* Space for the internal bar */
height -= (font->height + 6);
if (!config.disable_workspace_bar)
height -= (font->height + 6);
int xoffset[r_ws->rows];
int yoffset[r_ws->cols];
@ -707,7 +723,7 @@ void render_workspace(xcb_connection_t *conn, i3Screen *screen, Workspace *r_ws)
single_width = container->width;
}
LOG("height is %d\n", height);
DLOG("height is %d\n", height);
container->height = 0;
@ -727,10 +743,21 @@ void render_workspace(xcb_connection_t *conn, i3Screen *screen, Workspace *r_ws)
yoffset[cols] += single_height;
}
/* Reposition all floating clients with force_reconfigure == true */
TAILQ_FOREACH(client, &(r_ws->floating_clients), floating_clients) {
if (!client->force_reconfigure)
continue;
client->force_reconfigure = false;
reposition_client(conn, client);
resize_client(conn, client);
}
ignore_enter_notify_forall(conn, r_ws, false);
render_bars(conn, r_ws, width, &height);
render_internal_bar(conn, r_ws, width, font->height + 6);
if (!config.disable_workspace_bar)
render_internal_bar(conn, r_ws, width, font->height + 6);
}
/*
@ -742,14 +769,11 @@ void render_workspace(xcb_connection_t *conn, i3Screen *screen, Workspace *r_ws)
*
*/
void render_layout(xcb_connection_t *conn) {
i3Screen *screen;
Output *output;
if (virtual_screens == NULL)
return;
TAILQ_FOREACH(screen, virtual_screens, screens)
if (screen->current_workspace != NULL)
render_workspace(conn, screen, screen->current_workspace);
TAILQ_FOREACH(output, &outputs, outputs)
if (output->current_workspace != NULL)
render_workspace(conn, output, output->current_workspace);
xcb_flush(conn);
}

121
src/log.c Normal file
View File

@ -0,0 +1,121 @@
/*
* vim:ts=8:expandtab
*
* i3 - an improved dynamic tiling window manager
*
* © 2009 Michael Stapelberg and contributors
*
* See file LICENSE for license information.
*
* src/log.c: handles the setting of loglevels, contains the logging functions.
*
*/
#include <stdarg.h>
#include <stdio.h>
#include <string.h>
#include <stdbool.h>
#include "util.h"
#include "log.h"
/* loglevels.h is autogenerated at make time */
#include "loglevels.h"
static uint32_t loglevel = 0;
static bool verbose = false;
/**
* Set verbosity of i3. If verbose is set to true, informative messages will
* be printed to stdout. If verbose is set to false, only errors will be
* printed.
*
*/
void set_verbosity(bool _verbose) {
verbose = _verbose;
}
/**
* Enables the given loglevel.
*
*/
void add_loglevel(const char *level) {
/* Handle the special loglevel "all" */
if (strcasecmp(level, "all") == 0) {
loglevel = UINT32_MAX;
return;
}
for (int i = 0; i < sizeof(loglevels) / sizeof(char*); i++) {
if (strcasecmp(loglevels[i], level) != 0)
continue;
/* The position in the array (plus one) is the amount of times
* which we need to shift 1 to the left to get our bitmask for
* the specific loglevel. */
loglevel |= (1 << (i+1));
break;
}
}
/*
* Logs the given message to stdout while prefixing the current time to it.
* This is to be called by *LOG() which includes filename/linenumber/function.
*
*/
void vlog(char *fmt, va_list args) {
char timebuf[64];
/* Get current time */
time_t t = time(NULL);
/* Convert time to local time (determined by the locale) */
struct tm *tmp = localtime(&t);
/* Generate time prefix */
strftime(timebuf, sizeof(timebuf), "%x %X - ", tmp);
printf("%s", timebuf);
vprintf(fmt, args);
}
/**
* Logs the given message to stdout while prefixing the current time to it,
* but only if verbose mode is activated.
*
*/
void verboselog(char *fmt, ...) {
va_list args;
if (!verbose)
return;
va_start(args, fmt);
vlog(fmt, args);
va_end(args);
}
/**
* Logs the given message to stdout while prefixing the current time to it.
*
*/
void errorlog(char *fmt, ...) {
va_list args;
va_start(args, fmt);
vlog(fmt, args);
va_end(args);
}
/*
* Logs the given message to stdout while prefixing the current time to it,
* but only if the corresponding debug loglevel was activated.
* This is to be called by DLOG() which includes filename/linenumber
*
*/
void debuglog(int lev, char *fmt, ...) {
va_list args;
if ((loglevel & lev) == 0)
return;
va_start(args, fmt);
vlog(fmt, args);
va_end(args);
}

View File

@ -3,7 +3,7 @@
*
* i3 - an improved dynamic tiling window manager
*
* © 2009 Michael Stapelberg and contributors
* © 2009-2010 Michael Stapelberg and contributors
*
* See file LICENSE for license information.
*
@ -30,7 +30,6 @@
#include <xcb/xcb_property.h>
#include <xcb/xcb_keysyms.h>
#include <xcb/xcb_icccm.h>
#include <xcb/xinerama.h>
#include <ev.h>
@ -45,9 +44,16 @@
#include "table.h"
#include "util.h"
#include "xcb.h"
#include "randr.h"
#include "xinerama.h"
#include "manage.h"
#include "ipc.h"
#include "log.h"
#include "sighandler.h"
static int xkb_event_base;
int xkb_current_group;
xcb_connection_t *global_conn;
@ -82,6 +88,9 @@ int num_screens = 0;
/* The depth of the root screen (used e.g. for creating new pixmaps later) */
uint8_t root_depth;
/* We hope that XKB is supported and set this to false */
bool xkb_supported = true;
/*
* This callback is only a dummy, see xcb_prepare_cb and xcb_check_cb.
* See also man libev(3): "ev_prepare" and "ev_check" - customise your event loop
@ -119,25 +128,65 @@ static void xcb_check_cb(EV_P_ ev_check *w, int revents) {
*
*/
static void xkb_got_event(EV_P_ struct ev_io *w, int revents) {
LOG("got xkb event, yay\n");
XEvent ev;
DLOG("Handling XKB event\n");
XkbEvent ev;
/* When using xmodmap, every change (!) gets an own event.
* Therefore, we just read all events and only handle the
* mapping_notify once (we do not receive any other XKB
* events anyway). */
while (XPending(xkbdpy))
XNextEvent(xkbdpy, &ev);
* mapping_notify once. */
bool mapping_changed = false;
while (XPending(xkbdpy)) {
XNextEvent(xkbdpy, (XEvent*)&ev);
/* While we should never receive a non-XKB event,
* better do sanity checking */
if (ev.type != xkb_event_base)
continue;
if (ev.any.xkb_type == XkbMapNotify) {
mapping_changed = true;
continue;
}
if (ev.any.xkb_type != XkbStateNotify) {
ELOG("Unknown XKB event received (type %d)\n", ev.any.xkb_type);
continue;
}
/* See The XKB Extension: Library Specification, section 14.1 */
/* We check if the current group (each group contains
* two levels) has been changed. Mode_switch activates
* group XkbGroup2Index */
if (xkb_current_group == ev.state.group)
continue;
xkb_current_group = ev.state.group;
if (ev.state.group == XkbGroup2Index) {
DLOG("Mode_switch enabled\n");
grab_all_keys(global_conn, true);
}
if (ev.state.group == XkbGroup1Index) {
DLOG("Mode_switch disabled\n");
ungrab_all_keys(global_conn);
grab_all_keys(global_conn, false);
}
}
if (!mapping_changed)
return;
DLOG("Keyboard mapping changed, updating keybindings\n");
xcb_key_symbols_free(keysyms);
keysyms = xcb_key_symbols_alloc(global_conn);
xcb_get_numlock_mask(global_conn);
ungrab_all_keys(global_conn);
LOG("Re-grabbing...\n");
grab_all_keys(global_conn);
LOG("Done\n");
DLOG("Re-grabbing...\n");
translate_keysyms();
grab_all_keys(global_conn, (xkb_current_group == XkbGroup2Index));
DLOG("Done\n");
}
@ -145,6 +194,8 @@ int main(int argc, char *argv[], char *env[]) {
int i, screens, opt;
char *override_configpath = NULL;
bool autostart = true;
bool only_check_config = false;
bool force_xinerama = false;
xcb_connection_t *conn;
xcb_property_handlers_t prophs;
xcb_intern_atom_cookie_t atom_cookies[NUM_ATOMS];
@ -153,6 +204,7 @@ int main(int argc, char *argv[], char *env[]) {
{"config", required_argument, 0, 'c'},
{"version", no_argument, 0, 'v'},
{"help", no_argument, 0, 'h'},
{"force-xinerama", no_argument, 0, 0},
{0, 0, 0, 0}
};
int option_index = 0;
@ -165,7 +217,7 @@ int main(int argc, char *argv[], char *env[]) {
start_argv = argv;
while ((opt = getopt_long(argc, argv, "c:vahl", long_options, &option_index)) != -1) {
while ((opt = getopt_long(argc, argv, "c:Cvahld:V", long_options, &option_index)) != -1) {
switch (opt) {
case 'a':
LOG("Autostart disabled using -a\n");
@ -174,18 +226,46 @@ int main(int argc, char *argv[], char *env[]) {
case 'c':
override_configpath = sstrdup(optarg);
break;
case 'v':
printf("i3 version " I3_VERSION " © 2009 Michael Stapelberg and contributors\n");
exit(EXIT_SUCCESS);
case 'l':
config_use_lexer = true;
case 'C':
LOG("Checking configuration file only (-C)\n");
only_check_config = true;
break;
case 'v':
printf("i3 version " I3_VERSION " © 2009-2010 Michael Stapelberg and contributors\n");
exit(EXIT_SUCCESS);
case 'V':
set_verbosity(true);
break;
case 'd':
LOG("Enabling debug loglevel %s\n", optarg);
add_loglevel(optarg);
break;
case 'l':
/* DEPRECATED, ignored for the next 3 versions (3.e, 3.f, 3.g) */
break;
case 0:
if (strcmp(long_options[option_index].name, "force-xinerama") == 0) {
force_xinerama = true;
ELOG("Using Xinerama instead of RandR. This option should be "
"avoided at all cost because it does not refresh the list "
"of screens, so you cannot configure displays at runtime. "
"Please check if your driver really does not support RandR "
"and disable this option as soon as you can.\n");
break;
}
/* fall-through */
default:
fprintf(stderr, "Usage: %s [-c configfile] [-a] [-v]\n", argv[0]);
fprintf(stderr, "Usage: %s [-c configfile] [-d loglevel] [-a] [-v] [-V] [-C]\n", argv[0]);
fprintf(stderr, "\n");
fprintf(stderr, "-a: disable autostart\n");
fprintf(stderr, "-v: display version and exit\n");
fprintf(stderr, "-V: enable verbose mode\n");
fprintf(stderr, "-d <loglevel>: enable debug loglevel <loglevel>\n");
fprintf(stderr, "-c <configfile>: use the provided configfile instead\n");
fprintf(stderr, "-C: check configuration file and exit\n");
fprintf(stderr, "--force-xinerama: Use Xinerama instead of RandR. This "
"option should only be used if you are stuck with the "
"nvidia closed source driver which does not support RandR.\n");
exit(EXIT_FAILURE);
}
}
@ -204,6 +284,10 @@ int main(int argc, char *argv[], char *env[]) {
die("Cannot open display\n");
load_configuration(conn, override_configpath, false);
if (only_check_config) {
LOG("Done checking configuration file. Exiting.\n");
exit(0);
}
/* Create the initial container on the first workspace. This used to
* be part of init_table, but since it possibly requires an X
@ -234,6 +318,9 @@ int main(int argc, char *argv[], char *env[]) {
REQUEST_ATOM(UTF8_STRING);
REQUEST_ATOM(WM_STATE);
REQUEST_ATOM(WM_CLIENT_LEADER);
REQUEST_ATOM(_NET_CURRENT_DESKTOP);
REQUEST_ATOM(_NET_ACTIVE_WINDOW);
REQUEST_ATOM(_NET_WORKAREA);
/* TODO: this has to be more beautiful somewhen */
int major, minor, error;
@ -241,28 +328,32 @@ int main(int argc, char *argv[], char *env[]) {
major = XkbMajorVersion;
minor = XkbMinorVersion;
int evBase, errBase;
int errBase;
if ((xkbdpy = XkbOpenDisplay(getenv("DISPLAY"), &evBase, &errBase, &major, &minor, &error)) == NULL) {
fprintf(stderr, "XkbOpenDisplay() failed\n");
return 1;
if ((xkbdpy = XkbOpenDisplay(getenv("DISPLAY"), &xkb_event_base, &errBase, &major, &minor, &error)) == NULL) {
ELOG("ERROR: XkbOpenDisplay() failed, disabling XKB support\n");
xkb_supported = false;
}
if (fcntl(ConnectionNumber(xkbdpy), F_SETFD, FD_CLOEXEC) == -1) {
fprintf(stderr, "Could not set FD_CLOEXEC on xkbdpy\n");
return 1;
}
if (xkb_supported) {
if (fcntl(ConnectionNumber(xkbdpy), F_SETFD, FD_CLOEXEC) == -1) {
fprintf(stderr, "Could not set FD_CLOEXEC on xkbdpy\n");
return 1;
}
int i1;
if (!XkbQueryExtension(xkbdpy,&i1,&evBase,&errBase,&major,&minor)) {
fprintf(stderr, "XKB not supported by X-server\n");
return 1;
}
/* end of ugliness */
int i1;
if (!XkbQueryExtension(xkbdpy,&i1,&xkb_event_base,&errBase,&major,&minor)) {
fprintf(stderr, "XKB not supported by X-server\n");
return 1;
}
/* end of ugliness */
if (!XkbSelectEvents(xkbdpy, XkbUseCoreKbd, XkbMapNotifyMask, XkbMapNotifyMask)) {
fprintf(stderr, "Could not set XKB event mask\n");
return 1;
if (!XkbSelectEvents(xkbdpy, XkbUseCoreKbd,
XkbMapNotifyMask | XkbStateNotifyMask,
XkbMapNotifyMask | XkbStateNotifyMask)) {
fprintf(stderr, "Could not set XKB event mask\n");
return 1;
}
}
/* Initialize event loop using libev */
@ -278,11 +369,13 @@ int main(int argc, char *argv[], char *env[]) {
ev_io_init(xcb_watcher, xcb_got_event, xcb_get_file_descriptor(conn), EV_READ);
ev_io_start(loop, xcb_watcher);
ev_io_init(xkb, xkb_got_event, ConnectionNumber(xkbdpy), EV_READ);
ev_io_start(loop, xkb);
if (xkb_supported) {
ev_io_init(xkb, xkb_got_event, ConnectionNumber(xkbdpy), EV_READ);
ev_io_start(loop, xkb);
/* Flush the buffer so that libev can properly get new events */
XFlush(xkbdpy);
/* Flush the buffer so that libev can properly get new events */
XFlush(xkbdpy);
}
ev_check_init(xcb_check, xcb_check_cb);
ev_check_start(loop, xcb_check);
@ -305,9 +398,8 @@ int main(int argc, char *argv[], char *env[]) {
/* Expose = an Application should redraw itself, in this case its our titlebars. */
xcb_event_set_expose_handler(&evenths, handle_expose_event, NULL);
/* Key presses/releases are pretty obvious, I think */
/* Key presses are pretty obvious, I think */
xcb_event_set_key_press_handler(&evenths, handle_key_press, NULL);
xcb_event_set_key_release_handler(&evenths, handle_key_release, NULL);
/* Enter window = user moved his mouse over the window */
xcb_event_set_enter_notify_handler(&evenths, handle_enter_notify, NULL);
@ -322,6 +414,9 @@ int main(int argc, char *argv[], char *env[]) {
it any longer. Usually, the client destroys the window shortly afterwards. */
xcb_event_set_unmap_notify_handler(&evenths, handle_unmap_notify_event, NULL);
/* Destroy notify is handled the same as unmap notify */
xcb_event_set_destroy_notify_handler(&evenths, handle_destroy_notify_event, NULL);
/* Configure notify = windows configuration (geometry, stacking, …). We only need
it to set up ignore the following enter_notify events */
xcb_event_set_configure_notify_handler(&evenths, handle_configure_event, NULL);
@ -359,13 +454,15 @@ int main(int argc, char *argv[], char *env[]) {
XCB_EVENT_MASK_POINTER_MOTION |
XCB_EVENT_MASK_PROPERTY_CHANGE |
XCB_EVENT_MASK_ENTER_WINDOW };
xcb_change_window_attributes(conn, root, mask, values);
xcb_void_cookie_t cookie;
cookie = xcb_change_window_attributes_checked(conn, root, mask, values);
check_error(conn, cookie, "Another window manager seems to be running");
/* Setup NetWM atoms */
#define GET_ATOM(name) { \
xcb_intern_atom_reply_t *reply = xcb_intern_atom_reply(conn, atom_cookies[name], NULL); \
if (!reply) { \
LOG("Could not get atom " #name "\n"); \
ELOG("Could not get atom " #name "\n"); \
exit(-1); \
} \
atoms[name] = reply->atom; \
@ -390,6 +487,9 @@ int main(int argc, char *argv[], char *env[]) {
GET_ATOM(UTF8_STRING);
GET_ATOM(WM_STATE);
GET_ATOM(WM_CLIENT_LEADER);
GET_ATOM(_NET_CURRENT_DESKTOP);
GET_ATOM(_NET_ACTIVE_WINDOW);
GET_ATOM(_NET_WORKAREA);
xcb_property_set_handler(&prophs, atoms[_NET_WM_WINDOW_TYPE], UINT_MAX, handle_window_type, NULL);
/* TODO: In order to comply with EWMH, we have to watch _NET_WM_STRUT_PARTIAL */
@ -423,38 +523,39 @@ int main(int argc, char *argv[], char *env[]) {
xcb_get_numlock_mask(conn);
grab_all_keys(conn);
translate_keysyms();
grab_all_keys(conn, false);
/* Autostarting exec-lines */
struct Autostart *exec;
if (autostart) {
TAILQ_FOREACH(exec, &autostarts, autostarts) {
LOG("auto-starting %s\n", exec->command);
start_application(exec->command);
}
int randr_base;
if (force_xinerama) {
initialize_xinerama(conn);
} else {
DLOG("Checking for XRandR...\n");
initialize_randr(conn, &randr_base);
xcb_event_set_handler(&evenths,
randr_base + XCB_RANDR_SCREEN_CHANGE_NOTIFY,
handle_screen_change,
NULL);
}
/* check for Xinerama */
LOG("Checking for Xinerama...\n");
initialize_xinerama(conn);
xcb_flush(conn);
/* Get pointer position to see on which screen were starting */
xcb_query_pointer_reply_t *reply;
if ((reply = xcb_query_pointer_reply(conn, xcb_query_pointer(conn, root), NULL)) == NULL) {
LOG("Could not get pointer position\n");
ELOG("Could not get pointer position\n");
return 1;
}
i3Screen *screen = get_screen_containing(reply->root_x, reply->root_y);
Output *screen = get_output_containing(reply->root_x, reply->root_y);
if (screen == NULL) {
LOG("ERROR: No screen at %d x %d, starting on the first screen\n",
ELOG("ERROR: No screen at %d x %d, starting on the first screen\n",
reply->root_x, reply->root_y);
screen = TAILQ_FIRST(virtual_screens);
screen = get_first_output();
}
LOG("Starting on %d\n", screen->current_workspace);
DLOG("Starting on %p\n", screen->current_workspace);
c_ws = screen->current_workspace;
manage_existing_windows(conn, &prophs, root);
@ -463,7 +564,7 @@ int main(int argc, char *argv[], char *env[]) {
if (config.ipc_socket_path != NULL) {
int ipc_socket = ipc_create_socket(config.ipc_socket_path);
if (ipc_socket == -1) {
LOG("Could not create the IPC socket, IPC disabled\n");
ELOG("Could not create the IPC socket, IPC disabled\n");
} else {
struct ev_io *ipc_io = scalloc(sizeof(struct ev_io));
ev_io_init(ipc_io, ipc_new_client, ipc_socket, EV_READ);
@ -474,8 +575,24 @@ int main(int argc, char *argv[], char *env[]) {
/* Handle the events which arrived until now */
xcb_check_cb(NULL, NULL, 0);
setup_signal_handler();
/* Ignore SIGPIPE to survive errors when an IPC client disconnects
* while we are sending him a message */
signal(SIGPIPE, SIG_IGN);
/* Ungrab the server to receive events and enter libevs eventloop */
xcb_ungrab_server(conn);
/* Autostarting exec-lines */
struct Autostart *exec;
if (autostart) {
TAILQ_FOREACH(exec, &autostarts, autostarts) {
LOG("auto-starting %s\n", exec->command);
start_application(exec->command);
}
}
ev_loop(loop, 0);
/* not reached */

View File

@ -3,7 +3,7 @@
*
* i3 - an improved dynamic tiling window manager
*
* © 2009 Michael Stapelberg and contributors
* © 2009-2010 Michael Stapelberg and contributors
*
* See file LICENSE for license information.
*
@ -30,6 +30,8 @@
#include "floating.h"
#include "client.h"
#include "workspace.h"
#include "log.h"
#include "ewmh.h"
/*
* Go through all existing windows (if the window manager is restarted) and manage them
@ -61,6 +63,28 @@ void manage_existing_windows(xcb_connection_t *conn, xcb_property_handlers_t *pr
free(cookies);
}
/*
* Restores the geometry of each window by reparenting it to the root window
* at the position of its frame.
*
* This is to be called *only* before exiting/restarting i3 because of evil
* side-effects which are to be expected when continuing to run i3.
*
*/
void restore_geometry(xcb_connection_t *conn) {
Workspace *ws;
Client *client;
DLOG("Restoring geometry\n");
TAILQ_FOREACH(ws, workspaces, workspaces)
SLIST_FOREACH(client, &(ws->focus_stack), focus_clients)
xcb_reparent_window(conn, client->child, root,
client->rect.x, client->rect.y);
/* Make sure our changes reach the X server, we restart/exit now */
xcb_flush(conn);
}
/*
* Do some sanity checks and then reparent the window.
*
@ -78,7 +102,7 @@ void manage_window(xcb_property_handlers_t *prophs, xcb_connection_t *conn,
/* Check if the window is mapped (it could be not mapped when intializing and
calling manage_window() for every window) */
if ((attr = xcb_get_window_attributes_reply(conn, cookie, 0)) == NULL) {
LOG("Could not get attributes\n");
ELOG("Could not get attributes\n");
return;
}
@ -156,9 +180,9 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child,
/* Events for already managed windows should already be filtered in manage_window() */
assert(new == NULL);
LOG("Reparenting window 0x%08x\n", child);
LOG("x = %d, y = %d, width = %d, height = %d\n", x, y, width, height);
new = calloc(sizeof(Client), 1);
LOG("Managing window 0x%08x\n", child);
DLOG("x = %d, y = %d, width = %d, height = %d\n", x, y, width, height);
new = scalloc(sizeof(Client));
new->force_reconfigure = true;
/* Update the data structures */
@ -220,7 +244,7 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child,
new->awaiting_useless_unmap = true;
xcb_void_cookie_t cookie = xcb_reparent_window_checked(conn, child, new->frame, 0, font->height);
if (xcb_request_check(conn, cookie) != NULL) {
LOG("Could not reparent the window, aborting\n");
DLOG("Could not reparent the window, aborting\n");
xcb_destroy_window(conn, new->frame);
free(new);
return;
@ -247,13 +271,19 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child,
if (preply != NULL && preply->value_len > 0 && (atom = xcb_get_property_value(preply))) {
for (int i = 0; i < xcb_get_property_value_length(preply); i++)
if (atom[i] == atoms[_NET_WM_WINDOW_TYPE_DOCK]) {
LOG("Window is a dock.\n");
DLOG("Window is a dock.\n");
Output *t_out = get_output_containing(x, y);
if (t_out != c_ws->output) {
DLOG("Dock client requested to be on output %s by geometry (%d, %d)\n",
t_out->name, x, y);
new->workspace = t_out->current_workspace;
}
new->dock = true;
new->borderless = true;
new->titlebar_position = TITLEBAR_OFF;
new->force_reconfigure = true;
new->container = NULL;
SLIST_INSERT_HEAD(&(c_ws->screen->dock_clients), new, dock_clients);
SLIST_INSERT_HEAD(&(t_out->dock_clients), new, dock_clients);
/* If its a dock we cant make it float, so we break */
new->floating = FLOATING_AUTO_OFF;
break;
@ -263,19 +293,19 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child,
atom[i] == atoms[_NET_WM_WINDOW_TYPE_SPLASH]) {
/* Set the dialog window to automatically floating, will be used below */
new->floating = FLOATING_AUTO_ON;
LOG("dialog/utility/toolbar/splash window, automatically floating\n");
DLOG("dialog/utility/toolbar/splash window, automatically floating\n");
}
}
/* All clients which have a leader should be floating */
if (!new->dock && !client_is_floating(new) && new->leader != 0) {
LOG("Client has WM_CLIENT_LEADER hint set, setting floating\n");
DLOG("Client has WM_CLIENT_LEADER hint set, setting floating\n");
new->floating = FLOATING_AUTO_ON;
}
if (new->workspace->auto_float) {
new->floating = FLOATING_AUTO_ON;
LOG("workspace is in autofloat mode, setting floating\n");
DLOG("workspace is in autofloat mode, setting floating\n");
}
if (new->dock) {
@ -289,12 +319,12 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child,
TODO: bars at the top */
new->desired_height = strut[3];
if (new->desired_height == 0) {
LOG("Client wanted to be 0 pixels high, using the window's height (%d)\n", original_height);
DLOG("Client wanted to be 0 pixels high, using the window's height (%d)\n", original_height);
new->desired_height = original_height;
}
LOG("the client wants to be %d pixels high\n", new->desired_height);
DLOG("the client wants to be %d pixels high\n", new->desired_height);
} else {
LOG("The client didn't specify space to reserve at the screen edge, using its height (%d)\n", original_height);
DLOG("The client didn't specify space to reserve at the screen edge, using its height (%d)\n", original_height);
new->desired_height = original_height;
}
} else {
@ -316,6 +346,29 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child,
preply = xcb_get_property_reply(conn, leader_cookie, NULL);
handle_clientleader_change(NULL, conn, 0, new->child, atoms[WM_CLIENT_LEADER], preply);
/* if WM_CLIENT_LEADER is set, we put the new window on the
* same window as its leader. This might be overwritten by
* assignments afterwards. */
if (new->leader != XCB_NONE) {
DLOG("client->leader is set (to 0x%08x)\n", new->leader);
Client *parent = table_get(&by_child, new->leader);
if (parent != NULL && parent->container != NULL) {
Workspace *t_ws = parent->workspace;
new->container = t_ws->table[parent->container->col][parent->container->row];
new->workspace = t_ws;
old_focused = new->container->currently_focused;
map_frame = workspace_is_visible(t_ws);
new->urgent = true;
/* This is a little tricky: we cannot use
* workspace_update_urgent_flag() because the
* new window was not yet inserted into the
* focus stack on t_ws. */
t_ws->urgent = true;
} else {
DLOG("parent is not usable\n");
}
}
struct Assignment *assign;
TAILQ_FOREACH(assign, &assignments, assignments) {
if (get_matching_client(conn, assign->windowclass_title, new) == NULL)
@ -332,14 +385,14 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child,
LOG("Assignment \"%s\" matches, so putting it on workspace %d\n",
assign->windowclass_title, assign->workspace);
if (c_ws->screen->current_workspace->num == (assign->workspace-1)) {
LOG("We are already there, no need to do anything\n");
if (c_ws->output->current_workspace->num == (assign->workspace-1)) {
DLOG("We are already there, no need to do anything\n");
break;
}
LOG("Changing container/workspace and unmapping the client\n");
DLOG("Changing container/workspace and unmapping the client\n");
Workspace *t_ws = workspace_get(assign->workspace-1);
workspace_initialize(t_ws, c_ws->screen);
workspace_initialize(t_ws, c_ws->output, false);
new->container = t_ws->table[t_ws->current_col][t_ws->current_row];
new->workspace = t_ws;
@ -351,7 +404,7 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child,
}
if (new->workspace->fullscreen_client != NULL) {
LOG("Setting below fullscreen window\n");
DLOG("Setting below fullscreen window\n");
/* If we are in fullscreen, we should place the window below
* the fullscreen window to not be annoying */
@ -394,10 +447,10 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child,
* to (0, 0), so we push them to a reasonable position
* (centered over their leader) */
if (new->leader != 0 && x == 0 && y == 0) {
LOG("Floating client wants to (0x0), moving it over its leader instead\n");
DLOG("Floating client wants to (0x0), moving it over its leader instead\n");
Client *leader = table_get(&by_child, new->leader);
if (leader == NULL) {
LOG("leader is NULL, centering it over current workspace\n");
DLOG("leader is NULL, centering it over current workspace\n");
x = c_ws->rect.x + (c_ws->rect.width / 2) - (new->rect.width / 2);
y = c_ws->rect.y + (c_ws->rect.height / 2) - (new->rect.height / 2);
@ -408,10 +461,10 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child,
}
new->floating_rect.x = new->rect.x = x;
new->floating_rect.y = new->rect.y = y;
LOG("copying floating_rect from tiling (%d, %d) size (%d, %d)\n",
DLOG("copying floating_rect from tiling (%d, %d) size (%d, %d)\n",
new->floating_rect.x, new->floating_rect.y,
new->floating_rect.width, new->floating_rect.height);
LOG("outer rect (%d, %d) size (%d, %d)\n",
DLOG("outer rect (%d, %d) size (%d, %d)\n",
new->rect.x, new->rect.y, new->rect.width, new->rect.height);
/* Make sure it is on top of the other windows */
@ -455,8 +508,10 @@ map:
if (map_frame)
render_container(conn, new->container);
}
if (new->container == CUR_CELL || client_is_floating(new))
if (new->container == CUR_CELL || client_is_floating(new)) {
xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, new->child, XCB_CURRENT_TIME);
ewmh_update_active_window(new->child);
}
}
}

532
src/randr.c Normal file
View File

@ -0,0 +1,532 @@
/*
* vim:ts=8:expandtab
*
* i3 - an improved dynamic tiling window manager
*
* © 2009-2010 Michael Stapelberg and contributors
*
* See file LICENSE for license information.
*
* For more information on RandR, please see the X.org RandR specification at
* http://cgit.freedesktop.org/xorg/proto/randrproto/tree/randrproto.txt
* (take your time to read it completely, it answers all questions).
*
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <assert.h>
#include <time.h>
#include <unistd.h>
#include <xcb/xcb.h>
#include <xcb/randr.h>
#include "queue.h"
#include "i3.h"
#include "data.h"
#include "table.h"
#include "util.h"
#include "layout.h"
#include "xcb.h"
#include "config.h"
#include "workspace.h"
#include "log.h"
#include "ewmh.h"
#include "ipc.h"
#include "client.h"
/* While a clean namespace is usually a pretty good thing, we really need
* to use shorter names than the whole xcb_randr_* default names. */
typedef xcb_randr_get_crtc_info_reply_t crtc_info;
typedef xcb_randr_mode_info_t mode_info;
typedef xcb_randr_get_screen_resources_current_reply_t resources_reply;
/* Stores all outputs available in your current session. */
struct outputs_head outputs = TAILQ_HEAD_INITIALIZER(outputs);
static bool randr_disabled = false;
/*
* Get a specific output by its internal X11 id. Used by randr_query_outputs
* to check if the output is new (only in the first scan) or if we are
* re-scanning.
*
*/
static Output *get_output_by_id(xcb_randr_output_t id) {
Output *output;
TAILQ_FOREACH(output, &outputs, outputs)
if (output->id == id)
return output;
return NULL;
}
/*
* Returns the output with the given name if it is active (!) or NULL.
*
*/
Output *get_output_by_name(const char *name) {
Output *output;
TAILQ_FOREACH(output, &outputs, outputs)
if (output->active &&
strcasecmp(output->name, name) == 0)
return output;
return NULL;
}
/*
* Returns the first output which is active.
*
*/
Output *get_first_output() {
Output *output;
TAILQ_FOREACH(output, &outputs, outputs)
if (output->active)
return output;
return NULL;
}
/*
* Returns the active (!) output which contains the coordinates x, y or NULL
* if there is no output which contains these coordinates.
*
*/
Output *get_output_containing(int x, int y) {
Output *output;
TAILQ_FOREACH(output, &outputs, outputs) {
if (!output->active)
continue;
DLOG("comparing x=%d y=%d with x=%d and y=%d width %d height %d\n",
x, y, output->rect.x, output->rect.y, output->rect.width, output->rect.height);
if (x >= output->rect.x && x < (output->rect.x + output->rect.width) &&
y >= output->rect.y && y < (output->rect.y + output->rect.height))
return output;
}
return NULL;
}
/*
* Gets the output which is the last one in the given direction, for example
* the output on the most bottom when direction == D_DOWN, the output most
* right when direction == D_RIGHT and so on.
*
* This function always returns a output.
*
*/
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;
}
/*
* Initializes the specified output, assigning the specified workspace to it.
*
*/
void initialize_output(xcb_connection_t *conn, Output *output, Workspace *workspace) {
i3Font *font = load_font(conn, config.font);
workspace->output = output;
output->current_workspace = workspace;
/* Copy rect for the workspace */
memcpy(&(workspace->rect), &(output->rect), sizeof(Rect));
/* Map clients on the workspace, if any */
workspace_map_clients(conn, workspace);
/* Create a bar window on each output */
if (!config.disable_workspace_bar) {
Rect bar_rect = {output->rect.x,
output->rect.y + output->rect.height - (font->height + 6),
output->rect.x + output->rect.width,
font->height + 6};
uint32_t mask = XCB_CW_OVERRIDE_REDIRECT | XCB_CW_EVENT_MASK;
uint32_t values[] = {1, XCB_EVENT_MASK_EXPOSURE | XCB_EVENT_MASK_BUTTON_PRESS};
output->bar = create_window(conn, bar_rect, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_CURSOR_LEFT_PTR, true, mask, values);
output->bargc = xcb_generate_id(conn);
xcb_create_gc(conn, output->bargc, output->bar, 0, 0);
}
SLIST_INIT(&(output->dock_clients));
ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"init\"}");
DLOG("initialized output at (%d, %d) with %d x %d\n",
output->rect.x, output->rect.y, output->rect.width, output->rect.height);
DLOG("assigning configured workspaces to this output...\n");
Workspace *ws;
TAILQ_FOREACH(ws, workspaces, workspaces) {
if (ws == workspace)
continue;
if (ws->preferred_output == NULL ||
get_output_by_name(ws->preferred_output) != output)
continue;
DLOG("assigning ws %d\n", ws->num + 1);
workspace_assign_to(ws, output, true);
}
}
/*
* Disables RandR support by creating exactly one output with the size of the
* X11 screen.
*
*/
void disable_randr(xcb_connection_t *conn) {
xcb_screen_t *root_screen = xcb_setup_roots_iterator(xcb_get_setup(conn)).data;
DLOG("RandR extension unusable, disabling.\n");
Output *s = scalloc(sizeof(Output));
s->active = true;
s->rect.x = 0;
s->rect.y = 0;
s->rect.width = root_screen->width_in_pixels;
s->rect.height = root_screen->height_in_pixels;
s->name = "xroot-0";
TAILQ_INSERT_TAIL(&outputs, s, outputs);
randr_disabled = true;
}
/*
* This function needs to be called when changing the mode of an output when
* it already has some workspaces (or a bar window) assigned.
*
* It reconfigures the bar window for the new mode, copies the new rect into
* each workspace on this output and forces all windows on the affected
* workspaces to be reconfigured.
*
* It is necessary to call render_layout() afterwards.
*
*/
static void output_change_mode(xcb_connection_t *conn, Output *output) {
i3Font *font = load_font(conn, config.font);
Workspace *ws;
Client *client;
DLOG("Output mode changed, reconfiguring bar, updating workspaces\n");
Rect bar_rect = {output->rect.x,
output->rect.y + output->rect.height - (font->height + 6),
output->rect.x + output->rect.width,
font->height + 6};
xcb_set_window_rect(conn, output->bar, bar_rect);
/* go through all workspaces and set force_reconfigure */
TAILQ_FOREACH(ws, workspaces, workspaces) {
if (ws->output != output)
continue;
SLIST_FOREACH(client, &(ws->focus_stack), focus_clients) {
client->force_reconfigure = true;
if (!client_is_floating(client))
continue;
/* For floating clients we need to translate the
* coordinates (old workspace to new workspace) */
DLOG("old: (%x, %x)\n", client->rect.x, client->rect.y);
client->rect.x -= ws->rect.x;
client->rect.y -= ws->rect.y;
client->rect.x += ws->output->rect.x;
client->rect.y += ws->output->rect.y;
DLOG("new: (%x, %x)\n", client->rect.x, client->rect.y);
}
/* Update dimensions from output */
memcpy(&(ws->rect), &(ws->output->rect), sizeof(Rect));
/* Update the dimensions of a fullscreen client, if any */
if (ws->fullscreen_client != NULL) {
DLOG("Updating fullscreen client size\n");
client = ws->fullscreen_client;
Rect r = ws->rect;
xcb_set_window_rect(conn, client->frame, r);
r.x = 0;
r.y = 0;
xcb_set_window_rect(conn, client->child, r);
}
}
}
/*
* Gets called by randr_query_outputs() for each output. The function adds new
* outputs to the list of outputs, checks if the mode of existing outputs has
* been changed or if an existing output has been disabled. It will then change
* either the "changed" or the "to_be_deleted" flag of the output, if
* appropriate.
*
*/
static void handle_output(xcb_connection_t *conn, xcb_randr_output_t id,
xcb_randr_get_output_info_reply_t *output,
xcb_timestamp_t cts, resources_reply *res) {
/* each CRT controller has a position in which we are interested in */
crtc_info *crtc;
Output *new = get_output_by_id(id);
bool existing = (new != NULL);
if (!existing)
new = scalloc(sizeof(Output));
new->id = id;
FREE(new->name);
asprintf(&new->name, "%.*s",
xcb_randr_get_output_info_name_length(output),
xcb_randr_get_output_info_name(output));
DLOG("found output with name %s\n", new->name);
/* Even if no CRTC is used at the moment, we store the output so that
* we do not need to change the list ever again (we only update the
* position/size) */
if (output->crtc == XCB_NONE) {
if (!existing)
TAILQ_INSERT_TAIL(&outputs, new, outputs);
else if (new->active)
new->to_be_disabled = true;
return;
}
xcb_randr_get_crtc_info_cookie_t icookie;
icookie = xcb_randr_get_crtc_info(conn, output->crtc, cts);
if ((crtc = xcb_randr_get_crtc_info_reply(conn, icookie, NULL)) == NULL) {
DLOG("Skipping output %s: could not get CRTC (%p)\n",
new->name, crtc);
free(new);
return;
}
bool updated = update_if_necessary(&(new->rect.x), crtc->x) |
update_if_necessary(&(new->rect.y), crtc->y) |
update_if_necessary(&(new->rect.width), crtc->width) |
update_if_necessary(&(new->rect.height), crtc->height);
free(crtc);
new->active = (new->rect.width != 0 && new->rect.height != 0);
if (!new->active) {
DLOG("width/height 0/0, disabling output\n");
return;
}
DLOG("mode: %dx%d+%d+%d\n", new->rect.width, new->rect.height,
new->rect.x, new->rect.y);
/* If we dont need to change an existing output or if the output
* does not exist in the first place, the case is simple: we either
* need to insert the new output or we are done. */
if (!updated || !existing) {
if (!existing)
TAILQ_INSERT_TAIL(&outputs, new, outputs);
return;
}
new->changed = true;
}
/*
* (Re-)queries the outputs via RandR and stores them in the list of outputs.
*
*/
void randr_query_outputs(xcb_connection_t *conn) {
Workspace *ws;
Output *output, *other, *first;
xcb_randr_get_screen_resources_current_cookie_t rcookie;
resources_reply *res;
/* timestamp of the configuration so that we get consistent replies to all
* requests (if the configuration changes between our different calls) */
xcb_timestamp_t cts;
/* an output is VGA-1, LVDS-1, etc. (usually physical video outputs) */
xcb_randr_output_t *randr_outputs;
if (randr_disabled)
return;
/* Get screen resources (crtcs, outputs, modes) */
rcookie = xcb_randr_get_screen_resources_current(conn, root);
if ((res = xcb_randr_get_screen_resources_current_reply(conn, rcookie, NULL)) == NULL) {
disable_randr(conn);
return;
}
cts = res->config_timestamp;
int len = xcb_randr_get_screen_resources_current_outputs_length(res);
randr_outputs = xcb_randr_get_screen_resources_current_outputs(res);
/* Request information for each output */
xcb_randr_get_output_info_cookie_t ocookie[len];
for (int i = 0; i < len; i++)
ocookie[i] = xcb_randr_get_output_info(conn, randr_outputs[i], cts);
/* Loop through all outputs available for this X11 screen */
for (int i = 0; i < len; i++) {
xcb_randr_get_output_info_reply_t *output;
if ((output = xcb_randr_get_output_info_reply(conn, ocookie[i], NULL)) == NULL)
continue;
handle_output(conn, randr_outputs[i], output, cts, res);
free(output);
}
free(res);
/* Check for clones, disable the clones and reduce the mode to the
* lowest common mode */
TAILQ_FOREACH(output, &outputs, outputs) {
if (!output->active || output->to_be_disabled)
continue;
DLOG("output %p, position (%d, %d), checking for clones\n",
output, output->rect.x, output->rect.y);
for (other = output;
other != TAILQ_END(&outputs);
other = TAILQ_NEXT(other, outputs)) {
if (other == output || !other->active || other->to_be_disabled)
continue;
if (other->rect.x != output->rect.x ||
other->rect.y != output->rect.y)
continue;
DLOG("output %p has the same position, his mode = %d x %d\n",
other, other->rect.width, other->rect.height);
uint32_t width = min(other->rect.width, output->rect.width);
uint32_t height = min(other->rect.height, output->rect.height);
if (update_if_necessary(&(output->rect.width), width) |
update_if_necessary(&(output->rect.height), height))
output->changed = true;
update_if_necessary(&(other->rect.width), width);
update_if_necessary(&(other->rect.height), height);
DLOG("disabling output %p (%s)\n", other, other->name);
other->to_be_disabled = true;
DLOG("new output mode %d x %d, other mode %d x %d\n",
output->rect.width, output->rect.height,
other->rect.width, other->rect.height);
}
}
/* Handle outputs which have a new mode or are disabled now (either
* because the user disabled them or because they are clones) */
TAILQ_FOREACH(output, &outputs, outputs) {
if (output->to_be_disabled) {
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");
bool needs_init = (first->current_workspace == NULL);
TAILQ_FOREACH(ws, workspaces, workspaces) {
if (ws->output != output)
continue;
workspace_assign_to(ws, first, true);
if (!needs_init)
continue;
initialize_output(conn, first, ws);
needs_init = false;
}
Client *dock;
while (!SLIST_EMPTY(&(output->dock_clients))) {
dock = SLIST_FIRST(&(output->dock_clients));
SLIST_REMOVE_HEAD(&(output->dock_clients), dock_clients);
SLIST_INSERT_HEAD(&(first->dock_clients), dock, dock_clients);
}
output->current_workspace = NULL;
output->to_be_disabled = false;
} else if (output->changed) {
output_change_mode(conn, output);
output->changed = false;
}
}
if (TAILQ_EMPTY(&outputs)) {
ELOG("No outputs found via RandR, disabling\n");
disable_randr(conn);
}
ewmh_update_workarea();
/* Just go through each active output and associate one workspace */
TAILQ_FOREACH(output, &outputs, outputs) {
if (!output->active || output->current_workspace != NULL)
continue;
ws = get_first_workspace_for_output(output);
initialize_output(conn, output, ws);
}
/* render_layout flushes */
render_layout(conn);
}
/*
* We have just established a connection to the X server and need the initial
* XRandR information to setup workspaces for each screen.
*
*/
void initialize_randr(xcb_connection_t *conn, int *event_base) {
const xcb_query_extension_reply_t *extreply;
extreply = xcb_get_extension_data(conn, &xcb_randr_id);
if (!extreply->present)
disable_randr(conn);
else randr_query_outputs(conn);
if (event_base != NULL)
*event_base = extreply->first_event;
xcb_randr_select_input(conn, root,
XCB_RANDR_NOTIFY_MASK_SCREEN_CHANGE |
XCB_RANDR_NOTIFY_MASK_OUTPUT_CHANGE |
XCB_RANDR_NOTIFY_MASK_CRTC_CHANGE |
XCB_RANDR_NOTIFY_MASK_OUTPUT_PROPERTY);
xcb_flush(conn);
}

View File

@ -3,7 +3,7 @@
*
* i3 - an improved dynamic tiling window manager
*
* © 2009 Michael Stapelberg and contributors
* © 2009-2010 Michael Stapelberg and contributors
*
* See file LICENSE for license information.
*
@ -24,10 +24,49 @@
#include "xcb.h"
#include "debug.h"
#include "layout.h"
#include "xinerama.h"
#include "randr.h"
#include "config.h"
#include "floating.h"
#include "workspace.h"
#include "log.h"
/*
* This is an ugly data structure which we need because there is no standard
* way of having nested functions (only available as a gcc extension at the
* moment, clang doesnt support it) or blocks (only available as a clang
* extension and only on Mac OS X systems at the moment).
*
*/
struct callback_params {
resize_orientation_t orientation;
Output *screen;
xcb_window_t helpwin;
uint32_t *new_position;
};
DRAGGING_CB(resize_callback) {
struct callback_params *params = extra;
Output *screen = params->screen;
DLOG("new x = %d, y = %d\n", new_x, new_y);
if (params->orientation == O_VERTICAL) {
/* Check if the new coordinates are within screen boundaries */
if (new_x > (screen->rect.x + screen->rect.width - 25) ||
new_x < (screen->rect.x + 25))
return;
*(params->new_position) = new_x;
xcb_configure_window(conn, params->helpwin, XCB_CONFIG_WINDOW_X, params->new_position);
} else {
if (new_y > (screen->rect.y + screen->rect.height - 25) ||
new_y < (screen->rect.y + 25))
return;
*(params->new_position) = new_y;
xcb_configure_window(conn, params->helpwin, XCB_CONFIG_WINDOW_Y, params->new_position);
}
xcb_flush(conn);
}
/*
* Renders the resize window between the first/second container and resizes
@ -36,10 +75,10 @@
*/
int resize_graphical_handler(xcb_connection_t *conn, Workspace *ws, int first, int second,
resize_orientation_t orientation, xcb_button_press_event_t *event) {
int new_position;
i3Screen *screen = get_screen_containing(event->root_x, event->root_y);
uint32_t new_position;
Output *screen = get_output_containing(event->root_x, event->root_y);
if (screen == NULL) {
LOG("BUG: No screen found at this position (%d, %d)\n", event->root_x, event->root_y);
ELOG("BUG: No screen found at this position (%d, %d)\n", event->root_x, event->root_y);
return 1;
}
@ -48,12 +87,12 @@ int resize_graphical_handler(xcb_connection_t *conn, Workspace *ws, int first, i
* screens during runtime. Instead, we just use the most right and most
* bottom Xinerama screen and use their position + width/height to get
* the area of pixels currently in use */
i3Screen *most_right = get_screen_most(D_RIGHT, screen),
*most_bottom = get_screen_most(D_DOWN, screen);
Output *most_right = get_output_most(D_RIGHT, screen),
*most_bottom = get_output_most(D_DOWN, screen);
LOG("event->event_x = %d, event->root_x = %d\n", event->event_x, event->root_x);
DLOG("event->event_x = %d, event->root_x = %d\n", event->event_x, event->root_x);
LOG("Screen dimensions: (%d, %d) %d x %d\n", screen->rect.x, screen->rect.y, screen->rect.width, screen->rect.height);
DLOG("Screen dimensions: (%d, %d) %d x %d\n", screen->rect.x, screen->rect.y, screen->rect.width, screen->rect.height);
uint32_t mask = 0;
uint32_t values[2];
@ -92,36 +131,16 @@ int resize_graphical_handler(xcb_connection_t *conn, Workspace *ws, int first, i
xcb_window_t helpwin = create_window(conn, helprect, XCB_WINDOW_CLASS_INPUT_OUTPUT,
(orientation == O_VERTICAL ?
XCB_CURSOR_SB_V_DOUBLE_ARROW :
XCB_CURSOR_SB_H_DOUBLE_ARROW), true, mask, values);
XCB_CURSOR_SB_H_DOUBLE_ARROW :
XCB_CURSOR_SB_V_DOUBLE_ARROW), true, mask, values);
xcb_circulate_window(conn, XCB_CIRCULATE_RAISE_LOWEST, helpwin);
xcb_flush(conn);
void resize_callback(Rect *old_rect, uint32_t new_x, uint32_t new_y) {
LOG("new x = %d, y = %d\n", new_x, new_y);
if (orientation == O_VERTICAL) {
/* Check if the new coordinates are within screen boundaries */
if (new_x > (screen->rect.x + screen->rect.width - 25) ||
new_x < (screen->rect.x + 25))
return;
struct callback_params params = { orientation, screen, helpwin, &new_position };
values[0] = new_position = new_x;
xcb_configure_window(conn, helpwin, XCB_CONFIG_WINDOW_X, values);
} else {
if (new_y > (screen->rect.y + screen->rect.height - 25) ||
new_y < (screen->rect.y + 25))
return;
values[0] = new_position = new_y;
xcb_configure_window(conn, helpwin, XCB_CONFIG_WINDOW_Y, values);
}
xcb_flush(conn);
}
drag_pointer(conn, NULL, event, grabwin, BORDER_TOP, resize_callback);
drag_pointer(conn, NULL, event, grabwin, BORDER_TOP, resize_callback, &params);
xcb_destroy_window(conn, helpwin);
xcb_destroy_window(conn, grabwin);
@ -163,8 +182,29 @@ void resize_container(xcb_connection_t *conn, Workspace *ws, int first, int seco
if (ws->width_factor[second] == 0)
new_unoccupied_x += default_width;
LOG("\n\n\n");
LOG("old = %d, new = %d\n", old_unoccupied_x, new_unoccupied_x);
DLOG("\n\n\n");
DLOG("old = %d, new = %d\n", old_unoccupied_x, new_unoccupied_x);
int cols_without_wf = 0;
int old_width, old_second_width;
for (int col = 0; col < ws->cols; col++)
if (ws->width_factor[col] == 0)
cols_without_wf++;
DLOG("old_unoccupied_x = %d\n", old_unoccupied_x);
DLOG("Updating first (before = %f)\n", ws->width_factor[first]);
/* Convert 0 (for default width_factor) to actual numbers */
if (ws->width_factor[first] == 0)
old_width = (old_unoccupied_x / max(cols_without_wf, 1));
else old_width = ws->width_factor[first] * old_unoccupied_x;
DLOG("second (before = %f)\n", ws->width_factor[second]);
if (ws->width_factor[second] == 0)
old_second_width = (old_unoccupied_x / max(cols_without_wf, 1));
else old_second_width = ws->width_factor[second] * old_unoccupied_x;
DLOG("middle = %f\n", ws->width_factor[first]);
/* If the space used for customly resized columns has changed we need to adapt the
* other customly resized columns, if any */
@ -173,37 +213,33 @@ void resize_container(xcb_connection_t *conn, Workspace *ws, int first, int seco
if (ws->width_factor[col] == 0)
continue;
LOG("Updating other column (%d) (current width_factor = %f)\n", col, ws->width_factor[col]);
DLOG("Updating other column (%d) (current width_factor = %f)\n", col, ws->width_factor[col]);
ws->width_factor[col] = (ws->width_factor[col] * old_unoccupied_x) / new_unoccupied_x;
LOG("to %f\n", ws->width_factor[col]);
DLOG("to %f\n", ws->width_factor[col]);
}
LOG("old_unoccupied_x = %d\n", old_unoccupied_x);
LOG("Updating first (before = %f)\n", ws->width_factor[first]);
DLOG("Updating first (before = %f)\n", ws->width_factor[first]);
/* Convert 0 (for default width_factor) to actual numbers */
if (ws->width_factor[first] == 0)
ws->width_factor[first] = ((float)ws->rect.width / ws->cols) / new_unoccupied_x;
LOG("middle = %f\n", ws->width_factor[first]);
int old_width = ws->width_factor[first] * old_unoccupied_x;
LOG("first->width = %d, pixels = %d\n", pixels);
DLOG("first->width = %d, pixels = %d\n", old_width, pixels);
ws->width_factor[first] *= (float)(old_width + pixels) / old_width;
LOG("-> %f\n", ws->width_factor[first]);
DLOG("-> %f\n", ws->width_factor[first]);
LOG("Updating second (before = %f)\n", ws->width_factor[second]);
DLOG("Updating second (before = %f)\n", ws->width_factor[second]);
if (ws->width_factor[second] == 0)
ws->width_factor[second] = ((float)ws->rect.width / ws->cols) / new_unoccupied_x;
LOG("middle = %f\n", ws->width_factor[second]);
old_width = ws->width_factor[second] * old_unoccupied_x;
LOG("second->width = %d, pixels = %d\n", pixels);
ws->width_factor[second] *= (float)(old_width - pixels) / old_width;
LOG("-> %f\n", ws->width_factor[second]);
LOG("new unoccupied_x = %d\n", get_unoccupied_x(ws));
DLOG("middle = %f\n", ws->width_factor[second]);
DLOG("second->width = %d, pixels = %d\n", old_second_width, pixels);
ws->width_factor[second] *= (float)(old_second_width - pixels) / old_second_width;
DLOG("-> %f\n", ws->width_factor[second]);
LOG("\n\n\n");
DLOG("new unoccupied_x = %d\n", get_unoccupied_x(ws));
DLOG("\n\n\n");
} else {
int ws_height = workspace_height(ws);
int default_height = ws_height / ws->rows;
@ -228,24 +264,25 @@ void resize_container(xcb_connection_t *conn, Workspace *ws, int first, int seco
if (ws->height_factor[row] == 0)
cols_without_hf++;
LOG("old_unoccupied_y = %d\n", old_unoccupied_y);
DLOG("old_unoccupied_y = %d\n", old_unoccupied_y);
DLOG("Updating first (before = %f)\n", ws->height_factor[first]);
LOG("Updating first (before = %f)\n", ws->height_factor[first]);
/* Convert 0 (for default width_factor) to actual numbers */
if (ws->height_factor[first] == 0)
old_height = (old_unoccupied_y / max(cols_without_hf, 1));
else old_height = ws->height_factor[first] * old_unoccupied_y;
LOG("second (before = %f)\n", ws->height_factor[second]);
DLOG("second (before = %f)\n", ws->height_factor[second]);
if (ws->height_factor[second] == 0)
old_second_height = (old_unoccupied_y / max(cols_without_hf, 1));
else old_second_height = ws->height_factor[second] * old_unoccupied_y;
LOG("middle = %f\n", ws->height_factor[first]);
DLOG("middle = %f\n", ws->height_factor[first]);
LOG("\n\n\n");
LOG("old = %d, new = %d\n", old_unoccupied_y, new_unoccupied_y);
DLOG("\n\n\n");
DLOG("old = %d, new = %d\n", old_unoccupied_y, new_unoccupied_y);
/* If the space used for customly resized columns has changed we need to adapt the
* other customly resized columns, if any */
@ -254,33 +291,33 @@ void resize_container(xcb_connection_t *conn, Workspace *ws, int first, int seco
if (ws->height_factor[row] == 0)
continue;
LOG("Updating other column (%d) (current width_factor = %f)\n", row, ws->height_factor[row]);
DLOG("Updating other column (%d) (current width_factor = %f)\n", row, ws->height_factor[row]);
ws->height_factor[row] = (ws->height_factor[row] * old_unoccupied_y) / new_unoccupied_y;
LOG("to %f\n", ws->height_factor[row]);
DLOG("to %f\n", ws->height_factor[row]);
}
LOG("Updating first (before = %f)\n", ws->height_factor[first]);
DLOG("Updating first (before = %f)\n", ws->height_factor[first]);
/* Convert 0 (for default width_factor) to actual numbers */
if (ws->height_factor[first] == 0)
ws->height_factor[first] = ((float)ws_height / ws->rows) / new_unoccupied_y;
LOG("first->width = %d, pixels = %d\n", old_height, pixels);
DLOG("first->width = %d, pixels = %d\n", old_height, pixels);
ws->height_factor[first] *= (float)(old_height + pixels) / old_height;
LOG("-> %f\n", ws->height_factor[first]);
DLOG("-> %f\n", ws->height_factor[first]);
LOG("Updating second (before = %f)\n", ws->height_factor[second]);
DLOG("Updating second (before = %f)\n", ws->height_factor[second]);
if (ws->height_factor[second] == 0)
ws->height_factor[second] = ((float)ws_height / ws->rows) / new_unoccupied_y;
LOG("middle = %f\n", ws->height_factor[second]);
LOG("second->width = %d, pixels = %d\n", old_second_height, pixels);
DLOG("middle = %f\n", ws->height_factor[second]);
DLOG("second->width = %d, pixels = %d\n", old_second_height, pixels);
ws->height_factor[second] *= (float)(old_second_height - pixels) / old_second_height;
LOG("-> %f\n", ws->height_factor[second]);
DLOG("-> %f\n", ws->height_factor[second]);
LOG("new unoccupied_y = %d\n", get_unoccupied_y(ws));
DLOG("new unoccupied_y = %d\n", get_unoccupied_y(ws));
LOG("\n\n\n");
DLOG("\n\n\n");
}
render_layout(conn);

224
src/sighandler.c Normal file
View File

@ -0,0 +1,224 @@
/*
* vim:ts=8:expandtab
*
* i3 - an improved dynamic tiling window manager
*
* © 2009-2010 Michael Stapelberg and contributors
* © 2009-2010 Jan-Erik Rediger
*
* See file LICENSE for license information.
*
* sighandler.c: contains all functions for signal handling
*
*/
#include <ev.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <iconv.h>
#include <signal.h>
#include <xcb/xcb.h>
#include <xcb/xcb_aux.h>
#include <xcb/xcb_event.h>
#include <xcb/xcb_keysyms.h>
#include <X11/keysym.h>
#include "i3.h"
#include "util.h"
#include "xcb.h"
#include "log.h"
#include "config.h"
#include "randr.h"
static xcb_gcontext_t pixmap_gc;
static xcb_pixmap_t pixmap;
static int raised_signal;
static char *crash_text[] = {
"i3 just crashed.",
"To debug this problem, either attach gdb now",
"or press 'e' to exit and get a core-dump.",
"If you want to keep your session,",
"press 'r' to restart i3 in-place."
};
static int crash_text_longest = 1;
/*
* Draw the window containing the info text
*
*/
static int sig_draw_window(xcb_connection_t *conn, xcb_window_t win, int width, int height, int font_height) {
/* re-draw the background */
xcb_rectangle_t border = { 0, 0, width, height},
inner = { 2, 2, width - 4, height - 4};
xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#FF0000"));
xcb_poly_fill_rectangle(conn, pixmap, pixmap_gc, 1, &border);
xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#000000"));
xcb_poly_fill_rectangle(conn, pixmap, pixmap_gc, 1, &inner);
/* restore font color */
xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#FFFFFF"));
for (int i = 0; i < sizeof(crash_text) / sizeof(char*); i++) {
int text_len = strlen(crash_text[i]);
char *full_text = convert_utf8_to_ucs2(crash_text[i], &text_len);
xcb_image_text_16(conn, text_len, pixmap, pixmap_gc, 8 /* X */,
3 + (i + 1) * font_height /* Y = baseline of font */,
(xcb_char2b_t*)full_text);
free(full_text);
}
/* Copy the contents of the pixmap to the real window */
xcb_copy_area(conn, pixmap, win, pixmap_gc, 0, 0, 0, 0, width, height);
xcb_flush(conn);
return 1;
}
/*
* Handles keypresses of 'e' or 'r' to exit or restart i3
*
*/
static int sig_handle_key_press(void *ignored, xcb_connection_t *conn, xcb_key_press_event_t *event) {
uint16_t state = event->state;
/* Apparantly, after activating numlock once, the numlock modifier
* stays turned on (use xev(1) to verify). So, to resolve useful
* keysyms, we remove the numlock flag from the event state */
state &= ~xcb_numlock_mask;
xcb_keysym_t sym = xcb_key_press_lookup_keysym(keysyms, event, state);
if (sym == 'e') {
DLOG("User issued exit-command, raising error again.\n");
raise(raised_signal);
exit(1);
}
if (sym == 'r')
i3_restart();
return 1;
}
/*
* Opens the window we use for input/output and maps it
*
*/
static xcb_window_t open_input_window(xcb_connection_t *conn, Rect screen_rect, uint32_t width, uint32_t height) {
xcb_window_t root = xcb_setup_roots_iterator(xcb_get_setup(conn)).data->root;
xcb_window_t win = xcb_generate_id(conn);
uint32_t mask = 0;
uint32_t values[2];
mask |= XCB_CW_BACK_PIXEL;
values[0] = 0;
mask |= XCB_CW_OVERRIDE_REDIRECT;
values[1] = 1;
/* center each popup on the specified screen */
uint32_t x = screen_rect.x + ((screen_rect.width / 2) - (width / 2)),
y = screen_rect.y + ((screen_rect.height / 2) - (height / 2));
xcb_create_window(conn,
XCB_COPY_FROM_PARENT,
win, /* the window id */
root, /* parent == root */
x, y, width, height, /* dimensions */
0, /* border = 0, we draw our own */
XCB_WINDOW_CLASS_INPUT_OUTPUT,
XCB_WINDOW_CLASS_COPY_FROM_PARENT, /* copy visual from parent */
mask,
values);
/* Map the window (= make it visible) */
xcb_map_window(conn, win);
return win;
}
/*
* Handle signals
* It creates a window asking the user to restart in-place
* or exit to generate a core dump
*
*/
void handle_signal(int sig, siginfo_t *info, void *data) {
DLOG("i3 crashed. SIG: %d\n", sig);
struct sigaction action;
action.sa_handler = SIG_DFL;
sigaction(sig, &action, NULL);
raised_signal = sig;
xcb_connection_t *conn = global_conn;
/* setup event handler for key presses */
xcb_event_handlers_t sig_evenths;
memset(&sig_evenths, 0, sizeof(xcb_event_handlers_t));
xcb_event_handlers_init(conn, &sig_evenths);
xcb_event_set_key_press_handler(&sig_evenths, sig_handle_key_press, NULL);
i3Font *font = load_font(conn, config.font);
/* width and height of the popup window, so that the text fits in */
int crash_text_num = sizeof(crash_text) / sizeof(char*);
int height = 13 + (crash_text_num * font->height);
/* calculate width for longest text */
int text_len = strlen(crash_text[crash_text_longest]);
char *longest_text = convert_utf8_to_ucs2(crash_text[crash_text_longest], &text_len);
int font_width = predict_text_width(conn, config.font, longest_text, text_len);
int width = font_width + 20;
/* Open a popup window on each virtual screen */
Output *screen;
xcb_window_t win;
TAILQ_FOREACH(screen, &outputs, outputs) {
if (!screen->active)
continue;
win = open_input_window(conn, screen->rect, width, height);
/* Create pixmap */
pixmap = xcb_generate_id(conn);
pixmap_gc = xcb_generate_id(conn);
xcb_create_pixmap(conn, root_depth, pixmap, win, width, height);
xcb_create_gc(conn, pixmap_gc, pixmap, 0, 0);
/* Create graphics context */
xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FONT, font->id);
/* Grab the keyboard to get all input */
xcb_grab_keyboard(conn, false, win, XCB_CURRENT_TIME, XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC);
/* Grab the cursor inside the popup */
xcb_grab_pointer(conn, false, win, XCB_NONE, XCB_GRAB_MODE_ASYNC,
XCB_GRAB_MODE_ASYNC, win, XCB_NONE, XCB_CURRENT_TIME);
sig_draw_window(conn, win, width, height, font->height);
xcb_flush(conn);
}
xcb_event_wait_for_event_loop(&sig_evenths);
}
/*
* Setup signal handlers to safely handle SIGSEGV and SIGFPE
*
*/
void setup_signal_handler() {
struct sigaction action;
action.sa_sigaction = handle_signal;
action.sa_flags = SA_NODEFER | SA_RESETHAND | SA_SIGINFO;
sigemptyset(&action.sa_mask);
if (sigaction(SIGSEGV, &action, NULL) == -1 ||
sigaction(SIGFPE, &action, NULL) == -1)
ELOG("Could not setup signal handler");
}

View File

@ -27,6 +27,7 @@
#include "layout.h"
#include "config.h"
#include "workspace.h"
#include "log.h"
int current_workspace = 0;
int num_workspaces = 1;
@ -52,7 +53,7 @@ void init_table() {
static void new_container(Workspace *workspace, Container **container, int col, int row, bool skip_layout_switch) {
Container *new;
new = *container = calloc(sizeof(Container), 1);
new = *container = scalloc(sizeof(Container));
CIRCLEQ_INIT(&(new->clients));
new->colspan = 1;
new->rowspan = 1;
@ -96,9 +97,9 @@ void expand_table_rows_at_head(Workspace *workspace) {
workspace->height_factor = realloc(workspace->height_factor, sizeof(float) * workspace->rows);
LOG("rows = %d\n", workspace->rows);
DLOG("rows = %d\n", workspace->rows);
for (int rows = (workspace->rows - 1); rows >= 1; rows--) {
LOG("Moving height_factor %d (%f) to %d\n", rows-1, workspace->height_factor[rows-1], rows);
DLOG("Moving height_factor %d (%f) to %d\n", rows-1, workspace->height_factor[rows-1], rows);
workspace->height_factor[rows] = workspace->height_factor[rows-1];
}
@ -110,7 +111,7 @@ void expand_table_rows_at_head(Workspace *workspace) {
/* Move the other rows */
for (int cols = 0; cols < workspace->cols; cols++)
for (int rows = workspace->rows - 1; rows > 0; rows--) {
LOG("Moving row %d to %d\n", rows-1, rows);
DLOG("Moving row %d to %d\n", rows-1, rows);
workspace->table[cols][rows] = workspace->table[cols][rows-1];
workspace->table[cols][rows]->row = rows;
}
@ -130,7 +131,7 @@ void expand_table_cols(Workspace *workspace) {
workspace->width_factor[workspace->cols-1] = 0;
workspace->table = realloc(workspace->table, sizeof(Container**) * workspace->cols);
workspace->table[workspace->cols-1] = calloc(sizeof(Container*) * workspace->rows, 1);
workspace->table[workspace->cols-1] = scalloc(sizeof(Container*) * workspace->rows);
for (int c = 0; c < workspace->rows; c++)
new_container(workspace, &(workspace->table[workspace->cols-1][c]), workspace->cols-1, c, true);
@ -148,21 +149,21 @@ void expand_table_cols_at_head(Workspace *workspace) {
workspace->width_factor = realloc(workspace->width_factor, sizeof(float) * workspace->cols);
LOG("cols = %d\n", workspace->cols);
DLOG("cols = %d\n", workspace->cols);
for (int cols = (workspace->cols - 1); cols >= 1; cols--) {
LOG("Moving width_factor %d (%f) to %d\n", cols-1, workspace->width_factor[cols-1], cols);
DLOG("Moving width_factor %d (%f) to %d\n", cols-1, workspace->width_factor[cols-1], cols);
workspace->width_factor[cols] = workspace->width_factor[cols-1];
}
workspace->width_factor[0] = 0;
workspace->table = realloc(workspace->table, sizeof(Container**) * workspace->cols);
workspace->table[workspace->cols-1] = calloc(sizeof(Container*) * workspace->rows, 1);
workspace->table[workspace->cols-1] = scalloc(sizeof(Container*) * workspace->rows);
/* Move the other columns */
for (int rows = 0; rows < workspace->rows; rows++)
for (int cols = workspace->cols - 1; cols > 0; cols--) {
LOG("Moving col %d to %d\n", cols-1, cols);
DLOG("Moving col %d to %d\n", cols-1, cols);
workspace->table[cols][rows] = workspace->table[cols-1][rows];
workspace->table[cols][rows]->col = cols;
}
@ -201,7 +202,7 @@ static void shrink_table_cols(Workspace *workspace) {
if (workspace->width_factor[cols] == 0)
continue;
LOG("Added free space (%f) to %d (had %f)\n", free_space, cols,
DLOG("Added free space (%f) to %d (had %f)\n", free_space, cols,
workspace->width_factor[cols]);
workspace->width_factor[cols] += free_space;
break;
@ -230,7 +231,7 @@ static void shrink_table_rows(Workspace *workspace) {
if (workspace->height_factor[rows] == 0)
continue;
LOG("Added free space (%f) to %d (had %f)\n", free_space, rows,
DLOG("Added free space (%f) to %d (had %f)\n", free_space, rows,
workspace->height_factor[rows]);
workspace->height_factor[rows] += free_space;
break;
@ -256,7 +257,7 @@ static void free_container(xcb_connection_t *conn, Workspace *workspace, int col
}
static void move_columns_from(xcb_connection_t *conn, Workspace *workspace, int cols) {
LOG("firstly freeing \n");
DLOG("firstly freeing \n");
/* Free the columns which are cleaned up */
for (int rows = 0; rows < workspace->rows; rows++)
@ -264,10 +265,10 @@ static void move_columns_from(xcb_connection_t *conn, Workspace *workspace, int
for (; cols < workspace->cols; cols++)
for (int rows = 0; rows < workspace->rows; rows++) {
LOG("at col = %d, row = %d\n", cols, rows);
DLOG("at col = %d, row = %d\n", cols, rows);
Container *new_container = workspace->table[cols][rows];
LOG("moving cols = %d to cols -1 = %d\n", cols, cols-1);
DLOG("moving cols = %d to cols -1 = %d\n", cols, cols-1);
workspace->table[cols-1][rows] = new_container;
new_container->row = rows;
@ -283,7 +284,7 @@ static void move_rows_from(xcb_connection_t *conn, Workspace *workspace, int row
for (int cols = 0; cols < workspace->cols; cols++) {
Container *new_container = workspace->table[cols][rows];
LOG("moving rows = %d to rows -1 = %d\n", rows, rows - 1);
DLOG("moving rows = %d to rows -1 = %d\n", rows, rows - 1);
workspace->table[cols][rows-1] = new_container;
new_container->row = rows-1;
@ -296,19 +297,19 @@ static void move_rows_from(xcb_connection_t *conn, Workspace *workspace, int row
*
*/
void dump_table(xcb_connection_t *conn, Workspace *workspace) {
LOG("dump_table()\n");
DLOG("dump_table()\n");
FOR_TABLE(workspace) {
Container *con = workspace->table[cols][rows];
LOG("----\n");
LOG("at col=%d, row=%d\n", cols, rows);
LOG("currently_focused = %p\n", con->currently_focused);
DLOG("----\n");
DLOG("at col=%d, row=%d\n", cols, rows);
DLOG("currently_focused = %p\n", con->currently_focused);
Client *loop;
CIRCLEQ_FOREACH(loop, &(con->clients), clients) {
LOG("got client %08x / %s\n", loop->child, loop->name);
DLOG("got client %08x / %s\n", loop->child, loop->name);
}
LOG("----\n");
DLOG("----\n");
}
LOG("done\n");
DLOG("done\n");
}
/*
@ -316,7 +317,7 @@ void dump_table(xcb_connection_t *conn, Workspace *workspace) {
*
*/
void cleanup_table(xcb_connection_t *conn, Workspace *workspace) {
LOG("cleanup_table()\n");
DLOG("cleanup_table()\n");
/* Check for empty columns if we got more than one column */
for (int cols = 0; (workspace->cols > 1) && (cols < workspace->cols);) {
@ -327,7 +328,7 @@ void cleanup_table(xcb_connection_t *conn, Workspace *workspace) {
break;
}
if (completely_empty) {
LOG("Removing completely empty column %d\n", cols);
DLOG("Removing completely empty column %d\n", cols);
if (cols < (workspace->cols - 1))
move_columns_from(conn, workspace, cols+1);
else {
@ -344,14 +345,14 @@ void cleanup_table(xcb_connection_t *conn, Workspace *workspace) {
/* Check for empty rows if we got more than one row */
for (int rows = 0; (workspace->rows > 1) && (rows < workspace->rows);) {
bool completely_empty = true;
LOG("Checking row %d\n", rows);
DLOG("Checking row %d\n", rows);
for (int cols = 0; cols < workspace->cols; cols++)
if (workspace->table[cols][rows]->currently_focused != NULL) {
completely_empty = false;
break;
}
if (completely_empty) {
LOG("Removing completely empty row %d\n", rows);
DLOG("Removing completely empty row %d\n", rows);
if (rows < (workspace->rows - 1))
move_rows_from(conn, workspace, rows+1);
else {
@ -381,25 +382,25 @@ void cleanup_table(xcb_connection_t *conn, Workspace *workspace) {
*
*/
void fix_colrowspan(xcb_connection_t *conn, Workspace *workspace) {
LOG("Fixing col/rowspan\n");
DLOG("Fixing col/rowspan\n");
FOR_TABLE(workspace) {
Container *con = workspace->table[cols][rows];
if (con->colspan > 1) {
LOG("gots one with colspan %d (at %d c, %d r)\n", con->colspan, cols, rows);
DLOG("gots one with colspan %d (at %d c, %d r)\n", con->colspan, cols, rows);
while (con->colspan > 1 &&
(!cell_exists(workspace, cols + (con->colspan-1), rows) &&
workspace->table[cols + (con->colspan - 1)][rows]->currently_focused != NULL))
con->colspan--;
LOG("fixed it to %d\n", con->colspan);
DLOG("fixed it to %d\n", con->colspan);
}
if (con->rowspan > 1) {
LOG("gots one with rowspan %d (at %d c, %d r)\n", con->rowspan, cols, rows);
DLOG("gots one with rowspan %d (at %d c, %d r)\n", con->rowspan, cols, rows);
while (con->rowspan > 1 &&
(!cell_exists(workspace, cols, rows + (con->rowspan - 1)) &&
workspace->table[cols][rows + (con->rowspan - 1)]->currently_focused != NULL))
con->rowspan--;
LOG("fixed it to %d\n", con->rowspan);
DLOG("fixed it to %d\n", con->rowspan);
}
}
}

View File

@ -31,6 +31,11 @@
#include "util.h"
#include "xcb.h"
#include "client.h"
#include "log.h"
#include "ewmh.h"
#include "manage.h"
#include "workspace.h"
#include "ipc.h"
static iconv_t conversion_descriptor = 0;
struct keyvalue_table_head by_parent = TAILQ_HEAD_INITIALIZER(by_parent);
@ -45,24 +50,14 @@ int max(int a, int b) {
}
/*
* Logs the given message to stdout while prefixing the current time to it.
* This is to be called by LOG() which includes filename/linenumber
* Updates *destination with new_value and returns true if it was changed or false
* if it was the same
*
*/
void slog(char *fmt, ...) {
va_list args;
char timebuf[64];
bool update_if_necessary(uint32_t *destination, const uint32_t new_value) {
uint32_t old_value = *destination;
va_start(args, fmt);
/* Get current time */
time_t t = time(NULL);
/* Convert time to local time (determined by the locale) */
struct tm *tmp = localtime(&t);
/* Generate time prefix */
strftime(timebuf, sizeof(timebuf), "%x %X - ", tmp);
printf("%s", timebuf);
vprintf(fmt, args);
va_end(args);
return ((*destination = new_value) != old_value);
}
/*
@ -148,7 +143,7 @@ void start_application(const char *command) {
shell = "/bin/sh";
/* This is the child */
execl(shell, shell, "-c", command, NULL);
execl(shell, shell, "-c", command, (void*)NULL);
/* not reached */
}
exit(0);
@ -164,7 +159,7 @@ void start_application(const char *command) {
void check_error(xcb_connection_t *conn, xcb_void_cookie_t cookie, char *err_message) {
xcb_generic_error_t *error = xcb_request_check(conn, cookie);
if (error != NULL) {
fprintf(stderr, "ERROR: %s : %d\n", err_message , error->error_code);
fprintf(stderr, "ERROR: %s (X error %d)\n", err_message , error->error_code);
xcb_disconnect(conn);
exit(-1);
}
@ -178,16 +173,16 @@ void check_error(xcb_connection_t *conn, xcb_void_cookie_t cookie, char *err_mes
*
*/
char *convert_utf8_to_ucs2(char *input, int *real_strlen) {
size_t input_size = strlen(input) + 1;
/* UCS-2 consumes exactly two bytes for each glyph */
int buffer_size = input_size * 2;
size_t input_size = strlen(input) + 1;
/* UCS-2 consumes exactly two bytes for each glyph */
int buffer_size = input_size * 2;
char *buffer = smalloc(buffer_size);
size_t output_size = buffer_size;
/* We need to use an additional pointer, because iconv() modifies it */
char *output = buffer;
char *buffer = smalloc(buffer_size);
size_t output_size = buffer_size;
/* We need to use an additional pointer, because iconv() modifies it */
char *output = buffer;
/* We convert the input into UCS-2 big endian */
/* We convert the input into UCS-2 big endian */
if (conversion_descriptor == 0) {
conversion_descriptor = iconv_open("UCS-2BE", "UTF-8");
if (conversion_descriptor == 0) {
@ -196,22 +191,22 @@ char *convert_utf8_to_ucs2(char *input, int *real_strlen) {
}
}
/* Get the conversion descriptor back to original state */
iconv(conversion_descriptor, NULL, NULL, NULL, NULL);
/* Get the conversion descriptor back to original state */
iconv(conversion_descriptor, NULL, NULL, NULL, NULL);
/* Convert our text */
int rc = iconv(conversion_descriptor, (void*)&input, &input_size, &output, &output_size);
/* Convert our text */
int rc = iconv(conversion_descriptor, (void*)&input, &input_size, &output, &output_size);
if (rc == (size_t)-1) {
perror("Converting to UCS-2 failed");
if (real_strlen != NULL)
*real_strlen = 0;
*real_strlen = 0;
return NULL;
}
}
if (real_strlen != NULL)
*real_strlen = ((buffer_size - output_size) / 2) - 1;
*real_strlen = ((buffer_size - output_size) / 2) - 1;
return buffer;
return buffer;
}
/*
@ -250,6 +245,7 @@ void set_focus(xcb_connection_t *conn, Client *client, bool set_anyways) {
c_ws->current_row = current_row;
c_ws->current_col = current_col;
c_ws = client->workspace;
ewmh_update_current_desktop();
/* Load current_col/current_row if we switch to a client without a container */
current_col = c_ws->current_col;
current_row = c_ws->current_row;
@ -265,6 +261,7 @@ void set_focus(xcb_connection_t *conn, Client *client, bool set_anyways) {
CLIENT_LOG(client);
/* Set focus to the entered window, and flush xcb buffer immediately */
xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, client->child, XCB_CURRENT_TIME);
ewmh_update_active_window(client->child);
//xcb_warp_pointer(conn, XCB_NONE, client->child, 0, 0, 0, 0, 10, 10);
if (client->container != NULL) {
@ -280,7 +277,7 @@ void set_focus(xcb_connection_t *conn, Client *client, bool set_anyways) {
Client *last_focused = get_last_focused_client(conn, client->container, client);
if (last_focused != NULL) {
LOG("raising above frame %p / child %p\n", last_focused->frame, last_focused->child);
DLOG("raising above frame %p / child %p\n", last_focused->frame, last_focused->child);
uint32_t values[] = { last_focused->frame, XCB_STACK_MODE_ABOVE };
xcb_configure_window(conn, client->frame, XCB_CONFIG_WINDOW_SIBLING | XCB_CONFIG_WINDOW_STACK_MODE, values);
}
@ -294,22 +291,27 @@ void set_focus(xcb_connection_t *conn, Client *client, bool set_anyways) {
/* If the last client was a floating client, we need to go to the next
* tiling client in stack and re-decorate it. */
if (old_client != NULL && client_is_floating(old_client)) {
LOG("Coming from floating client, searching next tiling...\n");
DLOG("Coming from floating client, searching next tiling...\n");
Client *current;
SLIST_FOREACH(current, &(client->workspace->focus_stack), focus_clients) {
if (client_is_floating(current))
continue;
LOG("Found window: %p / child %p\n", current->frame, current->child);
DLOG("Found window: %p / child %p\n", current->frame, current->child);
redecorate_window(conn, current);
break;
}
}
SLIST_REMOVE(&(client->workspace->focus_stack), client, Client, focus_clients);
SLIST_INSERT_HEAD(&(client->workspace->focus_stack), client, focus_clients);
/* Clear the urgency flag if set (necessary when i3 sets the flag, for
* example when automatically putting windows on the workspace of their
* leader) */
client->urgent = false;
workspace_update_urgent_flag(client->workspace);
/* If were in stacking mode, this renders the container to update changes in the title
bars and to raise the focused client */
if ((old_client != NULL) && (old_client != client) && !old_client->dock)
@ -411,14 +413,14 @@ after_stackwin:
if (client == container->currently_focused || client == last_focused)
continue;
LOG("setting %08x below %08x / %08x\n", client->frame, container->currently_focused->frame);
DLOG("setting %08x below %08x / %08x\n", client->frame, container->currently_focused->frame);
uint32_t values[] = { container->currently_focused->frame, XCB_STACK_MODE_BELOW };
xcb_configure_window(conn, client->frame,
XCB_CONFIG_WINDOW_SIBLING | XCB_CONFIG_WINDOW_STACK_MODE, values);
}
if (last_focused != NULL) {
LOG("Putting last_focused directly underneath the currently focused\n");
DLOG("Putting last_focused directly underneath the currently focused\n");
uint32_t values[] = { container->currently_focused->frame, XCB_STACK_MODE_BELOW };
xcb_configure_window(conn, last_focused->frame,
XCB_CONFIG_WINDOW_SIBLING | XCB_CONFIG_WINDOW_STACK_MODE, values);
@ -457,15 +459,15 @@ Client *get_matching_client(xcb_connection_t *conn, const char *window_classtitl
goto done;
}
LOG("Getting clients for class \"%s\" / title \"%s\"\n", to_class, to_title);
DLOG("Getting clients for class \"%s\" / title \"%s\"\n", to_class, to_title);
Workspace *ws;
TAILQ_FOREACH(ws, workspaces, workspaces) {
if (ws->screen == NULL)
if (ws->output == NULL)
continue;
Client *client;
SLIST_FOREACH(client, &(ws->focus_stack), focus_clients) {
LOG("Checking client with class=%s / %s, name=%s\n", client->window_class_instance,
DLOG("Checking client with class=%s / %s, name=%s\n", client->window_class_instance,
client->window_class_class, client->name);
if (!client_matches_class_name(client, to_class, to_title, to_title_ucs, to_title_ucs_len))
continue;
@ -481,6 +483,47 @@ done:
return matching;
}
/*
* Goes through the list of arguments (for exec()) and checks if the given argument
* is present. If not, it copies the arguments (because we cannot realloc it) and
* appends the given argument.
*
*/
static char **append_argument(char **original, char *argument) {
int num_args;
for (num_args = 0; original[num_args] != NULL; num_args++) {
DLOG("original argument: \"%s\"\n", original[num_args]);
/* If the argument is already present we return the original pointer */
if (strcmp(original[num_args], argument) == 0)
return original;
}
/* Copy the original array */
char **result = smalloc((num_args+2) * sizeof(char*));
memcpy(result, original, num_args * sizeof(char*));
result[num_args] = argument;
result[num_args+1] = NULL;
return result;
}
/*
* Restart i3 in-place
* appends -a to argument list to disable autostart
*
*/
void i3_restart() {
restore_geometry(global_conn);
ipc_shutdown();
LOG("restarting \"%s\"...\n", start_argv[0]);
/* make sure -a is in the argument list or append it */
start_argv = append_argument(start_argv, "-a");
execvp(start_argv[0], start_argv);
/* not reached */
}
#if defined(__OpenBSD__)
/*

View File

@ -3,7 +3,7 @@
*
* i3 - an improved dynamic tiling window manager
*
* © 2009 Michael Stapelberg and contributors
* © 2009-2010 Michael Stapelberg and contributors
*
* See file LICENSE for license information.
*
@ -22,10 +22,13 @@
#include "config.h"
#include "xcb.h"
#include "table.h"
#include "xinerama.h"
#include "randr.h"
#include "layout.h"
#include "workspace.h"
#include "client.h"
#include "log.h"
#include "ewmh.h"
#include "ipc.h"
/*
* Returns a pointer to the workspace with the given number (starting at 0),
@ -42,10 +45,10 @@ Workspace *workspace_get(int number) {
/* If we are still there, we could not find the requested workspace. */
int last_ws = TAILQ_LAST(workspaces, workspaces_head)->num;
LOG("We need to initialize that one, last ws = %d\n", last_ws);
DLOG("We need to initialize that one, last ws = %d\n", last_ws);
for (int c = last_ws; c < number; c++) {
LOG("Creating new ws\n");
DLOG("Creating new ws\n");
ws = scalloc(sizeof(Workspace));
ws->num = c+1;
@ -55,8 +58,12 @@ Workspace *workspace_get(int number) {
workspace_set_name(ws, NULL);
TAILQ_INSERT_TAIL(workspaces, ws, workspaces);
ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"init\"}");
}
LOG("done\n");
DLOG("done\n");
ewmh_update_workarea();
return ws;
}
@ -80,13 +87,13 @@ void workspace_set_name(Workspace *ws, const char *name) {
errx(1, "asprintf() failed");
FREE(ws->name);
FREE(ws->utf8_name);
ws->name = convert_utf8_to_ucs2(label, &(ws->name_len));
if (config.font != NULL)
ws->text_width = predict_text_width(global_conn, config.font, ws->name, ws->name_len);
else ws->text_width = 0;
free(label);
ws->utf8_name = label;
}
/*
@ -96,7 +103,7 @@ void workspace_set_name(Workspace *ws, const char *name) {
*
*/
bool workspace_is_visible(Workspace *ws) {
return (ws->screen->current_workspace == ws);
return (ws->output != NULL && ws->output->current_workspace == ws);
}
/*
@ -109,29 +116,29 @@ void workspace_show(xcb_connection_t *conn, int workspace) {
/* t_ws (to workspace) is just a convenience pointer to the workspace were switching to */
Workspace *t_ws = workspace_get(workspace-1);
LOG("show_workspace(%d)\n", workspace);
DLOG("show_workspace(%d)\n", workspace);
/* Store current_row/current_col */
c_ws->current_row = current_row;
c_ws->current_col = current_col;
/* Check if the workspace has not been used yet */
workspace_initialize(t_ws, c_ws->screen);
workspace_initialize(t_ws, c_ws->output, false);
if (c_ws->screen != t_ws->screen) {
/* We need to switch to the other screen first */
LOG("moving over to other screen.\n");
if (c_ws->output != t_ws->output) {
/* We need to switch to the other output first */
DLOG("moving over to other output.\n");
/* Store the old client */
Client *old_client = CUR_CELL->currently_focused;
c_ws = t_ws->screen->current_workspace;
c_ws = t_ws->output->current_workspace;
current_col = c_ws->current_col;
current_row = c_ws->current_row;
if (CUR_CELL->currently_focused != NULL)
need_warp = true;
else {
Rect *dims = &(c_ws->screen->rect);
Rect *dims = &(c_ws->output->rect);
xcb_warp_pointer(conn, XCB_NONE, root, 0, 0, 0, 0,
dims->x + (dims->width / 2), dims->y + (dims->height / 2));
}
@ -140,10 +147,19 @@ void workspace_show(xcb_connection_t *conn, int workspace) {
if ((old_client != NULL) && !old_client->dock)
redecorate_window(conn, old_client);
else xcb_flush(conn);
/* We need to check if a global fullscreen-client is blocking
* the t_ws and if necessary switch that to local fullscreen */
Client* client = c_ws->fullscreen_client;
if (client != NULL && client->workspace != c_ws) {
if (c_ws->fullscreen_client->workspace != c_ws)
c_ws->fullscreen_client = NULL;
client_enter_fullscreen(conn, client, false);
}
}
/* Check if we need to change something or if were already there */
if (c_ws->screen->current_workspace->num == (workspace-1)) {
if (c_ws->output->current_workspace->num == (workspace-1)) {
Client *last_focused = SLIST_FIRST(&(c_ws->focus_stack));
if (last_focused != SLIST_END(&(c_ws->focus_stack)))
set_focus(conn, last_focused, true);
@ -152,24 +168,28 @@ void workspace_show(xcb_connection_t *conn, int workspace) {
xcb_flush(conn);
}
ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"focus\"}");
return;
}
Workspace *old_workspace = c_ws;
c_ws = t_ws->screen->current_workspace = workspace_get(workspace-1);
c_ws = t_ws->output->current_workspace = workspace_get(workspace-1);
/* Unmap all clients of the old workspace */
workspace_unmap_clients(conn, old_workspace);
current_row = c_ws->current_row;
current_col = c_ws->current_col;
LOG("new current row = %d, current col = %d\n", current_row, current_col);
DLOG("new current row = %d, current col = %d\n", current_row, current_col);
ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"focus\"}");
workspace_map_clients(conn, c_ws);
/* POTENTIAL TO IMPROVE HERE: due to the call to _map_clients first and
* render_layout afterwards, there is a short flickering on the source
* workspace (assign ws 3 to screen 0, ws 4 to screen 1, create single
* workspace (assign ws 3 to output 0, ws 4 to output 1, create single
* client on ws 4, move it to ws 3, switch to ws 3, youll see the
* flickering). */
@ -184,63 +204,61 @@ void workspace_show(xcb_connection_t *conn, int workspace) {
/* We can warp the pointer only after the window has been
* reconfigured in render_layout, otherwise the pointer will
* be warped to the old position, which will not work when we
* moved it to another screen. */
* moved it to another output. */
if (last_focused != SLIST_END(&(c_ws->focus_stack)) && need_warp) {
client_warp_pointer_into(conn, last_focused);
xcb_flush(conn);
}
}
/*
* Parses the preferred_screen property of a workspace. You can either specify
* the screen number (it is not given that the screen numbering always stays
* the same) or the screen coordinates (exact coordinates, e.g. 1280 will match
* the screen starting at x=1280, but 1281 will not). For coordinates, you can
* either specify an x coordinate ("1280") or an y coordinate ("x800") or both
* ("1280x800").
* Assigns the given workspace to the given output by correctly updating its
* state and reconfiguring all the clients on this workspace.
*
* This is called when initializing a output and when re-assigning it to a
* different output which just got available (if you configured it to be on
* output 1 and you just plugged in output 1).
*
*/
static i3Screen *get_screen_from_preference(struct screens_head *slist, char *preference) {
i3Screen *screen;
char *rest;
int preferred_screen = strtol(preference, &rest, 10);
void workspace_assign_to(Workspace *ws, Output *output, bool hide_it) {
Client *client;
bool empty = true;
bool visible = workspace_is_visible(ws);
LOG("Getting screen for preference \"%s\" (%d)\n", preference, preferred_screen);
ws->output = output;
if ((rest == preference) || (preferred_screen >= num_screens)) {
int x = INT_MAX, y = INT_MAX;
if (strchr(preference, 'x') != NULL) {
/* Check if only the y coordinate was specified */
if (*preference == 'x')
y = atoi(preference+1);
else {
x = atoi(preference);
y = atoi(strchr(preference, 'x') + 1);
}
} else {
x = atoi(preference);
}
/* Copy the dimensions from the virtual output */
memcpy(&(ws->rect), &(ws->output->rect), sizeof(Rect));
LOG("Looking for screen at %d x %d\n", x, y);
ewmh_update_workarea();
TAILQ_FOREACH(screen, slist, screens)
if ((x == INT_MAX || screen->rect.x == x) &&
(y == INT_MAX || screen->rect.y == y)) {
LOG("found %p\n", screen);
return screen;
}
LOG("none found\n");
return NULL;
} else {
int c = 0;
TAILQ_FOREACH(screen, slist, screens)
if (c++ == preferred_screen)
return screen;
/* Force reconfiguration for each client on that workspace */
SLIST_FOREACH(client, &(ws->focus_stack), focus_clients) {
client->force_reconfigure = true;
empty = false;
}
return NULL;
if (empty)
return;
/* Render the workspace to reconfigure the clients. However, they will be visible now, so… */
render_workspace(global_conn, output, ws);
/* …unless we want to see them at the moment, we should hide that workspace */
if (visible && !hide_it)
return;
/* however, if this is the current workspace, we only need to adjust
* the outputs current_workspace pointer (and must not unmap the
* windows) */
if (c_ws == ws) {
DLOG("Need to adjust output->current_workspace...\n");
output->current_workspace = c_ws;
ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"focus\"}");
return;
}
workspace_unmap_clients(global_conn, ws);
}
/*
@ -250,35 +268,43 @@ static i3Screen *get_screen_from_preference(struct screens_head *slist, char *pr
* the screen is not attached at the moment.
*
*/
void workspace_initialize(Workspace *ws, i3Screen *screen) {
if (ws->screen != NULL) {
LOG("Workspace already initialized\n");
void workspace_initialize(Workspace *ws, Output *output, bool recheck) {
Output *old_output;
if (ws->output != NULL && !recheck) {
DLOG("Workspace already initialized\n");
return;
}
/* If this workspace has no preferred screen or if the screen it wants
* to be on is not available at the moment, we initialize it with
* the screen which was given */
if (ws->preferred_screen == NULL ||
(ws->screen = get_screen_from_preference(virtual_screens, ws->preferred_screen)) == NULL)
ws->screen = screen;
old_output = ws->output;
/* Copy the dimensions from the virtual screen */
memcpy(&(ws->rect), &(ws->screen->rect), sizeof(Rect));
/* If this workspace has no preferred output or if the output it wants
* to be on is not available at the moment, we initialize it with
* the output which was given */
if (ws->preferred_output == NULL ||
(ws->output = get_output_by_name(ws->preferred_output)) == NULL)
ws->output = output;
DLOG("old_output = %p, ws->output = %p\n", old_output, ws->output);
/* If the assignment did not change, we do not need to update anything */
if (old_output != NULL && ws->output == old_output)
return;
workspace_assign_to(ws, ws->output, false);
}
/*
* Gets the first unused workspace for the given screen, taking into account
* the preferred_screen setting of every workspace (workspace assignments).
* the preferred_output setting of every workspace (workspace assignments).
*
*/
Workspace *get_first_workspace_for_screen(struct screens_head *slist, i3Screen *screen) {
Workspace *get_first_workspace_for_output(Output *output) {
Workspace *result = NULL;
Workspace *ws;
TAILQ_FOREACH(ws, workspaces, workspaces) {
if (ws->preferred_screen == NULL ||
!screens_are_equal(get_screen_from_preference(slist, ws->preferred_screen), screen))
if (ws->preferred_output == NULL ||
get_output_by_name(ws->preferred_output) != output)
continue;
result = ws;
@ -287,9 +313,8 @@ Workspace *get_first_workspace_for_screen(struct screens_head *slist, i3Screen *
if (result == NULL) {
/* No assignment found, returning first unused workspace */
Workspace *ws;
TAILQ_FOREACH(ws, workspaces, workspaces) {
if (ws->screen != NULL)
if (ws->output != NULL)
continue;
result = ws;
@ -298,16 +323,15 @@ Workspace *get_first_workspace_for_screen(struct screens_head *slist, i3Screen *
}
if (result == NULL) {
LOG("No existing free workspace found to assign, creating a new one\n");
DLOG("No existing free workspace found to assign, creating a new one\n");
Workspace *ws;
int last_ws = 0;
TAILQ_FOREACH(ws, workspaces, workspaces)
last_ws = ws->num;
result = workspace_get(last_ws + 1);
}
workspace_initialize(result, screen);
workspace_initialize(result, output, false);
return result;
}
@ -359,7 +383,7 @@ void workspace_unmap_clients(xcb_connection_t *conn, Workspace *u_ws) {
int unmapped_clients = 0;
FOR_TABLE(u_ws)
CIRCLEQ_FOREACH(client, &(u_ws->table[cols][rows]->clients), clients) {
LOG("unmapping normal client %p / %p / %p\n", client, client->frame, client->child);
DLOG("unmapping normal client %p / %p / %p\n", client, client->frame, client->child);
client_unmap(conn, client);
unmapped_clients++;
}
@ -369,7 +393,7 @@ void workspace_unmap_clients(xcb_connection_t *conn, Workspace *u_ws) {
if (!client_is_floating(client))
continue;
LOG("unmapping floating client %p / %p / %p\n", client, client->frame, client->child);
DLOG("unmapping floating client %p / %p / %p\n", client, client->frame, client->child);
client_unmap(conn, client);
unmapped_clients++;
@ -380,15 +404,15 @@ void workspace_unmap_clients(xcb_connection_t *conn, Workspace *u_ws) {
if (unmapped_clients == 0 && u_ws != c_ws) {
/* Re-assign the workspace of all dock clients which use this workspace */
Client *dock;
LOG("workspace %p is empty\n", u_ws);
SLIST_FOREACH(dock, &(u_ws->screen->dock_clients), dock_clients) {
DLOG("workspace %p is empty\n", u_ws);
SLIST_FOREACH(dock, &(u_ws->output->dock_clients), dock_clients) {
if (dock->workspace != u_ws)
continue;
LOG("Re-assigning dock client to c_ws (%p)\n", c_ws);
DLOG("Re-assigning dock client to c_ws (%p)\n", c_ws);
dock->workspace = c_ws;
}
u_ws->screen = NULL;
u_ws->output = NULL;
}
/* Unmap the stack windows on the given workspace, if any */
@ -406,16 +430,50 @@ void workspace_unmap_clients(xcb_connection_t *conn, Workspace *u_ws) {
*/
void workspace_update_urgent_flag(Workspace *ws) {
Client *current;
bool old_flag = ws->urgent;
bool urgent = false;
SLIST_FOREACH(current, &(ws->focus_stack), focus_clients) {
if (!current->urgent)
continue;
ws->urgent = true;
return;
urgent = true;
break;
}
ws->urgent = false;
ws->urgent = urgent;
if (old_flag != urgent)
ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"urgent\"}");
}
/*
* Returns the width of the workspace.
*
*/
int workspace_width(Workspace *ws) {
return ws->rect.width;
}
/*
* Returns the effective height of the workspace (without the internal bar and
* without dock clients).
*
*/
int workspace_height(Workspace *ws) {
int height = ws->rect.height;
i3Font *font = load_font(global_conn, config.font);
/* Reserve space for dock clients */
Client *client;
SLIST_FOREACH(client, &(ws->output->dock_clients), dock_clients)
height -= client->desired_height;
/* Space for the internal bar */
if (!config.disable_workspace_bar)
height -= (font->height + 6);
return height;
}
/*

View File

@ -3,7 +3,7 @@
*
* i3 - an improved dynamic tiling window manager
*
* © 2009 Michael Stapelberg and contributors
* © 2009-2010 Michael Stapelberg and contributors
*
* See file LICENSE for license information.
*
@ -21,6 +21,7 @@
#include "i3.h"
#include "util.h"
#include "xcb.h"
#include "log.h"
TAILQ_HEAD(cached_fonts_head, Font) cached_fonts = TAILQ_HEAD_INITIALIZER(cached_fonts);
unsigned int xcb_numlock_mask;
@ -98,14 +99,6 @@ xcb_window_t create_window(xcb_connection_t *conn, Rect dims, uint16_t window_cl
/* If the window class is XCB_WINDOW_CLASS_INPUT_ONLY, depth has to be 0 */
uint16_t depth = (window_class == XCB_WINDOW_CLASS_INPUT_ONLY ? 0 : XCB_COPY_FROM_PARENT);
/* Use the default cursor (left pointer) */
if (cursor > -1) {
i3Font *cursor_font = load_font(conn, "cursor");
xcb_create_glyph_cursor(conn, cursor_id, cursor_font->id, cursor_font->id,
XCB_CURSOR_LEFT_PTR, XCB_CURSOR_LEFT_PTR + 1,
0, 0, 0, 65535, 65535, 65535);
}
xcb_create_window(conn,
depth,
result, /* the window id */
@ -117,8 +110,14 @@ xcb_window_t create_window(xcb_connection_t *conn, Rect dims, uint16_t window_cl
mask,
values);
if (cursor > -1)
xcb_change_window_attributes(conn, result, XCB_CW_CURSOR, &cursor_id);
/* Set the cursor */
i3Font *cursor_font = load_font(conn, "cursor");
xcb_create_glyph_cursor(conn, cursor_id, cursor_font->id, cursor_font->id,
(cursor == -1 ? XCB_CURSOR_LEFT_PTR : cursor),
(cursor == -1 ? XCB_CURSOR_LEFT_PTR : cursor) + 1,
0, 0, 0, 65535, 65535, 65535);
xcb_change_window_attributes(conn, result, XCB_CW_CURSOR, &cursor_id);
xcb_free_cursor(conn, cursor_id);
/* Map the window (= make it visible) */
if (map)
@ -270,7 +269,7 @@ void xcb_raise_window(xcb_connection_t *conn, xcb_window_t window) {
*
*/
void cached_pixmap_prepare(xcb_connection_t *conn, struct Cached_Pixmap *pixmap) {
LOG("preparing pixmap\n");
DLOG("preparing pixmap\n");
/* If the Rect did not change, the pixmap does not need to be recreated */
if (memcmp(&(pixmap->rect), pixmap->referred_rect, sizeof(Rect)) == 0)
@ -279,11 +278,11 @@ void cached_pixmap_prepare(xcb_connection_t *conn, struct Cached_Pixmap *pixmap)
memcpy(&(pixmap->rect), pixmap->referred_rect, sizeof(Rect));
if (pixmap->id == 0 || pixmap->gc == 0) {
LOG("Creating new pixmap...\n");
DLOG("Creating new pixmap...\n");
pixmap->id = xcb_generate_id(conn);
pixmap->gc = xcb_generate_id(conn);
} else {
LOG("Re-creating this pixmap...\n");
DLOG("Re-creating this pixmap...\n");
xcb_free_gc(conn, pixmap->gc);
xcb_free_pixmap(conn, pixmap->id);
}
@ -309,7 +308,7 @@ int predict_text_width(xcb_connection_t *conn, const char *font_pattern, char *t
cookie = xcb_query_text_extents(conn, font->id, length, (xcb_char2b_t*)text);
if ((reply = xcb_query_text_extents_reply(conn, cookie, &error)) == NULL) {
LOG("Could not get text extents (X error code %d)\n",
ELOG("Could not get text extents (X error code %d)\n",
error->error_code);
/* We return the rather safe guess of 7 pixels, because a
* rendering error is better than a crash. Plus, the user will
@ -321,3 +320,16 @@ int predict_text_width(xcb_connection_t *conn, const char *font_pattern, char *t
free(reply);
return width;
}
/*
* Configures the given window to have the size/position specified by given rect
*
*/
void xcb_set_window_rect(xcb_connection_t *conn, xcb_window_t window, Rect r) {
xcb_configure_window(conn, window,
XCB_CONFIG_WINDOW_X |
XCB_CONFIG_WINDOW_Y |
XCB_CONFIG_WINDOW_WIDTH |
XCB_CONFIG_WINDOW_HEIGHT,
&(r.x));
}

View File

@ -3,234 +3,96 @@
*
* i3 - an improved dynamic tiling window manager
*
* © 2009 Michael Stapelberg and contributors
* © 2009-2010 Michael Stapelberg and contributors
*
* See file LICENSE for license information.
*
* This is LEGACY code (we support RandR, which can do much more than
* Xinerama), but necessary for the poor users of the nVidia binary
* driver which does not support RandR in 2010 *sigh*.
*
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <assert.h>
#include <time.h>
#include <xcb/xcb.h>
#include <xcb/xinerama.h>
#include "queue.h"
#include "i3.h"
#include "data.h"
#include "table.h"
#include "util.h"
#include "xinerama.h"
#include "layout.h"
#include "xcb.h"
#include "config.h"
#include "workspace.h"
#include "log.h"
#include "randr.h"
/* This TAILQ of i3Screens stores the virtual screens, used for handling overlapping screens
* (xrandr --same-as) */
struct screens_head *virtual_screens;
static bool xinerama_enabled = true;
static int num_screens;
/*
* Returns true if both screen objects describe the same screen (checks their
* size and position).
* Looks in outputs for the Output whose start coordinates are x, y
*
*/
bool screens_are_equal(i3Screen *screen1, i3Screen *screen2) {
/* If one of both objects (or both) are NULL, we cannot compare them */
if (screen1 == NULL || screen2 == NULL)
return false;
/* If the pointers are equal, take the short-circuit */
if (screen1 == screen2)
return true;
/* Compare their size - other properties are not relevant to determine
* if a screen is equal to another one */
return (memcmp(&(screen1->rect), &(screen2->rect), sizeof(Rect)) == 0);
}
/*
* Looks in virtual_screens for the i3Screen whose start coordinates are x, y
*
*/
i3Screen *get_screen_at(int x, int y, struct screens_head *screenlist) {
i3Screen *screen;
TAILQ_FOREACH(screen, screenlist, screens)
if (screen->rect.x == x && screen->rect.y == y)
return screen;
static Output *get_screen_at(int x, int y) {
Output *output;
TAILQ_FOREACH(output, &outputs, outputs)
if (output->rect.x == x && output->rect.y == y)
return output;
return NULL;
}
/*
* Looks in virtual_screens for the i3Screen which contains coordinates x, y
*
*/
i3Screen *get_screen_containing(int x, int y) {
i3Screen *screen;
TAILQ_FOREACH(screen, virtual_screens, screens) {
LOG("comparing x=%d y=%d with x=%d and y=%d width %d height %d\n",
x, y, screen->rect.x, screen->rect.y, screen->rect.width, screen->rect.height);
if (x >= screen->rect.x && x < (screen->rect.x + screen->rect.width) &&
y >= screen->rect.y && y < (screen->rect.y + screen->rect.height))
return screen;
}
return NULL;
}
/*
* Gets the screen which is the last one in the given direction, for example the screen
* on the most bottom when direction == D_DOWN, the screen most right when direction == D_RIGHT
* and so on.
*
* This function always returns a screen.
*
*/
i3Screen *get_screen_most(direction_t direction, i3Screen *current) {
i3Screen *screen, *candidate = NULL;
int position = 0;
TAILQ_FOREACH(screen, virtual_screens, screens) {
/* Repeated calls of WIN determine the winner of the comparison */
#define WIN(variable, condition) \
if (variable condition) { \
candidate = screen; \
position = variable; \
} \
break;
if (((direction == D_UP) || (direction == D_DOWN)) &&
(current->rect.x != screen->rect.x))
continue;
if (((direction == D_LEFT) || (direction == D_RIGHT)) &&
(current->rect.y != screen->rect.y))
continue;
switch (direction) {
case D_UP:
WIN(screen->rect.y, <= position);
case D_DOWN:
WIN(screen->rect.y, >= position);
case D_LEFT:
WIN(screen->rect.x, <= position);
case D_RIGHT:
WIN(screen->rect.x, >= position);
}
}
assert(candidate != NULL);
return candidate;
}
static void initialize_screen(xcb_connection_t *conn, i3Screen *screen, Workspace *workspace) {
i3Font *font = load_font(conn, config.font);
workspace->screen = screen;
screen->current_workspace = workspace;
/* Create a bar for each screen */
Rect bar_rect = {screen->rect.x,
screen->rect.y + screen->rect.height - (font->height + 6),
screen->rect.x + screen->rect.width,
font->height + 6};
uint32_t mask = XCB_CW_OVERRIDE_REDIRECT | XCB_CW_EVENT_MASK;
uint32_t values[] = {1, XCB_EVENT_MASK_EXPOSURE | XCB_EVENT_MASK_BUTTON_PRESS};
screen->bar = create_window(conn, bar_rect, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_CURSOR_LEFT_PTR, true, mask, values);
screen->bargc = xcb_generate_id(conn);
xcb_create_gc(conn, screen->bargc, screen->bar, 0, 0);
SLIST_INIT(&(screen->dock_clients));
LOG("that is virtual screen at %d x %d with %d x %d\n",
screen->rect.x, screen->rect.y, screen->rect.width, screen->rect.height);
}
/*
* Fills virtual_screens with exactly one screen with width/height of the whole X server.
*
*/
static void disable_xinerama(xcb_connection_t *conn) {
xcb_screen_t *root_screen = xcb_setup_roots_iterator(xcb_get_setup(conn)).data;
i3Screen *s = calloc(sizeof(i3Screen), 1);
s->rect.x = 0;
s->rect.y = 0;
s->rect.width = root_screen->width_in_pixels;
s->rect.height = root_screen->height_in_pixels;
num_screens = 1;
s->num = 0;
TAILQ_INSERT_TAIL(virtual_screens, s, screens);
xinerama_enabled = false;
}
/*
* Gets the Xinerama screens and converts them to virtual i3Screens (only one screen for two
* Gets the Xinerama screens and converts them to virtual Outputs (only one screen for two
* Xinerama screen which are configured in clone mode) in the given screenlist
*
*/
static void query_screens(xcb_connection_t *conn, struct screens_head *screenlist) {
static void query_screens(xcb_connection_t *conn) {
xcb_xinerama_query_screens_reply_t *reply;
xcb_xinerama_screen_info_t *screen_info;
time_t before_trying = time(NULL);
/* Try repeatedly to find screens (there might be short timeframes in
* which the X server does not return any screens, such as when rotating
* screens), but not longer than 5 seconds (strictly speaking, only four
* seconds of trying are guaranteed due to the 1-second-resolution) */
while ((time(NULL) - before_trying) < 5) {
reply = xcb_xinerama_query_screens_reply(conn, xcb_xinerama_query_screens_unchecked(conn), NULL);
if (!reply) {
LOG("Couldn't get Xinerama screens\n");
return;
}
screen_info = xcb_xinerama_query_screens_screen_info(reply);
int screens = xcb_xinerama_query_screens_screen_info_length(reply);
num_screens = 0;
reply = xcb_xinerama_query_screens_reply(conn, xcb_xinerama_query_screens_unchecked(conn), NULL);
if (!reply) {
ELOG("Couldn't get Xinerama screens\n");
return;
}
screen_info = xcb_xinerama_query_screens_screen_info(reply);
int screens = xcb_xinerama_query_screens_screen_info_length(reply);
for (int screen = 0; screen < screens; screen++) {
i3Screen *s = get_screen_at(screen_info[screen].x_org, screen_info[screen].y_org, screenlist);
if (s != NULL) {
/* This screen already exists. We use the littlest screen so that the user
can always see the complete workspace */
s->rect.width = min(s->rect.width, screen_info[screen].width);
s->rect.height = min(s->rect.height, screen_info[screen].height);
} else {
s = calloc(sizeof(i3Screen), 1);
s->rect.x = screen_info[screen].x_org;
s->rect.y = screen_info[screen].y_org;
s->rect.width = screen_info[screen].width;
s->rect.height = screen_info[screen].height;
/* We always treat the screen at 0x0 as the primary screen */
if (s->rect.x == 0 && s->rect.y == 0)
TAILQ_INSERT_HEAD(screenlist, s, screens);
else TAILQ_INSERT_TAIL(screenlist, s, screens);
num_screens++;
}
LOG("found Xinerama screen: %d x %d at %d x %d\n",
screen_info[screen].width, screen_info[screen].height,
screen_info[screen].x_org, screen_info[screen].y_org);
for (int screen = 0; screen < screens; screen++) {
Output *s = get_screen_at(screen_info[screen].x_org, screen_info[screen].y_org);
if (s != NULL) {
DLOG("Re-used old Xinerama screen %p\n", s);
/* This screen already exists. We use the littlest screen so that the user
can always see the complete workspace */
s->rect.width = min(s->rect.width, screen_info[screen].width);
s->rect.height = min(s->rect.height, screen_info[screen].height);
} else {
s = scalloc(sizeof(Output));
asprintf(&(s->name), "xinerama-%d", num_screens);
DLOG("Created new Xinerama screen %s (%p)\n", s->name, s);
s->active = true;
s->rect.x = screen_info[screen].x_org;
s->rect.y = screen_info[screen].y_org;
s->rect.width = screen_info[screen].width;
s->rect.height = screen_info[screen].height;
/* We always treat the screen at 0x0 as the primary screen */
if (s->rect.x == 0 && s->rect.y == 0)
TAILQ_INSERT_HEAD(&outputs, s, outputs);
else TAILQ_INSERT_TAIL(&outputs, s, outputs);
num_screens++;
}
free(reply);
DLOG("found Xinerama screen: %d x %d at %d x %d\n",
screen_info[screen].width, screen_info[screen].height,
screen_info[screen].x_org, screen_info[screen].y_org);
}
if (num_screens == 0) {
LOG("No screens found. This is weird. Trying again...\n");
continue;
}
free(reply);
break;
if (num_screens == 0) {
ELOG("No screens found. Please fix your setup. i3 will exit now.\n");
exit(0);
}
}
@ -240,198 +102,27 @@ static void query_screens(xcb_connection_t *conn, struct screens_head *screenlis
*
*/
void initialize_xinerama(xcb_connection_t *conn) {
virtual_screens = scalloc(sizeof(struct screens_head));
TAILQ_INIT(virtual_screens);
if (!xcb_get_extension_data(conn, &xcb_xinerama_id)->present) {
LOG("Xinerama extension not found, disabling.\n");
disable_xinerama(conn);
DLOG("Xinerama extension not found, disabling.\n");
disable_randr(conn);
} else {
xcb_xinerama_is_active_reply_t *reply;
reply = xcb_xinerama_is_active_reply(conn, xcb_xinerama_is_active(conn), NULL);
if (reply == NULL || !reply->state) {
LOG("Xinerama is not active (in your X-Server), disabling.\n");
disable_xinerama(conn);
DLOG("Xinerama is not active (in your X-Server), disabling.\n");
disable_randr(conn);
} else
query_screens(conn, virtual_screens);
query_screens(conn);
FREE(reply);
}
i3Screen *screen;
num_screens = 0;
/* Just go through each workspace and associate as many screens as we can. */
TAILQ_FOREACH(screen, virtual_screens, screens) {
screen->num = num_screens;
num_screens++;
Workspace *ws = get_first_workspace_for_screen(virtual_screens, screen);
initialize_screen(conn, screen, ws);
}
}
/*
* This is called when the rootwindow receives a configure_notify event and therefore the
* number/position of the Xinerama screens could have changed.
*
*/
void xinerama_requery_screens(xcb_connection_t *conn) {
i3Font *font = load_font(conn, config.font);
/* POSSIBLE PROBLEM: Is the order of the Xinerama screens always constant? That is, can
it change when I move the --right-of video projector to --left-of? */
if (!xinerama_enabled) {
LOG("Xinerama is disabled\n");
return;
}
/* We use a separate copy to diff with the previous set of screens */
struct screens_head *new_screens = scalloc(sizeof(struct screens_head));
TAILQ_INIT(new_screens);
query_screens(conn, new_screens);
i3Screen *first = TAILQ_FIRST(new_screens),
*screen,
*old_screen;
int screen_count = 0;
/* Mark each workspace which currently is assigned to a screen, so we
* can garbage-collect afterwards */
Output *output;
Workspace *ws;
TAILQ_FOREACH(ws, workspaces, workspaces)
ws->reassigned = (ws->screen == NULL);
TAILQ_FOREACH(screen, new_screens, screens) {
screen->num = screen_count;
screen->current_workspace = NULL;
TAILQ_FOREACH(old_screen, virtual_screens, screens) {
if (old_screen->num != screen_count)
continue;
LOG("Found a matching screen\n");
/* Use the same workspace */
screen->current_workspace = old_screen->current_workspace;
/* Re-use the old bar window */
screen->bar = old_screen->bar;
screen->bargc = old_screen->bargc;
LOG("old_screen->bar = %p\n", old_screen->bar);
Rect bar_rect = {screen->rect.x,
screen->rect.y + screen->rect.height - (font->height + 6),
screen->rect.x + screen->rect.width,
font->height + 6};
LOG("configuring bar to be at %d x %d with %d x %d\n",
bar_rect.x, bar_rect.y, bar_rect.height, bar_rect.width);
xcb_configure_window(conn, screen->bar, XCB_CONFIG_WINDOW_X |
XCB_CONFIG_WINDOW_Y |
XCB_CONFIG_WINDOW_WIDTH |
XCB_CONFIG_WINDOW_HEIGHT, &(bar_rect.x));
/* Copy the list head for the dock clients */
screen->dock_clients = old_screen->dock_clients;
SLIST_INIT(&(old_screen->dock_clients));
/* Update the dimensions */
Workspace *ws;
TAILQ_FOREACH(ws, workspaces, workspaces) {
if (ws->screen != old_screen)
continue;
LOG("re-assigning ws %d\n", ws->num);
memcpy(&(ws->rect), &(screen->rect), sizeof(Rect));
ws->screen = screen;
ws->reassigned = true;
}
break;
}
if (screen->current_workspace == NULL) {
/* Find the first unused workspace, preferring the ones
* which are assigned to this screen and initialize
* the screen with it. */
LOG("getting first ws for screen %p\n", screen);
Workspace *ws = get_first_workspace_for_screen(new_screens, screen);
initialize_screen(conn, screen, ws);
ws->reassigned = true;
/* As this workspace just got visible (we got a new screen
* without workspace), we need to map its clients */
workspace_map_clients(conn, ws);
}
screen_count++;
/* Just go through each active output and associate one workspace */
TAILQ_FOREACH(output, &outputs, outputs) {
ws = get_first_workspace_for_output(output);
initialize_output(conn, output, ws);
}
/* check for dock_clients which are out of bounds */
TAILQ_FOREACH(old_screen, virtual_screens, screens) {
if (SLIST_EMPTY(&(old_screen->dock_clients)))
continue;
LOG("dock_clients out of bounds at screen %p, reassigning\n", old_screen);
if (SLIST_EMPTY(&(first->dock_clients))) {
first->dock_clients = old_screen->dock_clients;
continue;
}
/* We need to merge the lists */
Client *dock_client;
while (!SLIST_EMPTY(&(old_screen->dock_clients))) {
dock_client = SLIST_FIRST(&(old_screen->dock_clients));
SLIST_INSERT_HEAD(&(first->dock_clients), dock_client, dock_clients);
SLIST_REMOVE_HEAD(&(old_screen->dock_clients), dock_clients);
}
}
/* Check for workspaces which are out of bounds */
TAILQ_FOREACH(ws, workspaces, workspaces) {
if (ws->reassigned)
continue;
Client *client;
LOG("Closing bar window (%p)\n", ws->screen->bar);
xcb_destroy_window(conn, ws->screen->bar);
LOG("Workspace %d's screen out of bounds, assigning to first screen\n", ws->num + 1);
ws->screen = first;
memcpy(&(ws->rect), &(first->rect), sizeof(Rect));
/* Force reconfiguration for each client on that workspace */
FOR_TABLE(ws)
CIRCLEQ_FOREACH(client, &(ws->table[cols][rows]->clients), clients)
client->force_reconfigure = true;
/* Render the workspace to reconfigure the clients. However, they will be visible now, so… */
render_workspace(conn, first, ws);
/* …unless we want to see them at the moment, we should hide that workspace */
if (workspace_is_visible(ws))
continue;
workspace_unmap_clients(conn, ws);
if (c_ws == ws) {
LOG("Need to adjust c_ws...\n");
c_ws = first->current_workspace;
}
}
xcb_flush(conn);
/* Free the old list */
while (!TAILQ_EMPTY(virtual_screens)) {
screen = TAILQ_FIRST(virtual_screens);
TAILQ_REMOVE(virtual_screens, screen, screens);
free(screen);
}
free(virtual_screens);
virtual_screens = new_screens;
LOG("Current workspace is now: %d\n", first->current_workspace);
render_layout(conn);
}