Merge branch 'next'
This commit is contained in:
@ -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();
|
||||
}
|
||||
|
||||
%%
|
||||
|
186
src/cfgparse.y
186
src/cfgparse.y
@ -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 don’t 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);
|
||||
}
|
||||
;
|
||||
|
71
src/click.c
71
src/click.c
@ -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 client’s 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);
|
||||
|
||||
/* Let’s see if this was on the borders (= resize). If not, we’re 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_
|
||||
/* Don’t 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)))
|
||||
|
176
src/client.c
176
src/client.c
@ -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, let’s 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);
|
||||
|
||||
/* Child’s 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 it’s 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 haven’t changed, it would not be
|
||||
re-configured if we don’t 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 client’s 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;
|
||||
}
|
||||
|
357
src/commands.c
357
src/commands.c
@ -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, we’re 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 {
|
||||
/* Let’s 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 {
|
||||
/* Let’s 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 it’d 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 we’re 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 we’re 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", ×) != 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 it’d 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 screen’s 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);
|
||||
|
626
src/config.c
626
src/config.c
@ -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 it’s 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 event’s 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 user’s 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 we’re 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 we’re 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
43
src/container.c
Normal 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;
|
||||
}
|
12
src/debug.c
12
src/debug.c
@ -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
103
src/ewmh.c
Normal 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,
|
||||
¤t_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);
|
||||
}
|
282
src/floating.c
282
src/floating.c
@ -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 doesn’t 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, ¶ms);
|
||||
|
||||
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 doesn’t 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, ¶ms);
|
||||
}
|
||||
|
||||
|
||||
@ -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);
|
||||
|
318
src/handlers.c
318
src/handlers.c
@ -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 don’t want it,
|
||||
since it’d 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 event’s 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 it’s 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
399
src/ipc.c
@ -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) {
|
||||
|
230
src/layout.c
230
src/layout.c
@ -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 window’s 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 container’s 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 client’s 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
121
src/log.c
Normal 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);
|
||||
}
|
241
src/mainx.c
241
src/mainx.c
@ -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 it’s 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 = window’s 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 we’re 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 libev’s 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 */
|
||||
|
103
src/manage.c
103
src/manage.c
@ -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 it’s a dock we can’t 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
532
src/randr.c
Normal 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 don’t 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);
|
||||
}
|
175
src/resize.c
175
src/resize.c
@ -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 doesn’t 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, ¶ms);
|
||||
|
||||
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
224
src/sighandler.c
Normal 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");
|
||||
}
|
63
src/table.c
63
src/table.c
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
127
src/util.c
127
src/util.c
@ -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 we’re 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__)
|
||||
|
||||
/*
|
||||
|
238
src/workspace.c
238
src/workspace.c
@ -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 we’re 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 we’re 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, you’ll 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 output’s 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;
|
||||
}
|
||||
|
||||
/*
|
||||
|
42
src/xcb.c
42
src/xcb.c
@ -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));
|
||||
}
|
||||
|
435
src/xinerama.c
435
src/xinerama.c
@ -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);
|
||||
}
|
||||
|
Reference in New Issue
Block a user